From 073ccd276a5977a07eef23ff9145fc2a53506e8e Mon Sep 17 00:00:00 2001 From: Reinhard Tartler Date: Sat, 14 Nov 2020 17:06:39 -0500 Subject: [PATCH] New upstream version 1.0.1 --- .gitattributes | 3 + .gitignore | 19 +- .gitmodules | 5 + .travis.yml | 59 +- AUTHORS | 28 - BUGS | 3 - Changelog | 211 +- INSTALLME | 67 - Makefile | 284 +- README.md | 143 +- TODO | 3 - applications/Makefile | 22 +- applications/{ => deprecated}/mp42ts/Makefile | 14 +- applications/{ => deprecated}/mp42ts/main.c | 248 +- applications/deprecated/mp42ts/mp42ts.vcxproj | 265 + .../deprecated/old_arch/GPAX/GPAX.cpp | 76 + .../deprecated/old_arch/GPAX/GPAX.def | 9 + .../deprecated/old_arch/GPAX/GPAX.dsp | 173 + applications/deprecated/old_arch/GPAX/GPAX.h | 570 + .../deprecated/old_arch/GPAX/GPAX.idl | 92 + applications/deprecated/old_arch/GPAX/GPAX.rc | 151 + .../deprecated/old_arch/GPAX/GPAX.rgs | 175 + .../deprecated/old_arch/GPAX/GPAX.vcxproj | 376 + .../deprecated/old_arch/GPAX/GPAXPlugin.cpp | 710 + .../deprecated/old_arch/GPAX/GPAXPlugin.h | 269 + .../deprecated/old_arch/GPAX/GPAX_i.c | 178 + .../deprecated/old_arch/GPAX/GPAX_p.c | 603 + .../deprecated/old_arch/GPAX/GPAXps.def | 11 + .../deprecated/old_arch/GPAX/GPAXps.mk | 16 + .../deprecated/old_arch/GPAX/StdAfx.cpp | 12 + .../deprecated/old_arch/GPAX/StdAfx.h | 34 + .../deprecated/old_arch/GPAX/dlldata.c | 38 + .../deprecated/old_arch/GPAX/gpax.bmp | Bin 0 -> 246 bytes .../deprecated/old_arch/GPAX/resource.h | 24 + .../old_arch}/dashcast/Makefile | 22 +- .../old_arch}/dashcast/audio_data.c | 0 .../old_arch}/dashcast/audio_data.h | 3 +- .../old_arch}/dashcast/audio_decoder.c | 16 +- .../old_arch}/dashcast/audio_decoder.h | 4 +- .../old_arch}/dashcast/audio_encoder.c | 16 +- .../old_arch}/dashcast/audio_encoder.h | 0 .../old_arch}/dashcast/audio_muxer.c | 14 +- .../old_arch}/dashcast/audio_muxer.h | 4 +- .../old_arch}/dashcast/circular_buffer.c | 0 .../old_arch}/dashcast/circular_buffer.h | 0 .../old_arch}/dashcast/cmd_data.c | 0 .../old_arch}/dashcast/cmd_data.h | 0 .../old_arch}/dashcast/controler.c | 2 +- .../old_arch}/dashcast/controler.h | 0 .../old_arch}/dashcast/dashcast.c | 0 .../old_arch}/dashcast/libav_compat.h | 0 .../old_arch}/dashcast/message_queue.c | 0 .../old_arch}/dashcast/message_queue.h | 0 .../old_arch}/dashcast/register.c | 0 .../old_arch}/dashcast/register.h | 0 .../{ => deprecated/old_arch}/dashcast/task.c | 0 .../{ => deprecated/old_arch}/dashcast/task.h | 0 .../old_arch}/dashcast/video_data.c | 0 .../old_arch}/dashcast/video_data.h | 2 +- .../old_arch}/dashcast/video_decoder.c | 2 - .../old_arch}/dashcast/video_decoder.h | 0 .../old_arch}/dashcast/video_encoder.c | 8 +- .../old_arch}/dashcast/video_encoder.h | 0 .../old_arch}/dashcast/video_muxer.c | 18 +- .../old_arch}/dashcast/video_muxer.h | 0 .../old_arch}/dashcast/video_scaler.c | 0 .../old_arch}/dashcast/video_scaler.h | 0 .../old_arch}/mp42avi/Makefile | 4 +- .../{ => deprecated/old_arch}/mp42avi/main.c | 29 +- .../old_arch/osmo4_sym/aif/osmo4_icon.bmp | Bin 0 -> 5862 bytes .../osmo4_sym/aif/osmo4_icon_mask.bmp | Bin 0 -> 414 bytes .../old_arch/osmo4_sym/aif/osmo4_menu.bmp | Bin 0 -> 3766 bytes .../osmo4_sym/aif/osmo4_menu_mask.bmp | Bin 0 -> 294 bytes .../old_arch/osmo4_sym/aif/osmo4aif.rss | 14 + .../deprecated/old_arch/osmo4_sym/osmo4.cpp | 82 + .../deprecated/old_arch/osmo4_sym/osmo4.h | 38 + .../old_arch/osmo4_sym/osmo4_ui.cpp | 605 + .../deprecated/old_arch/osmo4_sym/osmo4_ui.h | 181 + .../old_arch/osmo4_sym/osmo4_view.cpp | 587 + .../old_arch/osmo4_sym/osmo4_view.h | 171 + .../old_arch/osmo4_sym/playlist.cpp | 529 + .../deprecated/old_arch/osmo4_sym/playlist.h | 114 + .../old_arch/osmo4_sym/res/osmo4.rss | 19 + .../old_arch/osmo4_sym/res/osmo4.svg | 35 + .../old_arch/osmo4_sym/res/osmo4_caption.rss | 21 + .../old_arch/osmo4_sym/res/osmo4_gen.rss | 59 + .../old_arch/osmo4_sym/res/osmo4_reg.rss | 31 + .../old_arch/osmo4_w32/AddressBar.cpp | 182 + .../old_arch/osmo4_w32/AddressBar.h | 90 + .../old_arch/osmo4_w32/FileProps.cpp | 622 + .../deprecated/old_arch/osmo4_w32/FileProps.h | 70 + .../deprecated/old_arch/osmo4_w32/MainFrm.cpp | 1588 + .../deprecated/old_arch/osmo4_w32/MainFrm.h | 224 + .../deprecated/old_arch/osmo4_w32/OpenUrl.cpp | 98 + .../deprecated/old_arch/osmo4_w32/OpenUrl.h | 49 + .../deprecated/old_arch/osmo4_w32/Options.cpp | 2080 + .../deprecated/old_arch/osmo4_w32/Options.h | 624 + .../deprecated/old_arch/osmo4_w32/Osmo4.cpp | 944 + .../deprecated/old_arch/osmo4_w32/Osmo4.h | 115 + .../deprecated/old_arch/osmo4_w32/Osmo4.rc | 885 + .../old_arch/osmo4_w32/Osmo4.vcxproj | 387 + .../old_arch/osmo4_w32/Playlist.cpp | 969 + .../deprecated/old_arch/osmo4_w32/Playlist.h | 120 + .../deprecated/old_arch/osmo4_w32/Sliders.cpp | 148 + .../deprecated/old_arch/osmo4_w32/Sliders.h | 62 + .../deprecated/old_arch/osmo4_w32/StdAfx.cpp | 8 + .../deprecated/old_arch/osmo4_w32/StdAfx.h | 30 + .../old_arch/osmo4_w32/res/Osmo4.rc2 | 13 + .../old_arch/osmo4_w32/res/error.ico | Bin 0 -> 766 bytes .../old_arch/osmo4_w32/res/maintool.bmp | Bin 0 -> 1438 bytes .../old_arch/osmo4_w32/res/message.ico | Bin 0 -> 766 bytes .../old_arch/osmo4_w32/res/osmo4.ico | Bin 0 -> 15086 bytes .../old_arch/osmo4_w32/res/pause.ico | Bin 0 -> 1078 bytes .../old_arch/osmo4_w32/res/play.ico | Bin 0 -> 1078 bytes .../old_arch/osmo4_w32/res/playlist.bmp | Bin 0 -> 958 bytes .../old_arch/osmo4_w32/res/stop.ico | Bin 0 -> 1078 bytes .../deprecated/old_arch/osmo4_w32/resource.h | 323 + .../deprecated/old_arch/osmo4_wce/MainFrm.cpp | 661 + .../deprecated/old_arch/osmo4_wce/MainFrm.h | 151 + .../deprecated/old_arch/osmo4_wce/OpenDlg.cpp | 92 + .../deprecated/old_arch/osmo4_wce/OpenDlg.h | 47 + .../deprecated/old_arch/osmo4_wce/Options.cpp | 1237 + .../deprecated/old_arch/osmo4_wce/Options.h | 388 + .../deprecated/old_arch/osmo4_wce/Osmo4.cpp | 638 + .../deprecated/old_arch/osmo4_wce/Osmo4.h | 113 + .../deprecated/old_arch/osmo4_wce/Osmo4.rc | 758 + .../old_arch/osmo4_wce/ProgressBar.cpp | 133 + .../old_arch/osmo4_wce/ProgressBar.h | 53 + .../deprecated/old_arch/osmo4_wce/Resource.h | 170 + .../deprecated/old_arch/osmo4_wce/StdAfx.cpp | 6 + .../deprecated/old_arch/osmo4_wce/StdAfx.h | 39 + .../deprecated/old_arch/osmo4_wce/newres.h | 28 + .../old_arch/osmo4_wce/res/Cmdbar.bmp | Bin 0 -> 886 bytes .../old_arch/osmo4_wce/res/Osmo4.ico | Bin 0 -> 1078 bytes .../old_arch/osmo4_wce/res/Osmo4.rc2 | 13 + .../release/install/archive.bat | 2 +- .../release/install/build_installer.bat | 2 +- .../release/install/gpac.inf | 0 .../release/install/readme.txt | 2 +- .../old_arch/osmo4_wx/Darwin.Info.plist | 24 + .../osmo4_wx/Darwin.InfoPlist.strings | Bin 0 -> 504 bytes .../old_arch/osmo4_wx/Darwin.Osmo.icns | Bin 0 -> 38570 bytes .../deprecated/old_arch/osmo4_wx/Makefile | 85 + .../deprecated/old_arch/osmo4_wx/Playlist.cpp | 836 + .../deprecated/old_arch/osmo4_wx/Playlist.h | 135 + .../old_arch/osmo4_wx/fileprops.cpp | 650 + .../deprecated/old_arch/osmo4_wx/fileprops.h | 84 + .../deprecated/old_arch/osmo4_wx/menubtn.cpp | 870 + .../deprecated/old_arch/osmo4_wx/menubtn.h | 356 + .../deprecated/old_arch/osmo4_wx/osmo4.ico | Bin 0 -> 15086 bytes .../deprecated/old_arch/osmo4_wx/osmo4.xpm | 301 + .../deprecated/old_arch/osmo4_wx/playlist.xpm | 158 + .../deprecated/old_arch/osmo4_wx/resource.h | 16 + .../deprecated/old_arch/osmo4_wx/toolbar.xpm | 254 + .../old_arch/osmo4_wx/wxGPACControl.cpp | 1051 + .../old_arch/osmo4_wx/wxGPACControl.h | 138 + .../deprecated/old_arch/osmo4_wx/wxOsmo4.cpp | 2392 + .../deprecated/old_arch/osmo4_wx/wxOsmo4.h | 391 + .../deprecated/old_arch/osmo4_wx/wxOsmo4.rc | 72 + .../deprecated/old_arch/osmophone/Osmo4.ico | Bin 0 -> 1078 bytes .../deprecated/old_arch/osmophone/main.cpp | 1497 + .../deprecated/old_arch/osmophone/newres.h | 41 + .../old_arch/osmophone/openfile.cpp | 405 + .../old_arch/osmophone/osmophone.rc | 425 + .../old_arch/osmophone/osmophone.vcxproj | 343 + .../deprecated/old_arch/osmophone/resource.h | 169 + .../deprecated/old_arch/osmozilla/Makefile | 109 + .../old_arch/osmozilla/nsIOsmozilla.h | 148 + .../old_arch/osmozilla/nsIOsmozilla.idl | 10 + .../old_arch/osmozilla/nsIOsmozilla.xpt_linux | Bin 0 -> 180 bytes .../old_arch/osmozilla/nsIOsmozilla.xpt_w32 | Bin 0 -> 180 bytes .../old_arch/osmozilla/osmo_npapi.cpp | 854 + .../old_arch/osmozilla/osmo_npapi.h | 136 + .../old_arch/osmozilla/osmozilla.cpp | 531 + .../old_arch/osmozilla/osmozilla.def | 6 + .../deprecated/old_arch/osmozilla/osmozilla.h | 75 + .../old_arch/osmozilla/osmozilla.png | Bin 0 -> 121239 bytes .../old_arch/osmozilla/osmozilla.rc | 128 + .../old_arch/osmozilla/osmozilla.vcxproj | 304 + .../deprecated/old_arch/osmozilla/readme.txt | 8 + .../deprecated/old_arch/osmozilla/resource.h | 21 + applications/generators/MPEG4/Makefile | 4 +- applications/generators/MPEG4/main.c | 38 +- applications/generators/Makefile | 4 +- applications/generators/SVG/Makefile | 4 +- applications/generators/SVG/laser.c | 4 +- applications/generators/SVG/main.c | 6 +- applications/generators/SVG/v2.c | 3 - applications/generators/WebGLGen/Makefile | 52 + applications/generators/WebGLGen/WGLGen | Bin 0 -> 27288 bytes applications/generators/WebGLGen/main.c | 652 + applications/generators/WebGLGen/webgl1.idl | 708 + applications/generators/WebGLGen/webgl2.idl | 584 + applications/generators/X3D/Makefile | 4 +- applications/generators/X3D/main.c | 25 +- applications/gpac/Info.plist | 16 + applications/gpac/Makefile | 65 + applications/gpac/main.c | 4039 ++ applications/mp4box/Makefile | 22 +- applications/mp4box/filedump.c | 2028 +- applications/mp4box/fileimport.c | 2313 +- applications/mp4box/live.c | 366 +- applications/mp4box/main.c | 4263 +- applications/mp4box/mp4box.h | 145 + applications/mp4box/wrapper.c | 2 - applications/mp4client/Makefile | 25 +- applications/mp4client/carbon_events.c | 2 +- applications/mp4client/extract.c | 984 - applications/mp4client/main.c | 2147 +- applications/mp4client/mp4client.rc | 2 +- applications/testapps/atscdmx/Makefile | 50 + applications/testapps/atscdmx/main.c | 151 + applications/testapps/bmp4demux/Makefile | 4 +- applications/testapps/broadcaster/Makefile | 4 +- .../testapps/broadcaster/RTP_serv_generator.c | 3 +- .../testapps/broadcaster/broadcaster.c | 14 +- .../testapps/broadcaster/sdp_generator.c | 2 +- applications/testapps/fmp4demux/Makefile | 4 +- applications/testapps/fmp4demux/main.c | 2 - applications/testapps/hevcbench/Makefile | 4 +- applications/testapps/hevcbench/main.c | 10 +- applications/testapps/loadcompare/Makefile | 4 +- .../testapps/loadcompare/loadcompare.c | 15 +- applications/testapps/mpedemux/Makefile | 8 +- applications/testapps/player_api/main.c | 2 +- applications/testapps/segmp4demux/Makefile | 4 +- applications/testapps/svg2bifs/main.c | 29 +- applications/{ => testapps}/ts2hds/Makefile | 4 +- applications/{ => testapps}/ts2hds/f4m.c | 0 applications/{ => testapps}/ts2hds/f4v.c | 11 +- applications/{ => testapps}/ts2hds/main.c | 3 +- applications/{ => testapps}/ts2hds/ts2hds.h | 0 applications/testapps/ts2udp/main.c | 4 +- applications/{ => testapps}/udptsseg/Makefile | 6 +- applications/{ => testapps}/udptsseg/main.c | 6 +- .../{ => testapps}/udptsseg/udptsseg.dsp | 0 .../{ => testapps}/udptsseg/udptsseg.vcproj | 0 change_version.sh | 38 +- configure | 925 +- doc/CODING_STYLE | 133 - doc/INSTALL.gcc | 116 - doc/INSTALL.gpe | 100 - doc/INSTALL.symbian | 84 - doc/INSTALL.w32 | 217 - doc/INSTALL.wCE | 134 - doc/configuration.html | 999 - doc/gpac.mp4 | Bin 2688 -> 0 bytes doc/man/dashcast.1 | 161 - doc/man/gpac.1 | 447 - doc/man/mp42avi.1 | 69 - doc/man/mp42ts.1 | 172 - doc/man/mp4box.1 | 808 - doc/man/mp4client.1 | 301 - generate_installer.bat | 3 +- gpac.spec | 61 +- gui/extensions/player/init.js | 15 - gui/gui_old.bt | 42 - gui/gui_old.js | 2104 - include/gpac/00_doxy.h | 116 + include/gpac/Remotery.h | 679 + include/gpac/ait.h | 4 +- include/gpac/atsc.h | 229 + include/gpac/avparse.h | 722 +- include/gpac/base_coding.h | 96 +- include/gpac/bifs.h | 150 +- include/gpac/bitstream.h | 718 +- include/gpac/cache.h | 237 +- include/gpac/color.h | 249 +- include/gpac/compositor.h | 307 +- include/gpac/config_file.h | 239 +- include/gpac/configuration.h | 69 +- include/gpac/constants.h | 1312 +- include/gpac/crypt.h | 246 +- include/gpac/crypt_tools.h | 251 + include/gpac/dash.h | 944 +- include/gpac/download.h | 581 +- include/gpac/dsmcc.h | 6 +- include/gpac/esi.h | 213 - include/gpac/events.h | 184 +- include/gpac/events_constants.h | 50 +- include/gpac/evg.h | 867 + include/gpac/filestreamer.h | 131 - include/gpac/filters.h | 3811 ++ include/gpac/html5_media.h | 102 +- include/gpac/html5_mse.h | 31 +- include/gpac/ietf.h | 1018 +- include/gpac/internal/avilib.h | 44 +- include/gpac/internal/bifs_dev.h | 3 +- include/gpac/internal/camera.h | 6 +- include/gpac/internal/compositor_dev.h | 1435 +- include/gpac/internal/crypt_dev.h | 180 +- include/gpac/internal/dvb_mpe_dev.h | 8 +- include/gpac/internal/ietf_dev.h | 243 +- include/gpac/internal/isomedia_dev.h | 1308 +- include/gpac/internal/laser_dev.h | 8 +- include/gpac/internal/m3u8.h | 20 +- include/gpac/internal/media_dev.h | 373 +- include/gpac/internal/mesh.h | 2 + include/gpac/internal/mpd.h | 478 - include/gpac/internal/odf_dev.h | 5 +- include/gpac/internal/odf_parse_common.h | 11 +- include/gpac/internal/ogg.h | 26 +- include/gpac/internal/scenegraph_dev.h | 201 +- include/gpac/internal/smjs_api.h | 358 - include/gpac/internal/swf_dev.h | 17 +- include/gpac/internal/terminal_dev.h | 1313 - include/gpac/internal/vobsub.h | 18 +- include/gpac/ismacryp.h | 189 - include/gpac/iso639.h | 48 +- include/gpac/isomedia.h | 6982 ++- include/gpac/laser.h | 132 +- include/gpac/list.h | 234 +- include/gpac/main.h | 253 + include/gpac/map.h | 213 - include/gpac/maths.h | 983 +- include/gpac/media_tools.h | 1019 +- include/gpac/mediaobject.h | 364 +- include/gpac/module.h | 312 +- include/gpac/modules/audio_out.h | 73 +- include/gpac/modules/codec.h | 149 +- .../modules/{term_ext.h => compositor_ext.h} | 39 +- include/gpac/modules/font.h | 2 +- include/gpac/modules/hardcoded_proto.h | 2 +- include/gpac/modules/js_usr.h | 70 - include/gpac/modules/raster2d.h | 262 - include/gpac/modules/service.h | 848 - include/gpac/modules/video_out.h | 8 +- include/gpac/mpd.h | 1136 + include/gpac/mpeg4_odf.h | 674 +- include/gpac/mpegts.h | 1437 +- include/gpac/network.h | 720 +- include/gpac/nodes_mpeg4.h | 3 +- include/gpac/nodes_svg.h | 196 - include/gpac/nodes_xbl.h | 70 - include/gpac/options.h | 300 +- include/gpac/path2d.h | 544 +- include/gpac/ringbuffer.h | 94 - include/gpac/rtp_streamer.h | 267 +- include/gpac/scene_engine.h | 233 +- include/gpac/scene_manager.h | 336 +- include/gpac/scenegraph.h | 717 +- include/gpac/scenegraph_svg.h | 750 +- include/gpac/scenegraph_vrml.h | 628 +- include/gpac/setup.h | 355 +- include/gpac/svg_types.h | 206 +- include/gpac/sync_layer.h | 66 +- include/gpac/term_info.h | 231 +- include/gpac/terminal.h | 318 +- include/gpac/thread.h | 361 +- include/gpac/token.h | 90 +- include/gpac/tools.h | 2016 +- include/gpac/unicode.h | 63 - include/gpac/user.h | 93 +- include/gpac/utf.h | 110 +- include/gpac/version.h | 50 +- include/gpac/webvtt.h | 73 +- include/gpac/xml.h | 426 +- mkdmg.sh | 85 +- modules/Makefile | 136 +- modules/aac_in/Makefile | 62 - modules/aac_in/aac_in.c | 999 - modules/aac_in/faad_dec.c | 499 - modules/ac3_in/Makefile | 59 - modules/ac3_in/ac3_in.c | 704 - modules/ac3_in/liba52_dec.c | 374 - modules/alsa/Makefile | 6 +- modules/alsa/alsa.c | 43 +- modules/amr_dec/Makefile | 80 - modules/amr_dec/amr_dec.c | 342 - modules/amr_dec/amr_in.c | 620 - modules/amr_dec/amr_nb/typedefs.h | 195 - modules/amr_dec/amr_nb_api.h | 61 - modules/amr_float_dec/Makefile | 62 - modules/amr_float_dec/amr_api.h | 35 - modules/amr_float_dec/amr_float_dec.c | 343 - modules/bifs_dec/Makefile | 50 - modules/bifs_dec/bifs_dec.c | 220 - modules/ctx_load/Makefile | 49 - modules/dec_openhevc/Makefile | 59 + modules/dektec_out/Makefile | 10 +- modules/dektec_out/dektec_video.cpp | 1018 +- modules/dektec_out/dektec_video.h | 98 + modules/dektec_out/dektec_video_decl.c | 111 + modules/dektec_out/dektec_video_old.cpp | 367 + modules/dektec_out/out_dektec.vcxproj | 286 + modules/demo_is/Makefile | 6 +- modules/demo_is/demo_is.c | 2 +- .../{ => deprecated}/epoc_hw/epoc_aout.cpp | 0 .../{ => deprecated}/epoc_hw/epoc_codec.cpp | 0 .../{ => deprecated}/epoc_hw/epoc_vout.cpp | 4 +- .../old_arch}/audio_filter/Makefile | 4 +- .../old_arch}/audio_filter/audio_filter.c | 0 .../{ => deprecated/old_arch}/avcap/Makefile | 4 +- .../{ => deprecated/old_arch}/avcap/avcap.cpp | 4 +- .../old_arch}/ffmpeg_in/Makefile | 20 +- .../old_arch}/ffmpeg_in/ffmpeg_decode.c | 147 +- .../old_arch}/ffmpeg_in/ffmpeg_demux.c | 19 +- .../old_arch}/ffmpeg_in/ffmpeg_in.h | 15 +- .../old_arch}/ffmpeg_in/ffmpeg_load.c | 0 .../old_arch}/freenect/Makefile | 8 +- .../old_arch}/freenect/freenect.c | 0 .../{ => deprecated/old_arch}/gapi/gapi.cpp | 6 +- modules/{ => deprecated/old_arch}/gapi/gapi.h | 0 .../old_arch}/gdip_raster/gdip_font.cpp | 6 +- .../old_arch}/gdip_raster/gdip_grad.cpp | 0 .../old_arch}/gdip_raster/gdip_priv.h | 0 .../old_arch}/gdip_raster/gdip_rend.cpp | 4 +- .../old_arch}/gdip_raster/gdip_texture.cpp | 24 +- .../{ => deprecated/old_arch}/hyb_in/Makefile | 4 +- .../old_arch}/hyb_in/fm_fake_pull.c | 0 .../old_arch}/hyb_in/fm_fake_push.c | 0 .../old_arch}/hyb_in/fm_mmbtools.c | 0 .../{ => deprecated/old_arch}/hyb_in/hyb_in.c | 2 +- .../{ => deprecated/old_arch}/hyb_in/hyb_in.h | 0 .../old_arch}/libplayer/Makefile | 4 +- .../old_arch}/libplayer/libplayer.c | 0 .../{ => deprecated/old_arch}/mse_in/Makefile | 16 +- .../{ => deprecated/old_arch}/mse_in/mse_in.c | 8 +- modules/deprecated/old_arch/netctrl/Makefile | 47 + modules/deprecated/old_arch/netctrl/netctrl.c | 212 + .../old_arch}/opencv_is/Makefile | 4 +- .../old_arch}/opencv_is/demo-sensor.bt | 0 .../haarcascade_frontalface_default.xml | 0 .../old_arch}/opencv_is/opencv_is.c | 0 .../{ => deprecated/old_arch}/osd/Makefile | 4 +- modules/{ => deprecated/old_arch}/osd/osd.c | 4 +- .../platinum/GPACFileMediaServer.cpp | 0 .../old_arch}/platinum/GPACFileMediaServer.h | 0 .../platinum/GPACMediaController.cpp | 0 .../old_arch}/platinum/GPACMediaController.h | 0 .../old_arch}/platinum/GPACMediaRenderer.cpp | 4 +- .../old_arch}/platinum/GPACMediaRenderer.h | 0 .../old_arch}/platinum/GPACPlatinum.cpp | 56 +- .../old_arch}/platinum/GPACPlatinum.h | 0 .../old_arch}/platinum/GenericDevice.cpp | 0 .../old_arch}/platinum/GenericDevice.h | 0 .../old_arch}/platinum/Makefile | 22 +- modules/deprecated/old_arch/psvr/Makefile | 47 + modules/deprecated/old_arch/psvr/psvr.c | 213 + .../old_arch}/rvc_dec/Makefile | 4 +- .../old_arch}/rvc_dec/rvc_dec.c | 4 +- .../{ => deprecated/old_arch}/ui_rec/Makefile | 4 +- .../old_arch}/ui_rec/readme.txt | 0 .../{ => deprecated/old_arch}/ui_rec/ui_rec.c | 6 +- .../old_arch}/widgetman/Makefile | 20 +- .../old_arch}/widgetman/unzip.c | 0 .../old_arch}/widgetman/unzip.h | 0 .../old_arch}/widgetman/wgt_load.c | 4 +- .../old_arch}/widgetman/wgt_load_base.js | 0 .../old_arch}/widgetman/widget.c | 6 +- .../old_arch}/widgetman/widgetman.c | 32 +- .../old_arch}/widgetman/widgetman.h | 0 .../{ => deprecated/old_arch}/wiiis/Makefile | 4 +- .../old_arch}/wiiis/test_wii.bt | 0 .../{ => deprecated/old_arch}/wiiis/wiiis.c | 10 +- modules/{ => deprecated}/oss_audio/Makefile | 8 +- modules/{ => deprecated}/oss_audio/oss.c | 20 +- modules/directfb_out/Makefile | 7 +- modules/directfb_out/directfb_out.c | 89 +- modules/directfb_out/directfb_wrapper.c | 14 +- modules/droid_audio/droidaudio.c | 16 +- modules/droid_cam/droid_cam.c | 57 +- modules/droid_mpegv/droid_mpegv.c | 13 +- modules/droid_out/droid_vout-bitmap.c | 6 +- modules/droid_out/droid_vout.c | 144 +- modules/dummy_in/Makefile | 49 - modules/dummy_in/dummy_in.c | 527 - modules/dx_hw/Makefile | 10 +- modules/dx_hw/copy_pixels.c | 736 - modules/dx_hw/dx_2d.c | 123 +- modules/dx_hw/dx_audio.c | 53 +- modules/dx_hw/dx_hw.h | 15 +- modules/dx_hw/dx_hw.rc | 2 +- modules/dx_hw/dx_video.c | 180 +- modules/dx_hw/dx_window.c | 555 +- .../mediacodec_dec.h => filter_export.cpp} | 21 +- modules/ft_font/Makefile | 6 +- modules/ft_font/ft_font.c | 79 +- modules/gpac_js/Makefile | 63 - modules/gpac_js/gpac_js.c | 2517 - modules/img_in/Makefile | 78 - modules/img_in/bmp_dec.c | 210 - modules/img_in/img_dec.c | 159 - modules/img_in/img_in.c | 461 - modules/img_in/img_in.h | 107 - modules/img_in/jp2_dec.c | 469 - modules/img_in/jpeg_dec.c | 156 - modules/img_in/png_dec.c | 181 - modules/ios_cam/ios_cam.c | 12 +- modules/ios_mpegv/ios_mpegv.c | 6 +- modules/ismacryp/Makefile | 48 - modules/ismacryp/isma_ea.c | 658 - modules/isom_in/Makefile | 49 - modules/isom_in/isom_cache.c | 295 - modules/isom_in/isom_in.h | 166 - modules/isom_in/load.c | 331 - modules/isom_in/read.c | 1282 - modules/isom_in/read_ch.c | 946 - modules/jack/Makefile | 8 +- modules/jack/jack.c | 152 +- modules/laser_dec/Makefile | 50 - modules/laser_dec/laser_dec.c | 202 - modules/mediacodec_dec/Makefile | 58 - modules/mediacodec_dec/mediacodec_dec.c | 1104 - modules/mediacodec_dec/mediacodec_dec_jni.c | 204 - modules/mp3_in/Makefile | 65 - modules/mp3_in/mad_dec.c | 377 - modules/mp3_in/mp3_in.c | 781 - modules/mpd_in/Makefile | 46 - modules/mpd_in/mpd_in.c | 1668 - modules/mpegts_in/Makefile | 50 - modules/mpegts_in/mpegts_in.c | 1750 - modules/odf_dec/Makefile | 49 - modules/odf_dec/odf_dec.c | 323 - modules/ogg/Makefile | 83 - modules/ogg/ogg_in.c | 1015 - modules/ogg/ogg_in.h | 64 - modules/ogg/ogg_load.c | 165 - modules/ogg/theora_dec.c | 235 - modules/ogg/vorbis_dec.c | 278 - modules/openhevc_dec/Makefile | 58 - modules/openhevc_dec/openhevc_dec.c | 931 - modules/opensvc_dec/Makefile | 45 - modules/opensvc_dec/opensvc_dec.c | 458 - modules/pulseaudio/Makefile | 6 +- modules/pulseaudio/pulseaudio.c | 93 +- modules/raw_out/Makefile | 58 - modules/raw_out/raw_video.c | 321 - modules/redirect_av/Makefile | 53 - modules/redirect_av/ffmpeg_ts_muxer.c | 417 - modules/redirect_av/gpac_ts_muxer.c | 178 - modules/redirect_av/redirect_av.c | 865 - modules/redirect_av/ts_muxer.h | 161 - modules/rtp_in/Makefile | 51 - modules/rtp_in/rtp_in.c | 1014 - modules/rtp_in/rtp_signaling.c | 922 - modules/rtp_in/rtp_stream.c | 643 - modules/rtp_in/sdp_fetch.c | 186 - modules/rtp_in/sdp_load.c | 585 - modules/saf_in/Makefile | 51 - modules/saf_in/saf_in.c | 645 - modules/sdl_out/Makefile | 8 +- modules/sdl_out/audio.c | 49 +- modules/sdl_out/sdl_out.h | 13 +- modules/sdl_out/video.c | 1235 +- modules/soft_raster/Makefile | 55 - modules/soft_raster/rast_soft.h | 364 - modules/soft_raster/raster_565.c | 495 - modules/soft_raster/raster_argb.c | 730 - modules/soft_raster/raster_load.c | 106 - modules/soft_raster/raster_rgb.c | 402 - modules/soft_raster/stencil.c | 943 - modules/soft_raster/surface.c | 662 - modules/svg_in/Makefile | 57 - modules/svg_in/svg_in.c | 466 - modules/timedtext/Makefile | 50 - modules/timedtext/timedtext_in.c | 470 - modules/validator/Makefile | 6 +- modules/validator/validator.c | 263 +- modules/vtb_decode/Makefile | 43 - modules/vtb_decode/vtb_decode.c | 1341 - modules/vtt_in/Makefile | 51 - modules/vtt_in/vtt_dec.c | 490 - modules/vtt_in/vtt_in.c | 374 - modules/wav_out/Makefile | 6 +- modules/wav_out/wav_out.c | 34 +- modules/x11_out/Makefile | 18 +- modules/x11_out/x11_out.c | 784 +- modules/x11_out/x11_out.h | 4 +- modules/xvid_dec/Makefile | 61 - modules/xvid_dec/xvid_dec.c | 515 - modules/xvid_dec/xvid_dec_wce.cpp | 303 - packagers/osx/GPAC.app/Contents/Info.plist | 577 + packagers/osx/GPAC.app/Contents/PkgInfo | 1 + .../osx/GPAC.app/Contents/Resources/osmo.icns | Bin 0 -> 147235 bytes .../Contents/Resources/osmo_audio.icns | Bin 0 -> 209406 bytes .../Contents/Resources/osmo_generic.icns | Bin 0 -> 190916 bytes .../Contents/Resources/osmo_model.icns | Bin 0 -> 236301 bytes .../Contents/Resources/osmo_subs.icns | Bin 0 -> 176846 bytes .../Contents/Resources/osmo_video.icns | Bin 0 -> 221221 bytes .../Contents/Resources/osmo_widget.icns | Bin 0 -> 227171 bytes packagers/osx/SLA.r | 446 + packagers/osx/distribution.xml | 19 + packagers/osx/res/background.png | Bin 0 -> 98776 bytes packagers/osx/res/preamble.txt | 8 + packagers/osx/scripts/postinstall | 14 + packagers/win32_64/nsis/gpac_installer.nsi | 672 +- share/default.cfg | 16 + {gui => share/deprecated}/iphone_wm_gui.js | 50 +- {gui => share/deprecated}/iphone_wm_gui.svg | 2 +- share/deprecated/mpegu-core.js | 1794 + {gui => share/deprecated}/mpegu-wm.bt | 0 share/deprecated/mpegu-wm.js | 1723 + {gui => share/deprecated}/mpegu-wm.xmt | 8 +- {gui => share/deprecated}/tv_wm_gui.js | 4 +- {gui => share/deprecated}/tv_wm_gui.svg | 0 share/doc/CODING_STYLE | 137 + {doc => share/doc}/GPAC UPnP.doc | Bin {doc => share/doc}/ISO 639-2 codes.txt | 0 {doc => share/doc}/SceneGenerators | 0 share/doc/configuration.html | 103 + {doc => share/doc}/doxyfile | 38 +- {doc => share/doc}/doxyfile-full | 12 +- share/doc/idl/core.idl | 762 + share/doc/idl/evg.idl | 1828 + share/doc/idl/filtersession.idl | 278 + share/doc/idl/jsf.idl | 946 + share/doc/idl/scenejs.idl | 426 + share/doc/idl/storage.idl | 57 + share/doc/idl/webgl.idl | 457 + share/doc/idl/xhr.idl | 56 + {doc => share/doc}/ipmpx_syntax.bt | 2 +- share/doc/man/gpac-filters.1 | 6006 ++ share/doc/man/gpac.1 | 3713 ++ share/doc/man/mp4box.1 | 2317 + share/doc/man/mp4client.1 | 270 + {doc => share/doc}/osmo4.ico | Bin .../gui}/extensions/H2B2VS/H2B2VS.png | Bin .../gui}/extensions/H2B2VS/h2b2vs.js | 26 +- {gui => share/gui}/extensions/H2B2VS/init.js | 5 +- .../gui}/extensions/H2B2VS/logo_hd.png | Bin .../gui}/extensions/H2B2VS/logo_uhd.png | Bin share/gui/extensions/about/info.js | 15 + share/gui/extensions/about/info.svg | 48 + share/gui/extensions/about/init.js | 15 + .../bifs_tests/applications-other.svg | 0 .../gui}/extensions/bifs_tests/bifs_tests.js | 0 .../gui}/extensions/bifs_tests/init.js | 0 .../player/applications-multimedia.svg | 0 .../gui}/extensions/player/fileopen.js | 25 +- share/gui/extensions/player/init.js | 24 + .../gui}/extensions/player/player.js | 301 +- .../gui}/extensions/player/playlist.js | 16 +- {gui => share/gui}/extensions/player/stats.js | 58 +- .../gui}/extensions/showroom/init.js | 0 .../gui}/extensions/showroom/osmo.bt | 0 .../gui}/extensions/showroom/showroom.js | 0 .../widget_manager/applications-system.svg | 0 .../gui}/extensions/widget_manager/init.js | 6 +- {gui => share/gui}/gui.bt | 0 {gui => share/gui}/gui.js | 118 +- {gui => share/gui}/gwlib.js | 149 +- {gui => share/gui}/icons/add.svg | 0 {gui => share/gui}/icons/app.svg | 0 {gui => share/gui}/icons/audio.svg | 0 {gui => share/gui}/icons/audio_full.svg | 0 {gui => share/gui}/icons/audio_mute.svg | 0 {gui => share/gui}/icons/check.svg | 0 {gui => share/gui}/icons/close.svg | 0 {gui => share/gui}/icons/compass.svg | 0 {gui => share/gui}/icons/cross.svg | 0 {gui => share/gui}/icons/down.svg | 0 {gui => share/gui}/icons/expand.svg | 0 {gui => share/gui}/icons/file.svg | 0 {gui => share/gui}/icons/film.svg | 0 {gui => share/gui}/icons/folder.svg | 0 {gui => share/gui}/icons/harddrive.svg | 0 {gui => share/gui}/icons/heart.svg | 0 {gui => share/gui}/icons/home.svg | 0 {gui => share/gui}/icons/image.svg | 0 {gui => share/gui}/icons/info.svg | 0 {gui => share/gui}/icons/laptop.svg | 0 {gui => share/gui}/icons/left.svg | 0 {gui => share/gui}/icons/list.svg | 0 {gui => share/gui}/icons/live.svg | 0 {gui => share/gui}/icons/media_next.svg | 0 {gui => share/gui}/icons/media_prev.svg | 0 {gui => share/gui}/icons/monitor.svg | 0 {gui => share/gui}/icons/more.svg | 0 {gui => share/gui}/icons/musical.svg | 0 {gui => share/gui}/icons/navigation.svg | 0 {gui => share/gui}/icons/network.svg | 0 {gui => share/gui}/icons/next.svg | 0 {gui => share/gui}/icons/osmo.svg | 0 {gui => share/gui}/icons/overflowing.svg | 0 {gui => share/gui}/icons/pause.svg | 0 {gui => share/gui}/icons/pl_next.svg | 0 {gui => share/gui}/icons/pl_prev.svg | 0 {gui => share/gui}/icons/play.svg | 0 {gui => share/gui}/icons/play_loop.svg | 0 {gui => share/gui}/icons/play_shuffle.svg | 0 {gui => share/gui}/icons/play_single.svg | 0 {gui => share/gui}/icons/power.svg | 0 {gui => share/gui}/icons/previous.svg | 0 {gui => share/gui}/icons/remove.svg | 0 {gui => share/gui}/icons/resize.svg | 0 {gui => share/gui}/icons/rewind.svg | 0 {gui => share/gui}/icons/right.svg | 0 {gui => share/gui}/icons/seek_forward.svg | 0 {gui => share/gui}/icons/shrink.svg | 0 {gui => share/gui}/icons/sort.svg | 0 {gui => share/gui}/icons/speed.svg | 0 {gui => share/gui}/icons/star.svg | 0 {gui => share/gui}/icons/stop.svg | 0 {gui => share/gui}/icons/stop2.svg | 0 {gui => share/gui}/icons/trash.svg | 0 {gui => share/gui}/icons/tray.svg | 0 {gui => share/gui}/icons/tv.svg | 0 {gui => share/gui}/icons/up.svg | 0 {gui => share/gui}/icons/world.svg | 0 share/lang/fr.txt | 2440 + share/res/gpac.mp4 | Bin 0 -> 1660 bytes .../logo.png => share/res/gpac.png | Bin share/res/gpac_cfg_test.mp4 | Bin 0 -> 132360 bytes share/scripts/ttml-renderer.js | 277 + {gui => share/scripts}/webvtt-renderer.js | 14 +- {shaders => share/shaders}/fragment.glsl | 87 +- {shaders => share/shaders}/vertex.glsl | 0 share/vis/Code/Console.js | 208 + share/vis/Code/DataViewReader.js | 47 + share/vis/Code/PixelTimeRange.js | 61 + share/vis/Code/Remotery.js | 338 + share/vis/Code/SampleWindow.js | 215 + share/vis/Code/ThreadFrame.js | 28 + share/vis/Code/TimelineRow.js | 379 + share/vis/Code/TimelineWindow.js | 284 + share/vis/Code/TitleWindow.js | 71 + share/vis/Code/WebSocketConnection.js | 137 + share/vis/Styles/Remotery.css | 234 + .../extern/BrowserLib/Core/Code/Animation.js | 65 + share/vis/extern/BrowserLib/Core/Code/Bind.js | 92 + .../extern/BrowserLib/Core/Code/Convert.js | 218 + share/vis/extern/BrowserLib/Core/Code/Core.js | 26 + share/vis/extern/BrowserLib/Core/Code/DOM.js | 499 + .../extern/BrowserLib/Core/Code/Keyboard.js | 149 + .../extern/BrowserLib/Core/Code/LocalStore.js | 40 + .../vis/extern/BrowserLib/Core/Code/Mouse.js | 83 + .../BrowserLib/Core/Code/MurmurHash3.js | 68 + .../BrowserLib/WindowManager/Code/Button.js | 131 + .../BrowserLib/WindowManager/Code/ComboBox.js | 237 + .../WindowManager/Code/Container.js | 34 + .../BrowserLib/WindowManager/Code/EditBox.js | 119 + .../BrowserLib/WindowManager/Code/Grid.js | 248 + .../BrowserLib/WindowManager/Code/Label.js | 31 + .../BrowserLib/WindowManager/Code/Treeview.js | 352 + .../WindowManager/Code/TreeviewItem.js | 109 + .../BrowserLib/WindowManager/Code/Window.js | 295 + .../WindowManager/Code/WindowManager.js | 54 + .../WindowManager/Styles/WindowManager.css | 652 + share/vis/index.html | 55 + src/Makefile | 257 +- src/bifs/arith_decoder.c | 5 +- src/bifs/bifs_codec.c | 68 +- src/bifs/com_dec.c | 18 +- src/bifs/com_enc.c | 12 +- src/bifs/conditional.c | 1 + src/bifs/field_decode.c | 22 +- src/bifs/field_encode.c | 25 +- src/bifs/memory_decoder.c | 31 +- src/bifs/predictive_mffield.c | 2 +- src/bifs/quantize.c | 4 +- src/bifs/script.h | 4 +- src/bifs/script_dec.c | 4 +- src/bifs/script_enc.c | 13 +- src/bifs/unquantize.c | 2 +- src/compositor/audio_input.c | 215 +- src/compositor/audio_mixer.c | 622 +- src/compositor/audio_render.c | 860 +- src/compositor/bindable.c | 3 +- src/compositor/camera.c | 43 +- src/compositor/clock.c | 298 + src/compositor/compositor.c | 2122 +- src/compositor/compositor_2d.c | 361 +- src/compositor/compositor_3d.c | 24 +- src/compositor/compositor_node_init.c | 3 +- src/compositor/drawable.c | 41 +- src/compositor/drawable.h | 2 +- src/compositor/events.c | 73 +- src/compositor/font_engine.c | 121 +- src/compositor/gl_inc.h | 302 +- src/compositor/hardcoded_protos.c | 426 +- src/compositor/hc_flash_shape.c | 10 +- src/{terminal => compositor}/media_object.c | 1241 +- src/compositor/mesh.c | 40 +- src/compositor/mesh_collide.c | 10 +- src/compositor/mesh_tesselate.c | 26 +- src/compositor/mpeg4_animstream.c | 6 +- src/compositor/mpeg4_audio.c | 67 +- src/compositor/mpeg4_background.c | 9 +- src/compositor/mpeg4_background2d.c | 18 +- src/compositor/mpeg4_bitmap.c | 13 +- src/compositor/mpeg4_composite.c | 780 +- src/compositor/mpeg4_form.c | 7 +- src/compositor/mpeg4_geometry_2d.c | 101 +- src/compositor/mpeg4_geometry_3d.c | 107 +- src/compositor/mpeg4_geometry_ifs2d.c | 54 +- src/compositor/mpeg4_geometry_ils2d.c | 52 +- src/compositor/mpeg4_gradients.c | 161 +- src/compositor/mpeg4_grouping.c | 8 +- src/compositor/mpeg4_grouping_2d.c | 4 + src/compositor/mpeg4_grouping_3d.c | 2 + src/{terminal => compositor}/mpeg4_inline.c | 179 +- .../mpeg4_inputsensor.c} | 545 +- src/compositor/mpeg4_layer_3d.c | 199 +- src/compositor/mpeg4_layout.c | 28 +- .../mpeg4_mediacontrol.c} | 56 +- .../mpeg4_mediasensor.c} | 83 +- src/compositor/mpeg4_path_layout.c | 3 +- src/compositor/mpeg4_sensors.c | 68 +- src/compositor/mpeg4_sound.c | 21 +- src/compositor/mpeg4_text.c | 37 +- src/compositor/mpeg4_textures.c | 163 +- src/compositor/mpeg4_timesensor.c | 2 +- src/compositor/mpeg4_viewport.c | 50 +- src/compositor/navigate.c | 304 +- src/compositor/nodes_stacks.h | 29 +- src/compositor/object_manager.c | 1967 + src/compositor/offscreen_cache.c | 45 +- src/{terminal => compositor}/scene.c | 1486 +- .../scene_node_init.c} | 66 +- src/compositor/scene_ns.c | 612 + src/compositor/svg_base.c | 33 +- src/{terminal => compositor}/svg_external.c | 70 +- src/compositor/svg_filters.c | 12 +- src/compositor/svg_font.c | 36 +- src/compositor/svg_geometry.c | 2 +- src/compositor/svg_grouping.c | 125 +- src/compositor/svg_media.c | 55 +- src/compositor/svg_paint_servers.c | 85 +- src/compositor/svg_text.c | 14 +- src/compositor/texturing.c | 114 +- src/compositor/texturing.h | 11 +- src/compositor/texturing_gl.c | 1614 +- src/compositor/visual_manager.c | 18 +- src/compositor/visual_manager.h | 60 +- src/compositor/visual_manager_2d.c | 47 +- src/compositor/visual_manager_2d.h | 8 +- src/compositor/visual_manager_2d_draw.c | 209 +- src/compositor/visual_manager_3d.c | 236 +- src/compositor/visual_manager_3d.h | 9 +- src/compositor/visual_manager_3d_gl.c | 1169 +- src/compositor/x3d_geometry.c | 43 +- src/crypto/g_crypt.c | 105 + src/crypto/g_crypt_openssl.c | 226 + src/crypto/g_crypt_tinyaes.c | 201 + src/crypto/tiny_aes.c | 587 + src/crypto/tiny_aes.h | 93 + src/dir_info | 47 - {modules/soft_raster => src/evg}/ftgrays.c | 278 +- src/evg/rast_soft.h | 524 + src/evg/raster3d.c | 1480 + src/evg/raster_565.c | 662 + src/evg/raster_argb.c | 758 + src/evg/raster_rgb.c | 389 + src/evg/raster_yuv.c | 2228 + src/evg/stencil.c | 2021 + src/evg/surface.c | 1111 + src/export.cpp | 925 +- src/filter_core/filter.c | 3854 ++ src/filter_core/filter_pck.c | 1662 + src/filter_core/filter_pid.c | 6825 ++ src/filter_core/filter_props.c | 1595 + src/filter_core/filter_queue.c | 354 + src/filter_core/filter_register.c | 293 + src/filter_core/filter_session.c | 3254 + src/filter_core/filter_session.h | 1016 + src/filter_core/filter_session_js.c | 1241 + src/filters/base_filter_example.c | 168 + src/filters/bsrw.c | 642 + src/filters/compose.c | 996 + src/filters/dasher.c | 7208 +++ src/filters/dec_ac52.c | 298 + src/filters/dec_bifs.c | 300 + src/filters/dec_faad.c | 430 + src/filters/dec_img.c | 173 + src/filters/dec_j2k.c | 625 + src/filters/dec_laser.c | 273 + src/filters/dec_mad.c | 298 + src/filters/dec_mediacodec.c | 1278 + src/filters/dec_mediacodec.h | 128 + src/filters/dec_mediacodec_jni.c | 408 + src/filters/dec_nvdec.c | 1508 + src/filters/dec_nvdec_sdk.c | 712 + src/filters/dec_nvdec_sdk.h | 2898 + src/filters/dec_odf.c | 482 + src/filters/dec_openhevc.c | 1294 + src/filters/dec_opensvc.c | 597 + src/filters/dec_theora.c | 313 + src/filters/dec_ttml.c | 536 + .../timedtext_dec.c => src/filters/dec_ttxt.c | 999 +- src/filters/dec_vorbis.c | 313 + src/filters/dec_vtb.c | 2017 + .../filters/dec_vtb_glctx.m | 0 src/filters/dec_webvtt.c | 513 + src/filters/dec_xvid.c | 461 + src/filters/decrypt_cenc_isma.c | 1335 + src/filters/dmx_avi.c | 647 + src/filters/dmx_dash.c | 1851 + src/filters/dmx_gsf.c | 1290 + src/filters/dmx_m2ts.c | 1153 + src/filters/dmx_mpegps.c | 493 + src/filters/dmx_nhml.c | 1411 + src/filters/dmx_nhnt.c | 513 + src/filters/dmx_ogg.c | 845 + src/filters/dmx_saf.c | 464 + src/filters/dmx_vobsub.c | 486 + src/filters/enc_jpg.c | 409 + src/filters/enc_png.c | 387 + src/filters/encrypt_cenc_isma.c | 1890 + src/filters/ff_avf.c | 1032 + src/filters/ff_common.c | 1256 + src/filters/ff_common.h | 75 + src/filters/ff_dec.c | 933 + src/filters/ff_dmx.c | 1029 + src/filters/ff_enc.c | 1460 + src/filters/ff_mx.c | 1037 + src/filters/ff_rescale.c | 422 + src/filters/filelist.c | 1049 + src/filters/hevcmerge.c | 1652 + src/filters/hevcsplit.c | 962 + src/filters/in_atsc.c | 507 + src/filters/in_dvb4linux.c | 438 + src/filters/in_file.c | 593 + src/filters/in_http.c | 478 + src/filters/in_pipe.c | 534 + src/filters/in_rtp.c | 809 + .../rtp_in/rtp_in.h => src/filters/in_rtp.h | 256 +- .../filters/in_rtp_rtsp.c | 201 +- src/filters/in_rtp_sdp.c | 378 + src/filters/in_rtp_signaling.c | 867 + src/filters/in_rtp_stream.c | 766 + src/filters/in_sock.c | 543 + src/filters/inspect.c | 2799 + src/filters/isoffin.h | 222 + src/filters/isoffin_load.c | 1260 + src/filters/isoffin_read.c | 1428 + src/filters/isoffin_read_ch.c | 693 + src/filters/jsfilter.c | 4397 ++ .../ctx_load.c => src/filters/load_bt_xmt.c | 641 +- src/filters/load_svg.c | 481 + src/filters/load_text.c | 3082 + src/filters/mux_avi.c | 627 + src/filters/mux_gsf.c | 1154 + src/filters/mux_isom.c | 5653 ++ src/filters/mux_ts.c | 1725 + src/filters/out_audio.c | 579 + src/filters/out_file.c | 623 + src/filters/out_http.c | 2786 + src/filters/out_pipe.c | 513 + src/filters/out_rtp.c | 1194 + src/filters/out_rtp.h | 107 + src/filters/out_rtsp.c | 1348 + src/filters/out_sock.c | 581 + src/filters/out_video.c | 1554 + src/filters/reframe_ac3.c | 612 + src/filters/reframe_adts.c | 911 + src/filters/reframe_amr.c | 603 + src/filters/reframe_av1.c | 1114 + src/filters/reframe_flac.c | 665 + src/filters/reframe_h263.c | 722 + src/filters/reframe_img.c | 434 + src/filters/reframe_latm.c | 637 + src/filters/reframe_mp3.c | 820 + src/filters/reframe_mpgvid.c | 1188 + src/filters/reframe_nalu.c | 3316 + src/filters/reframe_prores.c | 671 + src/filters/reframe_qcp.c | 729 + src/filters/reframe_rawpcm.c | 324 + src/filters/reframe_rawvid.c | 306 + src/filters/reframer.c | 1859 + src/filters/resample_audio.c | 404 + src/filters/rewind.c | 262 + src/filters/rewrite_adts.c | 396 + src/filters/rewrite_mp4v.c | 169 + src/filters/rewrite_nalu.c | 495 + src/filters/rewrite_obu.c | 420 + src/filters/tileagg.c | 290 + src/filters/tssplit.c | 480 + src/filters/unit_test_filter.c | 1001 + src/filters/vcrop.c | 626 + src/filters/vflip.c | 511 + src/filters/write_generic.c | 1063 + src/filters/write_nhml.c | 954 + src/filters/write_nhnt.c | 390 + src/filters/write_qcp.c | 381 + src/filters/write_vtt.c | 322 + src/ietf/rtcp.c | 141 +- src/ietf/rtp.c | 255 +- src/ietf/rtp_depacketizer.c | 404 +- src/ietf/rtp_packetizer.c | 41 +- src/ietf/rtp_pck_3gpp.c | 65 +- src/ietf/rtp_pck_mpeg12.c | 12 +- src/ietf/rtp_pck_mpeg4.c | 13 +- src/ietf/rtp_streamer.c | 606 +- src/ietf/rtsp_command.c | 68 +- src/ietf/rtsp_common.c | 21 +- src/ietf/rtsp_response.c | 49 +- src/ietf/rtsp_session.c | 195 +- src/ietf/sdp.c | 52 +- src/isomedia/avc_ext.c | 1667 +- src/isomedia/box_code_3gpp.c | 348 +- src/isomedia/box_code_adobe.c | 163 +- src/isomedia/box_code_apple.c | 787 +- src/isomedia/box_code_base.c | 6023 +- src/isomedia/box_code_drm.c | 939 +- src/isomedia/box_code_meta.c | 416 +- src/isomedia/box_dump.c | 3704 +- src/isomedia/box_funcs.c | 903 +- src/isomedia/data_map.c | 189 +- src/isomedia/drm_sample.c | 1033 +- src/isomedia/hint_track.c | 183 +- src/isomedia/hinting.c | 385 +- src/isomedia/iff.c | 786 +- src/isomedia/isom_intern.c | 353 +- src/isomedia/isom_read.c | 1698 +- src/isomedia/isom_store.c | 789 +- src/isomedia/isom_write.c | 3680 +- src/isomedia/media.c | 491 +- src/isomedia/media_odf.c | 32 +- src/isomedia/meta.c | 563 +- src/isomedia/movie_fragments.c | 1753 +- src/isomedia/sample_descs.c | 807 +- src/isomedia/stbl_read.c | 208 +- src/isomedia/stbl_write.c | 961 +- src/isomedia/track.c | 882 +- src/isomedia/ttml.c | 1 + src/isomedia/tx3g.c | 376 +- src/jsmods/WebGLRenderingContextBase.c | 2151 + src/jsmods/core.c | 1919 + src/jsmods/evg.c | 6635 ++ src/jsmods/scene_js.c | 1903 + src/jsmods/storage.c | 231 + src/jsmods/webgl.c | 2026 + src/jsmods/webgl.h | 159 + src/jsmods/xhr.c | 1454 + src/laser/lsr_dec.c | 371 +- src/laser/lsr_enc.c | 189 +- src/mcrypt/cbc.c | 173 - src/mcrypt/cfb.c | 160 - src/mcrypt/ctr.c | 237 - src/mcrypt/des.c | 588 - src/mcrypt/ecb.c | 88 - src/mcrypt/g_crypt.c | 391 - src/mcrypt/ncfb.c | 320 - src/mcrypt/nofb.c | 211 - src/mcrypt/ofb.c | 163 - src/mcrypt/rijndael-128.c | 417 - src/mcrypt/rijndael-192.c | 418 - src/mcrypt/rijndael-256.c | 416 - src/mcrypt/stream.c | 75 - src/mcrypt/tripledes.c | 767 - src/media_tools/ait.c | 45 +- src/media_tools/atsc_dmx.c | 1942 + src/media_tools/av_parsers.c | 6742 +- src/media_tools/avilib.c | 149 +- src/media_tools/crypt_tools.c | 661 + src/media_tools/dash_client.c | 3427 +- src/media_tools/dash_segmenter.c | 7251 +-- src/media_tools/dsmcc.c | 58 +- src/media_tools/dvb_mpe.c | 219 +- src/media_tools/filestreamer.c | 672 - src/media_tools/gpac_ogg.c | 109 +- src/media_tools/html5_media.c | 22 +- src/media_tools/html5_mse.c | 36 +- src/media_tools/img.c | 257 +- src/media_tools/ismacryp.c | 2234 - src/media_tools/isom_hinter.c | 236 +- src/media_tools/isom_tools.c | 1532 +- src/media_tools/m2ts_mux.c | 522 +- src/media_tools/m3u8.c | 100 +- src/media_tools/media_export.c | 3002 +- src/media_tools/media_import.c | 10240 +-- src/media_tools/mpd.c | 2746 +- src/media_tools/mpeg2_ps.c | 373 +- src/media_tools/mpeg2_ps.h | 27 +- src/media_tools/mpegts.c | 2957 +- src/media_tools/reedsolomon.c | 27 +- src/media_tools/saf.c | 13 +- src/media_tools/text_import.c | 2631 - src/media_tools/vobsub.c | 49 +- src/media_tools/webvtt.c | 576 +- src/odf/desc_private.c | 85 +- src/odf/descriptors.c | 864 +- src/odf/ipmpx_code.c | 24 +- src/odf/ipmpx_dump.c | 102 +- src/odf/ipmpx_parse.c | 57 +- src/odf/oci_codec.c | 10 +- src/odf/odf_code.c | 192 +- src/odf/odf_codec.c | 150 +- src/odf/odf_command.c | 8 +- src/odf/odf_dump.c | 259 +- src/odf/odf_parse.c | 529 +- src/odf/qos.c | 22 +- src/odf/slc.c | 31 +- src/quickjs/GPAC_README.md | 9 + src/quickjs/cutils.c | 640 + src/quickjs/cutils.h | 356 + src/quickjs/libbf.c | 8172 +++ src/quickjs/libbf.h | 499 + src/quickjs/libregexp-opcode.h | 58 + src/quickjs/libregexp.c | 2553 + src/quickjs/libregexp.h | 91 + src/quickjs/libunicode-table.h | 4313 ++ src/quickjs/libunicode.c | 1572 + src/quickjs/libunicode.h | 124 + src/quickjs/list.h | 100 + src/quickjs/quickjs-atom.h | 289 + src/quickjs/quickjs-opcode.h | 371 + src/quickjs/quickjs.c | 51836 ++++++++++++++++ src/quickjs/quickjs.h | 1003 + src/scene_manager/encode_isom.c | 148 +- src/scene_manager/loader_bt.c | 180 +- src/scene_manager/loader_isom.c | 92 +- src/scene_manager/loader_qt.c | 48 +- src/scene_manager/loader_svg.c | 318 +- src/scene_manager/loader_xmt.c | 112 +- src/scene_manager/scene_dump.c | 1081 +- src/scene_manager/scene_engine.c | 173 +- src/scene_manager/scene_manager.c | 96 +- src/scene_manager/scene_stats.c | 64 +- src/scene_manager/swf_bifs.c | 72 +- src/scene_manager/swf_parse.c | 114 +- src/scene_manager/swf_svg.c | 14 +- src/scene_manager/text_to_bifs.c | 21 +- src/scenegraph/base_scenegraph.c | 262 +- src/scenegraph/commands.c | 29 +- src/scenegraph/dom_events.c | 68 +- src/scenegraph/dom_js.c | 3104 + src/scenegraph/dom_smjs.c | 4947 -- .../{html5_media_smjs.c => html5_media_js.c} | 87 +- .../{html5_mse_smjs.c => html5_mse_js.c} | 17 +- src/scenegraph/mpeg4_animators.c | 21 +- src/scenegraph/mpeg4_nodes.c | 724 +- src/scenegraph/mpeg4_valuator.c | 66 +- src/scenegraph/qjs_common.h | 252 + src/scenegraph/smil_anim.c | 93 +- src/scenegraph/smil_timing.c | 30 +- src/scenegraph/svg_attributes.c | 249 +- src/scenegraph/svg_js.c | 2754 + src/scenegraph/svg_smjs.c | 2986 - src/scenegraph/svg_types.c | 27 +- src/scenegraph/vrml_js.c | 4781 ++ src/scenegraph/vrml_proto.c | 101 +- src/scenegraph/vrml_route.c | 14 +- src/scenegraph/vrml_script.c | 11 +- src/scenegraph/vrml_smjs.c | 5130 -- src/scenegraph/vrml_tools.c | 32 +- src/scenegraph/webvtt_smjs.c | 128 - src/scenegraph/x3d_nodes.c | 2 +- src/scenegraph/xbl_process.c | 242 - src/scenegraph/xml_ns.c | 114 +- src/terminal/channel.c | 1964 - src/terminal/clock.c | 358 - src/terminal/decoder.c | 2152 - src/terminal/input_sensor.h | 111 - src/terminal/media_control.h | 115 - src/terminal/media_manager.c | 767 - src/terminal/media_memory.c | 757 - src/terminal/media_memory.h | 191 - src/terminal/network_service.c | 1479 - src/terminal/object_browser.c | 489 - src/terminal/object_manager.c | 2370 - src/terminal/terminal.c | 2458 +- src/utils/Remotery.c | 7420 +++ src/utils/alloc.c | 155 +- src/utils/base_encoding.c | 207 +- src/utils/bitstream.c | 867 +- src/utils/cache.c | 320 +- src/utils/color.c | 2184 +- src/utils/configfile.c | 183 +- src/utils/constants.c | 1439 + src/utils/dlmalloc.c | 10 +- src/utils/downloader.c | 1998 +- src/utils/error.c | 679 +- src/utils/gltools.c | 1401 + src/utils/{gzio.cpp => gzio.c} | 229 +- src/utils/list.c | 39 +- src/utils/map.c | 337 - src/utils/math.c | 243 +- src/utils/module.c | 413 +- src/utils/module_wrap.h | 5 + src/utils/os_config_init.c | 1701 +- src/utils/os_divers.c | 813 +- src/utils/os_file.c | 1162 +- src/utils/os_module.c | 46 +- src/utils/os_net.c | 698 +- src/utils/os_thread.c | 217 +- src/utils/path2d.c | 22 +- src/utils/path2d_stroker.c | 27 +- src/utils/ringbuffer.c | 157 - src/utils/sha1.c | 25 +- src/utils/symbian_net.cpp | 4 +- src/utils/symbian_os.cpp | 3 +- src/utils/token.c | 1 + src/utils/unicode.c | 4 +- src/utils/url.c | 58 +- src/utils/utf.c | 158 +- src/utils/xml_parser.c | 261 +- src/utils/zlib_symbian_ext.h | 11 +- src/utils/zutil.c | 2 - src/utils/zutil.h | 6 +- static.mak | 228 +- tests/README.MD | 300 - tests/ghp_deploy.sh | 43 - tests/index.html | 21 - tests/make_tests.sh | 1570 - tests/media/auxiliary_files/count_arabic.mp3 | Bin 59872 -> 0 bytes tests/media/auxiliary_files/count_english.mp3 | Bin 60060 -> 0 bytes tests/media/auxiliary_files/count_french.mp3 | Bin 60060 -> 0 bytes tests/media/auxiliary_files/count_german.mp3 | Bin 57365 -> 0 bytes tests/media/auxiliary_files/count_italian.mp3 | Bin 57678 -> 0 bytes tests/media/auxiliary_files/count_spanish.mp3 | Bin 60186 -> 0 bytes tests/media/auxiliary_files/count_video.cmp | Bin 146688 -> 0 bytes tests/media/auxiliary_files/counter.hvc | Bin 696407 -> 0 bytes tests/media/auxiliary_files/enst_audio.aac | Bin 85058 -> 0 bytes tests/media/auxiliary_files/enst_video.h264 | Bin 47679 -> 0 bytes tests/media/auxiliary_files/index2batch.xslt | 54 - tests/media/auxiliary_files/index2html.xslt | 62 - tests/media/auxiliary_files/index2sh.xslt | 71 - tests/media/auxiliary_files/logo.bt | 126 - tests/media/auxiliary_files/logo.jpg | Bin 13781 -> 0 bytes tests/media/auxiliary_files/nefertiti.wrl | 649 - tests/media/auxiliary_files/sky.jpg | Bin 6496 -> 0 bytes tests/media/auxiliary_files/subs.ismt | Bin 5033 -> 0 bytes tests/media/auxiliary_files/subtitle.srt | 23 - tests/media/auxiliary_files/subtitle_fr.srt | 23 - tests/media/auxiliary_files/svg2html.xslt | 117 - tests/media/auxiliary_files/x3d2html.xslt | 127 - tests/media/auxiliary_files/xmt2html.xslt | 159 - .../bifs-2D-background-background2D-bind.bt | 71 - .../bifs-2D-background-background2D-image.bt | 97 - ...bifs-2D-background-background2D-layer2D.bt | 92 - .../bifs-2D-background-background2D-movie.bt | 94 - ...s-2D-background-background2D-url-change.bt | 105 - .../bifs/bifs-2D-interactivity-discsensor.bt | 129 - .../bifs/bifs-2D-interactivity-htk-sensor.bt | 175 - .../bifs/bifs-2D-interactivity-keysensor.bt | 810 - .../bifs/bifs-2D-interactivity-mousesensor.bt | 182 - .../bifs-2D-interactivity-nested-sensors.bt | 123 - .../bifs-2D-interactivity-planesensor2D.bt | 124 - ...bifs-2D-interactivity-proximitysensor2D.bt | 89 - .../bifs-2D-interactivity-stringsensor.bt | 153 - ...fs-2D-interactivity-touchsensor-4states.bt | 103 - ...s-2D-interactivity-touchsensor-hitpoint.bt | 91 - ...ivity-touchsensor-isactive-exposedfield.bt | 99 - ...s-2D-interactivity-touchsensor-isactive.bt | 98 - ...ifs-2D-interactivity-touchsensor-isover.bt | 59 - ...-2D-interactivity-touchsensor-move_over.bt | 268 - .../bifs-2D-painting-colortransform-alpha.bt | 101 - .../bifs-2D-painting-colortransform-bitmap.bt | 109 - .../bifs-2D-painting-colortransform-color.bt | 102 - ...bifs-2D-painting-colortransform-texture.bt | 109 - .../bifs/bifs-2D-painting-lineproperties.bt | 168 - .../media/bifs/bifs-2D-painting-material2D.bt | 154 - .../bifs-2D-painting-xlineproperties-cap.bt | 211 - ...ting-xlineproperties-compositetexture2D.bt | 153 - .../bifs-2D-painting-xlineproperties-dash.bt | 142 - ...D-painting-xlineproperties-imagetexture.bt | 159 - .../bifs-2D-painting-xlineproperties-join.bt | 215 - ...painting-xlineproperties-lineargradient.bt | 122 - ...painting-xlineproperties-radialgradient.bt | 124 - ...fs-2D-painting-xlineproperties-scalable.bt | 123 - ...2D-painting-xlineproperties-transparent.bt | 176 - .../bifs/bifs-2D-positioning-clipper2D.bt | 316 - .../bifs-2D-positioning-form-align-center.bt | 162 - .../bifs-2D-positioning-form-align-horiz.bt | 129 - .../bifs-2D-positioning-form-align-vert.bt | 129 - .../bifs-2D-positioning-form-spread-horiz.bt | 182 - .../bifs-2D-positioning-form-spread-vert.bt | 170 - .../media/bifs/bifs-2D-positioning-layer2D.bt | 129 - .../bifs-2D-positioning-layer2d-in-layer2d.bt | 111 - ...-2D-positioning-layout-horiz-ltr-nowrap.bt | 403 - ...D-positioning-layout-horiz-ltr-wrap-btt.bt | 445 - ...D-positioning-layout-horiz-ltr-wrap-ttb.bt | 434 - ...-2D-positioning-layout-horiz-rtl-nowrap.bt | 415 - ...D-positioning-layout-horiz-rtl-wrap-btt.bt | 469 - ...D-positioning-layout-horiz-rtl-wrap-ttb.bt | 457 - .../bifs-2D-positioning-layout-horiz-text.bt | 84 - ...bifs-2D-positioning-layout-scroll-child.bt | 87 - .../bifs-2D-positioning-layout-scroll-full.bt | 344 - ...D-positioning-layout-scroll-modes-horiz.bt | 349 - ...2D-positioning-layout-scroll-modes-vert.bt | 350 - ...ifs-2D-positioning-layout-scroll-on-off.bt | 218 - ...s-2D-positioning-layout-vert-btt-nowrap.bt | 340 - ...2D-positioning-layout-vert-btt-wrap-ltr.bt | 372 - ...2D-positioning-layout-vert-btt-wrap-rtl.bt | 381 - ...s-2D-positioning-layout-vert-ttb-nowrap.bt | 331 - ...2D-positioning-layout-vert-ttb-wrap-ltr.bt | 363 - ...2D-positioning-layout-vert-ttb-wrap-rtl.bt | 372 - .../bifs/bifs-2D-positioning-orderedgroup.bt | 86 - ...bifs-2D-positioning-pathlayout-graphics.bt | 177 - .../bifs/bifs-2D-positioning-pathlayout.bt | 164 - .../bifs/bifs-2D-positioning-transform2D.bt | 133 - .../bifs-2D-positioning-transformmatrix2D.bt | 179 - tests/media/bifs/bifs-2D-shapes-all.bt | 267 - .../bifs/bifs-2D-shapes-indexfaceset2D.bt | 123 - .../bifs/bifs-2D-shapes-indexlineset2D.bt | 100 - tests/media/bifs/bifs-2D-shapes-pointset2D.bt | 56 - tests/media/bifs/bifs-2D-shapes-xcurve2D.bt | 188 - ...texturing-compositetexture2D-background.bt | 117 - ...-2D-texturing-compositetexture2D-bitmap.bt | 89 - ...exturing-compositetexture2D-transparent.bt | 129 - .../bifs/bifs-2D-texturing-gradients-text.bt | 137 - ...bifs-2D-texturing-gradients-transparent.bt | 120 - .../bifs-2D-texturing-imagetexture-shapes.bt | 282 - ...bifs-2D-texturing-lineargradient-simple.bt | 75 - ...bifs-2D-texturing-lineargradient-spread.bt | 113 - .../bifs-2D-texturing-movietexture-shapes.bt | 285 - .../bifs/bifs-2D-texturing-pixeltexture.bt | 191 - ...bifs-2D-texturing-radialgradient-simple.bt | 94 - ...bifs-2D-texturing-radialgradient-spread.bt | 118 - ...bifs-2D-texturing-texturetransform-base.bt | 234 - ...-2D-texturing-texturetransform-interact.bt | 231 - ...ring-texturetransform-transformmatrix2D.bt | 242 - tests/media/bifs/bifs-2D-viewport-complete.bt | 715 - tests/media/bifs/bifs-2D-viewport-simple.bt | 88 - tests/media/bifs/bifs-3D-background-images.bt | 87 - tests/media/bifs/bifs-3D-background.bt | 56 - .../bifs-3D-interactivity-collision-proxy.bt | 80 - .../bifs/bifs-3D-interactivity-collision.bt | 79 - .../bifs-3D-interactivity-cylindersensor.bt | 90 - .../bifs/bifs-3D-interactivity-planesensor.bt | 93 - .../bifs-3D-interactivity-proximitysensor.bt | 80 - .../bifs-3D-interactivity-spheresensor.bt | 90 - .../bifs-3D-interactivity-visibilitysensor.bt | 100 - .../bifs/bifs-3D-lighting-directionalLight.bt | 63 - tests/media/bifs/bifs-3D-lighting-fog.bt | 56 - .../media/bifs/bifs-3D-lighting-pointlight.bt | 71 - .../media/bifs/bifs-3D-lighting-spotlight.bt | 72 - ...-positioning-billboard-viewer-alignment.bt | 62 - .../bifs/bifs-3D-positioning-billboard.bt | 61 - .../media/bifs/bifs-3D-positioning-gravity.bt | 71 - .../bifs/bifs-3D-positioning-layer3D-views.bt | 160 - .../media/bifs/bifs-3D-positioning-layer3D.bt | 193 - tests/media/bifs/bifs-3D-positioning-lod.bt | 63 - .../bifs/bifs-3D-positioning-transform.bt | 64 - .../bifs/bifs-3D-shapes-box-transparent.bt | 117 - tests/media/bifs/bifs-3D-shapes-box.bt | 45 - tests/media/bifs/bifs-3D-shapes-cone.bt | 58 - tests/media/bifs/bifs-3D-shapes-cylinder.bt | 92 - .../bifs/bifs-3D-shapes-elevationgrid.bt | 74 - tests/media/bifs/bifs-3D-shapes-extrusion.bt | 99 - .../bifs/bifs-3D-shapes-indexedfaceset.bt | 66 - .../bifs/bifs-3D-shapes-indexedlineset.bt | 99 - .../bifs/bifs-3D-shapes-nonlineardeformer.bt | 796 - tests/media/bifs/bifs-3D-shapes-pointset.bt | 46 - .../bifs/bifs-3D-texturing-box-transparent.bt | 73 - .../media/bifs/bifs-3D-texturing-box-video.bt | 184 - tests/media/bifs/bifs-3D-texturing-box.bt | 69 - ...-3D-texturing-compositetexture3D-bitmap.bt | 113 - ...ifs-3D-texturing-compositetexture3D-box.bt | 106 - .../bifs-3D-texturing-cone-transparent.bt | 68 - tests/media/bifs/bifs-3D-texturing-cone.bt | 81 - .../bifs-3D-texturing-cylinder-transparent.bt | 93 - .../media/bifs/bifs-3D-texturing-cylinder.bt | 107 - .../bifs/bifs-3D-texturing-transform-box.bt | 85 - .../bifs-3D-texturing-transform-matrix-box.bt | 91 - tests/media/bifs/bifs-3D-viewpoint-anim.bt | 84 - .../media/bifs/bifs-3D-viewpoint-bind-jump.bt | 114 - tests/media/bifs/bifs-3D-viewpoint-bind.bt | 115 - .../bifs/bifs-3D-viewpoint-ortho-bind.bt | 118 - .../bifs/bifs-bitmap-image-meter-metrics.bt | 80 - .../bifs/bifs-bitmap-image-pixel-metrics.bt | 89 - .../media/bifs/bifs-bitmap-image-resizing.bt | 153 - .../bifs/bifs-bitmap-movie-materialkey.bt | 101 - tests/media/bifs/bifs-bitmap-movie.bt | 84 - .../media/bifs/bifs-bitmap-video-resizing.bt | 152 - tests/media/bifs/bifs-cachetexture_cache.bt | 90 - tests/media/bifs/bifs-cachetexture_nocache.bt | 57 - .../bifs/bifs-command-animated-osmo4logo.bt | 315 - tests/media/bifs/bifs-command-delete-index.bt | 58 - tests/media/bifs/bifs-command-delete-node.bt | 58 - tests/media/bifs/bifs-command-delete-route.bt | 67 - tests/media/bifs/bifs-command-global-qp.bt | 66 - tests/media/bifs/bifs-command-insert-index.bt | 58 - tests/media/bifs/bifs-command-insert-node.bt | 74 - .../media/bifs/bifs-command-insert-nodedef.bt | 64 - tests/media/bifs/bifs-command-insert-route.bt | 67 - .../bifs-command-multiple-replace-field.bt | 66 - .../bifs-command-multiple-replace-index.bt | 62 - .../media/bifs/bifs-command-node-delete-ex.bt | 125 - tests/media/bifs/bifs-command-proto-delete.bt | 93 - tests/media/bifs/bifs-command-proto-insert.bt | 83 - .../bifs/bifs-command-protolist-delete.bt | 93 - .../media/bifs/bifs-command-quantification.bt | 186 - .../media/bifs/bifs-command-replace-field.bt | 67 - .../media/bifs/bifs-command-replace-index.bt | 58 - .../bifs/bifs-command-replace-node-null.bt | 65 - tests/media/bifs/bifs-command-replace-node.bt | 67 - .../media/bifs/bifs-command-replace-route.bt | 67 - .../bifs/bifs-command-replace-scene-null.bt | 37 - .../media/bifs/bifs-command-replace-scene.bt | 76 - .../bifs/bifs-command-route-add-children.bt | 79 - .../media/bifs/bifs-command-route-children.bt | 79 - .../bifs-command-route-node-exposedfield.bt | 80 - tests/media/bifs/bifs-command-route-node.bt | 76 - .../bifs-command-route-remove-children.bt | 93 - tests/media/bifs/bifs-environmenttest.bt | 129 - .../bifs/bifs-externproto-forestgump-lib.bt | 463 - .../media/bifs/bifs-externproto-forestgump.bt | 96 - .../media/bifs/bifs-externproto-mfurl-lib.bt | 52 - tests/media/bifs/bifs-externproto-mfurl.bt | 69 - tests/media/bifs/bifs-externproto-nood-lib.bt | 109 - tests/media/bifs/bifs-externproto-nood.bt | 70 - .../media/bifs/bifs-externproto-simple-lib.bt | 90 - tests/media/bifs/bifs-externproto-simple.bt | 81 - tests/media/bifs/bifs-game-arrange.bt | 452 - tests/media/bifs/bifs-game-breakout.bt | 1035 - tests/media/bifs/bifs-game-bubble.bt | 433 - tests/media/bifs/bifs-game-minesweeper.bt | 1538 - tests/media/bifs/bifs-game-othello.bt | 2836 - .../bifs-interpolation-colorinterpolator.bt | 63 - ...-interpolation-coordinateinterpolator2D.bt | 64 - .../bifs-interpolation-positionanimator.bt | 63 - .../bifs-interpolation-positionanimator2D.bt | 65 - ...rpolation-positioninterpolator-position.bt | 94 - ...interpolation-positioninterpolator-size.bt | 58 - ...olation-positioninterpolator2D-position.bt | 96 - ...terpolation-positioninterpolator2D-size.bt | 64 - .../bifs/bifs-interpolation-scalaranimator.bt | 66 - .../bifs-interpolation-scalarinterpolator.bt | 63 - .../bifs-interpolation-timesensor-enabled.bt | 82 - ...polation-timesensor-starttime_norestart.bt | 81 - ...erpolation-timesensor-starttime_restart.bt | 82 - .../bifs-interpolation-valuator-sftime.bt | 58 - tests/media/bifs/bifs-keynavigator.bt | 220 - .../bifs/bifs-linking-anchor-mp4-next.bt | 67 - .../bifs/bifs-linking-anchor-mp4-prev.bt | 67 - .../bifs/bifs-linking-anchor-viewpoint.bt | 92 - tests/media/bifs/bifs-linking-anchor-www.bt | 67 - .../bifs/bifs-linking-animationstream.bt | 135 - .../bifs/bifs-linking-inline-direct-inline.bt | 75 - .../media/bifs/bifs-linking-inline-direct.bt | 51 - .../bifs/bifs-linking-inline-http-no-od.bt | 48 - tests/media/bifs/bifs-linking-inline-http.bt | 58 - .../bifs/bifs-linking-inline-od-inline.bt | 76 - tests/media/bifs/bifs-linking-inline-od.bt | 60 - .../bifs-linking-inline-segment-inline.bt | 74 - .../media/bifs/bifs-linking-inline-segment.bt | 41 - tests/media/bifs/bifs-media-audiobuffer.bt | 203 - .../bifs/bifs-media-audioclip-urlchanged.bt | 145 - tests/media/bifs/bifs-media-audioclip.bt | 133 - .../bifs/bifs-media-audiosource-mixing.bt | 194 - .../bifs/bifs-media-audiosource-urlchanged.bt | 146 - tests/media/bifs/bifs-media-audiosource.bt | 133 - .../bifs/bifs-media-imagetexture-OD-reuse.bt | 89 - .../bifs/bifs-media-imagetexture-no-od.bt | 60 - .../bifs-media-imagetexture-object-scale.bt | 97 - .../bifs-media-imagetexture-transparent.bt | 275 - .../bifs-media-imagetexture-url-change.bt | 95 - .../bifs/bifs-media-movietexture-control.bt | 148 - .../bifs/bifs-media-movietexture-no-od.bt | 65 - .../bifs-media-movietexture-od-joinsession.bt | 93 - ...ifs-media-movietexture-od-leave-session.bt | 93 - .../bifs/bifs-media-movietexture-owns-OCR.bt | 75 - .../bifs-media-movietexture-shares-OCR.bt | 74 - .../bifs-media-movietexture-url-change.bt | 158 - .../media/bifs/bifs-media-sound-spatialize.bt | 77 - tests/media/bifs/bifs-media-sound.bt | 77 - tests/media/bifs/bifs-misc-UTF16-input.bt | 55 - tests/media/bifs/bifs-misc-cyclic-graph.bt | 55 - tests/media/bifs/bifs-misc-hc-proto-events.bt | 57 - .../bifs/bifs-misc-hc-proto-offscreengroup.bt | 139 - .../bifs/bifs-misc-hc-proto-pathextrusion.bt | 103 - .../bifs-misc-hc-proto-planarextrusion.bt | 87 - .../bifs-misc-hc-proto-planeclipper-box.bt | 77 - .../bifs/bifs-misc-hc-proto-planeclipper.bt | 69 - .../media/bifs/bifs-misc-hc-proto-texture.bt | 63 - ...ifs-misc-non-linear-parsing-conditional.bt | 69 - .../bifs/bifs-misc-non-linear-parsing-use.bt | 60 - ...-misc-srt-import-3gpp-control-share-ocr.bt | 64 - .../bifs/bifs-misc-srt-import-3gpp-control.bt | 64 - tests/media/bifs/bifs-misc-srt-import-3gpp.bt | 64 - tests/media/bifs/bifs-misc-srt-import.bt | 76 - tests/media/bifs/bifs-od-language-code.bt | 117 - tests/media/bifs/bifs-od-remove-esd.bt | 78 - tests/media/bifs/bifs-od-remove-od.bt | 78 - tests/media/bifs/bifs-od-update-od.bt | 89 - tests/media/bifs/bifs-proto-conditional.bt | 107 - tests/media/bifs/bifs-proto-delete-def.bt | 91 - tests/media/bifs/bifs-proto-delete-index.bt | 95 - tests/media/bifs/bifs-proto-forestgump.bt | 478 - tests/media/bifs/bifs-proto-mfurl.bt | 78 - tests/media/bifs/bifs-proto-multiple.bt | 180 - tests/media/bifs/bifs-proto-nested.bt | 132 - tests/media/bifs/bifs-proto-route.bt | 110 - .../media/bifs/bifs-proto-sftime-protocode.bt | 99 - .../bifs/bifs-proto-sftime-protointerface.bt | 104 - tests/media/bifs/bifs-proto-simple.bt | 88 - tests/media/bifs/bifs-proto-use.bt | 93 - tests/media/bifs/bifs-script-char-to-int.bt | 87 - tests/media/bifs/bifs-script-child-create.bt | 61 - tests/media/bifs/bifs-script-date.bt | 64 - tests/media/bifs/bifs-script-event-out.bt | 85 - tests/media/bifs/bifs-script-initialize.bt | 86 - tests/media/bifs/bifs-script-load-url.bt | 67 - tests/media/bifs/bifs-script-node-access.bt | 86 - tests/media/bifs/bifs-script-node-create.bt | 80 - tests/media/bifs/bifs-script-proto.bt | 224 - tests/media/bifs/bifs-script-timestamp.bt | 68 - tests/media/bifs/bifs-storage.bt | 65 - tests/media/bifs/bifs-stream-text-switch.bt | 143 - tests/media/bifs/bifs-text-align-horiz1.bt | 304 - tests/media/bifs/bifs-text-align-horiz2.bt | 320 - tests/media/bifs/bifs-text-align-horiz3.bt | 320 - tests/media/bifs/bifs-text-align-horiz4.bt | 336 - tests/media/bifs/bifs-text-align-vert1.bt | 200 - tests/media/bifs/bifs-text-align-vert2.bt | 209 - tests/media/bifs/bifs-text-align-vert3.bt | 209 - tests/media/bifs/bifs-text-align-vert4.bt | 218 - tests/media/bifs/bifs-text-glyph-advance.bt | 436 - tests/media/bifs/bifs-text-length.bt | 230 - tests/media/bifs/bifs-text-maxextend.bt | 176 - tests/media/bifs/bifs-text-style.bt | 184 - tests/media/bifs/bifs-text-unicode.bt | 55 - tests/media/bifs/bifs-text-vrml-alignment.bt | 161 - .../bifs/bifs-timeline-mediacontrol-OCR.bt | 245 - .../bifs-timeline-mediacontrol-audio-speed.bt | 214 - .../bifs/bifs-timeline-mediacontrol-audio.bt | 352 - .../bifs-timeline-mediacontrol-complete.bt | 266 - ...bifs-timeline-mediacontrol-deactivation.bt | 105 - .../bifs/bifs-timeline-mediacontrol-http.bt | 340 - ...-timeline-mediacontrol-inline-av-inline.bt | 96 - .../bifs-timeline-mediacontrol-inline-av.bt | 231 - ...ine-mediacontrol-inline-segments-inline.bt | 81 - ...s-timeline-mediacontrol-inline-segments.bt | 66 - .../bifs-timeline-mediacontrol-segments.bt | 154 - .../bifs/bifs-timeline-mediacontrol-video.bt | 258 - .../bifs-timeline-mediacontrol-videospeed.bt | 202 - ...ifs-timeline-mediasensor-segment-switch.bt | 226 - .../bifs/bifs-timeline-mediasensor-segment.bt | 215 - tests/media/bifs/bifs-timeline-mediasensor.bt | 137 - tests/media/bifs/counter-auto.bt | 177 - .../mozilla_ie_action.html | 35 - .../mozilla_ie_simple.html | 24 - .../media/browser_integration/ppc_action.html | 35 - .../media/browser_integration/ppc_simple.html | 25 - tests/media/build-navigator-sh | 11 - tests/media/build-navigator-w32.bat | 14 - tests/media/chaptering/chapters.txt | 8 - tests/media/dom/gpac-dom-portability.js | 144 - tests/media/dom/gpac-html-portability.js | 96 - tests/media/encryption/drm_adobe.xml | 9 - tests/media/encryption/drm_cbc.xml | 28 - tests/media/encryption/drm_cbcs.xml | 28 - tests/media/encryption/drm_cens.xml | 25 - tests/media/encryption/drm_ctr.xml | 34 - tests/media/encryption/drm_isma.xml | 4 - tests/media/html5_video/basic_arraybuffer.js | 21 - tests/media/html5_video/basic_audio.svg | 12 - tests/media/html5_video/basic_mediasource.js | 25 - tests/media/html5_video/basic_sourcebuffer.js | 53 - tests/media/html5_video/basic_url.js | 26 - tests/media/html5_video/basic_video.js | 81 - tests/media/html5_video/basic_video.svg | 7 - tests/media/html5_video/bind.js | 26 - .../counter-mp4-audio-segments-http.js | 20 - .../counter-mp4-av-segments-http.js | 20 - .../counter-mp4-video-segments-http.js | 36 - ...unter-mp4-video-segments-live-nobs-http.js | 36 - .../counter-mp4-video-segments-local.js | 28 - tests/media/html5_video/file.json | 1 - tests/media/html5_video/file.txt | 1 - tests/media/html5_video/file.xml | 4 - tests/media/html5_video/gpac-mse-spatial.js | 55 - tests/media/html5_video/gpac-mse.js | 358 - .../html5_video/implementation_notes.txt | 36 - tests/media/html5_video/mediaevents.js | 348 - tests/media/html5_video/mediaevents.svg | 29 - tests/media/html5_video/mse-overlap.js | 71 - tests/media/html5_video/mse.svg | 58 - tests/media/html5_video/myanmar-tiles.js | 135 - tests/media/html5_video/nodejs-byte-server.js | 71 - .../redbull-mp4-audio-segments-http.js | 36 - .../redbull-mp4-video-segments-http.js | 140 - tests/media/html5_video/spatial-mse.svg | 28 - tests/media/html5_video/two-videos.svg | 33 - tests/media/html5_video/video.svg | 30 - tests/media/html5_video/xhr.js | 147 - tests/media/html5_video/xhr.svg | 4 - tests/media/index.css | 134 - tests/media/index.xml | 373 - tests/media/laser/enst_2laser3.xml | 90 - tests/media/laser/enst_3laser.xml | 53 - tests/media/laser/enst_afrique.xml | 70 - tests/media/laser/enst_amerique_centrale.xml | 23 - tests/media/laser/enst_amerique_sud.xml | 30 - tests/media/laser/enst_canvas.xml | 193 - tests/media/laser/enst_cee.xml | 103 - tests/media/laser/enst_europe.xml | 114 - tests/media/laser/enst_flow.xml | 1161 - tests/media/laser/enst_gold.xml | 637 - tests/media/laser/enst_hame.xml | 1505 - tests/media/laser/enst_sydn.xml | 292 - tests/media/laser/enst_topk.xml | 1240 - tests/media/laser/enst_tram.xml | 784 - tests/media/laser/stz_Navigation_simple.xml | 27 - tests/media/laser/stz_a_parsing.xml | 41 - tests/media/laser/stz_a_parsing2.xml | 21 - tests/media/laser/stz_animateMotion_mpath.xml | 32 - .../media/laser/stz_animateMotion_parsing.xml | 92 - .../laser/stz_animateMotion_parsing2.xml | 22 - .../laser/stz_animateTransform_rotate.xml | 37 - .../laser/stz_animateTransform_rotate2.xml | 21 - .../laser/stz_animateTransform_scale.xml | 28 - .../laser/stz_animateTransform_scale2.xml | 17 - .../media/laser/stz_animateTransform_skew.xml | 35 - .../laser/stz_animateTransform_skew2.xml | 22 - .../laser/stz_animateTransform_translate.xml | 35 - .../laser/stz_animateTransform_translate2.xml | 19 - tests/media/laser/stz_animate_choice.xml | 28 - tests/media/laser/stz_animate_choice2.xml | 21 - .../laser/stz_animate_colorrendering.xml | 23 - .../laser/stz_animate_colorrendering2.xml | 16 - .../media/laser/stz_animate_display-align.xml | 22 - .../laser/stz_animate_display-align2.xml | 15 - tests/media/laser/stz_animate_display.xml | 23 - tests/media/laser/stz_animate_display2.xml | 16 - tests/media/laser/stz_animate_editable.xml | 22 - tests/media/laser/stz_animate_editable2.xml | 15 - tests/media/laser/stz_animate_fill-rule.xml | 25 - tests/media/laser/stz_animate_fill-rule2.xml | 19 - tests/media/laser/stz_animate_fill.xml | 23 - tests/media/laser/stz_animate_fill2.xml | 16 - .../laser/stz_animate_fillstroke-opacity.xml | 26 - .../laser/stz_animate_fillstroke-opacity2.xml | 17 - tests/media/laser/stz_animate_font-family.xml | 22 - .../media/laser/stz_animate_font-family2.xml | 15 - tests/media/laser/stz_animate_font-style.xml | 22 - tests/media/laser/stz_animate_font-style2.xml | 15 - tests/media/laser/stz_animate_font-weight.xml | 22 - .../media/laser/stz_animate_font-weight2.xml | 15 - .../media/laser/stz_animate_gradientUnits.xml | 29 - .../laser/stz_animate_gradientUnits2.xml | 20 - tests/media/laser/stz_animate_gsize.xml | 28 - tests/media/laser/stz_animate_gsize2.xml | 21 - tests/media/laser/stz_animate_hrefa.xml | 30 - tests/media/laser/stz_animate_hrefa2.xml | 20 - .../media/laser/stz_animate_hrefstreamid.xml | 757 - .../media/laser/stz_animate_hrefstreamid2.xml | 746 - tests/media/laser/stz_animate_hrefuse.xml | 31 - tests/media/laser/stz_animate_hrefuse2.xml | 21 - .../laser/stz_animate_image-rendering.xml | 21 - .../laser/stz_animate_image-rendering2.xml | 14 - tests/media/laser/stz_animate_paintserver.xml | 31 - .../media/laser/stz_animate_paintserver2.xml | 23 - tests/media/laser/stz_animate_parsing.xml | 45 - tests/media/laser/stz_animate_parsing2.xml | 20 - tests/media/laser/stz_animate_path.xml | 25 - tests/media/laser/stz_animate_path2.xml | 18 - .../laser/stz_animate_pointer-events.xml | 23 - .../laser/stz_animate_pointer-events2.xml | 16 - tests/media/laser/stz_animate_points.xml | 24 - tests/media/laser/stz_animate_points2.xml | 16 - .../laser/stz_animate_preserveAspectRatio.xml | 29 - .../stz_animate_preserveAspectRatio2.xml | 319 - .../laser/stz_animate_shaperendering.xml | 23 - .../laser/stz_animate_shaperendering2.xml | 16 - .../laser/stz_animate_stroke-linecap.xml | 23 - .../laser/stz_animate_stroke-linecap2.xml | 16 - .../laser/stz_animate_stroke-linejoin.xml | 23 - .../laser/stz_animate_stroke-linejoin2.xml | 16 - .../media/laser/stz_animate_stroke-width.xml | 23 - .../media/laser/stz_animate_stroke-width2.xml | 16 - tests/media/laser/stz_animate_text-anchor.xml | 22 - .../media/laser/stz_animate_text-anchor2.xml | 15 - .../media/laser/stz_animate_vector-effect.xml | 23 - .../laser/stz_animate_vector-effect2.xml | 16 - tests/media/laser/stz_animate_viewBox.xml | 26 - tests/media/laser/stz_animate_viewBox2.xml | 17 - tests/media/laser/stz_animate_visibility.xml | 25 - tests/media/laser/stz_animate_visibility2.xml | 18 - tests/media/laser/stz_animate_xy.xml | 34 - tests/media/laser/stz_animate_xy2.xml | 17 - tests/media/laser/stz_anyXML_replace.xml | 18 - tests/media/laser/stz_circle_parsing.xml | 21 - tests/media/laser/stz_circle_parsing2.xml | 14 - tests/media/laser/stz_defs_parsing.xml | 28 - tests/media/laser/stz_defs_parsing2.xml | 21 - tests/media/laser/stz_desc_parsing.xml | 21 - tests/media/laser/stz_desc_parsing2.xml | 15 - tests/media/laser/stz_ellipse_parsing.xml | 21 - tests/media/laser/stz_ellipse_parsing2.xml | 14 - .../media/laser/stz_foreignObject_parsing.xml | 27 - tests/media/laser/stz_g_parsing.xml | 45 - tests/media/laser/stz_g_parsing2.xml | 33 - tests/media/laser/stz_image_parsing.xml | 45 - tests/media/laser/stz_image_parsing2.xml | 319 - tests/media/laser/stz_line_parsing.xml | 21 - tests/media/laser/stz_line_parsing2.xml | 14 - tests/media/laser/stz_linearGradient_bbox.xml | 30 - .../media/laser/stz_linearGradient_bbox2.xml | 21 - .../laser/stz_linearGradient_userSpace.xml | 37 - .../laser/stz_linearGradient_userSpace2.xml | 26 - tests/media/laser/stz_metadata_parsing.xml | 44 - tests/media/laser/stz_path_parsing.xml | 41 - tests/media/laser/stz_path_parsing2.xml | 25 - tests/media/laser/stz_polygon_parsing.xml | 34 - tests/media/laser/stz_polygon_parsing2.xml | 17 - tests/media/laser/stz_polyline_parsing.xml | 34 - tests/media/laser/stz_polyline_parsing2.xml | 17 - tests/media/laser/stz_rect_parsing.xml | 50 - tests/media/laser/stz_rect_parsing2.xml | 15 - tests/media/laser/stz_set_color.xml | 24 - tests/media/laser/stz_set_color2.xml | 17 - tests/media/laser/stz_svg_parsing.xml | 42 - tests/media/laser/stz_svg_parsing2.xml | 15 - tests/media/laser/stz_switch_parsing.xml | 31 - tests/media/laser/stz_text_parsing.xml | 38 - tests/media/laser/stz_text_parsing2.xml | 22 - tests/media/laser/stz_title_parsing.xml | 21 - tests/media/laser/stz_use_parsing.xml | 26 - tests/media/laser/stz_use_parsing2.xml | 19 - tests/media/laser/x_austria_relief.png | Bin 56516 -> 0 bytes tests/media/svg/all_syntaxes_1.1F2.svg | 498 - tests/media/svg/createanim-by-script.svg | 73 - tests/media/svg/createimage-by-script.svg | 16 - tests/media/svg/opacity.svg | 67 - tests/media/svg/utfscript.svg | 14 - tests/media/swf/cuisine.swf | Bin 42416 -> 0 bytes tests/media/ttml/ebu-ttd_prefix.ttml | 30 - tests/media/ttml/ebu-ttd_sample.ttml | 39 - ...u-ttd_sample_invalid_mixed_namespaces.ttml | 28 - .../media/ttml/ebu-ttd_sample_invalid_ns.ttml | 28 - .../ttml/ebu-ttd_sample_invalid_root.ttml | 28 - tests/media/ttml/ebu-ttd_sample_span.ttml | 29 - .../media/ttml/ebu-ttd_timing_contiguous.ttml | 30 - .../ttml/ebu-ttd_timing_non-contiguous.ttml | 30 - .../ttml/ebu-ttd_timing_overlapping_fail.ttml | 29 - tests/media/ttml/ttml.nhml | 14 - tests/media/webvtt/comments.vtt | 45 - tests/media/webvtt/concatenation.vtt | 51 - tests/media/webvtt/counter.srt | 2400 - tests/media/webvtt/counter.vtt | 4502 -- .../webvtt/elephants-dream-chapters-en.vtt | 29 - .../webvtt/elephants-dream-subtitles-de.vtt | 309 - .../webvtt/elephants-dream-subtitles-en.vtt | 357 - tests/media/webvtt/empty.vtt | 1 - tests/media/webvtt/empty2.vtt | 1 - tests/media/webvtt/empty3.vtt | 2 - tests/media/webvtt/empty4.vtt | 3 - tests/media/webvtt/header.vtt | 42 - tests/media/webvtt/invalid1.vtt | 3 - tests/media/webvtt/invalid2.vtt | 3 - tests/media/webvtt/invalid3.vtt | 5 - tests/media/webvtt/invalid4.vtt | 8 - tests/media/webvtt/invalid5.vtt | 12 - tests/media/webvtt/long-duration.vtt | 43814 ------------- .../webvtt/multiline-header-additional.vtt | 23 - .../webvtt/multiline-header-id-invalid.vtt | 23 - tests/media/webvtt/multiline-header-id.vtt | 22 - tests/media/webvtt/multiline-header.vtt | 25 - tests/media/webvtt/overlapping-end.vtt | 17 - tests/media/webvtt/overlapping-middle.vtt | 17 - tests/media/webvtt/overlapping-rewritten.vtt | 81 - tests/media/webvtt/overlapping-start.vtt | 17 - tests/media/webvtt/overlapping.vtt | 26 - tests/media/webvtt/simple.vtt | 32 - tests/media/webvtt/spaces.vtt | 36 - tests/media/webvtt/spec-example-basic.vtt | 40 - tests/media/webvtt/spec-example-comment.vtt | 10 - tests/media/webvtt/spec-example-comment2.vtt | 21 - .../media/webvtt/spec-example-identifier.vtt | 8 - .../webvtt/spec-example-multiple-lines.vtt | 11 - tests/media/webvtt/spec-example-nested.vtt | 19 - tests/media/webvtt/spec-example-voice.vtt | 13 - tests/media/webvtt/svg.vtt | 4502 -- tests/media/webvtt/timestamps-invalid.vtt | 10 - tests/media/webvtt/timestamps.vtt | 19 - tests/media/x3d/x3d-2D-Arc2d.x3dv | 23 - tests/media/x3d/x3d-2D-ArcClose2d.x3dv | 24 - tests/media/x3d/x3d-2D-Disk2d.x3dv | 24 - tests/media/x3d/x3d-2D-Polyline2d.x3dv | 23 - tests/media/x3d/x3d-2D-Polypoint2d.x3dv | 23 - tests/media/x3d/x3d-2D-TriangleSet2d.x3dv | 24 - .../x3d/x3d-3D-IndexedTriangleFanSet.x3dv | 28 - .../media/x3d/x3d-3D-IndexedTriangleSet.x3dv | 27 - .../x3d/x3d-3D-IndexedTriangleStripSet.x3dv | 28 - tests/media/x3d/x3d-3D-LineSet.x3dv | 27 - tests/media/x3d/x3d-3D-TriangleFanSet.x3dv | 28 - tests/media/x3d/x3d-3D-TriangleSet.x3dv | 26 - tests/media/x3d/x3d-3D-TriangleStripSet.x3dv | 28 - tests/media/x3d/x3d-misc-ColorRGBA.x3dv | 42 - tests/media/x3d/x3d-misc-HatchStyle.x3dv | 31 - tests/media/x3d/x3d-misc-KeySensor.x3dv | 73 - tests/media/x3d/x3d-misc-StringSensor.x3dv | 83 - tests/media/xmlin4/ebu-ttd_sample.ttml | 39 - tests/media/xmlin4/first.xml | 1 - tests/media/xmlin4/input.txt | 13 - tests/media/xmlin4/input.xml | 5 - tests/media/xmlin4/last.xml | 1 - tests/media/xmlin4/meta-mett-no-mime.nhml | 8 - tests/media/xmlin4/meta-mett-xml-header.nhml | 6 - tests/media/xmlin4/meta-mett-xml.nhml | 6 - tests/media/xmlin4/meta-mett.nhml | 8 - .../media/xmlin4/meta-metx-no-namespace.nhml | 6 - tests/media/xmlin4/meta-metx.nhml | 6 - tests/media/xmlin4/second.xml | 1 - tests/media/xmlin4/subt-sbtt-no-mime.nhml | 6 - tests/media/xmlin4/subt-sbtt.nhml | 6 - .../media/xmlin4/subt-stpp-no-namespace.nhml | 6 - tests/media/xmlin4/subt-stpp.nhml | 6 - tests/media/xmlin4/text-stxt-header.nhml | 7 - tests/media/xmlin4/text-stxt-no-mime.nhml | 8 - tests/media/xmlin4/text-stxt.nhml | 8 - ...D-background-background2D-bind-play-ui.xml | 73 - ...-background-background2D-image-play-ui.xml | 62 - ...-background-background2D-movie-play-ui.xml | 43 - ...ground-background2D-url-change-play-ui.xml | 53 - ...fs-2D-interactivity-discsensor-play-ui.xml | 120 - ...D-interactivity-htk-sensor-play-stderr.txt | 1 - ...ifs-2D-interactivity-keysensor-play-ui.xml | 97 - ...s-2D-interactivity-mousesensor-play-ui.xml | 93 - ...D-interactivity-nested-sensors-play-ui.xml | 81 - ...2D-interactivity-planesensor2D-play-ui.xml | 80 - ...nteractivity-proximitysensor2D-play-ui.xml | 75 - ...-2D-interactivity-stringsensor-play-ui.xml | 62 - ...eractivity-touchsensor-4states-play-ui.xml | 66 - ...ractivity-touchsensor-hitpoint-play-ui.xml | 115 - ...chsensor-isactive-exposedfield-play-ui.xml | 36 - ...ractivity-touchsensor-isactive-play-ui.xml | 26 - ...teractivity-touchsensor-isover-play-ui.xml | 40 - ...activity-touchsensor-move_over-play-ui.xml | 54 - ...D-painting-xlineproperties-cap-play-ui.xml | 75 - ...-painting-xlineproperties-join-play-ui.xml | 52 - ...xlineproperties-lineargradient-play-ui.xml | 23 - ...xlineproperties-radialgradient-play-ui.xml | 26 - .../bifs-2D-positioning-clipper2D-play-ui.xml | 20 - .../bifs-2D-positioning-layer2D-play-ui.xml | 34 - ...ning-layout-scroll-modes-horiz-play-ui.xml | 81 - ...oning-layout-scroll-modes-vert-play-ui.xml | 99 - ...sitioning-layout-scroll-on-off-play-ui.xml | 62 - ...fs-2D-positioning-orderedgroup-play-ui.xml | 34 - ...ositioning-pathlayout-graphics-play-ui.xml | 46 - ...bifs-2D-positioning-pathlayout-play-ui.xml | 40 - ...-positioning-transformmatrix2D-play-ui.xml | 63 - ...-compositetexture2D-background-play-ui.xml | 46 - ...compositetexture2D-transparent-play-ui.xml | 53 - ...fs-2D-texturing-gradients-text-play-ui.xml | 89 - ...exturing-gradients-transparent-play-ui.xml | 112 - ...exturing-lineargradient-simple-play-ui.xml | 123 - ...exturing-lineargradient-spread-play-ui.xml | 42 - ...exturing-radialgradient-simple-play-ui.xml | 92 - ...exturing-radialgradient-spread-play-ui.xml | 33 - ...ring-texturetransform-interact-play-ui.xml | 125 - .../rules/bifs-2D-viewport-simple-play-ui.xml | 36 - ...D-interactivity-cylindersensor-play-ui.xml | 101 - ...s-3D-interactivity-planesensor-play-ui.xml | 105 - ...-interactivity-proximitysensor-play-ui.xml | 56 - ...-3D-interactivity-spheresensor-play-ui.xml | 134 - ...interactivity-visibilitysensor-play-ui.xml | 44 - tests/rules/bifs-3D-lighting-fog-play-ui.xml | 53 - .../bifs-3D-positioning-layer3D-play-ui.xml | 149 - ...bifs-3D-shapes-box-transparent-play-ui.xml | 78 - .../bifs-3D-shapes-elevationgrid-play-ui.xml | 70 - .../bifs-3D-shapes-extrusion-play-ui.xml | 65 - ...fs-3D-shapes-nonlineardeformer-play-ui.xml | 5 - ...ring-compositetexture3D-bitmap-play-ui.xml | 43 - .../bifs-3D-viewpoint-bind-jump-play-ui.xml | 65 - .../rules/bifs-3D-viewpoint-bind-play-ui.xml | 55 - .../bifs-3D-viewpoint-ortho-bind-play-ui.xml | 56 - ...ifs-bitmap-image-meter-metrics-play-ui.xml | 96 - ...ifs-bitmap-image-pixel-metrics-play-ui.xml | 109 - .../bifs-cachetexture_nocache-play-ui.xml | 64 - .../bifs-command-proto-delete-play-stderr.txt | 4 - ...s-command-protolist-delete-play-stderr.txt | 3 - tests/rules/bifs-game-arrange-play-ui.xml | 118 - tests/rules/bifs-game-arrange.sh | 4 - tests/rules/bifs-game-breakout-play-ui.xml | 79 - tests/rules/bifs-game-breakout.sh | 4 - tests/rules/bifs-game-bubble-play-ui.xml | 109 - tests/rules/bifs-game-bubble.sh | 4 - tests/rules/bifs-game-minesweeper-play-ui.xml | 57 - tests/rules/bifs-game-minesweeper.sh | 5 - tests/rules/bifs-game-othello-play-ui.xml | 153 - tests/rules/bifs-game-othello.sh | 4 - ...terpolation-timesensor-enabled-play-ui.xml | 63 - ...timesensor-starttime_norestart-play-ui.xml | 47 - ...n-timesensor-starttime_restart-play-ui.xml | 44 - tests/rules/bifs-keynavigator-play-ui.xml | 70 - ...s-linking-inline-direct-inline-play-ui.xml | 83 - .../bifs-linking-inline-od-inline-play-ui.xml | 114 - ...-linking-inline-segment-inline-play-ui.xml | 61 - .../rules/bifs-media-audiobuffer-play-ui.xml | 84 - tests/rules/bifs-media-audioclip-play-ui.xml | 73 - ...ifs-media-audioclip-urlchanged-play-ui.xml | 51 - .../bifs-media-audiosource-mixing-play-ui.xml | 152 - .../rules/bifs-media-audiosource-play-ui.xml | 54 - ...s-media-audiosource-urlchanged-play-ui.xml | 54 - ...-media-imagetexture-url-change-play-ui.xml | 86 - ...ifs-media-movietexture-control-play-ui.xml | 68 - ...-media-movietexture-url-change-play-ui.xml | 39 - .../bifs-misc-hc-proto-events-play-ui.xml | 92 - ...non-linear-parsing-conditional-play-ui.xml | 72 - .../rules/bifs-proto-conditional-play-ui.xml | 30 - tests/rules/bifs-proto-multiple-play-ui.xml | 79 - tests/rules/bifs-proto-nested-play-ui.xml | 54 - tests/rules/bifs-proto-route-play-ui.xml | 21 - .../bifs-script-child-create-play-ui.xml | 31 - tests/rules/bifs-script-date.sh | 4 - tests/rules/bifs-script-load-url-play-ui.xml | 4 - .../rules/bifs-script-node-create-play-ui.xml | 50 - tests/rules/bifs-script-proto-play-ui.xml | 100 - tests/rules/bifs-storage-play-ui.xml | 68 - .../rules/bifs-stream-text-switch-play-ui.xml | 37 - ...bifs-timeline-mediacontrol-OCR-play-ui.xml | 139 - tests/rules/bifs-timeline-mediacontrol-OCR.sh | 2 - ...fs-timeline-mediacontrol-audio-play-ui.xml | 82 - ...eline-mediacontrol-audio-speed-play-ui.xml | 64 - ...timeline-mediacontrol-complete-play-ui.xml | 95 - ...line-mediacontrol-deactivation-play-ui.xml | 26 - ...ifs-timeline-mediacontrol-http-play-ui.xml | 112 - ...imeline-mediacontrol-inline-av-play-ui.xml | 105 - ...s-timeline-mediacontrol-inline-segments.sh | 2 - ...timeline-mediacontrol-segments-play-ui.xml | 23 - ...fs-timeline-mediacontrol-video-play-ui.xml | 85 - ...meline-mediacontrol-videospeed-play-ui.xml | 59 - ...r-stz_animate_hrefstreamid-play-stderr.txt | 2 - ...-stz_animate_hrefstreamid2-play-stderr.txt | 1 - ...n-meta-metx-no-namespace-import-stderr.txt | 3 - .../xmlin-subt-stpp-export-track-stderr.txt | 3 - ...n-subt-stpp-no-namespace-import-stderr.txt | 2 - .../xmlin-ttml-stpp-export-track-stderr.txt | 3 - tests/scripts/3D-HEVC.sh | 23 - tests/scripts/bifs_all.sh | 147 - tests/scripts/compositor_modes.sh | 80 - tests/scripts/dash-if-live.sh | 11 - tests/scripts/dash-if-ondemand.sh | 11 - tests/scripts/dash-live-generation.sh | 14 - tests/scripts/dash-live.sh | 11 - tests/scripts/dash-multiperiod.sh | 24 - tests/scripts/dash-ts.sh | 17 - tests/scripts/dash.sh | 11 - tests/scripts/encryption.sh | 61 - tests/scripts/fuzz-base.sh | 114 - tests/scripts/hls.sh | 14 - tests/scripts/iff.sh | 15 - tests/scripts/item.sh | 49 - tests/scripts/laser_all.sh | 81 - tests/scripts/mp42ts.sh | 45 - tests/scripts/mp4box-base.sh | 86 - tests/scripts/mp4box-io.sh | 119 - tests/scripts/mp4box-kind.sh | 157 - tests/scripts/mp4box-lang.sh | 86 - tests/scripts/mp4box-rtp.sh | 48 - tests/scripts/mp4box-subtitle.sh | 41 - tests/scripts/mp4client-base.sh | 65 - tests/scripts/mp4client-dash-config.sh | 31 - tests/scripts/mp4client-dash.sh | 33 - tests/scripts/mp4client-http.sh | 35 - tests/scripts/mp4client-rawout.sh | 36 - tests/scripts/negctts.sh | 16 - tests/scripts/nhml-subs.sh | 9 - tests/scripts/noFragsDefault.sh | 11 - tests/scripts/range_extension.sh | 35 - tests/scripts/scalable.sh | 51 - tests/scripts/smooth.sh | 45 - tests/scripts/swf.sh | 8 - tests/scripts/test_prl.sh | 28 - tests/scripts/vtt-all.sh | 43 - tests/scripts/x3d.sh | 27 - tests/scripts/xmlin.sh | 100 - tests/stylesheet.xsl | 38 - 1953 files changed, 429888 insertions(+), 291234 deletions(-) create mode 100644 .gitmodules delete mode 100644 AUTHORS delete mode 100644 BUGS delete mode 100644 INSTALLME delete mode 100644 TODO rename applications/{ => deprecated}/mp42ts/Makefile (80%) rename applications/{ => deprecated}/mp42ts/main.c (94%) create mode 100644 applications/deprecated/mp42ts/mp42ts.vcxproj create mode 100644 applications/deprecated/old_arch/GPAX/GPAX.cpp create mode 100644 applications/deprecated/old_arch/GPAX/GPAX.def create mode 100644 applications/deprecated/old_arch/GPAX/GPAX.dsp create mode 100644 applications/deprecated/old_arch/GPAX/GPAX.h create mode 100644 applications/deprecated/old_arch/GPAX/GPAX.idl create mode 100644 applications/deprecated/old_arch/GPAX/GPAX.rc create mode 100644 applications/deprecated/old_arch/GPAX/GPAX.rgs create mode 100644 applications/deprecated/old_arch/GPAX/GPAX.vcxproj create mode 100644 applications/deprecated/old_arch/GPAX/GPAXPlugin.cpp create mode 100644 applications/deprecated/old_arch/GPAX/GPAXPlugin.h create mode 100644 applications/deprecated/old_arch/GPAX/GPAX_i.c create mode 100644 applications/deprecated/old_arch/GPAX/GPAX_p.c create mode 100644 applications/deprecated/old_arch/GPAX/GPAXps.def create mode 100644 applications/deprecated/old_arch/GPAX/GPAXps.mk create mode 100644 applications/deprecated/old_arch/GPAX/StdAfx.cpp create mode 100644 applications/deprecated/old_arch/GPAX/StdAfx.h create mode 100644 applications/deprecated/old_arch/GPAX/dlldata.c create mode 100644 applications/deprecated/old_arch/GPAX/gpax.bmp create mode 100644 applications/deprecated/old_arch/GPAX/resource.h rename applications/{ => deprecated/old_arch}/dashcast/Makefile (78%) rename applications/{ => deprecated/old_arch}/dashcast/audio_data.c (100%) rename applications/{ => deprecated/old_arch}/dashcast/audio_data.h (98%) rename applications/{ => deprecated/old_arch}/dashcast/audio_decoder.c (95%) rename applications/{ => deprecated/old_arch}/dashcast/audio_decoder.h (97%) rename applications/{ => deprecated/old_arch}/dashcast/audio_encoder.c (93%) rename applications/{ => deprecated/old_arch}/dashcast/audio_encoder.h (100%) rename applications/{ => deprecated/old_arch}/dashcast/audio_muxer.c (96%) rename applications/{ => deprecated/old_arch}/dashcast/audio_muxer.h (97%) rename applications/{ => deprecated/old_arch}/dashcast/circular_buffer.c (100%) rename applications/{ => deprecated/old_arch}/dashcast/circular_buffer.h (100%) rename applications/{ => deprecated/old_arch}/dashcast/cmd_data.c (100%) rename applications/{ => deprecated/old_arch}/dashcast/cmd_data.h (100%) rename applications/{ => deprecated/old_arch}/dashcast/controler.c (99%) rename applications/{ => deprecated/old_arch}/dashcast/controler.h (100%) rename applications/{ => deprecated/old_arch}/dashcast/dashcast.c (100%) rename applications/{ => deprecated/old_arch}/dashcast/libav_compat.h (100%) rename applications/{ => deprecated/old_arch}/dashcast/message_queue.c (100%) rename applications/{ => deprecated/old_arch}/dashcast/message_queue.h (100%) rename applications/{ => deprecated/old_arch}/dashcast/register.c (100%) rename applications/{ => deprecated/old_arch}/dashcast/register.h (100%) rename applications/{ => deprecated/old_arch}/dashcast/task.c (100%) rename applications/{ => deprecated/old_arch}/dashcast/task.h (100%) rename applications/{ => deprecated/old_arch}/dashcast/video_data.c (100%) rename applications/{ => deprecated/old_arch}/dashcast/video_data.h (99%) rename applications/{ => deprecated/old_arch}/dashcast/video_decoder.c (99%) rename applications/{ => deprecated/old_arch}/dashcast/video_decoder.h (100%) rename applications/{ => deprecated/old_arch}/dashcast/video_encoder.c (96%) rename applications/{ => deprecated/old_arch}/dashcast/video_encoder.h (100%) rename applications/{ => deprecated/old_arch}/dashcast/video_muxer.c (98%) rename applications/{ => deprecated/old_arch}/dashcast/video_muxer.h (100%) rename applications/{ => deprecated/old_arch}/dashcast/video_scaler.c (100%) rename applications/{ => deprecated/old_arch}/dashcast/video_scaler.h (100%) rename applications/{ => deprecated/old_arch}/mp42avi/Makefile (92%) rename applications/{ => deprecated/old_arch}/mp42avi/main.c (95%) create mode 100644 applications/deprecated/old_arch/osmo4_sym/aif/osmo4_icon.bmp create mode 100644 applications/deprecated/old_arch/osmo4_sym/aif/osmo4_icon_mask.bmp create mode 100644 applications/deprecated/old_arch/osmo4_sym/aif/osmo4_menu.bmp create mode 100644 applications/deprecated/old_arch/osmo4_sym/aif/osmo4_menu_mask.bmp create mode 100644 applications/deprecated/old_arch/osmo4_sym/aif/osmo4aif.rss create mode 100644 applications/deprecated/old_arch/osmo4_sym/osmo4.cpp create mode 100644 applications/deprecated/old_arch/osmo4_sym/osmo4.h create mode 100644 applications/deprecated/old_arch/osmo4_sym/osmo4_ui.cpp create mode 100644 applications/deprecated/old_arch/osmo4_sym/osmo4_ui.h create mode 100644 applications/deprecated/old_arch/osmo4_sym/osmo4_view.cpp create mode 100644 applications/deprecated/old_arch/osmo4_sym/osmo4_view.h create mode 100644 applications/deprecated/old_arch/osmo4_sym/playlist.cpp create mode 100644 applications/deprecated/old_arch/osmo4_sym/playlist.h create mode 100644 applications/deprecated/old_arch/osmo4_sym/res/osmo4.rss create mode 100644 applications/deprecated/old_arch/osmo4_sym/res/osmo4.svg create mode 100644 applications/deprecated/old_arch/osmo4_sym/res/osmo4_caption.rss create mode 100644 applications/deprecated/old_arch/osmo4_sym/res/osmo4_gen.rss create mode 100644 applications/deprecated/old_arch/osmo4_sym/res/osmo4_reg.rss create mode 100644 applications/deprecated/old_arch/osmo4_w32/AddressBar.cpp create mode 100644 applications/deprecated/old_arch/osmo4_w32/AddressBar.h create mode 100644 applications/deprecated/old_arch/osmo4_w32/FileProps.cpp create mode 100644 applications/deprecated/old_arch/osmo4_w32/FileProps.h create mode 100644 applications/deprecated/old_arch/osmo4_w32/MainFrm.cpp create mode 100644 applications/deprecated/old_arch/osmo4_w32/MainFrm.h create mode 100644 applications/deprecated/old_arch/osmo4_w32/OpenUrl.cpp create mode 100644 applications/deprecated/old_arch/osmo4_w32/OpenUrl.h create mode 100644 applications/deprecated/old_arch/osmo4_w32/Options.cpp create mode 100644 applications/deprecated/old_arch/osmo4_w32/Options.h create mode 100644 applications/deprecated/old_arch/osmo4_w32/Osmo4.cpp create mode 100644 applications/deprecated/old_arch/osmo4_w32/Osmo4.h create mode 100644 applications/deprecated/old_arch/osmo4_w32/Osmo4.rc create mode 100644 applications/deprecated/old_arch/osmo4_w32/Osmo4.vcxproj create mode 100644 applications/deprecated/old_arch/osmo4_w32/Playlist.cpp create mode 100644 applications/deprecated/old_arch/osmo4_w32/Playlist.h create mode 100644 applications/deprecated/old_arch/osmo4_w32/Sliders.cpp create mode 100644 applications/deprecated/old_arch/osmo4_w32/Sliders.h create mode 100644 applications/deprecated/old_arch/osmo4_w32/StdAfx.cpp create mode 100644 applications/deprecated/old_arch/osmo4_w32/StdAfx.h create mode 100644 applications/deprecated/old_arch/osmo4_w32/res/Osmo4.rc2 create mode 100644 applications/deprecated/old_arch/osmo4_w32/res/error.ico create mode 100644 applications/deprecated/old_arch/osmo4_w32/res/maintool.bmp create mode 100644 applications/deprecated/old_arch/osmo4_w32/res/message.ico create mode 100644 applications/deprecated/old_arch/osmo4_w32/res/osmo4.ico create mode 100644 applications/deprecated/old_arch/osmo4_w32/res/pause.ico create mode 100644 applications/deprecated/old_arch/osmo4_w32/res/play.ico create mode 100644 applications/deprecated/old_arch/osmo4_w32/res/playlist.bmp create mode 100644 applications/deprecated/old_arch/osmo4_w32/res/stop.ico create mode 100644 applications/deprecated/old_arch/osmo4_w32/resource.h create mode 100644 applications/deprecated/old_arch/osmo4_wce/MainFrm.cpp create mode 100644 applications/deprecated/old_arch/osmo4_wce/MainFrm.h create mode 100644 applications/deprecated/old_arch/osmo4_wce/OpenDlg.cpp create mode 100644 applications/deprecated/old_arch/osmo4_wce/OpenDlg.h create mode 100644 applications/deprecated/old_arch/osmo4_wce/Options.cpp create mode 100644 applications/deprecated/old_arch/osmo4_wce/Options.h create mode 100644 applications/deprecated/old_arch/osmo4_wce/Osmo4.cpp create mode 100644 applications/deprecated/old_arch/osmo4_wce/Osmo4.h create mode 100644 applications/deprecated/old_arch/osmo4_wce/Osmo4.rc create mode 100644 applications/deprecated/old_arch/osmo4_wce/ProgressBar.cpp create mode 100644 applications/deprecated/old_arch/osmo4_wce/ProgressBar.h create mode 100644 applications/deprecated/old_arch/osmo4_wce/Resource.h create mode 100644 applications/deprecated/old_arch/osmo4_wce/StdAfx.cpp create mode 100644 applications/deprecated/old_arch/osmo4_wce/StdAfx.h create mode 100644 applications/deprecated/old_arch/osmo4_wce/newres.h create mode 100644 applications/deprecated/old_arch/osmo4_wce/res/Cmdbar.bmp create mode 100644 applications/deprecated/old_arch/osmo4_wce/res/Osmo4.ico create mode 100644 applications/deprecated/old_arch/osmo4_wce/res/Osmo4.rc2 rename {bin => applications/deprecated/old_arch/osmo4_wce}/smartphone 2003 (armv4)/release/install/archive.bat (90%) rename {bin => applications/deprecated/old_arch/osmo4_wce}/smartphone 2003 (armv4)/release/install/build_installer.bat (93%) rename {bin => applications/deprecated/old_arch/osmo4_wce}/smartphone 2003 (armv4)/release/install/gpac.inf (100%) rename {bin => applications/deprecated/old_arch/osmo4_wce}/smartphone 2003 (armv4)/release/install/readme.txt (90%) create mode 100644 applications/deprecated/old_arch/osmo4_wx/Darwin.Info.plist create mode 100644 applications/deprecated/old_arch/osmo4_wx/Darwin.InfoPlist.strings create mode 100644 applications/deprecated/old_arch/osmo4_wx/Darwin.Osmo.icns create mode 100644 applications/deprecated/old_arch/osmo4_wx/Makefile create mode 100644 applications/deprecated/old_arch/osmo4_wx/Playlist.cpp create mode 100644 applications/deprecated/old_arch/osmo4_wx/Playlist.h create mode 100644 applications/deprecated/old_arch/osmo4_wx/fileprops.cpp create mode 100644 applications/deprecated/old_arch/osmo4_wx/fileprops.h create mode 100644 applications/deprecated/old_arch/osmo4_wx/menubtn.cpp create mode 100644 applications/deprecated/old_arch/osmo4_wx/menubtn.h create mode 100644 applications/deprecated/old_arch/osmo4_wx/osmo4.ico create mode 100644 applications/deprecated/old_arch/osmo4_wx/osmo4.xpm create mode 100644 applications/deprecated/old_arch/osmo4_wx/playlist.xpm create mode 100644 applications/deprecated/old_arch/osmo4_wx/resource.h create mode 100644 applications/deprecated/old_arch/osmo4_wx/toolbar.xpm create mode 100644 applications/deprecated/old_arch/osmo4_wx/wxGPACControl.cpp create mode 100644 applications/deprecated/old_arch/osmo4_wx/wxGPACControl.h create mode 100644 applications/deprecated/old_arch/osmo4_wx/wxOsmo4.cpp create mode 100644 applications/deprecated/old_arch/osmo4_wx/wxOsmo4.h create mode 100644 applications/deprecated/old_arch/osmo4_wx/wxOsmo4.rc create mode 100644 applications/deprecated/old_arch/osmophone/Osmo4.ico create mode 100644 applications/deprecated/old_arch/osmophone/main.cpp create mode 100644 applications/deprecated/old_arch/osmophone/newres.h create mode 100644 applications/deprecated/old_arch/osmophone/openfile.cpp create mode 100644 applications/deprecated/old_arch/osmophone/osmophone.rc create mode 100644 applications/deprecated/old_arch/osmophone/osmophone.vcxproj create mode 100644 applications/deprecated/old_arch/osmophone/resource.h create mode 100644 applications/deprecated/old_arch/osmozilla/Makefile create mode 100644 applications/deprecated/old_arch/osmozilla/nsIOsmozilla.h create mode 100644 applications/deprecated/old_arch/osmozilla/nsIOsmozilla.idl create mode 100644 applications/deprecated/old_arch/osmozilla/nsIOsmozilla.xpt_linux create mode 100644 applications/deprecated/old_arch/osmozilla/nsIOsmozilla.xpt_w32 create mode 100644 applications/deprecated/old_arch/osmozilla/osmo_npapi.cpp create mode 100644 applications/deprecated/old_arch/osmozilla/osmo_npapi.h create mode 100644 applications/deprecated/old_arch/osmozilla/osmozilla.cpp create mode 100644 applications/deprecated/old_arch/osmozilla/osmozilla.def create mode 100644 applications/deprecated/old_arch/osmozilla/osmozilla.h create mode 100644 applications/deprecated/old_arch/osmozilla/osmozilla.png create mode 100644 applications/deprecated/old_arch/osmozilla/osmozilla.rc create mode 100644 applications/deprecated/old_arch/osmozilla/osmozilla.vcxproj create mode 100644 applications/deprecated/old_arch/osmozilla/readme.txt create mode 100644 applications/deprecated/old_arch/osmozilla/resource.h create mode 100644 applications/generators/WebGLGen/Makefile create mode 100755 applications/generators/WebGLGen/WGLGen create mode 100644 applications/generators/WebGLGen/main.c create mode 100644 applications/generators/WebGLGen/webgl1.idl create mode 100644 applications/generators/WebGLGen/webgl2.idl create mode 100644 applications/gpac/Info.plist create mode 100644 applications/gpac/Makefile create mode 100644 applications/gpac/main.c create mode 100644 applications/mp4box/mp4box.h delete mode 100644 applications/mp4client/extract.c create mode 100644 applications/testapps/atscdmx/Makefile create mode 100644 applications/testapps/atscdmx/main.c rename applications/{ => testapps}/ts2hds/Makefile (92%) rename applications/{ => testapps}/ts2hds/f4m.c (100%) rename applications/{ => testapps}/ts2hds/f4v.c (89%) rename applications/{ => testapps}/ts2hds/main.c (98%) rename applications/{ => testapps}/ts2hds/ts2hds.h (100%) rename applications/{ => testapps}/udptsseg/Makefile (93%) rename applications/{ => testapps}/udptsseg/main.c (99%) rename applications/{ => testapps}/udptsseg/udptsseg.dsp (100%) rename applications/{ => testapps}/udptsseg/udptsseg.vcproj (100%) delete mode 100644 doc/CODING_STYLE delete mode 100644 doc/INSTALL.gcc delete mode 100644 doc/INSTALL.gpe delete mode 100644 doc/INSTALL.symbian delete mode 100644 doc/INSTALL.w32 delete mode 100644 doc/INSTALL.wCE delete mode 100644 doc/configuration.html delete mode 100644 doc/gpac.mp4 delete mode 100644 doc/man/dashcast.1 delete mode 100644 doc/man/gpac.1 delete mode 100644 doc/man/mp42avi.1 delete mode 100644 doc/man/mp42ts.1 delete mode 100644 doc/man/mp4box.1 delete mode 100644 doc/man/mp4client.1 delete mode 100644 gui/extensions/player/init.js delete mode 100644 gui/gui_old.bt delete mode 100644 gui/gui_old.js create mode 100644 include/gpac/00_doxy.h create mode 100644 include/gpac/Remotery.h create mode 100644 include/gpac/atsc.h create mode 100644 include/gpac/crypt_tools.h delete mode 100644 include/gpac/esi.h create mode 100644 include/gpac/evg.h delete mode 100644 include/gpac/filestreamer.h create mode 100644 include/gpac/filters.h delete mode 100644 include/gpac/internal/mpd.h delete mode 100644 include/gpac/internal/smjs_api.h delete mode 100644 include/gpac/internal/terminal_dev.h delete mode 100644 include/gpac/ismacryp.h create mode 100644 include/gpac/main.h delete mode 100644 include/gpac/map.h rename include/gpac/modules/{term_ext.h => compositor_ext.h} (69%) delete mode 100644 include/gpac/modules/js_usr.h delete mode 100644 include/gpac/modules/raster2d.h delete mode 100644 include/gpac/modules/service.h create mode 100644 include/gpac/mpd.h delete mode 100644 include/gpac/nodes_xbl.h delete mode 100644 include/gpac/ringbuffer.h delete mode 100644 include/gpac/unicode.h delete mode 100644 modules/aac_in/Makefile delete mode 100644 modules/aac_in/aac_in.c delete mode 100644 modules/aac_in/faad_dec.c delete mode 100644 modules/ac3_in/Makefile delete mode 100644 modules/ac3_in/ac3_in.c delete mode 100644 modules/ac3_in/liba52_dec.c delete mode 100644 modules/amr_dec/Makefile delete mode 100644 modules/amr_dec/amr_dec.c delete mode 100644 modules/amr_dec/amr_in.c delete mode 100644 modules/amr_dec/amr_nb/typedefs.h delete mode 100644 modules/amr_dec/amr_nb_api.h delete mode 100644 modules/amr_float_dec/Makefile delete mode 100644 modules/amr_float_dec/amr_api.h delete mode 100644 modules/amr_float_dec/amr_float_dec.c delete mode 100644 modules/bifs_dec/Makefile delete mode 100644 modules/bifs_dec/bifs_dec.c delete mode 100644 modules/ctx_load/Makefile create mode 100644 modules/dec_openhevc/Makefile create mode 100644 modules/dektec_out/dektec_video.h create mode 100644 modules/dektec_out/dektec_video_decl.c create mode 100644 modules/dektec_out/dektec_video_old.cpp create mode 100644 modules/dektec_out/out_dektec.vcxproj rename modules/{ => deprecated}/epoc_hw/epoc_aout.cpp (100%) rename modules/{ => deprecated}/epoc_hw/epoc_codec.cpp (100%) rename modules/{ => deprecated}/epoc_hw/epoc_vout.cpp (98%) rename modules/{ => deprecated/old_arch}/audio_filter/Makefile (91%) rename modules/{ => deprecated/old_arch}/audio_filter/audio_filter.c (100%) rename modules/{ => deprecated/old_arch}/avcap/Makefile (92%) rename modules/{ => deprecated/old_arch}/avcap/avcap.cpp (98%) rename modules/{ => deprecated/old_arch}/ffmpeg_in/Makefile (82%) rename modules/{ => deprecated/old_arch}/ffmpeg_in/ffmpeg_decode.c (95%) rename modules/{ => deprecated/old_arch}/ffmpeg_in/ffmpeg_demux.c (98%) rename modules/{ => deprecated/old_arch}/ffmpeg_in/ffmpeg_in.h (95%) rename modules/{ => deprecated/old_arch}/ffmpeg_in/ffmpeg_load.c (100%) rename modules/{ => deprecated/old_arch}/freenect/Makefile (78%) rename modules/{ => deprecated/old_arch}/freenect/freenect.c (100%) rename modules/{ => deprecated/old_arch}/gapi/gapi.cpp (99%) rename modules/{ => deprecated/old_arch}/gapi/gapi.h (100%) rename modules/{ => deprecated/old_arch}/gdip_raster/gdip_font.cpp (97%) rename modules/{ => deprecated/old_arch}/gdip_raster/gdip_grad.cpp (100%) rename modules/{ => deprecated/old_arch}/gdip_raster/gdip_priv.h (100%) rename modules/{ => deprecated/old_arch}/gdip_raster/gdip_rend.cpp (99%) rename modules/{ => deprecated/old_arch}/gdip_raster/gdip_texture.cpp (96%) rename modules/{ => deprecated/old_arch}/hyb_in/Makefile (93%) rename modules/{ => deprecated/old_arch}/hyb_in/fm_fake_pull.c (100%) rename modules/{ => deprecated/old_arch}/hyb_in/fm_fake_push.c (100%) rename modules/{ => deprecated/old_arch}/hyb_in/fm_mmbtools.c (100%) rename modules/{ => deprecated/old_arch}/hyb_in/hyb_in.c (99%) rename modules/{ => deprecated/old_arch}/hyb_in/hyb_in.h (100%) rename modules/{ => deprecated/old_arch}/libplayer/Makefile (91%) rename modules/{ => deprecated/old_arch}/libplayer/libplayer.c (100%) rename modules/{ => deprecated/old_arch}/mse_in/Makefile (84%) rename modules/{ => deprecated/old_arch}/mse_in/mse_in.c (99%) create mode 100644 modules/deprecated/old_arch/netctrl/Makefile create mode 100644 modules/deprecated/old_arch/netctrl/netctrl.c rename modules/{ => deprecated/old_arch}/opencv_is/Makefile (92%) rename modules/{ => deprecated/old_arch}/opencv_is/demo-sensor.bt (100%) rename modules/{ => deprecated/old_arch}/opencv_is/haarcascade_frontalface_default.xml (100%) rename modules/{ => deprecated/old_arch}/opencv_is/opencv_is.c (100%) rename modules/{ => deprecated/old_arch}/osd/Makefile (92%) rename modules/{ => deprecated/old_arch}/osd/osd.c (98%) rename modules/{ => deprecated/old_arch}/platinum/GPACFileMediaServer.cpp (100%) rename modules/{ => deprecated/old_arch}/platinum/GPACFileMediaServer.h (100%) rename modules/{ => deprecated/old_arch}/platinum/GPACMediaController.cpp (100%) rename modules/{ => deprecated/old_arch}/platinum/GPACMediaController.h (100%) rename modules/{ => deprecated/old_arch}/platinum/GPACMediaRenderer.cpp (99%) rename modules/{ => deprecated/old_arch}/platinum/GPACMediaRenderer.h (100%) rename modules/{ => deprecated/old_arch}/platinum/GPACPlatinum.cpp (96%) rename modules/{ => deprecated/old_arch}/platinum/GPACPlatinum.h (100%) rename modules/{ => deprecated/old_arch}/platinum/GenericDevice.cpp (100%) rename modules/{ => deprecated/old_arch}/platinum/GenericDevice.h (100%) rename modules/{ => deprecated/old_arch}/platinum/Makefile (78%) create mode 100644 modules/deprecated/old_arch/psvr/Makefile create mode 100644 modules/deprecated/old_arch/psvr/psvr.c rename modules/{ => deprecated/old_arch}/rvc_dec/Makefile (91%) rename modules/{ => deprecated/old_arch}/rvc_dec/rvc_dec.c (98%) rename modules/{ => deprecated/old_arch}/ui_rec/Makefile (92%) rename modules/{ => deprecated/old_arch}/ui_rec/readme.txt (100%) rename modules/{ => deprecated/old_arch}/ui_rec/ui_rec.c (96%) rename modules/{ => deprecated/old_arch}/widgetman/Makefile (78%) rename modules/{ => deprecated/old_arch}/widgetman/unzip.c (100%) rename modules/{ => deprecated/old_arch}/widgetman/unzip.h (100%) rename modules/{ => deprecated/old_arch}/widgetman/wgt_load.c (98%) rename modules/{ => deprecated/old_arch}/widgetman/wgt_load_base.js (100%) rename modules/{ => deprecated/old_arch}/widgetman/widget.c (98%) rename modules/{ => deprecated/old_arch}/widgetman/widgetman.c (98%) rename modules/{ => deprecated/old_arch}/widgetman/widgetman.h (100%) rename modules/{ => deprecated/old_arch}/wiiis/Makefile (93%) rename modules/{ => deprecated/old_arch}/wiiis/test_wii.bt (100%) rename modules/{ => deprecated/old_arch}/wiiis/wiiis.c (95%) rename modules/{ => deprecated}/oss_audio/Makefile (88%) rename modules/{ => deprecated}/oss_audio/oss.c (94%) delete mode 100644 modules/dummy_in/Makefile delete mode 100644 modules/dummy_in/dummy_in.c delete mode 100644 modules/dx_hw/copy_pixels.c rename modules/{mediacodec_dec/mediacodec_dec.h => filter_export.cpp} (66%) delete mode 100644 modules/gpac_js/Makefile delete mode 100644 modules/gpac_js/gpac_js.c delete mode 100644 modules/img_in/Makefile delete mode 100644 modules/img_in/bmp_dec.c delete mode 100644 modules/img_in/img_dec.c delete mode 100644 modules/img_in/img_in.c delete mode 100644 modules/img_in/img_in.h delete mode 100644 modules/img_in/jp2_dec.c delete mode 100644 modules/img_in/jpeg_dec.c delete mode 100644 modules/img_in/png_dec.c delete mode 100644 modules/ismacryp/Makefile delete mode 100644 modules/ismacryp/isma_ea.c delete mode 100644 modules/isom_in/Makefile delete mode 100644 modules/isom_in/isom_cache.c delete mode 100644 modules/isom_in/isom_in.h delete mode 100644 modules/isom_in/load.c delete mode 100644 modules/isom_in/read.c delete mode 100644 modules/isom_in/read_ch.c delete mode 100644 modules/laser_dec/Makefile delete mode 100644 modules/laser_dec/laser_dec.c delete mode 100755 modules/mediacodec_dec/Makefile delete mode 100644 modules/mediacodec_dec/mediacodec_dec.c delete mode 100644 modules/mediacodec_dec/mediacodec_dec_jni.c delete mode 100644 modules/mp3_in/Makefile delete mode 100644 modules/mp3_in/mad_dec.c delete mode 100644 modules/mp3_in/mp3_in.c delete mode 100644 modules/mpd_in/Makefile delete mode 100644 modules/mpd_in/mpd_in.c delete mode 100644 modules/mpegts_in/Makefile delete mode 100644 modules/mpegts_in/mpegts_in.c delete mode 100644 modules/odf_dec/Makefile delete mode 100644 modules/odf_dec/odf_dec.c delete mode 100644 modules/ogg/Makefile delete mode 100644 modules/ogg/ogg_in.c delete mode 100644 modules/ogg/ogg_in.h delete mode 100644 modules/ogg/ogg_load.c delete mode 100644 modules/ogg/theora_dec.c delete mode 100644 modules/ogg/vorbis_dec.c delete mode 100644 modules/openhevc_dec/Makefile delete mode 100644 modules/openhevc_dec/openhevc_dec.c delete mode 100644 modules/opensvc_dec/Makefile delete mode 100644 modules/opensvc_dec/opensvc_dec.c delete mode 100644 modules/raw_out/Makefile delete mode 100644 modules/raw_out/raw_video.c delete mode 100644 modules/redirect_av/Makefile delete mode 100644 modules/redirect_av/ffmpeg_ts_muxer.c delete mode 100644 modules/redirect_av/gpac_ts_muxer.c delete mode 100644 modules/redirect_av/redirect_av.c delete mode 100644 modules/redirect_av/ts_muxer.h delete mode 100644 modules/rtp_in/Makefile delete mode 100644 modules/rtp_in/rtp_in.c delete mode 100644 modules/rtp_in/rtp_signaling.c delete mode 100644 modules/rtp_in/rtp_stream.c delete mode 100644 modules/rtp_in/sdp_fetch.c delete mode 100644 modules/rtp_in/sdp_load.c delete mode 100644 modules/saf_in/Makefile delete mode 100644 modules/saf_in/saf_in.c delete mode 100644 modules/soft_raster/Makefile delete mode 100644 modules/soft_raster/rast_soft.h delete mode 100644 modules/soft_raster/raster_565.c delete mode 100644 modules/soft_raster/raster_argb.c delete mode 100644 modules/soft_raster/raster_load.c delete mode 100644 modules/soft_raster/raster_rgb.c delete mode 100644 modules/soft_raster/stencil.c delete mode 100644 modules/soft_raster/surface.c delete mode 100644 modules/svg_in/Makefile delete mode 100644 modules/svg_in/svg_in.c delete mode 100644 modules/timedtext/Makefile delete mode 100644 modules/timedtext/timedtext_in.c delete mode 100644 modules/vtb_decode/Makefile delete mode 100644 modules/vtb_decode/vtb_decode.c delete mode 100644 modules/vtt_in/Makefile delete mode 100644 modules/vtt_in/vtt_dec.c delete mode 100644 modules/vtt_in/vtt_in.c delete mode 100644 modules/xvid_dec/Makefile delete mode 100644 modules/xvid_dec/xvid_dec.c delete mode 100644 modules/xvid_dec/xvid_dec_wce.cpp create mode 100644 packagers/osx/GPAC.app/Contents/Info.plist create mode 100644 packagers/osx/GPAC.app/Contents/PkgInfo create mode 100644 packagers/osx/GPAC.app/Contents/Resources/osmo.icns create mode 100644 packagers/osx/GPAC.app/Contents/Resources/osmo_audio.icns create mode 100644 packagers/osx/GPAC.app/Contents/Resources/osmo_generic.icns create mode 100644 packagers/osx/GPAC.app/Contents/Resources/osmo_model.icns create mode 100644 packagers/osx/GPAC.app/Contents/Resources/osmo_subs.icns create mode 100644 packagers/osx/GPAC.app/Contents/Resources/osmo_video.icns create mode 100644 packagers/osx/GPAC.app/Contents/Resources/osmo_widget.icns create mode 100644 packagers/osx/SLA.r create mode 100644 packagers/osx/distribution.xml create mode 100755 packagers/osx/res/background.png create mode 100644 packagers/osx/res/preamble.txt create mode 100755 packagers/osx/scripts/postinstall create mode 100644 share/default.cfg rename {gui => share/deprecated}/iphone_wm_gui.js (97%) rename {gui => share/deprecated}/iphone_wm_gui.svg (99%) create mode 100644 share/deprecated/mpegu-core.js rename {gui => share/deprecated}/mpegu-wm.bt (100%) create mode 100644 share/deprecated/mpegu-wm.js rename {gui => share/deprecated}/mpegu-wm.xmt (91%) rename {gui => share/deprecated}/tv_wm_gui.js (99%) rename {gui => share/deprecated}/tv_wm_gui.svg (100%) create mode 100644 share/doc/CODING_STYLE rename {doc => share/doc}/GPAC UPnP.doc (100%) rename {doc => share/doc}/ISO 639-2 codes.txt (100%) rename {doc => share/doc}/SceneGenerators (100%) create mode 100644 share/doc/configuration.html rename {doc => share/doc}/doxyfile (99%) rename {doc => share/doc}/doxyfile-full (99%) create mode 100644 share/doc/idl/core.idl create mode 100644 share/doc/idl/evg.idl create mode 100644 share/doc/idl/filtersession.idl create mode 100644 share/doc/idl/jsf.idl create mode 100644 share/doc/idl/scenejs.idl create mode 100644 share/doc/idl/storage.idl create mode 100644 share/doc/idl/webgl.idl create mode 100644 share/doc/idl/xhr.idl rename {doc => share/doc}/ipmpx_syntax.bt (98%) create mode 100644 share/doc/man/gpac-filters.1 create mode 100644 share/doc/man/gpac.1 create mode 100644 share/doc/man/mp4box.1 create mode 100644 share/doc/man/mp4client.1 rename {doc => share/doc}/osmo4.ico (100%) rename {gui => share/gui}/extensions/H2B2VS/H2B2VS.png (100%) rename {gui => share/gui}/extensions/H2B2VS/h2b2vs.js (95%) rename {gui => share/gui}/extensions/H2B2VS/init.js (80%) rename {gui => share/gui}/extensions/H2B2VS/logo_hd.png (100%) rename {gui => share/gui}/extensions/H2B2VS/logo_uhd.png (100%) create mode 100644 share/gui/extensions/about/info.js create mode 100644 share/gui/extensions/about/info.svg create mode 100644 share/gui/extensions/about/init.js rename {gui => share/gui}/extensions/bifs_tests/applications-other.svg (100%) rename {gui => share/gui}/extensions/bifs_tests/bifs_tests.js (100%) rename {gui => share/gui}/extensions/bifs_tests/init.js (100%) rename {gui => share/gui}/extensions/player/applications-multimedia.svg (100%) rename {gui => share/gui}/extensions/player/fileopen.js (94%) create mode 100644 share/gui/extensions/player/init.js rename {gui => share/gui}/extensions/player/player.js (89%) rename {gui => share/gui}/extensions/player/playlist.js (96%) rename {gui => share/gui}/extensions/player/stats.js (92%) rename {gui => share/gui}/extensions/showroom/init.js (100%) rename {gui => share/gui}/extensions/showroom/osmo.bt (100%) rename {gui => share/gui}/extensions/showroom/showroom.js (100%) rename {gui => share/gui}/extensions/widget_manager/applications-system.svg (100%) rename {gui => share/gui}/extensions/widget_manager/init.js (98%) rename {gui => share/gui}/gui.bt (100%) rename {gui => share/gui}/gui.js (68%) rename {gui => share/gui}/gwlib.js (97%) rename {gui => share/gui}/icons/add.svg (100%) rename {gui => share/gui}/icons/app.svg (100%) rename {gui => share/gui}/icons/audio.svg (100%) rename {gui => share/gui}/icons/audio_full.svg (100%) rename {gui => share/gui}/icons/audio_mute.svg (100%) rename {gui => share/gui}/icons/check.svg (100%) rename {gui => share/gui}/icons/close.svg (100%) rename {gui => share/gui}/icons/compass.svg (100%) rename {gui => share/gui}/icons/cross.svg (100%) rename {gui => share/gui}/icons/down.svg (100%) rename {gui => share/gui}/icons/expand.svg (100%) rename {gui => share/gui}/icons/file.svg (100%) rename {gui => share/gui}/icons/film.svg (100%) rename {gui => share/gui}/icons/folder.svg (100%) rename {gui => share/gui}/icons/harddrive.svg (100%) rename {gui => share/gui}/icons/heart.svg (100%) rename {gui => share/gui}/icons/home.svg (100%) rename {gui => share/gui}/icons/image.svg (100%) rename {gui => share/gui}/icons/info.svg (100%) rename {gui => share/gui}/icons/laptop.svg (100%) rename {gui => share/gui}/icons/left.svg (100%) rename {gui => share/gui}/icons/list.svg (100%) rename {gui => share/gui}/icons/live.svg (100%) rename {gui => share/gui}/icons/media_next.svg (100%) rename {gui => share/gui}/icons/media_prev.svg (100%) rename {gui => share/gui}/icons/monitor.svg (100%) rename {gui => share/gui}/icons/more.svg (100%) rename {gui => share/gui}/icons/musical.svg (100%) rename {gui => share/gui}/icons/navigation.svg (100%) rename {gui => share/gui}/icons/network.svg (100%) rename {gui => share/gui}/icons/next.svg (100%) rename {gui => share/gui}/icons/osmo.svg (100%) rename {gui => share/gui}/icons/overflowing.svg (100%) rename {gui => share/gui}/icons/pause.svg (100%) rename {gui => share/gui}/icons/pl_next.svg (100%) rename {gui => share/gui}/icons/pl_prev.svg (100%) rename {gui => share/gui}/icons/play.svg (100%) rename {gui => share/gui}/icons/play_loop.svg (100%) rename {gui => share/gui}/icons/play_shuffle.svg (100%) rename {gui => share/gui}/icons/play_single.svg (100%) rename {gui => share/gui}/icons/power.svg (100%) rename {gui => share/gui}/icons/previous.svg (100%) rename {gui => share/gui}/icons/remove.svg (100%) rename {gui => share/gui}/icons/resize.svg (100%) rename {gui => share/gui}/icons/rewind.svg (100%) rename {gui => share/gui}/icons/right.svg (100%) rename {gui => share/gui}/icons/seek_forward.svg (100%) rename {gui => share/gui}/icons/shrink.svg (100%) rename {gui => share/gui}/icons/sort.svg (100%) rename {gui => share/gui}/icons/speed.svg (100%) rename {gui => share/gui}/icons/star.svg (100%) rename {gui => share/gui}/icons/stop.svg (100%) rename {gui => share/gui}/icons/stop2.svg (100%) rename {gui => share/gui}/icons/trash.svg (100%) rename {gui => share/gui}/icons/tray.svg (100%) rename {gui => share/gui}/icons/tv.svg (100%) rename {gui => share/gui}/icons/up.svg (100%) rename {gui => share/gui}/icons/world.svg (100%) create mode 100644 share/lang/fr.txt create mode 100644 share/res/gpac.mp4 rename tests/media/auxiliary_files/logo.png => share/res/gpac.png (100%) create mode 100644 share/res/gpac_cfg_test.mp4 create mode 100644 share/scripts/ttml-renderer.js rename {gui => share/scripts}/webvtt-renderer.js (95%) rename {shaders => share/shaders}/fragment.glsl (82%) rename {shaders => share/shaders}/vertex.glsl (100%) create mode 100644 share/vis/Code/Console.js create mode 100644 share/vis/Code/DataViewReader.js create mode 100644 share/vis/Code/PixelTimeRange.js create mode 100644 share/vis/Code/Remotery.js create mode 100644 share/vis/Code/SampleWindow.js create mode 100644 share/vis/Code/ThreadFrame.js create mode 100644 share/vis/Code/TimelineRow.js create mode 100644 share/vis/Code/TimelineWindow.js create mode 100644 share/vis/Code/TitleWindow.js create mode 100644 share/vis/Code/WebSocketConnection.js create mode 100644 share/vis/Styles/Remotery.css create mode 100644 share/vis/extern/BrowserLib/Core/Code/Animation.js create mode 100644 share/vis/extern/BrowserLib/Core/Code/Bind.js create mode 100644 share/vis/extern/BrowserLib/Core/Code/Convert.js create mode 100644 share/vis/extern/BrowserLib/Core/Code/Core.js create mode 100644 share/vis/extern/BrowserLib/Core/Code/DOM.js create mode 100644 share/vis/extern/BrowserLib/Core/Code/Keyboard.js create mode 100644 share/vis/extern/BrowserLib/Core/Code/LocalStore.js create mode 100644 share/vis/extern/BrowserLib/Core/Code/Mouse.js create mode 100644 share/vis/extern/BrowserLib/Core/Code/MurmurHash3.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/Button.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/ComboBox.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/Container.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/EditBox.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/Grid.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/Label.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/Treeview.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/TreeviewItem.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/Window.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Code/WindowManager.js create mode 100644 share/vis/extern/BrowserLib/WindowManager/Styles/WindowManager.css create mode 100644 share/vis/index.html create mode 100644 src/compositor/clock.c rename src/{terminal => compositor}/media_object.c (51%) rename src/{terminal => compositor}/mpeg4_inline.c (82%) rename src/{terminal/input_sensor.c => compositor/mpeg4_inputsensor.c} (62%) rename src/{terminal/media_control.c => compositor/mpeg4_mediacontrol.c} (93%) rename src/{terminal/media_sensor.c => compositor/mpeg4_mediasensor.c} (79%) create mode 100644 src/compositor/object_manager.c rename src/{terminal => compositor}/scene.c (69%) rename src/{terminal/term_node_init.c => compositor/scene_node_init.c} (82%) create mode 100644 src/compositor/scene_ns.c rename src/{terminal => compositor}/svg_external.c (74%) create mode 100644 src/crypto/g_crypt.c create mode 100644 src/crypto/g_crypt_openssl.c create mode 100644 src/crypto/g_crypt_tinyaes.c create mode 100644 src/crypto/tiny_aes.c create mode 100644 src/crypto/tiny_aes.h delete mode 100644 src/dir_info rename {modules/soft_raster => src/evg}/ftgrays.c (76%) create mode 100644 src/evg/rast_soft.h create mode 100644 src/evg/raster3d.c create mode 100644 src/evg/raster_565.c create mode 100644 src/evg/raster_argb.c create mode 100644 src/evg/raster_rgb.c create mode 100644 src/evg/raster_yuv.c create mode 100644 src/evg/stencil.c create mode 100644 src/evg/surface.c create mode 100644 src/filter_core/filter.c create mode 100644 src/filter_core/filter_pck.c create mode 100644 src/filter_core/filter_pid.c create mode 100644 src/filter_core/filter_props.c create mode 100644 src/filter_core/filter_queue.c create mode 100644 src/filter_core/filter_register.c create mode 100644 src/filter_core/filter_session.c create mode 100644 src/filter_core/filter_session.h create mode 100644 src/filter_core/filter_session_js.c create mode 100644 src/filters/base_filter_example.c create mode 100644 src/filters/bsrw.c create mode 100644 src/filters/compose.c create mode 100644 src/filters/dasher.c create mode 100644 src/filters/dec_ac52.c create mode 100644 src/filters/dec_bifs.c create mode 100644 src/filters/dec_faad.c create mode 100644 src/filters/dec_img.c create mode 100644 src/filters/dec_j2k.c create mode 100644 src/filters/dec_laser.c create mode 100644 src/filters/dec_mad.c create mode 100644 src/filters/dec_mediacodec.c create mode 100644 src/filters/dec_mediacodec.h create mode 100644 src/filters/dec_mediacodec_jni.c create mode 100644 src/filters/dec_nvdec.c create mode 100644 src/filters/dec_nvdec_sdk.c create mode 100644 src/filters/dec_nvdec_sdk.h create mode 100644 src/filters/dec_odf.c create mode 100644 src/filters/dec_openhevc.c create mode 100644 src/filters/dec_opensvc.c create mode 100644 src/filters/dec_theora.c create mode 100644 src/filters/dec_ttml.c rename modules/timedtext/timedtext_dec.c => src/filters/dec_ttxt.c (51%) create mode 100644 src/filters/dec_vorbis.c create mode 100644 src/filters/dec_vtb.c rename modules/vtb_decode/glcontext.m => src/filters/dec_vtb_glctx.m (100%) create mode 100644 src/filters/dec_webvtt.c create mode 100644 src/filters/dec_xvid.c create mode 100644 src/filters/decrypt_cenc_isma.c create mode 100644 src/filters/dmx_avi.c create mode 100644 src/filters/dmx_dash.c create mode 100644 src/filters/dmx_gsf.c create mode 100644 src/filters/dmx_m2ts.c create mode 100644 src/filters/dmx_mpegps.c create mode 100644 src/filters/dmx_nhml.c create mode 100644 src/filters/dmx_nhnt.c create mode 100644 src/filters/dmx_ogg.c create mode 100644 src/filters/dmx_saf.c create mode 100644 src/filters/dmx_vobsub.c create mode 100644 src/filters/enc_jpg.c create mode 100644 src/filters/enc_png.c create mode 100644 src/filters/encrypt_cenc_isma.c create mode 100644 src/filters/ff_avf.c create mode 100644 src/filters/ff_common.c create mode 100644 src/filters/ff_common.h create mode 100644 src/filters/ff_dec.c create mode 100644 src/filters/ff_dmx.c create mode 100644 src/filters/ff_enc.c create mode 100644 src/filters/ff_mx.c create mode 100644 src/filters/ff_rescale.c create mode 100644 src/filters/filelist.c create mode 100644 src/filters/hevcmerge.c create mode 100644 src/filters/hevcsplit.c create mode 100644 src/filters/in_atsc.c create mode 100644 src/filters/in_dvb4linux.c create mode 100644 src/filters/in_file.c create mode 100644 src/filters/in_http.c create mode 100644 src/filters/in_pipe.c create mode 100644 src/filters/in_rtp.c rename modules/rtp_in/rtp_in.h => src/filters/in_rtp.h (52%) rename modules/rtp_in/rtp_session.c => src/filters/in_rtp_rtsp.c (62%) create mode 100644 src/filters/in_rtp_sdp.c create mode 100644 src/filters/in_rtp_signaling.c create mode 100644 src/filters/in_rtp_stream.c create mode 100644 src/filters/in_sock.c create mode 100644 src/filters/inspect.c create mode 100644 src/filters/isoffin.h create mode 100644 src/filters/isoffin_load.c create mode 100644 src/filters/isoffin_read.c create mode 100644 src/filters/isoffin_read_ch.c create mode 100644 src/filters/jsfilter.c rename modules/ctx_load/ctx_load.c => src/filters/load_bt_xmt.c (57%) create mode 100644 src/filters/load_svg.c create mode 100644 src/filters/load_text.c create mode 100644 src/filters/mux_avi.c create mode 100644 src/filters/mux_gsf.c create mode 100644 src/filters/mux_isom.c create mode 100644 src/filters/mux_ts.c create mode 100644 src/filters/out_audio.c create mode 100644 src/filters/out_file.c create mode 100644 src/filters/out_http.c create mode 100644 src/filters/out_pipe.c create mode 100644 src/filters/out_rtp.c create mode 100644 src/filters/out_rtp.h create mode 100644 src/filters/out_rtsp.c create mode 100644 src/filters/out_sock.c create mode 100644 src/filters/out_video.c create mode 100644 src/filters/reframe_ac3.c create mode 100644 src/filters/reframe_adts.c create mode 100644 src/filters/reframe_amr.c create mode 100644 src/filters/reframe_av1.c create mode 100644 src/filters/reframe_flac.c create mode 100644 src/filters/reframe_h263.c create mode 100644 src/filters/reframe_img.c create mode 100644 src/filters/reframe_latm.c create mode 100644 src/filters/reframe_mp3.c create mode 100644 src/filters/reframe_mpgvid.c create mode 100644 src/filters/reframe_nalu.c create mode 100644 src/filters/reframe_prores.c create mode 100644 src/filters/reframe_qcp.c create mode 100644 src/filters/reframe_rawpcm.c create mode 100644 src/filters/reframe_rawvid.c create mode 100644 src/filters/reframer.c create mode 100644 src/filters/resample_audio.c create mode 100644 src/filters/rewind.c create mode 100644 src/filters/rewrite_adts.c create mode 100644 src/filters/rewrite_mp4v.c create mode 100644 src/filters/rewrite_nalu.c create mode 100644 src/filters/rewrite_obu.c create mode 100644 src/filters/tileagg.c create mode 100644 src/filters/tssplit.c create mode 100644 src/filters/unit_test_filter.c create mode 100644 src/filters/vcrop.c create mode 100644 src/filters/vflip.c create mode 100644 src/filters/write_generic.c create mode 100644 src/filters/write_nhml.c create mode 100644 src/filters/write_nhnt.c create mode 100644 src/filters/write_qcp.c create mode 100644 src/filters/write_vtt.c create mode 100644 src/jsmods/WebGLRenderingContextBase.c create mode 100644 src/jsmods/core.c create mode 100644 src/jsmods/evg.c create mode 100644 src/jsmods/scene_js.c create mode 100644 src/jsmods/storage.c create mode 100644 src/jsmods/webgl.c create mode 100644 src/jsmods/webgl.h create mode 100644 src/jsmods/xhr.c delete mode 100644 src/mcrypt/cbc.c delete mode 100644 src/mcrypt/cfb.c delete mode 100644 src/mcrypt/ctr.c delete mode 100644 src/mcrypt/des.c delete mode 100644 src/mcrypt/ecb.c delete mode 100644 src/mcrypt/g_crypt.c delete mode 100644 src/mcrypt/ncfb.c delete mode 100644 src/mcrypt/nofb.c delete mode 100644 src/mcrypt/ofb.c delete mode 100644 src/mcrypt/rijndael-128.c delete mode 100644 src/mcrypt/rijndael-192.c delete mode 100644 src/mcrypt/rijndael-256.c delete mode 100644 src/mcrypt/stream.c delete mode 100644 src/mcrypt/tripledes.c create mode 100644 src/media_tools/atsc_dmx.c create mode 100644 src/media_tools/crypt_tools.c delete mode 100644 src/media_tools/filestreamer.c delete mode 100644 src/media_tools/ismacryp.c delete mode 100644 src/media_tools/text_import.c create mode 100644 src/quickjs/GPAC_README.md create mode 100644 src/quickjs/cutils.c create mode 100644 src/quickjs/cutils.h create mode 100644 src/quickjs/libbf.c create mode 100644 src/quickjs/libbf.h create mode 100644 src/quickjs/libregexp-opcode.h create mode 100644 src/quickjs/libregexp.c create mode 100644 src/quickjs/libregexp.h create mode 100644 src/quickjs/libunicode-table.h create mode 100644 src/quickjs/libunicode.c create mode 100644 src/quickjs/libunicode.h create mode 100644 src/quickjs/list.h create mode 100644 src/quickjs/quickjs-atom.h create mode 100644 src/quickjs/quickjs-opcode.h create mode 100644 src/quickjs/quickjs.c create mode 100644 src/quickjs/quickjs.h create mode 100644 src/scenegraph/dom_js.c delete mode 100644 src/scenegraph/dom_smjs.c rename src/scenegraph/{html5_media_smjs.c => html5_media_js.c} (97%) rename src/scenegraph/{html5_mse_smjs.c => html5_mse_js.c} (99%) create mode 100644 src/scenegraph/qjs_common.h create mode 100644 src/scenegraph/svg_js.c delete mode 100644 src/scenegraph/svg_smjs.c create mode 100644 src/scenegraph/vrml_js.c delete mode 100644 src/scenegraph/vrml_smjs.c delete mode 100644 src/scenegraph/webvtt_smjs.c delete mode 100644 src/scenegraph/xbl_process.c delete mode 100644 src/terminal/channel.c delete mode 100644 src/terminal/clock.c delete mode 100644 src/terminal/decoder.c delete mode 100644 src/terminal/input_sensor.h delete mode 100644 src/terminal/media_control.h delete mode 100644 src/terminal/media_manager.c delete mode 100644 src/terminal/media_memory.c delete mode 100644 src/terminal/media_memory.h delete mode 100644 src/terminal/network_service.c delete mode 100644 src/terminal/object_browser.c delete mode 100644 src/terminal/object_manager.c create mode 100644 src/utils/Remotery.c create mode 100644 src/utils/constants.c create mode 100644 src/utils/gltools.c rename src/utils/{gzio.cpp => gzio.c} (83%) delete mode 100644 src/utils/map.c delete mode 100644 src/utils/ringbuffer.c delete mode 100644 tests/README.MD delete mode 100755 tests/ghp_deploy.sh delete mode 100644 tests/index.html delete mode 100755 tests/make_tests.sh delete mode 100644 tests/media/auxiliary_files/count_arabic.mp3 delete mode 100644 tests/media/auxiliary_files/count_english.mp3 delete mode 100644 tests/media/auxiliary_files/count_french.mp3 delete mode 100644 tests/media/auxiliary_files/count_german.mp3 delete mode 100644 tests/media/auxiliary_files/count_italian.mp3 delete mode 100644 tests/media/auxiliary_files/count_spanish.mp3 delete mode 100644 tests/media/auxiliary_files/count_video.cmp delete mode 100644 tests/media/auxiliary_files/counter.hvc delete mode 100644 tests/media/auxiliary_files/enst_audio.aac delete mode 100644 tests/media/auxiliary_files/enst_video.h264 delete mode 100644 tests/media/auxiliary_files/index2batch.xslt delete mode 100644 tests/media/auxiliary_files/index2html.xslt delete mode 100644 tests/media/auxiliary_files/index2sh.xslt delete mode 100644 tests/media/auxiliary_files/logo.bt delete mode 100644 tests/media/auxiliary_files/logo.jpg delete mode 100644 tests/media/auxiliary_files/nefertiti.wrl delete mode 100644 tests/media/auxiliary_files/sky.jpg delete mode 100644 tests/media/auxiliary_files/subs.ismt delete mode 100644 tests/media/auxiliary_files/subtitle.srt delete mode 100644 tests/media/auxiliary_files/subtitle_fr.srt delete mode 100644 tests/media/auxiliary_files/svg2html.xslt delete mode 100644 tests/media/auxiliary_files/x3d2html.xslt delete mode 100644 tests/media/auxiliary_files/xmt2html.xslt delete mode 100644 tests/media/bifs/bifs-2D-background-background2D-bind.bt delete mode 100644 tests/media/bifs/bifs-2D-background-background2D-image.bt delete mode 100644 tests/media/bifs/bifs-2D-background-background2D-layer2D.bt delete mode 100644 tests/media/bifs/bifs-2D-background-background2D-movie.bt delete mode 100644 tests/media/bifs/bifs-2D-background-background2D-url-change.bt delete mode 100644 tests/media/bifs/bifs-2D-interactivity-discsensor.bt delete mode 100644 tests/media/bifs/bifs-2D-interactivity-htk-sensor.bt delete mode 100644 tests/media/bifs/bifs-2D-interactivity-keysensor.bt delete mode 100644 tests/media/bifs/bifs-2D-interactivity-mousesensor.bt delete mode 100644 tests/media/bifs/bifs-2D-interactivity-nested-sensors.bt delete mode 100644 tests/media/bifs/bifs-2D-interactivity-planesensor2D.bt delete mode 100644 tests/media/bifs/bifs-2D-interactivity-proximitysensor2D.bt delete mode 100644 tests/media/bifs/bifs-2D-interactivity-stringsensor.bt delete mode 100644 tests/media/bifs/bifs-2D-interactivity-touchsensor-4states.bt delete mode 100644 tests/media/bifs/bifs-2D-interactivity-touchsensor-hitpoint.bt delete mode 100644 tests/media/bifs/bifs-2D-interactivity-touchsensor-isactive-exposedfield.bt delete mode 100644 tests/media/bifs/bifs-2D-interactivity-touchsensor-isactive.bt delete mode 100644 tests/media/bifs/bifs-2D-interactivity-touchsensor-isover.bt delete mode 100644 tests/media/bifs/bifs-2D-interactivity-touchsensor-move_over.bt delete mode 100644 tests/media/bifs/bifs-2D-painting-colortransform-alpha.bt delete mode 100644 tests/media/bifs/bifs-2D-painting-colortransform-bitmap.bt delete mode 100644 tests/media/bifs/bifs-2D-painting-colortransform-color.bt delete mode 100644 tests/media/bifs/bifs-2D-painting-colortransform-texture.bt delete mode 100644 tests/media/bifs/bifs-2D-painting-lineproperties.bt delete mode 100644 tests/media/bifs/bifs-2D-painting-material2D.bt delete mode 100644 tests/media/bifs/bifs-2D-painting-xlineproperties-cap.bt delete mode 100644 tests/media/bifs/bifs-2D-painting-xlineproperties-compositetexture2D.bt delete mode 100644 tests/media/bifs/bifs-2D-painting-xlineproperties-dash.bt delete mode 100644 tests/media/bifs/bifs-2D-painting-xlineproperties-imagetexture.bt delete mode 100644 tests/media/bifs/bifs-2D-painting-xlineproperties-join.bt delete mode 100644 tests/media/bifs/bifs-2D-painting-xlineproperties-lineargradient.bt delete mode 100644 tests/media/bifs/bifs-2D-painting-xlineproperties-radialgradient.bt delete mode 100644 tests/media/bifs/bifs-2D-painting-xlineproperties-scalable.bt delete mode 100644 tests/media/bifs/bifs-2D-painting-xlineproperties-transparent.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-clipper2D.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-form-align-center.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-form-align-horiz.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-form-align-vert.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-form-spread-horiz.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-form-spread-vert.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layer2D.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layer2d-in-layer2d.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-horiz-ltr-nowrap.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-horiz-ltr-wrap-btt.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-horiz-ltr-wrap-ttb.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-horiz-rtl-nowrap.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-horiz-rtl-wrap-btt.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-horiz-rtl-wrap-ttb.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-horiz-text.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-scroll-child.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-scroll-full.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-scroll-modes-horiz.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-scroll-modes-vert.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-scroll-on-off.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-vert-btt-nowrap.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-vert-btt-wrap-ltr.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-vert-btt-wrap-rtl.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-vert-ttb-nowrap.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-vert-ttb-wrap-ltr.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-layout-vert-ttb-wrap-rtl.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-orderedgroup.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-pathlayout-graphics.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-pathlayout.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-transform2D.bt delete mode 100644 tests/media/bifs/bifs-2D-positioning-transformmatrix2D.bt delete mode 100644 tests/media/bifs/bifs-2D-shapes-all.bt delete mode 100644 tests/media/bifs/bifs-2D-shapes-indexfaceset2D.bt delete mode 100644 tests/media/bifs/bifs-2D-shapes-indexlineset2D.bt delete mode 100644 tests/media/bifs/bifs-2D-shapes-pointset2D.bt delete mode 100644 tests/media/bifs/bifs-2D-shapes-xcurve2D.bt delete mode 100644 tests/media/bifs/bifs-2D-texturing-compositetexture2D-background.bt delete mode 100644 tests/media/bifs/bifs-2D-texturing-compositetexture2D-bitmap.bt delete mode 100644 tests/media/bifs/bifs-2D-texturing-compositetexture2D-transparent.bt delete mode 100644 tests/media/bifs/bifs-2D-texturing-gradients-text.bt delete mode 100644 tests/media/bifs/bifs-2D-texturing-gradients-transparent.bt delete mode 100644 tests/media/bifs/bifs-2D-texturing-imagetexture-shapes.bt delete mode 100644 tests/media/bifs/bifs-2D-texturing-lineargradient-simple.bt delete mode 100644 tests/media/bifs/bifs-2D-texturing-lineargradient-spread.bt delete mode 100644 tests/media/bifs/bifs-2D-texturing-movietexture-shapes.bt delete mode 100644 tests/media/bifs/bifs-2D-texturing-pixeltexture.bt delete mode 100644 tests/media/bifs/bifs-2D-texturing-radialgradient-simple.bt delete mode 100644 tests/media/bifs/bifs-2D-texturing-radialgradient-spread.bt delete mode 100644 tests/media/bifs/bifs-2D-texturing-texturetransform-base.bt delete mode 100644 tests/media/bifs/bifs-2D-texturing-texturetransform-interact.bt delete mode 100644 tests/media/bifs/bifs-2D-texturing-texturetransform-transformmatrix2D.bt delete mode 100644 tests/media/bifs/bifs-2D-viewport-complete.bt delete mode 100644 tests/media/bifs/bifs-2D-viewport-simple.bt delete mode 100644 tests/media/bifs/bifs-3D-background-images.bt delete mode 100644 tests/media/bifs/bifs-3D-background.bt delete mode 100644 tests/media/bifs/bifs-3D-interactivity-collision-proxy.bt delete mode 100644 tests/media/bifs/bifs-3D-interactivity-collision.bt delete mode 100644 tests/media/bifs/bifs-3D-interactivity-cylindersensor.bt delete mode 100644 tests/media/bifs/bifs-3D-interactivity-planesensor.bt delete mode 100644 tests/media/bifs/bifs-3D-interactivity-proximitysensor.bt delete mode 100644 tests/media/bifs/bifs-3D-interactivity-spheresensor.bt delete mode 100644 tests/media/bifs/bifs-3D-interactivity-visibilitysensor.bt delete mode 100644 tests/media/bifs/bifs-3D-lighting-directionalLight.bt delete mode 100644 tests/media/bifs/bifs-3D-lighting-fog.bt delete mode 100644 tests/media/bifs/bifs-3D-lighting-pointlight.bt delete mode 100644 tests/media/bifs/bifs-3D-lighting-spotlight.bt delete mode 100644 tests/media/bifs/bifs-3D-positioning-billboard-viewer-alignment.bt delete mode 100644 tests/media/bifs/bifs-3D-positioning-billboard.bt delete mode 100644 tests/media/bifs/bifs-3D-positioning-gravity.bt delete mode 100644 tests/media/bifs/bifs-3D-positioning-layer3D-views.bt delete mode 100644 tests/media/bifs/bifs-3D-positioning-layer3D.bt delete mode 100644 tests/media/bifs/bifs-3D-positioning-lod.bt delete mode 100644 tests/media/bifs/bifs-3D-positioning-transform.bt delete mode 100644 tests/media/bifs/bifs-3D-shapes-box-transparent.bt delete mode 100644 tests/media/bifs/bifs-3D-shapes-box.bt delete mode 100644 tests/media/bifs/bifs-3D-shapes-cone.bt delete mode 100644 tests/media/bifs/bifs-3D-shapes-cylinder.bt delete mode 100644 tests/media/bifs/bifs-3D-shapes-elevationgrid.bt delete mode 100644 tests/media/bifs/bifs-3D-shapes-extrusion.bt delete mode 100644 tests/media/bifs/bifs-3D-shapes-indexedfaceset.bt delete mode 100644 tests/media/bifs/bifs-3D-shapes-indexedlineset.bt delete mode 100644 tests/media/bifs/bifs-3D-shapes-nonlineardeformer.bt delete mode 100644 tests/media/bifs/bifs-3D-shapes-pointset.bt delete mode 100644 tests/media/bifs/bifs-3D-texturing-box-transparent.bt delete mode 100644 tests/media/bifs/bifs-3D-texturing-box-video.bt delete mode 100644 tests/media/bifs/bifs-3D-texturing-box.bt delete mode 100644 tests/media/bifs/bifs-3D-texturing-compositetexture3D-bitmap.bt delete mode 100644 tests/media/bifs/bifs-3D-texturing-compositetexture3D-box.bt delete mode 100644 tests/media/bifs/bifs-3D-texturing-cone-transparent.bt delete mode 100644 tests/media/bifs/bifs-3D-texturing-cone.bt delete mode 100644 tests/media/bifs/bifs-3D-texturing-cylinder-transparent.bt delete mode 100644 tests/media/bifs/bifs-3D-texturing-cylinder.bt delete mode 100644 tests/media/bifs/bifs-3D-texturing-transform-box.bt delete mode 100644 tests/media/bifs/bifs-3D-texturing-transform-matrix-box.bt delete mode 100644 tests/media/bifs/bifs-3D-viewpoint-anim.bt delete mode 100644 tests/media/bifs/bifs-3D-viewpoint-bind-jump.bt delete mode 100644 tests/media/bifs/bifs-3D-viewpoint-bind.bt delete mode 100644 tests/media/bifs/bifs-3D-viewpoint-ortho-bind.bt delete mode 100644 tests/media/bifs/bifs-bitmap-image-meter-metrics.bt delete mode 100644 tests/media/bifs/bifs-bitmap-image-pixel-metrics.bt delete mode 100644 tests/media/bifs/bifs-bitmap-image-resizing.bt delete mode 100644 tests/media/bifs/bifs-bitmap-movie-materialkey.bt delete mode 100644 tests/media/bifs/bifs-bitmap-movie.bt delete mode 100644 tests/media/bifs/bifs-bitmap-video-resizing.bt delete mode 100644 tests/media/bifs/bifs-cachetexture_cache.bt delete mode 100644 tests/media/bifs/bifs-cachetexture_nocache.bt delete mode 100644 tests/media/bifs/bifs-command-animated-osmo4logo.bt delete mode 100644 tests/media/bifs/bifs-command-delete-index.bt delete mode 100644 tests/media/bifs/bifs-command-delete-node.bt delete mode 100644 tests/media/bifs/bifs-command-delete-route.bt delete mode 100644 tests/media/bifs/bifs-command-global-qp.bt delete mode 100644 tests/media/bifs/bifs-command-insert-index.bt delete mode 100644 tests/media/bifs/bifs-command-insert-node.bt delete mode 100644 tests/media/bifs/bifs-command-insert-nodedef.bt delete mode 100644 tests/media/bifs/bifs-command-insert-route.bt delete mode 100644 tests/media/bifs/bifs-command-multiple-replace-field.bt delete mode 100644 tests/media/bifs/bifs-command-multiple-replace-index.bt delete mode 100644 tests/media/bifs/bifs-command-node-delete-ex.bt delete mode 100644 tests/media/bifs/bifs-command-proto-delete.bt delete mode 100644 tests/media/bifs/bifs-command-proto-insert.bt delete mode 100644 tests/media/bifs/bifs-command-protolist-delete.bt delete mode 100644 tests/media/bifs/bifs-command-quantification.bt delete mode 100644 tests/media/bifs/bifs-command-replace-field.bt delete mode 100644 tests/media/bifs/bifs-command-replace-index.bt delete mode 100644 tests/media/bifs/bifs-command-replace-node-null.bt delete mode 100644 tests/media/bifs/bifs-command-replace-node.bt delete mode 100644 tests/media/bifs/bifs-command-replace-route.bt delete mode 100644 tests/media/bifs/bifs-command-replace-scene-null.bt delete mode 100644 tests/media/bifs/bifs-command-replace-scene.bt delete mode 100644 tests/media/bifs/bifs-command-route-add-children.bt delete mode 100644 tests/media/bifs/bifs-command-route-children.bt delete mode 100644 tests/media/bifs/bifs-command-route-node-exposedfield.bt delete mode 100644 tests/media/bifs/bifs-command-route-node.bt delete mode 100644 tests/media/bifs/bifs-command-route-remove-children.bt delete mode 100644 tests/media/bifs/bifs-environmenttest.bt delete mode 100644 tests/media/bifs/bifs-externproto-forestgump-lib.bt delete mode 100644 tests/media/bifs/bifs-externproto-forestgump.bt delete mode 100644 tests/media/bifs/bifs-externproto-mfurl-lib.bt delete mode 100644 tests/media/bifs/bifs-externproto-mfurl.bt delete mode 100644 tests/media/bifs/bifs-externproto-nood-lib.bt delete mode 100644 tests/media/bifs/bifs-externproto-nood.bt delete mode 100644 tests/media/bifs/bifs-externproto-simple-lib.bt delete mode 100644 tests/media/bifs/bifs-externproto-simple.bt delete mode 100644 tests/media/bifs/bifs-game-arrange.bt delete mode 100644 tests/media/bifs/bifs-game-breakout.bt delete mode 100644 tests/media/bifs/bifs-game-bubble.bt delete mode 100644 tests/media/bifs/bifs-game-minesweeper.bt delete mode 100644 tests/media/bifs/bifs-game-othello.bt delete mode 100644 tests/media/bifs/bifs-interpolation-colorinterpolator.bt delete mode 100644 tests/media/bifs/bifs-interpolation-coordinateinterpolator2D.bt delete mode 100644 tests/media/bifs/bifs-interpolation-positionanimator.bt delete mode 100644 tests/media/bifs/bifs-interpolation-positionanimator2D.bt delete mode 100644 tests/media/bifs/bifs-interpolation-positioninterpolator-position.bt delete mode 100644 tests/media/bifs/bifs-interpolation-positioninterpolator-size.bt delete mode 100644 tests/media/bifs/bifs-interpolation-positioninterpolator2D-position.bt delete mode 100644 tests/media/bifs/bifs-interpolation-positioninterpolator2D-size.bt delete mode 100644 tests/media/bifs/bifs-interpolation-scalaranimator.bt delete mode 100644 tests/media/bifs/bifs-interpolation-scalarinterpolator.bt delete mode 100644 tests/media/bifs/bifs-interpolation-timesensor-enabled.bt delete mode 100644 tests/media/bifs/bifs-interpolation-timesensor-starttime_norestart.bt delete mode 100644 tests/media/bifs/bifs-interpolation-timesensor-starttime_restart.bt delete mode 100644 tests/media/bifs/bifs-interpolation-valuator-sftime.bt delete mode 100644 tests/media/bifs/bifs-keynavigator.bt delete mode 100644 tests/media/bifs/bifs-linking-anchor-mp4-next.bt delete mode 100644 tests/media/bifs/bifs-linking-anchor-mp4-prev.bt delete mode 100644 tests/media/bifs/bifs-linking-anchor-viewpoint.bt delete mode 100644 tests/media/bifs/bifs-linking-anchor-www.bt delete mode 100644 tests/media/bifs/bifs-linking-animationstream.bt delete mode 100644 tests/media/bifs/bifs-linking-inline-direct-inline.bt delete mode 100644 tests/media/bifs/bifs-linking-inline-direct.bt delete mode 100644 tests/media/bifs/bifs-linking-inline-http-no-od.bt delete mode 100644 tests/media/bifs/bifs-linking-inline-http.bt delete mode 100644 tests/media/bifs/bifs-linking-inline-od-inline.bt delete mode 100644 tests/media/bifs/bifs-linking-inline-od.bt delete mode 100644 tests/media/bifs/bifs-linking-inline-segment-inline.bt delete mode 100644 tests/media/bifs/bifs-linking-inline-segment.bt delete mode 100644 tests/media/bifs/bifs-media-audiobuffer.bt delete mode 100644 tests/media/bifs/bifs-media-audioclip-urlchanged.bt delete mode 100644 tests/media/bifs/bifs-media-audioclip.bt delete mode 100644 tests/media/bifs/bifs-media-audiosource-mixing.bt delete mode 100644 tests/media/bifs/bifs-media-audiosource-urlchanged.bt delete mode 100644 tests/media/bifs/bifs-media-audiosource.bt delete mode 100644 tests/media/bifs/bifs-media-imagetexture-OD-reuse.bt delete mode 100644 tests/media/bifs/bifs-media-imagetexture-no-od.bt delete mode 100644 tests/media/bifs/bifs-media-imagetexture-object-scale.bt delete mode 100644 tests/media/bifs/bifs-media-imagetexture-transparent.bt delete mode 100644 tests/media/bifs/bifs-media-imagetexture-url-change.bt delete mode 100644 tests/media/bifs/bifs-media-movietexture-control.bt delete mode 100644 tests/media/bifs/bifs-media-movietexture-no-od.bt delete mode 100644 tests/media/bifs/bifs-media-movietexture-od-joinsession.bt delete mode 100644 tests/media/bifs/bifs-media-movietexture-od-leave-session.bt delete mode 100644 tests/media/bifs/bifs-media-movietexture-owns-OCR.bt delete mode 100644 tests/media/bifs/bifs-media-movietexture-shares-OCR.bt delete mode 100644 tests/media/bifs/bifs-media-movietexture-url-change.bt delete mode 100644 tests/media/bifs/bifs-media-sound-spatialize.bt delete mode 100644 tests/media/bifs/bifs-media-sound.bt delete mode 100644 tests/media/bifs/bifs-misc-UTF16-input.bt delete mode 100644 tests/media/bifs/bifs-misc-cyclic-graph.bt delete mode 100644 tests/media/bifs/bifs-misc-hc-proto-events.bt delete mode 100644 tests/media/bifs/bifs-misc-hc-proto-offscreengroup.bt delete mode 100644 tests/media/bifs/bifs-misc-hc-proto-pathextrusion.bt delete mode 100644 tests/media/bifs/bifs-misc-hc-proto-planarextrusion.bt delete mode 100644 tests/media/bifs/bifs-misc-hc-proto-planeclipper-box.bt delete mode 100644 tests/media/bifs/bifs-misc-hc-proto-planeclipper.bt delete mode 100644 tests/media/bifs/bifs-misc-hc-proto-texture.bt delete mode 100644 tests/media/bifs/bifs-misc-non-linear-parsing-conditional.bt delete mode 100644 tests/media/bifs/bifs-misc-non-linear-parsing-use.bt delete mode 100644 tests/media/bifs/bifs-misc-srt-import-3gpp-control-share-ocr.bt delete mode 100644 tests/media/bifs/bifs-misc-srt-import-3gpp-control.bt delete mode 100644 tests/media/bifs/bifs-misc-srt-import-3gpp.bt delete mode 100644 tests/media/bifs/bifs-misc-srt-import.bt delete mode 100755 tests/media/bifs/bifs-od-language-code.bt delete mode 100644 tests/media/bifs/bifs-od-remove-esd.bt delete mode 100644 tests/media/bifs/bifs-od-remove-od.bt delete mode 100644 tests/media/bifs/bifs-od-update-od.bt delete mode 100644 tests/media/bifs/bifs-proto-conditional.bt delete mode 100644 tests/media/bifs/bifs-proto-delete-def.bt delete mode 100644 tests/media/bifs/bifs-proto-delete-index.bt delete mode 100644 tests/media/bifs/bifs-proto-forestgump.bt delete mode 100644 tests/media/bifs/bifs-proto-mfurl.bt delete mode 100644 tests/media/bifs/bifs-proto-multiple.bt delete mode 100644 tests/media/bifs/bifs-proto-nested.bt delete mode 100644 tests/media/bifs/bifs-proto-route.bt delete mode 100644 tests/media/bifs/bifs-proto-sftime-protocode.bt delete mode 100644 tests/media/bifs/bifs-proto-sftime-protointerface.bt delete mode 100644 tests/media/bifs/bifs-proto-simple.bt delete mode 100644 tests/media/bifs/bifs-proto-use.bt delete mode 100644 tests/media/bifs/bifs-script-char-to-int.bt delete mode 100644 tests/media/bifs/bifs-script-child-create.bt delete mode 100644 tests/media/bifs/bifs-script-date.bt delete mode 100644 tests/media/bifs/bifs-script-event-out.bt delete mode 100644 tests/media/bifs/bifs-script-initialize.bt delete mode 100644 tests/media/bifs/bifs-script-load-url.bt delete mode 100644 tests/media/bifs/bifs-script-node-access.bt delete mode 100644 tests/media/bifs/bifs-script-node-create.bt delete mode 100644 tests/media/bifs/bifs-script-proto.bt delete mode 100644 tests/media/bifs/bifs-script-timestamp.bt delete mode 100644 tests/media/bifs/bifs-storage.bt delete mode 100644 tests/media/bifs/bifs-stream-text-switch.bt delete mode 100644 tests/media/bifs/bifs-text-align-horiz1.bt delete mode 100644 tests/media/bifs/bifs-text-align-horiz2.bt delete mode 100644 tests/media/bifs/bifs-text-align-horiz3.bt delete mode 100644 tests/media/bifs/bifs-text-align-horiz4.bt delete mode 100644 tests/media/bifs/bifs-text-align-vert1.bt delete mode 100644 tests/media/bifs/bifs-text-align-vert2.bt delete mode 100644 tests/media/bifs/bifs-text-align-vert3.bt delete mode 100644 tests/media/bifs/bifs-text-align-vert4.bt delete mode 100644 tests/media/bifs/bifs-text-glyph-advance.bt delete mode 100644 tests/media/bifs/bifs-text-length.bt delete mode 100644 tests/media/bifs/bifs-text-maxextend.bt delete mode 100644 tests/media/bifs/bifs-text-style.bt delete mode 100644 tests/media/bifs/bifs-text-unicode.bt delete mode 100644 tests/media/bifs/bifs-text-vrml-alignment.bt delete mode 100644 tests/media/bifs/bifs-timeline-mediacontrol-OCR.bt delete mode 100644 tests/media/bifs/bifs-timeline-mediacontrol-audio-speed.bt delete mode 100644 tests/media/bifs/bifs-timeline-mediacontrol-audio.bt delete mode 100644 tests/media/bifs/bifs-timeline-mediacontrol-complete.bt delete mode 100644 tests/media/bifs/bifs-timeline-mediacontrol-deactivation.bt delete mode 100644 tests/media/bifs/bifs-timeline-mediacontrol-http.bt delete mode 100644 tests/media/bifs/bifs-timeline-mediacontrol-inline-av-inline.bt delete mode 100644 tests/media/bifs/bifs-timeline-mediacontrol-inline-av.bt delete mode 100644 tests/media/bifs/bifs-timeline-mediacontrol-inline-segments-inline.bt delete mode 100644 tests/media/bifs/bifs-timeline-mediacontrol-inline-segments.bt delete mode 100644 tests/media/bifs/bifs-timeline-mediacontrol-segments.bt delete mode 100644 tests/media/bifs/bifs-timeline-mediacontrol-video.bt delete mode 100644 tests/media/bifs/bifs-timeline-mediacontrol-videospeed.bt delete mode 100644 tests/media/bifs/bifs-timeline-mediasensor-segment-switch.bt delete mode 100644 tests/media/bifs/bifs-timeline-mediasensor-segment.bt delete mode 100644 tests/media/bifs/bifs-timeline-mediasensor.bt delete mode 100644 tests/media/bifs/counter-auto.bt delete mode 100644 tests/media/browser_integration/mozilla_ie_action.html delete mode 100644 tests/media/browser_integration/mozilla_ie_simple.html delete mode 100644 tests/media/browser_integration/ppc_action.html delete mode 100644 tests/media/browser_integration/ppc_simple.html delete mode 100755 tests/media/build-navigator-sh delete mode 100644 tests/media/build-navigator-w32.bat delete mode 100755 tests/media/chaptering/chapters.txt delete mode 100644 tests/media/dom/gpac-dom-portability.js delete mode 100644 tests/media/dom/gpac-html-portability.js delete mode 100644 tests/media/encryption/drm_adobe.xml delete mode 100644 tests/media/encryption/drm_cbc.xml delete mode 100644 tests/media/encryption/drm_cbcs.xml delete mode 100644 tests/media/encryption/drm_cens.xml delete mode 100644 tests/media/encryption/drm_ctr.xml delete mode 100644 tests/media/encryption/drm_isma.xml delete mode 100644 tests/media/html5_video/basic_arraybuffer.js delete mode 100644 tests/media/html5_video/basic_audio.svg delete mode 100644 tests/media/html5_video/basic_mediasource.js delete mode 100644 tests/media/html5_video/basic_sourcebuffer.js delete mode 100644 tests/media/html5_video/basic_url.js delete mode 100644 tests/media/html5_video/basic_video.js delete mode 100644 tests/media/html5_video/basic_video.svg delete mode 100644 tests/media/html5_video/bind.js delete mode 100644 tests/media/html5_video/counter-mp4-audio-segments-http.js delete mode 100644 tests/media/html5_video/counter-mp4-av-segments-http.js delete mode 100644 tests/media/html5_video/counter-mp4-video-segments-http.js delete mode 100644 tests/media/html5_video/counter-mp4-video-segments-live-nobs-http.js delete mode 100644 tests/media/html5_video/counter-mp4-video-segments-local.js delete mode 100644 tests/media/html5_video/file.json delete mode 100644 tests/media/html5_video/file.txt delete mode 100644 tests/media/html5_video/file.xml delete mode 100644 tests/media/html5_video/gpac-mse-spatial.js delete mode 100644 tests/media/html5_video/gpac-mse.js delete mode 100644 tests/media/html5_video/implementation_notes.txt delete mode 100644 tests/media/html5_video/mediaevents.js delete mode 100644 tests/media/html5_video/mediaevents.svg delete mode 100644 tests/media/html5_video/mse-overlap.js delete mode 100644 tests/media/html5_video/mse.svg delete mode 100644 tests/media/html5_video/myanmar-tiles.js delete mode 100644 tests/media/html5_video/nodejs-byte-server.js delete mode 100644 tests/media/html5_video/redbull-mp4-audio-segments-http.js delete mode 100644 tests/media/html5_video/redbull-mp4-video-segments-http.js delete mode 100644 tests/media/html5_video/spatial-mse.svg delete mode 100644 tests/media/html5_video/two-videos.svg delete mode 100644 tests/media/html5_video/video.svg delete mode 100644 tests/media/html5_video/xhr.js delete mode 100644 tests/media/html5_video/xhr.svg delete mode 100644 tests/media/index.css delete mode 100644 tests/media/index.xml delete mode 100755 tests/media/laser/enst_2laser3.xml delete mode 100755 tests/media/laser/enst_3laser.xml delete mode 100755 tests/media/laser/enst_afrique.xml delete mode 100755 tests/media/laser/enst_amerique_centrale.xml delete mode 100755 tests/media/laser/enst_amerique_sud.xml delete mode 100755 tests/media/laser/enst_canvas.xml delete mode 100755 tests/media/laser/enst_cee.xml delete mode 100755 tests/media/laser/enst_europe.xml delete mode 100755 tests/media/laser/enst_flow.xml delete mode 100755 tests/media/laser/enst_gold.xml delete mode 100755 tests/media/laser/enst_hame.xml delete mode 100755 tests/media/laser/enst_sydn.xml delete mode 100755 tests/media/laser/enst_topk.xml delete mode 100755 tests/media/laser/enst_tram.xml delete mode 100755 tests/media/laser/stz_Navigation_simple.xml delete mode 100755 tests/media/laser/stz_a_parsing.xml delete mode 100755 tests/media/laser/stz_a_parsing2.xml delete mode 100755 tests/media/laser/stz_animateMotion_mpath.xml delete mode 100755 tests/media/laser/stz_animateMotion_parsing.xml delete mode 100755 tests/media/laser/stz_animateMotion_parsing2.xml delete mode 100755 tests/media/laser/stz_animateTransform_rotate.xml delete mode 100755 tests/media/laser/stz_animateTransform_rotate2.xml delete mode 100755 tests/media/laser/stz_animateTransform_scale.xml delete mode 100755 tests/media/laser/stz_animateTransform_scale2.xml delete mode 100755 tests/media/laser/stz_animateTransform_skew.xml delete mode 100755 tests/media/laser/stz_animateTransform_skew2.xml delete mode 100755 tests/media/laser/stz_animateTransform_translate.xml delete mode 100755 tests/media/laser/stz_animateTransform_translate2.xml delete mode 100755 tests/media/laser/stz_animate_choice.xml delete mode 100755 tests/media/laser/stz_animate_choice2.xml delete mode 100755 tests/media/laser/stz_animate_colorrendering.xml delete mode 100755 tests/media/laser/stz_animate_colorrendering2.xml delete mode 100755 tests/media/laser/stz_animate_display-align.xml delete mode 100755 tests/media/laser/stz_animate_display-align2.xml delete mode 100755 tests/media/laser/stz_animate_display.xml delete mode 100755 tests/media/laser/stz_animate_display2.xml delete mode 100755 tests/media/laser/stz_animate_editable.xml delete mode 100755 tests/media/laser/stz_animate_editable2.xml delete mode 100755 tests/media/laser/stz_animate_fill-rule.xml delete mode 100755 tests/media/laser/stz_animate_fill-rule2.xml delete mode 100755 tests/media/laser/stz_animate_fill.xml delete mode 100755 tests/media/laser/stz_animate_fill2.xml delete mode 100755 tests/media/laser/stz_animate_fillstroke-opacity.xml delete mode 100755 tests/media/laser/stz_animate_fillstroke-opacity2.xml delete mode 100755 tests/media/laser/stz_animate_font-family.xml delete mode 100755 tests/media/laser/stz_animate_font-family2.xml delete mode 100755 tests/media/laser/stz_animate_font-style.xml delete mode 100755 tests/media/laser/stz_animate_font-style2.xml delete mode 100755 tests/media/laser/stz_animate_font-weight.xml delete mode 100755 tests/media/laser/stz_animate_font-weight2.xml delete mode 100755 tests/media/laser/stz_animate_gradientUnits.xml delete mode 100755 tests/media/laser/stz_animate_gradientUnits2.xml delete mode 100755 tests/media/laser/stz_animate_gsize.xml delete mode 100755 tests/media/laser/stz_animate_gsize2.xml delete mode 100755 tests/media/laser/stz_animate_hrefa.xml delete mode 100755 tests/media/laser/stz_animate_hrefa2.xml delete mode 100755 tests/media/laser/stz_animate_hrefstreamid.xml delete mode 100755 tests/media/laser/stz_animate_hrefstreamid2.xml delete mode 100755 tests/media/laser/stz_animate_hrefuse.xml delete mode 100755 tests/media/laser/stz_animate_hrefuse2.xml delete mode 100755 tests/media/laser/stz_animate_image-rendering.xml delete mode 100755 tests/media/laser/stz_animate_image-rendering2.xml delete mode 100755 tests/media/laser/stz_animate_paintserver.xml delete mode 100755 tests/media/laser/stz_animate_paintserver2.xml delete mode 100755 tests/media/laser/stz_animate_parsing.xml delete mode 100755 tests/media/laser/stz_animate_parsing2.xml delete mode 100755 tests/media/laser/stz_animate_path.xml delete mode 100755 tests/media/laser/stz_animate_path2.xml delete mode 100755 tests/media/laser/stz_animate_pointer-events.xml delete mode 100755 tests/media/laser/stz_animate_pointer-events2.xml delete mode 100755 tests/media/laser/stz_animate_points.xml delete mode 100755 tests/media/laser/stz_animate_points2.xml delete mode 100755 tests/media/laser/stz_animate_preserveAspectRatio.xml delete mode 100755 tests/media/laser/stz_animate_preserveAspectRatio2.xml delete mode 100755 tests/media/laser/stz_animate_shaperendering.xml delete mode 100755 tests/media/laser/stz_animate_shaperendering2.xml delete mode 100755 tests/media/laser/stz_animate_stroke-linecap.xml delete mode 100755 tests/media/laser/stz_animate_stroke-linecap2.xml delete mode 100755 tests/media/laser/stz_animate_stroke-linejoin.xml delete mode 100755 tests/media/laser/stz_animate_stroke-linejoin2.xml delete mode 100755 tests/media/laser/stz_animate_stroke-width.xml delete mode 100755 tests/media/laser/stz_animate_stroke-width2.xml delete mode 100755 tests/media/laser/stz_animate_text-anchor.xml delete mode 100755 tests/media/laser/stz_animate_text-anchor2.xml delete mode 100755 tests/media/laser/stz_animate_vector-effect.xml delete mode 100755 tests/media/laser/stz_animate_vector-effect2.xml delete mode 100755 tests/media/laser/stz_animate_viewBox.xml delete mode 100755 tests/media/laser/stz_animate_viewBox2.xml delete mode 100755 tests/media/laser/stz_animate_visibility.xml delete mode 100755 tests/media/laser/stz_animate_visibility2.xml delete mode 100755 tests/media/laser/stz_animate_xy.xml delete mode 100755 tests/media/laser/stz_animate_xy2.xml delete mode 100755 tests/media/laser/stz_anyXML_replace.xml delete mode 100755 tests/media/laser/stz_circle_parsing.xml delete mode 100755 tests/media/laser/stz_circle_parsing2.xml delete mode 100755 tests/media/laser/stz_defs_parsing.xml delete mode 100755 tests/media/laser/stz_defs_parsing2.xml delete mode 100755 tests/media/laser/stz_desc_parsing.xml delete mode 100755 tests/media/laser/stz_desc_parsing2.xml delete mode 100755 tests/media/laser/stz_ellipse_parsing.xml delete mode 100755 tests/media/laser/stz_ellipse_parsing2.xml delete mode 100755 tests/media/laser/stz_foreignObject_parsing.xml delete mode 100755 tests/media/laser/stz_g_parsing.xml delete mode 100755 tests/media/laser/stz_g_parsing2.xml delete mode 100755 tests/media/laser/stz_image_parsing.xml delete mode 100755 tests/media/laser/stz_image_parsing2.xml delete mode 100755 tests/media/laser/stz_line_parsing.xml delete mode 100755 tests/media/laser/stz_line_parsing2.xml delete mode 100755 tests/media/laser/stz_linearGradient_bbox.xml delete mode 100755 tests/media/laser/stz_linearGradient_bbox2.xml delete mode 100755 tests/media/laser/stz_linearGradient_userSpace.xml delete mode 100755 tests/media/laser/stz_linearGradient_userSpace2.xml delete mode 100755 tests/media/laser/stz_metadata_parsing.xml delete mode 100755 tests/media/laser/stz_path_parsing.xml delete mode 100755 tests/media/laser/stz_path_parsing2.xml delete mode 100755 tests/media/laser/stz_polygon_parsing.xml delete mode 100755 tests/media/laser/stz_polygon_parsing2.xml delete mode 100755 tests/media/laser/stz_polyline_parsing.xml delete mode 100755 tests/media/laser/stz_polyline_parsing2.xml delete mode 100755 tests/media/laser/stz_rect_parsing.xml delete mode 100755 tests/media/laser/stz_rect_parsing2.xml delete mode 100755 tests/media/laser/stz_set_color.xml delete mode 100755 tests/media/laser/stz_set_color2.xml delete mode 100755 tests/media/laser/stz_svg_parsing.xml delete mode 100755 tests/media/laser/stz_svg_parsing2.xml delete mode 100755 tests/media/laser/stz_switch_parsing.xml delete mode 100755 tests/media/laser/stz_text_parsing.xml delete mode 100755 tests/media/laser/stz_text_parsing2.xml delete mode 100755 tests/media/laser/stz_title_parsing.xml delete mode 100755 tests/media/laser/stz_use_parsing.xml delete mode 100755 tests/media/laser/stz_use_parsing2.xml delete mode 100644 tests/media/laser/x_austria_relief.png delete mode 100644 tests/media/svg/all_syntaxes_1.1F2.svg delete mode 100644 tests/media/svg/createanim-by-script.svg delete mode 100644 tests/media/svg/createimage-by-script.svg delete mode 100644 tests/media/svg/opacity.svg delete mode 100644 tests/media/svg/utfscript.svg delete mode 100644 tests/media/swf/cuisine.swf delete mode 100644 tests/media/ttml/ebu-ttd_prefix.ttml delete mode 100644 tests/media/ttml/ebu-ttd_sample.ttml delete mode 100644 tests/media/ttml/ebu-ttd_sample_invalid_mixed_namespaces.ttml delete mode 100644 tests/media/ttml/ebu-ttd_sample_invalid_ns.ttml delete mode 100644 tests/media/ttml/ebu-ttd_sample_invalid_root.ttml delete mode 100644 tests/media/ttml/ebu-ttd_sample_span.ttml delete mode 100644 tests/media/ttml/ebu-ttd_timing_contiguous.ttml delete mode 100644 tests/media/ttml/ebu-ttd_timing_non-contiguous.ttml delete mode 100644 tests/media/ttml/ebu-ttd_timing_overlapping_fail.ttml delete mode 100644 tests/media/ttml/ttml.nhml delete mode 100644 tests/media/webvtt/comments.vtt delete mode 100644 tests/media/webvtt/concatenation.vtt delete mode 100644 tests/media/webvtt/counter.srt delete mode 100644 tests/media/webvtt/counter.vtt delete mode 100644 tests/media/webvtt/elephants-dream-chapters-en.vtt delete mode 100644 tests/media/webvtt/elephants-dream-subtitles-de.vtt delete mode 100644 tests/media/webvtt/elephants-dream-subtitles-en.vtt delete mode 100644 tests/media/webvtt/empty.vtt delete mode 100644 tests/media/webvtt/empty2.vtt delete mode 100644 tests/media/webvtt/empty3.vtt delete mode 100644 tests/media/webvtt/empty4.vtt delete mode 100644 tests/media/webvtt/header.vtt delete mode 100644 tests/media/webvtt/invalid1.vtt delete mode 100644 tests/media/webvtt/invalid2.vtt delete mode 100644 tests/media/webvtt/invalid3.vtt delete mode 100644 tests/media/webvtt/invalid4.vtt delete mode 100644 tests/media/webvtt/invalid5.vtt delete mode 100644 tests/media/webvtt/long-duration.vtt delete mode 100644 tests/media/webvtt/multiline-header-additional.vtt delete mode 100644 tests/media/webvtt/multiline-header-id-invalid.vtt delete mode 100644 tests/media/webvtt/multiline-header-id.vtt delete mode 100644 tests/media/webvtt/multiline-header.vtt delete mode 100644 tests/media/webvtt/overlapping-end.vtt delete mode 100644 tests/media/webvtt/overlapping-middle.vtt delete mode 100644 tests/media/webvtt/overlapping-rewritten.vtt delete mode 100644 tests/media/webvtt/overlapping-start.vtt delete mode 100644 tests/media/webvtt/overlapping.vtt delete mode 100644 tests/media/webvtt/simple.vtt delete mode 100644 tests/media/webvtt/spaces.vtt delete mode 100644 tests/media/webvtt/spec-example-basic.vtt delete mode 100644 tests/media/webvtt/spec-example-comment.vtt delete mode 100644 tests/media/webvtt/spec-example-comment2.vtt delete mode 100644 tests/media/webvtt/spec-example-identifier.vtt delete mode 100644 tests/media/webvtt/spec-example-multiple-lines.vtt delete mode 100644 tests/media/webvtt/spec-example-nested.vtt delete mode 100644 tests/media/webvtt/spec-example-voice.vtt delete mode 100644 tests/media/webvtt/svg.vtt delete mode 100644 tests/media/webvtt/timestamps-invalid.vtt delete mode 100644 tests/media/webvtt/timestamps.vtt delete mode 100644 tests/media/x3d/x3d-2D-Arc2d.x3dv delete mode 100644 tests/media/x3d/x3d-2D-ArcClose2d.x3dv delete mode 100644 tests/media/x3d/x3d-2D-Disk2d.x3dv delete mode 100644 tests/media/x3d/x3d-2D-Polyline2d.x3dv delete mode 100644 tests/media/x3d/x3d-2D-Polypoint2d.x3dv delete mode 100644 tests/media/x3d/x3d-2D-TriangleSet2d.x3dv delete mode 100644 tests/media/x3d/x3d-3D-IndexedTriangleFanSet.x3dv delete mode 100644 tests/media/x3d/x3d-3D-IndexedTriangleSet.x3dv delete mode 100644 tests/media/x3d/x3d-3D-IndexedTriangleStripSet.x3dv delete mode 100644 tests/media/x3d/x3d-3D-LineSet.x3dv delete mode 100644 tests/media/x3d/x3d-3D-TriangleFanSet.x3dv delete mode 100644 tests/media/x3d/x3d-3D-TriangleSet.x3dv delete mode 100644 tests/media/x3d/x3d-3D-TriangleStripSet.x3dv delete mode 100644 tests/media/x3d/x3d-misc-ColorRGBA.x3dv delete mode 100644 tests/media/x3d/x3d-misc-HatchStyle.x3dv delete mode 100644 tests/media/x3d/x3d-misc-KeySensor.x3dv delete mode 100644 tests/media/x3d/x3d-misc-StringSensor.x3dv delete mode 100644 tests/media/xmlin4/ebu-ttd_sample.ttml delete mode 100644 tests/media/xmlin4/first.xml delete mode 100644 tests/media/xmlin4/input.txt delete mode 100644 tests/media/xmlin4/input.xml delete mode 100644 tests/media/xmlin4/last.xml delete mode 100644 tests/media/xmlin4/meta-mett-no-mime.nhml delete mode 100644 tests/media/xmlin4/meta-mett-xml-header.nhml delete mode 100644 tests/media/xmlin4/meta-mett-xml.nhml delete mode 100644 tests/media/xmlin4/meta-mett.nhml delete mode 100644 tests/media/xmlin4/meta-metx-no-namespace.nhml delete mode 100644 tests/media/xmlin4/meta-metx.nhml delete mode 100644 tests/media/xmlin4/second.xml delete mode 100644 tests/media/xmlin4/subt-sbtt-no-mime.nhml delete mode 100644 tests/media/xmlin4/subt-sbtt.nhml delete mode 100644 tests/media/xmlin4/subt-stpp-no-namespace.nhml delete mode 100644 tests/media/xmlin4/subt-stpp.nhml delete mode 100644 tests/media/xmlin4/text-stxt-header.nhml delete mode 100644 tests/media/xmlin4/text-stxt-no-mime.nhml delete mode 100644 tests/media/xmlin4/text-stxt.nhml delete mode 100644 tests/rules/bifs-2D-background-background2D-bind-play-ui.xml delete mode 100644 tests/rules/bifs-2D-background-background2D-image-play-ui.xml delete mode 100644 tests/rules/bifs-2D-background-background2D-movie-play-ui.xml delete mode 100644 tests/rules/bifs-2D-background-background2D-url-change-play-ui.xml delete mode 100644 tests/rules/bifs-2D-interactivity-discsensor-play-ui.xml delete mode 100644 tests/rules/bifs-2D-interactivity-htk-sensor-play-stderr.txt delete mode 100644 tests/rules/bifs-2D-interactivity-keysensor-play-ui.xml delete mode 100644 tests/rules/bifs-2D-interactivity-mousesensor-play-ui.xml delete mode 100644 tests/rules/bifs-2D-interactivity-nested-sensors-play-ui.xml delete mode 100644 tests/rules/bifs-2D-interactivity-planesensor2D-play-ui.xml delete mode 100644 tests/rules/bifs-2D-interactivity-proximitysensor2D-play-ui.xml delete mode 100644 tests/rules/bifs-2D-interactivity-stringsensor-play-ui.xml delete mode 100644 tests/rules/bifs-2D-interactivity-touchsensor-4states-play-ui.xml delete mode 100644 tests/rules/bifs-2D-interactivity-touchsensor-hitpoint-play-ui.xml delete mode 100644 tests/rules/bifs-2D-interactivity-touchsensor-isactive-exposedfield-play-ui.xml delete mode 100644 tests/rules/bifs-2D-interactivity-touchsensor-isactive-play-ui.xml delete mode 100644 tests/rules/bifs-2D-interactivity-touchsensor-isover-play-ui.xml delete mode 100644 tests/rules/bifs-2D-interactivity-touchsensor-move_over-play-ui.xml delete mode 100644 tests/rules/bifs-2D-painting-xlineproperties-cap-play-ui.xml delete mode 100644 tests/rules/bifs-2D-painting-xlineproperties-join-play-ui.xml delete mode 100644 tests/rules/bifs-2D-painting-xlineproperties-lineargradient-play-ui.xml delete mode 100644 tests/rules/bifs-2D-painting-xlineproperties-radialgradient-play-ui.xml delete mode 100644 tests/rules/bifs-2D-positioning-clipper2D-play-ui.xml delete mode 100644 tests/rules/bifs-2D-positioning-layer2D-play-ui.xml delete mode 100644 tests/rules/bifs-2D-positioning-layout-scroll-modes-horiz-play-ui.xml delete mode 100644 tests/rules/bifs-2D-positioning-layout-scroll-modes-vert-play-ui.xml delete mode 100644 tests/rules/bifs-2D-positioning-layout-scroll-on-off-play-ui.xml delete mode 100644 tests/rules/bifs-2D-positioning-orderedgroup-play-ui.xml delete mode 100644 tests/rules/bifs-2D-positioning-pathlayout-graphics-play-ui.xml delete mode 100644 tests/rules/bifs-2D-positioning-pathlayout-play-ui.xml delete mode 100644 tests/rules/bifs-2D-positioning-transformmatrix2D-play-ui.xml delete mode 100644 tests/rules/bifs-2D-texturing-compositetexture2D-background-play-ui.xml delete mode 100644 tests/rules/bifs-2D-texturing-compositetexture2D-transparent-play-ui.xml delete mode 100644 tests/rules/bifs-2D-texturing-gradients-text-play-ui.xml delete mode 100644 tests/rules/bifs-2D-texturing-gradients-transparent-play-ui.xml delete mode 100644 tests/rules/bifs-2D-texturing-lineargradient-simple-play-ui.xml delete mode 100644 tests/rules/bifs-2D-texturing-lineargradient-spread-play-ui.xml delete mode 100644 tests/rules/bifs-2D-texturing-radialgradient-simple-play-ui.xml delete mode 100644 tests/rules/bifs-2D-texturing-radialgradient-spread-play-ui.xml delete mode 100644 tests/rules/bifs-2D-texturing-texturetransform-interact-play-ui.xml delete mode 100644 tests/rules/bifs-2D-viewport-simple-play-ui.xml delete mode 100644 tests/rules/bifs-3D-interactivity-cylindersensor-play-ui.xml delete mode 100644 tests/rules/bifs-3D-interactivity-planesensor-play-ui.xml delete mode 100644 tests/rules/bifs-3D-interactivity-proximitysensor-play-ui.xml delete mode 100644 tests/rules/bifs-3D-interactivity-spheresensor-play-ui.xml delete mode 100644 tests/rules/bifs-3D-interactivity-visibilitysensor-play-ui.xml delete mode 100644 tests/rules/bifs-3D-lighting-fog-play-ui.xml delete mode 100644 tests/rules/bifs-3D-positioning-layer3D-play-ui.xml delete mode 100644 tests/rules/bifs-3D-shapes-box-transparent-play-ui.xml delete mode 100644 tests/rules/bifs-3D-shapes-elevationgrid-play-ui.xml delete mode 100644 tests/rules/bifs-3D-shapes-extrusion-play-ui.xml delete mode 100644 tests/rules/bifs-3D-shapes-nonlineardeformer-play-ui.xml delete mode 100644 tests/rules/bifs-3D-texturing-compositetexture3D-bitmap-play-ui.xml delete mode 100644 tests/rules/bifs-3D-viewpoint-bind-jump-play-ui.xml delete mode 100644 tests/rules/bifs-3D-viewpoint-bind-play-ui.xml delete mode 100644 tests/rules/bifs-3D-viewpoint-ortho-bind-play-ui.xml delete mode 100644 tests/rules/bifs-bitmap-image-meter-metrics-play-ui.xml delete mode 100644 tests/rules/bifs-bitmap-image-pixel-metrics-play-ui.xml delete mode 100644 tests/rules/bifs-cachetexture_nocache-play-ui.xml delete mode 100644 tests/rules/bifs-command-proto-delete-play-stderr.txt delete mode 100644 tests/rules/bifs-command-protolist-delete-play-stderr.txt delete mode 100644 tests/rules/bifs-game-arrange-play-ui.xml delete mode 100644 tests/rules/bifs-game-arrange.sh delete mode 100644 tests/rules/bifs-game-breakout-play-ui.xml delete mode 100644 tests/rules/bifs-game-breakout.sh delete mode 100644 tests/rules/bifs-game-bubble-play-ui.xml delete mode 100644 tests/rules/bifs-game-bubble.sh delete mode 100644 tests/rules/bifs-game-minesweeper-play-ui.xml delete mode 100644 tests/rules/bifs-game-minesweeper.sh delete mode 100644 tests/rules/bifs-game-othello-play-ui.xml delete mode 100644 tests/rules/bifs-game-othello.sh delete mode 100644 tests/rules/bifs-interpolation-timesensor-enabled-play-ui.xml delete mode 100644 tests/rules/bifs-interpolation-timesensor-starttime_norestart-play-ui.xml delete mode 100644 tests/rules/bifs-interpolation-timesensor-starttime_restart-play-ui.xml delete mode 100644 tests/rules/bifs-keynavigator-play-ui.xml delete mode 100644 tests/rules/bifs-linking-inline-direct-inline-play-ui.xml delete mode 100644 tests/rules/bifs-linking-inline-od-inline-play-ui.xml delete mode 100644 tests/rules/bifs-linking-inline-segment-inline-play-ui.xml delete mode 100644 tests/rules/bifs-media-audiobuffer-play-ui.xml delete mode 100644 tests/rules/bifs-media-audioclip-play-ui.xml delete mode 100644 tests/rules/bifs-media-audioclip-urlchanged-play-ui.xml delete mode 100644 tests/rules/bifs-media-audiosource-mixing-play-ui.xml delete mode 100644 tests/rules/bifs-media-audiosource-play-ui.xml delete mode 100644 tests/rules/bifs-media-audiosource-urlchanged-play-ui.xml delete mode 100644 tests/rules/bifs-media-imagetexture-url-change-play-ui.xml delete mode 100644 tests/rules/bifs-media-movietexture-control-play-ui.xml delete mode 100644 tests/rules/bifs-media-movietexture-url-change-play-ui.xml delete mode 100644 tests/rules/bifs-misc-hc-proto-events-play-ui.xml delete mode 100644 tests/rules/bifs-misc-non-linear-parsing-conditional-play-ui.xml delete mode 100644 tests/rules/bifs-proto-conditional-play-ui.xml delete mode 100644 tests/rules/bifs-proto-multiple-play-ui.xml delete mode 100644 tests/rules/bifs-proto-nested-play-ui.xml delete mode 100644 tests/rules/bifs-proto-route-play-ui.xml delete mode 100644 tests/rules/bifs-script-child-create-play-ui.xml delete mode 100644 tests/rules/bifs-script-date.sh delete mode 100644 tests/rules/bifs-script-load-url-play-ui.xml delete mode 100644 tests/rules/bifs-script-node-create-play-ui.xml delete mode 100644 tests/rules/bifs-script-proto-play-ui.xml delete mode 100644 tests/rules/bifs-storage-play-ui.xml delete mode 100644 tests/rules/bifs-stream-text-switch-play-ui.xml delete mode 100644 tests/rules/bifs-timeline-mediacontrol-OCR-play-ui.xml delete mode 100644 tests/rules/bifs-timeline-mediacontrol-OCR.sh delete mode 100644 tests/rules/bifs-timeline-mediacontrol-audio-play-ui.xml delete mode 100644 tests/rules/bifs-timeline-mediacontrol-audio-speed-play-ui.xml delete mode 100644 tests/rules/bifs-timeline-mediacontrol-complete-play-ui.xml delete mode 100644 tests/rules/bifs-timeline-mediacontrol-deactivation-play-ui.xml delete mode 100644 tests/rules/bifs-timeline-mediacontrol-http-play-ui.xml delete mode 100644 tests/rules/bifs-timeline-mediacontrol-inline-av-play-ui.xml delete mode 100644 tests/rules/bifs-timeline-mediacontrol-inline-segments.sh delete mode 100644 tests/rules/bifs-timeline-mediacontrol-segments-play-ui.xml delete mode 100644 tests/rules/bifs-timeline-mediacontrol-video-play-ui.xml delete mode 100644 tests/rules/bifs-timeline-mediacontrol-videospeed-play-ui.xml delete mode 100644 tests/rules/laser-stz_animate_hrefstreamid-play-stderr.txt delete mode 100644 tests/rules/laser-stz_animate_hrefstreamid2-play-stderr.txt delete mode 100644 tests/rules/xmlin-meta-metx-no-namespace-import-stderr.txt delete mode 100644 tests/rules/xmlin-subt-stpp-export-track-stderr.txt delete mode 100644 tests/rules/xmlin-subt-stpp-no-namespace-import-stderr.txt delete mode 100644 tests/rules/xmlin-ttml-stpp-export-track-stderr.txt delete mode 100644 tests/scripts/3D-HEVC.sh delete mode 100755 tests/scripts/bifs_all.sh delete mode 100755 tests/scripts/compositor_modes.sh delete mode 100644 tests/scripts/dash-if-live.sh delete mode 100644 tests/scripts/dash-if-ondemand.sh delete mode 100644 tests/scripts/dash-live-generation.sh delete mode 100644 tests/scripts/dash-live.sh delete mode 100644 tests/scripts/dash-multiperiod.sh delete mode 100644 tests/scripts/dash-ts.sh delete mode 100644 tests/scripts/dash.sh delete mode 100755 tests/scripts/encryption.sh delete mode 100755 tests/scripts/fuzz-base.sh delete mode 100644 tests/scripts/hls.sh delete mode 100644 tests/scripts/iff.sh delete mode 100644 tests/scripts/item.sh delete mode 100755 tests/scripts/laser_all.sh delete mode 100755 tests/scripts/mp42ts.sh delete mode 100755 tests/scripts/mp4box-base.sh delete mode 100755 tests/scripts/mp4box-io.sh delete mode 100644 tests/scripts/mp4box-kind.sh delete mode 100644 tests/scripts/mp4box-lang.sh delete mode 100755 tests/scripts/mp4box-rtp.sh delete mode 100755 tests/scripts/mp4box-subtitle.sh delete mode 100755 tests/scripts/mp4client-base.sh delete mode 100644 tests/scripts/mp4client-dash-config.sh delete mode 100755 tests/scripts/mp4client-dash.sh delete mode 100755 tests/scripts/mp4client-http.sh delete mode 100755 tests/scripts/mp4client-rawout.sh delete mode 100644 tests/scripts/negctts.sh delete mode 100644 tests/scripts/nhml-subs.sh delete mode 100644 tests/scripts/noFragsDefault.sh delete mode 100644 tests/scripts/range_extension.sh delete mode 100755 tests/scripts/scalable.sh delete mode 100755 tests/scripts/smooth.sh delete mode 100644 tests/scripts/swf.sh delete mode 100755 tests/scripts/test_prl.sh delete mode 100755 tests/scripts/vtt-all.sh delete mode 100755 tests/scripts/x3d.sh delete mode 100755 tests/scripts/xmlin.sh delete mode 100644 tests/stylesheet.xsl diff --git a/.gitattributes b/.gitattributes index fe272ac..f32d776 100644 --- a/.gitattributes +++ b/.gitattributes @@ -20,3 +20,6 @@ configure text eol=lf #tests tests/media/xmlin4/input.txt text eol=crlf +tests/media/xmlin4/input.xml text eol=lf +tests/media/laser/*.xml text eol=lf +tests/media/ttml/ebu-ttd_sample.ttml text eol=lf diff --git a/.gitignore b/.gitignore index 803dfb7..ba9dd27 100644 --- a/.gitignore +++ b/.gitignore @@ -60,12 +60,12 @@ bld/ *.so *.dylib *.dll -*.lai -*.la -*.a +*.lai +*.la +*.a *.lib -*.exe -*.out +*.exe +*.out *.app *.apk *.dmg @@ -74,17 +74,18 @@ bld/ .depend .deps/ +.dep -#GPAC specific +#GPAC specific config.h include/gpac/revision.h include/gpac/revision.h.new extra_lib/lib/ -extra_lib/include/SDL/ -extra_lib/include/ios/ +extra_lib/include/ config.mak configure-stamp gpac.pc +share/gpac.desktop tests/external_media/ tests/results/ - +tests/hash_refs/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..670f16f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,5 @@ +[submodule "testsuite"] + path = testsuite + url = https://github.com/gpac/testsuite.git + branch = filters + ignore = dirty diff --git a/.travis.yml b/.travis.yml index 5047ab9..7ec1e92 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,30 @@ +#changes for GPAC filters (0.9+) for travis CI: GPAC hosts its own buildbot for building on various platforms, running tests, coverage and safety checks +#TravisCI is only used for checking sanity of PRs. Consequently: +#- OSX and mingw no longer compiled +#- linux only compiled for +# - mp4box static +# - release mode with mem tracking and gcov + language: c compiler: gcc os: - linux - - osx +# - osx +dist: bionic +services: + - xvfb + before_install: - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -y update -qq ; fi install: - - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -y install build-essential fakeroot dpkg-dev devscripts ccache debhelper pkg-config g++ mesa-utils ; fi - - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -y install -y zlib1g-dev libfreetype6-dev libjpeg62-dev libpng12-dev libopenjpeg-dev libmad0-dev libfaad-dev libogg-dev libvorbis-dev libtheora-dev liba52-0.7.4-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavdevice-dev libxv-dev x11proto-video-dev libgl1-mesa-dev x11proto-gl-dev linux-sound-base libxvidcore-dev libssl-dev libjack-dev libasound2-dev libpulse-dev libsdl1.2-dev dvb-apps libavcodec-extra-53 libmozjs185-dev ; fi - - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -y install -y gcc-mingw-w64-i686 g++-mingw-w64-i686 binutils-mingw-w64-i686 gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 binutils-mingw-w64-x86-64 mingw-w64-dev ; fi - - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -qq -y install lcov time; fi - - if [ "$TRAVIS_OS_NAME" == "osx" ]; then sudo chown -R "$USER":admin /usr/local; fi - - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew update; fi - - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew install gnu-time gnu-sed gnu-tar xz lcov ; fi - - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew install faad2 sdl freetype libvorbis theora openjpeg libmad xvid libogg spidermonkey ffmpeg ; fi + - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -y install build-essential fakeroot dpkg-dev devscripts ccache debhelper pkg-config g++ mesa-utils lcov ; fi + - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -y install -y zlib1g-dev libfreetype6-dev libjpeg62-dev libpng-dev libmad0-dev libfaad-dev libogg-dev libvorbis-dev libtheora-dev liba52-0.7.4-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavdevice-dev libxv-dev x11proto-video-dev libgl1-mesa-dev x11proto-gl-dev libxvidcore-dev libssl-dev libjack-dev libasound2-dev libpulse-dev libsdl2-dev dvb-apps ; fi + - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -qq -y install time; fi +# - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -y install -y gcc-mingw-w64-i686 g++-mingw-w64-i686 binutils-mingw-w64-i686 gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 binutils-mingw-w64-x86-64 mingw-w64-x86-64-dev ; fi +# - if [ "$TRAVIS_OS_NAME" == "osx" ]; then sudo chown -R "$USER":admin /usr/local; fi +# - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew update; fi +# - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew install gnu-time gnu-sed gnu-tar xz lcov ; fi +# - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew install faad2 sdl freetype libvorbis theora openjpeg libmad xvid libogg spidermonkey ffmpeg ; fi env: - GPAC_CONFIGURE_OPTIONS="--prefix=build/mp4box --enable-debug --static-mp4box" GPAC_CONFIGURE_ECFLAGS="" DOINSTALL="make install" DOTRAVIS="" - GPAC_CONFIGURE_OPTIONS="--enable-debug --static-modules" GPAC_CONFIGURE_ECFLAGS="" DOINSTALL="" DOTRAVIS="" @@ -22,23 +33,23 @@ matrix: include: - os: linux before_script: - - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" - - sleep 3 # give xvfb some time to start - - glxinfo # check glx status + #git submodule update is called by travis, just checkout filter branch + - "git -C ./testsuite checkout filters" + - xvfb-run -e /dev/stdout --auto-servernum --server-num=1 glxinfo # check glx status env: GPAC_CONFIGURE_OPTIONS="--enable-mem-track --enable-gcov" GPAC_CONFIGURE_ECFLAGS="" DOINSTALL="sudo make install" DOTRAVIS="make travis" AUDIODEV=null - - os: linux - env: GPAC_CONFIGURE_OPTIONS="--prefix=build/x86_64-w64-mingw32 --enable-debug --static-mp4box --use-zlib=no --target-os=mingw32 --cross-prefix=i686-w64-mingw32- --extra-ldflags=-Lbuild/x86_64-w64-mingw32/lib" GPAC_CONFIGURE_ECFLAGS="-Ibuild/x86_64-w64-mingw32/include -w -fPIC" DOINSTALL="" DOTRAVIS="" - - os: linux - env: GPAC_CONFIGURE_OPTIONS="--prefix=build/x86_64-w64-mingw32 --enable-debug --static-mp4box --use-zlib=no --target-os=mingw32 --cross-prefix=x86_64-w64-mingw32- --extra-ldflags=-Lbuild/x86_64-w64-mingw32/lib" GPAC_CONFIGURE_ECFLAGS="-Ibuild/x86_64-w64-mingw32/include -w -fPIC" DOINSTALL="" DOTRAVIS="" - - os: linux - env: GPAC_CONFIGURE_OPTIONS="--prefix=build/all --enable-debug --disable-all" GPAC_CONFIGURE_ECFLAGS="" DOINSTALL="make install" DOTRAVIS="" - - os: linux - env: GPAC_CONFIGURE_OPTIONS="--enable-debug --use-js=no --use-mad=no --use-xvid=no --use-ogg=no --use-vorbis=no --use-theora=no --use-openjpeg=no --disable-streaming --disable-isoff-frag --disable-isoff-hint --disable-isoff-write --disable-loader-xmt --disable-loader-bt --disable-loader-isoff --disable-scene-encode --disable-mcrypt --disable-od-dump --disable-scene-dump --disable-scene-stats --disable-swf --disable-export --disable-import --disable-m2ps --disable-ogg -disable-avi --disable-qtvr --disable-seng --disable-smgr --disable-x3d --disable-3d --disable-ssl --disable-jack --disable-pulse --use-a52=no --disable-odf --disable-isoff --disable-m2ts-mux --disable-dvbx --disable-saf --disable-vobsub --disable-ttxt --disable-od-parse" GPAC_CONFIGURE_ECFLAGS="" DOINSTALL="" DOTRAVIS="" - - os: osx - env: GPAC_CONFIGURE_OPTIONS="--enable-mem-track" GPAC_CONFIGURE_ECFLAGS="" DOINSTALL="sudo make install" DOTRAVIS="" AUDIODEV=null + +# - os: linux +# env: GPAC_CONFIGURE_OPTIONS="--prefix=build/x86_64-w64-mingw32 --enable-debug --static-mp4box --use-zlib=no --target-os=mingw32 --cross-prefix=i686-w64-mingw32- --extra-ldflags=-Lbuild/x86_64-w64-mingw32/lib" GPAC_CONFIGURE_ECFLAGS="-Ibuild/x86_64-w64-mingw32/include -w -fPIC" DOINSTALL="" DOTRAVIS="" +# - os: linux +# env: GPAC_CONFIGURE_OPTIONS="--prefix=build/x86_64-w64-mingw32 --enable-debug --static-mp4box --use-zlib=no --target-os=mingw32 --cross-prefix=x86_64-w64-mingw32- --extra-ldflags=-Lbuild/x86_64-w64-mingw32/lib" GPAC_CONFIGURE_ECFLAGS="-Ibuild/x86_64-w64-mingw32/include -w -fPIC" DOINSTALL="" DOTRAVIS="" +# - os: linux +# env: GPAC_CONFIGURE_OPTIONS="--prefix=build/all --enable-debug --disable-all" GPAC_CONFIGURE_ECFLAGS="" DOINSTALL="make install" DOTRAVIS="" +# - os: linux +# env: GPAC_CONFIGURE_OPTIONS="--enable-debug --use-js=no --use-mad=no --use-xvid=no --use-ogg=no --use-vorbis=no --use-theora=no --use-openjpeg=no --disable-streaming --disable-isoff-frag --disable-isoff-hint --disable-isoff-write --disable-loader-xmt --disable-loader-bt --disable-loader-isoff --disable-scene-encode --disable-mcrypt --disable-od-dump --disable-scene-dump --disable-scene-stats --disable-swf --disable-export --disable-import --disable-m2ps --disable-ogg -disable-avi --disable-qtvr --disable-seng --disable-smgr --disable-x3d --disable-3d --disable-ssl --disable-jack --disable-pulse --use-a52=no --disable-odf --disable-isoff --disable-m2ts-mux --disable-dvbx --disable-saf --disable-vobsub --disable-ttxt --disable-od-parse --disable-atsc" GPAC_CONFIGURE_ECFLAGS="" DOINSTALL="" DOTRAVIS="" +# - os: osx +# env: GPAC_CONFIGURE_OPTIONS="--enable-mem-track" GPAC_CONFIGURE_ECFLAGS="" DOINSTALL="sudo make install" DOTRAVIS="" AUDIODEV=null script: - - ./configure --extra-cflags=''"$GPAC_CONFIGURE_ECFLAGS"'' $GPAC_CONFIGURE_OPTIONS && make && $DOINSTALL && $DOTRAVIS + - ./configure --extra-cflags=''"$GPAC_CONFIGURE_ECFLAGS"'' $GPAC_CONFIGURE_OPTIONS && make && $DOINSTALL && ( if [ -n "$DOTRAVIS" ]; then xvfb-run -e /dev/stdout --auto-servernum --server-num=1 --server-args="-screen 0 1024x768x24" $DOTRAVIS ; fi ) after_success: - if [ "$TRAVIS_OS_NAME" == "linux" ]; then bash <(curl -s https://codecov.io/bash) ; fi diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index 1dd25b9..0000000 --- a/AUTHORS +++ /dev/null @@ -1,28 +0,0 @@ -Author: -======= -Jean Le Feuvre - -Contributors: -============= -Cyril Concolato -Romain Bouqueau -Pierre Souchay -Viet Tran Trung Nguyen -Emmanouil Potetsianakis -Rodolphe Fouquet -Jérôme Gorin -Arash Shafiei -Ivan Vecera -Jean-Claude Moissinac -Jean-Claude Dufourd -Benoit Pellan -Philippe de Cuetos -Jonathan Sillan -Harlina Daud -Daniel Comalrena -Stanislas Selle -Stéphane Thomas -Ayodeji Aribuki -Yi-Zhen Zhang -Berthele Rodriguez -Yen Chi Hsuan diff --git a/BUGS b/BUGS deleted file mode 100644 index 2785752..0000000 --- a/BUGS +++ /dev/null @@ -1,3 +0,0 @@ -BUGS - -A bug tracker is available at https://github.com/gpac/gpac/issues diff --git a/Changelog b/Changelog index 6c682c2..a3f1d79 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,212 @@ +10/09/2020: GPAC 1.0.1 +This release fixes build and installation issues in 1.0.0, as well as various bugs introduced during the migration to the filters architecture. + +It also adds several small features: +- better ttml import +- better support for MPEGH audio +- support fur DASH UTCTiming +- manifest generation from pre-fragmented DASH/HLS mp4 +- speed optimization in isobmf reading (normal and fragmented) +- improved JS API for the filter session +- core tools exposed as JS module (file io, bitstream, etc ...) +- android fixes + +16/06/2020: GPAC 1.0 + - Complete rewrite of GPAC streaming core: + * addition of a filter-based architecture, used by MP4Client and MP4Box. + * moving all decoders and demuxer plugins of MP4Client and most of MP4Box import/export code as filters for this new architecture, + * moving DASH/HLS segmenter to a filter + * moving MP4Client compositor and most of the GF_Terminal internals to a filter + * addition of a new application gpac, whose only purpose is to create and run filter chains + * removal of MP42TS and DashCast applications since these functionalities are provided by gpac + * deprecation of some features (widget management, MSE draft implementation for SVG media, UPnP, TEMI player support). +- Profile system allowing to override through a static file default options of all filters and libgpac core +- Alias system for gpac app to simplify your command lines +- Enhanced DASHer: + * Support for HLS and dual HLS / DASH generation + * Support for any input + * True low-latency mode for DASH + * Support for multiple periods + * Support for other segment formats (raw, mkv, webm currently tested) +- Input and outputs + * Generic pipe, TCP, UDP, and Unix Domain socket input and output + * RTSP server output + * HTTP output (client and server), supporting low latency DASH access + * Ad-hoc stream format called GSF to allow serialization to file, pipe or socket of a session (for distributed filter chains), supporting AES-128 CBC encryption. +- Raw audio (PCM) and video (RGB, YUV) reframers and exporters +- HEVC tile spliting and merging filters +- Compositor is a standalone filter (SVG/BIFS/VRML graphics in a filter chain) +- Image encoding support through libjpg and libpng +- Full FFMPEG support: + * Encoding/decoding through FFMPEG libavcodec + * Multiplexing/demultiplexing through FFMPEG libavformat + * Device grabbers through FFMPEG libavdevice + * Raw audio and video filters through FFMPEG libavfilter +- Support for QuickJS (ES2002) and bindings for: + * Complete filter API + * GPAC software rasterizer (EVG) + * WebGL 1.0 Core + * XmlHttpRequest and uDOM APIs + * Storage +- Inspect and analyze filter +- MPEG-2 TS splitter +- Video cropper filter with zero-copy mode +- Video flip filter +- Source concatenator filter +- Simple audio and video output filters +- Experimental audio and video rewinder filter +- Encryption + * On-the-fly encryption and decryption, now available as filters + * Segment-based encryption and decryption +- ISOBMFF + * box customization + * Better QT support, prores parsing and dumping + * Support for raw media (QT style or ISOBMFF for audio) + * Simplify HEIF batch conversion through item to track mapping + * Reading from pipes (fragmented or progressive files) + * Writing to packets rather than files + * Fast interleaved file creation mode with less storage requirements +- FileIO wrapper for cases where files are not stored in a file system known by GPAC +- Testing and Documentation + * Live doc generation (man and wiki) + * Improved coverage + * Split test suite as dedicated repo + * Moved all resource to https://wiki.gpac.io + * Started howto pages on wiki + * Many bug fixes + +27/06/2019: GPAC 0.8.0 + - General + * Many security fixes (static compile and fuzzing through AFL, always ongoing). + * Many bugs fixes + * Added :ncl option in log levels to disable color logs + * More tests and coverage + - File Formats + * Better support of QTFF / ProRes files + * Support for AV1 + - import and export + - source formats: OBUs (Section 5), IVF and AnnexB + - AV1 in HEIF + * Support for color (nclc, nclx and ICC profiles) in HEIF and ISOBMFF + * Support for HDR (mdcv, clli) info in HEIF and ISOBMFF + * Support for alpha in HEIF + * Support for enforcing pasp presence even for 1:1 ratios + * HEVC temporal sublayer split in MP4Box + * Allow meta storage before mdat for meta-only files (heif and co) + * Added option to keep AU delimiter in isobmff samples + * Support for opus import + * Support for pixi and ccst in HEIF + * DolbyVision 'dvcC' and partial 'dvhe' boxes for HDR + * Support for VP9 import and playback + * Sample dependency in avc and hevc importers, and track thinner for non-refs images + * Support for audio_roll signaling + * New audio import mode to control AudioSampleEntry creation (v0, v1 ISOBMFF, v1 QTFF) + * xHE-AAC import with detection of sync samples + * Added support for MPEG-H 3D audio boxes (no import yet) + * Handle Vobsub empty SPU packets + * Added auxv and pict support + - Common Encryption + * Fully compliant CENC supporting cenc, cens, cbc1 and cbcs + * CENC for AV1 + * Improved DASH+CENC support, pssh in MPD + * ForceClear mode for CENC to skip encryption without sample groups + * Made senc in movie fragments always stored before truns + * Added default values handling for cbcs and possibility to set protection system per track + * Compatibility with OpenSSL 1.1.x + - Streaming and Adaptive Streaming + * Support for ATSC3.0 both US and Korean versions ! + * Support for for live splices (xlink period insertions) in DASH client + * Automatic period continuity in DASH when no codec change between periods + * Added DASH cue-base segmentation (XML based) and -dsap option to generate cue files from source + * Support for BBA-0 and BOLA implementations + * Write fragment defaults in trex even when not using them + * Support for simple ssix for keyframe data byterange at the start of a segment + * Moved segment template at AdaptationSet level if only one representation + * Changed default bsmode in dasher if single input file + * Added init-seg-ext option + * Added -mvex-after-traks option to MP4Box when dashing for CMAF + * Added segmentation option to insert a tfdt per traf + * Added -closest mode for DASH segmentation + * Added -bound option to use audio segmenting method for video + * Renamed -dash-run-for to -run-for + * Added '=' in dash templates + * Improved bandwith estimation when using HTTP 1.1 chunk transfer + * Add option to force moof base offsets + - MP4Box + * Added -catpl to concatenate from playlist in MP4Box + * Added options to set movie timescale at import and dash time + * Added mpd rip option and top-level box compressor in MP4Box + * Made -dts skip timing check and added -dtsc for that + * Made force-cat option more agressive + * Support for MovieFragmentRandomAccess using -mfra option + * Added -dtsx to dump timing without offset + * Added -dnalc opt for nal CRC dump + * Added chunk extraction up to time until end + * ISOBMFF single track import now removes references by default + - Decoders + * Updated ffmpeg to 4.0.2 + * Moved to openHEVC 3.0 API + * Added nvdec support (windows, linux) with reuse of decoder context for tiled VR + * Added HEVC support to mediacodec on android + * AV1 playback through ffmpeg + * Opus playback through ffmpeg + - 3D, VR and 360 + * Added vrhud for multiviewpoint 360 + * Added forced visibility mode of tiles in VR + * Added tile visibility debug mode + * Added forced stereo output for openhevc + * Disable face nav if mouse grabbed + * Added simple face tracking vr navigation based on udp commands + * Added PSVR support + * Added mouse move emulation at window border to force sphere rotation when inactive + * Changed tile visibility algo to sample points in mesh + - Players (Mobile and Desktop) + * Added about extension + * Added multiple audio objects in dynamic scene + * Added addon splicing of main content + * Added mosaic://v1:.:vN url support + * Added gaze simulation through mouse and gaze-sphere visibility test + - Subtitles + * Allow * as argument of -srt|ttxt to dump all possible tracks (#925) + * Improved support for WebVTT import + * Improved support for WebVTT DASHing/fragmentation + - Misc + * OSX install now done through PKG and modify PATH env in/etc/paths + * Added initial PMT version and disc marker to TS muxer + * Moved dektec output to matrix API, added SDI clipping + * Added temi periodic toggle and manual toggle in MP42TS + +26/04/2017: GPAC 0.7.1 + - Full changelog at https://github.com/gpac/gpac/wiki/GPAC-0.7.0 + - Many security fixes (static compile and fuzzing through [AFL](http://lcamtuf.coredump.cx/afl/), always ongoing) + - Colorized log. + - Fix pkg-config Private.libs. + - Changed default audio volume to 100% instead of 75%. + - Expose more experimental options through the [documentation](http://htmlpreview.github.io/?https://github.com/gpac/gpac/blob/master/doc/configuration.html). + - Improved GLES renderer on mobile platforms + - YUV422 and YUV444 8 and 10 bit support in GLES renderer + - Improvements on AVI dump. + - SAT>IP modified RTSP client. + - Added L-HEVC File Format support (SHVC/MV-HEVC tracks and HEVC Tile Tracks). + - Added MPEG IFF (image File Format) support. + - Range extension support for AVC and HEVC. + - SHVC and MV-HEVC importers and playback: moved to final spec version (SHM6+). + - Support of HLS with fragmented MP4 playback. + - APIs: gf_mpd_() functions and new segmenter API. + - Improved alternate groups. + - More support for PIFF PSEC and Smooth Streaming ( file format & playback). + - DASH client: pluggable algorithms + improvements with scalable contents. + - The counter source from the DASH sequences added to the public content. + - HLS and DASH playback minor fixes. + - Cleanup of DASH client logs. + - Added support for DASH SRD in 360 for independent videos videos (NxM partial spheres) [more](https://gpac.wp.imt.fr/2016/05/25/srd/) + - Added support for DASH SRD in 360 for HEV Ctiled videos (NxM tiles on one sphere) - [checkout tuto](https://gpac.wp.imt.fr/2017/02/01/hevc-tile-based-adaptation-guide/) + - Apple VideoToolBox hardware decoding support for OSX and iOS for AVC|H264. + - Android hardware decoding hardware acceleration for AVC|H264 (HEVC on its way). + - Android build is based on Android Studio. + - Android: new File Manager. + - Import of TTML via NHML according to MPEG-4 part 30 improved. + 19/02/2016: GPAC 0.6.0 Many things added, improved and fixed - see https://github.com/gpac/gpac/releases/tag/v0.6.0 @@ -343,7 +552,7 @@ - added support for nero chapters in MP4 ('chpl' box), in MP4Box (-chap file.chp) and selection in players (chapters mapped to MPEG-4 SegmentDescriptors). note regarding chp files: not sure about file syntax, currently support for (one chapter entry per line): ZoomPlayer syntax: AddChapter (-fps for import framerate selection), AddChapterBySecond and AddChapterByTime - Regular time code … la SRT: HH:MM:SS[:ms or .ms] [Chapter Name] + Regular time code … la SRT: HH:MM:SS[:ms or .ms] [Chapter Name] SMPTE time code: HH:MM:SS;fr/fps [Chapter Name] - if fps is omitted, use '-fps' if specified or 25 fps default. - fixed avi packed bitstream flag removal bug with beta versions of DivX. diff --git a/INSTALLME b/INSTALLME deleted file mode 100644 index 9f7eb51..0000000 --- a/INSTALLME +++ /dev/null @@ -1,67 +0,0 @@ -Installation instructions for latest GPAC GIT version - March 2015: - -For Windows, Linux and Mac OS X versions, follow the instructions on http://gpac.io/downloads/ - -For Android, follow the instructions in gpac/build/android/README - -(WindowsMobile platform is no longer maintained) -For WindowsMobile platform, get the latest package of gpac extra libs available here: - -https://sourceforge.net/p/gpac/code/HEAD/tree/trunk/gpac_extra_libs/ - -and follow the instructions in gpac/doc/INSTALL.wCE - - - -Other Installation instructions for GPAC - - -* Foreword - - GPAC may be compiled without any third party libraries, but in this case its functionalities are very -limited (no still image, no audio, no video, no text, no scripting). It is therefore recommended to download the -extra lib package available at http://sourceforge.net/projects/gpac. Compilation instructions for these libraries -are provided per library in the package. - - The current extra_lib package to use with gpac is gpac_extra_libs available here: -http://download.tsi.telecom-paristech.fr/gpac/gpac_extra_libs.zip - - In case you have some of these libs already installed on your system, the detailed list of dependencies is - * freetype2 from version 2.1.4 on. - * SpiderMonkey v1.7 (libjs from mozilla). - * libjpg version 6b - * Libpng version 1.2.33 (older versions should work) - * MAD version 0.15.1b (older versions should work) - * xvid version 1.0 (0.9.0 / .1 / .2 should also work) - * ffmpeg (latest stable API version checked was 17 February 2016 snapshot, you may need to change a few things with current versions) - * libogg 1.1, libvorbis 1.1 and libtheora 1.0 from Xiph.org (newer versions work) - * faad2, version 2.0 or above (2.6.1 working) - * liba52, version 0.7.4 - * OpenJPEG, version 1.3 - * OpenSVCDecoder, version 1.3 - -* Installing GPAC - -/!\ GPAC won't compile if the 'git' command is not in your path /!\ - -!! WARNING !! -The following instructions may not be completely up to data -You should use online instructions which may be more up-to-date: -http://gpac.io/2011/04/18/command-line-gpac-compiling-on-windows-x86-using-free-microsoft-visual-c/ - -Detailed instruction for Win32 MSVC Compilation are available in gpac/doc/INSTALL.w32 - -Detailed instruction for WinCE eVC Compilation are available in gpac/doc/INSTALL.wCE - -Detailed instruction for GCC Compilation are available in gpac/doc/INSTALL.gcc - -Detailed instruction for GCC cross-compilation for familiar+GPE systems are available in gpac/doc/INSTALL.gpe - -Detailed instruction for GCCE/Symbian cross-compilation for Symbian v9.1 systems are available in gpac/doc/INSTALL.symbian - -Detailed instruction for iOS Compilation are available in gpac/build/xcode/README_IOS.txt - -* Configuring GPAC - -GPAC's client configuration is documented in gpac/doc/configuration.html -MP4Box documentation is available online at http://gpac.sourceforge.net diff --git a/Makefile b/Makefile index 9849173..303a413 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,10 @@ # include config.mak +ifndef SRC_PATH +override SRC_PATH = . +endif + vpath %.c $(SRC_PATH) all: version @@ -12,6 +16,11 @@ ifneq ($(MP4BOX_STATIC),yes) $(MAKE) -C modules all endif + +config.mak: + ./configure + + GITREV_PATH:=$(SRC_PATH)/include/gpac/revision.h TAG:=$(shell git --git-dir=$(SRC_PATH)/.git describe --tags --abbrev=0 2> /dev/null) VERSION:=$(shell echo `git --git-dir=$(SRC_PATH)/.git describe --tags --long || echo "UNKNOWN"` | sed "s/^$(TAG)-//") @@ -41,7 +50,7 @@ mods: instmoz: $(MAKE) -C applications/osmozilla install - + depend: $(MAKE) -C src dep $(MAKE) -C applications dep @@ -56,16 +65,21 @@ distclean: $(MAKE) -C src distclean $(MAKE) -C applications distclean $(MAKE) -C modules distclean - rm -f config.mak config.h + rm -f config.mak config.h config.log @find . -type f -name '*.gcno*' -delete @find . -type f -name '*.gcda*' -delete @rm -f coverage.info 2> /dev/null + @rm -f bin/gcc/gm_*$(DYN_LIB_SUFFIX) 2> /dev/null + @rm -f bin/gcc/gf_*$(DYN_LIB_SUFFIX) 2> /dev/null -docs: - @cd $(SRC_PATH)/doc && doxygen +doc: + @cd $(SRC_PATH)/share/doc && doxygen + +man: + @cd $(SRC_PATH)/share/doc/man && MP4Box -genman && MP4Client -genman && gpac -genman test_suite: - @cd $(SRC_PATH)/tests && ./make_tests.sh + @cd $(SRC_PATH)/testsuite && ./make_tests.sh -precommit -p=0 lcov_clean: lcov --directory . --zerocounters @@ -73,8 +87,8 @@ lcov_clean: lcov_only: @echo "Generating lcov info in coverage.info" @rm -f ./gpac-conf-* > /dev/null - @lcov -q -capture --directory . --output-file all.info - @lcov --remove all.info /usr/* /usr/include/* /usr/local/include/* /usr/include/libkern/i386/* /usr/include/sys/_types/* /opt/* /opt/local/include/* /opt/local/include/mozjs185/* --output coverage.info + @lcov -q -capture --directory . --output-file all.info + @lcov --remove all.info '*/usr/*' '*/opt/*' '*/include/*' '*/validator/*' '*/quickjs/*' '*/jsmods/WebGLRenderingContextBase*' '*/utils/Remotery*' '*/utils/gzio*' --output coverage.info @rm all.info @echo "Purging lcov info" @cd src ; for dir in * ; do cd .. ; sed -i -- "s/$$dir\/$$dir\//$$dir\//g" coverage.info; cd src; done ; cd .. @@ -85,12 +99,12 @@ lcov: lcov_only @genhtml -q -o coverage coverage.info travis_tests: - @echo "Running tests" - @cd $(SRC_PATH)/tests && ./make_tests.sh -warn -sync-before + @echo "Running tests in $(SRC_PATH)/testsuite" + @cd $(SRC_PATH)/testsuite && ./make_tests.sh -precommit -p=0 travis_deploy: @echo "Deploying results" - @cd $(SRC_PATH)/tests && ./ghp_deploy.sh + @cd $(SRC_PATH)/testsuite && ./ghp_deploy.sh travis: travis_tests lcov travis_deploy @@ -98,176 +112,202 @@ dep: depend install: $(INSTALL) -d "$(DESTDIR)$(prefix)" - $(INSTALL) -d "$(DESTDIR)$(prefix)/$(libdir)" + + $(MAKE) install-lib + $(INSTALL) -d "$(DESTDIR)$(prefix)/bin" -ifeq ($(DISABLE_ISOFF), no) + $(INSTALL) $(INSTFLAGS) -m 755 bin/gcc/gpac$(EXE_SUFFIX) "$(DESTDIR)$(prefix)/bin" +ifeq ($(DISABLE_ISOFF),no) if [ -f bin/gcc/MP4Box$(EXE_SUFFIX) ] ; then \ $(INSTALL) $(INSTFLAGS) -m 755 bin/gcc/MP4Box$(EXE_SUFFIX) "$(DESTDIR)$(prefix)/bin" ; \ fi -ifneq ($(MP4BOX_STATIC), yes) - if [ -f bin/gcc/MP42TS$(EXE_SUFFIX) ] ; then \ - $(INSTALL) $(INSTFLAGS) -m 755 bin/gcc/MP42TS$(EXE_SUFFIX) "$(DESTDIR)$(prefix)/bin" ; \ - fi -ifneq ($(CONFIG_WIN32), yes) -ifneq ($(CONFIG_FFMPEG), no) -ifneq ($(DISABLE_CORE_TOOLS), yes) -ifneq ($(DISABLE_AV_PARSERS), yes) - if [ -f bin/gcc/DashCast$(EXE_SUFFIX)g ] ; then \ - $(INSTALL) $(INSTFLAGS) -m 755 bin/gcc/DashCast$(EXE_SUFFIX) "$(DESTDIR)$(prefix)/bin" ; \ - fi -endif -endif -endif -endif -endif endif -ifneq ($(MP4BOX_STATIC), yes) -ifeq ($(DISABLE_PLAYER), no) +ifneq ($(MP4BOX_STATIC),yes) +ifeq ($(DISABLE_PLAYER),no) if [ -f bin/gcc/MP4Client$(EXE_SUFFIX) ] ; then \ $(INSTALL) $(INSTFLAGS) -m 755 bin/gcc/MP4Client$(EXE_SUFFIX) "$(DESTDIR)$(prefix)/bin" ; \ fi endif endif - if [ -d $(DESTDIR)$(prefix)/$(libdir)/pkgconfig ] ; then \ - $(INSTALL) $(INSTFLAGS) -m 644 gpac.pc "$(DESTDIR)$(prefix)/$(libdir)/pkgconfig" ; \ - fi - $(INSTALL) -d "$(DESTDIR)$(moddir)" + $(INSTALL) -d "$(DESTDIR)$(prefix)/$(lib_dir)/$(moddir)" ifneq ($(MP4BOX_STATIC),yes) - $(INSTALL) bin/gcc/*$(DYN_LIB_SUFFIX) "$(DESTDIR)$(moddir)" - rm -f $(DESTDIR)$(moddir)/libgpac$(DYN_LIB_SUFFIX) - rm -f $(DESTDIR)$(moddir)/nposmozilla$(DYN_LIB_SUFFIX) - $(MAKE) installdylib + $(INSTALL) bin/gcc/gm_*$(DYN_LIB_SUFFIX) "$(DESTDIR)$(prefix)/$(lib_dir)/$(moddir)" || true + $(INSTALL) bin/gcc/gf_*$(DYN_LIB_SUFFIX) "$(DESTDIR)$(prefix)/$(lib_dir)/$(moddir)" || true +ifeq ($(CONFIG_OPENHEVC),yes) + cp -a bin/gcc/libopenhevc* $(DESTDIR)$(prefix)/$(lib_dir)/ || true +endif + endif - $(INSTALL) -d "$(DESTDIR)$(mandir)" - $(INSTALL) -d "$(DESTDIR)$(mandir)/man1" - $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/doc/man/mp4box.1 $(DESTDIR)$(mandir)/man1/ - $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/doc/man/mp4client.1 $(DESTDIR)$(mandir)/man1/ - $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/doc/man/gpac.1 $(DESTDIR)$(mandir)/man1/ + $(INSTALL) -d "$(DESTDIR)$(prefix)/$(man_dir)" + $(INSTALL) -d "$(DESTDIR)$(prefix)/$(man_dir)/man1" + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/doc/man/mp4box.1 $(DESTDIR)$(prefix)/$(man_dir)/man1/ + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/doc/man/mp4client.1 $(DESTDIR)$(prefix)/$(man_dir)/man1/ + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/doc/man/gpac.1 $(DESTDIR)$(prefix)/$(man_dir)/man1/ + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/doc/man/gpac-filters.1 $(DESTDIR)$(prefix)/$(man_dir)/man1/ $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac" - $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/doc/gpac.mp4 $(DESTDIR)$(prefix)/share/gpac/ + $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac/res" $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac/gui" - $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/gui/gui.bt "$(DESTDIR)$(prefix)/share/gpac/gui/" - $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/gui/gui.js "$(DESTDIR)$(prefix)/share/gpac/gui/" - $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/gui/gwlib.js "$(DESTDIR)$(prefix)/share/gpac/gui/" - $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/gui/mpegu-core.js "$(DESTDIR)$(prefix)/share/gpac/gui/" - $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/gui/webvtt-renderer.js "$(DESTDIR)$(prefix)/share/gpac/gui/" $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac/gui/icons" $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac/gui/extensions" - $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac/shaders/" + $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac/shaders" + $(INSTALL) -d "$(DESTDIR)$(prefix)/share/gpac/scripts" + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/res/gpac.mp4 $(DESTDIR)$(prefix)/share/gpac/res/ + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/res/gpac_cfg_test.mp4 $(DESTDIR)$(prefix)/share/gpac/res/ + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/res/gpac.png $(DESTDIR)$(prefix)/share/gpac/res/ + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/default.cfg $(DESTDIR)$(prefix)/share/gpac/ + +ifneq ($(CONFIG_DARWIN),yes) + $(INSTALL) -d "$(DESTDIR)$(prefix)/share/pixmaps" + $(INSTALL) -d "$(DESTDIR)$(prefix)/share/applications" + + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/res/gpac.png "$(DESTDIR)$(prefix)/share/pixmaps/" + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/gpac.desktop "$(DESTDIR)$(prefix)/share/applications/" +endif + + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/gui/gui.bt "$(DESTDIR)$(prefix)/share/gpac/gui/" + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/gui/gui.js "$(DESTDIR)$(prefix)/share/gpac/gui/" + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/gui/gwlib.js "$(DESTDIR)$(prefix)/share/gpac/gui/" + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/scripts/webvtt-renderer.js "$(DESTDIR)$(prefix)/share/gpac/scripts/" + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/share/scripts/ttml-renderer.js "$(DESTDIR)$(prefix)/share/gpac/scripts/" + + ifeq ($(CONFIG_DARWIN),yes) - cp $(SRC_PATH)/gui/icons/* "$(DESTDIR)$(prefix)/share/gpac/gui/icons/" - cp -R $(SRC_PATH)/gui/extensions/* "$(DESTDIR)$(prefix)/share/gpac/gui/extensions/" - cp $(SRC_PATH)/shaders/* "$(DESTDIR)$(prefix)/share/gpac/shaders/" + cp $(SRC_PATH)/share/gui/icons/* "$(DESTDIR)$(prefix)/share/gpac/gui/icons/" + cp -R $(SRC_PATH)/share/gui/extensions/* "$(DESTDIR)$(prefix)/share/gpac/gui/extensions/" + cp $(SRC_PATH)/share/shaders/* "$(DESTDIR)$(prefix)/share/gpac/shaders/" else - cp --no-preserve=mode,ownership,timestamp $(SRC_PATH)/gui/icons/* $(DESTDIR)$(prefix)/share/gpac/gui/icons/ - cp -R --no-preserve=mode,ownership,timestamp $(SRC_PATH)/gui/extensions/* $(DESTDIR)$(prefix)/share/gpac/gui/extensions/ - cp --no-preserve=mode,ownership,timestamp $(SRC_PATH)/shaders/* $(DESTDIR)$(prefix)/share/gpac/shaders/ + cp --no-preserve=mode,ownership,timestamp $(SRC_PATH)/share/gui/icons/* $(DESTDIR)$(prefix)/share/gpac/gui/icons/ + cp -R --no-preserve=mode,ownership,timestamp $(SRC_PATH)/share/gui/extensions/* $(DESTDIR)$(prefix)/share/gpac/gui/extensions/ + cp --no-preserve=mode,ownership,timestamp $(SRC_PATH)/share/shaders/* $(DESTDIR)$(prefix)/share/gpac/shaders/ endif lninstall: $(INSTALL) -d "$(DESTDIR)$(prefix)" - $(INSTALL) -d "$(DESTDIR)$(prefix)/$(libdir)" + $(INSTALL) -d "$(DESTDIR)$(prefix)/$(lib_dir)" $(INSTALL) -d "$(DESTDIR)$(prefix)/bin" -ifeq ($(DISABLE_ISOFF), no) + ln -sf $(BUILD_PATH)/bin/gcc/gpac$(EXE_SUFFIX) $(DESTDIR)$(prefix)/bin/gpac$(EXE_SUFFIX) +ifeq ($(DISABLE_ISOFF),no) ln -sf $(BUILD_PATH)/bin/gcc/MP4Box$(EXE_SUFFIX) $(DESTDIR)$(prefix)/bin/MP4Box$(EXE_SUFFIX) - ln -sf $(BUILD_PATH)/bin/gcc/MP42TS$(EXE_SUFFIX) $(DESTDIR)$(prefix)/bin/MP42TS$(EXE_SUFFIX) endif ifneq ($(MP4BOX_STATIC),yes) -ifeq ($(DISABLE_PLAYER), no) +ifeq ($(DISABLE_PLAYER),no) ln -sf $(BUILD_PATH)/bin/gcc/MP4Client$(EXE_SUFFIX) $(DESTDIR)$(prefix)/bin/MP4Client$(EXE_SUFFIX) endif -ifneq ($(CONFIG_WIN32), yes) -ifneq ($(CONFIG_FFMPEG), no) -ifneq ($(DISABLE_CORE_TOOLS), yes) -ifneq ($(DISABLE_AV_PARSERS), yes) - ln -sf $(BUILD_PATH)/bin/gcc/DashCast$(EXE_SUFFIX) $(DESTDIR)$(prefix)/bin/DashCast$(EXE_SUFFIX) -endif -endif -endif -endif endif ifeq ($(CONFIG_DARWIN),yes) - ln -s $(BUILD_PATH)/bin/gcc/libgpac$(DYN_LIB_SUFFIX) $(DESTDIR)$(prefix)/$(libdir)/libgpac$(DYN_LIB_SUFFIX).$(VERSION_MAJOR) - ln -sf $(DESTDIR)$(prefix)/$(libdir)/libgpac$(DYN_LIB_SUFFIX).$(VERSION_MAJOR) $(DESTDIR)$(prefix)/$(libdir)/libgpac$(DYN_LIB_SUFFIX) + ln -s $(BUILD_PATH)/bin/gcc/libgpac$(DYN_LIB_SUFFIX) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.$(VERSION_SONAME)$(DYN_LIB_SUFFIX) + ln -sf $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.$(VERSION_SONAME)$(DYN_LIB_SUFFIX) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.$(VERSION_MAJOR)$(DYN_LIB_SUFFIX) + ln -sf $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.$(VERSION_SONAME)$(DYN_LIB_SUFFIX) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac$(DYN_LIB_SUFFIX) else - ln -s $(BUILD_PATH)/bin/gcc/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(libdir)/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) - ln -sf $(DESTDIR)$(prefix)/$(libdir)/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(libdir)/libgpac.so.$(VERSION_MAJOR) - ln -sf $(DESTDIR)$(prefix)/$(libdir)/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(libdir)/libgpac.so + ln -s $(BUILD_PATH)/bin/gcc/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) + ln -sf $(DESTDIR)$(prefix)/$(lib_dir)/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.so.$(VERSION_MAJOR) + ln -sf $(DESTDIR)$(prefix)/$(lib_dir)/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.so + + ln -s $(SRC_PATH)/share/ $(DESTDIR)$(prefix)/share/gpac + ln -sf $(DESTDIR)$(prefix)/share/gpac/res/gpac.png $(DESTDIR)/usr/share/pixmaps/gpac.png + ln -sf $(SRC_PATH)/share/gpac.desktop $(DESTDIR)/usr/share/applications/ + ifeq ($(DESTDIR)$(prefix),$(prefix)) ldconfig || true endif + endif uninstall: $(MAKE) -C applications uninstall - rm -rf $(DESTDIR)$(moddir) - rm -rf $(DESTDIR)$(prefix)/$(libdir)/libgpac* -ifeq ($(CONFIG_WIN32),yes) - rm -rf "$(DESTDIR)$(prefix)/bin/libgpac*" -endif - rm -rf $(DESTDIR)$(prefix)/$(libdir)/pkgconfig/gpac.pc + $(MAKE) uninstall-lib + rm -rf $(DESTDIR)$(prefix)/$(lib_dir)/$(moddir) rm -rf $(DESTDIR)$(prefix)/bin/MP4Box rm -rf $(DESTDIR)$(prefix)/bin/MP4Client - rm -rf $(DESTDIR)$(prefix)/bin/MP42TS - rm -rf $(DESTDIR)$(prefix)/bin/DashCast - rm -rf $(DESTDIR)$(mandir)/man1/mp4box.1 - rm -rf $(DESTDIR)$(mandir)/man1/mp4client.1 - rm -rf $(DESTDIR)$(mandir)/man1/gpac.1 + rm -rf $(DESTDIR)$(prefix)/bin/gpac + rm -rf $(DESTDIR)$(prefix)/$(man_dir)/man1/mp4box.1 + rm -rf $(DESTDIR)$(prefix)/$(man_dir)/man1/mp4client.1 + rm -rf $(DESTDIR)$(prefix)/$(man_dir)/man1/gpac.1 + rm -rf $(DESTDIR)$(prefix)/$(man_dir)/man1/gpac-filters.1 rm -rf $(DESTDIR)$(prefix)/share/gpac - rm -rf $(DESTDIR)$(prefix)/include/gpac + rm -rf $(DESTDIR)$(prefix)/share/pixmaps/gpac.png + rm -rf $(DESTDIR)$(prefix)/share/applications/gpac.desktop + installdylib: ifneq ($(MP4BOX_STATIC),yes) + + $(INSTALL) -d "$(DESTDIR)$(prefix)/$(lib_dir)" + ifeq ($(CONFIG_WIN32),yes) - $(INSTALL) $(INSTFLAGS) -m 755 bin/gcc/libgpac.dll.a $(DESTDIR)$(prefix)/$(libdir) + $(INSTALL) $(INSTFLAGS) -m 755 bin/gcc/libgpac.dll.a $(DESTDIR)$(prefix)/$(lib_dir) $(INSTALL) $(INSTFLAGS) -m 755 bin/gcc/libgpac.dll $(DESTDIR)$(prefix)/bin else + ifeq ($(DEBUGBUILD),no) - $(STRIP) bin/gcc/libgpac$(DYN_LIB_SUFFIX) + $(STRIP) -S bin/gcc/libgpac$(DYN_LIB_SUFFIX) endif + ifeq ($(CONFIG_DARWIN),yes) - $(INSTALL) -m 755 bin/gcc/libgpac$(DYN_LIB_SUFFIX) $(DESTDIR)$(prefix)/$(libdir)/libgpac.$(VERSION)$(DYN_LIB_SUFFIX) - ln -sf libgpac.$(VERSION)$(DYN_LIB_SUFFIX) $(DESTDIR)$(prefix)/$(libdir)/libgpac$(DYN_LIB_SUFFIX) + $(INSTALL) -m 755 bin/gcc/libgpac$(DYN_LIB_SUFFIX) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.$(VERSION_SONAME)$(DYN_LIB_SUFFIX) + ln -sf libgpac.$(VERSION_SONAME)$(DYN_LIB_SUFFIX) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.$(VERSION_MAJOR)$(DYN_LIB_SUFFIX) + ln -sf libgpac.$(VERSION_SONAME)$(DYN_LIB_SUFFIX) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac$(DYN_LIB_SUFFIX) else - $(INSTALL) $(INSTFLAGS) -m 755 bin/gcc/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(libdir)/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) - ln -sf libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(libdir)/libgpac.so.$(VERSION_MAJOR) - ln -sf libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(libdir)/libgpac.so + $(INSTALL) $(INSTFLAGS) -m 755 bin/gcc/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) + ln -sf libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.so.$(VERSION_MAJOR) + ln -sf libgpac$(DYN_LIB_SUFFIX).$(VERSION_SONAME) $(DESTDIR)$(prefix)/$(lib_dir)/libgpac.so ifeq ($(DESTDIR)$(prefix),$(prefix)) ldconfig || true endif endif + +endif + endif + + +uninstalldylib: + rm -rf $(DESTDIR)$(prefix)/$(lib_dir)/libgpac* +ifeq ($(CONFIG_WIN32),yes) + rm -rf "$(DESTDIR)$(prefix)/bin/libgpac*" endif install-lib: - mkdir -p "$(DESTDIR)$(prefix)/include/gpac" + $(INSTALL) -d "$(DESTDIR)$(prefix)/include/gpac" + $(INSTALL) -d "$(DESTDIR)$(prefix)/include/gpac/internal" + $(INSTALL) -d "$(DESTDIR)$(prefix)/include/gpac/modules" + $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/include/gpac/*.h "$(DESTDIR)$(prefix)/include/gpac" - mkdir -p "$(DESTDIR)$(prefix)/include/gpac/internal" $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/include/gpac/internal/*.h "$(DESTDIR)$(prefix)/include/gpac/internal" - mkdir -p "$(DESTDIR)$(prefix)/include/gpac/modules" $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/include/gpac/modules/*.h "$(DESTDIR)$(prefix)/include/gpac/modules" - $(INSTALL) $(INSTFLAGS) -m 644 config.h "$(DESTDIR)$(prefix)/include/gpac/configuration.h" -ifeq ($(GPAC_ENST), yes) - mkdir -p "$(DESTDIR)$(prefix)/include/gpac/enst" + + $(INSTALL) $(INSTFLAGS) -m 644 config.h "$(DESTDIR)$(prefix)/include/gpac/configuration.h" || true + +ifeq ($(GPAC_ENST),yes) + $(INSTALL) -d "$(DESTDIR)$(prefix)/include/gpac/enst" $(INSTALL) $(INSTFLAGS) -m 644 $(SRC_PATH)/include/gpac/enst/*.h "$(DESTDIR)$(prefix)/include/gpac/enst" endif - mkdir -p "$(DESTDIR)$(prefix)/$(libdir)" - $(INSTALL) $(INSTFLAGS) -m 644 "./bin/gcc/libgpac_static.a" "$(DESTDIR)$(prefix)/$(libdir)" + + $(INSTALL) -d "$(DESTDIR)$(prefix)/$(lib_dir)" + $(INSTALL) $(INSTFLAGS) -m 644 "./bin/gcc/libgpac_static.a" "$(DESTDIR)$(prefix)/$(lib_dir)" || true + + $(INSTALL) -d $(DESTDIR)$(prefix)/$(lib_dir)/pkgconfig + $(INSTALL) $(INSTFLAGS) -m 644 gpac.pc "$(DESTDIR)$(prefix)/$(lib_dir)/pkgconfig" + $(MAKE) installdylib + uninstall-lib: - rm -rf "$(prefix)/include/gpac/internal" - rm -rf "$(prefix)/include/gpac/modules" - rm -rf "$(prefix)/include/gpac/enst" - rm -rf "$(prefix)/include/gpac" + rm -rf "$(DESTDIR)$(prefix)/include/gpac/internal" + rm -rf "$(DESTDIR)$(prefix)/include/gpac/modules" + rm -rf "$(DESTDIR)$(prefix)/include/gpac/enst" + rm -rf "$(DESTDIR)$(prefix)/include/gpac" + rm -f "$(DESTDIR)$(prefix)/$(lib_dir)/libgpac_static.a" + rm -f "$(DESTDIR)$(prefix)/$(lib_dir)/pkgconfig/gpac.pc" + $(MAKE) uninstalldylib ifeq ($(CONFIG_DARWIN),yes) dmg: - @if [ ! -z "$(shell git diff FETCH_HEAD)" ]; then \ - echo "Local revision and remote revision are not congruent; you may have local commit."; \ - echo "Please consider pushing your commit before generating an installer"; \ - exit 1; \ - fi +# @if [ ! -z "$(shell git diff FETCH_HEAD)" ]; then \ +# echo "Local revision and remote revision are not congruent; you may have local commit."; \ +# echo "Please consider pushing your commit before generating an installer"; \ +# exit 1; \ +# fi rm "bin/gcc/MP4Client" $(MAKE) -C applications/mp4client ./mkdmg.sh $(arch) @@ -275,14 +315,10 @@ endif ifeq ($(CONFIG_LINUX),yes) deb: - @if [ ! -z "$(shell git diff FETCH_HEAD)" ]; then \ - echo "Local revision and remote revision are not congruent; you may have local commit."; \ - echo "Please consider pushing your commit before generating an installer"; \ - exit 1; \ - fi git checkout -- debian/changelog fakeroot debian/rules clean - sed -i "s/-DEV/-DEV-rev$(VERSION)-$(BRANCH)/" debian/changelog + # add version to changelog for final filename + sed -i -r "s/^(\w+) \(([0-9\.]+)(-[A-Z]+)?\)/\1 (\2\3-rev$(VERSION)-$(BRANCH))/" debian/changelog fakeroot debian/rules configure fakeroot debian/rules binary rm -rf debian/ @@ -298,27 +334,29 @@ help: @echo "modules: builds modules only" @echo "instmoz: build and local install of osmozilla" @echo "sggen: builds scene graph generators" - @echo + @echo @echo "clean: clean src repository" @echo "distclean: clean src repository and host config file" - @echo + @echo @echo "install: install applications and modules on system" @echo "uninstall: uninstall applications and modules" ifeq ($(CONFIG_DARWIN),yes) @echo "dmg: creates DMG package file for OSX" endif ifeq ($(CONFIG_LINUX),yes) - @echo "deb: creates DEB package file for debian based systems" + @echo "deb: creates DEB package file for debian based systems" endif - @echo + @echo @echo "install-lib: install gpac library (dyn and static) and headers , and " @echo "uninstall-lib: uninstall gpac library (dyn and static) and headers" @echo - @echo "tests: run all tests. For more info, check gpac/regression_tests/test_suite_make.sh -h" + @echo "test_suite: run all tests. For more info, check https://github.com/gpac/testsuite" + @echo + @echo "doc: build libgpac documentation in gpac/doc" + @echo "man: build gpac man files in gpac/doc/man (must have latest build binaries installed)" @echo @echo "lcov: generate lcov files" @echo "lcov_clean: clean all lcov/gcov files" - @echo - @echo "docs: build libgpac documentation in gpac/doc" + -include .depend diff --git a/README.md b/README.md index 3ced437..b9c4a4a 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,134 @@ -[![Build Status](https://travis-ci.org/gpac/gpac.svg?branch=master)](https://travis-ci.org/gpac/gpac) -[![codecov.io](https://codecov.io/github/gpac/gpac/coverage.svg?branch=master)](https://codecov.io/github/gpac/gpac?branch=master) +[![Build Status](https://tests.gpac.io/testres/badge/build/ubuntu64)](https://buildbot.gpac.io/#/grid?branch=master) +[![Tests](https://tests.gpac.io/testres/badge/tests/linux64)](https://tests.gpac.io/) -README for GPAC version 0.7.1 +[![Build Status](https://tests.gpac.io/testres/badge/build/ubuntu32)](https://buildbot.gpac.io/#/grid?branch=master) +[![Tests](https://tests.gpac.io/testres/badge/tests/linux32)](https://tests.gpac.io/) -GPAC is a multimedia framework oriented towards rich media and distributed under the LGPL license (see COPYING). -GPAC supports many multimedia formats, from simple audiovisual containers (avi, mov, mpg) to complex -presentation formats (MPEG-4 Systems, SVG Tiny 1.2, VRML/X3D). GPAC supports scripting of presentation for MPEG4/VRML/X3D through -mozilla SpiderMonkey javascript engine. -GPAC currently supports local playback, http progressive download, Adaptive HTTP Streaming (MPEG-DASH, HLS), RTP/RTSP streaming over UDP (unicast or multicast) or TCP and TS demuxing (from file, IP or DVB4Linux). -GPAC also features MP4Box, a multimedia swiss-army knife for the prompt. +[![Build Status](https://tests.gpac.io/testres/badge/build/windows64)](https://buildbot.gpac.io/#/grid?branch=master) +[![Tests](https://tests.gpac.io/testres/badge/tests/win64)](https://tests.gpac.io/) -For compilation and installation instruction, check INSTALLME file +[![Build Status](https://tests.gpac.io/testres/badge/build/windows32)](https://buildbot.gpac.io/#/grid?branch=master) +[![Tests](https://tests.gpac.io/testres/badge/tests/win32)](https://tests.gpac.io/) -For GPAC configuration instruction, check gpac/doc/configuration.html or gpac/doc/man/gpac.1 (man gpac when installed) +[![Build Status](https://tests.gpac.io/testres/badge/build/macos)](https://buildbot.gpac.io/#/grid?branch=master) +[![Tests](https://tests.gpac.io/testres/badge/tests/macos)](https://tests.gpac.io/) -For more information, visit the GPAC website: - http://gpac.io +[![Build Status](https://tests.gpac.io/testres/badge/build/ios)](https://buildbot.gpac.io/#/grid?branch=master) +[![Build Status](https://tests.gpac.io/testres/badge/build/android)](https://buildbot.gpac.io/#/grid?branch=master) - - +[![Coverage](https://tests.gpac.io/testres/badge/cov/linux64?branch=master)](https://tests.gpac.io/testres/) +[![Coverage](https://tests.gpac.io/testres/badge/covfn/linux64?branch=master)](https://tests.gpac.io/testres/) + +![License](https://img.shields.io/badge/license-LGPL-blue.svg) +[![OpenHub](https://www.openhub.net/p/gpac/widgets/project_thin_badge.gif)](https://www.openhub.net/p/gpac) + + +# GPAC Introduction +Current version: 1.0.1 + +GPAC is an open-source multimedia framework focused on modularity and standards compliance. +GPAC provides tools to process, inspect, package, stream playback and interact with media content. Such content can be any combination of audio, video, subtitles, metadata, scalable graphics, encrypted media, 2D/3D graphics and ECMAScript. +GPAC is best-known for its wide MP4 capabilities and is popular among video enthusiasts, academic researchers, standardization bodies, and professional broadcasters. + +For more information, visit [GPAC website](http://gpac.io) + +GPAC is distributed under the LGPL v2.1 or later, and is also available, for most of it, under a [commercial license](https://www.gpac-licensing.com). + +Please cite our work in your research: +- GPAC Filters: https://doi.org/10.1145/3339825.3394929 +- GPAC: https://doi.org/10.1145/1291233.1291452 + +# Features + +GPAC can process, analyse, package, stream, encode, decode and playback a wide variety of contents. Selected feature list: +- Audio: MPEG audio (mp1/2/3, aac), AC3, E-AC3, Opus, FLAC, … +- Video: MPEG 1 / 2 / 4 (H264/AVC) / H (HEVC), AV1, VP9, Theora, ... +- Subtitles: WebVTT, TTML (full, EBU-TTD, …), 3GPP/Apple Timed Text, … +- Encryption: CENC, PIFF, ISMA, OMA, ... +- Containers: MP4/fMP4/CMAF/Quicktime MOV/ProRes MOV, AVI, MPG, OGG, MKV, ... +- Streaming: MPEG-2 Transport Stream, RTP, RTSP, HTTP, Apple HLS, MPEG-DASH, ATSC 3.0 ROUTE, ... +- Supported IOs: local files, pipes, UDP/TCP, HTTP(S), custom IO +- Presentation formats: MPEG-4 BIFS, SVG Tiny 1.2, VRML/X3D +- JS scripting through QuickJS for both SVG/BIFS/VRML and extending GPAC framework tools +- 3D support (360 videos, WebGL JS filters…) +- Inputs: microphone, camera, desktop grabbing +- Highly configurable media processing pipeline + +Features are encapsulated in processing modules called filters: +- to get the full list of available features, you can run the command line `gpac -h filters` or check [filters' wiki](https://github.com/gpac/gpac/wiki/Filters). +- to get the full list of playback features, check [our wiki](https://github.com/gpac/gpac/wiki/Player-Features). + + +# Tools + +## MP4Box +MP4Box is a multi-purpose MP4 file manipulation for the prompt, featuring media importing and extracting, file inspection, DASH segmentation, RTP hinting, ... See `MP4Box -h`, `man MP4Box` or [our wiki](https://wiki.gpac.io/MP4Box-Introduction). + + +## gpac +As of version 0.9.0, GPAC includes a filter engine in charge of stream management and used by most applications in GPAC - [read this post](https://wiki.gpac.io/Rearchitecture) for more dicussion on how this impacts MP4Box and MP4Client. +The gpac application is a direct interface to the filter engine of GPAC, allowing any combinaison of filters not enabled by other applications. See `gpac -h`, `man gpac`, `man gpac-filters` or [our wiki](https://wiki.gpac.io/Filters) for more details. + +## MP4Client +MP4Client is a media player built upon libgpac, featuring a rich media interactive composition engine with MPEG-4 BIFS, SVG, VRML/X3D support. +For GPAC configuration instruction, check `MP4Client -h` , `man MP4Client` or [our wiki](https://wiki.gpac.io/mp4client). + + + +# Getting started +## Download +Stable and nightly builds installers for Windows, Linux, OSX, Android, iOS are available on [gpac.io](https://gpac.wp.imt.fr/downloads/). + +If you want to compile GPAC yourself, please follow the instructions in the [build section](https://wiki.gpac.io/Build-Introduction) of our wiki. + +## Documentation +The general GPAC framework documentation is available on [wiki.gpac.io](https://wiki.gpac.io), including [HowTos](https://github.com/gpac/gpac/wiki/Howtos). + +GPAC tools are mostly wrappers around an underlying library called libgpac which can easily be embedded in your projects. The libgpac developer documentation is available at [doxygen.gpac.io](https://doxygen.gpac.io), including documentation of [JS APIs](https://doxygen.gpac.io/group__jsapi__grp.html). + + +## Testing +GPAC has a test suite exercicing most features of the framework. The test suite is in a separate repository [https://github.com/gpac/testsuite/](https://github.com/gpac/testsuite/), but is available as a submodule of the GPAC main repository. To initialize the testsuite submodule, do `git submodule update --init`. + +For more details on the test suite, read [this page](https://github.com/gpac/gpac/wiki/GPAC_tests) and check the [testsuite readme](https://github.com/gpac/testsuite). + +Per-commit [build](https://buildbot.gpac.io/) and [tests results](https://tests.gpac.io) are available. + + +## Support +Please use [github](https://github.com/gpac/gpac/issues) for feature requests and bug reports. When filing a request there, please tag it as _feature-request_. + +## Contributing +A complex project like GPAC wouldn’t exist and persist without the support of its community. Please contribute: a nice message, supporting us in our communication, reporting issues when you see them… any gesture, even the smallest ones, counts. + +If you use GPAC in your published research, ! _please cite_ ! using +- [this paper](https://dl.acm.org/doi/abs/10.1145/3339825.3394929) for recent versions (0.9 or above) +- [this paper](https://dl.acm.org/doi/abs/10.1145/1291233.1291452) for legacy versions (0.8 or below). + +If you want to contribute to GPAC, you can find ideas at [GSoC page](https://gpac.wp.imt.fr/jobs/google-summer-of-code-ideas/) or look for ‘good first issue’ on [github](https://github.com/gpac/gpac/issues). In any doubt please feel free to [contact us](mailto:contact@gpac.io). + +# Team +GPAC is brought to you by an experienced team of developers with a wide track-record on media processing. + +The project is mainly developed at [Telecom Paris](https://www.telecom-paris.fr/), in the [MultiMedia group](http://www.tsi.telecom-paristech.fr/mm/), with the help of many [great contributors](https://github.com/gpac/gpac/graphs/contributors) + +GPAC has a peculiar story: started as a startup in NYC, GPAC gained traction from research and a nascent multimedia community as it was open-sourced in 2003. Since then we have never stopped transforming GPAC into a useful and up-to-date project, with many industrial R&D collaborations and a community of tens of thousands of users. This makes GPAC one of the few open-source multimedia projects that gathers so much diversity. + + +# Roadmap +Users are encouraged to use the latest tag or the master branch. + +The previous v0.8.X release (the last one using the legacy architecture) is LTS until 30/06/2021. Important bug fixes will be backported but new features won’t. API compatibility between both versions should make the migration easy. If not please [file a bug](https://github.com/gpac/gpac/issues). + +## Ongoing tasks and bugs +Please use [github](https://github.com/gpac/gpac/issues) for feature requests and bug reports. When filing a request there, please tag it as feature-request. + +## V1.1.0 +Targets: +- [ ] add kvazaar/other encoders support? +- [ ] improve remotery support +- [ ] more JS filters +- [ ] filters scriptable through other languages (python) ? +- [ ] move input sensors to filter ? +- [ ] fixed features disabled during rearchitecture or drop them (FILTER_FIXME macro) +- [ ] move Android client to filters diff --git a/TODO b/TODO deleted file mode 100644 index c68a2f9..0000000 --- a/TODO +++ /dev/null @@ -1,3 +0,0 @@ -cf https://github.com/gpac/gpac/issues for ongoing tasks and bugs. - -When filing a request there, please tag it as feature-request. diff --git a/applications/Makefile b/applications/Makefile index e60a811..935e231 100644 --- a/applications/Makefile +++ b/applications/Makefile @@ -1,31 +1,17 @@ include ../config.mak -APPDIRS= +APPDIRS=gpac ifeq ($(MP4BOX_STATIC),yes) APPDIRS+=mp4box else -ifeq ($(DISABLE_PLAYER), no) +ifeq ($(DISABLE_PLAYER),no) APPDIRS+=mp4client endif -ifeq ($(DISABLE_ISOFF), no) +ifeq ($(DISABLE_ISOFF),no) APPDIRS+=mp4box -ifeq ($(DISABLE_M2TS_MUX), no) -APPDIRS+=mp42ts -endif - -ifneq ($(CONFIG_WIN32), yes) -ifneq ($(CONFIG_FFMPEG), no) -ifneq ($(DISABLE_CORE_TOOLS), yes) -ifneq ($(DISABLE_AV_PARSERS), yes) -APPDIRS+=dashcast -endif -endif -endif -endif - endif @@ -38,7 +24,7 @@ else endif #disable due to version incompatibilities -ifeq ($(USE_WXWIDGETS), yes) +ifeq ($(USE_WXWIDGETS),yes) #APPDIRS+=osmo4_wx #V4STUDIODIR=V4Studio #INSTDIRS+=osmo4_wx diff --git a/applications/mp42ts/Makefile b/applications/deprecated/mp42ts/Makefile similarity index 80% rename from applications/mp42ts/Makefile rename to applications/deprecated/mp42ts/Makefile index 4565894..b069bf6 100644 --- a/applications/mp42ts/Makefile +++ b/applications/deprecated/mp42ts/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/applications/mp42ts CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif @@ -28,7 +28,7 @@ ifeq ($(STATICBUILD),yes) include ../../static.mak #FIXME we have to disable AAC+bifs support in mp42ts since it reuses things from aac_in already in libgpac ... -ifeq ($(STATIC_MODULES), yes) +ifeq ($(STATIC_MODULES),yes) CFLAGS+=-DGPAC_DISABLE_PLAYER endif @@ -36,9 +36,15 @@ LINKFLAGS+=-lgpac_static LINKFLAGS+= $(GPAC_SH_FLAGS) LINKFLAGS+=$(EXTRALIBS) else -LINKFLAGS+=-lgpac +LINKFLAGS+=-lgpac +ifeq ($(CONFIG_DARWIN),yes) +#LINKFLAGS+= -Wl,-rpath,'@loader_path' +else +LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath-link,../../bin/gcc +endif endif + #common objs - insert after ../static if any to overwrite list of objects OBJS= main.o diff --git a/applications/mp42ts/main.c b/applications/deprecated/mp42ts/main.c similarity index 94% rename from applications/mp42ts/main.c rename to applications/deprecated/mp42ts/main.c index 90664d7..a1465fb 100644 --- a/applications/mp42ts/main.c +++ b/applications/deprecated/mp42ts/main.c @@ -27,6 +27,7 @@ #include #include #include +#include #ifndef GPAC_DISABLE_STREAMING #include @@ -48,6 +49,8 @@ #define MP42TS_PRINT_TIME_MS 500 /*refresh printed info every CLOCK_REFRESH ms*/ #define MP42TS_VIDEO_FREQ 1000 /*meant to send AVC IDR only every CLOCK_REFRESH ms*/ +#define GPAC_DISABLE_PLAYER + s32 temi_id_1 = -1; s32 temi_id_2 = -1; @@ -55,21 +58,20 @@ s32 temi_id_2 = -1; u32 temi_url_insertion_delay = 1000; u32 temi_offset = 0; Bool temi_disable_loop = GF_FALSE; + +Double temi_period=0; +Bool request_temi_toggle = GF_FALSE; +Bool temi_on = GF_TRUE; +Bool temi_single_toggle = GF_FALSE; +u64 temi_period_last_dts = 0; FILE *logfile = NULL; -static void on_gpac_log(void *cbk, GF_LOG_Level ll, GF_LOG_Tool lm, const char *fmt, va_list list) -{ - FILE *logs = (FILE*)cbk; - vfprintf(logs, fmt, list); - fflush(logs); -} +//no longer supported for filters +#define GPAC_DISABLE_PLAYER + -static GFINLINE void usage() +static void usage() { - fprintf(stderr, "GPAC version " GPAC_FULL_VERSION "\n" - "GPAC Copyright (c) Telecom ParisTech 2000-2014\n" - "GPAC Configuration: " GPAC_CONFIGURATION "\n" - "Features: %s\n\n", gpac_features()); fprintf(stderr, "mp42ts [options]\n" "\n" "Inputs:\n" @@ -82,6 +84,8 @@ static GFINLINE void usage() " All sources with the same ID will be added to the same program\n" "name=STR program name, as used in DVB service description table\n" "provider=STR provider name, as used in DVB service description table\n" + "disc the first packet of each stream will have the discontinuity marker set\n" + "pmt=N sets version number of the PMT\n" "\n" "-prog filename same as -src filename\n" @@ -143,9 +147,13 @@ static GFINLINE void usage() "-mem-track enables memory tracker\n" "-mem-track-stack enables memory tracker stack dumping\n" #endif - "-logs set log tools and levels, formatted as a ':'-separated list of toolX[:toolZ]@levelX\n" "-h or -help print this screen\n" + "-hc print libgpac options\n" "\n" + "GPAC version %s\n" + "(c) Telecom ParisTech 2000-2018 - Licence LGPL v2\n" + "GPAC Configuration: " GPAC_CONFIGURATION "\n" + "Features: %s %s\n\n", gf_gpac_version(), gf_enabled_features(), gf_disabled_features() ); } @@ -181,6 +189,8 @@ typedef struct char provider_name[20]; u32 ID; Bool is_not_program_declaration; + Bool set_disc; + u32 pmt_version; Double last_ntp; } M2TSSource; @@ -251,13 +261,13 @@ static u32 format_af_descriptor(char *af_data, u32 timeline_id, u64 timecode, u3 { u32 res; u32 len; - u32 last_time; + u32 last_time=0; GF_BitStream *bs = gf_bs_new(af_data, 188, GF_BITSTREAM_WRITE); if (ntp) { last_time = 1000*(ntp>>32); last_time += 1000*(ntp&0xFFFFFFFF)/0xFFFFFFFF; - } else { + } else if (timescale) { last_time = (u32) (1000*timecode/timescale); } if (temi_url && (!*last_url_time || (last_time - *last_url_time + 1 >= temi_url_insertion_delay)) ) { @@ -273,7 +283,7 @@ static u32 format_af_descriptor(char *af_data, u32 timeline_id, u64 timecode, u3 gf_bs_write_int(bs, 0xFF, 5); //reserved gf_bs_write_int(bs, timeline_id, 7); //timeline_id - if (strlen(temi_url)) { + if (temi_url) { char *url = (char *)temi_url; if (!strnicmp(temi_url, "http://", 7)) { gf_bs_write_int(bs, 1, 8); //url_scheme @@ -354,13 +364,15 @@ static GF_Err mp4_input_ctrl(GF_ESInterface *ifce, u32 act_type, void *param) memset(&pck, 0, sizeof(GF_ESIPacket)); pck.flags = GF_ESI_DATA_AU_START | GF_ESI_DATA_HAS_CTS; - if (priv->sample->IsRAP) pck.flags |= GF_ESI_DATA_AU_RAP; + if (priv->sample->IsRAP) pck.sap_type = priv->sample->IsRAP; pck.cts = priv->sample->DTS + priv->ts_offset; if (priv->is_repeat) pck.flags |= GF_ESI_DATA_REPEAT; if (priv->timeline_id) { + Bool deactivate_temi=GF_FALSE; u64 ntp=0; u64 tc = priv->sample->DTS + priv->sample->CTS_Offset + priv->cts_dts_shift; + Bool insert_temi=GF_FALSE; if (temi_disable_loop) { tc += priv->ts_offset; } @@ -376,8 +388,43 @@ static GF_Err mp4_input_ctrl(GF_ESInterface *ifce, u32 act_type, void *param) ntp <<= 32; ntp |= frac; } - pck.mpeg2_af_descriptors_size = format_af_descriptor(af_data, priv->timeline_id - 1, tc, ifce->timescale, ntp, priv->temi_url, &priv->last_temi_url); - pck.mpeg2_af_descriptors = af_data; + if (!temi_period) { + //toggle temi at RAP POINTS ONLY + if (request_temi_toggle && priv->sample->IsRAP) { + temi_on = !temi_on; + if (!temi_on) { + deactivate_temi = GF_TRUE; + } + fprintf(stderr, "Turning TEMI %st at DTS "LLU" (%g sec)\n", temi_on ? "on" : "off" , priv->sample->DTS, ((Double)priv->sample->DTS)/ifce->timescale); + request_temi_toggle = GF_FALSE; + } + insert_temi = temi_on; + } else { + + if (!temi_on) { + if (priv->sample->IsRAP && ((priv->sample->DTS - temi_period_last_dts) >= temi_period * ifce->timescale)) { + temi_on = GF_TRUE; + temi_period_last_dts = priv->sample->DTS; + fprintf(stderr, "Turning TEMI on at DTS "LLU" (%g sec)\n", priv->sample->DTS, ((Double)priv->sample->DTS)/ifce->timescale); + } + } else { + if (!temi_single_toggle && priv->sample->IsRAP && ((priv->sample->DTS - temi_period_last_dts) >= temi_period * ifce->timescale)) { + temi_on = GF_FALSE; + temi_period_last_dts = priv->sample->DTS; + fprintf(stderr, "Turning TEMI off at DTS "LLU" (%g sec)\n", priv->sample->DTS, ((Double)priv->sample->DTS)/ifce->timescale); + deactivate_temi = GF_TRUE; + } + } + insert_temi = temi_on; + } + + if (insert_temi) { + pck.mpeg2_af_descriptors_size = format_af_descriptor(af_data, priv->timeline_id - 1, tc, ifce->timescale, ntp, priv->temi_url, &priv->last_temi_url); + pck.mpeg2_af_descriptors = af_data; + } else if (deactivate_temi) { + pck.mpeg2_af_descriptors_size = format_af_descriptor(af_data, priv->timeline_id - 1, 0, 0, 0, "", &priv->last_temi_url); + pck.mpeg2_af_descriptors = af_data; + } } if (priv->nb_repeat_last) { @@ -389,7 +436,7 @@ static GF_Err mp4_input_ctrl(GF_ESInterface *ifce, u32 act_type, void *param) pck.cts += + priv->cts_dts_shift; pck.flags |= GF_ESI_DATA_HAS_DTS; } - + if (priv->sample->CTS_Offset) { pck.cts += priv->sample->CTS_Offset; pck.flags |= GF_ESI_DATA_HAS_DTS; @@ -445,7 +492,7 @@ static GF_Err mp4_input_ctrl(GF_ESInterface *ifce, u32 act_type, void *param) if (!priv->source->real_time && !priv->is_repeat) { priv->source->samples_done++; - gf_set_progress("Converting to MPEG-2 TS", priv->source->samples_done, priv->source->samples_count); + gf_set_progress("MPEG-2 TS Muxing", priv->source->samples_done, priv->source->samples_count); } if (priv->sample_number==priv->sample_count) { @@ -521,36 +568,36 @@ static void fill_isom_es_ifce(M2TSSource *source, GF_ESInterface *ifce, GF_ISOFi memset(ifce, 0, sizeof(GF_ESInterface)); ifce->stream_id = gf_isom_get_track_id(mp4, track_num); - esd = gf_media_map_esd(mp4, track_num); + esd = gf_media_map_esd(mp4, track_num, 0); if (esd) { ifce->stream_type = esd->decoderConfig->streamType; ifce->object_type_indication = esd->decoderConfig->objectTypeIndication; if (esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->dataLength) { switch (esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_AUDIO_AAC_MPEG4: - case GPAC_OTI_AUDIO_AAC_MPEG2_MP: - case GPAC_OTI_AUDIO_AAC_MPEG2_LCP: - case GPAC_OTI_AUDIO_AAC_MPEG2_SSRP: - case GPAC_OTI_VIDEO_MPEG4_PART2: + case GF_CODECID_AAC_MPEG4: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + case GF_CODECID_MPEG4_PART2: ifce->decoder_config = (char *)gf_malloc(sizeof(char)*esd->decoderConfig->decoderSpecificInfo->dataLength); ifce->decoder_config_size = esd->decoderConfig->decoderSpecificInfo->dataLength; memcpy(ifce->decoder_config, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength); - if (esd->decoderConfig->objectTypeIndication == GPAC_OTI_VIDEO_MPEG4_PART2) { + if (esd->decoderConfig->objectTypeIndication == GF_CODECID_MPEG4_PART2) { priv->dsi = (char *)gf_malloc(sizeof(char)*esd->decoderConfig->decoderSpecificInfo->dataLength); priv->dsi_size = esd->decoderConfig->decoderSpecificInfo->dataLength; memcpy(priv->dsi, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength); } break; - case GPAC_OTI_VIDEO_HEVC: - case GPAC_OTI_VIDEO_LHVC: + case GF_CODECID_HEVC: + case GF_CODECID_LHVC: is_hevc=GF_TRUE; - case GPAC_OTI_VIDEO_AVC: - case GPAC_OTI_VIDEO_SVC: - case GPAC_OTI_VIDEO_MVC: + case GF_CODECID_AVC: + case GF_CODECID_SVC: + case GF_CODECID_MVC: gf_isom_set_nalu_extract_mode(mp4, track_num, GF_ISOM_NALU_EXTRACT_LAYER_ONLY | GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG | GF_ISOM_NALU_EXTRACT_ANNEXB_FLAG | GF_ISOM_NALU_EXTRACT_VDRD_FLAG); break; - case GPAC_OTI_SCENE_VTT_MP4: + case GF_CODECID_WEBVTT: ifce->decoder_config = (char *)gf_malloc(sizeof(char)*esd->decoderConfig->decoderSpecificInfo->dataLength); ifce->decoder_config_size = esd->decoderConfig->decoderSpecificInfo->dataLength; memcpy(ifce->decoder_config, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength); @@ -572,12 +619,11 @@ static void fill_isom_es_ifce(M2TSSource *source, GF_ESInterface *ifce, GF_ISOFi ifce->timescale = gf_isom_get_media_timescale(mp4, track_num); ifce->duration = gf_isom_get_media_timescale(mp4, track_num); avg_rate = gf_isom_get_media_data_size(mp4, track_num); + if (!avg_rate) return; avg_rate *= ifce->timescale * 8; if (0!=(duration=gf_isom_get_media_duration(mp4, track_num))) avg_rate /= duration; - if (gf_isom_has_time_offset(mp4, track_num)) ifce->caps |= GF_ESI_SIGNAL_DTS; - ifce->bit_rate = (u32) avg_rate; ifce->duration = (Double) (s64) gf_isom_get_media_duration(mp4, track_num); ifce->duration /= ifce->timescale; @@ -587,7 +633,7 @@ static void fill_isom_es_ifce(M2TSSource *source, GF_ESInterface *ifce, GF_ISOFi GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Failed to allocate interface SLConfig\n")); return; } - + ifce->sl_config->tag = GF_ODF_SLC_TAG; ifce->sl_config->useAccessUnitStartFlag = 1; ifce->sl_config->useAccessUnitEndFlag = 1; @@ -790,9 +836,9 @@ static void rtp_sl_packet_cbk(void *udta, char *payload, u32 size, GF_SLHeader * rtp->pck.flags = 0; if (hdr->compositionTimeStampFlag) rtp->pck.flags |= GF_ESI_DATA_HAS_CTS; if (hdr->decodingTimeStampFlag) rtp->pck.flags |= GF_ESI_DATA_HAS_DTS; - if (hdr->randomAccessPointFlag) rtp->pck.flags |= GF_ESI_DATA_AU_RAP; if (hdr->accessUnitStartFlag) rtp->pck.flags |= GF_ESI_DATA_AU_START; if (hdr->accessUnitEndFlag) rtp->pck.flags |= GF_ESI_DATA_AU_END; + if (hdr->randomAccessPointFlag) rtp->pck.sap_type = 1; if (rtp->use_carousel) { if ((hdr->AU_sequenceNumber==rtp->au_sn) && hdr->randomAccessPointFlag) rtp->pck.flags |= GF_ESI_DATA_REPEAT; @@ -897,23 +943,23 @@ static void fill_rtp_es_ifce(GF_ESInterface *ifce, GF_SDPMedia *media, GF_SDPInf return; } /*setup channel*/ - gf_rtp_setup_payload(rtp->rtp_ch, map); + gf_rtp_setup_payload(rtp->rtp_ch, map->PayloadType, map->ClockRate); ifce->input_udta = rtp; ifce->input_ctrl = rtp_input_ctrl; rtp->ifce = ifce; rtp->source = source; - ifce->object_type_indication = rtp->depacketizer->sl_map.ObjectTypeIndication; + ifce->object_type_indication = rtp->depacketizer->sl_map.CodecID; ifce->stream_type = rtp->depacketizer->sl_map.StreamType; ifce->timescale = gf_rtp_get_clockrate(rtp->rtp_ch); if (rtp->depacketizer->sl_map.config) { switch (ifce->object_type_indication) { - case GPAC_OTI_VIDEO_MPEG4_PART2: + case GF_CODECID_MPEG4_PART2: rtp->cat_dsi = GF_TRUE; break; - case GPAC_OTI_VIDEO_AVC: - case GPAC_OTI_VIDEO_SVC: - case GPAC_OTI_VIDEO_MVC: + case GF_CODECID_AVC: + case GF_CODECID_SVC: + case GF_CODECID_MVC: rtp->is_264 = GF_TRUE; rtp->depacketizer->flags |= GF_RTP_AVC_USE_ANNEX_B; { @@ -942,7 +988,7 @@ static void fill_rtp_es_ifce(GF_ESInterface *ifce, GF_SDPMedia *media, GF_SDPInf #endif } break; - case GPAC_OTI_AUDIO_AAC_MPEG4: + case GF_CODECID_AAC_MPEG4: ifce->decoder_config = (char*)gf_malloc(sizeof(char) * rtp->depacketizer->sl_map.configSize); ifce->decoder_config_size = rtp->depacketizer->sl_map.configSize; memcpy(ifce->decoder_config, rtp->depacketizer->sl_map.config, rtp->depacketizer->sl_map.configSize); @@ -954,9 +1000,6 @@ static void fill_rtp_es_ifce(GF_ESInterface *ifce, GF_SDPMedia *media, GF_SDPInf rtp->au_sn=0; } - /*DTS signaling is only supported for MPEG-4 visual*/ - if (rtp->depacketizer->sl_map.DTSDeltaLength) ifce->caps |= GF_ESI_SIGNAL_DTS; - gf_rtp_depacketizer_reset(rtp->depacketizer, GF_TRUE); e = gf_rtp_initialize(rtp->rtp_ch, 0x100000ul, GF_FALSE, 0, 10, 200, NULL); if (e!=GF_OK) { @@ -1139,7 +1182,7 @@ static void SampleCallBack(void *calling_object, u16 ESID, char *data, u32 size, if (ts) pck.cts = pck.dts = ts; if (priv->rap) - pck.flags |= GF_ESI_DATA_AU_RAP; + pck.sap_type = 1; if (source->repeat || !priv->vers_inc) { pck.flags |= GF_ESI_DATA_REPEAT; fprintf(stderr, "RAP carousel from scene engine sent: ESID=%d - size=%d - ts="LLD"\n", ESID, size, ts); @@ -1456,6 +1499,8 @@ static Bool open_source(M2TSSource *source, char *src, u32 carousel_rate, u32 mp s64 min_offset = 0; u32 min_offset_timescale = 0; source->mp4 = gf_isom_open(src, GF_ISOM_OPEN_READ, 0); + if (!source->mp4) + return GF_FALSE; source->nb_streams = 0; source->real_time = force_real_time; /*on MPEG-2 TS, carry 3GPP timed text as MPEG-4 Part17*/ @@ -1468,6 +1513,7 @@ static Bool open_source(M2TSSource *source, char *src, u32 carousel_rate, u32 mp continue; fill_isom_es_ifce(source, &source->streams[i], source->mp4, i+1, bifs_use_pes, compute_max_size); + if (!source->streams[i].input_udta) continue; if (min_offset > ((GF_ESIMP4 *)source->streams[i].input_udta)->ts_offset) { min_offset = ((GF_ESIMP4 *)source->streams[i].input_udta)->ts_offset; min_offset_timescale = source->streams[i].timescale; @@ -1485,8 +1531,8 @@ static Bool open_source(M2TSSource *source, char *src, u32 carousel_rate, u32 mp case GF_STREAM_VISUAL: /*turn on image repeat*/ switch (source->streams[i].object_type_indication) { - case GPAC_OTI_IMAGE_JPEG: - case GPAC_OTI_IMAGE_PNG: + case GF_CODECID_JPEG: + case GF_CODECID_PNG: ((GF_ESIMP4 *)source->streams[i].input_udta)->image_repeat_ms = carousel_rate; break; default: @@ -1638,18 +1684,12 @@ static Bool open_source(M2TSSource *source, char *src, u32 carousel_rate, u32 mp char *sdp_buf; u32 sdp_size, i; GF_Err e; - FILE *_sdp = gf_fopen(src, "rt"); - if (!_sdp) { - fprintf(stderr, "Error opening %s - no such file\n", src); + + e = gf_file_load_data(src, (u8 **) &sdp_buf, &sdp_size); + if (e) { + fprintf(stderr, "Error opening %s\n", src); return 0; } - gf_fseek(_sdp, 0, SEEK_END); - sdp_size = (u32)gf_ftell(_sdp); - gf_fseek(_sdp, 0, SEEK_SET); - sdp_buf = (char*)gf_malloc(sizeof(char)*sdp_size); - memset(sdp_buf, 0, sizeof(char)*sdp_size); - sdp_size = (u32) fread(sdp_buf, 1, sdp_size, _sdp); - gf_fclose(_sdp); sdp = gf_sdp_info_new(); e = gf_sdp_info_parse(sdp, sdp_buf, sdp_size); @@ -1744,9 +1784,9 @@ static Bool open_source(M2TSSource *source, char *src, u32 carousel_rate, u32 mp source->streams[source->nb_streams].stream_type = GF_STREAM_AUDIO; /*hack: http urls are not decomposed therefore audio_input_port remains null*/ if (audio_input_port) { /*UDP/RTP*/ - source->streams[source->nb_streams].object_type_indication = GPAC_OTI_AUDIO_MPEG1; + source->streams[source->nb_streams].object_type_indication = GF_CODECID_MPEG_AUDIO; } else { /*HTTP*/ - aac_reader->oti = source->streams[source->nb_streams].object_type_indication = GPAC_OTI_AUDIO_AAC_MPEG4; + aac_reader->oti = source->streams[source->nb_streams].object_type_indication = GF_CODECID_AAC_MPEG4; } source->streams[source->nb_streams].input_ctrl = void_input_ctrl; source->streams[source->nb_streams].stream_id = AUDIO_DATA_ESID; @@ -1757,7 +1797,7 @@ static Bool open_source(M2TSSource *source, char *src, u32 carousel_rate, u32 mp GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Failed to allocate audio input handler\n")); return 0; } - + ((GF_ESIStream*)source->streams[source->nb_streams].input_udta)->vers_inc = 1; /*increment version number at every audio update*/ assert( source ); //assert( source->iod); @@ -1805,7 +1845,7 @@ static Bool open_source(M2TSSource *source, char *src, u32 carousel_rate, u32 mp if (video_buffer) { /*add the video program*/ source->streams[source->nb_streams].stream_type = GF_STREAM_VISUAL; - source->streams[source->nb_streams].object_type_indication = GPAC_OTI_VIDEO_AVC; + source->streams[source->nb_streams].object_type_indication = GF_CODECID_AVC; source->streams[source->nb_streams].input_ctrl = void_input_ctrl; source->streams[source->nb_streams].stream_id = VIDEO_DATA_ESID; source->streams[source->nb_streams].timescale = 1000; @@ -1902,6 +1942,11 @@ static GFINLINE GF_Err parse_args(int argc, char **argv, u32 *mux_rate, u32 *car usage(); return GF_EOS; } + else if (!stricmp(arg, "-hc") ) { + fprintf(stderr, "libgpac options:\n"); + gf_sys_print_core_help(GF_ARGMODE_ALL, 0); + return GF_EOS; + } else if (CHECK_PARAM("-pcr-init")) { sscanf(next_arg, LLD, pcr_init_val); } @@ -1909,29 +1954,18 @@ static GFINLINE GF_Err parse_args(int argc, char **argv, u32 *mux_rate, u32 *car *pcr_offset = atoi(next_arg); } else if (CHECK_PARAM("-video")) { - FILE *f; + GF_Err e; if (video_input_found) { error_msg = "multiple '-video' found"; arg = NULL; goto error; } video_input_found = 1; - f = gf_fopen(next_arg, "rb"); - if (!f) { - error_msg = "video file not found: "; + e = gf_file_load_data(next_arg, (u8 **) video_buffer, video_buffer_size); + if (e) { + fprintf(stderr, "Error while reading video file %s\n", next_arg); goto error; } - gf_fseek(f, 0, SEEK_END); - *video_buffer_size = (u32)gf_ftell(f); - gf_fseek(f, 0, SEEK_SET); - assert(*video_buffer_size); - *video_buffer = (char*) gf_malloc(*video_buffer_size); - { - s32 read = (u32) fread(*video_buffer, sizeof(char), *video_buffer_size, f); - if (read != *video_buffer_size) - fprintf(stderr, "Error while reading video file, has readen %u chars instead of %u.\n", read, *video_buffer_size); - } - gf_fclose(f); } else if (CHECK_PARAM("-audio")) { if (audio_input_found) { error_msg = "multiple '-audio' found"; @@ -1990,7 +2024,7 @@ static GFINLINE GF_Err parse_args(int argc, char **argv, u32 *mux_rate, u32 *car #ifdef GPAC_MEMORY_TRACKING gf_sys_close(); mem_track = !strcmp(arg, "-mem-track-stack") ? GF_MemTrackerBackTrace : GF_MemTrackerSimple; - gf_sys_init(mem_track); + gf_sys_init(mem_track, NULL); gf_log_set_tool_level(GF_LOG_MEMORY, GF_LOG_INFO); #else fprintf(stderr, "WARNING - GPAC not compiled with Memory Tracker - ignoring \"%s\"\n", arg); @@ -2047,13 +2081,6 @@ static GFINLINE GF_Err parse_args(int argc, char **argv, u32 *mux_rate, u32 *car *ip_ifce = next_arg; } else if (CHECK_PARAM("-sdt-rate")) { *sdt_refresh_rate = atoi(next_arg); - } - else if (CHECK_PARAM("-logs")) { - if (gf_log_set_tools_levels(next_arg) != GF_OK) - return GF_BAD_PARAM; - } else if (CHECK_PARAM("-lf")) { - logfile = gf_fopen(next_arg, "wt"); - gf_log_set_callback(logfile, on_gpac_log); } else if (CHECK_PARAM("-segment-dir")) { if (seg_dir_found) { goto error; @@ -2144,6 +2171,14 @@ static GFINLINE GF_Err parse_args(int argc, char **argv, u32 *mux_rate, u32 *car temi_offset = atoi(next_arg); } else if (!stricmp(arg, "-temi-noloop")) { temi_disable_loop = 1; + } else if (!stricmp(arg, "-temi-off")) { + temi_on = GF_FALSE; + } else if (CHECK_PARAM("-temi-period")) { + temi_period = atof(next_arg); + if (temi_period<0) { + temi_period *= -1; + temi_single_toggle = GF_TRUE; + } } else if (!stricmp(arg, "-insert-ntp")) { insert_ntp = GF_TRUE; } @@ -2174,12 +2209,17 @@ static GFINLINE GF_Err parse_args(int argc, char **argv, u32 *mux_rate, u32 *car } } else if (CHECK_PARAM("-src")) { //second pass arguments } else if (CHECK_PARAM("-prog")) { //second pass arguments - } - else { - error_msg = "unknown option"; - goto error; + } else { + u32 res = gf_sys_is_gpac_arg(arg); + if (!res) { + error_msg = "unknown option"; + goto error; + } else if (res==2) { + if (!strchr(arg, '=')) i++; + } } } + if (*real_time) force_real_time = 1; rate_found = 1; @@ -2203,7 +2243,6 @@ static GFINLINE GF_Err parse_args(int argc, char **argv, u32 *mux_rate, u32 *car res = open_source(&sources[*nb_sources], next_arg, *carrousel_rate, mpeg4_signaling, *bifs_src_name, *audio_input_ip, *audio_input_port, *video_buffer, force_real_time, *bifs_use_pes, *temi_url, (*pcr_offset == (u32) -1) ? 1 : 0, insert_ntp); - //we may have arguments while (src_args) { char *sep = strchr(src_args, ':'); @@ -2226,6 +2265,10 @@ static GFINLINE GF_Err parse_args(int argc, char **argv, u32 *mux_rate, u32 *car break; } } + } else if (!strnicmp(src_args, "disc", 4)) { + sources[*nb_sources].set_disc = GF_TRUE; + } else if (!strnicmp(src_args, "PMT=", 4)) { + sources[*nb_sources].pmt_version = atoi(src_args+4); } if (sep) { @@ -2277,7 +2320,7 @@ static GF_Err write_manifest(char *manifest, char *segment_dir, u32 segment_dura sprintf(manifest_name, "%s", manifest); } - manifest_fp = gf_fopen(tmp_manifest, "w"); + manifest_fp = gf_fopen(tmp_manifest, "wt"); if (!manifest_fp) { fprintf(stderr, "Could not create m3u8 manifest file (%s)\n", tmp_manifest); return GF_BAD_PARAM; @@ -2352,7 +2395,7 @@ int main(int argc, char **argv) /*****************/ /* gpac init */ /*****************/ - gf_sys_init(GF_MemTrackerNone); + gf_sys_init(GF_MemTrackerNone, NULL); gf_log_set_tool_level(GF_LOG_ALL, GF_LOG_WARNING); /***********************/ @@ -2411,6 +2454,12 @@ int main(int argc, char **argv) goto exit; } + e = gf_sys_set_args(argc, (const char **) argv); + if (e) { + fprintf(stderr, "Error assigning libgpac arguments: %s\n", gf_error_to_string(e) ); + goto exit; + } + if (run_time && !mux_rate) { fprintf(stderr, "Cannot specify TS run time for VBR multiplex - disabling run time\n"); run_time = 0; @@ -2588,7 +2637,7 @@ int main(int argc, char **argv) } fprintf(stderr, "Setting up program ID %d - send rates: PSI %d ms PCR %d ms - PCR offset %d\n", sources[i].ID, psi_refresh_rate, pcr_ms, prog_pcr_offset); - program = gf_m2ts_mux_program_add(muxer, sources[i].ID, cur_pid, psi_refresh_rate, prog_pcr_offset, sources[i].mpeg4_signaling); + program = gf_m2ts_mux_program_add(muxer, sources[i].ID, cur_pid, psi_refresh_rate, prog_pcr_offset, sources[i].mpeg4_signaling, sources[i].pmt_version, sources[i].set_disc); if (sources[i].mpeg4_signaling) program->iod = sources[i].iod; if (sources[i].od_updates) { program->loop_descriptors = sources[i].od_updates; @@ -2607,7 +2656,7 @@ int main(int argc, char **argv) if (sources[i].streams[j].stream_type==GF_STREAM_SCENE) force_pes_mode = bifs_use_pes ? 1 : 0; - stream = gf_m2ts_program_stream_add(program, &sources[i].streams[j], cur_pid+j+1, (sources[i].pcr_idx==j) ? 1 : 0, force_pes_mode); + stream = gf_m2ts_program_stream_add(program, &sources[i].streams[j], cur_pid+j+1, (sources[i].pcr_idx==j) ? 1 : 0, force_pes_mode, GF_TRUE); if (split_rap && (sources[i].streams[j].stream_type==GF_STREAM_VISUAL)) stream->start_pes_at_rap = 1; } @@ -2642,7 +2691,7 @@ int main(int argc, char **argv) case GF_MP42TS_UDP: case GF_MP42TS_RTP: /*e =*/ - gf_sk_receive(audio_input_udp_sk, audio_input_buffer, audio_input_buffer_length, 0, &read); + gf_sk_receive(audio_input_udp_sk, audio_input_buffer, audio_input_buffer_length, &read); if (read) { SampleCallBack((void*)&sources[nb_sources-1], AUDIO_DATA_ESID, audio_input_buffer, read, gf_m2ts_get_sys_clock(muxer)); } @@ -2673,7 +2722,7 @@ int main(int argc, char **argv) nb_pck_in_pack=0; while ((ts_pck = gf_m2ts_mux_process(muxer, &status, &usec_till_next)) != NULL) { - if (ts_pack_buffer ) { + if (ts_pack_buffer) { memcpy(ts_pack_buffer + 188 * nb_pck_in_pack, ts_pck, 188); nb_pck_in_pack++; @@ -2780,6 +2829,7 @@ call_flush: if (gf_prompt_has_input()) { char c = gf_prompt_get_char(); if (c=='q') break; + else if (c=='t') request_temi_toggle = GF_TRUE; } } if (status == GF_M2TS_STATE_IDLE) { @@ -2869,7 +2919,6 @@ exit: if (aac_reader) AAC_Reader_del(aac_reader); #endif - if (logfile) gf_fclose(logfile); gf_sys_close(); #ifdef GPAC_MEMORY_TRACKING @@ -2881,4 +2930,3 @@ exit: #endif return 0; } - diff --git a/applications/deprecated/mp42ts/mp42ts.vcxproj b/applications/deprecated/mp42ts/mp42ts.vcxproj new file mode 100644 index 0000000..eab1861 --- /dev/null +++ b/applications/deprecated/mp42ts/mp42ts.vcxproj @@ -0,0 +1,265 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {F728CC74-A7D0-43D2-8A28-05CE9F2EF0D0} + + + + Application + false + MultiByte + v140 + + + Application + false + MultiByte + v140 + + + Application + false + MultiByte + v140 + + + Application + false + MultiByte + v140 + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + ../../bin/$(Platform)\$(Configuration)/ + ../../bin/$(Platform)\$(Configuration)/ + .\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + .\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ../../bin/$(Platform)\$(Configuration)/ + ../../bin/$(Platform)\$(Configuration)/ + .\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + .\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + false + false + + + + .\obj\mp42ts_deb\mp42ts.tlb + + + + + Disabled + ../../include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + $(IntDir) + $(IntDir) + $(IntDir) + true + Level3 + true + EditAndContinue + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName)$(TargetExt) + true + %(AdditionalLibraryDirectories) + true + $(IntDir)$(ProjectName).pdb + Console + MachineX86 + + + true + + + + + .\obj\mp42ts_deb\mp42ts.tlb + + + + + Disabled + ../../include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + + $(IntDir) + $(IntDir) + $(IntDir) + true + Level3 + true + ProgramDatabase + false + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName)$(TargetExt) + true + %(AdditionalLibraryDirectories) + true + $(IntDir)$(ProjectName).pdb + Console + + + true + + + + + .\obj\mp42ts_rel\mp42ts.tlb + + + + + MaxSpeed + OnlyExplicitInline + ../../include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + true + MultiThreadedDLL + true + .\obj\mp42ts_rel/$(ProjectName).pch + $(IntDir) + $(IntDir) + $(IntDir) + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName)$(TargetExt) + true + %(AdditionalLibraryDirectories) + $(IntDir)$(ProjectName).pdb + Console + MachineX86 + + + true + + + + + .\obj\mp42ts_rel\mp42ts.tlb + + + + + Full + AnySuitable + ../../include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + true + MultiThreadedDLL + true + .\obj\mp42ts_rel/$(ProjectName).pch + $(IntDir) + $(IntDir) + $(IntDir) + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName)$(TargetExt) + true + %(AdditionalLibraryDirectories) + $(IntDir)$(ProjectName).pdb + Console + + + true + + + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + + {d3540754-e0cf-4604-ac11-82de9bd4d814} + true + true + false + true + false + + + + + + \ No newline at end of file diff --git a/applications/deprecated/old_arch/GPAX/GPAX.cpp b/applications/deprecated/old_arch/GPAX/GPAX.cpp new file mode 100644 index 0000000..773b335 --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/GPAX.cpp @@ -0,0 +1,76 @@ +// GPAX.cpp : Implementation of DLL Exports. + + +// Note: Proxy/Stub Information +// To build a separate proxy/stub DLL, +// run nmake -f GPAXps.mk in the project directory. + +#include "stdafx.h" +#include "resource.h" +#include +#include "GPAX.h" + +#include "GPAX_i.c" +#include "GPAXPlugin.h" + + +CComModule _Module; + +BEGIN_OBJECT_MAP(ObjectMap) +OBJECT_ENTRY(CLSID_GPAX, CGPAXPlugin) +END_OBJECT_MAP() + +///////////////////////////////////////////////////////////////////////////// +// DLL Entry Point + +extern "C" +#ifdef _WIN32_WCE +BOOL WINAPI DllMain(HANDLE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) +#else +BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) +#endif +{ + if (dwReason == DLL_PROCESS_ATTACH) + { + _Module.Init(ObjectMap, (HINSTANCE) hInstance, &LIBID_GPAXLib); + DisableThreadLibraryCalls((HINSTANCE) hInstance); + } + else if (dwReason == DLL_PROCESS_DETACH) + _Module.Term(); + return TRUE; // ok +} + +///////////////////////////////////////////////////////////////////////////// +// Used to determine whether the DLL can be unloaded by OLE + +STDAPI DllCanUnloadNow(void) +{ + return (_Module.GetLockCount()==0) ? S_OK : S_FALSE; +} + +///////////////////////////////////////////////////////////////////////////// +// Returns a class factory to create an object of the requested type + +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) +{ + return _Module.GetClassObject(rclsid, riid, ppv); +} + +///////////////////////////////////////////////////////////////////////////// +// DllRegisterServer - Adds entries to the system registry + +STDAPI DllRegisterServer(void) +{ + // registers object, typelib and all interfaces in typelib + return _Module.RegisterServer(TRUE); +} + +///////////////////////////////////////////////////////////////////////////// +// DllUnregisterServer - Removes entries from the system registry + +STDAPI DllUnregisterServer(void) +{ + return _Module.UnregisterServer(TRUE); +} + + diff --git a/applications/deprecated/old_arch/GPAX/GPAX.def b/applications/deprecated/old_arch/GPAX/GPAX.def new file mode 100644 index 0000000..f61476d --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/GPAX.def @@ -0,0 +1,9 @@ +; GPAX.def : Declares the module parameters. + +LIBRARY "GPAX.dll" + +EXPORTS + DllCanUnloadNow PRIVATE + DllGetClassObject PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE diff --git a/applications/deprecated/old_arch/GPAX/GPAX.dsp b/applications/deprecated/old_arch/GPAX/GPAX.dsp new file mode 100644 index 0000000..ac3d1a3 --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/GPAX.dsp @@ -0,0 +1,173 @@ +# Microsoft Developer Studio Project File - Name="GPAX" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=GPAX - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "GPAX.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "GPAX.mak" CFG="GPAX - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "GPAX - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "GPAX - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "GPAX - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "obj/w32_deb" +# PROP Intermediate_Dir "obj/w32_deb" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /Yu"stdafx.h" /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /ZI /Od /I "../../include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /FR /Yu"stdafx.h" /FD /GZ /c +# ADD BASE RSC /l 0x804 /d "_DEBUG" +# ADD RSC /l 0x804 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 js32.lib zlib.lib winmm.lib ws2_32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /out:"../../bin/w32_deb/GPAX.dll" /pdbtype:sept /libpath:"../../extra_lib/lib/w32_deb" +# Begin Custom Build - Performing registration +OutDir=.\obj/w32_deb +TargetPath=\CVS\gpac\bin\w32_deb\GPAX.dll +InputPath=\CVS\gpac\bin\w32_deb\GPAX.dll +SOURCE="$(InputPath)" + +"$(OutDir)\regsvr32.trg" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + regsvr32 /s /c "$(TargetPath)" + echo regsvr32 exec. time > "$(OutDir)\regsvr32.trg" + +# End Custom Build + +!ELSEIF "$(CFG)" == "GPAX - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "obj/w32_rel" +# PROP BASE Intermediate_Dir "obj/w32_rel" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "obj/w32_rel" +# PROP Intermediate_Dir "obj/w32_rel" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MDd /W3 /Gm /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /FR /Yu"stdafx.h" /FD /GZ /c +# ADD CPP /nologo /MD /W3 /Gm /ZI /I "../../include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /FR /Yu"stdafx.h" /FD /GZ /c +# SUBTRACT CPP /O +# ADD BASE RSC /l 0x804 /d "_DEBUG" +# ADD RSC /l 0x804 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 js32.lib zlib.lib winmm.lib ws2_32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept /libpath:"../gpac/extra_lib/lib/w32_deb" +# ADD LINK32 js32.lib zlib.lib winmm.lib ws2_32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /out:"../../bin/w32_rel/GPAX.dll" /pdbtype:sept /libpath:"../../extra_lib/lib/w32_rel" +# Begin Special Build Tool +SOURCE="$(InputPath)" +PostBuild_Cmds=copy ..\..\bin\w32_rel\GPAX.dll "C:\Program Files\GPAC" +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "GPAX - Win32 Debug" +# Name "GPAX - Win32 Release" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\GPAX.cpp +# End Source File +# Begin Source File + +SOURCE=.\GPAX.def +# End Source File +# Begin Source File + +SOURCE=.\GPAX.idl +# ADD MTL /tlb ".\GPAX.tlb" /h "GPAX.h" /iid "GPAX_i.c" /Oicf +# End Source File +# Begin Source File + +SOURCE=.\GPAX.rc +# End Source File +# Begin Source File + +SOURCE=.\GPAXPlugin.cpp +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.cpp +# ADD CPP /Yc"stdafx.h" +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\GPAXPlugin.h +# End Source File +# Begin Source File + +SOURCE=.\Resource.h +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=.\gpax.bmp +# End Source File +# Begin Source File + +SOURCE=.\GPAX.rgs +# End Source File +# Begin Source File + +SOURCE=.\GPAXProp.rgs +# End Source File +# End Group +# End Target +# End Project +# Section GPAX : {00000000-0000-0000-0000-800000800000} +# 1:21:IDS_DOCSTRINGGPAXProp:105 +# 1:12:IDD_GPAXPROP:107 +# 1:12:IDR_GPAXPROP:106 +# 1:20:IDS_HELPFILEGPAXProp:104 +# 1:17:IDS_TITLEGPAXProp:103 +# End Section diff --git a/applications/deprecated/old_arch/GPAX/GPAX.h b/applications/deprecated/old_arch/GPAX/GPAX.h new file mode 100644 index 0000000..05400b7 --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/GPAX.h @@ -0,0 +1,570 @@ + +#pragma warning( disable: 4049 ) /* more than 64k source lines */ + +/* this ALWAYS GENERATED file contains the definitions for the interfaces */ + + +/* File created by MIDL compiler version 5.03.0286 */ +/* at Thu Jul 20 19:14:15 2006 + */ +/* Compiler settings for \CVS\gpac\applications\GPAX\GPAX.idl: + Oicf (OptLev=i2), W1, Zp8, env=Win32 (32b run), ms_ext, c_ext + error checks: allocation ref bounds_check enum stub_data + VC __declspec() decoration level: + __declspec(uuid()), __declspec(selectany), __declspec(novtable) + DECLSPEC_UUID(), MIDL_INTERFACE() +*/ +//@@MIDL_FILE_HEADING( ) + + +/* verify that the version is high enough to compile this file*/ +#ifndef __REQUIRED_RPCNDR_H_VERSION__ +#define __REQUIRED_RPCNDR_H_VERSION__ 440 +#endif + +#include "rpc.h" +#include "rpcndr.h" + +#ifndef __GPAX_h__ +#define __GPAX_h__ + +/* Forward Declarations */ + +#ifndef __IGPAX_FWD_DEFINED__ +#define __IGPAX_FWD_DEFINED__ +typedef interface IGPAX IGPAX; +#endif /* __IGPAX_FWD_DEFINED__ */ + + +#ifndef __IGPAXEvents_FWD_DEFINED__ +#define __IGPAXEvents_FWD_DEFINED__ +typedef interface IGPAXEvents IGPAXEvents; +#endif /* __IGPAXEvents_FWD_DEFINED__ */ + + +#ifndef __GPAX_FWD_DEFINED__ +#define __GPAX_FWD_DEFINED__ + +#ifdef __cplusplus +typedef class GPAX GPAX; +#else +typedef struct GPAX GPAX; +#endif /* __cplusplus */ + +#endif /* __GPAX_FWD_DEFINED__ */ + + +/* header files for imported files */ +#include "oaidl.h" +#include "ocidl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t); +void __RPC_USER MIDL_user_free( void __RPC_FAR * ); + + +#ifndef __GPAXLib_LIBRARY_DEFINED__ +#define __GPAXLib_LIBRARY_DEFINED__ + +/* library GPAXLib */ +/* [helpstring][version][uuid] */ + + + +#define DISPID_SRC ( 100 ) + +#define DISPID_AutoStart ( 101 ) + +#define DISPID_DownloadProgress ( 102 ) + +#define DISPID_PlayEvent ( 100 ) + +#define DISPID_PauseEvent ( 101 ) + +#define DISPID_StopEvent ( 102 ) + + +EXTERN_C const IID LIBID_GPAXLib; + +#ifndef __IGPAX_INTERFACE_DEFINED__ +#define __IGPAX_INTERFACE_DEFINED__ + +/* interface IGPAX */ +/* [object][oleautomation][hidden][dual][helpstring][uuid] */ + + +EXTERN_C const IID IID_IGPAX; + +#if defined(__cplusplus) && !defined(CINTERFACE) + +MIDL_INTERFACE("E2A9A937-BB35-47E0-8942-964806299AB4") +IGPAX : +public IDispatch +{ +public: + virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Play( void) = 0; + + virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Pause( void) = 0; + + virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Stop( void) = 0; + + virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Update( + /* [in] */ BSTR mtype, + /* [in] */ BSTR updates) = 0; + + virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE QualitySwitch( + /* [in] */ INT switchUp) = 0; + + virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE SetURL( + /* [in] */ BSTR url) = 0; + + virtual /* [helpstring][propget][id] */ HRESULT STDMETHODCALLTYPE get_src( + /* [retval][out] */ BSTR __RPC_FAR *url) = 0; + + virtual /* [helpstring][propput][id] */ HRESULT STDMETHODCALLTYPE put_src( + /* [in] */ BSTR url) = 0; + + virtual /* [helpstring][propget][id] */ HRESULT STDMETHODCALLTYPE get_AutoStart( + /* [retval][out] */ VARIANT_BOOL __RPC_FAR *autoplay) = 0; + + virtual /* [helpstring][propput][id] */ HRESULT STDMETHODCALLTYPE put_AutoStart( + /* [in] */ VARIANT_BOOL autoplay) = 0; + + virtual /* [helpstring][propget][id] */ HRESULT STDMETHODCALLTYPE get_DownloadProgress( + /* [retval][out] */ INT __RPC_FAR *downloadProgress) = 0; + + virtual /* [helpstring][propput][id] */ HRESULT STDMETHODCALLTYPE put_DownloadProgress( + /* [in] */ INT downloadProgress) = 0; + +}; + +#else /* C style interface */ + +typedef struct IGPAXVtbl +{ + BEGIN_INTERFACE + + HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )( + IGPAX __RPC_FAR * This, + /* [in] */ REFIID riid, + /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject); + + ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )( + IGPAX __RPC_FAR * This); + + ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )( + IGPAX __RPC_FAR * This); + + HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfoCount )( + IGPAX __RPC_FAR * This, + /* [out] */ UINT __RPC_FAR *pctinfo); + + HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfo )( + IGPAX __RPC_FAR * This, + /* [in] */ UINT iTInfo, + /* [in] */ LCID lcid, + /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo); + + HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetIDsOfNames )( + IGPAX __RPC_FAR * This, + /* [in] */ REFIID riid, + /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames, + /* [in] */ UINT cNames, + /* [in] */ LCID lcid, + /* [size_is][out] */ DISPID __RPC_FAR *rgDispId); + + /* [local] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Invoke )( + IGPAX __RPC_FAR * This, + /* [in] */ DISPID dispIdMember, + /* [in] */ REFIID riid, + /* [in] */ LCID lcid, + /* [in] */ WORD wFlags, + /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams, + /* [out] */ VARIANT __RPC_FAR *pVarResult, + /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo, + /* [out] */ UINT __RPC_FAR *puArgErr); + + /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Play )( + IGPAX __RPC_FAR * This); + + /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Pause )( + IGPAX __RPC_FAR * This); + + /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Stop )( + IGPAX __RPC_FAR * This); + + /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Update )( + IGPAX __RPC_FAR * This, + /* [in] */ BSTR mtype, + /* [in] */ BSTR updates); + + /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QualitySwitch )( + IGPAX __RPC_FAR * This, + /* [in] */ INT switchUp); + + /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *SetURL )( + IGPAX __RPC_FAR * This, + /* [in] */ BSTR url); + + /* [helpstring][propget][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_src )( + IGPAX __RPC_FAR * This, + /* [retval][out] */ BSTR __RPC_FAR *url); + + /* [helpstring][propput][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *put_src )( + IGPAX __RPC_FAR * This, + /* [in] */ BSTR url); + + /* [helpstring][propget][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_AutoStart )( + IGPAX __RPC_FAR * This, + /* [retval][out] */ VARIANT_BOOL __RPC_FAR *autoplay); + + /* [helpstring][propput][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *put_AutoStart )( + IGPAX __RPC_FAR * This, + /* [in] */ VARIANT_BOOL autoplay); + + END_INTERFACE +} IGPAXVtbl; + +interface IGPAX +{ + CONST_VTBL struct IGPAXVtbl __RPC_FAR *lpVtbl; +}; + + + +#ifdef COBJMACROS + + +#define IGPAX_QueryInterface(This,riid,ppvObject) \ + (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) + +#define IGPAX_AddRef(This) \ + (This)->lpVtbl -> AddRef(This) + +#define IGPAX_Release(This) \ + (This)->lpVtbl -> Release(This) + + +#define IGPAX_GetTypeInfoCount(This,pctinfo) \ + (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) + +#define IGPAX_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ + (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) + +#define IGPAX_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ + (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) + +#define IGPAX_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ + (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) + + +#define IGPAX_Play(This) \ + (This)->lpVtbl -> Play(This) + +#define IGPAX_Pause(This) \ + (This)->lpVtbl -> Pause(This) + +#define IGPAX_Stop(This) \ + (This)->lpVtbl -> Stop(This) + +#define IGPAX_Update(This,mtype,updates) \ + (This)->lpVtbl -> Update(This,mtype,updates) + +#define IGPAX_QualitySwitch(This,switchUp) \ + (This)->lpVtbl -> QualitySwitch(This,switchUp) + +#define IGPAX_SetURL(This,url) \ + (This)->lpVtbl -> SetURL(This,url) + +#define IGPAX_get_src(This,url) \ + (This)->lpVtbl -> get_src(This,url) + +#define IGPAX_put_src(This,url) \ + (This)->lpVtbl -> put_src(This,url) + +#define IGPAX_get_AutoStart(This,autoplay) \ + (This)->lpVtbl -> get_AutoStart(This,autoplay) + +#define IGPAX_put_AutoStart(This,autoplay) \ + (This)->lpVtbl -> put_AutoStart(This,autoplay) + +#endif /* COBJMACROS */ + + +#endif /* C style interface */ + + + +/* [helpstring] */ HRESULT STDMETHODCALLTYPE IGPAX_Play_Proxy( + IGPAX __RPC_FAR * This); + + +void __RPC_STUB IGPAX_Play_Stub( + IRpcStubBuffer *This, + IRpcChannelBuffer *_pRpcChannelBuffer, + PRPC_MESSAGE _pRpcMessage, + DWORD *_pdwStubPhase); + + +/* [helpstring] */ HRESULT STDMETHODCALLTYPE IGPAX_Pause_Proxy( + IGPAX __RPC_FAR * This); + + +void __RPC_STUB IGPAX_Pause_Stub( + IRpcStubBuffer *This, + IRpcChannelBuffer *_pRpcChannelBuffer, + PRPC_MESSAGE _pRpcMessage, + DWORD *_pdwStubPhase); + + +/* [helpstring] */ HRESULT STDMETHODCALLTYPE IGPAX_Stop_Proxy( + IGPAX __RPC_FAR * This); + + +void __RPC_STUB IGPAX_Stop_Stub( + IRpcStubBuffer *This, + IRpcChannelBuffer *_pRpcChannelBuffer, + PRPC_MESSAGE _pRpcMessage, + DWORD *_pdwStubPhase); + + +/* [helpstring] */ HRESULT STDMETHODCALLTYPE IGPAX_Update_Proxy( + IGPAX __RPC_FAR * This, + /* [in] */ BSTR mtype, + /* [in] */ BSTR updates); + + +void __RPC_STUB IGPAX_Update_Stub( + IRpcStubBuffer *This, + IRpcChannelBuffer *_pRpcChannelBuffer, + PRPC_MESSAGE _pRpcMessage, + DWORD *_pdwStubPhase); + +/* [helpstring] */ HRESULT STDMETHODCALLTYPE IGPAX_QualitySwitch_Proxy( + IGPAX __RPC_FAR * This, + /* [in] */ INT switchUp); + + +void __RPC_STUB IGPAX_QualitySwitch_Stub( + IRpcStubBuffer *This, + IRpcChannelBuffer *_pRpcChannelBuffer, + PRPC_MESSAGE _pRpcMessage, + DWORD *_pdwStubPhase); + +/* [helpstring] */ HRESULT STDMETHODCALLTYPE IGPAX_SetURL_Proxy( + IGPAX __RPC_FAR * This, + /* [in] */ BSTR url); + + +void __RPC_STUB IGPAX_SetURL_Stub( + IRpcStubBuffer *This, + IRpcChannelBuffer *_pRpcChannelBuffer, + PRPC_MESSAGE _pRpcMessage, + DWORD *_pdwStubPhase); + + +/* [helpstring][propget][id] */ HRESULT STDMETHODCALLTYPE IGPAX_get_src_Proxy( + IGPAX __RPC_FAR * This, + /* [retval][out] */ BSTR __RPC_FAR *url); + + +void __RPC_STUB IGPAX_get_src_Stub( + IRpcStubBuffer *This, + IRpcChannelBuffer *_pRpcChannelBuffer, + PRPC_MESSAGE _pRpcMessage, + DWORD *_pdwStubPhase); + + +/* [helpstring][propput][id] */ HRESULT STDMETHODCALLTYPE IGPAX_put_src_Proxy( + IGPAX __RPC_FAR * This, + /* [in] */ BSTR url); + + +void __RPC_STUB IGPAX_put_src_Stub( + IRpcStubBuffer *This, + IRpcChannelBuffer *_pRpcChannelBuffer, + PRPC_MESSAGE _pRpcMessage, + DWORD *_pdwStubPhase); + + +/* [helpstring][propget][id] */ HRESULT STDMETHODCALLTYPE IGPAX_get_AutoStart_Proxy( + IGPAX __RPC_FAR * This, + /* [retval][out] */ VARIANT_BOOL __RPC_FAR *autoplay); + + +void __RPC_STUB IGPAX_get_AutoStart_Stub( + IRpcStubBuffer *This, + IRpcChannelBuffer *_pRpcChannelBuffer, + PRPC_MESSAGE _pRpcMessage, + DWORD *_pdwStubPhase); + + +/* [helpstring][propput][id] */ HRESULT STDMETHODCALLTYPE IGPAX_put_AutoStart_Proxy( + IGPAX __RPC_FAR * This, + /* [in] */ VARIANT_BOOL autoplay); + + +void __RPC_STUB IGPAX_put_AutoStart_Stub( + IRpcStubBuffer *This, + IRpcChannelBuffer *_pRpcChannelBuffer, + PRPC_MESSAGE _pRpcMessage, + DWORD *_pdwStubPhase); + +/* [helpstring][propget][id] */ HRESULT STDMETHODCALLTYPE IGPAX_get_DownloadProgress_Proxy( + IGPAX __RPC_FAR * This, + /* [retval][out] */ INT __RPC_FAR *autoplay); + + +void __RPC_STUB IGPAX_get_DownloadProgress_Stub( + IRpcStubBuffer *This, + IRpcChannelBuffer *_pRpcChannelBuffer, + PRPC_MESSAGE _pRpcMessage, + DWORD *_pdwStubPhase); + + +/* [helpstring][propput][id] */ HRESULT STDMETHODCALLTYPE IGPAX_put_DownloadProgress_Proxy( + IGPAX __RPC_FAR * This, + /* [in] */ INT autoplay); + + +void __RPC_STUB IGPAX_put_DownloadProgress_Stub( + IRpcStubBuffer *This, + IRpcChannelBuffer *_pRpcChannelBuffer, + PRPC_MESSAGE _pRpcMessage, + DWORD *_pdwStubPhase); + + + +#endif /* __IGPAX_INTERFACE_DEFINED__ */ + + +#ifndef __IGPAXEvents_DISPINTERFACE_DEFINED__ +#define __IGPAXEvents_DISPINTERFACE_DEFINED__ + +/* dispinterface IGPAXEvents */ +/* [helpstring][uuid] */ + + +EXTERN_C const IID DIID_IGPAXEvents; + +#if defined(__cplusplus) && !defined(CINTERFACE) + +MIDL_INTERFACE("1FDA32FC-4C9A-461F-B33B-0715B0343006") +IGPAXEvents : +public IDispatch +{ +}; + +#else /* C style interface */ + +typedef struct IGPAXEventsVtbl +{ + BEGIN_INTERFACE + + HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )( + IGPAXEvents __RPC_FAR * This, + /* [in] */ REFIID riid, + /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject); + + ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )( + IGPAXEvents __RPC_FAR * This); + + ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )( + IGPAXEvents __RPC_FAR * This); + + HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfoCount )( + IGPAXEvents __RPC_FAR * This, + /* [out] */ UINT __RPC_FAR *pctinfo); + + HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfo )( + IGPAXEvents __RPC_FAR * This, + /* [in] */ UINT iTInfo, + /* [in] */ LCID lcid, + /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo); + + HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetIDsOfNames )( + IGPAXEvents __RPC_FAR * This, + /* [in] */ REFIID riid, + /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames, + /* [in] */ UINT cNames, + /* [in] */ LCID lcid, + /* [size_is][out] */ DISPID __RPC_FAR *rgDispId); + + /* [local] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Invoke )( + IGPAXEvents __RPC_FAR * This, + /* [in] */ DISPID dispIdMember, + /* [in] */ REFIID riid, + /* [in] */ LCID lcid, + /* [in] */ WORD wFlags, + /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams, + /* [out] */ VARIANT __RPC_FAR *pVarResult, + /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo, + /* [out] */ UINT __RPC_FAR *puArgErr); + + END_INTERFACE +} IGPAXEventsVtbl; + +interface IGPAXEvents +{ + CONST_VTBL struct IGPAXEventsVtbl __RPC_FAR *lpVtbl; +}; + + + +#ifdef COBJMACROS + + +#define IGPAXEvents_QueryInterface(This,riid,ppvObject) \ + (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) + +#define IGPAXEvents_AddRef(This) \ + (This)->lpVtbl -> AddRef(This) + +#define IGPAXEvents_Release(This) \ + (This)->lpVtbl -> Release(This) + + +#define IGPAXEvents_GetTypeInfoCount(This,pctinfo) \ + (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) + +#define IGPAXEvents_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ + (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) + +#define IGPAXEvents_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ + (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) + +#define IGPAXEvents_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ + (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) + +#endif /* COBJMACROS */ + + +#endif /* C style interface */ + + +#endif /* __IGPAXEvents_DISPINTERFACE_DEFINED__ */ + + +EXTERN_C const CLSID CLSID_GPAX; + +#ifdef __cplusplus + +class DECLSPEC_UUID("181D18E6-4DC1-4B55-B72E-BE2A10064995") + GPAX; +#endif +#endif /* __GPAXLib_LIBRARY_DEFINED__ */ + +/* Additional Prototypes for ALL interfaces */ + +/* end of Additional Prototypes */ + +#ifdef __cplusplus +} +#endif + +#endif + + diff --git a/applications/deprecated/old_arch/GPAX/GPAX.idl b/applications/deprecated/old_arch/GPAX/GPAX.idl new file mode 100644 index 0000000..af76432 --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/GPAX.idl @@ -0,0 +1,92 @@ +// GPAX.idl : IDL source for GPAX.dll +// + +// This file will be processed by the MIDL tool to +// produce the type library (GPAX.tlb) and marshalling code. + +import "oaidl.idl"; +import "ocidl.idl"; +#include "olectl.h" + + +[ + uuid(E64FAC7F-0134-4A75-A7DA-80D53EBC56A6), + version(1.0), + helpstring("GPAX ActiveX Control") +] + +library GPAXLib +{ + importlib("stdole2.tlb"); + + // Forward declare all types defined in this typelib + interface IGPAX; + dispinterface IGPAXEvents; + + const int DISPID_SRC = 100; + const int DISPID_AutoStart = 101; + const int DISPID_DownloadProgress = 102; + + + //IDispatch interface + [ + odl, + uuid(E2A9A937-BB35-47E0-8942-964806299AB4), + helpstring("GPAC ActiveX Control"), + dual, + hidden, + oleautomation + ] + interface IGPAX : IDispatch + { + /*functions*/ + [helpstring("Play Movie")] HRESULT Play(); + [helpstring("Pause/Resume Movie")] HRESULT Pause(); + [helpstring("Stop Movie")] HRESULT Stop(); + [helpstring("Update Scene")] HRESULT Update([in] BSTR mtype, [in] BSTR updates); + [helpstring("Switch Quality")] HRESULT QualitySwitch ([in] int switch_up); + [helpstring("Change URL")] HRESULT SetURL ([in] BSTR url); + + + /*properties*/ + [id(DISPID_SRC), propget, helpstring("Get/Set the media source")] + HRESULT src([out, retval] BSTR* url); + [id(DISPID_SRC), propput, helpstring("Get/Set the media source")] + HRESULT src([in] BSTR url); + + [id(DISPID_AutoStart), propget, helpstring("Get/Set automatic playback upon load")] + HRESULT AutoStart([out, retval] VARIANT_BOOL* autoplay); + [id(DISPID_AutoStart), propput, helpstring("Get/Set automatic playback upon load")] + HRESULT AutoStart([in] VARIANT_BOOL autoplay); + + [id(DISPID_DownloadProgress), propget, helpstring("Get/Set download progress")] + HRESULT DownloadProgress([out, retval] INT* downloadProgress); + [id(DISPID_DownloadProgress), propput, helpstring("Get/Set download progress")] + HRESULT DownloadProgress([in] INT downloadProgress); + }; + + + //event interface + [ + uuid(1FDA32FC-4C9A-461F-B33B-0715B0343006), + helpstring("GPAX Control Events") + ] + dispinterface IGPAXEvents + { + properties: + methods: + }; + + + //AX control + [ + uuid(181D18E6-4DC1-4B55-B72E-BE2A10064995), + helpstring("GPAC Control"), + control + ] + coclass GPAX + { + [default] interface IGPAX; + [default, source] dispinterface IGPAXEvents; + }; +}; diff --git a/applications/deprecated/old_arch/GPAX/GPAX.rc b/applications/deprecated/old_arch/GPAX/GPAX.rc new file mode 100644 index 0000000..374556b --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/GPAX.rc @@ -0,0 +1,151 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +#include + +///////////////////////////////////////////////////////////////////////////// +// Chinese (P.R.C.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS) +#ifdef _WIN32 +LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED +#pragma code_page(936) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "1 TYPELIB ""GPAX.tlb""\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // Chinese (P.R.C.) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "\0" + VALUE "CompanyName", "ENST\0" + VALUE "FileDescription", "GPAX\0" + VALUE "FileVersion", GPAC_VERSION"\0" + VALUE "InternalName", "GPAX\0" + VALUE "LegalCopyright", "Copyright © 2012-\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "GPAX.dll\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "Telecom ParisTech GPAX\0" + VALUE "ProductVersion", GPAC_VERSION"\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +IDB_GPAXPLUGIN BITMAP DISCARDABLE "gpax.bmp" + +///////////////////////////////////////////////////////////////////////////// +// +// REGISTRY +// + +IDR_GPAXPLUGIN REGISTRY DISCARDABLE "GPAX.rgs" + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDS_PROJNAME "GPAX" + IDS_TITLEGPAXProp "&GPAX" + IDS_HELPFILEGPAXProp "Help File Name" + IDS_DOCSTRINGGPAXProp "URLs setting" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +1 TYPELIB "GPAX.tlb" + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/applications/deprecated/old_arch/GPAX/GPAX.rgs b/applications/deprecated/old_arch/GPAX/GPAX.rgs new file mode 100644 index 0000000..2949a85 --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/GPAX.rgs @@ -0,0 +1,175 @@ +HKCR +{ + ForceRemove '.aac' + { + val 'Content Type' = s 'audio/aac' + } + ForceRemove '.amr' + { + val 'Content Type' = s 'audio/amr' + } + ForceRemove '.mp4' + { + val 'Content Type' = s 'application/mp4' + } + ForceRemove '.3gp' + { + val 'Content Type' = s 'video/3gpp' + } + ForceRemove '.3g2' + { + val 'Content Type' = s 'video/3gpp2' + } + ForceRemove '.wrl' + { + val 'Content Type' = s 'model/vrml' + } + ForceRemove '.x3dv' + { + val 'Content Type' = s 'model/x3d+vrml' + } + ForceRemove '.x3d' + { + val 'Content Type' = s 'model/x3d+xml' + } + ForceRemove '.svg' + { + val 'Content Type' = s 'image/svg+xml' + } + ForceRemove '.sdp' + { + val 'Content Type' = s 'application/sdp' + } + + GPAX.GPAXPlugin.1 = s 'GPAC ActiveX' + { + CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + } + GPAX.GPAXPlugin = s 'GPAC ActiveX' + { + CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + CurVer = s 'GPAX.GPAXPlugin.1' + } + NoRemove CLSID + { + ForceRemove {181D18E6-4DC1-4B55-B72E-BE2A10064995} = s 'GPAC ActiveX' + { + ProgID = s 'GPAX.GPAXPlugin.1' + VersionIndependentProgID = s 'GPAX.GPAXPlugin' + ForceRemove 'Programmable' + InprocServer32 = s '%MODULE%' + { + val ThreadingModel = s 'Both' + } + ForceRemove 'Control' + ForceRemove 'Insertable' + ForceRemove 'ToolboxBitmap32' = s '%MODULE%, 101' + 'MiscStatus' = s '0' + { + '1' = s '131473' + } + 'TypeLib' = s '{E64FAC7F-0134-4A75-A7DA-80D53EBC56A6}' + 'Version' = s '1.0' + ForceRemove 'EnableFullPage' + { + ForceRemove .mp4 + ForceRemove .3gp + ForceRemove .3g2 + ForceRemove .wrl + ForceRemove .x3d + ForceRemove .x3dv + ForceRemove .svg + } + } + } + + NoRemove MIME + { + NoRemove Database + { + NoRemove 'Content Type' + { + 'application/x-gpac' + { + val CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + val Extension = s '.gpac' + } + 'application/mp4' + { + val CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + val Extension = s '.mp4' + } + 'application/sdp' + { + val CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + val Extension = s '.sdp' + } + 'audio/aac' + { + val CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + val Extension = s '.aac' + } + 'audio/amr' + { + val CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + val Extension = s '.amr' + } + 'audio/mp4' + { + val CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + val Extension = s '.mp4' + } + 'audio/mpeg' + { + val CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + val Extension = s '.mp3' + } + 'image/svg+xml' + { + val CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + val Extension = s '.svg' + } + 'model/vrml' + { + val CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + val Extension = s '.wrl' + } + 'model/x3d+vrml' + { + val CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + val Extension = s '.x3dv' + } + 'model/x3d+xml' + { + val CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + val Extension = s '.x3d' + } + 'video/mp4' + { + val CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + val Extension = s '.mp4' + } + 'video/3gpp' + { + val CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + val Extension = s '.3gp' + } + 'video/3gpp2' + { + val CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + val Extension = s '.3g2' + } + 'video/avi' + { + val CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + val Extension = s '.avi' + } + 'video/mpeg' + { + val CLSID = s '{181D18E6-4DC1-4B55-B72E-BE2A10064995}' + val Extension = s '.mpg' + } + } + } + } +} diff --git a/applications/deprecated/old_arch/GPAX/GPAX.vcxproj b/applications/deprecated/old_arch/GPAX/GPAX.vcxproj new file mode 100644 index 0000000..be02f4a --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/GPAX.vcxproj @@ -0,0 +1,376 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {72486240-A124-496E-A67A-E76FEC7E99BE} + GPAX + + + + DynamicLibrary + false + Dynamic + MultiByte + v140 + + + DynamicLibrary + false + Dynamic + MultiByte + v140 + + + DynamicLibrary + false + Dynamic + MultiByte + v140 + + + DynamicLibrary + false + Dynamic + MultiByte + v140 + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + ../../bin/$(Platform)\$(Configuration)/ + ../../bin/$(Platform)\$(Configuration)/ + .\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + .\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + true + true + true + true + ../../bin/$(Platform)\$(Configuration)/ + ../../bin/$(Platform)\$(Configuration)/ + .\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + .\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + + + + + + + + + + + + .\obj/w32_deb/GPAX.tlb + + + + + Disabled + ../../include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebugDLL + $(IntDir)$(ProjectName).pch + $(IntDir) + $(IntDir) + $(IntDir) + true + Level3 + true + EditAndContinue + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + ../../include + + + $(OutDir)$(TargetName)$(TargetExt) + true + ../../extra_lib/lib/$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + ..\..\applications\GPAX\GPAX.def + true + $(IntDir)$(ProjectName).pdb + Windows + $(IntDir)$(ProjectName).lib + MachineX86 + + + true + $(IntDir)$(ProjectName).bsc + + + + + + + + + + + + + .\obj/w32_deb/GPAX.tlb + + + + + Disabled + ../../include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebugDLL + $(IntDir)$(ProjectName).pch + $(IntDir) + $(IntDir) + $(IntDir) + true + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + ../../include + + + $(OutDir)$(TargetName)$(TargetExt) + true + ../../extra_lib/lib/$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + ..\..\applications\GPAX\GPAX.def + true + $(IntDir)$(ProjectName).pdb + Windows + $(IntDir)$(ProjectName).lib + %(AdditionalDependencies) + + + true + $(IntDir)$(ProjectName).bsc + + + + + + + + + + + + + .\obj/w32_rel/GPAX.tlb + + + + + Disabled + ../../include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDLL + $(IntDir)$(ProjectName).pch + $(IntDir) + $(IntDir) + $(IntDir) + true + Level3 + true + EditAndContinue + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + ../../include + + + zlib.lib;winmm.lib;ws2_32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName)$(TargetExt) + true + ../../extra_lib/lib/$(Platform)/$(Configuration);%(AdditionalLibraryDirectories) + ..\..\applications\GPAX\GPAX.def + true + $(IntDir)$(ProjectName).pdb + Windows + false + $(IntDir)$(ProjectName).lib + MachineX86 + + + true + $(IntDir)$(ProjectName).bsc + + + + + + + + + + + + + + + + + .\obj/w32_rel/GPAX.tlb + + + + + Disabled + ../../include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + Default + MultiThreadedDLL + $(IntDir)$(ProjectName).pch + $(IntDir) + $(IntDir) + $(IntDir) + true + Level3 + true + ProgramDatabase + Default + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + ../../include + + + %(AdditionalDependencies) + $(OutDir)$(TargetName)$(TargetExt) + true + ../../extra_lib/lib/$(Platform)/$(Configuration);%(AdditionalLibraryDirectories) + ..\..\applications\GPAX\GPAX.def + true + $(IntDir)$(ProjectName).pdb + Windows + false + $(IntDir)$(ProjectName).lib + + + true + $(IntDir)$(ProjectName).bsc + + + + + + + + + + + + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + + + + + + + + .\GPAX.tlb + .\GPAX.tlb + .\GPAX.tlb + .\GPAX.tlb + + + + + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + \gpac\applications\GPAX;$(OUTDIR);%(AdditionalIncludeDirectories) + \gpac\applications\GPAX;$(OUTDIR);%(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + \gpac\applications\GPAX;$(OUTDIR);%(AdditionalIncludeDirectories) + \gpac\applications\GPAX;$(OUTDIR);%(AdditionalIncludeDirectories) + + + + + {d3540754-e0cf-4604-ac11-82de9bd4d814} + false + + + + + + \ No newline at end of file diff --git a/applications/deprecated/old_arch/GPAX/GPAXPlugin.cpp b/applications/deprecated/old_arch/GPAX/GPAXPlugin.cpp new file mode 100644 index 0000000..e419862 --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/GPAXPlugin.cpp @@ -0,0 +1,710 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2006-2012 + * All rights reserved + * + * This file is part of GPAC / ActiveX control + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "stdafx.h" +#include "GPAX.h" +#include "GPAXPlugin.h" +#include +#include +#include +#include + +#ifndef _WIN32_WCE +#include +#include +#endif + +///////////////////////////////////////////////////////////////////////////// +// CGPAXPlugin + +#if 0 +static print_err(char *msg, char *title) +{ + u16 w_msg[1024], w_title[1024]; + CE_CharToWide(msg, w_msg); + CE_CharToWide(title, w_title); + ::MessageBox(NULL, w_msg, w_title, MB_OK); +} +#endif + + +void CGPAXPlugin::SetStatusText(char *msg) +{ +#ifndef _WIN32_WCE + if (m_pBrowser) { + if (msg) { + u16 w_msg[1024]; + gf_utf8_mbstowcs(w_msg, 1024, (const char **)&msg); + m_pBrowser->put_StatusText((BSTR) w_msg); + } else { + m_pBrowser->put_StatusText(L""); + } + } +#endif +} +//GPAC player Event Handler. not yet implemented, just dummies here +Bool CGPAXPlugin::EventProc(GF_Event *evt) +{ + char msg[1024]; + if (!m_term) return GF_FALSE; + + switch (evt->type) { + case GF_EVENT_MESSAGE: + if (evt->message.error) { + sprintf(msg, "(GPAC) %s (%s)", evt->message.message, gf_error_to_string(evt->message.error)); + } else { + sprintf(msg, "(GPAC) %s", evt->message.message); + } + SetStatusText(msg); + break; + case GF_EVENT_PROGRESS: + if (evt->progress.done == evt->progress.total) { + SetStatusText(NULL); + m_iDownload_progress = 100; + } else { + char *szTitle = ""; + if (evt->progress.progress_type==0) szTitle = "Buffer "; + else if (evt->progress.progress_type==1) + { + szTitle = "Download "; + m_iDownload_progress = (int)floor((100.0*evt->progress.done) / evt->progress.total); + } + else if (evt->progress.progress_type==2) szTitle = "Import "; + sprintf(msg, "(GPAC) %s: %02.2f", szTitle, (100.0*evt->progress.done) / evt->progress.total); + SetStatusText(msg); + } + break; + case GF_EVENT_CONNECT: + m_bIsConnected = evt->connect.is_connected; + break; + /*IGNORE any scene size, just work with the size allocated in the parent doc*/ + case GF_EVENT_SCENE_SIZE: + gf_term_set_size(m_term, m_width, m_height); + break; + /*window has been resized (full-screen plugin), resize*/ + case GF_EVENT_SIZE: + m_width = evt->size.width; + m_height = evt->size.height; + gf_term_set_size(m_term, m_width, m_height); + break; + case GF_EVENT_DBLCLICK: + gf_term_set_option(m_term, GF_OPT_FULLSCREEN, !gf_term_get_option(m_term, GF_OPT_FULLSCREEN)); + break; + case GF_EVENT_KEYDOWN: + if ((evt->key.flags & GF_KEY_MOD_ALT)) { + } else { + switch (evt->key.key_code) { + case GF_KEY_HOME: + gf_term_set_option(m_term, GF_OPT_NAVIGATION_TYPE, 1); + break; + case GF_KEY_ESCAPE: + gf_term_set_option(m_term, GF_OPT_FULLSCREEN, !gf_term_get_option(m_term, GF_OPT_FULLSCREEN)); + break; + } + } + break; + case GF_EVENT_NAVIGATE_INFO: + strcpy(msg, evt->navigate.to_url); + SetStatusText(msg); + break; + case GF_EVENT_NAVIGATE: + if (gf_term_is_supported_url(m_term, evt->navigate.to_url, GF_TRUE, GF_TRUE)) { + gf_term_navigate_to(m_term, evt->navigate.to_url); + return GF_TRUE; + } +#ifndef _WIN32_WCE + else if (m_pBrowser) { + u32 i; + const char **sz_ptr; + u16 w_szTar[1024], w_szURL[1024]; + VARIANT target, flags; + flags.intVal = 0; + target.bstrVal = L"_SELF"; + + for (i=0; inavigate.param_count; i++) { + if (!strcmp(evt->navigate.parameters[i], "_parent")) target.bstrVal = L"_PARENT"; + else if (!strcmp(evt->navigate.parameters[i], "_blank")) target.bstrVal = L"_BLANK"; + else if (!strcmp(evt->navigate.parameters[i], "_top")) target.bstrVal = L"_TOP"; + else if (!strcmp(evt->navigate.parameters[i], "_new")) flags.intVal |= navOpenInNewWindow; + else if (!strnicmp(evt->navigate.parameters[i], "_target=", 8)) { + sz_ptr = & evt->navigate.parameters[i]+8; + gf_utf8_mbstowcs(w_szTar, 1024, (const char **)sz_ptr); + target.bstrVal = (BSTR) w_szTar; + } + } + sz_ptr = & evt->navigate.to_url; + gf_utf8_mbstowcs(w_szURL, 1024, (const char **)sz_ptr); + m_pBrowser->Navigate((BSTR) w_szURL, &flags, &target, NULL, NULL);; + return GF_TRUE; + } +#endif + break; + } + return GF_FALSE; +} + +Bool GPAX_EventProc(void *ptr, GF_Event *evt) +{ + CGPAXPlugin *_this = (CGPAXPlugin *)ptr; + return _this->EventProc(evt); +} + +//Read Parameters from pPropBag given by MSIE +Bool CGPAXPlugin::ReadParamString(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog, + WCHAR *name, char *buf, int bufsize) +{ + VARIANT v; + HRESULT hr; + Bool retval = GF_FALSE; + + v.vt = VT_EMPTY; + v.bstrVal = NULL; + hr = pPropBag->Read(name, &v, pErrorLog); + if(SUCCEEDED(hr)) + { + if(v.vt==VT_BSTR && v.bstrVal) + { +// USES_CONVERSION; +// lstrcpyn(buf,OLE2T(v.bstrVal),bufsize); + const u16 *srcp = (const u16 *) v.bstrVal; + size_t len = gf_utf8_wcstombs(buf, bufsize, &srcp); + if (len>=0) { + buf[(u32) len] = 0; + retval = GF_TRUE; + } + } + VariantClear(&v); + } + return retval; +} + +void CGPAXPlugin::LoadDATAUrl() +{ +#ifndef _WIN32_WCE + HRESULT hr; + + if (m_url[0]) return; + /*get parent doc*/ + CComPtr spContainer; + if (m_spClientSite->GetContainer(&spContainer) != S_OK) + return; + CComPtr spDoc = CComQIPtr(spContainer); + CComPtr spColl; + if (spDoc->get_all(&spColl) != S_OK) + return; + /*get HTML in the doc*/ + CComPtr spDisp; + CComPtr sphtmlObjects; + + CComPtr spDispObjects; + if (spColl->tags(CComVariant("OBJECT"), &spDispObjects) != S_OK) + return; + CComPtr spObjs = CComQIPtr(spDispObjects); + + /*browse all objects and find us*/ + long lCount = 0; + spObjs->get_length(&lCount); + for (long lCnt = 0; lCnt < lCount; lCnt++) { + IDispatch *an_obj= NULL; + CComVariant varEmpty; + CComVariant varName; + varName.vt = VT_I4; + varName.lVal = lCnt; + hr = spObjs->item(varName, varEmpty, &an_obj); + varName.Clear(); + varEmpty.Clear(); + if (hr != S_OK) continue; + + /*get the IHTMLObject*/ + IHTMLObjectElement* pObjectElem=NULL; + an_obj->QueryInterface(IID_IHTMLObjectElement, (void**)&pObjectElem); + if (!pObjectElem) continue; + + /*get its parent owner - it MUST be us*/ + IDispatch *disp= NULL; + pObjectElem->get_object(&disp); + if (disp != this) continue; + + BSTR data = NULL; + if ((pObjectElem->get_data(&data) == S_OK) && data) { + const u16 *srcp = (const u16 *) data; + size_t len = gf_utf8_wcstombs(m_url, MAXLEN_URL, &srcp); + if (len>=0) m_url[(u32) len] = 0; + } + SysFreeString(data); + break; + } + + if (m_url) { + UpdateURL(); + } +#endif + +} + +// trap keys and forward on to the control +BOOL CGPAXPlugin::PreTranslateMessage(MSG* pMsg) +{ + switch (pMsg->message) + { + case WM_KEYDOWN: + case WM_KEYUP: + switch (pMsg->wParam) + { + case VK_UP: + case VK_DOWN: + case VK_LEFT: + case VK_RIGHT: + case VK_HOME: + case VK_END: + SendMessage (pMsg->message, pMsg->wParam, pMsg->lParam); + // Windowless controls won't be able to call SendMessage. + // Instead, just respond to the message here. + return TRUE; + } + break; + } + return FALSE; +// return COleControl::PreTranslateMessage(pMsg); +} + + + + +#define GPAC_REG_KEY HKEY_CURRENT_USER + +static void gpax_do_log(void *cbk, GF_LOG_Level level, GF_LOG_Tool tool, const char *fmt, va_list list) +{ + FILE *logs = (FILE *) cbk; + vfprintf(logs, fmt, list); + fflush(logs); +} + +//Create window message fuction. when the window is created, also initialize a instance of +//GPAC player instance. +LRESULT CGPAXPlugin::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) +{ + if (m_term) return 0; + const char *str; + + if (m_hWnd==NULL) return 0; + + gf_sys_init(GF_MemTrackerNone); + + //Create a structure m_user for initialize the terminal. the parameters to set: + //1)config file path + //2)Modules file path + //3)window handler + //4)EventProc + memset(&m_user, 0, sizeof(m_user)); + + gf_log_set_tool_level(GF_LOG_ALL, GF_LOG_ERROR); + + m_user.config = gf_cfg_init(NULL, NULL); + if(!m_user.config) { +#ifdef _WIN32_WCE + ::MessageBox(NULL, _T("GPAC Configuration file not found"), _T("Fatal Error"), MB_OK); +#else + ::MessageBox(NULL, "GPAC Configuration file not found", "Fatal Error", MB_OK); +#endif + goto err_exit; + } + + /*check log file*/ + str = gf_opts_get_key("General", "log-file"); + if (str) { + m_pLogs = gf_fopen(str, "wt"); + if (m_pLogs) gf_log_set_callback(m_pLogs, gpax_do_log); + } + + /*if logs are specified, use them*/ + gf_log_set_tools_levels( gf_cfg_get_key(m_user.config, "General", "Logs") ); + + + str = gf_cfg_get_key(m_user.config, "Core", "ModulesDirectory"); + m_user.modules = gf_modules_new(NULL, m_user.config); + if(!gf_modules_get_count(m_user.modules)) goto err_exit; + + m_user.os_window_handler = m_hWnd; + m_user.opaque = this; + m_user.EventProc = GPAX_EventProc; + + //create a terminal + m_term = gf_term_new(&m_user); + + if (!m_term) goto err_exit; + + gf_term_set_option(m_term, GF_OPT_AUDIO_VOLUME, 100); + + LoadDATAUrl(); + + RECT rc; + ::GetWindowRect(m_hWnd, &rc); + m_width = rc.right-rc.left; + m_height = rc.bottom-rc.top; + if (m_bAutoStart && strlen(m_url)) Play(); + return 0; + + //Error Processing +err_exit: + if(m_user.modules) + gf_modules_del(m_user.modules); + m_user.modules = NULL; + if(m_user.config) + gf_cfg_del(m_user.config); + m_user.config = NULL; + gf_sys_close(); + return 1; +} + +void CGPAXPlugin::UnloadTerm() +{ + if (m_term) { + GF_Terminal *a_term = m_term; + m_term = NULL; + gf_term_del(a_term); + } + if (m_user.modules) gf_modules_del(m_user.modules); + if (m_user.config) gf_cfg_del(m_user.config); + if (m_pLogs) + gf_fclose(m_pLogs); + m_pLogs = NULL; + gf_log_set_callback(NULL, NULL); + memset(&m_user, 0, sizeof(m_user)); + gf_sys_close(); +} +CGPAXPlugin::~CGPAXPlugin() +{ + UnloadTerm(); +#ifndef _WIN32_WCE + if (m_pBrowser) m_pBrowser->Release(); + m_pBrowser = NULL; +#endif +} +LRESULT CGPAXPlugin::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) +{ + UnloadTerm(); + return 0; +} + + +HRESULT CGPAXPlugin::OnDraw(ATL_DRAWINFO& di) +{ + if (m_term && m_bInitialDraw) { + m_bInitialDraw = GF_FALSE; + if (m_bAutoStart) Play(); + } + return S_OK; +} + +// Load is called before OnCreate, but it may not be called at +// all if there are no parameters. +STDMETHODIMP CGPAXPlugin::Load(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog) +{ + char szOpt[1024]; + // examine the / tag arguments + + m_url[0] = 0; + ReadParamString(pPropBag,pErrorLog,L"src", m_url, MAXLEN_URL); + if (!m_url[0]) + ReadParamString(pPropBag,pErrorLog,L"data", m_url, MAXLEN_URL); + + if (ReadParamString(pPropBag,pErrorLog,L"autostart", szOpt, 1024)) + m_bAutoStart = (!stricmp(szOpt, "false") || !stricmp(szOpt, "no")) ? GF_FALSE : GF_TRUE; + + if (ReadParamString(pPropBag,pErrorLog,L"use3d", szOpt, 1024)) + m_bUse3D = (!stricmp(szOpt, "true") || !stricmp(szOpt, "yes")) ? GF_TRUE : GF_FALSE; + + if (ReadParamString(pPropBag,pErrorLog,L"aspectratio", szOpt, 1024)) { + if (!stricmp(szOpt, "keep")) m_AR = GF_ASPECT_RATIO_KEEP; + else if (!stricmp(szOpt, "16:9")) m_AR = GF_ASPECT_RATIO_16_9; + else if (!stricmp(szOpt, "4:3")) m_AR = GF_ASPECT_RATIO_4_3; + else if (!stricmp(szOpt, "fill")) m_AR = GF_ASPECT_RATIO_FILL_SCREEN; + } + + if (ReadParamString(pPropBag,pErrorLog,L"loop", szOpt, 1024)) + m_bLoop = !stricmp(szOpt, "true") ? GF_FALSE : GF_TRUE; + + if (ReadParamString(pPropBag,pErrorLog,L"gui", szOpt, 1024)) + m_bUseGUI = (!stricmp(szOpt, "true") || !stricmp(szOpt, "yes")) ? GF_TRUE : GF_FALSE; + + UpdateURL(); + +#ifndef _WIN32_WCE + /*get the top-level container*/ + if (!m_pBrowser) { + IServiceProvider *isp, *isp2 = NULL; + if ( SUCCEEDED(m_spClientSite->QueryInterface(IID_IServiceProvider, reinterpret_cast(&isp)) ) ) { + + if (SUCCEEDED(isp->QueryService(SID_STopLevelBrowser, IID_IServiceProvider, reinterpret_cast(&isp2)) ) ) { + isp2->QueryService(SID_SWebBrowserApp, IID_IWebBrowser2, reinterpret_cast(&m_pBrowser)); + isp2->Release(); + } + isp->Release(); + } + } + if (m_pBrowser) m_pBrowser->put_StatusText(L"GPAC Ready"); +#endif + + return IPersistPropertyBagImpl::Load(pPropBag, pErrorLog); +} + +void CGPAXPlugin::UpdateURL() +{ + /*get absolute URL*/ + if (!strlen(m_url)) return; + IMoniker* pMoniker = NULL; + LPOLESTR sDisplayName; + + if (SUCCEEDED(m_spClientSite->GetMoniker(OLEGETMONIKER_TEMPFORUSER, + OLEWHICHMK_CONTAINER, + &pMoniker) ) ) { + char parent_url[1024]; + pMoniker->GetDisplayName(NULL, NULL, &sDisplayName); + wcstombs(parent_url, sDisplayName, 300); + pMoniker->Release(); + + char *abs_url = gf_url_concatenate(parent_url, m_url); + if (abs_url) { + strcpy(m_url, abs_url); + gf_free(abs_url); + } + } +} + +STDMETHODIMP CGPAXPlugin::Save(LPPROPERTYBAG pPropBag, BOOL fClearDirty, BOOL fSaveAllProperties) +{ + u16 wurl[MAXLEN_URL]; + const char *sptr; + u16 len; + + VARIANT value; + if( pPropBag == NULL) return E_INVALIDARG; + + VariantInit(&value); + + V_VT(&value) = VT_BOOL; + V_BOOL(&value) = m_bAutoStart ? VARIANT_TRUE : VARIANT_FALSE; + pPropBag->Write(OLESTR("AutoStart"), &value); + VariantClear(&value); + + V_VT(&value) = VT_BSTR; + + sptr = (const char *)m_url; + len = (u16) gf_utf8_mbstowcs(wurl, MAXLEN_URL, &sptr); + V_BSTR(&value) = SysAllocStringLen(NULL, len+1); + memcpy(V_BSTR(&value) , wurl, len*sizeof(u16)); + V_BSTR(&value) [len] = 0; + + pPropBag->Write(OLESTR("src"), &value); + VariantClear(&value); + return S_OK; +} + +STDMETHODIMP CGPAXPlugin::Play() +{ + if (m_term) { + if (!m_bIsConnected) { + if (strlen(m_url)) { + /*connect from 0 and pause if not autoplay*/ + const char *gui = gf_cfg_get_key(m_user.config, "General", "StartupFile"); + if (gui && m_bUseGUI) { + gf_cfg_set_key(m_user.config, "Temp", "BrowserMode", "yes"); + gf_cfg_set_key(m_user.config, "Temp", "GUIStartupFile", m_url); + gf_term_connect(m_term, gui); + } else { + gf_term_connect(m_term, m_url); + } + gf_term_set_option(m_term, GF_OPT_ASPECT_RATIO, m_AR); + } + } else + gf_term_set_option(m_term, GF_OPT_PLAY_STATE, GF_STATE_PLAYING); //if target is connected, set it playing + } + return S_OK; +} + +STDMETHODIMP CGPAXPlugin::Pause() +{ + if(m_term) { + if (gf_term_get_option(m_term, GF_OPT_PLAY_STATE) == GF_STATE_PAUSED) { + gf_term_set_option(m_term, GF_OPT_PLAY_STATE, GF_STATE_PLAYING); + } else { + gf_term_set_option(m_term, GF_OPT_PLAY_STATE, GF_STATE_PAUSED); + } + } + return S_OK; +} + +STDMETHODIMP CGPAXPlugin::Stop() +{ + if(m_term) gf_term_disconnect(m_term); //set it stop + return S_OK; +} + +STDMETHODIMP CGPAXPlugin::QualitySwitch(int switch_up) +{ + if (m_term) gf_term_switch_quality(m_term, switch_up ? GF_TRUE : GF_FALSE); + return S_OK; +} + +STDMETHODIMP CGPAXPlugin::SetURL(BSTR _url) +{ + if (m_term) { + u16 *srcp; + u32 len; + char *url; + + srcp = (u16 *)_url; + len = (u32) gf_utf8_wcstombs(NULL, 0, (const u16 **)&srcp); + if (len) { + url = (char *) gf_malloc(sizeof(char) * (len+1)); + srcp = (u16 *)_url; + len = (u32) gf_utf8_wcstombs(url, len, (const u16 **)&srcp); + url[len] = 0; + strcpy(m_url, url); + gf_term_connect(m_term, url); + gf_free(url); + } + } + return S_OK; +} + +STDMETHODIMP CGPAXPlugin::Update(BSTR _mtype, BSTR _updates) +{ + if (m_term) { + u16 *srcp; + u32 len; + char mtype[1024], *updates; + + srcp = (u16 *) _mtype; + len = (u32) gf_utf8_wcstombs(mtype, 1024, (const u16 **)&srcp); + mtype[len] = 0; + + srcp = (u16 *)_updates; + len = (u32) gf_utf8_wcstombs(NULL, 0, (const u16 **)&srcp); + if (len) { + updates = (char *) gf_malloc(sizeof(char) * (len+1)); + srcp = (u16 *)_updates; + len = (u32) gf_utf8_wcstombs(updates, len, (const u16 **)&srcp); + updates[len] = 0; + gf_term_scene_update(m_term, mtype, updates); + gf_free(updates); + } + } + return S_OK; +} + +STDMETHODIMP CGPAXPlugin::get_src(BSTR *url) +{ + u16 wurl[MAXLEN_URL]; + const char *sptr; + u16 len; + if (url==NULL) return E_POINTER; + + sptr = (const char *)m_url; + len = (u32) gf_utf8_mbstowcs(wurl, MAXLEN_URL, &sptr); + *url = SysAllocStringLen(NULL, len+1); + memcpy(*url, wurl, len*sizeof(u16)); + *url[len] = 0; + return S_OK; +} +STDMETHODIMP CGPAXPlugin::put_src(BSTR url) +{ + const u16 *srcp = (const u16 *)url; + u32 len = (u32) gf_utf8_wcstombs(m_url, MAXLEN_URL, &srcp); + m_url[len] = 0; + UpdateURL(); + return S_OK; +} + +STDMETHODIMP CGPAXPlugin::get_AutoStart(VARIANT_BOOL *as) +{ + if (as==NULL) return E_POINTER; + *as = m_bAutoStart ? VARIANT_TRUE: VARIANT_FALSE; + return S_OK; +} +STDMETHODIMP CGPAXPlugin::put_AutoStart(VARIANT_BOOL as) +{ + m_bAutoStart = (as !=VARIANT_FALSE) ? GF_TRUE : GF_FALSE; + return S_OK; +} + +STDMETHODIMP CGPAXPlugin::get_DownloadProgress(INT *dp) +{ + if (dp==NULL) return E_POINTER; + *dp = m_iDownload_progress; + return S_OK; +} +STDMETHODIMP CGPAXPlugin::put_DownloadProgress(INT dp) +{ + return S_OK; +} + +STDMETHODIMP CGPAXPlugin::GetInterfaceSafetyOptions( + REFIID riid, + DWORD *pdwSupportedOptions, + DWORD *pdwEnabledOptions +) +{ + if( (NULL == pdwSupportedOptions) || (NULL == pdwEnabledOptions) ) + return E_POINTER; + + *pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA|INTERFACESAFE_FOR_UNTRUSTED_CALLER; + + if ((IID_IDispatch == riid) || (IID_IGPAX == riid)) { + *pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER; + return NOERROR; + } + else if (IID_IPersistPropertyBag == riid) { + *pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA; + return NOERROR; + } + *pdwEnabledOptions = 0; + return E_NOINTERFACE; +}; + +STDMETHODIMP CGPAXPlugin::SetInterfaceSafetyOptions( + REFIID riid, + DWORD dwOptionSetMask, + DWORD dwEnabledOptions +) +{ + if ((IID_IDispatch == riid) || (IID_IGPAX == riid) ) { + if( (INTERFACESAFE_FOR_UNTRUSTED_CALLER == dwOptionSetMask) + && (INTERFACESAFE_FOR_UNTRUSTED_CALLER == dwEnabledOptions) ) { + return NOERROR; + } + return E_FAIL; + } + else if (IID_IPersistPropertyBag == riid) { + if( (INTERFACESAFE_FOR_UNTRUSTED_DATA == dwOptionSetMask) + && (INTERFACESAFE_FOR_UNTRUSTED_DATA == dwEnabledOptions) ) { + return NOERROR; + } + return E_FAIL; + } + return E_FAIL; +}; + diff --git a/applications/deprecated/old_arch/GPAX/GPAXPlugin.h b/applications/deprecated/old_arch/GPAX/GPAXPlugin.h new file mode 100644 index 0000000..651399c --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/GPAXPlugin.h @@ -0,0 +1,269 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2006-2012 + * All rights reserved + * + * This file is part of GPAC / ActiveX control + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __GPAXPLUGIN_H_ +#define __GPAXPLUGIN_H_ + +#define MAXLEN_URL 300 + +#include "resource.h" // main symbols +#include +#include + + + +#include +#include +#include +#include +#include + +#if (_MSC_VER >= 1300) +using namespace ATL; +#endif + + +Bool GPAX_EventProc(void *ptr, GF_Event *evt); + +///////////////////////////////////////////////////////////////////////////// +// CGPAXPlugin +class ATL_NO_VTABLE CGPAXPlugin : + public CComObjectRootEx, + public IDispatchImpl, + public CComControl, + public CComCoClass, + public IOleControlImpl, + public IOleObjectImpl, + public IOleInPlaceActiveObjectImpl, + public IViewObjectExImpl, + public IOleInPlaceObjectWindowlessImpl, + public IProvideClassInfo2Impl<&CLSID_GPAX, &DIID_IGPAXEvents, &LIBID_GPAXLib>, + + public IPersistStreamInitImpl, + public ISupportErrorInfo, + public IConnectionPointContainerImpl, + public IPersistStorageImpl, + public ISpecifyPropertyPagesImpl, + public IQuickActivateImpl, + public IDataObjectImpl, + public IPropertyNotifySinkCP, + + public IPersistPropertyBagImpl, + public IObjectSafetyImpl + +{ +public: + CGPAXPlugin() { + m_term = NULL; + m_bAutoStart = GF_TRUE; + m_bInitialDraw = GF_TRUE; + m_bWindowOnly = GF_TRUE; //to declare that the control is a window control in order + //to inherit the member variable m_hWnd which contains the window handler + m_bIsConnected = GF_FALSE; + m_bUse3D = GF_FALSE; + m_bUseGUI = GF_FALSE; + m_iDownload_progress = 0; + m_AR = GF_ASPECT_RATIO_KEEP; + m_url[0] = 0; + m_pLogs = NULL; +#ifndef _WIN32_WCE + m_pBrowser = NULL; +#endif + memset(&m_user, 0, sizeof(m_user)); + + m_dwCurrentSafety = INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA; + } + + ~CGPAXPlugin(); + + Bool EventProc(GF_Event *evt); + + BOOL PreTranslateMessage(MSG* pMsg); + + DECLARE_REGISTRY_RESOURCEID(IDR_GPAXPLUGIN) + DECLARE_PROTECT_FINAL_CONSTRUCT() +#if (_MSC_VER >= 1300) + DECLARE_OLEMISC_STATUS(OLEMISC_ACTSLIKEBUTTON | OLEMISC_ACTIVATEWHENVISIBLE) +#endif + + static LPCTSTR GetWindowClassName() { + return TEXT("GPAC ActiveX"); + } + + BEGIN_COM_MAP(CGPAXPlugin) + COM_INTERFACE_ENTRY(IGPAX) + COM_INTERFACE_ENTRY(IDispatch) + COM_INTERFACE_ENTRY(IViewObjectEx) + COM_INTERFACE_ENTRY(IProvideClassInfo) + COM_INTERFACE_ENTRY(IOleControl) + COM_INTERFACE_ENTRY(IOleObject) + + COM_INTERFACE_ENTRY_IMPL(IViewObjectEx) + COM_INTERFACE_ENTRY_IMPL_IID(IID_IViewObject2, IViewObjectEx) + COM_INTERFACE_ENTRY_IMPL_IID(IID_IViewObject, IViewObjectEx) + COM_INTERFACE_ENTRY_IMPL_IID(IID_IOleWindow, IOleInPlaceObjectWindowless) + COM_INTERFACE_ENTRY_IMPL_IID(IID_IOleInPlaceObject, IOleInPlaceObjectWindowless) + COM_INTERFACE_ENTRY_IMPL_IID(IID_IOleWindow, IOleInPlaceActiveObject) + + COM_INTERFACE_ENTRY_IMPL(IOleInPlaceActiveObject) + COM_INTERFACE_ENTRY_IMPL(IOleInPlaceObjectWindowless) + +// COM_INTERFACE_ENTRY(IObjectSafety) + COM_INTERFACE_ENTRY_IID(IID_IObjectSafety, IObjectSafety) + COM_INTERFACE_ENTRY(IPersistPropertyBag) + COM_INTERFACE_ENTRY_IMPL_IID(IID_IPersist, IPersistPropertyBag) + + /* COM_INTERFACE_ENTRY(IViewObject) + COM_INTERFACE_ENTRY(IViewObject2) + COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless) + COM_INTERFACE_ENTRY(IOleInPlaceObject) + */ + + COM_INTERFACE_ENTRY(IProvideClassInfo2) + COM_INTERFACE_ENTRY(IPersistStreamInit) + COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit) + COM_INTERFACE_ENTRY(ISupportErrorInfo) + COM_INTERFACE_ENTRY(IConnectionPointContainer) + COM_INTERFACE_ENTRY(ISpecifyPropertyPages) + COM_INTERFACE_ENTRY(IQuickActivate) + COM_INTERFACE_ENTRY(IPersistStorage) + COM_INTERFACE_ENTRY(IDataObject) + + END_COM_MAP() + + BEGIN_PROP_MAP(CGPAXPlugin) + END_PROP_MAP() + + BEGIN_CONNECTION_POINT_MAP(CGPAXPlugin) + CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink) + END_CONNECTION_POINT_MAP() + + BEGIN_MSG_MAP(CGPAXPlugin) + CHAIN_MSG_MAP(CComControl) + DEFAULT_REFLECTION_HANDLER() + MESSAGE_HANDLER(WM_CREATE, OnCreate) + MESSAGE_HANDLER(WM_DESTROY, OnDestroy) + + END_MSG_MAP() + // Handler prototypes: + // LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + // LRESULT CommandHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled); + // LRESULT NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled); + + + + // ISupportsErrorInfo + STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid) + { + static const IID* arr[] = + { + &IID_IGPAX, + }; + for (int i=0; i + // + // + //the interface IPersistPropertyBag enable MSIE and ActiveX Control to communicate these + //properties included in tags + STDMETHODIMP Load(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog); + STDMETHODIMP Save(LPPROPERTYBAG, BOOL, BOOL); + + +private: + Bool ReadParamString(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog, WCHAR *name, char *buf, int bufsize); + void SetStatusText(char *msg); + void UpdateURL(); + void UnloadTerm(); + void LoadDATAUrl(); + + GF_Terminal *m_term; + GF_User m_user; + char m_url[MAXLEN_URL]; +#ifndef _WIN32_WCE + /*pointer to the parent browser if any*/ + IWebBrowser2 *m_pBrowser; +#endif + + u32 m_width, m_height, m_AR; + Bool m_bIsConnected, m_bInitialDraw, m_bAutoStart, m_bUse3D, m_bLoop, m_bUseGUI; + int m_iDownload_progress; + FILE *m_pLogs; + +}; + + + +#endif //__GPAXPLUGIN_H_ diff --git a/applications/deprecated/old_arch/GPAX/GPAX_i.c b/applications/deprecated/old_arch/GPAX/GPAX_i.c new file mode 100644 index 0000000..82d1beb --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/GPAX_i.c @@ -0,0 +1,178 @@ + +#pragma warning( disable: 4049 ) /* more than 64k source lines */ + +/* this ALWAYS GENERATED file contains the IIDs and CLSIDs */ + +/* link this file in with the server and any clients */ + + +/* File created by MIDL compiler version 5.03.0286 */ +/* at Thu Jul 20 19:14:15 2006 + */ +/* Compiler settings for \CVS\gpac\applications\GPAX\GPAX.idl: + Oicf (OptLev=i2), W1, Zp8, env=Win32 (32b run), ms_ext, c_ext + error checks: allocation ref bounds_check enum stub_data + VC __declspec() decoration level: + __declspec(uuid()), __declspec(selectany), __declspec(novtable) + DECLSPEC_UUID(), MIDL_INTERFACE() +*/ +//@@MIDL_FILE_HEADING( ) + +#if !defined(_M_IA64) && !defined(_M_AXP64) + +#ifdef __cplusplus +extern "C" { +#endif + + +#include +#include + +#ifdef _MIDL_USE_GUIDDEF_ + +#ifndef INITGUID +#define INITGUID +#include +#undef INITGUID +#else +#include +#endif + +#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ + DEFINE_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) + +#else // !_MIDL_USE_GUIDDEF_ + +#ifndef __IID_DEFINED__ +#define __IID_DEFINED__ + +typedef struct _IID +{ + unsigned long x; + unsigned short s1; + unsigned short s2; + unsigned char c[8]; +} IID; + +#endif // __IID_DEFINED__ + +#ifndef CLSID_DEFINED +#define CLSID_DEFINED +typedef IID CLSID; +#endif // CLSID_DEFINED + +#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ + const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} + +#endif !_MIDL_USE_GUIDDEF_ + +MIDL_DEFINE_GUID(IID, LIBID_GPAXLib,0xE64FAC7F,0x0134,0x4A75,0xA7,0xDA,0x80,0xD5,0x3E,0xBC,0x56,0xA6); + + +MIDL_DEFINE_GUID(IID, IID_IGPAX,0xE2A9A937,0xBB35,0x47E0,0x89,0x42,0x96,0x48,0x06,0x29,0x9A,0xB4); + + +MIDL_DEFINE_GUID(IID, DIID_IGPAXEvents,0x1FDA32FC,0x4C9A,0x461F,0xB3,0x3B,0x07,0x15,0xB0,0x34,0x30,0x06); + + +MIDL_DEFINE_GUID(CLSID, CLSID_GPAX,0x181D18E6,0x4DC1,0x4B55,0xB7,0x2E,0xBE,0x2A,0x10,0x06,0x49,0x95); + +#undef MIDL_DEFINE_GUID + +#ifdef __cplusplus +} +#endif + + + +#endif /* !defined(_M_IA64) && !defined(_M_AXP64)*/ + + +#pragma warning( disable: 4049 ) /* more than 64k source lines */ + +/* this ALWAYS GENERATED file contains the IIDs and CLSIDs */ + +/* link this file in with the server and any clients */ + + +/* File created by MIDL compiler version 5.03.0286 */ +/* at Thu Jul 20 19:14:15 2006 + */ +/* Compiler settings for \CVS\gpac\applications\GPAX\GPAX.idl: + Oicf (OptLev=i2), W1, Zp8, env=Win64 (32b run,appending), ms_ext, c_ext, robust + error checks: allocation ref bounds_check enum stub_data + VC __declspec() decoration level: + __declspec(uuid()), __declspec(selectany), __declspec(novtable) + DECLSPEC_UUID(), MIDL_INTERFACE() +*/ +//@@MIDL_FILE_HEADING( ) + +#if defined(_M_IA64) || defined(_M_AXP64) + +#ifdef __cplusplus +extern "C" { +#endif + + +#include +#include + +#ifdef _MIDL_USE_GUIDDEF_ + +#ifndef INITGUID +#define INITGUID +#include +#undef INITGUID +#else +#include +#endif + +#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ + DEFINE_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) + +#else // !_MIDL_USE_GUIDDEF_ + +#ifndef __IID_DEFINED__ +#define __IID_DEFINED__ + +typedef struct _IID +{ + unsigned long x; + unsigned short s1; + unsigned short s2; + unsigned char c[8]; +} IID; + +#endif // __IID_DEFINED__ + +#ifndef CLSID_DEFINED +#define CLSID_DEFINED +typedef IID CLSID; +#endif // CLSID_DEFINED + +#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ + const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} + +#endif !_MIDL_USE_GUIDDEF_ + +MIDL_DEFINE_GUID(IID, LIBID_GPAXLib,0xE64FAC7F,0x0134,0x4A75,0xA7,0xDA,0x80,0xD5,0x3E,0xBC,0x56,0xA6); + + +MIDL_DEFINE_GUID(IID, IID_IGPAX,0xE2A9A937,0xBB35,0x47E0,0x89,0x42,0x96,0x48,0x06,0x29,0x9A,0xB4); + + +MIDL_DEFINE_GUID(IID, DIID_IGPAXEvents,0x1FDA32FC,0x4C9A,0x461F,0xB3,0x3B,0x07,0x15,0xB0,0x34,0x30,0x06); + + +MIDL_DEFINE_GUID(CLSID, CLSID_GPAX,0x181D18E6,0x4DC1,0x4B55,0xB7,0x2E,0xBE,0x2A,0x10,0x06,0x49,0x95); + +#undef MIDL_DEFINE_GUID + +#ifdef __cplusplus +} +#endif + + + +#endif /* defined(_M_IA64) || defined(_M_AXP64)*/ + diff --git a/applications/deprecated/old_arch/GPAX/GPAX_p.c b/applications/deprecated/old_arch/GPAX/GPAX_p.c new file mode 100644 index 0000000..16cec2e --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/GPAX_p.c @@ -0,0 +1,603 @@ +/* this ALWAYS GENERATED file contains the proxy stub code */ + + +/* File created by MIDL compiler version 5.01.0164 */ +/* at Mon Jul 17 15:58:48 2006 + */ +/* Compiler settings for D:\CVS\gpac\applications\GPAX\GPAX.idl: + Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext + error checks: allocation ref bounds_check enum stub_data +*/ +//@@MIDL_FILE_HEADING( ) + +#define USE_STUBLESS_PROXY + + +/* verify that the version is high enough to compile this file*/ +#ifndef __REDQ_RPCPROXY_H_VERSION__ +#define __REQUIRED_RPCPROXY_H_VERSION__ 440 +#endif + + +#include "rpcproxy.h" +#ifndef __RPCPROXY_H_VERSION__ +#error this stub requires an updated version of +#endif // __RPCPROXY_H_VERSION__ + + +#include "GPAX.h" + +#define TYPE_FORMAT_STRING_SIZE 59 +#define PROC_FORMAT_STRING_SIZE 213 + +typedef struct _MIDL_TYPE_FORMAT_STRING +{ + short Pad; + unsigned char Format[ TYPE_FORMAT_STRING_SIZE ]; +} MIDL_TYPE_FORMAT_STRING; + +typedef struct _MIDL_PROC_FORMAT_STRING +{ + short Pad; + unsigned char Format[ PROC_FORMAT_STRING_SIZE ]; +} MIDL_PROC_FORMAT_STRING; + + +extern const MIDL_TYPE_FORMAT_STRING __MIDL_TypeFormatString; +extern const MIDL_PROC_FORMAT_STRING __MIDL_ProcFormatString; + + +/* Standard interface: __MIDL_itf_GPAX_0000, ver. 0.0, + GUID={0x00000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}} */ + + +/* Object interface: IUnknown, ver. 0.0, + GUID={0x00000000,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}} */ + + +/* Object interface: IDispatch, ver. 0.0, + GUID={0x00020400,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}} */ + + +/* Object interface: IGPAX, ver. 0.0, + GUID={0xE2A9A937,0xBB35,0x47E0,{0x89,0x42,0x96,0x48,0x06,0x29,0x9A,0xB4}} */ + + +extern const MIDL_STUB_DESC Object_StubDesc; + + +extern const MIDL_SERVER_INFO IGPAX_ServerInfo; + +#pragma code_seg(".orpc") +extern const USER_MARSHAL_ROUTINE_QUADRUPLE UserMarshalRoutines[1]; + +static const MIDL_STUB_DESC Object_StubDesc = +{ + 0, + NdrOleAllocate, + NdrOleFree, + 0, + 0, + 0, + 0, + 0, + __MIDL_TypeFormatString.Format, + 1, /* -error bounds_check flag */ + 0x20000, /* Ndr library version */ + 0, + 0x50100a4, /* MIDL Version 5.1.164 */ + 0, + UserMarshalRoutines, + 0, /* notify & notify_flag routine table */ + 1, /* Flags */ + 0, /* Reserved3 */ + 0, /* Reserved4 */ + 0 /* Reserved5 */ +}; + +static const unsigned short IGPAX_FormatStringOffsetTable[] = +{ + (unsigned short) -1, + (unsigned short) -1, + (unsigned short) -1, + (unsigned short) -1, + 0, + 22, + 44, + 66, + 100, + 128, + 156, + 184 +}; + +static const MIDL_SERVER_INFO IGPAX_ServerInfo = +{ + &Object_StubDesc, + 0, + __MIDL_ProcFormatString.Format, + &IGPAX_FormatStringOffsetTable[-3], + 0, + 0, + 0, + 0 +}; + +static const MIDL_STUBLESS_PROXY_INFO IGPAX_ProxyInfo = +{ + &Object_StubDesc, + __MIDL_ProcFormatString.Format, + &IGPAX_FormatStringOffsetTable[-3], + 0, + 0, + 0 +}; + +CINTERFACE_PROXY_VTABLE(15) _IGPAXProxyVtbl = +{ + &IGPAX_ProxyInfo, + &IID_IGPAX, + IUnknown_QueryInterface_Proxy, + IUnknown_AddRef_Proxy, + IUnknown_Release_Proxy , + 0 /* (void *)-1 /* IDispatch::GetTypeInfoCount */ , + 0 /* (void *)-1 /* IDispatch::GetTypeInfo */ , + 0 /* (void *)-1 /* IDispatch::GetIDsOfNames */ , + 0 /* IDispatch_Invoke_Proxy */ , + (void *)-1 /* IGPAX::Play */ , + (void *)-1 /* IGPAX::Pause */ , + (void *)-1 /* IGPAX::Stop */ , + (void *)-1 /* IGPAX::Update */ , + (void *)-1 /* IGPAX::QualitySwitch */ , + (void *)-1 /* IGPAX::get_URL */ , + (void *)-1 /* IGPAX::put_URL */ , + (void *)-1 /* IGPAX::get_AutoStart */ , + (void *)-1 /* IGPAX::put_AutoStart */ +}; + + +static const PRPC_STUB_FUNCTION IGPAX_table[] = +{ + STUB_FORWARDING_FUNCTION, + STUB_FORWARDING_FUNCTION, + STUB_FORWARDING_FUNCTION, + STUB_FORWARDING_FUNCTION, + NdrStubCall2, + NdrStubCall2, + NdrStubCall2, + NdrStubCall2, + NdrStubCall2, + NdrStubCall2, + NdrStubCall2, + NdrStubCall2 +}; + +CInterfaceStubVtbl _IGPAXStubVtbl = +{ + &IID_IGPAX, + &IGPAX_ServerInfo, + 15, + &IGPAX_table[-3], + CStdStubBuffer_DELEGATING_METHODS +}; + +#pragma data_seg(".rdata") + +static const USER_MARSHAL_ROUTINE_QUADRUPLE UserMarshalRoutines[1] = +{ + + { + BSTR_UserSize + ,BSTR_UserMarshal + ,BSTR_UserUnmarshal + ,BSTR_UserFree + } + +}; + + +#if !defined(__RPC_WIN32__) +#error Invalid build platform for this stub. +#endif + +#if !(TARGET_IS_NT40_OR_LATER) +#error You need a Windows NT 4.0 or later to run this stub because it uses these features: +#error -Oif or -Oicf, [wire_marshal] or [user_marshal] attribute, more than 32 methods in the interface. +#error However, your C/C++ compilation flags indicate you intend to run this app on earlier systems. +#error This app will die there with the RPC_X_WRONG_STUB_VERSION error. +#endif + + +static const MIDL_PROC_FORMAT_STRING __MIDL_ProcFormatString = +{ + 0, + { + + /* Procedure Play */ + + 0x33, /* FC_AUTO_HANDLE */ + 0x6c, /* Old Flags: object, Oi2 */ + /* 2 */ NdrFcLong( 0x0 ), /* 0 */ + /* 6 */ NdrFcShort( 0x7 ), /* 7 */ +#ifndef _ALPHA_ + /* 8 */ NdrFcShort( 0x8 ), /* x86, MIPS, PPC Stack size/offset = 8 */ +#else + NdrFcShort( 0x10 ), /* Alpha Stack size/offset = 16 */ +#endif + /* 10 */ NdrFcShort( 0x0 ), /* 0 */ + /* 12 */ NdrFcShort( 0x8 ), /* 8 */ + /* 14 */ 0x4, /* Oi2 Flags: has return, */ + 0x1, /* 1 */ + + /* Return value */ + + /* 16 */ NdrFcShort( 0x70 ), /* Flags: out, return, base type, */ +#ifndef _ALPHA_ + /* 18 */ NdrFcShort( 0x4 ), /* x86, MIPS, PPC Stack size/offset = 4 */ +#else + NdrFcShort( 0x8 ), /* Alpha Stack size/offset = 8 */ +#endif + /* 20 */ 0x8, /* FC_LONG */ + 0x0, /* 0 */ + + /* Procedure Pause */ + + /* 22 */ 0x33, /* FC_AUTO_HANDLE */ + 0x6c, /* Old Flags: object, Oi2 */ + /* 24 */ NdrFcLong( 0x0 ), /* 0 */ + /* 28 */ NdrFcShort( 0x8 ), /* 8 */ +#ifndef _ALPHA_ + /* 30 */ NdrFcShort( 0x8 ), /* x86, MIPS, PPC Stack size/offset = 8 */ +#else + NdrFcShort( 0x10 ), /* Alpha Stack size/offset = 16 */ +#endif + /* 32 */ NdrFcShort( 0x0 ), /* 0 */ + /* 34 */ NdrFcShort( 0x8 ), /* 8 */ + /* 36 */ 0x4, /* Oi2 Flags: has return, */ + 0x1, /* 1 */ + + /* Return value */ + + /* 38 */ NdrFcShort( 0x70 ), /* Flags: out, return, base type, */ +#ifndef _ALPHA_ + /* 40 */ NdrFcShort( 0x4 ), /* x86, MIPS, PPC Stack size/offset = 4 */ +#else + NdrFcShort( 0x8 ), /* Alpha Stack size/offset = 8 */ +#endif + /* 42 */ 0x8, /* FC_LONG */ + 0x0, /* 0 */ + + /* Procedure Stop */ + + /* 44 */ 0x33, /* FC_AUTO_HANDLE */ + 0x6c, /* Old Flags: object, Oi2 */ + /* 46 */ NdrFcLong( 0x0 ), /* 0 */ + /* 50 */ NdrFcShort( 0x9 ), /* 9 */ +#ifndef _ALPHA_ + /* 52 */ NdrFcShort( 0x8 ), /* x86, MIPS, PPC Stack size/offset = 8 */ +#else + NdrFcShort( 0x10 ), /* Alpha Stack size/offset = 16 */ +#endif + /* 54 */ NdrFcShort( 0x0 ), /* 0 */ + /* 56 */ NdrFcShort( 0x8 ), /* 8 */ + /* 58 */ 0x4, /* Oi2 Flags: has return, */ + 0x1, /* 1 */ + + /* Return value */ + + /* 60 */ NdrFcShort( 0x70 ), /* Flags: out, return, base type, */ +#ifndef _ALPHA_ + /* 62 */ NdrFcShort( 0x4 ), /* x86, MIPS, PPC Stack size/offset = 4 */ +#else + NdrFcShort( 0x8 ), /* Alpha Stack size/offset = 8 */ +#endif + /* 64 */ 0x8, /* FC_LONG */ + 0x0, /* 0 */ + + /* Procedure Update */ + + /* 66 */ 0x33, /* FC_AUTO_HANDLE */ + 0x6c, /* Old Flags: object, Oi2 */ + /* 68 */ NdrFcLong( 0x0 ), /* 0 */ + /* 72 */ NdrFcShort( 0xa ), /* 10 */ +#ifndef _ALPHA_ + /* 74 */ NdrFcShort( 0x10 ), /* x86, MIPS, PPC Stack size/offset = 16 */ +#else + NdrFcShort( 0x20 ), /* Alpha Stack size/offset = 32 */ +#endif + /* 76 */ NdrFcShort( 0x0 ), /* 0 */ + /* 78 */ NdrFcShort( 0x8 ), /* 8 */ + /* 80 */ 0x6, /* Oi2 Flags: clt must size, has return, */ + 0x3, /* 3 */ + + /* Parameter mtype */ + + /* 82 */ NdrFcShort( 0x8b ), /* Flags: must size, must free, in, by val, */ +#ifndef _ALPHA_ + /* 84 */ NdrFcShort( 0x4 ), /* x86, MIPS, PPC Stack size/offset = 4 */ +#else + NdrFcShort( 0x8 ), /* Alpha Stack size/offset = 8 */ +#endif + /* 86 */ NdrFcShort( 0x1a ), /* Type Offset=26 */ + + /* Parameter updates */ + + /* 88 */ NdrFcShort( 0x8b ), /* Flags: must size, must free, in, by val, */ +#ifndef _ALPHA_ + /* 90 */ NdrFcShort( 0x8 ), /* x86, MIPS, PPC Stack size/offset = 8 */ +#else + NdrFcShort( 0x10 ), /* Alpha Stack size/offset = 16 */ +#endif + /* 92 */ NdrFcShort( 0x1a ), /* Type Offset=26 */ + + /* Return value */ + + /* 94 */ NdrFcShort( 0x70 ), /* Flags: out, return, base type, */ +#ifndef _ALPHA_ + /* 96 */ NdrFcShort( 0xc ), /* x86, MIPS, PPC Stack size/offset = 12 */ +#else + NdrFcShort( 0x18 ), /* Alpha Stack size/offset = 24 */ +#endif + /* 98 */ 0x8, /* FC_LONG */ + 0x0, /* 0 */ + + /* Procedure get_URL */ + + /* 100 */ 0x33, /* FC_AUTO_HANDLE */ + 0x6c, /* Old Flags: object, Oi2 */ + /* 102 */ NdrFcLong( 0x0 ), /* 0 */ + /* 106 */ NdrFcShort( 0xb ), /* 11 */ +#ifndef _ALPHA_ + /* 108 */ NdrFcShort( 0xc ), /* x86, MIPS, PPC Stack size/offset = 12 */ +#else + NdrFcShort( 0x18 ), /* Alpha Stack size/offset = 24 */ +#endif + /* 110 */ NdrFcShort( 0x0 ), /* 0 */ + /* 112 */ NdrFcShort( 0x8 ), /* 8 */ + /* 114 */ 0x5, /* Oi2 Flags: srv must size, has return, */ + 0x2, /* 2 */ + + /* Parameter mrl */ + + /* 116 */ NdrFcShort( 0x2113 ), /* Flags: must size, must free, out, simple ref, srv alloc size=8 */ +#ifndef _ALPHA_ + /* 118 */ NdrFcShort( 0x4 ), /* x86, MIPS, PPC Stack size/offset = 4 */ +#else + NdrFcShort( 0x8 ), /* Alpha Stack size/offset = 8 */ +#endif + /* 120 */ NdrFcShort( 0x2c ), /* Type Offset=44 */ + + /* Return value */ + + /* 122 */ NdrFcShort( 0x70 ), /* Flags: out, return, base type, */ +#ifndef _ALPHA_ + /* 124 */ NdrFcShort( 0x8 ), /* x86, MIPS, PPC Stack size/offset = 8 */ +#else + NdrFcShort( 0x10 ), /* Alpha Stack size/offset = 16 */ +#endif + /* 126 */ 0x8, /* FC_LONG */ + 0x0, /* 0 */ + + /* Procedure put_URL */ + + /* 128 */ 0x33, /* FC_AUTO_HANDLE */ + 0x6c, /* Old Flags: object, Oi2 */ + /* 130 */ NdrFcLong( 0x0 ), /* 0 */ + /* 134 */ NdrFcShort( 0xc ), /* 12 */ +#ifndef _ALPHA_ + /* 136 */ NdrFcShort( 0xc ), /* x86, MIPS, PPC Stack size/offset = 12 */ +#else + NdrFcShort( 0x18 ), /* Alpha Stack size/offset = 24 */ +#endif + /* 138 */ NdrFcShort( 0x0 ), /* 0 */ + /* 140 */ NdrFcShort( 0x8 ), /* 8 */ + /* 142 */ 0x6, /* Oi2 Flags: clt must size, has return, */ + 0x2, /* 2 */ + + /* Parameter mrl */ + + /* 144 */ NdrFcShort( 0x8b ), /* Flags: must size, must free, in, by val, */ +#ifndef _ALPHA_ + /* 146 */ NdrFcShort( 0x4 ), /* x86, MIPS, PPC Stack size/offset = 4 */ +#else + NdrFcShort( 0x8 ), /* Alpha Stack size/offset = 8 */ +#endif + /* 148 */ NdrFcShort( 0x1a ), /* Type Offset=26 */ + + /* Return value */ + + /* 150 */ NdrFcShort( 0x70 ), /* Flags: out, return, base type, */ +#ifndef _ALPHA_ + /* 152 */ NdrFcShort( 0x8 ), /* x86, MIPS, PPC Stack size/offset = 8 */ +#else + NdrFcShort( 0x10 ), /* Alpha Stack size/offset = 16 */ +#endif + /* 154 */ 0x8, /* FC_LONG */ + 0x0, /* 0 */ + + /* Procedure get_AutoStart */ + + /* 156 */ 0x33, /* FC_AUTO_HANDLE */ + 0x6c, /* Old Flags: object, Oi2 */ + /* 158 */ NdrFcLong( 0x0 ), /* 0 */ + /* 162 */ NdrFcShort( 0xd ), /* 13 */ +#ifndef _ALPHA_ + /* 164 */ NdrFcShort( 0xc ), /* x86, MIPS, PPC Stack size/offset = 12 */ +#else + NdrFcShort( 0x18 ), /* Alpha Stack size/offset = 24 */ +#endif + /* 166 */ NdrFcShort( 0x0 ), /* 0 */ + /* 168 */ NdrFcShort( 0xe ), /* 14 */ + /* 170 */ 0x4, /* Oi2 Flags: has return, */ + 0x2, /* 2 */ + + /* Parameter autoplay */ + + /* 172 */ NdrFcShort( 0x2150 ), /* Flags: out, base type, simple ref, srv alloc size=8 */ +#ifndef _ALPHA_ + /* 174 */ NdrFcShort( 0x4 ), /* x86, MIPS, PPC Stack size/offset = 4 */ +#else + NdrFcShort( 0x8 ), /* Alpha Stack size/offset = 8 */ +#endif + /* 176 */ 0x6, /* FC_SHORT */ + 0x0, /* 0 */ + + /* Return value */ + + /* 178 */ NdrFcShort( 0x70 ), /* Flags: out, return, base type, */ +#ifndef _ALPHA_ + /* 180 */ NdrFcShort( 0x8 ), /* x86, MIPS, PPC Stack size/offset = 8 */ +#else + NdrFcShort( 0x10 ), /* Alpha Stack size/offset = 16 */ +#endif + /* 182 */ 0x8, /* FC_LONG */ + 0x0, /* 0 */ + + /* Procedure put_AutoStart */ + + /* 184 */ 0x33, /* FC_AUTO_HANDLE */ + 0x6c, /* Old Flags: object, Oi2 */ + /* 186 */ NdrFcLong( 0x0 ), /* 0 */ + /* 190 */ NdrFcShort( 0xe ), /* 14 */ +#ifndef _ALPHA_ + /* 192 */ NdrFcShort( 0xc ), /* x86, MIPS, PPC Stack size/offset = 12 */ +#else + NdrFcShort( 0x18 ), /* Alpha Stack size/offset = 24 */ +#endif + /* 194 */ NdrFcShort( 0x6 ), /* 6 */ + /* 196 */ NdrFcShort( 0x8 ), /* 8 */ + /* 198 */ 0x4, /* Oi2 Flags: has return, */ + 0x2, /* 2 */ + + /* Parameter autoplay */ + + /* 200 */ NdrFcShort( 0x48 ), /* Flags: in, base type, */ +#ifndef _ALPHA_ + /* 202 */ NdrFcShort( 0x4 ), /* x86, MIPS, PPC Stack size/offset = 4 */ +#else + NdrFcShort( 0x8 ), /* Alpha Stack size/offset = 8 */ +#endif + /* 204 */ 0x6, /* FC_SHORT */ + 0x0, /* 0 */ + + /* Return value */ + + /* 206 */ NdrFcShort( 0x70 ), /* Flags: out, return, base type, */ +#ifndef _ALPHA_ + /* 208 */ NdrFcShort( 0x8 ), /* x86, MIPS, PPC Stack size/offset = 8 */ +#else + NdrFcShort( 0x10 ), /* Alpha Stack size/offset = 16 */ +#endif + /* 210 */ 0x8, /* FC_LONG */ + 0x0, /* 0 */ + + 0x0 + } +}; + +static const MIDL_TYPE_FORMAT_STRING __MIDL_TypeFormatString = +{ + 0, + { + NdrFcShort( 0x0 ), /* 0 */ + /* 2 */ + 0x12, 0x0, /* FC_UP */ + /* 4 */ NdrFcShort( 0xc ), /* Offset= 12 (16) */ + /* 6 */ + 0x1b, /* FC_CARRAY */ + 0x1, /* 1 */ + /* 8 */ NdrFcShort( 0x2 ), /* 2 */ + /* 10 */ 0x9, /* Corr desc: FC_ULONG */ + 0x0, /* */ + /* 12 */ NdrFcShort( 0xfffc ), /* -4 */ + /* 14 */ 0x6, /* FC_SHORT */ + 0x5b, /* FC_END */ + /* 16 */ + 0x17, /* FC_CSTRUCT */ + 0x3, /* 3 */ + /* 18 */ NdrFcShort( 0x8 ), /* 8 */ + /* 20 */ NdrFcShort( 0xfffffff2 ), /* Offset= -14 (6) */ + /* 22 */ 0x8, /* FC_LONG */ + 0x8, /* FC_LONG */ + /* 24 */ 0x5c, /* FC_PAD */ + 0x5b, /* FC_END */ + /* 26 */ 0xb4, /* FC_USER_MARSHAL */ + 0x83, /* 131 */ + /* 28 */ NdrFcShort( 0x0 ), /* 0 */ + /* 30 */ NdrFcShort( 0x4 ), /* 4 */ + /* 32 */ NdrFcShort( 0x0 ), /* 0 */ + /* 34 */ NdrFcShort( 0xffffffe0 ), /* Offset= -32 (2) */ + /* 36 */ + 0x11, 0x4, /* FC_RP [alloced_on_stack] */ + /* 38 */ NdrFcShort( 0x6 ), /* Offset= 6 (44) */ + /* 40 */ + 0x13, 0x0, /* FC_OP */ + /* 42 */ NdrFcShort( 0xffffffe6 ), /* Offset= -26 (16) */ + /* 44 */ 0xb4, /* FC_USER_MARSHAL */ + 0x83, /* 131 */ + /* 46 */ NdrFcShort( 0x0 ), /* 0 */ + /* 48 */ NdrFcShort( 0x4 ), /* 4 */ + /* 50 */ NdrFcShort( 0x0 ), /* 0 */ + /* 52 */ NdrFcShort( 0xfffffff4 ), /* Offset= -12 (40) */ + /* 54 */ + 0x11, 0xc, /* FC_RP [alloced_on_stack] [simple_pointer] */ + /* 56 */ 0x6, /* FC_SHORT */ + 0x5c, /* FC_PAD */ + + 0x0 + } +}; + +const CInterfaceProxyVtbl * _GPAX_ProxyVtblList[] = +{ + ( CInterfaceProxyVtbl *) &_IGPAXProxyVtbl, + 0 +}; + +const CInterfaceStubVtbl * _GPAX_StubVtblList[] = +{ + ( CInterfaceStubVtbl *) &_IGPAXStubVtbl, + 0 +}; + +PCInterfaceName const _GPAX_InterfaceNamesList[] = +{ + "IGPAX", + 0 +}; + +const IID * _GPAX_BaseIIDList[] = +{ + &IID_IDispatch, + 0 +}; + + +#define _GPAX_CHECK_IID(n) IID_GENERIC_CHECK_IID( _GPAX, pIID, n) + +int __stdcall _GPAX_IID_Lookup( const IID * pIID, int * pIndex ) +{ + + if(!_GPAX_CHECK_IID(0)) + { + *pIndex = 0; + return 1; + } + + return 0; +} + +const ExtendedProxyFileInfo GPAX_ProxyFileInfo = +{ + (PCInterfaceProxyVtblList *) & _GPAX_ProxyVtblList, + (PCInterfaceStubVtblList *) & _GPAX_StubVtblList, + (const PCInterfaceName * ) & _GPAX_InterfaceNamesList, + (const IID ** ) & _GPAX_BaseIIDList, + & _GPAX_IID_Lookup, + 1, + 2, + 0, /* table of [async_uuid] interfaces */ + 0, /* Filler1 */ + 0, /* Filler2 */ + 0 /* Filler3 */ +}; diff --git a/applications/deprecated/old_arch/GPAX/GPAXps.def b/applications/deprecated/old_arch/GPAX/GPAXps.def new file mode 100644 index 0000000..c4ab3de --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/GPAXps.def @@ -0,0 +1,11 @@ + +LIBRARY "GPAXPS" + +DESCRIPTION 'Proxy/Stub DLL' + +EXPORTS + DllGetClassObject @1 PRIVATE + DllCanUnloadNow @2 PRIVATE + GetProxyDllInfo @3 PRIVATE + DllRegisterServer @4 PRIVATE + DllUnregisterServer @5 PRIVATE diff --git a/applications/deprecated/old_arch/GPAX/GPAXps.mk b/applications/deprecated/old_arch/GPAX/GPAXps.mk new file mode 100644 index 0000000..70cf327 --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/GPAXps.mk @@ -0,0 +1,16 @@ + +GPAXps.dll: dlldata.obj GPAX_p.obj GPAX_i.obj + link /dll /out:GPAXps.dll /def:GPAXps.def /entry:DllMain dlldata.obj GPAX_p.obj GPAX_i.obj \ + kernel32.lib rpcndr.lib rpcns4.lib rpcrt4.lib oleaut32.lib uuid.lib \ + +.c.obj: + cl /c /Ox /DWIN32 /D_WIN32_WINNT=0x0400 /DREGISTER_PROXY_DLL \ + $< + +clean: + @del GPAXps.dll + @del GPAXps.lib + @del GPAXps.exp + @del dlldata.obj + @del GPAX_p.obj + @del GPAX_i.obj diff --git a/applications/deprecated/old_arch/GPAX/StdAfx.cpp b/applications/deprecated/old_arch/GPAX/StdAfx.cpp new file mode 100644 index 0000000..a5eea17 --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/StdAfx.cpp @@ -0,0 +1,12 @@ +// stdafx.cpp : source file that includes just the standard includes +// stdafx.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +#ifdef _ATL_STATIC_REGISTRY +#include +#include +#endif + +#include diff --git a/applications/deprecated/old_arch/GPAX/StdAfx.h b/applications/deprecated/old_arch/GPAX/StdAfx.h new file mode 100644 index 0000000..597b3ca --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/StdAfx.h @@ -0,0 +1,34 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, +// but are changed infrequently + +#if !defined(AFX_STDAFX_H__2CD656F1_059C_4EC4_9EAA_8FECF66BB748__INCLUDED_) +#define AFX_STDAFX_H__2CD656F1_059C_4EC4_9EAA_8FECF66BB748__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define STRICT + +#ifndef _WIN32_WCE + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif +#define _ATL_APARTMENT_THREADED + +#endif //_WIN32_WCE + + +#include +//You may derive a class from CComModule and use it if you want to override +//something, but do not change the name of _Module +extern CComModule _Module; +#include +#include + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__2CD656F1_059C_4EC4_9EAA_8FECF66BB748__INCLUDED) diff --git a/applications/deprecated/old_arch/GPAX/dlldata.c b/applications/deprecated/old_arch/GPAX/dlldata.c new file mode 100644 index 0000000..837e022 --- /dev/null +++ b/applications/deprecated/old_arch/GPAX/dlldata.c @@ -0,0 +1,38 @@ +/********************************************************* + DllData file -- generated by MIDL compiler + + DO NOT ALTER THIS FILE + + This file is regenerated by MIDL on every IDL file compile. + + To completely reconstruct this file, delete it and rerun MIDL + on all the IDL files in this DLL, specifying this file for the + /dlldata command line option + +*********************************************************/ + +#define PROXY_DELEGATION + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +EXTERN_PROXY_FILE( GPAX ) + + +PROXYFILE_LIST_START +/* Start of list */ +REFERENCE_PROXY_FILE( GPAX ), + /* End of list */ + PROXYFILE_LIST_END + + + DLLDATA_ROUTINES( aProxyFileList, GET_DLL_CLSID ) + +#ifdef __cplusplus +} /*extern "C" */ +#endif + +/* end of generated dlldata file */ diff --git a/applications/deprecated/old_arch/GPAX/gpax.bmp b/applications/deprecated/old_arch/GPAX/gpax.bmp new file mode 100644 index 0000000000000000000000000000000000000000..3ff99ff3a736c68624df2285444fa06e7fb7e705 GIT binary patch literal 246 zcmZuqF%H5o47^H6WrIw#Q&!%=&R@a;>XdAx6J&}y@s&KOSx7n=Cq+!yzB^w|kx%D0 z*1EDB8T&69Y-(UC|07OHXv`|iyds3aS^}yAqOvi#%Tw{cx(nB_$-NMhCK{eV.depend distclean: clean diff --git a/applications/dashcast/audio_data.c b/applications/deprecated/old_arch/dashcast/audio_data.c similarity index 100% rename from applications/dashcast/audio_data.c rename to applications/deprecated/old_arch/dashcast/audio_data.c diff --git a/applications/dashcast/audio_data.h b/applications/deprecated/old_arch/dashcast/audio_data.h similarity index 98% rename from applications/dashcast/audio_data.h rename to applications/deprecated/old_arch/dashcast/audio_data.h index 3f60bbb..de888a8 100644 --- a/applications/dashcast/audio_data.h +++ b/applications/deprecated/old_arch/dashcast/audio_data.h @@ -34,6 +34,7 @@ #include "../../modules/ffmpeg_in/ffmpeg_in.h" #include "libavcodec/avcodec.h" +#include "libavutil/channel_layout.h" #include "libavutil/mem.h" #include "libav_compat.h" #include "circular_buffer.h" @@ -93,7 +94,7 @@ typedef struct { time_t end_time; /* RFC6381 codec name, only valid when VIDEO_MUXER == GPAC_INIT_VIDEO_MUXER_AVC1 */ - char codec6381[GF_MAX_PATH]; + char codec6381[RFC6381_CODEC_NAME_SIZE_MAX]; } AudioDataConf; /* diff --git a/applications/dashcast/audio_decoder.c b/applications/deprecated/old_arch/dashcast/audio_decoder.c similarity index 95% rename from applications/dashcast/audio_decoder.c rename to applications/deprecated/old_arch/dashcast/audio_decoder.c index e2d7045..01de12a 100644 --- a/applications/dashcast/audio_decoder.c +++ b/applications/deprecated/old_arch/dashcast/audio_decoder.c @@ -150,21 +150,21 @@ int dc_audio_decoder_open(AudioInputFile *audio_input_file, AudioDataConf *audio static int ensure_resampler(AudioInputFile *audio_input_file, int sample_rate, int num_channels, u64 channel_layout, enum AVSampleFormat sample_format) { if (!audio_input_file->aresampler) { - audio_input_file->aresampler = avresample_alloc_context(); + audio_input_file->aresampler = swr_alloc(); if (!audio_input_file->aresampler) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot allocate the audio resampler. Aborting.\n")); return -1; } - av_opt_set_int(audio_input_file->aresampler, "in_channel_layout", channel_layout, 0); - av_opt_set_int(audio_input_file->aresampler, "out_channel_layout", DC_AUDIO_CHANNEL_LAYOUT, 0); - av_opt_set_int(audio_input_file->aresampler, "in_sample_fmt", sample_format, 0); - av_opt_set_int(audio_input_file->aresampler, "out_sample_fmt", DC_AUDIO_SAMPLE_FORMAT, 0); + av_opt_set_channel_layout(audio_input_file->aresampler, "in_channel_layout", channel_layout, 0); + av_opt_set_channel_layout(audio_input_file->aresampler, "out_channel_layout", DC_AUDIO_CHANNEL_LAYOUT, 0); + av_opt_set_sample_fmt(audio_input_file->aresampler, "in_sample_fmt", sample_format, 0); + av_opt_set_sample_fmt(audio_input_file->aresampler, "out_sample_fmt", DC_AUDIO_SAMPLE_FORMAT, 0); av_opt_set_int(audio_input_file->aresampler, "in_sample_rate", sample_rate, 0); av_opt_set_int(audio_input_file->aresampler, "out_sample_rate", DC_AUDIO_SAMPLE_RATE, 0); av_opt_set_int(audio_input_file->aresampler, "in_channels", num_channels, 0); av_opt_set_int(audio_input_file->aresampler, "out_channels", DC_AUDIO_NUM_CHANNELS, 0); - if (avresample_open(audio_input_file->aresampler)) { + if (swr_init(audio_input_file->aresampler)) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Could not open the audio resampler. Aborting.\n")); return -1; } @@ -183,7 +183,7 @@ static int resample_audio(AudioInputFile *audio_input_file, AudioInputData *audi (*output) [i] = (uint8_t*)av_malloc(DC_AUDIO_MAX_CHUNCK_SIZE); //FIXME: fix using size below av_samples_get_buffer_size() } - i = avresample_convert(audio_input_file->aresampler, *output, DC_AUDIO_MAX_CHUNCK_SIZE, audio_input_data->aframe->nb_samples, audio_input_data->aframe->extended_data, audio_input_data->aframe->linesize[0], audio_input_data->aframe->nb_samples); + i = swr_convert(audio_input_file->aresampler, *output, audio_input_data->aframe->nb_samples, (const uint8_t **)audio_input_data->aframe->extended_data, audio_input_data->aframe->nb_samples); if (i < 0) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Could not resample audio frame. Aborting.\n")); return -1; @@ -441,6 +441,6 @@ void dc_audio_decoder_close(AudioInputFile *audio_input_file) av_fifo_free(audio_input_file->fifo); #ifdef DC_AUDIO_RESAMPLER - avresample_free(&audio_input_file->aresampler); + swr_free(&audio_input_file->aresampler); #endif } diff --git a/applications/dashcast/audio_decoder.h b/applications/deprecated/old_arch/dashcast/audio_decoder.h similarity index 97% rename from applications/dashcast/audio_decoder.h rename to applications/deprecated/old_arch/dashcast/audio_decoder.h index 9f155f0..90b9839 100644 --- a/applications/dashcast/audio_decoder.h +++ b/applications/deprecated/old_arch/dashcast/audio_decoder.h @@ -32,7 +32,7 @@ #include "libavutil/fifo.h" #ifdef DC_AUDIO_RESAMPLER #include "libavutil/opt.h" -#include "libavresample/avresample.h" +#include "libswresample/swresample.h" #endif @@ -57,7 +57,7 @@ typedef struct { AVFifoBuffer *fifo; #ifdef DC_AUDIO_RESAMPLER /* Optional audio resampling between the decoder and the encoder */ - AVAudioResampleContext *aresampler; + SwrContext *aresampler; #endif LockMode mode; diff --git a/applications/dashcast/audio_encoder.c b/applications/deprecated/old_arch/dashcast/audio_encoder.c similarity index 93% rename from applications/dashcast/audio_encoder.c rename to applications/deprecated/old_arch/dashcast/audio_encoder.c index 8d69c40..b957191 100644 --- a/applications/dashcast/audio_encoder.c +++ b/applications/deprecated/old_arch/dashcast/audio_encoder.c @@ -179,21 +179,21 @@ int dc_audio_encoder_flush(AudioOutputFile *audio_output_file, AudioInputData *a static int ensure_resampler(AudioOutputFile *audio_output_file, AVCodecContext *audio_codec_ctx) { if (!audio_output_file->aresampler) { - audio_output_file->aresampler = avresample_alloc_context(); + audio_output_file->aresampler = swr_alloc(); if (!audio_output_file->aresampler) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot allocate the audio resampler. Aborting.\n")); return -1; } - av_opt_set_int(audio_output_file->aresampler, "in_channel_layout", DC_AUDIO_CHANNEL_LAYOUT, 0); - av_opt_set_int(audio_output_file->aresampler, "out_channel_layout", audio_codec_ctx->channel_layout, 0); - av_opt_set_int(audio_output_file->aresampler, "in_sample_fmt", DC_AUDIO_SAMPLE_FORMAT, 0); - av_opt_set_int(audio_output_file->aresampler, "out_sample_fmt", audio_codec_ctx->sample_fmt, 0); + av_opt_set_channel_layout(audio_output_file->aresampler, "in_channel_layout", DC_AUDIO_CHANNEL_LAYOUT, 0); + av_opt_set_channel_layout(audio_output_file->aresampler, "out_channel_layout", audio_codec_ctx->channel_layout, 0); + av_opt_set_sample_fmt(audio_output_file->aresampler, "in_sample_fmt", DC_AUDIO_SAMPLE_FORMAT, 0); + av_opt_set_sample_fmt(audio_output_file->aresampler, "out_sample_fmt", audio_codec_ctx->sample_fmt, 0); av_opt_set_int(audio_output_file->aresampler, "in_sample_rate", DC_AUDIO_SAMPLE_RATE, 0); av_opt_set_int(audio_output_file->aresampler, "out_sample_rate", audio_codec_ctx->sample_rate, 0); av_opt_set_int(audio_output_file->aresampler, "in_channels", DC_AUDIO_NUM_CHANNELS, 0); av_opt_set_int(audio_output_file->aresampler, "out_channels", audio_codec_ctx->channels, 0); - if (avresample_open(audio_output_file->aresampler)) { + if (swr_init(audio_output_file->aresampler)) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Could not open the audio resampler. Aborting.\n")); return -1; } @@ -214,7 +214,7 @@ static int resample_audio(AudioOutputFile *audio_output_file, AVCodecContext *au output[i] = (uint8_t*)av_malloc(linesize); } - if (avresample_convert(audio_output_file->aresampler, output, linesize, audio_output_file->aframe->nb_samples, audio_output_file->aframe->extended_data, audio_output_file->aframe->linesize[0], audio_output_file->aframe->nb_samples) < 0) { + if (swr_convert(audio_output_file->aresampler, output, audio_output_file->aframe->nb_samples, (const uint8_t **)audio_output_file->aframe->extended_data, audio_output_file->aframe->nb_samples) < 0) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Could not resample audio frame. Aborting.\n")); return -1; } @@ -350,6 +350,6 @@ void dc_audio_encoder_close(AudioOutputFile *audio_output_file) av_free(audio_output_file->codec_ctx); #ifdef DC_AUDIO_RESAMPLER - avresample_free(&audio_output_file->aresampler); + swr_free(&audio_output_file->aresampler); #endif } diff --git a/applications/dashcast/audio_encoder.h b/applications/deprecated/old_arch/dashcast/audio_encoder.h similarity index 100% rename from applications/dashcast/audio_encoder.h rename to applications/deprecated/old_arch/dashcast/audio_encoder.h diff --git a/applications/dashcast/audio_muxer.c b/applications/deprecated/old_arch/dashcast/audio_muxer.c similarity index 96% rename from applications/dashcast/audio_muxer.c rename to applications/deprecated/old_arch/dashcast/audio_muxer.c index 2374c9d..dd861dc 100644 --- a/applications/dashcast/audio_muxer.c +++ b/applications/deprecated/old_arch/dashcast/audio_muxer.c @@ -122,7 +122,7 @@ int dc_gpac_audio_moov_create(AudioOutputFile *audio_output_file, char *filename bpsample = av_get_bytes_per_sample(audio_output_file->codec_ctx->sample_fmt) * 8; - ret = gf_isom_set_audio_info(audio_output_file->isof, track, di, audio_codec_ctx->sample_rate, audio_output_file->codec_ctx->channels, bpsample); + ret = gf_isom_set_audio_info(audio_output_file->isof, track, di, audio_codec_ctx->sample_rate, audio_output_file->codec_ctx->channels, bpsample, GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_BS); if (ret != GF_OK) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_set_audio_info\n", gf_error_to_string(ret))); return -1; @@ -138,7 +138,7 @@ int dc_gpac_audio_moov_create(AudioOutputFile *audio_output_file, char *filename GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("time scale: %d sample dur: %d \n", audio_codec_ctx->time_base.den, audio_output_file->codec_ctx->frame_size)); - ret = gf_isom_setup_track_fragment(audio_output_file->isof, track, 1, audio_output_file->codec_ctx->frame_size, 0, 0, 0, 0); + ret = gf_isom_setup_track_fragment(audio_output_file->isof, track, 1, audio_output_file->codec_ctx->frame_size, 0, 0, 0, 0, 0); if (ret != GF_OK) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_setup_track_fragment\n", gf_error_to_string(ret))); return -1; @@ -193,13 +193,14 @@ int dc_gpac_audio_isom_write(AudioOutputFile *audio_output_file) int dc_gpac_audio_isom_close_seg(AudioOutputFile *audio_output_file) { + u64 seg_size; GF_Err ret; - ret = gf_isom_close_segment(audio_output_file->isof, 0, 0,0, 0, 0, 0, 1, audio_output_file->seg_marker, NULL, NULL); + ret = gf_isom_close_segment(audio_output_file->isof, 0, 0, 0, 0, 0, 0, 0, GF_TRUE, GF_FALSE, audio_output_file->seg_marker, NULL, NULL, &seg_size); if (ret != GF_OK) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_close_segment\n", gf_error_to_string(ret))); return -1; } - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DashCast] Audio segment closed at "LLU"\n", gf_net_get_utc() )); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DashCast] Audio segment %s closed at "LLU" - size "LLU" bytes\n", gf_isom_get_segment_name(audio_output_file->isof), gf_net_get_utc(), seg_size )); //audio_output_file->acc_samples = 0; @@ -291,9 +292,8 @@ int dc_ffmpeg_audio_muxer_open(AudioOutputFile *audio_output_file, char *filenam } av_dict_free(&opts); - avformat_write_header(audio_output_file->av_fmt_ctx, NULL); + return avformat_write_header(audio_output_file->av_fmt_ctx, NULL); - return 0; } int dc_ffmpeg_audio_muxer_write(AudioOutputFile *audio_output_file) @@ -418,7 +418,7 @@ int dc_audio_muxer_write(AudioOutputFile *audio_output_file, int frame_nb, Bool case GPAC_INIT_AUDIO_MUXER: if (frame_nb % audio_output_file->frame_per_frag == 0) { gf_isom_start_fragment(audio_output_file->isof, 1); - + if (insert_ntp) { gf_isom_set_fragment_reference_time(audio_output_file->isof, 1, audio_output_file->frame_ntp, audio_output_file->first_dts * audio_output_file->codec_ctx->frame_size); } diff --git a/applications/dashcast/audio_muxer.h b/applications/deprecated/old_arch/dashcast/audio_muxer.h similarity index 97% rename from applications/dashcast/audio_muxer.h rename to applications/deprecated/old_arch/dashcast/audio_muxer.h index 94151d8..afc91ac 100644 --- a/applications/dashcast/audio_muxer.h +++ b/applications/deprecated/old_arch/dashcast/audio_muxer.h @@ -32,7 +32,7 @@ #include "libavformat/avformat.h" #include "libavdevice/avdevice.h" #ifdef DC_AUDIO_RESAMPLER -#include "libavresample/avresample.h" +#include "libswresample/swresample.h" #endif #include "libavutil/mathematics.h" #include "libavutil/opt.h" @@ -79,7 +79,7 @@ typedef struct { #ifdef DC_AUDIO_RESAMPLER /* Optional audio resampling between the decoder and the encoder */ - AVAudioResampleContext *aresampler; + SwrContext *aresampler; #endif /* Variables that encoder needs to encode data */ diff --git a/applications/dashcast/circular_buffer.c b/applications/deprecated/old_arch/dashcast/circular_buffer.c similarity index 100% rename from applications/dashcast/circular_buffer.c rename to applications/deprecated/old_arch/dashcast/circular_buffer.c diff --git a/applications/dashcast/circular_buffer.h b/applications/deprecated/old_arch/dashcast/circular_buffer.h similarity index 100% rename from applications/dashcast/circular_buffer.h rename to applications/deprecated/old_arch/dashcast/circular_buffer.h diff --git a/applications/dashcast/cmd_data.c b/applications/deprecated/old_arch/dashcast/cmd_data.c similarity index 100% rename from applications/dashcast/cmd_data.c rename to applications/deprecated/old_arch/dashcast/cmd_data.c diff --git a/applications/dashcast/cmd_data.h b/applications/deprecated/old_arch/dashcast/cmd_data.h similarity index 100% rename from applications/dashcast/cmd_data.h rename to applications/deprecated/old_arch/dashcast/cmd_data.h diff --git a/applications/dashcast/controler.c b/applications/deprecated/old_arch/dashcast/controler.c similarity index 99% rename from applications/dashcast/controler.c rename to applications/deprecated/old_arch/dashcast/controler.c index 1cab9d7..c4dbd2c 100644 --- a/applications/dashcast/controler.c +++ b/applications/deprecated/old_arch/dashcast/controler.c @@ -1121,7 +1121,7 @@ int dc_run_controler(CmdData *in_data) dc_message_queue_init(&delete_seg_mq); dc_message_queue_init(&send_frag_mq); - memset(&audio_input_data, 0, sizeof(AudioInputData));; + memset(&audio_input_data, 0, sizeof(AudioInputData)); memset(&audio_input_file, 0, sizeof(AudioInputFile)); memset(&video_input_data, 0, sizeof(VideoInputData)); diff --git a/applications/dashcast/controler.h b/applications/deprecated/old_arch/dashcast/controler.h similarity index 100% rename from applications/dashcast/controler.h rename to applications/deprecated/old_arch/dashcast/controler.h diff --git a/applications/dashcast/dashcast.c b/applications/deprecated/old_arch/dashcast/dashcast.c similarity index 100% rename from applications/dashcast/dashcast.c rename to applications/deprecated/old_arch/dashcast/dashcast.c diff --git a/applications/dashcast/libav_compat.h b/applications/deprecated/old_arch/dashcast/libav_compat.h similarity index 100% rename from applications/dashcast/libav_compat.h rename to applications/deprecated/old_arch/dashcast/libav_compat.h diff --git a/applications/dashcast/message_queue.c b/applications/deprecated/old_arch/dashcast/message_queue.c similarity index 100% rename from applications/dashcast/message_queue.c rename to applications/deprecated/old_arch/dashcast/message_queue.c diff --git a/applications/dashcast/message_queue.h b/applications/deprecated/old_arch/dashcast/message_queue.h similarity index 100% rename from applications/dashcast/message_queue.h rename to applications/deprecated/old_arch/dashcast/message_queue.h diff --git a/applications/dashcast/register.c b/applications/deprecated/old_arch/dashcast/register.c similarity index 100% rename from applications/dashcast/register.c rename to applications/deprecated/old_arch/dashcast/register.c diff --git a/applications/dashcast/register.h b/applications/deprecated/old_arch/dashcast/register.h similarity index 100% rename from applications/dashcast/register.h rename to applications/deprecated/old_arch/dashcast/register.h diff --git a/applications/dashcast/task.c b/applications/deprecated/old_arch/dashcast/task.c similarity index 100% rename from applications/dashcast/task.c rename to applications/deprecated/old_arch/dashcast/task.c diff --git a/applications/dashcast/task.h b/applications/deprecated/old_arch/dashcast/task.h similarity index 100% rename from applications/dashcast/task.h rename to applications/deprecated/old_arch/dashcast/task.h diff --git a/applications/dashcast/video_data.c b/applications/deprecated/old_arch/dashcast/video_data.c similarity index 100% rename from applications/dashcast/video_data.c rename to applications/deprecated/old_arch/dashcast/video_data.c diff --git a/applications/dashcast/video_data.h b/applications/deprecated/old_arch/dashcast/video_data.h similarity index 99% rename from applications/dashcast/video_data.h rename to applications/deprecated/old_arch/dashcast/video_data.h index eddcf35..aad3543 100644 --- a/applications/dashcast/video_data.h +++ b/applications/deprecated/old_arch/dashcast/video_data.h @@ -68,7 +68,7 @@ typedef struct { /* video codec */ char codec[GF_MAX_PATH]; /* RFC6381 codec name, only valid when VIDEO_MUXER == GPAC_INIT_VIDEO_MUXER_AVC1 */ - char codec6381[GF_MAX_PATH]; + char codec6381[RFC6381_CODEC_NAME_SIZE_MAX]; /* custom parameter to be passed directly to the encoder - free it once you're done */ char custom[GF_MAX_PATH]; /*low delay is used*/ diff --git a/applications/dashcast/video_decoder.c b/applications/deprecated/old_arch/dashcast/video_decoder.c similarity index 99% rename from applications/dashcast/video_decoder.c rename to applications/deprecated/old_arch/dashcast/video_decoder.c index 566e6cd..5fa80c7 100644 --- a/applications/dashcast/video_decoder.c +++ b/applications/deprecated/old_arch/dashcast/video_decoder.c @@ -376,13 +376,11 @@ int dc_video_decoder_read(VideoInputFile *video_input_file, VideoInputData *vide } #endif -#if 1 if ((pts != video_input_file->prev_pts) && (video_input_file->pts_init == 1)) { video_input_file->pts_init = 2; video_input_data->frame_duration = pts - video_input_file->prev_pts; video_input_file->sync_tolerance = 9*video_input_data->frame_duration/5; } -#endif video_input_file->prev_pts = pts; video_data_node->vframe->pts = pts; diff --git a/applications/dashcast/video_decoder.h b/applications/deprecated/old_arch/dashcast/video_decoder.h similarity index 100% rename from applications/dashcast/video_decoder.h rename to applications/deprecated/old_arch/dashcast/video_decoder.h diff --git a/applications/dashcast/video_encoder.c b/applications/deprecated/old_arch/dashcast/video_encoder.c similarity index 96% rename from applications/dashcast/video_encoder.c rename to applications/deprecated/old_arch/dashcast/video_encoder.c index 875e0bd..030ddb6 100644 --- a/applications/dashcast/video_encoder.c +++ b/applications/deprecated/old_arch/dashcast/video_encoder.c @@ -143,8 +143,10 @@ int dc_video_encoder_open(VideoOutputFile *video_output_file, VideoDataConf *vid av_opt_set_int(video_output_file->codec_ctx->priv_data, "key-int", video_output_file->gdr, 0); } +#ifdef AV_CODEC_FLAG_GLOBAL_HEADER //the global header gives access to the extradata (SPS/PPS) - video_output_file->codec_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; + video_output_file->codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; +#endif video_output_file->vstream_idx = 0;//video_stream->index; @@ -229,6 +231,8 @@ int dc_video_encoder_encode(VideoOutputFile *video_output_file, VideoScaledData video_codec_ctx->coded_frame->key_frame = (pkt.flags & AV_PKT_FLAG_KEY) ? 1 : 0; video_output_file->frame_ntp = video_data_node->frame_ntp; video_output_file->frame_utc = video_data_node->frame_utc; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DashCast] Video %s Frame TS "LLU" encoded at UTC "LLU" ms in "LLU" us size %d bytes\n", video_output_file->rep_id, pkt.pts, gf_net_get_utc(), time_spent, video_output_file->encoded_frame_size )); } } @@ -242,8 +246,6 @@ int dc_video_encoder_encode(VideoOutputFile *video_output_file, VideoScaledData return -1; } - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DashCast] Video %s Frame TS "LLU" encoded at UTC "LLU" ms in "LLU" us\n", video_output_file->rep_id, /*video_data_node->source_number, */video_data_node->vframe->pts, gf_net_get_utc(), time_spent )); - /* if zero size, it means the image was buffered */ // if (out_size > 0) { // av_init_packet(&pkt); diff --git a/applications/dashcast/video_encoder.h b/applications/deprecated/old_arch/dashcast/video_encoder.h similarity index 100% rename from applications/dashcast/video_encoder.h rename to applications/deprecated/old_arch/dashcast/video_encoder.h diff --git a/applications/dashcast/video_muxer.c b/applications/deprecated/old_arch/dashcast/video_muxer.c similarity index 98% rename from applications/dashcast/video_muxer.c rename to applications/deprecated/old_arch/dashcast/video_muxer.c index b6693bb..b1c36da 100644 --- a/applications/dashcast/video_muxer.c +++ b/applications/deprecated/old_arch/dashcast/video_muxer.c @@ -478,7 +478,7 @@ int dc_gpac_video_moov_create(VideoOutputFile *video_output_file, char *filename gf_isom_set_visual_info(video_output_file->isof, track, di, video_codec_ctx->width, video_codec_ctx->height); gf_isom_set_sync_table(video_output_file->isof, track); - ret = gf_isom_setup_track_fragment(video_output_file->isof, track, 1, video_output_file->use_source_timing ? (u32) video_output_file->frame_dur : 1, 0, 0, 0, 0); + ret = gf_isom_setup_track_fragment(video_output_file->isof, track, 1, video_output_file->use_source_timing ? (u32) video_output_file->frame_dur : 1, 0, 0, 0, 0, 0); if (ret != GF_OK) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_setup_track_fragment\n", gf_error_to_string(ret))); return -1; @@ -575,13 +575,13 @@ int dc_gpac_video_isom_write(VideoOutputFile *video_output_file) int dc_gpac_video_isom_close_seg(VideoOutputFile *video_output_file) { - GF_Err ret; - ret = gf_isom_close_segment(video_output_file->isof, 0, 0, 0, 0, 0, 0, 1, video_output_file->seg_marker, NULL, NULL); + u64 seg_size; + GF_Err ret = gf_isom_close_segment(video_output_file->isof, 0, 0, 0, 0, 0, 0, 0, GF_TRUE, GF_FALSE, video_output_file->seg_marker, NULL, NULL, &seg_size); if (ret != GF_OK) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_close_segment\n", gf_error_to_string(ret))); return -1; } - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DashCast] Rep %s Closing segment at UTC "LLU" ms\n", video_output_file->rep_id, gf_net_get_utc() )); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DashCast] Rep %s Closing segment %s at UTC "LLU" ms - size "LLU" bytes\n", video_output_file->rep_id, gf_isom_get_segment_name(video_output_file->isof), gf_net_get_utc(), seg_size )); return 0; } @@ -623,6 +623,7 @@ int dc_ffmpeg_video_muxer_open(VideoOutputFile *video_output_file, char *filenam { AVStream *video_stream; AVOutputFormat *output_fmt; + int ret; AVCodecContext *video_codec_ctx = video_output_file->codec_ctx; video_output_file->av_fmt_ctx = NULL; @@ -687,7 +688,12 @@ int dc_ffmpeg_video_muxer_open(VideoOutputFile *video_output_file, char *filenam return -1; } - avformat_write_header(video_output_file->av_fmt_ctx, NULL); + ret = avformat_write_header(video_output_file->av_fmt_ctx, NULL); + + if (!ret) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("avformat_write_header returned %d\n", ret)); + return ret; + } video_output_file->timescale = video_codec_ctx->time_base.den; return 0; @@ -846,7 +852,7 @@ int dc_video_muxer_write(VideoOutputFile *video_output_file, int frame_nb, Bool if (ret < 0) return -1; - + //insert UTC for each fragment if (insert_ntp) { gf_isom_set_fragment_reference_time(video_output_file->isof, video_output_file->trackID, video_output_file->frame_ntp, video_output_file->codec_ctx->coded_frame->pts); diff --git a/applications/dashcast/video_muxer.h b/applications/deprecated/old_arch/dashcast/video_muxer.h similarity index 100% rename from applications/dashcast/video_muxer.h rename to applications/deprecated/old_arch/dashcast/video_muxer.h diff --git a/applications/dashcast/video_scaler.c b/applications/deprecated/old_arch/dashcast/video_scaler.c similarity index 100% rename from applications/dashcast/video_scaler.c rename to applications/deprecated/old_arch/dashcast/video_scaler.c diff --git a/applications/dashcast/video_scaler.h b/applications/deprecated/old_arch/dashcast/video_scaler.h similarity index 100% rename from applications/dashcast/video_scaler.h rename to applications/deprecated/old_arch/dashcast/video_scaler.h diff --git a/applications/mp42avi/Makefile b/applications/deprecated/old_arch/mp42avi/Makefile similarity index 92% rename from applications/mp42avi/Makefile rename to applications/deprecated/old_arch/mp42avi/Makefile index ab7c02c..065cd0c 100644 --- a/applications/mp42avi/Makefile +++ b/applications/deprecated/old_arch/mp42avi/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/applications/mp42avi CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/applications/mp42avi/main.c b/applications/deprecated/old_arch/mp42avi/main.c similarity index 95% rename from applications/mp42avi/main.c rename to applications/deprecated/old_arch/mp42avi/main.c index 619ff34..262bee1 100644 --- a/applications/mp42avi/main.c +++ b/applications/deprecated/old_arch/mp42avi/main.c @@ -261,7 +261,7 @@ void bifs3d_viewpoints_merger(GF_ISOFile *file, char *szConfigFile, u32 width, u return; } - test = gf_cfg_get_key(user.config, "General", "ModulesDirectory"); + test = gf_cfg_get_key(user.config, "Core", "ModulesDirectory"); user.modules = gf_modules_new((const unsigned char *) test, user.config); strcpy(old_driv, "raw_out"); if (!gf_modules_get_count(user.modules)) { @@ -270,23 +270,15 @@ void bifs3d_viewpoints_merger(GF_ISOFile *file, char *szConfigFile, u32 width, u } /*switch driver to raw_driver*/ - test = gf_cfg_get_key(user.config, "Video", "DriverName"); + test = gf_cfg_get_key(user.config, "core", "video-output"); if (test) strcpy(old_driv, test); needs_raw = 0; test = gf_cfg_get_key(user.config, "Compositor", "RendererName"); /*since we only support RGB24 for MP42AVI force using RAW out with 2D driver*/ if (test && strstr(test, "2D")) { - gf_cfg_set_key(user.config, "Video", "DriverName", "Raw Video Output"); needs_raw = 1; } - if (needs_raw) { - test = gf_cfg_get_key(user.config, "Video", "DriverName"); - if (stricmp(test, "raw_out") && stricmp(test, "Raw Video Output")) { - printf("couldn't load raw output driver (%s used)\n", test); - goto err_exit; - } - } } memset(&b2v, 0, sizeof(BIFSVID)); @@ -443,7 +435,7 @@ err_exit: if (output_merged_frame) gf_free(output_merged_frame); */ if (user.modules) gf_modules_del(user.modules); - if (needs_raw) gf_cfg_set_key(user.config, "Video", "DriverName", old_driv); + if (needs_raw) gf_cfg_set_key(user.config, "core", "video-output", old_driv); gf_cfg_del(user.config); } @@ -481,7 +473,7 @@ void bifs_to_vid(GF_ISOFile *file, char *szConfigFile, u32 width, u32 height, ch conv_buf = NULL; esd = NULL; needs_raw = 0; - test = gf_cfg_get_key(user.config, "General", "ModulesDirectory"); + test = gf_cfg_get_key(user.config, "Core", "ModulesDirectory"); user.modules = gf_modules_new((const unsigned char *) test, user.config); strcpy(old_driv, "raw_out"); if (!gf_modules_get_count(user.modules)) { @@ -490,13 +482,12 @@ void bifs_to_vid(GF_ISOFile *file, char *szConfigFile, u32 width, u32 height, ch } /*switch driver to raw_driver*/ - test = gf_cfg_get_key(user.config, "Video", "DriverName"); + test = gf_cfg_get_key(user.config, "core", "video-output"); if (test) strcpy(old_driv, test); test = gf_cfg_get_key(user.config, "Compositor", "RendererName"); /*since we only support RGB24 for MP42AVI force using RAW out with 2D driver*/ if (test && strstr(test, "2D")) { - gf_cfg_set_key(user.config, "Video", "DriverName", "Raw Video Output"); needs_raw = 1; } @@ -515,14 +506,6 @@ void bifs_to_vid(GF_ISOFile *file, char *szConfigFile, u32 width, u32 height, ch b2v.bifs = gf_bifs_decoder_new(b2v.sg, 0); - if (needs_raw) { - test = gf_cfg_get_key(user.config, "Video", "DriverName"); - if (stricmp(test, "raw_out") && stricmp(test, "Raw Video Output")) { - printf("couldn't load raw output driver (%s used)\n", test); - goto err_exit; - } - } - strcpy(config_path, ""); if (out_dir) { strcat(config_path, out_dir); @@ -650,7 +633,7 @@ err_exit: if (avi_out) AVI_close(avi_out); if (conv_buf) gf_free(conv_buf); if (user.modules) gf_modules_del(user.modules); - if (needs_raw) gf_cfg_set_key(user.config, "Video", "DriverName", old_driv); + if (needs_raw) gf_cfg_set_key(user.config, "core", "video-output", old_driv); gf_cfg_del(user.config); } diff --git a/applications/deprecated/old_arch/osmo4_sym/aif/osmo4_icon.bmp b/applications/deprecated/old_arch/osmo4_sym/aif/osmo4_icon.bmp new file mode 100644 index 0000000000000000000000000000000000000000..f811efdbfea2e72282c35a6cd9662f39814d5b01 GIT binary patch literal 5862 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 literal 0 HcmV?d00001 diff --git a/applications/deprecated/old_arch/osmo4_sym/aif/osmo4_icon_mask.bmp b/applications/deprecated/old_arch/osmo4_sym/aif/osmo4_icon_mask.bmp new file mode 100644 index 0000000000000000000000000000000000000000..cff9766729ff720fff5aed99d283b0aaf8a8f720 GIT binary patch literal 414 zcmY+=J&MCH5Cz~T3~^)d1;V;bvBj2KC6GRM$PxAiGNkY=au%5sdky6V3TX@xo_#a2 z7b1{8^fUI%98XsV8+)!h&d+5!l#4SNKgzU+%OdMYfJpG7X4GbaGb7;jyax^{V>>!; z(6lr+*wl3TMpdala2J{)+(;wiBfZn4hT@I#EmnV*{XjF%he*HPbhc++=7h>*zUGWp z<8Fe^zAI6RFLRWq*#G5WM&92ObMZkm&vXc0^PaliYaO}m_gdFnW2<$pb^rH)d0Kzg OK4rAFmmozpmvMj6lcPug literal 0 HcmV?d00001 diff --git a/applications/deprecated/old_arch/osmo4_sym/aif/osmo4_menu.bmp b/applications/deprecated/old_arch/osmo4_sym/aif/osmo4_menu.bmp new file mode 100644 index 0000000000000000000000000000000000000000..f44d9b1afae601d554d0dabd8dd2d03bcd587ae1 GIT binary patch literal 3766 zcmc&$drXs86#s5j=-N_iX$32fsi26;qrv(B2?Fwn3fdYUxD}LOn=+inSTxZ{mbgC_ z6aGOH6U}1W7&--fp%YvL)V6lj#YB+0Mpqb=hbx6n8fn>nwyrB$YhizQ^L=0Lckeyt zcV73LlfJ%zO*w}M-x$2Hc*F4$DimLes=$6rgzI%K;PdxK9h1WE?>C=6uPH0LoSFH( zPM2DTDXP=?VDc1;Jl|1R12`00G#rlfFImKYyqNqpCtu`L-yW_`i z*zNWYO5q>A&GxFe_&Omi6!iy#@u09!BaxH~1mSc#{S)WFBq}OaD*a~T#yeA|YH^H4 zyDgLbFfuaYTLeGw?RLk`oi_=&PssDsRIOV5drnRcgTa_SeR^sTL_~b* z9O2>Nvu4eL6cC}JqQYc)nUc~15*n?!s;cpS;CFNk1O;6tq$@X9YqQyFYipr7bOv&C zbaZTN>>}rgiHVsvZ{Ff&-Q7z|zS6X8SpOQlT|rRVc? zU0v3(v9avz>=`p=KwAJNBqS_dx^&sHWg?MCC=@PUyf`W<3Pyn=%FD|Es8;s{1gJnR zm)B1Uzq8ZAVqGP~`1RNAU@$y992y!L5)uN<6B83xu3VXxmX@BLzIyfQl#~?60#m>n zFadrcEJ%uq?&FSfxplq0FT9H2Q~#4E9})6^!FXsg{RQgY-d=bZMuw^YU$bV-+O=!* z^77WLTbGrUwQAKWSOKEVoH=vJk|nOor`7h+XgWf4)z$YWj$crKEB!1rRpTNZ92|t_ zVSHE~Is~(n$=qmSu^LR$(=WMKJSRTw z9~M>*(j!OS=nuN8sw%h__Rq-3faVH?V$YsEa=9FH;3EX)&Yg<@ZE0z7gA^Ad9d*XX zPXb>cc#VJP&^e@>YKCc3)1j(2yl z;uBx5e?h0?3K$O_{Mjotq=5e+h1Xpy?%u5fIiG*sV)0%Gec~g9<>lQ#0_AX;pFHu> zQm9m_`1p8Sd!y0I?&HS;Or{3p`}e=O!S9Oc6CcNQIuo6K3H2vAxo&7^oNrOvQ4Wys z5CxtW-UTu|Y!iu4g}XvRuJ`neBlMKwL-;s*`0zbK?x2{iUw^@Dwz#Q}9Xo~)3=0dx z7iFiRp~1anHowfxZFbh*JJr?RJ?}c~2focVnxB7-5Oe|0laelKwe4_wM@I*05y}A? z0c0fPoVd6+i^YO1ovt%6u@Q&SvM7}rXB~1&_V&aNe2`eJuOyNNbRlT0==5f(^kQ;y zPC!5adI2;X7-+(#O$*q*{Y$CzW?&#{F8Z%lrBa3b|2E?IfB*17Gdk)xdQ`(=)&1Ll zBC8!Iq>M)Uj7IyMki8UjilQDO)!;x#$kpmpq*r@edc*4O_wIB4?* R^WL>d;lG#L#5X=Z{y!0U=#~Hg literal 0 HcmV?d00001 diff --git a/applications/deprecated/old_arch/osmo4_sym/aif/osmo4_menu_mask.bmp b/applications/deprecated/old_arch/osmo4_sym/aif/osmo4_menu_mask.bmp new file mode 100644 index 0000000000000000000000000000000000000000..4f02c6757c8b1e4ff3a55180707705cd6b2eb08f GIT binary patch literal 294 zcmYMvF%H5o3 + +RESOURCE AIF_DATA + { + + app_uid = 0x1000AC00; + num_icons = 2; + embeddability = KAppNotEmbeddable; + newfile = KAppDoesNotSupportNewFile; + } + +// End of File diff --git a/applications/deprecated/old_arch/osmo4_sym/osmo4.cpp b/applications/deprecated/old_arch/osmo4_sym/osmo4.cpp new file mode 100644 index 0000000..c817e2c --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_sym/osmo4.cpp @@ -0,0 +1,82 @@ +#include "osmo4.h" +#include "osmo4_ui.h" + + +EXPORT_C CApaApplication* NewApplication() +{ + return new COsmo4Application; +} + + +#ifdef EKA2 + +#include + +GLDEF_C TInt E32Main() +{ + return EikStart::RunApplication( NewApplication ); +} + +#else + + +GLDEF_C TInt E32Dll( TDllReason /*aReason*/ ) +{ + return KErrNone; +} + +#endif + + +#if defined(__SERIES60_3X__) +const TUid KUidOsmo4App = { 0xf01f9075 }; +#else +const TUid KUidOsmo4App = { 0x1000AC00 }; +#endif + + +CApaDocument* COsmo4Application::CreateDocumentL() +{ + return (static_cast ( COsmo4Document::NewL( *this ) ) ); +} + +TUid COsmo4Application::AppDllUid() const +{ + return KUidOsmo4App; +} + + +COsmo4Document* COsmo4Document::NewL( CEikApplication& aApp ) +{ + COsmo4Document* self = NewLC( aApp ); + CleanupStack::Pop( self ); + return self; +} + +COsmo4Document* COsmo4Document::NewLC( CEikApplication& aApp ) +{ + COsmo4Document* self = + new ( ELeave ) COsmo4Document( aApp ); + + CleanupStack::PushL( self ); + self->ConstructL(); + return self; +} +void COsmo4Document::ConstructL() +{ +} + +COsmo4Document::COsmo4Document( CEikApplication& aApp ) + : CAknDocument( aApp ) +{ +} + +COsmo4Document::~COsmo4Document() +{ +} + +CEikAppUi* COsmo4Document::CreateAppUiL() +{ + return ( static_cast ( new ( ELeave ) COsmo4AppUi ) ); +} + diff --git a/applications/deprecated/old_arch/osmo4_sym/osmo4.h b/applications/deprecated/old_arch/osmo4_sym/osmo4.h new file mode 100644 index 0000000..cc83636 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_sym/osmo4.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2004, Nokia. All rights reserved */ + + +#ifndef __OSMO4_H__ +#define __OSMO4_H__ + +// INCLUDES +#include +#include + +class COsmo4Application : public CAknApplication +{ +public: + TUid AppDllUid() const; + +protected: + CApaDocument* CreateDocumentL(); +}; + + +class COsmo4Document : public CAknDocument +{ +public: + static COsmo4Document* NewL( CEikApplication& aApp ); + static COsmo4Document* NewLC( CEikApplication& aApp ); + virtual ~COsmo4Document(); + +public: + CEikAppUi* CreateAppUiL(); + +private: + void ConstructL(); + COsmo4Document( CEikApplication& aApp ); + +}; + +#endif // __OSMO4_H__ + diff --git a/applications/deprecated/old_arch/osmo4_sym/osmo4_ui.cpp b/applications/deprecated/old_arch/osmo4_sym/osmo4_ui.cpp new file mode 100644 index 0000000..55191e4 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_sym/osmo4_ui.cpp @@ -0,0 +1,605 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2006-2012 + * All rights reserved + * + * This file is part of GPAC / Symbian GUI player + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +// INCLUDE FILES +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +#include "osmo4_ui.h" +#include "osmo4_view.h" +#include "playlist.h" + +#include +#include + + + +// ============================ MEMBER FUNCTIONS =============================== + + +// ----------------------------------------------------------------------------- +// Cosmo4AppUi::ConstructL() +// Symbian 2nd phase constructor can leave. +// ----------------------------------------------------------------------------- +// +void COsmo4AppUi::ConstructL() +{ + // Initialise app UI with standard value. + BaseConstructL(CAknAppUi::EAknEnableSkin); + + /*Create display*/ + iAppView = COsmo4AppView::NewL( ClientRect() ); + AddToStackL(iAppView); + + /*create playlist*/ +#ifndef GPAC_GUI_ONLY + iPlaylist = CPlaylist::NewL( ClientRect(), iAppView->GetUser() ); + + iPlaylist->MakeVisible(EFalse); +#endif + + iAppView->MakeVisible(ETrue); + view_mode = 0; + + m_title = NULL; + + //StatusPane ()->SwitchLayoutL ( R_AVKON_STATUS_PANE_LAYOUT_SMALL ); + + nb_keys = 0; + CaptureKeys(1); + + + + CCommandLineArguments *args = CCommandLineArguments::NewL(); +#ifndef GPAC_GUI_ONLY + if (args->Count() > 1) { + TPtrC url = args->Arg(1); +#if defined(_UNICODE) + char szURL[1024]; + u16 szURLUTF16[1024]; + size_t len; + len = url.Size(); + memcpy(szURLUTF16, url.Ptr(), sizeof(u8)*len); + szURLUTF16[len/2] = 0; + const u16 *sptr = szURLUTF16; + len = gf_utf8_wcstombs(szURL, 512, &sptr); + if (len != (size_t) -1) { + szURL[len] = 0; + iAppView->Connect((const char *)szURL); + } +#else + iAppView->Connect((const char *)url.Ptr()); +#endif + } +#endif + delete args; +} + +// ----------------------------------------------------------------------------- +// COsmo4AppUi::COsmo4AppUi() +// C++ default constructor can NOT contain any code, that might leave. +// ----------------------------------------------------------------------------- +// +COsmo4AppUi::COsmo4AppUi() +{ + // No implementation required +} + +// ----------------------------------------------------------------------------- +// COsmo4AppUi::~COsmo4AppUi() +// Destructor. +// ----------------------------------------------------------------------------- +// +COsmo4AppUi::~COsmo4AppUi() +{ + CaptureKeys(0); + + switch (view_mode) { + case 0: + if (iAppView) RemoveFromStack(iAppView); + break; + case 1: + if (iPlaylist) RemoveFromStack(iPlaylist); + break; + } + if (iAppView) delete iAppView; + if (iPlaylist) delete iPlaylist; + if (m_title) gf_free(m_title); + m_title = NULL; +} + + +void COsmo4AppUi::CaptureKey(TInt32 code, TInt32 scancode) +{ + RWindowGroup iWG = CCoeEnv::Static()->RootWin(); + if (nb_keys>=MAX_KEY_CAP) return; + keys[nb_keys].key_cap = iWG.CaptureKey(code, 0, 0); + keys[nb_keys].key_cap_ud = iWG.CaptureKeyUpAndDowns(scancode, 0, 0); + nb_keys++; +} +/* +possible meaning for key codes: +EStdKeyYes -Call +EStdKeyNo -End +EStdKeyApplication0 -Apps key +EStdKeyDevice0 -Left softkey +EStdKeyDevice1 -Right softkey +EStdKeyDevice2 -Power +EStdKeyDevice3 -Button press +EStdKeyDevice4 -Flip - Open +EStdKeyDevice5 -Flip - Close +EStdKeyDevice6 -Side key + +EStdKeyDeviceD -Jog Dial forward +EStdKeyDeviceE -Jog Dial back +*/ +void COsmo4AppUi::CaptureKeys(int do_capture) +{ + if (do_capture) { + CaptureKey(EKeyIncVolume, EStdKeyIncVolume); + CaptureKey(EKeyDecVolume, EStdKeyDecVolume); + } else { + RWindowGroup iWG = CCoeEnv::Static()->RootWin(); + for (int i=0; iShutdown(); + Exit(); + break; + /*PLAYLIST commands*/ + case EOsmo4PlayListAdd: + iPlaylist->PlaylistAct(Osmo4PLAdd); + break; + case EOsmo4PlayListRem: + iPlaylist->PlaylistAct(Osmo4PLRem); + break; + case EOsmo4PlayListMoveUp: + iPlaylist->PlaylistAct(Osmo4PLMoveUp); + break; + case EOsmo4PlayListMoveDown: + iPlaylist->PlaylistAct(Osmo4PLMoveDown); + break; + case EOsmo4PlayListClear: + iPlaylist->PlaylistAct(Osmo4PLClear); + break; + case EOsmo4PlayListMode: + iPlaylist->PlaylistAct(Osmo4PLToggleMode); + break; + case EOsmo4PlayListAllFiles: + iPlaylist->PlaylistAct(Osmo4PLToggleAllFiles); + break; + + /*FILE menu command*/ + case EOsmo4PlayListView: + TogglePlaylist(); + break; + case EOsmo4OpenURL: + break; + case EOsmo4Fullscreen: + break; + case EOsmo4ViewMaxSize: + { + CEikStatusPane* statusPane = StatusPane(); + if (statusPane->IsVisible()) statusPane->MakeVisible(EFalse); + else statusPane->MakeVisible(ETrue); + } + break; + case EOsmo4AROriginal: + gf_term_set_option(iAppView->m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_KEEP); + break; + case EOsmo4ARFillScreen: + gf_term_set_option(iAppView->m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_FILL_SCREEN); + break; + case EOsmo4AR4_3: + gf_term_set_option(iAppView->m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_4_3); + break; + case EOsmo4AR16_9: + gf_term_set_option(iAppView->m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_16_9); + break; + + case EOsmo4NavReset: + gf_term_set_option(iAppView->m_term, GF_OPT_NAVIGATION_TYPE, 0); + break; + case EOsmo4NavNone: + gf_term_set_option(iAppView->m_term, GF_OPT_NAVIGATION, GF_NAVIGATE_NONE); + break; + case EOsmo4NavSlide: + e = gf_term_set_option(iAppView->m_term, GF_OPT_NAVIGATION, GF_NAVIGATE_SLIDE); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Cannot set navigation: %s", gf_error_to_string(e) )); + } + break; + case EOsmo4NavWalk: + gf_term_set_option(iAppView->m_term, GF_OPT_NAVIGATION, GF_NAVIGATE_WALK); + break; + case EOsmo4NavFly: + gf_term_set_option(iAppView->m_term, GF_OPT_NAVIGATION, GF_NAVIGATE_FLY); + break; + case EOsmo4NavExamine: + gf_term_set_option(iAppView->m_term, GF_OPT_NAVIGATION, GF_NAVIGATE_EXAMINE); + break; + case EOsmo4NavHeadlight: + gf_term_set_option(iAppView->m_term, GF_OPT_HEADLIGHT, !gf_term_get_option(iAppView->m_term, GF_OPT_HEADLIGHT) ); + break; + case EOsmo4CollideNone: + gf_term_set_option(iAppView->m_term, GF_OPT_COLLISION, GF_COLLISION_NONE); + break; + case EOsmo4CollideSimple: + gf_term_set_option(iAppView->m_term, GF_OPT_COLLISION, GF_COLLISION_NORMAL); + break; + case EOsmo4CollideDisp: + gf_term_set_option(iAppView->m_term, GF_OPT_COLLISION, GF_COLLISION_DISPLACEMENT); + break; + case EOsmo4NavGravity: + gf_term_set_option(iAppView->m_term, GF_OPT_GRAVITY, !gf_term_get_option(iAppView->m_term, GF_OPT_GRAVITY)); + break; + case EOsmo4ViewRTI: + iAppView->show_rti = !iAppView->show_rti; + break; + + case EOsmo4OptEnableLogs: + { + const char *opt = gf_cfg_get_key(iAppView->m_user.config, "General", "Logs"); + if (opt && !stricmp(opt, "@debug")) { + gf_cfg_set_key(iAppView->m_user.config, "General", "Logs", "all@error"); + } else { + gf_cfg_set_key(iAppView->m_user.config, "General", "Logs", "all@debug"); + } + iAppView->SetupLogs(); + } + break; + case EOsmo4OptOpenGL: + { + const char *opt = gf_cfg_get_key(iAppView->m_user.config, "Compositor", "ForceOpenGL"); + Bool use_gl = (opt && !strcmp(opt, "yes")) ? 1 : 0; + gf_cfg_set_key(iAppView->m_user.config, "Compositor", "ForceOpenGL", use_gl ? "no" : "yes"); + gf_term_set_option(iAppView->m_term, GF_OPT_USE_OPENGL, !use_gl); + } + break; + case EOsmo4OptDirectDraw: + { + const char *opt = gf_cfg_get_key(iAppView->m_user.config, "Compositor", "DirectDraw"); + Bool use_dd = (opt && !strcmp(opt, "yes")) ? 1 : 0; + gf_cfg_set_key(iAppView->m_user.config, "Compositor", "DirectDraw", use_dd ? "no" : "yes"); + gf_term_set_option(iAppView->m_term, GF_OPT_DIRECT_DRAW, !use_dd); + } + break; + case EOsmo4OptXMLProgressive: + { + const char *opt = gf_cfg_get_key(iAppView->m_user.config, "SAXLoader", "Progressive"); + Bool use_prog = (opt && !strcmp(opt, "yes")) ? 1 : 0; + gf_cfg_set_key(iAppView->m_user.config, "SAXLoader", "Progressive", use_prog ? "no" : "yes"); + gf_cfg_set_key(iAppView->m_user.config, "SAXLoader", "MaxDuration", "100"); + } + break; + + default: + if ((aCommand>=EOsmo4OpenRecentFirst) && (aCommand<=EOsmo4OpenRecentLast)) { + const char *sOpt = gf_cfg_get_key_name(iAppView->m_user.config, "RecentFiles", aCommand - EOsmo4OpenRecentFirst); + if (sOpt) iAppView->Connect(sOpt); + } else { + iAppView->MessageBox("Unandled command - panic", "Osmo4"); + Panic( EOsmo4Ui ); + } + break; + } +#endif +} + + +// ----------------------------------------------------------------------------- +// Called by the framework when the application status pane +// size is changed. Passes the new client rectangle to the +// AppView +// ----------------------------------------------------------------------------- +// +void COsmo4AppUi::HandleStatusPaneSizeChange() +{ + iAppView->SetRect( ClientRect() ); +#ifndef GPAC_GUI_ONLY + iPlaylist->SetRect( ClientRect() ); +#endif +} + +void COsmo4AppUi::TogglePlaylist() +{ + CEikButtonGroupContainer* cba= CEikButtonGroupContainer::Current(); + +#ifndef GPAC_GUI_ONLY + switch (view_mode) { + case 0: + RemoveFromStack(iAppView); + iAppView->ShowHide(0); + AddToStackL(iPlaylist); + if (cba) { + cba->SetCommandSetL(R_AVKON_SOFTKEYS_OPTIONS_BACK); + cba->DrawDeferred(); + } + view_was_max = StatusPane()->IsVisible() ? 0 : 1; + if (view_was_max) StatusPane()->MakeVisible(ETrue); + iPlaylist->ShowHide(1); + view_mode = 1; + break; + case 1: + RemoveFromStack(iPlaylist); + iPlaylist->ShowHide(0); + AddToStackL(iAppView); + if (cba) { + cba->SetCommandSetL(R_AVKON_SOFTKEYS_OPTIONS_EXIT); + cba->DrawDeferred(); + } + iAppView->ShowHide(1); + if (view_was_max) StatusPane()->MakeVisible(EFalse); + view_was_max = 0; + view_mode = 0; + break; + } +#endif +} + +void COsmo4AppUi::PlayURL(const char *url) +{ + if (view_mode) { + TogglePlaylist(); + } + if (url) { + char *sep; + iAppView->Connect(url); + sep = strrchr(url, '\\'); + SetTitle(sep ? sep+1 : url); + } +} + +void COsmo4AppUi::SetTitleInfo(const char *title) +{ +#if 1 + CEikStatusPane* statusPane = StatusPane(); + CAknTitlePane *iTitlePane = (CAknTitlePane*) statusPane->ControlL(TUid::Uid(EEikStatusPaneUidTitle)); + + if (!title) title = "Osmo4"; + + HBufC *htitle = HBufC::NewL( strlen(title)+1); + htitle->Des().Copy( TPtrC8(( TText8* ) title) ); + iTitlePane->SetText(htitle); +#endif +} + +void COsmo4AppUi::SetTitle(const char *title, int store_it) +{ + if (store_it) { + if (m_title) gf_free(m_title); + m_title = NULL; + if (title) m_title = gf_strdup(title); + } + SetTitleInfo(title ? title : m_title); +} + +void COsmo4AppUi::SetInfo(const char *info) +{ + if (view_mode) return; + if (info) { + char szTitle[200]; + sprintf(szTitle, "%s\n%s", info, m_title ? m_title : "Osmo4"); + SetTitleInfo(szTitle); + } else { + SetTitleInfo(m_title); + } +} + + +#define DECLARE_MENU_ITEM(__text, __com, __check, __res, has_sep) \ + item.iText = __text; \ + item.iCommandId = __com; \ + item.iFlags = has_sep ? EEikMenuItemSeparatorAfter : 0; \ + if (__check) item.iFlags |= EEikMenuItemCheckBox | EEikMenuItemSymbolOn; \ + item.iCascadeId = __res; \ + aMenuPane->AddMenuItemL(item); + + +void COsmo4AppUi::DynInitMenuPaneL(TInt aResourceId, CEikMenuPane* aMenuPane) +{ + CEikMenuPaneItem::SData item; + + if (aResourceId==R_OSMO4_MENU) { + + aMenuPane->Reset(); + + if (view_mode==1) { +#ifndef GPAC_GUI_ONLY + Bool is_file = iPlaylist->SelectionIsFile(); + Bool in_pl = (is_file && iPlaylist->IsInPlaylist()) ? 1 : 0; + + if (iPlaylist->PlaylistMode()) { + DECLARE_MENU_ITEM(_L("Up"), EOsmo4PlayListMoveUp, 0, 0, 0); + DECLARE_MENU_ITEM(_L("Down"), EOsmo4PlayListMoveDown, 0, 0, 0); + DECLARE_MENU_ITEM(_L("Remove"), EOsmo4PlayListRem, 0, 0, 0); + DECLARE_MENU_ITEM(_L("Clear"), EOsmo4PlayListClear, 0, 0, 1); + } else if (!in_pl) { + DECLARE_MENU_ITEM(_L("Add to PlayList"), EOsmo4PlayListAdd, 0, 0, 1); + } else if (is_file) { + DECLARE_MENU_ITEM(_L("Remove from Playlist"), EOsmo4PlayListRem, 0, 0, 1); + } + + if (! iPlaylist->PlaylistMode()) { + DECLARE_MENU_ITEM(iPlaylist->ViewAllFiles() ? _L("View known files") : _L("View all files"), EOsmo4PlayListAllFiles, 0, 0, 1); + } else { + DECLARE_MENU_ITEM(_L("Sort"), 0, 0, R_OSMO4_SM1, 1); + } + DECLARE_MENU_ITEM(iPlaylist->PlaylistMode() ? _L("Browse") : _L("Playlist"), EOsmo4PlayListMode, 0, 0, 0); +#endif + } else { + /*open*/ + DECLARE_MENU_ITEM(_L("File"), 0, 0, R_OSMO4_SM1, 0); + DECLARE_MENU_ITEM(_L("View"), 0, 0, R_OSMO4_SM2, 0); + DECLARE_MENU_ITEM(_L("Options"), 0, 0, R_OSMO4_SM3, 0); + //DECLARE_MENU_ITEM(_L("Exit"), EEikCmdExit, 0, 0, 0); + } + smenu_id = 0; + return; + } + else if (aResourceId==R_OSMO4_SM1) { + aMenuPane->Reset(); + /*sort menu*/ + if (view_mode==1) { + } + /*file menu*/ + else { + DECLARE_MENU_ITEM(_L("Open local"), EOsmo4PlayListView, 0, 0, 0); + DECLARE_MENU_ITEM(_L("Open URL"), EOsmo4OpenURL, 0, 0, 1); +#ifndef GPAC_GUI_ONLY + if (gf_cfg_get_key_name(iAppView->m_user.config, "RecentFiles", 0) != NULL) { + DECLARE_MENU_ITEM(_L("Recent"), 0, 0, R_OSMO4_SSM1, 0); + } +#endif + } + smenu_id = 1; + return; + } + /*not used*/ + if (view_mode==1) return; + + /*View menu*/ + if (aResourceId==R_OSMO4_SM2) { + aMenuPane->Reset(); +#ifndef GPAC_GUI_ONLY + /*content view menu*/ + if (gf_term_get_option(iAppView->m_term, GF_OPT_NAVIGATION_TYPE) != GF_NAVIGATE_TYPE_NONE) { + DECLARE_MENU_ITEM(_L("Navigate"), 0, 0, R_OSMO4_SSM1, 1); + } +#endif + DECLARE_MENU_ITEM(_L("Fullscreen"), EOsmo4Fullscreen, 0, 0, 0); + /*don't allow content AR modification by user*/ + //DECLARE_MENU_ITEM(_L("Aspect Ratio"), 0, 0, R_OSMO4_SSM2, 1); + DECLARE_MENU_ITEM(_L("Maximize size"), EOsmo4ViewMaxSize, (StatusPane()->IsVisible() ? 0 : 1), 0, 1); + DECLARE_MENU_ITEM(_L("CPU Usage"), EOsmo4ViewRTI, iAppView->show_rti, 0, 0); + smenu_id = 2; + return; + } + /*Option menu*/ + if (aResourceId==R_OSMO4_SM3) { +#ifndef GPAC_GUI_ONLY + const char *opt = gf_cfg_get_key(iAppView->m_user.config, "Compositor", "ForceOpenGL"); + DECLARE_MENU_ITEM(_L("Use 2D OpenGL"), EOsmo4OptOpenGL, (opt && !strcmp(opt, "yes")) ? 1 : 0, 0, 0); + opt = gf_cfg_get_key(iAppView->m_user.config, "Compositor", "DirectDraw"); + DECLARE_MENU_ITEM(_L("Direct Draw"), EOsmo4OptDirectDraw, (opt && !strcmp(opt, "yes")) ? 1 : 0, 0, 0); + opt = gf_cfg_get_key(iAppView->m_user.config, "SAXLoader", "Progressive"); + DECLARE_MENU_ITEM(_L("Progressive XML"), EOsmo4OptXMLProgressive, (opt && !strcmp(opt, "yes")) ? 1 : 0, 0, 0); + +#endif + + DECLARE_MENU_ITEM(_L("Enable Logs"), EOsmo4OptEnableLogs, iAppView->do_log, 0, 0); + return; + } + + if (aResourceId==R_OSMO4_SSM1) { + aMenuPane->Reset(); + if (smenu_id == 1) { + u32 i = 0; +#ifndef GPAC_GUI_ONLY + while (1) { + const char *opt = gf_cfg_get_key_name(iAppView->m_user.config, "RecentFiles", i); + if (!opt) break; + const char *sep = strrchr(opt, '\\'); + if (!sep) sep = strrchr(opt, '/'); + if (!sep) sep = opt; + else sep += 1; + item.iText.Copy( TPtrC8(( TText8* ) sep) ); + item.iCommandId = EOsmo4OpenRecentFirst + i; + item.iFlags = 0; + item.iCascadeId = 0; + aMenuPane->AddMenuItemL(item); + i++; + if (i>=10) break; + } + if (!i) { + DECLARE_MENU_ITEM(_L("_"), 0, 0, 0, 0); + } +#endif + } else if (smenu_id == 2) { + DECLARE_MENU_ITEM(_L("Reset"), EOsmo4NavReset, 0, 0, 1); + DECLARE_MENU_ITEM(_L("None"), EOsmo4NavNone, 0, 0, 0); + DECLARE_MENU_ITEM(_L("Slide"), EOsmo4NavSlide, 0, 0, 0); + +#ifndef GPAC_GUI_ONLY + if (gf_term_get_option(iAppView->m_term, GF_OPT_NAVIGATION_TYPE) == GF_NAVIGATE_TYPE_3D) { + DECLARE_MENU_ITEM(_L("Walk"), EOsmo4NavWalk, 0, 0, 0); + DECLARE_MENU_ITEM(_L("Fly"), EOsmo4NavFly, 0, 0, 0); + DECLARE_MENU_ITEM(_L("Examine"), EOsmo4NavExamine, 0, 0, 1); + DECLARE_MENU_ITEM(_L("Headlight"), EOsmo4NavHeadlight, 0, 0, 0); + DECLARE_MENU_ITEM(_L("Gravity"), EOsmo4NavGravity, 0, 0, 0); + } +#endif + } + return; + } + + if (aResourceId==R_OSMO4_SSM2) { + aMenuPane->Reset(); + DECLARE_MENU_ITEM(_L("Keep Original"), EOsmo4AROriginal, 0, 0, 0); + DECLARE_MENU_ITEM(_L("Fill Screen"), EOsmo4ARFillScreen, 0, 0, 0); + DECLARE_MENU_ITEM(_L("Ratio 4-3"), EOsmo4AR4_3, 0, 0, 0); + DECLARE_MENU_ITEM(_L("Ratio 16-9"), EOsmo4AR16_9, 0, 0, 0); + return; + } +} + diff --git a/applications/deprecated/old_arch/osmo4_sym/osmo4_ui.h b/applications/deprecated/old_arch/osmo4_sym/osmo4_ui.h new file mode 100644 index 0000000..8179353 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_sym/osmo4_ui.h @@ -0,0 +1,181 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2006-2012 + * All rights reserved + * + * This file is part of GPAC / Symbian GUI player + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __OSMO4_UI_H__ +#define __OSMO4_UI_H__ + +// INCLUDES +#include + + +// FORWARD DECLARATIONS +class COsmo4AppView; +class CPlaylist; + + +// osmo4 enumerate command codes +enum TOsmo4Ids +{ + /*Playlist commands*/ + EOsmo4PlayListView = 0x6001, + EOsmo4PlayListAdd, + EOsmo4PlayListRem, + EOsmo4PlayListMode, + EOsmo4PlayListMoveUp, + EOsmo4PlayListMoveDown, + EOsmo4PlayListClear, + EOsmo4PlayListAllFiles, + /*file commands*/ + EOsmo4OpenURL, + EOsmo4OpenRecentFirst, + EOsmo4OpenRecentLast = EOsmo4OpenRecentFirst + 10, + /*view commands*/ + EOsmo4Fullscreen, + EOsmo4ViewMaxSize, + EOsmo4AROriginal, + EOsmo4ARFillScreen, + EOsmo4AR4_3, + EOsmo4AR16_9, + EOsmo4NavReset, + EOsmo4NavNone, + EOsmo4NavSlide, + EOsmo4NavWalk, + EOsmo4NavFly, + EOsmo4NavExamine, + EOsmo4NavHeadlight, + EOsmo4NavGravity, + EOsmo4CollideNone, + EOsmo4CollideSimple, + EOsmo4CollideDisp, + EOsmo4ViewRTI, + + /*option commands*/ + EOsmo4OptEnableLogs, + EOsmo4OptOpenGL, + EOsmo4OptDirectDraw, + EOsmo4OptXMLProgressive, + + +}; + + +/** osmo4 application panic codes */ +enum TOsmo4Panics +{ + EOsmo4Ui = 1 + // add further panics here +}; + +inline void Panic(TOsmo4Panics aReason) +{ + _LIT(applicationName,"Osmo4"); + User::Panic(applicationName, aReason); +} + + +#define MAX_KEY_CAP 10 +typedef struct +{ + TInt32 key_cap; + TInt32 key_cap_ud; +} KeyCapInfo; + +// CLASS DECLARATION +/** +* COsmo4AppUi application UI class. +* Interacts with the user through the UI and request message processing +* from the handler class +*/ +class COsmo4AppUi : public CAknAppUi +{ +public: // Constructors and destructor + + /** + * ConstructL. + * 2nd phase constructor. + */ + void ConstructL(); + + /** + * COsmo4AppUi. + * C++ default constructor. This needs to be public due to + * the way the framework constructs the AppUi + */ + COsmo4AppUi(); + + /** + * ~COsmo4AppUi. + * Virtual Destructor. + */ + virtual ~COsmo4AppUi(); + +private: // Functions from base classes + + /** + * From CEikAppUi, HandleCommandL. + * Takes care of command handling. + * @param aCommand Command to be handled. + */ + void HandleCommandL( TInt aCommand ); + + + /** + * HandleStatusPaneSizeChange. + * Called by the framework when the application status pane + * size is changed. + */ + + void HandleStatusPaneSizeChange(); + virtual void DynInitMenuPaneL(TInt aResourceId, CEikMenuPane* aMenuPane); + +public: + void PlayURL(const char *); + void SetTitle(const char *title, TBool store_it = ETrue); + void SetInfo(const char *); + +private: + void TogglePlaylist(); + void SetTitleInfo(const char *); + void HandleForegroundEventL(TBool aForeground); + void CaptureKeys(int do_capture); + void CaptureKey(TInt32 code, TInt32 scancode); + +private: + COsmo4AppView* iAppView; + CPlaylist *iPlaylist; + int view_was_max; + int smenu_id; + char *m_title; + /*current view mode*/ + int view_mode; + + KeyCapInfo keys[MAX_KEY_CAP]; + int nb_keys; +}; + +#endif // __OSMO4_UI_H__ + +// End of File + diff --git a/applications/deprecated/old_arch/osmo4_sym/osmo4_view.cpp b/applications/deprecated/old_arch/osmo4_sym/osmo4_view.cpp new file mode 100644 index 0000000..fed8f86 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_sym/osmo4_view.cpp @@ -0,0 +1,587 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2006-2012 + * All rights reserved + * + * This file is part of GPAC / Symbian GUI player + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +// INCLUDE FILES +#include +#include + +#include "osmo4_view.h" +#include "osmo4_ui.h" + +#include +/*for initial setup*/ +#include + + +#if defined(__SERIES60_3X__) +#define GPAC_CFG_DIR "\\private\\F01F9075\\" +#define GPAC_MODULES_DIR "\\sys\\bin\\" +#else +#define GPAC_CFG_DIR "\\system\\apps\\Osmo4\\" +#define GPAC_MODULES_DIR GPAC_CFG_DIR +#endif + +// ============================ MEMBER FUNCTIONS =============================== + +// ----------------------------------------------------------------------------- +// Cosmo4AppView::NewL() +// Two-phased constructor. +// ----------------------------------------------------------------------------- +// +COsmo4AppView* COsmo4AppView::NewL( const TRect& aRect ) +{ + COsmo4AppView* self = COsmo4AppView::NewLC( aRect ); + CleanupStack::Pop( self ); + return self; +} + +// ----------------------------------------------------------------------------- +// COsmo4AppView::NewLC() +// Two-phased constructor. +// ----------------------------------------------------------------------------- +// +COsmo4AppView* COsmo4AppView::NewLC( const TRect& aRect ) +{ + COsmo4AppView* self = new ( ELeave ) COsmo4AppView; + CleanupStack::PushL( self ); + self->ConstructL( aRect ); + return self; +} + + + +// ----------------------------------------------------------------------------- +// COsmo4AppView::COsmo4AppView() +// C++ default constructor can NOT contain any code, that might leave. +// ----------------------------------------------------------------------------- +// +COsmo4AppView::COsmo4AppView() +{ + // No implementation required + m_pTimer = NULL; +#ifndef GPAC_GUI_ONLY + memset(&m_user, 0, sizeof(GF_User)); + m_term = NULL; + m_mx = NULL; + memset(&m_rti, 0, sizeof(GF_SystemRTInfo)); +#endif + last_title_update = 0; + show_rti = 0; +#if defined(__SERIES60_3X__) + selector = NULL; + target = NULL; +#endif +} + + + +// ----------------------------------------------------------------------------- +// COsmo4AppView::~COsmo4AppView() +// Destructor. +// ----------------------------------------------------------------------------- +// +COsmo4AppView::~COsmo4AppView() +{ + Shutdown(); +#ifndef GPAC_GUI_ONLY + if (m_mx) gf_mx_del(m_mx); +#endif + +#if defined(__SERIES60_3X__) + if (selector) delete selector; + //if (target) delete target; +#endif +} + +void COsmo4AppView::Shutdown() +{ +// MessageBox("Osmo4 shutdown request", ""); + if (m_pTimer) { + m_pTimer->Cancel(); + delete m_pTimer; + m_pTimer = NULL; + } +#ifndef GPAC_GUI_ONLY + if (m_term) { + GF_Terminal *t = m_term; + m_term = NULL; + gf_term_del(t); + } + if (m_Logs) { + gf_fclose(m_Logs); + m_Logs = NULL; + } + if (m_user.config) { + gf_cfg_del(m_user.config); + m_user.config = NULL; + } + if (m_user.modules) { + gf_modules_del(m_user.modules); + m_user.modules = NULL; + } +#endif +// MessageBox("Osmo4 shutdown OK", ""); +} + +void COsmo4AppView::MessageBox(const char *text, const char *title) +{ + HBufC *msg1, *msg2; + TInt length = User::StringLength( ( TUint8* ) text) + 1; + msg1 = HBufC::NewL( length ); + msg1->Des().Copy( TPtrC8(( TText8* ) text) ); + + length = User::StringLength( ( TUint8* ) title) + 1; + msg2 = HBufC::NewL( length ); + msg2->Des().Copy( TPtrC8(( TText8* ) title) ); + + CEikonEnv::Static()->InfoWinL(*msg2, *msg1); + delete msg1; + delete msg2; +} + +TInt myTick(TAny* aObject) +{ + return ((COsmo4AppView*)aObject)->OnTick(); +} + +TInt COsmo4AppView::OnTick() +{ +#ifndef GPAC_GUI_ONLY + if (m_term) gf_term_process_step(m_term); + + /*check RTI display*/ + if (show_rti && gf_sys_get_rti(500, &m_rti, 0)) DisplayRTI(); +#endif + /*never stop...*/ + return 1; +} + +void COsmo4AppView::DisplayRTI() +{ +#ifndef GPAC_GUI_ONLY + COsmo4AppUi *app = (COsmo4AppUi *) CEikonEnv::Static()->AppUi(); + char szInfo[20]; + sprintf(szInfo, "CPU %02d FPS %02.2f", m_rti.process_cpu_usage, gf_term_get_framerate(m_term, 0)); + app->SetInfo(szInfo); +#endif +} + + +//GPAC log function +static void on_gpac_log(void *cbk, GF_LOG_Level ll, GF_LOG_Tool lm, const char *fmt, va_list list) +{ + char szMsg[2048]; + COsmo4AppView *app = (COsmo4AppView *)cbk; + +#ifndef GPAC_GUI_ONLY + gf_mx_p(app->m_mx); + if (app->m_Logs) { + vfprintf(app->m_Logs, fmt, list); + } else { + vsnprintf(szMsg, 2048, fmt, list); + app->MessageBox(szMsg, "Error:"); + } + gf_mx_v(app->m_mx); +#endif +} + +static Bool GPAC_EventProc(void *ptr, GF_Event *evt) +{ + COsmo4AppView *app = (COsmo4AppView *)ptr; + return app->EventProc(evt); +} + +Bool COsmo4AppView::EventProc(GF_Event *evt) +{ + TRect r; + +#ifndef GPAC_GUI_ONLY + switch (evt->type) { + case GF_EVENT_MESSAGE: + if (!evt->message.message) return 0; + if (evt->message.error) { + char err[1024]; + sprintf(err, "Error: %s", gf_error_to_string(evt->message.error)); + MessageBox(evt->message.message, err); + } else { + MessageBox(evt->message.message, "Info"); + } + break; + case GF_EVENT_SCENE_SIZE: + r = Rect(); + gf_term_set_size(m_term, r.Width(), r.Height()); + break; + } +#endif + return 0; +} + +void COsmo4AppView::SetupLogs() +{ + const char *opt; + +#ifndef GPAC_GUI_ONLY + gf_mx_p(m_mx); + if (do_log) { + gf_log_set_tool_level(GF_LOG_ALL, GF_LOG_NONE); + do_log = 0; + } + /*setup GPAC logs: log all errors*/ + opt = gf_cfg_get_key(m_user.config, "General", "Logs"); + if (opt && !strstr(opt, "none")) { + const char *filename = gf_cfg_get_key(m_user.config, "General", "LogFile"); + if (!filename) { + gf_cfg_set_key(m_user.config, "General", "LogFile", "\\data\\gpac_logs.txt"); + filename = "\\data\\gpac_logs.txt"; + } + m_Logs = gf_fopen(filename, "wt"); + if (!m_Logs) { + MessageBox("Cannot open log file - disabling logs", "Warning !"); + } else { + MessageBox("Debug logs enabled!", filename); + do_log = 1; + gf_log_set_tools_levels( opt ); + } + } + if (!do_log) { + gf_log_set_tool_level(GF_LOG_ALL, GF_LOG_ERROR); + if (m_Logs) gf_fclose(m_Logs); + } + + gf_log_set_callback(this, on_gpac_log); + gf_mx_v(m_mx); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Osmo4 logs initialized\n")); +#endif +} + + +static void Osmo4_progress_cbk(void *usr, char *title, u32 done, u32 total) +{ +#ifndef GPAC_GUI_ONLY + COsmo4AppView *view = (COsmo4AppView *) usr; + COsmo4AppUi *app = (COsmo4AppUi *) CEikonEnv::Static()->AppUi(); + + if (done==total) { + app->SetInfo(NULL); + } else if (view->last_title_update + 500 < gf_sys_clock()) { + char szName[1024]; + view->last_title_update = gf_sys_clock(); + sprintf(szName, "%s %02d %%", title, (done*100 / total) ); + app->SetInfo(szName); + } +#endif +} + +// ----------------------------------------------------------------------------- +// COsmo4AppView::ConstructL() +// Symbian 2nd phase constructor can leave. +// ----------------------------------------------------------------------------- +// +void COsmo4AppView::ConstructL( const TRect& aRect ) +{ + const char *opt; + Bool first_launch = 0; + +#if defined(__SERIES60_3X__) + selector = CRemConInterfaceSelector::NewL(); + target = CRemConCoreApiTarget::NewL(*selector, *this); + selector->OpenTargetL(); +#endif + + // Create a window for this application view + CreateWindowL(); + // Set the windows size + SetRect( aRect ); + //draw + ActivateL(); + +#ifndef GPAC_GUI_ONLY + m_window = Window(); + m_session = CEikonEnv::Static()->WsSession(); + + m_mx = gf_mx_new("Osmo4"); + + //load config file + m_user.config = gf_cfg_init(NULL, &first_launch); + if (!m_user.config) { + MessageBox("Cannot create GPAC Config file", "Fatal Error"); + User::Leave(KErrGeneral); + } + if (first_launch) { + MessageBox("Osmo4", "Thank you for Installing"); + } + + /*load modules*/ + opt = gf_cfg_get_key(m_user.config, "Core", "ModulesDirectory"); + m_user.modules = gf_modules_new(opt, m_user.config); + if (!m_user.modules || !gf_modules_get_count(m_user.modules)) { + MessageBox(m_user.modules ? "No modules available" : "Cannot create module manager", "Fatal Error"); + if (m_user.modules) gf_modules_del(m_user.modules); + gf_cfg_del(m_user.config); + User::Leave(KErrGeneral); + } + + if (first_launch) { + /*first launch, register all files ext*/ + for (u32 i=0; iCanHandleURL(ifce, "test.test"); + gf_modules_close_interface((GF_BaseInterface *)ifce); + } + } + } + + /*we don't thread the terminal, ie appart from the audio renderer, media decoding and visual rendering is + handled by the app process*/ + m_user.init_flags = GF_TERM_NO_REGULATION; + m_user.EventProc = GPAC_EventProc; + m_user.opaque = this; + m_user.os_window_handler = (void *) &m_window; + m_user.os_display = (void *) &m_session; + + m_term = gf_term_new(&m_user); + if (!m_term) { + MessageBox("Cannot load GPAC terminal", "Fatal Error"); + gf_modules_del(m_user.modules); + gf_cfg_del(m_user.config); + User::Leave(KErrGeneral); + } + //MessageBox("GPAC terminal loaded", "Success !"); + + /*ok set output size*/ + TSize s = m_window.Size(); + gf_term_set_size(m_term, s.iWidth, s.iHeight); + + + /*start our callback (every ms)*/ + const TInt KTickInterval = 33000; + m_pTimer = CPeriodic::NewL(CActive::EPriorityStandard); + m_pTimer->Start(KTickInterval, KTickInterval, TCallBack(myTick, this)); + + opt = gf_cfg_get_key(m_user.config, "General", "StartupFile"); + if (opt) gf_term_connect(m_term, opt); + +#endif + +} + + +// ----------------------------------------------------------------------------- +// COsmo4AppView::Draw() +// Draws the display. +// ----------------------------------------------------------------------------- +// +void COsmo4AppView::Draw( const TRect& /*aRect*/ ) const +{ +#ifndef GPAC_GUI_ONLY + if (!m_term) { + CWindowGc& gc = SystemGc(); + TRgb black(0,0,0); + gc.SetBrushColor(black); + gc.Clear(); + } else { + /*FIXME - this is just to force a screen flush, needs rework*/ + gf_term_set_option(m_term, GF_OPT_FREEZE_DISPLAY, 0); + } +#else + CWindowGc& gc = SystemGc(); + TRgb black(0,0,0); + TRect rect = Rect(); + gc.SetBrushColor(black); + gc.Clear(rect); +#endif +} + +void COsmo4AppView::ShowHide(Bool show) +{ +#ifndef GPAC_GUI_ONLY + if (show) { + MakeVisible(ETrue); + if (m_term) { + gf_term_set_option(m_term, GF_OPT_VISIBLE, 1); + DrawDeferred(); + } + } else { + MakeVisible(EFalse); + if (m_term) gf_term_set_option(m_term, GF_OPT_VISIBLE, 0); + } +#else + MakeVisible(ETrue); +#endif +} + +// ----------------------------------------------------------------------------- +// COsmo4AppView::SizeChanged() +// Called by framework when the view size is changed. +// ----------------------------------------------------------------------------- +// +void COsmo4AppView::SizeChanged() +{ +#ifndef GPAC_GUI_ONLY + if (m_term) { + TSize s = m_window.Size(); + gf_term_set_size(m_term, s.iWidth, s.iHeight); + } +#endif + DrawNow(); +} + +void COsmo4AppView::Connect(const char *url) +{ +#ifndef GPAC_GUI_ONLY + char the_url[1024]; + /*copy before removing from recent files*/ + strcpy(the_url, url); + gf_cfg_set_key(m_user.config, "RecentFiles", the_url, NULL); + gf_cfg_insert_key(m_user.config, "RecentFiles", the_url, "", 0); + u32 count = gf_cfg_get_key_count(m_user.config, "RecentFiles"); + if (count > 10) gf_cfg_set_key(m_user.config, "RecentFiles", gf_cfg_get_key_name(m_user.config, "RecentFiles", count-1), NULL); + + if (m_term) gf_term_connect(m_term, the_url); +#endif +} + + +TKeyResponse COsmo4AppView::OfferKeyEventL(const TKeyEvent& aKeyEvent, TEventCode aType) +{ + GF_Event evt; + u32 ret; + + evt.key.hw_code = aKeyEvent.iScanCode; + evt.key.flags = 0; + switch (aType) { + case EEventKeyUp: + evt.type = GF_EVENT_KEYUP; + break; + case EEventKeyDown: + case EEventKey: + evt.type = GF_EVENT_KEYDOWN; + break; + default: + return EKeyWasNotConsumed; + } + + switch (aKeyEvent.iCode) { + case EKeyLeftArrow: + evt.key.key_code = GF_KEY_LEFT; + break; + case EKeyRightArrow: + evt.key.key_code = GF_KEY_RIGHT; + break; + case EKeyUpArrow: + evt.key.key_code = GF_KEY_UP; + break; + case EKeyDownArrow: + evt.key.key_code = GF_KEY_DOWN; + break; + case EKeyIncVolume: + evt.key.key_code = GF_KEY_VOLUMEUP; + break; + case EKeyDecVolume: + evt.key.key_code = GF_KEY_VOLUMEDOWN; + break; + default: + switch (aKeyEvent.iScanCode) { + case EStdKeyIncVolume: + evt.key.key_code = GF_KEY_VOLUMEUP; + break; + case EStdKeyDecVolume: + evt.key.key_code = GF_KEY_VOLUMEDOWN; + break; + default: + return EKeyWasNotConsumed; + } + } +#ifndef GPAC_GUI_ONLY + ret = gf_term_user_event(m_term, &evt); + /*generate a key up*/ + if (aType==EEventKey) { + evt.type = GF_EVENT_KEYUP; + ret += gf_term_user_event(m_term, &evt); + } +#else + ret = 0; +#endif + return ret ? EKeyWasConsumed : EKeyWasNotConsumed; +} + +#if defined(__SERIES60_3X__) +void COsmo4AppView::MrccatoCommand(TRemConCoreApiOperationId aOperationId, TRemConCoreApiButtonAction aButtonAct) +{ + GF_Event e; + switch (aOperationId) { + /* + TRequestStatus status; + case ERemConCoreApiPausePlayFunction: + case ERemConCoreApiStop: + case ERemConCoreApiRewind: + case ERemConCoreApiForward: + case ERemConCoreApiFastForward: + case ERemConCoreApiBackward: + switch (aButtonAct) { + case ERemConCoreApiButtonPress: + break; + case ERemConCoreApiButtonRelease: + break; + case ERemConCoreApiButtonClick: + break; + default: + break; + } + */ + case ERemConCoreApiVolumeUp: + case ERemConCoreApiVolumeDown: +#ifndef GPAC_GUI_ONLY + e.key.hw_code = 0; + e.key.flags = 0; + e.key.key_code = (aOperationId==ERemConCoreApiVolumeUp) ? GF_KEY_VOLUMEUP : GF_KEY_VOLUMEDOWN; + switch (aButtonAct) { + case ERemConCoreApiButtonPress: + e.type = GF_EVENT_KEYDOWN; + gf_term_user_event(m_term, &e); + break; + case ERemConCoreApiButtonRelease: + e.type = GF_EVENT_KEYUP; + gf_term_user_event(m_term, &e); + break; + default: + e.type = GF_EVENT_KEYDOWN; + gf_term_user_event(m_term, &e); + e.type = GF_EVENT_KEYUP; + gf_term_user_event(m_term, &e); + break; + } +#endif + break; + default: + break; + } +} + +#endif diff --git a/applications/deprecated/old_arch/osmo4_sym/osmo4_view.h b/applications/deprecated/old_arch/osmo4_sym/osmo4_view.h new file mode 100644 index 0000000..c98e4a5 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_sym/osmo4_view.h @@ -0,0 +1,171 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2006-2012 + * All rights reserved + * + * This file is part of GPAC / Symbian GUI player + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#ifndef __OSMO4_VIEW_H__ +#define __OSMO4_VIEW_H__ + +// INCLUDES +#include + +#if defined(__SERIES60_3X__) +#include +#include +#include +#endif + + +#include +#include + + +// CLASS DECLARATION +class COsmo4AppView : public CCoeControl +#if defined(__SERIES60_3X__) + ,MRemConCoreApiTargetObserver +#endif + +{ +public: // New methods + + /** + * NewL. + * Two-phased constructor. + * Create a COsmo4AppView object, which will draw itself to aRect. + * @param aRect The rectangle this view will be drawn to. + * @return a pointer to the created instance of COsmo4AppView. + */ + static COsmo4AppView* NewL( const TRect& aRect ); + + /** + * NewLC. + * Two-phased constructor. + * Create a COsmo4AppView object, which will draw itself + * to aRect. + * @param aRect Rectangle this view will be drawn to. + * @return A pointer to the created instance of COsmo4AppView. + */ + static COsmo4AppView* NewLC( const TRect& aRect ); + + /** + * ~COsmo4AppView + * Virtual Destructor. + */ + virtual ~COsmo4AppView(); + +public: // Functions from base classes + + /** + * From CCoeControl, Draw + * Draw this COsmo4AppView to the screen. + * @param aRect the rectangle of this view that needs updating + */ + void Draw( const TRect& aRect ) const; + + /** + * From CoeControl, SizeChanged. + * Called by framework when the view size is changed. + */ + virtual void SizeChanged(); + +#ifndef GPAC_GUI_ONLY + GF_User *GetUser() { + return &m_user; + } +#else + GF_User *GetUser() { + return NULL; + } +#endif + void SetupLogs(); + void MessageBox(const char *text, const char *title); + + virtual TKeyResponse OfferKeyEventL(const TKeyEvent& aKeyEvent, TEventCode aType); + +#if defined(__SERIES60_3X__) + void MrccatoCommand(TRemConCoreApiOperationId aOperationId, TRemConCoreApiButtonAction aButtonAct); +#endif + + TInt OnTick(); + + void Shutdown(); + + void Connect(const char *url); + void ShowHide(Bool show); + Bool EventProc(GF_Event *evt); + +#ifndef GPAC_GUI_ONLY + GF_Terminal *m_term; +#endif + +private: // Constructors + + /** + * ConstructL + * 2nd phase constructor. + * Perform the second phase construction of a + * COsmo4AppView object. + * @param aRect The rectangle this view will be drawn to. + */ + void ConstructL(const TRect& aRect); + + void DisplayRTI(); + + /** + * COsmo4AppView. + * C++ default constructor. + */ + COsmo4AppView(); + + CPeriodic *m_pTimer; + + RWindow m_window; + RWsSession m_session; +#ifndef GPAC_GUI_ONLY + GF_SystemRTInfo m_rti; +#endif + +#if defined(__SERIES60_3X__) + CRemConInterfaceSelector *selector; + CRemConCoreApiTarget *target; +#endif + + +public: + u32 last_title_update; + FILE *m_Logs; + Bool do_log; + Bool show_rti; +#ifndef GPAC_GUI_ONLY + GF_Mutex *m_mx; + GF_User m_user; +#endif +}; + + +#endif // __OSMO4_VIEW_H__ + +// End of File + diff --git a/applications/deprecated/old_arch/osmo4_sym/playlist.cpp b/applications/deprecated/old_arch/osmo4_sym/playlist.cpp new file mode 100644 index 0000000..cca94d2 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_sym/playlist.cpp @@ -0,0 +1,529 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2006-2012 + * All rights reserved + * + * This file is part of GPAC / Symbian GUI player + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include + + +#include + +// INCLUDE FILES +#include "osmo4_ui.h" +#include "playlist.h" + +#ifdef USE_SKIN +#include +#include +#include // skin +#include //skin +#endif + + +CPlaylist::CPlaylist() +{ + playlist_mode = 0; + view_all_files = 0; +} +CPlaylist::~CPlaylist() +{ + delete iListBox; + delete iBackGround; +} + +CPlaylist* CPlaylist::NewL( const TRect& aRect, GF_User *user) +{ + CPlaylist* self = CPlaylist::NewLC( aRect, user); + CleanupStack::Pop( self ); + return self; +} +CPlaylist* CPlaylist::NewLC( const TRect& aRect, GF_User *user) +{ + CPlaylist* self = new ( ELeave ) CPlaylist; + CleanupStack::PushL( self ); + self->ConstructL( aRect, user); + return self; +} + +void CPlaylist::ConstructL(const TRect& aRect, GF_User *user) +{ + CreateWindowL(); + +#ifdef USE_SKIN + iListBox = new (ELeave) CAknSingleStyleListBox(); +#else + iListBox = new (ELeave) CEikTextListBox(); +#endif + iListBox->ConstructL(this); + iListBox->SetContainerWindowL(*this); + iListBox->SetListBoxObserver(this); + + CDesCArray* textArray = new (ELeave) CDesCArrayFlat(16); + iListBox->Model()->SetItemTextArray( textArray ); + iListBox->Model()->SetOwnershipType( ELbmOwnsItemArray ); + + // Creates scrollbar. + iListBox->CreateScrollBarFrameL( ETrue ); + iListBox->ScrollBarFrame()->SetScrollBarVisibilityL(CEikScrollBarFrame::EAuto, CEikScrollBarFrame::EAuto); + //iListBox->ActivateL(); + + iListBox->SetFocus(ETrue); + + SetRect(aRect); + ActivateL(); + MakeVisible(EFalse); + + strcpy(szCurrentDir, ""); + +#ifndef GPAC_GUI_ONLY + m_user = user; + + strcpy(ext_list, ""); + u32 count = gf_cfg_get_key_count(user->config, "MimeTypes"); + for (u32 i=0; iconfig, "MimeTypes", i); + const char *opt = gf_cfg_get_key(user->config, "MimeTypes", sMime); + strcpy(szKeyList, opt+1); + sKey = strrchr(szKeyList, '\"'); + if (!sKey) continue; + sKey[0] = 0; + strcat(ext_list, szKeyList); + strcat(ext_list, " "); + } + + const char *opt = gf_cfg_get_key(m_user->config, "General", "LastWorkingDir"); + if (opt) strcpy(szCurrentDir, opt); +#endif + +} + +void CPlaylist::SizeChanged() +{ + iListBox->SetRect( Rect() ); +} + +void CPlaylist::Draw(const TRect& aRect) const +{ +#ifdef USE_SKIN + CWindowGc& gc = SystemGc(); + MAknsSkinInstance* skin = AknsUtils::SkinInstance(); + MAknsControlContext* cc = AknsDrawUtils::ControlContext( this ); + AknsDrawUtils::Background( skin, cc, this, gc, aRect ); +#endif + +} + +#ifdef USE_SKIN +TTypeUid::Ptr CPlaylist::MopSupplyObject(TTypeUid aId) +{ + if(aId.iUid == MAknsControlContext::ETypeId && iBackGround) { + return MAknsControlContext::SupplyMopObject( aId, iBackGround); + } + return CCoeControl::MopSupplyObject( aId ); +} +#endif + + +TInt CPlaylist::CountComponentControls() const +{ + return 1; +} +CCoeControl* CPlaylist::ComponentControl(TInt aIndex) const +{ + switch (aIndex) { + case 0: + return iListBox; + default: + return NULL; + } +} + +TKeyResponse CPlaylist::OfferKeyEventL(const TKeyEvent& aKeyEvent, TEventCode aType) +{ + if (aType != EEventKey) return iListBox->OfferKeyEventL(aKeyEvent, aType); + + switch (aKeyEvent.iScanCode) { + case EStdKeyEnter: + HandleSelection(); + return EKeyWasConsumed; + default: + return iListBox->OfferKeyEventL(aKeyEvent, aType); + } +} +void CPlaylist::HandleListBoxEventL(CEikListBox* aListBox, TListBoxEvent aEventType ) +{ + if (aEventType == MEikListBoxObserver::EEventItemClicked || + aEventType == MEikListBoxObserver::EEventEnterKeyPressed) + + HandleSelection(); +} + +void CPlaylist::ShowHide(Bool show) +{ + if (show) { + RefreshPlaylist(); + MakeVisible(ETrue); + DrawNow(); + } else { + /*cleanup*/ + ResetView(); + MakeVisible(EFalse); + ((COsmo4AppUi *) CEikonEnv::Static()->AppUi())->SetTitle(NULL, 0); + } +} + + +void CPlaylist::FlushItemList() +{ + iListBox->HandleItemAdditionL(); + iListBox->SetCurrentItemIndexAndDraw(0); +} + +void CPlaylist::ResetView() +{ + CDesCArray* array = static_cast(iListBox->Model()->ItemTextArray()); + array->Reset(); + iListBox->Reset(); +} + +void CPlaylist::AddItem(const char *name, int is_directory) +{ + TBuf<100> tmp; + char szName[100]; + CDesCArray* array = static_cast(iListBox->Model()->ItemTextArray()); + + if (is_directory) { +#ifdef USE_SKIN + sprintf(szName, "\t+ %s\t\t", name); +#else + sprintf(szName, "+ %s", name); +#endif + } else { +#ifdef USE_SKIN + sprintf(szName, "\t%s\t\t", name); +#else + strcpy(szName, name); +#endif + } + tmp.SetLength(strlen(szName)+1); + tmp.Copy( TPtrC8(( TText8* ) szName) ); + tmp.ZeroTerminate(); + array->AppendL(tmp); +} + +static Bool enum_dirs(void *cbk, char *name, char *path, GF_FileEnumInfo *file_info) +{ + CPlaylist *of = (CPlaylist *)cbk; + of->AddItem(name, 1); + return 0; +} + +static Bool enum_files(void *cbk, char *name, char *path, GF_FileEnumInfo *file_info) +{ + CPlaylist *of = (CPlaylist *)cbk; + of->AddItem(name, 0); + return 0; +} + + +void CPlaylist::ScanDirectory(const char *dir) +{ + ResetView(); + + if (!dir || !strlen(dir)) { + RFs iFs; + TDriveList aList; + iFs.Connect(); + iFs.DriveList(aList); + for (TInt i=0; iAppUi())->SetTitle(szCurrentDir, 0); + +} + +void CPlaylist::GetSelectionName(char *szName) +{ + CDesCArray* array = static_cast(iListBox->Model()->ItemTextArray()); + TInt idx = iListBox->CurrentItemIndex(); + +#ifndef GPAC_GUI_ONLY + +#if defined(_UNICODE) + size_t len; + /*handle terminating zero !!*/ + u16 szNameUTF16[100]; + len = (*array)[idx].Size(); + memcpy(szNameUTF16, (*array)[idx].Ptr(), sizeof(u8)*len); + szNameUTF16[len/2] = 0; + const u16 *sptr = szNameUTF16; + + /*skip initial '\t'*/ +#ifdef USE_SKIN + sptr += 1; +#endif + + len = gf_utf8_wcstombs(szName, 512, &sptr); + szName[len] = 0; + + +#else + + char *src = (*array)[idx]).Ptr(); + /*skip initial '\t'*/ +#ifdef USE_SKIN + src += 1; +#endif + strcpy(szName, (const char *) src) ; +#endif + + /*remove trailing "\t\t"*/ +#ifdef USE_SKIN + len = strlen(szName); + szName[len-2] = 0; +#endif + +#else + szName[0] = 0; +#endif + +} + +void CPlaylist::HandleSelection() +{ + char szName[100]; + GetSelectionName(szName); + + /*sub-directory*/ + if ((szName[0] == '+') && (szName[1] == ' ')) { + /*browse up*/ + if ((szName[2] == '.') && (szName[3] == '.')) { + char *prev = strrchr(szCurrentDir, '\\'); + if (prev) { + prev[0] = 0; + ScanDirectory(szCurrentDir); + } else { + ScanDirectory(NULL); + } + } else { + strcat(szCurrentDir, "\\"); + strcat(szCurrentDir, szName+2); + ScanDirectory(szCurrentDir); + } + } else if (szName[1] == ':') { + ScanDirectory(szName); + } else { + char szURL[1024]; + COsmo4AppUi *app = (COsmo4AppUi *) CEikonEnv::Static()->AppUi(); + if (playlist_mode) { + TInt idx = iListBox->CurrentItemIndex(); +#ifndef GPAC_GUI_ONLY + const char *url = gf_cfg_get_key_name(m_user->config, "Playlist", idx); + if (url) app->PlayURL(url); +#endif + } else { + gf_cfg_set_key(m_user->config, "General", "LastWorkingDir", (const char *) szCurrentDir); + sprintf(szURL, "%s\\%s", szCurrentDir, szName); + app->PlayURL(szURL); + } + } +} + +Bool CPlaylist::SelectionIsFile() +{ + char szName[100]; + GetSelectionName(szName); + if ((szName[0] == '+') && (szName[1] == ' ')) return 0; + else if (szName[1] == ':') return 0; + return 1; +} + +Bool CPlaylist::IsInPlaylist() +{ + char szURL[1024]; + char szName[100]; + GetSelectionName(szName); + if ((szName[0] == '+') && (szName[1] == ' ')) return 0; + else if (szName[1] == ':') return 0; + + /*remove from playlist*/ + sprintf(szURL, "%s\\%s", szCurrentDir, szName); +#ifndef GPAC_GUI_ONLY + const char *opt = gf_cfg_get_key(m_user->config, "Playlist", szURL); + if (opt) return 1; +#endif + return 0; +} + +static Bool dir_add_files(void *cbk, char *name, char *path, GF_FileEnumInfo *file_info) +{ + CPlaylist *pl = (CPlaylist *)cbk; + +#if 0 + if (!bViewUnknownTypes && extension_list) { + char *ext = strrchr(name, '.'); + if (!ext || !strstr(extension_list, ext+1)) return 0; + } +#endif + +#ifndef GPAC_GUI_ONLY + gf_cfg_set_key(pl->m_user->config, "Playlist", path, ""); +#endif + + return 0; +} + + +void CPlaylist::RefreshPlaylist() +{ + if (playlist_mode) { +#ifndef GPAC_GUI_ONLY + u32 count = gf_cfg_get_key_count(m_user->config, "Playlist"); + ResetView(); + for (u32 i=0; iconfig, "Playlist", i); + const char *sep = strrchr(opt, '\\'); + if (!sep) sep = strrchr(opt, '/'); + AddItem(sep ? (sep+1) : opt, 0); + } + if (!count) AddItem("[empty]", 0); +#endif + FlushItemList(); + + ((COsmo4AppUi *) CEikonEnv::Static()->AppUi())->SetTitle("Playlist", 0); + } else { + ScanDirectory(szCurrentDir); + } +} + + +void CPlaylist::PlaylistAct(Osmo4_PLActions act) +{ + char szURL[1024]; + char szName[100]; + CDesCArray*array; + TInt idx; + TInt count; + + if (act==Osmo4PLClear) { + while (1) { +#ifndef GPAC_GUI_ONLY + const char *opt = gf_cfg_get_key_name(m_user->config, "Playlist", 0); + if (!opt) break; + gf_cfg_set_key(m_user->config, "Playlist", opt, NULL); +#endif + } + RefreshPlaylist(); + return; + } else if (act == Osmo4PLToggleMode) { + playlist_mode = !playlist_mode; + RefreshPlaylist(); + return; + } else if (act == Osmo4PLToggleAllFiles) { + view_all_files = !view_all_files; + RefreshPlaylist(); + return; + } else if (act == Osmo4PLAdd) { +#ifndef GPAC_GUI_ONLY + GetSelectionName(szName); + if ((szName[0] == '+') && (szName[1] == ' ')) { + if ((szName[2] != '.') && (szName[3] != '.')) { + sprintf(szURL, "%s\\%s", szCurrentDir, szName+2); + gf_enum_directory(szURL, 0, dir_add_files, this, view_all_files ? NULL : ext_list); + } + } else if (szName[1] == ':') { + gf_enum_directory(szName, 0, dir_add_files, this, view_all_files ? NULL : ext_list); + } else { + sprintf(szURL, "%s\\%s", szCurrentDir, szName); + gf_cfg_set_key(m_user->config, "Playlist", szURL, ""); + } +#endif + return; + } + + GetSelectionName(szName); + if ((szName[0] == '+') && (szName[1] == ' ')) return; + else if (szName[1] == ':') return; + + switch (act) { + /*remove from playlist*/ + case Osmo4PLRem: +#ifndef GPAC_GUI_ONLY + sprintf(szURL, "%s\\%s", szCurrentDir, szName); + gf_cfg_set_key(m_user->config, "Playlist", szURL, NULL); +#endif + RefreshPlaylist(); + break; + /*move up*/ + case Osmo4PLMoveUp: + array = static_cast(iListBox->Model()->ItemTextArray()); + count = array->Count(); + idx = iListBox->CurrentItemIndex(); + sprintf(szURL, "%s\\%s", szCurrentDir, szName); +#ifndef GPAC_GUI_ONLY + gf_cfg_set_key(m_user->config, "Playlist", szURL, NULL); + gf_cfg_insert_key(m_user->config, "Playlist", szURL, "", idx-1); +#endif + RefreshPlaylist(); + if (idx>1) iListBox->SetCurrentItemIndexAndDraw(idx-1); + break; + /*move down*/ + case Osmo4PLMoveDown: + array = static_cast(iListBox->Model()->ItemTextArray()); + count = array->Count(); + idx = iListBox->CurrentItemIndex(); + sprintf(szURL, "%s\\%s", szCurrentDir, szName); +#ifndef GPAC_GUI_ONLY + gf_cfg_set_key(m_user->config, "Playlist", szURL, NULL); + gf_cfg_insert_key(m_user->config, "Playlist", szURL, "", idx+1); +#endif + RefreshPlaylist(); + if (idxSetCurrentItemIndexAndDraw(idx+1); + break; + default: + break; + } +} + diff --git a/applications/deprecated/old_arch/osmo4_sym/playlist.h b/applications/deprecated/old_arch/osmo4_sym/playlist.h new file mode 100644 index 0000000..c8186c8 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_sym/playlist.h @@ -0,0 +1,114 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2006-2012 + * All rights reserved + * + * This file is part of GPAC / Symbian GUI player + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __osmo4playlist_H__ +#define __osmo4playlist_H__ + +#include +#include +#include + +#define USE_SKIN + +#ifdef USE_SKIN +class MAknsControlContext; // for skins support +#endif + + +#include + +class CEikTextListBox; //For list box + +enum Osmo4_PLActions +{ + Osmo4PLAdd = 0, + Osmo4PLRem, + Osmo4PLClear, + Osmo4PLMoveUp, + Osmo4PLMoveDown, + Osmo4PLToggleMode, + Osmo4PLToggleAllFiles, +}; + +class CPlaylist : public CCoeControl,MEikListBoxObserver +{ +public: + static CPlaylist* NewL( const TRect& aRect, GF_User *user); + static CPlaylist* NewLC( const TRect& aRect, GF_User *user); + virtual ~CPlaylist(); + void SizeChanged(); + + virtual TKeyResponse OfferKeyEventL(const TKeyEvent& aKeyEvent, TEventCode aType); + + void HandleListBoxEventL(CEikListBox* aListBox, TListBoxEvent aEventType ); + + void AddItem(const char *name, int is_directory); + TInt CountComponentControls() const; + CCoeControl* ComponentControl(TInt aIndex) const; + void Draw(const TRect& aRect) const; + + void ShowHide(Bool show); + Bool SelectionIsFile(); + Bool IsInPlaylist(); + Bool PlaylistMode() { + return playlist_mode; + } + Bool ViewAllFiles() { + return view_all_files; + } + + void PlaylistAct(Osmo4_PLActions act); + +#ifndef GPAC_GUI_ONLY + GF_User *m_user; +#endif + +private: + void ConstructL(const TRect& aRect, GF_User *user); + CPlaylist(); + + +#ifdef USE_SKIN + TTypeUid::Ptr MopSupplyObject(TTypeUid aId); + MAknsControlContext* iBackGround; +#endif + + + void ResetView(); + void FlushItemList(); + void ScanDirectory(const char *dir); + void HandleSelection(); + void GetSelectionName(char *name); + void RefreshPlaylist(); + + char szCurrentDir[1024]; + CEikTextListBox* iListBox; + Bool playlist_mode; + Bool view_all_files; + char ext_list[4096]; +}; + +#endif //__osmo4playlist_H__ + diff --git a/applications/deprecated/old_arch/osmo4_sym/res/osmo4.rss b/applications/deprecated/old_arch/osmo4_sym/res/osmo4.rss new file mode 100644 index 0000000..b73da36 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_sym/res/osmo4.rss @@ -0,0 +1,19 @@ +#include +#include "osmo4_gen.rss" + +rls_string STRING_osmo_caption_string "Osmo4" + +RESOURCE LOCALISABLE_APP_INFO r_osmo4_localisable_app_info + { + short_caption = STRING_osmo_caption_string; + caption_and_icon = + CAPTION_AND_ICON_INFO + { + caption = STRING_osmo_caption_string; + + number_of_icons = 1; + icon_file = "\\resource\\apps\\osmo4_aif.mif"; + }; + } + +// End of File diff --git a/applications/deprecated/old_arch/osmo4_sym/res/osmo4.svg b/applications/deprecated/old_arch/osmo4_sym/res/osmo4.svg new file mode 100644 index 0000000..a8fa8b0 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_sym/res/osmo4.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/deprecated/old_arch/osmo4_sym/res/osmo4_caption.rss b/applications/deprecated/old_arch/osmo4_sym/res/osmo4_caption.rss new file mode 100644 index 0000000..6a6a0a0 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_sym/res/osmo4_caption.rss @@ -0,0 +1,21 @@ +/* Copyright (c) 2004, Nokia. All rights reserved */ + + +// INCLUDES +#include + + +// RESOURCE DEFINITIONS +// ----------------------------------------------------------------------------- +// +// Caption data for Osmo4 +// +// ----------------------------------------------------------------------------- +// +RESOURCE CAPTION_DATA + { + caption="Osmo4"; + shortcaption= "Osmo4"; + } + +// End of File diff --git a/applications/deprecated/old_arch/osmo4_sym/res/osmo4_gen.rss b/applications/deprecated/old_arch/osmo4_sym/res/osmo4_gen.rss new file mode 100644 index 0000000..a1cb490 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_sym/res/osmo4_gen.rss @@ -0,0 +1,59 @@ +/* Copyright (c) 2004, Nokia. All rights reserved */ + +// RESOURCE IDENTIFIER +NAME OSMO // 4 letter ID + + +// INCLUDES +#include +#include +#include + +RESOURCE RSS_SIGNATURE + { + } + +RESOURCE TBUF r_default_document_name + { + buf="OSMO"; + } + +RESOURCE EIK_APP_INFO + { + menubar = r_Osmo4_menubar; + cba = R_AVKON_SOFTKEYS_OPTIONS_EXIT; + } + + +RESOURCE MENU_BAR r_Osmo4_menubar + { + titles = + { + MENU_TITLE { menu_pane = r_Osmo4_menu; } + }; + } + + +RESOURCE MENU_PANE r_Osmo4_menu + { + } + +RESOURCE MENU_PANE r_osmo4_sm1 + { + } + +RESOURCE MENU_PANE r_osmo4_sm2 + { + } + +RESOURCE MENU_PANE r_osmo4_sm3 + { + } + +RESOURCE MENU_PANE r_osmo4_ssm1 + { + } +RESOURCE MENU_PANE r_osmo4_ssm2 + { + } + diff --git a/applications/deprecated/old_arch/osmo4_sym/res/osmo4_reg.rss b/applications/deprecated/old_arch/osmo4_sym/res/osmo4_reg.rss new file mode 100644 index 0000000..eba4e8a --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_sym/res/osmo4_reg.rss @@ -0,0 +1,31 @@ +/* +* ============================================================================== +* Name : osmo4_reg.rss +* Part of : osmo4 +* Interface : +* Description : +* Version : +* +* Copyright (c) 2005-2006 Nokia Corporation. +* This material, including documentation and any related +* computer programs, is protected by copyright controlled by +* Nokia Corporation. +* ============================================================================== +*/ + +#include +#include + +UID2 KUidAppRegistrationResourceFile +UID3 0xF01F9075 + +RESOURCE APP_REGISTRATION_INFO + { + app_file="Osmo4"; + localisable_resource_file = "\\resource\\apps\\Osmo4"; + localisable_resource_id = R_OSMO4_LOCALISABLE_APP_INFO; + + embeddability=KAppNotEmbeddable; + newfile=KAppDoesNotSupportNewFile; + } + diff --git a/applications/deprecated/old_arch/osmo4_w32/AddressBar.cpp b/applications/deprecated/old_arch/osmo4_w32/AddressBar.cpp new file mode 100644 index 0000000..21d6049 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/AddressBar.cpp @@ -0,0 +1,182 @@ +// AddressBar.cpp : implementation file +// + +#include "stdafx.h" +#include "osmo4.h" +#include "MainFrm.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// AddressBar dialog + +IMPLEMENT_DYNAMIC(CInitDialogBar, CDialogBar) + +BEGIN_MESSAGE_MAP(CInitDialogBar, CDialogBar) +END_MESSAGE_MAP() + + +CInitDialogBar::CInitDialogBar() +{ +} + +CInitDialogBar::~CInitDialogBar() +{ +} + +BOOL CInitDialogBar::Create(CWnd * pParentWnd, LPCTSTR lpszTemplateName, UINT nStyle, UINT nID) +{ + if(!CDialogBar::Create(pParentWnd, lpszTemplateName, nStyle, nID)) + return FALSE; + + if (!OnInitDialog()) return FALSE; + + return TRUE; +} + +BOOL CInitDialogBar::Create(CWnd * pParentWnd, UINT nIDTemplate, UINT nStyle, UINT nID) +{ + if(!Create(pParentWnd, MAKEINTRESOURCE(nIDTemplate), nStyle, nID)) return FALSE; + + if(!OnInitDialog()) return FALSE; + return TRUE; +} + + +BOOL CInitDialogBar::OnInitDialog() +{ + UpdateData(FALSE); + return TRUE; +} + +void CInitDialogBar::DoDataExchange(CDataExchange* pDX) +{ + CDialogBar::DoDataExchange(pDX); +} + + + +IMPLEMENT_DYNAMIC(AddressBar, CInitDialogBar) + + +BEGIN_MESSAGE_MAP(AddressBar, CInitDialogBar) + //{{AFX_MSG_MAP(AddressBar) + ON_WM_SIZE() + ON_WM_CLOSE() + ON_CBN_SELENDOK(IDC_ADDRESS, OnSelendOK) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + + +AddressBar::AddressBar () : CInitDialogBar() +{ +} + +BOOL AddressBar::OnInitDialog() +{ + CInitDialogBar::OnInitDialog(); + + return TRUE; +} + + +void AddressBar::DoDataExchange(CDataExchange* pDX) +{ + CInitDialogBar::DoDataExchange(pDX); + //{{AFX_DATA_MAP(AddressBar) + DDX_Control(pDX, IDC_DUMTXT, m_Title); + DDX_Control(pDX, IDC_ADDRESS, m_Address); + //}}AFX_DATA_MAP +} + + +///////////////////////////////////////////////////////////////////////////// +// AddressBar message handlers + +void AddressBar::OnSize(UINT nType, int cx, int cy) +{ + u32 w; + POINT pt; + //CDialog::OnSize(nType, cx, cy); + + if (!m_Address.m_hWnd) return; + RECT rc; + m_Title.GetClientRect(&rc); + w = rc.right - rc.left; + m_Address.GetWindowRect(&rc); + pt.x = rc.left; + pt.y = rc.top; + ScreenToClient(&pt); + rc.right = cx - pt.x; + m_Address.SetWindowPos(this, 0, 0, rc.right, rc.bottom, SWP_NOZORDER | SWP_NOMOVE); + +} + +void AddressBar::OnClose() +{ +} + +void AddressBar::ReloadURLs() +{ + Osmo4 *gpac = GetApp(); + u32 i=0; + + while (m_Address.GetCount()) m_Address.DeleteString(0); + while (1) { + const char *sOpt = gf_cfg_get_key_name(gpac->m_user.config, "RecentFiles", i); + if (!sOpt) return; + m_Address.AddString(sOpt); + i++; + } +} + +void AddressBar::SelectionReady() +{ + void UpdateLastFiles(GF_Config *cfg, const char *URL); + + CString URL; + int sel = m_Address.GetCurSel(); + if (sel == CB_ERR) { + m_Address.GetWindowText(URL); + } else { + m_Address.GetLBText(sel, URL); + } + if (!URL.GetLength()) return; + Osmo4 *gpac = GetApp(); + Playlist *pl = ((CMainFrame*)gpac->m_pMainWnd)->m_pPlayList; + /*don't store local files*/ + if (URL.Find("://", 0)>0) { + UpdateLastFiles(gpac->m_user.config, URL); + ReloadURLs(); + } + pl->Truncate(); + pl->QueueURL(URL); + pl->RefreshList(); + pl->PlayNext(); +} + +void AddressBar::OnSelendOK() +{ + SelectionReady(); +} + +BOOL AddressBar::PreTranslateMessage(MSG* pMsg) +{ + if (pMsg->message == WM_KEYDOWN) { + switch (pMsg->wParam) { + case VK_RETURN: + ::TranslateMessage(pMsg); + ::DispatchMessage(pMsg); + SelectionReady(); + return TRUE; + default: + break; + } + } + return CInitDialogBar::PreTranslateMessage(pMsg); +} + diff --git a/applications/deprecated/old_arch/osmo4_w32/AddressBar.h b/applications/deprecated/old_arch/osmo4_w32/AddressBar.h new file mode 100644 index 0000000..7509d36 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/AddressBar.h @@ -0,0 +1,90 @@ +#if !defined(AFX_ADDRESSBAR_H__B0764C99_5CC2_4412_8B1F_22E71BAD70F0__INCLUDED_) +#define AFX_ADDRESSBAR_H__B0764C99_5CC2_4412_8B1F_22E71BAD70F0__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// AddressBar.h : header file +// + + +///////////////////////////////////////////////////////////////////////////// +// AddressBar dialog + +class CInitDialogBar : public CDialogBar +{ + DECLARE_DYNAMIC(CInitDialogBar) + +// Construction +public: + CInitDialogBar(); + virtual ~CInitDialogBar(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CInitDialogBar) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +public: + virtual BOOL Create(CWnd * pParentWnd, UINT nIDTemplate, UINT nStyle, UINT nID); + virtual BOOL Create(CWnd * pParentWnd, LPCTSTR lpszTemplateName, UINT nStyle, UINT nID); + +protected: + virtual BOOL OnInitDialog(); + +protected: + + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// +// AddressBar dialog + +class AddressBar : public CInitDialogBar +{ + + DECLARE_DYNAMIC(AddressBar) + + // Construction +public: + AddressBar(); + +// Dialog Data + //{{AFX_DATA(AddressBar) + enum { IDD = IDD_NAVBAR }; + CStatic m_Title; + CComboBox m_Address; + //}}AFX_DATA + + void ReloadURLs(); + void SelectionReady(); + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(AddressBar) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL PreTranslateMessage(MSG* pMsg); + //virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(AddressBar) + virtual BOOL OnInitDialog(); + afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg void OnClose(); + afx_msg void OnSelendOK(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_ADDRESSBAR_H__B0764C99_5CC2_4412_8B1F_22E71BAD70F0__INCLUDED_) diff --git a/applications/deprecated/old_arch/osmo4_w32/FileProps.cpp b/applications/deprecated/old_arch/osmo4_w32/FileProps.cpp new file mode 100644 index 0000000..dc23abb --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/FileProps.cpp @@ -0,0 +1,622 @@ +// FileProps.cpp : implementation file +// + +#include "stdafx.h" +#include "osmo4.h" +#include "FileProps.h" +#include "MainFrm.h" + +/*ISO 639 languages*/ +#include + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CFileProps dialog + + +CFileProps::CFileProps(CWnd* pParent /*=NULL*/) + : CDialog(CFileProps::IDD, pParent) +{ + //{{AFX_DATA_INIT(CFileProps) + //}}AFX_DATA_INIT +} + + +void CFileProps::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CFileProps) + DDX_Control(pDX, IDC_VIEWSEL, m_ViewSel); + DDX_Control(pDX, IDC_ODINFO, m_ODInfo); + DDX_Control(pDX, IDC_ODTREE, m_ODTree); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CFileProps, CDialog) + //{{AFX_MSG_MAP(CFileProps) + ON_NOTIFY(TVN_SELCHANGED, IDC_ODTREE, OnSelchangedOdtree) + ON_BN_CLICKED(IDC_WORLD, OnWorld) + ON_BN_CLICKED(IDC_VIEWSG, OnViewsg) + ON_WM_TIMER() + ON_WM_CLOSE() + ON_WM_DESTROY() + ON_NOTIFY(TCN_SELCHANGE, IDC_VIEWSEL, OnSelchangeViewsel) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CFileProps message handlers + + +#define FP_TIMER_ID 20 + +BOOL CFileProps::OnInitDialog() +{ + CDialog::OnInitDialog(); + + char sText[5000]; + sprintf(sText, "%s Properties", ((CMainFrame*)GetApp()->m_pMainWnd)->m_pPlayList->GetDisplayName()); + + SetWindowText(sText); + current_odm = NULL; + + m_ViewSel.InsertItem(0, "General"); + m_ViewSel.InsertItem(1, "Streams"); + m_ViewSel.InsertItem(2, "Playback"); + m_ViewSel.InsertItem(3, "Network"); + + m_ODTree.SetIndent(0); + RewriteODTree(); + SetTimer(FP_TIMER_ID, 500, NULL); + + return TRUE; +} + + +void CFileProps::WriteInlineTree(GF_ObjectManager *root_od, HTREEITEM parent) +{ + Osmo4 *gpac = GetApp(); + + /*browse all ODs*/ + u32 count = gf_term_get_object_count(gpac->m_term, root_od); + + for (u32 i=0; im_term, root_od, i); + if (!odm) return; + HTREEITEM item = m_ODTree.InsertItem("Object Descriptor", 0, 0, parent); + m_ODTree.SetItemData(item, (DWORD) odm); + /*if inline propagate*/ + switch (gf_term_object_subscene_type(gpac->m_term, odm)) { + case 1: + m_ODTree.SetItemText(item, "Root Scene"); + WriteInlineTree(odm, item); + break; + case 2: + m_ODTree.SetItemText(item, "Inline Scene"); + WriteInlineTree(odm, item); + break; + case 3: + m_ODTree.SetItemText(item, "Extern Proto Lib"); + WriteInlineTree(odm, item); + break; + default: + break; + } + } +} + +void CFileProps::RewriteODTree() +{ + Osmo4 *gpac = GetApp(); + + m_ODTree.DeleteAllItems(); + + GF_ObjectManager *root_odm = gf_term_get_root_object(gpac->m_term); + if (!root_odm) return; + + HTREEITEM root = m_ODTree.InsertItem("Root OD", 0, 0); + m_ODTree.SetItemData(root, (DWORD) root_odm); + + m_ODTree.SetItemText(root, "Root Scene"); + WriteInlineTree(root_odm, root); +} + +void CFileProps::OnSelchangedOdtree(NMHDR* pNMHDR, LRESULT* pResult) +{ + NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR; + *pResult = 0; + + HTREEITEM item = m_ODTree.GetSelectedItem(); + GF_ObjectManager *odm = (GF_ObjectManager *) m_ODTree.GetItemData(item); + if (!odm) return; + + SetInfo(odm); +} + + +void CFileProps::OnClose() +{ + KillTimer(FP_TIMER_ID); + DestroyWindow(); +} + +void CFileProps::OnDestroy() +{ + CDialog::OnDestroy(); + delete this; + ((CMainFrame *)GetApp()->m_pMainWnd)->m_pProps = NULL; +} + +void CFileProps::OnSelchangeViewsel(NMHDR* pNMHDR, LRESULT* pResult) +{ + SetInfo(current_odm); + *pResult = 0; +} + +void CFileProps::SetInfo(GF_ObjectManager *odm) +{ + current_odm = odm; + switch (m_ViewSel.GetCurSel()) { + case 3: + SetNetworkInfo(); + break; + case 2: + SetDecoderInfo(); + break; + case 1: + SetStreamsInfo(); + break; + default: + SetGeneralInfo(); + break; + } +} + +void CFileProps::OnTimer(UINT_PTR nIDEvent) +{ + if (nIDEvent == FP_TIMER_ID) { + switch (m_ViewSel.GetCurSel()) { + case 3: + SetNetworkInfo(); + break; + case 2: + SetDecoderInfo(); + break; + } + } + + CDialog::OnTimer(nIDEvent); +} + +void CFileProps::SetGeneralInfo() +{ + char info[10000]; + char buf[1000]; + GF_MediaInfo odi; + GF_ObjectManager *odm; + u32 h, m, s, i, j; + + Osmo4 *gpac = GetApp(); + odm = current_odm; + + strcpy(info, ""); + if (!odm || gf_term_get_object_info(gpac->m_term, odm, &odi) != GF_OK) return; + + if (!odi.od) { + strcat(info, odi.service_url); + m_ODInfo.SetWindowText(info); + return; + } + sprintf(buf, "%sObject Descriptor ID %d\r\n", (odi.has_profiles) ? "Initial " : "", odi.od->objectDescriptorID); + strcat(info, buf); + if (odi.duration) { + h = (u32) (odi.duration / 3600); + m = (u32) (odi.duration / 60) - h*60; + s = (u32) (odi.duration) - h*3600 - m*60; + sprintf(buf, "Duration %02d:%02d:%02d\r\n", h, m, s); + strcat(info, buf); + } else { + strcat(info, "Unknown duration\r\n"); + } + if (odi.owns_service) { + strcat(info, "Service Handler: "); + strcat(info, odi.service_handler); + strcat(info, "\r\n"); + strcat(info, "Service URL: "); + strcat(info, odi.service_url); + strcat(info, "\r\n"); + } + + if (odi.od->URLString) { + strcat(info, "Remote OD - URL: "); + strcat(info, odi.od->URLString); + strcat(info, "\r\n"); + } + /*get OD content info*/ + if (odi.codec_name) { + switch (odi.od_type) { + case GF_STREAM_VISUAL: + sprintf(buf, "Video Object: Width %d - Height %d\r\n", odi.width, odi.height); + strcat(info, buf); + strcat(info, "Media Codec "); + strcat(info, odi.codec_name); + strcat(info, "\r\n"); + if (odi.par) { + sprintf(buf, "Pixel Aspect Ratio: %d:%d\r\n", (odi.par>>16)&0xFF, (odi.par)&0xFF); + strcat(info, buf); + } + break; + case GF_STREAM_AUDIO: + sprintf(buf, "Audio Object: Sample Rate %d - %d channels\r\n", odi.sample_rate, odi.num_channels); + strcat(info, buf); + strcat(info, "Media Codec "); + strcat(info, odi.codec_name); + strcat(info, "\r\n"); + break; + case GF_STREAM_PRIVATE_SCENE: + case GF_STREAM_SCENE: + if (odi.width && odi.height) { + sprintf(buf, "Scene Description: Width %d - Height %d\r\n", odi.width, odi.height); + } else { + sprintf(buf, "Scene Description: No size specified\r\n"); + } + strcat(info, buf); + strcat(info, "Scene Codec "); + strcat(info, odi.codec_name); + strcat(info, "\r\n"); + break; + case GF_STREAM_TEXT: + if (odi.width && odi.height) { + sprintf(buf, "Text Object: Width %d - Height %d\r\n", odi.width, odi.height); + } else { + sprintf(buf, "Text Object: No size specified\r\n"); + } + strcat(info, buf); + strcat(info, "Text Codec "); + strcat(info, odi.codec_name); + strcat(info, "\r\n"); + break; + } + } + if (odi.protection) { + strcat(info, "Encrypted Media"); + if (odi.protection==2) strcat(info, " NOT UNLOCKED"); + strcat(info, "\r\n"); + } + + if (!gf_list_count(odi.od->OCIDescriptors)) { + m_ODInfo.SetWindowText(info); + return; + } + + strcat(info, "\r\nObject Content Information:\r\n"); + + /*check OCI (not everything interests us) - FIXME: support for unicode*/ + for (i=0; iOCIDescriptors); i++) { + GF_Descriptor *desc = (GF_Descriptor *) gf_list_get(odi.od->OCIDescriptors, i); + switch (desc->tag) { + case GF_ODF_SEGMENT_TAG: + { + GF_Segment *sd = (GF_Segment *) desc; + strcat(info, "\r\nSegment Descriptor:\r\n"); + sprintf(buf, "Name: %s - start time %g sec - duration %g sec\r\n", sd->SegmentName, sd->startTime, sd->Duration); + strcat(info, buf); + } + break; + case GF_ODF_CC_NAME_TAG: + { + GF_CC_Name *ccn = (GF_CC_Name *)desc; + strcat(info, "\r\nContent Creators:\r\n"); + for (j=0; jContentCreators); j++) { + GF_ContentCreatorInfo *ci = (GF_ContentCreatorInfo *) gf_list_get(ccn->ContentCreators, j); + if (!ci->isUTF8) continue; + strcat(info, "\t"); + strcat(info, ci->contentCreatorName); + strcat(info, "\r\n"); + } + } + break; + + case GF_ODF_SHORT_TEXT_TAG: + { + GF_ShortTextual *std = (GF_ShortTextual *)desc; + strcat(info, "\r\n"); + strcat(info, std->eventName); + strcat(info, ": "); + strcat(info, std->eventText); + strcat(info, "\r\n"); + } + break; + /*todo*/ + case GF_ODF_CC_DATE_TAG: + break; + default: + break; + } + + } + + m_ODInfo.SetWindowText(info); +} + +void CFileProps::OnWorld() +{ + CString wit; + const char *str; + GF_List *descs; + Osmo4 *gpac = GetApp(); + + descs = gf_list_new(); + str = gf_term_get_world_info(gpac->m_term, current_odm, descs); + if (!str) { + MessageBox("No World Info available", "Sorry!"); + return; + } + + wit = ""; + for (u32 i=0; iszUserPath); + if ( szOutRadname[strlen(szOutRadname)-1] != '\\' + && szOutRadname[strlen(szOutRadname)-1] != '/') + strcat(szOutRadname, "\\"); + strcat(szOutRadname, "scene_dump"); + + GF_Err e = gf_term_dump_scene(gpac->m_term, (char *) szOutRadname, &pFilename, gpac->m_ViewXMTA, GF_FALSE, current_odm); + + if (e) { + MessageBox(gf_error_to_string(e), "Error while dumping"); + } else { + ShellExecute(NULL, "open", pFilename, NULL, NULL, SW_SHOWNORMAL); + } + if (pFilename) gf_free(pFilename); +} + +void CFileProps::SetDecoderInfo() +{ + GF_MediaInfo odi; + char buf[1000], info[2000]; + u32 h, m, s; + Osmo4 *gpac = GetApp(); + + sprintf(info, ""); + m_ODInfo.SetWindowText(""); + + if (!current_odm || gf_term_get_object_info(gpac->m_term, current_odm, &odi)) return; + if (!odi.od) return; + + strcat(info, "Status: "); + switch (odi.status) { + case 0: + case 1: + case 2: + h = (u32) (odi.current_time / 3600); + m = (u32) (odi.current_time / 60) - h*60; + s = (u32) (odi.current_time) - h*3600 - m*60; + sprintf(buf, "%s\r\nObject Time: %02d:%02d:%02d\r\n", (odi.status==0) ? "Stopped" : (odi.status==1) ? "Playing" : "Paused", h, m, s); + strcat(info, buf); + break; + case 3: + strcat(info, "Not Setup"); + m_ODInfo.SetWindowText(info); + return; + default: + strcat(info, "Setup Failed"); + m_ODInfo.SetWindowText(info); + return; + } + /*get clock drift*/ + sprintf(buf, "Clock drift: %d ms\r\n", odi.clock_drift); + strcat(info, buf); + /*get buffering*/ + if (odi.buffer>=0) { + sprintf(buf, "Buffering Time: %d ms\r\n", odi.buffer); + strcat(info, buf); + } else if (odi.buffer==-1) { + strcat(info, "Not buffering\r\n"); + } else { + strcat(info, "Not Playing\r\n"); + } + /*get DB occupation*/ + if (odi.buffer>=0) { + sprintf(buf, "Decoding Buffer: %d Access Units\r\n", odi.db_unit_count); + strcat(info, buf); + } + /*get CB occupation*/ + if (odi.cb_max_count) { + sprintf(buf, "Composition Memory: %d/%d Units\r\n", odi.cb_unit_count, odi.cb_max_count); + strcat(info, buf); + } + + Float avg_dec_time = 0; + if (odi.nb_dec_frames) { + avg_dec_time = (Float) odi.total_dec_time; + avg_dec_time /= odi.nb_dec_frames; + } + sprintf(buf, "Bitrate over last second: %d kbps\r\nMax bitrate over one second: %d kbps\r\nAverage Decoding Time %.2f ms (%d max)\r\nTotal decoded frames %d - %d dropped\r\n", + (u32) odi.avg_bitrate/1024, odi.max_bitrate/1024, avg_dec_time, odi.max_dec_time, odi.nb_dec_frames, odi.nb_dropped); + strcat(info, buf); + + m_ODInfo.SetWindowText(info); +} + + +void CFileProps::SetStreamsInfo() +{ + u32 i, count; + char info[10000]; + char buf[1000]; + GF_MediaInfo odi; + GF_ObjectManager *odm; + Bool is_media; + + Osmo4 *gpac = GetApp(); + odm = current_odm; + + strcpy(info, ""); + m_ODInfo.SetWindowText(""); + + if (!odm || gf_term_get_object_info(gpac->m_term, odm, &odi) != GF_OK) return; + if (!odi.od) return; + + + if (odi.has_profiles) { + strcat(info, "MPEG-4 Profiles and Levels:\r\n"); + sprintf(buf, "\tOD Profile@Level %d\r\n", odi.OD_pl); + strcat(info, buf); + sprintf(buf, "\tScene Profile@Level %d\r\n", odi.scene_pl); + strcat(info, buf); + sprintf(buf, "\tGraphics Profile@Level %d\r\n", odi.graphics_pl); + strcat(info, buf); + sprintf(buf, "\tAudio Profile@Level %d\r\n", odi.audio_pl); + strcat(info, buf); + sprintf(buf, "\tVisual Profile@Level %d\r\n", odi.visual_pl); + strcat(info, buf); + sprintf(buf, "\tInline Content Profiled %s\r\n", odi.inline_pl ? "yes" : "no"); + strcat(info, buf); + strcat(info, "\r\n"); + } + is_media = GF_FALSE; + count = gf_list_count(odi.od->ESDescriptors); + + for (i=0; iESDescriptors, i); + + sprintf(buf, "\t** Stream ID %d - Clock ID %d **\r\n", esd->ESID, esd->OCRESID); + strcat(info, buf); + if (esd->dependsOnESID) { + sprintf(buf, "Depends on Stream ID %d for decoding\r\n", esd->dependsOnESID); + strcat(info, buf); + } + sprintf(buf, "%s\r\n", gf_esd_get_textual_description(esd)); + strcat(info, buf); + + sprintf(buf, "Buffer Size %d\r\nAverage Bitrate %d bps\r\nMaximum Bitrate %d bps\r\n", esd->decoderConfig->bufferSizeDB, esd->decoderConfig->avgBitrate, esd->decoderConfig->maxBitrate); + strcat(info, buf); + if (esd->slConfig->predefined==SLPredef_SkipSL) { + sprintf(buf, "Not using MPEG-4 Synchronization Layer\r\n"); + } else { + sprintf(buf, "Stream Clock Resolution %d\r\n", esd->slConfig->timestampResolution); + } + strcat(info, buf); + if (esd->URLString) { + sprintf(buf, "Stream Location: %s\r\n", esd->URLString); + strcat(info, buf); + } + + /*check language*/ + if (esd->langDesc) { + s32 lang_idx; + char lan[4]; + lan[0] = esd->langDesc->langCode>>16; + lan[1] = (esd->langDesc->langCode>>8)&0xFF; + lan[2] = (esd->langDesc->langCode)&0xFF; + lan[3] = 0; + + lang_idx = gf_lang_find(lan); + if (lang_idx>=0) { + sprintf(buf, "Stream Language: %s\r\n", gf_lang_get_name(lang_idx) ); + strcat(info, buf); + } + } + strcat(info, "\r\n"); + } + + m_ODInfo.SetWindowText(info); +} + +void CFileProps::SetNetworkInfo() +{ + char info[10000]; + char buf[10000]; + u32 id; + NetStatCommand com; + GF_MediaInfo odi; + u32 d_enum, nb_streams; + GF_Err e; + GF_ObjectManager *odm; + Osmo4 *gpac = GetApp(); + odm = current_odm; + + strcpy(info, ""); + m_ODInfo.SetWindowText(""); + + if (!odm || gf_term_get_object_info(gpac->m_term, odm, &odi) != GF_OK) return; + if (!odi.od) return; + + if (odi.owns_service) { + const char *url, *path; + u32 done, total, bps; + strcpy(info, "Current Downloads in service:\r\n"); + d_enum = 0; + while (gf_term_get_download_info(gpac->m_term, odm, &d_enum, &url, &path, &done, &total, &bps)) { + if (total && done) { + sprintf(buf, "%s %s: %d / %d bytes (%.2f %%) - %.2f kBps\r\n", url, path, done, total, (100.0f*done)/total, ((Float)bps)/1024); + } else { + sprintf(buf, "%s %s: %.2f kbps\r\n", url, path, ((Float)bps*8)/1024); + } + strcat(info, buf); + } + if (!d_enum) strcpy(info, "No Downloads in service\r\n"); + strcat(info, "\r\n"); + } + + d_enum = 0; + nb_streams = 0; + while (gf_term_get_channel_net_info(gpac->m_term, odm, &d_enum, &id, &com, &e)) { + if (e) continue; + if (!com.bw_down && !com.bw_up) continue; + nb_streams ++; + + sprintf(buf, "Stream ID %d statistics:\r\n", id); + strcat(info, buf); + if (com.multiplex_port) { + sprintf(buf, "\tMultiplex Port %d - multiplex ID %d\r\n", com.multiplex_port, com.port); + } else { + sprintf(buf, "\tPort %d\r\n", com.port); + } + strcat(info, buf); + sprintf(buf, "\tPacket Loss Percentage: %.4f\r\n", com.pck_loss_percentage); + strcat(info, buf); + sprintf(buf, "\tDown Bandwidth: %.3f kbps\r\n", ((Float)com.bw_down) / 1024); + strcat(info, buf); + if (com.bw_up) { + sprintf(buf, "\tUp Bandwidth: %d bps\r\n", com.bw_up); + strcat(info, buf); + } + if (com.ctrl_port) { + if (com.multiplex_port) { + sprintf(buf, "\tControl Multiplex Port: %d - Control Multiplex ID %d\r\n", com.multiplex_port, com.ctrl_port); + } else { + sprintf(buf, "\tControl Port: %d\r\n", com.ctrl_port); + } + strcat(info, buf); + sprintf(buf, "\tControl Down Bandwidth: %d bps\r\n", com.ctrl_bw_down); + strcat(info, buf); + sprintf(buf, "\tControl Up Bandwidth: %d bps\r\n", com.ctrl_bw_up); + strcat(info, buf); + } + strcat(info, "\r\n"); + } + if (!nb_streams) strcat(info, "No network streams in this object\r\n"); + + m_ODInfo.SetWindowText(info); +} diff --git a/applications/deprecated/old_arch/osmo4_w32/FileProps.h b/applications/deprecated/old_arch/osmo4_w32/FileProps.h new file mode 100644 index 0000000..4d8c5c8 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/FileProps.h @@ -0,0 +1,70 @@ +#if !defined(AFX_FILEPROPS_H__CA4484B4_0301_4BE1_8736_551250121C3F__INCLUDED_) +#define AFX_FILEPROPS_H__CA4484B4_0301_4BE1_8736_551250121C3F__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// FileProps.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CFileProps dialog + +class CFileProps : public CDialog +{ +// Construction +public: + CFileProps(CWnd* pParent = NULL); // standard constructor + BOOL Create(CWnd * pParent) + { + return CDialog::Create( CFileProps::IDD, pParent); + } + +// Dialog Data + //{{AFX_DATA(CFileProps) + enum { IDD = IDD_PROPERTIES }; + CTabCtrl m_ViewSel; + CEdit m_ODInfo; + CTreeCtrl m_ODTree; + //}}AFX_DATA + + + void RewriteODTree(); + void WriteInlineTree(GF_ObjectManager *root_od, HTREEITEM parent); + void SetInfo(GF_ObjectManager *odm); + +private: + GF_ObjectManager *current_odm; + void SetGeneralInfo(); + void SetStreamsInfo(); + void SetDecoderInfo(); + void SetNetworkInfo(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CFileProps) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CFileProps) + virtual BOOL OnInitDialog(); + afx_msg void OnSelchangedOdtree(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnWorld(); + afx_msg void OnViewsg(); + afx_msg void OnTimer(UINT_PTR nIDEvent); + afx_msg void OnClose(); + afx_msg void OnDestroy(); + afx_msg void OnSelchangeViewsel(NMHDR* pNMHDR, LRESULT* pResult); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_FILEPROPS_H__CA4484B4_0301_4BE1_8736_551250121C3F__INCLUDED_) diff --git a/applications/deprecated/old_arch/osmo4_w32/MainFrm.cpp b/applications/deprecated/old_arch/osmo4_w32/MainFrm.cpp new file mode 100644 index 0000000..07bff79 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/MainFrm.cpp @@ -0,0 +1,1588 @@ +// MainFrm.cpp : implementation of the CMainFrame class +// + +#include "stdafx.h" +#include "Osmo4.h" + +#include "MainFrm.h" +#include "resource.h" + +#include + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CChildView + +CChildView::CChildView() +{ +} + +CChildView::~CChildView() +{ + /*since the wndproc is overwritten by the terminal, we detach the handle otherwise we get a nice assertion + failure from windows*/ + HWND hWnd = Detach(); + ::PostMessage(hWnd, WM_QUIT, 0, 0); +} + + +BEGIN_MESSAGE_MAP(CChildView,CWnd ) + //{{AFX_MSG_MAP(CChildView) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + + +///////////////////////////////////////////////////////////////////////////// +// CChildView message handlers + +BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs) +{ + cs.dwExStyle = 0; + cs.style &= ~WS_BORDER; + + cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS, + ::LoadCursor(NULL, IDC_ARROW), HBRUSH(COLOR_WINDOW+1), NULL); + + return TRUE; +} + + + +///////////////////////////////////////////////////////////////////////////// +// CMainFrame + +IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd) + +BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) + //{{AFX_MSG_MAP(CMainFrame) + ON_WM_CREATE() + ON_WM_SETFOCUS() + ON_WM_INITMENUPOPUP() + ON_WM_SIZE() + ON_WM_MOVE() + ON_MESSAGE(WM_SETSIZE,OnSetSize) + ON_MESSAGE(WM_NAVIGATE,OnNavigate) + ON_MESSAGE(WM_OPENURL, Open) + ON_MESSAGE(WM_NEWINSTANCE, NewInstanceOpened) + + ON_WM_LBUTTONDOWN() + ON_WM_LBUTTONDBLCLK() + ON_WM_LBUTTONUP() + ON_WM_CHAR() + ON_WM_SYSKEYDOWN() + ON_WM_SYSKEYUP() + ON_WM_KEYDOWN() + ON_WM_KEYUP() + ON_WM_DROPFILES() + ON_MESSAGE(WM_CONSOLEMSG, OnConsoleMessage) + ON_COMMAND(ID_VIEW_ORIGINAL, OnViewOriginal) + ON_COMMAND(ID_VIEW_FULLSCREEN, OnViewFullscreen) + ON_COMMAND(ID_AR_KEEP, OnArKeep) + ON_COMMAND(ID_AR_FILL, OnArFill) + ON_COMMAND(ID_AR_43, OnAr43) + ON_COMMAND(ID_AR_169, OnAr169) + ON_UPDATE_COMMAND_UI(ID_AR_169, OnUpdateAr169) + ON_UPDATE_COMMAND_UI(ID_AR_43, OnUpdateAr43) + ON_UPDATE_COMMAND_UI(ID_AR_FILL, OnUpdateArFill) + ON_UPDATE_COMMAND_UI(ID_AR_KEEP, OnUpdateArKeep) + ON_COMMAND(ID_NAVIGATE_NONE, OnNavigateNone) + ON_COMMAND(ID_NAVIGATE_WALK, OnNavigateWalk) + ON_COMMAND(ID_NAVIGATE_FLY, OnNavigateFly) + ON_COMMAND(ID_NAVIGATE_EXAM, OnNavigateExam) + ON_COMMAND(ID_NAVIGATE_SLIDE, OnNavigateSlide) + ON_COMMAND(ID_NAVIGATE_PAN, OnNavigatePan) + ON_COMMAND(ID_NAVIGATE_ORBIT, OnNavigateOrbit) + ON_COMMAND(ID_NAVIGATE_GAME, OnNavigateGame) + ON_COMMAND(ID_NAVIGATE_VR, OnNavigateVR) + ON_COMMAND(ID_NAV_RESET, OnNavigateReset) + ON_COMMAND(ID_SHORTCUTS, OnShortcuts) + ON_COMMAND(IDD_CONFIGURE, OnConfigure) + ON_COMMAND(ID_FILE_PROP, OnFileProp) + ON_COMMAND(ID_VIEW_PL, OnViewPlaylist) + ON_UPDATE_COMMAND_UI(ID_FILE_PROP, OnUpdateFileProp) + ON_UPDATE_COMMAND_UI(ID_NAVIGATE_NONE, OnUpdateNavigate) + ON_COMMAND(ID_REC_ENABLE, OnCacheEnable) + ON_UPDATE_COMMAND_UI(ID_REC_ENABLE, OnUpdateCacheEnable) + ON_COMMAND(ID_REC_STOP, OnCacheStop) + ON_COMMAND(ID_REC_ABORT, OnCacheAbort) + ON_UPDATE_COMMAND_UI(ID_REC_STOP, OnUpdateCacheStop) + ON_COMMAND(ID_COLLIDE_DISP, OnCollideDisp) + ON_UPDATE_COMMAND_UI(ID_COLLIDE_DISP, OnUpdateCollideDisp) + ON_COMMAND(ID_COLLIDE_NONE, OnCollideNone) + ON_UPDATE_COMMAND_UI(ID_COLLIDE_NONE, OnUpdateCollideNone) + ON_COMMAND(ID_COLLIDE_REG, OnCollideReg) + ON_UPDATE_COMMAND_UI(ID_COLLIDE_REG, OnUpdateCollideReg) + ON_COMMAND(ID_HEADLIGHT, OnHeadlight) + ON_UPDATE_COMMAND_UI(ID_HEADLIGHT, OnUpdateHeadlight) + ON_COMMAND(ID_GRAVITY, OnGravity) + ON_UPDATE_COMMAND_UI(ID_GRAVITY, OnUpdateGravity) + ON_COMMAND(ID_NAV_INFO, OnNavInfo) + ON_COMMAND(ID_NAV_NEXT, OnNavNext) + ON_COMMAND(ID_NAV_PREV, OnNavPrev) + ON_UPDATE_COMMAND_UI(ID_NAV_NEXT, OnUpdateNavNext) + ON_UPDATE_COMMAND_UI(ID_NAV_PREV, OnUpdateNavPrev) + ON_COMMAND(ID_CLEAR_NAV, OnClearNav) + ON_UPDATE_COMMAND_UI(ID_VIEW_PL, OnUpdateViewPlaylist) + ON_COMMAND(ID_PLAYLIST_LOOP, OnPlaylistLoop) + ON_UPDATE_COMMAND_UI(ID_PLAYLIST_LOOP, OnUpdatePlaylistLoop) + ON_COMMAND(ID_ADD_SUBTITLE, OnAddSubtitle) + ON_UPDATE_COMMAND_UI(ID_REC_ABORT, OnUpdateCacheStop) + ON_UPDATE_COMMAND_UI(ID_NAVIGATE_WALK, OnUpdateNavigate) + ON_UPDATE_COMMAND_UI(ID_NAVIGATE_FLY, OnUpdateNavigate) + ON_UPDATE_COMMAND_UI(ID_NAVIGATE_EXAM, OnUpdateNavigate) + ON_UPDATE_COMMAND_UI(ID_NAVIGATE_PAN, OnUpdateNavigate) + ON_UPDATE_COMMAND_UI(ID_NAVIGATE_SLIDE, OnUpdateNavigate) + ON_UPDATE_COMMAND_UI(ID_NAVIGATE_ORBIT, OnUpdateNavigate) + ON_UPDATE_COMMAND_UI(ID_NAVIGATE_VR, OnUpdateNavigate) + ON_UPDATE_COMMAND_UI(ID_NAVIGATE_GAME, OnUpdateNavigate) + ON_COMMAND(ID_FILE_EXIT, OnFileExit) + ON_COMMAND(ID_VIEW_CPU, OnViewCPU) + ON_UPDATE_COMMAND_UI(ID_VIEW_CPU, OnUpdateViewCPU) + + ON_COMMAND(ID_FILE_COPY, OnFileCopy) + ON_UPDATE_COMMAND_UI(ID_FILE_COPY, OnUpdateFileCopy) + ON_COMMAND(ID_FILE_PASTE, OnFilePaste) + ON_UPDATE_COMMAND_UI(ID_FILE_PASTE, OnUpdateFilePaste) + + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + + +///////////////////////////////////////////////////////////////////////////// +// CMainFrame construction/destruction + +CMainFrame::CMainFrame() +{ + m_icoerror = AfxGetApp()->LoadIcon(IDI_ERR); + m_icomessage = AfxGetApp()->LoadIcon(IDI_MESSAGE); + m_bFullScreen = GF_FALSE; + m_RestoreFS = 0; + m_aspect_ratio = GF_ASPECT_RATIO_KEEP; + m_pProps = NULL; + m_pOpt = NULL; + m_pPlayList = NULL; + m_pWndView = new CChildView(); + m_bInitShow = GF_TRUE; + m_bStartupFile = GF_TRUE; + m_num_chapters = 0; + m_chapters_start = NULL; + m_last_prog = -1; + m_timer_on = 0; + m_show_rti = GF_FALSE; + nb_viewpoints = 0; +} + +CMainFrame::~CMainFrame() +{ + if (m_chapters_start) gf_free(m_chapters_start); + if (m_pProps != NULL) m_pProps->DestroyWindow(); + if (m_pOpt != NULL) m_pOpt->DestroyWindow(); + if (m_pPlayList != NULL) delete m_pPlayList; + delete m_pWndView; +} + +#define RTI_TIMER 22 +#define RTI_REFRESH_MS 250 + +void CALLBACK EXPORT RTInfoTimer(HWND , UINT , UINT_PTR nID , DWORD ) +{ + char szMsg[100]; + GF_SystemRTInfo rti; + if (nID != RTI_TIMER) return; + Osmo4 *app = GetApp(); + CMainFrame *pFrame = (CMainFrame *) app->m_pMainWnd; + /*shutdown*/ + if (!pFrame) return; + + if (pFrame->m_show_rti && !pFrame->m_timer_on) { + if (!gf_sys_get_rti(RTI_REFRESH_MS, &rti, 0)) return; + if (!rti.gpac_memory) rti.gpac_memory = rti.process_memory ? rti.process_memory : rti.physical_memory; + + if (pFrame->m_show_rti && !pFrame->m_timer_on) { + sprintf(szMsg, "FPS %02.2f - CPU %02d (%02d) - Mem %d kB", + gf_term_get_framerate(app->m_term, GF_FALSE), rti.total_cpu_usage, rti.process_cpu_usage, rti.gpac_memory/1024); + pFrame->m_wndStatusBar.SetPaneText(1, szMsg); + } + } + + if (! gf_term_get_option(app->m_term, GF_OPT_IS_FINISHED)) { + u32 ms = gf_term_get_time_in_ms(app->m_term); + u32 h = ms / 1000 / 3600; + u32 m = ms / 1000 / 60 - h*60; + u32 s = ms / 1000 - h*3600 - m*60; + + sprintf(szMsg, "%02d:%02d.%02d", h, m, s); + pFrame->m_wndStatusBar.SetPaneText(0, szMsg); + } +} + +static UINT status_indics[] = +{ + ID_TIMER, + ID_SEPARATOR, // status line indicator +}; + + + +int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) +{ + UINT buttonArray[50]; + TBBUTTONINFO bi; + u32 *ba; + if (CFrameWnd::OnCreate(lpCreateStruct) == -1) + return -1; + + // create a view to occupy the client area of the frame + if (!m_pWndView->CreateEx(0, NULL, NULL, WS_CHILD, 0, 0, 300, 200, m_hWnd, NULL, NULL)) + { + TRACE0("Failed to create view window\n"); + return -1; + } + m_pPlayList = new Playlist(); + m_pPlayList->Create(); + m_pPlayList->ShowWindow(SW_HIDE); + + + if (!m_wndToolBar.CreateEx(this, WS_CHILD | CBRS_TOP | CBRS_FLYBY) || + !m_wndToolBar.LoadBitmap(IDR_MAINTOOLS)) + { + TRACE0("Failed to create toolbar\n"); + return -1; // fail to create + } + + ba = &buttonArray[0]; + *ba = ID_OPEN_FILE; + ba++; + *ba = ID_SEPARATOR; + ba++; + *ba = ID_NAV_PREV; + ba++; + *ba = ID_NAV_NEXT; + ba++; + *ba = ID_SEPARATOR; + ba++; + *ba = ID_FILE_PLAY; + ba++; + *ba = ID_FILE_STEP; + ba++; + *ba = ID_FILE_STOP; + ba++; + *ba = ID_SEPARATOR; + ba++; + *ba = ID_FILE_PROP; + ba++; + *ba = ID_SEPARATOR; + ba++; + *ba = ID_FILE_PROP; + ba++; + *ba = ID_SWITCH_RENDER; + m_wndToolBar.SetButtons(buttonArray, 13); + m_wndToolBar.SetButtonInfo(0, ID_OPEN_FILE, TBBS_BUTTON, 0); + m_wndToolBar.SetButtonInfo(1, ID_SEPARATOR, TBBS_SEPARATOR, 0); + m_wndToolBar.SetButtonInfo(2, ID_NAV_PREV, TBBS_DROPDOWN, 1); + m_wndToolBar.SetButtonInfo(3, ID_NAV_NEXT, TBBS_DROPDOWN, 2); + m_wndToolBar.SetButtonInfo(4, ID_SEPARATOR, TBBS_SEPARATOR, 0); + m_wndToolBar.SetButtonInfo(5, ID_FILE_PLAY, TBBS_BUTTON, 3); + m_wndToolBar.SetButtonInfo(6, ID_FILE_STEP, TBBS_BUTTON, 5); + m_wndToolBar.SetButtonInfo(7, ID_FILE_STOP, TBBS_BUTTON, 6); + m_wndToolBar.SetButtonInfo(8, ID_SEPARATOR, TBBS_SEPARATOR, 0); + m_wndToolBar.SetButtonInfo(9, ID_FILE_PROP, TBBS_BUTTON, 7); + m_wndToolBar.SetButtonInfo(10, ID_SEPARATOR, TBBS_SEPARATOR, 0); + m_wndToolBar.SetButtonInfo(11, IDD_CONFIGURE, TBBS_BUTTON, 8); + m_wndToolBar.SetButtonInfo(12, ID_SWITCH_RENDER, TBBS_BUTTON, 9); + + CToolBarCtrl &ctrl = m_wndToolBar.GetToolBarCtrl(); + ctrl.SetStyle(TBSTYLE_FLAT | TBSTYLE_DROPDOWN); + ctrl.SetExtendedStyle(TBSTYLE_EX_DRAWDDARROWS); + + memset(&bi, 0, sizeof(bi)); + bi.cbSize = sizeof(bi); + ctrl.GetButtonInfo(2, &bi); + bi.fsStyle |= TBSTYLE_DROPDOWN; + ctrl.SetButtonInfo(ID_NAV_PREV, &bi); + + memset(&bi, 0, sizeof(bi)); + bi.cbSize = sizeof(bi); + ctrl.GetButtonInfo(3, &bi); + bi.fsStyle |= TBSTYLE_DROPDOWN; + ctrl.SetButtonInfo(ID_NAV_NEXT, &bi); + + if (!m_wndStatusBar.Create(this) || + !m_wndStatusBar.SetIndicators(status_indics, + sizeof(status_indics)/sizeof(UINT))) + { + TRACE0("Failed to create status bar\n"); + return -1; // fail to create + } + + if (!m_Address.Create(this, IDD_NAVBAR, WS_CHILD | CBRS_TOP | CBRS_FLYBY | CBRS_SIZE_DYNAMIC, IDD_NAVBAR) ) { + return -1; // fail to create + } + + if (!m_Sliders.Create(IDD_SLIDERS, this) ) { + return -1; // fail to create + } + + m_wndStatusBar.SetPaneInfo(0, ID_TIMER, SBPS_NORMAL, 60); + m_wndStatusBar.SetPaneInfo(1, ID_SEPARATOR, SBPS_STRETCH, 0); + SetIcon(AfxGetApp()->LoadIcon(IDR_MAINFRAME), TRUE); + SetIcon(AfxGetApp()->LoadIcon(IDR_MAINFRAME), FALSE); + + SetTimer(RTI_TIMER, RTI_REFRESH_MS, RTInfoTimer); + return 0; +} + + +BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) +{ + if( !CFrameWnd::PreCreateWindow(cs) ) + return FALSE; + // TODO: Modify the Window class or styles here by modifying + // the CREATESTRUCT cs + + cs.dwExStyle &= ~WS_EX_CLIENTEDGE; + cs.lpszClass = AfxRegisterWndClass(0); + return TRUE; +} + +///////////////////////////////////////////////////////////////////////////// +// CMainFrame diagnostics + +#ifdef _DEBUG +void CMainFrame::AssertValid() const +{ + CFrameWnd::AssertValid(); +} + +void CMainFrame::Dump(CDumpContext& dc) const +{ + CFrameWnd::Dump(dc); +} + +#endif //_DEBUG + +///////////////////////////////////////////////////////////////////////////// +// CMainFrame message handlers +void CMainFrame::OnSetFocus(CWnd* pOldWnd) +{ + m_pWndView->SetFocus(); + if (m_RestoreFS==1) { + m_RestoreFS=2; + } + else if (m_RestoreFS==2) { + m_RestoreFS = 0; + SetFullscreen(); + } +} + +BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) +{ + // let the view have first crack at the command + if (m_pWndView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) + return TRUE; + + // otherwise, do default handling + return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); +} + + +void CMainFrame::OnSize(UINT nType, int cx, int cy) +{ + RECT rc2; + u32 tool_h, slide_h, add_h, stat_h; + + if (m_bInitShow) { + CFrameWnd::OnSize(nType, cx, cy); + return; + } + m_wndToolBar.GetClientRect(&rc2); + tool_h = rc2.bottom - rc2.top; + m_Address.GetClientRect(&rc2); + add_h = rc2.bottom - rc2.top; + m_Sliders.GetClientRect(&rc2); + slide_h = rc2.bottom - rc2.top; + m_wndStatusBar.GetClientRect(&rc2); + stat_h = rc2.bottom - rc2.top; + if ((u32) cy <= tool_h+add_h+slide_h+stat_h) { + OnSetSize(cx, 1); + return; + } + + CFrameWnd::OnSize(nType, cx, cy); + cy -= tool_h + add_h + slide_h + stat_h; + + m_Address.SetWindowPos(this, 0, 0, cx, add_h, SWP_SHOWWINDOW | SWP_NOMOVE); + + m_pWndView->ShowWindow(SW_SHOW); + m_pWndView->SetWindowPos(this, 0, add_h + tool_h, cx, cy, SWP_NOZORDER); + + m_Sliders.SetWindowPos(this, 0, add_h + tool_h + cy, cx, slide_h, SWP_NOZORDER|SWP_SHOWWINDOW); + /*and resize term*/ + gf_term_set_size(GetApp()->m_term, cx, cy); +} + + +LRESULT CMainFrame::OnSetSize(WPARAM wParam, LPARAM lParam) +{ + UINT width, height; + width = (UINT) wParam; + height = (UINT) lParam; + if (m_bInitShow) { + m_wndToolBar.UpdateWindow(); + m_wndToolBar.ShowWindow(SW_SHOW); + m_Address.UpdateWindow(); + m_Address.ShowWindow(SW_SHOW); + m_Sliders.UpdateWindow(); + m_Sliders.ShowWindow(SW_SHOW); + m_Sliders.m_PosSlider.EnableWindow(FALSE); + m_pWndView->ShowWindow(SW_SHOW); + ShowWindow(SW_SHOW); + m_bInitShow = GF_FALSE; + } + + RECT winRect; + winRect.left = 0; + winRect.right = width; + winRect.top = 0; + winRect.bottom = height; + AdjustWindowRectEx(&winRect, GetStyle(), TRUE, GetExStyle()); + winRect.bottom -= winRect.top; + winRect.right -= winRect.left; + winRect.left = winRect.top = 0; + + RECT rc2; + m_Address.GetClientRect(&rc2); + winRect.bottom += rc2.bottom; + m_wndToolBar.GetClientRect(&rc2); + winRect.bottom += rc2.bottom; + m_Sliders.GetClientRect(&rc2); + winRect.bottom += rc2.bottom; + m_wndStatusBar.GetClientRect(&rc2); + winRect.bottom += rc2.bottom; + + GetWindowRect(&rc2); + rc2.bottom -= rc2.top; + rc2.right -= rc2.left; + if ((rc2.right != winRect.right) || (rc2.bottom != winRect.bottom)) { + SetWindowPos(NULL, 0, 0, winRect.right, winRect.bottom, SWP_NOZORDER | SWP_NOMOVE | SWP_SHOWWINDOW); + } else { + /*just resize term*/ + //gf_term_set_size(GetApp()->m_term, width, height); + SetWindowPos(NULL, 0, 0, winRect.right, winRect.bottom, SWP_NOZORDER | SWP_NOMOVE | SWP_SHOWWINDOW); + } + return 0; +} + +void CMainFrame::OnMove(int x, int y) +{ + CFrameWnd::OnMove(x, y); + RECT rc; + + m_wndToolBar.GetClientRect(&rc); + m_wndToolBar.SetWindowPos(this, x, y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW); + y += rc.bottom - rc.top; + m_Address.SetWindowPos(this, x, y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW); + m_Address.GetClientRect(&rc); + y += rc.bottom - rc.top; + m_pWndView->SetWindowPos(this, x, y, 0, 0, SWP_NOSIZE); + m_pWndView->GetClientRect(&rc); + y += rc.bottom; + m_Sliders.SetWindowPos(this, x, y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW); +} + + +#define PROGRESS_TIMER 20 +#define PROGRESS_REFRESH_MS 100 + +void CALLBACK EXPORT ProgressTimer(HWND , UINT , UINT_PTR nID , DWORD ) +{ + u32 now; + if (nID != PROGRESS_TIMER) return; + Osmo4 *app = GetApp(); + CMainFrame *pFrame = (CMainFrame *) app->m_pMainWnd; + /*shutdown*/ + if (!pFrame) return; + + now = gf_term_get_time_in_ms(app->m_term); + if (!now) return; + + if (app->can_seek && !pFrame->m_Sliders.m_grabbed) { + if (now >= app->max_duration + 100) { + if (gf_term_get_option(app->m_term, GF_OPT_IS_FINISHED)) { + pFrame->m_pPlayList->PlayNext(); + } + /*if no IsOver go on forever*/ + } else { + if (!app->m_reset) + pFrame->m_Sliders.m_PosSlider.SetPos(now); + } + } +} + +void CMainFrame::SetProgTimer(Bool bOn) +{ + if (bOn) + SetTimer(PROGRESS_TIMER, PROGRESS_REFRESH_MS, ProgressTimer); + else + KillTimer(PROGRESS_TIMER); +} + + +LRESULT CMainFrame::Open(WPARAM wParam, LPARAM lParam) +{ + Bool do_pause; + Osmo4 *app = GetApp(); + CString txt, url; + m_bStartupFile = GF_FALSE; + txt = "Osmo4 - "; + txt += m_pPlayList->GetDisplayName(); + + url = m_pPlayList->GetURL(); + m_Address.m_Address.SetWindowText(url); + SetWindowText(txt); + if (app->start_mode==1) do_pause = GF_TRUE; + else if (app->start_mode==2) do_pause = GF_FALSE; + else do_pause = /*!app->m_AutoPlay*/GF_FALSE; + gf_term_connect_from_time(app->m_term, (LPCSTR) url, app->m_reconnect_time, do_pause); + app->m_reconnect_time = 0; + app->start_mode = 0; + app->UpdatePlayButton(); + nb_viewpoints = 0; + return 1; +} + +LRESULT CMainFrame::NewInstanceOpened(WPARAM wParam, LPARAM lParam) +{ + Bool queue_only = GF_FALSE; + char *url = (char *) static_gpac_get_url(); + if (!strnicmp(url, "-queue ", 7)) { + queue_only = GF_TRUE; + url += 7; + } + m_pPlayList->QueueURL(url); + m_pPlayList->RefreshList(); + if (!queue_only) m_pPlayList->PlayNext(); + return 1; +} + + +void CMainFrame::ForwardMessage() +{ + const MSG *msg = GetCurrentMessage(); + m_pWndView->SendMessage(msg->message, msg->wParam, msg->lParam); +} +void CMainFrame::OnSysKeyUp(UINT , UINT , UINT ) { + ForwardMessage(); +} +void CMainFrame::OnSysKeyDown(UINT , UINT , UINT ) { + ForwardMessage(); +} +void CMainFrame::OnChar(UINT , UINT , UINT ) { + ForwardMessage(); +} +void CMainFrame::OnKeyDown(UINT , UINT , UINT ) { + ForwardMessage(); +} +void CMainFrame::OnKeyUp(UINT , UINT , UINT ) { + ForwardMessage(); +} +void CMainFrame::OnLButtonDown(UINT , CPoint ) { + ForwardMessage(); +} +void CMainFrame::OnLButtonDblClk(UINT , CPoint ) { + ForwardMessage(); +} +void CMainFrame::OnLButtonUp(UINT , CPoint ) { + ForwardMessage(); +} + +void CMainFrame::OnDropFiles(HDROP hDropInfo) +{ + u32 i, count; + Osmo4 *app = GetApp(); + char fileName[MAX_PATH]; + + count = ::DragQueryFile(hDropInfo, 0xFFFFFFFF, NULL, 0); + if (!count) return; + + /*if playing and sub d&d, open sub in current presentation*/ + if (app->m_isopen && (count==1)) { + ::DragQueryFile(hDropInfo, 0, fileName, MAX_PATH); + char *ext = strrchr(fileName, '.'); + if (ext && ( !stricmp(ext, ".srt") || !stricmp(ext, ".sub") || !stricmp(ext, ".ttxt") || !stricmp(ext, ".xml") ) ) { + AddSubtitle(fileName, GF_TRUE); + return; + } + } + + /* if (count==1) + m_pPlayList->Truncate(); + else + */ m_pPlayList->Clear(); + + for (i=0; iQueueURL(fileName); + } + m_pPlayList->RefreshList(); + m_pPlayList->PlayNext(); +} + +void CALLBACK EXPORT ConsoleTimer(HWND , UINT , UINT_PTR , DWORD ) +{ + CMainFrame *pFrame = (CMainFrame *) GetApp()->m_pMainWnd; + + pFrame->m_wndStatusBar.GetStatusBarCtrl().SetIcon(2, NULL); + pFrame->KillTimer(pFrame->m_timer_on); + pFrame->m_timer_on = 0; + pFrame->m_wndStatusBar.SetPaneText(1, "Ready"); +} + +#define CONSOLE_DISPLAY_TIME 1000 + +LRESULT CMainFrame::OnConsoleMessage(WPARAM wParam, LPARAM lParam) +{ + if (m_timer_on) KillTimer(m_timer_on); + + if (console_err>=0) { + m_wndStatusBar.GetStatusBarCtrl().SetIcon(2, m_icomessage); + m_wndStatusBar.SetPaneText(1, console_message); + } else { + char msg[5000]; + m_wndStatusBar.GetStatusBarCtrl().SetIcon(2, m_icoerror); + sprintf(msg, "%s (%s)", console_message, console_service); + m_wndStatusBar.SetPaneText(1, msg); + } + m_timer_on = SetTimer(10, wParam ? (UINT) wParam : CONSOLE_DISPLAY_TIME, ConsoleTimer); + return 0; +} + +BOOL CMainFrame::DestroyWindow() +{ + if (GetApp()->m_isopen) KillTimer(PROGRESS_TIMER); + /*signal close to prevent callbacks but don't close, this is done in ExitInstance (otherwise there's a + deadlock happening not sure why yet)*/ +// GetApp()->m_open = 0; + return CFrameWnd::DestroyWindow(); +} + + +void CMainFrame::OnViewOriginal() +{ + Osmo4 *gpac = GetApp(); + gf_term_set_option(gpac->m_term, GF_OPT_ORIGINAL_VIEW, 1); + OnSetSize(gpac->orig_width, gpac->orig_height); +} + +void CMainFrame::SetFullscreen() +{ + Osmo4 *gpac = GetApp(); + if (!m_bFullScreen) { + GetWindowRect(&backup_wnd_rc); + if (gf_term_set_option(gpac->m_term, GF_OPT_FULLSCREEN, 1) == GF_OK) + m_bFullScreen = GF_TRUE; + } else { + if (gf_term_set_option(gpac->m_term, GF_OPT_FULLSCREEN, 0) == GF_OK) + m_bFullScreen = GF_FALSE; + SetWindowPos(NULL, backup_wnd_rc.left, backup_wnd_rc.top, backup_wnd_rc.right-backup_wnd_rc.left, backup_wnd_rc.bottom-backup_wnd_rc.top, SWP_NOZORDER); + } +} + +void CMainFrame::OnViewFullscreen() +{ + SetFullscreen(); +} + +void CMainFrame::OnArKeep() +{ + gf_term_set_option(GetApp()->m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_KEEP); + m_aspect_ratio = GF_ASPECT_RATIO_KEEP; +} + +void CMainFrame::OnArFill() +{ + gf_term_set_option(GetApp()->m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_FILL_SCREEN); + m_aspect_ratio = GF_ASPECT_RATIO_FILL_SCREEN; +} + +void CMainFrame::OnAr43() +{ + gf_term_set_option(GetApp()->m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_4_3); + m_aspect_ratio = GF_ASPECT_RATIO_4_3; +} + +void CMainFrame::OnAr169() +{ + gf_term_set_option(GetApp()->m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_16_9); + m_aspect_ratio = GF_ASPECT_RATIO_16_9; +} + +void CMainFrame::OnUpdateAr169(CCmdUI* pCmdUI) +{ + pCmdUI->SetCheck(m_aspect_ratio == GF_ASPECT_RATIO_16_9); +} + +void CMainFrame::OnUpdateAr43(CCmdUI* pCmdUI) +{ + pCmdUI->SetCheck(m_aspect_ratio == GF_ASPECT_RATIO_4_3); +} + +void CMainFrame::OnUpdateArFill(CCmdUI* pCmdUI) +{ + pCmdUI->SetCheck(m_aspect_ratio == GF_ASPECT_RATIO_FILL_SCREEN); +} + +void CMainFrame::OnUpdateArKeep(CCmdUI* pCmdUI) +{ + pCmdUI->SetCheck(m_aspect_ratio == GF_ASPECT_RATIO_KEEP); +} + +void CMainFrame::OnUpdateNavigate(CCmdUI* pCmdUI) +{ + BOOL enable; + Osmo4 *app = GetApp(); + pCmdUI->Enable(FALSE); + if (!app->m_isopen) return; + + u32 type = gf_term_get_option(app->m_term, GF_OPT_NAVIGATION_TYPE); + enable = type ? TRUE : FALSE; + + if (pCmdUI->m_nID==ID_NAV_RESET) { + pCmdUI->Enable(TRUE); + return; + } + + u32 mode = gf_term_get_option(app->m_term, GF_OPT_NAVIGATION); + /*common 2D/3D modes*/ + if (pCmdUI->m_nID==ID_NAVIGATE_NONE) { + pCmdUI->Enable(enable); + pCmdUI->SetCheck(mode ? 0 : 1); + } + else if (pCmdUI->m_nID==ID_NAVIGATE_EXAM) { + pCmdUI->Enable(enable); + pCmdUI->SetCheck((mode==GF_NAVIGATE_EXAMINE) ? 1 : 0); + } + else if (pCmdUI->m_nID==ID_NAVIGATE_SLIDE) { + pCmdUI->Enable(enable); + pCmdUI->SetCheck((mode==GF_NAVIGATE_SLIDE) ? 1 : 0); + } + + if (type==GF_NAVIGATE_TYPE_2D) return; + pCmdUI->Enable(enable); + if (pCmdUI->m_nID==ID_NAVIGATE_WALK) pCmdUI->SetCheck((mode==GF_NAVIGATE_WALK) ? 1 : 0); + else if (pCmdUI->m_nID==ID_NAVIGATE_FLY) pCmdUI->SetCheck((mode==GF_NAVIGATE_FLY) ? 1 : 0); + else if (pCmdUI->m_nID==ID_NAVIGATE_PAN) pCmdUI->SetCheck((mode==GF_NAVIGATE_PAN) ? 1 : 0); + else if (pCmdUI->m_nID==ID_NAVIGATE_VR) pCmdUI->SetCheck((mode==GF_NAVIGATE_VR) ? 1 : 0); + else if (pCmdUI->m_nID==ID_NAVIGATE_GAME) pCmdUI->SetCheck((mode==GF_NAVIGATE_GAME) ? 1 : 0); +} + + +void CMainFrame::SetNavigate(u32 mode) +{ + Osmo4 *app = GetApp(); + gf_term_set_option(app->m_term, GF_OPT_NAVIGATION, mode); +} +void CMainFrame::OnNavigateNone() { + SetNavigate(GF_NAVIGATE_NONE); +} +void CMainFrame::OnNavigateWalk() { + SetNavigate(GF_NAVIGATE_WALK); +} +void CMainFrame::OnNavigateFly() { + SetNavigate(GF_NAVIGATE_FLY); +} +void CMainFrame::OnNavigateExam() { + SetNavigate(GF_NAVIGATE_EXAMINE); +} +void CMainFrame::OnNavigateSlide() { + SetNavigate(GF_NAVIGATE_SLIDE); +} +void CMainFrame::OnNavigatePan() { + SetNavigate(GF_NAVIGATE_PAN); +} +void CMainFrame::OnNavigateOrbit() { + SetNavigate(GF_NAVIGATE_ORBIT); +} +void CMainFrame::OnNavigateVR() { + SetNavigate(GF_NAVIGATE_VR); +} +void CMainFrame::OnNavigateGame() { + SetNavigate(GF_NAVIGATE_GAME); +} + +void CMainFrame::OnNavigateReset() +{ + Osmo4 *app = GetApp(); + gf_term_set_option(app->m_term, GF_OPT_NAVIGATION_TYPE, 0); +} + + +LRESULT CMainFrame::OnNavigate(WPARAM /*wParam*/, LPARAM /*lParam*/) +{ + Osmo4 *gpac = GetApp(); + + /*this is a migrate instruction, just disconnect the player*/ + if (gpac->m_navigate_url.IsEmpty() ) { + gf_term_disconnect(gpac->m_term); + return 0; + } + + if (gf_term_is_supported_url(gpac->m_term, gpac->m_navigate_url, GF_TRUE, gpac->m_NoMimeFetch)) { + char *str = gf_url_concatenate(m_pPlayList->GetURL(), gpac->m_navigate_url); + if (str) { + m_pPlayList->Truncate(); + m_pPlayList->QueueURL(str); + gf_free(str); + m_pPlayList->RefreshList(); + m_pPlayList->PlayNext(); + return 0; + } + } + + if (m_bFullScreen) { + SetFullscreen(); + m_RestoreFS = 1; + } + + console_message = gpac->m_navigate_url; + console_err = GF_OK; + PostMessage(WM_CONSOLEMSG); + ShellExecute(NULL, "open", (LPCSTR) gpac->m_navigate_url, NULL, NULL, SW_SHOWNORMAL); + + return 0; +} + +void CMainFrame::OnFileProp() +{ + if (!m_pProps) { + m_pProps = new CFileProps(this); + m_pProps->Create(this); + } + m_pProps->ShowWindow(SW_SHOW); +} + +void CMainFrame::OnUpdateFileProp(CCmdUI* pCmdUI) +{ + pCmdUI->Enable(GetApp()->m_isopen); +} + +void CMainFrame::OnConfigure() +{ + if (!m_pOpt) { + m_pOpt = new COptions(this); + m_pOpt->Create(this); + } + m_pOpt->ShowWindow(SW_SHOW); +} + +void CMainFrame::OnShortcuts() +{ + MessageBox( + "Open File: Ctrl + O\n" + "Open URL: Ctrl + U\n" + "Reload File: F5\n" + "Pause/Resume File: Ctrl + P\n" + "Step by Step: Ctrl + S\n" + "Seek +5%: Alt + left arrow\n" + "Seek -5%: Alt + right arrow\n" + "Switch quality up: Ctrl + H\n" + "Switch quality down: Ctrl + L\n" + "Fullscreen On/Off: Double-click or Escape\n" + "\n" + "Show Properties: Ctrl + I\n" + "Show Playlist: F3\n" + "Next Playlist Item: Ctrl + right arrow\n" + "Previous Playlist Item: Ctrl + left arrow\n" + "\n" + "Aspect Ratio Normal: Ctrl + 1\n" + "Aspect Ratio Fill: Ctrl + 2\n" + "Aspect Ratio 4/3: Ctrl + 3\n" + "Aspect Ratio 16/9: Ctrl + 4\n" + + + , "Shortcuts Available on Osmo4", MB_OK); +} + +void CMainFrame::OnNavInfo() +{ + MessageBox( + "* Walk & Fly modes:\n" + "\tH move: H pan - V move: Z-translate - V move+CTRL or Wheel: V pan - Right Click (Walk only): Jump\n" + "\tleft/right: H pan - left/right+CTRL: H translate - up/down: Z-translate - up/down+CTRL: V pan\n" + "* Pan mode:\n" + "\tH move: H pan - V move: V pan - V move+CTRL or Wheel: Z-translate\n" + "\tleft/right: H pan - left/right+CTRL: H translate - up/down: V pan - up/down+CTRL: Z-translate\n" + "* Slide mode:\n" + "\tH move: H translate - V move: V translate - V move+CTRL or Wheel: Z-translate\n" + "\tleft/right: H translate - left/right+CTRL: H pan - up/down: V translate - up/down+CTRL: Z-translate\n" + "* Examine & Orbit mode:\n" + "\tH move: Y-Axis rotate - H move+CTRL: Z-Axis rotate - V move: X-Axis rotate - V move+CTRL or Wheel: Z-translate\n" + "\tleft/right: Y-Axis rotate - left/right+CTRL: H translate - up/down: X-Axis rotate - up/down+CTRL: Y-translate\n" + "* VR mode:\n" + "\tH move: H pan - V move: V pan - V move+CTRL or Wheel: Camera Zoom\n" + "\tleft/right: H pan - up/down: V pan - up/down+CTRL: Camera Zoom\n" + "* Game mode (press END to escape):\n" + "\tH move: H pan - V move: V pan\n" + "\tleft/right: H translate - up/down: Z-translate\n" + "\n" + "* All 3D modes: CTRL+PGUP/PGDOWN will zoom in/out camera (field of view) \n" + + "\n" + "*Slide Mode in 2D:\n" + "\tH move: H translate - V move: V translate - V move+CTRL: zoom\n" + "\tleft/right: H translate - up/down: V translate - up/down+CTRL: zoom\n" + "*Examine Mode in 2D (3D renderer only):\n" + "\tH move: Y-Axis rotate - V move: X-Axis rotate\n" + "\tleft/right: Y-Axis rotate - up/down: X-Axis rotate\n" + + "\n" + "HOME: reset navigation to last viewpoint (2D or 3D navigation)\n" + "SHIFT key in all modes: fast movement\n" + + , "3D navigation keys (\'H\'orizontal and \'V\'ertical) used in GPAC", MB_OK); +} + + + +void CMainFrame::BuildViewList() +{ + Osmo4 *app = GetApp(); + if (!app->m_isopen) return; + + /*THIS IS HARCODED FROM THE MENU LAYOUT */ + CMenu *pMenu = GetMenu()->GetSubMenu(1)->GetSubMenu(0); + while (pMenu->GetMenuItemCount()) pMenu->DeleteMenu(0, MF_BYPOSITION); + + s32 id = ID_VP_0; + nb_viewpoints = 0; + while (1) { + const char *szName = NULL; + Bool bound; + GF_Err e = gf_term_get_viewpoint(app->m_term, nb_viewpoints+1, &szName, &bound); + if (e) break; + if (szName) { + pMenu->AppendMenu(MF_ENABLED, id+nb_viewpoints, szName); + } else { + char szLabel[1024]; + sprintf(szLabel, "Viewpoint #%d", nb_viewpoints+1); + pMenu->AppendMenu(MF_ENABLED, id+nb_viewpoints, szLabel); + } + nb_viewpoints++; + if (nb_viewpoints==ID_VP_19-ID_VP_0) break; + } +} + + +void CMainFrame::BuildStreamList(Bool reset_only) +{ + u32 nb_subs; + CMenu *pSelect; + Osmo4 *app = GetApp(); + + pSelect = GetMenu()->GetSubMenu(2)->GetSubMenu(0); + /*THIS IS HARCODED FROM THE MENU LAYOUT */ + CMenu *pMenu = pSelect->GetSubMenu(0); + while (pMenu->GetMenuItemCount()) pMenu->DeleteMenu(0, MF_BYPOSITION); + pMenu = pSelect->GetSubMenu(1); + while (pMenu->GetMenuItemCount()) pMenu->DeleteMenu(0, MF_BYPOSITION); + pMenu = pSelect->GetSubMenu(2); + while (pMenu->GetMenuItemCount()) pMenu->DeleteMenu(0, MF_BYPOSITION); + + if (reset_only) { + m_bFirstStreamQuery = GF_TRUE; + return; + } + if (!app->m_isopen || !gf_term_get_option(app->m_term, GF_OPT_CAN_SELECT_STREAMS)) return; + + GF_ObjectManager *root_od = gf_term_get_root_object(app->m_term); + if (!root_od) return; + u32 count = gf_term_get_object_count(app->m_term, root_od); + nb_subs = 0; + + for (u32 i=0; im_term, root_od, i); + if (!odm) return; + + if (gf_term_get_object_info(app->m_term, odm, &info) != GF_OK) break; + if (info.owns_service) { + char *szName = (char *)strrchr(info.service_url, '\\'); + if (!szName) szName = (char *)strrchr(info.service_url, '/'); + if (!szName) szName = (char *) info.service_url; + else szName += 1; + strcpy(szLabel, szName); + szName = strrchr(szLabel, '.'); + if (szName) szName[0] = 0; + } + + switch (info.od_type) { + case GF_STREAM_AUDIO: + pMenu = pSelect->GetSubMenu(0); + if (!info.owns_service) { + if (info.lang) { + sprintf(szLabel, "Language %s (ID %d)", gf_4cc_to_str(info.lang), info.od->objectDescriptorID); + } else { + sprintf(szLabel, "ID %d", info.od->objectDescriptorID); + } + } + pMenu->AppendMenu(MF_ENABLED, ID_SELOBJ_0 + i, szLabel); + break; + case GF_STREAM_VISUAL: + pMenu = pSelect->GetSubMenu(1); + if (!info.owns_service) sprintf(szLabel, "ID %d", info.od->objectDescriptorID); + pMenu->AppendMenu(MF_ENABLED, ID_SELOBJ_0 + i, szLabel); + break; + case GF_STREAM_TEXT: + nb_subs ++; + pMenu = pSelect->GetSubMenu(2); + if (!info.owns_service) { + if (info.lang) { + sprintf(szLabel, "Language %s (ID %d)", gf_4cc_to_str(info.lang), info.od->objectDescriptorID); + } else { + sprintf(szLabel, "ID %d", info.od->objectDescriptorID); + } + } + pMenu->AppendMenu(MF_ENABLED, ID_SELOBJ_0 + i, szLabel); + break; + } + } + if (m_bFirstStreamQuery) { + m_bFirstStreamQuery = GF_FALSE; + if (!nb_subs && app->m_LookForSubtitles) LookForSubtitles(); + } + +} + +BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam) +{ + int ID = LOWORD(wParam); + Osmo4 *app = GetApp(); + + if ( (ID>=ID_VP_0) && (ID<=ID_VP_0+nb_viewpoints)) { + ID -= ID_VP_0; + gf_term_set_viewpoint(app->m_term, ID+1, NULL); + return TRUE; + } + if ( (ID>=ID_NAV_PREV_0) && (ID<=ID_NAV_PREV_9)) { + ID -= ID_NAV_PREV_0; + s32 prev = m_pPlayList->m_cur_entry - ID; + if (prev>=0) { + m_pPlayList->m_cur_entry = prev; + m_pPlayList->PlayPrev(); + } + return TRUE; + } + if ( (ID>=ID_NAV_NEXT_0) && (ID<=ID_NAV_NEXT_9)) { + ID -= ID_NAV_NEXT_0; + u32 next = m_pPlayList->m_cur_entry + ID; + if (next < gf_list_count(m_pPlayList->m_entries) ) { + m_pPlayList->m_cur_entry = next; + m_pPlayList->PlayNext(); + } + return TRUE; + } + if ( (ID>=ID_SELOBJ_0) && (ID<=ID_SELOBJ_29)) { + ID -= ID_SELOBJ_0; + GF_ObjectManager *root_od = gf_term_get_root_object(app->m_term); + if (!root_od) return TRUE; + GF_ObjectManager *odm = gf_term_get_object(app->m_term, root_od, ID); + gf_term_select_object(app->m_term, odm); + return TRUE; + } + if ( (ID>=ID_SETCHAP_FIRST) && (ID<=ID_SETCHAP_LAST)) { + ID -= ID_SETCHAP_FIRST; + gf_term_play_from_time(app->m_term, (u32) (1000*m_chapters_start[ID]), 0); + return TRUE; + } + return CFrameWnd::OnCommand(wParam, lParam); +} + +void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT ID, BOOL bSys) +{ + Osmo4 *app = GetApp(); + /*viewport list*/ + if (pPopupMenu->GetMenuItemID(0)==ID_VP_0) { + for (int i=0; im_term, i+1, &szName, &bound); + pPopupMenu->EnableMenuItem(i, MF_BYPOSITION); + if (bound) pPopupMenu->CheckMenuItem(i, MF_BYPOSITION | MF_CHECKED); + } + return; + } + /*navigation*/ + if ((pPopupMenu->GetMenuItemID(0)==ID_NAV_PREV_0) || (pPopupMenu->GetMenuItemID(0)==ID_NAV_NEXT_0)) { + int count = pPopupMenu->GetMenuItemCount(); + for (int i=0; iEnableMenuItem(i, MF_BYPOSITION); + } + return; + } + /*stream selection*/ + if (pPopupMenu->m_hMenu == GetMenu()->GetSubMenu(2)->m_hMenu) { + if (!app->m_isopen || !gf_term_get_option(app->m_term, GF_OPT_CAN_SELECT_STREAMS)) { + pPopupMenu->EnableMenuItem(0, MF_BYPOSITION | MF_DISABLED | MF_GRAYED); + } else { + pPopupMenu->EnableMenuItem(0, MF_BYPOSITION | MF_ENABLED); + } + } + if ((pPopupMenu->GetMenuItemID(0)>=ID_SELOBJ_0) && (pPopupMenu->GetMenuItemID(0)<=ID_SELOBJ_29)) { + GF_ObjectManager *root_od = gf_term_get_root_object(app->m_term); + if (!root_od) return; + + int count = pPopupMenu->GetMenuItemCount(); + for (int i=0; iGetMenuItemID(i) - ID_SELOBJ_0; + GF_ObjectManager *odm = gf_term_get_object(app->m_term, root_od, id); + if (!odm) { + pPopupMenu->EnableMenuItem(i, MF_DISABLED | MF_BYPOSITION); + } else { + GF_MediaInfo info; + + gf_term_get_object_info(app->m_term, odm, &info); + pPopupMenu->EnableMenuItem(i, MF_BYPOSITION); + pPopupMenu->CheckMenuItem(i, MF_BYPOSITION | (info.status ? MF_CHECKED : MF_UNCHECKED) ); + } + } + return; + } + /*chapters*/ + if (pPopupMenu->m_hMenu == GetMenu()->GetSubMenu(2)->m_hMenu) { + if (!app->m_isopen || !m_num_chapters) { + pPopupMenu->EnableMenuItem(1, MF_BYPOSITION | MF_DISABLED | MF_GRAYED); + } else { + pPopupMenu->EnableMenuItem(1, MF_BYPOSITION | MF_ENABLED); + } + } + if ((pPopupMenu->GetMenuItemID(0)>=ID_SETCHAP_FIRST) && (pPopupMenu->GetMenuItemID(0)<=ID_SETCHAP_LAST)) { + Double now = gf_term_get_time_in_ms(app->m_term); + now /= 1000; + + int count = pPopupMenu->GetMenuItemCount(); + for (int i=0; iGetMenuItemID(i) - ID_SETCHAP_FIRST; + pPopupMenu->EnableMenuItem(i, MF_BYPOSITION); + + Bool is_current = GF_FALSE; + if (m_chapters_start[id]<=now) { + if (id+1now) is_current = GF_TRUE; + } else { + is_current = GF_TRUE; + } + } + pPopupMenu->CheckMenuItem(i, MF_BYPOSITION | (is_current ? MF_CHECKED : MF_UNCHECKED)); + } + return; + } + /*default*/ + CFrameWnd::OnInitMenuPopup(pPopupMenu, ID, bSys); +} + +void CMainFrame::OnCollideDisp() +{ + gf_term_set_option(GetApp()->m_term, GF_OPT_COLLISION, GF_COLLISION_DISPLACEMENT); +} + +void CMainFrame::OnUpdateCollideDisp(CCmdUI* pCmdUI) +{ + Osmo4 *gpac = GetApp(); + pCmdUI->Enable(gpac->m_isopen); + pCmdUI->SetCheck( (gf_term_get_option(gpac->m_term, GF_OPT_COLLISION) == GF_COLLISION_DISPLACEMENT) ? 1 : 0); +} + +void CMainFrame::OnCollideNone() +{ + gf_term_set_option(GetApp()->m_term, GF_OPT_COLLISION, GF_COLLISION_NONE); +} + +void CMainFrame::OnUpdateCollideNone(CCmdUI* pCmdUI) +{ + Osmo4 *gpac = GetApp(); + pCmdUI->Enable(gpac->m_isopen); + pCmdUI->SetCheck( (gf_term_get_option(gpac->m_term, GF_OPT_COLLISION) == GF_COLLISION_NONE) ? 1 : 0); +} + +void CMainFrame::OnCollideReg() +{ + gf_term_set_option(GetApp()->m_term, GF_OPT_COLLISION, GF_COLLISION_NORMAL); +} + +void CMainFrame::OnUpdateCollideReg(CCmdUI* pCmdUI) +{ + Osmo4 *gpac = GetApp(); + pCmdUI->Enable(gpac->m_isopen); + pCmdUI->SetCheck( (gf_term_get_option(gpac->m_term, GF_OPT_COLLISION) == GF_COLLISION_NORMAL) ? 1 : 0); +} + +void CMainFrame::OnHeadlight() +{ + Osmo4 *app = GetApp(); + Bool val = gf_term_get_option(app->m_term, GF_OPT_HEADLIGHT) ? GF_FALSE : GF_TRUE; + gf_term_set_option(app->m_term, GF_OPT_HEADLIGHT, val); +} + +void CMainFrame::OnUpdateHeadlight(CCmdUI* pCmdUI) +{ + Osmo4 *app = GetApp(); + pCmdUI->Enable(FALSE); + if (!app->m_isopen) return; + u32 type = gf_term_get_option(app->m_term, GF_OPT_NAVIGATION_TYPE); + if (type!=GF_NAVIGATE_TYPE_3D) return; + + pCmdUI->Enable(TRUE); + pCmdUI->SetCheck(gf_term_get_option(app->m_term, GF_OPT_HEADLIGHT) ? 1 : 0); +} + +void CMainFrame::OnGravity() +{ + Osmo4 *app = GetApp(); + Bool val = gf_term_get_option(app->m_term, GF_OPT_GRAVITY) ? GF_FALSE : GF_TRUE; + gf_term_set_option(app->m_term, GF_OPT_GRAVITY, val); +} + +void CMainFrame::OnUpdateGravity(CCmdUI* pCmdUI) +{ + Osmo4 *app = GetApp(); + pCmdUI->Enable(FALSE); + if (!app->m_isopen) return; + u32 type = gf_term_get_option(app->m_term, GF_OPT_NAVIGATION_TYPE); + if (type!=GF_NAVIGATE_TYPE_3D) return; + type = gf_term_get_option(app->m_term, GF_OPT_NAVIGATION); + if (type != GF_NAVIGATE_WALK) return; + pCmdUI->Enable(TRUE); + pCmdUI->SetCheck(gf_term_get_option(app->m_term, GF_OPT_GRAVITY) ? 1 : 0); +} + + +BOOL CMainFrame::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) +{ + + if (((LPNMHDR)lParam)->code == TBN_DROPDOWN) { + RECT rc; + s32 i, count, start; + POINT pt; + CMenu *pPopup = new CMenu(); + pPopup->CreatePopupMenu(); + + m_wndToolBar.GetWindowRect(&rc); + pt.y = rc.bottom; + pt.x = rc.left; + m_wndToolBar.GetToolBarCtrl().GetItemRect(0, &rc); + pt.x += (rc.right - rc.left); + m_wndToolBar.GetToolBarCtrl().GetItemRect(1, &rc); + pt.x += (rc.right - rc.left); + + count = gf_list_count(m_pPlayList->m_entries); + if ( ((LPNMTOOLBAR)lParam)->iItem == ID_NAV_PREV) { + start = m_pPlayList->m_cur_entry - 1; + for (i=0; i<10; i++) { + if (start - i < 0) break; + if (start - i >= count) break; + PLEntry *ple = (PLEntry *) gf_list_get(m_pPlayList->m_entries, start - i); + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_NAV_PREV_0 + i, ple->m_disp_name); + } + } else { + start = m_pPlayList->m_cur_entry + 1; + for (i=0; i<10; i++) { + if (start + i >= count) break; + PLEntry *ple = (PLEntry *) gf_list_get(m_pPlayList->m_entries, start + i); + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_NAV_NEXT_0 + i, ple->m_disp_name); + } + m_wndToolBar.GetToolBarCtrl().GetItemRect(2, &rc); + pt.x += (rc.right - rc.left); + } + pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this); + delete pPopup; + + return FALSE; + } + return CFrameWnd::OnNotify(wParam, lParam, pResult); +} + +void CMainFrame::OnNavNext() +{ + Osmo4 *app = GetApp(); + /*don't play if last could trigger playlist loop*/ + if ((m_pPlayList->m_cur_entry<0) || (gf_list_count(m_pPlayList->m_entries) == 1 + (u32) m_pPlayList->m_cur_entry)) return; + m_pPlayList->PlayNext(); +} + +void CMainFrame::OnUpdateNavNext(CCmdUI* pCmdUI) +{ + if (m_pPlayList->m_cur_entry<0) pCmdUI->Enable(FALSE); + else if ((u32) m_pPlayList->m_cur_entry + 1 == gf_list_count(m_pPlayList->m_entries) ) pCmdUI->Enable(FALSE); + else pCmdUI->Enable(TRUE); +} + +void CMainFrame::OnNavPrev() +{ + Osmo4 *app = GetApp(); + if (m_pPlayList->m_cur_entry<=0) return; + m_pPlayList->PlayPrev(); +} + +void CMainFrame::OnUpdateNavPrev(CCmdUI* pCmdUI) +{ + if (m_pPlayList->m_cur_entry<=0) pCmdUI->Enable(FALSE); + else pCmdUI->Enable(TRUE); +} + + +void CMainFrame::OnClearNav() +{ + m_pPlayList->ClearButPlaying(); +} + +void CMainFrame::OnViewPlaylist() +{ + m_pPlayList->ShowWindow(m_pPlayList->IsWindowVisible() ? SW_HIDE : SW_SHOW); +} + +void CMainFrame::OnUpdateViewPlaylist(CCmdUI* pCmdUI) +{ + pCmdUI->SetCheck(m_pPlayList->IsWindowVisible() ? 1 : 0); +} +void CMainFrame::OnPlaylistLoop() +{ + GetApp()->m_Loop = GetApp()->m_Loop ? GF_FALSE : GF_TRUE; + gf_cfg_set_key(GetApp()->m_user.config, "General", "PlaylistLoop", GetApp()->m_Loop ? "yes" : "no"); +} + +void CMainFrame::OnUpdatePlaylistLoop(CCmdUI* pCmdUI) +{ + pCmdUI->SetCheck(GetApp()->m_Loop ? GF_TRUE : GF_FALSE); +} + +void CMainFrame::OnAddSubtitle() +{ + CFileDialog fd(TRUE,NULL,NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, "All Subtitles|*.srt;*.sub;*.ttxt;*.xml|SRT Subtitles|*.srt|SUB Subtitles|*.sub|3GPP TimedText|*.ttxt|QuckTime TeXML|*.xml|"); + if (fd.DoModal() != IDOK) return; + + AddSubtitle(fd.GetPathName(), GF_TRUE); +} + +void CMainFrame::AddSubtitle(const char *fileName, Bool auto_play) +{ + gf_term_add_object(GetApp()->m_term, fileName, auto_play); +} + +static Bool subs_enum_dir_item(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) +{ + CMainFrame *_this = (CMainFrame *)cbck; + _this->AddSubtitle(item_path, GF_FALSE); + return GF_FALSE; +} + +void CMainFrame::LookForSubtitles() +{ + char dir[GF_MAX_PATH]; + CString url = m_pPlayList->GetURL(); + strcpy(dir, url); + char *sep = strrchr(dir, '\\'); + if (!sep) ::GetCurrentDirectory(GF_MAX_PATH, dir); + else sep[0] = 0; + + gf_enum_directory(dir, GF_FALSE, subs_enum_dir_item, this, "ttxt;srt"); +} + +void CMainFrame::OnCacheEnable() +{ + Osmo4 *app = GetApp(); + u32 state = gf_term_get_option(app->m_term, GF_OPT_MEDIA_CACHE); + if (state==GF_MEDIA_CACHE_DISABLED) { + gf_term_set_option(app->m_term, GF_OPT_MEDIA_CACHE, GF_MEDIA_CACHE_ENABLED); + } else if (state==GF_MEDIA_CACHE_DISABLED) { + gf_term_set_option(app->m_term, GF_OPT_MEDIA_CACHE, GF_MEDIA_CACHE_DISABLED); + } +} + +void CMainFrame::OnUpdateCacheEnable(CCmdUI* pCmdUI) +{ + Osmo4 *app = GetApp(); + u32 state = gf_term_get_option(app->m_term, GF_OPT_MEDIA_CACHE); + switch (state) { + case GF_MEDIA_CACHE_ENABLED: + pCmdUI->SetText("Enabled"); + pCmdUI->Enable(TRUE); + break; + case GF_MEDIA_CACHE_RUNNING: + pCmdUI->SetText("Running"); + pCmdUI->Enable(FALSE); + break; + case GF_MEDIA_CACHE_DISABLED: + pCmdUI->SetText("Disabled"); + break; + } +} + +void CMainFrame::OnUpdateCacheStop(CCmdUI* pCmdUI) +{ + Osmo4 *app = GetApp(); + u32 state = gf_term_get_option(app->m_term, GF_OPT_MEDIA_CACHE); + pCmdUI->Enable( (state==GF_MEDIA_CACHE_RUNNING) ? TRUE : FALSE); +} + +void CMainFrame::OnCacheStop() +{ + Osmo4 *app = GetApp(); + gf_term_set_option(app->m_term, GF_OPT_MEDIA_CACHE, GF_MEDIA_CACHE_DISABLED); +} +void CMainFrame::OnCacheAbort() +{ + Osmo4 *app = GetApp(); + gf_term_set_option(app->m_term, GF_OPT_MEDIA_CACHE, GF_MEDIA_CACHE_DISCARD); +} + +void CMainFrame::OnFileExit() +{ + DestroyWindow(); +} + + +void CMainFrame::BuildChapterList(Bool reset_only) +{ + CMenu *pChaps; + GF_MediaInfo odi; + NetInfoCommand com; + Osmo4 *app = GetApp(); + + /*THIS IS HARCODED FROM THE MENU LAYOUT */ + pChaps = GetMenu()->GetSubMenu(2)->GetSubMenu(1); + while (pChaps->GetMenuItemCount()) pChaps->DeleteMenu(0, MF_BYPOSITION); + + if (m_chapters_start) gf_free(m_chapters_start); + m_chapters_start = NULL; + m_num_chapters = 0; + if (reset_only) return; + + GF_ObjectManager *root_od = gf_term_get_root_object(app->m_term); + if (!root_od) return; + if (gf_term_get_object_info(app->m_term, root_od, &odi) != GF_OK) return; + + u32 count = gf_list_count(odi.od->OCIDescriptors); + m_num_chapters = 0; + for (u32 i=0; iOCIDescriptors, i); + if (seg->tag != GF_ODF_SEGMENT_TAG) continue; + + if (seg->SegmentName && strlen((const char *)seg->SegmentName)) { + strcpy(szLabel, (const char *) seg->SegmentName); + } else { + sprintf(szLabel, "Chapter #%02d", m_num_chapters+1); + } + pChaps->AppendMenu(MF_ENABLED, ID_SETCHAP_FIRST + m_num_chapters, szLabel); + + m_chapters_start = (Double *) gf_realloc(m_chapters_start, sizeof(Double)*(m_num_chapters+1)); + m_chapters_start[m_num_chapters] = seg->startTime; + m_num_chapters++; + } + + /*get any service info*/ + if (!m_bStartupFile && gf_term_get_service_info(app->m_term, root_od, &com) == GF_OK) { + CString title(""); + if (com.track_info) { + title.Format("%02d ", (u32) (com.track_info>>16) ); + } + if (com.artist) { + title += com.artist; + title += " "; + } + if (com.name) { + title += com.name; + title += " "; + } + if (com.album) { + title += "("; + title += com.album; + title += ")"; + } + + if (title.GetLength()) SetWindowText(title); + } +} + +void CMainFrame::OnViewCPU() +{ + m_show_rti = m_show_rti ? GF_FALSE : GF_TRUE; +} + +void CMainFrame::OnUpdateViewCPU(CCmdUI* pCmdUI) +{ + pCmdUI->Enable(TRUE); + pCmdUI->SetCheck(m_show_rti); +} + + +void CMainFrame::OnFileCopy() +{ + size_t len; + const char *text = gf_term_get_text_selection(GetApp()->m_term, GF_FALSE); + if (!text) return; + + if (!IsClipboardFormatAvailable(CF_TEXT)) return; + if (!OpenClipboard()) return; + EmptyClipboard(); + + len = strlen(text); + if (!len) return; + + HGLOBAL hglbCopy = GlobalAlloc(GMEM_MOVEABLE, (len + 1) * sizeof(char)); + LPTSTR lptstrCopy = (char *) GlobalLock(hglbCopy); + memcpy(lptstrCopy, text, len * sizeof(char)); + lptstrCopy[len] = 0; + GlobalUnlock(hglbCopy); + SetClipboardData(CF_TEXT, hglbCopy); + CloseClipboard(); +} + +void CMainFrame::OnUpdateFileCopy(CCmdUI* pCmdUI) +{ + Osmo4 *app = GetApp(); + if (IsClipboardFormatAvailable(CF_TEXT) + && app->m_term + && (gf_term_get_text_selection(app->m_term, GF_TRUE)!=NULL) + ) { + pCmdUI->Enable(TRUE); + } else { + pCmdUI->Enable(FALSE); + } +} + + +void CMainFrame::OnFilePaste() +{ + if (!IsClipboardFormatAvailable(CF_TEXT)) return; + if (!OpenClipboard()) return; + + HGLOBAL hglbCopy = GetClipboardData(CF_TEXT); + if (hglbCopy) { + LPTSTR lptstrCopy = (char *) GlobalLock(hglbCopy); + gf_term_paste_text(GetApp()->m_term, lptstrCopy, GF_FALSE); + GlobalUnlock(hglbCopy); + } + CloseClipboard(); +} + +void CMainFrame::OnUpdateFilePaste(CCmdUI* pCmdUI) +{ + Osmo4 *app = GetApp(); + if (IsClipboardFormatAvailable(CF_TEXT) + && app->m_term + && (gf_term_paste_text(app->m_term, NULL, GF_TRUE)==GF_OK) + ) { + pCmdUI->Enable(TRUE); + } else { + pCmdUI->Enable(FALSE); + } +} + + diff --git a/applications/deprecated/old_arch/osmo4_w32/MainFrm.h b/applications/deprecated/old_arch/osmo4_w32/MainFrm.h new file mode 100644 index 0000000..14d0e31 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/MainFrm.h @@ -0,0 +1,224 @@ +// MainFrm.h : interface of the CMainFrame class +// +///////////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_MAINFRM_H__3666B63B_D886_4F0B_9953_A2AF09E3C15A__INCLUDED_) +#define AFX_MAINFRM_H__3666B63B_D886_4F0B_9953_A2AF09E3C15A__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include +#include +#include +#include + +#include "FileProps.h" +#include "Options.h" +#include "AddressBar.h" +#include "Sliders.h" +#include "Playlist.h" + + +class CChildView : public CWnd +{ +// Construction +public: + CChildView(); + +// Attributes +public: + +// Operations +public: + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CChildView) +protected: + virtual BOOL PreCreateWindow(CREATESTRUCT& cs); + //}}AFX_VIRTUAL + +// Implementation +public: + virtual ~CChildView(); + + // Generated message map functions +protected: + //{{AFX_MSG(CChildView) + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +class CMainFrame : public CFrameWnd +{ + +public: + CMainFrame(); +protected: + DECLARE_DYNAMIC(CMainFrame) + +// Attributes +public: + +// Operations +public: + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CMainFrame) +public: + virtual BOOL PreCreateWindow(CREATESTRUCT& cs); + virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo); + virtual BOOL DestroyWindow(); +protected: + virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult); + //}}AFX_VIRTUAL + +// Implementation +public: + virtual ~CMainFrame(); +#ifdef _DEBUG + virtual void AssertValid() const; + virtual void Dump(CDumpContext& dc) const; +#endif + +public: + CStatusBar m_wndStatusBar; + CToolBar m_wndToolBar; + Sliders m_Sliders; + AddressBar m_Address; + CFileProps *m_pProps; + COptions *m_pOpt; + Playlist *m_pPlayList; + CChildView *m_pWndView; + Bool m_bFullScreen; + u32 m_RestoreFS; + UINT_PTR m_timer_on; + CString console_message; + CString console_service; + GF_Err console_err; + u32 m_aspect_ratio; + RECT backup_wnd_rc; + Bool m_bFirstStreamQuery; + /*filter progress events to avoid killing importers with status bar text display...*/ + s32 m_last_prog; + Bool m_show_rti; + Bool m_bStartupFile; + +public: + void SetFullscreen(); + void BuildViewList(); + void BuildStreamList(Bool reset_ony); + void BuildChapterList(Bool reset_ony); + void SetProgTimer(Bool bOn); + void AddSubtitle(const char *fileName, Bool auto_play); + +private: + void ForwardMessage(); + HICON m_icoerror, m_icomessage; + s32 nb_viewpoints; + Bool m_bInitShow; + + void SetNavigate(u32 mode); + void LookForSubtitles(); + + Double *m_chapters_start; + u32 m_num_chapters; + + +// Generated message map functions +protected: + //{{AFX_MSG(CMainFrame) + afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); + afx_msg void OnSetFocus(CWnd *pOldWnd); + afx_msg void OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu); + afx_msg BOOL OnCommand(WPARAM wParam, LPARAM lParam); + afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg void OnMove(int x, int y); + afx_msg LRESULT OnSetSize(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT OnNavigate(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT Open(WPARAM wParam, LPARAM lParam); + afx_msg LRESULT NewInstanceOpened(WPARAM wParam, LPARAM lParam); + + afx_msg void OnLButtonDown(UINT nFlags, CPoint point); + afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point); + afx_msg void OnLButtonUp(UINT nFlags, CPoint point); + afx_msg void OnChar( UINT nChar, UINT nRepCnt, UINT nFlags ); + afx_msg void OnSysKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags ); + afx_msg void OnSysKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags ); + afx_msg void OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags ); + afx_msg void OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags ); + afx_msg void OnDropFiles(HDROP hDropInfo); + afx_msg LRESULT OnConsoleMessage(WPARAM wParam, LPARAM lParam); + afx_msg void OnViewOriginal(); + afx_msg void OnViewFullscreen(); + afx_msg void OnArKeep(); + afx_msg void OnArFill(); + afx_msg void OnAr43(); + afx_msg void OnAr169(); + afx_msg void OnUpdateAr169(CCmdUI* pCmdUI); + afx_msg void OnUpdateAr43(CCmdUI* pCmdUI); + afx_msg void OnUpdateArFill(CCmdUI* pCmdUI); + afx_msg void OnUpdateArKeep(CCmdUI* pCmdUI); + afx_msg void OnNavigateNone(); + afx_msg void OnNavigateWalk(); + afx_msg void OnNavigateFly(); + afx_msg void OnNavigateExam(); + afx_msg void OnNavigateSlide(); + afx_msg void OnNavigatePan(); + afx_msg void OnNavigateOrbit(); + afx_msg void OnNavigateGame(); + afx_msg void OnNavigateVR(); + afx_msg void OnNavigateReset(); + afx_msg void OnShortcuts(); + afx_msg void OnConfigure(); + afx_msg void OnFileProp(); + afx_msg void OnViewPlaylist(); + afx_msg void OnUpdateFileProp(CCmdUI* pCmdUI); + afx_msg void OnUpdateNavigate(CCmdUI* pCmdUI); + afx_msg void OnCacheEnable(); + afx_msg void OnUpdateCacheEnable(CCmdUI* pCmdUI); + afx_msg void OnCacheStop(); + afx_msg void OnCacheAbort(); + afx_msg void OnUpdateCacheStop(CCmdUI* pCmdUI); + afx_msg void OnCollideDisp(); + afx_msg void OnUpdateCollideDisp(CCmdUI* pCmdUI); + afx_msg void OnCollideNone(); + afx_msg void OnUpdateCollideNone(CCmdUI* pCmdUI); + afx_msg void OnCollideReg(); + afx_msg void OnUpdateCollideReg(CCmdUI* pCmdUI); + afx_msg void OnHeadlight(); + afx_msg void OnUpdateHeadlight(CCmdUI* pCmdUI); + afx_msg void OnGravity(); + afx_msg void OnUpdateGravity(CCmdUI* pCmdUI); + afx_msg void OnNavInfo(); + afx_msg void OnNavNext(); + afx_msg void OnNavPrev(); + afx_msg void OnUpdateNavNext(CCmdUI* pCmdUI); + afx_msg void OnUpdateNavPrev(CCmdUI* pCmdUI); + afx_msg void OnClearNav(); + afx_msg void OnUpdateViewPlaylist(CCmdUI* pCmdUI); + afx_msg void OnPlaylistLoop(); + afx_msg void OnUpdatePlaylistLoop(CCmdUI* pCmdUI); + afx_msg void OnAddSubtitle(); + afx_msg void OnFileExit(); + afx_msg void OnViewCPU(); + afx_msg void OnUpdateViewCPU(CCmdUI* pCmdUI); + afx_msg void OnFileCopy(); + afx_msg void OnUpdateFileCopy(CCmdUI* pCmdUI); + afx_msg void OnFilePaste(); + afx_msg void OnUpdateFilePaste(CCmdUI* pCmdUI); + + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_MAINFRM_H__3666B63B_D886_4F0B_9953_A2AF09E3C15A__INCLUDED_) diff --git a/applications/deprecated/old_arch/osmo4_w32/OpenUrl.cpp b/applications/deprecated/old_arch/osmo4_w32/OpenUrl.cpp new file mode 100644 index 0000000..a0b1076 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/OpenUrl.cpp @@ -0,0 +1,98 @@ +// OpenUrl.cpp : implementation file +// + +#include "stdafx.h" +#include "Osmo4.h" +#include "OpenUrl.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// COpenUrl dialog + + +COpenUrl::COpenUrl(CWnd* pParent /*=NULL*/) + : CDialog(COpenUrl::IDD, pParent) +{ + //{{AFX_DATA_INIT(COpenUrl) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void COpenUrl::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COpenUrl) + DDX_Control(pDX, IDC_COMBOURL, m_URLs); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COpenUrl, CDialog) + //{{AFX_MSG_MAP(COpenUrl) + ON_BN_CLICKED(IDC_BUTGO, OnButgo) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + + + + +#define MAX_LAST_FILES 20 +void UpdateLastFiles(GF_Config *cfg, const char *URL) +{ + u32 nb_entries; + gf_cfg_set_key(cfg, "RecentFiles", URL, NULL); + gf_cfg_insert_key(cfg, "RecentFiles", URL, "", 0); + /*remove last entry if needed*/ + nb_entries = gf_cfg_get_key_count(cfg, "RecentFiles"); + if (nb_entries>MAX_LAST_FILES) { + gf_cfg_set_key(cfg, "RecentFiles", gf_cfg_get_key_name(cfg, "RecentFiles", nb_entries-1), NULL); + } +} + + +///////////////////////////////////////////////////////////////////////////// +// COpenUrl message handlers + +void COpenUrl::OnButgo() +{ + CString URL; + int sel = m_URLs.GetCurSel(); + if (sel == CB_ERR) { + m_URLs.GetWindowText(URL); + } else { + m_URLs.GetLBText(sel, URL); + } + if (!URL.GetLength()) { + EndDialog(IDCANCEL); + return; + } + + Osmo4 *gpac = GetApp(); + + m_url = URL; + UpdateLastFiles(gpac->m_user.config, (const char *) URL); + EndDialog(IDOK); +} + +BOOL COpenUrl::OnInitDialog() +{ + CDialog::OnInitDialog(); + + Osmo4 *gpac = GetApp(); + u32 i=0; + + while (m_URLs.GetCount()) m_URLs.DeleteString(0); + while (1) { + const char *sOpt = gf_cfg_get_key_name(gpac->m_user.config, "RecentFiles", i); + if (!sOpt) break; + m_URLs.AddString(sOpt); + i++; + } + return TRUE; +} diff --git a/applications/deprecated/old_arch/osmo4_w32/OpenUrl.h b/applications/deprecated/old_arch/osmo4_w32/OpenUrl.h new file mode 100644 index 0000000..ecd9a43 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/OpenUrl.h @@ -0,0 +1,49 @@ +#if !defined(AFX_OPENURL_H__ADB51A74_305E_4183_8D44_03EEB83D2BFA__INCLUDED_) +#define AFX_OPENURL_H__ADB51A74_305E_4183_8D44_03EEB83D2BFA__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// OpenUrl.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// COpenUrl dialog + +class COpenUrl : public CDialog +{ +// Construction +public: + COpenUrl(CWnd* pParent = NULL); // standard constructor + CString m_url; + +// Dialog Data + //{{AFX_DATA(COpenUrl) + enum { IDD = IDD_OPENFILE }; + CComboBox m_URLs; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COpenUrl) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COpenUrl) + afx_msg void OnBrowse(); + afx_msg void OnButgo(); + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_OPENURL_H__ADB51A74_305E_4183_8D44_03EEB83D2BFA__INCLUDED_) diff --git a/applications/deprecated/old_arch/osmo4_w32/Options.cpp b/applications/deprecated/old_arch/osmo4_w32/Options.cpp new file mode 100644 index 0000000..3fb3e05 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/Options.cpp @@ -0,0 +1,2080 @@ +// Options.cpp : implementation file +// + +#include "stdafx.h" +#include "Osmo4.h" +#include "MainFrm.h" + +#include +#include +#include +#include +#include + +#include +#include "Options.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// COptions dialog + + +COptions::COptions(CWnd* pParent /*=NULL*/) + : CDialog(COptions::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptions) + //}}AFX_DATA_INIT +} + + +void COptions::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptions) + DDX_Control(pDX, IDC_SELECT, m_Selector); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptions, CDialog) + //{{AFX_MSG_MAP(COptions) + ON_BN_CLICKED(IDC_SAVEOPT, OnSaveopt) + ON_WM_CLOSE() + ON_WM_DESTROY() + ON_CBN_SELCHANGE(IDC_SELECT, OnSelchangeSelect) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +BOOL COptions::OnInitDialog() +{ + CDialog::OnInitDialog(); + + m_general.Create(IDD_OPT_GEN, this); + m_systems.Create(IDD_OPT_SYSTEMS, this); + m_render.Create(IDD_OPT_RENDER, this); + m_render2d.Create(IDD_OPT_RENDER2D, this); + m_render3d.Create(IDD_OPT_RENDER3D, this); + m_decoder.Create(IDD_OPT_DECODER, this); + m_audio.Create(IDD_OPT_AUDIO, this); + m_video.Create(IDD_OPT_VIDEO, this); + m_http.Create(IDD_OPT_HTTP, this); + m_font.Create(IDD_OPT_FONT, this); + m_stream.Create(IDD_OPT_STREAM, this); + m_cache.Create(IDD_OPT_MCACHE, this); + m_files.Create(IDD_OPT_FILETYPES, this); + m_logs.Create(IDD_OPT_LOGS, this); + + m_Selector.AddString("General"); + m_Selector.AddString("MPEG-4 Systems"); + m_Selector.AddString("Media Decoders"); + m_Selector.AddString("Compositor"); + m_Selector.AddString("2D Drawing"); + m_Selector.AddString("3D Drawing"); + m_Selector.AddString("Video Output"); + m_Selector.AddString("Audio Output"); + m_Selector.AddString("Text Engine"); + m_Selector.AddString("File Download"); + m_Selector.AddString("Real-Time Streaming"); + m_Selector.AddString("Streaming Cache"); + m_Selector.AddString("File Types"); + m_Selector.AddString("Log System"); + + HideAll(); + + const char *sOpt = gf_cfg_get_key(GetApp()->m_user.config, "General", "ConfigPanel"); + u32 sel = sOpt ? atoi(sOpt) : 0; + if (sel>13) sel=13; + m_Selector.SetCurSel(sel); + m_general.ShowWindow(SW_SHOW); + + OnSelchangeSelect(); + + return TRUE; +} + + +///////////////////////////////////////////////////////////////////////////// +// COptions message handlers + +void COptions::HideAll() +{ + m_general.ShowWindow(SW_HIDE); + m_systems.ShowWindow(SW_HIDE); + m_render.ShowWindow(SW_HIDE); + m_render2d.ShowWindow(SW_HIDE); + m_render3d.ShowWindow(SW_HIDE); + m_audio.ShowWindow(SW_HIDE); + m_video.ShowWindow(SW_HIDE); + m_http.ShowWindow(SW_HIDE); + m_font.ShowWindow(SW_HIDE); + m_stream.ShowWindow(SW_HIDE); + m_decoder.ShowWindow(SW_HIDE); + m_cache.ShowWindow(SW_HIDE); + m_files.ShowWindow(SW_HIDE); + m_files.ShowWindow(SW_HIDE); + m_logs.ShowWindow(SW_HIDE); +} + +void COptions::OnSelchangeSelect() +{ + HideAll(); + switch (m_Selector.GetCurSel()) { + case 0: + m_general.ShowWindow(SW_SHOW); + break; + case 1: + m_systems.ShowWindow(SW_SHOW); + break; + case 2: + m_decoder.ShowWindow(SW_SHOW); + break; + case 3: + m_render.ShowWindow(SW_SHOW); + break; + case 4: + m_render2d.ShowWindow(SW_SHOW); + break; + case 5: + m_render3d.ShowWindow(SW_SHOW); + break; + case 6: + m_video.ShowWindow(SW_SHOW); + break; + case 7: + m_audio.ShowWindow(SW_SHOW); + break; + case 8: + m_font.ShowWindow(SW_SHOW); + break; + case 9: + m_http.ShowWindow(SW_SHOW); + break; + case 10: + m_stream.ShowWindow(SW_SHOW); + break; + case 11: + m_cache.ShowWindow(SW_SHOW); + break; + case 12: + m_files.ShowWindow(SW_SHOW); + break; + case 13: + m_logs.ShowWindow(SW_SHOW); + break; + } +} + +void COptions::OnSaveopt() +{ + m_general.SaveOptions(); + m_systems.SaveOptions(); + m_decoder.SaveOptions(); + m_render.SaveOptions(); + m_render2d.SaveOptions(); + m_render3d.SaveOptions(); + m_audio.SaveOptions(); + m_video.SaveOptions(); + m_http.SaveOptions(); + m_font.SaveOptions(); + m_stream.SaveOptions(); + m_cache.SaveOptions(); + m_logs.SaveOptions(); + + Osmo4 *gpac = GetApp(); + gf_term_set_option(gpac->m_term, GF_OPT_RELOAD_CONFIG, 1); + m_render2d.SetYUV(); +} + +void COptions::OnClose() +{ + char str[20]; + sprintf(str, "%d", m_Selector.GetCurSel()); + gf_cfg_set_key(GetApp()->m_user.config, "General", "ConfigPanel", str); + + DestroyWindow(); +} + +void COptions::OnDestroy() +{ + CDialog::OnDestroy(); + delete this; + ((CMainFrame *)GetApp()->m_pMainWnd)->m_pOpt = NULL; +} + + +COptGen::COptGen(CWnd* pParent /*=NULL*/) + : CDialog(COptGen::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptGen) + //}}AFX_DATA_INIT +} + + +void COptGen::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptGen) + DDX_Control(pDX, IDC_LOOKFORSUB, m_LookForSubs); + DDX_Control(pDX, IDC_DUMP_XMT, m_ViewXMT); + DDX_Control(pDX, IDC_NO_CONSOLE, m_NoConsole); + DDX_Control(pDX, IDC_LOOP, m_Loop); + DDX_Control(pDX, IDC_SINGLE_INSTANCE, m_SingleInstance); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptGen, CDialog) + //{{AFX_MSG_MAP(COptGen) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptGen message handlers + + + +BOOL COptGen::OnInitDialog() +{ + CDialog::OnInitDialog(); + + Osmo4 *gpac = GetApp(); + const char *sOpt; + + sOpt = gf_cfg_get_key(gpac->m_user.config, "General", "Loop"); + m_Loop.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "General", "LookForSubtitles"); + m_LookForSubs.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "General", "ConsoleOff"); + m_NoConsole.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "General", "ViewXMT"); + m_ViewXMT.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "General", "SingleInstance"); + m_SingleInstance.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + return TRUE; +} + +void COptGen::SaveOptions() +{ + Osmo4 *gpac = GetApp(); + + gpac->m_Loop = (Bool) m_Loop.GetCheck(); + gf_cfg_set_key(gpac->m_user.config, "General", "Loop", gpac->m_Loop ? "yes" : "no"); + gpac->m_LookForSubtitles = (Bool) m_LookForSubs.GetCheck(); + gf_cfg_set_key(gpac->m_user.config, "General", "LookForSubtitles", gpac->m_LookForSubtitles ? "yes" : "no"); + gpac->m_NoConsole = (Bool) m_NoConsole.GetCheck(); + gf_cfg_set_key(gpac->m_user.config, "General", "ConsoleOff", gpac->m_NoConsole ? "yes" : "no"); + gpac->m_ViewXMTA = (Bool) m_ViewXMT.GetCheck(); + gf_cfg_set_key(gpac->m_user.config, "General", "ViewXMT", gpac->m_ViewXMTA ? "yes" : "no"); + gpac->m_SingleInstance = (Bool) m_SingleInstance.GetCheck(); + gf_cfg_set_key(gpac->m_user.config, "General", "SingleInstance", gpac->m_SingleInstance ? "yes" : "no"); +} + +COptSystems::COptSystems(CWnd* pParent /*=NULL*/) + : CDialog(COptSystems::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptSystems) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void COptSystems::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptSystems) + DDX_Control(pDX, IDC_FORCE_DURATION, m_ForceDuration); + DDX_Control(pDX, IDC_DEC_THREAD, m_Threading); + DDX_Control(pDX, IDC_BIFSDROP, m_LateFramesAlwaysDrawn); + DDX_Control(pDX, IDC_LANG, m_Lang); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptSystems, CDialog) + //{{AFX_MSG_MAP(COptSystems) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptSystems message handlers + + + + + +BOOL COptSystems::OnInitDialog() +{ + CDialog::OnInitDialog(); + + Osmo4 *gpac = GetApp(); + const char *sOpt; + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Systems", "Language3CC"); + if (!sOpt) sOpt = "eng"; + s32 select = 0; + while (m_Lang.GetCount()) m_Lang.DeleteString(0); + u32 i, count = gf_lang_get_count(); + for (i=0; im_user.config, "Systems", "ThreadingPolicy"); + select = 0; + while (m_Threading.GetCount()) m_Threading.DeleteString(0); + m_Threading.AddString("Single Thread"); + m_Threading.AddString("Mutli Thread"); + if (sOpt && !stricmp(sOpt, "Multi")) select = 1; + m_Threading.AddString("Free"); + if (sOpt && !stricmp(sOpt, "Free")) select = 2; + m_Threading.SetCurSel(select); + + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Systems", "ForceSingleClock"); + if (sOpt && !stricmp(sOpt, "yes")) { + m_ForceDuration.SetCheck(1); + } else { + m_ForceDuration.SetCheck(0); + } + sOpt = gf_cfg_get_key(gpac->m_user.config, "Systems", "DrawLateFrames"); + if (sOpt && !stricmp(sOpt, "yes")) { + m_LateFramesAlwaysDrawn.SetCheck(1); + } else { + m_LateFramesAlwaysDrawn.SetCheck(0); + } + + + return TRUE; +} + + +void COptSystems::SaveOptions() +{ + Osmo4 *gpac = GetApp(); + + s32 sel = m_Lang.GetCurSel(); + u32 i=0; + + gf_cfg_set_key(gpac->m_user.config, "Systems", "LanguageName", gf_lang_get_name(i) ); + gf_cfg_set_key(gpac->m_user.config, "Systems", "Language3CC", gf_lang_get_3cc(i) ); + gf_cfg_set_key(gpac->m_user.config, "Systems", "Language2CC", gf_lang_get_2cc(i) ); + + sel = m_Threading.GetCurSel(); + gf_cfg_set_key(gpac->m_user.config, "Systems", "ThreadingPolicy", (sel==0) ? "Single" : ( (sel==1) ? "Multi" : "Free")); + gf_cfg_set_key(gpac->m_user.config, "Systems", "ForceSingleClock", m_ForceDuration.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "Systems", "DrawLateFrames", m_LateFramesAlwaysDrawn.GetCheck() ? "yes" : "no"); +} + + +OptDecoder::OptDecoder(CWnd* pParent /*=NULL*/) + : CDialog(OptDecoder::IDD, pParent) +{ + //{{AFX_DATA_INIT(OptDecoder) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void OptDecoder::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(OptDecoder) + DDX_Control(pDX, IDC_VIDEC_LIST, m_Video); + DDX_Control(pDX, IDC_AUDEC_LIST, m_Audio); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(OptDecoder, CDialog) + //{{AFX_MSG_MAP(OptDecoder) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// OptDecoder message handlers + + + +BOOL OptDecoder::OnInitDialog() +{ + u32 i; + const char *sOpt; + CDialog::OnInitDialog(); + + Osmo4 *gpac = GetApp(); + + /*audio dec enum*/ + while (m_Audio.GetCount()) m_Audio.DeleteString(0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Systems", "DefAudioDec"); + u32 count = gf_modules_get_count(gpac->m_user.modules); + GF_BaseDecoder *ifce; + s32 select = 0; + s32 to_sel = 0; + for (i=0; im_user.modules, i, GF_MEDIA_DECODER_INTERFACE); + if (!ifce) continue; + + if (ifce->CanHandleStream(ifce, GF_STREAM_AUDIO, NULL, 0)) { + if (sOpt && !stricmp(ifce->module_name, sOpt)) select = to_sel; + m_Audio.AddString(ifce->module_name); + to_sel++; + } + gf_modules_close_interface((GF_BaseInterface *)ifce); + } + m_Audio.SetCurSel(select); + + /*video dec enum*/ + while (m_Video.GetCount()) m_Video.DeleteString(0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Systems", "DefVideoDec"); + count = gf_modules_get_count(gpac->m_user.modules); + select = 0; + to_sel = 0; + for (i=0; im_user.modules, i, GF_MEDIA_DECODER_INTERFACE); + if (!ifce) continue; + + if (ifce->CanHandleStream(ifce, GF_STREAM_VISUAL, NULL, 0)) { + if (sOpt && !stricmp(ifce->module_name, sOpt)) select = to_sel; + m_Video.AddString(ifce->module_name); + to_sel++; + } + gf_modules_close_interface((GF_BaseInterface *)ifce); + } + m_Video.SetCurSel(select); + return TRUE; +} + +void OptDecoder::SaveOptions() +{ + Osmo4 *gpac = GetApp(); + char str[100]; + m_Audio.GetWindowText(str, 100); + gf_cfg_set_key(gpac->m_user.config, "Systems", "DefAudioDec", str); + m_Video.GetWindowText(str, 100); + gf_cfg_set_key(gpac->m_user.config, "Systems", "DefVideoDec", str); + +} + +COptRender::COptRender(CWnd* pParent /*=NULL*/) + : CDialog(COptRender::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptRender) + //}}AFX_DATA_INIT +} + + +void COptRender::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptRender) + DDX_Control(pDX, IDC_DRAW_BOUNDS, m_DrawBounds); + DDX_Control(pDX, IDC_GD_LIST, m_Graphics); + DDX_Control(pDX, IDC_USE_RENDER3D, m_Use3DRender); + DDX_Control(pDX, IDC_AA_LIST, m_AntiAlias); + DDX_Control(pDX, IDC_FORCE_SIZE, m_ForceSize); + DDX_Control(pDX, IDC_FAST_RENDER, m_HighSpeed); + DDX_Control(pDX, IDC_BIFS_RATE, m_BIFSRate); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptRender, CDialog) + //{{AFX_MSG_MAP(COptRender) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptRender message handlers + + + +#define NUM_RATES 11 +static char *BIFSRates[11] = +{ + "5.0", + "7.5", + "10.0", + "12.5", + "15.0", + "24.0", + "25.0", + "30.0", + "50.0", + "60.0", + "100.0", +}; + + + +BOOL COptRender::OnInitDialog() +{ + s32 i; + CDialog::OnInitDialog(); + + Osmo4 *gpac = GetApp(); + const char *sOpt; + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "OpenGLMode"); + m_Use3DRender.SetCheck( (sOpt && !strcmp(sOpt, "always")) ? 1 : 0); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "ForceSceneSize"); + m_ForceSize.SetCheck( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "FrameRate"); + if (!sOpt) sOpt = "30.0"; + s32 select = 0; + while (m_BIFSRate.GetCount()) m_BIFSRate.DeleteString(0); + for (i = 0; im_user.config, "Compositor", "HighSpeed"); + m_HighSpeed.SetCheck( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "AntiAlias"); + while (m_AntiAlias.GetCount()) m_AntiAlias.DeleteString(0); + + m_AntiAlias.AddString("None"); + m_AntiAlias.AddString("Text only"); + m_AntiAlias.AddString("Complete"); + select = 2; + if (sOpt && !stricmp(sOpt, "Text")) select = 1; + else if (sOpt && !stricmp(sOpt, "None")) select = 0; + m_AntiAlias.SetCurSel(select); + + /*graphics driver enum*/ + while (m_Graphics.GetCount()) m_Graphics.DeleteString(0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "core", "raster2d"); + s32 count = gf_modules_get_count(gpac->m_user.modules); + GF_BaseInterface *ifce; + select = 0; + u32 to_sel = 0; + for (i=0; im_user.modules, i, GF_RASTER_2D_INTERFACE); + if (!ifce) continue; + if (sOpt && !stricmp(ifce->module_name, sOpt)) select = to_sel; + m_Graphics.AddString(ifce->module_name); + gf_modules_close_interface(ifce); + to_sel++; + } + m_Graphics.SetCurSel(select); + + + m_DrawBounds.AddString("None"); + m_DrawBounds.AddString("Box/Rect"); + m_DrawBounds.AddString("AABB Tree"); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "BoundingVolume"); + if (sOpt && !stricmp(sOpt, "Box")) m_DrawBounds.SetCurSel(1); + else if (sOpt && !stricmp(sOpt, "AABB")) m_DrawBounds.SetCurSel(2); + else m_DrawBounds.SetCurSel(0); + + return TRUE; +} + + +Bool COptRender::SaveOptions() +{ + char str[50]; + Osmo4 *gpac = GetApp(); + + gf_cfg_set_key(gpac->m_user.config, "Compositor", "HighSpeed", m_HighSpeed.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "ForceSceneSize", m_ForceSize.GetCheck() ? "yes" : "no"); + + s32 sel = m_BIFSRate.GetCurSel(); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "FrameRate", BIFSRates[sel]); + + sel = m_AntiAlias.GetCurSel(); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "AntiAlias", (sel==0) ? "None" : ( (sel==1) ? "Text" : "All")); + + sel = m_DrawBounds.GetCurSel(); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "BoundingVolume", (sel==2) ? "AABB" : (sel==1) ? "Box" : "None"); + + m_Graphics.GetWindowText(str, 50); + gf_cfg_set_key(gpac->m_user.config, "core", "raster2d", str); + + gf_cfg_set_key(gpac->m_user.config, "Compositor", "OpenGLMode", m_Use3DRender.GetCheck() ? "always" : "disable"); + return GF_FALSE; +} + + +COptRender2D::COptRender2D(CWnd* pParent /*=NULL*/) + : CDialog(COptRender2D::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptRender2D) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void COptRender2D::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptRender2D) + DDX_Control(pDX, IDC_FORMAT_YUV, m_YUVFormat); + DDX_Control(pDX, IDC_YUV, m_NoYUV); + DDX_Control(pDX, IDC_ZOOM_SCALABLE, m_Scalable); + DDX_Control(pDX, IDC_DIRECTRENDER, m_DirectRender); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptRender2D, CDialog) + //{{AFX_MSG_MAP(COptRender2D) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptRender2D message handlers + +BOOL COptRender2D::OnInitDialog() +{ + CDialog::OnInitDialog(); + + Osmo4 *gpac = GetApp(); + const char *sOpt; + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "DrawMode"); + if (sOpt && !stricmp(sOpt, "immediate")) { + m_DirectRender.SetCheck(1); + } else { + m_DirectRender.SetCheck(0); + } + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "ScalableZoom"); + if (sOpt && !stricmp(sOpt, "no")) { + m_Scalable.SetCheck(0); + } else { + m_Scalable.SetCheck(1); + } + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "DisableYUV"); + if (sOpt && !stricmp(sOpt, "yes")) { + m_NoYUV.SetCheck(1); + } else { + m_NoYUV.SetCheck(0); + } + + SetYUV(); + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + +void COptRender2D::SetYUV() +{ + Osmo4 *gpac = GetApp(); + u32 yuv_format = gf_term_get_option(gpac->m_term, GF_OPT_YUV_FORMAT); + if (!yuv_format) { + m_YUVFormat.SetWindowText("(No YUV used)"); + } else { + char str[100]; + sprintf(str, "(%s used)", gf_4cc_to_str(yuv_format)); + m_YUVFormat.SetWindowText(str); + } +} + +void COptRender2D::SaveOptions() +{ + Osmo4 *gpac = GetApp(); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "DrawMode", m_DirectRender.GetCheck() ? "immediate" : "defer"); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "ScalableZoom", m_Scalable.GetCheck() ? "yes" : "no"); + + gf_cfg_set_key(gpac->m_user.config, "Compositor", "DisableYUV", m_NoYUV.GetCheck() ? "yes" : "no"); +} + + +COptRender3D::COptRender3D(CWnd* pParent /*=NULL*/) + : CDialog(COptRender3D::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptRender3D) + //}}AFX_DATA_INIT +} + + +void COptRender3D::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptRender3D) + DDX_Control(pDX, IDC_BITMAP_USE_PIXEL, m_BitmapPixels); + DDX_Control(pDX, IDC_DISABLE_TX_RECT, m_DisableTXRect); + DDX_Control(pDX, IDC_RASTER_OUTLINE, m_RasterOutlines); + DDX_Control(pDX, IDC_EMUL_POW2, m_EmulPow2); + DDX_Control(pDX, IDC_DISABLE_POLY_AA, m_PolyAA); + DDX_Control(pDX, IDC_DRAW_NORMALS, m_DrawNormals); + DDX_Control(pDX, IDC_BACK_CULL, m_BackCull); + DDX_Control(pDX, IDC_DRAW_MODE, m_Wireframe); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptRender3D, CDialog) + //{{AFX_MSG_MAP(COptRender3D) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptRender3D message handlers + + +BOOL COptRender3D::OnInitDialog() +{ + CDialog::OnInitDialog(); + + + Osmo4 *gpac = GetApp(); + const char *sOpt; + + m_DrawNormals.AddString("Never"); + m_DrawNormals.AddString("Per Face"); + m_DrawNormals.AddString("Per Vertex"); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "DrawNormals"); + if (sOpt && !stricmp(sOpt, "PerFace")) m_DrawNormals.SetCurSel(1); + else if (sOpt && !stricmp(sOpt, "PerVertex")) m_DrawNormals.SetCurSel(2); + else m_DrawNormals.SetCurSel(0); + + m_BackCull.AddString("Off"); + m_BackCull.AddString("On"); + m_BackCull.AddString("Alpha"); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "BackFaceCulling"); + if (sOpt && !stricmp(sOpt, "Off")) m_BackCull.SetCurSel(0); + else if (sOpt && !stricmp(sOpt, "Alpha")) m_BackCull.SetCurSel(2); + else m_BackCull.SetCurSel(1); + + m_Wireframe.AddString("Solid"); + m_Wireframe.AddString("Wireframe"); + m_Wireframe.AddString("Both"); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "Wireframe"); + if (sOpt && !stricmp(sOpt, "WireOnly")) m_Wireframe.SetCurSel(1); + else if (sOpt && !stricmp(sOpt, "WireOnSolid")) m_Wireframe.SetCurSel(2); + else m_Wireframe.SetCurSel(0); + + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "RasterOutlines"); + m_RasterOutlines.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "EmulatePOW2"); + m_EmulPow2.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "PolygonAA"); + m_PolyAA.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "BitmapCopyPixels"); + m_BitmapPixels.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "DisableRectExt"); + m_DisableTXRect.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + +void COptRender3D::SaveOptions() +{ + Osmo4 *gpac = GetApp(); + + u32 sel = m_DrawNormals.GetCurSel(); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "DrawNormals", (sel==2) ? "PerVertex" : (sel==1) ? "PerFace" : "Never"); + sel = m_BackCull.GetCurSel(); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "BackFaceCulling", (sel==2) ? "Alpha" : (sel==1) ? "On" : "Off"); + sel = m_Wireframe.GetCurSel(); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "Wireframe", (sel==2) ? "WireOnSolid" : (sel==1) ? "WireOnly" : "WireNone"); + + gf_cfg_set_key(gpac->m_user.config, "Compositor", "RasterOutlines", m_RasterOutlines.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "EmulatePOW2", m_EmulPow2.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "PolygonAA", m_PolyAA.GetCheck() ? "yes" : "no"); + + gf_cfg_set_key(gpac->m_user.config, "Compositor", "DisableRectExt", m_DisableTXRect.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "BitmapCopyPixels", m_BitmapPixels.GetCheck() ? "yes" : "no"); +} + +COptVideo::COptVideo(CWnd* pParent /*=NULL*/) + : CDialog(COptVideo::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptVideo) + //}}AFX_DATA_INIT +} + + +void COptVideo::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptVideo) + DDX_Control(pDX, IDC_SWITCH_RES, m_SwitchRes); + DDX_Control(pDX, IDC_VIDEO_LIST, m_Videos); + DDX_Control(pDX, IDC_HWMEMORY, m_UseHWMemory); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptVideo, CDialog) + //{{AFX_MSG_MAP(COptVideo) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptVideo message handlers + +BOOL COptVideo::OnInitDialog() +{ + CDialog::OnInitDialog(); + + Osmo4 *gpac = GetApp(); + const char *sOpt; + + m_SwitchRes.SetCheck(gf_cfg_get_bool(gpac->m_user.config, "core", "switch-vres") ); + sOpt = gf_cfg_get_bool(gpac->m_user.config, "core", "hwvmem"); + m_UseHWMemory.SetCheck(sOpt && !stricmp(sOpt, "Always") ? 1 : 0); + + + u32 count = gf_modules_get_count(gpac->m_user.modules); + GF_BaseInterface *ifce; + s32 to_sel = 0; + s32 select = 0; + /*video drivers enum*/ + while (m_Videos.GetCount()) m_Videos.DeleteString(0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "core", "video-output"); + + for (u32 i=0; im_user.modules, i, GF_VIDEO_OUTPUT_INTERFACE); + if (!ifce) continue; + if (sOpt && !stricmp(ifce->module_name, sOpt)) select = to_sel; + m_Videos.AddString(ifce->module_name); + gf_modules_close_interface(ifce); + to_sel++; + } + m_Videos.SetCurSel(select); + + return TRUE; + +} + +void COptVideo::SaveOptions() +{ + Osmo4 *gpac = GetApp(); + char str[50]; + + gf_cfg_set_key(gpac->m_user.config, "core", "switch-vres", m_SwitchRes.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "core", "hwvmem", m_UseHWMemory.GetCheck() ? "Always" : "Auto"); + m_Videos.GetWindowText(str, 50); + gf_cfg_set_key(gpac->m_user.config, "core", "video-output", str); +} + + +COptAudio::COptAudio(CWnd* pParent /*=NULL*/) + : CDialog(COptAudio::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptAudio) + //}}AFX_DATA_INIT +} + + +void COptAudio::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptAudio) + DDX_Control(pDX, IDC_AUDIO_NOTIFS, m_Notifs); + DDX_Control(pDX, IDC_DRIVER_LIST, m_DriverList); + DDX_Control(pDX, IDC_AUDIO_RESYNC, m_AudioResync); + DDX_Control(pDX, IDC_AUDIO_MULTICH, m_AudioMultiCH); + DDX_Control(pDX, IDC_AUDIO_FPS, m_AudioDur); + DDX_Control(pDX, IDC_SPIN_FPS, m_SpinFPS); + DDX_Control(pDX, IDC_FORCE_AUDIO, m_ForceConfig); + DDX_Control(pDX, IDC_SPIN_AUDIO, m_AudioSpin); + DDX_Control(pDX, IDC_EDIT_AUDIO, m_AudioEdit); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptAudio, CDialog) + //{{AFX_MSG_MAP(COptAudio) + ON_BN_CLICKED(IDC_FORCE_AUDIO, OnForceAudio) + ON_CBN_SELCHANGE(IDC_DRIVER_LIST, OnSelchangeDriverList) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptAudio message handlers + +BOOL COptAudio::OnInitDialog() +{ + CDialog::OnInitDialog(); + + m_AudioSpin.SetBuddy(& m_AudioEdit); + m_SpinFPS.SetBuddy(& m_AudioDur); + m_SpinFPS.SetRange(0, 2000); + + Osmo4 *gpac = GetApp(); + const char *sOpt; + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Audio", "ForceConfig"); + if (sOpt && !stricmp(sOpt, "yes")) { + m_ForceConfig.SetCheck(1); + } else { + m_ForceConfig.SetCheck(0); + } + sOpt = gf_cfg_get_key(gpac->m_user.config, "Audio", "NumBuffers"); + if (sOpt) { + m_AudioEdit.SetWindowText(sOpt); + } else { + m_AudioEdit.SetWindowText("2"); + } + sOpt = gf_cfg_get_key(gpac->m_user.config, "Audio", "TotalDuration"); + if (sOpt) { + m_AudioDur.SetWindowText(sOpt); + } else { + m_AudioDur.SetWindowText("120"); + } + + OnForceAudio(); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Audio", "NoResync"); + m_AudioResync.SetCheck( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Audio", "DisableMultiChannel"); + m_AudioMultiCH.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + /*driver enum*/ + while (m_DriverList.GetCount()) m_DriverList.DeleteString(0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "core", "audio-output"); + u32 count = gf_modules_get_count(gpac->m_user.modules); + GF_BaseInterface *ifce; + s32 select = 0; + s32 to_sel = 0; + for (u32 i=0; im_user.modules, i, GF_AUDIO_OUTPUT_INTERFACE); + if (!ifce) continue; + if (sOpt && !stricmp(ifce->module_name, sOpt)) select = to_sel; + m_DriverList.AddString(ifce->module_name); + gf_modules_close_interface(ifce); + to_sel++; + } + m_DriverList.SetCurSel(select); + + m_Notifs.ShowWindow(SW_HIDE); + if (sOpt && strstr(sOpt, "DirectSound")) m_Notifs.ShowWindow(SW_SHOW); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Audio", "DisableNotification"); + if (sOpt && !stricmp(sOpt, "yes")) + m_Notifs.SetCheck(1); + else + m_Notifs.SetCheck(0); + + return TRUE; +} + + +void COptAudio::SaveOptions() +{ + Osmo4 *gpac = GetApp(); + char str[50]; + + gf_cfg_set_key(gpac->m_user.config, "Audio", "ForceConfig", m_ForceConfig.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "Audio", "NoResync", m_AudioResync.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "Audio", "DisableMultiChannel", m_AudioMultiCH.GetCheck() ? "yes" : "no"); + + m_AudioEdit.GetWindowText(str, 20); + gf_cfg_set_key(gpac->m_user.config, "Audio", "NumBuffers", str); + m_AudioDur.GetWindowText(str, 20); + gf_cfg_set_key(gpac->m_user.config, "Audio", "TotalDuration", str); + + m_DriverList.GetWindowText(str, 50); + gf_cfg_set_key(gpac->m_user.config, "core", "audio-output", str); + + if (strstr(str, "DirectSound")) { + gf_cfg_set_key(gpac->m_user.config, "Audio", "DisableNotification", m_Notifs.GetCheck() ? "yes" : "no"); + } + +} + +void COptAudio::OnForceAudio() +{ + BOOL en = m_ForceConfig.GetCheck(); + + m_AudioSpin.EnableWindow(en); + m_AudioEdit.EnableWindow(en); + m_SpinFPS.EnableWindow(en); + m_AudioDur.EnableWindow(en); +} + +void COptAudio::OnSelchangeDriverList() +{ + char str[50]; + m_DriverList.GetWindowText(str, 50); + if (strstr(str, "DirectSound")) { + m_Notifs.ShowWindow(SW_SHOW); + } else { + m_Notifs.ShowWindow(SW_HIDE); + } +} + + + + +COptFont::COptFont(CWnd* pParent /*=NULL*/) + : CDialog(COptFont::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptFont) + //}}AFX_DATA_INIT +} + + +void COptFont::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptFont) + DDX_Control(pDX, IDC_TEXTURE_MODE, m_TextureModes); + DDX_Control(pDX, IDC_FONT_LIST, m_Fonts); + DDX_Control(pDX, IDC_BROWSE_FONT, m_BrowseFont); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptFont, CDialog) + //{{AFX_MSG_MAP(COptFont) + ON_BN_CLICKED(IDC_BROWSE_FONT, OnBrowseFont) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptFont message handlers + +BOOL COptFont::OnInitDialog() +{ + u32 i; + GF_BaseInterface *ifce; + + CDialog::OnInitDialog(); + + Osmo4 *gpac = GetApp(); + const char *sOpt; + + /*video drivers enum*/ + while (m_Fonts.GetCount()) m_Fonts.DeleteString(0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "FontCache", "FontReader"); + s32 to_sel = 0; + s32 select = 0; + u32 count = gf_modules_get_count(gpac->m_user.modules); + for (i=0; im_user.modules, i, GF_FONT_READER_INTERFACE); + if (!ifce) continue; + if (sOpt && !stricmp(ifce->module_name, sOpt)) select = to_sel; + m_Fonts.AddString(ifce->module_name); + gf_modules_close_interface(ifce); + to_sel++; + } + m_Fonts.SetCurSel(select); + + + sOpt = gf_cfg_get_key(gpac->m_user.config, "FontCache", "FontDirectory"); + if (sOpt) m_BrowseFont.SetWindowText(sOpt); + + /*text texturing modes*/ + while (m_TextureModes.GetCount()) m_TextureModes.DeleteString(0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "TextureTextMode"); + m_TextureModes.AddString("Default"); + m_TextureModes.AddString("Never"); + m_TextureModes.AddString("Always"); + if (sOpt && !stricmp(sOpt, "3D")) m_TextureModes.SetCurSel(1); + else if (sOpt && !stricmp(sOpt, "Always")) m_TextureModes.SetCurSel(2); + else m_TextureModes.SetCurSel(0); + + return TRUE; +} + + + +static char szCacheDir[MAX_PATH]; + +static int CALLBACK LocCbck(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) +{ + char dir[MAX_PATH]; + if (uMsg == BFFM_INITIALIZED) { + strcpy(dir, szCacheDir); + SendMessage(hwnd, BFFM_SETSELECTION, TRUE,(LPARAM) dir); + } + return 0; +} + +void COptFont::OnBrowseFont() +{ + BROWSEINFO brw; + LPMALLOC pMalloc; + LPITEMIDLIST ret; + char dir[MAX_PATH]; + + if (NOERROR == ::SHGetMalloc(&pMalloc) ) { + + m_BrowseFont.GetWindowText(szCacheDir, MAX_PATH); + + memset(&brw, 0, sizeof(BROWSEINFO)); + brw.hwndOwner = this->GetSafeHwnd(); + brw.pszDisplayName = dir; + brw.lpszTitle = "Select Font Directory..."; + brw.ulFlags = 0L; + brw.lpfn = LocCbck; + + ret = SHBrowseForFolder(&brw); + if (ret != NULL) { + if (::SHGetPathFromIDList(ret, dir)) { + m_BrowseFont.SetWindowText(dir); + } + pMalloc->Free(ret); + } + pMalloc->Release(); + } +} + + +void COptFont::SaveOptions() +{ + Osmo4 *gpac = GetApp(); + char str[MAX_PATH]; + + m_Fonts.GetWindowText(str, 50); + gf_cfg_set_key(gpac->m_user.config, "FontCache", "FontReader", str); + m_BrowseFont.GetWindowText(str, 50); + gf_cfg_set_key(gpac->m_user.config, "FontCache", "FontDirectory", str); + switch (m_TextureModes.GetCurSel()) { + case 2: + gf_cfg_set_key(gpac->m_user.config, "Compositor", "TextureTextMode", "Always"); + break; + case 1: + gf_cfg_set_key(gpac->m_user.config, "Compositor", "TextureTextMode", "Never"); + break; + default: + gf_cfg_set_key(gpac->m_user.config, "Compositor", "TextureTextMode", "Default"); + break; + } +} + + +COptHTTP::COptHTTP(CWnd* pParent /*=NULL*/) + : CDialog(COptHTTP::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptHTTP) + //}}AFX_DATA_INIT +} + + +void COptHTTP::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptHTTP) + DDX_Control(pDX, IDC_HTTP_PROXY, m_ProxyName); + DDX_Control(pDX, IDC_HTTP_USE_PROXY, m_useProxy); + DDX_Control(pDX, IDC_SAX_DELAY, m_SAXDuration); + DDX_Control(pDX, IDC_SAX_PROGRESSIVE, m_Progressive); + DDX_Control(pDX, IDC_RESTART_CACHE, m_DisableCache); + DDX_Control(pDX, IDC_CLEAN_CACHE, m_CleanCache); + DDX_Control(pDX, IDC_BROWSE_CACHE, m_CacheDir); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptHTTP, CDialog) + //{{AFX_MSG_MAP(COptHTTP) + ON_BN_CLICKED(IDC_BROWSE_CACHE, OnBrowseCache) + ON_BN_CLICKED(IDC_SAX_PROGRESSIVE, OnSaxProgressive) + ON_BN_CLICKED(IDC_HTTP_USE_PROXY, OnUseProxy) + //}}AFX_MSG_MAP + ON_BN_CLICKED(IDC_RESTART_CACHE, &COptHTTP::OnBnClickedRestartCache) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptHTTP message handlers + + +void COptHTTP::OnBrowseCache() +{ + BROWSEINFO brw; + LPMALLOC pMalloc; + LPITEMIDLIST ret; + char dir[MAX_PATH]; + + if (NOERROR == ::SHGetMalloc(&pMalloc) ) { + + m_CacheDir.GetWindowText(szCacheDir, MAX_PATH); + + memset(&brw, 0, sizeof(BROWSEINFO)); + brw.hwndOwner = this->GetSafeHwnd(); + brw.pszDisplayName = dir; + brw.lpszTitle = "Select HTTP Cache Directory..."; + brw.ulFlags = 0L; + brw.lpfn = LocCbck; + + ret = SHBrowseForFolder(&brw); + if (ret != NULL) { + if (::SHGetPathFromIDList(ret, dir)) { + m_CacheDir.SetWindowText(dir); + } + pMalloc->Free(ret); + } + pMalloc->Release(); + } +} + +BOOL COptHTTP::OnInitDialog() +{ + char proxy[GF_MAX_PATH]; + CDialog::OnInitDialog(); + + Osmo4 *gpac = GetApp(); + const char *sOpt; + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Core", "CacheDirectory"); + if (sOpt) m_CacheDir.SetWindowText(sOpt); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Core", "CleanCache"); + m_CleanCache.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Core", "DisableCache"); + m_DisableCache.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "SAXLoader", "Progressive"); + m_Progressive.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + OnSaxProgressive(); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "SAXLoader", "MaxDuration"); + if (sOpt) { + m_SAXDuration.SetWindowText(sOpt); + } else { + m_SAXDuration.SetWindowText("0"); + } + //if (m_Progressive.GetCheck()) m_SAXDuration.EnableWindow(1); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Core", "HTTPProxyEnabled"); + m_useProxy.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + OnUseProxy(); + strcpy(proxy, ""); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Core", "HTTPProxyName"); + if (sOpt) { + strcpy(proxy, sOpt); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Core", "HTTPProxyPort"); + if (sOpt) { + strcat(proxy, ":"); + strcat(proxy, sOpt); + } + } + m_ProxyName.SetWindowText(proxy); + return TRUE; +} + +void COptHTTP::OnSaxProgressive() +{ + if (m_Progressive.GetCheck()) { + m_SAXDuration.EnableWindow(1); + } else { + m_SAXDuration.EnableWindow(0); + } +} + + +void COptHTTP::OnUseProxy() +{ + if (m_useProxy.GetCheck()) { + m_ProxyName.EnableWindow(1); + } else { + m_ProxyName.EnableWindow(0); + } +} + +void COptHTTP::SaveOptions() +{ + Osmo4 *gpac = GetApp(); + + gf_cfg_set_key(gpac->m_user.config, "Core", "CleanCache", m_CleanCache.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "Core", "DisableCache", m_DisableCache.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "SAXLoader", "Progressive", m_Progressive.GetCheck() ? "yes" : "no"); + + m_SAXDuration.GetWindowText(szCacheDir, MAX_PATH); + gf_cfg_set_key(gpac->m_user.config, "SAXLoader", "MaxDuration", szCacheDir); + + gf_cfg_set_key(gpac->m_user.config, "Core", "HTTPProxyEnabled", m_useProxy.GetCheck() ? "yes" : "no"); + m_ProxyName.GetWindowText(szCacheDir, MAX_PATH); + char *sep = strrchr(szCacheDir, ':'); + if (sep) { + sep[0] = 0; + gf_cfg_set_key(gpac->m_user.config, "Core", "HTTPProxyName", szCacheDir); + sep[0] = ':'; + gf_cfg_set_key(gpac->m_user.config, "Core", "HTTPProxyPort", sep+1); + } else { + gf_cfg_set_key(gpac->m_user.config, "Core", "HTTPProxyName", szCacheDir); + gf_cfg_set_key(gpac->m_user.config, "Core", "HTTPProxyPort", NULL); + } + m_CacheDir.GetWindowText(szCacheDir, MAX_PATH); + gf_cfg_set_key(gpac->m_user.config, "Core", "CacheDirectory", szCacheDir); +} + + +COptStream::COptStream(CWnd* pParent /*=NULL*/) + : CDialog(COptStream::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptStream) + //}}AFX_DATA_INIT +} + + +void COptStream::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptStream) + DDX_Control(pDX, IDC_REBUFFER_LEN, m_RebufferLen); + DDX_Control(pDX, IDC_REBUFFER, m_Rebuffer); + DDX_Control(pDX, IDC_BUFFER, m_Buffer); + DDX_Control(pDX, IDC_TIMEOUT, m_Timeout); + DDX_Control(pDX, IDC_REORDER, m_Reorder); + DDX_Control(pDX, IDC_RTSP, m_UseRTSP); + DDX_Control(pDX, IDC_PORT, m_Port); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptStream, CDialog) + //{{AFX_MSG_MAP(COptStream) + ON_CBN_SELCHANGE(IDC_PORT, OnSelchangePort) + ON_BN_CLICKED(IDC_RTSP, OnRtsp) + ON_BN_CLICKED(IDC_REBUFFER, OnRebuffer) + ON_EN_UPDATE(IDC_REBUFFER_LEN, OnUpdateRebufferLen) + ON_EN_UPDATE(IDC_BUFFER, OnUpdateBuffer) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptStream message handlers + +BOOL COptStream::OnInitDialog() +{ + CDialog::OnInitDialog(); + + Osmo4 *gpac = GetApp(); + const char *sOpt; + + while (m_Port.GetCount()) m_Port.DeleteString(0); + m_Port.AddString("554 (RTSP standard)"); + m_Port.AddString("7070 (RTSP ext)"); + m_Port.AddString("80 (RTSP / HTTP tunnel)"); + m_Port.AddString("8080 (RTSP / HTTP tunnel)"); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Streaming", "DefaultPort"); + u32 port = 554; + Bool force_rtsp = GF_FALSE; + if (sOpt) port = atoi(sOpt); + switch (port) { + case 8080: + m_Port.SetCurSel(3); + force_rtsp = GF_TRUE; + break; + case 80: + m_Port.SetCurSel(2); + force_rtsp = GF_TRUE; + break; + case 7070: + m_Port.SetCurSel(1); + break; + default: + m_Port.SetCurSel(0); + break; + } + + Bool use_rtsp = GF_FALSE; + sOpt = gf_cfg_get_key(gpac->m_user.config, "Streaming", "RTPoverRTSP"); + if (sOpt && !stricmp(sOpt, "yes")) use_rtsp = GF_TRUE; + + if (force_rtsp) { + m_UseRTSP.SetCheck(1); + m_UseRTSP.EnableWindow(0); + m_Reorder.SetCheck(0); + m_Reorder.EnableWindow(0); + } else { + m_UseRTSP.SetCheck(use_rtsp); + m_UseRTSP.EnableWindow(1); + m_Reorder.EnableWindow(1); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Streaming", "ReorderSize"); + if (sOpt && !stricmp(sOpt, "0")) { + m_Reorder.SetCheck(0); + } else { + m_Reorder.SetCheck(1); + } + } + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Streaming", "RTSPTimeout"); + if (sOpt) { + m_Timeout.SetWindowText(sOpt); + } else { + m_Timeout.SetWindowText("30000"); + } + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Network", "BufferLength"); + if (sOpt) { + m_Buffer.SetWindowText(sOpt); + } else { + m_Buffer.SetWindowText("3000"); + } + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Network", "RebufferLength"); + u32 buf_len = 0; + if (sOpt) buf_len = atoi(sOpt); + if (buf_len) { + m_RebufferLen.SetWindowText(sOpt); + m_Rebuffer.SetCheck(1); + m_RebufferLen.EnableWindow(1); + } else { + m_RebufferLen.SetWindowText("0"); + m_Rebuffer.SetCheck(0); + m_RebufferLen.EnableWindow(0); + } + + return TRUE; +} + + +void COptStream::OnSelchangePort() +{ + s32 sel = m_Port.GetCurSel(); + switch (sel) { + case 3: + case 2: + m_UseRTSP.SetCheck(1); + m_UseRTSP.EnableWindow(0); + m_Reorder.SetCheck(0); + m_Reorder.EnableWindow(0); + break; + case 1: + default: + m_UseRTSP.SetCheck(0); + m_UseRTSP.EnableWindow(1); + m_Reorder.SetCheck(1); + m_Reorder.EnableWindow(1); + break; + } +} + +void COptStream::OnRtsp() +{ + if (m_UseRTSP.GetCheck()) { + m_Reorder.SetCheck(0); + m_Reorder.EnableWindow(0); + } else { + m_Reorder.SetCheck(1); + m_Reorder.EnableWindow(1); + } + +} + +void COptStream::CheckRebuffer() +{ + char str[50]; + s32 buf, rebuf; + m_Buffer.GetWindowText(str, 50); + buf = atoi(str); + m_RebufferLen.GetWindowText(str, 50); + rebuf = atoi(str); + if (rebuf*2 > buf) { + rebuf = buf/2; + sprintf(str, "%d", rebuf); + m_RebufferLen.SetWindowText(str); + } +} + +void COptStream::OnRebuffer() +{ + if (!m_Rebuffer.GetCheck()) { + m_RebufferLen.EnableWindow(0); + } else { + m_RebufferLen.EnableWindow(1); + CheckRebuffer(); + } +} + +void COptStream::OnUpdateRebufferLen() +{ + CheckRebuffer(); +} + +void COptStream::OnUpdateBuffer() +{ + CheckRebuffer(); +} + +void COptStream::SaveOptions() +{ + Osmo4 *gpac = GetApp(); + Bool force_rtsp = GF_FALSE; + s32 sel = m_Port.GetCurSel(); + switch (sel) { + case 3: + gf_cfg_set_key(gpac->m_user.config, "Streaming", "DefaultPort", "8080"); + force_rtsp = GF_TRUE; + break; + case 2: + gf_cfg_set_key(gpac->m_user.config, "Streaming", "DefaultPort", "80"); + force_rtsp = GF_TRUE; + break; + case 1: + gf_cfg_set_key(gpac->m_user.config, "Streaming", "DefaultPort", "7070"); + break; + default: + gf_cfg_set_key(gpac->m_user.config, "Streaming", "DefaultPort", "554"); + break; + } + + if (force_rtsp) { + gf_cfg_set_key(gpac->m_user.config, "Streaming", "RTPoverRTSP", "yes"); + } else { + gf_cfg_set_key(gpac->m_user.config, "Streaming", "RTPoverRTSP", m_UseRTSP.GetCheck() ? "yes" : "no"); + if (!m_UseRTSP.GetCheck()) gf_cfg_set_key(gpac->m_user.config, "Streaming", "ReorderSize", m_Reorder.GetCheck() ? "30" : "0"); + } + + char str[50]; + + m_Timeout.GetWindowText(str, 50); + gf_cfg_set_key(gpac->m_user.config, "Streaming", "RTSPTimeout", str); + + m_Buffer.GetWindowText(str, 50); + gf_cfg_set_key(gpac->m_user.config, "Network", "BufferLength", str); + if (m_Rebuffer.GetCheck()) { + m_RebufferLen.GetWindowText(str, 50); + gf_cfg_set_key(gpac->m_user.config, "Network", "RebufferLength", str); + } else { + gf_cfg_set_key(gpac->m_user.config, "Network", "RebufferLength", "0"); + } +} + + + + +COptMCache::COptMCache(CWnd* pParent /*=NULL*/) + : CDialog(COptMCache::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptMCache) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void COptMCache::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptMCache) + DDX_Control(pDX, IDC_BASEPRES, m_BaseName); + DDX_Control(pDX, IDC_MCACHE_USENAME, m_UseBase); + DDX_Control(pDX, IDC_MCACHE_OVERWRITE, m_Overwrite); + DDX_Control(pDX, IDC_BROWSE_MCACHE, m_RecDir); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptMCache, CDialog) + //{{AFX_MSG_MAP(COptMCache) + ON_BN_CLICKED(IDC_BROWSE_MCACHE, OnBrowseMcache) + ON_BN_CLICKED(IDC_MCACHE_USENAME, OnMcacheUsename) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptMCache message handlers + +void COptMCache::OnBrowseMcache() +{ + BROWSEINFO brw; + LPMALLOC pMalloc; + LPITEMIDLIST ret; + char dir[MAX_PATH]; + + if (NOERROR == ::SHGetMalloc(&pMalloc) ) { + + m_RecDir.GetWindowText(szCacheDir, MAX_PATH); + + memset(&brw, 0, sizeof(BROWSEINFO)); + brw.hwndOwner = this->GetSafeHwnd(); + brw.pszDisplayName = dir; + brw.lpszTitle = "Select HTTP Cache Directory..."; + brw.ulFlags = 0L; + brw.lpfn = LocCbck; + + ret = SHBrowseForFolder(&brw); + if (ret != NULL) { + if (::SHGetPathFromIDList(ret, dir)) { + m_RecDir.SetWindowText(dir); + } + pMalloc->Free(ret); + } + pMalloc->Release(); + } +} + +BOOL COptMCache::OnInitDialog() +{ + CDialog::OnInitDialog(); + + Osmo4 *gpac = GetApp(); + const char *sOpt; + + sOpt = gf_cfg_get_key(gpac->m_user.config, "StreamingCache", "RecordDirectory"); + if (!sOpt) sOpt = gf_cfg_get_key(gpac->m_user.config, "Core", "CacheDirectory"); + if (sOpt) m_RecDir.SetWindowText(sOpt); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "StreamingCache", "KeepExistingFiles"); + m_Overwrite.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 0 : 1); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "StreamingCache", "BaseFileName"); + if (sOpt) { + m_UseBase.SetCheck(1); + m_BaseName.EnableWindow(TRUE); + m_BaseName.SetWindowText(sOpt); + } else { + m_UseBase.SetCheck(0); + m_BaseName.EnableWindow(FALSE); + m_BaseName.SetWindowText("uses service URL"); + } + return TRUE; +} + +void COptMCache::OnMcacheUsename() +{ + if (m_UseBase.GetCheck()) { + m_BaseName.EnableWindow(TRUE); + m_BaseName.SetWindowText("record"); + } else { + m_BaseName.EnableWindow(FALSE); + m_BaseName.SetWindowText("uses service URL"); + } +} + +void COptMCache::SaveOptions() +{ + Osmo4 *gpac = GetApp(); + + gf_cfg_set_key(gpac->m_user.config, "StreamingCache", "KeepExistingFiles", m_Overwrite.GetCheck() ? "no" : "yes"); + if (m_UseBase.GetCheck()) { + m_BaseName.GetWindowText(szCacheDir, MAX_PATH); + gf_cfg_set_key(gpac->m_user.config, "StreamingCache", "BaseFileName", szCacheDir); + } else { + gf_cfg_set_key(gpac->m_user.config, "StreamingCache", "BaseFileName", NULL); + } + m_RecDir.GetWindowText(szCacheDir, MAX_PATH); + gf_cfg_set_key(gpac->m_user.config, "StreamingCache", "RecordDirectory", szCacheDir); +} + + +OptFiles::OptFiles(CWnd* pParent /*=NULL*/) + : CDialog(OptFiles::IDD, pParent) +{ + //{{AFX_DATA_INIT(OptFiles) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void OptFiles::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(OptFiles) + DDX_Control(pDX, IDC_ASSOCIATE, m_DoAssociate); + DDX_Control(pDX, IDC_FILES_PLUG, m_PlugName); + DDX_Control(pDX, IDC_FILES_MIMES, m_mimes); + DDX_Control(pDX, IDC_FILES_EXT, m_extensions); + DDX_Control(pDX, IDC_FILELIST, m_FileDescs); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(OptFiles, CDialog) + //{{AFX_MSG_MAP(OptFiles) + ON_CBN_SELCHANGE(IDC_FILELIST, OnSelchangeFilelist) + ON_BN_CLICKED(IDC_ASSOCIATE, OnAssociate) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// OptFiles message handlers + +BOOL OptFiles::OnInitDialog() +{ + CDialog::OnInitDialog(); + + Osmo4 *gpac = GetApp(); + u32 count, i; + + while (m_FileDescs.GetCount()) m_FileDescs.DeleteString(0); + count = gf_cfg_get_key_count(gpac->m_user.config, "MimeTypes"); + for (i=0; im_user.config, "MimeTypes", i); + if (!sMime) continue; + sOpt = gf_cfg_get_key(gpac->m_user.config, "MimeTypes", sMime); + if (!sOpt) continue; + sKey = (char *) strstr(sOpt, "\" \""); + if (!sKey) continue; + strcpy(sDesc, sKey+3); + sKey = strchr(sDesc, '\"'); + if (!sKey) continue; + sKey[0] = 0; + m_FileDescs.AddString(sDesc); + } + m_FileDescs.SetCurSel(0); + SetSelection(0); + return TRUE; +} + +void OptFiles::OnSelchangeFilelist() +{ + SetSelection(m_FileDescs.GetCurSel()); +} + +void OptFiles::SetSelection(u32 sel) +{ + Osmo4 *gpac = GetApp(); + char *sMime, *sKey, sDesc[200], sText[200]; + sMime = (char *) gf_cfg_get_key_name(gpac->m_user.config, "MimeTypes", sel); + sprintf(sText, "Mime Type: %s", sMime); + m_mimes.SetWindowText(sText); + strcpy(cur_mime, sMime); + sMime = (char *) gf_cfg_get_key(gpac->m_user.config, "MimeTypes", sMime); + strcpy(sDesc, sMime+1); + sKey = strchr(sDesc, '\"'); + sKey[0] = 0; + sprintf(sText, "Extensions: %s", sDesc); + strcpy(cur_ext, sDesc); + m_extensions.SetWindowText(sText); + sKey = strrchr(sMime, '\"'); + sprintf(sText, "Module: %s", sKey+2); + m_PlugName.SetWindowText(sText); + + Bool has_asso, need_asso, go = GF_TRUE; + sKey = cur_ext; + need_asso = has_asso = GF_FALSE; + + HKEY hKey; + DWORD dwSize; + while (go) { + Bool ok; + char szExt[50], szReg[60], c; + char *tmp = strchr(sKey, ' '); + if (!tmp) { + go = GF_FALSE; + } + else { + c = tmp[0]; + tmp[0] = 0; + } + sprintf(szExt, ".%s", sKey); + sprintf(szReg, "GPAC\\%s", sKey); + if (tmp) { + tmp[0] = c; + tmp += 1; + } + + if (RegOpenKeyEx(HKEY_CLASSES_ROOT, szExt, 0, KEY_READ, &hKey ) == ERROR_SUCCESS) { + dwSize = 200; + ok = GF_TRUE; + if (RegQueryValueEx(hKey, "", NULL, NULL,(unsigned char*) sDesc, &dwSize) != ERROR_SUCCESS) ok = GF_FALSE; + RegCloseKey(hKey); + if (ok && !stricmp((char *)sDesc, szReg)) has_asso = GF_TRUE; + else need_asso = GF_TRUE; + } else need_asso = GF_TRUE; + sKey = tmp; + + } + m_DoAssociate.SetCheck(has_asso); + if (need_asso && has_asso) + OnAssociate(); +} + + +void OptFiles::OnAssociate() +{ + char *sKey, sDesc[200]; + unsigned char szApp[MAX_PATH]; + unsigned char szIco[MAX_PATH]; + + strcpy((char *) szApp, GetApp()->szApplicationPath); + strcpy((char *) szIco, (const char *) szApp); + strcat((char *) szIco, "Osmo4.ico"); + strcat((char *) szApp, "Osmo4.exe \"%L\""); + + if (m_DoAssociate.GetCheck()) { + Bool go = GF_TRUE; + sKey = cur_ext; + + HKEY hKey; + DWORD dwSize; + while (go) { + Bool ok; + char szExt[50], szReg[60], szOld[80], szPath[1024], c; + char *tmp = strchr(sKey, ' '); + if (!tmp) { + go = GF_FALSE; + } + else { + c = tmp[0]; + tmp[0] = 0; + } + sprintf(szExt, ".%s", sKey); + sprintf(szReg, "GPAC\\%s", sKey); + if (tmp) { + tmp[0] = c; + tmp += 1; + } + + RegOpenKeyEx(HKEY_CLASSES_ROOT, szExt, 0, 0, &hKey ); + dwSize = 200; + ok = GF_TRUE; + if (RegQueryValueEx(hKey, "", NULL, NULL,(unsigned char*) sDesc, &dwSize) != ERROR_SUCCESS) ok = GF_FALSE; + RegCloseKey(hKey); + strcpy(szOld, ""); + if (ok && stricmp((char *)sDesc, szReg)) strcpy(szOld, sDesc); + + strcpy(szPath, szReg); + strcat(szPath, "\\DefaultIcon"); + RegCreateKeyEx(HKEY_CLASSES_ROOT, szPath, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, &dwSize); + RegSetValueEx(hKey, "", 0, REG_SZ, szIco, (DWORD) strlen((const char *) szIco)+1); + RegCloseKey(hKey); + + strcpy(szPath, szReg); + strcat(szPath, "\\Shell\\open\\command"); + RegCreateKeyEx(HKEY_CLASSES_ROOT, szPath, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, &dwSize); + RegSetValueEx(hKey, "", 0, REG_SZ, szApp, (DWORD) strlen((const char *) szApp)+1); + RegCloseKey(hKey); + + if (strlen(szOld)) { + strcpy(szPath, szReg); + strcat(szPath, "\\Backup"); + RegCreateKeyEx(HKEY_CLASSES_ROOT, szPath, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, &dwSize); + RegSetValueEx(hKey, "", 0, REG_SZ, (unsigned char *) szOld, (DWORD) strlen((const char *) szIco)+1); + RegCloseKey(hKey); + } + + RegCreateKeyEx(HKEY_CLASSES_ROOT, szExt, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, &dwSize); + RegSetValueEx(hKey, "", 0, REG_SZ, (const unsigned char *) szReg, (DWORD) strlen(szReg)+1); + RegCloseKey(hKey); + + sKey = tmp; + } + } else { + Bool go = GF_TRUE; + sKey = cur_ext; + + HKEY hKey; + DWORD dwSize; + while (go) { + Bool ok; + char szExt[50], szReg[60], szPath[1024], c; + char *tmp = strchr(sKey, ' '); + if (!tmp) { + go = GF_FALSE; + } + else { + c = tmp[0]; + tmp[0] = 0; + } + sprintf(szExt, ".%s", sKey); + sprintf(szReg, "GPAC\\%s", sKey); + if (tmp) { + tmp[0] = c; + tmp += 1; + } + + strcpy(szPath, szReg); + strcat(szPath, "\\Backup"); + RegOpenKeyEx(HKEY_CLASSES_ROOT, szPath, 0, 0, &hKey ); + dwSize = 200; + ok = GF_TRUE; + if (RegQueryValueEx(hKey, "", NULL, NULL,(unsigned char*) sDesc, &dwSize) != ERROR_SUCCESS) ok = GF_FALSE; + RegCloseKey(hKey); + if (ok && strlen((char *)sDesc)) { + RegCreateKeyEx(HKEY_CLASSES_ROOT, szExt, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, &dwSize); + RegSetValueEx(hKey, "", 0, REG_SZ, (unsigned char*) sDesc, (DWORD) strlen((const char *) sDesc)+1); + RegCloseKey(hKey); + } + + RegOpenKeyEx(HKEY_CLASSES_ROOT, szReg, 0, 0, &hKey ); + RegDeleteKey(hKey, "Backup"); + RegDeleteKey(hKey, "DefaultIcon"); + RegDeleteKey(hKey, "Shell\\open\\command"); + RegDeleteKey(hKey, "Shell\\open"); + RegDeleteKey(hKey, "Shell"); + RegCloseKey(hKey); + RegDeleteKey(HKEY_CLASSES_ROOT, szReg); + + sKey = tmp; + } + } +} + +COptLogs::COptLogs(CWnd* pParent /*=NULL*/) + : CDialog(COptLogs::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptLogs) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void COptLogs::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptLogs) + DDX_Control(pDX, IDC_TOOL_SYNC, m_sync); + DDX_Control(pDX, IDC_TOOL_SCRIPT, m_script); + DDX_Control(pDX, IDC_TOOL_SCENE, m_scene); + DDX_Control(pDX, IDC_TOOL_RTP, m_rtp); + DDX_Control(pDX, IDC_TOOL_RENDER, m_render); + DDX_Control(pDX, IDC_TOOL_PARSER, m_parser); + DDX_Control(pDX, IDC_TOOL_NET, m_net); + DDX_Control(pDX, IDC_TOOL_MMIO, m_mmio); + DDX_Control(pDX, IDC_TOOL_MEDIA, m_media); + DDX_Control(pDX, IDC_TOOL_CORE, m_core); + DDX_Control(pDX, IDC_TOOL_CONTAINER, m_container); + DDX_Control(pDX, IDC_TOOL_COMPOSE, m_compose); + DDX_Control(pDX, IDC_TOOL_CODING, m_coding); + DDX_Control(pDX, IDC_TOOL_CODEC, m_codec); + DDX_Control(pDX, IDC_TOOL_AUTHOR, m_author); + DDX_Control(pDX, IDC_LOG_LEVEL, m_Level); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptLogs, CDialog) + //{{AFX_MSG_MAP(COptLogs) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptLogs message handlers + +BOOL COptLogs::OnInitDialog() +{ + CDialog::OnInitDialog(); + +#if 0 + Osmo4 *gpac = GetApp(); + switch (gpac->m_log_level) { + case GF_LOG_ERROR: + m_Level.SetCurSel(1); + break; + case GF_LOG_WARNING: + m_Level.SetCurSel(2); + break; + case GF_LOG_INFO: + m_Level.SetCurSel(3); + break; + case GF_LOG_DEBUG: + m_Level.SetCurSel(4); + break; + default: + m_Level.SetCurSel(0); + break; + } + + m_sync.SetCheck(gpac->m_log_tools & GF_LOG_SYNC); + m_script.SetCheck(gpac->m_log_tools & GF_LOG_SCRIPT); + m_scene.SetCheck(gpac->m_log_tools & GF_LOG_SCENE); + m_rtp.SetCheck(gpac->m_log_tools & GF_LOG_RTP); + m_render.SetCheck(gpac->m_log_tools & GF_LOG_COMPOSE); + m_parser.SetCheck(gpac->m_log_tools & GF_LOG_PARSER); + m_net.SetCheck(gpac->m_log_tools & GF_LOG_NETWORK); + m_mmio.SetCheck(gpac->m_log_tools & GF_LOG_MMIO); + m_media.SetCheck(gpac->m_log_tools & GF_LOG_MEDIA); + m_core.SetCheck(gpac->m_log_tools & GF_LOG_CORE); + m_container.SetCheck(gpac->m_log_tools & GF_LOG_CONTAINER); + m_compose.SetCheck(gpac->m_log_tools & GF_LOG_INTERACT); + m_coding.SetCheck(gpac->m_log_tools & GF_LOG_CODING); + m_codec.SetCheck(gpac->m_log_tools & GF_LOG_CODEC); + m_author.SetCheck(gpac->m_log_tools & GF_LOG_AUTHOR); +#endif + + return TRUE; +} + +void COptLogs::SaveOptions() +{ + Osmo4 *gpac = GetApp(); + CString str = ""; + const char *level = "error"; + u32 flags = 0; + + switch (m_Level.GetCurSel()) { + case 1: + level = "error"; + break; + case 2: + level = "warning"; + break; + case 3: + level = "info"; + break; + case 4: + level = "debug"; + break; + default: + level = "none"; + break; + } + + if (m_sync.GetCheck()) { + str +="sync:"; + } + if (m_script.GetCheck()) { + str +="script:"; + } + if (m_scene.GetCheck()) { + str +="scene:"; + } + if (m_rtp.GetCheck()) { + str +="rtp:"; + } + if (m_render.GetCheck()) { + str +="compose:"; + } + if (m_parser.GetCheck()) { + str +="parser:"; + } + if (m_net.GetCheck()) { + str +="network:"; + } + if (m_mmio.GetCheck()) { + str +="mmio:"; + } + if (m_media.GetCheck()) { + str +="media:"; + } + if (m_core.GetCheck()) { + str +="core:"; + } + if (m_container.GetCheck()) { + str +="container:"; + } + if (m_compose.GetCheck()) { + str +="interact:"; + } + if (m_coding.GetCheck()) { + str +="coding:"; + } + if (m_codec.GetCheck()) { + str +="codec:"; + } + if (m_author.GetCheck()) { + str +="author:"; + } + + gf_cfg_set_key(gpac->m_user.config, "General", "Logs", str); + str += "@"; + str += level; + gf_log_set_tools_levels(str); +} + +void COptHTTP::OnBnClickedRestartCache() +{ + // TODO : ajoutez ici le code de votre gestionnaire de notification de contrôle +} diff --git a/applications/deprecated/old_arch/osmo4_w32/Options.h b/applications/deprecated/old_arch/osmo4_w32/Options.h new file mode 100644 index 0000000..9c8efd1 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/Options.h @@ -0,0 +1,624 @@ +#if !defined(AFX_OPTIONS_H__5C839953_58C0_4D9D_89CE_2820C7686C1B__INCLUDED_) +#define AFX_OPTIONS_H__5C839953_58C0_4D9D_89CE_2820C7686C1B__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// Options.h : header file +// + + +class COptAudio : public CDialog +{ +// Construction +public: + COptAudio(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptAudio) + enum { IDD = IDD_OPT_AUDIO }; + CButton m_Notifs; + CComboBox m_DriverList; + CButton m_AudioResync; + CButton m_AudioMultiCH; + CEdit m_AudioDur; + CSpinButtonCtrl m_SpinFPS; + CButton m_ForceConfig; + CSpinButtonCtrl m_AudioSpin; + CEdit m_AudioEdit; + //}}AFX_DATA + + void SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptAudio) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptAudio) + virtual BOOL OnInitDialog(); + afx_msg void OnForceAudio(); + afx_msg void OnSelchangeDriverList(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// +// OptDecoder dialog + +class OptDecoder : public CDialog +{ +// Construction +public: + OptDecoder(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(OptDecoder) + enum { IDD = IDD_OPT_DECODER }; + CComboBox m_Video; + CComboBox m_Audio; + //}}AFX_DATA + + void SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(OptDecoder) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(OptDecoder) + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +class OptFiles : public CDialog +{ +// Construction +public: + OptFiles(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(OptFiles) + enum { IDD = IDD_OPT_FILETYPES }; + CButton m_DoAssociate; + CStatic m_PlugName; + CStatic m_mimes; + CStatic m_extensions; + CComboBox m_FileDescs; + //}}AFX_DATA + + void SetSelection(u32 sel); + char cur_ext[200], cur_mime[200]; + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(OptFiles) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(OptFiles) + virtual BOOL OnInitDialog(); + afx_msg void OnSelchangeFilelist(); + afx_msg void OnAssociate(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// +// COptFont dialog + +class COptFont : public CDialog +{ +// Construction +public: + COptFont(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptFont) + enum { IDD = IDD_OPT_FONT }; + CComboBox m_TextureModes; + CComboBox m_Fonts; + CButton m_BrowseFont; + //}}AFX_DATA + + + void SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptFont) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptFont) + virtual BOOL OnInitDialog(); + afx_msg void OnBrowseFont(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// +// COptGen dialog + +class COptGen : public CDialog +{ +// Construction +public: + COptGen(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptGen) + enum { IDD = IDD_OPT_GEN }; + CButton m_LookForSubs; + CButton m_ViewXMT; + CButton m_NoConsole; + CButton m_Loop; + CButton m_SingleInstance; + //}}AFX_DATA + + void SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptGen) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptGen) + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// +// COptHTTP dialog + +class COptHTTP : public CDialog +{ +// Construction +public: + COptHTTP(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptHTTP) + enum { IDD = IDD_OPT_HTTP }; + CEdit m_ProxyName; + CButton m_useProxy; + CEdit m_SAXDuration; + CButton m_Progressive; + CButton m_DisableCache; + CButton m_CleanCache; + CButton m_CacheDir; + //}}AFX_DATA + + + void SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptHTTP) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptHTTP) + afx_msg void OnBrowseCache(); + virtual BOOL OnInitDialog(); + afx_msg void OnSaxProgressive(); + afx_msg void OnUseProxy(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +public: + afx_msg void OnBnClickedRestartCache(); +}; + + +class COptMCache : public CDialog +{ +// Construction +public: + COptMCache(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptMCache) + enum { IDD = IDD_OPT_MCACHE }; + CEdit m_BaseName; + CButton m_UseBase; + CButton m_Overwrite; + CButton m_RecDir; + //}}AFX_DATA + + + void SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptMCache) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptMCache) + virtual BOOL OnInitDialog(); + afx_msg void OnBrowseMcache(); + afx_msg void OnMcacheUsename(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +class COptRender : public CDialog +{ +// Construction +public: + COptRender(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptRender) + enum { IDD = IDD_OPT_RENDER }; + CComboBox m_DrawBounds; + CComboBox m_Graphics; + CButton m_Use3DRender; + CComboBox m_AntiAlias; + CButton m_ForceSize; + CButton m_HighSpeed; + CComboBox m_BIFSRate; + //}}AFX_DATA + + + Bool SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptRender) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptRender) + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +class COptRender2D : public CDialog +{ +// Construction +public: + COptRender2D(CWnd* pParent = NULL); // standard constructor + + void SaveOptions(); + void SetYUV(); + +// Dialog Data + //{{AFX_DATA(COptRender2D) + enum { IDD = IDD_OPT_RENDER2D }; + CStatic m_YUVFormat; + CButton m_NoYUV; + CButton m_Scalable; + CButton m_DirectRender; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptRender2D) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptRender2D) + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +class COptRender3D : public CDialog +{ +// Construction +public: + COptRender3D(CWnd* pParent = NULL); // standard constructor + + void SaveOptions(); + +// Dialog Data + //{{AFX_DATA(COptRender3D) + enum { IDD = IDD_OPT_RENDER3D }; + CButton m_BitmapPixels; + CButton m_DisableTXRect; + CButton m_RasterOutlines; + CButton m_EmulPow2; + CButton m_PolyAA; + CComboBox m_BackCull; + CComboBox m_DrawNormals; + CComboBox m_Wireframe; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptRender3D) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptRender3D) + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +class COptStream : public CDialog +{ +// Construction +public: + COptStream(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptStream) + enum { IDD = IDD_OPT_STREAM }; + CEdit m_RebufferLen; + CButton m_Rebuffer; + CEdit m_Buffer; + CEdit m_Timeout; + CButton m_Reorder; + CButton m_UseRTSP; + CComboBox m_Port; + //}}AFX_DATA + + + void SaveOptions(); + + void CheckRebuffer(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptStream) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptStream) + virtual BOOL OnInitDialog(); + afx_msg void OnSelchangePort(); + afx_msg void OnRtsp(); + afx_msg void OnRebuffer(); + afx_msg void OnUpdateRebufferLen(); + afx_msg void OnUpdateBuffer(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +class COptSystems : public CDialog +{ +// Construction +public: + COptSystems(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptSystems) + enum { IDD = IDD_OPT_SYSTEMS }; + CButton m_ForceDuration; + CComboBox m_Threading; + CButton m_LateFramesAlwaysDrawn; + CComboBox m_Lang; + //}}AFX_DATA + + void SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptSystems) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptSystems) + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +class COptVideo : public CDialog +{ +// Construction +public: + COptVideo(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptVideo) + enum { IDD = IDD_OPT_VIDEO }; + CButton m_SwitchRes; + CButton m_UseHWMemory; + CComboBox m_Videos; + //}}AFX_DATA + + + void SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptVideo) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptVideo) + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// +// COptLogs dialog + +class COptLogs : public CDialog +{ +// Construction +public: + COptLogs(CWnd* pParent = NULL); // standard constructor + void SaveOptions(); + +// Dialog Data + //{{AFX_DATA(COptLogs) + enum { IDD = IDD_OPT_LOGS }; + CButton m_sync; + CButton m_script; + CButton m_scene; + CButton m_rtp; + CButton m_render; + CButton m_parser; + CButton m_net; + CButton m_mmio; + CButton m_media; + CButton m_core; + CButton m_container; + CButton m_compose; + CButton m_coding; + CButton m_codec; + CButton m_author; + CComboBox m_Level; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptLogs) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptLogs) + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + + +///////////////////////////////////////////////////////////////////////////// +// COptions dialog + +class COptions : public CDialog +{ +// Construction +public: + COptions(CWnd* pParent = NULL); // standard constructor + BOOL Create(CWnd * pParent) + { + return CDialog::Create( COptions::IDD, pParent); + } + +// Dialog Data + //{{AFX_DATA(COptions) + enum { IDD = IDD_OPTIONS }; + CComboBox m_Selector; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptions) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + COptGen m_general; + COptSystems m_systems; + COptRender m_render; + COptRender2D m_render2d; + COptRender3D m_render3d; + COptAudio m_audio; + OptDecoder m_decoder; + COptVideo m_video; + COptHTTP m_http; + COptFont m_font; + COptStream m_stream; + COptMCache m_cache; + OptFiles m_files; + COptLogs m_logs; + + void HideAll(); + + // Generated message map functions + //{{AFX_MSG(COptions) + virtual BOOL OnInitDialog(); + afx_msg void OnSaveopt(); + afx_msg void OnClose(); + afx_msg void OnDestroy(); + afx_msg void OnSelchangeSelect(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_OPTIONS_H__5C839953_58C0_4D9D_89CE_2820C7686C1B__INCLUDED_) diff --git a/applications/deprecated/old_arch/osmo4_w32/Osmo4.cpp b/applications/deprecated/old_arch/osmo4_w32/Osmo4.cpp new file mode 100644 index 0000000..2446c12 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/Osmo4.cpp @@ -0,0 +1,944 @@ +// GPAC.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "Osmo4.h" +#include +#include +#include "MainFrm.h" +#include "OpenUrl.h" +#include "resource.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// Osmo4 + +BEGIN_MESSAGE_MAP(Osmo4, CWinApp) + //{{AFX_MSG_MAP(Osmo4) + ON_COMMAND(ID_OPEN_FILE, OnOpenFile) + ON_COMMAND(ID_FILE_STEP, OnFileStep) + ON_COMMAND(ID_OPEN_URL, OnOpenUrl) + ON_COMMAND(ID_FILE_RELOAD, OnFileReload) + ON_COMMAND(ID_CONFIG_RELOAD, OnConfigReload) + ON_COMMAND(ID_FILE_PLAY, OnFilePlay) + ON_UPDATE_COMMAND_UI(ID_FILE_PLAY, OnUpdateFilePlay) + ON_UPDATE_COMMAND_UI(ID_FILE_STEP, OnUpdateFileStep) + ON_COMMAND(ID_FILE_STOP, OnFileStop) + ON_UPDATE_COMMAND_UI(ID_FILE_STOP, OnUpdateFileStop) + ON_COMMAND(ID_SWITCH_RENDER, OnSwitchRender) + ON_UPDATE_COMMAND_UI(ID_FILE_RELOAD, OnUpdateFileStop) + ON_COMMAND(ID_H_ABOUT, OnAbout) + ON_COMMAND(ID_FILE_MIGRATE, OnFileMigrate) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// Osmo4 construction + +Osmo4::Osmo4() +{ +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only Osmo4 object + +Osmo4 theApp; + + + +class UserPassDialog : public CDialog +{ +// Construction +public: + UserPassDialog(CWnd* pParent = NULL); // standard constructor + + Bool GetPassword(const char *site_url, char *user, char *password); + +// Dialog Data + //{{AFX_DATA(UserPassDialog) + enum { IDD = IDD_PASSWD }; + CStatic m_SiteURL; + CEdit m_User; + CEdit m_Pass; + //}}AFX_DATA + + void SetSelection(u32 sel); + char cur_ext[200], cur_mime[200]; + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(UserPassDialog) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + const char *m_site_url; + char *m_user, *m_password; + + // Generated message map functions + //{{AFX_MSG(UserPassDialog) + virtual BOOL OnInitDialog(); + afx_msg void OnClose(); + //}}AFX_MSG +}; + +UserPassDialog::UserPassDialog(CWnd* pParent /*=NULL*/) + : CDialog(UserPassDialog::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptStream) + //}}AFX_DATA_INIT +} + +void UserPassDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(UserPassDialog) + DDX_Control(pDX, IDC_TXT_SITE, m_SiteURL); + DDX_Control(pDX, IDC_EDIT_USER, m_User); + DDX_Control(pDX, IDC_EDIT_PASSWORD, m_Pass); + //}}AFX_DATA_MAP +} + +BOOL UserPassDialog::OnInitDialog() +{ + CDialog::OnInitDialog(); + m_SiteURL.SetWindowText(m_site_url); + m_User.SetWindowText(m_user); + m_Pass.SetWindowText(""); + return TRUE; +} + +void UserPassDialog::OnClose() +{ + m_User.GetWindowText(m_user, 50); + m_Pass.GetWindowText(m_password, 50); +} + +Bool UserPassDialog::GetPassword(const char *site_url, char *user, char *password) +{ + m_site_url = site_url; + m_user = user; + if (DoModal() != IDOK) return GF_FALSE; + return GF_TRUE; +} + + +static void Osmo4_progress_cbk(const void *usr, const char *title, u64 done, u64 total) +{ + if (!total) return; + CMainFrame *pFrame = (CMainFrame *) ((Osmo4 *) usr)->m_pMainWnd; + s32 prog = (s32) ( (100 * (u64)done) / total); + if (pFrame->m_last_prog < prog) { + pFrame->console_err = GF_OK; + pFrame->m_last_prog = prog; + pFrame->console_message.Format("%s %02d %%", title, prog); + pFrame->PostMessage(WM_CONSOLEMSG, 0, 0); + if (done==total) pFrame->m_last_prog = -1; + } +} + +#define W32_MIN_WIDTH 120 + +static void log_msg(char *msg) +{ + ::MessageBox(NULL, msg, "GPAC", MB_OK); +} +Bool Osmo4_EventProc(void *priv, GF_Event *evt) +{ + u32 dur; + Osmo4 *gpac = (Osmo4 *) priv; + CMainFrame *pFrame = (CMainFrame *) gpac->m_pMainWnd; + /*shutdown*/ + if (!pFrame) return GF_FALSE; + + switch (evt->type) { + case GF_EVENT_DURATION: + dur = (u32) (1000 * evt->duration.duration); + //if (dur<1100) dur = 0; + pFrame->m_pPlayList->SetDuration((u32) evt->duration.duration ); + gpac->max_duration = dur; + gpac->can_seek = evt->duration.can_seek; + if (!gpac->can_seek) { + pFrame->m_Sliders.m_PosSlider.EnableWindow(FALSE); + } else { + pFrame->m_Sliders.m_PosSlider.EnableWindow(TRUE); + pFrame->m_Sliders.m_PosSlider.SetRangeMin(0); + pFrame->m_Sliders.m_PosSlider.SetRangeMax(dur); + } + break; + + case GF_EVENT_MESSAGE: + if (!evt->message.service || !strcmp(evt->message.service, (LPCSTR) pFrame->m_pPlayList->GetURL() )) { + pFrame->console_service = "main service"; + } else { + pFrame->console_service = evt->message.service; + } + if (evt->message.error!=GF_OK) { + if (evt->message.errorm_NoConsole) { + pFrame->console_err = evt->message.error; + pFrame->console_message = evt->message.message; + gpac->m_pMainWnd->PostMessage(WM_CONSOLEMSG, 0, 0); + + /*any error before connection confirm is a service connection error*/ + if (!gpac->m_isopen) pFrame->m_pPlayList->SetDead(); + } + return GF_FALSE; + } + if (gpac->m_NoConsole) return GF_FALSE; + + /*process user message*/ + pFrame->console_err = GF_OK; + pFrame->console_message = evt->message.message; + gpac->m_pMainWnd->PostMessage(WM_CONSOLEMSG, 0, 0); + break; + case GF_EVENT_PROGRESS: + char *szType; + if (evt->progress.progress_type==0) szType = "Buffer "; + else if (evt->progress.progress_type==1) szType = "Download "; + else if (evt->progress.progress_type==2) szType = "Import "; + gf_set_progress(szType, evt->progress.done, evt->progress.total); + break; + case GF_EVENT_NAVIGATE_INFO: + pFrame->console_message = evt->navigate.to_url; + gpac->m_pMainWnd->PostMessage(WM_CONSOLEMSG, 1000, 0); + break; + + case GF_EVENT_SCENE_SIZE: + if (evt->size.width && evt->size.height) { + gpac->orig_width = evt->size.width; + gpac->orig_height = evt->size.height; + if (gpac->m_term && !pFrame->m_bFullScreen) + pFrame->PostMessage(WM_SETSIZE, evt->size.width, evt->size.height); + } + break; + /*don't resize on win32 msg notif*/ +#if 0 + case GF_EVENT_SIZE: + if (/*gpac->m_term && !pFrame->m_bFullScreen && */gpac->orig_width && (evt->size.width < W32_MIN_WIDTH) ) + pFrame->PostMessage(WM_SETSIZE, W32_MIN_WIDTH, (W32_MIN_WIDTH*gpac->orig_height) / gpac->orig_width); + else + pFrame->PostMessage(WM_SETSIZE, evt->size.width, evt->size.height); + break; +#endif + + case GF_EVENT_CONNECT: +// if (pFrame->m_bStartupFile) return 0; + + pFrame->BuildStreamList(GF_TRUE); + if (evt->connect.is_connected) { + pFrame->BuildChapterList(GF_FALSE); + gpac->m_isopen = GF_TRUE; + //resetting sliders when opening a new file creates a deadlock on the window thread which is disconnecting + pFrame->m_wndToolBar.SetButtonInfo(5, ID_FILE_PLAY, TBBS_BUTTON, gpac->m_isopen ? 4 : 3); + pFrame->m_Sliders.m_PosSlider.SetPos(0); + pFrame->SetProgTimer(GF_TRUE); + } else { + gpac->max_duration = 0; + gpac->m_isopen = GF_FALSE; + pFrame->BuildChapterList(GF_TRUE); + } + if (!pFrame->m_bFullScreen) { + pFrame->SetFocus(); + pFrame->SetForegroundWindow(); + } + break; + + case GF_EVENT_QUIT: + pFrame->PostMessage(WM_CLOSE, 0L, 0L); + break; + case GF_EVENT_MIGRATE: + { + } + break; + case GF_EVENT_KEYDOWN: + gf_term_process_shortcut(gpac->m_term, evt); + /*update volume control*/ + pFrame->m_Sliders.SetVolume(); + + switch (evt->key.key_code) { + case GF_KEY_HOME: + gf_term_set_option(gpac->m_term, GF_OPT_NAVIGATION_TYPE, 1); + break; + case GF_KEY_ESCAPE: + pFrame->PostMessage(WM_COMMAND, ID_VIEW_FULLSCREEN); + break; + case GF_KEY_MEDIANEXTTRACK: + pFrame->m_pPlayList->PlayNext(); + break; + case GF_KEY_MEDIAPREVIOUSTRACK: + pFrame->m_pPlayList->PlayPrev(); + break; + case GF_KEY_H: + if ((evt->key.flags & GF_KEY_MOD_CTRL) && gpac->m_isopen) + gf_term_switch_quality(gpac->m_term, GF_TRUE); + break; + case GF_KEY_L: + if ((evt->key.flags & GF_KEY_MOD_CTRL) && gpac->m_isopen) + gf_term_switch_quality(gpac->m_term, GF_FALSE); + break; + case GF_KEY_LEFT: + case GF_KEY_RIGHT: + if (gpac->m_isopen && (gf_term_get_option(gpac->m_term, GF_OPT_NAVIGATION) == GF_NAVIGATE_NONE)) { + if (evt->key.flags & GF_KEY_MOD_CTRL) { + if (evt->key.key_code==GF_KEY_LEFT) pFrame->m_pPlayList->PlayPrev(); + else if (evt->key.key_code==GF_KEY_RIGHT) pFrame->m_pPlayList->PlayNext(); + } + else if (gpac->can_seek && (evt->key.flags & GF_KEY_MOD_ALT)) { + u32 duration = gpac->max_duration; + s32 current_time = gf_term_get_time_in_ms(gpac->m_term); + + if (evt->key.key_code==GF_KEY_LEFT) { + current_time -= 5*duration/100; + if (current_time<0) current_time=0; + gf_term_play_from_time(gpac->m_term, (u64) current_time, 0); + } + else if (evt->key.key_code==GF_KEY_RIGHT) { + current_time += 5*duration/100; + if ((u32) current_time < duration) { + gf_term_play_from_time(gpac->m_term, (u64) current_time, 0); + } + } + } + } + break; + } + break; + case GF_EVENT_NAVIGATE: + /*fixme - a proper browser would require checking mime type & co*/ + /*store URL since it may be destroyed, and post message*/ + gpac->m_navigate_url = evt->navigate.to_url; + pFrame->PostMessage(WM_NAVIGATE, NULL, NULL); + return GF_TRUE; + case GF_EVENT_VIEWPOINTS: + pFrame->BuildViewList(); + return GF_FALSE; + case GF_EVENT_STREAMLIST: + pFrame->BuildStreamList(GF_FALSE); + return GF_FALSE; + case GF_EVENT_SET_CAPTION: + pFrame->SetWindowText(evt->caption.caption); + break; + case GF_EVENT_DBLCLICK: + pFrame->PostMessage(WM_COMMAND, ID_VIEW_FULLSCREEN); + return GF_FALSE; + case GF_EVENT_AUTHORIZATION: + { + UserPassDialog passdlg; + return passdlg.GetPassword(evt->auth.site_url, evt->auth.user, evt->auth.password); + } + } + return GF_FALSE; +} + + +/*here's the trick: use a storage section shared among all processes for the wnd handle and for the command line +NOTE: this has to be static memory of course, don't try to alloc anything there...*/ +#pragma comment(linker, "/SECTION:.shr,RWS") +#pragma data_seg(".shr") +HWND static_gpac_hwnd = NULL; +char static_szCmdLine[MAX_PATH] = ""; +#pragma data_seg() + +const char *static_gpac_get_url() +{ + return (const char *) static_szCmdLine; +} + +static void osmo4_do_log(void *cbk, GF_LOG_Level level, GF_LOG_Tool tool, const char *fmt, va_list list) +{ + FILE *logs = (FILE *) cbk; + vfprintf(logs, fmt, list); + fflush(logs); +} + +BOOL Osmo4::InitInstance() +{ + CCommandLineInfo cmdInfo; + + afxAmbientActCtx = FALSE; + + m_logs = NULL; + + m_term = NULL; + + memset(&m_user, 0, sizeof(GF_User)); + + /*get Osmo4.exe path*/ + strcpy((char *) szApplicationPath, AfxGetApp()->m_pszHelpFilePath); + while (szApplicationPath[strlen((char *) szApplicationPath)-1] != '\\') szApplicationPath[strlen((char *) szApplicationPath)-1] = 0; + if (szApplicationPath[strlen((char *) szApplicationPath)-1] != '\\') strcat(szApplicationPath, "\\"); + + gf_sys_init(GF_MemTrackerNone); + + /*setup user*/ + memset(&m_user, 0, sizeof(GF_User)); + + Bool first_launch = GF_FALSE; + /*init config and modules*/ + m_user.config = gf_cfg_init(NULL, &first_launch); + if (!m_user.config) { + MessageBox(NULL, "GPAC Configuration file not found", "Fatal Error", MB_OK); + m_pMainWnd->PostMessage(WM_CLOSE); + } + + char *name = gf_cfg_get_filename(m_user.config); + char *sep = strrchr(name, '\\'); + if (sep) sep[0] = 0; + strcpy(szUserPath, name); + if (sep) sep[0] = '\\'; + + const char *opt = gf_cfg_get_key(m_user.config, "General", "SingleInstance"); + m_SingleInstance = (opt && !stricmp(opt, "yes")) ? GF_TRUE : GF_FALSE; + + m_hMutex = NULL; + if (m_SingleInstance) { + m_hMutex = CreateMutex(NULL, FALSE, "Osmo4_GPAC_INSTANCE"); + if ( GetLastError() == ERROR_ALREADY_EXISTS ) { + char szDIR[1024]; + if (m_hMutex) CloseHandle(m_hMutex); + m_hMutex = NULL; + + if (!static_gpac_hwnd || !IsWindow(static_gpac_hwnd) ) { + ::MessageBox(NULL, "Osmo4 ghost process detected", "Error at last shutdown" , MB_OK); + } else { + ::SetForegroundWindow(static_gpac_hwnd); + + if (m_lpCmdLine && strlen(m_lpCmdLine)) { + DWORD_PTR res; + size_t len; + char *the_url, *cmd; + GetCurrentDirectory(1024, szDIR); + if (szDIR[strlen(szDIR)-1] != '\\') strcat(szDIR, "\\"); + cmd = (char *)(const char *) m_lpCmdLine; + strcpy(static_szCmdLine, ""); + if (cmd[0]=='"') cmd+=1; + + if (!strnicmp(cmd, "-queue ", 7)) { + strcat(static_szCmdLine, "-queue "); + cmd += 7; + } + the_url = gf_url_concatenate(szDIR, cmd); + if (!the_url) { + strcat(static_szCmdLine, cmd); + } else { + strcat(static_szCmdLine, the_url); + gf_free(the_url); + } + while ( (len = strlen(static_szCmdLine)) ) { + char s = static_szCmdLine[len-1]; + if ((s==' ') || (s=='"')) static_szCmdLine[len-1]=0; + else break; + } + ::SendMessageTimeout(static_gpac_hwnd, WM_NEWINSTANCE, 0, 0, 0, 1000, &res); + } + } + + return FALSE; + } + } + +#if 0 + // Standard initialization +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif + +#endif + + SetRegistryKey(_T("GPAC")); + CMainFrame* pFrame = new CMainFrame; + m_pMainWnd = pFrame; + pFrame->LoadFrame(IDR_MAINFRAME, WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL, NULL); + pFrame->LoadAccelTable( MAKEINTRESOURCE(IDR_MAINACCEL)); + + m_pMainWnd->DragAcceptFiles(); + + if (m_SingleInstance) static_gpac_hwnd = m_pMainWnd->m_hWnd; + + m_user.modules = gf_modules_new(NULL, m_user.config); + if (!m_user.modules || ! gf_modules_get_count(m_user.modules) ) { + MessageBox(NULL, "No modules available - system cannot work", "Fatal Error", MB_OK); + m_pMainWnd->PostMessage(WM_CLOSE); + } + else if (first_launch) { + /*first launch, register all files ext*/ + u32 i; + for (i=0; iCanHandleURL(ifce, "test.test"); + gf_modules_close_interface((GF_BaseInterface *)ifce); + } + } + /*set some shortcuts*/ + gf_cfg_set_key(m_user.config, "Shortcuts", "VolumeUp", "ctrl+Up"); + gf_cfg_set_key(m_user.config, "Shortcuts", "VolumeDown", "ctrl+Down"); + gf_cfg_set_key(m_user.config, "Shortcuts", "FastRewind", "ctrl+Left"); + gf_cfg_set_key(m_user.config, "Shortcuts", "FastForward", "ctrl+Right"); + gf_cfg_set_key(m_user.config, "Shortcuts", "Play", "ctrl+ "); + } + + /*check log file*/ + const char *str = gf_cfg_get_key(m_user.config, "General", "LogFile"); + if (str) { + m_logs = gf_fopen(str, "wt"); + gf_log_set_callback(m_logs, osmo4_do_log); + } + else m_logs = NULL; + + /*set log level*/ + if (gf_log_set_tools_levels(gf_cfg_get_key(m_user.config, "General", "Logs")) != GF_OK) + fprintf(stdout, "osmo4: invalid log level specified\n"); + + m_user.opaque = this; + m_user.os_window_handler = pFrame->m_pWndView->m_hWnd; + m_user.EventProc = Osmo4_EventProc; + + m_reset = GF_FALSE; + orig_width = 320; + orig_height = 240; + + gf_set_progress_callback(this, Osmo4_progress_cbk); + + m_term = gf_term_new(&m_user); + if (! m_term) { + MessageBox(NULL, "Cannot load GPAC Terminal", "Fatal Error", MB_OK); + m_pMainWnd->PostMessage(WM_CLOSE); + return TRUE; + } + SetOptions(); + UpdateRenderSwitch(); + + pFrame->SendMessage(WM_SETSIZE, orig_width, orig_height); + pFrame->m_Address.ReloadURLs(); + + pFrame->m_Sliders.SetVolume(); + + m_reconnect_time = 0; + + + ParseCommandLine(cmdInfo); + + start_mode = 0; + + if (! cmdInfo.m_strFileName.IsEmpty()) { + pFrame->m_pPlayList->QueueURL(cmdInfo.m_strFileName); + pFrame->m_pPlayList->RefreshList(); + pFrame->m_pPlayList->PlayNext(); + } else { + char sPL[MAX_PATH]; + strcpy((char *) sPL, szUserPath); + strcat(sPL, "gpac_pl.m3u"); + pFrame->m_pPlayList->OpenPlayList(sPL); + const char *sOpt = gf_cfg_get_key(GetApp()->m_user.config, "General", "PLEntry"); + if (sOpt) { + s32 count = (s32)gf_list_count(pFrame->m_pPlayList->m_entries); + pFrame->m_pPlayList->m_cur_entry = atoi(sOpt); + if (pFrame->m_pPlayList->m_cur_entry>=count) + pFrame->m_pPlayList->m_cur_entry = count-1; + } else { + pFrame->m_pPlayList->m_cur_entry = -1; + } +#if 0 + if (pFrame->m_pPlayList->m_cur_entry>=0) { + start_mode = 1; + pFrame->m_pPlayList->Play(); + } +#endif + + sOpt = gf_cfg_get_key(m_user.config, "General", "StartupFile"); + if (sOpt && !strstr(sOpt, "gui") ) gf_term_connect(m_term, sOpt); + + sOpt = gf_cfg_get_key(m_user.config, "General", "PlaylistLoop"); + m_Loop = (sOpt && !strcmp(sOpt, "yes")) ? GF_TRUE : GF_FALSE; + } + pFrame->SetFocus(); + pFrame->SetForegroundWindow(); + return TRUE; +} + +int Osmo4::ExitInstance() +{ + if (m_term) gf_term_del(m_term); + if (m_user.modules) gf_modules_del(m_user.modules); + if (m_user.config) gf_cfg_del(m_user.config); + gf_sys_close(); + /*last instance*/ + if (m_hMutex) { + CloseHandle(m_hMutex); + static_gpac_hwnd = NULL; + } + if (m_logs) gf_fclose(m_logs); + return CWinApp::ExitInstance(); +} + + + +///////////////////////////////////////////////////////////////////////////// +// Osmo4 message handlers + + + + + +///////////////////////////////////////////////////////////////////////////// +// CAboutDlg dialog used for App About + +class CAboutDlg : public CDialog +{ +public: + CAboutDlg(); + +// Dialog Data + //{{AFX_DATA(CAboutDlg) + enum { IDD = IDD_ABOUTBOX }; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CAboutDlg) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + //{{AFX_MSG(CAboutDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnGogpac(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) +{ + //{{AFX_DATA_INIT(CAboutDlg) + //}}AFX_DATA_INIT +} + +void CAboutDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CAboutDlg) + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) + //{{AFX_MSG_MAP(CAboutDlg) + ON_BN_CLICKED(IDC_GOGPAC, OnGogpac) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +void Osmo4::OnAbout() +{ + CAboutDlg aboutDlg; + aboutDlg.DoModal(); +} + +BOOL CAboutDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + CString str = "GPAC/Osmo4 - version " GPAC_FULL_VERSION; + SetWindowText(str); + return TRUE; +} + +void CAboutDlg::OnGogpac() +{ + ShellExecute(NULL, "open", "http://gpac.io", NULL, NULL, SW_SHOWNORMAL); +} + +///////////////////////////////////////////////////////////////////////////// +// Osmo4 message handlers + + +void Osmo4::SetOptions() +{ + const char *sOpt = gf_cfg_get_key(m_user.config, "General", "Loop"); + m_Loop = (sOpt && !stricmp(sOpt, "yes")) ? GF_TRUE : GF_FALSE; + sOpt = gf_cfg_get_key(m_user.config, "General", "LookForSubtitles"); + m_LookForSubtitles = (sOpt && !stricmp(sOpt, "yes")) ? GF_TRUE : GF_FALSE; + sOpt = gf_cfg_get_key(m_user.config, "General", "ConsoleOff"); + m_NoConsole = (sOpt && !stricmp(sOpt, "yes")) ? GF_TRUE : GF_FALSE; + sOpt = gf_cfg_get_key(m_user.config, "General", "ViewXMT"); + m_ViewXMTA = (sOpt && !stricmp(sOpt, "yes")) ? GF_TRUE : GF_FALSE; + sOpt = gf_cfg_get_key(m_user.config, "General", "NoMIMETypeFetch"); + m_NoMimeFetch = (!sOpt || !stricmp(sOpt, "yes")) ? GF_TRUE : GF_FALSE; +} + + +void Osmo4::OnOpenUrl() +{ + COpenUrl url; + if (url.DoModal() != IDOK) return; + + CMainFrame *pFrame = (CMainFrame *) m_pMainWnd; + pFrame->m_pPlayList->Truncate(); + pFrame->m_pPlayList->QueueURL(url.m_url); + pFrame->m_pPlayList->RefreshList(); + pFrame->m_pPlayList->PlayNext(); +} + + +CString Osmo4::GetFileFilter() +{ + u32 keyCount, i; + CString sFiles; + CString sExts; + CString supportedFiles; + + /*force MP4 and 3GP files at beginning to make sure they are selected (Win32 bug with too large filters)*/ + supportedFiles = "All Known Files|*.m3u;*.pls;*.mp4;*.3gp;*.3g2"; + + sExts = ""; + sFiles = ""; + keyCount = gf_cfg_get_key_count(m_user.config, "MimeTypes"); + for (i=0; i=0) continue; + /*if same extensions for # mime types skip (don't polluate the file list)*/ + if (sExts.Find(szKeyList)>=0) continue; + + sExts += szKeyList; + sExts += " "; + sFiles += sDesc; + sFiles += "|"; + + first = GF_TRUE; + + sOpt = CString(szKeyList); + while (1) { + + int pos = sOpt.Find(' '); + CString ext = (pos==-1) ? sOpt : sOpt.Left(pos); + /*WATCHOUT: we do have some "double" ext , eg .wrl.gz - these are NOT supported by windows*/ + if (ext.Find(".")<0) { + if (!first) { + sFiles += ";"; + } else { + first = GF_FALSE; + } + sFiles += "*."; + sFiles += ext; + + CString sext = ext; + sext += ";"; + if (supportedFiles.Find(sext)<0) { + supportedFiles += ";*."; + supportedFiles += ext; + } + } + + if (sOpt==ext) break; + CString rem; + rem.Format("%s ", (LPCTSTR) ext); + sOpt.Replace((LPCTSTR) rem, ""); + } + sFiles += "|"; + } + supportedFiles += "|"; + supportedFiles += sFiles; + supportedFiles += "M3U Playlists|*.m3u|ShoutCast Playlists|*.pls|All Files |*.*|"; + return supportedFiles; +} + +void Osmo4::OnOpenFile() +{ + CString sFiles = GetFileFilter(); + u32 nb_items; + + /*looks like there's a bug here, main filter isn't used correctly while the others are*/ + CFileDialog fd(TRUE,NULL,NULL, OFN_ALLOWMULTISELECT | OFN_HIDEREADONLY | OFN_FILEMUSTEXIST , sFiles); + fd.m_ofn.nMaxFile = 25000; + fd.m_ofn.lpstrFile = (char *) gf_malloc(sizeof(char) * fd.m_ofn.nMaxFile); + fd.m_ofn.lpstrFile[0] = 0; + + if (fd.DoModal()!=IDOK) { + gf_free(fd.m_ofn.lpstrFile); + return; + } + + CMainFrame *pFrame = (CMainFrame *) m_pMainWnd; + + nb_items = 0; + POSITION pos = fd.GetStartPosition(); + while (pos) { + CString file = fd.GetNextPathName(pos); + nb_items++; + } + /*if several items, act as playlist (replace playlist), otherwise as browser (lost all "next" context)*/ + if (nb_items==1) + pFrame->m_pPlayList->Truncate(); + else + pFrame->m_pPlayList->Clear(); + + pos = fd.GetStartPosition(); + while (pos) { + CString file = fd.GetNextPathName(pos); + pFrame->m_pPlayList->QueueURL(file); + } + gf_free(fd.m_ofn.lpstrFile); + pFrame->m_pPlayList->RefreshList(); + pFrame->m_pPlayList->PlayNext(); +} + + +void Osmo4::Pause() +{ + if (!m_isopen) return; + gf_term_set_option(m_term, GF_OPT_PLAY_STATE, (gf_term_get_option(m_term, GF_OPT_PLAY_STATE)==GF_STATE_PLAYING) ? GF_STATE_PAUSED : GF_STATE_PLAYING); +} + +void Osmo4::OnMainPause() +{ + Pause(); +} + +void Osmo4::OnFileStep() +{ + gf_term_set_option(m_term, GF_OPT_PLAY_STATE, GF_STATE_STEP_PAUSE); + ((CMainFrame *) m_pMainWnd)->m_wndToolBar.SetButtonInfo(5, ID_FILE_PLAY, TBBS_BUTTON, 3); +} +void Osmo4::OnUpdateFileStep(CCmdUI* pCmdUI) +{ + pCmdUI->Enable(m_isopen && !m_reset); +} + +void Osmo4::PlayFromTime(u32 time) +{ + Bool do_pause; + if (start_mode==1) do_pause = GF_TRUE; + else if (start_mode==2) do_pause = GF_FALSE; + else do_pause = /*!m_AutoPlay*/GF_FALSE; + gf_term_play_from_time(m_term, time, do_pause); + m_reset = GF_FALSE; +} + + +void Osmo4::OnFileReload() +{ + gf_term_disconnect(m_term); + m_pMainWnd->PostMessage(WM_OPENURL); +} + +void Osmo4::OnFileMigrate() +{ +} + +void Osmo4::OnConfigReload() +{ + gf_term_set_option(m_term, GF_OPT_RELOAD_CONFIG, 1); +} + +void Osmo4::UpdatePlayButton(Bool force_play) +{ + if (!force_play && gf_term_get_option(m_term, GF_OPT_PLAY_STATE)==GF_STATE_PLAYING) { + ((CMainFrame *) m_pMainWnd)->m_wndToolBar.SetButtonInfo(5, ID_FILE_PLAY, TBBS_BUTTON, 4); + } else { + ((CMainFrame *) m_pMainWnd)->m_wndToolBar.SetButtonInfo(5, ID_FILE_PLAY, TBBS_BUTTON, 3); + } +} + +void Osmo4::OnFilePlay() +{ + if (m_isopen) { + if (m_reset) { + m_reset = GF_FALSE; + PlayFromTime(0); + ((CMainFrame *)m_pMainWnd)->SetProgTimer(GF_TRUE); + } else { + Pause(); + } + UpdatePlayButton(); + } else { + ((CMainFrame *) m_pMainWnd)->m_pPlayList->Play(); + } +} + +void Osmo4::OnUpdateFilePlay(CCmdUI* pCmdUI) +{ + if (m_isopen) { + pCmdUI->Enable(TRUE); + if (pCmdUI->m_nID==ID_FILE_PLAY) { + if (!m_isopen) { + pCmdUI->SetText("Play/Pause\tCtrl+P"); + } else if (gf_term_get_option(m_term, GF_OPT_PLAY_STATE)==GF_STATE_PLAYING) { + pCmdUI->SetText("Pause\tCtrl+P"); + } else { + pCmdUI->SetText("Resume\tCtrl+P"); + } + } + } else { + pCmdUI->Enable(((CMainFrame *)m_pMainWnd)->m_pPlayList->HasValidEntries() ); + pCmdUI->SetText("Play\tCtrl+P"); + } +} + +void Osmo4::OnFileStop() +{ + CMainFrame *pFrame = (CMainFrame *) m_pMainWnd; + if (m_reset) return; + if (gf_term_get_option(m_term, GF_OPT_PLAY_STATE)==GF_STATE_PLAYING) Pause(); + m_reset = GF_TRUE; + pFrame->m_Sliders.m_PosSlider.SetPos(0); + pFrame->SetProgTimer(GF_FALSE); + pFrame->m_wndToolBar.SetButtonInfo(5, ID_FILE_PLAY, TBBS_BUTTON, 3); + start_mode = 2; +} + +void Osmo4::OnUpdateFileStop(CCmdUI* pCmdUI) +{ +// pCmdUI->Enable(m_isopen); +} + +void Osmo4::OnSwitchRender() +{ + const char *opt = gf_cfg_get_key(m_user.config, "Compositor", "OpenGLMode"); + Bool use_gl = (opt && !stricmp(opt, "always")) ? GF_TRUE : GF_FALSE; + gf_cfg_set_key(m_user.config, "Compositor", "OpenGLMode", use_gl ? "disable" : "always"); + + gf_term_set_option(m_term, GF_OPT_USE_OPENGL, !use_gl); + + UpdateRenderSwitch(); +} + +void Osmo4::UpdateRenderSwitch() +{ + const char *opt = gf_cfg_get_key(m_user.config, "Compositor", "OpenGLMode"); + if (opt && !stricmp(opt, "disable")) + ((CMainFrame *) m_pMainWnd)->m_wndToolBar.SetButtonInfo(12, ID_SWITCH_RENDER, TBBS_BUTTON, 10); + else + ((CMainFrame *) m_pMainWnd)->m_wndToolBar.SetButtonInfo(12, ID_SWITCH_RENDER, TBBS_BUTTON, 9); +} diff --git a/applications/deprecated/old_arch/osmo4_w32/Osmo4.h b/applications/deprecated/old_arch/osmo4_w32/Osmo4.h new file mode 100644 index 0000000..cfc3c16 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/Osmo4.h @@ -0,0 +1,115 @@ +// GPAC.h : main header file for the GPAC application +// + +#if !defined(AFX_GPAC_H__8B06A368_E142_47E3_ABE7_0B459FC0E853__INCLUDED_) +#define AFX_GPAC_H__8B06A368_E142_47E3_ABE7_0B459FC0E853__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ +#error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// Osmo4: +// See GPAC.cpp for the implementation of this class +// + + +/*GPAC terminal*/ +#include +/*GPAC terminal info (OD browsing)*/ +#include + +enum { + WM_SCENE_DONE = WM_USER + 1, + WM_NAVIGATE, + WM_SETSIZE, + WM_OPENURL, + WM_RESTARTURL, + WM_CONSOLEMSG, + WM_NEWINSTANCE, +}; + +const char *static_gpac_get_url(); + +class Osmo4 : public CWinApp +{ +public: + Osmo4(); + + GF_Terminal *m_term; + GF_User m_user; + + Bool m_isopen, m_reset; + u32 max_duration; + Bool can_seek; + u32 orig_width,orig_height, m_reconnect_time; + + CString m_navigate_url; + void Pause(); + void PlayFromTime(u32 time); + + void SetOptions(); + void UpdateRenderSwitch(); + void UpdatePlayButton(Bool force_play = GF_FALSE); + + /*general options*/ + Bool m_Loop, m_LookForSubtitles, m_NoConsole, m_ViewXMTA, m_SingleInstance, m_NoMimeFetch; + u32 start_mode; + + CString GetFileFilter(); + + char szApplicationPath[GF_MAX_PATH]; + char szUserPath[GF_MAX_PATH]; + + FILE *m_logs; + + HANDLE m_hMutex; + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(Osmo4) +public: + virtual BOOL InitInstance(); + virtual int ExitInstance(); + //}}AFX_VIRTUAL + +// Implementation + +public: + //{{AFX_MSG(Osmo4) + afx_msg void OnOpenFile(); + afx_msg void OnMainPause(); + afx_msg void OnFileStep(); + afx_msg void OnOpenUrl(); + afx_msg void OnFileReload(); + afx_msg void OnFileMigrate(); + afx_msg void OnConfigReload(); + afx_msg void OnFilePlay(); + afx_msg void OnUpdateFilePlay(CCmdUI* pCmdUI); + afx_msg void OnUpdateFileStep(CCmdUI* pCmdUI); + afx_msg void OnFileStop(); + afx_msg void OnUpdateFileStop(CCmdUI* pCmdUI); + afx_msg void OnSwitchRender(); + afx_msg void OnAbout(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +inline Osmo4 *GetApp() { + return (Osmo4 *)AfxGetApp(); +} + + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_GPAC_H__8B06A368_E142_47E3_ABE7_0B459FC0E853__INCLUDED_) diff --git a/applications/deprecated/old_arch/osmo4_w32/Osmo4.rc b/applications/deprecated/old_arch/osmo4_w32/Osmo4.rc new file mode 100644 index 0000000..66a0ba5 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/Osmo4.rc @@ -0,0 +1,885 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_MAINFRAME MENU +BEGIN + POPUP "&File" + BEGIN + MENUITEM "&Open File\tCtrl+O", ID_OPEN_FILE + MENUITEM "Open &URL\tCtrl+U", ID_OPEN_URL + MENUITEM SEPARATOR + MENUITEM "File Propert&ies\tCtrl+I", ID_FILE_PROP + POPUP "Streaming Cache" + BEGIN + MENUITEM "&Enable", ID_REC_ENABLE + MENUITEM "&Stop and Save", ID_REC_STOP + MENUITEM "&Abort", ID_REC_ABORT + END + MENUITEM SEPARATOR + MENUITEM "Copy", ID_FILE_COPY + MENUITEM "Paste", ID_FILE_PASTE + MENUITEM SEPARATOR + MENUITEM "Exit", ID_FILE_EXIT + END + POPUP "View" + BEGIN + POPUP "Viewpoint" + BEGIN + MENUITEM "", ID_VIEWPORT_EMPTY + END + POPUP "&Navigation" + BEGIN + MENUITEM "Headlight", ID_HEADLIGHT + MENUITEM SEPARATOR + MENUITEM "&None", ID_NAVIGATE_NONE + MENUITEM "&Walk", ID_NAVIGATE_WALK + MENUITEM "&Fly", ID_NAVIGATE_FLY + MENUITEM "&Examine", ID_NAVIGATE_EXAM + MENUITEM "&Pan", ID_NAVIGATE_PAN + MENUITEM "&Slide", ID_NAVIGATE_SLIDE + MENUITEM "&Orbit", ID_NAVIGATE_ORBIT + MENUITEM "&VR", ID_NAVIGATE_VR + MENUITEM "&Game", ID_NAVIGATE_GAME + MENUITEM SEPARATOR + POPUP "Collision" + BEGIN + MENUITEM "Off", ID_COLLIDE_NONE + MENUITEM "Regular", ID_COLLIDE_REG + MENUITEM "Displacement", ID_COLLIDE_DISP + END + MENUITEM "Gravity", ID_GRAVITY + MENUITEM SEPARATOR + MENUITEM "&Reset", ID_NAV_RESET + END + MENUITEM SEPARATOR + MENUITEM "&Fullscreen", ID_VIEW_FULLSCREEN + MENUITEM "Original &Aspect", ID_VIEW_ORIGINAL + POPUP "Aspect &Ratio" + BEGIN + MENUITEM "&Keep Original", ID_AR_KEEP + MENUITEM "&Fill Screen", ID_AR_FILL + MENUITEM "Ratio 4/3", ID_AR_43 + MENUITEM "Ratio 16/9", ID_AR_169 + END + MENUITEM SEPARATOR + MENUITEM "Resource Usage", ID_VIEW_CPU + MENUITEM SEPARATOR + MENUITEM "&Options", IDD_CONFIGURE + END + POPUP "Play" + BEGIN + POPUP "Stream &Selection" + BEGIN + POPUP "Audio" + BEGIN + MENUITEM "", ID_AUDIO_EMPTY + END + POPUP "Video" + BEGIN + MENUITEM "", ID_VIDEO_EMPTY + END + POPUP "Subtitle" + BEGIN + MENUITEM "", ID_SUBS_EMPTY + END + MENUITEM SEPARATOR + MENUITEM "Add Subtitle", ID_ADD_SUBTITLE + END + POPUP "&Chapters" + BEGIN + MENUITEM "", ID_SETCHAP_FIRST + END + MENUITEM SEPARATOR + MENUITEM "Playlist\tF3", ID_VIEW_PL, CHECKED + MENUITEM "&Loop Playlist", ID_PLAYLIST_LOOP + MENUITEM SEPARATOR + MENUITEM "Play/Pause\tCtrl+P", ID_FILE_PLAY + MENUITEM "Step-by-Step\tCtrl+S", ID_FILE_STEP + MENUITEM "Stop", ID_FILE_STOP + MENUITEM SEPARATOR + MENUITEM "Reload File\tF5", ID_FILE_RELOAD + MENUITEM SEPARATOR + MENUITEM "Clear History", ID_CLEAR_NAV + MENUITEM "Reload Config", ID_CONFIG_RELOAD + END + POPUP "?" + BEGIN + MENUITEM "Shortcut List", ID_SHORTCUTS + MENUITEM "Navigation Keys", ID_NAV_INFO + MENUITEM SEPARATOR + MENUITEM "&About ...", ID_H_ABOUT + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ABOUTBOX DIALOGEX 0, 0, 209, 137 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_CLIENTEDGE +CAPTION "Osmo4 / GPAC version X.X.X" +FONT 8, "MS Sans Serif", 0, 0, 0x1 +BEGIN + ICON IDR_MAINFRAME,IDC_STATIC,8,4,20,20 + CTEXT "Osmo4 Player - GPAC Multimedia Framework",IDC_STATIC,31,10,150,10,SS_NOPREFIX + CTEXT "(c) 2000-2013 Telecom ParisTech\nAll Rights Reserved",IDC_STATIC,4,64,201,18 + CTEXT "This program is free software and may be distributed according to the terms of the GNU Lesser General Public License",IDC_STATIC,4,26,200,18 + PUSHBUTTON "http://gpac.io",IDC_GOGPAC,43,47,121,13,BS_FLAT,WS_EX_STATICEDGE + GROUPBOX "With Many Thanks To:",IDC_STATIC,3,82,203,53 + CTEXT "The FreeType Project\nMozilla SpiderMonkey (JavaScript support)\n\nZLIB, the PNG Group, the I.J.G.\nFFMPEG, FAAD, XVID, MAD",IDC_STATIC,9,92,189,41 +END + +IDD_PASSWD DIALOG 0, 0, 134, 71 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Enter user name and password" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,4,56,50,12 + PUSHBUTTON "Cancel",IDCANCEL,80,56,50,12 + LTEXT "Static__________________________",IDC_TXT_SITE,25,4,105,8 + LTEXT "Login",IDC_STATIC,7,21,18,8 + LTEXT "Password",IDC_STATIC,7,37,32,8 + EDITTEXT IDC_EDIT_USER,47,18,55,14,ES_AUTOHSCROLL + EDITTEXT IDC_EDIT_PASSWORD,47,34,55,14,ES_PASSWORD | ES_AUTOHSCROLL + LTEXT "Site",IDC_STATIC,7,4,13,8 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "Osmo4-GPAC" + VALUE "FileVersion", "0.8.0" + VALUE "InternalName", "Osmo4" + VALUE "LegalCopyright", "Copyright (C) Telecom ParisTech 2005-2012" + VALUE "OriginalFilename", "Osmo4.EXE" + VALUE "ProductName", "Osmo4-GPAC" + VALUE "ProductVersion", "0.8.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_ABOUTBOX, DIALOG + BEGIN + BOTTOMMARGIN, 136 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Toolbar +// + +IDR_MAINTOOLS TOOLBAR 16, 15 +BEGIN + BUTTON ID_OPEN_FILE + BUTTON ID_NAV_PREV + BUTTON ID_NAV_NEXT + BUTTON ID_FILE_PLAY + BUTTON ID_FILE_PLAY + BUTTON ID_FILE_STEP + BUTTON ID_FILE_STOP + BUTTON ID_FILE_PROPS + BUTTON IDD_CONFIGURE + BUTTON ID_SWITCH_RENDER + BUTTON ID_SWITCH_RENDER +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +IDR_MAINTOOLS BITMAP "res\\maintool.bmp" + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDR_MAINFRAME "Osmo4" +END + +STRINGTABLE +BEGIN + AFX_IDS_APP_TITLE "Osmo4" + AFX_IDS_IDLEMESSAGE "Ready" +END + +STRINGTABLE +BEGIN + ID_FILE_EXIT "Quit the application; prompts to save documents\nExit" + ID_H_ABOUT "Display program information, version number and copyright\nAbout" + ID_PLAYLIST_LOOP "Restarts playlist from beginning when playlist is over" + ID_OPEN_FILE "Opens local file" + ID_VIEW_ORIGINAL "restore Original Aspect of presentation" +END + +STRINGTABLE +BEGIN + ID_VIEW_STATUS_BAR "Show or hide the status bar\nToggle StatusBar" +END + +STRINGTABLE +BEGIN + AFX_IDS_SCSIZE "Change the window size" + AFX_IDS_SCMOVE "Change the window position" + AFX_IDS_SCMINIMIZE "Reduce the window to an icon" + AFX_IDS_SCMAXIMIZE "Enlarge the window to full size" + AFX_IDS_SCNEXTWINDOW "Switch to the next document window" + AFX_IDS_SCPREVWINDOW "Switch to the previous document window" + AFX_IDS_SCCLOSE "Close the active window and prompts to save the documents" +END + +STRINGTABLE +BEGIN + AFX_IDS_SCRESTORE "Restore the window to normal size" + AFX_IDS_SCTASKLIST "Activate Task List" +END + +STRINGTABLE +BEGIN + ID_FILE_STOP "Stops current presentation" + ID_SWITCH_RENDER "Switch between 2D and 3D renderers" + ID_COLLIDE_NONE "Turns collision detection off" + ID_COLLIDE_REG "Turns collision detection on" + ID_COLLIDE_DISP "Collision with camera displacement" + ID_HEADLIGHT "Turns headlight on/off" +END + +STRINGTABLE +BEGIN + ID_CLEAR_NAV "Clears navigation history" + ID_TIMER " " + ID_FPS " " + ID_VIEW_PL "View navigation history as a playlist" +END + +STRINGTABLE +BEGIN + ID_VIEW_FULLSCREEN "Move to Full Screen mode (Esc to exit)" + ID_AR_KEEP "Keep Aspect Ratio of presentation" + ID_SHORTCUTS "List of available shortcuts" + ID_FILE_PROP "Show presentation properties" + ID_FILE_STEP "Step one frame into presentation" + IDD_CONFIGURE "Configure Player" + ID_VIEW_SCALABLE "Uses vectorial zooming when resizing the window" + ID_OPEN_URL "Open remote presentation" + ID_FILE_RELOAD "Reload current presentation" + ID_FILE_PLAY "Play/Pause presentation" + ID_NAVIGATE_NONE "Disable navigation" +END + +STRINGTABLE +BEGIN + ID_NAVIGATE_WALK "Turn walk navigation on" + ID_AR_FILL "Ignores Aspect Ratio and always fill screen" + ID_AR_43 "Forces Aspect Ratio of 4/3" + ID_AR_169 "Forces Aspect Ratio of 16/9" + ID_NAV_RESET "Restore last viewpoint" +END + +STRINGTABLE +BEGIN + ID_NAVIGATE_VR "QT-VR like navigation" + ID_REC_ENABLE "Enable recording of streaming data" + ID_REC_STOP "Stops recording and save to file" + ID_REC_ABORT "Stops recording and discard data" +END + +STRINGTABLE +BEGIN + ID_FILE_COPY "Copy selected text to clipboard" + ID_FILE_PASTE "Paste clipboard" +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// French (France) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_FRA) +LANGUAGE LANG_FRENCH, SUBLANG_FRENCH +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_OPENFILE DIALOG 0, 0, 301, 23 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Select Location" +FONT 8, "MS Sans Serif" +BEGIN + COMBOBOX IDC_COMBOURL,3,5,273,67,CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_SORT | WS_VSCROLL | WS_TABSTOP + DEFPUSHBUTTON "OK",IDC_BUTGO,279,5,19,13 +END + +IDD_OPTIONS DIALOG 0, 0, 174, 106 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Osmo4 Options" +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "Apply",IDC_SAVEOPT,147,2,26,12 + COMBOBOX IDC_SELECT,42,2,99,173,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Category",IDC_STATIC,7,4,29,8 +END + +IDD_OPT_GEN DIALOG 0, 20, 169, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "Loop At End",IDC_LOOP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,4,4,55,10 + CONTROL "Look for subtitles",IDC_LOOKFORSUB,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,4,42,69,10 + CONTROL "Disable console messages",IDC_NO_CONSOLE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,4,17,99,10 + CONTROL "View Graph in XMT-A format",IDC_DUMP_XMT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,4,30,105,10 + CONTROL "Single Instance",IDC_SINGLE_INSTANCE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,4,54,65,10 +END + +IDD_OPT_RENDER DIALOG 0, 20, 169, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Rendering Frame Rate",IDC_STATIC,5,4,72,8 + COMBOBOX IDC_BIFS_RATE,81,2,84,55,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Fast Rendering",IDC_FAST_RENDER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,49,64,10 + CONTROL "Force Scene Size",IDC_FORCE_SIZE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,96,49,72,10 + LTEXT "Anti-Aliasing Level",IDC_STATIC,7,20,58,8 + COMBOBOX IDC_AA_LIST,81,17,84,46,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Use 3D Renderer",IDC_USE_RENDER3D,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,63,71,10 + LTEXT "2D Rasterizer",IDC_STATIC,7,35,44,8 + COMBOBOX IDC_GD_LIST,81,33,84,44,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_DRAW_BOUNDS,109,60,56,44,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Bounds",IDC_STATIC,82,64,25,8 +END + +IDD_OPT_AUDIO DIALOG 0, 20, 169, 76 +STYLE DS_SETFONT | DS_CONTROL | WS_CHILD | WS_THICKFRAME +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "Spin1",IDC_SPIN_AUDIO,"msctls_updown32",UDS_SETBUDDYINT | UDS_ARROWKEYS,27,15,11,14 + EDITTEXT IDC_EDIT_AUDIO,5,15,19,14,ES_READONLY | ES_NUMBER + CONTROL "Force Audio Config",IDC_FORCE_AUDIO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,3,73,10 + LTEXT "buffers",IDC_STATIC,43,18,23,8 + CONTROL "Spin1",IDC_SPIN_FPS,"msctls_updown32",UDS_SETBUDDYINT | UDS_ARROWKEYS,104,15,11,14 + EDITTEXT IDC_AUDIO_FPS,77,15,24,14,ES_NUMBER + LTEXT "Audio Driver",IDC_STATIC,7,63,40,8 + CONTROL "No Resynchronization",IDC_AUDIO_RESYNC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,4,46,82,10 + COMBOBOX IDC_DRIVER_LIST,63,60,103,62,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Disable Notifications",IDC_AUDIO_NOTIFS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,89,47,79,10 + CONTROL "Disable Multichannel",IDC_AUDIO_MULTICH,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,4,34,81,10 + LTEXT "ms total length",IDC_STATIC,118,18,49,8 +END + +IDD_OPT_VIDEO DIALOG 0, 20, 169, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Video Driver",IDC_STATIC,6,7,40,8 + COMBOBOX IDC_VIDEO_LIST,55,5,111,44,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Change video resolution in fullscreen",IDC_SWITCH_RES, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,29,131,10 + CONTROL "Use Hardware Video Memory in 2D mode",IDC_HWMEMORY, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,44,143,10 +END + +IDD_OPT_HTTP DIALOGEX 0, 20, 169, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + PUSHBUTTON "...",IDC_BROWSE_CACHE,57,4,109,12 + CONTROL "Clean cache at exit",IDC_CLEAN_CACHE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,87,23,77,10 + CONTROL "Disable Cache",IDC_RESTART_CACHE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,22,62,10 + LTEXT "Cache Directory",IDC_STATIC,5,5,52,8 + CONTROL "XML progressive load",IDC_SAX_PROGRESSIVE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,42,84,10 + EDITTEXT IDC_SAX_DELAY,142,41,22,12,ES_CENTER | ES_AUTOHSCROLL | ES_NUMBER + LTEXT "TimeSlice (ms)",IDC_STATIC,91,43,46,8 + EDITTEXT IDC_HTTP_PROXY,54,58,111,12,ES_AUTOHSCROLL + CONTROL "Use proxy",IDC_HTTP_USE_PROXY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,59,47,10 +END + +IDD_OPT_FONT DIALOG 0, 20, 169, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Font Engine",IDC_STATIC,6,11,39,8 + COMBOBOX IDC_FONT_LIST,60,8,105,44,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "...",IDC_BROWSE_FONT,2,40,164,12 + LTEXT "System Font Directory",IDC_STATIC,46,29,70,8 + COMBOBOX IDC_TEXTURE_MODE,101,58,64,44,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Text Texturing Mode",IDC_STATIC,7,60,69,8 +END + +IDD_OPT_SYSTEMS DIALOG 0, 20, 169, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + COMBOBOX IDC_LANG,75,4,92,58,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Prefered Language for stream selection",IDC_STATIC,3,3,61,17 + LTEXT "Decoder Threading",IDC_STATIC,4,28,62,8 + COMBOBOX IDC_DEC_THREAD,75,25,92,57,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Always draw late BIFS frames",IDC_BIFSDROP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,43,109,10 + CONTROL "Force Single Timeline",IDC_FORCE_DURATION,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,58,83,10 +END + +IDD_OPT_STREAM DIALOG 0, 20, 169, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Default Port",IDC_STATIC,6,6,40,8 + COMBOBOX IDC_PORT,52,3,113,61,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "RTP over RTSP",IDC_RTSP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,4,19,68,10 + CONTROL "use RTP reordering",IDC_REORDER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,81,19,76,10 + LTEXT "milliseconds before control timeout",IDC_STATIC,38,34,108,8 + EDITTEXT IDC_TIMEOUT,3,32,30,12,ES_CENTER | ES_AUTOHSCROLL | ES_NUMBER + LTEXT "milliseconds of Media Buffering ",IDC_STATIC,38,48,100,8 + EDITTEXT IDC_BUFFER,3,47,30,12,ES_CENTER | ES_AUTOHSCROLL | ES_NUMBER + EDITTEXT IDC_REBUFFER_LEN,83,60,30,12,ES_CENTER | ES_AUTOHSCROLL | ES_NUMBER + CONTROL "Rebuffer if less than",IDC_REBUFFER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,4,63,79,10 + LTEXT "milliseconds",IDC_STATIC,117,63,41,8 +END + +IDD_PROPERTIES DIALOGEX 0, 0, 338, 150 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Presentation Properties" +FONT 8, "MS Sans Serif", 0, 0, 0x1 +BEGIN + CONTROL "Tree1",IDC_ODTREE,"SysTreeView32",TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_DISABLEDRAGDROP | TVS_SHOWSELALWAYS | TVS_TRACKSELECT | TVS_SINGLEEXPAND | WS_BORDER | WS_TABSTOP,2,2,120,114 + EDITTEXT IDC_ODINFO,123,17,213,130,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_NOHIDESEL | ES_OEMCONVERT | ES_READONLY | WS_VSCROLL | WS_HSCROLL,WS_EX_DLGMODALFRAME | WS_EX_STATICEDGE + PUSHBUTTON "Get World Info",IDC_WORLD,2,118,119,13 + PUSHBUTTON "View Scene Graph",IDC_VIEWSG,2,134,119,13 + CONTROL "Tab1",IDC_VIEWSEL,"SysTabControl32",TCS_BUTTONS,124,2,208,14 +END + +IDD_OPT_DECODER DIALOG 0, 20, 169, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Prefered Audio Module",-1,46,5,69,8 + COMBOBOX IDC_AUDEC_LIST,26,17,111,56,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Prefered Video Module",-1,48,40,69,8 + COMBOBOX IDC_VIDEC_LIST,25,52,113,55,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP +END + +IDD_OPT_RENDER2D DIALOG 0, 20, 169, 76 +STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "Disable YUV Hardware",IDC_YUV,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,54,89,10 + LTEXT "Static",IDC_FORMAT_YUV,97,57,67,8 + CONTROL "Direct Rendering",IDC_DIRECTRENDER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,32,72,10 + CONTROL "Scalable Zoom",IDC_ZOOM_SCALABLE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,10,77,10 +END + +IDD_OPT_RENDER3D DIALOG 0, 20, 169, 76 +STYLE DS_SETFONT | DS_MODALFRAME | WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "Use OpenGL Outlines",IDC_RASTER_OUTLINE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,2,84,10 + CONTROL "Emulate power-of-two textures for video",IDC_EMUL_POW2, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,45,140,10 + CONTROL "Polygon Anti-Aliasing",IDC_DISABLE_POLY_AA,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,90,2,78,10 + CONTROL "Disable rectangular texture extensions",IDC_DISABLE_TX_RECT, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,55,135,10 + CONTROL "Bitmap node uses direct pixel copy",IDC_BITMAP_USE_PIXEL, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,65,125,10 + LTEXT "Draw Normals",IDC_STATIC,4,17,45,8 + COMBOBOX IDC_DRAW_NORMALS,4,25,48,44,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Backface Cull",IDC_STATIC,57,17,55,8 + COMBOBOX IDC_BACK_CULL,57,25,47,44,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Draw Mode",IDC_STATIC,113,17,55,8 + COMBOBOX IDC_DRAW_MODE,111,25,53,44,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP +END + +IDD_SLIDERS DIALOGEX 0, 0, 218, 18 +STYLE DS_SETFONT | WS_CHILD +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + CONTROL "Slider1",ID_SLIDER,"msctls_trackbar32",TBS_TOP | TBS_NOTICKS,0,3,185,12 + CONTROL "Slider1",ID_AUDIO_VOL,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_BORDER | WS_TABSTOP,187,3,30,13 +END + +IDD_NAVBAR DIALOGEX 0, 0, 279, 15 +STYLE DS_SETFONT | WS_CHILD +FONT 8, "MS Sans Serif", 0, 0, 0x1 +BEGIN + COMBOBOX IDC_ADDRESS,29,1,130,196,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP,WS_EX_ACCEPTFILES + LTEXT "Address",IDC_DUMTXT,1,4,26,8 +END + +IDD_PLAYLIST DIALOGEX 0, 0, 186, 54 +STYLE DS_SETFONT | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +EXSTYLE WS_EX_TOOLWINDOW +CAPTION "Osmo4 Playlist" +FONT 8, "MS Sans Serif", 0, 0, 0x1 +BEGIN + CONTROL "List4",IDC_FILELIST,"SysListView32",LVS_REPORT | WS_BORDER | WS_TABSTOP,1,0,182,51 +END + +IDD_OPT_MCACHE DIALOG 0, 20, 169, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "...",IDC_BROWSE_MCACHE,41,2,126,12 + CONTROL "Overwrite existing files",IDC_MCACHE_OVERWRITE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,18,85,10 + CONTROL "Use filename",IDC_MCACHE_USENAME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,31,57,10 + LTEXT "Record To",IDC_STATIC,4,4,35,8 + EDITTEXT IDC_BASEPRES,81,31,82,12,ES_CENTER | ES_AUTOHSCROLL | ES_NUMBER +END + +IDD_OPT_FILETYPES DIALOG 0, 20, 169, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + COMBOBOX IDC_FILELIST,59,6,108,61,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Supported Files",IDC_STATIC,6,8,50,8 + LTEXT "Extension",IDC_FILES_EXT,7,25,154,8 + LTEXT "Mime Type",IDC_FILES_MIMES,7,37,156,8 + CONTROL "Associate with Osmo4",IDC_ASSOCIATE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,60,84,10 + LTEXT "Plugin",IDC_FILES_PLUG,8,49,154,8 +END + +IDD_OPT_LOGS DIALOG 0, 20, 169, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Level",IDC_STATIC,6,7,18,8 + COMBOBOX IDC_LOG_LEVEL,30,4,64,74,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "core",IDC_TOOL_CORE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,23,30,10 + CONTROL "coding",IDC_TOOL_CODING,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,33,37,10 + CONTROL "container",IDC_TOOL_CONTAINER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,43,45,10 + CONTROL "network",IDC_TOOL_NET,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,53,41,10 + CONTROL "rtp",IDC_TOOL_RTP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,63,24,10 + CONTROL "author",IDC_TOOL_AUTHOR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,48,24,36,10 + CONTROL "sync",IDC_TOOL_SYNC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,48,34,31,10 + CONTROL "codec",IDC_TOOL_CODEC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,48,44,36,10 + CONTROL "parser",IDC_TOOL_PARSER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,48,54,35,10 + CONTROL "media",IDC_TOOL_MEDIA,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,48,64,35,10 + CONTROL "scene",IDC_TOOL_SCENE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,86,24,35,10 + CONTROL "script",IDC_TOOL_SCRIPT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,86,34,33,10 + CONTROL "compose",IDC_TOOL_COMPOSE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,86,44,45,10 + CONTROL "render",IDC_TOOL_RENDER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,86,54,36,10 + CONTROL "mmio",IDC_TOOL_MMIO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,86,64,32,10 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_OPT_VIDEO, DIALOG + BEGIN + RIGHTMARGIN, 168 + END + + IDD_OPT_FONT, DIALOG + BEGIN + LEFTMARGIN, 1 + RIGHTMARGIN, 168 + BOTTOMMARGIN, 74 + END + + IDD_OPT_SYSTEMS, DIALOG + BEGIN + RIGHTMARGIN, 167 + TOPMARGIN, 1 + BOTTOMMARGIN, 75 + END + + IDD_OPT_STREAM, DIALOG + BEGIN + LEFTMARGIN, 1 + RIGHTMARGIN, 167 + TOPMARGIN, 1 + END + + IDD_PROPERTIES, DIALOG + BEGIN + RIGHTMARGIN, 335 + TOPMARGIN, 1 + END + + IDD_OPT_DECODER, DIALOG + BEGIN + RIGHTMARGIN, 168 + END + + IDD_OPT_RENDER3D, DIALOG + BEGIN + RIGHTMARGIN, 168 + END + + IDD_NAVBAR, DIALOG + BEGIN + RIGHTMARGIN, 167 + END + + IDD_OPT_FILETYPES, DIALOG + BEGIN + RIGHTMARGIN, 168 + END + + IDD_OPT_LOGS, DIALOG + BEGIN + RIGHTMARGIN, 168 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Toolbar +// + +IDR_PLAYLIST TOOLBAR 16, 15 +BEGIN + BUTTON ID_PL_OPEN + BUTTON ID_PL_SAVE + BUTTON ID_PL_ADD_FILE + BUTTON ID_PL_REM_FILE + BUTTON ID_PL_UP + BUTTON ID_PL_DOWN + BUTTON ID_PL_SORT_FILE +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +IDR_PLAYLIST BITMAP "res\\playlist.bmp" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\Osmo4.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON "..\\..\\doc\\osmo4.ico" +IDI_PLAY ICON "res\\play.ico" +IDI_STOP ICON "res\\stop.ico" +IDI_PAUSE ICON "res\\pause.ico" +IDI_MESSAGE ICON "res\\message.ico" +IDI_ERR ICON "res\\error.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog Info +// + +IDD_OPT_RENDER DLGINIT +BEGIN + IDC_BIFS_RATE, 0x403, 4, 0 +0x2e35, 0x0030, + IDC_BIFS_RATE, 0x403, 4, 0 +0x2e37, 0x0035, + IDC_BIFS_RATE, 0x403, 5, 0 +0x3031, 0x302e, "\000" + IDC_BIFS_RATE, 0x403, 5, 0 +0x3231, 0x352e, "\000" + IDC_BIFS_RATE, 0x403, 5, 0 +0x3531, 0x302e, "\000" + IDC_BIFS_RATE, 0x403, 5, 0 +0x3432, 0x302e, "\000" + IDC_BIFS_RATE, 0x403, 5, 0 +0x3532, 0x302e, "\000" + IDC_BIFS_RATE, 0x403, 5, 0 +0x3033, 0x302e, "\000" + 0 +END + +IDD_OPT_SYSTEMS DLGINIT +BEGIN + IDC_LANG, 0x403, 8, 0 +0x6e45, 0x6c67, 0x7369, 0x0068, + IDC_LANG, 0x403, 7, 0 +0x7246, 0x6e65, 0x6863, "\000" + IDC_LANG, 0x403, 7, 0 +0x6547, 0x6d72, 0x6e61, "\000" + IDC_LANG, 0x403, 8, 0 +0x7449, 0x6c61, 0x6169, 0x006e, + IDC_LANG, 0x403, 8, 0 +0x7053, 0x6e61, 0x7369, 0x0068, + IDC_LANG, 0x403, 9, 0 +0x6843, 0x6e69, 0x6565, 0x6573, "\000" + IDC_LANG, 0x403, 10, 0 +0x614a, 0x6170, 0x656e, 0x7365, 0x0065, + IDC_DEC_THREAD, 0x403, 14, 0 +0x6953, 0x676e, 0x656c, 0x5420, 0x7268, 0x6165, 0x0064, + IDC_DEC_THREAD, 0x403, 13, 0 +0x754d, 0x6c74, 0x2069, 0x6854, 0x6572, 0x6461, "\000" + IDC_DEC_THREAD, 0x403, 5, 0 +0x7246, 0x6565, "\000" + 0 +END + +IDD_OPT_LOGS DLGINIT +BEGIN + IDC_LOG_LEVEL, 0x403, 9, 0 +0x6944, 0x6173, 0x6c62, 0x6465, "\000" + IDC_LOG_LEVEL, 0x403, 6, 0 +0x7245, 0x6f72, 0x0072, + IDC_LOG_LEVEL, 0x403, 8, 0 +0x6157, 0x6e72, 0x6e69, 0x0067, + IDC_LOG_LEVEL, 0x403, 5, 0 +0x6e49, 0x6f66, "\000" + IDC_LOG_LEVEL, 0x403, 6, 0 +0x6544, 0x7562, 0x0067, + 0 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Accelerator +// + +IDR_MAINACCEL ACCELERATORS +BEGIN + "^P", ID_FILE_PLAY, ASCII, NOINVERT + "\t", ID_FILE_PROP, ASCII, NOINVERT + VK_F5, ID_FILE_RELOAD, VIRTKEY, NOINVERT + "^S", ID_FILE_STEP, ASCII, NOINVERT + "^O", ID_OPEN_FILE, ASCII, NOINVERT + "^U", ID_OPEN_URL, ASCII, NOINVERT + VK_F3, ID_VIEW_PL, VIRTKEY, NOINVERT +END + +#endif // French (France) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\Osmo4.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/applications/deprecated/old_arch/osmo4_w32/Osmo4.vcxproj b/applications/deprecated/old_arch/osmo4_w32/Osmo4.vcxproj new file mode 100644 index 0000000..bd46ba3 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/Osmo4.vcxproj @@ -0,0 +1,387 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {C79C2D73-06E9-4622-92CE-F166B1B51792} + Osmo4 + MFCProj + + + + Application + Dynamic + MultiByte + v140 + + + Application + Dynamic + MultiByte + v140 + + + Application + Dynamic + MultiByte + v140 + + + Application + Dynamic + MultiByte + v140 + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + ../../bin/$(Platform)\$(Configuration)/ + ../../bin/$(Platform)\$(Configuration)/ + .\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + .\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + false + false + ../../bin/$(Platform)\$(Configuration)/ + ../../bin/$(Platform)\$(Configuration)/ + .\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + .\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + true + true + true + true + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\obj/osmo4_w32_rel/Osmo4.tlb + + + + + MaxSpeed + OnlyExplicitInline + ../../include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreadedDLL + true + $(IntDir)$(ProjectName).pch + $(IntDir) + $(IntDir) + $(IntDir) + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)$(TargetName)$(TargetExt) + true + $(IntDir)$(ProjectName).pdb + Windows + 8388608 + MachineX86 + + + true + $(IntDir)$(ProjectName).bsc + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + .\obj/osmo4_w32_rel/Osmo4.tlb + + + + + Disabled + Default + ../../include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreadedDLL + true + $(IntDir)$(ProjectName).pch + $(IntDir) + $(IntDir) + $(IntDir) + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)$(TargetName)$(TargetExt) + true + $(IntDir)$(ProjectName).pdb + Windows + 8388608 + + + true + $(IntDir)$(ProjectName).bsc + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\obj/osmo4_w32_deb/Osmo4.tlb + + + + + Disabled + ../../include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + $(IntDir)$(ProjectName).pch + $(IntDir) + $(IntDir) + $(IntDir) + true + Level3 + true + EditAndContinue + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + ../../include + + + $(OutDir)$(TargetName)$(TargetExt) + true + true + $(IntDir)$(ProjectName).pdb + Windows + MachineX86 + + + true + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + .\obj/osmo4_w32_deb/Osmo4.tlb + + + + + Disabled + ../../include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + $(IntDir)$(ProjectName).pch + $(IntDir) + $(IntDir) + $(IntDir) + true + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + ../../include + + + $(OutDir)$(TargetName)$(TargetExt) + true + true + $(IntDir)$(ProjectName).pdb + Windows + + + true + + + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + \gpac\applications\osmo4_w32;%(AdditionalIncludeDirectories) + \gpac\applications\osmo4_w32;%(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + \gpac\applications\osmo4_w32;%(AdditionalIncludeDirectories) + \gpac\applications\osmo4_w32;%(AdditionalIncludeDirectories) + + + + + + + + + + + + + + + + + + + + + + + + + + + + {d3540754-e0cf-4604-ac11-82de9bd4d814} + false + + + + + + \ No newline at end of file diff --git a/applications/deprecated/old_arch/osmo4_w32/Playlist.cpp b/applications/deprecated/old_arch/osmo4_w32/Playlist.cpp new file mode 100644 index 0000000..9adea5d --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/Playlist.cpp @@ -0,0 +1,969 @@ +// Playlist.cpp : implementation file +// + +#include "stdafx.h" +#include "osmo4.h" +#include "MainFrm.h" +#include "OpenURL.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// Playlist dialog + +PLEntry::PLEntry(CString url, char *path) +{ + if (!path || strrchr(url, '\\') || strstr(url, "://")) { + m_url = gf_strdup(url); + } else { + char szPath[MAX_PATH]; + strcpy(szPath, path); + strcat(szPath, url); + m_url = gf_strdup(szPath); + } + char *str = (char *) strrchr(url, '\\'); + if (!str) str = (char *) strrchr(url, '/'); + if (str && strlen(str+1)) { + m_disp_name = gf_strdup(str+1); + str = strrchr(m_disp_name, '.'); + if (str) str[0] = 0; + } else { + str = (char *) strstr(url, "://"); + if (str) { + str += 3; + m_disp_name = gf_strdup(str); + } else { + m_disp_name = gf_strdup(url); + str = strrchr(m_disp_name, '.'); + if (str) str[0] = 0; + } + } + m_duration = 0; + m_bIsDead = GF_FALSE; + m_bIsPlaying = GF_FALSE; + m_bIsSelected = GF_FALSE; +} + +PLEntry::~PLEntry() +{ + if (m_url) gf_free(m_url); + if (m_disp_name) gf_free(m_disp_name); + +} + + +static char szCacheDir[MAX_PATH]; + +Playlist::Playlist() + : CDialog(Playlist::IDD, NULL) +{ + //{{AFX_DATA_INIT(Playlist) + //}}AFX_DATA_INIT + + m_entries = gf_list_new(); + m_cur_entry = -1; + m_all_dead_entries=-1; + GetCurrentDirectory(MAX_PATH, szCacheDir); +} + +Playlist::~Playlist() +{ + Clear(); + gf_list_del(m_entries); +} + +void Playlist::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(Playlist) + DDX_Control(pDX, IDC_FILELIST, m_FileList); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(Playlist, CDialog) + //{{AFX_MSG_MAP(Playlist) + ON_WM_SIZE() + ON_COMMAND(ID_PL_ADD_FILE, OnPlAddFile) + ON_COMMAND(ID_PL_REM_FILE, OnPlRemFile) + ON_COMMAND(ID_PL_UP, OnSelUp) + ON_COMMAND(ID_PL_DOWN, OnSelDown) + ON_COMMAND(ID_PL_SAVE, OnPlSave) + ON_WM_DROPFILES() + ON_WM_CLOSE() + ON_WM_DESTROY() + ON_COMMAND(ID_PL_REM_DEAD, OnPlRemDead) + ON_COMMAND(ID_PL_REM_ALL, OnPlRemAll) + ON_COMMAND(ID_PL_ADD_DIR, OnPlAddDir) + ON_COMMAND(ID_PL_ADD_DIR_REC, OnPlAddDirRec) + ON_COMMAND(ID_PL_ADD_URL, OnPlAddUrl) + ON_COMMAND(ID_PL_OPEN, OnPlOpen) + ON_COMMAND(ID_PL_PLAY, OnPlPlay) + ON_COMMAND(ID_PL_SEL_REV, OnReverseSelection) + ON_COMMAND(ID_PL_SORT_REV, OnReverseList) + ON_COMMAND(ID_PL_RANDOM, OnRandomize) + ON_COMMAND(ID_PL_SORT_TITLE, OnSortTitle) + ON_COMMAND(ID_PL_SORT_FILE, OnSortFile) + ON_COMMAND(ID_PL_SORT_DUR, OnSortDuration) + ON_NOTIFY(NM_RCLICK, IDC_FILELIST, OnRclickFilelist) + ON_NOTIFY(NM_DBLCLK, IDC_FILELIST, OnDblclkFilelist) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// Playlist message handlers + +BOOL Playlist::OnInitDialog() +{ + UINT buttonArray[50]; + TBBUTTONINFO bi; + u32 *ba; + CDialog::OnInitDialog(); + + + SetIcon(AfxGetApp()->LoadIcon(IDR_MAINFRAME), TRUE); + SetIcon(AfxGetApp()->LoadIcon(IDR_MAINFRAME), FALSE); + + if (!m_toolBar.CreateEx(this, WS_CHILD | CBRS_TOP | CBRS_FLYBY) || + !m_toolBar.LoadBitmap(IDR_PLAYLIST)) + { + TRACE0("Failed to create toolbar\n"); + return 0; + } + + ba = &buttonArray[0]; + *ba = ID_PL_OPEN; + ba++; + *ba = ID_PL_SAVE; + ba++; + *ba = ID_SEPARATOR; + ba++; + *ba = ID_PL_ADD_FILE; + ba++; + *ba = ID_PL_REM_FILE; + ba++; + *ba = ID_SEPARATOR; + ba++; + *ba = ID_PL_UP; + ba++; + *ba = ID_PL_DOWN; + ba++; + *ba = ID_SEPARATOR; + ba++; + *ba = ID_PL_SORT_FILE; + ba++; + m_toolBar.SetButtons(buttonArray, 9); + m_toolBar.SetButtonInfo(0, ID_PL_OPEN, TBBS_BUTTON, 0); + m_toolBar.SetButtonInfo(1, ID_PL_SAVE, TBBS_BUTTON, 1); + m_toolBar.SetButtonInfo(2, ID_SEPARATOR, TBBS_SEPARATOR, 0); + m_toolBar.SetButtonInfo(3, ID_PL_ADD_FILE, TBBS_DROPDOWN | TBBS_BUTTON, 2); + m_toolBar.SetButtonInfo(4, ID_PL_REM_FILE, TBBS_DROPDOWN | TBBS_BUTTON, 3); + m_toolBar.SetButtonInfo(5, ID_SEPARATOR, TBBS_SEPARATOR, 0); + m_toolBar.SetButtonInfo(6, ID_PL_UP, TBBS_BUTTON, 4); + m_toolBar.SetButtonInfo(7, ID_PL_DOWN, TBBS_BUTTON, 5); +// m_toolBar.SetButtonInfo(8, ID_SEPARATOR, TBBS_SEPARATOR, 0); + m_toolBar.SetButtonInfo(8, ID_PL_SORT_FILE, TBBS_DROPDOWN | TBBS_BUTTON, 6); + + CToolBarCtrl &ctrl = m_toolBar.GetToolBarCtrl(); + ctrl.SetStyle(TBSTYLE_FLAT | TBSTYLE_DROPDOWN); + ctrl.SetExtendedStyle(TBSTYLE_EX_DRAWDDARROWS); + + memset(&bi, 0, sizeof(bi)); + bi.cbSize = sizeof(bi); + ctrl.GetButtonInfo(3, &bi); + bi.fsStyle |= TBSTYLE_DROPDOWN; + ctrl.SetButtonInfo(ID_PL_ADD_FILE, &bi); + + memset(&bi, 0, sizeof(bi)); + bi.cbSize = sizeof(bi); + ctrl.GetButtonInfo(4, &bi); + bi.fsStyle |= TBBS_DROPDOWN | TBSTYLE_DROPDOWN; + ctrl.SetButtonInfo(ID_PL_REM_FILE, &bi); + + memset(&bi, 0, sizeof(bi)); + bi.cbSize = sizeof(bi); + ctrl.GetButtonInfo(9, &bi); + bi.fsStyle |= TBSTYLE_DROPDOWN; + ctrl.SetButtonInfo(ID_PL_SORT_FILE, &bi); + + m_FileList.InsertColumn(0, "", LVCFMT_LEFT, 30, 0); + m_FileList.InsertColumn(1, "Title", LVCFMT_LEFT, 200, 1); + m_FileList.InsertColumn(2, "Duration", LVCFMT_LEFT, 200, 2); + + + m_toolBar.UpdateWindow(); + m_toolBar.ShowWindow(SW_SHOW); + + SetWindowPos(NULL, 0, 0, 400, 600, SWP_NOZORDER | SWP_NOMOVE); + + PostMessage(WM_NULL); + DragAcceptFiles(); + + return TRUE; +} + +void Playlist::OnSize(UINT nType, int cx, int cy) +{ + u32 tool_h; + CDialog::OnSize(nType, cx, cy); + RECT rc; + if (!m_toolBar.m_hWnd) return; + if (!m_FileList.m_hWnd) return; + + m_toolBar.GetClientRect(&rc); + tool_h = rc.bottom - rc.top; + m_toolBar.SetWindowPos(this, 0, 0, cx, tool_h, SWP_NOZORDER); + m_FileList.SetWindowPos(this, 0, tool_h, cx, cy-tool_h, SWP_NOZORDER); + + m_FileList.SetExtendedStyle(m_FileList.GetExtendedStyle() | LVS_EX_FULLROWSELECT); + + m_FileList.SetColumnWidth(0, 30); + m_FileList.SetColumnWidth(2, 60); + m_FileList.SetColumnWidth(1, cx-95); + +} + +void Playlist::OnDropFiles(HDROP hDropInfo) +{ + u32 i, count; + Osmo4 *app = GetApp(); + char fileName[MAX_PATH]; + count = ::DragQueryFile(hDropInfo, 0xFFFFFFFF, NULL, 0); + if (!count) return; + for (i=0; icode == TBN_DROPDOWN) { + RECT rc; + POINT pt; + CMenu *pPopup = new CMenu(); + pPopup->CreatePopupMenu(); + + m_toolBar.GetWindowRect(&rc); + pt.y = rc.bottom; + pt.x = rc.left; + + if ( ((LPNMTOOLBAR)lParam)->iItem == ID_PL_ADD_FILE) { + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_ADD_DIR, "Directory..."); + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_ADD_DIR_REC, "Directory and subfolders..."); + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_ADD_URL, "URL..."); + m_toolBar.GetToolBarCtrl().GetItemRect(3, &rc); + pt.x += rc.left; + } else if ( ((LPNMTOOLBAR)lParam)->iItem == ID_PL_REM_FILE) { + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_REM_ALL, "Clear"); + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_REM_DEAD, "Remove dead entries"); + + m_toolBar.GetToolBarCtrl().GetItemRect(4, &rc); + pt.x += rc.left; + } else if ( ((LPNMTOOLBAR)lParam)->iItem == ID_PL_SORT_FILE) { + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_SORT_TITLE, "Sort Files by title"); + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_SORT_FILE, "Sort Files by filename"); + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_SORT_DUR, "Sort Files by duration"); + pPopup->AppendMenu(MF_SEPARATOR,0); + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_SORT_REV, "Reverse Playlist"); + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_RANDOM, "Shuffle Playlist"); + + m_toolBar.GetToolBarCtrl().GetItemRect(8, &rc); + pt.x += rc.left; + } + pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this); + delete pPopup; + + return FALSE; + } + return CDialog::OnNotify(wParam, lParam, pResult); +} + +void Playlist::Clear() +{ + m_FileList.DeleteAllItems(); + while (gf_list_count(m_entries)) { + PLEntry *ple = (PLEntry *) gf_list_get(m_entries, 0); + gf_list_rem(m_entries, 0); + delete ple; + } + m_cur_entry = -1; +} + +void Playlist::ClearButPlaying() +{ + PLEntry *p=NULL; + if (m_cur_entry>=0) p = (PLEntry *) gf_list_get(m_entries, m_cur_entry); + if (p) gf_list_rem(m_entries, m_cur_entry); + + Clear(); + if (p) { + gf_list_add(m_entries, p); + m_cur_entry = 0; + } + RefreshList(); +} + +void Playlist::UpdateEntry(u32 i) +{ + char szText[20]; + PLEntry *ple = (PLEntry *) gf_list_get(m_entries, i); + if (i+1<10) sprintf(szText, "00%d", i+1); + else if (i+1<100) sprintf(szText, "0%d", i+1); + else sprintf(szText, "%d", i+1); + m_FileList.SetItem(i, 0, LVIF_TEXT, szText, 0, 0, 0, 0); + + CString str; + if (ple->m_bIsDead) { + str = "!! DEAD !! "; + str += ple->m_disp_name; + } + else if (ple->m_bIsPlaying) { + str = ">> "; + str += ple->m_disp_name; + str += " >>"; + } + else str = ple->m_disp_name; + m_FileList.SetItem(i, 1, LVIF_TEXT, str, 0, 0, 0, 0); + + if (ple->m_duration) { + u32 h = (u32) (ple->m_duration / 3600); + u32 m = (u32) (ple->m_duration / 60) - h*60; + u32 s = (u32) (ple->m_duration) - h*3600 - m*60; + sprintf(szText, "%02d:%02d:%02d", h, m, s); + m_FileList.SetItem(i, 2, LVIF_TEXT, szText, 0, 0, 0, 0); + } else { + m_FileList.SetItem(i, 2, LVIF_TEXT, "Unknown", 0, 0, 0, 0); + } + +} + +void Playlist::RefreshList() +{ + u32 i, top_idx; + char szPath[GF_MAX_PATH]; + + top_idx = m_FileList.GetTopIndex(); + m_FileList.DeleteAllItems(); + + for (i=0; im_bIsPlaying) m_cur_entry = i; + + if (ple->m_bIsSelected) { + m_FileList.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED); + ple->m_bIsSelected = GF_FALSE; + } + } + + if (m_cur_entry >= (s32)gf_list_count(m_entries)) m_cur_entry = gf_list_count(m_entries); + else { + s32 last_idx = top_idx + m_FileList.GetCountPerPage(); + m_FileList.EnsureVisible(top_idx, 0); + if (gf_list_count(m_entries)<(u32) last_idx) last_idx = gf_list_count(m_entries); + m_FileList.EnsureVisible(last_idx, 1); + } + + + strcpy((char *) szPath, GetApp()->szUserPath); + strcat(szPath, "gpac_pl.m3u"); + Save(szPath, GF_TRUE); +} + +void Playlist::OnPlAddFile() +{ + Osmo4 *app = GetApp(); + CString sFiles = app->GetFileFilter(); + + CFileDialog fd(TRUE,NULL,NULL, OFN_ALLOWMULTISELECT | OFN_HIDEREADONLY | OFN_FILEMUSTEXIST , sFiles); + fd.m_ofn.nMaxFile = 25000; + fd.m_ofn.lpstrFile = (char *) gf_malloc(sizeof(char) * fd.m_ofn.nMaxFile); + fd.m_ofn.lpstrFile[0] = 0; + + if (fd.DoModal() == IDOK) { + s32 cur = m_FileList.GetItemCount(); + POSITION pos = fd.GetStartPosition(); + while (pos) { + QueueURL(fd.GetNextPathName(pos)); + } + } + gf_free(fd.m_ofn.lpstrFile); + m_all_dead_entries=-1; + RefreshList(); +} + +void Playlist::OnClose() +{ + ShowWindow(SW_HIDE); +} + +void Playlist::OnPlRemFile() +{ + if (!m_FileList.GetSelectedCount()) return; + POSITION pos = m_FileList.GetFirstSelectedItemPosition(); + while (pos != NULL) { + int nItem = m_FileList.GetNextSelectedItem(pos); + PLEntry *ple = (PLEntry *) m_FileList.GetItemData(nItem); + gf_list_del_item(m_entries, ple); + delete ple; + } + m_all_dead_entries=-1; + RefreshList(); +} + +void Playlist::OnSelUp() +{ + s32 i; + if (!m_FileList.GetSelectedCount()) return; + POSITION pos = m_FileList.GetFirstSelectedItemPosition(); + int nItem = m_FileList.GetNextSelectedItem(pos); + if (nItem==0) return; + + pos = m_FileList.GetFirstSelectedItemPosition(); + while (pos != NULL) { + nItem = m_FileList.GetNextSelectedItem(pos); + PLEntry *ple = (PLEntry *) m_FileList.GetItemData(nItem); + i = gf_list_del_item(m_entries, ple); + assert(i>=1); + gf_list_insert(m_entries, ple, i-1); + ple->m_bIsSelected = GF_TRUE; + } + RefreshList(); +} + +void Playlist::OnSelDown() +{ + s32 i, nItem; + if (!m_FileList.GetSelectedCount()) return; + POSITION pos = m_FileList.GetFirstSelectedItemPosition(); + while (pos != NULL) nItem = m_FileList.GetNextSelectedItem(pos); + + if ((u32) nItem + 1 == gf_list_count(m_entries)) return; + + pos = m_FileList.GetFirstSelectedItemPosition(); + while (pos != NULL) { + nItem = m_FileList.GetNextSelectedItem(pos); + PLEntry *ple = (PLEntry *) m_FileList.GetItemData(nItem); + i = gf_list_del_item(m_entries, ple); + gf_list_insert(m_entries, ple, i+1); + ple->m_bIsSelected = GF_TRUE; + } + RefreshList(); +} + +void Playlist::OnPlRemAll() +{ + Clear(); + RefreshList(); + m_cur_entry = -1; +} + +void Playlist::OnPlRemDead() +{ + for (u32 i=0; im_bIsDead) continue; + gf_list_rem(m_entries, i); + i--; + delete ple; + } + m_all_dead_entries=-1; + RefreshList(); +} + + +static int CALLBACK LocCbck(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) +{ + char dir[MAX_PATH]; + if (uMsg == BFFM_INITIALIZED) { + strcpy(dir, szCacheDir); + SendMessage(hwnd, BFFM_SETSELECTION, TRUE,(LPARAM) dir); + } + return 0; +} +static Bool pl_enum_dir_item(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) +{ + Osmo4 *gpac = GetApp(); + Playlist *_this = (Playlist *)cbck; + + if (gf_term_is_supported_url(gpac->m_term, item_name, GF_FALSE, GF_TRUE)) { + _this->QueueURL(item_path); + } + return GF_FALSE; +} + +static Bool pl_enum_dir_dirs(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) +{ + gf_enum_directory(item_path, GF_FALSE, pl_enum_dir_item, cbck, NULL); + gf_enum_directory(item_path, GF_TRUE, pl_enum_dir_dirs, cbck, NULL); + return GF_FALSE; +} + + +void Playlist::AddDir(Bool do_recurse) +{ + BROWSEINFO brw; + LPMALLOC pMalloc; + LPITEMIDLIST ret; + char dir[MAX_PATH]; + + Bool res = GF_FALSE; + if (NOERROR == ::SHGetMalloc(&pMalloc) ) { + memset(&brw, 0, sizeof(BROWSEINFO)); + brw.hwndOwner = this->GetSafeHwnd(); + brw.pszDisplayName = dir; + brw.lpszTitle = "Select Directory..."; + brw.ulFlags = 0L; + brw.lpfn = LocCbck; + + ret = SHBrowseForFolder(&brw); + if (ret != NULL) { + if (::SHGetPathFromIDList(ret, dir)) res = GF_TRUE; + pMalloc->Free(ret); + } + pMalloc->Release(); + } + if (!res) return; + strcpy(szCacheDir, dir); + + gf_enum_directory(dir, GF_FALSE, pl_enum_dir_item, this, NULL); + if (do_recurse) gf_enum_directory(dir, GF_FALSE, pl_enum_dir_dirs, this, NULL); + m_all_dead_entries=-1; + RefreshList(); +} +void Playlist::OnPlAddDir() +{ + AddDir(GF_FALSE); +} +void Playlist::OnPlAddDirRec() +{ + AddDir(GF_TRUE); +} + +void Playlist::OnPlAddUrl() +{ + COpenUrl url; + if (url.DoModal() != IDOK) return; + PLEntry *ple = new PLEntry(url.m_url); + gf_list_add(m_entries, ple); + m_all_dead_entries=-1; + RefreshList(); +} + +void Playlist::OnPlSave() +{ + Bool save_m3u; + char szPath[GF_MAX_PATH]; + if (!gf_list_count(m_entries)) return; + CFileDialog fd(FALSE,NULL,NULL, OFN_OVERWRITEPROMPT, "M3U Playlists|*.m3u|ShoutCast Playlists|*.pls|"); + if (fd.DoModal() != IDOK) return; + + strcpy(szPath, fd.GetPathName()); + strlwr(szPath); + save_m3u = (fd.m_ofn.nFilterIndex==1) ? GF_TRUE : GF_FALSE; + if (save_m3u) { + if (!strstr(szPath, ".m3u")) { + strcpy(szPath, fd.GetPathName()); + strcat(szPath, ".m3u"); + } else { + strcpy(szPath, fd.GetPathName()); + } + } else { + if (!strstr(szPath, ".pls")) { + strcpy(szPath, fd.GetPathName()); + strcat(szPath, ".pls"); + } else { + strcpy(szPath, fd.GetPathName()); + } + } + Save(szPath, save_m3u); +} + +void Playlist::Save(char *szPath, Bool save_m3u) +{ + FILE *out = gf_fopen(szPath, "wt"); + if (!save_m3u) + fprintf(out, "[playlist]\nNumberOfEntries=%d\n", gf_list_count(m_entries)); + + for (u32 i=0; im_url); + } else { + fprintf(out, "File%d=%s\n", i+1, ple->m_url); + fprintf(out, "Title%d=%s\n", i+1, ple->m_disp_name); + fprintf(out, "Length%d=%d\n", i+1, ple->m_duration ? ple->m_duration : -1); + + } + } + if (!save_m3u) fprintf(out, "Version=2\n"); + + fprintf(out, "\n"); + gf_fclose(out); +} + +void Playlist::OnPlOpen() +{ + CFileDialog fd(TRUE,NULL,NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, "M3U & PLS Playlists|*.m3u;*.pls|M3U Playlists|*.m3u|ShoutCast Playlists|*.pls|"); + if (fd.DoModal() != IDOK) return; + + Clear(); + OpenPlayList(fd.GetPathName()); + m_cur_entry = 0; + Play(); +} + +void Playlist::OpenPlayList(CString fileName) +{ + FILE *pl; + PLEntry *ple; + Bool load_m3u, go; + char szLine[GF_MAX_PATH], *sep; + char szPath[GF_MAX_PATH]; + + strcpy(szPath, fileName); + sep = strrchr(szPath, '\\'); + if (sep) sep[1] = 0; + else szPath[0] = 0; + + pl = gf_fopen(fileName, "rt"); + if (!pl) return; + ple = NULL; + load_m3u = GF_TRUE; + while (!feof(pl)) { + fgets(szLine, GF_MAX_PATH, pl); + go = GF_TRUE; + while (go) { + switch (szLine[strlen(szLine)-1]) { + case '\n': + case '\r': + case ' ': + szLine[strlen(szLine)-1] = 0; + break; + default: + go = GF_FALSE; + break; + } + } + if (!strlen(szLine)) continue; + if (!stricmp(szLine, "[playlist]")) { + load_m3u = GF_FALSE; + } else if (load_m3u) { + ple = new PLEntry(szLine, szPath); + gf_list_add(m_entries, ple); + } else if (!strnicmp(szLine, "file", 4)) { + char *st = strchr(szLine, '='); + if (!st) ple = NULL; + else { + ple = new PLEntry(st + 1, szPath); + gf_list_add(m_entries, ple); + } + } else if (ple && !strnicmp(szLine, "Length", 6)) { + char *st = strchr(szLine, '='); + s32 d = atoi(st + 1); + if (d>0) ple->m_duration = d; + } else if (ple && !strnicmp(szLine, "Title", 5)) { + char *st = strchr(szLine, '='); + gf_free(ple->m_disp_name); + ple->m_disp_name = gf_strdup(st + 6); + } + } + gf_fclose(pl); + m_all_dead_entries=-1; + m_cur_entry = -1; + RefreshList(); +} + + +void Playlist::OnRclickFilelist(NMHDR* pNMHDR, LRESULT* pResult) +{ + if (!m_FileList.GetItemCount()) return; + + CMenu *pPopup = new CMenu(); + pPopup->CreatePopupMenu(); + + if (m_FileList.GetSelectedCount()==1) { + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_PLAY, "Play"); + pPopup->AppendMenu(MF_SEPARATOR, 0, ""); + } + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_SEL_REV, "Inverse Selection"); + if (m_FileList.GetSelectedCount()) pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_REM_FILE, "Remove File(s)"); + if (m_FileList.GetItemCount()>1) { + pPopup->AppendMenu(MF_SEPARATOR, 0, ""); + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_SORT_TITLE, "Sort By Title"); + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_SORT_FILE, "Sort By File Name"); + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_SORT_DUR, "Sort By Duration"); + pPopup->AppendMenu(MF_SEPARATOR, 0, ""); + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_SORT_REV, "Reverse List"); + pPopup->AppendMenu(MF_STRING | MF_ENABLED, ID_PL_RANDOM, "Randomize"); + } + + POINT pt; + GetCursorPos(&pt); + pPopup->TrackPopupMenu(TPM_RIGHTBUTTON, pt.x, pt.y, this); + delete pPopup; + + *pResult = 0; +} + +void Playlist::OnReverseSelection() +{ + u32 i; + POSITION pos = m_FileList.GetFirstSelectedItemPosition(); + while (pos != NULL) { + int nItem = m_FileList.GetNextSelectedItem(pos); + PLEntry *ple = (PLEntry *) m_FileList.GetItemData(nItem); + ple->m_bIsSelected = GF_TRUE; + } + + for (i=0; im_bIsSelected = (Bool) !ple->m_bIsSelected; + } + RefreshList(); +} + +void Playlist::OnReverseList() +{ + u32 count = gf_list_count(m_entries); + u32 hcount = count / 2; + count--; + for (u32 i=0; i1) { + u32 pos = gf_rand() % (gf_list_count(m_entries)-1); + PLEntry *ple = (PLEntry *)gf_list_get(m_entries, pos); + gf_list_rem(m_entries, pos); + gf_list_add(new_entries, ple); + } + PLEntry *ple = (PLEntry *)gf_list_get(m_entries, 0); + gf_list_rem(m_entries, 0); + gf_list_add(new_entries, ple); + + gf_list_del(m_entries); + m_entries = new_entries; + m_cur_entry = -1; + RefreshList(); +} + +void Playlist::Sort(u32 type) +{ + u32 i, j, smallest; + if (gf_list_count(m_entries)<=1) return; + + for (i=0; im_url, ple2->m_url); + break; + case 1: + test = stricmp(ple1->m_disp_name, ple2->m_disp_name); + break; + case 2: + test = ple1->m_duration - ple2->m_duration; + break; + } + if (test<0) smallest = j; + } + PLEntry *ple = (PLEntry *)gf_list_get(m_entries, smallest); + gf_list_rem(m_entries, smallest); + gf_list_insert(m_entries, ple, i); + } + m_cur_entry = -1; + RefreshList(); +} + +void Playlist::OnSortFile() { + Sort(0); +} +void Playlist::OnSortTitle() { + Sort(1); +} +void Playlist::OnSortDuration() { + Sort(2); +} + + +Bool Playlist::HasValidEntries() +{ + u32 nb_dead = 0; + if (m_all_dead_entries==-1) { + for (u32 i=0; im_bIsPlaying = GF_FALSE; + if (ple->m_bIsDead) nb_dead ++; + } + m_all_dead_entries = (nb_dead==gf_list_count(m_entries)) ? 1 : 0; + } + if (m_all_dead_entries==1) return GF_FALSE; + return GF_TRUE; +} + +void Playlist::RefreshCurrent() +{ + if (m_cur_entry==-1) return; + PLEntry *ple = (PLEntry *) gf_list_get(m_entries, m_cur_entry); + if (ple && ple->m_bIsPlaying) { + ple->m_bIsPlaying = GF_FALSE; + UpdateEntry(m_cur_entry); + } +} + +void Playlist::Play() +{ + PLEntry *ple; + + if (!HasValidEntries()) return; + + RefreshCurrent(); + if (m_cur_entry==-1) m_cur_entry = 0; + + if (m_cur_entry >= (s32)gf_list_count(m_entries)) { + if (!GetApp()->m_Loop) return; + m_cur_entry = 0; + } + + ple = (PLEntry *) gf_list_get(m_entries, m_cur_entry); + assert(ple); + if (ple->m_bIsDead) { + m_cur_entry++; + Play(); + } else { + char szPLE[20]; + ple->m_bIsPlaying = GF_TRUE; + UpdateEntry(m_cur_entry); + sprintf(szPLE, "%d", m_cur_entry); + gf_cfg_set_key(GetApp()->m_user.config, "General", "PLEntry", szPLE); + GetApp()->m_pMainWnd->PostMessage(WM_OPENURL); + } +} + +void Playlist::OnDblclkFilelist(NMHDR* pNMHDR, LRESULT* pResult) +{ + POSITION pos = m_FileList.GetFirstSelectedItemPosition(); + RefreshCurrent(); + m_cur_entry = m_FileList.GetNextSelectedItem(pos); + Play(); + *pResult = 0; +} + +void Playlist::OnPlPlay() +{ + POSITION pos = m_FileList.GetFirstSelectedItemPosition(); + + RefreshCurrent(); + m_cur_entry = m_FileList.GetNextSelectedItem(pos); + Play(); +} + +void Playlist::Truncate() +{ + while (m_cur_entry+1 < (s32)gf_list_count(m_entries)) { + PLEntry *ple = (PLEntry *) gf_list_get(m_entries, m_cur_entry+1); + gf_list_rem(m_entries, m_cur_entry+1); + delete ple; + } + RefreshList(); +} + + +void Playlist::QueueURL(CString filename) +{ + char *ext = (char *) strrchr(filename, '.'); + if (ext && (!stricmp(ext, ".m3u") || !stricmp(ext, ".pls")) ) { + OpenPlayList(filename); + } else { + PLEntry *ple = new PLEntry(filename); + gf_list_add(m_entries, ple); + } + m_all_dead_entries=-1; +} + +void Playlist::PlayNext() +{ + s32 count = (s32)gf_list_count(m_entries); + + RefreshCurrent(); + if (1+m_cur_entry < count) { + m_cur_entry++; + Play(); + } else if ((1+m_cur_entry == count) && GetApp()->m_Loop) { + m_cur_entry=0; + Play(); + } +} + +void Playlist::PlayPrev() +{ + RefreshCurrent(); + if (m_cur_entry>0) { + m_cur_entry--; + Play(); + } +} + +void Playlist::SetDead() +{ + PLEntry *ple = (PLEntry *) gf_list_get(m_entries, m_cur_entry); + if (ple) { + ple->m_bIsDead = GF_TRUE; + UpdateEntry(m_cur_entry); + m_all_dead_entries=-1; + if (ple->m_bIsPlaying) PlayNext(); + } +} +void Playlist::SetDuration(u32 duration) +{ + PLEntry *ple = (PLEntry *) gf_list_get(m_entries, m_cur_entry); + if (ple) { + ple->m_duration = duration; + UpdateEntry(m_cur_entry); + } +} + +CString Playlist::GetDisplayName() +{ + PLEntry *ple = (PLEntry *) gf_list_get(m_entries, m_cur_entry); + if (ple) return CString(ple->m_disp_name); + return CString(""); +} + +CString Playlist::GetURL() +{ + PLEntry *ple = (PLEntry *) gf_list_get(m_entries, m_cur_entry); + if (ple) return CString(ple->m_url); + return CString(""); +} + diff --git a/applications/deprecated/old_arch/osmo4_w32/Playlist.h b/applications/deprecated/old_arch/osmo4_w32/Playlist.h new file mode 100644 index 0000000..f42cfe2 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/Playlist.h @@ -0,0 +1,120 @@ +#if !defined(AFX_PLAYLIST_H__EA74376A_83DF_435E_8484_A15BF5B77A32__INCLUDED_) +#define AFX_PLAYLIST_H__EA74376A_83DF_435E_8484_A15BF5B77A32__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// Playlist.h : header file +// + + +class PLEntry +{ +public: + PLEntry(CString url, char *path = NULL); + ~PLEntry(); + + char *m_url; + char *m_disp_name; + u32 m_duration; + + Bool m_bIsSelected; + Bool m_bIsDead; + Bool m_bIsPlaying; +}; + + +///////////////////////////////////////////////////////////////////////////// +// Playlist dialog + +class Playlist : public CDialog +{ +// Construction +public: + Playlist(); + virtual ~Playlist(); + + virtual BOOL Create() { + /*use desktop window to enable playlist behind player*/ + return CDialog::Create(IDD_PLAYLIST, GetDesktopWindow()); + } + + CToolBar m_toolBar; + GF_List *m_entries; + + void Clear(); + void ClearButPlaying(); + void RefreshList(); + void AddDir(Bool do_recurse); + void Truncate(); + void SetDead(); + void SetDuration(u32 duration); + + void Play(); + void PlayNext(); + void PlayPrev(); + Bool HasValidEntries(); + CString GetDisplayName(); + CString GetURL(); + + void OpenPlayList(CString fileName); + + void QueueURL(CString filename); + s32 m_cur_entry; + +// Dialog Data + //{{AFX_DATA(Playlist) + enum { IDD = IDD_PLAYLIST}; + CListCtrl m_FileList; + //}}AFX_DATA + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(Playlist) +protected: + virtual void DoDataExchange(CDataExchange* pDX); + virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult); + //}}AFX_VIRTUAL + +// Implementation +protected: + s32 m_all_dead_entries; + void UpdateEntry(u32 idx); + void RefreshCurrent(); + void Sort(u32 type); + void Save(char *szPath, Bool save_m3u); + + // Generated message map functions + //{{AFX_MSG(Playlist) + virtual BOOL OnInitDialog() ; + afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg void OnDropFiles(HDROP hDropInfo); + afx_msg void OnPlAddFile(); + afx_msg void OnPlRemFile(); + afx_msg void OnSelUp(); + afx_msg void OnSelDown(); + afx_msg void OnPlSave(); + afx_msg void OnClose(); + afx_msg void OnPlRemDead(); + afx_msg void OnPlRemAll(); + afx_msg void OnPlAddDir(); + afx_msg void OnPlAddDirRec(); + afx_msg void OnPlAddUrl(); + afx_msg void OnPlOpen(); + afx_msg void OnReverseSelection(); + afx_msg void OnReverseList(); + afx_msg void OnRandomize(); + afx_msg void OnSortTitle(); + afx_msg void OnSortFile(); + afx_msg void OnSortDuration(); + afx_msg void OnPlPlay(); + afx_msg void OnRclickFilelist(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnDblclkFilelist(NMHDR* pNMHDR, LRESULT* pResult); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_PLAYLIST_H__EA74376A_83DF_435E_8484_A15BF5B77A32__INCLUDED_) diff --git a/applications/deprecated/old_arch/osmo4_w32/Sliders.cpp b/applications/deprecated/old_arch/osmo4_w32/Sliders.cpp new file mode 100644 index 0000000..dc73f50 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/Sliders.cpp @@ -0,0 +1,148 @@ +// Sliders.cpp : implementation file +// + +#include "stdafx.h" +#include "osmo4.h" +#include "Sliders.h" +#include + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// Sliders dialog + +Sliders::Sliders(CWnd* pParent /*=NULL*/) + : CDialog(Sliders::IDD, pParent) +{ + //{{AFX_DATA_INIT(Sliders) + //}}AFX_DATA_INIT + + m_grabbed = GF_FALSE; +} + + +void Sliders::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(Sliders) + DDX_Control(pDX, ID_AUDIO_VOL, m_AudioVol); + DDX_Control(pDX, ID_SLIDER, m_PosSlider); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(Sliders, CDialog) + //{{AFX_MSG_MAP(Sliders) + ON_WM_HSCROLL() + ON_WM_SIZE() + ON_WM_CLOSE() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// Sliders message handlers + +void Sliders::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) +{ + + Osmo4 *app = GetApp(); + if (pScrollBar->GetDlgCtrlID() == ID_SLIDER) { + switch (nSBCode) { + case TB_PAGEUP: + case TB_PAGEDOWN: + case TB_LINEUP: + case TB_LINEDOWN: + case TB_TOP: + case TB_BOTTOM: +// m_grabbed = GF_TRUE; + break; + case TB_THUMBPOSITION: + case TB_THUMBTRACK: + m_grabbed = GF_TRUE; + break; + case TB_ENDTRACK: + if (m_grabbed) { + if (!app->can_seek || !app->m_isopen) { + m_PosSlider.SetPos(0); + } else { + u32 range = m_PosSlider.GetRangeMax() - m_PosSlider.GetRangeMin(); + u32 seek_to = m_PosSlider.GetPos(); + app->PlayFromTime(seek_to); + } + m_grabbed = GF_FALSE; + } + break; + } + } + if (pScrollBar->GetDlgCtrlID() == ID_AUDIO_VOL) { + u32 vol = m_AudioVol.GetPos(); + gf_term_set_option(app->m_term, GF_OPT_AUDIO_VOLUME, vol); + } + CDialog::OnHScroll(nSBCode, nPos, pScrollBar); +} + +void Sliders::OnSize(UINT nType, int cx, int cy) +{ + CDialog::OnSize(nType, cx, cy); + + if (!m_PosSlider.m_hWnd) return; + RECT rc, rc2; + + u32 tw = 40; + //m_PosSlider.GetClientRect(&rc); + //rc.right = rc.left + cx; + //m_PosSlider.SetWindowPos(this, rc.left, rc.top, rc.right, rc.bottom, SWP_NOZORDER | SWP_NOMOVE); + + m_PosSlider.GetClientRect(&rc); + rc.right = rc.left + cx - tw; + rc.top += 10; + rc.bottom += 10; + m_PosSlider.SetWindowPos(this, rc.left, rc.top, rc.right, rc.bottom, SWP_NOZORDER | SWP_NOMOVE); + + const UINT nPixelsLength = 24; + m_PosSlider.ModifyStyle(0,TBS_FIXEDLENGTH,FALSE); + m_PosSlider.SendMessage(TBM_SETTHUMBLENGTH,nPixelsLength,0); + + m_AudioVol.GetClientRect(&rc2); + rc2.top = rc2.bottom = cy/2; + rc2.top -= cy/3; + rc2.bottom += cy/3; + rc2.left = rc.right; + rc2.right = rc.right+tw; + m_AudioVol.MoveWindow(&rc2); + +} + +/*we sure don't want to close this window*/ +void Sliders::OnClose() +{ + u32 i = 0; + return; +} + +BOOL Sliders::PreTranslateMessage(MSG* pMsg) +{ + if (pMsg->message == WM_KEYDOWN) { + GetApp()->m_pMainWnd->SetFocus(); + GetApp()->m_pMainWnd->PostMessage(pMsg->message, pMsg->wParam, pMsg->lParam); + return TRUE; + } + return CDialog::PreTranslateMessage(pMsg); +} + + +BOOL Sliders::OnInitDialog() +{ + CDialog::OnInitDialog(); + m_AudioVol.SetRange(0, 100); + return TRUE; +} + +void Sliders::SetVolume() +{ + m_AudioVol.SetPos(gf_term_get_option(GetApp()->m_term, GF_OPT_AUDIO_VOLUME)); +} diff --git a/applications/deprecated/old_arch/osmo4_w32/Sliders.h b/applications/deprecated/old_arch/osmo4_w32/Sliders.h new file mode 100644 index 0000000..c1a6319 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/Sliders.h @@ -0,0 +1,62 @@ +#if !defined(AFX_SLIDERS_H__3542255E_1376_4FB7_91E7_B4841BB4F173__INCLUDED_) +#define AFX_SLIDERS_H__3542255E_1376_4FB7_91E7_B4841BB4F173__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// Sliders.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// Sliders dialog +class MySliderCtrl : public CSliderCtrl +{ +public: + MySliderCtrl () {} // standard constructor + +protected: + afx_msg virtual void OnLButtonDown(UINT nFlags, CPoint point); +}; + +class Sliders : public CDialog +{ +// Construction +public: + Sliders(CWnd* pParent = NULL); // standard constructor + + void SetVolume(); + Bool m_grabbed; + +// Dialog Data + //{{AFX_DATA(Sliders) + enum { IDD = IDD_SLIDERS }; + CSliderCtrl m_AudioVol; + CSliderCtrl m_PosSlider; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(Sliders) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(Sliders) + afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar); + afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg void OnClose(); + virtual BOOL OnInitDialog(); + virtual BOOL PreTranslateMessage(MSG* pMsg); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_SLIDERS_H__3542255E_1376_4FB7_91E7_B4841BB4F173__INCLUDED_) diff --git a/applications/deprecated/old_arch/osmo4_w32/StdAfx.cpp b/applications/deprecated/old_arch/osmo4_w32/StdAfx.cpp new file mode 100644 index 0000000..cdfba52 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/StdAfx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// GPAC.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/applications/deprecated/old_arch/osmo4_w32/StdAfx.h b/applications/deprecated/old_arch/osmo4_w32/StdAfx.h new file mode 100644 index 0000000..879a8b4 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/StdAfx.h @@ -0,0 +1,30 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__1EEB44C5_1152_4872_8CA7_BD2994085EDC__INCLUDED_) +#define AFX_STDAFX_H__1EEB44C5_1152_4872_8CA7_BD2994085EDC__INCLUDED_ + +#ifndef WINVER +#define WINVER 0x0501 +#endif + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__1EEB44C5_1152_4872_8CA7_BD2994085EDC__INCLUDED_) diff --git a/applications/deprecated/old_arch/osmo4_w32/res/Osmo4.rc2 b/applications/deprecated/old_arch/osmo4_w32/res/Osmo4.rc2 new file mode 100644 index 0000000..b593608 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/res/Osmo4.rc2 @@ -0,0 +1,13 @@ +// +// Osmo4.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/applications/deprecated/old_arch/osmo4_w32/res/error.ico b/applications/deprecated/old_arch/osmo4_w32/res/error.ico new file mode 100644 index 0000000000000000000000000000000000000000..8a2d46cccd645a3ffde4476766fb4cbef301a5d0 GIT binary patch literal 766 zcmdUtF$w}P5JlgrAX{l=Yf5jSH#0}D4SN#VX1$4EV`-;_?D%6KZoopz{gXeF$x8x( zK&5bQNz`=$E7gtcd>X4KlqV^cV zA@^xRZXxmyhPr9P07KIbu{M+#N?e>BS))Jjv(8NI#@rq(?8(}Cw)#PPGp{R7@V_%5Jefp0b?LQa*UJ#a)b1_%T%Ek*g~CJb%X#TXYdWQ-NFD5qpWx*?R z_9vasiWq#9Vk$Qqpf}aEbEcbdqfl6ZyU&vc1$1a|JV{cV`{m*QOJX`Sl|x2!zsuWzq| z#eMI6@LvFhk3xfKLeno&Xk}t1j_bAgac*(V#<_8Sbs5d`x7_l3 vOSy}B6z_TKgP?Kc6kczBu3e6bCic&HRrNvV=H|VK_-ZHcWpVA{_W$f3h9LYm literal 0 HcmV?d00001 diff --git a/applications/deprecated/old_arch/osmo4_w32/res/message.ico b/applications/deprecated/old_arch/osmo4_w32/res/message.ico new file mode 100644 index 0000000000000000000000000000000000000000..95f120e148570e8679a718544de9bfe0cec4e987 GIT binary patch literal 766 zcmdUtI}QRd3`8e@M1#auw4~%194$xl0elF`Jq6}@DYC54QX#=4<4?wkrifHnwGK** z8|g&8zzVOhXh)P9+Iuual-8b!NGUTTrx4RtL{^ZMM;ZPB2Y0ubQTK4~Ju-bC_(K0; pVEMile#F4cPB;346@OqIw{wIYBif$dvW>gSDsyI<%{zJb10H3Su}J^` literal 0 HcmV?d00001 diff --git a/applications/deprecated/old_arch/osmo4_w32/res/osmo4.ico b/applications/deprecated/old_arch/osmo4_w32/res/osmo4.ico new file mode 100644 index 0000000000000000000000000000000000000000..36ff66711771065305f263aee09bda915091c6b0 GIT binary patch literal 15086 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 Ih+0ATC&$&)D*ylh literal 0 HcmV?d00001 diff --git a/applications/deprecated/old_arch/osmo4_w32/res/pause.ico b/applications/deprecated/old_arch/osmo4_w32/res/pause.ico new file mode 100644 index 0000000000000000000000000000000000000000..7fab3531842d5177d069e52f04a883c6ade58364 GIT binary patch literal 1078 zcmd6mO-{ow5QSe_q_VJRj*u1VE#OwL!#g${$_MZvy5tC5Ak6n1tOnC85E5dNnK%Ek zl4ohqM$xvE*RYRofzCqvtYxZM3%n3 zWM?ATth0@c<6x5ajoCeR7$&^$%uean!95~g1rWCUrDG~?u!y;zTg9Ei$6$c@se-qZLz-L<&*)1%A`S8Cs%)q*w9&$eJa*kUjr7QZy*hoLko|js=np^5ruYFcT zbFRrB(K_dje_bLo4#P|r!;1UcIiEYF9=LveD9s4zw%C+g)E jRZog~t9{rHXEJhsJqR6VKBxLtIBc64?7;cPAk2i57?sr{ zYpkusoV)qX)uiA9juAU$bq_zT6V~^Q5VU3Nrf{F?mldvA@y+x63Do>KSN@~I_It2n>1-Ww=r#f?viR8qwyj-s@H{9KfVQ-ZVl L^ZljK^e_DY4LCYJ literal 0 HcmV?d00001 diff --git a/applications/deprecated/old_arch/osmo4_w32/res/stop.ico b/applications/deprecated/old_arch/osmo4_w32/res/stop.ico new file mode 100644 index 0000000000000000000000000000000000000000..52e1a3b6ac8b5d228d1b3e16570ebda9cedbccf1 GIT binary patch literal 1078 zcmd6mF>b;@5JkTs5+GTDBe;sVMRF_X*pebeT5jV5>>(&~1d1q|_t&g37B136`DS+J z&z~8#n3XEc)HE9zpEKzNzu9zrTS?DzX-lrb-mpqfoKm3nF#;k=2%Y03RNJfi7E-g+GuG`i1|JP*n| c56;fv4VSfa=3TeEZ(Bf`vQp!E_xYLr0IvM^sQ>@~ literal 0 HcmV?d00001 diff --git a/applications/deprecated/old_arch/osmo4_w32/resource.h b/applications/deprecated/old_arch/osmo4_w32/resource.h new file mode 100644 index 0000000..3a13e88 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_w32/resource.h @@ -0,0 +1,323 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Osmo4.rc +// +#define IDD_ABOUTBOX 100 +#define IDR_MAINFRAME 128 +#define IDR_GPACTYPE 129 +#define IDD_CONTROL 132 +#define IDI_PLAY 141 +#define IDI_STOP 142 +#define IDI_PAUSE 143 +#define IDI_MESSAGE 144 +#define IDI_ERR 145 +#define IDD_OPTIONS 152 +#define IDD_OPT_GEN 154 +#define IDD_OPT_RENDER 155 +#define IDD_OPT_AUDIO 157 +#define IDD_OPT_VIDEO 158 +#define IDD_OPT_HTTP 159 +#define IDD_OPT_FONT 161 +#define IDD_OPT_SYSTEMS 162 +#define IDD_OPT_STREAM 164 +#define IDD_PROPERTIES 166 +#define IDD_OPT_DECODER 167 +#define IDD_OPT_RENDER2D 168 +#define IDD_OPT_RENDER3D 169 +#define IDR_MAINTOOLS 170 +#define ID_MAINSLIDER 171 +#define IDD_SLIDERS 173 +#define IDD_NAVBAR 176 +#define IDD_PLAYLIST 177 +#define IDR_PLAYLIST 178 +#define IDD_OPT_FILETYPES 179 +#define IDR_MENUPL 182 +#define IDD_PASSWD 188 +#define IDR_MAINACCEL 190 +#define IDC_FILES_MIMES 1000 +#define IDC_FILES_PLUG 1001 +#define ID_AUDIO_VOL 1002 +#define IDC_TXT_SITE 1003 +#define IDC_EDIT_USER 1004 +#define IDC_EDIT_PASSWORD 1005 +#define IDC_PLAY 1006 +#define IDC_STOP 1007 +#define IDC_COMBOURL 1008 +#define IDC_BROWSE 1009 +#define IDC_BUTGO 1010 +#define IDC_SPIN_OPT 1011 +#define IDC_OPT_SET_NAME 1012 +#define IDC_LANG 1013 +#define IDC_LOOP 1014 +#define IDC_AUTOSTART 1015 +#define IDC_FILEASSOC 1016 +#define IDC_NO_CONSOLE 1017 +#define IDC_BIFS_RATE 1018 +#define IDC_DEC_THREAD 1019 +#define IDC_DIRECTRENDER 1020 +#define IDC_HWMEMORY 1021 +#define IDC_BIFSDROP 1023 +#define IDC_SPIN_AUDIO 1024 +#define IDC_EDIT_AUDIO 1025 +#define IDC_FORCE_AUDIO 1026 +#define IDC_SPIN_FPS 1027 +#define IDC_AUDIO_FPS 1028 +#define IDC_AUDIO_MULTICH 1029 +#define IDC_GD_LIST 1031 +#define IDC_FAST_RENDER 1032 +#define IDC_FORCE_DURATION 1033 +#define IDC_YUV 1034 +#define IDC_AUDIO_RESYNC 1035 +#define IDC_AUDIO_NOTIFS 1036 +#define IDC_STOPATEND 1037 +#define IDC_CLEAN_CACHE 1038 +#define IDC_RESTART_CACHE 1039 +#define IDC_NOTIFY_PROG 1040 +#define IDC_BROWSE_CACHE 1041 +#define IDC_LOOKFORSUB 1042 +#define IDD_OPT_MCACHE 1043 +#define IDC_DRIVER_LIST 1044 +#define IDC_AA_LIST 1045 +#define IDC_ZOOM_SCALABLE 1046 +#define IDD_OPENFILE 1047 +#define IDC_SINGLE_INSTANCE 1048 +#define IDC_VIDEO_LIST 1049 +#define IDC_FONT_LIST 1050 +#define IDC_BROWSE_FONT 1051 +#define IDC_USE_FONT 1052 +#define IDC_SAVEOPT 1053 +#define IDC_PORT 1054 +#define IDC_RTSP 1055 +#define IDC_TIMEOUT 1056 +#define IDC_BUFFER 1057 +#define IDC_REBUFFER_LEN 1058 +#define IDC_REBUFFER 1059 +#define IDC_ODTREE 1060 +#define IDC_VIEWSG 1061 +#define IDC_FORCE_SIZE 1062 +#define IDC_USE_RENDER3D 1063 +#define IDC_ODINFO 1064 +#define IDC_WORLD 1065 +#define IDC_GOGPAC 1066 +#define IDC_GOOSMO4 1067 +#define IDC_DUMP_XMT 1068 +#define IDC_OBJECT_TIME 1069 +#define IDC_FORMAT_YUV 1070 +#define IDC_DRAW_BOUNDS 1071 +#define IDC_REORDER 1072 +#define IDC_TEXTURE_MODE 1073 +#define IDC_SWITCH_RES 1074 +#define IDC_AUDEC_LIST 1075 +#define IDC_VIDEC_LIST 1076 +#define IDC_ASSOCIATE 1077 +#define IDC_RASTER_OUTLINE 1078 +#define IDC_EMUL_POW2 1079 +#define IDC_DISABLE_POLY_AA 1080 +#define IDC_WIRE_NONE 1081 +#define IDC_WIRE_ONLY 1082 +#define IDC_WIRE_BOTH 1083 +#define IDC_DISABLE_TX_RECT 1084 +#define IDC_BITMAP_USE_PIXEL 1085 +#define IDC_TEXTURE_TEXT 1086 +#define IDC_SLIDER 1087 +#define ID_SLIDER 1088 +#define IDC_SELECT 1089 +#define IDC_NO_BACKCULL 1090 +#define IDC_FILES_EXT 1091 +#define IDC_VIEWSEL 1094 +#define IDC_ADDRESS 1096 +#define IDC_DUMTXT 1097 +#define IDC_FILELIST 1109 +#define IDC_BROWSE_MCACHE 1110 +#define IDC_MCACHE_OVERWRITE 1111 +#define IDC_MCACHE_USENAME 1112 +#define IDC_BASEPRES 1113 +#define ID_FILE_EXIT 1114 +#define ID_H_ABOUT 1115 +#define ID_PLAYLIST_LOOP 1116 +#define ID_OPEN_FILE 1117 +#define ID_VIEW_CONTROL 1118 +#define ID_VIEW_ORIGINAL 1119 +#define ID_VIEW_FULLSCREEN 1120 +#define ID_AR_KEEP 1121 +#define IDC_DRAW_NORMALS 1122 +#define IDC_BACK_CULL 1123 +#define IDC_DRAW_MODE 1124 +#define ID_SHORTCUTS 1125 +#define ID_FILE_RESTART 1126 +#define ID_OPT_QUALITY 1127 +#define ID_FILE_PROP 1128 +#define ID_FILE_STEP 1129 +#define IDD_CONFIGURE 1130 +#define ID_VIEW_SCALABLE 1131 +#define ID_OPEN_URL 1132 +#define ID_FILE_RELOAD 1133 +#define ID_FILE_PLAY 1134 +#define ID_NAVIGATE_NONE 1135 +#define ID_NAVIGATE_WALK 1136 +#define ID_NAVIGATE_FLY 1137 +#define ID_NAVIGATE_EXAM 1138 +#define ID_NAVIGATE_PAN 1139 +#define ID_NAVIGATE_SLIDE 1140 +#define ID_NAVIGATE_GAME 1141 +#define ID_AR_FILL 1142 +#define ID_AR_43 1143 +#define ID_AR_169 1144 +#define ID_FILE_MIGRATE 1145 +#define ID_NAV_RESET 1151 +#define ID_FILE_STOP 1152 +#define ID_FILE_PREV 1155 +#define ID_FILE_NEXT 1156 +#define ID_FILE_PROPS 1157 +#define ID_SWITCH_RENDER 1158 +#define ID_RELOAD_TERMINAL 1159 +#define ID_VIEW_PLAYLIST 1160 +#define ID_NAVIGATE_ORBIT 1161 +#define ID_COLLIDE_NONE 1162 +#define ID_COLLIDE_REG 1163 +#define ID_COLLIDE_DISP 1164 +#define ID_GRAVITY 1165 +#define ID_HEADLIGHT 1166 +#define ID_NAV_INFO 1167 +#define ID_NAV_PREV 1168 +#define ID_NAV_NEXT 1169 +#define ID_CLEAR_NAV 1170 +#define ID_TIMER 1171 +#define ID_FPS 1172 +#define ID_VIEWPORT_EMPTY 1173 +#define ID_PL_REM_ALL 1174 +#define ID_PL_REM_DEAD 1175 +#define ID_PL_ADD_DIR_REC 1176 +#define ID_PL_ADD_FILE 1177 +#define ID_PL_REM_FILE 1178 +#define ID_PL_OPEN 1179 +#define ID_PL_SAVE 1180 +#define ID_PL_UP 1181 +#define ID_PL_DOWN 1182 +#define ID_VIEW_PL 1183 +#define ID_PL_ADD_DIR 1184 +#define ID_PL_ADD_URL 1185 +#define ID_PL_PLAY 1186 +#define ID_PL_SEL_REV 1187 +#define ID_PL_SORT_TITLE 1188 +#define ID_PL_SORT_FILE 1189 +#define ID_PL_SORT_DUR 1190 +#define ID_PL_SORT_REV 1191 +#define ID_PL_RANDOM 1192 +#define ID_ADD_SUBTITLE 1193 +#define ID_NAVIGATE_VR 1194 +#define ID_REC_ENABLE 1195 +#define ID_REC_STOP 1196 +#define ID_REC_ABORT 1197 +#define ID_AUDIO_EMPTY 1198 +#define ID_VIDEO_EMPTY 1199 +#define ID_SUBS_EMPTY 1200 +#define ID_VIEW_CPU 1201 +#define IDC_SAX_PROGRESSIVE 1202 +#define IDC_SAX_DELAY 1203 +#define IDC_HTTP_PROXY 1204 +#define IDC_HTTP_USE_PROXY 1205 +#define IDC_LOG_LEVEL 1210 +#define IDC_TOOL_CORE 1211 +#define IDC_TOOL_CODING 1212 +#define IDC_TOOL_CONTAINER 1213 +#define IDC_TOOL_NET 1214 +#define IDC_TOOL_RTP 1215 +#define IDC_TOOL_AUTHOR 1216 +#define IDC_TOOL_CODEC 1217 +#define IDC_TOOL_PARSER 1218 +#define IDC_TOOL_MEDIA 1219 +#define IDC_TOOL_SCENE 1220 +#define IDC_TOOL_SCRIPT 1221 +#define IDC_TOOL_COMPOSE 1222 +#define IDC_TOOL_RENDER 1223 +#define IDC_TOOL_MMIO 1224 +#define IDC_TOOL_SYNC 1225 +#define IDD_OPT_LOGS 1226 +#define ID_VP_0 1300 +#define ID_VP_1 1301 +#define ID_VP_2 1302 +#define ID_VP_3 1303 +#define ID_VP_4 1304 +#define ID_VP_5 1305 +#define ID_VP_6 1306 +#define ID_VP_7 1307 +#define ID_VP_8 1308 +#define ID_VP_9 1309 +#define ID_VP_10 1310 +#define ID_VP_11 1311 +#define ID_VP_12 1312 +#define ID_VP_13 1313 +#define ID_VP_14 1314 +#define ID_VP_15 1315 +#define ID_VP_16 1316 +#define ID_VP_17 1317 +#define ID_VP_18 1318 +#define ID_VP_19 1319 +#define ID_NAV_PREV_0 1320 +#define ID_NAV_PREV_1 1321 +#define ID_NAV_PREV_2 1322 +#define ID_NAV_PREV_3 1323 +#define ID_NAV_PREV_4 1324 +#define ID_NAV_PREV_5 1325 +#define ID_NAV_PREV_6 1326 +#define ID_NAV_PREV_7 1327 +#define ID_NAV_PREV_8 1328 +#define ID_NAV_PREV_9 1329 +#define ID_NAV_NEXT_0 1330 +#define ID_NAV_NEXT_1 1331 +#define ID_NAV_NEXT_2 1332 +#define ID_NAV_NEXT_3 1333 +#define ID_NAV_NEXT_4 1334 +#define ID_NAV_NEXT_5 1335 +#define ID_NAV_NEXT_6 1336 +#define ID_NAV_NEXT_7 1337 +#define ID_NAV_NEXT_8 1338 +#define ID_NAV_NEXT_9 1339 +#define ID_SELOBJ_0 1340 +#define ID_SELOBJ_1 1341 +#define ID_SELOBJ_2 1342 +#define ID_SELOBJ_3 1343 +#define ID_SELOBJ_4 1344 +#define ID_SELOBJ_5 1345 +#define ID_SELOBJ_6 1346 +#define ID_SELOBJ_7 1347 +#define ID_SELOBJ_8 1348 +#define ID_SELOBJ_9 1349 +#define ID_SELOBJ_10 1350 +#define ID_SELOBJ_11 1351 +#define ID_SELOBJ_12 1352 +#define ID_SELOBJ_13 1353 +#define ID_SELOBJ_14 1354 +#define ID_SELOBJ_15 1355 +#define ID_SELOBJ_16 1356 +#define ID_SELOBJ_17 1357 +#define ID_SELOBJ_18 1358 +#define ID_SELOBJ_19 1359 +#define ID_SELOBJ_20 1360 +#define ID_SELOBJ_21 1361 +#define ID_SELOBJ_22 1362 +#define ID_SELOBJ_23 1363 +#define ID_SELOBJ_24 1364 +#define ID_SELOBJ_25 1365 +#define ID_SELOBJ_26 1366 +#define ID_SELOBJ_27 1367 +#define ID_SELOBJ_28 1368 +#define ID_SELOBJ_29 1369 +#define ID_SETCHAP_FIRST 2000 +#define ID_SETCHAP_LAST 2200 +#define ID_FILE_COPY 32961 +#define ID_FILE_PASTE 32962 +#define ID_CONFIG_RELOAD 32963 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 192 +#define _APS_NEXT_COMMAND_VALUE 32981 +#define _APS_NEXT_CONTROL_VALUE 1131 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/applications/deprecated/old_arch/osmo4_wce/MainFrm.cpp b/applications/deprecated/old_arch/osmo4_wce/MainFrm.cpp new file mode 100644 index 0000000..f13c6cb --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wce/MainFrm.cpp @@ -0,0 +1,661 @@ +// MainFrm.cpp : implementation of the CMainFrame class +// + +#ifdef _DEBUG +#include "stdafx.h" +#else +#include "stdafx.h" +#undef _DEBUG +#endif + +#include "Osmo4.h" + +#include +#include + +#include "MainFrm.h" +#include + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + + +CChildView::CChildView() +{ +} + +CChildView::~CChildView() +{ + /*since the wndproc is overwritten by the terminal, we detach the handle otherwise we get a nice assertion + failure from windows*/ + HWND hWnd = Detach(); + ::PostMessage(hWnd, WM_QUIT, 0, 0); +} + + +BEGIN_MESSAGE_MAP(CChildView,CWnd ) + //{{AFX_MSG_MAP(CChildView) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + + +///////////////////////////////////////////////////////////////////////////// +// CChildView message handlers + +BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs) +{ + if (!CWnd::PreCreateWindow(cs)) + return FALSE; + + cs.style &= ~WS_BORDER; + cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS, + NULL, HBRUSH(COLOR_WINDOW+1), NULL); + + return TRUE; +} + +///////////////////////////////////////////////////////////////////////////// +// CMainFrame + +IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd) + +BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) + //{{AFX_MSG_MAP(CMainFrame) + ON_WM_CREATE() + ON_WM_SETFOCUS() + ON_COMMAND(ID_APP_EXIT, OnAppExit) + ON_MESSAGE(WM_OPENURL, Open) + ON_MESSAGE(WM_SETTINGCHANGE, OnSIPChange) + ON_MESSAGE(WM_SETSIZE,OnSetSize) + ON_MESSAGE(WM_NAVIGATE,OnNavigate) + ON_WM_SIZE() + ON_COMMAND(ID_FILE_STEP, OnFileStep) + ON_UPDATE_COMMAND_UI(ID_FILE_STEP, OnUpdateFileStep) + ON_COMMAND(ID_FILE_PAUSE, OnFilePause) + ON_UPDATE_COMMAND_UI(ID_FILE_PAUSE, OnUpdateFilePause) + ON_COMMAND(ID_FILE_STOP, OnFileStop) + ON_UPDATE_COMMAND_UI(ID_FILE_STOP, OnUpdateFileStop) + ON_COMMAND(ID_VIEW_FULLSCREEN, OnViewFullscreen) + ON_UPDATE_COMMAND_UI(ID_VIEW_FULLSCREEN, OnUpdateViewFullscreen) + ON_WM_CLOSE() + ON_COMMAND(ID_VIEW_FIT, OnViewFit) + ON_UPDATE_COMMAND_UI(ID_VIEW_FIT, OnUpdateViewFit) + ON_COMMAND(ID_VIEW_AR_ORIG, OnViewArOrig) + ON_COMMAND(ID_VIEW_AR_FILL, OnViewArFill) + ON_COMMAND(ID_VIEW_AR_43, OnViewAr43) + ON_COMMAND(ID_VIEW_AR_169, OnViewAr169) + ON_COMMAND(ID_NAV_NONE, OnNavNone) + ON_COMMAND(ID_NAV_SLIDE, OnNavSlide) + ON_COMMAND(ID_NAV_RESET, OnNaveReset) + ON_COMMAND_RANGE(ID_NAV_NONE, ID_NAV_EXAMINE, OnSetNavigation) + ON_WM_KEYDOWN() + ON_WM_KEYUP() + ON_COMMAND(ID_VIEW_TIMING, OnViewTiming) + ON_UPDATE_COMMAND_UI(ID_VIEW_TIMING, OnUpdateViewTiming) + ON_WM_INITMENUPOPUP() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CMainFrame construction/destruction + +CMainFrame::CMainFrame() +{ + GXOpenInput(); + m_view_timing = 0; + m_restore_fs = 0; +} + +CMainFrame::~CMainFrame() +{ + GXCloseInput(); +} + +void CMainFrame::OnSetFocus(CWnd* pOldWnd) +{ + if (m_restore_fs) { + m_restore_fs = 0; + GetApp()->ShowTaskBar(0); + OnViewFullscreen(); + } +} + +int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) +{ + COsmo4 *app = GetApp(); + + if (CFrameWnd::OnCreate(lpCreateStruct) == -1) + return -1; + + // create a view to occupy the client area of the frame + if (!m_wndView.Create(NULL, NULL, AFX_WS_DEFAULT_VIEW | WS_BORDER, + CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST, NULL)) + { + TRACE0("Failed to create view window\n"); + return -1; + } + m_wndView.ShowWindow(SW_HIDE); + + + if (!m_dumbWnd.Create(NULL, NULL, AFX_WS_DEFAULT_VIEW | WS_BORDER, + CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST, NULL)) + { + TRACE0("Failed to create dumb window\n"); + return -1; + } + m_dumbWnd.SetWindowPos(this, 0, 0, app->m_screen_width, app->m_screen_height-app->m_menu_height, 0L); + m_dumbWnd.ShowWindow(SW_HIDE); + + if (!m_progBar.Create(IDD_CONTROL , this) ) { + TRACE0("Failed to create status bar\n"); + return -1; // fail to create + } + m_progBar.UpdateWindow(); + m_progBar.SetWindowPos(this, 0, 0, app->m_screen_width, app->m_menu_height, 0L); + m_progBar.ShowWindow(SW_SHOWNORMAL); + + +// m_wndCommandBar.m_bShowSharedNewButton = FALSE; + + if (!m_wndCommandBar.Create(this) + || !m_wndCommandBar.InsertMenuBar(IDR_MENU) + || !m_wndCommandBar.AddAdornments() + || !m_wndCommandBar.LoadBitmap(IDR_MAINFRAME) + ) + { + TRACE0("Failed to create CommandBar\n"); + return -1; // fail to create + } + + CToolBarCtrl & toolBar = m_wndCommandBar.GetToolBarCtrl(); + TBBUTTON tb; + memset(&tb, 0, sizeof(tb)); + tb.idCommand = ID_OPEN_FILE; + tb.iBitmap = 0; + tb.fsStyle = TBSTYLE_BUTTON; + toolBar.AddButtons(1, &tb); + tb.idCommand = 0; + tb.iBitmap = 0; + tb.fsStyle = TBSTYLE_SEP; + toolBar.AddButtons(1, &tb); + tb.idCommand = ID_FILE_PAUSE; + tb.iBitmap = 1; + tb.fsStyle = TBSTYLE_BUTTON; + toolBar.AddButtons(1, &tb); + tb.idCommand = ID_FILE_STEP; + tb.iBitmap = 2; + tb.fsStyle = TBSTYLE_BUTTON; + toolBar.AddButtons(1, &tb); + tb.idCommand = ID_FILE_STOP; + tb.iBitmap = 3; + tb.fsStyle = TBSTYLE_BUTTON; + toolBar.AddButtons(1, &tb); + tb.idCommand = 0; + tb.iBitmap = 0; + tb.fsStyle = TBSTYLE_SEP; + toolBar.AddButtons(1, &tb); + + SetIcon(AfxGetApp()->LoadIcon(IDR_MAINFRAME), TRUE); + SetIcon(AfxGetApp()->LoadIcon(IDR_MAINFRAME), FALSE); + SetWindowPos(NULL, 0, 0, app->m_screen_width, app->m_screen_height, 0L); + + SetWindowText(_T("Osmo4")); + return 0; +} + +void CMainFrame::SetPauseButton(Bool force_play_button) +{ + CToolBarCtrl & toolBar = m_wndCommandBar.GetToolBarCtrl(); + TBBUTTON tb; + memset(&tb, 0, sizeof(tb)); + tb.idCommand = ID_FILE_PAUSE; + tb.fsStyle = TBSTYLE_BUTTON; + + if (force_play_button || GetApp()->m_stopped || gf_term_get_option(GetApp()->m_term, GF_OPT_PLAY_STATE)==GF_STATE_PAUSED) { + tb.iBitmap = 4; + } else { + tb.iBitmap = 1; + } + toolBar.DeleteButton(5); + toolBar.InsertButton(5, &tb); +} + + +BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) +{ + if( !CFrameWnd::PreCreateWindow(cs) ) + return FALSE; + // TODO: Modify the Window class or styles here by modifying + // the CREATESTRUCT cs + + + cs.lpszClass = AfxRegisterWndClass(0); + return TRUE; +} + + +///////////////////////////////////////////////////////////////////////////// +// CMainFrame diagnostics + +/* +#ifdef _DEBUG +void CMainFrame::AssertValid() const +{ + CFrameWnd::AssertValid(); +} + +void CMainFrame::Dump(CDumpContext& dc) const +{ + CFrameWnd::Dump(dc); +} + +#endif //_DEBUG +*/ + +///////////////////////////////////////////////////////////////////////////// +// CMainFrame message handlers +BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) +{ + // let the view have first crack at the command + if (m_wndView.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) + return TRUE; + + // otherwise, do default handling + return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); +} + +#define PROGRESS_TIMER 20 +#define PROGRESS_REFRESH_MS 500 + +void CALLBACK EXPORT ProgressTimer(HWND , UINT , UINT nID , DWORD ) +{ + if (nID != PROGRESS_TIMER) return; + ((CMainFrame *) GetApp()->m_pMainWnd)->UpdateTime(); +} + +void CMainFrame::UpdateTime() +{ + u32 now; + + COsmo4 *app = GetApp(); + if (!app->m_open || app->m_stopped) return; + now = gf_term_get_time_in_ms(app->m_term); + if (!now) return; + + if (app->m_can_seek && (now>=app->m_duration + 100)) { + if (gf_term_get_option(app->m_term, GF_OPT_IS_FINISHED)) { + if (app->m_Loop && m_full_screen) { + gf_term_play_from_time(app->m_term, 0, 0); + } else { + OnFileStop(); + if (app->m_Loop) OnFilePause(); + } + return; + } + } + + if (!m_full_screen) m_progBar.SetPosition(now); +} + +void CMainFrame::CloseURL() +{ + COsmo4 *app = GetApp(); + if (!app->m_open) return; + if (m_view_timing) KillTimer(PROGRESS_TIMER); + gf_term_disconnect(app->m_term); + app->m_open = 0; + app->m_can_seek = 0; + app->m_duration = (u32) -1; + m_progBar.m_prev_time = 0; + m_progBar.SetPosition(0); +} + +void CMainFrame::OnAppExit() +{ + CloseURL(); + PostMessage(WM_QUIT); +} + +void CMainFrame::OnSize(UINT nType, int cx, int cy) +{ + COsmo4 *app = GetApp(); + u32 disp_w, disp_h, c_w, c_h, x, y; + + if (m_full_screen) return; + + disp_w = app->m_screen_width; + disp_h = app->m_screen_height; + CFrameWnd::OnSize(nType, disp_w, disp_h); + + x = y = 0; + disp_h -= app->m_menu_height; + + if (m_view_timing) { + disp_h -= app->m_menu_height; + y = app->m_menu_height; + m_progBar.SetWindowPos(this, 0, 0, app->m_screen_width, app->m_menu_height, 0L); + m_progBar.ShowWindow(SW_SHOWNORMAL); + } else { + m_progBar.ShowWindow(SW_HIDE); + } + m_dumbWnd.SetWindowPos(this, 0, y, disp_w, disp_h, 0L); + m_dumbWnd.ShowWindow(SW_SHOW); + + if (m_view_timing) + SetTimer(PROGRESS_TIMER, PROGRESS_REFRESH_MS, ProgressTimer); + + if (!app->m_scene_width || !app->m_scene_height) { + m_wndView.SetWindowPos(this, 0, y, disp_w, disp_h, SWP_SHOWWINDOW); + gf_term_set_size(app->m_term, disp_w, disp_h); + return; + } + + if (!app->m_fit_screen && (app->m_scene_width < disp_w) && (app->m_scene_height < disp_h)) { + c_w = app->m_scene_width; + c_h = app->m_scene_height; + x = (disp_w - c_w) / 2; + y = (disp_h - c_h) / 2; + } else { + c_w = disp_w; + c_h = disp_h; + } + m_wndView.SetWindowPos(this, x, y, c_w, c_h, SWP_SHOWWINDOW | SWP_NOZORDER); + gf_term_set_size(app->m_term, c_w, c_h); +} + + +void CMainFrame::OnViewFullscreen() +{ + COsmo4 *app = GetApp(); + if (!app->m_open) return; + u32 disp_w = app->m_screen_width; + u32 disp_h = app->m_screen_height; + + Bool is_full_screen = !m_full_screen; + + /*prevent resize messages*/ + m_full_screen = 1; + + HWND hWnd = GetSafeHwnd(); + ::SetForegroundWindow(hWnd); + ::CommandBar_Show(m_wndCommandBar.GetSafeHwnd(), is_full_screen ? FALSE : TRUE); + SHFullScreen(hWnd, SHFS_HIDESTARTICON | SHFS_HIDETASKBAR | SHFS_HIDESIPBUTTON); + + if (is_full_screen) { + m_dumbWnd.ShowWindow(SW_HIDE); + + ::MoveWindow(m_hWnd, 0, 0, disp_w, disp_h, 0); + m_wndView.GetWindowRect(&m_view_rc); + m_wndView.SetWindowPos(this, 0, 0, disp_w, disp_h, SWP_NOZORDER); + gf_term_set_option(app->m_term, GF_OPT_FULLSCREEN, is_full_screen); + m_full_screen = 1; + } else { + gf_term_set_option(app->m_term, GF_OPT_FULLSCREEN, is_full_screen); + m_full_screen = 0; + OnSetSize(0,0); + m_dumbWnd.ShowWindow(SW_SHOW); + gf_term_set_option(app->m_term, GF_OPT_REFRESH, 0); + } +} + + +void CMainFrame::OnUpdateViewFullscreen(CCmdUI* pCmdUI) +{ + pCmdUI->Enable(GetApp()->m_open ? TRUE : FALSE); +} + +LONG CMainFrame::OnSetSize(WPARAM wParam, LPARAM lParam) +{ + RECT rc; + if (m_full_screen) return 0; + GetWindowRect(&rc); + SetWindowPos(NULL, 0, 0, rc.right-rc.left, rc.bottom-rc.top, SWP_NOZORDER | SWP_NOMOVE); + return 1; +} + +LONG CMainFrame::Open(WPARAM wParam, LPARAM lParam) +{ + COsmo4 *app = GetApp(); + CloseURL(); + char filename[5000]; + CE_WideToChar((u16 *) (LPCTSTR) app->m_filename, filename); + app->m_stopped = 0; + + if (app->m_reconnect_time) { + gf_term_connect_from_time(app->m_term, filename, app->m_reconnect_time, 0); + app->m_reconnect_time = 0; + } else { + gf_term_connect(app->m_term, filename); + } + app->SetBacklightState(1); + return 1; +} + + + + +LONG CMainFrame::OnNavigate(WPARAM /*wParam*/, LPARAM /*lParam*/) +{ + COsmo4 *app = GetApp(); + char to_url[MAX_PATH]; + CE_WideToChar((u16 *) (LPCTSTR) app->m_navigate_url, to_url); + + if (gf_term_is_supported_url(app->m_term, to_url, 1, app->m_no_mime_fetch)) { + char fileName[MAX_PATH]; + TCHAR w_to_url[MAX_PATH]; + CE_WideToChar((u16 *) (LPCTSTR) app->m_filename, fileName); + char *str = gf_url_concatenate(fileName, to_url); + if (!str) str = gf_strdup(to_url); + CE_CharToWide(str, (u16 *)w_to_url); + gf_free(str); + app->m_filename = w_to_url; + Open(0, 0); + } else { + SHELLEXECUTEINFO info; + console_message = app->m_navigate_url; + console_err = GF_OK; + PostMessage(WM_CONSOLEMSG); + + + if (m_full_screen) { + OnViewFullscreen(); + app->ShowTaskBar(1); + m_restore_fs = 1; + } + + memset(&info, 0, sizeof(SHELLEXECUTEINFO)); + info.cbSize = sizeof(SHELLEXECUTEINFO); + info.lpVerb = L"open"; + info.fMask = SEE_MASK_NOCLOSEPROCESS; + info.lpFile = L"iexplore"; + info.lpParameters = (LPCTSTR) app->m_navigate_url; + info.nShow = SW_SHOWNORMAL; + ShellExecuteEx(&info); + } + return 1; +} + +void CMainFrame::OnFilePause() +{ + COsmo4 *app = GetApp(); + if (app->m_stopped) { + char filename[5000]; + CE_WideToChar((u16 *) (LPCTSTR) app->m_filename, filename); + app->m_stopped = 0; + gf_term_connect(app->m_term, filename); + app->SetBacklightState(1); + + if (m_view_timing) + SetTimer(PROGRESS_TIMER, PROGRESS_REFRESH_MS, ProgressTimer); + + SetPauseButton(); + } else { + app->Pause(); + } +} +void CMainFrame::OnUpdateFilePause(CCmdUI* pCmdUI) +{ + COsmo4 *app = GetApp(); + pCmdUI->Enable((app->m_open || app->m_stopped) ? TRUE : FALSE); +} +void CMainFrame::OnFileStop() +{ + COsmo4 *app = GetApp(); + if (!app->m_open) return; + if (m_full_screen) OnViewFullscreen(); + app->m_stopped = 1; + if (m_view_timing) KillTimer(PROGRESS_TIMER); + gf_term_disconnect(app->m_term); + m_progBar.SetPosition(0); + app->SetBacklightState(0); + SetPauseButton(); +} + +void CMainFrame::OnUpdateFileStop(CCmdUI* pCmdUI) +{ + pCmdUI->Enable( GetApp()->m_open ? TRUE : FALSE); +} + +void CMainFrame::OnFileStep() +{ + COsmo4 *app = GetApp(); + gf_term_set_option(app->m_term, GF_OPT_PLAY_STATE, GF_STATE_STEP_PAUSE); + app->SetBacklightState(0); + SetPauseButton(1); +} +void CMainFrame::OnUpdateFileStep(CCmdUI* pCmdUI) +{ + pCmdUI->Enable(GetApp()->m_open ? TRUE : FALSE); +} + +void CMainFrame::OnClose() +{ + PostMessage(WM_DESTROY); +} + +LONG CMainFrame::OnSIPChange(WPARAM wParam, LPARAM lParam) +{ + if (wParam == SPI_SETSIPINFO) GetApp()->ShowTaskBar(0); + return 1; +} + +void CMainFrame::OnViewFit() +{ + COsmo4 *app = GetApp(); + app->m_fit_screen = !app->m_fit_screen; + if (app->m_open) OnSetSize(0, 0); +} + +void CMainFrame::OnUpdateViewFit(CCmdUI* pCmdUI) +{ + pCmdUI->SetCheck(GetApp()->m_fit_screen ? TRUE : FALSE); +} + +void CMainFrame::OnViewArOrig() +{ + gf_term_set_option(GetApp()->m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_KEEP); +} +void CMainFrame::OnViewArFill() +{ + gf_term_set_option(GetApp()->m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_FILL_SCREEN); +} +void CMainFrame::OnViewAr43() +{ + gf_term_set_option(GetApp()->m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_4_3); +} +void CMainFrame::OnViewAr169() +{ + gf_term_set_option(GetApp()->m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_16_9); +} + +void CMainFrame::OnNavNone() +{ + gf_term_set_option(GetApp()->m_term, GF_OPT_NAVIGATION, GF_NAVIGATE_NONE); +} + +void CMainFrame::OnNavSlide() +{ + gf_term_set_option(GetApp()->m_term, GF_OPT_NAVIGATION, GF_NAVIGATE_SLIDE); +} + +void CMainFrame::OnNaveReset() +{ + gf_term_set_option(GetApp()->m_term, GF_OPT_NAVIGATION_TYPE, 0); +} + +void CMainFrame::ForwardMessage() +{ + const MSG *msg = GetCurrentMessage(); + m_wndView.SendMessage(msg->message, msg->wParam, msg->lParam); +} +void CMainFrame::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) +{ + ForwardMessage(); +} +void CMainFrame::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) +{ + ForwardMessage(); +} + + +void CMainFrame::OnViewTiming() +{ + if (m_full_screen) return; + if (m_view_timing) KillTimer(PROGRESS_TIMER); + m_view_timing = !m_view_timing; + OnSetSize(0, 0); +} + +void CMainFrame::OnUpdateViewTiming(CCmdUI* pCmdUI) +{ + pCmdUI->SetCheck(m_view_timing ? TRUE : FALSE); +} + +void CMainFrame::OnSetNavigation(UINT nID) +{ + gf_term_set_option(GetApp()->m_term, GF_OPT_NAVIGATION, nID - ID_NAV_NONE); +} + + +void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu) +{ + COsmo4 *app = GetApp(); + CFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu); + + u32 opt = gf_term_get_option(GetApp()->m_term, GF_OPT_ASPECT_RATIO); + CheckMenuItem(pPopupMenu->m_hMenu, ID_VIEW_AR_ORIG, MF_BYCOMMAND| (opt==GF_ASPECT_RATIO_KEEP) ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(pPopupMenu->m_hMenu, ID_VIEW_AR_FILL, MF_BYCOMMAND| (opt==GF_ASPECT_RATIO_FILL_SCREEN) ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(pPopupMenu->m_hMenu, ID_VIEW_AR_43, MF_BYCOMMAND| (opt==GF_ASPECT_RATIO_4_3) ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(pPopupMenu->m_hMenu, ID_VIEW_AR_169, MF_BYCOMMAND| (opt==GF_ASPECT_RATIO_16_9) ? MF_CHECKED : MF_UNCHECKED); + + CheckMenuItem(pPopupMenu->m_hMenu, ID_VIEW_FIT, MF_BYCOMMAND| app->m_fit_screen ? MF_CHECKED : MF_UNCHECKED); + + u32 type; + if (!app->m_open) type = GF_NAVIGATE_TYPE_NONE; + else type = gf_term_get_option(app->m_term, GF_OPT_NAVIGATION_TYPE); + + EnableMenuItem(pPopupMenu->m_hMenu, ID_NAV_NONE, MF_BYCOMMAND | ((type==GF_NAVIGATE_TYPE_NONE) ? MF_GRAYED : MF_ENABLED) ); + EnableMenuItem(pPopupMenu->m_hMenu, ID_NAV_SLIDE, MF_BYCOMMAND | ((type==GF_NAVIGATE_TYPE_NONE) ? MF_GRAYED : MF_ENABLED) ); + EnableMenuItem(pPopupMenu->m_hMenu, ID_NAV_RESET, MF_BYCOMMAND | ((type==GF_NAVIGATE_TYPE_NONE) ? MF_GRAYED : MF_ENABLED) ); + + EnableMenuItem(pPopupMenu->m_hMenu, ID_NAV_WALK, MF_BYCOMMAND | ( (type!=GF_NAVIGATE_TYPE_3D) ? MF_GRAYED : MF_ENABLED) ); + EnableMenuItem(pPopupMenu->m_hMenu, ID_NAV_FLY, MF_BYCOMMAND | ((type!=GF_NAVIGATE_TYPE_3D) ? MF_GRAYED : MF_ENABLED) ); + EnableMenuItem(pPopupMenu->m_hMenu, ID_NAV_EXAMINE, MF_BYCOMMAND | ((type!=GF_NAVIGATE_TYPE_3D) ? MF_GRAYED : MF_ENABLED) ); + EnableMenuItem(pPopupMenu->m_hMenu, ID_COLLIDE_OFF, MF_BYCOMMAND | ((type!=GF_NAVIGATE_TYPE_3D) ? MF_GRAYED : MF_ENABLED) ); + EnableMenuItem(pPopupMenu->m_hMenu, ID_COLLIDE_REG, MF_BYCOMMAND | ((type!=GF_NAVIGATE_TYPE_3D) ? MF_GRAYED : MF_ENABLED) ); + EnableMenuItem(pPopupMenu->m_hMenu, ID_COLLIDE_DISP, MF_BYCOMMAND | ((type!=GF_NAVIGATE_TYPE_3D) ? MF_GRAYED : MF_ENABLED) ); + EnableMenuItem(pPopupMenu->m_hMenu, ID_NAV_GRAVITY, MF_BYCOMMAND | ((type!=GF_NAVIGATE_TYPE_3D) ? MF_GRAYED : MF_ENABLED) ); + + if (type==GF_NAVIGATE_TYPE_NONE) { + u32 mode = gf_term_get_option(app->m_term, GF_OPT_NAVIGATION); + CheckMenuItem(pPopupMenu->m_hMenu, ID_NAV_NONE, MF_BYCOMMAND | ( (mode==GF_NAVIGATE_NONE) ? MF_CHECKED : MF_UNCHECKED) ); + CheckMenuItem(pPopupMenu->m_hMenu, ID_NAV_SLIDE, MF_BYCOMMAND | ( (mode==GF_NAVIGATE_SLIDE) ? MF_CHECKED : MF_UNCHECKED) ); + CheckMenuItem(pPopupMenu->m_hMenu, ID_NAV_WALK, MF_BYCOMMAND | ( (mode==GF_NAVIGATE_WALK) ? MF_CHECKED : MF_UNCHECKED) ); + CheckMenuItem(pPopupMenu->m_hMenu, ID_NAV_FLY, MF_BYCOMMAND | ((mode==GF_NAVIGATE_FLY) ? MF_CHECKED : MF_UNCHECKED) ); + CheckMenuItem(pPopupMenu->m_hMenu, ID_NAV_EXAMINE, MF_BYCOMMAND | ((mode==GF_NAVIGATE_EXAMINE) ? MF_CHECKED : MF_UNCHECKED) ); + } +} diff --git a/applications/deprecated/old_arch/osmo4_wce/MainFrm.h b/applications/deprecated/old_arch/osmo4_wce/MainFrm.h new file mode 100644 index 0000000..55de690 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wce/MainFrm.h @@ -0,0 +1,151 @@ +// MainFrm.h : interface of the CMainFrame class +// +///////////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_MAINFRM_H__1DEE4BC7_6B56_48A8_BDD7_5DC14EF6AD3E__INCLUDED_) +#define AFX_MAINFRM_H__1DEE4BC7_6B56_48A8_BDD7_5DC14EF6AD3E__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#include "ProgressBar.h" + + +class CChildView : public CWnd +{ +// Construction +public: + CChildView(); + +// Attributes +public: + +// Operations +public: + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CChildView) +protected: + virtual BOOL PreCreateWindow(CREATESTRUCT& cs); + //}}AFX_VIRTUAL + +// Implementation +public: + virtual ~CChildView(); + // Generated message map functions +protected: + //{{AFX_MSG(CChildView) + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +class CMainFrame : public CFrameWnd +{ +public: + CMainFrame(); + + + +protected: + DECLARE_DYNAMIC(CMainFrame) + +// Attributes +public: + + ProgressBar m_progBar; + Bool m_full_screen, m_restore_fs, m_view_timing; + u32 m_timer_on; + CString console_message; + GF_Err console_err; + u32 m_aspect_ratio; + +// Operations +public: + void SetPauseButton(Bool force_play_button = 0); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CMainFrame) + virtual BOOL PreCreateWindow(CREATESTRUCT& cs); + afx_msg void OnSetFocus(CWnd *pOldWnd); + virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo); + //}}AFX_VIRTUAL + +// Implementation +public: + virtual ~CMainFrame(); + + /* + #ifdef _DEBUG + virtual void AssertValid() const; + virtual void Dump(CDumpContext& dc) const; + #endif + */ + +protected: // control bar embedded members + +#if (_MSC_VER >= 1300) + CCommandBar m_wndCommandBar; +#else + CCeCommandBar m_wndCommandBar; +#endif + + void CloseURL(); + void ForwardMessage(); + +private: + RECT m_view_rc; + +public: + /*m_dumbWnd is used to clean the screen...*/ + CChildView m_wndView, m_dumbWnd; + void UpdateTime(); + +// Generated message map functions +protected: + //{{AFX_MSG(CMainFrame) + afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); + afx_msg void OnAppExit(); + afx_msg LONG Open(WPARAM wParam, LPARAM lParam); + afx_msg LONG OnSIPChange(WPARAM wParam, LPARAM lParam); + afx_msg LONG OnSetSize(WPARAM wParam, LPARAM lParam); + afx_msg LONG OnNavigate(WPARAM wParam, LPARAM lParam); + afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg void OnFileStep(); + afx_msg void OnUpdateFileStep(CCmdUI* pCmdUI); + afx_msg void OnFilePause(); + afx_msg void OnUpdateFilePause(CCmdUI* pCmdUI); + afx_msg void OnFileStop(); + afx_msg void OnUpdateFileStop(CCmdUI* pCmdUI); + afx_msg void OnViewFullscreen(); + afx_msg void OnUpdateViewFullscreen(CCmdUI* pCmdUI); + afx_msg void OnClose(); + afx_msg void OnViewFit(); + afx_msg void OnUpdateViewFit(CCmdUI* pCmdUI); + afx_msg void OnViewArOrig(); + afx_msg void OnViewArFill(); + afx_msg void OnViewAr43(); + afx_msg void OnViewAr169(); + afx_msg void OnNavNone(); + afx_msg void OnNavSlide(); + afx_msg void OnNaveReset(); + afx_msg void OnSetNavigation(UINT nID); + + afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); + afx_msg void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags); + afx_msg void OnViewTiming(); + afx_msg void OnUpdateViewTiming(CCmdUI* pCmdUI); + afx_msg void OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu); + //}}AFX_MSG + + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft eMbedded Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_MAINFRM_H__1DEE4BC7_6B56_48A8_BDD7_5DC14EF6AD3E__INCLUDED_) diff --git a/applications/deprecated/old_arch/osmo4_wce/OpenDlg.cpp b/applications/deprecated/old_arch/osmo4_wce/OpenDlg.cpp new file mode 100644 index 0000000..938e030 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wce/OpenDlg.cpp @@ -0,0 +1,92 @@ +// OpenDlg.cpp : implementation file +// +#include "stdafx.h" +#include "resource.h" +#include "OpenDlg.h" +#include "Osmo4.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// OpenDlg dialog + + +OpenDlg::OpenDlg(CWnd* pParent /*=NULL*/) + : CDialog(OpenDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(OpenDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void OpenDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(OpenDlg) + DDX_Control(pDX, IDC_FILELIST, m_URLs); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(OpenDlg, CDialog) + //{{AFX_MSG_MAP(OpenDlg) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +void OpenDlg::OnOK() +{ + CString URL; + char szUrl[5000]; + + int sel = m_URLs.GetCurSel(); + if (sel == CB_ERR) { + m_URLs.GetWindowText(URL); + } else { + m_URLs.GetLBText(sel, URL); + } + if (!URL.GetLength()) { + EndDialog(IDCANCEL); + return; + } + COsmo4 *app = GetApp(); + u32 nb_entries; + + app->m_filename = URL; + + CE_WideToChar((unsigned short *) (LPCTSTR) URL, szUrl); + + gf_cfg_set_key(app->m_user.config, "RecentFiles", szUrl, NULL); + gf_cfg_insert_key(app->m_user.config, "RecentFiles", szUrl, "", 0); + /*remove last entry if needed*/ + nb_entries = gf_cfg_get_key_count(app->m_user.config, "RecentFiles"); + if (nb_entries>20) { + gf_cfg_set_key(app->m_user.config, "RecentFiles", gf_cfg_get_key_name(app->m_user.config, "RecentFiles", nb_entries-1), NULL); + } + EndDialog(IDOK); +} + +BOOL OpenDlg::OnInitDialog() +{ + TCHAR w_str[5000]; + CDialog::OnInitDialog(); + COsmo4 *app = GetApp(); + const char *sOpt; + u32 i=0; + + while (m_URLs.GetCount()) m_URLs.DeleteString(0); + while (1) { + sOpt = gf_cfg_get_key_name(app->m_user.config, "RecentFiles", i); + if (!sOpt) break; + CE_CharToWide((char *) sOpt, (u16 *)w_str); + m_URLs.AddString(w_str); + i++; + } + + SetFocus(); + return TRUE; +} diff --git a/applications/deprecated/old_arch/osmo4_wce/OpenDlg.h b/applications/deprecated/old_arch/osmo4_wce/OpenDlg.h new file mode 100644 index 0000000..6a55b96 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wce/OpenDlg.h @@ -0,0 +1,47 @@ +#if !defined(AFX_OPENDLG_H__DD903B38_EA45_4251_A8C9_4E4B08BECCCC__INCLUDED_) +#define AFX_OPENDLG_H__DD903B38_EA45_4251_A8C9_4E4B08BECCCC__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 +// OpenDlg.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// OpenDlg dialog + +class OpenDlg : public CDialog +{ +// Construction +public: + OpenDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(OpenDlg) + enum { IDD = IDD_OPENFILE }; + CComboBox m_URLs; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(OpenDlg) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(OpenDlg) + virtual void OnOK(); + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_OPENDLG_H__DD903B38_EA45_4251_A8C9_4E4B08BECCCC__INCLUDED_) diff --git a/applications/deprecated/old_arch/osmo4_wce/Options.cpp b/applications/deprecated/old_arch/osmo4_wce/Options.cpp new file mode 100644 index 0000000..96805d9 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wce/Options.cpp @@ -0,0 +1,1237 @@ +// Options.cpp : implementation file +// + +#include "stdafx.h" +#include "Osmo4.h" +#include +#include +#include +#include +#include +#include + +#include "Options.h" +#include + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// COptions dialog + + +COptions::COptions(CWnd* pParent /*=NULL*/) + : CDialog(COptions::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptions) + //}}AFX_DATA_INIT + +} + + +void COptions::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptions) + DDX_Control(pDX, IDC_COMBOSEL, m_Selection); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptions, CDialog) + //{{AFX_MSG_MAP(COptions) + ON_BN_CLICKED(IDC_SAVEOPT, OnSaveopt) + ON_CBN_SELCHANGE(IDC_COMBOSEL, OnSelchangeCombosel) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptions message handlers + + +void COptions::OnSelchangeCombosel() +{ + HideAll(); + switch (m_Selection.GetCurSel()) { + case 0: + m_general.ShowWindow(SW_SHOW); + break; + case 1: + m_systems.ShowWindow(SW_SHOW); + break; + case 2: + m_decoder.ShowWindow(SW_SHOW); + break; + case 3: + m_render.ShowWindow(SW_SHOW); + break; + case 4: + m_render3D.ShowWindow(SW_SHOW); + break; + case 5: + m_audio.ShowWindow(SW_SHOW); + break; + case 6: + m_font.ShowWindow(SW_SHOW); + break; + case 7: + m_http.ShowWindow(SW_SHOW); + break; + case 8: + m_stream.ShowWindow(SW_SHOW); + break; + } +} + +void COptions::HideAll() +{ + m_general.ShowWindow(SW_HIDE); + m_systems.ShowWindow(SW_HIDE); + m_render.ShowWindow(SW_HIDE); + m_render3D.ShowWindow(SW_HIDE); + m_audio.ShowWindow(SW_HIDE); + m_http.ShowWindow(SW_HIDE); + m_font.ShowWindow(SW_HIDE); + m_stream.ShowWindow(SW_HIDE); + m_decoder.ShowWindow(SW_HIDE); +} + +BOOL COptions::OnInitDialog() +{ + CDialog::OnInitDialog(); + + m_general.Create(IDD_OPT_GEN, this); + m_systems.Create(IDD_OPT_SYSTEMS, this); + m_decoder.Create(IDD_OPT_DECODER, this); + m_render.Create(IDD_OPT_RENDER, this); + m_render3D.Create(IDD_OPT_RENDER3D, this); + m_audio.Create(IDD_OPT_AUDIO, this); + m_http.Create(IDD_OPT_HTTP, this); + m_font.Create(IDD_OPT_FONT, this); + m_stream.Create(IDD_OPT_STREAM, this); + + m_Selection.AddString(_T("General")); + m_Selection.AddString(_T("MPEG-4 Systems")); + m_Selection.AddString(_T("Decoders")); + m_Selection.AddString(_T("Compositor")); + m_Selection.AddString(_T("3D Rendering")); + m_Selection.AddString(_T("Audio")); + m_Selection.AddString(_T("Text")); + m_Selection.AddString(_T("Download")); + m_Selection.AddString(_T("Streaming")); + HideAll(); + + const char *sOpt = gf_cfg_get_key(GetApp()->m_user.config, "General", "ConfigPanel"); + u32 sel = sOpt ? atoi(sOpt) : 0; + if (sel>8) sel=8; + m_Selection.SetCurSel(sel); + OnSelchangeCombosel(); + + SetFocus(); + return TRUE; +} + +void COptions::OnSaveopt() +{ + m_general.SaveOptions(); + m_systems.SaveOptions(); + m_render.SaveOptions(); + m_render3D.SaveOptions(); + m_audio.SaveOptions(); + m_http.SaveOptions(); + m_font.SaveOptions(); + m_stream.SaveOptions(); + m_decoder.SaveOptions(); + + COsmo4 *gpac = GetApp(); + gf_term_set_option(gpac->m_term, GF_OPT_RELOAD_CONFIG, 1); +} + +void COptions::OnOK() +{ + char str[20]; + sprintf(str, "%d", m_Selection.GetCurSel()); + gf_cfg_set_key(GetApp()->m_user.config, "General", "ConfigPanel", str); + + EndDialog(IDCANCEL); +} + + + +COptAudio::COptAudio(CWnd* pParent /*=NULL*/) + : CDialog(COptAudio::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptAudio) + //}}AFX_DATA_INIT +} + + +void COptAudio::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptAudio) + DDX_Control(pDX, IDC_DRIVER_LIST, m_DriverList); + DDX_Control(pDX, IDC_AUDIO_RESYNC, m_AudioResync); + DDX_Control(pDX, IDC_AUDIO_DUR, m_AudioDur); + DDX_Control(pDX, IDC_SPIN_DUR, m_SpinDur); + DDX_Control(pDX, IDC_FORCE_AUDIO, m_ForceConfig); + DDX_Control(pDX, IDC_SPIN_AUDIO, m_AudioSpin); + DDX_Control(pDX, IDC_EDIT_AUDIO, m_AudioEdit); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptAudio, CDialog) + //{{AFX_MSG_MAP(COptAudio) + ON_BN_CLICKED(IDC_FORCE_AUDIO, OnForceAudio) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptAudio message handlers + +BOOL COptAudio::OnInitDialog() +{ + CDialog::OnInitDialog(); + + m_AudioSpin.SetBuddy(& m_AudioEdit); + m_SpinDur.SetBuddy(& m_AudioDur); + m_SpinDur.SetRange(0, 1000); + + COsmo4 *gpac = GetApp(); + const char *sOpt; + TCHAR wTmp[500]; + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Audio", "ForceConfig"); + m_ForceConfig.SetCheck( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Audio", "NumBuffers"); + if (sOpt) { + CE_CharToWide((char *)sOpt, (u16 *)wTmp); + m_AudioEdit.SetWindowText(wTmp); + } else { + m_AudioEdit.SetWindowText(_T("2")); + } + sOpt = gf_cfg_get_key(gpac->m_user.config, "Audio", "TotalDuration"); + if (sOpt) { + CE_CharToWide((char *)sOpt, (u16 *)wTmp); + m_AudioDur.SetWindowText(wTmp); + } else { + m_AudioDur.SetWindowText(_T("200")); + } + + OnForceAudio(); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Audio", "NoResync"); + if (sOpt && !stricmp(sOpt, "yes")) { + m_AudioResync.SetCheck(1); + } else { + m_AudioResync.SetCheck(0); + } + + /*driver enum*/ + while (m_DriverList.GetCount()) m_DriverList.DeleteString(0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "core", "audio-output"); + u32 count = gf_modules_get_count(gpac->m_user.modules); + GF_BaseInterface *ifce; + s32 select = 0; + s32 to_sel = 0; + for (u32 i=0; im_user.modules, i, GF_AUDIO_OUTPUT_INTERFACE); + if (!ifce) continue; + if (sOpt && !stricmp(((GF_BaseInterface *)ifce)->module_name, sOpt)) select = to_sel; + CE_CharToWide((char *) ((GF_BaseInterface *)ifce)->module_name, (u16 *)wTmp); + m_DriverList.AddString(wTmp); + gf_modules_close_interface(ifce); + to_sel++; + } + m_DriverList.SetCurSel(select); + + + return TRUE; +} + + +void COptAudio::SaveOptions() +{ + COsmo4 *gpac = GetApp(); + TCHAR wstr[50]; + char str[50]; + + gf_cfg_set_key(gpac->m_user.config, "Audio", "ForceConfig", m_ForceConfig.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "Audio", "NoResync", m_AudioResync.GetCheck() ? "yes" : "no"); + + m_AudioEdit.GetWindowText(wstr, 20); + CE_WideToChar((u16 *)wstr, str); + gf_cfg_set_key(gpac->m_user.config, "Audio", "NumBuffers", str); + m_AudioDur.GetWindowText(wstr, 20); + CE_WideToChar((u16 *)wstr, str); + gf_cfg_set_key(gpac->m_user.config, "Audio", "TotalDuration", str); + + m_DriverList.GetWindowText(wstr, 50); + CE_WideToChar((u16 *)wstr, str); + gf_cfg_set_key(gpac->m_user.config, "core", "audio-output", str); + +} + +void COptAudio::OnForceAudio() +{ + BOOL en = m_ForceConfig.GetCheck(); + + m_AudioSpin.EnableWindow(en); + m_AudioEdit.EnableWindow(en); + m_SpinDur.EnableWindow(en); + m_AudioDur.EnableWindow(en); +} + + +COptDecoder::COptDecoder(CWnd* pParent /*=NULL*/) + : CDialog(COptDecoder::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptDecoder) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void COptDecoder::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptDecoder) + DDX_Control(pDX, IDC_VIDEC_LIST, m_Video); + DDX_Control(pDX, IDC_AUDEC_LIST, m_Audio); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptDecoder, CDialog) + //{{AFX_MSG_MAP(COptDecoder) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptDecoder message handlers + +BOOL COptDecoder::OnInitDialog() +{ + u32 i; + CDialog::OnInitDialog(); + + COsmo4 *gpac = GetApp(); + const char *sOpt; + + /*audio dec enum*/ + while (m_Audio.GetCount()) m_Audio.DeleteString(0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Systems", "DefAudioDec"); + u32 count = gf_modules_get_count(gpac->m_user.modules); + GF_BaseDecoder *ifce; + s32 select = 0; + s32 to_sel = 0; + for (i=0; im_user.modules, i, GF_MEDIA_DECODER_INTERFACE); + if (!ifce) continue; + if (ifce->CanHandleStream(ifce, GF_STREAM_AUDIO, NULL, 0)) { + if (sOpt && !stricmp(((GF_BaseInterface *)ifce)->module_name, sOpt)) select = to_sel; + TCHAR wzTmp[500]; + CE_CharToWide((char *) ifce->module_name, (u16 *)wzTmp); + m_Audio.AddString(wzTmp); + to_sel++; + } + gf_modules_close_interface((GF_BaseInterface *) ifce); + } + m_Audio.SetCurSel(select); + + /*audio dec enum*/ + while (m_Video.GetCount()) m_Video.DeleteString(0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Systems", "DefVideoDec"); + count = gf_modules_get_count(gpac->m_user.modules); + select = 0; + to_sel = 0; + for (i=0; im_user.modules, i, GF_MEDIA_DECODER_INTERFACE); + if (!ifce) continue; + if (ifce->CanHandleStream(ifce, GF_STREAM_VISUAL, NULL, 0)) { + if (sOpt && !stricmp(((GF_BaseInterface *)ifce)->module_name, sOpt)) select = to_sel; + TCHAR wzTmp[500]; + CE_CharToWide((char *) ifce->module_name, (u16 *)wzTmp); + m_Video.AddString(wzTmp); + to_sel++; + } + gf_modules_close_interface((GF_BaseInterface *) ifce); + } + m_Video.SetCurSel(select); + + return TRUE; +} + +void COptDecoder::SaveOptions() +{ + COsmo4 *gpac = GetApp(); + TCHAR wstr[100]; + char str[100]; + + m_Audio.GetWindowText(wstr, 50); + CE_WideToChar((u16 *)wstr, str); + gf_cfg_set_key(gpac->m_user.config, "Systems", "DefAudioDec", str); + m_Video.GetWindowText(wstr, 50); + CE_WideToChar((u16 *)wstr, str); + gf_cfg_set_key(gpac->m_user.config, "Systems", "DefVideoDec", str); +} + + + +COptFont::COptFont(CWnd* pParent /*=NULL*/) + : CDialog(COptFont::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptFont) + //}}AFX_DATA_INIT +} + + +void COptFont::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptFont) + DDX_Control(pDX, IDC_USE_TEXTURE, m_UseTexture); + DDX_Control(pDX, IDC_FONT_LIST, m_Fonts); + DDX_Control(pDX, IDC_BROWSE_FONT, m_BrowseFont); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptFont, CDialog) + //{{AFX_MSG_MAP(COptFont) + ON_BN_CLICKED(IDC_BROWSE_FONT, OnBrowseFont) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptFont message handlers + +BOOL COptFont::OnInitDialog() +{ + u32 i; + GF_BaseInterface *ifce; + + CDialog::OnInitDialog(); + + COsmo4 *gpac = GetApp(); + TCHAR wTmp[500]; + const char *sOpt; + + /*video drivers enum*/ + while (m_Fonts.GetCount()) m_Fonts.DeleteString(0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "FontCache", "FontReader"); + s32 to_sel = 0; + s32 select = 0; + u32 count = gf_modules_get_count(gpac->m_user.modules); + for (i=0; im_user.modules, i, GF_FONT_READER_INTERFACE); + if (!ifce) continue; + if (sOpt && !stricmp(((GF_BaseInterface *)ifce)->module_name, sOpt)) select = to_sel; + CE_CharToWide((char *) ifce->module_name, (u16 *)wTmp); + m_Fonts.AddString(wTmp); + gf_modules_close_interface(ifce); + to_sel++; + } + m_Fonts.SetCurSel(select); + + + sOpt = gf_cfg_get_key(gpac->m_user.config, "FontCache", "FontDirectory"); + CE_CharToWide((char *)sOpt, (u16 *)wTmp); + if (sOpt) m_BrowseFont.SetWindowText(wTmp); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "TextureTextMode"); + m_UseTexture.SetCheck( (!sOpt || stricmp(sOpt, "Never")) ? 1 : 0); + + return TRUE; +} + +void COptFont::OnBrowseFont() +{ + +} + + +void COptFont::SaveOptions() +{ + COsmo4 *gpac = GetApp(); + char str[MAX_PATH]; + TCHAR wstr[MAX_PATH]; + + m_Fonts.GetWindowText(wstr, 50); + CE_WideToChar((u16 *)wstr, str); + gf_cfg_set_key(gpac->m_user.config, "FontCache", "FontReader", str); + m_BrowseFont.GetWindowText(wstr, 50); + CE_WideToChar((u16 *)wstr, str); + gf_cfg_set_key(gpac->m_user.config, "FontCache", "FontDirectory", str); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "TextureTextMode", m_UseTexture.GetCheck() ? "Default" : "Never"); +} + + + +COptGen::COptGen(CWnd* pParent /*=NULL*/) + : CDialog(COptGen::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptGen) + //}}AFX_DATA_INIT +} + + +void COptGen::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptGen) + DDX_Control(pDX, IDC_NO_BACKLIGHT, m_NoBacklight); + DDX_Control(pDX, IDC_FILL_SCREEN, m_Fill); + DDX_Control(pDX, IDC_LOOP, m_Loop); + DDX_Control(pDX, IDC_ENABLE_LOGS, m_Logs); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptGen, CDialog) + //{{AFX_MSG_MAP(COptGen) + ON_BN_CLICKED(IDC_FILEASSOC, OnFileassoc) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptGen message handlers + + + +BOOL COptGen::OnInitDialog() +{ + CDialog::OnInitDialog(); + COsmo4 *gpac = GetApp(); + const char *sOpt; + + sOpt = gf_cfg_get_key(gpac->m_user.config, "General", "Loop"); + if (sOpt && !stricmp(sOpt, "yes")) { + m_Loop.SetCheck(1); + } else { + m_Loop.SetCheck(0); + } + sOpt = gf_cfg_get_key(gpac->m_user.config, "General", "FillScreen"); + m_Fill.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "General", "DisableBackLight"); + m_NoBacklight.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "General", "Logs"); + m_Logs.SetCheck((sOpt && !strstr(sOpt, "none")) ? 1 : 0); + return TRUE; +} + +void COptGen::SaveOptions() +{ + COsmo4 *gpac = GetApp(); + + gpac->m_Loop = m_Loop.GetCheck(); + gf_cfg_set_key(gpac->m_user.config, "General", "Loop", gpac->m_Loop ? "yes" : "no"); + gpac->m_fit_screen = m_Fill.GetCheck(); + gf_cfg_set_key(gpac->m_user.config, "General", "FillScreen", gpac->m_fit_screen ? "yes" : "no"); + gpac->m_disable_backlight = m_NoBacklight.GetCheck(); + gf_cfg_set_key(gpac->m_user.config, "General", "DisableBackLight", gpac->m_disable_backlight ? "yes" : "no"); + + gpac->EnableLogs(m_Logs.GetCheck() ? 1 : 0); +} + +void COptGen::OnFileassoc() +{ + HKEY hSection; + TCHAR szDir[MAX_PATH]; + char szTemp[MAX_PATH]; + TCHAR cmd[MAX_PATH]; + DWORD ioSize = MAX_PATH; + DWORD dwDisp; + + RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Osmo4"), 0, KEY_READ, &hSection); + + GetModuleFileName(NULL, szDir, MAX_PATH); + + while (szDir[strlen((char *) szDir)-1] != (TCHAR) '\\') szDir[strlen((char *) szDir)-1] = 0; + if (!hSection) + RegCreateKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Osmo4"), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hSection, &dwDisp); + + CE_WideToChar((u16 *)szDir, szTemp); + /*overwrite install dir with current path*/ + RegSetValueEx(hSection, _T("Install_Dir"), 0, REG_SZ, (const unsigned char *) szTemp, strlen(szTemp)+1); + RegCloseKey(hSection); + + + /*overwrite .mp4 file associations */ + RegCreateKeyEx(HKEY_CLASSES_ROOT, _T("mp4file\\DefaultIcon"), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hSection, &dwDisp); + wcscpy(cmd, szDir); + wcscat(cmd, _T("Osmo4.ico") ); + CE_WideToChar((u16 *)cmd, szTemp); + + RegSetValueEx(hSection, _T(""), 0, REG_SZ, (const unsigned char *) szTemp, strlen((const char *) szTemp)+1); + RegCloseKey(hSection); + + RegCreateKeyEx(HKEY_CLASSES_ROOT, _T("mp4file\\Shell\\open\\command"), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hSection, &dwDisp); + wcscpy(cmd, szDir); + wcscat(cmd, _T("Osmo4.exe \"%L\"") ); + CE_WideToChar((u16 *)cmd, szTemp); + RegSetValueEx(hSection, _T(""), 0, REG_SZ, (const unsigned char *) szTemp, strlen(szTemp)+1); + RegCloseKey(hSection); + + RegCreateKeyEx(HKEY_CLASSES_ROOT, _T(".mp4"), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hSection, &dwDisp); + RegSetValueEx(hSection, _T(""), 0, REG_SZ, (const unsigned char *) "mp4file", strlen("mp4file")+1); + RegCloseKey(hSection); +} + + + +COptHTTP::COptHTTP(CWnd* pParent /*=NULL*/) + : CDialog(COptHTTP::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptHTTP) + //}}AFX_DATA_INIT +} + + +void COptHTTP::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptHTTP) + DDX_Control(pDX, IDC_RESTART_CACHE, m_RestartFile); + DDX_Control(pDX, IDC_CLEAN_CACHE, m_CleanCache); + DDX_Control(pDX, IDC_BROWSE_CACHE, m_CacheDir); + DDX_Control(pDX, IDC_SAX_PROGRESSIVE, m_Progressive); + DDX_Control(pDX, IDC_SAX_DURATION, m_SaxDuration); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptHTTP, CDialog) + //{{AFX_MSG_MAP(COptHTTP) + ON_BN_CLICKED(IDC_BROWSE_CACHE, OnBrowseCache) + ON_BN_CLICKED(IDC_SAX_PROGRESSIVE, OnProgressive) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptHTTP message handlers + + +void COptHTTP::OnBrowseCache() +{ + +} + +BOOL COptHTTP::OnInitDialog() +{ + CDialog::OnInitDialog(); + + COsmo4 *gpac = GetApp(); + TCHAR wTmp[500]; + const char *sOpt; + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Core", "CleanCache"); + m_CleanCache.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Core", "RestartFiles"); + m_RestartFile.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(gpac->m_user.config, "SAXLoader", "Progressive"); + m_Progressive.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "SAXLoader", "MaxDuration"); + if (sOpt) { + CE_CharToWide((char *) sOpt, (u16 *)wTmp); + m_SaxDuration.SetWindowText(wTmp); + } else { + m_SaxDuration.SetWindowText( _T("30") ); + } + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Core", "CacheDirectory"); + CE_CharToWide((char *) sOpt, (u16 *)wTmp); + if (sOpt) m_CacheDir.SetWindowText(wTmp); + + OnProgressive(); + return TRUE; +} + +void COptHTTP::OnProgressive() +{ + m_SaxDuration.EnableWindow( m_Progressive.GetCheck() ? TRUE : FALSE ); +} + +void COptHTTP::SaveOptions() +{ + TCHAR wTmp[500]; + char szCacheDir[500]; + COsmo4 *gpac = GetApp(); + + gf_cfg_set_key(gpac->m_user.config, "Core", "CleanCache", m_CleanCache.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "Core", "RestartFiles", m_RestartFile.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "SAXLoader", "Progressive", m_Progressive.GetCheck() ? "yes" : "no"); + + m_SaxDuration.GetWindowText(wTmp, MAX_PATH); + CE_WideToChar((u16 *)wTmp, szCacheDir); + gf_cfg_set_key(gpac->m_user.config, "SAXLoader", "MaxDuration", szCacheDir); + + m_CacheDir.GetWindowText(wTmp, MAX_PATH); + CE_WideToChar((u16 *)wTmp, szCacheDir); + gf_cfg_set_key(gpac->m_user.config, "Core", "CacheDirectory", szCacheDir); +} + + + +COptRender::COptRender(CWnd* pParent /*=NULL*/) + : CDialog(COptRender::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptRender) + //}}AFX_DATA_INIT +} + + +void COptRender::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptRender) + DDX_Control(pDX, IDC_AA_LIST, m_Antialias); + DDX_Control(pDX, IDC_FORCE_SIZE, m_ForceSize); + DDX_Control(pDX, IDC_FAST_RENDER, m_HighSpeed); + DDX_Control(pDX, IDC_ZOOM_SCALABLE, m_Scalable); + DDX_Control(pDX, IDC_DIRECTRENDER, m_DirectRender); + DDX_Control(pDX, IDC_BIFS_RATE, m_BIFSRate); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptRender, CDialog) + //{{AFX_MSG_MAP(COptRender) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptRender message handlers + + + +#define NUM_RATES 11 +static char *BIFSRates[11] = +{ + "5.0", + "7.5", + "10.0", + "12.5", + "15.0", + "24.0", + "25.0", + "30.0", + "50.0", + "60.0", + "100.0", +}; + + + +BOOL COptRender::OnInitDialog() +{ + CDialog::OnInitDialog(); + + COsmo4 *gpac = GetApp(); + const char *sOpt; + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "DirectDraw"); + if (sOpt && !stricmp(sOpt, "yes")) { + m_DirectRender.SetCheck(1); + } else { + m_DirectRender.SetCheck(0); + } + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "ScalableZoom"); + if (sOpt && !stricmp(sOpt, "no")) { + m_Scalable.SetCheck(0); + } else { + m_Scalable.SetCheck(1); + } + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "ForceSceneSize"); + if (sOpt && !stricmp(sOpt, "yes")) { + m_ForceSize.SetCheck(1); + } else { + m_ForceSize.SetCheck(0); + } + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "FrameRate"); + if (!sOpt) sOpt = "30.0"; + s32 select = 0; + while (m_BIFSRate.GetCount()) m_BIFSRate.DeleteString(0); + for (s32 i = 0; im_user.config, "Compositor", "HighSpeed"); + m_HighSpeed.SetCheck((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "AntiAlias"); + while (m_Antialias.GetCount()) m_Antialias.DeleteString(0); + + m_Antialias.AddString(_T("None")); + m_Antialias.AddString(_T("Text only")); + m_Antialias.AddString(_T("Complete")); + select = 2; + if (sOpt && !stricmp(sOpt, "Text")) select = 1; + else if (sOpt && !stricmp(sOpt, "None")) select = 0; + m_Antialias.SetCurSel(select); + + return TRUE; +} + + +void COptRender::SaveOptions() +{ + COsmo4 *gpac = GetApp(); + + gf_cfg_set_key(gpac->m_user.config, "Compositor", "DirectDraw", m_DirectRender.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "ScalableZoom", m_Scalable.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "HighSpeed", m_HighSpeed.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "ForceSceneSize", m_ForceSize.GetCheck() ? "yes" : "no"); + + s32 sel = m_BIFSRate.GetCurSel(); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "FrameRate", BIFSRates[sel]); + + sel = m_Antialias.GetCurSel(); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "AntiAlias", (sel==0) ? "None" : ( (sel==1) ? "Text" : "All")); +} + + + + +COptRender3D::COptRender3D(CWnd* pParent /*=NULL*/) + : CDialog(COptRender3D::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptRender) + //}}AFX_DATA_INIT +} + + +void COptRender3D::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptRender) + DDX_Control(pDX, IDC_WIRE_MODE, m_WireMode); + DDX_Control(pDX, IDC_DRAW_NORMALS, m_DrawNormals); + DDX_Control(pDX, IDC_USE_3D_REN, m_Use3DRender); + DDX_Control(pDX, IDC_NO_BACKCULL, m_NoBackFace); + DDX_Control(pDX, IDC_EMULATE_POW2, m_EmulatePOW2); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptRender3D, CDialog) + //{{AFX_MSG_MAP(COptRender3D) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +BOOL COptRender3D::OnInitDialog() +{ + CDialog::OnInitDialog(); + + COsmo4 *gpac = GetApp(); + const char *sOpt; + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "ForceOpenGL"); + m_Use3DRender.SetCheck( (sOpt && !strcmp(sOpt, "yes")) ? 1 : 0); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "BackFaceCulling"); + m_NoBackFace.SetCheck( (sOpt && !stricmp(sOpt, "Off")) ? 1 : 0); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "EmulatePOW2"); + m_EmulatePOW2.SetCheck( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + m_WireMode.ResetContent(); + m_WireMode.AddString(_T("Solid Draw")); + m_WireMode.AddString(_T("Wireframe")); + m_WireMode.AddString(_T("Both")); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "Wireframe"); + if (sOpt && !stricmp(sOpt, "WireOnly")) m_WireMode.SetCurSel(1); + else if (sOpt && !stricmp(sOpt, "WireOnSolid")) m_WireMode.SetCurSel(2); + else m_WireMode.SetCurSel(0); + + + m_DrawNormals.ResetContent(); + m_DrawNormals.AddString(_T("Never")); + m_DrawNormals.AddString(_T("Per Face")); + m_DrawNormals.AddString(_T("Per Vertex")); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Compositor", "DrawNormals"); + if (sOpt && !stricmp(sOpt, "PerFace")) m_DrawNormals.SetCurSel(1); + else if (sOpt && !stricmp(sOpt, "PerVertex")) m_DrawNormals.SetCurSel(2); + else m_DrawNormals.SetCurSel(0); + + return TRUE; +} + + +void COptRender3D::SaveOptions() +{ + COsmo4 *gpac = GetApp(); + + u32 sel = m_DrawNormals.GetCurSel(); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "DrawNormals", (sel==2) ? "PerVertex" : (sel==1) ? "PerFace" : "Never"); + + sel = m_WireMode.GetCurSel(); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "Wireframe", (sel==2) ? "WireOnSolid" : (sel==1) ? "WireOnly" : "WireNone"); + + gf_cfg_set_key(gpac->m_user.config, "Compositor", "BackFaceCulling", m_NoBackFace.GetCheck() ? "Off" : "On"); + gf_cfg_set_key(gpac->m_user.config, "Compositor", "EmulatePOW2", m_EmulatePOW2.GetCheck() ? "yes" : "no"); +} + + +COptStream::COptStream(CWnd* pParent /*=NULL*/) + : CDialog(COptStream::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptStream) + //}}AFX_DATA_INIT +} + + +void COptStream::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptStream) + DDX_Control(pDX, IDC_REBUFFER_LEN, m_RebufferLen); + DDX_Control(pDX, IDC_REBUFFER, m_Rebuffer); + DDX_Control(pDX, IDC_BUFFER, m_Buffer); + DDX_Control(pDX, IDC_TIMEOUT, m_Timeout); + DDX_Control(pDX, IDC_REORDER, m_Reorder); + DDX_Control(pDX, IDC_RTSP, m_UseRTSP); + DDX_Control(pDX, IDC_PORT, m_Port); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptStream, CDialog) + //{{AFX_MSG_MAP(COptStream) + ON_CBN_SELCHANGE(IDC_PORT, OnSelchangePort) + ON_BN_CLICKED(IDC_RTSP, OnRtsp) + ON_BN_CLICKED(IDC_REBUFFER, OnRebuffer) + ON_EN_UPDATE(IDC_REBUFFER_LEN, OnUpdateRebufferLen) + ON_EN_UPDATE(IDC_BUFFER, OnUpdateBuffer) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptStream message handlers + +BOOL COptStream::OnInitDialog() +{ + CDialog::OnInitDialog(); + + COsmo4 *gpac = GetApp(); + TCHAR wTmp[500]; + const char *sOpt; + + while (m_Port.GetCount()) m_Port.DeleteString(0); + m_Port.AddString(_T("554 (RTSP standard)")); + m_Port.AddString(_T("7070 (RTSP ext)")); + m_Port.AddString(_T("80 (RTSP / HTTP tunnel)")); + m_Port.AddString(_T("8080 (RTSP / HTTP tunnel)")); + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Streaming", "DefaultPort"); + u32 port = 554; + Bool force_rtsp = 0;; + if (sOpt) port = atoi(sOpt); + switch (port) { + case 8080: + m_Port.SetCurSel(3); + force_rtsp = 1; + break; + case 80: + m_Port.SetCurSel(2); + force_rtsp = 1; + break; + case 7070: + m_Port.SetCurSel(1); + break; + default: + m_Port.SetCurSel(0); + break; + } + + Bool use_rtsp = 0; + sOpt = gf_cfg_get_key(gpac->m_user.config, "Streaming", "RTPoverRTSP"); + if (sOpt && !stricmp(sOpt, "yes")) use_rtsp = 1; + + if (force_rtsp) { + m_UseRTSP.SetCheck(1); + m_UseRTSP.EnableWindow(0); + m_Reorder.SetCheck(0); + m_Reorder.EnableWindow(0); + } else { + m_UseRTSP.SetCheck(use_rtsp); + m_UseRTSP.EnableWindow(1); + m_Reorder.EnableWindow(1); + sOpt = gf_cfg_get_key(gpac->m_user.config, "Streaming", "ReorderSize"); + if (sOpt && !stricmp(sOpt, "0")) { + m_Reorder.SetCheck(0); + } else { + m_Reorder.SetCheck(1); + } + } + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Streaming", "RTSPTimeout"); + if (sOpt) { + CE_CharToWide((char *) sOpt, (u16 *)wTmp); + m_Timeout.SetWindowText(wTmp); + } else { + m_Timeout.SetWindowText(_T("30000")); + } + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Network", "BufferLength"); + if (sOpt) { + CE_CharToWide((char *) sOpt, (u16 *)wTmp); + m_Buffer.SetWindowText(wTmp); + } else { + m_Buffer.SetWindowText(_T("3000")); + } + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Network", "RebufferLength"); + u32 buf_len = 0; + if (sOpt) buf_len = atoi(sOpt); + if (buf_len) { + CE_CharToWide((char *) sOpt, (u16 *)wTmp); + m_RebufferLen.SetWindowText(wTmp); + m_Rebuffer.SetCheck(1); + m_RebufferLen.EnableWindow(1); + } else { + m_RebufferLen.SetWindowText(_T("0")); + m_Rebuffer.SetCheck(0); + m_RebufferLen.EnableWindow(0); + } + + return TRUE; +} + + +void COptStream::OnSelchangePort() +{ + s32 sel = m_Port.GetCurSel(); + switch (sel) { + case 3: + case 2: + m_UseRTSP.SetCheck(1); + m_UseRTSP.EnableWindow(0); + m_Reorder.SetCheck(0); + m_Reorder.EnableWindow(0); + break; + case 1: + default: + m_UseRTSP.SetCheck(0); + m_UseRTSP.EnableWindow(1); + m_Reorder.SetCheck(1); + m_Reorder.EnableWindow(1); + break; + } +} + +void COptStream::OnRtsp() +{ + if (m_UseRTSP.GetCheck()) { + m_Reorder.SetCheck(0); + m_Reorder.EnableWindow(0); + } else { + m_Reorder.SetCheck(1); + m_Reorder.EnableWindow(1); + } + +} + +void COptStream::CheckRebuffer() +{ + TCHAR wstr[50]; + char str[50]; + s32 buf, rebuf; + m_Buffer.GetWindowText(wstr, 50); + CE_WideToChar((u16 *)wstr, str); + buf = atoi(str); + m_RebufferLen.GetWindowText(wstr, 50); + CE_WideToChar((u16 *)wstr, str); + rebuf = atoi(str); + if (rebuf*2 > buf) { + rebuf = buf/2; + wsprintf(wstr, _T("%d"), rebuf); + m_RebufferLen.SetWindowText(wstr); + } +} + +void COptStream::OnRebuffer() +{ + if (!m_Rebuffer.GetCheck()) { + m_RebufferLen.EnableWindow(0); + } else { + m_RebufferLen.EnableWindow(1); + CheckRebuffer(); + } +} + +void COptStream::OnUpdateRebufferLen() +{ + CheckRebuffer(); +} + +void COptStream::OnUpdateBuffer() +{ + CheckRebuffer(); +} + +void COptStream::SaveOptions() +{ + COsmo4 *gpac = GetApp(); + Bool force_rtsp = 0; + s32 sel = m_Port.GetCurSel(); + switch (sel) { + case 3: + gf_cfg_set_key(gpac->m_user.config, "Streaming", "DefaultPort", "8080"); + force_rtsp = 1; + break; + case 2: + gf_cfg_set_key(gpac->m_user.config, "Streaming", "DefaultPort", "80"); + force_rtsp = 1; + break; + case 1: + gf_cfg_set_key(gpac->m_user.config, "Streaming", "DefaultPort", "7070"); + break; + default: + gf_cfg_set_key(gpac->m_user.config, "Streaming", "DefaultPort", "554"); + break; + } + + if (force_rtsp) { + gf_cfg_set_key(gpac->m_user.config, "Streaming", "RTPoverRTSP", "yes"); + } else { + gf_cfg_set_key(gpac->m_user.config, "Streaming", "RTPoverRTSP", m_UseRTSP.GetCheck() ? "yes" : "no"); + if (!m_UseRTSP.GetCheck()) gf_cfg_set_key(gpac->m_user.config, "Streaming", "ReorderSize", m_Reorder.GetCheck() ? "30" : "0"); + } + + TCHAR wstr[50]; + char str[50]; + + m_Timeout.GetWindowText(wstr, 50); + CE_WideToChar((u16 *)wstr, str); + gf_cfg_set_key(gpac->m_user.config, "Streaming", "RTSPTimeout", str); + + m_Buffer.GetWindowText(wstr, 50); + CE_WideToChar((u16 *)wstr, str); + gf_cfg_set_key(gpac->m_user.config, "Network", "BufferLength", str); + if (m_Rebuffer.GetCheck()) { + m_RebufferLen.GetWindowText(wstr, 50); + CE_WideToChar((u16 *)wstr, str); + gf_cfg_set_key(gpac->m_user.config, "Network", "RebufferLength", str); + } else { + gf_cfg_set_key(gpac->m_user.config, "Network", "RebufferLength", "0"); + } +} + + + +COptSystems::COptSystems(CWnd* pParent /*=NULL*/) + : CDialog(COptSystems::IDD, pParent) +{ + //{{AFX_DATA_INIT(COptSystems) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void COptSystems::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(COptSystems) + DDX_Control(pDX, IDC_FORCE_DURATION, m_ForceDuration); + DDX_Control(pDX, IDC_DEC_THREAD, m_Threading); + DDX_Control(pDX, IDC_BIFSDROP, m_BifsAlwaysDrawn); + DDX_Control(pDX, IDC_LANG, m_Lang); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(COptSystems, CDialog) + //{{AFX_MSG_MAP(COptSystems) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COptSystems message handlers + + + +BOOL COptSystems::OnInitDialog() +{ + CDialog::OnInitDialog(); + + COsmo4 *gpac = GetApp(); + const char *sOpt; + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Systems", "Language3CC"); + if (!sOpt) sOpt = "eng"; + s32 select = 0; + while (m_Lang.GetCount()) m_Lang.DeleteString(0); + s32 idx = gf_lang_find(sOpt); + u32 i, count = gf_lang_get_count(); + for (i=0; i=0 && (i==idx)) + select = i; + } + m_Lang.SetCurSel(select); + + + /*system config*/ + sOpt = gf_cfg_get_key(gpac->m_user.config, "Systems", "ThreadingPolicy"); + select = 0; + while (m_Threading.GetCount()) m_Threading.DeleteString(0); + m_Threading.AddString(_T("Single Thread")); + m_Threading.AddString(_T("Mutli Thread")); + if (sOpt && !stricmp(sOpt, "Multi")) select = 1; + m_Threading.AddString(_T("Free")); + if (sOpt && !stricmp(sOpt, "Free")) select = 2; + m_Threading.SetCurSel(select); + + + sOpt = gf_cfg_get_key(gpac->m_user.config, "Systems", "ForceSingleClock"); + if (sOpt && !stricmp(sOpt, "yes")) { + m_ForceDuration.SetCheck(1); + } else { + m_ForceDuration.SetCheck(0); + } + sOpt = gf_cfg_get_key(gpac->m_user.config, "Systems", "AlwaysDrawBIFS"); + if (sOpt && !stricmp(sOpt, "yes")) { + m_BifsAlwaysDrawn.SetCheck(1); + } else { + m_BifsAlwaysDrawn.SetCheck(0); + } + + + return TRUE; +} + + +void COptSystems::SaveOptions() +{ + COsmo4 *gpac = GetApp(); + + s32 sel = m_Lang.GetCurSel(); + gf_cfg_set_key(gpac->m_user.config, "Systems", "LanguageName", gf_lang_get_name(sel) ); + gf_cfg_set_key(gpac->m_user.config, "Systems", "Language3CC", gf_lang_get_3cc(sel) ); + gf_cfg_set_key(gpac->m_user.config, "Systems", "Language2CC", gf_lang_get_2cc(sel) ); + + sel = m_Threading.GetCurSel(); + gf_cfg_set_key(gpac->m_user.config, "Systems", "ThreadingPolicy", (sel==0) ? "Single" : ( (sel==1) ? "Multi" : "Free")); + + /*reset duration flag*/ + gpac->m_duration = (u32) -1; + gf_cfg_set_key(gpac->m_user.config, "Systems", "ForceSingleClock", m_ForceDuration.GetCheck() ? "yes" : "no"); + gf_cfg_set_key(gpac->m_user.config, "Systems", "AlwaysDrawBIFS", m_BifsAlwaysDrawn.GetCheck() ? "yes" : "no"); + +} + diff --git a/applications/deprecated/old_arch/osmo4_wce/Options.h b/applications/deprecated/old_arch/osmo4_wce/Options.h new file mode 100644 index 0000000..99e98f4 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wce/Options.h @@ -0,0 +1,388 @@ +#if !defined(AFX_OPTIONS_H__5C839953_58C0_4D9D_89CE_2820C7686C1B__INCLUDED_) +#define AFX_OPTIONS_H__5C839953_58C0_4D9D_89CE_2820C7686C1B__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// Options.h : header file +// + + +class COptSystems : public CDialog +{ +// Construction +public: + COptSystems(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptSystems) + enum { IDD = IDD_OPT_SYSTEMS }; + CButton m_ForceDuration; + CComboBox m_Threading; + CButton m_BifsAlwaysDrawn; + CComboBox m_Lang; + //}}AFX_DATA + + void SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptSystems) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptSystems) + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +class COptStream : public CDialog +{ +// Construction +public: + COptStream(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptStream) + enum { IDD = IDD_OPT_STREAM }; + CEdit m_RebufferLen; + CButton m_Rebuffer; + CEdit m_Buffer; + CEdit m_Timeout; + CButton m_Reorder; + CButton m_UseRTSP; + CComboBox m_Port; + //}}AFX_DATA + + + void SaveOptions(); + + void CheckRebuffer(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptStream) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptStream) + virtual BOOL OnInitDialog(); + afx_msg void OnSelchangePort(); + afx_msg void OnRtsp(); + afx_msg void OnRebuffer(); + afx_msg void OnUpdateRebufferLen(); + afx_msg void OnUpdateBuffer(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; +class COptRender : public CDialog +{ +// Construction +public: + COptRender(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptRender) + enum { IDD = IDD_OPT_RENDER }; + CComboBox m_Antialias; + CButton m_ForceSize; + CButton m_HighSpeed; + CButton m_Scalable; + CButton m_DirectRender; + CComboBox m_BIFSRate; + //}}AFX_DATA + + + void SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptRender) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptRender) + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; +class COptHTTP : public CDialog +{ +// Construction +public: + COptHTTP(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptHTTP) + enum { IDD = IDD_OPT_HTTP }; + CButton m_RestartFile; + CButton m_Progressive; + CButton m_CleanCache; + CButton m_CacheDir; + CEdit m_SaxDuration; + //}}AFX_DATA + + + void SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptHTTP) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptHTTP) + afx_msg void OnBrowseCache(); + afx_msg void OnProgressive(); + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; +class COptGen : public CDialog +{ +// Construction +public: + COptGen(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptGen) + enum { IDD = IDD_OPT_GEN }; + CButton m_NoBacklight; + CButton m_Fill; + CButton m_Loop; + CButton m_Logs; + //}}AFX_DATA + + void SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptGen) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptGen) + virtual BOOL OnInitDialog(); + afx_msg void OnFileassoc(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; +class COptFont : public CDialog +{ +// Construction +public: + COptFont(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptFont) + enum { IDD = IDD_OPT_FONT }; + CButton m_UseTexture; + CComboBox m_Fonts; + CButton m_BrowseFont; + //}}AFX_DATA + + + void SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptFont) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptFont) + virtual BOOL OnInitDialog(); + afx_msg void OnBrowseFont(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; +class COptDecoder : public CDialog +{ +// Construction +public: + COptDecoder(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptDecoder) + enum { IDD = IDD_OPT_DECODER }; + CComboBox m_Video; + CComboBox m_Audio; + //}}AFX_DATA + + void SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptDecoder) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptDecoder) + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +class COptAudio : public CDialog +{ +// Construction +public: + COptAudio(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptAudio) + enum { IDD = IDD_OPT_AUDIO }; + CComboBox m_DriverList; + CButton m_AudioResync; + CEdit m_AudioDur; + CSpinButtonCtrl m_SpinDur; + CButton m_ForceConfig; + CSpinButtonCtrl m_AudioSpin; + CEdit m_AudioEdit; + //}}AFX_DATA + + void SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptAudio) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptAudio) + virtual BOOL OnInitDialog(); + afx_msg void OnForceAudio(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +class COptRender3D : public CDialog +{ +// Construction +public: + COptRender3D(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptRender) + enum { IDD = IDD_OPT_RENDER3D }; + CComboBox m_WireMode; + CComboBox m_DrawNormals; + CButton m_Use3DRender; + CButton m_NoBackFace; + CButton m_EmulatePOW2; + //}}AFX_DATA + + void SaveOptions(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptRender) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(COptRender) + virtual BOOL OnInitDialog(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// +// COptions dialog + +class COptions : public CDialog +{ +// Construction +public: + COptions(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(COptions) + enum { IDD = IDD_OPTIONS }; + CComboBox m_Selection; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COptions) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + COptGen m_general; + COptSystems m_systems; + COptRender m_render; + COptRender3D m_render3D; + COptAudio m_audio; + COptHTTP m_http; + COptFont m_font; + COptStream m_stream; + COptDecoder m_decoder; + + + void HideAll(); + + // Generated message map functions + //{{AFX_MSG(COptions) + virtual BOOL OnInitDialog(); + virtual void OnOK(); + afx_msg void OnSaveopt(); + afx_msg void OnSelchangeCombosel(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_OPTIONS_H__5C839953_58C0_4D9D_89CE_2820C7686C1B__INCLUDED_) diff --git a/applications/deprecated/old_arch/osmo4_wce/Osmo4.cpp b/applications/deprecated/old_arch/osmo4_wce/Osmo4.cpp new file mode 100644 index 0000000..cbd8c56 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wce/Osmo4.cpp @@ -0,0 +1,638 @@ +// Osmo4.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "Osmo4.h" + +#include +#include +#include "MainFrm.h" +#include "OpenDlg.h" +#include "Options.h" +#include + + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// COsmo4 + +BEGIN_MESSAGE_MAP(COsmo4, CWinApp) + //{{AFX_MSG_MAP(COsmo4) + ON_COMMAND(ID_APP_ABOUT, OnAppAbout) + ON_COMMAND(IDD_CONFIGURE, OnConfigure) + ON_COMMAND(ID_OPEN_FILE, OnOpenFile) + ON_COMMAND(ID_OPEN_URL, OnOpenUrl) + ON_COMMAND(ID_SHORTCUTS, OnShortcuts) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + + + + + + +Bool Osmo4CE_EventProc(void *priv, GF_Event *event) +{ + u32 dur; + COsmo4 *app = (COsmo4 *) priv; + CMainFrame *pFrame = (CMainFrame *) app->m_pMainWnd; + /*shutdown*/ + if (!pFrame) return 0; + + switch (event->type) { + case GF_EVENT_MESSAGE: + if (event->message.error!=GF_OK) { + if (event->message.errorconsole_err = event->message.error; + pFrame->console_message = event->message.message; + pFrame->PostMessage(WM_CONSOLEMSG, 0, 0); + } + return 0; + } + if (1) return 0; + /*process user message*/ + pFrame->console_err = GF_OK; + pFrame->console_message = event->message.message; + pFrame->PostMessage(WM_CONSOLEMSG, 0, 0); + break; + case GF_EVENT_SIZE: + break; + case GF_EVENT_SCENE_SIZE: + app->m_scene_width = event->size.width; + app->m_scene_height = event->size.height; + if (!pFrame->m_full_screen) + pFrame->PostMessage(WM_SETSIZE, event->size.width, event->size.height); + break; + case GF_EVENT_CONNECT: + app->m_open = event->connect.is_connected; + break; + case GF_EVENT_DURATION: + dur = (u32) (1000 * event->duration.duration); + if (dur<2000) dur = 0; + app->m_duration = dur; + app->m_can_seek = event->duration.can_seek && dur; + pFrame->m_progBar.m_range_invalidated = 1; + /*by default, don't display timing if not seekable and vice-versa*/ + if (app->m_can_seek != pFrame->m_view_timing) { + pFrame->m_view_timing = app->m_can_seek; + if (!pFrame->m_full_screen) + pFrame->PostMessage(WM_SETSIZE, 0, 0); + } + break; + case GF_EVENT_NAVIGATE: + /*store URL since it may be destroyed, and post message*/ + app->m_navigate_url = event->navigate.to_url; + pFrame->PostMessage(WM_NAVIGATE, NULL, NULL); + return 1; + case GF_EVENT_QUIT: + pFrame->PostMessage(WM_CLOSE, 0L, 0L); + break; + /*ipaq keys*/ + case GF_EVENT_KEYDOWN: + switch (event->key.key_code) { + case GF_KEY_F1: + pFrame->PostMessage(WM_COMMAND, ID_FILE_OPEN); + break; + case GF_KEY_F2: + pFrame->PostMessage(WM_QUIT); + break; + case GF_KEY_F3: + pFrame->PostMessage(WM_COMMAND, ID_FILE_RESTART); + break; + case GF_KEY_F5: + pFrame->PostMessage(WM_COMMAND, ID_VIEW_FULLSCREEN); + break; + case GF_KEY_ENTER: + pFrame->PostMessage(WM_COMMAND, ID_FILE_PAUSE); + break; + case GF_KEY_LEFT: + if (app->m_duration>=2000) { + s32 res = gf_term_get_time_in_ms(app->m_term) - 5*app->m_duration/100; + if (res<0) res=0; + gf_term_play_from_time(app->m_term, res, 0); + } + break; + case GF_KEY_RIGHT: + if (app->m_duration>=2000) { + u32 res = gf_term_get_time_in_ms(app->m_term) + 5*app->m_duration/100; + if (res>=app->m_duration) res = 0; + gf_term_play_from_time(app->m_term, res, 0); + } + break; + case GF_KEY_UP: + if (app->m_duration>=2000) pFrame->PostMessage(WM_COMMAND, ID_FILE_STEP); + break; + case GF_KEY_DOWN: + gf_term_set_option(app->m_term, GF_OPT_REFRESH, 0); + break; + } + break; + case GF_EVENT_DBLCLICK: + pFrame->PostMessage(WM_COMMAND, ID_VIEW_FULLSCREEN); + return 0; + } + + return 0; +} + +COsmo4::COsmo4() + : CWinApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance + m_logs = NULL; +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only COsmo4 object + +COsmo4 theApp; + +static void osmo4_do_log(void *cbk, GF_LOG_Level level, GF_LOG_Tool tool, const char *fmt, va_list list) +{ + FILE *logs = (FILE *) cbk; + if (logs) { + vfprintf(logs, fmt, list); + fflush(logs); + } +} + +void COsmo4::EnableLogs(Bool turn_on) +{ + if (turn_on) { + const char *filename = gf_cfg_get_key(m_user.config, "General", "LogFile"); + if (!filename) { + gf_cfg_set_key(m_user.config, "General", "LogFile", "\\gpac_logs.txt"); + filename = "\\gpac_logs.txt"; + } + m_logs = gf_fopen(filename, "wt"); + if (!m_logs) { + MessageBox(NULL, _T("Couldn't open log file on file system"), _T("Disabling logs"), MB_OK); + turn_on = 0; + } else { + gf_log_set_tools_levels("network:rtp:sync:codec:media@debug"); + gf_log_set_callback(m_logs, osmo4_do_log); + gf_cfg_set_key(m_user.config, "General", "Logs", "network:rtp:sync:codec:media@debug"); + } + } + if (!turn_on) { + if (m_logs) { + gf_fclose(m_logs); + m_logs = 0; + } + gf_log_set_tool_level(GF_LOG_ALL, GF_LOG_NONE); + gf_log_set_callback(NULL, NULL); + gf_cfg_set_key(m_user.config, "General", "Logs", "all@quiet"); + } +} + +///////////////////////////////////////////////////////////////////////////// +// COsmo4 initialization + +BOOL COsmo4::InitInstance() +{ + Bool first_load = 0; + if (!AfxSocketInit()) + { + AfxMessageBox(IDP_SOCKETS_INIT_FAILED); + return FALSE; + } + + gf_sys_init(GF_MemTrackerNone); + + SetRegistryKey(_T("GPAC")); + + m_prev_batt_bl = m_prev_ac_bl = 0; + + m_screen_width = GetSystemMetrics(SM_CXSCREEN); + m_screen_height = GetSystemMetrics(SM_CYSCREEN); + m_menu_height = GetSystemMetrics(SM_CYMENU); + m_scene_width = m_scene_height = 0; + + CMainFrame* pFrame = new CMainFrame; + m_pMainWnd = pFrame; + + pFrame->LoadFrame(IDR_MAINFRAME, WS_VISIBLE, NULL, NULL); + + pFrame->ShowWindow(m_nCmdShow); + pFrame->UpdateWindow(); + + TCHAR w_config_path[MAX_PATH]; + char config_path[MAX_PATH]; + GetModuleFileName(NULL, w_config_path, MAX_PATH); + CE_WideToChar((u16 *) w_config_path, (char *) config_path); + + while (config_path[strlen((char *) config_path)-1] != '\\') config_path[strlen((char *) config_path)-1] = 0; + + /*setup user*/ + memset(&m_user, 0, sizeof(GF_User)); + + /*init config and plugins*/ + m_user.config = gf_cfg_init(NULL, &first_load); + if (!m_user.config) { + MessageBox(NULL, _T("GPAC Configuration file not found"), _T("Fatal Error"), MB_OK); + m_pMainWnd->PostMessage(WM_CLOSE); + } + + const char *str = gf_cfg_get_key(m_user.config, "General", "Logs"); + EnableLogs((str && strcmp(str, "all@quiet") ) ? 1 : 0); + + if (first_load) { + /*first launch, register all files ext*/ + u32 i; + for (i=0; iCanHandleURL(ifce, "test.test"); + gf_modules_close_interface((GF_BaseInterface *)ifce); + } + } + ::MessageBox(NULL, _T("Osmo4/GPAC Setup complete"), _T("Initial launch"), MB_OK); + } + + + str = gf_cfg_get_key(m_user.config, "Core", "ModulesDirectory"); + m_user.modules = gf_modules_new(str, m_user.config); + if (!m_user.modules || ! gf_modules_get_count(m_user.modules) ) { + MessageBox(NULL, _T("No plugins available - system cannot work"), _T("Fatal Error"), MB_OK); + m_pMainWnd->PostMessage(WM_QUIT); + return FALSE; + } + + m_user.config = m_user.config; + m_user.modules = m_user.modules; + m_user.EventProc = Osmo4CE_EventProc; + m_user.opaque = this; + m_user.os_window_handler = pFrame->m_wndView.m_hWnd; + + + m_term = gf_term_new(&m_user); + if (! m_term) { + MessageBox(NULL, _T("Cannot load MPEG-4 Terminal"), _T("Fatal Error"), MB_OK); + m_pMainWnd->PostMessage(WM_QUIT); + } + + m_stopped = 0; + m_open = 0; + m_can_seek = 0; + m_DoResume = 0; + SetOptions(); + pFrame->SendMessage(WM_SETSIZE, 0, 0); + ShowTaskBar(0); + + CCommandLineInfo cmdInfo; + ParseCommandLine(cmdInfo); + + if (! cmdInfo.m_strFileName.IsEmpty()) { + m_filename = cmdInfo.m_strFileName; + m_pMainWnd->PostMessage(WM_OPENURL); + } else { + str = gf_cfg_get_key(m_user.config, "General", "StartupFile"); + if (str) gf_term_connect(m_term, str); + } + return TRUE; +} + +///////////////////////////////////////////////////////////////////////////// +// COsmo4 message handlers + + + + + +///////////////////////////////////////////////////////////////////////////// +// CAboutDlg dialog used for App About + +class CAboutDlg : public CDialog +{ +public: + CAboutDlg(); + +// Dialog Data + //{{AFX_DATA(CAboutDlg) + enum { IDD = IDD_ABOUTBOX }; + CStatic m_AbtTxt; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CAboutDlg) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + //{{AFX_MSG(CAboutDlg) + virtual BOOL OnInitDialog(); // Added for WCE apps + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) +{ + //{{AFX_DATA_INIT(CAboutDlg) + //}}AFX_DATA_INIT +} + +void CAboutDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CAboutDlg) + DDX_Control(pDX, IDC_ABT_TEXT, m_AbtTxt); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) + //{{AFX_MSG_MAP(CAboutDlg) + // No message handlers + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// COsmo4 commands +// Added for WCE apps + +BOOL CAboutDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + CString str = _T("Osmo4 Player\nGPAC V"); + str += _T(GPAC_VERSION); + str += _T(" (build"); + str += _T(GPAC_BUILD_NUMBER); + str += _T(")"); + m_AbtTxt.SetWindowText(str); + return TRUE; +} + + + +void COsmo4::OnAppAbout() +{ + CAboutDlg aboutDlg; + ShowTaskBar(1); + aboutDlg.DoModal(); + ShowTaskBar(0); +} + + +int COsmo4::ExitInstance() +{ + gf_term_del(m_term); + gf_modules_del(m_user.modules); + gf_cfg_del(m_user.config); + ShowTaskBar(1); + gf_sys_close(); + if (m_logs) gf_fclose(m_logs); + return CWinApp::ExitInstance(); +} + +void COsmo4::SetOptions() +{ + const char *sOpt = gf_cfg_get_key(m_user.config, "General", "Loop"); + m_Loop = (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0; + sOpt = gf_cfg_get_key(m_user.config, "General", "FillScreen"); + m_fit_screen = (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0; + sOpt = gf_cfg_get_key(m_user.config, "General", "DisableBackLight"); + m_disable_backlight = (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0; + gf_term_set_option(m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_KEEP); + sOpt = gf_cfg_get_key(m_user.config, "General", "NoMIMETypeFetch"); + m_no_mime_fetch = (!sOpt || !stricmp(sOpt, "yes")) ? 1 : 0; + + //gf_term_set_option(m_term, GF_OPT_AUDIO_VOLUME, 100); +} + +void COsmo4::Pause() +{ + if (!m_open) return; + + if (gf_term_get_option(m_term, GF_OPT_PLAY_STATE)==GF_STATE_PLAYING) { + gf_term_set_option(m_term, GF_OPT_PLAY_STATE, GF_STATE_PAUSED); + SetBacklightState(0); + } else { + gf_term_set_option(m_term, GF_OPT_PLAY_STATE, GF_STATE_PLAYING); + SetBacklightState(1); + } + ((CMainFrame*)m_pMainWnd)->SetPauseButton(); +} + + +void COsmo4::OnConfigure() +{ + COptions dlg; + + ShowTaskBar(1); + dlg.DoModal(); + ShowTaskBar(0); +} + +void COsmo4::ShowTaskBar(Bool showIt, Bool pause_only) +{ + if (showIt) { + m_DoResume = 0; + if (gf_term_get_option(m_term, GF_OPT_PLAY_STATE)==GF_STATE_PLAYING) { + m_DoResume = 1; + gf_term_set_option(m_term, GF_OPT_PLAY_STATE, GF_STATE_PAUSED); + } + gf_term_set_option(m_term, GF_OPT_FREEZE_DISPLAY, 1); + if (!pause_only) { + SHFullScreen(GetForegroundWindow(), SHFS_HIDESTARTICON | SHFS_SHOWTASKBAR| SHFS_SHOWSIPBUTTON); + ::ShowWindow(::FindWindow(_T("HHTaskbar"),NULL), SW_SHOWNA); + } + SetBacklightState(0); + } else { + if (!pause_only) { + SHFullScreen(GetForegroundWindow(), SHFS_HIDESTARTICON | SHFS_HIDETASKBAR| SHFS_HIDESIPBUTTON); + ::ShowWindow(::FindWindow(_T("HHTaskbar"),NULL), SW_HIDE); + } + gf_term_set_option(m_term, GF_OPT_FREEZE_DISPLAY, 0); + gf_term_set_option(m_term, GF_OPT_REFRESH, 0); + if (m_DoResume) { + gf_term_set_option(m_term, GF_OPT_PLAY_STATE, GF_STATE_PLAYING); + SetBacklightState(1); + m_DoResume = 0; + } + m_pMainWnd->SetFocus(); + } +} + + +CString COsmo4::GetFileFilter() +{ + u32 keyCount, i; + CString sFiles; + CString sExts; + CString supportedFiles; + + return CString("All Files |*.*|"); + + supportedFiles = "All Known Files|*.m3u;*.pls"; + + sExts = ""; + sFiles = ""; + keyCount = gf_cfg_get_key_count(m_user.config, "MimeTypes"); + for (i=0; i + name*/ + strcpy(szKeyList, opt+1); + sKey = strrchr(szKeyList, '\"'); + if (!sKey) continue; + sKey[0] = 0; + /*get description*/ + sKey = strrchr(szKeyList, '\"'); + if (!sKey) continue; + strcpy(sDesc, sKey+1); + sKey[0] = 0; + sKey = strrchr(szKeyList, '\"'); + if (!sKey) continue; + sKey[0] = 0; + + CE_CharToWide(sDesc, (unsigned short *)swDesc); + CE_CharToWide(szKeyList, (unsigned short *)swKeyList); + + /*if same description for # mime types skip (means an old mime syntax)*/ + if (sFiles.Find((LPCTSTR) swDesc)>=0) continue; + /*if same extensions for # mime types skip (don't polluate the file list)*/ + if (sExts.Find((LPCTSTR) swKeyList)>=0) continue; + + sExts += (LPCTSTR) swKeyList; + sExts += " "; + sFiles += (LPCTSTR) swDesc; + sFiles += "|"; + + first = 1; + + sOpt = CString(szKeyList); + while (1) { + + int pos = sOpt.Find(' '); + CString ext = (pos==-1) ? sOpt : sOpt.Left(pos); + /*WATCHOUT: we do have some "double" ext , eg .wrl.gz - these are NOT supported by windows*/ + if (ext.Find(_T("."))<0) { + if (!first) { + sFiles += ";"; + } else { + first = 0; + } + sFiles += "*."; + sFiles += ext; + + CString sext = ext; + sext += ";"; + if (supportedFiles.Find(sext)<0) { + supportedFiles += ";*."; + supportedFiles += ext; + } + } + + if (sOpt==ext) break; + CString rem; + rem.Format(_T("%s "), (LPCTSTR) ext); + sOpt.Replace((LPCTSTR) rem, _T("")); + } + sFiles += "|"; + } + supportedFiles += "|"; + supportedFiles += sFiles; + //supportedFiles += "M3U Playlists|*.m3u|ShoutCast Playlists|*.pls|All Files |*.*|"; + supportedFiles += "All Files |*.*|"; + return supportedFiles; +} + +void COsmo4::OnOpenFile() +{ + Bool res; + CFileDialog fd(TRUE,NULL,_T("\\"),OFN_HIDEREADONLY, GetFileFilter()); + + ShowTaskBar(1); + res = 0; + if (fd.DoModal()==IDOK) { + res = 1; + m_filename = fd.GetPathName(); + m_DoResume = 0;/*done by term*/ + } + ShowTaskBar(0); + if (res) m_pMainWnd->PostMessage(WM_OPENURL); +} + +void COsmo4::OnOpenUrl() +{ + OpenDlg dlg; + Bool res; + ShowTaskBar(1, 1); + res = 0; + if (dlg.DoModal() == IDOK) { + res = 1; + m_DoResume = 0;/*done by term*/ + } + ShowTaskBar(0, 1); + if (res) m_pMainWnd->PostMessage(WM_OPENURL); +} + +void COsmo4::SetBacklightState(Bool disable) +{ + HKEY hKey = 0; + DWORD dwSize; + DWORD dwValue; + HANDLE hBL; + + if (!m_disable_backlight) return; + + if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("ControlPanel\\Backlight"), 0, 0, &hKey ) != ERROR_SUCCESS) return; + + if (disable) { + dwSize = 4; + RegQueryValueEx(hKey, _T("BatteryTimeout"), NULL, NULL,(unsigned char*) &m_prev_batt_bl, &dwSize); + dwSize = 4; + RegQueryValueEx(hKey, _T("ACTimeout"), NULL, NULL, (unsigned char*) &m_prev_ac_bl,&dwSize); + dwSize = 4; + dwValue = 0xefff ; + RegSetValueEx(hKey, _T("BatteryTimeout"), NULL, REG_DWORD, (unsigned char *)&dwValue, dwSize); + dwSize = 4; + dwValue = 0xefff ; + RegSetValueEx( hKey, _T("ACTimeout"), NULL, REG_DWORD, (unsigned char *)&dwValue, dwSize); + } else { + if (m_prev_batt_bl) { + dwSize = 4; + RegSetValueEx(hKey, _T("BatteryTimeout"), NULL, REG_DWORD, (unsigned char *)&m_prev_batt_bl, dwSize); + } + if (m_prev_ac_bl) { + dwSize = 4; + RegSetValueEx(hKey, _T("ACTimeout"), NULL, REG_DWORD,(unsigned char *)&m_prev_ac_bl, dwSize); + } + } + RegCloseKey(hKey); + hBL = CreateEvent(NULL, FALSE, FALSE, _T("BackLightChangeEvent")); + if (hBL) { + SetEvent(hBL); + CloseHandle(hBL); + } +} + +void COsmo4::OnShortcuts() +{ + ShowTaskBar(1); + + MessageBox(NULL, + _T("Double Click: Fullscreen on/off\n"), + + _T("Osmo4 Shortcuts"), + MB_OK); + + ShowTaskBar(0); +} + diff --git a/applications/deprecated/old_arch/osmo4_wce/Osmo4.h b/applications/deprecated/old_arch/osmo4_wce/Osmo4.h new file mode 100644 index 0000000..94820a2 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wce/Osmo4.h @@ -0,0 +1,113 @@ +// Osmo4.h : main header file for the OSMO4 application +// + +#if !defined(AFX_OSMO4_H__7E4A02D1_F77D_4E97_9E10_032054B29E33__INCLUDED_) +#define AFX_OSMO4_H__7E4A02D1_F77D_4E97_9E10_032054B29E33__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef __AFXWIN_H__ +#error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// COsmo4: +// See Osmo4.cpp for the implementation of this class +// + +/*MPEG4 term*/ +#include + +enum { + WM_SCENE_DONE = WM_USER + 1, + WM_NAVIGATE, + WM_SETSIZE, + WM_OPENURL, + WM_CONSOLEMSG, +}; + + +#define IPAQ_TRANSLATE_KEY(vk) (LOWORD(vk) != 0x5b ? LOWORD(vk) : vk ) +/*navigation pad keys*/ +#define VK_IPAQ_LEFT 0x25 +#define VK_IPAQ_UP 0x26 +#define VK_IPAQ_RIGHT 0x27 +#define VK_IPAQ_DOWN 0x28 +/*"enter" key*/ +#define VK_IPAQ_START 0x86 +/*ipaq keys from left to right*/ +#define VK_IPAQ_A 0xC1 +#define VK_IPAQ_B 0xC2 +#define VK_IPAQ_C 0xC3 +#define VK_IPAQ_D 0xC4 +/*record button*/ +#define VK_IPAQ_E 0xC5 + +class COsmo4 : public CWinApp +{ +public: + COsmo4(); + + GF_Terminal *m_term; + GF_User m_user; + CString m_filename; + + u32 m_duration; + CString m_navigate_url; + Bool m_Loop, m_fit_screen, m_can_seek, m_open, m_disable_backlight, m_stopped, m_no_mime_fetch; + void Pause(); + + u32 m_scene_width, m_scene_height, m_reconnect_time; + u32 m_screen_width, m_screen_height, m_menu_height; + /*task bar on/off*/ + void ShowTaskBar(Bool showIt, Bool pause_only = 0); + + CString GetFileFilter(); + + void SetBacklightState(Bool disable); + void EnableLogs(Bool turn_on); + +private: + Bool m_DoResume; + + void SetOptions(); + + /*power management*/ + u32 m_prev_batt_bl, m_prev_ac_bl; + + FILE *m_logs; + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(COsmo4) +public: + virtual BOOL InitInstance(); + virtual int ExitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(COsmo4) + afx_msg void OnAppAbout(); + afx_msg void OnConfigure(); + afx_msg void OnOpenFile(); + afx_msg void OnOpenUrl(); + afx_msg void OnShortcuts(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +inline COsmo4 *GetApp() { + return (COsmo4 *)AfxGetApp(); +} + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft eMbedded Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_OSMO4_H__7E4A02D1_F77D_4E97_9E10_032054B29E33__INCLUDED_) diff --git a/applications/deprecated/old_arch/osmo4_wce/Osmo4.rc b/applications/deprecated/old_arch/osmo4_wce/Osmo4.rc new file mode 100644 index 0000000..de8973a --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wce/Osmo4.rc @@ -0,0 +1,758 @@ +//Microsoft eMbedded Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" +#include "newres.h" + +#include + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\Osmo4.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +IDR_MAINFRAME BITMAP MOVEABLE PURE "res\\Cmdbar.bmp" + +///////////////////////////////////////////////////////////////////////////// +// +// Toolbar +// + +IDR_MAINFRAME TOOLBAR DISCARDABLE 16, 16 +BEGIN + BUTTON ID_OPEN_FILE + BUTTON ID_FILE_PAUSE + BUTTON ID_FILE_STEP + BUTTON ID_FILE_STOP + BUTTON ID_VIEW_FULLSCREEN +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Accelerator +// + +IDR_MAINFRAME ACCELERATORS PRELOAD MOVEABLE PURE +BEGIN + "O", ID_FILE_OPEN, VIRTKEY, CONTROL, NOINVERT + "Q", ID_APP_EXIT, VIRTKEY, CONTROL, NOINVERT + "S", ID_FILE_SAVE, VIRTKEY, CONTROL, NOINVERT + "V", ID_EDIT_PASTE, VIRTKEY, CONTROL, NOINVERT + "X", ID_EDIT_CUT, VIRTKEY, CONTROL, NOINVERT + "Z", ID_EDIT_UNDO, VIRTKEY, CONTROL, NOINVERT +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ABOUTBOX DIALOG DISCARDABLE 0, 0, 140, 153 +STYLE WS_POPUP | WS_CAPTION +EXSTYLE 0x80000000L +CAPTION "About Osmo4" +FONT 8, "System" +BEGIN + ICON IDR_MAINFRAME,IDC_STATIC,15,3,21,20 + CTEXT "Osmo4 player\nGPAC MPEG-4 SDK",IDC_ABT_TEXT,44,4,64,17 + CTEXT "This program is free software and may be distributed according to the terms of the GNU Lesser General Public License", + IDC_STATIC,3,24,131,24 + CTEXT "GPAC Copyright (C) Telecom ParisTech 2000 - 2012 - All Rights Reserved http://gpac.io", + IDC_STATIC,4,52,129,26 + LTEXT "Authors: Jean Le Feuvre, Cyril Concolato",IDC_STATIC,11,100,109,8 + LTEXT "Mozilla SpiderMonkey (JavaScript)",IDC_STATIC,11,110, + 109,8 + LTEXT "The FreeType Project",IDC_STATIC,11,120,70,8 + LTEXT "The PNG Group, The I.J.G.",IDC_STATIC,12,130,87,8 + LTEXT "XVID, FAAD, MAD, 3GPP",IDC_STATIC,13,139,80,8 + LTEXT "------------ With many thanks to: -------------", + IDC_STATIC,0,91,139,8 +END + +IDD_OPENFILE DIALOG DISCARDABLE 0, 0, 133, 116 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Select file location" +FONT 8, "System" +BEGIN + DEFPUSHBUTTON "OK",IDOK,92,33,27,14 + COMBOBOX IDC_FILELIST,4,17,127,52,CBS_DROPDOWN | CBS_AUTOHSCROLL | + CBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + DEFPUSHBUTTON "Cancel",IDCANCEL,14,34,27,14 + LTEXT "Enter path to remote file",IDC_STATIC,25,6,80,8 +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "\0" + VALUE "CompanyName", "\0" + VALUE "FileDescription", "Osmo4-GPAC\0" + VALUE "FileVersion", GPAC_VERSION"\0" + VALUE "InternalName", "Osmo4\0" + VALUE "LegalCopyright", "Copyright (C) Telecom ParisTech 2005-2012\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "Osmo4.EXE\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "Osmo4-GPAC\0" + VALUE "ProductVersion", GPAC_VERSION"\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_ABOUTBOX, DIALOG + BEGIN + RIGHTMARGIN, 138 + TOPMARGIN, 1 + END + + IDD_OPENFILE, DIALOG + BEGIN + LEFTMARGIN, 2 + TOPMARGIN, 2 + BOTTOMMARGIN, 113 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Menubar +// + +IDR_MENU MENU DISCARDABLE +BEGIN + POPUP "File" + BEGIN + MENUITEM "Open", ID_OPEN_FILE + MENUITEM "Open URL", ID_OPEN_URL + MENUITEM SEPARATOR + MENUITEM "Exit", ID_APP_EXIT + END + POPUP "View" + BEGIN + POPUP "Viewpoint" + BEGIN + MENUITEM "", ID_VIEWPORT_EMPTY + END + POPUP "Navigation" + BEGIN + MENUITEM "Reset", ID_NAV_RESET + MENUITEM SEPARATOR + MENUITEM "None", ID_NAV_NONE + MENUITEM "Walk", ID_NAV_WALK + MENUITEM "Fly", ID_NAV_FLY + MENUITEM "Examine", ID_NAV_EXAMINE + MENUITEM "Slide", ID_NAV_SLIDE + MENUITEM SEPARATOR + MENUITEM "Headlight", ID_NAV_HEADLIGHT + MENUITEM "Gravity", ID_NAV_GRAVITY + POPUP "Collision" + BEGIN + MENUITEM "Off", ID_COLLIDE_OFF + MENUITEM "Regular", ID_COLLIDE_REG + MENUITEM "Displacement", ID_COLLIDE_DISP + END + END + MENUITEM SEPARATOR + MENUITEM "Fullscreen", ID_VIEW_FULLSCREEN + POPUP "Aspect Ratio" + BEGIN + MENUITEM "Keep Original", ID_VIEW_AR_ORIG + MENUITEM "Fill Screen", ID_VIEW_AR_FILL + MENUITEM "Ratio 4/3", ID_VIEW_AR_43 + MENUITEM "Ratio 16/9", ID_VIEW_AR_169 + MENUITEM SEPARATOR + MENUITEM "Fit Screen", ID_VIEW_FIT + END + MENUITEM SEPARATOR + MENUITEM "Time Control", ID_VIEW_TIMING + MENUITEM SEPARATOR + MENUITEM "Settings", IDD_CONFIGURE + END + POPUP "?" + BEGIN + MENUITEM "Shortcuts", ID_SHORTCUTS + MENUITEM "About ...", ID_APP_ABOUT + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Data +// + +IDR_MENU SHMENUBAR DISCARDABLE +BEGIN + IDR_MENU, 3, + I_IMAGENONE, ID_FILE, TBSTATE_ENABLED, + TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, IDS_CAP_FILE, 0, 0, + I_IMAGENONE, ID_VIEW, TBSTATE_ENABLED, + TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, IDS_CAP_VIEW, 0, 1, + I_IMAGENONE, ID_MENUITEM32789, TBSTATE_ENABLED, + TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, IDS_CAP_MENUITEM32790, 0, 2, +END + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDS_EDIT "File" + IDS_TOOL "Tools" + IDP_SOCKETS_INIT_FAILED "Windows sockets initialization failed." +END + +STRINGTABLE PRELOAD DISCARDABLE +BEGIN + IDR_MAINFRAME "Osmo4\n\nOsmo4\n\n\nOsmo4.Document\nOsmo4 Document" +END + +STRINGTABLE PRELOAD DISCARDABLE +BEGIN + AFX_IDS_IDLEMESSAGE "Ready" +END + +STRINGTABLE DISCARDABLE +BEGIN + ID_FILE_SAVE_AS "Save the active document with a new name\nSave As" +END + +STRINGTABLE DISCARDABLE +BEGIN + ID_APP_ABOUT "Display program information, version number and copyright\nAbout" + ID_APP_EXIT "Quit the application; prompts to save documents\nExit" +END + +STRINGTABLE DISCARDABLE +BEGIN + ID_NEXT_PANE "Switch to the next window pane\nNext Pane" + ID_PREV_PANE "Switch back to the previous window pane\nPrevious Pane" +END + +STRINGTABLE DISCARDABLE +BEGIN + ID_WINDOW_SPLIT "Split the active window into panes\nSplit" +END + +STRINGTABLE DISCARDABLE +BEGIN + ID_EDIT_CLEAR "Erase the selection\nErase" + ID_EDIT_CLEAR_ALL "Erase everything\nErase All" + ID_EDIT_COPY "Copy the selection and put it on the Clipboard\nCopy" + ID_EDIT_CUT "Cut the selection and put it on the Clipboard\nCut" + ID_EDIT_FIND "Find the specified text\nFind" + ID_EDIT_PASTE "Insert Clipboard contents\nPaste" + ID_EDIT_REPEAT "Repeat the last action\nRepeat" + ID_EDIT_REPLACE "Replace specific text with different text\nReplace" + ID_EDIT_SELECT_ALL "Select the entire document\nSelect All" + ID_EDIT_UNDO "Undo the last action\nUndo" + ID_EDIT_REDO "Redo the previously undone action\nRedo" +END + +STRINGTABLE DISCARDABLE +BEGIN + AFX_IDS_SCCLOSE "Close the active window and prompts to save the documents" +END + +STRINGTABLE DISCARDABLE +BEGIN + AFX_IDS_SCTASKLIST "Activate Task List" +END + +STRINGTABLE DISCARDABLE +BEGIN + IDS_NEW "New" + IDS_FILE "File" + IDS_MHELP "Help" + IDS_SAVE "Save" + IDS_CUT "Cut" + IDS_COPY "Copy" + IDS_PASTE "Paste" + IDS_ABOUT "About" +END + +STRINGTABLE DISCARDABLE +BEGIN + ID_INDICATOR_CAPS "CAP" +END + +STRINGTABLE DISCARDABLE +BEGIN + IDS_CAP_FILE "File" + ID_FILE_RESTART "Restart presentation" + IDS_CAP_MENUITEM32790 "?" +END + +STRINGTABLE DISCARDABLE +BEGIN + IDS_CAP_VIEW "View" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// French (France) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_FRA) +#ifdef _WIN32 +LANGUAGE LANG_FRENCH, SUBLANG_FRENCH +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_CONTROL DIALOG DISCARDABLE 0, 0, 161, 15 +STYLE DS_SETFOREGROUND | WS_CHILD | WS_VISIBLE +EXSTYLE WS_EX_STATICEDGE +FONT 8, "System" +BEGIN + CONTROL "Slider1",IDC_SLIDER,"msctls_trackbar32",TBS_BOTH | + TBS_NOTICKS | WS_TABSTOP,0,1,101,12 + LTEXT "00:00 FPS 30.00",IDC_TIME,102,3,56,10 +END + +IDD_OPTIONS DIALOG DISCARDABLE 0, 0, 134, 121 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Osmo4 Options" +FONT 8, "System" +BEGIN + PUSHBUTTON "Apply",IDC_SAVEOPT,108,3,24,14 + COMBOBOX IDC_COMBOSEL,3,5,99,80,CBS_DROPDOWNLIST | WS_VSCROLL | + WS_TABSTOP + LTEXT "-------------------------------------------------------", + IDC_STATIC,0,17,133,8 +END + +IDD_OPT_GEN DIALOG DISCARDABLE 0, 120, 133, 121 +STYLE DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "System" +BEGIN + CONTROL "Loop at end",IDC_LOOP,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,6,28,54,10 + PUSHBUTTON "Make Default MPEG-4 Player",IDC_FILEASSOC,5,90,124,20, + BS_MULTILINE + CONTROL "Stretch display to fill screen",IDC_FILL_SCREEN,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,6,45,104,10 + CONTROL "Disable Backlight while playing",IDC_NO_BACKLIGHT, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,61,111,10 + CONTROL "Enable logs",IDC_ENABLE_LOGS,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,6,74,52,10 +END + +IDD_OPT_RENDER DIALOG DISCARDABLE 0, 20, 133, 121 +STYLE DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "System" +BEGIN + LTEXT "Rendering Frame Rate",IDC_STATIC,27,27,73,8 + COMBOBOX IDC_BIFS_RATE,29,39,68,47,CBS_DROPDOWNLIST | WS_VSCROLL | + WS_TABSTOP + CONTROL "Direct Rendering",IDC_DIRECTRENDER,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,1,64,69,10 + CONTROL "Scalable Zoom",IDC_ZOOM_SCALABLE,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,70,64,61,10 + CONTROL "Fast Rendering",IDC_FAST_RENDER,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,70,79,60,10 + CONTROL "Force Scene Size",IDC_FORCE_SIZE,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,1,79,70,10 + LTEXT "Anti-Aliasing",IDC_STATIC,6,103,40,8 + COMBOBOX IDC_AA_LIST,56,99,71,47,CBS_DROPDOWNLIST | WS_VSCROLL | + WS_TABSTOP +END + +IDD_OPT_SYSTEMS DIALOG DISCARDABLE 0, 20, 133, 121 +STYLE DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "System" +BEGIN + LTEXT "Prefered Language for stream selection",IDC_STATIC,3,46, + 64,19 + COMBOBOX IDC_LANG,69,48,60,72,CBS_DROPDOWNLIST | WS_VSCROLL | + WS_TABSTOP + LTEXT "Decoder Threading",IDC_STATIC,4,75,62,8 + COMBOBOX IDC_DEC_THREAD,69,72,60,30,CBS_DROPDOWNLIST | WS_VSCROLL | + WS_TABSTOP + CONTROL "Always draw late BIFS frames",IDC_BIFSDROP,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,4,91,111,10 + CONTROL "Force Single Timeline",IDC_FORCE_DURATION,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,4,106,82,10 +END + +IDD_OPT_VIDEO DIALOG DISCARDABLE 0, 20, 133, 121 +STYLE DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "System" +BEGIN + LTEXT "Graphics Driver",IDC_STATIC,5,54,50,8 + LTEXT "Anti-Aliasing",IDC_STATIC,6,77,40,8 + LTEXT "Video Driver",IDC_STATIC,5,98,40,8 + COMBOBOX IDC_GD_LIST,56,53,71,30,CBS_DROPDOWNLIST | CBS_SORT | + WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_AA_LIST,56,73,71,47,CBS_DROPDOWNLIST | WS_VSCROLL | + WS_TABSTOP + COMBOBOX IDC_VIDEO_LIST,56,95,71,30,CBS_DROPDOWNLIST | CBS_SORT | + WS_VSCROLL | WS_TABSTOP +END + +IDD_OPT_AUDIO DIALOG DISCARDABLE 0, 20, 133, 121 +STYLE DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "System" +BEGIN + CONTROL "Force Audio Config",IDC_FORCE_AUDIO,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,5,33,77,10 + LTEXT "Number of buffers",IDC_STATIC,5,51,60,8 + LTEXT "Total Duration in ms",IDC_STATIC,4,69,64,8 + EDITTEXT IDC_EDIT_AUDIO,68,48,34,14,ES_AUTOHSCROLL | ES_READONLY + CONTROL "Spin1",IDC_SPIN_AUDIO,"msctls_updown32",UDS_SETBUDDYINT | + UDS_ARROWKEYS | UDS_HORZ,105,49,19,12 + EDITTEXT IDC_AUDIO_DUR,68,67,34,14,ES_AUTOHSCROLL + CONTROL "Spin1",IDC_SPIN_DUR,"msctls_updown32",UDS_SETBUDDYINT | + UDS_ARROWKEYS | UDS_HORZ,105,68,19,12 + CONTROL "Disable Audio Resync",IDC_AUDIO_RESYNC,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,5,85,84,10 + LTEXT "Default Plugin",IDC_STATIC,5,103,45,8 + COMBOBOX IDC_DRIVER_LIST,52,100,73,30,CBS_DROPDOWNLIST | CBS_SORT | + WS_VSCROLL | WS_TABSTOP +END + +IDD_OPT_FONT DIALOG DISCARDABLE 0, 20, 133, 119 +STYLE DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "System" +BEGIN + LTEXT "Font Engine",IDC_STATIC,35,33,39,8 + COMBOBOX IDC_FONT_LIST,12,45,98,30,CBS_DROPDOWNLIST | CBS_SORT | + WS_VSCROLL | WS_TABSTOP + LTEXT "Systems Font Directory",IDC_STATIC,19,87,76,8 + PUSHBUTTON "...",IDC_BROWSE_FONT,6,98,115,12 + CONTROL "Draw text through texturing",IDC_USE_TEXTURE,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,10,68,107,10 +END + +IDD_OPT_HTTP DIALOG DISCARDABLE 0, 20, 133, 121 +STYLE DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "System" +BEGIN + LTEXT "Cache Directory",IDC_STATIC,35,9,53,8 + PUSHBUTTON "...",IDC_BROWSE_CACHE,7,22,113,14 + CONTROL "Remove temp files",IDC_CLEAN_CACHE,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,7,43,75,10 + CONTROL "Always redownload incomplete cached files", + IDC_RESTART_CACHE,"Button",BS_AUTOCHECKBOX | + BS_MULTILINE | WS_TABSTOP,7,54,102,22 + CONTROL "XML Progressive Load",IDC_SAX_PROGRESSIVE,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,8,77,85,10 + EDITTEXT IDC_SAX_DURATION,27,89,35,14,ES_CENTER | ES_AUTOHSCROLL | + ES_NUMBER +END + +IDD_OPT_STREAM DIALOG DISCARDABLE 0, 20, 133, 121 +STYLE DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "System" +BEGIN + LTEXT "Default RTSP Port",IDC_STATIC,33,33,58,8 + COMBOBOX IDC_PORT,15,42,95,30,CBS_DROPDOWNLIST | CBS_SORT | + WS_VSCROLL | WS_TABSTOP + CONTROL "RTP over RTSP",IDC_RTSP,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,1,58,64,10 + CONTROL "RTP Reordering",IDC_REORDER,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,66,58,66,10 + EDITTEXT IDC_TIMEOUT,1,72,35,14,ES_CENTER | ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "ms before control timeout",IDC_STATIC,44,75,83,8 + EDITTEXT IDC_BUFFER,1,87,35,14,ES_CENTER | ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "ms of Media Buffering ",IDC_STATIC,44,90,72,8 + CONTROL "Rebuffer if less than",IDC_REBUFFER,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,2,106,78,10 + EDITTEXT IDC_REBUFFER_LEN,81,105,35,12,ES_CENTER | ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "ms",IDC_STATIC,121,108,10,8 +END + +IDD_OPT_DECODER DIALOG DISCARDABLE 0, 20, 133, 121 +STYLE DS_MODALFRAME | DS_CONTROL | WS_CHILD +FONT 8, "System" +BEGIN + LTEXT "Prefered Audio Output",-1,33,9,70,8 + LTEXT "Prefered Video Output",-1,33,44,80,8 + COMBOBOX IDC_AUDEC_LIST,11,22,109,54,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_VIDEC_LIST,12,57,109,47,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP +END + +IDD_OPT_RENDER3D DIALOG DISCARDABLE 0, 0, 133, 121 +STYLE DS_MODALFRAME | WS_CHILD +FONT 8, "System" +BEGIN + CONTROL "Use 3D Renderer",IDC_USE_3D_REN,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,3,29,121,10 + CONTROL "Disable Backface Culling",IDC_NO_BACKCULL,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,3,41,92,10 + CONTROL "Emulate pow2 textures for video",IDC_EMULATE_POW2, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,1,97,121,10 + LTEXT "Drawing Mode",IDC_STATIC,5,56,46,8 + COMBOBOX IDC_WIRE_MODE,61,54,61,35,CBS_DROPDOWN | WS_VSCROLL | + WS_TABSTOP + LTEXT "Draw Normals",IDC_STATIC,5,73,45,8 + COMBOBOX IDC_DRAW_NORMALS,62,72,61,35,CBS_DROPDOWN | WS_VSCROLL | + WS_TABSTOP +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_CONTROL, DIALOG + BEGIN + RIGHTMARGIN, 160 + BOTTOMMARGIN, 14 + END + + IDD_OPTIONS, DIALOG + BEGIN + RIGHTMARGIN, 133 + BOTTOMMARGIN, 120 + END + + IDD_OPT_GEN, DIALOG + BEGIN + RIGHTMARGIN, 132 + BOTTOMMARGIN, 120 + END + + IDD_OPT_RENDER, DIALOG + BEGIN + RIGHTMARGIN, 132 + BOTTOMMARGIN, 120 + END + + IDD_OPT_SYSTEMS, DIALOG + BEGIN + RIGHTMARGIN, 132 + BOTTOMMARGIN, 120 + END + + IDD_OPT_VIDEO, DIALOG + BEGIN + RIGHTMARGIN, 132 + BOTTOMMARGIN, 120 + END + + IDD_OPT_AUDIO, DIALOG + BEGIN + RIGHTMARGIN, 132 + BOTTOMMARGIN, 120 + END + + IDD_OPT_FONT, DIALOG + BEGIN + RIGHTMARGIN, 132 + BOTTOMMARGIN, 118 + END + + IDD_OPT_HTTP, DIALOG + BEGIN + RIGHTMARGIN, 132 + BOTTOMMARGIN, 120 + END + + IDD_OPT_STREAM, DIALOG + BEGIN + RIGHTMARGIN, 132 + BOTTOMMARGIN, 120 + END + + IDD_OPT_DECODER, DIALOG + BEGIN + RIGHTMARGIN, 132 + BOTTOMMARGIN, 120 + END +END +#endif // APSTUDIO_INVOKED + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "#include ""newres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\Osmo4.rc2"" // non-Microsoft eMbedded Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#include ""wceres.rc"" // WCE-specific components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog Info +// + +IDD_OPT_RENDER DLGINIT +BEGIN + IDC_BIFS_RATE, 0x403, 8, 0 +0x0035, 0x002e, 0x0030, 0x0000, + IDC_BIFS_RATE, 0x403, 8, 0 +0x0037, 0x002e, 0x0035, 0x0000, + IDC_BIFS_RATE, 0x403, 10, 0 +0x0031, 0x0030, 0x002e, 0x0030, 0x0000, + IDC_BIFS_RATE, 0x403, 10, 0 +0x0031, 0x0032, 0x002e, 0x0035, 0x0000, + IDC_BIFS_RATE, 0x403, 10, 0 +0x0031, 0x0035, 0x002e, 0x0030, 0x0000, + IDC_BIFS_RATE, 0x403, 10, 0 +0x0032, 0x0034, 0x002e, 0x0030, 0x0000, + IDC_BIFS_RATE, 0x403, 10, 0 +0x0032, 0x0035, 0x002e, 0x0030, 0x0000, + IDC_BIFS_RATE, 0x403, 10, 0 +0x0033, 0x0030, 0x002e, 0x0030, 0x0000, + 0 +END + +IDD_OPT_SYSTEMS DLGINIT +BEGIN + IDC_DEC_THREAD, 0x403, 28, 0 +0x0053, 0x0069, 0x006e, 0x0067, 0x006c, 0x0065, 0x0020, 0x0054, 0x0068, +0x0072, 0x0065, 0x0061, 0x0064, 0x0000, + IDC_DEC_THREAD, 0x403, 26, 0 +0x004d, 0x0075, 0x0074, 0x006c, 0x0069, 0x0020, 0x0054, 0x0068, 0x0072, +0x0065, 0x0061, 0x0064, 0x0000, + IDC_DEC_THREAD, 0x403, 10, 0 +0x0046, 0x0072, 0x0065, 0x0065, 0x0000, + 0 +END + +#endif // French (France) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\Osmo4.rc2" // non-Microsoft eMbedded Visual C++ edited resources +#include "afxres.rc" // Standard components +//#include "wceres.rc" // WCE-specific components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/applications/deprecated/old_arch/osmo4_wce/ProgressBar.cpp b/applications/deprecated/old_arch/osmo4_wce/ProgressBar.cpp new file mode 100644 index 0000000..dea5b8c --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wce/ProgressBar.cpp @@ -0,0 +1,133 @@ +// ProgressBar.cpp : implementation file +// + +#include "stdafx.h" +#include "Osmo4.h" +#include "ProgressBar.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// ProgressBar dialog + + +ProgressBar::ProgressBar(CWnd* pParent /*=NULL*/) + : CDialog(ProgressBar::IDD, pParent) +{ + //{{AFX_DATA_INIT(ProgressBar) + //}}AFX_DATA_INIT + + m_grabbed = 0; + m_range_invalidated = 0; +} + + +void ProgressBar::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(ProgressBar) + DDX_Control(pDX, IDC_TIME, m_Time); + DDX_Control(pDX, IDC_SLIDER, m_Slider); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(ProgressBar, CDialog) + //{{AFX_MSG_MAP(ProgressBar) + ON_WM_SIZE() + ON_WM_HSCROLL() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// ProgressBar message handlers +#define TEXT_RIGHT_PAD 2 +void ProgressBar::OnSize(UINT nType, int cx, int cy) +{ + u32 tw; + CDialog::OnSize(nType, cx, cy); + + if (!m_Slider.m_hWnd) return; + + RECT rc; + m_Time.GetClientRect(&rc); + tw = rc.right-rc.left; + rc.left = cx - tw - TEXT_RIGHT_PAD; + rc.right = cx - TEXT_RIGHT_PAD; + m_Time.MoveWindow(&rc); + m_Slider.GetClientRect(&rc); + rc.left = 0; + rc.right = cx - tw - TEXT_RIGHT_PAD; + m_Slider.MoveWindow(&rc); +} + +void ProgressBar::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) +{ + COsmo4 *app = GetApp(); + + if (pScrollBar->GetDlgCtrlID() == IDC_SLIDER) { + if (!app->m_can_seek) return; + switch (nSBCode) { + case TB_LINEUP: + case TB_LINEDOWN: + case TB_PAGEUP: + case TB_PAGEDOWN: + case TB_THUMBPOSITION: + case TB_THUMBTRACK: + case TB_TOP: + case TB_BOTTOM: + m_grabbed = 1; + break; + case TB_ENDTRACK: + if (!app->m_open) { + SetPosition(0); + } else { + u32 seek_to = m_Slider.GetPos(); + gf_term_play_from_time(app->m_term, seek_to, 0); + } + m_grabbed = 0; + return; + } + } + CDialog::OnHScroll(nSBCode, nPos, pScrollBar); +} + +void ProgressBar::SetPosition(u32 now) +{ + TCHAR swText[20]; + u32 nb_s, nb_m; + COsmo4 *app = GetApp(); + + if (m_range_invalidated) { + if (app->m_can_seek) { + m_Slider.SetRangeMin(0); + m_Slider.SetRangeMax(app->m_duration); + m_Slider.ShowWindow(SW_SHOWNORMAL); + m_Slider.EnableWindow(TRUE); + } else { + m_Slider.ShowWindow(SW_SHOWNORMAL); + m_Slider.EnableWindow(FALSE); + + } + m_range_invalidated = 0; + } + if (now==m_prev_time) return; + + if (nowm_term, 0); + m_prev_time = now; + } + nb_s = now/1000; + nb_m = nb_s/60; + nb_s -= nb_m*60; + wsprintf(swText, _T("%02d:%02d FPS %02.2f"), nb_m, nb_s, m_FPS); + m_Time.SetWindowText(swText); + + if (!m_grabbed) m_Slider.SetPos(now); +} diff --git a/applications/deprecated/old_arch/osmo4_wce/ProgressBar.h b/applications/deprecated/old_arch/osmo4_wce/ProgressBar.h new file mode 100644 index 0000000..e8f6f10 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wce/ProgressBar.h @@ -0,0 +1,53 @@ +#if !defined(AFX_PROGRESSBAR_H__85FCAC2B_9C83_4C83_8E66_D712FC88B221__INCLUDED_) +#define AFX_PROGRESSBAR_H__85FCAC2B_9C83_4C83_8E66_D712FC88B221__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// ProgressBar.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// ProgressBar dialog + +class ProgressBar : public CDialog +{ +// Construction +public: + ProgressBar(CWnd* pParent = NULL); // standard constructor + + Bool m_grabbed, m_range_invalidated; + Double m_FPS; + u32 m_prev_time; + void SetPosition(u32 time_ms); + +// Dialog Data + //{{AFX_DATA(ProgressBar) + enum { IDD = IDD_CONTROL }; + CStatic m_Time; + CSliderCtrl m_Slider; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(ProgressBar) +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(ProgressBar) + afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_PROGRESSBAR_H__85FCAC2B_9C83_4C83_8E66_D712FC88B221__INCLUDED_) diff --git a/applications/deprecated/old_arch/osmo4_wce/Resource.h b/applications/deprecated/old_arch/osmo4_wce/Resource.h new file mode 100644 index 0000000..7cf3b7f --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wce/Resource.h @@ -0,0 +1,170 @@ +//{{NO_DEPENDENCIES}} +// Microsoft eMbedded Visual C++ generated include file. +// Used by Osmo4.rc +// +#define IDD_ABOUTBOX 100 +#define IDM_MENU 101 +#define IDS_EDIT 102 +#define IDS_TOOL 103 +#define IDP_SOCKETS_INIT_FAILED 104 +#define IDR_MAIN_EDIT 105 +#define IDR_MAIN_TOOL 106 +#define IDI_PAUSE 120 +#define IDD_OPT_HTTP 121 +#define IDR_MAINFRAME 128 +#define IDD_OPT_AUDIO 129 +#define IDD_OPENFILE 130 +#define IDD_CONTROL 131 +#define IDD_OPTIONS 132 +#define IDD_OPT_GEN 133 +#define IDD_OPT_RENDER 134 +#define IDD_OPT_SYSTEMS 135 +#define IDD_OPT_VIDEO 136 +#define IDI_PLAY 137 +#define IDD_OPT_FONT 138 +#define IDI_STOP 139 +#define IDD_OPT_STREAM 140 +#define IDR_MENU 141 +#define IDD_OPT_RENDER3D 142 +#define IDD_OPT_DECODER 143 +#define IDC_FILELIST 1000 +#define IDC_BROWSE 1001 +#define IDC_SEEK 1002 +#define IDC_PLAY 1003 +#define IDC_STOP 1004 +#define IDC_OPT_SET_NAME 1005 +#define IDC_SAVEOPT 1006 +#define IDC_LOOP 1007 +#define IDC_AUTOSTART 1008 +#define IDC_NO_CONSOLE 1009 +#define IDC_FILEASSOC 1010 +#define IDC_BIFS_RATE 1011 +#define IDC_DIRECTRENDER 1012 +#define IDC_ZOOM_SCALABLE 1013 +#define IDC_FAST_RENDER 1014 +#define IDC_FORCE_SIZE 1015 +#define IDC_COLOR 1016 +#define IDC_LANG 1017 +#define IDC_DEC_THREAD 1018 +#define IDC_BIFSDROP 1019 +#define IDC_FORCE_DURATION 1020 +#define IDC_SLIDER 1021 +#define IDC_GD_LIST 1022 +#define IDC_AA_LIST 1023 +#define IDC_VIDEO_LIST 1024 +#define IDC_FORCE_AUDIO 1025 +#define IDC_EDIT_AUDIO 1026 +#define IDC_SPIN_AUDIO 1027 +#define IDC_AUDIO_FPS 1028 +#define IDC_SPIN_DUR 1029 +#define IDC_AUDIO_RESYNC 1030 +#define IDC_DRIVER_LIST 1031 +#define IDC_FONT_LIST 1032 +#define IDC_BROWSE_FONT 1033 +#define IDC_BROWSE_CACHE 1034 +#define IDC_CLEAN_CACHE 1035 +#define IDC_RESTART_CACHE 1036 +#define IDC_PORT 1037 +#define IDC_RTSP 1038 +#define IDC_REORDER 1039 +#define IDC_TIMEOUT 1040 +#define IDC_BUFFER 1041 +#define IDC_REBUFFER 1042 +#define IDC_REBUFFER_LEN 1043 +#define IDC_VERSION 1044 +#define IDC_AUDEC_LIST 1046 +#define IDC_VIDEC_LIST 1047 +#define IDC_COMBOSEL 1048 +#define IDC_TIME 1049 +#define IDC_ABT_TEXT 1050 +#define IDC_CTRL_PLAY 1051 +#define IDC_USE_TEXTURE 1052 +#define IDC_USE_3D_REN 1053 +#define IDC_NO_BACKCULL 1054 +#define IDC_CTRL_STOP 1055 +#define IDC_EMULATE_POW2 1056 +#define IDC_WIRE_MODE 1057 +#define IDC_SAX_DURATION 1058 +#define IDC_DRAW_NORMALS 1059 +#define IDC_SPIN_OPT 1060 +#define IDC_AUDIO_DUR 1061 +#define IDC_SPIN_FPS 1062 +#define IDC_FILL_SCREEN 1063 +#define IDC_VIEW_FPS 1064 +#define IDC_NO_BACKLIGHT 1065 +#define IDC_SAX_PROGRESSIVE 1066 +#define IDC_ENABLE_LOGS 1067 +#define IDS_CAP_FILE 32772 +#define ID_FILE_RESTART 32773 +#define ID_FPS_DISP 32774 +#define IDD_CONFIGURE 32775 +#define ID_VIEW_CONTROL 32776 +#define ID_FILE_PAUSE 32777 +#define ID_FILE_STEP 32778 +#define ID_FILE_STOP 32779 +#define ID_VIEW_FULLSCREEN 32780 +#define ID_FILE 32781 +#define ID_MENUITEM32789 32782 +#define IDS_CAP_MENUITEM32790 32783 +#define ID_VIEW 32784 +#define IDS_CAP_VIEW 32785 +#define ID_OPEN_FILE 32786 +#define ID_OPEN_URL 32787 +#define ID_SHORTCUTS 32788 +#define ID_VIEW_FIT 32789 +#define ID_NAV_NONE 32806 +#define ID_NAV_SLIDE 32807 +#define ID_NAV_RESET 32808 +#define ID_NAV_WALK 32810 +#define ID_NAV_FLY 32811 +#define ID_NAV_EXAMINE 32812 +#define ID_NAV_GRAVITY 32813 +#define ID_COLLIDE_OFF 32815 +#define ID_COLLIDE_REG 32816 +#define ID_COLLIDE_DISP 32817 +#define ID_VIEWPORT_EMPTY 32818 +#define ID_NAV_HEADLIGHT 32819 +#define ID_VP_0 32820 +#define ID_VP_1 32821 +#define ID_VP_2 32822 +#define ID_VP_3 32823 +#define ID_VP_4 32824 +#define ID_VP_5 32825 +#define ID_VP_6 32826 +#define ID_VP_7 32827 +#define ID_VP_8 32828 +#define ID_VP_9 32829 +#define ID_VP_10 32830 +#define ID_VP_11 32831 +#define ID_VP_12 32832 +#define ID_VP_13 32833 +#define ID_VP_14 32834 +#define ID_VP_15 32835 +#define ID_VP_16 32836 +#define ID_VP_17 32837 +#define ID_VP_18 32838 +#define ID_VP_19 32839 +#define ID_VIEW_AR_ORIG 32890 +#define ID_VIEW_AR_FILL 32891 +#define ID_VIEW_AR_43 32892 +#define ID_VIEW_AR_169 32893 +#define ID_VIEW_TIMING 32894 +#define IDS_NEW 65000 +#define IDS_FILE 65001 +#define IDS_MHELP 65002 +#define IDS_SAVE 65003 +#define IDS_CUT 65004 +#define IDS_COPY 65005 +#define IDS_PASTE 65006 +#define IDS_ABOUT 65007 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 143 +#define _APS_NEXT_COMMAND_VALUE 32820 +#define _APS_NEXT_CONTROL_VALUE 1059 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/applications/deprecated/old_arch/osmo4_wce/StdAfx.cpp b/applications/deprecated/old_arch/osmo4_wce/StdAfx.cpp new file mode 100644 index 0000000..e6caa79 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wce/StdAfx.cpp @@ -0,0 +1,6 @@ +// stdafx.cpp : source file that includes just the standard includes +// Osmo4.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + diff --git a/applications/deprecated/old_arch/osmo4_wce/StdAfx.h b/applications/deprecated/old_arch/osmo4_wce/StdAfx.h new file mode 100644 index 0000000..93d8297 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wce/StdAfx.h @@ -0,0 +1,39 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__C81DFFE9_41A3_474E_A044_3A730DB4FFD6__INCLUDED_) +#define AFX_STDAFX_H__C81DFFE9_41A3_474E_A044_3A730DB4FFD6__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#if (_WIN32_WCE <= 200) +#error This project does not support MFCCE 2.00 or earlier, because it requires CControlBar, available only in MFCCE 2.01 or later +#endif + +#if (_WIN32_WCE <= 211) +#error This project can not be built for H/PC Pro 2.11 or earlier platforms. +#endif + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions + +#if defined(_AFXDLL) +#include // MFC support for Internet Explorer 4 Common Controls +#endif + +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +#include // MFC socket extensions + +//{{AFX_INSERT_LOCATION}} +// Microsoft eMbedded Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__C81DFFE9_41A3_474E_A044_3A730DB4FFD6__INCLUDED_) diff --git a/applications/deprecated/old_arch/osmo4_wce/newres.h b/applications/deprecated/old_arch/osmo4_wce/newres.h new file mode 100644 index 0000000..6cd34bb --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wce/newres.h @@ -0,0 +1,28 @@ +#ifndef __NEWRES_H__ +#define __NEWRES_H__ + +#define SHMENUBAR RCDATA +#if !(defined(_WIN32_WCE_PSPC) && (_WIN32_WCE >= 300)) +#undef HDS_HORZ +#undef HDS_BUTTONS +#undef HDS_HIDDEN + +#include +// for MenuBar +#define I_IMAGENONE (-2) +#define NOMENU 0xFFFF +#define IDS_SHNEW 1 +#define IDM_SHAREDNEW 10 +#define IDM_SHAREDNEWDEFAULT 11 + +// for Tab Control +#define TCS_SCROLLOPPOSITE 0x0001 // assumes multiline tab +#define TCS_BOTTOM 0x0002 +#define TCS_RIGHT 0x0002 +#define TCS_VERTICAL 0x0080 +#define TCS_MULTISELECT 0x0004 // allow multi-select in button mode +#define TCS_FLATBUTTONS 0x0008 +#endif //_WIN32_WCE_PSPC + + +#endif //__NEWRES_H__ diff --git a/applications/deprecated/old_arch/osmo4_wce/res/Cmdbar.bmp b/applications/deprecated/old_arch/osmo4_wce/res/Cmdbar.bmp new file mode 100644 index 0000000000000000000000000000000000000000..90764e4ac4815fd667742681ca767037578c6fa9 GIT binary patch literal 886 zcmcJNK@!3s5CpNbDmnTL-{IXq_ykUgM{fG8{0w_`2~Z|gIk|=n%ghb~@&1_3!D1rc z7*EEPaS9hQQE)H%oI>CUDfx@cG%U-)TR^raz8#=!ENbY0f2Lsm7@m0sf zZRR{wpR2wci_r6_D?hT)D1mZb4ASt!4~;*Md}q5Tzn@+G&JUfEZ~8m_1+V_uS@M-% s=)He~8$U|E<6GJBg`$s;-<;Kr?{poS;4}s1>lMTo4)XecJD_dgFMk0!djJ3c literal 0 HcmV?d00001 diff --git a/applications/deprecated/old_arch/osmo4_wce/res/Osmo4.ico b/applications/deprecated/old_arch/osmo4_wce/res/Osmo4.ico new file mode 100644 index 0000000000000000000000000000000000000000..96d9091d07264eb11ae7347b213d673a6961cc51 GIT binary patch literal 1078 zcmb7DJ8Hu~5Ph-@LXD+1l?8#|E2OfI6vzR*;6h!ZGKN5|BBLBc>QcINz`V0$q6QufMM=!G7K_ej6W!;qG=kQ z3Rw4twbID^$t7`%vJL4JaOP%H2Tw2uhff|#6jmk=R}oC-pqt^)$ZM3LFDn=s!Y);I z414{`&SE!Z(fHjywNw7txIflM7~;H(`_(t@I~2I5Ucc9);BW8Va)p-wyTFqZBKfB% z!BHF(9MN<@p2K#AfOf#zF1OYs_6V#Fz+w-~cI71RmZP*YNxC)3x&>Ns1@w_$P12|2 zsu}lHm-f{z@7IULwmq)e?P0O*_OrBmnx^Wdp6aa`n&oD4wD!Cpm*lwT4fWe(;1`_x z%>+q9<$rrFs92m)xP2o`)Me6v%%PUme?CK<>!l_tbj4wg+LH9C?Q) gpaccab.inf ECHO Provider = "GPAC %gpac_version%" >> gpaccab.inf diff --git a/bin/smartphone 2003 (armv4)/release/install/gpac.inf b/applications/deprecated/old_arch/osmo4_wce/smartphone 2003 (armv4)/release/install/gpac.inf similarity index 100% rename from bin/smartphone 2003 (armv4)/release/install/gpac.inf rename to applications/deprecated/old_arch/osmo4_wce/smartphone 2003 (armv4)/release/install/gpac.inf diff --git a/bin/smartphone 2003 (armv4)/release/install/readme.txt b/applications/deprecated/old_arch/osmo4_wce/smartphone 2003 (armv4)/release/install/readme.txt similarity index 90% rename from bin/smartphone 2003 (armv4)/release/install/readme.txt rename to applications/deprecated/old_arch/osmo4_wce/smartphone 2003 (armv4)/release/install/readme.txt index 43ce565..19bf9d1 100644 --- a/bin/smartphone 2003 (armv4)/release/install/readme.txt +++ b/applications/deprecated/old_arch/osmo4_wce/smartphone 2003 (armv4)/release/install/readme.txt @@ -1,6 +1,6 @@ This will install GPAC for ARM PocketPC/SmartPhones 2003 Platforms GPAC is an open source MPEG-4 framework developped by ENST and available at: - http://gpac.sourceforge.net + http://gpac.io WARNING: THIS RELEASE COMES WITH NO WARRANTY, AND MAY EVEN DAMAGE YOUR HANDHELD DEVICE. PLEASE READ CAREFULLY THE LICENSE HEREJOIN diff --git a/applications/deprecated/old_arch/osmo4_wx/Darwin.Info.plist b/applications/deprecated/old_arch/osmo4_wx/Darwin.Info.plist new file mode 100644 index 0000000..af4919b --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/Darwin.Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + Osmo4 + CFBundleIconFile + Osmo + CFBundleIdentifier + org.gpac.Osmo4 + CFBundlePackageType + APPL + CFBundleSignature + Omo4 + NSHumanReadableCopyright + Copyright (c) 2003-2005 TelecomParisTech + NSMainNibFile + NSMainNibFile + NSPrincipalClass + NSApplication + + diff --git a/applications/deprecated/old_arch/osmo4_wx/Darwin.InfoPlist.strings b/applications/deprecated/old_arch/osmo4_wx/Darwin.InfoPlist.strings new file mode 100644 index 0000000000000000000000000000000000000000..3eb07750bca68090fcbb11a545940f2233d9e97d GIT binary patch literal 504 zcmb7=OAolIQ9X>Mok-0z+_b1zqw zRgj^9LOHUv)mmq{T2MO@BgIrg1w@0maAHx3a23dVX_>gGvdkY^T@>~&=UEDGH{X*R zs6N$;bZp~l$E=A550A<86&O=>vMHUCX&oqEV`fgcIITO-rH8|zEcJLDA0p6%8#eD` RYo6ZD>T!RytdqaH-vAtHRLB4T literal 0 HcmV?d00001 diff --git a/applications/deprecated/old_arch/osmo4_wx/Darwin.Osmo.icns b/applications/deprecated/old_arch/osmo4_wx/Darwin.Osmo.icns new file mode 100644 index 0000000000000000000000000000000000000000..a598061aa6d15577ad67469469bec642644b3eca GIT binary patch literal 38570 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 literal 0 HcmV?d00001 diff --git a/applications/deprecated/old_arch/osmo4_wx/Makefile b/applications/deprecated/old_arch/osmo4_wx/Makefile new file mode 100644 index 0000000..fcfbea8 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/Makefile @@ -0,0 +1,85 @@ +include ../../config.mak + +vpath %.cpp $(SRC_PATH)/applications/osmo4_wx + +CFLAGS= $(CXXFLAGS) -I"$(SRC_PATH)/include" + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +ifeq ($(GPACREADONLY),yes) +CFLAGS+=-DGPAC_READ_ONLY +endif + +#common obj +OBJS= wxOsmo4.o wxGPACControl.o fileprops.o Playlist.o menubtn.o + +ifeq ($(CONFIG_WIN32),yes) +EXE=.exe +PROG=Osmo4$(EXE) +else +EXT= +PROG=Osmo4 +endif + +#3 - spidermonkey support +ifeq ($(CONFIG_JS),no) +else +SCENEGRAPH_CFLAGS+=$(JS_FLAGS) +ifeq ($(CONFIG_JS),local) +NEED_LOCAL_LIB=yes +endif +LINKFLAGS+=$(JS_LIBS) +endif + +CFLAGS+=$(WX_CFLAGS) + +SRCS := $(OBJS:.o=.cpp) + +all: $(PROG) + +Osmo4$(EXE): $(OBJS) + $(CC) -o ../../bin/gcc/$@ $(OBJS) -L../../bin/gcc -lgpac $(WX_LFLAGS) $(LINKFLAGS) $(LDFLAGS) + +clean: + rm -f $(OBJS) ../../bin/gcc/$(PROG) + +install: +ifeq ($(CONFIG_DARWIN),yes) + mkdir -p $(DESTDIR)$(mac_apps)/Osmo4.app/Contents/MacOS + mkdir -p $(DESTDIR)$(mac_apps)/Osmo4.app/Contents/Resources/English.lproj + cp ./Darwin.Info.plist \ + $(DESTDIR)$(mac_apps)/Osmo4.app/Contents/Info.plist + cp ./Darwin.InfoPlist.strings \ + $(DESTDIR)$(mac_apps)/Osmo4.app/Contents/Resources/English.lproj/InfoPlist.strings + cp ./Darwin.Osmo.icns \ + $(DESTDIR)$(mac_apps)/Osmo4.app/Contents/Resources/Osmo.icns + install -m 755 $(INSTFLAGS) ../../bin/gcc/Osmo4 \ + $(DESTDIR)$(mac_apps)/Osmo4.app/Contents/MacOS + echo -n 'APPLOsm4' > $(DESTDIR)$(mac_apps)/Osmo4.app/Contents/PkgInfo +else + rm -f wxOsmo4.o + mkdir -p $(DESTDIR)$(prefix)/bin + install -m 755 $(INSTFLAGS) ../../bin/gcc/Osmo4 "$(DESTDIR)$(prefix)/bin" +endif + +uninstall: + rm -rf $(DESTDIR)$(prefix)/bin/Osmo4 + +dep: + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/applications/deprecated/old_arch/osmo4_wx/Playlist.cpp b/applications/deprecated/old_arch/osmo4_wx/Playlist.cpp new file mode 100644 index 0000000..0c9e208 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/Playlist.cpp @@ -0,0 +1,836 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / Osmo4 wxWidgets GUI + * + * GPAC is gf_free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + */ + +#include "wxOsmo4.h" +#include "Playlist.h" + + +#include "playlist.xpm" + +PLEntry::PLEntry(wxString url) +{ + m_url = gf_strdup(url.mb_str(wxConvUTF8)); + Bool is_remote = 0;; + wxCharBuffer the_url = (const char *) url.mb_str(wxConvUTF8); + const char *_url = strstr(the_url, "://"); + if (_url) { + _url += 3; + is_remote = 1; + } + else _url = (const char *) the_url; + + char *str = (char*)strrchr(_url, '\\'); + if (!str) str = (char*)strrchr(_url, '/'); + if (str && strlen(str+1)) { + m_disp_name = gf_strdup(str+1); + str = strrchr(m_disp_name, '.'); + if (str) str[0] = 0; + } else { + m_disp_name = gf_strdup(_url); + if (!is_remote) { + str = strrchr(m_disp_name, '.'); + if (str) str[0] = 0; + } + } + m_duration = 0; + m_bIsDead = 0; + m_bIsPlaying = 0; + m_bIsSelected = 0; +} + +PLEntry::~PLEntry() +{ + if (m_url) gf_free(m_url); + if (m_disp_name) gf_free(m_disp_name); + +} + +wxPlaylist::wxPlaylist(wxWindow *parent) + : wxFrame(parent, -1, wxString(_T("Osmo4 Playlist")), wxDefaultPosition, wxDefaultSize, + wxCLOSE_BOX | wxSYSTEM_MENU | wxCAPTION | wxRESIZE_BORDER) +{ + + m_pApp = (wxOsmo4Frame *)parent; + + m_pOpen = new wxBitmap(pl_open); + m_pSave = new wxBitmap(pl_save); + m_pAdd = new wxBitmap(pl_add); + m_pRem = new wxBitmap(pl_rem); + m_pUp = new wxBitmap(pl_up); + m_pDown = new wxBitmap(pl_down); + m_pSort = new wxBitmap(pl_sort); + + m_pToolBar = CreateToolBar(wxTB_HORIZONTAL); + m_pAddBut = new wxMenuButton(m_pToolBar, ID_PL_ADD_FILE, *m_pAdd); + wxMenu *menu = new wxMenu(); + menu->Append(ID_PL_ADD_URL, wxT("&Url")); + menu->Append(ID_PL_ADD_DIR, wxT("&Directory")); + menu->Append(ID_PL_ADD_DIR_REC, wxT("&Directory and subfolders")); + m_pAddBut->AssignMenu(menu); + m_pAddBut->SetToolTip(wxString(wxT("Add Files"))); + + m_pRemBut = new wxMenuButton(m_pToolBar, ID_PL_REM_FILE, *m_pRem); + menu = new wxMenu(); + menu->Append(ID_PL_REM_ALL, wxT("&Clear")); + menu->Append(ID_PL_REM_DEAD, wxT("&Remove dead entries")); + m_pRemBut->AssignMenu(menu); + m_pRemBut->SetToolTip(wxString(wxT("Remove Selected Files"))); + + m_pSortBut = new wxMenuButton(m_pToolBar, ID_PL_SORT_FILE, *m_pSort); + menu = new wxMenu(); + menu->Append(ID_PL_SORT_TITLE, wxT("&Sort by Title")); + menu->Append(ID_PL_SORT_FILE, wxT("&Sort by file name")); + menu->Append(ID_PL_SORT_DUR, wxT("&Sort by Duration")); + menu->AppendSeparator(); + menu->Append(ID_PL_REVERSE, wxT("&Reverse")); + menu->Append(ID_PL_RANDOMIZE, wxT("&Randomize")); + m_pSortBut->AssignMenu(menu); + m_pSortBut->SetToolTip(wxString(wxT("Sort Playlist by filename"))); + + m_pToolBar->AddTool(ID_PL_OPEN, wxT(""), *m_pOpen, wxT("Open Playlist")); + m_pToolBar->AddTool(ID_PL_SAVE, wxT(""), *m_pSave, wxT("Save Playlist")); + m_pToolBar->AddSeparator(); + m_pToolBar->AddControl(m_pAddBut); + m_pToolBar->AddControl(m_pRemBut); + m_pToolBar->AddSeparator(); + m_pToolBar->AddTool(ID_PL_UP, wxT(""), *m_pUp, wxT("Moves Selected Files Up")); + m_pToolBar->AddTool(ID_PL_DOWN, wxT(""), *m_pDown, wxT("Moves Selected Files Down")); + m_pToolBar->AddSeparator(); + m_pToolBar->AddControl(m_pSortBut); + m_pToolBar->Realize(); + + m_FileList = new wxListCtrl(this, ID_FILE_LIST, wxDefaultPosition, wxDefaultSize, wxLC_REPORT); + + m_FileList->InsertColumn(0, wxT(""), wxLIST_FORMAT_LEFT, 1); + m_FileList->InsertColumn(1, wxT("Title"), wxLIST_FORMAT_LEFT, 1); + m_FileList->InsertColumn(2, wxT("Duration"), wxLIST_FORMAT_LEFT, 1); + + m_entries = gf_list_new(); + m_cur_entry = -1; + m_all_dead_entries = -1; + + SetSize(220, 380); + Centre(); +} + +wxPlaylist::~wxPlaylist() +{ + Clear(); + gf_list_del(m_entries); + delete m_pAddBut; + delete m_pRemBut; + delete m_pSortBut; + delete m_pOpen; + delete m_pSave; + delete m_pAdd; + delete m_pRem; + delete m_pUp; + delete m_pDown; + delete m_pSort; +} + + +BEGIN_EVENT_TABLE(wxPlaylist, wxWindow) + EVT_CLOSE(wxPlaylist::OnClose) + EVT_SIZE(wxPlaylist::OnSize) + EVT_TOOL(ID_PL_ADD_FILE, wxPlaylist::OnAddFile) + EVT_TOOL(ID_PL_ADD_URL, wxPlaylist::OnAddURL) + EVT_TOOL(ID_PL_ADD_DIR, wxPlaylist::OnAddDir) + EVT_TOOL(ID_PL_ADD_DIR_REC, wxPlaylist::OnAddDirRec) + EVT_TOOL(ID_PL_REM_FILE, wxPlaylist::OnRemFile) + EVT_TOOL(ID_PL_REM_ALL, wxPlaylist::OnRemAll) + EVT_TOOL(ID_PL_REM_DEAD, wxPlaylist::OnRemDead) + EVT_TOOL(ID_PL_UP, wxPlaylist::OnSelUp) + EVT_TOOL(ID_PL_DOWN, wxPlaylist::OnSelDown) + EVT_TOOL(ID_PL_SAVE, wxPlaylist::OnSave) + EVT_TOOL(ID_PL_OPEN, wxPlaylist::OnOpen) + EVT_MENU(ID_PL_PLAY, wxPlaylist::OnPlay) + EVT_MENU(ID_PL_RANDOMIZE, wxPlaylist::OnRandomize) + EVT_MENU(ID_PL_REVERSE, wxPlaylist::OnReverseList) + EVT_MENU(ID_PL_SEL_REV, wxPlaylist::OnReverseSelection) + EVT_MENU(ID_PL_SORT_TITLE, wxPlaylist::OnSortTitle) + EVT_MENU(ID_PL_SORT_FILE, wxPlaylist::OnSortFile) + EVT_MENU(ID_PL_SORT_DUR, wxPlaylist::OnSortDuration) + EVT_LIST_ITEM_ACTIVATED(ID_FILE_LIST, wxPlaylist::OnItemActivate) + EVT_LIST_ITEM_RIGHT_CLICK(ID_FILE_LIST, wxPlaylist::OnRightClick) +END_EVENT_TABLE() + +void wxPlaylist::OnClose(wxCloseEvent &event) +{ + if (event.CanVeto()) { + event.Veto(); + Hide(); + } +} + +void wxPlaylist::OnSize(wxSizeEvent &event) +{ + wxSize s = event.GetSize(); + m_FileList->SetSize(0, 0, s.GetWidth()-2, s.GetHeight()); + m_FileList->SetColumnWidth(0, 30); + m_FileList->SetColumnWidth(2, 60); + m_FileList->SetColumnWidth(1, s.GetWidth()-96); +} + +void wxPlaylist::Clear() +{ + m_FileList->DeleteAllItems(); + while (gf_list_count(m_entries)) { + PLEntry *ple = (PLEntry *) gf_list_get(m_entries, 0); + gf_list_rem(m_entries, 0); + delete ple; + } + m_cur_entry = -1; +} + +void wxPlaylist::ClearButPlaying() +{ + PLEntry *p = NULL; + if (m_cur_entry >= 0) { + p = (PLEntry *) gf_list_get(m_entries, m_cur_entry); + gf_list_rem(m_entries, m_cur_entry); + } + Clear(); + if (p) { + gf_list_add(m_entries, p); + m_cur_entry = 0; + } + RefreshList(); +} + +void wxPlaylist::UpdateEntry(u32 idx) +{ + char szText[20]; + + PLEntry *ple = (PLEntry *) gf_list_get(m_entries, idx); + if (idx+1<10) sprintf(szText, "00%d", idx+1); + else if (idx+1<100) sprintf(szText, "0%d", idx+1); + else sprintf(szText, "%d", idx+1); + m_FileList->SetItem(idx, 0, wxString(szText, wxConvUTF8)); + + wxString str; + if (ple->m_bIsDead) str = wxT("!! ") + wxString(ple->m_disp_name, wxConvUTF8) + wxT(" (DEAD)!!)"); + else if (ple->m_bIsPlaying) str = wxT(">> ") + wxString(ple->m_disp_name, wxConvUTF8) + wxT(" >>"); + else str = wxString(ple->m_disp_name, wxConvUTF8); + m_FileList->SetItem(idx, 1, str); + + if (ple->m_duration) { + u32 h = (u32) (ple->m_duration / 3600); + u32 m = (u32) (ple->m_duration / 60) - h*60; + u32 s = (u32) (ple->m_duration) - h*3600 - m*60; + m_FileList->SetItem(idx, 2, wxString::Format(wxT("%02d:%02d:%02d"), h, m, s) ); + } else { + m_FileList->SetItem(idx, 2, wxT("Unknown")); + } +} + +void wxPlaylist::RefreshList() +{ + u32 i, top_idx; + char szPath[GF_MAX_PATH]; + Bool first_sel; + + top_idx = m_FileList->GetTopItem(); + m_FileList->DeleteAllItems(); + + first_sel = 0; + for (i=0; iInsertItem(i, wxT("")); + /*cast for 64-bit compilation*/ + m_FileList->SetItemData(i, (unsigned long) ple); + UpdateEntry(i); + + if (ple->m_bIsPlaying) m_cur_entry = i; + + if (ple->m_bIsSelected) { + m_FileList->SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED | wxLIST_MASK_STATE); + ple->m_bIsSelected = 0; + /*ensure first item of selection is visible*/ + if (!first_sel) { + first_sel = 1; + top_idx = i; + } + } + } + + if (m_cur_entry >= (s32) gf_list_count(m_entries)-1) m_cur_entry = gf_list_count(m_entries)-1; + else { + s32 last_idx = top_idx + m_FileList->GetCountPerPage(); + m_FileList->EnsureVisible(top_idx); + if (gf_list_count(m_entries)<1+ (u32) last_idx) last_idx = gf_list_count(m_entries)-1; + m_FileList->EnsureVisible(last_idx); + } + + strcpy((char *) szPath, m_pApp->szAppPath); +#ifdef WIN32 + strcat(szPath, "gpac_pl.m3u"); +#else + strcat(szPath, ".gpac_pl.m3u"); +#endif + Save(szPath, 1); +} + +void wxPlaylist::OnAddFile(wxCommandEvent &WXUNUSED(event)) +{ + wxFileDialog dlg(this, wxT("Select file(s)"), wxT(""), wxT(""), m_pApp->GetFileFilter(), wxOPEN | wxCHANGE_DIR | /*wxHIDE_READONLY |*/ wxMULTIPLE); + + if (dlg.ShowModal() == wxID_OK) { + wxArrayString stra; + dlg.GetPaths(stra); + for (u32 i=0; im_pApp->m_term, item_name, 0, 1)) { + PLEntry *ple = new PLEntry(wxString(item_path, wxConvUTF8) ); + gf_list_add(_this->m_entries, ple); + } + return 0; +} + +static Bool pl_enum_dir_dirs(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) +{ + gf_enum_directory(item_path, 0, pl_enum_dir_item, cbck, NULL); + gf_enum_directory(item_path, 1, pl_enum_dir_dirs, cbck, NULL); + return 0; +} + + +void wxPlaylist::AddDir(Bool do_recurse) +{ + wxDirDialog dlg(this); + dlg.SetPath(wxString(szCacheDir, wxConvUTF8) ); + if (dlg.ShowModal() != wxID_OK) return; + + strcpy(szCacheDir, dlg.GetPath().mb_str(wxConvUTF8)); + gf_enum_directory(szCacheDir, 0, pl_enum_dir_item, this, NULL); + if (do_recurse) gf_enum_directory(szCacheDir, 1, pl_enum_dir_dirs, this, NULL); + m_all_dead_entries = -1; + RefreshList(); +} +void wxPlaylist::OnAddDir(wxCommandEvent &WXUNUSED(event)) +{ + AddDir(0); +} +void wxPlaylist::OnAddDirRec(wxCommandEvent &WXUNUSED(event)) +{ + AddDir(1); +} + +void wxPlaylist::OnAddURL(wxCommandEvent &WXUNUSED(event)) +{ + OpenURLDlg dlg(this, m_pApp->m_user.config); + if (dlg.ShowModal() != wxID_OK) return; + PLEntry *ple = new PLEntry(dlg.m_urlVal); + gf_list_add(m_entries, ple); + m_all_dead_entries = -1; + RefreshList(); +} + +void wxPlaylist::OnRemFile(wxCommandEvent &WXUNUSED(event)) +{ + if (!m_FileList->GetSelectedItemCount()) return; + + long item = -1; + for (;;) { + item = m_FileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (item == -1) break; + PLEntry *ple = (PLEntry *) m_FileList->GetItemData(item); + gf_list_del_item(m_entries, ple); + delete ple; + } + RefreshList(); +} + +void wxPlaylist::OnRemAll(wxCommandEvent &WXUNUSED(event)) +{ + Clear(); + RefreshList(); + m_cur_entry = -1; + m_all_dead_entries = 1; +} + +void wxPlaylist::OnRemDead(wxCommandEvent &WXUNUSED(event)) +{ + for (u32 i=0; im_bIsDead) continue; + gf_list_rem(m_entries, i); + i--; + delete ple; + } + m_all_dead_entries = gf_list_count(m_entries) ? 0 : 1; + RefreshList(); +} + + +void wxPlaylist::OnSelUp(wxCommandEvent &WXUNUSED(event)) +{ + s32 i; + if (!m_FileList->GetSelectedItemCount()) return; + long item = -1; + item = m_FileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (item <= 0) return; + + item = -1; + for (;;) { + item = m_FileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (item == -1) break; + PLEntry *ple = (PLEntry *) m_FileList->GetItemData(item); + i = gf_list_del_item(m_entries, ple); + assert(i>=1); + gf_list_insert(m_entries, ple, i-1); + ple->m_bIsSelected = 1; + } + RefreshList(); +} + +void wxPlaylist::OnSelDown(wxCommandEvent &WXUNUSED(event)) +{ + s32 i; + + if (!m_FileList->GetSelectedItemCount()) return; + long item = -1; + for (;;) { + item = m_FileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (item == -1) break; + } + if ((u32) item + 1 == gf_list_count(m_entries)) return; + + item = -1; + for (;;) { + item = m_FileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (item == -1) break; + PLEntry *ple = (PLEntry *) m_FileList->GetItemData(item); + i = gf_list_del_item(m_entries, ple); + assert(i>=1); + gf_list_insert(m_entries, ple, i+1); + ple->m_bIsSelected = 1; + } + RefreshList(); +} + + + +void wxPlaylist::OnSave(wxCommandEvent & WXUNUSED(event)) +{ + Bool save_m3u; + char szPath[GF_MAX_PATH]; + if (!gf_list_count(m_entries)) return; + + wxFileDialog dlg(this, wxT("Select file(s)"), wxT(""), wxT(""), wxT("M3U Playlists|*.m3u|ShoutCast Playlists|*.pls|"), wxSAVE | wxCHANGE_DIR | wxOVERWRITE_PROMPT); + if (dlg.ShowModal() != wxID_OK) return; + + strcpy(szPath, dlg.GetPath().mb_str(wxConvUTF8)); + strlwr(szPath); + save_m3u = (dlg.GetFilterIndex()==0) ? 1 : 0; + if (save_m3u) { + if (!strstr(szPath, ".m3u")) { + strcpy(szPath, dlg.GetPath().mb_str(wxConvUTF8)); + strcat(szPath, ".m3u"); + } else { + strcpy(szPath, dlg.GetPath().mb_str(wxConvUTF8)); + } + } else { + if (!strstr(szPath, ".pls")) { + strcpy(szPath, dlg.GetPath().mb_str(wxConvUTF8)); + strcat(szPath, ".pls"); + } else { + strcpy(szPath, dlg.GetPath().mb_str(wxConvUTF8)); + } + } + Save(szPath, save_m3u); +} + +void wxPlaylist::Save(char *szPath, Bool save_m3u) +{ + FILE *out = gf_fopen(szPath, "wt"); + if (!save_m3u) + fprintf(out, "[playlist]\nNumberOfEntries=%d\n", gf_list_count(m_entries)); + + for (u32 i=0; im_url); + } else { + fprintf(out, "File%d=%s\n", i+1, ple->m_url); + fprintf(out, "Title%d=%s\n", i+1, ple->m_disp_name); + if (ple->m_duration) fprintf(out, "Length%d=%d\n", i+1, ple->m_duration); + else fprintf(out, "Length%d=-1\n", i+1); + } + } + if (!save_m3u) fprintf(out, "Version=2\n"); + + fprintf(out, "\n"); + gf_fclose(out); +} + +void wxPlaylist::OnOpen(wxCommandEvent & WXUNUSED(event)) +{ + wxFileDialog dlg(this, wxT("Select file(s)"), wxT(""), wxT(""), wxT("M3U & PLS Playlists|*.m3u;*.pls|M3U Playlists|*.m3u|ShoutCast Playlists|*.pls|"), wxOPEN | wxCHANGE_DIR/* | wxHIDE_READONLY*/); + if (dlg.ShowModal() != wxID_OK) return; + + Clear(); + OpenPlaylist(dlg.GetPath()); + m_cur_entry = 0; + Play(); +} + +void wxPlaylist::OpenPlaylist(wxString filename) +{ + FILE *pl; + PLEntry *ple; + Bool load_m3u, go; + char szLine[GF_MAX_PATH]; + pl = gf_fopen(filename.mb_str(wxConvUTF8) , "rt"); + if (!pl) return; + + ple = NULL; + load_m3u = 1; + while (!feof(pl)) { + fgets(szLine, GF_MAX_PATH, pl); + go = 1; + while (go) { + switch (szLine[strlen(szLine)-1]) { + case '\n': + case '\r': + case ' ': + szLine[strlen(szLine)-1] = 0; + break; + default: + go = 0; + break; + } + } + if (!strlen(szLine)) continue; + if (!stricmp(szLine, "[playlist]")) { + load_m3u = 0; + } else if (load_m3u) { + ple = new PLEntry(wxString(szLine, wxConvUTF8) ); + gf_list_add(m_entries, ple); + } else if (!strnicmp(szLine, "file", 4)) { + char *st = strchr(szLine, '='); + if (!st) ple = NULL; + else { + ple = new PLEntry(wxString(st + 1, wxConvUTF8) ); + gf_list_add(m_entries, ple); + } + } else if (ple && !strnicmp(szLine, "Length", 6)) { + char *st = strchr(szLine, '='); + s32 d = atoi(st + 1); + if (d>0) ple->m_duration = d; + } else if (ple && !strnicmp(szLine, "Title", 5)) { + char *st = strchr(szLine, '='); + gf_free(ple->m_disp_name); + ple->m_disp_name = gf_strdup(st + 6); + } + } + gf_fclose(pl); + m_all_dead_entries = -1; + m_cur_entry = -1; + RefreshList(); +} + +void wxPlaylist::OnRightClick(wxListEvent & event) +{ + if (!m_FileList->GetItemCount()) return; + + wxMenu *popup = new wxMenu(); + + if (m_FileList->GetSelectedItemCount()==1) { + popup->Append(ID_PL_PLAY, wxT("Play")); + popup->AppendSeparator(); + } + popup->Append(ID_PL_SEL_REV, wxT("Inverse Selection")); + if (m_FileList->GetSelectedItemCount()) popup->Append(ID_PL_REM_FILE, wxT("Remove File(s)")); + if (m_FileList->GetItemCount()>1) { + popup->AppendSeparator(); + popup->Append(ID_PL_SORT_TITLE, wxT("Sort By Title")); + popup->Append(ID_PL_SORT_FILE, wxT("Sort By File Name")); + popup->Append(ID_PL_SORT_DUR, wxT("Sort By Duration")); + popup->AppendSeparator(); + popup->Append(ID_PL_REVERSE, wxT("Reverse List")); + popup->Append(ID_PL_RANDOMIZE, wxT("Randomize")); + } + + PopupMenu(popup, event.GetPoint()); + delete popup; +} + +void wxPlaylist::OnReverseSelection(wxCommandEvent &WXUNUSED(event) ) +{ + u32 i; + long item = -1; + for (;;) { + item = m_FileList->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (item == -1) break; + PLEntry *ple = (PLEntry *) m_FileList->GetItemData(item); + ple->m_bIsSelected = 1; + } + for (i=0; im_bIsSelected = !ple->m_bIsSelected; + } + RefreshList(); +} + +void wxPlaylist::OnReverseList(wxCommandEvent &WXUNUSED(event) ) +{ + u32 count = gf_list_count(m_entries); + u32 hcount = count / 2; + count--; + for (u32 i=0; i1) { + u32 pos = gf_rand() % (gf_list_count(m_entries)-1); + PLEntry *ple = (PLEntry *)gf_list_get(m_entries, pos); + gf_list_rem(m_entries, pos); + gf_list_add(new_entries, ple); + } + PLEntry *ple = (PLEntry *)gf_list_get(m_entries, 0); + gf_list_rem(m_entries, 0); + gf_list_add(new_entries, ple); + + gf_list_del(m_entries); + m_entries = new_entries; + m_cur_entry = -1; + RefreshList(); +} + +void wxPlaylist::Sort(u32 type) +{ + u32 i, j, smallest; + + for (i=0; im_url, ple2->m_url); + break; + case 1: + test = stricmp(ple1->m_disp_name, ple2->m_disp_name); + break; + case 2: + test = ple1->m_duration - ple2->m_duration; + break; + } + if (test<0) smallest = j; + } + PLEntry *ple = (PLEntry *)gf_list_get(m_entries, smallest); + gf_list_rem(m_entries, smallest); + gf_list_insert(m_entries, ple, i); + } + m_cur_entry = -1; + RefreshList(); +} + +void wxPlaylist::OnSortFile(wxCommandEvent &WXUNUSED(event) ) { + Sort(0); +} +void wxPlaylist::OnSortTitle(wxCommandEvent &WXUNUSED(event) ) { + Sort(1); +} +void wxPlaylist::OnSortDuration(wxCommandEvent &WXUNUSED(event) ) { + Sort(2); +} + +void wxPlaylist::RefreshCurrent() +{ + PLEntry *ple; + if (m_cur_entry<0) return; + ple = (PLEntry *) gf_list_get(m_entries, m_cur_entry); + if (ple && ple->m_bIsPlaying) { + ple->m_bIsPlaying = 0; + UpdateEntry(m_cur_entry); + } +} + +Bool wxPlaylist::HasValidEntries() +{ + u32 nb_dead = 0; + if (m_all_dead_entries==-1) { + for (u32 i=0; im_bIsPlaying = 0; + if (ple->m_bIsDead) nb_dead ++; + } + m_all_dead_entries = (nb_dead==gf_list_count(m_entries)) ? 1 : 0; + } + return !m_all_dead_entries; +} + +void wxPlaylist::Play() +{ + PLEntry *ple; + + if (!HasValidEntries()) return; + + RefreshCurrent(); + + if (m_cur_entry >= (s32)gf_list_count(m_entries)) { + if (!m_pApp->m_loop) return; + m_cur_entry = 0; + } + + ple = (PLEntry *) gf_list_get(m_entries, m_cur_entry); + if (!ple || ple->m_bIsDead) { + m_cur_entry++; + Play(); + } else { + char szPLE[20]; + ple->m_bIsPlaying = 1; + UpdateEntry(m_cur_entry); + sprintf(szPLE, "%d", m_cur_entry); + gf_cfg_set_key(m_pApp->m_user.config, "General", "PLEntry", szPLE); + m_pApp->DoConnect(); + } +} + +void wxPlaylist::OnItemActivate(wxListEvent &WXUNUSED(event) ) +{ + long item = m_FileList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (item==-1) return; + RefreshCurrent(); + m_cur_entry = item; + Play(); +} + + +void wxPlaylist::OnPlay(wxCommandEvent &WXUNUSED(event)) +{ + long item = m_FileList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (item==-1) return; + + RefreshCurrent(); + m_cur_entry = item; + Play(); +} + +void wxPlaylist::Truncate() +{ + if (m_cur_entry<0) return; + while ((u32) m_cur_entry+1 < gf_list_count(m_entries)) { + PLEntry *ple = (PLEntry *) gf_list_get(m_entries, m_cur_entry+1); + gf_list_rem(m_entries, m_cur_entry+1); + delete ple; + } + RefreshList(); +} + +void wxPlaylist::QueueURL(wxString filename) +{ + char *ext = (char*)strrchr(filename.mb_str(wxConvUTF8), '.'); + if (ext && (!stricmp(ext, ".m3u") || !stricmp(ext, ".pls")) ) { + OpenPlaylist(filename); + } else { + PLEntry *ple = new PLEntry(filename); + gf_list_add(m_entries, ple); + } +} + +void wxPlaylist::PlayNext() +{ + RefreshCurrent(); + if (1+m_cur_entry < (s32)gf_list_count(m_entries)) { + m_cur_entry++; + Play(); + } +} + +void wxPlaylist::PlayPrev() +{ + RefreshCurrent(); + if (m_cur_entry>0) { + m_cur_entry--; + Play(); + } +} + +void wxPlaylist::SetDead() +{ + PLEntry *ple = (PLEntry *) gf_list_get(m_entries, m_cur_entry); + if (ple) { + ple->m_bIsDead = 1; + UpdateEntry(m_cur_entry); + if (ple->m_bIsPlaying) PlayNext(); + m_all_dead_entries = -1; + } +} +void wxPlaylist::SetDuration(u32 duration) +{ + PLEntry *ple = (PLEntry *) gf_list_get(m_entries, m_cur_entry); + if (ple) { + ple->m_duration = duration; + UpdateEntry(m_cur_entry); + } +} + +wxString wxPlaylist::GetDisplayName() +{ + if (m_cur_entry>=0) { + PLEntry *ple = (PLEntry *) gf_list_get(m_entries, m_cur_entry); + if (ple) return wxString(wxString(ple->m_disp_name, wxConvUTF8) ); + } + return wxT(""); +} + +wxString wxPlaylist::GetURL() +{ + PLEntry *ple = (PLEntry *) gf_list_get(m_entries, m_cur_entry); + if (ple) return wxString(ple->m_url, wxConvUTF8); + return wxT(""); +} + diff --git a/applications/deprecated/old_arch/osmo4_wx/Playlist.h b/applications/deprecated/old_arch/osmo4_wx/Playlist.h new file mode 100644 index 0000000..444aefb --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/Playlist.h @@ -0,0 +1,135 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / Osmo4 wxWidgets GUI + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + */ + +#ifndef _PLAYLIST_H +#define _PLAYLIST_H + +#include "wx/wxprec.h" + +#ifndef WX_PRECOMP +#include "wx/wx.h" +#endif + +#include "menubtn.h" + +enum +{ + ID_FILE_LIST = 1000, +}; + +class wxOsmo4Frame; + +class PLEntry +{ +public: + PLEntry(wxString url); + ~PLEntry(); + + char *m_url; + char *m_disp_name; + u32 m_duration; + + Bool m_bIsSelected; + Bool m_bIsDead; + Bool m_bIsPlaying; +}; + + +class wxPlaylist : public wxFrame +{ +public: + wxPlaylist(wxWindow *parent); + virtual ~wxPlaylist(); + + void Clear(); + void ClearButPlaying(); + void RefreshList(); + + void Truncate(); + void QueueURL(wxString filename); + void Play(); + void PlayNext(); + void PlayPrev(); + void SetDead(); + void SetDuration(u32 duration); + Bool HasValidEntries(); + void OpenPlaylist(wxString fileName); + + /*for current entry played*/ + wxString GetDisplayName(); + wxString GetURL(); + + s32 m_cur_entry; + GF_List *m_entries; + + wxOsmo4Frame *m_pApp; + +private: + DECLARE_EVENT_TABLE() + + void OnClose(wxCloseEvent &event); + void OnSize(wxSizeEvent &event); + void OnAddFile(wxCommandEvent &event); + void OnAddURL(wxCommandEvent &event); + void OnAddDir(wxCommandEvent &event); + void OnAddDirRec(wxCommandEvent &event); + void OnRemFile(wxCommandEvent &event); + void OnRemAll(wxCommandEvent &event); + void OnRemDead(wxCommandEvent &event); + void OnSelUp(wxCommandEvent &event); + void OnSelDown(wxCommandEvent &event); + void OnSave(wxCommandEvent &event); + void OnOpen(wxCommandEvent &event); + void OnRightClick(wxListEvent & event); + void OnReverseSelection(wxCommandEvent &event); + void OnReverseList(wxCommandEvent &event); + void OnRandomize(wxCommandEvent &event); + void OnSortFile(wxCommandEvent &event); + void OnSortTitle(wxCommandEvent &event); + void OnSortDuration(wxCommandEvent &event); + void OnItemActivate(wxListEvent &event); + void OnPlay(wxCommandEvent &event); + + + void Sort(u32 type); + void UpdateEntry(u32 idx); + void RefreshCurrent(); + void Save(char *szPath, Bool save_m3u); + + wxBitmap *m_pOpen, *m_pSave, *m_pAdd, *m_pRem, *m_pUp, *m_pDown, *m_pSort; + wxMenuButton *m_pAddBut, *m_pRemBut, *m_pSortBut; + wxToolBar *m_pToolBar; + wxListCtrl *m_FileList; + char szCacheDir[GF_MAX_PATH]; + s32 m_all_dead_entries; + + void AddDir(Bool do_recurse); +}; + + + +#endif + diff --git a/applications/deprecated/old_arch/osmo4_wx/fileprops.cpp b/applications/deprecated/old_arch/osmo4_wx/fileprops.cpp new file mode 100644 index 0000000..c59be08 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/fileprops.cpp @@ -0,0 +1,650 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / Osmo4 wxWidgets GUI + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + */ + +#include "fileprops.h" +#include "wxOsmo4.h" +#include "Playlist.h" +#include +#include +#include +#include +/*ISO 639 languages*/ +#include + + +wxFileProps::wxFileProps(wxWindow *parent) + : wxDialog(parent, -1, wxString(_T("File Properties"))) +{ + + m_pApp = (wxOsmo4Frame *)parent; + SetSize(540, 260); + assert(m_pApp->m_pPlayList); + + m_pTreeView = new wxTreeCtrl(this, ID_TREE_VIEW, wxPoint(4, 2), wxSize(200, 180), wxTR_DEFAULT_STYLE | wxSUNKEN_BORDER); + + new wxStaticText(this, 0, _T("Information"), wxPoint(210, 2), wxSize(60, 20)); + m_pViewSel = new wxComboBox(this, ID_VIEW_SEL, _T(""), wxPoint(280, 2), wxSize(120, 24), 0, NULL, wxCB_READONLY); + m_pViewSel->Append(wxT("General")); + m_pViewSel->Append(wxT("Streams")); + m_pViewSel->Append(wxT("Playback")); + m_pViewSel->Append(wxT("Network")); + m_pViewSel->SetSelection(0); + + m_pViewInfo = new wxTextCtrl(this, -1, wxT(""), wxPoint(210, 30), wxSize(320, 200), wxTE_MULTILINE | wxTE_READONLY | wxHSCROLL | wxSUNKEN_BORDER); + +#ifdef WIN32 + m_pViewInfo->SetBackgroundColour(wxColour(wxT("LIGHT GREY"))); +#endif + + m_pViewWI = new wxButton(this, ID_VIEW_WI, wxT("View World Info"), wxPoint(4, 174), wxSize(200, 40)); + m_pViewSG = new wxButton(this, ID_VIEW_SG, wxT("View Scene Graph"), wxPoint(4, 220), wxSize(200, 40)); + + + wxString str = m_pApp->m_pPlayList->GetDisplayName(); + str += wxT(" Properties"); + SetTitle(str); + + m_pTimer = new wxTimer(); + m_pTimer->SetOwner(this, ID_OD_TIMER); + m_pTimer->Start(500, 0); + RewriteODTree(); + +} + +wxFileProps::~wxFileProps() +{ + m_pTimer->Stop(); + delete m_pTimer; +} + + +BEGIN_EVENT_TABLE(wxFileProps, wxDialog) + EVT_TREE_ITEM_ACTIVATED(ID_TREE_VIEW, wxFileProps::OnSetSelection) + EVT_TREE_SEL_CHANGED(ID_TREE_VIEW, wxFileProps::OnSetSelection) + EVT_TREE_ITEM_EXPANDED(ID_TREE_VIEW, wxFileProps::OnSetSelection) + EVT_TREE_ITEM_COLLAPSED(ID_TREE_VIEW, wxFileProps::OnSetSelection) + EVT_TIMER(ID_OD_TIMER, wxFileProps::OnTimer) + EVT_BUTTON(ID_VIEW_SG, wxFileProps::OnViewSG) + EVT_BUTTON(ID_VIEW_WI, wxFileProps::OnViewWorld) + EVT_COMBOBOX(ID_VIEW_SEL, wxFileProps::OnSelectInfo) +END_EVENT_TABLE() + +void wxFileProps::RewriteODTree() +{ + GF_ObjectManager *root_odm = gf_term_get_root_object(m_pApp->m_term); + if (!root_odm) return; + + m_pTreeView->DeleteAllItems(); + ODTreeData *root = new ODTreeData(root_odm); + m_pTreeView->AddRoot(wxT("Root OD"), -1, -1, root); + wxTreeItemId rootId = m_pTreeView->GetRootItem(); + + WriteInlineTree(root); + SetInfo(root_odm); +} + +void wxFileProps::WriteInlineTree(ODTreeData *root) +{ + /*browse all ODs*/ + u32 count = gf_term_get_object_count(m_pApp->m_term, root->m_pODMan); + + for (u32 i=0; im_term, root->m_pODMan, i); + if (!odm) return; + ODTreeData *odd = new ODTreeData(odm); + m_pTreeView->AppendItem(root->GetId(), wxT("Object Descriptor"), -1, -1, odd); + + /*if inline propagate*/ + switch (gf_term_object_subscene_type(m_pApp->m_term, odm)) { + case 1: + m_pTreeView->SetItemText(odd->GetId(), wxT("Root Scene")); + WriteInlineTree(odd); + break; + case 2: + m_pTreeView->SetItemText(odd->GetId(), wxT("Inline Scene")); + WriteInlineTree(odd); + break; + case 3: + m_pTreeView->SetItemText(odd->GetId(), wxT("Extern Proto Lib")); + break; + default: + break; + } + } +} + +void wxFileProps::OnSetSelection(wxTreeEvent& event) +{ + ODTreeData *odd = (ODTreeData *) m_pTreeView->GetItemData(event.GetItem()); + SetInfo(odd->m_pODMan); +} + +void wxFileProps::SetInfo(GF_ObjectManager *odm) +{ + m_current_odm = odm; + + switch (m_pViewSel->GetSelection()) { + case 3: + SetNetworkInfo(); + break; + case 2: + SetDecoderInfo(); + break; + case 1: + SetStreamsInfo(); + break; + default: + SetGeneralInfo(); + break; + } +} + +void wxFileProps::OnTimer(wxTimerEvent& WXUNUSED(event)) +{ + switch (m_pViewSel->GetSelection()) { + case 2: + SetDecoderInfo(); + break; + } +} +void wxFileProps::OnSelectInfo(wxCommandEvent & WXUNUSED(event) ) +{ + SetInfo(m_current_odm); +} + +void wxFileProps::SetGeneralInfo() +{ + wxString info; + GF_MediaInfo odi; + u32 h, m, s; + u32 i, j; + + info = wxT(""); + m_pViewInfo->Clear(); + m_pViewInfo->AppendText(info); + + if (!m_current_odm || gf_term_get_object_info(m_pApp->m_term, m_current_odm, &odi) != GF_OK) return; + + if (odi.has_profiles) info += wxT("Initial "); + info += wxString::Format(wxT("Object Descriptor ID %d\n"), odi.od->objectDescriptorID); + if (odi.duration) { + h = (u32) (odi.duration / 3600); + m = (u32) (odi.duration / 60) - h*60; + s = (u32) (odi.duration) - h*3600 - m*60; + info += wxString::Format(wxT("Duration %02d:%02d:%02d\n"), h, m, s); + } else { + info += wxT("Unknown duration\n"); + } + + if (odi.owns_service) { + info += wxT("Service Handler: ") + wxString(odi.service_handler, wxConvUTF8) + wxT("\n"); + info += wxT("Service URL: ") + wxString(odi.service_url, wxConvUTF8) + wxT("\n"); + } + + if (odi.od->URLString) { + info += wxT("Remote OD - URL: ") + wxString(odi.od->URLString, wxConvUTF8) + wxT("\n"); + } + + if (odi.codec_name) { + switch (odi.od_type) { + case GF_STREAM_VISUAL: + info += wxString::Format(wxT("Video Object: Width %d - Height %d\n"), odi.width, odi.height); + info += wxT("Media Codec ") + wxString(odi.codec_name, wxConvUTF8) + wxT("\n"); + break; + case GF_STREAM_AUDIO: + info += wxString::Format(wxT("Audio Object: Sample Rate %d - %d channels\n"), odi.sample_rate, odi.num_channels); + info += wxT("Media Codec ") + wxString(odi.codec_name, wxConvUTF8) + wxT("\n"); + break; + case GF_STREAM_PRIVATE_SCENE: + case GF_STREAM_SCENE: + if (odi.width && odi.height) { + info += wxString::Format(wxT("Scene Description: Width %d - Height %d\n"), odi.width, odi.height); + } else { + info += wxT("Scene Description: No size specified\n"); + } + info += wxT("Scene Codec ") + wxString(odi.codec_name, wxConvUTF8) + wxT("\n"); + break; + case GF_STREAM_TEXT: + if (odi.width && odi.height) { + info += wxString::Format(wxT("Text Object: Width %d - Height %d\n"), odi.width, odi.height); + } else { + info += wxString::Format(wxT("Text Object: No size specified\n")); + } + info += wxT("Text Codec ") + wxString(odi.codec_name, wxConvUTF8) + wxT("\n"); + break; + } + } + if (odi.protection==2) info += wxT("Encrypted Media NOT UNLOCKED"); + else if (odi.protection==1) info += wxT("Encrypted Media"); + + if (!gf_list_count(odi.od->OCIDescriptors)) { + m_pViewInfo->Clear(); + m_pViewInfo->AppendText(info); + return; + } + + info += wxT("\nObject Content Information:\n"); + + /*check OCI (not everything interests us) - FIXME: support for unicode*/ + for (i=0; iOCIDescriptors); i++) { + GF_Descriptor *desc = (GF_Descriptor *) gf_list_get(odi.od->OCIDescriptors, i); + switch (desc->tag) { + case GF_ODF_SEGMENT_TAG: + { + GF_Segment *sd = (GF_Segment *) desc; + info += wxT("\nSegment Descriptor:\nName: ") + wxString((char *) sd->SegmentName, wxConvUTF8); + info += wxString::Format(wxT(" - start time %g sec - duration %g sec\n"), sd->startTime, sd->Duration); + } + break; + case GF_ODF_CC_NAME_TAG: + { + GF_CC_Name *ccn = (GF_CC_Name *)desc; + info += wxT("\nContent Creators:\n"); + for (j=0; jContentCreators); j++) { + GF_ContentCreatorInfo *ci = (GF_ContentCreatorInfo *) gf_list_get(ccn->ContentCreators, j); + if (!ci->isUTF8) continue; + info += wxT("\t") + wxString(ci->contentCreatorName, wxConvUTF8) + wxT("\n"); + } + } + break; + + case GF_ODF_SHORT_TEXT_TAG: + { + GF_ShortTextual *std = (GF_ShortTextual *)desc; + info += wxT("\n") + wxString(std->eventName, wxConvUTF8) + wxT(": ") + wxString(std->eventText, wxConvUTF8) + wxT("\n"); + } + break; + /*todo*/ + case GF_ODF_CC_DATE_TAG: + break; + default: + break; + } + + } + + m_pViewInfo->Clear(); + m_pViewInfo->AppendText(info); +} + +void wxFileProps::SetStreamsInfo() +{ + u32 i, count; + wxString info; + GF_MediaInfo odi; + char code[5]; + + info = wxT(""); + m_pViewInfo->Clear(); + m_pViewInfo->AppendText(info); + + if (!m_current_odm || gf_term_get_object_info(m_pApp->m_term, m_current_odm, &odi) != GF_OK) return; + + if (odi.has_profiles) { + info += wxString::Format(wxT("\tOD Profile@Level %d\n"), odi.OD_pl); + info += wxString::Format(wxT("\tScene Profile@Level %d\n"), odi.scene_pl); + info += wxString::Format(wxT("\tGraphics Profile@Level %d\n"), odi.graphics_pl); + info += wxString::Format(wxT("\tAudio Profile@Level %d\n"), odi.audio_pl); + info += wxString::Format(wxT("\tVisual Profile@Level %d\n"), odi.scene_pl); + if (odi.inline_pl) info += wxT("\tInline Content use same profiles\n"); + info += wxT("\n"); + } + + count = gf_list_count(odi.od->ESDescriptors); + + for (i=0; iESDescriptors, i); + + info += wxString::Format(wxT("Stream ID %d - Clock ID %d\n"), esd->ESID, esd->OCRESID); + if (esd->dependsOnESID) { + info += wxString::Format(wxT("\tDepends on Stream ID %d for decoding\n"), esd->dependsOnESID); + } + switch (esd->decoderConfig->streamType) { + case GF_STREAM_OD: + info += wxString::Format(wxT("\tOD Stream - version %d\n"), esd->decoderConfig->objectTypeIndication); + break; + case GF_STREAM_OCR: + info += wxT("\tObject Clock Reference Stream\n"); + break; + case GF_STREAM_SCENE: + info += wxString::Format(wxT("\tScene Description Stream - version %d\n"), esd->decoderConfig->objectTypeIndication); + break; + case GF_STREAM_PRIVATE_SCENE: + info += wxString::Format(wxT("\tGPAC Private Scene Description Stream\n")); + break; + case GF_STREAM_VISUAL: + info += wxT("\tVisual Stream - media type: "); + switch (esd->decoderConfig->objectTypeIndication) { + case GPAC_OTI_VIDEO_MPEG4_PART2: + info += wxT("MPEG-4\n"); + break; + case GPAC_OTI_VIDEO_MPEG2_SIMPLE: + info += wxT("MPEG-2 Simple Profile\n"); + break; + case GPAC_OTI_VIDEO_MPEG2_MAIN: + info += wxT("MPEG-2 Main Profile\n"); + break; + case GPAC_OTI_VIDEO_MPEG2_SNR: + info += wxT("MPEG-2 SNR Profile\n"); + break; + case GPAC_OTI_VIDEO_MPEG2_SPATIAL: + info += wxT("MPEG-2 Spatial Profile\n"); + break; + case GPAC_OTI_VIDEO_MPEG2_HIGH: + info += wxT("MPEG-2 High Profile\n"); + break; + case GPAC_OTI_VIDEO_MPEG2_422: + info += wxT("MPEG-2 422 Profile\n"); + break; + case GPAC_OTI_VIDEO_MPEG1: + info += wxT("MPEG-1\n"); + break; + case GPAC_OTI_IMAGE_JPEG: + info += wxT("JPEG\n"); + break; + case GPAC_OTI_IMAGE_PNG: + info += wxT("PNG\n"); + break; + case GPAC_OTI_IMAGE_JPEG_2000: + info += wxT("JPEG2000\n"); + break; + case 0x80: + memcpy(code, esd->decoderConfig->decoderSpecificInfo->data, 4); + code[4] = 0; + info += wxT("GPAC Intern (") + wxString(code, wxConvUTF8) + wxT(")\n"); + break; + default: + info += wxString::Format(wxT("Private/Unknown Type (0x%x)\n"), esd->decoderConfig->objectTypeIndication); + break; + } + break; + + case GF_STREAM_AUDIO: + info += wxT("\tAudio Stream - media type: "); + switch (esd->decoderConfig->objectTypeIndication) { + case GPAC_OTI_AUDIO_AAC_MPEG4: + info += wxT("MPEG-4\n"); + break; + case GPAC_OTI_AUDIO_AAC_MPEG2_MP: + info += wxT("MPEG-2 AAC Main Profile\n"); + break; + case GPAC_OTI_AUDIO_AAC_MPEG2_LCP: + info += wxT("MPEG-2 AAC LowComplexity Profile\n"); + break; + case GPAC_OTI_AUDIO_AAC_MPEG2_SSRP: + info += wxT("MPEG-2 AAC Scalable Sampling Rate Profile\n"); + break; + case GPAC_OTI_AUDIO_MPEG2_PART3: + info += wxT("MPEG-2 Audio\n"); + break; + case GPAC_OTI_AUDIO_MPEG1: + info += wxT("MPEG-1 Audio\n"); + break; + case 0xA0: + info += wxT("EVRC Audio\n"); + break; + case 0xA1: + info += wxT("SMV Audio\n"); + break; + case 0xE1: + info += wxT("QCELP Audio\n"); + break; + case 0x80: + memcpy(code, esd->decoderConfig->decoderSpecificInfo->data, 4); + code[4] = 0; + info += wxT("GPAC Intern (") + wxString(code, wxConvUTF8) + wxT(")\n"); + break; + default: + info += wxString::Format(wxT("Private/Unknown Type (0x%x)\n"), esd->decoderConfig->objectTypeIndication); + break; + } + break; + case GF_STREAM_MPEG7: + info += wxString::Format(wxT("\tMPEG-7 Stream - version %d\n"), esd->decoderConfig->objectTypeIndication); + break; + case GF_STREAM_IPMP: + info += wxString::Format(wxT("\tIPMP Stream - version %d\n"), esd->decoderConfig->objectTypeIndication); + break; + case GF_STREAM_OCI: + info += wxString::Format(wxT("\tOCI Stream - version %d\n"), esd->decoderConfig->objectTypeIndication); + break; + case GF_STREAM_MPEGJ: + info += wxString::Format(wxT("\tMPEGJ Stream - version %d\n"), esd->decoderConfig->objectTypeIndication); + break; + case GF_STREAM_INTERACT: + info += wxString::Format(wxT("\tUser Interaction Stream - version %d\n"), esd->decoderConfig->objectTypeIndication); + break; + default: + info += wxT("Private/Unknown\n"); + break; + } + + info += wxString::Format(wxT("\tBuffer Size %d\n\tAverage Bitrate %d bps\n\tMaximum Bitrate %d bps\n"), esd->decoderConfig->bufferSizeDB, esd->decoderConfig->avgBitrate, esd->decoderConfig->maxBitrate); + if (esd->slConfig->predefined==SLPredef_SkipSL) { + info += wxString::Format(wxT("\tNot using MPEG-4 Synchronization Layer\n")); + } else { + info += wxString::Format(wxT("\tStream Clock Resolution %d\n"), esd->slConfig->timestampResolution); + } + if (esd->URLString) + info += wxT("\tStream Location: ") + wxString(esd->URLString, wxConvUTF8) + wxT("\n"); + + /*check language*/ + if (esd->langDesc) { + s32 idx; + char lan[4]; + lan[0] = esd->langDesc->langCode>>16; + lan[1] = (esd->langDesc->langCode>>8)&0xFF; + lan[2] = (esd->langDesc->langCode)&0xFF; + lan[3] = 0; + idx = gf_lang_find(lan); + if (idx>=0) { + info += wxString::Format(wxT("\tStream Language: %s\n"), gf_lang_get_name(idx) ); + } + } + + } + m_pViewInfo->Clear(); + m_pViewInfo->AppendText(info); +} + + +void wxFileProps::SetDecoderInfo() +{ + GF_MediaInfo odi; + wxString info; + u32 h, m, s; + + if (!m_current_odm || gf_term_get_object_info(m_pApp->m_term, m_current_odm, &odi)) { + m_pViewInfo->Clear(); + m_pViewInfo->AppendText(info); + return; + } + + info = wxT("Status: "); + switch (odi.status) { + case 0: + case 1: + case 2: + h = (u32) (odi.current_time / 3600); + m = (u32) (odi.current_time / 60) - h*60; + s = (u32) (odi.current_time) - h*3600 - m*60; + if (odi.status==0) info += wxT("Stopped"); + else if (odi.status==1) info += wxT("Playing"); + else info += wxT("Paused"); + info += wxString::Format(wxT("\nObject Time: %02d:%02d:%02d\n"), h, m, s); + break; + case 3: + info += wxT("Not Setup\n"); + m_pViewInfo->Clear(); + m_pViewInfo->AppendText(info); + return; + default: + info += wxT("Setup Failed\n"); + m_pViewInfo->Clear(); + m_pViewInfo->AppendText(info); + return; + } + /*get clock drift*/ + info += wxString::Format(wxT("Clock drift: %d ms\n"), odi.clock_drift); + /*get buffering*/ + if (odi.buffer>=0) info += wxString::Format(wxT("Buffering Time: %d ms\n"), odi.buffer); + else if (odi.buffer==-1) info += wxT("Not buffering\n"); + else info += wxT("Not Playing\n"); + + /*get DB occupation*/ + if (odi.buffer>=0) info += wxString::Format(wxT("Decoding Buffer: %d Access Units\n"), odi.db_unit_count); + /*get CB occupation*/ + if (odi.cb_max_count) + info += wxString::Format(wxT("Composition Memory: %d/%d Units\n"), odi.cb_unit_count, odi.cb_max_count); + + Float avg_dec_time = 0; + if (odi.nb_dec_frames) { + avg_dec_time = (Float) odi.total_dec_time; + avg_dec_time /= odi.nb_dec_frames; + } + info += wxString::Format(wxT("Average Bitrate %d kbps (%d max)\nAverage Decoding Time %.2f ms (%d max)\nTotal decoded frames %d - %d dropped\n"), + (u32) odi.avg_bitrate/1024, odi.max_bitrate/1024, avg_dec_time, odi.max_dec_time, odi.nb_dec_frames, odi.nb_dropped); + + m_pViewInfo->Clear(); + m_pViewInfo->AppendText(info); +} + +void wxFileProps::SetNetworkInfo() +{ + wxString info; + u32 id; + NetStatCommand com; + GF_MediaInfo odi; + u32 d_enum; + GF_Err e; + + info = wxT(""); + m_pViewInfo->Clear(); + m_pViewInfo->AppendText(wxT("")); + + if (!m_current_odm || gf_term_get_object_info(m_pApp->m_term, m_current_odm, &odi) != GF_OK) return; + + if (odi.owns_service) { + const char *url, *path; + u32 done, total, bps; + info = wxT("Current Downloads in service:\n"); + d_enum = 0; + while (gf_term_get_download_info(m_pApp->m_term, m_current_odm, &d_enum, &url, &path, &done, &total, &bps)) { + info += wxString(url, wxConvUTF8); + if (total) { + info += wxString::Format(wxT(": %d / %d bytes (%.2f %%) - %.2f kBps\n"), done, total, (100.0*done)/total, ((Double)bps)/1024); + } else { + info += wxString::Format(wxT(": %.2f kBps\n"), ((Double)bps)/1024); + } + } + if (!d_enum) info = wxT("No Downloads in service\n"); + info += wxT("\n"); + } + + d_enum = 0; + while (gf_term_get_channel_net_info(m_pApp->m_term, m_current_odm, &d_enum, &id, &com, &e)) { + if (e) continue; + if (!com.bw_down && !com.bw_up) continue; + + info += wxString::Format(wxT("Stream ID %d statistics:\n"), id); + if (com.multiplex_port) { + info += wxString::Format(wxT("\tMultiplex Port %d - multiplex ID %d\n"), com.multiplex_port, com.port); + } else { + info += wxString::Format(wxT("\tPort %d\n"), com.port); + } + info += wxString::Format(wxT("\tPacket Loss Percentage: %.4f\n"), com.pck_loss_percentage); + info += wxString::Format(wxT("\tDown Bandwidth: %.3f bps\n"), ((Float)com.bw_down)/1024); + if (com.bw_up) info += wxString::Format(wxT("\tUp Bandwidth: %d bps\n"), com.bw_up); + if (com.ctrl_port) { + if (com.multiplex_port) { + info += wxString::Format(wxT("\tControl Multiplex Port: %d - Control Multiplex ID %d\n"), com.multiplex_port, com.ctrl_port); + } else { + info += wxString::Format(wxT("\tControl Port: %d\n"), com.ctrl_port); + } + info += wxString::Format(wxT("\tControl Down Bandwidth: %d bps\n"), com.ctrl_bw_down); + info += wxString::Format(wxT("\tControl Up Bandwidth: %d bps\n"), com.ctrl_bw_up); + } + info += wxT("\n"); + } + m_pViewInfo->Clear(); + m_pViewInfo->AppendText(info); +} + + +void wxFileProps::OnViewWorld(wxCommandEvent &WXUNUSED(event)) +{ + wxString wit; + const char *str; + GF_List *descs; + descs = gf_list_new(); + str = gf_term_get_world_info(m_pApp->m_term, m_current_odm, descs); + + if (!str) { + wxMessageDialog(this, wxT("No World Info available"), wxT("Sorry!"), wxOK).ShowModal(); + return; + } + + wit = wxT(""); + for (u32 i=0; gf_list_count(descs); i++) { + const char *d = (const char *) gf_list_get(descs, i); + wit += wxString(d, wxConvUTF8); + wit += wxT("\n"); + } + wxMessageDialog(this, wit, wxString(str, wxConvUTF8), wxOK).ShowModal(); + gf_list_del(descs); +} + +void wxFileProps::OnViewSG(wxCommandEvent &WXUNUSED(event)) +{ + const char *sOpt; + Bool dump_xmt; + wxFileName out_file; + char szOutFile[GF_MAX_PATH]; + wxString fname; + + sOpt = gf_cfg_get_key(m_pApp->m_user.config, "Core", "CacheDirectory"); + out_file.AssignDir(wxString(sOpt, wxConvUTF8) ); + + sOpt = gf_cfg_get_key(m_pApp->m_user.config, "General", "ViewXMT"); + out_file.SetFullName(wxT("scene_dump")); + if (sOpt && !stricmp(sOpt, "yes")) { + dump_xmt = 1; + } else { + dump_xmt = 0; + } + strcpy(szOutFile, out_file.GetFullName().mb_str(wxConvUTF8)); + + GF_Err e = gf_term_dump_scene(m_pApp->m_term, szOutFile, NULL, dump_xmt, 0, m_current_odm); + if (e) { + wxMessageDialog dlg(this, wxString(gf_error_to_string(e), wxConvUTF8), wxT("Error while dumping"), wxOK); + dlg.ShowModal(); + } else { + wxString cmd = get_pref_browser(m_pApp->m_user.config); + cmd += wxT(" "); + cmd += wxString(szOutFile, wxConvUTF8); + wxExecute(cmd); + } +} diff --git a/applications/deprecated/old_arch/osmo4_wx/fileprops.h b/applications/deprecated/old_arch/osmo4_wx/fileprops.h new file mode 100644 index 0000000..28caaa1 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/fileprops.h @@ -0,0 +1,84 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / Osmo4 wxWidgets GUI + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + */ + +#ifndef _FILEPROPS_H +#define _FILEPROPS_H + +#include "wx/wxprec.h" + +#ifndef WX_PRECOMP +#include "wx/wx.h" +#endif + +#include + +#include + +/*abstract class for all items in the tree*/ +class ODTreeData : public wxTreeItemData +{ +public: + ODTreeData(GF_ObjectManager *odm) : wxTreeItemData(), m_pODMan(odm) {} + GF_ObjectManager *m_pODMan; +}; + + +class wxOsmo4Frame; +class wxFileProps : public wxDialog +{ +public: + wxFileProps(wxWindow *parent); + virtual ~wxFileProps(); + +private: + DECLARE_EVENT_TABLE() + + wxOsmo4Frame *m_pApp; + + wxTreeCtrl *m_pTreeView; + wxTextCtrl *m_pViewInfo; + wxComboBox *m_pViewSel; + wxButton *m_pViewWI, *m_pViewSG; + wxTimer *m_pTimer; + + GF_ObjectManager *m_current_odm; + + void RewriteODTree(); + void SetGeneralInfo(); + void SetStreamsInfo(); + void SetDecoderInfo(); + void SetNetworkInfo(); + void WriteInlineTree(ODTreeData *pRoot); + void OnSetSelection(wxTreeEvent &event); + void OnSelectInfo(wxCommandEvent &event); + void OnTimer(wxTimerEvent &event); + void OnViewWorld(wxCommandEvent &event); + void OnViewSG(wxCommandEvent &event); + void SetInfo(GF_ObjectManager *odm); +}; + +#endif + diff --git a/applications/deprecated/old_arch/osmo4_wx/menubtn.cpp b/applications/deprecated/old_arch/osmo4_wx/menubtn.cpp new file mode 100644 index 0000000..48b6f98 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/menubtn.cpp @@ -0,0 +1,870 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: wxMenuButton +// Purpose: A button with a dropdown wxMenu +// Author: John Labenski +// Modified by: +// Created: 11/05/2002 +// RCS-ID: +// Copyright: (c) John Labenki +// Licence: wxWidgets licence +///////////////////////////////////////////////////////////////////////////// + +#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) +#pragma implementation "menubtn.h" +#endif + +// For compilers that support precompilation, includes "wx/wx.h". +#include "wx/wxprec.h" + +#ifdef __BORLANDC__ +#pragma hdrstop +#endif + +#ifndef WX_PRECOMP +#include "wx/control.h" +#include "wx/menu.h" +#include "wx/settings.h" +#include "wx/bitmap.h" +#include "wx/pen.h" +#include "wx/dc.h" +#endif // WX_PRECOMP + +#include +#include +#include +#include + +#include "menubtn.h" + + + +// ========================================================================== +// wxCustomButton +// ========================================================================== +IMPLEMENT_DYNAMIC_CLASS( wxCustomButton, wxControl ) + +BEGIN_EVENT_TABLE(wxCustomButton,wxControl) + EVT_MOUSE_EVENTS ( wxCustomButton::OnMouseEvents ) + EVT_PAINT ( wxCustomButton::OnPaint ) + EVT_SIZE ( wxCustomButton::OnSize ) +END_EVENT_TABLE() + +wxCustomButton::~wxCustomButton() +{ + if (HasCapture()) ReleaseMouse(); + if (m_timer) delete m_timer; +} + +void wxCustomButton::Init() +{ + m_focused = FALSE; + m_labelMargin = wxSize(4,4); + m_bitmapMargin = wxSize(2,2); + m_down = 0; + m_timer = NULL; + m_eventType = 0; + m_button_style = wxCUSTBUT_TOGGLE|wxCUSTBUT_BOTTOM; +} + +bool wxCustomButton::Create(wxWindow* parent, wxWindowID id, + const wxString& label, const wxBitmap &bitmap, + const wxPoint& pos, const wxSize& size, + long style, const wxValidator& val, + const wxString& name) +{ + if (!wxControl::Create(parent,id,pos,size,wxNO_BORDER|wxCLIP_CHILDREN,val,name)) + return FALSE; + + wxControl::SetLabel(label); + wxControl::SetBackgroundColour(parent->GetBackgroundColour()); + wxControl::SetForegroundColour(parent->GetForegroundColour()); + wxControl::SetFont(parent->GetFont()); + + if (bitmap.Ok()) m_bmpLabel = bitmap; + + if (!SetButtonStyle(style)) return FALSE; + + wxSize bestSize = DoGetBestSize(); + SetSize(wxSize(size.x<0 ? bestSize.x:size.x, size.y<0 ? bestSize.y:size.y)); +#if (wxMINOR_VERSION<8) + SetBestSize(GetSize()); +#else + SetInitialSize(GetSize()); +#endif + + CalcLayout(TRUE); + return TRUE; +} + +void wxCustomButton::SetValue(bool depressed) +{ + wxCHECK_RET(!(m_button_style & wxCUSTBUT_NOTOGGLE), wxT("can't set button state")); + m_down = depressed ? 1 : 0; + Refresh(FALSE); +} + +bool wxCustomButton::SetButtonStyle(long style) +{ + int n_styles = 0; + if ((style & wxCUSTBUT_LEFT) != 0) n_styles++; + if ((style & wxCUSTBUT_RIGHT) != 0) n_styles++; + if ((style & wxCUSTBUT_TOP) != 0) n_styles++; + if ((style & wxCUSTBUT_BOTTOM) != 0) n_styles++; + wxCHECK_MSG(n_styles < 2, FALSE, wxT("Only one wxCustomButton label position allowed")); + + n_styles = 0; + if ((style & wxCUSTBUT_NOTOGGLE) != 0) n_styles++; + if ((style & wxCUSTBUT_BUTTON) != 0) n_styles++; + if ((style & wxCUSTBUT_TOGGLE) != 0) n_styles++; + if ((style & wxCUSTBUT_BUT_DCLICK_TOG) != 0) n_styles++; + if ((style & wxCUSTBUT_TOG_DCLICK_BUT) != 0) n_styles++; + wxCHECK_MSG(n_styles < 2, FALSE, wxT("Only one wxCustomButton style allowed")); + + m_button_style = style; + + if ((m_button_style & wxCUSTBUT_BUTTON) != 0) + m_down = 0; + + CalcLayout(TRUE); + return TRUE; +} + +void wxCustomButton::SetLabel( const wxString &label ) +{ + wxControl::SetLabel(label); + CalcLayout(TRUE); +} + +// sequence of events in GTK is up, dclick, up. + +void wxCustomButton::OnMouseEvents(wxMouseEvent& event) +{ + if (m_button_style & wxCUSTBUT_NOTOGGLE) return; + + if (event.LeftDown() || event.RightDown()) + { + if (!HasCapture()) + CaptureMouse(); // keep depressed until up + + m_down++; + Redraw(); + } + else if (event.LeftDClick() || event.RightDClick()) + { + m_down++; // GTK eats second down event + Redraw(); + } + else if (event.LeftUp()) + { + if (HasCapture()) + ReleaseMouse(); + + m_eventType = wxEVT_LEFT_UP; + +#if (wxMINOR_VERSION<8) + if (wxRect(wxPoint(0,0), GetSize()).Inside(event.GetPosition())) +#else + if (wxRect(wxPoint(0,0), GetSize()).Contains(event.GetPosition())) +#endif + { + if ((m_button_style & wxCUSTBUT_BUTTON) && (m_down > 0)) + { + m_down = 0; + Redraw(); + SendEvent(); + return; + } + else + { + if (!m_timer) + { + m_timer = new wxTimer(this, m_down+1); + m_timer->Start(200, TRUE); + } + else + { + m_eventType = wxEVT_LEFT_DCLICK; + } + + if ((m_button_style & wxCUSTBUT_TOGGLE) && + (m_button_style & wxCUSTBUT_TOG_DCLICK_BUT)) m_down++; + } + } + + Redraw(); + } + else if (event.RightUp()) + { + if (HasCapture()) + ReleaseMouse(); + + m_eventType = wxEVT_RIGHT_UP; + +#if (wxMINOR_VERSION<8) + if (wxRect(wxPoint(0,0), GetSize()).Inside(event.GetPosition())) +#else + if (wxRect(wxPoint(0,0), GetSize()).Contains(event.GetPosition())) +#endif + { + if ((m_button_style & wxCUSTBUT_BUTTON) && (m_down > 0)) + { + m_down = 0; + Redraw(); + SendEvent(); + return; + } + else + { + m_down++; + + if (!m_timer) + { + m_timer = new wxTimer(this, m_down); + m_timer->Start(250, TRUE); + } + else + { + m_eventType = wxEVT_RIGHT_DCLICK; + } + } + } + + Redraw(); + } + else if (event.Entering()) + { + m_focused = TRUE; + if ((event.LeftIsDown() || event.RightIsDown()) && HasCapture()) + m_down++; + + Redraw(); + } + else if (event.Leaving()) + { + m_focused = FALSE; + if ((event.LeftIsDown() || event.RightIsDown()) && HasCapture()) + m_down--; + + Redraw(); + } +} + + + +void wxCustomButton::SendEvent() +{ + if (((m_button_style & wxCUSTBUT_TOGGLE) && (m_eventType == wxEVT_LEFT_UP)) || + ((m_button_style & wxCUSTBUT_BUT_DCLICK_TOG) && (m_eventType == wxEVT_LEFT_DCLICK)) || + ((m_button_style & wxCUSTBUT_TOG_DCLICK_BUT) && (m_eventType == wxEVT_LEFT_UP))) + { + wxCommandEvent eventOut(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, GetId()); + eventOut.SetInt(m_down%2 ? 1 : 0); + eventOut.SetExtraLong(m_eventType); + eventOut.SetEventObject(this); + GetEventHandler()->ProcessEvent(eventOut); + } + else + { + wxCommandEvent eventOut(wxEVT_COMMAND_BUTTON_CLICKED, GetId()); + eventOut.SetInt(0); + eventOut.SetExtraLong(m_eventType); + eventOut.SetEventObject(this); + GetEventHandler()->ProcessEvent(eventOut); + } +} + +wxBitmap wxCustomButton::CreateBitmapDisabled(const wxBitmap &bitmap) const +{ + wxCHECK_MSG(bitmap.Ok(), wxNullBitmap, wxT("invalid bitmap")); + + unsigned char br = GetBackgroundColour().Red(); + unsigned char bg = GetBackgroundColour().Green(); + unsigned char bb = GetBackgroundColour().Blue(); + + wxImage image = bitmap.ConvertToImage(); + int pos, width = image.GetWidth(), height = image.GetHeight(); + unsigned char *img_data = image.GetData(); + + for (int j=0; j lh ? bh : lh; + if (has_bitmap && has_label) lw -= wxMin(m_labelMargin.x, m_bitmapMargin.x); + return wxSize(lw+bw, h); + } + + int w = bw > lw ? bw : lw; + if (has_bitmap && has_label) lh -= wxMin(m_labelMargin.y, m_bitmapMargin.y); + return wxSize(w, lh+bh); +} + +void wxCustomButton::CalcLayout(bool refresh) +{ + int w, h; + GetSize(&w,&h); + + int bw = 0, bh = 0; + int lw = 0, lh = 0; + + if (m_bmpLabel.Ok()) // assume they're all the same size + { + bw = m_bmpLabel.GetWidth(); + bh = m_bmpLabel.GetHeight(); + } + wxString label = GetLabel(); + if (!label.IsEmpty()) + { + GetTextExtent(label, &lw, &lh); + } + + // Center the label or bitmap if only one or the other + if (!m_bmpLabel.Ok()) + { + m_bitmapPos = wxPoint(0,0); + m_labelPos = wxPoint((w-lw)/2, (h-lh)/2); + } + else if (label.IsEmpty()) + { + m_bitmapPos = wxPoint((w-bw)/2, (h-bh)/2); + m_labelPos = wxPoint(0,0); + } + else if (m_button_style & wxCUSTBUT_LEFT) + { + int mid_margin = wxMax(m_labelMargin.x, m_bitmapMargin.x); + m_labelPos = wxPoint((w - (bw+lw+m_labelMargin.x+m_bitmapMargin.x+mid_margin))/2 + m_labelMargin.x, (h - lh)/2); + m_bitmapPos = wxPoint(m_labelPos.x + lw + mid_margin, (h - bh)/2); + } + else if (m_button_style & wxCUSTBUT_RIGHT) + { + int mid_margin = wxMax(m_labelMargin.x, m_bitmapMargin.x); + m_bitmapPos = wxPoint((w - (bw+lw+m_labelMargin.x+m_bitmapMargin.x+mid_margin))/2 + m_bitmapMargin.x, (h - bh)/2); + m_labelPos = wxPoint(m_bitmapPos.x + bw + mid_margin, (h - lh)/2); + } + else if (m_button_style & wxCUSTBUT_TOP) + { + int mid_margin = wxMax(m_labelMargin.y, m_bitmapMargin.y); + m_labelPos = wxPoint((w - lw)/2, (h - (bh+lh+m_labelMargin.y+m_bitmapMargin.y+mid_margin))/2 + m_labelMargin.y); + m_bitmapPos = wxPoint((w - bw)/2, m_labelPos.y + lh + mid_margin); + } + else // if (m_button_style & wxCUSTBUT_BOTTOM) DEFAULT + { + int mid_margin = wxMax(m_labelMargin.y, m_bitmapMargin.y); + m_bitmapPos = wxPoint((w - bw)/2, (h - (bh+lh+m_labelMargin.y+m_bitmapMargin.y+mid_margin))/2 + m_bitmapMargin.y); + m_labelPos = wxPoint((w - lw)/2, m_bitmapPos.y + bh + mid_margin); + } + + if (refresh) Refresh(FALSE); +} + + +/* XPM */ +static const char *down_arrow_xpm_data[] = { + /* columns rows colors chars-per-pixel */ + "5 3 2 1", + " c None", + "a c Black", + /* pixels */ + "aaaaa", + " aaa ", + " a " +}; + +static wxBitmap s_dropdownBitmap; // all buttons share the same bitmap + +enum +{ + IDD_DROPDOWN_BUTTON = 100 +}; + +//----------------------------------------------------------------------------- +// wxMenuButtonEvents +//----------------------------------------------------------------------------- + +DEFINE_LOCAL_EVENT_TYPE(wxEVT_MENUBUTTON_OPEN) + +// ========================================================================== +// MenuDropButton +// ========================================================================== + +class MenuDropButton : public wxCustomButton +{ +public: + MenuDropButton( wxWindow *parent, wxWindowID id, long style) : wxCustomButton() + { + if (!s_dropdownBitmap.Ok()) + s_dropdownBitmap = wxBitmap(down_arrow_xpm_data); + + Create( parent, id, wxEmptyString, s_dropdownBitmap, wxDefaultPosition, + wxSize(wxMENUBUTTON_DROP_WIDTH, wxMENUBUTTON_DROP_HEIGHT), style); + } + + virtual void Paint( wxDC &dc ) + { + wxCustomButton *labelBut = ((wxMenuButton*)GetParent())->GetLabelButton(); + + // pretend that both buttons have focus (for flat style) + if (labelBut) + { + wxPoint p = GetParent()->ScreenToClient(wxGetMousePosition()); + +#if (wxMINOR_VERSION<8) + if (GetRect().Inside(p) || labelBut->GetRect().Inside(p)) +#else + if (GetRect().Contains(p) || labelBut->GetRect().Contains(p)) +#endif + { + m_focused = TRUE; + + if (!labelBut->GetFocused()) + labelBut->SetFocused(TRUE); + } + else + { + m_focused = FALSE; + + if (labelBut->GetFocused()) + labelBut->SetFocused(FALSE); + } + } + + wxCustomButton::Paint(dc); + } +}; + +// ========================================================================== +// MenuLabelButton +// ========================================================================== + +class MenuLabelButton : public wxCustomButton +{ +public: + MenuLabelButton( wxWindow* parent, wxWindowID id, + const wxString &label, + const wxBitmap &bitmap, + long style ) : wxCustomButton() + { + Create(parent, id, label, bitmap, wxDefaultPosition, wxDefaultSize, style); + } + + virtual void Paint( wxDC &dc ) + { + wxCustomButton *dropBut = ((wxMenuButton*)GetParent())->GetDropDownButton(); + + // pretend that both buttons have focus (for flat style) + if (dropBut) + { + wxPoint p = GetParent()->ScreenToClient(wxGetMousePosition()); + +#if (wxMINOR_VERSION<8) + if (GetRect().Inside(p) || dropBut->GetRect().Inside(p)) +#else + if (GetRect().Contains(p) || dropBut->GetRect().Contains(p)) +#endif + { + m_focused = TRUE; + + if (!dropBut->GetFocused()) + dropBut->SetFocused(TRUE); + } + else + { + m_focused = FALSE; + + if (dropBut->GetFocused()) + dropBut->SetFocused(FALSE); + } + } + + wxCustomButton::Paint(dc); + } +}; + +// ========================================================================== +// wxMenuButton +// ========================================================================== + +IMPLEMENT_DYNAMIC_CLASS( wxMenuButton, wxControl ) + +BEGIN_EVENT_TABLE(wxMenuButton,wxControl) + EVT_BUTTON(wxID_ANY, wxMenuButton::OnButton) + +#ifdef __WXMSW__ + EVT_MENU(wxID_ANY, wxMenuButton::OnMenu) +#endif +END_EVENT_TABLE() + +wxMenuButton::~wxMenuButton() +{ + AssignMenu(NULL, TRUE); +} + +void wxMenuButton::Init() +{ + m_labelButton = NULL; + m_dropdownButton = NULL; + m_menu = NULL; + m_menu_static = FALSE; + m_style = 0; +} + +bool wxMenuButton::Create( wxWindow* parent, wxWindowID id, + const wxString &label, + const wxBitmap &bitmap, + const wxPoint& pos, + const wxSize& size, + long style, + const wxValidator& val, + const wxString& name) +{ + m_style = style; + + long flat = style & wxMENUBUT_FLAT; + + wxControl::Create(parent,id,pos,size,wxNO_BORDER|wxCLIP_CHILDREN,val,name); + wxControl::SetLabel(label); + SetBackgroundColour(parent->GetBackgroundColour()); + SetForegroundColour(parent->GetForegroundColour()); + SetFont(parent->GetFont()); + + m_labelButton = new MenuLabelButton(this, id, label, bitmap, wxCUSTBUT_BUTTON|flat); + m_dropdownButton = new MenuDropButton(this, IDD_DROPDOWN_BUTTON, wxCUSTBUT_BUTTON|flat); + + wxSize bestSize = DoGetBestSize(); + SetSize( wxSize(size.x < 0 ? bestSize.x : size.x, + size.y < 0 ? bestSize.y : size.y) ); + +#if (wxMINOR_VERSION<8) + SetBestSize(GetSize()); +#else + SetInitialSize(GetSize()); +#endif + + return TRUE; +} + +#ifdef __WXMSW__ +// FIXME - I think there was a patch to fix this +void wxMenuButton::OnMenu( wxCommandEvent &event ) +{ + event.Skip(); + wxMenuItem *mi = m_menu->FindItem(event.GetId()); + if (mi && (mi->GetKind() == wxITEM_RADIO)) + m_menu->Check(event.GetId(), TRUE); +} +#endif // __WXMSW__ + +void wxMenuButton::OnButton( wxCommandEvent &event) +{ + int win_id = event.GetId(); + + if (win_id == IDD_DROPDOWN_BUTTON) + { + wxNotifyEvent mevent(wxEVT_MENUBUTTON_OPEN, GetId()); + mevent.SetEventObject(this); + if (GetEventHandler()->ProcessEvent(mevent) && !mevent.IsAllowed()) + return; + + if (!m_menu) + return; + + PopupMenu(m_menu, wxPoint(0, GetSize().y)); + + m_labelButton->Refresh(FALSE); + m_dropdownButton->Refresh(FALSE); + } + else if (win_id == m_labelButton->GetId()) + { + + wxCommandEvent cevent(wxEVT_COMMAND_MENU_SELECTED, win_id); + cevent.SetEventObject(this); + cevent.SetId(win_id); + GetParent()->GetEventHandler()->ProcessEvent(cevent); + + if (!m_menu) return; + + const wxMenuItemList &items = m_menu->GetMenuItems(); + int first_radio_id = -1; + int checked_id = -1; + bool check_next = FALSE; + + // find the next available radio item to check + for (wxMenuItemList::Node *node = items.GetFirst(); node; node = node->GetNext()) + { + wxMenuItem *mi = (wxMenuItem*)node->GetData(); + if (mi && (mi->GetKind() == wxITEM_RADIO)) + { + if (first_radio_id == -1) + first_radio_id = mi->GetId(); + + if (check_next) + { + check_next = FALSE; + checked_id = mi->GetId(); + break; + } + else if (mi->IsChecked()) + check_next = TRUE; + } + } + // the last item was checked, go back to the first + if (check_next && (first_radio_id != -1)) + checked_id = first_radio_id; + + if (checked_id != -1) + { + m_menu->Check(checked_id, TRUE); + + wxCommandEvent mevent( wxEVT_COMMAND_MENU_SELECTED, checked_id); + mevent.SetEventObject( m_menu ); + mevent.SetInt(1); + GetEventHandler()->ProcessEvent(mevent); + } + } +} + +int wxMenuButton::GetSelection() const +{ + wxCHECK_MSG(m_menu != NULL, wxNOT_FOUND, wxT("No attached menu in wxMenuButton::GetSelection")); + + const wxMenuItemList &items = m_menu->GetMenuItems(); + + for (wxMenuItemList::Node *node = items.GetFirst(); node; node = node->GetNext()) + { + wxMenuItem *mi = (wxMenuItem*)node->GetData(); + if (mi && (mi->GetKind() == wxITEM_RADIO)) + { + if (mi->IsChecked()) + return mi->GetId(); + } + } + + return wxNOT_FOUND; +} + +void wxMenuButton::AssignMenu(wxMenu *menu, bool static_menu) +{ + if (!m_menu_static && m_menu) + delete m_menu; + + m_menu = menu; + m_menu_static = static_menu; +} + +void wxMenuButton::SetToolTip(const wxString &tip) +{ + wxWindow::SetToolTip(tip); + ((wxWindow*)m_labelButton)->SetToolTip(tip); + ((wxWindow*)m_dropdownButton)->SetToolTip(tip); +} +void wxMenuButton::SetToolTip(wxToolTip *tip) +{ + wxWindow::SetToolTip(tip); + ((wxWindow*)m_labelButton)->SetToolTip(tip); + ((wxWindow*)m_dropdownButton)->SetToolTip(tip); +} + +void wxMenuButton::DoSetSize(int x, int y, int width, int height, int sizeFlags) +{ + wxSize curSize( GetSize() ); + wxSize bestSize( DoGetBestSize() ); + + if (width == -1) + width = curSize.GetWidth(); + if (width < 10) + width = bestSize.GetWidth(); + + if (height == -1) + height = curSize.GetHeight(); + if (height < 5) + height = bestSize.GetHeight(); + + wxWindow::DoSetSize(x, y, width, height, sizeFlags); + + if (m_labelButton) + m_labelButton->SetSize(0, 0, width - wxMENUBUTTON_DROP_WIDTH, height); + if (m_dropdownButton) + m_dropdownButton->SetSize(width-wxMENUBUTTON_DROP_WIDTH, 0, wxMENUBUTTON_DROP_WIDTH, height); +} + +wxSize wxMenuButton::DoGetBestSize() +{ + if (!m_labelButton || !m_dropdownButton) + return wxSize(wxMENUBUTTON_DROP_WIDTH+wxMENUBUTTON_DROP_HEIGHT, wxMENUBUTTON_DROP_HEIGHT); + + wxSize size = m_labelButton->GetBestSize(); + size.x += wxMENUBUTTON_DROP_WIDTH; + return size; +} diff --git a/applications/deprecated/old_arch/osmo4_wx/menubtn.h b/applications/deprecated/old_arch/osmo4_wx/menubtn.h new file mode 100644 index 0000000..f82282e --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/menubtn.h @@ -0,0 +1,356 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: wxMenuButton +// Purpose: A button with a dropdown wxMenu +// Author: John Labenski +// Modified by: +// Created: 11/05/2002 +// Copyright: (c) John Labenski +// Licence: wxWidgets licence +///////////////////////////////////////////////////////////////////////////// + +/* + +wxMenuButton is a button that drops down an assigned wxMenu + +Create the button with either a text or bitmap label. + Create a new wxMenu and call AssignMenu and thats it. When you press the + dropdown button the menu appears. When you press the label button the next + wxITEM_RADIO (ie wxMenuItem::GetKind) in the menu is selected round robin. + If there are no radio items then it really just acts like a menubar, though + this is probably not too useful. The events sent in this case are EVT_MENUs + either generated by the menu when you click on it or created when you click + on the label to select the next radio item. +*/ + +#ifndef _WX_MENUBTN_H_ +#define _WX_MENUBTN_H_ + +#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) +#pragma interface "menubtn.h" +#endif + +class wxMenu; +class wxBitmap; +class wxCustomButton; + +//----------------------------------------------------------------------------- +// wxCustomButton styles +//----------------------------------------------------------------------------- + +enum wxCustomButton_Style +{ + // Position of the label, use only one + wxCUSTBUT_LEFT = 0x0001, + wxCUSTBUT_RIGHT = 0x0002, + wxCUSTBUT_TOP = 0x0004, + wxCUSTBUT_BOTTOM = 0x0008, + // Button style, use only one + wxCUSTBUT_NOTOGGLE = 0x0100, + wxCUSTBUT_BUTTON = 0x0200, + wxCUSTBUT_TOGGLE = 0x0400, + wxCUSTBUT_BUT_DCLICK_TOG = 0x0800, + wxCUSTBUT_TOG_DCLICK_BUT = 0x1000, + // drawing styles + wxCUSTBUT_FLAT = 0x2000 // flat, mouseover raises if not depressed +}; + +//----------------------------------------------------------------------------- +// wxCustomButton +//----------------------------------------------------------------------------- + +class WXDLLEXPORT wxCustomButton : public wxControl +{ +public: + + wxCustomButton() : wxControl() { + Init(); + } + + // wxToggleButton or wxButton compatible constructor (also wxTextCtrl) + wxCustomButton(wxWindow* parent, wxWindowID id, + const wxString& label, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxCUSTBUT_TOGGLE, + const wxValidator& val = wxDefaultValidator, + const wxString& name = wxT("wxCustomButton")) + : wxControl() + { + Init(); + Create(parent,id,label,wxNullBitmap,pos,size,style,val,name); + } + + // wxBitmapButton compatible constructor + wxCustomButton(wxWindow *parent, wxWindowID id, + const wxBitmap& bitmap, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxCUSTBUT_TOGGLE, + const wxValidator& val = wxDefaultValidator, + const wxString& name = wxT("wxCustomButton")) + : wxControl() + { + Init(); + Create(parent,id,wxEmptyString,bitmap,pos,size,style,val,name); + } + + // Native constructor + wxCustomButton(wxWindow *parent, wxWindowID id, + const wxString& label, const wxBitmap& bitmap, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxCUSTBUT_TOGGLE|wxCUSTBUT_BOTTOM, + const wxValidator& val = wxDefaultValidator, + const wxString& name = wxT("wxCustomButton")) + : wxControl() + { + Init(); + Create(parent,id,label,bitmap,pos,size,style,val,name); + } + + virtual ~wxCustomButton(); + + bool Create(wxWindow* parent, + wxWindowID id, + const wxString& label, + const wxBitmap &bitmap, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0, + const wxValidator& val = wxDefaultValidator, + const wxString& name = wxT("wxCustomButton")); + + bool GetValue() const { + return m_down%2 != 0; + } + void SetValue( bool depressed ); + + // Use combinations of wxCustomButton_Style(s) + long GetButtonStyle() const { + return m_button_style; + } + bool SetButtonStyle( long style ); + + // Set the text label, wxEmptyString for none + void SetLabel( const wxString &label ); + + // set the bitmaps, ONLY this Label bitmap is used for calculating control size + // all bitmaps will be centered accordingly in any case + // call SetSet(GetBestSize()) if you change their size and want the control to resize appropriately + void SetBitmapLabel(const wxBitmap& bitmap); + void SetBitmapSelected(const wxBitmap& sel) { + m_bmpSelected = sel; + CalcLayout(TRUE); + }; + void SetBitmapFocus(const wxBitmap& focus) { + m_bmpFocus = focus; + CalcLayout(TRUE); + }; + void SetBitmapDisabled(const wxBitmap& disabled) { + m_bmpDisabled = disabled; + CalcLayout(TRUE); + }; + // wxBitmapButton compatibility + void SetLabel(const wxBitmap& bitmap) { + SetBitmapLabel(bitmap); + } + + // retrieve the bitmaps + const wxBitmap& GetBitmapLabel() const { + return m_bmpLabel; + } + const wxBitmap& GetBitmapSelected() const { + return m_bmpSelected; + } + const wxBitmap& GetBitmapFocus() const { + return m_bmpFocus; + } + const wxBitmap& GetBitmapDisabled() const { + return m_bmpDisabled; + } + + // Creates a "disabled" bitmap by dithering it with the background colour + wxBitmap CreateBitmapDisabled(const wxBitmap &bitmap) const; + + // set/get the margins (in pixels) around the label and bitmap + // if fit = TRUE then resize the button to fit + void SetMargins(const wxSize &margin, bool fit = FALSE); + + // set/get the margins around the text label + // the inter bitmap/label margin is the max of either margin, not the sum + void SetLabelMargin(const wxSize &margin, bool fit = FALSE); + wxSize GetLabelMargin() const { + return m_labelMargin; + } + // set/get the margins around the bitmap + // the inter bitmap/label margin is the max of either margin, not the sum + void SetBitmapMargin(const wxSize &margin, bool fit = FALSE); + wxSize GetBitmapMargin() const { + return m_bitmapMargin; + } + + // can be used to activate the focused behavior (see MenuButton) + void SetFocused(bool focused) { + m_focused = focused; + Refresh(FALSE); + } + bool GetFocused() const { + return m_focused; + } + +protected: + void OnPaint(wxPaintEvent &event); + void Redraw(); + virtual void Paint( wxDC &dc ); + + virtual wxSize DoGetBestSize() const; + + virtual void SendEvent(); + + void OnMouseEvents(wxMouseEvent &event); + + void OnSize( wxSizeEvent &event ); + + virtual void CalcLayout(bool refresh); + + long m_down; // toggle state if m_down%2 then depressed + bool m_focused; // mouse in window + long m_button_style; + + // the bitmaps for various states + wxBitmap m_bmpLabel, + m_bmpSelected, + m_bmpFocus, + m_bmpDisabled; + + // the margins around the label/bitmap + wxSize m_labelMargin, + m_bitmapMargin; + + wxPoint m_bitmapPos, + m_labelPos; + + wxTimer *m_timer; + + wxEventType m_eventType; // store the mouse event type + +private: + void Init(); + DECLARE_DYNAMIC_CLASS(wxCustomButton) + DECLARE_EVENT_TABLE() +}; + +//----------------------------------------------------------------------------- +// wxMenuButton styles +//----------------------------------------------------------------------------- + +#define wxMENUBUTTON_DROP_WIDTH 10 +#define wxMENUBUTTON_DROP_HEIGHT 22 + +enum wxMenuButton_Styles +{ + wxMENUBUT_FLAT = wxCUSTBUT_FLAT +}; + +//----------------------------------------------------------------------------- +// wxMenuButton +//----------------------------------------------------------------------------- + +class wxMenuButton : public wxControl +{ +public: + + wxMenuButton() : wxControl() { + Init(); + } + + // Use this constructor if you need one compatible with a wxBitmapButton + // setup the button later with AssignMenu + wxMenuButton( wxWindow* parent, wxWindowID id, + const wxBitmap &bitmap, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0, + const wxValidator& val = wxDefaultValidator, + const wxString& name = wxT("wxMenuButton")) + : wxControl() + { + Init(); + Create(parent,id,wxEmptyString,bitmap,pos,size,style,val,name); + } + + virtual ~wxMenuButton(); + + bool Create( wxWindow* parent, + wxWindowID id, + const wxString &label, + const wxBitmap &bitmap, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxNO_BORDER, + const wxValidator& val = wxDefaultValidator, + const wxString& name = wxT("wxMenuButton")); + + // Gets the id of the first selected radio item or wxNOT_FOUND (-1) if none + int GetSelection() const; + + // This menu will be displayed when the dropdown button is pressed. + // if static_menu is FALSE it will be deleted when the buttton is destroyed. + void AssignMenu(wxMenu *menu, bool static_menu = FALSE); + + wxMenu *GetMenu() const { + return m_menu; + } + + // get a pointer to the label button, for turning it into a toggle perhaps + wxCustomButton *GetLabelButton() const { + return m_labelButton; + } + wxCustomButton *GetDropDownButton() const { + return m_dropdownButton; + } + + void SetToolTip(const wxString &tip); + void SetToolTip(wxToolTip *tip); + +protected: + void OnButton(wxCommandEvent &event); + + virtual void DoSetSize(int x, int y, int width, int height, + int sizeFlags = wxSIZE_AUTO); + + virtual wxSize DoGetBestSize(); + +// FIXME! - in MSW the radio items don't check themselves +#ifdef __WXMSW__ + void OnMenu( wxCommandEvent &event ); +#endif + + wxCustomButton *m_labelButton; + wxCustomButton *m_dropdownButton; + + wxMenu *m_menu; + bool m_menu_static; + long m_style; + +private: + void Init(); + DECLARE_DYNAMIC_CLASS(wxMenuButton) + DECLARE_EVENT_TABLE() +}; + +//----------------------------------------------------------------------------- +// wxMenuButtonEvents +// +// EVT_MENUBUTTON_OPEN(id, fn) - menu is about to be opened, (dis)(en)able items +// or call Veto() to stop menu from popping up +// this is a wxNotifyEvent +//----------------------------------------------------------------------------- + +BEGIN_DECLARE_EVENT_TYPES() +DECLARE_LOCAL_EVENT_TYPE( wxEVT_MENUBUTTON_OPEN, 0 ) +END_DECLARE_EVENT_TYPES() + +#define EVT_MENUBUTTON_OPEN(id, fn) DECLARE_EVENT_TABLE_ENTRY(wxEVT_MENUBUTTON_OPEN, id, wxID_ANY, (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) (wxNotifyEventFunction) & fn, (wxObject *) NULL ), + +#endif // _WX_MENUBTN_H_ diff --git a/applications/deprecated/old_arch/osmo4_wx/osmo4.ico b/applications/deprecated/old_arch/osmo4_wx/osmo4.ico new file mode 100644 index 0000000000000000000000000000000000000000..36ff66711771065305f263aee09bda915091c6b0 GIT binary patch literal 15086 zcmeHO30PIt+TLeio@ekdsi4dfDv^p9L!ya_hzg3RD2iy{goVNZ6g6`~O9RJLa4Iu3 zvvSrlE3>llW@f0BHr&FlB?;&3`@Z|I@xWE1zkC1xdG7O{=UJ?^_g=&Iee0XoLJ$;! zN(c`ZQ2Pqo{RJUX5CmV}re}LWxQuu9sQG=8S`ZeXLjd|)SFdr?Lqf8uI(3@-dT{W}GeJSKKMD#ec*om2_vN6V+|^N0lk(%^C&VNt zk8|4WLbr>8q4B&3Z94$gkr)%D4%R#wYyt5lox3dKvJARIt>6Xj4-eLz$wc8f~o zX1%%js@sl^WnXpdIJ4&AhbIio&d#>`-^3#;Yno&4-f4vzP2PEP^OX&P@Csl&iSjYZ zWt4AGen9!rsPLKZ`JMgPf3IGxuDs*qRC+Evd}>Zi%w+5TBpy?zPBn{jmLxqabh|nZG#ycAbk$`SG|oZKuDAL2~lQC=ZVrpF<{_XwH4V zf^r%*;d!3k&Rnm1u2k-CX$0@8;&_5^fzkcKjtS zZul%8pPgf{XXh{<=I6YepL?+fbXoEVHt! zPMbIN?p^I45)zUOc)thSdctRGYYQH+2M;-bZydo#PEui;@A;j$01rL(=XWnJuV^8yg!5KEmtj>MFXqxrrVg9?iww-CcU;?CdPzMm&fw_^bin zRwO1Sngs^#>1}TQ3ije`6AoN=uioB%>8YR~tz%pF0rGDe8ai=dReF=BFcI`w@PfyX?+gtSY^=&RbK0YF7Eow9xk+={KJ3BiW z2kt{Y=k3yEo3E|yo)d!bK@$#Kf2ZEfZEnIp~2p8XAUz}TNqK7!8J^;yNR(kF!oD~edRZA@2wN$ zaiDz?_+3Z&B;5%+X-nAr{ryGol^7fxEJ8NK(9lq+upJZ>Bz+?u+#Bu%aUcyS_u#p; zsi~=wPtmDUO_-_afm*EJ1jixB*QyKg@rxd4t0N=F=7Vo^g77(Dr(Qm+cXr>2v~OLJw2Dql)tyGb8~Z@TwRMmd)ju|{`1g<+rR43 zqo$qQ_mM{)@d51LQC`Wzq#@}}*hyo;NSGgf_+b%!#U4F+h&_Aul-f~IQDXP*-No?m za0yTD73G99pe#U^b)dy)d9AKpD@|-|ckUt_26#?_&sKdA7q`ku?qgg>MU5Y(R&NBp z#Fe~rQ19)%WvX!>=#;*Izk%yK2!5xRS8qr3akQO14PCSP>yI2*XqHz40! z08j3?I(YEz7Px}9v$+oSJM|-JPktxQkhY{XVTCM;k3II7*tc(A5xP;TiBAU0b;u`n!eiot&KQVQ07as=?o){Wz(2 zcduUF8c)b$0ePSFBJWTZ$v5P0@(N*3NJtR-_3I}N7%)H_IB=j;l9H0d{{8z)cyLdM zC&zFPCQ`7bE&B#07 zLg(zi-MRCYKE{362CP#7zn(HjeM#P-Op+(aGo&%$95iTM+8OeR#97veLwO-PF`)-AzsRUN`t%7Xe%Kop$Y(4>ax*5ivJU zq1c5z`WojdUmMqeqVx$BrEe zf5CHvyq0m@t=sa2gv$WmQSe#Sd&b}41|Bo$ecJ0&;GYfXfA1fBgvr=zJ#_}xru>rs zDVL-_;m^#>lwd~8Cg$bkiL4RlirfRjPx_PRD8JlC(gV8l0R6aotvYJdI>1A_MEFhu z{J0TOCF;9QeG!am>}lo=ZjB0^^^oZpN9<_Ch1VpJ}@v) z@*gmd4*F+3rd1fjWZX_ksQ^7sG{Juo^0~Iw_`8iw=~iR@x3t^}dAa`&`cDm8Uxu|C zD0`Ga@-pRsI+Qdf4wQjOlO{6tYzyAt+zpH=T+;+Zd{QX{S<|AM`7XgPxSr)htw8PZzw7s-j#DzA2@Bb($w zPo2cQpj^^Vqm5;uo}{0__<%eIoa^9wR^XfEedcX`O?&3zQt^?o{?}+$FKzCCMri1w zP~;ctjO_2Nz`z$CGVb%BHt5%>H@t+jRK$435-&ckEtQ^tMT(gsZSun7r( zZw~C-I>^N5@Rha$kEgLOBjERV%P~q@=3}33-Db{IDvy9S4E~Q&S@}~)NM%6l@h&ba zuqOwb{JaZ@ceZ~5Kd&u3)OsrE8F1QxXO6=!>T%BwZFmX&YoXCBLQLQ6f5OkGI2seP z(Y*EWE?t)Ogl~knm${1@*r(SU{ruLYw(i%~XXu$(*oy|lGe=>&TKHsbog+4O;!so5 zmyly+JVLmtb*`=z`S%*1m{?(EYrFcW!44SWFKg@Sv+?nj4~#kQH5cqxN9f4EQ%^#6 z8(~+o@73=?@6ywA95kA;_l@H(Q`6^eL`QFKjxk%0_xE4+IO0t38vSTP{OjqtwpbL! z``VV)b5O5B_iTe+(o+vnr$P=c!>{Ymy6^p-8Mj16PM)t)z1qajQv3@)wPM-*#_)Z1 zcBP4(UFB;=`3K~z_WT?Wuxh}A#u7h^c@)dqYnfc;b2R{9l;FIm`z z?!SfDG4WpGjNhfDWk2HTy7*hzE6|?)&RNLfraKzV%4x>^9@KVsUxU88m>b4;>a7nT z_b*)P*>h>sgT{eZmcgHZ?)8vtEMqhl+FQKS1CNg%blx&((6~5v_cFw>XPeeF6LC$&C(+SW_mx@Wd5qiM-m4uf zEh~>Q4{Xp~obx>7;icdF{1&bnIB-@wxj*cdHSFpk_yx>?{7e3s@fz%}oag-!xXQe! z7(IGw$B2lmbyijzaUQ_4C9Y%0Kbx35d&A#N%LhXgC*66U&CV2|nNOZk7`@*4cnkgi=vm%6&n z|4ykq)HE;WH{|~T`?W4z*7W(aFt_dx-Q$F`(k6pm<@w0n^B0BUP1uv?>#eL38x#sB z^IwHxV*_%spSrtO%zW&zwT>+o z`VT)4kC2dck2pE4UJ5<+kxI4kCeIFe_J%wv_~A_y$|Y+)^ZhoxO10rPbMw_-IXbS~ z;p4L^xmT}hhyO6Be>i<&;uhPes12bG4$ITpx34O*uvoT5tzPkxgTv}0ki~r})ia0z zR>QU}p8y_=?$v9(i&iVPumOL#$bV@E_?Gwy!ri*g$M7sDq#9m?mfsP<`LI4F2xhEH z1VP1GC{Za;>ygnuhPu%fcaF4zQ12-S`7x;BaYUi6dr}ZQeNe-D_eNcd`MlwoUqNk$ z`Z#Myu2dsia+1$-Jx>tk;n~NyhP9KPQOEE-;FN1EpTVsn>XTxL!cZSG6FHyQ7{mIw zs24>uS~I;?#QL>jqgIc)POOh{JyPi zt+qs3clPWj6+?&SAhkH>8E5B(=hW)8KM4Z!I?PSI369tgJ6Lna!eZ%lKfk%h5MRzf zUbq>S;$m$x{B2#kwA3&rIeB`Ri%Vg(Qn?j2;SARM3hU9f{)qYpVE7iC@I7kArr+Y- zMU*#@3tD_5Dk`UZ%9P12T(hmTC@Q)OYe>lKT$O4)d;{Lo`Btw~Ucs67dH6Ql8f|Uo z;;eU~-pXpM&c-GWIkV+Djpm5X%Ia@e|0>FLltb{33(u#dOz6`DL!0;~H}^@Ur)Oye z=_&}{0Kc!`C!IPM7+A3>Iy&W7jiw#!vO;fWCcuXhkoOmmZ?x2P=@NzWo>`|H9k*>m zUJ>#AH-OI9UbZW^X`Ka?qt})hAH5us&01Z5Hcq`d`%+Gs&Q^Bon65{ueu!{pP5P+ii-_= z#kTwduSv9N-+o>`?V>{Q9rpC}FX7=YruOfj;to7N!Ma>e=jLX}p)rPHOxP4hAwF>h z9Cm<1KwdC(LQ2X~M{DbyhYjc6U&DV~{zGPFuH-+0C!6;I@JQ``{mQz-kE>HCz5(6O z-VP71mf+QB++I?v%^CmvjP(az2R;}yS+8Iuy0lT%@W ze92Mn1LH>ABRHI&KTXOLQMP6LS=-vK#CZ+Rl`mng>bZQ^v**Ko6bd!&vpMQ8zmD-c za{Xm8Oc>4gMMWhl zc`u@)7xjcMa)K@hkS^h zxdp<>*viz@>`F>Xij)Hn4z9{F+9BBEO>C^M2^U%^#gRXRTes-lHmwXZDsH@Wn zAAg87_b|pmoUa3pdgiftR?NILbMh=~^BvFl5o>cV^o&haD%GuoghVM$n>cZaxuxYk z-2WgAUan70p4OZ*7&2s$l^|?J?)@r!`s1HvW-e$x-^RL!8D{}^9cZoRIy@)hnUZ`C z%se{b;Q252gZ#ieFYa&rj&r40115qH7+8gS4!nc$5$HJkY5Dzt0p;Mky|8TGAg{5# zhVQ|jD%Qxc?&kW~+eYGt^F%$@MNULIPn6F;2?Ni6(1)?Oo;ha3Rkv^^*Ol||mzugY z3^E1FUW@%)vhRr}W=Q$NgoOFSX}M{&!@^b-u`l>S%0q*e#XLi0&Jpo3&kyumI}>MV z@)>4YTAFm802)a9gY}^!T^X}p#+j!*=i_hItOX8cW^Y_%tY~Yy_H0(xjP`urzyH&? z55XAnk~nzqDk(oLua7)!B=-MT@HP3ak?Zpu75NhVlqplh$&)9GW5$f(`Kq4hOz5MB zj5LsD;6vy!N%u?gou6M~u2k+hNglAWs=hRK?Cc1BPfT2deR&V-U&I;bbA@~dJD^&y zV1Xo`pyNj7Ilx=LgV*#t^TE3Mf`S6+91*mT_5^n^^pJrD(jKwxPoPtfJQwTf(@V`v zOm-jR`qtLFE{_{G2m3Av{rWA+HJk%|3E7-4hZ6o0%>aW`W6jRk&(ov>l$NiG8&J-qFy7PlC=hkoy5R1MDdGllew|E-f|K7ayOE z<%YY;?}E+?viZHF#IOdAt;!7fhAYscMH1f@7q|4^h!bg1US2Ng2CSKnvy8W9&z@~# zn5#r-j5puO`hDccCC&!0KeL9-+XkIP`>TU)sDn&OvSf@uaF@nqW>&}JocJd74sic2 zIe97UA?G#hi%g@`RNlYY_=Q17U&7pTUYR*F50Mx5sZ0remga*F8pa$bWesaLP%dE~ z_Tx-m(wRj?#Y*tJ)Hf+HA%PHv5<UKz}C4z-`T<@_Z-JQB_#%$CnYWJhx`3k z;7bATv(QN!YjSg!|H~e641Z;1MG|cT1DB6S&H}Pf3m9tQ_pHAf96aQSfdl)R2L!ZV zq*g0%FTxi0)$EZ!b>JNd_`r|@ualX0yqEZSCQjQHZ)6Yj4K8_wL}V8-3{ee*nLGFYZ83pPrOdAoGPZukl*417`0@u45zjn3VZsx^9IMr~v_G;PPfeZM+r(u5H!9UxuaI+Kv!fT z4qSy8>m2m#N%#bFB|l1rLAIp>2bRQ;huz&z?eO&MxyI2^34cY|=;Wlj$`n1{|6>AvK01`xRgoqlb2U$5Bq%G*Y}+l`u2^BW9;kY)m|SE5UgYI_4PyCV*)=s zXi`+v9{4}+izz9y`pPhL?fN`?s}FBYnzYEWQzy?CTwFZpFW#a50voP}|J3N^sK>M5Z=BuWXQBW9HUmBtV>APIGgRxor6}ue(>JeZTPY}+zHeprud!q)XmM~ z2>Xm3y9|EOnV+Mgp4Ju@7bs0kPTlI?ziI}*jT$xD4?fUU-v5OksDmFU`C;&bbV*5r z64__;=mmio|9!V^D;Dxutv+3s>SUA&+ePD|v9)lm4hIkOPg(fU7r`(ya-J!$2DhrDvpR>*1 z$dTpY+>>6tmKXC`tDP_P>(QgA5KpDUSXfszs{Z4A@=$ZW44?z zH;=huVxocV(m-E_-SYCPJjnAtTiX{89c51UfyhJ z&GPbjD#%Q;Oj9v2Z(0WnT8q)|Xn=u*AgqAa+k{30( Ih+0ATC&$&)D*ylh literal 0 HcmV?d00001 diff --git a/applications/deprecated/old_arch/osmo4_wx/osmo4.xpm b/applications/deprecated/old_arch/osmo4_wx/osmo4.xpm new file mode 100644 index 0000000..bec8e6a --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/osmo4.xpm @@ -0,0 +1,301 @@ +/* XPM */ +static const char * osmo4[] = { +"32 32 266 2", +" c None", +". c #990909", +"+ c #A10505", +"@ c #AB0404", +"# c #AF0202", +"$ c #B10303", +"% c #AE0202", +"& c #A90505", +"* c #A10606", +"= c #990E0E", +"- c #990D0D", +"; c #A80303", +"> c #BD0000", +", c #D40000", +"' c #DE0000", +") c #E20000", +"! c #E10000", +"~ c #DC0000", +"{ c #CD0000", +"] c #B80101", +"^ c #A40909", +"/ c #A20505", +"( c #BD0101", +"_ c #E00000", +": c #CB0000", +"< c #A80000", +"[ c #7E0101", +"} c #6B0202", +"| c #670202", +"1 c #720202", +"2 c #920000", +"3 c #B70000", +"4 c #D70000", +"5 c #B80303", +"6 c #9D0B0B", +"7 c #980909", +"8 c #B00202", +"9 c #DB0000", +"0 c #B50909", +"a c #8A1313", +"b c #1C0505", +"c c #070505", +"d c #080808", +"e c #0A0A0A", +"f c #090909", +"g c #070707", +"h c #0C0404", +"i c #5B1414", +"j c #A70F0F", +"k c #CC0303", +"l c #CC0000", +"m c #AB0505", +"n c #950D0D", +"o c #B60101", +"p c #DD0000", +"q c #CF0202", +"r c #9C1414", +"s c #141414", +"t c #0C0C0C", +"u c #0F0F0F", +"v c #111111", +"w c #101010", +"x c #0E0E0E", +"y c #B70707", +"z c #DF0000", +"A c #D50000", +"B c #A90606", +"C c #970E0E", +"D c #B30202", +"E c #C50505", +"F c #812121", +"G c #191919", +"H c #161616", +"I c #181818", +"J c #171717", +"K c #151515", +"L c #AF0A0A", +"M c #D70101", +"N c #D60000", +"O c #AD0606", +"P c #AA0303", +"Q c #DA0000", +"R c #C80505", +"S c #8E1E1E", +"T c #1D1D1D", +"U c #1F1F1F", +"V c #202020", +"W c #B00909", +"X c #CE0000", +"Y c #9F0A0A", +"Z c #9B0808", +"` c #D20000", +" . c #D80101", +".. c #8B1B1B", +"+. c #2D2D2D", +"@. c #272727", +"#. c #262626", +"$. c #252525", +"%. c #BB0606", +"&. c #BB0303", +"*. c #AF0303", +"=. c #AF0B0B", +"-. c #303030", +";. c #2B2B2B", +">. c #323232", +",. c #CD0202", +"'. c #A60A0A", +"). c #9E0909", +"!. c #CF0000", +"~. c #CE0404", +"{. c #292929", +"]. c #B10808", +"^. c #A70303", +"/. c #B60A0A", +"(. c #961010", +"_. c #B90000", +":. c #9C1212", +"<. c #1A1A1A", +"[. c #222222", +"}. c #1C1C1C", +"|. c #C20202", +"1. c #A30606", +"2. c #C60000", +"3. c #2A2A2A", +"4. c #3B3B3B", +"5. c #444444", +"6. c #434343", +"7. c #3A3A3A", +"8. c #1E1E1E", +"9. c #131313", +"0. c #BB0A0A", +"a. c #AD0707", +"b. c #9A1515", +"c. c #D30000", +"d. c #353535", +"e. c #484848", +"f. c #5F5F5F", +"g. c #6C6C6C", +"h. c #646464", +"i. c #565656", +"j. c #343434", +"k. c #212121", +"l. c #121212", +"m. c #B30505", +"n. c #B10404", +"o. c #9A1111", +"p. c #424242", +"q. c #555555", +"r. c #6B6B6B", +"s. c #757575", +"t. c #6E6E6E", +"u. c #606060", +"v. c #4E4E4E", +"w. c #3F3F3F", +"x. c #2C2C2C", +"y. c #B10909", +"z. c #B60707", +"A. c #9B1515", +"B. c #D00000", +"C. c #494949", +"D. c #595959", +"E. c #676767", +"F. c #696969", +"G. c #616161", +"H. c #525252", +"I. c #454545", +"J. c #B20707", +"K. c #363636", +"L. c #626262", +"M. c #5B5B5B", +"N. c #505050", +"O. c #282828", +"P. c #B80B0B", +"Q. c #AC0707", +"R. c #AA1111", +"S. c #0D0D0D", +"T. c #585858", +"U. c #545454", +"V. c #4B4B4B", +"W. c #BF0303", +"X. c #9F0B0B", +"Y. c #B90707", +"Z. c #242424", +"`. c #313131", +" + c #3E3E3E", +".+ c #474747", +"++ c #4D4D4D", +"@+ c #4F4F4F", +"#+ c #4C4C4C", +"$+ c #3D3D3D", +"%+ c #D90000", +"&+ c #8E1010", +"*+ c #A00808", +"=+ c #D00303", +"-+ c #3C3C3C", +";+ c #0B0B0B", +">+ c #AE0808", +",+ c #C10101", +"'+ c #BB0202", +")+ c #BA0A0A", +"!+ c #1B1B1B", +"~+ c #2F2F2F", +"{+ c #C10303", +"]+ c #A10A0A", +"^+ c #9F0707", +"/+ c #D80000", +"(+ c #BE0303", +"_+ c #821C1C", +":+ c #C20404", +"<+ c #232323", +"[+ c #A90C0C", +"}+ c #970C0C", +"|+ c #960E0E", +"1+ c #BE0101", +"2+ c #C60505", +"3+ c #131212", +"4+ c #B00B0B", +"5+ c #CE0101", +"6+ c #A80707", +"7+ c #990B0B", +"8+ c #C40404", +"9+ c #AD0909", +"0+ c #D00101", +"a+ c #B00505", +"b+ c #7B1C1C", +"c+ c #960C0C", +"d+ c #BC0202", +"e+ c #BC0808", +"f+ c #962525", +"g+ c #482E2E", +"h+ c #1E1616", +"i+ c #141010", +"j+ c #2F2020", +"k+ c #703636", +"l+ c #A81212", +"m+ c #A90707", +"n+ c #8E1313", +"o+ c #D30606", +"p+ c #B91E1E", +"q+ c #901919", +"r+ c #661010", +"s+ c #540F0F", +"t+ c #4F0D0D", +"u+ c #5A0F0F", +"v+ c #7A1515", +"w+ c #A21E1E", +"x+ c #C51212", +"y+ c #D60101", +"z+ c #980D0D", +"A+ c #930C0C", +"B+ c #B30404", +"C+ c #C60202", +"D+ c #7B1919", +"E+ c #8C1515", +"F+ c #9F0909", +"G+ c #B20303", +"H+ c #C30000", +"I+ c #CA0000", +"J+ c #C90000", +"K+ c #BC0101", +"L+ c #AA0505", +"M+ c #8F1111", +"N+ c #7F1E1E", +"O+ c #732626", +"P+ c #762A2A", +"Q+ c #6E2828", +" ", +" . + @ # $ % & * = ", +" - ; > , ' ) ) ! ) ! ~ { ] ^ ", +" / ( ' _ : < [ } | 1 2 3 4 ) , 5 6 ", +" 7 8 9 ' 0 a b c d e e f g h i j k ) l m ", +" n o p q r s t u v v w t x y z A B ", +" C D ) E F G H I G J K L M N O ", +" P Q R S T U U T V W ' X Y ", +" Z ` ... +.@.#.$. %.! &. ", +" *.) =. -.;.>. ,.4 '. ", +" ).!.~. {. ].) ( ", +" ^.z /. $. A ` (. ", +" _.! :. K K <.V [.}.K K |.z 1. ", +" 2.9 v <.3.4.5.6.7.;.8.9.J 0.) a. ", +" b.!.c. w }.d.e.f.g.h.i.5.j.k.l. m._ n. ", +" o.` !. u J 3.p.q.r.s.t.u.v.w.x.T w y._ z. ", +" A.B.` u k.j.C.D.E.g.F.G.H.I.j.$.9.l. m._ J. ", +" : 4 s v #.K.C.q.u.h.L.M.N.I.K.O.J x P.) Q. ", +" > z R. S.9.#.d.5.v.i.D.T.U.V.p.j.{.G t W._ X. ", +" % ) Y. l.9.Z.`. +.+++N.@+#+I.$+>.@.I t { %+&+ ", +" *+, =+ x w V ;.K.$+6.I.I.p.-+K.;.[.H ;+ >+z ,+ ", +" '+_ )+ x S.!+Z.~+d.7.-+-+7.d.~+#.8.9.;+ {+_ ]+ ", +" ^+/+, e s }.$.;.~+`.`.-.;.#.U J S.w z.' (+_+ ", +" ] ) :+ t S.K T [.@.O.O.@.<+U I v ;+ [+` ~ }+ ", +" |+1+) 2+ e t 9.I }.T 8.}.G K u e 3+ 4+5+' 6+ ", +" 7+l ) 8+ e ;+u 9.K K 9.v S.f w 9+0+) a+b+ ", +" c+d+) N e+f+g+h+;+f e f i+j+k+l+|.~ /+m+ ", +" n+D N ! o+p+q+r+s+t+u+v+w+x+y+! X z+ ", +" A+B+X _ ! ~ /+N %+' ) ~ C+Y D+ ", +" E+F+G+H+I+l J+K+L+M+N+ ", +" O+P+Q+ ", +" "}; diff --git a/applications/deprecated/old_arch/osmo4_wx/playlist.xpm b/applications/deprecated/old_arch/osmo4_wx/playlist.xpm new file mode 100644 index 0000000..cfad2dc --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/playlist.xpm @@ -0,0 +1,158 @@ +/* XPM */ +static const char* pl_open[] = { +"16 16 5 1", +" c #000000", +"! c #808000", +"# c #C0C0C0", +"$ c #FFFF00", +"% c #FFFFFF", +"################", +"################", +"######### ####", +"######## ### # #", +"############# #", +"# ######## #", +" %$% #####", +" $%$%$%$%$ #####", +" %$%$%$%$% #####", +" $%$% #", +" %$% !!!!!!!!! #", +" $% !!!!!!!!! ##", +" % !!!!!!!!! ###", +" !!!!!!!!! ####", +" #####", +"################"}; + +/* XPM */ +static const char* pl_save[] = { +"16 16 3 1", +" c #000000", +"! c #808000", +"# c #C0C0C0", +"################", +"# #", +"# ! ######## # #", +"# ! ######## #", +"# ! ######## ! #", +"# ! ######## ! #", +"# ! ######## ! #", +"# ! ######## ! #", +"# !! !! #", +"# !!!!!!!!!!!! #", +"# !! ! #", +"# !! ## ! #", +"# !! ## ! #", +"# !! ## ! #", +"## #", +"################"}; + +/* XPM */ +static const char* pl_add[] = { +"16 16 2 1", +" c #000000", +"! c #C0C0C0", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!! !!!!!!!", +"!!!!!! !!!!!!!", +"!!!!!! !!!!!!!", +"!!!!!! !!!!!!!", +"!! !!!", +"!! !!!", +"!! !!!", +"!!!!!! !!!!!!!", +"!!!!!! !!!!!!!", +"!!!!!! !!!!!!!", +"!!!!!! !!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!"}; + +/* XPM */ +static const char* pl_rem[] = { +"16 16 2 1", +" c #000000", +"! c #C0C0C0", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!! !!!", +"!! !!!", +"!! !!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!"}; + +/* XPM */ +static const char* pl_up[] = { +"16 16 2 1", +" c #000000", +"! c #C0C0C0", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!! !!!!!!!!", +"!!!!!! !!!!!!!", +"!!!!! !!!!!!", +"!!!! !!!!!", +"!!! !!!!", +"!!!!!! !!!!!!!", +"!!!!!! !!!!!!!", +"!!!!!! !!!!!!!", +"!!!!!! !!!!!!!", +"!!!!!! !!!!!!!", +"!!!!!! !!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!"}; + +/* XPM */ +static const char* pl_down[] = { +"16 16 2 1", +" c #000000", +"! c #C0C0C0", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!! !!!!!!!", +"!!!!!! !!!!!!!", +"!!!!!! !!!!!!!", +"!!!!!! !!!!!!!", +"!!!!!! !!!!!!!", +"!!!!!! !!!!!!!", +"!!! !!!!", +"!!!! !!!!!", +"!!!!! !!!!!!", +"!!!!!! !!!!!!!", +"!!!!!!! !!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!"}; + +/* XPM */ +static const char* pl_sort[] = { +"16 16 2 1", +" c #000000", +"! c #C0C0C0", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!! !!!!! !!!!!", +"!!!! !!!! !!!!", +"!!!! !!! !!!", +"!!!! !!!!! !!!!!", +"!!!! !!!!! !!!!!", +"!!!! !!!!! !!!!!", +"!!!! !!!!! !!!!!", +"!! !!! !!!!!", +"!!! !!!! !!!!!", +"!!!! !!!!! !!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!"}; + diff --git a/applications/deprecated/old_arch/osmo4_wx/resource.h b/applications/deprecated/old_arch/osmo4_wx/resource.h new file mode 100644 index 0000000..94a1520 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by wxOsmo4.rc +// +#define IDI_OSMO_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/applications/deprecated/old_arch/osmo4_wx/toolbar.xpm b/applications/deprecated/old_arch/osmo4_wx/toolbar.xpm new file mode 100644 index 0000000..887d59e --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/toolbar.xpm @@ -0,0 +1,254 @@ +/* XPM */ +static const char* tool_open_file[] = { +"16 16 2 1", +" c #000000", +"! c none", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!! !!!!!!!", +"!!!!!! !!!!!!", +"!!!!! !!!!!", +"!!!! !!!!", +"!!! !!!", +"!! !!", +"!! !!", +"!!!!!!!!!!!!!!!!", +"!! !!", +"!! !!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!"}; + +/* XPM */ +static const char* tool_prev[] = { +"16 16 3 1", +" c #000000", +"! c none", +"# c #FF0000", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!! !!", +"!!!!!!!!!! !!", +"!!!!!!!! ## !!", +"!!!!!! #### !!", +"!!!! ###### !!", +"!!! ######## !!", +"!!!! ##### !!", +"!!!!!! ### !!", +"!!!!!!!! # !!", +"!!!!!!!!!! !!", +"!!!!!!!!!!!! !!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!"}; + +/* XPM */ +static const char* tool_next[] = { +"16 16 3 1", +" c #000000", +"! c none", +"# c #FF0000", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!! !!!!!!!!!!!!", +"!! !!!!!!!!!!", +"!! ## !!!!!!!!", +"!! #### !!!!!!", +"!! ###### !!!!", +"!! ######## !!!", +"!! ###### !!!!", +"!! #### !!!!!!", +"!! ## !!!!!!!!", +"!! !!!!!!!!!!", +"!! !!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!"}; + +/* XPM */ +static const char* tool_play[] = { +"16 16 2 1", +" c #000000", +"! c none", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!! !!!!!!!!!!!!", +"!! !!!!!!!!!!", +"!! !!!!!!!!", +"!! !!!!!!", +"!! !!!!", +"!! !!", +"!! !!!!", +"!! !!!!!!", +"!! !!!!!!!!", +"!! !!!!!!!!!!", +"!! !!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!"}; + +/* XPM */ +static const char* tool_pause[] = { +"16 16 3 1", +" c #000000", +"! c #808080", +"# c none", +"################", +"################", +"################", +"### !##! ###", +"### !##! ###", +"### !##! ###", +"### !##! ###", +"### !##! ###", +"### !##! ###", +"### !##! ###", +"### !##! ###", +"### !##! ###", +"### !##! ###", +"### !##! ###", +"################", +"################"}; + + +/* XPM */ +static const char* tool_step[] = { +"16 16 2 1", +" c #000000", +"! c none", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!! !!!!!!!!!!!", +"!! !!!!!!!!!", +"!! !!!!!!!", +"!!!! !!!!!", +"!!!!!! !!!", +"!!!!!!!! !!", +"!!!!!! !!!", +"!!!! !!!!!", +"!! !!!!!!!", +"!! !!!!!!!!!", +"!! !!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!"}; + + +/* XPM */ +static const char* tool_stop[] = { +"16 16 2 1", +" c #000000", +"! c none", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!", +"!! !!", +"!! !!", +"!! !!", +"!! !!", +"!! !!", +"!! !!", +"!! !!", +"!! !!", +"!! !!", +"!! !!", +"!! !!", +"!!!!!!!!!!!!!!!!", +"!!!!!!!!!!!!!!!!"}; + +/* XPM */ +static const char* tool_info[] = { +"16 16 3 1", +" c #000000", +"! c none", +"# c #0000FF", +"!!!!!!!!!!!!!!!!", +"!!!!! !!!!!", +"!!! !!!", +"!! ###### !!", +"! ####!!#### !", +"! ####!!#### !", +" ############ ", +" #####!!##### ", +" ######!!##### ", +" #####!!##### ", +" #####!!##### ", +"! ####!!#### !", +"! #####!!### !", +"!! ###### !!", +"!!! !!!", +"!!!!! !!!!!"}; + + +/* XPM */ +static const char* tool_config[] = { +"16 16 3 1", +" c #000000", +"! c #808080", +"# c none", +"################", +"################", +"## ## ######", +"## ## # ## # ##", +"## ## #", +"## #!# ####### #", +"## # #!# ## ## #", +"## #!# #!# ! # #", +"## # #!# ## ## #", +"## #!# #!##### #", +"## # #!# ## ## #", +"## #!# #!# ! # #", +"## # #!# ## ## #", +"## #", +"################", +"################"}; + +/* XPM */ +static const char* tool_sw_2d[] = { +"16 16 4 1", +" c #FF0000", +". c #C0C0C0", +"+ c #0000FF", +"@ c #000000", +" .............. ", +". ..++..++++.. .", +".. +..+..+..+ ..", +"... ..+..+.. ...", +".... +...+. +...", +"...+. +..+ .+...", +"...+++ .+ ++....", +"....... .......", +"....@@ @@ @.....", +"...@. .@.@ .....", +"...@ ....@. ....", +"... ..@@.@.. ...", +".. @...@.@.@. ..", +". ..@@@@@@@@.. .", +" .............. ", +"................"}; + +/* XPM */ +static const char* tool_sw_3d[] = { +"16 16 4 1", +" c #FFFFFF", +". c #C0C0C0", +"+ c #0000FF", +"@ c #000000", +" ", +"................", +"....++..++++....", +"...+..+..+..+...", +"......+..+..+...", +"....++...+..+...", +"...+..+..+..+...", +"...++++.++++....", +"................", +"....@@@@@@@.....", +"...@...@.@......", +"...@.....@......", +"...@..@@.@......", +"...@...@.@.@....", +"....@@@@@@@@....", +"................"}; + diff --git a/applications/deprecated/old_arch/osmo4_wx/wxGPACControl.cpp b/applications/deprecated/old_arch/osmo4_wx/wxGPACControl.cpp new file mode 100644 index 0000000..e422e4d --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/wxGPACControl.cpp @@ -0,0 +1,1051 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / Osmo4 wxWidgets GUI + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + */ + +#include "wxOsmo4.h" +#include +#include +#include +#include +#include +#include + +#include + +#include "wxGPACControl.h" + + +#define NUM_RATES 11 +static const char *BIFSRates[11] = +{ + "5.0", + "7.5", + "10.0", + "12.5", + "15.0", + "24.0", + "25.0", + "30.0", + "50.0", + "60.0", + "100.0" +}; + +void wxGPACControl::SetYUVLabel() +{ + u32 yuv_format = gf_term_get_option(m_pApp->m_term, GF_OPT_YUV_FORMAT); + if (!yuv_format) { + m_yuvtxt->SetLabel(wxT("(No YUV used)")); + } else { + char str[100]; + sprintf(str, "(%s used)", gf_4cc_to_str(yuv_format)); + m_yuvtxt->SetLabel(wxString(str, wxConvUTF8) ); + } +} + +wxGPACControl::wxGPACControl(wxWindow *parent) + : wxDialog(parent, -1, wxString(wxT("GPAC Control Panel"))) +{ + const char *sOpt; + SetSize(320, 240); + u32 i; + wxBoxSizer *bs; + Centre(); + + m_pApp = (wxOsmo4Frame *)parent; + + s_main = new wxBoxSizer(wxVERTICAL); + + s_header = new wxBoxSizer(wxHORIZONTAL); + //s_header->Add(new wxStaticText(this, 0, wxT("Category"), wxDefaultPosition, wxSize(60, 20)), wxALIGN_CENTER); + m_select = new wxComboBox(this, ID_SELECT, wxT(""), wxDefaultPosition, wxSize(120, 30), 0, NULL, wxCB_READONLY); + s_header->Add(m_select, 2, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_header->Add( new wxButton(this, ID_APPLY, wxT("Apply"), wxDefaultPosition, +#ifdef WIN32 + wxSize(40, 20) +#else + wxSize(40, 30) +#endif + ), + 1, wxALIGN_TOP|wxALIGN_RIGHT|wxADJUST_MINSIZE); + s_main->Add(s_header, 0, wxEXPAND, 0); + + /*general section*/ + s_general = new wxBoxSizer(wxVERTICAL); + m_loop = new wxCheckBox(this, 0, wxT("Loop at End"), wxPoint(10, 40), wxSize(140, 20)); + s_general->Add(m_loop); + m_lookforsubs = new wxCheckBox(this, 0, wxT("Look for Subtitles"), wxPoint(180, 40), wxSize(140, 20)); + s_general->Add(m_lookforsubs); + m_noconsole = new wxCheckBox(this, 0, wxT("Disable console messages"), wxPoint(10, 80), wxSize(180, 20)); + s_general->Add(m_noconsole); + m_viewxmt = new wxCheckBox(this, 0, wxT("View graph in XMT-A format"), wxPoint(10, 120), wxSize(180, 20)); + s_general->Add(m_viewxmt); + s_main->Add(s_general, 0, wxEXPAND, 0); + + /*MPEG-4 systems*/ + s_mpeg4 = new wxBoxSizer(wxVERTICAL); + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Prefered Stream Language")), wxALIGN_CENTER | wxADJUST_MINSIZE); + m_lang = new wxComboBox(this, 0, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + bs->Add(m_lang, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_mpeg4->Add(bs, 0, wxALL|wxEXPAND, 2); + + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Decoder Threading Mode")), wxALIGN_CENTER | wxADJUST_MINSIZE); + m_thread = new wxComboBox(this, 0, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + bs->Add(m_thread, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_mpeg4->Add(bs, 0, wxALL|wxEXPAND, 2); + m_bifsalwaysdrawn = new wxCheckBox(this, 0, wxT("Always draw late BIFS frames")); + s_mpeg4->Add(m_bifsalwaysdrawn); + m_singletime = new wxCheckBox(this, 0, wxT("Force Single Timeline")); + s_mpeg4->Add(m_singletime); + s_main->Add(s_mpeg4, 0, wxEXPAND, 0); + + /*media decoders*/ + s_mdec = new wxBoxSizer(wxVERTICAL); + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Prefered Audio Output")), wxALIGN_CENTER | wxADJUST_MINSIZE); + m_decaudio = new wxComboBox(this, 0, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + bs->Add(m_decaudio, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_mdec->Add(bs, 0, wxALL|wxEXPAND, 2); + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Prefered Video Output")), wxALIGN_CENTER | wxADJUST_MINSIZE); + m_decvideo = new wxComboBox(this, 0, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + bs->Add(m_decvideo, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_mdec->Add(bs, 0, wxALL|wxEXPAND, 2); + s_main->Add(s_mdec, 0, wxEXPAND, 0); + + /*Rendering*/ + s_rend = new wxBoxSizer(wxVERTICAL); + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Target Frame Rate")), wxALIGN_CENTER | wxADJUST_MINSIZE); + m_fps = new wxComboBox(this, 0, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + bs->Add(m_fps, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_rend->Add(bs, 0, wxALL|wxEXPAND, 2); + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Anti-Aliasing")), wxALIGN_CENTER | wxADJUST_MINSIZE); + m_aa = new wxComboBox(this, 0, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + bs->Add(m_aa, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_rend->Add(bs, 0, wxALL|wxEXPAND, 2); + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Graphics Driver")), wxALIGN_CENTER | wxADJUST_MINSIZE); + m_graph = new wxComboBox(this, 0, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + bs->Add(m_graph, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_rend->Add(bs, 0, wxALL|wxEXPAND, 2); + bs = new wxBoxSizer(wxHORIZONTAL); + m_draw_bounds = new wxComboBox(this, 0, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + bs->Add(new wxStaticText(this, 0, wxT("Bounds")), wxALIGN_CENTER | wxADJUST_MINSIZE | wxALIGN_RIGHT); + bs->Add(m_draw_bounds, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_rend->Add(bs, 0, wxALL|wxEXPAND, 2); + m_fast = new wxCheckBox(this, 0, wxT("Fast Rendering")); + m_force_size = new wxCheckBox(this, 0, wxT("Force Scene Size")); + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(m_fast, wxALIGN_CENTER | wxADJUST_MINSIZE); + bs->Add(m_force_size, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_rend->Add(bs, 0, wxALL|wxEXPAND, 2); + m_use3D = new wxCheckBox(this, 0, wxT("Use 3D Renderer")); + s_rend->Add(m_use3D, 0, wxALL|wxEXPAND, 2); + m_bWas3D = m_use3D->GetValue(); + s_main->Add(s_rend, 0, wxEXPAND, 0); + + /*Render 2D*/ + s_rend2d = new wxBoxSizer(wxVERTICAL); + m_direct = new wxCheckBox(this, 0, wxT("Direct Rendering")); + s_rend2d->Add(m_direct, 0, wxALL|wxEXPAND, 2); + m_scalable = new wxCheckBox(this, 0, wxT("Scalable Zoom")); + s_rend2d->Add(m_scalable, 0, wxALL|wxEXPAND, 2); + bs = new wxBoxSizer(wxHORIZONTAL); + m_noyuv = new wxCheckBox(this, 0, wxT("Disable YUV hardware")); + bs->Add(m_noyuv, wxALIGN_CENTER | wxADJUST_MINSIZE); + m_yuvtxt = new wxStaticText(this, 0, wxT("(No YUV used)"), wxDefaultPosition, wxSize(60, 20), wxALIGN_LEFT); + bs->Add(m_yuvtxt, wxALIGN_CENTER|wxADJUST_MINSIZE); + s_rend2d->Add(bs, 0, wxALL|wxEXPAND, 2); + s_main->Add(s_rend2d, 0, wxEXPAND, 0); + + /*Render 3D*/ + s_rend3d = new wxBoxSizer(wxVERTICAL); + m_raster_outlines = new wxCheckBox(this, 0, wxT("Use OpenGL Raster outlines")); + s_rend3d->Add(m_raster_outlines, 0, wxALL|wxEXPAND, 2); + m_polyaa = new wxCheckBox(this, 0, wxT("Enable polygon anti-aliasing")); + s_rend3d->Add(m_polyaa, 0, wxALL|wxEXPAND, 2); + m_nobackcull = new wxCheckBox(this, 0, wxT("Disable backface culling")); + s_rend3d->Add(m_nobackcull, 0, wxALL|wxEXPAND, 2); + + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Wireframe mode")), wxALIGN_CENTER | wxADJUST_MINSIZE); + m_wire = new wxComboBox(this, 0, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + bs->Add(m_wire, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_rend3d->Add(bs, 0, wxALL|wxEXPAND, 2); + + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Draw Normals")), wxALIGN_CENTER | wxADJUST_MINSIZE); + m_normals = new wxComboBox(this, 0, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + bs->Add(m_normals, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_rend3d->Add(bs, 0, wxALL|wxEXPAND, 2); + + m_emulpow2 = new wxCheckBox(this, 0, wxT("Emulate power-of-two textures for video")); + s_rend3d->Add(m_emulpow2, 0, wxALL|wxEXPAND, 2); + m_norectext = new wxCheckBox(this, 0, wxT("Disable rectangular texture extensions")); + s_rend3d->Add(m_norectext, 0, wxALL|wxEXPAND, 2); + m_copypixels = new wxCheckBox(this, 0, wxT("Bitmap node uses direct pixel copy")); + s_rend3d->Add(m_copypixels, 0, wxALL|wxEXPAND, 2); + s_main->Add(s_rend3d, 0, wxEXPAND, 0); + + /*video*/ + s_video = new wxBoxSizer(wxVERTICAL); + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Video Driver")), wxALIGN_CENTER | wxADJUST_MINSIZE | wxALIGN_RIGHT); + m_video = new wxComboBox(this, 0, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + bs->Add(m_video , wxALIGN_CENTER | wxADJUST_MINSIZE); + s_video->Add(bs, 0, wxALL|wxEXPAND, 2); + m_switchres = new wxCheckBox(this, 0, wxT("Change video resolution in fullscreen")); + s_video->Add(m_switchres, 0, wxALL|wxEXPAND, 2); + m_usehwmem = new wxCheckBox(this, 0, wxT("Use hardware memory in 2D mode")); + s_video->Add(m_usehwmem, 0, wxALL|wxEXPAND, 2); + s_main->Add(s_video, 0, wxEXPAND, 0); + + + /*audio*/ + s_audio = new wxBoxSizer(wxVERTICAL); + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Audio Driver")), wxALIGN_CENTER | wxADJUST_MINSIZE); + m_audio = new wxComboBox(this, ID_AUDIO_DRIVER, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + bs->Add(m_audio, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_audio->Add(bs, 0, wxALL|wxEXPAND, 2); + m_forcecfg = new wxCheckBox(this, ID_FORCE_AUDIO, wxT("Force Audio Config")); + m_forcecfg->SetValue(1); + s_audio->Add(m_forcecfg, 0, wxALL|wxEXPAND, 2); + + + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Number of buffers")), wxALIGN_CENTER|wxADJUST_MINSIZE); + m_nbbuf = new wxSpinCtrl(this, -1, wxT(""), wxDefaultPosition, wxSize(20, 20), wxSP_WRAP | wxSP_ARROW_KEYS, 1, 30, 15); + m_nbbuf->SetValue(8); + bs->Add(m_nbbuf, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_audio->Add(bs, 0, wxALL|wxEXPAND, 2); + + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Total length in ms")), wxALIGN_CENTER | wxADJUST_MINSIZE); + m_buflen = new wxSpinCtrl(this, -1, wxT(""), wxDefaultPosition, wxSize(20, 20), wxSP_WRAP | wxSP_ARROW_KEYS, 1, 1000); + m_buflen->SetValue(400); + bs->Add(m_buflen, wxALIGN_CENTER | wxADJUST_MINSIZE|wxLEFT,10); + s_audio->Add(bs, 0, wxALL|wxEXPAND, 2); + + m_noresync = new wxCheckBox(this, -1, wxT("Disable Resynchronization")); + s_audio->Add(m_noresync); + m_nomulitch = new wxCheckBox(this, -1, wxT("Disable Multichannel")); + s_audio->Add(m_nomulitch); +#ifdef WIN32 + m_notifs = new wxCheckBox(this, -1, wxT("Disable DirectSound Notifications")); + s_audio->Add(m_notifs); +#endif + s_main->Add(s_audio, 0, wxEXPAND, 0); + + /*font*/ + s_font = new wxBoxSizer(wxVERTICAL); + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Font Engine")), wxALIGN_CENTER | wxADJUST_MINSIZE | wxALIGN_RIGHT); + m_font = new wxComboBox(this, 0, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + bs->Add(m_font, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_font->Add(bs, 0, wxALL|wxEXPAND, 2); + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("System Font Directory")), wxALIGN_CENTER | wxADJUST_MINSIZE | wxALIGN_RIGHT); + m_fontdir = new wxButton(this, ID_FONT_DIR, wxT("..."), wxDefaultPosition, wxDefaultSize); + bs->Add(m_fontdir, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_font->Add(bs, 0, wxALL|wxEXPAND, 2); + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Text Texturing Mode")), wxALIGN_CENTER | wxADJUST_MINSIZE | wxALIGN_RIGHT); + m_texturemode = new wxComboBox(this, 0, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + bs->Add(m_texturemode, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_font->Add(bs, 0, wxALL|wxEXPAND, 2); + s_main->Add(s_font, 0, wxEXPAND, 0); + + /*download*/ + s_dnld = new wxBoxSizer(wxVERTICAL); + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Cache Directory")), wxALIGN_CENTER | wxADJUST_MINSIZE | wxALIGN_RIGHT); + m_cachedir = new wxButton(this, ID_CACHE_DIR, wxT("...")); + bs->Add(m_cachedir, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_dnld->Add(bs, 0, wxALL|wxEXPAND, 2); + m_cleancache = new wxCheckBox(this, -1, wxT("Remove temp files on exit")); + s_dnld->Add(m_cleancache); + m_restartcache = new wxCheckBox(this, -1, wxT("Always redownload incomplete cached files")); + s_dnld->Add(m_restartcache); + bs = new wxBoxSizer(wxHORIZONTAL); + m_progressive = new wxCheckBox(this, ID_PROGRESSIVE, wxT("XML progressive load")); + bs->Add(m_progressive, wxALIGN_CENTER | wxADJUST_MINSIZE); + m_sax_duration = new wxTextCtrl(this, 0, wxT(""), wxPoint(10, 120), wxSize(60, 20)); + bs->Add(m_sax_duration, wxALIGN_CENTER | wxADJUST_MINSIZE); + bs->Add(new wxStaticText(this, 0, wxT("max load slice (ms)")), wxADJUST_MINSIZE | wxALIGN_CENTER); + s_dnld->Add(bs, 0, wxALL|wxEXPAND, 2); + bs = new wxBoxSizer(wxHORIZONTAL); + m_use_proxy = new wxCheckBox(this, ID_USE_PROXY, wxT("Use proxy")); + bs->Add(m_use_proxy, wxALIGN_CENTER | wxADJUST_MINSIZE); + m_proxy_name = new wxTextCtrl(this, 0, wxT(""), wxPoint(10, 120), wxSize(60, 20)); + bs->Add(m_proxy_name, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_dnld->Add(bs, 0, wxALL|wxEXPAND, 2); + s_main->Add(s_dnld, 0, wxEXPAND, 0); + + /*streaming*/ + s_stream = new wxBoxSizer(wxVERTICAL); + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Default RTSP port")), wxALIGN_CENTER | wxADJUST_MINSIZE | wxALIGN_RIGHT); + m_port = new wxComboBox(this, ID_RTSP_PORT, wxT(""), wxPoint(160, 40), wxSize(140, 30), 0, NULL, wxCB_READONLY); + bs->Add(m_port, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_stream->Add(bs, 0, wxALL|wxEXPAND, 2); + m_rtsp = new wxCheckBox(this, ID_RTP_OVER_RTSP, wxT("RTP over RTSP"), wxPoint(10, 80), wxSize(140, 20)); + m_reorder = new wxCheckBox(this, -1, wxT("use RTP reordering"), wxPoint(160, 80), wxSize(130, 20)); + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(m_rtsp, wxALIGN_CENTER | wxADJUST_MINSIZE); + bs->Add(m_reorder, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_stream->Add(bs, 0, wxALL|wxEXPAND, 2); + bs = new wxBoxSizer(wxHORIZONTAL); + m_timeout = new wxTextCtrl(this, 0, wxT(""), wxPoint(10, 120), wxSize(60, 20)); + bs->Add(new wxStaticText(this, 0, wxT("Control Timeout (ms): ")), wxALIGN_CENTER | wxADJUST_MINSIZE | wxALIGN_RIGHT); + bs->Add(m_timeout, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_stream->Add(bs, 0, wxALL|wxEXPAND, 2); + bs = new wxBoxSizer(wxHORIZONTAL); + m_buffer = new wxTextCtrl(this, 0, wxT(""), wxPoint(10, 150), wxSize(60, 20)); + bs->Add(new wxStaticText(this, 0, wxT("Media Buffering (ms): ")), wxALIGN_CENTER | wxADJUST_MINSIZE | wxALIGN_RIGHT); + bs->Add(m_buffer, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_stream->Add(bs, 0, wxALL|wxEXPAND, 2); + bs = new wxBoxSizer(wxHORIZONTAL); + m_dorebuffer = new wxCheckBox(this, ID_RTSP_REBUFFER, wxT("Rebuffer if below")); + bs->Add(m_dorebuffer, wxALIGN_CENTER | wxADJUST_MINSIZE); + m_rebuffer = new wxTextCtrl(this, 0, wxT(""), wxPoint(200, 180), wxSize(60, 20)); + bs->Add(m_rebuffer, wxALIGN_CENTER | wxADJUST_MINSIZE); + m_rebuffer->Disable(); + s_stream->Add(bs, 0, wxALL|wxEXPAND, 2); + s_main->Add(s_stream, 0, wxEXPAND, 0); + + /*streaming cache*/ + s_rec = new wxBoxSizer(wxVERTICAL); + bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(new wxStaticText(this, 0, wxT("Record To: ")), wxALIGN_CENTER | wxADJUST_MINSIZE | wxALIGN_RIGHT); + m_recdir = new wxButton(this, ID_RECORD_DIR, wxT("...")); + bs->Add(m_recdir, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_rec->Add(bs, 0, wxALL|wxEXPAND, 2); + m_overwrite = new wxCheckBox(this, -1, wxT("Overwrite existing files")); + s_rec->Add(m_overwrite); + bs = new wxBoxSizer(wxHORIZONTAL); + m_usename = new wxCheckBox(this, ID_USE_FILENAME, wxT("Use filename")); + m_recfile = new wxTextCtrl(this, 0, wxT("")); + bs->Add(m_usename, wxALIGN_CENTER | wxADJUST_MINSIZE); + bs->Add(m_recfile, wxALIGN_CENTER | wxADJUST_MINSIZE); + s_rec->Add(bs, 0, wxALL|wxEXPAND, 2); + s_main->Add(s_rec, 0, wxEXPAND, 0); + + /*load options*/ + GF_Config *cfg = m_pApp->m_user.config; + /*general*/ + sOpt = gf_cfg_get_key(cfg, "General", "Loop"); + m_loop->SetValue((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "General", "LookForSubtitles"); + m_lookforsubs->SetValue((sOpt && !stricmp(sOpt, "no")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "General", "ConsoleOff"); + m_noconsole->SetValue((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "General", "ViewXMT"); + m_viewxmt->SetValue((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + /*lang config*/ + u32 count = gf_lang_get_count(); + for (i=0; iAppend(wxString(gf_lang_get_name(i), wxConvUTF8) ); + } + + sOpt = gf_cfg_get_key(cfg, "Systems", "Language3CC"); + if (!sOpt) sOpt = "eng"; + s32 select = gf_lang_find(sOpt); + if (select<0) select = 0; + m_lang->SetSelection(select); + + /*systems config*/ + sOpt = gf_cfg_get_key(cfg, "Systems", "ThreadingPolicy"); + select = 0; + m_thread->Append(wxT("Single Thread")); + m_thread->Append(wxT("Mutli Thread")); + if (sOpt && !stricmp(sOpt, "Multi")) select = 1; + m_thread->Append(wxT("Free")); + if (sOpt && !stricmp(sOpt, "Free")) select = 2; + m_thread->SetSelection(select); + sOpt = gf_cfg_get_key(cfg, "Systems", "ForceSingleClock"); + m_singletime->SetValue((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "Systems", "AlwaysDrawBIFS"); + m_bifsalwaysdrawn->SetValue((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + + /*audio dec enum*/ + sOpt = gf_cfg_get_key(cfg, "Systems", "DefAudioDec"); + u32 count = gf_modules_get_count(m_pApp->m_user.modules); + GF_BaseDecoder *ifc_d; + select = 0; + s32 to_sel = 0; + for (i=0; im_user.modules, i, GF_MEDIA_DECODER_INTERFACE); + if (!ifc_d) continue; + if (ifc_d->CanHandleStream(ifc_d, GF_STREAM_AUDIO, NULL, 0)) { + if (sOpt && !stricmp(ifc_d->module_name, sOpt)) select = to_sel; + m_decaudio->Append(wxString(ifc_d->module_name, wxConvUTF8) ); + to_sel++; + } + gf_modules_close_interface((GF_BaseInterface *) ifc_d); + } + m_decaudio->SetSelection(select); + + /*video dec enum*/ + sOpt = gf_cfg_get_key(cfg, "Systems", "DefVideoDec"); + select = to_sel = 0; + for (i=0; im_user.modules, i, GF_MEDIA_DECODER_INTERFACE); + if (!ifc_d) continue; + if (ifc_d->CanHandleStream(ifc_d, GF_STREAM_VISUAL, NULL, 0)) { + if (sOpt && !stricmp(ifc_d->module_name, sOpt)) select = to_sel; + m_decvideo->Append(wxString(ifc_d->module_name, wxConvUTF8) ); + to_sel++; + } + gf_modules_close_interface((GF_BaseInterface *) ifc_d); + } + m_decvideo->SetSelection(select); + + /*rendering FIXME*/ + m_bWas3D = 0; + m_use3D->SetValue(m_bWas3D ? 1 : 0); + + sOpt = gf_cfg_get_key(cfg, "Compositor", "ForceSceneSize"); + m_force_size->SetValue( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + sOpt = gf_cfg_get_key(cfg, "Compositor", "FrameRate"); + if (!sOpt) sOpt = "30.0"; + select = 0; + for (i = 0; iAppend(wxString(BIFSRates[i], wxConvUTF8) ); + if (sOpt && !stricmp(sOpt, BIFSRates[i]) ) select = i; + } + m_fps->SetSelection(select); + + sOpt = gf_cfg_get_key(cfg, "Compositor", "HighSpeed"); + m_fast->SetValue( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + sOpt = gf_cfg_get_key(cfg, "Compositor", "AntiAlias"); + m_aa->Append(wxT("None")); + m_aa->Append(wxT("Text only")); + m_aa->Append(wxT("Complete")); + select = 2; + if (sOpt && !stricmp(sOpt, "Text")) select = 1; + else if (sOpt && !stricmp(sOpt, "None")) select = 0; + m_aa->SetSelection(select); + + sOpt = gf_cfg_get_key(cfg, "Compositor", "BoundingVolume"); + m_draw_bounds->Append(wxT("None")); + m_draw_bounds->Append(wxT("Box/Rect")); + m_draw_bounds->Append(wxT("AABB Tree")); + select = 0; + if (sOpt && !stricmp(sOpt, "Box")) select = 1; + else if (sOpt && !stricmp(sOpt, "AABB")) select = 2; + m_draw_bounds->SetSelection(select); + + /*render2d*/ + sOpt = gf_cfg_get_key(cfg, "Compositor", "DirectDraw"); + m_direct->SetValue( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "Compositor", "ScalableZoom"); + m_scalable->SetValue( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "Compositor", "DisableYUV"); + m_noyuv->SetValue( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + SetYUVLabel(); + + /*graphics driver enum*/ + sOpt = gf_cfg_get_key(cfg, "core", "raster2d"); + GF_BaseInterface *ifce; + select = to_sel = 0; + for (i=0; im_user.modules, i, GF_RASTER_2D_INTERFACE); + if (!ifce) continue; + if (sOpt && !stricmp(((GF_BaseInterface *)ifce)->module_name, sOpt)) select = to_sel; + m_graph->Append(wxString(((GF_BaseInterface *)ifce)->module_name, wxConvUTF8) ); + gf_modules_close_interface(ifce); + to_sel++; + } + m_graph->SetSelection(select); + + /*render3d*/ + sOpt = gf_cfg_get_key(cfg, "Compositor", "RasterOutlines"); + m_raster_outlines->SetValue( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "Compositor", "EmulatePOW2"); + m_emulpow2->SetValue((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "Compositor", "PolygonAA"); + m_polyaa->SetValue((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "Compositor", "BackFaceCulling"); + m_nobackcull->SetValue((sOpt && !stricmp(sOpt, "Off")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "Compositor", "Wireframe"); + sOpt = gf_cfg_get_key(cfg, "Compositor", "BitmapCopyPixels"); + m_copypixels->SetValue((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "Compositor", "DisableRectExt"); + m_norectext->SetValue((sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + m_wire->Append(wxT("No Wireframe")); + m_wire->Append(wxT("Wireframe Only")); + m_wire->Append(wxT("Solid and Wireframe")); + sOpt = gf_cfg_get_key(cfg, "Compositor", "Wireframe"); + if (sOpt && !stricmp(sOpt, "WireOnly")) m_wire->SetSelection(1); + else if (sOpt && !stricmp(sOpt, "WireOnSolid")) m_wire->SetSelection(2); + else m_wire->SetSelection(0); + m_normals->Append(wxT("Never")); + m_normals->Append(wxT("Per Face")); + m_normals->Append(wxT("Per Vertex")); + sOpt = gf_cfg_get_key(cfg, "Compositor", "DrawNormals"); + if (sOpt && !stricmp(sOpt, "PerFace")) m_normals->SetSelection(1); + else if (sOpt && !stricmp(sOpt, "PerVertex")) m_normals->SetSelection(2); + else m_normals->SetSelection(0); + + /*video*/ + m_switchres->SetValue( gf_cfg_get_bool(cfg, "core", "switch-vres") ); + sOpt = gf_cfg_get_key(cfg, "core", "hwvmem"); + m_usehwmem->SetValue( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "core", "video-output"); + select = to_sel = 0; + for (i=0; im_user.modules, i, GF_VIDEO_OUTPUT_INTERFACE); + if (!ifce) continue; + if (sOpt && !stricmp(((GF_BaseInterface *)ifce)->module_name, sOpt)) select = to_sel; + m_video->Append(wxString(((GF_BaseInterface *)ifce)->module_name, wxConvUTF8) ); + gf_modules_close_interface(ifce); + to_sel++; + } + m_video->SetSelection(select); + + /*audio*/ + sOpt = gf_cfg_get_key(cfg, "Audio", "ForceConfig"); + m_forcecfg->SetValue( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "Audio", "NumBuffers"); + m_nbbuf->SetValue( sOpt ? wxString(sOpt, wxConvUTF8) : wxT("2")); + sOpt = gf_cfg_get_key(cfg, "Audio", "TotalDuration"); + m_buflen->SetValue( sOpt ? wxString(sOpt, wxConvUTF8) : wxT("120")); + wxCommandEvent event; + ForceAudio(event); + sOpt = gf_cfg_get_key(cfg, "Audio", "NoResync"); + m_noresync->SetValue( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "Audio", "DisableMultiChannel"); + m_nomulitch->SetValue( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + + /*driver enum*/ + sOpt = gf_cfg_get_key(cfg, "core", "audio-output"); + select = to_sel = 0; + for (i=0; im_user.modules, i, GF_AUDIO_OUTPUT_INTERFACE); + if (!ifce) continue; + if (sOpt && !stricmp(((GF_BaseInterface *)ifce)->module_name, sOpt)) select = to_sel; + m_audio->Append(wxString(((GF_BaseInterface *)ifce)->module_name, wxConvUTF8) ); + gf_modules_close_interface(ifce); + to_sel++; + } + m_audio->SetSelection(select); +#ifdef WIN32 + sOpt = gf_cfg_get_key(cfg, "Audio", "DisableNotification"); + m_notifs->SetValue( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + wxCommandEvent audevt; + OnSetAudioDriver(audevt); +#endif + + /*font*/ + sOpt = gf_cfg_get_key(cfg, "FontCache", "FontReader"); + to_sel = select = 0; + for (i=0; im_user.modules, i, GF_FONT_READER_INTERFACE); + if (!ifce) continue; + if (sOpt && !stricmp(((GF_BaseInterface *)ifce)->module_name, sOpt)) select = to_sel; + m_font->Append(wxString(((GF_BaseInterface *)ifce)->module_name, wxConvUTF8) ); + gf_modules_close_interface(ifce); + to_sel++; + } + m_font->SetSelection(select); + sOpt = gf_cfg_get_key(cfg, "FontCache", "FontDirectory"); + if (sOpt) m_fontdir->SetLabel(wxString(sOpt, wxConvUTF8) ); + sOpt = gf_cfg_get_key(cfg, "Compositor", "TextureTextMode"); + m_texturemode->Append(wxT("Default")); + m_texturemode->Append(wxT("Never")); + m_texturemode->Append(wxT("Always")); + if (sOpt && !stricmp(sOpt, "Always")) m_texturemode->SetSelection(2); + else if (sOpt && !stricmp(sOpt, "3D")) m_texturemode->SetSelection(1); + else m_texturemode->SetSelection(0); + + /*downloader*/ + sOpt = gf_cfg_get_key(cfg, "Core", "CacheDirectory"); + if (sOpt) m_cachedir->SetLabel(wxString(sOpt, wxConvUTF8) ); + sOpt = gf_cfg_get_key(cfg, "Core", "CleanCache"); + m_cleancache->SetValue( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "Core", "RestartFiles"); + m_restartcache->SetValue( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "SAXLoader", "Progressive"); + m_progressive->SetValue( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + sOpt = gf_cfg_get_key(cfg, "SAXLoader", "MaxDuration"); + m_sax_duration->SetValue(sOpt ? wxString(sOpt, wxConvUTF8) : wxT("30")); + if (! m_progressive->GetValue()) m_sax_duration->Enable(0); + + sOpt = gf_cfg_get_key(cfg, "Core", "HTTPProxyEnabled"); + m_use_proxy->SetValue( (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0); + char szProxy[GF_MAX_PATH]; + strcpy(szProxy, ""); + sOpt = gf_cfg_get_key(cfg, "Core", "HTTPProxyName"); + if (sOpt) { + strcat(szProxy, sOpt); + sOpt = gf_cfg_get_key(cfg, "Core", "HTTPProxyPort"); + if (sOpt) { + strcat(szProxy, ":"); + strcat(szProxy, sOpt); + } + } else { + m_use_proxy->SetValue(0); + } + m_proxy_name->SetValue( wxString((char *)szProxy, wxConvUTF8) ); + if (! m_use_proxy->GetValue()) m_proxy_name->Enable(0); + + /*streaming*/ + m_port->Append(wxT("554 (RTSP standard)")); + m_port->Append(wxT("7070 (RTSP ext)")); + m_port->Append(wxT("80 (RTSP / HTTP tunnel)")); + m_port->Append(wxT("8080 (RTSP / HTTP tunnel)")); + sOpt = gf_cfg_get_key(cfg, "Streaming", "DefaultPort"); + u32 port = 554; + Bool force_rtsp = 0; + if (sOpt) port = atoi(sOpt); + switch (port) { + case 8080: + m_port->SetSelection(3); + force_rtsp = 1; + break; + case 80: + m_port->SetSelection(2); + force_rtsp = 1; + break; + case 7070: + m_port->SetSelection(1); + break; + default: + m_port->SetSelection(0); + break; + } + + Bool use_rtsp = 0; + sOpt = gf_cfg_get_key(cfg, "Streaming", "RTPoverRTSP"); + if (sOpt && !stricmp(sOpt, "yes")) use_rtsp = 1; + + if (force_rtsp) { + m_rtsp->SetValue(1); + m_rtsp->Enable(0); + m_reorder->SetValue(0); + m_reorder->Enable(0); + } else { + m_rtsp->SetValue(use_rtsp ? 1 : 0); + m_rtsp->Enable(1); + m_reorder->Enable(1); + sOpt = gf_cfg_get_key(cfg, "Streaming", "ReorderSize"); + m_reorder->SetValue( (sOpt && !stricmp(sOpt, "0")) ? 1 : 0); + } + sOpt = gf_cfg_get_key(cfg, "Streaming", "RTSPTimeout"); + m_timeout->SetValue(sOpt ? wxString(sOpt, wxConvUTF8) : wxT("30000")); + sOpt = gf_cfg_get_key(cfg, "Network", "BufferLength"); + m_buffer->SetValue(sOpt ? wxString(sOpt, wxConvUTF8) : wxT("3000")); + sOpt = gf_cfg_get_key(cfg, "Network", "RebufferLength"); + u32 buf_len = 0; + if (sOpt) buf_len = atoi(sOpt); + if (buf_len) { + m_dorebuffer->SetValue(1); + m_rebuffer->SetValue(wxString(sOpt, wxConvUTF8)); + m_rebuffer->Enable(1); + } else { + m_dorebuffer->SetValue(0); + m_rebuffer->SetValue(wxT("0")); + m_rebuffer->Enable(0); + } + + RTPoverRTSP(event); + + sOpt = gf_cfg_get_key(cfg, "StreamingCache", "RecordDirectory"); + if (!sOpt) sOpt = gf_cfg_get_key(cfg, "Core", "CacheDirectory"); + if (sOpt) m_recdir->SetLabel(wxString(sOpt, wxConvUTF8)); + sOpt = gf_cfg_get_key(cfg, "StreamingCache", "KeepExistingFiles"); + m_overwrite->SetValue((sOpt && !stricmp(sOpt, "yes")) ? 0 : 1); + + sOpt = gf_cfg_get_key(cfg, "StreamingCache", "BaseFileName"); + if (sOpt) { + m_usename->SetValue(1); + m_recfile->Enable(1); + m_recfile->SetValue(wxString(sOpt, wxConvUTF8)); + } else { + m_usename->SetValue(0); + m_recfile->Enable(0); + m_recfile->SetValue(wxT("uses service URL")); + } + + m_select->Append(wxT("General")); + m_select->Append(wxT("MPEG-4 Systems")); + m_select->Append(wxT("Media Decoders")); + m_select->Append(wxT("Compositor")); + m_select->Append(wxT("Renderer 2D")); + m_select->Append(wxT("Renderer 3D")); + m_select->Append(wxT("Video Output")); + m_select->Append(wxT("Audio Output")); + m_select->Append(wxT("Text Engine")); + m_select->Append(wxT("File Download")); + m_select->Append(wxT("Real-Time Streaming")); + m_select->Append(wxT("Streaming Cache")); + + sOpt = gf_cfg_get_key(cfg, "General", "ConfigPanel"); + m_sel = sOpt ? atoi(sOpt) : 0; + if (m_sel>11) m_sel=11; + m_select->SetSelection(m_sel); + + DoSelect(); +} + +BEGIN_EVENT_TABLE(wxGPACControl, wxDialog) + EVT_BUTTON(ID_APPLY, wxGPACControl::Apply) + EVT_COMBOBOX(ID_SELECT, wxGPACControl::OnSetSelection) + EVT_CHECKBOX(ID_FORCE_AUDIO, wxGPACControl::ForceAudio) + EVT_COMBOBOX(ID_AUDIO_DRIVER, wxGPACControl::OnSetAudioDriver) + EVT_BUTTON(ID_FONT_DIR, wxGPACControl::FontDir) + EVT_BUTTON(ID_CACHE_DIR, wxGPACControl::CacheDir) + EVT_CHECKBOX(ID_PROGRESSIVE, wxGPACControl::OnProgressive) + EVT_CHECKBOX(ID_USE_PROXY, wxGPACControl::OnUseProxy) + EVT_CHECKBOX(ID_RTP_OVER_RTSP, wxGPACControl::RTPoverRTSP) + EVT_CHECKBOX(ID_RTSP_REBUFFER, wxGPACControl::Rebuffer) + EVT_COMBOBOX(ID_RTSP_PORT, wxGPACControl::OnSetRTSPPort) + EVT_CHECKBOX(ID_USE_FILENAME, wxGPACControl::OnUseFileName) + EVT_BUTTON(ID_RECORD_DIR, wxGPACControl::OnRecDir) +END_EVENT_TABLE() + + +wxGPACControl::~wxGPACControl() +{ + char str[20]; + sprintf(str, "%d", m_sel); + gf_cfg_set_key(m_pApp->m_user.config, "General", "ConfigPanel", str); +} + + +void wxGPACControl::DoSelect() +{ + + /*hide everything*/ + s_main->Show(s_general, false); + s_main->Show(s_mpeg4, false); + s_main->Show(s_mdec, false); + s_main->Show(s_rend, false); + s_main->Show(s_rend2d, false); + s_main->Show(s_rend3d, false); + s_main->Show(s_video, false); + s_main->Show(s_audio, false); + s_main->Show(s_font, false); + s_main->Show(s_dnld, false); + s_main->Show(s_stream, false); + s_main->Show(s_rec, false); + switch (m_sel) { + case 0: + s_main->Show(s_general, true); + break; + case 1: + s_main->Show(s_mpeg4, true); + break; + case 2: + s_main->Show(s_mdec, true); + break; + case 3: + s_main->Show(s_rend, true); + break; + case 4: + s_main->Show(s_rend2d, true); + break; + case 5: + s_main->Show(s_rend3d, true); + break; + case 6: + s_main->Show(s_video, true); + break; + case 7: + s_main->Show(s_audio, true); + break; + case 8: + s_main->Show(s_font, true); + break; + case 9: + s_main->Show(s_dnld, true); + break; + case 10: + s_main->Show(s_stream, true); + break; + case 11: + s_main->Show(s_rec, true); + break; + } + SetSizer(s_main); + s_main->Fit(this); + //s_main->Layout(); + return; + +} + +void wxGPACControl::OnSetSelection(wxCommandEvent &WXUNUSED(event)) +{ + m_sel = m_select->GetSelection(); + DoSelect(); +} + +void wxGPACControl::FontDir(wxCommandEvent &WXUNUSED(event)) +{ + wxDirDialog dlg(this); + dlg.SetPath(m_fontdir->GetLabel()); + if (dlg.ShowModal() == wxID_OK) { + m_fontdir->SetLabel(dlg.GetPath()); + } +} +void wxGPACControl::CacheDir(wxCommandEvent &WXUNUSED(event)) +{ + wxDirDialog dlg(this); + dlg.SetPath(m_cachedir->GetLabel()); + if (dlg.ShowModal() == wxID_OK) { + m_cachedir->SetLabel(dlg.GetPath()); + } +} + +void wxGPACControl::OnProgressive(wxCommandEvent &WXUNUSED(event)) +{ + m_sax_duration->Enable(m_progressive->GetValue() ? 1 : 0); +} + +void wxGPACControl::OnUseProxy(wxCommandEvent &WXUNUSED(event)) +{ + m_proxy_name->Enable(m_use_proxy->GetValue() ? 1 : 0); +} + +void wxGPACControl::RTPoverRTSP(wxCommandEvent &WXUNUSED(event)) +{ + m_reorder->Enable(m_rtsp->GetValue() ? 0 : 1); +} + +void wxGPACControl::Rebuffer(wxCommandEvent &WXUNUSED(event)) +{ + if (m_dorebuffer->GetValue()) { + m_rebuffer->Enable(); + } else { + m_rebuffer->Disable(); + } +} + +void wxGPACControl::OnSetRTSPPort(wxCommandEvent &WXUNUSED(event)) +{ + if (m_port->GetSelection() > 1) { + m_rtsp->Enable(0); + m_reorder->Enable(0); + } else { + m_rtsp->Enable(1); + m_reorder->Enable(1); + } +} + +void wxGPACControl::OnRecDir(wxCommandEvent &WXUNUSED(event)) +{ + wxDirDialog dlg(this); + dlg.SetPath(m_recdir->GetLabel()); + if (dlg.ShowModal() == wxID_OK) { + m_recdir->SetLabel(dlg.GetPath()); + } +} + +void wxGPACControl::OnUseFileName(wxCommandEvent &WXUNUSED(event)) +{ + if (m_usename->GetValue()) { + m_recfile->Enable(); + m_recfile->SetValue(wxT("record")); + } else { + m_recfile->Disable(); + m_recfile->SetValue(wxT("uses service URL")); + } +} + +void wxGPACControl::ForceAudio(wxCommandEvent &WXUNUSED(event)) +{ + if (m_forcecfg->GetValue()) { + m_nbbuf->Enable(); + m_buflen->Enable(); + } else { + m_nbbuf->Disable(); + m_buflen->Disable(); + } +} + +void wxGPACControl::OnSetAudioDriver(wxCommandEvent &WXUNUSED(event)) +{ +#ifdef WIN32 + if (strstr(m_audio->GetStringSelection().mb_str(wxConvUTF8), "DirectSound")) { + m_notifs->Enable(1); + } else { + m_notifs->Enable(0); + } +#endif +} + + + +void wxGPACControl::Apply(wxCommandEvent &WXUNUSED(event)) +{ + /*save options*/ + GF_Config *cfg = m_pApp->m_user.config; + + m_pApp->m_loop = m_loop->GetValue() ? 1 : 0; + gf_cfg_set_key(cfg, "General", "Loop", m_loop->GetValue() ? "yes" : "no"); + m_pApp->m_lookforsubs = m_lookforsubs->GetValue() ? 1 : 0; + gf_cfg_set_key(cfg, "General", "LookForSubtitles", m_lookforsubs->GetValue() ? "yes" : "no"); + m_pApp->m_console_off = m_noconsole->GetValue() ? 1 : 0; + gf_cfg_set_key(cfg, "General", "ConsoleOff", m_noconsole->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "General", "ViewXMT", m_viewxmt->GetValue() ? "yes" : "no"); + + s32 sel = m_lang->GetSelection(); + gf_cfg_set_key(cfg, "Systems", "LanguageName", gf_lang_get_name(sel) ); + gf_cfg_set_key(cfg, "Systems", "Language3CC", gf_lang_get_3cc(sel) ); + gf_cfg_set_key(cfg, "Systems", "Language2CC", gf_lang_get_2cc(sel) ); + + + sel = m_thread->GetSelection(); + gf_cfg_set_key(cfg, "Systems", "ThreadingPolicy", (sel==0) ? "Single" : ( (sel==1) ? "Multi" : "Free")); + gf_cfg_set_key(cfg, "Systems", "ForceSingleClock", m_singletime->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "Systems", "AlwaysDrawBIFS", m_bifsalwaysdrawn->GetValue() ? "yes" : "no"); + + gf_cfg_set_key(cfg, "Systems", "DefAudioDec", m_decaudio->GetStringSelection().mb_str(wxConvUTF8)); + gf_cfg_set_key(cfg, "Systems", "DefVideoDec", m_decvideo->GetStringSelection().mb_str(wxConvUTF8)); + + + gf_cfg_set_key(cfg, "Compositor", "HighSpeed", m_fast->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "Compositor", "ForceSceneSize", m_force_size->GetValue() ? "yes" : "no"); + + gf_cfg_set_key(cfg, "Compositor", "FrameRate", BIFSRates[m_fps->GetSelection()]); + sel = m_aa->GetSelection(); + gf_cfg_set_key(cfg, "Compositor", "AntiAlias", (sel==0) ? "None" : ( (sel==1) ? "Text" : "All")); + sel = m_draw_bounds->GetSelection(); + gf_cfg_set_key(cfg, "Compositor", "BoundingVolume", (sel==2) ? "AABB" : (sel==1) ? "Box" : "None"); + + Bool is_3D = m_use3D->GetValue() ? 1 : 0; + if (m_bWas3D != is_3D) { + /*FIXME*/ + } + gf_cfg_set_key(cfg, "core", "raster2d", m_graph->GetStringSelection().mb_str(wxConvUTF8)); + + gf_cfg_set_key(cfg, "Compositor", "DirectDraw", m_direct->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "Compositor", "ScalableZoom", m_scalable->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "Compositor", "DisableYUV", m_noyuv->GetValue() ? "yes" : "no"); + + gf_cfg_set_key(cfg, "Compositor", "RasterOutlines", m_raster_outlines->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "Compositor", "EmulatePOW2", m_emulpow2->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "Compositor", "PolygonAA", m_polyaa->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "Compositor", "DisableRectExt", m_norectext->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "Compositor", "BitmapCopyPixels", m_copypixels->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "Compositor", "BackFaceCulling", m_nobackcull->GetValue() ? "Off" : "On"); + + sel = m_wire->GetSelection(); + gf_cfg_set_key(cfg, "Compositor", "Wireframe", (sel==2) ? "WireOnSolid" : ( (sel==1) ? "WireOnly" : "WireNone" ) ); + sel = m_normals->GetSelection(); + gf_cfg_set_key(cfg, "Compositor", "DrawNormals", (sel==2) ? "PerVertex" : ( (sel==1) ? "PerFace" : "Never" ) ); + + gf_cfg_set_key(cfg, "core", "switch-vres", m_switchres->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "core", "hwvmem", m_usehwmem->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "core", "video-output", m_video->GetStringSelection().mb_str(wxConvUTF8)); + + + gf_cfg_set_key(cfg, "Audio", "ForceConfig", m_forcecfg->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "Audio", "NoResync", m_noresync->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "Audio", "DisableMultiChannel", m_nomulitch->GetValue() ? "yes" : "no"); + + gf_cfg_set_key(cfg, "Audio", "NumBuffers", wxString::Format(wxT("%d"), m_nbbuf->GetValue()).mb_str(wxConvUTF8) ); + gf_cfg_set_key(cfg, "Audio", "TotalDuration", wxString::Format(wxT("%d"), m_buflen->GetValue()).mb_str(wxConvUTF8) ); + gf_cfg_set_key(cfg, "core", "audio-output", m_audio->GetStringSelection().mb_str(wxConvUTF8)); +#ifdef WIN32 + if (m_notifs->IsEnabled()) + gf_cfg_set_key(cfg, "Audio", "DisableNotification", m_notifs->GetValue() ? "yes" : "no"); +#endif + + gf_cfg_set_key(cfg, "FontCache", "FontReader", m_font->GetStringSelection().mb_str(wxConvUTF8)); + gf_cfg_set_key(cfg, "FontCache", "FontDirectory", m_fontdir->GetLabel().mb_str(wxConvUTF8)); + switch (m_texturemode->GetSelection()) { + case 2: + gf_cfg_set_key(cfg, "Compositor", "TextureTextMode", "Always"); + break; + case 1: + gf_cfg_set_key(cfg, "Compositor", "TextureTextMode", "Never"); + break; + default: + gf_cfg_set_key(cfg, "Compositor", "TextureTextMode", "Default"); + break; + } + + gf_cfg_set_key(cfg, "Core", "CleanCache", m_cleancache->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "Core", "RestartFiles", m_restartcache->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "SAXLoader", "Progressive", m_progressive->GetValue() ? "yes" : "no"); + gf_cfg_set_key(cfg, "SAXLoader", "MaxDuration", m_sax_duration->GetLabel().mb_str(wxConvUTF8)); + gf_cfg_set_key(cfg, "Core", "CacheDirectory", m_cachedir->GetLabel().mb_str(wxConvUTF8)); + + + Bool force_rtsp = 0; + switch (m_port->GetSelection()) { + case 3: + gf_cfg_set_key(cfg, "Streaming", "DefaultPort", "8080"); + force_rtsp = 1; + break; + case 2: + gf_cfg_set_key(cfg, "Streaming", "DefaultPort", "80"); + force_rtsp = 1; + break; + case 1: + gf_cfg_set_key(cfg, "Streaming", "DefaultPort", "7070"); + break; + default: + gf_cfg_set_key(cfg, "Streaming", "DefaultPort", "554"); + break; + } + + if (force_rtsp) { + gf_cfg_set_key(cfg, "Streaming", "RTPoverRTSP", "yes"); + } else { + gf_cfg_set_key(cfg, "Streaming", "RTPoverRTSP", m_rtsp->GetValue() ? "yes" : "no"); + if (!m_rtsp->GetValue()) gf_cfg_set_key(cfg, "Streaming", "ReorderSize", m_dorebuffer->GetValue() ? "30" : "0"); + } + + gf_cfg_set_key(cfg, "Streaming", "RTSPTimeout", m_timeout->GetValue().mb_str(wxConvUTF8)); + gf_cfg_set_key(cfg, "Network", "BufferLength", m_buffer->GetValue().mb_str(wxConvUTF8)); + if (m_dorebuffer->GetValue()) { + gf_cfg_set_key(cfg, "Network", "RebufferLength", m_rebuffer->GetValue().mb_str(wxConvUTF8)); + } else { + gf_cfg_set_key(cfg, "Network", "RebufferLength", "0"); + } + + gf_cfg_set_key(cfg, "StreamingCache", "KeepExistingFiles", m_overwrite->GetValue() ? "no" : "yes"); + if (m_usename->GetValue()) { + gf_cfg_set_key(cfg, "StreamingCache", "BaseFileName", m_recfile->GetValue().mb_str(wxConvUTF8)); + } else { + gf_cfg_set_key(cfg, "StreamingCache", "BaseFileName", NULL); + } + gf_cfg_set_key(cfg, "StreamingCache", "RecordDirectory", m_recdir->GetLabel().mb_str(wxConvUTF8)); + + + gf_term_set_option(m_pApp->m_term, GF_OPT_RELOAD_CONFIG, 1); +} + diff --git a/applications/deprecated/old_arch/osmo4_wx/wxGPACControl.h b/applications/deprecated/old_arch/osmo4_wx/wxGPACControl.h new file mode 100644 index 0000000..eb4226d --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/wxGPACControl.h @@ -0,0 +1,138 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / Osmo4 wxWidgets GUI + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + */ + +#ifndef _OPTIONS_H +#define _OPTIONS_H + +#include "wx/wxprec.h" + +#ifndef WX_PRECOMP +#include "wx/wx.h" +#endif + +#include +#include + +enum +{ + ID_SELECT = 1000, + ID_APPLY, + + ID_MAKE_DEF, + ID_FORCE_AUDIO, + ID_AUDIO_DRIVER, + ID_FONT_DIR, + ID_CACHE_DIR, + ID_PROGRESSIVE, + ID_RTSP_PORT, + ID_RTP_OVER_RTSP, + ID_RTSP_REBUFFER, + ID_RECORD_DIR, + ID_USE_FILENAME, + ID_USE_PROXY, +}; + +class wxOsmo4Frame; +class wxGPACControl : public wxDialog +{ +public: + wxGPACControl(wxWindow *parent); + virtual ~wxGPACControl(); + +private: + DECLARE_EVENT_TABLE() + + wxOsmo4Frame *m_pApp; + + wxComboBox *m_select; + Bool m_bWas3D; + + void Apply(wxCommandEvent &event); + void OnSetSelection(wxCommandEvent &event); + void ForceAudio(wxCommandEvent &event); + void OnSetAudioDriver(wxCommandEvent &event); + void FontDir(wxCommandEvent &event); + void CacheDir(wxCommandEvent &event); + void OnProgressive(wxCommandEvent &event); + void OnUseProxy(wxCommandEvent &event); + void RTPoverRTSP(wxCommandEvent &event); + void Rebuffer(wxCommandEvent &event); + void OnSetRTSPPort(wxCommandEvent &event); + void OnUseFileName(wxCommandEvent &event); + void OnRecDir(wxCommandEvent &event); + void DoSelect(); + s32 m_sel; + void SetYUVLabel(); + + wxBoxSizer *s_header, *s_main, *s_general, *s_mpeg4, *s_mdec, *s_rend, *s_rend2d, *s_rend3d, *s_audio, *s_video, *s_font, *s_dnld, *s_stream, *s_rec; + + /*general section*/ + wxCheckBox *m_loop, *m_lookforsubs, *m_noconsole, *m_viewxmt; + /*MPEG-4 systems*/ + wxCheckBox *m_bifsalwaysdrawn, *m_singletime; + wxComboBox *m_lang, *m_thread; + /*media decoders*/ + wxComboBox *m_decaudio, *m_decvideo; + /*Rendering*/ + wxComboBox *m_fps, *m_aa, *m_draw_bounds; + wxCheckBox *m_use3D, *m_fast, *m_force_size; + /*Renderer 2D*/ + wxComboBox *m_graph; + wxCheckBox *m_noyuv, *m_direct, *m_scalable; + wxStaticText *m_yuvtxt; + /*Renderer 3D*/ + wxCheckBox *m_raster_outlines, *m_polyaa, *m_nobackcull, *m_emulpow2, *m_norectext, *m_copypixels; + wxComboBox *m_wire, *m_normals; + /*video*/ + wxComboBox *m_video; + wxCheckBox *m_switchres, *m_usehwmem; + /*audio*/ + wxSpinCtrl *m_nbbuf, *m_buflen; + wxComboBox *m_audio; + wxCheckBox *m_forcecfg, *m_noresync, *m_nomulitch; +#ifdef WIN32 + wxCheckBox *m_notifs; +#endif + /*font*/ + wxComboBox *m_font; + wxButton *m_fontdir; + wxComboBox *m_texturemode; + /*file download*/ + wxButton *m_cachedir; + wxCheckBox *m_cleancache, *m_restartcache, *m_progressive, *m_use_proxy; + wxTextCtrl *m_sax_duration, *m_proxy_name; + /*streaming*/ + wxComboBox *m_port; + wxCheckBox *m_rtsp, *m_reorder, *m_dorebuffer; + wxTextCtrl *m_timeout, *m_buffer, *m_rebuffer; + /*file recorder*/ + wxButton *m_recdir; + wxCheckBox *m_overwrite, *m_usename; + wxTextCtrl *m_recfile; +}; + +#endif + diff --git a/applications/deprecated/old_arch/osmo4_wx/wxOsmo4.cpp b/applications/deprecated/old_arch/osmo4_wx/wxOsmo4.cpp new file mode 100644 index 0000000..5d851bf --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/wxOsmo4.cpp @@ -0,0 +1,2392 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / Osmo4 wxWidgets GUI + * + * GPAC is gf_free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + */ + + +#include "wxOsmo4.h" +#include "wxGPACControl.h" +#include "fileprops.h" +#include +#include +#include +#include +#include + + +IMPLEMENT_APP(wxOsmo4App) + +#include "osmo4.xpm" + +#include + + + + + +#include +#include + +#include "toolbar.xpm" + +#include "Playlist.h" + +#ifdef WIN32 +#define FRAME_H 140 +#else +#define FRAME_H 110 +#endif + + +wxString get_pref_browser(GF_Config *cfg) +{ + const char *sOpt = gf_cfg_get_key(cfg, "General", "Browser"); + if (sOpt) return wxString(sOpt, wxConvUTF8); + return wxEmptyString; + /* + #ifdef __WXMAC__ + return wxT("safari"); + #else + #ifdef WIN32 + return wxT("explorer.exe"); + #else + return wxT("mozilla"); + #endif + #endif*/ +} + + +IMPLEMENT_DYNAMIC_CLASS(wxGPACEvent, wxEvent ) + +wxGPACEvent::wxGPACEvent(wxWindow* win) +{ + SetEventType(GPAC_EVENT); + SetEventObject(win); + gpac_evt.type = 0; + to_url = wxT(""); +} +wxEvent *wxGPACEvent::Clone() const +{ + wxGPACEvent *evt = new wxGPACEvent((wxWindow *) m_eventObject); + evt->to_url = to_url; + evt->gpac_evt = gpac_evt; + return evt; +} + +#include + +/*open file dlg*/ +BEGIN_EVENT_TABLE(OpenURLDlg, wxDialog) + EVT_BUTTON(ID_URL_GO, OpenURLDlg::OnGo) +END_EVENT_TABLE() + +OpenURLDlg::OpenURLDlg(wxWindow *parent, GF_Config *cfg) + : wxDialog(parent, -1, wxString(wxT("Enter remote presentation location"))) +{ +#ifndef WIN32 + SetSize(430, 35); +#else + SetSize(430, 55); +#endif + Centre(); + m_url = new wxComboBox(this, -1, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_DROPDOWN); + m_url->SetSize(0, 2, 340, 18, wxSIZE_AUTO); + m_go = new wxButton(this, ID_URL_GO, wxT("Go !")); +#ifndef WIN32 + m_go->SetSize(344, 2, 20, 18, wxSIZE_AUTO); +#else + m_go->SetSize(364, 2, 30, 18, wxSIZE_AUTO); +#endif + m_urlVal = wxT(""); + + m_cfg = cfg; + + const char *sOpt; + u32 i=0; + + while (1) { + sOpt = gf_cfg_get_key_name(m_cfg, "RecentFiles", i); + if (!sOpt) break; + m_url->Append(wxString(sOpt, wxConvUTF8) ); + i++; + } +} + +#define MAX_LAST_FILES 20 +void UpdateLastFiles(GF_Config *cfg, const char *URL) +{ + u32 nb_entries; + gf_cfg_set_key(cfg, "RecentFiles", URL, NULL); + gf_cfg_insert_key(cfg, "RecentFiles", URL, "", 0); + /*remove last entry if needed*/ + nb_entries = gf_cfg_get_key_count(cfg, "RecentFiles"); + if (nb_entries>MAX_LAST_FILES) { + gf_cfg_set_key(cfg, "RecentFiles", gf_cfg_get_key_name(cfg, "RecentFiles", nb_entries-1), NULL); + } +} + +void OpenURLDlg::OnGo(wxCommandEvent& event) +{ + m_urlVal = m_url->GetValue(); + UpdateLastFiles(m_cfg, m_urlVal.mb_str(wxConvUTF8)); + EndModal(wxID_OK); +} +/*end open file dlg*/ + +#ifdef WIN32 +u32 get_sys_col(int idx) +{ + u32 res; + DWORD val = GetSysColor(idx); + res = (val)&0xFF; + res<<=8; + res |= (val>>8)&0xFF; + res<<=8; + res |= (val>>16)&0xFF; + return res; +} +#endif + +static void wxOsmo4_progress_cbk(const void *usr, const char *title, u64 done, u64 total) +{ + if (!total) return; + wxOsmo4Frame *app = (wxOsmo4Frame *)usr; + s32 prog = (s32) ( (100 * (u64)done) / total); + if (app->m_last_prog < prog) { + app->m_last_prog = prog; + + if (prog<100) { + /*appears to crash wxWidgets / X11 when refreshing the text too often*/ + if (app->m_LastStatusTime + 200 > gf_sys_clock()) return; + char msg[1024]; + sprintf(msg, "%s %02d %%)", title, prog); + //app->SetStatus(wxString(msg, wxConvUTF8)); + } else { + app->SetStatus(wxT("Ready")); + app->m_last_prog = -1; + } + } +} + +Bool GPAC_EventProc(void *ptr, GF_Event *evt) +{ + wxCommandEvent event; + wxOsmo4Frame *app = (wxOsmo4Frame *)ptr; + + switch (evt->type) { + case GF_EVENT_DURATION: + app->m_duration = (u32) (evt->duration.duration*1000); + app->m_can_seek = evt->duration.can_seek; + if (app->m_duration<1100) app->m_can_seek = 0; + app->m_pProg->Enable(app->m_can_seek ? 1 : 0); + app->m_pPlayList->SetDuration((u32) evt->duration.duration); + break; + case GF_EVENT_MESSAGE: + { + const char *servName; + if (!evt->message.service || !strcmp(evt->message.service, app->m_pPlayList->GetURL().mb_str(wxConvUTF8))) { + servName = "main service"; + } else { + servName = evt->message.service; + } + if (!evt->message.message) return 0; + + if (evt->message.error) { + app->SetStatus(wxString(evt->message.message, wxConvUTF8) + wxT(" (") + wxString(servName, wxConvUTF8) + wxT(")") ); + if (!app->m_connected) app->m_pPlayList->SetDead(); + } + else if (!app->m_console_off) { + if (strstr(evt->message.message, "100 %")) { + app->SetStatus(wxT("")); + } else { + app->SetStatus(wxString(evt->message.message, wxConvUTF8) ); + } + } + +#if 0 + /*log*/ + if (evt->message.error) + ::wxLogMessage(wxString(evt->message.message, wxConvUTF8) + wxT(" (") + wxString(servName, wxConvUTF8) + wxT(") ") + wxString(gf_error_to_string(evt->message.error), wxConvUTF8) ); + else + ::wxLogMessage(wxString(evt->message.message, wxConvUTF8) + wxT(" (") + wxString(servName, wxConvUTF8) + wxT(")")); +#endif + } + break; + case GF_EVENT_PROGRESS: + { + const char *sTitle; + if (evt->progress.progress_type==0) sTitle = (char *)"Buffer"; + else if (evt->progress.progress_type==1) sTitle = (char *)"Download"; + else if (evt->progress.progress_type==2) sTitle = (char *)"Import"; + gf_set_progress(sTitle, evt->progress.done, evt->progress.total); + } + break; + case GF_EVENT_KEYDOWN: + if (app->m_can_seek && (evt->key.flags & GF_KEY_MOD_ALT)) { + s32 res; + switch (evt->key.key_code) { + case GF_KEY_LEFT: + res = gf_term_get_time_in_ms(app->m_term) - 5*app->m_duration/100; + if (res<0) res=0; + gf_term_play_from_time(app->m_term, res, 0); + break; + case GF_KEY_RIGHT: + res = gf_term_get_time_in_ms(app->m_term) + 5*app->m_duration/100; + if ((u32) res>=app->m_duration) res = 0; + gf_term_play_from_time(app->m_term, res, 0); + break; + case GF_KEY_DOWN: + res = gf_term_get_time_in_ms(app->m_term) - 60000; + if (res<0) res=0; + gf_term_play_from_time(app->m_term, res, 0); + break; + case GF_KEY_UP: + res = gf_term_get_time_in_ms(app->m_term) + 60000; + if ((u32) res>=app->m_duration) res = 0; + gf_term_play_from_time(app->m_term, res, 0); + break; + } + } else if (evt->key.flags & GF_KEY_MOD_CTRL) { + switch (evt->key.key_code) { + case GF_KEY_LEFT: + app->m_pPlayList->PlayPrev(); + break; + case GF_KEY_RIGHT: + app->m_pPlayList->PlayNext(); + break; + } + } else { + switch (evt->key.key_code) { + case GF_KEY_HOME: + gf_term_set_option(app->m_term, GF_OPT_NAVIGATION_TYPE, 1); + break; + case GF_KEY_ESCAPE: + if (gf_term_get_option(app->m_term, GF_OPT_FULLSCREEN)) + gf_term_set_option(app->m_term, GF_OPT_FULLSCREEN, 0); + break; + default: + { + wxGPACEvent wxevt(app); + wxevt.gpac_evt = *evt; + app->AddPendingEvent(wxevt); + } + break; + } + } + break; + + case GF_EVENT_CONNECT: + { + wxGPACEvent wxevt(app); + wxevt.gpac_evt.type = GF_EVENT_CONNECT; + wxevt.gpac_evt.connect.is_connected = evt->connect.is_connected; + if (!evt->connect.is_connected) app->m_duration = 0; + app->AddPendingEvent(wxevt); + } + break; + case GF_EVENT_NAVIGATE: + { + wxGPACEvent wxevt(app); + wxevt.to_url = wxString(evt->navigate.to_url, wxConvUTF8); + wxevt.gpac_evt.type = evt->type; + app->AddPendingEvent(wxevt); + } + return 1; + case GF_EVENT_SET_CAPTION: + { + wxGPACEvent wxevt(app); + wxevt.to_url = wxString(evt->caption.caption, wxConvUTF8); + wxevt.gpac_evt.type = evt->type; + app->AddPendingEvent(wxevt); + } + return 1; + + case GF_EVENT_QUIT: + case GF_EVENT_VIEWPOINTS: + case GF_EVENT_STREAMLIST: + case GF_EVENT_SCENE_SIZE: +// case GF_EVENT_SIZE: + { + wxGPACEvent wxevt(app); + wxevt.gpac_evt = *evt; + app->AddPendingEvent(wxevt); + } + break; + case GF_EVENT_DBLCLICK: + gf_term_set_option(app->m_term, GF_OPT_FULLSCREEN, !gf_term_get_option(app->m_term, GF_OPT_FULLSCREEN)); + return 0; + case GF_EVENT_MOUSEDOWN: + if (!gf_term_get_option(app->m_term, GF_OPT_FULLSCREEN)) { +#ifdef __WXGTK__ + app->m_pVisual->SetFocus(); +#else + app->m_pView->SetFocus(); +#endif + } + break; + case GF_EVENT_AUTHORIZATION: + { + wxGPACEvent wxevt(app); + wxTextEntryDialog user_d (0, + wxT("Please set the user name for connection"), + wxString(evt->auth.site_url, wxConvUTF8), + wxString(evt->auth.user, wxConvUTF8)); + if (user_d.ShowModal() != wxID_OK) + return 0; + strncpy(evt->auth.user, user_d.GetValue().mb_str(wxConvUTF8), 50); + wxPasswordEntryDialog passwd_d(0, + wxT("Please enter password"), + wxString(evt->auth.site_url, wxConvUTF8), + wxString(evt->auth.password, wxConvUTF8)); + if (passwd_d.ShowModal() != wxID_OK) + return 0; + strncpy(evt->auth.password, passwd_d.GetValue().mb_str(wxConvUTF8), 50); + return 1; + } + case GF_EVENT_SYS_COLORS: +#ifdef WIN32 + evt->sys_cols.sys_colors[0] = get_sys_col(COLOR_ACTIVEBORDER); + evt->sys_cols.sys_colors[1] = get_sys_col(COLOR_ACTIVECAPTION); + evt->sys_cols.sys_colors[2] = get_sys_col(COLOR_APPWORKSPACE); + evt->sys_cols.sys_colors[3] = get_sys_col(COLOR_BACKGROUND); + evt->sys_cols.sys_colors[4] = get_sys_col(COLOR_BTNFACE); + evt->sys_cols.sys_colors[5] = get_sys_col(COLOR_BTNHIGHLIGHT); + evt->sys_cols.sys_colors[6] = get_sys_col(COLOR_BTNSHADOW); + evt->sys_cols.sys_colors[7] = get_sys_col(COLOR_BTNTEXT); + evt->sys_cols.sys_colors[8] = get_sys_col(COLOR_CAPTIONTEXT); + evt->sys_cols.sys_colors[9] = get_sys_col(COLOR_GRAYTEXT); + evt->sys_cols.sys_colors[10] = get_sys_col(COLOR_HIGHLIGHT); + evt->sys_cols.sys_colors[11] = get_sys_col(COLOR_HIGHLIGHTTEXT); + evt->sys_cols.sys_colors[12] = get_sys_col(COLOR_INACTIVEBORDER); + evt->sys_cols.sys_colors[13] = get_sys_col(COLOR_INACTIVECAPTION); + evt->sys_cols.sys_colors[14] = get_sys_col(COLOR_INACTIVECAPTIONTEXT); + evt->sys_cols.sys_colors[15] = get_sys_col(COLOR_INFOBK); + evt->sys_cols.sys_colors[16] = get_sys_col(COLOR_INFOTEXT); + evt->sys_cols.sys_colors[17] = get_sys_col(COLOR_MENU); + evt->sys_cols.sys_colors[18] = get_sys_col(COLOR_MENUTEXT); + evt->sys_cols.sys_colors[19] = get_sys_col(COLOR_SCROLLBAR); + evt->sys_cols.sys_colors[20] = get_sys_col(COLOR_3DDKSHADOW); + evt->sys_cols.sys_colors[21] = get_sys_col(COLOR_3DFACE); + evt->sys_cols.sys_colors[22] = get_sys_col(COLOR_3DHIGHLIGHT); + evt->sys_cols.sys_colors[23] = get_sys_col(COLOR_3DLIGHT); + evt->sys_cols.sys_colors[24] = get_sys_col(COLOR_3DSHADOW); + evt->sys_cols.sys_colors[25] = get_sys_col(COLOR_WINDOW); + evt->sys_cols.sys_colors[26] = get_sys_col(COLOR_WINDOWFRAME); + evt->sys_cols.sys_colors[27] = get_sys_col(COLOR_WINDOWTEXT); + return 1; +#else + memset(evt->sys_cols.sys_colors, 0, sizeof(u32)*28); + return 1; +#endif + } + return 0; +} + + + +bool wxOsmo4App::OnInit() +{ +#ifdef __WXGTK__ + XSynchronize((Display *) wxGetDisplay(), 1); +#endif + wxFrame *frame = new wxOsmo4Frame(); + frame->Show(TRUE); + SetTopWindow(frame); + return true; +} + + +class myDropfiles : public wxFileDropTarget +{ +public: + myDropfiles() : wxFileDropTarget() {} + virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames); + wxOsmo4Frame *m_pMain; +}; + +bool myDropfiles::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) +{ + u32 count = filenames.GetCount(); + + if (count==1) { + const char *ext = strrchr(filenames.Item(0).mb_str(wxConvUTF8) , '.'); + /*if playing and sub d&d, open sub in current presentation*/ + if (m_pMain->m_connected && ext && ( !stricmp(ext, ".srt") || !stricmp(ext, ".sub") || !stricmp(ext, ".ttxt") || !stricmp(ext, ".xml") ) ) { + m_pMain->AddSubtitle(filenames.Item(0).mb_str(wxConvUTF8) , 1); + return TRUE; + } + } + + for (u32 i=0; im_pPlayList->QueueURL(filenames.Item(i)); + + m_pMain->m_pPlayList->RefreshList(); + m_pMain->m_pPlayList->PlayNext(); + return TRUE; +} + +bool GPACLogs::OnFrameClose(wxFrame *frame) +{ + Show(FALSE); + return 0; +} + +void wxOsmo4Frame::ShowViewWindow(Bool do_show) +{ + m_pView->Show(do_show ? 1 : 0); +#ifdef __WXGTK__ + //m_pView->Show(0); +#endif +} + +#ifdef __WXGTK__ +extern "C" { +#ifdef __WXGTK20__ + int gdk_x11_drawable_get_xid( void * ); + void *gdk_x11_drawable_get_xdisplay( void * ); +#endif + void *gtk_widget_get_parent_window( void * ); +} +#endif + + +void wxOsmo4Frame::CheckVideoOut() +{ + const char *sOpt = gf_cfg_get_key(m_user.config, "core", "video-output"); + void *os_handle = NULL; + void *os_display = NULL; + /*build a child window for embed display*/ + if (sOpt && stricmp(sOpt, "SDL Video Output")) { + if (m_user.os_window_handler) return; + m_bExternalView = 0; + +#ifdef __WXGTK__ + GtkWidget* widget = m_pVisual->GetHandle(); + +#ifdef __WXGTK20__ + os_handle = (void *) gdk_x11_drawable_get_xid(gtk_widget_get_parent_window(widget)); +#else + os_handle = (void *)*(int *)( (char *)gtk_widget_get_parent_window(widget) + 2 * sizeof(void *) ); +#endif + +#elif defined (WIN32) + os_handle = m_pView->GetHandle(); +#endif + if (os_handle) { + m_user.os_window_handler = os_handle; + m_user.os_display = os_display; + ShowViewWindow(1); + m_pView->SetSize(320, 240); + SetSize(wxSize(320, 240+FRAME_H)); + SetWindowStyle(wxDEFAULT_FRAME_STYLE); + DoLayout(320, 240); + return; + } + } + /*we're using SDL, don't use SDL hack*/ + m_bExternalView = 1; + m_user.os_window_handler = 0; + m_user.os_display = NULL; + SetSize(wxSize(320,FRAME_H)); + m_pView->SetSize(0, 0); + ShowViewWindow(0); + DoLayout(); + SetWindowStyle(wxDEFAULT_FRAME_STYLE & ~(wxMAXIMIZE_BOX | wxRESIZE_BORDER)); +} + +static void wxOsmo4_do_log(void *cbk, GF_LOG_Level level, GF_LOG_Tool tool, const char *fmt, va_list list) +{ + wxOsmo4Frame *osmo = (wxOsmo4Frame *)cbk; + + if (osmo->m_logs) { + vfprintf(osmo->m_logs, fmt, list); + fflush(osmo->m_logs); + } else { + ::wxVLogMessage(wxString(fmt, wxConvUTF8), list); + } +} + + +Bool wxOsmo4Frame::LoadTerminal() +{ + m_term = NULL; + memset(&m_user, 0, sizeof(GF_User)); + + /*locate exec dir for cfg file*/ + wxPathList pathList; + wxString currentDir(wxGetCwd()); + wxString abs_gpac_path = wxT(""); + char *gpac_cfg, *sep; + + ::wxLogMessage(wxT("Looking for GPAC configuration file")); + + /*load config*/ + Bool first_launch = 0; + m_user.config = gf_cfg_init(NULL, &first_launch); + + if (!m_user.config) { + wxMessageDialog(NULL, wxT("Cannot open GPAC configuration file"), wxT("Init error"), wxOK); + return 0; + } + + gpac_cfg = gf_cfg_get_filename(m_user.config); + sep = strrchr(gpac_cfg, '/'); + if (!sep) sep = strrchr(gpac_cfg, '\\'); + if (sep) sep[0] = 0; + strcpy(szAppPath, gpac_cfg); + if (sep) sep[0] = '/'; + + /*check log file*/ + const char *str = gf_cfg_get_key(m_user.config, "General", "LogFile"); + if (str) m_logs = gf_fopen(str, "wt"); + gf_log_set_callback(this, wxOsmo4_do_log); + + /*set log level*/ + gf_log_set_tools_levels( gf_cfg_get_key(m_user.config, "General", "Logs") ); + + gf_sys_init(GF_MemTrackerNone); + + ::wxLogMessage(wxT("GPAC configuration file opened - looking for modules")); + + m_user.modules = gf_modules_new(str, m_user.config); + /*initial launch*/ + if (!m_user.modules || !gf_modules_get_count(m_user.modules)) { + wxMessageDialog(NULL, wxT("No modules available - system cannot work"), wxT("Fatal Error"), wxOK).ShowModal(); + if (m_user.modules) gf_modules_del(m_user.modules); + gf_cfg_del(m_user.config); + m_user.config = NULL; + return 0; + } + + if (first_launch) { + u32 i; + for (i=0; iCanHandleURL(ifce, "test.test"); + gf_modules_close_interface((GF_BaseInterface *) ifce); + } + } + } + + + + ::wxLogMessage(wxT("%d modules found:"), gf_modules_get_count(m_user.modules)); + for (u32 i=0; im_pMain = this; + SetDropTarget(droptarget); + m_pLogs = new GPACLogs(this); + m_bGrabbed = 0; + + /*new menu bar*/ + wxMenuBar *b = new wxMenuBar(); + /*file*/ + wxMenu *menu = new wxMenu(); + menu->Append(GWX_FILE_OPEN, wxT("&Open File\tCtrl+O"), wxT("Open local presentation")); + menu->Append(GWX_FILE_OPEN_URL, wxT("&Open URL\tCtrl+U"), wxT("Open remote presentation")); + menu->AppendSeparator(); + menu->Append(FILE_PROPERTIES, wxT("&Properties\tCtrl+I"), wxT("Show presentation properties")); + menu->Enable(FILE_PROPERTIES, 0); + wxMenu *smenu = new wxMenu(); + smenu->Append(ID_MCACHE_ENABLE, wxT("&Enable"), wxT("Turns Recorder On/Off")); + smenu->Append(ID_MCACHE_STOP, wxT("&Stop"), wxT("Stops recording and saves")); + smenu->Append(ID_MCACHE_ABORT, wxT("&Abort"), wxT("Stops recording and discards")); + menu->Append(0, wxT("&Streaming Cache"), smenu); + menu->AppendSeparator(); + menu->Append(FILE_COPY, wxT("&Copy\tCtrl+C"), wxT("Copy selected text")); + menu->Append(FILE_PASTE, wxT("&Paste\tCtrl+V"), wxT("Copy selected text")); + menu->AppendSeparator(); + menu->Append(FILE_QUIT, wxT("E&xit"), wxT("Quit the application")); + b->Append(menu, wxT("&File")); + /*view*/ + menu = new wxMenu(); + vp_list = new wxMenu(); + menu->Append(0, wxT("&Viewpoint"), vp_list); + smenu = new wxMenu(); + smenu->Append(ID_HEADLIGHT, wxT("Headlight"), wxT("Turns headlight on/off"), wxITEM_CHECK); + smenu->AppendSeparator(); + smenu->Append(ID_NAVIGATE_NONE, wxT("None"), wxT("Disables Navigation"), wxITEM_CHECK); + smenu->Append(ID_NAVIGATE_WALK, wxT("Walk"), wxT("Walk Navigation"), wxITEM_CHECK); + smenu->Append(ID_NAVIGATE_FLY, wxT("Fly"), wxT("Fly Navigation"), wxITEM_CHECK); + smenu->Append(ID_NAVIGATE_EXAMINE, wxT("Examine"), wxT("Examine Navigation"), wxITEM_CHECK); + smenu->Append(ID_NAVIGATE_PAN, wxT("Pan"), wxT("Pan Navigation"), wxITEM_CHECK); + smenu->Append(ID_NAVIGATE_SLIDE, wxT("Slide"), wxT("Slide Navigation"), wxITEM_CHECK); + smenu->Append(ID_NAVIGATE_ORBIT, wxT("Orbit"), wxT("Orbit Navigation"), wxITEM_CHECK); + smenu->Append(ID_NAVIGATE_GAME, wxT("Game"), wxT("Game Navigation"), wxITEM_CHECK); + smenu->AppendSeparator(); + wxMenu *ssmenu = new wxMenu(); + ssmenu->Append(ID_COLLIDE_NONE, wxT("None"), wxT("No Collision detection"), wxITEM_CHECK); + ssmenu->Append(ID_COLLIDE_REG, wxT("Regular"), wxT("Regular Collision detection"), wxITEM_CHECK); + ssmenu->Append(ID_COLLIDE_DISP, wxT("Displacement"), wxT("Collision detecion with camera displacement"), wxITEM_CHECK); + smenu->Append(0, wxT("&Collision"), ssmenu); + smenu->Append(ID_GRAVITY, wxT("Gravity"), wxT("Turns gravity on/off"), wxITEM_CHECK); + smenu->AppendSeparator(); + smenu->Append(ID_NAVIGATE_RESET, wxT("Reset"), wxT("Reset Navigation")); + + menu->Append(0, wxT("&Navigation"), smenu); + menu->AppendSeparator(); + menu->Append(VIEW_FULLSCREEN, wxT("&Fullscreen"), wxT("Toggles Fullscreen"), wxITEM_CHECK); + menu->Append(VIEW_ORIGINAL, wxT("&Original Size"), wxT("Restore original size")); + smenu = new wxMenu(); + smenu->Append(VIEW_AR_KEEP, wxT("Keep Original\tCtrl+1"), wxT("Keep original aspect ratio"), wxITEM_CHECK); + smenu->Append(VIEW_AR_FILL, wxT("Fill Screen\tCtrl+2"), wxT("Stretch presentation to fill screen"), wxITEM_CHECK); + smenu->Append(VIEW_AR_43, wxT("Ratio 4/3\tCtrl+3"), wxT("Force aspect ratio to 4/3"), wxITEM_CHECK); + smenu->Append(VIEW_AR_169, wxT("Ratio 16/9\tCtrl+4"), wxT("Force aspect ratio to 16/9"), wxITEM_CHECK); + menu->Append(0, wxT("&Aspect Ratio"), smenu); + smenu->Check(VIEW_AR_KEEP, 1); + menu->AppendSeparator(); + menu->Append(VIEW_OPTIONS, wxT("&Options"), wxT("View Options")); + menu->AppendSeparator(); + menu->Append(VIEW_RTI, wxT("&Resource Usage"), wxT("View Resource Usage"), wxITEM_CHECK); + menu->Append(VIEW_LOGS, wxT("&Logs"), wxT("View GPAC logs")); + b->Append(menu, wxT("&View")); + + /*play*/ + menu = new wxMenu(); + sel_menu = new wxMenu(); + sel_menu->Append(0, wxT("&Audio"), new wxMenu()); + sel_menu->Append(0, wxT("&Video"), new wxMenu()); + sel_menu->Append(0, wxT("&Subtitles"), new wxMenu()); + sel_menu->AppendSeparator(); + sel_menu->Append(ID_ADD_SUB, wxT("&Add Subtitle"), wxT("Adds subtitle")); + menu->Append(ID_STREAM_MENU, wxT("&Streams Selection"), sel_menu); + chap_menu = new wxMenu(); + menu->Append(ID_CHAPTER_MENU, wxT("&Chapters"), chap_menu); + + menu->AppendSeparator(); + menu->Append(VIEW_PLAYLIST, wxT("&Playlist\tCtrl+L"), wxT("Show navigation history as playlist"), wxITEM_CHECK); + menu->Append(ID_CLEAR_NAV, wxT("&Clear History"), wxT("Clear navigation history")); + menu->AppendSeparator(); + menu->Append(FILE_PLAY, wxT("&Play/Pause\tCtrl+P"), wxT("Play/Pause/Resume Presentation")); + menu->Append(FILE_STEP, wxT("&Step-by-Step\tCtrl+S"), wxT("Play/Pause/Resume Presentation")); + menu->Append(FILE_STOP, wxT("&Stop"), wxT("Stop Presentation")); + menu->AppendSeparator(); + menu->Append(FILE_RELOAD_CONFIG, wxT("&Reload Config\tCtrl+R"), wxT("Reload Configuration File")); + menu->Append(FILE_RELOAD, wxT("&Reload File\tCtrl+R"), wxT("Reload Presentation")); + b->Append(menu, wxT("&Play")); + + menu = new wxMenu(); + menu->Append(APP_SHORTCUTS, wxT("&Shortcuts"), wxT("Show keyboard shortcuts")); + menu->Append(APP_NAV_KEYS, wxT("&Navigation Keys"), wxT("Show navigation keys")); + menu->AppendSeparator(); + menu->Append(APP_ABOUT, wxT("&About"), wxT("Display information and copyright")); + b->Append(menu, wxT("&?")); + + SetMenuBar(b); + + m_pStatusbar = CreateStatusBar(1, 0, -1, wxT("statusBar")); + ws[0] = 60; + ws[1] = 70; + ws[2] = -1; + m_pStatusbar->SetFieldsCount(3, ws); + + SetStatusBarPane(2); + wxColour foreCol = m_pStatusbar->GetBackgroundColour(); + SetBackgroundColour(foreCol); + + + m_pTimer = new wxTimer(); + m_pTimer->SetOwner(this, ID_CTRL_TIMER); + m_bGrabbed = 0; + + /*create toolbar*/ + m_pToolBar = CreateToolBar(wxTB_FLAT|wxTB_HORIZONTAL); + m_pOpenFile = new wxBitmap(tool_open_file); + m_pPrev = new wxBitmap(tool_prev); + m_pNext = new wxBitmap(tool_next); + m_pPlay = new wxBitmap(tool_play); + m_pPause = new wxBitmap(tool_pause); + m_pStep = new wxBitmap(tool_step); + m_pStop = new wxBitmap(tool_stop); + m_pInfo = new wxBitmap(tool_info); + m_pConfig = new wxBitmap(tool_config); + m_pSW2D = new wxBitmap(tool_sw_2d); + m_pSW3D = new wxBitmap(tool_sw_3d); + + m_pToolBar->AddTool(GWX_FILE_OPEN, wxT(""), *m_pOpenFile, wxT("Open File")); + m_pToolBar->AddSeparator(); + m_pPrevBut = new wxMenuButton(m_pToolBar, FILE_PREV, *m_pPrev); + m_pPrevBut->SetToolTip(wxT("Previous Location")); + m_pToolBar->AddControl(m_pPrevBut); + m_pNextBut = new wxMenuButton(m_pToolBar, FILE_NEXT, *m_pNext); + m_pNextBut->SetToolTip(wxT("Next Location")); + m_pToolBar->AddControl(m_pNextBut); + + m_pToolBar->AddSeparator(); + m_pToolBar->AddTool(FILE_PLAY, wxT(""), *m_pPlay, wxT("Play/Pause File")); + m_pToolBar->AddTool(FILE_STEP, wxT(""), *m_pStep, wxT("Step-by-Step Mode")); + m_pToolBar->AddTool(FILE_STOP, wxT(""), *m_pStop, wxT("Stop File")); + m_pToolBar->AddSeparator(); + m_pToolBar->AddTool(FILE_PROPERTIES, wxT(""), *m_pInfo, wxT("Show File Information")); + m_pToolBar->AddSeparator(); + m_pToolBar->AddTool(VIEW_OPTIONS, wxT(""), *m_pConfig, wxT("GPAC Configuration")); + m_pToolBar->AddTool(SWITCH_RENDER, wxT(""), *m_pSW3D, wxT("Switch 2D/3D Renderers")); + + m_pToolBar->Realize(); + + m_Address = new wxMyComboBox(this, ID_ADDRESS, wxT(""), wxPoint(50, 0), wxSize(80, 20)); + wxStaticText *add_text = new wxStaticText(this, -1, wxT("URL"), wxPoint(0, 0), wxSize(40, 20)); + add_text->SetBackgroundColour(foreCol); + + m_pAddBar = new wxBoxSizer(wxHORIZONTAL); + m_pAddBar->Add(add_text, 0, wxALIGN_TOP); + m_pAddBar->Add(m_Address, 2, wxALIGN_CENTER|wxEXPAND|wxADJUST_MINSIZE); + m_pAddBar->SetMinSize(80, 32); + m_pAddBar->Layout(); + + m_pProg = new wxSlider(this, ID_SLIDER, 0, 0, 1000, wxPoint(0, 22), wxSize(80, 22), wxSL_HORIZONTAL|wxSUNKEN_BORDER); + m_pProg->Enable(0); + m_pProg->Show(); + m_pProg->SetBackgroundColour(foreCol); + + m_pView = new wxWindow(this, -1, wxDefaultPosition, wxDefaultSize, wxNO_BORDER); + m_pView->SetBackgroundColour(wxColour(wxT("BLACK"))); +#ifdef __WXGTK__ + m_pVisual = new wxWindow(m_pView, -1, wxDefaultPosition, wxDefaultSize, wxNO_BORDER); + m_pVisual->SetBackgroundColour(wxColour(wxT("BLACK"))); +#endif + + m_pPlayList = new wxPlaylist(this); + m_pPlayList->SetIcon(wxIcon(osmo4)); + m_pPlayList->Hide(); + Raise(); + Show(); + + m_connected = 0; + if (!LoadTerminal()) { + Close(TRUE); + return; + } + + + + if (m_bExternalView) SetWindowStyle(wxDEFAULT_FRAME_STYLE & ~(wxMAXIMIZE_BOX | wxRESIZE_BORDER)); + DoLayout(320, 240); + UpdateRenderSwitch(); + + const char *sOpt = gf_cfg_get_key(m_user.config, "General", "ConsoleOff"); + m_console_off = (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0; + sOpt = gf_cfg_get_key(m_user.config, "General", "Loop"); + m_loop = (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0; + sOpt = gf_cfg_get_key(m_user.config, "General", "LookForSubtitles"); + m_lookforsubs = (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0; + gf_term_set_option(m_term, GF_OPT_AUDIO_VOLUME, 100); + + ReloadURLs(); + Raise(); + m_pStatusbar->SetStatusText(wxT("Ready"), 2); + m_LastStatusTime = 0; + + m_pPrevBut->Refresh(); + m_pNextBut->Refresh(); + + wxOsmo4App &app = wxGetApp(); + if (app.argc>1) { + m_pPlayList->QueueURL(wxString(app.argv[1])); + m_pPlayList->RefreshList(); + m_pPlayList->PlayNext(); + } else { + char sPL[GF_MAX_PATH]; + strcpy((char *) sPL, szAppPath); +#ifdef WIN32 + strcat(sPL, "gpac_pl.m3u"); +#else + strcat(sPL, ".gpac_pl.m3u"); +#endif + m_pPlayList->OpenPlaylist(wxString(sPL, wxConvUTF8) ); + const char *sOpt = gf_cfg_get_key(m_user.config, "General", "PLEntry"); + if (sOpt) { + m_pPlayList->m_cur_entry = atoi(sOpt); + if (m_pPlayList->m_cur_entry>=(s32)gf_list_count(m_pPlayList->m_entries)) + m_pPlayList->m_cur_entry = -1; + } + + sOpt = gf_cfg_get_key(m_user.config, "General", "StartupFile"); + if (sOpt) { + gf_term_connect(m_term, sOpt); + m_bStartupFile = 1; + } + } + + sOpt = gf_cfg_get_key(m_user.config, "core", "audio-output"); + + if (!strcmp(sOpt, "No Audio Output Available")) { + ::wxLogMessage(wxT("WARNING: no audio output availble - make sure no other program is locking the sound card")); + SetStatus(wxT("No audio ouput available")); + + } else { + SetStatus(wxT("Ready")); + } +} + +wxOsmo4Frame::~wxOsmo4Frame() +{ + vp_list = NULL; + sel_menu = NULL; + + if (m_user.modules) gf_modules_del(m_user.modules); + gf_sys_close(); + if (m_user.config) gf_cfg_del(m_user.config); + + if (m_chapters_start) gf_free(m_chapters_start); + if (m_pView) delete m_pView; + + //m_pToolBar->RemoveTool(FILE_PREV); + //m_pToolBar->RemoveTool(FILE_NEXT); + + delete m_pPrevBut; + delete m_pNextBut; + delete m_pPlayList; + delete m_pTimer; + delete m_pOpenFile; + delete m_pPrev; + delete m_pNext; + delete m_pPlay; + delete m_pPause; + delete m_pStep; + delete m_pStop; + delete m_pInfo; + delete m_pConfig; + delete m_pSW2D; + delete m_pSW3D; +} + + +BEGIN_EVENT_TABLE(wxOsmo4Frame, wxFrame) + EVT_CLOSE(wxOsmo4Frame::OnCloseApp) + EVT_MENU(GWX_FILE_OPEN, wxOsmo4Frame::OnFileOpen) + EVT_MENU(GWX_FILE_OPEN_URL, wxOsmo4Frame::OnFileOpenURL) + EVT_MENU(FILE_RELOAD_CONFIG, wxOsmo4Frame::OnFileReloadConfig) + EVT_MENU(FILE_RELOAD, wxOsmo4Frame::OnFileReload) + EVT_MENU(FILE_PROPERTIES, wxOsmo4Frame::OnFileProperties) + EVT_MENU(FILE_QUIT, wxOsmo4Frame::OnFileQuit) + EVT_MENU(VIEW_FULLSCREEN, wxOsmo4Frame::OnFullScreen) + EVT_MENU(VIEW_OPTIONS, wxOsmo4Frame::OnOptions) + EVT_MENU(VIEW_AR_KEEP, wxOsmo4Frame::OnViewARKeep) + EVT_MENU(VIEW_AR_FILL, wxOsmo4Frame::OnViewARFill) + EVT_MENU(VIEW_AR_169, wxOsmo4Frame::OnViewAR169) + EVT_MENU(VIEW_AR_43, wxOsmo4Frame::OnViewAR43) + EVT_MENU(VIEW_ORIGINAL, wxOsmo4Frame::OnViewOriginal) + EVT_MENU(VIEW_PLAYLIST, wxOsmo4Frame::OnPlaylist) + EVT_UPDATE_UI(VIEW_PLAYLIST, wxOsmo4Frame::OnUpdatePlayList) + EVT_MENU(FILE_COPY, wxOsmo4Frame::OnFileCopy) + EVT_UPDATE_UI(FILE_COPY, wxOsmo4Frame::OnUpdateFileCopy) + EVT_MENU(FILE_PASTE, wxOsmo4Frame::OnFilePaste) + EVT_UPDATE_UI(FILE_PASTE, wxOsmo4Frame::OnUpdateFilePaste) + + EVT_MENU(ID_CLEAR_NAV, wxOsmo4Frame::OnClearNav) + EVT_UPDATE_UI(ID_STREAM_MENU, wxOsmo4Frame::OnUpdateStreamMenu) + EVT_UPDATE_UI(ID_CHAPTER_MENU, wxOsmo4Frame::OnUpdateChapterMenu) + EVT_MENU(ID_ADD_SUB, wxOsmo4Frame::OnAddSub) + + EVT_MENU(ID_MCACHE_ENABLE, wxOsmo4Frame::OnCacheEnable) + EVT_UPDATE_UI(ID_MCACHE_ENABLE, wxOsmo4Frame::OnUpdateCacheEnable) + EVT_MENU(ID_MCACHE_STOP, wxOsmo4Frame::OnCacheStop) + EVT_MENU(ID_MCACHE_ABORT, wxOsmo4Frame::OnCacheAbort) + EVT_UPDATE_UI(ID_MCACHE_STOP, wxOsmo4Frame::OnUpdateCacheAbort) + EVT_UPDATE_UI(ID_MCACHE_ABORT, wxOsmo4Frame::OnUpdateCacheAbort) + + + EVT_MENU(APP_SHORTCUTS, wxOsmo4Frame::OnShortcuts) + EVT_MENU(APP_NAV_KEYS, wxOsmo4Frame::OnNavInfo) + EVT_MENU(APP_ABOUT, wxOsmo4Frame::OnAbout) + EVT_GPACEVENT(wxOsmo4Frame::OnGPACEvent) + EVT_TIMER(ID_CTRL_TIMER, wxOsmo4Frame::OnTimer) + EVT_COMMAND_SCROLL(ID_SLIDER, wxOsmo4Frame::OnSlide) + EVT_MENU(VIEW_LOGS, wxOsmo4Frame::OnLogs) + EVT_MENU(VIEW_RTI, wxOsmo4Frame::OnRTI) + + EVT_MENUBUTTON_OPEN(FILE_PREV, wxOsmo4Frame::OnFilePrevOpen) + EVT_MENUBUTTON_OPEN(FILE_NEXT, wxOsmo4Frame::OnFileNextOpen) + EVT_MENU(FILE_PREV, wxOsmo4Frame::OnNavPrev) + EVT_UPDATE_UI(FILE_PREV, wxOsmo4Frame::OnUpdateNavPrev) + EVT_MENU_RANGE(ID_NAV_PREV_0, ID_NAV_PREV_9, wxOsmo4Frame::OnNavPrevMenu) + EVT_MENU(FILE_NEXT, wxOsmo4Frame::OnNavNext) + EVT_UPDATE_UI(FILE_NEXT, wxOsmo4Frame::OnUpdateNavNext) + EVT_MENU_RANGE(ID_NAV_NEXT_0, ID_NAV_NEXT_9, wxOsmo4Frame::OnNavNextMenu) + + EVT_TOOL(FILE_PLAY, wxOsmo4Frame::OnFilePlay) + EVT_TOOL(FILE_STEP, wxOsmo4Frame::OnFileStep) + EVT_TOOL(FILE_STOP, wxOsmo4Frame::OnFileStop) + EVT_TOOL(SWITCH_RENDER, wxOsmo4Frame::OnRenderSwitch) + + EVT_COMBOBOX(ID_ADDRESS, wxOsmo4Frame::OnURLSelect) + + EVT_MENU_RANGE(ID_SELSTREAM_0, ID_SELSTREAM_9, wxOsmo4Frame::OnStreamSel) + EVT_UPDATE_UI_RANGE(ID_SELSTREAM_0, ID_SELSTREAM_9, wxOsmo4Frame::OnUpdateStreamSel) + + EVT_MENU_RANGE(ID_SETCHAP_FIRST, ID_SETCHAP_LAST, wxOsmo4Frame::OnChapterSel) + EVT_UPDATE_UI_RANGE(ID_SETCHAP_FIRST, ID_SETCHAP_LAST, wxOsmo4Frame::OnUpdateChapterSel) + + EVT_MENU_RANGE(ID_VIEWPOINT_FIRST, ID_VIEWPOINT_LAST, wxOsmo4Frame::OnViewport) + EVT_UPDATE_UI_RANGE(ID_VIEWPOINT_FIRST, ID_VIEWPOINT_LAST, wxOsmo4Frame::OnUpdateViewport) + + EVT_MENU_RANGE(ID_NAVIGATE_NONE, ID_NAVIGATE_GAME, wxOsmo4Frame::OnNavigate) + EVT_UPDATE_UI_RANGE(ID_NAVIGATE_NONE, ID_NAVIGATE_GAME, wxOsmo4Frame::OnUpdateNavigation) + EVT_MENU(ID_NAVIGATE_RESET, wxOsmo4Frame::OnNavigateReset) + + EVT_MENU_RANGE(ID_COLLIDE_NONE, ID_COLLIDE_DISP, wxOsmo4Frame::OnCollide) + EVT_UPDATE_UI_RANGE(ID_COLLIDE_NONE, ID_COLLIDE_DISP, wxOsmo4Frame::OnUpdateCollide) + + EVT_MENU(ID_HEADLIGHT, wxOsmo4Frame::OnHeadlight) + EVT_UPDATE_UI(ID_HEADLIGHT, wxOsmo4Frame::OnUpdateHeadlight) + EVT_MENU(ID_GRAVITY, wxOsmo4Frame::OnGravity) + EVT_UPDATE_UI(ID_GRAVITY, wxOsmo4Frame::OnUpdateGravity) + + EVT_UPDATE_UI(FILE_PROPERTIES, wxOsmo4Frame::OnUpdateNeedsConnect) + EVT_UPDATE_UI(FILE_RELOAD, wxOsmo4Frame::OnUpdateNeedsConnect) + EVT_UPDATE_UI(FILE_PLAY, wxOsmo4Frame::OnUpdatePlay) + EVT_UPDATE_UI(FILE_STOP, wxOsmo4Frame::OnUpdateNeedsConnect) + EVT_UPDATE_UI(FILE_STEP, wxOsmo4Frame::OnUpdateNeedsConnect) + EVT_UPDATE_UI(VIEW_ORIGINAL, wxOsmo4Frame::OnUpdateNeedsConnect) + EVT_UPDATE_UI(VIEW_FULLSCREEN, wxOsmo4Frame::OnUpdateFullScreen) + EVT_UPDATE_UI(VIEW_AR_KEEP, wxOsmo4Frame::OnUpdateAR) + EVT_UPDATE_UI(VIEW_AR_FILL, wxOsmo4Frame::OnUpdateAR) + EVT_UPDATE_UI(VIEW_AR_169, wxOsmo4Frame::OnUpdateAR) + EVT_UPDATE_UI(VIEW_AR_43, wxOsmo4Frame::OnUpdateAR) + + EVT_SIZE(wxOsmo4Frame::OnSize) +END_EVENT_TABLE() + +void wxOsmo4Frame::DoLayout(u32 v_width, u32 v_height) +{ + wxPoint pos; + if (!m_Address || !m_pProg) return; + + int t_h = m_pToolBar->GetSize().y; + int a_h = m_pAddBar->GetSize().y; + int p_h = m_pProg->GetSize().y; + + if (m_bExternalView) { + if (v_width && v_height) { + m_orig_width = v_width; + m_orig_height = v_height; + } + SetClientSize(320, a_h+p_h+t_h); + m_pAddBar->SetDimension(0,0, 320, a_h); + m_pProg->SetSize(0, t_h+a_h, 320, p_h, 0); + return; + } + + if (v_width && v_height) { + m_orig_width = v_width; + m_orig_height = v_height; + v_height += a_h + p_h + t_h; + SetClientSize(v_width, v_height); + m_pView->SetSize(0, a_h+t_h, v_width, v_height, 0); + m_pAddBar->SetDimension(0, t_h, v_width, a_h); + m_pProg->SetSize(0, v_height - p_h, v_width, p_h, 0); + } + wxSize s = GetClientSize(); + s.y -= a_h + p_h + t_h; + if (m_pView) { + m_pView->SetSize(0, a_h+t_h, s.x, s.y, 0); + m_pAddBar->SetDimension(0, 0, s.x, a_h); + m_pAddBar->SetDimension(0, 0, s.x, a_h); + m_pAddBar->Layout(); + m_pProg->SetSize(0, s.y+t_h+a_h, s.x, p_h, 0); + if (m_term) gf_term_set_size(m_term, s.x, s.y); + } +} + +void wxOsmo4Frame::OnSize(wxSizeEvent &event) +{ + DoLayout(); +} + +void wxOsmo4Frame::OnCloseApp(wxCloseEvent &WXUNUSED(event)) +{ + if (m_term) gf_term_del(m_term); + m_term = NULL; + Destroy(); +} + + +wxString wxOsmo4Frame::GetFileFilter() +{ + u32 keyCount, i; + wxString sFiles, sSupportedFiles, sExts; + + /*force MP4 and 3GP files at beginning to make sure they are selected (Win32 bug with too large filters)*/ + sSupportedFiles = wxT("All Known Files|*.m3u;*.pls;*.mp4;*.3gp;*.3g2"); + sExts = wxT(""); + sFiles = wxT(""); + keyCount = gf_cfg_get_key_count(m_user.config, "MimeTypes"); + for (i=0; i=0) continue; + /*if same extensions for # mime types skip (don't polluate the file list)*/ + if (sExts.Find(wxString(szKeyList, wxConvUTF8) )>=0) continue; + + sExts += wxString(szKeyList, wxConvUTF8); + sExts += wxT(" "); + sFiles += wxString(sDesc, wxConvUTF8); + sFiles += wxT("|"); + + wxString sOpt = wxString(szKeyList, wxConvUTF8); + while (1) { + wxString ext = sOpt.BeforeFirst(' '); + if (ext.Find('.')<0) { + if (first) first = 0; + else sFiles += wxT(";"); + sFiles += wxT("*."); + sFiles += ext; + wxString sext = ext; + sext += wxT(";"); + if (sSupportedFiles.Find(sext)<0) { + sSupportedFiles += wxT(";*."); + sSupportedFiles += ext; + } + } + if (sOpt==ext) break; + wxString rem = ext + wxT(" "); + sOpt.Replace(rem, wxT(""), TRUE); + } + sFiles += wxT("|"); + } + sSupportedFiles += wxT("|"); + sSupportedFiles += sFiles; + sSupportedFiles += wxT("M3U Playlists|*.m3u|ShoutCast Playlists|*.pls|All Files|*.*||"); + return sSupportedFiles; +} + +void wxOsmo4Frame::OnFileOpen(wxCommandEvent & WXUNUSED(event)) +{ + wxFileDialog dlg(this, wxT("Select file(s)"), wxT(""), wxT(""), GetFileFilter(), wxOPEN | wxMULTIPLE | wxCHANGE_DIR /*| wxHIDE_READONLY*/); + + if (dlg.ShowModal() != wxID_OK) return; + + wxArrayString stra; + dlg.GetPaths(stra); + if (stra.GetCount() == 1) { + m_pPlayList->Truncate(); + } else { + m_pPlayList->Clear(); + } + for (u32 i=0; iQueueURL(stra[i]); + + m_pPlayList->RefreshList(); + m_pPlayList->PlayNext(); +} + +void wxOsmo4Frame::OnFileOpenURL(wxCommandEvent & WXUNUSED(event)) +{ + OpenURLDlg dlg(this, m_user.config); + if (dlg.ShowModal()==wxID_OK) { + m_pPlayList->Truncate(); + m_pPlayList->QueueURL(dlg.m_urlVal); + m_pPlayList->RefreshList(); + m_pPlayList->PlayNext(); + } +} + +void wxOsmo4Frame::OnFileProperties(wxCommandEvent & WXUNUSED(event)) +{ + wxFileProps dlg(this); + dlg.SetIcon(wxIcon(osmo4)); + dlg.ShowModal(); +} + +void wxOsmo4Frame::OnFileReload(wxCommandEvent & WXUNUSED(event)) +{ + gf_term_disconnect(m_term); + m_connected = 0; + DoConnect(); +} + +void wxOsmo4Frame::OnFileReloadConfig(wxCommandEvent & WXUNUSED(event)) +{ + gf_term_set_option(m_term, GF_OPT_RELOAD_CONFIG, 1); +} + +void wxOsmo4Frame::OnFileQuit(wxCommandEvent & WXUNUSED(event)) +{ + Close(FALSE); +} + +void wxOsmo4Frame::OnViewOriginal(wxCommandEvent & WXUNUSED(event)) +{ + if (!m_bExternalView) { + DoLayout(m_orig_width, m_orig_height); + } else { + gf_term_set_option(m_term, GF_OPT_ORIGINAL_VIEW, 1); + } +} + +void wxOsmo4Frame::OnOptions(wxCommandEvent & WXUNUSED(event)) +{ + wxGPACControl dlg(this); + dlg.SetIcon(wxIcon(osmo4)); + dlg.ShowModal(); +} + +void wxOsmo4Frame::DoConnect() +{ + //if (m_connected) { gf_term_disconnect(m_term); m_connected = 0; } + + wxString url = m_pPlayList->GetURL(); + m_Address->SetValue(url); +#ifdef __WXGTK__ + m_pVisual->SetFocus(); +#else + m_pView->SetFocus(); +#endif + wxString txt = wxT("Osmo4 - "); + txt += m_pPlayList->GetDisplayName(); + SetTitle(txt); + m_bStartupFile = 0; + gf_term_connect(m_term, url.mb_str(wxConvUTF8)); +} + +void wxOsmo4Frame::OnLogs(wxCommandEvent & WXUNUSED(event)) +{ + m_pLogs->Show(); +} + +void wxOsmo4Frame::OnUpdateNeedsConnect(wxUpdateUIEvent &event) +{ + event.Enable(m_connected ? 1 : 0); +} + +void wxOsmo4Frame::OnUpdatePlay(wxUpdateUIEvent &event) +{ + event.Enable( (m_connected || m_pPlayList->HasValidEntries()) ? 1 : 0); +} + +void wxOsmo4Frame::OnUpdateFullScreen(wxUpdateUIEvent &event) +{ + if (m_connected) { + event.Enable(1); + event.Check(gf_term_get_option(m_term, GF_OPT_FULLSCREEN) ? 1 : 0); + } else { + event.Enable(0); + } +} + +void wxOsmo4Frame::OnFullScreen(wxCommandEvent & WXUNUSED(event)) +{ + Bool isFS = gf_term_get_option(m_term, GF_OPT_FULLSCREEN) ? 1 : 0; + gf_term_set_option(m_term, GF_OPT_FULLSCREEN, isFS ? 0 : 1); +} + +void wxOsmo4Frame::OnViewARKeep(wxCommandEvent & WXUNUSED(event)) +{ + gf_term_set_option(m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_KEEP); +} +void wxOsmo4Frame::OnViewARFill(wxCommandEvent & WXUNUSED(event)) +{ + gf_term_set_option(m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_FILL_SCREEN); +} +void wxOsmo4Frame::OnViewAR169(wxCommandEvent & WXUNUSED(event)) +{ + gf_term_set_option(m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_16_9); +} +void wxOsmo4Frame::OnViewAR43(wxCommandEvent & WXUNUSED(event)) +{ + gf_term_set_option(m_term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_4_3); +} + +void wxOsmo4Frame::OnUpdateAR(wxUpdateUIEvent &event) +{ + if (!m_connected) { + event.Enable(0); + return; + } + event.Enable(1); + u32 val = gf_term_get_option(m_term, GF_OPT_ASPECT_RATIO); + if ((event.GetId() == VIEW_AR_FILL) && (val==GF_ASPECT_RATIO_FILL_SCREEN)) + event.Check(1); + else if ((event.GetId() == VIEW_AR_KEEP) && (val==GF_ASPECT_RATIO_KEEP)) + event.Check(1); + else if ((event.GetId() == VIEW_AR_169) && (val==GF_ASPECT_RATIO_16_9)) + event.Check(1); + else if ((event.GetId() == VIEW_AR_43) && (val==GF_ASPECT_RATIO_4_3)) + event.Check(1); + else event.Check(0); +} + +void wxOsmo4Frame::OnShortcuts(wxCommandEvent & WXUNUSED(event)) +{ + wxMessageDialog dlg(this, + wxT("Shortcuts with focus on main frame:\n") + wxT("Open File: Ctrl + O\n") + wxT("Show File Information: Ctrl + I\n") + wxT("Reload File: Ctrl + R\n") + wxT("Pause/Resume File: Ctrl + P\n") + wxT("Step by Step: Ctrl + S\n") + wxT("Fullscreen On/Off: Alt + Return\n") + wxT("View Playlist: Ctrl + L\n") + wxT("Aspect Ratio Normal: Ctrl + 1\n") + wxT("Aspect Ratio Fill: Ctrl + 2\n") + wxT("Aspect Ratio 4/3: Ctrl + 3\n") + wxT("Aspect Ratio 16/9: Ctrl + 4\n") + wxT("\n") + wxT("Shortcuts with focus on video frame:\n") + wxT("Seek +5% into presentation: Alt + right arrow\n") + wxT("Seek -5% into presentation: Alt + left arrow\n") + wxT("Seek +1min into presentation: Alt + up arrow\n") + wxT("Seek -1min into presentation: Alt + down arrow\n") + wxT("Next Playlist Entry: Ctrl + right arrow\n") + wxT("Prev Playlist Entry: Ctrl + left arrow\n") + + , wxT("Shortcuts Available on Osmo4") + , wxOK); + + dlg.ShowModal(); +} + +void wxOsmo4Frame::OnNavInfo(wxCommandEvent & WXUNUSED(event)) +{ + wxMessageDialog dlg(this, + wxT("* Walk & Fly modes:\n") + wxT("\tH move: H pan - V move: Z-translate - V move+CTRL or Wheel: V pan - Right Click (Walk only): Jump\n") + wxT("\tleft/right: H pan - left/right+CTRL: H translate - up/down: Z-translate - up/down+CTRL: V pan\n") + wxT("* Pan mode:\n") + wxT("\tH move: H pan - V move: V pan - V move+CTRL or Wheel: Z-translate\n") + wxT("\tleft/right: H pan - left/right+CTRL: H translate - up/down: V pan - up/down+CTRL: Z-translate\n") + wxT("* Slide mode:\n") + wxT("\tH move: H translate - V move: V translate - V move+CTRL or Wheel: Z-translate\n") + wxT("\tleft/right: H translate - left/right+CTRL: H pan - up/down: V translate - up/down+CTRL: Z-translate\n") + wxT("* Examine & Orbit mode:\n") + wxT("\tH move: Y-Axis rotate - H move+CTRL: No move - V move: X-Axis rotate - V move+CTRL or Wheel: Z-translate\n") + wxT("\tleft/right: Y-Axis rotate - left/right+CTRL: H translate - up/down: X-Axis rotate - up/down+CTRL: Y-translate\n") + wxT("* VR mode:\n") + wxT("\tH move: H pan - V move: V pan - V move+CTRL or Wheel: Camera Zoom\n") + wxT("\tleft/right: H pan - up/down: V pan - up/down+CTRL: Camera Zoom\n") + wxT("* Game mode (press END to escape):\n") + wxT("\tH move: H pan - V move: V pan\n") + wxT("\tleft/right: H translate - up/down: Z-translate\n") + wxT("\n") + wxT("* All 3D modes: CTRL+PGUP/PGDOWN will zoom in/out camera (field of view) \n") + wxT("\n") + wxT("*Slide Mode in 2D:\n") + wxT("\tH move: H translate - V move: V translate - V move+CTRL: zoom\n") + wxT("\tleft/right: H translate - up/down: V translate - up/down+CTRL: zoom\n") + wxT("*Examine Mode in 2D (3D renderer only):\n") + wxT("\tH move: Y-Axis rotate - V move: X-Axis rotate\n") + wxT("\tleft/right: Y-Axis rotate - up/down: X-Axis rotate\n") + wxT("\n") + wxT("HOME: reset navigation to last viewpoint (2D or 3D navigation)\n") + wxT("SHIFT key in all modes: fast movement\n") + + , wxT("3D navigation keys (\'H\'orizontal and \'V\'ertical) used in GPAC") + , wxOK ); + dlg.ShowModal(); +} + + +/*open file dlg*/ +class AboutDlg : public wxDialog { +public: + AboutDlg(wxWindow *parent); + +private: + wxStaticText *m_info; + wxButton *m_close; + void OnClose(wxCommandEvent& event); + DECLARE_EVENT_TABLE() +}; + +BEGIN_EVENT_TABLE(AboutDlg, wxDialog) + EVT_BUTTON(ID_ABOUT_CLOSE, AboutDlg::OnClose) +END_EVENT_TABLE() + +AboutDlg::AboutDlg(wxWindow *parent) + : wxDialog(parent, -1, wxString(wxT("GPAC/Osmo4 V ")wxT(GPAC_FULL_VERSION))) +{ + SetSize(220, 320); + Centre(); + wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); + + m_info = new wxStaticText(this, -1, wxT("http://gpac.io"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE); + sizer->Add(m_info, 1, wxEXPAND|wxADJUST_MINSIZE, 0); + m_close = new wxButton(this, ID_ABOUT_CLOSE, wxT("Close"), wxDefaultPosition, wxSize(120, 20)); + sizer->Add(m_close, 0, wxEXPAND, 0); + + SetIcon(wxIcon(osmo4)); + m_info->SetLabel( + wxT("Osmo4 Player\n") + wxT("GPAC Multimedia Framework\n") + wxT("\n") + wxT("This program is gf_free software and may\n") + wxT("be distributed according to the terms\n") + wxT("of the GNU Lesser General Public License\n") + wxT("\n") + wxT("Authors: Jean Le Feuvre\n") + wxT("(c) Telecom ParisTech 2005-2012\n") + wxT("All Rights Reserved\n") + wxT("http://gpac.io\n") + wxT("\n") + wxT(" ** With Many Thanks To ** \n\n") + wxT("Mozilla SpiderMonkey (JavaScript)\n") + wxT("The FreeType Project\n") + wxT("The PNG Group, The I.J.G.\n") + wxT("FFMPEG, FAAD, XVID, MAD\n") + ); + + SetSizer(sizer); + sizer->Fit(this); +} +void AboutDlg::OnClose(wxCommandEvent& WXUNUSED(event)) +{ + Close(FALSE); +} + +void wxOsmo4Frame::OnAbout(wxCommandEvent & WXUNUSED(event)) +{ + AboutDlg dlg(this); + dlg.ShowModal(); +} + + +void wxOsmo4Frame::OnGPACEvent(wxGPACEvent &event) +{ + wxString cmd; + wxCommandEvent evt; + if (!m_term) return; + + switch (event.gpac_evt.type) { + case GF_EVENT_NAVIGATE: + if (gf_term_is_supported_url(m_term, event.to_url.mb_str(wxConvUTF8), 1, 0)) { + char *str = gf_url_concatenate(m_pPlayList->GetURL().mb_str(wxConvUTF8), event.to_url.mb_str(wxConvUTF8)); + if (str) { + m_pPlayList->Truncate(); + m_pPlayList->QueueURL(wxString(str, wxConvUTF8)); + m_pPlayList->RefreshList(); + gf_free(str); + m_pPlayList->PlayNext(); + } + return; + } + cmd = get_pref_browser(m_user.config); + if (cmd.IsEmpty()) { + cmd += wxT(" "); + cmd += event.to_url; + wxExecute(cmd); + } else { +#ifdef wxLaunchDefaultBrowser + wxLaunchDefaultBrowser(event.to_url); +#endif + } + break; + case GF_EVENT_QUIT: + Close(TRUE); + break; + case GF_EVENT_SET_CAPTION: + SetTitle(event.to_url); + break; + case GF_EVENT_CONNECT: + BuildStreamList(0); + ConnectAcknowledged(event.gpac_evt.connect.is_connected); + break; + case GF_EVENT_KEYDOWN: + if (!(event.gpac_evt.key.flags & GF_KEY_MOD_CTRL)) return; + switch (event.gpac_evt.key.key_code) { + case GF_KEY_R: + gf_term_set_option(m_term, GF_OPT_REFRESH, 1); + break; + case GF_KEY_P: + OnFilePlay(evt); + break; + case GF_KEY_S: + OnFileStep(evt); + break; + } + break; + case GF_EVENT_SCENE_SIZE: + m_orig_width = event.gpac_evt.size.width; + m_orig_height = event.gpac_evt.size.height; + case GF_EVENT_SIZE: + if (! gf_term_get_option(m_term, GF_OPT_FULLSCREEN)) { + DoLayout(event.gpac_evt.size.width, event.gpac_evt.size.height); + } + break; + case GF_EVENT_VIEWPOINTS: + BuildViewList(); + break; + case GF_EVENT_STREAMLIST: + BuildStreamList(0); + break; + } +} + + +static wxString format_time(u32 duration, u32 timescale) +{ + u32 h, m, s; + Float time = duration; + time /= timescale; + time *= 1000; + h = (u32) (time / 1000 / 3600); + m = (u32) (time / 1000 / 60 - h*60); + s = (u32) (time / 1000 - h*3600 - m*60); + return wxString::Format(wxT("%02d:%02d:%02d"), h, m, s); +} + +void wxOsmo4Frame::SetStatus(wxString str) +{ + //m_pStatusbar->SetStatusText(str, 2); + m_LastStatusTime = gf_sys_clock(); +} + +#define RTI_REFRESH_MS 500 +void wxOsmo4Frame::OnRTI(wxCommandEvent & event) +{ + m_bViewRTI = event.IsChecked(); + if (m_bViewRTI) { + if (!m_pTimer->IsRunning()) m_pTimer->Start(RTI_REFRESH_MS, 0); + } else if (!m_connected && m_pTimer->IsRunning()) { + m_LastStatusTime = 0; + m_pStatusbar->SetStatusText(wxT("Ready"), 2); + m_pTimer->Stop(); + } +} + +void wxOsmo4Frame::OnTimer(wxTimerEvent& WXUNUSED(event)) +{ + wxString str; + u32 now; + if (m_LastStatusTime) { + now = gf_sys_clock(); + if (now > 1000+m_LastStatusTime) { + m_LastStatusTime = 0; + m_pStatusbar->SetStatusText(wxT("Ready"), 2); + } + } + + if (m_bViewRTI) { + GF_SystemRTInfo rti; + if (!gf_sys_get_rti(RTI_REFRESH_MS, &rti, 0)) return; + if (rti.gpac_memory) rti.process_memory = rti.gpac_memory; + + str = wxString::Format(wxT("CPU %02d (%02d) - Mem %d kB" ), + rti.total_cpu_usage, rti.process_cpu_usage, rti.gpac_memory/1024); + + m_pStatusbar->SetStatusText(str, 2); + } + if (!m_connected) return; + + now = gf_term_get_time_in_ms(m_term); + if (!now) return; + + if (!m_duration) { + str = format_time(now, 1000); + m_pStatusbar->SetStatusText(str); + str = wxString::Format(wxT("FPS %.2f"), gf_term_get_framerate(m_term, 0)); + m_pStatusbar->SetStatusText(str, 1); + return; + } +#ifdef __WXGTK__ + if (m_bGrabbed) { + u32 now = gf_sys_clock() - m_last_grab_time; + if (now>200) { + m_bGrabbed = 0; + Double res = (Double) m_last_grab_pos; + res /= 1000; + res *= m_duration; + if (gf_term_get_option(m_term, GF_OPT_PLAY_STATE)==GF_STATE_PAUSED) { + gf_term_set_option(m_term, GF_OPT_PLAY_STATE, GF_STATE_PLAYING); + m_bToReset = 0; + } + gf_term_play_from_time(m_term, (u32) res, 0); + return; + } + } +#endif + + if (!m_bGrabbed) { + if ((now >= m_duration + 500) && gf_term_get_option(m_term, GF_OPT_IS_FINISHED)) { + m_pPlayList->PlayNext(); + } else { + Double val = now * 1000; + val /= m_duration; + m_pProg->SetValue((val<=1000) ? (u32) val : 1000); + + if (0) { + str = format_time(m_duration-now, 1000); + } else { + str = format_time(now, 1000); + } + m_pStatusbar->SetStatusText(str); + str = wxString::Format(wxT("FPS %.2f"), gf_term_get_framerate(m_term, 0)); + m_pStatusbar->SetStatusText(str, 1); + } + } +} + +void wxOsmo4Frame::ConnectAcknowledged(Bool bOk) +{ + if (bOk) { + m_pTimer->Start(RTI_REFRESH_MS, 0); + m_connected = 1; + m_bToReset = 0; + UpdatePlay(); + BuildChapterList(0); + } else { + BuildChapterList(1); + if (!m_connected) { + UpdatePlay(); + m_pTimer->Stop(); + //m_pProg->Enable(0); + } + } +} + +void wxOsmo4Frame::OnFilePlay(wxCommandEvent & WXUNUSED(event)) +{ + wxCommandEvent evt; + if (m_connected) { + if (gf_term_get_option(m_term, GF_OPT_PLAY_STATE)==GF_STATE_PAUSED) { + gf_term_set_option(m_term, GF_OPT_PLAY_STATE, GF_STATE_PLAYING); + if (m_bToReset) { + m_pTimer->Start(100, 0); + gf_term_play_from_time(m_term, 0, 0); + } + m_bToReset = 0; + UpdatePlay(); + } else { + gf_term_set_option(m_term, GF_OPT_PLAY_STATE, GF_STATE_PAUSED); + UpdatePlay(); + } + } else { + m_pPlayList->Play(); + } +} + +void wxOsmo4Frame::OnFileStep(wxCommandEvent & WXUNUSED(event)) +{ + wxCommandEvent evt; + gf_term_set_option(m_term, GF_OPT_PLAY_STATE, GF_STATE_STEP_PAUSE); + UpdatePlay(); +} + +void wxOsmo4Frame::OnFileStop(wxCommandEvent &WXUNUSED(event)) +{ + Stop(); +} + +void wxOsmo4Frame::Stop() +{ + if (gf_term_get_option(m_term, GF_OPT_PLAY_STATE)==GF_STATE_PLAYING) { + gf_term_set_option(m_term, GF_OPT_PLAY_STATE, GF_STATE_PAUSED); + } + m_bToReset = 1; + m_pTimer->Stop(); + m_pProg->SetValue(0); + UpdatePlay(); +} + +void wxOsmo4Frame::OnSlide(wxScrollEvent &event) +{ + if (!m_duration) return; + + /*wxSlider on GTK is buggy, so track a release timeout*/ +#ifdef __WXGTK__ + m_last_grab_time = gf_sys_clock(); + m_bGrabbed = 1; + m_last_grab_pos = event.GetPosition(); + Double now = (Double) m_last_grab_pos; + now /= 1000; + now *= m_duration; + wxString str = format_time((u32) (now), 1000); + m_pStatusbar->SetStatusText(str); + if (!m_pTimer->IsRunning()) m_pTimer->Start(100, 0); +#else + s32 type = event.GetEventType(); + if (type == wxEVT_SCROLL_THUMBTRACK) { + m_bGrabbed = 1; + Double now = (Double) event.GetPosition(); + now /= 1000; + now *= m_duration; + wxString str = format_time((u32) (now), 1000); + m_pStatusbar->SetStatusText(str); + } + else if (m_bGrabbed) { + m_bGrabbed = 0; + Double res = (Double) m_pProg->GetValue(); + res /= 1000; + res *= m_duration; + if (gf_term_get_option(m_term, GF_OPT_PLAY_STATE)==GF_STATE_PAUSED) { + gf_term_set_option(m_term, GF_OPT_PLAY_STATE, GF_STATE_PLAYING); + m_bToReset = 0; + if (!m_pTimer->IsRunning()) m_pTimer->Start(100, 0); + } + gf_term_play_from_time(m_term, (u32) res, 0); + } +#endif +} + + +void wxOsmo4Frame::BuildViewList() +{ + if (!vp_list || !m_connected) return; + + while (vp_list->GetMenuItemCount()) { + wxMenuItem* it = vp_list->FindItemByPosition(0); + vp_list->Delete(it); + } + + s32 id = ID_VIEWPOINT_FIRST; + nb_viewpoints = 0; + while (1) { + const char *szName; + Bool bound; + GF_Err e = gf_term_get_viewpoint(m_term, nb_viewpoints+1, &szName, &bound); + if (e) break; + if (szName) { + vp_list->AppendCheckItem(id+nb_viewpoints, wxString(szName, wxConvUTF8) ); + } else { + vp_list->AppendCheckItem(id+nb_viewpoints, wxString::Format(wxT("Viewpoint #%d"), nb_viewpoints+1) ); + } + nb_viewpoints++; + } +} + +void wxOsmo4Frame::OnViewport(wxCommandEvent & event) +{ + u32 ID = event.GetId() - ID_VIEWPOINT_FIRST; + gf_term_set_viewpoint(m_term, ID+1, NULL); +} + +void wxOsmo4Frame::OnUpdateViewport(wxUpdateUIEvent & event) +{ + u32 ID = event.GetId() - ID_VIEWPOINT_FIRST; + const char *szName; + Bool bound; + gf_term_get_viewpoint(m_term, ID+1, &szName, &bound); + event.Enable(1); + if (bound) event.Check(1); +} + +void wxOsmo4Frame::OnNavigate(wxCommandEvent & event) +{ + switch (event.GetId()) { + case ID_NAVIGATE_NONE: + gf_term_set_option(m_term, GF_OPT_NAVIGATION, GF_NAVIGATE_NONE); + break; + case ID_NAVIGATE_WALK: + gf_term_set_option(m_term, GF_OPT_NAVIGATION, GF_NAVIGATE_WALK); + break; + case ID_NAVIGATE_FLY: + gf_term_set_option(m_term, GF_OPT_NAVIGATION, GF_NAVIGATE_FLY); + break; + case ID_NAVIGATE_EXAMINE: + gf_term_set_option(m_term, GF_OPT_NAVIGATION, GF_NAVIGATE_EXAMINE); + break; + case ID_NAVIGATE_PAN: + gf_term_set_option(m_term, GF_OPT_NAVIGATION, GF_NAVIGATE_PAN); + break; + case ID_NAVIGATE_SLIDE: + gf_term_set_option(m_term, GF_OPT_NAVIGATION, GF_NAVIGATE_SLIDE); + break; + case ID_NAVIGATE_ORBIT: + gf_term_set_option(m_term, GF_OPT_NAVIGATION, GF_NAVIGATE_ORBIT); + break; + case ID_NAVIGATE_GAME: + gf_term_set_option(m_term, GF_OPT_NAVIGATION, GF_NAVIGATE_GAME); + break; + } +} +void wxOsmo4Frame::OnNavigateReset(wxCommandEvent & WXUNUSED(event)) +{ + gf_term_set_option(m_term, GF_OPT_NAVIGATION_TYPE, 0); +} +void wxOsmo4Frame::OnUpdateNavigation(wxUpdateUIEvent & event) +{ + u32 ID = event.GetId(); + event.Enable(0); + if (!m_connected) return; + u32 type = gf_term_get_option(m_term, GF_OPT_NAVIGATION_TYPE); + bool enable = type ? 1 : 0; + + u32 mode = gf_term_get_option(m_term, GF_OPT_NAVIGATION); + /*common 2D/3D modes*/ + if (ID==ID_NAVIGATE_NONE) { + event.Enable(enable); + event.Check(mode ? 0 : 1); + } + else if (ID==ID_NAVIGATE_EXAMINE) { + event.Enable(enable); + event.Check((mode==GF_NAVIGATE_EXAMINE) ? 1 : 0); + } + else if (ID==ID_NAVIGATE_SLIDE) { + event.Enable(enable); + event.Check((mode==GF_NAVIGATE_SLIDE) ? 1 : 0); + } + + if (type==GF_NAVIGATE_TYPE_2D) return; + event.Enable(enable); + if (ID==ID_NAVIGATE_WALK) event.Check((mode==GF_NAVIGATE_WALK) ? 1 : 0); + else if (ID==ID_NAVIGATE_FLY) event.Check((mode==GF_NAVIGATE_FLY) ? 1 : 0); + else if (ID==ID_NAVIGATE_PAN) event.Check((mode==GF_NAVIGATE_PAN) ? 1 : 0); + else if (ID==ID_NAVIGATE_ORBIT) event.Check((mode==GF_NAVIGATE_ORBIT) ? 1 : 0); + else if (ID==ID_NAVIGATE_GAME) event.Check((mode==GF_NAVIGATE_GAME) ? 1 : 0); +} + +void wxOsmo4Frame::OnRenderSwitch(wxCommandEvent &WXUNUSED(event)) +{ + const char *opt = gf_cfg_get_key(m_user.config, "Compositor", "ForceOpenGL"); + Bool use_gl = (opt && !stricmp(opt, "yes")) ? 1 : 0; + gf_cfg_set_key(m_user.config, "Compositor", "ForceOpenGL", use_gl ? "no" : "yes"); + + gf_term_set_option(m_term, GF_OPT_USE_OPENGL, !use_gl); + + UpdateRenderSwitch(); +} + +void wxOsmo4Frame::UpdateRenderSwitch() +{ + const char *opt = gf_cfg_get_key(m_user.config, "Compositor", "ForceOpenGL"); + m_pToolBar->RemoveTool(SWITCH_RENDER); + if (opt && !stricmp(opt, "yes")) + m_pToolBar->InsertTool(12, SWITCH_RENDER, *m_pSW2D, wxNullBitmap, FALSE, NULL, wxT("2D Rasterizer")); + else + m_pToolBar->InsertTool(12, SWITCH_RENDER, *m_pSW3D, wxNullBitmap, FALSE, NULL, wxT("OpenGL Rendering")); + +#ifdef WIN32 + /*there's a display bug with the menubtn, remove and reinsert*/ + m_pToolBar->RemoveTool(FILE_PREV); + m_pToolBar->RemoveTool(FILE_NEXT); + m_pToolBar->InsertControl(2, m_pPrevBut); + m_pToolBar->InsertControl(3, m_pNextBut); +#endif + m_pToolBar->Realize(); +} + +void wxOsmo4Frame::UpdatePlay() +{ + m_pToolBar->RemoveTool(FILE_PLAY); + if (m_connected) { + if (gf_term_get_option(m_term, GF_OPT_PLAY_STATE)==GF_STATE_PAUSED) + m_pToolBar->InsertTool(5, FILE_PLAY, *m_pPlay, wxNullBitmap, FALSE, NULL, wxT("Pause File")); + else + m_pToolBar->InsertTool(5, FILE_PLAY, *m_pPause, wxNullBitmap, FALSE, NULL, wxT("Play File")); + } else { + m_pToolBar->InsertTool(5, FILE_PLAY, *m_pPlay, wxNullBitmap, FALSE, NULL, wxT("Pause File")); + } + +#ifdef WIN32 + /*there's a display bug with the menubtn, remove and reinsert*/ + m_pToolBar->RemoveTool(FILE_PREV); + m_pToolBar->RemoveTool(FILE_NEXT); + m_pToolBar->InsertControl(2, m_pPrevBut); + m_pToolBar->InsertControl(3, m_pNextBut); +#endif + m_pToolBar->Realize(); +} + +void wxOsmo4Frame::OnCollide(wxCommandEvent & event) +{ + u32 ID = event.GetId(); + if (ID==ID_COLLIDE_NONE) gf_term_set_option(m_term, GF_OPT_COLLISION, GF_COLLISION_NONE); + else if (ID==ID_COLLIDE_REG) gf_term_set_option(m_term, GF_OPT_COLLISION, GF_COLLISION_NORMAL); + else if (ID==ID_COLLIDE_DISP) gf_term_set_option(m_term, GF_OPT_COLLISION, GF_COLLISION_DISPLACEMENT); +} +void wxOsmo4Frame::OnUpdateCollide(wxUpdateUIEvent & event) +{ + u32 ID = event.GetId(); + event.Enable(0); + if (!m_connected) return; + event.Enable(1); + u32 mode = gf_term_get_option(m_term, GF_OPT_COLLISION); + if (ID==ID_COLLIDE_NONE) { + event.Check((mode==GF_COLLISION_NONE) ? 1 : 0); + } + else if (ID==ID_COLLIDE_REG) { + event.Check((mode==GF_COLLISION_NORMAL) ? 1 : 0); + } + else if (ID==ID_COLLIDE_DISP) { + event.Check((mode==GF_COLLISION_DISPLACEMENT) ? 1 : 0); + } +} + +void wxOsmo4Frame::OnHeadlight(wxCommandEvent &WXUNUSED(event)) +{ + Bool val = !gf_term_get_option(m_term, GF_OPT_HEADLIGHT); + gf_term_set_option(m_term, GF_OPT_HEADLIGHT, val); +} +void wxOsmo4Frame::OnUpdateHeadlight(wxUpdateUIEvent & event) +{ + event.Enable(0); + if (!m_connected) return; + u32 type = gf_term_get_option(m_term, GF_OPT_NAVIGATION_TYPE); + if (type!=GF_NAVIGATE_TYPE_3D) return; + + event.Enable(1); + event.Check(gf_term_get_option(m_term, GF_OPT_HEADLIGHT) ? 1 : 0); +} +void wxOsmo4Frame::OnGravity(wxCommandEvent & WXUNUSED(event)) +{ + Bool val = gf_term_get_option(m_term, GF_OPT_GRAVITY) ? 0 : 1; + gf_term_set_option(m_term, GF_OPT_GRAVITY, val); +} +void wxOsmo4Frame::OnUpdateGravity(wxUpdateUIEvent & event) +{ + event.Enable(0); + if (!m_connected) return; + u32 type = gf_term_get_option(m_term, GF_OPT_NAVIGATION_TYPE); + if (type!=GF_NAVIGATE_TYPE_3D) return; + type = gf_term_get_option(m_term, GF_OPT_NAVIGATION); + if (type != GF_NAVIGATE_WALK) return; + event.Enable(1); + event.Check(gf_term_get_option(m_term, GF_OPT_GRAVITY) ? 1 : 0); +} + + +BEGIN_EVENT_TABLE(wxMyComboBox, wxComboBox) + EVT_KEY_UP(wxMyComboBox::OnKeyUp) +END_EVENT_TABLE() + +void wxMyComboBox::OnKeyUp(wxKeyEvent &event) +{ + if (event.GetKeyCode()==WXK_RETURN) { + event.Skip(); + wxCommandEvent evt; + evt.SetEventType(wxEVT_COMMAND_COMBOBOX_SELECTED); + evt.SetEventObject(this); + evt.SetId(GetId()); + GetParent()->AddPendingEvent(evt); + } +} + + +void wxOsmo4Frame::ReloadURLs() +{ + const char *sOpt; + u32 i=0; + + m_Address->Clear(); + while (1) { + sOpt = gf_cfg_get_key_name(m_user.config, "RecentFiles", i); + if (!sOpt) break; + m_Address->Append(wxString(sOpt, wxConvUTF8) ); + i++; + } +} + +void wxOsmo4Frame::SelectionReady() +{ + wxString urlVal = m_Address->GetValue(); + if (urlVal.Find(wxT("://"))>0) { + UpdateLastFiles(m_user.config, urlVal.mb_str(wxConvUTF8)); + ReloadURLs(); + } + m_pPlayList->Truncate(); + m_pPlayList->QueueURL(urlVal); + m_pPlayList->RefreshList(); + m_pPlayList->PlayNext(); +} + +void wxOsmo4Frame::OnURLSelect(wxCommandEvent &WXUNUSED(event)) +{ + SelectionReady(); +} + +void wxOsmo4Frame::OnPlaylist(wxCommandEvent &WXUNUSED(event)) +{ + assert(m_pPlayList); + m_pPlayList->Show(m_pPlayList->IsShown() ? 0 : 1); +} + +void wxOsmo4Frame::OnUpdatePlayList(wxUpdateUIEvent & event) +{ + event.Enable(1); + event.Check(m_pPlayList->IsShown() ? 1 : 0); +} + +void wxOsmo4Frame::OnFilePrevOpen(wxNotifyEvent & event) +{ + u32 count = gf_list_count(m_pPlayList->m_entries); + u32 start = m_pPlayList->m_cur_entry - 1; + wxMenu *popup = new wxMenu(); + + for (u32 i=0; i<10; i++) { + if (i > start) break; + if (start - i >= count) break; + PLEntry *ple = (PLEntry *) gf_list_get(m_pPlayList->m_entries, start - i); + popup->Append(ID_NAV_PREV_0 + i, wxString(ple->m_disp_name, wxConvUTF8) ); + } + m_pPrevBut->AssignMenu(popup); +} + +void wxOsmo4Frame::OnFileNextOpen(wxNotifyEvent & event) +{ + u32 count = gf_list_count(m_pPlayList->m_entries); + wxMenu *popup = new wxMenu(); + u32 start = m_pPlayList->m_cur_entry + 1; + for (u32 i=0; i<10; i++) { + if (start + i >= count) break; + PLEntry *ple = (PLEntry *) gf_list_get(m_pPlayList->m_entries, start + i); + popup->Append(ID_NAV_NEXT_0 + i, wxString(ple->m_disp_name, wxConvUTF8) ); + } + m_pNextBut->AssignMenu(popup); +} + +void wxOsmo4Frame::OnNavPrev(wxCommandEvent &WXUNUSED(event)) +{ + if (m_pPlayList->m_cur_entry<=0) return; + m_pPlayList->PlayPrev(); +} +void wxOsmo4Frame::OnUpdateNavPrev(wxUpdateUIEvent & event) +{ + if (m_pPlayList->m_cur_entry<=0) event.Enable(0); + else event.Enable(TRUE); +} +void wxOsmo4Frame::OnNavPrevMenu(wxCommandEvent &event) +{ + u32 ID = event.GetId() - ID_NAV_PREV_0; + s32 prev = m_pPlayList->m_cur_entry - ID; + if (prev>=0) { + m_pPlayList->m_cur_entry = prev; + m_pPlayList->PlayPrev(); + } +} +void wxOsmo4Frame::OnNavNext(wxCommandEvent &WXUNUSED(event)) +{ + /*don't play if last could trigger playlist loop*/ + if ((m_pPlayList->m_cur_entry<0) || (gf_list_count(m_pPlayList->m_entries) == 1 + (u32) m_pPlayList->m_cur_entry)) return; + m_pPlayList->PlayNext(); +} +void wxOsmo4Frame::OnUpdateNavNext(wxUpdateUIEvent & event) +{ + if (m_pPlayList->m_cur_entry<0) event.Enable(0); + else if ((u32) m_pPlayList->m_cur_entry + 1 == gf_list_count(m_pPlayList->m_entries) ) event.Enable(0); + else event.Enable(1); +} + +void wxOsmo4Frame::OnNavNextMenu(wxCommandEvent &event) +{ + u32 ID = event.GetId() - ID_NAV_NEXT_0; + s32 next = m_pPlayList->m_cur_entry + ID; + if (next < (s32) gf_list_count(m_pPlayList->m_entries) ) { + m_pPlayList->m_cur_entry = next; + m_pPlayList->PlayNext(); + } +} + +void wxOsmo4Frame::OnClearNav(wxCommandEvent &WXUNUSED(event)) +{ + m_pPlayList->ClearButPlaying(); +} + + +void wxOsmo4Frame::BuildStreamList(Bool reset_only) +{ + u32 nb_subs; + wxMenu *pMenu; + + pMenu = sel_menu->FindItemByPosition(0)->GetSubMenu(); + while (pMenu->GetMenuItemCount()) { + wxMenuItem* it = pMenu->FindItemByPosition(0); + pMenu->Delete(it); + } + pMenu = sel_menu->FindItemByPosition(1)->GetSubMenu(); + while (pMenu->GetMenuItemCount()) { + wxMenuItem* it = pMenu->FindItemByPosition(0); + pMenu->Delete(it); + } + pMenu = sel_menu->FindItemByPosition(2)->GetSubMenu(); + while (pMenu->GetMenuItemCount()) { + wxMenuItem* it = pMenu->FindItemByPosition(0); + pMenu->Delete(it); + } + + if (reset_only) { + m_bFirstStreamListBuild = 1; + return; + } + + if (!gf_term_get_option(m_term, GF_OPT_CAN_SELECT_STREAMS)) return; + + nb_subs = 0; + GF_ObjectManager *root_od = gf_term_get_root_object(m_term); + if (!root_od) return; + u32 count = gf_term_get_object_count(m_term, root_od); + + for (u32 i=0; iFindItemByPosition(0)->GetSubMenu(); + if (!info.owns_service) sprintf(szLabel, "Audio #"LLU, (u64)pMenu->GetMenuItemCount() + 1); + pMenu->AppendCheckItem(ID_SELSTREAM_0 +i, wxString(szLabel, wxConvUTF8)); + break; + case GF_STREAM_VISUAL: + pMenu = sel_menu->FindItemByPosition(1)->GetSubMenu(); + if (!info.owns_service) sprintf(szLabel, "Video #"LLU, (u64)pMenu->GetMenuItemCount() + 1); + pMenu->AppendCheckItem(ID_SELSTREAM_0 +i, wxString(szLabel, wxConvUTF8)); + break; + case GF_STREAM_TEXT: + nb_subs ++; + pMenu = sel_menu->FindItemByPosition(2)->GetSubMenu(); + if (!info.owns_service) sprintf(szLabel, "Subtitle #"LLU, (u64)pMenu->GetMenuItemCount() + 1); + pMenu->AppendCheckItem(ID_SELSTREAM_0 +i, wxString(szLabel, wxConvUTF8)); + break; + } + } + if (m_bFirstStreamListBuild) { + m_bFirstStreamListBuild = 0; + if (!nb_subs && m_lookforsubs) LookForSubtitles(); + } +} + +void wxOsmo4Frame::OnStreamSel(wxCommandEvent & event) +{ + GF_ObjectManager *root_od = gf_term_get_root_object(m_term); + if (!root_od) return; + u32 ID = event.GetId() - ID_SELSTREAM_0; + GF_ObjectManager *odm = gf_term_get_object(m_term, root_od, ID); + gf_term_select_object(m_term, odm); +} + +void wxOsmo4Frame::OnUpdateStreamSel(wxUpdateUIEvent & event) +{ + GF_ObjectManager *root_od = gf_term_get_root_object(m_term); + if (!root_od) return; + u32 ID = event.GetId() - ID_SELSTREAM_0; + + GF_ObjectManager *odm = gf_term_get_object(m_term, root_od, ID); + if (!odm) return; + + GF_MediaInfo info; + gf_term_get_object_info(m_term, odm, &info); + event.Enable(1); + event.Check(info.status ? 1 : 0); +} + +void wxOsmo4Frame::OnUpdateStreamMenu(wxUpdateUIEvent & event) +{ + if (!m_connected || !gf_term_get_option(m_term, GF_OPT_CAN_SELECT_STREAMS)) { + event.Enable(0); + } else { + event.Enable(1); + } +} + +void wxOsmo4Frame::OnAddSub(wxCommandEvent &WXUNUSED(event)) +{ + wxFileDialog dlg(this, wxT("Add Subtitle"), wxT(""), wxT(""), wxT("All Subtitles|*.srt;*.ttxt|SRT Subtitles|*.srt|3GPP TimedText|*.ttxt|"), wxOPEN | wxCHANGE_DIR /* | wxHIDE_READONLY*/); + + if (dlg.ShowModal() == wxID_OK) { + AddSubtitle(dlg.GetPath().mb_str(wxConvUTF8), 1); + } + +} + +void wxOsmo4Frame::AddSubtitle(const char *fileName, Bool auto_play) +{ + gf_term_add_object(m_term, fileName, auto_play); +} + +static Bool subs_enum_dir_item(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) +{ + wxOsmo4Frame *_this = (wxOsmo4Frame*)cbck; + _this->AddSubtitle(item_path, 0); + return 0; +} + +void wxOsmo4Frame::LookForSubtitles() +{ + char dir[GF_MAX_PATH]; + const char *url = m_pPlayList->GetURL().mb_str(wxConvUTF8); + strcpy(dir, url); + char *sep = strrchr(dir, '\\'); + if (!sep) strcpy(dir, ::wxGetCwd().mb_str(wxConvUTF8)); + else sep[0] = 0; + + gf_enum_directory(dir, 0, subs_enum_dir_item, this, "ttxt;srt"); +} + +void wxOsmo4Frame::OnCacheEnable(wxCommandEvent &WXUNUSED(event)) +{ + u32 state = gf_term_get_option(m_term, GF_OPT_MEDIA_CACHE); + if (state==GF_MEDIA_CACHE_DISABLED) { + gf_term_set_option(m_term, GF_OPT_MEDIA_CACHE, GF_MEDIA_CACHE_ENABLED); + } else if (state==GF_MEDIA_CACHE_DISABLED) { + gf_term_set_option(m_term, GF_OPT_MEDIA_CACHE, GF_MEDIA_CACHE_DISABLED); + } +} + +void wxOsmo4Frame::OnCacheStop(wxCommandEvent &WXUNUSED(event)) +{ + gf_term_set_option(m_term, GF_OPT_MEDIA_CACHE, GF_MEDIA_CACHE_DISABLED); +} + +void wxOsmo4Frame::OnCacheAbort(wxCommandEvent &WXUNUSED(event)) +{ + gf_term_set_option(m_term, GF_OPT_MEDIA_CACHE, GF_MEDIA_CACHE_DISCARD); +} + +void wxOsmo4Frame::OnUpdateCacheEnable(wxUpdateUIEvent & event) +{ + u32 state = gf_term_get_option(m_term, GF_OPT_MEDIA_CACHE); + switch (state) { + case GF_MEDIA_CACHE_ENABLED: + event.Enable(1); + event.SetText(wxT("Enabled")); + break; + case GF_MEDIA_CACHE_RUNNING: + event.SetText(wxT("Running")); + event.Enable(0); + break; + case GF_MEDIA_CACHE_DISABLED: + event.SetText(wxT("Disabled")); + break; + } +} + +void wxOsmo4Frame::OnUpdateCacheAbort(wxUpdateUIEvent & event) +{ + u32 state = gf_term_get_option(m_term, GF_OPT_MEDIA_CACHE); + event.Enable( (state==GF_MEDIA_CACHE_RUNNING) ? 1 : 0); +} + + + +void wxOsmo4Frame::BuildChapterList(Bool reset_only) +{ + GF_MediaInfo odi; + + while (chap_menu->GetMenuItemCount()) { + wxMenuItem* it = chap_menu->FindItemByPosition(0); + chap_menu->Delete(it); + } + if (m_chapters_start) gf_free(m_chapters_start); + m_chapters_start = NULL; + m_num_chapters = 0; + if (reset_only) return; + + GF_ObjectManager *root_od = gf_term_get_root_object(m_term); + if (!root_od) return; + if (gf_term_get_object_info(m_term, root_od, &odi) != GF_OK) return; + + u32 count = gf_list_count(odi.od->OCIDescriptors); + m_num_chapters = 0; + for (u32 i=0; iOCIDescriptors, i); + if (seg->tag != GF_ODF_SEGMENT_TAG) continue; + + if (seg->SegmentName && strlen((const char *)seg->SegmentName)) { + strcpy(szLabel, (const char *) seg->SegmentName); + } else { + sprintf(szLabel, "Chapter %02d", m_num_chapters+1); + } + chap_menu->AppendCheckItem(ID_SETCHAP_FIRST + m_num_chapters, wxString(szLabel, wxConvUTF8)); + + m_chapters_start = (Double *) gf_realloc(m_chapters_start, sizeof(Double)*(m_num_chapters+1)); + m_chapters_start[m_num_chapters] = seg->startTime; + m_num_chapters++; + } + + /*get any service info*/ + NetInfoCommand com; + if (!m_bStartupFile && gf_term_get_service_info(m_term, root_od, &com) == GF_OK) { + wxString title = wxT(""); + if (com.track_info) { + title.Format(wxT("%02d "), (u32) (com.track_info>>16) ); + } + if (com.artist) { + title.Append(wxString(com.artist, wxConvUTF8)); + title += wxT(" "); + } + if (com.name) { + title.Append(wxString(com.name, wxConvUTF8)); + title += wxT(" "); + } + if (com.album) { + title += wxT("("); + title.Append(wxString(com.album, wxConvUTF8)); + title += wxT(")"); + } + + if (title.length()) SetTitle(title); + } + +} + +void wxOsmo4Frame::OnChapterSel(wxCommandEvent & event) +{ + GF_ObjectManager *root_od = gf_term_get_root_object(m_term); + if (!root_od) return; + u32 ID = event.GetId() - ID_SETCHAP_FIRST; + gf_term_play_from_time(m_term, (u32) (1000*m_chapters_start[ID]), 0); +} + +void wxOsmo4Frame::OnUpdateChapterSel(wxUpdateUIEvent & event) +{ + Double now; + Bool is_current; + u32 ID = event.GetId() - ID_SETCHAP_FIRST; + + now = gf_term_get_time_in_ms(m_term); + now /= 1000; + + is_current = 0; + if (m_chapters_start[ID]<=now) { + if (ID+1now) is_current = 1; + } else { + is_current = 1; + } + } + event.Enable(1); + event.Check(is_current ? 1 : 0); +} + +void wxOsmo4Frame::OnUpdateChapterMenu(wxUpdateUIEvent & event) +{ + if (!m_connected || !m_num_chapters) { + event.Enable(0); + } else { + event.Enable(1); + } +} + +void wxOsmo4Frame::OnFileCopy(wxCommandEvent &event) +{ + const char *text = gf_term_get_text_selection(m_term, 0); + if (!text) return; + if (!wxTheClipboard->Open()) return; + + wxTheClipboard->SetData( new wxTextDataObject( wxString(text, wxConvUTF8)) ); + wxTheClipboard->Close(); +} + +void wxOsmo4Frame::OnUpdateFileCopy(wxUpdateUIEvent &event) +{ + if (gf_term_get_text_selection(m_term, 1)!=NULL) { + event.Enable(1); + } else { + event.Enable(0); + } +} + +void wxOsmo4Frame::OnFilePaste(wxCommandEvent &event) +{ + if (!wxTheClipboard->Open()) return; + if (wxTheClipboard->IsSupported( wxDF_TEXT )) { + wxTextDataObject data; + wxTheClipboard->GetData(data); + gf_term_paste_text(m_term, data.GetText().mb_str(wxConvUTF8), 0); + } + wxTheClipboard->Close(); +} + +void wxOsmo4Frame::OnUpdateFilePaste(wxUpdateUIEvent &event) +{ + Bool ok = 0; + if (wxTheClipboard->Open()) { + if (wxTheClipboard->IsSupported( wxDF_TEXT )) { + if (gf_term_paste_text(m_term, NULL, 1)==GF_OK) { + ok = 1; + } + } + wxTheClipboard->Close(); + } + event.Enable(ok ? 1 : 0); +} + diff --git a/applications/deprecated/old_arch/osmo4_wx/wxOsmo4.h b/applications/deprecated/old_arch/osmo4_wx/wxOsmo4.h new file mode 100644 index 0000000..ae19a6c --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/wxOsmo4.h @@ -0,0 +1,391 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / Osmo4 wxWidgets GUI + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + */ + +#ifndef _WXOSMO4_H +#define _WXOSMO4_H + +/* +we need to force X to work in sync mode when we use embedded view... +include first to avoid Bool type redef between X11 and gpac +*/ +#ifdef __WXGTK__ +#include +#endif + +#include "wx/wxprec.h" + +#ifndef WX_PRECOMP +#include "wx/wx.h" +#endif + +#include +#include +#include +#include "menubtn.h" + +/*include gpac AFTER wx in case we override malloc/realloc/free for mem tracking*/ +#include +#include + +class wxOsmo4App : public wxApp +{ +public: + virtual bool OnInit(); +}; + +DECLARE_APP(wxOsmo4App) + +class wxOsmo4Frame; +class wxPlaylist; + +class GPACLogs : public wxLogWindow { +public: + GPACLogs(wxFrame *parent) : wxLogWindow(parent, wxT("GPAC Logs"), FALSE, FALSE) { + m_pMain = (wxOsmo4Frame *) parent; + } + virtual bool OnFrameClose(wxFrame *frame); + +private: + wxOsmo4Frame *m_pMain; +}; + +#define MAX_VIEWPOINTS 50 + +// Menu commands +enum +{ + GWX_FILE_OPEN = wxID_HIGHEST, + GWX_FILE_OPEN_URL, + FILE_RELOAD, + FILE_RELOAD_CONFIG, + FILE_PLAY, + FILE_STEP, + FILE_STOP, + FILE_PREV, + FILE_NEXT, + FILE_PROPERTIES, + FILE_COPY, + FILE_PASTE, + TERM_RELOAD, + FILE_QUIT, + VIEW_FULLSCREEN, + VIEW_ORIGINAL, + VIEW_AR_KEEP, + VIEW_AR_FILL, + VIEW_AR_43, + VIEW_AR_169, + VIEW_OPTIONS, + VIEW_LOGS, + VIEW_RTI, + VIEW_PLAYLIST, + SWITCH_RENDER, + APP_SHORTCUTS, + APP_NAV_KEYS, + APP_ABOUT, + ID_ADDRESS, + ID_URL_GO, + ID_ABOUT_CLOSE, + ID_CLEAR_NAV, + ID_STREAM_MENU, + ID_CHAPTER_MENU, + ID_ADD_SUB, + + ID_MCACHE_ENABLE, + ID_MCACHE_STOP, + ID_MCACHE_ABORT, + + ID_CTRL_TIMER, + ID_SLIDER, + + ID_TREE_VIEW, + ID_OD_TIMER, + ID_VIEW_SG, + ID_VIEW_WI, + ID_VIEW_SEL, + + + ID_HEADLIGHT, + ID_NAVIGATE_NONE, + ID_NAVIGATE_WALK, + ID_NAVIGATE_FLY, + ID_NAVIGATE_EXAMINE, + ID_NAVIGATE_SLIDE, + ID_NAVIGATE_PAN, + ID_NAVIGATE_ORBIT, + ID_NAVIGATE_GAME, + ID_NAVIGATE_RESET, + + ID_COLLIDE_NONE, + ID_COLLIDE_REG, + ID_COLLIDE_DISP, + ID_GRAVITY, + + ID_PL_OPEN, + ID_PL_SAVE, + ID_PL_ADD_FILE, + ID_PL_ADD_URL, + ID_PL_ADD_DIR, + ID_PL_ADD_DIR_REC, + ID_PL_REM_FILE, + ID_PL_REM_ALL, + ID_PL_REM_DEAD, + ID_PL_UP, + ID_PL_DOWN, + ID_PL_RANDOMIZE, + ID_PL_REVERSE, + ID_PL_SEL_REV, + ID_PL_SORT_TITLE, + ID_PL_SORT_FILE, + ID_PL_SORT_DUR, + ID_PL_PLAY, + + + /*reserve IDs for viewpoint menu*/ + ID_VIEWPOINT_FIRST, + ID_VIEWPOINT_LAST = ID_VIEWPOINT_FIRST + MAX_VIEWPOINTS, + + /*reserve IDs for navigation menus*/ + ID_NAV_PREV_0, + ID_NAV_PREV_9 = ID_NAV_PREV_0 + 10, + ID_NAV_NEXT_0, + ID_NAV_NEXT_9 = ID_NAV_NEXT_0 + 10, + /*reserve IDs for stream selection menus*/ + ID_SELSTREAM_0, + ID_SELSTREAM_9 = ID_SELSTREAM_0 + 10, + + /*reserve IDs for chapter selection menus*/ + ID_SETCHAP_FIRST, + ID_SETCHAP_LAST = ID_SELSTREAM_0 + 200, +}; + +wxString get_pref_browser(GF_Config *cfg); + +class wxGPACEvent : public wxEvent +{ +public: + wxGPACEvent( wxWindow* win = (wxWindow*) NULL ); + void CopyObject( wxObject& obj ) const; + virtual wxEvent *Clone() const; + + wxString to_url; + GF_Event gpac_evt; + + DECLARE_DYNAMIC_CLASS(wxGPACEvent) +}; +typedef void (wxEvtHandler::*GPACEventFunction)(wxGPACEvent&); +DEFINE_EVENT_TYPE(GPAC_EVENT) + +#define EVT_GPACEVENT(func) DECLARE_EVENT_TABLE_ENTRY(GPAC_EVENT, -1, -1, (wxObjectEventFunction) (wxEventFunction) (GPACEventFunction) & func, (wxObject*) NULL), + +class OpenURLDlg : public wxDialog { +public: + OpenURLDlg(wxWindow *parent, GF_Config *cfg); + wxString m_urlVal; +private: + wxButton *m_go; + wxComboBox *m_url; + GF_Config *m_cfg; + void OnGo(wxCommandEvent& event); + DECLARE_EVENT_TABLE() +}; + +class wxMyComboBox : public wxComboBox +{ +public: + wxMyComboBox(wxWindow* parent, wxWindowID id, const wxString& value = wxT(""), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize) + : wxComboBox(parent, id, value, pos, size, 0, NULL, wxCB_DROPDOWN) + {} + +private: + DECLARE_EVENT_TABLE() + + void OnKeyUp(wxKeyEvent &event); +}; + +class wxOsmo4Frame : public wxFrame { +public: + wxOsmo4Frame(); + virtual ~wxOsmo4Frame(); + + char szAppPath[GF_MAX_PATH]; + + u32 m_duration; + wxString the_next_url; + GF_Terminal *m_term; + GF_User m_user; + Bool m_connected, m_can_seek, m_console_off, m_loop, m_lookforsubs; + + void DoConnect(); + + void ConnectAcknowledged(Bool bOk); + void SetStatus(wxString str); + + void OnFilePlay(wxCommandEvent &event); + void OnFileStep(wxCommandEvent &event); + void OnFileStop(wxCommandEvent &event); + wxString GetFileFilter(); + + void BuildViewList(); + void BuildStreamList(Bool reset_only); + void BuildChapterList(Bool reset_only); + + void AddSubtitle(const char *fileName, Bool auto_play); + + wxWindow *m_pView; + +#ifdef __WXGTK__ + u32 m_last_grab_time, m_last_grab_pos; + wxWindow *m_pVisual; +#endif + wxSlider *m_pProg; + wxPlaylist *m_pPlayList; + + void DoLayout(u32 v_width = 0, u32 v_height = 0); + s32 m_last_prog; + + FILE *m_logs; + u32 m_log_level, m_log_tools; + u32 m_LastStatusTime; + +protected: + +private: + DECLARE_EVENT_TABLE() + + void OnCloseApp(wxCloseEvent &event); + void OnSize(wxSizeEvent &event); + + void OnFileOpen(wxCommandEvent &event); + void OnFileOpenURL(wxCommandEvent &event); + void OnFileReload(wxCommandEvent &event); + void OnFileReloadConfig(wxCommandEvent & event); + void OnFileProperties(wxCommandEvent &event); + void OnFileQuit(wxCommandEvent &event); + void OnFullScreen(wxCommandEvent &event); + void OnOptions(wxCommandEvent &event); + void OnViewARKeep(wxCommandEvent &event); + void OnViewARFill(wxCommandEvent &event); + void OnViewAR169(wxCommandEvent &event); + void OnViewAR43(wxCommandEvent &event); + void OnViewOriginal(wxCommandEvent &event); + void OnPlaylist(wxCommandEvent &event); + void OnShortcuts(wxCommandEvent &event); + void OnNavInfo(wxCommandEvent &event); + void OnAddSub(wxCommandEvent &event); + void OnAbout(wxCommandEvent &event); + Bool LoadTerminal(); + void OnGPACEvent(wxGPACEvent &event); + void OnTimer(wxTimerEvent& event); + void OnSlide(wxScrollEvent &event); + void OnRelease(wxScrollEvent &event); + void OnLogs(wxCommandEvent & event); + void OnRTI(wxCommandEvent & event); + void OnUpdatePlay(wxUpdateUIEvent &event); + void OnUpdateNeedsConnect(wxUpdateUIEvent &event); + void OnUpdateFullScreen(wxUpdateUIEvent &event); + void OnUpdateAR(wxUpdateUIEvent &event); + void OnViewport(wxCommandEvent & event); + void OnUpdateViewport(wxUpdateUIEvent & event); + void OnNavigate(wxCommandEvent & event); + void OnNavigateReset(wxCommandEvent & event); + void OnUpdateNavigation(wxUpdateUIEvent & event); + void OnRenderSwitch(wxCommandEvent &event); + void OnCollide(wxCommandEvent & event); + void OnUpdateCollide(wxUpdateUIEvent & event); + void OnHeadlight(wxCommandEvent & event); + void OnUpdateHeadlight(wxUpdateUIEvent & event); + void OnGravity(wxCommandEvent & event); + void OnUpdateGravity(wxUpdateUIEvent & event); + void OnURLSelect(wxCommandEvent &event); + void OnUpdatePlayList(wxUpdateUIEvent & event); + void OnFilePrevOpen(wxNotifyEvent & event); + void OnFileNextOpen(wxNotifyEvent & event); + void OnNavPrev(wxCommandEvent &event); + void OnUpdateNavPrev(wxUpdateUIEvent & event); + void OnNavPrevMenu(wxCommandEvent &event); + void OnNavNext(wxCommandEvent &event); + void OnUpdateNavNext(wxUpdateUIEvent & event); + void OnNavNextMenu(wxCommandEvent &event); + void OnClearNav(wxCommandEvent &event); + void OnStreamSel(wxCommandEvent &event); + void OnUpdateStreamSel(wxUpdateUIEvent & event); + void OnUpdateStreamMenu(wxUpdateUIEvent & event); + void OnChapterSel(wxCommandEvent &event); + void OnUpdateChapterSel(wxUpdateUIEvent & event); + void OnUpdateChapterMenu(wxUpdateUIEvent & event); + + void SelectionReady(); + void ReloadURLs(); + void LookForSubtitles(); + + void OnCacheEnable(wxCommandEvent &event); + void OnCacheStop(wxCommandEvent &event); + void OnCacheAbort(wxCommandEvent &event); + void OnUpdateCacheEnable(wxUpdateUIEvent & event); + void OnUpdateCacheAbort(wxUpdateUIEvent & event); + + void OnFileCopy(wxCommandEvent &event); + void OnUpdateFileCopy(wxUpdateUIEvent &event); + void OnFilePaste(wxCommandEvent &event); + void OnUpdateFilePaste(wxUpdateUIEvent &event); + + + void CheckVideoOut(); + + wxMenuBar* m_pMenubar; + wxStatusBar* m_pStatusbar; + wxTimer *m_pTimer; + GPACLogs *m_pLogs; + wxBoxSizer *m_pAddBar; + + Bool m_bGrabbed, m_bToReset, m_bFirstStreamListBuild; + wxBitmap *m_pOpenFile, *m_pPrev, *m_pNext, *m_pPlay, *m_pPause, *m_pStep, *m_pStop, *m_pInfo, *m_pConfig, *m_pSW2D, *m_pSW3D; + wxMenuButton *m_pPrevBut, *m_pNextBut; + wxToolBar *m_pToolBar; + wxMyComboBox *m_Address; + + wxMenu *vp_list; + wxMenu *sel_menu; + wxMenu *chap_menu; + void Stop(); + + s32 nb_viewpoints; + + void UpdateRenderSwitch(); + void UpdatePlay(); + + u32 m_orig_width, m_orig_height; + + u32 m_num_chapters; + Double *m_chapters_start; + Bool m_bExternalView, m_bViewRTI, m_bStartupFile; + + void ShowViewWindow(Bool do_show); +}; + + +#endif //_WXOSMO4_H + diff --git a/applications/deprecated/old_arch/osmo4_wx/wxOsmo4.rc b/applications/deprecated/old_arch/osmo4_wx/wxOsmo4.rc new file mode 100644 index 0000000..3e839b8 --- /dev/null +++ b/applications/deprecated/old_arch/osmo4_wx/wxOsmo4.rc @@ -0,0 +1,72 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// French (France) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_FRA) +#ifdef _WIN32 +LANGUAGE LANG_FRENCH, SUBLANG_FRENCH +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_OSMO_ICON ICON DISCARDABLE "../../doc/osmo4.ico" +#endif // French (France) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/applications/deprecated/old_arch/osmophone/Osmo4.ico b/applications/deprecated/old_arch/osmophone/Osmo4.ico new file mode 100644 index 0000000000000000000000000000000000000000..d544ffce61cc278ca5f2b433f70dc50a3c3435b4 GIT binary patch literal 1078 zcma)5J8s)R5Pd5f;IOfFJ8oqH0Rmkim3*WCIe>>yA*xB1od^WTRVXM2p}LeR9Y8Si zW=W@V;gM!`-u(ANz+r{D_AvUh0zPx~e#RRIJlp{r+G_F>+2NiNP{zJbN+&(4^Fm1# z!!U3)$CUq=Cez6LnaPAvYeGz9YRi`N-yqTiQl(48p@}7sT*#%NU4)|_uF3%tN@sfs z=yE{gGo&F|z#*WT+emb(LODZ7(=(v!=Yyb8Vh|0TE`Dfa#n>6-c-(H&sYGikZL7qt z_Yyrdj&E4XO8Nyefx*N!RNf!hyE^ttPmUMEj3=J!wT4-b8N#I5A3YV2Z1$4 +#include +/*for initial setup*/ +#include + +#include +#include + +#include +#include +#include + +#ifdef _WIN32_WCE +#include +#include + +#else + +#ifndef _T +#define _T(__a) (LPCSTR) __a +#endif + +#endif + +#include "resource.h" + +#define WM_LOADTERM WM_USER + 1 +#define STATE_TIMER_ID 20 +#define STATE_TIMER_DUR 1000 +#define GPAC_TIMER_ID 21 +#define GPAC_TIMER_DUR 33 + + +Bool gf_file_dialog(HINSTANCE inst, HWND parent, char *url, const char *ext_list, GF_Config *cfg); +void set_backlight_state(Bool disable); +void refresh_recent_files(); +void do_layout(Bool notif_size); + +static HWND g_hwnd = NULL; +static HWND g_hwnd_disp = NULL; +static HWND g_hwnd_menu = NULL; +static HWND g_hwnd_status = NULL; +static HINSTANCE g_hinst = NULL; +static Bool is_ppc = GF_FALSE; + +static Bool is_connected = GF_FALSE; +static Bool navigation_on = GF_FALSE; +static Bool playlist_navigation_on = GF_TRUE; + +static u32 log_level = GF_LOG_ERROR; + +static u32 Duration; +static Bool CanSeek = GF_FALSE; +static u32 Volume=100; +static char the_url[GF_MAX_PATH] = ""; +static Bool NavigateTo = GF_FALSE; +static char the_next_url[GF_MAX_PATH]; +static GF_Terminal *term; +static GF_User user; +static u32 disp_w = 0; +static u32 disp_h = 0; +static u32 screen_w = 0; +static u32 screen_h = 0; +static u32 menu_h = 0; +static u32 caption_h = 0; +static u32 ratio_h = 1; +static Bool backlight_off = GF_FALSE; +static u32 prev_batt_bl, prev_ac_bl; +static Bool show_status = GF_TRUE; +static Bool reset_status = GF_TRUE; +static u32 last_state_time = 0; +static Bool loop = GF_FALSE; +static Bool full_screen = GF_FALSE; +static Bool force_2d_gl = GF_FALSE; +static Bool ctrl_mod_down = GF_FALSE; +static Bool view_cpu = GF_TRUE; +static Bool use_low_fps = GF_FALSE; +static Bool use_svg_prog = GF_FALSE; + +static Bool log_rti = GF_FALSE; +static FILE *log_file = NULL; +static u32 rti_update_time_ms = 200; + +static u32 playlist_act = 0; + + +void recompute_res(u32 sw, u32 sh ) +{ + caption_h = GetSystemMetrics(SM_CYCAPTION); + menu_h = GetSystemMetrics(SM_CYMENU)+2; + screen_w = GetSystemMetrics(SM_CXSCREEN); + screen_h = GetSystemMetrics(SM_CYSCREEN); + + ratio_h = sh / screen_h; + screen_w = sw; + screen_h = sh; + menu_h *= ratio_h; +// caption_h *= ratio_h; + disp_w = screen_w; + disp_h = screen_h - menu_h /*- caption_h*/; +} + + +void set_status(char *state) +{ + if (show_status && g_hwnd_status) { +#ifdef _WIN32_WCE + TCHAR wstate[1024]; + CE_CharToWide(state, (u16 *) wstate); + SendMessage(g_hwnd_status, WM_SETTEXT, 0, (LPARAM) wstate); +#else + SendMessage(g_hwnd_status, WM_SETTEXT, 0, (LPARAM) state); +#endif + last_state_time = GetTickCount(); + } +} + +void update_state_info() +{ + TCHAR wstate[1024]; + Double FPS; + u32 time, m, s; + if (!show_status) return; + if (last_state_time) { + if (GetTickCount() > last_state_time + 200) { + last_state_time = 0; + reset_status = GF_TRUE; + } + else return; + } + if (!term) return; + if (!is_connected && reset_status) { + SendMessage(g_hwnd_status, WM_SETTEXT, 0, (LPARAM) TEXT("Ready") ); + reset_status = GF_FALSE; + return; + } + + FPS = gf_term_get_framerate(term, GF_FALSE); + time = gf_term_get_time_in_ms(term) / 1000; + m = time/60; + s = time - m*60; + if (view_cpu) { + GF_SystemRTInfo rti; + if (!gf_sys_get_rti(STATE_TIMER_DUR, &rti, 0)) return; + wsprintf(wstate, TEXT("T %02d:%02d : FPS %02.2f : CPU %02d"), m, s, FPS, rti.total_cpu_usage); + } else { + wsprintf(wstate, TEXT("T %02d:%02d : FPS %02.2f"), m, s, FPS); + } + SendMessage(g_hwnd_status, WM_SETTEXT, 0, (LPARAM) wstate); +} + + +static u64 prev_pos = 0; +void cbk_on_progress(void *_title, u64 done, u64 total) +{ +#if 0 + char szMsg[1024]; + u32 pos = (u32) ((u64) done * 100)/total; + if (pos=GF_LOG_INFO)) { + vfprintf(log_file, fmt, list); + } +} +static void on_gpac_log(void *cbk, GF_LOG_Level ll, GF_LOG_Tool lm, const char *fmt, va_list list) +{ + if (fmt && log_file) vfprintf(log_file, fmt, list); +} + + +static void setup_logs() +{ + if (log_file) gf_fclose(log_file); + log_file = NULL; + + gf_log_set_tool_level(GF_LOG_ALL, GF_LOG_ERROR); + gf_log_set_callback(NULL, NULL); + + if (log_rti) { + const char *filename = gf_cfg_get_key(user.config, "General", "LogFile"); + if (!filename) { + gf_cfg_set_key(user.config, "General", "LogFile", "\\gpac_logs.txt"); + filename = "\\gpac_logs.txt"; + } + log_file = gf_fopen(filename, "a+t"); + + fprintf(log_file, "!! GPAC RunTime Info for file %s !!\n", the_url); + fprintf(log_file, "SysTime(ms)\tSceneTime(ms)\tCPU\tFPS\tMemory(kB)\tObservation\n"); + + gf_log_set_tool_level(GF_LOG_ALL, GF_LOG_ERROR); + gf_log_set_tool_level(GF_LOG_RTI, GF_LOG_DEBUG); + gf_log_set_callback(log_file, on_gpac_rti_log); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RTI, ("[RTI] System state when enabling log\n")); + } else { + const char *filename = gf_cfg_get_key(user.config, "General", "LogFile"); + if (!filename) { + gf_cfg_set_key(user.config, "General", "LogFile", "\\gpac_logs.txt"); + filename = "\\gpac_logs.txt"; + } + const char *logs = gf_cfg_get_key(user.config, "General", "Logs"); + if (logs) { + if (gf_log_set_tools_levels( logs ) != GF_OK) { + } else { + if (log_file = gf_fopen(filename, "a+t")) { + gf_log_set_callback(log_file, on_gpac_log); + } + } + } + } +} + +void do_open_file() +{ + gf_cfg_set_key(user.config, "RecentFiles", the_url, NULL); + gf_cfg_insert_key(user.config, "RecentFiles", the_url, "", 0); + u32 count = gf_cfg_get_key_count(user.config, "RecentFiles"); + if (count > 10) gf_cfg_set_key(user.config, "RecentFiles", gf_cfg_get_key_name(user.config, "RecentFiles", count-1), NULL); + + setup_logs(); + + gf_term_connect(term, the_url); +} + +void switch_playlist(Bool play_prev) +{ + char szPLE[20]; + u32 idx = 0; + u32 count; + const char *ple = gf_cfg_get_key(user.config, "General", "PLEntry"); + if (ple) idx = atoi(ple); + + count = gf_cfg_get_key_count(user.config, "Playlist"); + if (!count) return; + /*not the first launch*/ + if (strlen(the_url)) { + if (!idx && play_prev) return; + if (play_prev) idx--; + else idx++; + if (idx>=count) return; + } else { + if (idx>=count) idx=0; + } + + ple = gf_cfg_get_key_name(user.config, "Playlist", idx); + if (!ple) return; + + sprintf(szPLE, "%d", idx); + gf_cfg_set_key(user.config, "General", "PLEntry", szPLE); + + strcpy(the_url, ple); + do_open_file(); +} + + +Bool GPAC_EventProc(void *ptr, GF_Event *evt) +{ + switch (evt->type) { + case GF_EVENT_DURATION: + Duration = (u32) (evt->duration.duration*1000); + CanSeek = evt->duration.can_seek; + break; + case GF_EVENT_MESSAGE: + { + if (!evt->message.message) return GF_FALSE; + GF_LOG(GF_LOG_ERROR, GF_LOG_CONSOLE, ("%s: %s\n", evt->message.message, gf_error_to_string(evt->message.error))); + //set_status((char *) evt->message.message); + } + break; + case GF_EVENT_PROGRESS: + { + char *szTitle = ""; + if (evt->progress.progress_type==0) szTitle = "Buffer "; + else if (evt->progress.progress_type==1) szTitle = "Download "; + else if (evt->progress.progress_type==2) szTitle = "Import "; + cbk_on_progress(szTitle, evt->progress.done, evt->progress.total); + } + break; + + case GF_EVENT_SIZE: + break; + case GF_EVENT_RESOLUTION: + recompute_res(evt->size.width, evt->size.height); + do_layout(GF_TRUE); + break; + + case GF_EVENT_SCENE_SIZE: + do_layout(GF_TRUE); + break; + case GF_EVENT_DBLCLICK: + set_full_screen(); + return GF_FALSE; + case GF_EVENT_CONNECT: + if (evt->connect.is_connected) { + is_connected = GF_TRUE; + if (!backlight_off) set_backlight_state(GF_TRUE); + refresh_recent_files(); + navigation_on = (gf_term_get_option(term, GF_OPT_NAVIGATION)==GF_NAVIGATE_NONE) ? GF_FALSE : GF_TRUE; + } else { + navigation_on = GF_FALSE; + is_connected = GF_FALSE; + Duration = 0; + } + break; + case GF_EVENT_EOS: + if (Duration>2000) + gf_term_play_from_time(term, 0, 0); + break; + case GF_EVENT_QUIT: + PostMessage(g_hwnd, WM_DESTROY, 0, 0); + break; + case GF_EVENT_KEYDOWN: + switch (evt->key.key_code) { + case GF_KEY_ENTER: + if (full_screen) set_full_screen(); + break; + case GF_KEY_1: + ctrl_mod_down = (Bool)!ctrl_mod_down; + evt->key.key_code = GF_KEY_CONTROL; + evt->type = ctrl_mod_down ? GF_EVENT_KEYDOWN : GF_EVENT_KEYUP; + gf_term_user_event(term, evt); + break; + case GF_KEY_MEDIAPREVIOUSTRACK: + playlist_act = 2; + break; + case GF_KEY_MEDIANEXTTRACK: + playlist_act = 1; + break; + } + break; + case GF_EVENT_NAVIGATE: + if (gf_term_is_supported_url(term, evt->navigate.to_url, GF_TRUE, GF_TRUE)) { + gf_term_navigate_to(term, evt->navigate.to_url); + return GF_TRUE; + } else { +#ifdef _WIN32_WCE + u16 dst[1024]; +#endif + SHELLEXECUTEINFO info; + + /* + if (full_screen) gf_term_set_option(term, GF_OPT_FULLSCREEN, 0); + full_screen = 0; + */ + memset(&info, 0, sizeof(SHELLEXECUTEINFO)); + info.cbSize = sizeof(SHELLEXECUTEINFO); + info.lpVerb = _T("open"); + info.fMask = SEE_MASK_NOCLOSEPROCESS; + info.lpFile = _T("iexplore"); +#ifdef _WIN32_WCE + CE_CharToWide((char *) evt->navigate.to_url, dst); + info.lpParameters = (LPCTSTR) dst; +#else + info.lpParameters = evt->navigate.to_url; +#endif + info.nShow = SW_SHOWNORMAL; + ShellExecuteEx(&info); + } + return GF_TRUE; + } + return GF_FALSE; +} + +//#define TERM_NOT_THREADED + +Bool LoadTerminal() +{ + /*by default use current dir*/ + strcpy(the_url, "."); + + setup_logs(); + + g_hwnd_disp = CreateWindow(TEXT("STATIC"), NULL, WS_CHILD | WS_VISIBLE , 0, 0, disp_w, disp_h, g_hwnd, NULL, g_hinst, NULL); + + user.EventProc = GPAC_EventProc; + /*dummy in this case (global vars) but MUST be non-NULL*/ + user.opaque = user.modules; + user.os_window_handler = g_hwnd_disp; +#ifdef TERM_NOT_THREADED + user.init_flags = GF_TERM_NO_REGULATION; + user.threads = 0; +#endif + + term = gf_term_new(&user); + if (!term) { + gf_modules_del(user.modules); + gf_cfg_del(user.config); + memset(&user, 0, sizeof(GF_User)); + return GF_FALSE; + } + +#ifdef _WIN32_WCE + screen_w = term->compositor->video_out->max_screen_width; + screen_h = term->compositor->video_out->max_screen_height; + disp_w = screen_w; + disp_h = screen_h - menu_h /*- caption_h*/; +#endif + +#ifdef TERM_NOT_THREADED + ::SetTimer(g_hwnd, GPAC_TIMER_ID, GPAC_TIMER_DUR, NULL); +#endif + + const char *str = gf_cfg_get_key(user.config, "General", "StartupFile"); + if (str) { + do_layout(GF_TRUE); + strcpy(the_url, str); + gf_term_connect(term, str); + } + return GF_TRUE; +} + +void do_layout(Bool notif_size) +{ + u32 w, h; + if (full_screen) { + w = screen_w; + h = screen_h; + ::ShowWindow(g_hwnd_status, SW_HIDE); +#ifdef _WIN32_WCE + ::ShowWindow(g_hwnd_menu, SW_HIDE); +#endif + +#ifdef _WIN32_WCE + SHFullScreen(g_hwnd, SHFS_HIDESIPBUTTON); +#endif + ::MoveWindow(g_hwnd, 0, 0, screen_w, screen_h, 1); + ::MoveWindow(g_hwnd_disp, 0, 0, screen_w, screen_h, 1); + SetForegroundWindow(g_hwnd_disp); + } else { +#ifdef _WIN32_WCE + ::ShowWindow(g_hwnd_menu, SW_SHOW); +#endif + if (show_status) { + ::MoveWindow(g_hwnd, 0, 0, disp_w, disp_h, 1); + ::ShowWindow(g_hwnd_status, SW_SHOW); + ::MoveWindow(g_hwnd_status, 0, 0, disp_w, caption_h, 1); + ::MoveWindow(g_hwnd_disp, 0, caption_h, disp_w, disp_h - caption_h, 1); + w = disp_w; + h = disp_h - caption_h*ratio_h; + } else { + ::ShowWindow(g_hwnd_status, SW_HIDE); +// ::MoveWindow(g_hwnd, 0, caption_h, disp_w, disp_h, 1); + ::MoveWindow(g_hwnd, 0, 0, disp_w, disp_h, 1); + ::MoveWindow(g_hwnd_disp, 0, 0, disp_w, disp_h, 1); + w = disp_w; + h = disp_h; + } + } + if (notif_size && term) gf_term_set_size(term, w, h); +} + + +void set_backlight_state(Bool disable) +{ +#ifdef _WIN32_WCE + HKEY hKey = 0; + DWORD dwSize; + DWORD dwValue; + HANDLE hBL; + if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("ControlPanel\\Backlight"), 0, 0, &hKey ) != ERROR_SUCCESS) return; + + if (disable) { + dwSize = 4; + RegQueryValueEx(hKey, _T("BatteryTimeout"), NULL, NULL,(unsigned char*) &prev_batt_bl, &dwSize); + dwSize = 4; + RegQueryValueEx(hKey, _T("ACTimeout"), NULL, NULL, (unsigned char*) &prev_ac_bl,&dwSize); + dwSize = 4; + dwValue = 0xefff ; + RegSetValueEx(hKey, _T("BatteryTimeout"), NULL, REG_DWORD, (unsigned char *)&dwValue, dwSize); + dwSize = 4; + dwValue = 0xefff ; + RegSetValueEx( hKey, _T("ACTimeout"), NULL, REG_DWORD, (unsigned char *)&dwValue, dwSize); + backlight_off = GF_TRUE; + } else { + if (prev_batt_bl) { + dwSize = 4; + RegSetValueEx(hKey, _T("BatteryTimeout"), NULL, REG_DWORD, (unsigned char *)&prev_batt_bl, dwSize); + } + if (prev_ac_bl) { + dwSize = 4; + RegSetValueEx(hKey, _T("ACTimeout"), NULL, REG_DWORD,(unsigned char *)&prev_ac_bl, dwSize); + } + backlight_off = GF_FALSE; + } + RegCloseKey(hKey); + hBL = CreateEvent(NULL, FALSE, FALSE, _T("BackLightChangeEvent")); + if (hBL) { + SetEvent(hBL); + CloseHandle(hBL); + } +#endif +} + +static Bool do_resume = GF_FALSE; +static Bool prev_backlight_state; +void gf_freeze_display(Bool do_gf_freeze) +{ + if (do_gf_freeze) { + prev_backlight_state = backlight_off; + do_resume = GF_FALSE; + if (0 && is_connected && gf_term_get_option(term, GF_OPT_PLAY_STATE)==GF_STATE_PLAYING) { + do_resume= GF_TRUE; + gf_term_set_option(term, GF_OPT_PLAY_STATE, GF_STATE_PAUSED); + } + /*gf_freeze display*/ + gf_term_set_option(term, GF_OPT_FREEZE_DISPLAY, 1); + + set_backlight_state(GF_FALSE); + gf_sleep(100); + } else { + if (prev_backlight_state) set_backlight_state(GF_TRUE); + gf_term_set_option(term, GF_OPT_FREEZE_DISPLAY, 0); + + if (do_resume) { + gf_term_set_option(term, GF_OPT_PLAY_STATE, GF_STATE_PLAYING); + set_backlight_state(GF_TRUE); + } + } +} + +static void show_taskbar(Bool show_it) +{ +#ifdef _WIN32_WCE + HWND wnd; + if (!is_ppc) return; + + wnd = GetForegroundWindow(); + wnd = g_hwnd; + if (show_it) { + SHFullScreen(wnd, SHFS_SHOWSTARTICON | SHFS_SHOWTASKBAR| SHFS_SHOWSIPBUTTON); + ::ShowWindow(::FindWindow(_T("HHTaskbar"),NULL), SW_SHOWNA); + } else { + ::ShowWindow(::FindWindow(_T("HHTaskbar"),NULL), SW_HIDE); + SHFullScreen(wnd, SHFS_HIDESTARTICON | SHFS_HIDETASKBAR| SHFS_HIDESIPBUTTON); + } +#endif +} + +void refresh_recent_files() +{ +#ifdef _WIN32_WCE + u32 count = gf_cfg_get_key_count(user.config, "RecentFiles"); + + HMENU hMenu = (HMENU)SendMessage(g_hwnd_menu, SHCMBM_GETSUBMENU, 0, ID_MENU_FILE); + /*pos is hardcoded*/ + hMenu = GetSubMenu(hMenu, 2); + + while (RemoveMenu(hMenu, 0, MF_BYPOSITION)) {} + + for (u32 i=0; iCanHandleURL(ifce, "test.test"); + gf_modules_close_interface((GF_BaseInterface *)ifce); + } + } + MessageBox(NULL, _T("Thank you for installing GPAC"), _T("Initial setup done"), MB_OK); + } + + str = gf_cfg_get_key(user.config, "General", "Loop"); + loop = (!str || !stricmp(str, "yes")) ? GF_TRUE : GF_FALSE; + + str = gf_cfg_get_key(user.config, "SAXLoader", "Progressive"); + use_svg_prog = (str && !strcmp(str, "yes")) ? GF_TRUE : GF_FALSE; + + str = gf_cfg_get_key(user.config, "General", "RTIRefreshPeriod"); + if (str) { + rti_update_time_ms = atoi(str); + } else { + gf_cfg_set_key(user.config, "General", "RTIRefreshPeriod", "200"); + } + + str = gf_cfg_get_key(user.config, "General", "ShowStatusBar"); + show_status = (str && !strcmp(str, "yes")) ? GF_TRUE : GF_FALSE; + + +#ifdef _WIN32_WCE + if (is_ppc) GXOpenInput(); +#endif + + if (InitInstance(nShowCmd)) { + SetForegroundWindow(g_hwnd); + show_taskbar(GF_FALSE); + + force_2d_gl = (Bool)gf_term_get_option(term, GF_OPT_USE_OPENGL); + + while (GetMessage(&msg, NULL, 0,0) == TRUE) { + TranslateMessage (&msg); + DispatchMessage (&msg); + + if (playlist_act) { + switch_playlist((Bool)(playlist_act-1)); + playlist_act = 0; + } + } + show_taskbar(GF_TRUE); + } +#ifdef _WIN32_WCE + if (is_ppc) GXCloseInput(); +#endif + + /*and destroy*/ + if (term) gf_term_del(term); + if (user.modules) gf_modules_del(user.modules); + if (user.config) gf_cfg_del(user.config); + + if (backlight_off) set_backlight_state(GF_FALSE); + + gf_sys_close(); + if (log_file) gf_fclose(log_file); + return 0; +} diff --git a/applications/deprecated/old_arch/osmophone/newres.h b/applications/deprecated/old_arch/osmophone/newres.h new file mode 100644 index 0000000..523557a --- /dev/null +++ b/applications/deprecated/old_arch/osmophone/newres.h @@ -0,0 +1,41 @@ +#ifndef __NEWRES_H__ +#define __NEWRES_H__ + +#if defined(_WIN32_WCE) && !defined(UNDER_CE) +#define UNDER_CE _WIN32_WCE +#endif + +#if defined(_WIN32_WCE) +#if !defined(WCEOLE_ENABLE_DIALOGEX) +#define DIALOGEX DIALOG DISCARDABLE +#endif +#include +#define SHMENUBAR RCDATA +#if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300) +#include +#define AFXCE_IDR_SCRATCH_SHMENU 28700 +#else +#define I_IMAGENONE (-2) +#define NOMENU 0xFFFF +#define IDS_SHNEW 1 + +#define IDM_SHAREDNEW 10 +#define IDM_SHAREDNEWDEFAULT 11 +#endif // _WIN32_WCE_PSPC +#define AFXCE_IDD_SAVEMODIFIEDDLG 28701 +#endif // _WIN32_WCE + +#ifdef RC_INVOKED +#ifndef _INC_WINDOWS +#define _INC_WINDOWS +#include "winuser.h" // extract from windows header +#include "winver.h" +#endif +#endif + +#ifdef IDC_STATIC +#undef IDC_STATIC +#endif +#define IDC_STATIC (-1) + +#endif //__NEWRES_H__ diff --git a/applications/deprecated/old_arch/osmophone/openfile.cpp b/applications/deprecated/old_arch/osmophone/openfile.cpp new file mode 100644 index 0000000..55ea606 --- /dev/null +++ b/applications/deprecated/old_arch/osmophone/openfile.cpp @@ -0,0 +1,405 @@ +#include + +#ifdef _WIN32_WCE + +#include + +#include +#include "resource.h" + +static HINSTANCE g_hInst = NULL; +static HMENU g_hMenuView; +static HWND g_hWndMenuBar; +static HWND hDirTxt; +static HWND the_wnd; +static HWND hList; +static TCHAR w_current_dir[GF_MAX_PATH] = _T("\\"); +static u8 current_dir[GF_MAX_PATH] = "\\"; +static const char *extension_list = NULL; +static char *out_url = NULL; +Bool bViewUnknownTypes = GF_FALSE; +Bool playlist_mode = GF_FALSE; +GF_Config *cfg; + + +static void refresh_menu_states() +{ + if (playlist_mode) { + EnableMenuItem(g_hMenuView, IDM_OF_PL_UP, MF_BYCOMMAND|MF_ENABLED); + EnableMenuItem(g_hMenuView, IDM_OF_PL_DOWN, MF_BYCOMMAND|MF_ENABLED ); + } else { + EnableMenuItem(g_hMenuView, IDM_OF_VIEW_ALL, MF_BYCOMMAND| (extension_list ? MF_ENABLED : MF_GRAYED) ); + CheckMenuItem(g_hMenuView, IDM_OF_VIEW_ALL, MF_BYCOMMAND| (bViewUnknownTypes ? MF_CHECKED : MF_UNCHECKED) ); + } + CheckMenuItem(g_hMenuView, IDM_OF_PLAYLIST, MF_BYCOMMAND| (playlist_mode ? MF_CHECKED : MF_UNCHECKED) ); +} + +static void switch_menu_pl() +{ + DeleteMenu(g_hMenuView, IDM_OF_VIEW_ALL, MF_BYCOMMAND); + DeleteMenu(g_hMenuView, IDM_OF_PL_UP, MF_BYCOMMAND); + DeleteMenu(g_hMenuView, IDM_OF_PL_DOWN, MF_BYCOMMAND); + DeleteMenu(g_hMenuView, IDM_OF_PL_CLEAR, MF_BYCOMMAND); + + if (playlist_mode) { + InsertMenu(g_hMenuView, 0, MF_BYPOSITION, IDM_OF_PL_CLEAR, _T("Clear")); + InsertMenu(g_hMenuView, 0, MF_BYPOSITION, IDM_OF_PL_DOWN, _T("Move Down") ); + InsertMenu(g_hMenuView, 0, MF_BYPOSITION, IDM_OF_PL_UP, _T("Move Up") ); + } else { + InsertMenu(g_hMenuView, 0, MF_BYPOSITION, IDM_OF_VIEW_ALL, _T("All Unknown Files") ); + } + TBBUTTONINFO tbbi; + tbbi.cbSize = sizeof(tbbi); + tbbi.dwMask = TBIF_TEXT; + tbbi.pszText = playlist_mode ? _T("Remove") : _T("Add"); + SendMessage(g_hWndMenuBar, TB_SETBUTTONINFO, IDM_OF_PL_ACT, (LPARAM)&tbbi); + refresh_menu_states(); +} + + +Bool enum_dirs(void *cbk, char *name, char *path, GF_FileEnumInfo *file_info) +{ + TCHAR w_name[GF_MAX_PATH], w_str_name[GF_MAX_PATH]; + + CE_CharToWide(name, (u16 *) w_name); + wcscpy(w_str_name, _T("+ ")); + wcscat(w_str_name, w_name); + int iRes = SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)(LPCTSTR) w_str_name); + SendMessage(hList, LB_SETITEMDATA, iRes, (LPARAM) 1); + return GF_FALSE; +} + +Bool enum_files(void *cbk, char *name, char *path, GF_FileEnumInfo *file_info) +{ + TCHAR w_name[GF_MAX_PATH]; + + if (!bViewUnknownTypes && extension_list) { + char *ext = strrchr(name, '.'); + if (!ext || !strstr(extension_list, ext+1)) return GF_FALSE; + } + CE_CharToWide(name, (u16 *) w_name); + SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)(LPCTSTR) w_name); + return GF_FALSE; +} + + +void set_directory(TCHAR *dir) +{ + SendMessage(hList, LB_RESETCONTENT, 0, 0); + + CE_WideToChar((u16 *) dir, (char *) current_dir); + wcscpy(w_current_dir, dir); + SetWindowText(hDirTxt, w_current_dir); + + if (strcmp((const char *) current_dir, "\\")) { + int iRes = SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)(LPCTSTR) _T("+ ..") ); + SendMessage(hList, LB_SETITEMDATA, iRes, (LPARAM) 1); + } + + /*enum directories*/ + gf_enum_directory((const char *) current_dir, GF_TRUE, enum_dirs, NULL, NULL); + /*enum files*/ + gf_enum_directory((char *) current_dir, GF_FALSE, enum_files, NULL, NULL); + SendMessage(hList, LB_SETCURSEL, 0, 0); + SetFocus(hList); +} + + + +void refresh_playlist() +{ + TCHAR w_name[GF_MAX_PATH]; + u32 count, i; + + SetWindowText(hDirTxt, _T("Playlist")); + SendMessage(hList, LB_RESETCONTENT, 0, 0); + + count = gf_cfg_get_key_count(cfg, "Playlist"); + for (i=0; i=count) i=0; + SendMessage(hList, LB_SETCURSEL, i, 0); + SetFocus(hList); +} + +void playlist_act(u32 act_type) +{ + u32 idx, count; + char entry[MAX_PATH]; + const char *url; + + /*reset all*/ + if (act_type == 3) { + count = gf_cfg_get_key_count(cfg, "Playlist"); + while (count) { + url = gf_cfg_get_key_name(cfg, "Playlist", 0); + gf_cfg_set_key(cfg, "Playlist", url, NULL); + count--; + } + refresh_playlist(); + return; + } + count = SendMessage(hList, LB_GETSELCOUNT, 0, 0); + if (!count) return; + idx = SendMessage(hList, LB_GETCURSEL, 0, 0); + + if ((act_type==1) && !idx) return; + else if ((act_type==2) && (idx+1==count)) return; + + url = gf_cfg_get_key_name(cfg, "Playlist", idx); + if (!url) return; + strcpy(entry, url); + /*remove from playlist*/ + gf_cfg_set_key(cfg, "Playlist", url, NULL); + switch (act_type) { + /*remove*/ + case 0: + if (idx+1==count) idx--; + break; + /*up*/ + case 1: + gf_cfg_insert_key(cfg, "Playlist", entry, "", idx-1); + idx--; + break; + /*down*/ + case 2: + gf_cfg_insert_key(cfg, "Playlist", entry, "", idx+1); + idx++; + break; + } + refresh_playlist(); + SendMessage(hList, LB_SETCURSEL, idx, 0); + SetFocus(hList); +} + +Bool add_files(void *cbk, char *name, char *path, GF_FileEnumInfo *file_info) +{ + if (!bViewUnknownTypes && extension_list) { + char *ext = strrchr(name, '.'); + if (!ext || !strstr(extension_list, ext+1)) return GF_FALSE; + } + gf_cfg_set_key(cfg, "Playlist", path, ""); + return GF_FALSE; +} + +void process_list_change(HWND hWnd, Bool add_to_pl) +{ + TCHAR sTxt[GF_MAX_PATH]; + if (!SendMessage(hList, LB_GETSELCOUNT, 0, 0)) return; + + u32 idx = SendMessage(hList, LB_GETCURSEL, 0, 0); + SendMessage(hList, LB_GETTEXT, idx, (LPARAM)(LPCTSTR) sTxt); + + DWORD param = SendMessage(hList, LB_GETITEMDATA, idx, 0); + if (param==1) { + if (!wcscmp(sTxt, _T("+ ..") ) ) { + if (add_to_pl) return; + current_dir[strlen((const char *) current_dir)-1] = 0; + char *b = strrchr((const char *) current_dir, '\\'); + if (b) b[1] = 0; + else b[0] = '\\'; + CE_CharToWide((char *) current_dir, (u16 *) w_current_dir); + set_directory(w_current_dir); + } else { + if (add_to_pl) { + char dir[MAX_PATH]; + TCHAR wdir[MAX_PATH]; + wcscpy(wdir, w_current_dir); + wcscat(wdir, sTxt+2); + wcscat(wdir, _T("\\")); + CE_WideToChar((u16 *) wdir, (char *) dir); + gf_enum_directory(dir, GF_FALSE, add_files, NULL, NULL); + } else { + wcscat(w_current_dir, sTxt+2); + wcscat(w_current_dir, _T("\\")); + CE_WideToChar((u16 *) w_current_dir, (char *) current_dir); + set_directory(w_current_dir); + } + } + } else { + char szTxt[1024]; + CE_WideToChar((u16 *) sTxt, (char *) szTxt); + strcpy((char *) out_url, (const char *) current_dir); + strcat(out_url, szTxt); + if (add_to_pl) { + gf_cfg_set_key(cfg, "Playlist", out_url, ""); + strcpy(out_url, ""); + } else { + if (playlist_mode) { + const char *file; + char szPLE[20]; + sprintf(szPLE, "%d", idx); + gf_cfg_set_key(cfg, "General", "PLEntry", szPLE); + file = gf_cfg_get_key_name(cfg, "Playlist", idx); + strcpy(out_url, file); + } + gf_cfg_set_key(cfg, "General", "LastWorkingDir", (const char *) current_dir); + EndDialog(hWnd, 1); + } + } +} + +BOOL InitFileDialog(const HWND hWnd) +{ + TCHAR psz[80]; + ZeroMemory(psz, sizeof(psz)); + SHINITDLGINFO sid; + ZeroMemory(&sid, sizeof(sid)); + sid.dwMask = SHIDIM_FLAGS; + sid.dwFlags = SHIDIF_SIZEDLGFULLSCREEN; + sid.hDlg = hWnd; + + if (FALSE == SHInitDialog(&sid)) + return FALSE; + + SHMENUBARINFO mbi; + ZeroMemory(&mbi, sizeof(SHMENUBARINFO)); + mbi.cbSize = sizeof(SHMENUBARINFO); + mbi.hwndParent = hWnd; + mbi.nToolBarId = IDR_MENU_OPEN; + mbi.hInstRes = g_hInst; + + if (FALSE == SHCreateMenuBar(&mbi)) + { + return FALSE; + } + g_hWndMenuBar = mbi.hwndMB; + + ShowWindow(g_hWndMenuBar, SW_SHOW); + + the_wnd = hWnd; + + hDirTxt = GetDlgItem(hWnd, IDC_DIRNAME); + hList = GetDlgItem(hWnd, IDC_FILELIST); + g_hMenuView = (HMENU)SendMessage(g_hWndMenuBar, SHCMBM_GETSUBMENU, 0, ID_OF_VIEW); + + RECT rc; + GetClientRect(hWnd, &rc); + u32 caption_h = GetSystemMetrics(SM_CYCAPTION) - 3; + MoveWindow(hDirTxt, 0, 0, rc.right - rc.left, caption_h, 1); + MoveWindow(hList, 0, caption_h, rc.right - rc.left, rc.bottom - rc.top - caption_h, 1); + + if (playlist_mode) { + refresh_playlist(); + } else { + if (!strcmp((const char *) current_dir, "\\")) { + char *opt = (char *) gf_cfg_get_key(cfg, "General", "LastWorkingDir"); + if (opt) CE_CharToWide(opt, (u16 *) w_current_dir); + } + set_directory(w_current_dir); + } + switch_menu_pl(); + return TRUE; +} + +BOOL CALLBACK FileDialogProc(const HWND hWnd, const UINT Msg, const WPARAM wParam, const LPARAM lParam) +{ + BOOL bProcessedMsg = TRUE; + + switch (Msg) { + case WM_INITDIALOG: + if (FALSE == InitFileDialog(hWnd)) + EndDialog(hWnd, -1); + break; + + case WM_ACTIVATE: + if (WA_INACTIVE != LOWORD(wParam)) SetFocus(hWnd); + break; + + case WM_CLOSE: + EndDialog(hWnd, 0); + break; + + case WM_COMMAND: + if (LOWORD(wParam) == IDC_FILELIST) { + if (HIWORD(wParam) == LBN_DBLCLK) { + process_list_change(hWnd, GF_FALSE); + } else { + bProcessedMsg = FALSE; + } + } else { + switch (LOWORD(wParam)) { + case IDOK: + process_list_change(hWnd, GF_FALSE); + break; + case IDCANCEL: + EndDialog(hWnd, 0); + break; + case IDM_OF_VIEW_ALL: + bViewUnknownTypes = (Bool) !bViewUnknownTypes; + refresh_menu_states(); + set_directory(w_current_dir); + break; + case IDM_OF_PLAYLIST: + playlist_mode = (Bool) !playlist_mode; + if (playlist_mode) refresh_playlist(); + else set_directory(w_current_dir); + switch_menu_pl(); + break; + case IDM_OF_PL_ACT: + if (playlist_mode) { + playlist_act(0); + } else { + process_list_change(hWnd, GF_TRUE); + } + break; + case IDM_OF_PL_UP: + playlist_act(1); + break; + case IDM_OF_PL_DOWN: + playlist_act(2); + break; + case IDM_OF_PL_CLEAR: + playlist_act(3); + break; + default: + bProcessedMsg = FALSE; + break; + } + } + break; + case WM_KEYDOWN: + switch (wParam) { + case VK_LEFT: + case '1': + playlist_act(1); + break; + case VK_RIGHT: + case '2': + playlist_act(2); + break; + default: + bProcessedMsg = FALSE; + break; + } + break; + + default: + bProcessedMsg = FALSE; + } + + return bProcessedMsg; +} + +Bool gf_file_dialog(HINSTANCE inst, HWND parent, char *url, const char *ext_list, GF_Config *gpac_cfg) +{ + extension_list = ext_list; + out_url = url; + g_hInst = inst; + cfg = gpac_cfg; + int iResult = DialogBox(inst, MAKEINTRESOURCE(IDD_FILEDIALOG), parent,(DLGPROC)FileDialogProc); + if (iResult>0) return GF_TRUE; + return GF_FALSE; +} + + +#endif + diff --git a/applications/deprecated/old_arch/osmophone/osmophone.rc b/applications/deprecated/old_arch/osmophone/osmophone.rc new file mode 100644 index 0000000..a947092 --- /dev/null +++ b/applications/deprecated/old_arch/osmophone/osmophone.rc @@ -0,0 +1,425 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "newres.h" + +#include + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Anglais (États-Unis) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON ICON "../../doc/osmo4.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""newres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// RCDATA +// + +IDM_MAIN_MENU1 RCDATA +BEGIN + 0x0066, 0x0002, 0xfffe, 0x9c53, 0x0004, 0x0018, 0x9c55, 0x0000, 0x0000, + 0xfffe, 0x9c58, 0x0004, 0x0018, 0x9c59, 0x0000, 0x0001 +END + +IDR_ABOUT_MENU RCDATA +BEGIN + 0x006b, 0x0001, 0xfffe, 0x9c77, 0x0004, 0x0010, 0x9c6c, 0x0000, 0xffff + +END + +IDM_MAIN_MENU2 RCDATA +BEGIN + 0x006c, 0x0002, 0xfffe, 0x9c83, 0x0004, 0x0018, 0x9c85, 0x0000, 0x0000, + 0xfffe, 0x9c87, 0x0004, 0x0018, 0x9c89, 0x0000, 0x0001 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDM_MAIN_MENU MENU +BEGIN + POPUP "File" + BEGIN + MENUITEM "Open", IDM_FILE_OPEN + MENUITEM "Open URL", IDM_FILE_OPEN_URL + POPUP "Recent" + BEGIN + MENUITEM "FILE 1", IDM_OPEN_FILE1 + MENUITEM "FILE 2", IDM_OPEN_FILE2 + MENUITEM "FILE 3", IDM_OPEN_FILE3 + MENUITEM "FILE 4", IDM_OPEN_FILE4 + MENUITEM "FILE 5", IDM_OPEN_FILE5 + MENUITEM "FILE 6", IDM_OPEN_FILE6 + MENUITEM "FILE 7", IDM_OPEN_FILE7 + MENUITEM "FILE 8 ", IDM_OPEN_FILE8 + MENUITEM "FILE 9", IDM_OPEN_FILE9 + END + MENUITEM SEPARATOR + MENUITEM "Pause", IDM_FILE_PAUSE + MENUITEM "Copy/Paste", ID_FILE_CUT_PASTE + MENUITEM SEPARATOR + POPUP "Logs" + BEGIN + POPUP "Level" + BEGIN + MENUITEM "None", ID_LOGLEVEL_NONE + MENUITEM "Error", ID_LOGLEVEL_ERROR + MENUITEM "Warning", ID_LOGLEVEL_WARNING + MENUITEM "Info", ID_LOGLEVEL_INFO + MENUITEM "Debug", ID_LOGLEVEL_DEBUG + END + POPUP "Tools" + BEGIN + MENUITEM "core", ID_TOOLS_CORE + MENUITEM "coding", ID_TOOLS_CODING + MENUITEM "container", ID_TOOLS_CONTAINER + MENUITEM "network", ID_TOOLS_NETWORK + MENUITEM "rtp", ID_TOOLS_RTP + MENUITEM "sync", ID_TOOLS_SYNC + MENUITEM "codec", ID_TOOLS_CODEC + MENUITEM "parser", ID_TOOLS_PARSER + MENUITEM "media", ID_TOOLS_MEDIA + MENUITEM "scene", ID_TOOLS_SCENE + MENUITEM "script", ID_TOOLS_SCRIPT + MENUITEM "interact", ID_TOOLS_INTERACT + MENUITEM "compose", ID_TOOLS_COMPOSE + MENUITEM "mmio", ID_TOOLS_MMIO + MENUITEM "rti", ID_TOOLS_RTI + MENUITEM SEPARATOR + MENUITEM "none", ID_TOOLS_NONE + MENUITEM "all", ID_TOOLS_ALL + END + MENUITEM "Reset", ID_LOGS_RESET + MENUITEM "Log CPU", IDM_FILE_LOG_RTI + END + MENUITEM SEPARATOR + MENUITEM "Exit", IDM_ITEM_QUIT + END + POPUP "View" + BEGIN + POPUP "Navigate" + BEGIN + MENUITEM "Reset", IDM_NAV_RESET + MENUITEM SEPARATOR + MENUITEM "None", IDM_NAV_NONE + MENUITEM "Slide", IDM_NAV_SLIDE + MENUITEM "Walk", IDM_NAV_WALK + MENUITEM "Fly", IDM_NAV_FLY + MENUITEM "Examine", IDM_NAV_EXAMINE + MENUITEM SEPARATOR + MENUITEM "Headlight", IDM_NAV_HEADLIGHT + MENUITEM "Gravity", IDM_NAV_GRAVITY + POPUP "Collision" + BEGIN + MENUITEM "Disabled", IDM_NAV_COL_NONE + MENUITEM "Regular", IDM_NAV_COL_REG + MENUITEM "Displacement", IDM_NAV_COL_DISP + END + END + MENUITEM SEPARATOR + MENUITEM "Fullscreen", IDM_VIEW_FS + POPUP "Info" + BEGIN + MENUITEM "Status Bar", IDM_VIEW_STATUS + MENUITEM "CPU usage", IDM_VIEW_CPU + END + MENUITEM SEPARATOR + POPUP "Video" + BEGIN + MENUITEM "Direct FB", ID_VIDEO_DIRECTFB + MENUITEM "GAPI", ID_VIDEO_GAPI + MENUITEM "GDI", ID_VIDEO_GDI + MENUITEM SEPARATOR + MENUITEM "2D OpenGL", IDM_VIEW_FORCEGL + MENUITEM "Direct Draw", ID_VIDEO_DIRECTDRAW + MENUITEM "15.0 FPS", IDM_VIEW_LOW_RATE + POPUP "Aspect Ratio" + BEGIN + MENUITEM "Keep Original", IDM_VIEW_AR_NONE + MENUITEM "Fill Screen", IDM_VIEW_AR_FILL + MENUITEM "Ratio 4/3", IDM_VIEW_AR_4_3 + MENUITEM "Ratio 16/9", IDM_VIEW_AR_16_9 + END + END + POPUP "Options" + BEGIN + MENUITEM "Progressive SVG ", IDM_VIEW_SVG_LOAD + MENUITEM "Disable Playlist", IDS_CAP_DISABLE_PLAYLIST + END + MENUITEM "About", IDM_VIEW_ABOUT + END +END + +IDR_ABOUT_MENU MENU +BEGIN + MENUITEM "OK", IDM_ABOUT_OK +END + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_CLASSNAME "Osmophone" + IDS_WINDOWNAME "Osmo4" + IDS_APPNAME "Osmophone.exe" +END + +STRINGTABLE +BEGIN + IDS_CAP_QUIT "Exit" + IDS_CAP_FILE "File" + IDS_CAP_EXIT "View" + IDS_CAP_VIEW "View" +END + +STRINGTABLE +BEGIN + IDS_CAP_OK "OK" +END + +STRINGTABLE +BEGIN + IDS_CAP_NAVIGATE "Navigate" +END + +STRINGTABLE +BEGIN + IDS_CAP_OPTION "Options" + IDS_CAP_MENUITEM40072 "?" +END + +STRINGTABLE +BEGIN + IDS_CAP_DISABLE_PLAYLIST "Copy/Paste" +END + +#endif // Anglais (États-Unis) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// Français (France) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_FRA) +#ifdef _WIN32 +LANGUAGE LANG_FRENCH, SUBLANG_FRENCH +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// RCDATA +// + +IDR_MENU_OPEN RCDATA +BEGIN + 0x006a, 0x0002, 0xfffe, 0x9c9b, 0x0004, 0x0010, 0x9c9f, 0x0000, 0xffff, + 0xfffe, 0x9c9a, 0x0004, 0x0018, 0x9c59, 0x0000, 0x0001 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_MENU_OPEN MENU +BEGIN + MENUITEM "Add", IDM_OF_PL_ACT + POPUP "View" + BEGIN + MENUITEM "All Unknown Files", IDM_OF_VIEW_ALL + MENUITEM "Move Up", IDM_OF_PL_UP + MENUITEM "Move Down", IDM_OF_PL_DOWN + MENUITEM "Clear", IDM_OF_PL_CLEAR + MENUITEM SEPARATOR + MENUITEM "Playlist Mode", IDM_OF_PLAYLIST + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_APPABOUT, DIALOG + BEGIN + LEFTMARGIN, 1 + RIGHTMARGIN, 113 + BOTTOMMARGIN, 74 + END + + IDD_FILEDIALOG, DIALOG + BEGIN + RIGHTMARGIN, 103 + BOTTOMMARGIN, 85 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_APPABOUT DIALOG 0, 0, 114, 77 +STYLE DS_SETFONT | DS_SETFOREGROUND | WS_POPUP | WS_CAPTION +CAPTION "About Osmophone" +FONT 8, "System" +BEGIN + ICON IDI_ICON,IDC_STATIC,1,0,16,16 + CTEXT "Osmo4/GPAC\n"GPAC_VERSION" ",IDC_NAMECTRL,23,1,65,17 + CTEXT "Copyright (c) 2007 Telecom ParisTech\nAll Rights Reserved\nLicensed under LGPL",IDC_STATIC,2,44,111,33 +END + +IDD_FILEDIALOG DIALOG 0, 0, 104, 86 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Select File" +FONT 8, "System" +BEGIN + EDITTEXT IDC_DIRNAME,1,3,100,12,ES_AUTOHSCROLL | ES_READONLY + LISTBOX IDC_FILELIST,1,18,102,65,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040c04b0" + BEGIN + VALUE "CompanyName", "Telecom ParisTech" + VALUE "FileDescription", "osmophone" + VALUE "FileVersion", GPAC_VERSION + VALUE "InternalName", "osmophone" + VALUE "LegalCopyright", "Copyright © Telecom ParisTech 2008" + VALUE "OriginalFilename", "osmophone.exe" + VALUE "ProductName", "Osmo4-GPAC" + VALUE "ProductVersion", GPAC_VERSION + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x40c, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_CAP_FILE "+" + IDS_CAP_VIEW "View" +END + +STRINGTABLE +BEGIN + IDS_CAP_OK "OK" +END + +STRINGTABLE +BEGIN + IDS_CAP_SELECT "File" +END + +STRINGTABLE +BEGIN + IDS_CAP_MENUITEM40092 "Add" + IDS_CAP_ADD "Add" +END + +#endif // Français (France) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/applications/deprecated/old_arch/osmophone/osmophone.vcxproj b/applications/deprecated/old_arch/osmophone/osmophone.vcxproj new file mode 100644 index 0000000..90f552c --- /dev/null +++ b/applications/deprecated/old_arch/osmophone/osmophone.vcxproj @@ -0,0 +1,343 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {CAA7EF3E-C7F4-4F8A-9E17-9A06DEF9E717} + osmophone + + + + Application + false + Unicode + + + Application + false + Unicode + + + Application + false + Unicode + + + Application + false + Unicode + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(Configuration)\ + $(Configuration)\ + $(Configuration)\ + $(Configuration)\ + true + true + $(Configuration)\ + $(Configuration)\ + $(Configuration)\ + $(Configuration)\ + false + false + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(Platform)\$(Configuration)/osmophone.tlb + + + + + Disabled + ../../include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_CONSOLE + MultiThreadedDebugDLL + + + $(IntDir)$(ProjectName).pch + .\obj\$(Platform)\$(Configuration)\osmophone/ + .\obj\$(Platform)\$(Configuration)\osmophone/ + .\obj\$(Platform)\$(Configuration)\osmophone/ + true + Level3 + true + ProgramDatabase + + + /r %(AdditionalOptions) + UNDER_CE=$(CEVER);_WIN32_WCE=$(CEVER);DEBUG;UNICODE;_UNICODE;$(CePlatform);ARM;_ARM_;ARMV4;%(PreprocessorDefinitions) + 0x0409 + ../../include + + + ../../bin/$(Platform)\$(Configuration)/osmophone.exe + true + ../../extra_lib/lib/$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + + + true + .\obj\$(Platform)\$(Configuration)\osmophone/osmophone.pdb + Windows + 65536 + 4096 + WinMainCRTStartup + 0x00010000 + MachineX86 + + + true + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + $(Platform)\$(Configuration)/osmophone.tlb + + + + + Disabled + ../../include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_CONSOLE + MultiThreadedDebugDLL + + + $(IntDir)$(ProjectName).pch + .\obj\$(Platform)\$(Configuration)\osmophone/ + .\obj\$(Platform)\$(Configuration)\osmophone/ + .\obj\$(Platform)\$(Configuration)\osmophone/ + true + Level3 + true + ProgramDatabase + + + /r %(AdditionalOptions) + UNDER_CE=$(CEVER);_WIN32_WCE=$(CEVER);DEBUG;UNICODE;_UNICODE;$(CePlatform);ARM;_ARM_;ARMV4;%(PreprocessorDefinitions) + 0x0409 + ../../include + + + ../../bin/$(Platform)\$(Configuration)/osmophone.exe + true + ../../extra_lib/lib/$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + + + true + .\obj\$(Platform)\$(Configuration)\osmophone/osmophone.pdb + Windows + 65536 + 4096 + WinMainCRTStartup + 0x00010000 + + + true + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(Platform)\$(Configuration)/osmophone.tlb + + + + + Disabled + ../../include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_CONSOLE + MultiThreadedDLL + + + $(IntDir)$(ProjectName).pch + $(IntDir) + $(IntDir) + $(IntDir) + Level3 + true + + + /r %(AdditionalOptions) + UNDER_CE=$(CEVER);_WIN32_WCE=$(CEVER);NDEBUG;UNICODE;_UNICODE;$(CePlatform);ARM;_ARM_;ARMV4;%(PreprocessorDefinitions) + 0x0409 + + + ../../bin/$(Platform)/$(Configuration)/$(ProjectName).exe + true + ../../extra_lib/lib/$(Platform)/$(Configuration);%(AdditionalLibraryDirectories) + + + .\obj\$(Platform)\$(Configuration)\osmophone/osmophone.pdb + Windows + 65536 + 4096 + WinMainCRTStartup + MachineX86 + + + true + $(IntDir)$(ProjectName).bsc + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + $(Platform)\$(Configuration)/osmophone.tlb + + + + + Disabled + ../../include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_CONSOLE + MultiThreadedDLL + + + $(IntDir)$(ProjectName).pch + $(IntDir) + $(IntDir) + $(IntDir) + Level3 + true + + + /r %(AdditionalOptions) + UNDER_CE=$(CEVER);_WIN32_WCE=$(CEVER);NDEBUG;UNICODE;_UNICODE;$(CePlatform);ARM;_ARM_;ARMV4;%(PreprocessorDefinitions) + 0x0409 + + + ../../bin/$(Platform)/$(Configuration)/$(ProjectName).exe + true + ../../extra_lib/lib/$(Platform)/$(Configuration);%(AdditionalLibraryDirectories) + + + .\obj\$(Platform)\$(Configuration)\osmophone/osmophone.pdb + Windows + 65536 + 4096 + WinMainCRTStartup + + + true + $(IntDir)$(ProjectName).bsc + + + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + + + + + true + true + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + + + + + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + + + + + true + true + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + + + + + + + + + true + true + DEBUG;_AFXDLL + DEBUG;_AFXDLL + 0x0809 + 0x0809 + ../../doc;%(AdditionalIncludeDirectories) + ../../doc;%(AdditionalIncludeDirectories) + true + true + DEBUG;_AFXDLL + DEBUG;_AFXDLL + 0x0809 + 0x0809 + ../../doc;%(AdditionalIncludeDirectories) + ../../doc;%(AdditionalIncludeDirectories) + + + + + + + + + + + + {d3540754-e0cf-4604-ac11-82de9bd4d814} + false + + + + + + \ No newline at end of file diff --git a/applications/deprecated/old_arch/osmophone/resource.h b/applications/deprecated/old_arch/osmophone/resource.h new file mode 100644 index 0000000..5a966d1 --- /dev/null +++ b/applications/deprecated/old_arch/osmophone/resource.h @@ -0,0 +1,169 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by osmophone.rc +// +#define IDS_CLASSNAME 1 +#define IDS_WINDOWNAME 4 +#define IDS_APPNAME 5 +#define IDI_ICON 101 +#define IDM_MENU 102 +#define IDM_MAIN_MENU 102 +#define IDM_MAIN_MENU1 102 +#define IDD_ABOUTBOX 103 +#define IDD_APPABOUT 104 +#define IDD_OPENFILE 105 +#define IDD_FILEDIALOG 105 +#define IDR_MENU_OPEN 106 +#define IDR_ABOUT_MENU 107 +#define IDM_MAIN_MENU2 108 +#define IDM_MAIN_MENUITEM1 401 +#define IDM_MAIN_MENUITEM2 402 +#define IDS_MAIN_MENUITEM1 410 +#define IDS_MAIN_MENUITEM2 412 +#define IDC_EDIT1 1001 +#define IDC_DIRNAME 1001 +#define IDC_LIST1 1002 +#define IDC_FILELIST 1002 +#define IDC_LIST2 1003 +#define IDC_NAMECTRL 1004 +#define IDM_FILE_EXIT 40002 +#define IDM_HELP_ABOUT 40003 +#define IDM_GRAB 40004 +#define IDM_RELEASE 40005 +#define ID_FILE_SHOW_WMKEYDOWN 40006 +#define ID_FILE_SHOW_WMKEYUP 40007 +#define ID_FILE_SHOW_WMCHAR 40008 +#define ID_FILE_SHOW_WMMOUSEMOVE 40009 +#define ID_FILE_SHOW_WMLBUTTONDOWN 40010 +#define ID_FILE_SHOW_WMLBUTTONDBLCLK 40011 +#define ID_FILE_SHOW_WMLBUTTONUP 40012 +#define ID_FILE_SHOW_WMRBUTTONDOWN 40013 +#define ID_FILE_SHOW_WMRBUTTONUP 40014 +#define ID_FILE_SHOW_WMRBUTTONDBLCLK 40015 +#define IDM_ITEM_QUIT 40016 +#define IDS_CAP_QUIT 40018 +#define ID_MENU_FILE 40019 +#define IDS_CAP_FILE 40021 +#define IDM_FILE_OPEN 40022 +#define IDS_CAP_EXIT 40023 +#define IDM_MENU_VIEW 40024 +#define IDS_CAP_VIEW 40025 +#define IDM_FILE_OPEN_URL 40026 +#define IDM_OPEN_FILE1 40030 +#define IDM_OPEN_FILE2 40031 +#define IDM_OPEN_FILE3 40032 +#define IDM_OPEN_FILE4 40033 +#define IDM_OPEN_FILE5 40034 +#define IDM_OPEN_FILE6 40035 +#define IDM_OPEN_FILE7 40036 +#define IDM_OPEN_FILE8 40037 +#define IDM_OPEN_FILE9 40038 +#define IDM_OPEN_FILE10 40039 +#define ID_VIEW 40040 +#define IDM_OF_VIEW_ALL 40041 +#define IDS_CAP_OK 40044 +#define IDS_CAP_MENUITEM40045 40046 +#define ID_SELECT 40047 +#define IDS_CAP_SELECT 40049 +#define IDM_OF_PL_ADD 40050 +#define IDM_OF_PLAYLIST 40051 +#define IDM_OF_PL_REM 40052 +#define IDM_VIEW_FS 40053 +#define IDM_VIEW_ABOUT 40054 +#define IDM_ABOUT_OK 40055 +#define IDM_VIEW_STATUS 40057 +#define IDM_VIEW_FORCEGL 40058 +#define ID_NAVIGATE 40059 +#define IDS_CAP_NAVIGATE 40061 +#define IDM_NAV_NONE 40062 +#define IDM_NAV_SLIDE 40063 +#define IDM_SELECT 40064 +#define IDM_NAV_RESET 40065 +#define IDM_MENU_SWITCH 40066 +#define ID_OPTION 40067 +#define IDS_CAP_OPTION 40069 +#define ID_MENUITEM40071 40071 +#define IDS_CAP_MENUITEM40072 40073 +#define IDM_NAV_WALK 40074 +#define IDM_NAV_FLY 40075 +#define IDM_NAV_EXAMINE 40076 +#define IDM_NAV_HEADLIGHT 40077 +#define IDM_NAV_GRAVITY 40078 +#define IDM_NAV_COL_NONE 40079 +#define IDM_NAV_COL_REG 40080 +#define IDM_NAV_COL_DISP 40081 +#define IDM_VIEW_AR_NONE 40082 +#define IDM_VIEW_AR_FILL 40083 +#define IDM_VIEW_AR_4_3 40084 +#define IDM_VIEW_AR_16_9 40085 +#define IDM_OF_PL_UP 40088 +#define IDM_OF_PL_DOWN 40089 +#define ID_OF_VIEW 40090 +#define IDM_OF_PL_ACT 40091 +#define IDS_CAP_MENUITEM40092 40093 +#define IDS_CAP_ADD 40095 +#define IDM_OF_PL_CLEAR 40096 +#define IDM_FILE_LOG_RTI 40097 +#define IDM_VIEW_CPU 40098 +#define IDM_FILE_PAUSE 40099 +#define IDM_VIEW_LOW_RATE 40100 +#define IDM_VIEW_SVG_LOAD 40102 +#define IDS_CAP_DISABLE_PLAYLIST 40103 +#define ID_FILE_CUT_PASTE 40104 +#define Fra 40105 +#define ID_VIDEO_DIRECTFB 40106 +#define ID_VIDEO_GAPI 40107 +#define ID_VIDEO_GDI 40108 +#define ID_VIDEO_DIRECTDRAW 40109 +#define ID_OPTIONS_TEST 40111 +#define ID_VIEW_INFO 40112 +#define ID_Menu 40113 +#define ID_OPTIONS_TEST40114 40114 +#define ID_OPTIONS_VIDEOOUT 40115 +#define ID_VIDEOOUT_FD 40116 +#define ID_OPTIONS_VIDEOOUT40117 40117 +#define ID_FILE_LOGS 40118 +#define ID_FILE_DS 40119 +#define ID_LOGS_ENABLED 40120 +#define ID_LOGS_LEVEL 40121 +#define ID_LEVEL_DEBUG 40122 +#define ID_LEVEL_WARNING 40123 +#define ID_LEVEL_INFO 40124 +#define ID_LEVEL_DEBUG40125 40125 +#define ID_LOGLEVEL_ERROR 40126 +#define ID_LOGLEVEL_WARNING 40127 +#define ID_LOGLEVEL_INFO 40128 +#define ID_LOGLEVEL_DEBUG 40129 +#define ID_LOGS_TOOLS 40130 +#define ID_LOGTOOL_CORE 40131 +#define ID_TOOLS_CODING 40132 +#define ID_TOOLS_CONTAINER 40133 +#define ID_TOOLS_NETWORK 40134 +#define ID_TOOLS_RTP 40135 +#define ID_TOOLS_SYNC 40136 +#define ID_TOOLS_CODEC 40137 +#define ID_TOOLS_PARSER 40138 +#define ID_TOOLS_MEDIA 40139 +#define ID_TOOLS_SCENE 40140 +#define ID_TOOLS_SCRIPT 40141 +#define ID_TOOLS_INTERACT 40142 +#define ID_TOOLS_COMPOSE 40143 +#define ID_TOOLS_MMIO 40144 +#define ID_TOOLS_RTI 40145 +#define ID_TOOLS_NONE 40146 +#define ID_TOOLS_ALL 40147 +#define ID_TOOLS_CORE 40148 +#define ID_LEVEL_NONE 40149 +#define ID_LOGLEVEL_NONE 40150 +#define ID_LOGS_RESET 40151 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 111 +#define _APS_NEXT_COMMAND_VALUE 40152 +#define _APS_NEXT_CONTROL_VALUE 1005 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/applications/deprecated/old_arch/osmozilla/Makefile b/applications/deprecated/old_arch/osmozilla/Makefile new file mode 100644 index 0000000..694701e --- /dev/null +++ b/applications/deprecated/old_arch/osmozilla/Makefile @@ -0,0 +1,109 @@ +include ../../config.mak + +vpath %.cpp $(SRC_PATH)/applications/osmozilla + +ifeq ($(CONFIG_WIN32),yes) +USER_NAME=root +else +USER_NAME=$(shell whoami) +ifeq ($(USER_NAME),root) +else +MOZILLA_DIR=local +endif +endif + +CFLAGS=$(CXXFLAGS) $(XUL_CFLAGS) -I"$(SRC_PATH)/include" + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +ifeq ($(CONFIG_WIN32),yes) +CFLAGS+=-DXP_WIN +else +ifeq ($(CONFIG_DARWIN),yes) +CFLAGS+=-DXP_MAC +else +CFLAGS+=-DXP_UNIX -DMOZ_X11 +endif +endif + +CFLAGS+=-DNPBASIC_EXPORTS -DMOZILLA_STRICT_API -DXPCOM_GLUE + + +LINKLIBS=-L../../bin/gcc -lgpac + +OBJS=osmozilla.o osmo_npapi.o + +SRCS := $(OBJS:.o=.cpp) + + +LIB=nposmozilla$(DYN_LIB_SUFFIX) +ifeq ($(CONFIG_WIN32),yes) +LINKLIBS+=-lwinmm -lgdi32 +LDFLAGS+=--export-all-symbols +endif + +all: $(LIB) + +$(LIB): $(OBJS) +ifeq ($(CONFIG_WIN32),yes) + windres osmozilla.rc osmoz.o + $(CXX) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) osmoz.o $(LINKLIBS) $(LDFLAGS) + cp "$(SRC_PATH)/applications/osmozilla/nsIOsmozilla.xpt_w32" ../../bin/gcc/nposmozilla.xpt + chmod +w ../../bin/gcc/nposmozilla.xpt +else + $(CXX) $(SHFLAGS) $(OBJS) $(LINKLIBS) -o ../../bin/gcc/$@ $(LDFLAGS) + cp "$(SRC_PATH)/applications/osmozilla/nsIOsmozilla.xpt_linux" ../../bin/gcc/nposmozilla.xpt + chmod +w ../../bin/gcc/nposmozilla.xpt +endif + @echo $(USER_ROOT) + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) ../../bin/gcc/nposmozilla.xpt +ifeq ($(CONFIG_WIN32),yes) + rm -f osmoz.o +endif + +install: +ifeq ($(MOZILLA_DIR),local) +ifeq ($(USER_NAME),root) + @echo "*** Root cannot install local mozilla plugins! ***" + @echo "*** Exit root mode and reinstall mozilla plugin! ***" +else + $(MAKE) $(LIB) + $(INSTALL) -D -m 755 ../../bin/gcc/$(LIB) "$(HOME)/.mozilla/plugins/$(LIB)" + $(INSTALL) -D -m 755 ../../bin/gcc/nposmozilla.xpt "$(HOME)/.mozilla/components/nposmozilla.xpt" +endif +else + $(INSTALL) -D -m 755 ../../bin/gcc/$(LIB) "$(MOZILLA_DIR)/components/$(LIB)" + $(INSTALL) -D -m 755 ../../bin/gcc/nposmozilla.xpt "$(MOZILLA_DIR)/components/nposmozilla.xpt" +endif + +uninstall: +ifeq ($(MOZILLA_DIR),local) +ifeq ($(USER_NAME),root) +else + rm -rf "$(HOME)/.mozilla/plugins/$(LIB)" + rm -rf "$(HOME)/.mozilla/components/nposmozilla.xpt" +endif +else + rm -rf "$(MOZILLA_DIR)/components/$(LIB)" + rm -rf "$(MOZILLA_DIR)/components/nposmozilla.xpt" +endif + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + diff --git a/applications/deprecated/old_arch/osmozilla/nsIOsmozilla.h b/applications/deprecated/old_arch/osmozilla/nsIOsmozilla.h new file mode 100644 index 0000000..e52276d --- /dev/null +++ b/applications/deprecated/old_arch/osmozilla/nsIOsmozilla.h @@ -0,0 +1,148 @@ +/* +* DO NOT EDIT. THIS FILE IS GENERATED FROM nsIOsmozilla.idl +*/ + +#ifndef __gen_nsIOsmozilla_h__ +#define __gen_nsIOsmozilla_h__ + +#include "osmo_npapi.h" + +#ifdef GECKO_XPCOM + +#ifndef __gen_nsISupports_h__ +#include "nsISupports.h" +#endif + +/* For IDL files that don't want to include root IDL files. */ +#ifndef NS_NO_VTABLE +#define NS_NO_VTABLE +#endif + +/* starting interface: nsIOsmozilla */ +#define NS_IOSMOZILLA_IID_STR "d2d536a0-b6fc-11d5-9d10-0060b0fbd80b" + +#define NS_IOSMOZILLA_IID \ + {0xd2d536a0, 0xb6fc, 0x11d5, \ + { 0x9d, 0x10, 0x00, 0x60, 0xb0, 0xfb, 0xd8, 0x0b }} + +class NS_NO_VTABLE nsIOsmozilla : public nsISupports { +public: + + NS_DEFINE_STATIC_IID_ACCESSOR(NS_IOSMOZILLA_IID) + + /* void Pause (); */ + NS_IMETHOD Pause(void) = 0; + + /* void Play (); */ + NS_IMETHOD Play(void) = 0; + + /* void Stop (); */ + NS_IMETHOD Stop(void) = 0; + + /* void Update (in string type, in string commands); */ + NS_IMETHOD Update(const char *type, const char *commands) = 0; + + /* void QualitySwitch (in int switch_up); */ + NS_IMETHOD QualitySwitch(int switch_up) = 0; + + /* void SetURL (in string url); */ + NS_IMETHOD SetURL(const char *url) = 0; +}; + +/* Use this macro when declaring classes that implement this interface. */ +#define NS_DECL_NSIOSMOZILLA \ + NS_IMETHOD Pause(void); \ + NS_IMETHOD Play(void); \ + NS_IMETHOD Stop(void); \ + NS_IMETHOD Update(const char *type, const char *commands); \ + NS_IMETHOD QualitySwitch(int switch_up); \ + NS_IMETHOD SetURL(const char *type); + +/* Use this macro to declare functions that forward the behavior of this interface to another object. */ +#define NS_FORWARD_NSIOSMOZILLA(_to) \ + NS_IMETHOD Pause(void) { return _to Pause(); } \ + NS_IMETHOD Play(void) { return _to Play(); } \ + NS_IMETHOD Stop(void) { return _to Stop(); } \ + NS_IMETHOD Update(const char *type, const char *commands) { return _to Update(type, commands); } \ + NS_IMETHOD QualitySwitch(int switch_up) { return _to QualitySwitch( switch_up ); } \ + NS_IMETHOD SetURL(const char *url) { return _to SetURL(url); } + +/* Use this macro to declare functions that forward the behavior of this interface to another object in a safe way. */ +#define NS_FORWARD_SAFE_NSIOSMOZILLA(_to) \ + NS_IMETHOD Pause(void) { return !_to ? NS_ERROR_NULL_POINTER : _to->Pause(); } \ + NS_IMETHOD Play(void) { return !_to ? NS_ERROR_NULL_POINTER : _to->Play(); } \ + NS_IMETHOD Stop(void) { return !_to ? NS_ERROR_NULL_POINTER : _to->Stop(); } \ + NS_IMETHOD Update(const char *type, const char *commands) { return !_to ? NS_ERROR_NULL_POINTER : _to->Update(type, commands); } \ + NS_IMETHOD QualitySwitch(int switch_up) { return !_to ? NS_ERROR_NULL_POINTER : _to->QualitySwitch(switch_up); } \ + NS_IMETHOD SetURL(const char *url) { return !_to ? NS_ERROR_NULL_POINTER : _to->Update(url); } \ + +#if 0 +/* Use the code below as a template for the implementation class for this interface. */ + +/* Header file */ +class nsOsmozilla : public nsIOsmozilla +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOSMOZILLA + + nsOsmozilla(); + virtual ~nsOsmozilla(); + /* additional members */ +}; + +/* Implementation file */ +NS_IMPL_ISUPPORTS1(nsOsmozilla, nsIOsmozilla) + +nsOsmozilla::nsOsmozilla() +{ + /* member initializers and constructor code */ +} + +nsOsmozilla::~nsOsmozilla() +{ + /* destructor code */ +} + +/* void Pause (); */ +NS_IMETHODIMP nsOsmozilla::Pause() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void Play (); */ +NS_IMETHODIMP nsOsmozilla::Play() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void Stop (); */ +NS_IMETHODIMP nsOsmozilla::Stop() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void Update (in string type, in string commands); */ +NS_IMETHODIMP nsOsmozilla::Update(const char *type, const char *commands) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void QualitySwitch (in int switch_up); */ +NS_IMETHODIMP nsOsmozilla::QualitySwitch(int switch_up) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void SetURL (in string url); */ +NS_IMETHODIMP nsOsmozilla::SetURL(const char *type) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* End of implementation class template. */ +#endif + +#endif //GECKO_XPCOM + +#endif /* __gen_nsIOsmozilla_h__ */ diff --git a/applications/deprecated/old_arch/osmozilla/nsIOsmozilla.idl b/applications/deprecated/old_arch/osmozilla/nsIOsmozilla.idl new file mode 100644 index 0000000..ba44e77 --- /dev/null +++ b/applications/deprecated/old_arch/osmozilla/nsIOsmozilla.idl @@ -0,0 +1,10 @@ +#include "nsISupports.idl" + +[scriptable, uuid(ce32e3ff-36f8-425f-94be-d85b26e634ee)] +interface nsIOsmozilla : nsISupports { + void Pause(); + void Play(); + void Stop(); + void Update(in string type, in string commands); + void QualitySwitch(in long switch_up); +}; diff --git a/applications/deprecated/old_arch/osmozilla/nsIOsmozilla.xpt_linux b/applications/deprecated/old_arch/osmozilla/nsIOsmozilla.xpt_linux new file mode 100644 index 0000000000000000000000000000000000000000..1c96fbb3cbf5e69ed61129a611f127121875f92a GIT binary patch literal 180 zcmazDaQ64*3aKne^~p@)<&t7#VqjumVAul0N-XaA`q7eo;v=1CZ@soSR>jnUj+U(#`@>-XaA`q7eo;v=1CZ@soSR>jnUj+U(#`@> +#include +#endif + +NPNetscapeFuncs *sBrowserFunctions = NULL; + +NPError Osmozilla_GetURL(NPP instance, const char *url, const char *target) +{ + if (!sBrowserFunctions) return NPERR_INVALID_FUNCTABLE_ERROR; + return sBrowserFunctions->geturl(instance, url, target); +} + +void Osmozilla_SetStatus(NPP instance, const char *message) +{ + if (!sBrowserFunctions) return; + sBrowserFunctions->status(instance, message); +} + +#ifndef GECKO_XPCOM +void Osmozilla_InitScripting(Osmozilla *osmo); +#endif + +NPError NPOsmozilla_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved) +{ + Osmozilla *osmo; + NPError rv = NPERR_NO_ERROR; + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + osmo = (Osmozilla *) malloc(sizeof(Osmozilla)); + memset(osmo, 0, sizeof(Osmozilla)); + + osmo->np_instance = instance; + // associate the plugin instance object with NPP instance + instance->pdata = (void *)osmo; + + osmo->supports_xembed = 0; + sBrowserFunctions->getvalue(NULL, NPNVSupportsXEmbedBool, (void *)&osmo->supports_xembed); + + Osmozilla_Initialize(osmo, argc, argn, argv); + +#ifndef GECKO_XPCOM + Osmozilla_InitScripting(osmo); +#endif + + return rv; +} + +// here is the place to clean up and destroy the nsPluginInstance object +NPError NPOsmozilla_Destroy (NPP instance, NPSavedData** save) +{ + NPError rv = NPERR_NO_ERROR; + Osmozilla *osmozilla; + if(instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + osmozilla = (Osmozilla*)instance->pdata; + if (osmozilla != NULL) { + Osmozilla_Shutdown(osmozilla); +#ifdef GECKO_XPCOM + NPOsmozilla_ShutdownScript(osmozilla); +#else + if (osmozilla->script_obj) sBrowserFunctions->releaseobject(osmozilla->script_obj); + osmozilla->script_obj = NULL; +#endif + + free(osmozilla); + } + instance->pdata = NULL; + return rv; +} + +// during this call we know when the plugin window is ready or +// is about to be destroyed so we can do some gui specific +// initialization and shutdown +NPError NPOsmozilla_SetWindow (NPP instance, NPWindow* pNPWindow) +{ + Osmozilla *osmozilla; + void *os_wnd_handle, *os_wnd_display; + NPError rv = NPERR_NO_ERROR; + + if (!instance || !instance->pdata) return NPERR_INVALID_INSTANCE_ERROR; + if (pNPWindow == NULL) return NPERR_GENERIC_ERROR; + osmozilla = (Osmozilla *)instance->pdata; + + // window just created + if (!osmozilla->window_set) { + if (pNPWindow->window == NULL) return NPERR_GENERIC_ERROR; + +#ifdef XP_WIN + os_wnd_handle = pNPWindow->window; + os_wnd_display = NULL; +#elif defined(XP_MAXOS) + os_wnd_handle = pNPWindow->window; + os_wnd_display = NULL; +#elif defined(XP_UNIX) + os_wnd_handle = pNPWindow->window; + /*HACK - although we don't use the display in the X11 plugin, this is used to signal that + the user is mozilla and prevent some X11 calls crashing the browser in file playing mode + (eg, "firefox myfile.mp4" )*/ + os_wnd_display =((NPSetWindowCallbackStruct *)pNPWindow->ws_info)->display; +#endif + + if (! Osmozilla_SetWindow(osmozilla, os_wnd_handle, os_wnd_display, pNPWindow->width, pNPWindow->height) ) { + return NPERR_MODULE_LOAD_FAILED_ERROR; + } + + } + +#if 0 + // window goes away + if((pNPWindow->window == NULL) && plugin->isInitialized()) + return plugin->SetWindow(pNPWindow); + + // window resized? + if(plugin->isInitialized() && (pNPWindow->window != NULL)) + return plugin->SetWindow(pNPWindow); + + // this should not happen, nothing to do + if((pNPWindow->window == NULL) && !plugin->isInitialized()) + return plugin->SetWindow(pNPWindow); +#endif + + return rv; +} + +NPError NPOsmozilla_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16_t *stype) +{ + Osmozilla *osmozilla; + if(instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + osmozilla = (Osmozilla *)instance->pdata; + if(osmozilla== NULL) + return NPERR_GENERIC_ERROR; + + Osmozilla_ConnectTo(osmozilla, stream->url); + *stype = NP_SEEK; + return NPERR_NO_ERROR; +} + +NPINT32 NPOsmozilla_WriteReady (NPP instance, NPStream *stream) +{ + return 0x0fffffff; +} + +NPINT32 NPOsmozilla_Write (NPP instance, NPStream *stream, NPINT32 offset, NPINT32 len, void *buffer) +{ + return len; +} + +NPError NPOsmozilla_DestroyStream (NPP instance, NPStream *stream, NPError reason) +{ + return NPERR_NO_ERROR; +} + +void NPOsmozilla_StreamAsFile (NPP instance, NPStream* stream, const char* fname) +{ +} + +void NPOsmozilla_Print (NPP instance, NPPrint* printInfo) +{ + Osmozilla *osmozilla; + if(instance == NULL) + return; + + osmozilla = (Osmozilla *)instance->pdata; + if(osmozilla== NULL) + return; + + Osmozilla_Print(osmozilla, (printInfo->mode == NP_EMBED) ? 1 : 0, printInfo->print.embedPrint.platformPrint, + printInfo->print.embedPrint.window.x, printInfo->print.embedPrint.window.y, + printInfo->print.embedPrint.window.width, printInfo->print.embedPrint.window.height); +} + +void NPOsmozilla_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData) +{ + return; +} + +NPError NPOsmozilla_GetValue(NPP instance, NPPVariable variable, void *value) +{ + NPError rv = NPERR_NO_ERROR; + Osmozilla *osmozilla; + if(instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + osmozilla = (Osmozilla *)instance->pdata; + if(osmozilla== NULL) return NPERR_GENERIC_ERROR; + + switch (variable) { +#ifdef GECKO_XPCOM + case NPPVpluginScriptableInstance: + rv = NPOsmozilla_GetPeer(osmozilla, value); + break; + + case NPPVpluginScriptableIID: + rv = NPOsmozilla_GetPeerIID(osmozilla, value); + break; +#else + + case NPPVpluginScriptableNPObject: + sBrowserFunctions->retainobject(osmozilla->script_obj); + * (void **)value = osmozilla->script_obj; + break; + +#endif + case NPPVpluginNeedsXEmbed: + *((int *)value) = 1; + break; + case NPPVpluginNameString : + *(const char**)value = "Osmozilla/GPAC plugin for NPAPI"; + break; + default: + break; + } + + return rv; +} + + + +NPError NPOsmozilla_SetValue(NPP instance, NPNVariable variable, void *value) +{ + return NPERR_NO_ERROR; +} + +int16_t NPOsmozilla_HandleEvent(NPP instance, void* event) +{ + /*we hacked the proc*/ + return 0; +} + + +NPError OSCALL NP_Shutdown() +{ +#ifdef GECKO_XPCOM + NPOsmozilla_ReleaseServiceManager(); +#endif + return NPERR_NO_ERROR; +} + +static NPError fillPluginFunctionTable(NPPluginFuncs* aNPPFuncs) +{ + if(aNPPFuncs == NULL) + return NPERR_INVALID_FUNCTABLE_ERROR; + + // Set up the plugin function table that Netscape will use to + // call us. Netscape needs to know about our version and size + // and have a UniversalProcPointer for every function we implement. + + aNPPFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; + aNPPFuncs->newp = NPOsmozilla_New; + aNPPFuncs->destroy = NPOsmozilla_Destroy; + aNPPFuncs->setwindow = NPOsmozilla_SetWindow; + aNPPFuncs->newstream = NPOsmozilla_NewStream; + aNPPFuncs->destroystream = NPOsmozilla_DestroyStream; + aNPPFuncs->asfile = NPOsmozilla_StreamAsFile; + aNPPFuncs->writeready = NPOsmozilla_WriteReady; + aNPPFuncs->write = NPOsmozilla_Write; + aNPPFuncs->print = NPOsmozilla_Print; + aNPPFuncs->event = NPOsmozilla_HandleEvent; + aNPPFuncs->urlnotify = NPOsmozilla_URLNotify; + aNPPFuncs->getvalue = NPOsmozilla_GetValue; + aNPPFuncs->setvalue = NPOsmozilla_SetValue; + return NPERR_NO_ERROR; +} + + +static NPError NS_PluginInitialize() +{ +#ifdef GECKO_XPCOM + NPOsmozilla_GetServiceManager(); +#endif + return NPERR_NO_ERROR; +} + +// get values per plugin +NPError NS_PluginGetValue(NPPVariable aVariable, void *aValue) +{ + NPError err = NPERR_NO_ERROR; + switch (aVariable) { + case NPPVpluginNameString: + *((char **)aValue) = (char *) "Osmozilla"; + break; + case NPPVpluginDescriptionString: + *((char **)aValue) = Osmozilla_GetVersion(); + break; + default: + err = NPERR_INVALID_PARAM; + break; + } + return err; +} + + + + +#define GPAC_PLUGIN_MIMETYPES \ + "audio/mpeg:mp2,mp3,mpga,mpega:MP3 Music;" \ + "audio/x-mpeg:mp2,mp3,mpga,mpega:MP3 Music;" \ + "audio/amr:amr,awb:AMR Audio;" \ + "audio/mp4:mp4,mpg4,mpeg4,m4a:MPEG-4 Audio;" \ + "audio/aac:aac:MPEG-4 AAC Music;" \ + "audio/aacp:aac:MPEG-4 AACPlus Music;" \ + "audio/basic:snd,au:Basic Audio;" \ + "audio/x-wav:wav:WAV Audio;" \ + "audio/3gpp:3gp,3gpp:3GPP/MMS Music;" \ + "audio/3gpp2:3g2,3gp2:3GPP2/MMS Music;" \ + "video/mpeg:mpg,mpeg,mpe,mpv2:MPEG Video;" \ + "video/x-mpeg:mpg,mpeg,mpe,mpv2:MPEG Video;" \ + "video/mpeg-system:mpg,mpeg,mpe,vob,mpv2:MPEG Video;" \ + "video/x-mpeg-system:mpg,mpeg,mpe,vob,mpv2:MPEG Video;" \ + "video/avi:avi:AVI Video;" \ + "video/quicktime:mov,qt:QuickTime Movies;" \ + "video/x-ms-asf:asf,asx:Windows Media Video;" \ + "video/x-ms-wmv:wmv:Windows Media;" \ + "video/mp4:mp4,mpg4:MPEG-4 Video;" \ + "video/3gpp:3gp,3gpp:3GPP/MMS Video;" \ + "video/3gpp2:3g2,3gp2:3GPP2/MMS Video;" \ + "image/jpeg:jpeg,jpg:JPEG Images;" \ + "image/png:png:PNG Images;" \ + "image/bmp:bmp:MS Bitmap Images;" \ + "image/svg+xml:svg,svg.gz,svgz:SVG Document;" \ + "image/x-svgm:svgm:SVGM Document;" \ + "x-subtitle/srt:srt:SRT SubTitles;" \ + "x-subtitle/sub:sub:SUB SubTitles;" \ + "x-subtitle/ttxt:ttxt:GPAC 3GPP TimedText;" \ + "model/vrml:wrl,wrl.gz:VRML World;" \ + "model/x3d+vrml:x3dv,x3dv.gz,x3dvz:X3D/VRML World;" \ + "model/x3d+xml:x3d,x3d.gz,x3dz:X3D/XML World;" \ + "application/ogg:ogg:Ogg Media;" \ + "application/x-ogg:ogg:Ogg Media;" \ + "application/x-bt:bt,bt.gz,btz:MPEG-4 Text (BT);" \ + "application/x-xmt:xmt,xmt.gz,xmtz:MPEG-4 Text (XMT);" \ + "application/mp4:mp4,mpg4:MPEG-4 Movies;" \ + "application/sdp:sdp:Streaming Media Session;" \ + /* explicit plugin call */ \ + "application/x-gpac::GPAC plugin;" \ + +char * NP_GetMIMEDescription(void) +{ + return (char *) GPAC_PLUGIN_MIMETYPES; +} + + +NPError NP_GetValue(void *future, NPPVariable aVariable, void *aValue) +{ + return NS_PluginGetValue(aVariable, aValue); +} + + +#if defined(XP_WIN) || defined(XP_MACOS) + +NPError OSCALL NP_Initialize(NPNetscapeFuncs* aNPNFuncs) +{ + sBrowserFunctions = aNPNFuncs; + + return NS_PluginInitialize(); +} + +NPError OSCALL NP_GetEntryPoints(NPPluginFuncs* aNPPFuncs) +{ + return fillPluginFunctionTable(aNPPFuncs); +} + + +#elif defined(XP_UNIX) + +NPError OSCALL NP_Initialize(NPNetscapeFuncs* aNPNFuncs, NPPluginFuncs* aNPPFuncs) +{ + NPError rv; + sBrowserFunctions = aNPNFuncs; + rv = fillPluginFunctionTable(aNPPFuncs); + if(rv != NPERR_NO_ERROR) + return rv; + + return NS_PluginInitialize(); +} +#endif + + +#ifdef GECKO_XPCOM + + +#include +#include +#include +#include +#include + +#include "nsIOsmozilla.h" + +#include "osmozilla.h" + + +nsIServiceManager *gServiceManager = NULL; + + +// We must implement nsIClassInfo because it signals the +// Mozilla Security Manager to allow calls from JavaScript. +// helper class to implement all necessary nsIClassInfo method stubs +// and to set flags used by the security system + +class nsClassInfoMixin : public nsIClassInfo +{ + // These flags are used by the DOM and security systems to signal that + // JavaScript callers are allowed to call this object's scritable methods. + NS_IMETHOD GetFlags(PRUint32 *aFlags) + { *aFlags = nsIClassInfo::PLUGIN_OBJECT | nsIClassInfo::DOM_OBJECT; + return NS_OK; + } + + NS_IMETHOD GetImplementationLanguage(PRUint32 *aImplementationLanguage) + { *aImplementationLanguage = nsIProgrammingLanguage::CPLUSPLUS; + return NS_OK; + } + + // The rest of the methods can safely return error codes... + NS_IMETHOD GetInterfaces(PRUint32 *count, nsIID ***array) + { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetHelperForLanguage(PRUint32 language, nsISupports **_retval) + { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetContractID(char **aContractID) + { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetClassDescription(char **aClassDescription) + { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetClassID(nsCID **aClassID) + { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetClassIDNoAlloc(nsCID *aClassIDNoAlloc) + { + return NS_ERROR_NOT_IMPLEMENTED; + } +}; + + +class nsOsmozillaPeer : public nsIOsmozilla , public nsClassInfoMixin +{ +public: + nsOsmozillaPeer(Osmozilla *osmo); + virtual ~nsOsmozillaPeer(); + + // methods from nsISupports + NS_IMETHOD QueryInterface(const nsIID & aIID, void **aInstancePtr); + NS_IMETHOD_(nsrefcnt) AddRef(); + NS_IMETHOD_(nsrefcnt) Release(); + +public: + NS_DECL_NSIOSMOZILLA + void SetInstance(Osmozilla *osmo); + +protected: + nsrefcnt mRefCnt; + Osmozilla *mPlugin; +}; + + +static NS_DEFINE_IID(kIZillaPluginIID, NS_IOSMOZILLA_IID); +static NS_DEFINE_IID(kIClassInfoIID, NS_ICLASSINFO_IID); +static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); + +nsOsmozillaPeer::nsOsmozillaPeer(Osmozilla *osmo) +{ + mPlugin=osmo; + mRefCnt = 0; +} + +nsOsmozillaPeer::~nsOsmozillaPeer() +{ +} +// Notice that we expose our claim to implement nsIClassInfo. +//NS_IMPL_ISUPPORTS2(nsOsmozillaPeer, nsITestPlugin, nsIClassInfo) + +// the following method will be callable from JavaScript +NS_IMETHODIMP nsOsmozillaPeer::Pause() { + Osmozilla_Pause(mPlugin); + return NS_OK; +} +NS_IMETHODIMP nsOsmozillaPeer::Play() { + Osmozilla_Play(mPlugin); + return NS_OK; +} +NS_IMETHODIMP nsOsmozillaPeer::Stop() { + Osmozilla_Stop(mPlugin); + return NS_OK; +} + +NS_IMETHODIMP nsOsmozillaPeer::Update(const char *type, const char *commands) +{ + Osmozilla_Update(mPlugin, type, commands); + return NS_OK; +} + +NS_IMETHODIMP nsOsmozillaPeer::QualitySwitch(int switch_up) +{ + Osmozilla_QualitySwitch(mPlugin, switch_up); + return NS_OK; +} + +NS_IMETHODIMP nsOsmozillaPeer::SetURL(const char *url) +{ + Osmozilla_SetURL(mPlugin, url); + return NS_OK; +} + +void nsOsmozillaPeer::SetInstance(Osmozilla *osmo) +{ + mPlugin = osmo; +} + +NS_IMETHODIMP_(nsrefcnt) nsOsmozillaPeer::AddRef() +{ + ++mRefCnt; + return mRefCnt; +} + +NS_IMETHODIMP_(nsrefcnt) nsOsmozillaPeer::Release() +{ + --mRefCnt; + if (mRefCnt == 0) { + delete this; + return 0; + } + return mRefCnt; +} + +// here nsOsmozillaPeer should return three interfaces it can be asked for by their iid's +// static casts are necessary to ensure that correct pointer is returned +NS_IMETHODIMP nsOsmozillaPeer::QueryInterface(const nsIID & aIID, + void **aInstancePtr) +{ + if (!aInstancePtr) + return NS_ERROR_NULL_POINTER; + + if (aIID.Equals(kIZillaPluginIID)) { + *aInstancePtr = NS_STATIC_CAST(nsIOsmozilla *, this); + AddRef(); + return NS_OK; + } + + if (aIID.Equals(kIClassInfoIID)) { + *aInstancePtr = NS_STATIC_CAST(nsIClassInfo *, this); + AddRef(); + return NS_OK; + } + + if (aIID.Equals(kISupportsIID)) { + *aInstancePtr = NS_STATIC_CAST(nsISupports *, (NS_STATIC_CAST (nsIOsmozilla *, this))); + AddRef(); + return NS_OK; + } + return NS_NOINTERFACE; +} + +extern NPNetscapeFuncs *sBrowserFunctions; + +void NPOsmozilla_GetServiceManager() +{ + // this is probably a good place to get the service manager + // note that Mozilla will add reference, so do not forget to release + nsISupports *sm = NULL; + + if (!sBrowserFunctions) return; + sBrowserFunctions->getvalue(NULL, NPNVserviceManager, &sm); + + // Mozilla returns nsIServiceManager so we can use it directly; doing QI on + // nsISupports here can still be more appropriate in case something is changed + // in the future so we don't need to do casting of any sort. + if (sm) { + sm->QueryInterface(NS_GET_IID(nsIServiceManager), (void **) &gServiceManager); + NS_RELEASE(sm); + } +} + +void NPOsmozilla_ReleaseServiceManager() +{ +#ifdef GECKO_XPCOM + // we should release the service manager + NS_IF_RELEASE(gServiceManager); + gServiceManager = NULL; +#endif +} + +void NPOsmozilla_ShutdownScript(Osmozilla *osmo) +{ + nsOsmozillaPeer *peer = (nsOsmozillaPeer *) osmo->scriptable_peer; + if (peer != NULL) { + peer->SetInstance(NULL); + NS_IF_RELEASE(peer); + } +} + + +NPError NPOsmozilla_GetPeer(Osmozilla *osmo, void *value) +{ + if (!osmo->scriptable_peer) { + osmo->scriptable_peer = new nsOsmozillaPeer(osmo); + if (!osmo->scriptable_peer) return NPERR_OUT_OF_MEMORY_ERROR; + NS_ADDREF( (nsOsmozillaPeer *) osmo->scriptable_peer); + } + + NS_ADDREF( (nsOsmozillaPeer *)osmo->scriptable_peer); + *(nsISupports **) value = (nsISupports *) osmo->scriptable_peer; + return NPERR_NO_ERROR; +} + +NPError NPOsmozilla_GetPeerIID(Osmozilla *osmo, void *value) +{ + static nsIID scriptableIID = NS_IOSMOZILLA_IID; + if (!sBrowserFunctions) return NPERR_OUT_OF_MEMORY_ERROR; + + nsIID *ptr = (nsIID *) sBrowserFunctions->memalloc( sizeof(nsIID) ); + if (! ptr) return NPERR_OUT_OF_MEMORY_ERROR; + + *ptr = scriptableIID; + *(nsIID **) value = ptr; + return NPERR_NO_ERROR; +} + +#else + +enum +{ + kOSMOZILLA_ID_METHOD_PLAY = 0, + kOSMOZILLA_ID_METHOD_PAUSE, + kOSMOZILLA_ID_METHOD_STOP, + kOSMOZILLA_ID_METHOD_UPDATE, + kOSMOZILLA_ID_METHOD_QUALITY_SWITCH, + kOSMOZILLA_ID_METHOD_SET_URL, + + kOSMOZILLA_NUM_METHODS +}; + +NPIdentifier v_OSMOZILLA_MethodIdentifiers[kOSMOZILLA_NUM_METHODS]; +const NPUTF8 * v_OSMOZILLA_MethodNames[kOSMOZILLA_NUM_METHODS] = { + "Play", + "Pause", + "Stop", + "Update", + "QualitySwitch", + "SetURL", +}; + +NPClass osmozilla_script_class; + +typedef struct { + NPClass *_class; + uint32_t referenceCount; + Osmozilla *osmo; +} OsmozillaObject; + +NPObject *OSMOZILLA_Allocate(NPP npp, NPClass *theClass) +{ + OsmozillaObject *obj = NULL; + + sBrowserFunctions->getstringidentifiers(v_OSMOZILLA_MethodNames, kOSMOZILLA_NUM_METHODS, v_OSMOZILLA_MethodIdentifiers); + obj = (OsmozillaObject *)malloc(sizeof(OsmozillaObject)); + obj->osmo = (Osmozilla *) npp->pdata; + return (NPObject *)obj; +} + +void OSMOZILLA_Deallocate(NPObject* obj) +{ + free(obj); + return; +} + +void OSMOZILLA_Invalidate(NPObject* obj) +{ + return; +} + +bool OSMOZILLA_HasMethod(NPObject* obj, NPIdentifier name) +{ + int i = 0; + while (i < kOSMOZILLA_NUM_METHODS) { + if ( name == v_OSMOZILLA_MethodIdentifiers[i] ) { + return 1; + } + i++; + } + return 0; +} + +bool OSMOZILLA_Invoke(NPObject* obj, NPIdentifier name, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + OsmozillaObject *npo = (OsmozillaObject *)obj; + if (!npo->osmo) return 0; + if (name == v_OSMOZILLA_MethodIdentifiers[kOSMOZILLA_ID_METHOD_PLAY]) { + Osmozilla_Play(npo->osmo); + return 1; + } + if (name == v_OSMOZILLA_MethodIdentifiers[kOSMOZILLA_ID_METHOD_PAUSE]) { + Osmozilla_Pause(npo->osmo); + return 1; + } + if (name == v_OSMOZILLA_MethodIdentifiers[kOSMOZILLA_ID_METHOD_STOP]) { + Osmozilla_Stop(npo->osmo); + return 1; + } + if (name == v_OSMOZILLA_MethodIdentifiers[kOSMOZILLA_ID_METHOD_UPDATE]) { + const char *mime = NULL; + const char *update = NULL; + if (argCount==2) { + mime = (args[0].type==NPVariantType_String) ? args[0].value.stringValue.UTF8Characters : NULL; + update = (args[1].type==NPVariantType_String) ? args[1].value.stringValue.UTF8Characters : NULL; + } + if (!update) return 0; + Osmozilla_Update(npo->osmo, mime, update); + return 1; + } + if (name == v_OSMOZILLA_MethodIdentifiers[kOSMOZILLA_ID_METHOD_QUALITY_SWITCH]) { + int up = 1; + if (argCount==1) { + if (args[0].type==NPVariantType_Bool) up = args[0].value.boolValue ? 1 : 0; + else if (args[0].type==NPVariantType_Int32) up = args[0].value.intValue ? 1 : 0; + } + Osmozilla_QualitySwitch(npo->osmo, up); + return 1; + } + if (name == v_OSMOZILLA_MethodIdentifiers[kOSMOZILLA_ID_METHOD_SET_URL]) { + const char *url = ""; + if (argCount>=1) { + if (args[0].type==NPVariantType_String) + url = args[0].value.stringValue.UTF8Characters; + } + Osmozilla_SetURL(npo->osmo, url); + return 1; + } + return 0; +} + +bool OSMOZILLA_InvokeDefault(NPObject *npobj, const NPVariant *args, uint32_t argCount, NPVariant *result) +{ + return 1; +} + +bool OSMOZILLA_HasProperty(NPObject* obj, NPIdentifier name) +{ + bool result = 0; + if ( sBrowserFunctions->identifierisstring(name) ) + { + NPUTF8 *val = sBrowserFunctions->utf8fromidentifier(name); + + if ( !strcmp(val, "DownloadProgress") ) + { + result = 1; + } + + sBrowserFunctions->memfree(val); + } + /*nothing exposed yet*/ + return result; +} + +bool OSMOZILLA_GetProperty(NPObject* obj, NPIdentifier name, NPVariant* result) +{ + OsmozillaObject *npo = (OsmozillaObject *)obj; + if (!npo->osmo) return 0; + if ( sBrowserFunctions->identifierisstring(name) ) + { + NPUTF8 *val = sBrowserFunctions->utf8fromidentifier(name); + + if ( !strcmp(val, "DownloadProgress") ) + { + int val = Osmozilla_GetDownloadProgress(npo->osmo); + INT32_TO_NPVARIANT(val, *result); + } + + sBrowserFunctions->memfree(val); + } + return 1; +} + +bool OSMOZILLA_SetProperty(NPObject *obj, NPIdentifier name, const NPVariant *value) +{ + return 1; +} + +bool OSMOZILLA_RemoveProperty(NPObject *npobj, NPIdentifier name) +{ + return 1; +} + +bool OSMOZILLA_Enumerate(NPObject *npobj, NPIdentifier **value, uint32_t *count) +{ + return 1; +} + +void Osmozilla_InitScripting(Osmozilla *osmo) +{ + osmozilla_script_class.allocate = OSMOZILLA_Allocate; + osmozilla_script_class.deallocate = OSMOZILLA_Deallocate; + osmozilla_script_class.invalidate = OSMOZILLA_Invalidate; + osmozilla_script_class.hasMethod = OSMOZILLA_HasMethod; + osmozilla_script_class.invoke = OSMOZILLA_Invoke; + osmozilla_script_class.invokeDefault = OSMOZILLA_InvokeDefault; + osmozilla_script_class.hasProperty = OSMOZILLA_HasProperty; + osmozilla_script_class.getProperty = OSMOZILLA_GetProperty; + osmozilla_script_class.setProperty = OSMOZILLA_SetProperty; + osmozilla_script_class.removeProperty = OSMOZILLA_RemoveProperty; + osmozilla_script_class.enumerate = OSMOZILLA_Enumerate; + + /*create script object*/ + osmo->script_obj = sBrowserFunctions->createobject(osmo->np_instance, &osmozilla_script_class); + +} + +#endif //GECKO_XPCOM + diff --git a/applications/deprecated/old_arch/osmozilla/osmo_npapi.h b/applications/deprecated/old_arch/osmozilla/osmo_npapi.h new file mode 100644 index 0000000..a7a83ae --- /dev/null +++ b/applications/deprecated/old_arch/osmozilla/osmo_npapi.h @@ -0,0 +1,136 @@ +/* +* GPAC - Multimedia Framework C SDK +* + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 +* All rights reserved +* +* This file is part of GPAC / Osmozilla NPAPI plugin +* +* GPAC is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* GPAC is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; see the file COPYING. If not, write to +* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +* +*/ + +#ifndef _OSMO_NPAPI_H_ +#define _OSMO_NPAPI_H_ + + +#ifdef WIN32 +#include +#ifndef __cplusplus +typedef BOOL bool; +#endif //__cplusplus +#endif + +#include "npapi.h" + +/*check this with gecko 1.9.2*/ +#if (NP_VERSION_MINOR < 20) +#define GECKO_XPCOM +#endif + +#ifdef GECKO_XPCOM +#include "npupp.h" + +#ifndef uint16_t +typedef uint16 uint16_t; +#endif + +#ifndef int16_t +typedef int16 int16_t; +#endif + +#define NPINT32 int32 + +#else + +#include "npfunctions.h" + +#define NPINT32 int32_t + +#endif + +#ifdef XP_UNIX +#include +#endif //XP_UNIX + + +#ifndef HIBYTE +#define HIBYTE(i) (i >> 8) +#endif + +#ifndef LOBYTE +#define LOBYTE(i) (i & 0xff) +#endif + +/*functions callbacks to browser*/ +NPError Osmozilla_GetURL(NPP instance, const char *url, const char *target); +void Osmozilla_SetStatus(NPP instance, const char *message); + + +/* +Plugins functions exposed to browser +*/ +NPError NPOsmozilla_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved); +NPError NPOsmozilla_Destroy(NPP instance, NPSavedData** save); +NPError NPOsmozilla_SetWindow(NPP instance, NPWindow* window); +NPError NPOsmozilla_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16_t* stype); +NPError NPOsmozilla_DestroyStream(NPP instance, NPStream* stream, NPError reason); +NPINT32 NPOsmozilla_WriteReady(NPP instance, NPStream* stream); +NPINT32 NPOsmozilla_Write(NPP instance, NPStream* stream, NPINT32 offset, NPINT32 len, void* buffer); +void NPOsmozilla_StreamAsFile(NPP instance, NPStream* stream, const char* fname); +void NPOsmozilla_Print(NPP instance, NPPrint* platformPrint); +int16_t NPOsmozilla_HandleEvent(NPP instance, void* event); +void NPOsmozilla_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData); +NPError NPOsmozilla_GetValue(NPP instance, NPPVariable variable, void *result); +NPError NPOsmozilla_SetValue(NPP instance, NPNVariable variable, void *value); + + +/* +Functions called by browser +*/ + +typedef struct __tag_osmozilla Osmozilla; + +/*base functions*/ +int Osmozilla_Initialize(Osmozilla *osmo, signed short argc, char* argn[], char* argv[]); +void Osmozilla_Shutdown(Osmozilla *osmo); +int Osmozilla_SetWindow(Osmozilla *osmozilla, void *os_wnd_handle, void *os_wnd_display, unsigned int width, unsigned int height); +void Osmozilla_ConnectTo(Osmozilla *osmozilla, const char *url); +void Osmozilla_Print(Osmozilla *osmozilla, unsigned int is_embed, void *os_print_dc, unsigned int target_x, unsigned int target_y, unsigned int target_width, unsigned int target_height); +char *Osmozilla_GetVersion(); + + +/*scripting functions*/ +void Osmozilla_Play(Osmozilla *osmo); +void Osmozilla_Pause(Osmozilla *osmo); +void Osmozilla_Stop(Osmozilla *osmo); +void Osmozilla_Update(Osmozilla *osmo, const char *type, const char *commands); +void Osmozilla_QualitySwitch(Osmozilla *osmo, int switch_up); +void Osmozilla_SetURL(Osmozilla *osmo, const char *url); +int Osmozilla_GetDownloadProgress(Osmozilla *osmo); + + +#ifdef GECKO_XPCOM + +void NPOsmozilla_GetServiceManager(); +void NPOsmozilla_ReleaseServiceManager(); +void NPOsmozilla_ShutdownScript(Osmozilla *osmo); +NPError NPOsmozilla_GetPeer(Osmozilla *osmo, void *value); +NPError NPOsmozilla_GetPeerIID(Osmozilla *osmo, void *value); + +#endif //GECKO_XPCOM + +#endif //_NPPLAT_H_ diff --git a/applications/deprecated/old_arch/osmozilla/osmozilla.cpp b/applications/deprecated/old_arch/osmozilla/osmozilla.cpp new file mode 100644 index 0000000..2f0b26e --- /dev/null +++ b/applications/deprecated/old_arch/osmozilla/osmozilla.cpp @@ -0,0 +1,531 @@ +/* +* GPAC - Multimedia Framework C SDK +* + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 +* All rights reserved +* +* This file is part of GPAC / Osmozilla NPAPI plugin +* +* GPAC is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* GPAC is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; see the file COPYING. If not, write to +* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +* +*/ + +#include "osmozilla.h" + +#ifdef XP_WIN +#include +#endif + +#include +#include +#include + + +short Osmozilla_GetURL(NPP instance, const char *url, const char *target); +void Osmozilla_SetStatus(NPP instance, const char *message); + + +void Osmozilla_Shutdown(Osmozilla *osmo) +{ + if (osmo->url) gf_free(osmo->url); + osmo->url = NULL; + if (osmo->term) { + GF_Terminal *a_term = osmo->term; + osmo->term = NULL; + gf_term_del(a_term); + } + if (osmo->user) { + if (osmo->user->modules) gf_modules_del(osmo->user->modules); + if (osmo->user->config) gf_cfg_del(osmo->user->config); + gf_free(osmo->user); + osmo->user = NULL; + } +} + +static void osmozilla_do_log(void *cbk, GF_LOG_Level level, GF_LOG_Tool tool, const char *fmt, va_list list) +{ + FILE *logs = (FILE *) cbk; + vfprintf(logs, fmt, list); + fflush(logs); +} + + +Bool Osmozilla_EventProc(void *opaque, GF_Event *evt) +{ + char msg[1024]; + Osmozilla *osmo = (Osmozilla *)opaque; + if (!osmo->term) return GF_FALSE; + + switch (evt->type) { + case GF_EVENT_MESSAGE: + if (!evt->message.message) return GF_FALSE; + if (evt->message.error) + sprintf((char *)msg, "GPAC: %s (%s)", evt->message.message, gf_error_to_string(evt->message.error)); + else + sprintf((char *)msg, "GPAC: %s", evt->message.message); + + Osmozilla_SetStatus(osmo->np_instance, msg); + break; + case GF_EVENT_PROGRESS: + if (evt->progress.done == evt->progress.total) { + Osmozilla_SetStatus(osmo->np_instance, ""); + osmo->download_progress = 100; + } else { + char *szTitle = (char *)""; + if (evt->progress.progress_type==0) szTitle = (char *)"Buffer "; + else if (evt->progress.progress_type==1) + { + szTitle = (char *)"Download "; + osmo->download_progress = (int) (100.0*evt->progress.done) / evt->progress.total; + } + else if (evt->progress.progress_type==2) szTitle = (char *)"Import "; + + sprintf(msg, "(GPAC) %s: %02.2f", szTitle, (100.0*evt->progress.done) / evt->progress.total); + Osmozilla_SetStatus(osmo->np_instance, msg); + } + break; + + /*IGNORE any scene size, just work with the size allocated in the parent doc*/ + case GF_EVENT_SCENE_SIZE: + gf_term_set_size(osmo->term, osmo->width, osmo->height); + break; + /*window has been resized (full-screen plugin), resize*/ + case GF_EVENT_SIZE: + osmo->width = evt->size.width; + osmo->height = evt->size.height; + gf_term_set_size(osmo->term, osmo->width, osmo->height); + break; + case GF_EVENT_CONNECT: + osmo->is_connected = evt->connect.is_connected; + break; + case GF_EVENT_DURATION: + osmo->can_seek = evt->duration.can_seek; + osmo->duration = evt->duration.duration; + break; + case GF_EVENT_DBLCLICK: + gf_term_set_option(osmo->term, GF_OPT_FULLSCREEN, !gf_term_get_option(osmo->term, GF_OPT_FULLSCREEN)); + break; + case GF_EVENT_NAVIGATE_INFO: + strcpy(msg, evt->navigate.to_url); + Osmozilla_SetStatus(osmo->np_instance, msg); + break; + case GF_EVENT_NAVIGATE: + if (gf_term_is_supported_url(osmo->term, evt->navigate.to_url, GF_TRUE, osmo->disable_mime ? GF_TRUE : GF_FALSE)) { + gf_term_navigate_to(osmo->term, evt->navigate.to_url); + return GF_TRUE; + } else { + u32 i; + char *target = (char *)"_self"; + + for (i=0; inavigate.param_count; i++) { + if (!strcmp(evt->navigate.parameters[i], "_parent")) target = (char *)"_parent"; + else if (!strcmp(evt->navigate.parameters[i], "_blank")) target = (char *)"_blank"; + else if (!strcmp(evt->navigate.parameters[i], "_top")) target = (char *)"_top"; + else if (!strcmp(evt->navigate.parameters[i], "_new")) target = (char *)"_new"; + else if (!strnicmp(evt->navigate.parameters[i], "_target=", 8)) target = (char *) evt->navigate.parameters[i]+8; + } + Osmozilla_GetURL(osmo->np_instance, evt->navigate.to_url, target); + return GF_TRUE; + } + break; + } + return GF_FALSE; +} + +int Osmozilla_Initialize(Osmozilla *osmo, signed short argc, char* argn[], char* argv[]) +{ + const char *str; + int i; + osmo->auto_start = 1; + osmo->use_gui = 0; + + /*options sent from plugin*/ + for(i=0; iauto_start = 0; + + else if (!stricmp(argn[i],"src") ) { + if (osmo->url) gf_free(osmo->url); + osmo->url = gf_strdup(argv[i]); + } + else if (!stricmp(argn[i],"use3d") && (!stricmp(argv[i], "true") || !stricmp(argv[i], "yes") ) ) { + osmo->use_3d = 1; + } + else if (!stricmp(argn[i],"loop") && (!stricmp(argv[i], "true") || !stricmp(argv[i], "yes") ) ) { + osmo->loop = 1; + } + else if (!stricmp(argn[i],"aspectratio")) { + osmo->aspect_ratio = GF_ASPECT_RATIO_KEEP; + if (!stricmp(argv[i], "keep")) osmo->aspect_ratio = GF_ASPECT_RATIO_KEEP; + else if (!stricmp(argv[i], "16:9")) osmo->aspect_ratio = GF_ASPECT_RATIO_16_9; + else if (!stricmp(argv[i], "4:3")) osmo->aspect_ratio = GF_ASPECT_RATIO_4_3; + else if (!stricmp(argv[i], "fill")) osmo->aspect_ratio = GF_ASPECT_RATIO_FILL_SCREEN; + } + else if (!stricmp(argn[i],"gui") && (!stricmp(argv[i], "true") || !stricmp(argv[i], "yes") ) ) + osmo->use_gui = 1; + } + + /*URL is not absolute, request new stream to mozilla - we don't pass absolute URLs since some may not be + handled by gecko */ + if (osmo->url) { + Bool absolute_url = GF_FALSE; + if (strstr(osmo->url, "://")) absolute_url = GF_TRUE; + else if (osmo->url[0] == '/') { + FILE *test = gf_fopen(osmo->url, "rb"); + if (test) { + absolute_url = GF_TRUE; + gf_fclose(test); + } + } + else if ((osmo->url[1] == ':') && ((osmo->url[2] == '\\') || (osmo->url[2] == '/'))) absolute_url = GF_TRUE; + + if (!absolute_url) { + char *url = osmo->url; + osmo->url = NULL; + Osmozilla_GetURL(osmo->np_instance, url, NULL); + gf_free(url); + } + } + + GF_SAFEALLOC(osmo->user, GF_User); + osmo->user->config = gf_cfg_init(NULL, NULL); + /*need to have a valid cfg file for now*/ + if (!osmo->user->config) { + gf_free(osmo->user); + osmo->user = NULL; +#ifdef WIN32 + MessageBox(NULL, "GPAC CONFIGURATION FILE NOT FOUND OR INVALID", "OSMOZILLA FATAL ERROR", MB_OK); +#else + fprintf(stdout, "OSMOZILLA FATAL ERROR\nGPAC CONFIGURATION FILE NOT FOUND OR INVALID\n"); +#endif + return 0; + } + + osmo->user->modules = gf_modules_new(NULL, osmo->user->config); + if (!gf_modules_get_count(osmo->user->modules)) { + if (osmo->user->modules) gf_modules_del(osmo->user->modules); + gf_free(osmo->user); + osmo->user = NULL; +#ifdef WIN32 + MessageBox(NULL, "GPAC MODULES NOT FOUND", "OSMOZILLA FATAL ERROR", MB_OK); +#else + fprintf(stdout, "OSMOZILLA FATAL ERROR\nGPAC MODULES NOT FOUND\n"); +#endif + return 0; + } + + osmo->user->opaque = osmo; + osmo->user->EventProc = Osmozilla_EventProc; + + /*always fetch mime ? Check with anchor examples*/ + osmo->disable_mime = 0; + str = gf_cfg_get_key(osmo->user->config, "General", "NoMIMETypeFetch"); + if (str && !strcmp(str, "yes")) osmo->disable_mime = 0; + /*check log file*/ + str = gf_cfg_get_key(osmo->user->config, "General", "LogFile"); + if (str) { + osmo->logs = gf_fopen(str, "wt"); + if (osmo->logs) gf_log_set_callback(osmo->logs, osmozilla_do_log); + } + + /*setup logs*/ + if (gf_log_set_tools_levels(gf_cfg_get_key(osmo->user->config, "General", "Logs")) != GF_OK) + fprintf(stdout, "Osmozilla: invalid log level specified\n"); + + fprintf(stdout, "Osmozilla initialized\n"); + return 1; +} + +int Osmozilla_SetWindow(Osmozilla *osmo, void *os_wnd_handle, void *os_wnd_display, unsigned int width, unsigned int height) +{ + const char *gui; + + if (!osmo->user) return 0; + + if (osmo->window_set) { + osmo->width = width; + osmo->height = height; + if (osmo->is_connected) gf_term_set_size(osmo->term, osmo->width, osmo->height); + return 1; + } + if (!os_wnd_handle) return 0; + + osmo->width = width; + osmo->height = height; + + osmo->user->os_window_handler = os_wnd_handle; + osmo->user->os_display = os_wnd_display; + + /*Everything is now setup, create the terminal*/ + fprintf(stdout, "Creating Osmozilla terminal\n"); + osmo->term = gf_term_new(osmo->user); + if (!osmo->term) return 0; + fprintf(stdout, "Osmozilla terminal created\n"); + + gf_term_set_option(osmo->term, GF_OPT_ASPECT_RATIO, osmo->aspect_ratio); + osmo->window_set = 1; + +#ifdef XP_WIN + SetFocus((HWND)os_wnd_handle); +#endif + + /*stream not ready*/ + if (!osmo->url || !osmo->auto_start) { + fprintf(stdout, "Osmozilla ready - not connecting to %s yet\n", osmo->url); + return 1; + } + + /*connect from 0 and pause if not autoplay*/ + gui = gf_cfg_get_key(osmo->user->config, "General", "StartupFile"); + if (gui && osmo->use_gui) { + gf_cfg_set_key(osmo->user->config, "Temp", "BrowserMode", "yes"); + gf_cfg_set_key(osmo->user->config, "Temp", "GUIStartupFile", osmo->url); + gf_term_connect(osmo->term, gui); + } else { + gf_term_connect(osmo->term, osmo->url); + } + fprintf(stdout, "Osmozilla connected to %s\n", osmo->url); + return 1; +} + +char *Osmozilla_GetVersion() +{ + return (char *) "GPAC Plugin " GPAC_FULL_VERSION " for NPAPI compatible Web Browsers. For more information go to GPAC website"; +} + +void Osmozilla_ConnectTo(Osmozilla *osmo, const char *url) +{ + if (!osmo->user) return; + + if ( osmo->url && !strcmp(url, osmo->url)) + return; + + fprintf(stdout, "Osmozilla connecting to %s\n", url); + + if (osmo->url) gf_free(osmo->url); + osmo->url = gf_strdup(url); + + /*connect from 0 and pause if not autoplay*/ + if (osmo->auto_start) { + const char *gui = gf_cfg_get_key(osmo->user->config, "General", "StartupFile"); + if (gui && osmo->use_gui) { + gf_cfg_set_key(osmo->user->config, "Temp", "BrowserMode", "yes"); + gf_cfg_set_key(osmo->user->config, "Temp", "GUIStartupFile", url); + gf_term_connect(osmo->term, gui); + } else { + gf_term_connect(osmo->term, url); + } + } + fprintf(stdout, "Osmozilla connected to %s\n", url); +} + +void Osmozilla_Pause(Osmozilla *osmo) +{ + if (osmo->term) { + if (gf_term_get_option(osmo->term, GF_OPT_PLAY_STATE) == GF_STATE_PAUSED) { + gf_term_set_option(osmo->term, GF_OPT_PLAY_STATE, GF_STATE_PLAYING); + } else { + gf_term_set_option(osmo->term, GF_OPT_PLAY_STATE, GF_STATE_PAUSED); + } + } +} + +void Osmozilla_Play(Osmozilla *osmo) +{ + if (!osmo->is_connected) { + if (osmo->url) gf_term_connect(osmo->term, (const char *) osmo->url); + } else { + gf_term_set_option(osmo->term, GF_OPT_PLAY_STATE, GF_STATE_PLAYING); + } +} + +void Osmozilla_Stop(Osmozilla *osmo) +{ + if (osmo->term) gf_term_disconnect(osmo->term); +} + +#ifdef XP_WIN +PBITMAPINFO CreateBitmapInfoStruct(GF_VideoSurface *pfb) +{ + PBITMAPINFO pbmi; + WORD cClrBits; + + cClrBits = 32; + + pbmi = (PBITMAPINFO) LocalAlloc(LPTR, + sizeof(BITMAPINFOHEADER)); + + pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + pbmi->bmiHeader.biWidth = pfb->width; + pbmi->bmiHeader.biHeight = 1; + pbmi->bmiHeader.biPlanes = 1; + pbmi->bmiHeader.biBitCount = cClrBits; + + pbmi->bmiHeader.biCompression = BI_RGB; + pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits +31) & ~31) /8 + * pbmi->bmiHeader.biHeight; + pbmi->bmiHeader.biClrImportant = 0; + return pbmi; +} +#endif + +void Osmozilla_Print(Osmozilla *osmo, unsigned int is_embed, void *os_print_dc, unsigned int target_x, unsigned int target_y, unsigned int target_width, unsigned int target_height) +{ + if (is_embed) { +#ifdef XP_MACOS + /* + os_print_dc contains a THPrint reference on MacOS + */ + } +#endif // XP_MACOS +#ifdef XP_UNIX + /* + os_print_dc contains a NPPrintCallbackStruct on Unix and + the plug-in location and size in the NPWindow are in page coordinates (720/ inch), but the printer requires point coordinates (72/inch) + */ +#endif // XP_UNIX +#ifdef XP_WIN + /* + The coordinates for the window rectangle are in TWIPS format. + This means that you need to convert the x-y coordinates using the Windows API call DPtoLP when you output text + */ + GF_VideoSurface fb; + u32 xsrc, ysrc; + u16 src_16; + char *src; + float deltay; + int ysuiv = 0; + char *ligne; + BITMAPINFO *infoSrc; + HDC pDC = (HDC)os_print_dc; + /*lock the source buffer */ + gf_term_get_screen_buffer(osmo->term, &fb); + infoSrc = CreateBitmapInfoStruct(&fb); + deltay = (float)target_height/(float)fb.height; + ligne = (char *) LocalAlloc(GMEM_FIXED, fb.width*4); + for (ysrc=0; ysrc> 8) & 0xf8; + dst[2] += dst[2]>>5; + dst[1] = (src_16 >> 3) & 0xfc; + dst[1] += dst[1]>>6; + dst[0] = (src_16 << 3) & 0xf8; + dst[0] += dst[0]>>5; + src+=2; + break; + case GF_PIXEL_RGB_555: + src_16 = * (u16 *)src; + dst[2] = (src_16 >> 7) & 0xf8; + dst[2] += dst[2]>>5; + dst[1] = (src_16 >> 2) & 0xf8; + dst[1] += dst[1]>>5; + dst[0] = (src_16 << 3) & 0xf8; + dst[0] += dst[0]>>5; + src+=2; + break; + } + dst += 4; + } + ycrt = ysuiv; + ysuiv = (u32) ( ((float)ysrc+1.0)*deltay); + delta = ysuiv-ycrt; + StretchDIBits( + pDC, target_x, target_y, target_width, + delta, + 0, 0, fb.width, 1, + ligne, infoSrc, DIB_RGB_COLORS, SRCCOPY); + } + + /*unlock GPAC frame buffer */ + gf_term_release_screen_buffer(osmo->term, &fb); + /* gf_free temporary objects */ + GlobalFree(ligne); + LocalFree(infoSrc); +#endif // XP_WIN + + return; +} + +/*TODO - this is full print, present the print dialog and manage the print*/ +} + +void Osmozilla_Update(Osmozilla *osmo, const char *type, const char *commands) +{ + if (osmo->term) { + GF_Err e = gf_term_scene_update(osmo->term, (char *) type, (char *) commands); + if (e) { + char szMsg[1024]; + sprintf((char *)szMsg, "GPAC: Error applying update (%s)", gf_error_to_string(e) ); + Osmozilla_SetStatus(osmo->np_instance, szMsg); + } + } +} + +void Osmozilla_QualitySwitch(Osmozilla *osmo, int switch_up) +{ + if (osmo->term) + gf_term_switch_quality(osmo->term, switch_up ? GF_TRUE : GF_FALSE); +} + +void Osmozilla_SetURL(Osmozilla *osmo, const char *url) +{ + if (osmo->term) { + if (osmo->url) gf_free(osmo->url); + osmo->url = gf_strdup(url); + gf_term_connect(osmo->term, osmo->url); + } +} + +int Osmozilla_GetDownloadProgress(Osmozilla *osmo) +{ + if (osmo->term) + return osmo->download_progress; + return 0; +} diff --git a/applications/deprecated/old_arch/osmozilla/osmozilla.def b/applications/deprecated/old_arch/osmozilla/osmozilla.def new file mode 100644 index 0000000..5d5bbb1 --- /dev/null +++ b/applications/deprecated/old_arch/osmozilla/osmozilla.def @@ -0,0 +1,6 @@ +LIBRARY nposmozilla + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 diff --git a/applications/deprecated/old_arch/osmozilla/osmozilla.h b/applications/deprecated/old_arch/osmozilla/osmozilla.h new file mode 100644 index 0000000..9556ac4 --- /dev/null +++ b/applications/deprecated/old_arch/osmozilla/osmozilla.h @@ -0,0 +1,75 @@ +/* +* GPAC - Multimedia Framework C SDK +* + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 +* All rights reserved +* +* This file is part of GPAC / Osmozilla NPAPI plugin +* +* GPAC is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* GPAC is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; see the file COPYING. If not, write to +* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +* +*/ + +#ifndef __OSMOZILLA_H__ +#define __OSMOZILLA_H__ + + +/*DO NOT INCLUDE ANY GPAC FILE IN THIS HEADER, IT CAUSES TYPE REDEFINITION CONFLICT ON OSX*/ +typedef struct _tag_terminal GF_Terminal; +typedef struct _tag_user GF_User; + +#include + + +typedef struct _NPP *NPP; + +typedef struct __tag_osmozilla +{ + /*plugiun & window info*/ + NPP np_instance; + int window_set; + unsigned int height, width; + + int supports_xembed; + /*GPAC term*/ + GF_User *user; + GF_Terminal *term; + + /*general options*/ + unsigned int loop, auto_start, is_connected, use_3d, disable_mime; + unsigned int aspect_ratio; + + /*the URL we are connected to*/ + char *url; + /*timing info of current url*/ + double duration; + char can_seek; + int use_gui; + int download_progress; + + /*log file if any*/ + FILE *logs; + +#ifdef GECKO_XPCOM + void *scriptable_peer; +#else + struct NPObject *script_obj; +#endif + +} Osmozilla; + + +#endif // __OSMOZILLA_H__ diff --git a/applications/deprecated/old_arch/osmozilla/osmozilla.png b/applications/deprecated/old_arch/osmozilla/osmozilla.png new file mode 100644 index 0000000000000000000000000000000000000000..6f66a17b1edda041995dfdee6d4baae64b040a71 GIT binary patch literal 121239 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 Gxb=Tf0re~Z literal 0 HcmV?d00001 diff --git a/applications/deprecated/old_arch/osmozilla/osmozilla.rc b/applications/deprecated/old_arch/osmozilla/osmozilla.rc new file mode 100644 index 0000000..39c14a0 --- /dev/null +++ b/applications/deprecated/old_arch/osmozilla/osmozilla.rc @@ -0,0 +1,128 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winresrc.h" + +/*do not include setup.h*/ +#define _GF_SETUP_H_ +#include + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Neutral resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEU) +#ifdef _WIN32 +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +#pragma code_page(1252) +#endif //_WIN32 + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,1,0 + PRODUCTVERSION 1,0,1,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "Comments", "\0" + VALUE "CompanyName", " \0" + VALUE "FileDescription", "Osmozilla allows playback of many media and rich media files. For more information, visit gpac.io\0" + VALUE "FileExtents", "*|aac|wrl,wrl.gz|x3dv,x3dv.gz,x3dvz|x3d,x3d.gz,x3dz|svg,svg.gz,svgz|mpg,mpeg,mp2,mpa,mpe,mpv2|asf,wma,wmv,asx,asr|avi|mp4,mpg4|mp4|m4a|3gp,3gpp|3gp,3gpp|3g2,3gp2|3g2,3gp2|mp2,mp3,mpga,mpega|ogg|sdp\0" + VALUE "FileOpenName", "GPAC Plugin|AAC Music|VRML World|X3D/VRML World|X3D/XML World|SVG Document|MPEG Video|WindowsMedia Movies|AVI Movies|MPEG-4 Videos|MPEG-4 Movies|MPEG-4 Music|3GPP Movies|3GPP Music|3GPP2 Movies|3GPP2 Music|MP3 Music|OGG Movies|SDP Session\0" + VALUE "FileVersion", GPAC_VERSION"-rev"GPAC_GIT_REVISION"\0" + VALUE "InternalName", "nposmozilla\0" + VALUE "LegalCopyright", "Copyright � Telecom ParisTech 2005-2007\0" + VALUE "LegalTrademarks", "\0" + VALUE "MIMEType", "application/x-gpac|audio/aac|model/vrml|model/x3d+vrml|model/x3d+xml|image/svg+xml|video/mpeg|video/x-ms-asf|video/avi|video/mp4|application/mp4|audio/mp4|video/3gpp|audio/3gpp|video/3gpp2|audio/3gpp2|audio/mpeg|application/ogg|application/sdp\0" + VALUE "OriginalFilename", "nposmozilla.dll\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "Osmozilla - GPAC Plugin for Mozilla\0" + VALUE "ProductVersion", GPAC_VERSION"-rev"GPAC_GIT_REVISION"\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // !_MAC + +#endif // Neutral resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""winresrc.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/applications/deprecated/old_arch/osmozilla/osmozilla.vcxproj b/applications/deprecated/old_arch/osmozilla/osmozilla.vcxproj new file mode 100644 index 0000000..9170bba --- /dev/null +++ b/applications/deprecated/old_arch/osmozilla/osmozilla.vcxproj @@ -0,0 +1,304 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {A0288B75-0D95-4106-A3A7-779A891E8FEF} + osmozilla + + + + DynamicLibrary + false + MultiByte + v140 + + + DynamicLibrary + false + MultiByte + v140 + + + DynamicLibrary + false + MultiByte + v140 + + + DynamicLibrary + false + MultiByte + v140 + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + ../../bin/$(Platform)\$(Configuration)/ + ../../bin/$(Platform)\$(Configuration)/ + .\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + .\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + true + true + true + true + ../../bin/$(Platform)\$(Configuration)/ + ../../bin/$(Platform)\$(Configuration)/ + .\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + .\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + false + false + nposmozilla + nposmozilla + nposmozilla + nposmozilla + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\obj/osmozilla_deb/osmozilla.tlb + + + + + Disabled + ..\..\extra_lib\include\xulrunner-sdk\include;..\..\include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;XP_WIN32;XP_WIN;_X86_;_WINDOWS;_USRDLL;NPBASIC_EXPORTS;MOZILLA_STRICT_API;XPCOM_GLUE;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + .\obj/osmozilla_deb/osmozilla.pch + $(IntDir) + $(IntDir) + $(IntDir) + true + Level3 + true + EditAndContinue + Cdecl + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + advapi32.lib;user32.lib;gdi32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName)$(TargetExt) + true + ..\..\applications\osmozilla\osmozilla.def + true + $(IntDir)$(ProjectName).pdb + .\obj/osmozilla_deb/nposmozilla.lib + MachineX86 + + + true + .\obj/osmozilla_deb/osmozilla.bsc + + + copy ..\..\applications\osmozilla\nsIOsmozilla.xpt_w32 ..\..\bin\$(Platform)\$(Configuration)\nposmozilla.xpt + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + .\obj/osmozilla_deb/osmozilla.tlb + + + + + Disabled + ..\..\extra_lib\include\xulrunner-sdk\include;..\..\include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;XP_WIN32;XP_WIN;_X86_;_WINDOWS;_USRDLL;NPBASIC_EXPORTS;MOZILLA_STRICT_API;XPCOM_GLUE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + .\obj/osmozilla_deb/osmozilla.pch + $(IntDir) + $(IntDir) + $(IntDir) + true + Level3 + true + ProgramDatabase + Cdecl + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + advapi32.lib;user32.lib;gdi32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName)$(TargetExt) + true + ..\..\applications\osmozilla\osmozilla.def + true + $(IntDir)$(ProjectName).pdb + .\obj/osmozilla_deb/nposmozilla.lib + + + true + .\obj/osmozilla_deb/osmozilla.bsc + + + copy ..\..\applications\osmozilla\nsIOsmozilla.xpt_w32 ..\..\bin\$(Platform)\$(Configuration)\nposmozilla.xpt + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\obj/osmozilla_rel/osmozilla.tlb + + + + + MaxSpeed + OnlyExplicitInline + ..\..\extra_lib\include\xulrunner-sdk\include;..\..\include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_WINDOWS;_USRDLL;NPBASIC_EXPORTS;MOZILLA_STRICT_API;XP_WIN;_X86_;XPCOM_GLUE;WINVER=0x0400;%(PreprocessorDefinitions) + true + MultiThreadedDLL + true + .\obj/osmozilla_rel/osmozilla.pch + .\obj/osmozilla_rel/ + .\obj/osmozilla_rel/ + .\obj/osmozilla_rel/ + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + advapi32.lib;user32.lib;gdi32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName)$(TargetExt) + true + ..\..\applications\osmozilla\osmozilla.def + .\obj/osmozilla_rel/nposmozilla.pdb + .\obj/osmozilla_rel/nposmozilla.lib + MachineX86 + + + true + $(IntDir)$(ProjectName).bsc + + + copy ..\..\applications\osmozilla\nsIOsmozilla.xpt_w32 ..\..\bin\$(Platform)\$(Configuration)\nposmozilla.xpt + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + .\obj/osmozilla_rel/osmozilla.tlb + + + + + Disabled + Default + ..\..\extra_lib\include\xulrunner-sdk\include;..\..\include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_WINDOWS;_USRDLL;NPBASIC_EXPORTS;MOZILLA_STRICT_API;XP_WIN;_WIN64;XPCOM_GLUE;WINVER=0x0400;%(PreprocessorDefinitions) + true + MultiThreadedDLL + true + .\obj/osmozilla_rel/osmozilla.pch + .\obj/osmozilla_rel/ + .\obj/osmozilla_rel/ + .\obj/osmozilla_rel/ + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + advapi32.lib;user32.lib;gdi32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName)$(TargetExt) + true + ..\..\applications\osmozilla\osmozilla.def + .\obj/osmozilla_rel/nposmozilla.pdb + .\obj/osmozilla_rel/nposmozilla.lib + + + true + $(IntDir)$(ProjectName).bsc + + + copy ..\..\applications\osmozilla\nsIOsmozilla.xpt_w32 ..\..\bin\$(Platform)\$(Configuration)\nposmozilla.xpt + + + + + + + + + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + ../../include;%(AdditionalIncludeDirectories) + ../../include;%(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + ../../include;%(AdditionalIncludeDirectories) + ../../include;%(AdditionalIncludeDirectories) + + + + + + + + + + {d3540754-e0cf-4604-ac11-82de9bd4d814} + false + + + + + + \ No newline at end of file diff --git a/applications/deprecated/old_arch/osmozilla/readme.txt b/applications/deprecated/old_arch/osmozilla/readme.txt new file mode 100644 index 0000000..7a61b5c --- /dev/null +++ b/applications/deprecated/old_arch/osmozilla/readme.txt @@ -0,0 +1,8 @@ +to regenerate plugin interface from IDL: + +xpidl -m header -I path_to\gecko-sdk\xpcom\idl nsIOsmozilla.idl +xpidl -m typelib -I path_to\gecko-sdk\xpcom\idl nsIOsmozilla.idl +xpt_link nposmozilla.xpt nsIOsmozilla.xpt + +This MUST be done for win32 and linux OSs independently, an .xpt file generated on one OS is not compatible with another OS... +Please keep the w32 file "nsIOsmozilla.xpt_w32" and the linux one "nsIOsmozilla.xpt_linux" to bear with makefiles... \ No newline at end of file diff --git a/applications/deprecated/old_arch/osmozilla/resource.h b/applications/deprecated/old_arch/osmozilla/resource.h new file mode 100644 index 0000000..1179a5e --- /dev/null +++ b/applications/deprecated/old_arch/osmozilla/resource.h @@ -0,0 +1,21 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by osmozilla.rc +// +#define IDD_MAIN 101 +#define IDC_BUTTON_GO 1002 +#define IDC_STATIC_UA 1003 +#define IDC_BUTTON1 1005 +#define IDC_BUTTON_DONT 1005 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1006 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/applications/generators/MPEG4/Makefile b/applications/generators/MPEG4/Makefile index e739047..b3f0391 100644 --- a/applications/generators/MPEG4/Makefile +++ b/applications/generators/MPEG4/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/applications/generators/MPEG4 CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/applications/generators/MPEG4/main.c b/applications/generators/MPEG4/main.c index e4a143b..ef7ab5f 100644 --- a/applications/generators/MPEG4/main.c +++ b/applications/generators/MPEG4/main.c @@ -182,9 +182,8 @@ BNode *BlankNode() u8 IsNDT(GF_List *NDTs, char *famName) { u32 i; - char *ndtName; for (i=0; iNDT); i++) { - ndt = gf_list_get(node->NDT, i); + char *ndt = gf_list_get(node->NDT, i); if (!strcmp(ndt, NDTName)) return 1; } return 0; @@ -422,7 +419,6 @@ u32 GetBitsCount(u32 MaxVal) u32 GetNDTCount(char *NDTName, GF_List *BNodes, u32 Version) { u32 i, nodeCount; - BNode *n; //V1 begins at 0 if (Version == 1) { @@ -437,7 +433,7 @@ u32 GetNDTCount(char *NDTName, GF_List *BNodes, u32 Version) nodeCount = 1; } for (i=0; iversion != Version) continue; if (!IsNodeInTable(n, NDTName)) continue; nodeCount++; @@ -449,7 +445,6 @@ u32 GetNDTCount(char *NDTName, GF_List *BNodes, u32 Version) void WriteNDT_H(FILE *f, GF_List *BNodes, GF_List *NDTs, u32 Version) { u32 i, j, first, count; - char *NDTName; BNode *n; @@ -457,7 +452,7 @@ void WriteNDT_H(FILE *f, GF_List *BNodes, GF_List *NDTs, u32 Version) //for all NDTs for (i=0; iname); for (i=0; iFields) ; i++ ) { - bf = gf_list_get(n->Fields, i); + BField *bf = gf_list_get(n->Fields, i); if (!bf->hasAnim && !bf->hasQuant) continue; fprintf(f, "\tcase %d:\n", i); @@ -1038,7 +1031,7 @@ void WriteNodeCode(GF_List *BNodes) } //SFString else if (!strcmp(bf->familly, "SFString")) { - fprintf(f, "\tp->%s.buffer = (char*)gf_malloc(sizeof(char) * %d);\n", bf->name, strlen(bf->def)+1); + fprintf(f, "\tp->%s.buffer = (char*)gf_malloc(sizeof(char) * %u);\n", bf->name, (u32) strlen(bf->def)+1); fprintf(f, "\tstrcpy(p->%s.buffer, \"%s\");\n", bf->name, bf->def); } @@ -1209,7 +1202,7 @@ void WriteNodeCode(GF_List *BNodes) store = CurrentLine; CurrentLine = token; GetNextToken(tok, " \""); - fprintf(f, "\tp->%s.vals[%d] = (char*)gf_malloc(sizeof(char) * %d);\n", bf->name, j, strlen(tok)+1); + fprintf(f, "\tp->%s.vals[%d] = (char*)gf_malloc(sizeof(char) * %u);\n", bf->name, j, (u32) strlen(tok)+1); fprintf(f, "\tstrcpy(p->%s.vals[%d], \"%s\");\n", bf->name, j, tok); j+=1; CurrentLine = store; @@ -1378,10 +1371,6 @@ void ParseTemplateFile(FILE *nodes, GF_List *BNodes, GF_List *NDTs, u32 version) //get its name GetNextToken(n->name, " \t["); - if (!strcmp(n->name, "TimeSensor")) { - n = n; - } - //extract the NDTs GetNextToken(token, "\t[ %#="); if (strcmp(token, "NDT")) { @@ -1474,9 +1463,9 @@ void ParseTemplateFile(FILE *nodes, GF_List *BNodes, GF_List *NDTs, u32 version) } } else if (!strcmp(f->familly, "SFInt32")) { if (!strcmp(f->def, "+I") || !strcmp(f->def, "I")) { - strcpy(f->def, "1 << 31"); + strcpy(f->def, "0x80000000"); } else if (!strcmp(f->def, "-I")) { - strcpy(f->def, "- (1 << 31)"); + strcpy(f->def, "GF_INT_MIN"); } } } @@ -1527,12 +1516,11 @@ void ParseTemplateFile(FILE *nodes, GF_List *BNodes, GF_List *NDTs, u32 version) void WriteNodeDump(FILE *f, BNode *n) { - BField *bf; u32 i; fprintf(f, "static const char *%s_FieldName[] = {\n", n->name); for (i=0; iFields); i++) { - bf = gf_list_get(n->Fields, i); + BField *bf = gf_list_get(n->Fields, i); if (!i) { fprintf(f, " \"%s\"", bf->name); } else { @@ -1634,7 +1622,7 @@ void generate_ndts(GF_List *NDTs, GF_List *nodes, u32 nbVersion) "\n"\ "\n"\ "Node Coding Tables for BIFS Version %d group\n" - ,GPAC_FULL_VERSION, i+1, i+1); + ,gf_gpac_version(), i+1, i+1); for (j=0; jnode->doc == NULL ) { /* if one XPaths a node from a fragment, libxml2 will - refuse the lookup. this is not very usefull for XML + refuse the lookup. this is not very useful for XML scripters. thus we need to create a temporary document to make libxml2 do it's job correctly. */ @@ -461,8 +461,6 @@ void setAttributeType(SVGGenAttribute *att) strcpy(att->impl_type, "SVG_PointerEvents"); } else if (!strcmp(att->svg_name, "vector-effect")) { strcpy(att->impl_type, "SVG_VectorEffect"); - } else if (!strcmp(att->svg_name, "vector-effect")) { - strcpy(att->impl_type, "SVG_VectorEffect"); } else if (!strcmp(att->svg_name, "display-align")) { strcpy(att->impl_type, "SVG_DisplayAlign"); } else if (!strcmp(att->svg_name, "text-align")) { @@ -631,7 +629,7 @@ SVGGenAttrGrp *getOneGlobalAttrGrp(xmlDocPtr doc, xmlXPathContextPtr xpathCtx, x /* attributes group already resolved */ for (j = 0; j < gf_list_count(globalAttrGrp); j++) { - SVGGenAttrGrp *attgrp = gf_list_get(globalAttrGrp, j); + attgrp = gf_list_get(globalAttrGrp, j); if (!strcmp(attgrp->name, name)) { return attgrp; } diff --git a/applications/generators/SVG/v2.c b/applications/generators/SVG/v2.c index f0359aa..957fd48 100644 --- a/applications/generators/SVG/v2.c +++ b/applications/generators/SVG/v2.c @@ -194,9 +194,6 @@ void generateNodeImpl2(FILE *output, SVGGenElement* svg_elt) } else if (!strcmp("solid-opacity", att->svg_name)) { fprintf(output, "\tp->solid_opacity.type = SVG_NUMBER_VALUE;\n"); fprintf(output, "\tp->solid_opacity.value = FIX_ONE;\n"); - } else if (!strcmp("solid-color", att->svg_name)) { - fprintf(output, "\tp->stop_color.type = SVG_PAINT_COLOR;\n"); - fprintf(output, "\tp->stop_color.color.type = SVG_COLOR_RGBCOLOR;\n"); } else if (!strcmp("stop-opacity", att->svg_name)) { fprintf(output, "\tp->stop_opacity.type = SVG_NUMBER_VALUE;\n"); fprintf(output, "\tp->stop_opacity.value = FIX_ONE;\n"); diff --git a/applications/generators/WebGLGen/Makefile b/applications/generators/WebGLGen/Makefile new file mode 100644 index 0000000..6a3d7e2 --- /dev/null +++ b/applications/generators/WebGLGen/Makefile @@ -0,0 +1,52 @@ +include ../../../config.mak + +vpath %.c $(SRC_PATH)/applications/generators/WebGLGen + +CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +#common obj +OBJS= main.o + +ifeq ($(CONFIG_WIN32),yes) +EXE=.exe +PROG=WGLGen$(EXE) +else +EXT= +PROG=WGLGen +endif + +SRCS := $(OBJS:.o=.c) + +all: $(PROG) + +$(PROG): $(OBJS) + $(CC) -o $@ $(OBJS) $(EXTRALIBS) -L../../../bin/gcc -L../../../extra_lib/lib/gcc -lgpac $(LDFLAGS) + + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + + +clean: + rm -f $(OBJS) $(PROG) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/applications/generators/WebGLGen/WGLGen b/applications/generators/WebGLGen/WGLGen new file mode 100755 index 0000000000000000000000000000000000000000..394bb6e3e953da11531c60e6094e4d0c3399073d GIT binary patch literal 27288 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 +#include + +static void start_ctx(char *ctx_name, FILE **ifce_c, FILE **fun_c) +{ + char szFile[GF_MAX_PATH]; + sprintf(szFile, "../../../src/jsmods/%s.c", ctx_name); + FILE *res = gf_fopen(szFile, "wt"); + *ifce_c = res; + *fun_c = gf_file_temp(NULL); + + fprintf(res, "/* !! AUTOGENERATED FILE - DO NOT MODIFY !! \n"); + fprintf(res, "\n%s implementation for QJS\n*/\n\n", ctx_name); +} + + +static void end_ctx(FILE *ifce_c, FILE *func_c, char *ctx_name) +{ + + fprintf(ifce_c, "static const JSCFunctionListEntry %s_funcs[] =\n\{\n", ctx_name); + gf_fseek(func_c, 0, SEEK_SET); + while (!feof(func_c)) { + char szLine[4001]; + szLine[0] = 0; + gf_fgets(szLine, 4000, func_c); + gf_fputs(szLine, ifce_c); + } + gf_fclose(func_c); + fprintf(ifce_c, "};\n"); + gf_fclose(ifce_c); + gf_free(ctx_name); + +} + +static void put_const(FILE *fun_defs, char *c_name, char *c_val) +{ + fprintf(fun_defs, "\tJS_PROP_INT32_DEF(\"%s\", %s, JS_PROP_CONFIGURABLE),\n", c_name, c_val); +} +static void put_fun(FILE *fun_defs, char *f_name) +{ + fprintf(fun_defs, "\tJS_CFUNC_DEF(\"%s\", 0, wgl_%s),\n", f_name, f_name); +} +static void put_prop(FILE *fun_defs, char *ctx_name, char *pname, Bool is_readonly) +{ + if (is_readonly) { + fprintf(fun_defs, "\tJS_CGETSET_MAGIC_DEF(\"%s\", %s_getProperty, NULL, %s_PROP_%s),\n", pname, ctx_name, ctx_name, pname); + } else { + fprintf(fun_defs, "\tJS_CGETSET_MAGIC_DEF(\"%s\", %s_getProperty, %s_setProperty, %s_PROP_%s),\n", pname, ctx_name, ctx_name, ctx_name, pname); + } +} + +static const char *get_arg_type(char *arg_type, Bool *is_array) +{ + *is_array = GF_FALSE; + if (!strcmp(arg_type, "GLenum")) return "u32"; + if (!strcmp(arg_type, "DOMString")) return "const char *"; + if (!strcmp(arg_type, "DOMString?")) return "const char *"; + if (!strcmp(arg_type, "GLuint")) return "u32"; + if (!strcmp(arg_type, "GLint")) return "s32"; + if (!strcmp(arg_type, "GLuint")) return "u32"; + if (!strcmp(arg_type, "GLclampf")) return "Float"; + if (!strcmp(arg_type, "GLfloat")) return "Float"; + if (!strcmp(arg_type, "GLboolean")) return "Bool"; + if (!strcmp(arg_type, "boolean")) return "Bool"; + if (!strcmp(arg_type, "GLsizei")) return "u32"; + if (!strcmp(arg_type, "GLbitfield")) return "u32"; + if (!strcmp(arg_type, "GLshort")) return "s16"; + if (!strcmp(arg_type, "GLushort")) return "u16"; + if (!strcmp(arg_type, "GLsizeiptr")) return "u64"; + if (!strcmp(arg_type, "GLintptr")) return "u64"; + if (!strcmp(arg_type, "GLbyte")) return "s8"; + if (!strcmp(arg_type, "GLubyte")) return "u8"; + if (!strcmp(arg_type, "WebGLProgram?")) return "WebGLProgram"; + if (!strcmp(arg_type, "WebGLShader?")) return "WebGLShader"; + if (!strcmp(arg_type, "WebGLBuffer?")) return "WebGLBuffer"; + if (!strcmp(arg_type, "WebGLFramebuffer?")) return "WebGLFramebuffer"; + if (!strcmp(arg_type, "WebGLRenderbuffer?")) return "WebGLRenderbuffer"; + if (!strcmp(arg_type, "WebGLTexture?")) return "WebGLTexture"; + if (!strcmp(arg_type, "WebGLUniformLocation?")) return "WebGLUniformLocation"; + if (!strcmp(arg_type, "TexImageSource?")) return "TexImageSource"; + if (!strcmp(arg_type, "WebGLContextAttributes?")) return "WebGLContextAttributes"; + if (!strcmp(arg_type, "boolean?")) return "Bool"; + if (!strcmp(arg_type, "WebGLActiveInfo?")) return "JSValue"; + + if (!strcmp(arg_type, "Float32Array")) { + *is_array = GF_TRUE; + return "Float *"; + } + if (!strcmp(arg_type, "Int32Array")) { + *is_array = GF_TRUE; + return "s32 *"; + } + if (!strcmp(arg_type, "sequence?")) { + return "JSValue"; + } + if (!strcmp(arg_type, "sequence?")) { + return "JSValue"; + } + if (!strcmp(arg_type, "ArrayBufferView") + || !strcmp(arg_type, "ArrayBufferView?") + || !strcmp(arg_type, "BufferDataSource?") + ) { + *is_array = GF_TRUE; + return "u8 *"; + } + + if (!strcmp(arg_type, "object?") + || !strcmp(arg_type, "WebGLShaderPrecisionFormat?") + || !strcmp(arg_type, "any") + ) { + return NULL; + } + + fprintf(stderr, "arg type %s not supported\n", arg_type); + return "JSValue"; +} + +static void put_arg_get(FILE *ifce_c, char *fname, int idx, char *arg_type, char *arg_name) +{ + Bool is_array; + const char *native_type = get_arg_type(arg_type, &is_array); + if (!native_type) return; + + if (!strcmp(native_type, "Bool")) { + fprintf(ifce_c, "\tWGL_GET_BOOL(%s, argv[%d]);\n", arg_name, idx); + } + else if (!strcmp(native_type, "s32")) { + fprintf(ifce_c, "\tWGL_GET_S32(%s, argv[%d]);\n", arg_name, idx); + } + else if (!strcmp(native_type, "u32")) { + fprintf(ifce_c, "\tWGL_GET_U32(%s, argv[%d]);\n", arg_name, idx); + } + else if (!strcmp(native_type, "u64")) { + fprintf(ifce_c, "\tWGL_GET_U64(%s, argv[%d]);\n", arg_name, idx); + } + else if (!strcmp(native_type, "s64")) { + fprintf(ifce_c, "\tWGL_GET_S64(%s, argv[%d]);\n", arg_name, idx); + } + else if (!strcmp(native_type, "s16")) { + fprintf(ifce_c, "\tWGL_GET_S16(%s, argv[%d]);\n", arg_name, idx); + } + else if (!strcmp(native_type, "u16")) { + fprintf(ifce_c, "\tWGL_GET_U16(%s, argv[%d]);\n", arg_name, idx); + } + else if (!strcmp(arg_type, "GLclampf")) { + fprintf(ifce_c, "\tWGL_GET_FLOAT_CLAMP(%s, argv[%d]);\n", arg_name, idx); + } + else if (!strcmp(native_type, "Float")) { + fprintf(ifce_c, "\tWGL_GET_FLOAT(%s, argv[%d]);\n", arg_name, idx); + } + else if (!strcmp(native_type, "const char *")) { + fprintf(ifce_c, "\tWGL_GET_STRING(%s, argv[%d]);\n", arg_name, idx); + } + else if (!strcmp(native_type, "WebGLProgram") + || !strcmp(native_type, "WebGLShader") + || !strcmp(native_type, "WebGLBuffer") + || !strcmp(native_type, "WebGLFramebuffer") + || !strcmp(native_type, "WebGLRenderbuffer") + || !strcmp(native_type, "WebGLTexture") + || !strcmp(native_type, "WebGLUniformLocation") + ) { + fprintf(ifce_c, "\tWGL_GET_GLID(%s, argv[%d], %s_class_id);\n", arg_name, idx, native_type); + } + + else if (!strcmp(arg_type, "BufferDataSource?")) { + fprintf(ifce_c, "\tif(!JS_IsObject(argv[%d])) {\n", idx); + fprintf(ifce_c, "\t\tWGL_GET_U32(%s_size, argv[%d]);\n", arg_name, idx); + fprintf(ifce_c, "\t} else {\n"); + fprintf(ifce_c, "\t\t%s = wgl_GetArrayBuffer(ctx, &%s_size, argv[%d]);\n", arg_name, arg_name, idx); + fprintf(ifce_c, "\t}\n"); + } + else if (is_array && !strcmp(native_type, "u8 *")) { + fprintf(ifce_c, "\tif(!JS_IsArray(ctx, argv[%d])) return js_throw_err(ctx, WGL_INVALID_VALUE);\n", idx); + fprintf(ifce_c, "\t%s = wgl_GetArrayBuffer(ctx, &%s_size, argv[%d]);\n", arg_name, arg_name, idx); + } + else if (is_array && (!strcmp(native_type, "Float *") || !strcmp(native_type, "s32 *")) ) { + u32 vlen = 1; + if (strstr(fname, "2fv")) vlen = 2; + else if (strstr(fname, "3fv")) vlen = 3; + else if (strstr(fname, "4fv")) vlen = 4; + + if (!strcmp(native_type, "s32 *")) + fprintf(ifce_c, "\tif (!WGL_LOAD_INT32_VEC(ctx, argv[%d], (s32 **) &%s, &%s_size, %d)) return js_throw_err(ctx, WGL_INVALID_VALUE);;\n", idx, arg_name, arg_name, vlen); + else { + Bool is_matrix = GF_FALSE; + if (strstr(fname, "Matrix")) is_matrix = GF_TRUE; + if (strstr(fname, "vertexAttrib")) { + fprintf(ifce_c, "\tif (!WGL_LOAD_FLOAT_VEC(ctx, argv[%d], (Float **) &%s, NULL, %d, %d)) return js_throw_err(ctx, WGL_INVALID_VALUE);\n", idx, arg_name, vlen, is_matrix); + } else { + fprintf(ifce_c, "\tif (!WGL_LOAD_FLOAT_VEC(ctx, argv[%d], (Float **) &%s, &%s_size, %d, %d)) return js_throw_err(ctx, WGL_INVALID_VALUE);\n", idx, arg_name, arg_name, vlen, is_matrix); + } + } + } + else { + fprintf(stderr, "arg type %s not supported\n", arg_type); + } + +} + +static void put_arg_decl(FILE *ifce_c, char *fname, char *arg_type, char *arg_name) +{ + Bool is_array = GF_FALSE; + const char *native_type = get_arg_type(arg_type, &is_array); + if (!native_type) + return; + + if (is_array) { + if (strstr(fname, "vertexAttrib1fv")) { + fprintf(ifce_c, "\tFloat %s[1];\n", arg_name); + } else if (strstr(fname, "vertexAttrib2fv")) { + fprintf(ifce_c, "\tFloat %s[2];\n", arg_name); + } else if (strstr(fname, "vertexAttrib3fv")) { + fprintf(ifce_c, "\tFloat %s[3];\n", arg_name); + } else if (strstr(fname, "vertexAttrib4fv")) { + fprintf(ifce_c, "\tFloat %s[4];\n", arg_name); + } else { + fprintf(ifce_c, "\t%s %s = NULL;\n", native_type, arg_name); + fprintf(ifce_c, "\tu32 %s_size = 0;\n", arg_name); + } + } else { + if (strstr(arg_name, " *")) + fprintf(ifce_c, "\t%s %s = NULL;\n", native_type, arg_name); + else if (!strcmp(native_type, "JSValue")) + fprintf(ifce_c, "\t%s %s = JS_UNDEFINED;\n", native_type, arg_name); + else if (!strncmp(native_type, "WebGL", 5)) + fprintf(ifce_c, "\tGLuint %s = 0;\n", arg_name); + else + fprintf(ifce_c, "\t%s %s = 0;\n", native_type, arg_name); + } +} +/*all functions that cannot be mapped directly due to to heavy syntax pacthing or our own hacking*/ +static const char *not_autogen_funcs = "getContextAttributes,isContextLost,getSupportedExtensions,getExtension,getBufferParameter,getParameter,getFramebufferAttachmentParameter,getProgramParameter,getRenderbufferParameter,getShaderParameter,getShaderPrecisionFormat,getProgramInfoLog,getShaderParameter,getShaderInfoLog,getShaderSource,getTexParameter,getUniform,getVertexAttrib,getVertexAttribOffset,readPixels,shaderSource,texImage2D,texSubImage2D,useProgram,activeTexture,bindTexture,getUniformLocation,createTexture,bindFramebuffer"; + +static void wrap_func(FILE *ifce_c, char *fname, char *fun_def) +{ + char *sep, *nsep; + u32 len, i, nb_args; + u32 first_char; + u32 ret_type=0; + Bool is_create=GF_FALSE; + Bool is_delete_fun=GF_FALSE; + char *ret_val=NULL, *exc_value=NULL, *arg_type[50], *arg_name[50]; + + if (strstr(not_autogen_funcs, fname)) { + fprintf(ifce_c, "\nstatic JSValue wgl_%s(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);\n", fname); + return; + } + + fprintf(ifce_c, "\nstatic JSValue wgl_%s(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)\n{\n", fname); + + nb_args = 0; + memset(arg_type, 0, sizeof(char *)*50); + memset(arg_name, 0, sizeof(char *)*50); + + sep = fun_def; + while (sep[0]==' ') sep++; + /*parse exception*/ + if (sep[0]=='[') { + nsep = strchr(sep, ']'); + assert(nsep); + nsep[0]=0; + exc_value = gf_strdup(sep); + nsep[0]=']'; + sep = nsep+1; + while (sep[0]==' ') sep++; + } + /*parse return value*/ + nsep = strchr(sep, ' '); + assert(nsep); + nsep[0] = 0; + ret_val = strdup(sep); + sep = nsep+1; + len = strlen(fname); + assert(!strncmp(sep, fname, len) ); + sep += len+1; + while (sep) { + char *next_arg; + nsep = strchr(sep, ','); + if (!nsep) { + nsep = strchr(sep, ')'); + if (!nsep) break; + } + nsep[0]=0; + next_arg = nsep+1; + + if (nb_args>=50) { + assert(0); + } + nsep = strchr(sep, ' '); + if (!nsep) + break; + assert(nsep); + nsep[0] = 0; + arg_type[nb_args] = gf_strdup(sep); + arg_name[nb_args] = gf_strdup(nsep+1); + nb_args++; + sep = next_arg; + while (sep[0]==' ') sep++; + } + + fprintf(ifce_c, "\tJSValue ret_val_js = JS_UNDEFINED;\n"); + + for (i=0; i 'Z')) { + first_char += 'A' - 'a'; + } + + if (is_create) { + fprintf(ifce_c, "\tGF_SAFEALLOC(wglo, GF_WebGLObject);\n"); + fprintf(ifce_c, "\tif (!wglo) return js_throw_err(ctx, WGL_OUT_OF_MEMORY);\n"); + fprintf(ifce_c, "\twglo->par_ctx = JS_GetOpaque(this_val, WebGLRenderingContextBase_class_id);\n"); + fprintf(ifce_c, "\tglGen%ss(1, &wglo->gl_id", fname+6); + } else { + if (!strcmp(fname, "deleteShader") || !strcmp(fname, "deleteProgram")) { + is_delete_fun=GF_TRUE; + } + if (!strcmp(fname, "deleteBuffer") || !strcmp(fname, "deleteTexture") || !strcmp(fname, "deleteFramebuffer") || !strcmp(fname, "deleteRenderbuffer")) { + fprintf(ifce_c, "\tgl%c%ss(1, &", first_char, fname+1); + is_delete_fun=GF_TRUE; + } + else if (!strcmp(fname, "createProgram") || !strcmp(fname, "createShader")) { + fprintf(ifce_c, "\tGF_SAFEALLOC(wglo, GF_WebGLObject);\n"); + fprintf(ifce_c, "\tif (!wglo) return js_throw_err(ctx, WGL_OUT_OF_MEMORY);\n"); + fprintf(ifce_c, "\twglo->par_ctx = JS_GetOpaque(this_val, WebGLRenderingContextBase_class_id);\n"); + fprintf(ifce_c, "\twglo->gl_id = gl%c%s(", first_char, fname+1); + } else if (!strcmp(fname, "clearDepth") || !strcmp(fname, "depthRange")) { + fprintf(ifce_c, "#if defined(GPAC_USE_GLES2)\n"); + fprintf(ifce_c, "\tgl%c%sf(\n", first_char, fname+1); + fprintf(ifce_c, "#else\n"); + fprintf(ifce_c, "\tgl%c%s(\n", first_char, fname+1); + fprintf(ifce_c, "#endif\n\t\t"); + } else { + fprintf(ifce_c, "\t"); + if (ret_type==1) { + fprintf(ifce_c, "ret_val = "); + } + if (!strncmp(fname, "uniform", 7)) { + + fprintf(ifce_c, "/*hack for GPAC textures, we create uniforms with loc -2 for such textures*/\n\tif ((s32) location>=0)\n\t\t"); + } + fprintf(ifce_c, "gl%c%s(", first_char, fname+1); + } + } + + Bool prev_is_transpose = GF_FALSE; + for (i=0; igl_id=0;\n", nat_type); + fprintf(ifce_c, "\tJS_FreeValue(ctx, glo->obj);\n\tglo->obj = JS_UNDEFINED;\n\tgf_list_del_item(glo->par_ctx->all_objects, glo);\n"); + fprintf(ifce_c, "\t}\n\t}\n"); + } + + if (ret_type==1) { + + if (!strcmp(ret_val, "GLenum") || !strcmp(ret_val, "GLint")) { + fprintf(ifce_c, "\tret_val_js = JS_NewInt32(ctx, ret_val);\n"); + } else if (!strcmp(ret_val, "GLboolean") ) { + fprintf(ifce_c, "\tret_val_js = JS_NewBool(ctx, ret_val);\n"); + } else if (!strcmp(ret_val, "WebGLProgram") ) { + fprintf(ifce_c, "\tret_val_js = JS_NewObjectClass(ctx, WebGLProgram_class_id);\n"); + fprintf(ifce_c, "\tJS_SetOpaque(ret_val_js, wglo);\n"); + } else { + fprintf(stderr, "retval %s not supported in %s\n", ret_val, fname); + } + } else if (ret_type==2) { + Bool is_array; + const char *native_type = get_arg_type(ret_val, &is_array); + fprintf(ifce_c, "\tret_val_js = JS_NewObjectClass(ctx, %s_class_id);\n", native_type); + fprintf(ifce_c, "\tJS_SetOpaque(ret_val_js, wglo);\n"); + fprintf(ifce_c, "\twglo->obj = JS_DupValue(ctx, ret_val_js);\n"); + fprintf(ifce_c, "\twglo->class_id = %s_class_id;\n", native_type); + fprintf(ifce_c, "\tgf_list_add(wglo->par_ctx->all_objects, wglo);\n"); + } + + for (i=0; i1) { + gf_free(arg_type[nb_args-1]); + gf_free(arg_name[nb_args-1]); + nb_args--; + } + gf_free(ret_val); + fprintf(ifce_c, "}\n"); +} + + +static void load_idl(const char *file) +{ + Bool in_ctxbase = GF_FALSE; + char szLine[4001], *sep; + char *curCtx = NULL; + u32 len; + FILE * idl = gf_fopen(file, "rt"); + FILE *ifce_c=NULL; + FILE *fun_defs=NULL; + + while (!feof(idl)) { + gf_fgets(szLine, 4000, idl); + if (strstr(szLine, "implements")) + continue; + len = strlen(szLine); + while (len && strchr(" \n", szLine[len])) { + szLine[len] = 0; + len--; + } + if (!len) continue; + + if (!in_ctxbase) { + if (!strstr(szLine, "RenderingContextBase")) + continue; + in_ctxbase = GF_TRUE; + sep = strchr(szLine, ' '); + assert(sep); + curCtx = gf_strdup(sep+1); + start_ctx(curCtx, &ifce_c, &fun_defs); + continue; + } else { + if (strstr(szLine, "};")){ + in_ctxbase = GF_FALSE; + end_ctx(ifce_c, fun_defs, curCtx); + ifce_c = NULL; + continue; + } + } + + if (strstr(szLine, "/*")) continue; + sep = strstr(szLine, "const "); + if (sep) { + char *cname, *nsep; + sep = strchr(sep+7, ' '); + assert(sep); + sep++; + nsep = strchr(sep, ' '); + assert(nsep); + nsep[0] = 0; + cname = gf_strdup(sep); + sep = strchr(nsep+1, '='); + assert(sep); + sep++; + while (sep[0]==' ') sep++; + nsep = strchr(sep, ';'); + assert(nsep); + nsep[0]=0; + put_const(fun_defs, cname, sep); + gf_free(cname); + continue; + } + sep = strstr(szLine, "typedef"); + if (sep) continue; + + sep = strstr(szLine, "attribute"); + if (sep) { + char *nsep; + Bool is_readonly = GF_FALSE; + sep = szLine; + while (sep[0]==' ') sep++; + if (!strncmp(sep, "readonly", 8)) { + is_readonly = GF_TRUE; + sep+=9; + } + sep += 10; + sep = strchr(sep, ' '); + assert(sep); + sep+=1; + nsep = strchr(sep, ';'); + assert(nsep); + nsep[0] = 0; + put_prop(fun_defs, curCtx, sep, is_readonly); + continue; + } + /*double type declaration for glXXXifv*/ + if (strstr(szLine, ", sequence<")) + continue; + /*double type declaration for bufferData*/ + if (strstr(szLine, ", GLsizeiptr size")) + continue; + /*double type declaration for texImage2D and texSubImage2D*/ + if (strstr(szLine, ", TexImageSource")) + continue; + + sep = strstr(szLine, "("); + /*this is a function*/ + if (sep) { + char *nsep, *fname; + sep = strchr(szLine, ']'); + if (sep) sep+=1; + else sep = szLine; + while (sep[0] == ' ') sep++; + sep = strchr(sep, ' '); + assert(sep); + sep++; + nsep = strchr(sep, '('); + assert(nsep); + nsep[0] = 0; + fname = gf_strdup(sep); + put_fun(fun_defs, sep); + nsep[0] = '('; + + wrap_func(ifce_c, fname, szLine); + gf_free(fname); + continue; + } + fprintf(stderr, "instruction %s not handled\n", szLine); + } + + + gf_fclose(idl); +} + +int main(int argc, char **argv) +{ + u32 i=0; + for (i=1; i? getSupportedExtensions(); + object? getExtension(DOMString name); + + void activeTexture(GLenum texture); + void attachShader(WebGLProgram? program, WebGLShader? shader); + void bindAttribLocation(WebGLProgram? program, GLuint index, DOMString name); + void bindBuffer(GLenum target, WebGLBuffer? buffer); + void bindFramebuffer(GLenum target, WebGLFramebuffer? framebuffer); + void bindRenderbuffer(GLenum target, WebGLRenderbuffer? renderbuffer); + void bindTexture(GLenum target, WebGLTexture? texture); + void blendColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); + void blendEquation(GLenum mode); + void blendEquationSeparate(GLenum modeRGB, GLenum modeAlpha); + void blendFunc(GLenum sfactor, GLenum dfactor); + void blendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); + + typedef (ArrayBuffer or ArrayBufferView) BufferDataSource; + void bufferData(GLenum target, GLsizeiptr size, GLenum usage); + void bufferData(GLenum target, BufferDataSource? data, GLenum usage); + void bufferSubData(GLenum target, GLintptr offset, BufferDataSource? data); + + [WebGLHandlesContextLoss] GLenum checkFramebufferStatus(GLenum target); + void clear(GLbitfield mask); + void clearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); + void clearDepth(GLclampf depth); + void clearStencil(GLint s); + void colorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); + void compileShader(WebGLShader? shader); + + void compressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, ArrayBufferView data); + void compressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, ArrayBufferView data); + + void copyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); + void copyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); + + WebGLBuffer? createBuffer(); + WebGLFramebuffer? createFramebuffer(); + WebGLProgram? createProgram(); + WebGLRenderbuffer? createRenderbuffer(); + WebGLShader? createShader(GLenum type); + WebGLTexture? createTexture(); + + void cullFace(GLenum mode); + + void deleteBuffer(WebGLBuffer? buffer); + void deleteFramebuffer(WebGLFramebuffer? framebuffer); + void deleteProgram(WebGLProgram? program); + void deleteRenderbuffer(WebGLRenderbuffer? renderbuffer); + void deleteShader(WebGLShader? shader); + void deleteTexture(WebGLTexture? texture); + + void depthFunc(GLenum func); + void depthMask(GLboolean flag); + void depthRange(GLclampf zNear, GLclampf zFar); + void detachShader(WebGLProgram? program, WebGLShader? shader); + void disable(GLenum cap); + void disableVertexAttribArray(GLuint index); + void drawArrays(GLenum mode, GLint first, GLsizei count); + void drawElements(GLenum mode, GLsizei count, GLenum type, GLintptr offset); + + void enable(GLenum cap); + void enableVertexAttribArray(GLuint index); + void finish(); + void flush(); + void framebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, WebGLRenderbuffer? renderbuffer); + void framebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, WebGLTexture? texture, GLint level); + void frontFace(GLenum mode); + + void generateMipmap(GLenum target); + + WebGLActiveInfo? getActiveAttrib(WebGLProgram? program, GLuint index); + WebGLActiveInfo? getActiveUniform(WebGLProgram? program, GLuint index); + sequence? getAttachedShaders(WebGLProgram? program); + + [WebGLHandlesContextLoss] GLint getAttribLocation(WebGLProgram? program, DOMString name); + + any getBufferParameter(GLenum target, GLenum pname); + any getParameter(GLenum pname); + + [WebGLHandlesContextLoss] GLenum getError(); + + any getFramebufferAttachmentParameter(GLenum target, GLenum attachment, GLenum pname); + any getProgramParameter(WebGLProgram? program, GLenum pname); + DOMString? getProgramInfoLog(WebGLProgram? program); + any getRenderbufferParameter(GLenum target, GLenum pname); + any getShaderParameter(WebGLShader? shader, GLenum pname); + WebGLShaderPrecisionFormat? getShaderPrecisionFormat(GLenum shadertype, GLenum precisiontype); + DOMString? getShaderInfoLog(WebGLShader? shader); + + DOMString? getShaderSource(WebGLShader? shader); + + any getTexParameter(GLenum target, GLenum pname); + + any getUniform(WebGLProgram? program, WebGLUniformLocation? location); + + WebGLUniformLocation? getUniformLocation(WebGLProgram? program, DOMString name); + + any getVertexAttrib(GLuint index, GLenum pname); + + [WebGLHandlesContextLoss] GLsizeiptr getVertexAttribOffset(GLuint index, GLenum pname); + + void hint(GLenum target, GLenum mode); + [WebGLHandlesContextLoss] GLboolean isBuffer(WebGLBuffer? buffer); + [WebGLHandlesContextLoss] GLboolean isEnabled(GLenum cap); + [WebGLHandlesContextLoss] GLboolean isFramebuffer(WebGLFramebuffer? framebuffer); + [WebGLHandlesContextLoss] GLboolean isProgram(WebGLProgram? program); + [WebGLHandlesContextLoss] GLboolean isRenderbuffer(WebGLRenderbuffer? renderbuffer); + [WebGLHandlesContextLoss] GLboolean isShader(WebGLShader? shader); + [WebGLHandlesContextLoss] GLboolean isTexture(WebGLTexture? texture); + void lineWidth(GLfloat width); + void linkProgram(WebGLProgram? program); + void pixelStorei(GLenum pname, GLint param); + void polygonOffset(GLfloat factor, GLfloat units); + + void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, ArrayBufferView? pixels); + + void renderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); + void sampleCoverage(GLclampf value, GLboolean invert); + void scissor(GLint x, GLint y, GLsizei width, GLsizei height); + + void shaderSource(WebGLShader? shader, DOMString source); + + void stencilFunc(GLenum func, GLint ref, GLuint mask); + void stencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask); + void stencilMask(GLuint mask); + void stencilMaskSeparate(GLenum face, GLuint mask); + void stencilOp(GLenum fail, GLenum zfail, GLenum zpass); + void stencilOpSeparate(GLenum face, GLenum fail, GLenum zfail, GLenum zpass); + + typedef (ImageData or HTMLImageElement or HTMLCanvasElement or HTMLVideoElement) TexImageSource; + void texImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, ArrayBufferView? pixels); + void texImage2D(GLenum target, GLint level, GLenum internalformat, GLenum format, GLenum type, TexImageSource? source); // May throw DOMException + + void texParameterf(GLenum target, GLenum pname, GLfloat param); + void texParameteri(GLenum target, GLenum pname, GLint param); + + void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, ArrayBufferView? pixels); + void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLenum format, GLenum type, TexImageSource? source); // May throw DOMException + + void uniform1f(WebGLUniformLocation? location, GLfloat x); + void uniform1fv(WebGLUniformLocation? location, Float32Array v); + void uniform1fv(WebGLUniformLocation? location, sequence v); + void uniform1i(WebGLUniformLocation? location, GLint x); + void uniform1iv(WebGLUniformLocation? location, Int32Array v); + void uniform1iv(WebGLUniformLocation? location, sequence v); + void uniform2f(WebGLUniformLocation? location, GLfloat x, GLfloat y); + void uniform2fv(WebGLUniformLocation? location, Float32Array v); + void uniform2fv(WebGLUniformLocation? location, sequence v); + void uniform2i(WebGLUniformLocation? location, GLint x, GLint y); + void uniform2iv(WebGLUniformLocation? location, Int32Array v); + void uniform2iv(WebGLUniformLocation? location, sequence v); + void uniform3f(WebGLUniformLocation? location, GLfloat x, GLfloat y, GLfloat z); + void uniform3fv(WebGLUniformLocation? location, Float32Array v); + void uniform3fv(WebGLUniformLocation? location, sequence v); + void uniform3i(WebGLUniformLocation? location, GLint x, GLint y, GLint z); + void uniform3iv(WebGLUniformLocation? location, Int32Array v); + void uniform3iv(WebGLUniformLocation? location, sequence v); + void uniform4f(WebGLUniformLocation? location, GLfloat x, GLfloat y, GLfloat z, GLfloat w); + void uniform4fv(WebGLUniformLocation? location, Float32Array v); + void uniform4fv(WebGLUniformLocation? location, sequence v); + void uniform4i(WebGLUniformLocation? location, GLint x, GLint y, GLint z, GLint w); + void uniform4iv(WebGLUniformLocation? location, Int32Array v); + void uniform4iv(WebGLUniformLocation? location, sequence v); + + void uniformMatrix2fv(WebGLUniformLocation? location, GLboolean transpose, Float32Array value); + void uniformMatrix2fv(WebGLUniformLocation? location, GLboolean transpose, sequence value); + void uniformMatrix3fv(WebGLUniformLocation? location, GLboolean transpose, Float32Array value); + void uniformMatrix3fv(WebGLUniformLocation? location, GLboolean transpose, sequence value); + void uniformMatrix4fv(WebGLUniformLocation? location, GLboolean transpose, Float32Array value); + void uniformMatrix4fv(WebGLUniformLocation? location, GLboolean transpose, sequence value); + + void useProgram(WebGLProgram? program); + void validateProgram(WebGLProgram? program); + + void vertexAttrib1f(GLuint indx, GLfloat x); + void vertexAttrib1fv(GLuint indx, Float32Array values); + void vertexAttrib1fv(GLuint indx, sequence values); + void vertexAttrib2f(GLuint indx, GLfloat x, GLfloat y); + void vertexAttrib2fv(GLuint indx, Float32Array values); + void vertexAttrib2fv(GLuint indx, sequence values); + void vertexAttrib3f(GLuint indx, GLfloat x, GLfloat y, GLfloat z); + void vertexAttrib3fv(GLuint indx, Float32Array values); + void vertexAttrib3fv(GLuint indx, sequence values); + void vertexAttrib4f(GLuint indx, GLfloat x, GLfloat y, GLfloat z, GLfloat w); + void vertexAttrib4fv(GLuint indx, Float32Array values); + void vertexAttrib4fv(GLuint indx, sequence values); + void vertexAttribPointer(GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLintptr offset); + + void viewport(GLint x, GLint y, GLsizei width, GLsizei height); +}; + +interface WebGLRenderingContext +{ +}; +WebGLRenderingContext implements WebGLRenderingContextBase; + + +[Constructor(DOMString type, optional WebGLContextEventInit eventInit)] +interface WebGLContextEvent : Event { + readonly attribute DOMString statusMessage; +}; + +// EventInit is defined in the DOM4 specification. +dictionary WebGLContextEventInit : EventInit { + DOMString statusMessage; +}; diff --git a/applications/generators/WebGLGen/webgl2.idl b/applications/generators/WebGLGen/webgl2.idl new file mode 100644 index 0000000..e5dbdc4 --- /dev/null +++ b/applications/generators/WebGLGen/webgl2.idl @@ -0,0 +1,584 @@ +// AUTOGENERATED FILE -- DO NOT EDIT -- SEE Makefile +// +// WebGL IDL definitions scraped from the Khronos specification: +// https://www.khronos.org/registry/webgl/specs/latest/ +// +// This IDL depends on the typed array specification defined at: +// https://www.khronos.org/registry/typedarray/specs/latest/typedarrays.idl + +typedef long long GLint64; +typedef unsigned long long GLuint64; + + +interface WebGLQuery : WebGLObject { +}; + +interface WebGLSampler : WebGLObject { +}; + +interface WebGLSync : WebGLObject { +}; + +interface WebGLTransformFeedback : WebGLObject { +}; + +interface WebGLVertexArrayObject : WebGLObject { +}; + +typedef (Float32Array or sequence) Float32List; +typedef (Int32Array or sequence) Int32List; +typedef (Uint32Array or sequence) Uint32List; + +[NoInterfaceObject] +interface WebGL2RenderingContextBase +{ + const GLenum READ_BUFFER = 0x0C02; + const GLenum UNPACK_ROW_LENGTH = 0x0CF2; + const GLenum UNPACK_SKIP_ROWS = 0x0CF3; + const GLenum UNPACK_SKIP_PIXELS = 0x0CF4; + const GLenum PACK_ROW_LENGTH = 0x0D02; + const GLenum PACK_SKIP_ROWS = 0x0D03; + const GLenum PACK_SKIP_PIXELS = 0x0D04; + const GLenum COLOR = 0x1800; + const GLenum DEPTH = 0x1801; + const GLenum STENCIL = 0x1802; + const GLenum RED = 0x1903; + const GLenum RGB8 = 0x8051; + const GLenum RGBA8 = 0x8058; + const GLenum RGB10_A2 = 0x8059; + const GLenum TEXTURE_BINDING_3D = 0x806A; + const GLenum UNPACK_SKIP_IMAGES = 0x806D; + const GLenum UNPACK_IMAGE_HEIGHT = 0x806E; + const GLenum TEXTURE_3D = 0x806F; + const GLenum TEXTURE_WRAP_R = 0x8072; + const GLenum MAX_3D_TEXTURE_SIZE = 0x8073; + const GLenum UNSIGNED_INT_2_10_10_10_REV = 0x8368; + const GLenum MAX_ELEMENTS_VERTICES = 0x80E8; + const GLenum MAX_ELEMENTS_INDICES = 0x80E9; + const GLenum TEXTURE_MIN_LOD = 0x813A; + const GLenum TEXTURE_MAX_LOD = 0x813B; + const GLenum TEXTURE_BASE_LEVEL = 0x813C; + const GLenum TEXTURE_MAX_LEVEL = 0x813D; + const GLenum MIN = 0x8007; + const GLenum MAX = 0x8008; + const GLenum DEPTH_COMPONENT24 = 0x81A6; + const GLenum MAX_TEXTURE_LOD_BIAS = 0x84FD; + const GLenum TEXTURE_COMPARE_MODE = 0x884C; + const GLenum TEXTURE_COMPARE_FUNC = 0x884D; + const GLenum CURRENT_QUERY = 0x8865; + const GLenum QUERY_RESULT = 0x8866; + const GLenum QUERY_RESULT_AVAILABLE = 0x8867; + const GLenum STREAM_READ = 0x88E1; + const GLenum STREAM_COPY = 0x88E2; + const GLenum STATIC_READ = 0x88E5; + const GLenum STATIC_COPY = 0x88E6; + const GLenum DYNAMIC_READ = 0x88E9; + const GLenum DYNAMIC_COPY = 0x88EA; + const GLenum MAX_DRAW_BUFFERS = 0x8824; + const GLenum DRAW_BUFFER0 = 0x8825; + const GLenum DRAW_BUFFER1 = 0x8826; + const GLenum DRAW_BUFFER2 = 0x8827; + const GLenum DRAW_BUFFER3 = 0x8828; + const GLenum DRAW_BUFFER4 = 0x8829; + const GLenum DRAW_BUFFER5 = 0x882A; + const GLenum DRAW_BUFFER6 = 0x882B; + const GLenum DRAW_BUFFER7 = 0x882C; + const GLenum DRAW_BUFFER8 = 0x882D; + const GLenum DRAW_BUFFER9 = 0x882E; + const GLenum DRAW_BUFFER10 = 0x882F; + const GLenum DRAW_BUFFER11 = 0x8830; + const GLenum DRAW_BUFFER12 = 0x8831; + const GLenum DRAW_BUFFER13 = 0x8832; + const GLenum DRAW_BUFFER14 = 0x8833; + const GLenum DRAW_BUFFER15 = 0x8834; + const GLenum MAX_FRAGMENT_UNIFORM_COMPONENTS = 0x8B49; + const GLenum MAX_VERTEX_UNIFORM_COMPONENTS = 0x8B4A; + const GLenum SAMPLER_3D = 0x8B5F; + const GLenum SAMPLER_2D_SHADOW = 0x8B62; + const GLenum FRAGMENT_SHADER_DERIVATIVE_HINT = 0x8B8B; + const GLenum PIXEL_PACK_BUFFER = 0x88EB; + const GLenum PIXEL_UNPACK_BUFFER = 0x88EC; + const GLenum PIXEL_PACK_BUFFER_BINDING = 0x88ED; + const GLenum PIXEL_UNPACK_BUFFER_BINDING = 0x88EF; + const GLenum FLOAT_MAT2x3 = 0x8B65; + const GLenum FLOAT_MAT2x4 = 0x8B66; + const GLenum FLOAT_MAT3x2 = 0x8B67; + const GLenum FLOAT_MAT3x4 = 0x8B68; + const GLenum FLOAT_MAT4x2 = 0x8B69; + const GLenum FLOAT_MAT4x3 = 0x8B6A; + const GLenum SRGB = 0x8C40; + const GLenum SRGB8 = 0x8C41; + const GLenum SRGB8_ALPHA8 = 0x8C43; + const GLenum COMPARE_REF_TO_TEXTURE = 0x884E; + const GLenum RGBA32F = 0x8814; + const GLenum RGB32F = 0x8815; + const GLenum RGBA16F = 0x881A; + const GLenum RGB16F = 0x881B; + const GLenum VERTEX_ATTRIB_ARRAY_INTEGER = 0x88FD; + const GLenum MAX_ARRAY_TEXTURE_LAYERS = 0x88FF; + const GLenum MIN_PROGRAM_TEXEL_OFFSET = 0x8904; + const GLenum MAX_PROGRAM_TEXEL_OFFSET = 0x8905; + const GLenum MAX_VARYING_COMPONENTS = 0x8B4B; + const GLenum TEXTURE_2D_ARRAY = 0x8C1A; + const GLenum TEXTURE_BINDING_2D_ARRAY = 0x8C1D; + const GLenum R11F_G11F_B10F = 0x8C3A; + const GLenum UNSIGNED_INT_10F_11F_11F_REV = 0x8C3B; + const GLenum RGB9_E5 = 0x8C3D; + const GLenum UNSIGNED_INT_5_9_9_9_REV = 0x8C3E; + const GLenum TRANSFORM_FEEDBACK_BUFFER_MODE = 0x8C7F; + const GLenum MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS = 0x8C80; + const GLenum TRANSFORM_FEEDBACK_VARYINGS = 0x8C83; + const GLenum TRANSFORM_FEEDBACK_BUFFER_START = 0x8C84; + const GLenum TRANSFORM_FEEDBACK_BUFFER_SIZE = 0x8C85; + const GLenum TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN = 0x8C88; + const GLenum RASTERIZER_DISCARD = 0x8C89; + const GLenum MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS = 0x8C8A; + const GLenum MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS = 0x8C8B; + const GLenum INTERLEAVED_ATTRIBS = 0x8C8C; + const GLenum SEPARATE_ATTRIBS = 0x8C8D; + const GLenum TRANSFORM_FEEDBACK_BUFFER = 0x8C8E; + const GLenum TRANSFORM_FEEDBACK_BUFFER_BINDING = 0x8C8F; + const GLenum RGBA32UI = 0x8D70; + const GLenum RGB32UI = 0x8D71; + const GLenum RGBA16UI = 0x8D76; + const GLenum RGB16UI = 0x8D77; + const GLenum RGBA8UI = 0x8D7C; + const GLenum RGB8UI = 0x8D7D; + const GLenum RGBA32I = 0x8D82; + const GLenum RGB32I = 0x8D83; + const GLenum RGBA16I = 0x8D88; + const GLenum RGB16I = 0x8D89; + const GLenum RGBA8I = 0x8D8E; + const GLenum RGB8I = 0x8D8F; + const GLenum RED_INTEGER = 0x8D94; + const GLenum RGB_INTEGER = 0x8D98; + const GLenum RGBA_INTEGER = 0x8D99; + const GLenum SAMPLER_2D_ARRAY = 0x8DC1; + const GLenum SAMPLER_2D_ARRAY_SHADOW = 0x8DC4; + const GLenum SAMPLER_CUBE_SHADOW = 0x8DC5; + const GLenum UNSIGNED_INT_VEC2 = 0x8DC6; + const GLenum UNSIGNED_INT_VEC3 = 0x8DC7; + const GLenum UNSIGNED_INT_VEC4 = 0x8DC8; + const GLenum INT_SAMPLER_2D = 0x8DCA; + const GLenum INT_SAMPLER_3D = 0x8DCB; + const GLenum INT_SAMPLER_CUBE = 0x8DCC; + const GLenum INT_SAMPLER_2D_ARRAY = 0x8DCF; + const GLenum UNSIGNED_INT_SAMPLER_2D = 0x8DD2; + const GLenum UNSIGNED_INT_SAMPLER_3D = 0x8DD3; + const GLenum UNSIGNED_INT_SAMPLER_CUBE = 0x8DD4; + const GLenum UNSIGNED_INT_SAMPLER_2D_ARRAY = 0x8DD7; + const GLenum DEPTH_COMPONENT32F = 0x8CAC; + const GLenum DEPTH32F_STENCIL8 = 0x8CAD; + const GLenum FLOAT_32_UNSIGNED_INT_24_8_REV = 0x8DAD; + const GLenum FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING = 0x8210; + const GLenum FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE = 0x8211; + const GLenum FRAMEBUFFER_ATTACHMENT_RED_SIZE = 0x8212; + const GLenum FRAMEBUFFER_ATTACHMENT_GREEN_SIZE = 0x8213; + const GLenum FRAMEBUFFER_ATTACHMENT_BLUE_SIZE = 0x8214; + const GLenum FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE = 0x8215; + const GLenum FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE = 0x8216; + const GLenum FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE = 0x8217; + const GLenum FRAMEBUFFER_DEFAULT = 0x8218; + const GLenum DEPTH_STENCIL_ATTACHMENT = 0x821A; + const GLenum DEPTH_STENCIL = 0x84F9; + const GLenum UNSIGNED_INT_24_8 = 0x84FA; + const GLenum DEPTH24_STENCIL8 = 0x88F0; + const GLenum UNSIGNED_NORMALIZED = 0x8C17; + const GLenum DRAW_FRAMEBUFFER_BINDING = 0x8CA6; /* Same as FRAMEBUFFER_BINDING */ + const GLenum READ_FRAMEBUFFER = 0x8CA8; + const GLenum DRAW_FRAMEBUFFER = 0x8CA9; + const GLenum READ_FRAMEBUFFER_BINDING = 0x8CAA; + const GLenum RENDERBUFFER_SAMPLES = 0x8CAB; + const GLenum FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER = 0x8CD4; + const GLenum MAX_COLOR_ATTACHMENTS = 0x8CDF; + const GLenum COLOR_ATTACHMENT1 = 0x8CE1; + const GLenum COLOR_ATTACHMENT2 = 0x8CE2; + const GLenum COLOR_ATTACHMENT3 = 0x8CE3; + const GLenum COLOR_ATTACHMENT4 = 0x8CE4; + const GLenum COLOR_ATTACHMENT5 = 0x8CE5; + const GLenum COLOR_ATTACHMENT6 = 0x8CE6; + const GLenum COLOR_ATTACHMENT7 = 0x8CE7; + const GLenum COLOR_ATTACHMENT8 = 0x8CE8; + const GLenum COLOR_ATTACHMENT9 = 0x8CE9; + const GLenum COLOR_ATTACHMENT10 = 0x8CEA; + const GLenum COLOR_ATTACHMENT11 = 0x8CEB; + const GLenum COLOR_ATTACHMENT12 = 0x8CEC; + const GLenum COLOR_ATTACHMENT13 = 0x8CED; + const GLenum COLOR_ATTACHMENT14 = 0x8CEE; + const GLenum COLOR_ATTACHMENT15 = 0x8CEF; + const GLenum FRAMEBUFFER_INCOMPLETE_MULTISAMPLE = 0x8D56; + const GLenum MAX_SAMPLES = 0x8D57; + const GLenum HALF_FLOAT = 0x140B; + const GLenum RG = 0x8227; + const GLenum RG_INTEGER = 0x8228; + const GLenum R8 = 0x8229; + const GLenum RG8 = 0x822B; + const GLenum R16F = 0x822D; + const GLenum R32F = 0x822E; + const GLenum RG16F = 0x822F; + const GLenum RG32F = 0x8230; + const GLenum R8I = 0x8231; + const GLenum R8UI = 0x8232; + const GLenum R16I = 0x8233; + const GLenum R16UI = 0x8234; + const GLenum R32I = 0x8235; + const GLenum R32UI = 0x8236; + const GLenum RG8I = 0x8237; + const GLenum RG8UI = 0x8238; + const GLenum RG16I = 0x8239; + const GLenum RG16UI = 0x823A; + const GLenum RG32I = 0x823B; + const GLenum RG32UI = 0x823C; + const GLenum VERTEX_ARRAY_BINDING = 0x85B5; + const GLenum R8_SNORM = 0x8F94; + const GLenum RG8_SNORM = 0x8F95; + const GLenum RGB8_SNORM = 0x8F96; + const GLenum RGBA8_SNORM = 0x8F97; + const GLenum SIGNED_NORMALIZED = 0x8F9C; + const GLenum COPY_READ_BUFFER = 0x8F36; + const GLenum COPY_WRITE_BUFFER = 0x8F37; + const GLenum COPY_READ_BUFFER_BINDING = 0x8F36; /* Same as COPY_READ_BUFFER */ + const GLenum COPY_WRITE_BUFFER_BINDING = 0x8F37; /* Same as COPY_WRITE_BUFFER */ + const GLenum UNIFORM_BUFFER = 0x8A11; + const GLenum UNIFORM_BUFFER_BINDING = 0x8A28; + const GLenum UNIFORM_BUFFER_START = 0x8A29; + const GLenum UNIFORM_BUFFER_SIZE = 0x8A2A; + const GLenum MAX_VERTEX_UNIFORM_BLOCKS = 0x8A2B; + const GLenum MAX_FRAGMENT_UNIFORM_BLOCKS = 0x8A2D; + const GLenum MAX_COMBINED_UNIFORM_BLOCKS = 0x8A2E; + const GLenum MAX_UNIFORM_BUFFER_BINDINGS = 0x8A2F; + const GLenum MAX_UNIFORM_BLOCK_SIZE = 0x8A30; + const GLenum MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS = 0x8A31; + const GLenum MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS = 0x8A33; + const GLenum UNIFORM_BUFFER_OFFSET_ALIGNMENT = 0x8A34; + const GLenum ACTIVE_UNIFORM_BLOCKS = 0x8A36; + const GLenum UNIFORM_TYPE = 0x8A37; + const GLenum UNIFORM_SIZE = 0x8A38; + const GLenum UNIFORM_BLOCK_INDEX = 0x8A3A; + const GLenum UNIFORM_OFFSET = 0x8A3B; + const GLenum UNIFORM_ARRAY_STRIDE = 0x8A3C; + const GLenum UNIFORM_MATRIX_STRIDE = 0x8A3D; + const GLenum UNIFORM_IS_ROW_MAJOR = 0x8A3E; + const GLenum UNIFORM_BLOCK_BINDING = 0x8A3F; + const GLenum UNIFORM_BLOCK_DATA_SIZE = 0x8A40; + const GLenum UNIFORM_BLOCK_ACTIVE_UNIFORMS = 0x8A42; + const GLenum UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES = 0x8A43; + const GLenum UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER = 0x8A44; + const GLenum UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER = 0x8A46; + const GLenum INVALID_INDEX = 0xFFFFFFFF; + const GLenum MAX_VERTEX_OUTPUT_COMPONENTS = 0x9122; + const GLenum MAX_FRAGMENT_INPUT_COMPONENTS = 0x9125; + const GLenum MAX_SERVER_WAIT_TIMEOUT = 0x9111; + const GLenum OBJECT_TYPE = 0x9112; + const GLenum SYNC_CONDITION = 0x9113; + const GLenum SYNC_STATUS = 0x9114; + const GLenum SYNC_FLAGS = 0x9115; + const GLenum SYNC_FENCE = 0x9116; + const GLenum SYNC_GPU_COMMANDS_COMPLETE = 0x9117; + const GLenum UNSIGNALED = 0x9118; + const GLenum SIGNALED = 0x9119; + const GLenum ALREADY_SIGNALED = 0x911A; + const GLenum TIMEOUT_EXPIRED = 0x911B; + const GLenum CONDITION_SATISFIED = 0x911C; + const GLenum WAIT_FAILED = 0x911D; + const GLenum SYNC_FLUSH_COMMANDS_BIT = 0x00000001; + const GLenum VERTEX_ATTRIB_ARRAY_DIVISOR = 0x88FE; + const GLenum ANY_SAMPLES_PASSED = 0x8C2F; + const GLenum ANY_SAMPLES_PASSED_CONSERVATIVE = 0x8D6A; + const GLenum SAMPLER_BINDING = 0x8919; + const GLenum RGB10_A2UI = 0x906F; + const GLenum INT_2_10_10_10_REV = 0x8D9F; + const GLenum TRANSFORM_FEEDBACK = 0x8E22; + const GLenum TRANSFORM_FEEDBACK_PAUSED = 0x8E23; + const GLenum TRANSFORM_FEEDBACK_ACTIVE = 0x8E24; + const GLenum TRANSFORM_FEEDBACK_BINDING = 0x8E25; + const GLenum TEXTURE_IMMUTABLE_FORMAT = 0x912F; + const GLenum MAX_ELEMENT_INDEX = 0x8D6B; + const GLenum TEXTURE_IMMUTABLE_LEVELS = 0x82DF; + + const GLint64 TIMEOUT_IGNORED = -1; + + /* WebGL-specific enums */ + const GLenum MAX_CLIENT_WAIT_TIMEOUT_WEBGL = 0x9247; + + /* Buffer objects */ + // WebGL1: + void bufferData(GLenum target, GLsizeiptr size, GLenum usage); + void bufferData(GLenum target, ArrayBuffer? srcData, GLenum usage); + void bufferData(GLenum target, ArrayBufferView srcData, GLenum usage); + void bufferSubData(GLenum target, GLintptr dstByteOffset, BufferDataSource srcData); + // WebGL2: + void bufferData(GLenum target, ArrayBufferView srcData, GLenum usage, GLuint srcOffset, + optional GLuint length = 0); + void bufferSubData(GLenum target, GLintptr dstByteOffset, ArrayBufferView srcData, + GLuint srcOffset, optional GLuint length = 0); + + void copyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, + GLintptr writeOffset, GLsizeiptr size); + // MapBufferRange, in particular its read-only and write-only modes, + // can not be exposed safely to JavaScript. GetBufferSubData + // replaces it for the purpose of fetching data back from the GPU. + void getBufferSubData(GLenum target, GLintptr srcByteOffset, ArrayBufferView dstData, + optional GLuint dstOffset = 0, optional GLuint length = 0); + + /* Framebuffer objects */ + void blitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, + GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); + void framebufferTextureLayer(GLenum target, GLenum attachment, WebGLTexture? texture, GLint level, + GLint layer); + void invalidateFramebuffer(GLenum target, sequence attachments); + void invalidateSubFramebuffer(GLenum target, sequence attachments, + GLint x, GLint y, GLsizei width, GLsizei height); + void readBuffer(GLenum src); + + /* Renderbuffer objects */ + any getInternalformatParameter(GLenum target, GLenum internalformat, GLenum pname); + void renderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, + GLsizei width, GLsizei height); + + /* Texture objects */ + void texStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, + GLsizei height); + void texStorage3D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, + GLsizei height, GLsizei depth); + + // WebGL1 legacy entrypoints: + void texImage2D(GLenum target, GLint level, GLint internalformat, + GLsizei width, GLsizei height, GLint border, GLenum format, + GLenum type, ArrayBufferView? pixels); + void texImage2D(GLenum target, GLint level, GLint internalformat, + GLenum format, GLenum type, TexImageSource source); // May throw DOMException + + void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, + GLenum format, GLenum type, ArrayBufferView? pixels); + void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLenum format, GLenum type, TexImageSource source); // May throw DOMException + + // WebGL2 entrypoints: + void texImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, + GLint border, GLenum format, GLenum type, GLintptr pboOffset); + void texImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, + GLint border, GLenum format, GLenum type, + TexImageSource source); // May throw DOMException + void texImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, + GLint border, GLenum format, GLenum type, ArrayBufferView srcData, + GLuint srcOffset); + + void texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, + GLsizei depth, GLint border, GLenum format, GLenum type, GLintptr pboOffset); + void texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, + GLsizei depth, GLint border, GLenum format, GLenum type, + TexImageSource source); // May throw DOMException + void texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, + GLsizei depth, GLint border, GLenum format, GLenum type, ArrayBufferView? srcData); + void texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, + GLsizei depth, GLint border, GLenum format, GLenum type, ArrayBufferView srcData, + GLuint srcOffset); + + void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, + GLsizei height, GLenum format, GLenum type, GLintptr pboOffset); + void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, + GLsizei height, GLenum format, GLenum type, + TexImageSource source); // May throw DOMException + void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, + GLsizei height, GLenum format, GLenum type, ArrayBufferView srcData, + GLuint srcOffset); + + void texSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, + GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, + GLintptr pboOffset); + void texSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, + GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, + TexImageSource source); // May throw DOMException + void texSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, + GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, + ArrayBufferView? srcData, optional GLuint srcOffset = 0); + + void copyTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, + GLint x, GLint y, GLsizei width, GLsizei height); + + void compressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, + GLsizei height, GLint border, GLintptr offset); + void compressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, + GLsizei height, GLint border, ArrayBufferView srcData, + optional GLuint srcOffset = 0, optional GLuint srcLengthOverride = 0); + + void compressedTexImage3D(GLenum target, GLint level, GLenum internalformat, GLsizei width, + GLsizei height, GLsizei depth, GLint border, GLintptr offset); + void compressedTexImage3D(GLenum target, GLint level, GLenum internalformat, GLsizei width, + GLsizei height, GLsizei depth, GLint border, ArrayBufferView srcData, + optional GLuint srcOffset = 0, optional GLuint srcLengthOverride = 0); + + void compressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, GLenum format, GLintptr offset); + void compressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, GLenum format, + ArrayBufferView srcData, + optional GLuint srcOffset = 0, + optional GLuint srcLengthOverride = 0); + + void compressedTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, + GLenum format, GLintptr offset); + void compressedTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, + GLenum format, ArrayBufferView srcData, + optional GLuint srcOffset = 0, + optional GLuint srcLengthOverride = 0); + + /* Programs and shaders */ + [WebGLHandlesContextLoss] GLint getFragDataLocation(WebGLProgram program, DOMString name); + + /* Uniforms */ + void uniform1ui(WebGLUniformLocation? location, GLuint v0); + void uniform2ui(WebGLUniformLocation? location, GLuint v0, GLuint v1); + void uniform3ui(WebGLUniformLocation? location, GLuint v0, GLuint v1, GLuint v2); + void uniform4ui(WebGLUniformLocation? location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); + + void uniform1fv(WebGLUniformLocation? location, Float32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform2fv(WebGLUniformLocation? location, Float32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform3fv(WebGLUniformLocation? location, Float32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform4fv(WebGLUniformLocation? location, Float32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + + void uniform1iv(WebGLUniformLocation? location, Int32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform2iv(WebGLUniformLocation? location, Int32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform3iv(WebGLUniformLocation? location, Int32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform4iv(WebGLUniformLocation? location, Int32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + + void uniform1uiv(WebGLUniformLocation? location, Uint32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform2uiv(WebGLUniformLocation? location, Uint32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform3uiv(WebGLUniformLocation? location, Uint32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform4uiv(WebGLUniformLocation? location, Uint32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + + void uniformMatrix2fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + void uniformMatrix3x2fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + void uniformMatrix4x2fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + + void uniformMatrix2x3fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + void uniformMatrix3fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + void uniformMatrix4x3fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + + void uniformMatrix2x4fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + void uniformMatrix3x4fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + void uniformMatrix4fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + + /* Vertex attribs */ + void vertexAttribI4i(GLuint index, GLint x, GLint y, GLint z, GLint w); + void vertexAttribI4iv(GLuint index, Int32List values); + void vertexAttribI4ui(GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); + void vertexAttribI4uiv(GLuint index, Uint32List values); + void vertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); + + /* Writing to the drawing buffer */ + void vertexAttribDivisor(GLuint index, GLuint divisor); + void drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instanceCount); + void drawElementsInstanced(GLenum mode, GLsizei count, GLenum type, GLintptr offset, GLsizei instanceCount); + void drawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, GLintptr offset); + + /* Reading back pixels */ + // WebGL1: + void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, + ArrayBufferView? dstData); + // WebGL2: + void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, + GLintptr offset); + void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, + ArrayBufferView dstData, GLuint dstOffset); + + /* Multiple Render Targets */ + void drawBuffers(sequence buffers); + + void clearBufferfv(GLenum buffer, GLint drawbuffer, Float32List values, + optional GLuint srcOffset = 0); + void clearBufferiv(GLenum buffer, GLint drawbuffer, Int32List values, + optional GLuint srcOffset = 0); + void clearBufferuiv(GLenum buffer, GLint drawbuffer, Uint32List values, + optional GLuint srcOffset = 0); + + void clearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); + + /* Query Objects */ + WebGLQuery? createQuery(); + void deleteQuery(WebGLQuery? query); + [WebGLHandlesContextLoss] GLboolean isQuery(WebGLQuery? query); + void beginQuery(GLenum target, WebGLQuery query); + void endQuery(GLenum target); + WebGLQuery? getQuery(GLenum target, GLenum pname); + any getQueryParameter(WebGLQuery query, GLenum pname); + + /* Sampler Objects */ + WebGLSampler? createSampler(); + void deleteSampler(WebGLSampler? sampler); + [WebGLHandlesContextLoss] GLboolean isSampler(WebGLSampler? sampler); + void bindSampler(GLuint unit, WebGLSampler? sampler); + void samplerParameteri(WebGLSampler sampler, GLenum pname, GLint param); + void samplerParameterf(WebGLSampler sampler, GLenum pname, GLfloat param); + any getSamplerParameter(WebGLSampler sampler, GLenum pname); + + /* Sync objects */ + WebGLSync? fenceSync(GLenum condition, GLbitfield flags); + [WebGLHandlesContextLoss] GLboolean isSync(WebGLSync? sync); + void deleteSync(WebGLSync? sync); + GLenum clientWaitSync(WebGLSync sync, GLbitfield flags, GLuint64 timeout); + void waitSync(WebGLSync sync, GLbitfield flags, GLint64 timeout); + any getSyncParameter(WebGLSync sync, GLenum pname); + + /* Transform Feedback */ + WebGLTransformFeedback? createTransformFeedback(); + void deleteTransformFeedback(WebGLTransformFeedback? tf); + [WebGLHandlesContextLoss] GLboolean isTransformFeedback(WebGLTransformFeedback? tf); + void bindTransformFeedback (GLenum target, WebGLTransformFeedback? tf); + void beginTransformFeedback(GLenum primitiveMode); + void endTransformFeedback(); + void transformFeedbackVaryings(WebGLProgram program, sequence varyings, GLenum bufferMode); + WebGLActiveInfo? getTransformFeedbackVarying(WebGLProgram program, GLuint index); + void pauseTransformFeedback(); + void resumeTransformFeedback(); + + /* Uniform Buffer Objects and Transform Feedback Buffers */ + void bindBufferBase(GLenum target, GLuint index, WebGLBuffer? buffer); + void bindBufferRange(GLenum target, GLuint index, WebGLBuffer? buffer, GLintptr offset, GLsizeiptr size); + any getIndexedParameter(GLenum target, GLuint index); + sequence? getUniformIndices(WebGLProgram program, sequence uniformNames); + any getActiveUniforms(WebGLProgram program, sequence uniformIndices, GLenum pname); + GLuint getUniformBlockIndex(WebGLProgram program, DOMString uniformBlockName); + any getActiveUniformBlockParameter(WebGLProgram program, GLuint uniformBlockIndex, GLenum pname); + DOMString? getActiveUniformBlockName(WebGLProgram program, GLuint uniformBlockIndex); + void uniformBlockBinding(WebGLProgram program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); + + /* Vertex Array Objects */ + WebGLVertexArrayObject? createVertexArray(); + void deleteVertexArray(WebGLVertexArrayObject? vertexArray); + [WebGLHandlesContextLoss] GLboolean isVertexArray(WebGLVertexArrayObject? vertexArray); + void bindVertexArray(WebGLVertexArrayObject? array); +}; +WebGL2RenderingContextBase implements WebGLRenderingContextBase; + +interface WebGL2RenderingContext +{ +}; +WebGL2RenderingContext implements WebGL2RenderingContextBase; + + diff --git a/applications/generators/X3D/Makefile b/applications/generators/X3D/Makefile index 56f2c2d..c62f2ba 100644 --- a/applications/generators/X3D/Makefile +++ b/applications/generators/X3D/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/applications/generators/X3D CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/applications/generators/X3D/main.c b/applications/generators/X3D/main.c index 72e3670..0e788a8 100644 --- a/applications/generators/X3D/main.c +++ b/applications/generators/X3D/main.c @@ -145,9 +145,8 @@ X3DNode *BlankNode() u8 IsNDT(GF_List *NDTs, char *famName) { u32 i; - char *ndtName; for (i=0; ifamilly, "SFString")) { - fprintf(vrml_code, "\tp->%s.buffer = (char*) gf_malloc(sizeof(char) * %d);\n", bf->name, strlen(bf->def)+1); + fprintf(vrml_code, "\tp->%s.buffer = (char*) gf_malloc(sizeof(char) * %u);\n", bf->name, (u32) strlen(bf->def)+1); fprintf(vrml_code, "\tstrcpy(p->%s.buffer, \"%s\");\n", bf->name, bf->def); } @@ -809,7 +808,7 @@ void WriteNodeCode(GF_List *BNodes, FILE *vrml_code) store = CurrentLine; CurrentLine = token; GetNextToken(tok, " \""); - fprintf(vrml_code, "\tp->%s.vals[%d] = (char*)gf_malloc(sizeof(char) * %d);\n", bf->name, j, strlen(tok)+1); + fprintf(vrml_code, "\tp->%s.vals[%d] = (char*)gf_malloc(sizeof(char) * %u);\n", bf->name, j, (u32) strlen(tok)+1); fprintf(vrml_code, "\tstrcpy(p->%s.vals[%d], \"%s\");\n", bf->name, j, tok); j+=1; CurrentLine = store; @@ -912,10 +911,9 @@ void WriteNodeCode(GF_List *BNodes, FILE *vrml_code) static u32 IsNodeInTable(X3DNode *node, char *NDTName) { u32 i; - char *ndt; for (i=0; iNDT); i++) { - ndt = gf_list_get(node->NDT, i); + char *ndt = gf_list_get(node->NDT, i); if (!strcmp(ndt, NDTName)) return 1; } return 0; @@ -923,11 +921,9 @@ static u32 IsNodeInTable(X3DNode *node, char *NDTName) static u32 GetNDTCount(char *NDTName, GF_List *XNodes) { - u32 i, nodeCount; - X3DNode *n; - nodeCount = 0; + u32 i, nodeCount = 0; for (i=0; iname); for (i=0; iFields); i++) { - bf = gf_list_get(n->Fields, i); + X3DField *bf = gf_list_get(n->Fields, i); if (!i) { fprintf(f, " \"%s\"", bf->name); } else { @@ -1203,9 +1198,8 @@ void parse_profile(GF_List *nodes, FILE *prof) int main (int argc, char **argv) { - FILE *nodes, *pf; + FILE *nodes; GF_List *XNodes, *NDTs; - X3DNode *n; X3DField *bf; u32 nb_nodes, nb_imp; @@ -1222,6 +1216,7 @@ int main (int argc, char **argv) gf_fclose(nodes); if (argc>1) { + FILE *pf; pf = gf_fopen(argv[1], "rt"); if (!pf) fprintf(stdout, "Cannot open profile file %s\n", argv[1]); else { @@ -1255,7 +1250,7 @@ int main (int argc, char **argv) nb_imp = 0; //free nodes while (gf_list_count(XNodes)) { - n = gf_list_get(XNodes, 0); + X3DNode *n = gf_list_get(XNodes, 0); if (!n->skip_impl) nb_imp++; gf_list_rem(XNodes, 0); while (gf_list_count(n->NDT)) { diff --git a/applications/gpac/Info.plist b/applications/gpac/Info.plist new file mode 100644 index 0000000..45b0ed1 --- /dev/null +++ b/applications/gpac/Info.plist @@ -0,0 +1,16 @@ + + + + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleShortVersionString + 0.9.0 + CFBundleVersion + 1 + NSCameraUsageDescription + GPAC needs camera access to handle this URL + NSMicrophoneUsageDescription + GPAC needs microphone access to handle this URL + + diff --git a/applications/gpac/Makefile b/applications/gpac/Makefile new file mode 100644 index 0000000..a1bb8db --- /dev/null +++ b/applications/gpac/Makefile @@ -0,0 +1,65 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/applications/gpac + +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +LINKFLAGS=-L../../bin/gcc +ifeq ($(CONFIG_WIN32),yes) +EXE=.exe +PROG=gpac$(EXE) +LINKFLAGS+=$(UNICODEFLAGS) +else +EXT= +PROG=gpac +endif + +ifeq ($(STATICBUILD),yes) +##include static modules and other deps for libgpac +include ../../static.mak +LINKFLAGS+=$(shell pkg-config ../../gpac.pc --libs --static | sed 's/-lgpac //' ) +LINKFLAGS+= $(GPAC_SH_FLAGS) +LINKFLAGS+=$(EXTRALIBS) +else +LINKFLAGS+=-lgpac +ifeq ($(CONFIG_DARWIN),yes) +#LINKFLAGS+= -Wl,-rpath,'@loader_path' +else +LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath-link,../../bin/gcc +endif +endif + + +#common objs - insert after ../static if any to overwrite list of objects +OBJS= main.o + +SRCS := $(OBJS:.o=.c) + +all: $(PROG) + +$(PROG): $(OBJS) + $(CC) -o ../../bin/gcc/$@ $(OBJS) $(LINKFLAGS) $(LDFLAGS) + +clean: + rm -f $(OBJS) ../../bin/gcc/$(PROG) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/applications/gpac/main.c b/applications/gpac/main.c new file mode 100644 index 0000000..936b1b1 --- /dev/null +++ b/applications/gpac/main.c @@ -0,0 +1,4039 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / gpac application + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include + +static GF_SystemRTInfo rti; +static GF_FilterSession *session=NULL; +static u32 list_filters = 0; +static Bool dump_stats = GF_FALSE; +static Bool dump_graph = GF_FALSE; +static Bool print_filter_info = GF_FALSE; +static Bool print_meta_filters = GF_FALSE; +static Bool load_test_filters = GF_FALSE; +static s32 nb_loops = 0; +static s32 runfor = 0; +Bool runfor_exit = GF_FALSE; +Bool enable_prompt = GF_FALSE; +u32 enable_reports = 0; +char *report_filter = NULL; +Bool do_unit_tests = GF_FALSE; +static int alias_argc = 0; +static char **alias_argv = NULL; +static GF_List *args_used = NULL; +static GF_List *args_alloc = NULL; +static u32 gen_doc = 0; +static u32 help_flags = 0; + +//coverage for FileIO +static const char *make_fileio(const char *inargs, const char **out_arg, Bool is_input, GF_Err *e); +static void cleanup_file_io(); + +FILE *sidebar_md=NULL; +static FILE *helpout = NULL; + +static const char *auto_gen_md_warning = "\n"; + +//uncomment to check argument description matches our conventions - see filter.h +#define CHECK_DOC + +//the default set of separators +static char separator_set[7] = GF_FS_DEFAULT_SEPS; +#define SEP_LINK 5 +#define SEP_FRAG 2 +#define SEP_LIST 3 + +static Bool print_filters(int argc, char **argv, GF_FilterSession *session, GF_SysArgMode argmode); +static void dump_all_props(void); +static void dump_all_colors(void); +static void dump_all_codec(GF_FilterSession *session); +static void write_filters_options(GF_FilterSession *fsess); +static void write_core_options(); +static void write_file_extensions(); +static int gpac_make_lang(char *filename); +static Bool gpac_expand_alias(int argc, char **argv); +static u32 gpac_unit_tests(GF_MemTrackerType mem_track); + +static Bool revert_cache_file(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info); + + + +#ifndef GPAC_DISABLE_DOC +const char *gpac_doc = +"# General\n" +"Filters are configurable processing units consuming and producing data packets. These packets are carried " +"between filters through a data channel called __pid__. A PID is in charge of allocating/tracking data packets, " +"and passing the packets to the destination filter(s). A filter output PID may be connected to zero or more filters. " +"This fan-out is handled internally by GPAC (no such thing as a tee filter in GPAC).\n" +"Note: When a PID cannot be connected to any filter, a warning is thrown and all packets dispatched on " +"this PID will be destroyed. The session may however still run, unless [-full-link](CORE) is set.\n" +" \nEach output PID carries a set of properties describing the data it delivers (eg __width__, __height__, __codec__, ...). Properties " +"can be built-in (identified by a 4 character code **abcd**, see [properties (-h props)](filters_properties) ), or user-defined (identified by a string). Each PID tracks " +"its properties changes and triggers filter reconfiguration during packet processing. This allows the filter chain to be " +"reconfigured at run time, potentially reloading part of the chain (eg unload a video decoder when switching from compressed " +"to uncompressed sources).\n" +" \nEach filter exposes one or more sets of capabilities, called __capability bundle__, which are property type and values " +"that must be matched or excluded by connecting PIDs.\n" +" \nEach filter exposes a set of argument to configure itself, using property types and values described as strings formated with " +"separators. This help is given with default separator sets `:=#,@` to specify filters, properties and options. Use [-seps](GPAC) to change them.\n" +"# Property format\n" +"- boolean: formatted as `yes`|`true`|`1` or `no`|`false`|`0`\n" +"- enumeration (for filter arguments only): must use the syntax given in the argument description, otherwise value `0` (first in enum) is assumed.\n" +"- 1-dimension (numbers, floats, ints...): formatted as `value[unit]`, where `unit` can be `k`|`K` (x1000) or `m`|`M` (x1000000) or `g`|`G` (x1000000000). " +"For such properties, value `+I` means maximum possible value, `-I` minimum possible value.\n" +"- fraction: formatted as `num/den` or `num-den` or `num`, in which case the denominator is 1 if `num` is an integer, or 1000000 if `num` is a floating-point value.\n" +"- unsigned 32 bit integer: formated as number or hexadecimal using the format `0xAABBCCDD`.\n" +"- N-dimension (vectors): formatted as `DIM1xDIM2[xDIM3[xDIM4]]` values, without unit multiplier.\n" +"- string: formatted as:\n" +" - `value`: copies value to string.\n" +" - `file@FILE`: load string from local `FILE` (opened in binary mode).\n" +" - `bxml@FILE`: binarize XML from local `FILE` and set property type to data - see https://wiki.gpac.io/NHML-Format.\n" +"- data: formatted as:\n" +" - `size@address`: constant data block, not internally copied; `size` gives the size of the block, `address` the data pointer.\n" +" - `0xBYTESTRING`: data block specified in hexadecimal, internally copied.\n" +" - `file@FILE`: load data from local `FILE` (opened in binary mode).\n" +" - `bxml@FILE`: binarize XML from local `FILE` - see https://wiki.gpac.io/NHML-Format.\n" +"- pointer: are formatted as `address` giving the pointer address (32 or 64 bit depending on platforms).\n" +"- string lists: formatted as `val1,val2[,...]`. Each value can also use `file@FILE` syntax.\n" +"- integer lists: formatted as `val1,val2[,...]`\n" +"Note: The special characters in property formats (0x,/,-,+I,-I,x) cannot be configured.\n" +"# Filter declaration [__FILTER__]\n" +"## Generic declaration\n" +"Each filter is declared by its name, with optional filter arguments appended as a list of colon-separated `name=value` pairs. Additional syntax is provided for:\n" +"- boolean: `value` can be omitted, defaulting to `true` (eg `:noedit`). Using `!` before the name negates the result (eg `:!moof_first`)\n" +"- enumerations: name can be omitted (eg `:disp=pbo` is equivalent to `:pbo`), provided that filter developers pay attention to not reuse enumeration names in the same filter.\n" +"\n \n" +"When string parameters are used (eg URLs), it is recommended to escape the string using the keyword `gpac`. \n" +"EX filter:ARG=http://foo/bar?yes:gpac:opt=VAL\n" +"This will properly extract the URL.\n" +"EX filter:ARG=http://foo/bar?yes:opt=VAL\n" +"This will fail to extract it and keep `:opt=VAL` as part of the URL.\n" +"The escape mechanism is not needed for local source, for which file existence is probed during argument parsing. " +"It is also not needed for builtin procotol handlers (`avin://`, `video://`, `audio://`, `pipe://`)\n" +"For `tcp://` and `udp://` protocols, the escape is not needed if a trailing `/` is appended after the port number.\n" +"EX -i tcp://127.0.0.1:1234:OPT\n" +"This will fail to extract the URL and options.\n" +"EX -i tcp://127.0.0.1:1234/:OPT\n" +"This will extract the URL and options.\n" +"Note: one trick to avoid the escape sequence is to declare the URLs option at the end, eg `f1:opt1=foo:url=http://bar`, provided you have only one URL parameter to specify on the filter.\n" +"\n" +"It is possible to disable option parsing (for string options) by duplicating the seperator.\n" +"EX filter::opt1=UDP://IP:PORT/:someopt=VAL::opt2=VAL2\n" +"This will pass `UDP://IP:PORT/:someopt=VAL` to `opt1` without inspecting it, and `VAL2` to `opt2`.\n" +" \n" +"A filter may be assigned a name (for inspection purposes) using `:N=name` option. This name is not used in link resolution and may be changed at runtime by the filter instance.\n" +"## Source and Sink filters\n" +"Source and sink filters do not need to be addressed by the filter name, specifying `src=` or `dst=` instead is enough. " +"You can also use the syntax `-src URL` or `-i URL` for sources and `-dst URL` or `-o URL` for destination, this allows prompt completion in shells.\n" +"EX \"src=file.mp4\" or \"-src file.mp4\" or \"-i file.mp4\"\n" +"This will find a filter (for example `fin`) able to load `file.mp4`. The same result can be achieved by using `fin:src=file.mp4`.\n" +"EX \"dst=dump.yuv\" or \"-dst dump.yuv\" or \"-o dump.yuv\"\n" +"This will dump the video content in `dump.yuv`. The same result can be achieved by using `fout:dst=dump.yuv`.\n" +"\n" +"Specific source or sink filters may also be specified using `filterName:src=URL` or `filterName:dst=URL`.\n" +"\n" +"The `src=` and `dst=` syntaxes can also be used in alias for dynamic argument cloning (see `gpac -hx alias`).\n" +"## Forcing specific filters\n" +"There is a special option called `gfreg` which allows specifying preferred filters to use when handling URLs.\n" +"EX src=file.mp4:gfreg=ffdmx,ffdec\n" +"This will use __ffdmx__ to read `file.mp4` and __ffdec__ to decode it.\n" +"This can be used to test a specific filter when alternate filter chains are possible.\n" +"## Specifying encoders and decoders\n" +"By default filters chain will be resolved without any decoding/encoding if the destination accepts the desired format. " +"Otherwise, decoders/encoders will be dynamically loaded to perform the conversion, unless dynamic resolution is disabled. " +"There is a special shorcut filter name for encoders `enc` allowing to match a filter providing the desired encoding. " +"The parameters for `enc` are:\n" +"- c=NAME: identifes the desired codec. `NAME` can be the gpac codec name or the encoder instance for ffmpeg/others\n" +"- b=UINT, rate=UINT, bitrate=UINT: indicates the bitrate in bits per second\n" +"- g=UINT, gop=UINT: indicates the GOP size in frames\n" +"- pfmt=NAME: indicates the target pixel format name (see [properties (-h props)](filters_properties) ) of the source, if supported by codec\n" +"- all_intra=BOOL: indicates all frames should be intra frames, if supported by codec\n" +"\n \nOther options will be passed to the filter if it accepts generic argument parsing (as is the case for ffmpeg).\n" +"EX src=dump.yuv:size=320x240:fps=25 enc:c=avc:b=150000:g=50:cgop=true:fast=true dst=raw.264\n" +"This creates a 25 fps AVC at 175kbps with a gop duration of 2 seconds, using closed gop and fast encoding settings for ffmpeg.\n" +"\n" +"The inverse operation (forcing a decode to happen) is possible using the __reframer__ filter.\n" +"EX src=file.mp4 reframer:raw @ -o null\n" +"This will force decoding media from `file.mp4` and trash (send to `null`) the result (doing a decoder benchmark for example).\n" +"\n" +"When a filter uses an option defined as a string using the same separator character as gpac, you can either " +"modify the set of separators, or escape the seperator by duplicating it. The options enclosed by duplicated " +"separator are not parsed. This is mostly used for meta filters, such as ffmpeg, to pass options to subfilters " +"such as libx264 (cf `x264opts` parameter).\n" +"EX f:a=foo:b=bar\n" +"This will set option `a` to `foo` and option `b` to `bar` on the filter.\n" +"EX f::a=foo:b=bar\n" +"This will set option `a` to `foo:b=bar` on the filter.\n" +"EX f:a=foo::b=bar:c::d=fun\n" +"This will set option `a` to `foo`, `b` to `bar:c` and the option `d` to `fun` on the filter.\n" +"# Expliciting links between filters [__LINK__]\n" +"## Quick links\n" +"Link between filters may be manually specified. The syntax is an `@` character optionaly followed by an integer (0 if omitted). " +"This indicates which filter previously specified at prompt should be link to the next filter listed. The optional integer is a 0-based index to the previous filter declarations, 0 indicating the previous filter declaration, 1 the one before the previous delaration, ...).\n" +"Only the last link directive occuring before a filter is used to setup links for that filter.\n" +"EX fA fB @1 fC\n" +"This indicates to direct `fA` outputs to `fC`.\n" +"EX fA fB @1 @0 fC\n" +"This indicates to direct `fB` outputs to `fC`, `@1` is ignored.\n" +"\nIf no link directives are given, the links will be dynamically solved to fullfill as many connections as possible (__see below__).\n" +"Warning: This means that `fA fB fC` and `fA fB @ fC` will likely not give the same result.\n" +"\n" +"## Complex links\n" +"The link directive is just a quick shortcut to set the following arguments:\n" +"- FID=name, which assigns an identifier to the filter\n" +"- SID=name1[,name2...], which set a list of filter identifiers , or __sourceIDs__, restricting the list of possible inputs for a filter.\n" +"\n" +"EX fA fB @1 fC\n" +"This is equivalent to `fA:FID=1 fB fC:SID=1`.\n" +"Link directives specify which source a filter can accept connections from. They do not specifiy which destination a filter can connect to.\n" +"EX fA:FID=1 fB fC:SID=1\n" +"This indicates that `fC` only accpets input from `fA`, but `fB` might accept inputs from `fA`.\n" +"EX fA:FID=1 fB:FID=2 fC:SID=1 fD:SID=1,2\n" +"This indicates that `fD` only accepts input from `fA` and `fB` and `fC` only from `fA`\n" +"Note: A filter with sourceID set cannot get input from filters with no IDs.\n" +"A sourceID name can be further extended using fragment identifier (`#` by default):\n" +"- name#PIDNAME: accepts only PID(s) with name `PIDNAME`\n" +"- name#TYPE: accepts only PIDs of matching media type. TYPE can be `audio`, `video`, `scene`, `text`, `font`, `meta`\n" +"- name#TYPEN: accepts only `N`th PID of matching type from source\n" +"- name#P4CC=VAL: accepts only PIDs with property matching `VAL`.\n" +"- name#PName=VAL: same as above, using the builtin name corresponding to the property.\n" +"- name#AnyName=VAL: same as above, using the name of a non built-in property.\n" +"- name#Name=OtherPropName: compares the value with the value of another property of the PID. The matching will fail if the value to compare to is not present or different from the value to check. The property to compare with shall be a built-in property.\n" +"If the property is not defined on the PID, the property is matched. Otherwise, its value is checked against the given value.\n" +"\n" +"The following modifiers for comparisons are allowed (for both `P4CC=`, `PName=` and `AnyName=`):\n" +"- name#P4CC=!VAL: accepts only PIDs with property NOT matching `VAL`.\n" +"- name#P4CC-VAL: accepts only PIDs with property strictly less than `VAL` (only for 1-dimension number properties).\n" +"- name#P4CC+VAL: accepts only PIDs with property strictly greater than `VAL` (only for 1-dimension number properties).\n" +"\n" +"A sourceID name can also use wildcard or be empty to match a property regardless of the source filter.\n" +"EX fA fB:SID=*#ServiceID=2\n" +"EX fA fB:SID=#ServiceID=2\n" +"This indicates to match connection between `fA` and `fB` only for PIDs with a `ServiceID` property of `2`.\n" +"These extensions also work with the __LINK__ `@` shortcut.\n" +"EX fA fB @1#video fC\n" +"This indicates to direct `fA` video outputs to `fC`.\n" +"EX src=img.heif @#ItemID=200 vout\n" +"This indicates to connect to `vout` only PIDs with `ItemID` property equal to `200`.\n" +"EX src=vid.mp4 @#PID=1 vout\n" +"This indicates to connect to `vout` only PIDs with `ID` property equal to `1`.\n" +"EX src=vid.mp4 @#Width=640 vout\n" +"This indicates to connect to `vout` only PIDs with `Width` property equal to `640`.\n" +"EX src=vid.mp4 @#Width-640 vout\n" +"This indicates to connect to `vout` only PIDs with `Width` property less than `640`\n" +"EX src=vid.mp4 @#ID=ItemID#ItemNumber=1 vout\n" +"This will connect to `vout` only PID with an ID property equal to ItemID property (keep items, discard tracks) and an Item number of 1 (first item).\n" +"\n" +"Multiple fragment can be specified to check for multiple PID properties.\n" +"EX src=vid.mp4 @#Width=640#Height+380 vout\n" +"This indicates to connect to `vout` only PIDs with `Width` property equal to `640` and `Height` greater than `380`.\n" +"\n" +"Warning: If a filter PID gets connected to a loaded filter, no further dynamic link resolution will " +"be done to connect it to other filters, unless sourceIDs are set. Link directives should be carfully setup.\n" +"EX src=file.mp4 @ reframer dst=dump.mp4\n" +"This will link src `file.mp4` PID (type __file__) to dst `dump.mp4`filter (type `file`) because dst has no sourceID and therefore will " +"accept input from src. Since the PID is connected, the filter engine will not try to solve " +"a link between src and `reframer`. The result is a direct copy of the source file, `reframer` being unused.\n" +"EX src=file.mp4 reframer @ dst=dump.mp4\n" +"This will force dst to accept only from reframer, a muxer will be loaded to solve this link, and " +"src PID will be linked to `reframer` (no source ID), loading a demuxer to solve the link. The result is a complete remux of the source file.\n" +"# Arguments inheriting\n" +"Unless explicitly disabled (see [-max-chain](CORE)), the filter engine will resolve implicit or explicit (__LINK__) connections " +"between filters and will allocate any filter chain required to connect the filters. " +"In doing so, it loads new filters with arguments inherited from both the source and the destination.\n" +"EX src=file.mp4:OPT dst=file.aac dst=file.264\n" +"This will pass the `:OPT` to all filters loaded between the source and the two destinations.\n" +"EX src=file.mp4 dst=file.aac:OPT dst=file.264\n" +"This will pass the `:OPT` to all filters loaded between the source and the file.aac destination.\n" +"Note: the destination arguments inherited are the arguments placed **AFTER** the `dst=` option.\n" +"EX src=file.mp4 fout:OPTFOO:dst=file.aac:OPTBAR\n" +"This will pass the `:OPTBAR` to all filters loaded between `file.mp4` source and `file.aac` destination, but not `OPTFOO`.\n" +"Arguments inheriting can be stopped by using the keyword `gfloc`: arguments after the keyword will not be inherited.\n" +"EX src=file.mp4 dst=file.aac:OPTFOO:gfloc:OPTBAR dst=file.264\n" +"This will pass the `:OPTFOO` to all filters loaded between `file.mp4`source and `file.aac` destination, but not `OPTBAR`\n" +"Arguments are by default tracked to check if they were used by the filter chain, and a warning is thrown if this is not the case.\n" +"It may be usefull to specify arguments which may not be consumed depending on the graph resolution; the specific keyword `gfopt` indicates that arguments after the keyword will not be tracked.\n" +"EX src=file.mp4 dst=file.aac:OPTFOO:gfopt:OPTBAR dst=file.264\n" +"This will warn if `OPTFOO` is not consumed, but will not track `OPTBAR`.\n" +"# URL templating\n" +"Destination URLs can be templated using the same mechanism as MPEG-DASH, where `$KEYWORD$` is replaced in the template with the " +"resolved value and `$KEYWORD%%0Nd$` is replaced in the template with the resolved integer, padded with N zeros if needed. " +"`$$` is an escape for $\n" +"`KEYWORD` is **case sensitive**, and may be present multiple times in the string. Supported `KEYWORD` are:\n" +"- num: replaced by file number if defined, 0 otherwise\n" +"- PID: ID of the source PID\n" +"- URL: URL of source file\n" +"- File: path on disk for source file\n" +"- p4cc=ABCD: uses PID property with 4CC value `ABCD`\n" +"- pname=VAL: uses PID property with name `VAL`\n" +"- OTHER: locates property 4CC for the given name, or property name if no 4CC matches.\n" +"\n \nTemplating can be useful when encoding several qualities in one pass.\n" +"EX src=dump.yuv:size=640x360 vcrop:wnd=0x0x320x180 enc:c=avc:b=1M @2 enc:c=avc:b=750k dst=dump_$CropOrigin$x$Width$x$Height$.264:clone\n" +"This will create a croped version of the source, encoded in AVC at 1M, and a full version of the content in AVC at 750k. " +"Outputs will be `dump_0x0x320x180.264` for the croped version and `dump_0x0x640x360.264` for the non-croped one.\n" +"# Cloning filters\n" +"When a filter accepts a single connection and has a connected input, it is no longer available for dynamic resolution. " +"There may be cases where this behaviour is undesired. Take a HEIF file with N items and do:\n" +"EX src=img.heif dst=dump_$ItemID$.jpg\n" +"In this case, only one item (likely the first declared in the file) will connect to the destination.\n" +"Other items will not be connected since the destination only accepts one input PID.\n" +"There is a special option `clone` allowing destination filters (**and only them**) to be cloned with the same arguments:\n" +"EX src=img.heif dst=dump_$ItemID$.jpg:clone\n" +"In this case, the destination will be cloned for each item, and all will be exported to different JPEGs thanks to URL templating.\n" +"# Templating filter chains\n" +"There can be cases where the number of desired outputs depends on the source content, for example dumping a multiplex of N services into N files. When the destination involves multiplexing the input PIDs, the `:clone`option is not enough since the muxer will always accept the input PIDs.\n" +"To handle this, it is possible to use a PID property name in the sourceID of a filter with the value `*` or an empty value. In this case, whenever a new PID with a new value for the property is found, the filter with such sourceID will be dynamically cloned.\n" +"Warning: This feature should only be called with a single property set to `*` per source ID, results are undefined otherwise.\n" +"EX src=source.ts dst=file_$ServiceID$.mp4:SID=*#ServiceID=*\n" +"EX src=source.ts dst=file_$ServiceID$.mp4:SID=#ServiceID=\n" +"In this case, each new `ServiceID` value found when connecting PIDs to the destination will create a new destination file.\n" +"# Assigning PID properties\n" +"It is possible to define properties on output PIDs that will be declared by a filter. This allows tagging parts of the " +"graph with different properties than other parts (for example `ServiceID`). " +"The syntax is the same as filter option, and uses the fragment separator to identify properties, eg `#Name=Value`.\n" +"This sets output PIDs property (4cc, built-in name or any name) to the given value. Value can be omitted for booleans " +"(defaults to true, eg `:#Alpha`).\n" +"Non built-in properties are parsed as follows:\n" +"- `file@FOO` will be declared as string with a value set to the content of `FOO`.\n" +"- `bxml@FOO` will be declared as data with a value set to the binarized content of `FOO`.\n" +"- `FOO` will be declared as string with a value set to `FOO`.\n" +"- `TYPE@FOO` will be parsed according to `TYPE`. If the type is not recognized, the entire value is copied as string. See `gpac -h props` for defined types.\n" + +"Warning: Properties are not filtered and override the properties of the filter's output PIDs, be carefull not to break " +"the session by overriding core properties such as width/height/samplerate/... !\n" +"EX -i v1.mp4:#ServiceID=4 -i v2.mp4:#ServiceID=2 -o dump.ts\n" +"This will mux the streams in `dump.ts`, using `ServiceID` 4 for PIDs from `v1.mp4` and `ServiceID` 2 for PIDs from `v2.mp4`.\n" +"\n" +"PID properties may be conditionally assigned by checking other PID properties. The syntax uses paranthesis (not configurable) after the property assignment sign:\n" +"`#Prop=(CP=CV)VAL`\n" +"This will assign PID property `Prop` to `VAL` for PIDs with property `CP` equal to `CV`.\n" +"`#Prop=(CP=CV)VAL,(CP2=CV2)VAL2`\n" +"This will assign PID property `Prop` to `VAL` for PIDs with property `CP` equal to `CV`, and to `VAL2` for PIDs with property `CP2` equal to `CV2`.\n" +"`#Prop=(CP=CV)(CP2=CV2)VAL`\n" +"This will assign PID property `Prop` to `VAL` for PIDs with property `CP` equal to `CV` and property `CP2` equal to `CV2`.\n" +"`#Prop=(CP=CV)VAL,()DEFAULT`\n" +"This will assign PID property `Prop` to `VAL` for PIDs with property `CP` equal to `CV`, or to `DEFAULT` for other PIDs.\n" +"The condition syntax is the same as source ID fragment syntax.\n" +"Note: When set, the default value (empty condition) always matches the PID, therefore it should be placed last in the list of conditions.\n" +"EX gpac -i source.mp4:#MyProp=(audio)\"Super Audio\",(video)\"Super Video\"\n" +"This will assign property `MyProp` to `Super Audio` for audio PIDs and to `Super Video` for video PIDs.\n" +"EX gpac -i source.mp4:#MyProp=(audio1)\"Super Audio\"\n" +"This will assign property `MyProp` to `Super Audio` for first audio PID declared.\n" +"EX gpac -i source.mp4:#MyProp=(Width+1280)HD\n" +"This will assign property `MyProp` to `HD` for PIDs with property `Width` greater than 1280.\n" +"# Using option files\n" +"It is possible to use a file to define options of a filter, by specifying the target file name as an option without value, i.e. `:myopts.txt`.\n" +"Warning: Only local files are allowed.\n" +"An option file is a simple text file containing one or more options or PID properties on one or more lines.\n" +"A line begining with \"//\" is a comment and is ignored.\n" +"Options in an option file may point to other option files, with a maximum redirection level of 5.\n" +"An option file declaration (`filter:myopts.txt`) follows the same inheritance rules as regular options.\n" +"EX src=source.mp4:myopts.txt:foo=bar dst\n" +"Any filter loaded between `source.mp4` and `dst` will inherit both `myopts.txt` and `foo` options and will resolve options and PID properties given in `myopts.txt`.\n" +"# Specific filter options\n" +"Some specific keywords are replaced when processing filter options.\n" +"Warning: These keywords do not apply to PID properties. Multiple keywords cannot be defined for a single option.\n" +"Defined keywords:\n" +"- $GSHARE: replaced by system path to GPAC shared directory (e.g. /usr/share)\n" +"- $GJS: replaced by the first path specified by global config option [-js-dirs](CORE) that contains the file name following the macro, e.g. $GJS/source.js\n" +"- $GLANG: replaced by the global config language option [-lang](CORE)\n" +"- $GUA: replaced by the global config user agent option [-user-agent](CORE)\n" +"- $GINC(init_val[,inc]): replaced by `init_val` and increment `init_val` by `inc` (positive or negative number, 1 if not specified) each time a new filter using this string is created.\n" +"\n" +"The $GINC construct can be used to dynamically assign numbers in filter chains:\n" +"EX gpac -i source.ts tssplit @#ServiceID= -o dump_$GINC(10,2).ts\n" +"This will dump first service in dump_10.ts, second service in dump_12.ts, etc...\n" +"# External filters\n" +"GPAC comes with a set of built-in filters in libgpac. It may also load external filters in dynamic libraries, located in " +"folders listed in [-mod-dirs](CORE) option. The files shall be named `gf_*` and shall export" +" a single function returning a filter register - see [libgpac documentation](https://doxygen.gpac.io/) for more details.\n" +"\n"; +#endif + +static void gpac_filter_help(void) +{ + gf_sys_format_help(helpout, help_flags, +"Usage: gpac [options] FILTER [LINK] FILTER [...] \n" +#ifndef GPAC_DISABLE_DOC + "%s", gf_sys_localized("gpac", "doc", gpac_doc) +#else + "GPAC compiled without built-in doc.\n" +#endif + ); + +} + +#include +#include +#include +#include +#include + +static void gpac_modules_help(void) +{ + u32 i; + gf_sys_format_help(helpout, help_flags, "Available modules:\n"); + for (i=0; i= GF_ARGMODE_EXPERT) { + +#ifndef GPAC_DISABLE_DOC + gf_sys_format_help(helpout, help_flags, "%s", gf_sys_localized("gpac", "alias", gpac_alias) ); +#else + gf_sys_format_help(helpout, help_flags, "%s", "GPAC compiled without built-in doc.\n"); +#endif + if (argmode == GF_ARGMODE_EXPERT) { + return; + } + } + + count = gf_opts_get_key_count("gpac.alias"); + if (count) { + if (argmode < GF_ARGMODE_EXPERT) { + gf_sys_format_help(helpout, help_flags, "Available aliases (use 'gpac -hx alias' for more info on aliases):\n"); + } else { + gf_sys_format_help(helpout, help_flags, "Available aliases:\n"); + } + for (i=0; i=GF_ARGMODE_ADVANCED) + gf_sys_format_help(helpout, help_flags, " (%s)", alias_value); + + if (alias_doc) + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_OPT_DESC, ": %s", gf_sys_localized("gpac", "aliasdoc", alias_doc)); + else if (argmodeflags & GF_ARG_HINT_EXPERT)) continue; + if ((argmode==GF_ARGMODE_ADVANCED) && !(arg->flags & GF_ARG_HINT_ADVANCED)) continue; + else if ((argmode==GF_ARGMODE_BASE) && (arg->flags & (GF_ARG_HINT_ADVANCED| GF_ARG_HINT_EXPERT)) ) continue; + } + + gf_sys_print_arg(helpout, help_flags, arg, "gpac"); + } + + if (argmode>=GF_ARGMODE_ADVANCED) { + gf_sys_format_help(helpout, help_flags, "\n \nThe following libgpac core options allow customizing the filter session:\n \n" ); + gf_sys_print_core_help(helpout, help_flags, argmode, GF_ARG_SUBSYS_FILTERS); + } + + if (argmode==GF_ARGMODE_BASE) { + if ( gf_opts_get_key_count("gpac.aliasdoc")) { + gf_sys_format_help(helpout, help_flags, "\n"); + gpac_alias_help(GF_ARGMODE_BASE); + } + + gf_sys_format_help(helpout, help_flags, "\ngpac - GPAC command line filter engine - version %s\n%s\n", gf_gpac_version(), gf_gpac_copyright_cite() ); + } +} + +#ifndef GPAC_DISABLE_DOC +const char *gpac_config = +{ +"# Configuration file\n" +"GPAC uses a configuration file to modify default options of libgpac and filters. This configuration file is located in `$HOME/.gpac/GPAC.cfg`.\n" +"Applications in GPAC can also specify a different configuration file through the [-p](GPAC) option to indicate a profile. " +"This allows different configurations for different usages and simplifies command line typing.\n" +"EX gpac -p=foo []\n" +"This will load configuration from $HOME/.gpac/foo/GPAC.cfg, creating it if needed.\n" +"The reserved name `0` is used to disable configuration file writing.\n" +"By default the configuration file only holds a few system specific options and directories. It is possible to serialize " +"the entire set of options to the configuration file, using [-wc](GPAC) [-wf](GPAC). This should be avoided as the resulting " +"configuration file size will be quite large, hence larger memory usage for the applications.\n" +"The options specified in the configuration file may be overridden by the values in __restrict.cfg__ file located in GPAC share system directory (e.g. /usr/share/gpac), " +"if present; this allows enforcing system-wide configuration values.\n" +"Note: The methods describe in this section apply to any application in GPAC transferring their arguments " +"to libgpac. This is the case for __gpac__, __MP4Box__, __MP4Client/Osmo4__.\n" +"# Core options\n" +"The options from libgpac core can also be assigned though the config file from section __core__ using " +"option name **without initial dash** as key name.\n" +"EX [core]threads=2\n" +"Setting this in the config file is equivalent to using `-threads=2`.\n" +"The options specified at prompt overrides the value of the config file.\n" +"# Filter options in configuration\n" +"It is possible to alter the default value of a filter option by modifing the configuration file. Filter __foo__ options are stored in section " +"`[filter@foo]`, using option name and value as key-value pair. Options specified through the configuration file do not take precedence over " +"options specified at prompt or through alias.\n" +"EX [filter@rtpin]interleave=yes\n" +"This will force the rtp input filter to always request RTP over RTSP by default.\n" +"To generate a configuration file with all filters options serialized, use [-wf](GPAC).\n" +"# Global filter options\n" +"It is possible to specify options global to multiple filters using `--OPTNAME=VAL`. Global options do not override filter options but " +"take precedence over options loaded from configuration file.\n" +"This will set option `OPTNAME`, when present, to `VAL` in any loaded filter.\n" +"EX --buffer=100 -i file vout aout\n" +"This is equivalent to specifying `vout:buffer=100 aout:buffer=100`.\n" +"EX --buffer=100 -i file vout aout:buffer=10\n" +"This is equivalent to specifying `vout:buffer=100 aout:buffer=10`.\n" +"Warning: This syntax only applies to regular filter options. It cannot be used with builtin shortcuts (gfreg, enc, ...).\n" +"Meta-filter options can be set in the same way using the syntax `-+OPT_NAME=VAL`.\n" +"EX -+profile=Baseline -i file.cmp -o dump.264\n" +"This is equivalent to specifying `-o dump.264:profile=Baseline`.\n" +" \n" +"For both syntax, it is possible to specify the filter registry name of the option, using `--FNAME@OPTNAME=VAL`.\n" +"In this case the option will only be set for filters which are instances of registry FNAME. This is used when several registries use same option names.\n" +"EX --flist@timescale=100 -i plist1 -i plist2 -o live.mpd\n" +"This will set the timescale option on the playlists filters but not on the dasher filter.\n" +}; +#endif + +static void gpac_config_help() +{ + +#ifndef GPAC_DISABLE_DOC + gf_sys_format_help(helpout, help_flags, "%s", gf_sys_localized("gpac", "config", gpac_config) ); +#else + gf_sys_format_help(helpout, help_flags, "%s", "GPAC compiled without built-in doc.\n"); +#endif +} + + +static Bool logs_to_file=GF_FALSE; + +#define DEF_LOG_ENTRIES 10 + +struct _logentry +{ + u32 tool, level; + u32 nb_repeat; + u64 clock; + char *szMsg; +} *static_logs; +static u32 nb_log_entries = DEF_LOG_ENTRIES; + +static u32 log_write=0; + +static char *log_buf = NULL; +static u32 log_buf_size=0; +static void gpac_on_logs(void *cbck, GF_LOG_Level log_level, GF_LOG_Tool log_tool, const char* fmt, va_list vlist) +{ + va_list vlist_tmp; + va_copy(vlist_tmp, vlist); + u32 len = vsnprintf(NULL, 0, fmt, vlist_tmp); + va_end(vlist_tmp); + if (log_buf_size < len+2) { + log_buf_size = len+2; + log_buf = gf_realloc(log_buf, log_buf_size); + } + vsprintf(log_buf, fmt, vlist); + + if (log_write && static_logs[log_write-1].szMsg) { + if (!strcmp(static_logs[log_write-1].szMsg, log_buf)) { + static_logs[log_write-1].nb_repeat++; + return; + } + } + + static_logs[log_write].level = log_level; + static_logs[log_write].tool = log_tool; + if (static_logs[log_write].szMsg) gf_free(static_logs[log_write].szMsg); + static_logs[log_write].szMsg = gf_strdup(log_buf); + static_logs[log_write].clock = gf_net_get_utc(); + + log_write++; + if (log_write==nb_log_entries) { + log_write = nb_log_entries - 1; + gf_free(static_logs[0].szMsg); + memmove(&static_logs[0], &static_logs[1], sizeof (struct _logentry) * (nb_log_entries-1) ); + memset(&static_logs[log_write], 0, sizeof(struct _logentry)); + } +} + +u64 last_report_clock_us = 0; +static void print_date(u64 time) +{ + time_t gtime; + struct tm *t; + u32 sec; + u32 ms; + gtime = time / 1000; + sec = (u32)(time / 1000); + ms = (u32)(time - ((u64)sec) * 1000); + t = gf_gmtime(>ime); +// fprintf(stderr, "[%d-%02d-%02dT%02d:%02d:%02d.%03dZ", 1900 + t->tm_year, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, ms); + fprintf(stderr, "[%02d:%02d:%02d.%03dZ] ", t->tm_hour, t->tm_min, t->tm_sec, ms); +} + +static void gpac_print_report(GF_FilterSession *fsess, Bool is_init, Bool is_final) +{ + u32 i, count, nb_active; + u64 now; + + if (is_init) { + if (enable_reports==2) + gf_sys_set_console_code(stderr, GF_CONSOLE_SAVE); + + logs_to_file = gf_log_use_file(); + if (!logs_to_file && (enable_reports==2) ) { + if (!nb_log_entries) nb_log_entries = 1; + static_logs = gf_malloc(sizeof(struct _logentry) * nb_log_entries); + memset(static_logs, 0, sizeof(struct _logentry) * nb_log_entries); + gf_log_set_callback(fsess, gpac_on_logs); + } + last_report_clock_us = gf_sys_clock_high_res(); + return; + } + + now = gf_sys_clock_high_res(); + if ( (now - last_report_clock_us < 200000) && !is_final) + return; + + last_report_clock_us = now; + if (!is_final) + gf_sys_set_console_code(stderr, GF_CONSOLE_CLEAR); + + gf_sys_get_rti(100, &rti, 0); + gf_sys_set_console_code(stderr, GF_CONSOLE_CYAN); + print_date(gf_net_get_utc()); + fprintf(stderr, "GPAC Session Status: "); + gf_sys_set_console_code(stderr, GF_CONSOLE_RESET); + fprintf(stderr, "mem % 10"LLD_SUF" kb CPU % 2d", rti.gpac_memory/1000, rti.process_cpu_time); + fprintf(stderr, "\n"); + + gf_fs_lock_filters(fsess, GF_TRUE); + nb_active = count = gf_fs_get_filters_count(fsess); + for (i=0; itype==GF_EVENT_PROGRESS) { + if (event->progress.progress_type==3) { + gpac_print_report(fsess, GF_FALSE, GF_FALSE); + } + } + else if (event->type==GF_EVENT_QUIT) { + gf_fs_abort(fsess, GF_TRUE); + } + return GF_FALSE; +} + + +typedef enum +{ + GPAC_COM_UNDEF = 0, + GPAC_QUIT, + GPAC_EXIT, + GPAC_PRINT_STATS, + GPAC_PRINT_GRAPH, + GPAC_SEND_UPDATE, + GPAC_LIST_FILTERS, + GPAC_INSERT_FILTER, + GPAC_REMOVE_FILTER, + GPAC_PRINT_HELP +} GPAC_Command; + +struct _gpac_key +{ + u8 char_code; + GPAC_Command cmd_type; + const char *cmd_help; + u32 flags; +} GPAC_Keys[] = { + {'q', GPAC_QUIT, "flush all streams and exit", 0}, + {'x', GPAC_EXIT, "exit with no flush (may break output files)", 0}, + {'s', GPAC_PRINT_STATS, "print statistics", 0}, + {'g', GPAC_PRINT_GRAPH, "print filter graph", 0}, + {'l', GPAC_LIST_FILTERS, "list filters", 0}, + {'u', GPAC_SEND_UPDATE, "update argument of filter", 0}, + {'i', GPAC_INSERT_FILTER, "insert a filter in the chain", 0}, + {'r', GPAC_REMOVE_FILTER, "remove a filter from the chain", 0}, + {'h', GPAC_PRINT_HELP, "print this help", 0}, + {0} +}; + +static GPAC_Command get_cmd(u8 char_code) +{ + u32 i=0; + while (GPAC_Keys[i].char_code) { + if (GPAC_Keys[i].char_code == char_code) + return GPAC_Keys[i].cmd_type; + i++; + } + return GPAC_COM_UNDEF; +} + +static void gpac_fsess_task_help() +{ + gf_sys_format_help(helpout, help_flags, "Available runtime options/keys:\n"); + + u32 i=0; + while (GPAC_Keys[i].char_code) { + gf_sys_format_help(helpout, help_flags, "- %c: %s\n", GPAC_Keys[i].char_code, GPAC_Keys[i].cmd_help); + i++; + } +} + +char szFilter[100]; +char szCom[2048]; +static u64 run_start_time = 0; +static Bool gpac_fsess_task(GF_FilterSession *fsess, void *callback, u32 *reschedule_ms) +{ + if (enable_prompt && gf_prompt_has_input()) { + u32 i, count; + GF_Filter *filter; + const GF_Filter *link_from=NULL; + GF_Err e; + char *link_args = NULL; + GPAC_Command c = get_cmd(gf_prompt_get_char()); + switch (c) { + case GPAC_QUIT: + gf_fs_abort(fsess, GF_TRUE); + nb_loops = 0; + return GF_FALSE; + case GPAC_EXIT: + gf_fs_abort(fsess, GF_FALSE); + nb_loops = 0; + return GF_FALSE; + case GPAC_PRINT_STATS: + gf_fs_print_stats(fsess); + break; + case GPAC_PRINT_GRAPH: + gf_fs_print_connections(fsess); + break; + case GPAC_PRINT_HELP: + gpac_fsess_task_help(); + break; + case GPAC_SEND_UPDATE: + fprintf(stderr, "Sending filter update - enter the target filter ID, name, registry name or #N with N the 0-based index in loaded filter list:\n"); + + if (1 > scanf("%99s", szFilter)) { + fprintf(stderr, "Cannot read the filter ID, aborting.\n"); + break; + } + e = GF_OK; + if (szFilter[0]=='#') { + GF_FilterStats stats; + u32 idx = atoi(szFilter+1); + gf_fs_lock_filters(fsess, GF_TRUE); + e = gf_fs_get_filter_stats(fsess, idx, &stats); + gf_fs_lock_filters(fsess, GF_FALSE); + if (e==GF_OK) + strncpy(szFilter, stats.filter_id ? stats.filter_id : stats.name, 99); + } + if (e) { + fprintf(stderr, "Cannot get filter for ID %s, aborting.\n", szFilter); + break; + } + fprintf(stderr, "Enter the command to send\n"); + if (1 > scanf("%2047s", szCom)) { + fprintf(stderr, "Cannot read the command, aborting.\n"); + break; + } + gf_fs_send_update(fsess, szFilter, NULL, szCom, NULL, 0); + break; + case GPAC_LIST_FILTERS: + gf_fs_lock_filters(fsess, GF_TRUE); + count = gf_fs_get_filters_count(fsess); + fprintf(stderr, "Loaded filters:\n"); + for (i=0; i scanf("%99s", szFilter)) { + fprintf(stderr, "Cannot read the filter ID or index, aborting.\n"); + break; + } + link_args = strchr(szFilter, separator_set[SEP_LINK]); + if (link_args) { + link_args[0] = 0; + link_args++; + } + if (szFilter[0]=='#') { + GF_FilterStats stats; + u32 idx = atoi(szFilter+1); + gf_fs_lock_filters(fsess, GF_TRUE); + gf_fs_get_filter_stats(fsess, idx, &stats); + gf_fs_lock_filters(fsess, GF_FALSE); + link_from = stats.filter; + } else { + const GF_Filter *link_from_by_reg=NULL; + gf_fs_lock_filters(fsess, GF_TRUE); + count = gf_fs_get_filters_count(fsess); + for (i=0; i scanf("%2047s", szCom)) { + fprintf(stderr, "Cannot read the filter to insert, aborting.\n"); + break; + } + + if (!strncmp(szCom, "src=", 4)) { + filter = gf_fs_load_source(fsess, szCom+4, NULL, NULL, &e); + } else if (!strncmp(szCom, "dst=", 4)) { + filter = gf_fs_load_destination(fsess, szCom+4, NULL, NULL, &e); + } else { + filter = gf_fs_load_filter(fsess, szCom, &e); + } + + if (!filter) { + fprintf(stderr, "Cannot load filter %s: %s\n", szCom, gf_error_to_string(e)); + break; + } + gf_filter_set_source(filter, (GF_Filter *) link_from, link_args); + //reconnect outputs of source + gf_filter_reconnect_output((GF_Filter *) link_from); + break; + default: + break; + } + } + if (runfor>0) { + u64 now = gf_sys_clock_high_res(); + if (!run_start_time) run_start_time = now; + else if (now - run_start_time > runfor) { + if (runfor_exit) + exit(0); + + gf_fs_abort(fsess, GF_TRUE); + nb_loops = 0; + return GF_FALSE; + } + } + + if (gf_fs_is_last_task(fsess)) + return GF_FALSE; + *reschedule_ms = 500; + return GF_TRUE; +} + +static Bool sigint_catched=GF_FALSE; +static Bool sigint_processed=GF_FALSE; +#ifdef WIN32 +#include +static BOOL WINAPI gpac_sig_handler(DWORD sig) +{ + if (sig == CTRL_C_EVENT) { +#else +#include +static void gpac_sig_handler(int sig) +{ + if (sig == SIGINT) { +#endif + nb_loops = 0; + if (session) { + char input=0; + int res; + if (sigint_catched) { + if (sigint_processed) { + fprintf(stderr, "catched SIGINT twice and session not responding, forcing exit. Please report to GPAC devs https://github.com/gpac/gpac\n"); + } + exit(1); + } + sigint_catched = GF_TRUE; + fprintf(stderr, "catched SIGINT - flush session before exit ? (Y/n):\n"); + res = scanf("%c", &input); + if (res!=1) input=0; + switch (input) { + case 'Y': + case 'y': + case '\n': + sigint_processed = GF_TRUE; + gf_fs_abort(session, GF_TRUE); + break; + case 0: + break; + default: + sigint_processed = GF_TRUE; + gf_fs_abort(session, GF_FALSE); + break; + } + } + } +#ifdef WIN32 + return TRUE; +#endif +} + +static void parse_sep_set(const char *arg, Bool *override_seps) +{ + if (!arg) return; + u32 len = (u32) strlen(arg); + if (!len) return; + *override_seps = GF_TRUE; + if (len>=1) separator_set[0] = arg[0]; + if (len>=2) separator_set[1] = arg[1]; + if (len>=3) separator_set[2] = arg[2]; + if (len>=4) separator_set[3] = arg[3]; + if (len>=5) separator_set[4] = arg[4]; + if (len>=6) separator_set[5] = arg[5]; +} + + +static int gpac_exit_fun(int code, char **alias_argv, int alias_argc) +{ + u32 i; + for (i=1; i +static void gpac_load_suggested_filter_args() +{ + u32 i, count, k; + GF_Config *opts = NULL; + GF_FilterSession *fsess; + const char *version, *cfg_path; + char *all_opts; + + if (gf_opts_get_bool("core", "no-argchk")) + return; + + cfg_path = gf_opts_get_filename(); + all_opts = gf_url_concatenate(cfg_path, "all_opts.txt"); + + opts = gf_cfg_force_new("probe", all_opts); + gf_free(all_opts); + + version = gf_cfg_get_key(opts, "version", "version"); + if (version && !strcmp(version, gf_gpac_version()) ) { + gf_cfg_del(opts); + return; + } + gf_cfg_del_section(opts, "allopts"); + gf_cfg_set_key(opts, "version", "version", gf_gpac_version()); + + gf_sys_format_help(stderr, 0, "__Refreshing all options registry, this may take some time ... "); + + fsess = gf_fs_new(0, GF_FS_SCHEDULER_LOCK_FREE, GF_FS_FLAG_LOAD_META, NULL); + if (!fsess) { + gf_sys_format_help(stderr, 0, "\nWarning: Error creating session\n"); + gf_cfg_del(opts); + return; + } + + count = gf_fs_filters_registers_count(fsess); + for (i=0; iargs) { + const char *old_val; + char *argn=NULL; + const GF_FilterArgs *arg = &freg->args[k]; + if (!arg || !arg->arg_name) { + break; + } + k++; + if (arg->flags & GF_FS_ARG_HINT_HIDE) continue; + + if (arg->flags & GF_FS_ARG_META) { + gf_dynstrcat(&argn, "-+", NULL); + } else { + gf_dynstrcat(&argn, "--", NULL); + } + gf_dynstrcat(&argn, arg->arg_name, NULL); + + old_val = gf_cfg_get_key(opts, "allopts", argn); + if (old_val) { + char *newval = gf_strdup(freg->name); + gf_dynstrcat(&newval, old_val, " "); + gf_cfg_set_key(opts, "allopts", argn, newval); + gf_free(newval); + } else { + gf_cfg_set_key(opts, "allopts", argn, freg->name); + } + gf_free(argn); + } + } + gf_fs_del(fsess); + gf_cfg_del(opts); + gf_sys_format_help(stderr, 0, "done\n"); +} + + +static void gpac_suggest_arg(char *aname) +{ + u32 k; + const GF_GPACArg *args; + if (!aname) return; + Bool found=GF_FALSE; + for (k=0; k<2; k++) { + u32 i=0; + if (k==0) args = gpac_args; + else args = gf_sys_get_options(); + + while (args[i].name) { + const GF_GPACArg *arg = &args[i]; + i++; + if (gf_sys_word_match(aname, arg->name)) { + if (!found) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Unrecognized option \"%s\", did you mean:\n", aname)); + found = GF_TRUE; + } + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("\t-%s (see gpac -hx%s)\n", arg->name, k ? " core" : "")); + } + } + } + if (!found) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Unrecognized option \"%s\", check usage \"gpac -h\"\n", aname)); + } +} + +static void gpac_suggest_filter(char *fname, Bool is_help, Bool filter_only) +{ + Bool found = GF_FALSE; + u32 i, count; + if (!fname) return; + count = gf_fs_filters_registers_count(session); + for (i=0; iname)) { + if (!found) { + found = GF_TRUE; + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Closest filter names: \n")); + } + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("- %s\n", freg->name)); + } + } + if (!found && is_help) { + const char *doc_helps[] = { + "log", "core", "modules", "doc", "alias", "props", "colors", "cfg", "prompt", "codecs", "links", "bin", "filters", "filters:*", "filters:@", NULL + }; + i=0; + while (doc_helps[i]) { + if (gf_sys_word_match(fname, doc_helps[i])) { + if (!found) { + found = GF_TRUE; + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Closest help command: \n")); + } + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("-h %s\n", doc_helps[i])); + } + i++; + } + if (!filter_only) { + Bool arg_found=GF_FALSE; + u32 nb_alias = gf_opts_get_key_count("gpac.alias"); + for (i=0; iargs) { + const GF_FilterArgs *arg = ®->args[j]; + if (!arg || !arg->arg_name) break; + j++; + if (!gf_sys_word_match(fname, arg->arg_name)) continue; + + if (!arg_found) { + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("Closest matching filter option:\n", fname)); + arg_found = GF_TRUE; + found = GF_TRUE; + } + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_HIGHLIGHT_FIRST, "%s.%s \n", reg->name, arg->arg_name); + } + } + } + } + if (!found) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("No filter %swith similar name found, see gpac -h filters%s\n", + is_help ? "or help command " : "", + is_help ? " or gpac -h" : "" + )); + } +} + +static void gpac_suggest_filter_arg(GF_Config *opts, char *argname, u32 atype) +{ + char *keyname; + const char *keyval; + u32 i, count, len, nb_filters, j; + Bool f_found = GF_FALSE; + char szSep[2]; + + if (gf_opts_get_bool("core", "no-argchk")) + return; + + szSep[0] = separator_set[0]; + szSep[1] = 0; + + len = (u32) strlen(argname); + keyname = gf_malloc(sizeof(char)*(len+3)); + sprintf(keyname, "-%c%s", (atype==2) ? '+' : '-', argname); + keyval = gf_cfg_get_key(opts, "allopts", keyname); + gf_free(keyname); + if (keyval) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Argument \"%s%s\" was set but not used by any filter\n", + (atype==2) ? "-+" : (atype ? "--" : szSep), argname)); + return; + } + + nb_filters = gf_fs_get_filters_count(session); + count = gf_cfg_get_key_count(opts, "allopts"); + for (i=0; i2*len) continue; + + for (j=0; j=2) || print_meta_filters || dump_codecs || print_filter_info) sflags |= GF_FS_FLAG_LOAD_META; + + if (view_filter_conn || list_filters || (print_filter_info && (argmode == GF_ARGMODE_ALL)) ) + gf_opts_set_key("temp", "gendoc", "yes"); + + +restart: + + session = gf_fs_new_defaults(sflags); + + if (!session) { + gpac_exit(1); + } + if (override_seps) gf_fs_set_separators(session, separator_set); + if (load_test_filters) gf_fs_register_test_filters(session); + + if (gf_fs_get_max_resolution_chain_length(session) <= 1 ) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\nDynamic resolution of filter connections disabled\n\n")); + } + + if (list_filters || print_filter_info) { + if (print_filters(argc, argv, session, argmode)==GF_FALSE) + e = GF_FILTER_NOT_FOUND; + goto exit; + } + if (view_filter_conn) { + gf_fs_print_all_connections(session, view_conn_for_filter, gf_sys_format_help); + goto exit; + } + if (dump_codecs) { + dump_all_codec(session); + goto exit; + } + if (write_profile || write_extensions || write_core_opts) { + if (write_core_opts) + write_core_options(); + if (write_extensions) + write_file_extensions(); + if (write_profile) + write_filters_options(session); + goto exit; + } + + if (session_js) { + e = gf_fs_load_script(session, session_js); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Failed to load JS for session: %s\n", gf_error_to_string(e) )); + goto exit; + } + } + + //all good to go, load filters + has_xopt = GF_FALSE; + loaded_filters = gf_list_new(); + for (i=1; i1) { + link_prev_filter = atoi(arg+1); + if (link_prev_filter<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Wrong filter index %d, must be positive\n", link_prev_filter)); + e = GF_BAD_PARAM; + goto exit; + } + } + + if (ext) ext[0] = separator_set[SEP_FRAG]; + continue; + } + + if (!strncmp(arg, "src=", 4) ) { + filter = gf_fs_load_source(session, arg+4, NULL, NULL, &e); + } else if (!strncmp(arg, "dst=", 4) ) { + filter = gf_fs_load_destination(session, arg+4, NULL, NULL, &e); + } else { + e = GF_EOS; + char *need_gfio = strstr(arg, "@gfi://"); + if (!need_gfio) need_gfio = strstr(arg, "@gfo://"); + if (need_gfio) { + const char *fargs=NULL; + const char *fio_url; + char *updated_args; + u32 len = (u32) (need_gfio - arg); + updated_args = gf_malloc(sizeof(char)*(len+1)); + strncpy(updated_args, arg, len); + updated_args[len]=0; + + fio_url = make_fileio(need_gfio+7, &fargs, need_gfio[3]=='i' ? GF_TRUE : GF_FALSE, &e); + if (fio_url) { + gf_dynstrcat(&updated_args, fio_url, NULL); + if (fargs) + gf_dynstrcat(&updated_args, fargs, NULL); + + filter = gf_fs_load_filter(session, updated_args, &e); + } + gf_free(updated_args); + } else { + filter = gf_fs_load_filter(session, arg, &e); + } + is_simple=GF_TRUE; + if (!filter && has_xopt) + continue; + } + } + + if (!filter) { + if (!e) e = GF_FILTER_NOT_FOUND; + + if (e!=GF_FILTER_NOT_FOUND) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Failed to load filter%s \"%s\": %s\n", is_simple ? "" : " for", arg, gf_error_to_string(e) )); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Failed to find filter%s \"%s\"\n", is_simple ? "" : " for", arg)); + + gpac_suggest_filter(arg, GF_FALSE, GF_TRUE); + } + nb_filters=0; + goto exit; + } + nb_filters++; + + if (link_prev_filter>=0) { + GF_Filter *link_from = gf_list_get(loaded_filters, gf_list_count(loaded_filters)-1-link_prev_filter); + if (!link_from) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Wrong filter index @%d\n", link_prev_filter)); + e = GF_BAD_PARAM; + goto exit; + } + link_prev_filter = -1; + gf_filter_set_source(filter, link_from, link_prev_filter_ext); + link_prev_filter_ext = NULL; + } + + gf_list_add(loaded_filters, filter); + } + if (!gf_list_count(loaded_filters) && !session_js) { + if (nothing_to_do && !gen_doc) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Nothing to do, check usage \"gpac -h\"\ngpac - GPAC command line filter engine - version %s\n%s\n", gf_gpac_version(), gf_gpac_copyright_cite())); + e = GF_BAD_PARAM; + } else { + e = GF_EOS; + } + goto exit; + } + if (enable_reports) { + if (enable_reports==2) + gf_fs_set_ui_callback(session, gpac_event_proc, session); + + gf_fs_enable_reporting(session, GF_TRUE); + } + + if (enable_prompt || (runfor>0)) { + if (enable_prompt) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Running session, press 'h' for help\n")); + } + gf_fs_post_user_task(session, gpac_fsess_task, NULL, "gpac_fsess_task"); + } + if (!enable_prompt) { +#ifdef WIN32 + SetConsoleCtrlHandler((PHANDLER_ROUTINE)gpac_sig_handler, TRUE); +#else + signal(SIGINT, gpac_sig_handler); +#endif + } + + if (enable_reports) { + gpac_print_report(session, GF_TRUE, GF_FALSE); + } + + e = gf_fs_run(session); + if (e>0) e = GF_OK; + + if (e) { + fprintf(stderr, "session error %s\n", gf_error_to_string(e) ); + } else { + e = gf_fs_get_last_connect_error(session); + if (e<0) fprintf(stderr, "session last connect error %s\n", gf_error_to_string(e) ); + + if (!e) { + e = gf_fs_get_last_process_error(session); + if (e<0) fprintf(stderr, "session last process error %s\n", gf_error_to_string(e) ); + } + gpac_check_session_args(); + } + + if (enable_reports) { + if (enable_reports==2) { + gf_sys_set_console_code(stderr, GF_CONSOLE_RESTORE); + } + gpac_print_report(session, GF_FALSE, GF_TRUE); + } + +exit: + if (enable_reports==2) { + gf_log_set_callback(session, NULL); + } + + if (e && nb_filters) { + gf_fs_run(session); + } + if (dump_stats) + gf_fs_print_stats(session); + if (dump_graph) + gf_fs_print_connections(session); + + + tmp_sess = session; + session = NULL; + gf_fs_del(tmp_sess); + if (loaded_filters) gf_list_del(loaded_filters); + + cleanup_file_io(); + + if (!e && nb_loops) { + if (nb_loops>0) nb_loops--; + loops_done++; + fprintf(stderr, "session done, restarting (loop %d)\n", loops_done); + gf_log_reset_file(); + goto restart; + } + + gpac_exit(e<0 ? 1 : 0); +} + +GF_MAIN_FUNC(gpac_main) + + +/********************************************************* + Filter and property info/dump functions +*********************************************************/ + +static void dump_caps(u32 nb_caps, const GF_FilterCapability *caps) +{ + u32 i; + for (i=0;iflags & GF_CAPFLAG_IN_BUNDLE) && i+1==nb_caps) break; + if (!i) gf_sys_format_help(helpout, help_flags, "Capabilities Bundle:\n"); + else if (!(cap->flags & GF_CAPFLAG_IN_BUNDLE) ) { + gf_sys_format_help(helpout, help_flags, "Capabilities Bundle:\n"); + continue; + } + + szName = cap->name ? cap->name : gf_props_4cc_get_name(cap->code); + if (!szName) szName = gf_4cc_to_str(cap->code); + gf_sys_format_help(helpout, help_flags, "\t Flags:"); + if (cap->flags & GF_CAPFLAG_INPUT) gf_sys_format_help(helpout, help_flags, " Input"); + if (cap->flags & GF_CAPFLAG_OUTPUT) gf_sys_format_help(helpout, help_flags, " Output"); + if (cap->flags & GF_CAPFLAG_EXCLUDED) gf_sys_format_help(helpout, help_flags, " Exclude"); + if (cap->flags & GF_CAPFLAG_LOADED_FILTER) gf_sys_format_help(helpout, help_flags, " LoadedFilterOnly"); + + //dump some interesting predefined ones which are not mapped to types + if (cap->code==GF_PROP_PID_STREAM_TYPE) szVal = gf_stream_type_name(cap->val.value.uint); + else if (cap->code==GF_PROP_PID_CODECID) szVal = (const char *) gf_codecid_name(cap->val.value.uint); + else szVal = gf_props_dump_val(&cap->val, szDump, GF_FALSE, NULL); + + gf_sys_format_help(helpout, help_flags, " %s=\"%s\"", szName, szVal); + if (cap->priority) gf_sys_format_help(helpout, help_flags, ", priority=%d", cap->priority); + gf_sys_format_help(helpout, help_flags, "\n"); + } +} + +static void print_filter_arg(const GF_FilterArgs *a, u32 gen_doc) +{ + Bool is_enum = GF_FALSE; + + if ((a->arg_type==GF_PROP_UINT) && a->min_max_enum && strchr(a->min_max_enum, '|')) { + is_enum = GF_TRUE; + } + + if (gen_doc==1) { + gf_sys_format_help(helpout, help_flags, "", a->arg_name); + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_HIGHLIGHT_FIRST, "%s", a->arg_name); + gf_sys_format_help(helpout, help_flags, " (%s", is_enum ? "enum" : gf_props_get_type_name(a->arg_type)); + } else { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_HIGHLIGHT_FIRST, "%s (%s", a->arg_name, is_enum ? "enum" : gf_props_get_type_name(a->arg_type)); + } + if (a->arg_default_val) { + if (!strcmp(a->arg_default_val, "2147483647")) + gf_sys_format_help(helpout, help_flags, ", default: __+I__"); + else if (!strcmp(a->arg_default_val, "-2147483648")) + gf_sys_format_help(helpout, help_flags, ", default: __-I__"); + else + gf_sys_format_help(helpout, help_flags, ", default: __%s__", a->arg_default_val); + } else { +// gf_sys_format_help(helpout, help_flags, ", no default"); + } + if (a->min_max_enum && !is_enum) { + if (!strcmp(a->min_max_enum, "-2147483648-I")) + gf_sys_format_help(helpout, help_flags, ", %s: -I-I", /*strchr(a->min_max_enum, '|') ? "Enum" : */"minmax"); + else + gf_sys_format_help(helpout, help_flags, ", %s: %s", /*strchr(a->min_max_enum, '|') ? "Enum" : */"minmax", a->min_max_enum); + } + if (a->flags & GF_FS_ARG_UPDATE) gf_sys_format_help(helpout, help_flags, ", updatable"); +// if (a->flags & GF_FS_ARG_META) gf_sys_format_help(helpout, help_flags, ", meta"); + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_OPT_DESC, "): %s\n", a->arg_desc); + + //check syntax + if (gen_doc) { + GF_GPACArg _a; + memset(&_a, 0, sizeof(GF_GPACArg)); + _a.name = a->arg_name; + _a.description = a->arg_desc; + _a.flags = GF_ARG_HINT_HIDE; + gf_sys_print_arg(NULL, 0, &_a, ""); + } + + if (a->min_max_enum && strchr(a->min_max_enum, '|')) + gf_sys_format_help(helpout, help_flags, "\n"); +} + +static void print_filter_single_opt(const GF_FilterRegister *reg, char *optname, GF_Filter *filter_inst) +{ + u32 idx=0; + Bool found = GF_FALSE; + const GF_FilterArgs *args = NULL; + if (filter_inst) args = gf_filter_get_args(filter_inst); + else args = reg->args; + + if (!args) return; + + while (1) { + const GF_FilterArgs *a = & args[idx]; + if (!a || !a->arg_name) break; + idx++; + if (strcmp(a->arg_name, optname)) continue; + + print_filter_arg(a, 0); + found = GF_TRUE; + break; + } + if (found) return; + + idx = 0; + while (1) { + const GF_FilterArgs *a = & args[idx]; + if (!a || !a->arg_name) break; + idx++; + if (gf_sys_word_match(optname, a->arg_name) + || (a->arg_desc && strstr(a->arg_desc, optname)) + ) { + if (!found) { + found = GF_TRUE; + fprintf(stderr, "No such option %s for filter %s - did you mean:\n", optname, filter_inst ? gf_filter_get_name(filter_inst) : reg->name); + } + fprintf(stderr, " - %s: %s\n", a->arg_name, a->arg_desc); + } + } + if (found) return; + + fprintf(stderr, "No such option %s for filter %s\n", optname, filter_inst ? gf_filter_get_name(filter_inst) : reg->name); +} + +static void print_filter(const GF_FilterRegister *reg, GF_SysArgMode argmode, GF_Filter *filter_inst) +{ + const GF_FilterArgs *args = NULL; + + if (gen_doc==1) { + char szName[1024]; + sprintf(szName, "%s.md", reg->name); + if (gen_doc==1) { + gf_fclose(helpout); + helpout = gf_fopen(szName, "w"); + fprintf(helpout, "[**HOME**](Home) » [**Filters**](Filters) » %s\n", reg->description); + fprintf(helpout, "%s", auto_gen_md_warning); + + if (!sidebar_md) { + char *sbbuf = NULL; + if (gf_file_exists("_Sidebar.md")) { + char szLine[1024]; + u32 end_pos=0; + sidebar_md = gf_fopen("_Sidebar.md", "r"); + gf_fseek(sidebar_md, 0, SEEK_SET); + while (!feof(sidebar_md)) { + char *read = gf_fgets(szLine, 1024, sidebar_md); + if (!read) break; + if (!strncmp(szLine, "**Filters Help**", 16)) { + end_pos = (u32) gf_ftell(sidebar_md); + break; + } + } + if (!end_pos) end_pos = (u32) gf_ftell(sidebar_md); + if (end_pos) { + sbbuf = gf_malloc(end_pos+1); + gf_fseek(sidebar_md, 0, SEEK_SET); + end_pos = (u32) gf_fread(sbbuf, end_pos, sidebar_md); + sbbuf[end_pos]=0; + gf_fclose(sidebar_md); + } + } + sidebar_md = gf_fopen("_Sidebar.md", "w"); + if (sbbuf) { + fprintf(sidebar_md, "%s\n \n", sbbuf); + gf_free(sbbuf); + } + } +#ifndef GPAC_DISABLE_DOC + if (reg->description) { + fprintf(sidebar_md, "[[%s (%s)|%s]] \n", reg->description, reg->name, reg->name); + } else { + fprintf(sidebar_md, "[[%s|%s]] \n", reg->name, reg->name); + } + if (!reg->help) { + fprintf(stderr, "filter %s without help, forbidden\n", reg->name); + exit(1); + } + if (!reg->description) { + fprintf(stderr, "filter %s without description, forbidden\n", reg->name); + exit(1); + } +#endif + } + +#ifndef GPAC_DISABLE_DOC + gf_sys_format_help(helpout, help_flags, "# %s\n", reg->description); +#endif + gf_sys_format_help(helpout, help_flags, "Register name used to load filter: **%s**\n", reg->name); + if (reg->flags & GF_FS_REG_EXPLICIT_ONLY) { + gf_sys_format_help(helpout, help_flags, "This filter is not checked during graph resolution and needs explicit loading.\n"); + } else { + gf_sys_format_help(helpout, help_flags, "This filter may be automatically loaded during graph resolution.\n"); + } + if (reg->flags & GF_FS_REG_REQUIRES_RESOLVER) { + gf_sys_format_help(helpout, help_flags, "This filter requires the graph resolver to be activated.\n"); + } + } else { + gf_sys_format_help(helpout, help_flags, "# %s\n", filter_inst ? gf_filter_get_name(filter_inst) : reg->name); + if (filter_inst) + gf_sys_format_help(helpout, help_flags, "Description: %s\n", gf_filter_get_description(filter_inst) ); + else { +#ifndef GPAC_DISABLE_DOC + if (reg->description) gf_sys_format_help(helpout, help_flags, "Description: %s\n", reg->description); +#endif + + } + + if (filter_inst) + gf_sys_format_help(helpout, help_flags, "Version: %s\n", gf_filter_get_version(filter_inst) ); + else { + if (reg->version) { + if (!strncmp(reg->version, "! ", 2)) { + if (!gen_doc) + gf_sys_format_help(helpout, help_flags, "%s\n", reg->version+2); + } else { + gf_sys_format_help(helpout, help_flags, "Version: %s\n", reg->version); + } + } + } + } + + if (filter_inst) { + gf_sys_format_help(helpout, help_flags, "Author: %s\n", gf_filter_get_author(filter_inst) ); + gf_sys_format_help(helpout, help_flags, "\n%s\n\n", gf_filter_get_help(filter_inst) ); + } else { +#ifndef GPAC_DISABLE_DOC + if (reg->author) gf_sys_format_help(helpout, help_flags, "Author: %s\n", reg->author); + if (reg->help) { + u32 hf = help_flags; + if (gen_doc==1) hf |= GF_PRINTARG_ESCAPE_XML; + gf_sys_format_help(helpout, hf, "\n%s\n\n", reg->help); + } +#else + gf_sys_format_help(helpout, help_flags, "GPAC compiled without built-in doc\n"); +#endif + } + + if (argmode==GF_ARGMODE_EXPERT) { + if (reg->max_extra_pids==(u32) -1) gf_sys_format_help(helpout, help_flags, "Max Input PIDs: any\n"); + else gf_sys_format_help(helpout, help_flags, "Max Input PIDs: %d\n", 1 + reg->max_extra_pids); + + gf_sys_format_help(helpout, help_flags, "Flags:"); + if (reg->flags & GF_FS_REG_EXPLICIT_ONLY) gf_sys_format_help(helpout, help_flags, " ExplicitOnly"); + if (reg->flags & GF_FS_REG_MAIN_THREAD) gf_sys_format_help(helpout, help_flags, " MainThread"); + if (reg->flags & GF_FS_REG_CONFIGURE_MAIN_THREAD) gf_sys_format_help(helpout, help_flags, " ConfigureMainThread"); + if (reg->flags & GF_FS_REG_HIDE_WEIGHT) gf_sys_format_help(helpout, help_flags, " HideWeight"); + if (reg->flags & GF_FS_REG_REQUIRES_RESOLVER) gf_sys_format_help(helpout, help_flags, " RequireResolver"); + if (reg->flags & GF_FS_REG_DYNLIB) gf_sys_format_help(helpout, help_flags, " DynamicLib"); + if (reg->probe_url) gf_sys_format_help(helpout, help_flags, " URLMimeProber"); + if (reg->probe_data) gf_sys_format_help(helpout, help_flags, " DataProber"); + if (reg->reconfigure_output) gf_sys_format_help(helpout, help_flags, " ReconfigurableOutput"); + + gf_sys_format_help(helpout, help_flags, "\nPriority %d", reg->priority); + + gf_sys_format_help(helpout, help_flags, "\n"); + } + + if (filter_inst) args = gf_filter_get_args(filter_inst); + else args = reg->args; + + if (args) { + u32 idx=0; + if (gen_doc==1) { + gf_sys_format_help(helpout, help_flags, "# Options \n"); + } else { + switch (argmode) { + case GF_ARGMODE_ALL: + case GF_ARGMODE_EXPERT: + gf_sys_format_help(helpout, help_flags, "# Options (expert):\n"); + break; + case GF_ARGMODE_ADVANCED: + gf_sys_format_help(helpout, help_flags, "# Options (advanced):\n"); + break; + case GF_ARGMODE_BASE: + gf_sys_format_help(helpout, help_flags, "# Options (basic):\n"); + break; + } + } + + while (1) { + const GF_FilterArgs *a = & args[idx]; + if (!a || !a->arg_name) break; + idx++; + + if (a->flags & GF_FS_ARG_HINT_HIDE) continue; + + switch (argmode) { + case GF_ARGMODE_ALL: + case GF_ARGMODE_EXPERT: + break; + case GF_ARGMODE_ADVANCED: + if (a->flags & GF_FS_ARG_HINT_EXPERT) continue; + break; + case GF_ARGMODE_BASE: + if (a->flags & (GF_FS_ARG_HINT_EXPERT|GF_FS_ARG_HINT_ADVANCED)) continue; + break; + } + + print_filter_arg(a, gen_doc); + + if (!gen_doc) continue; + +#ifdef CHECK_DOC + if (a->flags & GF_FS_ARG_META) continue; + + u8 achar; + u32 j=0; + char szArg[100]; + sprintf(szArg, " %s ", a->arg_name); + if (reg->help && strstr(reg->help, szArg)) { + fprintf(stderr, "\nWARNING: filter %s bad help, uses arg %s without link\n", reg->name, a->arg_name); + exit(1); + } + while (1) { + const GF_FilterArgs *anarg = & args[j]; + if (!anarg || !anarg->arg_name) break; + j++; + if (a == anarg) continue; + if (reg->help && strstr(reg->help, szArg)) { + fprintf(stderr, "\nWARNING: filter %s bad description for argument %s, uses arg %s without link\n", reg->name, anarg->arg_name, a->arg_name); + exit(1); + } + } + + if (a->min_max_enum) { + //check format + if ((a->arg_type!=GF_PROP_UINT_LIST) && !(a->flags&GF_FS_ARG_META) && strchr(a->min_max_enum, '|') ) { + if (!strstr(a->arg_desc, "- ")) { + fprintf(stderr, "\nWARNING: filter %s bad description format for arg %s, missing list bullet \"- \"\n", reg->name, a->arg_name); + exit(1); + } + if (strstr(a->arg_desc, ":\n")) { + fprintf(stderr, "\nWARNING: filter %s bad description format for arg %s, should not use \":\\n\"\n", reg->name, a->arg_name); + exit(1); + } + } else if (!(a->flags&GF_FS_ARG_META) && strchr(a->arg_desc, '\n')) { + fprintf(stderr, "\nWARNING: filter %s bad description format for arg %s, should not contain \"\\n\"\n", reg->name, a->arg_name); + exit(1); + } + } + if (!(a->flags&GF_FS_ARG_META)) { + char *sep; + + achar = a->arg_desc[strlen(a->arg_desc)-1]; + if (achar == '\n') { + fprintf(stderr, "\nWARNING: filter %s bad description format for arg %s, should not end with \"\\n\"\n", reg->name, a->arg_name); + exit(1); + } + + achar = a->arg_desc[0]; + if ((achar >= 'A') && (achar <= 'Z')) { + achar = a->arg_desc[1]; + if ((achar < 'A') || (achar > 'Z')) { + fprintf(stderr, "\nWARNING: filter %s bad description format for arg %s, should start with lowercase\n", reg->name, a->arg_name); + exit(1); + } + } + if (a->arg_desc[0] == ' ') { + fprintf(stderr, "\nWARNING: filter %s bad description format for arg %s, first character should not be space\n", reg->name, a->arg_name); + exit(1); + } + sep = strchr(a->arg_desc+1, ' '); + if (sep) sep--; + if (sep && (sep[0] == 's') && (sep[-1] != 's')) { + fprintf(stderr, "\nWARNING: filter %s bad description format for arg %s, first word should be infinitive\n", reg->name, a->arg_name); + exit(1); + } + } +#endif + } + } else { + gf_sys_format_help(helpout, help_flags, "No options\n"); + } + + if (reg->nb_caps) { + if (!gen_doc && (argmode==GF_ARGMODE_ALL)) { + dump_caps(reg->nb_caps, reg->caps); + } + } + gf_sys_format_help(helpout, help_flags, "\n"); +} + +static Bool strstr_nocase(const char *text, const char *subtext, u32 subtext_len) +{ + if (!*text || !subtext || !subtext_len) + return GF_FALSE; + + while (*text) { + if (tolower(*text) == *subtext) { + if (!strnicmp(text, subtext, subtext_len)) + return GF_TRUE; + + } + text++; + } + return GF_FALSE; +} + +static Bool print_filters(int argc, char **argv, GF_FilterSession *session, GF_SysArgMode argmode) +{ + Bool found = GF_FALSE; + char *fname = NULL; + char *l_fname = NULL; + u32 lf_len = 0; + u32 i, count = gf_fs_filters_registers_count(session); + + if (!gen_doc && list_filters) gf_sys_format_help(helpout, help_flags, "Listing %d supported filters%s:\n", count, (list_filters==2) ? " including meta-filters" : ""); + for (i=0; iname) ) { + if (optname) + print_filter_single_opt(reg, optname, NULL); + else + print_filter(reg, argmode, NULL); + found = GF_TRUE; + } else { + char *sepo = strchr(arg, ':'); + char *sepd = strchr(reg->name, ':'); + Bool patch_meta = GF_FALSE; + if (sepo && sepd) { + char *subf = strstr(reg->name, sepo+1); + if (subf) { + u32 slen = (u32) strlen(sepo+1); + if ((subf[slen]==0) || (subf[slen]==',')) + patch_meta = GF_TRUE; + } + } + if (!strcmp(arg, "*:*") || !strcmp(arg, "@:@") + || (!sepo && (!strcmp(arg, "*") || !strcmp(arg, "@")) ) + || (sepo && (!strcmp(sepo, ":*") || !strcmp(sepo, ":@")) && !strncmp(reg->name, arg, 1+sepo - arg) ) + || patch_meta + ) { + if (optname) + print_filter_single_opt(reg, optname, NULL); + else + print_filter(reg, argmode, NULL); + found = GF_TRUE; + break; + } + //quick shortcuts + else if (!strcmp(reg->name, "jsf") && (!strncmp(arg, "jsf:", 4) || strstr(arg, ".js")) ) { + GF_Filter *f = gf_fs_load_filter(session, arg, NULL); + if (f) { + if (optname) + print_filter_single_opt(reg, optname, f); + else + print_filter(reg, argmode, f); + found = GF_TRUE; + } + } + } + if (sepe) sepe[0] = '.'; + } + } else { +#ifndef GPAC_DISABLE_DOC + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_HIGHLIGHT_FIRST, "%s: %s\n", reg->name, reg->description); +#else + gf_sys_format_help(helpout, help_flags, "%s (compiled without built-in doc)\n", reg->name); +#endif + found = GF_TRUE; + + } + } + if (found) return GF_TRUE; + if (!fname) return GF_FALSE; + if (!print_filter_info) return GF_FALSE; + if (gen_doc) return GF_FALSE; + + if (argmode==GF_ARGMODE_EXPERT) { + l_fname = gf_strdup(fname); + strlwr(l_fname); + lf_len = (u32) strlen(l_fname); + } + + for (i=0; iargs) { + const GF_FilterArgs *arg = ®->args[j]; + if (!arg || !arg->arg_name) break; + j++; + if (argmode==GF_ARGMODE_EXPERT) { + if (!arg->arg_desc || !strstr_nocase(arg->arg_desc, l_fname, lf_len)) continue; + if (!found) { + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("\"%s\" is mentionned in the following filters options:\n", fname)); + found = GF_TRUE; + } + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_HIGHLIGHT_FIRST, "%s.%s \n", reg->name, arg->arg_name); +#if 0 + + } else { + if (strcmp(arg->arg_name, fname)) continue; + if (!found) { + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("No such filter \"%s\" but found filters with matching options:\n", fname)); + found = GF_TRUE; + } + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_HIGHLIGHT_FIRST, "%s.%s: %s\n", reg->name, arg->arg_name, arg->arg_desc); +#endif + } + } + } + if (l_fname) gf_free(l_fname); + + + if (found) return GF_TRUE; + + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("No such filter \"%s\"\n", fname)); + gpac_suggest_filter(fname, GF_TRUE, GF_FALSE); + return GF_FALSE; +} + +static void dump_all_props(void) +{ + u32 i=0; + const GF_BuiltInProperty *prop_info; + + if (gen_doc==1) { + gf_sys_format_help(helpout, help_flags, "## Built-in property types\n" + " \n"); + + gf_sys_format_help(helpout, help_flags, "Name | Description \n"); + gf_sys_format_help(helpout, help_flags, "--- | --- \n"); + for (i=GF_PROP_FORBIDEN+1; iname) continue; + + if (gen_doc==1) { + strcpy(szFlags, ""); + if (prop_info->flags & GF_PROP_FLAG_GSF_REM) strcat(szFlags, "D"); + if (prop_info->flags & GF_PROP_FLAG_PCK) strcat(szFlags, "P"); + + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, "%s | %s | %s | %s | %s \n", prop_info->name, gf_props_get_type_name(prop_info->data_type), + szFlags, + prop_info->description, + gf_4cc_to_str(prop_info->type) + ); + } else if (gen_doc==2) { + gf_sys_format_help(helpout, help_flags, ".TP\n.B %s (%s,%s,%s%s)\n%s\n", prop_info->name, gf_4cc_to_str(prop_info->type), gf_props_get_type_name(prop_info->data_type), + (prop_info->flags & GF_PROP_FLAG_GSF_REM) ? "D" : " ", + (prop_info->flags & GF_PROP_FLAG_PCK) ? "P" : " ", + prop_info->description); + } else { + u32 len; + const char *ptype; + szFlags[0]=0; + if (prop_info->flags & GF_PROP_FLAG_GSF_REM) strcat(szFlags, "D"); + if (prop_info->flags & GF_PROP_FLAG_PCK) strcat(szFlags, "P"); + + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_HIGHLIGHT_FIRST, "%s", prop_info->name); + len = (u32) strlen(prop_info->name); + while (len<16) { + gf_sys_format_help(helpout, help_flags, " "); + len++; + } + ptype = gf_props_get_type_name(prop_info->data_type); + gf_sys_format_help(helpout, help_flags, " (%s %s %s):", gf_4cc_to_str(prop_info->type), ptype, szFlags); + len = (u32) strlen(ptype); + while (len<6) { + gf_sys_format_help(helpout, help_flags, " "); + len++; + } + + gf_sys_format_help(helpout, help_flags, "%s", prop_info->description); + + if (prop_info->data_type==GF_PROP_PIXFMT) { + gf_sys_format_help(helpout, help_flags, "\n\tNames: %s\n\tFile extensions: %s", gf_pixel_fmt_all_names(), gf_pixel_fmt_all_shortnames() ); + } else if (prop_info->data_type==GF_PROP_PCMFMT) { + gf_sys_format_help(helpout, help_flags, "\n\tNames: %s\n\tFile extensions: %s", gf_audio_fmt_all_names(), gf_audio_fmt_all_shortnames() ); + } else if (prop_info->type==GF_PROP_PID_STREAM_TYPE) { + gf_sys_format_help(helpout, help_flags, "\n\tNames: %s\n\t", gf_stream_type_all_names() ); + } + gf_sys_format_help(helpout, help_flags, "\n"); + } + } + if (gen_doc==1) { + u32 idx=0; + const char *name, *fileext, *desc; + gf_sys_format_help(helpout, help_flags, "# Pixel formats\n"); + gf_sys_format_help(helpout, help_flags, "Name | File extensions | Description \n"); + gf_sys_format_help(helpout, help_flags, " --- | --- | --- \n"); + while ( gf_pixel_fmt_enum(&idx, &name, &fileext, &desc)) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, "%s | %s | %s \n", name, fileext, desc); + } + + idx=0; + gf_sys_format_help(helpout, help_flags, "# Audio formats\n"); + gf_sys_format_help(helpout, help_flags, " Name | File extensions | Description \n"); + gf_sys_format_help(helpout, help_flags, " --- | --- | --- \n"); + while ( gf_audio_fmt_enum(&idx, &name, &fileext, &desc)) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, "%s | %s | %s \n", name, fileext, desc); + } + + idx=0; + gf_sys_format_help(helpout, help_flags, "# Stream types\n"); + gf_sys_format_help(helpout, help_flags, " Name | Description \n"); + gf_sys_format_help(helpout, help_flags, " --- | --- \n"); + while ( gf_stream_types_enum(&idx, &name, &desc)) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, "%s | %s \n", name, desc); + } + + idx=0; + gf_sys_format_help(helpout, help_flags, "# Codecs\n"); + gf_sys_format_help(helpout, help_flags, "The codec name identifies a codec within GPAC. There can be several names for a given codec. The first name is used as a default file extension when dumping a raw media stream.\n" + " \n"); + + gf_sys_format_help(helpout, help_flags, " Name | Description \n"); + gf_sys_format_help(helpout, help_flags, " --- | --- \n"); + while ( gf_codecid_enum(idx, &name, &desc)) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR | GF_PRINTARG_ESCAPE_PIPE, "%s | %s \n", name, desc); + idx++; + } + + } else if (gen_doc==2) { + u32 idx=0; + const char *name, *fileext, *desc; + gf_sys_format_help(helpout, help_flags, "# Pixel formats\n"); + while ( gf_pixel_fmt_enum(&idx, &name, &fileext, &desc)) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, ".TP\n.B %s (ext *.%s)\n%s\n", name, fileext, desc); + } + + idx=0; + gf_sys_format_help(helpout, help_flags, "# Audio formats\n"); + while ( gf_audio_fmt_enum(&idx, &name, &fileext, &desc)) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, ".TP\n.B %s (ext *.%s)\n%s\n", name, fileext, desc); + } + + idx=0; + gf_sys_format_help(helpout, help_flags, "# Stream types\n"); + while ( gf_stream_types_enum(&idx, &name, &desc)) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, ".TP\n.B %s\n%s\n", name, desc); + } + + idx=0; + gf_sys_format_help(helpout, help_flags, "# Codecs\n"); + while ( gf_codecid_enum(idx, &name, &desc)) { + gf_sys_format_help(helpout, help_flags | GF_PRINTARG_NL_TO_BR, ".TP\n.B %s\n%s\n", name, desc); + idx++; + } + } +} +#include +static void dump_all_colors(void) +{ + u32 i=0; + GF_Color color; + const char *name; + while (gf_color_enum(&i, &color, &name)) { + gf_sys_format_help(helpout, help_flags|GF_PRINTARG_HIGHLIGHT_FIRST, "%s: 0x%08X\n", name, color); + } +} + +static void dump_all_codec(GF_FilterSession *session) +{ + GF_PropertyValue rawp, filep, stp; + GF_PropertyValue cp, acp; + char szFlags[10], szCap[2]; + u32 cidx=0; + u32 count = gf_fs_filters_registers_count(session); + + gf_sys_format_help(helpout, help_flags, "Codec support in filters, listed as `built_in_name[|variant] [FLAGS]: full_name (mime)` with possible FLAGS values:\n"); + gf_sys_format_help(helpout, help_flags, " - I: Raw format input (demux) support\n"); + gf_sys_format_help(helpout, help_flags, " - O: Raw format output (mux) support\n"); + gf_sys_format_help(helpout, help_flags, " - D: Decoder support\n"); + gf_sys_format_help(helpout, help_flags, " - E: Encoder support\n"); + gf_sys_format_help(helpout, help_flags, "\nNote: Raw output may still be possible even when no output serializer is given\n\n"); + + rawp.type = cp.type = GF_PROP_UINT; + rawp.value.uint = GF_CODECID_RAW; + filep.type = cp.type = GF_PROP_UINT; + filep.value.uint = GF_STREAM_FILE; + stp.type = GF_PROP_UINT; + cp.type = GF_PROP_UINT; + acp.type = GF_PROP_UINT; + szCap[1] = 0; + + while (1) { + u32 i; + const char *lname; + const char *sname; + const char *mime; + u32 rfc4cc; + Bool enc_found = GF_FALSE; + Bool dec_found = GF_FALSE; + Bool dmx_found = GF_FALSE; + Bool mx_found = GF_FALSE; + cp.value.uint = gf_codecid_enum(cidx, &sname, &lname); + cidx++; + if (cp.value.uint == GF_CODECID_NONE) break; + if (cp.value.uint == GF_CODECID_RAW) continue; + if (!sname) break; + + stp.value.uint = gf_codecid_type(cp.value.uint); + acp.value.uint = gf_codecid_alt(cp.value.uint); + + for (i=0; iname); + meta_sep = strchr(szSecName + 7, ':'); + if (meta_sep) meta_sep[0] = 0; + + while (freg->args) { + const GF_FilterArgs *arg = &freg->args[j]; + if (!arg || !arg->arg_name) break; + j++; + + if (arg->arg_default_val && !gf_opts_get_key(szSecName, arg->arg_name)) { + gf_opts_set_key(szSecName, arg->arg_name, arg->arg_default_val); + } + } + } +} + +/********************************************************* + Language file creation / update functions +*********************************************************/ + +static Bool lang_updated = GF_FALSE; +static void gpac_lang_set_key(GF_Config *cfg, const char *sec_name, const char *key_name, const char *key_val) +{ + char szKeyCRC[1024]; + const char *opt = gf_cfg_get_key(cfg, sec_name, key_name); + u32 crc_key = 0; + if (opt) { + sprintf(szKeyCRC, "%s_crc", key_name); + const char *crc_opt = gf_cfg_get_key(cfg, sec_name, szKeyCRC); + if (crc_opt) { + u32 old_crc = atoi(crc_opt); + crc_key = gf_crc_32((u8*)key_val, (u32) strlen(key_val)); + if (old_crc != crc_key) { + gf_sys_format_help(helpout, help_flags, "Warning: description has changed for %s:%s (crc %d - crc in file %d) - please check translation\n", sec_name, key_name, crc_key, old_crc); + } + return; + } + } + if (!opt) { + char szKeyCRCVal[100]; + if (!crc_key) { + sprintf(szKeyCRC, "%s_crc", key_name); + crc_key = gf_crc_32((u8*)key_val, (u32) strlen(key_val)); + } + sprintf(szKeyCRCVal, "%u", crc_key); + gf_cfg_set_key(cfg, sec_name, key_name, key_val); + gf_cfg_set_key(cfg, sec_name, szKeyCRC, szKeyCRCVal); + lang_updated = GF_TRUE; + } +} +static int gpac_make_lang(char *filename) +{ + GF_Config *cfg; + u32 i; + gf_sys_init(GF_MemTrackerNone, NULL); + + session = gf_fs_new_defaults(0); + if (!session) { + gf_sys_format_help(helpout, help_flags, "failed to load session, cannot create language file\n"); + return 1; + } + if (!gf_file_exists(filename)) { + FILE *f = gf_fopen(filename, "wt"); + if (!f) { + gf_sys_format_help(helpout, help_flags, "failed to open %s in write mode\n", filename); + gf_fs_del(session); + return 1; + } + gf_fclose(f); + } + + cfg = gf_cfg_new(NULL, filename); + + //print gpac help + i = 0; + while (gpac_args[i].name) { + gpac_lang_set_key(cfg, "gpac", gpac_args[i].name, gpac_args[i].description); + i++; + } + + //print gpac doc + gpac_lang_set_key(cfg, "gpac", "doc", gpac_doc); + + //print gpac alias doc + gpac_lang_set_key(cfg, "gpac", "alias", gpac_alias); + + //print libgpac core help + const GF_GPACArg *args = gf_sys_get_options(); + i = 0; + + while (args[i].name) { + gpac_lang_set_key(cfg, "core", args[i].name, args[i].description); + i++; + } + + //print properties + i=0; + const GF_BuiltInProperty *prop_info; + while ((prop_info = gf_props_get_description(i))) { + i++; + if (! prop_info->name || !prop_info->description) continue; + gpac_lang_set_key(cfg, "properties", prop_info->name, prop_info->description); + } + + //print filters + u32 count = gf_fs_filters_registers_count(session); + for (i=0; idescription) { + gpac_lang_set_key(cfg, reg->name, "desc", reg->description); + } + if (reg->help) { + gpac_lang_set_key(cfg, reg->name, "help", reg->help); + } + while (reg->args && reg->args[j].arg_name) { + gpac_lang_set_key(cfg, reg->name, reg->args[j].arg_name, reg->args[j].arg_desc); + j++; + } + } + + if (!lang_updated) { + gf_cfg_discard_changes(cfg); + fprintf(stderr, "lang file %s has not been modified\n", filename); + } else { + fprintf(stderr, "lang file generated in %s\n", filename); + } + gf_cfg_del(cfg); + + gf_fs_del(session); + gf_sys_close(); + return 0; +} + + +/********************************************************* + Alias functions +*********************************************************/ + +static GFINLINE void push_arg(char *_arg, Bool _dup) +{ + alias_argv = gf_realloc(alias_argv, sizeof(char**) * (alias_argc+1));\ + alias_argv[alias_argc] = _dup ? gf_strdup(_arg) : _arg; \ + if (_dup) { + if (!args_alloc) args_alloc = gf_list_new(); + gf_list_add(args_alloc, alias_argv[alias_argc]); + } + alias_argc++; +} + +static Bool gpac_expand_alias_arg(char *param, char *prefix, char *suffix, int arg_idx, int argc, char **argv); + +static Bool check_param_extension(char *szArg, int arg_idx, int argc, char **argv) +{ + char *par_start = strstr(szArg, "@{"); + if (par_start) { + char szPar[100]; + Bool ok; + char *par_end = strchr(par_start, '}'); + if (!par_end) { + fprintf(stderr, "Bad format %s for alias parameter, expecting @{N}\n", szArg); + return GF_FALSE; + } + par_end[0] = 0; + strcpy(szPar, par_start+2); + par_start[0] = 0; + + ok = gpac_expand_alias_arg(szPar, szArg, par_end+1, arg_idx, argc, argv); + if (!ok) return GF_FALSE; + par_start[0] = '@'; + par_end[0] = '}'; + return GF_TRUE; + } + //done, push arg + push_arg(szArg, 1); + return GF_TRUE; +} + +static Bool gpac_expand_alias_arg(char *param, char *prefix, char *suffix, int arg_idx, int argc, char **argv) +{ + char szArg[1024]; + char szSepList[2]; + char *oparam = param; + szSepList[0] = separator_set[SEP_LIST]; + szSepList[1] = 0; + + Bool is_list = param[0]=='-'; + Bool is_expand = param[0]=='+'; + + if (is_list || is_expand) param++; + strcpy(szArg, prefix); + while (param) { + u32 idx=0; + u32 last_idx=0; + char *sep = strchr(param, ','); + if (sep) sep[0]=0; + + if ((param[0]=='n') || (param[0]=='N')) { + idx = argc - arg_idx - 1; + if (param[1]=='-') { + u32 diff = atoi(param+2); + if (diff>=idx) { + if (sep) sep[0]=','; + fprintf(stderr, "Bad usage for alias parameter %s: not enough parameters\n", oparam); + return GF_FALSE; + } + idx -= diff; + } + } else { + char *lsep = strchr(param, ':'); + if (lsep) { + lsep[0] = 0; + if (strlen(param)) + idx = atoi(param); + else + idx=1; + + lsep[0] = ':'; + if ((lsep[1]=='n') || (lsep[1]=='N')) { + + last_idx = argc - arg_idx - 1; + if (lsep[2]=='-') { + u32 diff = atoi(lsep+3); + if (diff>=last_idx) { + fprintf(stderr, "Bad usage for alias parameter %s: not enough parameters\n", oparam); + return GF_FALSE; + } + last_idx -= diff; + } + } else { + last_idx = atoi(lsep+1); + } + } else { + idx = atoi(param); + } + } + if (!idx) { + if (sep) sep[0]=','; + fprintf(stderr, "Bad format for alias parameter %s: cannot extract argument index\n", oparam); + return GF_FALSE; + } + if ((int) idx + arg_idx >= argc) { + if (sep) sep[0]=','; + fprintf(stderr, "Bad format for alias parameter %s: argment out of bounds (not enough paramteters?)\n", oparam); + return GF_FALSE; + } + + if (!last_idx) last_idx=idx; + for (; idx<=last_idx;idx++) { + char *an_arg = argv[idx+arg_idx]; + + if (!args_used) args_used = gf_list_new(); + gf_list_add(args_used, an_arg); + + if (is_expand) { + strcpy(szArg, prefix); + strcat(szArg, an_arg); + strcat(szArg, suffix); + + Bool ok = check_param_extension(szArg, arg_idx, argc, argv); + if (!ok) { + if (sep) sep[0]=','; + return GF_FALSE; + } + } else { + strcat(szArg, an_arg); + if (is_list && (idx +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif +static u32 gpac_unit_tests(GF_MemTrackerType mem_track) +{ +#ifdef GPAC_ENABLE_COVERAGE + u32 ucs4_buf[4]; + u8 utf8_buf[7]; + + void *mem = gf_calloc(4, sizeof(u32)); + gf_free(mem); + + if (mem_track == GF_MemTrackerNone) return 0; + + gpac_fsess_task_help(); //for coverage + gf_dm_sess_last_error(NULL); + gf_log_use_color(); + GF_LOG(GF_LOG_INFO, GF_LOG_CORE, ("[CoreUnitTests] performing tests\n")); + + utf8_buf[0] = 'a'; + utf8_buf[1] = 0; + if (! utf8_to_ucs4 (ucs4_buf, 1, (unsigned char *) utf8_buf)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] UCS-4 translation failed for single char\n")); + return 1; + } + utf8_buf[0] = 0xc2; + utf8_buf[1] = 0xa3; + utf8_buf[2] = 'a'; + utf8_buf[3] = 0; + if (! utf8_to_ucs4 (ucs4_buf, 3, (unsigned char *) utf8_buf)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] UCS-4 translation failed for 2-byte + 1-byte char\n")); + return 1; + } + utf8_buf[0] = 0xe0; + utf8_buf[1] = 0xa4; + utf8_buf[2] = 0xb9; + utf8_buf[3] = 0; + if (! utf8_to_ucs4 (ucs4_buf, 3, (unsigned char *) utf8_buf)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] UCS-4 translation failed for 3-byte char\n")); + return 1; + } + utf8_buf[0] = 0xf0; + utf8_buf[1] = 0x90; + utf8_buf[2] = 0x8d; + utf8_buf[3] = 0x88; + utf8_buf[4] = 0; + if (! utf8_to_ucs4 (ucs4_buf, 4, (unsigned char *) utf8_buf)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] UCS-4 translation failed for 4-byte char\n")); + return 1; + } + + utf8_buf[0] = 0xf8; + utf8_buf[1] = 0x80; + utf8_buf[2] = 0x80; + utf8_buf[3] = 0x80; + utf8_buf[4] = 0xaf; + utf8_buf[5] = 0; + if (! utf8_to_ucs4 (ucs4_buf, 5, (unsigned char *) utf8_buf)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] UCS-4 translation failed for 5-byte char\n")); + return 1; + } + utf8_buf[0] = 0xfc; + utf8_buf[1] = 0x80; + utf8_buf[2] = 0x80; + utf8_buf[3] = 0x80; + utf8_buf[4] = 0x80; + utf8_buf[5] = 0xaf; + utf8_buf[6] = 0; + if (! utf8_to_ucs4 (ucs4_buf, 6, (unsigned char *) utf8_buf)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] UCS-4 translation failed for 6-byte char\n")); + return 1; + } + //test error case + utf8_buf[0] = 0xf8; + utf8_to_ucs4 (ucs4_buf, 6, (unsigned char *) utf8_buf); + + char buf[5], obuf[3]; + obuf[0] = 1; + obuf[1] = 2; + u32 res = gf_base16_encode(obuf, 2, buf, 5); + if (res != 4) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] base16 encode fail\n")); + return 1; + } + u32 res2 = gf_base16_decode(buf, res, obuf, 3); + if (res2 != 2) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] base16 decode fail\n")); + return 1; + } + + u8 *zbuf; + u32 osize; + GF_Err e; + u8 *ozbuf; + +#ifndef GPAC_DISABLE_ZLIB + zbuf = gf_strdup("123451234512345123451234512345"); + osize=0; + e = gf_gz_compress_payload(&zbuf, 1 + (u32) strlen(zbuf), &osize); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] zlib compress fail\n")); + gf_free(zbuf); + return 1; + } + ozbuf=NULL; + res=0; + e = gf_gz_decompress_payload(zbuf, osize, &ozbuf, &res); + gf_free(zbuf); + if (ozbuf) gf_free(ozbuf); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] zlib decompress fail\n")); + return 1; + } +#endif + + zbuf = gf_strdup("123451234512345123451234512345"); + osize=0; + e = gf_lz_compress_payload(&zbuf, 1+(u32) strlen(zbuf), &osize); + if (e && (e!= GF_NOT_SUPPORTED)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] lzma compress fail\n")); + gf_free(zbuf); + return 1; + } + ozbuf=NULL; + res=0; + e = gf_lz_decompress_payload(zbuf, osize, &ozbuf, &res); + gf_free(zbuf); + if (ozbuf) gf_free(ozbuf); + if (e && (e!= GF_NOT_SUPPORTED)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] lzma decompress fail\n")); + return 1; + } + + gf_htonl(0xAABBCCDD); + gf_ntohl(0xAABBCCDD); + gf_htons(0xAABB); + gf_ntohs(0xAABB); + gf_errno_str(-1); + + /* these two lock the bash shell in test mode + gf_prompt_set_echo_off(GF_TRUE); + gf_prompt_set_echo_off(GF_FALSE); + */ + + gf_net_set_ntp_shift(-1000); + gf_net_get_ntp_diff_ms(gf_net_get_ntp_ts() ); + gf_net_get_timezone(); + gf_net_get_utc_ts(70, 1, 0, 0, 0, 0); + gf_net_ntp_diff_ms(1000000, 1000000); + gf_lang_get_count(); + gf_lang_get_2cc(2); + GF_Blob b; + b.data = (u8 *) "test"; + b.size = 5; + char url[100]; + u8 *data; + u32 size; + sprintf(url, "gmem://%p", &b); + + gf_sys_profiler_set_callback(NULL, NULL); + + gf_blob_get_data(url, &data, &size); + if (!data || strcmp((char *)data, "test")) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[CoreUnitTests] blob url parsing fail\n")); + return 1; + } + gf_sys_get_battery_state(NULL, NULL, NULL, NULL, NULL); + gf_sys_get_process_id(); + data = (u8 *) gf_log_get_tools_levels(); + if (data) gf_free(data); + + gf_sys_is_quiet(); + gf_sys_get_argv(); + gf_mx_get_num_locks(NULL); + sigint_catched = GF_TRUE; + +#ifdef WIN32 + gpac_sig_handler(CTRL_C_EVENT); +#else + gpac_sig_handler(SIGINT); +#endif + + gf_mkdir("testdir"); + gf_mkdir("testdir/somedir"); + strcpy(url, "testdir/somedir/test.bin"); + FILE *f=gf_fopen(url, "wb"); + fprintf(f, "some test\n"); +#ifdef GPAC_MEMORY_TRACKING + gf_memory_print(); +#endif + gf_fclose(f); + gf_file_modification_time(url); + gf_m2ts_probe_file(url); + + gf_cleanup_dir("testdir"); + gf_rmdir("testdir"); + + //math.c not covered yet by our sample files + GF_Matrix2D mx; + gf_mx2d_init(mx); + gf_mx2d_add_skew(&mx, FIX_ONE, FIX_ONE); + gf_mx2d_add_skew_x(&mx, GF_PI/4); + gf_mx2d_add_skew_y(&mx, GF_PI/4); + GF_Point2D scale, translate; + Fixed rotate; + gf_mx2d_decompose(&mx, &scale, &rotate, &translate); + GF_Rect rc1, rc2; + memset(&rc1, 0, sizeof(GF_Rect)); + memset(&rc2, 0, sizeof(GF_Rect)); + gf_rect_equal(&rc1, &rc2); + + GF_Matrix mat; + gf_mx_init(mat); + Fixed yaw, pitch, roll; + gf_mx_get_yaw_pitch_roll(&mat, &yaw, &pitch, &roll); + gf_mx_ortho_reverse_z(&mat, -20, 20, -20, 20, 0.1, 100.0); + gf_mx_perspective_reverse_z(&mat, 0.76, 1.0, 0.1, 100.0); + + GF_Ray ray; + GF_Vec center, outPoint; + memset(&ray, 0, sizeof(GF_Ray)); + ray.dir.z = FIX_ONE; + memset(¢er, 0, sizeof(GF_Vec)); + gf_ray_hit_sphere(&ray, ¢er, FIX_ONE, &outPoint); + + gf_closest_point_to_line(center, ray.dir, center); + + GF_Plane plane; + plane.d = FIX_ONE; + plane.normal = center; + gf_plane_intersect_line(&plane, ¢er, &ray.dir, &outPoint); + + GF_Vec4 rot, quat; + rot.x = rot.y = 0; + rot.z = FIX_ONE; + rot.q = GF_PI/4; + quat = gf_quat_from_rotation(rot); + gf_quat_get_inv(&quat); + gf_quat_rotate(&quat, &ray.dir); + gf_quat_slerp(quat, quat, FIX_ONE/2); + GF_BBox bbox; + memset(&bbox, 0, sizeof(GF_BBox)); + gf_bbox_equal(&bbox, &bbox); + + GF_Vec v; + v.x = v.y = v.z = 0; + gf_vec_scale_p(&v, 2*FIX_ONE); + + //token.c + char container[1024]; + gf_token_get_strip("12 34{ 56 : }", 0, "{:", " ", container, 1024); + + //netwok.c + char name[GF_MAX_IP_NAME_LEN]; + gf_sk_get_host_name(name); + gf_sk_set_usec_wait(NULL, 1000); + u32 fam; + u16 port; + //to remove once we have rtsp server back + gf_sk_get_local_info(NULL, &port, &fam); + gf_sk_receive_wait(NULL, NULL, 0, &fam, 1); + gf_sk_send_wait(NULL, NULL, 0, 1); + + //path2D + GF_Path *path = gf_path_new(); + gf_path_add_move_to(path, 0, 0); + gf_path_add_quadratic_to(path, 5, 5, 10, 0); + gf_path_point_over(path, 4, 0); + gf_path_del(path); + + //xml dom - to update once we find a way to integrate atsc demux in tests + GF_DOMParser *dom = gf_xml_dom_new(); + gf_xml_dom_parse_string(dom, "test"); + gf_xml_dom_get_error(dom); + gf_xml_dom_get_line(dom); + gf_xml_dom_get_root_nodes_count(dom); + gf_xml_dom_del(dom); + + //downloader - to update once we find a way to integrate atsc demux in tests + GF_DownloadManager *dm = gf_dm_new(NULL); + gf_dm_set_auth_callback(dm, NULL, NULL); + + gf_dm_set_data_rate(dm, 0); + gf_dm_get_data_rate(dm); + gf_dm_set_localcache_provider(dm, NULL, NULL); + + const DownloadedCacheEntry ent = gf_dm_add_cache_entry(dm, "http://localhost/test.dummy", "test", 4, 0, 0, "application/octet-string", GF_FALSE, 1); + + gf_dm_force_headers(dm, ent, "x-GPAC: test\r\n"); + gf_dm_sess_enum_headers(NULL, NULL, NULL, NULL);//this one is deactivated in test mode in httpin because of Date: header + gf_dm_sess_abort(NULL); + gf_dm_del(dm); + + //constants + gf_stream_type_afx_name(GPAC_AFX_3DMC); + //thread + gf_th_stop(NULL); + gf_list_swap(NULL, NULL); + //bitstream + GF_BitStream *bs = gf_bs_new("test", 4, GF_BITSTREAM_READ); + gf_bs_bits_available(bs); + gf_bs_get_bit_offset(bs); + gf_bs_read_vluimsbf5(bs); + gf_bs_del(bs); + //module + gf_module_load_static(NULL); + + gf_mp3_version_name(0); + char tsbuf[188]; + u8 is_pes=GF_TRUE; + memset(tsbuf, 0, 188); + tsbuf[0] = 0x47; + tsbuf[1] = 0x40; + tsbuf[4]=0x00; + tsbuf[5]=0x00; + tsbuf[6]=0x01; + tsbuf[10] = 0x80; + tsbuf[11] = 0xc0; + tsbuf[13] = 0x2 << 4; + gf_m2ts_restamp(tsbuf, 188, 1000, &is_pes); + + + gf_filter_post_task(NULL,NULL,NULL,NULL); + gf_filter_get_num_events_queued(NULL); + gf_filter_get_arg_str(NULL, NULL, NULL); + gf_filter_all_sinks_done(NULL); + + gf_opts_discard_changes(); + + gf_rtp_reset_ssrc(NULL); + gf_rtp_enable_nat_keepalive(NULL, 0); + gf_rtp_stop(NULL); + gf_rtp_streamer_get_payload_type(NULL); + gf_rtsp_unregister_interleave(NULL, 0); + gf_rtsp_reset_aggregation(NULL); + + get_cmd('h'); + gpac_suggest_arg("blcksize"); + gpac_suggest_filter("outf", GF_FALSE, GF_FALSE); + //todo: build tests for these two + gf_filter_pid_negociate_property_str(NULL, NULL, NULL); + gf_filter_pid_negociate_property_dyn(NULL, NULL, NULL); + + gf_props_parse_type("uint"); + //this one is just a wrapper around an internal function + gf_filter_pck_new_copy(NULL, NULL, NULL); + gf_filter_get_max_extra_input_pids(NULL); + gf_filter_remove(NULL); + gf_filter_reconnect_output(NULL); + + gf_audio_fmt_get_cicp_layout(2, 1, 1); + gf_audio_fmt_get_layout_from_cicp(3); + gf_audio_fmt_get_layout_name_from_cicp(3); + gf_audio_fmt_get_cicp_from_layout(GF_AUDIO_CH_FRONT_LEFT|GF_AUDIO_CH_FRONT_RIGHT); + + //old bifs parsing stuff + gf_odf_desc_del(gf_odf_desc_new(GF_ODF_ELEM_MASK_TAG)); + GF_TextConfig *txtc = (GF_TextConfig *)gf_odf_desc_new(GF_ODF_TEXT_CFG_TAG); + gf_odf_get_text_config(NULL, 0, 0, txtc); + gf_odf_dump_txtcfg(txtc, NULL, 0, GF_FALSE); + gf_odf_desc_del((GF_Descriptor *) txtc); + + //stuff only used by vtbdec + gf_media_hevc_read_pps_bs(NULL, NULL); + gf_media_hevc_read_sps_bs(NULL, NULL); + gf_media_hevc_read_vps_bs(NULL, NULL); + gf_mpegv12_get_config(NULL, 0, NULL); + + //hinting stuff + GF_HintPacket *hpck = gf_isom_hint_pck_new(GF_ISOM_BOX_TYPE_RTCP_STSD); + gf_isom_hint_pck_length(hpck); + gf_isom_hint_pck_size(hpck); + GF_BitStream *hbs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + gf_isom_hint_pck_write(hpck, hbs); + u8 *hbuf; + u32 hsize; + gf_bs_get_content(hbs, &hbuf, &hsize); + gf_bs_del(hbs); + hbs = gf_bs_new(hbuf, hsize, GF_BITSTREAM_READ); + gf_isom_hint_pck_read(hpck, hbs); + gf_bs_del(hbs); + gf_free(hbuf); + gf_isom_hint_pck_del(hpck); + + gf_isom_last_error(NULL); + gf_isom_get_media_time(NULL, 0, 0, NULL); + gf_isom_get_sample_description_index(NULL, 0, 0); + + +#endif + return 0; +} + +static Bool revert_cache_file(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) +{ + const char *url; + GF_Config *cached; + if (strncmp(item_name, "gpac_cache_", 11)) return GF_FALSE; + cached = gf_cfg_new(NULL, item_path); + url = gf_cfg_get_key(cached, "cache", "url"); + if (url) url = strstr(url, "://"); + if (url) { + u32 i, len, dir_len=0, k=0; + char *dst_name; + char *sep; + + sep = strstr(item_path, "gpac_cache_"); + if (sep) { + sep[0] = 0; + dir_len = (u32) strlen(item_path); + sep[0] = 'g'; + } + url+=3; + len = (u32) strlen(url); + dst_name = gf_malloc(len+dir_len+1); + memset(dst_name, 0, len+dir_len+1); + + strncpy(dst_name, item_path, dir_len); + k=dir_len; + for (i=0; ifilep) return GF_BAD_PARAM; + gf_fseek(ioctx->filep, offset, whence); + return GF_OK; +} +static u32 fio_read(GF_FileIO *fileio, u8 *buffer, u32 bytes) +{ + FileIOCtx *ioctx = gf_fileio_get_udta(fileio); + if (!ioctx || !ioctx->filep) return 0; + return (u32) gf_fread(buffer, bytes, ioctx->filep); +} +static u32 fio_write(GF_FileIO *fileio, u8 *buffer, u32 bytes) +{ + FileIOCtx *ioctx = gf_fileio_get_udta(fileio); + if (!ioctx || !ioctx->filep) return 0; + if (!bytes) { + fflush(ioctx->filep); + return 0; + } + return (u32) gf_fwrite(buffer, bytes, ioctx->filep); +} +static s64 fio_tell(GF_FileIO *fileio) +{ + FileIOCtx *ioctx = gf_fileio_get_udta(fileio); + if (!ioctx || !ioctx->filep) return -1; + return gf_ftell(ioctx->filep); +} +static Bool fio_eof(GF_FileIO *fileio) +{ + FileIOCtx *ioctx = gf_fileio_get_udta(fileio); + if (!ioctx || !ioctx->filep) return GF_TRUE; + return feof(ioctx->filep); +} +static int fio_printf(GF_FileIO *fileio, const char *format, va_list args) +{ + FileIOCtx *ioctx = gf_fileio_get_udta(fileio); + if (!ioctx || !ioctx->filep) return -1; + return vfprintf(ioctx->filep, format, args); +} + +static GF_FileIO *fio_open(GF_FileIO *fileio_ref, const char *url, const char *mode, GF_Err *out_err) +{ + GF_FileIO *gfio; + FileIOCtx *ioctx; + u32 i, count; + u64 file_size; + Bool no_concatenate = GF_FALSE; + FileIOCtx *ioctx_ref = gf_fileio_get_udta(fileio_ref); + + *out_err = GF_OK; + + if (!strcmp(mode, "ref")) { + ioctx_ref->nb_refs++; + return fileio_ref; + } + if (!strcmp(mode, "unref")) { + if (!ioctx_ref->nb_refs) return NULL; + ioctx_ref->nb_refs--; + if (ioctx_ref->nb_refs) + return fileio_ref; + + url = NULL; + } + + if (!strcmp(mode, "url")) { + if (!url) return NULL; + GF_SAFEALLOC(ioctx, FileIOCtx); + if (!ioctx) return NULL; + ioctx->path = gf_url_concatenate(ioctx_ref->path, url); + gfio = gf_fileio_new(ioctx->path, ioctx, fio_open, fio_seek, fio_read, fio_write, fio_tell, fio_eof, fio_printf); + if (!gfio) { + if (ioctx->path) gf_free(ioctx->path); + gf_free(ioctx); + return NULL; + } + //remember it but no need to keep a ref on it + gf_list_add(all_gfio_defined, gfio); + return gfio; + } + if (!strcmp(mode, "probe")) { + if (!gf_file_exists(url)) *out_err = GF_URL_ERROR; + return NULL; + } + + if (!url) { + if (ioctx_ref->filep) gf_fclose(ioctx_ref->filep); + ioctx_ref->filep = NULL; + + if (!ioctx_ref->nb_refs) { + gf_list_del_item(all_gfio_defined, fileio_ref); + gf_fileio_del(fileio_ref); + if (ioctx_ref->path) gf_free(ioctx_ref->path); + gf_free(ioctx_ref); + } + return NULL; + } + + //file handle not opened, we can use the current gfio + if (!ioctx_ref->filep && (!strnicmp(url, "gfio://", 7) || !strcmp(url, ioctx_ref->path)) ) { + ioctx_ref->filep = gf_fopen(ioctx_ref->path, mode); + if (!ioctx_ref->filep) { + *out_err = GF_IO_ERR; + return NULL; + } + file_size = gf_fsize(ioctx_ref->filep); + //in test mode we want to use our ftell and fseek wrappers + if (strchr(mode, 'r')) { + gf_fileio_set_stats(fileio_ref, file_size, file_size, GF_TRUE, 0); + } + return fileio_ref; + } + + //file handle already open (file is being opened twice), create a new gfio or check if we have already created one + gfio = NULL; + ioctx = NULL; + count = gf_list_count(all_gfio_defined); + for (i=0; ipath, url)) { + if (ioctx->filep) { + no_concatenate = GF_TRUE; + ioctx = NULL; + } + gfio = a_gfio; + break; + } + ioctx = NULL; + } + if (!ioctx) { + GF_SAFEALLOC(ioctx, FileIOCtx); + if (!ioctx) { + *out_err = GF_OUT_OF_MEM; + return NULL; + } + if (strnicmp(url, "gfio://", 7)) { + if (no_concatenate) + ioctx->path = gf_strdup(url); + else + ioctx->path = gf_url_concatenate(ioctx_ref->path, url); + } else { + ioctx->path = gf_strdup(ioctx_ref->path); + } + gfio = gf_fileio_new(ioctx->path, ioctx, fio_open, fio_seek, fio_read, fio_write, fio_tell, fio_eof, fio_printf); + if (!gfio) { + if (ioctx->path) gf_free(ioctx->path); + gf_free(ioctx); + *out_err = GF_OUT_OF_MEM; + } + } + + if (strnicmp(url, "gfio://", 7)) { + ioctx->filep = gf_fopen(ioctx->path, mode); + } else { + ioctx->filep = gf_fopen(ioctx_ref->path, mode); + } + + if (!ioctx->filep) { + *out_err = GF_IO_ERR; + gf_list_del_item(all_gfio_defined, gfio); + gf_fileio_del(gfio); + if (ioctx->path) gf_free(ioctx->path); + gf_free(ioctx); + return NULL; + } + + file_size = gf_fsize(ioctx->filep); + if (strchr(mode, 'r')) + gf_fileio_set_stats(gfio, file_size,file_size, GF_TRUE, 0); + return gfio; +} + + +static const char *make_fileio(const char *inargs, const char **out_arg, Bool is_input, GF_Err *e) +{ + FileIOCtx *ioctx; + GF_FileIO *fio; + char *sep = (char *) gf_url_colon_suffix(inargs); + *out_arg = NULL; + if (sep) sep[0] = 0; + + GF_SAFEALLOC(ioctx, FileIOCtx); + if (!ioctx) return NULL; + ioctx->path = gf_strdup(inargs); + if (!ioctx->path) { + gf_free(ioctx); + return NULL; + } + if (sep) { + sep[0] = ':'; + *out_arg = sep+1; + } + fio = gf_fileio_new(ioctx->path, ioctx, fio_open, fio_seek, fio_read, fio_write, fio_tell, fio_eof, fio_printf); + if (!fio) { + gf_free(ioctx->path); + gf_free(ioctx); + *e = GF_OUT_OF_MEM; + return NULL; + } + if (!all_gfio_defined) { + all_gfio_defined = gf_list_new(); + if (!all_gfio_defined) return NULL; + } + gf_list_add(all_gfio_defined, fio); + //keep alive until end + ioctx->nb_refs = 1; + return gf_fileio_url(fio); +} + +static void cleanup_file_io() +{ + if (!all_gfio_defined) return; + while (gf_list_count(all_gfio_defined)) { + GF_FileIO *gfio = gf_list_pop_back(all_gfio_defined); + FileIOCtx *ioctx = gf_fileio_get_udta(gfio); + gf_fileio_del(gfio); + + if (ioctx->filep) { + fprintf(stderr, "Warning: file IO for %s still opened!\n", ioctx->path); + gf_fclose(ioctx->filep); + } + if (ioctx->path) gf_free(ioctx->path); + gf_free(ioctx); + } + gf_list_del(all_gfio_defined); + all_gfio_defined = NULL; +} + diff --git a/applications/mp4box/Makefile b/applications/mp4box/Makefile index 83f7bad..b3f20be 100644 --- a/applications/mp4box/Makefile +++ b/applications/mp4box/Makefile @@ -2,24 +2,29 @@ include ../../config.mak vpath %.c $(SRC_PATH)/applications/mp4box -CFLAGS= $(OPTFLAGS) -DGPAC_HAVE_CONFIG_H -I"$(SRC_PATH)/include" +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif LINKFLAGS=-L../../bin/gcc -L../../extra_lib/lib/gcc +ifeq ($(CONFIG_DARWIN),yes) +#LINKFLAGS+= -Wl,-rpath,'@loader_path' +else +LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath-link,../../bin/gcc +endif ifeq ($(STATICBUILD),yes) ##include static modules and other deps for libgpac include ../../static.mak -LINKFLAGS+=-lgpac_static +LINKFLAGS+=$(shell pkg-config ../../gpac.pc --libs --static | sed 's/-lgpac //' ) LINKFLAGS+= $(GPAC_SH_FLAGS) LINKFLAGS+=$(EXTRALIBS) else @@ -47,29 +52,28 @@ endif endif - #common obj -OBJS=main.o filedump.o fileimport.o +OBJS=main.o filedump.o fileimport.o ifeq ($(DISABLE_STREAMING),no) OBJS+=live.o endif -SRCS := $(OBJS:.o=.c) +SRCS := $(OBJS:.o=.c) all: $(PROG) $(PROG): $(OBJS) $(CC) -o ../../bin/gcc/$@ $(OBJS) $(LINKFLAGS) $(LDFLAGS) -clean: +clean: rm -f $(OBJS) ../../bin/gcc/$(PROG) dep: depend depend: - rm -f .depend + rm -f .depend $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend distclean: clean diff --git a/applications/mp4box/filedump.c b/applications/mp4box/filedump.c index c9499ba..a56f8b9 100644 --- a/applications/mp4box/filedump.c +++ b/applications/mp4box/filedump.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / mp4box application @@ -23,7 +23,7 @@ * */ -#include +#include "mp4box.h" #if defined(GPAC_DISABLE_ISOM) || defined(GPAC_DISABLE_ISOM_WRITE) @@ -43,8 +43,6 @@ #include #include #include -/*for asctime and gmtime*/ -#include /*ISO 639 languages*/ #include #include @@ -53,19 +51,17 @@ #include #endif #include -#include #include +/*for built-in box printing*/ +#include extern u32 swf_flags; extern Float swf_flatten_angle; extern GF_FileType get_file_type_by_ext(char *inName); +extern u32 fs_dump_flags; void scene_coding_log(void *cbk, GF_LOG_Level log_level, GF_LOG_Tool log_tool, const char *fmt, va_list vlist); -#if !defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_MEDIA_IMPORT) -GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, Double force_fps, u32 frames_per_sample); -#endif - void PrintLanguages() { u32 i=0, count = gf_lang_get_count(); @@ -86,8 +82,7 @@ static const char *GetLanguage(char *lcode) GF_Err dump_isom_cover_art(GF_ISOFile *file, char *inName, Bool is_final_name) { - const char *tag; - char szName[1024]; + const u8 *tag; FILE *t; u32 tag_len; GF_Err e = gf_isom_apple_get_tag(file, GF_ISOM_ITUNE_COVER_ART, &tag, &tag_len); @@ -100,6 +95,7 @@ GF_Err dump_isom_cover_art(GF_ISOFile *file, char *inName, Bool is_final_name) } if (inName) { + char szName[1024]; if (is_final_name) { strcpy(szName, inName); } else { @@ -113,40 +109,15 @@ GF_Err dump_isom_cover_art(GF_ISOFile *file, char *inName, Bool is_final_name) } else { t = stdout; } - gf_fwrite(tag, tag_len & 0x7FFFFFFF, 1, t); + gf_fwrite(tag, tag_len & 0x7FFFFFFF, t); if (inName) gf_fclose(t); return GF_OK; } -#ifndef GPAC_DISABLE_ISOM_WRITE - -GF_Err set_cover_art(GF_ISOFile *file, char *inName) -{ - GF_Err e; - char *tag, *ext; - FILE *t; - u32 tag_len; - t = gf_fopen(inName, "rb"); - gf_fseek(t, 0, SEEK_END); - tag_len = (u32) gf_ftell(t); - gf_fseek(t, 0, SEEK_SET); - tag = gf_malloc(sizeof(char) * tag_len); - tag_len = (u32) fread(tag, sizeof(char), tag_len, t); - gf_fclose(t); - - ext = strrchr(inName, '.'); - if (!stricmp(ext, ".png")) tag_len |= 0x80000000; - e = gf_isom_apple_set_tag(file, GF_ISOM_ITUNE_COVER_ART, tag, tag_len); - gf_free(tag); - return e; -} - -#endif - #ifndef GPAC_DISABLE_SCENE_DUMP -GF_Err dump_isom_scene(char *file, char *inName, Bool is_final_name, GF_SceneDumpFormat dump_mode, Bool do_log) +GF_Err dump_isom_scene(char *file, char *inName, Bool is_final_name, GF_SceneDumpFormat dump_mode, Bool do_log, Bool no_odf_conv) { GF_Err e; GF_SceneManager *ctx; @@ -178,12 +149,16 @@ GF_Err dump_isom_scene(char *file, char *inName, Bool is_final_name, GF_SceneDum gf_sg_del(sg); return e; } + if (no_odf_conv) + gf_isom_disable_odf_conversion(load.isom, GF_TRUE); + } else if (ftype==GF_FILE_TYPE_LSR_SAF) { load.isom = gf_isom_open("saf_conv", GF_ISOM_WRITE_EDIT, NULL); #ifndef GPAC_DISABLE_MEDIA_IMPORT - if (load.isom) - e = import_file(load.isom, file, 0, 0, 0); - else + if (load.isom) { + GF_Fraction _frac = {0,0}; + e = import_file(load.isom, file, 0, _frac, 0, NULL, NULL, 0); + } else #else fprintf(stderr, "Warning: GPAC was compiled without Media Import support\n"); #endif @@ -234,7 +209,7 @@ GF_Err dump_isom_scene(char *file, char *inName, Bool is_final_name, GF_SceneDum #ifndef GPAC_DISABLE_SCENE_STATS -static void dump_stats(FILE *dump, GF_SceneStatistics *stats) +static void dump_stats(FILE *dump, const GF_SceneStatistics *stats) { u32 i; s32 created, count, draw_created, draw_count, deleted, draw_deleted; @@ -394,7 +369,7 @@ void dump_isom_scene_stats(char *file, char *inName, Bool is_final_name, u32 sta e = gf_sm_load_init(&load); if (!e) e = gf_sm_load_run(&load); gf_sm_load_done(&load); - if (e) goto exit; + if (e<0) goto exit; if (inName) { strcpy(szBuf, inName); @@ -413,8 +388,12 @@ void dump_isom_scene_stats(char *file, char *inName, Bool is_final_name, u32 sta fprintf(stderr, "Analysing Scene\n"); fprintf(dump, "\n"); - fprintf(dump, "\n"); - fprintf(dump, "\n", file, (stat_level==1) ? "full scene" : ((stat_level==2) ? "AccessUnit based" : "SceneGraph after each AU")); + fprintf(dump, "\n"); + + fprintf(dump, "\n", gf_file_basename(file), (stat_level==1) ? "full scene" : ((stat_level==2) ? "AccessUnit based" : "SceneGraph after each AU")); sm = gf_sm_stats_new(); @@ -456,9 +435,9 @@ void dump_isom_scene_stats(char *file, char *inName, Bool is_final_name, u32 sta if (e) goto exit; } if (stat_level==2) { - fprintf(dump, "\n", au->owner->ESID, LLD_CAST au->timing); + fprintf(dump, "\n", au->owner->ESID, au->timing); } else { - fprintf(dump, "\n", au->owner->ESID, LLD_CAST au->timing); + fprintf(dump, "\n", au->owner->ESID, au->timing); } /*dump stats*/ dump_stats(dump, gf_sm_stats_get(sm) ); @@ -548,60 +527,21 @@ static void PrintNodeSFField(u32 type, void *far_ptr) } #endif -void PrintNode(const char *name, u32 graph_type) +#ifndef GPAC_DISABLE_VRML +static void do_print_node(GF_Node *node, GF_SceneGraph *sg, const char *name, u32 graph_type, Bool is_nodefield, Bool do_cov) { -#ifdef GPAC_DISABLE_VRML - fprintf(stderr, "VRML/MPEG-4/X3D scene graph is disabled in this build of GPAC\n"); - return; -#else - const char *nname, *std_name; - char szField[1024]; - GF_Node *node; - GF_SceneGraph *sg; - u32 tag, nbF, i; + u32 nbF, i; GF_FieldInfo f; #ifndef GPAC_DISABLE_BIFS u8 qt, at; Fixed bmin, bmax; u32 nbBits; #endif /*GPAC_DISABLE_BIFS*/ - Bool is_nodefield = 0; - - char *sep = strchr(name, '.'); - if (sep) { - strcpy(szField, sep+1); - sep[0] = 0; - is_nodefield = 1; - } - - if (graph_type==1) { -#ifndef GPAC_DISABLE_X3D - tag = gf_node_x3d_type_by_class_name(name); - std_name = "X3D"; -#else - fprintf(stderr, "X3D node printing is not supported (X3D support disabled)\n"); - return; -#endif - } else { - tag = gf_node_mpeg4_type_by_class_name(name); - std_name = "MPEG4"; - } - if (!tag) { - fprintf(stderr, "Unknown %s node %s\n", std_name, name); - return; - } - sg = gf_sg_new(); - node = gf_node_new(sg, tag); - gf_node_register(node, NULL); - nname = gf_node_get_class_name(node); - if (!node) { - fprintf(stderr, "Node %s not supported in current built\n", nname); - return; - } nbF = gf_node_get_field_count(node); if (is_nodefield) { + char szField[1024]; u32 tfirst, tlast; if (gf_node_get_field_by_name(node, szField, &f) != GF_OK) { fprintf(stderr, "Field %s is not a member of node %s\n", szField, name); @@ -628,8 +568,29 @@ void PrintNode(const char *name, u32 graph_type) } return; } + if (do_cov) { + u32 ndt; + if (graph_type==0) { + u32 all; + gf_node_mpeg4_type_by_class_name(name); + gf_bifs_get_child_table(node); + all = gf_node_get_num_fields_in_mode(node, GF_SG_FIELD_CODING_ALL); + for (i=0; icount; i++) { - if (i) fprintf(stderr, " "); - gf_sg_vrml_mf_get_item(f.far_ptr, f.fieldType, &ptr, i); + for (j=0; jcount; j++) { + if (j) fprintf(stderr, " "); + gf_sg_vrml_mf_get_item(f.far_ptr, f.fieldType, &ptr, j); PrintNodeSFField(sftype, ptr); } fprintf(stderr, "]"); @@ -673,18 +634,72 @@ void PrintNode(const char *name, u32 graph_type) } #endif /*GPAC_DISABLE_BIFS*/ fprintf(stderr, "\n"); + + if (do_cov) { + gf_node_get_field_by_name(node, (char *) f.name, &f); + } } fprintf(stderr, "}\n\n"); +} +#endif + + +void PrintNode(const char *name, u32 graph_type) +{ +#ifdef GPAC_DISABLE_VRML + fprintf(stderr, "VRML/MPEG-4/X3D scene graph is disabled in this build of GPAC\n"); + return; +#else + const char *std_name; + GF_Node *node; + GF_SceneGraph *sg; + u32 tag; +#ifndef GPAC_DISABLE_BIFS +#endif /*GPAC_DISABLE_BIFS*/ + Bool is_nodefield = 0; + + char *sep = strchr(name, '.'); + if (sep) { + sep[0] = 0; + is_nodefield = 1; + } + + if (graph_type==1) { +#ifndef GPAC_DISABLE_X3D + tag = gf_node_x3d_type_by_class_name(name); + std_name = "X3D"; +#else + fprintf(stderr, "X3D node printing is not supported (X3D support disabled)\n"); + return; +#endif + } else { + tag = gf_node_mpeg4_type_by_class_name(name); + std_name = "MPEG4"; + } + if (!tag) { + fprintf(stderr, "Unknown %s node %s\n", std_name, name); + return; + } + + sg = gf_sg_new(); + node = gf_node_new(sg, tag); + gf_node_register(node, NULL); + name = gf_node_get_class_name(node); + if (!node) { + fprintf(stderr, "Node %s not supported in current built\n", name); + return; + } + do_print_node(node, sg, name, graph_type, is_nodefield, GF_FALSE); + gf_node_unregister(node, NULL); gf_sg_del(sg); #endif /*GPAC_DISABLE_VRML*/ } -void PrintBuiltInNodes(u32 graph_type) +void PrintBuiltInNodes(u32 graph_type, Bool dump_nodes) { #if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_X3D) && !defined(GPAC_DISABLE_SVG) - GF_Node *node; GF_SceneGraph *sg; u32 i, nb_in, nb_not_in, start_tag, end_tag; @@ -724,10 +739,14 @@ void PrintBuiltInNodes(u32 graph_type) fprintf(stderr, "Available MPEG-4 nodes in this build (encoding/decoding/dumping):\n"); } for (i=start_tag; i\n"); //index 0 is our internal unknown box handler for (i=1; i\n"); } @@ -766,9 +803,9 @@ void dump_isom_rtp(GF_ISOFile *file, char *inName, Bool is_final_name) u32 i, j, size; FILE *dump; const char *sdp; - char szBuf[1024]; if (inName) { + char szBuf[1024]; strcpy(szBuf, inName); if (!is_final_name) strcat(szBuf, "_rtp.xml"); dump = gf_fopen(szBuf, "wt"); @@ -791,9 +828,11 @@ void dump_isom_rtp(GF_ISOFile *file, char *inName, Bool is_final_name) gf_isom_sdp_track_get(file, i+1, &sdp, &size); fprintf(dump, "%s", sdp); +#ifndef GPAC_DISABLE_ISOM_HINTING for (j=0; j\n"); } fprintf(dump, "\n"); @@ -802,14 +841,23 @@ void dump_isom_rtp(GF_ISOFile *file, char *inName, Bool is_final_name) #endif -void dump_isom_timestamps(GF_ISOFile *file, char *inName, Bool is_final_name) +void dump_isom_timestamps(GF_ISOFile *file, char *inName, Bool is_final_name, u32 dump_mode) { u32 i, j, k, count; - Bool has_error; + Bool has_ctts_error, is_fragmented=GF_FALSE; FILE *dump; - char szBuf[1024]; + Bool skip_offset = ((dump_mode==2) || (dump_mode==4)) ? GF_TRUE : GF_FALSE; + Bool check_ts = ((dump_mode==3) || (dump_mode==4)) ? GF_TRUE : GF_FALSE; + struct _ts_info { + u64 dts; + s64 cts; + }; + struct _ts_info *timings = NULL; + u32 nb_timings=0, nb_timings_alloc = 0; + if (inName) { + char szBuf[1024]; strcpy(szBuf, inName); if (!is_final_name) strcat(szBuf, "_ts.txt"); dump = gf_fopen(szBuf, "wt"); @@ -820,20 +868,37 @@ void dump_isom_timestamps(GF_ISOFile *file, char *inName, Bool is_final_name) } else { dump = stdout; } + if (gf_isom_is_fragmented(file)) + is_fragmented = GF_TRUE; - has_error = 0; + has_ctts_error = GF_FALSE; for (i=0; iDTS; cts = dts + (s32) samp->CTS_Offset; - fprintf(dump, "Sample %d\tDTS "LLD"\tCTS "LLD"\t%d\t%d\t"LLD"\t%d\t%d\t%d\t%d\t%d\t%d\t%d", j+1, LLD_CAST dts, LLD_CAST cts, samp->dataLength, samp->IsRAP, offset, isLeading, dependsOn, dependedOn, redundant, is_rap, has_roll, roll_distance); - if (ctsdataLength, samp->IsRAP); + + if (!skip_offset) + fprintf(dump, "\t"LLU, offset); + + fprintf(dump, "\t%d\t%d\t%d\t%d\t%d\t%d\t%d", isLeading, dependsOn, dependedOn, redundant, is_rap, roll_type, roll_distance); + + if (cts< (s64) dts) { if (has_cts_offset==2) { - if (cts_dts_shift && (cts+cts_dts_shiftDTS; - acts = adts + (s32) samp->CTS_Offset; - - if (adts==dts) { + if (timings[k].dts==dts) { fprintf(dump, " #SAME DTS USED!!!"); - has_error = 1; + has_ctts_error = 1; } - if (acts==cts) { + if (timings[k].cts==cts) { fprintf(dump, " #SAME CTS USED!!! "); - has_error = 1; + has_ctts_error = 1; } - - gf_isom_sample_del(&samp); } + timings[nb_timings].dts = dts; + timings[nb_timings].cts = cts; + nb_timings++; } + gf_isom_sample_del(&samp); + + if (is_fragmented) { + fprintf(dump, "\t%d", gf_isom_sample_is_fragment_start(file, i+1, j+1, NULL) ); + } fprintf(dump, "\n"); - gf_set_progress("Analysing Track Timing", j+1, count); + gf_set_progress("Dumping track timing", j+1, count); } fprintf(dump, "\n\n"); - gf_set_progress("Analysing Track Timing", count, count); + gf_set_progress("Dumping track timing", count, count); } + if (timings) gf_free(timings); + if (inName) gf_fclose(dump); - if (has_error) fprintf(stderr, "\tFile has CTTS table errors\n"); + if (has_ctts_error) fprintf(stderr, "\tFile has CTTS table errors\n"); } -static u32 read_nal_size_hdr(char *ptr, u32 nalh_size) +static u32 read_nal_size_hdr(u8 *ptr, u32 nalh_size) { u32 nal_size=0; u32 v = nalh_size; @@ -909,340 +980,31 @@ static u32 read_nal_size_hdr(char *ptr, u32 nalh_size) } #ifndef GPAC_DISABLE_AV_PARSERS - -static void dump_sei(FILE *dump, u8 *ptr, u32 ptr_size, Bool is_hevc) -{ - u32 sei_idx=0; - u32 i=2; - fprintf(dump, " SEI=\""); - while (i+1 < ptr_size) { - u32 sei_type = 0; - u32 sei_size = 0; - while (ptr[i] == 0xFF) { - sei_type+= 255; - i++; - } - sei_type += ptr[i]; - i++; - while (ptr[i] == 0xFF) { - sei_size += 255; - i++; - } - sei_size += ptr[i]; - i++; - i+=sei_size; - - if (sei_idx) fprintf(dump, ","); - fprintf(dump, "(type=%u, size=%u)", sei_type, sei_size); - sei_idx++; - if (ptr[i]== 0x80) { - i=ptr_size; - break; - } - } - if (i!=ptr_size) fprintf(dump, "(garbage at end)"); - fprintf(dump, "\""); -} - -static void dump_nalu(FILE *dump, char *ptr, u32 ptr_size, Bool is_svc, HEVCState *hevc, AVCState *avc, u32 nalh_size) -{ - s32 res; - u8 type; - u8 dependency_id, quality_id, temporal_id; - u8 track_ref_index; - s8 sample_offset; - u32 data_offset, idx, data_size; - GF_BitStream *bs; - - if (!ptr_size) { - fprintf(dump, "error=\"invalid nal size 0\""); - return; - } - if (hevc) { -#ifndef GPAC_DISABLE_HEVC - res = gf_media_hevc_parse_nalu(ptr, ptr_size, hevc, &type, &temporal_id, &quality_id); - - fprintf(dump, "code=\"%d\" type=\"", type); - switch (type) { - case GF_HEVC_NALU_SLICE_TRAIL_N: - fputs("TRAIL_N slice segment", dump); - break; - case GF_HEVC_NALU_SLICE_TRAIL_R: - fputs("TRAIL_R slice segment", dump); - break; - case GF_HEVC_NALU_SLICE_TSA_N: - fputs("TSA_N slice segment", dump); - break; - case GF_HEVC_NALU_SLICE_TSA_R: - fputs("TSA_R slice segment", dump); - break; - case GF_HEVC_NALU_SLICE_STSA_N: - fputs("STSA_N slice segment", dump); - break; - case GF_HEVC_NALU_SLICE_STSA_R: - fputs("STSA_R slice segment", dump); - break; - case GF_HEVC_NALU_SLICE_RADL_N: - fputs("RADL_N slice segment", dump); - break; - case GF_HEVC_NALU_SLICE_RADL_R: - fputs("RADL_R slice segment", dump); - break; - case GF_HEVC_NALU_SLICE_RASL_N: - fputs("RASL_N slice segment", dump); - break; - case GF_HEVC_NALU_SLICE_RASL_R: - fputs("RASL_R slice segment", dump); - break; - case GF_HEVC_NALU_SLICE_BLA_W_LP: - fputs("Broken link access slice (W LP)", dump); - break; - case GF_HEVC_NALU_SLICE_BLA_W_DLP: - fputs("Broken link access slice (W DLP)", dump); - break; - case GF_HEVC_NALU_SLICE_BLA_N_LP: - fputs("Broken link access slice (N LP)", dump); - break; - case GF_HEVC_NALU_SLICE_IDR_W_DLP: - fputs("IDR slice (W DLP)", dump); - break; - case GF_HEVC_NALU_SLICE_IDR_N_LP: - fputs("IDR slice (N LP)", dump); - break; - case GF_HEVC_NALU_SLICE_CRA: - fputs("CRA slice", dump); - break; - - case GF_HEVC_NALU_VID_PARAM: - gf_media_hevc_read_vps(ptr, ptr_size, hevc); - fputs("Video Parameter Set", dump); - break; - case GF_HEVC_NALU_SEQ_PARAM: - gf_media_hevc_read_sps(ptr, ptr_size, hevc); - fputs("Sequence Parameter Set", dump); - break; - case GF_HEVC_NALU_PIC_PARAM: - gf_media_hevc_read_pps(ptr, ptr_size, hevc); - fputs("Picture Parameter Set", dump); - break; - case GF_HEVC_NALU_ACCESS_UNIT: - fputs("AU Delimiter", dump); - fprintf(dump, "\" primary_pic_type=\"%d", ptr[2] >> 5); - break; - case GF_HEVC_NALU_END_OF_SEQ: - fputs("End of Sequence", dump); - break; - case GF_HEVC_NALU_END_OF_STREAM: - fputs("End of Stream", dump); - break; - case GF_HEVC_NALU_FILLER_DATA: - fputs("Filler Data", dump); - break; - case GF_HEVC_NALU_SEI_PREFIX: - fputs("SEI Prefix", dump); - break; - case GF_HEVC_NALU_SEI_SUFFIX: - fputs("SEI Suffix", dump); - break; - case 48: - fputs("HEVCAggregator", dump); - break; - case 49: - fputs("HEVCExtractor", dump); - track_ref_index = (u8) ptr[3]; - sample_offset = (s8) ptr[4]; - data_offset = read_nal_size_hdr(&ptr[5], nalh_size); - data_size = read_nal_size_hdr(&ptr[5+nalh_size], nalh_size); - fprintf(dump, "\" track_ref_index=\"%d\" sample_offset=\"%d\" data_offset=\"%d\" data_size=\"%d", track_ref_index, sample_offset, data_offset, data_size); - break; - default: - fprintf(dump, "UNKNOWN (parsing return %d)", res); - break; - } - fputs("\"", dump); - - if ((type==GF_HEVC_NALU_SEI_PREFIX) || (type==GF_HEVC_NALU_SEI_SUFFIX)) { - dump_sei(dump, (u8 *) ptr, ptr_size, hevc ? GF_TRUE : GF_FALSE); - } - - if (types_info.slice_type==GF_HEVC_SLICE_TYPE_I) ? "I" : (hevc->s_info.slice_type==GF_HEVC_SLICE_TYPE_P) ? "P" : (hevc->s_info.slice_type==GF_HEVC_SLICE_TYPE_B) ? "B" : "Unknown", hevc->s_info.poc); - fprintf(dump, " first_slice_in_pic=\"%d\"", hevc->s_info.first_slice_segment_in_pic_flag); - fprintf(dump, " dependent_slice_segment=\"%d\"", hevc->s_info.dependent_slice_segment_flag); - } - - fprintf(dump, " layer_id=\"%d\" temporal_id=\"%d\"", quality_id, temporal_id); - -#endif //GPAC_DISABLE_HEVC - return; - } - - bs = gf_bs_new(ptr, ptr_size, GF_BITSTREAM_READ); - type = gf_bs_read_u8(bs) & 0x1F; - fprintf(dump, "code=\"%d\" type=\"", type); - res = 0; - switch (type) { - case GF_AVC_NALU_NON_IDR_SLICE: - res = gf_media_avc_parse_nalu(bs, ptr[0], avc); - fputs("Non IDR slice", dump); - - if (res>=0) - fprintf(dump, "\" poc=\"%d", avc->s_info.poc); - break; - case GF_AVC_NALU_DP_A_SLICE: - fputs("DP Type A slice", dump); - break; - case GF_AVC_NALU_DP_B_SLICE: - fputs("DP Type B slice", dump); - break; - case GF_AVC_NALU_DP_C_SLICE: - fputs("DP Type C slice", dump); - break; - case GF_AVC_NALU_IDR_SLICE: - res = gf_media_avc_parse_nalu(bs, ptr[0], avc); - fputs("IDR slice", dump); - if (res>=0) - fprintf(dump, "\" poc=\"%d", avc->s_info.poc); - break; - case GF_AVC_NALU_SEI: - fputs("SEI Message", dump); - break; - case GF_AVC_NALU_SEQ_PARAM: - fputs("SequenceParameterSet", dump); - idx = gf_media_avc_read_sps(ptr, ptr_size, avc, 0, NULL); - assert (idx >= 0); - fprintf(dump, "\" sps_id=\"%d", idx); - fprintf(dump, "\" frame_mbs_only_flag=\"%d", avc->sps->frame_mbs_only_flag); - fprintf(dump, "\" mb_adaptive_frame_field_flag=\"%d", avc->sps->mb_adaptive_frame_field_flag); - fprintf(dump, "\" vui_parameters_present_flag=\"%d", avc->sps->vui_parameters_present_flag); - fprintf(dump, "\" max_num_ref_frames=\"%d", avc->sps->max_num_ref_frames); - fprintf(dump, "\" gaps_in_frame_num_value_allowed_flag=\"%d", avc->sps->gaps_in_frame_num_value_allowed_flag); - fprintf(dump, "\" chroma_format_idc=\"%d", avc->sps->chroma_format); - fprintf(dump, "\" bit_depth_luma_minus8=\"%d", avc->sps->luma_bit_depth_m8); - fprintf(dump, "\" bit_depth_chroma_minus8=\"%d", avc->sps->chroma_bit_depth_m8); - fprintf(dump, "\" width=\"%d", avc->sps->width); - fprintf(dump, "\" height=\"%d", avc->sps->height); - fprintf(dump, "\" crop_top=\"%d", avc->sps->crop.top); - fprintf(dump, "\" crop_left=\"%d", avc->sps->crop.left); - fprintf(dump, "\" crop_bottom=\"%d", avc->sps->crop.bottom); - fprintf(dump, "\" crop_right=\"%d", avc->sps->crop.right); - if (avc->sps->vui_parameters_present_flag) { - fprintf(dump, "\" vui_video_full_range_flag=\"%d", avc->sps->vui.video_full_range_flag); - fprintf(dump, "\" vui_video_signal_type_present_flag=\"%d", avc->sps->vui.video_signal_type_present_flag); - fprintf(dump, "\" vui_aspect_ratio_info_present_flag=\"%d", avc->sps->vui.aspect_ratio_info_present_flag); - fprintf(dump, "\" vui_aspect_ratio_num=\"%d", avc->sps->vui.par_num); - fprintf(dump, "\" vui_aspect_ratio_den=\"%d", avc->sps->vui.par_den); - fprintf(dump, "\" vui_overscan_info_present_flag=\"%d", avc->sps->vui.overscan_info_present_flag); - fprintf(dump, "\" vui_colour_description_present_flag=\"%d", avc->sps->vui.colour_description_present_flag); - fprintf(dump, "\" vui_colour_primaries=\"%d", avc->sps->vui.colour_primaries); - fprintf(dump, "\" vui_transfer_characteristics=\"%d", avc->sps->vui.transfer_characteristics); - fprintf(dump, "\" vui_matrix_coefficients=\"%d", avc->sps->vui.matrix_coefficients); - fprintf(dump, "\" vui_low_delay_hrd_flag=\"%d", avc->sps->vui.low_delay_hrd_flag); - } - break; - case GF_AVC_NALU_PIC_PARAM: - fputs("PictureParameterSet", dump); - idx = gf_media_avc_read_pps(ptr, ptr_size, avc); - assert (idx >= 0); - fprintf(dump, "\" pps_id=\"%d\" sps_id=\"%d", idx, avc->pps[idx].sps_id); - fprintf(dump, "\" entropy_coding_mode_flag=\"%d", avc->pps[idx].entropy_coding_mode_flag); - - break; - case GF_AVC_NALU_ACCESS_UNIT: - fputs("AccessUnit delimiter", dump); - fprintf(dump, "\" primary_pic_type=\"%d", gf_bs_read_u8(bs) >> 5); - break; - case GF_AVC_NALU_END_OF_SEQ: - fputs("EndOfSequence", dump); - break; - case GF_AVC_NALU_END_OF_STREAM: - fputs("EndOfStream", dump); - break; - case GF_AVC_NALU_FILLER_DATA: - fputs("Filler data", dump); - break; - case GF_AVC_NALU_SEQ_PARAM_EXT: - fputs("SequenceParameterSetExtension", dump); - break; - case GF_AVC_NALU_SVC_PREFIX_NALU: - fputs("SVCPrefix", dump); - break; - case GF_AVC_NALU_SVC_SUBSEQ_PARAM: - fputs("SVCSubsequenceParameterSet", dump); - idx = gf_media_avc_read_sps(ptr, ptr_size, avc, 1, NULL); - assert (idx >= 0); - fprintf(dump, "\" sps_id=\"%d", idx - GF_SVC_SSPS_ID_SHIFT); - break; - case GF_AVC_NALU_SLICE_AUX: - fputs("Auxiliary Slice", dump); - break; - - case GF_AVC_NALU_SVC_SLICE: - gf_media_avc_parse_nalu(bs, ptr[0], avc); - fputs(is_svc ? "SVCSlice" : "CodedSliceExtension", dump); - dependency_id = (ptr[2] & 0x70) >> 4; - quality_id = (ptr[2] & 0x0F); - temporal_id = (ptr[3] & 0xE0) >> 5; - fprintf(dump, "\" dependency_id=\"%d\" quality_id=\"%d\" temporal_id=\"%d", dependency_id, quality_id, temporal_id); - fprintf(dump, "\" poc=\"%d", avc->s_info.poc); - break; - case 30: - fputs("SVCAggregator", dump); - break; - case 31: - fputs("SVCExtractor", dump); - track_ref_index = (u8) ptr[4]; - sample_offset = (s8) ptr[5]; - data_offset = read_nal_size_hdr(&ptr[6], nalh_size); - data_size = read_nal_size_hdr(&ptr[6+nalh_size], nalh_size); - fprintf(dump, "\" track_ref_index=\"%d\" sample_offset=\"%d\" data_offset=\"%d\" data_size=\"%d\"", track_ref_index, sample_offset, data_offset, data_size); - break; - - default: - fputs("UNKNOWN", dump); - break; - } - fputs("\"", dump); - - if (type==GF_AVC_NALU_SEI) { - dump_sei(dump, (u8 *) ptr, ptr_size, GF_FALSE); - } - - if (res<0) - fprintf(dump, " status=\"error decoding slice\""); - - if (bs) gf_bs_del(bs); -} +void gf_inspect_dump_nalu(FILE *dump, u8 *ptr, u32 ptr_size, Bool is_svc, HEVCState *hevc, AVCState *avc, u32 nalh_size, Bool dump_crc, Bool is_encrypted); #endif -void dump_isom_nal_ex(GF_ISOFile *file, u32 trackID, FILE *dump) + +static void dump_isom_nal_ex(GF_ISOFile *file, GF_ISOTrackID trackID, FILE *dump, u32 dump_flags) { - u32 i, count, track, nalh_size, timescale, cur_extract_mode; + u32 i, j, count, nb_descs, track, nalh_size, timescale, cur_extract_mode; s32 countRef; - Bool is_adobe_protection = GF_FALSE; -#ifndef GPAC_DISABLE_AV_PARSERS + Bool is_adobe_protected = GF_FALSE; + Bool is_cenc_protected = GF_FALSE; Bool is_hevc = GF_FALSE; +#ifndef GPAC_DISABLE_AV_PARSERS AVCState avc; HEVCState hevc; +#endif GF_AVCConfig *avccfg, *svccfg; GF_HEVCConfig *hevccfg, *lhvccfg; GF_AVCConfigSlot *slc; -#endif + Bool has_svcc = GF_FALSE; track = gf_isom_get_track_by_id(file, trackID); #ifndef GPAC_DISABLE_AV_PARSERS memset(&avc, 0, sizeof(AVCState)); - avccfg = gf_isom_avc_config_get(file, track, 1); - svccfg = gf_isom_svc_config_get(file, track, 1); memset(&hevc, 0, sizeof(HEVCState)); - hevccfg = gf_isom_hevc_config_get(file, track, 1); - lhvccfg = gf_isom_lhvc_config_get(file, track, 1); - if (!avccfg && !svccfg && !hevccfg && !lhvccfg) { - fprintf(stderr, "Error: Track #%d is not NALU-based!\n", trackID); - return; - } #endif count = gf_isom_get_sample_count(file, track); @@ -1251,84 +1013,132 @@ void dump_isom_nal_ex(GF_ISOFile *file, u32 trackID, FILE *dump) cur_extract_mode = gf_isom_get_nalu_extract_mode(file, track); + nb_descs = gf_isom_get_sample_description_count(file, track); + if (!nb_descs) { + fprintf(stderr, "Error: Track #%d has no sample description so is likely not NALU-based!\n", trackID); + return; + } + fprintf(dump, "\n", trackID, count, timescale); #ifndef GPAC_DISABLE_AV_PARSERS - //for tile tracks the hvcC is stored in the 'tbas' track - if (!hevccfg && gf_isom_get_reference_count(file, track, GF_4CC('t','b','a','s'))) { - u32 tk = 0; - gf_isom_get_reference(file, track, GF_4CC('t','b','a','s'), 1, &tk); - hevccfg = gf_isom_hevc_config_get(file, tk, 1); - } - fprintf(dump, " \n"); -#define DUMP_ARRAY(arr, name, loc)\ +#define DUMP_ARRAY(arr, name, loc, _is_svc)\ + if (arr) {\ + fprintf(dump, " <%sArray location=\"%s\">\n", name, loc);\ + for (i=0; isize);\ + gf_inspect_dump_nalu(dump, (u8 *) slc->data, slc->size, _is_svc, is_hevc ? &hevc : NULL, &avc, nalh_size, (dump_flags&1) ? GF_TRUE : GF_FALSE, GF_FALSE);\ + fprintf(dump, "/>\n");\ + }\ + fprintf(dump, " \n", name);\ + }\ + +#else + +#define DUMP_ARRAY(arr, name, loc, _is_svc)\ if (arr) {\ fprintf(dump, " <%sArray location=\"%s\">\n", name, loc);\ for (i=0; isize);\ - dump_nalu(dump, slc->data, slc->size, svccfg ? 1 : 0, is_hevc ? &hevc : NULL, &avc, nalh_size);\ + fprintf(dump, " size);\ fprintf(dump, "/>\n");\ }\ fprintf(dump, " \n", name);\ }\ - + +#endif + nalh_size = 0; - if (avccfg) { - nalh_size = avccfg->nal_unit_size; - - DUMP_ARRAY(avccfg->sequenceParameterSets, "AVCSPS", "avcC") - DUMP_ARRAY(avccfg->pictureParameterSets, "AVCPPS", "avcC") - DUMP_ARRAY(avccfg->sequenceParameterSetExtensions, "AVCSPSEx", "avcC") - } - if (svccfg) { - if (!nalh_size) nalh_size = svccfg->nal_unit_size; - DUMP_ARRAY(svccfg->sequenceParameterSets, "SVCSPS", "svcC") - DUMP_ARRAY(svccfg->pictureParameterSets, "SVCPPS", "svcC") - } - if (hevccfg) { -#ifndef GPAC_DISABLE_HEVC - u32 idx; - nalh_size = hevccfg->nal_unit_size; - is_hevc = 1; - for (idx=0; idxparam_array); idx++) { - GF_HEVCParamArray *ar = gf_list_get(hevccfg->param_array, idx); - if (ar->type==GF_HEVC_NALU_SEQ_PARAM) { - DUMP_ARRAY(ar->nalus, "HEVCSPS", "hvcC") - } else if (ar->type==GF_HEVC_NALU_PIC_PARAM) { - DUMP_ARRAY(ar->nalus, "HEVCPPS", "hvcC") - } else if (ar->type==GF_HEVC_NALU_VID_PARAM) { - DUMP_ARRAY(ar->nalus, "HEVCVPS", "hvcC") - } else { - DUMP_ARRAY(ar->nalus, "HEVCUnknownPS", "hvcC") + for (j=0; j\n"); + + if (!avccfg && !svccfg && !hevccfg && !lhvccfg) { + fprintf(stderr, "Error: Track #%d is not NALU-based!\n", trackID); + return; + } + + if (avccfg) { + nalh_size = avccfg->nal_unit_size; + + DUMP_ARRAY(avccfg->sequenceParameterSets, "AVCSPS", "avcC", is_svc); + DUMP_ARRAY(avccfg->pictureParameterSets, "AVCPPS", "avcC", is_svc) + DUMP_ARRAY(avccfg->sequenceParameterSetExtensions, "AVCSPSEx", "avcC", is_svc) + } + if (is_svc) { + if (!nalh_size) nalh_size = svccfg->nal_unit_size; + DUMP_ARRAY(svccfg->sequenceParameterSets, "SVCSPS", "svcC", is_svc) + DUMP_ARRAY(svccfg->pictureParameterSets, "SVCPPS", "svcC", is_svc) + } + if (mvccfg) { + if (!nalh_size) nalh_size = mvccfg->nal_unit_size; + DUMP_ARRAY(mvccfg->sequenceParameterSets, "SVCSPS", "mvcC", is_svc) + DUMP_ARRAY(mvccfg->pictureParameterSets, "SVCPPS", "mvcC", is_svc) + } + if (hevccfg) { + u32 idx; + nalh_size = hevccfg->nal_unit_size; + is_hevc = 1; + for (idx=0; idxparam_array); idx++) { + GF_HEVCParamArray *ar = gf_list_get(hevccfg->param_array, idx); + if (ar->type==GF_HEVC_NALU_SEQ_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCSPS", "hvcC", 0) + } else if (ar->type==GF_HEVC_NALU_PIC_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCPPS", "hvcC", 0) + } else if (ar->type==GF_HEVC_NALU_VID_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCVPS", "hvcC", 0) + } else { + DUMP_ARRAY(ar->nalus, "HEVCUnknownPS", "hvcC", 0) + } } } -#endif - } - if (lhvccfg) { -#ifndef GPAC_DISABLE_HEVC - u32 idx; - nalh_size = lhvccfg->nal_unit_size; - is_hevc = 1; - for (idx=0; idxparam_array); idx++) { - GF_HEVCParamArray *ar = gf_list_get(lhvccfg->param_array, idx); - if (ar->type==GF_HEVC_NALU_SEQ_PARAM) { - DUMP_ARRAY(ar->nalus, "HEVCSPS", "lhcC") - } else if (ar->type==GF_HEVC_NALU_PIC_PARAM) { - DUMP_ARRAY(ar->nalus, "HEVCPPS", "lhcC") - } else if (ar->type==GF_HEVC_NALU_VID_PARAM) { - DUMP_ARRAY(ar->nalus, "HEVCVPS", "lhcC") - } else { - DUMP_ARRAY(ar->nalus, "HEVCUnknownPS", "lhcC") + if (lhvccfg) { + u32 idx; + nalh_size = lhvccfg->nal_unit_size; + is_hevc = 1; + for (idx=0; idxparam_array); idx++) { + GF_HEVCParamArray *ar = gf_list_get(lhvccfg->param_array, idx); + if (ar->type==GF_HEVC_NALU_SEQ_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCSPS", "lhcC", 0) + } else if (ar->type==GF_HEVC_NALU_PIC_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCPPS", "lhcC", 0) + } else if (ar->type==GF_HEVC_NALU_VID_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCVPS", "lhcC", 0) + } else { + DUMP_ARRAY(ar->nalus, "HEVCUnknownPS", "lhcC", 0) + } } } -#endif - } + fprintf(dump, " \n"); -#endif - fprintf(dump, " \n"); + if (avccfg) gf_odf_avc_cfg_del(avccfg); + if (svccfg) { + gf_odf_avc_cfg_del(svccfg); + has_svcc = GF_TRUE; + } + if (hevccfg) gf_odf_hevc_cfg_del(hevccfg); + if (lhvccfg) gf_odf_hevc_cfg_del(lhvccfg); + } /*fixme: for dumping encrypted track: we don't have neither avccfg nor svccfg*/ if (!nalh_size) nalh_size = 4; @@ -1337,7 +1147,7 @@ void dump_isom_nal_ex(GF_ISOFile *file, u32 trackID, FILE *dump) countRef = gf_isom_get_reference_count(file, track, GF_ISOM_REF_SCAL); if (countRef > 0) { - u32 refTrackID; + GF_ISOTrackID refTrackID; fprintf(dump, " \n"); for (i = 1; i <= (u32) countRef; i++) { @@ -1350,26 +1160,38 @@ void dump_isom_nal_ex(GF_ISOFile *file, u32 trackID, FILE *dump) fprintf(dump, " \n"); gf_isom_set_nalu_extract_mode(file, track, GF_ISOM_NALU_EXTRACT_INSPECT); - is_adobe_protection = gf_isom_is_adobe_protection_media(file, track, 1); + is_adobe_protected = gf_isom_is_adobe_protection_media(file, track, 1); + is_cenc_protected = gf_isom_is_cenc_media(file, track, 1); for (i=0; i\n", i+1); continue; } dts = samp->DTS; cts = dts + (s32) samp->CTS_Offset; + is_rap = samp->IsRAP; + if (!is_rap) gf_isom_get_sample_rap_roll_info(file, track, i+1, &is_rap, NULL, NULL); + + if (dump_flags&2) { + fprintf(dump, " dataLength, is_rap); + } else { + fprintf(dump, " dataLength, is_rap); + } + if (nb_descs>1) + fprintf(dump, " sample_description=\"%d\"", di); + fprintf(dump, " >\n"); - fprintf(dump, " \n", i+1, dts, cts, samp->dataLength, samp->IsRAP); if (cts\n"); idx = 1; ptr = samp->data; size = samp->dataLength; - if (is_adobe_protection) { + if (is_adobe_protected) { u8 encrypted_au = ptr[0]; if (encrypted_au) { fprintf(dump, " \n", i+1); @@ -1385,13 +1207,21 @@ void dump_isom_nal_ex(GF_ISOFile *file, u32 trackID, FILE *dump) nal_size = read_nal_size_hdr(ptr, nalh_size); ptr += nalh_size; - if (nalh_size + nal_size > size) { + if (nal_size >= UINT_MAX-nalh_size || nalh_size + nal_size > size) { fprintf(dump, " \n", idx, nal_size, size); break; } else { - fprintf(dump, " \n"); } @@ -1403,30 +1233,211 @@ void dump_isom_nal_ex(GF_ISOFile *file, u32 trackID, FILE *dump) gf_isom_sample_del(&samp); fprintf(dump, "\n"); - gf_set_progress("Analysing Track NALUs", i+1, count); + gf_set_progress("Analysing Track NALUs", i+1, count); + } + fprintf(dump, " \n"); + fprintf(dump, "\n"); + + gf_isom_set_nalu_extract_mode(file, track, cur_extract_mode); +} + +static void dump_isom_obu(GF_ISOFile *file, GF_ISOTrackID trackID, FILE *dump, Bool dump_crc); +static void dump_qt_prores(GF_ISOFile *file, GF_ISOTrackID trackID, FILE *dump, Bool dump_crc); + +void dump_isom_nal(GF_ISOFile *file, GF_ISOTrackID trackID, char *inName, Bool is_final_name, u32 dump_flags) +{ + Bool is_av1 = GF_FALSE; + Bool is_prores = GF_FALSE; + + FILE *dump; + if (inName) { + GF_ESD* esd; + char szBuf[GF_MAX_PATH]; + + strcpy(szBuf, inName); + + u32 track = gf_isom_get_track_by_id(file, trackID); + esd = gf_isom_get_esd(file, track, 1); + + if (!esd || !esd->decoderConfig) { + switch (gf_isom_get_media_subtype(file, track, 1)) { + case GF_ISOM_SUBTYPE_AV01: + is_av1 = GF_TRUE; + break; + case GF_QT_SUBTYPE_APCH: + case GF_QT_SUBTYPE_APCO: + case GF_QT_SUBTYPE_APCN: + case GF_QT_SUBTYPE_APCS: + case GF_QT_SUBTYPE_AP4X: + case GF_QT_SUBTYPE_AP4H: + is_prores = GF_TRUE; + break; + } + } + else if (esd->decoderConfig->objectTypeIndication == GF_CODECID_AV1) { + is_av1 = GF_TRUE; + } + if (esd) gf_odf_desc_del((GF_Descriptor*)esd); + + if (!is_final_name) sprintf(szBuf, "%s_%d_%s.xml", inName, trackID, is_av1 ? "obu" : "nalu"); + dump = gf_fopen(szBuf, "wt"); + if (!dump) { + fprintf(stderr, "Failed to open %s for dumping\n", szBuf); + return; + } + } else { + dump = stdout; + } + + if (is_av1) + dump_isom_obu(file, trackID, dump, dump_flags); + else if (is_prores) + dump_qt_prores(file, trackID, dump, dump_flags); + else + dump_isom_nal_ex(file, trackID, dump, dump_flags); + + if (inName) gf_fclose(dump); +} + +#ifndef GPAC_DISABLE_AV_PARSERS +void gf_inspect_dump_obu(FILE *dump, AV1State *av1, u8 *obu, u64 obu_length, ObuType obu_type, u64 obu_size, u32 hdr_size, Bool dump_crc); +#endif + +static void dump_isom_obu(GF_ISOFile *file, GF_ISOTrackID trackID, FILE *dump, Bool dump_crc) +{ +#ifndef GPAC_DISABLE_AV_PARSERS + u32 i, count, track, timescale; + AV1State av1; + ObuType obu_type; + u64 obu_size; + u32 hdr_size; + GF_BitStream *bs; + u32 idx; + + track = gf_isom_get_track_by_id(file, trackID); + + gf_av1_init_state(&av1); + av1.config = gf_isom_av1_config_get(file, track, 1); + if (!av1.config) { + fprintf(stderr, "Error: Track #%d is not AV1!\n", trackID); + return; + } + + count = gf_isom_get_sample_count(file, track); + timescale = gf_isom_get_media_timescale(file, track); + + fprintf(dump, "\n", trackID, count, timescale); + + fprintf(dump, " \n"); + + for (i=0; iobu_array); i++) { + GF_AV1_OBUArrayEntry *obu = gf_list_get(av1.config->obu_array, i); + bs = gf_bs_new(obu->obu, (u32) obu->obu_length, GF_BITSTREAM_READ); + gf_media_aom_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, &av1); + gf_inspect_dump_obu(dump, &av1, obu->obu, obu->obu_length, obu_type, obu_size, hdr_size, dump_crc); + gf_bs_del(bs); + } + fprintf(dump, " \n"); + + fprintf(dump, " \n"); + + for (i=0; i\n", i+1); + continue; + } + dts = samp->DTS; + cts = dts + (s32) samp->CTS_Offset; + + fprintf(dump, " \n", i+1, dts, cts, samp->dataLength, samp->IsRAP); + if (cts\n"); + + idx = 1; + ptr = samp->data; + size = samp->dataLength; + + bs = gf_bs_new(ptr, size, GF_BITSTREAM_READ); + while (size) { + gf_media_aom_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, &av1); + if (obu_size > size) { + fprintf(dump, " \n", idx, (u32) obu_size, size); + break; + } + gf_inspect_dump_obu(dump, &av1, ptr, obu_size, obu_type, obu_size, hdr_size, dump_crc); + ptr += obu_size; + size -= (u32)obu_size; + idx++; + } + gf_bs_del(bs); + fprintf(dump, " \n"); + gf_isom_sample_del(&samp); + + fprintf(dump, "\n"); + gf_set_progress("Analysing Track OBUs", i+1, count); + } + fprintf(dump, " \n"); + fprintf(dump, "\n"); + + if (av1.config) gf_odf_av1_cfg_del(av1.config); + gf_av1_reset_state(&av1, GF_TRUE); +#endif +} + +static void dump_qt_prores(GF_ISOFile *file, u32 trackID, FILE *dump, Bool dump_crc) +{ +#ifndef GPAC_DISABLE_AV_PARSERS + u32 i, count, track, timescale; + + track = gf_isom_get_track_by_id(file, trackID); + + count = gf_isom_get_sample_count(file, track); + timescale = gf_isom_get_media_timescale(file, track); + + fprintf(dump, "\n", trackID, count, timescale); + + for (i=0; i\n", i+1); + continue; + } + dts = samp->DTS; + cts = dts + (s32) samp->CTS_Offset; + + if (cts!=dts) fprintf(dump, "\n", cts, dts); + if (!samp->IsRAP) fprintf(dump, "\n"); + + fprintf(dump, " \n", i+1, cts, samp->dataLength); + + gf_inspect_dump_prores(dump, samp->data, samp->dataLength, dump_crc); + fprintf(dump, " \n"); + + gf_isom_sample_del(&samp); + + fprintf(dump, "\n"); + gf_set_progress("Analysing ProRes Track", i+1, count); } - fprintf(dump, " \n"); - fprintf(dump, "\n"); - -#ifndef GPAC_DISABLE_AV_PARSERS - if (avccfg) gf_odf_avc_cfg_del(avccfg); - if (svccfg) gf_odf_avc_cfg_del(svccfg); -#ifndef GPAC_DISABLE_HEVC - if (hevccfg) gf_odf_hevc_cfg_del(hevccfg); - if (lhvccfg) gf_odf_hevc_cfg_del(lhvccfg); + fprintf(dump, "\n"); #endif - -#endif - gf_isom_set_nalu_extract_mode(file, track, cur_extract_mode); } -void dump_isom_nal(GF_ISOFile *file, u32 trackID, char *inName, Bool is_final_name) +void dump_isom_saps(GF_ISOFile *file, GF_ISOTrackID trackID, u32 dump_saps_mode, char *inName, Bool is_final_name) { FILE *dump; + u32 i, count; + s64 media_offset=0; + u32 track = gf_isom_get_track_by_id(file, trackID); if (inName) { char szBuf[GF_MAX_PATH]; strcpy(szBuf, inName); - if (!is_final_name) sprintf(szBuf, "%s_%d_nalu.xml", inName, trackID); + + if (!is_final_name) sprintf(szBuf, "%s_%d_cues.xml", inName, trackID); dump = gf_fopen(szBuf, "wt"); if (!dump) { fprintf(stderr, "Failed to open %s for dumping\n", szBuf); @@ -1435,8 +1446,64 @@ void dump_isom_nal(GF_ISOFile *file, u32 trackID, char *inName, Bool is_final_na } else { dump = stdout; } - dump_isom_nal_ex(file, gf_isom_get_track_id(file, trackID), dump); + fprintf(dump, "\n"); + fprintf(dump, "\n"); + fprintf(dump, "\n"); + + count = gf_isom_get_sample_count(file, track); + for (i=0; iIsRAP; + if (!sap_type) { + Bool is_rap; + GF_ISOSampleRollType roll_type; + s32 roll_dist; + gf_isom_get_sample_rap_roll_info(file, track, i+1, &is_rap, &roll_type, &roll_dist); + if (roll_type) sap_type = SAP_TYPE_4; + else if (is_rap) sap_type = SAP_TYPE_3; + } + + if (!sap_type) { + gf_isom_sample_del(&samp); + continue; + } + + dts = cts = samp->DTS; + cts += samp->CTS_Offset; + fprintf(dump, "\n"); + + gf_isom_sample_del(&samp); + } + fprintf(dump, "\n"); + fprintf(dump, "\n"); if (inName) gf_fclose(dump); } @@ -1446,9 +1513,9 @@ void dump_isom_ismacryp(GF_ISOFile *file, char *inName, Bool is_final_name) { u32 i, j; FILE *dump; - char szBuf[1024]; if (inName) { + char szBuf[1024]; strcpy(szBuf, inName); if (!is_final_name) strcat(szBuf, "_ismacryp.xml"); dump = gf_fopen(szBuf, "wt"); @@ -1481,12 +1548,11 @@ void dump_isom_ismacryp(GF_ISOFile *file, char *inName, Bool is_final_name) } -void dump_isom_timed_text(GF_ISOFile *file, u32 trackID, char *inName, Bool is_final_name, Bool is_convert, GF_TextDumpType dump_type) +void dump_isom_timed_text(GF_ISOFile *file, GF_ISOTrackID trackID, char *inName, Bool is_final_name, Bool is_convert, GF_TextDumpType dump_type) { FILE *dump; GF_Err e; u32 track; - char szBuf[1024]; track = gf_isom_get_track_by_id(file, trackID); if (!track) { @@ -1504,6 +1570,7 @@ void dump_isom_timed_text(GF_ISOFile *file, u32 trackID, char *inName, Bool is_f } if (inName) { + char szBuf[1024]; char *ext; ext = ((dump_type==GF_TEXTDUMPTYPE_SVG) ? "svg" : ((dump_type==GF_TEXTDUMPTYPE_SRT) ? "srt" : "ttxt")); if (is_final_name) { @@ -1512,7 +1579,7 @@ void dump_isom_timed_text(GF_ISOFile *file, u32 trackID, char *inName, Bool is_f sprintf(szBuf, "%s.%s", inName, ext) ; else sprintf(szBuf, "%s_%d_text.%s", inName, trackID, ext); - + dump = gf_fopen(szBuf, "wt"); if (!dump) { fprintf(stderr, "Failed to open %s for dumping\n", szBuf); @@ -1537,13 +1604,12 @@ void dump_isom_sdp(GF_ISOFile *file, char *inName, Bool is_final_name) const char *sdp; u32 size, i; FILE *dump; - char szBuf[1024]; if (inName) { - char *ext; + char szBuf[1024]; strcpy(szBuf, inName); if (!is_final_name) { - ext = strchr(szBuf, '.'); + char *ext = strchr(szBuf, '.'); if (ext) ext[0] = 0; strcat(szBuf, "_sdp.txt"); } @@ -1558,7 +1624,8 @@ void dump_isom_sdp(GF_ISOFile *file, char *inName, Bool is_final_name) } //get the movie SDP gf_isom_sdp_get(file, &sdp, &size); - fprintf(dump, "%s", sdp); + if (sdp && size) + fprintf(dump, "%s", sdp); fprintf(dump, "\r\n"); //then tracks @@ -1576,15 +1643,15 @@ void dump_isom_sdp(GF_ISOFile *file, char *inName, Bool is_final_name) #ifndef GPAC_DISABLE_ISOM_DUMP -GF_Err dump_isom_xml(GF_ISOFile *file, char *inName, Bool is_final_name, Bool do_track_dump) +GF_Err dump_isom_xml(GF_ISOFile *file, char *inName, Bool is_final_name, Bool do_track_dump, Bool merge_vtt_cues, Bool skip_init, Bool skip_samples) { GF_Err e; FILE *dump = stdout; Bool do_close=GF_FALSE; - char szBuf[1024]; if (!file) return GF_ISOM_INVALID_FILE; - + if (inName) { + char szBuf[1024]; strcpy(szBuf, inName); if (!is_final_name) { strcat(szBuf, do_track_dump ? "_dump.xml" : "_info.xml"); @@ -1601,22 +1668,22 @@ GF_Err dump_isom_xml(GF_ISOFile *file, char *inName, Bool is_final_name, Bool do if (do_track_dump) { fprintf(dump, "\n"); } - e = gf_isom_dump(file, dump); + e = gf_isom_dump(file, dump, skip_init, skip_samples); if (e) { fprintf(stderr, "Error dumping ISO structure\n"); } - if ( gf_isom_get_track_count(file) == 0 ) - do_track_dump = GF_FALSE; if (do_track_dump) { - u32 i, j; +#ifndef GPAC_DISABLE_MEDIA_EXPORT + u32 i; //because of dump mode we need to reopen in regular read mode to avoid mem leaks GF_ISOFile *the_file = gf_isom_open(gf_isom_get_filename(file), GF_ISOM_OPEN_READ, NULL); u32 tcount = gf_isom_get_track_count(the_file); fprintf(dump, "\n"); + for (i=0; i\n", name, trackID); - scount=gf_isom_get_sample_count(the_file, i+1); +#ifndef GPAC_DISABLE_ISOM_HINTING + u32 j, scount=gf_isom_get_sample_count(the_file, i+1); for (j=0; j\n", name); fmt_handled = GF_TRUE; +#endif /*GPAC_DISABLE_ISOM_HINTING*/ } else if (gf_isom_get_avc_svc_type(the_file, i+1, 1) || gf_isom_get_hevc_lhvc_type(the_file, i+1, 1)) { - dump_isom_nal_ex(the_file, trackID, dump); + dump_isom_nal_ex(the_file, trackID, dump, GF_FALSE); fmt_handled = GF_TRUE; } else if ((mtype==GF_ISOM_MEDIA_TEXT) || (mtype==GF_ISOM_MEDIA_SUBT) ) { if (msubtype==GF_ISOM_SUBTYPE_WVTT) { - gf_webvtt_dump_iso_track(&dumper, NULL, i+1, GF_FALSE, GF_TRUE); + gf_webvtt_dump_iso_track(&dumper, i+1, merge_vtt_cues, GF_TRUE); fmt_handled = GF_TRUE; } else if ((msubtype==GF_ISOM_SUBTYPE_TX3G) || (msubtype==GF_ISOM_SUBTYPE_TEXT)) { gf_isom_text_dump(the_file, i+1, dump, GF_TEXTDUMPTYPE_TTXT_BOXES); @@ -1659,15 +1729,18 @@ GF_Err dump_isom_xml(GF_ISOFile *file, char *inName, Bool is_final_name, Bool do } if (!fmt_handled) { - dumper.nhml_only = GF_TRUE; - dumper.flags = GF_EXPORT_NATIVE | GF_EXPORT_NHML | GF_EXPORT_NHML_FULL; - gf_media_export_nhml(&dumper, GF_FALSE); + dumper.flags = GF_EXPORT_NHML | GF_EXPORT_NHML_FULL; + dumper.print_stats_graph = fs_dump_flags; + gf_media_export(&dumper); } } +#else + return GF_NOT_SUPPORTED; +#endif /*GPAC_DISABLE_MEDIA_EXPORT*/ gf_isom_delete(the_file); fprintf(dump, "\n"); + fprintf(dump, "\n"); } - fprintf(dump, "\n"); if (do_close) gf_fclose(dump); return e; } @@ -1715,7 +1788,7 @@ static char *format_date(u64 time, char *szTime) } else { time -= 2082844800; now = (u32) time; - sprintf(szTime, "GMT %s", asctime(gmtime(&now)) ); + sprintf(szTime, "GMT %s", asctime(gf_gmtime(&now)) ); } return szTime; } @@ -1740,7 +1813,7 @@ void print_udta(GF_ISOFile *file, u32 track_number) GF_Err dump_isom_udta(GF_ISOFile *file, char *inName, Bool is_final_name, u32 dump_udta_type, u32 dump_udta_track) { - char szName[1024], *data; + u8 *data; FILE *t; bin128 uuid; u32 count, res; @@ -1761,11 +1834,12 @@ GF_Err dump_isom_udta(GF_ISOFile *file, char *inName, Bool is_final_name, u32 du return e; } if (inName) { + char szName[1024]; if (is_final_name) strcpy(szName, inName); else sprintf(szName, "%s_%s.udta", inName, gf_4cc_to_str(dump_udta_type) ); - + t = gf_fopen(szName, "wb"); if (!t) { gf_free(data); @@ -1775,31 +1849,55 @@ GF_Err dump_isom_udta(GF_ISOFile *file, char *inName, Bool is_final_name, u32 du } else { t = stdout; } - res = (u32) fwrite(data, 1, count, t); + res = (u32) gf_fwrite(data+8, count-8, t); if (inName) gf_fclose(t); gf_free(data); - if (count != res) { + if (count-8 != res) { fprintf(stderr, "Error writing udta to file\n"); - gf_free(data); return GF_IO_ERR; } return GF_OK; } -GF_Err dump_isom_chapters(GF_ISOFile *file, char *inName, Bool is_final_name, Bool dump_ogg) +GF_Err dump_isom_chapters(GF_ISOFile *file, char *inName, Bool is_final_name, u32 dump_mode) { - char szName[1024]; FILE *t; u32 i, count; + u32 chap_tk = 0; count = gf_isom_get_chapter_count(file, 0); + + if (dump_mode==2) dump_mode = GF_TEXTDUMPTYPE_OGG_CHAP; + else if (dump_mode==3) dump_mode = GF_TEXTDUMPTYPE_ZOOM_CHAP; + else dump_mode = GF_TEXTDUMPTYPE_TTXT_CHAP; + + if (!count) { + for (i=0; i\n"); + fprintf(t, "\n"); + fprintf(t, "\n"); + fprintf(t, "\n"); + fprintf(t, "\n"); + } + for (i=0; i%s\n" + , format_duration(chapter_time, 1000, szDur), name); } } + if (dump_mode==GF_TEXTDUMPTYPE_TTXT_CHAP) { + fprintf(t, "\n"); + } if (inName) gf_fclose(t); return GF_OK; } @@ -1850,8 +1962,9 @@ static void DumpMetaItem(GF_ISOFile *file, Bool root_meta, u32 tk_num, char *nam const char *it_name, *mime, *enc, *url, *urn; Bool self_ref; u32 ID; - gf_isom_get_meta_item_info(file, root_meta, tk_num, i+1, &ID, NULL, &self_ref, &it_name, &mime, &enc, &url, &urn); - fprintf(stderr, "Item #%d - ID %d", i+1, ID); + u32 it_type; + gf_isom_get_meta_item_info(file, root_meta, tk_num, i+1, &ID, &it_type, NULL, &self_ref, &it_name, &mime, &enc, &url, &urn); + fprintf(stderr, "Item #%d - ID %d - type %s ", i+1, ID, gf_4cc_to_str(it_type)); if (self_ref) fprintf(stderr, " - Self-Reference"); else if (it_name) fprintf(stderr, " - Name: %s", it_name); if (mime) fprintf(stderr, " - MimeType: %s", mime); @@ -1866,10 +1979,9 @@ static void DumpMetaItem(GF_ISOFile *file, Bool root_meta, u32 tk_num, char *nam static void print_config_hash(GF_List *xps_array, char *szName) { u32 i, j; - GF_AVCConfigSlot *slc; u8 hash[20]; for (i=0; idata, slc->size, hash); fprintf(stderr, "\t%s#%d hash: ", szName, i+1); for (j=0; j<20; j++) fprintf(stderr, "%02X", hash[j]); @@ -1877,10 +1989,16 @@ static void print_config_hash(GF_List *xps_array, char *szName) } } -#if !defined(GPAC_DISABLE_HEVC) && !defined( GPAC_DISABLE_AV_PARSERS) -void dump_hevc_track_info(GF_ISOFile *file, u32 trackNum, GF_HEVCConfig *hevccfg, HEVCState *hevc_state) +void dump_hevc_track_info(GF_ISOFile *file, u32 trackNum, GF_HEVCConfig *hevccfg +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) + , HEVCState *hevc_state +#endif /*GPAC_DISABLE_AV_PARSERS && defined(GPAC_DISABLE_HEVC)*/ + ) { - u32 k, idx; +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) + u32 idx; +#endif + u32 k; Bool non_hevc_base_layer=GF_FALSE; fprintf(stderr, "\t%s Info:", hevccfg->is_lhvc ? "LHVC" : "HEVC"); if (!hevccfg->is_lhvc) @@ -1902,17 +2020,21 @@ void dump_hevc_track_info(GF_ISOFile *file, u32 trackNum, GF_HEVCConfig *hevccfg if (ar->type==GF_HEVC_NALU_VID_PARAM) { fprintf(stderr, "%d VPS ", gf_list_count(ar->nalus)); +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) for (idx=0; idxnalus); idx++) { GF_AVCConfigSlot *vps = gf_list_get(ar->nalus, idx); - s32 idx=gf_media_hevc_read_vps(vps->data, vps->size, hevc_state); - if (hevccfg->is_lhvc && (idx>=0)) { - non_hevc_base_layer = ! hevc_state->vps[idx].base_layer_internal_flag; + s32 ps_idx=gf_media_hevc_read_vps(vps->data, vps->size, hevc_state); + if (hevccfg->is_lhvc && (ps_idx>=0)) { + non_hevc_base_layer = ! hevc_state->vps[ps_idx].base_layer_internal_flag; } } +#endif + } } fprintf(stderr, "\n"); +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) for (k=0; kparam_array); k++) { GF_HEVCParamArray *ar=gf_list_get(hevccfg->param_array, k); u32 width, height; @@ -1936,8 +2058,8 @@ void dump_hevc_track_info(GF_ISOFile *file, u32 trackNum, GF_HEVCConfig *hevccfg fprintf(stderr, "\nFailed to read SPS: %s\n\n", gf_error_to_string((e) )); } } - } +#endif if (!hevccfg->is_lhvc) fprintf(stderr, "\tBit Depth luma %d - Chroma %d - %d temporal layers\n", hevccfg->luma_bit_depth, hevccfg->chroma_bit_depth, hevccfg->numTemporalLayers); else @@ -1953,21 +2075,25 @@ void dump_hevc_track_info(GF_ISOFile *file, u32 trackNum, GF_HEVCConfig *hevccfg else if (ar->type==GF_HEVC_NALU_VID_PARAM) print_config_hash(ar->nalus, "VPS"); } } -#endif -void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) +void DumpTrackInfo(GF_ISOFile *file, GF_ISOTrackID trackID, Bool full_dump, Bool is_track_num) { Float scale; Bool is_od_track = 0; - u32 trackNum, i, j, max_rate, rate, ts, mtype, msub_type, timescale, sr, nb_ch, count, alt_group, nb_groups, nb_edits; + u32 trackNum, i, j, max_rate, rate, ts, mtype, msub_type, timescale, sr, nb_ch, count, alt_group, nb_groups, nb_edits, cdur, csize, bps; u64 time_slice, dur, size; - u8 bps; + s32 cts_shift; GF_ESD *esd; char szDur[50]; char *lang; - trackNum = gf_isom_get_track_by_id(file, trackID); + if (!is_track_num) { + trackNum = gf_isom_get_track_by_id(file, trackID); + } else { + trackNum = trackID; + trackID = gf_isom_get_track_id(file, trackNum); + } if (!trackNum) { fprintf(stderr, "No track with ID %d found\n", trackID); return; @@ -1978,10 +2104,18 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) fprintf(stderr, "Media Duration %s - ", format_duration(gf_isom_get_media_duration(file, trackNum), timescale, szDur)); fprintf(stderr, "Indicated Duration %s\n", format_duration(gf_isom_get_media_original_duration(file, trackNum), timescale, szDur)); - nb_edits = gf_isom_get_edit_segment_count(file, trackNum); + if (gf_isom_check_data_reference(file, trackNum, 1) != GF_OK) { + fprintf(stderr, "Track uses external data reference not supported by GPAC!\n"); + } + + nb_edits = gf_isom_get_edits_count(file, trackNum); if (nb_edits) fprintf(stderr, "Track has %d edit lists: track duration is %s\n", nb_edits, format_duration(gf_isom_get_track_duration(file, trackNum), gf_isom_get_timescale(file), szDur)); + cts_shift = gf_isom_get_composition_offset_shift(file, trackNum); + if (cts_shift) + fprintf(stderr, "Track composition offset shift (negative CTS offset): %d\n", cts_shift); + if (gf_isom_is_track_in_root_od(file, trackNum) ) fprintf(stderr, "Track is present in Root OD\n"); if (!gf_isom_is_track_enabled(file, trackNum)) fprintf(stderr, "Track is disabled\n"); gf_isom_get_media_language(file, trackNum, &lang); @@ -1997,16 +2131,26 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) for (i = 0; i < count; i++) { char *kind_scheme, *kind_value; gf_isom_get_track_kind(file, trackNum, i, &kind_scheme, &kind_value); - fprintf(stderr, "Kind: %s - %s\n", kind_scheme, kind_value); + fprintf(stderr, "Kind: %s - %s\n", kind_scheme ? kind_scheme : "null", kind_value ? kind_value : "null"); if (kind_scheme) gf_free(kind_scheme); if (kind_value) gf_free(kind_value); } if (gf_isom_is_track_fragmented(file, trackID) ) { + u32 defaultDuration, defaultSize, defaultDescriptionIndex, defaultRandomAccess; + u8 defaultPadding; + u16 defaultDegradationPriority; u32 frag_samples; u64 frag_duration; gf_isom_get_fragmented_samples_info(file, trackID, &frag_samples, &frag_duration); fprintf(stderr, "Fragmented track: %d samples - Media Duration %s\n", frag_samples, format_duration(frag_duration, timescale, szDur)); + + gf_isom_get_fragment_defaults(file, trackNum, &defaultDuration, &defaultSize, &defaultDescriptionIndex, &defaultRandomAccess, &defaultPadding, &defaultDegradationPriority); + + fprintf(stderr, "Fragment sample defaults: duration %d size %d stsd %d sync %d padding %d degradation_priority %d\n", + defaultDuration, defaultSize, defaultDescriptionIndex, defaultRandomAccess, + (u32) defaultPadding, (u32) defaultDegradationPriority + ); } if (!gf_isom_is_self_contained(file, trackNum, 1)) { @@ -2023,9 +2167,15 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) print_udta(file, trackNum); - if (mtype==GF_ISOM_MEDIA_VISUAL) { + if (gf_isom_is_video_handler_type(mtype) ) { s32 tx, ty; u32 w, h; + u16 bit_depth; + + gf_isom_get_visual_info(file, trackNum, 1, &w, &h); + gf_isom_get_visual_bit_depth(file, trackNum, 1, &bit_depth); + fprintf(stderr, "Visual Sample Entry Info: width=%d height=%d (depth=%d bits)\n", w, h, (int)bit_depth); + gf_isom_get_track_layout_info(file, trackNum, &w, &h, &tx, &ty, NULL); fprintf(stderr, "Visual Track layout: x=%d y=%d width=%d height=%d\n", tx, ty, w, h); } @@ -2034,8 +2184,10 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) gf_isom_set_nalu_extract_mode(file, trackNum, GF_ISOM_NALU_EXTRACT_INSPECT); msub_type = gf_isom_get_media_subtype(file, trackNum, 1); + if (msub_type==GF_ISOM_SUBTYPE_MPEG4_CRYP) + gf_isom_get_original_format_type(file, trackNum, 1, &msub_type); + if ((msub_type==GF_ISOM_SUBTYPE_MPEG4) - || (msub_type==GF_ISOM_SUBTYPE_MPEG4_CRYP) || (msub_type==GF_ISOM_SUBTYPE_AVC_H264) || (msub_type==GF_ISOM_SUBTYPE_AVC2_H264) || (msub_type==GF_ISOM_SUBTYPE_AVC3_H264) @@ -2052,10 +2204,11 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) || (msub_type==GF_ISOM_SUBTYPE_HVT1) ) { esd = gf_isom_get_esd(file, trackNum, 1); - if (!esd) { + if (!esd || !esd->decoderConfig) { fprintf(stderr, "WARNING: Broken MPEG-4 Track\n"); + if (esd) gf_odf_desc_del((GF_Descriptor *)esd); } else { - const char *st = gf_odf_stream_type_name(esd->decoderConfig->streamType); + const char *st = gf_stream_type_name(esd->decoderConfig->streamType); if (st) { fprintf(stderr, "MPEG-4 Config%s%s Stream - ObjectTypeIndication 0x%02x\n", full_dump ? "\n\t" : ": ", st, esd->decoderConfig->objectTypeIndication); @@ -2070,7 +2223,7 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) u32 w, h; u16 rvc_predef; w = h = 0; - if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_MPEG4_PART2) { + if (esd->decoderConfig->objectTypeIndication==GF_CODECID_MPEG4_PART2) { #ifndef GPAC_DISABLE_AV_PARSERS if (!esd->decoderConfig->decoderSpecificInfo) { #else @@ -2094,16 +2247,12 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) } #endif } else if (gf_isom_get_avc_svc_type(file, trackNum, 1) != GF_ISOM_AVCTYPE_NONE) { -#ifndef GPAC_DISABLE_AV_PARSERS GF_AVCConfig *avccfg, *svccfg, *mvccfg; - GF_AVCConfigSlot *slc; - s32 par_n, par_d; -#endif gf_isom_get_visual_info(file, trackNum, 1, &w, &h); if (full_dump) fprintf(stderr, "\t"); fprintf(stderr, "AVC/H264 Video - Visual Size %d x %d\n", w, h); -#ifndef GPAC_DISABLE_AV_PARSERS + avccfg = gf_isom_avc_config_get(file, trackNum, 1); svccfg = gf_isom_svc_config_get(file, trackNum, 1); mvccfg = gf_isom_mvc_config_get(file, trackNum, 1); @@ -2113,8 +2262,11 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) fprintf(stderr, "\tAVC Info: %d SPS - %d PPS", gf_list_count(avccfg->sequenceParameterSets) , gf_list_count(avccfg->pictureParameterSets) ); fprintf(stderr, " - Profile %s @ Level %g\n", gf_avc_get_profile_name(avccfg->AVCProfileIndication), ((Double)avccfg->AVCLevelIndication)/10.0 ); fprintf(stderr, "\tNAL Unit length bits: %d\n", 8*avccfg->nal_unit_size); + +#ifndef GPAC_DISABLE_AV_PARSERS for (i=0; isequenceParameterSets); i++) { - slc = gf_list_get(avccfg->sequenceParameterSets, i); + s32 par_n, par_d; + GF_AVCConfigSlot *slc = gf_list_get(avccfg->sequenceParameterSets, i); gf_avc_get_sps_info(slc->data, slc->size, NULL, NULL, NULL, &par_n, &par_d); if ((par_n>0) && (par_d>0)) { u32 tw, th; @@ -2123,6 +2275,8 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) } if (!full_dump) break; } +#endif + if (avccfg->chroma_bit_depth) { fprintf(stderr, "\tChroma format %s - Luma bit depth %d - chroma bit depth %d\n", gf_avc_hevc_get_chroma_format_name(avccfg->chroma_format), avccfg->luma_bit_depth, avccfg->chroma_bit_depth); } @@ -2135,9 +2289,11 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) if (svccfg) { fprintf(stderr, "\n\tSVC Info: %d SPS - %d PPS - Profile %s @ Level %g\n", gf_list_count(svccfg->sequenceParameterSets) , gf_list_count(svccfg->pictureParameterSets), gf_avc_get_profile_name(svccfg->AVCProfileIndication), ((Double)svccfg->AVCLevelIndication)/10.0 ); fprintf(stderr, "\tSVC NAL Unit length bits: %d\n", 8*svccfg->nal_unit_size); +#ifndef GPAC_DISABLE_AV_PARSERS for (i=0; isequenceParameterSets); i++) { - slc = gf_list_get(svccfg->sequenceParameterSets, i); + GF_AVCConfigSlot *slc = gf_list_get(svccfg->sequenceParameterSets, i); if (slc) { + s32 par_n, par_d; u32 s_w, s_h, sps_id; gf_avc_get_sps_info(slc->data, slc->size, &sps_id, &s_w, &s_h, &par_n, &par_d); fprintf(stderr, "\t\tSPS ID %d - Visual Size %d x %d\n", sps_id, s_w, s_h); @@ -2148,6 +2304,7 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) } } } +#endif print_config_hash(svccfg->sequenceParameterSets, "SPS"); print_config_hash(svccfg->pictureParameterSets, "PPS"); print_config_hash(svccfg->sequenceParameterSetExtensions, "SPSEx"); @@ -2158,10 +2315,12 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) if (mvccfg) { fprintf(stderr, "\n\tMVC Info: %d SPS - %d PPS - Profile %s @ Level %g\n", gf_list_count(mvccfg->sequenceParameterSets) , gf_list_count(mvccfg->pictureParameterSets), gf_avc_get_profile_name(mvccfg->AVCProfileIndication), ((Double)mvccfg->AVCLevelIndication)/10.0 ); fprintf(stderr, "\tMVC NAL Unit length bits: %d\n", 8*mvccfg->nal_unit_size); +#ifndef GPAC_DISABLE_AV_PARSERS for (i=0; isequenceParameterSets); i++) { - slc = gf_list_get(mvccfg->sequenceParameterSets, i); + GF_AVCConfigSlot *slc = gf_list_get(mvccfg->sequenceParameterSets, i); if (slc) { u32 s_w, s_h, sps_id; + s32 par_n, par_d; gf_avc_get_sps_info(slc->data, slc->size, &sps_id, &s_w, &s_h, &par_n, &par_d); fprintf(stderr, "\t\tSPS ID %d - Visual Size %d x %d\n", sps_id, s_w, s_h); if ((par_n>0) && (par_d>0)) { @@ -2171,19 +2330,19 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) } } } +#endif print_config_hash(mvccfg->sequenceParameterSets, "SPS"); print_config_hash(mvccfg->pictureParameterSets, "PPS"); gf_odf_avc_cfg_del(mvccfg); } -#endif /*GPAC_DISABLE_AV_PARSERS*/ - } else if ((esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_HEVC) - || (esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_LHVC) + } else if ((esd->decoderConfig->objectTypeIndication==GF_CODECID_HEVC) + || (esd->decoderConfig->objectTypeIndication==GF_CODECID_LHVC) ) { + GF_HEVCConfig *hevccfg, *lhvccfg; GF_OperatingPointsInformation *oinf; #if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) HEVCState hevc_state; - GF_HEVCConfig *hevccfg, *lhvccfg; memset(&hevc_state, 0, sizeof(HEVCState)); hevc_state.sps_active_idx = -1; #endif @@ -2191,18 +2350,17 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) gf_isom_get_visual_info(file, trackNum, 1, &w, &h); if (full_dump) fprintf(stderr, "\t"); fprintf(stderr, "HEVC Video - Visual Size %d x %d\n", w, h); -#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) hevccfg = gf_isom_hevc_config_get(file, trackNum, 1); lhvccfg = gf_isom_lhvc_config_get(file, trackNum, 1); if (msub_type==GF_ISOM_SUBTYPE_HVT1) { - const char *data; - u32 size; - u32 is_default, x,y,w,h, id, independent; + const u8 *data; + u32 tsize; + u32 is_default, tx,ty,tw,th, id, independent; Bool full_frame; - if (gf_isom_get_tile_info(file, trackNum, 1, &is_default, &id, &independent, &full_frame, &x, &y, &w, &h)) { - fprintf(stderr, "\tHEVC Tile - ID %d independent %d (x,y,w,h)=%d,%d,%d,%d \n", id, independent, x, y, w, h); - } else if (gf_isom_get_sample_group_info(file, trackNum, 1, GF_4CC('t','r','i','f'), &is_default, &data, &size)) { + if (gf_isom_get_tile_info(file, trackNum, 1, &is_default, &id, &independent, &full_frame, &tx, &ty, &tw, &th)) { + fprintf(stderr, "\tHEVC Tile - ID %d independent %d (x,y,w,h)=%d,%d,%d,%d \n", id, independent, tx, ty, tw, th); + } else if (gf_isom_get_sample_group_info(file, trackNum, 1, GF_ISOM_SAMPLE_GROUP_TRIF, &is_default, &data, &tsize)) { fprintf(stderr, "\tHEVC Tile track containing a tile set\n"); } else { fprintf(stderr, "\tHEVC Tile track without tiling info\n"); @@ -2210,17 +2368,25 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) } else if (!hevccfg && !lhvccfg) { fprintf(stderr, "\n\n\tNon-compliant HEVC track: No hvcC or shcC found in sample description\n"); } - - if (gf_isom_get_reference_count(file, trackNum, GF_4CC('s','a','b','t'))) { + + if (gf_isom_get_reference_count(file, trackNum, GF_ISOM_REF_SABT)) { fprintf(stderr, "\tHEVC Tile base track\n"); } if (hevccfg) { - dump_hevc_track_info(file, trackNum, hevccfg, &hevc_state); + dump_hevc_track_info(file, trackNum, hevccfg +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) + , &hevc_state +#endif + ); gf_odf_hevc_cfg_del(hevccfg); fprintf(stderr, "\n"); } if (lhvccfg) { - dump_hevc_track_info(file, trackNum, lhvccfg, &hevc_state); + dump_hevc_track_info(file, trackNum, lhvccfg +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) + , &hevc_state +#endif + ); gf_odf_hevc_cfg_del(lhvccfg); } @@ -2244,27 +2410,26 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) fprintf(stderr, ") num_profile_tier_level %d ", gf_list_count(oinf->profile_tier_levels) ); fprintf(stderr, " num_operating_points %d dependency layers %d \n", gf_list_count(oinf->operating_points), gf_list_count(oinf->dependency_layers) ); } -#endif /*GPAC_DISABLE_AV_PARSERS && defined(GPAC_DISABLE_HEVC)*/ } /*OGG media*/ - else if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_MEDIA_OGG) { + else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_THEORA) { char *szName; gf_isom_get_visual_info(file, trackNum, 1, &w, &h); if (full_dump) fprintf(stderr, "\t"); - if (!strnicmp(&esd->decoderConfig->decoderSpecificInfo->data[3], "theora", 6)) szName = "Theora"; + if (!strnicmp((char *) &esd->decoderConfig->decoderSpecificInfo->data[3], "theora", 6)) szName = "Theora"; else szName = "Unknown"; fprintf(stderr, "Ogg/%s video / GPAC Mux - Visual Size %d x %d\n", szName, w, h); } - else if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_IMAGE_JPEG) { + else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_JPEG) { gf_isom_get_visual_info(file, trackNum, 1, &w, &h); fprintf(stderr, "JPEG Stream - Visual Size %d x %d\n", w, h); } - else if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_IMAGE_PNG) { + else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_PNG) { gf_isom_get_visual_info(file, trackNum, 1, &w, &h); fprintf(stderr, "PNG Stream - Visual Size %d x %d\n", w, h); } - else if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_IMAGE_JPEG_2000) { + else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_J2K) { gf_isom_get_visual_info(file, trackNum, 1, &w, &h); fprintf(stderr, "JPEG2000 Stream - Visual Size %d x %d\n", w, h); } @@ -2285,11 +2450,11 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) #endif Bool is_mp2 = GF_FALSE; switch (esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_AUDIO_AAC_MPEG2_MP: - case GPAC_OTI_AUDIO_AAC_MPEG2_LCP: - case GPAC_OTI_AUDIO_AAC_MPEG2_SSRP: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: is_mp2 = GF_TRUE; - case GPAC_OTI_AUDIO_AAC_MPEG4: + case GF_CODECID_AAC_MPEG4: #ifndef GPAC_DISABLE_AV_PARSERS if (!esd->decoderConfig->decoderSpecificInfo) e = GF_NON_COMPLIANT_BITSTREAM; @@ -2298,12 +2463,19 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) if (full_dump) fprintf(stderr, "\t"); if (e) fprintf(stderr, "Corrupted AAC Config\n"); else { + char *signaling = "implicit"; char *heaac = ""; if (!is_mp2 && a_cfg.has_sbr) { if (a_cfg.has_ps) heaac = "(HE-AAC v2) "; else heaac = "(HE-AAC v1) "; } - fprintf(stderr, "%s %s- %d Channel(s) - SampleRate %d", gf_m4a_object_type_name(a_cfg.base_object_type), heaac, a_cfg.nb_chan, a_cfg.base_sr); + if (a_cfg.base_object_type==2) { + if (a_cfg.has_ps || a_cfg.has_sbr) + signaling = "backward compatible"; + } else { + signaling = "hierarchical"; + } + fprintf(stderr, "%s (AOT=%d %s) %s- %d Channel(s) - SampleRate %d", gf_m4a_object_type_name(a_cfg.base_object_type), a_cfg.base_object_type, signaling, heaac, a_cfg.nb_chan, a_cfg.base_sr); if (is_mp2) fprintf(stderr, " (MPEG-2 Signaling)"); if (a_cfg.has_sbr) fprintf(stderr, " - SBR: SampleRate %d Type %s", a_cfg.sbr_sr, gf_m4a_object_type_name(a_cfg.sbr_object_type)); if (a_cfg.has_ps) fprintf(stderr, " - PS"); @@ -2313,21 +2485,21 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) fprintf(stderr, "MPEG-2/4 Audio - %d Channels - SampleRate %d\n", nb_ch, sr); #endif break; - case GPAC_OTI_AUDIO_MPEG2_PART3: - case GPAC_OTI_AUDIO_MPEG1: + case GF_CODECID_MPEG2_PART3: + case GF_CODECID_MPEG_AUDIO: if (msub_type == GF_ISOM_SUBTYPE_MPEG4_CRYP) { fprintf(stderr, "MPEG-1/2 Audio - %d Channels - SampleRate %d\n", nb_ch, sr); } else { #ifndef GPAC_DISABLE_AV_PARSERS GF_ISOSample *samp = gf_isom_get_sample(file, trackNum, 1, &oti); if (samp) { - oti = GF_4CC((u8)samp->data[0], (u8)samp->data[1], (u8)samp->data[2], (u8)samp->data[3]); + u32 mhdr = GF_4CC((u8)samp->data[0], (u8)samp->data[1], (u8)samp->data[2], (u8)samp->data[3]); if (full_dump) fprintf(stderr, "\t"); fprintf(stderr, "%s Audio - %d Channel(s) - SampleRate %d - Layer %d\n", - gf_mp3_version_name(oti), - gf_mp3_num_channels(oti), - gf_mp3_sampling_rate(oti), - gf_mp3_layer(oti) + gf_mp3_version_name(mhdr), + gf_mp3_num_channels(mhdr), + gf_mp3_sampling_rate(mhdr), + gf_mp3_layer(mhdr) ); gf_isom_sample_del(&samp); } else { @@ -2339,30 +2511,28 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) } break; /*OGG media*/ - case GPAC_OTI_MEDIA_OGG: - { - char *szName; - if (full_dump) fprintf(stderr, "\t"); - if (!strnicmp(&esd->decoderConfig->decoderSpecificInfo->data[3], "vorbis", 6)) szName = "Vorbis"; - else if (!strnicmp(&esd->decoderConfig->decoderSpecificInfo->data[2], "Speex", 5)) szName = "Speex"; - else if (!strnicmp(&esd->decoderConfig->decoderSpecificInfo->data[2], "Flac", 4)) szName = "Flac"; - else szName = "Unknown"; - fprintf(stderr, "Ogg/%s audio / GPAC Mux - Sample Rate %d - %d channel(s)\n", szName, sr, nb_ch); - } - break; - case GPAC_OTI_AUDIO_EVRC_VOICE: + case GF_CODECID_VORBIS: + fprintf(stderr, "Ogg/Vorbis audio / GPAC Mux - Sample Rate %d - %d channel(s)\n", sr, nb_ch); + break; + case GF_CODECID_FLAC: + fprintf(stderr, "Ogg/FLAC audio / GPAC Mux - Sample Rate %d - %d channel(s)\n", sr, nb_ch); + break; + case GF_CODECID_SPEEX: + fprintf(stderr, "Ogg/Speex audio / GPAC Mux - Sample Rate %d - %d channel(s)\n", sr, nb_ch); + break; + case GF_CODECID_EVRC: fprintf(stderr, "EVRC Audio - Sample Rate 8000 - 1 channel\n"); break; - case GPAC_OTI_AUDIO_SMV_VOICE: + case GF_CODECID_SMV: fprintf(stderr, "SMV Audio - Sample Rate 8000 - 1 channel\n"); break; - case GPAC_OTI_AUDIO_13K_VOICE: + case GF_CODECID_QCELP: fprintf(stderr, "QCELP Audio - Sample Rate 8000 - 1 channel\n"); break; /*packetVideo hack for EVRC...*/ - case 0xD1: + case GF_CODECID_EVRC_PV: if (esd->decoderConfig->decoderSpecificInfo && (esd->decoderConfig->decoderSpecificInfo->dataLength==8) - && !strnicmp(esd->decoderConfig->decoderSpecificInfo->data, "pvmm", 4)) { + && !strnicmp((char *)esd->decoderConfig->decoderSpecificInfo->data, "pvmm", 4)) { if (full_dump) fprintf(stderr, "\t"); fprintf(stderr, "EVRC Audio (PacketVideo Mux) - Sample Rate 8000 - 1 channel\n"); } @@ -2377,19 +2547,19 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) fprintf(stderr, "\tWidth %d Height %d Pixel Metrics %s\n", b_cfg->pixelWidth, b_cfg->pixelHeight, b_cfg->pixelMetrics ? "yes" : "no"); } gf_odf_desc_del((GF_Descriptor *)b_cfg); - } else if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_SCENE_AFX) { + } else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_AFX) { u8 tag = esd->decoderConfig->decoderSpecificInfo ? esd->decoderConfig->decoderSpecificInfo->data[0] : 0xFF; - const char *afxtype = gf_afx_get_type_description(tag); + const char *afxtype = gf_stream_type_afx_name(tag); fprintf(stderr, "AFX Stream - type %s (%d)\n", afxtype, tag); - } else if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_FONT) { + } else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_FONT) { fprintf(stderr, "Font Data stream\n"); - } else if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_SCENE_LASER) { + } else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_LASER) { GF_LASERConfig l_cfg; gf_odf_get_laser_config(esd->decoderConfig->decoderSpecificInfo, &l_cfg); fprintf(stderr, "LASER Stream - %s\n", l_cfg.newSceneIndicator ? "Full Scene" : "Scene Segment"); - } else if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_TEXT_MPEG4) { + } else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_TEXT_MPEG4) { fprintf(stderr, "MPEG-4 Streaming Text stream\n"); - } else if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_SCENE_SYNTHESIZED_TEXTURE) { + } else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_SYNTHESIZED_TEXTURE) { fprintf(stderr, "Synthetized Texture stream stream\n"); } else { fprintf(stderr, "Unknown Systems stream OTI %d\n", esd->decoderConfig->objectTypeIndication); @@ -2440,10 +2610,11 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) fprintf(stderr, "Rights Issuer: %s\n", KMS_URI); fprintf(stderr, "Content ID: %s\n", scheme_URI); if (textHdrs) { - u32 i, offset; + u32 offset; const char *start = textHdrs; fprintf(stderr, "OMA Textual Headers:\n"); - i=offset=0; + i=0; + offset=0; while (i>16, version&0xFFFF); - if (IV_size) fprintf(stderr, "Initialization Vector size: %d bits\n", IV_size*8); + if (IV_size) + fprintf(stderr, "Initialization Vector size: %d bits\n", IV_size*8); + if (gf_isom_cenc_is_pattern_mode(file, trackNum, 1)) + fprintf(stderr, "Pattern mode enabled\n"); + } else if(gf_isom_is_adobe_protection_media(file, trackNum, 1)) { - gf_isom_get_adobe_protection_info(file, trackNum, 1, NULL, &scheme_type, &version); + gf_isom_get_adobe_protection_info(file, trackNum, 1, NULL, &scheme_type, &version, NULL); fprintf(stderr, "\n*Encrypted stream - Adobe protection scheme %s (version %d)\n", gf_4cc_to_str(scheme_type), version); } else { - fprintf(stderr, "\n*Encrypted stream - unknown scheme %s\n", gf_4cc_to_str(gf_isom_is_media_encrypted(file, trackNum, 1) )); + fprintf(stderr, "\n*Encrypted stream - unknown scheme %s\n", gf_4cc_to_str(gf_isom_is_media_encrypted(file, trackNum, 0) )); } } } + } else if (msub_type == GF_ISOM_SUBTYPE_AV01) { + GF_AV1Config *av1c; + u32 w, h; + gf_isom_get_visual_info(file, trackNum, 1, &w, &h); + fprintf(stderr, "\tAOM AV1 stream - Resolution %d x %d\n", w, h); + + av1c = gf_isom_av1_config_get(file, trackNum, 1); + fprintf(stderr, "\tversion=%u, profile=%u, level_idx0=%u, tier=%u\n", (u32)av1c->version, (u32)av1c->seq_profile, (u32)av1c->seq_level_idx_0, (u32)av1c->seq_tier_0); + fprintf(stderr, "\thigh_bitdepth=%u, twelve_bit=%u, monochrome=%u\n", (u32)av1c->high_bitdepth, (u32)av1c->twelve_bit, (u32)av1c->monochrome); + fprintf(stderr, "\tchroma: subsampling_x=%u, subsampling_y=%u, sample_position=%u\n", (u32)av1c->chroma_subsampling_x, (u32)av1c->chroma_subsampling_y, (u32)av1c->chroma_sample_position); + + if (av1c->initial_presentation_delay_present) + fprintf(stderr, "\tInitial presentation delay %u\n", (u32) av1c->initial_presentation_delay_minus_one+1); + + count = gf_list_count(av1c->obu_array); + for (i=0; iobu_array, i); + gf_sha1_csum((u8*)obu->obu, (u32)obu->obu_length, hash); + fprintf(stderr, "\tOBU#%d %s hash: ", i+1, gf_av1_get_obu_name(obu->obu_type) ); + for (j=0; j<20; j++) fprintf(stderr, "%02X", hash[j]); + fprintf(stderr, "\n"); + } + gf_odf_av1_cfg_del(av1c); } else if (msub_type == GF_ISOM_SUBTYPE_3GP_H263) { u32 w, h; gf_isom_get_visual_info(file, trackNum, 1, &w, &h); fprintf(stderr, "\t3GPP H263 stream - Resolution %d x %d\n", w, h); - } else if (msub_type == GF_4CC('m','j','p','2')) { + } else if (msub_type == GF_ISOM_SUBTYPE_MJP2) { u32 w, h; gf_isom_get_visual_info(file, trackNum, 1, &w, &h); fprintf(stderr, "\tMotionJPEG2000 stream - Resolution %d x %d\n", w, h); @@ -2488,26 +2687,25 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) fprintf(stderr, "\t3GPP QCELP stream - Sample Rate %d - %d channel(s) %d bps\n", sr, nb_ch, (u32) bps); } else if (msub_type == GF_ISOM_SUBTYPE_MP3) { fprintf(stderr, "\tMPEG 1/2 Audio stream - Sample Rate %d - %d channel(s) %d bps\n", sr, nb_ch, (u32) bps); - } else if (msub_type == GF_ISOM_SUBTYPE_AC3) { + } else if ((msub_type == GF_ISOM_SUBTYPE_AC3) || (msub_type == GF_ISOM_SUBTYPE_EC3)) { u32 br = 0; - Bool lfe = 0; - Bool is_ec3 = 0; + const char *lfe = ""; + Bool is_ec3 = (msub_type == GF_ISOM_SUBTYPE_EC3) ? GF_TRUE : GF_FALSE; #ifndef GPAC_DISABLE_AV_PARSERS GF_AC3Config *ac3 = gf_isom_ac3_config_get(file, trackNum, 1); if (ac3) { - int i; nb_ch = gf_ac3_get_channels(ac3->streams[0].acmod); for (i=0; istreams[0].nb_dep_sub; ++i) { assert(ac3->streams[0].nb_dep_sub == 1); nb_ch += gf_ac3_get_channels(ac3->streams[0].chan_loc); } - lfe = ac3->streams[0].lfon; + if (ac3->streams[0].lfon) lfe = ".1"; br = ac3->is_ec3 ? ac3->brcode : gf_ac3_get_bitrate(ac3->brcode); is_ec3 = ac3->is_ec3; gf_free(ac3); } #endif - fprintf(stderr, "\t%s stream - Sample Rate %d - %d%s channel(s) - bitrate %d\n", is_ec3 ? "EC-3" : "AC-3", sr, nb_ch, lfe ? ".1" : "", br); + fprintf(stderr, "\t%s stream - Sample Rate %d - %d%s channel(s) - bitrate %d\n", is_ec3 ? "EC-3" : "AC-3", sr, nb_ch, lfe, br); } else if (msub_type == GF_ISOM_SUBTYPE_3GP_SMV) { fprintf(stderr, "\t3GPP SMV stream - Sample Rate %d - %d channel(s) %d bits per samples\n", sr, nb_ch, (u32) bps); } else if (msub_type == GF_ISOM_SUBTYPE_3GP_DIMS) { @@ -2524,10 +2722,10 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) if (dims.content_script_types) fprintf(stderr, "\tscript languages %s\n", dims.content_script_types); } else if (mtype==GF_ISOM_MEDIA_HINT) { u32 refTrack; - s32 i, refCount = gf_isom_get_reference_count(file, trackNum, GF_ISOM_REF_HINT); - if (refCount) { + s32 refCount = gf_isom_get_reference_count(file, trackNum, GF_ISOM_REF_HINT); + if (refCount>0) { fprintf(stderr, "Streaming Hint Track for track%s ", (refCount>1) ? "s" :""); - for (i=0; i0) { + for (i=0; i<(u32) refCount; i++) { + const char *name = gf_isom_get_payt_info(file, trackNum, i+1, &refTrack); + fprintf(stderr, "\tPayload ID %d: type %s\n", refTrack, name); + } } #endif } else if (mtype==GF_ISOM_MEDIA_FLASH) { @@ -2619,11 +2819,42 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) } else { fprintf(stderr, "Unknown Metadata Stream\n"); } + } else if ((msub_type == GF_ISOM_SUBTYPE_MH3D_MHA1) || (msub_type == GF_ISOM_SUBTYPE_MH3D_MHA2)) { + fprintf(stderr, "\tMPEG-H Audio stream - Sample Rate %d - %d channel(s) %d bps\n", sr, nb_ch, (u32) bps); + esd = gf_media_map_esd(file, trackNum, 1); + if (!esd || !esd->decoderConfig || !esd->decoderConfig->decoderSpecificInfo + || !esd->decoderConfig->decoderSpecificInfo->data || (esd->decoderConfig->decoderSpecificInfo->dataLength<5) + ) { + fprintf(stderr, "\tInvalid MPEG-H audio config\n"); + } else { + fprintf(stderr, "\tProfileLevelIndication: %02X\n", esd->decoderConfig->decoderSpecificInfo->data[1]); + } + if (esd) gf_odf_desc_del((GF_Descriptor *)esd); + } else if ((msub_type == GF_ISOM_SUBTYPE_MH3D_MHM1) || (msub_type == GF_ISOM_SUBTYPE_MH3D_MHM2)) { + fprintf(stderr, "\tMPEG-H AudioMux stream - Sample Rate %d - %d channel(s) %d bps\n", sr, nb_ch, (u32) bps); + esd = gf_media_map_esd(file, trackNum, 1); + if (!esd || !esd->decoderConfig || !esd->decoderConfig->decoderSpecificInfo + || !esd->decoderConfig->decoderSpecificInfo->data) { + GF_ISOSample *samp = gf_isom_get_sample(file, trackNum, 1, NULL); + if (samp) { + s32 PL = gf_mpegh_get_mhas_pl(samp->data, samp->dataLength); + if (PL>=0) + fprintf(stderr, "\tProfileLevelIndication: %02X\n", PL); + gf_isom_sample_del(&samp); + } + } else if (esd->decoderConfig->decoderSpecificInfo->dataLength<5) { + fprintf(stderr, "\tInvalid MPEG-H audio config\n"); + } else { + fprintf(stderr, "\tProfileLevelIndication: %02X\n", esd->decoderConfig->decoderSpecificInfo->data[1]); + } + if (esd) gf_odf_desc_del((GF_Descriptor *)esd); } else { GF_GenericSampleDescription *udesc = gf_isom_get_generic_sample_description(file, trackNum, 1); if (udesc) { - if (mtype==GF_ISOM_MEDIA_VISUAL) { - fprintf(stderr, "Visual Track - Compressor \"%s\" - Resolution %d x %d\n", udesc->compressor_name, udesc->width, udesc->height); + if (gf_isom_is_video_handler_type(mtype) ) { + fprintf(stderr, "%s Track - Compressor \"%s\" - Resolution %d x %d\n", + (mtype == GF_ISOM_MEDIA_VISUAL?"Visual":"Auxiliary Video"), + udesc->compressor_name, udesc->width, udesc->height); } else if (mtype==GF_ISOM_MEDIA_AUDIO) { fprintf(stderr, "Audio Track - Sample Rate %d - %d channel(s)\n", udesc->samplerate, udesc->nb_channels); } else { @@ -2643,9 +2874,11 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) } { - char szCodec[100]; - gf_media_get_rfc_6381_codec_name(file, trackNum, szCodec, GF_FALSE, GF_FALSE); - fprintf(stderr, "\tRFC6381 Codec Parameters: %s\n", szCodec); + char szCodec[RFC6381_CODEC_NAME_SIZE_MAX]; + GF_Err e = gf_media_get_rfc_6381_codec_name(file, trackNum, szCodec, GF_FALSE, GF_FALSE); + if (e == GF_OK) { + fprintf(stderr, "\tRFC6381 Codec Parameters: %s\n", szCodec); + } } DumpMetaItem(file, 0, trackNum, "Track Meta"); @@ -2691,6 +2924,7 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) fprintf(stderr, "\tNo sync sample found\n"); break; } + fprintf(stderr, "\tMax sample duration: %d / %d\n", gf_isom_get_max_sample_delta(file, trackNum), timescale); if (!full_dump) { fprintf(stderr, "\n"); @@ -2701,28 +2935,40 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) max_rate = rate = 0; time_slice = 0; ts = gf_isom_get_media_timescale(file, trackNum); - for (j=0; jDTS+samp->CTS_Offset; - size += samp->dataLength; - rate += samp->dataLength; - if (samp->DTS - time_slice>ts) { - if (max_rate < rate) max_rate = rate; - rate = 0; - time_slice = samp->DTS; + csize = gf_isom_get_constant_sample_size(file, trackNum); + cdur = gf_isom_get_constant_sample_duration(file, trackNum); + count = gf_isom_get_sample_count(file, trackNum); + if (csize && cdur) { + size = count * csize; + dur = cdur * count; + } else { + + for (j=0; jDTS+samp->CTS_Offset; + size += samp->dataLength; + rate += samp->dataLength; + if (samp->DTS - time_slice>ts) { + if (max_rate < rate) max_rate = rate; + rate = 0; + time_slice = samp->DTS; + } + gf_isom_sample_del(&samp); } - gf_isom_sample_del(&samp); } fprintf(stderr, "\nComputed info from media:\n"); + if (csize && cdur) { + fprintf(stderr, "\tConstant sample size %d bytes and dur %d / %d\n", csize, cdur, ts); + } scale = 1000; scale /= ts; dur = (u64) (scale * (s64)dur); @@ -2733,7 +2979,12 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) } /*rate in byte, dur is in ms*/ rate = (u32) ((size * 8 * 1000) / dur); - max_rate *= 8; + + if (!max_rate) + max_rate = rate; + else + max_rate *= 8; + if (rate >= 1500) { rate /= 1000; max_rate /= 1000; @@ -2752,7 +3003,6 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) count = gf_isom_get_chapter_count(file, trackNum); if (count) { - char szDur[20]; const char *name; u64 time; fprintf(stderr, "\nChapters:\n"); @@ -2763,58 +3013,21 @@ void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump) } } -static const char* ID3v1Genres[] = { - "Blues", "Classic Rock", "Country", "Dance", "Disco", - "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal", - "New Age", "Oldies", "Other", "Pop", "R&B", - "Rap", "Reggae", "Rock", "Techno", "Industrial", - "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", - "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", - "Fusion", "Trance", "Classical", "Instrumental", "Acid", - "House", "Game", "Sound Clip", "Gospel", "Noise", - "AlternRock", "Bass", "Soul", "Punk", "Space", - "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", - "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", - "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta", - "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", - "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes", - "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", - "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", - "Folk", "Folk/Rock", "National Folk", "Swing", -}; -static const char *id3_get_genre(u32 tag) -{ - if ((tag>0) && (tag <= (sizeof(ID3v1Genres)/sizeof(const char *)) )) { - return ID3v1Genres[tag-1]; - } - return "Unknown"; -} -u32 id3_get_genre_tag(const char *name) -{ - u32 i, count = sizeof(ID3v1Genres)/sizeof(const char *); - if (!name) return 0; - for (i=0; iSDTs) ; GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("Program Description found - %d desc:\n", count)); for (i=0; iSDTs, i); GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("\tServiceID %d - Provider %s - Name %s\n", sdt->service_id, sdt->provider, sdt->service)); } +#endif break; case GF_M2TS_EVT_SDT_UPDATE: +#ifndef GPAC_DISABLE_LOG count = gf_list_count(ts->SDTs) ; GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("Program Description updated - %d desc\n", count)); for (i=0; iSDTs, i); GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("\tServiceID %d - Provider %s - Name %s\n", sdt->service_id, sdt->provider, sdt->service)); } +#endif break; case GF_M2TS_EVT_SDT_REPEAT: break; @@ -3119,7 +3340,7 @@ static void on_m2ts_dump_event(GF_M2TS_Demuxer *ts, u32 evt_type, void *par) if (interpolated_pcr_value) fprintf(dumper->timestamps_info_file, "%f", interpolated_pcr_value/(300.0 * 90000)); fprintf(dumper->timestamps_info_file, "\t"); if (pck->DTS) fprintf(dumper->timestamps_info_file, "%f", (pck->DTS / 90000.0)); - fprintf(dumper->timestamps_info_file, "\t%f\t%d\t%d", pck->PTS / 90000.0, (pck->flags & GF_M2TS_PES_PCK_RAP ? 1 : 0), (pck->flags & GF_M2TS_PES_PCK_DISCONTINUITY ? 1 : 0)); + fprintf(dumper->timestamps_info_file, "\t%f\t%d\t%d", pck->PTS / 90000.0, (pck->flags & GF_M2TS_PES_PCK_RAP) ? 1 : 0, (pck->flags & GF_M2TS_PES_PCK_DISCONTINUITY) ? 1 : 0); if (interpolated_pcr_value) { diff = (pck->DTS ? pck->DTS : pck->PTS) / 90000.0; diff -= pes->last_pcr_value / (300.0 * 90000); @@ -3132,7 +3353,7 @@ static void on_m2ts_dump_event(GF_M2TS_Demuxer *ts, u32 evt_type, void *par) } if (dumper->has_seen_pat && dumper->pes_out && (dumper->dump_pid == pck->stream->pid)) { - gf_fwrite(pck->data, pck->data_len, 1, dumper->pes_out); + gf_fwrite(pck->data, pck->data_len, dumper->pes_out); } break; case GF_M2TS_EVT_PES_PCR: @@ -3140,7 +3361,7 @@ static void on_m2ts_dump_event(GF_M2TS_Demuxer *ts, u32 evt_type, void *par) if (gf_list_count(ts->programs)>1 && pck->stream->program->number != dumper->prog_number) break; if (dumper->timestamps_info_file) { - fprintf(dumper->timestamps_info_file, "%u\t%d\t%f\t\t\t\t%d\n", pck->stream->program->last_pcr_value_pck_number, pck->stream->pid, pck->PTS / (300*90000.0), (pck->flags & GF_M2TS_PES_PCK_DISCONTINUITY ? 1 : 0)); + fprintf(dumper->timestamps_info_file, "%u\t%d\t%f\t\t\t\t%d\n", pck->stream->program->last_pcr_value_pck_number, pck->stream->pid, pck->PTS / (300*90000.0), (pck->flags & GF_M2TS_PES_PCK_DISCONTINUITY) ? 1 : 0); } break; case GF_M2TS_EVT_SL_PCK: @@ -3153,7 +3374,7 @@ static void on_m2ts_dump_event(GF_M2TS_Demuxer *ts, u32 evt_type, void *par) if (sl_pck->stream->mpeg4_es_id) { GF_ESD *esd = ((GF_M2TS_PES*)sl_pck->stream)->esd; if (!dumper->is_info_dumped) { - if (esd->decoderConfig->decoderSpecificInfo) gf_fwrite(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, 1, dumper->pes_out_info); + if (esd->decoderConfig->decoderSpecificInfo) gf_fwrite(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, dumper->pes_out_info); dumper->is_info_dumped = 1; fprintf(dumper->pes_out_nhml, "pes_out_nhml, "timeScale=\"%d\" ", esd->slConfig->timestampResolution); @@ -3164,8 +3385,8 @@ static void on_m2ts_dump_event(GF_M2TS_Demuxer *ts, u32 evt_type, void *par) fprintf(dumper->pes_out_nhml, "inRootOD=\"yes\">\n"); } gf_sl_depacketize(esd->slConfig, &header, sl_pck->data, sl_pck->data_len, &header_len); - gf_fwrite(sl_pck->data+header_len, sl_pck->data_len-header_len, 1, dumper->pes_out); - fprintf(dumper->pes_out_nhml, "\n", LLD_CAST header.decodingTimeStamp, sl_pck->data_len-header_len, (header.randomAccessPointFlag?"yes":"no")); + gf_fwrite(sl_pck->data+header_len, sl_pck->data_len-header_len, dumper->pes_out); + fprintf(dumper->pes_out_nhml, "\n", header.decodingTimeStamp, sl_pck->data_len-header_len, (header.randomAccessPointFlag?"yes":"no")); } } } @@ -3176,7 +3397,7 @@ static void on_m2ts_dump_event(GF_M2TS_Demuxer *ts, u32 evt_type, void *par) void dump_mpeg2_ts(char *mpeg2ts_file, char *out_name, Bool prog_num) { - char data[188]; + u8 data[188]; GF_M2TS_Dump dumper; u32 size; @@ -3184,6 +3405,10 @@ void dump_mpeg2_ts(char *mpeg2ts_file, char *out_name, Bool prog_num) GF_M2TS_Demuxer *ts; FILE *src; + if (!prog_num && !out_name) { + fprintf(stderr, "No program number nor output filename specified. No timestamp file will be generated."); + } + src = gf_fopen(mpeg2ts_file, "rb"); if (!src) { fprintf(stderr, "Cannot open %s: no such file\n", mpeg2ts_file); @@ -3220,7 +3445,7 @@ void dump_mpeg2_ts(char *mpeg2ts_file, char *out_name, Bool prog_num) /* first loop to process all packets between two PAT, and assume all signaling was found between these 2 PATs */ while (!feof(src)) { - size = (u32) fread(data, 1, 188, src); + size = (u32) gf_fread(data, 188, src); if (size<188) break; gf_m2ts_process_data(ts, data, size); @@ -3228,6 +3453,16 @@ void dump_mpeg2_ts(char *mpeg2ts_file, char *out_name, Bool prog_num) } dumper.has_seen_pat = GF_TRUE; + if (!prog_num) { + GF_M2TS_Program *p = gf_list_get(ts->programs, 0); + if (p) prog_num = p->number; + fprintf(stderr, "No program number specified, defaulting to first program\n"); + } + + if (!prog_num && !out_name) { + fprintf(stderr, "No program number nor output filename specified. No timestamp file will be generated\n"); + } + if (prog_num) { sprintf(dumper.timestamps_info_name, "%s_prog_%d_timestamps.txt", mpeg2ts_file, prog_num/*, mpeg2ts_file*/); dumper.timestamps_info_file = gf_fopen(dumper.timestamps_info_name, "wt"); @@ -3243,7 +3478,7 @@ void dump_mpeg2_ts(char *mpeg2ts_file, char *out_name, Bool prog_num) fdone = 0; while (!feof(src)) { - size = (u32) fread(data, 1, 188, src); + size = (u32) gf_fread(data, 188, src); if (size<188) break; gf_m2ts_process_data(ts, data, size); @@ -3265,5 +3500,274 @@ void dump_mpeg2_ts(char *mpeg2ts_file, char *out_name, Bool prog_num) if (dumper.timestamps_info_file) gf_fclose(dumper.timestamps_info_file); } - #endif /*GPAC_DISABLE_MPEG2TS*/ + + +#include +#include + +void get_file_callback(void *usr_cbk, GF_NETIO_Parameter *parameter) +{ + if (parameter->msg_type==GF_NETIO_DATA_EXCHANGE) { + u64 tot_size, done, max; + u32 bps; + gf_dm_sess_get_stats(parameter->sess, NULL, NULL, &tot_size, &done, &bps, NULL); + if (tot_size) { + max = done; + max *= 100; + max /= tot_size; + fprintf(stderr, "download %02d %% at %05d kpbs\r", (u32) max, bps*8/1000); + } + } +} + +static GF_DownloadSession *get_file(const char *url, GF_DownloadManager *dm, GF_Err *e) +{ + GF_DownloadSession *sess; + sess = gf_dm_sess_new(dm, url, GF_NETIO_SESSION_NOT_THREADED, get_file_callback, NULL, e); + if (!sess) return NULL; + *e = gf_dm_sess_process(sess); + if (*e) { + gf_dm_sess_del(sess); + return NULL; + } + return sess; +} + +static void revert_cache_file(char *item_path) +{ + char szPATH[GF_MAX_PATH]; + const char *url; + GF_Config *cached; + + if (!strstr(item_path, "gpac_cache_")) { + fprintf(stderr, "%s is not a gpac cache file\n", item_path); + return; + } + if (!strncmp(item_path, "./", 2) || !strncmp(item_path, ".\\", 2)) + item_path += 2; + + strcpy(szPATH, item_path); + strcat(szPATH, ".txt"); + + cached = gf_cfg_new(NULL, szPATH); + url = gf_cfg_get_key(cached, "cache", "url"); + if (url) url = strstr(url, "://"); + if (url) { + u32 i, len, dir_len=0, k=0; + char *sep; + char *dst_name; + sep = strstr(item_path, "gpac_cache_"); + if (sep) { + sep[0] = 0; + dir_len = (u32) strlen(item_path); + sep[0] = 'g'; + } + url+=3; + len = (u32) strlen(url); + dst_name = gf_malloc(len+dir_len+1); + memset(dst_name, 0, len+dir_len+1); + + strncpy(dst_name, item_path, dir_len); + k=dir_len; + for (i=0; itype==GF_MPD_TYPE_DYNAMIC) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("MPD rip is not supported on live sources\n")); + e = GF_NOT_SUPPORTED; + goto err_exit; + } + + i=0; + while ((period = (GF_MPD_Period *) gf_list_enum(mpd->periods, &i))) { + char *initTemplate = NULL; + Bool segment_base = GF_FALSE; + u32 j=0; + + if (period->segment_base) segment_base=GF_TRUE; + + if (period->segment_template && period->segment_template->initialization) { + initTemplate = period->segment_template->initialization; + } + + while ((as = gf_list_enum(period->adaptation_sets, &j))) { + u32 k=0; + if (!initTemplate && as->segment_template && as->segment_template->initialization) { + initTemplate = as->segment_template->initialization; + } + if (as->segment_base) segment_base=GF_TRUE; + + while ((rep = gf_list_enum(as->representations, &k))) { + u64 out_range_start, out_range_end, segment_duration; + Bool is_in_base_url; + char *seg_url; + u32 seg_idx=0; + if (rep->segment_template && rep->segment_template->initialization) { + initTemplate = rep->segment_template->initialization; + } else if (k>1) { + initTemplate = NULL; + } + if (rep->segment_base) segment_base=GF_TRUE; + + e = gf_mpd_resolve_url(mpd, rep, as, period, mpd_src, 0, GF_MPD_RESOLVE_URL_INIT, 0, 0, &seg_url, &out_range_start, &out_range_end, &segment_duration, &is_in_base_url, NULL, NULL); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Error resolving init segment name : %s\n", gf_error_to_string(e))); + continue; + } + //not a byte range, replace URL + if (segment_base) { + + } else if (out_range_start || out_range_end || !seg_url) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("byte range rip not yet implemented\n")); + if (seg_url) gf_free(seg_url); + e = GF_NOT_SUPPORTED; + goto err_exit; + } + + fprintf(stderr, "Downloading %s\n", seg_url); + sess = get_file(seg_url, dm, &e); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Error downloading init segment %s from MPD %s : %s\n", seg_url, mpd_src, gf_error_to_string(e))); + goto err_exit; + } + revert_cache_file((char *) gf_dm_sess_get_cache_name(sess) ); + gf_free(seg_url); + gf_dm_sess_del(sess); + + if (segment_base) continue; + + while (1) { + e = gf_mpd_resolve_url(mpd, rep, as, period, mpd_src, 0, GF_MPD_RESOLVE_URL_MEDIA, seg_idx, 0, &seg_url, &out_range_start, &out_range_end, &segment_duration, NULL, NULL, NULL); + if (e) { + if (e<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Error resolving segment name : %s\n", gf_error_to_string(e))); + } + break; + } + + seg_idx++; + + if (out_range_start || out_range_end || !seg_url) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("byte range rip not yet implemented\n")); + if (seg_url) gf_free(seg_url); + break; + } + fprintf(stderr, "Downloading %s\n", seg_url); + sess = get_file(seg_url, dm, &e); + if (e) { + gf_free(seg_url); + if (e != GF_URL_ERROR) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Error downloading segment %s: %s\n", seg_url, gf_error_to_string(e))); + } else { + //todo, properly detect end of dash representation + e = GF_OK; + } + break; + } + revert_cache_file((char *) gf_dm_sess_get_cache_name(sess) ); + gf_free(seg_url); + gf_dm_sess_del(sess); + } + } + } + } + +err_exit: + if (mpd) gf_mpd_del(mpd); + gf_dm_del(dm); + return e; +} diff --git a/applications/mp4box/fileimport.c b/applications/mp4box/fileimport.c index 437110b..1ad2e19 100644 --- a/applications/mp4box/fileimport.c +++ b/applications/mp4box/fileimport.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / mp4box application @@ -24,7 +24,8 @@ */ -#include +#include "mp4box.h" + #include #include #include @@ -46,6 +47,7 @@ #ifndef GPAC_DISABLE_ISOM_WRITE #include +#include typedef struct { @@ -56,7 +58,7 @@ typedef struct GF_Err set_file_udta(GF_ISOFile *dest, u32 tracknum, u32 udta_type, char *src, Bool is_box_array) { - char *data = NULL; + u8 *data = NULL; GF_Err res = GF_OK; u32 size; bin128 uuid; @@ -64,7 +66,7 @@ GF_Err set_file_udta(GF_ISOFile *dest, u32 tracknum, u32 udta_type, char *src, B if (!udta_type && !is_box_array) return GF_BAD_PARAM; - if (!src) { + if (!src || !strlen(src)) { return gf_isom_remove_user_data(dest, tracknum, udta_type, uuid); } @@ -73,22 +75,12 @@ GF_Err set_file_udta(GF_ISOFile *dest, u32 tracknum, u32 udta_type, char *src, B src += 7; size = (u32) strlen(src); data = gf_malloc(sizeof(char) * size); - size = gf_base64_decode(src, size, data, size); + size = gf_base64_decode((u8 *)src, size, data, size); } else #endif { - FILE *t = gf_fopen(src, "rb"); - if (!t) return GF_IO_ERR; - fseek(t, 0, SEEK_END); - size = ftell(t); - fseek(t, 0, SEEK_SET); - data = gf_malloc(sizeof(char)*size); - if (size != fread(data, 1, size, t) ) { - gf_free(data); - gf_fclose(t); - return GF_IO_ERR; - } - gf_fclose(t); + GF_Err e = gf_file_load_data(src, (u8 **) &data, &size); + if (e) return e; } if (size && data) { @@ -107,10 +99,11 @@ GF_Err set_file_udta(GF_ISOFile *dest, u32 tracknum, u32 udta_type, char *src, B extern u32 swf_flags; extern Float swf_flatten_angle; extern Bool keep_sys_tracks; +extern u32 fs_dump_flags; void scene_coding_log(void *cbk, GF_LOG_Level log_level, GF_LOG_Tool log_tool, const char *fmt, va_list vlist); -void convert_file_info(char *inName, u32 trackID) +void convert_file_info(char *inName, GF_ISOTrackID trackID) { GF_Err e; u32 i; @@ -120,6 +113,8 @@ void convert_file_info(char *inName, u32 trackID) import.trackID = trackID; import.in_name = inName; import.flags = GF_IMPORT_PROBE_ONLY; + import.print_stats_graph = fs_dump_flags; + e = gf_media_import(&import); if (e) { fprintf(stderr, "Error probing file %s: %s\n", inName, gf_error_to_string(e)); @@ -144,80 +139,73 @@ void convert_file_info(char *inName, u32 trackID) if (!trackID) fprintf(stderr, "\tTrack %d type: ", import.tk_info[i].track_num); else fprintf(stderr, "Track type: "); - switch (import.tk_info[i].type) { - case GF_ISOM_MEDIA_VISUAL: - fprintf(stderr, "Video (%s)", gf_4cc_to_str(import.tk_info[i].media_type)); + switch (import.tk_info[i].stream_type) { + case GF_STREAM_VISUAL: + if (import.tk_info[i].media_subtype == GF_ISOM_MEDIA_AUXV) fprintf(stderr, "Auxiliary Video"); + else if (import.tk_info[i].media_subtype == GF_ISOM_MEDIA_PICT) fprintf(stderr, "Picture Sequence"); + else fprintf(stderr, "Video"); + if (import.tk_info[i].video_info.temporal_enhancement) fprintf(stderr, " Temporal Enhancement"); break; - case GF_ISOM_MEDIA_AUDIO: - fprintf(stderr, "Audio (%s)", gf_4cc_to_str(import.tk_info[i].media_type)); + case GF_STREAM_AUDIO: + fprintf(stderr, "Audio"); break; - case GF_ISOM_MEDIA_TEXT: - fprintf(stderr, "Text (%s)", gf_4cc_to_str(import.tk_info[i].media_type)); + case GF_STREAM_TEXT: + fprintf(stderr, "Text"); break; - case GF_ISOM_MEDIA_SCENE: - fprintf(stderr, "Scene (%s)", gf_4cc_to_str(import.tk_info[i].media_type)); + case GF_STREAM_SCENE: + fprintf(stderr, "Scene"); break; - case GF_ISOM_MEDIA_OD: - fprintf(stderr, "OD (%s)", gf_4cc_to_str(import.tk_info[i].media_type)); + case GF_STREAM_OD: + fprintf(stderr, "OD"); break; - case GF_ISOM_MEDIA_META: - fprintf(stderr, "Metadata (%s)", gf_4cc_to_str(import.tk_info[i].media_type)); + case GF_STREAM_METADATA: + fprintf(stderr, "Metadata"); break; default: - fprintf(stderr, "Other (4CC: %s)", gf_4cc_to_str(import.tk_info[i].type)); + fprintf(stderr, "Other (%s)", gf_4cc_to_str(import.tk_info[i].stream_type)); break; } + if (import.tk_info[i].codecid) fprintf(stderr, " Codec %s (ID %d)", gf_codecid_name(import.tk_info[i].codecid), import.tk_info[i].codecid); - if (import.tk_info[i].lang) fprintf(stderr, " - lang %s", gf_4cc_to_str(import.tk_info[i].lang)); + if (import.tk_info[i].lang) fprintf(stderr, " lang %s", gf_4cc_to_str(import.tk_info[i].lang)); - if (import.tk_info[i].mpeg4_es_id) fprintf(stderr, " - MPEG-4 ESID %d", import.tk_info[i].mpeg4_es_id); + if (import.tk_info[i].mpeg4_es_id) fprintf(stderr, " MPEG-4 ESID %d", import.tk_info[i].mpeg4_es_id); if (import.tk_info[i].prog_num) { if (!import.nb_progs) { - fprintf(stderr, " - Program %d", import.tk_info[i].prog_num); + fprintf(stderr, " Program %d", import.tk_info[i].prog_num); } else { u32 j; for (j=0; j> 16, import.tk_info[i].video_info.par & 0xFFFF); + if (import.tk_info[i].stream_type==GF_STREAM_VISUAL) { + if (import.tk_info[i].video_info.par) fprintf(stderr, " PAR: %d:%d", import.tk_info[i].video_info.par >> 16, import.tk_info[i].video_info.par & 0xFFFF); + } + fprintf(stderr, "\n"); } - else if ((import.tk_info[i].type==GF_ISOM_MEDIA_AUDIO) && import.tk_info[i].audio_info.sample_rate) { - fprintf(stderr, "Source: %s - SampleRate %d - %d channels\n", gf_4cc_to_str(import.tk_info[i].media_type), import.tk_info[i].audio_info.sample_rate, import.tk_info[i].audio_info.nb_channels); - } else { - fprintf(stderr, "Source: %s\n", gf_4cc_to_str(import.tk_info[i].media_type)); + else if ((import.tk_info[i].stream_type==GF_STREAM_AUDIO) && import.tk_info[i].audio_info.sample_rate) { + fprintf(stderr, "\tSampleRate %d - %d channels\n", import.tk_info[i].audio_info.sample_rate, import.tk_info[i].audio_info.nb_channels); } - - fprintf(stderr, "\nImport Capabilities:\n"); - if (import.tk_info[i].flags & GF_IMPORT_USE_DATAREF) fprintf(stderr, "\tCan use data referencing\n"); - if (import.tk_info[i].flags & GF_IMPORT_NO_FRAME_DROP) fprintf(stderr, "\tCan use fixed FPS import\n"); - if (import.tk_info[i].flags & GF_IMPORT_FORCE_PACKED) fprintf(stderr, "\tCan force packed bitstream import\n"); - if (import.tk_info[i].flags & GF_IMPORT_OVERRIDE_FPS) fprintf(stderr, "\tCan override source frame rate\n"); - if (import.tk_info[i].flags & (GF_IMPORT_SBR_IMPLICIT|GF_IMPORT_SBR_EXPLICIT)) fprintf(stderr, "\tCan use AAC-SBR signaling\n"); - if (import.tk_info[i].flags & (GF_IMPORT_PS_IMPLICIT|GF_IMPORT_PS_EXPLICIT)) fprintf(stderr, "\tCan use AAC-PS signaling\n"); - if (import.tk_info[i].flags & GF_IMPORT_FORCE_MPEG4) fprintf(stderr, "\tCan force MPEG-4 Systems signaling\n"); - if (import.tk_info[i].flags & GF_IMPORT_3GPP_AGGREGATION) fprintf(stderr, "\tCan use 3GPP frame aggregation\n"); - if (import.tk_info[i].flags & GF_IMPORT_NO_DURATION) fprintf(stderr, "\tCannot use duration-based import\n"); - - found = 1; - break; + if (trackID) { + found = 1; + break; + } } fprintf(stderr, "\n"); + fprintf(stderr, "For more details, use `gpac -i %s inspect[:deep][:analyze]`\n", gf_file_basename(inName)); if (!found && trackID) fprintf(stderr, "Cannot find track %d in file\n", trackID); } @@ -226,8 +214,8 @@ static void set_chapter_track(GF_ISOFile *file, u32 track, u32 chapter_ref_trak) u64 ref_duration, chap_duration; Double scale; - gf_isom_set_track_reference(file, chapter_ref_trak, GF_4CC('c','h','a','p'), gf_isom_get_track_id(file, track) ); - gf_isom_set_track_enabled(file, track, 0); + gf_isom_set_track_reference(file, chapter_ref_trak, GF_ISOM_REF_CHAP, gf_isom_get_track_id(file, track) ); + gf_isom_set_track_enabled(file, track, GF_FALSE); ref_duration = gf_isom_get_media_duration(file, chapter_ref_trak); chap_duration = gf_isom_get_media_duration(file, track); @@ -240,27 +228,96 @@ static void set_chapter_track(GF_ISOFile *file, u32 track, u32 chapter_ref_trak) chap_duration = ref_duration - chap_duration; gf_isom_set_last_sample_duration(file, track, (u32) chap_duration); } +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + u8 NbBits; + u32 switchGroupID, nb_crit, size, reserved; + u8 priority; + Bool discardable; + GF_AudioChannelLayout layout; + gf_isom_get_sample_size(file, track, 1); + gf_isom_get_sample_dts(file, track, 1); + gf_isom_get_sample_from_dts(file, track, 0); + gf_isom_set_sample_padding(file, track, 0); + gf_isom_has_padding_bits(file, track); + gf_isom_get_sample_padding_bits(file, track, 1, &NbBits); + gf_isom_keep_utc_times(file, 1); +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + gf_isom_set_single_moof_mode(file, GF_TRUE); + gf_isom_reset_sample_count(NULL); + gf_isom_set_traf_mss_timeext(NULL, 0, 0, 0); + gf_isom_get_next_moof_number(NULL); + gf_isom_set_fragment_reference_time(NULL, 0, 0, 0); +#endif + //this one is not tested in master due to old-arch compat, to remove when we enable tests without old-arch + gf_isom_get_audio_layout(file, track, 1, &layout); + + gf_isom_get_track_switch_parameter(file, track, 1, &switchGroupID, &nb_crit); + gf_isom_sample_has_subsamples(file, track, 1, 0); + gf_isom_sample_get_subsample(file, track, 1, 0, 1, &size, &priority, &reserved, &discardable); +#ifndef GPAC_DISABLE_ISOM_HINTING + gf_isom_hint_blank_data(NULL, 0, 0); + gf_isom_hint_sample_description_data(NULL, 0, 0, 1, 0, 0, 0); + gf_isom_get_payt_info(NULL, 0, 0, NULL); +#endif + gf_isom_estimate_size(file); + + } +#endif } -GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, Double force_fps, u32 frames_per_sample) +GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, GF_FilterSession *fsess, char **mux_args_if_first_pass, u32 tk_idx) { - u32 track_id, i, j, timescale, track, stype, profile, level, new_timescale, rescale, svc_mode, txt_flags, split_tile_mode, temporal_mode; - s32 par_d, par_n, prog_id, delay; + u32 track_id, i, j, timescale, track, stype, profile, level, new_timescale, rescale_num, rescale_den, svc_mode, txt_flags, split_tile_mode, temporal_mode, nb_tracks; + s32 par_d, par_n, prog_id, delay, force_rate, moov_timescale; s32 tw, th, tx, ty, txtw, txth, txtx, txty; - Bool do_audio, do_video, do_all, disable, track_layout, text_layout, chap_ref, is_chap, is_chap_file, keep_handler, negative_cts_offset, rap_only; + Bool do_audio, do_video, do_auxv,do_pict, do_all, disable, track_layout, text_layout, chap_ref, is_chap, is_chap_file, keep_handler, negative_cts_offset, rap_only, refs_only, force_par, rewrite_bs; u32 group, handler, rvc_predefined, check_track_for_svc, check_track_for_lhvc, check_track_for_hevc; const char *szLan; - GF_Err e; + GF_Err e = GF_OK; + u32 tmcd_track = 0; + Bool keep_audelim = GF_FALSE; + u32 print_stats_graph=fs_dump_flags; GF_MediaImporter import; char *ext, szName[1000], *handler_name, *rvc_config, *chapter_name; GF_List *kinds; GF_TextFlagsMode txt_mode = GF_ISOM_TEXT_FLAGS_OVERWRITE; u8 max_layer_id_plus_one, max_temporal_id_plus_one; + u32 clap_wn, clap_wd, clap_hn, clap_hd, clap_hon, clap_hod, clap_von, clap_vod; + Bool has_clap=GF_FALSE; + Bool use_stz2=GF_FALSE; + Bool has_mx=GF_FALSE; + s32 mx[9]; + u32 bitdepth=0; + u32 dv_profile=0; /*Dolby Vision*/ + u32 clr_type=0; + u32 clr_prim; + u32 clr_tranf; + u32 clr_mx; + u32 clr_full_range=GF_FALSE; + Bool fmt_ok = GF_TRUE; + u32 icc_size=0; + u8 *icc_data = NULL; + u32 tc_fps_num=0, tc_fps_den=0, tc_h=0, tc_m=0, tc_s=0, tc_f=0, tc_frames_per_tick=0; + Bool tc_force_counter=GF_FALSE; + Bool tc_drop_frame = GF_FALSE; + char *ext_start; + u32 xps_inband=0; + u64 source_magic=0; + char *opt_src = NULL; + char *opt_dst = NULL; + char *fchain = NULL; + Bool set_ccst=GF_FALSE; + Bool has_last_sample_dur=GF_FALSE; + GF_Fraction last_sample_dur = {0,0}; + + clap_wn = clap_wd = clap_hn = clap_hd = clap_hon = clap_hod = clap_von = clap_vod = 0; rvc_predefined = 0; chapter_name = NULL; new_timescale = 1; - rescale = 0; + moov_timescale = 0; + rescale_num = rescale_den = 0; text_layout = 0; /*0: merge all 1: split base and all SVC in two tracks @@ -296,38 +353,91 @@ GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, Double forc split_tile_mode = 0; temporal_mode = 0; rap_only = 0; + refs_only = 0; txt_flags = 0; max_layer_id_plus_one = max_temporal_id_plus_one = 0; + force_rate = -1; tw = th = tx = ty = txtw = txth = txtx = txty = 0; - par_d = par_n = -2; - /*use ':' as separator, but beware DOS paths...*/ - ext = strchr(szName, ':'); - if (ext && ext[1]=='\\') ext = strchr(szName+2, ':'); + par_d = par_n = -1; + force_par = rewrite_bs = GF_FALSE; + + ext = gf_url_colon_suffix(szName); handler_name = NULL; rvc_config = NULL; while (ext) { - char *ext2 = strchr(ext+1, ':'); - if (ext2 && !strncmp(ext2, "://", 3)) ext2 = strchr(ext2+1, ':'); - if (ext2 && !strncmp(ext2, ":\\", 2)) ext2 = strchr(ext2+1, ':'); + char *ext2 = gf_url_colon_suffix(ext+1); + if (ext2) ext2[0] = 0; /*all extensions for track-based importing*/ - if (!strnicmp(ext+1, "dur=", 4)) import.duration = (u32) (atof(ext+5) * 1000); - else if (!strnicmp(ext+1, "lang=", 5)) szLan = ext+6; + if (!strnicmp(ext+1, "dur=", 4)) { + s32 dur_n=0, dur_d=0; + if (strchr(ext, '/')) { + sscanf(ext+5, "%d/%d", &dur_n, &dur_d); + } else if (strchr(ext, '-')) { + dur_n = atoi(ext+5); + dur_d = 1; + } else { + //use 1/10 of millisecond precision + dur_n = (u32)( (atof(ext+5) * 10000) + 0.5 ); + dur_d = 10000; + } + import.duration.num = dur_n; + import.duration.den = dur_d; + } + else if (!strnicmp(ext+1, "lang=", 5)) { + /* prevent leak if param is set twice */ + if (szLan) + gf_free((char*) szLan); + + szLan = gf_strdup(ext+6); + } else if (!strnicmp(ext+1, "delay=", 6)) delay = atoi(ext+7); else if (!strnicmp(ext+1, "par=", 4)) { - if (!stricmp(ext+5, "none")) { - par_n = par_d = -1; + if (!stricmp(ext + 5, "none")) { + par_n = par_d = 0; + } else if (!stricmp(ext + 5, "auto")) { + force_par = GF_TRUE; + } else if (!stricmp(ext + 5, "force")) { + par_n = par_d = 1; + force_par = GF_TRUE; } else { - if (ext2) ext2[0] = ':'; - if (ext2) ext2 = strchr(ext2+1, ':'); - if (ext2) ext2[0] = 0; - sscanf(ext+5, "%d:%d", &par_n, &par_d); + if (ext2) { + ext2[0] = ':'; + ext2 = strchr(ext2+1, ':'); + if (ext2) ext2[0] = 0; + } + if (ext[5]=='w') { + rewrite_bs = GF_TRUE; + sscanf(ext+6, "%d:%d", &par_n, &par_d); + } else { + sscanf(ext+5, "%d:%d", &par_n, &par_d); + } + } + } + else if (!strnicmp(ext+1, "clap=", 5)) { + if (!stricmp(ext+6, "none")) { + has_clap=GF_TRUE; + } else { + if (sscanf(ext+6, "%d,%d,%d,%d,%d,%d,%d,%d", &clap_wn, &clap_wd, &clap_hn, &clap_hd, &clap_hon, &clap_hod, &clap_von, &clap_vod)==8) { + has_clap=GF_TRUE; + } } } - else if (!strnicmp(ext+1, "name=", 5)) handler_name = gf_strdup(ext+6); + else if (!strnicmp(ext+1, "mx=", 3)) { + if (strstr(ext+4, "0x")) { + if (sscanf(ext+4, "0x%d:0x%d:0x%d:0x%d:0x%d:0x%d:0x%d:0x%d:0x%d", &mx[0], &mx[1], &mx[2], &mx[3], &mx[4], &mx[5], &mx[6], &mx[7], &mx[8])==9) { + has_mx=GF_TRUE; + } + } else if (sscanf(ext+4, "%d:%d:%d:%d:%d:%d:%d:%d:%d", &mx[0], &mx[1], &mx[2], &mx[3], &mx[4], &mx[5], &mx[6], &mx[7], &mx[8])==9) { + has_mx=GF_TRUE; + } + } + else if (!strnicmp(ext+1, "name=", 5)) { + handler_name = gf_strdup(ext+6); + } else if (!strnicmp(ext+1, "ext=", 4)) { /*extensions begin with '.'*/ if (*(ext+5) == '.') @@ -345,20 +455,24 @@ GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, Double forc if (!group) group = gf_isom_get_next_alternate_group_id(dest); } else if (!strnicmp(ext+1, "fps=", 4)) { - if (!strcmp(ext+5, "auto")) force_fps = GF_IMPORT_AUTO_FPS; - else if (strchr(ext+5, '-')) { - u32 ticks, dts_inc; - sscanf(ext+5, "%u-%u", &ticks, &dts_inc); + u32 ticks, dts_inc; + if (!strcmp(ext+5, "auto")) { + fprintf(stderr, "Warning, fps=auto option is deprecated\n"); + } else if ((sscanf(ext+5, "%u-%u", &ticks, &dts_inc) == 2) || (sscanf(ext+5, "%u/%u", &ticks, &dts_inc) == 2)) { if (!dts_inc) dts_inc=1; - force_fps = ticks; - force_fps /= dts_inc; + force_fps.num = ticks; + force_fps.den = dts_inc; + } else { + force_fps.den = 1000; + force_fps.num = (u32) (atof(ext+5) * force_fps.den); } - else force_fps = atof(ext+5); } else if (!stricmp(ext+1, "rap")) rap_only = 1; + else if (!stricmp(ext+1, "refs")) refs_only = 1; else if (!stricmp(ext+1, "trailing")) import_flags |= GF_IMPORT_KEEP_TRAILING; else if (!strnicmp(ext+1, "agg=", 4)) frames_per_sample = atoi(ext+5); else if (!stricmp(ext+1, "dref")) import_flags |= GF_IMPORT_USE_DATAREF; + else if (!stricmp(ext+1, "keep_refs")) import_flags |= GF_IMPORT_KEEP_REFS; else if (!stricmp(ext+1, "nodrop")) import_flags |= GF_IMPORT_NO_FRAME_DROP; else if (!stricmp(ext+1, "packed")) import_flags |= GF_IMPORT_FORCE_PACKED; else if (!stricmp(ext+1, "sbr")) import_flags |= GF_IMPORT_SBR_IMPLICIT; @@ -378,14 +492,16 @@ GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, Double forc if (!stricmp(mode, "splitnox")) svc_mode = 3; + else if (!stricmp(mode, "splitnoxib")) + svc_mode = 4; else if (!stricmp(mode, "splitall") || !stricmp(mode, "split")) svc_mode = 2; else if (!stricmp(mode, "splitbase")) svc_mode = 1; - else if (!stricmp(mode, "merged")) + else if (!stricmp(mode, "merged") || !stricmp(mode, "merge")) svc_mode = 0; } - /*split SVC layers*/ + /*split SHVC temporal sublayers*/ else if (!strnicmp(ext+1, "temporal=", 9)) { char *mode = ext+10; if (!stricmp(mode, "split")) @@ -400,8 +516,13 @@ GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, Double forc } } else if (!stricmp(ext+1, "subsamples")) import_flags |= GF_IMPORT_SET_SUBSAMPLES; + else if (!stricmp(ext+1, "deps")) import_flags |= GF_IMPORT_SAMPLE_DEPS; + else if (!stricmp(ext+1, "ccst")) set_ccst = GF_TRUE; + else if (!stricmp(ext+1, "alpha")) import.is_alpha = GF_TRUE; else if (!stricmp(ext+1, "forcesync")) import_flags |= GF_IMPORT_FORCE_SYNC; - else if (!stricmp(ext+1, "xps_inband")) import_flags |= GF_IMPORT_FORCE_XPS_INBAND; + else if (!stricmp(ext+1, "xps_inband")) xps_inband = 1; + else if (!stricmp(ext+1, "xps_inbandx")) xps_inband = 2; + else if (!stricmp(ext+1, "au_delim")) keep_audelim = GF_TRUE; else if (!strnicmp(ext+1, "max_lid=", 8) || !strnicmp(ext+1, "max_tid=", 8)) { s32 val = atoi(ext+9); if (val < 0) { @@ -409,7 +530,7 @@ GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, Double forc } else { if (!strnicmp(ext+1, "max_lid=", 8)) max_layer_id_plus_one = 1 + (u8) val; - else + else max_temporal_id_plus_one = 1 + (u8) val; } } @@ -423,7 +544,9 @@ GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, Double forc stype = GF_4CC(ext[7], ext[8], ext[9], ext[10]); } else if (!stricmp(ext+1, "chap")) is_chap = 1; - else if (!strnicmp(ext+1, "chapter=", 8)) chapter_name = gf_strdup(ext+9); + else if (!strnicmp(ext+1, "chapter=", 8)) { + chapter_name = gf_strdup(ext+9); + } else if (!strnicmp(ext+1, "chapfile=", 9)) { chapter_name = gf_strdup(ext+10); is_chap_file=1; @@ -437,11 +560,18 @@ GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, Double forc } } else if (!strnicmp(ext+1, "rescale=", 8)) { - rescale = atoi(ext+9); + if (sscanf(ext+9, "%d/%d", &rescale_num, &rescale_den) != 2) { + rescale_num = atoi(ext+9); + rescale_den = 0; + } } else if (!strnicmp(ext+1, "timescale=", 10)) { new_timescale = atoi(ext+11); } + else if (!strnicmp(ext+1, "moovts=", 7)) { + moov_timescale = atoi(ext+8); + } + else if (!stricmp(ext+1, "noedit")) import_flags |= GF_IMPORT_NO_EDIT_LIST; @@ -454,6 +584,7 @@ GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, Double forc else if (!strnicmp(ext+1, "profile=", 8)) profile = atoi(ext+9); else if (!strnicmp(ext+1, "level=", 6)) level = atoi(ext+7); else if (!strnicmp(ext+1, "novpsext", 8)) import_flags |= GF_IMPORT_NO_VPS_EXTENSIONS; + else if (!strnicmp(ext+1, "keepav1t", 8)) import_flags |= GF_IMPORT_KEEP_AV1_TEMPORAL_OBU; else if (!strnicmp(ext+1, "font=", 5)) import.fontName = gf_strdup(ext+6); else if (!strnicmp(ext+1, "size=", 5)) import.fontSize = atoi(ext+6); @@ -479,7 +610,7 @@ GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, Double forc else if (!stricmp(ext+1, "swf-same-app")) import.swf_flags |= GF_SM_SWF_REUSE_APPEARANCE; else if (!strnicmp(ext+1, "swf-flatten=", 12)) import.swf_flatten_angle = (Float) atof(ext+13); #endif - + else if (!strnicmp(ext+1, "kind=", 5)) { char *kind_scheme, *kind_value; char *kind_data = ext+6; @@ -510,10 +641,142 @@ GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, Double forc txt_mode = GF_ISOM_TEXT_FLAGS_UNTOGGLE; } } + else if (!strnicmp(ext+1, "rate=", 5)) { + force_rate = atoi(ext+6); + } + else if (!stricmp(ext+1, "fstat")) + print_stats_graph |= 1; + else if (!stricmp(ext+1, "fgraph")) + print_stats_graph |= 2; + else if (!strncmp(ext+1, "sopt", 4) || !strncmp(ext+1, "dopt", 4) || !strncmp(ext+1, "@@", 2)) { + if (ext2) ext2[0] = ':'; + opt_src = strstr(ext, ":sopt:"); + opt_dst = strstr(ext, ":dopt:"); + fchain = strstr(ext, ":@@"); + if (opt_src) opt_src[0] = 0; + if (opt_dst) opt_dst[0] = 0; + if (fchain) fchain[0] = 0; + + if (opt_src) import.filter_src_opts = opt_src+6; + if (opt_dst) import.filter_dst_opts = opt_dst+6; + if (fchain) import.filter_chain = fchain+3; + ext = NULL; + break; + } + + else if (!strnicmp(ext+1, "asemode=", 8)){ + char *mode = ext+9; + if (!stricmp(mode, "v0-bs")) + import.asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_BS; + else if (!stricmp(mode, "v0-2")) + import.asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_2; + else if (!stricmp(mode, "v1")) + import.asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_MPEG; + else if (!stricmp(mode, "v1-qt")) + import.asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_QTFF; + else + fprintf(stderr, "Unrecognized audio sample entry mode %s, ignoring\n", mode); + } + + else if (!strnicmp(ext+1, "audio_roll=", 11)) { + import.audio_roll_change = GF_TRUE; + import.audio_roll = atoi(ext+12); + } + else if (!strcmp(ext+1, "stz2")) { + use_stz2 = GF_TRUE; + } else if (!strnicmp(ext+1, "bitdepth=", 9)) { + bitdepth=atoi(ext+10); + } + else if (!strnicmp(ext+1, "colr=", 5)) { + char *cval = ext+6; + if (strlen(cval)<6) { + fmt_ok = GF_FALSE; + } else { + clr_type = GF_4CC(cval[0],cval[1],cval[2],cval[3]); + cval+=4; + if (cval[0] != ',') { + fmt_ok = GF_FALSE; + } + else if (clr_type==GF_ISOM_SUBTYPE_NCLX) { + if (sscanf(cval+1, "%d,%d,%d,%d", &clr_prim, &clr_tranf, &clr_mx, &clr_full_range) != 4) + fmt_ok=GF_FALSE; + } + else if (clr_type==GF_ISOM_SUBTYPE_NCLC) { + if (sscanf(cval+1, "%d,%d,%d", &clr_prim, &clr_tranf, &clr_mx) != 3) + fmt_ok=GF_FALSE; + } + else if ((clr_type==GF_ISOM_SUBTYPE_RICC) || (clr_type==GF_ISOM_SUBTYPE_PROF)) { + FILE *f = gf_fopen(cval+1, "rb"); + if (!f) { + fprintf(stderr, "Failed to open file %s\n", cval+1); + fmt_ok = GF_FALSE; + } else { + gf_fseek(f, 0, SEEK_END); + icc_size = (u32) gf_ftell(f); + icc_data = gf_malloc(sizeof(char)*icc_size); + gf_fseek(f, 0, SEEK_SET); + icc_size = (u32) gf_fread(icc_data, icc_size, f); + gf_fclose(f); + } + } else { + fprintf(stderr, "unrecognized profile %s\n", gf_4cc_to_str(clr_type) ); + fmt_ok = GF_FALSE; + } + } + if (!fmt_ok) { + fprintf(stderr, "Bad format for clr option, check help\n"); + e = GF_BAD_PARAM; + goto exit; + } + } + else if (!strnicmp(ext + 1, "dv-profile=", 11)) { + dv_profile = atoi(ext + 12); + } + else if (!strnicmp(ext+1, "tc=", 3)) { + char *tc_str = ext+4; + if (tc_str[0] == 'd') { + tc_drop_frame=GF_TRUE; + tc_str+=1; + } + if (sscanf(tc_str, "%d/%d,%d,%d,%d,%d,%d", &tc_fps_num, &tc_fps_den, &tc_h, &tc_m, &tc_s, &tc_f, &tc_frames_per_tick) == 7) { + } else if (sscanf(tc_str, "%d/%d,%d,%d,%d,%d", &tc_fps_num, &tc_fps_den, &tc_h, &tc_m, &tc_s, &tc_f) == 6) { + } else if (sscanf(tc_str, "%d,%d,%d,%d,%d,%d", &tc_fps_num, &tc_h, &tc_m, &tc_s, &tc_f, &tc_frames_per_tick) == 6) { + tc_fps_den = 1; + } else if (sscanf(tc_str, "%d,%d,%d,%d,%d", &tc_fps_num, &tc_h, &tc_m, &tc_s, &tc_f) == 5) { + tc_fps_den = 1; + } else if (sscanf(tc_str, "%d/%d,%d,%d", &tc_fps_num, &tc_fps_den, &tc_f, &tc_frames_per_tick) == 4) { + tc_force_counter = GF_TRUE; + tc_h = tc_m = tc_s = 0; + } else if (sscanf(tc_str, "%d/%d,%d", &tc_fps_num, &tc_fps_den, &tc_f) == 3) { + tc_force_counter = GF_TRUE; + tc_h = tc_m = tc_s = 0; + } else if (sscanf(tc_str, "%d,%d,%d", &tc_fps_num, &tc_f, &tc_frames_per_tick) == 3) { + tc_force_counter = GF_TRUE; + tc_h = tc_m = tc_s = 0; + tc_fps_den = 1; + } else if (sscanf(tc_str, "%d,%d", &tc_fps_num, &tc_f) == 2) { + tc_force_counter = GF_TRUE; + tc_h = tc_m = tc_s = 0; + tc_fps_den = 1; + } else { + fprintf(stderr, "Bad format %s for timecode, ignoring\n", ext+1); + } + } + else if (!strnicmp(ext+1, "lastsampdur", 11)) { + has_last_sample_dur = GF_TRUE; + if (!strnicmp(ext+1, "lastsampdur=", 12)) { + if (sscanf(ext+13, "%d/%u", &last_sample_dur.num, &last_sample_dur.den)==2) { + } else { + last_sample_dur.num = atoi(ext+13); + last_sample_dur.den = 1000; + } + } + } /*unrecognized, assume name has colon in it*/ else { fprintf(stderr, "Unrecognized import option %s, ignoring\n", ext+1); + if (ext2) ext2[0] = ':'; ext = ext2; continue; } @@ -521,34 +784,45 @@ GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, Double forc if (ext2) ext2[0] = ':'; ext[0] = 0; - ext = strchr(ext+1, ':'); + + /* restart from where we stopped + * if we didn't stop (ext2 null) then the end has been reached + * so we can stop the whole thing */ + ext = ext2; } /*check duration import (old syntax)*/ ext = strrchr(szName, '%'); if (ext) { - import.duration = (u32) (atof(ext+1) * 1000); + import.duration.num = (u32) (atof(ext+1) * 1000000); + import.duration.den = 1000000; ext[0] = 0; } /*select switches for av containers import*/ - do_audio = do_video = 0; + do_audio = do_video = do_auxv = do_pict = 0; track_id = prog_id = 0; do_all = 1; - ext = strrchr(szName, '#'); - if (ext) ext[0] = 0; - - keep_handler = gf_isom_probe_file(szName); - import.in_name = szName; - import.flags = GF_IMPORT_PROBE_ONLY; - e = gf_media_import(&import); - if (e) goto exit; + ext_start = gf_file_ext_start(szName); + ext = strrchr(ext_start ? ext_start : szName, '#'); + if (ext) ext[0] = 0; if (ext) { ext++; + char *sep = gf_url_colon_suffix(ext); + if (sep) sep[0] = 0; + + //we have a fragment, we need to check if the track or the program is present in source + import.in_name = szName; + import.flags = GF_IMPORT_PROBE_ONLY; + e = gf_media_import(&import); + if (e) goto exit; + if (!strnicmp(ext, "audio", 5)) do_audio = 1; else if (!strnicmp(ext, "video", 5)) do_video = 1; + else if (!strnicmp(ext, "auxv", 4)) do_auxv = 1; + else if (!strnicmp(ext, "pict", 4)) do_pict = 1; else if (!strnicmp(ext, "trackID=", 8)) track_id = atoi(&ext[8]); else if (!strnicmp(ext, "PID=", 4)) track_id = atoi(&ext[4]); else if (!strnicmp(ext, "program=", 8)) { @@ -565,17 +839,53 @@ GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, Double forc do_all = 0; } else track_id = atoi(ext); + + //figure out trackID + if (do_audio || do_video || do_auxv || do_pict || track_id) { + Bool found = track_id ? GF_FALSE : GF_TRUE; + for (i=0; i0) { - gf_isom_append_edit_segment(import.dest, i+1, (timescale*delay)/1000, 0, GF_ISOM_EDIT_EMPTY); - gf_isom_append_edit_segment(import.dest, i+1, tk_dur, 0, GF_ISOM_EDIT_NORMAL); - } else if (delay<0) { - u64 to_skip = (timescale*(-delay))/1000; - if (to_skip=0) && (par_d>=0)) { - e = gf_media_change_par(import.dest, i+1, par_n, par_d); - } + if (e) { + if (import.update_mux_args) gf_free(import.update_mux_args); + goto exit; + } - if (rap_only) { - e = gf_media_remove_non_rap(import.dest, i+1); - } + if (fsess) { + *mux_args_if_first_pass = import.update_mux_args; + import.update_mux_args = NULL; + goto exit; + } + } - if (handler_name) gf_isom_set_handler_name(import.dest, i+1, handler_name); - else if (!keep_handler) { - char szHName[1024]; - const char *fName = gf_url_get_resource_name((const char *)inName); - fName = strchr(fName, '.'); - if (fName) fName += 1; - else fName = "?"; + nb_tracks = gf_isom_get_track_count(dest); + for (i=0; i>=32; + + keep_handler = (tk_source_magic & 1) ? GF_TRUE : GF_FALSE; - if (group) { - gf_isom_set_alternate_group_id(import.dest, i+1, group); + media_type = gf_isom_get_media_type(dest, track); + + if (moov_timescale) { + if (moov_timescale<0) moov_timescale = gf_isom_get_media_timescale(dest, track); + gf_isom_set_timescale(dest, moov_timescale); + moov_timescale = 0; + } + + timescale = gf_isom_get_timescale(dest); + if (szLan) gf_isom_set_media_language(dest, track, (char *) szLan); + if (disable) gf_isom_set_track_enabled(dest, track, GF_FALSE); + + if (import_flags & GF_IMPORT_NO_EDIT_LIST) + gf_isom_remove_edits(dest, track); + + if (delay) { + u64 tk_dur; + gf_isom_remove_edits(dest, track); + tk_dur = gf_isom_get_track_duration(dest, track); + if (delay>0) { + gf_isom_append_edit(dest, track, (timescale*delay)/1000, 0, GF_ISOM_EDIT_EMPTY); + gf_isom_append_edit(dest, track, tk_dur, 0, GF_ISOM_EDIT_NORMAL); + } else { + u64 to_skip = (timescale*(-delay))/1000; + if (to_skip=0) && (par_d>=0)) || force_par) { + e = gf_media_change_par(dest, track, par_n, par_d, force_par, rewrite_bs); } - if (stype) - gf_isom_set_media_subtype(import.dest, i+1, 1, stype); - - if (is_chap && chap_ref) { - set_chapter_track(import.dest, i+1, chap_ref); + if (has_clap) { + e = gf_isom_set_clean_aperture(dest, track, 1, clap_wn, clap_wd, clap_hn, clap_hd, clap_hon, clap_hod, clap_von, clap_vod); } - - for (j = 0; j < gf_list_count(kinds); j+=2) { - char *kind_scheme = (char *)gf_list_get(kinds, j); - char *kind_value = (char *)gf_list_get(kinds, j+1); - gf_isom_add_track_kind(import.dest, i+1, kind_scheme, kind_value); + if (bitdepth) { + gf_isom_set_visual_bit_depth(dest, track, 1, bitdepth); } - - if (profile || level) - gf_media_change_pl(import.dest, i+1, profile, level); - - if (gf_isom_get_media_subtype(import.dest, i+1, 1)== GF_4CC( 'm', 'p', '4', 's' )) - keep_sys_tracks = 1; - - gf_isom_set_composition_offset_mode(import.dest, i+1, negative_cts_offset); - - if (gf_isom_get_avc_svc_type(import.dest, i+1, 1)>=GF_ISOM_AVCTYPE_AVC_SVC) - check_track_for_svc = i+1; - - switch (gf_isom_get_hevc_lhvc_type(import.dest, i+1, 1)) { - case GF_ISOM_HEVCTYPE_HEVC_LHVC: - case GF_ISOM_HEVCTYPE_LHVC_ONLY: - check_track_for_lhvc = i+1; - break; - case GF_ISOM_HEVCTYPE_HEVC_ONLY: - check_track_for_hevc=1; - break; + if (clr_type) { + gf_isom_set_visual_color_info(dest, track, 1, clr_type, clr_prim, clr_tranf, clr_mx, clr_full_range, icc_data, icc_size); } - - if (txt_flags) { - gf_isom_text_set_display_flags(import.dest, i+1, 0, txt_flags, txt_mode); + if (dv_profile) { + gf_isom_set_dolby_vision_profile(dest, track, 1, dv_profile); } - - if (split_tile_mode) { - switch (gf_isom_get_media_subtype(import.dest, i+1, 1)) { - case GF_ISOM_SUBTYPE_HVC1: - case GF_ISOM_SUBTYPE_HEV1: - case GF_ISOM_SUBTYPE_HVC2: - case GF_ISOM_SUBTYPE_HEV2: - break; - default: - split_tile_mode = 0; - break; - } + if (set_ccst) { + gf_isom_set_image_sequence_coding_constraints(dest, track, 1, GF_FALSE, GF_FALSE, GF_TRUE, 15); } } - } else { - for (i=0; i0) { - gf_isom_append_edit_segment(import.dest, track, (timescale*delay)/1000, 0, GF_ISOM_EDIT_EMPTY); - gf_isom_append_edit_segment(import.dest, track, tk_dur, 0, GF_ISOM_EDIT_NORMAL); - } else { - u64 to_skip = (timescale*(-delay))/1000; - if (to_skip=-1) && (par_d>=-1)) { - e = gf_media_change_par(import.dest, track, par_n, par_d); - } - if (rap_only) { - e = gf_media_remove_non_rap(import.dest, track); - } - if (handler_name) gf_isom_set_handler_name(import.dest, track, handler_name); - else if (!keep_handler) { - char szHName[1024]; - const char *fName = gf_url_get_resource_name((const char *)inName); - fName = strchr(fName, '.'); - if (fName) fName += 1; - else fName = "?"; + sprintf(szHName, "%s@GPAC%s", fName, gf_gpac_version()); + gf_isom_set_handler_name(dest, track, szHName); + } + if (handler) gf_isom_set_media_type(dest, track, handler); - sprintf(szHName, "%s@GPAC%s", fName, GPAC_FULL_VERSION); - gf_isom_set_handler_name(import.dest, track, szHName); - } - if (handler) gf_isom_set_media_type(import.dest, track, handler); + if (group) { + gf_isom_set_alternate_group_id(dest, track, group); + } - if (group) { - gf_isom_set_alternate_group_id(import.dest, track, group); - } + if (track_layout) { + gf_isom_set_track_layout_info(dest, track, tw<<16, th<<16, tx<<16, ty<<16, 0); + } + if (stype) + gf_isom_set_media_subtype(dest, track, 1, stype); - if (track_layout) { - gf_isom_set_track_layout_info(import.dest, track, tw<<16, th<<16, tx<<16, ty<<16, 0); - } - if (stype) - gf_isom_set_media_subtype(import.dest, track, 1, stype); + if (is_chap && chap_ref) { + set_chapter_track(dest, track, chap_ref); + } - if (is_chap && chap_ref) { - set_chapter_track(import.dest, track, chap_ref); - } - for (j = 0; j < gf_list_count(kinds); j+=2) { - char *kind_scheme = (char *)gf_list_get(kinds, j); - char *kind_value = (char *)gf_list_get(kinds, j+1); - gf_isom_add_track_kind(import.dest, i+1, kind_scheme, kind_value); - } + for (j = 0; j < gf_list_count(kinds); j+=2) { + char *kind_scheme = (char *)gf_list_get(kinds, j); + char *kind_value = (char *)gf_list_get(kinds, j+1); + gf_isom_add_track_kind(dest, i+1, kind_scheme, kind_value); + } - if (profile || level) - gf_media_change_pl(import.dest, track, profile, level); + if (profile || level) + gf_media_change_pl(dest, track, profile, level); - if (gf_isom_get_mpeg4_subtype(import.dest, track, 1)) - keep_sys_tracks = 1; + if (gf_isom_get_mpeg4_subtype(dest, track, 1)) + keep_sys_tracks = 1; - if (new_timescale>1) { - gf_isom_set_media_timescale(import.dest, track, new_timescale, 0); - } + if (new_timescale>1) { + gf_isom_set_media_timescale(dest, track, new_timescale, 0, 0); + } - if (rescale>1) { - switch (gf_isom_get_media_type(import.dest, track)) { - case GF_ISOM_MEDIA_AUDIO: - fprintf(stderr, "Cannot force media timescale for audio media types - ignoring\n"); - break; - default: - gf_isom_set_media_timescale(import.dest, track, rescale, 1); - break; + if (rescale_num > 1) { + switch (gf_isom_get_media_type(dest, track)) { + case GF_ISOM_MEDIA_AUDIO: + fprintf(stderr, "Cannot force media timescale for audio media types - ignoring\n"); + break; + default: + e = gf_isom_set_media_timescale(dest, track, rescale_num, rescale_den, 1); + if (e==GF_EOS) { + fprintf(stderr, "Rescale ignored, same config in source file\n"); + e = GF_OK; + } else if (e) { + fprintf(stderr, "Error rescaling media track %d\n", track); + goto exit; } + break; } + } - if (rvc_config) { - FILE *f = gf_fopen(rvc_config, "rb"); - if (f) { - char *data; - u32 size; - size_t read; - gf_fseek(f, 0, SEEK_END); - size = (u32) gf_ftell(f); - gf_fseek(f, 0, SEEK_SET); - data = gf_malloc(sizeof(char)*size); - read = fread(data, 1, size, f); - gf_fclose(f); - if (read != size) { - fprintf(stderr, "Error: could not read rvc config from %s\n", rvc_config); - e = GF_IO_ERR; - goto exit; - } + if (has_last_sample_dur) { + gf_isom_set_last_sample_duration_ex(dest, track, last_sample_dur.num, last_sample_dur.den); + } + if (rvc_config) { + u8 *data; + u32 size; + e = gf_file_load_data(rvc_config, (u8 **) &data, &size); + if (e) { + fprintf(stderr, "Error: failed to load rvc config from file: %s\n", gf_error_to_string(e) ); + } else { #ifdef GPAC_DISABLE_ZLIB - fprintf(stderr, "Error: no zlib support - RVC not available\n"); - e = GF_NOT_SUPPORTED; - goto exit; + fprintf(stderr, "Error: no zlib support - RVC not available\n"); + e = GF_NOT_SUPPORTED; + gf_free(data); + goto exit; #else - gf_gz_compress_payload(&data, size, &size); + gf_gz_compress_payload(&data, size, &size); #endif - gf_isom_set_rvc_config(import.dest, track, 1, 0, "application/rvc-config+xml+gz", data, size); - gf_free(data); - } - } else if (rvc_predefined>0) { - gf_isom_set_rvc_config(import.dest, track, 1, rvc_predefined, NULL, NULL, 0); + gf_isom_set_rvc_config(dest, track, 1, 0, "application/rvc-config+xml+gz", data, size); + gf_free(data); } + } else if (rvc_predefined>0) { + gf_isom_set_rvc_config(dest, track, 1, rvc_predefined, NULL, NULL, 0); + } - gf_isom_set_composition_offset_mode(import.dest, track, negative_cts_offset); + gf_isom_set_composition_offset_mode(dest, track, negative_cts_offset); - if (gf_isom_get_avc_svc_type(import.dest, track, 1)>=GF_ISOM_AVCTYPE_AVC_SVC) - check_track_for_svc = track; + if (gf_isom_get_avc_svc_type(dest, track, 1)>=GF_ISOM_AVCTYPE_AVC_SVC) + check_track_for_svc = track; - switch (gf_isom_get_hevc_lhvc_type(import.dest, track, 1)) { - case GF_ISOM_HEVCTYPE_HEVC_LHVC: - case GF_ISOM_HEVCTYPE_LHVC_ONLY: - check_track_for_lhvc = i+1; - break; - case GF_ISOM_HEVCTYPE_HEVC_ONLY: - check_track_for_hevc=1; - break; - } + switch (gf_isom_get_hevc_lhvc_type(dest, track, 1)) { + case GF_ISOM_HEVCTYPE_HEVC_LHVC: + case GF_ISOM_HEVCTYPE_LHVC_ONLY: + check_track_for_lhvc = i+1; + break; + case GF_ISOM_HEVCTYPE_HEVC_ONLY: + check_track_for_hevc=1; + break; + default: + break; + } - if (txt_flags) { - gf_isom_text_set_display_flags(import.dest, track, 0, txt_flags, txt_mode); - } + if (txt_flags) { + gf_isom_text_set_display_flags(dest, track, 0, txt_flags, txt_mode); + } + if (force_rate>=0) { + gf_isom_update_bitrate(dest, i+1, 1, force_rate, force_rate, 0); + } - if (split_tile_mode) { - switch (gf_isom_get_media_subtype(import.dest, track, 1)) { - case GF_ISOM_SUBTYPE_HVC1: - case GF_ISOM_SUBTYPE_HEV1: - case GF_ISOM_SUBTYPE_HVC2: - case GF_ISOM_SUBTYPE_HEV2: - break; - default: - split_tile_mode = 0; - break; - } + if (split_tile_mode) { + switch (gf_isom_get_media_subtype(dest, track, 1)) { + case GF_ISOM_SUBTYPE_HVC1: + case GF_ISOM_SUBTYPE_HEV1: + case GF_ISOM_SUBTYPE_HVC2: + case GF_ISOM_SUBTYPE_HEV2: + break; + default: + split_tile_mode = 0; + break; } } - if (track_id) fprintf(stderr, "WARNING: Track ID %d not found in file\n", track_id); - else if (do_video) fprintf(stderr, "WARNING: Video track not found\n"); - else if (do_audio) fprintf(stderr, "WARNING: Audio track not found\n"); } if (chapter_name) { if (is_chap_file) { - e = gf_media_import_chapters(import.dest, chapter_name, 0); + GF_Fraction a_fps = {0,0}; + e = gf_media_import_chapters(dest, chapter_name, a_fps, GF_FALSE); } else { - e = gf_isom_add_chapter(import.dest, 0, 0, chapter_name); + e = gf_isom_add_chapter(dest, 0, 0, chapter_name); + } + } + + if (tmcd_track) { + u32 tmcd_id = gf_isom_get_track_id(dest, tmcd_track); + for (i=0; i < gf_isom_get_track_count(dest); i++) { + switch (gf_isom_get_media_type(dest, i+1)) { + case GF_ISOM_MEDIA_VISUAL: + case GF_ISOM_MEDIA_AUXV: + case GF_ISOM_MEDIA_PICT: + break; + default: + continue; + } + gf_isom_set_track_reference(dest, i+1, GF_ISOM_REF_TMCD, tmcd_id); } } /*force to rewrite all dependencies*/ - for (i = 1; i <= gf_isom_get_track_count(import.dest); i++) + for (i = 1; i <= gf_isom_get_track_count(dest); i++) { - e = gf_isom_rewrite_track_dependencies(import.dest, i); + e = gf_isom_rewrite_track_dependencies(dest, i); if (e) { - fprintf(stderr, "Warning: track ID %d has references to a track not imported\n", gf_isom_get_track_id(import.dest, i)); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Warning: track ID %d has references to a track not imported\n", gf_isom_get_track_id(dest, i) )); e = GF_OK; } } +#ifndef GPAC_DISABLE_AV_PARSERS if (max_layer_id_plus_one || max_temporal_id_plus_one) { - for (i = 1; i <= gf_isom_get_track_count(import.dest); i++) + for (i = 1; i <= gf_isom_get_track_count(dest); i++) { - e = gf_media_filter_hevc(import.dest, i, max_temporal_id_plus_one, max_layer_id_plus_one); + e = gf_media_filter_hevc(dest, i, max_temporal_id_plus_one, max_layer_id_plus_one); if (e) { - fprintf(stderr, "Warning: track ID %d: error while filtering LHVC layers\n", gf_isom_get_track_id(import.dest, i)); + fprintf(stderr, "Warning: track ID %d: error while filtering LHVC layers\n", gf_isom_get_track_id(dest, i)); e = GF_OK; } } } +#endif if (check_track_for_svc) { if (svc_mode) { - e = gf_media_split_svc(import.dest, check_track_for_svc, (svc_mode==2) ? 1 : 0); + e = gf_media_split_svc(dest, check_track_for_svc, (svc_mode==2) ? 1 : 0); if (e) goto exit; } else { - e = gf_media_merge_svc(import.dest, check_track_for_svc, 1); + e = gf_media_merge_svc(dest, check_track_for_svc, 1); if (e) goto exit; } } -#ifndef GPAC_DISABLE_HEVC +#ifndef GPAC_DISABLE_AV_PARSERS if (check_track_for_lhvc) { if (svc_mode) { - e = gf_media_split_lhvc(import.dest, check_track_for_lhvc, GF_FALSE, (svc_mode==1) ? 0 : 1, (svc_mode==3) ? 0 : 1 ); + GF_LHVCExtractoreMode xmode = GF_LHVC_EXTRACTORS_ON; + if (svc_mode==3) xmode = GF_LHVC_EXTRACTORS_OFF; + else if (svc_mode==4) xmode = GF_LHVC_EXTRACTORS_OFF_FORCE_INBAND; + e = gf_media_split_lhvc(dest, check_track_for_lhvc, GF_FALSE, (svc_mode==1) ? 0 : 1, xmode ); if (e) goto exit; } else { //TODO - merge, temporal sublayers } } +#ifndef GPAC_DISABLE_HEVC if (check_track_for_hevc) { if (split_tile_mode) { - e = gf_media_split_hevc_tiles(import.dest, split_tile_mode - 1); + e = gf_media_split_hevc_tiles(dest, split_tile_mode - 1); if (e) goto exit; } if (temporal_mode) { - e = gf_media_split_lhvc(import.dest, check_track_for_hevc, GF_TRUE, (temporal_mode==1) ? GF_FALSE : GF_TRUE, (temporal_mode==3) ? GF_FALSE : GF_TRUE ); + GF_LHVCExtractoreMode xmode = (temporal_mode==3) ? GF_LHVC_EXTRACTORS_OFF : GF_LHVC_EXTRACTORS_ON; + e = gf_media_split_lhvc(dest, check_track_for_hevc, GF_TRUE, (temporal_mode==1) ? GF_FALSE : GF_TRUE, xmode ); if (e) goto exit; } } +#endif -#endif /*GPAC_DISABLE_HEVC*/ + if (tc_fps_num) { + u32 desc_index=0; + u32 tmcd_tk, tmcd_id; + u32 video_ref = 0; + GF_BitStream *bs; + GF_ISOSample *samp; + for (i=0; iIsRAP = SAP_TYPE_1; + gf_bs_get_content(bs, &samp->data, &samp->dataLength); + gf_bs_del(bs); + e = gf_isom_add_sample(dest, tmcd_tk, desc_index, samp); + gf_isom_sample_del(&samp); + + if (video_ref) { + u64 video_ref_dur = gf_isom_get_media_duration(dest, video_ref); + video_ref_dur *= tc_fps_num; + video_ref_dur /= gf_isom_get_media_timescale(dest, video_ref); + gf_isom_set_last_sample_duration(dest, tmcd_tk, (u32) video_ref_dur); + } else { + gf_isom_set_last_sample_duration(dest, tmcd_tk, tc_fps_den ? tc_fps_den : 1); + } + } + +#endif /*GPAC_DISABLE_AV_PARSERS*/ exit: while (gf_list_count(kinds)) { @@ -960,6 +1306,10 @@ exit: gf_list_rem(kinds, 0); if (kind) gf_free(kind); } + if (opt_src) opt_src[0] = ':'; + if (opt_dst) opt_dst[0] = ':'; + if (fchain) fchain[0] = ':'; + gf_list_del(kinds); if (handler_name) gf_free(handler_name); if (chapter_name ) gf_free(chapter_name); @@ -967,657 +1317,186 @@ exit: if (import.streamFormat) gf_free(import.streamFormat); if (import.force_ext) gf_free(import.force_ext); if (rvc_config) gf_free(rvc_config); + if (szLan) gf_free((char *)szLan); + if (icc_data) gf_free(icc_data); return e; } -typedef struct + +static Bool on_split_event(void *_udta, GF_Event *evt) { - u32 tk; - Bool has_non_raps; - u32 last_sample; - u32 sample_count; - u32 time_scale; - u64 firstDTS, lastDTS; - u32 dst_tk; - /*set if media can be duplicated at split boundaries - only used for text tracks and provate tracks, this assumes all - samples are RAP*/ - Bool can_duplicate; - /*controls import by time rather than by sample (otherwise we would have to remove much more samples video vs audio for example*/ - Bool first_sample_done; - Bool next_sample_is_rap; - u32 stop_state; -} TKInfo; - -GF_Err split_isomedia_file(GF_ISOFile *mp4, Double split_dur, u64 split_size_kb, char *inName, Double InterleavingTime, Double chunk_start_time, Bool adjust_split_end, char *outName, const char *tmpdir) + Double progress; + u32 *prev_progress = (u32 *)_udta; + if (!_udta) return GF_FALSE; + if (evt->type != GF_EVENT_PROGRESS) return GF_FALSE; + if (!evt->progress.total) return GF_FALSE; + + progress = (Double) (100*evt->progress.done) / evt->progress.total; + if ((u32) progress==*prev_progress) + return GF_FALSE; + + *prev_progress = (u32) progress; +#ifndef GPAC_DISABLE_LOG + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Spliting: % 2.2f %%\r", progress)); +#else + fprintf(stderr, "Spliting: % 2.2f %%\r", progress); +#endif + return GF_FALSE; +} + +GF_Err split_isomedia_file(GF_ISOFile *mp4, Double split_dur, u64 split_size_kb, char *inName, Double InterleavingTime, Double chunk_start_time, Bool adjust_split_end, char *outName, const char *tmpdir, Bool force_rap_split, const char *split_range_str) { - u32 i, count, nb_tk, needs_rap_sync, cur_file, conv_type, nb_tk_done, nb_samp, nb_done, di; - Double max_dur, cur_file_time; - Bool do_add, all_duplicatable, size_exceeded, chunk_extraction, rap_split, split_until_end; - GF_ISOFile *dest; - GF_ISOSample *samp; + Bool chunk_extraction, rap_split, split_until_end; GF_Err e; - TKInfo *tks, *tki; - char *ext, szName[1000], szFile[1000]; + char *ext, szName[GF_MAX_PATH], szFile[GF_MAX_PATH+100], szArgs[100]; Double chunk_start = (Double) chunk_start_time; - - chunk_extraction = (chunk_start>=0) ? 1 : 0; - split_until_end = 0; - rap_split = 0; - if (split_size_kb == (u64)-1) rap_split = 1; - if (split_dur == -1) rap_split = 1; - else if (split_dur==-2) { + char *filter_args = NULL; + GF_FilterSession *fs; + GF_Filter *src, *reframe, *dst; + u32 progress = (u32) -1; + + chunk_extraction = (chunk_start>=0) ? GF_TRUE : GF_FALSE; + if (split_range_str) + chunk_extraction = GF_TRUE; + split_until_end = GF_FALSE; + rap_split = GF_FALSE; + if (split_size_kb == (u64)-1) rap_split = GF_TRUE; + if (split_dur == -1) rap_split = GF_TRUE; + else if (split_dur <= -2) { split_size_kb = 0; - split_until_end = 1; - split_dur = 0; + split_until_end = GF_TRUE; } + else if (force_rap_split) + rap_split = GF_TRUE; - if (rap_split) { - split_size_kb = 0; - split_dur = (double) GF_MAX_FLOAT; - } - - - ext = strrchr(inName, '/'); - if (!ext) ext = strrchr(inName, '\\'); - strcpy(szName, ext ? ext+1 : inName); + //split in same dir as source + strcpy(szName, inName); ext = strrchr(szName, '.'); if (ext) ext[0] = 0; - ext = strrchr(inName, '.'); - dest = NULL; - - conv_type = 0; - switch (gf_isom_guess_specification(mp4)) { - case GF_4CC('I','S','M','A'): - conv_type = 1; - break; - case GF_ISOM_BRAND_3GP4: - case GF_ISOM_BRAND_3GP5: - case GF_ISOM_BRAND_3GP6: - case GF_ISOM_BRAND_3GG6: - case GF_ISOM_BRAND_3G2A: - conv_type = 2; - break; + fs = gf_fs_new_defaults(0); + if (!fs) { + fprintf(stderr, "Failed to load filter session, aborting\n"); + return GF_IO_ERR; } - if (!stricmp(ext, ".3gp") || !stricmp(ext, ".3g2")) conv_type = 2; - - count = gf_isom_get_track_count(mp4); - tks = (TKInfo *)gf_malloc(sizeof(TKInfo)*count); - memset(tks, 0, sizeof(TKInfo)*count); - - e = GF_OK; - max_dur = 0; - nb_tk = 0; - all_duplicatable = 1; - needs_rap_sync = 0; - nb_samp = 0; - for (i=0; i1) { - break; - } - continue; - case GF_ISOM_MEDIA_HINT: - case GF_ISOM_MEDIA_SCENE: - case GF_ISOM_MEDIA_OCR: - case GF_ISOM_MEDIA_OD: - case GF_ISOM_MEDIA_OCI: - case GF_ISOM_MEDIA_IPMP: - case GF_ISOM_MEDIA_MPEGJ: - case GF_ISOM_MEDIA_MPEG7: - case GF_ISOM_MEDIA_FLASH: - fprintf(stderr, "WARNING: Track ID %d (type %s) not handled by splitter - skipping\n", gf_isom_get_track_id(mp4, i+1), gf_4cc_to_str(mtype)); - continue; - default: - /*for all other track types, only split if more than one sample*/ - if (gf_isom_get_sample_count(mp4, i+1)==1) { - fprintf(stderr, "WARNING: Track ID %d (type %s) not handled by splitter - skipping\n", gf_isom_get_track_id(mp4, i+1), gf_4cc_to_str(mtype)); - continue; - } - tks[nb_tk].can_duplicate = 1; - } - tks[nb_tk].sample_count = gf_isom_get_sample_count(mp4, i+1); - nb_samp += tks[nb_tk].sample_count; - tks[nb_tk].last_sample = 0; - tks[nb_tk].firstDTS = 0; - tks[nb_tk].time_scale = gf_isom_get_media_timescale(mp4, i+1); - tks[nb_tk].has_non_raps = gf_isom_has_sync_points(mp4, i+1); - /*seen that on some 3gp files from nokia ...*/ - if (mtype==GF_ISOM_MEDIA_AUDIO) tks[nb_tk].has_non_raps = 0; - dur = (Double) (s64) gf_isom_get_media_duration(mp4, i+1); - dur /= tks[nb_tk].time_scale; - if (max_dur=max_dur) { - fprintf(stderr, "Input file (%f) shorter than requested split start offset (%f)\n", max_dur, chunk_start); - gf_free(tks); - return GF_NOT_SUPPORTED; + //default output name formatting + if (!outName) { + strcpy(szFile, szName); + strcat(szFile, "_$num%03d$"); } - if (split_until_end) { - split_dur = max_dur; - } else if (!rap_split && (max_dur<=split_dur)) { - fprintf(stderr, "Input file (%f) shorter than requested split duration (%f)\n", max_dur, split_dur); - gf_free(tks); - return GF_NOT_SUPPORTED; - } - if (needs_rap_sync) { - Bool has_enough_sync = GF_FALSE; - tki = &tks[needs_rap_sync-1]; - - if (chunk_start == 0.0f) - has_enough_sync = GF_TRUE; - else if (gf_isom_get_sync_point_count(mp4, tki->tk) > 1) - has_enough_sync = GF_TRUE; - else if (gf_isom_get_sample_group_info(mp4, tki->tk, 1, GF_4CC('r', 'a', 'p', ' '), NULL, NULL, NULL)) - has_enough_sync = GF_TRUE; - if (!has_enough_sync) { - fprintf(stderr, "Not enough Random Access points in input file - cannot split\n"); - gf_free(tks); - return GF_NOT_SUPPORTED; - } - } - split_size_kb *= 1024; - cur_file_time = 0; - - if (chunk_start>0) { - if (needs_rap_sync) { - u32 sample_num; - Double start; - tki = &tks[needs_rap_sync-1]; - - start = (Double) (s64) gf_isom_get_sample_dts(mp4, tki->tk, tki->sample_count); - start /= tki->time_scale; - if (startstop_state = 2; - } else { - e = gf_isom_get_sample_for_media_time(mp4, tki->tk, (u64) (chunk_start*tki->time_scale), &di, GF_ISOM_SEARCH_SYNC_BACKWARD, &samp, &sample_num); - if (e!=GF_OK) { - fprintf(stderr, "Cannot locate RAP in track ID %d for chunk extraction from %02.2f sec\n", gf_isom_get_track_id(mp4, tki->tk), chunk_start); - gf_free(tks); - return GF_NOT_SUPPORTED; - } - start = (Double) (s64) samp->DTS; - start /= tki->time_scale; - gf_isom_sample_del(&samp); - fprintf(stderr, "Adjusting chunk start time to previous random access at %02.2f sec\n", start); - split_dur += (chunk_start - start); - chunk_start = start; - } - } - /*sync all tracks*/ - for (i=0; ilast_samplesample_count) { - Double time; - u64 dts; - dts = gf_isom_get_sample_dts(mp4, tki->tk, tki->last_sample+1); - time = (Double) (s64) dts; - time /= tki->time_scale; - if (time>=chunk_start) { - /*rewind one sample (text tracks & co)*/ - if (tki->can_duplicate && tki->last_sample) { - tki->last_sample--; - tki->firstDTS = (u64) (chunk_start*tki->time_scale); - } else { - tki->firstDTS = dts; - } - break; - } - tki->last_sample++; + gf_dynstrcat(&filter_args, "reframer:splitrange", NULL); + if (rap_split) { + gf_dynstrcat(&filter_args, ":xs=SAP", NULL); + } else if (split_size_kb) { + sprintf(szArgs, ":xs=S"LLU"k", split_size_kb); + gf_dynstrcat(&filter_args, szArgs, NULL); + } else if (chunk_extraction) { + gf_dynstrcat(&filter_args, ":xround=closest", NULL); + if (split_range_str) { + char *end = (char *) strchr(split_range_str, '-'); + assert(end); + end[0] = 0; + sprintf(szArgs, ":xs=T%s:xe=T%s", split_range_str, end+1); + end[0] = '-'; + } else if (split_until_end) { + Double end=0; + if (split_dur<-2) { + end = (Double) gf_isom_get_duration(mp4); + end /= gf_isom_get_timescale(mp4); + split_dur = -2 - split_dur; + if (end > split_dur) end-=split_dur; + else end = 0; + } + if (end>0) { + sprintf(szArgs, ":xs=%u/1000:xe=%u/1000", (u32) (chunk_start*1000), (u32) (end * 1000) ); + } else { + sprintf(szArgs, ":xs=%u/1000", (u32) (chunk_start*1000)); } - } - cur_file_time = chunk_start; - } else { - chunk_start = 0; - } - - dest = NULL; - nb_done = 0; - nb_tk_done = 0; - cur_file = 0; - while (nb_tk_donestop_state==2) continue; - - e = gf_isom_clone_track(mp4, tki->tk, dest, GF_FALSE, &tki->dst_tk); - if (e) { - fprintf(stderr, "Error cloning track %d\n", tki->tk); - goto err_exit; - } - /*use non-packet CTS offsets (faster add/remove)*/ - if (gf_isom_has_time_offset(mp4, tki->tk)) { - gf_isom_set_cts_packing(dest, tki->dst_tk, GF_TRUE); - } - gf_isom_remove_edit_segments(dest, tki->dst_tk); - } - do_add = 1; - is_last_rap = 0; - last_rap_sample_time = 0; - file_split_dur = split_dur; - - size_exceeded = 0; - max_dts = 0; - while (do_add) { - Bool is_rap; - Double time; - u32 nb_over, nb_av = 0; - /*perfom basic de-interleaving to make sure we're not importing too much of a given track*/ - u32 nb_add = 0; - /*add one sample of each track*/ - for (i=0; ican_duplicate) nb_av++; - - if (tki->stop_state) - continue; - if (tki->last_sample==tki->sample_count) - continue; - - /*get sample info, see if we need to check it (basic de-interleaver)*/ - dts = gf_isom_get_sample_dts(mp4, tki->tk, tki->last_sample+1); - - /*reinsertion (timed text)*/ - if (dts < tki->firstDTS) { - samp = gf_isom_get_sample(mp4, tki->tk, tki->last_sample+1, &di); - samp->DTS = 0; - e = gf_isom_add_sample(dest, tki->dst_tk, di, samp); - if (!e) { - e = gf_isom_copy_sample_info(dest, tki->dst_tk, mp4, tki->tk, tki->last_sample+1); - } - - gf_isom_sample_del(&samp); - tki->last_sample += 1; - dts = gf_isom_get_sample_dts(mp4, tki->tk, tki->last_sample+1); - } - dts -= tki->firstDTS; - - - t = (Double) (s64) dts; - t /= tki->time_scale; - if (tki->first_sample_done) { - if (!all_av_done && (t>max_dts)) continue; - } else { - /*here's the trick: only take care of a/v media for splitting, and add other media - only if thir dts is less than the max AV dts found. Otherwise with some text streams we will end up importing - too much video and corrupting the last sync point indication*/ - if (!tki->can_duplicate && (t>max_dts)) max_dts = t; - tki->first_sample_done = 1; - } - samp = gf_isom_get_sample(mp4, tki->tk, tki->last_sample+1, &di); - samp->DTS -= tki->firstDTS; - - nb_add += 1; - - is_rap = GF_FALSE; - if (samp->IsRAP) { - is_rap = GF_TRUE; - } else { - Bool has_roll; - gf_isom_get_sample_rap_roll_info(mp4, tki->tk, tki->last_sample+1, &is_rap, &has_roll, NULL); - } - - - if (tki->has_non_raps && is_rap) { - GF_ISOSample *next_rap; - u32 next_rap_num, sdi; - last_rap_sample_time = (Double) (s64) samp->DTS; - last_rap_sample_time /= tki->time_scale; - e = gf_isom_get_sample_for_media_time(mp4, tki->tk, samp->DTS+tki->firstDTS+2, &sdi, GF_ISOM_SEARCH_SYNC_FORWARD, &next_rap, &next_rap_num); - if (e==GF_EOS) - is_last_rap = 1; - if (next_rap) { - if (!next_rap->IsRAP) - is_last_rap = 1; - gf_isom_sample_del(&next_rap); - } - } - tki->lastDTS = samp->DTS; - e = gf_isom_add_sample(dest, tki->dst_tk, di, samp); - gf_isom_sample_del(&samp); - - if (!e) { - e = gf_isom_copy_sample_info(dest, tki->dst_tk, mp4, tki->tk, tki->last_sample+1); - } - tki->last_sample += 1; - gf_set_progress("Splitting", nb_done, nb_samp); - nb_done++; - if (e) { - fprintf(stderr, "Error cloning track %d sample %d\n", tki->tk, tki->last_sample); - goto err_exit; - } - - tki->next_sample_is_rap = 0; - if (rap_split && tki->has_non_raps) { - if ( gf_isom_get_sample_sync(mp4, tki->tk, tki->last_sample+1)) - tki->next_sample_is_rap = 1; - } - } - - /*test by size/duration*/ - nb_over = 0; - - /*test by file size: same as duration test, only dynamically increment import duration*/ - if (split_size_kb) { - u64 est_size = gf_isom_estimate_size(dest); - /*while below desired size keep importing*/ - if (est_sizestop_state) { - nb_over++; - if (!tki->can_duplicate && (tki->last_sample==tki->sample_count) ) - nb_av--; - continue; - } - time = (Double) (s64) tki->lastDTS; - time /= tki->time_scale; - if (size_exceeded - || (tki->last_sample==tki->sample_count) - || (!tki->can_duplicate && (time>file_split_dur)) - || (rap_split && tki->has_non_raps && tki->next_sample_is_rap) - ) { - nb_over++; - tki->stop_state = 1; - if (tki->last_samplesample_count) - is_last_rap = 0; - else if (tki->first_sample_done) - is_last_rap = 0; - - if (rap_split && tki->next_sample_is_rap) { - file_split_dur = (Double) ( gf_isom_get_sample_dts(mp4, tki->tk, tki->last_sample+1) - tki->firstDTS); - file_split_dur /= tki->time_scale; - } - } - /*special tracks (not audio, not video)*/ - else if (tki->can_duplicate) { - u64 dts = gf_isom_get_sample_dts(mp4, tki->tk, tki->last_sample+1); - time = (Double) (s64) (dts - tki->firstDTS); - time /= tki->time_scale; - if (time>file_split_dur) { - nb_over++; - tki->stop_state = 1; - } - } - if (!nb_add && (!max_dts || (tki->lastDTS <= 1 + (u64) (tki->time_scale*max_dts) ))) - tki->first_sample_done = 0; - } - if (nb_over==nb_tk) do_add = 0; - - if (!nb_av) - all_av_done = GF_TRUE; - } - - /*remove samples - first figure out smallest duration*/ - file_split_dur = (Double) GF_MAX_FLOAT; - for (i=0; istop_state==2) || (!is_last_rap && (tki->sample_count == tki->last_sample)) ) { - if (tki->has_non_raps) last_rap_sample_time = 0; - time = (Double) (s64) ( gf_isom_get_sample_dts(mp4, tki->tk, tki->last_sample+1) - tki->firstDTS); - time /= tki->time_scale; - if (file_split_dur==(Double)GF_MAX_FLOAT || file_split_durlastDTS) - { - //time = (Double) (s64) tki->lastDTS; - time = (Double) (s64) ( gf_isom_get_sample_dts(mp4, tki->tk, tki->last_sample+1) - tki->firstDTS); - time /= tki->time_scale; - if ((!tki->can_duplicate || all_duplicatable) && timenext_sample_is_rap) file_split_dur = time; - } + sprintf(szArgs, ":xs=%u/1000:xe=%u/1000", (u32) (chunk_start*1000), (u32) ((chunk_start+split_dur) * 1000) ); } - if (file_split_dur == (Double) GF_MAX_FLOAT) { - fprintf(stderr, "Cannot split file (duration too small or size too small)\n"); - goto err_exit; + gf_dynstrcat(&filter_args, szArgs, NULL); + if (adjust_split_end) { + gf_dynstrcat(&filter_args, ":xadjust", NULL); } - if (chunk_extraction) { - if (adjust_split_end) { - fprintf(stderr, "Adjusting chunk end time to previous random access at %02.2f sec\n", chunk_start + last_rap_sample_time); - file_split_dur = last_rap_sample_time; - if (outName) strcpy(szFile, outName); - else sprintf(szFile, "%s_%d_%d%s", szName, (u32) chunk_start, (u32) (chunk_start+file_split_dur), ext); - gf_isom_set_final_name(dest, szFile); - } - else file_split_dur = split_dur; + if (!outName) { + sprintf(szFile, "%s_$FS$", szName); } - - /*don't split if eq to copy...*/ - if (is_last_rap && !cur_file && !chunk_start) { - fprintf(stderr, "Cannot split file (Not enough sync samples, duration too large or size too big)\n"); - goto err_exit; - } - - - /*if not last chunk and longer duration adjust to previous RAP point*/ - if ( (size_exceeded || !split_size_kb) && (file_split_dur>split_dur) && !chunk_start) { - /*if larger than last RAP, rewind till it*/ - if (last_rap_sample_time && (last_rap_sample_timedst_tk); - - time = (Double) (s64) gf_isom_get_media_duration(dest, tki->dst_tk); - //time could get slightly higher than requests dur due to rounding precision. We use 1/4 of the last sample dur as safety marge - time -= (Double) (s64) gf_isom_get_sample_duration(dest, tki->dst_tk, tki->last_sample) / 4; - time /= tki->time_scale; - - if (last_samp<=1) break; - - /*done*/ - if (tki->last_sample==tki->sample_count) { - if (!chunk_extraction && !tki->can_duplicate) { - tki->stop_state=2; - break; - } - } - - if (time <= file_split_dur) break; - - gf_isom_remove_sample(dest, tki->dst_tk, last_samp); - tki->last_sample--; - assert(tki->last_sample); - nb_done--; - gf_set_progress("Splitting", nb_done, nb_samp); - } - if (tki->last_samplesample_count) { - u64 dts; - tki->stop_state = 0; - dts = gf_isom_get_sample_dts(mp4, tki->tk, tki->last_sample+1); - time = (Double) (s64) (dts - tki->firstDTS); - time /= tki->time_scale; - /*re-insert prev sample*/ - if (tki->can_duplicate && (time>file_split_dur) ) { - Bool was_insert = GF_FALSE; - tki->last_sample--; - dts = gf_isom_get_sample_dts(mp4, tki->tk, tki->last_sample+1); - if (dts < tki->firstDTS) was_insert = GF_TRUE; - tki->firstDTS += (u64) (file_split_dur*tki->time_scale); - //the original, last sample added starts before the first sample in the file: we have re-inserted - //a single sample, use split duration as target duration - if (was_insert) { - gf_isom_set_last_sample_duration(dest, tki->dst_tk, (u32) (file_split_dur*tki->time_scale)); - } else { - gf_isom_set_last_sample_duration(dest, tki->dst_tk, (u32) (tki->firstDTS - dts) ); - } - } else { - tki->firstDTS = dts; - } - tki->first_sample_done = 0; - } else { - nb_tk_done++; - } - - } + } else if (split_dur) { + sprintf(szArgs, ":xs=D%u", (u32) (split_dur*1000)); + gf_dynstrcat(&filter_args, szArgs, NULL); + if (adjust_split_end) { + gf_dynstrcat(&filter_args, ":xadjust", NULL); } + } else { + gf_fs_del(fs); + gf_free(filter_args); + fprintf(stderr, "Unrecognized split syntax\n"); + return GF_BAD_PARAM; + } - if (chunk_extraction) { - fprintf(stderr, "Extracting chunk %s - duration %02.2fs (%02.2fs->%02.2fs)\n", szFile, file_split_dur, chunk_start, (chunk_start+split_dur)); - } else { - fprintf(stderr, "Storing split-file %s - duration %02.2f seconds\n", szFile, file_split_dur); - } + reframe = gf_fs_load_filter(fs, filter_args, &e); + gf_free(filter_args); + if (!reframe) { + fprintf(stderr, "Failed to load reframer filter: %s\n", gf_error_to_string(e) ); + gf_fs_del(fs); + return e; + } - /*repack CTSs*/ - for (i=0; istop_state == 2) continue; - if (!gf_isom_get_sample_count(dest, tki->dst_tk)) { - gf_isom_remove_track(dest, tki->dst_tk); - continue; - } - if (gf_isom_has_time_offset(mp4, tki->tk)) { - gf_isom_set_cts_packing(dest, tki->dst_tk, GF_FALSE); - } - if (is_last_rap && tki->can_duplicate) { - gf_isom_set_last_sample_duration(dest, tki->dst_tk, gf_isom_get_sample_duration(mp4, tki->tk, tki->sample_count)); - } + if (!outName) { + strcat(szFile, ".mp4"); + } else { + strcpy(szFile, outName); + } + dst = gf_fs_load_destination(fs, szFile, NULL, NULL, &e); + if (!dst) { + fprintf(stderr, "Failed to load destination filter: %s\n", gf_error_to_string(e) ); + gf_fs_del(fs); + return e; + } + //link reframer to dst + gf_filter_set_source(dst, reframe, NULL); - /*rewrite edit list*/ - new_track_dur = gf_isom_get_track_duration(dest, tki->dst_tk); - count = gf_isom_get_edit_segment_count(mp4, tki->tk); - if (count>2) { - fprintf(stderr, "Warning: %d edit segments - not supported while splitting (max 2) - ignoring extra\n", count); - count=2; - } - for (j=0; jtk, j+1, &editTime, &segDur, &MediaTime, &mode); - if (!j && (mode!=GF_ISOM_EDIT_EMPTY) ) { - fprintf(stderr, "Warning: Edit list doesn't look like a track delay scheme - ignoring\n"); - break; - } - if (mode==GF_ISOM_EDIT_NORMAL) { - segDur = new_track_dur; - } - gf_isom_set_edit_segment(dest, tki->dst_tk, editTime, segDur, MediaTime, mode); - } - } - /*check chapters*/ - do_add = 1; - for (i=0; icur_file_time+file_split_dur) break; - max_dts-=cur_file_time; - chap_time = (u64) (max_dts*1000); - gf_isom_add_chapter(dest, 0, chap_time, name); - /*add prev*/ - if (do_add && i) { - gf_isom_get_chapter(mp4, 0, i, &chap_time, (const char **) &name); - gf_isom_add_chapter(dest, 0, 0, name); - do_add = 0; - } - } - cur_file_time += file_split_dur; - - if (conv_type==1) gf_media_make_isma(dest, 1, 0, 0); - else if (conv_type==2) gf_media_make_3gpp(dest); - if (InterleavingTime) { - gf_isom_make_interleave(dest, InterleavingTime); - } else { - gf_isom_set_storage_mode(dest, GF_ISOM_STORE_STREAMABLE); - } - gf_isom_clone_pl_indications(mp4, dest); - e = gf_isom_close(dest); - dest = NULL; - if (e) fprintf(stderr, "Error storing file %s\n", gf_error_to_string(e)); - if (is_last_rap || chunk_extraction) break; - cur_file++; + e = gf_fs_run(fs); + if (e>=GF_OK) { + e = gf_fs_get_last_connect_error(fs); + if (e>=GF_OK) + e = gf_fs_get_last_process_error(fs); } - gf_set_progress("Splitting", nb_samp, nb_samp); -err_exit: - if (dest) gf_isom_delete(dest); - gf_free(tks); + gf_fs_del(fs); + if (eAVCLevelIndication!=avc_dst->AVCLevelIndication) { + if (!force_cat && (avc_src->AVCLevelIndication!=avc_dst->AVCLevelIndication)) { dst_tk = 0; - } else if (avc_src->AVCProfileIndication!=avc_dst->AVCProfileIndication) { + } else if (!force_cat && (avc_src->AVCProfileIndication!=avc_dst->AVCProfileIndication)) { dst_tk = 0; } else { /*rewrite all samples if using different NALU size*/ if (avc_src->nal_unit_size > avc_dst->nal_unit_size) { - gf_media_avc_rewrite_samples(dest, dst_tk, 8*avc_dst->nal_unit_size, 8*avc_src->nal_unit_size); + gf_media_nal_rewrite_samples(dest, dst_tk, 8*avc_src->nal_unit_size); avc_dst->nal_unit_size = avc_src->nal_unit_size; } else if (avc_src->nal_unit_size < avc_dst->nal_unit_size) { - gf_media_avc_rewrite_samples(orig, src_track, 8*avc_src->nal_unit_size, 8*avc_dst->nal_unit_size); + gf_media_nal_rewrite_samples(orig, src_track, 8*avc_dst->nal_unit_size); } /*merge PS*/ @@ -1677,7 +1556,7 @@ static u32 merge_avc_config(GF_ISOFile *dest, u32 tk_id, GF_ISOFile *orig, u32 s dst_tk = gf_isom_get_track_by_id(dest, tk_id); gf_isom_set_nalu_extract_mode(orig, src_track, GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG); if (!force_cat) { - gf_isom_avc_set_inband_config(dest, dst_tk, 1); + gf_isom_avc_set_inband_config(dest, dst_tk, 1, GF_FALSE); } else { fprintf(stderr, "WARNING: Concatenating track ID %d even though sample descriptions do not match\n", tk_id); } @@ -1701,10 +1580,10 @@ static u32 merge_hevc_config(GF_ISOFile *dest, u32 tk_id, GF_ISOFile *orig, u32 else { /*rewrite all samples if using different NALU size*/ if (hevc_src->nal_unit_size > hevc_dst->nal_unit_size) { - gf_media_avc_rewrite_samples(dest, dst_tk, 8*hevc_dst->nal_unit_size, 8*hevc_src->nal_unit_size); + gf_media_nal_rewrite_samples(dest, dst_tk, 8*hevc_src->nal_unit_size); hevc_dst->nal_unit_size = hevc_src->nal_unit_size; } else if (hevc_src->nal_unit_size < hevc_dst->nal_unit_size) { - gf_media_avc_rewrite_samples(orig, src_track, 8*hevc_src->nal_unit_size, 8*hevc_dst->nal_unit_size); + gf_media_nal_rewrite_samples(orig, src_track, 8*hevc_dst->nal_unit_size); } /*merge PS*/ @@ -1731,7 +1610,7 @@ static u32 merge_hevc_config(GF_ISOFile *dest, u32 tk_id, GF_ISOFile *orig, u32 dst_tk = gf_isom_get_track_by_id(dest, tk_id); gf_isom_set_nalu_extract_mode(orig, src_track, GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG); if (!force_cat) { - gf_isom_hevc_set_inband_config(dest, dst_tk, 1); + gf_isom_hevc_set_inband_config(dest, dst_tk, 1, GF_FALSE); } else { fprintf(stderr, "WARNING: Concatenating track ID %d even though sample descriptions do not match\n", tk_id); } @@ -1740,7 +1619,9 @@ static u32 merge_hevc_config(GF_ISOFile *dest, u32 tk_id, GF_ISOFile *orig, u32 } #endif /*GPAC_DISABLE_HEVC */ -GF_Err cat_isomedia_file(GF_ISOFile *dest, char *fileName, u32 import_flags, Double force_fps, u32 frames_per_sample, char *tmp_dir, Bool force_cat, Bool align_timelines, Bool allow_add_in_command) +GF_Err cat_playlist(GF_ISOFile *dest, char *playlistName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, char *tmp_dir, Bool force_cat, Bool align_timelines, Bool allow_add_in_command); + +GF_Err cat_isomedia_file(GF_ISOFile *dest, char *fileName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, char *tmp_dir, Bool force_cat, Bool align_timelines, Bool allow_add_in_command, Bool is_pl) { u32 i, j, count, nb_tracks, nb_samp, nb_done; GF_ISOFile *orig; @@ -1752,18 +1633,19 @@ GF_Err cat_isomedia_file(GF_ISOFile *dest, char *fileName, u32 import_flags, Dou u64 insert_dts; Bool is_isom; GF_ISOSample *samp; - Double aligned_to_DTS = 0; + GF_Fraction64 aligned_to_DTS_frac; + + if (is_pl) return cat_playlist(dest, fileName, import_flags, force_fps, frames_per_sample, tmp_dir, force_cat, align_timelines, allow_add_in_command); - if (strchr(fileName, '*')) return cat_multiple_files(dest, fileName, import_flags, force_fps, frames_per_sample, tmp_dir, force_cat, align_timelines, allow_add_in_command); + if (strchr(fileName, '*') || (strchr(fileName, '@')) ) + return cat_multiple_files(dest, fileName, import_flags, force_fps, frames_per_sample, tmp_dir, force_cat, align_timelines, allow_add_in_command); multi_cat = allow_add_in_command ? strchr(fileName, '+') : NULL; if (multi_cat) { multi_cat[0] = 0; multi_cat = &multi_cat[1]; } - opts = strchr(fileName, ':'); - if (opts && (opts[1]=='\\')) - opts = strchr(fileName, ':'); + opts = gf_url_colon_suffix(fileName); e = GF_OK; @@ -1772,7 +1654,7 @@ GF_Err cat_isomedia_file(GF_ISOFile *dest, char *fileName, u32 import_flags, Dou if (!is_isom || opts) { orig = gf_isom_open("temp", GF_ISOM_WRITE_EDIT, tmp_dir); - e = import_file(orig, fileName, import_flags, force_fps, frames_per_sample); + e = import_file(orig, fileName, import_flags, force_fps, frames_per_sample, NULL, NULL, 0); if (e) return e; } else { /*we open the original file in edit mode since we may have to rewrite AVC samples*/ @@ -1783,7 +1665,7 @@ GF_Err cat_isomedia_file(GF_ISOFile *dest, char *fileName, u32 import_flags, Dou char *sep = strchr(multi_cat, '+'); if (sep) sep[0] = 0; - e = import_file(orig, multi_cat, import_flags, force_fps, frames_per_sample); + e = import_file(orig, multi_cat, import_flags, force_fps, frames_per_sample, NULL, NULL, 0); if (e) { gf_isom_delete(orig); return e; @@ -1796,7 +1678,7 @@ GF_Err cat_isomedia_file(GF_ISOFile *dest, char *fileName, u32 import_flags, Dou nb_samp = 0; nb_tracks = gf_isom_get_track_count(orig); for (i=0; i1)) { insert_dts = 2*gf_isom_get_sample_dts(dest, dst_tk, count) - gf_isom_get_sample_dts(dest, dst_tk, count-1); } else { @@ -2028,14 +1960,13 @@ GF_Err cat_isomedia_file(GF_ISOFile *dest, char *fileName, u32 import_flags, Dou /*if not a new track, see if we can merge the edit list - this is a crude test that only checks we have the same edit types*/ - if (nb_edits && (nb_edits == gf_isom_get_edit_segment_count(dest, dst_tk)) ) { + if (nb_edits && (nb_edits == gf_isom_get_edits_count(dest, dst_tk)) ) { u64 editTime, segmentDuration, mediaTime, dst_editTime, dst_segmentDuration, dst_mediaTime; - u8 dst_editMode, editMode; - u32 j; + GF_ISOEditType dst_editMode, editMode; merge_edits = 1; for (j=0; jCTS_Offset = (u32) (samp->CTS_Offset * ts_scale); if (gf_isom_is_self_contained(orig, i+1, di)) { - e = gf_isom_add_sample(dest, dst_tk, di, samp); + e = gf_isom_add_sample(dest, dst_tk, di + dst_tk_sample_entry, samp); } else { u64 offset; GF_ISOSample *s = gf_isom_get_sample_info(orig, i+1, j+1, &di, &offset); - e = gf_isom_add_sample_reference(dest, dst_tk, di, samp, offset); + e = gf_isom_add_sample_reference(dest, dst_tk, di + dst_tk_sample_entry, samp, offset); gf_isom_sample_del(&s); } + if (samp->nb_pack) + j+= samp->nb_pack-1; + gf_isom_sample_del(&samp); if (e) goto err_exit; @@ -2072,8 +2008,9 @@ GF_Err cat_isomedia_file(GF_ISOFile *dest, char *fileName, u32 import_flags, Dou } /*scene description and text: compute last sample duration based on original media duration*/ if (!use_ts_dur) { - insert_dts = gf_isom_get_media_duration(orig, i+1) - last_DTS; - gf_isom_set_last_sample_duration(dest, dst_tk, (u32) insert_dts); + u64 extend_dur = gf_isom_get_media_duration(orig, i+1) - last_DTS; + //extend the duration of the last sample, but don't insert any edit list entry + gf_isom_set_last_sample_duration(dest, dst_tk, (u32) (ts_scale*extend_dur) ); } if (new_track && insert_dts) { @@ -2084,54 +2021,54 @@ GF_Err cat_isomedia_file(GF_ISOFile *dest, char *fileName, u32 import_flags, Dou /*convert from orig to dst time scale*/ rescale *= ts_scale; - gf_isom_set_edit_segment(dest, dst_tk, 0, (u64) (s64) (insert_dts*rescale), 0, GF_ISOM_EDIT_EMPTY); - gf_isom_set_edit_segment(dest, dst_tk, (u64) (s64) (insert_dts*rescale), (u64) (s64) (media_dur*rescale), 0, GF_ISOM_EDIT_NORMAL); + gf_isom_set_edit(dest, dst_tk, 0, (u64) (s64) (insert_dts*rescale), 0, GF_ISOM_EDIT_EMPTY); + gf_isom_set_edit(dest, dst_tk, (u64) (s64) (insert_dts*rescale), (u64) (s64) (media_dur*rescale), 0, GF_ISOM_EDIT_NORMAL); } else if (merge_edits) { /*convert from media time to track time*/ - Double rescale = (Float) gf_isom_get_timescale(dest); - rescale /= (Float) gf_isom_get_media_timescale(dest, dst_tk); - /*convert from orig to dst time scale*/ - rescale *= ts_scale; + u32 movts_dst = gf_isom_get_timescale(dest); + u32 trackts_dst = gf_isom_get_media_timescale(dest, dst_tk); + u32 trackts_orig = gf_isom_get_media_timescale(orig, i+1); /*get the first edit normal mode and add the new track dur*/ for (j=nb_edits; j>0; j--) { u64 editTime, segmentDuration, mediaTime; - u8 editMode; - gf_isom_get_edit_segment(dest, dst_tk, j, &editTime, &segmentDuration, &mediaTime, &editMode); + GF_ISOEditType editMode; + gf_isom_get_edit(dest, dst_tk, j, &editTime, &segmentDuration, &mediaTime, &editMode); if (editMode==GF_ISOM_EDIT_NORMAL) { Double prev_dur = (Double) (s64) dest_track_dur_before_cat; Double dur = (Double) (s64) gf_isom_get_media_duration(orig, i+1); - dur *= rescale; - prev_dur *= rescale; - /*safety test: some files have broken edit lists. If no more than 2 entries, check that the segment duration is less or equal to the movie duration*/ - if (prev_dur < segmentDuration) { - fprintf(stderr, "Warning: suspicious edit list entry found: duration %g sec but longest track duration before cat is %g - fixing it\n", (Double) (s64) segmentDuration/1000.0, prev_dur/1000); - segmentDuration = (u64) (s64) ( (Double) (s64) (dest_track_dur_before_cat - mediaTime) * rescale ); + if (prev_dur * movts_dst < segmentDuration * trackts_dst) { + fprintf(stderr, "Warning: suspicious edit list entry found: duration %g sec but longest track duration before cat is %g - fixing it\n", (Double) (s64) segmentDuration/movts_dst, prev_dur/trackts_dst); + segmentDuration = (dest_track_dur_before_cat - mediaTime) * movts_dst; + segmentDuration /= trackts_dst; } + //express original dur in new timescale + dur /= trackts_orig; + dur *= movts_dst; + segmentDuration += (u64) (s64) dur; - gf_isom_modify_edit_segment(dest, dst_tk, j, segmentDuration, mediaTime, editMode); + gf_isom_modify_edit(dest, dst_tk, j, segmentDuration, mediaTime, editMode); break; } } } else { u64 editTime, segmentDuration, mediaTime, edit_offset; Double t; - u8 editMode; - u32 j, count; + GF_ISOEditType editMode; - count = gf_isom_get_edit_segment_count(dest, dst_tk); + count = gf_isom_get_edits_count(dest, dst_tk); if (count) { - e = gf_isom_get_edit_segment(dest, dst_tk, count, &editTime, &segmentDuration, &mediaTime, &editMode); + e = gf_isom_get_edit(dest, dst_tk, count, &editTime, &segmentDuration, &mediaTime, &editMode); if (e) { fprintf(stderr, "Error: edit segment error on destination track %u could not be retrieved.\n", dst_tk); goto err_exit; } - } else if (gf_isom_get_edit_segment_count(orig, i+1)) { + } else if (gf_isom_get_edits_count(orig, i+1)) { /*fake empty edit segment*/ /*convert from media time to track time*/ Double rescale = (Float) gf_isom_get_timescale(dest); @@ -2139,7 +2076,8 @@ GF_Err cat_isomedia_file(GF_ISOFile *dest, char *fileName, u32 import_flags, Dou segmentDuration = (u64) (dest_track_dur_before_cat * rescale); editTime = 0; mediaTime = 0; - gf_isom_set_edit_segment(dest, dst_tk, editTime, segmentDuration, mediaTime, GF_ISOM_EDIT_NORMAL); + if (segmentDuration) + gf_isom_set_edit(dest, dst_tk, editTime, segmentDuration, mediaTime, GF_ISOM_EDIT_NORMAL); } else { editTime = 0; segmentDuration = 0; @@ -2150,9 +2088,9 @@ GF_Err cat_isomedia_file(GF_ISOFile *dest, char *fileName, u32 import_flags, Dou ts_scale /= (Float) gf_isom_get_timescale(orig); edit_offset = editTime + segmentDuration; - count = gf_isom_get_edit_segment_count(orig, i+1); + count = gf_isom_get_edits_count(orig, i+1); for (j=0; j 0)) { editMode = GF_ISOM_EDIT_NORMAL; } - gf_isom_set_edit_segment(dest, dst_tk, editTime, segmentDuration, mediaTime, editMode); + gf_isom_set_edit(dest, dst_tk, editTime, segmentDuration, mediaTime, editMode); } } + gf_media_update_bitrate(dest, dst_tk); } + for (i = 0; i < gf_isom_get_track_count(dest); ++i) { + if (gf_isom_get_media_type(dest, i+1) == GF_ISOM_MEDIA_TIMECODE) { + u32 video_ref = 0; + for (j = 0; j < gf_isom_get_track_count(dest); ++j) { + if (gf_isom_is_video_handler_type(gf_isom_get_media_type(dest, j+1))) { + video_ref = j+1; + break; + } + } + if (video_ref) { + u64 video_ref_dur = gf_isom_get_media_duration(dest, video_ref); + u32 last_sample_dur = gf_isom_get_sample_duration(dest, i+1, gf_isom_get_sample_count(dest, i+1)); + u64 dur = video_ref_dur - gf_isom_get_media_duration(dest, i+1) + last_sample_dur; + gf_isom_set_last_sample_duration(dest, i+1, (u32)dur); + } + } + } gf_set_progress("Appending", nb_samp, nb_samp); + /*check brands*/ + gf_isom_get_brand_info(orig, NULL, NULL, &j); + for (i=0; iszRad1, len_rad1)) return 0; if (strlen(cat_enum->szRad2) && !strstr(szName + len_rad1, cat_enum->szRad2) ) return 0; - strcpy(szFileName, szName); + strcpy(szFileName, szPath); strcat(szFileName, cat_enum->szOpt); - e = cat_isomedia_file(cat_enum->dest, szFileName, cat_enum->import_flags, cat_enum->force_fps, cat_enum->frames_per_sample, cat_enum->tmp_dir, cat_enum->force_cat, cat_enum->align_timelines, cat_enum->allow_add_in_command); + e = cat_isomedia_file(cat_enum->dest, szFileName, cat_enum->import_flags, cat_enum->force_fps, cat_enum->frames_per_sample, cat_enum->tmp_dir, cat_enum->force_cat, cat_enum->align_timelines, cat_enum->allow_add_in_command, GF_FALSE); if (e) return 1; return 0; } -GF_Err cat_multiple_files(GF_ISOFile *dest, char *fileName, u32 import_flags, Double force_fps, u32 frames_per_sample, char *tmp_dir, Bool force_cat, Bool align_timelines, Bool allow_add_in_command) +GF_Err cat_multiple_files(GF_ISOFile *dest, char *fileName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, char *tmp_dir, Bool force_cat, Bool align_timelines, Bool allow_add_in_command) { CATEnum cat_enum; char *sep; @@ -2247,30 +2210,91 @@ GF_Err cat_multiple_files(GF_ISOFile *dest, char *fileName, u32 import_flags, Do cat_enum.align_timelines = align_timelines; cat_enum.allow_add_in_command = allow_add_in_command; + if (strlen(fileName) >= sizeof(cat_enum.szPath)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("File name %s is too long.\n", fileName)); + return GF_NOT_SUPPORTED; + } strcpy(cat_enum.szPath, fileName); sep = strrchr(cat_enum.szPath, GF_PATH_SEPARATOR); if (!sep) sep = strrchr(cat_enum.szPath, '/'); if (!sep) { strcpy(cat_enum.szPath, "."); + if (strlen(fileName) >= sizeof(cat_enum.szRad1)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("File name %s is too long.\n", fileName)); + return GF_NOT_SUPPORTED; + } strcpy(cat_enum.szRad1, fileName); } else { + if (strlen(sep + 1) >= sizeof(cat_enum.szRad1)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("File name %s is too long.\n", (sep + 1))); + return GF_NOT_SUPPORTED; + } strcpy(cat_enum.szRad1, sep+1); sep[0] = 0; } sep = strchr(cat_enum.szRad1, '*'); + if (!sep) sep = strchr(cat_enum.szRad1, '@'); + if (strlen(sep + 1) >= sizeof(cat_enum.szRad2)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("File name %s is too long.\n", (sep + 1))); + return GF_NOT_SUPPORTED; + } strcpy(cat_enum.szRad2, sep+1); sep[0] = 0; sep = strchr(cat_enum.szRad2, '%'); if (!sep) sep = strchr(cat_enum.szRad2, '#'); - if (!sep) sep = strchr(cat_enum.szRad2, ':'); + if (!sep) sep = gf_url_colon_suffix(cat_enum.szRad2); strcpy(cat_enum.szOpt, ""); if (sep) { + if (strlen(sep) >= sizeof(cat_enum.szOpt)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Invalid option: %s.\n", sep)); + return GF_NOT_SUPPORTED; + } strcpy(cat_enum.szOpt, sep); sep[0] = 0; } return gf_enum_directory(cat_enum.szPath, 0, cat_enumerate, &cat_enum, NULL); } +GF_Err cat_playlist(GF_ISOFile *dest, char *playlistName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, char *tmp_dir, Bool force_cat, Bool align_timelines, Bool allow_add_in_command) +{ + GF_Err e; + FILE *pl = gf_fopen(playlistName, "r"); + if (!pl) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Failed to open playlist file %s\n", playlistName)); + return GF_URL_ERROR; + } + + e = GF_OK; + while (!feof(pl)) { + char szLine[10000]; + char *url; + u32 len; + szLine[0] = 0; + if (gf_fgets(szLine, 10000, pl) == NULL) break; + if (szLine[0]=='#') continue; + len = (u32) strlen(szLine); + while (len && strchr("\r\n \t", szLine[len-1])) { + szLine[len-1] = 0; + len--; + } + if (!len) continue; + + url = gf_url_concatenate(playlistName, szLine); + if (!url) url = gf_strdup(szLine); + + e = cat_isomedia_file(dest, url, import_flags, force_fps, frames_per_sample, tmp_dir, force_cat, align_timelines, allow_add_in_command, GF_FALSE); + + + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Failed to concatenate file %s\n", url)); + gf_free(url); + break; + } + gf_free(url); + } + gf_fclose(pl); + return e; +} #ifndef GPAC_DISABLE_SCENE_ENCODER /* @@ -2299,6 +2323,7 @@ GF_Err EncodeFile(char *in, GF_ISOFile *mp4, GF_SMEncodeOptions *opts, FILE *log load.swf_flatten_limit = swf_flatten_angle; /*since we're encoding we must get MPEG4 nodes only*/ load.flags = GF_SM_LOAD_MPEG4_STRICT; + e = gf_sm_load_init(&load); if (e<0) { gf_sm_load_done(&load); @@ -2314,7 +2339,7 @@ GF_Err EncodeFile(char *in, GF_ISOFile *mp4, GF_SMEncodeOptions *opts, FILE *log statsman = gf_sm_stats_new(); e = gf_sm_stats_for_scene(statsman, ctx); if (!e) { - GF_SceneStatistics *stats = gf_sm_stats_get(statsman); + const GF_SceneStatistics *stats = gf_sm_stats_get(statsman); /*LASeR*/ if (opts->auto_quant==1) { if (opts->resolution > (s32)stats->frac_res_2d) { @@ -2365,7 +2390,7 @@ GF_Err EncodeFile(char *in, GF_ISOFile *mp4, GF_SMEncodeOptions *opts, FILE *log if ((stats->count_3f+stats->count_3d) && opts->resolution) { qp->position3DMin = stats->min_3d; qp->position3DMax = stats->max_3d; - qp->position3DQuant = opts->resolution; + qp->position3DNbBits = opts->resolution; qp->position3DQuant = 1; qp->textureCoordinateQuant = 1; } @@ -2402,7 +2427,7 @@ GF_Err EncodeFile(char *in, GF_ISOFile *mp4, GF_SMEncodeOptions *opts, FILE *log } gf_isom_set_brand_info(mp4, GF_ISOM_BRAND_MP42, 1); - gf_isom_modify_alternate_brand(mp4, GF_ISOM_BRAND_ISOM, 1); + gf_isom_modify_alternate_brand(mp4, GF_ISOM_BRAND_ISOM, GF_TRUE); err_exit: #ifndef GPAC_DISABLE_SCENE_STATS @@ -2433,18 +2458,16 @@ static u32 GetNbBits(u32 MaxVal) GF_Err EncodeBIFSChunk(GF_SceneManager *ctx, char *bifsOutputFile, GF_Err (*AUCallback)(GF_ISOSample *)) { GF_Err e; - char *data; + u8 *data; u32 data_len; GF_BifsEncoder *bifsenc; GF_InitialObjectDescriptor *iod; u32 i, j, count; - GF_StreamContext *sc; - GF_ESD *esd; Bool encode_names, delete_bcfg; GF_BIFSConfig *bcfg; GF_AUContext *au; char szRad[GF_MAX_PATH], *ext; - char szName[1024]; + char szName[GF_MAX_PATH+100]; FILE *f; strcpy(szRad, bifsOutputFile); @@ -2461,20 +2484,21 @@ GF_Err EncodeBIFSChunk(GF_SceneManager *ctx, char *bifsOutputFile, GF_Err (*AUCa if (!iod) { count = 0; for (i=0; istreams); i++) { - sc = gf_list_get(ctx->streams, i); + GF_StreamContext *sc = gf_list_get(ctx->streams, i); if (sc->streamType == GF_STREAM_OD) count++; } - if (!iod && count>1) return GF_NOT_SUPPORTED; + if (count>1) return GF_NOT_SUPPORTED; } count = gf_list_count(ctx->streams); for (i=0; istreams, i); - esd = NULL; + if (sc->streamType != GF_STREAM_SCENE) continue; - esd = NULL; if (iod) { for (j=0; jESDescriptors); j++) { esd = gf_list_get(iod->ESDescriptors, j); @@ -2500,6 +2524,7 @@ GF_Err EncodeBIFSChunk(GF_SceneManager *ctx, char *bifsOutputFile, GF_Err (*AUCa esd->decoderConfig->decoderSpecificInfo = NULL; esd->ESID = sc->ESID; esd->decoderConfig->streamType = GF_STREAM_SCENE; + delete_esd = GF_TRUE; } if (!esd->decoderConfig) return GF_OUT_OF_MEM; @@ -2538,7 +2563,6 @@ GF_Err EncodeBIFSChunk(GF_SceneManager *ctx, char *bifsOutputFile, GF_Err (*AUCa gf_bifs_encoder_new_stream(bifsenc, sc->ESID, bcfg, encode_names, 0); if (delete_bcfg) gf_odf_desc_del((GF_Descriptor *)bcfg); - /*setup MP4 track*/ if (!esd->slConfig) esd->slConfig = (GF_SLConfig *) gf_odf_desc_new(GF_ODF_SLC_TAG); if (sc->timeScale) esd->slConfig->timestampResolution = sc->timeScale; if (!esd->slConfig->timestampResolution) esd->slConfig->timestampResolution = 1000; @@ -2552,18 +2576,17 @@ GF_Err EncodeBIFSChunk(GF_SceneManager *ctx, char *bifsOutputFile, GF_Err (*AUCa esd->decoderConfig->objectTypeIndication = gf_bifs_encoder_get_version(bifsenc, sc->ESID); for (j=1; jAUs); j++) { - char *data; - u32 data_len; au = gf_list_get(sc->AUs, j); e = gf_bifs_encode_au(bifsenc, sc->ESID, au->commands, &data, &data_len); if (data) { - sprintf(szName, "%s%02d.bifs", szRad, j); + sprintf(szName, "%s-%02d-%02d.bifs", szRad, sc->ESID, j); f = gf_fopen(szName, "wb"); - gf_fwrite(data, data_len, 1, f); + gf_fwrite(data, data_len, f); gf_fclose(f); gf_free(data); } } + if (delete_esd) gf_odf_desc_del((GF_Descriptor*)esd); } gf_bifs_encoder_del(bifsenc); return e; @@ -2574,12 +2597,12 @@ GF_Err EncodeBIFSChunk(GF_SceneManager *ctx, char *bifsOutputFile, GF_Err (*AUCa #endif /*GPAC_DISABLE_BIFS_ENC*/ /** - * \param chunkFile BT chunk to be encoded - * \param bifs output file name for the BIFS data - * \param inputContext initial BT upon which the chunk is based (shall not be NULL) - * \param outputContext: file name to dump the context after applying the new chunk to the input context +\param chunkFile BT chunk to be encoded +\param bifs output file name for the BIFS data +\param inputContext initial BT upon which the chunk is based (shall not be NULL) +\param outputContext: file name to dump the context after applying the new chunk to the input context can be NULL, without .bt - * \param tmpdir can be NULL +\param tmpdir can be NULL */ GF_Err EncodeFileChunk(char *chunkFile, char *bifs, char *inputContext, char *outputContext, const char *tmpdir) { @@ -2604,7 +2627,7 @@ GF_Err EncodeFileChunk(char *chunkFile, char *bifs, char *inputContext, char *ou if (!e) e = gf_sm_load_run(&load); gf_sm_load_done(&load); if (e) { - fprintf(stderr, "Cannot load context %s - %s\n", inputContext, gf_error_to_string(e)); + fprintf(stderr, "Cannot load context %s: %s\n", inputContext, gf_error_to_string(e)); goto exit; } @@ -2621,7 +2644,7 @@ GF_Err EncodeFileChunk(char *chunkFile, char *bifs, char *inputContext, char *ou if (!e) e = gf_sm_load_run(&load); gf_sm_load_done(&load); if (e) { - fprintf(stderr, "Cannot load chunk context %s - %s\n", chunkFile, gf_error_to_string(e)); + fprintf(stderr, "Cannot load scene commands chunk %s: %s\n", chunkFile, gf_error_to_string(e)); goto exit; } fprintf(stderr, "Context and chunks loaded\n"); @@ -2685,14 +2708,13 @@ void sax_node_start(void *sax_cbck, const char *node_name, const char *name_spac { char szCheck[100]; GF_List *imports = sax_cbck; - GF_XMLAttribute *att; u32 i=0; /*do not process hyperlinks*/ if (!strcmp(node_name, "a") || !strcmp(node_name, "Anchor")) return; for (i=0; iname, "xlink:href") && stricmp(att->name, "url")) continue; if (att->value[0]=='#') continue; if (!strnicmp(att->value, "od:", 3)) continue; @@ -2707,8 +2729,8 @@ static Bool wgt_enum_files(void *cbck, char *file_name, char *file_path, GF_File WGTEnum *wgt = (WGTEnum *)cbck; if (!strcmp(wgt->root_file, file_path)) return 0; - /*remove CVS stuff*/ - if (strstr(file_path, ".#")) return 0; + //remove hidden files + if (file_path[0] == '.') return 0; gf_list_add(wgt->imports, gf_strdup(file_path) ); return 0; } @@ -2781,10 +2803,10 @@ GF_ISOFile *package_file(char *file_name, char *fcc, const char *tmpdir, Bool ma mtype = GF_4CC(fcc[0],fcc[1],fcc[2],fcc[3]); } else { mtype = 0; - if (!stricmp(type, "svg")) mtype = ascii ? GF_4CC('s','v','g',' ') : GF_4CC('s','v','g','z'); - else if (!stricmp(type, "smil")) mtype = ascii ? GF_4CC('s','m','i','l') : GF_4CC('s','m','l','z'); - else if (!stricmp(type, "x3d")) mtype = ascii ? GF_4CC('x','3','d',' ') : GF_4CC('x','3','d','z') ; - else if (!stricmp(type, "xmt-a")) mtype = ascii ? GF_4CC('x','m','t','a') : GF_4CC('x','m','t','z'); + if (!stricmp(type, "svg")) mtype = ascii ? GF_META_TYPE_SVG : GF_META_TYPE_SVGZ; + else if (!stricmp(type, "smil")) mtype = ascii ? GF_META_TYPE_SMIL : GF_META_TYPE_SMLZ; + else if (!stricmp(type, "x3d")) mtype = ascii ? GF_META_TYPE_X3D : GF_META_TYPE_X3DZ ; + else if (!stricmp(type, "xmt-a")) mtype = ascii ? GF_META_TYPE_XMTA : GF_META_TYPE_XMTZ; } if (!mtype) { fprintf(stderr, "Missing 4CC code for meta name - please use ABCD:fileName\n"); @@ -2797,9 +2819,16 @@ GF_ISOFile *package_file(char *file_name, char *fcc, const char *tmpdir, Bool ma count = gf_list_count(imports); for (i=0; iname, "HDR")) { + fprintf(stderr, "Error parsing HDR XML file: root name is \"%s\", expecting \"HDR\"\n", root->name); + gf_xml_dom_del(parser); + return GF_NON_COMPLIANT_BITSTREAM; + } + + i = 0; + while ((stream = gf_list_enum(root->content, &i))) { + u32 id = 0, j; + GF_XMLAttribute* att = NULL; + GF_XMLNode *box = NULL; + + if (stream->type != GF_XML_NODE_TYPE) continue; + if (strcmp(stream->name, "Track")) continue; + + j = 0; + while ((att = gf_list_enum(stream->attributes, &j))) { + if (!strcmp(att->name, "id")) id = atoi(att->value); + else fprintf(stderr, "HDR XML: ignoring track attribute \"%s\"\n", att->name); + } + + j = 0; + while ((box = gf_list_enum(stream->content, &j))) { + u32 k; + + if (box->type != GF_XML_NODE_TYPE) continue; + + if (!strcmp(box->name, "mdcv")) { + k = 0; + while ((att = gf_list_enum(box->attributes, &k))) { + if (!strcmp(att->name, "display_primaries_0_x")) mdcv.display_primaries[0].x = atoi(att->value); + else if (!strcmp(att->name, "display_primaries_0_y")) mdcv.display_primaries[0].y = atoi(att->value); + else if (!strcmp(att->name, "display_primaries_1_x")) mdcv.display_primaries[1].x = atoi(att->value); + else if (!strcmp(att->name, "display_primaries_1_y")) mdcv.display_primaries[1].y = atoi(att->value); + else if (!strcmp(att->name, "display_primaries_2_x")) mdcv.display_primaries[2].x = atoi(att->value); + else if (!strcmp(att->name, "display_primaries_2_y")) mdcv.display_primaries[2].y = atoi(att->value); + else if (!strcmp(att->name, "white_point_x")) mdcv.white_point_x = atoi(att->value); + else if (!strcmp(att->name, "white_point_y")) mdcv.white_point_y = atoi(att->value); + else if (!strcmp(att->name, "max_display_mastering_luminance")) mdcv.max_display_mastering_luminance = atoi(att->value); + else if (!strcmp(att->name, "min_display_mastering_luminance")) mdcv.min_display_mastering_luminance = atoi(att->value); + else fprintf(stderr, "HDR XML: ignoring box \"%s\" attribute \"%s\"\n", box->name, att->name); + } + } else if (!strcmp(box->name, "clli")) { + k = 0; + while ((att = gf_list_enum(box->attributes, &k))) { + if (!strcmp(att->name, "max_content_light_level")) clli.max_content_light_level = atoi(att->value); + else if (!strcmp(att->name, "max_pic_average_light_level")) clli.max_pic_average_light_level = atoi(att->value); + else fprintf(stderr, "HDR XML: ignoring box \"%s\" attribute \"%s\"\n", box->name, att->name); + } + } else { + fprintf(stderr, "HDR XML: ignoring box element \"%s\"\n", box->name); + continue; + } + } + + e = gf_isom_set_high_dynamic_range_info(movie, id, 1, &mdcv, &clli); + if (e) { + fprintf(stderr, "HDR XML: error in gf_isom_set_high_dynamic_range_info()\n"); + break; + } + } + + gf_xml_dom_del(parser); + return e; +} + #else GF_ISOFile *package_file(char *file_name, char *fcc, const char *tmpdir, Bool make_wgt) { fprintf(stderr, "XML Not supported in this build of GPAC - cannot package file\n"); return NULL; } + +GF_Err parse_high_dynamc_range_xml_desc(GF_ISOFile* movie, char* file_name) +{ + fprintf(stderr, "XML Not supported in this build of GPAC - cannot process HDR parameter file\n"); + return GF_OK; +} #endif //#ifndef GPAC_DISABLE_CORE_TOOLS diff --git a/applications/mp4box/live.c b/applications/mp4box/live.c index da512be..06153b1 100644 --- a/applications/mp4box/live.c +++ b/applications/mp4box/live.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * Authors: Jean Le Feuvre @@ -26,23 +26,17 @@ */ -#include -#include +#include "mp4box.h" #ifndef GPAC_DISABLE_SENG #include #endif #ifndef GPAC_DISABLE_STREAMING -#include #include #endif #include -#ifndef GPAC_DISABLE_STREAMING -#include -#endif - #if defined(GPAC_DISABLE_ISOM) || defined(GPAC_DISABLE_ISOM_WRITE) #error "Cannot compile MP4Box if GPAC is not built with ISO File Format support" @@ -51,164 +45,16 @@ #if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG) -void PrintStreamerUsage() -{ - fprintf(stderr, "File Streamer Options\n" - "\n" - "MP4Box can stream ISO files to RTP. The streamer currently doesn't support\n" - "data carrouselling and will therefore not handle BIFS and OD streams properly.\n" - "\n" - "-rtp enables streamer\n" - "-run-for=T runs for T seconds of the media then exits\n" - "-noloop disables looping when streaming\n" - "-mpeg4 forces MPEG-4 ES Generic for all RTP streams\n" - "-dst=IP IP destination (uni/multi-cast). Default: 127.0.0.1\n" - "-port=PORT output port of the first stream. Default: 7000\n" - "-mtu=MTU path MTU for RTP packets. Default is 1450 bytes\n" - "-ifce=IFCE IP address of the physical interface to use. Default: NULL (ANY)\n" - "-ttl=TTL time to live for multicast packets. Default: 1\n" - "-sdp=Name file name of the generated SDP. Default: \"session.sdp\"\n" - "\n" - ); -} - -static void on_logs(void *cbk, GF_LOG_Level ll, GF_LOG_Tool lm, const char *fmt, va_list list) -{ - FILE *logs = (FILE*)cbk; - vfprintf(logs, fmt, list); - fflush(logs); -} - -int stream_file_rtp(int argc, char **argv) -{ - GF_ISOMRTPStreamer *file_streamer; - char *sdp_file = "session.sdp"; - char *ip_dest = "127.0.0.1"; - char *ifce_addr = NULL; - char *inName = NULL; - char *logs=NULL; - FILE *logfile=NULL; - u16 port = 7000; - u32 ttl = 1; - Bool loop = GF_TRUE; - GF_MemTrackerType mem_track = GF_MemTrackerNone; - Bool force_mpeg4 = GF_FALSE; - u32 path_mtu = 1450; - Double run_for = -1.0; - u32 i; - - for (i = 1; i < (u32) argc ; i++) { - char *arg = argv[i]; - - if (arg[0] != '-') { - if (inName) { - fprintf(stderr, "Error - 2 input names specified, please check usage\n"); - return 1; - } - inName = arg; - } - else if (!stricmp(arg, "-noloop")) loop = GF_FALSE; - else if (!stricmp(arg, "-mpeg4")) force_mpeg4 = GF_TRUE; - else if (!strnicmp(arg, "-port=", 6)) port = atoi(arg+6); - else if (!strnicmp(arg, "-mtu=", 5)) path_mtu = atoi(arg+5); - else if (!strnicmp(arg, "-dst=", 5)) ip_dest = arg+5; - else if (!strnicmp(arg, "-ttl=", 5)) ttl = atoi(arg+5); - else if (!strnicmp(arg, "-ifce=", 6)) ifce_addr = arg+6; - else if (!strnicmp(arg, "-sdp=", 5)) sdp_file = arg+5; - else if (!stricmp(arg, "-mem-track")) mem_track = GF_MemTrackerSimple; - else if (!stricmp(arg, "-mem-track-stack")) mem_track = GF_MemTrackerBackTrace; - else if (!strnicmp(arg, "-logs=", 6)) logs = arg+6; - else if (!strnicmp(arg, "-lf=", 4)) logfile = gf_fopen(arg+4, "wt"); - else if (!strnicmp(arg, "-run-for=", 9)) run_for = atof(arg+9); - } - gf_sys_init(mem_track); - if (logs) - gf_log_set_tools_levels(logs); - else - gf_log_set_tool_level(GF_LOG_RTP, GF_LOG_INFO); //set to debug to have packet list - if (logfile) { - gf_log_set_callback(logfile, on_logs); - } - if (!gf_isom_probe_file(inName)) { - fprintf(stderr, "File %s is not a valid ISO Media file and cannot be streamed\n", inName); - if (logfile) gf_fclose(logfile); - gf_sys_close(); - return 1; - } - file_streamer = gf_isom_streamer_new(inName, ip_dest, port, loop, force_mpeg4, path_mtu, ttl, ifce_addr); - if (!file_streamer) { - fprintf(stderr, "Cannot create file streamer\n"); - } else { - Bool run = GF_TRUE; - u32 check = 50; - fprintf(stderr, "Starting streaming %s to %s:%d\n", inName, ip_dest, port); - gf_isom_streamer_write_sdp(file_streamer, sdp_file); - - if (run_for==0) run=GF_FALSE; - - while (run) { - gf_isom_streamer_send_next_packet(file_streamer, 0, 0); - check--; - if (!check) { - if (gf_prompt_has_input()) { - char c = (char) gf_prompt_get_char(); - if (c=='q') break; - } - check = 50; - } - if ((run_for > 0) && (run_for < gf_isom_streamer_get_current_time(file_streamer)) ) - break; - } - gf_isom_streamer_del(file_streamer); - } - if (logfile) gf_fclose(logfile); - gf_sys_close(); - return 0; -} - - -void PrintLiveUsage() -{ - fprintf(stderr, - - "Live scene encoder options:\n" - "-dst=IP destination IP - default: NULL\n" - "-port=PORT destination port - default: 7000\n" - "-mtu=MTU path MTU for RTP packets. Default is 1450 bytes\n" - "-ifce=IFCE IP address of the physical interface to use. Default: NULL(ANY)\n" - "-ttl=TTL time to live for multicast packets. Default: 1\n" - "-sdp=Name ouput SDP file - default: session.sdp\n" - "\n" - "-dims turns on DIMS mode for SVG input - default: off\n" - "-no-rap disabled RAP sending - this also disables carousel generation. Default: off\n" - "-src=file source of updates - default: null\n" - "-rap=time duration in ms of base carousel - default: 0 (off)\n" - " you can specify the RAP period of a single ESID (not in DIMS):\n" - " -rap=ESID=X:time\n" - "\n" - "Runtime options:\n" - "q: quits\n" - "u: inputs some commands to be sent\n" - "U: same as u but signals the updates as critical\n" - "e: inputs some commands to be sent without being aggregated\n" - "E: same as e but signals the updates as critical\n" - "f: forces RAP sending\n" - "F: forces RAP regeneration and sending\n" - "p: dumps current scene\n" - "\n" - "GPAC version: " GPAC_FULL_VERSION "\n" - ""); -} typedef struct { GF_RTPStreamer *rtp; Bool manual_rtcp; u16 ESID; - char *carousel_data; + u8 *carousel_data; u32 carousel_size, carousel_alloc; u32 last_carousel_time; u64 carousel_ts, time_at_carousel_store; @@ -265,7 +111,7 @@ RTPChannel *next_carousel(LiveSession *sess, u32 *timeout) } -static void live_session_callback(void *calling_object, u16 ESID, char *data, u32 size, u64 ts) +static void live_session_callback(void *calling_object, u16 ESID, u8 *data, u32 size, u64 ts) { LiveSession *livesess = (LiveSession *) calling_object; RTPChannel *rtpch; @@ -310,7 +156,7 @@ static void live_session_callback(void *calling_object, u16 ESID, char *data, u3 static void live_session_send_carousel(LiveSession *livesess, RTPChannel *ch) { u32 now = gf_sys_clock(); - u64 ts=0; + u64 ts; if (ch) { if (ch->carousel_size) { ts = ch->carousel_ts + ch->timescale * ( (ch->adjust_carousel_time ? (u64)gf_sys_clock() : ch->time_at_carousel_store) - ch->init_time + ch->ts_delta)/1000; @@ -346,7 +192,7 @@ static void live_session_send_carousel(LiveSession *livesess, RTPChannel *ch) } } -static void live_session_setup(LiveSession *livesess, char *ip, u16 port, u32 path_mtu, u32 ttl, char *ifce_addr, char *sdp_name) +static Bool live_session_setup(LiveSession *livesess, char *ip, u16 port, u32 path_mtu, u32 ttl, char *ifce_addr, char *sdp_name) { RTPChannel *rtpch; u32 count = gf_seng_get_stream_count(livesess->seng); @@ -355,10 +201,16 @@ static void live_session_setup(LiveSession *livesess, char *ip, u16 port, u32 pa char *sdp = gf_rtp_streamer_format_sdp_header("GPACSceneStreamer", ip, NULL, iod64); if (iod64) gf_free(iod64); +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + GF_Descriptor *desc = gf_seng_get_iod(livesess->seng); + if (desc) gf_odf_desc_del(desc); + } +#endif for (i=0; iseng, i, &ESID, &config, &config_len, &st, &oti, &ts); @@ -373,9 +225,9 @@ static void live_session_setup(LiveSession *livesess, char *ip, u16 port, u32 pa switch (st) { case GF_STREAM_OD: case GF_STREAM_SCENE: - rtpch->rtp = gf_rtp_streamer_new_extended(st, oti, ts, ip, port, path_mtu, ttl, ifce_addr, - GP_RTP_PCK_SYSTEMS_CAROUSEL, (char *) config, config_len, - 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4); + rtpch->rtp = gf_rtp_streamer_new(st, oti, ts, ip, port, path_mtu, ttl, ifce_addr, + GP_RTP_PCK_SYSTEMS_CAROUSEL, config, config_len, + 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, GF_FALSE); if (rtpch->rtp) { gf_rtp_streamer_disable_auto_rtcp(rtpch->rtp); @@ -383,18 +235,20 @@ static void live_session_setup(LiveSession *livesess, char *ip, u16 port, u32 pa } break; default: - rtpch->rtp = gf_rtp_streamer_new(st, oti, ts, ip, port, path_mtu, ttl, ifce_addr, GP_RTP_PCK_SIGNAL_RAP, (char *) config, config_len); + rtpch->rtp = gf_rtp_streamer_new(st, oti, ts, ip, port, path_mtu, ttl, ifce_addr, GP_RTP_PCK_SIGNAL_RAP, config, config_len, 96, 0, 0, GF_FALSE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, GF_FALSE); break; } rtpch->ESID = ESID; rtpch->adjust_carousel_time = 1; gf_list_add(livesess->streams, rtpch); - gf_rtp_streamer_append_sdp(rtpch->rtp, ESID, (char *) config, config_len, NULL, &sdp); + if (!rtpch->rtp) + return GF_FALSE; + + gf_rtp_streamer_append_sdp(rtpch->rtp, ESID, config, config_len, NULL, &sdp); /*fetch initial config of the broadcast*/ gf_seng_get_stream_carousel_info(livesess->seng, ESID, &rtpch->carousel_period, &rtpch->aggregate_on_stream); - port += 2; } if (sdp) { @@ -403,6 +257,7 @@ static void live_session_setup(LiveSession *livesess, char *ip, u16 port, u32 pa gf_fclose(out); gf_free(sdp); } + return GF_TRUE; } void live_session_shutdown(LiveSession *livesess) @@ -475,7 +330,7 @@ int live_session(int argc, char **argv) u32 i; char *filename = NULL; char *dst = NULL; - char *ifce_addr = NULL; + const char *ifce_addr = NULL; char *sdp_name = "session.sdp"; u16 dst_port = 7000; u32 load_type=0; @@ -483,7 +338,7 @@ int live_session(int argc, char **argv) u32 ttl = 1; u32 path_mtu = 1450; s32 next_time; - u64 last_src_modif, mod_time; + u64 last_src_modif, mod_time, runfor=0, start_time; char *src_name = NULL; Bool run, has_carousel, no_rap; Bool udp = 0; @@ -502,12 +357,15 @@ int live_session(int argc, char **argv) aggregate_au = 1; es_id = 0; no_rap = 0; - gf_sys_init(GF_MemTrackerNone); + gf_sys_init(GF_MemTrackerNone, NULL); memset(&livesess, 0, sizeof(LiveSession)); gf_log_set_tool_level(GF_LOG_ALL, GF_LOG_INFO); + gf_sys_set_args(argc, (const char **) argv); + + for (i=1; i<(u32) argc; i++) { char *arg = argv[i]; if (arg[0] != '-') filename = arg; @@ -516,7 +374,6 @@ int live_session(int argc, char **argv) else if (!strnicmp(arg, "-sdp=", 5)) sdp_name = arg+5; else if (!strnicmp(arg, "-mtu=", 5)) path_mtu = atoi(arg+5); else if (!strnicmp(arg, "-ttl=", 5)) ttl = atoi(arg+5); - else if (!strnicmp(arg, "-ifce=", 6)) ifce_addr = arg+6; else if (!strnicmp(arg, "-no-rap", 7)) no_rap = 1; else if (!strnicmp(arg, "-dims", 5)) load_type = GF_SM_LOAD_DIMS; else if (!strnicmp(arg, "-src=", 5)) src_name = arg+5; @@ -528,12 +385,17 @@ int live_session(int argc, char **argv) sk_port = atoi(arg+5); udp = 0; } + else if (!stricmp(arg, "-run-for")) { + runfor = 1 + 1000 * atoi(argv[i+1]); + i++; + } } if (!filename) { fprintf(stderr, "Missing filename\n"); PrintLiveUsage(); return 1; } + ifce_addr = gf_opts_get_key("core", "ifce"); if (dst_port && dst) livesess.streams = gf_list_new(); @@ -542,7 +404,16 @@ int live_session(int argc, char **argv) fprintf(stderr, "Cannot create scene engine\n"); return 1; } - if (livesess.streams) live_session_setup(&livesess, dst, dst_port, path_mtu, ttl, ifce_addr, sdp_name); + if (livesess.streams) { + Bool res = live_session_setup(&livesess, dst, dst_port, path_mtu, ttl, (char *) ifce_addr, sdp_name); + if (!res) { + live_session_shutdown(&livesess); + if (update_buffer) gf_free(update_buffer); + if (sk) gf_sk_del(sk); + gf_sys_close(); + return e ? 1 : 0; + } + } has_carousel = 0; last_src_modif = src_name ? gf_file_modification_time(src_name) : 0; @@ -563,8 +434,7 @@ int live_session(int argc, char **argv) for (i=0; i<(u32) argc; i++) { char *arg = argv[i]; if (!strnicmp(arg, "-rap=", 5)) { - u32 period, id, j; - RTPChannel *ch; + u32 id, j; period = id = 0; if (strchr(arg, ':')) { sscanf(arg, "-rap=ESID=%u:%u", &id, &period); @@ -604,6 +474,21 @@ int live_session(int argc, char **argv) live_session_send_carousel(&livesess, NULL); + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + aggregate_on_stream = (u16) -1; + adjust_carousel_time = force_rap = discard_pending = signal_rap = signal_critical = 0; + aggregate_au = version_inc = 1; + period = -1; + ts_delta = 0; + es_id = 0; + + set_broadcast_params(&livesess, es_id, period, ts_delta, aggregate_on_stream, adjust_carousel_time, force_rap, aggregate_au, discard_pending, signal_rap, signal_critical, version_inc); + } +#endif + + start_time = gf_sys_clock_high_res(); check = 10; run = 1; while (run) { @@ -617,14 +502,12 @@ int live_session(int argc, char **argv) run=0; break; case 'U': - livesess.critical = 1; case 'u': { - GF_Err e; char szCom[8192]; fprintf(stderr, "Enter command to send:\n"); szCom[0] = 0; - if (1 > scanf("%[^\t\n]", szCom)) { + if (1 > scanf("%8191[^\t\n]", szCom)) { fprintf(stderr, "No command entered properly, aborting.\n"); break; } @@ -634,19 +517,17 @@ int live_session(int argc, char **argv) if (e) fprintf(stderr, "Processing command failed: %s\n", gf_error_to_string(e)); e = gf_seng_aggregate_context(livesess.seng, 0); if (e) fprintf(stderr, "Aggregating context failed: %s\n", gf_error_to_string(e)); - livesess.critical = 0; + livesess.critical = (c=='U') ? 1 : 0; update_context = 1; } break; case 'E': - livesess.critical = 1; case 'e': { - GF_Err e; char szCom[8192]; fprintf(stderr, "Enter command to send:\n"); szCom[0] = 0; - if (1 > scanf("%[^\t\n]", szCom)) { + if (1 > scanf("%8191[^\t\n]", szCom)) { printf("No command entered properly, aborting.\n"); break; } @@ -654,7 +535,7 @@ int live_session(int argc, char **argv) while (getchar()!='\n') {} e = gf_seng_encode_from_string(livesess.seng, 0, 1, szCom, live_session_callback); if (e) fprintf(stderr, "Processing command failed: %s\n", gf_error_to_string(e)); - livesess.critical = 0; + livesess.critical = (c=='E') ? 1 : 0; e = gf_seng_aggregate_context(livesess.seng, 0); if (e) fprintf(stderr, "Aggregating context failed: %s\n", gf_error_to_string(e)); @@ -665,7 +546,7 @@ int live_session(int argc, char **argv) { char rad[GF_MAX_PATH]; fprintf(stderr, "Enter output file name - \"std\" for stderr: "); - if (1 > scanf("%s", rad)) { + if (1 > scanf("%1023s", rad)) { fprintf(stderr, "No ouput file name entered, aborting.\n"); break; } @@ -696,7 +577,7 @@ int live_session(int argc, char **argv) if (!srcf) continue; /*checks if we have a broadcast config*/ - if (!fgets(flag_buf, 200, srcf)) + if (!gf_fgets(flag_buf, 200, srcf)) flag_buf[0] = '\0'; gf_fclose(srcf); @@ -712,7 +593,7 @@ int live_session(int argc, char **argv) if (flag) { flag += strlen("gpac_broadcast_config "); /*move to next word*/ - while (flag && (flag[0]==' ')) flag++; + while (flag[0]==' ') flag++; while (1) { char *sep = strchr(flag, ' '); @@ -750,13 +631,13 @@ int live_session(int argc, char **argv) /*process updates from socket source*/ if (sk) { - char buffer[2049]; + u8 buffer[2049]; u32 bytes_read; u32 update_length; u32 bytes_received; - e = gf_sk_receive(sk, buffer, 2048, 0, &bytes_read); + e = gf_sk_receive(sk, buffer, 2048, &bytes_read); if (e == GF_OK) { u32 hdr_length = 0; u8 cmd_type = buffer[0]; @@ -801,7 +682,7 @@ int live_session(int argc, char **argv) bytes_received = bytes_read-hdr_length; } while (bytes_received start_time+runfor)) + break; + + if (!has_carousel) { gf_sleep(10); continue; @@ -852,6 +737,16 @@ int live_session(int argc, char **argv) live_session_send_carousel(&livesess, ch); } +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { +/* gf_seng_save_context(livesess.seng, NULL); + gf_seng_aggregate_context + gf_seng_encode_from_string + gf_seng_encode_from_file +*/ + } +#endif + exit: live_session_shutdown(&livesess); if (update_buffer) gf_free(update_buffer); @@ -864,98 +759,3 @@ exit: #endif /*!defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG)*/ #endif /*defined(GPAC_DISABLE_ISOM) || defined(GPAC_DISABLE_ISOM_WRITE)*/ - -#ifndef GPAC_DISABLE_MPEG2TS - -u32 grab_live_m2ts(const char *grab_m2ts, const char *grab_ifce, const char *outName) -{ - char data[0x80000]; - u32 check = 50; - u64 nb_pck; - Bool first_run, is_rtp; - FILE *output; -#ifndef GPAC_DISABLE_STREAMING - u16 seq_num; - GF_RTPReorder *ch = NULL; -#endif - GF_Socket *sock; - GF_Err e = gf_m2ts_get_socket(grab_m2ts, grab_ifce, GF_M2TS_UDP_BUFFER_SIZE, &sock); - - if (e) { - fprintf(stderr, "Cannot open %s: %s\n", grab_m2ts, gf_error_to_string(e)); - return 1; - } - output = gf_fopen(outName, "wb"); - if (!output) { - fprintf(stderr, "Cannot open %s: check path and rights\n", outName); - gf_sk_del(sock); - return 1; - } - - fprintf(stderr, "Dumping %s stream to %s - press q to abort\n", grab_m2ts, outName); - - first_run = 1; - is_rtp = 0; - while (1) { - u32 size = 0; - - check--; - if (!check) { - if (gf_prompt_has_input()) { - char c = (char) gf_prompt_get_char(); - if (c=='q') break; - } - check = 50; - } - - /*m2ts chunks by chunks*/ - e = gf_sk_receive(sock, data, 0x40000, 0, &size); - if (!size || e) { - gf_sleep(1); - continue; - } - if (first_run) { - first_run = 0; - /*FIXME: we assume only simple RTP packaging (no CSRC nor extensions)*/ - if ((data[0] != 0x47) && ((data[1] & 0x7F) == 33) ) { - is_rtp = 1; -#ifndef GPAC_DISABLE_STREAMING - ch = gf_rtp_reorderer_new(100, 500); -#endif - } - } - /*process chunk*/ - if (is_rtp) { -#ifndef GPAC_DISABLE_STREAMING - char *pck; - seq_num = ((data[2] << 8) & 0xFF00) | (data[3] & 0xFF); - gf_rtp_reorderer_add(ch, (void *) data, size, seq_num); - - pck = (char *) gf_rtp_reorderer_get(ch, &size); - if (pck) { - fwrite(pck+12, size-12, 1, output); - gf_free(pck); - } -#else - fwrite(data+12, size-12, 1, output); -#endif - } else { - fwrite(data, size, 1, output); - } - } - nb_pck = gf_ftell(output); - nb_pck /= 188; - fprintf(stderr, "Captured "LLU" TS packets\n", nb_pck ); - gf_fclose(output); - gf_sk_del(sock); - -#ifndef GPAC_DISABLE_STREAMING - if (ch) - gf_rtp_reorderer_del(ch); -#endif - return 0; -} - -#endif /* GPAC_DISABLE_MPEG2TS */ - - diff --git a/applications/mp4box/main.c b/applications/mp4box/main.c index 5611fe5..7871248 100644 --- a/applications/mp4box/main.c +++ b/applications/mp4box/main.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / mp4box application @@ -24,13 +24,7 @@ */ -#include -#include -#include - -#ifndef GPAC_DISABLE_SMGR -#include -#endif +#include "mp4box.h" #ifdef GPAC_DISABLE_ISOM @@ -44,108 +38,28 @@ #endif #include +#include /*RTP packetizer flags*/ #ifndef GPAC_DISABLE_STREAMING #include #endif -#ifndef GPAC_DISABLE_MCRYPT -#include +#ifndef GPAC_DISABLE_CRYPTO +#include #endif #include +#include -#include - -#include +#include #define BUFFSIZE 8192 #define DEFAULT_INTERLEAVING_IN_SEC 0.5 -/*in fileimport.c*/ - -#ifndef GPAC_DISABLE_MEDIA_IMPORT -void convert_file_info(char *inName, u32 trackID); -#endif - -#ifndef GPAC_DISABLE_ISOM_WRITE - -GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, Double force_fps, u32 frames_per_sample); -GF_Err split_isomedia_file(GF_ISOFile *mp4, Double split_dur, u64 split_size_kb, char *inName, Double interleaving_time, Double chunk_start, Bool adjust_split_end, char *outName, const char *tmpdir); -GF_Err cat_isomedia_file(GF_ISOFile *mp4, char *fileName, u32 import_flags, Double force_fps, u32 frames_per_sample, char *tmp_dir, Bool force_cat, Bool align_timelines, Bool allow_add_in_command); - -#if !defined(GPAC_DISABLE_SCENE_ENCODER) -GF_Err EncodeFile(char *in, GF_ISOFile *mp4, GF_SMEncodeOptions *opts, FILE *logs); -GF_Err EncodeFileChunk(char *chunkFile, char *bifs, char *inputContext, char *outputContext, const char *tmpdir); -#endif - -GF_ISOFile *package_file(char *file_name, char *fcc, const char *tmpdir, Bool make_wgt); - -#endif - -GF_Err dump_isom_cover_art(GF_ISOFile *file, char *inName, Bool is_final_name); -GF_Err dump_isom_chapters(GF_ISOFile *file, char *inName, Bool is_final_name, Bool dump_ogg); -void dump_isom_udta(GF_ISOFile *file, char *inName, Bool is_final_name, u32 dump_udta_type, u32 dump_udta_track); -GF_Err set_file_udta(GF_ISOFile *dest, u32 tracknum, u32 udta_type, char *src, Bool is_box_array); -u32 id3_get_genre_tag(const char *name); - -/*in filedump.c*/ -#ifndef GPAC_DISABLE_SCENE_DUMP -GF_Err dump_isom_scene(char *file, char *inName, Bool is_final_name, GF_SceneDumpFormat dump_mode, Bool do_log); -//void gf_check_isom_files(char *conf_rules, char *inName); -#endif -#ifndef GPAC_DISABLE_SCENE_STATS -void dump_isom_scene_stats(char *file, char *inName, Bool is_final_name, u32 stat_level); -#endif -void PrintNode(const char *name, u32 graph_type); -void PrintBuiltInNodes(u32 graph_type); -void PrintBuiltInBoxes(); - -#ifndef GPAC_DISABLE_ISOM_DUMP -GF_Err dump_isom_xml(GF_ISOFile *file, char *inName, Bool is_final_name, Bool do_track_dump); -#endif - - -#ifndef GPAC_DISABLE_ISOM_HINTING -#ifndef GPAC_DISABLE_ISOM_DUMP -void dump_isom_rtp(GF_ISOFile *file, char *inName, Bool is_final_name); -#endif -void dump_isom_sdp(GF_ISOFile *file, char *inName, Bool is_final_name); -#endif - -void dump_isom_timestamps(GF_ISOFile *file, char *inName, Bool is_final_name); -void dump_isom_nal(GF_ISOFile *file, u32 trackID, char *inName, Bool is_final_name); - -#ifndef GPAC_DISABLE_ISOM_DUMP -void dump_isom_ismacryp(GF_ISOFile *file, char *inName, Bool is_final_name); -void dump_isom_timed_text(GF_ISOFile *file, u32 trackID, char *inName, Bool is_final_name, Bool is_convert, GF_TextDumpType dump_type); -#endif /*GPAC_DISABLE_ISOM_DUMP*/ - - -void DumpTrackInfo(GF_ISOFile *file, u32 trackID, Bool full_dump); -void DumpMovieInfo(GF_ISOFile *file); -void PrintLanguages(); - -#ifndef GPAC_DISABLE_MPEG2TS -void dump_mpeg2_ts(char *mpeg2ts_file, char *pes_out_name, Bool prog_num); -#endif - - -#if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG) -void PrintStreamerUsage(); -int stream_file_rtp(int argc, char **argv); -int live_session(int argc, char **argv); -void PrintLiveUsage(); -#endif - -#if !defined(GPAC_DISABLE_STREAMING) -u32 grab_live_m2ts(const char *grab_m2ts, const char *ifce_name, const char *outName); -#endif int mp4boxTerminal(int argc, char **argv); -u32 quiet = 0; Bool dvbhdemux = GF_FALSE; Bool keep_sys_tracks = GF_FALSE; @@ -154,7 +68,8 @@ Bool keep_sys_tracks = GF_FALSE; u32 swf_flags = 0; Float swf_flatten_angle = 0; s32 laser_resolution = 0; - +static FILE *helpout = NULL; +static u32 help_flags = 0; typedef struct { u32 code; @@ -189,684 +104,1061 @@ u32 nb_itunes_tags = sizeof(itags) / sizeof(itunes_tag); void PrintVersion() { - fprintf(stderr, "MP4Box - GPAC version " GPAC_FULL_VERSION "\n" - "GPAC Copyright (c) Telecom ParisTech 2000-2012\n" + fprintf(stderr, "MP4Box - GPAC version %s\n" + "%s\n" "GPAC Configuration: " GPAC_CONFIGURATION "\n" - "Features: %s\n", gpac_features()); + "Features: %s %s\n", gf_gpac_version(), gf_gpac_copyright_cite(), gf_sys_features(GF_FALSE), gf_sys_features(GF_TRUE)); } -void PrintGeneralUsage() +GF_GPACArg m4b_gen_args[] = { - fprintf(stderr, "General Options:\n" #ifdef GPAC_MEMORY_TRACKING - " -mem-track: enables memory tracker\n" - " -mem-track-stack: enables memory tracker with stack dumping\n" -#endif - " -strict-error exits after the first error is reported\n" - " -inter time_in_ms interleaves file data (track chunks of time_in_ms)\n" - " * Note 1: Interleaving is 0.5s by default\n" - " * Note 2: Performs drift checking across tracks\n" - " * Note 3: a value of 0 disables interleaving\n" - " -old-inter time same as -inter but doesn't perform drift checking\n" - " -tight performs tight interleaving (sample based) of the file\n" - " * Note: reduces disk seek but increases file size\n" - " -flat stores file with all media data first, non-interleaved\n" - " -frag time_in_ms fragments file (track fragments of time_in_ms)\n" - " * Note: Always disables interleaving\n" - " -out filename specifies output file name\n" - " * Note: By default input (MP4,3GP) file is overwritten\n" - " -tmp dirname specifies directory for temporary file creation\n" - " * Note: Default temp dir is OS-dependent\n" - " -for-test disables all creation/modif dates and GPAC versions in files\n" - " -co64 forces usage of 64-bit chunk offsets for ISOBMF files\n" - " -write-buffer SIZE specifies write buffer in bytes for ISOBMF files\n" - " -no-sys removes all MPEG-4 Systems info except IOD (profiles)\n" - " * Note: Set by default whith '-add' and '-cat'\n" - " -no-iod removes InitialObjectDescriptor from file\n" - " -isma rewrites the file as an ISMA 1.0 AV file\n" - " -ismax same as \'-isma\' and removes all clock references\n" - " -3gp rewrites as 3GPP(2) file (no more MPEG-4 Systems Info)\n" - " * Note 1: some tracks may be removed in the process\n" - " * Note 2: always on for *.3gp *.3g2 *.3gpp\n" - " -ipod rewrites the file for iPod\n" - " -psp rewrites the file for PSP devices\n" - " -brand ABCD[:v] sets major brand of file, with optional version\n" - " -ab ABCD adds given brand to file's alternate brand list\n" - " -rb ABCD removes given brand from file's alternate brand list\n" - " -cprt string adds copyright string to movie\n" - " -chap file adds chapter information contained in file\n" - " -set-track-id id1:id2 changes the id of a track from id1 to id2\n" - " -swap-track-id id1:id2 swaps the IDs of the identified tracks\n" - " -rem trackID removes track from file\n" - " -rap trackID removes all non-RAP samples from track\n" - " -enable trackID enables track\n" - " -disable trackID disables track\n" - " -new forces creation of a new destination file\n" - " -timescale VAL sets movie timescale to VAL ticks per second (default is 600)\n" - " -lang [tkID=]LAN sets track language. LAN is the BCP-47 code (eng, en-UK, ...)\n" - " -delay tkID=TIME sets track start delay in ms\n" - " -par tkID=PAR sets visual track pixel aspect ratio (PAR=N:D or \"none\")\n" - " -name tkID=NAME sets track handler name\n" - " * NAME can indicate a UTF-8 file (\"file://file name\"\n" - " -itags tag1[:tag2] sets iTunes tags to file - more info: MP4Box -tag-list\n" - " -split time_sec splits in files of time_sec max duration, starting each file at RAP.\n" - " * Note: this removes all MPEG-4 Systems media\n" - " -split-size size splits in files of max filesize kB. same as -splits.\n" - " * Note: this removes all MPEG-4 Systems media\n" - " -split-rap splits in files beginning at each RAP. same as -splitr.\n" - " * Note: this removes all MPEG-4 Systems media\n" - " -split-chunk S:E extracts a new file from Start to End (in seconds). same as -splitx\n" - " * Note: this removes all MPEG-4 Systems media\n" - " -splitz S:E same as -split-chunk, but adjust the end time to be before the last RAP sample\n" - " * Note: this removes all MPEG-4 Systems media\n" - " -group-add fmt creates a new grouping information in the file. Format is\n" - " a colon-separated list of following options:\n" - " refTrack=ID: ID of the track used as a group reference.\n" - " If not set, the track will belong to the same group as the previous trackID specified.\n" - " If 0 or no previous track specified, a new alternate group will be created\n" - " switchID=ID: ID of the switch group to create.\n" - " If 0, a new ID will be computed for you\n" - " If <0, disables SwitchGroup\n" - " criteria=string: list of space-separated 4CCs.\n" - " trackID=ID: ID of the track to add to this group.\n" - "\n" - " *WARNING* Options modify state as they are parsed:\n" - " trackID=1:criteria=lang:trackID=2\n" - " is different from:\n" - " criteria=lang:trackID=1:trackID=2\n" - "\n" - " -group-rem-track ID removes track from its group\n" - " -group-rem ID removes the track's group\n" - " -group-clean removes all group information from all tracks\n" - " -group-single puts all tracks in a single group\n" - " -ref id:XXXX:refID adds a reference of type 4CC from track ID to track refID\n" - " -keep-utc keeps UTC timing in the file after edit\n" - " -udta ID:[OPTS] sets udta for given track or movie if ID is 0. OPTS is a colon separated list of:\n" - " type=CODE where code is the 4CC code of the UDTA (not needed for box= option)\n" - " box=FILE where FILE is the location of the udta data, formatted as serialized boxes\n" -#ifndef GPAC_DISABLE_CORE_TOOLS - " box=base64,DATA where DATA is the base64 encoded udta data, formatted as serialized boxes\n" -#endif - " src=FILE where FILE is the location of the udta data (will be stored in a single box of type CODE)\n" -#ifndef GPAC_DISABLE_CORE_TOOLS - " src=base64,DATA where DATA is the base64 encoded udta data (will be stored in a single box of type CODE)\n" + GF_DEF_ARG("mem-track", NULL, "enable memory tracker", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("mem-track-stack", NULL, "enable memory tracker with stack dumping", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), #endif - " If no source is set, UDTA of type CODE will be removed\n" - "\n"); + GF_DEF_ARG("p", NULL, "use indicated profile for the global GPAC config. If not found, config file is created. If a file path is indicated, this will load profile from that file. Otherwise, this will create a directory of the specified name and store new config there. Reserved name `0` means a new profile, not stored to disk. Works using -p=NAME or -p NAME", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("inter", NULL, "interleave file, producing track chunks with given duration in ms. A value of 0 disables interleaving ", "0.5", NULL, GF_ARG_DOUBLE, 0), + GF_DEF_ARG("old-inter", NULL, "same as [-inter]() but wihout drift correction", NULL, NULL, GF_ARG_DOUBLE, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("tight", NULL, "tight interleaving (sample based) of the file. This reduces disk seek operations but increases file size", NULL, NULL, GF_ARG_DOUBLE, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("flat", NULL, "store file with all media data first, non-interleaved. This speeds up writing time when creating new files", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("frag", NULL, "fragment file, producing track fragments of given duration in ms. This disables interleaving", NULL, NULL, GF_ARG_DOUBLE, 0), + GF_DEF_ARG("out", NULL, "specify output file name. By default input file is overwritten", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("tmp", NULL, "specify directory for temporary file creation", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("co64", NULL, "force usage of 64-bit chunk offsets for ISOBMF files", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("new", NULL, "force creation of a new destination file", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("newfs", NULL, "force creation of a new destination file without temp file but interleaving support", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("no-sys", NULL, "remove all MPEG-4 Systems info except IOD, kept for profiles. This is the default when creating regular AV content", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("no-iod", NULL, "remove MPEG-4 InitialObjectDescriptor from file", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("mfra", NULL, "insert movie fragment random offset when fragmenting file (ignored in dash mode)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("isma", NULL, "rewrite the file as an ISMA 1.0 file", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("ismax", NULL, "same as [-isma]() and remove all clock references", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("3gp", NULL, "rewrite as 3GPP(2) file (no more MPEG-4 Systems Info), always enabled if destination file extension is `.3gp`, `.3g2` or `.3gpp`. Some tracks may be removed in the process", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("ipod", NULL, "rewrite the file for iPod/old iTunes", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("psp", NULL, "rewrite the file for PSP devices", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("brand", NULL, "set major brand of file (`ABCD`) or brand with optional version (`ABCD:v`)", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("ab", NULL, "add given brand to file's alternate brand list", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("rb", NULL, "remove given brand to file's alternate brand list", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("cprt", NULL, "add copyright string to file", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("chap", NULL, "set chapter information from given file", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("chapqt", NULL, "set chapter information from given file, using QT signaling for text tracks", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("set-track-id `id1:id2`", NULL, "change id of track with id1 to id2", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("swap-track-id `id1:id2`", NULL, "swap the id between tracks with id1 to id2", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("rem", NULL, "remove given track from file", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("rap", NULL, "remove all non-RAP samples from given track", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("refonly", NULL, "remove all non-reference pictures from given track", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("enable", NULL, "enable given track", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("disable", NULL, "disable given track", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("timescale", NULL, "set movie timescale to given value (ticks per second)", "600", NULL, GF_ARG_INT, 0), + GF_DEF_ARG("lang `[tkID=]LAN`", NULL, "set language. LAN is the BCP-47 code (eng, en-UK, ...). If no track ID is given, sets language to all tracks", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("delay `tkID=TIME`", NULL, "set track start delay in ms", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("par `tkID=PAR`", NULL, "set visual track pixel aspect ratio. PAR is:\n" + " - N:D: set PAR to N:D in track, do not modify the bitstream\n" + " - wN:D: set PAR to N:D in track and try to modify the bitstream\n" + " - none: remove PAR info from track, do not modify the bitstream\n" + " - auto: retrieve PAR info from bitstream and set it in track\n" + " - force: force 1:1 PAR in track, do not modify the bitstream", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("clap `tkID=CLAP`", NULL, "set visual track clean aperture. CLAP is `Wn,Wd,Hn,Hd,HOn,HOd,VOn,VOd` or `none`\n" + "- n, d: numerator, denominator\n" + "- W, H, HO, VO: clap width, clap height, clap horizontal offset, clap vertical offset\n" + , NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("mx `tkID=MX`", NULL, "set track matrix, with MX is M1:M2:M3:M4:M5:M6:M7:M8:M9 in 16.16 fixed point intergers or hexa" + , NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("name `tkID=NAME`", NULL, "set track handler name to NAME (UTF-8 string)", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("itags `tag1[:tag2]`", NULL, "set iTunes tags to file, see [-tag-list]()", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("tag-list", NULL, "print the set of supported iTunes tags", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("split", NULL, "split in files of given max duration", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("split-size", "splits", "split in files of given max size (in kb)", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("split-rap", "splitr", "split in files at each new RAP", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("split-chunk VAL", "splitx", "extract a new file from source. `VAL` can be formated as:\n" + "- `S:E`: `S` (number of seconds) to `E` with `E` a number (in seconds), `end` or `end-N`, N number of seconds before the end\n" + "- `S-E`: start and end dates, each formatted as `HH:MM:SS.ms` or `MM:SS.ms`", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("splitz `S:E`", NULL, "same as -split-chunk, but adjust the end time to be before the last RAP sample", NULL, NULL, GF_ARG_STRING, 0), + + GF_DEF_ARG("group-add", NULL, "create a new grouping information in the file. Format is a colon-separated list of following options:\n" + "- refTrack=ID: ID of the track used as a group reference. If not set, the track will belong to the same group as the " + "previous trackID specified. If 0 or no previous track specified, a new alternate group will be created\n" + "- switchID=ID: ID of the switch group to create. If 0, a new ID will be computed for you. If <0, disables SwitchGroup\n" + "- criteria=string: list of space-separated 4CCs\n" + "- trackID=ID: ID of the track to add to this group\n" + " \n" + "Warning: Options modify state as they are parsed, `trackID=1:criteria=lang:trackID=2` is different from `criteria=lang:trackID=1:trackID=2`" + "\n", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + + GF_DEF_ARG("group-rem-track", NULL, "remove given track from its group", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("group-rem", NULL, "remove the track's group\n", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("group-clean", NULL, "remove all group information from all tracks\n", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("ref `id:XXXX:refID`", NULL, "add a reference of type 4CC from track ID to track refID\n", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("keep-utc", NULL, "keep UTC timing in the file after edit\n", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("udta tkID:[OPTS]", NULL, "set udta for given track or movie if tkID is 0. OPTS is a colon separated list of:\n" + "- type=CODE: 4CC code of the UDTA (not needed for `box=` option)\n" + "- box=FILE: location of the udta data, formatted as serialized boxes\n" + "- box=base64,DATA: base64 encoded udta data, formatted as serialized boxes\n" + "- src=FILE: location of the udta data (will be stored in a single box of type CODE)\n" + "- src=base64,DATA: base64 encoded udta data (will be stored in a single box of type CODE)\n" + "Note: If no source is set, UDTA of type CODE will be removed\n", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("patch [tkID=]FILE", NULL, "apply box patch described in FILE, for given trackID if set\n", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("bo", NULL, "freeze the order of boxes in input file\n", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("init-seg", NULL, "use the given file as an init segment for dumping or for encryption\n", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("zmov", NULL, "compress movie box according to ISOBMFF box compression\n", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + + + {0} +}; + +void PrintGeneralUsage() +{ + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# General Options\n" + "MP4Box is a multimedia packager, with a vast number of functionalities: conversion, splitting, hinting, dumping, DASH-ing, encryption and others.\n" + "MP4Box provides a large set of options, classified by categories (see [-h]()). These options do not follow any particular ordering.\n" + "MP4Box performs in-place rewrite of IsoMedia files (the input file is overwritten). You can change this behaviour by using the [-out]() option.\n" + "MP4Box stores by default the file with 0.5 second interleaving and meta-data (`moov`...) at the beginning, making it suitable for HTTP streaming. This may however takes longer to store the file, use [-flat]() to change this behaviour.\n" + "MP4Box usually generates a temporary file when creating a new IsoMedia file. The location of this temporary file is OS-dependent, and it may happen that the drive/partition the temporary file is created on has not enough space or no write access. In such a case, you can specify a temporary file location with [-tmp]().\n" + "Note: Track operations identify tracks through their ID (usually refered as tkID in the help), not their order.\n" + "Option values:\n" + "Unless specified otherwise, an option of type `integer` expects a trackID value following it." + "An option of type `boolean` expects no following value." + " \n" + "# File Splitting and Concatenation\n" + "MP4Box can split IsoMedia files by size, duration or extract a given part of the file to new IsoMedia file(s). This process requires that at most one track in the input file has non random-access points (typically one video track at most). This process will also ignore all MPEG-4 Systems tracks and hint tracks, but will try to split private media tracks.\n" + "Note: The input file must have enough random access points in order to be split. This may not be the case with some video files where only the very first sample of the video track is a key frame (many 3GP files with H263 video are recorded that way). In order to split such files you will have to use a real video editor and re-encode the content.\n" + "Note: You can add media to a file and split it in the same pass. In this case, the destination file (the one which would be obtained without spliting) will not be stored.\n" + " \n" + "Options:\n" + ); + + while (m4b_gen_args[i].name) { + GF_GPACArg *arg = &m4b_gen_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-gen"); + } } + +GF_GPACArg m4b_dash_args[] = +{ + GF_DEF_ARG("mpd", NULL, "convert given HLS or smooth manifest (local or remote http) to MPD. \nWarning: This is not compatible with other DASH options and does not convert associated segments", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("dash", "-dash-strict", "create DASH from input files with given segment (subsegment for onDemand profile) duration in ms", NULL, NULL, GF_ARG_DOUBLE, 0), + GF_DEF_ARG("dash-live", NULL, "generate a live DASH session using the given segment duration in ms; using `-dash-live=F`will also write the live context to `F`. MP4Box will run the live session until `q` is pressed or a fatal error occurs", NULL, NULL, GF_ARG_DOUBLE, 0), + GF_DEF_ARG("ddbg-live", NULL, "same as [-dash-live]() without time regulation for debug purposes", NULL, NULL, GF_ARG_DOUBLE, 0), + GF_DEF_ARG("frag", NULL, "specify the fragment duration in ms. If not set, this is the DASH duration (one fragment per segment)", NULL, NULL, GF_ARG_DOUBLE, 0), + GF_DEF_ARG("out", NULL, "specify the output MPD file name", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("tmp", NULL, "specify directory for temporary file creation", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("profile", NULL, "specify the target DASH profile, and set default options to ensure conformance to the desired profile. Default profile is `full` in static mode, `live` in dynamic mode", NULL, "onDemand|live|main|simple|full|hbbtv1.5:live|dashavc264:live|dashavc264:onDemand", GF_ARG_STRING, 0), + GF_DEF_ARG("profile-ext", NULL, "specify a list of profile extensions, as used by DASH-IF and DVB. The string will be colon-concatenated with the profile used", NULL, NULL, GF_ARG_STRING, 0), + + GF_DEF_ARG("rap", NULL, "ensure that segments begin with random access points, segment durations might vary depending on the source encoding", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("frag-rap", NULL, "ensure that all fragments begin with random access points (duration might vary depending on the source encoding)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("segment-name", NULL, "set the segment name for generated segments. If not set (default), segments are concatenated in output file except in `live` profile where `dash_%%s`. Supported replacement strings are:\n" + "- $Number[%%0Nd]$ is replaced by the segment number, possibly prefixed with 0\n" + "- $RepresentationID$ is replaced by representation name\n" + "- $Time$ is replaced by segment start time\n" + "- $Bandwidth$ is replaced by representation bandwidth\n" + "- $Init=NAME$ is replaced by NAME for init segment, ignored otherwise\n" + "- $Index=NAME$ is replaced by NAME for index segments, ignored otherwise\n" + "- $Path=PATH$ is replaced by PATH when creating segments, ignored otherwise\n" + "- $Segment=NAME$ is replaced by NAME for media segments, ignored for init segments", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("segment-ext", NULL, "set the segment extension, `null` means no extension", "m4s", NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("init-segment-ext", NULL, "set the segment extension for init, index and bitstream switching segments, `null` means no extension\n", "mp4", NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("segment-timeline", NULL, "use `SegmentTimeline` when generating segments", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("segment-marker `MARK`", NULL, "add a box of type `MARK` (4CC) at the end of each DASH segment", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("insert-utc", NULL, "insert UTC clock at the beginning of each ISOBMF segment", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("base-url", NULL, "set Base url at MPD level. Can be used several times. \nWarning: this does not modify generated files location", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("mpd-title", NULL, "set MPD title", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("mpd-source", NULL, "set MPD source", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("mpd-info-url", NULL, "set MPD info url", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("cprt", NULL, "add copyright string to MPD", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("dash-ctx", NULL, "store/restore DASH timing from indicated file", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("dynamic", NULL, "use dynamic MPD type instead of static", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("last-dynamic", NULL, "same as [-dynamic]() but close the period (insert lmsg brand if needed and update duration)", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("mpd-duration", NULL, "set the duration in second of a live session (if `0`, you must use [-mpd-refresh]())", "0", NULL, GF_ARG_DOUBLE, 0), + GF_DEF_ARG("mpd-refresh", NULL, "specify MPD update time in seconds", NULL, NULL, GF_ARG_DOUBLE, 0), + GF_DEF_ARG("time-shift", NULL, "specify MPD time shift buffer depth in seconds, `-1` to keep all files)", NULL, NULL, GF_ARG_DOUBLE, 0), + GF_DEF_ARG("subdur", NULL, "specify maximum duration in ms of the input file to be dashed in LIVE or context mode. This does not change the segment duration, but stops dashing once segments produced exceeded the duration. If there is not enough samples to finish a segment, data is looped unless [-no-loop]() is used which triggers a period end", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("run-for", NULL, "run for given ms the dash-live session then exits", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("min-buffer", NULL, "specify MPD min buffer time in ms", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("ast-offset", NULL, "specify MPD AvailabilityStartTime offset in ms if positive, or availabilityTimeOffset of each representation if negative", "0", NULL, GF_ARG_INT, 0), + GF_DEF_ARG("dash-scale", NULL, "specify that timing for [-dash]() and [-frag]() are expressed in given timexale (units per seconds)", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("mem-frags", NULL, "fragmentation happens in memory rather than on disk before flushing to disk", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("pssh", NULL, "set pssh store mode\n" + "- v: initial movie\n" + "- f: movie fragments\n" + "- m: MPD\n" + "- mv, vm: in initial movie and MPD\n" + "- mf, fm: in movie fragments and MPD", NULL, "v|f|m|mv|vm|mf|fm", GF_ARG_INT, 0), + GF_DEF_ARG("sample-groups-traf", NULL, "store sample group descriptions in traf (duplicated for each traf). If not set, sample group descriptions are stored in the initial movie", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("mvex-after-traks", NULL, "store `mvex` box after `trak` boxes within the moov box. If not set, `mvex` is before", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("sdtp-traf", NULL, "use `sdtp` box in `traf` (Smooth-like)\n" + "- no: do not use sdtp\n" + "- sdtp: use sdtp box to indicate sample dependencies and don't write info in trun sample flags\n" + "- both: use sdtp box to indicate sample dependencies and also write info in trun sample flags\n", NULL, "no|sdtp|both", GF_ARG_INT, 0), + GF_DEF_ARG("no-cache", NULL, "disable file cache for dash inputs", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("no-loop", NULL, "disable looping content in live mode and uses period switch instead", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("hlsc", NULL, "insert UTC in variant playlists for live HLS", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("bound", NULL, "segmentation will always try to split before or at, but never after, the segment boundary", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("closest", NULL, "segmentation will use the closest frame to the segment boundary (before or after)", NULL, NULL, GF_ARG_BOOL, 0), + + GF_DEF_ARG("subsegs-per-sidx", NULL, "set the number of subsegments to be written in each SIDX box\n" + "- 0: a single SIDX box is used per segment\n" + "- -1: no SIDX box is used", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("ssix", NULL, "enable SubsegmentIndexBox describing 2 ranges, first one from moof to end of first I-frame, second one unmapped. This does not work with daisy chaining mode enabled", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("url-template", NULL, "use SegmentTemplate instead of explicit sources in segments. Ignored if segments are stored in the output file", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("daisy-chain", NULL, "use daisy-chain SIDX instead of hierarchical. Ignored if frags/sidx is 0", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("single-segment", NULL, "use a single segment for the whole file (OnDemand profile)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("single-file", NULL, "use a single file for the whole file (default)", "yes", NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("bs-switching", NULL, "set bitstream switching mode\n" + "- inband: use inband param set and a single init segment\n" + "- merge: try to merge param sets in a single sample description, fallback to `no`\n" + "- multi: use several sample description, one per quality\n" + "- no: use one init segment per quality\n" + "- single: to test with single input", "inband", "inband|merge|multi|no|single", GF_ARG_STRING, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("moof-sn", NULL, "set sequence number of first moof to given value", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("tfdt", NULL, "set TFDT of first traf to given value in SCALE units (cf -dash-scale)", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("no-frags-default", NULL, "disable default fragments flags in trex (required by some dash-if profiles and CMAF/smooth streaming compatibility)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("single-traf", NULL, "use a single track fragment per moof (smooth streaming and derived specs may require this)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("tfdt-traf", NULL, "use a tfdt per track fragment (when -single-traf is used)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("dash-ts-prog", NULL, "program_number to be considered in case of an MPTS input file", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("frag-rt", NULL, "when using fragments in live mode, flush fragments according to their timing", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("cp-location", NULL, "set ContentProtection element location\n" + "- as: sets ContentProtection in AdaptationSet element\n" + "- rep: sets ContentProtection in Representation element\n" + "- both: sets ContentProtection in both elements", NULL, "as|rep\both", GF_ARG_STRING, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("start-date", NULL, "for live mode, set start date (as xs:date, eg YYYY-MM-DDTHH:MM:SSZ). Default is current UTC\n" + "Warning: Do not use with multiple periods, nor when DASH duration is not a multiple of GOP size", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), + + GF_DEF_ARG("cues", NULL, "ignore dash duration and segment according to cue times in given XML file (tests/media/dash_cues for examples)", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("strict-cues", NULL, "throw error if something is wrong while parsing cues or applying cue-based segmentation", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + {0} +}; + void PrintDASHUsage() { - fprintf(stderr, "DASH Options:\n" - " -mpd m3u8 converts HLS manifest (local or remote http) to MPD \n" - " Note: not compatible with other DASH options (except -out and -tmp) and does not convert associated segments\n" - " -dash dur enables DASH-ing of the file(s) with a segment duration of DUR ms\n" - " Note: the duration of a fragment (subsegment) is set\n" - " using the -frag switch.\n" - " Note: for onDemand profile, sets duration of a subsegment\n" - " -dash-strict dur enables DASH-ing of the file(s) with a segment duration of DUR ms (old behaviour)\n" - " Note: the duration will be the closest to \'dur\', and will remain constant\n" - " -dash-live[=F] dur generates a live DASH session using dur segment duration, optionally writing live context to F\n" - " MP4Box will run the live session until \'q\' is pressed or a fatal error occurs.\n" - " -ddbg-live[=F] dur same as -dash-live without time regulation for debug purposes.\n" - " -frag time_in_ms Specifies a fragment duration of time_in_ms.\n" - " * Note: By default, this is the DASH duration\n" - " -out filename specifies output MPD file name.\n" - " -tmp dirname specifies directory for temporary file creation\n" - " * Note: Default temp dir is OS-dependent\n" - " -profile NAME specifies the target DASH profile: \"onDemand\",\n" - " \"live\", \"main\", \"simple\", \"full\",\n" - " \"hbbtv1.5:live\", \"dashavc264:live\", \"dashavc264:onDemand\"\n" - " * This will set default option values to ensure conformance to the desired profile\n" - " * Default profile is \"full\" in static mode, \"live\" in dynamic mode\n" - " -profile-ext STRING specifies a list of profile extensions, as used by DASH-IF and DVB.\n" - " The string will be colon-concatenated with the profile used\n" - "\n" - "Input media files to dash can use the following modifiers\n" - " \"#trackID=N\" only uses the track ID N from the source file\n" - " \"#video\" only uses the first video track from the source file\n" - " \"#audio\" only uses the first audio track from the source file\n" - " \":id=NAME\" sets the representation ID to NAME\n" - " \":dur=VALUE\" processes VALUE seconds from the media\n" - " If VALUE is longer than the media duration, the last media duration is lengthen.\n" - " \":period=NAME\" sets the representation's period to NAME. Multiple periods may be used\n" - " period appear in the MPD in the same order as specified with this option\n" - " \":BaseURL=NAME\" sets the BaseURL. Set multiple times for multiple BaseURLs\n" - " \":bandwidth=VALUE\" sets the representation's bandwidth to a given value\n" - " \":period_duration=VALUE\" increases the duration of this period by the given duration in seconds\n" - " only used when no input media is specified (remote period insertion), eg :period=X:xlink=Z:duration=Y.\n" - " \":xlink=VALUE\" sets the xlink value for the period containing this element\n" - " only the xlink declared on the first rep of a period will be used\n" - " \":role=VALUE\" sets the role of this representation (cf DASH spec).\n" - " media with different roles belong to different adaptation sets.\n" - " \":desc_p=VALUE\" adds a descriptor at the Period level. Value must be a properly formatted XML element.\n" - " \":desc_as=VALUE\" adds a descriptor at the AdaptationSet level. Value must be a properly formatted XML element.\n" - " two input files with different values will be in different AdaptationSet elements.\n" - " \":desc_as_c=VALUE\" adds a descriptor at the AdaptationSet level. Value must be a properly formatted XML element.\n" - " value is ignored while creating AdaptationSet elements.\n" - " \":desc_rep=VALUE\" adds a descriptor at the Representation level. Value must be a properly formatted XML element.\n" - " value is ignored while creating AdaptationSet elements.\n" - "\n" - " -rap segments begin with random access points\n" - " Note: segment duration may not be exactly what asked by\n" - " \"-dash\" since encoded video data is not modified\n" - " -frag-rap All fragments begin with random access points\n" - " Note: fragment duration may not be exactly what is asked by\n" - " \"-frag\" since encoded video data is not modified\n" - " -segment-name name sets the segment name for generated segments\n" - " If not set (default), segments are concatenated in output file\n" - " except in \"live\" profile where dash_%%s is used\n" - " -segment-ext name sets the segment extension. Default is m4s, \"null\" means no extension\n" - " -segment-timeline uses SegmentTimeline when generating segments.\n" - " -segment-marker MARK adds a box of type \'MARK\' at the end of each DASH segment. MARK shall be a 4CC identifier\n" - " -insert-utc inserts UTC clock at the begining of each ISOBMF segment\n" - " -base-url string sets Base url at MPD level. Can be used several times.\n" - " -mpd-title string sets MPD title.\n" - " -mpd-source string sets MPD source.\n" - " -mpd-info-url string sets MPD info url.\n" - " -cprt string adds copyright string to MPD\n" - " -dash-ctx FILE stores/restore DASH timing from FILE.\n" - " -dynamic uses dynamic MPD type instead of static.\n" - " -last-dynamic same as dynamic but closes the period (insert lmsg brand if needed and update duration).\n" - " -mpd-duration DUR sets the duration in second of a live session (0 by default). If 0, you must use -mpd-refresh.\n" - " -mpd-refresh TIME specifies MPD update time in seconds (double can be used).\n" - " -time-shift TIME specifies MPD time shift buffer depth in seconds (default 0). Specify -1 to keep all files\n" - " -subdur DUR specifies maximum duration in ms of the input file to be dashed in LIVE or context mode.\n" - " NOTE: This does not change the segment duration: dashing stops once segments produced exceeded the duration.\n" - " -dash-run-for TIME In case of dash live, runs for T ms of the media then exits\n" - " -min-buffer TIME specifies MPD min buffer time in milliseconds\n" - " -ast-offset TIME specifies MPD AvailabilityStartTime offset in ms if positive, or availabilityTimeOffset of each representation if negative. Default is 0 sec delay\n" - " -dash-scale SCALE specifies that timing for -dash and -frag are expressed in SCALE units per seconds\n" - " -mem-frags fragments will be produced in memory rather than on disk before flushing to disk\n" - " -pssh-moof stores PSSH boxes in first moof of each segments. By default PSSH are stored in movie box.\n" - " -sample-groups-traf stores sample group descriptions in traf (duplicated for each traf). If not used, sample group descriptions are stored in the movie box.\n" + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# DASH Options\n" + "Also see:\n" + "- the [dasher `gpac -h dash`](dasher) filter documentation\n" + "- [[online DASH Intro doc|DASH Introduction]].\n" + "\n" + "# Specifying input files\n" + "Input media files to dash can use the following modifiers\n" + "- #trackID=N: only use the track ID N from the source file\n" + "- #N: only use the track ID N from the source file (mapped to [-tkid](mp4dmx))\n" + "- #video: only use the first video track from the source file\n" + "- #audio: only use the first audio track from the source file\n" + "- :id=NAME: set the representation ID to NAME. Reserved value `NULL` disables representation ID for multiplexed inputs\n" + "- :dur=VALUE: process VALUE seconds from the media. If VALUE is longer than media duration, last sample duration is extended.\n" + "- :period=NAME: set the representation's period to NAME. Multiple periods may be used. Periods appear in the MPD in the same order as specified with this option\n" + "- :BaseURL=NAME: set the BaseURL. Set multiple times for multiple BaseURLs\nWarning: This does not modify generated files location (see segment template).\n" + "- :bandwidth=VALUE: set the representation's bandwidth to a given value\n" + "- :pdur=VALUE: increase the duration of this period by the given duration in seconds (alias for period_duration:VALUE). This is only used when no input media is specified (remote period insertion), eg `:period=X:xlink=Z:pdur=Y`\n" + "- :duration=VALUE: override target DASH segment duration for this input\n" + "- :xlink=VALUE: set the xlink value for the period containing this element. Only the xlink declared on the first rep of a period will be used\n" + "- :asID=VALUE: set the AdaptationSet ID to NAME\n" + "- :role=VALUE: set the role of this representation (cf DASH spec). Media with different roles belong to different adaptation sets.\n" + "- :desc_p=VALUE: add a descriptor at the Period level. Value must be a properly formatted XML element.\n" + "- :desc_as=VALUE: add a descriptor at the AdaptationSet level. Value must be a properly formatted XML element. Two input files with different values will be in different AdaptationSet elements.\n" + "- :desc_as_c=VALUE: add a descriptor at the AdaptationSet level. Value must be a properly formatted XML element. Value is ignored while creating AdaptationSet elements.\n" + "- :desc_rep=VALUE: add a descriptor at the Representation level. Value must be a properly formatted XML element. Value is ignored while creating AdaptationSet elements.\n" + "- :sscale: force movie timescale to match media timescale of the first track in the segment.\n" + "- :trackID=N: only use the track ID N from the source file\n" + "- @@f1[:args][@@fN:args]: set a filter chain to insert between the source and the dasher. Each filter in the chain is formatted as a regular filter, see [filter doc `gpac -h doc`](filters_general). If several filters are set, they will be chained in the given order.\n" + "\n" + "Note: `@@f` must be placed after all other options.\n" + "\n" + "# Options\n" + ); - "\n" - "Advanced Options, should not be needed when using -profile:\n" - " -subsegs-per-sidx N sets the number of subsegments to be written in each SIDX box\n" - " If 0, a single SIDX box is used per segment\n" - " If -1, no SIDX box is used\n" - " -url-template uses SegmentTemplate instead of explicit sources in segments.\n" - " Ignored if segments are stored in the output file.\n" - " -daisy-chain uses daisy-chain SIDX instead of hierarchical. Ignored if frags/sidx is 0.\n" - " -single-segment uses a single segment for the whole file (OnDemand profile). \n" - " -single-file uses a single file for the whole file (default). \n" - " -bs-switching MODE sets bitstream switching to \"inband\" (default), \"merge\", \"multi\", \"no\" or \"single\" to test with single input.\n" - " -moof-sn N sets sequence number of first moof to N\n" - " -tfdt N sets TFDT of first traf to N in SCALE units (cf -dash-scale)\n" - " -no-frags-default disables default flags in fragments\n" - " -single-traf uses a single track fragment per moof (smooth streaming and derived specs may require this)\n" - " -dash-ts-prog N program_number to be considered in case of an MPTS input file.\n" - " -frag-rt when using fragments in live mode, flush fragments according to their timing (only supported with a single input).\n" - " -cp-location=MODE sets ContentProtection element location. Possible values for mode are:\n" - " as: sets ContentProtection in AdaptationSet element\n" - " rep: sets ContentProtection in Representation element\n" - " both: sets ContentProtection in both elements\n" - " -start-date for live mode, sets start date (as xs:date, eg YYYY-MM-DDTHH:MM:SSZ. Default is now.\n" - " !! Do not use with multiple periods, nor when DASH duration is not a multiple of GOP size !!\n" - "\n"); + + while (m4b_dash_args[i].name) { + GF_GPACArg *arg = &m4b_dash_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-dash"); + } } -void PrintFormats() + +GF_GPACArg m4b_imp_args[] = { - fprintf(stderr, "Supported raw formats and file extensions:\n" - " NHNT .media .nhnt .info\n" - " NHML .nhml (opt: .media .info)\n" - " MPEG-1-2 Video .m1v .m2v\n" - " MPEG-4 Video .cmp .m4v\n" - " H263 Video .263 .h263\n" - " AVC/H264 Video .h264 .h26L .264 .26L .x264 .svc\n" - " HEVC Video .hevc .h265 .265 .hvc .shvc .lhvc .mhvc\n" - " JPEG Images .jpg .jpeg\n" - " JPEG-2000 Images .jp2\n" - " PNG Images .png\n" - " MPEG 1-2 Audio .mp3, .mp2, .m1a, .m2a\n" - " ADTS-AAC Audio .aac\n" - " Dolby (e)AC-3 Audio .ac3 .ec3\n" - " AMR(WB) Audio .amr .awb\n" - " EVRC Audio .evc\n" - " SMV Audio .smv\n" - "\n" - "Supported containers and file extensions:\n" - " AVI .avi\n" - " MPEG-2 PS .mpg .mpeg .vob .vcd .svcd\n" - " MPEG-2 TS .ts .m2t\n" - " QCP .qcp\n" - " OGG .ogg\n" - " ISO-Media files no extension checking\n" - "\n" - "Supported text formats:\n" - " SRT Subtitles .srt\n" - " SUB Subtitles .sub\n" - " VobSub .idx\n" - " GPAC Timed Text .ttxt\n" - " VTT .vtt\n" - " TTML .ttml\n" - " QuickTime TeXML Text .xml (cf QT documentation)\n" - "\n" - "Supported Scene formats:\n" - " MPEG-4 XMT-A .xmt .xmta .xmt.gz .xmta.gz\n" - " MPEG-4 BT .bt .bt.gz\n" - " MPEG-4 SAF .saf .lsr\n" - " VRML .wrl .wrl.gz\n" - " X3D-XML .x3d .x3d.gz\n" - " X3D-VRML .x3dv .x3dv.gz\n" - " MacroMedia Flash .swf (very limited import support only)\n" - "\n" - "Supported chapter formats:\n" - " Nero chapters .txt .chap\n" - "\n" - ); -} + GF_DEF_ARG("add", NULL, "add given file tracks to file. Multiple inputs can be specified using `+`, eg `-add url1+url2", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("cat", NULL, "concatenate given file samples to file, creating tracks if needed. Multiple inputs can be specified using `+`(eg `-cat url1+url2). \nNote: This aligns initial timestamp of the file to be concatenated", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("catx", NULL, "same as [-cat]() but new tracks can be imported before concatenation by specifying `+ADD_COMMAND` where `ADD_COMMAND` is a regular [-add]() syntax", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("catpl", NULL, "concatenate files listed in the given playlist file (one file per line, lines starting with # are comments). \nNote: Each listed file is concatenated as if called with -cat", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("unalign-cat", NULL, "do not attempt to align timestamps of samples inbetween tracks", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("force-cat", NULL, "skip media configuration check when concatenating file. \nWarning: THIS MAY BREAK THE CONCATENATED TRACK(S)", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("keep-sys", NULL, "keep all MPEG-4 Systems info when using [-add]() and [-cat]() (only used when adding IsoMedia files)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("dref", NULL, "keep media data in original file using `data referencing`. The resulting file only contains the meta-data of the presentation (frame sizes, timing, etc...) and references media data in the original file. This is extremely useful when developping content, since importing and storage of the MP4 file is much faster and the resulting file much smaller. \nNote: Data referencing may fail on some files because it requires the framed data (eg an IsoMedia sample) to be continuous in the original file, which is not always the case depending on the original interleaving or bitstream format (__AVC__ or __HEVC__ cannot use this option)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("no-drop", NULL, "force constant FPS when importing AVI video", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("packed", NULL, "force packed bitstream when importing raw MPEG-4 part 2 Advanced Simple Profile", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("sbr", NULL, "backward compatible signaling of AAC-SBR", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("sbrx", NULL, "non-backward compatible signaling of AAC-SBR", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("ps", NULL, "backward compatible signaling of AAC-PS", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("psx", NULL, "non-backward compatible signaling of AAC-PS", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("ovsbr", NULL, "oversample SBR import (SBR AAC, PS AAC and oversampled SBR cannot be detected at import time)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("fps", NULL, "force frame rate for video and SUB subtitles import to the given value, expressed as a number or as `timescale-increment`. \nNote: For raw H263 import, default FPS is `15`, otherwise `25`. This is ignored for ISOBMFF import, use `:rescale` option for that", "25", NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("mpeg4", NULL, "force MPEG-4 sample descriptions when possible. For AAC, forces MPEG-4 AAC signaling even if MPEG-2", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("agg", NULL, "aggregate N audio frames in 1 sample (3GP media only, maximum value is 15)", NULL, NULL, GF_ARG_INT, 0), + {0} +}; + + +static GF_GPACArg ImportFileOpts [] = { + GF_DEF_ARG("dur", NULL, "`X` import only the specified duration from the media. Value can be:\n" + " - positive float: specifies duration in seconds\n" + " - fraction: specifies duration as NUM/DEN fraction\n" + " - negative integer: specifies duration in number of coded frames", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("lang", NULL, "set imported media language code", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("delay", NULL, "set imported media initial delay in ms", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("par", NULL, "set visual pixel aspect ratio (see [-par](MP4B_GEN) )", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("clap", NULL, "set visual clean aperture (see [-clap](MP4B_GEN) )", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("mx", NULL, "set track matrix (see [-mx](MP4B_GEN) )", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("name", NULL, "set track handler name", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("ext", NULL, "override file extension when importing", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("hdlr", NULL, "set track handler type to the given code point (4CC)", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("disable", NULL, "disable imported track(s)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("group", NULL, "add the track as part of the G alternate group. If G is 0, the first available GroupID will be picked", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("fps", NULL, "same as [-fps]()", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("rap", NULL, "`D` import only RAP samples", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("refs", NULL, "`D` import only reference pictures", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("trailing", NULL, "keep trailing 0-bytes in AVC/HEVC samples", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("agg", NULL, "`X` same as [-agg]()", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("dref", NULL, "`X` same as [-dref]()", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("keep_refs", NULL, "keep track reference when importing a single track", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("nodrop", NULL, "same as [-nodrop]()", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("packed", NULL, "`X` same as [-packed]()", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("sbr", NULL, "same as [-sbr]()", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("sbrx", NULL, "same as [-sbrx]()", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("ovsbr", NULL, "same as [-ovsbr]()", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("ps", NULL, "same as [-ps]()", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("psx", NULL, "same as [-psx]()", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("asemode", NULL, "`X` set the mode to create the AudioSampleEntry. Value can be:\n" + " - v0-bs: use MPEG AudioSampleEntry v0 and the channel count from the bitstream (even if greater than 2) - default\n" + " - v0-2: use MPEG AudioSampleEntry v0 and the channel count is forced to 2\n" + " - v1: use MPEG AudioSampleEntry v1 and the channel count from the bitstream\n" + " - v1-qt: use QuickTime Sound Sample Description Version 1 and the channel count from the bitstream (even if greater than 2). This will also trigger using alis data references instead of url, even for non-audio tracks", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("audio_roll", NULL, "add a roll sample group with roll_distance `N`", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("mpeg4", NULL, "`X` same as [-mpeg4]() option", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("nosei", NULL, "discard all SEI messages during import", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("svc", NULL, "import SVC/LHVC with explicit signaling (no AVC base compatibility)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("nosvc", NULL, "discard SVC/LHVC data when importing", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("svcmode", NULL, "`D` set SVC/LHVC import mode. Value can be:\n" + " - split: each layer is in its own track\n" + " - merge: all layers are merged in a single track\n" + " - splitbase: all layers are merged in a track, and the AVC base in another\n" + " - splitnox: each layer is in its own track, and no extractors are written\n" + " - splitnoxib: each layer is in its own track, no extractors are written, using inband param set signaling", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("temporal", NULL, "`D` set HEVC/LHVC temporal sublayer import mode. Value can be:\n" + " - split: each sublayer is in its own track\n" + " - splitbase: all sublayers are merged in a track, and the HEVC base in another\n" + " - splitnox: each layer is in its own track, and no extractors are written", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("subsamples", NULL, "add SubSample information for AVC+SVC", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("deps", NULL, "import sample dependency information for AVC and HEVC", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("ccst", NULL, "add default HEIF ccst box to visual sample entry", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("forcesync", NULL, "force non IDR samples with I slices to be marked as sync points (AVC GDR)\n" + "Warning: RESULTING FILE IS NOT COMPLIANT WITH THE SPEC but will fix seeking in most players", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("xps_inband", NULL, "`X` set xPS inband for AVC/H264 and HEVC (for reverse operation, re-import from raw media)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("xps_inbandx", NULL, "`X` same as xps_inband and also keep first xPS in sample desciption", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("au_delim", NULL, "keep AU delimiter NAL units in the imported file", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("max_lid", NULL, "set HEVC max layer ID to be imported to `N` (by default imports all layers)", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("max_tid", NULL, "set HEVC max temporal ID to be imported to `N` (by default imports all temporal sublayers)", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("tiles", NULL, "add HEVC tiles signaling and NALU maps without splitting the tiles into different tile tracks", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("split_tiles", NULL, "`D` split HEVC tiles into different tile tracks, one tile (or all tiles of one slice) per track", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("negctts", NULL, "use negative CTS-DTS offsets (ISO4 brand)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("chap", NULL, "specify the track is a chapter track", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("chapter", NULL, "add a single chapter (old nero format) with given name lasting the entire file", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("chapfile", NULL, "add a chapter file (old nero format)", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("layout", NULL, "specify the track layout as WxHxXxY\n" + " - if W (resp H) = 0: the max width (resp height) of the tracks in the file are used\n" + " - if Y=-1: the layout is moved to the bottom of the track area\n" + " - X and Y can be omitted: `:layout=WxH`", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("rescale", NULL, "force media timescale to TS !! changes the media duration", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("timescale", NULL, "set imported media timescale to TS", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("moovts", NULL, "set movie timescale to TS. A negative value picks the media timescale of the first track imported", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("noedit", NULL, "`X` do not set edit list when importing B-frames video tracks", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("rvc", NULL, "set RVC configuration for the media", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("fmt", NULL, "override format detection with given format (cf BT/XMTA doc)", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("profile", NULL, "override AVC profile", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("level", NULL, "override AVC level", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("novpsext", NULL, "remove VPS extensions from HEVC VPS", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("keepav1t", NULL, "keep AV1 temporal delimiter OBU in samples, might help if source file had losses", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("font", NULL, "specify font name for text import (default `Serif`)", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("size", NULL, "specify font size for text import (default `18`)", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("text_layout", NULL, "specify the track text layout as WxHxXxY\n" + " - if W (resp H) = 0: the max width (resp height) of the tracks in the file are used\n" + " - if Y=-1: the layout is moved to the bottom of the track area\n" + " - X and Y can be omitted: `:layout=WxH`", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("swf-global", NULL, "all SWF defines are placed in first scene replace rather than when needed", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-no-ctrl", NULL, "use a single stream for movie control and dictionary (this will disable ActionScript)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-no-text", NULL, "remove all SWF text", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-no-font", NULL, "remove all embedded SWF Fonts (local playback host fonts used)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-no-line", NULL, "remove all lines from SWF shapes", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-no-grad", NULL, "remove all gradients from SWF shapes", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-quad", NULL, "use quadratic bezier curves instead of cubic ones", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-xlp", NULL, "support for lines transparency and scalability", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-ic2d", NULL, "use indexed curve 2D hardcoded proto", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-same-app", NULL, "appearance nodes are reused", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("swf-flatten", NULL, "complementary angle below which 2 lines are merged, `0` means no flattening", NULL, NULL, GF_ARG_DOUBLE, 0), + GF_DEF_ARG("kind", NULL, "set kind for the track as `schemeURI=value`", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("txtflags", NULL, "set display flags (hexa number) of text track. Use `txtflags+=FLAGS` to add flags and `txtflags-=FLAGS` to remove flags", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("rate", NULL, "force average rate and max rate to VAL (in bps) in btrt box. If 0, removes btrt box", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("stz2", NULL, "use compact size table (for low-bitrates)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("bitdepth", NULL, "set bit depth to VAL for imported video content (default is 24)", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("colr", NULL, "set color profile for imported video content (see ISO/IEC 23001-8). Value is formatted as:\n" + " - nclc,p,t,m: with p colour primary, t transfer characteristics and m matrix coef\n" + " - nclx,p,t,m,r: same as `nclx` with r full range flag\n" + " - prof,path: with path indicating the file containing the ICC color profile\n" + " - rICC,path: with path indicating the file containing the restricted ICC color profile", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("dv-profile", NULL, "set the Dolby Vision profile", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("tc", NULL, "inject a single QT timecode. Value is formated as:\n" + " - [d]FPS[/FPS_den],h,m,s,f[,framespertick]: optional drop flag, framerate (integer or fractional), hours, minutes, seconds and frame number\n" + " - : `d` is an optional flag used to indicate that the counter is in drop-frame format\n" + " - : the `framespertick` is optional and defaults to round(framerate); it indicates the number of frames per counter tick", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("lastsampdur", NULL, "set duration of the last sample. Value is formated as:\n" + " - no value: use the previous sample duration\n" + " - integer: indicate the duration in milliseconds\n" + " - N/D: indicate the duration as fractional second", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("fstat", NULL, "print filter session stats after import", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("fgraph", NULL, "print filter session graph after import", NULL, NULL, GF_ARG_BOOL, 0), + {"sopt:[OPTS]", NULL, "set `OPTS` as additional arguments to source filter. `OPTS` can be any usual filter argument, see [filter doc `gpac -h doc`](Filters)"}, + {"dopt:[OPTS]", NULL, "`X` set `OPTS` as additional arguments to [destination filter](mp4mx). OPTS can be any usual filter argument, see [filter doc `gpac -h doc`](Filters)"}, + {"@@f1[:args][@@fN:args]", NULL, "set a filter chain to insert before the muxer. Each filter in the chain is formatted as a regular filter, see [filter doc `gpac -h doc`](Filters). If several filters are set, they will be chained in the given order. The last filter shall not have any Filter ID specified"}, + {0} +}; void PrintImportUsage() { - fprintf(stderr, "Importing Options\n" - "\nFile importing syntax:\n" - " \"#video\" \"#audio\" base import for most AV files\n" - " \"#trackID=ID\" track import for IsoMedia and other files\n" - " \"#pid=ID\" stream import from MPEG-2 TS\n" - " \":dur=D\" imports only the first D seconds\n" - " \":lang=LAN\" sets imported media language code\n" - " \":delay=delay_ms\" sets imported media initial delay in ms\n" - " \":par=PAR\" sets visual pixel aspect ratio (PAR=Num:Den)\n" - " \":name=NAME\" sets track handler name\n" - " \":ext=EXT\" overrides file extension when importing\n" - " \":hdlr=code\" sets track handler type to the given code point (4CC)\n" - " \":disable\" imported track(s) will be disabled\n" - " \":group=G\" adds the track as part of the G alternate group.\n" - " If G is 0, the first available GroupID will be picked.\n" - " \":fps=VAL\" same as -fps option\n" - " \":rap\" imports only RAP samples\n" - " \":trailing\" keeps trailing 0-bytes in AVC/HEVC samples\n" - " \":agg=VAL\" same as -agg option\n" - " \":dref\" same as -dref option\n" - " \":nodrop\" same as -nodrop option\n" - " \":packed\" same as -packed option\n" - " \":sbr\" same as -sbr option\n" - " \":sbrx\" same as -sbrx option\n" - " \":ovsbr\" same as -ovsbr option\n" - " \":ps\" same as -ps option\n" - " \":psx\" same as -psx option\n" - " \":mpeg4\" same as -mpeg4 option\n" - " \":nosei\" discard all SEI messages during import\n" - " \":svc\" import SVC/LHVC with explicit signaling (no AVC base compatibility)\n" - " \":nosvc\" discard SVC/LHVC data when importing\n" - " \":svcmode=MODE\" sets SVC/LHVC import mode:\n" - " \" split : each layer is in its own track\n" - " \" merge : all layers are merged in a single track\n" - " \" splitbase : all layers are merged in a track, and the AVC base in another\n" - " \" splitnox : each layer is in its own track, and no extractors are written\n" - " \":subsamples\" adds SubSample information for AVC+SVC\n" - " \":forcesync\" forces non IDR samples with I slices to be marked as sync points (AVC GDR)\n" - " !! RESULTING FILE IS NOT COMPLIANT WITH THE SPEC but will fix seeking in most players\n" - " \":xps_inband\" Sets xPS inband for AVC/H264 and HEVC (for reverse operation, re-import from raw media)\n" - " \":max_lid=N\" sets HEVC max layer ID to be imported to N. Default imports all.\n" - " \":max_tid=N\" sets HEVC max temporal ID to be imported to N. Default imports all.\n" - " \":tiles\" adds HEVC tiles signaling and NALU maps without splitting the tiles into different tile tracks.\n" - " \":split_tiles\" splits HEVC tiles into different tile tracks, one tile (or all tiles of one slice) per track.\n" - " \":negctts\" uses negative CTS-DTS offsets (ISO4 brand)\n" - " \":stype=4CC\" forces the sample description type to a different value\n" - " !! THIS MAY BREAK THE FILE WRITING !!\n" - " \":chap\" specifies the track is a chapter track\n" - " \":chapter=NAME\" adds a single chapter (old nero format) with given name lasting the entire file\n" - " This command can be used in -cat as well\n" - " \":chapfile=file\" adds a chapter file (old nero format)\n" - " This command can be used in -cat as well\n" - " \":layout=WxHxXxY\" specifies the track layout\n" - " - if W (resp H) = 0, the max width (resp height) of\n" - " the tracks in the file are used.\n" - " - if Y=-1, the layout is moved to the bottom of the\n" - " track area\n" - " - X and Y can be omitted (:layout=WxH)\n" - " \":rescale=TS\" forces media timescale to TS !! changes the media duration\n" - " \":timescale=TS\" sets import timescale to TS\n" - " \":noedit\" do not set edit list when importing B-frames video tracks\n" - " \":rvc=FILENAME\" sets TVC configuration for the media\n" - " \":fmt=FORMAT\" overrides format detection with given format (cf BT/XMTA doc)\n" - " \":profile=INT\" overrides AVC profile\n" - " \":level=INT\" overrides AVC level\n" - " \":novpsext\" removes VPS extensions from HEVC VPS\n" - - " \":font=name\" specifies font name for text import (default \"Serif\")\n" - " \":size=s\" specifies font size for text import (default 18)\n" - " \":text_layout=WxHxXxY\" specifies the track text layout\n" - " - if W (resp H) = 0, the max width (resp height) of\n" - " the tracks in the file are used.\n" - " - if Y=-1, the layout is moved to the bottom of the\n" - " track area\n" - " - X and Y can be omitted (:layout=WxH)\n" - - " \":swf-global\" all SWF defines are placed in first scene replace\n" - " * Note: By default SWF defines are sent when needed\n" - " \":swf-no-ctrl\" uses a single stream for movie control and dictionary\n" - " * Note: this will disable ActionScript\n" - " \":swf-no-text\" removes all SWF text\n" - " \":swf-no-font\" removes all embedded SWF Fonts (terminal fonts used)\n" - " \":swf-no-line\" removes all lines from SWF shapes\n" - " \":swf-no-grad\" removes all gradients from swf shapes\n" - " \":swf-quad\" uses quadratic bezier curves instead of cubic ones\n" - " \":swf-xlp\" support for lines transparency and scalability\n" - " \":swf-ic2d\" uses indexed curve 2D hardcoded proto\n" - " \":swf-same-app\" appearance nodes are reused\n" - " \":swf-flatten=ang\" complementary angle below which 2 lines are merged\n" - " * Note: angle \'0\' means no flattening\n" - " \":kind=schemeURI=value\" sets kind for the track\n" - " \":txtflags=flags\" sets display flags (hexa number) of text track\n" - " \":txtflags+=flags\" adds display flags (hexa number) to text track\n" - " \":txtflags-=flags\" removes display flags (hexa number) from text track\n" - "\n" - " -add file add file tracks to (new) output file\n" - " -cat file concatenates file samples to (new) output file\n" - " * Note: creates tracks if needed\n" - " * Note: aligns initial timestamp of the file to be concatenated.\n" - " -catx file same as cat but new tracks can be imported before concatenation by specifying '+ADD_COMMAND'\n" - " where ADD_COMMAND is a regular -add syntax\n" - " -unalign-cat does not attempt to align timestamps of samples inbetween tracks\n" - " -force-cat skips media configuration check when concatenating file\n" - " !!! THIS MAY BREAK THE CONCATENATED TRACK(S) !!!\n" - " -keep-sys keeps all MPEG-4 Systems info when using '-add' / 'cat'\n" - " -keep-all keeps all existing tracks when using '-add'\n" - " * Note: only used when adding IsoMedia files\n" - "\n" - "All the following options can be specified as default or for each track.\n" - "When specified by track the syntax is \":opt\" or \":opt=val\".\n\n" - " -dref keeps media data in original file\n" - " -no-drop forces constant FPS when importing AVI video\n" - " -packed forces packed bitstream when importing raw ASP\n" - " -sbr backward compatible signaling of AAC-SBR\n" - " -sbrx non-backward compatible signaling of AAC-SBR\n" - " -ps backward compatible signaling of AAC-PS\n" - " -psx non-backward compatible signaling of AAC-PS\n" - " -ovsbr oversample SBR\n" - " * Note: SBR AAC, PS AAC and oversampled SBR cannot be detected at import time\n" - " -fps FPS forces frame rate for video and SUB subtitles import\n" - " FPS is either a number or expressed as timescale-increment\n" - " * For raw H263 import, default FPS is 15\n" - " * For all other imports, default FPS is 25\n" - " !! THIS IS IGNORED FOR IsoMedia IMPORT !!\n" - " -mpeg4 forces MPEG-4 sample descriptions when possible (3GPP2)\n" - " For AAC, forces MPEG-4 AAC signaling even if MPEG-2\n" - " -agg N aggregates N audio frames in 1 sample (3GP media only)\n" - " * Note: Maximum value is 15 - Disabled by default\n" - "\n" - ); + u32 i; + + gf_sys_format_help(helpout, help_flags, "# Importing Options\n" + "# File importing\n" + "Syntax is [-add]() / [-cat]() `filename[#FRAGMENT][:opt1...:optN=val]`\n" + "This process will create the destination file if not existing, and add the track(s) to it. If you wish to always create a new destination file, add [-new](MP4B_GEN).\n" + "The supported input media types depend on your installation, check [filters documentation](Filters) for more info.\n" + " \n" + "To select a desired media track from a source, a fragment identifier '#' can be specified, bfore any other options. The following syntax is used:\n" + "- `#video`: adds the first video track found in source\n" + "- `#audio`: adds the first audio track found in source\n" + "- `#auxv`: adds the first auxiliary video track found in source\n" + "- `#pict`: adds the first picture track found in source\n" + "- `#trackID=ID` or `#ID`: adds the specified track. For IsoMedia files, ID is the track ID. For other media files, ID is the value indicated by `MP4Box -info inputFile`\n" + "- `#pid=ID`: number of desired PID for MPEG-2 TS sources\n" + "- `#prog_id=ID`: number of desired program for MPEG-2 TS sources\n" + "- `#program=NAME`: name of desired program for MPEG-2 TS sources\n" + " \n" + "By default all imports are performed sequentially, and final interleaving is done at the end; this however requires a temporary file holding original ISOBMF file (if any) and added files before creating the final output. Since this can become quite large, it is possible to add media to a new file without temporary storage, using [-flat](MP4B_GEN) option, but this disables media interleaving.\n" + " \n" + "If you wish to create an interleaved new file with no temporary storage, use the [-newfs](MP4B_GEN) option. The interleaving might not be as precise as when using [-new]() since it is dependent on muxer input scheduling (each execution might lead to a slightly different result). Additionally in this mode: \n" + " - Some muxing options (marked with `X` below) will be activated for all inputs (e.g it is not possible to import one AVC track with `xps_inband` and another without).\n" + " - Some muxing options (marked as `D` below) cannot be used as they require temporary storage for file edition.\n" + " - Usage of [-cat]() is possible, but concatenated sources will not be interleaved in the output. If you wish to perforom more complex cat/add operations without temp file, use the [gpac application](Filters).\n" + " \n" + "Note: MP4Box cannot start importing from a random point in the input, it always import from the begining. If you wish to import from another point in the source, use the [gpac application](Filters).\n" + " \n" + "Note: When importing SRT or SUB files, MP4Box will choose default layout options to make the subtitle appear at the bottom of the video. You SHOULD NOT import such files before any video track is added to the destination file, otherwise the results will likelly not be useful (default SRT/SUB importing uses default serif font, fontSize 18 and display size 400x60). For more details, check [TTXT doc](Subtitling-with-GPAC).\n" + " \n" + "When importing several tracks/sources in one pass, all options will be applied if relevant to each source. These options are set for all imported streams. If you need to specify these options par stream, set per-file options using the syntax `-add stream[:opt1:...:optN]`. Allowed per-file options:\n\n" + ); + + i=0; + while (ImportFileOpts[i].name) { + GF_GPACArg *arg = &ImportFileOpts[i]; + i++; + gf_sys_print_arg(helpout, help_flags | GF_PRINTARG_NO_DASH, arg, "mp4box-import"); + } + + gf_sys_format_help(helpout, help_flags, "\n" + "Note: `sopt`, `dopt` and `@@f` must be placed after all other options.\n" + "# Global import options\n" + ); + + i=0; + while (m4b_imp_args[i].name) { + GF_GPACArg *arg = &m4b_imp_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-import"); + } } +GF_GPACArg m4b_senc_args[] = +{ + GF_DEF_ARG("mp4", NULL, "specify input file is for encoding", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("def", NULL, "encode DEF names in BIFS", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("sync", NULL, "force BIFS sync sample generation every given time in ms (cannot be used with [-shadow]() )", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("shadow", NULL, "force BIFS sync shadow sample generation every given time in ms (cannot be used with [-sync]() )", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("sclog", NULL, "generate scene codec log file if available", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("ms", NULL, "import tracks from the given file", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("ctx-in", NULL, "specify initial context (MP4/BT/XMT) file for chunk processing. Input file must be a commands-only file", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("ctx-out", NULL, "specify storage of updated context (MP4/BT/XMT) file for chunk processing, optional", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("resolution", NULL, "resolution factor (-8 to 7, default 0) for LASeR encoding, and all coords are multiplied by `2^res` before truncation (LASeR encoding)", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("coord-bits", NULL, "number of bits used for encoding truncated coordinates (0 to 31, default 12) (LASeR encoding)", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("scale-bits", NULL, "extra bits used for encoding truncated scales (0 to 4, default 0) (LASeR encoding)", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("auto-quant", NULL, "resolution is given as if using -resolution but coord-bits and scale-bits are infered (LASeR encoding)", NULL, NULL, GF_ARG_INT, 0), + {0} +}; + + void PrintEncodeUsage() { - fprintf(stderr, "MPEG-4 Scene Encoding Options\n" - " -mp4 specify input file is for encoding.\n" - " -def encode DEF names\n" - " -sync time_in_ms forces BIFS sync sample generation every time_in_ms\n" - " * Note: cannot be used with -shadow\n" - " -shadow time_ms forces BIFS sync shadow sample generation every time_ms.\n" - " * Note: cannot be used with -sync\n" - " -log generates scene codec log file if available\n" - " -ms file specifies file for track importing\n" - "\nChunk Processing\n" - " -ctx-in file specifies initial context (MP4/BT/XMT)\n" - " * Note: input file must be a commands-only file\n" - " -ctx-out file specifies storage of updated context (MP4/BT/XMT)\n" - "\n" - "LASeR Encoding options\n" - " -resolution res resolution factor (-8 to 7, default 0)\n" - " all coords are multiplied by 2^res before truncation\n" - " -coord-bits bits bits used for encoding truncated coordinates\n" - " (0 to 31, default 12)\n" - " -scale-bits bits extra bits used for encoding truncated scales\n" - " (0 to 4, default 0)\n" - " -auto-quant res resolution is given as if using -resolution\n" - " but coord-bits and scale-bits are infered\n" - ); + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# MPEG-4 Scene Encoding Options\n" + "## General considerations\n" + "MP4Box supports encoding and decoding of of BT, XMT, VRML and (partially) X3D formats int MPEG-4 BIFS, and encoding and decoding of XSR and SVG into MPEG-4 LASeR\n" + "Any media track specified through a `MuxInfo` element will be imported in the resulting MP4 file.\n" + "See https://wiki.gpac.io/MPEG-4-BIFS-Textual-Format and related pages.\n" + "## Scene Random Access\n" + "MP4Box can encode BIFS or LASeR streams and insert random access points at a given frequency. This is useful when packaging content for broadcast, where users will not turn in the scene at the same time. In MPEG-4 terminology, this is called the __scene carousel__." + "## BIFS Chunk Processing\n" + "The BIFS chunk encoding mode alows encoding single BIFS access units from an initial context and a set of commands.\n" + "The generated AUs are raw BIFS (not SL-packetized), in files called FILE-ESID-AUIDX.bifs, with FILE the basename of the input file.\n" + "Commands with a timing of 0 in the input will modify the carousel version only (i.e. output context).\n" + "Commands with a timing different from 0 in the input will generate new AUs.\n" + " \n" + "Options:\n" + ); + + while (m4b_senc_args[i].name) { + GF_GPACArg *arg = &m4b_senc_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-senc"); + } } +GF_GPACArg m4b_crypt_args[] = +{ + GF_DEF_ARG("crypt", NULL, "encrypt the input file using the given `CryptFile`", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("decrypt", NULL, "decrypt the input file, potentially using the given `CryptFile`. If `CryptFile` is not given, will fail if the key management system is not supported", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("set-kms", NULL, "change ISMA/OMA KMS location for all tracks, or for a given one if `ID=kms_uri` is used", NULL, NULL, GF_ARG_STRING, 0), + {0} +}; + void PrintEncryptUsage() { - fprintf(stderr, "ISMA Encryption/Decryption Options\n" - " -crypt drm_file crypts a specific track using ISMA AES CTR 128\n" - " -decrypt [drm_file] decrypts a specific track using ISMA AES CTR 128\n" - " * Note: drm_file can be omitted if keys are in file\n" - " -set-kms kms_uri changes KMS location for all tracks or a given one.\n" - " * to address a track, use \'tkID=kms_uri\'\n" - "\n" - "DRM file syntax for GPAC ISMACryp:\n" - " File is XML and shall start with xml header\n" - " File root is an \"ISMACryp\" element\n" - " File is a list of \"cryptrack\" elements\n" - "\n" - "cryptrack attributes are\n" - " TrackID ID of track to en/decrypt\n" - " key AES-128 key formatted (hex string \'0x\'+32 chars)\n" - " salt CTR IV salt key (64 bits) (hex string \'0x\'+16 chars)\n" - "\nEncryption only attributes:\n" - " Scheme_URI URI of scheme used\n" - " KMS_URI URI of key management system\n" - " * Note: \'self\' writes key and salt in the file\n" - " selectiveType selective encryption type - understood values are:\n" - " \"None\" all samples encrypted (default)\n" - " \"Clear\" all samples clean (not encrypted)\n" - " \"RAP\" only encrypts random access units\n" - " \"Non-RAP\" only encrypts non-random access units\n" - " \"Rand\" random selection is performed\n" - " \"X\" Encrypts every first sample out of X (uint)\n" - " \"RandX\" Encrypts one random sample out of X (uint)\n" - "\n" - " ipmpType IPMP Signaling Type: None, IPMP, IPMPX\n" - " ipmpDescriptorID IPMP_Descriptor ID to use if IPMP(X) is used\n" - " * If not set MP4Box will generate one for you\n" - "\n" - ); + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# Encryption/Decryption Options\n" + "MP4Box supports encryption and decryption of ISMA, OMA and CENC content, see [encryption filter `gpac -h cecrypt`](cecrypt).\n" + "It requires a specific XML file called `CryptFile`, whose syntax is available at https://wiki.gpac.io/Common-Encryption\n" + " \n" + "Options:\n" + ); + while (m4b_crypt_args[i].name) { + GF_GPACArg *arg = &m4b_crypt_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-crypt"); + } } +GF_GPACArg m4b_hint_args[] = +{ + GF_DEF_ARG("hint", NULL, "hint the file for RTP/RTSP", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("mtu", NULL, "specify RTP MTU (max size) in bytes (this includes 12 bytes RTP header)", "1450", NULL, GF_ARG_INT, 0), + GF_DEF_ARG("copy", NULL, "copy media data to hint track rather than reference (speeds up server but takes much more space)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("multi `[maxptime]`", NULL, "enable frame concatenation in RTP packets if possible (with max duration 100 ms or `maxptime` ms if given)", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("rate", NULL, "specify rtp rate in Hz when no default for payload", "90000", NULL, GF_ARG_INT, 0), + GF_DEF_ARG("mpeg4", NULL, "force MPEG-4 generic payload whenever possible", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("latm", NULL, "force MPG4-LATM transport for AAC streams", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("static", NULL, "enable static RTP payload IDs whenever possible (by default, dynamic payloads are always used)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("add-sdp", NULL, "add given SDP string to hint track (`tkID:string`) or movie (`string`)", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("no-offset", NULL, "signal no random offset for sequence number and timestamp (support will depend on server)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("unhint", NULL, "remove all hinting information from file", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("group-single", NULL, "put all tracks in a single hint group", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("ocr", NULL, "force all MPEG-4 streams to be synchronized (MPEG-4 Systems only)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("rap", NULL, "signal random access points in RTP packets (MPEG-4 Systems)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("ts", NULL, "signal AU Time Stamps in RTP packets (MPEG-4 Systems)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("size", NULL, "signal AU size in RTP packets (MPEG-4 Systems)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("idx", NULL, "signal AU sequence numbers in RTP packets (MPEG-4 Systems)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("iod", NULL, "prevent systems tracks embedding in IOD (MPEG-4 Systems), not compatible with [-isma]()", NULL, NULL, GF_ARG_BOOL, 0), + {0} +}; + void PrintHintUsage() { - fprintf(stderr, "Hinting Options\n" - " -hint hints the file for RTP/RTSP\n" - " -mtu size specifies RTP MTU (max size) in bytes. Default size is 1450\n" - " * Note: this includes the RTP header (12 bytes)\n" - " -copy copies media data to hint track rather than reference\n" - " * Note: speeds up server but takes much more space\n" - " -multi [maxptime] enables frame concatenation in RTP packets if possible\n" - " maxptime max packet duration in ms (optional, default 100ms)\n" - " -rate ck_rate specifies rtp rate in Hz when no default for payload\n" - " * Note: default value is 90000 (MPEG rtp rates)\n" - " -mpeg4 forces MPEG-4 generic payload whenever possible\n" - " -latm forces MPG4-LATM transport for AAC streams\n" - " -static enables static RTP payload IDs whenever possible\n" - " * By default, dynamic payloads are always used\n" - "\n" - "MPEG-4 Generic Payload Options\n" - " -ocr forces all streams to be synchronized\n" - " * Most RTSP servers only support synchronized streams\n" - " -rap signals random access points in RTP packets\n" - " -ts signals AU Time Stamps in RTP packets\n" - " -size signals AU size in RTP packets\n" - " -idx signals AU sequence numbers in RTP packets\n" - " -iod prevents systems tracks embedding in IOD\n" - " * Note: shouldn't be used with -isma option\n" - "\n" - " -add-sdp string adds sdp string to (hint) track (\"-add-sdp tkID:string\")\n" - " or movie. This will take care of SDP lines ordering\n" - " -unhint removes all hinting information.\n" - "\n"); + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# Hinting Options\n" + "IsoMedia hinting consists in creating special tracks in the file that contain transport protocol specific information and optionally multiplexing information. These tracks are then used by the server to create the actual packets being sent over the network, in other words they provide the server with hints on how to build packets, hence their names `hint tracks`.\n" + "MP4Box supports creation of hint tracks for RTSP servers supporting these such as QuickTime Streaming Server, DarwinStreaming Server or 3GPP-compliant RTSP servers.\n" + "Note: GPAC streaming tools [rtp output](rtpout) and [rtsp server](rtspout) do not use hint tracks, they use on-the-fly packetization " + "from any media sources, not just MP4\n" + " \n" + "Options:\n" + ); + while (m4b_hint_args[i].name) { + GF_GPACArg *arg = &m4b_hint_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-hint"); + } } + + +GF_GPACArg m4b_extr_args[] = +{ + GF_DEF_ARG("raw", NULL, "extract given track in raw format when supported. Use `tkID:output=FileName` to set output file name", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("raws", NULL, "extract each sample of the given track to a file. Use `tkID:N`to extract the Nth sample", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("nhnt", NULL, "extract given track to [NHNT](nhntr) format", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("nhml", NULL, "extract given track to [NHML](nhmlr) format. Use `tkID:full` for full NHML dump with all packet properties", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("webvtt-raw", NULL, "extract given track as raw media in WebVTT as metadata. Use `tkID:embedded` to include media data in the WebVTT file", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("single", NULL, "extract given track to a new mp4 file", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("six", NULL, "extract given track as raw media in **experimental** XML streaming instructions", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("avi", NULL, "extract given track to an avi file", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("avi", NULL, "same as [-raw]() but defaults to QCP file for EVRC/SMV", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("aviraw", NULL, "extract AVI track in raw format; parameter can be `video`, `audio`or `audioN`", NULL, "video|audio", GF_ARG_STRING, 0), + GF_DEF_ARG("saf", NULL, "remux file to SAF multiplex", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("dvbhdemux", NULL, "demux DVB-H file into IP Datagrams sent on the network", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("raw-layer", NULL, "same as [-raw]() but skips SVC/MVC/LHVC extractors when extracting", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("diod", NULL, "extract file IOD in raw format", NULL, NULL, GF_ARG_BOOL, 0), + {0} +}; + void PrintExtractUsage() { - fprintf(stderr, "Extracting Options:\n" - " -raw TrackID extracts track in raw format when supported\n" - " :output=FileName sets the output filename for this extraction \n" - " -raws TrackID extract each track sample to a file\n" - " * Note: \"TrackID:N\" extracts Nth sample\n" - " -nhnt TrackID extracts track in nhnt format\n" - " -nhml TrackID extracts track in nhml format (XML nhnt).\n" - " * Note: \"-nhml TrackID:full\" for full dump\n" - " -webvtt-raw TrackID extracts raw media track in WebVTT as metadata.\n" - " * Note: \"-webvtt-raw TrackID:embedded\" to include media data in the WebVTT file\n" - " -six TrackID extracts raw media track in experimental XML streaming instructions.\n" - " -single TrackID extracts track to a new mp4 file\n" - " -avi TrackID extracts visual track to an avi file\n" - " -qcp TrackID same as \'-raw\' but defaults to QCP file for EVRC/SMV\n" - " -aviraw TK extracts AVI track in raw format\n" - " $TK can be one of \"video\" \"audio\" \"audioN\"\n" - " -saf remux file to SAF multiplex\n" - " -dvbhdemux demux DVB-H file into IP Datagrams\n" - " * Note: can be used when encoding scene descriptions\n" - " -raw-layer ID same as -raw but skips SVC/MVC extractors when extracting\n" - " -diod extracts file IOD in raw format when supported\n" - "\n" -#if !defined(GPAC_DISABLE_STREAMING) - " -grab-ts IP:port grabs TS over UDP or RTP at IP:port location to output TS file\n" - " -ifce IFCE indicates default ifce for grab operations\n" -#endif - "\n"); + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# Extracting Options\n" + "MP4Box can be used to extract media tracks from MP4 files. If you need to convert these tracks however, please check the [filters doc](Filters).\n" + " \n" + "Options:\n" + ); + while (m4b_extr_args[i].name) { + GF_GPACArg *arg = &m4b_extr_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-extract"); + } } + +GF_GPACArg m4b_dump_args[] = +{ + GF_DEF_ARG("stdb", NULL, "dump/write to stdout and assume stdout is opened in binary mode", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("stdb", NULL, "dump/write to stdout and try to reopen stdout in binary mode", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("tracks", NULL, "print the number of tracks on stdout", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("info", NULL, "print movie info (no parameter) or track info with specified ID", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("infon", NULL, "print track info for given track number, 1 being the first track in the file", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("diso", NULL, "dump IsoMedia file boxes in XML output", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("dxml", NULL, "dump IsoMedia file boxes and known track samples in XML output", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("disox", NULL, "dump IsoMedia file boxes except sample tables in XML output", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("keep-ods", NULL, "do not translate ISOM ODs and ESDs tags (debug purpose only)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("bt", NULL, "dump scene to BT format", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("xmt", NULL, "dump scene to XMT format", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("wrl", NULL, "dump scene to VRML format", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("x3d", NULL, "dump scene to X3D XML format", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("x3dc", NULL, "dump scene to X3D VRML format", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("lsr", NULL, "dump scene to LASeR XML (XSR) format", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("drtp", NULL, "dump rtp hint samples structure to XML output", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("dts", NULL, "print sample timing, size and position in file to text output", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("dtsx", NULL, "same as [-dts]() but does not print offset", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("dtsc", NULL, "same as [-dts]() but analyse each sample for duplicated dts/cts (__slow !__)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("dtsxc", NULL, "same as [-dtsc]() but does not print offset (__slow !__)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("dnal", NULL, "print NAL sample info of given track", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("dnalc", NULL, "print NAL sample info of given track, adding CRC for each nal", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("dnald", NULL, "print NAL sample info of given track without DTS and CTS info", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("dnalx", NULL, "print NAL sample info of given track without DTS and CTS info and adding CRC for each nal", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("sdp", NULL, "dump SDP description of hinted file", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("dsap", NULL, "dump DASH SAP cues (see -cues) for a given track", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("dsaps", NULL, "same as [-dsap]() but only print sample number", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("dsapc", NULL, "same as [-dsap]() but only print CTS", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("dsapd", NULL, "same as [-dsap]() but only print DTS, `-dsapp` to only print presentation time", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("dsapp", NULL, "same as [-dsap]() but only print presentation time", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("dcr", NULL, "dump ISMACryp samples structure to XML output", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("dump-cover", NULL, "extract cover art", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("dump-chap", NULL, "extract chapter file as TTXT format", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("dump-chap-ogg", NULL, "extract chapter file as OGG format", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("dump-chap-zoom", NULL, "extract chapter file as zoom format", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("dump-udta `[tkID:]4cc`", NULL, "extract udta for the given 4CC. If `tkID` is given, dumps from UDTA of the given track ID, otherwise moov is used", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("mergevtt", NULL, "merge vtt cues while dumping", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("ttxt", NULL, "convert input subtitle to GPAC TTXT format if no parameter. Otherwise, dump given text track to GPAC TTXT format", NULL, NULL, GF_ARG_INT, 0), + GF_DEF_ARG("srt", NULL, "convert input subtitle to SRT format if no parameter. Otherwise, dump given text track to SRT format", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("rip-mpd", NULL, "download manifest and segments of an MPD. Does not work with live sessions", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("stat", NULL, "generate node/field statistics for scene", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("stats", NULL, "generate node/field statistics per Access Unit", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("statx", NULL, "generate node/field statistics for scene after each AU", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("hash", NULL, "generate SHA-1 Hash of the input file", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("comp", NULL, "replace with compressed version all top level box types given as parameter, formated as `orig_4cc_1=comp_4cc_1[,orig_4cc_2=comp_4cc_2]`", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("bin", NULL, "convert input XML file using NHML bitstream syntax to binary", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("topcount", NULL, "print to stdout the number of top-level boxes matching box types given as parameter, formated as `4cc_1,4cc_2N`", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("topsize", NULL, "print to stdout the number of bytes of top-level boxes matching types given as parameter, formated as `4cc_1,4cc_2N` or `all` for all boxes", NULL, NULL, GF_ARG_STRING, 0), + {0} +}; + void PrintDumpUsage() { - fprintf(stderr, "Dumping Options\n" - " -stdb dumps/write to stdout and assumes stdout is opened in binary mode\n" - " -std dumps/write to stdout and try to reopen stdout in binary mode.\n" - " -info [trackID] prints movie info / track info if trackID specified\n" - " * Note: for non IsoMedia files, gets import options\n" - " -bt scene to bt format - removes unknown MPEG4 nodes\n" - " -xmt scene to XMT-A format - removes unknown MPEG4 nodes\n" - " -wrl scene VRML format - removes unknown VRML nodes\n" - " -x3d scene to X3D/XML format - removes unknown X3D nodes\n" - " -x3dv scene to X3D/VRML format - removes unknown X3D nodes\n" - " -lsr scene to LASeR format\n" - " -diso dumps IsoMedia file boxes in XML output\n" - " -dxml dumps IsoMedia file boxes and known track samples in XML output\n" - " -drtp rtp hint samples structure to XML output\n" - " -dts prints sample timing to text output\n" - " -dnal trackID prints NAL sample info of given track\n" - " -sdp dumps SDP description of hinted file\n" - " -dcr ISMACryp samples structure to XML output\n" - " -dump-cover Extracts cover art\n" - " -dump-chap Extracts chapter file\n" - " -dump-chap-ogg Extracts chapter file as OGG format\n" - " -dump-udta [ID:]4cc Extracts udta for the given 4CC. If ID is given, dumps from UDTA of the given track ID, otherwise moov is used.\n" - "\n" -#ifndef GPAC_DISABLE_ISOM_WRITE - " -ttxt Converts input subtitle to GPAC TTXT format\n" -#endif - " -ttxt TrackID Dumps Text track to GPAC TTXT format\n" -#ifndef GPAC_DISABLE_ISOM_WRITE - " -srt Converts input subtitle to SRT format\n" -#endif - " -srt TrackID Dumps Text track to SRT format\n" - "\n" - " -stat generates node/field statistics for scene\n" - " -stats generates node/field statistics per MPEG-4 Access Unit\n" - " -statx generates node/field statistics for scene after each AU\n" - "\n" - " -hash generates SHA-1 Hash of the input file\n" -#ifndef GPAC_DISABLE_CORE_TOOLS - " -bin converts input XML file using NHML bitstream syntax to binary\n" -#endif - "\n"); + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# File Dumping\n" + " \n" + "MP4Box has many dump functionalities, from simple track listing to more complete reporting of special tracks.\n" + " \n" + "Options:\n" + ); + while (m4b_dump_args[i].name) { + GF_GPACArg *arg = &m4b_dump_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-extract"); + } } +GF_GPACArg m4b_meta_args[] = +{ + GF_DEF_ARG("set-meta `ABCD[:tk=tkID]`", NULL, "set meta box type, with `ABCD` the four char meta type (NULL or 0 to remove meta)\n" + "- tk not set: use root (file) meta\n" + "- tkID == 0: use moov meta\n" + "- tkID != 0: use meta of given track", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("add-items", NULL, "add resource to meta, with parameter syntax `file_path[:opt1:optN]`\n" + "- file_path `this` or `self`: item is the file itself\n" + "- tk=tkID: meta location (file, moov, track)\n" + "- name=str: item name\n" + "- type=itype: item 4cc type (not needed if mime is provided)\n" + "- mime=mtype: item mime type\n" + "- encoding=enctype: item content-encoding type\n" + "- id=ID: item ID\n" + "- ref=4cc,id: reference of type 4cc to an other item", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("add-image", NULL, "add the given file (with parameters) as HEIF image item. Same syntax as [-add-item]()\n" + "- name=str: see [-add-item]()\n" + "- id=id: see [-add-item]()\n" + "- ref=4cc, id: see [-add-item]()\n" + "- primary: indicate that this item should be the primary item\n" + "- time=t: use the next sync sample after time t (float, in sec, default 0). A negative time imports ALL frames as items\n" + "- split_tiles: for an HEVC tiled image, each tile is stored as a separate item\n" + "- rotation=a: set the rotation angle for this image to 90*a degrees anti-clockwise\n" + "- hidden: indicate that this image item should be hidden\n" + "- icc_path: path to icc to add as colr\n" + "- alpha: indicate that the image is an alpha image (should use ref=auxl also)\n" + "- any other option will be passed as options to the media importer, see [-add]()", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("rem-item `item_ID[:tk=tkID]`", NULL, "remove resource from meta", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("set-primary `item_ID[:tk=tkID]`", NULL, "set item as primary for meta", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("set-xml `xml_file_path[:tk=tkID][:binary]`", NULL, "set meta XML data", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("rem-xml `[tk=tkID]`", NULL, "remove meta XML data", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("dump-xml `file_path[:tk=tkID]`", NULL, "dump meta XML to file", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("dump-item `item_ID[:tk=tkID][:path=fileName]`", NULL, "dump item to file", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("package", NULL, "package input XML file into an ISO container, all media referenced except hyperlinks are added to file", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("package", NULL, "package input XML file into an MPEG-U widget with ISO container, all files contained in the current folder are added to the widget package", NULL, NULL, GF_ARG_STRING, 0), + {0} +}; + void PrintMetaUsage() { - fprintf(stderr, "Meta handling Options\n" - " -set-meta args sets given meta type - syntax: \"ABCD[:tk=ID]\"\n" - " * ABCD: four char meta type (NULL or 0 to remove meta)\n" - " * [:tk=ID]: if not set use root (file) meta\n" - " if ID is 0 use moov meta\n" - " if ID is not 0 use track meta\n" - " -add-item args adds resource to meta\n" - " * syntax: file_path + options (\':\' separated):\n" - " file_path \"this\" or \"self\": item is the file itself\n" - " tk=ID: meta location (file, moov, track)\n" - " name=str: item name\n" - " type=itype: item 4cc type (not needed if mime is provided)\n" - " mime=mtype: item mime type\n" - " encoding=enctype: item content-encoding type\n" - " id=id: item ID\n" - " ref=4cc,id: reference of type 4cc to an other item\n" - " Image Item options\n" - " image-size=wxh sets the width and height of the image.\n" - " image-pasp=wxh sets the pixel aspect ratio property of the image.\n" - " image-rloc=wxh sets the location of this image within another image item.\n" - " image-irot=a sets the rotation angle for this image to 90*a degrees anti-clockwise.\n" - " image-hidden indicates that this image item should be hidden.\n" - " -rem-item args removes resource from meta - syntax: item_ID[:tk=ID]\n" - " -set-primary args sets item as primary for meta - syntax: item_ID[:tk=ID]\n" - " -set-xml args sets meta XML data\n" - " * syntax: xml_file_path[:tk=ID][:binary]\n" - " -rem-xml [tk=ID] removes meta XML data\n" - " -dump-xml args dumps meta XML to file - syntax file_path[:tk=ID]\n" - " -dump-item args dumps item to file - syntax item_ID[:tk=ID][:path=fileName]\n" - " -package packages input XML file into an ISO container\n" - " * all media referenced except hyperlinks are added to file\n" - " -mgt packages input XML file into an MPEG-U widget with ISO container.\n" - " * all files contained in the current folder are added to the widget package\n" - ); + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# Meta and HEIF Options\n" + "IsoMedia files can be used as generic meta-data containers, for examples storing XML information and sample images for a movie. The resulting file may not always contain a movie as is the case with some HEIF files or MPEG-21 files.\n" + " \n" + "These information can be stored at the file root level, as is the case for HEIF/IFF and MPEG-21 file formats, or at the moovie or track level for a regular movie." + " \n \n"); + while (m4b_meta_args[i].name) { + GF_GPACArg *arg = &m4b_meta_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-extract"); + } } +GF_GPACArg m4b_swf_args[] = +{ + GF_DEF_ARG("global", NULL, "all SWF defines are placed in first scene replace rather than when needed", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("no-ctrl", NULL, "use a single stream for movie control and dictionary (this will disable ActionScript)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("no-text", NULL, "remove all SWF text", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("no-font", NULL, "remove all embedded SWF Fonts (local playback host fonts used)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("no-line", NULL, "remove all lines from SWF shapes", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("no-grad", NULL, "remove all gradients from swf shapes", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("quad", NULL, "use quadratic bezier curves instead of cubic ones", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("xlp", NULL, "support for lines transparency and scalability", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("flatten", NULL, "complementary angle below which 2 lines are merged, value `0`means no flattening", NULL, NULL, GF_ARG_DOUBLE, 0), + {0} +}; + void PrintSWFUsage() { - fprintf(stderr, - "SWF Importer Options\n" + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# SWF Importer Options\n" "\n" "MP4Box can import simple Macromedia Flash files (\".SWF\")\n" "You can specify a SWF input file with \'-bt\', \'-xmt\' and \'-mp4\' options\n" + " \n" + "Options:\n" + ); + while (m4b_swf_args[i].name) { + GF_GPACArg *arg = &m4b_swf_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-extract"); + } +} + +GF_GPACArg m4b_liveenc_args[] = +{ + GF_DEF_ARG("dst", NULL, "destination IP", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("port", NULL, "destination port", "7000", NULL, GF_ARG_INT, 0), + GF_DEF_ARG("mtu", NULL, "path MTU for RTP packets", "1450", NULL, GF_ARG_INT, 0), + GF_DEF_ARG("ifce", NULL, "IP address of the physical interface to use", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("ttl", NULL, "time to live for multicast packets", "1", NULL, GF_ARG_INT, 0), + GF_DEF_ARG("sdp", NULL, "output SDP file", "session.sdp", NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("dims", NULL, "turn on DIMS mode for SVG input", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("no-rap", NULL, "disable RAP sending and carousel generation", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("src", NULL, "source of scene updates", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("rap", NULL, "duration in ms of base carousel; you can specify the RAP period of a single ESID (not in DIMS) using `ESID=X:time`", NULL, NULL, GF_ARG_INT, 0), + {0} +}; + +void PrintLiveUsage() +{ + u32 i=0; + gf_sys_format_help(helpout, help_flags, "# Live Scene Encoder Options\n" + "The options shall be specified as òpt_name=opt_val.\n" + "Options:\n" "\n" - " -global all SWF defines are placed in first scene replace\n" - " * Note: By default SWF defines are sent when needed\n" - " -no-ctrl uses a single stream for movie control and dictionary\n" - " * Note: this will disable ActionScript\n" - " -no-text removes all SWF text\n" - " -no-font removes all embedded SWF Fonts (terminal fonts used)\n" - " -no-line removes all lines from SWF shapes\n" - " -no-grad removes all gradients from swf shapes\n" - " -quad uses quadratic bezier curves instead of cubic ones\n" - " -xlp support for lines transparency and scalability\n" - " -flatten ang complementary angle below which 2 lines are merged\n" - " * Note: angle \'0\' means no flattening\n" - "\n" - ); + ); + while (m4b_liveenc_args[i].name) { + GF_GPACArg *arg = &m4b_liveenc_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-extract"); + } + + gf_sys_format_help(helpout, help_flags, " \n" + "Runtime options:\n" + "- q: quits\n" + "- u: inputs some commands to be sent\n" + "- U: same as u but signals the updates as critical\n" + "- e: inputs some commands to be sent without being aggregated\n" + "- E: same as e but signals the updates as critical\n" + "- f: forces RAP sending\n" + "- F: forces RAP regeneration and sending\n" + "- p: dumps current scene\n" + ); +} + +void PrintCoreUsage() +{ + gf_sys_format_help(helpout, help_flags, "# libgpac core options\n"); + gf_sys_print_core_help(helpout, 0, GF_ARGMODE_ALL, 0); } +GF_GPACArg m4b_usage_args[] = +{ + GF_DEF_ARG("h", NULL, "print help\n" + "- general: general options help\n" + "- hint: hinting options help\n" + "- dash: DASH segmenter help\n" + "- import: import options help\n" + "- encode: encode options help\n" + "- meta: meta handling options help\n" + "- extract: extraction options help\n" + "- dump: dump options help\n" + "- swf: Flash (SWF) options help\n" + "- crypt: ISMA E&A options help\n" + "- format: supported formats help\n" + "- live: BIFS streamer help\n" + "- core: libgpac core options\n" + "- all: print all the above help screens\n" + "- opts: print all options\n" + "- VAL: search for option named `VAL` (without `-` or `--`) in MP4Box, libgpac core and all filters\n" + , NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("hx", NULL, "look for given string in all possible options" + , NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("nodes", NULL, "list supported MPEG4 nodes", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("node", NULL, "get given MPEG4 node syntax and QP infolist", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("xnodes", NULL, "list supported X3D nodes", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("xnode", NULL, "get given X3D node syntax", NULL, NULL, GF_ARG_STRING, 0), + GF_DEF_ARG("snodes", NULL, "list supported SVG nodes", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("languages", NULL, "list supported ISO 639 languages", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("boxes", NULL, "list all supported ISOBMF boxes and their syntax", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("fstat", NULL, "print filter session statistics (import/export/encrypt/decrypt/dashing)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("fgraph", NULL, "print filter session graph (import/export/encrypt/decrypt/dashing)", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("v", NULL, "verbose mode", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("version", NULL, "get build version", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("-- INPUT", NULL, "escape option if INPUT starts with `-` character", NULL, NULL, GF_ARG_BOOL, 0), + {0} +}; + void PrintUsage() { - fprintf (stderr, "MP4Box [option] input [option]\n" - " -h general general options help\n" - " -h hint hinting options help\n" - " -h dash DASH segmenter help\n" - " -h import import options help\n" - " -h encode encode options help\n" - " -h meta meta handling options help\n" - " -h extract extraction options help\n" - " -h dump dump options help\n" - " -h swf Flash (SWF) options help\n" - " -h crypt ISMA E&A options help\n" - " -h format supported formats help\n" - " -h rtp file streamer help\n" - " -h live BIFS streamer help\n" - " -h all all options are printed\n" - "\n" - " -nodes lists supported MPEG4 nodes\n" - " -node NodeName gets MPEG4 node syntax and QP info\n" - " -xnodes lists supported X3D nodes\n" - " -xnode NodeName gets X3D node syntax\n" - " -snodes lists supported SVG nodes\n" - " -languages lists supported ISO 639 languages\n" - " -boxes lists all supported ISOBMF boxes and their syntax\n" - "\n" - " -quiet quiet mode\n" - " -noprog disables progress\n" - " -v verbose mode\n" - " -logs set log tools and levels, formatted as a ':'-separated list of toolX[:toolZ]@levelX\n" - " -log-file FILE sets output log file. Also works with -lf FILE\n" - " -log-clock or -lc logs time in micro sec since start time of GPAC before each log line.\n" - " -log-utc or -lu logs UTC time in ms before each log line.\n" - " -version gets build version\n" - " -- INPUT escape option if INPUT starts with - character\n" - "\n" - ); + u32 i=0; + gf_sys_format_help(helpout, help_flags, "MP4Box [option] input [option]\n" + " \n" + "# General Options:\n" + ); + while (m4b_usage_args[i].name) { + GF_GPACArg *arg = &m4b_usage_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, arg, "mp4box-general"); + } +} + +enum +{ + SEARCH_ARG_EXACT, + SEARCH_ARG_CLOSE, + SEARCH_DESC, +}; + +static Bool strstr_nocase(const char *text, const char *subtext, u32 subtext_len) +{ + if (!*text || !subtext || !subtext_len) + return GF_FALSE; + + while (*text) { + if (tolower(*text) == *subtext) { + if (!strnicmp(text, subtext, subtext_len)) + return GF_TRUE; + + } + text++; + } + return GF_FALSE; } +static u32 PrintHelpForArgs(char *arg_name, GF_GPACArg *args, u32 search_type) +{ + u32 res=0; + u32 i=0; + u32 alen = (u32) strlen(arg_name); + while (args[i].name) { + u32 flags=0; + GF_GPACArg *arg = &args[i]; + GF_GPACArg an_arg; + Bool do_match = GF_FALSE; + + if (args==ImportFileOpts) { + flags = GF_PRINTARG_COLON; + if (!strncmp(arg_name, arg->name, alen) && ((arg->name[alen]==0) || (arg->name[alen]=='='))) + do_match = GF_TRUE; + } + else if (!strcmp(arg_name, arg->name)) + do_match = GF_TRUE; + else if ((alen < (u32) strlen(arg->name)) && (arg->name[alen]==' ') && !strncmp(arg_name, arg->name, alen)) + do_match = GF_TRUE; + + if (arg_name[0] == '@') + do_match = GF_TRUE; + + if ((search_type==SEARCH_ARG_EXACT) && !do_match) { + i++; + continue; + } + if ((search_type==SEARCH_ARG_CLOSE) && !gf_sys_word_match(arg_name, arg->name)) { + i++; + continue; + } + if ((search_type==SEARCH_DESC) && !strstr_nocase(arg->description, arg_name, alen)) { + i++; + continue; + } + + an_arg = *arg; + if (search_type!=SEARCH_ARG_EXACT) { + an_arg.description = NULL; + an_arg.type = GF_ARG_BOOL; + } + gf_sys_print_arg(helpout, flags, &an_arg, ""); + res++; + i++; + } + return res; +} +static Bool PrintHelpArg(char *arg_name, u32 search_type, GF_FilterSession *fs) +{ + Bool first=GF_TRUE; + GF_GPACArg an_arg; + u32 i, count; + u32 res = 0; + u32 alen = (u32) strlen(arg_name); + res += PrintHelpForArgs(arg_name, m4b_gen_args, search_type); + res += PrintHelpForArgs(arg_name, m4b_dash_args, search_type); + res += PrintHelpForArgs(arg_name, m4b_imp_args, search_type); + res += PrintHelpForArgs(arg_name, ImportFileOpts, search_type); + res += PrintHelpForArgs(arg_name, m4b_senc_args, search_type); + res += PrintHelpForArgs(arg_name, m4b_crypt_args, search_type); + res += PrintHelpForArgs(arg_name, m4b_hint_args, search_type); + res += PrintHelpForArgs(arg_name, m4b_extr_args, search_type); + res += PrintHelpForArgs(arg_name, m4b_dump_args, search_type); + res += PrintHelpForArgs(arg_name, m4b_meta_args, search_type); + res += PrintHelpForArgs(arg_name, m4b_swf_args, search_type); + res += PrintHelpForArgs(arg_name, m4b_liveenc_args, search_type); + res += PrintHelpForArgs(arg_name, m4b_usage_args, search_type); + res += PrintHelpForArgs(arg_name, (GF_GPACArg *) gf_sys_get_options(), search_type); + + if (!fs) return res; + + memset(&an_arg, 0, sizeof(GF_GPACArg)); + count = gf_fs_filters_registers_count(fs); + for (i=0; iargs) { + u32 len; + const GF_FilterArgs *arg = ®->args[j]; + if (!arg || !arg->arg_name) break; + j++; + if ((search_type==SEARCH_ARG_EXACT) && strcmp(arg->arg_name, arg_name)) continue; + + if ((search_type==SEARCH_ARG_CLOSE) && !gf_sys_word_match(arg->arg_name, arg_name)) continue; + + if (search_type==SEARCH_DESC) { + if (!strstr_nocase(arg->arg_desc, arg_name, alen)) continue; + } + + an_arg.name = arg->arg_name; + if (search_type==SEARCH_ARG_EXACT) { + an_arg.description = arg->arg_desc; + switch (arg->arg_type) { + case GF_PROP_BOOL: + an_arg.type = GF_ARG_BOOL; + break; + case GF_PROP_UINT: + case GF_PROP_SINT: + an_arg.type = GF_ARG_INT; + break; + case GF_PROP_DOUBLE: + an_arg.type = GF_ARG_DOUBLE; + break; + case GF_PROP_STRING_LIST: + case GF_PROP_UINT_LIST: + an_arg.type = GF_ARG_STRINGS; + break; + default: + an_arg.type = GF_ARG_STRING; + break; + } + if (first) { + first = GF_FALSE; + gf_sys_format_help(helpout, 0, "\nGlobal filter session arguments. Syntax is `--arg` or `--arg=VAL`. `[F]` indicates filter name. See `gpac -h` and `gpac -h F` for more info.\n"); + } + fprintf(helpout, "[%s]", reg->name); + len = (u32)strlen(reg->name); + while (len<10) { + len++; + fprintf(helpout, " "); + } + fprintf(helpout, " "); + } + + gf_sys_print_arg(helpout, GF_PRINTARG_ADD_DASH, &an_arg, "TEST"); + res++; + } + } + if (res) return GF_TRUE; + return GF_FALSE; +} + +static void PrintHelp(char *arg_name, Bool search_desc, Bool no_match) +{ + GF_FilterSession *fs; + Bool res; + + fs = gf_fs_new_defaults(0); + + if (arg_name[0]=='-') + arg_name++; + + if (search_desc) { + char *_arg_name = gf_strdup(arg_name); + strlwr(_arg_name); + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Possible options mentionning `%s`:\n", arg_name)); + PrintHelpArg(_arg_name, SEARCH_DESC, fs); + gf_free(_arg_name); + } else { + res = no_match ? GF_FALSE : PrintHelpArg(arg_name, SEARCH_ARG_EXACT, fs); + if (!res) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Option -%s unknown, please check usage.\n", arg_name)); + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Possible options are:\n")); + + PrintHelpArg(arg_name, SEARCH_ARG_CLOSE, fs); + } + } + if (fs) + gf_fs_del(fs); +} void scene_coding_log(void *cbk, GF_LOG_Level log_level, GF_LOG_Tool log_tool, const char *fmt, va_list vlist) { @@ -908,7 +1200,7 @@ void SetupClockReferences(GF_ISOFile *file) /*base RTP payload type used (you can specify your own types if needed)*/ #define BASE_PAYT 96 -GF_Err HintFile(GF_ISOFile *file, u32 MTUSize, u32 max_ptime, u32 rtp_rate, u32 base_flags, Bool copy_data, Bool interleave, Bool regular_iod, Bool single_group) +GF_Err HintFile(GF_ISOFile *file, u32 MTUSize, u32 max_ptime, u32 rtp_rate, u32 base_flags, Bool copy_data, Bool interleave, Bool regular_iod, Bool single_group, Bool hint_no_offset) { GF_ESD *esd; GF_InitialObjectDescriptor *iod; @@ -969,6 +1261,18 @@ GF_Err HintFile(GF_ISOFile *file, u32 MTUSize, u32 max_ptime, u32 rtp_rate, u32 media_prio = 2; } break; + case GF_ISOM_MEDIA_AUXV: + if (single_av) { + media_group = 2; + media_prio = 3; + } + break; + case GF_ISOM_MEDIA_PICT: + if (single_av) { + media_group = 2; + media_prio = 4; + } + break; case GF_ISOM_MEDIA_AUDIO: if (single_av) { media_group = 2; @@ -979,7 +1283,7 @@ GF_Err HintFile(GF_ISOFile *file, u32 MTUSize, u32 max_ptime, u32 rtp_rate, u32 continue; default: /*no hinting of systems track on isma*/ - if (spec_type==GF_4CC('I','S','M','A')) continue; + if (spec_type==GF_ISOM_BRAND_ISMA) continue; } mtype = gf_isom_get_media_subtype(file, i+1, 1); if ((mtype==GF_ISOM_SUBTYPE_MPEG4) || (mtype==GF_ISOM_SUBTYPE_MPEG4_CRYP) ) mtype = gf_isom_get_mpeg4_subtype(file, i+1, 1); @@ -1030,6 +1334,10 @@ GF_Err HintFile(GF_ISOFile *file, u32 MTUSize, u32 max_ptime, u32 rtp_rate, u32 } continue; } + + if (hint_no_offset) + gf_hinter_track_force_no_offsets(hinter); + bw = gf_hinter_track_get_bandwidth(hinter); tot_bw += bw; flags = gf_hinter_track_get_flags(hinter); @@ -1078,18 +1386,17 @@ GF_Err HintFile(GF_ISOFile *file, u32 MTUSize, u32 max_ptime, u32 rtp_rate, u32 static void check_media_profile(GF_ISOFile *file, u32 track) { u8 PL; - GF_M4ADecSpecInfo dsi; GF_ESD *esd = gf_isom_get_esd(file, track, 1); if (!esd) return; switch (esd->decoderConfig->streamType) { case 0x04: PL = gf_isom_get_pl_indication(file, GF_ISOM_PL_VISUAL); - if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_MPEG4_PART2) { - GF_M4VDecSpecInfo dsi; - gf_m4v_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi); - if (dsi.VideoPL > PL) gf_isom_set_pl_indication(file, GF_ISOM_PL_VISUAL, dsi.VideoPL); - } else if ((esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_AVC) || (esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_SVC)) { + if (esd->decoderConfig->objectTypeIndication==GF_CODECID_MPEG4_PART2) { + GF_M4VDecSpecInfo vdsi; + gf_m4v_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &vdsi); + if (vdsi.VideoPL > PL) gf_isom_set_pl_indication(file, GF_ISOM_PL_VISUAL, vdsi.VideoPL); + } else if ((esd->decoderConfig->objectTypeIndication==GF_CODECID_AVC) || (esd->decoderConfig->objectTypeIndication==GF_CODECID_SVC)) { gf_isom_set_pl_indication(file, GF_ISOM_PL_VISUAL, 0x15); } else if (!PL) { gf_isom_set_pl_indication(file, GF_ISOM_PL_VISUAL, 0xFE); @@ -1098,12 +1405,15 @@ static void check_media_profile(GF_ISOFile *file, u32 track) case 0x05: PL = gf_isom_get_pl_indication(file, GF_ISOM_PL_AUDIO); switch (esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_AUDIO_AAC_MPEG2_MP: - case GPAC_OTI_AUDIO_AAC_MPEG2_LCP: - case GPAC_OTI_AUDIO_AAC_MPEG2_SSRP: - case GPAC_OTI_AUDIO_AAC_MPEG4: - gf_m4a_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi); - if (dsi.audioPL > PL) gf_isom_set_pl_indication(file, GF_ISOM_PL_AUDIO, dsi.audioPL); + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + case GF_CODECID_AAC_MPEG4: + { + GF_M4ADecSpecInfo adsi; + gf_m4a_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &adsi); + if (adsi.audioPL > PL) gf_isom_set_pl_indication(file, GF_ISOM_PL_AUDIO, adsi.audioPL); + } break; default: if (!PL) gf_isom_set_pl_indication(file, GF_ISOM_PL_AUDIO, 0xFE); @@ -1127,6 +1437,8 @@ void remove_systems_tracks(GF_ISOFile *file) for (i=0; iact_type = act_type; - meta->mime_type[0] = 0; - meta->enc_type[0] = 0; - meta->szName[0] = 0; - meta->szPath[0] = 0; meta->trackID = 0; meta->root_meta = 1; if (!opts) return 0; while (1) { + char *next; + char *szSlot; if (!opts || !opts[0]) return ret; if (opts[0]==':') opts += 1; - strcpy(szSlot, opts); - next = strchr(szSlot, ':'); - /*use ':' as separator, but beware DOS paths...*/ - if (next && next[1]=='\\') next = strchr(szSlot+2, ':'); + + szSlot = opts; + next = gf_url_colon_suffix(opts); if (next) next[0] = 0; if (!strnicmp(szSlot, "tk=", 3)) { @@ -1289,37 +1585,40 @@ static Bool parse_meta_args(MetaAction *meta, MetaActionType act_type, char *opt } else if (!strnicmp(szSlot, "ref=", 4)) { char type[10]; - sscanf(szSlot, "ref=%u,%s", &meta->ref_item_id, type); + sscanf(szSlot, "ref=%9s,%u", type, &meta->ref_item_id); meta->ref_type = GF_4CC(type[0], type[1], type[2], type[3]); ret = 1; - } + } else if (!strnicmp(szSlot, "name=", 5)) { - strcpy(meta->szName, szSlot+5); + meta->szName = gf_strdup(szSlot+5); ret = 1; } else if (!strnicmp(szSlot, "path=", 5)) { - strcpy(meta->szPath, szSlot+5); + meta->szPath = gf_strdup(szSlot+5); ret = 1; } else if (!strnicmp(szSlot, "mime=", 5)) { - meta->item_type = GF_4CC('m','i','m','e'); - strcpy(meta->mime_type, szSlot+5); + meta->item_type = GF_META_ITEM_TYPE_MIME; + meta->mime_type = gf_strdup(szSlot+5); ret = 1; } else if (!strnicmp(szSlot, "encoding=", 9)) { - strcpy(meta->enc_type, szSlot+9); + meta->enc_type = gf_strdup(szSlot+9); ret = 1; } else if (!strnicmp(szSlot, "image-size=", 11)) { if (!meta->image_props) { GF_SAFEALLOC(meta->image_props, GF_ImageItemProperties); + if (!meta->image_props) return 0; } + sscanf(szSlot+11, "%dx%d", &meta->image_props->width, &meta->image_props->height); ret = 1; } else if (!strnicmp(szSlot, "image-pasp=", 11)) { if (!meta->image_props) { GF_SAFEALLOC(meta->image_props, GF_ImageItemProperties); + if (!meta->image_props) return 0; } sscanf(szSlot+11, "%dx%d", &meta->image_props->hSpacing, &meta->image_props->vSpacing); ret = 1; @@ -1327,47 +1626,71 @@ static Bool parse_meta_args(MetaAction *meta, MetaActionType act_type, char *opt else if (!strnicmp(szSlot, "image-rloc=", 11)) { if (!meta->image_props) { GF_SAFEALLOC(meta->image_props, GF_ImageItemProperties); + if (!meta->image_props) return 0; } sscanf(szSlot+11, "%dx%d", &meta->image_props->hOffset, &meta->image_props->vOffset); ret = 1; } - else if (!strnicmp(szSlot, "image-irot=", 11)) { + else if (!strnicmp(szSlot, "rotation=", 9)) { if (!meta->image_props) { GF_SAFEALLOC(meta->image_props, GF_ImageItemProperties); + if (!meta->image_props) return 0; } - meta->image_props->angle = atoi(szSlot+11); + meta->image_props->angle = atoi(szSlot+9); ret = 1; } - else if (!strnicmp(szSlot, "image-hidden", 12)) { + else if (!stricmp(szSlot, "hidden")) { if (!meta->image_props) { GF_SAFEALLOC(meta->image_props, GF_ImageItemProperties); + if (!meta->image_props) return 0; } meta->image_props->hidden = GF_TRUE; ret = 1; } - else if (!strnicmp(szSlot, "tilemode=", 9)) { + else if (!stricmp(szSlot, "alpha")) { if (!meta->image_props) { GF_SAFEALLOC(meta->image_props, GF_ImageItemProperties); + if (!meta->image_props) return 0; } - if (!strnicmp(szSlot + 9, "nobase", 6)) { - meta->image_props->tile_mode = TILE_ITEM_ALL_NO_BASE; - } else if (!strnicmp(szSlot + 9, "base", 4)) { - meta->image_props->tile_mode = TILE_ITEM_ALL_BASE; - } else if (!strnicmp(szSlot + 9, "grid", 4)) { - meta->image_props->tile_mode = TILE_ITEM_ALL_GRID; - } else { - meta->image_props->tile_mode = TILE_ITEM_SINGLE; - sscanf(szSlot + 9, "%d", &meta->image_props->single_tile_number); - } + meta->image_props->alpha = GF_TRUE; + ret = 1; } - else if (!strnicmp(szSlot, "dref", 4)) { + else if (!strnicmp(szSlot, "time=", 5)) { + if (!meta->image_props) { + GF_SAFEALLOC(meta->image_props, GF_ImageItemProperties); + if (!meta->image_props) return 0; + } + meta->image_props->time = atof(szSlot+5); + ret = 1; + } + else if (!stricmp(szSlot, "split_tiles")) { + if (!meta->image_props) { + GF_SAFEALLOC(meta->image_props, GF_ImageItemProperties); + if (!meta->image_props) return 0; + } + meta->image_props->tile_mode = TILE_ITEM_ALL_BASE; + ret = 1; + } + else if (!stricmp(szSlot, "dref")) { meta->use_dref = 1; ret = 1; } + else if (!stricmp(szSlot, "primary")) { + meta->primary = 1; + ret = 1; + } else if (!stricmp(szSlot, "binary")) { if (meta->act_type==META_ACTION_SET_XML) meta->act_type=META_ACTION_SET_BINARY_XML; ret = 1; } + else if (!strnicmp(szSlot, "icc_path=", 9)) { + if (!meta->image_props) { + GF_SAFEALLOC(meta->image_props, GF_ImageItemProperties); + if (!meta->image_props) return 0; + } + strcpy(meta->image_props->iccPath, szSlot+9); + ret = 1; + } else if (!strchr(szSlot, '=')) { switch (meta->act_type) { case META_ACTION_SET_TYPE: @@ -1379,7 +1702,12 @@ static Bool parse_meta_args(MetaAction *meta, MetaActionType act_type, char *opt case META_ACTION_ADD_IMAGE_ITEM: case META_ACTION_SET_XML: case META_ACTION_DUMP_XML: - strcpy(meta->szPath, szSlot); + if (!strncmp(szSlot, "dopt", 4) || !strncmp(szSlot, "sopt", 4) || !strncmp(szSlot, "@@", 2)) { + if (next) next[0]=':'; + next=NULL; + } + //cat as -add arg + gf_dynstrcat(&meta->szPath, szSlot, ":"); ret = 1; break; case META_ACTION_REM_ITEM: @@ -1392,12 +1720,13 @@ static Bool parse_meta_args(MetaAction *meta, MetaActionType act_type, char *opt break; } } + if (!next) break; opts += strlen(szSlot); + next[0] = ':'; } return ret; } -#endif - +#endif //GPAC_DISABLE_ISOM_WRITE typedef enum { TSEL_ACTION_SET_PARAM = 0, @@ -1408,24 +1737,25 @@ typedef enum { typedef struct { TSELActionType act_type; - u32 trackID; + GF_ISOTrackID trackID; - u32 refTrackID; + GF_ISOTrackID refTrackID; u32 criteria[30]; u32 nb_criteria; Bool is_switchGroup; u32 switchGroupID; } TSELAction; +#ifndef GPAC_DISABLE_ISOM_WRITE static Bool parse_tsel_args(TSELAction **__tsel_list, char *opts, u32 *nb_tsel_act, TSELActionType act) { - u32 refTrackID = 0; + GF_ISOTrackID refTrackID = 0; Bool has_switch_id; u32 switch_id = 0; u32 criteria[30]; u32 nb_criteria = 0; TSELAction *tsel_act; - char szSlot[1024], *next; + char szSlot[1024]; TSELAction *tsel_list; has_switch_id = 0; @@ -1433,12 +1763,11 @@ static Bool parse_tsel_args(TSELAction **__tsel_list, char *opts, u32 *nb_tsel_a if (!opts) return 0; while (1) { + char *next; if (!opts || !opts[0]) return 1; if (opts[0]==':') opts += 1; strcpy(szSlot, opts); - next = strchr(szSlot, ':'); - /*use ':' as separator, but beware DOS paths...*/ - if (next && next[1]=='\\') next = strchr(szSlot+2, ':'); + next = gf_url_colon_suffix(szSlot); if (next) next[0] = 0; @@ -1488,9 +1817,15 @@ static Bool parse_tsel_args(TSELAction **__tsel_list, char *opts, u32 *nb_tsel_a } return 1; } +#endif // GPAC_DISABLE_ISOM_WRITE + +#define CHECK_NEXT_ARG if (i+1==(u32)argc) {\ + fprintf(stderr, "Missing arg - please check usage\n"); return mp4box_cleanup(1);\ + } else { \ + has_next_arg = GF_TRUE;\ + } -#define CHECK_NEXT_ARG if (i+1==(u32)argc) { fprintf(stderr, "Missing arg - please check usage\n"); return mp4box_cleanup(1); } typedef enum { TRAC_ACTION_REM_TRACK = 0, @@ -1509,23 +1844,29 @@ typedef enum { TRAC_ACTION_SET_ID = 13, TRAC_ACTION_SET_UDTA = 14, TRAC_ACTION_SWAP_ID = 15, + TRAC_ACTION_REM_NON_REFS = 16, + TRAC_ACTION_SET_CLAP = 17, + TRAC_ACTION_SET_MX = 18, } TrackActionType; typedef struct { TrackActionType act_type; - u32 trackID; + GF_ISOTrackID trackID; char lang[10]; s32 delay_ms; const char *kms; const char *hdl_name; s32 par_num, par_den; + u8 force_par, rewrite_bs; u32 dump_type, sample_num; char *out_name; char *src_name; u32 udta_type; char *kind_scheme, *kind_value; u32 newTrackID; + s32 clap_wnum, clap_wden, clap_hnum, clap_hden, clap_honum, clap_hoden, clap_vonum, clap_voden; + s32 mx[9]; } TrackAction; enum @@ -1534,7 +1875,8 @@ enum GF_ISOM_CONV_TYPE_ISMA_EX, GF_ISOM_CONV_TYPE_3GPP, GF_ISOM_CONV_TYPE_IPOD, - GF_ISOM_CONV_TYPE_PSP + GF_ISOM_CONV_TYPE_PSP, + GF_ISOM_CONV_TYPE_MOV }; @@ -1542,21 +1884,8 @@ enum GF_DashSegmenterInput *set_dash_input(GF_DashSegmenterInput *dash_inputs, char *name, u32 *nb_dash_inputs) { GF_DashSegmenterInput *di; - char *sep; - // skip ./ and ../, and look for first . to figure out extension - if ((name[1]=='/') || (name[2]=='/') || (name[1]=='\\') || (name[2]=='\\') ) sep = strchr(name+3, '.'); - else { - char *s2 = strchr(name, ':'); - sep = strchr(name, '.'); - if (sep && s2 && (s2 - sep) < 0) { - sep = name; - } - } - - //then look for our opt separator : - sep = strchr(sep ? sep : name, ':'); - - if (sep && (sep[1]=='\\')) sep = strchr(sep+1, ':'); + char *other_opts = NULL; + char *sep = gf_url_colon_suffix(name); dash_inputs = gf_realloc(dash_inputs, sizeof(GF_DashSegmenterInput) * (*nb_dash_inputs + 1) ); memset(&dash_inputs[*nb_dash_inputs], 0, sizeof(GF_DashSegmenterInput) ); @@ -1567,7 +1896,7 @@ GF_DashSegmenterInput *set_dash_input(GF_DashSegmenterInput *dash_inputs, char * char *opts, *first_opt; opts = first_opt = sep; while (opts) { - sep = strchr(opts, ':'); + sep = gf_url_colon_suffix(opts); while (sep) { /* this is a real separator if it is followed by a keyword we are looking for */ if (!strnicmp(sep, ":id=", 4) || @@ -1577,28 +1906,38 @@ GF_DashSegmenterInput *set_dash_input(GF_DashSegmenterInput *dash_inputs, char * !strnicmp(sep, ":bandwidth=", 11) || !strnicmp(sep, ":role=", 6) || !strnicmp(sep, ":desc", 5) || - !strnicmp(sep, ":duration=", 10) || /*legacy*/!strnicmp(sep, ":period_duration=", 10) || - !strnicmp(sep, ":xlink=", 7)) { + !strnicmp(sep, ":sscale", 7) || + !strnicmp(sep, ":duration=", 10) || + !strnicmp(sep, ":period_duration=", 10) || + !strnicmp(sep, ":pdur=", 6) || + !strnicmp(sep, ":xlink=", 7) || + !strnicmp(sep, ":asID=", 6) || + !strnicmp(sep, ":sn=", 4) || + !strnicmp(sep, ":tpl=", 5) || + !strnicmp(sep, ":hls=", 5) || + !strnicmp(sep, ":trackID=", 9) || + !strnicmp(sep, ":@@", 3) + ) { break; } else { + char *nsep = gf_url_colon_suffix(sep+1); + if (nsep) nsep[0] = 0; + + gf_dynstrcat(&other_opts, sep, ":"); + + if (nsep) nsep[0] = ':'; + sep = strchr(sep+1, ':'); } } - if (sep && !strncmp(sep, "://", 3)) sep = strchr(sep+3, ':'); + if (sep && !strncmp(sep, "://", 3) && strnicmp(sep, ":@@", 3)) sep = gf_url_colon_suffix(sep+3); if (sep) sep[0] = 0; if (!strnicmp(opts, "id=", 3)) { - u32 i; di->representationID = gf_strdup(opts+3); - /* check to see if this representation Id has already been assigned */ - for (i=0; i<(*nb_dash_inputs)-1; i++) { - GF_DashSegmenterInput *other_di; - other_di = &dash_inputs[i]; - if (!strcmp(other_di->representationID, di->representationID)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error: Duplicate Representation ID \"%s\" in command line\n", di->representationID)); - } - } - } else if (!strnicmp(opts, "dur=", 4)) di->media_duration = (Double)atof(opts+4); + /*we allow the same repID to be set to force muxed representations*/ + } + else if (!strnicmp(opts, "dur=", 4)) di->media_duration = (Double)atof(opts+4); else if (!strnicmp(opts, "period=", 7)) di->periodID = gf_strdup(opts+7); else if (!strnicmp(opts, "BaseURL=", 8)) { di->baseURL = (char **)gf_realloc(di->baseURL, (di->nb_baseURL+1)*sizeof(char *)); @@ -1643,10 +1982,19 @@ GF_DashSegmenterInput *set_dash_input(GF_DashSegmenterInput *dash_inputs, char * } else if (!strnicmp(opts, "xlink=", 6)) di->xlink = gf_strdup(opts+6); - else if (!strnicmp(opts, "period_duration=", 16)) { - di->period_duration = (Double) atof(opts+16); - } else if (!strnicmp(opts, "duration=", 9)) { - di->period_duration = (Double) atof(opts+9); /*legacy: use period_duration instead*/ + else if (!strnicmp(opts, "sscale", 6)) di->sscale = GF_TRUE; + else if (!strnicmp(opts, "pdur=", 5)) di->period_duration = (Double) atof(opts+5); + else if (!strnicmp(opts, "period_duration=", 16)) di->period_duration = (Double) atof(opts+16); + else if (!strnicmp(opts, "duration=", 9)) di->dash_duration = (Double) atof(opts+9); + else if (!strnicmp(opts, "asID=", 5)) di->asID = atoi(opts+5); + else if (!strnicmp(opts, "sn=", 3)) di->startNumber = atoi(opts+3); + else if (!strnicmp(opts, "tpl=", 4)) di->seg_template = gf_strdup(opts+4); + else if (!strnicmp(opts, "hls=", 4)) di->hls_pl = gf_strdup(opts+4); + else if (!strnicmp(opts, "trackID=", 8)) di->track_id = atoi(opts+8); + else if (!strnicmp(opts, "@@", 2)) { + di->filter_chain = gf_strdup(opts+2); + if (sep) sep[0] = ':'; + sep = NULL; } if (!sep) break; @@ -1656,6 +2004,8 @@ GF_DashSegmenterInput *set_dash_input(GF_DashSegmenterInput *dash_inputs, char * first_opt[0] = '\0'; } di->file_name = name; + di->source_opts = other_opts; + if (!di->representationID) { char szRep[100]; sprintf(szRep, "%d", *nb_dash_inputs); @@ -1669,9 +2019,9 @@ static GF_Err parse_track_action_params(char *string, TrackAction *action) { char *param = string; if (!action || !string) return GF_BAD_PARAM; - + while (param) { - param = strchr(param, ':'); + param = gf_url_colon_suffix(param); if (param) { *param = 0; param++; @@ -1719,11 +2069,11 @@ static u32 create_new_track_action(char *string, TrackAction **actions, u32 *nb_ } #ifndef GPAC_DISABLE_CORE_TOOLS -static GF_Err nhml_bs_to_bin(char *inName, char *outName, u32 dump_std) +static GF_Err xml_bs_to_bin(char *inName, char *outName, u32 dump_std) { GF_Err e; GF_XMLNode *root; - char *data = NULL; + u8 *data = NULL; u32 data_size; GF_DOMParser *dom = gf_xml_dom_new(); @@ -1739,7 +2089,7 @@ static GF_Err nhml_bs_to_bin(char *inName, char *outName, u32 dump_std) return GF_OK; } - e = gf_xml_parse_bit_sequence(root, &data, &data_size); + e = gf_xml_parse_bit_sequence(root, inName, &data, &data_size); gf_xml_dom_del(dom); if (e) { @@ -1748,7 +2098,7 @@ static GF_Err nhml_bs_to_bin(char *inName, char *outName, u32 dump_std) } if (dump_std) { - fwrite(data, 1, data_size, stdout); + gf_fwrite(data, data_size, stdout); } else { FILE *t; char szFile[GF_MAX_PATH]; @@ -1763,7 +2113,7 @@ static GF_Err nhml_bs_to_bin(char *inName, char *outName, u32 dump_std) fprintf(stderr, "Failed to open file %s\n", szFile); e = GF_IO_ERR; } else { - if (fwrite(data, 1, data_size, t) != data_size) { + if (gf_fwrite(data, data_size, t) != data_size) { fprintf(stderr, "Failed to write output to file %s\n", szFile); e = GF_IO_ERR; } @@ -1775,6 +2125,300 @@ static GF_Err nhml_bs_to_bin(char *inName, char *outName, u32 dump_std) } #endif /*GPAC_DISABLE_CORE_TOOLS*/ +static u64 do_size_top_boxes(char *inName, char *compress_top_boxes, u32 mode) +{ + FILE *in; + u64 top_size = 0; + Bool do_all = GF_FALSE; + GF_BitStream *bs_in; + if (!compress_top_boxes) return GF_BAD_PARAM; + if (!strcmp(compress_top_boxes, "all")) + do_all = GF_TRUE; + + in = gf_fopen(inName, "rb"); + if (!in) return GF_URL_ERROR; + bs_in = gf_bs_from_file(in, GF_BITSTREAM_READ); + while (gf_bs_available(bs_in)) { + const char *stype; + u32 hdr_size = 8; + u64 lsize = gf_bs_read_u32(bs_in); + u32 type = gf_bs_read_u32(bs_in); + + if (lsize==1) { + lsize = gf_bs_read_u64(bs_in); + hdr_size = 16; + } else if (lsize==0) { + lsize = gf_bs_available(bs_in) + 8; + } + stype = gf_4cc_to_str(type); + if (do_all || strstr(compress_top_boxes, stype)) { + //only count boxes + if (mode==2) { + top_size += 1; + } else { + top_size += lsize; + } + } + gf_bs_skip_bytes(bs_in, lsize - hdr_size); + } + gf_bs_del(bs_in); + gf_fclose(in); + return top_size; + +} + +static GF_Err do_compress_top_boxes(char *inName, char *outName, char *compress_top_boxes, u32 comp_top_box_version, Bool use_lzma) +{ + FILE *in, *out; + u8 *buf; + u32 buf_alloc, comp_size, start_offset; + s32 bytes_comp=0; + s32 bytes_uncomp=0; + GF_Err e = GF_OK; + u64 source_size, dst_size; + u32 orig_box_overhead; + u32 final_box_overhead; + u32 gzip_code = use_lzma ? GF_4CC('l','z','m','a') : GF_4CC('g','z','i','p') ; + u32 nb_added_box_bytes=0; + Bool has_mov = GF_FALSE; + u32 range_idx, nb_ranges=0; + Bool replace_all = !strcmp(compress_top_boxes, "*"); + Bool requires_byte_ranges=GF_FALSE; + GF_BitStream *bs_in, *bs_out; + u32 idx_size=0, nb_moof; + struct _ranges { + u32 size, csize; + } *ranges=NULL; + + if (!outName) { + fprintf(stderr, "Missing output file name\n"); + return GF_BAD_PARAM; + } + + in = gf_fopen(inName, "rb"); + if (!in) return GF_URL_ERROR; + out = gf_fopen(outName, "wb"); + if (!out) return GF_IO_ERR; + + buf_alloc = 4096; + buf = gf_malloc(buf_alloc); + + bs_in = gf_bs_from_file(in, GF_BITSTREAM_READ); + source_size = gf_bs_get_size(bs_in); + + bs_out = gf_bs_from_file(out, GF_BITSTREAM_WRITE); + + start_offset = 0; + nb_moof = 0; + if (comp_top_box_version==2) { + u32 i; + while (gf_bs_available(bs_in)) { + u32 size = gf_bs_read_u32(bs_in); + u32 type = gf_bs_read_u32(bs_in); + const char *b4cc = gf_4cc_to_str(type); + const char *replace = strstr(compress_top_boxes, b4cc); + + if (start_offset) { + Bool compress = (replace || replace_all) ? 1 : 0; + ranges = gf_realloc(ranges, sizeof(struct _ranges)*(nb_ranges+1)); + ranges[nb_ranges].csize = compress; + ranges[nb_ranges].size = size-8; + nb_ranges++; + } + if (!strcmp(b4cc, "ftyp") || !strcmp(b4cc, "styp")) { + if (!start_offset) start_offset = (u32) gf_bs_get_position(bs_in) + size-8; + } + if (!strcmp(b4cc, "sidx") || !strcmp(b4cc, "ssix")) { + requires_byte_ranges = GF_TRUE; + } + if (!strcmp(b4cc, "moof")) + nb_moof++; + + gf_bs_skip_bytes(bs_in, size-8); + } + + gf_bs_seek(bs_in, 0); + if (buf_allocbuf_alloc) nbytes=buf_alloc; + gf_bs_read_data(bs_in, buf, nbytes); + gf_bs_write_data(bs_out, buf, nbytes); + size-=nbytes; + } + continue; + } + orig_box_overhead += size; + + if (comp_top_box_version != 1) + size-=8; + + if (size>buf_alloc) { + buf_alloc = size; + buf = gf_realloc(buf, buf_alloc); + } + gf_bs_read_data(bs_in, buf, size); + + if (comp_top_box_version != 1) + replace+=5; + + comp_size = buf_alloc; + + if (use_lzma) { + e = gf_lz_compress_payload(&buf, size, &comp_size); + } else { + e = gf_gz_compress_payload(&buf, size, &comp_size); + } + if (e) break; + + if (comp_size>buf_alloc) { + buf_alloc = comp_size; + } + bytes_uncomp += size; + bytes_comp += comp_size; + if (comp_top_box_version==1) + nb_added_box_bytes +=8; + + //write size + gf_bs_write_u32(bs_out, comp_size+8); + //write type + if (comp_top_box_version==1) + gf_bs_write_u32(bs_out, gzip_code); + else + gf_bs_write_data(bs_out, replace, 4); + //write data + gf_bs_write_data(bs_out, buf, comp_size); + + final_box_overhead += 8+comp_size; + + if (ranges) { + assert(ranges[range_idx].size == size); + ranges[range_idx].csize = comp_size; + range_idx++; + } + } + dst_size = gf_bs_get_position(bs_out); + + if (comp_top_box_version==2) { + u32 i; + gf_bs_seek(bs_out, start_offset); + gf_bs_write_u32(bs_out, idx_size); + gf_bs_write_u32(bs_out, GF_4CC('c','m','a','p')); + gf_bs_write_u32(bs_out, gzip_code); + gf_bs_write_u32(bs_out, nb_ranges); + for (i=0; i0xFFFF ? 1 : 0; + gf_bs_write_int(bs_out, ranges[i].csize ? 1 : 0, 1); + gf_bs_write_int(bs_out, large_size ? 1 : 0, 1); + gf_bs_write_int(bs_out, 0, 6); + large_size = large_size ? 32 : 16; + + gf_bs_write_int(bs_out, ranges[i].size, large_size); + if (ranges[i].csize) + gf_bs_write_int(bs_out, ranges[i].csize, large_size); + } + final_box_overhead += idx_size; + nb_added_box_bytes += idx_size; + + } + gf_bs_del(bs_in); + gf_bs_del(bs_out); + gf_fclose(in); + gf_fclose(out); + if (e) { + fprintf(stderr, "Error compressing: %s\n", gf_error_to_string(e)); + return e; + } + + if (has_mov) { + u32 i, nb_tracks, nb_samples; + GF_ISOFile *mov; + Double rate, new_rate, duration; + + mov = gf_isom_open(inName, GF_ISOM_OPEN_READ, NULL); + duration = (Double) gf_isom_get_duration(mov); + duration /= gf_isom_get_timescale(mov); + + nb_samples = 0; + nb_tracks = gf_isom_get_track_count(mov); + for (i=0; irepresentationID) gf_free(di->representationID); if (di->periodID) gf_free(di->periodID); if (di->xlink) gf_free(di->xlink); + if (di->seg_template) gf_free(di->seg_template); + if (di->hls_pl) gf_free(di->hls_pl); + if (di->source_opts) gf_free(di->source_opts); + if (di->filter_chain) gf_free(di->filter_chain); if (di->roles) { for (j = 0; jnb_roles; j++) { @@ -2006,7 +2656,7 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) u32 i = *current_index; /*parse our args*/ { - arg = argv[i]; + char *arg = argv[i]; if (!stricmp(arg, "-itags")) { CHECK_NEXT_ARG itunes_tags = argv[i + 1]; @@ -2023,6 +2673,7 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) remove_hint = 1; } else if (!stricmp(arg, "-copy")) HintCopy = 1; + else if (!stricmp(arg, "-no-offset")) hint_no_offset = GF_TRUE; else if (!stricmp(arg, "-tight")) { FullInter = 1; open_edit = GF_TRUE; @@ -2030,12 +2681,12 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) } else if (!stricmp(arg, "-ocr")) force_ocr = 1; else if (!stricmp(arg, "-latm")) hint_flags |= GP_RTP_PCK_USE_LATM_AAC; - else if (!stricmp(arg, "-rap")) { + else if (!stricmp(arg, "-rap") || !stricmp(arg, "-refonly")) { if ((i + 1 < (u32)argc) && (argv[i + 1][0] != '-')) { if (sscanf(argv[i + 1], "%d", &trackID) == 1) { tracks = gf_realloc(tracks, sizeof(TrackAction) * (nb_track_act + 1)); memset(&tracks[nb_track_act], 0, sizeof(TrackAction)); - tracks[nb_track_act].act_type = TRAC_ACTION_REM_NON_RAP; + tracks[nb_track_act].act_type = !stricmp(arg, "-rap") ? TRAC_ACTION_REM_NON_RAP : TRAC_ACTION_REM_NON_REFS; tracks[nb_track_act].trackID = trackID; nb_track_act++; i++; @@ -2048,6 +2699,9 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) else if (!stricmp(arg, "-frag-rap")) { frag_at_rap = 1; } + else if (!stricmp(arg, "-mfra")) { + use_mfra = GF_TRUE; + } else if (!stricmp(arg, "-ts")) hint_flags |= GP_RTP_PCK_SIGNAL_TS; else if (!stricmp(arg, "-size")) hint_flags |= GP_RTP_PCK_SIGNAL_SIZE; else if (!stricmp(arg, "-idx")) hint_flags |= GP_RTP_PCK_SIGNAL_AU_IDX; @@ -2132,10 +2786,15 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) else if (!stricmp(arg, "-iod")) regular_iod = 1; else if (!stricmp(arg, "-flat")) { open_edit = GF_TRUE; - do_flat = GF_TRUE; + do_flat = 1; } else if (!stricmp(arg, "-keep-utc")) keep_utc = GF_TRUE; - else if (!stricmp(arg, "-new")) force_new = GF_TRUE; + else if (!stricmp(arg, "-new")) force_new = 1; + else if (!stricmp(arg, "-newfs")) { + force_new = 2; + interleaving_time = 0.5; + do_flat = 1; + } else if (!stricmp(arg, "-timescale")) { CHECK_NEXT_ARG timescale = atoi(argv[i + 1]); @@ -2156,7 +2815,7 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) nb_add++; i++; } - else if (!stricmp(arg, "-cat") || !stricmp(arg, "-catx")) { + else if (!stricmp(arg, "-cat") || !stricmp(arg, "-catx") || !stricmp(arg, "-catpl")) { CHECK_NEXT_ARG nb_cat++; i++; @@ -2194,51 +2853,130 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) nb_track_act++; i++; } - else if (!stricmp(arg, "-set-track-id") || !stricmp(arg, "-swap-track-id")) { - char *sep; + else if (!stricmp(arg, "-set-track-id") || !stricmp(arg, "-swap-track-id")) { + char *sep; + CHECK_NEXT_ARG + tracks = gf_realloc(tracks, sizeof(TrackAction) * (nb_track_act + 1)); + memset(&tracks[nb_track_act], 0, sizeof(TrackAction)); + tracks[nb_track_act].act_type = !stricmp(arg, "-set-track-id") ? TRAC_ACTION_SET_ID : TRAC_ACTION_SWAP_ID; + sep = strchr(argv[i + 1], ':'); + if (!sep) { + fprintf(stderr, "Bad format for -set-track-id - expecting \"id1:id2\" got \"%s\"\n", argv[i + 1]); + return 2; + } + *sep = 0; + tracks[nb_track_act].trackID = atoi(argv[i + 1]); + *sep = ':'; + sep++; + tracks[nb_track_act].newTrackID = atoi(sep); + open_edit = GF_TRUE; + nb_track_act++; + i++; + } + else if (!stricmp(arg, "-par")) { + char szTK[20], *ext; + CHECK_NEXT_ARG + tracks = gf_realloc(tracks, sizeof(TrackAction) * (nb_track_act + 1)); + memset(&tracks[nb_track_act], 0, sizeof(TrackAction)); + + tracks[nb_track_act].act_type = TRAC_ACTION_SET_PAR; + assert(strlen(argv[i + 1]) + 1 <= sizeof(szTK)); + strncpy(szTK, argv[i + 1], sizeof(szTK)); + ext = strchr(szTK, '='); + if (!ext) { + fprintf(stderr, "Bad format for track par - expecting tkID=none or tkID=PAR_NUM:PAR_DEN got %s\n", argv[i + 1]); + return 2; + } + if (!stricmp(ext + 1, "none")) { + tracks[nb_track_act].par_num = tracks[nb_track_act].par_den = 0; + } + else if (!stricmp(ext + 1, "auto")) { + tracks[nb_track_act].par_num = tracks[nb_track_act].par_den = -1; + tracks[nb_track_act].force_par = 1; + } + else if (!stricmp(ext + 1, "force")) { + tracks[nb_track_act].par_num = tracks[nb_track_act].par_den = 1; + tracks[nb_track_act].force_par = 1; + } + else { + if (ext[1]=='w') { + tracks[nb_track_act].rewrite_bs = 1; + ext++; + } + sscanf(ext + 1, "%d", &tracks[nb_track_act].par_num); + ext = strchr(ext + 1, ':'); + if (!ext) { + fprintf(stderr, "Bad format for track par - expecting tkID=PAR_NUM:PAR_DEN got %s\n", argv[i + 1]); + return 2; + } + sscanf(ext + 1, "%d", &tracks[nb_track_act].par_den); + } + ext[0] = 0; + tracks[nb_track_act].trackID = atoi(szTK); + open_edit = GF_TRUE; + nb_track_act++; + i++; + } + else if (!stricmp(arg, "-clap")) { + char szTK[200], *ext; + TrackAction *tka; CHECK_NEXT_ARG tracks = gf_realloc(tracks, sizeof(TrackAction) * (nb_track_act + 1)); memset(&tracks[nb_track_act], 0, sizeof(TrackAction)); - tracks[nb_track_act].act_type = !stricmp(arg, "-set-track-id") ? TRAC_ACTION_SET_ID : TRAC_ACTION_SWAP_ID; - sep = strchr(argv[i + 1], ':'); - if (!sep) { - fprintf(stderr, "Bad format for -set-track-id - expecting \"id1:id2\" got \"%s\"\n", argv[i + 1]); + + tracks[nb_track_act].act_type = TRAC_ACTION_SET_CLAP; + assert(strlen(argv[i + 1]) + 1 <= sizeof(szTK)); + strncpy(szTK, argv[i + 1], sizeof(szTK)); + ext = strchr(szTK, '='); + if (!ext) { + fprintf(stderr, "Bad format for track clap - expecting tkID=none or tkID=Wn,Wd,Hn,Hd,HOn,HOd,VOn,VOd got %s\n", argv[i + 1]); return 2; } - *sep = 0; - tracks[nb_track_act].trackID = atoi(argv[i + 1]); - *sep = ':'; - sep++; - tracks[nb_track_act].newTrackID = atoi(sep); + tka = &tracks[nb_track_act]; + if (!stricmp(ext + 1, "none")) { + tka->clap_wnum= tka->clap_wden = tka->clap_hnum = tka->clap_hden = tka->clap_honum = tka->clap_hoden = tka->clap_vonum = tka->clap_voden = 0; + } else { + if (sscanf(ext + 1, "%d,%d,%d,%d,%d,%d,%d,%d", &tka->clap_wnum, &tka->clap_wden, &tka->clap_hnum, &tka->clap_hden, &tka->clap_honum, &tka->clap_hoden, &tka->clap_vonum, &tka->clap_voden) != 8) { + + fprintf(stderr, "Bad format for track clap - expecting tkID=none or tkID=Wn,Wd,Hn,Hd,HOn,HOd,VOn,VOd got %s\n", argv[i + 1]); + return 2; + } + } + ext[0] = 0; + tracks[nb_track_act].trackID = atoi(szTK); open_edit = GF_TRUE; nb_track_act++; i++; } - else if (!stricmp(arg, "-par")) { - char szTK[20], *ext; + else if (!stricmp(arg, "-mx")) { + char szTK[200], *ext; + TrackAction *tka; CHECK_NEXT_ARG tracks = gf_realloc(tracks, sizeof(TrackAction) * (nb_track_act + 1)); memset(&tracks[nb_track_act], 0, sizeof(TrackAction)); - tracks[nb_track_act].act_type = TRAC_ACTION_SET_PAR; + tracks[nb_track_act].act_type = TRAC_ACTION_SET_MX; assert(strlen(argv[i + 1]) + 1 <= sizeof(szTK)); strncpy(szTK, argv[i + 1], sizeof(szTK)); ext = strchr(szTK, '='); if (!ext) { - fprintf(stderr, "Bad format for track par - expecting ID=PAR_NUM:PAR_DEN got %s\n", argv[i + 1]); + fprintf(stderr, "Bad format for track matrix - expecting ID=none or ID=M1:M2:M3:M4:M5:M6:M7:M8:M9 got %s\n", argv[i + 1]); return 2; } + tka = &tracks[nb_track_act]; if (!stricmp(ext + 1, "none")) { - tracks[nb_track_act].par_num = tracks[nb_track_act].par_den = -1; - } - else { - sscanf(ext + 1, "%d", &tracks[nb_track_act].par_num); - ext = strchr(ext + 1, ':'); - if (!ext) { - fprintf(stderr, "Bad format for track par - expecting ID=PAR_NUM:PAR_DEN got %s\n", argv[i + 1]); + memset(tka->mx, 0, sizeof(s32)*9); + } else { + s32 res; + if (strstr(ext+1, "0x")) { + res = sscanf(ext + 1, "0x%d:0x%d:0x%d:0x%d:0x%d:0x%d:0x%d:0x%d:0x%d", &tka->mx[0], &tka->mx[1], &tka->mx[2], &tka->mx[3], &tka->mx[4], &tka->mx[5], &tka->mx[6], &tka->mx[7], &tka->mx[8]); + } else { + res = sscanf(ext + 1, "%d:%d:%d:%d:%d:%d:%d:%d:%d", &tka->mx[0], &tka->mx[1], &tka->mx[2], &tka->mx[3], &tka->mx[4], &tka->mx[5], &tka->mx[6], &tka->mx[7], &tka->mx[8]); + } + if (res != 9) { + fprintf(stderr, "Bad format for track matrix - expecting ID=none or ID=M1:M2:M3:M4:M5:M6:M7:M8:M9 got %s\n", argv[i + 1]); return 2; } - sscanf(ext + 1, "%d", &tracks[nb_track_act].par_den); } ext[0] = 0; tracks[nb_track_act].trackID = atoi(szTK); @@ -2246,6 +2984,27 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) nb_track_act++; i++; } + else if (!stricmp(arg, "-hdr")) { + CHECK_NEXT_ARG + high_dynamc_range_filename = argv[i + 1]; + i++; + } + else if (!stricmp(arg, "-bo")) { + freeze_box_order = GF_TRUE; + } + else if (!stricmp(arg, "-patch")) { + CHECK_NEXT_ARG + box_patch_filename = argv[i + 1]; + char *sep = strchr(box_patch_filename, '='); + if (sep) { + sep[0] = 0; + box_patch_trackID = atoi(box_patch_filename); + sep[0] = '='; + box_patch_filename = sep+1; + } + open_edit = GF_TRUE; + i++; + } else if (!stricmp(arg, "-lang")) { char szTK[20], *ext; CHECK_NEXT_ARG @@ -2254,7 +3013,8 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) tracks[nb_track_act].act_type = TRAC_ACTION_SET_LANGUAGE; tracks[nb_track_act].trackID = 0; - strcpy(szTK, argv[i + 1]); + strncpy(szTK, argv[i + 1], sizeof(szTK)-1); + szTK[sizeof(szTK)-1] = 0; ext = strchr(szTK, '='); if (!strnicmp(argv[i + 1], "all=", 4)) { strncpy(tracks[nb_track_act].lang, argv[i + 1] + 4, 10); @@ -2332,10 +3092,11 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) tracks = gf_realloc(tracks, sizeof(TrackAction) * (nb_track_act + 1)); memset(&tracks[nb_track_act], 0, sizeof(TrackAction)); - strcpy(szTK, argv[i + 1]); + strncpy(szTK, argv[i + 1], sizeof(szTK)-1); + szTK[sizeof(szTK)-1] = 0; ext = strchr(szTK, '='); if (!ext) { - fprintf(stderr, "Bad format for track delay - expecting ID=DLAY got %s\n", argv[i + 1]); + fprintf(stderr, "Bad format for track delay - expecting tkID=DLAY got %s\n", argv[i + 1]); return 2; } tracks[nb_track_act].act_type = TRAC_ACTION_SET_DELAY; @@ -2355,7 +3116,7 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) szTK = argv[i + 1]; ext = strchr(szTK, ':'); if (!ext) { - fprintf(stderr, "Bad format for track reference - expecting ID:XXXX:refID got %s\n", argv[i + 1]); + fprintf(stderr, "Bad format for track reference - expecting tkID:XXXX:refID got %s\n", argv[i + 1]); return 2; } tracks[nb_track_act].act_type = TRAC_ACTION_REFERENCE; @@ -2365,7 +3126,7 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) szTK = ext + 1; ext = strchr(szTK, ':'); if (!ext) { - fprintf(stderr, "Bad format for track reference - expecting ID:XXXX:refID got %s\n", argv[i + 1]); + fprintf(stderr, "Bad format for track reference - expecting tkID:XXXX:refID got %s\n", argv[i + 1]); return 2; } ext[0] = 0; @@ -2382,10 +3143,11 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) tracks = gf_realloc(tracks, sizeof(TrackAction) * (nb_track_act + 1)); memset(&tracks[nb_track_act], 0, sizeof(TrackAction)); - strcpy(szTK, argv[i + 1]); + strncpy(szTK, argv[i + 1], sizeof(szTK)-1); + szTK[sizeof(szTK)-1] = 0; ext = strchr(szTK, '='); if (!ext) { - fprintf(stderr, "Bad format for track name - expecting ID=name got %s\n", argv[i + 1]); + fprintf(stderr, "Bad format for track name - expecting tkID=name got %s\n", argv[i + 1]); return 2; } tracks[nb_track_act].act_type = TRAC_ACTION_SET_HANDLER_NAME; @@ -2408,22 +3170,23 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) else if (!stricmp(arg, "-ovsbr")) import_flags |= GF_IMPORT_OVSBR; else if (!stricmp(arg, "-fps")) { CHECK_NEXT_ARG - if (!strcmp(argv[i + 1], "auto")) import_fps = GF_IMPORT_AUTO_FPS; + if (!strcmp(argv[i + 1], "auto")) { fprintf(stderr, "Warning, fps=auto option is deprecated\n"); } else if (strchr(argv[i + 1], '-')) { u32 ticks, dts_inc; sscanf(argv[i + 1], "%u-%u", &ticks, &dts_inc); if (!dts_inc) dts_inc = 1; - import_fps = ticks; - import_fps /= dts_inc; + import_fps.num = ticks; + import_fps.den = dts_inc; + } else { + import_fps.num = (s32) (1000 * atof(argv[i + 1])); + import_fps.den = 1000; } - else import_fps = atof(argv[i + 1]); i++; } else if (!stricmp(arg, "-agg")) { CHECK_NEXT_ARG agg_samples = atoi(argv[i + 1]); i++; } - else if (!stricmp(arg, "-keep-all") || !stricmp(arg, "-keepall")) import_flags |= GF_IMPORT_KEEP_ALL_TRACKS; #endif /*!defined(GPAC_DISABLE_MEDIA_EXPORT) && !defined(GPAC_DISABLE_MEDIA_IMPORT*/ else if (!stricmp(arg, "-keep-sys") || !stricmp(arg, "-keepsys")) keep_sys_tracks = 1; else if (!stricmp(arg, "-ms")) { @@ -2437,8 +3200,8 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) else if (!stricmp(arg, "-saf")) { do_saf = GF_TRUE; } - else if (!stricmp(arg, "-log")) { - do_log = GF_TRUE; + else if (!stricmp(arg, "-sclog")) { + do_scene_log = GF_TRUE; } #ifndef GPAC_DISABLE_MPD else if (!stricmp(arg, "-mpd")) { @@ -2450,54 +3213,54 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) #endif #ifndef GPAC_DISABLE_SCENE_ENCODER - else if (!stricmp(arg, "-def")) opts.flags |= GF_SM_ENCODE_USE_NAMES; + else if (!stricmp(arg, "-def")) smenc_opts.flags |= GF_SM_ENCODE_USE_NAMES; else if (!stricmp(arg, "-sync")) { CHECK_NEXT_ARG - opts.flags |= GF_SM_ENCODE_RAP_INBAND; - opts.rap_freq = atoi(argv[i + 1]); + smenc_opts.flags |= GF_SM_ENCODE_RAP_INBAND; + smenc_opts.rap_freq = atoi(argv[i + 1]); i++; } else if (!stricmp(arg, "-shadow")) { CHECK_NEXT_ARG - opts.flags &= ~GF_SM_ENCODE_RAP_INBAND; - opts.flags |= GF_SM_ENCODE_RAP_SHADOW; - opts.rap_freq = atoi(argv[i + 1]); + smenc_opts.flags &= ~GF_SM_ENCODE_RAP_INBAND; + smenc_opts.flags |= GF_SM_ENCODE_RAP_SHADOW; + smenc_opts.rap_freq = atoi(argv[i + 1]); i++; } else if (!stricmp(arg, "-carousel")) { CHECK_NEXT_ARG - opts.flags &= ~(GF_SM_ENCODE_RAP_INBAND | GF_SM_ENCODE_RAP_SHADOW); - opts.rap_freq = atoi(argv[i + 1]); + smenc_opts.flags &= ~(GF_SM_ENCODE_RAP_INBAND | GF_SM_ENCODE_RAP_SHADOW); + smenc_opts.rap_freq = atoi(argv[i + 1]); i++; } /*LASeR options*/ else if (!stricmp(arg, "-resolution")) { CHECK_NEXT_ARG - opts.resolution = atoi(argv[i + 1]); + smenc_opts.resolution = atoi(argv[i + 1]); i++; } #ifndef GPAC_DISABLE_SCENE_STATS else if (!stricmp(arg, "-auto-quant")) { CHECK_NEXT_ARG - opts.resolution = atoi(argv[i + 1]); - opts.auto_quant = 1; + smenc_opts.resolution = atoi(argv[i + 1]); + smenc_opts.auto_quant = 1; i++; } #endif else if (!stricmp(arg, "-coord-bits")) { CHECK_NEXT_ARG - opts.coord_bits = atoi(argv[i + 1]); + smenc_opts.coord_bits = atoi(argv[i + 1]); i++; } else if (!stricmp(arg, "-scale-bits")) { CHECK_NEXT_ARG - opts.scale_bits = atoi(argv[i + 1]); + smenc_opts.scale_bits = atoi(argv[i + 1]); i++; } else if (!stricmp(arg, "-global-quant")) { CHECK_NEXT_ARG - opts.resolution = atoi(argv[i + 1]); - opts.auto_quant = 2; + smenc_opts.resolution = atoi(argv[i + 1]); + smenc_opts.auto_quant = 2; i++; } /*chunk encoding*/ @@ -2513,6 +3276,8 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) i++; } #endif /*GPAC_DISABLE_SCENE_ENCODER*/ + +#ifndef GPAC_DISABLE_ISOM_WRITE else if (!strcmp(arg, "-crypt")) { CHECK_NEXT_ARG crypt = 1; @@ -2558,7 +3323,7 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) else if (!stricmp(arg, "-split")) { CHECK_NEXT_ARG split_duration = atof(argv[i + 1]); - if (split_duration < 0) split_duration = 0;; + if (split_duration < 0) split_duration = 0; i++; split_size = 0; } @@ -2580,12 +3345,22 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) return 2; } if (strstr(argv[i + 1], "end")) { - sscanf(argv[i + 1], "%lf:end", &split_start); - split_duration = -2; + if (strstr(argv[i + 1], "end-")) { + Double dur_end=0; + sscanf(argv[i + 1], "%lf:end-%lf", &split_start, &dur_end); + split_duration = -2 - dur_end; + } else { + sscanf(argv[i + 1], "%lf:end", &split_start); + split_duration = -2; + } } else { - sscanf(argv[i + 1], "%lf:%lf", &split_start, &split_duration); - split_duration -= split_start; + if (strchr(argv[i + 1], '-')) { + split_range_str = argv[i + 1]; + } else { + sscanf(argv[i + 1], "%lf:%lf", &split_start, &split_duration); + split_duration -= split_start; + } } split_size = 0; if (!stricmp(arg, "-splitz")) adjust_split_end = 1; @@ -2652,7 +3427,7 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) nb_meta_act++; i++; } - else if (!stricmp(arg, "-group-add") || !stricmp(arg, "-group-rem-track") || !stricmp(arg, "-group-rem") || !stricmp(arg, "-group-clean")) { + else if (!stricmp(arg, "-group-add") || !stricmp(arg, "-group-rem-track") || !stricmp(arg, "-group-rem") ) { TSELActionType act_type; if (!stricmp(arg, "-group-rem")) { act_type = TSEL_ACTION_REMOVE_ALL_TSEL_IN_GROUP; @@ -2682,6 +3457,9 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) pack_file = argv[i + 1]; i++; } + else if (!stricmp(arg, "-zmov")) { + compress_moov = GF_TRUE; + } else if (!stricmp(arg, "-mgt")) { CHECK_NEXT_ARG pack_file = argv[i + 1]; @@ -2693,7 +3471,12 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) CHECK_NEXT_ARG major_brand = GF_4CC(b[0], b[1], b[2], b[3]); open_edit = GF_TRUE; - if (b[4] == ':') minor_version = atoi(b + 5); + if (b[4] == ':') { + if (!strncmp(b+5, "0x", 2)) + sscanf(b+5, "0x%x", &minor_version); + else + minor_version = atoi(b + 5); + } i++; } else if (!stricmp(arg, "-ab")) { @@ -2715,62 +3498,177 @@ u32 mp4box_parse_args_continue(int argc, char **argv, u32 *current_index) i++; } #endif -else if (!stricmp(arg, "-languages")) { - PrintLanguages(); - return 1; -} -else if (!stricmp(arg, "-h")) { - if (i + 1 == (u32)argc) PrintUsage(); - else if (!strcmp(argv[i + 1], "general")) PrintGeneralUsage(); - else if (!strcmp(argv[i + 1], "extract")) PrintExtractUsage(); - else if (!strcmp(argv[i + 1], "dash")) PrintDASHUsage(); - else if (!strcmp(argv[i + 1], "dump")) PrintDumpUsage(); - else if (!strcmp(argv[i + 1], "import")) PrintImportUsage(); - else if (!strcmp(argv[i + 1], "format")) PrintFormats(); - else if (!strcmp(argv[i + 1], "hint")) PrintHintUsage(); - else if (!strcmp(argv[i + 1], "encode")) PrintEncodeUsage(); - else if (!strcmp(argv[i + 1], "crypt")) PrintEncryptUsage(); - else if (!strcmp(argv[i + 1], "meta")) PrintMetaUsage(); - else if (!strcmp(argv[i + 1], "swf")) PrintSWFUsage(); + else if (!stricmp(arg, "-languages")) { + PrintLanguages(); + return 1; + } + else if (!stricmp(arg, "-h")) { + gf_sys_set_args(argc, (const char**) argv); + + if (i + 1 == (u32)argc) PrintUsage(); + else if (!strcmp(argv[i + 1], "general")) PrintGeneralUsage(); + else if (!strcmp(argv[i + 1], "extract")) PrintExtractUsage(); + else if (!strcmp(argv[i + 1], "dash")) PrintDASHUsage(); + else if (!strcmp(argv[i + 1], "dump")) PrintDumpUsage(); + else if (!strcmp(argv[i + 1], "import")) PrintImportUsage(); + else if (!strcmp(argv[i + 1], "format")) fprintf(stderr, "deprectaed, see [filters documentation](Filters)\n"); + else if (!strcmp(argv[i + 1], "hint")) PrintHintUsage(); + else if (!strcmp(argv[i + 1], "encode")) PrintEncodeUsage(); + else if (!strcmp(argv[i + 1], "crypt")) PrintEncryptUsage(); + else if (!strcmp(argv[i + 1], "meta")) PrintMetaUsage(); + else if (!strcmp(argv[i + 1], "swf")) PrintSWFUsage(); #if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG) - else if (!strcmp(argv[i + 1], "rtp")) PrintStreamerUsage(); - else if (!strcmp(argv[i + 1], "live")) PrintLiveUsage(); + else if (!strcmp(argv[i + 1], "rtp")) fprintf(stderr, "RTP streaming deprecated in MP4Box, use gpac application\n"); + else if (!strcmp(argv[i + 1], "live")) PrintLiveUsage(); #endif - else if (!strcmp(argv[i + 1], "all")) { - PrintGeneralUsage(); - PrintExtractUsage(); - PrintDASHUsage(); - PrintDumpUsage(); - PrintImportUsage(); - PrintFormats(); - PrintHintUsage(); - PrintEncodeUsage(); - PrintEncryptUsage(); - PrintMetaUsage(); - PrintSWFUsage(); + else if (!strcmp(argv[i + 1], "core")) PrintCoreUsage(); + else if (!strcmp(argv[i + 1], "all")) { + PrintGeneralUsage(); + PrintExtractUsage(); + PrintDASHUsage(); + PrintDumpUsage(); + PrintImportUsage(); + PrintHintUsage(); + PrintEncodeUsage(); + PrintEncryptUsage(); + PrintMetaUsage(); + PrintSWFUsage(); #if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG) - PrintStreamerUsage(); - PrintLiveUsage(); + PrintLiveUsage(); #endif + PrintCoreUsage(); + } else if (!strcmp(argv[i + 1], "opts")) { + PrintHelp("@", GF_FALSE, GF_FALSE); + } else { + PrintHelp(argv[i+1], GF_FALSE, GF_FALSE); + } + return 1; + } + else if (!stricmp(arg, "-hx")) { + if (i + 1 == (u32)argc) PrintUsage(); + else PrintHelp(argv[i+1], GF_TRUE, GF_FALSE); + return 1; + } + else if (!strcmp(arg, "-genmd")) { + help_flags = GF_PRINTARG_MD | GF_PRINTARG_IS_APP; + helpout = gf_fopen("mp4box-gen-opts.md", "w"); + + fprintf(helpout, "[**HOME**](Home) » [**MP4Box**](MP4Box) » General"); + fprintf(helpout, "\n"); + fprintf(helpout, "# Syntax\n"); + gf_sys_format_help(helpout, help_flags, "MP4Box [option] input [option] [other_dash_inputs]\n" + " \n" + ); + PrintGeneralUsage(); + PrintEncryptUsage(); + fprintf(helpout, "# Help Options\n"); + while (m4b_usage_args[i].name) { + GF_GPACArg *g_arg = &m4b_usage_args[i]; + i++; + gf_sys_print_arg(helpout, help_flags, g_arg, "mp4box-general"); + } + + gf_fclose(helpout); + + helpout = gf_fopen("mp4box-import-opts.md", "w"); + fprintf(helpout, "[**HOME**](Home) » [**MP4Box**](MP4Box) » Media Import"); + fprintf(helpout, "\n"); + PrintImportUsage(); + gf_fclose(helpout); + + helpout = gf_fopen("mp4box-dash-opts.md", "w"); + fprintf(helpout, "[**HOME**](Home) » [**MP4Box**](MP4Box) » Media DASH"); + fprintf(helpout, "\n"); + PrintDASHUsage(); + gf_fclose(helpout); + + helpout = gf_fopen("mp4box-dump-opts.md", "w"); + fprintf(helpout, "[**HOME**](Home) » [**MP4Box**](MP4Box) » Media Dump and Export"); + fprintf(helpout, "\n"); + PrintExtractUsage(); + PrintDumpUsage(); + gf_fclose(helpout); + + helpout = gf_fopen("mp4box-meta-opts.md", "w"); + fprintf(helpout, "[**HOME**](Home) » [**MP4Box**](MP4Box) » Meta and HEIF/IFF"); + fprintf(helpout, "\n"); + PrintMetaUsage(); + gf_fclose(helpout); + + + helpout = gf_fopen("mp4box-scene-opts.md", "w"); + fprintf(helpout, "[**HOME**](Home) » [**MP4Box**](MP4Box) » Scene Description"); + fprintf(helpout, "\n"); + PrintEncodeUsage(); +#if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG) + PrintLiveUsage(); +#endif + PrintSWFUsage(); + gf_fclose(helpout); + + helpout = gf_fopen("mp4box-other-opts.md", "w"); + fprintf(helpout, "[**HOME**](Home) » [**MP4Box**](MP4Box) » Other Features"); + fprintf(helpout, "\n"); + PrintHintUsage(); + gf_fclose(helpout); + + gf_sys_close(); + return 1; + } else if (!strcmp(arg, "-genman")) { + help_flags = GF_PRINTARG_MAN; + helpout = gf_fopen("mp4box.1", "w"); + + + fprintf(helpout, ".TH MP4Box 1 2019 MP4Box GPAC\n"); + fprintf(helpout, ".\n.SH NAME\n.LP\nMP4Box \\- GPAC command-line media packager\n.SH SYNOPSIS\n.LP\n.B MP4Box\n.RI [options] \\ [file] \\ [options]\n.br\n.\n"); + + PrintGeneralUsage(); + PrintExtractUsage(); + PrintDASHUsage(); + PrintDumpUsage(); + PrintImportUsage(); + PrintHintUsage(); + PrintEncodeUsage(); + PrintEncryptUsage(); + PrintMetaUsage(); + PrintSWFUsage(); +#if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG) + PrintLiveUsage(); +#endif + + fprintf(helpout, ".SH EXAMPLES\n.TP\nBasic and advanced examples are available at https://wiki.gpac.io/MP4Box-Introduction\n"); + fprintf(helpout, ".SH MORE\n.LP\nAuthors: GPAC developers, see git repo history (-log)\n" + ".br\nFor bug reports, feature requests, more information and source code, visit http://github.com/gpac/gpac\n" + ".br\nbuild: %s\n" + ".br\nCopyright: %s\n.br\n" + ".SH SEE ALSO\n" + ".LP\ngpac(1), MP4Client(1)\n", gf_gpac_version(), gf_gpac_copyright()); + + gf_fclose(helpout); + gf_sys_close(); + return 1; + } + + else if (!stricmp(arg, "-v")) verbose++; + else if (!stricmp(arg, "-tag-list")) { + fprintf(stderr, "Supported iTunes tag modifiers:\n"); + for (i = 0; i < nb_itunes_tags; i++) { + fprintf(stderr, "\t%s\t%s\n", itags[i].name, itags[i].comment); + } + return 1; + } + else if (!live_scene) { + u32 res = gf_sys_is_gpac_arg(arg); + if (res==0) { + PrintHelp(arg, GF_FALSE, GF_TRUE); + return 2; + } else if (res==2) { + i++; + } + } } - else PrintUsage(); - return 1; -} -else if (!stricmp(arg, "-v")) verbose++; -else if (!stricmp(arg, "-tag-list")) { - fprintf(stderr, "Supported iTunes tag modifiers:\n"); - for (i = 0; i < nb_itunes_tags; i++) { - fprintf(stderr, "\t%s\t%s\n", itags[i].name, itags[i].comment); - } - return 1; -} -else if (!live_scene && !stream_rtp) { - fprintf(stderr, "Option %s unknown. Please check usage\n", arg); - return 2; -} -} -*current_index = i; -return 0; + *current_index = i; + return 0; } Bool mp4box_parse_args(int argc, char **argv) @@ -2778,7 +3676,7 @@ Bool mp4box_parse_args(int argc, char **argv) u32 i; /*parse our args*/ for (i = 1; i < (u32)argc; i++) { - arg = argv[i]; + char *arg = argv[i]; /*input file(s)*/ if ((arg[0] != '-') || !stricmp(arg, "--")) { char *arg_val = arg; @@ -2816,54 +3714,39 @@ Bool mp4box_parse_args(int argc, char **argv) return 1; } else if (!stricmp(arg, "-sdp")) print_sdp = 1; - else if (!stricmp(arg, "-quiet")) quiet = 2; else if (!strcmp(argv[i], "-mem-track")) continue; else if (!strcmp(argv[i], "-mem-track-stack")) continue; - - else if (!stricmp(arg, "-logs")) { - CHECK_NEXT_ARG - gf_logs = argv[i + 1]; - if (gf_logs) - gf_log_set_tools_levels(gf_logs); - i++; + else if (!strcmp(argv[i], "-p")) { + i++; + continue; } - else if (!strcmp(arg, "-log-file") || !strcmp(arg, "-lf")) { - logfile = gf_fopen(argv[i + 1], "wt"); - gf_log_set_callback(logfile, on_gpac_log); + else if (!strncmp(argv[i], "-p=", 3)) continue; + else if (!stricmp(arg, "-logs") || !strcmp(arg, "-log-file") || !strcmp(arg, "-lf")) { i++; } - else if (!strcmp(arg, "-lc") || !strcmp(arg, "-log-clock")) { - log_sys_clock = GF_TRUE; - } - else if (!strcmp(arg, "-lu") || !strcmp(arg, "-log-utc")) { - log_utc_time = GF_TRUE; - } - else if (!stricmp(arg, "-noprog")) quiet = 1; - else if (!stricmp(arg, "-info")) { + else if (!stricmp(arg, "-tracks")) get_nb_tracks = 1; + else if (!stricmp(arg, "-info") || !stricmp(arg, "-infon")) { print_info = 1; if ((i + 1<(u32)argc) && (sscanf(argv[i + 1], "%u", &info_track_id) == 1)) { char szTk[20]; sprintf(szTk, "%u", info_track_id); if (!strcmp(szTk, argv[i + 1])) i++; else info_track_id = 0; + + if (!stricmp(arg, "-infon")) print_info = 2; } else { info_track_id = 0; } } -#if !defined(GPAC_DISABLE_STREAMING) else if (!stricmp(arg, "-grab-ts")) { - CHECK_NEXT_ARG - grab_m2ts = argv[i + 1]; - i++; + fprintf(stderr, "Deprecated option - use gpac application\n"); + return mp4box_cleanup(2); } - else if (!stricmp(arg, "-ifce")) { - CHECK_NEXT_ARG - grab_ifce = argv[i + 1]; - i++; + else if (!stricmp(arg, "-atsc")) { + fprintf(stderr, "Deprecated option - use gpac application\n"); + return mp4box_cleanup(2); } - -#endif #if !defined(GPAC_DISABLE_CORE_TOOLS) else if (!stricmp(arg, "-wget")) { CHECK_NEXT_ARG @@ -2933,12 +3816,14 @@ Bool mp4box_parse_args(int argc, char **argv) else if (!stricmp(arg, "-avi")) { CHECK_NEXT_ARG track_dump_type = create_new_track_action(argv[i + 1], &tracks, &nb_track_act, GF_EXPORT_AVI); - i++; + if (tracks[nb_track_act-1].trackID) + i++; } #endif /*GPAC_DISABLE_MEDIA_EXPORT*/ #if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG) else if (!stricmp(arg, "-rtp")) { - stream_rtp = GF_TRUE; + fprintf(stderr, "Deprecated option - use gpac application\n"); + return mp4box_cleanup(2); } else if (!stricmp(arg, "-live")) { live_scene = GF_TRUE; @@ -2958,29 +3843,36 @@ Bool mp4box_parse_args(int argc, char **argv) PrintNode(argv[i + 1], 1); return 1; } - else if (!stricmp(arg, "-nodes")) { - PrintBuiltInNodes(0); + else if (!stricmp(arg, "-nodes") || !stricmp(arg, "-nodex")) { + PrintBuiltInNodes(0, !stricmp(arg, "-nodex") ? GF_TRUE : GF_FALSE); return 1; } - else if (!stricmp(arg, "-xnodes")) { - PrintBuiltInNodes(1); + else if (!stricmp(arg, "-xnodes") || !stricmp(arg, "-xnodex")) { + PrintBuiltInNodes(1, !stricmp(arg, "-xnodex") ? GF_TRUE : GF_FALSE); return 1; } #endif #ifndef GPAC_DISABLE_SVG else if (!stricmp(arg, "-snodes")) { - PrintBuiltInNodes(2); + PrintBuiltInNodes(2, GF_FALSE); return 1; } #endif - else if (!stricmp(arg, "-boxes")) { - PrintBuiltInBoxes(); + else if (!stricmp(arg, "-boxcov")) { + gf_sys_set_args(argc, (const char **) argv); + PrintBuiltInBoxes(GF_TRUE); + return 1; + } else if (!stricmp(arg, "-boxes")) { + PrintBuiltInBoxes(GF_FALSE); return 1; } else if (!stricmp(arg, "-std")) dump_std = 2; else if (!stricmp(arg, "-stdb")) dump_std = 1; + else if (!stricmp(arg, "-fstat")) fs_dump_flags |= 1; + else if (!stricmp(arg, "-fgraph")) fs_dump_flags |= 1<<1; #if !defined(GPAC_DISABLE_MEDIA_EXPORT) && !defined(GPAC_DISABLE_SCENE_DUMP) + else if (!stricmp(arg, "-keep-ods")) no_odf_conf = GF_TRUE; else if (!stricmp(arg, "-bt")) dump_mode = GF_SM_DUMP_BT; else if (!stricmp(arg, "-xmt")) dump_mode = GF_SM_DUMP_XMTA; else if (!stricmp(arg, "-wrl")) dump_mode = GF_SM_DUMP_VRML; @@ -2995,12 +3887,45 @@ Bool mp4box_parse_args(int argc, char **argv) else if (!stricmp(arg, "-statx")) stat_level = 3; else if (!stricmp(arg, "-diso")) dump_isom = 1; else if (!stricmp(arg, "-dxml")) dump_isom = 2; + else if (!stricmp(arg, "-disox")) dump_isom = 3; + else if (!stricmp(arg, "-mergevtt")) merge_vtt_cues = GF_TRUE; else if (!stricmp(arg, "-dump-cover")) dump_cart = 1; else if (!stricmp(arg, "-dump-chap")) dump_chap = 1; else if (!stricmp(arg, "-dump-chap-ogg")) dump_chap = 2; + else if (!stricmp(arg, "-dump-chap-zoom")) dump_chap = 3; else if (!stricmp(arg, "-hash")) do_hash = GF_TRUE; + else if (!strnicmp(arg, "-comp", 5)) { + CHECK_NEXT_ARG + + if (strchr(arg, 'x')) comp_top_box_version = 1; + else if (strchr(arg, 'f')) comp_top_box_version = 2; + + if (strchr(arg, 'l')) comp_lzma = GF_TRUE; + + compress_top_boxes = argv[i + 1]; + i++; + } + else if (!strnicmp(arg, "-topsize", 8)) { + CHECK_NEXT_ARG + size_top_box = 1; + compress_top_boxes = argv[i + 1]; + i++; + } + else if (!strnicmp(arg, "-topcount", 8)) { + CHECK_NEXT_ARG + size_top_box = 2; + compress_top_boxes = argv[i + 1]; + i++; + } + else if (!stricmp(arg, "-mpd-rip")) do_mpd_rip = GF_TRUE; + else if (!strcmp(arg, "-init-seg")) { + CHECK_NEXT_ARG + use_init_seg = argv[i + 1]; + i += 1; + } + #ifndef GPAC_DISABLE_CORE_TOOLS - else if (!stricmp(arg, "-bin")) do_bin_nhml = GF_TRUE; + else if (!stricmp(arg, "-bin")) do_bin_xml = GF_TRUE; #endif else if (!stricmp(arg, "-dump-udta")) { char *sep, *code; @@ -3015,7 +3940,20 @@ Bool mp4box_parse_args(int argc, char **argv) else { code = argv[i + 1]; } - dump_udta_type = GF_4CC(code[0], code[1], code[2], code[3]); + if (strlen(code) == 4) { + dump_udta_type = GF_4CC(code[0], code[1], code[2], code[3]); + } else if (strlen(code) == 8) { + // hex representation on 8 chars + u32 hex1, hex2, hex3, hex4; + if (sscanf(code, "%02x%02x%02x%02x", &hex1, &hex2, &hex3, &hex4) != 4) { + fprintf(stderr, "udta code is either a 4CC or 8 hex chars for non-printable 4CC\n"); + return mp4box_cleanup(1); + } + dump_udta_type = GF_4CC(hex1, hex2, hex3, hex4); + } else { + fprintf(stderr, "udta code is either a 4CC or 8 hex chars for non-printable 4CC\n"); + return mp4box_cleanup(1); + } i++; } else if (!stricmp(arg, "-dmp4")) { @@ -3032,9 +3970,31 @@ Bool mp4box_parse_args(int argc, char **argv) } } } - else if (!stricmp(arg, "-dnal")) { + else if (!stricmp(arg, "-dtsx")) { + dump_timestamps = 2; + } + else if (!stricmp(arg, "-dtsc")) { + dump_timestamps = 3; + } + else if (!stricmp(arg, "-dtsxc")) { + dump_timestamps = 4; + } + else if (!strnicmp(arg, "-dnal", 5)) { CHECK_NEXT_ARG dump_nal = atoi(argv[i + 1]); + if (arg[5] == 'c') dump_nal_type |= 1; + else if (arg[5] == 'd') dump_nal_type |= 2; + else if (arg[5] == 'x') dump_nal_type |= 2|1; + i++; + } + else if (!strnicmp(arg, "-dsap", 5)) { + CHECK_NEXT_ARG + dump_saps = atoi(argv[i + 1]); + if (!stricmp(arg, "-dsaps")) dump_saps_mode = 1; + else if (!stricmp(arg, "-dsapc")) dump_saps_mode = 2; + else if (!stricmp(arg, "-dsapd")) dump_saps_mode = 3; + else if (!stricmp(arg, "-dsapp")) dump_saps_mode = 4; + else dump_saps_mode = 0; i++; } else if (!stricmp(arg, "-dcr")) dump_cr = 1; @@ -3045,6 +4005,10 @@ Bool mp4box_parse_args(int argc, char **argv) if (!strcmp(szTk, argv[i + 1])) i++; else trackID = 0; } + else if ((i + 1<(u32)argc) && !strcmp(argv[i + 1], "*")) { + trackID = (u32)-1; + i++; + } else { trackID = 0; } @@ -3053,7 +4017,7 @@ Bool mp4box_parse_args(int argc, char **argv) fprintf(stderr, "Error: Read-Only version - subtitle conversion not available\n"); return 2; } -#endif +#endif //GPAC_DISABLE_ISOM_WRITE if (!stricmp(arg, "-ttxt")) dump_ttxt = GF_TRUE; else dump_srt = GF_TRUE; import_subtitle = 1; @@ -3122,16 +4086,14 @@ Bool mp4box_parse_args(int argc, char **argv) CHECK_NEXT_ARG tmpdir = argv[i + 1]; i++; } - else if (!stricmp(arg, "-for-test")) { - force_test_mode = GF_TRUE; - } else if (!stricmp(arg, "-co64")) { force_co64 = GF_TRUE; open_edit = GF_TRUE; } else if (!stricmp(arg, "-write-buffer")) { CHECK_NEXT_ARG - gf_isom_set_output_buffering(NULL, atoi(argv[i + 1])); + fprintf(stderr, "\tWARNING: \"-write-buffer\" deprecated and will soon be removed, use -bs-cache-size=%s\n", argv[i + 1]); + gf_opts_set_key("temp", "bs-cache-size", argv[i + 1]); i++; } else if (!stricmp(arg, "-cprt")) { @@ -3139,17 +4101,17 @@ Bool mp4box_parse_args(int argc, char **argv) i++; if (!dash_duration) open_edit = GF_TRUE; } - else if (!stricmp(arg, "-chap")) { - CHECK_NEXT_ARG chap_file = argv[i + 1]; + else if (!stricmp(arg, "-chap") || !stricmp(arg, "-chapqt")) { + CHECK_NEXT_ARG + chap_file = argv[i + 1]; i++; open_edit = GF_TRUE; - } - else if (!strcmp(arg, "-strict-error")) { - gf_log_set_strict_error(1); + if (!stricmp(arg, "-chapqt")) chap_qt = GF_TRUE; } else if (!stricmp(arg, "-inter") || !stricmp(arg, "-old-inter")) { CHECK_NEXT_ARG interleaving_time = atof(argv[i + 1]) / 1000; + if (!interleaving_time) do_flat = 2; open_edit = GF_TRUE; needSave = GF_TRUE; if (!stricmp(arg, "-old-inter")) old_interleave = 1; @@ -3160,7 +4122,7 @@ Bool mp4box_parse_args(int argc, char **argv) interleaving_time = atof(argv[i + 1]) / 1000; needSave = GF_TRUE; i++; - Frag = 1; + Frag = GF_TRUE; } else if (!stricmp(arg, "-dash")) { CHECK_NEXT_ARG @@ -3178,7 +4140,7 @@ Bool mp4box_parse_args(int argc, char **argv) fprintf(stderr, "\tERROR: \"-dash-dash_duration\": invalid parameter %s\n", argv[i + 1]); return 2; } - dash_duration_strict = GF_TRUE; + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] -dash-strict is deprecated, will behave like -dash\n")); i++; } else if (!stricmp(arg, "-subdur")) { @@ -3210,16 +4172,36 @@ Bool mp4box_parse_args(int argc, char **argv) seg_name = argv[i + 1]; i++; } - else if (!stricmp(arg, "-dash-run-for")) { + else if (!stricmp(arg, "-run-for")) { CHECK_NEXT_ARG - dash_run_for = atoi(argv[i + 1]); + run_for = atoi(argv[i + 1]); i++; } + else if (!stricmp(arg, "-no-cache")) { + no_cache = GF_TRUE; + } + else if (!stricmp(arg, "-no-loop")) { + no_loop = GF_TRUE; + } + else if (!stricmp(arg, "-hlsc")) { + hls_clock = GF_TRUE; + } + else if (!stricmp(arg, "-bound")) { + dash_split_mode = GF_DASH_SPLIT_IN; + } + else if (!stricmp(arg, "-closest")) { + dash_split_mode = GF_DASH_SPLIT_CLOSEST; + } else if (!stricmp(arg, "-segment-ext")) { CHECK_NEXT_ARG seg_ext = argv[i + 1]; i++; } + else if (!stricmp(arg, "-init-segment-ext")) { + CHECK_NEXT_ARG + init_seg_ext = argv[i + 1]; + i++; + } else if (!stricmp(arg, "-bs-switching")) { CHECK_NEXT_ARG if (!stricmp(argv[i + 1], "no") || !stricmp(argv[i + 1], "off")) bitstream_switching_mode = GF_DASH_BSMODE_NONE; @@ -3243,7 +4225,7 @@ Bool mp4box_parse_args(int argc, char **argv) frag_real_time = GF_TRUE; } else if (!stricmp(arg, "-start-date")) { - dash_start_date = gf_net_parse_date(argv[i+1]); + dash_start_date = argv[i+1]; i++; } else if (!strnicmp(arg, "-cp-location=", 13)) { @@ -3305,6 +4287,9 @@ Bool mp4box_parse_args(int argc, char **argv) else if (!stricmp(arg, "-single-traf")) { single_traf_per_moof = 1; } + else if (!stricmp(arg, "-tfdt-traf")) { + tfdt_per_traf = 1; + } else if (!stricmp(arg, "-mpd-title")) { CHECK_NEXT_ARG dash_title = argv[i + 1]; i++; @@ -3330,6 +4315,9 @@ Bool mp4box_parse_args(int argc, char **argv) dash_ctx_file = argv[i + 1]; i++; } + else if (!stricmp(arg, "-ssix")) { + use_ssix = 1; + } else if (!stricmp(arg, "-daisy-chain")) { daisy_chain_sidx = 1; } @@ -3340,11 +4328,29 @@ Bool mp4box_parse_args(int argc, char **argv) single_file = 1; } else if (!stricmp(arg, "-pssh-moof")) { - pssh_in_moof = 1; + pssh_mode = GF_DASH_PSSH_MOOF; + } + else if (!strnicmp(arg, "-pssh=", 6)) { + if (!strcmp(arg+6, "f")) pssh_mode = GF_DASH_PSSH_MOOF; + else if (!strcmp(arg+6, "v")) pssh_mode = GF_DASH_PSSH_MOOV; + else if (!strcmp(arg+6, "m")) pssh_mode = GF_DASH_PSSH_MPD; + else if (!strcmp(arg+6, "mf") || !strcmp(arg+6, "fm")) pssh_mode = GF_DASH_PSSH_MOOF_MPD; + else if (!strcmp(arg+6, "mv") || !strcmp(arg+6, "vm")) pssh_mode = GF_DASH_PSSH_MOOV_MPD; + else pssh_mode = GF_DASH_PSSH_MOOV; } else if (!stricmp(arg, "-sample-groups-traf")) { samplegroups_in_traf = 1; } + else if (!stricmp(arg, "-mvex-after-traks")) { + mvex_after_traks = GF_TRUE; + } + else if (!stricmp(arg, "-sdtp-traf")) { + CHECK_NEXT_ARG + if (!stricmp(argv[i + 1], "both")) sdtp_in_traf = 2; + else if (!stricmp(argv[i + 1], "sdtp")) sdtp_in_traf = 1; + else sdtp_in_traf = 0; + i++; + } else if (!stricmp(arg, "-dash-profile") || !stricmp(arg, "-profile")) { CHECK_NEXT_ARG if (!stricmp(argv[i + 1], "live") || !stricmp(argv[i + 1], "simple")) dash_profile = GF_DASH_PROFILE_LIVE; @@ -3359,7 +4365,11 @@ Bool mp4box_parse_args(int argc, char **argv) dash_profile = GF_DASH_PROFILE_AVC264_ONDEMAND; } else if (!stricmp(argv[i + 1], "main")) dash_profile = GF_DASH_PROFILE_MAIN; - else dash_profile = GF_DASH_PROFILE_FULL; + else if (!stricmp(argv[i + 1], "full")) dash_profile = GF_DASH_PROFILE_FULL; + else { + fprintf(stderr, "\tWARNING: Unrecognized DASH profile \"%s\" - please check usage\n", argv[i + 1]); + return 2; + } i++; } else if (!stricmp(arg, "-profile-ext")) { @@ -3386,9 +4396,22 @@ Bool mp4box_parse_args(int argc, char **argv) segment_marker = GF_4CC(m[0], m[1], m[2], m[3]); i++; } + else if (!stricmp(arg, "-cues")) { + CHECK_NEXT_ARG + dash_cues = argv[i + 1]; + i++; + } + else if (!stricmp(arg, "-strict-cues")) { + strict_cues = GF_TRUE; + } else if (!stricmp(arg, "-insert-utc")) { insert_utc = GF_TRUE; } +#endif //GPAC_DISABLE_ISOM_WRITE + else if (!stricmp(arg, "-udp-write")) { + udp_dest = argv[i+1]; + i++; + } else { u32 ret = mp4box_parse_args_continue(argc, argv, &i); if (ret) return ret; @@ -3399,27 +4422,32 @@ Bool mp4box_parse_args(int argc, char **argv) int mp4boxMain(int argc, char **argv) { + u32 i, j; + const char *gpac_profile = "0"; + GF_Err e = GF_OK; nb_tsel_acts = nb_add = nb_cat = nb_track_act = nb_sdp_ex = max_ptime = nb_meta_act = rtp_rate = major_brand = nb_alt_brand_add = nb_alt_brand_rem = car_dur = minor_version = 0; - e = GF_OK; + split_duration = 0.0; split_start = -1.0; - interleaving_time = DEFAULT_INTERLEAVING_IN_SEC; + interleaving_time = 0; dash_duration = dash_subduration = 0.0; - dash_duration_strict = GF_FALSE; - import_fps = 0; + import_fps.num = import_fps.den = 0; import_flags = 0; split_size = 0; movie_time = 0; - dump_nal = 0; - FullInter = HintInter = encode = do_log = old_interleave = do_saf = do_hash = verbose = GF_FALSE; + dump_nal = dump_saps = dump_saps_mode = force_new = 0; + FullInter = HintInter = encode = do_scene_log = old_interleave = do_saf = do_hash = verbose = do_mpd_rip = merge_vtt_cues = get_nb_tracks = GF_FALSE; #ifndef GPAC_DISABLE_SCENE_DUMP dump_mode = GF_SM_DUMP_NONE; #endif - Frag = force_ocr = remove_sys_tracks = agg_samples = remove_hint = keep_sys_tracks = remove_root_od = single_group = clean_groups = GF_FALSE; - conv_type = HintIt = needSave = print_sdp = print_info = regular_iod = dump_std = open_edit = dump_rtp = dump_cr = dump_srt = dump_ttxt = force_new = dump_timestamps = dump_m2ts = dump_cart = import_subtitle = force_cat = pack_wgt = dash_live = GF_FALSE; + Frag = force_ocr = remove_sys_tracks = agg_samples = remove_hint = keep_sys_tracks = remove_root_od = single_group = clean_groups = compress_moov = GF_FALSE; + conv_type = HintIt = needSave = print_sdp = regular_iod = dump_std = open_edit = dump_rtp = dump_cr = dump_srt = dump_ttxt = dump_m2ts = dump_cart = import_subtitle = force_cat = pack_wgt = dash_live = GF_FALSE; no_fragments_defaults = GF_FALSE; - single_traf_per_moof = GF_FALSE, + single_traf_per_moof = hls_clock = GF_FALSE; + tfdt_per_traf = GF_FALSE; + dump_nal_type = 0; dump_isom = 0; + print_info = 0; /*align cat is the new default behaviour for -cat*/ align_cat = GF_TRUE; subsegs_per_sidx = 0; @@ -3429,18 +4457,19 @@ int mp4boxMain(int argc, char **argv) file = NULL; itunes_tags = pes_dump = NULL; seg_name = dash_ctx_file = NULL; + compress_top_boxes = NULL; initial_moof_sn = 0; initial_tfdt = 0; #ifndef GPAC_DISABLE_SCENE_ENCODER - memset(&opts, 0, sizeof(opts)); + memset(&smenc_opts, 0, sizeof(smenc_opts)); #endif trackID = stat_level = hint_flags = 0; program_number = 0; info_track_id = 0; - do_flat = GF_FALSE; - inName = outName = mediaSource = input_ctx = output_ctx = drm_file = avi2raw = cprt = chap_file = pack_file = raw_cat = NULL; + do_flat = 0; + inName = outName = mediaSource = input_ctx = output_ctx = drm_file = avi2raw = cprt = chap_file = pack_file = raw_cat = high_dynamc_range_filename = use_init_seg = box_patch_filename = NULL; #ifndef GPAC_DISABLE_SWF_IMPORT swf_flags = GF_SM_SWF_SPLIT_TIMELINE; @@ -3457,16 +4486,34 @@ int mp4boxMain(int argc, char **argv) #endif break; } + else if (!strcmp(argv[i], "-p")) { + if (i+1<(u32) argc) + gpac_profile = argv[i+1]; + else { + fprintf(stderr, "Bad argument for -p, expecting profile name but no more args\n"); + return 1; + } + } + else if (!strncmp(argv[i], "-p=", 3)) + gpac_profile = argv[i]+3; } +#ifdef _TWO_DIGIT_EXPONENT + _set_output_format(_TWO_DIGIT_EXPONENT); +#endif + /*init libgpac*/ - gf_sys_init(mem_track); + gf_sys_init(mem_track, gpac_profile); if (argc < 2) { - PrintUsage(); + fprintf(stderr, "Not enough arguments - check usage with -h\n" + "MP4Box - GPAC version %s\n" + "%s\n", gf_gpac_version(), gf_gpac_copyright_cite()); gf_sys_close(); return 0; } + helpout = stdout; + i = mp4box_parse_args(argc, argv); if (i) { return mp4box_cleanup(i - 1); @@ -3476,7 +4523,11 @@ int mp4boxMain(int argc, char **argv) inName = "std"; if (!inName) { - PrintUsage(); + if (has_next_arg) { + fprintf(stderr, "Broken argument specifier or file name missing - check usage with -h\n"); + } else { + PrintUsage(); + } return mp4box_cleanup(1); } if (!strcmp(inName, "std")) dump_std = 2; @@ -3489,8 +4540,9 @@ int mp4boxMain(int argc, char **argv) /*by default use single fragment per dash segment*/ if (dash_duration) interleaving_time = dash_duration; - else - do_flat = GF_TRUE; + else if (!do_flat) { + interleaving_time = DEFAULT_INTERLEAVING_IN_SEC; + } } if (dump_std) @@ -3513,12 +4565,26 @@ int mp4boxMain(int argc, char **argv) int ret = live_session(argc, argv); return mp4box_cleanup(ret); } - if (stream_rtp) { - int ret = stream_file_rtp(argc, argv); - return mp4box_cleanup(ret); - } #endif + GF_LOG_Level level = verbose ? GF_LOG_DEBUG : GF_LOG_INFO; + gf_log_set_tool_level(GF_LOG_CONTAINER, level); + gf_log_set_tool_level(GF_LOG_SCENE, level); + gf_log_set_tool_level(GF_LOG_PARSER, level); + gf_log_set_tool_level(GF_LOG_AUTHOR, level); + gf_log_set_tool_level(GF_LOG_CODING, level); + gf_log_set_tool_level(GF_LOG_DASH, level); +#ifdef GPAC_MEMORY_TRACKING + if (mem_track) + gf_log_set_tool_level(GF_LOG_MEMORY, level); +#endif + + e = gf_sys_set_args(argc, (const char **) argv); + if (e) { + fprintf(stderr, "Error assigning libgpac arguments: %s\n", gf_error_to_string(e) ); + return mp4box_cleanup(1); + } + if (raw_cat) { char chunk[4096]; FILE *fin, *fout; @@ -3536,8 +4602,8 @@ int mp4boxMain(int argc, char **argv) gf_fseek(fin, 0, SEEK_SET); done = 0; while (1) { - u32 nb_bytes = (u32) fread(chunk, 1, 4096, fin); - gf_fwrite(chunk, 1, nb_bytes, fout); + u32 nb_bytes = (u32) gf_fread(chunk, 4096, fin); + gf_fwrite(chunk, nb_bytes, fout); done += nb_bytes; fprintf(stderr, "Appending file %s - %02.2f done\r", raw_cat, 100.0*done/to_copy); if (done >= to_copy) break; @@ -3546,29 +4612,20 @@ int mp4boxMain(int argc, char **argv) gf_fclose(fout); return mp4box_cleanup(0); } -#if !defined(GPAC_DISABLE_STREAMING) - if (grab_m2ts) { - return grab_live_m2ts(grab_m2ts, grab_ifce, inName); + if (compress_top_boxes) { + if (size_top_box) { + u64 top_size = do_size_top_boxes(inName, compress_top_boxes, size_top_box); + fprintf(stdout, LLU"\n", top_size); + return mp4box_cleanup(e ? 1 : 0); + } else { + e = do_compress_top_boxes(inName, outName, compress_top_boxes, comp_top_box_version, comp_lzma); + return mp4box_cleanup(e ? 1 : 0); + } } -#endif - if (gf_logs) { - //gf_log_set_tools_levels(gf_logs); - } else { - GF_LOG_Level level = verbose ? GF_LOG_DEBUG : GF_LOG_INFO; - gf_log_set_tool_level(GF_LOG_CONTAINER, level); - gf_log_set_tool_level(GF_LOG_SCENE, level); - gf_log_set_tool_level(GF_LOG_PARSER, level); - gf_log_set_tool_level(GF_LOG_AUTHOR, level); - gf_log_set_tool_level(GF_LOG_CODING, level); -#ifdef GPAC_MEMORY_TRACKING - if (mem_track) - gf_log_set_tool_level(GF_LOG_MEMORY, level); -#endif - if (quiet) { - if (quiet==2) gf_log_set_tool_level(GF_LOG_ALL, GF_LOG_QUIET); - gf_set_progress_callback(NULL, progress_quiet); - } + if (do_mpd_rip) { + e = rip_mpd(inName, outName); + return mp4box_cleanup(e ? 1 : 0); } #ifndef GPAC_DISABLE_CORE_TOOLS @@ -3576,11 +4633,31 @@ int mp4boxMain(int argc, char **argv) e = gf_dm_wget(do_wget, inName, 0, 0, NULL); if (e != GF_OK) { fprintf(stderr, "Cannot retrieve %s: %s\n", do_wget, gf_error_to_string(e) ); + return mp4box_cleanup(1); } - return mp4box_cleanup(1); + return mp4box_cleanup(0); } #endif + if (udp_dest) { + GF_Socket *sock = gf_sk_new(GF_SOCK_TYPE_UDP); + u16 port = 2345; + char *sep = strrchr(udp_dest, ':'); + if (sep) { + sep[0] = 0; + port = atoi(sep+1); + } + e = gf_sk_bind( sock, "127.0.0.1", 0, udp_dest, port, 0); + if (sep) sep[0] = ':'; + if (e) fprintf(stderr, "Failed to bind socket to %s: %s\n", udp_dest, gf_error_to_string(e) ); + else { + e = gf_sk_send(sock, (u8 *) inName, (u32)strlen(inName)); + if (e) fprintf(stderr, "Failed to send datagram: %s\n", gf_error_to_string(e) ); + } + gf_sk_del(sock); + return 0; + } + #ifndef GPAC_DISABLE_MPD if (do_mpd) { Bool remote = GF_FALSE; @@ -3604,10 +4681,11 @@ int mp4boxMain(int argc, char **argv) if (outName) strcpy(outfile, outName); else { - const char *sep = strrchr(inName, '/'); - char *ext = strstr(sep, ".m3u8"); + const char *sep = gf_file_basename(inName); + char *ext = gf_file_ext_start(sep); if (ext) ext[0] = 0; - sprintf(outfile, "%s.mpd", sep+1); + sprintf(outfile, "%s.mpd", sep); + if (ext) ext[0] = '.'; } } else { if (outName) @@ -3627,7 +4705,29 @@ int mp4boxMain(int argc, char **argv) fprintf(stderr, "[DASH] Error: MPD creation problem %s\n", gf_error_to_string(e)); mp4box_cleanup(1); } - e = gf_m3u8_to_mpd(remote ? "tmp_main.m3u8" : inName, mpd_base_url ? mpd_base_url : inName, outfile, 0, "video/mp2t", GF_TRUE, use_url_template, NULL, mpd, GF_TRUE); + FILE *f = gf_fopen(remote ? "tmp_main.m3u8" : inName, "r"); + u32 manif_type = 0; + if (f) { + char szDATA[1000]; + s32 read; + szDATA[999]=0; + read = (s32) gf_fread(szDATA, 999, f); + if (read<0) read = 0; + szDATA[read]=0; + gf_fclose(f); + if (strstr(szDATA, "SmoothStreamingMedia")) + manif_type = 2; + else if (strstr(szDATA, "#EXTM3U")) + manif_type = 1; + } + + if (manif_type==1) { + e = gf_m3u8_to_mpd(remote ? "tmp_main.m3u8" : inName, mpd_base_url ? mpd_base_url : inName, outfile, 0, "video/mp2t", GF_TRUE, use_url_template, segment_timeline, NULL, mpd, GF_TRUE, GF_TRUE); + } else if (manif_type==2) { + e = gf_mpd_smooth_to_mpd(remote ? "tmp_main.m3u8" : inName, mpd, mpd_base_url ? mpd_base_url : inName); + } else { + e = GF_NOT_SUPPORTED; + } if (!e) gf_mpd_write_file(mpd, outfile); @@ -3637,13 +4737,13 @@ int mp4boxMain(int argc, char **argv) gf_free(mpd_base_url); if (remote) { - gf_delete_file("tmp_main.m3u8"); + gf_file_delete("tmp_main.m3u8"); } if (e != GF_OK) { - fprintf(stderr, "Error converting M3U8 (%s) to MPD (%s): %s\n", inName, outfile, gf_error_to_string(e)); + fprintf(stderr, "Error converting %s (%s) to MPD (%s): %s\n", (manif_type==1) ? "HLS" : "Smooth", inName, outfile, gf_error_to_string(e)); return mp4box_cleanup(1); } else { - fprintf(stderr, "Done converting M3U8 (%s) to MPD (%s)\n", inName, outfile); + fprintf(stderr, "Done converting %s (%s) to MPD (%s)\n", (manif_type==1) ? "HLS" : "Smooth", inName, outfile); return mp4box_cleanup(0); } } @@ -3693,7 +4793,7 @@ int mp4boxMain(int argc, char **argv) if (e) { fprintf(stderr, "Error importing %s: %s\n", inName, gf_error_to_string(e)); gf_isom_delete(file); - gf_delete_file("ttxt_convert"); + gf_file_delete("ttxt_convert"); return mp4box_cleanup(1); } /* Prepare the export */ @@ -3711,7 +4811,7 @@ int mp4boxMain(int argc, char **argv) #endif /* Clean the importer */ gf_isom_delete(file); - gf_delete_file("ttxt_convert"); + gf_file_delete("ttxt_convert"); if (e) { fprintf(stderr, "Error converting %s: %s\n", inName, gf_error_to_string(e)); return mp4box_cleanup(1); @@ -3724,110 +4824,189 @@ int mp4boxMain(int argc, char **argv) } #if !defined(GPAC_DISABLE_MEDIA_IMPORT) && !defined(GPAC_DISABLE_ISOM_WRITE) - if (nb_add) { - u8 open_mode = GF_ISOM_OPEN_EDIT; - if (force_new) { - open_mode = (do_flat) ? GF_ISOM_OPEN_WRITE : GF_ISOM_WRITE_EDIT; - } else { - FILE *test = gf_fopen(inName, "rb"); - if (!test) { - open_mode = (do_flat) ? GF_ISOM_OPEN_WRITE : GF_ISOM_WRITE_EDIT; - if (!outName) outName = inName; + if (nb_add || nb_cat) { + u32 ipass, nb_pass = 1; + char *mux_args=NULL; + GF_FilterSession *fs = NULL; + if (nb_add) { + + GF_ISOOpenMode open_mode = GF_ISOM_OPEN_EDIT; + if (force_new) { + open_mode = (do_flat || (force_new==2)) ? GF_ISOM_OPEN_WRITE : GF_ISOM_WRITE_EDIT; } else { - gf_fclose(test); - if (! gf_isom_probe_file(inName) ) { + FILE *test = gf_fopen(inName, "rb"); + if (!test) { open_mode = (do_flat) ? GF_ISOM_OPEN_WRITE : GF_ISOM_WRITE_EDIT; if (!outName) outName = inName; + } else { + gf_fclose(test); + if (! gf_isom_probe_file(inName) ) { + open_mode = (do_flat) ? GF_ISOM_OPEN_WRITE : GF_ISOM_WRITE_EDIT; + if (!outName) outName = inName; + } } } + open_edit = do_flat ? GF_FALSE : GF_TRUE; + file = gf_isom_open(inName, open_mode, tmpdir); + if (!file) { + fprintf(stderr, "Cannot open destination file %s: %s\n", inName, gf_error_to_string(gf_isom_last_error(NULL)) ); + return mp4box_cleanup(1); + } + + if (freeze_box_order) + gf_isom_freeze_order(file); } - open_edit = GF_TRUE; - file = gf_isom_open(inName, open_mode, tmpdir); - if (!file) { - fprintf(stderr, "Cannot open destination file %s: %s\n", inName, gf_error_to_string(gf_isom_last_error(NULL)) ); - return mp4box_cleanup(1); + if (do_flat && interleaving_time) { + char szSubArg[100]; + gf_isom_set_storage_mode(file, GF_ISOM_STORE_FASTSTART); + do_flat = 2; + nb_pass = 2; + fs = gf_fs_new_defaults(0); + if (!fs) { + fprintf(stderr, "Error creating filter session\n"); + gf_isom_delete(file); + return mp4box_cleanup(1); + } + + //mux args + gf_dynstrcat(&mux_args, "mp4mx:importer:store=fstart", ":"); + + sprintf(szSubArg, "file=%p", file); + gf_dynstrcat(&mux_args, szSubArg, ":"); + sprintf(szSubArg, "cdur=%g", interleaving_time); + gf_dynstrcat(&mux_args, szSubArg, ":"); } - for (i=0; i<(u32) argc; i++) { - if (!strcmp(argv[i], "-add")) { - char *src = argv[i+1]; + for (ipass=0; ipass0) && (dash_duration > dash_subduration)) { fprintf(stderr, "Warning: -subdur parameter (%g s) should be greater than segment duration (%g s), using segment duration instead\n", dash_subduration, dash_duration); dash_subduration = dash_duration; @@ -3919,15 +5108,20 @@ int mp4boxMain(int argc, char **argv) fprintf(stderr, "Live DASH-ing - press 'q' to quit, 's' to save context and quit\n"); if (!dash_ctx_file && dash_live) { - dash_ctx = gf_cfg_new(NULL, NULL); + u32 r1; + u64 add = (u64) (intptr_t) &dasher; + add ^= gf_net_get_utc(); + r1 = (u32) add ^ (u32) (add/0xFFFFFFFF); + r1 ^= gf_rand(); + sprintf(szStateFile, "%s/dasher_%X.xml", gf_get_default_cache_directory(), r1 ); + dash_ctx_file = szStateFile; + dyn_state_file = GF_TRUE; } else if (dash_ctx_file) { if (force_new) - gf_delete_file(dash_ctx_file); - - dash_ctx = gf_cfg_force_new(NULL, dash_ctx_file); + gf_file_delete(dash_ctx_file); } - if (dash_profile==GF_DASH_PROFILE_UNKNOWN) + if (dash_profile==GF_DASH_PROFILE_AUTO) dash_profile = dash_mode ? GF_DASH_PROFILE_LIVE : GF_DASH_PROFILE_FULL; if (!dash_mode) { @@ -3946,24 +5140,24 @@ int mp4boxMain(int argc, char **argv) } /*setup dash*/ - dasher = gf_dasher_new(szMPD, dash_profile, tmpdir, dash_scale, dash_ctx); + dasher = gf_dasher_new(szMPD, dash_profile, tmpdir, dash_scale, dash_ctx_file); if (!dasher) { return mp4box_cleanup(1); - return GF_OUT_OF_MEM; } - e = gf_dasher_set_info(dasher, dash_title, cprt, dash_more_info, dash_source); + e = gf_dasher_set_info(dasher, dash_title, cprt, dash_more_info, dash_source, NULL); if (e) { fprintf(stderr, "DASH Error: %s\n", gf_error_to_string(e)); + gf_dasher_del(dasher); return mp4box_cleanup(1); } - if (dash_start_date) gf_dasher_set_start_date(dasher, dash_start_date); - - //e = gf_dasher_set_location(dasher, mpd_source); + gf_dasher_set_start_date(dasher, dash_start_date); + gf_dasher_set_location(dasher, dash_source); for (i=0; i < nb_mpd_base_urls; i++) { e = gf_dasher_add_base_url(dasher, mpd_base_urls[i]); if (e) { fprintf(stderr, "DASH Error: %s\n", gf_error_to_string(e)); + gf_dasher_del(dasher); return mp4box_cleanup(1); } } @@ -3972,47 +5166,60 @@ int mp4boxMain(int argc, char **argv) fprintf(stderr, "DASH Warning: using -segment-timeline with no -url-template. Forcing URL template.\n"); use_url_template = GF_TRUE; } - - e = gf_dasher_enable_url_template(dasher, (Bool) use_url_template, seg_name, seg_ext); + + e = gf_dasher_enable_url_template(dasher, (Bool) use_url_template, seg_name, seg_ext, init_seg_ext); if (!e) e = gf_dasher_enable_segment_timeline(dasher, segment_timeline); if (!e) e = gf_dasher_enable_single_segment(dasher, single_segment); if (!e) e = gf_dasher_enable_single_file(dasher, single_file); if (!e) e = gf_dasher_set_switch_mode(dasher, bitstream_switching_mode); - if (!e) e = gf_dasher_set_durations(dasher, dash_duration, dash_duration_strict, interleaving_time); + if (!e) e = gf_dasher_set_durations(dasher, dash_duration, interleaving_time, dash_subduration); if (!e) e = gf_dasher_enable_rap_splitting(dasher, seg_at_rap, frag_at_rap); if (!e) e = gf_dasher_set_segment_marker(dasher, segment_marker); - if (!e) e = gf_dasher_enable_sidx(dasher, (subsegs_per_sidx>=0) ? 1 : 0, (u32) subsegs_per_sidx, daisy_chain_sidx); + if (!e) e = gf_dasher_enable_sidx(dasher, (subsegs_per_sidx>=0) ? 1 : 0, (u32) subsegs_per_sidx, daisy_chain_sidx, use_ssix); if (!e) e = gf_dasher_set_dynamic_mode(dasher, dash_mode, mpd_update_time, time_shift_depth, mpd_live_duration); if (!e) e = gf_dasher_set_min_buffer(dasher, min_buffer); if (!e) e = gf_dasher_set_ast_offset(dasher, ast_offset_ms); if (!e) e = gf_dasher_enable_memory_fragmenting(dasher, memory_frags); if (!e) e = gf_dasher_set_initial_isobmf(dasher, initial_moof_sn, initial_tfdt); - if (!e) e = gf_dasher_configure_isobmf_default(dasher, no_fragments_defaults, pssh_in_moof, samplegroups_in_traf, single_traf_per_moof); + if (!e) e = gf_dasher_configure_isobmf_default(dasher, no_fragments_defaults, pssh_mode, samplegroups_in_traf, single_traf_per_moof, tfdt_per_traf, mvex_after_traks, sdtp_in_traf); if (!e) e = gf_dasher_enable_utc_ref(dasher, insert_utc); if (!e) e = gf_dasher_enable_real_time(dasher, frag_real_time); if (!e) e = gf_dasher_set_content_protection_location_mode(dasher, cp_location_mode); if (!e) e = gf_dasher_set_profile_extension(dasher, dash_profile_extension); + if (!e) e = gf_dasher_enable_cached_inputs(dasher, no_cache); + if (!e) e = gf_dasher_enable_loop_inputs(dasher, ! no_loop); + if (!e) e = gf_dasher_set_split_mode(dasher, dash_split_mode); + if (!e) e = gf_dasher_set_hls_clock(dasher, hls_clock); + if (!e && dash_cues) e = gf_dasher_set_cues(dasher, dash_cues, strict_cues); + if (!e && fs_dump_flags) e = gf_dasher_print_session_info(dasher, fs_dump_flags); for (i=0; i < nb_dash_inputs; i++) { if (!e) e = gf_dasher_add_input(dasher, &dash_inputs[i]); } if (e) { fprintf(stderr, "DASH Setup Error: %s\n", gf_error_to_string(e)); + gf_dasher_del(dasher); return mp4box_cleanup(1); } dash_cumulated_time=0; while (1) { - if (dash_run_for && (dash_cumulated_time>dash_run_for)) + if (run_for && (dash_cumulated_time >= run_for)) { + fprintf(stderr, "Done running, computing static MPD\n"); do_abort = 3; + } dash_prev_time=gf_sys_clock(); if (do_abort>=2) { e = gf_dasher_set_dynamic_mode(dasher, GF_DASH_DYNAMIC_LAST, 0, time_shift_depth, mpd_live_duration); } - if (!e) e = gf_dasher_process(dasher, dash_subduration); + if (!e) e = gf_dasher_process(dasher); + if (!dash_live && (e==GF_EOS) ) { + fprintf(stderr, "Nothing to dash, too early ...\n"); + e = GF_OK; + } if (do_abort) break; @@ -4029,7 +5236,12 @@ int mp4boxMain(int argc, char **argv) u64 ms_in_session=0; u32 slept = gf_sys_clock(); u32 sleep_for = gf_dasher_next_update_time(dasher, &ms_in_session); - fprintf(stderr, "Next generation scheduled in %u ms (DASH time "LLU" ms)\n", sleep_for, ms_in_session); + fprintf(stderr, "Next generation scheduled in %u ms (DASH time "LLU" ms)\r", sleep_for, ms_in_session); + if (run_for && (ms_in_session>=run_for)) { + dash_cumulated_time = 1+run_for; + continue; + } + while (1) { if (gf_prompt_has_input()) { char c = (char) gf_prompt_get_char(); @@ -4052,12 +5264,12 @@ int mp4boxMain(int argc, char **argv) } if (!sleep_for) break; - gf_sleep(1); + gf_sleep(sleep_for/10); sleep_for = gf_dasher_next_update_time(dasher, NULL); - if (sleep_for<1) { + if (sleep_for<=1) { dash_now_time=gf_sys_clock(); - fprintf(stderr, "Slept for %d ms before generation\n", dash_now_time - slept); dash_cumulated_time+=(dash_now_time-dash_prev_time); + fprintf(stderr, "Slept for %d ms before generation, dash cumulated time %d\n", dash_now_time - slept, dash_cumulated_time); break; } } @@ -4068,45 +5280,61 @@ int mp4boxMain(int argc, char **argv) gf_dasher_del(dasher); - if (dash_ctx) { - if (do_abort==3) { - if (!dash_ctx_file) { - char szName[1024]; - fprintf(stderr, "Enter file name to save dash context:\n"); - if (scanf("%s", szName) == 1) { - gf_cfg_set_filename(dash_ctx, szName); - gf_cfg_save(dash_ctx); - } - } + if (!run_for && dash_ctx_file && (do_abort==3) && (dyn_state_file) && !gf_sys_is_test_mode() ) { + char szName[1024]; + fprintf(stderr, "Enter file name to save dash context:\n"); + if (scanf("%1023s", szName) == 1) { + gf_file_move(dash_ctx_file, szName); } - gf_cfg_del(dash_ctx); } if (e) fprintf(stderr, "Error DASHing file: %s\n", gf_error_to_string(e)); if (file) gf_isom_delete(file); if (del_file) - gf_delete_file(inName); + gf_file_delete(inName); if (e) return mp4box_cleanup(1); goto exit; } - else if (!file + else if (!file && !do_hash #ifndef GPAC_DISABLE_MEDIA_EXPORT && !(track_dump_type & GF_EXPORT_AVI_NATIVE) #endif ) { FILE *st = gf_fopen(inName, "rb"); Bool file_exists = 0; + GF_ISOOpenMode omode; if (st) { file_exists = 1; gf_fclose(st); } switch (get_file_type_by_ext(inName)) { case 1: - file = gf_isom_open(inName, (u8) (open_edit ? GF_ISOM_OPEN_EDIT : ( ((dump_isom>0) || print_info) ? GF_ISOM_OPEN_READ_DUMP : GF_ISOM_OPEN_READ) ), tmpdir); + omode = (u8) (force_new ? GF_ISOM_WRITE_EDIT : (open_edit ? GF_ISOM_OPEN_EDIT : ( ((dump_isom>0) || print_info) ? GF_ISOM_OPEN_READ_DUMP : GF_ISOM_OPEN_READ) ) ); + + if (crypt) { + //keep fragment signaling in moov + omode = GF_ISOM_OPEN_READ; + if (use_init_seg) + file = gf_isom_open(use_init_seg, GF_ISOM_OPEN_READ, tmpdir); + } + if (!crypt && use_init_seg) { + file = gf_isom_open(use_init_seg, GF_ISOM_OPEN_READ_DUMP, tmpdir); + if (file) { + e = gf_isom_open_segment(file, inName, 0, 0, 0); + if (e) { + fprintf(stderr, "Error opening segment %s: %s\n", inName, gf_error_to_string(e) ); + gf_isom_delete(file); + file = NULL; + } + } + } + if (!file) + file = gf_isom_open(inName, omode, tmpdir); + if (!file && (gf_isom_last_error(NULL) == GF_ISOM_INCOMPLETE_FILE) && !open_edit) { u64 missing_bytes; - e = gf_isom_open_progressive(inName, 0, 0, &file, &missing_bytes); + e = gf_isom_open_progressive(inName, 0, 0, GF_FALSE, &file, &missing_bytes); fprintf(stderr, "Truncated file - missing "LLD" bytes\n", missing_bytes); } @@ -4121,6 +5349,8 @@ int mp4boxMain(int argc, char **argv) return mp4box_cleanup(1); } } + if (freeze_box_order) + gf_isom_freeze_order(file); break; /*allowed for bt<->xmt*/ case 2: @@ -4145,7 +5375,7 @@ int mp4boxMain(int argc, char **argv) else if (!open_edit && file_exists /* && !gf_isom_probe_file(inName) */ #ifndef GPAC_DISABLE_SCENE_DUMP && dump_mode == GF_SM_DUMP_NONE -#endif +#endif //GPAC_DISABLE_SCENE_DUMP ) { /*************************************************************************************************/ #ifndef GPAC_DISABLE_MEDIA_IMPORT @@ -4161,7 +5391,7 @@ int mp4boxMain(int argc, char **argv) if (e) { fprintf(stderr, "Error importing %s: %s\n", inName, gf_error_to_string(e)); gf_isom_delete(file); - gf_delete_file("ttxt_convert"); + gf_file_delete("ttxt_convert"); return mp4box_cleanup(1); } } @@ -4176,15 +5406,18 @@ int mp4boxMain(int argc, char **argv) dump_mpeg2_ts(inName, pes_dump, program_number); #endif #ifndef GPAC_DISABLE_CORE_TOOLS - } else if (do_bin_nhml) { - nhml_bs_to_bin(inName, outName, dump_std); + } else if (do_bin_xml) { + xml_bs_to_bin(inName, outName, dump_std); #endif } else if (do_hash) { hash_file(inName, dump_std); - } else { + } else if (print_info) { #ifndef GPAC_DISABLE_MEDIA_IMPORT convert_file_info(inName, info_track_id); #endif + } else { + fprintf(stderr, "Input %s is not an MP4 file, operation not allowed\n", inName); + return mp4box_cleanup(1); } goto exit; } @@ -4202,15 +5435,15 @@ int mp4boxMain(int argc, char **argv) } } - if (file && keep_utc && open_edit) { - gf_isom_keep_utc_times(file, 1); + if (high_dynamc_range_filename) { + e = parse_high_dynamc_range_xml_desc(file, high_dynamc_range_filename); + if (e) goto err_exit; } - if (file && force_test_mode) { - gf_isom_no_version_date_info(file, 1); + if (file && keep_utc && open_edit) { + gf_isom_keep_utc_times(file, 1); } - strcpy(outfile, outName ? outName : inName); { @@ -4225,6 +5458,8 @@ int mp4boxMain(int argc, char **argv) conv_type = GF_ISOM_CONV_TYPE_IPOD; else if (!stricmp(szExt, ".psp")) conv_type = GF_ISOM_CONV_TYPE_PSP; + else if (!stricmp(szExt, ".mov") || !stricmp(szExt, ".qt")) + conv_type = GF_ISOM_CONV_TYPE_MOV; //remove extension from outfile *szExt = 0; @@ -4233,7 +5468,7 @@ int mp4boxMain(int argc, char **argv) #ifndef GPAC_DISABLE_MEDIA_EXPORT if (track_dump_type & GF_EXPORT_AVI_NATIVE) { - char szFile[1024]; + char szFile[GF_MAX_PATH+24]; GF_MediaExporter mdump; memset(&mdump, 0, sizeof(mdump)); mdump.in_name = inName; @@ -4251,13 +5486,14 @@ int mp4boxMain(int argc, char **argv) mdump.out_name = szFile; } + mdump.print_stats_graph = fs_dump_flags; e = gf_media_export(&mdump); if (e) goto err_exit; goto exit; } - if (!open_edit && !gf_isom_probe_file(inName) && track_dump_type) { + if (!open_edit && track_dump_type && !gf_isom_probe_file(inName)) { GF_MediaExporter mdump; - char szFile[1024]; + char szFile[GF_MAX_PATH+24]; for (i=0; iact_type != TRAC_ACTION_RAW_EXTRACT) continue; @@ -4275,6 +5511,7 @@ int mp4boxMain(int argc, char **argv) } else { mdump.out_name = outfile; } + mdump.print_stats_graph = fs_dump_flags; e = gf_media_export(&mdump); if (e) goto err_exit; } @@ -4285,7 +5522,7 @@ int mp4boxMain(int argc, char **argv) #ifndef GPAC_DISABLE_SCENE_DUMP if (dump_mode != GF_SM_DUMP_NONE) { - e = dump_isom_scene(inName, dump_std ? NULL : (outName ? outName : outfile), outName ? GF_TRUE : GF_FALSE, dump_mode, do_log); + e = dump_isom_scene(inName, dump_std ? NULL : (outName ? outName : outfile), outName ? GF_TRUE : GF_FALSE, dump_mode, do_scene_log, no_odf_conf); if (e) goto err_exit; } #endif @@ -4297,23 +5534,38 @@ int mp4boxMain(int argc, char **argv) #ifndef GPAC_DISABLE_ISOM_HINTING if (!HintIt && print_sdp) dump_isom_sdp(file, dump_std ? NULL : (outName ? outName : outfile), outName ? GF_TRUE : GF_FALSE); #endif + if (get_nb_tracks) { + fprintf(stdout, "%d\n", gf_isom_get_track_count(file)); + } if (print_info) { if (!file) { fprintf(stderr, "Cannot print info on a non ISOM file (%s)\n", inName); } else { - if (info_track_id) DumpTrackInfo(file, info_track_id, 1); + if (info_track_id) DumpTrackInfo(file, info_track_id, 1, (print_info==2) ? GF_TRUE : GF_FALSE); else DumpMovieInfo(file); } } #ifndef GPAC_DISABLE_ISOM_DUMP if (dump_isom) { - e = dump_isom_xml(file, dump_std ? NULL : (outName ? outName : outfile), outName ? GF_TRUE : GF_FALSE, (dump_isom==2) ? GF_TRUE : GF_FALSE); + e = dump_isom_xml(file, dump_std ? NULL : (outName ? outName : outfile), outName ? GF_TRUE : GF_FALSE, (dump_isom==2) ? GF_TRUE : GF_FALSE, merge_vtt_cues, use_init_seg ? GF_TRUE : GF_FALSE, (dump_isom==3) ? GF_TRUE : GF_FALSE); if (e) goto err_exit; } if (dump_cr) dump_isom_ismacryp(file, dump_std ? NULL : (outName ? outName : outfile), outName ? GF_TRUE : GF_FALSE); - if ((dump_ttxt || dump_srt) && trackID) - dump_isom_timed_text(file, trackID, dump_std ? NULL : (outName ? outName : outfile), outName ? GF_TRUE : GF_FALSE, - GF_FALSE, dump_srt ? GF_TEXTDUMPTYPE_SRT : GF_TEXTDUMPTYPE_TTXT); + if ((dump_ttxt || dump_srt) && trackID) { + + if (trackID == (u32)-1) { + for (j=0; jtrackID==(u32) -1) { - u32 j; for (j=0; jtrackID) tk = gf_isom_get_track_by_id(file, meta->trackID); @@ -4437,17 +5696,17 @@ int mp4boxMain(int argc, char **argv) case META_ACTION_SET_TYPE: /*note: we don't handle file brand modification, this is an author stuff and cannot be guessed from meta type*/ e = gf_isom_set_meta_type(file, meta->root_meta, tk, meta->meta_4cc); - gf_isom_modify_alternate_brand(file, GF_ISOM_BRAND_ISO2, 1); + gf_isom_modify_alternate_brand(file, GF_ISOM_BRAND_ISO2, GF_TRUE); needSave = GF_TRUE; break; case META_ACTION_ADD_ITEM: self_ref = !stricmp(meta->szPath, "NULL") || !stricmp(meta->szPath, "this") || !stricmp(meta->szPath, "self"); e = gf_isom_add_meta_item(file, meta->root_meta, tk, self_ref, self_ref ? NULL : meta->szPath, - strlen(meta->szName) ? meta->szName : NULL, + meta->szName, meta->item_id, meta->item_type, - strlen(meta->mime_type) ? meta->mime_type : NULL, - strlen(meta->enc_type) ? meta->enc_type : NULL, + meta->mime_type, + meta->enc_type, meta->use_dref ? meta->szPath : NULL, NULL, meta->image_props); if (meta->ref_type) { @@ -4456,30 +5715,41 @@ int mp4boxMain(int argc, char **argv) needSave = GF_TRUE; break; case META_ACTION_ADD_IMAGE_ITEM: - { - e = import_file(file, meta->szPath, 0, 0, 0); + { + u32 old_tk_count = gf_isom_get_track_count(file); + GF_Fraction _frac = {0,0}; + e = import_file(file, meta->szPath, 0, _frac, 0, NULL, NULL, 0); + if (e == GF_OK) { + u32 meta_type = gf_isom_get_meta_type(file, meta->root_meta, tk); + if (!meta_type) { + e = gf_isom_set_meta_type(file, meta->root_meta, tk, GF_META_ITEM_TYPE_PICT); + } else { + if (meta_type != GF_META_ITEM_TYPE_PICT) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Warning: file already has a root 'meta' box of type %s\n", gf_4cc_to_str(meta_type))); + e = GF_BAD_PARAM; + } + } if (e == GF_OK) { - if (!gf_isom_get_meta_type(file, meta->root_meta, tk)) { - e = gf_isom_set_meta_type(file, meta->root_meta, tk, GF_4CC('p','i','c','t')); + if (!meta->item_id) { + e = gf_isom_meta_get_next_item_id(file, meta->root_meta, tk, &meta->item_id); } if (e == GF_OK) { - if (!meta->item_id) { - e = gf_isom_meta_get_next_item_id(file, meta->root_meta, tk, &meta->item_id); - } - if (e == GF_OK) { - gf_isom_iff_create_image_item_from_track(file, meta->root_meta, tk, 1, - strlen(meta->szName) ? meta->szName : NULL, + e = gf_isom_iff_create_image_item_from_track(file, meta->root_meta, tk, 1, + meta->szName, meta->item_id, meta->image_props, NULL); - if (meta->ref_type) { - e = gf_isom_meta_add_item_ref(file, meta->root_meta, tk, meta->item_id, meta->ref_item_id, meta->ref_type, NULL); - } + if (e == GF_OK && meta->primary) { + e = gf_isom_set_meta_primary_item(file, meta->root_meta, tk, meta->item_id); + } + if (e == GF_OK && meta->ref_type) { + e = gf_isom_meta_add_item_ref(file, meta->root_meta, tk, meta->item_id, meta->ref_item_id, meta->ref_type, NULL); } } } - gf_isom_remove_track(file, 1); - needSave = GF_TRUE; } + gf_isom_remove_track(file, old_tk_count+1); + needSave = GF_TRUE; + } break; case META_ACTION_REM_ITEM: e = gf_isom_remove_meta_item(file, meta->root_meta, tk, meta->item_id); @@ -4491,7 +5761,7 @@ int mp4boxMain(int argc, char **argv) break; case META_ACTION_SET_XML: case META_ACTION_SET_BINARY_XML: - e = gf_isom_set_meta_xml(file, meta->root_meta, tk, meta->szPath, (meta->act_type==META_ACTION_SET_BINARY_XML) ? 1 : 0); + e = gf_isom_set_meta_xml(file, meta->root_meta, tk, meta->szPath, NULL, 0, (meta->act_type==META_ACTION_SET_BINARY_XML) ? 1 : 0); needSave = GF_TRUE; break; case META_ACTION_REM_XML: @@ -4509,7 +5779,8 @@ int mp4boxMain(int argc, char **argv) fprintf(stderr, "No meta box in input file\n"); } break; -#endif +#endif // GPAC_DISABLE_ISOM_WRITE + case META_ACTION_DUMP_XML: if (gf_isom_has_meta_xml(file, meta->root_meta, tk)) { e = gf_isom_extract_meta_xml(file, meta->root_meta, tk, meta->szPath, NULL); @@ -4538,7 +5809,7 @@ int mp4boxMain(int argc, char **argv) if (e) goto err_exit; needSave = GF_TRUE; } - + for (i=0; i0) { u32 tk, k; for (k=0; k<(u32) count; k++) { - gf_isom_get_reference(file, j+1, GF_4CC('c','h','a','p'), k+1, &tk); + gf_isom_get_reference(file, j+1, GF_ISOM_REF_CHAP, k+1, &tk); if (tk==i+1) { is_chap = 1; break; @@ -4705,41 +5971,20 @@ int mp4boxMain(int argc, char **argv) break; } } - gf_isom_set_brand_info(file, major_brand, 1); - gf_isom_modify_alternate_brand(file, GF_ISOM_BRAND_MP42, 1); + gf_isom_set_brand_info(file, ipod_major_brand, 1); + gf_isom_modify_alternate_brand(file, GF_ISOM_BRAND_MP42, GF_TRUE); needSave = GF_TRUE; } -#ifndef GPAC_DISABLE_MCRYPT - if (crypt) { - if (!drm_file) { - fprintf(stderr, "Missing DRM file location - usage '-%s drm_file input_file\n", (crypt==1) ? "crypt" : "decrypt"); - e = GF_BAD_PARAM; - goto err_exit; - } - if (get_file_type_by_ext(inName) != GF_FILE_TYPE_ISO_MEDIA) { - fprintf(stderr, "MP4Box can crypt only ISOMedia File\n"); - e = GF_BAD_PARAM; - goto err_exit; - } - if (crypt == 1) { - e = gf_crypt_file(file, drm_file); - } else if (crypt ==2) { - e = gf_decrypt_file(file, drm_file); - } - if (e) goto err_exit; - needSave = GF_TRUE; - } -#endif /*GPAC_DISABLE_MCRYPT*/ } else if (outName) { strcpy(outfile, outName); } - - for (i=0; itrackID ? gf_isom_get_track_by_id(file, tka->trackID) : 0; - u32 timescale = gf_isom_get_timescale(file); + + timescale = gf_isom_get_timescale(file); switch (tka->act_type) { case TRAC_ACTION_REM_TRACK: e = gf_isom_remove_track(file, track); @@ -4781,26 +6026,26 @@ int mp4boxMain(int argc, char **argv) if (tka->delay_ms) { u64 tk_dur; - gf_isom_remove_edit_segments(file, track); + gf_isom_remove_edits(file, track); tk_dur = gf_isom_get_track_duration(file, track); - if (gf_isom_get_edit_segment_count(file, track)) + if (gf_isom_get_edits_count(file, track)) needSave = GF_TRUE; if (tka->delay_ms>0) { - gf_isom_append_edit_segment(file, track, (timescale*tka->delay_ms)/1000, 0, GF_ISOM_EDIT_EMPTY); - gf_isom_append_edit_segment(file, track, tk_dur, 0, GF_ISOM_EDIT_NORMAL); + gf_isom_append_edit(file, track, (timescale*tka->delay_ms)/1000, 0, GF_ISOM_EDIT_EMPTY); + gf_isom_append_edit(file, track, tk_dur, 0, GF_ISOM_EDIT_NORMAL); needSave = GF_TRUE; } else { u64 to_skip = (timescale*(-tka->delay_ms))/1000; if (to_skipdelay_ms)*gf_isom_get_media_timescale(file, track) / 1000; - gf_isom_append_edit_segment(file, track, tk_dur-to_skip, media_time, GF_ISOM_EDIT_NORMAL); + gf_isom_append_edit(file, track, tk_dur-to_skip, media_time, GF_ISOM_EDIT_NORMAL); needSave = GF_TRUE; } else { fprintf(stderr, "Warning: request negative delay longer than track duration - ignoring\n"); } } - } else if (gf_isom_get_edit_segment_count(file, track)) { - gf_isom_remove_edit_segments(file, track); + } else if (gf_isom_get_edits_count(file, track)) { + gf_isom_remove_edits(file, track); needSave = GF_TRUE; } break; @@ -4850,7 +6095,15 @@ int mp4boxMain(int argc, char **argv) } break; case TRAC_ACTION_SET_PAR: - e = gf_media_change_par(file, track, tka->par_num, tka->par_den); + e = gf_media_change_par(file, track, tka->par_num, tka->par_den, tka->force_par, tka->rewrite_bs); + needSave = GF_TRUE; + break; + case TRAC_ACTION_SET_CLAP: + e = gf_isom_set_clean_aperture(file, track, 1, tka->clap_wnum, tka->clap_wden, tka->clap_hnum, tka->clap_hden, tka->clap_honum, tka->clap_hoden, tka->clap_vonum, tka->clap_voden); + needSave = GF_TRUE; + break; + case TRAC_ACTION_SET_MX: + e = gf_isom_set_track_matrix(file, track, tka->mx); needSave = GF_TRUE; break; case TRAC_ACTION_SET_HANDLER_NAME: @@ -4859,13 +6112,13 @@ int mp4boxMain(int argc, char **argv) break; case TRAC_ACTION_ENABLE: if (!gf_isom_is_track_enabled(file, track)) { - e = gf_isom_set_track_enabled(file, track, 1); + e = gf_isom_set_track_enabled(file, track, GF_TRUE); needSave = GF_TRUE; } break; case TRAC_ACTION_DISABLE: if (gf_isom_is_track_enabled(file, track)) { - e = gf_isom_set_track_enabled(file, track, 0); + e = gf_isom_set_track_enabled(file, track, GF_FALSE); needSave = GF_TRUE; } break; @@ -4875,10 +6128,16 @@ int mp4boxMain(int argc, char **argv) break; case TRAC_ACTION_REM_NON_RAP: fprintf(stderr, "Removing non-rap samples from track %d\n", tka->trackID); - e = gf_media_remove_non_rap(file, track); + e = gf_media_remove_non_rap(file, track, GF_FALSE); + needSave = GF_TRUE; + break; + case TRAC_ACTION_REM_NON_REFS: + fprintf(stderr, "Removing non-reference samples from track %d\n", tka->trackID); + e = gf_media_remove_non_rap(file, track, GF_TRUE); needSave = GF_TRUE; break; case TRAC_ACTION_SET_UDTA: + fprintf(stderr, "Assigning udta box\n"); e = set_file_udta(file, track, tka->udta_type, tka->src_name, tka->sample_num ? GF_TRUE : GF_FALSE); if (e) goto err_exit; needSave = GF_TRUE; @@ -4894,7 +6153,7 @@ int mp4boxMain(int argc, char **argv) while (tags) { char *val; - char *sep = strchr(tags, ':'); + char *sep = gf_url_colon_suffix(tags); u32 tlen, itag = 0; if (sep) { while (sep) { @@ -4904,7 +6163,7 @@ int mp4boxMain(int argc, char **argv) if (itag>8; - _t[5]=t; - _t[4]=t>>8; - } - else if (sscanf(val, "%u", &n) == 1) { - _t[3]=n; - _t[2]=n>>8; + if (val) { + memset(_t, 0, sizeof(char) * 8); + tlen = (itag == GF_ISOM_ITUNE_DISK) ? 6 : 8; + if (sscanf(val, "%u/%u", &n, &t) == 2) { + _t[3] = n; + _t[2] = n >> 8; + _t[5] = t; + _t[4] = t >> 8; + } + else if (sscanf(val, "%u", &n) == 1) { + _t[3] = n; + _t[2] = n >> 8; + } + else tlen = 0; } - else tlen = 0; - if (tlen) gf_isom_apple_set_tag(file, itag, _t, tlen); + if (!val || tlen) gf_isom_apple_set_tag(file, itag, val ? (u8 *)_t : NULL, tlen); } break; case GF_ISOM_ITUNE_GAPLESS: case GF_ISOM_ITUNE_COMPILATION: { - char _t[1]; + u8 _t[1]; if (val && !stricmp(val, "yes")) _t[0] = 1; else _t[0] = 0; gf_isom_apple_set_tag(file, itag, _t, 1); } break; default: - gf_isom_apple_set_tag(file, itag, val, tlen); + gf_isom_apple_set_tag(file, itag, (u8 *)val, tlen); break; } needSave = GF_TRUE; @@ -5018,16 +6277,95 @@ int mp4boxMain(int argc, char **argv) needSave = GF_TRUE; } + if (cprt) { + e = gf_isom_set_copyright(file, "und", cprt); + needSave = GF_TRUE; + if (e) goto err_exit; + } + if (chap_file) { +#ifndef GPAC_DISABLE_MEDIA_IMPORT + e = gf_media_import_chapters(file, chap_file, import_fps, chap_qt); + needSave = GF_TRUE; +#else + fprintf(stderr, "Warning: GPAC compiled without Media Import, chapters can't be imported\n"); + e = GF_NOT_SUPPORTED; +#endif + if (e) goto err_exit; + } + + if (major_brand) { + gf_isom_set_brand_info(file, major_brand, minor_version); + needSave = GF_TRUE; + } + for (i=0; i len) { - fprintf(stderr, "Length allocated for conversion of wide char to UTF-8 not sufficient\n"); - return -1; - } - } - res = mp4boxMain(argc, argv); - for (i = 0; i < argc; i++) { - free(argv[i]); - } - free(argv); - return res; -} -#else -int main(int argc, char** argv) -{ - return mp4boxMain( argc, argv ); -} -#endif //win32 + +GF_MAIN_FUNC(mp4boxMain) #endif /*GPAC_DISABLE_ISOM*/ diff --git a/applications/mp4box/mp4box.h b/applications/mp4box/mp4box.h new file mode 100644 index 0000000..562d99d --- /dev/null +++ b/applications/mp4box/mp4box.h @@ -0,0 +1,145 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2012 + * All rights reserved + * + * This file is part of GPAC / mp4box application + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _MP4BOX_H +#define _MP4BOX_H + +#include +#include +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_SMGR +#include +#endif + + +typedef enum { + GF_FILE_TYPE_NOT_SUPPORTED = 0, + GF_FILE_TYPE_ISO_MEDIA = 1, + GF_FILE_TYPE_BT_WRL_X3DV = 2, + GF_FILE_TYPE_XMT_X3D = 3, + GF_FILE_TYPE_SVG = 4, + GF_FILE_TYPE_SWF = 5, + GF_FILE_TYPE_LSR_SAF = 6, +} GF_FileType; + + +#ifndef GPAC_DISABLE_MEDIA_IMPORT +void convert_file_info(char *inName, GF_ISOTrackID trackID); +#endif + +GF_Err parse_high_dynamc_range_xml_desc(GF_ISOFile* movie, char* file_name); + +#ifndef GPAC_DISABLE_ISOM_WRITE + +#ifndef GPAC_DISABLE_MEDIA_IMPORT +GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, GF_FilterSession *fsess, char **mux_args_if_first_pass, u32 tk_idx); +#else +GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, GF_FilterSession *fsess, Bool second_pass) { + return GF_NOT_SUPPORTED; +} +#endif +GF_Err split_isomedia_file(GF_ISOFile *mp4, Double split_dur, u64 split_size_kb, char *inName, Double interleaving_time, Double chunk_start, Bool adjust_split_end, char *outName, const char *tmpdir, Bool force_rap_split, const char *split_range_str); +GF_Err cat_isomedia_file(GF_ISOFile *mp4, char *fileName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, char *tmp_dir, Bool force_cat, Bool align_timelines, Bool allow_add_in_command, Bool is_pl); + +#if !defined(GPAC_DISABLE_SCENE_ENCODER) +GF_Err EncodeFile(char *in, GF_ISOFile *mp4, GF_SMEncodeOptions *opts, FILE *logs); +GF_Err EncodeFileChunk(char *chunkFile, char *bifs, char *inputContext, char *outputContext, const char *tmpdir); +#endif + +GF_ISOFile *package_file(char *file_name, char *fcc, const char *tmpdir, Bool make_wgt); + +#endif + +GF_Err dump_isom_cover_art(GF_ISOFile *file, char *inName, Bool is_final_name); +GF_Err dump_isom_chapters(GF_ISOFile *file, char *inName, Bool is_final_name, Bool dump_ogg); +GF_Err dump_isom_udta(GF_ISOFile *file, char *inName, Bool is_final_name, u32 dump_udta_type, u32 dump_udta_track); + +GF_Err set_file_udta(GF_ISOFile *dest, u32 tracknum, u32 udta_type, char *src, Bool is_box_array); +u32 id3_get_genre_tag(const char *name); + +/*in filedump.c*/ +#ifndef GPAC_DISABLE_SCENE_DUMP +GF_Err dump_isom_scene(char *file, char *inName, Bool is_final_name, GF_SceneDumpFormat dump_mode, Bool do_log, Bool no_odf_conv); +//void gf_check_isom_files(char *conf_rules, char *inName); +#endif +#ifndef GPAC_DISABLE_SCENE_STATS +void dump_isom_scene_stats(char *file, char *inName, Bool is_final_name, u32 stat_level); +#endif +void PrintNode(const char *name, u32 graph_type); +void PrintBuiltInNodes(u32 graph_type, Bool dump_all); +void PrintBuiltInBoxes(Bool do_cov); + +#ifndef GPAC_DISABLE_ISOM_DUMP +GF_Err dump_isom_xml(GF_ISOFile *file, char *inName, Bool is_final_name, Bool do_track_dump, Bool merge_vtt_cues, Bool skip_init, Bool skip_samples); +#endif + + +#ifndef GPAC_DISABLE_ISOM_HINTING +#ifndef GPAC_DISABLE_ISOM_DUMP +void dump_isom_rtp(GF_ISOFile *file, char *inName, Bool is_final_name); +#endif +void dump_isom_sdp(GF_ISOFile *file, char *inName, Bool is_final_name); +#endif + +void dump_isom_timestamps(GF_ISOFile *file, char *inName, Bool is_final_name, Bool skip_offset); +void dump_isom_nal(GF_ISOFile *file, GF_ISOTrackID trackID, char *inName, Bool is_final_name, u32 dump_flags); +void dump_isom_saps(GF_ISOFile *file, GF_ISOTrackID trackID, u32 dump_saps_mode, char *inName, Bool is_final_name); + +#ifndef GPAC_DISABLE_ISOM_DUMP +void dump_isom_ismacryp(GF_ISOFile *file, char *inName, Bool is_final_name); +void dump_isom_timed_text(GF_ISOFile *file, GF_ISOTrackID trackID, char *inName, Bool is_final_name, Bool is_convert, GF_TextDumpType dump_type); +#endif /*GPAC_DISABLE_ISOM_DUMP*/ + + +void DumpTrackInfo(GF_ISOFile *file, GF_ISOTrackID trackID, Bool full_dump, Bool is_track_num); +void DumpMovieInfo(GF_ISOFile *file); +void PrintLanguages(); + +#ifndef GPAC_DISABLE_MPEG2TS +void dump_mpeg2_ts(char *mpeg2ts_file, char *pes_out_name, Bool prog_num); +#endif + + +#if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_SENG) +void PrintStreamerUsage(); +int stream_file_rtp(int argc, char **argv); +int live_session(int argc, char **argv); +void PrintLiveUsage(); +#endif + +#if !defined(GPAC_DISABLE_STREAMING) +u32 grab_live_m2ts(const char *grab_m2ts, const char *outName); +#endif + +GF_Err rip_mpd(const char *mpd, const char *dst_file); + +GF_Err cat_playlist(GF_ISOFile *dest, char *playlistName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, char *tmp_dir, Bool force_cat, Bool align_timelines, Bool allow_add_in_command); + +#endif // _MP4BOX_H + diff --git a/applications/mp4box/wrapper.c b/applications/mp4box/wrapper.c index d4a22c7..74e84e6 100644 --- a/applications/mp4box/wrapper.c +++ b/applications/mp4box/wrapper.c @@ -42,8 +42,6 @@ JNIEXPORT void JNICALL Java_com_enst_mp4box_mp4terminal_run(JNIEnv * env, jobjec jniLOGV("mp4terminal::command get back ok"); jniLOGV(sOriginalCommand); - int iNbArg = 0; - int i = 0; char** sConvertedCommandLine; sConvertedCommandLine = ConvertCommandLine( sOriginalCommand, &i ); diff --git a/applications/mp4client/Makefile b/applications/mp4client/Makefile index 9cb577e..caea206 100644 --- a/applications/mp4client/Makefile +++ b/applications/mp4client/Makefile @@ -2,21 +2,21 @@ include ../../config.mak vpath %.c $(SRC_PATH)/applications/mp4client -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -I../../ +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) LINKFLAGS=$(GPAC_SH_FLAGS) -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif -ifeq ($(GPACREADONLY), yes) +ifeq ($(GPACREADONLY),yes) CFLAGS+=-DGPAC_READ_ONLY endif @@ -37,28 +37,33 @@ endif ifeq ($(STATICBUILD),yes) ##include static modules and other deps for libgpac include ../../static.mak -LINKFLAGS=-lgpac_static $(GPAC_SH_FLAGS) $(EXTRALIBS) +LINKFLAGS+=$(shell pkg-config ../../gpac.pc --libs --static | sed 's/-lgpac //' ) +LINKFLAGS+=$(GPAC_SH_FLAGS) $(EXTRALIBS) else LINKFLAGS+=-lgpac +ifeq ($(CONFIG_DARWIN),yes) +#LINKFLAGS+= -Wl,-rpath,'@loader_path' +else +LINKFLAGS+= -Wl,-rpath,'$$ORIGIN' -Wl,-rpath-link,../../bin/gcc +endif endif - #common obj -OBJS= main.o extract.o +OBJS= main.o ifeq ($(CONFIG_DARWIN),yes) OBJS+= carbon_events.o LDFLAGS += -framework Carbon endif -SRCS := $(OBJS:.o=.c) +SRCS := $(OBJS:.o=.c) all: $(PROG) MP4Client$(EXE): $(OBJS) $(CC) -o ../../bin/gcc/$@ $(OBJS) -L../../bin/gcc $(LINKFLAGS) $(LDFLAGS) -clean: +clean: rm -f $(OBJS) ../../bin/gcc/$(PROG) install: clean @@ -70,7 +75,7 @@ uninstall: dep: depend depend: - rm -f .depend + rm -f .depend $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend distclean: clean diff --git a/applications/mp4client/carbon_events.c b/applications/mp4client/carbon_events.c index a3ac7a4..5903d6b 100644 --- a/applications/mp4client/carbon_events.c +++ b/applications/mp4client/carbon_events.c @@ -28,7 +28,7 @@ #endif -void gf_sys_set_args(int argc, const char **argv); +int gf_sys_set_args(int argc, const char **argv); void send_open_url(const char *url); void RunApplicationEventLoop(void); diff --git a/applications/mp4client/extract.c b/applications/mp4client/extract.c deleted file mode 100644 index d9e7d6e..0000000 --- a/applications/mp4client/extract.c +++ /dev/null @@ -1,984 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2012 - * All rights reserved - * - * This file is part of GPAC / command-line client - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include -#include -#include - -#ifdef WIN32 -#include -#else -typedef struct tagBITMAPFILEHEADER -{ - u16 bfType; - u32 bfSize; - u16 bfReserved1; - u16 bfReserved2; - u32 bfOffBits; -} BITMAPFILEHEADER; - -typedef struct tagBITMAPINFOHEADER { - u32 biSize; - s32 biWidth; - s32 biHeight; - u16 biPlanes; - u16 biBitCount; - u32 biCompression; - u32 biSizeImage; - s32 biXPelsPerMeter; - s32 biYPelsPerMeter; - u32 biClrUsed; - u32 biClrImportant; -} BITMAPINFOHEADER; - -#define BI_RGB 0L - -#endif - - -#include -#include -#include - - -enum -{ - DUMP_NONE = 0, - DUMP_AVI = 1, - DUMP_BMP = 2, - DUMP_PNG = 3, - DUMP_RAW = 4, - DUMP_SHA1 = 5, - - //DuMP flags - DUMP_DEPTH_ONLY = 1<<16, - DUMP_RGB_DEPTH = 1<<17, - DUMP_RGB_DEPTH_SHAPE = 1<<18 -}; - - -extern Bool is_connected; -extern GF_Terminal *term; -extern u64 Duration; -extern GF_Err last_error; -extern Bool no_prog; - - -static GFINLINE u8 colmask(s32 a, s32 n) -{ - s32 mask = (1 << n) - 1; - return (u8) (a & (0xff & ~mask)) | ((-((a >> n) & 1)) & mask); -} - -static u32 put_pixel(FILE *fout, u32 type, u32 pf, char *ptr) -{ - u16 col; - switch (pf) { - case GF_PIXEL_RGB_32: - case GF_PIXEL_ARGB: - fputc(ptr[0], fout); - fputc(ptr[1], fout); - fputc(ptr[2], fout); - return 4; - - case GF_PIXEL_BGR_32: - case GF_PIXEL_RGBA: - fputc(ptr[2], fout); - fputc(ptr[1], fout); - fputc(ptr[0], fout); - return 4; - - case GF_PIXEL_RGB_24: - fputc(ptr[2], fout); - fputc(ptr[1], fout); - fputc(ptr[0], fout); - return 3; - - case GF_PIXEL_BGR_24: - fputc(ptr[2], fout); - fputc(ptr[1], fout); - fputc(ptr[0], fout); - return 3; - case GF_PIXEL_RGB_565: - col = * (u16 *)ptr; - fputc(colmask(col << 3, 3), fout); - fputc(colmask(col >> (5 - 2), 2), fout); - fputc(colmask(col >> (11 - 3), 3), fout); - return 2; - - case GF_PIXEL_RGB_555: - col = * (u16 *)ptr; - fputc(colmask(col << 3, 3), fout); - fputc(colmask(col >> (5 - 3), 3), fout); - fputc(colmask(col >> (10 - 3), 3), fout); - return 2; - /* this is used to write the byte depthbuffer in greyscale when dumping depth*/ - case GF_PIXEL_GREYSCALE: - /* bmp always needs 3 pixels */ - fputc(ptr[0], fout); - fputc(ptr[0], fout); - fputc(ptr[0], fout); - /* if printing the characters corresponding to the float depth buffer: */ - /* - { - u32 i=0; - while (ptr[i]!='\0') { - fputc(ptr[i], fout); - i++; - } - fputc('\b', fout); - } - */ - return 1; - - - case 0: - fputc(ptr[0], fout); - return 1; - } - return 0; -} - -void write_bmp(GF_VideoSurface *fb, char *rad_name, u32 img_num) -{ - char str[GF_MAX_PATH]; - BITMAPFILEHEADER fh; - BITMAPINFOHEADER fi; - FILE *fout; - u32 j, i; - char *ptr; - - if (fb->pixel_format==GF_PIXEL_GREYSCALE) sprintf(str, "%s_%d_depth.bmp", rad_name, img_num); - else sprintf(str, "%s_%d.bmp", rad_name, img_num); - - fout = gf_fopen(str, "wb"); - if (!fout) return; - - memset(&fh, 0, sizeof(fh)); - fh.bfType = 19778; - fh.bfOffBits = 14 + 40; - - memset(&fi, 0, sizeof(char)*40); - fi.biSize = sizeof(char)*40; - fi.biWidth = fb->width; - fi.biHeight = fb->height; - fi.biPlanes = 1; - if (fb->pixel_format==GF_PIXEL_GREYSCALE) fi.biBitCount = 24; - else fi.biBitCount = 24; - fi.biCompression = BI_RGB; - fi.biSizeImage = fb->width * fb->height * 3; - - /*NOT ALIGNED!!*/ - gf_fwrite(&fh.bfType, 2, 1, fout); - gf_fwrite(&fh.bfSize, 4, 1, fout); - gf_fwrite(&fh.bfReserved1, 2, 1, fout); - gf_fwrite(&fh.bfReserved2, 2, 1, fout); - gf_fwrite(&fh.bfOffBits, 4, 1, fout); - - gf_fwrite(&fi, 1, 40, fout); - for (j=fb->height; j>0; j--) { - ptr = fb->video_buffer + (j-1)*fb->pitch_y; - for (i=0; iwidth; i++) { - u32 res = put_pixel(fout, 0, fb->pixel_format, ptr); - assert(res); - ptr += res; - } - } - gf_fclose(fout); -} - -#include - -void write_png(GF_VideoSurface *fb, char *rad_name, u32 img_num) -{ -#ifndef GPAC_DISABLE_AV_PARSERS - char str[GF_MAX_PATH]; - FILE *fout; - u32 dst_size; - char *dst; - char *prev = strrchr(rad_name, '.'); - if (prev) prev[0] = '\0'; - sprintf(str, "%s_%d.png", rad_name, img_num); - if (prev) prev[0] = '.'; - - switch (fb->pixel_format) { - case GF_PIXEL_ARGB: - case GF_PIXEL_RGBA: - dst_size = fb->width*fb->height*4; - break; - default: - dst_size = fb->width*fb->height*3; - break; - } - dst = (char*)gf_malloc(sizeof(char)*dst_size); - - - fout = gf_fopen(str, "wb"); - if (fout) { - GF_Err e = gf_img_png_enc(fb->video_buffer, fb->width, fb->height, fb->pitch_y, fb->pixel_format, dst, &dst_size); - if (!e) { - gf_fwrite(dst, dst_size, 1, fout); - gf_fclose(fout); - } - } - - gf_free(dst); -#endif //GPAC_DISABLE_AV_PARSERS -} - - - -/*writes onto a file the content of the framebuffer in *fb interpreted as the byte depthbuffer */ -/*it's also possible to write a float depthbuffer by passing the floats to strings and writing chars in putpixel - see comments*/ -void write_depthfile(GF_VideoSurface *fb, char *rad_name, u32 img_num) -{ - FILE *fout; - u32 i, j; - unsigned char *depth; - - depth = (unsigned char *) fb->video_buffer; - - fout = gf_fopen("dump_depth", "wb"); - if (!fout) return; - for (j=0; jheight; j++) { - for (i=0; iwidth; i++) { - -#ifdef GPAC_USE_TINYGL - fputc(depth[2*i+j*fb->width*sizeof(unsigned short)], fout); - fputc(depth[2*i+j*fb->width*sizeof(unsigned short) + 1], fout); -#else - fputc(depth[i+j*fb->width], fout); -#endif - } - } - gf_fclose(fout); -} - -void write_texture_file(GF_VideoSurface *fb, char *rad_name, u32 img_num, u32 dump_mode_flags) -{ - - FILE *fout; - u32 i, j; - unsigned char *buf; - - buf = (unsigned char *) fb->video_buffer; - - if (dump_mode_flags & DUMP_RGB_DEPTH_SHAPE) fout = gf_fopen("dump_rgbds", "wb"); - else if (dump_mode_flags & DUMP_RGB_DEPTH) fout = gf_fopen("dump_rgbd", "wb"); - else return; - - if (!fout) return; - for (j=0; jheight; j++) { - for (i=0; iwidth*4; i++) { - fputc(buf[i+j*fb->pitch_y], fout); - } - } - gf_fclose(fout); -} - - -void write_raw(GF_VideoSurface *fb, char *rad_name, u32 img_num) -{ - u32 j, i; - char *ptr, *prev; - char str[GF_MAX_PATH]; - FILE *fout; - prev = strrchr(rad_name, '.'); - if (prev) prev[0] = '\0'; - if (img_num<10) { - sprintf(str, "%s_00%d.raw", rad_name, img_num); - } else if (img_num<100) { - sprintf(str, "%s_0%d.raw", rad_name, img_num); - } else { - sprintf(str, "%s_%d.raw", rad_name, img_num); - } - - fout = gf_fopen(str, "wb"); - if (!fout) return; - - - for (j=0; jheight; j++) { - ptr = fb->video_buffer + j*fb->pitch_y; - for (i=0; iwidth; i++) { - u32 res = put_pixel(fout, 0, fb->pixel_format, ptr); - assert(res); - ptr += res; - } - } - gf_fclose(fout); -} - -void write_hash(FILE *sha_out, char *buf, u32 size) -{ - u8 hash[20]; - gf_sha1_csum((u8 *)buf, size, hash); - fwrite(hash, 1, 20, sha_out); -} - - -/* creates a .bmp format greyscale image of the byte depthbuffer and a binary with only the content of the depthbuffer */ -void dump_depth (GF_Terminal *term, char *rad_name, u32 dump_mode_flags, u32 frameNum, char *conv_buf, void *avi_out, FILE *sha_out) -{ - GF_Err e; - u32 i, k; - - GF_VideoSurface fb; - u32 dump_mode = dump_mode_flags & 0x0000FFFF; - - /*lock it*/ - e = gf_sc_get_screen_buffer(term->compositor, &fb, 1); - if (e) fprintf(stderr, "Error grabbing depth buffer: %s\n", gf_error_to_string(e)); - else fprintf(stderr, "OK\n"); - /*export frame*/ - switch (dump_mode) { - case DUMP_AVI: - /*reverse frame*/ - for (k=0; k> 8/*(11 - 3)*/, 3); - dst[1] = colmask(src_16 >> 3/*(5 - 2)*/, 2); - dst[0] = colmask(src_16 << 3, 3); - src+=2; - break; - case GF_PIXEL_RGB_555: - src_16 = * (u16 *)src; - dst[2] = colmask(src_16 >> 7/*(10 - 3)*/, 3); - dst[1] = colmask(src_16 >> 2/*(5 - 3)*/, 3); - dst[0] = colmask(src_16 << 3, 3); - src+=2; - break; - /*for depth .avi*/ - case GF_PIXEL_GREYSCALE: - dst[0] = src[0]; - dst[1] = src[0]; - dst[2] = src[0]; - src+=1; - break; - } - dst += 3; - } - } -#ifndef GPAC_DISABLE_AVILIB - if (avi_out) { - if (AVI_write_frame(avi_out, conv_buf, fb.height*fb.width*3, 1) <0) - fprintf(stderr, "Error writing frame\n"); - } else -#endif - if (sha_out) { - write_hash(sha_out, conv_buf, fb.height*fb.width*3); - } - - /*in -depth -avi mode, do not release it yet*/ - if (dump_mode_flags & DUMP_DEPTH_ONLY) return; - break; - case DUMP_BMP: - write_bmp(&fb, rad_name, frameNum); - break; - case DUMP_PNG: - write_png(&fb, rad_name, frameNum); - break; - case DUMP_RAW: - write_raw(&fb, rad_name, frameNum); - break; - default: - write_depthfile(&fb, rad_name, frameNum); - break; - } - /*unlock it*/ - gf_sc_release_screen_buffer(term->compositor, &fb); -} - -void dump_frame(GF_Terminal *term, char *rad_name, u32 dump_mode_flags, u32 frameNum, char *conv_buf, void *avi_out, FILE *sha_out) -{ - GF_Err e = GF_OK; - u32 i, k, out_size; - GF_VideoSurface fb; - - u32 dump_mode = dump_mode_flags & 0x0000FFFF; - u32 depth_dump_mode = 0; - - if (dump_mode_flags & DUMP_RGB_DEPTH_SHAPE) depth_dump_mode = 3; - else if (dump_mode_flags & DUMP_RGB_DEPTH) depth_dump_mode = 2; - else if (dump_mode_flags & DUMP_DEPTH_ONLY) depth_dump_mode = 1; - - /*lock it*/ - e = gf_sc_get_screen_buffer(term->compositor, &fb, depth_dump_mode); - if (e) fprintf(stderr, "Error grabbing frame buffer: %s\n", gf_error_to_string(e)); - - /*export frame*/ - switch (dump_mode) { - case DUMP_AVI: - case DUMP_SHA1: - /*reverse frame*/ - for (k=0; k> 8/*(11 - 3)*/, 3); - dst[1] = colmask(src_16 >> 3/*(5 - 2)*/, 2); - dst[0] = colmask(src_16 << 3, 3); - src+=2; - dst+=3; - } - break; - case GF_PIXEL_RGB_555: - for (i=0; i> 7/*(10 - 3)*/, 3); - dst[1] = colmask(src_16 >> 2/*(5 - 3)*/, 3); - dst[0] = colmask(src_16 << 3, 3); - src+=2; - dst +=3; - } - break; - } - } - if (dump_mode_flags & (DUMP_RGB_DEPTH | DUMP_RGB_DEPTH_SHAPE)) { - out_size = fb.height*fb.width*4; - } else { - out_size = fb.height*fb.width*3; - } -#ifndef GPAC_DISABLE_AVILIB - if (avi_out) { - if (AVI_write_frame(avi_out, conv_buf, out_size, 1) <0) - fprintf(stderr, "Error writing frame\n"); - } else -#endif - if (sha_out) { - write_hash(sha_out, conv_buf, out_size); - } - break; - case DUMP_BMP: - write_bmp(&fb, rad_name, frameNum); - break; - case DUMP_PNG: - write_png(&fb, rad_name, frameNum); - break; - case DUMP_RAW: - write_raw(&fb, rad_name, frameNum); - break; - - default: - write_texture_file(&fb, rad_name, frameNum, dump_mode_flags); - break; - } - /*unlock it*/ - gf_sc_release_screen_buffer(term->compositor, &fb); -} - -#ifndef GPAC_DISABLE_AVILIB - -typedef struct -{ - GF_AudioListener al; - GF_Mutex *mx; - avi_t *avi; - u32 time_scale; - u64 max_dur, nb_bytes, audio_time; - u32 next_video_time, audio_time_init, flush_retry, nb_write, audio_clock_at_video_init; - u32 samplerate, bits_per_sample, nb_channel; -} AVI_AudioListener; - -void avi_audio_frame(void *udta, char *buffer, u32 buffer_size, u32 time, u32 delay) -{ - AVI_AudioListener *avil = (AVI_AudioListener *)udta; - - if (avil->audio_clock_at_video_init > time) - return; - - if (avil->audio_time >= avil->audio_time_init + avil->max_dur) - return; - - gf_mx_p(avil->mx); - - if (!avil->time_scale) { - AVI_set_audio(avil->avi, avil->nb_channel, avil->samplerate, avil->bits_per_sample, WAVE_FORMAT_PCM, 0); - avil->time_scale = avil->nb_channel*avil->bits_per_sample*avil->samplerate/8; - gf_term_set_option(term, GF_OPT_FORCE_AUDIO_CONFIG, 1); - } - - avil->nb_bytes+=buffer_size; - avil->flush_retry=0; - - if (avil->audio_time >= avil->audio_time_init) { - avil->nb_write++; - AVI_write_audio(avil->avi, buffer, buffer_size); - } - - - avil->audio_time = 1000*avil->nb_bytes/avil->time_scale; - - //we are behind video dump, force audio flush - if (avil->audio_time < avil->next_video_time) { - gf_term_step_clocks(term, 0); - } - gf_mx_v(avil->mx); -} - -void avi_audio_reconfig(void *udta, u32 samplerate, u32 bits_per_sample, u32 nb_channel, u32 channel_cfg) -{ - AVI_AudioListener *avil = (AVI_AudioListener *)udta; - - avil->nb_channel = nb_channel; - avil->samplerate = samplerate; - avil->bits_per_sample = bits_per_sample; -} -#endif - -Bool dump_file(char *url, char *out_url, u32 dump_mode_flags, Double fps, u32 width, u32 height, Float scale, u32 *times, u32 nb_times) -{ - GF_Err e; - Bool ret = 0; - u32 i = 0; - GF_VideoSurface fb; - char szPath[GF_MAX_PATH]; - char szOutPath[GF_MAX_PATH]; - char *prev=NULL; - u32 time, prev_time, nb_frames, init_time; - u64 dump_dur; - char *conv_buf = NULL; -#ifndef GPAC_DISABLE_AVILIB - avi_t *avi_out = NULL; - avi_t *depth_avi_out = NULL; - AVI_AudioListener avi_al; - char comp[5]; -#else - void *avi_out = NULL; - void *depth_avi_out = NULL; -#endif - GF_Mutex *avi_mx = NULL; - - FILE *sha_out = NULL; - FILE *sha_depth_out = NULL; - char szPath_depth[GF_MAX_PATH]; - u32 cur_time_idx; - u32 mode = dump_mode_flags & 0x0000FFFF; - - if (!out_url) out_url = url; - prev = strstr(url, "://"); - if (prev) { - prev = strrchr(url, '/'); - if (prev) prev++; - } - - if (!prev) prev = url; - strcpy(szPath, prev); - prev = gf_file_ext_start(szPath); - if (prev) *prev = 0; - - if (out_url) { - strcpy(szOutPath, out_url); - } else { - strcpy(szOutPath, szPath); - } - prev = gf_file_ext_start(szOutPath); - if (prev) *prev = 0; - - gf_term_set_simulation_frame_rate(term, (Double) fps); - - fprintf(stderr, "Opening URL %s\n", url); - /*connect and pause */ - gf_term_connect_from_time(term, url, 0, 2); - - while (!term->compositor->scene - || term->compositor->msg_type - || (gf_term_get_option(term, GF_OPT_PLAY_STATE) == GF_STATE_STEP_PAUSE) - ) { - if (last_error) return 1; - e = gf_term_process_flush(term); - if (e) { - fprintf(stderr, "Error initializing plalback: %s\n", gf_error_to_string(e)); - return 1; - } - } - - if (width && height) { - gf_term_set_size(term, width, height); - gf_term_process_flush(term); - } -#ifndef GPAC_USE_TINYGL - e = gf_sc_get_screen_buffer(term->compositor, &fb, 0); -#else - e = gf_sc_get_screen_buffer(term->compositor, &fb, 1); -#endif - if (e != GF_OK) { - fprintf(stderr, "Error grabbing screen buffer: %s\n", gf_error_to_string(e)); - return 1; - } - width = fb.width; - height = fb.height; - gf_sc_release_screen_buffer(term->compositor, &fb); - - if (scale != 1) { - width = (u32)(width * scale); - height = (u32)(height * scale); - gf_term_set_size(term, width, height); - gf_term_process_flush(term); - } - - /*we work in RGB24, and we must make sure the pitch is %4*/ - if ((width*3)%4) { - fprintf(stderr, "Adjusting width (%d) to have a stride multiple of 4\n", width); - while ((width*3)%4) width--; - - gf_term_set_size(term, width, height); - gf_term_process_flush(term); - - gf_sc_get_screen_buffer(term->compositor, &fb, 0); - width = fb.width; - height = fb.height; - gf_sc_release_screen_buffer(term->compositor, &fb); - } - - - strcpy(szPath_depth, szOutPath); - - if (mode==DUMP_AVI) { -#ifdef GPAC_DISABLE_AVILIB - fprintf(stderr, "AVILib is disabled in this build of GPAC\n"); - return 0; -#else - strcat(szOutPath, ".avi"); - avi_out = AVI_open_output_file(szOutPath); - if (!avi_out) { - fprintf(stderr, "Error creating AVI file %s\n", szOutPath); - return 1; - } -#endif - } - - if (mode==DUMP_SHA1) { - strcat(szOutPath, ".sha1"); - sha_out = gf_fopen(szOutPath, "wb"); - if (!sha_out) { - fprintf(stderr, "Error creating SHA file %s\n", szOutPath); - return 1; - } - } - - if (dump_mode_flags & DUMP_DEPTH_ONLY) { - if (mode==DUMP_AVI) { -#ifndef GPAC_DISABLE_AVILIB - strcat(szPath_depth, "_depth.avi"); - depth_avi_out = AVI_open_output_file(szPath_depth); - if (!depth_avi_out) { - fprintf(stderr, "Error creating depth AVI file %s\n", szPath_depth); - return 1; - } -#endif - } - if (mode==DUMP_SHA1) { - strcat(szPath_depth, "_depth.sha1"); - sha_depth_out = gf_fopen(szPath_depth, "wb"); - if (!sha_depth_out) { - fprintf(stderr, "Error creating depgth SHA file %s\n", szPath_depth); - return 1; - } - } - } - - - if (!fps) fps = GF_IMPORT_DEFAULT_FPS; - time = prev_time = 0; - nb_frames = 0; - - if (nb_times==2) { - prev_time = times[0]; - dump_dur = times[1] - times[0]; - } else if ((mode==DUMP_AVI) || (mode==DUMP_SHA1)) { - dump_dur = times[0] ? times[0] : Duration; - } else { - dump_dur = times[nb_times-1]; - dump_dur ++; - } - if (!dump_dur) { - fprintf(stderr, "Warning: file has no duration, defaulting to 1 sec\n"); - dump_dur = 1000; - } - - if (mode==DUMP_AVI) { - avi_mx = gf_mx_new("AVIMutex"); - -#ifndef GPAC_DISABLE_AVILIB - comp[0] = comp[1] = comp[2] = comp[3] = comp[4] = 0; - AVI_set_video(avi_out, width, height, fps, comp); - - if (! (term->user->init_flags & GF_TERM_NO_AUDIO)) { - memset(&avi_al, 0, sizeof(avi_al)); - avi_al.al.udta = &avi_al; - avi_al.al.on_audio_frame = avi_audio_frame; - avi_al.al.on_audio_reconfig = avi_audio_reconfig; - avi_al.mx = avi_mx; - avi_al.avi = avi_out; - avi_al.max_dur=dump_dur; - - gf_sc_add_audio_listener(term->compositor, &avi_al.al); - } - - if (dump_mode_flags & DUMP_DEPTH_ONLY) - AVI_set_video(depth_avi_out, width, height, fps, comp); -#endif - } - - if ((mode==DUMP_AVI) || (mode==DUMP_SHA1)) { - - if (dump_mode_flags & (DUMP_RGB_DEPTH | DUMP_RGB_DEPTH_SHAPE) ) - conv_buf = gf_malloc(sizeof(char) * width * height * 4); - else - conv_buf = gf_malloc(sizeof(char) * width * height * 3); - } - - cur_time_idx = 0; - init_time = 0; - /*step to first frame*/ - if (prev_time) { - gf_term_step_clocks(term, prev_time); - init_time = prev_time; - prev_time=0; - } -#ifndef GPAC_DISABLE_AVILIB - avi_al.audio_time_init = avi_al.next_video_time = init_time; - avi_al.audio_clock_at_video_init = gf_term_get_clock(term); -#endif - - ret = 0; - while (time < dump_dur) { - u32 frame_start_time = gf_sys_clock(); - while ((gf_term_get_option(term, GF_OPT_PLAY_STATE) == GF_STATE_STEP_PAUSE)) { - e = gf_term_process_flush(term); - if (e) { - ret = 1; - break; - } - //if we can't flush a frame in 30 seconds consider this is an error - if (gf_sys_clock() - frame_start_time > 30000) { - GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[MP4Client] Could not flush frame in 30 seconds for AVI dump, aborting dump\n")); - ret = 1; - break; - } - } - if (ret) - break; - - if ((mode==DUMP_AVI) || (mode==DUMP_SHA1)) { - - if (!no_prog) - fprintf(stderr, "Dumping %02d/100 %% - time %.02f sec\r", (u32) ((100.0*prev_time)/dump_dur), prev_time/1000.0 ); - - if (avi_mx) gf_mx_p(avi_mx); - - if (dump_mode_flags & DUMP_DEPTH_ONLY) { - - /*we'll dump both buffers at once*/ - gf_mx_p(term->compositor->mx); - dump_depth(term, szPath_depth, dump_mode_flags, i+1, conv_buf, depth_avi_out, sha_depth_out); - dump_frame(term, szOutPath, mode, i+1, conv_buf, avi_out, sha_out); - gf_mx_v(term->compositor->mx); - } else { - dump_frame(term, szOutPath, dump_mode_flags, i+1, conv_buf, avi_out, sha_out); - } - - if (avi_mx) gf_mx_v(avi_mx); - - } else { - if ( times[cur_time_idx] <= time) { - if (dump_mode_flags & (DUMP_DEPTH_ONLY | DUMP_RGB_DEPTH | DUMP_RGB_DEPTH_SHAPE) ) { - dump_depth(term, szOutPath, dump_mode_flags, cur_time_idx+1, NULL, NULL, NULL); - } else { - dump_frame(term, out_url, dump_mode_flags, cur_time_idx+1, NULL, NULL, NULL); - } - - cur_time_idx++; - if (cur_time_idx>=nb_times) - break; - } - } - - nb_frames++; - time = (u32) (nb_frames*1000/fps); -#ifndef GPAC_DISABLE_AVILIB - avi_al.next_video_time = init_time + time; -#endif - gf_term_step_clocks(term, time - prev_time); - prev_time = time; - - if (gf_prompt_has_input() && (gf_prompt_get_char()=='q')) { - fprintf(stderr, "Aborting dump\n"); - break; - } - } - -#ifndef GPAC_DISABLE_AVILIB - //flush audio dump - if (!ret && (mode==DUMP_AVI) && ! (term->user->init_flags & GF_TERM_NO_AUDIO)) { - avi_al.flush_retry=0; - while ((avi_al.flush_retry <1000) && (avi_al.audio_time < avi_al.audio_time_init + avi_al.max_dur)) { - gf_term_step_clocks(term, 0); - avi_al.flush_retry++; - gf_sleep(1); - } - if (avi_al.audio_time < avi_al.audio_time_init + avi_al.max_dur) { - GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Failed to flush audio frames: audio time "LLU" - expected "LLU" - retry %d\n", avi_al.audio_time, avi_al.audio_time_init + avi_al.max_dur, avi_al.flush_retry)); - ret = 1; - } - } - - if (! (term->user->init_flags & GF_TERM_NO_AUDIO)) { - gf_sc_remove_audio_listener(term->compositor, &avi_al.al); - } - if (avi_out) AVI_close(avi_out); - if (depth_avi_out) AVI_close(depth_avi_out); - if (avi_mx) gf_mx_del(avi_mx); -#endif - - if (sha_out) gf_fclose(sha_out); - if (sha_depth_out) gf_fclose(sha_depth_out); - - if (conv_buf) { - gf_free(conv_buf); - fprintf(stderr, "Dumping done: %d frames at %g FPS\n", nb_frames, fps); - } - - return ret; -} - diff --git a/applications/mp4client/main.c b/applications/mp4client/main.c index 397bf6c..4a1b1d2 100644 --- a/applications/mp4client/main.c +++ b/applications/mp4client/main.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2012 + * Copyright (c) Telecom ParisTech 2005-2020 * All rights reserved * * This file is part of GPAC / command-line client @@ -31,18 +31,15 @@ #include #include #include -#include +#include + #include #include #include -#include /*ISO 639 languages*/ #include -//FIXME we need a plugin for playlists -#include - #ifndef WIN32 #include @@ -62,31 +59,33 @@ void carbon_uninit(); #endif //WIN32 /*local prototypes*/ -void PrintWorldInfo(GF_Terminal *term); -void ViewOD(GF_Terminal *term, u32 OD_ID, u32 number, const char *URL); -void PrintODList(GF_Terminal *term, GF_ObjectManager *root_odm, u32 num, u32 indent, char *root_name); +static void PrintWorldInfo(GF_Terminal *term); +static void ViewOD(GF_Terminal *term, u32 OD_ID, u32 number, const char *URL); +static void PrintODList(GF_Terminal *term, GF_ObjectManager *root_odm, u32 num, u32 indent, char *root_name); -void ViewODs(GF_Terminal *term, Bool show_timing); -void PrintGPACConfig(); +static void ViewODs(GF_Terminal *term, Bool show_timing); +static void MakeScreenshot(Bool for_coverage); +static void PrintAVInfo(Bool final); static u32 gui_mode = 0; +static int ret_val = 0; static Bool restart = GF_FALSE; static Bool reload = GF_FALSE; +Bool do_coverage = GF_FALSE; Bool no_prog = 0; +static FILE *helpout = NULL; +u32 help_flags = 0; + #if defined(__DARWIN__) || defined(__APPLE__) -//we keep no decoder thread because of JS_GC deadlocks between threads ... -static u32 threading_flags = GF_TERM_NO_COMPOSITOR_THREAD | GF_TERM_NO_DECODER_THREAD; #define VK_MOD GF_KEY_MOD_ALT #else -static u32 threading_flags = 0; #define VK_MOD GF_KEY_MOD_CTRL #endif + static Bool no_audio = GF_FALSE; -static Bool term_step = GF_FALSE; -static Bool no_regulation = GF_FALSE; static u32 bench_mode = 0; static u32 bench_mode_start = 0; static u32 bench_buffer = 0; @@ -105,16 +104,13 @@ static s32 request_next_playlist_item = GF_FALSE; FILE *playlist = NULL; static Bool readonly_playlist = GF_FALSE; -static GF_Config *cfg_file; static u32 display_rti = 0; static Bool Run; static Bool CanSeek = GF_FALSE; static char the_url[GF_MAX_PATH]; static char pl_path[GF_MAX_PATH]; static Bool no_mime_check = GF_TRUE; -static Bool be_quiet = GF_FALSE; -static u64 log_time_start = 0; -static Bool log_utc_time = GF_FALSE; +static u64 log_rti_time_start = 0; static Bool loop_at_end = GF_FALSE; static u32 forced_width=0; static u32 forced_height=0; @@ -126,45 +122,97 @@ u32 init_h = 0; u32 last_x, last_y; Bool right_down = GF_FALSE; -void dump_frame(GF_Terminal *term, char *rad_path, u32 dump_type, u32 frameNum); - -enum -{ - DUMP_NONE = 0, - DUMP_AVI = 1, - DUMP_BMP = 2, - DUMP_PNG = 3, - DUMP_RAW = 4, - DUMP_SHA1 = 5, - - //DuMP flags - DUMP_DEPTH_ONLY = 1<<16, - DUMP_RGB_DEPTH = 1<<17, - DUMP_RGB_DEPTH_SHAPE = 1<<18 -}; +Float scale = 1; -Bool dump_file(char *the_url, char *out_url, u32 dump_mode, Double fps, u32 width, u32 height, Float scale, u32 *times, u32 nb_times); +static Bool shell_visible = GF_TRUE; +#if defined(WIN32) && !defined(_WIN32_WCE) +static HWND console_hwnd = NULL; +static Bool owns_wnd = GF_FALSE; +#include +#include +static DWORD getParentPID(DWORD pid) +{ + DWORD ppid = 0; + HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (h) { + PROCESSENTRY32 pe = { 0 }; + pe.dwSize = sizeof(PROCESSENTRY32); + if (Process32First(h, &pe)) { + do { + if (pe.th32ProcessID == pid) { + ppid = pe.th32ParentProcessID; + break; + } + } while (Process32Next(h, &pe)); + } + CloseHandle(h); + } + return (ppid); +} -static Bool shell_visible = GF_TRUE; -void hide_shell(u32 cmd_type) +static void getProcessName(DWORD pid, PUCHAR fname, DWORD sz) +{ + HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); + if (h) { + GetModuleFileNameEx(h, NULL, fname, sz); + CloseHandle(h); + } +} +static void w32_hide_shell(u32 cmd_type) { -#if defined(WIN32) && !defined(_WIN32_WCE) typedef HWND (WINAPI *GetConsoleWindowT)(void); HMODULE hk32 = GetModuleHandle("kernel32.dll"); - GetConsoleWindowT GetConsoleWindow = (GetConsoleWindowT ) GetProcAddress(hk32,"GetConsoleWindow"); + if (!console_hwnd) { + char parentName[GF_MAX_PATH]; + DWORD dwProcessId = 0; + DWORD dwParentProcessId = 0; + DWORD dwParentParentProcessId = 0; + GetConsoleWindowT GetConsoleWindow = (GetConsoleWindowT)GetProcAddress(hk32, "GetConsoleWindow"); + console_hwnd = GetConsoleWindow(); + dwProcessId = GetCurrentProcessId(); + dwParentProcessId = getParentPID(dwProcessId); + if (dwParentProcessId) + dwParentParentProcessId = getParentPID(dwParentProcessId); + //get parent process name, check for explorer + parentName[0] = 0; + getProcessName(dwParentProcessId, parentName, GF_MAX_PATH); + if (strstr(parentName, "explorer")) { + owns_wnd = GF_TRUE; + } + //get parent parent process name, check for devenv (or any other ide name ...) + else if (dwParentParentProcessId) { + owns_wnd = GF_FALSE; + parentName[0] = 0; + getProcessName(dwParentParentProcessId, parentName, GF_MAX_PATH); + if (strstr(parentName, "devenv")) { + owns_wnd = GF_TRUE; + } + } else { + owns_wnd = GF_FALSE; + } + } + if (!owns_wnd || !console_hwnd) return; + if (cmd_type==0) { - ShowWindow( GetConsoleWindow(), SW_SHOW); + ShowWindow(console_hwnd, SW_SHOW); shell_visible = GF_TRUE; } else if (cmd_type==1) { - ShowWindow( GetConsoleWindow(), SW_HIDE); + ShowWindow(console_hwnd, SW_HIDE); shell_visible = GF_FALSE; } - else if (cmd_type==2) PostMessage(GetConsoleWindow(), WM_CLOSE, 0, 0); + else if (cmd_type == 2) { + PostMessage(GetConsoleWindow(), WM_CLOSE, 0, 0); + } +} + +#define hide_shell w32_hide_shell +#else + +#define hide_shell(_var) #endif -} void send_open_url(const char *url) @@ -176,198 +224,218 @@ void send_open_url(const char *url) gf_term_send_event(term, &evt); } -void PrintUsage() +GF_GPACArg mp4client_args[] = { - fprintf(stderr, "Usage MP4Client [options] [filename]\n" - "\t-c fileName: user-defined configuration file. Also works with -cfg\n" #ifdef GPAC_MEMORY_TRACKING - "\t-mem-track: enables memory tracker\n" - "\t-mem-track-stack: enables memory tracker with stack dumping\n" -#endif - "\t-rti fileName: logs run-time info (FPS, CPU, Mem usage) to file\n" - "\t-rtix fileName: same as -rti but driven by GPAC logs\n" - "\t-quiet: removes script message, buffering and downloading status\n" - "\t-strict-error: exit when the player reports its first error\n" - "\t-opt option: Overrides an option in the configuration file. String format is section:key=value. \n" - "\t \"section:key=null\" removes the key\n" - "\t \"section:*=null\" removes the section\n" - "\t-conf option: Same as -opt but does not start player.\n" - "\t-log-file file: sets output log file. Also works with -lf\n" - "\t-logs log_args: sets log tools and levels, formatted as a ':'-separated list of toolX[:toolZ]@levelX\n" - "\t levelX can be one of:\n" - "\t \"quiet\" : skip logs\n" - "\t \"error\" : logs only error messages\n" - "\t \"warning\" : logs error+warning messages\n" - "\t \"info\" : logs error+warning+info messages\n" - "\t \"debug\" : logs all messages\n" - "\t toolX can be one of:\n" - "\t \"core\" : libgpac core\n" - "\t \"coding\" : bitstream formats (audio, video, scene)\n" - "\t \"container\" : container formats (ISO File, MPEG-2 TS, AVI, ...)\n" - "\t \"network\" : network data exept RTP trafic\n" - "\t \"rtp\" : rtp trafic\n" - "\t \"author\" : authoring tools (hint, import, export)\n" - "\t \"sync\" : terminal sync layer\n" - "\t \"codec\" : terminal codec messages\n" - "\t \"parser\" : scene parsers (svg, xmt, bt) and other\n" - "\t \"media\" : terminal media object management\n" - "\t \"scene\" : scene graph and scene manager\n" - "\t \"script\" : scripting engine messages\n" - "\t \"interact\" : interaction engine (events, scripts, etc)\n" - "\t \"smil\" : SMIL timing engine\n" - "\t \"compose\" : composition engine (2D, 3D, etc)\n" - "\t \"mmio\" : Audio/Video HW I/O management\n" - "\t \"rti\" : various run-time stats\n" - "\t \"cache\" : HTTP cache subsystem\n" - "\t \"audio\" : Audio renderer and mixers\n" -#ifdef GPAC_MEMORY_TRACKING - "\t \"mem\" : GPAC memory tracker\n" -#endif -#ifndef GPAC_DISABLE_DASH_CLIENT - "\t \"dash\" : HTTP streaming logs\n" + GF_DEF_ARG("mem-track", NULL, "enable memory tracker", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("mem-track-stack", NULL, "enable memory tracker with stack dumping", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), #endif - "\t \"module\" : GPAC modules debugging\n" - "\t \"mutex\" : mutex\n" - "\t \"all\" : all tools logged - other tools can be specified afterwards.\n" - "\n" - "\t-log-clock or -lc : logs time in micro sec since start time of GPAC before each log line.\n" - "\t-log-utc or -lu : logs UTC time in ms before each log line.\n" - "\t-ifce IPIFCE : Sets default Multicast interface\n" - "\t-size WxH: specifies visual size (default: scene size)\n" -#if defined(__DARWIN__) || defined(__APPLE__) - "\t-thread: enables thread usage for terminal and compositor \n" -#else - "\t-no-thread: disables thread usage (except for audio)\n" -#endif - "\t-no-compositor-thread: disables compositor thread (iOS and Android mode)\n" - "\t-no-audio: disables audio \n" - "\t-no-wnd: uses windowless mode (Win32 only)\n" - "\t-no-back: uses transparent background for output window when no background is specified (Win32 only)\n" - "\t-align vh: specifies v and h alignment for windowless mode\n" - "\t possible v values: t(op), m(iddle), b(ottom)\n" - "\t possible h values: l(eft), m(iddle), r(ight)\n" - "\t default alignment is top-left\n" - "\t default alignment is top-left\n" - "\t-pause: pauses at first frame\n" - "\t-play-from T: starts from T seconds in media\n" - "\t-speed S: starts with speed S\n" - "\t-loop: loops presentation\n" - "\t-no-regulation: disables framerate regulation\n" - "\t-bench: disable a/v output and bench source decoding (as fast as possible)\n" - "\t-vbench: disable audio output, video sync bench source decoding/display (as fast as possible)\n" - "\t-sbench: disable all decoders and bench systems layer (as fast as possible)\n" - "\t-fs: starts in fullscreen mode\n" - "\t-views v1:.:vN: creates an auto-stereo scene of N views. vN can be any type of URL supported by GPAC.\n" - "\t in this mode, URL argument of GPAC is ignored, GUI as well.\n" - "\t this is equivalent as using views://v1:.:N as an URL.\n" - "\n" - "\t-exit: automatically exits when presentation is over\n" - "\t-run-for TIME: runs for TIME seconds and exits\n" - "\t-service ID: auto-tune to given service ID in a multiplex\n" - "\t-noprog: disable progress report\n" - "\t-no-save: disable saving config file on exit\n" - "\t-no-addon: disable automatic loading of media addons declared in source URL\n" - "\t-gui: starts in GUI mode. The GUI is indicated in GPAC config, section General, by the key [StartupFile]\n" - "\t-ntp-shift T: shifts NTP clock of T (signed int) milliseconds\n" - "\n" - "Dumper Options (times is a formated as start-end, with start being sec, h:m:s:f/fps or h:m:s:ms):\n" - "\t-bmp [times]: dumps given frames to bmp\n" - "\t-png [times]: dumps given frames to png\n" - "\t-raw [times]: dumps given frames to raw\n" - "\t-avi [times]: dumps given file to raw avi\n" - "\t-sha [times]: dumps given file to raw SHA-1 (1 hash per frame)\n" - "\r-out filename: name of the output file\n" - "\t-rgbds: dumps the RGBDS pixel format texture\n" - "\t with -avi [times]: dumps an rgbds-format .avi\n" - "\t-rgbd: dumps the RGBD pixel format texture\n" - "\t with -avi [times]: dumps an rgbd-format .avi\n" - "\t-depth: dumps depthmap (z-buffer) frames\n" - "\t with -avi [times]: dumps depthmap in grayscale .avi\n" - "\t with -bmp: dumps depthmap in grayscale .bmp\n" - "\t with -png: dumps depthmap in grayscale .png\n" - "\t-fps FPS: specifies frame rate for AVI dumping (default: %f)\n" - "\t-scale s: scales the visual size (default: 1)\n" - "\t-fill: uses fill aspect ratio for dumping (default: none)\n" - "\t-show: shows window while dumping (default: no)\n" - "\n" - "\t-uncache: Revert all cached items to their original name and location. Does not start player.\n" - "\n" - "\t-help: shows this screen\n" - "\n" - "MP4Client - GPAC command line player and dumper - version "GPAC_FULL_VERSION"\n" - "GPAC Written by Jean Le Feuvre (c) 2001-2005 - ENST (c) 2005-200X\n" - "GPAC Configuration: " GPAC_CONFIGURATION "\n" - "Features: %s\n", - GF_IMPORT_DEFAULT_FPS, - gpac_features() - ); + GF_DEF_ARG("rti", NULL, "log run-time info (FPS, CPU, Mem usage) to given file", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("rtix", NULL, "same as -rti but driven by GPAC logs", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("size", NULL, "specify visual size WxH. If not set, scene size or video size is used", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("rti-refresh", NULL, "set refresh time in ms between two runt-time counters queries (default is 200)", NULL, NULL, GF_ARG_INT, 0), + + GF_DEF_ARG("no-thread", NULL, "disable thread usage (except for depending on driver audio)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("no-audio", NULL, "disable audio", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + +#ifdef GPAC_CONFIG_WIN32 + GF_DEF_ARG("no-wnd", NULL, "use windowless mode (Win32 only)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERIMENTAL), + GF_DEF_ARG("no-back", NULL, "use transparent background for output window when no background is specified (Win32 only)", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERIMENTAL), + GF_DEF_ARG("align", NULL, "specify v and h alignment for windowless mode\n" + "- tl: top/left\n" + "- tm: top/horizontal middle\n" + "- tr: top/right\n" + "- ml: vertical middle/left\n" + "- mm: vertical middle/horizontal middle\n" + "- mr: vertical middle/right\n" + "- bl: bottom/left\n" + "- bm: bottom/horizontal middle\n" + "- br: bottom/right", "tl", "tl|tm|tr|ml|mm|mr|bl|bm|br", GF_ARG_INT, GF_ARG_HINT_EXPERIMENTAL), +#endif // GPAC_CONFIG_WIN32 + + GF_DEF_ARG("pause", NULL, "pause at first frame", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("play-from", NULL, "start playback from given time in seconds in media", NULL, NULL, GF_ARG_DOUBLE, 0), + GF_DEF_ARG("speed", NULL, "start playback wit given speed", NULL, NULL, GF_ARG_DOUBLE, 0), + GF_DEF_ARG("loop", NULL, "loop playback", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("fs", NULL, "start in fullscreen mode", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("exit", NULL, "exit when presentation is over", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("run-for", NULL, "run for indicated time in seconds and exits", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("service", NULL, "auto-tune to given service ID in a multiplex", NULL, NULL, GF_ARG_INT, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("no-save", NULL, "do not save configuration file on exit", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("no-addon", NULL, "disable automatic loading of media addons declared in source URL", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("gui", NULL, "start in GUI mode. The GUI is indicated in the [configuration](core_config) file __[General]StartupFile__", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("p", NULL, "use indicated profile for the global GPAC config. If not found, config file is created. If a file path is indicated, this will load profile from that file. Otherwise, this will create a directory of the specified name and store new config there. Reserved name `0` means a new profile, not stored to disk. Works using -p=NAME or -p NAME", NULL, NULL, GF_ARG_STRING, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("stats", NULL, "dump filter session stats after playback", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("graph", NULL, "dump filter session graph after playback", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_EXPERT), + GF_DEF_ARG("nk", NULL, "disable keyboard interaction", NULL, NULL, GF_ARG_BOOL, GF_ARG_HINT_ADVANCED), + GF_DEF_ARG("h", "help", "show this help. Use `-hx` to show expert help", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("hc", NULL, "show libgpac core options", NULL, NULL, GF_ARG_BOOL, 0), + GF_DEF_ARG("hr", NULL, "show runtime options when keybard interaction is enabled", NULL, NULL, GF_ARG_BOOL, 0), + {0} +}; + + +void PrintUsage(Bool show_all) +{ + u32 i=0; + + if (help_flags & GF_PRINTARG_MAN) { + fprintf(helpout, ".SH \"DESCRIPTION\"\n.LP\nMP4Client is GPAC command-line media player. It supports all GPAC playback features (2D and 3D support, local playback, RTP streaming, HTTP faststart, many audio and video codecs ...).\n.br\nSpecific URLs shortcuts are available, see gpac -h compositor\n.\n.\n.SH OPTIONS\n"); + } else { + gf_sys_format_help(helpout, help_flags, "Usage: MP4Client [options] [filename]\n" + "# General\n" + "The player accepts any URL supported by GPAC.\n" + "Specific URLs shortcuts are available, see [GPAC Compositor (gpac -h compositor)](compositor)\n" + "Version: %s\n" + "%s\n" + "For more info on GPAC configuration, use `gpac ` [-h](GPAC) `bin` \n \n" + "# Options \n \n", + (help_flags == GF_PRINTARG_MD) ? GPAC_VERSION : gf_gpac_version(), + gf_gpac_copyright_cite() + ); + } + + while (mp4client_args[i].name) { + GF_GPACArg *arg = &mp4client_args[i]; + i++; + if (!show_all && (arg->flags & (GF_ARG_HINT_EXPERIMENTAL|GF_ARG_HINT_EXPERT) )) + continue; + gf_sys_print_arg(helpout, help_flags, arg, "mp4client"); + } +} + +typedef enum +{ + MP4C_QUIT = 0, + MP4C_KILL, + MP4C_RELOAD, + MP4C_OPEN, + MP4C_OPEN_PL, + MP4C_PL_NEXT, + MP4C_PL_JUMP, + MP4C_DISCONNECT, + MP4C_SELECT, + MP4C_PAUSE_RESUME, + MP4C_STEP, + MP4C_SEEK, + MP4C_SEEK_TIME, + MP4C_TIME, + MP4C_UPDATE, + MP4C_EVALJS, + MP4C_SCREENSHOT, + MP4C_WORLDINFO, + MP4C_ODLIST, + MP4C_ODINFO_ID, + MP4C_ODINFO_NUM, + MP4C_ODTIME, + MP4C_ODBUF, + MP4C_DUMPSCENE, + MP4C_STRESSMODE, + MP4C_NAVMODE, + MP4C_LASTVP, + MP4C_OGL2D, + MP4C_AR_4_3, + MP4C_AR_16_9, + MP4C_AR_NONE, + MP4C_AR_ORIG, + MP4C_LOGS, + MP4C_RELOAD_OPTS, + MP4C_DISP_RTI, + MP4C_DISP_FPS, + MP4C_DISP_STATS, + MP4C_DISP_GRAPH, + MP4C_HELP, + MP4C_DOWNRATE, + MP4C_VMEM_CACHE, + +} MP4C_Command; + +struct _mp4c_key +{ + u8 char_code; + MP4C_Command cmd_type; + const char *cmd_help; + u32 flags; +} MP4C_Keys[] = { + {'q', MP4C_QUIT, "quit", 0}, + {'X', MP4C_KILL, "kill", 0}, + {'r', MP4C_KILL, "reload current presentation", 0}, + {'o', MP4C_OPEN, "connect to the specified URL", 0}, + {'O', MP4C_OPEN_PL, "connect to the specified playlist", 0}, + {'N', MP4C_PL_NEXT, "switch to the next URL in the playlist. Also works with `\\n`", 0}, + {'P', MP4C_PL_JUMP, "jump to a given number ahead in the playlist", 0}, + {'D', MP4C_DISCONNECT, "disconnect the current presentation", 0}, + {'G', MP4C_SELECT, "select object or service ID", 0}, + {'p', MP4C_PAUSE_RESUME, "play/pause the presentation", 0}, + {'s', MP4C_STEP, "step one frame ahead", 0}, + {'z', MP4C_SEEK, "seek into presentation by percentage", 0}, + {'T', MP4C_SEEK_TIME, "seek into presentation by time", 0}, + {'t', MP4C_TIME, "print current timing", 0}, + {'u', MP4C_UPDATE, "send a command (BIFS or LASeR) to the main scene", 0}, + {'e', MP4C_EVALJS, "evaluate JavaScript code in the main scene", 0}, + {'Z', MP4C_SCREENSHOT, "dump current output frame to PNG", 0}, + {'w', MP4C_WORLDINFO, "view world info", 0}, + { 'v', MP4C_ODLIST, "view list of active media objects in scene", 0}, + { 'i', MP4C_ODINFO_ID, "view Object Descriptor info (by ID)", 0}, + { 'j', MP4C_ODINFO_NUM, "view Object Descriptor info (by number)", 0}, + { 'b', MP4C_ODTIME, "view media objects timing and buffering info", 0}, + { 'm', MP4C_ODBUF, "view media objects buffering and memory info", 0}, + { 'd', MP4C_DUMPSCENE, "dump scene graph", 0}, + { 'k', MP4C_STRESSMODE, "turn stress mode on/off", 0}, + { 'n', MP4C_NAVMODE, "change navigation mode", 0}, + { 'x', MP4C_LASTVP, "reset to last active viewpoint", 0}, + { '3', MP4C_OGL2D, "switch OpenGL on or off for 2D scenes", 0}, + { '4', MP4C_AR_4_3, "force 4/3 Aspect Ratio", 0}, + { '5', MP4C_AR_16_9, "force 16/9 Aspect Ratio", 0}, + { '6', MP4C_AR_NONE, "force no Aspect Ratio (always fill screen)", 0}, + { '7', MP4C_AR_ORIG, "force original Aspect Ratio (default)", 0}, + { 'H', MP4C_DOWNRATE, "set HTTP max download rate", 0}, + { 'E', MP4C_RELOAD_OPTS, "force reload of compositor options", 0}, + + { 'L', MP4C_LOGS, "change to new log tool/level. CF MP4Client usage for possible values", 0}, + { 'R', MP4C_DISP_RTI, "toggle run-time info display in window title bar on/off", 0}, + { 'F', MP4C_DISP_FPS, "toggle displaying of FPS in stderr on/off", 0}, + { 'f', MP4C_DISP_STATS, "print filter session stats", 0}, + { 'g', MP4C_DISP_GRAPH, "print filter session graph", 0}, + + { 'h', MP4C_HELP, "print this message", 0}, + //below this, only experimental features + { 'M', MP4C_VMEM_CACHE, "specify video cache memory for 2D objects", 1}, + {0} +}; + +MP4C_Command get_cmd(u8 char_code) +{ + u32 i=0; + while (MP4C_Keys[i].char_code) { + if (MP4C_Keys[i].char_code == char_code) + return MP4C_Keys[i].cmd_type; + i++; + } + return 0; } void PrintHelp() { - fprintf(stderr, "MP4Client command keys:\n" - "\tq: quit\n" - "\tX: kill\n" - "\to: connect to the specified URL\n" - "\tO: connect to the specified playlist\n" - "\tN: switch to the next URL in the playlist. Also works with \\n\n" - "\tP: jumps to a given number ahead in the playlist\n" - "\tr: reload current presentation\n" - "\tD: disconnects the current presentation\n" - "\tG: selects object or service ID\n" - "\n" - "\tp: play/pause the presentation\n" - "\ts: step one frame ahead\n" - "\tz: seek into presentation by percentage\n" - "\tT: seek into presentation by time\n" - "\tt: print current timing\n" - "\n" - "\tu: sends a command (BIFS or LASeR) to the main scene\n" - "\te: evaluates JavaScript code\n" - "\tZ: dumps output video to PNG\n" - "\n" - "\tw: view world info\n" - "\tv: view Object Descriptor list\n" - "\ti: view Object Descriptor info (by ID)\n" - "\tj: view Object Descriptor info (by number)\n" - "\tb: view media objects timing and buffering info\n" - "\tm: view media objects buffering and memory info\n" - "\td: dumps scene graph\n" - "\n" - "\tk: turns stress mode on/off\n" - "\tn: changes navigation mode\n" - "\tx: reset to last active viewpoint\n" - "\n" - "\t3: switch OpenGL on or off for 2D scenes\n" - "\n" - "\t4: forces 4/3 Aspect Ratio\n" - "\t5: forces 16/9 Aspect Ratio\n" - "\t6: forces no Aspect Ratio (always fill screen)\n" - "\t7: forces original Aspect Ratio (default)\n" - "\n" - "\tL: changes to new log level. CF MP4Client usage for possible values\n" - "\tT: select new tools to log. CF MP4Client usage for possible values\n" - "\n" - "\tl: list available modules\n" - "\tc: prints some GPAC configuration info\n" - "\tE: forces reload of GPAC configuration\n" - "\n" - "\tR: toggles run-time info display in window title bar on/off\n" - "\tF: toggle displaying of FPS in stderr on/off\n" - "\tg: print GPAC allocated memory\n" - "\th: print this message\n" - "\n" - "\tEXPERIMENTAL/UNSTABLE OPTIONS\n" - "\tC: Enable Streaming Cache\n" - "\tS: Stops Streaming Cache and save to file\n" - "\tA: Aborts Streaming Cache\n" - "\tM: specifies video cache memory for 2D objects\n" - "\n" - "MP4Client - GPAC command line player - version %s\n" - "GPAC Written by Jean Le Feuvre (c) 2001-2005 - ENST (c) 2005-200X\n", - - GPAC_FULL_VERSION - ); + u32 i=0; + + gf_sys_format_help(helpout, help_flags, "# MP4Client runtime commands\n" + "## Prompt Interaction\n" + "The following keys are used for prompt interaction:\n"); + + while (MP4C_Keys[i].char_code) { + struct _mp4c_key *k = &MP4C_Keys[i]; + i++; + gf_sys_format_help(helpout, help_flags|GF_PRINTARG_HIGHLIGHT_FIRST, "%c: %s%s\n", k->char_code, k->cmd_help, k->flags ? " **! experimental !**" : ""); + } + + gf_sys_format_help(helpout, help_flags, "\n" + "## Content interaction\n" + "It is possible to interact with content (interactive or not) using mouse and keyboard.\n" + "The following commands are available:\n" + "TODO\n" + "\n" + ); } @@ -381,8 +449,6 @@ static void PrintTime(u64 time) fprintf(stderr, "%02d:%02d:%02d.%03d", h, m, s, ms); } -void PrintAVInfo(Bool final); - static u32 rti_update_time_ms = 200; static FILE *rti_logs = NULL; @@ -392,6 +458,7 @@ static void UpdateRTInfo(const char *legend) GF_SystemRTInfo rti; /*refresh every second*/ + if (!Run) return; if (!display_rti && !rti_logs) return; if (!gf_sys_get_rti(rti_update_time_ms, &rti, 0) && !legend) return; @@ -432,43 +499,46 @@ static void UpdateRTInfo(const char *legend) } } +#include +#define MP4CLIENT_CAPTION "GPAC MP4Client "GPAC_VERSION "-rev" GPAC_GIT_REVISION + static void ResetCaption() { GF_Event event; if (display_rti) return; event.type = GF_EVENT_SET_CAPTION; + event.caption.caption = NULL; + if (is_connected) { char szName[1024]; - NetInfoCommand com; + GF_TermURLInfo urli; - event.caption.caption = NULL; /*get any service info*/ - if (!startup_file && gf_term_get_service_info(term, gf_term_get_root_object(term), &com) == GF_OK) { + if (!startup_file && gf_term_get_service_info(term, gf_term_get_root_object(term), &urli) == GF_OK) { strcpy(szName, ""); - if (com.track_info) { + if (urli.track_num) { char szBuf[10]; - sprintf(szBuf, "%02d ", (u32) (com.track_info>>16) ); + sprintf(szBuf, "%02d ", (u32) urli.track_num ); strcat(szName, szBuf); } - if (com.artist) { - strcat(szName, com.artist); + if (urli.artist) { + strcat(szName, urli.artist); strcat(szName, " "); } - if (com.name) { - strcat(szName, com.name); + if (urli.name) { + strcat(szName, urli.name); strcat(szName, " "); } - if (com.album) { + if (urli.album) { strcat(szName, "("); - strcat(szName, com.album); + strcat(szName, urli.album); strcat(szName, ")"); } - if (com.provider) { + if (urli.provider) { strcat(szName, "("); - strcat(szName, com.provider); + strcat(szName, urli.provider); strcat(szName, ")"); } - if (strlen(szName)) event.caption.caption = szName; } if (!event.caption.caption) { @@ -477,7 +547,7 @@ static void ResetCaption() event.caption.caption = str ? str+1 : the_url; } } else { - event.caption.caption = "GPAC MP4Client " GPAC_FULL_VERSION; + event.caption.caption = MP4CLIENT_CAPTION; } gf_term_user_event(term, &event); } @@ -504,6 +574,12 @@ void switch_bench(u32 is_on) gf_term_set_option(term, GF_OPT_VIDEO_BENCH, is_on); } +#ifdef GPAC_ENABLE_COVERAGE +#define getch() 0 +#define read_line_input(_line, _maxSize, _showContent) GF_FALSE + +#else + #ifndef WIN32 #include int getch() { @@ -536,7 +612,7 @@ int getch() { * @param maxSize the maximum size of the line to read * @param showContent boolean indicating if the line read should be printed on stderr or not */ -static const char * read_line_input(char * line, int maxSize, Bool showContent) { +static Bool read_line_input(char * line, int maxSize, Bool showContent) { char read; int i = 0; if (fflush( stderr )) @@ -544,7 +620,7 @@ static const char * read_line_input(char * line, int maxSize, Bool showContent) do { line[i] = '\0'; if (i >= maxSize - 1) - return line; + return GF_FALSE; read = getch(); if (read == 8 || read == 127) { if (i > 0) { @@ -558,9 +634,10 @@ static const char * read_line_input(char * line, int maxSize, Bool showContent) fflush(stderr); } while (read != '\n'); if (!read) - return 0; - return line; + return GF_FALSE; + return GF_TRUE; } +#endif //!GPAC_ENABLE_COVERAGE static void do_set_speed(Fixed desired_speed) { @@ -579,6 +656,8 @@ Bool GPAC_EventProc(void *ptr, GF_Event *evt) if (gui_mode==1) { if (evt->type==GF_EVENT_QUIT) { Run = 0; + if (evt->message.error>0) + ret_val = evt->message.error; } else if (evt->type==GF_EVENT_KEYDOWN) { switch (evt->key.key_code) { case GF_KEY_C: @@ -620,8 +699,9 @@ Bool GPAC_EventProc(void *ptr, GF_Event *evt) } else { GF_LOG(GF_LOG_ERROR, GF_LOG_CONSOLE, ("%s %s: %s\n", servName, evt->message.message, gf_error_to_string(evt->message.error))); } - } else if (!be_quiet) + } else { GF_LOG(GF_LOG_INFO, GF_LOG_CONSOLE, ("%s %s\n", servName, evt->message.message)); + } } break; case GF_EVENT_PROGRESS: @@ -682,7 +762,6 @@ Bool GPAC_EventProc(void *ptr, GF_Event *evt) } break; case GF_EVENT_KEYDOWN: - gf_term_process_shortcut(term, evt); switch (evt->key.key_code) { case GF_KEY_SPACE: if (evt->key.flags & GF_KEY_MOD_CTRL) { @@ -814,6 +893,10 @@ Bool GPAC_EventProc(void *ptr, GF_Event *evt) if (playback_speed != FIX_ONE) gf_term_set_speed(term, playback_speed); + if (do_coverage) { + gf_term_switch_quality(term, 1); + } + } else if (is_connected) { fprintf(stderr, "Service %s\n", is_connected ? "Disconnected" : "Connection Failed"); is_connected = 0; @@ -826,7 +909,13 @@ Bool GPAC_EventProc(void *ptr, GF_Event *evt) break; case GF_EVENT_EOS: eos_seen = GF_TRUE; - if (!playlist && loop_at_end) restart = 1; + if (playlist) { + if (Duration>1500) + request_next_playlist_item = GF_TRUE; + } + else if (loop_at_end) { + restart = 1; + } break; case GF_EVENT_SIZE: if (user.init_flags & GF_TERM_WINDOWLESS) { @@ -839,23 +928,28 @@ Bool GPAC_EventProc(void *ptr, GF_Event *evt) } break; case GF_EVENT_SCENE_SIZE: - if (forced_width && forced_height) { + + if ((forced_width && forced_height) || scale) { GF_Event size; - size.type = GF_EVENT_SIZE; - size.size.width = forced_width; - size.size.height = forced_height; - gf_term_user_event(term, &size); + u32 nw = forced_width ? forced_width : evt->size.width; + u32 nh = forced_height ? forced_height : evt->size.height; + + if (scale != 1) { + nw = (u32)(nw * scale); + nh = (u32)(nh * scale); + } + if ((nw != evt->size.width) || (nh != evt->size.height)) { + size.type = GF_EVENT_SIZE; + size.size.width = nw; + size.size.height = nh; + gf_term_user_event(term, &size); + } } break; case GF_EVENT_METADATA: ResetCaption(); break; - - case GF_EVENT_RELOAD: - if (is_connected) - reload = 1; - break; case GF_EVENT_DROPFILE: { u32 i, pos; @@ -867,9 +961,9 @@ Bool GPAC_EventProc(void *ptr, GF_Event *evt) readonly_playlist = 0; if (!playlist) { readonly_playlist = 0; - playlist = gf_temp_file_new(NULL); + playlist = gf_file_temp(NULL); } - pos = ftell(playlist); + pos = (u32) gf_ftell(playlist); i=0; while (iopen_file.nb_files) { if (evt->open_file.files[i] != NULL) { @@ -877,14 +971,16 @@ Bool GPAC_EventProc(void *ptr, GF_Event *evt) } i++; } - fseek(playlist, pos, SEEK_SET); + gf_fseek(playlist, pos, SEEK_SET); request_next_playlist_item = 1; } return 1; case GF_EVENT_QUIT: - if (evt->message.error) { + if (evt->message.error<0) { fprintf(stderr, "A fatal error was encoutered: %s (%s) - exiting ...\n", evt->message.message ? evt->message.message : "no details", gf_error_to_string(evt->message.error) ); + } else { + ret_val = evt->message.error; } Run = 0; break; @@ -896,11 +992,13 @@ Bool GPAC_EventProc(void *ptr, GF_Event *evt) } break; case GF_EVENT_NAVIGATE_INFO: - if (evt->navigate.to_url) fprintf(stderr, "Go to URL: \"%s\"\r", evt->navigate.to_url); + if (evt->navigate.to_url) + fprintf(stderr, "Go to URL: \"%s\"\r", evt->navigate.to_url); break; case GF_EVENT_NAVIGATE: if (gf_term_is_supported_url(term, evt->navigate.to_url, 1, no_mime_check)) { - strcpy(the_url, evt->navigate.to_url); + strncpy(the_url, evt->navigate.to_url, sizeof(the_url)-1); + the_url[sizeof(the_url) - 1] = 0; fprintf(stderr, "Navigating to URL %s\n", the_url); gf_term_navigate_to(term, evt->navigate.to_url); return 1; @@ -913,20 +1011,23 @@ Bool GPAC_EventProc(void *ptr, GF_Event *evt) break; case GF_EVENT_AUTHORIZATION: { - int maxTries = 1; + u32 nb_retry = 4; assert( evt->type == GF_EVENT_AUTHORIZATION); assert( evt->auth.user); assert( evt->auth.password); assert( evt->auth.site_url); - while ((!strlen(evt->auth.user) || !strlen(evt->auth.password)) && (maxTries--) >= 0) { + while ((!strlen(evt->auth.user) || !strlen(evt->auth.password)) && (nb_retry > 0) ) { + nb_retry--; fprintf(stderr, "**** Authorization required for site %s ****\n", evt->auth.site_url); fprintf(stderr, "login : "); - read_line_input(evt->auth.user, 50, 1); + if (!read_line_input(evt->auth.user, 50, 1)) + continue; fprintf(stderr, "\npassword: "); - read_line_input(evt->auth.password, 50, 0); + if (!read_line_input(evt->auth.password, 50, 0)) + continue; fprintf(stderr, "*********\n"); } - if (maxTries < 0) { + if (nb_retry == 0) { fprintf(stderr, "**** No User or password has been filled, aborting ***\n"); return 0; } @@ -943,12 +1044,12 @@ Bool GPAC_EventProc(void *ptr, GF_Event *evt) } -void list_modules(GF_ModuleManager *modules) +void list_modules() { u32 i; fprintf(stderr, "\rAvailable modules:\n"); - for (i=0; i #endif -static void progress_quiet(const void *cbck, const char *title, u64 done, u64 total) { } - int mp4client_main(int argc, char **argv) { char c; + MP4C_Command cmdtype; const char *str; - int ret_val = 0; - u32 i, times[100], nb_times, dump_mode; + GF_Err e; + u32 i, ll; u32 simulation_time_in_ms = 0; u32 initial_service_id = 0; Bool auto_exit = GF_FALSE; - Bool logs_set = GF_FALSE; Bool start_fs = GF_FALSE; Bool use_rtix = GF_FALSE; Bool pause_at_first = GF_FALSE; Bool no_cfg_save = GF_FALSE; - Bool is_cfg_only = GF_FALSE; - + Bool print_stats = GF_FALSE; + Bool print_graph = GF_FALSE; + Bool no_keyboard = GF_FALSE; Double play_from = 0; #ifdef GPAC_MEMORY_TRACKING GF_MemTrackerType mem_track = GF_MemTrackerNone; #endif - Double fps = GF_IMPORT_DEFAULT_FPS; - Bool fill_ar, visible, do_uncache; - char *url_arg, *out_arg, *the_cfg, *rti_file, *views, *default_com; + Bool has_command; + char *url_arg, *gpac_profile, *rti_file; FILE *logfile = NULL; - Float scale = 1; #ifndef WIN32 dlopen(NULL, RTLD_NOW|RTLD_GLOBAL); #endif + helpout = stdout; + /*by default use current dir*/ strcpy(the_url, "."); memset(&user, 0, sizeof(GF_User)); - dump_mode = DUMP_NONE; - fill_ar = visible = do_uncache = GF_FALSE; - url_arg = out_arg = the_cfg = rti_file = views = default_com = NULL; - nb_times = 0; - times[0] = 0; + has_command = GF_FALSE; + url_arg = gpac_profile = rti_file = NULL; - /*first locate config file if specified*/ + /*first identify profile and mem tracking */ for (i=1; i<(u32) argc; i++) { char *arg = argv[i]; - if (!strcmp(arg, "-c") || !strcmp(arg, "-cfg")) { - the_cfg = argv[i+1]; + if (!strcmp(arg, "-p")) { + gpac_profile = argv[i+1]; i++; - } - else if (!strcmp(arg, "-mem-track") || !strcmp(arg, "-mem-track-stack")) { + } else if (!strncmp(arg, "-p=", 3)) { + gpac_profile = argv[i]+3; + } else if (!strcmp(arg, "-mem-track") || !strcmp(arg, "-mem-track-stack")) { #ifdef GPAC_MEMORY_TRACKING mem_track = !strcmp(arg, "-mem-track-stack") ? GF_MemTrackerBackTrace : GF_MemTrackerSimple; #else @@ -1238,162 +1183,139 @@ int mp4client_main(int argc, char **argv) } else if (!strcmp(arg, "-guid")) { gui_mode = 2; } else if (!strcmp(arg, "-h") || !strcmp(arg, "-help")) { - PrintUsage(); + PrintUsage(GF_FALSE); + return 0; + } else if (!strcmp(arg, "-hx")) { + PrintUsage(GF_TRUE); + return 0; + } else if (!strcmp(arg, "-hr")) { + PrintHelp(); + return 0; + } else if (!strcmp(arg, "-hc")) { + fprintf(helpout, "libgpac options:\n"); + gf_sys_print_core_help(helpout, help_flags, GF_ARGMODE_ALL, 0); return 0; } } #ifdef GPAC_MEMORY_TRACKING - gf_sys_init(mem_track); + gf_sys_init(mem_track, gpac_profile); #else - gf_sys_init(GF_MemTrackerNone); + gf_sys_init(GF_MemTrackerNone, gpac_profile); #endif - gf_sys_set_args(argc, (const char **) argv); - cfg_file = gf_cfg_init(the_cfg, NULL); - if (!cfg_file) { - fprintf(stderr, "Error: Configuration File not found\n"); - return 1; - } - /*if logs are specified, use them*/ - if (gf_log_set_tools_levels( gf_cfg_get_key(cfg_file, "General", "Logs") ) != GF_OK) { - return 1; - } + gf_log_set_tool_level(GF_LOG_ALL, GF_LOG_WARNING); + //we by default want 2 additional threads: + //main thread might get locked on vsync + //second thread might be busy decoding audio/video + //third thread will then be able to refill all buffers/perform networks tasks + gf_opts_set_key("temp", "threads", "2"); - if( gf_cfg_get_key(cfg_file, "General", "Logs") != NULL ) { - logs_set = GF_TRUE; + e = gf_sys_set_args(argc, (const char **) argv); + if (e) { + fprintf(stderr, "Error assigning libgpac arguments: %s\n", gf_error_to_string(e) ); + gf_sys_close(); + return 1; } if (!gui_mode) { - str = gf_cfg_get_key(cfg_file, "General", "ForceGUI"); + str = gf_opts_get_key("General", "ForceGUI"); if (str && !strcmp(str, "yes")) gui_mode = 1; } for (i=1; i<(u32) argc; i++) { char *arg = argv[i]; - if (!strcmp(arg, "-rti")) { + if (!strcmp(arg, "-genmd")) { + help_flags = GF_PRINTARG_MD; + helpout = gf_fopen("mp4client.md", "w"); + + fprintf(helpout, "[**HOME**](Home) » MP4Client \n"); + fprintf(helpout, "\n"); + PrintUsage(GF_TRUE); + PrintHelp(); + gf_fclose(helpout); + gf_sys_close(); + return 0; + } else if (!strcmp(arg, "-genman")) { + help_flags = GF_PRINTARG_MAN; + helpout = gf_fopen("mp4client.1", "w"); + + + fprintf(helpout, ".TH MP4Client 1 2019 MP4Client GPAC\n"); + fprintf(helpout, ".\n.SH NAME\n.LP\nMP4Client \\- GPAC command-line media player\n.SH SYNOPSIS\n.LP\n.B MP4Client\n.RI [options] \\ [file]\n.br\n.\n"); + + PrintUsage(GF_TRUE); + PrintHelp(); + + fprintf(helpout, ".SH EXAMPLES\n.TP\nBasic and advanced examples are available at https://wiki.gpac.io/mp4client\n"); + fprintf(helpout, ".SH MORE\n.LP\nAuthors: GPAC developers, see git repo history (-log)\n" + ".br\nFor bug reports, feature requests, more information and source code, visit http://github.com/gpac/gpac\n" + ".br\nbuild: %s\n" + ".br\nCopyright: %s\n.br\n" + ".SH SEE ALSO\n" + ".LP\ngpac(1), MP4Box(1)\n", gf_gpac_version(), gf_gpac_copyright()); + + gf_fclose(helpout); + gf_sys_close(); + return 0; + } else if (!strcmp(arg, "-rti")) { rti_file = argv[i+1]; i++; } else if (!strcmp(arg, "-rtix")) { rti_file = argv[i+1]; i++; use_rtix = GF_TRUE; + } else if (!strcmp(arg, "-rti-refresh")) { + rti_update_time_ms = atoi(argv[i+1]); + i++; } else if (!stricmp(arg, "-size")) { /*usage of %ud breaks sscanf on MSVC*/ if (sscanf(argv[i+1], "%dx%d", &forced_width, &forced_height) != 2) { forced_width = forced_height = 0; } i++; - } else if (!strcmp(arg, "-quiet")) { - be_quiet = 1; - } else if (!strcmp(arg, "-strict-error")) { - gf_log_set_strict_error(1); - } else if (!strcmp(arg, "-log-file") || !strcmp(arg, "-lf")) { - logfile = gf_fopen(argv[i+1], "wt"); - gf_log_set_callback(logfile, on_gpac_log); - i++; - } else if (!strcmp(arg, "-logs") ) { - if (gf_log_set_tools_levels(argv[i+1]) != GF_OK) { - return 1; - } - logs_set = GF_TRUE; - i++; - } else if (!strcmp(arg, "-log-clock") || !strcmp(arg, "-lc")) { - log_time_start = 1; - } else if (!strcmp(arg, "-log-utc") || !strcmp(arg, "-lu")) { - log_utc_time = 1; } -#if defined(__DARWIN__) || defined(__APPLE__) - else if (!strcmp(arg, "-thread")) threading_flags = 0; -#else - else if (!strcmp(arg, "-no-thread")) threading_flags = GF_TERM_NO_DECODER_THREAD | GF_TERM_NO_COMPOSITOR_THREAD | GF_TERM_WINDOW_NO_THREAD; -#endif - else if (!strcmp(arg, "-no-compositor-thread")) threading_flags |= GF_TERM_NO_COMPOSITOR_THREAD; - else if (!strcmp(arg, "-no-audio")) no_audio = 1; - else if (!strcmp(arg, "-no-regulation")) no_regulation = 1; - else if (!strcmp(arg, "-fs")) start_fs = 1; - - else if (!strcmp(arg, "-opt")) { - set_cfg_option(argv[i+1]); - i++; - } else if (!strcmp(arg, "-conf")) { - set_cfg_option(argv[i+1]); - is_cfg_only=GF_TRUE; - i++; - } - else if (!strcmp(arg, "-ifce")) { - gf_cfg_set_key(cfg_file, "Network", "DefaultMCastInterface", argv[i+1]); + //libgpac opts using an argument + else if (!strcmp(arg, "-log-file") || !strcmp(arg, "-lf") || !strcmp(arg, "-logs") || !strcmp(arg, "-cfg") || !strcmp(arg, "-ifce") ) { i++; } - else if (!stricmp(arg, "-help")) { - PrintUsage(); - return 1; + + else if (!strcmp(arg, "-no-thread")) { + gf_opts_set_key("temp", "threads", "0"); } - else if (!stricmp(arg, "-noprog")) { - no_prog=1; - gf_set_progress_callback(NULL, progress_quiet); + else if (!strcmp(arg, "-no-audio")) { + no_audio = GF_TRUE; } + else if (!strcmp(arg, "-fs")) start_fs = 1; else if (!stricmp(arg, "-no-save") || !stricmp(arg, "--no-save") /*old versions used --n-save ...*/) { no_cfg_save=1; } - else if (!stricmp(arg, "-ntp-shift")) { - s32 shift = atoi(argv[i+1]); - i++; - gf_net_set_ntp_shift(shift); - } else if (!stricmp(arg, "-run-for")) { - simulation_time_in_ms = atoi(argv[i+1]) * 1000; + simulation_time_in_ms = (u32) (atof(argv[i+1]) * 1000); if (!simulation_time_in_ms) simulation_time_in_ms = 1; /*1ms*/ i++; - } - - else if (!strcmp(arg, "-out")) { - out_arg = argv[i+1]; - i++; - } - else if (!stricmp(arg, "-fps")) { - fps = atof(argv[i+1]); - i++; - } else if (!strcmp(arg, "-avi") || !strcmp(arg, "-sha")) { - dump_mode &= 0xFFFF0000; - - if (!strcmp(arg, "-sha")) dump_mode |= DUMP_SHA1; - else dump_mode |= DUMP_AVI; - - if ((url_arg || (i+2<(u32)argc)) && get_time_list(argv[i+1], times, &nb_times)) { - if (!strcmp(arg, "-avi") && (nb_times!=2) ) { - fprintf(stderr, "Only one time arg found for -avi - check usage\n"); - return 1; - } - i++; - } - } else if (!strcmp(arg, "-rgbds")) { /*get dump in rgbds pixel format*/ - dump_mode |= DUMP_RGB_DEPTH_SHAPE; - } else if (!strcmp(arg, "-rgbd")) { /*get dump in rgbd pixel format*/ - dump_mode |= DUMP_RGB_DEPTH; - } else if (!strcmp(arg, "-depth")) { - dump_mode |= DUMP_DEPTH_ONLY; - } else if (!strcmp(arg, "-bmp")) { - dump_mode &= 0xFFFF0000; - dump_mode |= DUMP_BMP; - if ((url_arg || (i+2<(u32)argc)) && get_time_list(argv[i+1], times, &nb_times)) i++; - } else if (!strcmp(arg, "-png")) { - dump_mode &= 0xFFFF0000; - dump_mode |= DUMP_PNG; - if ((url_arg || (i+2<(u32)argc)) && get_time_list(argv[i+1], times, &nb_times)) i++; - } else if (!strcmp(arg, "-raw")) { - dump_mode &= 0xFFFF0000; - dump_mode |= DUMP_RAW; - if ((url_arg || (i+2<(u32)argc)) && get_time_list(argv[i+1], times, &nb_times)) i++; } else if (!stricmp(arg, "-scale")) { sscanf(argv[i+1], "%f", &scale); i++; } - + /* already parsed */ + else if (!strcmp(arg, "-nk")) { + no_keyboard = GF_TRUE; + } + /* already parsed */ + else if (!strcmp(arg, "-p")) { + i++; + } + /* already parsed */ + else if (!strcmp(arg, "-mem-track") || !strcmp(arg, "-mem-track-stack") || !strcmp(arg, "-gui") || !strcmp(arg, "-guid") + || !strncmp(arg, "-p=", 3) + ) { + } + /*arguments only used in non-gui mode*/ - if (!gui_mode) { + else if (!gui_mode) { if (arg[0] != '-') { if (url_arg) { fprintf(stderr, "Several input URLs provided (\"%s\", \"%s\"). Check your command-line.\n", url_arg, arg); @@ -1426,57 +1348,40 @@ int mp4client_main(int argc, char **argv) if (argv[i+1][1]=='m') align_mode |= 1; else if (argv[i+1][1]=='r') align_mode |= 2; i++; - } else if (!strcmp(arg, "-fill")) { - fill_ar = GF_TRUE; - } else if (!strcmp(arg, "-show")) { - visible = 1; - } else if (!strcmp(arg, "-uncache")) { - do_uncache = GF_TRUE; } else if (!strcmp(arg, "-exit")) auto_exit = GF_TRUE; - else if (!stricmp(arg, "-views")) { - views = argv[i+1]; - i++; - } else if (!stricmp(arg, "-com")) { - default_com = argv[i+1]; + has_command = GF_TRUE; i++; } else if (!stricmp(arg, "-service")) { initial_service_id = atoi(argv[i+1]); i++; + } else if (!stricmp(arg, "-stats")) { + print_stats=GF_TRUE; + } else if (!stricmp(arg, "-graph")) { + print_graph=GF_TRUE; + } else if (!stricmp(arg, "-cov")) { + do_coverage = GF_TRUE; + print_stats = GF_TRUE; + print_graph = GF_TRUE; + } else { + u32 res = gf_sys_is_gpac_arg(arg); + if (!res) { + fprintf(stderr, "Unrecognized option %s\n", arg); + } else if (res==2) { + i++; + } } + } else if (gf_sys_is_gpac_arg(arg)==2) { + i++; } - } - if (is_cfg_only) { - gf_cfg_del(cfg_file); - fprintf(stderr, "GPAC Config updated\n"); - return 0; - } - if (do_uncache) { - const char *cache_dir = gf_cfg_get_key(cfg_file, "General", "CacheDirectory"); - do_flatten_cache(cache_dir); - fprintf(stderr, "GPAC Cache dir %s flattened\n", cache_dir); - gf_cfg_del(cfg_file); - return 0; - } + } - if (dump_mode && !url_arg ) { - FILE *test; - url_arg = (char *)gf_cfg_get_key(cfg_file, "General", "StartupFile"); - test = url_arg ? gf_fopen(url_arg, "rt") : NULL; - if (!test) url_arg = NULL; - else gf_fclose(test); - - if (!url_arg) { - fprintf(stderr, "Missing argument for dump\n"); - PrintUsage(); - if (logfile) gf_fclose(logfile); - return 1; - } - } + //will run switch helpout to stderr + helpout = stderr; - if (!gui_mode && !url_arg && (gf_cfg_get_key(cfg_file, "General", "StartupFile") != NULL)) { + if (!gui_mode && !url_arg && (gf_opts_get_key("General", "StartupFile") != NULL)) { gui_mode=1; } @@ -1486,7 +1391,7 @@ int mp4client_main(int argc, char **argv) TCHAR buffer[1024]; DWORD res = GetCurrentDirectory(1024, buffer); buffer[res] = 0; - opt = gf_cfg_get_key(cfg_file, "General", "ModulesDirectory"); + opt = gf_opts_get_key("core", "mod-dirs"); if (strstr(opt, buffer)) { gui_mode=1; } else { @@ -1499,27 +1404,14 @@ int mp4client_main(int argc, char **argv) hide_shell(1); } if (gui_mode) { - no_prog=1; - gf_set_progress_callback(NULL, progress_quiet); + gf_sys_set_cfg_option("core:noprog=yes"); } - if (!url_arg && simulation_time_in_ms) - simulation_time_in_ms += gf_sys_clock(); - #if defined(__DARWIN__) || defined(__APPLE__) carbon_init(); #endif - if (dump_mode) rti_file = NULL; - - if (!logs_set) { - gf_log_set_tool_level(GF_LOG_ALL, GF_LOG_WARNING); - } - //only override default log callback when needed - if (rti_file || logfile || log_utc_time || log_time_start) - gf_log_set_callback(NULL, on_gpac_log); - if (rti_file) init_rti_logs(rti_file, url_arg, use_rtix); { @@ -1529,74 +1421,37 @@ int mp4client_main(int argc, char **argv) } - /*setup dumping options*/ - if (dump_mode) { - user.init_flags |= GF_TERM_NO_DECODER_THREAD | GF_TERM_NO_COMPOSITOR_THREAD | GF_TERM_NO_REGULATION; - if (!visible) - user.init_flags |= GF_TERM_INIT_HIDE; - - gf_cfg_set_key(cfg_file, "Audio", "DriverName", "Raw Audio Output"); - no_cfg_save=GF_TRUE; - } else { - init_w = forced_width; - init_h = forced_height; - } - - user.modules = gf_modules_new(NULL, cfg_file); - if (user.modules) i = gf_modules_get_count(user.modules); - if (!i || !user.modules) { - fprintf(stderr, "Error: no modules found - exiting\n"); - if (user.modules) gf_modules_del(user.modules); - gf_cfg_del(cfg_file); - gf_sys_close(); - if (logfile) gf_fclose(logfile); - return 1; - } - fprintf(stderr, "Modules Found : %d \n", i); + init_w = forced_width; + init_h = forced_height; - user.config = cfg_file; user.EventProc = GPAC_EventProc; /*dummy in this case (global vars) but MUST be non-NULL*/ - user.opaque = user.modules; - if (threading_flags) user.init_flags |= threading_flags; - if (no_audio) user.init_flags |= GF_TERM_NO_AUDIO; - if (no_regulation) user.init_flags |= GF_TERM_NO_REGULATION; - - if (threading_flags & (GF_TERM_NO_DECODER_THREAD|GF_TERM_NO_COMPOSITOR_THREAD) ) term_step = GF_TRUE; + user.opaque = &user; - //in dump mode we don't want to rely on system clock but on the number of samples being consumed - if (dump_mode) user.init_flags |= GF_TERM_USE_AUDIO_HW_CLOCK; + if (no_audio) user.init_flags |= GF_TERM_NO_AUDIO; if (bench_mode) { - gf_cfg_discard_changes(user.config); + gf_opts_discard_changes(); auto_exit = GF_TRUE; - gf_cfg_set_key(user.config, "Audio", "DriverName", "Raw Audio Output"); - if (bench_mode!=2) { - gf_cfg_set_key(user.config, "Video", "DriverName", "Raw Video Output"); - gf_cfg_set_key(user.config, "RAWVideo", "RawOutput", "null"); - gf_cfg_set_key(user.config, "Compositor", "OpenGLMode", "disable"); - } else { - gf_cfg_set_key(user.config, "Video", "DisableVSync", "yes"); - } + if (bench_mode!=2) user.init_flags |= GF_TERM_NO_VIDEO; } - { + if (forced_width && forced_height) { char dim[50]; sprintf(dim, "%d", forced_width); - gf_cfg_set_key(user.config, "Compositor", "DefaultWidth", forced_width ? dim : NULL); + gf_opts_set_key("Temp", "DefaultWidth", dim); sprintf(dim, "%d", forced_height); - gf_cfg_set_key(user.config, "Compositor", "DefaultHeight", forced_height ? dim : NULL); + gf_opts_set_key("Temp", "DefaultHeight", dim); } fprintf(stderr, "Loading GPAC Terminal\n"); i = gf_sys_clock(); + term = gf_term_new(&user); if (!term) { fprintf(stderr, "\nInit error - check you have at least one video out and one rasterizer...\nFound modules:\n"); - list_modules(user.modules); - gf_modules_del(user.modules); - gf_cfg_discard_changes(cfg_file); - gf_cfg_del(cfg_file); + list_modules(); + gf_opts_discard_changes(); gf_sys_close(); if (logfile) gf_fclose(logfile); return 1; @@ -1609,68 +1464,54 @@ int mp4client_main(int argc, char **argv) if (bench_mode==1) bench_mode=2; } - if (dump_mode) { -// gf_term_set_option(term, GF_OPT_VISIBLE, 0); - if (fill_ar) gf_term_set_option(term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_FILL_SCREEN); - } else { - /*check video output*/ - str = gf_cfg_get_key(cfg_file, "Video", "DriverName"); - if (!bench_mode && !strcmp(str, "Raw Video Output")) fprintf(stderr, "WARNING: using raw output video (memory only) - no display used\n"); - /*check audio output*/ - str = gf_cfg_get_key(cfg_file, "Audio", "DriverName"); - if (!str || !strcmp(str, "No Audio Output Available")) fprintf(stderr, "WARNING: no audio output available - make sure no other program is locking the sound card\n"); + str = gf_opts_get_key("General", "NoMIMETypeFetch"); + no_mime_check = (str && !stricmp(str, "yes")) ? 1 : 0; - str = gf_cfg_get_key(cfg_file, "General", "NoMIMETypeFetch"); - no_mime_check = (str && !stricmp(str, "yes")) ? 1 : 0; - } - - str = gf_cfg_get_key(cfg_file, "HTTPProxy", "Enabled"); - if (str && !strcmp(str, "yes")) { - str = gf_cfg_get_key(cfg_file, "HTTPProxy", "Name"); + if (gf_opts_get_bool("core", "proxy-on")) { + str = gf_opts_get_key("core", "proxy-name"); if (str) fprintf(stderr, "HTTP Proxy %s enabled\n", str); } if (rti_file) { - str = gf_cfg_get_key(cfg_file, "General", "RTIRefreshPeriod"); - if (str) { - rti_update_time_ms = atoi(str); - } else { - gf_cfg_set_key(cfg_file, "General", "RTIRefreshPeriod", "200"); - } UpdateRTInfo("At GPAC load time\n"); } Run = 1; - if (dump_mode) { - if (!nb_times) { - times[0] = 0; - nb_times++; - } - ret_val = dump_file(url_arg, out_arg, dump_mode, fps, forced_width, forced_height, scale, times, nb_times); - Run = 0; - } - else if (views) { + if (url_arg && !strcmp(url_arg, "NOGUI")) { + url_arg = NULL; } /*connect if requested*/ else if (!gui_mode && url_arg) { char *ext; - strcpy(the_url, url_arg); + if (strlen(url_arg) >= sizeof(the_url)) { + fprintf(stderr, "Input url %s is too long, truncating to %d chars.\n", url_arg, (int)(sizeof(the_url) - 1)); + strncpy(the_url, url_arg, sizeof(the_url)-1); + the_url[sizeof(the_url) - 1] = 0; + } + else { + strcpy(the_url, url_arg); + } ext = strrchr(the_url, '.'); if (ext && (!stricmp(ext, ".m3u") || !stricmp(ext, ".pls"))) { - GF_Err e = GF_OK; + e = GF_OK; fprintf(stderr, "Opening Playlist %s\n", the_url); strcpy(pl_path, the_url); /*this is not clean, we need to have a plugin handle playlist for ourselves*/ if (!strncmp("http:", the_url, 5)) { +#ifdef FILTER_FIXME GF_DownloadSession *sess = gf_dm_sess_new(term->downloader, the_url, GF_NETIO_SESSION_NOT_THREADED, NULL, NULL, &e); if (sess) { e = gf_dm_sess_process(sess); - if (!e) strcpy(the_url, gf_dm_sess_get_cache_name(sess)); + if (!e) { + strncpy(the_url, gf_dm_sess_get_cache_name(sess), sizeof(the_url) - 1); + the_url[sizeof(the_url) - 1] = 0; + } gf_dm_sess_del(sess); } +#endif } playlist = e ? NULL : gf_fopen(the_url, "rt"); @@ -1689,9 +1530,10 @@ int mp4client_main(int argc, char **argv) } } else { fprintf(stderr, "Hit 'h' for help\n\n"); - str = gf_cfg_get_key(cfg_file, "General", "StartupFile"); + str = gf_opts_get_key("General", "StartupFile"); if (str) { - strcpy(the_url, "MP4Client "GPAC_FULL_VERSION); + snprintf(the_url, sizeof(the_url)-1, "MP4Client %s", gf_gpac_version() ); + the_url[sizeof(the_url) - 1] = 0; gf_term_connect(term, str); startup_file = 1; is_connected = 1; @@ -1701,25 +1543,23 @@ int mp4client_main(int argc, char **argv) if (start_fs) gf_term_set_option(term, GF_OPT_FULLSCREEN, 1); - if (views) { - char szTemp[4046]; - sprintf(szTemp, "views://%s", views); - gf_term_connect(term, szTemp); - } if (bench_mode) { rti_update_time_ms = 500; bench_mode_start = gf_sys_clock(); } + if (simulation_time_in_ms) + simulation_time_in_ms += gf_sys_clock(); + while (Run) { /*we don't want getchar to block*/ - if ((gui_mode==1) || !gf_prompt_has_input()) { + if ((gui_mode==1) || (no_keyboard || !gf_prompt_has_input()) ) { if (reload) { reload = 0; gf_term_disconnect(term); - gf_term_connect(term, startup_file ? gf_cfg_get_key(cfg_file, "General", "StartupFile") : the_url); + gf_term_connect(term, startup_file ? gf_opts_get_key("General", "StartupFile") : the_url); } if (restart && gf_term_get_option(term, GF_OPT_IS_OVER)) { restart = 0; @@ -1731,9 +1571,14 @@ int mp4client_main(int argc, char **argv) goto force_input; } - if (default_com && is_connected) { - gf_term_scene_update(term, NULL, default_com); - default_com = NULL; + if (has_command && is_connected) { + has_command = GF_FALSE; + for (i=0; i<(u32)argc; i++) { + if (!strcmp(argv[i], "-com")) { + gf_term_scene_update(term, NULL, argv[i+1]); + i++; + } + } } if (initial_service_id && is_connected) { GF_ObjectManager *root_od = gf_term_get_root_object(term); @@ -1744,11 +1589,10 @@ int mp4client_main(int argc, char **argv) } if (!use_rtix || display_rti) UpdateRTInfo(NULL); - if (term_step) { - gf_term_process_step(term); - } else { - gf_sleep(rti_update_time_ms); - } + + + gf_term_process_step(term); + if (auto_exit && eos_seen && gf_term_get_option(term, GF_OPT_IS_OVER)) { Run = GF_FALSE; } @@ -1761,11 +1605,15 @@ int mp4client_main(int argc, char **argv) } continue; } + c = gf_prompt_get_char(); force_input: - switch (c) { - case 'q': + if (c=='\n') cmdtype=MP4C_PL_NEXT; + else cmdtype = get_cmd(c); + + switch (cmdtype) { + case MP4C_QUIT: { GF_Event evt; memset(&evt, 0, sizeof(GF_Event)); @@ -1774,32 +1622,31 @@ force_input: } // Run = 0; break; - case 'X': + case MP4C_KILL: exit(0); break; - case 'Q': - break; - case 'o': + + case MP4C_OPEN: startup_file = 0; gf_term_disconnect(term); fprintf(stderr, "Enter the absolute URL\n"); - if (1 > scanf("%s", the_url)) { + if (1 > scanf("%1023s", the_url)) { fprintf(stderr, "Cannot read absolute URL, aborting\n"); break; } if (rti_file) init_rti_logs(rti_file, the_url, use_rtix); gf_term_connect(term, the_url); break; - case 'O': + case MP4C_OPEN_PL: gf_term_disconnect(term); fprintf(stderr, "Enter the absolute URL to the playlist\n"); - if (1 > scanf("%s", the_url)) { + if (1 > scanf("%1023s", the_url)) { fprintf(stderr, "Cannot read the absolute URL, aborting.\n"); break; } playlist = gf_fopen(the_url, "rt"); if (playlist) { - if (1 > fscanf(playlist, "%s", the_url)) { + if (1 > fscanf(playlist, "%1023s", the_url)) { fprintf(stderr, "Cannot read any URL from playlist, aborting.\n"); gf_fclose( playlist); break; @@ -1808,16 +1655,15 @@ force_input: gf_term_connect(term, the_url); } break; - case '\n': - case 'N': + case MP4C_PL_NEXT: if (playlist) { int res; gf_term_disconnect(term); - res = fscanf(playlist, "%s", the_url); + res = fscanf(playlist, "%1023s", the_url); if ((res == EOF) && loop_at_end) { - fseek(playlist, 0, SEEK_SET); - res = fscanf(playlist, "%s", the_url); + gf_fseek(playlist, 0, SEEK_SET); + res = fscanf(playlist, "%1023s", the_url); } if (res == EOF) { fprintf(stderr, "No more items - exiting\n"); @@ -1830,7 +1676,7 @@ force_input: } } break; - case 'P': + case MP4C_PL_JUMP: if (playlist) { u32 count; gf_term_disconnect(term); @@ -1839,7 +1685,7 @@ force_input: break; } while (count) { - if (fscanf(playlist, "%s", the_url)) { + if (fscanf(playlist, "%1023s", the_url)) { fprintf(stderr, "Failed to read line, aborting\n"); break; } @@ -1849,23 +1695,23 @@ force_input: gf_term_connect(term, the_url); } break; - case 'r': + case MP4C_RELOAD: if (is_connected) reload = 1; break; - case 'D': + case MP4C_DISCONNECT: if (is_connected) gf_term_disconnect(term); break; - case 'p': + case MP4C_PAUSE_RESUME: if (is_connected) { Bool is_pause = gf_term_get_option(term, GF_OPT_PLAY_STATE); fprintf(stderr, "[Status: %s]\n", is_pause ? "Playing" : "Paused"); gf_term_set_option(term, GF_OPT_PLAY_STATE, is_pause ? GF_STATE_PLAYING : GF_STATE_PAUSED); } break; - case 's': + case MP4C_STEP: if (is_connected) { gf_term_set_option(term, GF_OPT_PLAY_STATE, GF_STATE_STEP_PAUSE); fprintf(stderr, "Step time: "); @@ -1874,8 +1720,8 @@ force_input: } break; - case 'z': - case 'T': + case MP4C_SEEK: + case MP4C_SEEK_TIME: if (!CanSeek || (Duration<=2000)) { fprintf(stderr, "scene not seekable\n"); } else { @@ -1884,7 +1730,7 @@ force_input: fprintf(stderr, "Duration: "); PrintTime(Duration); res = gf_term_get_time_in_ms(term); - if (c=='z') { + if (cmdtype==MP4C_SEEK) { res *= 100; res /= (s64)Duration; fprintf(stderr, " (current %.2f %%)\nEnter Seek percentage:\n", res); @@ -1920,7 +1766,7 @@ force_input: } break; - case 't': + case MP4C_TIME: { if (is_connected) { fprintf(stderr, "Current Time: "); @@ -1931,13 +1777,13 @@ force_input: } } break; - case 'w': + case MP4C_WORLDINFO: if (is_connected) PrintWorldInfo(term); break; - case 'v': + case MP4C_ODLIST: if (is_connected) PrintODList(term, NULL, 0, 0, "Root"); break; - case 'i': + case MP4C_ODINFO_ID: if (is_connected) { u32 ID; fprintf(stderr, "Enter OD ID (0 for main OD): "); @@ -1946,12 +1792,12 @@ force_input: ViewOD(term, ID, (u32)-1, NULL); } else { char str_url[GF_MAX_PATH]; - if (scanf("%s", str_url) == 1) + if (scanf("%1023s", str_url) == 1) ViewOD(term, 0, (u32)-1, str_url); } } break; - case 'j': + case MP4C_ODINFO_NUM: if (is_connected) { u32 num; do { @@ -1961,31 +1807,26 @@ force_input: ViewOD(term, (u32)-1, num, NULL); } break; - case 'b': + case MP4C_ODTIME: if (is_connected) ViewODs(term, 1); break; - case 'm': + case MP4C_ODBUF: if (is_connected) ViewODs(term, 0); break; - case 'l': - list_modules(user.modules); - break; - - case 'n': + case MP4C_NAVMODE: if (is_connected) set_navigation(); break; - case 'x': + case MP4C_LASTVP: if (is_connected) gf_term_set_option(term, GF_OPT_NAVIGATION_TYPE, 0); break; - case 'd': + case MP4C_DUMPSCENE: if (is_connected) { GF_ObjectManager *odm = NULL; char radname[GF_MAX_PATH], *sExt; - GF_Err e; - u32 i, count, odid; + u32 count, odid; Bool xml_dump, std_out; radname[0] = 0; do { @@ -2000,7 +1841,7 @@ force_input: GF_MediaInfo info; odm = gf_term_get_object(term, root_odm, i); if (gf_term_get_object_info(term, odm, &info) == GF_OK) { - if (info.od->objectDescriptorID==odid) break; + if (info.ODID==odid) break; } odm = NULL; } @@ -2008,7 +1849,7 @@ force_input: do { fprintf(stderr, "Enter file radical name (+\'.x\' for XML dumping) - \"std\" for stderr: "); fflush(stderr); - } while( 1 > scanf("%s", radname)); + } while( 1 > scanf("%1023s", radname)); sExt = strrchr(radname, '.'); xml_dump = 0; if (sExt) { @@ -2021,10 +1862,7 @@ force_input: } break; - case 'c': - PrintGPACConfig(); - break; - case '3': + case MP4C_OGL2D: { Bool use_3d = !gf_term_get_option(term, GF_OPT_USE_OPENGL); if (gf_term_set_option(term, GF_OPT_USE_OPENGL, use_3d)==GF_OK) { @@ -2032,7 +1870,7 @@ force_input: } } break; - case 'k': + case MP4C_STRESSMODE: { Bool opt = gf_term_get_option(term, GF_OPT_STRESS_MODE); opt = !opt; @@ -2040,70 +1878,45 @@ force_input: gf_term_set_option(term, GF_OPT_STRESS_MODE, opt); } break; - case '4': + case MP4C_AR_4_3: gf_term_set_option(term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_4_3); break; - case '5': + case MP4C_AR_16_9: gf_term_set_option(term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_16_9); break; - case '6': + case MP4C_AR_NONE: gf_term_set_option(term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_FILL_SCREEN); break; - case '7': + case MP4C_AR_ORIG: gf_term_set_option(term, GF_OPT_ASPECT_RATIO, GF_ASPECT_RATIO_KEEP); break; - case 'C': - switch (gf_term_get_option(term, GF_OPT_MEDIA_CACHE)) { - case GF_MEDIA_CACHE_DISABLED: - gf_term_set_option(term, GF_OPT_MEDIA_CACHE, GF_MEDIA_CACHE_ENABLED); - break; - case GF_MEDIA_CACHE_ENABLED: - gf_term_set_option(term, GF_OPT_MEDIA_CACHE, GF_MEDIA_CACHE_DISABLED); - break; - case GF_MEDIA_CACHE_RUNNING: - fprintf(stderr, "Streaming Cache is running - please stop it first\n"); - continue; - } - switch (gf_term_get_option(term, GF_OPT_MEDIA_CACHE)) { - case GF_MEDIA_CACHE_ENABLED: - fprintf(stderr, "Streaming Cache Enabled\n"); - break; - case GF_MEDIA_CACHE_DISABLED: - fprintf(stderr, "Streaming Cache Disabled\n"); - break; - case GF_MEDIA_CACHE_RUNNING: - fprintf(stderr, "Streaming Cache Running\n"); - break; - } - break; - case 'S': - case 'A': - if (gf_term_get_option(term, GF_OPT_MEDIA_CACHE)==GF_MEDIA_CACHE_RUNNING) { - gf_term_set_option(term, GF_OPT_MEDIA_CACHE, (c=='S') ? GF_MEDIA_CACHE_DISABLED : GF_MEDIA_CACHE_DISCARD); - fprintf(stderr, "Streaming Cache stopped\n"); - } else { - fprintf(stderr, "Streaming Cache not running\n"); - } - break; - case 'R': + case MP4C_DISP_RTI: display_rti = !display_rti; ResetCaption(); break; - case 'F': + case MP4C_DISP_FPS: if (display_rti) display_rti = 0; else display_rti = 2; ResetCaption(); break; - - case 'u': + case MP4C_DISP_STATS: + case MP4C_DISP_GRAPH: + ll = gf_log_get_tool_level(GF_LOG_APP); + gf_log_set_tool_level(GF_LOG_APP, GF_LOG_INFO); + if (cmdtype==MP4C_DISP_STATS) + gf_term_print_stats(term); + else + gf_term_print_graph(term); + gf_log_set_tool_level(GF_LOG_APP, ll); + break; + case MP4C_UPDATE: { - GF_Err e; char szCom[8192]; fprintf(stderr, "Enter command to send:\n"); - fflush(stdin); +// fflush(stdin); szCom[0] = 0; - if (1 > scanf("%[^\t\n]", szCom)) { + if (1 > scanf("%8191[^\t\n]", szCom)) { fprintf(stderr, "Cannot read command to send, aborting.\n"); break; } @@ -2111,14 +1924,13 @@ force_input: if (e) fprintf(stderr, "Processing command failed: %s\n", gf_error_to_string(e)); } break; - case 'e': + case MP4C_EVALJS: { - GF_Err e; char jsCode[8192]; fprintf(stderr, "Enter JavaScript code to evaluate:\n"); - fflush(stdin); +// fflush(stdin); jsCode[0] = 0; - if (1 > scanf("%[^\t\n]", jsCode)) { + if (1 > scanf("%8191[^\t\n]", jsCode)) { fprintf(stderr, "Cannot read code to evaluate, aborting.\n"); break; } @@ -2127,13 +1939,13 @@ force_input: } break; - case 'L': + case MP4C_LOGS: { char szLog[1024], *cur_logs; cur_logs = gf_log_get_tools_levels(); fprintf(stderr, "Enter new log level (current tools %s):\n", cur_logs); gf_free(cur_logs); - if (scanf("%s", szLog) < 1) { + if (scanf("%1023s", szLog) < 1) { fprintf(stderr, "Cannot read new log level, aborting.\n"); break; } @@ -2141,14 +1953,7 @@ force_input: } break; - case 'g': - { - GF_SystemRTInfo rti; - gf_sys_get_rti(rti_update_time_ms, &rti, 0); - fprintf(stderr, "GPAC allocated memory "LLD"\n", rti.gpac_memory); - } - break; - case 'M': + case MP4C_VMEM_CACHE: { u32 size; do { @@ -2158,7 +1963,7 @@ force_input: } break; - case 'H': + case MP4C_DOWNRATE: { u32 http_bitrate = gf_term_get_option(term, GF_OPT_HTTP_MAX_RATE); do { @@ -2169,102 +1974,24 @@ force_input: } break; - case 'E': + case MP4C_RELOAD_OPTS: gf_term_set_option(term, GF_OPT_RELOAD_CONFIG, 1); break; - case 'B': - switch_bench(!bench_mode); - break; - - case 'Y': - { - char szOpt[8192]; - fprintf(stderr, "Enter option to set (Section:Name=Value):\n"); - fflush(stdin); - szOpt[0] = 0; - if (1 > scanf("%[^\t\n]", szOpt)) { - fprintf(stderr, "Cannot read option\n"); - break; - } - set_cfg_option(szOpt); - } - break; - /*extract to PNG*/ - case 'Z': - { - char szFileName[100]; - u32 nb_pass, nb_views, offscreen_view = 0; - GF_VideoSurface fb; - GF_Err e; - nb_pass = 1; - nb_views = gf_term_get_option(term, GF_OPT_NUM_STEREO_VIEWS); - if (nb_views>1) { - fprintf(stderr, "Auto-stereo mode detected - type number of view to dump (0 is main output, 1 to %d offscreen view, %d for all offscreen, %d for all offscreen and main)\n", nb_views, nb_views+1, nb_views+2); - if (scanf("%d", &offscreen_view) != 1) { - offscreen_view = 0; - } - if (offscreen_view==nb_views+1) { - offscreen_view = 1; - nb_pass = nb_views; - } - else if (offscreen_view==nb_views+2) { - offscreen_view = 0; - nb_pass = nb_views+1; - } - } - while (nb_pass) { - nb_pass--; - if (offscreen_view) { - sprintf(szFileName, "view%d_dump.png", offscreen_view); - e = gf_term_get_offscreen_buffer(term, &fb, offscreen_view-1, 0); - } else { - sprintf(szFileName, "gpac_video_dump_"LLU".png", gf_net_get_utc() ); - e = gf_term_get_screen_buffer(term, &fb); - } - offscreen_view++; - if (e) { - fprintf(stderr, "Error dumping screen buffer %s\n", gf_error_to_string(e) ); - nb_pass = 0; - } else { -#ifndef GPAC_DISABLE_AV_PARSERS - u32 dst_size = fb.width*fb.height*4; - char *dst = (char*)gf_malloc(sizeof(char)*dst_size); - - e = gf_img_png_enc(fb.video_buffer, fb.width, fb.height, fb.pitch_y, fb.pixel_format, dst, &dst_size); - if (e) { - fprintf(stderr, "Error encoding PNG %s\n", gf_error_to_string(e) ); - nb_pass = 0; - } else { - FILE *png = gf_fopen(szFileName, "wb"); - if (!png) { - fprintf(stderr, "Error writing file %s\n", szFileName); - nb_pass = 0; - } else { - gf_fwrite(dst, dst_size, 1, png); - gf_fclose(png); - fprintf(stderr, "Dump to %s\n", szFileName); - } - } - if (dst) gf_free(dst); - gf_term_release_screen_buffer(term, &fb); -#endif //GPAC_DISABLE_AV_PARSERS - } - } - fprintf(stderr, "Done: %s\n", szFileName); - } - break; + case MP4C_SCREENSHOT: + MakeScreenshot(GF_FALSE); + break; - case 'G': + case MP4C_SELECT: { GF_ObjectManager *root_od, *odm; u32 index; char szOpt[8192]; fprintf(stderr, "Enter 0-based index of object to select or service ID:\n"); - fflush(stdin); +// fflush(stdin); szOpt[0] = 0; - if (1 > scanf("%[^\t\n]", szOpt)) { + if (1 > scanf("%8191[^\t\n]", szOpt)) { fprintf(stderr, "Cannot read OD ID\n"); break; } @@ -2272,18 +1999,22 @@ force_input: odm = NULL; root_od = gf_term_get_root_object(term); if (root_od) { - odm = gf_term_get_object(term, root_od, index); - if (odm) { - gf_term_select_object(term, odm); - } else { - fprintf(stderr, "Cannot find object at index %d - trying with serviceID\n", index); + if ( gf_term_find_service(term, root_od, index)) { gf_term_select_service(term, root_od, index); + } else { + fprintf(stderr, "Cannot find service %d - trying with object index\n", index); + odm = gf_term_get_object(term, root_od, index); + if (odm) { + gf_term_select_object(term, odm); + } else { + fprintf(stderr, "Cannot find object at index %d\n", index); + } } } } break; - case 'h': + case MP4C_HELP: PrintHelp(); break; default: @@ -2300,28 +2031,142 @@ force_input: gf_log_set_strict_error(0); } + if (print_graph || print_stats) { + ll = gf_log_get_tool_level(GF_LOG_APP); + gf_log_set_tool_level(GF_LOG_APP, GF_LOG_INFO); + if (print_graph) + gf_term_print_graph(term); + if (print_stats) + gf_term_print_stats(term); + gf_log_set_tool_level(GF_LOG_APP, ll); + } + +#ifdef GPAC_ENABLE_COVERAGE + if (do_coverage) { + u32 w, h, k, nb_drawn; + GF_Event evt; + Bool is_bound; + const char *outName; + GF_ObjectManager *root_odm, *odm; + PrintAVInfo(GF_TRUE); + PrintODList(term, NULL, 0, 0, "Root"); + ViewODs(term, GF_TRUE); + ViewODs(term, GF_FALSE); + ViewOD(term, 0, (u32) -1, NULL); + ViewOD(term, 0, 1, NULL); + PrintUsage(0); + PrintHelp(); + PrintWorldInfo(term); + gf_term_dump_scene(term, NULL, NULL, GF_FALSE, 0, NULL); + gf_term_get_current_service_id(term); + gf_term_toggle_addons(term, GF_FALSE); + set_navigation(); + get_cmd('v'); + + gf_term_play_from_time(term, 0, 0); + + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_MOUSEUP; + evt.mouse.x = 20; + evt.mouse.y = 20; + gf_term_send_event(term, &evt); + + gf_term_set_option(term, GF_OPT_PLAY_STATE, GF_STATE_STEP_PAUSE); + //exercise step clocks + gf_term_set_option(term, GF_OPT_PLAY_STATE, GF_STATE_STEP_PAUSE); + root_odm = gf_term_get_root_object(term); + gf_term_find_service(term, root_odm, 0); + gf_term_select_service(term, root_odm, 0); + odm = gf_term_get_object(term, root_odm, 0); + gf_term_select_object(term, odm ); + gf_term_object_subscene_type(term, odm); + gf_term_get_visual_output_size(term, &w, &h); + + gf_term_is_type_supported(term, "video/mp4"); + gf_term_get_url(term); + gf_term_get_simulation_frame_rate(term, &nb_drawn); + + gf_term_get_text_selection(term, GF_TRUE); + gf_term_paste_text(term, "test", GF_TRUE); + + gf_term_set_option(term, GF_OPT_AUDIO_MUTE, 1); + + + MakeScreenshot(GF_TRUE); + + gf_term_scene_update(term, NULL, "REPLACE DYN_TRANS.translation BY 10 10"); + gf_term_add_object(term, NULL, GF_TRUE); + + gf_term_get_viewpoint(term, 1, &outName, &is_bound); + gf_term_set_viewpoint(term, 1, "testvp"); + + for (k=GF_NAVIGATE_WALK; k<=GF_NAVIGATE_VR; k++) { + gf_term_set_option(term, GF_OPT_NAVIGATION, k); + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_MOUSEDOWN; + evt.mouse.x = 100; + evt.mouse.y = 100; + gf_term_user_event(term, &evt); + evt.type = GF_EVENT_MOUSEMOVE; + evt.mouse.x += 10; + gf_term_user_event(term, &evt); + evt.mouse.y += 10; + gf_term_user_event(term, &evt); + + evt.type = GF_EVENT_KEYDOWN; + evt.key.key_code = GF_KEY_CONTROL; + gf_term_user_event(term, &evt); + + evt.type = GF_EVENT_MOUSEMOVE; + evt.mouse.x = 120; + evt.mouse.y = 110; + gf_term_user_event(term, &evt); + + evt.type = GF_EVENT_KEYUP; + evt.key.key_code = GF_KEY_CONTROL; + gf_term_user_event(term, &evt); + + evt.type = GF_EVENT_KEYDOWN; + evt.key.key_code = GF_KEY_J; + gf_term_user_event(term, &evt); + + } + gf_term_set_option(term, GF_OPT_NAVIGATION_TYPE, 0); + + gf_term_connect_with_path(term, "logo.jpg", "./media/auxiliary_files/"); + gf_term_navigate_to(term, "./media/auxiliary_files/logo.jpg"); + send_open_url("./media/auxiliary_files/logo.jpg"); + switch_bench(1); + do_set_speed(1.0); + hide_shell(0); + list_modules(); + } +#endif i = gf_sys_clock(); gf_term_disconnect(term); if (rti_file) UpdateRTInfo("Disconnected\n"); - fprintf(stderr, "Deleting terminal... "); if (playlist) gf_fclose(playlist); #if defined(__DARWIN__) || defined(__APPLE__) carbon_uninit(); #endif + //special condition for immediate exit without terminal deletion + if (ret_val==3) { + fprintf(stderr, "Exit forced, no cleanup\n"); + exit(0); + } + + fprintf(stderr, "Deleting terminal... "); gf_term_del(term); fprintf(stderr, "done (in %d ms) - ran for %d ms\n", gf_sys_clock() - i, gf_sys_clock()); fprintf(stderr, "GPAC cleanup ...\n"); - gf_modules_del(user.modules); if (no_cfg_save) - gf_cfg_discard_changes(cfg_file); - - gf_cfg_del(cfg_file); + gf_opts_discard_changes(); gf_sys_close(); @@ -2343,49 +2188,90 @@ force_input: return ret_val; } -#if defined(WIN32) && !defined(NO_WMAIN) -int wmain(int argc, wchar_t** wargv) -{ - int i; - int res; - size_t len; - size_t res_len; - char **argv; - argv = (char **)malloc(argc*sizeof(wchar_t *)); - for (i = 0; i < argc; i++) { - wchar_t *src_str = wargv[i]; - len = UTF8_MAX_BYTES_PER_CHAR * gf_utf8_wcslen(wargv[i]); - argv[i] = (char *)malloc(len + 1); - res_len = gf_utf8_wcstombs(argv[i], len, &src_str); - argv[i][res_len] = 0; - if (res_len > len) { - fprintf(stderr, "Length allocated for conversion of wide char to UTF-8 not sufficient\n"); - return -1; - } - } - res = mp4client_main(argc, argv); - for (i = 0; i < argc; i++) { - free(argv[i]); - } - free(argv); - return res; -} -#else -int main(int argc, char** argv) +GF_MAIN_FUNC(mp4client_main) + + +static void MakeScreenshot(Bool for_coverage) { - return mp4client_main(argc, argv); + char szFileName[100]; + u32 nb_pass, nb_views, offscreen_view = 0; + GF_VideoSurface fb; + GF_Err e; + nb_pass = 1; + nb_views = gf_term_get_option(term, GF_OPT_NUM_STEREO_VIEWS); + if (nb_views>1) { + fprintf(stderr, "Auto-stereo mode detected - type number of view to dump (0 is main output, 1 to %d offscreen view, %d for all offscreen, %d for all offscreen and main)\n", nb_views, nb_views+1, nb_views+2); + if (!for_coverage) { + if (scanf("%d", &offscreen_view) != 1) { + offscreen_view = 0; + } + } + if (offscreen_view==nb_views+1) { + offscreen_view = 1; + nb_pass = nb_views; + } + else if (offscreen_view==nb_views+2) { + offscreen_view = 0; + nb_pass = nb_views+1; + } + } + if (for_coverage && !offscreen_view) { + if (gf_term_get_offscreen_buffer(term, &fb, 0, 0)==GF_OK) + gf_term_release_screen_buffer(term, &fb); + } + while (nb_pass) { + nb_pass--; + if (offscreen_view) { + sprintf(szFileName, "view%d_dump.png", offscreen_view); + e = gf_term_get_offscreen_buffer(term, &fb, offscreen_view-1, 0); + } else { + sprintf(szFileName, "gpac_video_dump_"LLU".png", gf_net_get_utc() ); + e = gf_term_get_screen_buffer(term, &fb); + } + offscreen_view++; + if (e) { + fprintf(stderr, "Error dumping screen buffer %s\n", gf_error_to_string(e) ); + nb_pass = 0; + } else { +#ifndef GPAC_DISABLE_AV_PARSERS + u32 dst_size = fb.width*fb.height*4; + char *dst = (char*)gf_malloc(sizeof(char)*dst_size); + + e = gf_img_png_enc(fb.video_buffer, fb.width, fb.height, fb.pitch_y, fb.pixel_format, dst, &dst_size); + if (e) { + fprintf(stderr, "Error encoding PNG %s\n", gf_error_to_string(e) ); + nb_pass = 0; + } else { + FILE *png = gf_fopen(szFileName, "wb"); + if (!png) { + fprintf(stderr, "Error writing file %s\n", szFileName); + nb_pass = 0; + } else { + gf_fwrite(dst, dst_size, png); + gf_fclose(png); + fprintf(stderr, "Dump to %s\n", szFileName); + } + } + if (dst) gf_free(dst); + gf_term_release_screen_buffer(term, &fb); + + if (for_coverage) gf_file_delete(szFileName); +#endif //GPAC_DISABLE_AV_PARSERS + } + } + fprintf(stderr, "Done: %s\n", szFileName); } -#endif //win32 + static GF_ObjectManager *video_odm = NULL; static GF_ObjectManager *audio_odm = NULL; static GF_ObjectManager *scene_odm = NULL; static u32 last_odm_count = 0; -void PrintAVInfo(Bool final) + +static void PrintAVInfo(Bool final) { GF_MediaInfo a_odi, v_odi, s_odi; - Double avg_dec_time=0; - u32 tot_time=0; - Bool print_codecs = final; + Double avg_dec_time; + u32 tot_time; if (scene_odm) { GF_ObjectManager *root_odm = gf_term_get_root_object(term); @@ -2397,7 +2283,7 @@ void PrintAVInfo(Bool final) } if (!video_odm && !audio_odm && !scene_odm) { u32 count, i; - GF_ObjectManager *root_odm = root_odm = gf_term_get_root_object(term); + GF_ObjectManager *root_odm = gf_term_get_root_object(term); if (!root_odm) return; if (gf_term_get_object_info(term, root_odm, &v_odi)==GF_OK) { @@ -2437,12 +2323,12 @@ void PrintAVInfo(Bool final) } else { memset(&v_odi, 0, sizeof(v_odi)); } - if (print_codecs && audio_odm) { + if (audio_odm) { gf_term_get_object_info(term, audio_odm, &a_odi); } else { memset(&a_odi, 0, sizeof(a_odi)); } - if ((print_codecs || !video_odm) && scene_odm) { + if (!video_odm && scene_odm) { gf_term_get_object_info(term, scene_odm, &s_odi); } else { memset(&s_odi, 0, sizeof(s_odi)); @@ -2460,54 +2346,51 @@ void PrintAVInfo(Bool final) fprintf(stderr, "Drawn %d frames FPS %.2f (simulation FPS %.2f) - duration %d ms\n", nb_frames_drawn, ((Float)nb_frames_drawn*1000)/tot_time,(Float) FPS, gf_term_get_time_in_ms(term) ); } } - if (print_codecs) { - if (video_odm) { - fprintf(stderr, "%s %dx%d sar=%d:%d duration %.2fs\n", v_odi.codec_name, v_odi.width, v_odi.height, v_odi.par ? (v_odi.par>>16)&0xFF : 1, v_odi.par ? (v_odi.par)&0xFF : 1, v_odi.duration); - if (final) { - u32 dec_run_time = v_odi.last_frame_time - v_odi.first_frame_time; - if (!dec_run_time) dec_run_time = 1; - if (v_odi.duration) fprintf(stderr, "%d%% ", (u32) (100*v_odi.current_time / v_odi.duration ) ); - fprintf(stderr, "%d frames FPS %.2f (max %d us/f) rate avg %d max %d", v_odi.nb_dec_frames, ((Float)v_odi.nb_dec_frames*1000) / dec_run_time, v_odi.max_dec_time, (u32) v_odi.avg_bitrate/1000, (u32) v_odi.max_bitrate/1000); - if (v_odi.nb_dropped) { - fprintf(stderr, " (Error during bench: %d frames drop)", v_odi.nb_dropped); - } - fprintf(stderr, "\n"); - } - } - if (audio_odm) { - fprintf(stderr, "%s SR %d num channels %d bpp %d duration %.2fs\n", a_odi.codec_name, a_odi.sample_rate, a_odi.num_channels, a_odi.bits_per_sample, a_odi.duration); - if (final) { - u32 dec_run_time = a_odi.last_frame_time - a_odi.first_frame_time; - if (!dec_run_time) dec_run_time = 1; - if (a_odi.duration) fprintf(stderr, "%d%% ", (u32) (100*a_odi.current_time / a_odi.duration ) ); - fprintf(stderr, "%d frames (ms/f %.2f avg %.2f max) rate avg %d max %d", a_odi.nb_dec_frames, ((Float)dec_run_time)/a_odi.nb_dec_frames, a_odi.max_dec_time/1000.0, (u32) a_odi.avg_bitrate/1000, (u32) a_odi.max_bitrate/1000); - if (a_odi.nb_dropped) { - fprintf(stderr, " (Error during bench: %d frames drop)", a_odi.nb_dropped); - } - fprintf(stderr, "\n"); + if (video_odm) { + fprintf(stderr, "%s %dx%d sar=%d:%d duration %.2fs\n", v_odi.codec_name, v_odi.width, v_odi.height, v_odi.par ? (v_odi.par>>16)&0xFF : 1, v_odi.par ? (v_odi.par)&0xFF : 1, v_odi.duration); + if (final) { + u32 dec_run_time = v_odi.last_frame_time - v_odi.first_frame_time; + if (!dec_run_time) dec_run_time = 1; + if (v_odi.duration) fprintf(stderr, "%d%% ", (u32) (100*v_odi.current_time / v_odi.duration ) ); + fprintf(stderr, "%d frames FPS %.2f (max %d us/f) rate avg %d max %d", v_odi.nb_dec_frames, ((Float)v_odi.nb_dec_frames*1000) / dec_run_time, v_odi.max_dec_time, (u32) v_odi.avg_bitrate/1000, (u32) v_odi.max_bitrate/1000); + if (v_odi.nb_dropped) { + fprintf(stderr, " (Error during bench: %d frames drop)", v_odi.nb_dropped); } + fprintf(stderr, "\n"); } - if (scene_odm) { - u32 w, h; - gf_term_get_visual_output_size(term, &w, &h); - fprintf(stderr, "%s scene size %dx%d rastered to %dx%d duration %.2fs\n", s_odi.codec_name ? s_odi.codec_name : "", s_odi.width, s_odi.height, w, h, s_odi.duration); - if (final) { - if (s_odi.nb_dec_frames>2 && s_odi.total_dec_time) { - u32 dec_run_time = s_odi.last_frame_time - s_odi.first_frame_time; - if (!dec_run_time) dec_run_time = 1; - fprintf(stderr, "%d frames FPS %.2f (max %d us/f) rate avg %d max %d", s_odi.nb_dec_frames, ((Float)s_odi.nb_dec_frames*1000) / dec_run_time, s_odi.max_dec_time, (u32) s_odi.avg_bitrate/1000, (u32) s_odi.max_bitrate/1000); - fprintf(stderr, "\n"); - } else { - u32 nb_frames_drawn; - Double FPS; - gf_term_get_simulation_frame_rate(term, &nb_frames_drawn); - tot_time = gf_sys_clock() - bench_mode_start; - FPS = gf_term_get_framerate(term, 0); - fprintf(stderr, "%d frames FPS %.2f (abs %.2f)\n", nb_frames_drawn, (1000.0*nb_frames_drawn / tot_time), FPS); - } + } + if (audio_odm) { + fprintf(stderr, "%s SR %d num channels %d %s duration %.2fs\n", a_odi.codec_name, a_odi.sample_rate, a_odi.num_channels, gf_audio_fmt_name(a_odi.afmt), a_odi.duration); + if (final) { + u32 dec_run_time = a_odi.last_frame_time - a_odi.first_frame_time; + if (!dec_run_time) dec_run_time = 1; + if (a_odi.duration) fprintf(stderr, "%d%% ", (u32) (100*a_odi.current_time / a_odi.duration ) ); + fprintf(stderr, "%d frames (ms/f %.2f avg %.2f max) rate avg %d max %d", a_odi.nb_dec_frames, ((Float)dec_run_time)/a_odi.nb_dec_frames, a_odi.max_dec_time/1000.0, (u32) a_odi.avg_bitrate/1000, (u32) a_odi.max_bitrate/1000); + if (a_odi.nb_dropped) { + fprintf(stderr, " (Error during bench: %d frames drop)", a_odi.nb_dropped); } + fprintf(stderr, "\n"); } + } + if (scene_odm) { + u32 w, h; + gf_term_get_visual_output_size(term, &w, &h); + fprintf(stderr, "%s scene size %dx%d rastered to %dx%d duration %.2fs\n", s_odi.codec_name ? s_odi.codec_name : "", s_odi.width, s_odi.height, w, h, s_odi.duration); if (final) { + if (s_odi.nb_dec_frames>2 && s_odi.total_dec_time) { + u32 dec_run_time = s_odi.last_frame_time - s_odi.first_frame_time; + if (!dec_run_time) dec_run_time = 1; + fprintf(stderr, "%d frames FPS %.2f (max %d us/f) rate avg %d max %d", s_odi.nb_dec_frames, ((Float)s_odi.nb_dec_frames*1000) / dec_run_time, s_odi.max_dec_time, (u32) s_odi.avg_bitrate/1000, (u32) s_odi.max_bitrate/1000); + fprintf(stderr, "\n"); + } else { + u32 nb_frames_drawn; + Double FPS; + gf_term_get_simulation_frame_rate(term, &nb_frames_drawn); + tot_time = gf_sys_clock() - bench_mode_start; + FPS = gf_term_get_framerate(term, 0); + fprintf(stderr, "%d frames FPS %.2f (abs %.2f)\n", nb_frames_drawn, (1000.0*nb_frames_drawn / tot_time), FPS); + } + fprintf(stderr, "**********************************************************\n\n"); return; } @@ -2517,7 +2400,7 @@ void PrintAVInfo(Bool final) tot_time = v_odi.last_frame_time - v_odi.first_frame_time; if (!tot_time) tot_time=1; if (v_odi.duration) fprintf(stderr, "%d%% ", (u32) (100*v_odi.current_time / v_odi.duration ) ); - fprintf(stderr, "%d f FPS %.2f (%.2f ms max) rate %d ", v_odi.nb_dec_frames, ((Float)v_odi.nb_dec_frames*1000) / tot_time, v_odi.max_dec_time/1000.0, (u32) v_odi.instant_bitrate/1000); + fprintf(stderr, "%d f FPS %.2f (%.2f ms max) rate %d ", v_odi.nb_dec_frames, ((Float)v_odi.nb_dec_frames*1000) / tot_time, v_odi.max_dec_time/1000.0, (u32) v_odi.avg_bitrate/1000); } else if (scene_odm) { @@ -2525,7 +2408,7 @@ void PrintAVInfo(Bool final) avg_dec_time = (Float) 1000000 * s_odi.nb_dec_frames; avg_dec_time /= s_odi.total_dec_time; if (s_odi.duration) fprintf(stderr, "%d%% ", (u32) (100*s_odi.current_time / s_odi.duration ) ); - fprintf(stderr, "%d f %.2f (%d us max) - rate %d ", s_odi.nb_dec_frames, avg_dec_time, s_odi.max_dec_time, (u32) s_odi.instant_bitrate/1000); + fprintf(stderr, "%d f %.2f (%d us max) - rate %d ", s_odi.nb_dec_frames, avg_dec_time, s_odi.max_dec_time, (u32) s_odi.avg_bitrate/1000); } else { u32 nb_frames_drawn; Double FPS; @@ -2536,9 +2419,8 @@ void PrintAVInfo(Bool final) } } else if (audio_odm) { - if (!print_codecs) { - gf_term_get_object_info(term, audio_odm, &a_odi); - } + gf_term_get_object_info(term, audio_odm, &a_odi); + tot_time = a_odi.last_frame_time - a_odi.first_frame_time; if (!tot_time) tot_time=1; if (a_odi.duration) fprintf(stderr, "%d%% ", (u32) (100*a_odi.current_time / a_odi.duration ) ); @@ -2546,7 +2428,7 @@ void PrintAVInfo(Bool final) } } -void PrintWorldInfo(GF_Terminal *term) +static void PrintWorldInfo(GF_Terminal *term) { u32 i; const char *title; @@ -2565,12 +2447,11 @@ void PrintWorldInfo(GF_Terminal *term) gf_list_del(descs); } -void PrintODList(GF_Terminal *term, GF_ObjectManager *root_odm, u32 num, u32 indent, char *root_name) +static void PrintODList(GF_Terminal *term, GF_ObjectManager *root_odm, u32 num, u32 indent, char *root_name) { GF_MediaInfo odi; u32 i, count; char szIndent[50]; - GF_ObjectManager *odm; if (!root_odm) { fprintf(stderr, "Currently loaded objects:\n"); @@ -2583,7 +2464,7 @@ void PrintODList(GF_Terminal *term, GF_ObjectManager *root_odm, u32 num, u32 ind fprintf(stderr, "Current service ID %d\n", count); if (gf_term_get_object_info(term, root_odm, &odi) != GF_OK) return; - if (!odi.od) { + if (!odi.ODID) { fprintf(stderr, "Service not attached\n"); return; } @@ -2593,11 +2474,11 @@ void PrintODList(GF_Terminal *term, GF_ObjectManager *root_odm, u32 num, u32 ind fprintf(stderr, "%s", szIndent); fprintf(stderr, "#%d %s - ", num, root_name); - if (odi.od->ServiceID) fprintf(stderr, "Service ID %d ", odi.od->ServiceID); + if (odi.ServiceID) fprintf(stderr, "Service ID %d ", odi.ServiceID); if (odi.media_url) { fprintf(stderr, "%s\n", odi.media_url); } else { - fprintf(stderr, "OD ID %d\n", odi.od->objectDescriptorID); + fprintf(stderr, "OD ID %d\n", odi.ODID); } szIndent[indent]=' '; @@ -2606,7 +2487,7 @@ void PrintODList(GF_Terminal *term, GF_ObjectManager *root_odm, u32 num, u32 ind count = gf_term_get_object_count(term, root_odm); for (i=0; iobjectDescriptorID); + fprintf(stderr, "ID %d", odi.ODID); } fprintf(stderr, " - %s", (odi.od_type==GF_STREAM_VISUAL) ? "Video" : (odi.od_type==GF_STREAM_AUDIO) ? "Audio" : "Systems"); - if (odi.od && odi.od->ServiceID) fprintf(stderr, " - Service ID %d", odi.od->ServiceID); + if (odi.ServiceID) fprintf(stderr, " - Service ID %d", odi.ServiceID); fprintf(stderr, "\n"); break; } @@ -2637,12 +2522,12 @@ void PrintODList(GF_Terminal *term, GF_ObjectManager *root_odm, u32 num, u32 ind } } -void ViewOD(GF_Terminal *term, u32 OD_ID, u32 number, const char *szURL) +static void ViewOD(GF_Terminal *term, u32 OD_ID, u32 number, const char *szURL) { GF_MediaInfo odi; - u32 i, j, count, d_enum,id; + u32 i, count, d_enum, id; + GF_TermNetStats stats; GF_Err e; - NetStatCommand com; GF_ObjectManager *odm, *root_odm = gf_term_get_root_object(term); if (!root_odm) return; @@ -2657,7 +2542,7 @@ void ViewOD(GF_Terminal *term, u32 OD_ID, u32 number, const char *szURL) if (!odm) break; if (gf_term_get_object_info(term, odm, &odi) == GF_OK) { if (szURL && strstr(odi.service_url, szURL)) break; - if ((number == (u32)(-1)) && (odi.od->objectDescriptorID == OD_ID)) break; + if ((number == (u32)(-1)) && (odi.ODID == OD_ID)) break; else if (i == (u32)(number-1)) break; } odm = NULL; @@ -2669,26 +2554,12 @@ void ViewOD(GF_Terminal *term, u32 OD_ID, u32 number, const char *szURL) else fprintf(stderr, "cannot find OD with number %d\n", number); return; } - if (!odi.od) { + if (!odi.ODID) { if (number == (u32)-1) fprintf(stderr, "Object %d not attached yet\n", OD_ID); else fprintf(stderr, "Object #%d not attached yet\n", number); return; } - if (!odi.od) { - fprintf(stderr, "Service not attached\n"); - return; - } - - if (odi.od->tag==GF_ODF_IOD_TAG) { - fprintf(stderr, "InitialObjectDescriptor %d\n", odi.od->objectDescriptorID); - fprintf(stderr, "Profiles and Levels: Scene %x - Graphics %x - Visual %x - Audio %x - OD %x\n", - odi.scene_pl, odi.graphics_pl, odi.visual_pl, odi.audio_pl, odi.OD_pl); - fprintf(stderr, "Inline Profile Flag %d\n", odi.inline_pl); - } else { - fprintf(stderr, "ObjectDescriptor %d\n", odi.od->objectDescriptorID); - } - fprintf(stderr, "Object Duration: "); if (odi.duration) { PrintTime((u32) (odi.duration*1000)); @@ -2741,114 +2612,13 @@ void ViewOD(GF_Terminal *term, u32 OD_ID, u32 number, const char *szURL) } if (odi.protection) fprintf(stderr, "Encrypted Media%s\n", (odi.protection==2) ? " NOT UNLOCKED" : ""); - count = gf_list_count(odi.od->ESDescriptors); - fprintf(stderr, "%d streams in OD\n", count); - for (i=0; iESDescriptors, i); - - fprintf(stderr, "\nStream ID %d - Clock ID %d\n", esd->ESID, esd->OCRESID); - if (esd->dependsOnESID) fprintf(stderr, "\tDepends on Stream ID %d for decoding\n", esd->dependsOnESID); - - switch (esd->decoderConfig->streamType) { - case GF_STREAM_OD: - fprintf(stderr, "\tOD Stream - version %d\n", esd->decoderConfig->objectTypeIndication); - break; - case GF_STREAM_OCR: - fprintf(stderr, "\tOCR Stream\n"); - break; - case GF_STREAM_SCENE: - fprintf(stderr, "\tScene Description Stream - version %d\n", esd->decoderConfig->objectTypeIndication); - break; - case GF_STREAM_VISUAL: - fprintf(stderr, "\tVisual Stream - media type: %s", gf_esd_get_textual_description(esd)); - break; - case GF_STREAM_AUDIO: - fprintf(stderr, "\tAudio Stream - media type: %s", gf_esd_get_textual_description(esd)); - break; - case GF_STREAM_MPEG7: - fprintf(stderr, "\tMPEG-7 Stream - version %d\n", esd->decoderConfig->objectTypeIndication); - break; - case GF_STREAM_IPMP: - fprintf(stderr, "\tIPMP Stream - version %d\n", esd->decoderConfig->objectTypeIndication); - break; - case GF_STREAM_OCI: - fprintf(stderr, "\tOCI Stream - version %d\n", esd->decoderConfig->objectTypeIndication); - break; - case GF_STREAM_MPEGJ: - fprintf(stderr, "\tMPEGJ Stream - version %d\n", esd->decoderConfig->objectTypeIndication); - break; - case GF_STREAM_INTERACT: - fprintf(stderr, "\tUser Interaction Stream - version %d\n", esd->decoderConfig->objectTypeIndication); - break; - case GF_STREAM_TEXT: - fprintf(stderr, "\tStreaming Text Stream - version %d\n", esd->decoderConfig->objectTypeIndication); - break; - default: - fprintf(stderr, "\tUnknown Stream\n"); - break; - } - - fprintf(stderr, "\tBuffer Size %d\n\tAverage Bitrate %d bps\n\tMaximum Bitrate %d bps\n", esd->decoderConfig->bufferSizeDB, esd->decoderConfig->avgBitrate, esd->decoderConfig->maxBitrate); - if (esd->slConfig->predefined==SLPredef_SkipSL) { - fprintf(stderr, "\tNot using MPEG-4 Synchronization Layer\n"); - } else { - fprintf(stderr, "\tStream Clock Resolution %d\n", esd->slConfig->timestampResolution); - } - if (esd->URLString) fprintf(stderr, "\tStream Location: %s\n", esd->URLString); + fprintf(stderr, "\nStream ID %d - %s - Clock ID %d\n", odi.pid_id, gf_stream_type_name(odi.od_type), odi.ocr_id); +// if (esd->dependsOnESID) fprintf(stderr, "\tDepends on Stream ID %d for decoding\n", esd->dependsOnESID); - /*check language*/ - if (esd->langDesc) { - s32 lang_idx; - char lan[4]; - lan[0] = esd->langDesc->langCode>>16; - lan[1] = (esd->langDesc->langCode>>8)&0xFF; - lan[2] = (esd->langDesc->langCode)&0xFF; - lan[3] = 0; + if (odi.lang_code) + fprintf(stderr, "\tStream Language: %s\n", odi.lang_code); - lang_idx = gf_lang_find(lan); - if (lang_idx>=0) { - fprintf(stderr, "\tStream Language: %s\n", gf_lang_get_name(lang_idx)); - } - } - } fprintf(stderr, "\n"); - /*check OCI (not everything interests us) - FIXME: support for unicode*/ - count = gf_list_count(odi.od->OCIDescriptors); - if (count) { - fprintf(stderr, "%d Object Content Information descriptors in OD\n", count); - for (i=0; iOCIDescriptors, i); - switch (desc->tag) { - case GF_ODF_SEGMENT_TAG: - { - GF_Segment *sd = (GF_Segment *) desc; - fprintf(stderr, "Segment Descriptor: Name: %s - start time %g sec - duration %g sec\n", sd->SegmentName, sd->startTime, sd->Duration); - } - break; - case GF_ODF_CC_NAME_TAG: - { - GF_CC_Name *ccn = (GF_CC_Name *)desc; - fprintf(stderr, "Content Creators:\n"); - for (j=0; jContentCreators); j++) { - GF_ContentCreatorInfo *ci = (GF_ContentCreatorInfo *) gf_list_get(ccn->ContentCreators, j); - if (!ci->isUTF8) continue; - fprintf(stderr, "\t%s\n", ci->contentCreatorName); - } - } - break; - - case GF_ODF_SHORT_TEXT_TAG: - { - GF_ShortTextual *std = (GF_ShortTextual *)desc; - fprintf(stderr, "Description:\n\tEvent: %s\n\t%s\n", std->eventName, std->eventText); - } - break; - default: - break; - } - } - fprintf(stderr, "\n"); - } switch (odi.status) { case 0: @@ -2878,7 +2648,7 @@ void ViewOD(GF_Terminal *term, u32 OD_ID, u32 number, const char *szURL) const char *url; u32 done, total, bps; d_enum = 0; - while (gf_term_get_download_info(term, odm, &d_enum, &url, NULL, &done, &total, &bps)) { + while (gf_term_get_download_info(term, odm, &d_enum, &url, &done, &total, &bps)) { if (d_enum==1) fprintf(stderr, "Current Downloads in service:\n"); if (done && total) { fprintf(stderr, "%s: %d / %d bytes (%.2f %%) - %.2f kBps\n", url, done, total, (100.0f*done)/total, ((Float)bps)/1024.0f); @@ -2889,34 +2659,35 @@ void ViewOD(GF_Terminal *term, u32 OD_ID, u32 number, const char *szURL) if (!d_enum) fprintf(stderr, "No Downloads in service\n"); fprintf(stderr, "\n"); } + d_enum = 0; - while (gf_term_get_channel_net_info(term, odm, &d_enum, &id, &com, &e)) { + while (gf_term_get_channel_net_info(term, odm, &d_enum, &id, &stats, &e)) { if (e) continue; - if (!com.bw_down && !com.bw_up) continue; + if (!stats.bw_down && !stats.bw_up) continue; fprintf(stderr, "Stream ID %d statistics:\n", id); - if (com.multiplex_port) { - fprintf(stderr, "\tMultiplex Port %d - multiplex ID %d\n", com.multiplex_port, com.port); + if (stats.multiplex_port) { + fprintf(stderr, "\tMultiplex Port %d - multiplex ID %d\n", stats.multiplex_port, stats.port); } else { - fprintf(stderr, "\tPort %d\n", com.port); - } - fprintf(stderr, "\tPacket Loss Percentage: %.4f\n", com.pck_loss_percentage); - fprintf(stderr, "\tDown Bandwidth: %d bps\n", com.bw_down); - if (com.bw_up) fprintf(stderr, "\tUp Bandwidth: %d bps\n", com.bw_up); - if (com.ctrl_port) { - if (com.multiplex_port) { - fprintf(stderr, "\tControl Multiplex Port: %d - Control Multiplex ID %d\n", com.multiplex_port, com.ctrl_port); + fprintf(stderr, "\tPort %d\n", stats.port); + } + fprintf(stderr, "\tPacket Loss Percentage: %.4f\n", stats.pck_loss_percentage); + fprintf(stderr, "\tDown Bandwidth: %d bps\n", stats.bw_down); + if (stats.bw_up) fprintf(stderr, "\tUp Bandwidth: %d bps\n", stats.bw_up); + if (stats.ctrl_port) { + if (stats.multiplex_port) { + fprintf(stderr, "\tControl Multiplex Port: %d - Control Multiplex ID %d\n", stats.multiplex_port, stats.ctrl_port); } else { - fprintf(stderr, "\tControl Port: %d\n", com.ctrl_port); + fprintf(stderr, "\tControl Port: %d\n", stats.ctrl_port); } - fprintf(stderr, "\tDown Bandwidth: %d bps\n", com.ctrl_bw_down); - fprintf(stderr, "\tUp Bandwidth: %d bps\n", com.ctrl_bw_up); + fprintf(stderr, "\tDown Bandwidth: %d bps\n", stats.ctrl_bw_down); + fprintf(stderr, "\tUp Bandwidth: %d bps\n", stats.ctrl_bw_up); } fprintf(stderr, "\n"); } } -void PrintODTiming(GF_Terminal *term, GF_ObjectManager *odm, u32 indent) +static void PrintODTiming(GF_Terminal *term, GF_ObjectManager *odm, u32 indent) { GF_MediaInfo odi; u32 ind = indent; @@ -2924,7 +2695,7 @@ void PrintODTiming(GF_Terminal *term, GF_ObjectManager *odm, u32 indent) if (!odm) return; if (gf_term_get_object_info(term, odm, &odi) != GF_OK) return; - if (!odi.od) { + if (!odi.ODID) { fprintf(stderr, "Service not attached\n"); return; } @@ -2935,7 +2706,7 @@ void PrintODTiming(GF_Terminal *term, GF_ObjectManager *odm, u32 indent) if (! odi.generated_scene) { - fprintf(stderr, "- OD %d: ", odi.od->objectDescriptorID); + fprintf(stderr, "- OD %d: ", odi.ODID); switch (odi.status) { case 1: fprintf(stderr, "Playing - "); @@ -2967,7 +2738,7 @@ void PrintODTiming(GF_Terminal *term, GF_ObjectManager *odm, u32 indent) } -void PrintODBuffer(GF_Terminal *term, GF_ObjectManager *odm, u32 indent) +static void PrintODBuffer(GF_Terminal *term, GF_ObjectManager *odm, u32 indent) { Float avg_dec_time; GF_MediaInfo odi; @@ -2975,7 +2746,7 @@ void PrintODBuffer(GF_Terminal *term, GF_ObjectManager *odm, u32 indent) if (!odm) return; if (gf_term_get_object_info(term, odm, &odi) != GF_OK) return; - if (!odi.od) { + if (!odi.ODID) { fprintf(stderr, "Service not attached\n"); return; } @@ -2989,7 +2760,7 @@ void PrintODBuffer(GF_Terminal *term, GF_ObjectManager *odm, u32 indent) if (odi.generated_scene) { fprintf(stderr, "+ Service %s:\n", odi.service_url); } else { - fprintf(stderr, "- OD %d: ", odi.od->objectDescriptorID); + fprintf(stderr, "- OD %d: ", odi.ODID); switch (odi.status) { case 1: fprintf(stderr, "Playing"); @@ -3037,7 +2808,7 @@ void PrintODBuffer(GF_Terminal *term, GF_ObjectManager *odm, u32 indent) } -void ViewODs(GF_Terminal *term, Bool show_timing) +static void ViewODs(GF_Terminal *term, Bool show_timing) { GF_ObjectManager *root_odm = gf_term_get_root_object(term); if (!root_odm) return; @@ -3049,41 +2820,3 @@ void ViewODs(GF_Terminal *term, Bool show_timing) } fprintf(stderr, "\n"); } - - -void PrintGPACConfig() -{ - u32 i, j, cfg_count, key_count; - char szName[200]; - char *secName = NULL; - - fprintf(stderr, "Enter section name (\"*\" for complete dump):\n"); - if (1 > scanf("%s", szName)) { - fprintf(stderr, "No section name, aborting.\n"); - return; - } - if (strcmp(szName, "*")) secName = szName; - - fprintf(stderr, "\n\n*** GPAC Configuration ***\n\n"); - - cfg_count = gf_cfg_get_section_count(cfg_file); - for (i=0; i.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/applications/testapps/atscdmx/main.c b/applications/testapps/atscdmx/main.c new file mode 100644 index 0000000..792a3bc --- /dev/null +++ b/applications/testapps/atscdmx/main.c @@ -0,0 +1,151 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018 + * All rights reserved + * + * This file is part of GPAC - ATSC/ROUTE implementation + * + */ + +#include +#include + +void PrintUsage() +{ + fprintf(stderr, "USAGE: [OPTS]\n" +#ifdef GPAC_MEMORY_TRACKING + "-mem-track: enables memory tracker\n" + "-mem-track-stack: enables memory tracker with stack dumping\n" +#endif + "-log-file file: sets output log file. Also works with -lf\n" + "-logs log_args: sets log tools and levels, formatted as a ':'-separated list of toolX[:toolZ]@levelX\n" + "-log-clock or -lc logs time in micro sec since start time of GPAC before each log line.\n" + "-log-utc or -lu logs UTC time in ms before each log line.\n" + "-ifce IP: IP adress of network interface to use\n" + "-dir PATH: local filesystem path to which the files are written. If not set, memory mode is used.\n" + " NOTE: memory mode is not yet implemented ...\n" + "-service ID: ID of the service to play. If not set, all services are dumped. If 0, no services are dumped\n" + "\n" + "\n on OSX with VM packet replay you will need to force mcast routing, eg:\n" + "route add -net 239.255.1.4/32 -interface vboxnet0\n" + ); +} + +static u64 log_time_start = 0; +static Bool log_utc_time = GF_FALSE; +static u64 last_log_time=0; +static void on_atscd_log(void *cbk, GF_LOG_Level ll, GF_LOG_Tool lm, const char *fmt, va_list list) +{ + FILE *logs = cbk ? cbk : stderr; + if (log_time_start) { + u64 now = gf_sys_clock_high_res(); + fprintf(logs, "At "LLD" (diff %d) - ", now - log_time_start, (u32) (now - last_log_time) ); + last_log_time = now; + } + if (log_utc_time) { + u64 utc_clock = gf_net_get_utc() ; + time_t secs = utc_clock/1000; + struct tm t; + t = *gmtime(&secs); + fprintf(logs, "UTC %d-%02d-%02dT%02d:%02d:%02dZ (TS "LLU") - ", 1900+t.tm_year, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, utc_clock); + } + vfprintf(logs, fmt, list); + fflush(logs); +} + +int main(int argc, char **argv) +{ + u32 i, serviceID=0xFFFFFFFF; + Bool run = GF_TRUE; + GF_MemTrackerType mem_track = GF_MemTrackerNone; + const char *ifce = NULL; + const char *dir = NULL; + FILE *logfile = NULL; + GF_ATSCDmx *atscd; + if (argc<1) { + PrintUsage(); + return 0; + } + for (i=0; i<(u32)argc; i++) { + char *arg = argv[i]; + if (!strcmp(arg, "-mem-track") || !strcmp(arg, "-mem-track-stack")) { +#ifdef GPAC_MEMORY_TRACKING + mem_track = !strcmp(arg, "-mem-track-stack") ? GF_MemTrackerBackTrace : GF_MemTrackerSimple; +#else + fprintf(stderr, "WARNING - GPAC not compiled with Memory Tracker - ignoring \"%s\"\n", argv[i]); +#endif + } + } + + /*****************/ + /* gpac init */ + /*****************/ +#ifdef GPAC_MEMORY_TRACKING + gf_sys_init(mem_track); +#else + gf_sys_init(GF_MemTrackerNone); +#endif + gf_log_set_tool_level(GF_LOG_ALL, GF_LOG_WARNING); + gf_log_set_tool_level(GF_LOG_CONTAINER, GF_LOG_INFO); + + //parse args + for (i=0; i<(u32)argc; i++) { + char *arg = argv[i]; + + if (!strcmp(arg, "-ifce")) ifce=argv[++i]; + else if (!strcmp(arg, "-dir")) dir=argv[++i]; + else if (!strcmp(arg, "-service")) serviceID=atoi(argv[++i]); + else if (!strcmp(arg, "-log-file") || !strcmp(arg, "-lf")) { + logfile = gf_fopen(argv[i+1], "wt"); + i++; + } else if (!strcmp(arg, "-logs") ) { + if (gf_log_set_tools_levels(argv[i+1]) != GF_OK) { + return 1; + } + i++; + } else if (!strcmp(arg, "-log-clock") || !strcmp(arg, "-lc")) { + log_time_start = 1; + } else if (!strcmp(arg, "-log-utc") || !strcmp(arg, "-lu")) { + log_utc_time = 1; + } + } + + gf_log_set_callback(logfile, on_atscd_log); + if (log_time_start) log_time_start = gf_sys_clock_high_res(); + /*****************/ + /* atsc init */ + /*****************/ + atscd = gf_atsc3_dmx_new(ifce, dir, 0); + gf_atsc3_tune_in(atscd, serviceID, GF_FALSE); + + while (atscd && run) { + GF_Err e = gf_atsc3_dmx_process(atscd); + if (e == GF_IP_NETWORK_EMPTY) + gf_sleep(1); + + if (gf_prompt_has_input()) { + u8 c = gf_prompt_get_char(); + switch (c) { + case 'q': + run = GF_FALSE; + break; + } + } + } + gf_atsc3_dmx_del(atscd); + + if (logfile) gf_fclose(logfile); + gf_sys_close(); + +#ifdef GPAC_MEMORY_TRACKING + if (mem_track && (gf_memory_size() || gf_file_handles_count() )) { + gf_log_set_tool_level(GF_LOG_MEMORY, GF_LOG_INFO); + gf_memory_print(); + return 2; + } +#endif + return 0; +} + diff --git a/applications/testapps/bmp4demux/Makefile b/applications/testapps/bmp4demux/Makefile index 031c4bb..db23616 100644 --- a/applications/testapps/bmp4demux/Makefile +++ b/applications/testapps/bmp4demux/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/applications/testapps/bmp4demux CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/applications/testapps/broadcaster/Makefile b/applications/testapps/broadcaster/Makefile index c3c3056..821a1f4 100644 --- a/applications/testapps/broadcaster/Makefile +++ b/applications/testapps/broadcaster/Makefile @@ -8,12 +8,12 @@ SOURCES= APPNAME=broadcaster -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/applications/testapps/broadcaster/RTP_serv_generator.c b/applications/testapps/broadcaster/RTP_serv_generator.c index 027aa3a..4e927b0 100644 --- a/applications/testapps/broadcaster/RTP_serv_generator.c +++ b/applications/testapps/broadcaster/RTP_serv_generator.c @@ -207,7 +207,7 @@ static char * eat_buffer_to_bs(char * data, int newStart, int upToPosition, int GF_Err PNC_processBIFSGenerator(PNC_CallbackData * data) { const int tmpBufferSize = 2048; - char *tmpBuffer = (char*)alloca(tmpBufferSize); + char *tmpBuffer = (char*)malloc(tmpBufferSize); int byteRead=0; char *bsBuffer; @@ -219,6 +219,7 @@ GF_Err PNC_processBIFSGenerator(PNC_CallbackData * data) data->socket = NULL; e = gf_sk_accept(data->server_socket, &(data->socket)); if (e) { + free(tmpBuffer); return GF_OK; } else { dprintf(DEBUG_RTP_serv_generator, "New TCP client connected !\n"); diff --git a/applications/testapps/broadcaster/broadcaster.c b/applications/testapps/broadcaster/broadcaster.c index 287667d..f685d4f 100644 --- a/applications/testapps/broadcaster/broadcaster.c +++ b/applications/testapps/broadcaster/broadcaster.c @@ -62,8 +62,10 @@ static int command_line_parsing(int argc, const char** argv, unsigned short * tc } else if (!strcmp("-m", a) || !strcmp("--mtu", a)) { - *mtu_size = atoi(argv[counter+1]); - if (!(mtu_size)) return -3; + if (mtu_size) { + *mtu_size = atoi(argv[counter+1]); + if (! (*mtu_size)) return -3; + } } else if (!strcmp("-d", a) || !strcmp("--debug", a)) { @@ -102,10 +104,10 @@ u32 RAP_send(void *par) { RAP_Input *input = par; PNC_CallbackData *data = input->data; - u32 *timer; input->status = 1; while (input->status==1) { + u32 *timer; gf_mx_p(input->carrousel_mutex); timer = input->RAPtimer; @@ -169,7 +171,6 @@ u32 tcp_server(void *par) unsigned char temp[MAX_BUF]; FILE *fp; u32 byte_read; - int ret; GF_Config *gf_config_file; GF_Socket *TCP_socket; GF_Socket *conn_socket; @@ -186,6 +187,7 @@ u32 tcp_server(void *par) while(input->status == 1) { + int ret; memset(buffer, 0, sizeof(buffer)); e = gf_sk_accept(TCP_socket, &conn_socket); if (e == GF_OK) { @@ -237,7 +239,7 @@ u32 tcp_server(void *par) while (1) { - GF_Err e = gf_sk_receive(conn_socket, temp, sizeof(temp), 0, &byte_read); + e = gf_sk_receive(conn_socket, temp, sizeof(temp), 0, &byte_read); if (e == GF_OK) { gf_fwrite(temp, 1, byte_read, fp); @@ -410,7 +412,7 @@ int main (const int argc, const char** argv) run = 1; while (run) { - GF_Err e = PNC_processBIFSGenerator(data); + e = PNC_processBIFSGenerator(data); if (e) { fprintf(stderr, "Cannot Process BIFS data (%s)\n", gf_error_to_string(e)); break; diff --git a/applications/testapps/broadcaster/sdp_generator.c b/applications/testapps/broadcaster/sdp_generator.c index 2547037..fd31578 100644 --- a/applications/testapps/broadcaster/sdp_generator.c +++ b/applications/testapps/broadcaster/sdp_generator.c @@ -7,7 +7,6 @@ int sdp_generator(PNC_CallbackData *data, char *ip_dest, char *sdp_fmt) GF_ESD *esd = NULL; u32 size,size64; char *buffer; - char buf64[5000]; FILE *fp; int ret; char temp[5000]; @@ -35,6 +34,7 @@ int sdp_generator(PNC_CallbackData *data, char *ip_dest, char *sdp_fmt) codec = (GF_SceneEngine *) data->codec; if (codec) { + char buf64[5000]; buffer = NULL; size = 0; gf_odf_desc_write((GF_Descriptor *) codec->ctx->root_od, &buffer, &size); diff --git a/applications/testapps/fmp4demux/Makefile b/applications/testapps/fmp4demux/Makefile index c36d72a..f0ee334 100644 --- a/applications/testapps/fmp4demux/Makefile +++ b/applications/testapps/fmp4demux/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/applications/testapps/fmp4demux CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/applications/testapps/fmp4demux/main.c b/applications/testapps/fmp4demux/main.c index 5ff6b87..062b813 100644 --- a/applications/testapps/fmp4demux/main.c +++ b/applications/testapps/fmp4demux/main.c @@ -139,7 +139,6 @@ static u32 iso_progressive_read_thread(void *param) /* release internal structures associated with the samples read so far */ gf_isom_reset_tables(reader->movie, GF_TRUE); -#if 1 /* release the associated input data as well */ gf_isom_reset_data_offset(reader->movie, &new_buffer_start); if (new_buffer_start) { @@ -149,7 +148,6 @@ static u32 iso_progressive_read_thread(void *param) } sprintf(reader->data_url, "gmem://%d@%p", reader->valid_data_size, reader->data); gf_isom_refresh_fragmented(reader->movie, &missing_bytes, reader->data_url); -#endif /* update the sample count and sample index */ sample_count = new_sample_count - sample_count; diff --git a/applications/testapps/hevcbench/Makefile b/applications/testapps/hevcbench/Makefile index 9668111..768dcb0 100644 --- a/applications/testapps/hevcbench/Makefile +++ b/applications/testapps/hevcbench/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/applications/testapps/hevcbench CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" $(SDL_CFLAGS) $(OGL_INCLS) $(OHEVC_CFLAGS) -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/applications/testapps/hevcbench/main.c b/applications/testapps/hevcbench/main.c index a6c92b1..74c12fe 100644 --- a/applications/testapps/hevcbench/main.c +++ b/applications/testapps/hevcbench/main.c @@ -563,27 +563,33 @@ void sdl_draw_frame(u8 *pY, u8 *pU, u8 *pV, u32 w, u32 h, u32 bit_depth, u32 str glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB); #if (COPY_TYPE!=5) - needs_stride=0; +// needs_stride=0; #endif #if (NO_TEX==0) glBindTexture(texture_type, txid[0] ); glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo_Y); +#if (COPY_TYPE!=5) if (needs_stride) glPixelStorei(GL_UNPACK_ROW_LENGTH, needs_stride); +#endif glTexImage2D(texture_type, 0, GL_LUMINANCE, w, h, 0, pixel_format, memory_format, NULL); //glTexSubImage2D crashes with PBO and 2-bytes luminance on my FirePro W5000 ... // glTexSubImage2D(texture_type, 0, 0, 0, w, h, pixel_format, memory_format, pY); glBindTexture(texture_type, txid[1] ); glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo_U); +#if (COPY_TYPE!=5) if (needs_stride) glPixelStorei(GL_UNPACK_ROW_LENGTH, needs_stride/2); +#endif glTexImage2D(texture_type, 0, GL_LUMINANCE, uv_w, uv_h, 0, pixel_format, memory_format, NULL); // glTexSubImage2D(texture_type, 0, 0, 0, uv_w, uv_h, pixel_format, memory_format, pU); glBindTexture(texture_type, txid[2] ); glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo_V); +#if (COPY_TYPE!=5) if (needs_stride) glPixelStorei(GL_UNPACK_ROW_LENGTH, needs_stride/2); +#endif glTexImage2D(texture_type, 0, GL_LUMINANCE, uv_w, uv_h, 0, pixel_format, memory_format, NULL); // glTexSubImage2D(texture_type, 0, 0, 0, uv_w, uv_h, pixel_format, memory_format, pV); #endif @@ -868,7 +874,7 @@ int main(int argc, char **argv) if (!sdl_is_init && !no_display) { u64 sdl_init_time = gf_sys_clock_high_res(); u32 stride = HVCFrame.frameInfo.nYPitch; - u32 bpp = HVCFrame.frameInfo.nBitDepth; + bpp = HVCFrame.frameInfo.nBitDepth; if ((bpp==10) && output_8bit) { bpp = 8; stride = HVCFrame.frameInfo.nYPitch/2; diff --git a/applications/testapps/loadcompare/Makefile b/applications/testapps/loadcompare/Makefile index 46b6d98..9d3c500 100644 --- a/applications/testapps/loadcompare/Makefile +++ b/applications/testapps/loadcompare/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/applications/testapps/loadcompare CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/applications/testapps/loadcompare/loadcompare.c b/applications/testapps/loadcompare/loadcompare.c index 5388ef1..62ef67d 100644 --- a/applications/testapps/loadcompare/loadcompare.c +++ b/applications/testapps/loadcompare/loadcompare.c @@ -59,7 +59,6 @@ GF_Err load_mp4(GF_LoadCompare *lc, GF_ISOFile *mp4, u32 *loadtime) { GF_Err e = GF_OK; GF_SceneLoader load; - GF_SceneGraph *sg; u32 i, starttime, endtime; u32 nb; if (lc->spread_repeat) nb = 1; @@ -67,6 +66,7 @@ GF_Err load_mp4(GF_LoadCompare *lc, GF_ISOFile *mp4, u32 *loadtime) *loadtime = 0; for (i = 0; i< nb; i++) { + GF_SceneGraph *sg; memset(&load, 0, sizeof(GF_SceneLoader)); sg = gf_sg_new(); load.ctx = gf_sm_new(sg); @@ -101,7 +101,6 @@ GF_Err gpacctx_load_file(GF_LoadCompare *lc, char *item_path, u32 *loadtime) { GF_Err e = GF_OK; GF_SceneLoader load; - GF_SceneGraph *sg; u32 i, starttime, endtime; u32 nb; @@ -111,6 +110,7 @@ GF_Err gpacctx_load_file(GF_LoadCompare *lc, char *item_path, u32 *loadtime) *loadtime = 0; for (i = 0; iverbose) fprintf(stdout, "Could not open file %s\n", mp4_path); e = GF_IO_ERR; } else { + GF_SceneGraph *sg; sg = gf_sg_new(); ctx = gf_sm_new(sg); memset(&load, 0, sizeof(GF_SceneLoader)); @@ -347,9 +347,7 @@ GF_Err decode_svg(GF_LoadCompare *lc, char *item_name, char *item_path, char *sv GF_Err libxml_load_svg(GF_LoadCompare *lc, char *item_path, u32 *loadtime) { GF_Err e = GF_OK; - GF_SceneGraph *sg; u32 i, starttime, endtime; - void *p; u32 nb; if (lc->spread_repeat) nb = 1; @@ -358,6 +356,8 @@ GF_Err libxml_load_svg(GF_LoadCompare *lc, char *item_path, u32 *loadtime) *loadtime = 0; for (i = 0; iverbose) fprintf(stdout, "Could not open file %s or %s\n", item_path, gz_path); e = GF_IO_ERR; } else { + char buffer[100]; while ((read = fread(buffer, 1, 100, file))) gzwrite(gz, buffer, read); gf_fclose(file); gzclose(gz); @@ -600,7 +600,6 @@ void usage() int main(int argc, char **argv) { u32 i; - char *arg; GF_LoadCompare lc; Bool single = 0; char *out = NULL; @@ -613,7 +612,7 @@ int main(int argc, char **argv) lc.out = stdout; for (i = 1; i < (u32) argc ; i++) { - arg = argv[i]; + char *arg = argv[i]; if (!stricmp(arg, "-out")) { out = argv[i+1]; i++; diff --git a/applications/testapps/mpedemux/Makefile b/applications/testapps/mpedemux/Makefile index 21b3b8e..e54df3e 100644 --- a/applications/testapps/mpedemux/Makefile +++ b/applications/testapps/mpedemux/Makefile @@ -4,22 +4,22 @@ vpath %.c $(SRC_PATH)/applications/test_apps/mpedemux CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif #file format is read-only -ifeq ($(GPACREADONLY), yes) +ifeq ($(GPACREADONLY),yes) CFLAGS+= -DGPAC_READ_ONLY endif -ifeq ($(DISABLE_SVG), yes) +ifeq ($(DISABLE_SVG),yes) CFLAGS+=-DGPAC_DISABLE_SVG endif diff --git a/applications/testapps/player_api/main.c b/applications/testapps/player_api/main.c index fc622c3..318a6d2 100644 --- a/applications/testapps/player_api/main.c +++ b/applications/testapps/player_api/main.c @@ -68,7 +68,7 @@ static Bool player_foreach(int argc, char **argv) if (!term) return GF_FALSE; - for (i=0; imaterial = gf_node_new(sg, TAG_MPEG4_Material2D); @@ -181,7 +181,7 @@ static GF_Node *add_transform2d(SVG2BIFS_Converter *converter, GF_Node *node) return (GF_Node *)tr; } -static void svg_parse_animation(GF_SceneGraph *sg, SVG_DeferedAnimation *anim) +static void svg_parse_animation(GF_SceneGraph *sg, SVG_DeferredAnimation *anim) { GF_FieldInfo info; u32 tag; @@ -280,7 +280,7 @@ static void svg2bifs_node_start(void *sax_cbck, const char *name, const char *na char *id_string = NULL; u32 tag; SVG_Element *elt; - SVG_DeferedAnimation *anim = NULL; + SVG_DeferredAnimation *anim = NULL; tag = gf_xml_get_element_tag(name, 0); elt = (SVG_Element*)gf_node_new(converter->svg_sg, tag); @@ -296,7 +296,7 @@ static void svg2bifs_node_start(void *sax_cbck, const char *name, const char *na // if (converter->bifs_parent) fprintf(stdout, "%s\n", gf_node_get_class_name(converter->bifs_parent)); if (gf_svg_is_animation_tag(tag)) { - GF_SAFEALLOC(anim, SVG_DeferedAnimation); + GF_SAFEALLOC(anim, SVG_DeferredAnimation); /*default anim target is parent node*/ anim->animation_elt = elt; if (converter->svg_parent) { @@ -532,7 +532,7 @@ static void svg2bifs_node_start(void *sax_cbck, const char *name, const char *na if (converter->all_atts.d) { M_Coordinate2D *c2d; M_XCurve2D *xc = (M_XCurve2D *)shape->geometry; - u32 i, j, c, k; + u32 j, c, k; xc->point = gf_node_new(converter->bifs_sg, TAG_MPEG4_Coordinate2D); c2d = (M_Coordinate2D *)xc->point; @@ -630,7 +630,6 @@ static void svg2bifs_node_start(void *sax_cbck, const char *name, const char *na if (converter->all_atts.points) { M_Coordinate2D *c2d; M_IndexedFaceSet2D *ifs = (M_IndexedFaceSet2D *)shape->geometry; - u32 i; ifs->coord = gf_node_new(converter->bifs_sg, TAG_MPEG4_Coordinate2D); c2d = (M_Coordinate2D *)ifs->coord; @@ -837,7 +836,7 @@ static void svg2bifs_node_start(void *sax_cbck, const char *name, const char *na M_PositionInterpolator2D *pi2d = (M_PositionInterpolator2D *)child; if (converter->all_atts.keyTimes) { SFFloat *g; - u32 count, i; + u32 count; count = gf_list_count(*converter->all_atts.keyTimes); for (i = 0; i < count; i++) { Fixed *f = gf_list_get(*converter->all_atts.keyTimes, i); @@ -847,7 +846,7 @@ static void svg2bifs_node_start(void *sax_cbck, const char *name, const char *na } if (converter->all_atts.values) { SFVec2f *g; - u32 count, i; + u32 count; count = gf_list_count(converter->all_atts.values->values); for (i = 0; i < count; i++) { SVG_Point_Angle *p; @@ -879,7 +878,7 @@ static void svg2bifs_node_start(void *sax_cbck, const char *name, const char *na M_ScalarInterpolator *si = (M_ScalarInterpolator *)child; if (converter->all_atts.keyTimes) { SFFloat *g; - u32 count, i; + u32 count; count = gf_list_count(*converter->all_atts.keyTimes); for (i = 0; i < count; i++) { Fixed *f = gf_list_get(*converter->all_atts.keyTimes, i); @@ -889,7 +888,7 @@ static void svg2bifs_node_start(void *sax_cbck, const char *name, const char *na } if (converter->all_atts.values) { SFFloat *g; - u32 count, i; + u32 count; count = gf_list_count(converter->all_atts.values->values); for (i = 0; i < count; i++) { SVG_Point_Angle *p; @@ -926,7 +925,7 @@ static void svg2bifs_node_start(void *sax_cbck, const char *name, const char *na M_PositionInterpolator2D *pi2d = (M_PositionInterpolator2D *)child; if (converter->all_atts.keyTimes) { SFFloat *g; - u32 count, i; + u32 count; count = gf_list_count(*converter->all_atts.keyTimes); for (i = 0; i < count; i++) { Fixed *f = gf_list_get(*converter->all_atts.keyTimes, i); @@ -936,7 +935,7 @@ static void svg2bifs_node_start(void *sax_cbck, const char *name, const char *na } if (converter->all_atts.values) { SFVec2f *g; - u32 count, i; + u32 count; count = gf_list_count(converter->all_atts.values->values); for (i = 0; i < count; i++) { SVG_Point *p; diff --git a/applications/ts2hds/Makefile b/applications/testapps/ts2hds/Makefile similarity index 92% rename from applications/ts2hds/Makefile rename to applications/testapps/ts2hds/Makefile index c379871..965762d 100644 --- a/applications/ts2hds/Makefile +++ b/applications/testapps/ts2hds/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/applications/ts2hds CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/applications/ts2hds/f4m.c b/applications/testapps/ts2hds/f4m.c similarity index 100% rename from applications/ts2hds/f4m.c rename to applications/testapps/ts2hds/f4m.c diff --git a/applications/ts2hds/f4v.c b/applications/testapps/ts2hds/f4v.c similarity index 89% rename from applications/ts2hds/f4v.c rename to applications/testapps/ts2hds/f4v.c index c507f48..2cb3a12 100644 --- a/applications/ts2hds/f4v.c +++ b/applications/testapps/ts2hds/f4v.c @@ -30,6 +30,10 @@ //we need to write Adobe custom boxes #include +extern struct box_registry_entry box_registry[]; +extern u32 get_box_reg_idx(u32 boxCode, u32 parent_type); +extern size_t size_registry(); + GF_Err adobize_segment(GF_ISOFile *isom_file, AdobeHDSCtx *ctx) { GF_Err e; @@ -54,6 +58,7 @@ GF_Err adobize_segment(GF_ISOFile *isom_file, AdobeHDSCtx *ctx) afra->type = GF_4CC('a', 'f', 'r', 'a'); afra->version = 0; afra->flags = 0; + afra->registry = (const struct box_registry_entry*)((u8*)& box_registry + size_registry() * get_box_reg_idx(afra->type, 0)); afra->long_ids = 1; afra->long_offsets = 1; afra->global_entries = 0; @@ -78,6 +83,7 @@ GF_Err adobize_segment(GF_ISOFile *isom_file, AdobeHDSCtx *ctx) abst->type = GF_4CC('a', 'b', 's', 't'); abst->version = 0; abst->flags = 0; + abst->registry = (const struct box_registry_entry*)((u8*)& box_registry + size_registry() * get_box_reg_idx(abst->type, 0)); abst->bootstrapinfo_version = 1; abst->profile = 0; abst->live = 1; @@ -99,6 +105,7 @@ GF_Err adobize_segment(GF_ISOFile *isom_file, AdobeHDSCtx *ctx) asrt->type = GF_4CC('a', 's', 'r', 't'); asrt->version = 0; asrt->flags = 0; + asrt->registry = (const struct box_registry_entry*)((u8*)& box_registry + size_registry() * get_box_reg_idx(asrt->type, 0)); asrt->segment_run_entry_count = 1; { asre->first_segment = ctx->segnum; @@ -124,6 +131,7 @@ GF_Err adobize_segment(GF_ISOFile *isom_file, AdobeHDSCtx *ctx) afrt->type = GF_4CC('a', 'f', 'r', 't'); afrt->version = 0; afrt->flags = 0; + afrt->registry = (const struct box_registry_entry*)((u8*)& box_registry + size_registry() * get_box_reg_idx(afrt->type, 0)); afrt->timescale = gf_isom_get_timescale(isom_file); afrt->fragment_run_entry_count = 1; { @@ -152,6 +160,7 @@ GF_Err adobize_segment(GF_ISOFile *isom_file, AdobeHDSCtx *ctx) return e; } + abst->size = 12; /*full box*/ e = abst_Size((GF_Box*)abst); if (e) { fprintf(stderr, "Impossible to compute ABST box size: %s\n", gf_error_to_string(e)); @@ -182,4 +191,4 @@ GF_Err adobize_segment(GF_ISOFile *isom_file, AdobeHDSCtx *ctx) gf_isom_modify_alternate_brand(isom_file, GF_4CC('m','4','v',' '), 1); return GF_OK; -} \ No newline at end of file +} diff --git a/applications/ts2hds/main.c b/applications/testapps/ts2hds/main.c similarity index 98% rename from applications/ts2hds/main.c rename to applications/testapps/ts2hds/main.c index 2298fe6..125751c 100644 --- a/applications/ts2hds/main.c +++ b/applications/testapps/ts2hds/main.c @@ -266,8 +266,7 @@ int main(int argc, char **argv) //interleave data and remove imported file //FIXME: set multiple fragments: sprintf(tmpstr, "%s_HD_100_Seg%u-Frag1", output, ctx.segnum); //FIXME: "HD", "100" and fragnum: pass as arg - //e = gf_media_fragment_file(isom_file_in, tmpstr, 1.0); - e = gf_media_fragment_file(isom_file_in, tmpstr, 1.0+gf_isom_get_duration(isom_file_in)/gf_isom_get_timescale(isom_file_in)); + e = gf_media_fragment_file(isom_file_in, tmpstr, 1.0+gf_isom_get_duration(isom_file_in)/gf_isom_get_timescale(isom_file_in), GF_FALSE); if (e) { fprintf(stderr, "Error while fragmenting file to output %s: %s\n", output, gf_error_to_string(e)); assert(0); diff --git a/applications/ts2hds/ts2hds.h b/applications/testapps/ts2hds/ts2hds.h similarity index 100% rename from applications/ts2hds/ts2hds.h rename to applications/testapps/ts2hds/ts2hds.h diff --git a/applications/testapps/ts2udp/main.c b/applications/testapps/ts2udp/main.c index 2c3a272..d02a043 100644 --- a/applications/testapps/ts2udp/main.c +++ b/applications/testapps/ts2udp/main.c @@ -78,7 +78,7 @@ u32 sender_thread_proc(void *param) { int main(int argc, char **argv) { u32 size = 0; - size_t fsize = 0, fdone = 0; + u64 fsize = 0, fdone = 0; FILE *src = NULL; GF_M2TS_Demuxer *ts = NULL; GF_Thread *th = NULL; @@ -138,7 +138,7 @@ int main(int argc, char **argv) gf_set_progress("MPEG-2 TS Processing", fsize, fsize); exit: - gf_fclose(src); + if(src) gf_fclose(src); gf_m2ts_demux_del(ts); gf_th_del(th); gf_sema_del(sender.sema); diff --git a/applications/udptsseg/Makefile b/applications/testapps/udptsseg/Makefile similarity index 93% rename from applications/udptsseg/Makefile rename to applications/testapps/udptsseg/Makefile index 25e8a45..7ba82cb 100644 --- a/applications/udptsseg/Makefile +++ b/applications/testapps/udptsseg/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/applications/udptsseg CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif @@ -42,7 +42,7 @@ ifeq ($(CONFIG_JS),no) else SCENEGRAPH_CFLAGS+=$(JS_FLAGS) ifeq ($(CONFIG_JS),local) -NEED_LOCAL_LIB="yes" +NEED_LOCAL_LIB=yes endif LINKFLAGS+=$(JS_LIBS) endif diff --git a/applications/udptsseg/main.c b/applications/testapps/udptsseg/main.c similarity index 99% rename from applications/udptsseg/main.c rename to applications/testapps/udptsseg/main.c index 5074095..7c87714 100644 --- a/applications/udptsseg/main.c +++ b/applications/testapps/udptsseg/main.c @@ -115,7 +115,6 @@ int main(int argc, char **argv) u32 segment_index = 0; u32 segment_number = 0; char segment_manifest_default[GF_MAX_PATH]; - u32 run = 1; u32 last_segment_time = 0; u32 last_segment_size = 0; u32 read = 0; @@ -228,7 +227,7 @@ int main(int argc, char **argv) /*****************/ last_segment_time = gf_sys_clock(); last_segment_size = 0; - while (run) { + while (1) { /*check for some input from the network*/ if (input_ip) { gf_sk_receive(input_udp_sk, input_buffer+leftinbuffer, input_buffer_size-leftinbuffer, 0, &read); @@ -236,8 +235,7 @@ int main(int argc, char **argv) if (leftinbuffer) { fprintf(stderr, "Processing %s segment ... received %d bytes (buffer: %d, segment: %d)\n", segment_name, read, leftinbuffer, last_segment_size); if (input_buffer[0] != 0x47) { - u32 i = 0; - while (input_buffer[i] != 0x47 && i < leftinbuffer) i++; + while ((i < leftinbuffer) && (input_buffer[i] != 0x47)) i++; fprintf(stderr, "Warning: data in buffer not starting with the MPEG-2 TS sync byte, skipping %d bytes of %d\n", i, leftinbuffer); if (i < leftinbuffer) memmove(input_buffer, input_buffer+i, leftinbuffer-i); leftinbuffer -=i; diff --git a/applications/udptsseg/udptsseg.dsp b/applications/testapps/udptsseg/udptsseg.dsp similarity index 100% rename from applications/udptsseg/udptsseg.dsp rename to applications/testapps/udptsseg/udptsseg.dsp diff --git a/applications/udptsseg/udptsseg.vcproj b/applications/testapps/udptsseg/udptsseg.vcproj similarity index 100% rename from applications/udptsseg/udptsseg.vcproj rename to applications/testapps/udptsseg/udptsseg.vcproj diff --git a/change_version.sh b/change_version.sh index 0301f7c..821c387 100755 --- a/change_version.sh +++ b/change_version.sh @@ -14,18 +14,6 @@ sed -e "s/README for GPAC version.*/README for GPAC version $version/;" $source rm $source mv ftmp $source -#patch doc/configuration.html -source="doc/configuration.html" -sed -e "s/GPAC Version.*/GPAC Version $version/;" $source > ftmp -rm $source -mv ftmp $source - -#patch doc/man/gpac.1 -source="doc/man/gpac.1" -sed -e "s/GPAC framework version.*/GPAC framework version $version./;" $source > ftmp -rm $source -mv ftmp $source - #patch applications/osmo4_ios/osmo4ios-Info.plist @@ -34,26 +22,6 @@ sed -e "/CFBundleShortVersionString/{n;s/.*/ $version<\/string>/;}" $sou rm $source mv ftmp $source -# patch applications/osmo4_w32/Osmo4.rc - -source="applications/osmo4_w32/Osmo4.rc" -sed -e "/\"FileDescription\", \"Osmo4-GPAC\"/{n;s/.*/ VALUE \"FileVersion\", \"$version\"/;}" $source | sed -e "/\"ProductName\", \"Osmo4-GPAC\"/{n;s/.*/ VALUE \"ProductVersion\", \"$version\"/;}" > ftmp -rm $source -mv ftmp $source - -# patch bin/smartphone 2003 (armv4)/release/install/archive.bat -source="bin/smartphone 2003 (armv4)/release/install/archive.bat" -sed -e "s/set gpac_version=.*/set gpac_version=\"$version-r%gpac_revision%/;" "$source" > ftmp -rm "$source" -mv ftmp "$source" - - -# patch bin/smartphone 2003 (armv4)/release/install/build_installer.bat -source="bin/smartphone 2003 (armv4)/release/install/build_installer.bat" -sed -e "s/set gpac_version=.*/set gpac_version=\"$version-r%gpac_revision%/;" "$source" > ftmp -rm "$source" -mv ftmp "$source" - # patch file gpac.pc source="gpac.pc" sed -e "s/Version:.*/Version:$version/;" $source > ftmp @@ -77,3 +45,9 @@ source="packagers/win32_64/nsis/gpac_installer.nsi" sed -e "s/\!define GPAC_VERSION.*/\!define GPAC_VERSION $version/;" $source > ftmp rm $source mv ftmp $source + +# patch file share/doc/configuration.html +source="share/doc/configuration.html" +sed -e "s/GPAC Version.* ftmp +rm $source +mv ftmp $source diff --git a/configure b/configure index 485bfe2..de8ea99 100755 --- a/configure +++ b/configure @@ -1,12 +1,11 @@ #!/bin/sh # -# GPAC configure script -# (c) 2003-2012 Telecom ParisTech +# GPAC configure script +# (c) 2003-2018 Telecom ParisTech # Authors: Jean Le Feuvre, Romain Bouqueau # #set -v - #set temporary file name if test ! -z "$TMPDIR" ; then TMPDIR1="${TMPDIR}" @@ -68,8 +67,6 @@ libdir="lib" #GPAC module config static_modules="no" -js_flags="XP_UNIX" -js_lib="-ljs" lm_lib="" has_mingw_directx="no" has_js="no" @@ -105,14 +102,15 @@ enable_tinygl="no" has_ssl="no" has_ipv6="no" has_dvb4linux="no" -has_xmlrpc="no" has_openjpeg="no" gprof_build="no" static_build="no" want_pic="no" want_gcov="no" has_joystick="no" -has_xul="no" +has_hid="no" +has_sock_un="no" +has_lzma="no" enable_joystick="no" static_mp4box="no" disable_core_tools="no" @@ -146,7 +144,7 @@ disable_loader_xmt="no" disable_od_dump="no" disable_od_parse="no" disable_isom_dump="no" -disable_mcrypt="no" +disable_crypto="no" disable_mpd="no" disable_dash="no" disable_isoff="no" @@ -163,13 +161,18 @@ disable_vobsub="no" disable_ttxt="no" disable_ttml="no" disable_hevc="no" +disable_atsc="no" +disable_crypto="no" +enable_qjs="yes" enable_depth_compositor="no" enable_renoir="no" +enable_sanitizer="no" has_avcap="no" avcap_cflags="" avcap_ldflags="-lavcap" has_opensvc="no" has_openhevc="no" +has_vtb="no" win32="no" mingw32="no" @@ -209,7 +212,7 @@ Options: [defaults in brackets after descriptions] GPAC configuration options: --help print this message --prefix=PREFIX install in PREFIX [$prefix] - --mandir=DIR man documentation in DIR [PREFIX/man] + --mandir=DIR man documentation in DIR [PREFIX/share/man] --verbose enable verbose building [$verbose] --source-path=PATH path of source code [$source_path] @@ -238,9 +241,10 @@ GPAC configuration options: --std-allocator uses standard lib memory allocator --static-modules includes static modules in libgpac whenever possible --enable-mem-track enable tracking of all memory allocated by gpac + --enable-sanitizer enable adress sanitizer + --enable-afl enable instrumentation for American Fuzzy Lop --disable-opt disable GCC optimizations --disable-ipv6 disable IPV6 support - --disable-wx disable wxWidgets support --disable-platinum disable Platinum UPnP support --disable-alsa disable Alsa audio --disable-oss-audio disable OSS audio @@ -294,7 +298,7 @@ Configuration options for libgpac - all options can be enabled with --enable-opt --disable-loader-xmt disable scene loading from ISO File Format --disable-od-dump disable OD dump --disable-isom-dump disable ISOM dump - --disable-mcrypt disable mcrypt + --disable-crypt disable crypto tools --disable-isoff disable ISO File Format --disable-isoff-write disable ISO File Format edit/write --disable-isoff-hint disable ISO File Format hinting @@ -308,9 +312,10 @@ Configuration options for libgpac - all options can be enabled with --enable-opt --disable-player disable player (terminal and compositor) --disable-scenegraph disable scenegraph, scene parsers and player (terminal and compositor) --disable-hevc disable HEVC support + --disable-atsc disable ATSC3 support + --disable-crypto disable crypto support -Extra libraries configuration. You can turn a libray off or force using the local version in gpac/extra_lib/ - --use-js=OPT force SpiderMonkey ECMAScript OPT=[no,local] +Extra libraries configuration. You can turn a library off or force using the local version in gpac/extra_lib/ --use-ft=OPT force FreeType OPT=[no,local] --use-zlib=OPT force ZLIB OPT=[no,system,local] --use-jpeg=OPT force JPEG OPT=[no,local] @@ -325,7 +330,7 @@ Extra libraries configuration. You can turn a libray off or force using the loca --use-openjpeg=OPT force openjpeg OPT=[no,system,local] --use-a52=OPT force a52 (ac3) OPT=[no,system,local] -NOTE: The object files are build at the place where configure is launched +NOTE: The object files are built at the place where configure is launched EOF exit 1 fi @@ -368,6 +373,8 @@ for opt do ;; --enable-gcov) want_gcov="yes"; ;; + --enable-afl) cc_orig="afl-gcc"; cxx_orig="afl-g++" + ;; --verbose) verbose="yes"; ;; esac @@ -414,7 +421,7 @@ case "$cpu" in ;; ppc64) cpu="powerpc" - ;; + ;; "Power Macintosh"|ppc) cpu="powerpc" ;; @@ -481,8 +488,6 @@ if test "$targetos" = "" ; then fi case $targetos in BeOS) - js_flags=-DXP_BEOS - xul_flags=-DXP_BEOS prefix="/boot/home/config" CFLAGS="$CFLAGS -DPIC -fomit-frame-pointer" # 3 gcc releases known for BeOS, each with ugly bugs @@ -539,7 +544,6 @@ EOF CFLAGS="$CFLAGS -pthread" GPAC_SH_FLAGS=-pthread freebsd="yes" - js_flags="-DXP_UNIX -I/usr/include/js" ;; BSD/OS) @@ -548,10 +552,8 @@ EOF ;; Darwin) - js_flags=-DXP_MAC - xul_flags=-DXP_MAC CFLAGS_DIR="-I$prefix/include" - LDFLAGS="-L$prefix/lib" + LDFLAGS="-L$prefix/$libdir" if test -d /sw/bin ; then alt_macosx_dir="/sw" CFLAGS_DIR="-I/sw/include $CFLAGS_DIR" @@ -573,20 +575,19 @@ EOF darwin="yes" gcc_version=`$cc -v 2>>$logs | grep version | cut -d ' ' -f3` case "$gcc_version" in - *2.95*) + *2.95*) CFLAGS="$CFLAGS -no-cpp-precomp -pipe -fomit-frame-pointer" ;; 3.*) CFLAGS="$CFLAGS -no-cpp-precomp -pipe -fomit-frame-pointer -mdynamic-no-pic -fno-common" ;; - 4.*) + 4.*) CFLAGS="$CFLAGS -pipe -fomit-frame-pointer -fno-common" ;; esac ;; MINGW32*|mingw32|MINGW64*|mingw64|msys*|MSYS*) - js_flags="-DXP_PC -D_declspec=__declspec" mingw32="yes" win32="yes" want_pic="no" @@ -610,14 +611,12 @@ EOF ;; CYGWIN*|cygwin*) - js_flags=-DXP_PC extralibs="$extralibs -lws2_32 -lwinmm" cygwin="yes" win32="yes" ;; Linux|linux|android) - js_flags="-DXP_UNIX -I/usr/include/js" LDFLAGS="$LDFLAGS -Wl,--warn-common -Wl,-z,defs" linux="yes" case "$cpu" in @@ -643,7 +642,7 @@ esac #defines directory for binaries and libs (ex. for TinyGL) -target_bin_dir="" +target_bin_dir="" if test "$cross_prefix" = "" ; then target_bin_dir=`${cc} -v 2>>$logs | sed -n '2p' | awk ' {print $2}'`-${cc_orig} else @@ -660,7 +659,8 @@ mkdir -p extra_lib/lib/gcc #OK check for all local & systems lib local_inc=$source_path/extra_lib/include -local_lib=extra_lib/lib/gcc +local_lib=$source_path/extra_lib/lib/gcc +execdir=$source_path/bin/gcc dolog() { @@ -687,7 +687,7 @@ docc() { docxx() { - $cc -o $TMPO $TMPC $@ 0>/dev/null 2>$TMPL + $cc -o $TMPO $TMPCXX $@ 0>/dev/null 2>$TMPL dolog $@ } @@ -777,247 +777,15 @@ fi -#look for spidermonkey JS support -mozjs_pkgcfg="no" - -#spidermonkey test for new API -if test "$has_js" = "no" ; then - -cat > $TMPC << EOF -#include -int main( void ) { JSContext *cx=NULL; jsval rp; return JS_AddValueRoot(cx, &rp); } -EOF - - #try local - js_inc="$local_inc/js" - js_flags="-DXP_UNIX -I$local_inc/js" - if test "$pkg_config" != "no"; then - #try pkg-config - if $pkg_config --exists mozilla-js ; then - mozjs_pkgcfg="mozilla-js" - elif $pkg_config --exists mozjs ; then - mozjs_pkgcfg="mozjs" - elif $pkg_config --exists mozjs185 ; then - mozjs_pkgcfg="mozjs185" - elif docc $js_flags -L$local_lib -ljs -lpthread ; then - has_js="local" - #dc added - fi - - if test $mozjs_pkgcfg != "no" ; then - js_flags=`$pkg_config --cflags $mozjs_pkgcfg` - js_lib_pkg=`$pkg_config --libs $mozjs_pkgcfg` - if docc $js_flags $js_lib_pkg $LDFLAGS -lpthread ; then - has_js="system" - js_lib=`$pkg_config --libs $mozjs_pkgcfg` - fi - #try firefox folders (starting at ubuntu 11.10, no pkg-config) - elif ls -d /usr/lib/firefox* > /dev/null 2>>$logs ; then - firefox_version=`cd /usr/lib ; ls -d firefox* | grep -v addons | grep -v devel ; cd - > /dev/null` - for i in $firefox_version ; do - if test "$has_js" = "no" ; then - js_inc="/usr/include/$i" - js_flags="-DXP_UNIX -I$js_inc" - js_lib="-L/usr/lib/$i/ -lxul -lmozsqlite3 -lmozalloc -lnssutil3 -lnss3 -lnspr4 -lsmime3" - if docc $js_flags $js_lib ; then - has_js="$i" - elif docc $js_flags -lnssutil3 $js_lib -lssl3 ; then - #firefox 11 compatibility - has_js="$i" - js_lib="-lnssutil3 $js_lib -lssl3" - fi - fi - done - fi - - if test "$has_js" = "no" ; then - #try prefix (DC) - js_inc="$prefix/include/js" - js_flags="-DXP_UNIX -I$prefix/include/js" - if docc $js_flags -L$prefix/lib -ljs -lpthread ; then - has_js="prefix" - #dc added end - else - if docc $js_flags $LDFLAGS -ljs -lpthread ; then - js_inc="/usr/include" - has_js="system" - elif docc -DXP_UNIX -I$alt_macosx_dir/include/js -L$alt_macosx_dir/lib $LDFLAGS -ljs -lpthread 2>>$logs ; then - has_js="system" - js_flags="-DXP_UNIX -I$alt_macosx_dir/include/js" - js_lib="-L$alt_macosx_dir/lib -ljs" - js_inc="$alt_macosx_dir/include/js" - else - #debian spidermonkey (smjs) - js_flags="-DXP_UNIX -I/usr/include/smjs" - js_inc="/usr/include/smjs" - if docc $js_flags $LDFLAGS -lsmjs -lpthread ; then - has_js="system" - js_lib="-lsmjs" - else - #debian spidermonkey (mozjs) - js_flags="-DXP_UNIX -I/usr/include/mozjs" - js_inc="/usr/include/mozjs" - if docc $js_flags $LDFLAGS -lmozjs -lpthread ; then - has_js="system" - js_lib="-lmozjs" - fi - fi - fi - fi - fi - fi -fi - -new_js_api="no" -if test "$has_js" != "no" ; then - js_flags="-DSPIDERMONKEY_NEW_API $js_flags" - new_js_api="yes" -fi - -#spidermonkey test for regular API -if test "$has_js" = "no" ; then - -cat > $TMPC << EOF -#include -int main( void ) { return 0; } -EOF - - #try local - js_inc="$local_inc/js" - js_flags="-DXP_UNIX -I$local_inc/js" - if test "$cpu" = "sh4" ; then - lm_lib="-lm" - fi - if docc $js_flags $lm_lib -L$local_lib -ljs ; then - has_js="local" - #dc added - else - #try prefix (DC) - js_inc="$prefix/include/js" - js_flags="-DXP_UNIX -I$prefix/include/js" - if docc $js_flags -L$prefix/lib -ljs ; then - has_js="prefix" - #dc added end - elif test "$pkg_config" != "no"; then - if $pkg_config --exists mozilla-js ; then - mozjs_pkgcfg="mozilla-js" - elif $pkg_config --exists mozjs ; then - mozjs_pkgcfg="mozjs" - elif $pkg_config --exists mozjs185 ; then - mozjs_pkgcfg="mozjs185" - fi - - if test $mozjs_pkgcfg != "no" ; then - js_flags=`$pkg_config --cflags $mozjs_pkgcfg` - js_lib_pkg=`$pkg_config --libs $mozjs_pkgcfg` - if docc $js_flags $js_lib_pkg $LDFLAGS -lpthread ; then - has_js="system" - js_lib=`$pkg_config --libs $mozjs_pkgcfg` - fi - fi - fi - if test "$has_js" = "no" ; then - if docc $js_flags $LDFLAGS -ljs ; then - js_inc="/usr/include" - has_js="system" - elif docc -DXP_UNIX -I$alt_macosx_dir/include/js -L$alt_macosx_dir/lib $LDFLAGS -ljs 2>>$logs ; then - has_js="system" - js_flags="-DXP_UNIX -I$alt_macosx_dir/include/js" - js_lib="-L$alt_macosx_dir/lib -ljs" - js_inc="$alt_macosx_dir/include/js" - else - #debian spidermonkey (smjs) - js_flags="-DXP_UNIX -I/usr/include/smjs" - js_inc="/usr/include/smjs" - if docc $js_flags $LDFLAGS -lsmjs ; then - has_js="system" - js_lib="-lsmjs" - else - #debian spidermonkey (mozjs) - js_flags="-DXP_UNIX -I/usr/include/mozjs" - js_inc="/usr/include/mozjs" - if docc $js_flags $LDFLAGS -lmozjs ; then - has_js="system" - js_lib="-lmozjs" - fi - fi - fi - fi - fi -fi - -if test "$has_js" != "no" ; then - if test "$linux" = "yes" ; then - if test "$cpu" != "sh4"; then - #WARNING: there is a bug in MOZJS packages, the MOZILLA_1_8_BRANCH macro is not signaled, there is no way of knowing - #if the lib has been compiled with or without the macro. We currently just decide that if the macro is present - #in the header, it was enabled in the build - if test "$new_js_api" = "no" ; then - if grep MOZILLA_1_8_BRANCH $js_inc/jsapi.h > /dev/null 2>>$logs ; then - js_flags="-DMOZILLA_1_8_BRANCH $js_flags" - echo "WARNING: Turning on MOZILLA_1_8_BRANCH SpiderMonkey macro" - echo "If you have troubles with scripts in GPAC, disable this macro and recompile" - fi - else - -cat > $TMPC << EOF -#include -int main( void ) { JSObject *obj; JS_GetPrivate(obj); return 0; } -EOF - if docc $js_flags $LDFLAGS $js_lib ; then - -cat > $TMPC << EOF -#include -int main( void ) { jsval vp; JSContext *cx=NULL; JSObject *obj = JS_NewObjectForConstructor(cx, &vp); return 0; } -EOF - if docc $js_flags $LDFLAGS $js_lib ; then - js_flags="-DUSE_FFDEV_12 $js_flags" - elif grep JSMutableHandleValue $js_inc/jsapi.h | grep JSHasInstanceOp > /dev/null 2>>$logs ; then - js_flags="-DUSE_FFDEV_18 $js_flags" - elif grep JSMutableHandleValue $js_inc/jsapi.h > /dev/null 2>>$logs ; then - js_flags="-DUSE_FFDEV_18 $js_flags" - elif ! grep JS_ConstructObject $js_inc/jsapi.h > /dev/null 2>>$logs ; then - js_flags="-DUSE_FFDEV_16 $js_flags" - elif grep JSHandleObject $js_inc/jsapi.h > /dev/null 2>>$logs ; then - js_flags="-DUSE_FFDEV_15 $js_flags" - else - js_flags="-DUSE_FFDEV_14 $js_flags" - fi - fi - fi - fi - fi -fi - - - -if test "$has_js" != "no" ; then -cat > $TMPC << EOF -#include -int main( void ) { JSContext *cx=NULL; JS_SetRuntimeThread(cx); return 0;} -EOF - if docc $js_flags $LDFLAGS $js_lib ; then - js_flags="$js_flags" - else - js_flags="-DNO_JS_RUNTIMETHREAD $js_flags" - fi -fi - - -#end JS test - - - #look for platinum support -cat > $TMPC << EOF +cat > $TMPCXX << EOF #include int main( void ) { return 0; } EOF -if docxx -o $TMPO $TMPC -I$local_inc/platinum $LDFLAGS -L$local_lib -lPlatinum -lPltMediaServer -lPltMediaConnect -lPltMediaRenderer -lNeptune -lZlib -lpthread ; then +if docxx -I$local_inc/platinum $LDFLAGS -L$local_lib -lPlatinum -lPltMediaServer -lPltMediaConnect -lPltMediaRenderer -lNeptune -lZlib -lpthread ; then has_platinum="yes" fi - #look for avcap support avcap_cflags="" avcap_ldflags="-lavcap" @@ -1025,14 +793,14 @@ avcap_ldflags="-lavcap" cat > $TMPC << EOF #include using namespace avcap; -int main( void ) { +int main( void ) { const DeviceCollector::DeviceList& dl = DEVICE_COLLECTOR::instance().getDeviceList(); DeviceDescriptor* dd = 0; for (DeviceCollector::DeviceList::const_iterator i = dl.begin(); i != dl.end(); i++) { dd = *i; std::cout << dd->getName().c_str() << "\n"; } - return 0; + return 0; } EOF if docxx -o $TMPO $TMPC $LDFLAGS $avcap_cflags $avcap_ldflags ; then @@ -1047,7 +815,7 @@ else fi if docxx -o $TMPO $TMPC $avcap_cflags $LDFLAGS -L$local_lib $avcap_ldflags ; then has_avcap="yes" - avcap_ldflags="-L../../$local_lib $avcap_ldflags" + avcap_ldflags="-L$local_lib $avcap_ldflags" fi fi @@ -1069,12 +837,12 @@ EOF if docc $osvc_cflags $LDFLAGS $osvc_ldflags ; then has_opensvc="yes" else -osvc_cflags="-I$local_inc" +osvc_cflags="-idirafter $local_inc" osvc_ldflags="-lOpenSVCDec" if docc $osvc_cflags $LDFLAGS -L$local_lib $osvc_ldflags ; then has_opensvc="yes" - osvc_ldflags="-L../../$local_lib $osvc_ldflags" + osvc_ldflags="-L$local_lib $osvc_ldflags" fi fi @@ -1083,32 +851,34 @@ fi if test "$darwin" = "yes" ; then ohevc_cflags="-I/usr/include -I/usr/local/include" - ohevc_ldflags="-L/usr/lib -L/usr/local/lib -lLibOpenHevcWrapper -lm -lpthread " + ohevc_ldflags="-L/usr/lib -L/usr/local/lib -lopenhevc -lm -lpthread " elif test "$cross_prefix" = "" ; then ohevc_cflags="-I/usr/include -I/usr/local/include" - ohevc_ldflags="-L/usr/lib -L/usr/local/lib -lLibOpenHevcWrapper -lm -lpthread" + ohevc_ldflags="-L/usr/lib -L/usr/local/lib -lopenhevc -lm -lpthread" else ohevc_cflags="-I${prefix}include" - ohevc_ldflags="-lLibOpenHevcWrapper -lm -lpthread" + ohevc_ldflags="-lopenhevc -lm -lpthread" fi cat > $TMPC << EOF #include -#include -int main( void ) { libOpenHevcInit(1, 1); return 0; } +#include +int main( void ) { oh_init(1, 1); return 0; } EOF if docc $ohevc_cflags $ohevc_ldflags $LDFLAGS ; then has_openhevc="yes" else ohevc_cflags="-I$local_inc" - ohevc_ldflags="-lLibOpenHevcWrapper -lm -lpthread" + ohevc_ldflags="-lopenhevc -lm -lpthread" if docc $ohevc_cflags $ohevc_ldflags $LDFLAGS -L$local_lib ; then has_openhevc="yes" - ohevc_ldflags="-L../../$local_lib $ohevc_ldflags" + ohevc_ldflags="-L$local_lib $ohevc_ldflags" + elif docc $ohevc_cflags $ohevc_ldflags $LDFLAGS -L$execdir ; then + has_openhevc="yes" + ohevc_ldflags="-L$execdir $ohevc_ldflags" fi fi - #look for freetype support cat > $TMPC << EOF #include @@ -1118,32 +888,29 @@ cat > $TMPC << EOF int main( void ) { return 0; } EOF ft_cflags="-I$prefix/include " -ft_lflags="-L$prefix/lib -lfreetype" +ft_lflags="-L$prefix/$libdir -lfreetype" if docc $CFLAGS_DIR $ft_cflags $ft_lflags $LDFLAGS ; then has_ft="system" fi -if test "$cross_prefix" = "" ; then - if test "$has_ft" = "no" ; then - ft_cflags="`freetype-config --cflags 2>>$logs`" - ft_lflags="`freetype-config --libs 2>>$logs`" - if docc $ft_cflags $ft_lflags $LDFLAGS ; then - has_ft="system" - fi + +if test "$has_ft" = "no" ; then + ft_cflags="`pkg-config --cflags freetype2 2>>$logs`" + ft_lflags="`pkg-config --libs freetype2 2>>$logs`" + if docc $ft_cflags $ft_lflags $LDFLAGS ; then + has_ft="system" fi fi + if test "$has_ft" = "no" ; then - if test "`which freetype-config 2>>$logs`" != ""; then - ft_cflags="-I$local_inc/freetype" - ft_lflags="-L$local_lib -lfreetype" - if docc $ft_cflags $ft_lflags ; then - has_ft="local" - fi + ft_cflags="-I$local_inc/freetype" + ft_lflags="-L$local_lib -lfreetype" + if docc $ft_cflags $ft_lflags ; then + has_ft="local" fi fi #end freetype test - #look for OpenSSL support cat > $TMPC << EOF #include @@ -1166,6 +933,48 @@ if docc $CFLAGS_DIR $LINK_SSL $LDFLAGS ; then fi +#look for atomic.h +cat > $TMPC << EOF +#include +#include +int main( void ) { return 0; } +EOF + +has_atomic="no" +if docc $CFLAGS_DIR $LDFLAGS ; then + has_atomic="yes" +else + CFLAGS="$CFLAGS -DGPAC_NO_STDATOMIC" +fi + + +cat > $TMPC << EOF +#include +int main(void) { + int i4 = 4; + __int64_t i8 = 8; + __sync_add_and_fetch(&i4, 12); + __sync_add_and_fetch(&i8, 12); +} +EOF +has_builtinatomic="no" +if docc ; then + has_builtinatomic="yes" +else +cat > $TMPC << EOF +#include +int main(void) { + int i4 = 4; + __int64_t i8 = 8; + __atomic_add_fetch(&i4, 12, __ATOMIC_SEQ_CST); + __atomic_add_fetch(&i8, 12, __ATOMIC_SEQ_CST); +} +EOF + if docc -latomic ; then + CFLAGS="$CFLAGS -DGPAC_NEED_LIBATOMIC" + LDFLAGS="$LDFLAGS -latomic" + fi +fi #look for JPEG support cat > $TMPC << EOF @@ -1191,7 +1000,7 @@ if test "$cross_prefix" = "" ; then fi else jpeg_cflags="-I$prefix/include" - jpeg_lflags="-L$prefix/lib -ljpeg" + jpeg_lflags="-L$prefix/$libdir -ljpeg" if docc $jpeg_cflags $jpeg_lflags $LDFLAGS ; then has_jpeg="system" fi @@ -1209,6 +1018,20 @@ fi #look for OpenJPEG support + +if test "$cross_prefix" = "" -a "$pkg_config" != "no"; then + if $pkg_config --exists libopenjp2 ; then + has_openjpeg="system" + openjpeg_cflags=`$pkg_config --cflags libopenjp2` + openjpeg_ldflags=`$pkg_config --libs libopenjp2` + elif $pkg_config --exists libopenjpeg ; then + has_openjpeg="system" + openjpeg_cflags=`$pkg_config --cflags libopenjpeg` + openjpeg_ldflags=`$pkg_config --libs libopenjpeg` + fi +fi + +if test "$has_openjpeg" = "no" ; then cat > $TMPC << EOF #include #include @@ -1217,12 +1040,14 @@ EOF if docc $LDFLAGS -lopenjpeg ; then has_openjpeg="system" + openjpeg_ldflags="-lopenjpeg" fi if test "$cross_prefix" = "" ; then if test "$has_openjpeg" = "no" ; then if test "$alt_macosx_dir" != "" ; then - if cc -o $TMPO $TMPC -I$alt_macosx_dir/include -L$alt_macosx_dir/lib $LDFLAGS -ljpeg 2>>$logs ; then + if cc -o $TMPO $TMPC -I$alt_macosx_dir/include -L$alt_macosx_dir/lib $LDFLAGS -lopenjpeg 2>>$logs ; then has_openjpeg="system" + openjpeg_ldflags="-lopenjpeg" fi fi fi @@ -1231,9 +1056,11 @@ fi if test "$has_openjpeg" = "no" ; then if docc -I$local_inc/openjpeg -L$local_lib -lopenjpeg ; then has_openjpeg="local" + openjpeg_ldflags="-lopenjpeg" fi fi +fi #look for PNG support @@ -1243,7 +1070,7 @@ int main( void ) { return 0; } EOF png_cflags="-I$prefix/include" -png_lflags="-L$prefix/lib -lpng -lz" +png_lflags="-L$prefix/$libdir -lpng -lz" if docc $png_cflags $png_lflags $LDFLAGS ; then has_png="system" elif docc $LDFLAGS -lpng -lz ; then @@ -1285,7 +1112,7 @@ if test "$cross_prefix" = "" ; then fi fi if test "$has_mad" = "no" ; then - if docc -I$local_inc/mad -L$local_lib -lmad ; then + if docc -I$local_inc -L$local_lib -lmad ; then has_mad="local" fi fi @@ -1328,7 +1155,7 @@ cat > $TMPC << EOF int main( void ) { return 0; } EOF -if docc -I$prefix/include -L$prefix/lib $LDFLAGS -lxvidcore -lpthread ; then +if docc -I$prefix/include -L$prefix/$libdir $LDFLAGS -lxvidcore -lpthread ; then has_xvid="system" elif docc $LDFLAGS -lxvidcore -lpthread ; then has_xvid="system" @@ -1343,7 +1170,7 @@ if test "$cross_prefix" = "" ; then fi fi if test "$has_xvid" = "no" ; then - if docc -I$local_inc/xvid -L$local_lib -lxvidcore -lpthread ; then + if docc -I$local_inc -L$local_lib -lxvidcore -lpthread ; then has_xvid="local" fi fi @@ -1369,7 +1196,7 @@ if test "$cross_prefix" = "" ; then fi fi if test "$has_faad" = "no" ; then - if docc -I$local_inc/faad -L$local_lib -lfaad -lm ; then + if docc -I$local_inc -L$local_lib -lfaad -lm ; then has_faad="local" fi fi @@ -1379,14 +1206,12 @@ fi #look for FFMPEG support ffmpeg_cflags="" -ffmpeg_lflags="-lz -lavcodec -lavformat -lavutil -lswscale $ffmpeg_extra_ldflags" -ffmpeg_lflags_dashcast="$ffmpeg_lflags -lavdevice" +ffmpeg_lflags="-lz -lavcodec -lavformat -lavutil -lavdevice -lswscale -lswresample -lavfilter $ffmpeg_extra_ldflags" if test "$cross_prefix" = "" -a "$pkg_config" != "no"; then - if $pkg_config --exists libavcodec libavformat libswscale libavdevice libavutil libavresample ; then - ffmpeg_cflags=`$pkg_config --cflags libavcodec libavformat libswscale libavutil libavdevice libavresample` - ffmpeg_lflags=`$pkg_config --libs libavcodec libavformat libswscale libavutil` - ffmpeg_lflags_dashcast=`$pkg_config --libs libavcodec libavformat libswscale libavutil libavdevice` + if $pkg_config --exists libavcodec libavformat libswscale libavdevice libavutil libswresample libavfilter ; then + ffmpeg_cflags=`$pkg_config --cflags libavcodec libavformat libavutil libavdevice libswscale libswresample libavfilter` + ffmpeg_lflags=`$pkg_config --libs libavcodec libavformat libavutil libavdevice libswscale libswresample libavfilter` has_ffmpeg="system" fi fi @@ -1403,6 +1228,7 @@ if docc $ffmpeg_cflags $ffmpeg_lflags $LDFLAGS ; then else old_ffmpeg_inc="yes" +# this is immediatly overwritten below?? cat > $TMPC << EOF #include #include @@ -1429,11 +1255,10 @@ int main(void) { } EOF -if docc -I$prefix/include -L$prefix/lib $ffmpeg_lflags $LDFLAGS ; then +if docc -I$prefix/include -L$prefix/$libdir $ffmpeg_lflags $LDFLAGS ; then has_ffmpeg="system" ffmpeg_cflags="-I$prefix/include" - ffmpeg_lflags="-L$prefix/lib $ffmpeg_lflags" - ffmpeg_lflags_dashcast="-L$prefix/lib $ffmpeg_lflags_dashcast" + ffmpeg_lflags="-L$prefix/$libdir $ffmpeg_lflags" elif docc $ffmpeg_lflags $LDFLAGS ; then has_ffmpeg="system" fi @@ -1444,7 +1269,6 @@ if test "$cross_prefix" = "" ; then has_ffmpeg="system" ffmpeg_cflags="-I$alt_macosx_dir/include" ffmpeg_lflags="-L$alt_macosx_dir/lib $ffmpeg_lflags" - ffmpeg_lflags_dashcast="-L$alt_macosx_dir/lib $ffmpeg_lflags_dashcast" fi fi fi @@ -1454,10 +1278,25 @@ if test "$has_ffmpeg" = "no" ; then has_ffmpeg="local" ffmpeg_cflags="-I$local_inc" ffmpeg_lflags="-L$local_lib $ffmpeg_lflags" - ffmpeg_lflags_dashcast="-L$local_lib $ffmpeg_lflags_dashcast" fi fi + +#now that cflags has been correctly set, retest +cat > $TMPC << EOF +#include +int main(void) { + return 0; +} +EOF + +if docc $ffmpeg_cflags $ffmpeg_lflags $LDFLAGS ; then + old_ffmpeg_inc="no" +else + old_ffmpeg_inc="yes" +fi + + cat > $TMPC << EOF #include #include @@ -1490,21 +1329,21 @@ EOF fi fi -#detect libavresample (libav only, but ffmpeg backported with '--enable-avresample') for dashcast only +#detect libswresample for dashcast only cat > $TMPC << EOF -#include "libavresample/avresample.h" +#include "libswresample/swresample.h" int main(void) { - AVAudioResampleContext *aresampler = avresample_alloc_context(); + SwrContext *aresampler = swr_alloc(); free(aresampler); return 0; } EOF -if docc $ffmpeg_cflags $ffmpeg_lflags_dashcast -lavresample ; then - has_libavresample="yes" - ffmpeg_lflags_dashcast="$ffmpeg_lflags_dashcast -lavresample" +if docc $ffmpeg_cflags $ffmpeg_lflags_dashcast -lswresample ; then + has_libswresample="yes" + ffmpeg_lflags_dashcast="$ffmpeg_lflags_dashcast -lswresample" else - has_libavresample="no" + has_libswresample="no" fi @@ -1517,8 +1356,8 @@ if test "$pkg_config" != "no"; then freenect_flags=`$pkg_config --cflags libfreenect` freenect_libs=`$pkg_config --libs libfreenect` has_freenect="system" - freenect_flags="-DFREENECT_FLAT_HEADERS $freenect_flags" - fi + freenect_flags="-DFREENECT_FLAT_HEADERS $freenect_flags" + fi fi if test "$has_freenect" = "no"; then @@ -1609,6 +1448,21 @@ else fi +#look for VideoToolBox support + +if test "$darwin" = "yes" ; then +vtb_ldflags="-framework CoreFoundation -framework CoreVideo -framework CoreMedia -framework VideoToolBox" + cat > $TMPC << EOF +#include +int main( void ) { return 0; } +EOF + +if docc $LDFLAGS $vtb_ldflags ; then + has_vtb="yes" +fi +fi + + #look for OSS support if test "$darwin" = "yes" ; then @@ -1652,53 +1506,6 @@ EOF fi - - -#look for wxWidgets support -has_wx="no" -wx_too_old="no" - -if test "$cross_prefix" = "" ; then - if type wx-config >/dev/null 2>>$logs; then - - wx_version=`wx-config --version | sed 's/[^0-9]//g'` - - if test "$wx_version" -lt 250 ; then - wx_too_old="yes" - has_wx="yes" - else - if test "$wx_version" -lt 260 ; then - has_wx="yes" - wx_cflags=`wx-config --cppflags` - wx_lflags=`wx-config --libs` - else - has_wx="yes" - wx_cflags=`wx-config --cppflags core, base` - wx_lflags=`wx-config --libs core, base` - fi - - if test "$darwin" = "yes" ; then - wx_lflags="-Wl,-bind_at_load $wx_lflags -lstdc++" #10.4 needs it, not sure about 10.3 - fi - fi - - cat > $TMPCXX << EOF -#include -int main( void ) { return 0; } -EOF - - if $cc $wx_cflags -o $TMPO $TMPCXX $wx_lflags > /dev/null 2>>$logs ; then - wx_version=`wx-config --version | sed 's/[^0-9]//g'` - if test "$wx_version" -lt 254 ; then - wx_too_old="yes" - else - has_wx="yes" - fi - fi - fi -fi -#end wx test - #look for IPv6 cat > $TMPC << EOF #include @@ -1737,23 +1544,6 @@ if docc $LDFLAGS ; then fi - -#look for XMLRPC -cat > $TMPC << EOF -#include -#include -#include -int main( void ) { -return 0; -} -EOF - -if docc $LDFLAGS ; then - has_xmlrpc="yes" -fi - - - #look for alsa cat > $TMPC << EOF #include @@ -1845,6 +1635,41 @@ EOF fi +#look for hid support +cat > $TMPC << EOF +#include +int main( void ) { hid_init(); hid_exit(); return 0; } +EOF + +if docc -lhidapi-hidraw $LDFLAGS ; then + hid_lib="-lhidapi-hidraw" + has_hid="yes" +fi + +#look for sys/un.h support +cat > $TMPC << EOF +#include +int main( void ) { struct sockaddr_un serv_add; return 0; } +EOF + +if docc $LDFLAGS ; then + has_sock_un="yes" +fi + + +#look for lzma support +cat > $TMPC << EOF +#include +int main( void ) { lzma_options_lzma opt_lzma2; lzma_lzma_preset(&opt_lzma2, 9); return 0; } +EOF + +if docc $CFLAGS_DIR -llzma $LDFLAGS ; then + has_lzma="yes" + GPAC_SH_FLAGS="$GPAC_SH_FLAGS -llzma" + if test "$win32" = "yes"; then + extralibs="$extralibs -llzma" + fi +fi #overwrite detection with manual settings for opt do @@ -1887,8 +1712,6 @@ for opt do ;; --disable-ipv6) has_ipv6="no" ;; - --disable-wx) has_wx="no" - ;; --disable-platinum) has_platinum="no" ;; --disable-oss-audio) has_oss_audio="no" @@ -1905,10 +1728,14 @@ for opt do ;; --enable-mem-track) use_memory_tracking="yes" ;; + --enable-sanitizer) enable_sanitizer="yes" + ;; --enable-tinygl) enable_tinygl="yes" ;; --disable-ssl) has_ssl="no" ;; + --disable-lzma) has_lzma="no" + ;; --enable-depth) enable_depth_compositor="yes" ;; --static-mp4box) static_mp4box="yes" @@ -1935,13 +1762,13 @@ for opt do if test "$tmp_has_png" = "system" ; then if test "$has_png" != "system" ; then if test "$cross_prefix" != "" ; then - echo + echo echo "WARNING: PNG has been forced to system, but we are cross-compiling, it will have to be on target" - echo + echo else - echo + echo echo "WARNING!! : PNG has been forced to system even though it hasn't been found in this host" - echo + echo fi fi fi @@ -1951,21 +1778,26 @@ for opt do if test "$tmp_has_zlib" = "system" ; then if test "$has_zlib" != "system" ; then if test "$cross_prefix" != "" ; then - echo + echo echo "WARNING: ZLIB has been forced to system, but we are cross-compiling, it will have to be on target" - echo + echo else - echo + echo echo "WARNING!! : ZLIB has been forced to system even though it hasn't been found in this host" - echo + echo fi fi has_zlib=$tmp_has_zlib elif test "$tmp_has_zlib" = "no" ; then - echo + echo echo "WARNING!! : you have forced not to use ZLIB. This will disable some core functionalities of GPAC." - echo + echo has_zlib="force-no" + elif test "$tmp_has_zlib" = "local" ; then + echo + echo "WARNING!! : zlib is forced to be local without check - make sure it is avaliable in $local_lib" + echo + has_zlib="local" fi ;; --use-ogg=*) has_ogg=${opt#--use-ogg=} @@ -1980,9 +1812,9 @@ for opt do ;; --enable-pulseaudio) has_pulseaudio="yes" ;; - --disable-all) has_pulseaudio="no"; has_alsa="no"; disable_core_tools="yes"; disable_3d="yes"; disable_svg="yes"; disable_vrml="yes"; disable_od="yes"; disable_bifs="yes"; disable_bifs_enc="yes"; disable_laser="yes"; disable_seng="yes"; disable_qtvr="yes"; disable_avi="yes"; disable_ogg="yes"; disable_m2ps="yes"; disable_m2ts="yes"; disable_m2ts_mux="yes"; disable_parsers="yes"; disable_import="yes"; disable_export="yes"; disable_swf="yes"; disable_scene_stats="yes"; disable_scene_dump="yes"; disable_scene_encode="yes"; disable_loader_isoff="yes"; disable_od_dump="yes"; disable_od_parse="yes"; disable_isom_dump="yes"; disable_mcrypt="yes"; disable_isoff="yes"; disable_isoff_write="yes"; disable_isoff_hint="yes"; disable_isoff_frag="yes"; disable_streaming="yes"; disable_x3d="yes"; disable_loader_bt="yes"; disable_loader_xmt="yes"; has_dvb4linux="no"; disable_player="yes"; disable_vobsub="yes"; disable_scenegraph="yes"; disable_dvbx="yes"; disable_ttxt="yes"; disable_ttml="yes"; disable_saf="yes"; disable_smgr="yes"; disable_mpd="yes"; disable_dash="yes"; disable_isoff_hds="yes"; disable_hevc="yes" + --disable-all) has_pulseaudio="no"; has_alsa="no"; disable_core_tools="yes"; disable_3d="yes"; disable_svg="yes"; disable_vrml="yes"; disable_od="yes"; disable_bifs="yes"; disable_bifs_enc="yes"; disable_laser="yes"; disable_seng="yes"; disable_qtvr="yes"; disable_avi="yes"; disable_ogg="yes"; disable_m2ps="yes"; disable_m2ts="yes"; disable_m2ts_mux="yes"; disable_parsers="yes"; disable_import="yes"; disable_export="yes"; disable_swf="yes"; disable_scene_stats="yes"; disable_scene_dump="yes"; disable_scene_encode="yes"; disable_loader_isoff="yes"; disable_od_dump="yes"; disable_od_parse="yes"; disable_isom_dump="yes"; disable_crypto="yes"; disable_isoff="yes"; disable_isoff_write="yes"; disable_isoff_hint="yes"; disable_isoff_frag="yes"; disable_streaming="yes"; disable_x3d="yes"; disable_loader_bt="yes"; disable_loader_xmt="yes"; has_dvb4linux="no"; disable_player="yes"; disable_vobsub="yes"; disable_scenegraph="yes"; disable_dvbx="yes"; disable_ttxt="yes"; disable_vtt="yes"; disable_ttml="yes"; disable_saf="yes"; disable_smgr="yes"; disable_mpd="yes"; disable_dash="yes"; disable_isoff_hds="yes"; disable_hevc="yes" ; disable_atsc="yes" ; enable_qjs="no" ;; - --isomedia-only) has_pulseaudio="no"; has_alsa="no"; disable_core_tools="yes"; disable_3d="yes"; disable_svg="yes"; disable_vrml="yes"; disable_od="yes"; disable_bifs="yes"; disable_bifs_enc="yes"; disable_laser="yes"; disable_seng="yes"; disable_qtvr="yes"; disable_avi="yes"; disable_ogg="yes"; disable_m2ps="yes"; disable_m2ts="yes"; disable_m2ts_mux="yes"; disable_parsers="yes"; disable_import="yes"; disable_export="yes"; disable_swf="yes"; disable_scene_stats="yes"; disable_scene_dump="yes"; disable_scene_encode="yes"; disable_loader_isoff="yes"; disable_od_dump="yes"; disable_od_parse="yes"; disable_isom_dump="yes"; disable_mcrypt="yes"; disable_streaming="yes"; disable_x3d="yes"; disable_loader_bt="yes"; disable_loader_xmt="yes"; has_dvb4linux="no"; disable_player="yes"; disable_vobsub="yes"; disable_scenegraph="yes"; disable_dvbx="yes"; disable_ttxt="yes"; disable_saf="yes"; disable_smgr="yes"; disable_mpd="yes"; disable_dash="yes"; disable_hevc="no"; disable_isoff="no"; disable_isoff_hds="no"; disable_isoff_write="no"; disable_isoff_hint="no"; disable_isoff_frag="no" + --isomedia-only) has_pulseaudio="no"; has_alsa="no"; disable_core_tools="yes"; disable_3d="yes"; disable_svg="yes"; disable_vrml="yes"; disable_od="yes"; disable_bifs="yes"; disable_bifs_enc="yes"; disable_laser="yes"; disable_seng="yes"; disable_qtvr="yes"; disable_avi="yes"; disable_ogg="yes"; disable_m2ps="yes"; disable_m2ts="yes"; disable_m2ts_mux="yes"; disable_parsers="yes"; disable_import="yes"; disable_export="yes"; disable_swf="yes"; disable_scene_stats="yes"; disable_scene_dump="yes"; disable_scene_encode="yes"; disable_loader_isoff="yes"; disable_od_dump="yes"; disable_od_parse="yes"; disable_isom_dump="yes"; disable_crypto="yes"; disable_streaming="yes"; disable_x3d="yes"; disable_loader_bt="yes"; disable_loader_xmt="yes"; has_dvb4linux="no"; disable_player="yes"; disable_vobsub="yes"; disable_scenegraph="yes"; disable_dvbx="yes"; disable_ttxt="yes"; disable_saf="yes"; disable_smgr="yes"; disable_mpd="yes"; disable_dash="yes"; disable_hevc="no"; disable_isoff="no"; disable_isoff_hds="no"; disable_isoff_write="no"; disable_isoff_hint="no"; disable_isoff_frag="no" ; disable_atsc="yes" ; enable_qjs="no" ;; --disable-3d) disable_3d="yes" ;; @@ -2098,9 +1930,9 @@ for opt do ;; --enable-isom-dump) disable_isom_dump="no" ;; - --disable-mcrypt) disable_mcrypt="yes" + --disable-crypto) disable_crypto="yes" ;; - --enable-mcrypt) disable_mcrypt="no" + --enable-crypto) disable_crypto="no" ;; --disable-isoff) disable_isoff="yes" ;; @@ -2125,55 +1957,67 @@ for opt do --disable-streaming) disable_streaming="yes" ;; --enable-streaming) disable_streaming="no" - ;; + ;; --disable-player) disable_player="yes" ;; --enable-player) disable_player="no" - ;; + ;; --disable-scenegraph) disable_scenegraph="yes" ;; --enable-scenegraph) disable_scenegraph="no" - ;; + ;; --disable-dvbx) disable_dvbx="yes" ;; --enable-dvbx) disable_dvbx="no" - ;; + ;; --disable-vobsub) disable_vobsub="yes" ;; --enable-vobsub) disable_vobsub="no" - ;; + ;; --disable-ttxt) disable_ttxt="yes" ;; --enable-ttxt) disable_ttxt="no" - ;; + ;; --disable-ttml) disable_ttml="yes" ;; + --enable-vtt) disable_vtt="no" + ;; + --disable-vtt) disable_vtt="yes" + ;; --enable-ttml) disable_ttml="no" - ;; + ;; --disable-saf) disable_saf="yes" ;; --enable-saf) disable_saf="no" - ;; + ;; --disable-smgr) disable_smgr="yes" ;; --enable-smgr) disable_smgr="no" - ;; + ;; --disable-mpd) disable_mpd="yes" ;; --enable-mpd) disable_mpd="no" - ;; + ;; --disable-dash) disable_dash="yes" ;; --enable-dash) disable_dash="no" - ;; + ;; --disable-core) disable_core_tools="yes" ;; --enable-core) disable_core_tools="no" - ;; + ;; --disable-hevc) disable_hevc="yes" ;; --enable-hevc) disable_hevc="no" - ;; + ;; + --disable-atsc) disable_atsc="yes" + ;; + --enable-atsc) disable_atsc="no" + ;; + --disable-qjs) enable_qjs="no" + ;; + --enable-qjs) enable_qjs="yes" + ;; esac done @@ -2259,48 +2103,6 @@ if test "$enable_tinygl" = "yes" ;then fi - -#look for GECKO support -cat > $TMPCXX << EOF -#include -int main( void ) { return 0; } -EOF - -if docxx -I$xulsdk_path $LDFLAGS ; then - has_xul="system" - xul_flags="-I$xulsdk_path $xul_flags" -fi - -if test "$pkg_config" != "no"; then - if test "$has_xul" = "no" ; then - if $pkg_config --exists libxul 2>>$logs ; then - if docxx -o $TMPO $TMPCXX `$pkg_config --cflags libxul` `$pkg_config --libs libxul` ; then - has_xul="system" - xul_flags="`$pkg_config --cflags libxul` `$pkg_config --libs libxul`" - fi - fi - fi -fi - -if test "$has_xul" = "no" ; then - if docxx $xul_flags -I$local_inc/gecko-sdk/include $LDFLAGS ; then - has_xul="local" - xul_flags="-I$local_inc/gecko-sdk/include $xul_flags" - else - #xulrunner directories are sometimes included through js/xul/ff packages - if test ! "$has_js" = "no" -a ! "$has_js" = "local" ; then - if docxx $js_flags $js_lib_pkg $LDFLAGS ; then - if test "$mozjs_pkgcfg" != "no" ; then - xul_flags=`$pkg_config --cflags $mozjs_pkgcfg` - has_xul="$has_js" - fi - fi - fi - fi -fi - - - #look for joystick support cat > $TMPC << EOF #include @@ -2335,8 +2137,6 @@ EOF fi - - #look for SDL support sdl_too_old=no has_sdl=no @@ -2412,8 +2212,6 @@ EOF fi #end SDL check - - #look at endianess if test -z "$cross_prefix" ; then @@ -2447,7 +2245,7 @@ fi #man dir if test x"$mandir" = x""; then - mandir="${prefix}/man" + mandir="share/man" fi if test "$static_mp4box" = "yes"; then @@ -2560,9 +2358,11 @@ echo "debug version: $debuginfo" echo "GProf enabled: $gprof_build" echo "Static build enabled: $static_build" echo "Memory tracking enabled: $use_memory_tracking" +echo "Sanitizer enabled: $enable_sanitizer" echo "Fixed-Point Version: $use_fixed_point" echo "IPV6 Support: $has_ipv6" echo "Static Modules: $static_modules" +echo "QuickJS Support: $enable_qjs" if test "$disable_player" = "yes" ; then echo "Player disabled" @@ -2704,9 +2504,9 @@ if test "$disable_isom_dump" = "yes" ; then echo "ISOM dump disabled" echo "#define GPAC_DISABLE_ISOM_DUMP" >> $TMPH fi -if test "$disable_mcrypt" = "yes" ; then - echo "MCrypt disabled" - echo "#define GPAC_DISABLE_MCRYPT" >> $TMPH +if test "$disable_crypto" = "yes" ; then + echo "Crypto tools disabled" + echo "#define GPAC_DISABLE_CRYPTO" >> $TMPH fi if test "$disable_isoff" = "yes" ; then echo "ISO File Format disabled" @@ -2752,6 +2552,11 @@ if test "$disable_ttml" = "yes" ; then echo "#define GPAC_DISABLE_TTML" >> $TMPH fi +if test "$disable_vtt" = "yes" ; then + echo "WebVTT disabled" + echo "#define GPAC_DISABLE_VTT" >> $TMPH +fi + if test "$enable_depth_compositor" = "yes" ; then echo "Depth Compositor enabled" echo "#define GF_SR_USE_DEPTH" >> $TMPH @@ -2772,6 +2577,16 @@ if test "$disable_hevc" = "yes" ; then echo "#define GPAC_DISABLE_HEVC" >> $TMPH fi +if test "$disable_atsc" = "yes" ; then + echo "ATSC3 Support disabled" + echo "#define GPAC_DISABLE_ATSC" >> $TMPH +fi + +if test "$disable_crypto" = "yes" ; then + echo "CRYPTO Support disabled" + echo "#define GPAC_DISABLE_CRYPTO" >> $TMPH +fi + echo "" echo "** Detected libraries **" @@ -2797,8 +2612,6 @@ echo "OpenGL support: $has_opengl" echo "TinyGL support: $has_tinygl" echo "OpenSSL support: $has_ssl" -echo "Mozilla XUL/GECKO support: $has_xul" - if test "$win32" = "yes" ; then echo "DirectX Support: $has_mingw_directx" fi @@ -2806,22 +2619,8 @@ if test "$linux" = "yes" ; then echo "DVB Support: $has_dvb4linux" fi -echo "XMLRPC Support: $has_xmlrpc" - -if test "$wx_too_old" = "yes" ; then - has_wx="no" - echo "wxWidgets Version too old - please upgrade to 2.6.0 for wxWidgets support" -fi -if test "$has_wx" = "yes" ; then - echo "wxWidgets support: Version $wx_version" -else - echo "wxWidgets support: no" -fi - - echo "" echo "** Extra Libraries used **" -echo "SpiderMonkey: $has_js" echo "FreeType: $has_ft" echo "JPEG: $has_jpeg" echo "OpenJPEG: $has_openjpeg" @@ -2830,6 +2629,7 @@ echo "MAD: $has_mad" echo "FAAD: $has_faad" echo "XVID: $has_xvid" echo "FFMPEG: $has_ffmpeg" +echo "LZMA: $has_lzma" echo "Xiph OGG: $has_ogg" echo "Platinum UPnP: $has_platinum" echo "AVCap: $has_avcap" @@ -2888,10 +2688,20 @@ if test "$gprof_build" = "yes"; then fi fi +#we add no deprecate by default on osx (due to opengl ...) if test "$darwin" = "yes" ; then - CFLAGS="$CFLAGS_DIR $CFLAGS" + CFLAGS="$CFLAGS_DIR $CFLAGS -Wno-deprecated -Wno-deprecated-declarations" +else + CFLAGS="$CFLAGS -Wno-deprecated -Wno-deprecated-declarations -Wno-int-in-bool-context" fi + +if test "$enable_sanitizer" = "yes" ; then + CFLAGS="$CFLAGS -fsanitize=address,undefined -fno-sanitize-recover -g" + LDFLAGS="$LDFLAGS -fsanitize=address,undefined -ldl" +fi + + ldir=`pwd` CFLAGS="$CFLAGS -DGPAC_HAVE_CONFIG_H -I\"$ldir\"" if test "$win32" = "no" ; then @@ -2907,9 +2717,7 @@ echo "GPAC_CONFIGURATION=$GPAC_CONFIGURATION" >> config.mak echo "prefix=$prefix" >> config.mak echo "DESTDIR=$DESTDIR" >> config.mak -echo "moddir=$prefix/$libdir/gpac" >> config.mak -echo "moddir_path=$prefix/$libdir/gpac" >> config.mak -echo "mandir=$mandir" >> config.mak +echo "moddir=gpac" >> config.mak echo "tinygl_target_bin_dir=$target_bin_dir" >> config.mak echo "MAKE=$make" >> config.mak @@ -2934,7 +2742,15 @@ echo "OPTFLAGS=$CFLAGS" >> config.mak echo "CXXFLAGS=$CXXFLAGS" >> config.mak echo "LDFLAGS=$LDFLAGS" >> config.mak echo "SHFLAGS=$SHFLAGS" >> config.mak -echo "libdir=$libdir" >> config.mak + +pf="$prefix/" +lib_dir=${libdir#"$pf"} +echo "lib_dir=$lib_dir" >> config.mak + +pf="$prefix/" +man_dir=${mandir#"$pf"} +echo "man_dir=$man_dir" >> config.mak + echo "STATIC_MODULES=$static_modules" >> config.mak @@ -2988,7 +2804,7 @@ if test "$win32" = "yes" ; then echo "#define fseeko64 fseek" >> $TMPH fi if test "$mingw32" = "yes" ; then - + # -municode test cat > $TMPC << EOF #include @@ -3046,18 +2862,11 @@ fi echo "INSTFLAGS=$INSTFLAGS" >> config.mak -echo "CONFIG_JS=$has_js" >> config.mak -if test "$has_js" = "no" ; then - has_js="no" -else - if test "$has_js" = "local" ; then - js_flags="-DXP_UNIX -I$local_inc/js" - js_lib="-ljs" - fi - echo "JS_FLAGS=$js_flags" >> config.mak - echo "JS_LIBS=$js_lib" >> config.mak - echo "#define GPAC_HAS_SPIDERMONKEY" >> $TMPH +echo "CONFIG_JS=$enable_qjs" >> config.mak +if test "$enable_qjs" = "yes" ; then +echo "#define GPAC_HAS_QJS" >> $TMPH fi + if test "$has_zlib" = "no" -o "$has_zlib" = "force-no" ; then echo "#define GPAC_DISABLE_ZLIB" >> $TMPH echo "CONFIG_ZLIB=no" >> config.mak @@ -3069,6 +2878,8 @@ echo "CONFIG_FT=$has_ft" >> config.mak echo "CONFIG_JPEG=$has_jpeg" >> config.mak if test "$has_jpeg" != "no" ; then echo "#define GPAC_HAS_JPEG" >> $TMPH + echo "jpeg_cflags=$jpeg_cflags" >> config.mak + echo "jpeg_lflags=$jpeg_lflags" >> config.mak fi echo "CONFIG_PNG=$has_png" >> config.mak @@ -3076,22 +2887,57 @@ if test "$has_png" != "no" ; then echo "#define GPAC_HAS_PNG" >> $TMPH fi +echo "CONFIG_VTB=$has_vtb" >> config.mak +if test "$has_vtb" != "no" ; then + echo "#define GPAC_HAS_VTB" >> $TMPH + echo "vtb_ldflags=$vtb_ldflags" >> config.mak +fi + +if test "$has_sock_un" != "no" ; then + echo "#define GPAC_HAS_SOCK_UN" >> $TMPH +fi + +echo "CONFIG_LZMA=$has_lzma" >> config.mak +if test "$has_lzma" = "yes"; then +echo "#define GPAC_HAS_LZMA" >> $TMPH +fi + echo "CONFIG_JP2=$has_openjpeg" >> config.mak +if test "$has_openjpeg" != "no" ; then + echo "JP2_CFLAGS=$openjpeg_cflags" >> config.mak + echo "JP2_LDFLAGS=$openjpeg_ldflags" >> config.mak + echo "#define GPAC_HAS_JP2" >> $TMPH +fi echo "CONFIG_FAAD=$has_faad" >> config.mak +if test "$has_faad" != "no" ; then + echo "#define GPAC_HAS_FAAD" >> $TMPH +fi echo "CONFIG_MAD=$has_mad" >> config.mak +if test "$has_mad" != "no" ; then + echo "#define GPAC_HAS_MAD" >> $TMPH +fi echo "CONFIG_XVID=$has_xvid" >> config.mak +if test "$has_xvid" != "no" ; then + echo "#define GPAC_HAS_XVID" >> $TMPH +fi echo "CONFIG_OGG=$has_ogg" >> config.mak echo "CONFIG_VORBIS=$has_vorbis" >> config.mak +if test "$has_vorbis" != "no" ; then + echo "#define GPAC_HAS_VORBIS" >> $TMPH +fi echo "CONFIG_THEORA=$has_theora" >> config.mak +if test "$has_theora" != "no" ; then + echo "#define GPAC_HAS_THEORA" >> $TMPH +fi echo "CONFIG_FFMPEG=$has_ffmpeg" >> config.mak if test "$has_ffmpeg" = "no"; then echo "DISABLE_DASHCAST=yes" >> config.mak else echo "ffmpeg_cflags=$ffmpeg_cflags" >> config.mak echo "ffmpeg_lflags=$ffmpeg_lflags" >> config.mak - echo "ffmpeg_lflags_dashcast=$ffmpeg_lflags_dashcast" >> config.mak echo "CONFIG_LIBAV=$is_libav" >> config.mak - echo "CONFIG_LIBAVRESAMPLE=$has_libavresample" >> config.mak + echo "CONFIG_LIBSWRESAMPLE=$has_libswresample" >> config.mak + echo "#define GPAC_HAS_FFMPEG" >> $TMPH fi echo "CONFIG_FFMPEG_OLD=$old_ffmpeg_inc" >> config.mak @@ -3099,6 +2945,9 @@ echo "CONFIG_OSS_AUDIO=$has_oss_audio" >> config.mak echo "CONFIG_ALSA=$has_alsa" >> config.mak echo "CONFIG_JACK=$has_jack" >> config.mak echo "CONFIG_A52=$has_a52" >> config.mak +if test "$has_a52" != "no" ; then + echo "#define GPAC_HAS_LIBA52" >> $TMPH +fi echo "CONFIG_PULSEAUDIO=$has_pulseaudio" >> config.mak echo "CONFIG_FREENECT=$has_freenect" >> config.mak if test "$has_freenect" != "no" @@ -3107,6 +2956,10 @@ then echo "FREENECT_LDLAGS=$freenect_ld" >> config.mak fi +if test "$want_gcov" = "yes" ; then + echo "#define GPAC_ENABLE_COVERAGE" >> $TMPH +fi + echo "DISABLE_PLAYER=$disable_player" >> config.mak echo "DISABLE_STREAMING=$disable_streaming" >> config.mak echo "DISABLE_SVG=$disable_svg" >> config.mak @@ -3123,7 +2976,7 @@ echo "DISABLE_SCENE_STATS=$disable_scene_stats" >> config.mak echo "DISABLE_SCENE_DUMP=$disable_scene_dump" >> config.mak echo "DISABLE_SCENE_ENCODE=$disable_scene_encode" >> config.mak echo "DISABLE_SCENEGRAPH=$disable_scenegraph" >> config.mak -echo "DISABLE_MCRYPT=$disable_mcrypt" >> config.mak +echo "DISABLE_CRYPTO=$disable_crypto" >> config.mak echo "DISABLE_DVBX=$disable_dvbx" >> config.mak echo "DISABLE_AVILIB=$disable_avi" >> config.mak echo "DISABLE_M2PS=$disable_m2ps" >> config.mak @@ -3145,6 +2998,8 @@ echo "DISABLE_OD_PARSE=$disable_od_parse" >> config.mak echo "MINIMAL_OD=$disable_od" >> config.mak echo "DISABLE_ISOM_ADOBE=$disable_isoff_hds" >> config.mak echo "DISABLE_VRML=$disable_vrml" >> config.mak +echo "DISABLE_ATSC=$disable_atsc" >> config.mak +echo "DISABLE_CRYPTO=$disable_crypto" >> config.mak if test "$disable_parsers" = "yes" ; then disable_m2ts_mux="yes" @@ -3211,12 +3066,6 @@ if test "$win32" = "yes" ; then fi fi -echo "USE_WXWIDGETS=$has_wx" >> config.mak -if test "$has_wx" = "yes"; then - echo "WX_CFLAGS=$wx_cflags" >> config.mak - echo "WX_LFLAGS=$wx_lflags" >> config.mak -fi - echo "CONFIG_PLATINUM=$has_platinum" >> config.mak echo "CONFIG_AVCAP=$has_avcap" >> config.mak @@ -3229,28 +3078,23 @@ echo "CONFIG_OPENSVC=$has_opensvc" >> config.mak if test "$has_opensvc" = "yes" ; then echo "OSVC_CFLAGS=$osvc_cflags" >> config.mak echo "OSVC_LDFLAGS=$osvc_ldflags" >> config.mak + echo "#define GPAC_HAS_OPENSVC" >> $TMPH fi echo "CONFIG_OPENHEVC=$has_openhevc" >> config.mak if test "$has_openhevc" = "yes" ; then echo "OHEVC_CFLAGS=$ohevc_cflags" >> config.mak echo "OHEVC_LDFLAGS=$ohevc_ldflags" >> config.mak + echo "#define GPAC_HAS_OPENHEVC" >> $TMPH fi echo "MOZILLA_DIR=$moz_path" >> config.mak -echo "CONFIG_XUL=$has_xul" >> config.mak -if test "$has_xul" != "no"; then - echo "XUL_CFLAGS=$xul_flags" >> config.mak -fi - echo "LINUX_DVB=$has_dvb4linux" >> config.mak if test "$has_dvb4linux" = "yes"; then echo "#define GPAC_HAS_LINUX_DVB" >> $TMPH fi -echo "XMLRPC_INC=$has_xmlrpc" >> config.mak - if test "$has_oss_audio" != "no"; then echo "OSS_INC_TYPE=$has_oss_audio" >> config.mak echo "OSS_CFLAGS=$OSS_CFLAGS" >> config.mak @@ -3270,6 +3114,11 @@ if test "$has_x11_xv" = "yes"; then echo "USE_X11_XV=$has_x11_xv" >> config.mak fi +echo "CONFIG_HID=$has_hid" >> config.mak +echo "HID_LDFLAGS=$hid_lib" >> config.mak + + + if test "$is_64" = "yes"; then #not on OSX ... if test "$darwin" = "yes"; then @@ -3315,9 +3164,9 @@ echo "GPAC_ENST_INC=$GPAC_ENST" >> config.mak if test "$source_path_used" = "yes" ; then echo "Creating compilation tree image" - SRC_DIRS="src src/utils src/isomedia src/ietf src/odf src/bifs src/scenegraph src/terminal src/mcrypt src/media_tools src/scene_manager src/compositor src/laser" + SRC_DIRS="src src/utils src/isomedia src/ietf src/odf src/bifs src/scenegraph src/filter_core src/filters src/terminal src/crypto src/media_tools src/scene_manager src/compositor src/laser src/evg src/quickjs src/jsmods" - APP_DIRS="applications/mp4box applications/mp4client applications/osmozilla applications/osmo4_wx applications/mp42ts applications/dashcast" + APP_DIRS="applications/gpac applications/mp4box applications/mp4client" for dir in $SRC_DIRS ; do mkdir -p "$dir" @@ -3413,16 +3262,46 @@ fi echo ' $(CXX) $(CFLAGS) -c -o $@ $<' >> config.mak #pkg-config -echo "prefix=$prefix" > gpac.pc -echo "exec_prefix=\${prefix}" >> gpac.pc -echo "libdir=\${exec_prefix}/$libdir" >> gpac.pc -echo "includedir=\${exec_prefix}/include" >> gpac.pc -echo "" >> gpac.pc -echo "Name: gpac" >> gpac.pc -echo "Description: GPAC Multimedia Framework" >> gpac.pc -echo "URL: http://gpac.sourceforge.net" >> gpac.pc -echo "Version:$version" >> gpac.pc -echo "Cflags: -I\${prefix}/include/gpac" >> gpac.pc -echo "Libs: -L\${libdir} -lgpac" >> gpac.pc +generate_pkgconfig () { + echo "prefix=$prefix" + echo "exec_prefix=\${prefix}" + echo "libdir=\${exec_prefix}/$libdir" + echo "includedir=\${exec_prefix}/include" + echo "" + echo "Name: gpac" + echo "Description: GPAC Multimedia Framework" + echo "URL: http://gpac.io" + echo "Version:$version" + echo "Cflags: -I\${prefix}/include" + echo "Libs: -L\${libdir} -lgpac" +} + +generate_pkgconfig > gpac.pc + + +generate_gpacdesktop () { + echo "[Desktop Entry]" + echo "Version=1.0" + echo "Name=MP4Client" + echo "Comment=GPAC Media Player" + echo "GenericName=Media Player" + echo "Keywords=Media Player" + echo "Exec=MP4Client -gui %u" + echo "Terminal=false" + echo "X-MultipleArgs=false" + echo "Type=Application" + echo "Icon=$prefix/share/pixmaps/gpac.png" + echo "Categories=AudioVideo" + echo "MimeType=text/text;text/xml;application/xhtml+xml;application/xml;image/jpeg;image/png;video/webm;video/mp4;video/mpeg;audio/mp4;audio/mpeg;x-scheme-handler/rtsp;x-scheme-handler/rtp;x-scheme-handler/atsc" + echo "StartupNotify=true" + echo "Actions=new-window" + echo "" + echo "[Desktop Action new-window]" + echo "Name=Open a New Window" + echo "Exec=MP4Client" + echo "" +} + +generate_gpacdesktop > "$source_path/share/gpac.desktop" echo "Done - type 'make help' for make info, 'make' to build" diff --git a/doc/CODING_STYLE b/doc/CODING_STYLE deleted file mode 100644 index 9440327..0000000 --- a/doc/CODING_STYLE +++ /dev/null @@ -1,133 +0,0 @@ -GPAC coding style - -Introduction - coding styles only concern the GPAC library (M4Systems in GPAC<=0.3.0, libgpac now), and, although recommended, are not mandatory for plugins - or applications development. - - AStyle is a code beautifier tool. Code should be compliant with the following pattern provided by AStyle : - AStyle -r --indent=tab '*.c' '*.h' '*.cpp' '*.hpp' - -1 Exported symbols - - 1.0 Informative note - The GF_ or gf_ stands for "gpac framework" - - 1.1 typedef of structures - typedef of base types - typedef of functions - -All symbols defined within libgpac library and exported for application development purposes shall begin be named GF_* -with the first character of "*" being in capital, regardless of their functionality. -For example, GF_Thread (structure), GF_Err (redefined type), GF_LineCap (enum typedef) - -All structure typedefs, whether defined (declaration exported through ehaders) or not (declaration intern to the GPAC core lib), must follow -this principle. -Furthermore, typedef of non exported structure shall only apply to the structure itself, not a pointer to it. - typedef struct _my_gpac_thingy *GF_LPMYGPACSTUFF; /*this is FORBIDDEN*/ - typedef struct _my_gpac_thingy GF_MyGPACStuff; /*this is OK*/ -This will allow easy exporting of structure declaration if needed at some point in the development. - - - 1.2 constants - -All constants (whether #defines or enums) shall be in capital letters and begin with "GF_" and use "_" for word separation -For example, error code GF_BAD_PARAM. - -Enums names shall refer to the main module they are used in through a keyword right after the GF_ . -For example, - * enums refering to file format (isomedia) will be prefixed by GF_ISOM_ - * enums refering to MPEG-4 OD tools (odf) will be prefixed by GF_ODF_ - - 1.3 functions - -All exported functions and pointer to functions shall begin with "gf_". The name shall refer to the main module they are sued in -through a keyword right after the gf_ when reasonable (gf_isom_, gf_odf_, gf_term_), or to the tool they refer to (gf_bs_, gf_url, ...) - -2 Miscalleanous - -All exported functions' names shall be in lower case exclusively. -All exported functions' parameters should be in lower case exclusively. -Exported structures may use case in any fashion as long as they respect the rules expressed in section 1 above. -Exported structures member should preferably all be lower case - this may not be feasible for scene graph nodes & like... - -All exported header files shall be in lower case -All source files within the gpac core shall be in lower case - -All comments should be C-like ones (/**/) not C++ ones (//) -All exported headers documentation should be written with dowygen syntax -All constructor-like functions should be of style gf_zzz_new -All destructor like functions should be of style gf_zzz_del - -3 Rearchitecture of the GPAC repository - - This recaps the changes between gpac 0.3.0 and gpac 0.4.0 as far as the core architecture is concerned. - - - The former M4Systems library is now simply libGPAC (eg libgpac.lib, libgpac.dll, libgpac.so, libgpac.a) - Currently no decision has been taken regarding splitting of this library in smaller packages, this still needs discussion. - - directory architecture: - /gpac - /gpac/applications/ - former Applications/, misc renames - /gpac/applications/generators: former SceneGenerators - /gpac/applications/testapps: added for apps testing some features of gpac (currently only BIFSEngine tester) - /gpac/bin - rename of directories: - arm_debug -> arm_ppc02_deb - arm_release -> arm_ppc02_rel - Debug -> w32_deb - Release -> w32_rel - /gpac/build: former IDEs directory, with project files for MS eVC*/VC* - /gpac/doc - /gpac/doc/libgpac: future place for doc of all exported functions from libgpac - /gpac/extra_lib - /gpac/extra_lib/include - unchanged - /gpac/extra_lib/lib - rename of directories: - arm_debug -> arm_ppc02_deb - arm_release -> arm_ppc02_rel - w32_debug -> w32_deb - w32_release -> w32_rel - - - /gpac/src: former M4Systems directory, source for the gpac core lib - /gpac/src/utils: - former Tools - function names: gf_* - /gpac/src/isomedia: - former MP4 - code for isomedia - function names: gf_isom_* - /gpac/src/odf: - former OD - code for MPEG-4 OD Framework - function names: gf_odf_* - /gpac/src/ietf: - former IETF - code for IETF SDP/RTP/RTCP/RTSP protocols - function names: (or gf_sdp_*, gf_rtp_*, gf_rtsp_*) - moved packetizers in this place since payloads are quite related to IETF... - /gpac/src/bifs: - former BIFS - code for BIFS encoding and decoding - exported function names: gf_bifs_* - /gpac/src/terminal: - former ESM - code for terminal module - exported function names: gf_term_* - /gpac/src/mcrypt: - unchaged - function names: gf_crypt_* - /gpac/src/renderer: - former renderer - function names: gf_sr_* - /gpac/src/scenegraph: - former SceneGraph - function names: gf_sg_* for all common code (base nodes and graph handling), - gf_sg_vrml_*: MPEG4, VRML, X3D related - gf_sg_svg_*: SVG related (maybe laser-related) - - /gpac/src/media_tools: - former authoring on media import/export and IsoMedia file stuff. function names: gf_media_*, gf_hinter_ - - /gpac/src/scene_manager: - former authoring on scene manager. function names: gf_sm_* - - - /gpac/include - /gpac/include/gpac: - splitting of former m4_tools.h into dedicated headers, and for some other modules too (media parsers, ...) - - /gpac/include/gpac/internal: - former intern , no big changes - - /gpac/modules: - former Plugins directory. - - \ No newline at end of file diff --git a/doc/INSTALL.gcc b/doc/INSTALL.gcc deleted file mode 100644 index a3517cc..0000000 --- a/doc/INSTALL.gcc +++ /dev/null @@ -1,116 +0,0 @@ -Installation instructions for GPAC on GCC-powered platforms -last modified: December 2008 - -0 Foreword - The output directory for all plugins and applications is gpac/bin/gcc - - As of 0.2.2, GPAC cannot be compiled without ZLIB. You'd better make sure it is installed on your system (zlib is provided in gpac_extra_libs package) - -I Extra lib installation - It is recommended to install (source or package) all extra libs needed by gpac not installed on your system. - Libraries found on the system are indicated as an output of the configure script - - -II GPAC compilation - - II.1 SDL Support - GPAC can use SDL for audio/video output. If SDL is not installed on your system, you may indicate configure to build with a local copy: - --sdl-cfg=path/to/local/sdlcfg/ - (make sure to update the local sdl-cfg according to your needs) - - If you can't get SDL and don't have OSS or WAV audio nor DirectX or X11 video support on your system, you won't be able to play any presentation with GPAC, - but you can still use MP4Box. - - II.2 wxWidgets Support - GPAC comes with a GUI player called Osmo4. To compile this player you will need wxWidgets 2.6.0 (2.5.2 should work) installed on your system. - Both unicode and ANSI versions of wxWidgets should be supported. - If you don't use wxWidgets, you can always use GPAC command-line player MP4Client. - - II.3 MinGW DirectX support - When building GPAC under MinGW, it is also possible to compile the DirectX plugin. You will need the MinGW versions of DX libs - (available at http://alleg.sourceforge.net/files/). Get dx70_mgw or dx80_mgw (dx8 is provided in gpac_extra_libs package) - copy the archive content in your MSys tree (for ex, /usr/local/DirectX) and configure gpac with the option --dxsdk-path=/usr/local/DirectX - ** you must keep include and lib folders under the same directory for the configure script to detect DirectX ** - - II.4 Building GPAC - go to root of gpac distribution - ./configure (--help for options) - you may need to "chmod +x" this file... - make - - any fixes to configure are welcome :) - - II.5 Installing GPAC - get root - type "make install" in gpac/ - - This will install MP4Client, Osmo4 if configured, MP4Box and all plugins as well as GPAC documentation man: MP4Box(1), MP4Client(1) and GPAC(1). - type "make uninstall" to remove gpac from your system - - II.6 Installing GPAC SDK - get root - type "make install-lib" in gpac/ - - This will install gpac base headers (), gpac development headers ( and libgpac_static - the static version of libgpac shared library. - type "make uninstall-lib" to remove gpac from your system - There is no documentation regarding headers/SDK for now, you will have to rely on function descriptions in each header. - -III Running GPAC - - III.1 MP4Client - MP4Client is a command-line interface to GPAC. Note that the player cannot work without video support - - You need a GPAC configuration file to run MP4Client, and you will need it each time. - - First launch of MP4Client - go to gpac/bin/gcc if not using the install. - type MP4Client - the prompt will ask for - 1- GPAC plugin dir: enter the path from / to gpac/bin/gcc. This is skipped when using the install version of MP4Client (the plugin path is hardcoded to - the plugins install location on the system) - 2- Font directory: enter the path to a truetype font directory on your system (note that if you don't have compiled with freetype any directory will do) - 3- cache directory: any directory with write access - You now have a valid config file for GPAC, more info on this try "man GPAC" or check gpac/doc/configuration.html. - - The config file is called ".gpacrc" and is located in the user home directory. You may run the client with a different config file by using the "-c" option. - - *If you don't see any output window, check the config file doesn't use gm_raw_out.so as a video renderer (or simply remove gm_raw_out.so). - - MINGW USERS: there are known and terrible bugs with MSys rxvt stdio buffering, do NOT use it to run MP4Client unless you want to understand - these bugs. Use w32 CMD.exe instead. Other GPAC apps are no pb for MSys rxvt. - - III.2 Osmo4 - Osmo4 is the GUI frontend to GPAC. If you have installed Osmo4 on your system, the first launch of the player should ask you to locate a - directory with TrueType fonts and a cache directory for internet downloads if no configuration file is found. - - III.3 MP4Box - MP4Box is a tool to encode, decode and manipulate MPEG-4 systems data. It does not need a configuration file. - Help for MP4Box is available on GPAC web site, with man MP4Box (except on MinGW) and with 'MP4Box -h' - - III.4 Osmozilla - Osmozilla is GPAC plugin for Mozilla-based browsers. It is by default installed to the user mozilla directory ~/.mozilla - To install the plugin on the system - * get root - * ./configure --mozdir=/path/to/mozilla - for example --mozdir=/usr/lib/mozilla-firefox - * make -C applications/osmozilla install - - * OR you may copy by hand bin/gcc/nposmozilla.so to /usr/lib/moz**/plugins and bin/gcc/nposmozilla.xpt to /usr/lib/moz**/components - -IV Configuration - - IV.0 Foreword - All configuration information is described in gpac/doc/configuration.html, or man gpac. - - IV.1 OpenGL - OpenGL is badly known for performing quite poorly as far as high data rate texturing is involved. This is a big issue when displaying a typical movie and you will likely find the GPAC 3D Rendering very slow on your system. If your GPU supports non power of 2 texturing or rectangular texturing (most Win32 drivers do) - you shouldn't have any problem with video. Otherwise here are some tips to configure GPAC on your system: - 1- set the "BitmapCopyPixels" option on: some cards perform rather well at direct pixel transfer. If no improvement, set it off. - 2- set the "BitmapCopyPixels" option off and the "EmulatePOW2" option on. This will trick the GL texturing by using only Power Of 2 textures when converting from YUV to RGB. - 3- If this does not improve video playback, you're only chance is through discussion forums & co to gather info about your system, your GL implementation and how to fine-tune it. - -V Misc - - There is a demo 2D authoring tool called V4Studio. No makefiles available yet but should compile without pbs (only needs libgpac - and wxWidgets ). It is not usable to design content but is a funny toy. - - diff --git a/doc/INSTALL.gpe b/doc/INSTALL.gpe deleted file mode 100644 index 84169cb..0000000 --- a/doc/INSTALL.gpe +++ /dev/null @@ -1,100 +0,0 @@ -Installation instructions for GPAC on Familiar+GPE platforms -last modified: December 2008 - -0 Foreword - This file is about installing the GPAC framework on an arm device running Linux familiar (cf http://www.handhelds.org) - - Compilation has only been tested for familiar+GPE platforms ( http://gpe.handhelds.org). - The GPE version enables GPAC to use X11 video output directly, including shared memory extensions. - - The output directory for all plugins and applications is gpac/bin/gcc - - As of 0.2.2, GPAC cannot be compiled without ZLIB. You'd better make sure it is installed on your system (zlib is provided in gpac_extra_libs package) - - To install the arm cross-compilation environement on your linux system, refer to: - * http://www.handhelds.org - * we're currently using the following tool-chain for Familiar+GPE compil http://www.roebling.de/embedded.html, with X11 and GTK+ support, - already compiled. - - Do not forget to update your environment variables according to your toolchain. - -I Extra lib installation - It is recommended to install all extra libs needed by gpac not installed on your system. - -II GPAC compilation - - II.1 SDL Support - GPAC can use SDL for audio/video output. If SDL is not installed on your system, you may indicate configure to build with a local copy: - --sdl-cfg=path/to/local/sdlcfg/ - (make sure to update the local sdl-cfg according to your needs) - - If you can't get SDL and don't have OSS audio nor X11 video support on your system, you won't be able to play any presentation with GPAC, - but you can still use MP4Box. - - II.2 wxWidgets Support - GPAC comes with a GUI player called Osmo4. To compile this player you will need wxWidgets 2.6.0 (2.5.2 should work) installed on your system. - NOTE: Osmo4+wxWidgets is quite slow on familiar+GPE, and is therefore not recommended at the time being. - You can always use GPAC command-line player MP4Client. - - II.3 OpenGL ES support - OpenGL ES is not yet supported on Familiar-GPE. However, GPAC renderer supports OpenGL ES API. Porting the 3D module only requires modifying the X11 output (modules/x11_out) for - OpenGL ES. - - II.4 Building GPAC - go to root of gpac distribution. Note: you may need to "chmod +x" the configure file. - (./configure --help for options) - ./configure --prefix=/usr/local/arm/3.3.2 --cpu=armv4l --enable-fixed-point - Note: this assume the cross-compilation tool-chain is located in /usr/local/arm/3.3.2 - Note: you may also specify any other option supported by configure. It is not recommended to use the floating-point version of GPAC for ARM-based architectures. - make - - II.5 Installing GPAC - copy all files to your device, possibly creating a dedicated directory for modules - edit or create ~/.gpacrc to have at least the following lines: - [General] - ModulesDirectory=AbsolutePathToTheModules - - For more information on GPAC configuration file, cf man GPAC of gpac/doc/configuration.html - -III Running GPAC - - III.1 MP4Client - MP4Client is a command-line interface to GPAC. Note that the player cannot work without video support (so you'd better get SDL) - - You need a GPAC configuration file to run MP4Client, and you will need it each time. - - First launch of MP4Client - go to gpac/bin/gcc if not using the install. - type MP4Client - the prompt will ask for - 1- GPAC plugin dir: enter the path from / to gpac/bin/gcc. This is skipped when using the install version of MP4Client (the plugin path is hardcoded to - the plugins install location on the system) - 2- Font directory: enter the path to a truetype font directory on your system (note that if you don't have compiled with freetype any directory will do) - 3- cache directory: any directory with write access - You now have a valid config file for GPAC, more info on this try "man GPAC" or check gpac/doc/configuration.html. - - The config file is called ".gpacrc" and is located in the user home directory. You may run the client with a different config file by using the "-c" option. - - *If you don't see any output window, check the config file doesn't use raw_out.so as a video renderer (or simply remove raw_out.so). - - III.2 Osmo4 - Osmo4 is the GUI frontend to GPAC. If you have installed Osmo4 on your system, the first launch of the player should ask you to locate a - directory with TrueType fonts and a cache directory for internet downloads if no configuration file is found. - - III.3 MP4Box - MP4Box is a tool to encode, decode and manipulate MPEG-4 systems data. It does not need a configuration file. - Help for MP4Box is available on GPAC web site, with man MP4Box and with 'MP4Box -h' - -IV Configuration - - IV.0 Foreword - All configuration information is described in gpac/doc/configuration.html, or man gpac. - - IV.1 OpenGL - OpenGL is badly known for performing quite poorly as far as high data rate texturing is involved. This is a big issue when displaying a typical movie and you will likely find the GPAC 3D Renderer very slow on your system. If your GPU supports non power of 2 texturing or rectangular texturing (most Win32 drivers do) - you shouldn't have any problem with video. Otherwise here are some tips to configure GPAC on your system: - 1- set the "BitmapCopyPixels" option on: some cards perform rather well at direct pixel transfer. If no improvement, set it off. - 2- set the "BitmapCopyPixels" option off and the "EmulatePOW2" option on. This will trick the GL texturing by using only Power Of 2 textures when converting from YUV to RGB. - 3- If this does not improve video playback, you're only chance is through discussion forums & co to gather info about your system, your GL implementation and how to fine-tune it. - - diff --git a/doc/INSTALL.symbian b/doc/INSTALL.symbian deleted file mode 100644 index 3918cd5..0000000 --- a/doc/INSTALL.symbian +++ /dev/null @@ -1,84 +0,0 @@ -Installation instructions for GPAC 0.4.5 on Symbian 9.1 (S60 3rd Edition) platform -last modified: December 2008 - -0 Foreword - ! GPAC versions later than 0.4.5 are no longer supported on Symbian ! - - Compilation has only been tested with GCCE & Nokia S60 SDK - In order to fully compile, you must get: - - the complete S60 3rd edition SDK (maintainance release) - - the MMF SDK update - - IMPORTANT NOTE: - You must install the SDK and the code you will compile with it (be it GPAC or anything else) - !! ON THE SAME LOGICAL DRIVE !! - If you do not do so, compilation will simply fail due to some misbehavior of the SDK's environment variables. - - - What is currently supported on symbian: - * all GPAC core, with audio and video output - * MP4 demux, AAC demux, MP3 demux, AMR demux - * native audio media codec from the phone through MMF: AAC and AMR - * MPEG-4 ASP video decoding through xvid - * JPEG and PNG decoding through libjpeg and libpng - * MP3 decoding through MAD - * TrueType fonts through FreeType - * Scripting with JS32 (not fully tested yet) - * FFMPEG (not really stable, random crashes) - - What is currently NOT supported on symbian: - * networking - - TIP: - Global compiler options are located in SDKRoot\Epoc32\tools\compilation_config\ - you may edit the default C++ rules to remove some - warnings during C compil. - - Side notes: - several SDKs may coexist on the drive. - * To see the list of SDKs, at DOS prompt, c:\devices - * To change the default SDK, at DOS prompt, c:\devices -setdefault @sdk_name - you may need to exit prompt and start a new one in order to refresh environment variables. - GPAC should compile on EKA2 (Symbian OS v8.0b, v8.1b). - GPAC CANNOT COMPILE ON EKA1 (Symbian OS v6.1, v7.0, v7.0s, v8.0a v8.1a) because EKA1 does not support writable static data in DLLs - -I Extra lib installation - Please follow the instructions in gpac_extra_lib/00_README_FIRST - -II GPAC compilation - - Go to gpac/build/symbian - - II.1 Configuring extra libs - * If you don't have libjs, libpng or libjpeg for symbian, comment indicated lines in libgpac.mmp (change #if 1 into #if 0) - * If you don't have OpenGL ES for symbian, comment indicated lines in libgpac.mmp (change #if 1 into #if 0) - * If you don't have freetype for symbian, comment ft_font.mmp in file bld.inf - * If you don't have ffmpeg for symbian, comment ffmpeg_in.mmp in file bld.inf - * If you don't have libmad for symbian, comment indicated lines in mp3_in.mmp - * If you don't have libopenjpeg (JPEG 2000) for symbian, comment indicated lines in img_in.mmp (change #if 1 into #if 0) - - II.2 Compiling GPAC - cd gpac/build/symbian - bldmake bldfiles - * for GCCE - abld build gcce urel - * for thumb - abld build thumb urel - cd sis - set EPOCROOT=\path\to\epoc\root\ - * for GCCE - makesis -d%EPOCROOT% osmo4_gcce.pkg - signsis osmo4_gcce.SIS osmo4_gcce.SIS gpac.cer gpac.key password - * for THUMB - makesis -d%EPOCROOT% osmo4_thumb.pkg - - Note: If you need to sign the SIS with your own certificate, you may generate one with openssl: - openssl req -new -x509 -nodes -sha1 -days 3650 -key gpac.key > gpac.cer - -You will get a .SIS package that installs properly on a symbian device. -NOTES: -* The app and plugins are all installed in \sys\bin on the device. DO NOT change this path, since it won't work otherwise (due to symbian 9.1 caged data stuff) -* If you create/succeed to compile a new plugin for GPAC, you must: - - edit build/symbian/sis/osmo4_gcce.pkg to add your plugin in the installer - - edit build/symbian/sis/GPAC.cfg to instruct the player of this new plugin (enumeration of DLLs on Symbian 9.1 is just not possible without a good amount of $$) - - diff --git a/doc/INSTALL.w32 b/doc/INSTALL.w32 deleted file mode 100644 index a338b23..0000000 --- a/doc/INSTALL.w32 +++ /dev/null @@ -1,217 +0,0 @@ -Installation instructions for GPAC on windows platform -last modified: May 2012 - -0 Foreword - For any question on the installation procedure, please refer to http://gpac.sourceforge.net/home_download.php - - The output directory for all plugins and applications is - gpac/bin/win32/debug in debug mode - gpac/bin/win32/release in release mode - - As of 0.2.2, GPAC cannot be compiled without ZLIB. You'd better make sure it is installed locally or on your system (zlib is provided in gpac_extra_libs package) - -I Extra lib installation - - It is recommended to download and compile all extra libs needed by gpac on windows. Please read carefull the ReadMe file included in the gpac_extra_libs package - - -II GPAC compilation - - open the GPAC workspace: - gpac/build/msvc6/GPAC.dsw with MSVC6 - gpac/build/msvc8/gpac.sln with VS2005 - gpac/build/msvc9/gpac.sln with VS2008 - - NEVER ATTEMPT TO LOAD A PROJECT OUTSIDE THIS WORKSPACE, AS DEPENDENCY RULES WILL BE BROKEN - - II.1 Recompiling GPAC - libgpac is the core library of the GPAC framework used by all GPAC applications. It is available as a static library and as a dynamic one - Set the "libgpac_dll" project as the active one - If you have not installed the SpiderMonkey (JavaScript, libjs), JPEG or PNG libraries, remove the indicated macros in the file gpac/include/gpac/internal/config_static.h - recompile (libgpac_dll compilation will fail if zlib is not found) - - Note: If you wish to build the fixed-point version of GPAC (not recommended), you will have to modify by hand the file gpac/include/gpac/maths.h - and replace the line - #define GPAC_NO_FIXED_POINT - by the line - #define GPAC_FIXED_POINT - - !!Do not attempts to enable fixed-point compilation in any other way!! - - II.2 Recompiling MP4Box - MP4Box is GPAC command-line tool for content authoring. - Set the "MP4Box" project as the active one - Recompile - - II.3 Plugins check - Before compiling other applications in GPAC you must make sure the projects are configured with the right libraries - - * aac_in - If you have not installed faad2 library, remove the GPAC_HAS_FAAD preprocessor variable from the aac_in project settings. - Note that if no aac decoder is installed for gpac you may as well remove the "aac_in" project from the workspace (DEL). - - * ac3_in - If you have not installed liba52 library, remove the GPAC_HAS_LIBA52 preprocessor variable from the ac3_in project settings. - Note that if no ac3 decoder is installed for gpac you may as well remove the "ac3_in" project from the workspace (DEL). - - * mp3_in - If you have not installed mad library, remove the GPAC_HAS_MAD preprocessor variable from the mp3_in project settings. - Note that if no mp3 decoder is installed for gpac you may as well remove the "mp3_in" project from the workspace (DEL). - - * xvid_dec - if you have not installed xvid library, remove the "xvid_dec" project from the workspace (DEL). - - * img_in: - If you have not installed libopenjpeg, remove the GPAC_HAS_JP2 preprocessor variable from the img_in project settings - and the relevant library from the link settings - If you have not installed any of the above libraries, you may remove the "img_in" project from the workspace (DEL). - - * amr_float_dec - This plugin handles speech coded data using AMR Narrow Band and Wide Band formats. It uses two decoders from the 3GPP consortium available at: - AMR Narrowband decoder: http://www.3gpp.org/ftp/Specs/archive/26_series/26.104/26104-700.zip - AMR Wideband decoder: http://www.3gpp.org/ftp/Specs/archive/26_series/26.204/26204-710.zip - - (The same versions are included in gpac_extra_libs/AMR_NB_FT and gpac_extra_libs/AMR_WB_FT ) - - To compile this plugin, copy the source code to modules/amr_float_dec/amr_nb_ft and modules/amr_float_dec/amr_wb_ft respectively, WITHOUT OVERWRITING typedefs.h files. - You may as well compile with only one of these libraries installed: - If you have not installed AMR NB, remove the GPAC_HAS_AMR_FT preprocessor variable from the amr_float_dec project settings - If you have not installed AMR WB, remove the GPAC_HAS_AMR_FT_WB preprocessor variable from the amr_float_dec project settings - Otherwise, remove the "amr_float_dec" project from the workspace (DEL). - - * amr_dec - This plugin handles speech coded data using AMR Narrow Band format with a fixed-point decoder (suited for embedded platforms). - It uses the decoder of the 3GPP consortium (TS26.073) available from http://www.3gpp.org/ftp/Specs/archive/26_series/26.073/26073-700.zip. - It is the same version included in gpac_extra_libs/AMR_NB with a slight header modifcation. - To compile this plugin, copy the source code to modules/amr_dec/amr_nb WITHOUT OVERWRITING typedefs.h file. - Otherwise, remove the "amr_dec" project from the workspace (DEL). Note you usually won't need this plugin, the float version of the decoders being much faster. - - * ffmpeg_in - To install ffmpeg libraries, cf gpac_extra_libs/ReadMe - if you have not installed ffmpeg libraries (avcodec.lib/dll, avformat.lib/dll, avutil.lib/dll), remove the "ffmpeg_in" project from the workspace (DEL). - - * ft_font - if you have not installed freetype, simply remove the "ft_font" project from the workspace (DEL). - - * gdip_rend - To install GDIPLus, either get Microsft PlatformSDK (http://www.microsoft.com/msdownload/platformsdk/sdkupdate/) or read gpac_extra_libs/GDIPlus/install.txt - if you have not installed GDIPlus or WindowsSDK, remove the "gdip_rend" project from the workspace (DEL). - - * ogg - if you have not installed libogg library, remove the "ogg" project from the workspace (DEL). - if you have not installed libvorbis library, remove the GPAC_HAS_VORBIS preprocessor variable from the ogg project settings - if you have not installed libtheora library, remove the GPAC_HAS_THEORA preprocessor variable from the ogg project settings - Note that if you have only installed libogg you can also remove the "ogg" project from the workspace, it won't be any usefull. - - * sdl_out: - GPAC can use SDL library for audio (all clients) and video (MP4Client and wxOsmo4) output. - You will need to get SDL 1.2 for windows. Get it at http://www.libsdl.org/download-1.2.php. You need the DEVELOPMENT LIBRARY SDL-devel-1.2.7-VC6.zip - Unpack and setup your path or VisualC++ for SDL include and lib directories (you may need to restart VisualC++) - You will need to modify your VC settings to look for libxml headers and library directories. - If you don't want to install libSDL or don't plan to use MP4Client or wxOsmo4, simply remove the "sdl_out" project from the workspace (DEL). - NOTE: SDL is not needed at all on windows, you will still have audio and video support without SDL. The SDL plugin is much slower than the directX plugin - when dealing with video since it uses software YUV to RGB conversion. - - NOTE: if you have not installed GDIPlus nor freetype, you won't have text support at all in GPAC. - - - II.4 Recompiling Osmo4 - Osmo4 is the GUI MPEG-4 player of GPAC for the windows platform. - Set Osmo4 as the active project - recompile (enabled plugins will be recompiled in the process) - - II.5 Recompiling MP4Client - MP4Client is the command MPEG-4 player of GPAC. - Set MP4Client as the active project - recompile (enabled plugins will be recompiled in the process) - - II.6 Recompiling Osmo4/wxWidgets - Osmo4 / wxWidgets is the cross-platform GUI MPEG-4 player of GPAC. You must have wxWidgets 2.6.0 installed and configured on your system (this may be tricky. Please - make sure you can recompile some sample wxWidgets applications from wxWidgets distribution first). - Set wxOsmo4 as the active project - recompile (some plugins may be recompiled in the process). You may need to change the linker settings, depending on your wxWidgets version and config - - II.7 Recompiling Osmozilla - Osmozilla is GPAC plugin for Mozilla-based browsers, enabling embedding all GPAC supported contents in a web page. Recompile it only if you think it may - be helpful. - * Get the gecko sdk (http://ftp.mozilla.org/pub/mozilla.org/mozilla/releases/mozilla1.7.13/gecko-sdk-i586-pc-msvc-1.7.13.zip or in the gpac_extra_libs package) - * Extract it to gpac/extra_lib/include/gecko-sdk - * Set Osmozilla as the active project and recompile - - II.7 Recompiling GPAX - GPAX is GPAC ActiveX control, only usable as an Internet Explorer plugin for now, enabling to embed all GPAC supported contents in a web page. Recompile it only if you think it may - be helpful (Set as the active project and recompile) - -III Launching the applications - - If you have compiled libgpac with SpiderMonkey (JavaScript) enabled, copy the JS32.dll file in the output directory. - - Some applications need to locate the GPAC configuration file called GPAC.cfg. This file is automatically generated by Osmo4 in its directory. It is recommended -to first launch Osmo4 to get a working config file. - For more information regarding the config file, cf gpac/doc/configuration.html - - You can launch Osmo4 as is, the application should start and be operational right away. - - You can use MP4Box as is. - - MP4Client needs GPAC configuration file to run. It will by default search for the file in the current directory, and some hardcoded directories that will likely not work. - If the config file is not found, a new one is created in the current directory. You can pass a given config file as a parameter (MP4Client -c config_path). - You can also modify the hardcoded path in MP4Client (gpac/Applications/MP4Client/main.c) to point to gpac output directory and recompile. - - - Osmozilla cannot be launched without a mozilla-based web browser. You should not try to install it in any other way than with the GPAC installer - (cf below). Once installed, you may try any test in an html page viewed with your browser, for example: - - - - To make sure your browser has loaded the osmozilla plugin and to check the mime types supported, try typing "about:plugins" in your browser address bar. - - GPAX cannot be launched without Internet Explorer. You should not try to install it in any other way than with the GPAC installer - (cf below). Once installed, you may try any test in an html page viewed with your browser, for instance: - - - - - -IV Configuration - - IV.0 Foreword - All configuration information is described in gpac/doc/configuration.html, or man gpac. - - IV.1 OpenGL - OpenGL is badly known for performing quite poorly as far as high data rate texturing is involved. This is a big issue when displaying a typical movie and you - will likely find the GPAC 3D Renderer very slow on your system. If your GPU supports non power of 2 texturing or rectangular texturing (most Win32 drivers do) - you shouldn't have any problem with video. Otherwise here are some tips to configure GPAC on your system: - 1- set the "BitmapCopyPixels" option on: some cards perform rather well at direct pixel transfer. If no improvement, set it off. - 2- set the "BitmapCopyPixels" option off and the "EmulatePOW2" option on. This will trick the GL texturing by using only Power Of 2 textures when converting - from YUV to RGB. - 3- If this does not improve video playback, you're only chance is through discussion forums & co to gather info about your system, your GL implementation - and how to fine-tune it. - -V Misc - - V.1 Scene Generators - In gpac/applications/generators you will find the code generators for MPEG-4, X3D and SVG scene graphs used in gpac. - If you want to modify the set of nodes understood by GPAC in encoding/decoding/rendering, you will need those. - The Scene generators can be recompiled without dependencies to extra libraries nor libgpac library. - To recompile them, open the related project files (.dsp) and recompile. - To use them, cf gpac/doc/SceneGenerators - - VI.2 V4Studio - V4Studio is a very simple authoring tool for 2D content. It is not usable but may interest developers. - For Installation instructions cf gpac/Applications/V4Studio/install - - VI.3 GPAC Installer - The GPAC installer uses the NSIS installing system. The installation script is gpac/bin/w32_rel/install/GPAC_Install.nsi - - Before building the installer - * get NullSoft installer (nsis.sourceforge.net), at least version 2.0 - * make sure the js32.dll is copied in the gpac/bin/w32_rel dir if you have compiled GPAC with SpiderMonkey support, otherwise comment - out "..\js32.dll" line from the install script - * copy gdiplus.dll to gpac/bin/w32_rel/install - * make sure avcodec.dll and avformat.dll are copied in gpac/bin/w32_rel if you have built ffmpeg plugin - * make sure iconv.dll, libxml2.dll, zlib1.dll in gpac/bin/w32_rel if you have compiled libxml2 - - - Building GPAC installer - The installer includes Osmo4, MP4Box, Osmozilla and all modules except SDL_out and raw_out. To install, right click on "GPAC_Install.nsi" and compile - For other configurations, modify the script "GPAC_Install.nsi" to comment out unwanted features - diff --git a/doc/INSTALL.wCE b/doc/INSTALL.wCE deleted file mode 100644 index e308319..0000000 --- a/doc/INSTALL.wCE +++ /dev/null @@ -1,134 +0,0 @@ -Installation instructions for GPAC on windows CE platform -last modified: Mai 2012 - -0 Foreword - - ** GPAC IS NO LONGER MAINTAINED ON PocketPC 2002/eVC3 PLATFORMS ** - - Compilation has only been tested with evc4 and ARM platforms - - GPAC should be stable on most devices running PocketPC/SmartPhone 2003. It has know bugs with higher versions - of the Windows Mobile OS, but should still be stable. - - The output directory for all plugins and applications for Windows PocketPC 2003 is - gpac/bin/Smartphone 2003/Debug in debug mode - gpac/bin/Smartphone 2003/Release in release mode - - As of 0.2.2, GPAC cannot be compiled without ZLIB. You'd better make sure it is installed locally or on your system (zlib is provided in gpac_extra_libs package) - -I Extra lib installation - It is recommended to download and compile all extra libs needed by gpac on winCE. - In case you don't do so, you will need to to modify project settings. - - -II GPAC compilation - open /build/msvc8/gpac.sln (GPAC workspace) with MSVC 2005 - open /build/msvc9/gpac.sln (GPAC workspace) with MSVC 2008 - - NEVER ATTEMPT TO LOAD A PROJECT OUTSIDE THIS WORKSPACE - - II.1 Setting up libgpac - libgpac is the core library of the GPAC framework used by all GPAC applications - - If you have not installed the SpiderMonkey (JavaScript, libjs), JPEG or PNG libraries, remove the indicated macros in the file gpac/include/gpac/internal/config_static.h - - If you are compiling for PocketPC 2003 OS, and wish to use the Intel GPP library, uncomment the desired macro in - the file gpac/include/gpac/internal/config_static.h - - 3D Rendering - GPAC can use OpenGL ES for 3D rendering on embedded devices, but you will need development files of OpenGL ES. - GPAC has been tested with 2 implementations of OpenGL ES: - * HybridGraphics one (cf http://www.hybrid.fi/), by far the most efficient one tested, available for evc4/PockePC 2003 only - * Vincent3D (aka ogl-es, cf http://ogl-es.sourceforge.net/), slower but with good visual results - - To setup OpenGL ES, get one of these libraries, configure the IDE to locate the include and lib files (or copy them in gpac/extra_lib/ which is setup by default) - The GL ES lib shall be named "libGLES_CM.lib" or "libGLES_CL.lib" for the OpenGL-ES Light profile, and the GL ES include files shall be located in a "GLES" directory, the include path pointing to its parent directory. - - NOTE 1: The Klimt library (cf http://studierstube.org/klimt/), which is not 100% OpenGL_ES compliant, may be used but needs some code rewrite here and there, - mainly in gapi video output. - - NOTE 2: If you do not plan to support 3D rendering, uncomment the related macro in the file gpac/include/gpac/internal/config_static.h - - - II.2 Checking Plugins - - Before compiling GPAC plugins, you must make sure the projects are configured with the right libraries - * aac_in - If you have not installed faad2 library, remove the GPAC_HAS_FAAD preprocessor variable from the aac_in project settings. - Note that if no aac decoder is installed for gpac you may as well remove the "aac_in" project from the workspace (DEL). - - * ac3_in - If you have not installed liba52 library, remove the GPAC_HAS_LIBA52 preprocessor variable from the ac3_in project settings. - Note that if no ac3 decoder is installed for gpac you may as well remove the "ac3_in" project from the workspace (DEL). - - * mp3_in - If you have not installed mad library, remove the GPAC_HAS_MAD preprocessor variable from the mp3_in project settings. - Note that if no mp3 decoder is installed for gpac you may as well remove the "mp3_in" project from the workspace (DEL). - - * amr_nb - This plugin handles speech coded data using AMR Narrow Band format. It uses the decoder of the 3GPP consortium (TS26.073) available - from http://www.3gpp.org/ftp/Specs/archive/26_series/26.073/26073-700.zip. - It is the same version included in gpac_extra_libs/AMR_NB with a slight header modifcation. - To compile this plugin, copy the source code to modules/amr_dec/amr_nb WITHOUT OVERWRITING typedefs.h file. - Otherwise, remove the "amr_dec" project from the workspace (DEL). - - * img_in: - If you have not installed libopenjpeg, remove the GPAC_HAS_JP2 preprocessor variable from the img_in project settings - and the relevant library from the link settings - If none of the above lib is installed, you can simply remove the img_in project from the workspace (DEL). - - * ft_font - if you have not installed freetype, remove the project dependecies to freetype in project Osmo4 and MP4Client - NOTE: if you have not installed freetype, you won't have text support at all in GPAC. - - * ogg - if you have not installed libogg library, remove the "ogg" project from the workspace (DEL). - if you have not installed libvorbis library, remove the GPAC_HAS_VORBIS preprocessor variable from the ogg project settings - if you have not installed libtheora library, remove the GPAC_HAS_THEORA preprocessor variable from the ogg project settings - Note that if you have only installed libogg you can also remove the "ogg" project from the workspace, it won't be any usefull. - - * gapi: - you must make sure you have installed the GAPI SDK for windows (also known as GX) before compiling - cf gpac_extra_libs/ReadMe - - II.3 Recompiling - select the target platform (PocketPC or Smartphone) - Rebuild all (Build->Batch Build->Rebuild All) - - Note: - Osmo4 build will fail for smartphone devices, the GPAC GUI available on this platform is Osmophone - - II.4 Launching Osmo4 or Osmophone - Osmo4/Osmophone will automatically create a config file in the Osmo4/Osmophone executable directory if needed. You must copy all plugins to this - directory (recommended), or edit by hand the config file "ModulesDirectory" key to point to the modules directory, otherwise Osmo4/Osmophone will not - launch. You can then launch Osmo4/Osmophone (cf Misc section below) - -III GPAC Installer for WinCE - - The GPAC installer uses MS installer and EZSetup to get an Osmo4/Osmophone installer on PocketPC 2003 Systems - Install scripts are located in gpac/bin/arm_ppc02_rel/install and gpac/bin/arm_ppc03_rel/install - THIS INSTALLER IS ONLY INTENDED FOR STRONGARM BASED DEVICES - - * compile GPAC for PocketPC 2003 - * make sure the js.dll is copied in the release build directory if you have compiled GPAC with SpiderMonkey support, otherwise comment lines "js.dll" in gpac.inf - * make sure the libGLES_CM.dll (or libGLES_CL.dll ) is copied in the release build directory if you have compiled with OpenGL ES support, otherwise comment lines "libGLES_CM.dll" and "gm_render3d.dll" in gpac.inf - * make sure the libxml2.dll is copied in the release build directory if you have compiled the svg loader, otherwise comment lines "libxml2.dll" in gpac.inf - * remove any lines in gpac.inf that refer to plugins you have not built. - * if you have built the smartphone version, change AppName and ExeName accordingly in gpac.inf - * get MS PocketPC Cabwizard (search for Cabwiz.exe in your ActiveSync distribution) - * copy cabwiz.exe, cabwiz.ddf and MAKECAB.exe in the install directory - * if needed copy gx.dll in the install directory (download from microsoft, or use gpac extra libs version) - * get Ezsetup from http://www.eskimo.com/~scottlu/win/, and copy it in this directory - * run do.bat file - -IV Misc - - IV.1 Note on Video - As of 0.2.3, the OpenDivx plugin is no longer included in GPAC. A new, modified version of XviD 1.0 for ARM is included (cf gpac/modules/xvid_dec/xvid_wce/ReadMe.txt) - - IV.2 Note on Network - Ipaq devices under WindowsCE below Windows CE .NET 4.1 (and maybe other devices) have a small default UDP buffer size, and this buffer size cannot - be changed through WinSock (software) calls. To increase it, you must manually edit the registry and set (add a DWORD key if not present): - HKEY_LOCAL_MACHINE\Comm\Afd\DgramBuffer 16 - 16 is the maximum value possible, DON'T TRY TO SPECIFY MORE. - The device MUST be reseted (soft reset, cf your manual) for the parameter to be taken into account. - diff --git a/doc/configuration.html b/doc/configuration.html deleted file mode 100644 index 91fb4a1..0000000 --- a/doc/configuration.html +++ /dev/null @@ -1,999 +0,0 @@ - - - - - - GPAC Configuration documentation - - -

-
-GPAC Configuration file documentation -
-GPAC Version 0.7.1 -
-
-

- -

- -Overview -

-Some applications in the GPAC framework use a configuration file shared among modules and reloadable at run time. Modules may use the configuration file as well (to avoid multiple config files). This doc attempts to provide explanations for the different options. -

-The config file is ordered by sections and keys. -
A section is declared as SectionName. Defined sections are: -
-General -RecentFiles -Systems -Compositor -Audio -Video -Network -FontEngine -Downloader -HTTPProxy -Streaming -MimeTypes -StreamingCache -SAXLoader -XviD -FFMPEG -ISOReader -DVB -DASH -ALSA -Shortcuts -DSMCC -OpenHEVC -DirectFB -DektecVideo -M2TS -RAWVideo - -

-

-A key is declared as keyName=value. The key value is not interpreted and always handled as ASCII text. -

-
    -
  • -On Windows plateforms, this config file is called "GPAC.cfg" and is usually located in C:\\Program Files\\GPAC. Note that Osmo4 will always create a config file in its own directory when none is found. -

    -
  • -
  • -On Windows CE plateform, this config file is called "GPAC.cfg" and is usually located in \\Program Files\\GPAC. Note that Osmo4 / CE will always create a config file in its own directory when none is found. -

    -
  • -
  • -On GNU/Linux plateforms, this config file is called ".gpacrc" and is always located at the root of the user home directory (for ex, /home/jean/.gpacrc). -

    -
  • -
-
- -

Note on module names: -
Module names as given in the config file are names exported by each interface and not name of the physical library file (.dll, .so, ...). The physical file name can however be used to identify a module - it will then be replaced by the module name. - -

-

- - -Section "General" Back to top -

-The General section of the config file holds player specific options. -

-ModulesDirectory [value: path to directory] -

-Specifies the path to modules directory. The MPEG-4 Systems engine cannot be loaded without modules. This option is used by GPAC clients on all platforms. -

-CacheDirectory [value: path to storage directory] -

-Specifies location of temp files. The user must have write access to this location. Although not used by applications, this is used by several modules. -

-StartupFile [value: filename] -

-Specifies file to load upon startup of most clients (Osmo4/MP4Client). If not specified, no file is loaded. -

- -LogFile [value: filename] -

-Specifies where to output GPAC's log. By default, the logs are written to stdout. Note that GPAC may be compiled without log support. This is not used by MP4Client. -

-Logs [value: tool[:tool]@level:tool[:tool]@level] -

-Specifies log level for each tool. By default, only errors are logged. Available levels are: -

    -
  • quiet : no logging is performed on the tool
  • -
  • error : only errors are logged
  • -
  • warning : warnings are also logged.
  • -
  • info : information messages are also logged
  • -
  • debug : debug messages are also logged
  • -
-
-Available tools are: -
    -
  • core : libgpac core
  • -
  • coding : bitstream formats (audio, video, scene)
  • -
  • container : container formats (ISO File, MPEG-2 TS, AVI, ...)
  • -
  • network : network data exept RTP trafic
  • -
  • rtp : rtp, rtcp and rtsp trafic
  • -
  • author : authoring tools (hint, import, export)
  • -
  • sync : terminal sync layer
  • -
  • codec : terminal codec messages
  • -
  • parser : scene parsers (svg, xmt, bt) and other
  • -
  • media : terminal media object management
  • -
  • scene : scene graph and scene manager
  • -
  • script : scripting engine messages
  • -
  • interact : user interaction messages
  • -
  • compose : composition engine (events, etc)
  • -
  • cache : HTTP cache
  • -
  • mmio : Audio/Video HW I/O management
  • -
  • rti : Run-time info (CPU, mem, ...)
  • -
  • smil : SMIL timing and animation
  • -
  • memory : GPAC memory tracker
  • -
  • audio : Audio renderer and mixers
  • -
  • module : used by some modules
  • -
  • mutex : mutex information
  • -
  • console : console messages, such as script alert() and error notifications
  • -
-

- -

-

-

- -Options defined for Osmo4 (Windows version and wxWidgets version):

-Loop [value: "yes" "no"] -

-Specifies whether the presentation has to be restarted when done playing. -

-ConsoleOff [value: "yes" "no"] -

-Specifies whether application messages (script, buffering, download progress) are displayed in the console or not. -

-SingleInstance [value: "yes", "no"] -

-Specifies if the player should be a single instance application or not (Osmo4-Win32 only). -

-LookForSubtitles [value: "yes" "no"] -

-Specifies if Osmo4 shall look for subtitle files when opening a presentation. -

-ViewXMT [value: "yes" "no"] -

-Specifies if scene dumping shall be done in XML (XMT, X3D) or in VRML-like syntax (BT, WRL). -

-ConfigPanel [value: positive integer] -

-Specifies the latest config panel selected by user. -

-NoMIMETypeFetch [value: "yes", "no"] -

-Specifies if the player has to check for mime type when following hyperlinks, or only follow links of known extensions. -

-Loop [value: "yes", "no"] -

-Specifies if the playlist shall be restarted when playback is over. -

-PLEntry [value: positive integer] -

-Indicates active playlist entry when player was last closed. Playlist backup is: -

-
    -
  • "gpac_pl.m3u" on win32 platforms, stored in same directory as application
  • -
  • ".gpac_pl.m3u" on other platforms, stored in user home directory
  • -
-FillScreen [value: "yes", "no"] -

-Specifies if the display area shall fill all available space on screen. WindowsMobile/Symbian only -

-DisableBackLight [value: "yes", "no"] -

-Specifies if the screen backlighting shall be disabled while playing. WindowsMobile/Symbian only -

-LastWorkingDir [value: "yes", "no"] -

-Specifies the directory of the last local file opened. Smartphone Windows only -

-Browser [value: string] -

-Specifies prefered browser for WWW anchors and scene graph viewing - Only used by Osmo4/wxWidgets. -

- - -Section "RecentFiles" Back to top -

-The "RecentFiles" section of the config file holds last accessed files (hardcoded to no more than 20) in the last access order. The keys are -the file names and no value is used. This section is only used by GUI clients (osmo4/wxOsmo4) -

- - -Section "Systems" Back to top -

-The "Systems" section of the config file holds all configuration options for the MPEG-4 Systems engine. -

-LanguageName [value: string] -

-Specifies the user prefered language in readable english. This is used to select streams in case of alternate content in an audio object. -

-Language3CC [value: 3-char code from ISO 639-2] -

-Specifies the user prefered language as expressed in ISO 639-2. This is used to select streams in case of alternate content in an audio object. -

-Language2CC [value: 2-char code from ISO 639-1] -

-Specifies the user prefered language as expressed in ISO 639-1. This is used to select streams in case of alternate content in an audio object. -

-DrawLateFrames [value: "yes" "no"] -

-If set, late frames will still be drawn. If not set, the late frames are droped (or executed for systems decoders) untill the decoder output is back in sync. This is by default on to keep better testing heavy content or slow renderers, but should be set to off when needing a better sync or monitoring skipped frames. -

-ForceSingleClock [value: "yes" "no"] -

-One big problem with MP4 files is that the notion of "duration" has been unclear for a long time, and most content available (audio-video files) specifiy a -wrong BIFS duration. In such a case the movie cannot be controled/seeked into. Another problem with ISMA streaming is that BIFS/OD don't use the same clock as -audio/video, thus seeking the main timeline does not seek AV media. Setting the ForceSingleClock will handle both cases by using a single timeline for all media -streams and setting the duration to the one of the longest stream. -

-ThreadingPolicy [value: "Free" "Single" "Multi"] -

-Specifies how media decoders are to be threaded. "Free" lets decoders decide of their threading, "Single" means that all decoders are managed in a single thread performing scheduling and priority -handling and "Multi" means that each decoder runs in its own thread. -

-Priority [value: "low" "normal" "high" "real-time"] -

-Specifies the priority of the decoders (priority is applied to decoder thread(s) regardless of threading mode). -

-TimeSlice [value: unsigned integer] -

-Specifies the target maximum time (in ms) of one cycle of the media manager (the media manager will attempts to call all the active decoders within this time. Depending on the threading mode this option can be ignored; -

-ModuleUnload [value: "yes" "no"] -

-Specifies whether modules should be unloaded if not used or not. Default: "yes". -

-ResyncLateClock [value: unsigned integer] -

-Specifies the threshold after which late clocks are resynchronized to timestamps for OCR streams. By default, no threashold (0) is used and clocks are never resynchronized. This allows to -resync clocks to the media owning the clock when the decoding is really too slow, and should only be used for debugging purposes. -

-NoVisualThread [value: "yes" "no"] -

-Specifies whether the visual rendering is done in the main codec manager or in a dedicated thread. -

-DefAudioDec , DefVideoDec and DefImageDec [value: string] -

-Specifies which module to use by default for audio/video/image decoding. The string is the name of the module to be used (same considerations as other modules, cf introduction). -

-codec_XX_XX [value: string] -

-Allows to specify default media module (audio/video) per stream type and object type. This is usefull if you have more than one decoder for a given type (ex, XviD and FFMPEG for MPEG-4 visual SP). The syntax is: -
codec_AA_BB=modulename
-where AA is the hexadecimal MPEG-4 streamType value for the codec (04=visual, 05=audio) and BB is the hexadecimal MPEG-4 objectTypeIndication of the media (0x20 = MPEG-4 video, 0x40=MPEG-4 Audio, ...). -The string is the name of the module to be used (same considerations as other modules, cf introduction). -

- - -Section "Compositor" Back to top -

-The "Compositor" section of the config file holds all configuration options for the compositor (logical rendering engine). -

-Raster2D [value: string] -

-Specifies the 2D rasterizer to use for vectorial drawing. Same as above, this module cannot be reloaded during a presentation.

-FrameRate [value: float] -

-Specifies the simulation frame-rate of the presentation - this value is also used by the MPEG-4 Systems engine to determine when a BIFS frame is mature for decoding.

-AntiAlias [value: "None" "All" "Text"] -

-Specifies antialiasing settings - whether the setting is applied or not depends on the graphics module / graphic card. -

-
    -
  • "None": no anti-aliasing
  • -
  • "Text": anti-aliasing for text only
  • -
  • "All": complete anti-aliasing
  • -
-HighSpeed [value: "yes" "no"] -

-Specifies whether rendering should target speed or quality - whether the setting is applied or not depends on the renderer, graphics module / graphic card.

-ForceSceneSize [value: "yes" "no"] -

-Forces the scene to resize to the biggest bitmap available if no size info is given in the BIFS configuration.

-StressMode [value: "yes" "no"] -

-Specifies that the renderer runs in worst case scenario, recomputing display lists and reloading textures (sending them to graphics card) at each frame even when no change has occured.

-BoundingVolume [value: "None" "Box" "AABB"] -

-Specifies whether the bounding volume of an object shall be drawn or not. Note that the 2D renderer only uses rectangles as bounding volumes. The "AABB" value is used by the -3D renderer only, and specifies the object bounding-box tree shall be drawn.

-ColorKey [value: unsigned hexadecimal integer, formated as AARRGGBB] -

-Specifies the color key to use for windowless rendering. GPAC currently doesn't support true alpha blitting to desktop due to limitations in most windowing toolkit, it therefore uses color keying mechanism. The alpha part of the key is used for global transparency of GPAC's output, if supported. -

-BackColor [value: unsigned hexadecimal integer, formated as AARRGGBB] -

-Specifies the background color to use when displaying transparent images or video with no scene compoistion instructions. -

-DrawMode [value: "immediate" "defer" "defer-debug"] -

-Specifies whether immediate drawing should be used or not. In immediate mode, the screen is completely redrawn at each frame. In defer mode -object positioning is tracked from frame to frame and dirty rectangles info is collected in order to redraw the minimal amount of the screen buffer. Whether -the setting is applied or not depends on the graphics module (currently all modules handle both mode). Defer Debug mode only renders changed areas.

-ScalableZoom [value: "yes" "no"] -

-Specifies whether scalable zoom should be used or not. When scalable zoom is enabled, resizing the output window will also recompute all vectorial objects. Otherwise only the final buffer is stretched.

-DisableYUV [value: "yes" "no"] -

-Disables YUV hardware support (YUV hardware support may not be available for the current video output module).

-TextureFromDecoderMemory [value: "yes" "no"] -

-Allows video textures to be build directly from video decoder internal buffers. This may increase performances on some systems. Default is no.

-ForceOpenGL [value: "always", "disable", "hybrid", "raster"] -

-Specifies that 2D rendering will be performed by OpenGL rather than raster 2D. This will involve polygon tesselation which may not be supported on all platforms, and 2D graphics will not loo as nice as 2D mode. The hybrid mode performs software drawing of 2D graphics with no textures (better quality) and uses OpenGL for all textures. The raster mode only uses OpenGL for pixel IO but does not perform polygin fill (no tesselation) (slow, mainly for test purposes).

-EnablePBO [value: "yes", "no"] -

-Uses PixelBufferObjects to push YUV textures to GPU in OpenGL Mode.. This may slightly increase the performances of the playback.

-DefaultNavigationMode [value: "Walk", "Fly", "Examine"] -

-Overrides the default navigation mode of MPEG-4/VRML (Walk) and X3D (Examine).

-RasterOutlines [value: "yes" "no"] -

-Specifies that outlining shall be done through OpenGL pen width rather than vectorial outlining.

-PolygonAA [value: "yes" "no"] -

-Specifies whether polygon antialiasing should be used in full antialiasing mode. If not set, only lines and points antialiasing are used.

-DisableBackFaceCulling [value: "yes" "no"] -

-Specifies whether backface culling shall be disable or not. If not set, backface culling is performed.

-Wireframe [value: "WireNone" "WireOnly" "WireOnSolid"] -

-Specifies wireframe drawing options:

-
    -
  • "WireNone": objects are drawn as solid
  • -
  • "WireOnly": objects are drawn as wireframe only
  • -
  • "WireOnSolid": objects are drawn as solid and wireframe is then drawn
  • -
-DisableRectExt [value: "yes" "no"] -

-Specifies whether OpenGL rectangular texture extension (GL_EXT_texture_rectangle or GL_NV_texture_rectangle) shall be used or not.

-
    -
  • "yes": textures whose dimensions are not power of two are rescaled except if hardware support non-power-of-two textures (GL_ARB_texture_non_power_of_two) natively.
  • -
  • "no": if extension is available, textures are used without rescaling. Note that in this case texture transformations are disabled.
  • -
-EmulatePOW2 [value: "yes" "no"] -

-Enables power of 2 emulation. Ignored if openGL rectangular texture extension is enabled.

-
    -
  • "yes": video texture is not resized but emulated with padding. This usually speeds up video mapping on shapes but disables texture transformations.
  • -
  • "no": video is resized to a power of 2 texture when mapping to a shape.
  • -
-DisableGLUScale [value: "yes" "no"] -

-Disables usage of gluScaleImage, which may be slower but nicer than GPAC's software stretch routines.

-TextureTextMode (value: "Default", "Never", "Always"] -

-Specifies whether text shall be drawn to a texture and then rendered or directly rendered. Using textured text can improve text rendering in 3D and also improve text-on-video like content. Default value will use texturing for OpenGL rendering.

-OpenGLExtensions [value: string] -

-Read-only option listing the OpenGL extensions supported by the GL driver. Only valid after the 3D renderer has been used. -

-StereoType [value: "None", "SideBySide", "StereoHeadset", "TopToBottom", "Anaglyph", "Columns", "Rows", "SPV19", "Custom"] -

-Specifies the stereo output type (default "None"). If your graphic card does not support OpenGL shaders, only SideBySide and TopToBottom modes will be available.

-
    -
  • "SideBySide": images are displayed side by side from left to right.
  • -
  • "TopToBottom": images are displayed from top (laft view) to bottom (right view).
  • -
  • "StereoHeadset": same as SideBySide except that view aspect ratio is not changed.
  • -
  • "Anaglyph": Standard color anaglyph (red for left view, green and blue for right view) is used.
  • -
  • "Columns": images are interleaved by columns, left view on even columns and left view on odd columns.
  • -
  • "Rows": images are interleaved by columns, left view on even rows and left view on odd rows.
  • -
  • "SPV19": images are interleaved by for SpatialView 19'' 5 views display, fullscreen mode.
  • -
  • "Custom": images are interleaved according to the shader specified by InterleaverShader -
- -NumViews [value: unsigned integer] -

-Specifies the number of views to use in stereo mode. If mode is "Anaglyph", "Columns" or "Rows", the number of views is forced to 2.

- -InterleaverShader [value: path to fragment shader file] -

-Specifies the fragment shader file to use for view interleaving. Each view is rendered in its own texture. The shader is exposed each view as uniform sampler2D gfViewX, where X is the view number starting from the left (gfView1).

- -ReverseViews [value: "yes", "no"] -

-Specifies if the view order should be reversed (from right to left) or not.

-EyeSeparation [value: float] -

-Specifies the eye separation in cm (distance between the cameras). Default: 6.3 cm.

-CameraLayout [value: "OffAxis", Linear", "Circular"] -

-Specifies the camera layout. The default value is OffAxis in (auto-)stereo modes, ignored in mono mode.

-ViewDistance [value: integer] -

-Specifies the distance in cm between the camera and the zero-disparity plane. There is currently no automatic calibration of depth in GPAC.

- -Output8bit [value: "yes" "no"] -

-Specifies whether 10 bit textures should be converted to 8 bit before GPU upload or use as is. Default is no if screen supports 10 bpp, yes otherwise.

- -VRDefaultFOV [value: float] -

-Default field of view for VR 360. Default is PI/2.

- - -

- - -Section "Audio" Back to top -

The "Audio" section of the config file holds all configuration options for the audio rendering engine and hardware.

-DriverName [value: string] -

-Specifies the driver to use for audio rendering. This driver cannot be reloaded at run-time, the complete system must be restarted.

-ForceConfig [value: "yes" "no"] -

-Forces a given sound card configuration to be used. If not set the sound card will be setup to use 2 audio buffers of 1024 samples each. -This may not work properly on some audio cards due to hardware latency, therefore forcing the config may be very usefull.

-NumBuffers [value: positive integer, 0 forbidden] -

-When config is forced, specifies the number of audio buffers to allocate (audio buffers are played in ring).

-TotalDuration [value: positive integer, 0 forbidden] -

-When config is forced, specifies the total audio buffer size in milliseconds. Be aware that the longer the audio buffer is, the longer the audio latency will be when pausing an -audio object. The quality of fast forward audio playback will also be degradated when using large audio buffers.

-NoResync [value: "yes" "no"] -

-Disables audio resynchronization: audio data is never dropped but may get out of sync.

-DisableMultiChannel [value: "yes" "no"] -

-Disables audio multichannel output and always downmix to stereo. This may be usefull if the multichannel output behaves weirdly.

-DisableNotification [value: "yes" "no"] -

-Disables usage of audio buffer notifications when supported (currently only DirectSound supports it). If DirectSound audio sounds weird try without notifications.

-Volume [value: integer (0-100)] -

-Default audio volume used when launching GPAC.

-Pan [value: integer (0-100)] -

-Default audio stereo balance used when launching GPAC - 0 for full left, 100 for full right, 50 for balanced.

-Filter [value: string] -

-Defines a set of audio filters. Audio filters are declared as a list of strings separated with ";;". The exact syntax of the string is filter specific.

- -

- - -Section "Video" Back to top -

The "Video" section of the config file holds all configuration options for the video renderer and hardware.

-DriverName [value: string] -

-Specifies the driver to use for video memory access. This driver cannot be reloaded at run-time, the complete system must be restarted.

-SwitchResolution [value: "yes" "no"] -

-Specifies fullscreen resolution mode. If enabled, selects smallest video resolution larger than scene size, otherwise use current video resolution.

-HardwareMemory [value: "Auto" "Always" "Never"] -

-Only valid for 2D renderer. Specifies if main video backbuffer is always on hardware, always on system memory or selected by GPAC (default mode). Depending on the scene type, this may drastically change the playback speed.

-DisableColorKeying [value: "yes" "no"] -

-Only valid for 2D renderer. Specifies if partial overlays should be disabled. If not disabled, hardware color keying for overlays is tested and used if present. Otherwise, only the top-most video with no overlapping objects will be drawn using overlays. Default value is "no".

-UseGLDoubleBuffering [value: "yes" "no"] -

-Specifies if OpenGL double buffering shall be used. Default is "no".

-GLNbBitsPerComponent [value: positive integer] -

-Specifies the number of bits per color component. Default is 5.

-GLNbBitsDepth [value: positive integer] -

-Specifies the number of bits for the depth buffer. Default is 16.

-X113DOffscreenMode [value: "Window" "VisibleWindow" "Pixmap"] -

-Specifies the type of OpenGL offscreen rendering in X11. Default mode is "Pixmap".

-
    -
  • "Window": A hidden window is used to perform offscreen rendering. Depending on your video driver and X11 configuration, this may not work.
  • -
  • "VisibleWindow": A visible window is used to perform offscreen rendering. This can be usefull while debugging.
  • -
  • "Pixmap": An X11 Pixmap is used to perform offscreen rendering. Depending on your video driver and X11 configuration, this may not work and can even crash the player.
  • -
- - -

- -Section "Network" Back to top -

The "Network" section of the config file holds all configuration options for the network used by modules and systems engine.

-AutoReconfigUDP [value: "yes" "no"] -

-Specifies if network manager shall reconnect a scene if UDP is not present.

-DataTimeout [value: positive integer] -

-Specifies timeout in ms befor initial media buffering aborts. Default terminal value is 20000 (20 seconds).

-UDPNotAvailable [value: "yes" "no"] -

-Specifies if UDP traffic is not available. This is automatically set by GPAC if AutoReconfigUDP is set.

-UDPTimeout [value: positive integer] -

-Specifies timeout in ms for initial UDP detection. Once a UDP packet is recieved the timeout is ignored.

-BufferLength [value: positive integer] -

-Specifies the length of the decoding buffer in milliseconds. The client will wait for the buffer to be filled before starting decoding. A module may decide to use a different value based on protocol and network jitters.

-RebufferLength [value: positive integer] -

-Specifies rebuffering length of the decoding buffer in milliseconds. Whenever the decoding buffer fullness is less than this value, the object clock is paused and -the stream rebuffered till BufferLength. Therefore a value of 0 means no rebuffering. A module may decide to use a different value based on protocol and network jitters. -

-BufferMaxOccupancy [value: positive integer] -

-Specifies maximum buffer occupancy for buffer regulation. Corresponds to the max amount of data to be prefetch. Any value less than BufferLength will be forced to BufferLength. -

- -LowLatencyBufferMax [value: positive integer] -

-Specifies the maximum buffer level for low latency mode, in ms. If media buffer max is above this value, full buffering will be done (clock resume at first frame displayed). Default value is 500 ms.

-

-MobileIP [value: IP Address] -

-Specifies a Mobile IP interface overriding the default IP. If set, all sockets will be locally bound to this IP address.

-

-DefaultMCastInterface [value: IP Address] -

-Specifies a default IP interface for Multicast overriding the default IP.If not set, the multicast will be setup using the default IP.

-

-HTTPRebuffer [value: positive integer] -

-Specifies the default HTTP rebuffer time in ms. When playback position reaches download position, playback will be paused if this integer is not zero. Playback will resume once the HTTPRebuffer ms of playback are available. Default value is 5000 ms.

-

-HTTPAutoRebuffer [value: "yes", "no"] -

-Specifies whether auto rebuffering is used. When auto-rebuffering is used, playback only resumes when estimated time to download the rest of the movie is less than remaining time of the playback. Default value is no.

-

- - - -

- -Section "FontEngine" Back to top -

The "FontEngine" section of the config file holds all configuration options for the font handling. The GPAC rendering module handles text through vectorial outline, allowing graphics module development without having to -integrate text rendering which is always heavy work.

-FontReader [value: string] -

-Specifies the module to use for font handling. This module cannot be reloaded at run-time, GPAC must be restarted.

-WaitForFontLoad [value: "yes" "no"] -

-Forces to wait for SVG fonts to be loaded before displaying frames - default is "no".

-FontDirectory (value: path to TrueType (*.ttf, *.ttc) font directory] -

-Specifies the directory where fonts are located - currently only one directory can be specified (however nothing stops a font module from using a private directory). -
Note: -The freetype module will scan the entire sub-directories for fonts. -

-FontSerif [value: string]}: specifies default SERIF font.
-FontSans [value: string]}: specifies default SANS font.
-FontFixed [value: string]}: specifies default fixed font.
-
-Note:
-The FreeType module uses this section to cache familly names to font file name associations. - - -

- -Section "Downloader" Back to top -

The "Downloader" section of the config file holds all configuration options for file downloading and caching.

-CleanCache [value: "yes" "no"] -

-Specifies whether downloaded files shall be removed once used.

-DisableCache [value: "yes" "no"] -

-Specifies whether HTTP caching instructions are disabled or not.

-AllowOfflineCache [value: "yes" "no"] -

-When enabled, allows HTTP request to use cached file if any when network is not available.

-MaxRate [value: positive integer] -

-Specifies a maximum data rate in kilo bits per seconds for file downloading. This is used for simulation purposes. A value of 0 means no rate restriction.

-UserAgent [value: string] -

-Specifies an alternate user agent (default one is "GPAC $VERSION").

-HTTPHeadTimeout [value: positive integer] -

-Specifies timeout in milliseconds before considering HEAD request failed. 0 means no HEAD request is issued, only GET.

-AllowBrokenCertificate [value: yes no] -

-If set to yes, ignores invalid certificates and process anyway. Default is no.

- -

- -Section "HTTPProxy" Back to top -

The "HTTPProxy" section of the config file holds configuration option for HTTP proxy adressing. Currently only one proxy can be enabled, and no URI selection is done.

-Enabled [value: "yes" "no"] -

-Specifies whether the proxy should be used or not when downloading files.

-Name [value: string] -

-Specifies the proxy name (IP address or resolved name) without protocol identifier (eg, no "http://"). If not present, the proxy is disabled.

-Port [value: positive integer] -

-Specifies the port to use with the proxy. If no port is specified, the default HTTP port (80) is used.

- -

- -Section "Streaming" Back to top -

The "Streaming" section of the config file holds all configuration options for real-time streaming using IETF SDP/RTSP/RTP/RTCP protocols.

-DefaultPort [value: unsigned short] -

-Specifies the default port to use when connecting to a server (ignored if a port is specified in the url) if the port is 80 or 8080 (HTTP), the client will connect to the -RTSP server through an HTTP tunnel, and transport will take place on the RTSP connection.

-ReorderSize [value: positive integer] -

-Size of the RTP reordering buffer - 0 means no reordering. Ignored when transport takes place on the RTSP connection. The bigger this value, the longer the reordering delay will be.

-RTPoverRTSP [value: "yes" "no" "OnlyCritical"] -

-Specifies whether RTP packets should be carried on the RTSP connection (TCP or UDP), or carried on UDP. If the connection port is an HTTP port, this value is assumed to be true. If set to OnlyCritical, transport will take place on TCP only if a critical media (eg, neither audio nor video) is found in the session.

-RTSPTimeout [value: positive integer] -

-Specifies connection timeout with the server: an RTSP request is considered as failed when the timeout expires.

-ForceFirstPort [value: positive integer] -

-Specifies first port for RTP channels. If not set, the default first port used by GPAC is 7040.

-NATKeepAlive [value: positive integer] -

-Specifies the maximum inactivity period in milliseconds for RTP sockets. If no data is received after this period, an empty RTP packet will be sent in order to keep any NAT alive. If 0 (default), disables NAT keep-alive packets.

-ForceMulticastIP [value: IP4 or IP6 address] -

-Forces the specified multicast address to be used instead of the regular unicast. Note that some servers may not support multicast initiation by the client.

-ForceMulticastTTL [value: Positive integer] -

-Indicates the TTL to use when the client initiates the multicast. Default value is 127.

-FirstPacketDrop [value: positive integer] -

-Specifies the sequence number of the first RTP packet to be droped - 0 means no packet drop. Used for packet drop simulation tests.

-PacketDropFrequency [value: positive integer] -

-Specifies the frequency at which SL packets are droped. If value is 20, one packet every 20 recieved packets will be droped. Used for packet drop simulation tests.

- -

- - -Section "MimeTypes" Back to top -

This section is used to keep MIME types and file associations for GPAC modules. -
The format of the key is: -
mimeType="fileExt1 filexExt2 .. filexExtN" "MimeType description" ModuleName -
-The description is used for GUI purposes (open file dialogs). You may modify the file extension list to support your own extensions. -MIME Type is always checked when processing a remote ressource (eg http file) in order to load the appropriated modules. -If MIME type is not available, provided extensions are first checked, then all input modules are queried. -

- -

- - -Section "StreamingCache" Back to top -

The "StreamingCache" section of the config file holds all configuration options for the streaming cache. Streaming cache allows for recording of -live sources such as RTP/RTSP sessions and internet radios. This is currently just an experimental feature in GPAC.

-RecordDirectory [value: path] -

-Specifies path for recorded files. Cached data is written directly to disk (no re-interleaving or similar processes). -If not specified, the default cache directory (cf General) is used.

-BaseFileName [value: string] -

-Specifies the base name for recorded files. If not present in configuration file, the service name (URL) is used.

-KeepExistingFiles [value: "yes" "no"] -

-Specifies if cached files with same name should be kept or not. If not, an integer number is added to the cached file name, the higest number for the latest file.

- -

- - -Section "SAXLoader" Back to top -

The "SAXLoader" section of the config file holds all configuration options for XML SAX parsing of SVG, XMT and X3D files.

-Progressive [value: "yes" "no" "DOM"] -

Specifies XML parsing mode used by different file loaders using the SAX parser.

-
    -
  • "yes": SAX parsing is used with progressive loading of the document.
  • -
  • "no": SAX parsing is used, document will first be completely downloaded.
  • -
  • "DOM": DOM parsing is used, document will first be completely downloaded. Only supported by libXML2 plugin, otherwise handled as "no".
  • -
-MaxDuration [value positive integer] -

-Specifies the maximum amount of time the SAX parser should spent loading a portion of the document. Only used with SAX Progressive mode

- -

- - -Section "XviD" Back to top -

The "XviD" section of the config file holds all configuration options for the XviD codec.

-PostProc [value: "FilmEffect" "Deblock_Y" "Deblock_UV"] -

-Specifies filters to apply when decoding video. The string is a list of filters separated with a space character.

-
    -
  • "FilmEffect": xvid 1.0.0 filmEffect.
  • -
  • "Deblock_Y": Y plane deblocking filter.
  • -
  • "Deblock_UV": UV plane deblocking filter.
  • -
-Threaded [value "yes" "no"] -

Specifies whether the decoder should run in its own thread or not.

- -

- - -Section "FFMPEG" Back to top -

The "FFMPEG" section of the config file holds all configuration options for the FFMPEG demuxer and decoder.

-DataBufferMS [value: positive integer] -

-Specifies the amount of video/audio data (in milliseconds) to be bufferer before starting decoding. For developpers only.

-IOBufferSize [value: positive integer] -

-Specifies the size (in bytes) of the buffer used to fecth data from network (http playback only). Default size is 8192 bytes.

- -

- - -Section "ISOReader" Back to top -

The "ISOReader" section of the config file holds all configuration options for the ISO Media File demuxer.

-IgnoreMPEG-4ForBrands [value: Full 4CC or 4CC pattern (abc* ab*)] -

-Ignores all MPEG-4 systems tracks and IOD for files showing the listed brands in their compatible brand list.

- -

- - -Section "DVB" Back to top -

The "DVB" section of the config file holds all configuration options for DVB playback on GNU/Linux systems.

-ChannelsFile [value: Absolute file path] -

-Specifies the DVB channels configuration file as produced by dvbtools' scan util.

- -

- - -Section "DASH" Back to top -

The "DASH" section of the config file holds all configuration options for DASH or HLS/M3U8 playback.

-KeepFiles [value: yes, no] -

-Specifies whether downloaded files should not be deleted.

-AutoSwitchCount [value: positive integer] -

-For debug purposes, instructs the player to switch representation every N segments. If 0 (default), switching is disabled.

-BufferingMode [value: segments, minBuffer, none] -

-Selects buffer mode: -

    -
  • segments: buffers complete segments as indicated in MPD before handing them to the player.
  • -
  • minBuffer: asks the player to buffer media for the time indicated in the MPD, but segments are not pre-buffered. Default mode.
  • -
  • none: uses the player settings for buffering.
  • -
-

-NetworkAdaptation [value: disabled, bandwidth, buffer] -

-Sets automatic adaptation logic mode: -

    -
  • disabled: no adaptation to network condition is used.
  • -
  • bandwidth: network adaptation is based only on available download rate.
  • -
  • buffer: network adaptation is based on available download rate for quality increase and buffer levels for quality drops. Default mode.
  • -
- -

-MemoryStorage [value: yes, no] -

-Files are only stored in memory and destroyed after playback, no disk IO is used. Default is yes

-UseMaxResolution [value: yes, no] -

-Forces the player to set the output video resolution to the max resolution available instead of resizing the window. Default is yes

-UseScreenResolution [value: yes, no] -

-Disables all resolutions that are higher than the screen resolution. Default is yes

-StartRepresentation [value: minBandwidth, maxBandwidth, maxBandwidthTiles, minQuality, maxQuality] -

-Instructs the DASH client to start playing the indicated representation before doing any switching. Default is minBandwidth. -maxBandwidthTiles selects the highest bandwidth for tiles with high priority and lowest for other tiles, whereas maxBandwidth selects the highest bandwidth for tiles with high priority and closest lower bandwidth for other tiles. -

-InitialTimeshift [value: positive integer ] -

-If between 0 and 100, indicates the percentage of the timeshift buffer when starting playback.
-If more than 100, indicates the number of milliseconds to rewind in the timeshift buffer when starting playback.
-Default is 0 to tune to the live point.

-LowLatency [value: always, chunk, no] -

-Sets low-latency mode enabled. In low-latency mode, media data is parsed as soon as possible while segment is being downloaded. Default is no. -If chunk is selected, media data is re-parsed at each HTTP 1.1 chunk end. If always is selected, media data is re-parsed as soon as HTTP data is received.

-AllowAbort [value: yes, no] -

-Enables aborts of HTTP transfer when rate gets too low. This imply data loss and may also result in a connection loss. Default is no.

-UseServerUTC [value: yes, no] -

-Enables using Server-UTC HTTP header to compensate any drift between client and server. Default is yes.

-DebugAdaptationSet [value: integer] -

-Plays only the adaptation set indicated by its index in the MPD. If index is negative, all sets are used (default mode). -

-TimeBetween404 [value: unsigned integer] -

-Sets how many millisconds to wait between two 404 on the same segment. Default is 500. -

-SegmentExpirationThreshold [value: unsigned integer] -

-Sets how many millisconds to wait after the segment AvailabilityEndDate before considering the segment lost. Default is 100. -

-ThreadedDownload [value: yes no] -

-Enables threade download of media segments. When low latency mode is used, this option is forced to yes. Default is no. -

-SpeedAdaptation [value: yes no] -

-Enables adaptation based on playback speed. Default is no. -

-SwitchProbeCount [value: unsigned integer] -

-Sets how many segments the client shall wait before switching up bandwidth. If 0, switch will happen as soon as the bandwidth is enough, but this is more prone to network variations. -Default value is 1. -

-AgressiveSwitching [value: yes no] -

-If yes, switching targets to the closest bandwidth fitting the available download rate. If no, switching targets the lowest bitrate representation that is above the currently played (eg does not try to switch to max bandwidth). Default value is no. -

-TileAdaptation [value: none, rows, reverseRows, middleRows, columns, reverseColumns, middleColumns, center] -

-Selects how bitrate is shared across tiles of a video: -

    -
  • none: bitrate is shared equaly accross all tiles (default).
  • -
  • rows: bitrate decreases for each row of tiles starting from the top, same rate for each tile on the row.
  • -
  • reverseRows: bitrate decreases for each row of tiles starting from the bottom, same rate for each tile on the row.
  • -
  • middleRows: bitrate decreased for top and bottom rows only, same rate for each tile on the row.
  • -
  • columns: bitrate decreases for each columns of tiles starting from the left, same rate for each tile on the columns.
  • -
  • reverseColumns: bitrate decreases for each columns of tiles starting from the right, same rate for each tile on the columns.
  • -
  • middleColumns: bitrate decreased for left and right columns only, same rate for each tile on the columns.
  • -
  • center: bitrate decreased for all tiles on the edge of the picture.
  • -
  • edges: bitrate decreased for all tiles on the center of the picture.
  • -
-

-TileRateDecrease [value: percentage] -

-Indicates the amount of bandwidth to use at each quality level. The rate is recursively applied at each level, e.g. if 50%, Level1 gets 50%, level2 gets 25%, ... If 100, automatic rate allocation will be done by maximizing the quality in order of priority. -If 0, bitstream will not be smoothed across tiles/qualities, and concurrency may happen between different media. -Default is 100.

- - -

- - -Section "ALSA" Back to top -

The "ALSA" section of the config file holds all configuration options of the ALSA audio output module on GNU/Linux systems.

-DeviceName [value: string] -

-Specifies the ALSA device to use. Default device is "hw:0,0".

- -

- - -Section "StreamingText" Back to top -

The "StreamingText" section of the config file holds all configuration options for the 3GPP/MPEG-4 Streaming Text decoder.

-UseTexturing [value: "yes" "no"] -

-Specifies whether the text shall be drawn using an intermediate texture or not.<OutlineText [value: "yes" "no"] -

-Specifies whether the text shall be drawn with a black thin outline or not.

- - -

- - -Section "Shortcuts" Back to top -

The "Shortcuts" section of the config file holds all shortcuts in GPAC. You can define a shorcut for an action as:
-'action'='keyname' or 'action'='ctrl+keyname' or 'action'='alt+keyname' or "='action'='ctrl+alt+keyname'
-Shift is not supported as a key modifier, and case is insensitive. -Currently defined actions are as follows:

-Play or Pause
-Stop
-Step
-Exit
-Mute
-VolumeUp
-VolumeDown
-JumpForward
-JumpBackward
-JumpStart
-JumpEnd
-FastForward
-FineForward
-SlowForward
-FastRewind
-FineRewind
-SlowRewind
-Next
-Previous
- -

- - -Section "DSMCC" Back to top -

-The "DSMCC" section of the config file holds the configuration option for the processing of DSMCC data. -

-Activated [value: "true" "false"] -

-Specifies if the DSMCC data will be processed (true). It implies creations of directories and files in the temp directory.

- - -

- - -Section "OpenHEVC" Back to top -

-The openHEVC section of the config file holds the configuration option for the OpenHEVC decoder. -

-NumThreads [value: unsigned integer] -

-Specifies the number of threads to allocate to the OpenHEVC decoder. Default is the number of detected cores minus one, or one if core detection fails.

-ThreadingType [value: frame , wpp , frame+wpp ] -

-Specifies the threading type for the openHEVC decoder. Default is frame (wpp disabled).

-CBUnits [value: unsigned integer ] -

-Specifies the number of decoded frames in memory before display. Default value is 4.

-PackHFR [value: yes, no ] -

-Packs 4 consecutive frames in a single 4x frame.

- - -

- - -Section "DirectFB" Back to top -

-The "DirectFB" section of the config file holds the configuration options for the DirectFB output module. You may also want to check the official documentation. -

-DisableAcceleration [value: "yes" "no"] -

-Forces to disable hardware acceleration.

-DisableDisplay [value: "yes" "no"] -

-Specifies the DisableDisplay parameter value.

-FlipSyncMode [value: "waitsync" "wait" "sync" "swap"] -

-Specifies the flip sync mode.

-DisableBlit [value: "yes" "no"] -

-Forces to disable hardware blitting.

-WindowMode [value: "X11" "SDL"] -

-Specifies the underlying windowing library.

- - -

- - -Section "DektecVideo" Back to top -

-The "DektecVideo" section of the config file holds the configuration options for the Dektec SDI video output module. -

-SDIOutput [value: unsigned integer] -

-Specifies the output port of the card. Default value is 1.

- -

- - -Section "M2TS" Back to top -

-The "M2TS" section of the config file holds configuration options for MPEG-2 TS demultiplexer module. -

-ForceTEMILocation [value: URL string] -

-Overrides the URL in TEMI location descriptor with the specified value.

-RecordTo [value: file path] -

-Records the TS content to the specified file.

- - -Section "RAWVideo" Back to top -

-The "RAWVideo" section of the config file holds configuration options for Raw video and audio output module. -

-RawOutput [value: "null" or any string] -

-If set to null string, the raw output does nothing (no bliting calls, no backbuffer) - used for bench mode only.

-PixelFormat [value: 555, 565, rgb, bgr, rgb32, bgr32, rgba, argb] -

-Sets the output video format of the raw module, mostly used for testing. The default format is rgb.

- - - diff --git a/doc/gpac.mp4 b/doc/gpac.mp4 deleted file mode 100644 index ac25dd75d0dcb6b4f404cf2b2c28a7ee50f93b88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2688 zcmZuz3s6&M7XEK8#AvX~QwhW+3gwX$7LyM zE^d9G$U=x6X(KB%;|#A+B4r0HwDNL9XB}IaDU0IZxZSlf%kGw4XU~6gvu2$=Gw1&2 zeCPbCck+4SFOLCaqBorog0qdCBA=LgeCGqjX}nL0f6|Po=b!$#3JaK$R;O z6shtHQl)~t!tHwmJJeO$N*$g5z+|k_LrzGQ$=3kv1g#I_Yt1?w!-8pt)S4>=jP0Q; z6_sEYP!@_l3kJ`Gv!eHl@so7G25dy~;1Lu?tVOGqv5zH~B}iuzc$=dsizRRxw8QH| zd?>HMsUVm_phiOM2p#d2#4*4$)jka8vVMuss^=!H+AflyDE+J-ow_y~j2W8(7n zd0-VKE6;VzK4 z(-Y-oOVGvHsi|)sJDmd@#8hr~gJOKErOMs|KUH8u5t z)9I1{Ov0?Safy2Uc}8sj`bBWovrmB#CldH?0$t+^up?ki^#X7>du=HAyFYRco*(gr z=x4_*xWM7(Z3xAIsxbC%iA4Glw8dm0`a=Ho!bBej*sllJ4!L1JLg60)gf$wSG&a4% z#5uKdD9m>2C*k!q@9s1wm##72{8hKa)WU@6A?blcQZVjxUIhKTF0Tfmi%;RCu+G66 zAcy@k8$4kTk6gGp6B7n^1-6aY1AD^-#diarApsyQ!@Ko;xP)JD5d9ODh;@gW8v&EB zkjsN5D}PS@+gD3v$t$2sU{3;ur{cm~#>X*QnfytJVOm`9={a~V&cU^q`S{>S#>X*Q znf$Mq{v5f?A%i-MII^9tkC!A;>^c(l2W*e@^b6-${p2@xjSshftv>)t?qEM z+ygL&#-T5s*&=)FA@u69IjR06s(jPh1wl!3I1H%M;mv z^U){y_vLQ4ede%rYaN$IxvZY%SdqlqyfuZQM4hD@a?M6{Wml=toX{}>m%CIX^$uJO zjqehC)|8NWxvwcXy}z%kd>|($Ys321{=xL#*6b@+y_@^`Grf|&)=cqLwR|1?-@!G_ zxsx-aZ~eYKHFmT%C{oO-7oQ>HNs@IZo(y&bT^OxkRRq1ckdeo_9rX7tcbD?mtzvfg zs)EFI+xOmGpV-GFwv*vmU-I_wz@-iP**&l$6#zq%2YeMWrX;pI@C?%zIE4dowon*3Y z`Y*58B0MhPio~O-Sm_rvyOh=%qOp~l+?bs?>78zA7(da$Ds10~P#0e@zj(-C)EMN3YNJMnguMBr^~F(8 z*U?e&>g=J_mQ6A-H->)89kXvfp}B!1b+le - GPAC (c) 2000-2005 -. -.SH "SEE ALSO" -.LP -MP4Client(1), MP4Box(1) diff --git a/doc/man/mp42avi.1 b/doc/man/mp42avi.1 deleted file mode 100644 index 5f4adca..0000000 --- a/doc/man/mp42avi.1 +++ /dev/null @@ -1,69 +0,0 @@ -.TH "MP42AVI" 1 "March 2005" "MP42AVI" "GPAC" -. -.SH NAME -.LP -MP42AVI \- MPEG-4 BIFS to Video converter -.SH "SYNOPSIS" -.LP -.B MP42AVI -.RI [options] \ file\ -.br -. -.SH "DESCRIPTION" -.LP -MP42AVI is a tool to convert a pure MPEG-4 BIFS presentation (no audio, no image, no video) to an uncompressed sequence of images or an uncompressed RGB avi file. -. -. -.SH OPTIONS -.P -.TP -.B \-fps Framerate -specifies extraction framerate. If not set the framerate is computed from the BIFS track duration. -.TP -.B \-size WxH -forces BIFS scene size to the given resolution -.TP -.B \-raw [frame] -uses raw format for output. If frame is specified, only the given frame is dumped. Otherwise, all samples are dumped into raw files. -.TP -.B \-bmp [frame] -uses BMP format for output. If frame is specified, only the given frame is dumped. Otherwise, all samples are dumped into BMP files. -.TP -.B \-outpath path -specifies output directory to dump frames/movie. -.TP -.B \-cfg -specifies path to GPAC config file. -.TP -.B \-v -prints build version. -.TP -.B \-h -prints this message -. -.SH SPECIAL CONSIDERATIONS -.TP -.B Frame Dumping -When dumping a frame, either the frame number (sample number) can be specified or the frame time -in the format hh:mm:ss:xFz where hh, mm, ss are hours, minutes, seconds, x the number -of the frame in the seconds and z the frame rate used to express the time -.TP -.br -.B Input Files -The current version of MP42AVI is only designed to handle pure BIFS presentation. If the input BIFS scene uses audio or visual media, these are ignored during the dump. It may even crash MP42AVI. -.TP -.B GPAC Config File -MP42AVI needs the GPAC configuration file in order to locate the different plugins used for rasterization. You must therefore have launched successfully MP4Client before trying to use MP42AVI -. -.SH "FILES" -.LP -.B GPAC Configuration File: -~/.gpacrc -. -.SH "AUTHORS" -.LP -Jean Le Feuvre - GPAC (c) 2000-2005 -. -.SH "SEE ALSO" -.LP -MP4Client(1), MP4Box(1), GPAC(1) diff --git a/doc/man/mp42ts.1 b/doc/man/mp42ts.1 deleted file mode 100644 index 0c7a05d..0000000 --- a/doc/man/mp42ts.1 +++ /dev/null @@ -1,172 +0,0 @@ -.TH MP42TS "1" "July 2015" "MP42TS" "GPAC" - -.SH NAME -MP42TS - MPEG-4 Systems Framework and Software Development Kit - -.SH SYNOPSIS -.B MP42TS -.RI [options] - -.SH OPTIONS -.SS "Inputs" -.TP -\fB\-src \fRfilename[:OPTS] -specifies an input file used for a TS service: -.br - * currently only supports ISO files and SDP files -.br - * can be used several times, once for each program -.br -By default each source is a program in a TS. -Source options are colon-separated list of options, as follows: -.br - -.br -\fBID\fR=\fBN \fRspecifies the program ID for this source. -All sources with the same ID will be added to the same program -.br -\fBname\fR=\fBSTR \fRprogram name, as used in DVB service description table -.br -\fBprovider\fR=\fBSTR \fRprovider name, as used in DVB service description table - -.TP -\fB\-prog filename -same as "-src filename" - -.SS "Destinations" -Several destinations may be specified as follows, at least one is mandatory: -.TP -\fB\-dst-udp \fRUDP_address:port (multicast or unicast) -.TP -\fB\-dst-rtp \fRRTP_address:port -.TP -\fB\-dst-file \fRfilename - -The following parameters may be specified when -dst-file is used: -.TP -\fB\-segment-dir \fRdir -server local directory to store segments (ends with a '/') -.TP -\fB\-segment-duration \fRdur -segment duration in seconds -.TP -\fB\-segment-manifest \fRfile -m3u8 file basename -.TP -\fB\-segment-http-prefix \fRp -client address for accessing server segments -.TP -\fB\-segment-number \fRn -number of segments to list in the manifest - -.SS "Basic options" -.TP -\fB\-rate \fRR -specifies target rate in kbps of the multiplex (optional) -.TP -\fB\-real-time -specifies the muxer will work in real-time mode -.br - * if not specified, the muxer will generate the TS as quickly as possible -.br - * automatically set for SDP or BT input -.TP -\fB\-pcr-init \fRV -sets initial value V for PCR - if not set, random value is used -.TP -\fB\-pcr-offset \fRV -offsets all timestamps from PCR by V, in 90kHz. Default value is computed based on input media. -.TP -\fB\-psi-rate \fRV -sets PSI refresh rate V in ms (default 100ms). -.br - * If 0, PSI data is only send once at the beginning or before each IDR when -rap option is set. -.br - * This should be set to 0 for DASH streams. -.TP -\fB\-time \fRn -request the muxer to stop after n ms -.TP -\fB\-single-au -forces 1 PES = 1 AU (disabled by default) -.TP -\fB\-rap -forces RAP/IDR to be aligned with PES start for video streams (disabled by default). -In this mode, PAT, PMT and PCR will be inserted before the first TS packet of the RAP PES. -.TP -\fB\-flush-rap -same as -rap but flushes all other streams (sends remaining PES packets) before inserting PAT/PMT -.TP -\fB\-nb-pack \fRN -specifies to pack up to N TS packets together before sending on network or writing to file -.TP -\fB\-pcr-ms \fRN -sets max interval in ms between 2 PCR. Default is 100 ms -.TP -\fB\-ttl \fRN -specifies Time-To-Live for multicast. Default is 1. -.TP -\fB\-ifce \fRIPIFCE -specifies default IP interface to use. Default is IF_ANY. -.TP -\fB\-temi \fR[URL] -Inserts TEMI time codes in adaptation field. URL is optional -.TP -\fB\-temi-delay \fRDelayMS -Specifies delay between two TEMI url descriptors (default is 1000) -.TP -\fB\-temi-offset \fROffsetMS -Specifies an offset in ms to add to TEMI (by default TEMI starts at 0) -.TP -\fB\-temi-noloop -Do not restart the TEMI timeline at the end of the source -.TP -\fB\-sdt-rate \fRMS -Gives the SDT carrousel rate in milliseconds. If 0 (default), SDT is not sent - -.SS "MPEG-4/T-DMB options" -.TP -\fB\-bifs-src \fRfilename -update file: must be either an .sdp or a .bt file -.TP -\fB\-audio \fRurl -may be mp3/udp or aac/http (shoutcast/icecast) -.TP -\fB\-video \fRurl -shall be a raw h264 frame -.TP -\fB\-mpeg4-carousel \fRn -carousel period in ms -.TP -\fB\-mpeg4\fR,\fB \-4on2 -forces usage of MPEG-4 signaling (IOD and SL Config) -.TP -\fB\-4over2 -same as -4on2 and uses PMT to carry OD Updates -.TP -\fB\-bifs-pes -carries BIFS over PES instead of sections -.TP -\fB\-bifs-pes-ex -carries BIFS over PES without writing timestamps in SL - -.SS "Misc options" -.TP -\fB\-logs -set log tools and levels, formatted as a ':'-separated list of toolX[:toolZ]@levelX -.TP -\fB\-h\fR,\fB \-help -print this screen - -.SH BUGS and OTHER HELP -For bug reports, more information on BT or XMT-A formats or GPAC TTXT files or ISMACryp, -MPEG-4 Systems usage and more help on MP42TS please visit the GPAC web site http://www.gpac.io - -.SH "AUTHORS" -Jean Le Feuvre, Cyril Concolato, Romain Bouqueau. -Copyright \(co Telecom ParisTech 2005-2012. - -.SH "SEE ALSO" -.BR GPAC (1), -.BR MP4Box (1), -.BR MP4Client (1) diff --git a/doc/man/mp4box.1 b/doc/man/mp4box.1 deleted file mode 100644 index f19ab4c..0000000 --- a/doc/man/mp4box.1 +++ /dev/null @@ -1,808 +0,0 @@ -.TH "MP4Box" 1 "June 2005" "MP4Box" "GPAC" -. -.SH NAME -.LP -MP4Box \- MPEG-4 Systems Toolbox -.SH "SYNOPSIS" -.LP -.B MP4Box -.RI [options] \ file \ [options] -.br -. -.SH "DESCRIPTION" -.LP -MP4Box is a multi-purpose command line tool to create and edit MPEG-4 Systems presentations and manipulate ISO-media files (MP4, 3GP, MOV). -.LP -MP4Box supports file conversion from various raw formats and IsoMedia/AVI/MPEG-PS/OGG containers, file hinting for RTP streaming for QuickTime compatible streaming servers, file interleaving, file fragmentation and track extraction. -.LP -MP4Box also provides dump tools used to inspect file layout, RTP hint tracks, SDP information, scene composition. It may also be used to convert to and from BT/XMT-A/VRML/X3D. -.LP -MP4Box also features MPEG-4 Systems encoders and decoders for BIFS and OD tools. -.LP -MP4Box doesn't expect any particular order in options at prompt. -. -.SH GETTING HELP WITH MP4Box -.TP -.B \-h [type] -prints help screen. "type" can be one of "general", "hint", "import", "encode", "extract", "dump", "swf", "meta", "crypt", "format", "rtp", "live" for specific help. -.TP -.B \-version -prints version of MP4Box. -.TP -.B \-noprog -disable progress reports. -.TP -.B \-quiet -quiet mode (no log info). -.TP -.B \-v -verbose mode (more log info). -.TP -.B \-strict-error -exits after the first error is reported. -.TP -.B \-nodes -lists supported MPEG-4 Systems nodes in MP4Box current build. -.TP -.B \-node NodeName -prints the node syntax. Each field is listed as -.br -.I eventType fieldType fieldName defaultValue -.br -and field quantization information and quantization bounds if any. -. -.TP -.B \-xnodes -lists supported X3D nodes in MP4Box current build. -.TP -.B \-xnode NodeName -prints the X3D node syntax. Each field is listed as -. -.TP -.B \-snodes -lists supported SVG/LASeR nodes in MP4Box current build. -. -.SH GENERAL OPTIONS -.P -.TP -.B \-inter duration -interleaves media data in chunks of the maximum specified duration (expressed in milliseconds) and prepare file for HTTP/FTP streaming. By default MP4Box always stores with 0.5 second interleaving. An interleaving value of '0' stores without interleaving (meta-data then track after track). -.br -.B \-old-inter duration -same as -inter but without drift adjustment -.br -.B NOTE: Track Edit Lists are ignored when performing interleaving. -.TP -.B \-flat -stores file with all media data first, non interleaved. If used when creating a new file, no temporary file is created (faster storage). -.TP -.B \-frag duration -fragments file using ISO-Media movie fragments. Tracks will be fragmented so that no track run exceeds the specified duration (expressed in milliseconds). Disables interleaving. -.TP -.B -out\ \ -specifies output file name. -.TP -.B -tmp\ \ -specifies driectory for temporary storage. If not set, temporary storage is OS-dependent. -.TP -.B -ffspace size -inserts free space before moof in fragmented files. -.TP -.B \-nosys -removes all MPEG-4 Systems information from the file but keeps an empty IOD for MPEG-4 Profile@Level indications. alias: -no-sys -.TP -.B \-no-iod -removes InitialObjectDescriptor from file. -.TP -.B \-isma -rewrites the file as an ISMA 1.0 Audio/Video file (all systems info rewritten) with proper clock references. -.TP -.B \-ismax -rewrites the file as an ISMA 1.0 Audio/Video file (all systems info rewritten) with ISMA clock references (streams not explicetly synchronized). -.br -.B NOTE: Seeking may be broken with some players (in ISMA, audio and video streams do not depend on main scene clock). -.TP -.B \-3gp -rewrites the file as a 3GP file (no more MPEG-4 Systems specific info). This option is turned on by default when input file extension is .3gp or .3g2. -.TP -.B \-ipod -rewrites the file for iPod. -.TP -.B \-brand ABCD[:v] -sets major brand of file, with optional version. -.TP -.B \-ab ABCD -adds given brand to file's alternate brand list. -.TP -.B \-rb ABCD -removes given brand to file's alternate brand list. -.TP -.B -cprt string -adds string copyright notice to file. -.TP -.B \-chap chap_file -adds chapter information contained in chap_file to movie. For more details on chapter file syntax, cf http://gpac.sourceforge.net/auth_mp4box.php. -.TP -.B -rem trackID -removes given track from file - can be specified multiple times. -.TP -.B -enable trackID -enables given track. -.TP -.B -disable trackID -disables given track. -.TP -.B -new -forces creation of a new destination file. -.TP -.B -lang [tkID=]LAN -sets the language of all tracks or the given track. LAN is the ISO 639-2 3 character code. -.TP -.B -delay [tkID=]delay_ms -sets the delay in milliseconds of all tracks or the given track. LAN is the ISO 639-2 3 character code. -.TP -.B -par tkID=PAR -sets visual track pixel aspect ratio (PAR=Num:Den or "none"). -.TP -.B -name tkID=name -sets track handler name. -.TP -.B -itags tag1[:tag2] -sets iTunes tags to file - more info: MP4Box -tag-list. -.TP -.B \-split time_in_seconds -splits in files of desired maximum duration. This will remove all MPEG-4 Systems media. -.TP -.B \-split-size size_in_kilobytes -splits in files of desired maximum size. This will remove all MPEG-4 Systems media. Alias: -splits -.TP -.B \-split-chunk start:end -extracts a new file from specified start to end times (in seconds). This will remove all MPEG-4 Systems media. Alias: -splitx -.TP -.B \-splitz start:end -same as -split-chunk option, but adjust end time to be juste before the last rap found in the chunk. -.TP -.B \-split-rap start:end -splits in files begining at each RAP. This will remove all MPEG-4 Systems media. Alias: -splitr -.TP -.B \-group-add fmt -creates a new grouping information in the file. Format is a colon-separated list of following options: -.br -refTrack=ID: ID of the track used as a group reference. If not set, the track will belong to the same group as the previous trackID specified. If 0 or no previous track specified, a new alternate group will be created. -.br -switchID=ID: ID of the switch group to create. If 0, a new ID will be computed for you. If <0, disables SwitchGroup. -.br -criteria=string: list of space-separated 4CCs. -.br -trackID=ID: ID of the track to add to this group. -.br -*WARNING* Options modify state as they are parsed: trackID=1:criteria=lang:trackID=2 is different from: criteria=lang:trackID=1:trackID=2. -.B \-group-rem-track ID -removes track from its group. -.TP -.B \-group-rem ID -removes the track's group. -.TP -.B \-group-clean -removes all group information from all tracks. -.TP -.B \-ref id:XXXX:refID -adds a reference of type 4CC from track ID to track refID. -.TP -.B \-dash DUR -enables DASH-ing of the file with a segment duration of DUR. -.TP -.B \-rap -segments begin with random access points. -.TP -.B \-frags-per-sidx N -sets the number of segments to be written in each SIDX box. -.TP -.B \-segment-name name -sets the segment name for generated segments. -.TP -.B \-segment-ext name -sets the segment extension name. Default is m4s. -.TP -.B \-url-template -uses UrlTemplate instead of explicit sources in segments. -.TP -.B \-daisy-chain -Uses daisy-chain SIDX instead of hierarchical. Ignored if frags/sidx is 0. -.TP -.B \-dash-ctx FILE -Stores/restore DASH timing from FILE. -.TP -.B \-dash-ts-prog N -Program_number to be considered in case of an MPTS input file. - -.B When input file is an ISO-Media file (QT, MP4, 3GP), if no output is specified THE INPUT FILE IS OVERWRITTEN. -. -. -.SH HINTING OPTIONS -.TP -.B \-hint -hint the file for RTP\/RTSP sessions. Payload type is automatically detected and configured unless forced through one of MPEG-4 Generic RTP payload. -.TP -.B \-mtu size -specifies Maximum Transmission Unit size in bytes (eg maximum RTP packet size). Default size is 1500 bytes (Ethernet MTU). This must be choosen carefully: specifying too large packets will result in undesired packet fragmentation at UDP layer while specifying too small packets will result in RTP header overhead. -.TP -.B \-copy -forces hinted data to be copied to the hint track instead of simply referenced. This speeds up RTP packet construction at the server side but results in much bigger files. -.TP -.B \-tight -performs sample-based interleaving of media tracks and hint tracks. This should reduce disk seeks at server side (depending on server implementation) but results in a bigger file. -.TP -.B \-multi [maxptime] -enables Access Units concatenation in RTP packets if possible. maxptime is optional and specifies the maximum packet duration in milliseconds (default 100). -.TP -.B \-rate ck_rate -specifies the rtp rate in Hz when no default rate for payload. Default value is 90000 (MPEG rtp rates). -.TP -.B \-mpeg4 -forces usage of the MPEG-4 generic payload whenever possible. Media tracks without a mapping to MPEG-4 Systems cannot use this. -.TP -.B \-latm -forces usage of the LATM payload for AAC audio. -.TP -.B \-static -enables usage of static RTP payload IDs for streams with official payload IDs. By default MP4Box always uses dynamic payload IDs for maximum interoperability, some players having troubles with static ones. -. -.P -.B MPEG-4 Generic Payload Options (Experts only) -.IP -.B \-ocr -forces all media tracks in the file to be served synchronized. This is needed because most streaming servers don't support desynchronized tracks in a single file. Be extremelly carefull when designing MPEG-4 interactive presentations for streaming since you will have to take care of the streaming server capabilities... MP4Box generates warnings when the file timeline can be ambiguously interpreted by the server. -.IP -.B \-rap -signals Access Units random access flag in RTP packets. This is usually only needed for streaming of MPEG-4 Systems streams. -.IP -.B \-ts -signals Access Units Time Stamps (CTS and DTS) in RTP packets. -.IP -.B \-size -signals Access Units size in RTP packets. -.IP -.B \-idx -signals Access Units indexes (sequence numbers) in RTP packets. -.IP -.B \-multi -enables Access Units concatenation in RTP packets (-ts, -size and -idx are selected if needed). -.IP -.B \-iod -prevents system tracks embedding in IOD (ISMA-like IOD) when generating in SDP. MP4Box automatically detects ambiguous (ISMA/non-ISMA) files but nobody's perfect. This shouldn't be used with -isma option. -. -.TP -.B \-add-sdp string -adds string to movie SDP or track SDP (tkID:string, where tkID is the OD of the hint track or its media track). This takes care of SDP line reordering, but not of SDP content validity. -.TP -.B \-unhint -removes all hint tracks and other hinting info from the file. -. -.SH IMPORT OPTIONS -.TP -.B \-add -adds all src_file tracks to input file, creating it if not exisiting. Up to 20 cumulated -add operations can be used. Supported syntaxes are: -.br -file#video: imports first video track from src_file. -.br -file#audio: imports first audio track from src_file. -.br -file#trackID=ID or file#ID: imports given trackfrom src_file. To get a listing of tracks in input file, use -info [ID] -.br -[;lang=LAN]: specifies language of imported media. -.br -[;delay=delay_ms]: specifies initial delay in milliseconds of imported media. -.TP -.B \-cat -concatenates all src_file tracks to input file, creating it if not exisiting. Media samples are added at the end of existing compatible tracks. If no compatible track is found for a media it is created. Up to 20 cumulated -cat operations can be used. Syntax is the same as -add. -.TP -.B \-force-cat -skips media configuration check when concatenating file. -.TP -.B \-keepsys -by default all MPEG-4 systems media are removed with -add and -cat. This option will avoid removing them from final file. -.TP -.B \-keep-all -keeps all existing tracks when add file. -.TP -.B \-dref -keeps media data in original file an only imports meta-data (frame timing, size and random access). -.br -.TP -.B NOTE -Data referencing may fail with some AVI because it requires the framed data (eg an MP4 sample) to be continuous in the original file, which is not always the case depending on the original interleaving. -.TP -.B \-no-drop -forces constant FPS when importing AVI video. By default non coded frames (n-vop) are removed at import time, resulting in a variable frame-rate media. -.TP -.B \-packed -for CMP/M4V (raw MPEG-4 Visual), forces packed-bitstream mode (removes all n-vops and import at constant frame rate). -.TP -.B \-sbr -imports AAC as AAC-SBR, with backward compatible signaling (non AAC-SBR decoders should be able to play it). -.TP -.B \-sbrx -imports AAC as AAC-SBR, with non-backward compatible signaling (non AAC-SBR decoders will not be able to play it). -.TP -.B \-ovsbr -imports AAC as AAC-SBR with oversample SBR. -.TP -.B \-ps -imports AAC as AAC-PS, with backward compatible signaling of AAC-PS. -.TP -.B \-psx -imports AAC as AAC-PS, with non-backward compatible signaling (non AAC-PS decoders will not be able to play it). -.TP -.B \-fps FrameRate -overrides the input video frame rate or specifies it for SUB subtitles. -.TP -.B \-mpeg4 -forces using MPEG-4 sample descriptions rather than 3GP ones (3GP2 QCELP/EVRC/SMV audio only). -.TP -.B \-agg N -aggregates N audio frames in 1 sample (3GP media only). Maximum possible value is 15, and default value is 1 (no aggregation). -. -.SH ENCODING OPTIONS -.TP -.B \-mp4 -specifies input file is for encoding. Supported inputs are BT/XMT-A/WRL/SWF files. Output file name is by default the input file name without extensions plus ".mp4" extension. -.TP -.B \-def -encodes DEF'ed nodes and routes with their textual names. -.TP -.B \-log -generates BIFS encoder log file. -.TP -.B \-ms file -specifies file for track importing - by default FILE.mp4 is used when encoding FILE.bt (in-place rewrite). This option is only needed if you don't provide -a proper muxInfo per stream in the BT/XMT file. -.TP -.B \-sync time -forces BIFS random access point generation every time milliseconds. Cannot be used with -shadow. WARNING: this may result in weird behavior of your presentation since a BIFS random access point restarts all media currently running in the scene (EXPERTS ONLY - DANGEROUS). Cannot be used with -shadow. -.TP -.B \-shadow time -forces BIFS shadow random access points generation every time milliseconds. Shadow samples are random access points that can be used instead of non random access points when seeking. WARNING: this may be not supported by some players (EXPERTS ONLY - DANGEROUS). Cannot be used with -sync. -.TP -.B \-ctx-in file -specifies initial context (MP4/BT/XMTA) for chunk processing. Input file must then be a command-only file: no IOD, and no implicit commands (commands without 'AT'). -.TP -.B \-ctx-out file -specifies output file of updated context (MP4/BT/XMTA) in chunk processing mode. This is optional, chunk processing doesn't need to store the final context. -.TP -.B \-resolution res -LASeR resolution factor (-8 to 7, default 0). All coords are multiplied by 2^res before truncation. -.TP -.B \-coord-bits bits -bits used for encoding truncated coordinates in LASeR. (0 to 31, default 12) -.TP -.B \-scale-bits bits -bits used for encoding truncated scales in LASeR. (0 to 4, default 0) -.TP -.B \-auto-quant res -Use automatic LASeR quantification. resolution is given as if using -resolution but coord-bits and scale-bits are infered. -. -.SH ISMACRYPT OPTIONS -.TP -.B \-crypt drm_file -crypts a specific track using ISMA AES CTR 128. -.TP -.B \-decrypt [drm_file] -decrypts a specific track using ISMA AES CTR 128. drm_file can be omitted if keys are in file. -.TP -.B \-set-kms [tkID=]kms_uri -changes KMS location for all tracks or a given one if tkID is specified. -.TP -.B DRM file syntax for GPAC ISMACryp -File is XML and shall start with xml header. File root is an "ISMACryp" element. File is a list of "ISMACrypTrack" elements. -.br -ISMACrypTrack attributes: -.br -TrackID: ID of track to en/decrypt. -.br -key: AES-128 key formatted (hex string 0x +32 chars. -.br -salt: CTR IV salt key (64 bits) (hex string 0x +16 chars. -.br -Encryption only attributes -.br -Scheme_URI: URI of scheme used. -.br -KMS_URI: URI of key management system - \'self\' writes key and salt in the file. -.br -selectiveType selective encryption type - understood values are "None": all samples encrypted (default), "RAP": only encrypts random access units, "Non-RAP": only encrypts non-random access units, "Rand": random selection is performed", "X": Encrypts every first sample out of X, "RandX": Encrypts one random sample out of X. -.br -ipmpType: IPMP Signaling Type: None, IPMP, IPMPX. -.br -ipmpDescriptorID: IPMP_Descriptor ID to use if IPMP(X) is used. If not set MP4Box will generate one for you. -. - -.SH EXTRACTING OPTIONS -.TP -.B \-raw TrackID -extracts given track in native format when supported. -.TP -.B \-raws TrackID -extract each track sample to a file. Note: 'TrackID:N' extracts Nth sample of the track. -.TP -.B \-nhnt TrackID -extracts given track in NHNT format. All track types except ObjectDescriptors tracks can be exported. -.TP -.B \-nhml TrackID -extracts track in nhml format (XML nhnt). All track types except ObjectDescriptors tracks can be exported. -.TP -.B \-single TrackID -extracts given track to a new mp4 file with a single track. -.TP -.B \-avi TrackID -extracts visual track to an avi file. -.TP -.B \-qcp TrackID -same as '-raw' but defaults to QCP file format for AVRC and SMV audio codecs. -.TP -.B \-aviraw TK -extracts AVI track to its raw format. TK can be one of "video", "audio" or "audioN" for multi-track avi files (cf '-info'). -.TP -.B \-saf -remux file to SAF multiplex. -.TP -.B \-dvbhdemux -demux DVB-H file into IP Datagrams. -.TP -.B \-diod -extracts file IOD in raw format when supported. -. -.SH DUMP OPTIONS -.TP -.B \-info [TrackID] -prints movie and tracks information. If TrackID specified, dumps only extended track info. If input file is not an IsoMedia file, lists known tracks for import. -.TP -.B \-bt -dumps complete scene in a BT file. This will remove unknown MPEG4 nodes. -.TP -.B \-xmt -dumps complete scene in an XMT-A file. This will remove unknown MPEG4 nodes. -.TP -.B \-wrl -dumps complete scene in an VRML97 WRL file. This will remove unknown VRML97 nodes. -.TP -.B \-x3d -dumps complete scene in an X3D XML file. This will remove unknown X3D nodes. -.TP -.B \-x3dv -dumps complete scene in an X3D Text (VRML) file. This will remove unknown X3D nodes. -.TP -.B \-lsr -dumps complete scene in a LASeR+XML file. -.TP -.B \-diso -creates XML image of the file atoms. -.TP -.B \-drtp -creates XML image of all hint tracks samples of a hinted mp4 file. -.TP -.B \-dts -prints sample timing to text output. -.TP -.B \-dcr -creates XML image of all ISMACryp tracks samples of an mp4 file. -.TP -.B \-sdp -creates SDP file associated with a hinted mp4 file. -.TP -.B \-dump-cover -Extracts cover art if any. -.TP -.B \-dump-chap -Extracts chapter list to file if any. -.TP -.B \-ttxt -converts input subtitle to GPAC TTXT format. -.TP -.B \-ttxt trackID -exports given text track to GPAC TTXT format. -.TP -.B \-srt -converts input subtitle to SRT format. -.TP -.B \-srt trackID -exports given text track to SRT subtitle format. -.TP -.B \-std -dumps to stdout rather than file. -.TP -.B \-stat -generates statitistic report on node/field usage for the whole presentation. -.TP -.B \-stats -generates statistic report on node/field usage per BIFS Access Unit. -.TP -.B \-statx -generates statistic report on node/field usage in the scene graph after each BIFS Access Unit. -.TP -.B \-hash -generates SHA-1 Hash of the input file. -. -.SH Meta OPTIONS -. -.LP -MP4Box supports the Meta addition to IsoMedia file format, used to make the file a generic data container (timed or untimed). -.TP -.B \-set-meta args -sets meta object type. Syntax is "ABCD[;tk=N] where: -.br -ABCD is the four character code of the type, or O/NULL to remove the meta object -.br -tk indicates whether the meta is at the root level (no "tk"), at the moov level (tk=0) or at the track level (tk=trackID). -.TP -.B \-add-item args -adds resource to the given meta. args is the item file path followed by ';' separated options: -.br -tk=N: same as above -.br -name=item_name: specifies the item name, otherwise file name is used -.br -mime=mimeType: specifies the item mime type, otherwise application/octet-stream is used -.br -encoding=enctype: specifies the content encoding type -Note that file path set to "this" or "self" will identify the item added as the whole IsoMedia file -.TP -.B \-rem-item args -removes item from given meta. Syntax is itemID[;tk=ID]. -.TP -.B \-set-primary args -sets given item as the primary one of the given meta. Syntax is itemID[;tk=ID]. -.TP -.B \-set-xml args -sets XML to the given meta. Syntax is xml_file_path[;tk=ID][;binary]. -.TP -.B \-rem-xml [tk=ID] -removes XML data from the given meta. -.TP -.B \-dump-xml xml_out_file_name[;tk=ID] -dumps the XML data of the given meta to a file. -.TP -.B \-dump-item itemID[;tk=ID][;path=fileName] -dumps the given item of the the given meta to a file. By default the item name is used as the output file name. -.TP -.B \-package -packages input XML file into an ISO container. all media referenced except hyperlinks are added to file. -.TP -.B \-mgt -packages input XML file into an MPEG-U widget in ISO container. All files contained in the current folder are added to the widget package. -. -.SH RTP STREAMER OPTIONS -. -.LP -MP4Box can stream ISO files to RTP. The streamer currently doesn't support data carrouselling and will therefore not handle BIFS and OD streams properly. -.TP -.B \-rtp -enables rtp file streamer. -.TP -.B \-noloop -disables looping when streaming. -.TP -.B \-mpeg4 -forces MPEG-4 ES Generic for all RTP streams. -.TP -.B \-dst=IP -IP destination (uni/multi-cast). Default: 127.0.0.1. -.TP -.B \-port=PORT -output port of the first stream. Default: 7000. -.TP -.B \-mtu=MTU -path MTU for RTP packets. Default is 1450 bytes. -.TP -.B \-ifce=IP -IP address of the physical interface to use. Default: NULL (ANY). -.TP -.B \-ttl=N -time to live for multicast packets. Default: 1. -.TP -.B \-sdp=FILE -file name of the generated SDP. Default is session.sdp. -. -.SH LIVE SCENE STREAMER OPTIONS -. -.LP -MP4Box can stream BIFS or DIMS content to RTP with carousel generation and updates. The streamer currently doesn't support audio/video/image streaming at the same time. All options supported by the file streamer are supported. -.TP -.B \-live -enables rtp live streamer. -.TP -.B \-dims -turns on DIMS mode for SVG input - default: off. -.TP -.B \-src=FILE -source of updates - default: null. -.TP -.B \-rap=TIME -duration in ms of base carousel - default: 0 (off). you can specify the RAP period of a single ESID (not in DIMS) with -rap=ESID=X:time. -.TP -.B Runtime Options -The following options can be used at prompt: -.br -q: quits -.br -u: inputs some commands to be sent -.br -U: same as u but signals the updates as critical -.br -e: inputs some commands to be sent without being aggregated -.br -E: same as e but signals the updates as critical -.br -f: forces RAP sending -.br -F: forces RAP regeneration and sending -.br -p: dumps current scene -. -. -.SH SWF OPTIONS -. -.LP -MP4Box can import very simple Macromedia Flash files (".SWF"). You can specify a SWF input file with -bt, xmt and -mp4 switches. -.TP -.B \-global -all SWF defines are placed in first scene replace. By default SWF defines are sent when needed. -.TP -.B \-no-ctrl -uses a dedicated stream for movie control. This will disable ActionScript. -.TP -.B \-no-text -removes all SWF text. -.TP -.B \-no-font -removes all embedded SWF Fonts, forcing usage of MPEG-4 Text and terminal fonts. -.TP -.B \-no-line -removes all lines from SWF shapes. -.TP -.B \-no-grad -removes all gradients from swf shapes. -.TP -.B \-quad -uses quadratic bezier curves instead of cubic ones. -.TP -.B \-xlp -support for lines transparency and scalability. -.TP -.B \-flatten Value -replaces 2 consecutive lines by a single one when angle between lines is less than Value (expressed in radians). Value 0 disables flattening. -. -. -.SH SUPPORTED INPUT FORMATS -.TP -.B RAW Formats and extensions -.IP -NHNT (.media .nhnt .info) -.br -MPEG Audio (.mp3) -.br -ADTS-AAC (.aac) -.br -MPEG-4 Visual (.cmp .m4v) -.br -H263 Video (.263 .h263) -.br -AVC/H264 Video (.h264 .h26L .264 .26L) -.br -JPEG Images (.jpg .jpeg) -.br -PNG Images (.png) -.br -AMR(WB) Audio (.amr .awb) -.br -EVRC Audio (.evc) -.br -SMV Audio (.smv) -. -.TP -.B Container Formats and extensions -.IP -AVI (.avi) -.br -MPEG-PS (.mpg .mpeg .vob .vcd .svcd) -.br -QCP (.qcp) -.br -OGG (.ogg) -.br -ISO-Media files (no extension checking) -. -.TP -.B Text Formats and extensions -.IP -.br -SRT Subtitles (.srt) -.br -SUB Subtitles (.sub) -.br -GPAC Timed Text (.ttxt) -.br -QuickTime TeXML Text (.xml) (cf QT documentation) -. -.TP -.B Scene Formats and extensions -.IP -MPEG-4 XMT-A (.xmt .xmta .xmt.gz .xmta.gz) -.br -.br -MPEG-4 BT (.bt .bt.gz) -.br -VRML (.wrl .wrl.gz) -.br -X3D-XML (.x3d .x3d.gz) -.br -X3D-VRML (.x3dv .x3dv.gz) -.br -MacroMedia Flash (.swf) - very limitted import support only -. -. -.SH COMMON USAGE -.TP -.B Importing a file -.IP -Import an AVI: MP4Box -add myfile.avi mymp4.mp4 -.br -Import audio from an AVI: MP4Box -add myfile.avi#audio mymp4.mp4 -.br -Import video from an AVI: MP4Box -add myfile.avi#video mymp4.mp4 -.br -Import an MP3 and leave data outside: MP4Box -dref -add myfile.mp3 mymp4.mp4 -.br -Import a JPEG or PNG: MP4Box -add myfile.jpg mymp4.mp4 -.br -Create a movie with subtitle: MP4Box -add video.avi -add audio.mp3 -add Subtitle.srt mymp4.mp4 -.br -.TP -.B Base File operations -.IP -Convert file for HTTP/FTP streaming: MP4Box -inter 1000 myfile.mp4 -.br -Convert unknown file to ISMA for HTTP/FTP streaming: MP4Box -isma -inter 1000 myfile.mp4 -.br -Convert unknown file to ISMA and respect ISMA broken sync: MP4Box -ismax myfile.mp4 -.br -Convert unknown file to 3GP : MP4Box -3gp myfile.mp4 -.br -.TP -.B Hinting a File -.IP -Prepare any mp4 for ISMA streaming: MP4Box -isma -hint myfile.mp4 -.br -Prepare any 3GP for streaming: MP4Box -hint myfile.3gp -.br -Prepare any 3GP for streaming with RTP aggregation : MP4Box -hint -multi myfile.3gp -.br -Prepare a simple audio/video mp4 optimized for server: MP4Box -hint -copy -tight myfile.mp4 -.br -Prepare a complex mp4 with BIFS for streaming: MP4Box -ocr -iod -hint myfile.mp4 -.br -.TP -.B Encoding/Decoding a file -.IP -Encode a BT file and keep node names for later inspection: MP4Box -mp4 -def pres.bt -.br -Encode a XMT-A file: MP4Box -mp4 pres.xmt -.br -Translate BT to XMT-A file: MP4Box -xmt pres.bt -.br -Translate XMT-A to BT file: MP4Box -bt pres.xmt -.br -Decode MP4 to BT: MP4Box -bt pres.mp4 -.br -. -.SH BUGS and OTHER HELP -.TP -For bug reports, more information on BT or XMT-A formats or GPAC TTXT files or ISMACryp, MPEG-4 Systems usage and more help on MP4Box please visit the GPAC web site http://gpac.sourceforge.net -. -.SH "AUTHORS" -.LP -Jean Le Feuvre - GPAC (c) 2000-2005 - (c) Telecom ParisTech 2005-2011 -. -.SH "SEE ALSO" -.LP -GPAC(1), MP4Client(1) diff --git a/doc/man/mp4client.1 b/doc/man/mp4client.1 deleted file mode 100644 index 8ede331..0000000 --- a/doc/man/mp4client.1 +++ /dev/null @@ -1,301 +0,0 @@ -.TH "MP4Client" 1 "March 2005" "MP4Client" "GPAC" -. -.SH NAME -.LP -MP4Client \- GPAC MPEG-4 command-line Player -.SH "SYNOPSIS" -.LP -.B MP4Client -.RI [options] \ [file] -.br -. -.SH "DESCRIPTION" -.LP -MP4Client is GPAC command-line player. It supports all GPAC playback features (2D and 3D support, local playback, RTP streaming, HTTP faststart, many audio and video codecs ...). MP4Client also supports visual extraction to BMP, RAW or AVI (no compression, no audio). -. -. -.SH STARTUP OPTIONS -.P -.TP -.B \-c file -specifies config file to use. Default config file is located in user home directory and created if not found. -.TP -.B \-rti file -logs run-time information to file. Information logged is: FPS, CPU, Memory usage. -.TP -.B \-quiet -removes script message, buffering and downloading status. -.TP -.B \-strict-error -exits after the first error is reported. -.TP -.B \-log-file FILE -specifies where to write logs. Default is stdout. Same as -lf. -.TP -.B \-logs TOOLS -sets log tools and levels, formatted as a ':'-separated list of toolX[:toolZ]@level. By default all errors are logged. level can be one of quiet, error, warning, info or debug. tool can be: -.br -core: libgpac core -.br -coding: bitstream formats (audio, video, scene) -.br -container: container formats (ISO File, MPEG-2 TS, AVI, ...) -.br -network: network data exept RTP trafic -.br -rtp: rtp trafic -.br -author: authoring tools (hint, import, export) -.br -sync: terminal sync layer -.br -codec: terminal codec messages -.br -parser: scene parsers (svg, xmt, bt) and other -.br -media: terminal media object management -.br -scene: scene graph and scene manager -.br -script: scripting engine messages -.br -interact: interaction engine (events, scripts, etc) -.br -smil: SMIL timing engine -.br -compose: composition engine (2D, 3D, etc) -.br -mmio: Audio/Video HW I/O management -.br -rti: various run-time stats -.br -cache: HTTP cache subsystem -.br -audio: Audio renderer and mixers -.br -mem: GPAC memory tracker -.br -module: GPAC modules debugging -.br -mutex: mutex -.br -none: no tool logged -.br -all: all tools logged - other tools can be specified afterwards. -.br -. -.SH PLAYBACK OPTIONS -A file can be controled during playback by typing one of the following key at prompt. -.TP -.B o -connects to a new URL entered at prompt by user. -.TP -.B r -restarts the current presentation -.TP -.B p -plays/pauses the current presentation -.TP -.B s -step one frame ahead in the current presentation -.TP -.B z -seek into presentation. Seek percentage is entered at prompt -.TP -.B t -prints current timing of the main timeline of the presentation -.TP -.B w -prints world info of the current presentation -.TP -.B v -prints list of all GF_ObjectDescriptor used in current presentation. -.TP -.B i -prints information of a given OD. Object ID is entered at prompt. -.TP -.B b -prints all objects timing and buffering info. -.TP -.B m -prints all objects buffering and memory info. -.TP -.B d -dumps current presentation scene graph to file or stdout. -.TP -.B k -Turns stress mode on/off. -.TP -.B n -Changes current navigation mode if possible/allowed. When a Layer3D is selected, sets layer3D navigation if allowed. -.TP -.B x -Reset to last active viewpoint. When a layer3D is selected, resets layer3D viewpoint. -.TP -.B 2 -Reload MPEG-4 Client with GPAC 2D Renderer. -.TP -.B 3 -Reload MPEG-4 Client with GPAC 3D Renderer. -.TP -.B 4 -forces 4/3 Aspect Ratio. -.TP -.B 5 -forces 16/9 Aspect Ratio. -.TP -.B 6 -forces no aspect ratio (always fill screen). -.TP -.B 7 -forces original Aspect Ratio (default mode). -.TP -.B l -prints the list of available plugins. -.TP -.B c -prints some GPAC configuration info. -.TP -.B q -quits the application. -.TP -.B C -enables streaming cache. -.TP -.B S -stops streaming cache and record it. -.TP -.B A -stops streaming cache and discard it. -.TP -.B h -prints list of available commands. -. -.SH WINDOW SHORTCUTS -When MP4Client window has the focus, the following shortcuts are available -.TP -.B Alt + right -Seeks 5% ahead from current time. -.TP -.B Alt + left -Seeks 5% before the current time -.TP -.B Alt + up -Increases volume by 5% -.TP -.B Alt + down -Decreases volume by 5% -.TP -.B HOME -Resets navigation to last bound viewpoint/viewport -.TP -.B 4, 5, 6, 7 -Same as non-window values -.TP -.B ESCAPE , alt+return, doubleclick -Toggles fullscreen on/off. -.TP -.B ctrl + 'f' -Prints current rendering framerate. -.TP -.B ctrl + 'R' -Sends a redraw message (complete scene state recompute). -.TP -.B ctrl + 'P' -Play/Pause. -.TP -.B ctrl + 'S' -Step one frame ahead. -. -.SH CONFIGURATION FILE -When launched for the first time, MP4Client will ask for a font directory and a cache directory. -.TP -.B Font Directory -Indicates the absolute path to a directory containing a TrueType font repository. In case this directory is not specified or does not contain valid TrueType fonts, text will be disabled. -.TP -.B Cache Directory -Indicates the absolute path to a directory with write access to store files retrieved through HTTP downloads. In case this directory is not specified or has no write access HTTP downloads won't work. -. -.SH NAVIGATION -Navigation with MP4Client depends on the renderer type (2D or 3D) and also on content - some content may disable user selectable navigation. -.TP -.B Walk & Fly modes -Mouse: Horizontal move: Horizontal pan - Vertical move: Z-translate - Vertical move+CTRL or Wheel: Vertical pan - Right Click (Walk mode only): Jump -Keys: left/right: Horizontal pan - left/right+CTRL: Horizontal translate - up/down: Z-translate - up/down+CTRL: Vertical pan -.TP -.B Pan mode -Mouse: Horizontal move: Horizontal pan - Vertical move: Vertical pan - Vertical move+CTRL or Wheel: Z-translate -Keys: left/right: Horizontal pan - left/right+CTRL: Horizontal translate - up/down: Vertical pan - up/down+CTRL: Z-translate -.TP -.B Slide mode -Mouse: Horizontal move: Horizontal translate - Vertical move: Vertical translate - Vertical move+CTRL or Wheel: Z-translate -Keys: left/right: Horizontal translate - left/right+CTRL: Horizontal pan - up/down: Vertical translate - up/down+CTRL: Z-translate -.TP -.B Examine & Orbit mode -Mouse: Horizontal move: Y-Axis rotate - Horizontal move+CTRL: No move - Vertical move: X-Axis rotate - Vertical move+CTRL or Wheel: Z-translate -Keys: left/right: Y-Axis rotate - left/right+CTRL: Horizontal translate - up/down: X-Axis rotate - up/down+CTRL: Y-translate -.TP -.B Game mode (press END to escape) -Mouse: Horizontal move: H pan - Vertical move: Vertical pan -Keys: left/right: Horizontal translate - up/down: Z-translate -.TP -.B All 3D modes -CTRL+PGUP/PGDOWN will zoom in/out camera (field of view) -.TP -.B Slide Mode in 2D -Mouse: Horizontal move: Horizontal translate - Vertical move: Vertical translate - V move+CTRL: zoom -Keys: left/right: Horizontal translate - up/down: Vertical translate - up/down+CTRL: zoom -.TP -.B Examine Mode in 2D (3D renderer only) -Mouse: Horizontal move: Y-Axis rotate - Vertical move: X-Axis rotate -Keys: left/right: Y-Axis rotate - up/down: X-Axis rotate -.TP -.B SHIFT -Speeds up movement -. -.SH VISUAL EXTRACTION OPTIONS -When used to dump a visual presentation, the client is no longer interactive. All GPAC features are supported during capture, except audio-related ones. The following options can be passed at prompt: -.P -.TP -.B \-bmp [times] -dumps the specified frames to BMP format. If no time is specified, capture is made at t=0s. time is given in millisecond, as an SMPTE time code or as HH:MM:SS:MS time code. You may specify several frame times by using a dash-separated list of times (eg, '0-10-15'). -.TP -.B \-raw [times] -dumps the specified frames to RAW format. If no time is specified, cpature is made at t=0s. -.TP -.B \-avi start:end -dumps the specified segment to uncompressed AVI format. -.TP -.B \-fps rate -specifies frame rate for AVI dumping. Default frame rate is 25.0. -.TP -.B \-size WxH -specifies frame size for dumping. Default frame size is the scene size. -.TP -.B \-fill -uses fill aspect ratio. By default, the scene aspect ratio is preserved when dumping. -.TP -.B \-show -show window while dumping the content. By default, the window is hidden during capture. -.TP -.B \-2d -forces usage of 2D renderer. By default, MP4Client uses the renderer indicated in GPAC configuration file. -.TP -.B \-3d -forces usage of 3D renderer. By default, MP4Client uses the renderer indicated in GPAC configuration file. -.TP -.B \-quiet -removes script message, buffering and downloading status. -. -.SH "FILES" -.LP -.B GPAC Configuration File: -~/.gpacrc -. -.SH "AUTHORS" -.LP -Jean Le Feuvre - GPAC (c) 2000-2005 - ENST 2005-200X -. -.SH "SEE ALSO" -.LP -GPAC(1), MP4Box(1) diff --git a/generate_installer.bat b/generate_installer.bat index 70a5979..1f008ca 100644 --- a/generate_installer.bat +++ b/generate_installer.bat @@ -46,7 +46,7 @@ if not exist include/gpac/revision.h goto Abort REM check if found a local commit which has not been pushed for /f "delims=" %%a in ('git diff FETCH_HEAD') do @set diff=%%a echo diff = %diff% -if not "%diff%"=="" goto RevisionAbort +if not "%diff%"=="" echo Local and remote revisions not in sync, consider pushing or pulling changes REM execute git and check if the result if found within revision.h for /f "delims=" %%a in ('git describe --tags --long') do @set VERSION=%%a @@ -73,6 +73,7 @@ REM ============================================ echo Executing NSIS REM ============================================ call %NSIS_EXEC% packagers\win32_64\nsis\gpac_installer.nsi +IF %ERRORLEVEL% NEQ 0 GOTO Abort echo: diff --git a/gpac.spec b/gpac.spec index d388d0e..a560e13 100644 --- a/gpac.spec +++ b/gpac.spec @@ -1,15 +1,14 @@ # $Id: gpac.spec,v 1.5 2008-12-02 18:04:42 jeanlf Exp $ -Summary: GPAC is a multimedia framework covering MPEG-4, VRML/X3D and SVG. +Summary: Framework for production, encoding, delivery and interactive playback of multimedia content Name: gpac -Version: 0.7.1 -Release: 0.7.1 +Version: 1.0.1 +Release: 1.0.1 License: LGPL Group: Applications/Multimedia -Source0: gpac-0.7.1.tar.gz%{?_with_amr:Source1:http://www.3gpp.org/ftp/Specs/archive/26_series/26.073/26073-700.zip} -URL: http://gpac.sourceforge.net/ +Source0: gpac-1.0.1.tar.gz%{?_with_amr:Source1:http://www.3gpp.org/ftp/Specs/archive/26_series/26.073/26073-700.zip} +URL: http://gpac.io/ BuildRoot: %{_tmppath}/%{name}-root Requires: SDL -%{!?_without_js:Requires: js} %{!?_without_freetype:Requires: freetype} %{!?_without_faad:Requires: faad2} %{!?_without_jpeg:Requires: libjpeg-6b} @@ -19,7 +18,6 @@ Requires: SDL %{!?_without_ffmpeg:Requires: ffmpeg} %{!?_without_jack:Requires: libjack} BuildRequires: SDL-devel -%{!?_without_js:BuildRequires: js-devel} %{!?_without_freetype:BuildRequires: freetype-devel} %{!?_without_faad:BuildRequires: faad2-devel} %{!?_without_jpeg:BuildRequires: libjpeg-devel} @@ -27,49 +25,31 @@ BuildRequires: SDL-devel %{!?_without_mad:BuildRequires: libmad-devel} %{!?_without_xvid:BuildRequires: xvidcore-devel} %{!?_without_ffmpeg:BuildRequires: ffmpeg-devel} -%{!?_without_jack:BuildRequires: libjack-devel} +%{!?_without_jack:BuildRequires: jack-audio-connection-kit} + +%global debug_package %{nil} %description -GPAC is a multimedia framework for MPEG-4, VRML/X3D and SVG/SMIL. GPAC is built upon an implementation of the MPEG-4 Systems -standard (ISO/IEC 14496-1) developed from scratch in C. - -The main development goal is to provide a clean (a.k.a. readable by as many -people as possible), small and flexible alternative to the MPEG-4 Systems -reference software (known as IM1 and distributed in ISO/IEC 14496-5). -GPAC covers a very large part of the MPEG-4 standard, and features what can probably -be seen as the most advanced and robust 2D MPEG-4 Player available worldwide, as well as -a decent 3D MPEG-4/VRML player. - -The second development goal is to achieve integration of recent multimedia -standards for content playback (SVG/SMIL, VRML, X3D, SWF, etc) and content delivery into a single framework. -GPAC features 2D and 3D multimedia playback, MPEG-4 Systems (BIFS and LASeR) encoders, multiplexers and -publishing tools for content distribution, such as RTP streamers, MPEG-2 TS muxers, ISO Base Media File (MP4 & 3GP a.k.a. ISO/IEC 14496-12) -and MPEG DASH muxers. + +GPAC is a framework for production, encoding, delivery and interactive playback of multimedia content + +GPAC supports many AV codecs, multimedia containers (MP4,fMP4, TS, avi, mov, mpg, mkv ...), complex presentation formats (MPEG-4 Systems, SVG Tiny 1.2, VRML/X3D) and subtitles (SRT, WebVTT, TTXT/TX3G, TTML). +Supported inputs and outputs are pipes, UDP/TCP/UN sockets, local files, HTTP, RTP/RTSP, TS demuxing (from file, IP or DVB4Linux), ATSC 3.0 ROUTE sessions, desktop grabbing, camera/microphone inputs and any input format supported by FFmpeg. + +GPAC features a highly configurable media processing pipeline, and can further be extended using JavaScript. GPAC is licensed under the GNU Lesser General Public License. Available rpmbuild rebuild options : ---without : js freetype faad a52 jpeg png mad xvid ffmpeg ---with : amr +--without : freetype faad a52 jpeg png mad xvid ffmpeg jack -To build with amr, download -http://www.3gpp.org/ftp/Specs/archive/26_series/26.073/26073-700.zip -and put it in the rpm source directory. Then invoke rpmbuild with the -"--with amr" option. %prep %setup -q -n gpac -%if %{?_with_amr:1}%{!?_with_amr:0} -mkdir -p modules/amr_dec/amr_nb -cd Plugins/amr_dec/AMR_NB -unzip -j /usr/src/redhat/SOURCES/26073-700.zip -unzip -j 26073-700_ANSI_C_source_code.zip -cd ../../.. -%endif %build -%configure --enable-oss-audio %{?_with_amr: --enable-amr-nb} %{?_without_js: --disable-js} %{?_without_freetype: --disable-ft} %{?_without_faad: --disable-faad} %{?_without_jpeg: --disable-jpeg} %{?_without_png: --disable-png} %{?_without_mad: --disable-mad} %{?_without_xvid: --disable-xvid} %{?_without_ffmpeg: --disable-ffmpeg} %{?_without_jack: --disable-jack} +%configure --enable-oss-audio %{?_without_freetype: --disable-ft} %{?_without_faad: --disable-faad} %{?_without_jpeg: --disable-jpeg} %{?_without_png: --disable-png} %{?_without_mad: --disable-mad} %{?_without_xvid: --disable-xvid} %{?_without_ffmpeg: --disable-ffmpeg} %{?_without_jack: --disable-jack} make %install @@ -86,12 +66,15 @@ rm -rf %{buildroot} %files %defattr(-, root, root) -%doc AUTHORS BUGS Changelog COPYING README.md TODO +# %doc Changelog COPYING README.md %{_bindir}/* %{_libdir}/* -%{_mandir}/man1/* +%{_includedir}/* +%{_datadir}/* %changelog +* Fri Sep 4 2020 Jean Le Feuvre +- GPAC 1.0 release * Fri Jul 3 2015 Jean Le Feuvre - Changed to README.md * Wed Feb 13 2008 Pierre Souchay diff --git a/gui/extensions/player/init.js b/gui/extensions/player/init.js deleted file mode 100644 index 4dfa5a9..0000000 --- a/gui/extensions/player/init.js +++ /dev/null @@ -1,15 +0,0 @@ - -obj = { - whoami: "GPAC-GUI-Extension", - name: "Player", - icon: "applications-multimedia.svg", - author: "JeanLF", - description: "Nice test extension", - url: "http://foo.bar", - execjs: ["player.js", "fileopen.js", "playlist.js", "stats.js"], - autostart: true, - config_id: "Player", - requires_gl: false, - version_major: 1, - version_minor: 0 -}; diff --git a/gui/gui_old.bt b/gui/gui_old.bt deleted file mode 100644 index 34a3378..0000000 --- a/gui/gui_old.bt +++ /dev/null @@ -1,42 +0,0 @@ -####################################################################################### -# Hardcoded PROTO node which displays object without taking into account user transforms -####################################################################################### -EXTERNPROTO Untransform [ - exposedField MFNode children [] -] -[ "urn:inet:gpac:builtin:Untransform"] - - - -####################################################################################### -# Main Scene -####################################################################################### - -DEF GPAC_UI OrderedGroup { - children [ - Background2D { backColor 1 1 1} - - DEF MovieControl MediaControl {} - DEF MovieSensor MediaSensor {} - DEF Movie Transform2D { - children [ - Inline { url ""} - ] - } - - DEF UI_ROOT Untransform { - children [ - ] - } - - DEF GUIScript Script { - field SFNode root USE GPAC_UI - field SFNode ui_root USE UI_ROOT - field SFNode movie USE Movie - field SFNode movie_ctrl USE MovieControl - field SFNode movie_sensor USE MovieSensor - - url "gui_old.js" - } - ] -} diff --git a/gui/gui_old.js b/gui/gui_old.js deleted file mode 100644 index aef0aad..0000000 --- a/gui/gui_old.js +++ /dev/null @@ -1,2104 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////// -// -// Authors: -// Jean Le Feuvre, Telecom ParisTech -// Jean-Claude Dufourd, Telecom ParisTech -// -///////////////////////////////////////////////////////////////////////////////// - -UPnP_Enabled = false; - - -function new_extension() -{ - var obj = new Object(); - obj.type = 'extension'; - return obj; -} - -function on_movie_duration(value) -{ - UPnP.MovieDuration = value; -} - -function on_movie_time(value) -{ - UPnP.MovieTime = value; - current_time = value; -} - -//function filter_event(evt) -//{ -// alert('event'); -//} - -//Initialize the main UI script -function initialize() { - //var icon; - var i, count, wid; - - gpac.caption = 'Osmo4'; - current_time = 0; - - display_width = parseInt(gpac.get_option('General', 'LastWidth')); - display_height = parseInt(gpac.get_option('General', 'LastHeight')); - - if (!gpac.fullscreen && display_width && display_height) { - gpac.set_size(display_width, display_height); - } else { - display_width = gpac.get_screen_width(); - display_height = gpac.get_screen_height(); - } - - //request event listeners on the window - GPAC specific BIFS extensions !!! - root.addEventListener('resize', on_resize, 0); - root.addEventListener('zoom', on_zoom, 0); - root.addEventListener('scroll', on_scroll, 0); - - Browser.addRoute(movie_sensor, 'mediaDuration', movie_sensor, on_movie_duration); - Browser.addRoute(movie_sensor, 'mediaCurrentTime', movie_sensor, on_movie_time); - // gpac.set_event_filter(filter_event); - - widget_default_size = 200; - scene_width = 0; - - icon_size = 48; - dock_height = 48; - info_height = 32; - toggle_bar_height = 16; - screen_dpi = gpac.get_horizontal_dpi(); - - widget_remote_candidate = null; - controlled_renderer = null; - UPnP_Enabled = eval("(typeof(UPnP) != 'undefined');"); - if (UPnP_Enabled) { - UPnP.onMediaRendererAdd = onMediaRendererAdd; - UPnP.onMediaConnect = onMediaConnect; - UPnP.onMediaStop = onMediaStop; - UPnP.onMediaPause = onMediaPause; - UPnP.onMediaPlay = onMediaPlay; - UPnP.onMediaSeek = OnMediaSeek; - UPnP.onMigrate = OnMediaMigrate; - UPnP.BindRenderer(); - upnp_renders = null; - UPnP.MovieURL = ''; - UPnP.MovieDuration = 0.0; - UPnP.MovieTime = 0.0; - } - - /*setup dock*/ - widget_ui_visible = false; - - /*widget subtree*/ - widget_display = new SFNode('Transform2D'); - ui_root.children[0] = widget_display; - - /*widget manager page*/ - widget_ui = new SFNode('Layer2D'); - ui_root.children[1] = widget_ui; - - /*all other UI elements subtree*/ - dlg_display = new SFNode('Transform2D'); - ui_root.children[2] = dlg_display; - - infobar = text_label('', 'MIDDLE'); - ui_root.children[3] = infobar; - - /*our dock*/ - dock = new SFNode('Transform2D'); - ui_root.children[4] = dock; - - - nb_widgets_on_screen = 0; - first_visible_widget = 0; - setup_icons(); - - /*init the widget manager*/ - Browser.loadScript('mpegu-core.js', true); - log_level = l_inf; - widget_manager_init(); - - if (typeof(WidgetManager) != 'undefined') { - WidgetManager.on_widget_remove = widget_remove; - WidgetManager.on_widget_add = widget_insert; - WidgetManager.coreOutSetSize = widget_request_size; - WidgetManager.coreOutShow = widget_request_show; - WidgetManager.coreOutHide = widget_request_hide; - WidgetManager.coreOutRequestActivate = widget_request_activate; - WidgetManager.coreOutRequestDeactivate = widget_request_deactivate; - WidgetManager.coreOutShowNotification = widget_request_notification; - WidgetManager.coreOutPlaceComponent = widget_place_component; - WidgetManager.coreOutGetAttention = widget_request_attention; - - WidgetManager.coreOutInstallWidget = widget_request_install; - WidgetManager.coreOutMigrateComponent = widget_migrate_component; - WidgetManager.coreOutRequestMigrationTargets = widget_migration_targets; - WidgetManager.coreOutActivateTemporaryWidget = widget_activate_temporary_widget; - - /*restore our widgets*/ - widgets_init(); - } - - /*extensions are no longer usable with old GUI as we changed the gwlib interfaces*/ - /*init all extensions*/ -/* - var list = gpac.enum_directory('extensions', '*', 0); - for (i=0; i=hw) || (2*ry>=hh)) rx = ry = 6; - - temp = this.geometry.point.point; - temp[0] = new SFVec2f(hw-rx, hh); - temp[1] = new SFVec2f(hw, hh);/*bezier ctrl point*/ - temp[2] = new SFVec2f(hw, hh-ry); - temp[3] = new SFVec2f(hw, -hh+ry); - temp[4] = new SFVec2f(hw, -hh);/*bezier control point*/ - temp[5] = new SFVec2f(hw-rx, -hh); - temp[6] = new SFVec2f(-hw+rx, -hh); - temp[7] = new SFVec2f(-hw, -hh);/*bezier control point*/ - temp[8] = new SFVec2f(-hw, -hh+ry); - temp[9] = new SFVec2f(-hw, hh-ry); - temp[10] = new SFVec2f(-hw, hh);/*bezier control point*/ - temp[11] = new SFVec2f(-hw+rx, hh); - } - obj.set_color = function(r, g, b) { - this.appearance.material.emissiveColor.r = r; - this.appearance.material.emissiveColor.g = g; - this.appearance.material.emissiveColor.b = b; - } - return obj; -} - -function icon_button(url, label, no_back) -{ - var obj = new SFNode('Transform2D'); - obj.visible = true; - obj.children[0] = new SFNode('Transform2D'); - obj.children[0].scale.x = 0; - obj.children[0].children[0] = rectangle(); - - obj.children[1] = new SFNode('Layer2D'); - obj.children[1].size.x = icon_size; - obj.children[1].size.y = icon_size; - obj.children[1].children[0] = new SFNode('Inline'); - obj.children[1].children[0].url[0] = url; - - obj.touch = new SFNode('TouchSensor'); - obj.children[1].children[1] = obj.touch; - obj.button_click = NULL; - obj.down = false; - obj.over = false; - obj.on_active = function(value) { - if (value) { - this.down = true; - } else { - if (this.down && this.over && this.button_click) this.button_click(); - this.down = false; - } - }; - obj.button_over = on_icon_over; - obj.on_over = function(value) { - this.over = value; - if (!no_back) - this.children[0].scale.x = value ? 1 : 0; - if (this.button_over) this.button_over(value); - }; - Browser.addRoute(obj.touch, 'isOver', obj, obj.on_over); - Browser.addRoute(obj.touch, 'isActive', obj, obj.on_active); - obj.label = label; - obj.hide = function() { this.scale.x = this.scale.y = 0; this.visible = false; }; - obj.show = function() { this.scale.x = this.scale.y = 1; this.visible = true; }; - obj.set_size = function(x, y) { - this.children[0].children[0].set_size(x, y); - this.children[1].size.x = x; - this.children[1].size.y = y; - }; - return obj; -} - -function text_label(label, justify) -{ - var obj = new SFNode('Transform2D'); - obj.children[0] = new SFNode('Shape'); - obj.children[0].appearance = new SFNode('Appearance'); - obj.children[0].appearance.material = new SFNode('Material2D'); - obj.children[0].appearance.material.filled = TRUE; - obj.children[0].appearance.material.emissiveColor = new SFColor(0, 0, 0); - obj.children[0].geometry = new SFNode('Text'); - obj.children[0].geometry.string[0] = label; - obj.children[0].geometry.fontStyle = new SFNode('FontStyle'); - obj.children[0].geometry.fontStyle.justify[0] = justify; - obj.children[0].geometry.fontStyle.justify[1] = 'MIDDLE'; - obj.children[0].geometry.fontStyle.size = 20; - obj.set_label = function(value) { - this.children[0].geometry.string[0] = value; - } - return obj; -} - - -function text_rect(label) -{ - var obj = new SFNode('Transform2D'); - obj.children[0] = rectangle(); - obj.children[0].set_color(0.7, 0.7, 0.8); - - obj.children[1] = new SFNode('Shape'); - obj.children[1].appearance = new SFNode('Appearance'); - obj.children[1].appearance.material = new SFNode('Material2D'); - obj.children[1].appearance.material.filled = TRUE; - obj.children[1].appearance.material.emissiveColor = new SFColor(0, 0, 0); - obj.children[1].geometry = new SFNode('Text'); - obj.children[1].geometry.string[0] = label; - obj.children[1].geometry.fontStyle = new SFNode('FontStyle'); - obj.children[1].geometry.fontStyle.justify[0] = 'MIDDLE'; - obj.children[1].geometry.fontStyle.justify[1] = 'MIDDLE'; - obj.children[1].geometry.fontStyle.size = 20; - obj.children[2] = new SFNode('TouchSensor'); - - obj.set_size = function(w, h) { - this.children[0].set_size(w, h); - }; - - - obj.over = false; - obj.on_over = function(value) { - this.children[0].set_color(0.7, value ? 0.5 : 0.7, 0.8); - this.over = value; - }; - Browser.addRoute(obj.children[2], 'isOver', obj, obj.on_over); - - obj.down = false; - obj.button_click = null; - obj.on_active = function(value) { - if (value) { - this.down = true; - } else { - if (this.down && this.over && this.button_click) this.button_click(); - this.down = false; - } - }; - Browser.addRoute(obj.children[2], 'isActive', obj, obj.on_active); - - return obj; -} - -function new_slider(vertical) -{ - var obj = new SFNode('Transform2D'); - obj.children[0] = new SFNode('Shape'); - obj.children[0].appearance = new SFNode('Appearance'); - obj.children[0].appearance.material = new SFNode('Material2D'); - obj.children[0].appearance.material.filled = TRUE; - obj.children[0].appearance.texture = new SFNode('LinearGradient'); - obj.children[0].appearance.texture.endPoint.x = vertical ? 1 : 0; - obj.children[0].appearance.texture.endPoint.y = vertical ? 0 : 1; - obj.children[0].appearance.texture.key[0] = 0; - obj.children[0].appearance.texture.key[1] = 0.5; - obj.children[0].appearance.texture.key[2] = 1; - obj.children[0].appearance.texture.keyValue[0] = new SFColor(0.4, 0.4, 0.6); - obj.children[0].appearance.texture.keyValue[1] = new SFColor(0.4, 0.4, 1); - obj.children[0].appearance.texture.keyValue[2] = new SFColor(0.4, 0.4, 0.6); - obj.children[0].geometry = new SFNode('Rectangle'); - - obj.children[1] = new SFNode('Transform2D'); - obj.children[1].children[0] = new SFNode('Shape'); - obj.children[1].children[0].appearance = new SFNode('Appearance'); - obj.children[1].children[0].appearance.material = new SFNode('Material2D'); - obj.children[1].children[0].appearance.material.filled = TRUE; - obj.children[1].children[0].appearance.material.emissiveColor = new SFColor(0, 0, 1); - obj.children[1].children[0].geometry = new SFNode('Circle'); - - obj.on_slide = function(value) { } - obj.children[2] = new SFNode('PlaneSensor2D'); - obj.set_translation = function(value) { - this.frac = 0.5 + (vertical ? value.y/this.height : value.x/this.width); - this.children[1].translation = value; - this.on_slide(this.min + (this.max-this.min) * this.frac); - } - Browser.addRoute(obj.children[2], 'translation_changed', obj, obj.set_translation); - - - obj.set_value = function(value) { - value -= this.min; - if (value<0) value = 0; - else if (value>this.max-this.min) value = this.max-this.min; - if (this.max==this.min) value = 0; - else value /= (this.max-this.min); - - value -= 0.5; - value *= (vertical ? this.height : this.width); - - if (vertical) { - this.children[1].translation.y = value; - this.children[2].offset.y = value; - } else { - this.children[1].translation.x = value; - this.children[2].offset.x = value; - } - } - - obj.radius = 10; - obj.vertical = vertical; - obj.set_size = function(w, h) { - var rad; - this.children[0].geometry.size.x = w; - this.children[0].geometry.size.y = h; - rad = vertical ? w : h; - rad/=1.33; - this.children[1].children[0].geometry.radius = rad; - this.children[2].maxPosition.x = this.vertical ? 0 : w/2; - this.children[2].maxPosition.y = this.vertical ? h/2 : 0; - this.children[2].minPosition.x = this.vertical ? 0 : -w/2; - this.children[2].minPosition.y = this.vertical ? -h/2 : 0; - this.width = w; - this.height = h; - } - obj.min = 0; - obj.max = 100; - - obj.set_size(vertical ? 10 : 200, vertical ? 200 : 10); - return obj; -} - -function new_radio_button(label) -{ - var obj = new SFNode('Transform2D'); - obj.children[0] = new SFNode('Shape'); - obj.children[0].appearance = new SFNode('Appearance'); - obj.children[0].appearance.material = new SFNode('Material2D'); - obj.children[0].appearance.material.filled = TRUE; - obj.children[0].appearance.material.emissiveColor = new SFColor(1, 1, 1); - obj.children[0].appearance.material.lineProps = new SFNode('LineProperties'); - obj.children[0].appearance.material.lineProps.lineColor = new SFColor(0, 0, 0); - obj.children[0].appearance.material.lineProps.width = 1; - obj.children[0].geometry = new SFNode('Circle'); - - obj.children[1] = new SFNode('Shape'); - obj.children[1].appearance = new SFNode('Appearance'); - obj.children[1].appearance.material = new SFNode('Material2D'); - obj.children[1].appearance.material.filled = TRUE; - obj.children[1].appearance.material.emissiveColor = new SFColor(0, 0, 0); - obj.children[1].appearance.material.transparency = 1; - obj.children[1].geometry = new SFNode('Circle'); - - obj.children[2] = text_label(label, 'BEGIN'); - - obj.children[3] = new SFNode ('TouchSensor'); - - obj.on = false; - obj.on_select = function(value) {} - obj.on_active = function(value) { - if (!value) return; - this.on = !this.on; - this.children[1].appearance.material.transparency = this.on ? 0 : 1; - this.on_select(this.on); - } - Browser.addRoute(obj.children[3], 'isActive', obj, obj.on_active); - - obj.enable = function(value) { - this.on = value; - this.children[1].appearance.material.transparency = this.on ? 0 : 1; - } - - obj.set_width = function(w, h) { - var rad = w0) { - info.children[i++] = text_label(txt.substring(0, idx), 'BEGIN'); - txt = txt.substring(idx+1, txt.length); - } else { - info.children[i++] = text_label(txt, 'BEGIN'); - break; - } - } - - info_dlg.ifce_idx = info_dlg.children.length; - info = text_rect('Interfaces (count: ' + wid.num_interfaces + ' - bound: ' + wid.num_bound_interfaces+')', 'BEGIN'); - info_dlg.children[info_dlg.ifce_idx] = info; - info.visible = false; - info.button_click = function() { - this.visible = !this.visible; - layout(); - } - i=3; - for (j=0; jdisplay_width) { - start_x = (icon_size-display_width)/2; - } else { - start_x = (icon_size-tot_len)/2; - } - /*translate / size all items in the dock*/ - for (i=0;i0) { - first_visible_widget += nb_wid; - } - - if (first_visible_widget) widget_ui.prev.show(); - else widget_ui.prev.hide(); - - if (first_visible_widget+nb_wid < count) widget_ui.next.show(); - else widget_ui.next.hide(); - - for (i=0; i= count) { - break; - } - wid = widget_ui.children[i +first_visible_widget+widget_ui.nb_tools]; - wid.show(); - wid.set_size(icon_size, icon_size); - wid.translation.x = start_x; - wid.translation.y = start_y; - start_x += icon_size + spread_x; - if (start_x + icon_size / 2 >= display_width/2) { - start_x = (icon_size-display_width)/2 + spread_x/2; - start_y -= icon_size; - } - nb_widgets_on_screen = i+1; - if (start_y - icon_size < (dock_height-display_height)/2) { - i++; - break; - } - } -} - -//performs layout on all contents -function layout() { - var i, list, start_x; - - gpac.set_option('General', 'LastWidth', ''+display_width); - gpac.set_option('General', 'LastHeight', '' + display_height); - - if (dlg_display.children.length) { - widget_display.scale.x = 0; - widget_ui_visible = false; - } - //layout all icons in the dock - dock_layout(); - widget_ui_layout(0); - - if (dlg_display.children.length) { - list = dlg_display.children; - for (i=0; i 0) { - break; - } - } - return icon; -} - -//initialize GPAC widget manager and load all widgets -function widgets_init() { - - count = WidgetManager.num_widgets; - for (i=0; i 1 && args[1] != null) { - alert('Migrating component to ' + UPnP.GetMediaRenderer(parseInt(args[1])).Name); - WidgetManager.migrate_widget(UPnP.GetMediaRenderer(parseInt(args[1])), comp); - widget_close(comp); - } else { - widget_remote_candidate = comp; - on_upnpopen(false, false); - } - if (ifce != null) { - wmjs_core_out_invoke_reply(coreOut.migrateComponentMessage, ifce.get_message("migrateComponent"), wid, 1); // send return code 1 = success - } -} - -function widget_migration_targets(wid, args) -{ - var count = UPnP.MediaRenderersCount, codes = new Array(), names = new Array(), descriptions = new Array(), i; - - for (i = 0; i < count; i++) { - var render = UPnP.GetMediaRenderer(i); - codes.push(""+i); - names.push(render.Name); - descriptions.push(render.HostName +" "+ render.UUID); - } - i = null; - var ifce_count = wid.num_interfaces, j; - for (j = 0; j < ifce_count; j++) { - var ifce = wid.get_interface(j); - if (ifce.type == "urn:mpeg:mpegu:schema:widgets:core:out:2010") { - i = ifce; - break; - } - } - if (i != null) { - wmjs_core_out_invoke_reply(coreOut.requestMigrationTargetsMessage, i.get_message("requestMigrationTargets"), - wid, codes, names, descriptions); - } -} - - -// -// implementation of core:out activate temporary widget -// -function widget_activate_temporary_widget(wid, args) { - var w = WidgetManager.open(args[0], null); - if (w != null) widget_launch(w); - var ifce = getInterfaceByType(wid, "urn:mpeg:mpegu:schema:widgets:core:out:2010"); - if (ifce != null) { - wmjs_core_out_invoke_reply(coreOut.activateTemporaryWidgetMessage, ifce.get_message("activateTemporaryWidget"), - wid, (w != null ? 1 : 0)); // send return code 1 = success - } -} - - - -function insert_widget_icon(new_wid, no_layout) { - var icon; - icon = icon_button(widget_get_icon(new_wid), new_wid.name, 0); - icon.tooltip = new_wid.name; - new_wid.in_panel = true; - new_wid.visible = false; - new_wid.icon_dock = icon; - icon.button_click = on_widget_launch; - icon.widget = new_wid; - widget_ui.addChildren[0] = new_wid.icon_dock; - if (!no_layout) layout(); -} - -function scan_directory(dir) -{ - var i, j, count, list, new_wid, uri; - list = gpac.enum_directory(dir, '.xml;.wgt;.mgt', 0); - for (i=0; i this.list.length) this.first = this.list.length - this.nb_items; - } - if (this.first) this.children[1].show(); - if (this.first+this.nb_items < this.list.length) this.children[2].show(); - - for (i=0; i=this.list.length) break; - item = text_rect(this.list[i+this.first].name); - item.path = this.list[i+this.first].path; - item.name = this.list[i+this.first].name; - item.directory = this.list[i+this.first].directory; - item.filebrowse = this; - if (this.upnp_mode) { - item.is_server = this.list[i+this.first].is_server; - item.resource_uri = this.list[i+this.first].resource_uri; - item.button_click = function() { - if (this.directory) { - if (this.is_server) { - this.filebrowse.server_uuid = this.path; - } else { - this.filebrowse.directory = this.path; - } - this.filebrowse.browse(false); - } else { - dlg_display.children.length = 0; - if (this.filebrowse.on_browse) { - this.filebrowse.on_browse(this.resource_uri, null); - } - } - }; - } else { - item.button_click = function() { - if (this.directory) { - this.filebrowse.directory = this.path + this.name; - this.filebrowse.browse(false); - } else { - dlg_display.children.length = 0; - if (this.filebrowse.on_browse) { - this.filebrowse.on_browse(this.path + this.name, this.filebrowse.directory); - } - } - }; - } - this.children[this.nb_tools+i] = item; - } - this.set_size(this.width, this.height); - } - - - filebrowse.set_size = function(w, h) { - var i, x, y, isize, nbi; - isize = 24; - if (w>display_width - isize) w = display_width - isize; - - this.width = w; - this.height = h; - - i = 1; - while ((i+1)*isize <= h - isize) i++; - - if (i != this.nb_items) { - this.nb_items = i; - this.layout(0); - return; - } - x = -w/2 + isize/2; - y = h/2 - isize/2; - - for (i=0;ii+1) { - this.children[i].set_size(isize, isize); - } - this.children[i].translation.x = x; - this.children[i].translation.y = y; - x += isize; - } - y-=isize; - while (i= 0) return true; - return false; -} - -function on_upnpopen(push_mode, remote_only) -{ - upnp_renders = new SFNode('Transform2D'); - upnp_renders.nb_items = 0; - upnp_renders.refresh = function () { - var i, count, render, item, start_y, w, str; - this.children.length = 0; - count = UPnP.MediaRenderersCount; - if (count+1>this.nb_items) count = this.nb_items-1; - - item = text_rect('Close'); - item.button_click = function() { - dlg_display.children.length = 0; - upnp_renders=null; - widget_display.scale.x = 1; - infobar.set_label(''); - } - this.children[this.children.length] = item; - - if (!remote_only) { - str = (controlled_renderer==null) ? '+ ' : ''; - str += 'Local Renderer'; - item = text_rect(str); - item.button_click = function() { - dlg_display.children.length = 0; - upnp_renders=null; - widget_display.scale.x = 1; - infobar.set_label(''); - controlled_renderer = null; - } - this.children[this.children.length] = item; - } - - for (i=0; iSample with its call to rmt_EndSample when leaving scope +#define rmt_ScopedCPUSample(name, flags) \ + RMT_OPTIONAL(RMT_ENABLED, rmt_BeginCPUSample(name, flags)); \ + RMT_OPTIONAL(RMT_ENABLED, rmt_EndCPUSampleOnScopeExit rmt_ScopedCPUSample##name); +#define rmt_ScopedCUDASample(name, stream) \ + RMT_OPTIONAL(RMT_USE_CUDA, rmt_BeginCUDASample(name, stream)); \ + RMT_OPTIONAL(RMT_USE_CUDA, rmt_EndCUDASampleOnScopeExit rmt_ScopedCUDASample##name(stream)); +#define rmt_ScopedD3D11Sample(name) \ + RMT_OPTIONAL(RMT_USE_D3D11, rmt_BeginD3D11Sample(name)); \ + RMT_OPTIONAL(RMT_USE_D3D11, rmt_EndD3D11SampleOnScopeExit rmt_ScopedD3D11Sample##name); +#define rmt_ScopedOpenGLSample(name) \ + RMT_OPTIONAL(RMT_USE_OPENGL, rmt_BeginOpenGLSample(name)); \ + RMT_OPTIONAL(RMT_USE_OPENGL, rmt_EndOpenGLSampleOnScopeExit rmt_ScopedOpenGLSample##name); +#define rmt_ScopedMetalSample(name) \ + RMT_OPTIONAL(RMT_USE_METAL, rmt_BeginMetalSample(name)); \ + RMT_OPTIONAL(RMT_USE_METAL, rmt_EndMetalSampleOnScopeExit rmt_ScopedMetalSample##name); + +#endif + + + +/* +------------------------------------------------------------------------------------------------------------------------ +------------------------------------------------------------------------------------------------------------------------ + Private Interface - don't directly call these +------------------------------------------------------------------------------------------------------------------------ +------------------------------------------------------------------------------------------------------------------------ +*/ + + + +#if RMT_ENABLED + +#ifdef __cplusplus +extern "C" { +#endif + +RMT_API rmtSettings* _rmt_Settings( void ); +RMT_API enum rmtError _rmt_CreateGlobalInstance(Remotery** remotery); +RMT_API void _rmt_DestroyGlobalInstance(Remotery* remotery); +RMT_API void _rmt_SetGlobalInstance(Remotery* remotery); +RMT_API Remotery* _rmt_GetGlobalInstance(void); +RMT_API void _rmt_SetCurrentThreadName(rmtPStr thread_name); +RMT_API void _rmt_LogText(rmtPStr text); +RMT_API void _rmt_BeginCPUSample(rmtPStr name, rmtU32 flags, rmtU32* hash_cache); +RMT_API void _rmt_EndCPUSample(void); +RMT_API void _rmt_EnableSampling(rmtBool enable); +RMT_API rmtBool _rmt_SamplingEnabled(); + +#if RMT_USE_CUDA +RMT_API void _rmt_BindCUDA(const rmtCUDABind* bind); +RMT_API void _rmt_BeginCUDASample(rmtPStr name, rmtU32* hash_cache, void* stream); +RMT_API void _rmt_EndCUDASample(void* stream); +#endif + +#if RMT_USE_D3D11 +RMT_API void _rmt_BindD3D11(void* device, void* context); +RMT_API void _rmt_UnbindD3D11(void); +RMT_API void _rmt_BeginD3D11Sample(rmtPStr name, rmtU32* hash_cache); +RMT_API void _rmt_EndD3D11Sample(void); +#endif + +#if RMT_USE_OPENGL +RMT_API void _rmt_BindOpenGL(); +RMT_API void _rmt_UnbindOpenGL(void); +RMT_API void _rmt_BeginOpenGLSample(rmtPStr name, rmtU32* hash_cache); +RMT_API void _rmt_EndOpenGLSample(void); +#endif + +#if RMT_USE_METAL +RMT_API void _rmt_BeginMetalSample(rmtPStr name, rmtU32* hash_cache); +RMT_API void _rmt_EndMetalSample(void); +#endif + +#ifdef __cplusplus + +} +#endif + +#if RMT_USE_METAL +#ifdef __OBJC__ +RMT_API void _rmt_BindMetal(id command_buffer); +RMT_API void _rmt_UnbindMetal(); +#endif +#endif + +#endif // RMT_ENABLED + +//! @endcond + +#endif diff --git a/include/gpac/ait.h b/include/gpac/ait.h index 078d134..06f8e7a 100644 --- a/include/gpac/ait.h +++ b/include/gpac/ait.h @@ -10,8 +10,8 @@ extern "C" { #endif /*! - * \file - * Specific extensions for handling AIT in MPEG-2 TS. +\file +\brief Specific extensions for handling AIT in MPEG-2 TS. */ #include diff --git a/include/gpac/atsc.h b/include/gpac/atsc.h new file mode 100644 index 0000000..3663d4a --- /dev/null +++ b/include/gpac/atsc.h @@ -0,0 +1,229 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2019 + * All rights reserved + * + * This file is part of GPAC / ATSC demuxer + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _GF_ATSC_H_ +#define _GF_ATSC_H_ + +#include + +#ifndef GPAC_DISABLE_ATSC + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file +\brief Specific extensions for ATSC ROUTE demux +*/ + +/*! +\addtogroup atsc_grp ATSC3 +\ingroup media_grp +\brief ATSC 3.0 reciever + +The ATSC 3.0 receiver implements part of the ATSC 3.0 specification, mostly low-level signaling and ROUTE reception. +It can write received files to disk, or send them back to the user for cache population + +@{ +*/ + +/*!The GF_ATSCDmx object.*/ +typedef struct __gf_atscdmx GF_ATSCDmx; + +/*!The types of events used to communicate withe the demuxer user.*/ +typedef enum +{ + /*! A new service detected, service ID is in evt_param*/ + GF_ATSC_EVT_SERVICE_FOUND = 0, + /*! Service scan completed, no evt_param*/ + GF_ATSC_EVT_SERVICE_SCAN, + /*! New MPD available for service, service ID is in evt_param*/ + GF_ATSC_EVT_MPD, + /*! Init segment update, service ID is in evt_param, file info is in finfo*/ + GF_ATSC_EVT_INIT_SEG, + /*! Segment reception, service ID is in evt_param, file info is in finfo*/ + GF_ATSC_EVT_SEG, +} GF_ATSCEventType; + +/*! Structure used to communicate file objects properties to the user*/ +typedef struct +{ + /*! original file name*/ + const char *filename; + /*! data pointer*/ + u8 *data; + /*! data size*/ + u32 size; + /*! object TSI*/ + u32 tsi; + /*! object TOI*/ + u32 toi; + /*! download time in ms*/ + u32 download_ms; + /*! flag set if file is corrupted*/ + Bool corrupted; +} GF_ATSCEventFileInfo; + +/*! Creates a new ATSC demultiplexer +\param ifce network interface to monitor, NULL for INADDR_ANY +\param dir output directory for files. If NULL, files are not written to disk and user callback will be called if set +\param sock_buffer_size default buffer size for the udp sockets. If 0, uses 0x2000 +\return the ATSC demultiplexer created +*/ +GF_ATSCDmx *gf_atsc3_dmx_new(const char *ifce, const char *dir, u32 sock_buffer_size); +/*! Deletes an ATSC demultiplexer +\param atscd the ATSC demultiplexer to delete +*/ +void gf_atsc3_dmx_del(GF_ATSCDmx *atscd); + +/*! Processes demultiplexing, returns when nothing to read +\param atscd the ATSC demultiplexer +\return error code if any, GF_IP_NETWORK_EMPTY if nothing was read + */ +GF_Err gf_atsc3_dmx_process(GF_ATSCDmx *atscd); + +/*! Sets user callback for disk-less operations +\param atscd the ATSC demultiplexer +\param on_event the user callback function +\param udta the user data passed back by the callback +\return error code if any + */ +GF_Err gf_atsc3_set_callback(GF_ATSCDmx *atscd, void (*on_event)(void *udta, GF_ATSCEventType evt, u32 evt_param, GF_ATSCEventFileInfo *finfo), void *udta); + +/*! Sets the maximum number of objects to store on disk per TSI +\param atscd the ATSC demultiplexer +\param max_segs max number of objects (segments) to store. If 0, all objects are kept +\return error code if any + */ +GF_Err gf_atsc3_set_max_objects_store(GF_ATSCDmx *atscd, u32 max_segs); + +/*! Sets reordering on. +\param atscd the ATSC demultiplexer +\param force_reorder if TRUE, the order flag in ROUTE/LCT is ignored and objects are gathered for the given time. Otherwise, if order flag is set in ROUTE/LCT, an object is considered done as soon as a new object starts +\param timeout_ms maximum delay to wait before considering the object is done when ROUTE/LCT order is not used. A value of 0 implies waiting forever (default value is 5s). +\return error code if any + */ +GF_Err gf_atsc3_set_reorder(GF_ATSCDmx *atscd, Bool force_reorder, u32 timeout_ms); + +/*! Sets the maximum number of objects to store on disk per TSI +\param atscd the ATSC demultiplexer +\param service_id ID of the service to tune in. 0 means no service, 0xFFFFFFFF means all services and 0xFFFFFFFE means first service found +\param tune_others if set, will tune all non-selected services to get the MPD, but won't receive any media data +\return error code if any + */ +GF_Err gf_atsc3_tune_in(GF_ATSCDmx *atscd, u32 service_id, Bool tune_others); + + +/*! Gets the number of objects currently loaded in the service +\param atscd the ATSC demultiplexer +\param service_id ID of the service to query +\return number of objects in service + */ +u32 gf_atsc3_dmx_get_object_count(GF_ATSCDmx *atscd, u32 service_id); + +/*! Removes an object with a given filename +\param atscd the ATSC demultiplexer +\param service_id ID of the service to query +\param fileName name of the file associated with the object +\param purge_previous if set, indicates that all objects with the same TSI and a TOI less than TOI of the deleted object will be removed + */ +void gf_atsc3_dmx_remove_object_by_name(GF_ATSCDmx *atscd, u32 service_id, char *fileName, Bool purge_previous); + +/*! Removes the first object loaded in the service +\param atscd the ATSC demultiplexer +\param service_id ID of the service to query +\return GF_TRUE if success, GF_FALSE if no object could be removed (the object is in download) + */ +Bool gf_atsc3_dmx_remove_first_object(GF_ATSCDmx *atscd, u32 service_id); + +/*! Checks existence of a service +\param atscd the ATSC demultiplexer +\param service_id ID of the service to query +\return true if service is found, false otherwise + */ +Bool gf_atsc3_dmx_find_service(GF_ATSCDmx *atscd, u32 service_id); + +/*! Removes all non-signaling objects (ie TSI!=0), keeping only init segments and currently/last downloaded objects +\note this is mostly useful in case of looping session, or at MPD switch boundaries +\param atscd the ATSC demultiplexer +\param service_id ID of the service to cleanup + */ +void gf_atsc3_dmx_purge_objects(GF_ATSCDmx *atscd, u32 service_id); + + +/*! Gets high resolution system time clock of the first packet received +\param atscd the ATSC demultiplexer +\return system clock in microseconds of first packet received + */ +u64 gf_atsc3_dmx_get_first_packet_time(GF_ATSCDmx *atscd); + +/*! Gets high resolution system time clock of the last packet received +\param atscd the ATSC demultiplexer +\return system clock in microseconds of last packet received + */ +u64 gf_atsc3_dmx_get_last_packet_time(GF_ATSCDmx *atscd); + +/*! Gets the number of packets received since start of the session, for all active services +\param atscd the ATSC demultiplexer +\return number of packets received + */ +u64 gf_atsc3_dmx_get_nb_packets(GF_ATSCDmx *atscd); + +/*! Gets the number of bytes received since start of the session, for all active services +\param atscd the ATSC demultiplexer +\return number of bytes received + */ +u64 gf_atsc3_dmx_get_recv_bytes(GF_ATSCDmx *atscd); + +/*! Gather only objects with given TSI (for debug purposes) +\param atscd the ATSC demultiplexer +\param tsi the target TSI, 0 for no filtering + */ +void gf_atsc3_dmx_debug_tsi(GF_ATSCDmx *atscd, u32 tsi); + +/*! Sets udta for given service id +\param atscd the ATSC demultiplexer +\param service_id the target service +\param udta the target user data + */ +void gf_atsc3_dmx_set_service_udta(GF_ATSCDmx *atscd, u32 service_id, void *udta); + +/*! Gets udta for given service id +\param atscd the ATSC demultiplexer +\param service_id the target service +\return the user data associated with the service + */ +void *gf_atsc3_dmx_get_service_udta(GF_ATSCDmx *atscd, u32 service_id); + +/*! @} */ +#ifdef __cplusplus +} +#endif + +#endif /* GPAC_DISABLE_ATSC */ + +#endif //_GF_ATSC_H_ + diff --git a/include/gpac/avparse.h b/include/gpac/avparse.h index 434135c..2a26db3 100644 --- a/include/gpac/avparse.h +++ b/include/gpac/avparse.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / Authoring Tools sub-project @@ -32,244 +32,609 @@ extern "C" { #endif /*! - * \file - * \brief Utility tools for audio and video raw media parsing. - */ - -/*! \defgroup media_grp Media Tools - * You will find in this module the documentation of all media tools in GPAC. +\file +\brief Utility tools for audio and video raw media parsing. +*/ + +/*! +\addtogroup media_grp + +You will find in this module the documentation of all media tools in GPAC. */ /*! - * \addtogroup avp_grp AV Parsing - * \ingroup media_grp - * \brief Utility tools for audio and video raw media parsing. - * - *This section documents the audio and video parsing functions of the GPAC framework. - * @{ - */ - - +\addtogroup avp_grp AV Parsing +\ingroup media_grp +\brief Utility tools for audio and video raw media parsing. + +This section documents the audio and video parsing functions of the GPAC framework. +@{ +*/ + + #include /*! Reduces input width/height to common aspect ration num/denum values - \param width width of the aspect ratio - \param height height of the aspect ratio +\param width width of the aspect ratio +\param height height of the aspect ratio */ void gf_media_reduce_aspect_ratio(u32 *width, u32 *height); -/*! +/*! Reduces input FPS to a more compact value (eg 25000/1000 -> 25/1) - \param timescale timescale of the aspect ratio - \param sample_dur sample duration of the aspect ratio in the given timescale +\param timescale timescale of the aspect ratio +\param sample_dur sample duration of the aspect ratio in the given timescale */ void gf_media_get_reduced_frame_rate(u32 *timescale, u32 *sample_dur); +/*! inserts emulation prevention bytes from buffer_src into buffer_dst +\param buffer_src source buffer (NAL without EPB) +\param buffer_dst destination buffer (NAL with EPB) +\param nal_size source buffer size +\return size of buffer after adding emulation prevention bytes +*/ +u32 gf_media_nalu_add_emulation_bytes(const u8 *buffer_src, u8 *buffer_dst, u32 nal_size); -/*basic MPEG (1,2,4) visual object parser (DSI extraction and timing/framing)*/ +/*! gets the number of emulation prevention bytes to add to a non emulated buffer +\param buffer source buffer (NAL without EPB) +\param nal_size source buffer size +\return the number of emulation prevention bytes to add +*/ +u32 gf_media_nalu_emulation_bytes_add_count(u8 *buffer, u32 nal_size); + +/*! MPEG-1, MPEG-1, MPEG-4 part 2 decoder specific info (only serialized for MPEG-4) */ typedef struct { - /*video PL*/ + /*! video PL*/ u8 VideoPL; - u8 RAP_stream, objectType, has_shape, enh_layer; - /*video resolution*/ - u16 width, height; - /*pixel aspect ratio*/ - u8 par_num, par_den; - + /*! set if stream is RAP only*/ + u8 RAP_stream; + /*! MPEG-4 part 2 video object type*/ + u8 objectType; + /*! set if object has shape coding*/ + u8 has_shape; + /*! set if object is an enhancement layer*/ + u8 enh_layer; + /*! video horizontal size*/ + u16 width; + /*! video vertical size*/ + u16 height; + /*! pixel aspect ratio numerator*/ + u8 par_num; + /*! pixel aspect ratio denominator*/ + u8 par_den; + + /*! video clock rate - frames are spaced by time_increment/clock_rate seconds*/ u16 clock_rate; + /*! number of bits to code the time increment (internal use only)*/ u8 NumBitsTimeIncrement; + /*! time increment between frames*/ u32 time_increment; - /*for MPEG 1/2*/ + /*! framerate, for MPEG 1/2*/ Double fps; - + /*! position of next object in the bitstream*/ u32 next_object_start; } GF_M4VDecSpecInfo; +/*! MPEG (1,2,4) visual object parser (DSI extraction and timing/framing)*/ typedef struct __tag_m4v_parser GF_M4VParser; #ifndef GPAC_DISABLE_AV_PARSERS - -GF_M4VParser *gf_m4v_parser_new(char *data, u64 data_size, Bool mpeg12video); +/*! Creates a new MPEG-1/2/4 video parser +\param data buffer to parse +\param data_size size of buffer to parse +\param mpeg12video if set, parses as MPEG-1 or MPEG-2 +\return the created parser +*/ +GF_M4VParser *gf_m4v_parser_new(u8 *data, u64 data_size, Bool mpeg12video); +/*! Creates a new MPEG-1/2/4 video parser from a bitstream object +\param bs the bitstream object to use for parsing +\param mpeg12video if set, parses as MPEG-1 or MPEG-2 +\return the created parser +*/ GF_M4VParser *gf_m4v_parser_bs_new(GF_BitStream *bs, Bool mpeg12video); +/*! Deletes a MPEG-1/2/4 video parser +\param m4v the mpeg video parser +*/ void gf_m4v_parser_del(GF_M4VParser *m4v); +/*! Deletes a MPEG-1/2/4 video parser without destroying associated bitstream +\param m4v the mpeg video parser +*/ +void gf_m4v_parser_del_no_bs(GF_M4VParser *m4v); +/*! parses the decoder specific info (if found) +\param m4v the mpeg video parser +\param dsi the decoder spcific info structure to fill +\return GF_OK if found, GF_EOS if not enough data, error otherwise +*/ GF_Err gf_m4v_parse_config(GF_M4VParser *m4v, GF_M4VDecSpecInfo *dsi); - -/*get a frame (can contain GOP). The parser ALWAYS resync on the next object in the bitstream -thus you can seek the bitstream to copy the payload without re-seeking it */ -GF_Err gf_m4v_parse_frame(GF_M4VParser *m4v, GF_M4VDecSpecInfo dsi, u8 *frame_type, u32 *time_inc, u64 *size, u64 *start, Bool *is_coded); -/*returns current object start in bitstream*/ +/*! resets the parser +\param m4v the mpeg video parser +\param obj_type if not 0, skip next start code and use (obj_type-1) for next obj parsing +*/ +void gf_m4v_parser_reset(GF_M4VParser *m4v, u8 obj_type); + +/*! parses a frame. The parser ALWAYS resync on the next object in the bitstream +thus you can seek the bitstream to copy the payload without re-seeking it +\param m4v the mpeg video parser +\param dsi pointer to the decoder specific info parsed +\param frame_type set to the frame type (I:0, P:1, B:2) +\param time_inc set to the time increment since last frame +\param size set to the size of the compressed frame +\param start set to the position of the first byte in the buffer/bitstream +\param is_coded set to 1 if frame is coded, 0 if skip frame +\return error if any +*/ +GF_Err gf_m4v_parse_frame(GF_M4VParser *m4v, GF_M4VDecSpecInfo *dsi, u8 *frame_type, u32 *time_inc, u64 *size, u64 *start, Bool *is_coded); +/*! returns current object start in bitstream +\param m4v the mpeg video parser +\return the the position in the buffer/bitstream +*/ u64 gf_m4v_get_object_start(GF_M4VParser *m4v); -/*returns 1 if current object is a valid MPEG-4 Visual object*/ -Bool gf_m4v_is_valid_object_type(GF_M4VParser *m4v); -/*decodes DSI/VOSHeader for MPEG4*/ -GF_Err gf_m4v_get_config(char *rawdsi, u32 rawdsi_size, GF_M4VDecSpecInfo *dsi); -/*decodes DSI/VOSHeader for MPEG12*/ -GF_Err gf_mpegv12_get_config(char *rawdsi, u32 rawdsi_size, GF_M4VDecSpecInfo *dsi); - -/*rewrites PL code in DSI*/ -void gf_m4v_rewrite_pl(char **io_dsi, u32 *io_dsi_len, u8 PL); -/*rewrites PAR code in DSI. Negative values will remove the par*/ -GF_Err gf_m4v_rewrite_par(char **o_data, u32 *o_dataLen, s32 par_n, s32 par_d); +/*! decodes DSI/VOSHeader for MPEG4 +\param rawdsi encoded MPEG-4 decoder config +\param rawdsi_size size of encoded MPEG-4 decoder config +\param dsi the decoder spcific info structure to fill +\return error if any +*/ +GF_Err gf_m4v_get_config(u8 *rawdsi, u32 rawdsi_size, GF_M4VDecSpecInfo *dsi); +/*! decodes DSI/VOSHeader for MPEG12 +\param rawdsi encoded MPEG-1/2 decoder config +\param rawdsi_size size of encoded MPEG-1/2 decoder config +\param dsi the decoder spcific info structure to fill +\return error if any +*/ +GF_Err gf_mpegv12_get_config(u8 *rawdsi, u32 rawdsi_size, GF_M4VDecSpecInfo *dsi); + +/*! rewrites Profile and Level indicator in MPEG-4 DSI +\param io_dsi encoded MPEG-4 decoder config +\param io_dsi_len size of encoded MPEG-4 decoder config +\param PL the new Profile/level to set +*/ +void gf_m4v_rewrite_pl(u8 **io_dsi, u32 *io_dsi_len, u8 PL); +/*! rewrites PAR code in DSI. Negative values will remove the par +\param io_dsi encoded MPEG-4 decoder config +\param io_dsi_len size of encoded MPEG-4 decoder config +\param par_n the numerator of the new aspect ratio to write +\param par_d the denominator of the new aspect ratio to write +\return error if any +*/ +GF_Err gf_m4v_rewrite_par(u8 **io_dsi, u32 *io_dsi_len, s32 par_n, s32 par_d); #endif /*GPAC_DISABLE_AV_PARSERS*/ -/*returns readable description of profile*/ +/*! returns readable description of profile +\param video_pl the Profile/level +\return name of the profile (never NULL) +*/ const char *gf_m4v_get_profile_name(u8 video_pl); #ifndef GPAC_DISABLE_AV_PARSERS -s32 gf_mv12_next_start_code(unsigned char *pbuffer, u32 buflen, u32 *optr, u32 *scode); -s32 gf_mv12_next_slice_start(unsigned char *pbuffer, u32 startoffset, u32 buflen, u32 *slice_offset); +/*! returns next start code in mpeg 1/2 video buffer +\param pbuffer the video buffer +\param buflen size of the video buffer +\param optr set to the byte offset in the buffer +\param scode set to the start code value if found +\return 0 if found, -1 otherwise +*/ +s32 gf_mv12_next_start_code(u8 *pbuffer, u32 buflen, u32 *optr, u32 *scode); +/*! returns next slice start in mpeg 1/2 video buffer +\param pbuffer the video buffer +\param startoffset the offset in the buffer at which analysis shall begin +\param buflen size of the video buffer +\param slice_offset set to the byte offset of the slice start +\return 0 if found, -1 otherwise +*/ +s32 gf_mv12_next_slice_start(u8 *pbuffer, u32 startoffset, u32 buflen, u32 *slice_offset); #endif /* GPAC_DISABLE_AV_PARSERS*/ #ifndef GPAC_DISABLE_AV_PARSERS /*MP3 tools*/ +/*! gets the number of channels in an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the number of channels +*/ u8 gf_mp3_num_channels(u32 hdr); +/*! gets the sampling rate of an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the sampling rate +*/ u16 gf_mp3_sampling_rate(u32 hdr); +/*! gets the window size (number of samples) in an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the window size +*/ u16 gf_mp3_window_size(u32 hdr); +/*! gets the bitrate of an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the bit rate +*/ u32 gf_mp3_bit_rate(u32 hdr); +/*! gets the MPEG-4 object type indication of an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the object type indication +*/ u8 gf_mp3_object_type_indication(u32 hdr); +/*! gets the layer of an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the layer +*/ u8 gf_mp3_layer(u32 hdr); +/*! gets the frame size of an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the size in bytes (includes the 4 bytes header size) +*/ u16 gf_mp3_frame_size(u32 hdr); -u32 gf_mp3_get_next_header(FILE* in); -u32 gf_mp3_get_next_header_mem(const char *buffer, u32 size, u32 *pos); +/*! locates the next frame start in an MPEG-1/2/3 audio stream file +\param fin file to search +\return the frame header value, or 0 if not found +*/ +u32 gf_mp3_get_next_header(FILE* fin); +/*! locates the next frame start in an MPEG-1/2/3 audio buffer +\param buffer buffer to search +\param size size of buffer to search +\param pos set to the start position of the frame header in the buffer +\return the frame header value, or 0 if not found +*/ +u32 gf_mp3_get_next_header_mem(const u8 *buffer, u32 size, u32 *pos); #endif /*GPAC_DISABLE_AV_PARSERS*/ +/*! gets the version size of an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the version +*/ u8 gf_mp3_version(u32 hdr); +/*! gets the version name of an MPEG-1/2/3 audio frame +\param hdr the frame header +\return the version name +*/ const char *gf_mp3_version_name(u32 hdr); - #if !defined(GPAC_DISABLE_AV_PARSERS) && !defined (GPAC_DISABLE_OGG) -/*vorbis tools*/ +/*! OGG audio codec descriptor*/ +typedef struct ogg_audio_codec_desc_t +{ + /*! name of the codec*/ + const char* codec_name; + /*! private*/ + void *parserPrivateState; + /*! number of channels*/ + int channels; + /*! samplerate*/ + int sample_rate; + + /*! process the data and returns the sample block_size (i.e. duration)*/ + GF_Err (*process)(struct ogg_audio_codec_desc_t *parserState, u8 *data, u32 data_length, void *importer, Bool *destroy_esd, u32 *track, u32 *di, u64 *duration, int *block_size); + /*! release private state*/ + void (*release)(struct ogg_audio_codec_desc_t *parserState); +} ogg_audio_codec_desc; + +/*! OGG Vorbis parser*/ typedef struct { - u32 sample_rate, channels, version; - s32 max_r, avg_r, low_r; + u32 version, num_headers; u32 min_block, max_block; + u32 max_r, avg_r, low_r; /*do not touch, parser private*/ - Bool is_init; u32 modebits; Bool mode_flag[64]; + u32 nb_init; + GF_BitStream *vbs; + + u32 sample_rate, channels; } GF_VorbisParser; -/*call with vorbis header packets - you MUST initialize the structure to 0 before!! -returns 1 if success, 0 if error.*/ -Bool gf_vorbis_parse_header(GF_VorbisParser *vp, char *data, u32 data_len); -/*returns 0 if init error or not a vorbis frame, otherwise returns the number of audio samples -in this frame*/ -u32 gf_vorbis_check_frame(GF_VorbisParser *vp, char *data, u32 data_length); +/*! parses vorbis header packets + initializes the parser on success, leave it to NULL otherwise +\param vp pointer to a vorbis parser to use +\param data source buffer +\param data_len size of buffer +\return GF_TRUE if success, GF_FALSE otherwise +*/ +Bool gf_vorbis_parse_header(GF_VorbisParser *vp, u8 *data, u32 data_len); + +/*! checks vorbis frame +\param vp the vorbis parser to use +\param data source buffer +\param data_len size of buffer +\return 0 if init error or not a vorbis frame, otherwise returns the number of audio samples in this frame +*/ +u32 gf_vorbis_check_frame(GF_VorbisParser *vp, u8 *data, u32 data_len); + +/*! OPUS parser*/ +typedef struct +{ + u32 version; + + u32 sample_rate, channels; + u8 OutputChannelCount; + u16 PreSkip; + u32 InputSampleRate; + u16 OutputGain; + + u8 ChannelMappingFamily, StreamCount, CoupledCount; + u8 ChannelMapping[255]; +} GF_OpusParser; + +/*! parses opus header packets - initializes the parser on success, leave it to NULL otherwise +\param op pointer to a vorbis parser to use +\param data opus header buffer to parse +\param data_len size of opus header buffer +\return 1 if success, 0 if error +*/ +Bool gf_opus_parse_header(GF_OpusParser *op, u8 *data, u32 data_len); + +/*! checks if an opus frame is valid +\param op the vorbis parser to use +\param data source buffer +\param data_len size of buffer +\return 0 if init error or not a vorbis frame, otherwise returns the number of audio samples in this frame*/ +u32 gf_opus_check_frame(GF_OpusParser *op, u8 *data, u32 data_len); #endif /*!defined(GPAC_DISABLE_AV_PARSERS) && !defined (GPAC_DISABLE_OGG)*/ +/*! reads escaped value according to usac/mpegh +\param bs bitstream object +\param nBits1 first number of bits +\param nBits2 second number of bits +\param nBits3 third number set of bits +\return value read +*/ +u64 gf_mpegh_escaped_value(GF_BitStream *bs, u32 nBits1, u32 nBits2, u32 nBits3); + +/*! parse profile and level from a MHAS payload +\param ptr the MHAS payhload +\param size size of the MHAS payhload +\return the MHAS profile found, or -1 of not found +*/ +s32 gf_mpegh_get_mhas_pl(u8 *ptr, u32 size); +/*! reads a 32 bit sync safe integer of id3v2 from a bitstream object +\param bs the bitstream object to use - has to be positioned on the start if an id3v2 size field +\return the id3v2 size field read +*/ +u32 gf_id3_read_size(GF_BitStream *bs); + +/*! MPEG-4 audio object types*/ enum { + /*! AAC main*/ GF_M4A_AAC_MAIN = 1, + /*! AAC LC*/ GF_M4A_AAC_LC = 2, + /*! AAC SSR*/ GF_M4A_AAC_SSR = 3, + /*! AAC LTP*/ GF_M4A_AAC_LTP = 4, + /*! AAC SBR*/ GF_M4A_AAC_SBR = 5, + /*! AAC scalable*/ GF_M4A_AAC_SCALABLE = 6, + /*! TwinVQ*/ GF_M4A_TWINVQ = 7, + /*! CELP*/ GF_M4A_CELP = 8, + /*! HVXC*/ GF_M4A_HVXC = 9, + /*! TTSI*/ GF_M4A_TTSI = 12, + /*! Main Synthetic*/ GF_M4A_MAIN_SYNTHETIC = 13, + /*! Wavetable synthesis*/ GF_M4A_WAVETABLE_SYNTHESIS = 14, + /*! General Midi*/ GF_M4A_GENERAL_MIDI = 15, + /*! AudioFX synthesis*/ GF_M4A_ALGO_SYNTH_AUDIO_FX = 16, + /*! Error Resilient AAC LC*/ GF_M4A_ER_AAC_LC = 17, + /*! Error Resilient AAC LTP*/ GF_M4A_ER_AAC_LTP = 19, + /*! Error Resilient AAC Scalable*/ GF_M4A_ER_AAC_SCALABLE = 20, + /*! Error Resilient TwinVQ*/ GF_M4A_ER_TWINVQ = 21, + /*! Error Resilient BSAC*/ GF_M4A_ER_BSAC = 22, + /*! Error Resilient AAC LD*/ GF_M4A_ER_AAC_LD = 23, + /*! Error Resilient CELP*/ GF_M4A_ER_CELP = 24, + /*! Error Resilient HVXC*/ GF_M4A_ER_HVXC = 25, + /*! Error Resilient HILN*/ GF_M4A_ER_HILN = 26, + /*! Error Resilient Parametric*/ GF_M4A_ER_PARAMETRIC = 27, + /*! SSC*/ GF_M4A_SSC = 28, + /*! AAC PS*/ GF_M4A_AAC_PS = 29, + /*! MPEG-1/2 layer 1*/ GF_M4A_LAYER1 = 32, + /*! MPEG-1/2 layer 2*/ GF_M4A_LAYER2 = 33, + /*! MPEG-1/2 layer 3*/ GF_M4A_LAYER3 = 34, + /*! DST*/ GF_M4A_DST = 35, + /*! ALS*/ GF_M4A_ALS = 36 }; -#ifndef GPAC_DISABLE_AV_PARSERS - +/*! AAC sample rates*/ static const u32 GF_M4ASampleRates[] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, 0, 0, 0 }; -/*new values should now be defined in 23001-8*/ +/*! AAC channel configurations*/ static const u32 GF_M4ANumChannels[] = { 1, 2, 3, 4, 5, 6, 8, 2, 3, 4, 7, 8, 24, 8, 12, 10, 12, 14 }; -/*returns channel config value (as written in AAC DSI) for the given number of channels*/ +#ifndef GPAC_DISABLE_AV_PARSERS + +/*! returns channel config value (as written in AAC DSI) for the given number of channels +\param nb_chan the number of channels +\return the MPEG-4 AAC channel config value +*/ u32 gf_m4a_get_channel_cfg(u32 nb_chan); -/*get Audio type from dsi. return audio codec type:*/ +/*! MPEG-4 Audio decoder specific info*/ typedef struct { + /*Number of channels*/ u32 nb_chan; - u32 base_object_type, base_sr, base_sr_index; - /*SBR*/ + /*base audio object type*/ + u32 base_object_type; + /*base sample rate*/ + u32 base_sr; + /*index of base sample rate*/ + u32 base_sr_index; + /*set if SBR (Spectral Band Replication) is present*/ Bool has_sbr; - u32 sbr_object_type, sbr_sr, sbr_sr_index; - /*PS*/ + /*sbr audio object type*/ + u32 sbr_object_type; + /*SBR sample rate*/ + u32 sbr_sr; + /*SBR sample index*/ + u32 sbr_sr_index; + /*set if PS (Parametric Stereo) is present*/ Bool has_ps; - /*PL indication*/ + /*audio Profile level indication*/ u8 audioPL; - /*program config element*/ - Bool program_config_element_present, mono_mixdown_present, stereo_mixdown_present, matrix_mixdown_idx_present, pseudo_surround_enable ; - u8 element_instance_tag, object_type, sampling_frequency_index, num_front_channel_elements, num_side_channel_elements, num_back_channel_elements, num_lfe_channel_elements, num_assoc_data_elements, num_valid_cc_elements; - u8 mono_mixdown_element_number, stereo_mixdown_element_number, matrix_mixdown_idx; - - u8 front_element_is_cpe[15], front_element_tag_select[15]; - u8 side_element_is_cpe[15], side_element_tag_select[15]; - u8 back_element_is_cpe[15], back_element_tag_select[15]; + /*set if program config element is present - members until end of struct are ignored/invalid if this is not set*/ + Bool program_config_element_present; + /*set if mono mixdown is present*/ + Bool mono_mixdown_present; + /*set if stereo mixdown is present*/ + Bool stereo_mixdown_present; + /*set if matrix mixdown is present*/ + Bool matrix_mixdown_idx_present; + /*set if pseudo surround is present*/ + Bool pseudo_surround_enable; + /*element instance*/ + u8 element_instance_tag; + /*object type*/ + u8 object_type; + /*samplerate index*/ + u8 sampling_frequency_index; + /*number of front channels*/ + u8 num_front_channel_elements; + /*number of side channels*/ + u8 num_side_channel_elements; + /*number of back channels*/ + u8 num_back_channel_elements; + /*number of LFE channels*/ + u8 num_lfe_channel_elements; + /*number of associated data channels*/ + u8 num_assoc_data_elements; + /*number of valid CC elements*/ + u8 num_valid_cc_elements; + /*id of the mono mixdown element*/ + u8 mono_mixdown_element_number; + /*id of the stereo mixdown element*/ + u8 stereo_mixdown_element_number; + /*id of the matrix mixdown element*/ + u8 matrix_mixdown_idx; + /*front channels cpe flags*/ + u8 front_element_is_cpe[15]; + /*front channels select flags*/ + u8 front_element_tag_select[15]; + /*side channels cpe flags*/ + u8 side_element_is_cpe[15]; + /*side channels select flags*/ + u8 side_element_tag_select[15]; + /*back channels cpe flags*/ + u8 back_element_is_cpe[15]; + /*back channels select flags*/ + u8 back_element_tag_select[15]; + /*LFE channels select flags*/ u8 lfe_element_tag_select[15]; + /*associated data select flags*/ u8 assoc_data_element_tag_select[15]; - u8 cc_element_is_ind_sw[15], valid_cc_element_tag_select[15]; + /*CC elements indep flags*/ + u8 cc_element_is_ind_sw[15]; + /*CC elements select flags*/ + u8 valid_cc_element_tag_select[15]; + /*size of comment field*/ u8 comment_field_bytes; + /*comment field*/ u8 comments[255]; } GF_M4ADecSpecInfo; -/*parses dsi and updates audioPL*/ -GF_Err gf_m4a_get_config(char *dsi, u32 dsi_size, GF_M4ADecSpecInfo *cfg); -/*gets audioPL for given cfg*/ +/*! parses MPEG-4 audio dsi +\param dsi the buffer containing the decoder config +\param dsi_size size of the buffer containing the decoder config +\param cfg will be filled with the parsed value +\return error code if any +*/ +GF_Err gf_m4a_get_config(u8 *dsi, u32 dsi_size, GF_M4ADecSpecInfo *cfg); +/*! gets audio profile and level for a given configuration +\param cfg the parsed MPEG-4 audio configuration +\return audio PL +*/ u32 gf_m4a_get_profile(GF_M4ADecSpecInfo *cfg); -GF_Err gf_m4a_write_config(GF_M4ADecSpecInfo *cfg, char **dsi, u32 *dsi_size); +/*! writes MPEG-4 audio dsi in a byte buffer +\param cfg the configuration to write +\param dsi set to the encoded buffer (to be freed by caller) +\param dsi_size set to the size of the encoded buffer +\return error code if any +*/ +GF_Err gf_m4a_write_config(GF_M4ADecSpecInfo *cfg, u8 **dsi, u32 *dsi_size); +/*! writes MPEG-4 audio dsi in a bitstream object +\param bs the bitstream object to write to +\param cfg the configuration to write +\return error code if any +*/ GF_Err gf_m4a_write_config_bs(GF_BitStream *bs, GF_M4ADecSpecInfo *cfg); +/*! parses MPEG-4 audio dsi from bitstream +\param bs the bitstream object to use (shall start in the begining of the dsi) +\param cfg will be filled with the parsed value +\param size_known set to GF_TRUE if the bitstream contains the complete DSI (and only it) +\return error code if any +*/ GF_Err gf_m4a_parse_config(GF_BitStream *bs, GF_M4ADecSpecInfo *cfg, Bool size_known); #endif /*GPAC_DISABLE_AV_PARSERS*/ +/*! gets the name of a given MPEG-4 audio object type +\param objectType the object type +\return the MPEG-4 audio object type name +*/ const char *gf_m4a_object_type_name(u32 objectType); +/*! gets the name of the the given MPEG-4 audio profile +\param audio_pl the profile/level +\return the MPEG-4 audio profile name +*/ const char *gf_m4a_get_profile_name(u8 audio_pl); #ifndef GPAC_DISABLE_AV_PARSERS +/*! AC-3 header*/ +typedef struct +{ + u8 fscod, bsid, bsmod, acmod, lfon, brcode; + u8 asvc, num_dep_sub; + u16 chan_loc; +} GF_AC3StreamInfo; +/*! AC-3 header*/ typedef struct { u32 bitrate; @@ -278,43 +643,184 @@ typedef struct u32 channels; u16 substreams; //bit-mask, used for channel map > 5.1 /*only set if full parse*/ - u8 fscod, bsid, bsmod, acmod, lfon, brcode; + GF_AC3StreamInfo streams[8]; //0->7 sibstream ids + u8 nb_streams; //main and substream, only independent ones + //data rate in kbps + u32 data_rate; } GF_AC3Header; +/*! parses an AC-3 header from a buffer +\param buffer buffer to parse +\param buffer_size size of buffer to parse +\param pos set to start offset (in bytes) of the AC3 header parsed +\param out_hdr will be filled by parser +\param full_parse if GF_TRUE, complete parsing of the header will be done +\return GF_TRUE if success +*/ Bool gf_ac3_parser(u8 *buffer, u32 buffer_size, u32 *pos, GF_AC3Header *out_hdr, Bool full_parse); +/*! parses an AC-3 header from a bitstream +\param bs bitstream to parse +\param hdr will be filled by parser +\param full_parse if GF_TRUE, complete parsing of the header will be done +\return GF_TRUE if success +*/ Bool gf_ac3_parser_bs(GF_BitStream *bs, GF_AC3Header *hdr, Bool full_parse); +/*! parses an EAC-3 header from a bitstream +\param bs bitstream to parse +\param hdr will be filled by parser +\param full_parse if GF_TRUE, complete parsing of the header will be done +\return GF_TRUE if success +*/ Bool gf_eac3_parser_bs(GF_BitStream *bs, GF_AC3Header *hdr, Bool full_parse); +/*! gets the number of channels in an AC3 frame +\param acmod acmod of the associated frame header +\return number of channels +*/ u32 gf_ac3_get_channels(u32 acmod); +/*! gets the bitrate of an AC3 frame +\param brcode brcode of the associated frame header +\return bitrate +*/ u32 gf_ac3_get_bitrate(u32 brcode); -GF_Err gf_avc_get_sps_info(char *sps, u32 sps_size, u32 *sps_id, u32 *width, u32 *height, s32 *par_n, s32 *par_d); -GF_Err gf_avc_get_pps_info(char *pps, u32 pps_size, u32 *pps_id, u32 *sps_id); +/*! gets basic information from an AVC Sequence Parameter Set +\param sps SPS NAL buffer +\param sps_size size of buffer +\param sps_id set to the ID +\param width set to the width +\param height set to the height +\param par_n set to the pixel aspect ratio numerator +\param par_d set to the pixel aspect ratio denominator +\return error code if any +*/ +GF_Err gf_avc_get_sps_info(u8 *sps, u32 sps_size, u32 *sps_id, u32 *width, u32 *height, s32 *par_n, s32 *par_d); +/*! gets basic information from an AVC Picture Parameter Set +\param pps PPS NAL buffer +\param pps_size size of buffer +\param pps_id set to the ID of the PPS +\param sps_id set to the SPS ID of the PPS +\return error code if any +*/ +GF_Err gf_avc_get_pps_info(u8 *pps, u32 pps_size, u32 *pps_id, u32 *sps_id); + +/*! gets basic information from an HEVC Sequence Parameter Set +\param sps_data SPS NAL buffer +\param sps_size size of buffer +\param sps_id set to the ID +\param width set to the width +\param height set to the height +\param par_n set to the pixel aspect ratio numerator +\param par_d set to the pixel aspect ratio denominator +\return error code if any +*/ +GF_Err gf_hevc_get_sps_info(u8 *sps_data, u32 sps_size, u32 *sps_id, u32 *width, u32 *height, s32 *par_n, s32 *par_d); -//hevc_state is optional but shall be used for layer extensions since all size info is in VPS and not SPS -GF_Err gf_hevc_get_sps_info(char *sps_data, u32 sps_size, u32 *sps_id, u32 *width, u32 *height, s32 *par_n, s32 *par_d); #endif /*GPAC_DISABLE_AV_PARSERS*/ -//get chroma format name from chroma format (1: 420, 2: 422, 3: 444 +/*! gets chroma format name from MPEG chroma format +\param chroma_format the chroma format to query (1: 420, 2: 422, 3: 444) +\return the name of the format +*/ const char * gf_avc_hevc_get_chroma_format_name(u8 chroma_format); -/*get AVC profile name from profile indication*/ +/*! gets AVC profile name from profile indication +\param profile_idc the PL indication +\return the name of the profile +*/ const char *gf_avc_get_profile_name(u8 profile_idc); -/*returns true if given profile is in range extensions*/ +/*! checks if an AVC profile is a range extension profile +\param profile_idc the PL indication +\return GF_TRUE if given profile is in range extensions +*/ Bool gf_avc_is_rext_profile(u8 profile_idc); -/*get HEVC profile name from profile indication*/ -const char *gf_hevc_get_profile_name(u8 profile_idc); - -/*gets image size (bs must contain the whole image) -@OTI: image type (JPEG=0x6C, PNG=0x6D) -@width, height: image resolution - for jpeg max size if thumbnail included*/ -void gf_img_parse(GF_BitStream *bs, u8 *OTI, u32 *mtype, u32 *width, u32 *height, char **dsi, u32 *dsi_len); +/*! gets HEVC profile name from profile indication +\param profile_idc the PL indication +\return the name of the profile +*/ +const char *gf_hevc_get_profile_name(u8 profile_idc); -GF_Err gf_img_jpeg_dec(char *jpg, u32 jpg_size, u32 *width, u32 *height, u32 *pixel_format, char *dst, u32 *dst_size, u32 dst_nb_comp); -GF_Err gf_img_png_dec(char *png, u32 png_size, u32 *width, u32 *height, u32 *pixel_format, char *dst, u32 *dst_size); -GF_Err gf_img_file_dec(char *png_file, u32 *oti, u32 *width, u32 *height, u32 *pixel_format, char **dst, u32 *dst_size); -GF_Err gf_img_png_enc(char *data, u32 width, u32 height, s32 stride, u32 pixel_format, char *dst, u32 *dst_size); -GF_Err gf_img_png_enc_file(char *data, u32 width, u32 height, s32 stride, u32 pixel_format, char *dst_file); +/*! parses an image from a bitstream oject. The bitstream must contain the whole image. If a thumbnail is included in the image, the indicated resolution is the one of the main image. +\param bs the source bitstream +\param codecid set to the codec ID of the image +\param width set to the width of the image +\param height set to the height of the image +\param dsi set to a buffer containing the decoder config of the image if any (in whihc case this buffer shall be freed by the caller) +\param dsi_len set to the allocated buffer size +*/ +void gf_img_parse(GF_BitStream *bs, u32 *codecid, u32 *width, u32 *height, u8 **dsi, u32 *dsi_len); + +/*! decodes a JPEG image in a preallocated buffer +\param jpg the JPEG buffer +\param jpg_size size of the JPEG buffer +\param width set to width of the image +\param height set to height of the image +\param pixel_format set to pixel format of the image +\param dst buffer to hold the decoded pixels (may be NULL) +\param dst_size size in bytes of the buffer to hold the decoded pixels (may be 0) +\param dst_nb_comp number of components in destination buffer +\return GF_BUFFER_TOO_SMALL if destination buffer is too small or error if any +*/ +GF_Err gf_img_jpeg_dec(u8 *jpg, u32 jpg_size, u32 *width, u32 *height, u32 *pixel_format, u8 *dst, u32 *dst_size, u32 dst_nb_comp); + +/*! decodes a PNG image in a preallocated buffer +\param png the PNG buffer +\param png_size size of the PNG buffer +\param width set to width of the image +\param height set to height of the image +\param pixel_format set to pixel format of the image +\param dst buffer to hold the decoded pixels (may be NULL) +\param dst_size size in bytes of the buffer to hold the decoded pixels (may be 0) +\return GF_BUFFER_TOO_SMALL if destination buffer is too small or error if any +*/ +GF_Err gf_img_png_dec(u8 *png, u32 png_size, u32 *width, u32 *height, u32 *pixel_format, u8 *dst, u32 *dst_size); +/*! encodes a raw image into a PNG image +\param data the pixel data +\param width the pixel width +\param height the pixel height +\param stride the pixel horizontal stride +\param pixel_format pixel format of the image +\param dst buffer to hold the decoded pixels (may be NULL) +\param dst_size set to the size in bytes of the buffer to hold the decoded pixels (may be 0) +\return GF_BUFFER_TOO_SMALL if destination buffer is too small or error if any +*/ +GF_Err gf_img_png_enc(u8 *data, u32 width, u32 height, s32 stride, u32 pixel_format, u8 *dst, u32 *dst_size); + + +/*!\brief OBU types*/ +typedef enum { + OBU_RESERVED_0 = 0, + OBU_SEQUENCE_HEADER = 1, + OBU_TEMPORAL_DELIMITER = 2, + OBU_FRAME_HEADER = 3, + OBU_TILE_GROUP = 4, + OBU_METADATA = 5, + OBU_FRAME = 6, + OBU_REDUNDANT_FRAME_HEADER = 7, + OBU_TILE_LIST = 8, + OBU_RESERVED_9 = 9, + OBU_RESERVED_10 = 10, + OBU_RESERVED_11 = 11, + OBU_RESERVED_12 = 12, + OBU_RESERVED_13 = 13, + OBU_RESERVED_14 = 14, + OBU_PADDING = 15, +} ObuType; + +/*!\brief OBU metadata types*/ +typedef enum { + OBU_METADATA_TYPE_HDR_CLL = 1, + OBU_METADATA_TYPE_HDR_MDCV = 2, + OBU_METADATA_TYPE_SCALABILITY = 3, + OBU_METADATA_TYPE_ITUT_T35 = 4, + OBU_METADATA_TYPE_TIMECODE = 5 +} ObuMetadataType; + +/*! gets the name of a given OBU type +\param obu_type the OBU type +\return the OBU name +*/ +const char *gf_av1_get_obu_name(ObuType obu_type); /*! @} */ diff --git a/include/gpac/base_coding.h b/include/gpac/base_coding.h index ad99e6a..6dd73ab 100644 --- a/include/gpac/base_coding.h +++ b/include/gpac/base_coding.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -31,17 +31,17 @@ extern "C" { #endif /*! - * \file - * \brief Base 16 and 64 coding. - */ +\file +\brief Base 16 and 64 coding. +*/ /*! - * \addtogroup bascod_grp Base coding - * \ingroup utils_grp - * \brief Base 16 and 64 coding - * - * This section documents the base encoding and decoding functions of the GPAC framework. - * @{ +\addtogroup bascod_grp +\brief Base 16 and 64 coding + +This section documents the base encoding and decoding functions of the GPAC framework. + +@{ */ #include @@ -49,53 +49,53 @@ extern "C" { #ifndef GPAC_DISABLE_CORE_TOOLS /*! - *\brief base64 encoder - * - *Encodes a data buffer to Base64 - *\param in_buffer input data buffer - *\param in_buffer_size input data buffer size - *\param out_buffer output Base64 buffer location - *\param out_buffer_size output Base64 buffer allocated size - *\return size of the encoded Base64 buffer - *\note the encoded data buffer is not NULL-terminated. +\brief base64 encoder + +Encodes a data buffer to Base64 +\param in_buffer input data buffer +\param in_buffer_size input data buffer size +\param out_buffer output Base64 buffer location +\param out_buffer_size output Base64 buffer allocated size +\return size of the encoded Base64 buffer +\note the encoded data buffer is not NULL-terminated. */ -u32 gf_base64_encode(const char *in_buffer, u32 in_buffer_size, char *out_buffer, u32 out_buffer_size); +u32 gf_base64_encode(const u8 *in_buffer, u32 in_buffer_size, u8 *out_buffer, u32 out_buffer_size); /*! - *\brief base64 decoder - * - *Decodes a Base64 buffer to data - *\param in_buffer input Base64 buffer - *\param in_buffer_size input Base64 buffer size - *\param out_buffer output data buffer location - *\param out_buffer_size output data buffer allocated size - *\return size of the decoded buffer +\brief base64 decoder + +Decodes a Base64 buffer to data +\param in_buffer input Base64 buffer +\param in_buffer_size input Base64 buffer size +\param out_buffer output data buffer location +\param out_buffer_size output data buffer allocated size +\return size of the decoded buffer */ -u32 gf_base64_decode(char *in_buffer, u32 in_buffer_size, char *out_buffer, u32 out_buffer_size); +u32 gf_base64_decode(u8 *in_buffer, u32 in_buffer_size, u8 *out_buffer, u32 out_buffer_size); /*! - *\brief base16 encoder - * - *Encodes a data buffer to Base16 - *\param in_buffer input data buffer - *\param in_buffer_size input data buffer size - *\param out_buffer output Base16 buffer location - *\param out_buffer_size output Base16 buffer allocated size - *\return size of the encoded Base16 buffer - *\note the encoded data buffer is not NULL-terminated. +\brief base16 encoder + +Encodes a data buffer to Base16 +\param in_buffer input data buffer +\param in_buffer_size input data buffer size +\param out_buffer output Base16 buffer location +\param out_buffer_size output Base16 buffer allocated size +\return size of the encoded Base16 buffer +\note the encoded data buffer is not NULL-terminated. */ -u32 gf_base16_encode(char *in_buffer, u32 in_buffer_size, char *out_buffer, u32 out_buffer_size); +u32 gf_base16_encode(u8 *in_buffer, u32 in_buffer_size, u8 *out_buffer, u32 out_buffer_size); /*! - *\brief base16 decoder - * - *Decodes a Base16 buffer to data - *\param in_buffer input Base16 buffer - *\param in_buffer_size input Base16 buffer size - *\param out_buffer output data buffer location - *\param out_buffer_size output data buffer allocated size - *\return size of the decoded buffer +\brief base16 decoder + +Decodes a Base16 buffer to data +\param in_buffer input Base16 buffer +\param in_buffer_size input Base16 buffer size +\param out_buffer output data buffer location +\param out_buffer_size output data buffer allocated size +\return size of the decoded buffer */ -u32 gf_base16_decode(char *in_buffer, u32 in_buffer_size, char *out_buffer, u32 out_buffer_size); +u32 gf_base16_decode(u8 *in_buffer, u32 in_buffer_size, u8 *out_buffer, u32 out_buffer_size); /*! @} */ diff --git a/include/gpac/bifs.h b/include/gpac/bifs.h index 6de4d2d..313c3a8 100644 --- a/include/gpac/bifs.h +++ b/include/gpac/bifs.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / BIFS codec sub-project @@ -31,17 +31,17 @@ extern "C" { #endif /*! - * \file - * \brief MPEG-4 BIFS encoding and decoding. - */ +\file +\brief MPEG-4 BIFS encoding and decoding. +*/ /*! - * \addtogroup bifs_grp MPEG-4 BIFS - * \ingroup mpeg4sys_grp - * \brief MPEG-4 BIFS encoding and decoding - * - *This section documents the BIFS encoding and decoding of the GPAC framework. For scene graph documentation, check scenegraph.h - * @{ +\addtogroup bifs_grp MPEG-4 BIFS +\ingroup mpeg4sys_grp +\brief MPEG-4 BIFS encoding and decoding + +This section documents the BIFS encoding and decoding of the GPAC framework. For scene graph documentation, check scenegraph.h +@{ */ @@ -51,62 +51,116 @@ extern "C" { #ifndef GPAC_DISABLE_BIFS - +/*! BIFS decoder*/ typedef struct __tag_bifs_dec GF_BifsDecoder; -/*BIFS decoder constructor - - @command_dec: if set, the decoder will only work in memory mode (creating commands for the graph) - otherwise the decoder will always apply commands while decoding them*/ +/*! creates a new BIFS decoder +\param scenegraph the scene graph on which the decoder operates +\param command_dec if set, the decoder will only work in memory mode (creating commands for the graph) + otherwise the decoder will always apply commands while decoding them +\return a new BIFS decoder, NULL if error +*/ GF_BifsDecoder *gf_bifs_decoder_new(GF_SceneGraph *scenegraph, Bool command_dec); +/*! destroys a BIFS decoder +\param codec the decoder to destroy +*/ void gf_bifs_decoder_del(GF_BifsDecoder *codec); -/*assigns extraction path for BIFS decoder - default is current directory*/ -void gf_bifs_decoder_set_extraction_path(GF_BifsDecoder *codec, const char *path, const char *serviceURL); - -/*sets the scene time. Scene time is the real clock of the bifs stream in secs*/ -void gf_bifs_decoder_set_time_offset(GF_BifsDecoder *codec, Double ts); - -/*signals the sizeInfo of the config should be ignored - used for BIFS in AnimationStream nodes*/ -void gf_bifs_decoder_ignore_size_info(GF_BifsDecoder *codec); - -/*setup a stream*/ -GF_Err gf_bifs_decoder_configure_stream(GF_BifsDecoder *codec, u16 ESID, char *DecoderSpecificInfo, u32 DecoderSpecificInfoLength, u32 objectTypeIndication); -/*removes a stream*/ -GF_Err gf_bifs_decoder_remove_stream(GF_BifsDecoder *codec, u16 ESID); - -/*decode a BIFS AU and applies it to the graph (non-memory mode only)*/ -GF_Err gf_bifs_decode_au(GF_BifsDecoder *codec, u16 ESID, const char *data, u32 data_length, Double ts_offset); - -/*Memory BIFS decoding - fills the command list with the content of the AU - cf scenegraph_vrml.h for commands usage - @ESID: ID of input stream - @data, @data_length: BIFS AU - @com_list: target list for decoded commands +/*! sets up a new BIFS stream +\param codec the BIFS decoder to use +\param ESID the ESID of the stream +\param DecoderSpecificInfo the decoder config of the stream +\param DecoderSpecificInfoLength the size of the decoder config of the stream +\param objectTypeIndication the object type indication for this stream +\return error if any */ -GF_Err gf_bifs_decode_command_list(GF_BifsDecoder *codec, u16 ESID, char *data, u32 data_length, GF_List *com_list); +GF_Err gf_bifs_decoder_configure_stream(GF_BifsDecoder *codec, u16 ESID, u8 *DecoderSpecificInfo, u32 DecoderSpecificInfoLength, u32 objectTypeIndication); + +/*! decodes a BIFS access unit and applies it to the graph (non-memory mode only) +\param codec the BIFS decoder to use +\param ESID the ESID of the stream +\param data the compressed BIFS access unit +\param data_length the size of the compressed BIFS access unit +\param ts_offset the time in seconds of the access unit since the start of the scene +\return error if any +*/ +GF_Err gf_bifs_decode_au(GF_BifsDecoder *codec, u16 ESID, const u8 *data, u32 data_length, Double ts_offset); + +/*! decodes a BIFS access unit in memory - cf scenegraph_vrml.h for commands usage +\param codec the BIFS decoder to use +\param ESID the ESID of the stream +\param data the compressed BIFS access unit +\param data_length the size of the compressed BIFS access unit +\param com_list list object to retrieve the list of decoded commands +\return error if any +*/ +GF_Err gf_bifs_decode_command_list(GF_BifsDecoder *codec, u16 ESID, u8 *data, u32 data_length, GF_List *com_list); +/*! checks if conditionnals have been defined for the decoder +\param codec the BIFS decoder to check +\return GF_TRUE if the decoder has conditionnal nodes attached +*/ +Bool gf_bifs_decode_has_conditionnals(GF_BifsDecoder *codec); #ifndef GPAC_DISABLE_BIFS_ENC -/*BIFS encoding*/ +/*! BIFS encoder*/ typedef struct __tag_bifs_enc GF_BifsEncoder; -/*constructor - @graph: scene graph being encoded*/ +/*! creates a new BIFS encoder +\param graph the scene graph being encoded +\return a new BIFS encoder, NULL if error +*/ GF_BifsEncoder *gf_bifs_encoder_new(GF_SceneGraph *graph); -/*destructor*/ +/*! destroys a BIFS encoder +\param codec the BIFS encoder +*/ void gf_bifs_encoder_del(GF_BifsEncoder *codec); -/*setup a destination stream*/ +/*! configures a new destination stream +\param codec the BIFS encoder to use +\param ESID the ESID of the created stream +\param cfg object describing the configuration of the stream +\param encodeNames if set, names of nodes with IDs will be encoded (as strings) +\param has_predictive set to GF_TRUE if the stream uses predictive field coding +\return error if any +*/ GF_Err gf_bifs_encoder_new_stream(GF_BifsEncoder *codec, u16 ESID, GF_BIFSConfig *cfg, Bool encodeNames, Bool has_predictive); -/*encodes a list of commands for the given stream in the output buffer - data is dynamically allocated for output +/*! encodes a list of commands for the given stream in the output buffer - data is dynamically allocated for output the scenegraph used is the one described in SceneReplace command, hence scalable streams shall be encoded in time order +\param codec the BIFS encoder to use +\param ESID the ESID of the stream owning the command list +\param command_list the list of commands to encode +\param out_data set to the allocated output buffer, to be freed by the caller +\param out_data_length set to the size of the allocated output buffer +\return error if any +*/ +GF_Err gf_bifs_encode_au(GF_BifsEncoder *codec, u16 ESID, GF_List *command_list, u8 **out_data, u32 *out_data_length); +/*! returns the encoded decoder configuration of a given stream +\param codec the BIFS encoder to use +\param ESID the ESID of the stream +\param out_data set to the allocated output buffer, to be freed by the caller +\param out_data_length set to the size of the allocated output buffer +\return error if any +*/ +GF_Err gf_bifs_encoder_get_config(GF_BifsEncoder *codec, u16 ESID, u8 **out_data, u32 *out_data_length); +/*! returns the BIFS version used by the encoder for a given stream +\param codec the BIFS encoder to use +\param ESID the ESID of the stream +\return BIFS version used */ -GF_Err gf_bifs_encode_au(GF_BifsEncoder *codec, u16 ESID, GF_List *command_list, char **out_data, u32 *out_data_length); -/*returns encoded config desc*/ -GF_Err gf_bifs_encoder_get_config(GF_BifsEncoder *codec, u16 ESID, char **out_data, u32 *out_data_length); -/*returns BIFS version used by codec for given stream*/ u8 gf_bifs_encoder_get_version(GF_BifsEncoder *codec, u16 ESID); -/*Encodes current graph as a scene replace*/ -GF_Err gf_bifs_encoder_get_rap(GF_BifsEncoder *codec, char **out_data, u32 *out_data_length); - +/*! encodes current graph as a scene replacement command +\param codec the BIFS encoder to use +\param out_data set to the allocated output buffer, to be freed by the caller +\param out_data_length set to the size of the allocated output buffer +\return error if any +*/ +GF_Err gf_bifs_encoder_get_rap(GF_BifsEncoder *codec, u8 **out_data, u32 *out_data_length); +/*! sets the URL of the source content being encoded, used for opening relative path files in some nodes attributes +\param codec the BIFS encoder to use +\param src_url URL (parent directory or source file) of content being encoded +\return error if any +*/ GF_Err gf_bifs_encoder_set_source_url(GF_BifsEncoder *codec, const char *src_url); #endif /*GPAC_DISABLE_BIFS_ENC*/ diff --git a/include/gpac/bitstream.h b/include/gpac/bitstream.h index eb94df4..cafbe22 100644 --- a/include/gpac/bitstream.h +++ b/include/gpac/bitstream.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -31,501 +31,597 @@ extern "C" { #endif /*! - * \file - * \brief bitstream reading and writing. - */ +\file + \brief bitstream reading and writing. +*/ /*! - * \addtogroup bs_grp Bitstream - * \ingroup utils_grp - * \brief BitStream object - * - * This section documents the bitstream object of the GPAC framework. - * \note Unless specified, all functions assume Big-Endian ordering of data in the bitstream. - * @{ +\addtogroup bs_grp +\brief Bitstream Reading and Writing + +This section documents the bitstream object of the GPAC framework. +\note Unless specified, all functions assume Big-Endian ordering of data in the bitstream. + +@{ */ #include +/*! bitstream creation modes*/ enum { + /*! read-only mode*/ GF_BITSTREAM_READ = 0, - GF_BITSTREAM_WRITE + /*! read-write mode*/ + GF_BITSTREAM_WRITE, + /*! allows reallocating the buffer passed in WRITE mode*/ + GF_BITSTREAM_WRITE_DYN }; +/*! bitstream object*/ typedef struct __tag_bitstream GF_BitStream; /*! - * \brief bitstream constructor - * - * Constructs a bitstream from a buffer (read or write mode) - * \param buffer buffer to read or write. In WRITE mode, this can be NULL to let the bitstream object dynamically allocate memory, in which case the size param is ignored. - * \param size size of the buffer given. - * \param mode operation mode for this bitstream: GF_BITSTREAM_READ for read, GF_BITSTREAM_WRITE for write. - * \return new bitstream object - * \note In write mode on an existing data buffer, data overflow is never signaled but simply ignored, it is the caller responsability to ensure it +\brief bitstream constructor + +Constructs a bitstream from a buffer (read or write mode) +\param buffer buffer to read or write. In WRITE mode, this can be NULL to let the bitstream object dynamically allocate memory, in which case the size param is ignored. +\param size size of the buffer given. +\param mode operation mode for this bitstream: GF_BITSTREAM_READ for read, GF_BITSTREAM_WRITE for write. +\return new bitstream object +\note In write mode on an existing data buffer, data overflow is never signaled but simply ignored, it is the caller responsability to ensure it * does not write more than possible. */ -GF_BitStream *gf_bs_new(const char *buffer, u64 size, u32 mode); +GF_BitStream *gf_bs_new(const u8 *buffer, u64 size, u32 mode); + /*! - * \brief bitstream constructor from file handle - * - * Creates a bitstream from a file handle. - * \param f handle of the file to use. This handle must be created with binary mode. - * \param mode operation mode for this bitstream: GF_BITSTREAM_READ for read, GF_BITSTREAM_WRITE for write. - * \return new bitstream object - * \note - You have to open your file in the appropriated mode:\n - * - GF_BITSTREAM_READ: bitstream is constructed for reading\n - * - GF_BITSTREAM_WRITE: bitstream is constructed for writing\n - * \note - you may use any of these modes for a file with read/write access. - * \warning RESULTS ARE UNEXPECTED IF YOU TOUCH THE FILE WHILE USING THE BITSTREAM. +\brief bitstream reassignment + +Reassigns a bitstream in GF_BITSTREAM_READ mode to a new buffer +\param bs the bitstream to reassign +\param buffer buffer to read +\param size size of the buffer given. +\return error code if any + */ +GF_Err gf_bs_reassign_buffer(GF_BitStream *bs, const u8 *buffer, u64 size); + +/*! +\brief bitstream constructor from file handle + +Creates a bitstream from a file handle. +\param f handle of the file to use. This handle must be created with binary mode. +\param mode operation mode for this bitstream: GF_BITSTREAM_READ for read, GF_BITSTREAM_WRITE for write. +\return new bitstream object +\note - You have to open your file in the appropriated mode:\n + - GF_BITSTREAM_READ: bitstream is constructed for reading\n + - GF_BITSTREAM_WRITE: bitstream is constructed for writing\n +\note - you may use any of these modes for a file with read/write access. +\warning RESULTS ARE UNEXPECTED IF YOU TOUCH THE FILE WHILE USING THE BITSTREAM. */ GF_BitStream *gf_bs_from_file(FILE *f, u32 mode); /*! - * \brief bitstream constructor from file handle - * - * Deletes the bitstream object. If the buffer was created by the bitstream, it is deleted if still present. +\brief bitstream destructor from file handle + +Deletes the bitstream object. If the buffer was created by the bitstream, it is deleted if still present. +\param bs the target bitstream */ void gf_bs_del(GF_BitStream *bs); /*! - * \brief sets bitstream write cache size - * - * Sets the write cache size for file-based bitstreams. - * \param bs the target bitstream - * \param size size of the write cache in bytes - * \return error if any. +\brief bitstream constructor from callback output + +Creates a bitstream from in write mode suing output callback. +\param on_block_out callback function used to write blocks. +\param usr_data user data for callback. +\param block_size internal buffer size used while dispatching bytes. If 0, defaults to 40k. +\return new bitstream object */ -GF_Err gf_bs_set_output_buffering(GF_BitStream *bs, u32 size); +GF_BitStream *gf_bs_new_cbk(GF_Err (*on_block_out)(void *cbk, u8 *data, u32 block_size), void *usr_data, u32 block_size); +/*! +\brief bitstream constructor from callback output and preallocated buffer + +Creates a bitstream from in write mode suing output callback. +\param on_block_out callback function used to write blocks. +\param usr_data user data for callback. +\param buffer internal buffer to use while dispatching bytes. If NULL, buffer is allocated internally using buffer_size. +\param buffer_size internal buffer size used while dispatching bytes. If 0, defaults to 40k. +\return new bitstream object + */ +GF_BitStream *gf_bs_new_cbk_buffer(GF_Err (*on_block_out)(void *cbk, u8 *data, u32 block_size), void *usr_data, u8 *buffer, u32 buffer_size); /*! - * \brief gets bitstream write cache size - * - * Gets the write cache size for file-based bitstreams. - * \param bs the target bitstream - * \return size of the write cache in bytes, 0 if no cache +\brief prevents block dispatch +Prevents byte dispatching in callback mode. This is used when seek operations are used. +\warning Block dispatch prevention acts in a counter mode: you must call as many time the function with prevent_dispatch = GF_FALSE as you called the function with prevent_dispatch = GF_TRUE +\param bs the target bitstream +\param prevent_dispatch activates temporary internal storage if set */ -u32 gf_bs_get_output_buffering(GF_BitStream *bs); +void gf_bs_prevent_dispatch(GF_BitStream *bs, Bool prevent_dispatch); + /*! - * \brief integer reading - * - * Reads an integer coded on a number of bit. - * \param bs the target bitstream - * \param nBits the number of bits to read - * \return the integer value read. +\brief integer reading + +Reads an integer coded on a number of bit. +\param bs the target bitstream +\param nBits the number of bits to read +\return the integer value read. */ u32 gf_bs_read_int(GF_BitStream *bs, u32 nBits); /*! - * \brief large integer reading - * - * Reads a large integer coded on a number of bit bigger than 32. - * \param bs the target bitstream - * \param nBits the number of bits to read - * \return the large integer value read. +\brief large integer reading + +Reads a large integer coded on a number of bit bigger than 32. +\param bs the target bitstream +\param nBits the number of bits to read +\return the large integer value read. */ u64 gf_bs_read_long_int(GF_BitStream *bs, u32 nBits); /*! - * \brief float reading - * - * Reads a float coded as IEEE 32 bit format. - * \param bs the target bitstream - * \return the float value read. +\brief float reading + +Reads a float coded as IEEE 32 bit format. +\param bs the target bitstream +\return the float value read. */ Float gf_bs_read_float(GF_BitStream *bs); /*! - * \brief double reading - * - * Reads a double coded as IEEE 64 bit format. - * \param bs the target bitstream - * \return the double value read. +\brief double reading + +Reads a double coded as IEEE 64 bit format. +\param bs the target bitstream +\return the double value read. */ Double gf_bs_read_double(GF_BitStream *bs); /*! - * \brief data reading - * - * Reads a data buffer - * \param bs the target bitstream - * \param data the data buffer to be filled - * \param nbBytes the amount of bytes to read - * \return the number of bytes actually read. - * \warning the data buffer passed must be large enough to hold the desired amount of bytes. +\brief data reading + +Reads a data buffer. Emultation prevention byte removal is not applied here ! +\param bs the target bitstream +\param data the data buffer to be filled +\param nbBytes the amount of bytes to read +\return the number of bytes actually read. +\warning the data buffer passed must be large enough to hold the desired amount of bytes. */ -u32 gf_bs_read_data(GF_BitStream *bs, char *data, u32 nbBytes); +u32 gf_bs_read_data(GF_BitStream *bs, u8 *data, u32 nbBytes); /*! - * \brief align char reading - * - * Reads an integer coded on 8 bits starting at a byte boundary in the bitstream. - * \warning you must not use this function if the bitstream is not aligned - * \param bs the target bitstream - * \return the char value read. +\brief align char reading + +Reads an integer coded on 8 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\return the char value read. */ u32 gf_bs_read_u8(GF_BitStream *bs); /*! - * \brief align char reading until reaching the given value - * - * Reads an integer coded on 8 bits starting at a byte boundary in the bitstream until - * the given appears on the bitstream. - * \note the bytes read in the bitstream will only be update if the delimiter is found - * \param bs the target bitstream - * \param delimiter the stop condition - * \param out the resulting value - * \param max_length the maximum length of the output - * \return the number of value read. - */ -u32 gf_bs_read_u8_until_delimiter(GF_BitStream *bs, u8 delimiter, u8* out, u32 max_length); +\brief align short reading -/*! - * \brief align short reading - * - * Reads an integer coded on 16 bits starting at a byte boundary in the bitstream. - * \warning you must not use this function if the bitstream is not aligned - * \param bs the target bitstream - * \return the short value read. +Reads an integer coded on 16 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\return the short value read. */ u32 gf_bs_read_u16(GF_BitStream *bs); /*! - * \brief align 24-bit integer reading - * - * Reads an integer coded on 24 bits starting at a byte boundary in the bitstream. - * \warning you must not use this function if the bitstream is not aligned - * \param bs the target bitstream - * \return the integer value read. +\brief align 24-bit integer reading + +Reads an integer coded on 24 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\return the integer value read. */ u32 gf_bs_read_u24(GF_BitStream *bs); /*! - * \brief align integer reading - * - * Reads an integer coded on 32 bits starting at a byte boundary in the bitstream. - * \warning you must not use this function if the bitstream is not aligned - * \param bs the target bitstream - * \return the integer value read. +\brief align integer reading + +Reads an integer coded on 32 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\return the integer value read. */ u32 gf_bs_read_u32(GF_BitStream *bs); /*! - * \brief align large integer reading - * - * Reads an integer coded on 64 bits starting at a byte boundary in the bitstream. - * \warning you must not use this function if the bitstream is not aligned - * \param bs the target bitstream - * \return the large integer value read. +\brief align large integer reading + +Reads an integer coded on 64 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\return the large integer value read. */ u64 gf_bs_read_u64(GF_BitStream *bs); + /*! - * \brief little endian integer reading - * - * Reads an integer coded on 32 bits in little-endian order. - * \param bs the target bitstream - * \return the integer value read. +\brief little endian integer reading + +Reads an integer coded on 64 bits in little-endian order. +\param bs the target bitstream +\return the large integer value read. + */ +u64 gf_bs_read_u64_le(GF_BitStream *bs); +/*! +\brief little endian integer reading + +Reads an integer coded on 32 bits in little-endian order. +\param bs the target bitstream +\return the integer value read. */ u32 gf_bs_read_u32_le(GF_BitStream *bs); /*! - * \brief little endian integer reading - * - * Reads an integer coded on 16 bits in little-endian order. - * \param bs the target bitstream - * \return the integer value read. +\brief little endian integer reading + +Reads an integer coded on 16 bits in little-endian order. +\param bs the target bitstream +\return the integer value read. */ u16 gf_bs_read_u16_le(GF_BitStream *bs); /*! - * \brief variable length integer reading - * - * Reads an integer coded on a variable number of 4-bits chunks. The number of chunks is given by the number of non-0 bits at the beginning. - * \param bs the target bitstream - * \return the integer value read. +\brief variable length integer reading + +Reads an integer coded on a variable number of 4-bits chunks. The number of chunks is given by the number of non-0 bits at the beginning. +\param bs the target bitstream +\return the integer value read. */ u32 gf_bs_read_vluimsbf5(GF_BitStream *bs); /*! - * \brief bit position - * - * Returns current bit position in the bitstream - only works in memory mode. - * \param bs the target bitstream - * \return the integer value read. +\brief bit position + +Returns current bit position in the bitstream - only works in memory mode. +\param bs the target bitstream +\return the integer value read. */ u32 gf_bs_get_bit_offset(GF_BitStream *bs); /*! - * \brief current bit position - * - * Returns bit position in the current byte of the bitstream - only works in memory mode. - * \param bs the target bitstream - * \return the integer value read. +\brief current bit position + +Returns bit position in the current byte of the bitstream - only works in memory mode. +\param bs the target bitstream +\return the integer value read. */ u32 gf_bs_get_bit_position(GF_BitStream *bs); /*! - * \brief integer writing - * - * Writes an integer on a given number of bits. - * \param bs the target bitstream - * \param value the integer to write - * \param nBits number of bits used to code the integer +\brief integer writing + +Writes an integer on a given number of bits. +\param bs the target bitstream +\param value the integer to write +\param nBits number of bits used to code the integer */ void gf_bs_write_int(GF_BitStream *bs, s32 value, s32 nBits); /*! - * \brief large integer writing - * - * Writes an integer on a given number of bits greater than 32. - * \param bs the target bitstream - * \param value the large integer to write - * \param nBits number of bits used to code the integer +\brief large integer writing + +Writes an integer on a given number of bits greater than 32. +\param bs the target bitstream +\param value the large integer to write +\param nBits number of bits used to code the integer */ void gf_bs_write_long_int(GF_BitStream *bs, s64 value, s32 nBits); /*! - * \brief float writing - * - * Writes a float in IEEE 32 bits format. - * \param bs the target bitstream - * \param value the float to write +\brief float writing + +Writes a float in IEEE 32 bits format. +\param bs the target bitstream +\param value the float to write */ void gf_bs_write_float(GF_BitStream *bs, Float value); /*! - * \brief double writing - * - * Writes a double in IEEE 64 bits format. - * \param bs the target bitstream - * \param value the double to write +\brief double writing + +Writes a double in IEEE 64 bits format. +\param bs the target bitstream +\param value the double to write */ void gf_bs_write_double(GF_BitStream *bs, Double value); /*! - * \brief data writing - * - * Writes a data buffer. - * \param bs the target bitstream - * \param data the data to write - * \param nbBytes number of data bytes to write +\brief data writing + +Writes a data buffer. +\param bs the target bitstream +\param data the data to write +\param nbBytes number of data bytes to write +\return the number of written bytes */ -u32 gf_bs_write_data(GF_BitStream *bs, const char *data, u32 nbBytes); +u32 gf_bs_write_data(GF_BitStream *bs, const u8 *data, u32 nbBytes); /*! - * \brief align char writing - * - * Writes an integer on 8 bits starting at a byte boundary in the bitstream. - * \warning you must not use this function if the bitstream is not aligned - * \param bs the target bitstream - * \param value the char value to write +\brief align char writing + +Writes an integer on 8 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\param value the char value to write */ void gf_bs_write_u8(GF_BitStream *bs, u32 value); /*! - * \brief align short writing - * - * Writes an integer on 16 bits starting at a byte boundary in the bitstream. - * \warning you must not use this function if the bitstream is not aligned - * \param bs the target bitstream - * \param value the short value to write +\brief align short writing + +Writes an integer on 16 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\param value the short value to write */ void gf_bs_write_u16(GF_BitStream *bs, u32 value); /*! - * \brief align 24-bits integer writing - * - * Writes an integer on 24 bits starting at a byte boundary in the bitstream. - * \warning you must not use this function if the bitstream is not aligned - * \param bs the target bitstream - * \param value the integer value to write +\brief align 24-bits integer writing + +Writes an integer on 24 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\param value the integer value to write */ void gf_bs_write_u24(GF_BitStream *bs, u32 value); /*! - * \brief align integer writing - * - * Writes an integer on 32 bits starting at a byte boundary in the bitstream. - * \warning you must not use this function if the bitstream is not aligned - * \param bs the target bitstream - * \param value the integer value to write +\brief align integer writing + +Writes an integer on 32 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\param value the integer value to write */ void gf_bs_write_u32(GF_BitStream *bs, u32 value); /*! - * \brief align large integer writing - * - * Writes an integer on 64 bits starting at a byte boundary in the bitstream. - * \warning you must not use this function if the bitstream is not aligned - * \param bs the target bitstream - * \param value the large integer value to write +\brief align large integer writing + +Writes an integer on 64 bits starting at a byte boundary in the bitstream. +\warning you must not use this function if the bitstream is not aligned +\param bs the target bitstream +\param value the large integer value to write */ void gf_bs_write_u64(GF_BitStream *bs, u64 value); /*! - * \brief little endian integer writing - * - * Writes an integer on 32 bits in little-endian order. - * \param bs the target bitstream - * \param value the integer value to write +\brief little endian integer writing + +Writes an integer on 32 bits in little-endian order. +\param bs the target bitstream +\param value the integer value to write */ void gf_bs_write_u32_le(GF_BitStream *bs, u32 value); + /*! - * \brief little endian short writing - * - * Writes an integer on 16 bits in little-endian order. - * \param bs the target bitstream - * \param value the short value to write +\brief little endian large integer writing + +Writes an integer on 64 bits in little-endian order. +\param bs the target bitstream +\param value the integer value to write + */ +void gf_bs_write_u64_le(GF_BitStream *bs, u64 value); + +/*! +\brief little endian short writing + +Writes an integer on 16 bits in little-endian order. +\param bs the target bitstream +\param value the short value to write */ void gf_bs_write_u16_le(GF_BitStream *bs, u32 value); /*! - * \brief write byte multiple times - * - * Writes a give byte multiple times. - * \param bs the target bitstream - * \param byte the byte value to write - * \param count the number of times the byte should be written - * \return the number of bytes written +\brief write byte multiple times + +Writes a give byte multiple times. +\param bs the target bitstream +\param byte the byte value to write +\param count the number of times the byte should be written +\return the number of bytes written */ u32 gf_bs_write_byte(GF_BitStream *bs, u8 byte, u32 count); /*! - * \brief end of bitstream management - * - * Assigns a notification callback function for end of stream signaling in read mode - * \param bs the target bitstream - * \param EndOfStream the notification function to use - * \param par opaque user data passed to the bitstream +\brief end of bitstream management + +Assigns a notification callback function for end of stream signaling in read mode +\param bs the target bitstream +\param EndOfStream the notification function to use +\param par opaque user data passed to the bitstream */ void gf_bs_set_eos_callback(GF_BitStream *bs, void (*EndOfStream)(void *par), void *par); /*! - * \brief bitstream alignment - * - * Aligns bitstream to next byte boundary. In write mode, this will write 0 bit values until alignment. - * \param bs the target bitstream - * \return the number of bits read/written until alignment +\brief bitstream alignment checking + +Checks if bitstream position is aligned to a byte boundary. +\param bs the target bitstream +\return GF_TRUE if aligned with regard to the read/write mode, GF_FALSE otherwise + */ +Bool gf_bs_is_align(GF_BitStream *bs); + +/*! +\brief bitstream alignment + +Aligns bitstream to next byte boundary. In write mode, this will write 0 bit values until alignment. +\param bs the target bitstream +\return the number of bits read/written until alignment */ u8 gf_bs_align(GF_BitStream *bs); /*! - * \brief capacity query - * - * Returns the number of bytes still available in the bitstream in read mode. - * \param bs the target bitstream - * \return the number of bytes still available in read mode, -1 in write modes. +\brief capacity query + +Returns the number of bytes still available in the bitstream in read mode. +\param bs the target bitstream +\return the number of bytes still available in read mode, -1 in write modes. */ u64 gf_bs_available(GF_BitStream *bs); /*! - * \brief buffer fetching - * - * Fetches the internal bitstream buffer in write mode. If a buffer was given at the bitstream construction, or if the bitstream is in read mode, this does nothing. - * \param bs the target bitstream - * \param output address of a memory block to be allocated for bitstream data. - * \param outSize set to the size of the allocated memory block. - * \note - * It is the user responsability to destroy the allocated buffer - * Once this function has been called, the internal bitstream buffer is reseted. +\brief buffer fetching + +Fetches the internal bitstream buffer in write mode. If a buffer was given at the bitstream construction, or if the bitstream is in read mode, this does nothing. +\param bs the target bitstream +\param output address of the memory block allocated by the bitstream. +\param outSize size of the allocated memory block. +\note + - It is the user responsability to destroy the allocated buffer + - Once this function has been called, the internal bitstream buffer is reseted. */ -void gf_bs_get_content(GF_BitStream *bs, char **output, u32 *outSize); +void gf_bs_get_content(GF_BitStream *bs, u8 **output, u32 *outSize); + /*! - * \brief byte skipping - * - * Skips bytes in the bitstream. In Write mode, this will write the 0 integer value for memory-based bitstreams or seek the stream - for file-based bitstream. - * \param bs the target bitstream - * \param nbBytes the number of bytes to skip +\brief buffer fetching + +Fetches the internal bitstream buffer in write mode. If a buffer was given at the bitstream construction, or if the bitstream is in read mode, this does nothing. Retrieves both the allocated buffer size and the written size +\param bs the target bitstream +\param output address of the memory block allocated by the bitstream. +\param outSize number of bytes written in the allocated memory block. +\param allocSize size of the allocated memory block. +\note + - It is the user responsability to destroy the allocated buffer + - Once this function has been called, the internal bitstream buffer is reseted. + */ +void gf_bs_get_content_no_truncate(GF_BitStream *bs, u8 **output, u32 *outSize, u32 *allocSize); + +/*! +\brief byte skipping + +Skips bytes in the bitstream. In Write mode, this will write the 0 integer value for memory-based bitstreams or seek the stream + for file-based bitstream. In read mode, emultation prevention byte removal is not applied ! +\param bs the target bitstream +\param nbBytes the number of bytes to skip */ void gf_bs_skip_bytes(GF_BitStream *bs, u64 nbBytes); /*! - *\brief bitstream seeking - * - *Seeks the bitstream to a given offset after the beginning of the stream. This will perform alignment of the bitstream in all modes. - *\warning Results are unpredictable if seeking beyond the bitstream end is performed. - *\param bs the target bitstream - *\param offset buffer/file offset to seek to +\brief bitstream seeking + +Seeks the bitstream to a given offset after the beginning of the stream. This will perform alignment of the bitstream in all modes. +\warning Results are unpredictable if seeking beyond the bitstream end is performed. +\param bs the target bitstream +\param offset buffer/file offset to seek to +\return error if any */ GF_Err gf_bs_seek(GF_BitStream *bs, u64 offset); /*! - *\brief bitstream truncation - * - *Truncates the bitstream at the current position - *\param bs the target bitstream +\brief bitstream truncation + +Truncates the bitstream at the current position +\param bs the target bitstream */ void gf_bs_truncate(GF_BitStream *bs); /*! - *\brief bit peeking - * - *Peeks a given number of bits (read without moving the position indicator) for read modes only. - *\param bs the target bitstream - *\param numBits the number of bits to peek - *\param byte_offset +\brief bit peeking + +Peeks a given number of bits (read without moving the position indicator) for read modes only. +\param bs the target bitstream +\param numBits the number of bits to peek +\param byte_offset * if set, bitstream is aligned and moved from byte_offset before peeking (byte-aligned picking) * otherwise, bitstream is not aligned and bits are peeked from current state - *\return the integer value read +\return the integer value read */ u32 gf_bs_peek_bits(GF_BitStream *bs, u32 numBits, u64 byte_offset); /*! - *\brief bit reservoir query - * - * Queries the number of bits available in read mode. - *\param bs the target bitstream - *\return number of available bits if position is in the last byte of the buffer/stream, 8 otherwise +\brief bit reservoir query + +Queries the number of bits available in read mode. +\param bs the target bitstream +\return number of available bits if position is in the last byte of the buffer/stream, 8 otherwise */ u8 gf_bs_bits_available(GF_BitStream *bs); /*! - *\brief position query - * - *Returns the reading/writting position in the buffer/file. - *\param bs the target bitstream - *\return the read/write position of the bitstream +\brief position query + +Returns the reading/writting position in the buffer/file. +\param bs the target bitstream +\return the read/write position of the bitstream */ u64 gf_bs_get_position(GF_BitStream *bs); /*! - *\brief size query - * - *Returns the size of the associated buffer/file. - *\param bs the target bitstream - *\return the size of the bitstream +\brief size query + +Returns the size of the associated buffer/file. +\param bs the target bitstream +\return the size of the bitstream */ u64 gf_bs_get_size(GF_BitStream *bs); /*! - *\brief file-based size query - * - *Returns the size of a file-based bitstream and force a seek to end of file. This is used in case the file handle - *describes a file being constructed on disk while being read? - * - *\param bs the target bitstream - *\return the disk size of the associated file +\brief file-based size query + +Returns the size of a file-based bitstream and force a seek to end of file. This is used in case the file handle describes a file being constructed on disk while being read? +\param bs the target bitstream +\return the disk size of the associated file */ u64 gf_bs_get_refreshed_size(GF_BitStream *bs); /*! - *\brief transfer content from source bitstream to destination bitstream - * - *Returns the size of the associated buffer/file. - *\param dst the target bitstream - *\param src the source bitstream. This bitstream is empty after calling teh function - *\return error if any +\brief transfer content from source bitstream to destination bitstream + +Returns the size of the associated buffer/file. +\param dst the target bitstream +\param src the source bitstream. +\param keep_src If not set, the source bitstream is empty after calling the function +\return error if any */ -GF_Err gf_bs_transfer(GF_BitStream *dst, GF_BitStream *src); +GF_Err gf_bs_transfer(GF_BitStream *dst, GF_BitStream *src, Bool keep_src); /*! - *\brief Flushes bitstream contet to disk - * - *Flushes bitstream contet to disk - *\param bs the target bitstream +\brief Flushes bitstream content to disk + +Flushes bitstream contet to disk +\param bs the target bitstream */ void gf_bs_flush(GF_BitStream *bs); /*! - *\brief Reassigns FILE object for stream-based bitstreams - * - *Reassigns FILE object for stream-based bitstreams. Automatically sets the stream position to the bitstream position - *\param bs the target bitstream - *\param stream the new stream to assign +\brief AVC&HEVC Annex B mode, only used for read mode + +Enables or disable emulation byte prevention for AVC and HEVC annex B formats in read mode. This does NOT apply to \ref gf_bs_read_data nor \ref gf_bs_skip_bytes +\param bs the target bitstream +\param do_remove if true, emulation prevention bytes will be removed + */ +void gf_bs_enable_emulation_byte_removal(GF_BitStream *bs, Bool do_remove); + +/*! +\brief Inserts a data block, moving bytes to the end + +Inserts a data block at a given position, pushing all bytes after the insertion point to the end of the stream. + This does NOT work if \ref gf_bs_enable_emulation_byte_removal or \ref gf_bs_new_cbk where used. + The position after the call will be the same as before the call. If the position is not the end of the bitstream + all bytes after the position will be lost. +\param bs the target bitstream +\param data block to insert +\param size size of the block to insert +\param offset insertion offset from bitstream start +\return error code if any + */ +GF_Err gf_bs_insert_data(GF_BitStream *bs, u8 *data, u32 size, u64 offset); + +/*! +\brief Sets cookie + +Sets a 64 bit cookie (integer, pointer) on the bitstream, returning the current cookie value +\param bs the target bitstream +\param cookie the new cookie to assign +\return the cookie value before re-assign + */ +u64 gf_bs_set_cookie(GF_BitStream *bs, u64 cookie); + +/*! +\brief Gets cookie + +Gets the current cookie on the bitstream +\param bs the target bitstream +\return the current cookie value */ -void gf_bs_reassign(GF_BitStream *bs, FILE *stream); +u64 gf_bs_get_cookie(GF_BitStream *bs); /*! @} */ diff --git a/include/gpac/cache.h b/include/gpac/cache.h index 8f9d1af..767280b 100644 --- a/include/gpac/cache.h +++ b/include/gpac/cache.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre, Pierre Souchay - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -27,18 +27,17 @@ #define _GF_CACHE_H_ /*! - * \file - * \brief HTTP Cache management. +\file +\brief HTTP Cache management. */ /*! - * \addtogroup dld_grp Downloader - * \ingroup utils_grp - * \brief HTTP Downloader Cache - * - * This section documents the file HTTP caching tools the GPAC framework. - * - * @{ +\addtogroup cache_grp +\brief HTTP Downloader Cache + +This section documents the file HTTP caching tools the GPAC framework. + +@{ */ @@ -54,142 +53,149 @@ extern "C" { */ typedef struct __DownloadedCacheEntryStruct * DownloadedCacheEntry; - +/*! cache object*/ typedef struct __CacheReaderStruct * GF_CacheReader; /** - * Free The DownloadedCacheEntry handle - * \param entry The entry to delete - * \return GF_OK + +Free The DownloadedCacheEntry handle +\param entry The entry to delete +\return GF_OK */ GF_Err gf_cache_delete_entry( const DownloadedCacheEntry entry ); /** * Get the ETag associated with this cache entry if any - * \param entry The entry - * \return The ETag if any was defined, NULL otherwise +\param entry The entry +\return The ETag if any was defined, NULL otherwise */ const char * gf_cache_get_etag_on_server( const DownloadedCacheEntry entry ); /** - * Set the eTag in the cache. Data is duplicated, so original string can be freed by caller. - * \param entry The entry - * \param eTag The eTag to set - * \return GF_OK if entry and eTag are valid, GF_BAD_PARAM otherwise + +Set the eTag in the cache. Data is duplicated, so original string can be freed by caller. +\param entry The entry +\param eTag The eTag to set +\return GF_OK if entry and eTag are valid, GF_BAD_PARAM otherwise */ GF_Err gf_cache_set_etag_on_disk(const DownloadedCacheEntry entry, const char * eTag ); -/** - * Get the ETag associated with this cache entry if any - * \param entry The entry - * \return The ETag if any was defined, NULL otherwise - */ -const char * gf_cache_get_etag_on_disk( const DownloadedCacheEntry entry ); /** - * Set the eTag in the cache. Data is duplicated, so original string can be freed by caller. - * \param entry The entry - * \param eTag The eTag to set - * \return GF_OK if entry and eTag are valid, GF_BAD_PARAM otherwise + +Set the eTag in the cache. Data is duplicated, so original string can be freed by caller. +\param entry The entry +\param eTag The eTag to set +\return GF_OK if entry and eTag are valid, GF_BAD_PARAM otherwise */ GF_Err gf_cache_set_etag_on_server(const DownloadedCacheEntry entry, const char * eTag ); /** - * Get the Mime-Type associated with this cache entry. - * \param entry The entry - * \return The Mime-Type (never NULL if entry is valid) + +Get the Mime-Type associated with this cache entry. +\param entry The entry +\return The Mime-Type (never NULL if entry is valid) */ const char * gf_cache_get_mime_type( const DownloadedCacheEntry entry ); /** - * Set the Mime-Type in the cache. Data is duplicated, so original string can be freed by caller. - * \param entry The entry - * \param mime_type The mime-type to set - * \return GF_OK if entry and mime-type are valid, GF_BAD_PARAM otherwise + +Set the Mime-Type in the cache. Data is duplicated, so original string can be freed by caller. +\param entry The entry +\param mime_type The mime-type to set +\return GF_OK if entry and mime-type are valid, GF_BAD_PARAM otherwise */ GF_Err gf_cache_set_mime_type(const DownloadedCacheEntry entry, const char * mime_type ); /** - * Get the URL associated with this cache entry. - * \param entry The entry - * \return The Hash key (never NULL if entry is valid) + +Get the URL associated with this cache entry. +\param entry The entry +\return The Hash key (never NULL if entry is valid) */ const char * gf_cache_get_url( const DownloadedCacheEntry entry ); /** - * Get the Hash Key associated with this cache entry. - * \param entry The entry - * \return The Hash key (never NULL if entry is valid) - */ -const char * gf_cache_get_hash( const DownloadedCacheEntry entry ); -/** - * Tells whether a cache entry should be cached safely (no - * \param entry The entry - * \return 1 if entry should be cached +Tells whether a cache entry should be cached safely (no +\param entry The entry +\return 1 if entry should be cached */ Bool gf_cache_can_be_cached( const DownloadedCacheEntry entry ); /** - * Get the Last-Modified information associated with this cache entry. - * \param entry The entry - * \return The Last-Modified header (can be NULL) - */ -const char * gf_cache_get_last_modified_on_disk ( const DownloadedCacheEntry entry ); -/** - * Get the Last-Modified information associated with this cache entry. - * \param entry The entry - * \return The Last-Modified header (can be NULL) +Get the Last-Modified information associated with this cache entry. +\param entry The entry +\return The Last-Modified header (can be NULL) */ const char * gf_cache_get_last_modified_on_server ( const DownloadedCacheEntry entry ); /** - * Set the Last-Modified header for this cache entry - * \param entry The entry - * \param newLastModified The new value to set, will be duplicated - * \return GF_OK if everything went alright, GF_BAD_PARAM if entry is NULL + +Set the Last-Modified header for this cache entry +\param entry The entry +\param newLastModified The new value to set, will be duplicated +\return GF_OK if everything went alright, GF_BAD_PARAM if entry is NULL */ GF_Err gf_cache_set_last_modified_on_disk ( const DownloadedCacheEntry entry, const char * newLastModified ); /** - * Set the Last-Modified header for this cache entry - * \param entry The entry - * \param newLastModified The new value to set, will be duplicated - * \return GF_OK if everything went alright, GF_BAD_PARAM if entry is NULL + +Set the Last-Modified header for this cache entry +\param entry The entry +\param newLastModified The new value to set, will be duplicated +\return GF_OK if everything went alright, GF_BAD_PARAM if entry is NULL */ GF_Err gf_cache_set_last_modified_on_server ( const DownloadedCacheEntry entry, const char * newLastModified ); /** - * Get the file name of cache associated with this cache entry. - * \param entry The entry - * \return The Cache file (never NULL if entry is valid) + +Get the file name of cache associated with this cache entry. +\param entry The entry +\return The Cache file (never NULL if entry is valid) */ const char * gf_cache_get_cache_filename( const DownloadedCacheEntry entry ); /** - * Get the real file size of the cache entry - * \param entry The entry - * \return the file size + +Get the real file size of the cache entry +\param entry The entry +\return the file size */ u32 gf_cache_get_cache_filesize( const DownloadedCacheEntry entry ); -/** - * Flushes The disk cache for this entry (by persisting the property file - * \param entry The entry - */ +/*! + +Flushes The disk cache for this entry (by persisting the property file +\param entry The entry +\return error if any +*/ GF_Err gf_cache_flush_disk_cache( const DownloadedCacheEntry entry ); +/*! + +Set content length of resource +\param entry The entry +\param length size of the content in bytes +\return error if any + */ GF_Err gf_cache_set_content_length( const DownloadedCacheEntry entry, u32 length ); +/** + +Get content length of resource +\param entry The entry +\return size of the content in bytes + */ u32 gf_cache_get_content_length( const DownloadedCacheEntry entry); /** - * \brief append cache directives to an HTTP GET request - * \param entry The entry of cache to use - * \param httpRequest The HTTP GET request to populate. The request must have been allocated enough to handle the cache arguments - * \return GF_OK if everything went fine, GF_BAD_PARAM if parameters are wrong +Append cache directives to an HTTP GET request +\param entry The entry of cache to use +\param httpRequest The HTTP GET request to populate. The request must have been allocated enough to handle the cache arguments +\return GF_OK if everything went fine, GF_BAD_PARAM if parameters are wrong */ GF_Err gf_cache_append_http_headers(const DownloadedCacheEntry entry, char * httpRequest); @@ -197,45 +203,74 @@ GF_Err gf_cache_append_http_headers(const DownloadedCacheEntry entry, char * htt * Cache Management functions */ -/*! - * Delete all cached files in given directory starting with startpattern - * \param directory to clean up - * \return GF_OK if everything went fine - */ -GF_Err gf_cache_delete_all_cached_files(const char * directory); - + /*! -/* - * Cache Reader functions +Computes the size of the cache elements in directory +\param directory containing cache files +\return size in bytes */ +u64 gf_cache_get_size(const char * directory); -GF_CacheReader gf_cache_reader_new(const DownloadedCacheEntry entry); - -GF_Err gf_cache_reader_del( GF_CacheReader handle ); - -s64 gf_cache_reader_seek_at( GF_CacheReader reader, u64 seekPosition); - -s64 gf_cache_reader_get_position( const GF_CacheReader reader); - -s64 gf_cache_reader_get_currentSize( GF_CacheReader reader ); +/*! +Delete all cached files in given directory starting with startpattern +\param directory to clean up +\return GF_OK if everything went fine + */ +GF_Err gf_cache_delete_all_cached_files(const char * directory); -s64 gf_cache_reader_get_full_size( GF_CacheReader reader ); -s32 gf_cache_reader_read( GF_CacheReader reader, char * buff, s32 length); +/*! +Check if a given cache entry is corrupted (incomplete) +\param entry The entry +\return GF_TRUE if resource is corrupted + */ Bool gf_cache_check_if_cache_file_is_corrupted(const DownloadedCacheEntry entry); +/*! +Mark associated files as "to be deleted" when the cache entry is removed +\param entry The entry + */ void gf_cache_entry_set_delete_files_when_deleted(const DownloadedCacheEntry entry); +/*! +Check if associated files is marked as "to be deleted" when the cache entry is removed +\param entry The entry +\return GF_TRUE if cache entry is flaged as "to be deleted" + */ Bool gf_cache_entry_is_delete_files_when_deleted(const DownloadedCacheEntry entry); +/*! +Get the number of sessions for a cache entry +\param entry The entry +\return the number of sessions using this cache entry + */ u32 gf_cache_get_sessions_count_for_cache_entry(const DownloadedCacheEntry entry); +/*! +Get the start range of a cache entry +\param entry The entry +\return the start range in bytes + */ u64 gf_cache_get_start_range( const DownloadedCacheEntry entry ); +/*! +Get the end range of a cache entry +\param entry The entry +\return the end range in bytes + */ u64 gf_cache_get_end_range( const DownloadedCacheEntry entry ); - +/*! +Check if the entry is marked as "headers processed" (reply headers have been parsed) +\param entry The entry +\return GF_TRUE if the entry is marked + */ Bool gf_cache_are_headers_processed(const DownloadedCacheEntry entry); +/*! +Mark the entry as "headers processed" +\param entry The entry +\return error if any + */ GF_Err gf_cache_set_headers_processed(const DownloadedCacheEntry entry); /*! @} */ diff --git a/include/gpac/color.h b/include/gpac/color.h index 3c1bdca..13e4c70 100644 --- a/include/gpac/color.h +++ b/include/gpac/color.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / common tools interfaces @@ -37,24 +37,22 @@ extern "C" { /*! - * \file - * \brief Color conversion. - */ +\file +\brief Color conversion. +*/ /*! - *\addtogroup color_grp Color - *\ingroup utils_grp - *\brief Color tools - * - *This section documents color tools for image processing and color conversion - * @{ - */ +\addtogroup color_grp +\brief Color tools + +This section documents color tools for image processing and color conversion +@{ +*/ /*!\brief Video framebuffer object - * - *The video framebuffer object represents uncompressed color data like images in a variety of formats. Data in - *the video framebuffer MUST be continuous. + +The video framebuffer object represents uncompressed color data like images in a variety of formats. Data in the video framebuffer MUST be continuous. */ typedef struct { @@ -71,18 +69,18 @@ typedef struct /*!Pixel format of the video framebuffer*/ u32 pixel_format; /*!pointer to the beginning of the video memory (top-left corner)*/ - char *video_buffer; + u8 *video_buffer; /*!indicates that the video data reside on systems memory or video card one*/ Bool is_hardware_memory; /*!indicates U and V (and optional alpha) buffers in case of planar video with separated component. If not set, all components are in the video_buffer pointer*/ - char *u_ptr, *v_ptr, *a_ptr; + u8 *u_ptr, *v_ptr, *a_ptr; /*! alpha value for this surface*/ u8 global_alpha; } GF_VideoSurface; /*!\brief Video Window object - * - *The video window object represents a rectangle in framebuffer coordinate system + +The video window object represents a rectangle in framebuffer coordinate system */ typedef struct { @@ -98,9 +96,9 @@ typedef struct /*!\brief color matrix object - * - *The Color transformation matrix object allows complete color space transformation (shift, rotate, skew, add).\n - *The matrix coefs are in rgba order, hence the color RGBA is transformed to: + +The Color transformation matrix object allows complete color space transformation (shift, rotate, skew, add).\n + The matrix coefs are in rgba order, hence the color RGBA is transformed to: \code R' m0 m1 m2 m3 m4 R G' m5 m6 m7 m8 m9 G @@ -108,7 +106,7 @@ typedef struct A' m15 m16 m17 m18 m19 A 0 0 0 0 0 1 0 \endcode - *Coeficients are in intensity scale, ranging from 0 to \ref FIX_ONE. + Coeficients are in intensity scale, ranging from 0 to \ref FIX_ONE. */ typedef struct { @@ -119,12 +117,13 @@ typedef struct } GF_ColorMatrix; /*!\brief ARGB color object - * - *The color type used in the GPAC framework represents colors in the form 0xAARRGGBB, with each component ranging from 0 to 255 + +The color type used in the GPAC framework represents colors in the form 0xAARRGGBB, with each component ranging from 0 to 255 */ typedef u32 GF_Color; /*!\hideinitializer color formating macro from alpha, red, green and blue components expressed as integers ranging from 0 to 255*/ -#define GF_COL_ARGB(a, r, g, b) ((a)<<24 | (r)<<16 | (g)<<8 | (b)) +#define GF_COL_ARGB(a, r, g, b) (((u32) a)<<24 | ((u32) r)<<16 | ((u32) g)<<8 | ((u32) b)) + /*!\hideinitializer color formating macro from alpha, red, green and blue components expressed as fixed numbers ranging from 0 to \ref FIX_ONE*/ #define GF_COL_ARGB_FIXED(_a, _r, _g, _b) GF_COL_ARGB(FIX2INT(255*(_a)), FIX2INT(255*(_r)), FIX2INT(255*(_g)), FIX2INT(255*(_b))) /*!\hideinitializer gets alpha component of a color*/ @@ -139,101 +138,124 @@ typedef u32 GF_Color; #define GF_COL_565(r, g, b) (u16) (((r & 248)<<8) + ((g & 252)<<3) + (b>>3)) /*!\hideinitializer 15-bits color formating macro from red, green and blue components*/ #define GF_COL_555(r, g, b) (u16) (((r & 248)<<7) + ((g & 248)<<2) + (b>>3)) -/*!\hideinitializer 15-bits color formating macro from red, green and blue components*/ -#define GF_COL_444(r, g, b) (u16) (((r & 240)<<4) + ((g & 240)) + ((b & 240)>>4)) -/*!\hideinitializer 16-bits alphagrey color formating macro alpha and grey components*/ -#define GF_COL_AG(a, g) (u16) ( (a << 8) | g) -/*!\hideinitializer transfoms a 32-bits color into a 16-bits one.\note alpha component is lost*/ -#define GF_COL_TO_565(c) (((GF_COL_R(c) & 248)<<8) + ((GF_COL_G(c) & 252)<<3) + (GF_COL_B(c)>>3)) -/*!\hideinitializer transfoms a 32-bits color into a 15-bits one.\note alpha component is lost*/ -#define GF_COL_TO_555(c) (((GF_COL_R(c) & 248)<<7) + ((GF_COL_G(c) & 248)<<2) + (GF_COL_B(c)>>3)) -/*!\hideinitializer transfoms a 32-bits color into a 16-bits alphagrey one.\note red component is used for grey, green and blue components are lost.*/ -#define GF_COL_TO_AG(c) ( (GF_COL_A(c) << 8) | GF_COL_R(c)) -/*!\hideinitializer transfoms a 32-bits color into a 15-bits one.\note alpha component is lost*/ -#define GF_COL_TO_444(c) (((GF_COL_R(c) & 240)<<4) + ((GF_COL_G(c) & 240)) + ((GF_COL_B(c)>>4) & 240) ) + +/*!\hideinitializer gets alpha component of a wide color*/ +#define GF_COLW_A(c) (u16) ((c)>>48) +/*!\hideinitializer gets red component of a wide color*/ +#define GF_COLW_R(c) (u16) ( ((c)>>32) & 0xFFFF) +/*!\hideinitializer gets green component of a wide color*/ +#define GF_COLW_G(c) (u16) ( ((c)>>16) & 0xFFFF) +/*!\hideinitializer gets blue component of a wide color*/ +#define GF_COLW_B(c) (u16) ( (c) & 0xFFFF) /*!Parses color from HTML name or hexa representation - *\param name name of the color to parse - *\return GF_Color value with alpha set to 0xFF if successfull, 0 otherwise +\param name name of the color to parse +\return GF_Color value with alpha set to 0xFF if successfull, 0 otherwise */ GF_Color gf_color_parse(const char *name); -/*!Gets color from HTML name or hexa representation - *\param col color to identify - *\return name of the color if successfull, NULL otherwise +/*! Gets color from HTML name or hexa representation +\param col color to identify +\return name of the color if successfull, NULL otherwise */ const char *gf_color_get_name(GF_Color col); -/*!Inits a color matrix to identity*/ -void gf_cmx_init(GF_ColorMatrix *_this); -/*!Inits all coefficients of a color matrix - *\param _this color matrix to initialize - *\param coefs list of the 20 fixed numbers to copy +/*! Enumerates built-in colors +\param idx index of color to query, incremented by one on success +\param color set to color value, can be NULL +\param color_name set to color name, can be NULL +\return GF_TRUE if sucess, GF_FALSE otherwise */ -void gf_cmx_set_all(GF_ColorMatrix *_this, Fixed *coefs); -/*!Inits all coefficients of a color matrix - *\param _this color matrix to initialize - *\param mrr red-to-red multiplication factor - *\param mrg red-to-green multiplication factor - *\param mrb red-to-blue multiplication factor - *\param mra red-to-alpha multiplication factor - *\param tr red translation factor - *\param mgr green-to-red multiplication factor - *\param mgg green-to-green multiplication factor - *\param mgb green-to-blue multiplication factor - *\param mga green-to-alpha multiplication factor - *\param tg green translation factor - *\param mbr blue-to-red multiplication factor - *\param mbg blue-to-green multiplication factor - *\param mbb blue-to-blue multiplication factor - *\param mba blue-to-alpha multiplication factor - *\param tb blue translation factor - *\param mar alpha-to-red multiplication factor - *\param mag alpha-to-green multiplication factor - *\param mab alpha-to-blue multiplication factor - *\param maa alpha-to-alpha multiplication factor - *\param ta alpha translation factor +Bool gf_color_enum(u32 *idx, GF_Color *color, const char **color_name); + +/*! Inits a color matrix to identity +\param _this the target color matrix to initialize*/ +void gf_cmx_init(GF_ColorMatrix *_this); + +/*! Inits all coefficients of a color matrix +\param _this color matrix to initialize +\param mrr red-to-red multiplication factor +\param mrg red-to-green multiplication factor +\param mrb red-to-blue multiplication factor +\param mra red-to-alpha multiplication factor +\param tr red translation factor +\param mgr green-to-red multiplication factor +\param mgg green-to-green multiplication factor +\param mgb green-to-blue multiplication factor +\param mga green-to-alpha multiplication factor +\param tg green translation factor +\param mbr blue-to-red multiplication factor +\param mbg blue-to-green multiplication factor +\param mbb blue-to-blue multiplication factor +\param mba blue-to-alpha multiplication factor +\param tb blue translation factor +\param mar alpha-to-red multiplication factor +\param mag alpha-to-green multiplication factor +\param mab alpha-to-blue multiplication factor +\param maa alpha-to-alpha multiplication factor +\param ta alpha translation factor */ void gf_cmx_set(GF_ColorMatrix *_this, Fixed mrr, Fixed mrg, Fixed mrb, Fixed mra, Fixed tr, Fixed mgr, Fixed mgg, Fixed mgb, Fixed mga, Fixed tg, Fixed mbr, Fixed mbg, Fixed mbb, Fixed mba, Fixed tb, Fixed mar, Fixed mag, Fixed mab, Fixed maa, Fixed ta); -/*!Inits a matrix from another matrix - *\param _this color matrix to initialize - *\param from color matrix to copy from +/*! Inits a matrix from another matrix +\param _this color matrix to initialize +\param from color matrix to copy from */ void gf_cmx_copy(GF_ColorMatrix *_this, GF_ColorMatrix *from); /*!\brief color matrix multiplication - * - *Multiplies a color matrix by another one. Result is _this*with - *\param _this color matrix to transform. Once the function called, _this will contain the resulting color matrix - *\param with color matrix to add + +Multiplies a color matrix by another one. Result is _this*with +\param _this color matrix to transform. Once the function called, _this will contain the resulting color matrix +\param with color matrix to add */ void gf_cmx_multiply(GF_ColorMatrix *_this, GF_ColorMatrix *with); /*!\brief color matrix transform - * - *Transforms a color with a given color matrix - *\param _this color matrix to use. - *\param col color to transform - *\return transformed color + +Transforms a color with a given color matrix +\param _this color matrix to use. +\param col color to transform +\return transformed color */ GF_Color gf_cmx_apply(GF_ColorMatrix *_this, GF_Color col); + +/*!\brief color matrix transform + +Transforms a color with a given color matrix +\param _this color matrix to use. +\param a alpha to transform (in/out) +\param r red to transform (in/out) +\param g green to transform (in/out) +\param b blue to transform (in/out) +*/ +void gf_cmx_apply_argb(GF_ColorMatrix *_this, u8 *a, u8 *r, u8 *g, u8 *b); + +/*!\brief color matrix transform on wide pixel (16 bit per component) + +Transforms a color with a given color matrix +\param _this color matrix to use. +\param col color to transform +\return transformed color +*/ +u64 gf_cmx_apply_wide(GF_ColorMatrix *_this, u64 col); + /*!\brief color components matrix transform - * - *Transforms color components with a given color matrix - *\param _this color matrix to use. - *\param a pointer to alpha component. Once the function is called, a contains the transformed alpha component - *\param r pointer to red component. Once the function is called, r contains the transformed red component - *\param g pointer to green component. Once the function is called, g contains the transformed green component - *\param b pointer to blue component. Once the function is called, b contains the transformed blue component + +Transforms color components with a given color matrix +\param _this color matrix to use. +\param a pointer to alpha component. Once the function is called, a contains the transformed alpha component +\param r pointer to red component. Once the function is called, r contains the transformed red component +\param g pointer to green component. Once the function is called, g contains the transformed green component +\param b pointer to blue component. Once the function is called, b contains the transformed blue component */ void gf_cmx_apply_fixed(GF_ColorMatrix *_this, Fixed *a, Fixed *r, Fixed *g, Fixed *b); /*!\brief Color Key descriptor - * - *The ColorKey object represents a ColorKey with low and high threshold keying + +The ColorKey object represents a ColorKey with low and high threshold keying */ typedef struct { @@ -248,46 +270,23 @@ typedef struct } GF_ColorKey; /*!\brief stretches two video surfaces - * - * Software stretch of source surface ont destination surface. - *\param dst destination surface - *\param src source surface - *\param dst_wnd destination rectangle. If null the entire destination surface is used - *\param src_wnd source rectangle. If null the entire source surface is used - *\param alpha blend factor of source over alpha - *\param flip flips the source - *\param colorKey makes source pixel matching the color key transparent - *\param cmat applies color matrix to the source - *\return error code if any + +Software stretch of source surface onto destination surface. +\param dst destination surface +\param src source surface +\param dst_wnd destination rectangle. If null the entire destination surface is used +\param src_wnd source rectangle. If null the entire source surface is used +\param alpha blend factor of source over alpha +\param flip flips the source +\param colorKey makes source pixel matching the color key transparent +\param cmat applies color matrix to the source +\return error code if any */ GF_Err gf_stretch_bits(GF_VideoSurface *dst, GF_VideoSurface *src, GF_Window *dst_wnd, GF_Window *src_wnd, u8 alpha, Bool flip, GF_ColorKey *colorKey, GF_ColorMatrix * cmat); -/*!\brief copies YUV 420 10 bits to YUV destination (only YUV420 8 bits supported) - * - * Software stretch of source surface ont destination surface. - *\param vs_dst destination surface - *\param pY source Y plane - *\param pU source U plane. if NULL, the U plane is located after the Y plane - *\param pV source V plane. if NULL, the V plane is located after the U plane - *\param src_stride source stride in bytes - *\param src_width source width in pixels - *\param src_height source height in pixels - *\param src_wnd source rectangle. If null the entire source surface is used - *\param swap_uv If GF_TRUE, swaps U and V components. - *\return error code if any - */ -GF_Err gf_color_write_yv12_10_to_yuv(GF_VideoSurface *vs_dst, unsigned char *pY, unsigned char *pU, unsigned char*pV, u32 src_stride, u32 src_width, u32 src_height, const GF_Window *src_wnd, Bool swap_uv); - /*! @} */ - -GF_Err gf_color_write_yuv422_10_to_yuv422(GF_VideoSurface *vs_dst, unsigned char *pY, unsigned char *pU, unsigned char*pV, u32 src_stride, u32 src_width, u32 src_height, const GF_Window *src_wnd, Bool swap_uv); -GF_Err gf_color_write_yuv444_10_to_yuv444(GF_VideoSurface *vs_dst, unsigned char *pY, unsigned char *pU, unsigned char*pV, u32 src_stride, u32 src_width, u32 src_height, const GF_Window *src_wnd, Bool swap_uv); -GF_Err gf_color_write_yuv422_10_to_yuv(GF_VideoSurface *vs_dst, unsigned char *pY, unsigned char *pU, unsigned char*pV, u32 src_stride, u32 src_width, u32 src_height, const GF_Window *src_wnd, Bool swap_uv); -GF_Err gf_color_write_yuv444_10_to_yuv(GF_VideoSurface *vs_dst, unsigned char *pY, unsigned char *pU, unsigned char*pV, u32 src_stride, u32 src_width, u32 src_height, const GF_Window *src_wnd, Bool swap_uv); - - #ifdef __cplusplus } #endif diff --git a/include/gpac/compositor.h b/include/gpac/compositor.h index a0b2d82..552e7c4 100644 --- a/include/gpac/compositor.h +++ b/include/gpac/compositor.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / Scene Compositor sub-project @@ -31,17 +31,20 @@ extern "C" { #endif /*! - * \file - * \brief GPAC A/V/2D/3D compositor/rendering. - */ +\file +\brief GPAC A/V/2D/3D compositor/rendering. +*/ /*! - *\addtogroup compose_grp Compositor - *\ingroup playback_grp - *\brief GPAC A/V/2D/3D compositor/rendering. - * - *This section documents the compositor of GPAC? in charge of assembling audio, images, video, text, 2D and 3D graphics with in a timed way - * @{ +\addtogroup compose_grp Compositor +\ingroup playback_grp +\brief GPAC A/V/2D/3D compositor/rendering. + +This section documents the compositor of GPAC in charge of assembling audio, images, video, text, 2D and 3D graphics + in a timed fashion. The compositor can only be run as a filter starting from GPAC 0.9.0, as it requires the filters API + to fetch media data. + The compositor can work in real-time mode (player) or as a regular filter. See `gpac -h compositor`for more information. +@{ */ @@ -52,136 +55,222 @@ extern "C" { /*frame buffer definition*/ #include +/*! Compositor object*/ typedef struct __tag_compositor GF_Compositor; -/*creates default compositor -if self_threaded, video compositor uses a dedicated thread, otherwise visual rendering is done by the user -audio compositor always runs in its own thread if enabled -term may be NULL, in which case InputSensors won't be enabled +/*! loads a compositor object +\param compositor a preallocated structure for the compositor to initialize +\return error if any */ -GF_Compositor *gf_sc_new(GF_User *user_interface, Bool self_threaded, GF_Terminal *term); -void gf_sc_del(GF_Compositor *sr); - -/*sets simulation frame rate*/ -void gf_sc_set_fps(GF_Compositor *sr, Double fps); - -/*set the root scene graph of the compositor - if NULL remove current and reset simulation time*/ -GF_Err gf_sc_set_scene(GF_Compositor *sr, GF_SceneGraph *scene_graph); +GF_Err gf_sc_load(GF_Compositor *compositor); +/*! unloads compositor +\param compositor the compositor object to unload. The structure memory is not freed +*/ +void gf_sc_unload(GF_Compositor *compositor); -/*if the compositor doesn't use its own thread for visual, this will perform a render pass -return 1 if there are pending tasks (frame late, fonts pending, etc) or 0 if everything was ready while drawing the frame*/ -Bool gf_sc_draw_frame(GF_Compositor *sr, Bool no_video_flush, s32 *ms_till_next); +/*! sets simulation frame rate. The compositor framerate impacts the frequency at which time nodes and animations are updated, +but does not impact the video objects frame rates. +\param compositor the target compositor +\param fps the desired frame rate +*/ +void gf_sc_set_fps(GF_Compositor *compositor, GF_Fraction fps); -/*flushes the video to screen - typically used after @gf_sc_draw_frame without flush*/ -void gf_sc_flush_video(GF_Compositor *compositor); +/*! sets the root scene graph of the compositor. +\param compositor the target compositor +\param scene_graph the scene graph to attach. If NULL, removes current scene and resets simulation time +\return error if any +*/ +GF_Err gf_sc_set_scene(GF_Compositor *compositor, GF_SceneGraph *scene_graph); -/*inits rendering info for the node - shall be called for all nodes the parent system doesn't handle*/ -void gf_sc_on_node_init(GF_Compositor *sr, GF_Node *node); +/*! draws a single frame. If the frame is drawn, a packet is sent on the compositor vout output pi +\param compositor the target compositor +\param no_video_flush disables video frame flushing to graphics card. Ignored in non-player mode +\param ms_till_next set to the number of milliseconds until next expected frame +\return GF_TRUE if there are pending tasks (frame late, fonts pending, etc) or GF_FALSE if everything was ready while drawing the frame +*/ +Bool gf_sc_draw_frame(GF_Compositor *compositor, Bool no_video_flush, s32 *ms_till_next); -/*notify the given node has been modified. The compositor filters object to decide whether the scene graph has to be -traversed or not- if object is NULL, this means complete traversing of the graph is requested (use carefully since it -can be a time consuming operation)*/ -void gf_sc_invalidate(GF_Compositor *sr, GF_Node *byObj); +/*! notify the given node has been modified. The compositor filters object to decide whether the scene graph has to be +traversed or not. +\param compositor the target compositor +\param node the node to invalidate. If NULL, this means complete traversing of the graph is requested +*/ +void gf_sc_invalidate(GF_Compositor *compositor, GF_Node *node); -/*return the compositor time - this is the time every time line syncs on*/ -u32 gf_sc_get_clock(GF_Compositor *sr); +/*! returns the compositor time. The compositor time is the time every time line is synchronized to +\param compositor the target compositor +\return compositor time in milliseconds +*/ +u32 gf_sc_get_clock(GF_Compositor *compositor); -//signals the node is about to be destroyed (called after the node destructor if any). If node is NULL, SG will be set to indicate the entire scene graph is about to be reset +/*! signals the node or scenegraph is about to be destroyed. This should be called after the node destructor if any. +This function cleans up any pending events on the target node or graph. +\param compositor the target compositor +\param node the target node. If NULL, sg shall be set to the scenegraph about to be destroyed +\param sg the target scenegraph +*/ void gf_sc_node_destroy(GF_Compositor *compositor, GF_Node *node, GF_SceneGraph *sg); -/*locks/unlocks the visual scene rendering - modification of the scene tree shall only happen when scene compositor is locked*/ -void gf_sc_lock(GF_Compositor *sr, Bool doLock); -/*locks/unlocks the audio scene rendering - this is needed whenever an audio object changes config on the fly*/ -void gf_sc_lock_audio(GF_Compositor *sr, Bool doLock); - -/*notify user input - returns 0 if event hasn't been handled by the compositor*/ -Bool gf_sc_user_event(GF_Compositor *sr, GF_Event *event); - -/*maps screen coordinates to bifs 2D coordinates for the current zoom/pan settings -X and Y are point coordinates in the display expressed in BIFS-like fashion (0,0) at center of -display and Y increasing from bottom to top*/ -void gf_sc_map_point(GF_Compositor *sr, s32 X, s32 Y, Fixed *bifsX, Fixed *bifsY); - -/*signal the size of the display area has been changed*/ -GF_Err gf_sc_size_changed(GF_Compositor *sr, u32 NewWidth, u32 NewHeight); +/*! locks/unlocks the visual scene rendering. Modifications of the scene tree shall only happen when scene compositor is locked +\param compositor the target compositor +\param do_lock indicates if the compositor should be locked (GF_TRUE) or released (GF_FALSE) +*/ +void gf_sc_lock(GF_Compositor *compositor, Bool do_lock); + +/*! notify user input +\param compositor the target compositor +\param event the target event to notify +\return GF_FALSE if event hasn't been handled by the compositor, GF_TRUE otherwise*/ +Bool gf_sc_user_event(GF_Compositor *compositor, GF_Event *event); + +/*! maps screen coordinates to bifs 2D coordinates for the current zoom/pan settings. The input coordinate X and Y are + point coordinates in the display expressed in BIFS-like fashion (0,0) at center of display and Y increasing from bottom to top + +\param compositor the target compositor +\param X horizontal point coordinate +\param Y vertical point coordinate +\param bifsX set to the scene horizontal coordinate of the point +\param bifsY set to the scene vertical coordinate of the point +*/ +void gf_sc_map_point(GF_Compositor *compositor, s32 X, s32 Y, Fixed *bifsX, Fixed *bifsY); -/*set/get user options - options are as defined in user.h*/ -GF_Err gf_sc_set_option(GF_Compositor *sr, u32 type, u32 value); -u32 gf_sc_get_option(GF_Compositor *sr, u32 type); +/*! sets user options. Options are as defined in user.h +\param compositor the target compositor +\param type the target option type +\param value the target option value +\return error if any +*/ +GF_Err gf_sc_set_option(GF_Compositor *compositor, u32 type, u32 value); +/*! gets user options. Options are as defined in user.h +\param compositor the target compositor +\param type the target option type +\return the option value +*/ +u32 gf_sc_get_option(GF_Compositor *compositor, u32 type); -/*returns current FPS -if @absoluteFPS is set, the return value is the absolute framerate, eg NbFrameCount/NbTimeSpent regardless of -whether a frame has been drawn or not, which means the FPS returned can be much greater than the compositor FPS -if @absoluteFPS is not set, the return value is the FPS taking into account not drawn frames (eg, less than or equal to -compositor FPS) +/*! returns current FPS +\param compositor the target compositor +\param absoluteFPS if set to GF_TRUE, the return value is the absolute framerate, eg NbFrameCount/NbTimeSpent regardless of +whether a frame has been drawn or not, which means the FPS returned can be much greater than the compositor FPS. If set to GF_FALSE, the return value is the FPS taking into account not drawn frames (eg, less than or equal to compositor FPS) +\return the current frame rate */ -Double gf_sc_get_fps(GF_Compositor *sr, Bool absoluteFPS); +Double gf_sc_get_fps(GF_Compositor *compositor, Bool absoluteFPS); +/*! checks if a text selection is in progress +\param compositor the target compositor +\return GF_TRUE is some text is selected, GF_FALSE otherwise +*/ Bool gf_sc_has_text_selection(GF_Compositor *compositor); + +/*! gets text selection +\param compositor the target compositor +\return the selected text as UTF8 string +*/ const char *gf_sc_get_selected_text(GF_Compositor *compositor); +/*! replace text selection content +\param compositor the target compositor +\param text the text to paste as UTF8 string +\return error if any +*/ GF_Err gf_sc_paste_text(GF_Compositor *compositor, const char *text); -/*user-define management: this is used for instant visual rendering of the scene graph, -for exporting or authoring tools preview. User is responsible for calling render when desired and shall also maintain -scene timing*/ - -/*force render tick*/ -void gf_sc_render(GF_Compositor *sr); -/*gets screen buffer - this locks the scene graph too until released is called*/ -GF_Err gf_sc_get_screen_buffer(GF_Compositor *sr, GF_VideoSurface *framebuffer, u32 depth_buffer_mode); -/*gets offscreen buffer - this locks the scene graph too until released is called*/ -GF_Err gf_sc_get_offscreen_buffer(GF_Compositor *sr, GF_VideoSurface *framebuffer, u32 view_idx, u32 depth_buffer_mode); -/*releases screen buffer and unlocks graph*/ -GF_Err gf_sc_release_screen_buffer(GF_Compositor *sr, GF_VideoSurface *framebuffer); - -/*renders one frame*/ -void gf_sc_render_frame(GF_Compositor *sr); - -/*forces graphics cache recompute*/ -void gf_sc_reset_graphics(GF_Compositor *sr); - -/*picks a node (may return NULL) - coords are given in OS client system coordinate, as in UserInput*/ -GF_Node *gf_sc_pick_node(GF_Compositor *sr, s32 X, s32 Y); - -/*get viewpoints/viewports for main scene - idx is 1-based, and if greater than number of viewpoints return GF_EOS*/ -GF_Err gf_sc_get_viewpoint(GF_Compositor *sr, u32 viewpoint_idx, const char **outName, Bool *is_bound); -/*set viewpoints/viewports for main scene given its name - idx is 1-based, or 0 to retrieve by viewpoint name -if only one viewpoint is present in the scene, this will bind/unbind it*/ -GF_Err gf_sc_set_viewpoint(GF_Compositor *sr, u32 viewpoint_idx, const char *viewpoint_name); - -/*render subscene root node. rs is the current traverse stack -this is needed to handle graph metrics changes between scenes...*/ -void gf_sc_traverse_subscene(GF_Compositor *sr, GF_Node *inline_parent, GF_SceneGraph *subscene, void *rs); - -/*set outupt size*/ -GF_Err gf_sc_set_size(GF_Compositor *sr, u32 NewWidth, u32 NewHeight); -/*get outupt size*/ -Bool gf_sc_get_size(GF_Compositor *sr, u32 *Width, u32 *Height); +/*! gets screen buffer. This locks the scene graph too until \ref gf_sc_get_offscreen_buffer is called +\param compositor the target compositor +\param framebuffer will be set to the grabbed framebuffer. The pixel data is owned by the compositor and shall not be freed +\param depth_grab_mode mode for depth grabbing in 3D +\return error if any +*/ +GF_Err gf_sc_get_screen_buffer(GF_Compositor *compositor, GF_VideoSurface *framebuffer, GF_CompositorGrabMode depth_grab_mode); + +/*! gets offscreen buffer in autostereo rendering modes. This locks the scene graph too until \ref gf_sc_get_offscreen_buffer is called +\param compositor the target compositor +\param framebuffer will be set to the grabbed framebuffer. The pixel data is owned by the compositor and shall not be freed +\param view_idx indicates the 0-based index of the view to grab +\param depth_grab_mode mode for depth grabbing in 3D +\return error if any +*/ +GF_Err gf_sc_get_offscreen_buffer(GF_Compositor *compositor, GF_VideoSurface *framebuffer, u32 view_idx, GF_CompositorGrabMode depth_grab_mode); -/*returns total length of audio hardware buffer in ms, 0 if no audio*/ -u32 gf_sc_get_audio_buffer_length(GF_Compositor *sr); +/*! releases screen buffer and unlocks graph +\param compositor the target compositor +\param framebuffer used during grab call +\return error if any +*/ +GF_Err gf_sc_release_screen_buffer(GF_Compositor *compositor, GF_VideoSurface *framebuffer); -/*add/remove extra scene from compositor (extra scenes are OSDs or any other scene graphs not directly -usable by main scene, like 3GP text streams*/ -void gf_sc_register_extra_graph(GF_Compositor *sr, GF_SceneGraph *extra_scene, Bool do_remove); +/*! forces full graphics reset (deletes GL textures, FBO, 2D offscreen caches, etc ...). +\param compositor the target compositor +*/ +void gf_sc_reset_graphics(GF_Compositor *compositor); + +/*! gets viewpoints/viewports for main scene - idx is 1-based, and if greater than number of viewpoints return GF_EOS +\param compositor the target compositor +\param viewpoint_idx the index of the requested viewport. This is a 1-based index, and if greater than number of viewpoints the function will return GF_EOS +\param out_name set to the viewport name. May be NULL +\param is_bound set to GF_TRUE if the viewport is bound. May be NULL +\return GF_EOS if no more viewpoint, or error if any +*/ +GF_Err gf_sc_get_viewpoint(GF_Compositor *compositor, u32 viewpoint_idx, const char **out_name, Bool *is_bound); + +/*! sets viewpoints/viewports for main scene given its name - idx is 1-based, or 0 to retrieve by viewpoint name +if only one viewpoint is present in the scene, this will bind/unbind it +\param compositor the target compositor +\param viewpoint_idx the index of the viewport to bind. This is a 1-based index, and if greater than number of viewpoints the function will return GF_EOS. Use 0 to bind the viewpoint with the given name. +\param viewpoint_name name of the viewpoint to bind if viewpoint_idx is 0 +\return error if any +*/ +GF_Err gf_sc_set_viewpoint(GF_Compositor *compositor, u32 viewpoint_idx, const char *viewpoint_name); -/*gets audio hardware delay*/ -u32 gf_sc_get_audio_delay(GF_Compositor *sr); +/*! renders subscene root node. rs is the current traverse stack +\param compositor the target compositor +\param inline_parent the parent node of the subscene (VRML Inline, SVG animation, ...) +\param subscene the subscene tree to render +\param rs the rendering state at the parent level. This is needed to handle graph metrics changes between scenes +*/ +void gf_sc_traverse_subscene(GF_Compositor *compositor, GF_Node *inline_parent, GF_SceneGraph *subscene, void *rs); -/*returns total length of audio hardware buffer in ms, 0 if no audio*/ -u32 gf_sc_get_audio_buffer_length(GF_Compositor *sr); +/*! sets output (display) size +\param compositor the target compositor +\param new_width the desired new output width +\param new_height the desired new output height +\return error if any +*/ +GF_Err gf_sc_set_size(GF_Compositor *compositor, u32 new_width, u32 new_height); -void *gf_sc_get_visual_compositor(GF_Compositor *sr); +/*! adds or removes extra scene from compositor. Extra scenes are on-screen displays, text tracks or any other scene graphs +not directly loaded by the main scene +\param compositor the target compositor +\param extra_scene the scene graph to add/remove +\param do_remove if set to GF_TRUE, the given scene graph will be unregistered; otherwise, the given scene graph will be registered +*/ +void gf_sc_register_extra_graph(GF_Compositor *compositor, GF_SceneGraph *extra_scene, Bool do_remove); +/*! retrieves the compositor object associated with a node. Currently GPAC only handles one possible compositor per node. +\param node the target node +\return the compositor used by the node +*/ GF_Compositor *gf_sc_get_compositor(GF_Node *node); -Bool gf_sc_script_action(GF_Compositor *sr, u32 type, GF_Node *n, GF_JSAPIParam *param); - -void gf_sc_reload_audio_filters(GF_Compositor *compositor); +/*! executes a script action +\param compositor the target compositor +\param type the script action type +\param node the optional node on which the action takes place +\param param the parameter for this action +\return GF_TRUE if success +*/ +Bool gf_sc_script_action(GF_Compositor *compositor, GF_JSAPIActionType type, GF_Node *node, GF_JSAPIParam *param); +/*! checks if the given URI matches a built-in GPAC VRML Prototype node +\param compositor the target compositor +\param uri the URI to test +\return GF_TRUE if the URI indicates a built-in prototype, GF_FALSE otherwise +*/ Bool gf_sc_uri_is_hardcoded_proto(GF_Compositor *compositor, const char *uri); +/*! reloads the compositor configuration from the GPAC config file +\param compositor the target compositor +*/ void gf_sc_reload_config(GF_Compositor *compositor); /*! @} */ diff --git a/include/gpac/config_file.h b/include/gpac/config_file.h index 1495da8..1f4c8c0 100644 --- a/include/gpac/config_file.h +++ b/include/gpac/config_file.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -31,196 +31,163 @@ extern "C" { #endif /*! - * \file - * \brief configuration file. +\file +\brief configuration file. */ /*! -* \addtogroup cfg_grp Configuration File -* \ingroup utils_grp -* \brief Configuration File object -* -* This section documents the configuration file object of the GPAC framework. -* This file is formatted as the INI file mode of WIN32 in sections and keys.\n -*\note For more information on the GPAC configuration file itself, please refer to the GPAC configuration help provided with GPAC. -* @{ +\addtogroup cfg_grp +\brief Configuration File object + +This section documents the configuration file objects of the GPAC framework. +A specific global configuration file is used for libgpac, see \ref libsys_grp. +Such objects may be used by third-party applications to store data, and are used by the player scripting API to store +various GUI data. +A configuration file is formatted as the INI file mode of WIN32 in sections and keys. +\code +[Section1] +Key1=foo +Key2=bar + +[Section2] +Key1=@multiline +for +some +reason@ +Key3=2 + +\endcode +@{ */ #include - +/*! configuration file object*/ typedef struct __tag_config GF_Config; /*! - * \brief configuration file initialization - * - * Constructs a configuration file from fileName. If fileName is NULL, the default GPAC configuration file is loaded with the - * proper module directory, font directory and other default options. If fileName is non-NULL no configuration file is found, - * a "light" default configuration file is created. - *\param file name of the configuration file, or NULL for default file - *\param new_cfg Boolean set to true if a new configuration file has been created - *\return the configuration file object, NULL if the file could not be created - */ -GF_Config *gf_cfg_init(const char *file, Bool *new_cfg); +\brief configuration file constructor -/*! - * \brief configuration file constructor - * - * Constructs a configuration file. - *\param filePath directory the file is located in - *\param fileName name of the configuration file - *\return the configuration file object, NULL if the file does not exist +Constructs a configuration file. +\param filePath directory the file is located in +\param fileName name of the configuration file +\return the configuration file object, NULL if the file does not exist */ GF_Config *gf_cfg_new(const char *filePath, const char *fileName); /*! - * \brief alternative configuration file constructor - * - * Constructs a configuration file. If file does not exist, configuration will be still created - *\param filePath directory the file is located in - *\param fileName name of the configuration file - *\return the configuration file object, never NULL, even if file does not exist +\brief alternative configuration file constructor + +Constructs a configuration file. If file does not exist, configuration will be still created +\param filePath directory the file is located in +\param fileName name of the configuration file +\return the configuration file object, never NULL, even if file does not exist */ GF_Config *gf_cfg_force_new(const char *filePath, const char *fileName); /*! - * \brief configuration file destructor - * - *Destroys the configuration file and saves it if needed. - *\param cfgFile the target configuration file +\brief configuration file destructor + +Destroys the configuration file and saves it if needed. +\param cfgFile the target configuration file */ void gf_cfg_del(GF_Config *cfgFile); /*! - * \brief configuration file destructor - * - *Destroys the configuration file and removes the file from disk. - *\param cfgFile the target configuration file +\brief configuration file destructor + +Destroys the configuration file and removes the file from disk. +\param cfgFile the target configuration file */ void gf_cfg_remove(GF_Config *cfgFile); /*! - * \brief configuration saving - * - *Saves the configuration file if modified. - *\param cfgFile the target configuration file +\brief configuration saving + +Saves the configuration file if modified. +\param cfgFile the target configuration file +\return error if any */ GF_Err gf_cfg_save(GF_Config *cfgFile); /*! - * \brief key value query - * - *Gets a key value from its section and name. - *\param cfgFile the target configuration file - *\param secName the desired key parent section name - *\param keyName the desired key name - *\return the desired key value if found, NULL otherwise. +\brief key value query + +Gets a key value from its section and name. +\param cfgFile the target configuration file +\param secName the desired key parent section name +\param keyName the desired key name +\return the desired key value if found, NULL otherwise. */ const char *gf_cfg_get_key(GF_Config *cfgFile, const char *secName, const char *keyName); + /*! - * \brief key value query ignoring case - * - *Gets a key value from its section and name. Comparison is performed while ignoring case. - *\param cfgFile the target configuration file - *\param secName the desired key parent section name (case ignored) - *\param keyName the desired key name (case ignored) - *\return the desired key value if found, NULL otherwise. - */ -const char *gf_cfg_get_ikey(GF_Config *cfgFile, const char *secName, const char *keyName); -/*! - * \brief key value update - * - *Sets a key value from its section and name. - *\param cfgFile the target configuration file - *\param secName the desired key parent section name - *\param keyName the desired key name - *\param keyValue the desired key value - *\note this will also create both section and key if they are not found in the configuration file +\brief key value update + +Sets a key value from its section and name. +\param cfgFile the target configuration file +\param secName the desired key parent section name +\param keyName the desired key name +\param keyValue the desired key value +\note this will also create both section and key if they are not found in the configuration file +\return error if any */ GF_Err gf_cfg_set_key(GF_Config *cfgFile, const char *secName, const char *keyName, const char *keyValue); /*! - * \brief section count query - * - *Gets the number of sections in the configuration file - *\param cfgFile the target configuration file - *\return the number of sections +\brief section count query + +Gets the number of sections in the configuration file +\param cfgFile the target configuration file +\return the number of sections */ u32 gf_cfg_get_section_count(GF_Config *cfgFile); /*! - * \brief section name query - * - *Gets a section name based on its index - *\param cfgFile the target configuration file - *\param secIndex 0-based index of the section to query - *\return the section name if found, NULL otherwise +\brief section name query + + Gets a section name based on its index +\param cfgFile the target configuration file +\param secIndex 0-based index of the section to query +\return the section name if found, NULL otherwise */ const char *gf_cfg_get_section_name(GF_Config *cfgFile, u32 secIndex); /*! - * \brief key count query - * - *Gets the number of keys in a section of the configuration file - *\param cfgFile the target configuration file - *\param secName the target section - *\return the number of keys in the section +\brief key count query + +Gets the number of keys in a section of the configuration file +\param cfgFile the target configuration file +\param secName the target section +\return the number of keys in the section */ u32 gf_cfg_get_key_count(GF_Config *cfgFile, const char *secName); /*! - * \brief key count query - * - *Gets the number of keys in a section of the configuration file - *\param cfgFile the target configuration file - *\param secName the target section - *\param keyIndex 0-based index of the key in the section - *\return the key name if found, NULL otherwise +\brief key count query + +Gets the number of keys in a section of the configuration file +\param cfgFile the target configuration file +\param secName the target section +\param keyIndex 0-based index of the key in the section +\return the key name if found, NULL otherwise */ const char *gf_cfg_get_key_name(GF_Config *cfgFile, const char *secName, u32 keyIndex); /*! - * \brief key insertion - * - *Inserts a new key in a given section. Returns an error if a key with the given name - *already exists in the section - *\param cfgFile the target configuration file - *\param secName the target section - *\param keyName the name of the target key - *\param keyValue the value for the new key - *\param index the 0-based index position of the new key - */ -GF_Err gf_cfg_insert_key(GF_Config *cfgFile, const char *secName, const char *keyName, const char *keyValue, u32 index); +\brief section destrouction -/*! - * \brief section destrouction - * - *Removes all entries in the given section - *\param cfgFile the target configuration file - *\param secName the target section +Removes all entries in the given section +\param cfgFile the target configuration file +\param secName the target section */ void gf_cfg_del_section(GF_Config *cfgFile, const char *secName); /*! - * get a sub key (separator is ':') in a given key in a given section. Returns an error if the key does not exist - *\param cfgFile the target configuration file - *\param secName the target section - *\param keyName the name of the target key - *\param sub_index the 0-based index position of the sub key*/ -const char *gf_cfg_get_sub_key(GF_Config *cfgFile, const char *secName, const char *keyName,u32 sub_index); -/*! - * Get the full filename associated with this config file - * The caller is responsible for freeing memory - * \param iniFile The Configuration - * \return a newly allocated filename +Get the full filename associated with this config file +\param iniFile The Configuration +\return the associated filename */ -char * gf_cfg_get_filename(GF_Config *iniFile); - -/*! - * Set the full filename associated with this config file - * \param iniFile The Configuration - * \param fileName new filename for the config - * \return erroro code - */ -GF_Err gf_cfg_set_filename(GF_Config *iniFile, const char * fileName); +const char * gf_cfg_get_filename(GF_Config *iniFile); /*! - * Do not save results to file - * \param iniFile The Configuration - * \return error code + +Do not save results to file +\param iniFile The Configuration +\return error code */ GF_Err gf_cfg_discard_changes(GF_Config *iniFile); diff --git a/include/gpac/configuration.h b/include/gpac/configuration.h index ad70bb8..181e751 100644 --- a/include/gpac/configuration.h +++ b/include/gpac/configuration.h @@ -27,8 +27,19 @@ #ifndef _GF_CONFIG_H_ #define _GF_CONFIG_H_ +/*! +\addtogroup setup_grp +\brief Base data types + +This section documents the base data types of GPAC. + +@{ +*/ + #define GPAC_CONFIGURATION "(static configuration file)" + + /*this file defines all common macros for libgpac compilation except for symbian32 which uses .mmp directives ... */ @@ -39,9 +50,21 @@ #define GPAC_HAS_SSL -#define GPAC_HAS_SPIDERMONKEY +#define GPAC_HAS_QJS +//codecs #define GPAC_HAS_JPEG #define GPAC_HAS_PNG +#define GPAC_HAS_LIBA52 +#define GPAC_HAS_FAAD +#define GPAC_HAS_JP2 +#define GPAC_HAS_MAD +#define GPAC_HAS_OPENHEVC +#define GPAC_HAS_OPENSVC +#define GPAC_HAS_THEORA +#define GPAC_HAS_VORBIS +#define GPAC_HAS_XVID +#define GPAC_HAS_FFMPEG +#define GPAC_HAS_DTAPI /*IPv6 enabled - for win32, this is evaluated at compile time, !! do not uncomment !!*/ @@ -52,6 +75,11 @@ #define GPAC_HAS_GLU +#ifndef GPAC_CONFIG_WIN32 +#define GPAC_CONFIG_WIN32 +#endif //GPAC_CONFIG_WIN32 + + #endif /*GPAC_MP4BOX_MINI*/ /*Configuration for WindowsCE 32 bits */ @@ -75,7 +103,7 @@ #endif -#define GPAC_HAS_SPIDERMONKEY +#define GPAC_HAS_QJS #define GPAC_HAS_JPEG #define GPAC_HAS_PNG @@ -86,47 +114,48 @@ /*Configuration for Android */ #elif defined(GPAC_CONFIG_ANDROID) -#ifndef GPAC_ANDROID -#define GPAC_ANDROID -#endif - #define GPAC_HAS_IPV6 #define GPAC_USE_GLES1X /*don't use fixed-point version on Android, not needed*/ //#define GPAC_FIXED_POINT -#define GPAC_HAS_SPIDERMONKEY +#define GPAC_HAS_QJS #define GPAC_HAS_JPEG #define GPAC_HAS_PNG /*Configuration for XCode OSX (not iOS) */ -#elif defined(GPAC_CONFIG_DARWIN) && !defined(GPAC_IPHONE) +#elif defined(GPAC_CONFIG_DARWIN) && !defined(GPAC_CONFIG_IOS) #define GPAC_HAS_IPV6 #define GPAC_HAS_SSL +#define GPAC_HAS_SOCK_UN //64-bits OSX #ifdef __LP64__ +/*! macro defined for 64-bits platforms*/ #define GPAC_64_BITS #endif -#define GPAC_HAS_SPIDERMONKEY +#define GPAC_HAS_QJS #define GPAC_HAS_JPEG #define GPAC_HAS_PNG #define GPAC_HAS_GLU #define GPAC_MEMORY_TRACKING + /*Configuration for XCode iOS*/ -#elif defined(GPAC_CONFIG_DARWIN) && defined(GPAC_IPHONE) +#elif defined(GPAC_CONFIG_DARWIN) && defined(GPAC_CONFIG_IOS) //64-bits iOS #ifdef __LP64__ +/*! macro defined for 64-bits platforms*/ #define GPAC_64_BITS #endif -#define GPAC_HAS_SPIDERMONKEY +#define GPAC_HAS_QJS #define GPAC_HAS_JPEG #define GPAC_HAS_PNG +#define GPAC_HAS_SOCK_UN /*don't use fixed-point version on iOS, not needed*/ //#define GPAC_FIXED_POINT @@ -146,7 +175,7 @@ #define GPAC_HAS_IPV6 #define GPAC_HAS_SSL - +#define GPAC_DISABLE_OGG /*Configuration for Symbian*/ #elif defined(__SYMBIAN32__) @@ -155,7 +184,7 @@ #define GPAC_FIXED_POINT #endif -#define GPAC_HAS_SPIDERMONKEY +#define GPAC_HAS_QJS #define GPAC_HAS_JPEG #define GPAC_HAS_PNG @@ -189,7 +218,8 @@ /*disables X3D scene graphs*/ //#define GPAC_DISABLE_X3D -/*disables MPEG-4 OD Framework - this only minimalize the set of OD features used, however all cannot be removed*/ +/*disables MPEG-4 OD Framework - this only minimalize the set of OD features used, however all cannot be removed +this macro is currently defined in setup.h */ //#define GPAC_MINIMAL_ODF /*disables BIFS coding*/ @@ -199,7 +229,7 @@ //#define GPAC_DISABLE_LASER //#define GPAC_DISABLE_SAF -/*disables BIFS Engine support - TODO - merge DIMS and LASeR into BENG and rename it*/ +/*disables BIFS Engine support*/ //#define GPAC_DISABLE_SENG /*disables Cubic QTVR importing*/ @@ -247,8 +277,8 @@ /*disables XMTA/X3D -> scene decoder*/ //#define GPAC_DISABLE_LOADER_XMT -/*disables mcrypt*/ -//#define GPAC_DISABLE_MCRYPT +/*disables crypto tools*/ +//#define GPAC_DISABLE_CRYPTO /*disables all ISO FF*/ //#define GPAC_DISABLE_ISOM @@ -295,9 +325,14 @@ /*disables HEVC */ //#define GPAC_DISABLE_HEVC +/*disables AOM AV1 */ +//#define GPAC_DISABLE_AV1 + /*disables VOBSUB */ //#define GPAC_DISABLE_VOBSUB +/*! @} */ + #endif /*_GF_CONFIG_H_*/ diff --git a/include/gpac/constants.h b/include/gpac/constants.h index eb7c4aa..bc0522e 100644 --- a/include/gpac/constants.h +++ b/include/gpac/constants.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / exported constants @@ -30,46 +30,30 @@ extern "C" { #endif -/*! - * \file - * \brief Most constants defined in GPAC are in this file. - */ +#include +/*! +\file +\brief Most constants defined in GPAC are in this file. +\addtogroup cst_grp +\brief Constants -/*! \addtogroup cst_grp Constants - * \brief Constants used within GPAC - * - * This section documents some constants used in the GPAC framework which are not related to - * any specific sub-project. - * \ingroup utils_grp - * @{ - */ +This section documents some constants used in the GPAC framework which are not related to any specific sub-project. -/*! - * \brief Supported file types - * \hideinitializer - * - * Supported file types in most operations (file playback, editing, saving ...). +@{ */ -typedef enum { - GF_FILE_TYPE_NOT_SUPPORTED = 0, - GF_FILE_TYPE_ISO_MEDIA = 1, - GF_FILE_TYPE_BT_WRL_X3DV = 2, - GF_FILE_TYPE_XMT_X3D = 3, - GF_FILE_TYPE_SVG = 4, - GF_FILE_TYPE_SWF = 5, - GF_FILE_TYPE_LSR_SAF = 6, -} GF_FileType; /*! - * \brief Supported media stream types - * \hideinitializer - * - * Supported media stream types for media objects. +\brief Supported media stream types +\hideinitializer + +Supported media stream types for media objects. */ enum { + /*!Unknown stream type*/ + GF_STREAM_UNKNOWN = 0, /*!MPEG-4 Object Descriptor Stream*/ GF_STREAM_OD = 0x01, /*!MPEG-4 Object Clock Reference Stream*/ @@ -106,87 +90,66 @@ enum /*!GPAC Private Scene streams\n - *\n\note - *this stream type (MPEG-4 user-private) is reserved for streams only used to create a scene decoder - *handling the scene without input streams, as is the case for file readers (BT/VRML/XML..).\n - *The decoderSpecificInfo carried is as follows: - \code - u32 file_size: total file size - char file_name[dsi_size - sizeof(u32)]: local file name. - \n\note: File may be a cache file, it is the decoder responsability to check if the file is completely - downloaded before parsing if needed. - \endcode - *The inBufferLength param for decoders using these streams is the stream clock in ms (no input data is given).\n - *The "dummy_in" module is available to generate these streams for common files, and also takes care of proper - clock init in case of seeking.\n - *This is a reentrant stream type: if any media object with this streamtype also exist in the scene, they will be - *attached to the scene decoder (except when a new inline scene is detected, in which case a new decoder will - *be created). This allows for animation/sprite usage along with the systems timing/stream management.\n - *\n - *the objectTypeIndication currently in use for these streams are documented below\n + \n + \note + This stream type (MPEG-4 user-private) is reserved for streams used to create a scene decoder + handling the scene without input streams, as is the case for file readers (BT/VRML/XML..).\n */ GF_STREAM_PRIVATE_SCENE = 0x20, - /*!GPAC Private Media streams\n - *\n\note - *this stream type (MPEG-4 user-private) is reserved for media streams bypassing GPAC for decoding - and composition. The media decoder is only in charge of repositioning the video output, and the compositor will - draw an empty rectangle if using alpha composition - - *The decoderSpecificInfo carried only contains an opaque pointer in the data field, which depends on the underlying InputServce provider + GF_STREAM_METADATA = 0x21, - *the objectTypeIndication currently in use for these streams are documented below\n - */ - GF_STREAM_PRIVATE_MEDIA = 0x21, + GF_STREAM_ENCRYPTED = 0xE0, + /*stream carries files, each file being a complete AU*/ + GF_STREAM_FILE = 0xE1, - /*used internally to signal that the OTI carries a 4CC code, typically media subtype (stsd entry in file format)*/ - GF_STREAM_4CC = 0xF0 + //other stream types may be declared using their handler 4CC as defined in ISOBMFF }; +/*! Gets the stream type name based on stream type +\param streamType stream type GF_STREAM_XXX as defined in constants.h +\return NULL if unknown, otherwise value + */ +const char *gf_stream_type_name(u32 streamType); -/*! - * Media Object types - * - * This type provides a hint to network modules which may have to generate an service descriptor on the fly. - * They occur only if objects/services used in the scene are not referenced through ObjectDescriptors (MPEG-4) - * but direct through URL -*/ -enum -{ - /*!service descriptor expected is of undefined type. This should be treated like GF_MEDIA_OBJECT_SCENE*/ - GF_MEDIA_OBJECT_UNDEF = 0, - /*!service descriptor expected is of SCENE type and shall contain a scene stream and OD one if needed*/ - GF_MEDIA_OBJECT_SCENE, - /*!service descriptor expected is of SCENE UPDATES type (animation streams)*/ - GF_MEDIA_OBJECT_UPDATES, - /*!service descriptor expected is of VISUAL type*/ - GF_MEDIA_OBJECT_VIDEO, - /*!service descriptor expected is of AUDIO type*/ - GF_MEDIA_OBJECT_AUDIO, - /*!service descriptor expected is of TEXT type (3GPP/MPEG4)*/ - GF_MEDIA_OBJECT_TEXT, - /*!service descriptor expected is of UserInteraction type (MPEG-4 InputSensor)*/ - GF_MEDIA_OBJECT_INTERACT -}; -/*! All Media Objects inserted through URLs and not MPEG-4 OD Framework use this ODID*/ -#define GF_MEDIA_EXTERNAL_ID 1050 +/*! Gets the stream type by name +\param name name of the stream type to query +\return GF_STREAM_UNKNOWN if unknown, otherwise GF_STREAM_XXX value + */ +u32 gf_stream_type_by_name(const char *name); +/*! Gets the list of names of all stream types defined +\return names of all stream types defined + */ +const char *gf_stream_type_all_names(); + +/*! Enumerates defined stream types +\param idx index of the stream type, 0-based +\param name name of the stream type (used when parsing stream type from textual definition) +\param desc description of the stream type +\return stream type, or GF_STREAM_UNKNOWN if no more stream types + */ +u32 gf_stream_types_enum(u32 *idx, const char **name, const char **desc); -/*! - * \brief Pixel Formats - * - * Supported pixel formats for everything using video -*/ #ifndef GF_4CC -#define GF_4CC(a,b,c,d) (((a)<<24)|((b)<<16)|((c)<<8)|(d)) +/*! macro for 4CC*/ +#define GF_4CC(a,b,c,d) ((((u32)a)<<24)|(((u32)b)<<16)|(((u32)c)<<8)|(d)) #endif + +/*! +\brief Pixel Formats + +Supported pixel formats for everything using video +*/ typedef enum { /*!8 bit GREY */ GF_PIXEL_GREYSCALE = GF_4CC('G','R','E','Y'), - /*!16 bit greyscale*/ + /*!16 bit greyscale, first alpha, then grey*/ GF_PIXEL_ALPHAGREY = GF_4CC('G','R','A','L'), + /*!16 bit greyscale, first grey, then alpha*/ + GF_PIXEL_GREYALPHA = GF_4CC('A','L','G','R'), /*!12 bit RGB on 16 bits (4096 colors)*/ GF_PIXEL_RGB_444 = GF_4CC('R','4','4','4'), /*!15 bit RGB*/ @@ -194,18 +157,27 @@ typedef enum /*!16 bit RGB*/ GF_PIXEL_RGB_565 = GF_4CC('R','5','6','5'), /*!24 bit RGB*/ - GF_PIXEL_RGB_24 = GF_4CC('R','G','B','3'), + GF_PIXEL_RGB = GF_4CC('R','G','B','3'), /*!24 bit BGR*/ - GF_PIXEL_BGR_24 = GF_4CC('B','G','R','3'), - /*!32 bit RGB. Component ordering in bytes is B-G-R-X.*/ - GF_PIXEL_RGB_32 = GF_4CC('R','G','B','4'), - /*!32 bit BGR. Component ordering in bytes is R-G-B-X.*/ - GF_PIXEL_BGR_32 = GF_4CC('B','G','R','4'), + GF_PIXEL_BGR = GF_4CC('B','G','R','3'), + /*!32 bit RGB. Component ordering in bytes is R-G-B-X.*/ + GF_PIXEL_RGBX = GF_4CC('R','G','B','4'), + /*!32 bit BGR. Component ordering in bytes is B-G-R-X.*/ + GF_PIXEL_BGRX = GF_4CC('B','G','R','4'), + /*!32 bit RGB. Component ordering in bytes is X-R-G-B.*/ + GF_PIXEL_XRGB = GF_4CC('R','G','B','X'), + /*!32 bit BGR. Component ordering in bytes is X-B-G-R.*/ + GF_PIXEL_XBGR = GF_4CC('B','G','R','X'), - /*!32 bit ARGB. Component ordering in bytes is B-G-R-A.*/ + /*!32 bit ARGB. Component ordering in bytes is A-R-G-B.*/ GF_PIXEL_ARGB = GF_4CC('A','R','G','B'), /*!32 bit RGBA (openGL like). Component ordering in bytes is R-G-B-A.*/ GF_PIXEL_RGBA = GF_4CC('R','G','B', 'A'), + /*!32 bit BGRA. Component ordering in bytes is B-G-R-A.*/ + GF_PIXEL_BGRA = GF_4CC('B','G','R','A'), + /*!32 bit ABGR. Component ordering in bytes is A-B-G-R.*/ + GF_PIXEL_ABGR = GF_4CC('A','B','G','R'), + /*!RGB24 + depth plane. Component ordering in bytes is R-G-B-D.*/ GF_PIXEL_RGBD = GF_4CC('R', 'G', 'B', 'D'), /*!RGB24 + depth plane (7 lower bits) + shape mask. Component ordering in bytes is R-G-B-(S+D).*/ @@ -216,277 +188,417 @@ typedef enum GF_PIXEL_RGBAS = GF_4CC('R', 'G', 'A', 'S'), /*internal format for OpenGL using pachek RGB 24 bit plus planar depth plane at the end of the image*/ - GF_PIXEL_RGB_24_DEPTH = GF_4CC('R', 'G', 'B', 'd'), + GF_PIXEL_RGB_DEPTH = GF_4CC('R', 'G', 'B', 'd'), - /*!YUV packed format*/ - GF_PIXEL_YUY2 = GF_4CC('Y','U','Y','2'), - /*!YUV packed format*/ + /*!YUV packed 422 format*/ + GF_PIXEL_YUYV = GF_4CC('Y','U','Y','2'), + /*!YUV packed 422 format*/ GF_PIXEL_YVYU = GF_4CC('Y','V','Y','U'), - /*!YUV packed format*/ + /*!YUV packed 422 format*/ GF_PIXEL_UYVY = GF_4CC('U','Y','V','Y'), - /*!YUV packed format*/ + /*!YUV packed 422 format*/ GF_PIXEL_VYUY = GF_4CC('V','Y','U','Y'), - /*!YUV packed format*/ - GF_PIXEL_Y422 = GF_4CC('Y','4','2','2'), - /*!YUV packed format*/ - GF_PIXEL_UYNV = GF_4CC('U','Y','N','V'), - /*!YUV packed format*/ - GF_PIXEL_YUNV = GF_4CC('Y','U','N','V'), - /*!YUV packed format*/ - GF_PIXEL_V422 = GF_4CC('V','4','2','2'), /*!YUV planar format*/ - GF_PIXEL_YV12 = GF_4CC('Y','V','1','2'), - /*!YUV planar format*/ - GF_PIXEL_IYUV = GF_4CC('I','Y','U','V'), - /*!YUV planar format*/ - GF_PIXEL_I420 = GF_4CC('I','4','2','0'), - /*!YUV planar format*/ - GF_PIXEL_I444 = GF_4CC('I','4','4','4'), + GF_PIXEL_YUV = GF_4CC('Y','V','1','2'), + /*!YUV420p in 10 bits mode, little endian*/ + GF_PIXEL_YUV_10 = GF_4CC('Y','0','1','0'), + /*!YUV420p + Alpha plane*/ + GF_PIXEL_YUVA = GF_4CC('Y', 'U', 'V', 'A'), + /*!YUV420p + Depth plane*/ + GF_PIXEL_YUVD = GF_4CC('Y', 'U', 'V', 'D'), /*!420 Y planar UV interleaved*/ GF_PIXEL_NV21 = GF_4CC('N','V','2','1'), + /*!420 Y planar UV interleaved, 10 bits, little endian */ + GF_PIXEL_NV21_10 = GF_4CC('N','2','1','0'), /*!420 Y planar VU interleaved (U and V swapped) */ GF_PIXEL_NV12 = GF_4CC('N','V','1','2'), - - /*!YV12 + Alpha plane*/ - GF_PIXEL_YUVA = GF_4CC('Y', 'U', 'V', 'A'), - - /*!YV12 + Depth plane*/ - GF_PIXEL_YUVD = GF_4CC('Y', 'U', 'V', 'D'), - - /*!YUV planar format in 10 bits mode, all components are stored as shorts*/ - GF_PIXEL_YV12_10 = GF_4CC('Y','0','1','0'), - + /*!420 Y planar VU interleaved (U and V swapped), 10 bits, little endian */ + GF_PIXEL_NV12_10 = GF_4CC('N','1','2','0'), + /*!422 YUV*/ GF_PIXEL_YUV422 = GF_4CC('Y','4','4','2'), + /*!422 YUV, 10 bits, little endian*/ GF_PIXEL_YUV422_10 = GF_4CC('Y','2','1','0'), + /*!444 YUV+Alpha*/ + GF_PIXEL_YUVA444 = GF_4CC('Y','A','4','4'), + /*!444 YUV*/ GF_PIXEL_YUV444 = GF_4CC('Y','4','4','4'), - GF_PIXEL_YUV444_10 = GF_4CC('Y','4','1','0') - + /*!444 YUV, 10 bits, little endian*/ + GF_PIXEL_YUV444_10 = GF_4CC('Y','4','1','0'), + + /*!Unknown format exposed a single openGL texture to be consumed using samplerExternalOES*/ + GF_PIXEL_GL_EXTERNAL = GF_4CC('E','X','G','L') } GF_PixelFormat; -/*! - * \brief Scene ObjectTypeIndication Formats - * - * Supported ObjectTypeIndication for scene description streams. *_FILE_* are only used with private scene streams - * and only carry the file name for the scene. Other internal stream types can be used in a real streaming environment +/*! enumerates GPAC pixel formats +\param pf_name name of the pixel format +\return pixel format code */ -enum +GF_PixelFormat gf_pixel_fmt_parse(const char *pf_name); + +/*! gets name of pixel formats +\param pfmt pixel format code +\return pixel format name +*/ +const char *gf_pixel_fmt_name(GF_PixelFormat pfmt); + +/*! gets short name of pixel formats, as used for file extensions +\param pfmt pixel format code +\return pixel format short name +*/ +const char *gf_pixel_fmt_sname(GF_PixelFormat pfmt); + +/*! enumerates pixel formats +\param idx index of the pixel format, 0-based +\param name name of the pixel format +\param fileext file extension of the pixel format +\param description description of the pixel format +\return pixel format code, 0 if no more pixel formats are availble +*/ +Bool gf_pixel_fmt_enum(u32 *idx, const char **name, const char **fileext, const char **description); + +/*! gets the list of all supported pixel format names +\return list of supported pixel format names +*/ +const char *gf_pixel_fmt_all_names(); + +/*! gets the list of all supported pixel format names +\return list of supported pixel format short names +*/ +const char *gf_pixel_fmt_all_shortnames(); + +/*! returns size and stride characteristics for the pixel format. If the stride or stride_uv value are not 0, they are used to compute the size. Otherwise no padding at end of line is assumed. +\param pixfmt pixfmt format code +\param width target frame width +\param height target frame height +\param[out] out_size output frame size +\param[in,out] out_stride output frame stride for single plane or plane 0 +\param[in,out] out_stride_uv output frame stride for UV planes +\param[out] out_planes output frame plane numbers +\param[out] out_plane_uv_height height of UV planes +\return error code if any +*/ +Bool gf_pixel_get_size_info(GF_PixelFormat pixfmt, u32 width, u32 height, u32 *out_size, u32 *out_stride, u32 *out_stride_uv, u32 *out_planes, u32 *out_plane_uv_height); + +/*! Gets the number of bytes per pixel on first plane +\param pixfmt pixel format code +\return number of bytes per pixel +*/ +u32 gf_pixel_get_bytes_per_pixel(GF_PixelFormat pixfmt); + +/*! Gets the number of component per pixel +\param pixfmt pixel format code +\return number of bytes per pixel +*/ +u32 gf_pixel_get_nb_comp(GF_PixelFormat pixfmt); + +/*! +\brief Codec IDs + +Codec ID identifies the stream coding type. The enum is devided into values less than 255, which are equivalent to MPEG-4 systems ObjectTypeIndication. Other values are 4CCs, usually matching ISOMEDIA sample entry types*/ +typedef enum { - /*!OTI for BIFS v1*/ - GPAC_OTI_SCENE_BIFS = 0x01, - /*!OTI for OD v1*/ - GPAC_OTI_OD_V1 = 0x01, - /*!OTI for BIFS v2*/ - GPAC_OTI_SCENE_BIFS_V2 = 0x02, - /*!OTI for OD v2*/ - GPAC_OTI_OD_V2 = 0x02, - /*!OTI for BIFS InputSensor streams*/ - GPAC_OTI_SCENE_INTERACT = 0x03, - /*!OTI for streams with extended BIFS config*/ - GPAC_OTI_SCENE_BIFS_EXTENDED = 0x04, - /*!OTI for AFX streams with AFXConfig*/ - GPAC_OTI_SCENE_AFX = 0x05, - /*!OTI for Font data streams */ - GPAC_OTI_FONT = 0x06, - /*!OTI for synthesized texture streams */ - GPAC_OTI_SCENE_SYNTHESIZED_TEXTURE = 0x07, - /*!OTI for streaming text streams */ - GPAC_OTI_TEXT_MPEG4 = 0x08, - /*!OTI for LASeR streams*/ - GPAC_OTI_SCENE_LASER = 0x09, - /*!OTI for SAF streams*/ - GPAC_OTI_SCENE_SAF = 0x0A, - - /*!OTI for MPEG-4 Video Part 2 streams*/ - GPAC_OTI_VIDEO_MPEG4_PART2 = 0x20, - /*!OTI for MPEG-4 Video Part 10 (H.264 | AVC ) streams*/ - GPAC_OTI_VIDEO_AVC = 0x21, - /*!OTI for AVC Parameter sets streams*/ - GPAC_OTI_VIDEO_AVC_PS = 0x22, - /*!OTI for HEVC video */ - GPAC_OTI_VIDEO_HEVC = 0x23, - /*!OTI for H264-SVC streams*/ - GPAC_OTI_VIDEO_SVC = 0x24, - /*!OTI for HEVC layered streams*/ - GPAC_OTI_VIDEO_LHVC = 0x25, - /*!OTI for H264-SVC streams*/ - GPAC_OTI_VIDEO_MVC = 0x29, - /*!OTI for MPEG-4 AAC streams*/ - GPAC_OTI_AUDIO_AAC_MPEG4 = 0x40, - - /*!OTI for MPEG-2 Visual Simple Profile streams*/ - GPAC_OTI_VIDEO_MPEG2_SIMPLE = 0x60, - /*!OTI for MPEG-2 Visual Main Profile streams*/ - GPAC_OTI_VIDEO_MPEG2_MAIN = 0x61, - /*!OTI for MPEG-2 Visual SNR Profile streams*/ - GPAC_OTI_VIDEO_MPEG2_SNR = 0x62, - /*!OTI for MPEG-2 Visual SNR Profile streams*/ - GPAC_OTI_VIDEO_MPEG2_SPATIAL = 0x63, - /*!OTI for MPEG-2 Visual SNR Profile streams*/ - GPAC_OTI_VIDEO_MPEG2_HIGH = 0x64, - /*!OTI for MPEG-2 Visual SNR Profile streams*/ - GPAC_OTI_VIDEO_MPEG2_422 = 0x65, - - - /*!OTI for MPEG-2 AAC Main Profile streams*/ - GPAC_OTI_AUDIO_AAC_MPEG2_MP = 0x66, - /*!OTI for MPEG-2 AAC Low Complexity Profile streams*/ - GPAC_OTI_AUDIO_AAC_MPEG2_LCP = 0x67, - /*!OTI for MPEG-2 AAC Scalable Sampling Rate Profile streams*/ - GPAC_OTI_AUDIO_AAC_MPEG2_SSRP = 0x68, - /*!OTI for MPEG-2 Audio Part 3 streams*/ - GPAC_OTI_AUDIO_MPEG2_PART3 = 0x69, - /*!OTI for MPEG-1 Video streams*/ - GPAC_OTI_VIDEO_MPEG1 = 0x6A, - /*!OTI for MPEG-1 Audio streams*/ - GPAC_OTI_AUDIO_MPEG1 = 0x6B, - /*!OTI for JPEG streams*/ - GPAC_OTI_IMAGE_JPEG = 0x6C, - /*!OTI for PNG streams*/ - GPAC_OTI_IMAGE_PNG = 0x6D, - /*!OTI for JPEG-2000 streams*/ - GPAC_OTI_IMAGE_JPEG_2000 = 0x6E, + /*!Never used by PID declarations, but used by filters caps*/ + GF_CODECID_NONE = 0, + /*! codecid for BIFS v1*/ + GF_CODECID_BIFS = 0x01, + /*! codecid for OD v1*/ + GF_CODECID_OD_V1 = 0x01, + /*! codecid for BIFS v2*/ + GF_CODECID_BIFS_V2 = 0x02, + /*! codecid for OD v2*/ + GF_CODECID_OD_V2 = 0x02, + /*! codecid for BIFS InputSensor streams*/ + GF_CODECID_INTERACT = 0x03, + /*! codecid for streams with extended BIFS config*/ + GF_CODECID_BIFS_EXTENDED = 0x04, + /*! codecid for AFX streams with AFXConfig*/ + GF_CODECID_AFX = 0x05, + /*! codecid for Font data streams */ + GF_CODECID_FONT = 0x06, + /*! codecid for synthesized texture streams */ + GF_CODECID_SYNTHESIZED_TEXTURE = 0x07, + /*! codecid for streaming text streams */ + GF_CODECID_TEXT_MPEG4 = 0x08, + /*! codecid for LASeR streams*/ + GF_CODECID_LASER = 0x09, + /*! codecid for SAF streams when stored in MP4 ...*/ + GF_CODECID_SAF = 0x0A, - /*! - * \brief Extra ObjectTypeIndication - * - * ObjectTypeIndication for media (audio/video) codecs not defined in MPEG-4. Since GPAC signals streams through MPEG-4 Descriptions, - * it needs extensions for non-MPEG-4 streams such as AMR, H263 , etc.\n - *\note The decoder specific info for such streams is always carried encoded, with the following syntax:\n - * DSI Syntax for audio streams - \code - * u32 codec_four_cc: the codec 4CC reg code / codec id for ffmpeg - * u32 sample_rate: sampling rate or 0 if unknown - * u16 nb_channels: num channels or 0 if unknown - * u16 frame_size: num audio samples per frame or 0 if unknown - * u8 nb_bits_per_sample: nb bits or 0 if unknown - * u8 num_frames_per_au: num audio frames per AU (used in 3GPP, max 15), 0 if unknown - * char *data: per-codec extensions till end of DSI bitstream - \endcode - \n - * DSI Syntax for video streams - \code - * u32 codec_four_cc: the codec 4CC reg code / codec id for ffmpeg - * u16 width: video width or 0 if unknown - * u16 height: video height or 0 if unknown - * char *data: per-codec extensions till end of DSI bitstream - \endcode - */ - GPAC_OTI_MEDIA_GENERIC = 0x80, - /*! - * \brief FFMPEG ObjectTypeIndication - * - * ObjectTypeIndication for FFMPEG codecs not defined in MPEG-4. FFMPEG uses the base GPAC_OTI_MEDIA_GENERIC specific info formats, and extends it as follows: - \code - * u32 bit_rate: the stream rate or 0 if unknown - * u32 codec_tag: FFMPEG codec tag as defined in libavcodec - * char *data: codec extensions till end of DSI bitstream - \endcode - */ - GPAC_OTI_MEDIA_FFMPEG = 0x81, - - /*!OTI for EVRC Voice streams*/ - GPAC_OTI_AUDIO_EVRC_VOICE = 0xA0, - /*!OTI for SMV Voice streams*/ - GPAC_OTI_AUDIO_SMV_VOICE = 0xA1, - /*!OTI for 3GPP2 CMF streams*/ - GPAC_OTI_3GPP2_CMF = 0xA2, - /*!OTI for SMPTE VC-1 Video streams*/ - GPAC_OTI_VIDEO_SMPTE_VC1 = 0xA3, - /*!OTI for Dirac Video streams*/ - GPAC_OTI_VIDEO_DIRAC = 0xA4, - /*!OTI for AC-3 audio streams*/ - GPAC_OTI_AUDIO_AC3 = 0xA5, - /*!OTI for enhanced AC-3 audio streams*/ - GPAC_OTI_AUDIO_EAC3 = 0xA6, - /*!OTI for DRA audio streams*/ - GPAC_OTI_AUDIO_DRA = 0xA7, - /*!OTI for ITU G719 audio streams*/ - GPAC_OTI_AUDIO_ITU_G719 = 0xA8, - /*!OTI for DTS Coherent Acoustics audio streams*/ - GPAC_OTI_AUDIO_DTS_CA = 0xA9, - /*!OTI for DTS-HD High Resolution audio streams*/ - GPAC_OTI_AUDIO_DTS_HD_HR = 0xAA, - /*!OTI for DTS-HD Master audio streams*/ - GPAC_OTI_AUDIO_DTS_HD_MASTER = 0xAB, - - /*!OTI for dummy streams (dsi = file name) using the generic context loader (BIFS/VRML/SWF/...) - GPAC internal*/ - GPAC_OTI_PRIVATE_SCENE_GENERIC = 0xC0, - /*!OTI for SVG dummy stream (dsi = file name) - GPAC internal*/ - GPAC_OTI_PRIVATE_SCENE_SVG = 0xC1, - /*!OTI for LASeR/SAF+XML dummy stream (dsi = file name) - GPAC internal*/ - GPAC_OTI_PRIVATE_SCENE_LASER = 0xC2, - /*!OTI for XBL dummy streams (dsi = file name) - GPAC internal*/ - GPAC_OTI_PRIVATE_SCENE_XBL = 0xC3, - /*!OTI for EPG dummy streams (dsi = null) - GPAC internal*/ - GPAC_OTI_PRIVATE_SCENE_EPG = 0xC4, - /*!OTI for WGT dummy streams (dsi = null) - GPAC internal*/ - GPAC_OTI_PRIVATE_SCENE_WGT = 0xC5, - /*!OTI for VTT dummy stream (dsi = file name) - GPAC internal*/ - GPAC_OTI_PRIVATE_SCENE_VTT = 0xC6, - - /*!OTI for streaming SVG - GPAC internal*/ - GPAC_OTI_SCENE_SVG = 0xD0, - /*!OTI for streaming SVG + gz - GPAC internal*/ - GPAC_OTI_SCENE_SVG_GZ = 0xD1, - /*!OTI for DIMS (dsi = 3GPP DIMS configuration) - GPAC internal*/ - GPAC_OTI_SCENE_DIMS = 0xD2, - /*!OTI for streaming VTT - GPAC internal*/ - GPAC_OTI_SCENE_VTT = 0xD3, - /*!OTI for streaming VTT from MP4- GPAC internal*/ - GPAC_OTI_SCENE_VTT_MP4 = 0xD4, - /*!OTI for streaming simple text from MP4- GPAC internal*/ - GPAC_OTI_SCENE_SIMPLE_TEXT_MP4 = 0xD5, + /*! codecid for MPEG-4 Video Part 2 streams*/ + GF_CODECID_MPEG4_PART2 = 0x20, + /*! codecid for MPEG-4 Video Part 10 (H.264 | AVC ) streams*/ + GF_CODECID_AVC = 0x21, + /*! codecid for AVC Parameter sets streams*/ + GF_CODECID_AVC_PS = 0x22, + /*! codecid for HEVC video */ + GF_CODECID_HEVC = 0x23, + /*! codecid for H264-SVC streams*/ + GF_CODECID_SVC = 0x24, + /*! codecid for HEVC layered streams*/ + GF_CODECID_LHVC = 0x25, + /*! codecid for H264-SVC streams*/ + GF_CODECID_MVC = 0x29, + /*! codecid for MPEG-4 AAC streams*/ + GF_CODECID_AAC_MPEG4 = 0x40, + /*! codecid for MPEG-2 Visual Simple Profile streams*/ + GF_CODECID_MPEG2_SIMPLE = 0x60, + /*! codecid for MPEG-2 Visual Main Profile streams*/ + GF_CODECID_MPEG2_MAIN = 0x61, + /*! codecid for MPEG-2 Visual SNR Profile streams*/ + GF_CODECID_MPEG2_SNR = 0x62, + /*! codecid for MPEG-2 Visual SNR Profile streams*/ + GF_CODECID_MPEG2_SPATIAL = 0x63, + /*! codecid for MPEG-2 Visual SNR Profile streams*/ + GF_CODECID_MPEG2_HIGH = 0x64, + /*! codecid for MPEG-2 Visual SNR Profile streams*/ + GF_CODECID_MPEG2_422 = 0x65, + /*! codecid for MPEG-2 AAC Main Profile streams*/ + GF_CODECID_AAC_MPEG2_MP = 0x66, + /*! codecid for MPEG-2 AAC Low Complexity Profile streams*/ + GF_CODECID_AAC_MPEG2_LCP = 0x67, + /*! codecid for MPEG-2 AAC Scalable Sampling Rate Profile streams*/ + GF_CODECID_AAC_MPEG2_SSRP = 0x68, + /*! codecid for MPEG-2 Audio Part 3 streams*/ + GF_CODECID_MPEG2_PART3 = 0x69, + /*! codecid for MPEG-1 Video streams*/ + GF_CODECID_MPEG1 = 0x6A, + /*! codecid for MPEG-1 Audio streams*/ + GF_CODECID_MPEG_AUDIO = 0x6B, + /*! codecid for JPEG streams*/ + GF_CODECID_JPEG = 0x6C, + /*! codecid for PNG streams*/ + GF_CODECID_PNG = 0x6D, + + GF_CODECID_LAST_MPEG4_MAPPING = 0xFF, + + /*! codecid for JPEG-2000 streams*/ + GF_CODECID_J2K = GF_4CC('j','p','2','k'), + + /*!H263 visual streams*/ + GF_CODECID_S263 = GF_4CC('s','2','6','3'), + GF_CODECID_H263 = GF_4CC('h','2','6','3'), + + /*! codecid for HEVC tiles */ + GF_CODECID_HEVC_TILES = GF_4CC( 'h', 'v', 't', '1' ), + + /*! codecid for EVRC Voice streams*/ + GF_CODECID_EVRC = GF_4CC('s','e','v','c'), + /*! codecid for SMV Voice streams*/ + GF_CODECID_SMV = GF_4CC('s','s','m','v'), + /*! codecid for 13K Voice / QCELP audio streams*/ + GF_CODECID_QCELP = GF_4CC('s','q','c','p'), + /*! codecid for AMR*/ + GF_CODECID_AMR = GF_4CC('s','a','m','r'), + /*! codecid for AMR-WB*/ + GF_CODECID_AMR_WB = GF_4CC('s','a','w','b'), + /*! codecid for EVRC, PacketVideo MUX*/ + GF_CODECID_EVRC_PV = GF_4CC('p','e','v','c'), + + /*! codecid for SMPTE VC-1 Video streams*/ + GF_CODECID_SMPTE_VC1 = GF_4CC('v','c','-','1'), + /*! codecid for Dirac Video streams*/ + GF_CODECID_DIRAC = GF_4CC('d','r','a','c'), + /*! codecid for AC-3 audio streams*/ + GF_CODECID_AC3 = GF_4CC('a','c','-','3'), + /*! codecid for enhanced AC-3 audio streams*/ + GF_CODECID_EAC3 = GF_4CC('e','c','-','3'), + /*! codecid for DRA audio streams*/ + GF_CODECID_DRA = GF_4CC('d','r','a','1'), + /*! codecid for ITU G719 audio streams*/ + GF_CODECID_G719 = GF_4CC('g','7','1','9'), + /*! codecid for DTS Express low bit rate audio*/ + GF_CODECID_DTS_LBR = GF_4CC('d','t','s','e'), + /*! codecid for DTS Coherent Acoustics audio streams*/ + GF_CODECID_DTS_CA = GF_4CC('d','t','s','c'), + /*! codecid for DTS-HD High Resolution audio streams*/ + GF_CODECID_DTS_HD_HR = GF_4CC('d','t','s','h'), + /*! codecid for DTS-HD Master audio streams*/ + GF_CODECID_DTS_HD_MASTER = GF_4CC('d','t','s','l'), + /*! codecid for DTS-X Master audio streams*/ + GF_CODECID_DTS_X = GF_4CC('d','t','s','x'), + + /*! codecid for DVB EPG*/ + GF_CODECID_DVB_EIT = GF_4CC('e','i','t',' '), + + /*! codecid for streaming SVG*/ + GF_CODECID_SVG = GF_4CC('s','g','g',' '), + /*! codecid for streaming SVG + gz*/ + GF_CODECID_SVG_GZ = GF_4CC('s','v','g','z'), + /*! codecid for DIMS (dsi = 3GPP DIMS configuration)*/ + GF_CODECID_DIMS = GF_4CC('d','i','m','s'), + /*! codecid for streaming VTT*/ + GF_CODECID_WEBVTT = GF_4CC('w','v','t','t'), + /*! codecid for streaming simple text*/ + GF_CODECID_SIMPLE_TEXT = GF_4CC('s','t','x','t'), + /*! codecid for meta data streams in text format*/ + GF_CODECID_META_TEXT = GF_4CC('m','e','t','t'), + /*! codecid for meta data streams in XML format*/ + GF_CODECID_META_XML = GF_4CC('m','e','t','x'), + /*! codecid for subtitle streams in text format*/ + GF_CODECID_SUBS_TEXT = GF_4CC('s','b','t','t'), + /*! codecid for subtitle streams in xml format*/ + GF_CODECID_SUBS_XML = GF_4CC('s','t','p','p'), + + /*! codecid for subtitle/text streams in tx3g / apple text format*/ + GF_CODECID_TX3G = GF_4CC( 't', 'x', '3', 'g' ), /*! - * \brief OGG ObjectTypeIndication - * - * Object type indication for all OGG media. The DSI contains all intitialization ogg packets for the codec - * and is formated as follows:\n - *\code - while (dsi_size) { + \brief OGG DecoderConfig + + The DecoderConfig for theora, vorbis, flac and opus contains all intitialization ogg packets for the codec + and is formated as follows:\n + \code + while (dsi_size) { bit(16) packet_size; char packet[packet_size]; dsi_size -= packet_size; - }\endcode - */ - GPAC_OTI_MEDIA_OGG = 0xDD, - GPAC_OTI_MEDIA_THEORA = 0xDF, - - GPAC_OTI_MEDIA_SUBPIC = 0xE0, - - /*!OTI for 13K Voice / QCELP audio streams*/ - GPAC_OTI_AUDIO_13K_VOICE = 0xE1, - - /*!OTI for RAW media streams. Input data is directly dispatched to the composition memory. The DSI contains is formated (MSBF) as follows:\n - * DSI Syntax for audio streams - \code - * u32 sample_rate: sampling rate - * u16 nb_channels: num channels - * u16 nb_bits_per_sample: num of bits per audio sample - * u32 frame_size: max size of audio frame in byte - * u32 channel_config: GPAC mask of GF_AUDIO_CH_ constants, or 0 if unknown - \endcode - \n - * DSI Syntax for video streams - \code - * u32 codec_four_cc: the codec 4CC reg code / codec id for ffmpeg - * u16 width: video width or 0 if unknown - * u16 height: video height or 0 if unknown - * u32 frame_size: size of the video frame - * u32 stride: horizontal stride of the video frame - \endcode + } + \endcode */ - GPAC_OTI_RAW_MEDIA_STREAM = 0x101, + /*! codecid for theora video streams*/ + GF_CODECID_THEORA = GF_4CC('t','h','e','u'), + /*! codecid for vorbis audio streams*/ + GF_CODECID_VORBIS = GF_4CC('v','o','r','b'), + /*! codecid for flac audio streams*/ + GF_CODECID_FLAC = GF_4CC('f','l','a','c'), + /*! codecid for speex audio streams*/ + GF_CODECID_SPEEX = GF_4CC('s','p','e','x'), + /*! codecid for opus audio streams*/ + GF_CODECID_OPUS = GF_4CC('O','p','u','s'), + /*! codecid for subpic DVD subtittles - the associated stream type is text*/ + GF_CODECID_SUBPIC = GF_4CC('s','u','b','p'), + /*! codecid for ADPCM audio, as used in AVI*/ + GF_CODECID_ADPCM = GF_4CC('A','P','C','M'), + /*! codecid for IBM CVSD audio, as used in AVI*/ + GF_CODECID_IBM_CVSD = GF_4CC('C','S','V','D'), + /*! codecid for ALAW audio, as used in AVI*/ + GF_CODECID_ALAW = GF_4CC('A','L','A','W'), + /*! codecid for MULAW audio, as used in AVI*/ + GF_CODECID_MULAW = GF_4CC('M','L','A','W'), + /*! codecid for OKI ADPCM audio, as used in AVI*/ + GF_CODECID_OKI_ADPCM = GF_4CC('O','P','C','M'), + /*! codecid for DVI ADPCM audio, as used in AVI*/ + GF_CODECID_DVI_ADPCM = GF_4CC('D','P','C','M'), + /*! codecid for DIGISTD audio, as used in AVI*/ + GF_CODECID_DIGISTD = GF_4CC('D','S','T','D'), + /*! codecid for Yamaha ADPCM audio, as used in AVI*/ + GF_CODECID_YAMAHA_ADPCM = GF_4CC('Y','P','C','M'), + /*! codecid for TrueSpeech audio, as used in AVI*/ + GF_CODECID_DSP_TRUESPEECH = GF_4CC('T','S','P','E'), + /*! codecid for GSM 610 audio, as used in AVI*/ + GF_CODECID_GSM610 = GF_4CC('G','6','1','0'), + /*! codecid for IBM MULAW audio, as used in AVI*/ + GF_CODECID_IBM_MULAW = GF_4CC('I','U','L','W'), + /*! codecid for IBM ALAW audio, as used in AVI*/ + GF_CODECID_IBM_ALAW = GF_4CC('I','A','L','W'), + /*! codecid for IBM ADPCM audio, as used in AVI*/ + GF_CODECID_IBM_ADPCM = GF_4CC('I','P','C','M'), + /*! codecid for Flash/ShockWave streams*/ + GF_CODECID_FLASH = GF_4CC( 'f', 'l', 's', 'h' ), + /*! codecid for RAW media streams. No decoder config associated (config through PID properties)*/ + GF_CODECID_RAW = GF_4CC('R','A','W','M'), - /*!OTI for LIBPLAYER private streams. The data pointer in the DSI is the libplayer handle object*/ - GPAC_OTI_PRIVATE_MEDIA_LIBPLAYER = 0xF1 -}; + GF_CODECID_AV1 = GF_4CC('A','V','1',' '), + + GF_CODECID_VP8 = GF_4CC('V','P','0','8'), + GF_CODECID_VP9 = GF_4CC('V','P','0','9'), + GF_CODECID_VP10 = GF_4CC('V','P','1','0'), + + /*MPEG-H audio*/ + GF_CODECID_MPHA = GF_4CC('m','p','h','a'), + /*MPEG-H mux audio*/ + GF_CODECID_MHAS = GF_4CC('m','h','a','s'), + + /*QT ProRes*/ + GF_CODECID_APCH = GF_4CC( 'a', 'p', 'c', 'h' ), + GF_CODECID_APCO = GF_4CC( 'a', 'p', 'c', 'o' ), + GF_CODECID_APCN = GF_4CC( 'a', 'p', 'c', 'n' ), + GF_CODECID_APCS = GF_4CC( 'a', 'p', 'c', 's' ), + GF_CODECID_AP4X = GF_4CC( 'a', 'p', '4', 'x' ), + GF_CODECID_AP4H = GF_4CC( 'a', 'p', '4', 'h' ), + + GF_CODECID_TMCD = GF_4CC('t','m','c','d'), + + + GF_CODECID_FFMPEG = GF_4CC('F','F','I','D'), + + //fake codec IDs for RTP + GF_CODECID_FAKE_MP2T = GF_4CC('M','P','2','T') +} GF_CodecID; +/*! Gets a textual description for the given codecID +\param codecid target codec ID +\return textual description of the stream +*/ +const char *gf_codecid_name(GF_CodecID codecid); + +/*! Enumerates supported codec format +\param idx 0-based index, to incremented at each call +\param short_name pointer for codec name +\param long_name pointer for codec description +\return codec ID +*/ +GF_CodecID gf_codecid_enum(u32 idx, const char **short_name, const char **long_name); + +/*! Gets the associated streamtype for the given codecID +\param codecid target codec ID +\return stream type if known, GF_STREAM_UNKNOWN otherwise +*/ +u32 gf_codecid_type(GF_CodecID codecid); + +/*! Gets alternate ID of codec if any +\param codecid target codec ID +\return alternate codec ID if known, GF_CODECID_NONE otherwise +*/ +GF_CodecID gf_codecid_alt(GF_CodecID codecid); + +/*! Gets the associated ObjectTypeIndication if any for the given codecID +\param codecid target codec ID +\return ObjectTypeIndication if defined, 0 otherwise +*/ +u8 gf_codecid_oti(GF_CodecID codecid); + +/*! Gets the codecID from a given ObjectTypeIndication +\param stream_type stream type of the stream +\param oti ObjectTypeIndication of the stream +\return the codecID for this OTI +*/ +GF_CodecID gf_codecid_from_oti(u32 stream_type, u32 oti); + +/*! Gets the associated 4CC used by isomedia or RFC6381 +\param codecid target codec ID +\return RFC 4CC of codec, 0 if not mapped/known +*/ +u32 gf_codecid_4cc_type(GF_CodecID codecid); + +/*! Gets the codecid given the associated short name +\param cname target codec short name +\return codecid codec ID +*/ +GF_CodecID gf_codec_parse(const char *cname); + +/*! Gets the raw file ext (one or more, | separated) for the given codecid +\param codecid codec ID +\return returns file extension +*/ +const char *gf_codecid_file_ext(GF_CodecID codecid); + +/*! Gets the raw file mime type for the given codecid +\param codecid codec ID +\return returns file mime type +*/ +const char *gf_codecid_mime(GF_CodecID codecid); + +/*! Gets the codecid from isomedia code point +\param isobmftype isomedia code point +\return codec ID, 0 if not mapped/known +*/ +GF_CodecID gf_codec_id_from_isobmf(u32 isobmftype); /*! - * \brief AFX Object Code +\brief AFX Object Code */ enum { @@ -517,15 +629,21 @@ enum /*!Scalable Complexity*/ GPAC_AFX_SCALABLE_COMPLEXITY = 0x0C, }; +/*! Gets a textual description of an AFX stream type +\param afx_code target stream type descriptor +\return textural description of the AFX stream +*/ +const char *gf_stream_type_afx_name(u8 afx_code); /*channel cfg flags - DECODERS MUST OUTPUT STEREO/MULTICHANNEL IN THIS ORDER*/ + /*! - * \brief Audio Channel Configuration - * - * Audio channel flags for spatialization. +\brief Audio Channel Configuration + +Audio channel flags for spatialization. \note Decoders must output stereo/multichannel audio channels in this order in the decoded audio frame. - */ +*/ enum { /*!Left Audio Channel*/ @@ -537,24 +655,278 @@ enum /*!LFE Audio Channel*/ GF_AUDIO_CH_LFE = (1<<3), /*!Back Left Audio Channel*/ - GF_AUDIO_CH_BACK_LEFT = (1<<4), + GF_AUDIO_CH_SURROUND_LEFT = (1 << 4), /*!Back Right Audio Channel*/ - GF_AUDIO_CH_BACK_RIGHT = (1<<5), - /*!Back Center Audio Channel*/ - GF_AUDIO_CH_BACK_CENTER = (1<<6), + GF_AUDIO_CH_SURROUND_RIGHT = (1 << 5), + /*Between left and center in front Audio Channel*/ + GF_AUDIO_CH_FRONT_CENTER_LEFT = (1 << 6), + /*Between right and center in front Audio Channel*/ + GF_AUDIO_CH_FRONT_CENTER_RIGHT = (1 << 7), /*!Side Left Audio Channel*/ - GF_AUDIO_CH_SIDE_LEFT = (1<<7), + GF_AUDIO_CH_REAR_SURROUND_LEFT = (1<<8), /*!Side Right Audio Channel*/ - GF_AUDIO_CH_SIDE_RIGHT = (1<<8) + GF_AUDIO_CH_REAR_SURROUND_RIGHT = (1<<9), + /*!Back Center Audio Channel*/ + GF_AUDIO_CH_REAR_CENTER = (1 << 10), + /*!Left surround direct Channel*/ + GF_AUDIO_CH_SURROUND_DIRECT_LEFT = (1 << 11), + /*!Right surround direct Channel*/ + GF_AUDIO_CH_SURROUND_DIRECT_RIGHT = (1 << 12), + /*!Left side surround Channel*/ + GF_AUDIO_CH_SIDE_SURROUND_LEFT = (1 << 13), + /*!Right side surround Channel*/ + GF_AUDIO_CH_SIDE_SURROUND_RIGHT = (1 << 14), + /*!Left wide front Channel*/ + GF_AUDIO_CH_WIDE_FRONT_LEFT = (1 << 15), + /*!Right wide front Channel*/ + GF_AUDIO_CH_WIDE_FRONT_RIGHT = (1 << 16), + /*!Left front top Channel*/ + GF_AUDIO_CH_FRONT_TOP_LEFT = (1 << 17), + /*!Right front top Channel*/ + GF_AUDIO_CH_FRONT_TOP_RIGHT = (1 << 18), + /*!Center front top Channel*/ + GF_AUDIO_CH_FRONT_TOP_CENTER = (1 << 19), + /*!Left surround top Channel*/ + GF_AUDIO_CH_SURROUND_TOP_LEFT = (1 << 20), + /*!Right surround top Channel*/ + GF_AUDIO_CH_SURROUND_TOP_RIGHT = (1 << 21), + /*!Center surround top Channel*/ + GF_AUDIO_CH_REAR_CENTER_TOP = (1 << 22), + /*!Left side surround top Channel*/ + GF_AUDIO_CH_SIDE_SURROUND_TOP_LEFT = (1 << 23), + /*!Left side surround top Channel*/ + GF_AUDIO_CH_SIDE_SURROUND_TOP_RIGHT = (1 << 24), + /*!Center surround top Channel*/ + GF_AUDIO_CH_CENTER_SURROUND_TOP = (1 << 25), + /*!LFE 2 Channel*/ + GF_AUDIO_CH_LFE2 = (1 << 26), + /*!Left front bottom Channel*/ + GF_AUDIO_CH_FRONT_BOTTOM_LEFT = (1 << 27), + /*!Right front bottom Channel*/ + GF_AUDIO_CH_FRONT_BOTTOM_RIGHT = (1 << 28), + /*!Center front bottom Channel*/ + GF_AUDIO_CH_FRONT_BOTTOM_CENTER = (1 << 29), + /*!Left surround bottom Channel*/ + GF_AUDIO_CH_SURROUND_BOTTOM_LEFT = (1 << 30), + /*!Right surround bottom Channel*/ + GF_AUDIO_CH_SURROUND_BOTTOM_RIGHT = 0x80000000 //(1 << 31) }; +/*64 bit flags are defined as macro to avoid msvc compil warnings*/ +/*!Left edge of screen Channel*/ +#define GF_AUDIO_CH_SCREEN_EDGE_LEFT 0x2000000000ULL +/*!Right edge of screen Channel*/ +#define GF_AUDIO_CH_SCREEN_EDGE_RIGHT 0x4000000000ULL +/*!left back surround Channel*/ +#define GF_AUDIO_CH_BACK_SURROUND_LEFT 0x20000000000ULL +/*!right back surround Channel*/ +#define GF_AUDIO_CH_BACK_SURROUND_RIGHT 0x40000000000ULL + + +/*! +\brief Audio Sample format + + Audio sample bit format. +*/ +typedef enum +{ + /*! sample = unsigned byte, interleaved channels*/ + GF_AUDIO_FMT_U8 = 1, + /*! sample = signed short Little Endian, interleaved channels*/ + GF_AUDIO_FMT_S16, + /*! sample = signed integer, interleaved channels*/ + GF_AUDIO_FMT_S32, + /*! sample = 1 float, interleaved channels*/ + GF_AUDIO_FMT_FLT, + /*! sample = 1 double, interleaved channels*/ + GF_AUDIO_FMT_DBL, + /*! sample = unsigned byte, planar channels*/ + GF_AUDIO_FMT_U8P, + /*! sample = signed short, planar channels*/ + GF_AUDIO_FMT_S16P, + /*! sample = signed integer, planar channels*/ + GF_AUDIO_FMT_S32P, + /*! sample = 1 float, planar channels*/ + GF_AUDIO_FMT_FLTP, + /*! sample = 1 double, planar channels*/ + GF_AUDIO_FMT_DBLP, + /*! sample = signed integer, interleaved channels*/ + GF_AUDIO_FMT_S24, + /*! sample = signed integer, planar channels*/ + GF_AUDIO_FMT_S24P, +} GF_AudioFormat; + + +/*! enumerates GPAC audio formats +\param af_name name of the audio format +\return audio format code +*/ +GF_AudioFormat gf_audio_fmt_parse(const char *af_name); + +/*! gets name of audio formats +\param afmt audio format code +\return audio format name +*/ +const char *gf_audio_fmt_name(GF_AudioFormat afmt); + +/*! gets short name of audio formats, as used for file extensions +\param afmt audio format code +\return audio format short name +*/ +const char *gf_audio_fmt_sname(GF_AudioFormat afmt); + + +/*! gets the list of all supported audio format names +\return list of supported audio format names +*/ +const char *gf_audio_fmt_all_names(); + +/*! gets the list of all supported audio format names +\return list of supported audio format short names +*/ +const char *gf_audio_fmt_all_shortnames(); + +/*! returns number of bots per sample for the given format +\param afmt desired audio format +\return bit depth of format +*/ +u32 gf_audio_fmt_bit_depth(GF_AudioFormat afmt); + +/*! Check if a given audio format is planar +\param afmt desired audio format +\return GF_TRUE if the format is planar, false otherwise +*/ +Bool gf_audio_fmt_is_planar(GF_AudioFormat afmt); + +/*! Returns audio format for raw audio ISOBMFF sample description type +\param msubtype ISOBMFF sample description type +\return the associated audio format of 0 if not known + */ +GF_AudioFormat gf_audio_fmt_from_isobmf(u32 msubtype); + +/*! enumerates audio formats +\param idx index of the audio format, 0-based +\param name name of the audio format +\param fileext file extension of the pixel format +\param desc audio format description +\return audio format or 0 if no more audio formats are availble +*/ +GF_AudioFormat gf_audio_fmt_enum(u32 *idx, const char **name, const char **fileext, const char **desc); + +/*! get CICP layout code point from audio configuration +\param nb_chan number of channels +\param nb_surr number of surround channels +\param nb_lfe number of LFE channels +\return CICP layout code point format or 0 if unknown +*/ +u32 gf_audio_fmt_get_cicp_layout(u32 nb_chan, u32 nb_surr, u32 nb_lfe); + +/*! get channel layout mask from CICP layout +\param cicp_layout channel layout CICP code point +\return layout mask or 0 if unknown +*/ +u64 gf_audio_fmt_get_layout_from_cicp(u32 cicp_layout); + +/*! get CICP layout name +\param cicp_layout channel layout CICP code point +\return name of layout of "unknown" if unknown +*/ +const char *gf_audio_fmt_get_layout_name_from_cicp(u32 cicp_layout); + +/*! get CICP layout value from channel layout mask +\param chan_layout channel layout mask +\return CICP code point or 255 if unknown +*/ +u32 gf_audio_fmt_get_cicp_from_layout(u64 chan_layout); + +/*! Color primaries as defined by ISO/IEC 23001-8 / 23091-2 + */ +typedef enum +{ + GF_COLOR_PRIM_RESERVED0 = 0, + GF_COLOR_PRIM_BT709 = 1, + GF_COLOR_PRIM_UNSPECIFIED = 2, + GF_COLOR_PRIM_RESERVED = 3, + GF_COLOR_PRIM_BT470M = 4, + GF_COLOR_PRIM_BT470BG = 5, + GF_COLOR_PRIM_SMPTE170M = 6, + GF_COLOR_PRIM_SMPTE240M = 7, + GF_COLOR_PRIM_FILM = 8, + GF_COLOR_PRIM_BT2020 = 9, + GF_COLOR_PRIM_SMPTE428 = 10, + GF_COLOR_PRIM_SMPTE431 = 11, + GF_COLOR_PRIM_SMPTE432 = 12, + GF_COLOR_PRIM_EBU3213 = 22 +} GF_ColorPrimaries; + +/*! Color Transfer Characteristics as defined by ISO/IEC 23001-8 / 23091-2 +*/ +typedef enum +{ + GF_COLOR_TRC_RESERVED0 = 0, + GF_COLOR_TRC_BT709 = 1, + GF_COLOR_TRC_UNSPECIFIED = 2, + GF_COLOR_TRC_RESERVED = 3, + GF_COLOR_TRC_GAMMA22 = 4, + GF_COLOR_TRC_GAMMA28 = 5, + GF_COLOR_TRC_SMPTE170M = 6, + GF_COLOR_TRC_SMPTE240M = 7, + GF_COLOR_TRC_LINEAR = 8, + GF_COLOR_TRC_LOG = 9, + GF_COLOR_TRC_LOG_SQRT = 10, + GF_COLOR_TRC_IEC61966_2_4 = 11, + GF_COLOR_TRC_BT1361_ECG = 12, + GF_COLOR_TRC_IEC61966_2_1 = 13, + GF_COLOR_TRC_BT2020_10 = 14, + GF_COLOR_TRC_BT2020_12 = 15, + GF_COLOR_TRC_SMPTE2084 = 16, + GF_COLOR_TRC_SMPTE428 = 17, + GF_COLOR_TRC_ARIB_STD_B67 = 18 +} GF_ColorTransferCharacteristic; +/*! MatrixCoefficients as defined by ISO/IEC 23001-8 / 23091-2 +*/ +typedef enum +{ + GF_COLOR_MX_RGB = 0, + GF_COLOR_MX_BT709 = 1, + GF_COLOR_MX_UNSPECIFIED = 2, + GF_COLOR_MX_RESERVED = 3, + GF_COLOR_MX_FCC47 = 4, + GF_COLOR_MX_BT470BG = 5, + GF_COLOR_MX_SMPTE170M = 6, + GF_COLOR_MX_SMPTE240M = 7, + GF_COLOR_MX_YCGCO = 8, + GF_COLOR_MX_BT2020_NCL = 9, + GF_COLOR_MX_BT2020_CL = 10, + GF_COLOR_MX_SMPTE2085 = 11, +} GF_ColorMatrixCoefficients; + +/*! Chroma location values, semantics from CoreVideo - direct match of values to FFmpeg*/ +typedef enum { + /*! Chroma location is not known*/ + GF_CHROMALOC_UNKNOWN=0, + /*! Chroma sample is horizontally co-sited with the left column of luma samples, but centered vertically (MPEG-2/4 4:2:0, H.264 default for 4:2:0)*/ + GF_CHROMALOC_LEFT, + /*! The chroma sample is fully centered ( MPEG-1 4:2:0, JPEG 4:2:0, H.263 4:2:0)*/ + GF_CHROMALOC_CENTER, + /*! The chroma sample is co-sited with the top-left luma sample (ITU-R 601, SMPTE 274M 296M S314M(DV 4:1:1), mpeg2 4:2:2)*/ + GF_CHROMALOC_TOPLEFT, + /*! The chroma sample is horizontally centered, but is co-sited with the top row of luma samples*/ + GF_CHROMALOC_TOP, + /*! The chroma sample is co-sited with the bottom-left luma sample*/ + GF_CHROMALOC_BOTTOMLEFT, + /*! The chroma sample is horizontally centered, but is co-sited with the bottom row of luma samples*/ + GF_CHROMALOC_BOTTOM, + /*! The Cr and Cb samples are alternatingly co-sited with the left luma samples of the same field */ + GF_CHROMALOC_DV420, +} GF_ChromaLocation; /*DIMS unit flags */ /*! - * \brief DIMS Unit header flags - * - * DIMS Unit header flags as 3GPP TS 26.142. +\brief DIMS Unit header flags + +DIMS Unit header flags as 3GPP TS 26.142. */ enum { @@ -573,9 +945,7 @@ enum }; -/*! - * AVC NAL unit types - */ +/*! AVC NAL unit types */ enum { /*! Non IDR AVC slice*/ @@ -613,13 +983,15 @@ enum /*! SVC slice*/ GF_AVC_NALU_SVC_SLICE = 20, /*! View and dependency representation delimiter */ - GF_AVC_NALU_VDRD = 24 + GF_AVC_NALU_VDRD = 24, + /*! Dolby Vision RPU */ + GF_AVC_NALU_DV_RPU = 28, + /*! Dolby Vision EL */ + GF_AVC_NALU_DV_EL = 30, }; -/*! - * AVC slice types - */ +/*! AVC slice types */ enum { /*! P slice*/ @@ -643,10 +1015,8 @@ enum /*! Type2 SI slice*/ GF_AVC_TYPE2_SI = 9 }; - -/*! - HEVC NAL unit types - */ + +/*! HEVC NAL unit types */ enum { /*! Trail N HEVC slice*/ @@ -669,6 +1039,14 @@ enum GF_HEVC_NALU_SLICE_RASL_N = 8, /*! RASL R HEVC slice*/ GF_HEVC_NALU_SLICE_RASL_R = 9, + /*! Reserved non-IRAP SLNR VCL NAL unit types*/ + GF_HEVC_NALU_SLICE_RSV_VCL_N10 = 10, + GF_HEVC_NALU_SLICE_RSV_VCL_N12 = 12, + GF_HEVC_NALU_SLICE_RSV_VCL_N14 = 14, + /*! Reserved non-IRAP sub-layer reference VCL NAL unit types*/ + GF_HEVC_NALU_SLICE_RSV_VCL_R11 = 11, + GF_HEVC_NALU_SLICE_RSV_VCL_R13 = 13, + GF_HEVC_NALU_SLICE_RSV_VCL_R15 = 15, /*! BLA LP HEVC slice*/ GF_HEVC_NALU_SLICE_BLA_W_LP = 16, /*! BLA DLP HEVC slice*/ @@ -699,20 +1077,24 @@ enum GF_HEVC_NALU_SEI_PREFIX = 39, /*! suffix SEI message*/ GF_HEVC_NALU_SEI_SUFFIX = 40, + /*! Dolby Vision RPU */ + GF_HEVC_NALU_DV_RPU = 62, + /*! Dolby Vision EL */ + GF_HEVC_NALU_DV_EL = 63 }; - + /*! Number of defined QCELP rate sizes*/ static const unsigned int GF_QCELP_RATE_TO_SIZE_NB = 7; /*! QCELP rate sizes - note that these sizes INCLUDE the rate_type header byte*/ static const unsigned int GF_QCELP_RATE_TO_SIZE [] = {0, 1, 1, 4, 2, 8, 3, 17, 4, 35, 5, 8, 14, 1}; - + /*! Number of defined EVRC rate sizes*/ static const unsigned int GF_SMV_EVRC_RATE_TO_SIZE_NB = 6; /*! EVRC rate sizes - note that these sizes INCLUDE the rate_type header byte*/ static const unsigned int GF_SMV_EVRC_RATE_TO_SIZE [] = {0, 1, 1, 3, 2, 6, 3, 11, 4, 23, 5, 1}; - + /*! AMR frame sizes*/ static const unsigned int GF_AMR_FRAME_SIZE[16] = { 12, 13, 15, 17, 19, 20, 26, 31, 5, 0, 0, 0, 0, 0, 0, 0 }; /*! AMR WB frame sizes*/ @@ -722,6 +1104,196 @@ static const unsigned int GF_AMR_WB_FRAME_SIZE[16] = { 17, 23, 32, 36, 40, 46, 5 /*! out-of-band sample description index for 3GPP (128 and 255 reserved in RFC)*/ #define GF_RTP_TX3G_SIDX_OFFSET 129 + +/*! RFC6381 codec name max length*/ +#define RFC6381_CODEC_NAME_SIZE_MAX 100 + + +/*! ID3v2 tags*/ +typedef enum { + GF_ID3V2_FRAME_AENC = GF_4CC('A','E','N','C'), + GF_ID3V2_FRAME_APIC = GF_4CC('A','P','I','C'), + GF_ID3V2_FRAME_COMM = GF_4CC('C','O','M','M'), + GF_ID3V2_FRAME_COMR = GF_4CC('C','O','M','R'), + GF_ID3V2_FRAME_ENCR = GF_4CC('E','N','C','R'), + GF_ID3V2_FRAME_EQUA = GF_4CC('E','Q','U','A'), + GF_ID3V2_FRAME_ETCO = GF_4CC('E','T','C','O'), + GF_ID3V2_FRAME_GEOB = GF_4CC('G','E','O','B'), + GF_ID3V2_FRAME_GRID = GF_4CC('G','R','I','D'), + GF_ID3V2_FRAME_IPLS = GF_4CC('I','P','L','S'), + GF_ID3V2_FRAME_LINK = GF_4CC('L','I','N','K'), + GF_ID3V2_FRAME_MCDI = GF_4CC('M','C','D','I'), + GF_ID3V2_FRAME_MLLT = GF_4CC('M','L','L','T'), + GF_ID3V2_FRAME_OWNE = GF_4CC('O','W','N','E'), + GF_ID3V2_FRAME_PRIV = GF_4CC('P','R','I','V'), + GF_ID3V2_FRAME_PCNT = GF_4CC('P','C','N','T'), + GF_ID3V2_FRAME_POPM = GF_4CC('P','O','P','M'), + GF_ID3V2_FRAME_POSS = GF_4CC('P','O','S','S'), + GF_ID3V2_FRAME_RBUF = GF_4CC('R','B','U','F'), + GF_ID3V2_FRAME_RVAD = GF_4CC('R','V','A','D'), + GF_ID3V2_FRAME_RVRB = GF_4CC('R','V','R','B'), + GF_ID3V2_FRAME_SYLT = GF_4CC('S','Y','L','T'), + GF_ID3V2_FRAME_SYTC = GF_4CC('S','Y','T','C'), + GF_ID3V2_FRAME_TALB = GF_4CC('T','A','L','B'), + GF_ID3V2_FRAME_TBPM = GF_4CC('T','B','P','M'), + GF_ID3V2_FRAME_TCMP = GF_4CC('T','C','M','P'), + GF_ID3V2_FRAME_TCOM = GF_4CC('T','C','O','M'), + GF_ID3V2_FRAME_TCON = GF_4CC('T','C','O','N'), + GF_ID3V2_FRAME_TCOP = GF_4CC('T','C','O','P'), + GF_ID3V2_FRAME_TDAT = GF_4CC('T','D','A','T'), + GF_ID3V2_FRAME_TDLY = GF_4CC('T','D','L','Y'), + GF_ID3V2_FRAME_TDRC = GF_4CC('T','D','R','C'), + GF_ID3V2_FRAME_TENC = GF_4CC('T','E','N','C'), + GF_ID3V2_FRAME_TEXT = GF_4CC('T','E','X','T'), + GF_ID3V2_FRAME_TFLT = GF_4CC('T','F','L','T'), + GF_ID3V2_FRAME_TIME = GF_4CC('T','I','M','E'), + GF_ID3V2_FRAME_TIT1 = GF_4CC('T','I','T','1'), + GF_ID3V2_FRAME_TIT2 = GF_4CC('T','I','T','2'), + GF_ID3V2_FRAME_TIT3 = GF_4CC('T','I','T','3'), + GF_ID3V2_FRAME_TKEY = GF_4CC('T','K','E','Y'), + GF_ID3V2_FRAME_TLAN = GF_4CC('T','L','A','N'), + GF_ID3V2_FRAME_TLEN = GF_4CC('T','L','E','N'), + GF_ID3V2_FRAME_TMED = GF_4CC('T','M','E','D'), + GF_ID3V2_FRAME_TOAL = GF_4CC('T','O','A','L'), + GF_ID3V2_FRAME_TOFN = GF_4CC('T','O','F','N'), + GF_ID3V2_FRAME_TOLY = GF_4CC('T','O','L','Y'), + GF_ID3V2_FRAME_TOPE = GF_4CC('T','O','P','E'), + GF_ID3V2_FRAME_TORY = GF_4CC('T','O','R','Y'), + GF_ID3V2_FRAME_TOWN = GF_4CC('T','O','W','N'), + GF_ID3V2_FRAME_TPE1 = GF_4CC('T','P','E','1'), + GF_ID3V2_FRAME_TPE2 = GF_4CC('T','P','E','2'), + GF_ID3V2_FRAME_TPE3 = GF_4CC('T','P','E','3'), + GF_ID3V2_FRAME_TPE4 = GF_4CC('T','P','E','4'), + GF_ID3V2_FRAME_TPOS = GF_4CC('T','P','E','5'), + GF_ID3V2_FRAME_TPUB = GF_4CC('T','P','U','B'), + GF_ID3V2_FRAME_TRCK = GF_4CC('T','R','C','K'), + GF_ID3V2_FRAME_TRDA = GF_4CC('T','R','D','A'), + GF_ID3V2_FRAME_TRSN = GF_4CC('T','R','S','N'), + GF_ID3V2_FRAME_TRSO = GF_4CC('T','R','S','O'), + GF_ID3V2_FRAME_TSIZ = GF_4CC('T','S','I','Z'), + GF_ID3V2_FRAME_TSRC = GF_4CC('T','S','R','C'), + GF_ID3V2_FRAME_TSSE = GF_4CC('T','S','S','E'), + GF_ID3V2_FRAME_TYER = GF_4CC('T','Y','E','R'), + GF_ID3V2_FRAME_TXXX = GF_4CC('T','X','X','X'), + GF_ID3V2_FRAME_UFID = GF_4CC('U','F','I','D'), + GF_ID3V2_FRAME_USER = GF_4CC('U','S','E','R'), + GF_ID3V2_FRAME_USLT = GF_4CC('U','S','L','T'), + GF_ID3V2_FRAME_WCOM = GF_4CC('W','C','O','M'), + GF_ID3V2_FRAME_WCOP = GF_4CC('W','C','O','P'), + GF_ID3V2_FRAME_WOAF = GF_4CC('W','O','A','F'), + GF_ID3V2_FRAME_WOAR = GF_4CC('W','O','A','R'), + GF_ID3V2_FRAME_WOAS = GF_4CC('W','O','A','S'), + GF_ID3V2_FRAME_WORS = GF_4CC('W','O','R','S'), + GF_ID3V2_FRAME_WPAY = GF_4CC('W','P','A','Y'), + GF_ID3V2_FRAME_WPUB = GF_4CC('W','P','U','B'), + GF_ID3V2_FRAME_WXXX = GF_4CC('W','X','X','X'), +} GF_ID3v2FrameType; + +/*! tag base types*/ +enum +{ + /*! tag is a string*/ + GF_ITAG_STR=0, + /*! tag is an int*/ + GF_ITAG_INT, + /*! tag is a fraction*/ + GF_ITAG_FRAC, + /*! tag is a boolean*/ + GF_ITAG_BOOL, + /*! tag is a string but name is matched as substring*/ + GF_ITAG_SUBSTR, +}; +/*! finds a tag by its ID3 value + \param id3tag ID3 tag value + \return corresponding tag index, -1 if not found +*/ +s32 gf_itags_find_by_id3tag(u32 id3tag); + +/*! finds a tag by its itunes value + \param itag itunes tag value + \return corresponding tag index, -1 if not found +*/ +s32 gf_itags_find_by_itag(u32 itag); + +/*! finds a tag by its name + \param tag_name tag name + \return corresponding tag index, -1 if not found +*/ +s32 gf_itags_find_by_name(const char *tag_name); + +/*! gets tag associated type + \param tag_idx tag index + \return corresponding tag type, 0 if error +*/ +u32 gf_itags_get_type(u32 tag_idx); + +/*! gets tag associated name + \param tag_idx tag index + \return corresponding tag name, NULL if error +*/ +const char *gf_itags_get_name(u32 tag_idx); + +/*! gets tag associated itunes tag + \param tag_idx tag index + \return corresponding itunes tag, 0 if error +*/ +u32 gf_itags_get_itag(u32 tag_idx); + +/*! gets tag associated id3 tag + \param tag_idx tag index + \return corresponding id3 tag, 0 if error +*/ +u32 gf_itags_get_id3tag(u32 tag_idx); + +/*! enumerates tags + \param tag_idx tag index, increased at each call + \param itag set to itunes tag value - may be NULL + \param id3tag set to ID3 tag value - may be NULL + \param type set to tag type - may be NULL + \return tag name, NULL if error +*/ +const char *gf_itags_enum_tags(u32 *tag_idx, u32 *itag, u32 *id3tag, u32 *type); + +/*! gets genre name by genre tag +\param tag genre tag (from 1 to max number of ID3 genres) +\return genre name, NULL if not found*/ +const char *gf_id3_get_genre(u32 tag); + +/*! gets genre tag by genre name +\param name genre name +\return genre tag, 0 if not found*/ +u32 gf_id3_get_genre_tag(const char *name); + + +/*! meta types from box_code_meta.c - fileimport.c */ +enum { + + GF_META_ITEM_TYPE_MIME = GF_4CC('m', 'i', 'm', 'e'), + GF_META_ITEM_TYPE_URI = GF_4CC('u', 'r', 'i', ' '), + GF_META_ITEM_TYPE_PICT = GF_4CC('p', 'i', 'c', 't'), + + GF_META_TYPE_SVG = GF_4CC('s','v','g',' '), + GF_META_TYPE_SVGZ = GF_4CC('s','v','g','z'), + GF_META_TYPE_SMIL = GF_4CC('s','m','i','l'), + GF_META_TYPE_SMLZ = GF_4CC('s','m','l','z'), + GF_META_TYPE_X3D = GF_4CC('x','3','d',' '), + GF_META_TYPE_X3DZ = GF_4CC('x','3','d','z'), + GF_META_TYPE_XMTA = GF_4CC('x','m','t','a'), + GF_META_TYPE_XMTZ = GF_4CC('x','m','t','z'), + + GF_META_TYPE_RVCI = GF_4CC('r','v','c','i'), + +}; + + +/*! meta types from box_code_meta.c - fileimport.c */ +enum { + + GF_S4CC_MPEG4 = GF_4CC('m', 'p', '4', 's'), + GF_S4CC_LASER = GF_4CC('l', 's', 'r', '1'), +}; + + /*! @} */ #ifdef __cplusplus diff --git a/include/gpac/crypt.h b/include/gpac/crypt.h index 0892499..0fcb3d3 100644 --- a/include/gpac/crypt.h +++ b/include/gpac/crypt.h @@ -1,52 +1,33 @@ /* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / Crypto Tools sub-project - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ +* GPAC - Multimedia Framework C SDK +* +* Authors: Jean Le Feuvre +* Copyright (c) Telecom ParisTech 2000-2019 +* All rights reserved +* +* This file is part of GPAC / Crypto Tools sub-project +* +* GPAC is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* GPAC is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; see the file COPYING. If not, write to +* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +* +*/ /* - The GPAC crypto lib is a simplified version of libmcrypt - not all algos are included. - Doc here is man mcrypt - Original libmcrypt license +The GPAC crypto lib uses either openSSL 1.x or tiny-AES (https://github.com/kokke/tiny-AES-c) when not available +The crypto lib only supports AES 128 bits in CBC and CTR modes */ -/* - * Copyright (C) 1998,1999,2000 Nikos Mavroyanopoulos - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Library General Public License as published - * by the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ #ifndef _GF_CRYPT_H_ #define _GF_CRYPT_H_ @@ -57,119 +38,109 @@ extern "C" { #endif /*! - * \file - * \brief Utility tools for encryption and decryption. - */ - +\file +\brief Utility tools for encryption and decryption. +*/ + /*! - * \addtogroup crypt_grp Cryptography - * \ingroup media_grp - * \brief Utility tools for encryption and decryption. - * - *This section documents the encryption and decryption routines used by GPAC, mostly AES 128 in CBC or CTR modes. - * @{ - */ +\addtogroup crypt_grp Cryptography +\ingroup media_grp +\brief Utility tools for encryption and decryption. -#include +This section documents the encryption and decryption routines used by GPAC, mostly AES 128 in CBC or CTR modes. -#ifndef GPAC_DISABLE_MCRYPT +@{ +*/ +#include -/*max number of possible key sizes for all supported modes*/ -#define MAX_KEY_SIZES 4 +/*! cryptographic context object*/ +typedef struct _gf_crypt_context GF_Crypt; -/*crypto lib handler*/ -typedef struct _tag_crypt_stream GF_Crypt; +/*! Key size in bytes for AES 128*/ +#define GF_AES_128_KEYSIZE 16 -/*supported modes (case insensitive): "CBC", "CFB", "CTR", "ECB", "nCFB", "nOFB", "OFB", "STREAM"*/ -/*supported algos (case insensitive): - "AES-128" == "Rijndael-128" - "AES-192" == "Rijndael-192" - "AES-256" == "Rijndael-256" - "DES", "3DES" -*/ +/*! Chaining mode of AES*/ +typedef enum { + /*! CBC chaining mode*/ + GF_CBC = 0, + /*! CTR chaining mode*/ + GF_CTR = 1 +} GF_CRYPTO_MODE; +/*! Algorithm mode to use*/ +typedef enum { + /*! AES 128 bit encryption*/ + GF_AES_128 = 0 +} GF_CRYPTO_ALGO; -/*opens crypto context - algo and mode SHALL NOT be NULL*/ -GF_Crypt *gf_crypt_open(const char *algorithm, const char *mode); -/*close crypto context*/ -void gf_crypt_close(GF_Crypt *gfc); -/* sets the state of the algorithm. Can be used only with block algorithms and certain modes like CBC, CFB etc. -It is usefully if you want to restart or start a different encryption quickly. +/*! opens crypto context +\param algorithm the algorithm to use +\param mode the chaining mode of the algorithm +\return a new crypto context */ -GF_Err gf_crypt_set_state(GF_Crypt *gfc, const void *iv, int size); -/*gets the state of the algorithm. Can be used only certain modes and algorithms. -The size will hold the size of the state and the state must have enough bytes to hold it. +GF_Crypt *gf_crypt_open(GF_CRYPTO_ALGO algorithm, GF_CRYPTO_MODE mode); +/*! destroys a crypto context +\param gfc the target crytpo context */ -GF_Err gf_crypt_get_state(GF_Crypt *gfc, void *iv, int *size); -/*Returns 1 if the algorithm is a block algorithm or 0 if it is a stream algorithm.*/ -Bool gf_crypt_is_block_algorithm(GF_Crypt *gfc); -/*Returns 1 if the mode is for use with block algorithms, otherwise it returns 0.*/ -Bool gf_crypt_is_block_algorithm_mode(GF_Crypt *gfc); -/*Returns 1 if the mode outputs blocks of bytes or 0 if it outputs bytes. (eg. 1 for cbc and ecb, and 0 for cfb and stream)*/ -Bool gf_crypt_is_block_mode(GF_Crypt *gfc); -/*Returns the block size of the algorithm specified by the encryption descriptor in bytes.*/ -u32 gf_crypt_get_block_size(GF_Crypt *gfc); -/*Returns the maximum supported key size of the algorithm specified by the encryption descriptor in bytes.*/ -u32 gf_crypt_get_key_size(GF_Crypt *gfc); -/*Returns the number of supported key sizes. -@keys: array of at least MAX_KEY_SIZES size - will hold the supported sizes*/ -u32 gf_crypt_get_supported_key_sizes(GF_Crypt *gfc, u32 *key_sizes); -/*Returns size (in bytes) of the IV of the algorithm specified for the context. -If it is '0' then the IV is ignored in that algorithm. -IV is used in CBC, CFB, OFB modes, and in some algorithms in STREAM mode. +void gf_crypt_close(GF_Crypt *gfc); + +/*! initializes all buffers for the specified context +After calling this function you can use the crypto context for encryption or decryption (not both). +\param gfc the target crytpo context +\param key the key to use. MUST be 16 bytes long +\param iv the seed/initialization vector to use. It needs to be random and unique (but not secret). The same IV must be used +for encryption/decryption. MUST be 16 bytes long +\return error if any */ -u32 gf_crypt_get_iv_size(GF_Crypt *gfc); -/*Returns 1 if the mode needs an IV, 0 otherwise. -Some 'stream' algorithms may need an IV even if the mode itself does not need an IV. +GF_Err gf_crypt_init(GF_Crypt *gfc, void *key, const void *iv); + +/*! changes key, does not touch IV. This is used for key rolling +\param gfc the target crytpo context +\param key the new key to use. MUST be 16 bytes long +\return error if any */ -Bool gf_crypt_mode_has_iv(GF_Crypt *gfc); +GF_Err gf_crypt_set_key(GF_Crypt *gfc, void *key); -/*guess what these do...*/ -const char *gf_crypt_get_algorithm_name(GF_Crypt *gfc); -u32 gf_crypt_get_algorithm_version(GF_Crypt *gfc); -const char *gf_crypt_get_mode_name(GF_Crypt *gfc); -u32 gf_crypt_get_mode_version(GF_Crypt *gfc); +/*! sets the IV for the algorithm. Used for ISMA and CENC CTR. +\param gfc the target crytpo context +\param iv the new eed/initialization vector to use +\param size the size of the IV. Must be 16 or 17 for AES. If 17 (AES CTR only), the first byte shall contain the counter position +\return error if any +*/ +GF_Err gf_crypt_set_IV(GF_Crypt *gfc, const void *iv, u32 size); + +/*! gets the IV of the algorithm. +The size will hold the size of the state and the state must have enough bytes to hold it (17 is enough for AES 128). +In CTR mode, the first byte will be set to the counter value (number of bytes consummed in last block), or 0 if all bytes were consummed +\param gfc the target crytpo context +\param iv filled with the current IV +\param size will be set to the IV size (16 for AES CBC? 17 for AES CTR) +\return error if any +*/ +GF_Err gf_crypt_get_IV(GF_Crypt *gfc, void *iv, u32 *size); +/*! encrypts a payload. The encryption is done inplace (plaintext is replaced by the ciphertext). + The buffer size should be k*algorithms_block_size if used in a mode which operated in blocks (CBC) or whatever when used in CTR which operate in streams. -/* -This function initializes all buffers for the specified context -@Lenofkey: key size in BYTES - maximum value of lenofkey should be the one obtained by -calling gf_crypt_get_key_size() and every value smaller than this is legal. -@IV: usually size of the algorithms block size - get it by calling gf_crypt_get_iv_size(). - IV is ignored in ECB. IV MUST exist in CFB, CBC, STREAM, nOFB and OFB modes. - It needs to be random and unique (but not secret). The same IV must be used - for encryption/decryption. -After calling this function you can use the descriptor for encryption or decryption (not both). +\param gfc the target crytpo context +\param plaintext the clear buffer +\param size the size of the clear buffer +\return error if any */ -GF_Err gf_crypt_init(GF_Crypt *gfc, void *key, u32 lenofkey, const void *IV); -/*releases context buffers - you may call gf_crypt_init after that, or gf_crypt_close*/ -void gf_crypt_deinit(GF_Crypt *gfc); -/*changes key and IV*/ -GF_Err gf_crypt_set_key(GF_Crypt *gfc, void *key, u32 keysize, const void *iv); +GF_Err gf_crypt_encrypt(GF_Crypt *gfc, void *plaintext, u32 size); -/* -main encryption function. -@Plaintext, @len: plaintext to encrypt - len should be k*algorithms_block_size if used in a mode -which operated in blocks (cbc, ecb, nofb), or whatever when used in cfb or ofb which operate in streams. -The plaintext is replaced by the ciphertext. +/*! decrypts a payload. The decryption is done inplace (ciphertext is replaced by the plaintext). + The buffer size should be k*algorithms_block_size if used in a mode which operated in blocks (CBC) or whatever when used in CTR which operate in streams. + +\param gfc the target crytpo context +\param ciphertext the encrypted buffer +\param size the size of the encrypted buffer +\return error if any */ -GF_Err gf_crypt_encrypt(GF_Crypt *gfc, void *plaintext, int len); -/*decryption function. It is almost the same with gf_crypt_generic.*/ -GF_Err gf_crypt_decrypt(GF_Crypt *gfc, void *ciphertext, int len); - -/*various queries on both modes and algo*/ -u32 gf_crypt_str_get_algorithm_version(const char *algorithm); -u32 gf_crypt_str_get_mode_version(const char *mode); -Bool gf_crypt_str_is_block_algorithm(const char *algorithm); -Bool gf_crypt_str_is_block_algorithm_mode(const char *algorithm); -Bool gf_crypt_str_is_block_mode(const char *mode); -u32 gf_crypt_str_module_get_algo_block_size(const char *algorithm); -u32 gf_crypt_str_module_get_algo_key_size(const char *algorithm); -u32 gf_crypt_str_get_algo_supported_key_sizes(const char *algorithm, int *keys); - -#endif /*GPAC_DISABLE_MCRYPT*/ +GF_Err gf_crypt_decrypt(GF_Crypt *gfc, void *ciphertext, u32 size); + /*! @} */ @@ -179,4 +150,3 @@ u32 gf_crypt_str_get_algo_supported_key_sizes(const char *algorithm, int *keys); #endif #endif /*_GF_CRYPT_H_*/ - diff --git a/include/gpac/crypt_tools.h b/include/gpac/crypt_tools.h new file mode 100644 index 0000000..68f73f8 --- /dev/null +++ b/include/gpac/crypt_tools.h @@ -0,0 +1,251 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / Media Tools sub-project + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _GF_CRYPT_TOOLS_H_ +#define _GF_CRYPT_TOOLS_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file +\brief Utility tools for ISMA and Common Encryption. +*/ + +/*! +\addtogroup crypt_grp +\ingroup media_grp +\brief Utility tools for ISMA and Common Encryption. + +This section documents the helper tools (mostly crypt info file) used in ISMA and CENC encryption. + +@{ + */ + +#include + +/*! Supported encryption scheme types */ +enum +{ + /*! ISMA E&A encryption*/ + GF_CRYPT_TYPE_ISMA = GF_4CC( 'i', 'A', 'E', 'C' ), + /*! OMA DRM encryption*/ + GF_CRYPT_TYPE_OMA = GF_4CC( 'o', 'd', 'k', 'm' ), + /*! CENC CTR-128 encryption*/ + GF_CRYPT_TYPE_CENC = GF_4CC('c','e','n','c'), + /*! CENC CBC-128 encryption*/ + GF_CRYPT_TYPE_CBC1 = GF_4CC('c','b','c','1'), + /*! CENC CTR-128 pattern encryption*/ + GF_CRYPT_TYPE_CENS = GF_4CC('c','e','n','s'), + /*! CENC CBC-128 pattern encryption*/ + GF_CRYPT_TYPE_CBCS = GF_4CC('c','b','c','s'), + /*! Adobe CBC-128 encryption*/ + GF_CRYPT_TYPE_ADOBE = GF_4CC('a','d','k','m'), + /*! PIFF CTR-128 encryption*/ + GF_CRYPT_TYPE_PIFF = GF_4CC('p','i','f','f'), +}; + +/*! Selective encryption modes */ +enum +{ + /*! no selective encryption*/ + GF_CRYPT_SELENC_NONE = 0, + /*! only encrypts RAP samples*/ + GF_CRYPT_SELENC_RAP, + /*! only encrypts non-RAP samples*/ + GF_CRYPT_SELENC_NON_RAP, + /*! selective encryption of random samples*/ + GF_CRYPT_SELENC_RAND, + /*! selective encryption of a random sample in given range*/ + GF_CRYPT_SELENC_RAND_RANGE, + /*! selective encryption of first sample in given range*/ + GF_CRYPT_SELENC_RANGE, + /*! encryption of all samples but the preview range*/ + GF_CRYPT_SELENC_PREVIEW, + /*!no encryption of samples, signaled with sample group*/ + GF_CRYPT_SELENC_CLEAR, + /*!no encryption of samples, signaled without sample group but bytesOfEncrypted = 0*/ + GF_CRYPT_SELENC_CLEAR_FORCED, +}; + +/*! Crypto information for one media stream*/ +typedef struct +{ + /*! scheme type used for encryptio of the track*/ + u32 scheme_type; + /*! ID of track / PID / ... to be encrypted*/ + u32 trackID; + /*! Initialization vector of the first sample/AU in track/media for CENC. For IMA/OMA, the first 8 bytes contain the salt data.*/ + unsigned char first_IV[16]; + /*! URI of key management system / rightsIssuerURL*/ + char *KMS_URI; + /*! Scheme URI or contentID for OMA*/ + char *Scheme_URI; + /*! selective encryption type*/ + u32 sel_enc_type; + /*! for OMA, sets preview range in samples. Otherwise sets encryption AU frequency (encrypts 1 AU every sel_enc_range ones)*/ + u32 sel_enc_range; + /*! IPMP signaling: 0: none, 1: IPMP, 2: IPMPX + when IPMP signaling is enabled, the OD stream will be updated with IPMP Update commands + THIS IS DEPRECATED IN GPAC + */ + u32 ipmp_type; + /*! if not set and IPMP enabled, defaults to TrackID + THIS IS DEPRECATED IN GPAC + */ + u32 ipmp_desc_id; + /*! type of box where sample auxiliary informations is saved, either "senc" or "PSEC" (PIFF)*/ + u32 sai_saved_box_type; + + /*! OMA encryption type: 0: none, 1: AES CBC, 2: AES CTR*/ + u8 encryption; + /*! OMA textual headers*/ + char *TextualHeaders; + /*! OMA transaction ID*/ + char TransactionID[17]; + + /*! CENC extensions - TODO, we could extend the support to allow per key patterns and selective encryption modes + and also add support for multiple keys in ISMA ?*/ + /*! default encryption state for samples*/ + u32 IsEncrypted; + /*! size of init vector: 0, 8 or 16*/ + u8 IV_size; + /*! number of KEY IDs and Keys defined*/ + u32 KID_count; + /*! KEY IDs defined*/ + bin128 *KIDs; + /*! keys defined*/ + bin128 *keys; + /*! default key index to use*/ + u32 defaultKeyIdx; + /*! roll period of keys (change keys every keyRoll AUs)*/ + u32 keyRoll; + /*! number of bytes to leave in the clear for non NAL-based tracks. Only used in cbcs mode*/ + u32 clear_bytes; + /*! CENS/CBCS pattern */ + u8 crypt_byte_block, skip_byte_block; + /*! cponstant IV size */ + u8 constant_IV_size; + /*! constant IV */ + unsigned char constant_IV[16]; + + /* ! for avc1 ctr CENC edition 1 */ + Bool allow_encrypted_slice_header; + /*! force cenc and cbc1: 0: default, 1: no block alignment of encrypted data, 2: always block align even if producing non encrypted samples*/ + u32 block_align; + + /*0: same stsd for clear samples + 1: dedicated stsd entry for clear samples, placed before the crypted entry in stsd, + 2: dedicated stsd entry for clear samples, placed after the crypted entry in stsd, + */ + u32 force_clear_stsd_idx; + + /*! adobe metadata in base64*/ + char *metadata; + + /*! force using type set in XML rather than type indicated in file when decrypting*/ + Bool force_type; +} GF_TrackCryptInfo; + +/*! Crypto information*/ +typedef struct +{ + /*! list of track infos*/ + GF_List *tcis; + /*! global for all tracks unless overridden*/ + u32 def_crypt_type; + /*! indicates a common key is used*/ + Bool has_common_key; + /*! intern to parser*/ + Bool in_text_header; +} GF_CryptInfo; + +/*! loads a given crypto configuration file. Full doc is available at https://gpac.wp.imt.fr/mp4box/encryption/common-encryption/ +\param file name of the crypt XML file +\param out_err set to return error +\return the crypt info +*/ +GF_CryptInfo *gf_crypt_info_load(const char *file, GF_Err *out_err); + +/*! deletes crypto configuration file. +\param info the target crypt info +*/ +void gf_crypt_info_del(GF_CryptInfo *info); + +#if !defined(GPAC_DISABLE_CRYPTO) && !defined(GPAC_DISABLE_ISOM_WRITE) + +/*! decrypts a file +\param infile source MP4 file to decrypt +\param drm_file location of crypto configuration file +\param outname location of destination file +\param interleave_time interleave time of the destination file - 0 means flat storage +\param fs_dump_flags flags for session stats (1) and session graph (1<<1) dumping +\return error code if any +*/ +GF_Err gf_decrypt_file(GF_ISOFile *infile, const char *drm_file, const char *outname, Double interleave_time, u32 fs_dump_flags); + +/*! decrypts a fragment +\param infile source MP4 file to decrypt +\param drm_file location of crypto configuration file +\param dst_file location of destination file +\param frag_name name of fragment to decrypt +\param fs_dump_flags flags for session stats (1) and session graph (1<<1) dumping +\return error code if any +*/ +GF_Err gf_decrypt_fragment(GF_ISOFile *infile, const char *drm_file, const char *dst_file, const char *frag_name, u32 fs_dump_flags); + +/*! encrypts a file +\param infile source MP4 file to encrypt +\param drm_file location of crypto configuration file +\param outname location of destination file +\param interleave_time interleave time of the destination file - 0 means flat storage +\param fs_dump_flags flags for session stats (1) and session graph (1<<1) dumping +\return error code if any +*/ +GF_Err gf_crypt_file(GF_ISOFile *infile, const char *drm_file, const char *outname, Double interleave_time, u32 fs_dump_flags); + +/*! encrypts a fragment +\param infile init segment of the MP4 file to encrypt - this SHALL NOT be encrypted +\param drm_file location of crypto configuration file +\param dst_file location of destination file +\param frag_name name of fragment to encrypt +\param fs_dump_flags flags for session stats (1) and session graph (1<<1) dumping +\return error code if any +*/ +GF_Err gf_crypt_fragment(GF_ISOFile *infile, const char *drm_file, const char *dst_file, const char *frag_name, u32 fs_dump_flags); + +#endif /*!defined(GPAC_DISABLE_CRYPTO) && !defined(GPAC_DISABLE_ISOM_WRITE)*/ + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /*_GF_CRYPT_TOOLS_H_*/ + diff --git a/include/gpac/dash.h b/include/gpac/dash.h index 98e5839..7cda853 100644 --- a/include/gpac/dash.h +++ b/include/gpac/dash.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2012 + * Copyright (c) Telecom ParisTech 2012-2019 * All rights reserved * * This file is part of GPAC / Adaptive HTTP Streaming sub-project @@ -32,17 +32,17 @@ extern "C" { #endif /*! - * \file - * \brief DASH Client API. The DASH client can be used without GPAC player but requires at least the base utils (threads, lists, NTP timing). The HTTP interface used can be either GPAC's one or any other downloader. - */ +\file +\brief DASH Client API. The DASH client can be used without GPAC player but requires at least the base utils (threads, lists, NTP timing). The HTTP interface used can be either GPAC's one or any other downloader. +*/ /*! - * \addtogroup dashc_grp DASH - * \ingroup media_grp - * \brief MPEG-DASH and HLS Client - * - * @{ - */ +\addtogroup dashc_grp DASH Client +\ingroup media_grp +\brief MPEG-DASH and HLS Client + +@{ +*/ #include @@ -61,7 +61,7 @@ static const char * const GF_DASH_M3U8_MIME_TYPES[] = { "video/x-mpegurl", "audi /*! * All the possible Mime-types for Smooth files */ -static const char * GF_DASH_SMOOTH_MIME_TYPES[] = { "application/vnd.ms-sstr+xml", NULL}; +static const char * const GF_DASH_SMOOTH_MIME_TYPES[] = { "application/vnd.ms-sstr+xml", NULL}; /*! DASH Event type. The DASH client communitcaes with the user through a callback mechanism using events*/ typedef enum @@ -76,10 +76,6 @@ typedef enum GF_DASH_EVENT_CREATE_PLAYBACK, /*! event sent when reseting groups at period switch or at exit - user should unload playback chain(s) at this point*/ GF_DASH_EVENT_DESTROY_PLAYBACK, - /*! event sent after each segment begins/ends download when segment bufferig is enabled*/ - GF_DASH_EVENT_BUFFERING, - /*! event sent once buffering is done*/ - GF_DASH_EVENT_BUFFER_DONE, /*! event sent once a new segment becomes available*/ GF_DASH_EVENT_SEGMENT_AVAILABLE, /*! event sent when quality has been switched for the given group*/ @@ -90,428 +86,916 @@ typedef enum GF_DASH_EVENT_TIMESHIFT_OVERFLOW, /*! event send when we need the decoding statistics*/ GF_DASH_EVENT_CODEC_STAT_QUERY, + /*! event send when no threading to trigger segment download abort*/ + GF_DASH_EVENT_ABORT_DOWNLOAD, + /*! event send whenever cache is full, to allow client to dispatch any segment*/ + GF_DASH_EVENT_CACHE_FULL, + /*! event send when all groups are done in a period*/ + GF_DASH_EVENT_END_OF_PERIOD, } GF_DASHEventType; -/*structure used for all IO operations for DASH*/ +/*! DASH network I/O abstraction object*/ typedef struct _gf_dash_io GF_DASHFileIO; +/*! structure used for all IO sessions for DASH*/ typedef void *GF_DASHFileIOSession; +/*! DASH network I/O abstraction object*/ struct _gf_dash_io { - /*user private data*/ + /*! user private data*/ void *udta; - /*signals errors or specific actions to perform*/ + /*! signals errors or specific actions to perform*/ GF_Err (*on_dash_event)(GF_DASHFileIO *dashio, GF_DASHEventType evt, s32 group_idx, GF_Err setup_error); - /*used to check whether a representation is supported or not. Function returns 1 if supported, 0 otherwise + /*! used to check whether a representation is supported or not. Function returns 1 if supported, 0 otherwise if this callback is not set, the representation is assumed to be supported*/ Bool (*dash_codec_supported)(GF_DASHFileIO *dashio, const char *codec, u32 width, u32 height, Bool is_interlaced, u32 fps_num, u32 fps_denum, u32 sample_rate); - /*called whenever a file has to be deleted*/ + /*! called whenever a file has to be deleted*/ void (*delete_cache_file)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, const char *cache_url); - /*create a file download session for the given resource - group_idx may be -1 if this is a global resource , otherwise it indicates the group/adaptationSet in which the download happens*/ + /*! create a file download session for the given resource - group_idx may be -1 if this is a global resource , otherwise it indicates the group/adaptationSet in which the download happens*/ GF_DASHFileIOSession (*create)(GF_DASHFileIO *dashio, Bool persistent, const char *url, s32 group_idx); - /*delete a file download session*/ + /*! delete a file download session*/ void (*del)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); - /*aborts downloading in the given file session*/ + /*! aborts downloading in the given file session*/ void (*abort)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); - /*resetup the file session with a new resource to get - this allows persistent connection usage with HTTP servers*/ + /*! resetup the file session with a new resource to get - this allows persistent connection usage with HTTP servers*/ GF_Err (*setup_from_url)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, const char *url, s32 group_idx); - /*set download range for the file session*/ + /*! set download range for the file session*/ GF_Err (*set_range)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, u64 start_range, u64 end_range, Bool discontinue_cache); - /*initialize the file session - all the headers shall be fetched before returning*/ + /*! initialize the file session - all the headers shall be fetched before returning*/ GF_Err (*init)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); - /*download the content - synchronous call: all the file shall be fetched before returning*/ + /*! download the content - synchronous call: all the file shall be fetched before returning*/ GF_Err (*run)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); - /*get URL of the file - i tmay be different from the original one if resource relocation happened*/ + /*! get URL of the file - it may be different from the original one if resource relocation happened*/ const char *(*get_url)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); - /*get the name of the cache file. If NULL is returned, the file cannot be cached and its associated UTL will be used when + /*! get the name of the cache file. If NULL is returned, the file cannot be cached and its associated URL will be used when the client request file to play*/ const char *(*get_cache_name)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); - /*get the MIME type of the file*/ + /*! get the MIME type of the file*/ const char *(*get_mime)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); - /*get the given hedaer value in the last HTTP response. Function is optional*/ + /*! get the given hedaer value in the last HTTP response. Function is optional*/ const char *(*get_header_value)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, const char *header_name); - /*gets the UTC time at which reply has been received. Function is optional*/ + /*! gets the UTC time at which reply has been received. Function is optional*/ u64 (*get_utc_start_time)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); - /*get the average download rate for the session. If no session is specified, gets the max download rate + /*! get the average download rate for the session. If no session is specified, gets the max download rate for the client (used for bandwidth simulation in local files)*/ u32 (*get_bytes_per_sec)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); - /*get the total size on bytes for the session*/ + /*! get the total size on bytes for the session*/ u32 (*get_total_size)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); - /*get the total size on bytes for the session*/ + /*! get the total size on bytes for the session*/ u32 (*get_bytes_done)(GF_DASHFileIO *dashio, GF_DASHFileIOSession session); }; +/*! DASH client object*/ typedef struct __dash_client GF_DashClient; +/*! Quality selection mode of initial segments*/ typedef enum { - //selects the lowest quality when starting - if one of the representation does not have video (HLS), it may be selected - GF_DASH_SELECT_QUALITY_LOWEST, - //selects the highest quality when starting + /*! selects the lowest quality when starting - if one of the representation does not have video (HLS), it may be selected*/ + GF_DASH_SELECT_QUALITY_LOWEST=0, + /*! selects the highest quality when starting*/ GF_DASH_SELECT_QUALITY_HIGHEST, - //selects the lowest bandwidth when starting - if one of the representation does not have video (HLS), it will NOT be selected + /*! selects the lowest bandwidth when starting - if one of the representation does not have video (HLS), it will NOT be selected*/ GF_DASH_SELECT_BANDWIDTH_LOWEST, - //selects the highest bandwidth when starting - for tiles all low priority tiles will have the lower (below max) bandwidth selected + /*! selects the highest bandwidth when starting - for tiles all low priority tiles will have the lower (below max) bandwidth selected*/ GF_DASH_SELECT_BANDWIDTH_HIGHEST, - //selects the highest bandwidth when starting - for tiles all low priority tiles will have their lowest bandwidth selected + /*! selects the highest bandwidth when starting - for tiles all low priority tiles will have their lowest bandwidth selected*/ GF_DASH_SELECT_BANDWIDTH_HIGHEST_TILES } GF_DASHInitialSelectionMode; -/*create a new DASH client: - @dash_io: DASH callbacks to the user - @max_cache_duration: maximum duration in milliseconds for the cached media. If less than mpd@minBufferTime, mpd@minBufferTime is used - @auto_switch_count: forces representation switching every auto_switch_count segments, set to 0 to disable - @keep_files: do not delete files from the cache - @disable_switching: turn off bandwidth switching algorithm - @first_select_mode: indicates which representation to select upon startup - @enable_buffering: forces buffering of segments for the duration indicated in the MPD before calling back the user - @initial_time_shift_value: sets initial buffering: if between 0 and 100, this is a percentage of the time shift window of the session. If greater than 100, this is a time shift in milliseconds. -*/ -GF_DashClient *gf_dash_new(GF_DASHFileIO *dash_io, +/*! DASH client threading mode*/ +typedef enum +{ + /*! no threads used, gf_dash_process shall be called on regular basis*/ + GF_DASH_THREAD_NONE = 0, + /*! single thread used for MPD and segment download*/ + GF_DASH_THREAD_SINGLE, + /*! one thread for MPD and each independent representations*/ + GF_DASH_THREAD_ALL +} GF_DASHThreadMode; + +/*! create a new DASH client +\param dash_io DASH callbacks to the user +\param thread_mode threading mode of the dash client +\param max_cache_duration maximum duration in milliseconds for the cached media. If less than \code mpd@minBufferTime \endcode , \code mpd@minBufferTime \endcode is used +\param auto_switch_count forces representation switching every auto_switch_count segments, set to 0 to disable +\param keep_files do not delete files from the cache +\param disable_switching turn off bandwidth switching algorithm +\param first_select_mode indicates which representation to select upon startup +\param initial_time_shift_value sets initial buffering: if between 0 and 100, this is a percentage of the time shift window of the session. If greater than 100, this is a time shift in milliseconds. +\return a new DASH client +*/ +GF_DashClient *gf_dash_new(GF_DASHFileIO *dash_io, GF_DASHThreadMode thread_mode, u32 max_cache_duration, u32 auto_switch_count, Bool keep_files, Bool disable_switching, GF_DASHInitialSelectionMode first_select_mode, - Bool enable_buffering, u32 initial_time_shift_value); + u32 initial_time_shift_value); -/*delete the DASH client*/ +/*! destroys a DASH client +\param dash the target dash clien +*/ void gf_dash_del(GF_DashClient *dash); -/*open the DASH client for the specific manifest file*/ +/*! opens the DASH client for the specific manifest file +\param dash the target dash client +\param manifest_url the URL of the manifest to open +\return error if any +*/ GF_Err gf_dash_open(GF_DashClient *dash, const char *manifest_url); -/*closes the dash client*/ + +/*! closes the dash client +\param dash the target dash client +*/ void gf_dash_close(GF_DashClient *dash); -/*returns URL of the DASH manifest file*/ +/*! for unthreaded session, executes DASH logic +\param dash the target dash client +\return error if any, GF_EOS on end of session*/ +GF_Err gf_dash_process(GF_DashClient *dash); + +/*! returns URL of the DASH manifest file +\param dash the target dash client +\return the currently loaded manifest +*/ const char *gf_dash_get_url(GF_DashClient *dash); -/*tells whether we are playing some Apple HLS M3U8*/ +/*! tells whether we are playing some Apple HLS M3U8 +\param dash the target dash client +\return GF_TRUE if the manifest is M3U8 +*/ Bool gf_dash_is_m3u8(GF_DashClient *dash); -/*get title and source for this MPD*/ +/*! gets title and source for this MPD +\param dash the target dash client +\param title set to the title of the manifest (may be NULL) +\param source set to the source of the manifest (may be NULL) +*/ void gf_dash_get_info(GF_DashClient *dash, const char **title, const char **source); -/*switches quality up or down*/ +/*! switches quality up or down +\param dash the target dash client +\param switch_up indicates if the quality should be increased (GF_TRUE) or decreased (GF_FALSE) +\param force_immediate_switch if GF_TRUE, aborts all current downloads, remove downloaded segments not yet played and switch. Otherwise, existing switching will only happen once the existing downloaded segments have been played +*/ void gf_dash_switch_quality(GF_DashClient *dash, Bool switch_up, Bool force_immediate_switch); -/*indicates whether the DASH client is running or not. For the moment, the DASH client is always run by an internal thread*/ +/*! indicates whether the DASH client is running or not +\param dash the target dash client +\return GF_TRUE if the session is still active, GF_FALSE otherwise +*/ Bool gf_dash_is_running(GF_DashClient *dash); -/*get duration of the presentation*/ -Double gf_dash_get_duration(GF_DashClient *dash); +/*! indicates whether the DASH client is in setup stage (sloving periods, destroying or creating playback chain) or not +\param dash the target dash client +\return GF_TRUE if the session is in setup, GF_FALSE otherwise +*/ +Bool gf_dash_is_in_setup(GF_DashClient *dash); -/*check that the given file has the right XML root element*/ -Bool gf_dash_check_mpd_root_type(const char *local_url); +/*! gets duration of the presentation +\param dash the target dash client +\return the duration in second of the session +*/ +Double gf_dash_get_duration(GF_DashClient *dash); -/*sets timeshift for the presentation - this function does not trigger a seek, this has to be done by the caller - @ms_in_timeshift: if between 0 and 100, this is a percentage of the time shift window of the session. If greater than 100, this is a time shift in milliseconds. +/*! sets timeshift for the presentation - this function does not trigger a seek, this has to be done by the caller +\param dash the target dash client +\param ms_in_timeshift if between 0 and 100, this is a percentage of the time shift window of the session. If greater than 100, this is a time shift in milliseconds +\return error if any */ GF_Err gf_dash_set_timeshift(GF_DashClient *dash, u32 ms_in_timeshift); -/*returns the number of groups. A group is a set of media resources that are alternate of each other in terms of bandwidth/quality.*/ +/*! returns the number of groups. A group is a set of media resources that are alternate of each other in terms of bandwidth/quality +\param dash the target dash client +\return the number of groups in the active session +*/ u32 gf_dash_get_group_count(GF_DashClient *dash); -/*associate user data (or NULL) with this group*/ + +/*! associates user data (or NULL) with a given group +\param dash the target dash client +\param group_index the 0-based index of the target group +\param udta the opaque data to associate to the group +\return error if any +*/ GF_Err gf_dash_set_group_udta(GF_DashClient *dash, u32 group_index, void *udta); -/*returns the user data associated with this group*/ + +/*! returns the user data associated with this group +\param dash the target dash client +\param group_index the 0-based index of the target group +\return the opaque data to associate to the group +*/ void *gf_dash_get_group_udta(GF_DashClient *dash, u32 group_index); -/*indicates whether a group is selected for playback or not. Currently groups cannot be selected during playback*/ + +/*! indicates whether a group is selected for playback or not. Currently groups cannot be selected during playback +\param dash the target dash client +\param group_index the 0-based index of the target group +\return GF_TRUE if the group is selected for playback/download +*/ Bool gf_dash_is_group_selected(GF_DashClient *dash, u32 group_index); -/*indicates whether this group is dependent on another group (because representations are). If this is the case, all representations of this group will be made available through the base group (and @has_next_segment flag) if the group is selected. -returns -1 if not dependent on another group, otherwise return dependent group index*/ -s32 gf_dash_group_has_dependent_group(GF_DashClient *dash, u32 idx); +/*! indicates whether this group is dependent on another group (because representations are). If this is the case, all representations of this group will be made available through the base group (and has_next_segment flag) if the group is selected. +returns -1 if not dependent on another group, otherwise return dependent group index +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return -1 if the group does not depend on another group, otherwise returns the dependent group index +*/ +s32 gf_dash_group_has_dependent_group(GF_DashClient *dash, u32 group_idx); -/*gives the number of groups depending on this one for decoding.*/ -u32 gf_dash_group_get_num_groups_depending_on(GF_DashClient *dash, u32 idx); +/*! gives the number of groups depending on this one for decoding +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return the number of other groups depending on this group +*/ +u32 gf_dash_group_get_num_groups_depending_on(GF_DashClient *dash, u32 group_idx); -/*gets the index of the depending_on group with the specified group_depending_on_dep_idx (between 0 and gf_dash_group_get_num_groups_depending_on()-1)*/ -s32 gf_dash_get_dependent_group_index(GF_DashClient *dash, u32 idx, u32 group_depending_on_dep_idx); +/*! gets the index of the depending_on group with the specified group_depending_on_dep_idx (between 0 and \ref gf_dash_group_get_num_groups_depending_on -1) +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param group_depending_on_dep_idx the 0-based index of the queried dependent group +\return the index of the depending_on group +*/ +s32 gf_dash_get_dependent_group_index(GF_DashClient *dash, u32 group_idx, u32 group_depending_on_dep_idx); -/*indicates whether a group can be selected for playback or not. Some groups may have been disabled because of non supported features*/ -Bool gf_dash_is_group_selectable(GF_DashClient *dash, u32 idx); +/*! indicates whether a group can be selected for playback or not. Some groups may have been disabled because of non supported features +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return GF_TRUE if the group can be selected for playback, GF_FALSE otherwise +*/ +Bool gf_dash_is_group_selectable(GF_DashClient *dash, u32 group_idx); -/*selects a group for playback. If other groups are alternate to this group (through the @group attribute), they are automatically deselected. */ -void gf_dash_group_select(GF_DashClient *dash, u32 idx, Bool select); +/*! selects a group for playback. If other groups are alternate to this group (through the group attribute), they are automatically deselected +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param select if GF_TRUE, will select this group and disable any alternate group. If GF_FALSE, only deselects the group +*/ +void gf_dash_group_select(GF_DashClient *dash, u32 group_idx, Bool select); -/*performs selection of representations based on language code*/ +/*! performs selection of representations based on language code +\param dash the target dash client +\param lang_code_rfc_5646 the language code used by the default group selection +*/ void gf_dash_groups_set_language(GF_DashClient *dash, const char *lang_code_rfc_5646); -/*returns the mime type of the media resources in this group*/ -const char *gf_dash_group_get_segment_mime(GF_DashClient *dash, u32 idx); -/*returns the URL of the first media resource to play (init segment or first media segment depending on format). start_range and end_range are optional*/ -const char *gf_dash_group_get_segment_init_url(GF_DashClient *dash, u32 idx, u64 *start_range, u64 *end_range); - -/*returns the URL and IV associated with the first media segment if any (init segment or first media segment depending on format). key_IV is optional*/ -const char *gf_dash_group_get_segment_init_keys(GF_DashClient *dash, u32 idx, bin128 *key_IV); +/*! returns the mime type of the media resources in this group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return the mime type of the segments in this group +*/ +const char *gf_dash_group_get_segment_mime(GF_DashClient *dash, u32 group_idx); + +/*! returns the URL of the first media resource to play (init segment or first media segment depending on format). start_range and end_range are optional +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param start_range set to the byte start offset in the init segment +\param end_range set to the byte end offset in the init segment +\return URL of the init segment (can be a relative path if manifest is a local file) +*/ +const char *gf_dash_group_get_segment_init_url(GF_DashClient *dash, u32 group_idx, u64 *start_range, u64 *end_range); + +/*! returns the URL and IV associated with the first media segment if any (init segment or first media segment depending on format). +This is used for full segment encryption modes of MPEG-2 TS segments. key_IV is optional +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param key_IV set to the IV used for the first media segment (can be NULL) +\return the key URL of the first media segment +*/ +const char *gf_dash_group_get_segment_init_keys(GF_DashClient *dash, u32 group_idx, bin128 *key_IV); -/*returns the language of the group, or NULL if none associated*/ -const char *gf_dash_group_get_language(GF_DashClient *dash, u32 idx); +/*! returns the language of the group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return the language of the group or NULL if no language associated +*/ +const char *gf_dash_group_get_language(GF_DashClient *dash, u32 group_idx); -/*returns the language of the group, or NULL if none associated*/ -u32 gf_dash_group_get_audio_channels(GF_DashClient *dash, u32 idx); +/*! returns the number of audio channelsof the group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return the number of audio channels, or 0 if not audio or unspecified +*/ +u32 gf_dash_group_get_audio_channels(GF_DashClient *dash, u32 group_idx); -/*get time shift buffer depth of the group - (u32) -1 means infinity*/ -u32 gf_dash_group_get_time_shift_buffer_depth(GF_DashClient *dash, u32 idx); +/*! gets time shift buffer depth of the group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return time shift buffer depth of the group, -1 means infinity +*/ +u32 gf_dash_group_get_time_shift_buffer_depth(GF_DashClient *dash, u32 group_idx); -/*get current time in time shift buffer in seconds - 0 means 'live point' -this gets the maximum value (further in the past) of all representations playing*/ +/*! gets current time in time shift buffer +This functions retrieves the maximum value (further in the past) of all selected/active groups +\param dash the target dash client +\return current time in timeshift buffer in seconds - 0 means 'live point' +*/ Double gf_dash_get_timeshift_buffer_pos(GF_DashClient *dash); -/*sets codec statistics for playback rate adjustment*/ -void gf_dash_group_set_codec_stat(GF_DashClient *dash, u32 idx, u32 avg_dec_time, u32 max_dec_time, u32 irap_avg_dec_time, u32 irap_max_dec_time, Bool codec_reset, Bool decode_only_rap); - -/*sets buffer levels*/ -void gf_dash_group_set_buffer_levels(GF_DashClient *dash, u32 idx, u32 buffer_min_ms, u32 buffer_max_ms, u32 buffer_occupancy_ms); - -/*indicates the buffer time in ms after which the player resumes playback. This value is less or equal to the buffer_max_ms -indicated in gf_dash_group_set_buffer_levels */ -GF_Err gf_dash_group_set_max_buffer_playout(GF_DashClient *dash, u32 idx, u32 max_target_buffer_ms); +/*! sets codec statistics of a group for playback rate adjustment +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param avg_dec_time average decode time in microseconds of a frame, 0 if unknown +\param max_dec_time maximum decode time in microseconds of a frame, 0 if unknown +\param irap_avg_dec_time average decode time of SAP 1/2/3 pictures in microseconds of a frame, 0 if unknown +\param irap_max_dec_time maximum decode time of SAP 1/2/3 pictures in microseconds of a frame, 0 if unknown +\param codec_reset indicates a codec reset is pending (pipeline flush is needed before reinit) +\param decode_only_rap indicates only SAP1/2/3 are currently being decoded. This may trigger a switch to a trick mode representation +*/ +void gf_dash_group_set_codec_stat(GF_DashClient *dash, u32 group_idx, u32 avg_dec_time, u32 max_dec_time, u32 irap_avg_dec_time, u32 irap_max_dec_time, Bool codec_reset, Bool decode_only_rap); + +/*! sets buffer levels of a group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param buffer_min_ms minimum buffer in milliseconds (pipeline would rebuffer below this level) +\param buffer_max_ms maximum buffer in milliseconds (pipeline would block above this level) +\param buffer_occupancy_ms current buffer occupancy in milliseconds +*/ +void gf_dash_group_set_buffer_levels(GF_DashClient *dash, u32 group_idx, u32 buffer_min_ms, u32 buffer_max_ms, u32 buffer_occupancy_ms); + +/*! indicates the buffer time in ms after which the player resumes playback. This value is less or equal to the buffer_max_ms +indicated in \ref gf_dash_group_set_buffer_levels +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param max_target_buffer_ms buffer in milliseconds above which pipeline resumes after a rebuffering event +\return error if any +*/ +GF_Err gf_dash_group_set_max_buffer_playout(GF_DashClient *dash, u32 group_idx, u32 max_target_buffer_ms); +/*! DASH descriptor types*/ typedef enum { + /*! Accessibility descriptor*/ GF_MPD_DESC_ACCESSIBILITY, + /*! Audio config descriptor*/ GF_MPD_DESC_AUDIOCONFIG, + /*! Content Protection descriptor*/ GF_MPD_DESC_CONTENT_PROTECTION, + /*! Essential Property descriptor*/ GF_MPD_DESC_ESSENTIAL_PROPERTIES, + /*! Supplemental Property descriptor*/ GF_MPD_DESC_SUPPLEMENTAL_PROPERTIES, + /*! Frame packing descriptor*/ GF_MPD_DESC_FRAME_PACKING, + /*! Role descriptor*/ GF_MPD_DESC_ROLE, + /*! Rating descriptor*/ GF_MPD_DESC_RATING, + /*! Viewpoint descriptor*/ GF_MPD_DESC_VIEWPOINT } GF_DashDescriptorType; -//enumerate descriptors of the given type: -//group_idx: index of the group for which descriptors are enumerated -//desc_type: type of descriptor being checked, one of the above -//role_idx: index of the descriptor being checked for this type +/*! enumerates descriptors of the given type +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param desc_type type of descriptor being checked +\param role_idx index of the descriptor being checked for this type +\param desc_id set to the ID of the descriptor if found (optional, may be NULL) +\param desc_scheme set to the scheme of the descriptor if found (optional, may be NULL) +\param desc_value set to the desc_value of the descriptor if found (optional, may be NULL) +\return GF_TRUE if descriptor is found, GF_FALSE otherwise +*/ Bool gf_dash_group_enum_descriptor(GF_DashClient *dash, u32 group_idx, GF_DashDescriptorType desc_type, u32 role_idx, const char **desc_id, const char **desc_scheme, const char **desc_value); -/*returns the URL and byte range of the next media resource to play in this group. -If switching occured, sets switching_index to the new representation index. -If no bitstream switching is possible, also set the url and byte range of the media file required to intialize -the playback of the next segment -original_url is optional and may be used to het the URI of the segment +/*! returns the URL and byte range of the next media resource to play in this group. +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param dependent_representation_index index of the dependent representation to query, 0-based +\param url set to the URL of the next segment +\param start_range set to the start byte offset in the segment (optional, may be NULL) +\param end_range set to the end byte offset in the segment (optional, may be NULL) +\param switching_index set to the quality index of the segment (optional, may be NULL) +\param switching_url set to the URL of the switching segment if needed (optional, may be NULL) +\param switching_start_range set to start byte offset of the switching segment if needed (optional, may be NULL) +\param switching_end_range set to end byte offset of the switching segment if needed (optional, may be NULL) +\param original_url set to original URL value of the segment (optional, may be NULL) +\param has_next_segment set to GF_TRUE if next segment location is known (unthreaded mode) or next segment is downloaded (threaded mode) (optional, may be NULL) +\param key_url set to the key URL of the next segment for MPEG-2 TS full segment encryption (optional, may be NULL) +\param key_IV set to the key initialization vector of the next segment for MPEG-2 TS full segment encryption (optional, may be NULL) +\return GF_BUFFER_TOO_SMALL if no segment found, GF_EOS if end of session or error if any */ -GF_Err gf_dash_group_get_next_segment_location(GF_DashClient *dash, u32 idx, u32 dependent_representation_index, const char **url, u64 *start_range, u64 *end_range, +GF_Err gf_dash_group_get_next_segment_location(GF_DashClient *dash, u32 group_idx, u32 dependent_representation_index, const char **url, u64 *start_range, u64 *end_range, s32 *switching_index, const char **switching_url, u64 *switching_start_range, u64 *switching_end_range, const char **original_url, Bool *has_next_segment, const char **key_url, bin128 *key_IV); -/*same as gf_dash_group_get_next_segment_location but query the current downloaded segment*/ +/*! same as gf_dash_group_get_next_segment_location but query the current downloaded segment +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param url set to the URL of the next segment +\param switching_index set to the quality index of the segment (optional, may be NULL) +\param switching_url set to the URL of the switching segment if needed (optional, may be NULL) +\param original_url set to original URL value of the segment (optional, may be NULL) +\param switched set to GF_TRUE if current segment being downloaded follows an aborted download (optional, may be NULL) +\return GF_BUFFER_TOO_SMALL if no segment found, GF_EOS if end of session or error if any +*/ GF_EXPORT -GF_Err gf_dash_group_probe_current_download_segment_location(GF_DashClient *dash, u32 idx, const char **url, s32 *switching_index, const char **switching_url, const char **original_url, Bool *switched); - -/*returns 1 if segment numbers loops at this level (not allowed but happens when looping captures ...*/ -Bool gf_dash_group_loop_detected(GF_DashClient *dash, u32 idx); - -/*returns number of seconds at which playback shall start for the group in the current period -the first segment available for the period will be so that gf_dash_group_get_start_range is in this range after the caller -adjusts it with PTO (eg the returned time is in period timeline, not media timeline */ -Double gf_dash_group_get_start_range(GF_DashClient *dash, u32 idx); - -/*discards the first media resource in the queue of this group*/ -void gf_dash_group_discard_segment(GF_DashClient *dash, u32 idx); -/*get the number of media resources available in the cache for this group*/ -u32 gf_dash_group_get_num_segments_ready(GF_DashClient *dash, u32 idx, Bool *group_is_done); -/*get the maximum number of media resources that can be put in the cache for this group*/ -u32 gf_dash_group_get_max_segments_in_cache(GF_DashClient *dash, u32 idx); -/*indicates to the DASH engine that the group playback has been stopped by the user*/ -void gf_dash_set_group_done(GF_DashClient *dash, u32 idx, Bool done); -/*gets presentationTimeOffset and timescale for the active representation*/ -GF_Err gf_dash_group_get_presentation_time_offset(GF_DashClient *dash, u32 idx, u64 *presentation_time_offset, u32 *timescale); - -/*returns 1 if the playback position is in the last period of the presentation -if check_eos is set, return one only if the last period is known to be the last one (eg not an open period in live) +GF_Err gf_dash_group_probe_current_download_segment_location(GF_DashClient *dash, u32 group_idx, const char **url, s32 *switching_index, const char **switching_url, const char **original_url, Bool *switched); + +/*! checks if loop was detected in playback. This is mostly used for broadcast (eMBMS, ROUTE) based on pcap replay. +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return GF_TRUE if segment numbers loop was detected +*/ +Bool gf_dash_group_loop_detected(GF_DashClient *dash, u32 group_idx); + +/*! returns number of seconds at which playback shall start for the group in the current period. +The first segment available for the period will be so that gf_dash_group_get_start_range is in this range after the caller +adjusts it with PTO (eg the returned time is in period timeline, not media timeline) +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return media playback start range in seconds*/ +Double gf_dash_group_get_start_range(GF_DashClient *dash, u32 group_idx); + +/*! discards the first media resource in the queue of this group +\param dash the target dash client +\param group_idx the 0-based index of the target group +*/ +void gf_dash_group_discard_segment(GF_DashClient *dash, u32 group_idx); + +/*! gets the number of media resources available in the cache for this group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param group_is_done setto GF_TRUE if group is done playing +\return number of segments ready +*/ +u32 gf_dash_group_get_num_segments_ready(GF_DashClient *dash, u32 group_idx, Bool *group_is_done); + +/*! get the maximum number of media resources that can be put in the cache for this group in threaded mode. +In unthreaded mode, only the next URL is available and the caller is responsible for fetching the media +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return maximum number of cached segments +*/ +u32 gf_dash_group_get_max_segments_in_cache(GF_DashClient *dash, u32 group_idx); + +/*! indicates to the DASH engine that the group playback has been stopped by the user +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param done mark group as done if GF_TRUE, or not done if GF_FALSE +*/ +void gf_dash_set_group_done(GF_DashClient *dash, u32 group_idx, Bool done); + +/*! gets presentationTimeOffset and timescale for the active representation +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param presentation_time_offset set to the presentation time offset for the group +\param timescale set to the timescale used to represent the presentation time offset +\return error if any +*/ +GF_Err gf_dash_group_get_presentation_time_offset(GF_DashClient *dash, u32 group_idx, u64 *presentation_time_offset, u32 *timescale); + +/*! checks if the session is in the last period +\param dash the target dash client +\param check_eos if GF_TRUE, return GF_TRUE only if the last period is known to be the last one (not an open period in live) +\return GF_TRUE if the playback position is in the last period of the presentation */ Bool gf_dash_in_last_period(GF_DashClient *dash, Bool check_eos); -/*return value: + +/*! gets current period switching status for the session. +\param dash the target dash client +\return possible values: 1 if the period switching has been requested (due to seeking), 2 if the switching is in progress (all groups will soon be destroyed and plyback will be stopped and restarted) 0 if no switching is requested */ u32 gf_dash_get_period_switch_status(GF_DashClient *dash); -/*request period switch - this is typically called when the media engine signals that no more data is available for playback*/ -void gf_dash_request_period_switch(GF_DashClient *dash); -/*returns 1 if the DASH engine is currently setting up a period (creating groups and fetching initial segments)*/ -Bool gf_dash_in_period_setup(GF_DashClient *dash); -/*seeks playback to the given time. If period changes, all playback is stopped and restarted -If the session is dynamic (live), the start_range is ignored and recomputed from current UTC clock to be at the live point. If timeshifting is desired, use @gf_dash_set_timeshift before seeking.*/ -void gf_dash_seek(GF_DashClient *dash, Double start_range); -/*when seeking, this flag is set when the seek is outside of the previously playing segment.*/ -Bool gf_dash_group_segment_switch_forced(GF_DashClient *dash, u32 idx); -/*get video info for this group if video*/ -GF_Err gf_dash_group_get_video_info(GF_DashClient *dash, u32 idx, u32 *max_width, u32 *max_height); - -/*sets playback speed of the session. Speed is used in adaptation logic*/ -void gf_dash_set_speed(GF_DashClient *dash, Double speed); -/*returns the start_time of the first segment in the queue (usually the one being played)*/ -Double gf_dash_group_current_segment_start_time(GF_DashClient *dash, u32 idx); +/*! request period switch - this is typically called when the media engine signals that no more data is available for playback +\param dash the target dash client +*/ +void gf_dash_request_period_switch(GF_DashClient *dash); -/*allow reloading of MPD on the local file system - useful for testing live generators*/ -void gf_dash_allow_local_mpd_update(GF_DashClient *dash, Bool allow_local_mpd_update); +/*! checks if the client is in a period setup state +\param dash the target dash client +\return GF_TRUE if the DASH engine is currently setting up a period (creating groups and fetching initial segments) +*/ +Bool gf_dash_in_period_setup(GF_DashClient *dash); -/*gets media info for representation*/ -GF_Err gf_dash_group_get_representation_info(GF_DashClient *dash, u32 idx, u32 representation_idx, u32 *width, u32 *height, u32 *audio_samplerate, u32 *bandwidth, const char **codecs); +/*! seeks playback to the given time. If period changes, all playback is stopped and restarted +If the session is dynamic (live), the start_range is ignored and recomputed from current UTC clock to be at the live point. If timeshifting is desired, use \ref gf_dash_set_timeshift before seeking +\param dash the target dash client +\param start_range the desired seek time in seconds +*/ +void gf_dash_seek(GF_DashClient *dash, Double start_range); -/*gets media buffering info for all active representations*/ -void gf_dash_get_buffer_info(GF_DashClient *dash, u32 *total_buffer, u32 *media_buffered); +/*! checks if seeking time is in the previously playing segment +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return GF_TRUE if the seek request was in a different segment than the previously playing one +*/ +Bool gf_dash_group_segment_switch_forced(GF_DashClient *dash, u32 group_idx); +/*! gets video info for this group if video +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param max_width set to the maximum width in the group +\param max_height set to the maximum height in the group +\return error if any +*/ +GF_Err gf_dash_group_get_video_info(GF_DashClient *dash, u32 group_idx, u32 *max_width, u32 *max_height); -/*updates media bandwidth for the given group. Only allowed for groups without dependencies to other groups*/ -GF_Err gf_dash_group_check_bandwidth(GF_DashClient *dash, u32 idx); +/*! seeks only a given group - results are undefined if the seek operation triggers a period switch +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param seek_to the desired seek time in seconds +*/ +void gf_dash_group_seek(GF_DashClient *dash, u32 group_idx, Double seek_to); -/*resync the downloader so that the next downloaded segment falls into the indicated range - used for error recovery*/ -GF_Err gf_dash_resync_to_segment(GF_DashClient *dash, const char *latest_segment_name, const char *earliest_segment_name); +/*! sets playback speed of the session. Speed is used in adaptation logic +\param dash the target dash client +\param speed current playback speed +*/ +void gf_dash_set_speed(GF_DashClient *dash, Double speed); -/*sets dash idle interval. The default is 1 sec. The dash client thread will never go to sleep for more than this interval*/ -void gf_dash_set_idle_interval(GF_DashClient *dash, u32 idle_time_ms); +/*! updates media bandwidth for the given group. Only allowed for groups without dependencies to other groups +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return error if any +*/ +GF_Err gf_dash_group_check_bandwidth(GF_DashClient *dash, u32 group_idx); -/*enables UTC drift computation using HTTP header "Server-UTC: UTC", where UTC is in ms*/ +/*! enables UTC drift computation using HTTP header "Server-UTC: UTC", where UTC is in ms +\param dash the target dash client +\param estimate_utc_drift if GF_TRUE, enables UTC drift compensation, otherwise disables it +*/ void gf_dash_enable_utc_drift_compensation(GF_DashClient *dash, Bool estimate_utc_drift); -/*returns GF-TRUE if MPD is dynamic, false otherwise*/ +/*! checks if session is dynamic offering (live) +\param dash the target dash client +\return GF_TRUE if MPD is dynamic, GF_FALSE otherwise +*/ Bool gf_dash_is_dynamic_mpd(GF_DashClient *dash); -/*returns minimum buffer time indicated in mpd in ms*/ +/*! gets minimum buffer time of session indicated in MPD +\param dash the target dash client +\return minimum buffer time in ms +*/ u32 gf_dash_get_min_buffer_time(GF_DashClient *dash); -// gets the difference between the local UTC clock and the one reported by the server +/*! gets the difference between the local UTC clock and the one reported by the server +\param dash the target dash client +\return difference in milliseconds +*/ s32 gf_dash_get_utc_drift_estimate(GF_DashClient *dash); -//shifts UTC clock of server by shift_utc_ms so that new UTC in MPD is old + shift_utc_ms +/*! shifts UTC clock of server by shift_utc_ms so that new UTC in MPD is old + shift_utc_ms +\param dash the target dash client +\param shift_utc_ms UTC clock shift in milliseconds. A positive value will move the clock in the future, a negative value in the past +*/ void gf_dash_set_utc_shift(GF_DashClient *dash, s32 shift_utc_ms); -//sets max resolution@bpp for all video +/*! sets max video display capabilities +\param dash the target dash client +\param width the maximum width of the display +\param height the maximum height of the display +\param max_display_bpp the maximum bits per pixel of the display +\return error if any +*/ GF_Err gf_dash_set_max_resolution(GF_DashClient *dash, u32 width, u32 height, u8 max_display_bpp); -//sets min time in ms between a 404 and the next request on the same group. The default value is 500 ms. +/*! sets min time in ms between a 404 and the next request on the same group. The default value is 500 ms. +\param dash the target dash client +\param min_timeout_between_404 minimum delay in milliseconds between retries +\return error if any +*/ GF_Err gf_dash_set_min_timeout_between_404(GF_DashClient *dash, u32 min_timeout_between_404); -//sets time in ms after which 404 request for a segment will indicate segment lost. The default value is 100 ms. +/*! sets time in ms after which 404 request for a segment will indicate segment lost. The clien always retries for segment availability time + segment duration. This allows extanding slightly the probe time (used when segment durations varies, or for VBR broadcast). The default value is 100 ms. +\param dash the target dash client +\param expire_after_ms delay in milliseconds +\return error if any +*/ GF_Err gf_dash_set_segment_expiration_threshold(GF_DashClient *dash, u32 expire_after_ms); +/*! only enables the given group - this shall be set before calling \ref gf_dash_open. If group_idx is <0 (default) no groups will be disabled +\param dash the target dash client +\param group_idx the 0-based index of the target group +*/ +void gf_dash_debug_group(GF_DashClient *dash, s32 group_idx); -//only enables the given group - this shall be set before calling @gf_dash_open. If group_index is <0 (default) no groups will be disabled. -void gf_dash_debug_group(GF_DashClient *dash, s32 group_index); +/*! split all adatation sets so that they contain only one representation (quality) +\param dash the target dash client +*/ +void gf_dash_split_adaptation_sets(GF_DashClient *dash); -//indicates typical buffering used by the user app . This allows fetching data earlier in live mode, if the timeshiftbuffer allows for it +/*! low latency mode of dash client*/ +typedef enum +{ + /*! disable low latency*/ + GF_DASH_LL_DISABLE = 0, + /*! strict respect of segment availability start time*/ + GF_DASH_LL_STRICT, + /*! allow fetching segments earlier than their availability start time in case of empty demux*/ + GF_DASH_LL_EARLY_FETCH, +} GF_DASHLowLatencyMode; + +/*! allow early segment fetch in low latency mode +\param dash the target dash client +\param low_lat_mode low latency mode +*/ +void gf_dash_set_low_latency_mode(GF_DashClient *dash, GF_DASHLowLatencyMode low_lat_mode); + +/*! indicates typical buffering used by the user app before playback starts. + This allows fetching data earlier in live mode, if the timeshiftbuffer allows for it +\param dash the target dash client +\param buffer_time_ms typical playout buffer in milliseconds +*/ void gf_dash_set_user_buffer(GF_DashClient *dash, u32 buffer_time_ms); -//indicates the number of segments to wait before switching up bandwidth. The default value is 1 (ie stay in current -//bandwidth or one more segment before switching up, event if download rate is enough) -//seting to 0 means the switch will happen instantly, but this is more prone to quality changes due to network variations +/*! indicates the number of segments to wait before switching up bandwidth. The default value is 1 (ie stay in current bandwidth or one more segment before switching up, event if download rate is enough). +Setting this to 0 means the switch will happen instantly, but this is more prone to quality changes due to network variations +\param dash the target dash client +\param switch_probe_count the number of probes before switching +*/ void gf_dash_set_switching_probe_count(GF_DashClient *dash, u32 switch_probe_count); -/*If agressive switching is enabled, switching targets to the closest bandwidth fitting the available download rate. Otherwise, switching targets the lowest bitrate representation that is above the currently played (eg does not try to switch to max bandwidth). Default value is no. + +/*! enables agressive switching mode. If agressive switching is enabled, switching targets to the closest bandwidth fitting the available download rate. Otherwise, switching targets the lowest bitrate representation that is above the currently played (eg does not try to switch to max bandwidth). Default value is no. +\param dash the target dash client +\param enable_agressive_switch if GF_TRUE, enables agressive mode, otherwise disables it */ -void gf_dash_set_agressive_adaptation(GF_DashClient *dash, Bool eanble_agressive_switch); +void gf_dash_set_agressive_adaptation(GF_DashClient *dash, Bool enable_agressive_switch); -/*returns active period start in ms*/ +/*! returns active period start +\param dash the target dash client +\return period start in milliseconds +*/ u64 gf_dash_get_period_start(GF_DashClient *dash); -/*returns active period duration in ms*/ + +/*! gets active period duration +\param dash the target dash client +\return active period duration in milliseconds +*/ u64 gf_dash_get_period_duration(GF_DashClient *dash); -//returns number of quality available for the given group -u32 gf_dash_group_get_num_qualities(GF_DashClient *dash, u32 idx); +/*! gets number of quality available for a group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return number of quality available +*/ +u32 gf_dash_group_get_num_qualities(GF_DashClient *dash, u32 group_idx); -void gf_dash_disable_speed_adaptation(GF_DashClient *dash, Bool disable); +/*! gets the number of components in a group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return number of components +*/ +u32 gf_dash_group_get_num_components(GF_DashClient *dash, u32 group_idx); +/*! disable speed adaptation +\param dash the target dash client +\param disable if GF_TRUE? speed adaptation is disabled, otherwise it is enabled +*/ +void gf_dash_disable_speed_adaptation(GF_DashClient *dash, Bool disable); +/*! DASH/HLS quality information structure*/ typedef struct { + /*! bandwidth in bits per second*/ u32 bandwidth; + /*! ID*/ const char *ID; + /*! mime type*/ const char *mime; + /*! codec parameter of mime type*/ const char *codec; + /*! video width*/ u32 width; + /*! video width*/ u32 height; + /*! video interlaced flag*/ Bool interlaced; - u32 fps_den, fps_num; + /*! video framerate numerator*/ + u32 fps_den; + /*! video framerate denominator*/ + u32 fps_num; + /*! video sample aspect ratio numerator*/ u32 par_num; + /*! video sample aspect ratio denominator*/ u32 par_den; + /*! audio sample rate*/ u32 sample_rate; + /*! audio channel count*/ u32 nb_channels; + /*! disabled flag (not supported by DASH client)*/ Bool disabled; + /*! selected flag*/ Bool is_selected; } GF_DASHQualityInfo; -//returns number of quality available for the given group -GF_Err gf_dash_group_get_quality_info(GF_DashClient *dash, u32 idx, u32 quality_idx, GF_DASHQualityInfo *quality); +/*! gets information on a given quality +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param quality_idx the 0-based index of the quality +\param quality filled with information for the desired quality +\return error if any +*/ +GF_Err gf_dash_group_get_quality_info(GF_DashClient *dash, u32 group_idx, u32 quality_idx, GF_DASHQualityInfo *quality); -//returns 1 if automatic quality switching is enabled +/*! checks automatic switching mode +\param dash the target dash client +\return GF_TRUE if automatic quality switching is enabled +*/ Bool gf_dash_get_automatic_switching(GF_DashClient *dash); -//sets automatic quality switching enabled or disabled +/*! sets automatic quality switching mode. If automatic switching is off, switching can only happen based on caller inputs +\param dash the target dash client +\param enable_switching if GF_TRUE, automatic switching is enabled; otherwise it is disabled +\return error if any +*/ GF_Err gf_dash_set_automatic_switching(GF_DashClient *dash, Bool enable_switching); -//selects quality of given ID -GF_Err gf_dash_group_select_quality(GF_DashClient *dash, u32 idx, const char *ID); - -//gets download rate in bytes per second for the given group -u32 gf_dash_group_get_download_rate(GF_DashClient *dash, u32 idx); +/*! selects quality of a group, either by ID or by index if ID is null +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param ID the ID of the desired quality +\param q_idx the 0-based index of the desired quality +\return error if any +*/ +GF_Err gf_dash_group_select_quality(GF_DashClient *dash, u32 group_idx, const char *ID, u32 q_idx); + +/*! gets currently active quality of a group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return the current quality index for the given group*/ +s32 gf_dash_group_get_active_quality(GF_DashClient *dash, u32 group_idx); + +/*! gets download rate for a given group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return download rate in bytes per second +*/ +u32 gf_dash_group_get_download_rate(GF_DashClient *dash, u32 group_idx); -//forces NTP of the DASH client to be the given NTP +/*! forces NTP of the DASH client to be the given NTP +\param dash the target dash client +\param server_ntp NTP timestamp to set as server clock +*/ void gf_dash_override_ntp(GF_DashClient *dash, u64 server_ntp); -//specifies how bitrate is allocated accross tiles of the same video +/*! Tile adaptation mode +This mode specifies how bitrate is allocated accross tiles of the same video +*/ typedef enum { - //each tile receives the same amount of bitrate (default strategy) + /*! each tile receives the same amount of bitrate (default strategy)*/ GF_DASH_ADAPT_TILE_NONE=0, - //bitrate decreases for each row of tiles starting from the top, same rate for each tile on the row + /*! bitrate decreases for each row of tiles starting from the top, same rate for each tile on the row*/ GF_DASH_ADAPT_TILE_ROWS, - //bitrate decreases for each row of tiles starting from the bottom, same rate for each tile on the row + /*! bitrate decreases for each row of tiles starting from the bottom, same rate for each tile on the row*/ GF_DASH_ADAPT_TILE_ROWS_REVERSE, - //bitrate decreased for top and bottom rows only, same rate for each tile on the row + /*! bitrate decreased for top and bottom rows only, same rate for each tile on the row*/ GF_DASH_ADAPT_TILE_ROWS_MIDDLE, - //bitrate decreases for each column of tiles starting from the left, same rate for each tile on the column + /*! bitrate decreases for each column of tiles starting from the left, same rate for each tile on the column*/ GF_DASH_ADAPT_TILE_COLUMNS, - //bitrate decreases for each column of tiles starting from the right, same rate for each tile on the column + /*! bitrate decreases for each column of tiles starting from the right, same rate for each tile on the column*/ GF_DASH_ADAPT_TILE_COLUMNS_REVERSE, - //bitrate decreased for left and right columns only, same rate for each tile on the column + /*! bitrate decreased for left and right columns only, same rate for each tile on the column*/ GF_DASH_ADAPT_TILE_COLUMNS_MIDDLE, - //bitrate decreased for all tiles on the edge of the picture + /*! bitrate decreased for all tiles on the edge of the picture*/ GF_DASH_ADAPT_TILE_CENTER, - //bitrate decreased for all tiles on the center of the picture + /*! bitrate decreased for all tiles on the center of the picture*/ GF_DASH_ADAPT_TILE_EDGES, } GF_DASHTileAdaptationMode; -//sets type of tile adaptation. -// @tile_rate_decrease: percentage (0->100) of global bandwidth to use at each level (recursive rate decrease for all level). If 0% or 100%, automatic rate allocation among tiles is performed (default mode) +/*! sets tile adaptation mode +\param dash the target dash client +\param mode the desired tile adaptation mode +\param tile_rate_decrease percentage (0->100) of global bandwidth to use at each level (recursive rate decrease for all level). If 0% or 100%, automatic rate allocation among tiles is performed (default mode) +*/ void gf_dash_set_tile_adaptation_mode(GF_DashClient *dash, GF_DASHTileAdaptationMode mode, u32 tile_rate_decrease); -/*returns tile adaptation mode currently used*/ +/*! gets current tile adaptation mode +\param dash the target dash client +\return tile adaptation mode*/ GF_DASHTileAdaptationMode gf_dash_get_tile_adaptation_mode(GF_DashClient *dash); -//gets max width and height in pixels of the SRD this group belongs to, if any -Bool gf_dash_group_get_srd_max_size_info(GF_DashClient *dash, u32 idx, u32 *max_width, u32 *max_height); +/*! gets max width and height in pixels of the SRD (Spatial Relationship Descriptor) a given group belongs to, if any +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param max_width set to the maximum width of the SRD of this group +\param max_height set to the maximum height of the SRD of this group +\return GF_TRUE if the group has an SRD descriptor associated, GF_FALSE otherwise +*/ +Bool gf_dash_group_get_srd_max_size_info(GF_DashClient *dash, u32 group_idx, u32 *max_width, u32 *max_height); + +/*! gets SRD info, in SRD coordinate, of the SRD this group belongs to, if any +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param srd_id set to the id of the SRD of this group +\param srd_x set to the horizontal coordinate of the SRD of this group +\param srd_y set to the vertical coordinate of the SRD of this group +\param srd_w set to the width of the SRD of this group +\param srd_h set to the height of the SRD of this group +\param srd_width set to the reference width (usually max width) of the SRD of this group +\param srd_height set to the reference height (usually max height) of the SRD of this group +\return GF_TRUE if the group has an SRD descriptor associated, GF_FALSE otherwise +*/ +Bool gf_dash_group_get_srd_info(GF_DashClient *dash, u32 group_idx, u32 *srd_id, u32 *srd_x, u32 *srd_y, u32 *srd_w, u32 *srd_h, u32 *srd_width, u32 *srd_height); -//gets SRD info, in SRD coordinate, of the SRD this group belongs to, if any -Bool gf_dash_group_get_srd_info(GF_DashClient *dash, u32 idx, u32 *srd_id, u32 *srd_x, u32 *srd_y, u32 *srd_w, u32 *srd_h, u32 *srd_width, u32 *srd_height); +/*! sets quality hint for the given group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param quality_degradation_hint quality degradation from 0 (no degradation) to 100 (worse quality possible) +\return error if any +*/ +GF_Err gf_dash_group_set_quality_degradation_hint(GF_DashClient *dash, u32 group_idx, u32 quality_degradation_hint); + +/*! sets visible rectangle of a video object, may be used for adaptation. If min_x==max_x==min_y=max_y==0, disable adaptation +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param min_x horizontal coordinate of first visible column +\param max_x horizontal coordinate of last visible column +\param min_y horizontal coordinate of first visible row +\param max_y horizontal coordinate of last visible row +\param is_gaze if set, {min_x, min_y} indicate the position of the gaze (and {max_x, max_y} are ignored) +\return error if any +*/ +GF_Err gf_dash_group_set_visible_rect(GF_DashClient *dash, u32 group_idx, u32 min_x, u32 max_x, u32 min_y, u32 max_y, Bool is_gaze); -//sets quality hint for the given group. Quality degradation ranges from 0 (no degradation) to 100 (worse quality possible) -GF_Err gf_dash_group_set_quality_degradation_hint(GF_DashClient *dash, u32 idx, u32 quality_degradation_hint); +/*! Ignores xlink on periods if some adaptation sets are specified in the period with xlink +\param dash the target dash client +\param ignore_xlink if GF_TRUE? xlinks will be ignored on periods containing both xlinks and adaptation sets +*/ +void gf_dash_ignore_xlink(GF_DashClient *dash, Bool ignore_xlink); -//sets visible rectangle of a video object, may be used for adaptation. If min_x==max_x==min_y=max_y==0, disable adaptation -GF_Err gf_dash_group_set_visible_rect(GF_DashClient *dash, u32 idx, u32 min_x, u32 max_x, u32 min_y, u32 max_y); +/*! checks if all groups are done +\param dash the target dash client +\return GF_TRUE if all active groups in period are done +*/ +Bool gf_dash_all_groups_done(GF_DashClient *dash); -/*Enables or disables threaded downloads of media files for the dash client - @use_threads: if true, threads are used to download files*/ -void gf_dash_set_threaded_download(GF_DashClient *dash, Bool use_threads); +/*! sets a query string to append to xlink on periods +\param dash the target dash client +\param query_string the query string to append to xlinks on periods +*/ +void gf_dash_set_period_xlink_query_string(GF_DashClient *dash, const char *query_string); +/*! DASH client adaptation algorithm*/ typedef enum { + /*! no adaptation*/ GF_DASH_ALGO_NONE = 0, - GF_DASH_ALGO_GPAC_LEGACY_RATE = 1, - GF_DASH_ALGO_GPAC_LEGACY_BUFFER = 2, - - GF_DASH_ALGO_GPAC_TEST = 20, + /*! GPAC rate-based adaptation*/ + GF_DASH_ALGO_GPAC_LEGACY_RATE, + /*! GPAC buffer-based adaptation*/ + GF_DASH_ALGO_GPAC_LEGACY_BUFFER, + /*! BBA-0*/ + GF_DASH_ALGO_BBA0, + /*! BOLA finite*/ + GF_DASH_ALGO_BOLA_FINITE, + /*! BOLA basic*/ + GF_DASH_ALGO_BOLA_BASIC, + /*! BOLA-U*/ + GF_DASH_ALGO_BOLA_U, + /*! BOLA-O*/ + GF_DASH_ALGO_BOLA_O } GF_DASHAdaptationAlgorithm; + +/*! sets dash adaptation algorithm. Cannot be called on an active session +\param dash the target dash client +\param algo the algorithm to use +*/ void gf_dash_set_algo(GF_DashClient *dash, GF_DASHAdaptationAlgorithm algo); +/*! sets group download status of the last downloaded segment for non threaded modes +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param err error status of the download, GF_OK if no error +*/ +void gf_dash_set_group_download_state(GF_DashClient *dash, u32 group_idx, GF_Err err); + +/*! sets group download statistics of the last downloaded segment for non threaded modes +\param dash the target dash client +\param group_idx the 0-based index of the target group +\param bytes_per_sec transfer rates in bytes per seconds +\param file_size segment size in bytes +\param bytes_done number of received bytes +\param is_broadcast set to GF_TRUE if the file is received over a multicast/broadcast link such as eMBMS or ROUTE (i.e. file was pushed to cache) +*/ +void gf_dash_group_store_stats(GF_DashClient *dash, u32 group_idx, u32 bytes_per_sec, u32 file_size, u32 bytes_done, Bool is_broadcast); + +/*! sets availabilityStartTime shift for ATSC. By default the ATSC tune-in is done by matching the last received segment name +to the segment template and deriving the ATSC UTC reference from that. The function allows shifting the computed value by a given amount. +\param dash the target dash client +\param ast_shift clock shift in milliseconds of the ATSC receiver tune-in. Positive values shift the clock in the future, negative ones in the past +*/ +void gf_dash_set_atsc_ast_shift(GF_DashClient *dash, u32 ast_shift); + +/*! gets the minimum wait time before calling \ref gf_dash_process again for unthreaded mode +\param dash the target dash client +\return minimum wait time in ms +*/ +u32 gf_dash_get_min_wait_ms(GF_DashClient *dash); + +/*! gets the adaptation set ID of a given group +\param dash the target dash client +\param group_idx the 0-based index of the target group +\return the adaptation set ID +*/ +u32 gf_dash_group_get_as_id(GF_DashClient *dash, u32 group_idx); + + #endif //GPAC_DISABLE_DASH_CLIENT /*! @} */ diff --git a/include/gpac/download.h b/include/gpac/download.h index 25302ab..ea15ca3 100644 --- a/include/gpac/download.h +++ b/include/gpac/download.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -27,19 +27,19 @@ #define _GF_DOWNLOAD_H_ /*! - * \file - * \brief HTTP(S) Downloader. +\file +\brief HTTP(S) Downloader. */ /*! - * \addtogroup dld_grp Downloader - * \ingroup utils_grp - * \brief File Downloader objects - * - * This section documents the file downloading tools the GPAC framework. Currently HTTP is supported, HTTPS is under testing but may not be supported - *depending on GPAC compilation options (HTTPS in GPAC needs OpenSSL installed on the system). - * - * @{ + +\addtogroup download_grp +\brief HTTP Downloader + +This section documents the file downloading tools the GPAC framework. Currently HTTP and HTTPS are supported. HTTPS may not be supported depending on GPAC compilation options (HTTPS in GPAC needs OpenSSL installed on the system). + +@{ + */ @@ -51,12 +51,14 @@ extern "C" { #include #include - /*!the download manager object. This is usually not used by GPAC modules*/ typedef struct __gf_download_manager GF_DownloadManager; /*!the download manager session.*/ typedef struct __gf_download_session GF_DownloadSession; +/*!the optional filter session.*/ +typedef struct __gf_filter_session GF_DownloadFilterSession; +/*! URL information object*/ typedef struct GF_URL_Info_Struct { const char * protocol; char * server_name; @@ -68,64 +70,61 @@ typedef struct GF_URL_Info_Struct { } GF_URL_Info; /*! - * Extracts the information from an URL. A call to gf_dm_url_info_init() must have been issue before calling this method. - * \param url The URL to fill - * \param info This structure will be initialized properly and filled with the data - * \param baseURL The baseURL to use if any (can be null) - * \return GF_OK if URL is well formed and supported by GPAC +Extracts the information from an URL. A call to gf_dm_url_info_init() must have been issue before calling this method. +\param url The URL to fill +\param info This structure will be initialized properly and filled with the data +\param baseURL The baseURL to use if any (can be null) +\return GF_OK if URL is well formed and supported by GPAC */ GF_Err gf_dm_get_url_info(const char * url, GF_URL_Info * info, const char * baseURL); /** - * Init the GF_URL_Info structure before it can be used - * \param info The structure to initialize +Inits the GF_URL_Info structure before it can be used +\param info The structure to initialize */ void gf_dm_url_info_init(GF_URL_Info * info); /*! - * Frees the inner structures of a GF_URL_Info_Struct - * \param info The info to free +Frees the inner structures of a GF_URL_Info_Struct +\param info The info to free */ void gf_dm_url_info_del(GF_URL_Info * info); /*! - *\brief download manager constructor - * - *Creates a new download manager object. - *\param cfg optional configuration file. Currently the download manager needs a configuration file for cache location and - *other options. The cache directory must be indicated in the section "General", key "CacheDirectory" of the configuration - *file. If the cache directory is not found, the cache will be disabled but the downloader will still work. - *\return the download manager object +\brief download manager constructor + +Creates a new download manager object. +\param fsess optional filter session. If not NULL, the filter session will be used for async downloads. Otherwise, threads will be created +\return the download manager object */ -GF_DownloadManager *gf_dm_new(GF_Config *cfg); -/* - *\brief download manager destructor - * - *Deletes the download manager. All running sessions are aborted - *\param dm the download manager object +GF_DownloadManager *gf_dm_new(GF_DownloadFilterSession *fsess); +/*! +\brief download manager destructor + +Deletes the download manager. All running sessions are aborted +\param dm the download manager object */ void gf_dm_del(GF_DownloadManager *dm); /*! - *\brief callback function for authentication - * - * The gf_dm_get_usr_pass type is the type for the callback of the \ref gf_dm_set_auth_callback function used for password retrieval - *\param usr_cbk opaque user data - *\param site_url url of the site the user and password are requested for - *\param usr_name the user name for this site. The allocated space for this buffer is 50 bytes. \note this varaibale may already be formatted. - *\param password the password for this site and user. The allocated space for this buffer is 50 bytes. - *\return 0 if user didn't fill in the information which will result in an authentication failure, 1 otherwise. +\brief callback function for authentication + +The gf_dm_get_usr_pass type is the type for the callback of the \ref gf_dm_set_auth_callback function used for password retrieval +\param usr_cbk opaque user data +\param site_url url of the site the user and password are requested for +\param usr_name the user name for this site. The allocated space for this buffer is 50 bytes. \note this varaibale may already be formatted. +\param password the password for this site and user. The allocated space for this buffer is 50 bytes. +\return 0 if user didn't fill in the information which will result in an authentication failure, 1 otherwise. */ typedef Bool (*gf_dm_get_usr_pass)(void *usr_cbk, const char *site_url, char *usr_name, char *password); /*! - *\brief password retrieval assignment - * - *Assigns the callback function used for user password retrieval. If no such function is assigned to the download manager, - *all downloads requiring authentication will fail. - *\param dm the download manager object - *\param get_pass \ref gf_dm_get_usr_pass callback function for user and password retrieval. - *\param usr_cbk opaque user data passed to callback function +\brief password retrieval assignment + +Assigns the callback function used for user password retrieval. If no such function is assigned to the download manager, all downloads requiring authentication will fail. +\param dm the download manager object +\param get_pass specifies \ref gf_dm_get_usr_pass callback function for user and password retrieval. +\param usr_cbk opaque user data passed to callback function */ void gf_dm_set_auth_callback(GF_DownloadManager *dm, gf_dm_get_usr_pass get_pass, void *usr_cbk); @@ -173,6 +172,8 @@ typedef enum /*file is stored in memory, and the cache name is set to gpac://%u@%p, where %d is the size in bytes and %d is the the pointer to the memory. Memory cached files are destroyed upon downloader destruction*/ GF_NETIO_SESSION_MEMORY_CACHE = 1<<4, + /*! do not delete files after download*/ + GF_NETIO_SESSION_KEEP_CACHE = 1<<5, } GF_NetIOFlags; @@ -184,7 +185,7 @@ typedef struct /*error code if any. Valid for all message types.*/ GF_Err error; /*!data received or data to send. Only valid for GF_NETIO_GET_CONTENT and GF_NETIO_DATA_EXCHANGE (when no cache is setup) messages*/ - const char *data; + const u8 *data; /*!size of associated data. Only valid for GF_NETIO_GET_CONTENT and GF_NETIO_DATA_EXCHANGE messages*/ u32 size; /*protocol header. Only valid for GF_NETIO_GET_HEADER, GF_NETIO_PARSE_HEADER and GF_NETIO_GET_METHOD*/ @@ -204,27 +205,27 @@ typedef struct } GF_NETIO_Parameter; /*! - *\brief callback function for data reception and state signaling - * - * The gf_dm_user_io type is the type for the data callback function of a download session - *\param usr_cbk opaque user data - *\param parameter the input/output parameter structure +\brief callback function for data reception and state signaling + +The gf_dm_user_io type is the type for the data callback function of a download session +\param usr_cbk opaque user data +\param parameter the input/output parameter structure */ typedef void (*gf_dm_user_io)(void *usr_cbk, GF_NETIO_Parameter *parameter); /*! - *\brief download session constructor - * - *Creates a new download session - *\param dm the download manager object - *\param url file to retrieve (no PUT/POST yet, only downloading is supported) - *\param dl_flags combination of session download flags - *\param user_io \ref gf_dm_user_io callback function for data reception and service messages - *\param usr_cbk opaque user data passed to callback function - *\param error error for failure cases - *\return the session object or NULL if error. If no error is indicated and a NULL session is returned, this means the file is local +\brief download session constructor + +Creates a new download session +\param dm the download manager object +\param url file to retrieve (no PUT/POST yet, only downloading is supported) +\param dl_flags combination of session download flags +\param user_io specifies \ref gf_dm_user_io callback function for data reception and service messages +\param usr_cbk opaque user data passed to callback function +\param error error for failure cases +\return the session object or NULL if error. If no error is indicated and a NULL session is returned, this means the file is local */ GF_DownloadSession * gf_dm_sess_new(GF_DownloadManager *dm, const char *url, u32 dl_flags, gf_dm_user_io user_io, @@ -232,16 +233,16 @@ GF_DownloadSession * gf_dm_sess_new(GF_DownloadManager *dm, const char *url, u32 GF_Err *error); /*! - *\brief download session simple constructor - * - *Creates a new download session - *\param dm The download manager used to create the download session - *\param url file to retrieve (no PUT/POST yet, only downloading is supported) - *\param dl_flags combination of session download flags - *\param user_io \ref gf_dm_user_io callback function for data reception and service messages - *\param usr_cbk opaque user data passed to callback function - *\param e error for failure cases - *\return the session object or NULL if error. If no error is indicated and a NULL session is returned, this means the file is local +\brief download session simple constructor + +Creates a new download session +\param dm The download manager used to create the download session +\param url file to retrieve (no PUT/POST yet, only downloading is supported) +\param dl_flags combination of session download flags +\param user_io specifies \ref gf_dm_user_io callback function for data reception and service messages +\param usr_cbk opaque user data passed to callback function +\param e error for failure cases +\return the session object or NULL if error. If no error is indicated and a NULL session is returned, this means the file is local */ GF_DownloadSession *gf_dm_sess_new_simple(GF_DownloadManager * dm, const char *url, u32 dl_flags, gf_dm_user_io user_io, @@ -249,327 +250,305 @@ GF_DownloadSession *gf_dm_sess_new_simple(GF_DownloadManager * dm, const char *u GF_Err *e); /*! - *brief downloader session destructor - * - *Deletes the download session, cleaning the cache if indicated in the configuration file of the download manager (section "Downloader", key "CleanCache") - *\param sess the download session + \brief downloader session destructor + +Deletes the download session, cleaning the cache if indicated in the configuration file of the download manager (section "Downloader", key "CleanCache") +\param sess the download session */ void gf_dm_sess_del(GF_DownloadSession * sess); /*! - *\brief aborts downloading - * - *Aborts all operations in the session, regardless of its state. The session cannot be reused once this is called. - *\param sess the download session - */ -void gf_dm_sess_abort(GF_DownloadSession * sess); +\brief aborts downloading -/*! - *\brief sets private data - * - *associate private data with the session. - *\param sess the download session - *\param private_data the private data - *\warning the private_data parameter is reserved for bandwidth statistics per service when used in the GPAC terminal. +Aborts all operations in the session, regardless of its state. The session cannot be reused once this is called. +\param sess the download session */ -void gf_dm_sess_set_private(GF_DownloadSession * sess, void *private_data); +void gf_dm_sess_abort(GF_DownloadSession * sess); /*! - *\brief gets private data - * - *Gets private data associated with the session. - *\param sess the download session - *\return the private data - *\warning the private_data parameter is reserved for bandwidth statistics per service when used in the GPAC terminal. - */ -void *gf_dm_sess_get_private(GF_DownloadSession * sess); +\brief gets last session error -/*! - *\brief gets last session error - * - *Gets the last error that occured in the session - *\param sess the download session - *\return the last error +Gets the last error that occured in the session +\param sess the download session +\return the last error */ GF_Err gf_dm_sess_last_error(GF_DownloadSession *sess); -/*! - *\brief is download manager thread dead? - * - *Indicates whether the thread has ended - *\param sess the download session - */ -Bool gf_dm_is_thread_dead(GF_DownloadSession *sess); /*! - *\brief fetches data on session - * - *Fetches data from the server. This will also performs connections and all needed exchange with server. - *\param sess the download session - *\param buffer destination buffer - *\param buffer_size destination buffer allocated size - *\param read_size amount of data actually fetched - *\note this can only be used when the session is not threaded +\brief fetches data on session + +Fetches data from the server. This will also performs connections and all needed exchange with server. +\param sess the download session +\param buffer destination buffer +\param buffer_size destination buffer allocated size +\param read_size amount of data actually fetched +\note this can only be used when the session is not threaded +\return error if any */ GF_Err gf_dm_sess_fetch_data(GF_DownloadSession * sess, char *buffer, u32 buffer_size, u32 *read_size); /*! - *\brief get mime type as lower case - * - *Fetches the mime type of the URL this session is fetching, value will be returned lower case, so application/x-mpegURL will be returned as application/x-mpegurl - *\param sess the download session - *\return the mime type of the URL, or NULL if error. You should get the error with \ref gf_dm_sess_last_error +\brief get mime type as lower case + +Fetches the mime type of the URL this session is fetching, value will be returned lower case, so application/x-mpegURL will be returned as application/x-mpegurl +\param sess the download session +\return the mime type of the URL, or NULL if error. You should get the error with \ref gf_dm_sess_last_error */ const char *gf_dm_sess_mime_type(GF_DownloadSession * sess); /*! - *\brief sets session range - * - *Sets the session byte range. This shll be called before processing the session. - *\param sess the download session - *\param start_range HTTP download start range in byte - *\param end_range HTTP download end range in byte - *\param discontinue_cache If set, forces a new cache entry if byte range are not continuous. Otherwise a single cache entry is used to reconstruct the file - *\note this can only be used when the session is not threaded +\brief sets session range + +Sets the session byte range. This shll be called before processing the session. +\param sess the download session +\param start_range HTTP download start range in byte +\param end_range HTTP download end range in byte +\param discontinue_cache If set, forces a new cache entry if byte range are not continuous. Otherwise a single cache entry is used to reconstruct the file +\note this can only be used when the session is not threaded +\return error if any */ GF_Err gf_dm_sess_set_range(GF_DownloadSession *sess, u64 start_range, u64 end_range, Bool discontinue_cache); /*! - *\brief get cache file name - * - * Gets the cache file name for the session. - *\param sess the download session - *\return the absolute path of the cache file, or NULL if the session is not cached*/ +\brief get cache file name + +Gets the cache file name for the session. +\param sess the download session +\return the absolute path of the cache file, or NULL if the session is not cached*/ const char *gf_dm_sess_get_cache_name(GF_DownloadSession * sess); /*! - * \brief Marks the cache file to be deleted once the file is not used anymore by any session - * \param dm the download manager - * \param url The URL associate to the cache entry to be deleted +\brief Marks the cache file to be deleted once the file is not used anymore by any session +\param dm the download manager +\param url The URL associate to the cache entry to be deleted */ void gf_dm_delete_cached_file_entry(const GF_DownloadManager * dm, const char * url); /*! - * Convenience function - * \see gf_dm_delete_cached_file_entry - *\param sess the download session - * \param url The URL associate to the cache entry to be deleted +Marks the cache file for this session to be deleted once the file is not used anymore by any session +\param sess the download session +\param url The URL associate to the cache entry to be deleted */ void gf_dm_delete_cached_file_entry_session(const GF_DownloadSession * sess, const char * url); /*! - * Get a range of a cache entry file - * \param sess The session - * \param startOffset The first byte of the request to get - * \param endOffset The last byte of request to get - * \return The temporary name for the file created to have a range of the file - */ -const char * gf_cache_get_cache_filename_range( const GF_DownloadSession * sess, u64 startOffset, u64 endOffset ); +\brief get statistics -/*! - *\brief get statistics - * - *Gets download statistics for the session. All output parameters are optional and may be set to NULL. - *\param sess the download session - *\param server the remote server address - *\param path the path on the remote server - *\param total_size the total size in bytes the file fetched, 0 if unknown. - *\param bytes_done the amount of bytes received from the server - *\param bytes_per_sec the average data rate in bytes per seconds - *\param net_status the session status +Gets download statistics for the session. All output parameters are optional and may be set to NULL. +\param sess the download session +\param server the remote server address +\param path the path on the remote server +\param total_size the total size in bytes the file fetched, 0 if unknown. +\param bytes_done the amount of bytes received from the server +\param bytes_per_sec the average data rate in bytes per seconds +\param net_status the session status +\return error if any */ -GF_Err gf_dm_sess_get_stats(GF_DownloadSession * sess, const char **server, const char **path, u32 *total_size, u32 *bytes_done, u32 *bytes_per_sec, GF_NetIOStatus *net_status); +GF_Err gf_dm_sess_get_stats(GF_DownloadSession * sess, const char **server, const char **path, u64 *total_size, u64 *bytes_done, u32 *bytes_per_sec, GF_NetIOStatus *net_status); /*! - *\brief get start time - * - *Gets session start time in UTC. If chunk-transfer is used, the start time is reset at each chunk start - *\param sess the download session - *\return UTC start time +\brief get start time + +Gets session start time in UTC. If chunk-transfer is used, the start time is reset at each chunk start +\param sess the download session +\return UTC start time */ u64 gf_dm_sess_get_utc_start(GF_DownloadSession *sess); /*! - *\brief fetch session object - * - *Fetches the session object (process all headers and data transfer). This is only usable if the session is not threaded - *\param sess the download session - *\return the last error in the session or 0 if none*/ +\brief fetch session object + +Fetches the session object (process all headers and data transfer). This is only usable if the session is not threaded +\param sess the download session +\return the last error in the session or 0 if none*/ GF_Err gf_dm_sess_process(GF_DownloadSession *sess); /*! - *\brief fetch session object headers - * - *Fetch the session object headers and stops after that. This is only usable if the session is not threaded - *\param sess the download session - *\return the last error in the session or 0 if none*/ +\brief fetch session object headers + +Fetch the session object headers and stops after that. This is only usable if the session is not threaded +\param sess the download session +\return the last error in the session or 0 if none*/ GF_Err gf_dm_sess_process_headers(GF_DownloadSession * sess); /*! - *\brief fetch session status - * - *Fetch the session current status - *\param sess the download session - *\return the session status*/ -u32 gf_dm_sess_get_status(GF_DownloadSession * sess); +\brief Get session resource url -/*! - *\brief Get session resource url - * - *Returns the original resource URL associated with the session - *\param sess the download session - *\return the associated URL +Returns the original resource URL associated with the session +\param sess the download session +\return the associated URL */ const char *gf_dm_sess_get_resource_name(GF_DownloadSession *sess); -/*! - *\brief Get session original resource url - * - *Returns the original resource URL before any redirection associated with the session - *\param sess the download session - *\return the associated URL - */ -const char *gf_dm_sess_get_original_resource_name(GF_DownloadSession *sess); - #ifndef GPAC_DISABLE_CORE_TOOLS /*! - * \brief Download a file over the network using a download manager - * \param dm The download manager to use, function will use all associated cache ressources - * \param url The url to download - * \param filename The filename to download - * \param start_range start position of a byte range - * \param end_range end position of a byte range - * \param redirected_url If not NULL, \p redirected_url will be allocated and filled with the URL after redirection. Caller takes ownership - * \return GF_OK if everything went fine, an error otherwise +Downloads a file over the network using a download manager +\param dm The download manager to use, function will use all associated cache ressources +\param url The url to download +\param filename The filename to download +\param start_range start position of a byte range +\param end_range end position of a byte range +\param redirected_url If not NULL, \p redirected_url will be allocated and filled with the URL after redirection. Caller takes ownership +\return GF_OK if everything went fine, an error otherwise */ GF_Err gf_dm_wget_with_cache(GF_DownloadManager * dm, const char *url, const char *filename, u64 start_range, u64 end_range, char **redirected_url); /*! - * \brief Same as gf_dm_wget_with_cache, but initializes the GF_DownloadManager by itself. - * This function is deprecated, please use gf_dm_wget_with_cache instead - * \param url The url to download - * \param filename The filename to download - * \param start_range start position of a byte range - * \param end_range end position of a byte range - * \param redirected_url If not NULL, \p redirected_url will be allocated and filled with the URL after redirection. Caller takes ownership - * \return GF_OK if everything went fine, an error otherwise +\brief Same as gf_dm_wget_with_cache, but initializes the GF_DownloadManager by itself. + +This function is deprecated, please use gf_dm_wget_with_cache instead +\param url The url to download +\param filename The filename to download +\param start_range start position of a byte range +\param end_range end position of a byte range +\param redirected_url If not NULL, \p redirected_url will be allocated and filled with the URL after redirection. Caller takes ownership +\return GF_OK if everything went fine, an error otherwise */ GF_Err gf_dm_wget(const char *url, const char *filename, u64 start_range, u64 end_range, char **redirected_url); #endif /* GPAC_DISABLE_CORE_TOOLS */ /*! - *\brief Reset session - * - *Resets the session for new processing of the same url - *\param sess the download session - *\return error code if any +Re-setup an existing, completed session to download a new URL. If same server/port/protocol is used, the same socket will be reused if the session has the GF_NETIO_SESSION_PERSISTENT flag set. This is only possible if the session is not threaded. +\param sess The session +\param url The new url for the session +\param allow_direct_reuse Allow reuse of cache entry without checking cache directives +\return GF_OK or error */ -GF_Err gf_dm_sess_reset(GF_DownloadSession *sess); +GF_Err gf_dm_sess_setup_from_url(GF_DownloadSession *sess, const char *url, Bool allow_direct_reuse); /*! - * \brief forces the refresh of a cache entry - * The entry is still allocated in the session. - * \param sess The session - * \return a pointer to the entry of session refreshed +\brief retrieves the HTTP header value for the given name + +Retrieves the HTTP header value for the given header name. +\param sess the current session +\param name the target header name +\return header value or NULL if not found */ -DownloadedCacheEntry gf_dm_refresh_cache_entry(GF_DownloadSession *sess); +const char *gf_dm_sess_get_header(GF_DownloadSession *sess, const char *name); /*! - * Tells whether session can be cached on disk. - * Typically, when request has no content length, it deserves being streamed an cannot be cached - * (ICY or MPEG-streamed content - * \param sess The session - * \return True if a cache can be created - */ -Bool gf_dm_sess_can_be_cached_on_disk(const GF_DownloadSession *sess); +\brief enumerates the HTTP headers for a session +Retrieves the HTTP header name and value for the given header index. +\param sess the current session +\param idx index for the enumeration, must be initialized to 0 for the first call +\param hdr_name where name of header is stored - do not free +\param hdr_val where value of header is stored - do not free +\return error code, GF_OK or GF_EOS if no more headers + */ +GF_Err gf_dm_sess_enum_headers(GF_DownloadSession *sess, u32 *idx, const char **hdr_name, const char **hdr_val); /*! - * Reassigns session flags and callbacks. This is only possible if the session is not threaded. - * \param sess The session - * \param flags The new flags for the session - if flags is 0xFFFFFFFF, existing flags are not modified - * \param user_io The new callback function - * \param cbk The new user data to ba used in the callback function - * \return GF_OK or error +\brief sets download manager max rate per session + +Sets the maximum rate (per session only at the current time). +\param dm the download manager object +\param rate_in_bits_per_sec the new rate in bits per sec. If 0, HTTP rate will not be limited */ -GF_Err gf_dm_sess_reassign(GF_DownloadSession *sess, u32 flags, gf_dm_user_io user_io, void *cbk); +void gf_dm_set_data_rate(GF_DownloadManager *dm, u32 rate_in_bits_per_sec); /*! - * Re-setup an existing, completed session to download a new URL. If same server/port/protocol is used, the same socket will be reused if the session - * has the GF_NETIO_SESSION_PERSISTENT flag set. This is only possible if the session is not threaded. - * \param sess The session - * \param url The new url for the session - * \return GF_OK or error - */ -GF_Err gf_dm_sess_setup_from_url(GF_DownloadSession *sess, const char *url); +\brief gets download manager max rate per session -/* - *\retrieves the HTTP header value for the given name - * - *Retrieves the HTTP header value for the given header name. - *\param sess the current session - *\param name the target header name - * \return header value or NULL if not found +Sets the maximum rate (per session only at the current time). +\param dm the download manager object +\return the rate in bits per sec. If 0, HTTP rate is not limited */ -const char *gf_dm_sess_get_header(GF_DownloadSession *sess, const char *name); +u32 gf_dm_get_data_rate(GF_DownloadManager *dm); -/* - *\brief sets download manager max rate per session - * - *Sets the maximum rate (per session only at the current time). - *\param dm the download manager object - *\param rate_in_bits_per_sec the new rate in bits per sec. If 0, HTTP rate will not be limited + +/*! +\brief gets cumultaed download rate for all sessions + +Gets the cumultated bitrate in of all active sessions. +\param dm the download manager object +\return the cumulated rate in bits per sec. */ -void gf_dm_set_data_rate(GF_DownloadManager *dm, u32 rate_in_bits_per_sec); +u32 gf_dm_get_global_rate(GF_DownloadManager *dm); -/* - *\brief gets download manager max rate per session - * - *Sets the maximum rate (per session only at the current time). - *\param dm the download manager object - *\return the rate in bits per sec. If 0, HTTP rate is not limited + +/*! +\brief Get header sizes and times stats for the session + +Get header sizes and times stats for the session +\param sess the current session +\param req_hdr_size request header size in bytes. May be NULL. +\param rsp_hdr_size response header size in bytes. May be NULL. +\param connect_time connection time in micro seconds. May be NULL. +\param reply_time elapsed time between request sent and response header received, in micro seconds. May be NULL. +\param download_time download time since request sent, in micro seconds. May be NULL. +\return error code if any */ -u32 gf_dm_get_data_rate(GF_DownloadManager *dm); +GF_Err gf_dm_sess_get_header_sizes_and_times(GF_DownloadSession *sess, u32 *req_hdr_size, u32 *rsp_hdr_size, u32 *connect_time, u32 *reply_time, u32 *download_time); +/*! +\brief Forces session to use memory storage -/* - *\brief gets cumultaed download rate for all sessions - * - *Gets the cumultated bitrate in of all active sessions. - *\param dm the download manager object - *\return the cumulated rate in bits per sec. +Forces session to use memory storage for future downloads +\param sess the current session */ -u32 gf_dm_get_global_rate(GF_DownloadManager *dm); +void gf_dm_sess_force_memory_mode(GF_DownloadSession *sess); +/*! +Registers a locacl cache provider (bypassing the http session), used when populating cache from input data (atsc for example) -/* - *\brief fetches remote file in memory - * - *Fetches remote file in memory. - *\param url the data to fetch - *\param out_data output data (allocated by function) - *\param out_size output data size - *\param out_mime if not NULL, pointer will contain the mime type (allocated by function) - *\return error code if any +\param dm the download manager +\param local_cache_url_provider_cbk callback function to the cache provider. The callback function shall return GF_TRUE if the requested URL is provided by this local cache +\param lc_udta opaque pointer passed to the callback function +\return error code if any */ -GF_Err gf_dm_get_file_memory(const char *url, char **out_data, u32 *out_size, char **out_mime); +GF_Err gf_dm_set_localcache_provider(GF_DownloadManager *dm, Bool (*local_cache_url_provider_cbk)(void *udta, char *url, Bool is_cache_destroy), void *lc_udta); +/*! +Adds a local entry in the cache -/* - *\brief Get header sizes and times stats for the session - * - *Get header sizes and times stats for the session - *\param sess the current session - *\param req_hdr_size request header size in bytes. May be NULL. - *\param rsp_hdr_size response header size in bytes. May be NULL. - *\param connect_time connection time in micro seconds. May be NULL. - *\param reply_time elapsed time between request sent and response header received, in micro seconds. May be NULL. - *\param download_time download time since request sent, in micro seconds. May be NULL. - *\return error code if any +\param dm the download manager +\param szURL the URL this resource is caching +\param data data of the resource +\param size size of the resource +\param start_range start range of the data in the resource +\param end_range start range of the data in the resource. If both start_range and end_range are 0, the data is the complete resource +\param mime associated MIME type if any +\param clone_memory indicates that the data shall be cloned in the cache because the caller will discard it +\param download_time_ms indicates the download time of the associated resource, if known, 0 otherwise. +\return a cache entry structure */ -GF_Err gf_dm_sess_get_header_sizes_and_times(GF_DownloadSession *sess, u32 *req_hdr_size, u32 *rsp_hdr_size, u32 *connect_time, u32 *reply_time, u32 *download_time); +const DownloadedCacheEntry gf_dm_add_cache_entry(GF_DownloadManager *dm, const char *szURL, u8 *data, u64 size, u64 start_range, u64 end_range, const char *mime, Bool clone_memory, u32 download_time_ms); + +/*! +Forces HTTP headers for a given cache entry +\param dm the download manager +\param entry the cache entry to update +\param headers the header string, including CRLF delimiters, to force +\return error code if any +*/ +GF_Err gf_dm_force_headers(GF_DownloadManager *dm, const DownloadedCacheEntry entry, const char *headers); + +/*! HTTP methods*/ +enum +{ + /*! unsupported*/ + GF_HTTP_UNKNOWN = 0, + /*! GET*/ + GF_HTTP_GET, + /*! HEAD*/ + GF_HTTP_HEAD, + /*! OPTIONS*/ + GF_HTTP_OPTIONS, + /*! CONNECT*/ + GF_HTTP_CONNECT, + /*! TRACE*/ + GF_HTTP_TRACE, + /*! PUT*/ + GF_HTTP_PUT, + /*! POST*/ + GF_HTTP_POST, + /*! DELETE*/ + GF_HTTP_DELETE +}; /*! @} */ diff --git a/include/gpac/dsmcc.h b/include/gpac/dsmcc.h index 444db69..a32ec1a 100644 --- a/include/gpac/dsmcc.h +++ b/include/gpac/dsmcc.h @@ -66,7 +66,7 @@ typedef struct { u8 descriptor_length; u32 carousel_id; u8 FormatID; - char *private_data_byte; + u8 *private_data_byte; u8 ModuleVersion; u8 ModuleId; u16 BlockSize; @@ -75,7 +75,7 @@ typedef struct { u32 OriginalSize; u8 TimeOut; u8 ObjectKeyLength; - char* ObjectKeyData; + u8* ObjectKeyData; } GF_M2TS_CAROUSEL_INDENTIFIER_DESCRIPTOR; typedef struct @@ -184,7 +184,7 @@ typedef struct { u8 subDescriptorType; u8 subDescriptorLength; - char *additionalInformation; + u8 *additionalInformation; } GF_M2TS_DSMCC_SUBDESCRIPTOR; diff --git a/include/gpac/esi.h b/include/gpac/esi.h deleted file mode 100644 index 90d0c7a..0000000 --- a/include/gpac/esi.h +++ /dev/null @@ -1,213 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2006-2012 - * All rights reserved - * - * This file is part of GPAC / Elementary Stream Interface sub-project - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef _GF_ESI_H_ -#define _GF_ESI_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -/*! - * \file - * \brief Draft elementary stream interface API used by MP42TS. - */ - -/*! - * \addtogroup esi_grp ES Interface - * \ingroup media_grp - * \brief Draft elementary stream interface API used by MP42TS. - * - *This section documents the draft ES interface used by MP42TS to abstract input sources. - * @{ - */ - -#include - -/* ESI input control commands*/ -enum -{ - /*forces a data flush from interface to dest (caller) - used for non-threaded interfaces - corresponding parameter: unused - */ - GF_ESI_INPUT_DATA_FLUSH, - /*pulls a COMPLETE AU from the stream - corresponding parameter: pointer to a GF_ESIPacket to fill. The indut data_len in the packet is used to indicate any padding in bytes - */ - GF_ESI_INPUT_DATA_PULL, - /*releases the currently pulled AU from the stream - AU cannot be pulled after that, unless seek happens - corresponding parameter: unused - */ - GF_ESI_INPUT_DATA_RELEASE, - - /*destroys any allocated resource by the stream interface*/ - GF_ESI_INPUT_DESTROY, -}; - -/* ESI output control commands*/ -enum -{ - /*forces a data flush from interface to dest (caller) - used for non-threaded interfaces - corresponding parameter: GF_ESIPacket - */ - GF_ESI_OUTPUT_DATA_DISPATCH -}; - -/* - data packet flags -*/ -enum -{ - GF_ESI_DATA_AU_START = 1, - GF_ESI_DATA_AU_END = 1<<1, - GF_ESI_DATA_AU_RAP = 1<<2, - GF_ESI_DATA_HAS_CTS = 1<<3, - GF_ESI_DATA_HAS_DTS = 1<<4, - GF_ESI_DATA_REPEAT = 1<<5, - GF_ESI_DATA_CRITICAL = 1<<6, - GF_ESI_DATA_ENCRYPTED = 1<<7, -}; - -typedef struct __data_packet_ifce -{ - u32 flags; - char *data; - u32 data_len; - /*DTS, CTS/PTS and duration expressed in media timescale*/ - u64 dts, cts; - u32 duration; - u32 pck_sn; - /*MPEG-4 stuff*/ - u32 au_sn; - /*for packets using ISMACrypt/OMA/3GPP based crypto*/ - u32 isma_bso; - - char *mpeg2_af_descriptors; - u32 mpeg2_af_descriptors_size; -} GF_ESIPacket; - -struct __esi_video_info -{ - u32 width, height, par; - Double FPS; -}; -struct __esi_audio_info -{ - u32 sample_rate, nb_channels; -}; - -enum -{ - /*data can be pulled from this stream*/ - GF_ESI_AU_PULL_CAP = 1, - /*DTS is signaled for this stream*/ - GF_ESI_SIGNAL_DTS = 1<<1, - /*no more data to expect from this stream*/ - GF_ESI_STREAM_IS_OVER = 1<<2, - /*stream is not signaled through MPEG-4 Systems (OD stream) */ - GF_ESI_STREAM_WITHOUT_MPEG4_SYSTEMS = 1<<3, -}; - -typedef struct __elementary_stream_ifce -{ - /*misc caps of the stream*/ - u32 caps; - /*matches PID for MPEG2, ES_ID for MPEG-4*/ - u32 stream_id; - /*MPEG-TS program number if any*/ - u16 program_number; - /*MPEG-4 ST/OTIs*/ - u8 stream_type; - u8 object_type_indication; - /*stream 4CC for non-mpeg codecs, 0 otherwise (stream is identified through StreamType/ObjectType)*/ - u32 fourcc; - /*packed 3-char language code (4CC with last byte ' ')*/ - u32 lang; - /*media timescale*/ - u32 timescale; - /*duration in ms - 0 if unknown*/ - Double duration; - /*average bit rate in bit/sec - 0 if unknown*/ - u32 bit_rate; - /*repeat rate in ms for carrouseling - 0 if no repeat*/ - u32 repeat_rate; - - char *decoder_config; - u32 decoder_config_size; - - /* MPEG-4 SL Config if any*/ - GF_SLConfig *sl_config; - - struct __esi_video_info info_video; - struct __esi_audio_info info_audio; - - /*input ES control from caller*/ - GF_Err (*input_ctrl)(struct __elementary_stream_ifce *_self, u32 ctrl_type, void *param); - /*input user data of interface - usually set by interface owner*/ - void *input_udta; - - /*output ES control of destination*/ - GF_Err (*output_ctrl)(struct __elementary_stream_ifce *_self, u32 ctrl_type, void *param); - /*output user data of interface - usually set during interface setup*/ - void *output_udta; - - u32 depends_on_stream; - -} GF_ESInterface; - -typedef struct __service_ifce -{ - u32 type; - - /*input service control from caller*/ - GF_Err (*input_ctrl)(struct __service_ifce *_self, u32 ctrl_type, void *param); - /*input user data of interface - usually set by interface owner*/ - void *input_udta; - - /*output service control of destination*/ - GF_Err (*output_ctrl)(struct __service_ifce *_self, u32 ctrl_type, void *param); - /*output user data of interface - usually set during interface setup*/ - void *output_udta; - - GF_ESInterface **streams; - u32 nb_streams; -} GF_ServiceInterface; - - -typedef struct __data_io -{ - u32 (*read)(struct __data_io *_self, char *buffer, u32 nb_bytes); - u32 (*write)(struct __data_io *_self, char *buffer, u32 nb_bytes); - void *udta; -} GF_DataIO; - -/*! @} */ - -#ifdef __cplusplus -} -#endif - -#endif //_GF_ESI_H_ - diff --git a/include/gpac/events.h b/include/gpac/events.h index 1d8d3b4..93523ce 100644 --- a/include/gpac/events.h +++ b/include/gpac/events.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / Events management @@ -33,17 +33,17 @@ extern "C" { #endif /*! - * \file - * \brief Event system used by GPAC playback. +\file +\brief Event system used by GPAC playback. */ /*! - * \addtogroup evt_grp Event System - * \ingroup playback_grp - * \brief Event system used by GPAC playback. - * - *This section documents the event structures used by the terminal, the compositor, input modules and output rendering modules for communication. - * @{ +\addtogroup evt_grp Event System +\ingroup playback_grp +\brief Event system used by GPAC playback. + +This section documents the event structures used by the terminal, the compositor, input modules and output rendering modules for communication. +@{ */ #include @@ -51,7 +51,7 @@ extern "C" { #include -/*mouse button modifiers*/ +/*! mouse button modifiers*/ enum { GF_MOUSE_LEFT = 0, @@ -59,7 +59,9 @@ enum GF_MOUSE_RIGHT }; -/*event proc return value: ignored*/ +/*! Mouse event structure + event proc return value: ignored +*/ typedef struct { /*GF_EVENT_MOUSEMOVE, GF_EVENT_MOUSEWHEEL, GF_EVENT_MOUSEDOWN, GF_EVENT_MOUSEUP*/ @@ -74,7 +76,26 @@ typedef struct u32 key_states; } GF_EventMouse; -/*event proc return value: ignored*/ +/*! Mouse event structure + event proc return value: ignored +*/ +typedef struct +{ + /*GF_EVENT_MULTITOUCH*/ + u8 type; + /*normalized center of multitouch event*/ + Fixed x, y; + /*finger rotation*/ + Fixed rotation; + /*finger pinch*/ + Fixed pinch; + /*number of fingers detected*/ + u32 num_fingers; +} GF_EventMultiTouch; + +/*! Keyboard key event + event proc return value: ignored +*/ typedef struct { /*GF_EVENT_KEYDOWN and GF_EVENT_KEYUP*/ @@ -87,7 +108,9 @@ typedef struct u32 flags; } GF_EventKey; -/*event proc return value: ignored*/ +/*! Keyboard character event + event proc return value: ignored +*/ typedef struct { /*GF_EVENT_TEXTINPUT*/ @@ -96,40 +119,46 @@ typedef struct u32 unicode_char; } GF_EventChar; -/*event proc return value: ignored*/ +/*! Window resize event + event proc return value: ignored +*/ typedef struct { /*GF_EVENT_SIZE*/ u8 type; /*width and height*/ - u16 width, height; + u32 width, height; } GF_EventSize; -/*event proc return value: ignored*/ +/*! Video setup (2D or 3D) event + event proc return value: ignored +*/ typedef struct { /*GF_EVENT_VIDEO_SETUP*/ u8 type; /*width and height of visual surface to allocate*/ - u16 width, height; + u32 width, height; /*indicates whether double buffering is desired - when sent from plugin to player, indicates the backbuffer has been destroyed*/ Bool back_buffer; /*indicates whether system memory for the backbuffer is desired (no video blitting)*/ Bool system_memory; /*indicates whether opengl context shall be created. Values are: - 0: no opengl context shall be created - 1: opengl context shall be created for the main window and set as the current one - 2: an extra opengl context shall be created for offscreen rendering and set as the current one - if not supported, mix of 2D (raster) and 3D (openGL) will be disabled + GF_FALSE: no opengl context shall be created + GF_TRUE: opengl context shall be created for the main window and set as the current one */ - u32 opengl_mode; + Bool use_opengl; + + Bool disable_vsync; // resources must be resetup before next render step (this is mainly due to discard all openGL textures and cached objects) - inly used when sent from plugin to term Bool hw_reset; } GF_EventVideoSetup; -/*event proc return value: ignored -this event may be triggered by the compositor if owning window or if shortcut fullscreen is detected*/ +/*! Windows show event + event proc return value: ignored + this event may be triggered by the compositor if owning window or if shortcut fullscreen is detected +*/ typedef struct { /*GF_EVENT_SHOWHIDE*/ @@ -138,7 +167,9 @@ typedef struct u32 show_type; } GF_EventShow; -/*event proc return value: ignored*/ +/*! Mouse cursor event + event proc return value: ignored +*/ typedef struct { /*GF_EVENT_SET_CURSOR*/ @@ -147,16 +178,19 @@ typedef struct u32 cursor_type; } GF_EventCursor; -/*event proc return value: ignored*/ +/*! Window caption event + event proc return value: ignored +*/ typedef struct { /*GF_EVENT_SET_CAPTION*/ u8 type; - /*window style flags - NOT USED YET*/ const char *caption; } GF_EventCaption; -/*event proc: never posted*/ +/*! Window move event + event proc: never posted +*/ typedef struct { /*GF_EVENT_MOVE*/ @@ -168,8 +202,10 @@ typedef struct u8 align_x, align_y; } GF_EventMove; -/*duration may be signaled several times: it may change when setting up streams -event proc return value: ignored*/ +/*! Media duration event + duration may be signaled several times: it may change when setting up streams + event proc return value: ignored +*/ typedef struct { /*GF_EVENT_DURATION*/ @@ -180,8 +216,9 @@ typedef struct Bool can_seek; } GF_EventDuration; -/*event proc return value: 0 if URL not supported, 1 if accepted (it is the user responsability to load the url) -YOU SHALL NOT DIRECTLY OPEN THE NEW URL IN THE EVENT PROC, THIS WOULD DEADLOCK THE TERMINAL +/*! Hyperlink navigation event + event proc return value: 0 if URL not supported, 1 if accepted (it is the user responsability to load the url) + YOU SHALL NOT DIRECTLY OPEN THE NEW URL IN THE EVENT PROC, THIS WOULD DEADLOCK THE TERMINAL */ typedef struct { @@ -195,7 +232,9 @@ typedef struct } GF_EventNavigate; -/*event proc return value: ignored*/ +/*! Service message event + event proc return value: ignored +*/ typedef struct { /*GF_EVENT_MESSAGE*/ @@ -208,14 +247,16 @@ typedef struct GF_Err error; } GF_EventMessage; -/*event proc return value: ignored*/ +/*! Progress event + event proc return value: ignored +*/ typedef struct { /*GF_EVENT_PROGRESS*/ u8 type; /*name of service issuing the progress notif*/ const char *service; - /*progress type: 0: buffering, 1: downloading, 2: importing (BT/VRML/...)*/ + /*progress type: 0: buffering, 1: downloading, 2: importing (BT/VRML/...), 3: filter status update*/ u32 progress_type; /*amount done and total amount of operation. For buffer events, expresses current and total desired stream buffer in scene in milliseconds @@ -223,11 +264,18 @@ typedef struct For import events, no units defined (depends on importers) */ u32 done, total; - /* for download events */ - u32 bytes_per_seconds; + union { + /* for download events */ + u32 bytes_per_seconds; + /* for filter status event, index of filter in session */ + u32 filter_idx; + }; + } GF_EventProgress; -/*event proc return value: ignored*/ +/*! Service connection event + event proc return value: ignored +*/ typedef struct { /*GF_EVENT_CONNECT*/ @@ -236,7 +284,9 @@ typedef struct Bool is_connected; } GF_EventConnect; -/*event proc return value: 1 to indicate the terminal should attempt a default layout for this addon, 0: nothing will be done*/ +/*! Addon connection notification event + event proc return value: 1 to indicate the terminal should attempt a default layout for this addon, 0: nothing will be done +*/ typedef struct { /*GF_EVENT_ADDON_DETECTED*/ @@ -245,8 +295,9 @@ typedef struct const char *mime_type; } GF_EventAddonConnect; -/*event proc return value: 1 if info has been completed, 0 otherwise (and operation this request was for -will then fail)*/ +/*! Authentication event + event proc return value: 1 if info has been completed, 0 otherwise (and operation this request was for will then fail) +*/ typedef struct { /*GF_EVENT_AUTHORIZATION*/ @@ -260,7 +311,9 @@ typedef struct } GF_EventAuthorize; -/*event proc return value: 1 if info has been completed, 0 otherwise */ +/*! System desktop colors and window decoration event + event proc return value: 1 if info has been completed, 0 otherwise +*/ typedef struct { /*GF_EVENT_SYS_COLORS*/ @@ -274,7 +327,7 @@ typedef struct u32 sys_colors[28]; } GF_EventSysColors; - +/*! DOM mutation event*/ typedef struct { /* GF_EVENT_TREE_MODIFIED, GF_EVENT_NODE_INSERTED, GF_EVENT_NODE_REMOVED, GF_EVENT_NODE_INSERTED_DOC, GF_EVENT_NODE_REMOVED_DOC, GF_EVENT_ATTR_MODIFIED, GF_EVENT_CHAR_DATA_MODIFIED */ u8 type; @@ -285,7 +338,7 @@ typedef struct { u8 attrChange; } GF_EventMutation; - +/*! Open file notification event*/ typedef struct { /* GF_EVENT_DROPFILE*/ u8 type; @@ -293,30 +346,21 @@ typedef struct { char **files; } GF_EventOpenFile; -typedef struct { - /*GF_EVENT_FROM_SERVICE*/ - u8 type; - //cf GF_EVT_FORWARDED_ * - u8 forward_type; - /*original type of event as forwarded by the service*/ - u32 service_event_type; - /*parameter of event as forwarded by the service - creation/deletion is handled by the service*/ - void *param; -} GF_EventFromService; - - - -/*event proc return value: ignored*/ +/*! Orientation sensor change event + event proc return value: ignored +*/ typedef struct { /*GF_EVENT_SENSOR_ORIENTATION*/ u8 type; - /*device orientation as quaternion*/ + /*device orientation as quaternion if w is not 0, or as radians otherwise*/ Float x, y, z, w; } GF_EventSensor; -/*event proc return value: ignored*/ +/*! Orientation sensor activation event + event proc return value: ignored +*/ typedef struct { /*GF_EVENT_SENSOR_REQUEST*/ @@ -326,10 +370,28 @@ typedef struct Bool activate; } GF_EventSensorRequest; + +/*! Clipboard + event proc return value: true if text has been set for COPY, ignored otherwise +*/ +typedef struct +{ + /*GF_EVENT_MESSAGE*/ + u8 type; + /* + - const char * for PASTE_TEXT + - char * for COPY_TEXT, must be freed by caller + */ + char *text; +} GF_EventClipboard; + + +/*! Event object*/ typedef union { u8 type; GF_EventMouse mouse; + GF_EventMultiTouch mtouch; GF_EventKey key; GF_EventChar character; GF_EventSensor sensor; @@ -349,8 +411,8 @@ typedef union GF_EventMutation mutation; GF_EventOpenFile open_file; GF_EventAddonConnect addon_connect; - GF_EventFromService from_service; GF_EventSensorRequest activate_sensor; + GF_EventClipboard clipboard; } GF_Event; /*! @} */ diff --git a/include/gpac/events_constants.h b/include/gpac/events_constants.h index fc605df..0b0e1e3 100644 --- a/include/gpac/events_constants.h +++ b/include/gpac/events_constants.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / Events management @@ -28,17 +28,14 @@ #define _GF_EVENTS_CONSTANTS_H_ /*! - * \file - * \brief Constants for event system used by GPAC playback. +\file +\brief Constants for event system used by GPAC playback. */ /*! - * \addtogroup evt_grp - * \ingroup playback_grp - * \brief Constants for event system used by GPAC playback. - * - *This section documents the event types used by the terminal, the compositor, input modules and output rendering modules for communication. - * @{ +\addtogroup evt_grp +\ingroup playback_grp +@{ */ @@ -48,7 +45,7 @@ DO NOT CHANGE THEIR POSITION IN THE LIST, USED TO SPEED UP FILTERING OF USER INPUT EVENTS */ -/*events*/ +/*! Event types*/ typedef enum { /****************************************************** @@ -74,6 +71,7 @@ typedef enum { /*character input*/ GF_EVENT_TEXTINPUT, + GF_EVENT_MULTITOUCH, /****************************************************** @@ -198,11 +196,9 @@ typedef enum { */ GF_EVENT_SCENE_SIZE, GF_EVENT_SHOWHIDE, /*window show/hide (minimized or other). */ - GF_EVENT_SHOWHIDE_NOTIF, /*window has been show/hide (minimized or other). This is sent to the user to signal focus switch in fullscreen*/ GF_EVENT_SET_CURSOR, /*set mouse cursor*/ GF_EVENT_SET_CAPTION, /*set window caption*/ GF_EVENT_MOVE, /*move window*/ - GF_EVENT_MOVE_NOTIF, /*move window*/ GF_EVENT_REFRESH, /*window needs repaint (whenever needed, eg restore, hide->show, background refresh, paint)*/ GF_EVENT_QUIT, /*app is being closed - associated structure is evt.message to carry any potential reason for quiting*/ /*video hw setup message: @@ -215,6 +211,8 @@ typedef enum { - when sent from gpac to user, indicate aspect ratio has been modified and video output is ready */ GF_EVENT_VIDEO_SETUP, + //set current GL context for the calling thread + GF_EVENT_SET_GL, /*queries the list of system colors - only exchanged between compositor and video output*/ GF_EVENT_SYS_COLORS, @@ -232,7 +230,6 @@ typedef enum { GF_EVENT_NAVIGATE_INFO, /*indicates the link or its description under the mouse pointer*/ GF_EVENT_MESSAGE, /*message from the MPEG-4 terminal*/ GF_EVENT_PROGRESS, /*progress message from the MPEG-4 terminal*/ - GF_EVENT_FORWARDED, /*event forwarded by service (MPEG-2, RTP, ...)*/ GF_EVENT_VIEWPOINTS, /*indicates viewpoint list has changed - no struct associated*/ GF_EVENT_STREAMLIST, /*indicates stream list has changed - no struct associated - only used when no scene info is present*/ GF_EVENT_METADATA, /*indicates a change in associated metadata*/ @@ -244,6 +241,7 @@ typedef enum { GF_EVENT_TEXT_EDITING_START, GF_EVENT_TEXT_EDITING_END, + //fire when quality change is detected by compositor GF_EVENT_QUALITY_SWITCHED, //fire when timeshift depth changes GF_EVENT_TIMESHIFT_DEPTH, @@ -255,17 +253,13 @@ typedef enum { GF_EVENT_TIMESHIFT_UNDERRUN, GF_EVENT_MAIN_ADDON_STATE, - GF_EVENT_FROM_SERVICE, - GF_EVENT_ADDON_DETECTED, - GF_EVENT_RELOAD, - GF_EVENT_SENSOR_ORIENTATION, GF_EVENT_SENSOR_REQUEST, } GF_EventType; -/*GPAC/DOM3 key codes*/ +/*! GPAC/DOM3 key codes*/ typedef enum { GF_KEY_UNIDENTIFIED = 0, GF_KEY_ACCEPT = 1, /* "Accept" The Accept (Commit) key.*/ @@ -484,7 +478,7 @@ typedef enum { } GF_KeyCode; -/*key modifiers state - set by terminal (not set by video driver)*/ +/*! key modifiers state - set by terminal (not set by video driver)*/ typedef enum { GF_KEY_MOD_SHIFT = (1), @@ -496,11 +490,14 @@ typedef enum GF_KEY_EXT_RIGHT = (1<<6) } GF_KeyModifier; -/*sensor signaling*/ +/*! Sensor apperance signaling*/ enum { + /*regular*/ GF_CURSOR_NORMAL = 0x00, + /*hyperlink*/ GF_CURSOR_ANCHOR, + /*touch/click*/ GF_CURSOR_TOUCH, /*discSensor, cylinderSensor, sphereSensor*/ GF_CURSOR_ROTATE, @@ -510,24 +507,21 @@ enum GF_CURSOR_PLANE, /*collision*/ GF_CURSOR_COLLIDE, + /*hide the cursor*/ GF_CURSOR_HIDE, }; -/*Mutation AttrChangeType Signaling*/ +#if 0 +/*! Mutation AttrChangeType Signaling*/ enum { + /*! modification */ GF_MUTATION_ATTRCHANGE_MODIFICATION = 0x01, GF_MUTATION_ATTRCHANGE_ADDITION = 0x02, GF_MUTATION_ATTRCHANGE_REMOVAL = 0x03, }; +#endif -enum -{ - //regular even codes - GF_EVT_REGULAR = 0, - /*events forwarded from MPEG-2 stack*/ - GF_EVT_MPEG2 = 1, -}; /*! @} */ diff --git a/include/gpac/evg.h b/include/gpac/evg.h new file mode 100644 index 0000000..c1789a2 --- /dev/null +++ b/include/gpac/evg.h @@ -0,0 +1,867 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / Embedded Vector Graphics engine + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#ifndef _GF_EVG_H_ +#define _GF_EVG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/*! +\file "gpac/evg.h" +\brief 2D vector graphics rasterizer + +This file contains all defined functions for 2D vector graphics of the GPAC framework. +*/ + +/*! +\addtogroup evg_grp +\brief Vector Graphics rendering of GPAC. + +GPAC uses a software rasterizer for vector graphics based on FreeType's "ftgray" anti-aliased renderer. + +The rasterizer supports +- drawing to RGB, YUV and grayscale surfaces. +- Texture mapping using RGB, YUV and grayscale textures +- Linear Gradients with opacity +- Radial Gradients with opacity +- Solid brush with opacity +- axis-aligned clipping + +The rasterizer is only capable of filling a given GF_Path object +- striking (outlining) of a path must be done by using \ref gf_path_get_outline to the the outline path +- text must be first converted to GF_Path + +The rasterizer also supports experimental drawing of 3D primitives (point, lines, triangles). This is obviously not intended to replace GPU rendering, but can +be useful to draw simple primitives over an existing video source. The following constraints apply in 3D mode: +- stencils are not used +- path can only be drawn using \ref gf_evg_surface_draw_path + +@{ +*/ + +/*! stencil type used for all stencils*/ +typedef struct _gf_evg_base_stencil GF_EVGStencil; +/*! surface type*/ +typedef struct _gf_evg_surface GF_EVGSurface; + + +/*! stencil types*/ +typedef enum +{ + /*! solid color stencil*/ + GF_STENCIL_SOLID = 0, + /*! linear color gradient stencil*/ + GF_STENCIL_LINEAR_GRADIENT, + /*! radial color gradient stencil*/ + GF_STENCIL_RADIAL_GRADIENT, + /*! texture stencil*/ + GF_STENCIL_VERTEX_GRADIENT, + /*! texture stencil*/ + GF_STENCIL_TEXTURE, +} GF_StencilType; + +/*! gradient filling modes*/ +typedef enum +{ + /*! edge colors are repeated until path is filled*/ + GF_GRADIENT_MODE_PAD, + /*! pattern is inversed each time it's repeated*/ + GF_GRADIENT_MODE_SPREAD, + /*! pattern is repeated to fill path*/ + GF_GRADIENT_MODE_REPEAT +} GF_GradientMode; + +/*! texture map flags*/ +typedef enum +{ + /*! texture is repeated in its horizontal direction*/ + GF_TEXTURE_REPEAT_S = (1<<1), + /*! texture is repeated in its horizontal direction*/ + GF_TEXTURE_REPEAT_T = (1<<2), + /*! texture is fliped horizontally*/ + GF_TEXTURE_FLIP_X = (1<<3), + /*! texture is fliped vertically*/ + GF_TEXTURE_FLIP_Y = (1<<4), +} GF_TextureMapFlags; + +/*! filter levels for texturing - up to the graphics engine but the following levels are used by +the client*/ +typedef enum +{ + /*! high speed mapping (ex, no filtering applied)*/ + GF_TEXTURE_FILTER_HIGH_SPEED, + /*! compromise between speed and quality (ex, filter to nearest pixel)*/ + GF_TEXTURE_FILTER_MID, + /*! high quality mapping (ex, bi-linear/bi-cubic interpolation)*/ + GF_TEXTURE_FILTER_HIGH_QUALITY +} GF_TextureFilter; + +/*! rasterizer antialiasing depending on the graphics engine*/ +typedef enum +{ + /*! raster shall use no antialiasing */ + GF_RASTER_HIGH_SPEED, + /*! raster should use fast mode and good quality if possible*/ + GF_RASTER_MID, + /*! raster should use full antialiasing*/ + GF_RASTER_HIGH_QUALITY +} GF_RasterQuality; + + +/*! common constructor for all stencil types +\param type the stencil type +\return a new stencil object*/ +GF_EVGStencil *gf_evg_stencil_new(GF_StencilType type); + +/*! common destructor for all stencils +\param stencil the target stencil +*/ +void gf_evg_stencil_delete(GF_EVGStencil *stencil); + +/*! sets stencil transformation matrix +\param stencil the target stencil +\param mat the 2D matrix to set +\return error if any +*/ +GF_Err gf_evg_stencil_set_matrix(GF_EVGStencil *stencil, GF_Matrix2D *mat); + +/*! gets stencil type +\param stencil the target stencil +\return stencil type +*/ +GF_StencilType gf_evg_stencil_type(GF_EVGStencil *stencil); + +/*! sets color for solid brush stencil +\param stencil the target stencil +\param color the color to set +\return error if any +*/ +GF_Err gf_evg_stencil_set_brush_color(GF_EVGStencil *stencil, GF_Color color); + +/*! sets gradient repeat mode for a gradient stencil +\note this may be called before the gradient is setup +\param stencil the target stencil +\param mode the gradient mode +\return error if any +*/ +GF_Err gf_evg_stencil_set_gradient_mode(GF_EVGStencil *stencil, GF_GradientMode mode); +/*! sets linear gradient direction for a gradient stencil +Gradient line is defined by start and end. For example, {0,0}, {1,0} defines an horizontal gradient +\param stencil the target stencil +\param start_x horizontal coordinate of starting point +\param start_y vertical coordinate of starting point +\param end_x horizontal coordinate of ending point +\param end_y vertical coordinate of ending point +\return error if any +*/ +GF_Err gf_evg_stencil_set_linear_gradient(GF_EVGStencil *stencil, Fixed start_x, Fixed start_y, Fixed end_x, Fixed end_y); +/*! sets radial gradient center point, focal point and radius for a gradient stencil +\param stencil the target stencil +\param cx horizontal coordinate of center +\param cy vertical coordinate of center +\param fx horizontal coordinate of focal point +\param fy vertical coordinate of focal point +\param x_radius horizontal radius +\param y_radius vertical radius +\return error if any +*/ +GF_Err gf_evg_stencil_set_radial_gradient(GF_EVGStencil *stencil, Fixed cx, Fixed cy, Fixed fx, Fixed fy, Fixed x_radius, Fixed y_radius); + +/*! sets color interpolation for a gradient stencil +\note the colors at 0 and 1.0 MUST be provided +\note colors shall be fed in order from 0 to 1 + +\param stencil the target stencil +\param pos interpolation positions. Each position gives the distance from (center for radial, start for linear) expressed between 0 and 1 (1 being the gradient bounds) - may be NULL when count is 0 +\param col colors at the given position - may be NULL when count is 0 +\param count number of colors and position. A value of 0 resets the gradient colors +\return error if any +*/ +GF_Err gf_evg_stencil_set_gradient_interpolation(GF_EVGStencil *stencil, Fixed *pos, GF_Color *col, u32 count); + +/*! pushes a single color interpolation for a gradient stencil +\note the colors at 0 and 1.0 MUST be provided +\note colors shall be fed in order from 0 to 1 + +\param stencil the target stencil +\param pos interpolation position. A position gives the distance from (center for radial, start for linear) expressed between 0 and 1 (1 being the gradient bounds) +\param col color at the given position +\return error if any +*/ +GF_Err gf_evg_stencil_push_gradient_interpolation(GF_EVGStencil *stencil, Fixed pos, GF_Color col); + +/*! sets global alpha blending level for a texture or gradient stencil +The alpha channel will be combined with the color matrix if any +\warning do not use with solid brush stencil +\param stencil the target stencil +\param alpha the alpha value between 0 (full transparency) and 255 (full opacityy) +\return error if any +*/ +GF_Err gf_evg_stencil_set_alpha(GF_EVGStencil *stencil, u8 alpha); + +/*! sets pixel data for a texture stencil +\param stencil the target stencil +\param pixels texture data starting from top row +\param width texture width in pixels +\param height texture height in pixels +\param stride texture horizontal pitch (bytes to skip to get to next row) +\param pixelFormat texture pixel format +\note this stencil acts as a data wrapper, the pixel data is not locally copied +\return error if any +*/ +GF_Err gf_evg_stencil_set_texture(GF_EVGStencil *stencil, u8 *pixels, u32 width, u32 height, u32 stride, GF_PixelFormat pixelFormat); + +/*! sets pixel planes data for a texture stencil +\param stencil the target stencil +\param width texture width in pixels +\param height texture height in pixels +\param pixelFormat texture pixel format +\param y_or_rgb Y plane or RGB plane +\param stride stride of the Y or RGB plane +\param u_plane U plane or UV plane for semi-planar YUV formats +\param v_plane V plane for planar YUV format, NULL for semi-planar YUV formats +\param uv_stride stride of the U and V or UV plane +\param alpha_plane alpha plane +\param alpha_stride stride of the alpha plane; if 0 and alpha plane is not NULL, the stride is assumed to be the same as the Y plane stride +\note this stencil acts as a data wrapper, the pixel data is not locally copied +\return error if any +*/ +GF_Err gf_evg_stencil_set_texture_planes(GF_EVGStencil *stencil, u32 width, u32 height, GF_PixelFormat pixelFormat, const u8 *y_or_rgb, u32 stride, const u8 *u_plane, const u8 *v_plane, u32 uv_stride, const u8 *alpha_plane, u32 alpha_stride); + +/*! callback function prototype for parametric textures +\param cbk user data callback +\param x horizontal coordinate in texture, ranging between 0 and width-1, (0,0) being top-left pixel, or horizontal coordinate of destination pixel on surface +\param y vertical coordinate in texture, ranging between 0 and height-1, (0,0) being top-left pixel, or vertical coordinate of destination pixel on surface +\param r set to the red value (not initialized before call) +\param g set to the green value (not initialized before call) +\param b set to the blue value (not initialized before call) +\param a set to the alpha value (not initialized before call) +*/ +typedef void (*gf_evg_texture_callback)(void *cbk, u32 x, u32 y, Float *r, Float *g, Float *b, Float *a); + +/*! sets parametric callback on a texture stencil. A parametric texture gets its pixel values from a callback function, the resulting value being blended according to the antialiasing level of the pixel. This allows creating rather complex custom textures, in a fashion similar to fragment shaders. +\param stencil the target stencil +\param width texture width in pixels +\param height texture height in pixels +\param pixelFormat texture pixel format, indicates if the returned values are in RGB or YUV +\param callback the callback function to use +\param cbk_udta user data to pass to the callback function +\param use_screen_coords if set, the coordinates passed to the callback function are screen coordinates rather than texture coordinates +\return error if any +*/ +GF_Err gf_evg_stencil_set_texture_parametric(GF_EVGStencil *stencil, u32 width, u32 height, GF_PixelFormat pixelFormat, gf_evg_texture_callback callback, void *cbk_udta, Bool use_screen_coords); + +/*! sets texture mapping options for a texture stencil +\param stencil the target stencil +\param map_mode texture mapping flags to set +\return error if any +*/ +GF_Err gf_evg_stencil_set_mapping(GF_EVGStencil *stencil, GF_TextureMapFlags map_mode); + +/*! sets filtering mode for a texture stencil +\param stencil the target stencil +\param filter_mode texture filtering mode to set +\return error if any +*/ +GF_Err gf_evg_stencil_set_filter(GF_EVGStencil *stencil, GF_TextureFilter filter_mode); + +/*! sets color matrix of a stencil +\warning ignored for solid brush stencil +\param stencil the target stencil +\param cmat the color matrix to use. If NULL, resets current color matrix +\return error if any + */ +GF_Err gf_evg_stencil_set_color_matrix(GF_EVGStencil *stencil, GF_ColorMatrix *cmat); + +/*! gets pixel at given position (still experimental) +\param stencil the target stencil +\param x horizontal coord +\param y vertical coord +\return pixel value + */ +u32 gf_evg_stencil_get_pixel(GF_EVGStencil *stencil, s32 x, s32 y); + +/*! gets ARGB pixel at a given position + The position is given as normalize coordinates (between 0.0 and 1.0), {0.0,0.0} is top-left, {1.0,1.0} is bottom-right +\param stencil the target stencil +\param x horizontal coord +\param y vertical coord +\param r output r component +\param g output g component +\param b output b component +\param a output a component +\return error if any + */ +GF_Err gf_evg_stencil_get_pixel_f(GF_EVGStencil *stencil, Float x, Float y, Float *r, Float *g, Float *b, Float *a); + +/*! gets AYUV pixel at a given position + The position is given as normalize coordinates (between 0.0 and 1.0), {0.0,0.0} is top-left, {1.0,1.0} is bottom-right +\param stencil the target stencil +\param x horizontal coord +\param y vertical coord +\param Y output y component +\param U output u component +\param V output v component +\param A output a component +\return error if any + */ +GF_Err gf_evg_stencil_get_pixel_yuv_f(GF_EVGStencil *stencil, Float x, Float y, Float *Y, Float *U, Float *V, Float *A); + +/*! creates a canvas surface object +\param center_coords if GF_TRUE, indicates mathematical-like coord system (0,0) at the center of the canvas, otherwise indicates computer-like coord system (0,0) top-left corner +\return a new surface*/ +GF_EVGSurface *gf_evg_surface_new(Bool center_coords); + +/*! deletes a surface object +\param surf the surface object +*/ +void gf_evg_surface_delete(GF_EVGSurface *surf); + +/*! attaches a surface object to a texture stencil object +\param surf the surface object +\param sten the stencil object (shll be a texture stencil) +\return error if any +*/ +GF_Err gf_evg_surface_attach_to_texture(GF_EVGSurface *surf, GF_EVGStencil *sten); + +/*! attaches a surface object to a memory buffer +\param surf the surface object +\param pixels: texture data +\param width texture width in pixels +\param height texture height in pixels +\param pitch_x texture horizontal pitch (bytes to skip to get to next pixel). O means linear frame buffer (eg pitch_x==bytes per pixel) +\param pitch_y texture vertical pitch (bytes to skip to get to next line) +\param pixelFormat texture pixel format +\return error if any +*/ +GF_Err gf_evg_surface_attach_to_buffer(GF_EVGSurface *surf, u8 *pixels, u32 width, u32 height, s32 pitch_x, s32 pitch_y, GF_PixelFormat pixelFormat); + +/*! sets rasterizer precision +\param surf the surface object +\param level the raster quality level +\return error if any +*/ +GF_Err gf_evg_surface_set_raster_level(GF_EVGSurface *surf, GF_RasterQuality level); + +/*! sets the given matrix as the current transformations for all drawn paths +\note this is only used for 2D rasterizer, and ignored in 3D mode +\param surf the surface object +\param mat the matrix to set; if NULL, resets the current transformation +\return error if any +*/ +GF_Err gf_evg_surface_set_matrix(GF_EVGSurface *surf, GF_Matrix2D *mat); + +/*! sets the given matrix as the current transformations for all drawn paths. The matrix shall be a projection matrix (ortho or perspective) +with normalized coordinates in [-1,1]. It may also contain a modelview part. +\note this is only used for 2D rasterizer, and ignored in 3D mode +\param surf the surface object +\param mat the matrix to set; if NULL, resets the current transformation +\return error if any +*/ +GF_Err gf_evg_surface_set_matrix_3d(GF_EVGSurface *surf, GF_Matrix *mat); + +/*! sets the given rectangle as a clipper +When a clipper is enabled, nothing is drawn outside of the clipper. The clipper is not affected by the surface matrix +\param surf the surface object +\param rc the clipper to set, in pixel coordinates of the surface; if NULL, disables clipper +\return error if any +*/ +GF_Err gf_evg_surface_set_clipper(GF_EVGSurface *surf, GF_IRect *rc); + +/*! sets the given path as the current one for drawing +\warning This will internally copy the path after transformation with the current transfom. Changing the surface transform will have no effect unless calling this function again. +The clipper and stencil mode may be changed at will + +\note this is only used for 2D rasterizer, and ignored in 3D mode +\param surf the surface object +\param path the target path to rasterize +\return error if any +*/ +GF_Err gf_evg_surface_set_path(GF_EVGSurface *surf, GF_Path *path); + +/*! draw (filling) the current path on a surface using the given stencil and current clipper if any. +If the stencil is a solid brush and its alpha value is 0, the surface is cleared if it has an alpha component, otherwise the call is ignored. +\note this can be called several times with the same current path +\note this is only used for 2D rasterizer, and ignored in 3D mode +\param surf the surface object +\param stencil the stencil to use to fill a path +\return error if any +*/ +GF_Err gf_evg_surface_fill(GF_EVGSurface *surf, GF_EVGStencil *stencil); + +/*! clears given pixel rectangle on a surface with the given color +\warning this ignores any clipper set on the surface +\param surf the surface object +\param rc the rectangle in pixel coordinates to clear. This may lay outside the surface, and shall not be NULL +\param col the color used to clear the canvas. The alpha component is discarded if the surface does not have alpha, otherwise it is used +\return error if any +*/ +GF_Err gf_evg_surface_clear(GF_EVGSurface *surf, GF_IRect *rc, GF_Color col); + +/*! sets center coord mode of a surface +\note this is only used for 2D rasterizer, and ignored in 3D mode +\param surf the surface object +\param center_coords if GF_TRUE, indicates mathematical-like coord system (0,0) at the center of the canvas, otherwise indicates computer-like coord system (0,0) top-left corner +*/ +void gf_evg_surface_set_center_coords(GF_EVGSurface *surf, Bool center_coords); + +/*! Composition mode used for ARGB surfaces - cf Canvas2D modes*/ +typedef enum +{ + /*! source over*/ + GF_EVG_SRC_OVER = 0, + /*! source atop*/ + GF_EVG_SRC_ATOP, + /*! source in*/ + GF_EVG_SRC_IN, + /*! source out*/ + GF_EVG_SRC_OUT, + /*! destination atop*/ + GF_EVG_DST_ATOP, + /*! destination in*/ + GF_EVG_DST_IN, + /*! destination out*/ + GF_EVG_DST_OUT, + /*! destination over*/ + GF_EVG_DST_OVER, + /*! destination * source*/ + GF_EVG_LIGHTER, + /*! source copy*/ + GF_EVG_COPY, + /*! source XOR destination*/ + GF_EVG_XOR, +} GF_EVGCompositeMode; + +/*! sets surface composite mode, as defined in Canvas2D - only used for ARGB surfaces + \warning this is still experimental +\param surf the surface object +\param comp_mode the composition mode to use +*/ +void gf_evg_surface_set_composite_mode(GF_EVGSurface *surf, GF_EVGCompositeMode comp_mode); + +/*! callback type for alpha override +\param udta opaque data passed back to caller +\param src_alpha alpha value about to be blended +\param x horizontal coordinate of pixel to be drawn, {0,0} being top-left, positive Y go down +\param y vertical coordinate of pixel to be drawn, {0,0} being top-left, positive Y go down +\return final alpha value to use*/ +typedef u8 (*gf_evg_get_alpha)(void *udta, u8 src_alpha, s32 x, s32 y); + +/*! sets alpha callback function. This allows finer control over the blending, but does not allow for pixel color modification +\param surf the surface object +\param get_alpha the callback function to alter the alpha value +\param cbk opaque data for the callback function +*/ +void gf_evg_surface_set_alpha_callback(GF_EVGSurface *surf, gf_evg_get_alpha get_alpha, void *cbk); + + +/*! Primitive types for 3D software rasterize - see OpenGL terminology*/ +typedef enum +{ + //do NOT modify order + + /*! points, 1 vertex index per primitive */ + GF_EVG_POINTS=1, + /*! polygon, all vertex indices in array are used for a single face*/ + GF_EVG_POLYGON, + /*! lines, 2 vertex indices per primitive*/ + GF_EVG_LINES, + /*! triangles, 3 vertex indices per primitive*/ + GF_EVG_TRIANGLES, + /*! quads, 4 vertex indices per primitive*/ + GF_EVG_QUADS, + + /*! line strip, 2 vertex indices for first primitive, then one for subsequent ones*/ + GF_EVG_LINE_STRIP, + /*! triangle strip, 3 vertex indices for first primitive, then one for subsequent ones*/ + GF_EVG_TRIANGLE_STRIP, + /*! triangle fan, 3 vertex indices for first primitive, then one for subsequent ones*/ + GF_EVG_TRIANGLE_FAN, + /*! quad strip, 4 vertex indices for first primitive, then one for subsequent ones*/ + GF_EVG_QUAD_STRIP, +} GF_EVGPrimitiveType; + +/*! creates a new 3D software rasterizer + \note use \ref gf_evg_surface_delete for destruction + \return NULL if error*/ +GF_EVGSurface *gf_evg_surface3d_new(); +/*! sets projection matrix + \note this is only used for 3D rasterizer, and fails 2D mode + \param surf the target 3D surface + \param mx the target projection matrix + \return error if any + */ +GF_Err gf_evg_surface_set_projection(GF_EVGSurface *surf, GF_Matrix *mx); +/*! sets modelview matrix +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param mx the target modelview matrix +\return error if any +*/ +GF_Err gf_evg_surface_set_modelview(GF_EVGSurface *surf, GF_Matrix *mx); +/*! draws a set of primitive using vertices and an index buffer modelview matrix +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param indices the array of indices to use in the vertex buffer +\param nb_indices the number of indices +\param vertices the array of vertices to use +\param nb_vertices the number of vertices +\param nb_comp the number of component per vertices (eg, 2, 3) +\param prim_type the primitive type to use +\return error if any +*/ +GF_Err gf_evg_surface_draw_array(GF_EVGSurface *surf, u32 *indices, u32 nb_indices, Float *vertices, u32 nb_vertices, u32 nb_comp, GF_EVGPrimitiveType prim_type); + +/*! clears the depth buffer + \note the depth buffer is set by the caller. If no depth buffer is assigned, this function returns GF_OK + \note the color buffer is cleared using \ref gf_evg_surface_clear +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param depth the depth value to use +\return error if any +*/ +GF_Err gf_evg_surface_clear_depth(GF_EVGSurface *surf, Float depth); +/*! sets the surface viewport to convert from projected coordiantes to screen coordinates +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param x the top-left coordinate of the viewport in pixels +\param y the top-left coordinate of the viewport in pixels +\param w the width of the viewport in pixels +\param h the height of the viewport in pixels +\return error if any +*/ +GF_Err gf_evg_surface_viewport(GF_EVGSurface *surf, u32 x, u32 y, u32 w, u32 h); +/*! draws path on a 3D surface with the given z. + +The vertex shader is ignored (surface matrix must be set), and the fragment shader is called using perspective interpolation weights derived from the path bounds, +using path bounds (top-left, top-right, bottom-right) vertices. + \note this is only used for 3D rasterizer, and fails 2D mode + +\param surf the target 3D surface +\param path the path to draw +\param z the z value to assign to the path in local coordinate system +\return error if any +*/ +GF_Err gf_evg_surface_draw_path(GF_EVGSurface *surf, GF_Path *path, Float z); + +/*! Depth test modes*/ +typedef enum +{ + /*! depth test is disabled*/ + GF_EVGDEPTH_DISABLE, + /*! depth test always fails*/ + GF_EVGDEPTH_NEVER, + /*! depth test always succeeds*/ + GF_EVGDEPTH_ALWAYS, + /*! depth test succeeds if fragment depth is == than depth buffer value*/ + GF_EVGDEPTH_EQUAL, + /*! depth test succeeds if fragment depth is != than depth buffer value*/ + GF_EVGDEPTH_NEQUAL, + /*! depth test succeeds if fragment depth is < than depth buffer value*/ + GF_EVGDEPTH_LESS, + /*! depth test succeeds if fragment depth is <= than depth buffer value*/ + GF_EVGDEPTH_LESS_EQUAL, + /*! depth test succeeds if fragment depth is > than depth buffer value*/ + GF_EVGDEPTH_GREATER, + /*! depth test succeeds if fragment depth is >= than depth buffer value*/ + GF_EVGDEPTH_GREATER_EQUAL +} GF_EVGDepthTest; + +/*! sets depth buffer test +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param mode the desired depth test mode +\return error if any +*/ +GF_Err gf_evg_set_depth_test(GF_EVGSurface *surf, GF_EVGDepthTest mode); + +/*! fragment color type */ +typedef enum +{ + /*! fragment is invalid (discarded or error) */ + GF_EVG_FRAG_INVALID = 0, + /*! fragment is RGB */ + GF_EVG_FRAG_RGB, + /*! fragment is YUV */ + GF_EVG_FRAG_YUV, +} GF_EVGFragmentType; + +/*! Parameters for the fragment callback */ +typedef struct +{ + /*! screen x in pixels - input param*/ + Float screen_x; + /*! screen y in pixels - input param*/ + Float screen_y; + /*! screen z in NDC - input param*/ + Float screen_z; + /*! depth - input and output param*/ + Float depth; + /*! primitive index in the current \ref gf_evg_surface_draw_array call, 0 being the first primitive - input param*/ + u32 prim_index; + /*! index of first vertex in the current primitive - input param*/ + u32 idx1; + /*! index of second vertex in the current primitive (for lines or triangles/quads) - input param*/ + u32 idx2; + /*! index of third vertex in the current primitive (for triangles/quads) - input param*/ + u32 idx3; + + /*! primitive type - input param*/ + GF_EVGPrimitiveType ptype; + + /*! fragment color, must be written if fragment is not discarded - output value*/ + GF_Vec4 color; + /*! fragment valid state - output value*/ + GF_EVGFragmentType frag_valid; + + /*vars for lerp*/ + /*perspective correct interpolation is done according to openGL eq 14.9 + f = (a*fa/wa + b*fb/wb + c*fc/wc) / (a/w_a + b/w_b + c/w_c) + */ + /*! perspective corrected barycentric, eg bc1/q1, bc2/q2, bc3/q3 + these are constant throughout the fragment*/ + Float pbc1, pbc2, pbc3; + /*! perspective divider + \note this is also 1/W of the fragment, eg opengl gl_fragCoord.w + */ + Float persp_denum; +} GF_EVGFragmentParam; + + +/*! Parameters for the vertex callback */ +typedef struct +{ + /*! input vertex - input param*/ + GF_Vec4 in_vertex; + /*! primitive index in the current \ref gf_evg_surface_draw_array call, 0 being the first primitive - input param*/ + u32 prim_index; + /*! index of the vertex - input param*/ + u32 vertex_idx; + /*! index of the vertex in the current primitive - input param*/ + u32 vertex_idx_in_prim; + /*! primitive type - input param*/ + u32 ptype; + + /*! transformed vertex to use, must be written - output values*/ + GF_Vec4 out_vertex; +} GF_EVGVertexParam; + +/*! callback type for fragment shader + \param udta opaque data passed back to caller + \param fragp fragment paramters + \return GF_TRUE if success; GF_FALSE if error*/ +typedef Bool (*gf_evg_fragment_shader)(void *udta, GF_EVGFragmentParam *fragp); +/*! assigns fragment shader to the rasterizer +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param shader the fragment shader callback to use +\param shader_udta opaque data to pass to the callback function +\return error if any + */ +GF_Err gf_evg_surface_set_fragment_shader(GF_EVGSurface *surf, gf_evg_fragment_shader shader, void *shader_udta); + +/*! callback type for vertex shader +\param udta opaque data passed back to caller +\param vertp vertex paramters +\return GF_TRUE if success; GF_FALSE if error*/ +typedef Bool (*gf_evg_vertex_shader)(void *udta, GF_EVGVertexParam *vertp); +/*! assigns vertex shader to the rasterizer +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param shader the vertex shader callback to use +\param shader_udta opaque data to pass to the callback function +\return error if any + */ +GF_Err gf_evg_surface_set_vertex_shader(GF_EVGSurface *surf, gf_evg_vertex_shader shader, void *shader_udta); + +/*! sets face orientation for the primitives +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param is_ccw if GF_TRUE, face are given in counter-clockwise order, otherwise in clockwise order +\return error if any + */ +GF_Err gf_evg_surface_set_ccw(GF_EVGSurface *surf, Bool is_ccw); +/*! enables/disables backface culling + \warning not enabling backface culling when anti-aliasing is on may result in visual artefacts +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param backcull if GF_TRUE, faces with normals poiting away from camera will not be drawn +\return error if any + */ +GF_Err gf_evg_surface_set_backcull(GF_EVGSurface *surf, Bool backcull); +/*! enables/disables anti-aliased rendering + \warning not enabling backface culling when anti-aliasing is on may result in visual artefacts +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param antialias if GF_TRUE, edges will be anti-aliased +\return error if any + */ +GF_Err gf_evg_surface_set_antialias(GF_EVGSurface *surf, Bool antialias); +/*! sets minimum depth value for coordinates normalization - see OpenGL glDepthRange +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param min_depth the minimum depth value +\return error if any + */ +GF_Err gf_evg_surface_set_min_depth(GF_EVGSurface *surf, Float min_depth); +/*! sets maximum depth value for coordinates normalization - see OpenGL glDepthRange +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param max_depth the maximum depth value +\return error if any + */ +GF_Err gf_evg_surface_set_max_depth(GF_EVGSurface *surf, Float max_depth); +/*! indicates that the clip space after projection is [0,1] and not [-1, 1] +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param clip_zero if GF_TRUE, the projected vertices depth are in [0, 1], otherwise in [-1, 1] +\return error if any + */ +GF_Err gf_evg_surface_set_clip_zero(GF_EVGSurface *surf, Bool clip_zero); +/*! sets point size value for points primitives +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param size the desired point size in pixels +\return error if any + */ +GF_Err gf_evg_surface_set_point_size(GF_EVGSurface *surf, Float size); +/*! sets line size (width) value for points primitives +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param size the desired line size in pixels +\return error if any + */ +GF_Err gf_evg_surface_set_line_size(GF_EVGSurface *surf, Float size); +/*! enables point smoothing. + When point smoothing is enabled, the geometry drawn is a circle, otherwise a square +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param smooth if GF_TRUE, point smoothing is enabled +\return error if any + */ +GF_Err gf_evg_surface_set_point_smooth(GF_EVGSurface *surf, Bool smooth); +/*! disables early depth test +If your fragment shader modifies the depth of the fragment, you must call this function to avoid early depth test (fragment discarded before calling the shader) +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param disable if GF_TRUE, early depth test is disabled +\return error if any + */ +GF_Err gf_evg_surface_disable_early_depth(GF_EVGSurface *surf, Bool disable); +/*! disables depth buffer write +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param do_write if GF_TRUE, depth values are written to the depth buffer (default when creating the rasterizer) +\return error if any + */ +GF_Err gf_evg_surface_write_depth(GF_EVGSurface *surf, Bool do_write); +/*! sets depth buffer + The 3D rasterizer does not allocate any surface (depth or color buffers), it is the user responsability to set these buffers + \warning do NOT forget to resize your depth buffer when resizing the canvas ! +\note this is only used for 3D rasterizer, and fails 2D mode +\param surf the target 3D surface +\param depth the depth buffer to use. Its size shall be Float*width*height +\return error if any + */ +GF_Err gf_evg_surface_set_depth_buffer(GF_EVGSurface *surf, Float *depth); + + +/*! performs RGB to YUV conversion +\param surf the target surface +\param r the source red component +\param g the source green component +\param b the source blue component +\param y the output Y component +\param cb the output U/Cb component +\param cr the output V/Cr component +\return error if any + */ +GF_Err gf_gf_evg_rgb_to_yuv_f(GF_EVGSurface *surf, Float r, Float g, Float b, Float *y, Float *cb, Float *cr); + +/*! performs YUV to RGB conversion +\param surf the target surface +\param y the source Y component +\param cb the source U/Cb component +\param cr the source V/Cr component +\param r the output red component +\param g the output green component +\param b the output blue component +\return error if any + */ +GF_Err gf_evg_yuv_to_rgb_f(GF_EVGSurface *surf, Float y, Float cb, Float cr, Float *r, Float *g, Float *b); + +/*! converts RGB to YUV + \param surf the target surface + \param col the source RGBA color + \param y the output Y value + \param cb the output Cb/U value + \param cr the output Cr/V value + */ +void gf_evg_rgb_to_yuv(GF_EVGSurface *surf, GF_Color col, u8*y, u8*cb, u8*cr); + +/*! converts ARGB to AYUV +\param surf the target surface +\param col the source ARGB color +\return the result AYUV color +*/ +GF_Color gf_evg_argb_to_ayuv(GF_EVGSurface *surf, GF_Color col); + +/*! converts AYUV to ARGB +\param surf the target surface +\param col the source AYUV color +\return the result ARGB color +*/ +GF_Color gf_evg_ayuv_to_argb(GF_EVGSurface *surf, GF_Color col); + +/*! converts ARGB to AYUV for wide colors +\param surf the target surface +\param col the source ARGB color +\return the result AYUV color +*/ +u64 gf_evg_argb_to_ayuv_wide(GF_EVGSurface *surf, u64 col); + +/*! converts AYUV to ARGB for wide color +\param surf the target surface +\param col the source AYUV color +\return the result ARGB color +*/ +u64 gf_evg_ayuv_to_argb_wide(GF_EVGSurface *surf, u64 col); + + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif /*_GF_EVG_H_*/ + diff --git a/include/gpac/filestreamer.h b/include/gpac/filestreamer.h deleted file mode 100644 index da7fc95..0000000 --- a/include/gpac/filestreamer.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2008-2012 - * All rights reserved - * - * This file is part of GPAC / media tools sub-project - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef _GF_ISOMRTPStreamer_H_ -#define _GF_ISOMRTPStreamer_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -/*! - * \file - * \brief RTP file streamer. - */ - -/*! - * \ingroup media_grp - * \brief FileStreamer object - * - * This section documents the list object of the GPAC framework. - * @{ - */ - -#include - - -typedef struct __isom_rtp_streamer GF_ISOMRTPStreamer; - -/*! - * \brief ISO File RTP Streamer list constructor - * - * Constructs a new ISO file RTP streamer - *\param file_name source file name to stream. Hint tracks will be ignored, all media tracks will be streamed - *\param ip_dest destination IP address (V4 or V6, unicast or multicast) - *\param port destination port - *\param loop whether streaming stops at the end of all tracks or not. If not, RTP TS will continuously be incremented - *\param force_mpeg4 forces usage of MPEG-4 generic (RFC3640) for all streams - *\param path_mtu maximum RTP packet payload size allowed - *\param ttl multicast time to live - *\param ifce_addr IP of the local interface to use (may be NULL) - *\return new list object - */ -GF_ISOMRTPStreamer *gf_isom_streamer_new(const char *file_name, const char *ip_dest, u16 port, Bool loop, Bool force_mpeg4, u32 path_mtu, u32 ttl, char *ifce_addr); - -/*! - * \brief RTP file streamer destructor - * - * Destructs an RTP file streamer - * \param streamer object to destruct - */ -void gf_isom_streamer_del(GF_ISOMRTPStreamer *streamer); - -/*! - * \brief writes the SDP file - * - * Writes the SDP file asscoiated with the streaming session - * \param streamer RTP streamer object - * \param sdpfilename SDP file name to create - */ -GF_Err gf_isom_streamer_write_sdp(GF_ISOMRTPStreamer *streamer, char*sdpfilename); - -/*! - * \brief gets the SDP file - * - * Gets the SDP asscoiated with all media in the streaming session (only media parts are returned) - * \param streamer RTP streamer object - * \param out_sdp_buffer location to the SDP buffer to allocate and fill - */ -GF_Err gf_isom_streamer_get_sdp(GF_ISOMRTPStreamer *streamer, char **out_sdp_buffer); - - -/*! - * \brief sends RTP packet - * - * Sends the next RTP packet in the current file, potentially waiting for the TS to be mature. If the last packet is sent and looping is disabled, this will return GF_EOS. - * \param streamer RTP streamer object - * \param send_ahead_delay delay in milliseconds for packet sending. A packet is sent if (packet.timestamp + send_ahead_delay) is greate than the current time. - * \param max_sleep_time indicates that if the streamer has to wait more than max_sleep_time before sending the packet, it should return and send it later. - */ -GF_Err gf_isom_streamer_send_next_packet(GF_ISOMRTPStreamer *streamer, s32 send_ahead_delay, s32 max_sleep_time); - -/*! - * \brief resets RTP sender - * - * Reset the RTP streams to the beginning of the media file - * \param streamer RTP streamer object - * \param is_loop indicates whether the RTP timelines shall continue from the end of the file or shall restart from 0 - */ -void gf_isom_streamer_reset(GF_ISOMRTPStreamer *streamer, Bool is_loop); - - -/*! - * \brief gets current media time - * - * Get the media time (DTS) of the last RTP packet sent in this session - * \param streamer RTP streamer object - * \return media time (DTS) in seconds - */ -Double gf_isom_streamer_get_current_time(GF_ISOMRTPStreamer *streamer); - -/*! @} */ - -#ifdef __cplusplus -} -#endif - - -#endif /*_GF_ISOMRTPStreamer_H_*/ - diff --git a/include/gpac/filters.h b/include/gpac/filters.h new file mode 100644 index 0000000..ff66847 --- /dev/null +++ b/include/gpac/filters.h @@ -0,0 +1,3811 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / filters sub-project + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _GF_FILTERS_H_ +#define _GF_FILTERS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +#include +#include + +//for offsetof() +#include + +/*! +\file "gpac/filters.h" +\brief Filter management of GPAC. + +This file contains all exported functions for filter management of the GPAC framework. +*/ + +/*! +\addtogroup filters_grp Filter Management +\brief Filter Management of GPAC. + +API Documentation of the filter managment system of GPAC. + +The filter management in GPAC is built using the following core objects: +- \ref GF_FilterSession in charge of + loading filters from register, managing argument parsing and co + resolving filter graphs to handle PID connection(s) + tracking data packets and properties exchanged on PIDs + scheduling tasks between filters + ensuring thread-safe filter state: a filter may be called from any thread in the session (unless explicitely asked not to), but only by a single thread at any time. +- \ref __gf_filter_register static structure describing possible entry points of the filter, possible arguments and input output PID capabilities. + Each filter share the same API (register definition) regardless of its type: source/sink, mux/demux, encode/decode, raw media processing, encoded media processing, ... +- \ref GF_Filter is an instance of the filter register. A filter implementation typical tasks are: + accepting new input PIDs (for non source filters) + defining new output PIDs (for non sink filters), applying any property change due to filter processing + consuming packets on the input PIDs + dispatching packets on the output PIDs +- \ref GF_FilterPid handling the connections between two filters. + PID natively supports fan-out (one filter PID connecting to multiple destinations). + A PID is in charge of dispatching packets to possible destinations and storing PID properties in sync with dispatched packets. + Whenever PID properties change, the next packet sent on that PID is associated with the new state, and the destination filter(s) will be called + upon fetching the new packet. This is the one of the two reentrant code of a filter, the other one being the \ref GF_FEVT_INFO_UPDATE event. + When blocking mode is not disabled at the session filter, a PID is also in charge of managing its occupancy through either a number of packets or the + cumulated duration of the packets it is holding. + Whenever a PID holds too much data, it enters a blocking state. A filter with ALL its output PIDs in a blocked state won't be scheduled + for processing. This is a semi-blocking design, which imply that if a filter has one of its PIDs in a non blocking state, it will be scheduled for processing. If a PID has multiple destinations and one of the destination consumes faster than the other one, the filter is currently not blocking (this might change in the near future). + A PID is in charge of managing the packet references across filters, by performing memory management of allocated data packets + (avoid alloc/free at each packet but rather recycle the memory) and tracking shared packets references. +- \ref GF_FilterPacket holding data to dispatch from a filter on a given PID. + Packets are always associated to a single output PID, ie it is not possible for a filter to send one packet to multiple PIDs, the data has to be cloned. + Packets have default attributes such as timestamps, size, random access status, start/end frame, etc, as well as optional properties. + All packets are reference counted. + A packet can hold allocated block on the output PID, a pointer to some filter internal data, a data reference to a single input packet, or a frame interface object used for accessing data or OpenGL textures of the emitting filter. + Packets holding data references rather than copy are notified back to their creators upon destruction. +- \ref __gf_prop_val holding various properties for a PID or a packet + Properties can be copied/merged between input and output PIDs, or input and output packets. These properties are reference counted. + Two kinds of properties are defined, built-in ones which use a 32 bit identifier (usually a four character code), and user properties identified by a string. + PID properties are defined by the filter creating the PID. They can be overridden/added after being set by the filter by specifying fragment properties + in the filter arguments. For example \code fin=src=myfile.foo:#FEXT=bar \endcode will override the file extension property (FEXT) foo to bar AFTER the PID is being defined. +- \ref __gf_filter_event used to pass various events (play/stop/buffer requirements/...) up and down the filter chain. + This part of the API will likely change in the future, being merged with the global GF_Event of GPAC. + + +GPAC comes with a set of built-in filters in libgpac. It is also possible to define external filters in dynamic libraries. GPAC will look for such libraries + in folders listed in GPAC config file section core, key mod-dirs. The files SHALL be named gf_* and export a function called RegisterFilter + with the following prototype: + +\param fsess is set to NULL unless meta filters are listed, in which case the filter register should list all possible meta filters it supports +\return a GF_FilterRegister structure used for instantiating the filter. +const GF_FilterRegister *RegisterFilter(GF_FilterSession *fsess); + +*/ + +/*! \hideinitializer +A packet byte offset is set to this value when not valid +*/ +#define GF_FILTER_NO_BO 0xFFFFFFFFFFFFFFFFUL +/*! \hideinitializer +A packet timestamp (Decoding or Composition) is set to this value when not valid +*/ +#define GF_FILTER_NO_TS 0xFFFFFFFFFFFFFFFFUL + +/*! \hideinitializer +Filter Session object + */ +typedef struct __gf_filter_session GF_FilterSession; + +/*! \hideinitializer +Filter object + */ +typedef struct __gf_filter GF_Filter; +/*! \hideinitializer +Filter PID object + */ +typedef struct __gf_filter_pid GF_FilterPid; + +/*! \hideinitializer +Filter Packet object + */ +typedef struct __gf_filter_pck GF_FilterPacket; +/*! +Filter Packet destructor function prototype + */ +typedef void (*gf_fsess_packet_destructor)(GF_Filter *filter, GF_FilterPid *PID, GF_FilterPacket *pck); + +/*! +Filter Event object + */ +typedef union __gf_filter_event GF_FilterEvent; + +/*! +Filter Session Task object + */ +typedef struct __gf_fs_task GF_FSTask; + +/*! +Filter Register object + */ +typedef struct __gf_filter_register GF_FilterRegister; +/*! +Filter Property object + */ +typedef struct __gf_prop_val GF_PropertyValue; + +/*! +Filter Property Reference object, used for info query only + */ +typedef struct __gf_prop_entry GF_PropertyEntry; + +/*! +\addtogroup fs_grp Filter Session +\ingroup filters_grp +\brief Filter Session + + The GPAC filter session object allows building media pipelines using multiple sources and destinations and arbitrary filter chains. + + Filters are described through a \ref __gf_filter_register structure. A set of built-in filters are available, and user-defined filters can be added or removed at runtime. + + The filter session keeps an internal graph representation of all available filters and their possible input connections, which is used when resolving connections between filters. + + The number of \ref GF_FilterCapability matched between registries defines the weight of the connection. + + Paths from an instantiated filter are enabled/disabled based on the source PID capabilities. + + Paths to destination are recomputed for each destination, based on the instantiated destination filter capabilities. + + The graph edges are then enabled in the possible subgraphs allowed by the destination capabilities, and unused filter registries (without enabled input connections) are removed from the graph. + + The resulting weighted graph is then solved using Dijkstra's algorithm, using filter priority in case of weight equality. + + The filter session works by default in a semi-blocking state. Whenever output PID buffers on a filter are all full, the filter is marked as blocked and not scheduled for processing. Whenever one output PID buffer is not full, the filter unblocks. + + This implies that PID buffers may grow quite large if a filter is consuming data from a PID at a much faster rate than another filter consuming from that same PID. + * @{ + */ + + +/*! Filter session scheduler type */ +typedef enum +{ + /*! In this mode, the scheduler does not use locks for packet and property queues. Main task list is mutex-protected */ + GF_FS_SCHEDULER_LOCK_FREE=0, + /*! In this mode, the scheduler uses locks for packet and property queues. Defaults to lock-free if no threads are used. Main task list is mutex-protected */ + GF_FS_SCHEDULER_LOCK, + /*! In this mode, the scheduler does not use locks for packet and property queues, nor for the main task list */ + GF_FS_SCHEDULER_LOCK_FREE_X, + /*! In this mode, the scheduler uses locks for packet and property queues even if single-threaded (test mode) */ + GF_FS_SCHEDULER_LOCK_FORCE, + /*! In this mode, the scheduler uses direct dispatch and no threads, trying to nest task calls within task calls */ + GF_FS_SCHEDULER_DIRECT +} GF_FilterSchedulerType; + +/*! Flag set to indicate meta filters should be loaded. A meta filter is a filter providing various subfilters. +The subfilters are usually not exposed as filters, only the parent one is. +When set, all subfilters are exposed. This should only be set when inspecting filters help*/ +#define GF_FS_FLAG_LOAD_META 1<<1 +/*! Flag set to disable the blocking mode of the filter session. The default is a semi-blocking mode, cf \ref gf_filter_pid_would_block*/ +#define GF_FS_FLAG_NO_BLOCKING 1<<2 +/*! Flag set to disable internal caching of filter graph connections. If disabled, the graph will be recomputed at each link resolution (less memory occupancy but slower)*/ +#define GF_FS_FLAG_NO_GRAPH_CACHE 1<<3 +/*! Flag set to disable main thread. Such sessions shall be run using \ref gf_fs_run_step*/ +#define GF_FS_FLAG_NO_MAIN_THREAD 1<<4 +/*! Flag set to disable session regulation (no sleep)*/ +#define GF_FS_FLAG_NO_REGULATION 1<<5 +/*! Flag set to disable data probe*/ +#define GF_FS_FLAG_NO_PROBE (1<<6) +/*! Flag set to disable source reassignment (e.g. switching from fin to ffdmx) in PID resolution*/ +#define GF_FS_FLAG_NO_REASSIGN (1<<7) +/*! Flag set to print enabled/disabled edges for debug of PID resolution*/ +#define GF_FS_FLAG_PRINT_CONNECTIONS (1<<8) +/*! Flag set to disable argument checking*/ +#define GF_FS_FLAG_NO_ARG_CHECK (1<<9) +/*! Disables reservoir for packets and properties, uses much less memory but much more alloc/free*/ +#define GF_FS_FLAG_NO_RESERVOIR (1<<10) +/*! Throws an error if any PID in the filter graph cannot be linked. The default behaviour is tu run the session even when some PIDs are not connected*/ +#define GF_FS_FLAG_FULL_LINK (1<<11) + +/*! Creates a new filter session. This will also load all available filter registers not blacklisted. +\param nb_threads number of extra threads to allocate. A negative value means all core used by session (eg nb_cores-1 extra threads) +\param type scheduler type +\param flags set of above flags for the session. Modes set by flags cannot be changed at runtime +\param blacklist string containing comma-separated names of filters to disable +\return the created filter session +*/ +GF_FilterSession *gf_fs_new(s32 nb_threads, GF_FilterSchedulerType type, u32 flags, const char *blacklist); + +/*! Creates a new filter session, loading parameters from gpac config. This will also load all available filter registers not blacklisted. +\param flags set of flags for the session. Only \ref GF_FS_FLAG_LOAD_META and \ref GF_FS_FLAG_NO_MAIN_THREAD are used, other flags +\return the created filter session +*/ +GF_FilterSession *gf_fs_new_defaults(u32 flags); + +/*! Destructs the filter session +\param session the filter session to destruct +*/ +void gf_fs_del(GF_FilterSession *session); +/*! Loads a given filter by its register name. Filter are created using their register name, with options appended as a list of colon-separated Name=Value pairs. +Value can be omitted for booleans, defaulting to true (eg :noedit). Using '!' before the name negates the result (eg :!moof_first). +Name can be omitted for enumerations (eg :disp=pbo is equivalent to :pbo), provided that filter developers pay attention to not reuse enum names in one filter. + +\param session filter session +\param name name and arguments of the filter register to instantiate. +\param err_code set to error code if any - may be NULL. If initially set to GF_EOS, disables log messages. +\return created filter or NULL if filter register cannot be found +*/ +GF_Filter *gf_fs_load_filter(GF_FilterSession *session, const char *name, GF_Err *err_code); + +/*! Checks if a filter register exists by name. +\param session filter session +\param name name of the filter register to check. +\return GF_TRUE if a filter register exists with the given name, GF_FALSE otherwise +*/ +Bool gf_fs_filter_exists(GF_FilterSession *session, const char *name); + +/*! Runs the session +\param session filter session +\return error if any, or GF_EOS. The last errors can be retrieved using \ref gf_fs_get_last_connect_error and \ref gf_fs_get_last_process_error +*/ +GF_Err gf_fs_run(GF_FilterSession *session); + +/*! The default separator set used*/ +#define GF_FS_DEFAULT_SEPS ":=#,!@" + +/*! Sets the set of separators to use when parsing args +\param session filter session +\param separator_set filter session. +The first char is used to separate argument names - default is ':' +The second char, if present, is used to separate names and values - default is '=' +The third char, if present, is used to separate fragments for PID sources - default is '#' +The fourth char, if present, is used for list separators (sourceIDs, gfreg, ...) - default is ',' +The fifth char, if present, is used for boolean negation - default is '!' +The sixth char, if present, is used for LINK directives - default is '@' +\return error if any +*/ +GF_Err gf_fs_set_separators(GF_FilterSession *session, const char *separator_set); + +/*! Sets the maximum length of a filter chain dynamically loaded to solve connection between two filters +\param session filter session +\param max_chain_length sets maximum chain length when resolving filter links. +Default value is 6 ([ in -> ] demux -> reframe -> decode -> encode -> reframe -> mux [ -> out ]) +(filter chains loaded for adaptation (eg pixel format change, audio resample) are loaded after the link resolution) +Setting the value to 0 disables dynamic link resolution. You will have to specify the entire chain manually +\return error if any +*/ +GF_Err gf_fs_set_max_resolution_chain_length(GF_FilterSession *session, u32 max_chain_length); + +/*! Sets the maximum sleep time when postponing tasks. +\param session filter session +\param max_sleep maximum sleep time in milliseconds. 0 means yield only. +\return error if any +*/ +GF_Err gf_fs_set_max_sleep_time(GF_FilterSession *session, u32 max_sleep); + +/*! gets the maximum filter chain lengtG +\param session filter session +\return maximum chain length when resolving filter links. +*/ +u32 gf_fs_get_max_resolution_chain_length(GF_FilterSession *session); + +/*! Runs session in non blocking mode: process all tasks of oldest scheduled filter, process any pending PID connections and returns. +This can only be used if the flag \ref GF_FS_FLAG_NO_MAIN_THREAD was specified at session creation time +\param session filter session +*/ +void gf_fs_run_step(GF_FilterSession *session); + +/*! Stops the session, waiting for all additional threads to complete +\param session filter session +\return error if any +*/ +GF_Err gf_fs_stop(GF_FilterSession *session); + +/*! Gets the number of available filter registries (not blacklisted) +\param session filter session +\return number of filter registries +*/ +u32 gf_fs_filters_registers_count(GF_FilterSession *session); + +/*! Returns the register at the given index +\param session filter session +\param idx index of register, from 0 to \ref gf_fs_filters_registers_count +\return the register object, or NULL if index is out of bounds +*/ +const GF_FilterRegister *gf_fs_get_filter_register(GF_FilterSession *session, u32 idx); + +/*! Registers the test filters used for unit tests +\param session filter session +*/ +void gf_fs_register_test_filters(GF_FilterSession *session); + +/*! Loads a source filter from a URL and arguments +\param session filter session +\param url URL of the source to load. Can be a local file name, a full path (/.., \\...) or a full URL with scheme (eg http://, tcp://) +\param args arguments for the filter, see \ref gf_fs_load_filter +\param parent_url parent URL of the source, or NULL if none +\param err if not NULL, is set to error code if any +\return the filter loaded or NULL if error +*/ +GF_Filter *gf_fs_load_source(GF_FilterSession *session, const char *url, const char *args, const char *parent_url, GF_Err *err); + +/*! Loads a destination filter from a URL and arguments +\param session filter session +\param url URL of the source to load. Can ba a local file name, a full path (/.., \\...) or a full URL with scheme (eg http://, tcp://) +\param args arguments for the filter, see \ref gf_fs_load_filter +\param parent_url parent URL of the source, or NULL if none +\param err if not NULL, is set to error code if any +\return the filter loaded or NULL if error +*/ +GF_Filter *gf_fs_load_destination(GF_FilterSession *session, const char *url, const char *args, const char *parent_url, GF_Err *err); + +/*! Returns the last error which happened during a PID connection +\param session filter session +\return the error code if any +*/ +GF_Err gf_fs_get_last_connect_error(GF_FilterSession *session); + +/*! Returns the last error which happened during a filter process +\param session filter session +\return the error code if any +*/ +GF_Err gf_fs_get_last_process_error(GF_FilterSession *session); + +/*! Adds a user-defined register to the session +\param session filter session +\param freg filter register to add +*/ +void gf_fs_add_filter_register(GF_FilterSession *session, const GF_FilterRegister *freg); + +/*! Removes a user-defined register from the session +\param session filter session +\param freg filter register to remove +*/ +void gf_fs_remove_filter_register(GF_FilterSession *session, GF_FilterRegister *freg); + +/*! Posts a user task to the session +\param session filter session +\param task_execute the callback function for the task. The callback can return GF_TRUE to reschedule the task, in which case the task will be rescheduled +immediately or after reschedule_ms. +\param udta_callback callback user data passed back to the task_execute function +\param log_name log name of the task. If NULL, default is "user_task" +\return the error code if any +*/ +GF_Err gf_fs_post_user_task(GF_FilterSession *session, Bool (*task_execute) (GF_FilterSession *fsess, void *callback, u32 *reschedule_ms), void *udta_callback, const char *log_name); + +/*! Aborts the session. This can be called within a callback task to stop the session. Do NOT use \ref gf_fs_stop from within a user task callback, this will deadlock the session +\param session filter session +\param do_flush if set to true, sources will be forced into end of stream and all emitted packets will be processed. Otherwise everything is discarded, potentially breaking output files +\return the error code if any +*/ +GF_Err gf_fs_abort(GF_FilterSession *session, Bool do_flush); +/*! Checks if the session is processing its last task. This can be called within a callback task to check if this is the last task, in order to avoid rescheduling the task +\param session filter session +\return GF_TRUE if no more task, GF_FALSE otherwise +*/ +Bool gf_fs_is_last_task(GF_FilterSession *session); + +/*! Checks if a given MIME type is supported as input +\param session filter session +\param mime MIME type to query +\return GF_TRUE if MIME is supported +*/ +Bool gf_fs_mime_supported(GF_FilterSession *session, const char *mime); + + +/*! Sets UI callback event +\param session filter session +\param ui_event_proc the event proc callback function. Its return value depends on the event type, usually 0 +\param cbk_udta pointer passed back to callback +*/ +void gf_fs_set_ui_callback(GF_FilterSession *session, Bool (*ui_event_proc)(void *opaque, GF_Event *event), void *cbk_udta); + +/*! Prints stats to logs using \code LOG_APP@LOG_INFO \endcode +\param session filter session +*/ +void gf_fs_print_stats(GF_FilterSession *session); + +/*! Prints connections between loaded filters in the session to logs using \code LOG_APP@LOG_INFO \endcode +\param session filter session +*/ +void gf_fs_print_connections(GF_FilterSession *session); + +/*! Prints all possible connections between filter registries to logs using \code LOG_APP@LOG_INFO \endcode +\param session filter session +\param filter_name if not null, only prints input connection for this filter register +\param print_fn optional callback function for print, otherwise print to stderr +*/ +void gf_fs_print_all_connections(GF_FilterSession *session, char *filter_name, void (*print_fn)(FILE *output, GF_SysPrintArgFlags flags, const char *fmt, ...) ); + +/*! Checks the presence of an input capability and an output capability in a target register. The caps are matched only if they belong to the same bundle. +\param filter_reg filter register to check +\param in_cap_code capability code (property type) of input capability to check +\param in_cap capabiility value of input capability to check +\param out_cap_code capability code (property type) of output capability to check +\param out_cap capability value of output capability to check +\param exact_match_only if true returns TRUE only if exact match (code and value), otherwise return TRUE if caps code are matched +\return GF_TRUE if filter register has such a match, GF_FALSE otherwise +*/ +Bool gf_fs_check_filter_register_cap(const GF_FilterRegister *filter_reg, u32 in_cap_code, GF_PropertyValue *in_cap, u32 out_cap_code, GF_PropertyValue *out_cap, Bool exact_match_only); + + +/*! Enables or disables filter reporting +\param session filter session +\param reporting_on if GF_TRUE, reporting will be enabled +*/ +void gf_fs_enable_reporting(GF_FilterSession *session, Bool reporting_on); + +/*! Locks global session mutex - mostly used to query filter reports and avoids concurrent destruction of a filter +\param session filter session +\param do_lock if GF_TRUE, session is locked, otherwise session is unlocked +*/ +void gf_fs_lock_filters(GF_FilterSession *session, Bool do_lock); + +/*! Gets number of active filters in the session +\param session filter session +\return number of active filters +*/ +u32 gf_fs_get_filters_count(GF_FilterSession *session); + +/*! Type of filter*/ +typedef enum +{ + /*! Unknown filter type*/ + GF_FS_STATS_FILTER_UNKNOWN, + /*! raw input (file, socket, pipe) filter type*/ + GF_FS_STATS_FILTER_RAWIN, + /*! demultiplexer filter type*/ + GF_FS_STATS_FILTER_DEMUX, + /*! decoder filter type*/ + GF_FS_STATS_FILTER_DECODE, + /*! encoder filter type*/ + GF_FS_STATS_FILTER_ENCODE, + /*! multiplexer filter type*/ + GF_FS_STATS_FILTER_MUX, + /*! raw output (file, socket, pipe) filter type*/ + GF_FS_STATS_FILTER_RAWOUT, + /*! media sink (video out, audio out, ...) filter type*/ + GF_FS_STATS_FILTER_MEDIA_SINK, + /*! media source (capture audio or video ...) filter type*/ + GF_FS_STATS_FILTER_MEDIA_SOURCE, +} GF_FSFilterType; + +/*! Filter statistics object*/ +typedef struct +{ + /*!filter object*/ + const GF_Filter *filter; + /*!set if filter is only an alias, in which case all remaining fields of the structure are not set*/ + const GF_Filter *filter_alias; + + /*!number of tasks executed by this filter*/ + u64 nb_tasks_done; + /*!number of packets processed by this filter*/ + u64 nb_pck_processed; + /*!number of bytes processed by this filter*/ + u64 nb_bytes_processed; + /*!number of packets sent by this filter*/ + u64 nb_pck_sent; + /*!number of hardware frames packets sent by this filter*/ + u64 nb_hw_pck_sent; + /*!number of processing errors in the lifetime of the filter*/ + u32 nb_errors; + + /*!number of bytes sent by this filter*/ + u64 nb_bytes_sent; + /*!number of microseconds this filter was active*/ + u64 time_process; + /*!percentage of data processed (between 0 and 100), otherwise unknown (-1)*/ + s32 percent; + /*!last status report from filter, null if session reporting is not enabled*/ + const char *status; + /*!set to GF_TRUE of status or percent changed since last query for this filter, GF_FALSE otherwise*/ + Bool report_updated; + /*!filter name*/ + const char *name; + /*!filter register name*/ + const char *reg_name; + /*!filter register ID*/ + const char *filter_id; + /*!set to GF_TRUE if filter is done processing*/ + Bool done; + /*!number of input PIDs*/ + u32 nb_pid_in; + /*!number of input packets processed*/ + u64 nb_in_pck; + /*!number of output PIDs*/ + u32 nb_pid_out; + /*!number of output packets sent*/ + u64 nb_out_pck; + /*!set to GF_TRUE if filter has seen end of stream*/ + Bool in_eos; + /*!set to GF_TRUE if filter has seen end of stream*/ + GF_FSFilterType type; + /*!set to streamtype of output PID if single output, GF_STREAM_UNKNOWN otherwise*/ + u32 stream_type; + /*!set to codecid of output PID if single output, GF_CODECID_NONE otherwise*/ + u32 codecid; + /*! timestamp and timescale of last packet emitted on output pids*/ + GF_Fraction64 last_ts_sent; + /*! timestamp and timescale of last packet droped on input pids*/ + GF_Fraction64 last_ts_drop; +} GF_FilterStats; + +/*! Gets statistics for a given filter index in the session +\param session filter session +\param idx index of filter to query +\param stats statistics for filter +\return error code if any +*/ +GF_Err gf_fs_get_filter_stats(GF_FilterSession *session, u32 idx, GF_FilterStats *stats); + + +/*! Enumerates filter and meta-filter arguments not matched in the session +\param session filter session +\param idx index of argument to query, 0 being first argument; this value is automatically incremented +\param argname set to argument name +\param argtype set to argument type: 0 was a filter param (eg:arg=val), 1 was a global arg (eg --arg=val) and 2 was a global meta arg (eg -+arg=val) +\return GF_TRUE if success, GF_FALSE if nothing more to enumerate +*/ +Bool gf_fs_enum_unmapped_options(GF_FilterSession *session, u32 *idx, char **argname, u32 *argtype); + + + +/*! Flags for argument update event*/ +typedef enum +{ + /*! the update event can be sent down the source chain*/ + GF_FILTER_UPDATE_DOWNSTREAM = 1<<1, + /*! the update event can be sent up the filter chain*/ + GF_FILTER_UPDATE_UPSTREAM = 1<<2, +} GF_EventPropagateType; + +/*! Enumerates filter and meta-filter arguments not matched in the session +\param session filter session +\param fid ID of filter on which to send the update, NULL if filter is set +\param filter filter on which to send the update, NULL if fid is set +\param name name of filter option to update +\param val value of filter option to update +\param propagate_mask propagation flags - 0 means no propagation +*/ +void gf_fs_send_update(GF_FilterSession *session, const char *fid, GF_Filter *filter, const char *name, const char *val, GF_EventPropagateType propagate_mask); + + +/*! Loads JS script for filter session +\param session filter session +\param jsfile path to local JS script file to use +\return error if any +*/ +GF_Err gf_fs_load_script(GF_FilterSession *session, const char *jsfile); + +/*! @} */ + + +/*! +\addtogroup fs_props Filter Properties +\ingroup filters_grp +\brief PID and filter properties + +Documents the property object used for PID and packets. +@{ + */ + +/*! Property types*/ +typedef enum +{ + /*! not allowed*/ + GF_PROP_FORBIDEN=0, + /*! signed 32 bit integer*/ + GF_PROP_SINT, + /*! unsigned 32 bit integer*/ + GF_PROP_UINT, + /*! signed 64 bit integer*/ + GF_PROP_LSINT, + /*! unsigned 64 bit integer*/ + GF_PROP_LUINT, + /*! boolean*/ + GF_PROP_BOOL, + /*! 32 bit / 32 bit fraction*/ + GF_PROP_FRACTION, + /*! 64 bit / 64 bit fraction*/ + GF_PROP_FRACTION64, + /*! float (Fixed) number*/ + GF_PROP_FLOAT, + /*! double number*/ + GF_PROP_DOUBLE, + /*! 2D signed integer vector*/ + GF_PROP_VEC2I, + /*! 2D double number vector*/ + GF_PROP_VEC2, + /*! 3D signed integer vector*/ + GF_PROP_VEC3I, + /*! 3D double number vector*/ + GF_PROP_VEC3, + /*! 4D signed integer vector*/ + GF_PROP_VEC4I, + /*! 4D double number vector*/ + GF_PROP_VEC4, + /*! Video Pixel format*/ + GF_PROP_PIXFMT, + /*! Audio PCM format*/ + GF_PROP_PCMFMT, + /*! string property, memory is duplicated when setting the property and managed internally*/ + GF_PROP_STRING, + /*! string property, memory is NOT duplicated when setting the property but is then managed (and free) internally. + Only used when setting a property, the type then defaults to GF_PROP_STRING*/ + GF_PROP_STRING_NO_COPY, + /*! data property, memory is duplicated when setting the property and managed internally*/ + GF_PROP_DATA, + /*! const string property, memory is NOT duplicated when setting the property, stays user-managed*/ + GF_PROP_NAME, + /*! data property, memory is NOT duplicated when setting the property but is then managed (and free) internally. + Only used when setting a property, the type then defaults to GF_PROP_DATA*/ + GF_PROP_DATA_NO_COPY, + /*! const data property, memory is NOT duplicated when setting the property, stays user-managed*/ + GF_PROP_CONST_DATA, + /*! user-managed pointer*/ + GF_PROP_POINTER, + /*! string list, memory is NOT duplicated when setting the property, the GF_List * of + the passed property is directly assigned to the new property and will be and managed internally (freed by the filter session)*/ + GF_PROP_STRING_LIST, + /*! unsigned 32 bit integer list, memory is ALWAYS duplicated when setting the property*/ + GF_PROP_UINT_LIST, + + /*! not allowed*/ + GF_PROP_LAST_DEFINED, +} GF_PropType; + +/*! Data property*/ +typedef struct +{ + /*! data pointer */ + u8 *ptr; + /*! data size */ + u32 size; +} GF_PropData; + +/*! List of unsigned int property*/ +typedef struct +{ + /*! array of unsigned integers */ + u32 *vals; + /*! number of items in array */ + u32 nb_items; +} GF_PropUIntList; + +/*! 2D signed integer vector property*/ +typedef struct +{ + /*! x coord */ + s32 x; + /*! y coord */ + s32 y; +} GF_PropVec2i; + +/*! 2D double number vector property*/ +typedef struct +{ + /*! x coord */ + Double x; + /*! y coord */ + Double y; +} GF_PropVec2; + +/*! 3D signed integer vector property*/ +typedef struct +{ + /*! x coord */ + s32 x; + /*! y coord */ + s32 y; + /*! z coord */ + s32 z; +} GF_PropVec3i; + +/*! 3D double number vector property*/ +typedef struct +{ + /*! x coord */ + Double x; + /*! y coord */ + Double y; + /*! z coord */ + Double z; +} GF_PropVec3; + +/*! 4D signed integer vector property*/ +typedef struct +{ + /*! x coord */ + s32 x; + /*! y coord */ + s32 y; + /*! z coord */ + s32 z; + /*! w coord */ + s32 w; +} GF_PropVec4i; + +/*! 4D double number vector property*/ +typedef struct +{ + /*! x coord */ + Double x; + /*! y coord */ + Double y; + /*! z coord */ + Double z; + /*! w coord */ + Double w; +} GF_PropVec4; + +/*! Property value used by PIDs and packets*/ +struct __gf_prop_val +{ + /*! type of the property */ + GF_PropType type; + /*! union of all possible property data types */ + union { + /*! 64 bit unsigned integer value of property */ + u64 longuint; + /*! 64 bit signed integer value of property */ + s64 longsint; + /*! 32 bit signed integer value of property */ + s32 sint; + /*! 32 bit unsigned integer value of property */ + u32 uint; + /*! boolean value of property */ + Bool boolean; + /*! fraction (32/32 bits) value of property */ + GF_Fraction frac; + /*! fraction (64/64 bits) value of property */ + GF_Fraction64 lfrac; + /*! fixed number (float or 32 bit depending on compilation settings) value of property */ + Fixed fnumber; + /*! double value of property */ + Double number; + /*! 2D signed integer vector value of property */ + GF_PropVec2i vec2i; + /*! 2D double vector value of property */ + GF_PropVec2 vec2; + /*! 3D signed integer vector value of property */ + GF_PropVec3i vec3i; + /*! 3D double vector value of property */ + GF_PropVec3 vec3; + /*! 4D signed integer vector value of property */ + GF_PropVec4i vec4i; + /*! 4D double vector value of property */ + GF_PropVec4 vec4; + /*! data value of property. For non const data type, the memory is freed by filter session. + Otherwise caller is responsible to free it at end of filter/session*/ + GF_PropData data; + /*! string value of property. For non const string / names, the memory is freed by filter session, otherwise handled as const char *. */ + char *string; + /*! pointer value of property */ + void *ptr; + /*! string list value of property - memory is handled by filter session (always copy)*/ + GF_List *string_list; + /*! unsigned integer list value of property - memory is handled by filter session (always copy)*/ + GF_PropUIntList uint_list; + } value; +}; + +/*! Playback mode type supported on PID*/ +typedef enum +{ + /*! simplest playback mode, can play from 0 at speed=1 only*/ + GF_PLAYBACK_MODE_NONE=0, + /*! seek playback mode, can play from any position at speed=1 only*/ + GF_PLAYBACK_MODE_SEEK, + /*! fast forward playback mode, can play from any position at speed=N only, with N>=0*/ + GF_PLAYBACK_MODE_FASTFORWARD, + /*! rewind playback mode, can play from any position at speed=N, N positive or negative*/ + GF_PLAYBACK_MODE_REWIND +} GF_FilterPidPlaybackMode; + +/*! Built-in property types +See gpac help (gpac -h props) for codes, types, formats and and meaning + \hideinitializer +*/ +enum +{ + GF_PROP_PID_ID = GF_4CC('P','I','D','I'), + GF_PROP_PID_ESID = GF_4CC('E','S','I','D'), + GF_PROP_PID_ITEM_ID = GF_4CC('I','T','I','D'), + GF_PROP_PID_ITEM_NUM = GF_4CC('I','T','I','X'), + GF_PROP_PID_TRACK_NUM = GF_4CC('P','I','D','X'), + GF_PROP_PID_SERVICE_ID = GF_4CC('P','S','I','D'), + GF_PROP_PID_CLOCK_ID = GF_4CC('C','K','I','D'), + GF_PROP_PID_DEPENDENCY_ID = GF_4CC('D','P','I','D'), + GF_PROP_PID_SUBLAYER = GF_4CC('D','P','S','L'), + GF_PROP_PID_PLAYBACK_MODE = GF_4CC('P','B','K','M'), + GF_PROP_PID_SCALABLE = GF_4CC('S','C','A','L'), + GF_PROP_PID_TILE_BASE = GF_4CC('S','A','B','T'), + GF_PROP_PID_LANGUAGE = GF_4CC('L','A','N','G'), + GF_PROP_PID_SERVICE_NAME = GF_4CC('S','N','A','M'), + GF_PROP_PID_SERVICE_PROVIDER = GF_4CC('S','P','R','O'), + GF_PROP_PID_STREAM_TYPE = GF_4CC('P','M','S','T'), + GF_PROP_PID_SUBTYPE = GF_4CC('P','S','S','T'), + GF_PROP_PID_ISOM_SUBTYPE = GF_4CC('P','I','S','T'), + GF_PROP_PID_ORIG_STREAM_TYPE = GF_4CC('P','O','S','T'), + GF_PROP_PID_CODECID = GF_4CC('P','O','T','I'), + GF_PROP_PID_IN_IOD = GF_4CC('P','I','O','D'), + GF_PROP_PID_UNFRAMED = GF_4CC('P','F','R','M'), + GF_PROP_PID_UNFRAMED_FULL_AU = GF_4CC('P','F','R','F'), + GF_PROP_PID_DURATION = GF_4CC('P','D','U','R'), + GF_PROP_PID_NB_FRAMES = GF_4CC('N','F','R','M'), + GF_PROP_PID_FRAME_SIZE = GF_4CC('C','F','R','S'), + GF_PROP_PID_TIMESHIFT_DEPTH = GF_4CC('P','T','S','D'), + GF_PROP_PID_TIMESHIFT_TIME = GF_4CC('P','T','S','T'), + GF_PROP_PID_TIMESHIFT_STATE = GF_4CC('P','T','S','S'), + GF_PROP_PID_TIMESCALE = GF_4CC('T','I','M','S'), + GF_PROP_PID_PROFILE_LEVEL = GF_4CC('P','R','P','L'), + GF_PROP_PID_DECODER_CONFIG = GF_4CC('D','C','F','G'), + GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT = GF_4CC('E','C','F','G'), + GF_PROP_PID_CONFIG_IDX = GF_4CC('I','C','F','G'), + GF_PROP_PID_SAMPLE_RATE = GF_4CC('A','U','S','R'), + GF_PROP_PID_SAMPLES_PER_FRAME = GF_4CC('F','R','M','S'), + GF_PROP_PID_NUM_CHANNELS = GF_4CC('C','H','N','B'), + GF_PROP_PID_AUDIO_BPS = GF_4CC('A','B','P','S'), + GF_PROP_PID_CHANNEL_LAYOUT = GF_4CC('C','H','L','O'), + GF_PROP_PID_AUDIO_FORMAT = GF_4CC('A','F','M','T'), + GF_PROP_PID_AUDIO_SPEED = GF_4CC('A','S','P','D'), + GF_PROP_PID_UNFRAMED_LATM = GF_4CC('L','A','T','M'), + GF_PROP_PID_DELAY = GF_4CC('M','D','L','Y'), + GF_PROP_PID_CTS_SHIFT = GF_4CC('M','D','T','S'), + GF_PROP_PID_WIDTH = GF_4CC('W','I','D','T'), + GF_PROP_PID_HEIGHT = GF_4CC('H','E','I','G'), + GF_PROP_PID_PIXFMT = GF_4CC('P','F','M','T'), + GF_PROP_PID_PIXFMT_WRAPPED = GF_4CC('P','F','M','W'), + GF_PROP_PID_STRIDE = GF_4CC('V','S','T','Y'), + GF_PROP_PID_STRIDE_UV = GF_4CC('V','S','T','C'), + GF_PROP_PID_BIT_DEPTH_Y = GF_4CC('Y','B','P','S'), + GF_PROP_PID_BIT_DEPTH_UV = GF_4CC('C','B','P','S'), + GF_PROP_PID_FPS = GF_4CC('V','F','P','F'), + GF_PROP_PID_INTERLACED = GF_4CC('V','I','L','C'), + GF_PROP_PID_SAR = GF_4CC('P','S','A','R'), + GF_PROP_PID_PAR = GF_4CC('V','P','A','R'), + GF_PROP_PID_WIDTH_MAX = GF_4CC('M', 'W','I','D'), + GF_PROP_PID_HEIGHT_MAX = GF_4CC('M', 'H','E','I'), + GF_PROP_PID_ZORDER = GF_4CC('V', 'Z','I','X'), + GF_PROP_PID_TRANS_X = GF_4CC('V','T','R','X'), + GF_PROP_PID_TRANS_Y = GF_4CC('V','T','R','Y'), + GF_PROP_PID_HIDDEN = GF_4CC('H','I','D','E'), + GF_PROP_PID_CROP_POS = GF_4CC('V','C','X','Y'), + GF_PROP_PID_ORIG_SIZE = GF_4CC('V','O','W','H'), + GF_PROP_PID_SRD = GF_4CC('S','R','D',' '), + GF_PROP_PID_SRD_REF = GF_4CC('S','R','D','R'), + GF_PROP_PID_SRD_MAP = GF_4CC('S','R','D','M'), + GF_PROP_PID_ALPHA = GF_4CC('V','A','L','P'), + GF_PROP_PID_DOLBY_VISION = GF_4CC('D','O','V','I'), + GF_PROP_PID_BITRATE = GF_4CC('R','A','T','E'), + GF_PROP_PID_MAXRATE = GF_4CC('M','R','A','T'), + GF_PROP_PID_DBSIZE = GF_4CC('D','B','S','Z'), + GF_PROP_PID_MEDIA_DATA_SIZE = GF_4CC('M','D','S','Z'), + GF_PROP_PID_CAN_DATAREF = GF_4CC('D','R','E','F'), + GF_PROP_PID_URL = GF_4CC('F','U','R','L'), + GF_PROP_PID_REMOTE_URL = GF_4CC('R','U','R','L'), + GF_PROP_PID_REDIRECT_URL = GF_4CC('R','E','L','O'), + GF_PROP_PID_FILEPATH = GF_4CC('F','S','R','C'), + GF_PROP_PID_MIME = GF_4CC('M','I','M','E'), + GF_PROP_PID_FILE_EXT = GF_4CC('F','E','X','T'), + GF_PROP_PID_OUTPATH = GF_4CC('F','D','S','T'), + GF_PROP_PID_FILE_CACHED = GF_4CC('C','A','C','H'), + GF_PROP_PID_DOWN_RATE = GF_4CC('D','L','B','W'), + GF_PROP_PID_DOWN_SIZE = GF_4CC('D','L','S','Z'), + GF_PROP_PID_DOWN_BYTES = GF_4CC('D','L','B','D'), + GF_PROP_PID_FILE_RANGE = GF_4CC('F','B','R','A'), + GF_PROP_PID_DISABLE_PROGRESSIVE = GF_4CC('N','P','R','G'), + GF_PROP_PID_ISOM_BRANDS = GF_4CC('A','B','R','D'), + GF_PROP_PID_ISOM_MBRAND = GF_4CC('M','B','R','D'), + GF_PROP_PID_ISOM_MOVIE_TIME = GF_4CC('M','H','T','S'), + GF_PROP_PID_HAS_SYNC = GF_4CC('P','S','Y','N'), + GF_PROP_SERVICE_WIDTH = GF_4CC('D','W','D','T'), + GF_PROP_SERVICE_HEIGHT = GF_4CC('D','H','G','T'), + GF_PROP_PID_CAROUSEL_RATE = GF_4CC('C','A','R','A'), + GF_PROP_PID_UTC_TIME = GF_4CC('U','T','C','D'), + GF_PROP_PID_UTC_TIMESTAMP = GF_4CC('U','T','C','T'), + GF_PROP_PID_AUDIO_VOLUME = GF_4CC('A','V','O','L'), + GF_PROP_PID_AUDIO_PAN = GF_4CC('A','P','A','N'), + GF_PROP_PID_AUDIO_PRIORITY = GF_4CC('A','P','R','I'), + GF_PROP_PID_PROTECTION_SCHEME_TYPE = GF_4CC('S','C','H','T'), + GF_PROP_PID_PROTECTION_SCHEME_VERSION = GF_4CC('S','C','H','V'), + GF_PROP_PID_PROTECTION_SCHEME_URI = GF_4CC('S','C','H','U'), + GF_PROP_PID_PROTECTION_KMS_URI = GF_4CC('K','M','S','U'), + GF_PROP_PID_ISMA_SELECTIVE_ENC = GF_4CC('I','S','S','E'), + GF_PROP_PID_ISMA_IV_LENGTH = GF_4CC('I','S','I','V'), + GF_PROP_PID_ISMA_KI_LENGTH = GF_4CC('I','S','K','I'), + GF_PROP_PID_ISMA_KI = GF_4CC('I','K','E','Y'), + GF_PROP_PID_OMA_CRYPT_TYPE = GF_4CC('O','M','C','T'), + GF_PROP_PID_OMA_CID = GF_4CC('O','M','I','D'), + GF_PROP_PID_OMA_TXT_HDR = GF_4CC('O','M','T','H'), + GF_PROP_PID_OMA_CLEAR_LEN = GF_4CC('O','M','P','T'), + GF_PROP_PID_CRYPT_INFO = GF_4CC('E','C','R','I'), + GF_PROP_PID_DECRYPT_INFO = GF_4CC('E','D','R','I'), + GF_PROP_PCK_SENDER_NTP = GF_4CC('N','T','P','S'), + GF_PROP_PCK_RECEIVER_NTP = GF_4CC('N','T','P','R'), + GF_PROP_PID_ADOBE_CRYPT_META = GF_4CC('A','M','E','T'), + GF_PROP_PID_ENCRYPTED = GF_4CC('E','P','C','K'), + GF_PROP_PID_OMA_PREVIEW_RANGE = GF_4CC('O','D','P','R'), + GF_PROP_PID_CENC_PSSH = GF_4CC('P','S','S','H'), + GF_PROP_PCK_CENC_SAI = GF_4CC('S','A','I','S'), + GF_PROP_PID_KID = GF_4CC('S','K','I','D'), + GF_PROP_PID_CENC_IV_SIZE = GF_4CC('S','A','I','V'), + GF_PROP_PID_CENC_IV_CONST = GF_4CC('C','B','I','V'), + GF_PROP_PID_CENC_PATTERN = GF_4CC('C','P','T','R'), + GF_PROP_PID_CENC_STORE = GF_4CC('C','S','T','R'), + GF_PROP_PID_CENC_STSD_MODE = GF_4CC('C','S','T','M'), + GF_PROP_PID_AMR_MODE_SET = GF_4CC('A','M','S','T'), + GF_PROP_PCK_SUBS = GF_4CC('S','U','B','S'), + GF_PROP_PID_MAX_NALU_SIZE = GF_4CC('N','A','L','S'), + GF_PROP_PCK_FILENUM = GF_4CC('F','N','U','M'), + GF_PROP_PCK_FILENAME = GF_4CC('F','N','A','M'), + GF_PROP_PCK_IDXFILENAME = GF_4CC('I','N','A','M'), + GF_PROP_PCK_FILESUF = GF_4CC('F','S','U','F'), + GF_PROP_PCK_EODS = GF_4CC('E','O','D','S'), + GF_PROP_PID_MAX_FRAME_SIZE = GF_4CC('M','F','R','S'), + GF_PROP_PID_AVG_FRAME_SIZE = GF_4CC('A','F','R','S'), + GF_PROP_PID_MAX_TS_DELTA = GF_4CC('M','T','S','D'), + GF_PROP_PID_MAX_CTS_OFFSET = GF_4CC('M','C','T','O'), + GF_PROP_PID_CONSTANT_DURATION = GF_4CC('S','C','T','D'), + GF_PROP_PID_ISOM_TRACK_TEMPLATE = GF_4CC('I','T','K','T'), + GF_PROP_PID_ISOM_TREX_TEMPLATE = GF_4CC('I','T','X','T'), + GF_PROP_PID_ISOM_STSD_TEMPLATE = GF_4CC('I','S','T','D'), + GF_PROP_PID_ISOM_UDTA = GF_4CC('I','M','U','D'), + GF_PROP_PID_ISOM_HANDLER = GF_4CC('I','H','D','L'), + GF_PROP_PID_ISOM_TRACK_FLAGS = GF_4CC('I','T','K','F'), + GF_PROP_PID_ISOM_TRACK_MATRIX = GF_4CC('I','T','K','M'), + GF_PROP_PID_PERIOD_ID = GF_4CC('P','E','I','D'), + GF_PROP_PID_PERIOD_START = GF_4CC('P','E','S','T'), + GF_PROP_PID_PERIOD_DUR = GF_4CC('P','E','D','U'), + GF_PROP_PID_REP_ID = GF_4CC('D','R','I','D'), + GF_PROP_PID_AS_ID = GF_4CC('D','A','I','D'), + GF_PROP_PID_MUX_SRC = GF_4CC('M','S','R','C'), + GF_PROP_PID_DASH_MODE = GF_4CC('D','M','O','D'), + GF_PROP_PID_DASH_DUR = GF_4CC('D','D','U','R'), + GF_PROP_PID_DASH_MULTI_PID = GF_4CC('D','M','S','D'), + GF_PROP_PID_DASH_MULTI_PID_IDX = GF_4CC('D','M','S','I'), + GF_PROP_PID_DASH_MULTI_TRACK = GF_4CC('D','M','T','K'), + GF_PROP_PID_ROLE = GF_4CC('R','O','L','E'), + GF_PROP_PID_PERIOD_DESC = GF_4CC('P','D','E','S'), + GF_PROP_PID_AS_COND_DESC = GF_4CC('A','C','D','S'), + GF_PROP_PID_AS_ANY_DESC = GF_4CC('A','A','D','S'), + GF_PROP_PID_REP_DESC = GF_4CC('R','D','E','S'), + GF_PROP_PID_BASE_URL = GF_4CC('B','U','R','L'), + GF_PROP_PID_TEMPLATE = GF_4CC('D','T','P','L'), + GF_PROP_PID_START_NUMBER = GF_4CC('D','R','S','N'), + GF_PROP_PID_XLINK = GF_4CC('X','L','N','K'), + GF_PROP_PID_CLAMP_DUR = GF_4CC('D','C','M','D'), + GF_PROP_PID_HLS_PLAYLIST = GF_4CC('H','L','V','P'), + GF_PROP_PID_HLS_GROUPID = GF_4CC('H','L','G','I'), + GF_PROP_PID_DASH_CUE = GF_4CC('D','C','U','E'), + GF_PROP_PID_DASH_SEGMENTS = GF_4CC('D','C','N','S'), + GF_PROP_PID_CODEC = GF_4CC('C','O','D','S'), + GF_PROP_PID_SINGLE_SCALE = GF_4CC('D','S','T','S'), + GF_PROP_PID_UDP = GF_4CC('P','U','D','P'), + + GF_PROP_PID_PRIMARY_ITEM = GF_4CC('P','I','T','M'), + + GF_PROP_PID_COLR_PRIMARIES = GF_4CC('C','P','R','M'), + GF_PROP_PID_COLR_TRANSFER = GF_4CC('C','T','R','C'), + GF_PROP_PID_COLR_MX = GF_4CC('C','M','X','C'), + GF_PROP_PID_COLR_RANGE = GF_4CC('C','F','R','A'), + GF_PROP_PID_COLR_CHROMALOC = GF_4CC('C','L','O','C'), + GF_PROP_PID_COLR_SPACE = GF_4CC('C','S','P','C'), + GF_PROP_PID_SRC_MAGIC = GF_4CC('P','S','M','G'), + GF_PROP_PID_MUX_INDEX = GF_4CC('T','I','D','X'), + GF_PROP_NO_TS_LOOP = GF_4CC('N','T','S','L'), + GF_PROP_PCK_FRAG_START = GF_4CC('P','F','R','B'), + GF_PROP_PCK_FRAG_RANGE = GF_4CC('P','F','R','R'), + GF_PROP_PCK_SIDX_RANGE = GF_4CC('P','F','S','R'), + GF_PROP_PCK_MOOF_TEMPLATE = GF_4CC('M','F','T','P'), + GF_PROP_PID_RAWGRAB = GF_4CC('P','G','R','B'), + GF_PROP_PID_KEEP_AFTER_EOS = GF_4CC('P','K','A','E'), + GF_PROP_PID_COVER_ART = GF_4CC('P','C','O','V'), + //internal property indicating pointer to associated GF_DownloadSession + GF_PROP_PID_DOWNLOAD_SESSION = GF_4CC('G','H','T','T') +}; + +/*! Block patching requirements for FILE pids, as signaled by GF_PROP_PID_DISABLE_PROGRESSIVE + \hideinitializer +*/ +enum +{ + GF_PID_FILE_PATCH_NONE = 0, + GF_PID_FILE_PATCH_REPLACE = 1, + GF_PID_FILE_PATCH_INSERT = 2, +}; + +/*! Gets readable name of built-in property +\param prop_4cc property built-in 4cc +\return readable name +*/ +const char *gf_props_4cc_get_name(u32 prop_4cc); + +/*! Gets property type of built-in property +\param prop_4cc property built-in 4cc +\return property name +*/ +u32 gf_props_4cc_get_type(u32 prop_4cc); + +/*! Checks if two properties are equal +\param p1 first property to compare +\param p2 second property to compare +\return GF_TRUE if properties are equal, GF_FALSE otherwise +*/ +Bool gf_props_equal(const GF_PropertyValue *p1, const GF_PropertyValue *p2); + +/*! Gets the readable name for a property type +\param type property type +\return readable name +*/ +const char *gf_props_get_type_name(GF_PropType type); + +/*! Gets the description for a property type +\param type property type +\return description +*/ +const char *gf_props_get_type_desc(GF_PropType type); + +/*! Gets the description type for a given property type name +\param name property type name +\return property type or GF_PROP_FORBIDEN +*/ +GF_PropType gf_props_parse_type(const char *name); + + +/*! Parses a property value from string +\param type property type to parse +\param name property name to parse (for logs) +\param value string containing the value to parse +\param enum_values string containig enum_values, or NULL. enum_values are used for unsigned int properties, take the form "a|b|c" and resolve to 0|1|2. +\param list_sep_char value of the list seperator character to use +\return the parsed property value +*/ +GF_PropertyValue gf_props_parse_value(u32 type, const char *name, const char *value, const char *enum_values, char list_sep_char); + +/*! Maximum string size to use when dumping a property*/ +#define GF_PROP_DUMP_ARG_SIZE 100 + +/*! Dumps a property value to string +\param att property value +\param dump buffer holding the resulting value for types requiring string conversions (integers, ...) +\param dump_data if set data will be dumped in hexadecimal. Otherwise, data buffer is not dumped +\param min_max_enum optional, gives the min/max or enum string when the property is a filter argument +\return string +*/ +const char *gf_props_dump_val(const GF_PropertyValue *att, char dump[GF_PROP_DUMP_ARG_SIZE], Bool dump_data, const char *min_max_enum); + +/*! Dumps a property value to string, resolving any built-in types (pix formats, codec id, ...) +\param p4cc property 4CC +\param att property value +\param dump buffer holding the resulting value for types requiring string conversions (integers, ...) +\param dump_data if set data will be dumped in hexadecimal. Otherwise, data buffer is not dumped +\return string +*/ +const char *gf_props_dump(u32 p4cc, const GF_PropertyValue *att, char dump[GF_PROP_DUMP_ARG_SIZE], Bool dump_data); + +/*! Resets a property value, freeing allocated data or strings depending on the property type +\param prop property 4CC +*/ +void gf_props_reset_single(GF_PropertyValue *prop); + +/*! Property aplies only to packets */ +#define GF_PROP_FLAG_PCK 1 +/*! Property is optional for GPAC GSF serialization (not transmitted over network when property removal is enabled) */ +#define GF_PROP_FLAG_GSF_REM 1<<1 + +/*! Structure describing a built-in property in GPAC*/ +typedef struct { + /*! identifier (4CC) */ + u32 type; + /*! name */ + const char *name; + /*! description */ + const char *description; + /*! data type (uint, float, etc ..) */ + u8 data_type; + /*! flags for the property */ + u8 flags; +} GF_BuiltInProperty; + +/*! Gets property description +\param prop_idx built-in property index, starting from 0 +\return associated property description or NULL if property was not found +*/ +const GF_BuiltInProperty *gf_props_get_description(u32 prop_idx); + +/*! Gets built-in property 4CC from name +\param name built-in property name +\return built-in property 4CC or 0 if not found +*/ +u32 gf_props_get_id(const char *name); + +/*! Gets flags of built-in property +\param prop_4cc built-in property 4CC +\return built-in property flags, 0 if not found +*/ +u8 gf_props_4cc_get_flags(u32 prop_4cc); + +/*! Helper macro to set signed int property */ +#define PROP_SINT(_val) (GF_PropertyValue){.type=GF_PROP_SINT, .value.sint = _val} +/*! Helper macro to set unsigned int property */ +#define PROP_UINT(_val) (GF_PropertyValue){.type=GF_PROP_UINT, .value.uint = _val} +/*! Helper macro to set long signed int property */ +#define PROP_LONGSINT(_val) (GF_PropertyValue){.type=GF_PROP_LSINT, .value.longsint = _val} +/*! Helper macro to set long unsigned int property */ +#define PROP_LONGUINT(_val) (GF_PropertyValue){.type=GF_PROP_LUINT, .value.longuint = _val} +/*! Helper macro to set boolean property */ +#define PROP_BOOL(_val) (GF_PropertyValue){.type=GF_PROP_BOOL, .value.boolean = _val} +/*! Helper macro to set fixed-point number property */ +#define PROP_FIXED(_val) (GF_PropertyValue){.type=GF_PROP_FLOAT, .value.fnumber = _val} +/*! Helper macro to set float property */ +#define PROP_FLOAT(_val) (GF_PropertyValue){.type=GF_PROP_FLOAT, .value.fnumber = FLT2FIX(_val)} +/*! Helper macro to set 32-bit fraction property from integers*/ +#define PROP_FRAC_INT(_num, _den) (GF_PropertyValue){.type=GF_PROP_FRACTION, .value.frac.num = _num, .value.frac.den = _den} +/*! Helper macro to set 32-bit fraction property*/ +#define PROP_FRAC(_val) (GF_PropertyValue){.type=GF_PROP_FRACTION, .value.frac = _val } +/*! Helper macro to set 64-bit fraction property from integers*/ +#define PROP_FRAC64(_val) (GF_PropertyValue){.type=GF_PROP_FRACTION64, .value.lfrac = _val} +/*! Helper macro to set 64-bit fraction property*/ +#define PROP_FRAC64_INT(_num, _den) (GF_PropertyValue){.type=GF_PROP_FRACTION64, .value.lfrac.num = _num, .value.lfrac.den = _den} +/*! Helper macro to set double property */ +#define PROP_DOUBLE(_val) (GF_PropertyValue){.type=GF_PROP_DOUBLE, .value.number = _val} +/*! Helper macro to set string property */ +#define PROP_STRING(_val) (GF_PropertyValue){.type=GF_PROP_STRING, .value.string = (char *) _val} +/*! Helper macro to set string property without string copy (string memory is owned by filter) */ +#define PROP_STRING_NO_COPY(_val) (GF_PropertyValue){.type=GF_PROP_STRING_NO_COPY, .value.string = _val} +/*! Helper macro to set name property */ +#define PROP_NAME(_val) (GF_PropertyValue){.type=GF_PROP_NAME, .value.string = _val} +/*! Helper macro to set data property */ +#define PROP_DATA(_val, _len) (GF_PropertyValue){.type=GF_PROP_DATA, .value.data.ptr = _val, .value.data.size=_len} +/*! Helper macro to set data property without data copy ( memory is owned by filter) */ +#define PROP_DATA_NO_COPY(_val, _len) (GF_PropertyValue){.type=GF_PROP_DATA_NO_COPY, .value.data.ptr = _val, .value.data.size =_len} +/*! Helper macro to set const data property */ +#define PROP_CONST_DATA(_val, _len) (GF_PropertyValue){.type=GF_PROP_CONST_DATA, .value.data.ptr = _val, .value.data.size = _len} +/*! Helper macro to set 2D float vector property */ +#define PROP_VEC2(_val) (GF_PropertyValue){.type=GF_PROP_VEC2, .value.vec2 = _val} +/*! Helper macro to set 2D integer vector property */ +#define PROP_VEC2I(_val) (GF_PropertyValue){.type=GF_PROP_VEC2I, .value.vec2i = _val} +/*! Helper macro to set 2D integer vector property from intergers*/ +#define PROP_VEC2I_INT(_x, _y) (GF_PropertyValue){.type=GF_PROP_VEC2I, .value.vec2i.x = _x, .value.vec2i.y = _y} +/*! Helper macro to set 3D float vector property */ +#define PROP_VEC3(_val) (GF_PropertyValue){.type=GF_PROP_VEC3, .value.vec3 = _val} +/*! Helper macro to set 3D integer vector property */ +#define PROP_VEC3I(_val) (GF_PropertyValue){.type=GF_PROP_VEC3I, .value.vec3i = _val} +/*! Helper macro to set 4D float vector property */ +#define PROP_VEC4(_val) (GF_PropertyValue){.type=GF_PROP_VEC4, .value.vec4 = _val} +/*! Helper macro to set 4D integer vector property */ +#define PROP_VEC4I(_val) (GF_PropertyValue){.type=GF_PROP_VEC4I, .value.vec4i = _val} +/*! Helper macro to set 4D integer vector property from integers */ +#define PROP_VEC4I_INT(_x, _y, _z, _w) (GF_PropertyValue){.type=GF_PROP_VEC4I, .value.vec4i.x = _x, .value.vec4i.y = _y, .value.vec4i.z = _z, .value.vec4i.w = _w} +/*! Helper macro to set pointer property */ +#define PROP_POINTER(_val) (GF_PropertyValue){.type=GF_PROP_POINTER, .value.ptr = (void*)_val} + + +/*! @} */ + + + + +/*! +\addtogroup fs_evt Filter Events +\ingroup filters_grp +\brief Filter Events + +PIDs may receive commands and may emit messages using this system. + +Events may flow downwards (towards the source), in which case they are commands, or upwards (towards the sink), in which case they are informative event. + +A filter not implementing a process_event will result in the event being forwarded down to all input PIDs or up to all output PIDs. + +A filter may decide to cancel an event, in which case the event is no longer forwarded down/up the chain. + +GF_FEVT_PLAY, GF_FEVT_STOP and GF_FEVT_SOURCE_SEEK events will trigger a reset of PID buffers. + +A GF_FEVT_PLAY event on a PID already playing is discarded. + +A GF_FEVT_STOP event on a PID already stopped is discarded. + +GF_FEVT_PLAY and GF_FEVT_SET_SPEED events will trigger larger (abs(speed)>1) or smaller (abs(speed)<1) PID buffer limit in blocking mode. + +GF_FEVT_STOP and GF_FEVT_SOURCE_SEEK events are filtered to reset the PID buffers. + +@{ + */ + +/*! Filter event types */ +typedef enum +{ + /*! PID control, usually triggered by sink - see \ref GF_FilterPidPlaybackMode*/ + GF_FEVT_PLAY = 1, + /*! PID speed control, usually triggered by sink - see \ref GF_FilterPidPlaybackMode*/ + GF_FEVT_SET_SPEED, + /*! PID control, usually triggered by sink - see \ref GF_FilterPidPlaybackMode*/ + GF_FEVT_STOP, + /*! PID pause, usually triggered by sink - see \ref GF_FilterPidPlaybackMode*/ + GF_FEVT_PAUSE, + /*! PID resume, usually triggered by sink - see \ref GF_FilterPidPlaybackMode*/ + GF_FEVT_RESUME, + /*! PID source seek, allows seeking in bytes the source*/ + GF_FEVT_SOURCE_SEEK, + /*! PID source switch, allows a source filter to switch its source URL for the same protocol*/ + GF_FEVT_SOURCE_SWITCH, + /*! DASH segment size info, sent down from muxers to manifest generators*/ + GF_FEVT_SEGMENT_SIZE, + /*! Scene attach event, sent down from compositor to filters (BIFS/OD/timedtext/any scene-related) to share the scene (resources and node graph)*/ + GF_FEVT_ATTACH_SCENE, + /*! Scene reset event, sent down from compositor to filters (BIFS/OD/timedtext/any scene-related) to indicate scene reset (resources and node graph). This is a direct filter call, only sent processed by filters running on the main thread*/ + GF_FEVT_RESET_SCENE, + /*! quality switching request event, helps filters decide how to adapt their processing*/ + GF_FEVT_QUALITY_SWITCH, + /*! visibility hint event, helps filters decide how to adapt their processing*/ + GF_FEVT_VISIBILITY_HINT, + /*! special event type sent to a filter whenever the PID info properties have been modified. No cancel because no forward - cf \ref gf_filter_pid_set_info. A filter returning GF_TRUE on this event will prevent info update notification on the destination filters*/ + GF_FEVT_INFO_UPDATE, + /*! buffer requirement event. This event is NOT sent to filters, it is internaly processed by the filter session. Filters may however send this + event to indicate their buffereing preference (real-time sinks mostly)*/ + GF_FEVT_BUFFER_REQ, + /*! filter session capability change, sent whenever global capabilities (max width, max heoght, ... ) are changed*/ + GF_FEVT_CAPS_CHANGE, + /*! inidicates the PID could not be connected - the PID passed is an output PID of the filter, no specific event structure is associated*/ + GF_FEVT_CONNECT_FAIL, + /*! user event, sent from compositor/vout down to filters*/ + GF_FEVT_USER, + /*! PLAY hint event, used to signal if block dispatch is needed or not for the source*/ + GF_FEVT_PLAY_HINT, + /*! file delete event, sent upstream by dahser to notify file deletion*/ + GF_FEVT_FILE_DELETE, +} GF_FEventType; + +/*! type: the type of the event*/ +/*! on_pid: PID to which the event is targeted. If NULL the event is targeted at the whole filter */ +#define FILTER_EVENT_BASE \ + GF_FEventType type; \ + GF_FilterPid *on_pid; \ + \ + + +/*! Macro helper for event structure initializing*/ +#define GF_FEVT_INIT(_a, _type, _on_pid) { memset(&_a, 0, sizeof(GF_FilterEvent)); _a.base.type = _type; _a.base.on_pid = _on_pid; } + +/*! Base type of events. All events start with the fields defined in \ref FILTER_EVENT_BASE*/ +typedef struct +{ + FILTER_EVENT_BASE +} GF_FEVT_Base; + +/*! Event structure for GF_FEVT_PLAY, GF_FEVT_SET_SPEED*/ +typedef struct +{ + FILTER_EVENT_BASE + + /*! params for : ranges in sec - if range is <0, it means end of file (eg [2, -1] with speed>0 means 2 +oo) */ + Double start_range, end_range; + /*! params for GF_NET_CHAN_PLAY and GF_NET_CHAN_SPEED*/ + Double speed; + + /*! indicates playback should start from given packet number - used by dasher when reloading sources*/ + u32 from_pck; + + /*! set when PLAY event is sent upstream to audio out, indicates HW buffer reset*/ + u8 hw_buffer_reset; + /*! params for GF_FEVT_PLAY only: indicates this is the first PLAY on an element inserted from bcast*/ + u8 initial_broadcast_play; + /*! params for GF_NET_CHAN_PLAY only + 0: range is in media time + 1: range is in timesatmps + 2: range is in media time but timestamps should not be shifted (hybrid dash only for now) + */ + u8 timestamp_based; + /*! indicates the consumer only cares for the full file, not packets*/ + u8 full_file_only; + /*! indicates any current download should be aborted*/ + u8 forced_dash_segment_switch; + /*! indicates non ref frames should be drawn for faster processing*/ + u8 drop_non_ref; + /*! indicates that a demuxer must not forward this event as a source seek because seek has already been done + (typically this play request is a segment play and byte range access within the file has already been performed by DASH client)*/ + u8 no_byterange_forward; +} GF_FEVT_Play; + +/*! Event structure for GF_FEVT_SOURCE_SEEK and GF_FEVT_SOURCE_SWITCH*/ +typedef struct +{ + FILTER_EVENT_BASE + + /*! start offset in source*/ + u64 start_offset; + /*! end offset in source*/ + u64 end_offset; + /*! new path to switch to*/ + const char *source_switch; + /*! indicates previous source was a DASH init segment and should be kept in memory cache*/ + u8 previous_is_init_segment; + /*! ignore cache expiration directive for HTTP*/ + u8 skip_cache_expiration; + /*! hint block size for source, might not be respected*/ + u32 hint_block_size; +} GF_FEVT_SourceSeek; + +/*! Event structure for GF_FEVT_SEGMENT_SIZE*/ +typedef struct +{ + FILTER_EVENT_BASE + /*! URL of segment this info is for, or NULL if single file*/ + const char *seg_url; + /*! global sidx is signaled using is_init=1 and range in idx range*/ + Bool is_init; + /*! media start range in segment file*/ + u64 media_range_start; + /*! media end range in segment file*/ + u64 media_range_end; + /*! index start range in segment file*/ + u64 idx_range_start; + /*! index end range in segment file*/ + u64 idx_range_end; +} GF_FEVT_SegmentSize; + +/*! Event structure for GF_FEVT_ATTACH_SCENE and GF_FEVT_RESET_SCENE +For GF_FEVT_RESET_SCENE, THIS IS A DIRECT FILTER CALL NOT THREADSAFE, filters processing this event SHALL run on the main thread*/ +typedef struct +{ + FILTER_EVENT_BASE + /*! Pointer to a GF_ObjectManager structure for this PID*/ + void *object_manager; +} GF_FEVT_AttachScene; + +/*! Event structure for GF_FEVT_QUALITY_SWITCH*/ +typedef struct +{ + FILTER_EVENT_BASE + + /*! switch quality up or down */ + Bool up; + /*! 0: current group, otherwise index of the depending_on group */ + u32 dependent_group_index; + /*! index of the quality to switch, as indicated in "has:qualities" property. If < 0, sets to automatic quality switching*/ + s32 q_idx; + /*! 1+tile mode adaptation (does not change other selections) */ + u32 set_tile_mode_plus_one; + /*! quality degradation hint, between 0 (full quality) and 100 (lowest quality, stream not currently rendered)*/ + u32 quality_degradation; +} GF_FEVT_QualitySwitch; + +/*! Event structure for GF_FEVT_USER*/ +typedef struct +{ + FILTER_EVENT_BASE + /*! GF_Event structure*/ + GF_Event event; +} GF_FEVT_Event; + +/*! Event structure for GF_FEVT_FILE_DELETE*/ +typedef struct +{ + FILTER_EVENT_BASE + /*! URL to delete*/ + const char *url; +} GF_FEVT_FileDelete; + +/*! Event structure for GF_FEVT_VISIBILITY_HINT*/ +typedef struct +{ + FILTER_EVENT_BASE + /*! gives minimun and maximum coordinates of the visible rectangle associated with channels. min_x may be greater than max_x in case of 360 videos */ + u32 min_x, max_x, min_y, max_y; + /*! if set, only min_x, min_y are used and indicate the gaze direction in pixels in the visual with/height frame (0,0) being top-left*/ + Bool is_gaze; +} GF_FEVT_VisibililityHint; + + +/*! Event structure for GF_FEVT_BUFFER_REQ*/ +typedef struct +{ + FILTER_EVENT_BASE + /*! indicates the max buffer to set on PID - the buffer is only activated on PIDs connected to decoders*/ + u32 max_buffer_us; + /*! indicates the max playout buffer to set on PID (buffer level triggering playback) + Note: this is not used internally by the blocking mechanisms, but may be needed by other filters to take decisions + */ + u32 max_playout_us; + /*! indicates the min playout buffer to set on PID (buffer level triggering rebuffering) + Note: this is not used internally by the blocking mechanisms, but may be needed by other filters to take decisions + */ + u32 min_playout_us; + /*! if set, only the PID target of the event will have the buffer req set; otherwise, the buffer requirement event is passed down the chain until a raw media PID is found or a decoder is found. Used for muxers*/ + Bool pid_only; +} GF_FEVT_BufferRequirement; + +/*! Event object*/ +union __gf_filter_event +{ + GF_FEVT_Base base; + GF_FEVT_Play play; + GF_FEVT_SourceSeek seek; + GF_FEVT_AttachScene attach_scene; + GF_FEVT_Event user_event; + GF_FEVT_QualitySwitch quality_switch; + GF_FEVT_VisibililityHint visibility_hint; + GF_FEVT_BufferRequirement buffer_req; + GF_FEVT_SegmentSize seg_size; + GF_FEVT_FileDelete file_del; +}; + +/*! Gets readable name for event type +\param type type of the event +\return readable name of the event +*/ +const char *gf_filter_event_name(GF_FEventType type); + +/*! @} */ + + +/*! +\addtogroup fs_filter Filter +\ingroup filters_grp +\brief Filter + + +The API for filters documents declaration of filters, their creation and functions available to interact with the filter session. +The gf_filter_* functions shall only be called from within a given filter, this filter being the target of the function. This is not checked at runtime. + +Calling these functions on other filters will result in unpredictable behaviour, very likely crashes in multi-threading conditions. + +A filter is instanciated through a filter register. The register holds entry points to a filter, arguments of a filter (basically property values) and capabilities. + +Capabilities are used to check if a filter can be connected to a given input PID, or if two filters can be directly connected (capability match). + +Capabilities are organized in so-called bundles, gathering the caps that shall be present or not present in the PID / connecting filter. Typically a capability bundle +will contain a stream type for input a stream type for output, a codec id for input and a codec id for output. + +Several capability bundles can be used if needed. A good example is the writegen filter in GPAC, which simply transforms a sequence of media frames into a raw file, hence converts +stream_type/codecID to file extension and MIME type - cf gpac/src/filters/write_generic.c + +When resolving a chain, PID properties are checked against these capabilities. If a property of the same type exists in the PID than in the capability, +it must match the capability requirement (equal, excluded). If no property exists for a given non-optional capability type, + the bundle is marked as not matching and the ext capability bundle in the filter is checked. + A PID property not listed in any capability of the filter does not impact the matching. + +The GF_PROP_PID_FILE_EXT and GF_PROP_PID_MIME are handled as alternate to each other, this allows matching a PID if either its MIME or extension map and avoids failing if the pid has no MIME or extension set. +@{ + */ + + +/*! Structure holding arguments for a filter*/ +typedef enum +{ + /*! used for GUI config: regular argument type */ + GF_FS_ARG_HINT_NORMAL = 0, + /*! used for GUI config: advanced argument type */ + GF_FS_ARG_HINT_ADVANCED = 1<<1, + /*! used for GUI config: expert argument type */ + GF_FS_ARG_HINT_EXPERT = 1<<2, + /*! used for GUI config: hidden argument type */ + GF_FS_ARG_HINT_HIDE = 1<<3, + /*! if set indicates that the argument is updatable. If so, the value will be changed if offset_in_private is valid, and the update_args function will be called if not NULL*/ + GF_FS_ARG_UPDATE = 1<<4, + /*! used by meta filters (ffmpeg & co) to indicate the parsing is handled by the filter in which case the type is overloaded to string and passed to the update_args function*/ + GF_FS_ARG_META = 1<<5, + /*! internal flag used by meta filters (ffmpeg & co) to indicate the description of the argument is a dynamic allocated memory*/ + GF_FS_ARG_META_ALLOC = 1<<6, +} GF_FSArgumentFlags; + +/*! Structure holding arguments for a filter*/ +typedef struct +{ + /*! argument name. Naming conventions: + - only use lowercase + - keep short and avoid '_' + */ + const char *arg_name; + /*! offset of the argument in the structure, -1 means not exposed/stored in structure, in which case it is notified through the update_arg function. + The offset is casted into the corresponding property value of the argument type*/ + s32 offset_in_private; + /*! description of argument. Format conventions: + - first letter is lowercase except if it is an acronym (eg, 'IP'). + - description does not end with a '.'; multiple sentences are however allowed with '.' separators. + - description does not end with a new line CR or LF; multiple sentences are however allowed with '.' separators. + - keep it short. If too long, use " - see filter help" and put description in filter help. + - only use markdown '`' and '*', except for enumeration lists which shall use "- ". + - do not use CR/LF in description except to start enum types description. + - use infinitive (eg \"set foo to bar\" and not \"sets foo to bar\"). + - Enumerations should be described as: + sentence(s)'\n' (no '.' nor ':' at end) + - name1: val'\n' (no '.' at end) + - nameN: val (no '.' nor '\n' at end of last value description) + and enum value description order shall match enum order + - links are allowed, see GF_FilterRegister doc + */ + const char *arg_desc; + /*! type of argument - this is a property type*/ + GF_PropType arg_type; + /*! default value of argument, can be NULL (for number types, value 0 for each dimension of the type is assumed)*/ + const char *arg_default_val; + /*! string describing an enum (unsigned integer type) or a min/max value. + For min/max, the syntax is "min,max", with -I = -infinity and +I = +infinity + For enum, the syntax is "a|b|...", resoling in a=0, b=1,... To skip a value insert '|' eg "|a|b" resolves to a=1, b=2, "a||b" resolves to a=0, b=2 + + Naming conventions for enumeration: + - use single word + - use lowercase escept for single/double characters name (eg 'A', 'V'). + */ + const char *min_max_enum; + /*! set of argument flags*/ + GF_FSArgumentFlags flags; +} GF_FilterArgs; + +/*! Shortcut macro to assign singed integer capability type*/ +#define CAP_SINT(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_SINT, .value.sint = _b}, .flags=(_f) } +/*! Shortcut macro to assign unsigned integer capability type*/ +#define CAP_UINT(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_UINT, .value.uint = _b}, .flags=(_f) } +/*! Shortcut macro to assign signed long integer capability type*/ +#define CAP_LSINT(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_LSINT, .value.longsint = _b}, .flags=(_f) } +/*! Shortcut macro to assign unsigned long integer capability type*/ +#define CAP_LUINT(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_LUINT, .value.longuint = _b}, .flags=(_f) } +/*! Shortcut macro to assign boolean capability type*/ +#define CAP_BOOL(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_BOOL, .value.boolean = _b}, .flags=(_f) } +/*! Shortcut macro to assign fixed-point number capability type*/ +#define CAP_FIXED(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_FLOAT, .value.fnumber = _b}, .flags=(_f) } +/*! Shortcut macro to assign float capability type*/ +#define CAP_FLOAT(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_FLOAT, .value.fnumber = FLT2FIX(_b)}, .flags=(_f) } +/*! Shortcut macro to assign 32-bit fraction capability type*/ +#define CAP_FRAC_INT(_f, _a, _b, _c) { .code=_a, .val={.type=GF_PROP_FRACTION, .value.frac.num = _b, .value.frac.den = _c}, .flags=(_f) } +/*! Shortcut macro to assign 32-bit fraction capability type from integers*/ +#define CAP_FRAC(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_FRACTION, .value.frac = _b}, .flags=(_f) } +/*! Shortcut macro to assign double capability type*/ +#define CAP_DOUBLE(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_DOUBLE, .value.number = _b}, .flags=(_f) } +/*! Shortcut macro to assign name (const string) capability type*/ +#define CAP_NAME(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_NAME, .value.string = _b}, .flags=(_f) } +/*! Shortcut macro to assign string capability type*/ +#define CAP_STRING(_f, _a, _b) { .code=_a, .val={.type=GF_PROP_STRING, .value.string = _b}, .flags=(_f) } +/*! Shortcut macro to assign unsigned integer capability type with capability priority*/ +#define CAP_UINT_PRIORITY(_f, _a, _b, _p) { .code=_a, .val={.type=GF_PROP_UINT, .value.uint = _b}, .flags=(_f), .priority=_p} + +/*! Flags for filter capabilities*/ +enum +{ + /*! when not set, indicates the start of a new set of capabilities. Set by default by the generic GF_CAPS_* */ + GF_CAPFLAG_IN_BUNDLE = 1, + /*! if set this is an input capability of the bundle*/ + GF_CAPFLAG_INPUT = 1<<1, + /*! if set this is an output capability of the bundle. A capability can be declared both as input and output. For example stream type is usually the same on both inputs and outputs*/ + GF_CAPFLAG_OUTPUT = 1<<2, + /*! when set, the capability is valid if the value does not match. If an excluded capability is not found in the destination PID, it is assumed to match*/ + GF_CAPFLAG_EXCLUDED = 1<<3, + /*! when set, the capability is validated only for filter loaded for this destination filter*/ + GF_CAPFLAG_LOADED_FILTER = 1<<4, + /*! Only used for output capabilities, indicates that this capability applies to all bundles. This avoids repeating capabilities common to all bundles by setting them only in the first*/ + GF_CAPFLAG_STATIC = 1<<5, + /*! Only used for input capabilities, indicates that this capability is optional in the input PID */ + GF_CAPFLAG_OPTIONAL = 1<<6, +}; + +/*! Shortcut macro to set for input capability flags*/ +#define GF_CAPS_INPUT (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT) +/*! Shortcut macro to set for optional input capability flags*/ +#define GF_CAPS_INPUT_OPT (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_OPTIONAL) +/*! Shortcut macro to set for static input capability flags*/ +#define GF_CAPS_INPUT_STATIC (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_STATIC) +/*! Shortcut macro to set for static optional input capability flags*/ +#define GF_CAPS_INPUT_STATIC_OPT (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_STATIC|GF_CAPFLAG_OPTIONAL) +/*! Shortcut macro to set for excluded input capability flags*/ +#define GF_CAPS_INPUT_EXCLUDED (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_EXCLUDED) +/*! Shortcut macro to set for input for loaded filter only capability flags*/ +#define GF_CAPS_INPUT_LOADED_FILTER (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_LOADED_FILTER) +/*! Shortcut macro to set for output capability flags*/ +#define GF_CAPS_OUTPUT (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_OUTPUT) +/*! Shortcut macro to set for output for loaded filter only capability flags*/ +#define GF_CAPS_OUTPUT_LOADED_FILTER (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_OUTPUT|GF_CAPFLAG_LOADED_FILTER) +/*! Shortcut macro to set for excluded output capability flags*/ +#define GF_CAPS_OUTPUT_EXCLUDED (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_OUTPUT|GF_CAPFLAG_EXCLUDED) +/*! Shortcut macro to set for static output capability flags*/ +#define GF_CAPS_OUTPUT_STATIC (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_OUTPUT|GF_CAPFLAG_STATIC) +/*! Shortcut macro to set for excluded static output capability flags*/ +#define GF_CAPS_OUTPUT_STATIC_EXCLUDED (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_OUTPUT|GF_CAPFLAG_EXCLUDED|GF_CAPFLAG_STATIC) +/*! Shortcut macro to set for input and output capability flags*/ +#define GF_CAPS_INPUT_OUTPUT (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_OUTPUT) +/*! Shortcut macro to set for optional input and output capability flags*/ +#define GF_CAPS_INPUT_OUTPUT_OPT (GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_OUTPUT|GF_CAPFLAG_OPTIONAL) + +/*! Filter capability description*/ +typedef struct +{ + /*! 4cc of the capability listed. This shall be 0 or the type of a built-in property */ + u32 code; + /*! default type and value of the capability listed*/ + GF_PropertyValue val; + /*! name of the capability listed, NULL if code is set. The special value * is used to indicate that the capability is solved at runtime (the filter must be loaded)*/ + const char *name; + /*! Flags of the capability*/ + u32 flags; + /*! overrides the filter register priority for this capability. Usually 0*/ + u8 priority; +} GF_FilterCapability; + +/*! Filter session capability structure*/ +typedef struct +{ + u32 max_screen_width; + u32 max_screen_height; + u32 max_screen_bpp; + u32 max_screen_fps; + u32 max_screen_nb_views; + u32 max_audio_channels; + u32 max_audio_sample_rate; + u32 max_audio_bit_depth; +} GF_FilterSessionCaps; + +/*! Gets filter session capability +\param filter source of the query - avoids fetching the filter session object +\param caps pointer filled with caps +*/ +void gf_filter_get_session_caps(GF_Filter *filter, GF_FilterSessionCaps *caps); + +/*! Sets filter session capability +\param filter source of the query - avoids fetching the filter session object +\param caps session capability new values - completely replace the old ones +*/ +void gf_filter_set_session_caps(GF_Filter *filter, GF_FilterSessionCaps *caps); + +/*! Filter probe score, used when probing a URL/MIME or when probing formats from data*/ +typedef enum +{ + /*! (de)mux format is not supported*/ + GF_FPROBE_NOT_SUPPORTED = 0, + /*! (de)mux format is supported with potentially missing features*/ + GF_FPROBE_MAYBE_SUPPORTED, + /*! (de)mux format is supported*/ + GF_FPROBE_SUPPORTED, + /*! demux format should be handled by this filter*/ + GF_FPROBE_FORCE, + /*! used by demux formats not supporting data prober*/ + GF_FPROBE_EXT_MATCH, +} GF_FilterProbeScore; + +/*! Quick macro for assigning the capability arrays to the register structure*/ +#define SETCAPS( __struct ) .caps = __struct, .nb_caps=sizeof(__struct)/sizeof(GF_FilterCapability) + +#ifndef GPAC_DISABLE_DOC +/*! Macro for assigning filter register description*/ +#define GF_FS_SET_DESCRIPTION(_desc) .description = _desc, +/*! Macro for assigning filter register author*/ +#define GF_FS_SET_AUTHOR(_author) .author = _author, +/*! Macro for assigning filter register help*/ +#define GF_FS_SET_HELP(_help) .help = _help, +/*! Macro for assigning filter register argument types and description*/ +#define GF_FS_DEF_ARG(_name, _offset, _desc, _type, _default, _enum, _flags) { _name, _offset, _desc, _type, _default, _enum, _flags } +#else +#define GF_FS_SET_DESCRIPTION(_desc) +#define GF_FS_SET_AUTHOR(_author) +#define GF_FS_SET_HELP(_help) +#define GF_FS_SET_ARGHELP(_help) +#define GF_FS_DEF_ARG(_name, _offset, _desc, _type, _default, _enum, _flags) {_name, _offset, _type, _default, _enum, _flags} +#endif + +/*! Filter register flags*/ +typedef enum +{ + /*! when set indicates all calls shall take place in the main thread (running GL output) - to be refined*/ + GF_FS_REG_MAIN_THREAD = 1<<1, + /*! when set indicates the initial call to configure_pid will happen in the main thread. This is typically called by decoders requiring + a GL context (currently only in main thread) upon init, but not requiring it for the decode. Such decoders get their GL frames mapped + (through get_gl_texture callback) in the main GL thread*/ + GF_FS_REG_CONFIGURE_MAIN_THREAD = 1<<2, + /*! when set indicates the filter does not take part of dynamic filter chain resolution and can only be used by explicitly loading the filter*/ + GF_FS_REG_EXPLICIT_ONLY = 1<<3, + /*! when set ignores the filter weight during link resolution - this is typically needed by decoders requiring a specific reframing so that the weight of the reframer+decoder is the same as the weight of other decoders*/ + GF_FS_REG_HIDE_WEIGHT = 1<<4, + /*! Usually set for filters acting as sources but without exposing an src argument. This prevents throwing warnings on arguments not handled by the filter*/ + GF_FS_REG_ACT_AS_SOURCE = 1<<5, + /*! Indicates the filter is likely to block for a long time (typically, network IO). + In multithread mode, this prevents the filter to be scheduled on the main thread, blocking video or audio output. + Ignored in single thread mode.*/ + GF_FS_REG_BLOCKING = 1<<6, + /*! Indicates the filter PIDs may be dynamically added during process (e.g.M2TS, GSF, etc). + This will prevent deactivating a filter when none of its output PIDs are connected*/ + GF_FS_REG_DYNAMIC_PIDS = 1<<7, + /*! Indicates the filter is a script-based filter. The registry is not valid until the script is loaded*/ + GF_FS_REG_SCRIPT = 1<<8, + /*! Indicates the filter is a meta filter, wrapping various underlying filters (e.g., FFmpeg)*/ + GF_FS_REG_META = 1<<9, + /*! Indicates that this filter, when dynamically loaded, allows the link resolver to redirect PID connection to this filter rather than to its next explicetly loaded filter in the chain. + This is typically used by mux filters + */ + GF_FS_REG_DYNAMIC_REDIRECT = 1<<10, + /*! Indicates the filter requires graph resolver (typically because it creates new destinations/sinks at run time)*/ + GF_FS_REG_REQUIRES_RESOLVER = 1<<11, + + /*! flag dynamically set at runtime for registries loaded through shared libraries*/ + GF_FS_REG_DYNLIB = 0x80000000 +} GF_FSRegisterFlags; + +/*! The filter register. Registries are loaded once at the start of the session and shall never be modified after that. +If capabilities need to be changed for a specific filter, use \ref gf_filter_override_caps*/ +struct __gf_filter_register +{ + /*! mandatory - name of the filter as used when setting up filters. Naming conventions: + - shall not contain any space (breaks filter lookup) + - should use lowercase + - should not use '_' + */ + const char *name; + /*! optional - size of private stack structure. The structure is allocated by the framework and arguments are setup before calling any of the filter functions*/ + u32 private_size; + /*! indicates the max number of additional input PIDs - muxers and scalable filters typically set this to (u32) -1. A value of 0 implies the filter can only handle one PID*/ + u32 max_extra_pids; + /*! set of register flags*/ + GF_FSRegisterFlags flags; + + /*! list of PID capabilities*/ + const GF_FilterCapability *caps; + /*! number of PID capabilities*/ + u32 nb_caps; + + /*! optional - filter arguments if any*/ + const GF_FilterArgs *args; + + /*! mandatory - callback for filter processing + This function is called whenever packets are available on the input PID and buffer space is available on the output. + The session will by default monitor a filter for errors, and throw en error if a filter is not consuming nor producing packets for a given amount of process calls. + In some cases, it might be needed to not consume nor produce a packet for a given time (for example, waiting for a packet drop before reconfiguring a filter). + A filter must signal this using \ref gf_filter_ask_rt_reschedule, possibly with no timeout. + + A filter may return GF_EOS to indicate no more data is expected to be produced by this filter + + A filter may return GF_PROFILE_NOT_SUPPORTED to indicate that the filter is not supported (when unable to detect this at configure) and trigger a relink of the filter graph unless disabled at session level. + */ + GF_Err (*process)(GF_Filter *filter); + + /*! optional for sources, mandatory for filters and sinks - callback for PID update may be called several times + on the same PID if PID config is changed. + Since discontinuities may happen at any time, and a filter may fetch packets in burst, + this function may be called while the filter is calling \ref gf_filter_pid_get_packet (this is the only reentrant call for filters) + + \param filter the target filter + \param PID the input PID to configure + \param is_remove indicates the input PID is removed + \return error if any. + a return error of GF_REQUIRES_NEW_INSTANCE indicates the PID cannot be processed in this instance but could be in a clone of the filter. + a return error of GF_FILTER_NOT_SUPPORTED indicates the PID cannot be processed and no alternate chain resolution would help + a return error of GF_BAD_PARAM indicates the PID cannot be processed and no alternate chain resolution would help, and throws a log error message + ano other return error will trigger a reconfigure of the chain to find another filter unless disabled at session level. + */ + GF_Err (*configure_pid)(GF_Filter *filter, GF_FilterPid *PID, Bool is_remove); + + /*! optional - callback for filter initialization - private stack of filter is allocated by framework) + \param filter the target filter + \return error if any. A filter may return GF_EOS to indicate the filter session shall not be run, but that no error should be thrown (used by dasher in realtime regulation mode) + */ + GF_Err (*initialize)(GF_Filter *filter); + + /*! optional - callback for filter desctruction - private stack of filter is freed by framework + \param filter the target filter + */ + void (*finalize)(GF_Filter *filter); + + /*! optional - callback for arguments update. If GF_OK is returned, the filter private stack is updated accordingly. + If function is NULL, all updatable arguments will be changed in the filter private stack without the filter being notified. + If argument is a meta argument, it is the filter responsability to handle the update, as meta arguments do not live on the filter provate stack. + If the filter is a meta filter and argument is not declared in the argument list, the function is always called. + + \param filter the target filter + \param arg_name the name of the argument being set + \param new_val the value of the argument being set + \return error if any, GF_NOT_FOUND to silently disable argument value change. + */ + GF_Err (*update_arg)(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *new_val); + + /*! optional - process a given event. Retruns TRUE if the event has to be canceled, FALSE otherwise + If a downstream (towards source) event is not canceled, it will be forwarded to each input PID of the filter. + If you need to forward the event only to one input pid, send a copy of the event to the desired input and cancel the event. + \param filter the target filter + \param evt the event to process + \return GF_TRUE if the event should be canceled, GF_FALSE otherwise + */ + Bool (*process_event)(GF_Filter *filter, const GF_FilterEvent *evt); + + /*! optional - Called whenever an output PID needs format renegotiaition. If not set, a filter chain will be loaded to solve the negotiation + + \param filter the target filter + \param PID the filter output PID being reconfigured + \return error code if any + */ + GF_Err (*reconfigure_output)(GF_Filter *filter, GF_FilterPid *PID); + + /*! optional, mostly used for source filters and destination filters - probe the given URL, returning a score. + This function is called before opening the source (no data received yet) + + \param url the url of the source to probe, never NULL. + \param mime the MIME type of the source to probe. Can be NULL if mime not available at probe type + \return probe score + */ + GF_FilterProbeScore (*probe_url)(const char *url, const char *mime); + + /*! optional, usually set by demuxers. This function probes the mime type of a data chunk, usually located at the start of the file. + This function is called once the source is open, but is never called on an instanciated filter. The returned mime type (if any) is then used instead of the file extension + for solving filter graph. + Note: demux filters should always exposed 2 input caps bundle, one for specifiying input cap by file extension and one for specifying input cap by mime type. + \param data data to probe + \param size size of the data to probe + \param score set to the probe score. Initially set to \ref GF_FPROBE_NOT_SUPPORTED before calling the function. If you are certain of the data type, use \ref GF_FPROBE_SUPPORTED, if unsure use \ref GF_FPROBE_MAYBE_SUPPORTED. If the format cannot be probed (bad design), set it to \ref GF_FPROBE_EXT_MATCH + \return mime type of content, list of known extensions for the format if score is set to GF_FPROBE_EXT_MATCH, or NULL if not detected. + */ + const char * (*probe_data)(const u8 *data, u32 size, GF_FilterProbeScore *score); + + /*! for filters having the same match of input capabilities for a PID, the filter with priority at the lowest value will be used + scalable decoders should use high values, so that they are only selected when enhancement layers are present*/ + u8 priority; + + /*! optional for dynamic filter registries. Dynamic registries may declare any number of registries. The register_free function will be called to cleanup any allocated memory + + \param session the filter session + \param freg the filter register to destroy + */ + void (*register_free)(GF_FilterSession *session, struct __gf_filter_register *freg); + /*! user data of register loader, not inspected/modified by filter session*/ + void *udta; + + /*! optional. Used by sink filters offering multiple sink support + GPAC instantiates filter chains based on filter capabilities and arguments of source and destination. When a sink/destination filter can handle + multiple file inputs of various formats/types, temporary filters called alias are needed to instantiate the proper chain. + If the return value is true, an alias filter of the same type as this filter will be created with the proper arguments and initialized (in order to modify the + alias filter capabilities if needed), and finalized when no longer needed. All other callbacks will however be made on the main filter, not the alias. + + A good example is HTTP output of a DASH session: + - the first PID will be a FILE stream for the manifest (MPD, M3U8), and the http output capabilities will be set to accept the manifest file extension.mime + - the other PIDs will be media PIDs (mp4, ts, mkv ...) and won't match the filter instance, creating a new instance which we don't want + + By creating filter alias, a temprary HTTP output (alias) will be created, holding the arguments for the chain (including destination type). Overloading the alias filter capabilities + with the desired file extension or MIME will trigger the proper chain resolution. The alias will be switched to the final filter upon connecting the PIDs + + To access original arguments of the chain, see \ref gf_filter_pid_get_alias_udta + To check if an initialize callback targets the main filter or the temporary one, see \ref gf_filter_is_alias + \param filter the target filter + \param url the url of the target destination, never NULL. + \param mime the MIME type of the target destination. Can be NULL if mime not available + \return GF_TRUE if this filter instance should be used to handle the given URL, in which case an alias filter will be created + */ + Bool (*use_alias)(GF_Filter *filter, const char *url, const char *mime); + + + /*! version of the filter, usually only for external libs + Note: If this strings starts with "! " it indicates an error message at load time of the registry. This should only be set when \code gf_opts_get_bool("temp", "gendoc"); \endcode returns true, indicating the filter session is only loaded for documentation purposes (man/md generation and command line help). + */ + const char *version; +#ifndef GPAC_DISABLE_DOC + /*! short description of the filter. Conventions: + - should be small (used to generate UI / documentation) + - Should be Capitalized + - shall not use markdown + - shall be single line + */ + const char *description; + /*! author of the filter. Conventions: + - shall not use markdown + - first line if present is author name and should be normally capitalized + - second line if present is comma-separated list of contact info (eg http://foo.bar,mailto:foo@bar.com) + */ + const char *author; + /*! help of the filter. Conventions: + - describe any non-obvious behaviour of the filter here + - markdown is allowed. Use '`' for code, "__" for italic, "**" for bold and "~~" for strikethrough. + - mardown top-level sections shall use '#', second level '##', and so on + - mardown bullet lists shall use "- ", " - " etc... + - notes shall be identifed as a line starting with "Note: " + - warnings shall be identifed as a line starting with "Warning: " + - the sequence "[-" is reserved for option links. It is formated as: + - "[-OPT]()": link to self page for option OPT + - "[-OPT](LINK)": link to other page for option OPT. + LINK can be: + - GPAC: resolves to general GPAC help + - CORE: resolves to general CORE help + - LOG: resolves to general LOG help + - ANY: resolves to filter with register name 'ANY' + + Note that [text](ANY) with ANY a filter register name will link to that filter help page + */ + const char *help; +#endif +}; + + +/*! Gets filter private stack - the stack is allocated and freed by the filter session, as are any arguments. The rest is up to the filter to delete it +\param filter target filter +\return filter private stack if any, NULL otherwise +*/ +void *gf_filter_get_udta(GF_Filter *filter); + +/*! Sets filter name - mostly used for logging purposes +\param filter target filter +\param name new name to assign. If NULL, name is reset to register name +*/ +void gf_filter_set_name(GF_Filter *filter, const char *name); + +/*! Gets filter name +\param filter target filter +\return name of the filter +*/ +const char *gf_filter_get_name(GF_Filter *filter); + +/*! Makes the filter sticky. A sticky filter is not removed when all its input PIDs are disconnected. Typically used by the player +\param filter target filter +*/ +void gf_filter_make_sticky(GF_Filter *filter); + +/*! Return the number of queued events on the filter. Events are not aggregated, some filter may want to wait until all events are processed before taking actions. The function recursively goes up the filter chain and count queued events. +\param filter target filter +\return number of queued events +*/ +u32 gf_filter_get_num_events_queued(GF_Filter *filter); + +/*! Returns the single instance of GPAC download manager. DO NOT DESTROY IT!! +\param filter target filter +\return the GPAC download manager +*/ +GF_DownloadManager *gf_filter_get_download_manager(GF_Filter *filter); + +/*! Returns the single instance of GPAC font manager. DO NOT DESTROY IT!! +\param filter target filter +\return the GPAC font manager +*/ +struct _gf_ft_mgr *gf_filter_get_font_manager(GF_Filter *filter); + +/*! Asks task reschedule for a given delay. There is no guarantee that the task will be recalled at exactly the desired delay +\param filter target filter +\param us_until_next number of microseconds to wait before recalling this task +*/ +void gf_filter_ask_rt_reschedule(GF_Filter *filter, u32 us_until_next); + +/*! Posts a filter process task to the parent session. This is needed for some filters not having any input packets to process but still needing to work +such as decoder flushes, servers, etc... The filter session will ignore this call if the filter is already scheduled for processing +\param filter target filter +*/ +void gf_filter_post_process_task(GF_Filter *filter); + +/*! Posts a task to the session scheduler. This task is not a process task but the filter will still be called by a single thread at any time (ie, might delay process) +\param filter target filter +\param task_execute the callback function for the task. The callback can return GF_TRUE to reschedule the task, in which case the task will be rescheduled +immediately or after reschedule_ms. +\param udta user data passed back to the task function +\param task_name name of the task for logging purposes +\return error code if failure +*/ +GF_Err gf_filter_post_task(GF_Filter *filter, Bool (*task_execute) (GF_Filter *filter, void *callback, u32 *reschedule_ms), void *udta, const char *task_name); + + +/*! Sets callback function on source filter setup failure +\param filter target filter +\param source_filter the source filter to monitor +\param on_setup_error callback function to call upon source setup error +\param udta user data passed back to the task function +*/ +void gf_filter_set_setup_failure_callback(GF_Filter *filter, GF_Filter *source_filter, void (*on_setup_error)(GF_Filter *f, void *on_setup_error_udta, GF_Err e), void *udta); + +/*! Notify a filter setup error. This is typically called when a source filter or a filter having accepted input PIDs detects an issue. +For a source filter (no input PID), the failure callback will be called if any, and the filter will be removed. +For a connected filter, all input PIDs od the filter will be disconnected and the filter removed. +\param filter target filter +\param reason the failure reason code +*/ +void gf_filter_setup_failure(GF_Filter *filter, GF_Err reason); + +/*! Notify a filter fatal error but not a connection error. Typically, a 404 in HTTP. +\param filter target filter +\param reason the failure reason code +\param force_disconnect indicates if the filter chain should be disconnected or not +*/ +void gf_filter_notification_failure(GF_Filter *filter, GF_Err reason, Bool force_disconnect); + +/*! Disconnects a source filter chain between two filters +\param filter the calling filter. This filter is NOT disconnected +\param src_filter the source filter point of the chain to disconnect. +*/ +void gf_filter_remove_src(GF_Filter *filter, GF_Filter *src_filter); + +/*! Disconnects a filter from fthe chain +\param filter the filter to remove +*/ +void gf_filter_remove(GF_Filter *filter); + +/*! Sets the number of additional input PID a filter can accept. This overrides the default value of the filter register +\param filter the target filter +\param max_extra_pids the number of additional PIDs this filter can accept +*/ +void gf_filter_set_max_extra_input_pids(GF_Filter *filter, u32 max_extra_pids); + +/*! Gets the number of additional input PID a filter can accept. This overrides the default value of the filter register +\param filter the target filter +\return max_extra_pids the number of additional PIDs this filter can accept +*/ +u32 gf_filter_get_max_extra_input_pids(GF_Filter *filter); + + +/*! Queries if blocking mode is enabled for the filter +\param filter the target filter +\return GF_TRUE if blocking mode is enabled, GF_FALSE otherwise +*/ +Bool gf_filter_block_enabled(GF_Filter *filter); + +/*! Indicates the EOS status on input PIDs of this filter shall not be checked when probing for end of stream in the chain +\param filter the target filter +\param do_block if GF_TRUE, prevents EOS checking on input stream, otherwise enables it (default is FALSE upon creation) +*/ +void gf_filter_block_eos(GF_Filter *filter, Bool do_block); + + +/*! Connects a source to this filter. +Note: +Any filter loaded between the source and the calling filter will not use argument inheritance from the caller. + +\param filter the target filter +\param url url of source to connect to, with optional arguments. +\param parent_url url of parent if any +\param inherit_args if GF_TRUE, the source to connect will inherit arguments of the target filter's destination +\param err return code - can be NULL +\return the new source filter instance or NULL if error +*/ +GF_Filter *gf_filter_connect_source(GF_Filter *filter, const char *url, const char *parent_url, Bool inherit_args, GF_Err *err); + +/*! Connects a destination to this filter +\param filter the target filter +\param url url of destination to connect to, with optional arguments. +\param err return code - can be NULL +\return the new destination filter instance or NULL if error +*/ +GF_Filter *gf_filter_connect_destination(GF_Filter *filter, const char *url, GF_Err *err); + + +/*! Loads a new filter in the session - see \ref gf_fs_load_filter +\param filter the target filter +\param name name and arguments of the filter register to instantiate. +\param err_code error code if any +\return created filter or NULL if filter register cannot be found +*/ +GF_Filter *gf_filter_load_filter(GF_Filter *filter, const char *name, GF_Err *err_code); + +/*! Checks if a source filter can handle the given URL. The source filter is not loaded. + +\param filter the target filter +\param url url of source to connect to, with optional arguments. +\param parent_url url of parent if any +\return GF_TRUE if a source filter can be found for this URL, GF_FALSE otherwise +*/ +Bool gf_filter_is_supported_source(GF_Filter *filter, const char *url, const char *parent_url); + +/*! Gets the number of input PIDs connected to a filter +\param filter the target filter +\return number of input PIDs +*/ +u32 gf_filter_get_ipid_count(GF_Filter *filter); + +/*! Gets an input PIDs connected to a filter +\param filter the target filter +\param idx index of the input PID to retrieve, between 0 and \ref gf_filter_get_ipid_count +\return the input PID, or NULL if not found +*/ +GF_FilterPid *gf_filter_get_ipid(GF_Filter *filter, u32 idx); + +/*! Gets the number of output PIDs connected to a filter +\param filter the target filter +\return number of output PIDs +*/ +u32 gf_filter_get_opid_count(GF_Filter *filter); + +/*! Gets an output PIDs connected to a filter +\param filter the target filter +\param idx index of the output PID to retrieve, between 0 and \ref gf_filter_get_opid_count +\return the output PID, or NULL if not found +*/ +GF_FilterPid *gf_filter_get_opid(GF_Filter *filter, u32 idx); + +/*! Queries buffer max limits on a filter. This is the max of buffer limits on all its connected outputs +\param filter the target filter +\param max_buf will be set to the maximum buffer duration in microseconds - may be NULL +\param max_playout_buf will be set to the maximum playout buffer (the one triggering play) duration in microseconds - may be NULL +*/ +void gf_filter_get_output_buffer_max(GF_Filter *filter, u32 *max_buf, u32 *max_playout_buf); + +/*! Sets a clock state at session level indicating the time / timestamp of the last rendered frame. This is used by basic audio output +\param filter the target filter +\param time_in_us the system time in us, see \ref gf_sys_clock_high_res +\param media_timestamp the media timestamp associated with this time. +*/ +void gf_filter_hint_single_clock(GF_Filter *filter, u64 time_in_us, GF_Fraction64 media_timestamp); + +/*! Retrieves the clock state at session level, as set by \ref gf_filter_hint_single_clock +\param filter the target filter +\param time_in_us will be set to the system time in us, see \ref gf_sys_clock_high_res - may be NULL +\param media_timestamp will be set to the media timestamp associated with this time - may be NULL. +*/ +void gf_filter_get_clock_hint(GF_Filter *filter, u64 *time_in_us, GF_Fraction64 *media_timestamp); + +/*! Explicitly assigns a source ID to a filter. This shall be called before connecting the link_from filter +If no ID is assigned to the linked filter, a dynamic one in the form of _%08X_ (using the filter mem address) will be used +\param filter the target filter +\param link_from the filter to link from +\param link_ext any link extensions allowed in link syntax: +\c \#PIDNAME: accepts only PID(s) with name PIDNAME +\c \#TYPE: accepts only PIDs of matching media type. TYPE can be 'audio' 'video' 'scene' 'text' 'font' +\c \#TYPEN: accepts only Nth PID of matching type from source +\c \#P4CC=VAL: accepts only PIDs with property matching VAL. +\c \#PName=VAL: same as above, using the built-in name corresponding to the property. +\c \#P4CC-VAL: accepts only PIDs with property strictly less than VAL (only for 1-dimension number properties). +\c \#P4CC+VAL: accepts only PIDs with property strictly greater than VAL (only for 1-dimension number properties). +\return error code if any +*/ +GF_Err gf_filter_set_source(GF_Filter *filter, GF_Filter *link_from, const char *link_ext); + +/*! Explicitly reset sourceID of a filter. This shall be called before connecting the filter (eg creating PIDs). + + This is mostly used to reset a source ID of a filter created from a destination (e.g., dasher creating muxers from the MPD URL) where the destination arguments could have sourceIDs specified/ + +\param filter the target filter +*/ +void gf_filter_reset_source(GF_Filter *filter); + +/*! Explicitly assigns an ID to a filter. This shall be called before running the session, and cannot be called on a filter with ID assign +\param filter the target filter +\param filter_id the ID to assign. If NULL, a dynmic ID is generated +\return error code if any +*/ +GF_Err gf_filter_assign_id(GF_Filter *filter, const char *filter_id); + +/*! Gets the ID of a filter +\param filter the target filter +\return ID of the filter, NULL if not defined +*/ +const char *gf_filter_get_id(GF_Filter *filter); + + +/*! Overrides the filter register caps with new caps for this instance. Typically used when an option of the filter changes the capabilities +\param filter the target filter +\param caps the new set of capabilities to use for the filter. These are NOT copied and shall be valid for the lifetime of the filter +\param nb_caps number of capabilities set +\return error code if any +*/ +GF_Err gf_filter_override_caps(GF_Filter *filter, const GF_FilterCapability *caps, u32 nb_caps ); + +/*! Filter session separator set query*/ +typedef enum +{ + /*! queries the character code used to separate between filter arguments*/ + GF_FS_SEP_ARGS=0, + /*! queries the character code used to separate between argument name and value*/ + GF_FS_SEP_NAME, + /*! queries the character code used to indicate fragment identifiers (source PIDs, PID properties)*/ + GF_FS_SEP_FRAG, + /*! queries the character code used to separate items in a list*/ + GF_FS_SEP_LIST, + /*! queries the character code used to negate values*/ + GF_FS_SEP_NEG, +} GF_FilterSessionSepType; + +/*! Queries the character code used as a given separator type in argument names. Used for formating arguments when loading sources and destinations from inside a filter +\param filter the target filter +\param sep_type the separator type to query +\return character code of the separator +*/ +u8 gf_filter_get_sep(GF_Filter *filter, GF_FilterSessionSepType sep_type); + +/*! Queries the arguments of the destination arguments. The first output PID connected to a filter with non NULL args will be used (this is a recursive check until end of chain) +\param filter the target filter +\return the argument string of the destination args +*/ +const char *gf_filter_get_dst_args(GF_Filter *filter); + +/*! Queries the destination name. The first output PID connected to a filter with non NULL args will be used (this is a recursive check until end of chain) +\param filter the target filter +\return the argument string of the destination (SHALL be freed by caller), NULL if none found +*/ +char *gf_filter_get_dst_name(GF_Filter *filter); + +/*! Sends an event on all input PIDs (downstream) or on all output PIDs (upstream) +\param filter the target filter +\param evt the event to send +\param upstream send the even upstream +*/ +void gf_filter_send_event(GF_Filter *filter, GF_FilterEvent *evt, Bool upstream); + +/*! Trigger reconnection of output PIDs of a filter. This is needed when inserting a filter in the chain while the session is running +\param filter the target filter +\return error if any +*/ +GF_Err gf_filter_reconnect_output(GF_Filter *filter); + + +/*! Indicates that the filter accept and can process events coming from outside the filter chain, typically used by application firing events. + The event is sent on the process_event function with no associated PID. +\param filter the target filter +\param enable_events if GF_TRUE, the filter is considered an event target +\return error if any +*/ +GF_Err gf_filter_set_event_target(GF_Filter *filter, Bool enable_events); + +/*! Looks for a built-in property value marked as informative on a filter on all PIDs (inputs and output) +This is a recursive call on both input and ouput chain. +There is no guarantee that a queried property will still be valid at the setter side upon returning the call, the setter could have +already reassigned it to NULL. To avoids random behaviour, the property returned is reference counted so that it is not +destroyed by the setter while the caller uses it. + +Properties retrieved shall be released using \ref gf_filter_release_property. Failure to do so will cause memory leaks in the program. + +If the propentry pointer references a non-null value, this value will be released using \ref gf_filter_release_property, +so make sure to initialize the pointer to a NULL value on the first call. +This avoid calling \ref gf_filter_release_property after each get_info in the calling code: + +\code{.c} +GF_PropertyEntry *pe=NULL; +const GF_PropertyValue *p; +p = gf_filter_get_info(filter, FOO, &pe); +if (p) { } +p = gf_filter_get_info_str(filter, "BAR", &pe); +if (p) { } +p = gf_filter_pid_get_info(PID, ABCD, &pe); +if (p) { } +p = gf_filter_pid_get_info_str(PID, "MyProp", &pe); +if (p) { } +gf_filter_release_property(pe); +\endcode + + + +\param filter the target filter +\param prop_4cc the code of the built-in property to fetch +\param propentry the property reference object for later release. SHALL not be NULL +\return the property if found NULL otherwise. +*/ +const GF_PropertyValue *gf_filter_get_info(GF_Filter *filter, u32 prop_4cc, GF_PropertyEntry **propentry); + +/*! Looks for a property value on a filter on all PIDs (inputs and output). +This is a recursive call on both input and ouput chain +Properties retrieved shall be released using \ref gf_filter_release_property. See \ref gf_filter_pid_get_info for more details. +\param filter the target filter +\param prop_name the name of the property to fetch +\param propentry the property reference object for later release. See \ref gf_filter_pid_get_info for more details. +\return the property if found NULL otherwise +*/ +const GF_PropertyValue *gf_filter_get_info_str(GF_Filter *filter, const char *prop_name, GF_PropertyEntry **propentry); + +/*! Release a property previously queried, only used for []_get_info_[] functions. +This is a recursive call on both input and ouput chain +\param propentry the property reference object to be released +*/ +void gf_filter_release_property(GF_PropertyEntry *propentry); + +/*! Sends a filter argument update +\param filter the target filter +\param target_filter_id if set, the target filter will be changed to the filter with this id if any. otherwise the target filter is the one specified +\param arg_name argument name +\param arg_val argument value +\param propagate_mask propagation flags - 0 means no propagation +*/ +void gf_filter_send_update(GF_Filter *filter, const char *target_filter_id, const char *arg_name, const char *arg_val, GF_EventPropagateType propagate_mask); + + + +/*! Filters may request listening on global events.\n +\warning This is not thread safe, the callback will be called directly upon event firing, your filter has to take care of this + +API of event filter is likely to change any time soon - used for backward compatibility with some old modules +*/ +typedef struct +{ + /*! user data of the listener */ + void *udta; + /*! callback called when an event should be filtered. + \param udta user data of the listener + \param evt the event to be processed + \param consumed_by_compositor indicates the event was already used by the compositor + \return GF_TRUE if the event is to be cancelled, GF_FALSE otherwise + */ + Bool (*on_event)(void *udta, GF_Event *evt, Bool consumed_by_compositor); +} GF_FSEventListener; + +/*! Adds an event listener to the session +\param filter filter object +\param el the event listener to add +\return the error code if any +*/ +GF_Err gf_filter_add_event_listener(GF_Filter *filter, GF_FSEventListener *el); + +/*! Removes an event listener from the session +\param filter filter object +\param el the event listener to add +\return the error code if any +*/ +GF_Err gf_filter_remove_event_listener(GF_Filter *filter, GF_FSEventListener *el); + +/*! Forwards an event to the filter session +\param filter filter object +\param evt the event forwarded +\param consumed if set, indicates the event was already consummed/processed before forwarding +\param skip_user if set, indicates the event should only be dispatched to event listeners. +Otherwise, if a user is assigned to the session, the event is forwarded to the user +\return the error code if any +*/ +Bool gf_filter_forward_gf_event(GF_Filter *filter, GF_Event *evt, Bool consumed, Bool skip_user); + +/*! Forwards an event to the filter session. This is a shortcut for gf_filter_forward_gf_event(session, evt, GF_FALSE, GF_FALSE); +\param filter filter object +\param evt the event forwarded +\return the error code if any +*/ +Bool gf_filter_send_gf_event(GF_Filter *filter, GF_Event *evt); + +/*! Checks if all sink filters in the session have seen end of stream. +\param filter filter object +\return GF_TRUE if all sinks are in end of stream, GF_FALSE otherwise +*/ +Bool gf_filter_all_sinks_done(GF_Filter *filter); + +/*! Gets a filter argument value as string for a given argument name.. +\param filter filter object +\param arg_name name of the filter argument +\param dump buffer in which any formating of argument value will take place +\return the string value of the argument, or NULL if argument is not found or is invalid +*/ +const char *gf_filter_get_arg_str(GF_Filter *filter, const char *arg_name, char dump[GF_PROP_DUMP_ARG_SIZE]); + +/*! Gets a filter argument value for a given argument name.. +\param filter filter object +\param arg_name name of the filter argument +\param prop filled with the arguments value, do NOT modify +\return GF_TURE if success, GF_FALSE otherwise (argument is not found or is invalid) +*/ +Bool gf_filter_get_arg(GF_Filter *filter, const char *arg_name, GF_PropertyValue *prop); + + +/*! Checks if a given mime type is supported as input in the parent session +\param filter querying filter +\param mime mime type to query +\return GF_TRUE if mime is supported +*/ +Bool gf_filter_is_supported_mime(GF_Filter *filter, const char *mime); + + +/*! Sends UI event from video output into the session. For now only direct callback to global event handler is used, event is not forwarded down the chain +\param filter triggering filter +\param uievt event to send +\return GF_TRUE if event has been cancelled, FALSE otherwise +*/ +Bool gf_filter_ui_event(GF_Filter *filter, GF_Event *uievt); + + +/*! Registers filter as an openGL provider. This is only used by sink filters creating a openGL context, to avoid creating another context. Filters registered as OpenGL providers will run on the main thread. +\param filter filter providing OpenGL context +\param do_register if TRUE the filter carries a valid gl context, if FALSE the filter no longer carries a valid GL context +*/ +void gf_filter_register_opengl_provider(GF_Filter *filter, Bool do_register); + +/*! Requests openGL support for this filter. If no OpenGL providers exist, a default provider will be created (GL context creation on hidden window). Filters requiring OpenGL will run on the main thread. + +\note Filters using openGL should not assume any persistent GL state among calls. Other filters using openGL might change the openGL context state (depth test, viewport, alpha blending, culling, etc...). + +\param filter filter providing OpenGL context +\return error code if any +*/ +GF_Err gf_filter_request_opengl(GF_Filter *filter); + +/*! Sets openGL context active for this filter. +\note There may be several openGL context created in the filter session, depending on activated filters. A filter using openGL must call this function before issuing any openGL calls + +\param filter filter asking for OpenGL context activation +\return error code if any +*/ +GF_Err gf_filter_set_active_opengl_context(GF_Filter *filter); + + +/*! Count the number of source filters for the given filter matching the given protocol type. +\param filter filter to inspect +\param protocol_scheme scheme of the protocol to test, without ://, eg "http", "rtp", "https" +\param expand_proto if set to GF_TRUE, any source protocol with a name begining like protocol_scheme will be matched. For example, use this with "http" to match all http and https schemes. +\param enum_pids user function to enumerate PIDs against which the source should be checked . If not set, all source filters will be checked. +\param udta user data for the callback function +\return the number of source filters matched by protocols +*/ +u32 gf_filter_count_source_by_protocol(GF_Filter *filter, const char *protocol_scheme, Bool expand_proto, GF_FilterPid * (*enum_pids)(void *udta, u32 *idx), void *udta); + +/*! Disables data probing on the given filter. Typically used by filters loading source filters. +\param filter target filter +*/ +void gf_filter_disable_probe(GF_Filter *filter); + +/*! Disables input connection on the filter. This is used by filters acting both as true sources or demux/processing filters depending on their options. +\param filter target filter +*/ +void gf_filter_disable_inputs(GF_Filter *filter); + +/*! Checks if some PIDs are still not connected in the graph originating at filter. This is typically used by filters dynamically loading source filters to make sure all PIDs from the source are connected. + +NOTE: this does not guarantee that no other PID remove or configure will happen later on, this depends on the source type and is unknown by GPAC's filter architecture. +\param filter target filter +\param stop_at_filter check connections until this filter. If NULL, connections are checked until upper (sink) end of graph +\return GF_TRUE if any filter in the path has pending PID connections +*/ +Bool gf_filter_has_pid_connection_pending(GF_Filter *filter, GF_Filter *stop_at_filter); + +/*! checks if the some PID connection tasks are still pending at the session level + +This can be needed by some filters needing to make sure all their inputs are known before starting producing packets. + +\param filter target filter +\return GF_TRUE if some connection tasks are pending, GF_FALSE otherwise +*/ +Bool gf_filter_connections_pending(GF_Filter *filter); + +/*! Disables blocking check for a given filter + +This can be needed by some filters internally managing their blocking state because one of their output is not managed by the filter session. + +\param filter target filter +\param prevent_blocking_enabled if GF_TRUE, filter will still be called even if one of its output is in blocking mode +\return error if any +*/ +GF_Err gf_filter_prevent_blocking(GF_Filter *filter, Bool prevent_blocking_enabled); + +/*! Checks if filter was loaded as part of a link resolution or explicitly loaded by application +\param filter target filter +\return GF_TRUE if filter was loaded for link resolution, GF_FALSE if filter was explicitly loaded by the application +*/ +Bool gf_filter_is_dynamic(GF_Filter *filter); + +/*! Checks if reporting is turned on at session level. +\param filter target filter +\return GF_TRUE if reporting is enabled, GF_FALSE otherwise +*/ +Bool gf_filter_reporting_enabled(GF_Filter *filter); + +/*! Updates filter status string and progress. Should not be called if reporting is turned off at session level. +This allows gathering stats from filter in realtime. The status string can be anything but shall not contain '\n' and +should not contain any source or destination URL except for sources and sinks. +\param filter target filter +\param percent percentage (from 0 to 10000) of operation status. If more than 10000 ignored +\param szStatus string giving a status of the filter +\return error code if any +*/ +GF_Err gf_filter_update_status(GF_Filter *filter, u32 percent, char *szStatus); + +/*! check if session has been scheduled for destruction +\param filter target filter +\return GF_TRUE if session is about to be destroyed +*/ +Bool gf_filter_end_of_session(GF_Filter *filter); + +/*! used by meta-filters (ffmpeg and co) to report an option was set but not used by the filter. This is needed since these filters might not +know the set of available options at initialize() time. +\param filter target filter +\param arg name of the argument not used/found +*/ +void gf_filter_report_unused_meta_option(GF_Filter *filter, const char *arg); + +/*! used by script to set a per-instance description +\param filter target filter +\param new_desc the new description to set +\return error if any +*/ +GF_Err gf_filter_set_description(GF_Filter *filter, const char *new_desc); + +/*! get a per-instance description +\param filter target filter +\return the filter instance description, NULL otherwise +*/ +const char *gf_filter_get_description(GF_Filter *filter); + +/*! used by script to set a per-instance version +\param filter target filter +\param new_version the new version to set +\return error if any +*/ +GF_Err gf_filter_set_version(GF_Filter *filter, const char *new_version); + +/*! get a per-instance version +\param filter target filter +\return the filter instance version, NULL otherwise +*/ +const char *gf_filter_get_version(GF_Filter *filter); + +/*! used by script to set a per-instance author +\param filter target filter +\param new_author the new author to set +\return error if any +*/ +GF_Err gf_filter_set_author(GF_Filter *filter, const char *new_author); + +/*! get a per-instance author +\param filter target filter +\return the filter instance author, NULL otherwise +*/ +const char *gf_filter_get_author(GF_Filter *filter); + +/*! used by script to set a per-instance help +\param filter target filter +\param new_help the new help to set +\return error if any +*/ +GF_Err gf_filter_set_help(GF_Filter *filter, const char *new_help); + +/*! get a per-instance help +\param filter target filter +\return the filter instance help, NULL otherwise +*/ +const char *gf_filter_get_help(GF_Filter *filter); + + +/*! used by script to set a per-instance arguments. The passed array shall have a 0 argument at the end and shall be valid +for the lifetime of the filter (not locally copied) +\param filter target filter +\param new_args the new args to set +\return error if any +*/ +GF_Err gf_filter_define_args(GF_Filter *filter, GF_FilterArgs *new_args); + +/*! get per-instance args +\param filter target filter +\return the filter instance args if any, NULL otherwise +*/ +GF_FilterArgs *gf_filter_get_args(GF_Filter *filter); + + +/*! probes mime type of a given block of data (should be begining of file ) +\param filter target filter +\param data buffer to probe +\param size size of buffer +\return the mime type probed, or NULL if not recognized +*/ +const char *gf_filter_probe_data(GF_Filter *filter, u8 *data, u32 size); + +/*! checks if the given filter is an alias filter created by a multiple sink filter +\param filter target filter +\return GF_TRUE if this is an alias filter created by a multiple sink filter, GF_FALSE otherwise +*/ +Bool gf_filter_is_alias(GF_Filter *filter); + +/*! checks if the given filter is in the chain ending up at parent +\param parent end of filter chain to check +\param filter target filter to check +\return GF_TRUE if filter is present in the parent chain, GF_FALSE otherwise +*/ +Bool gf_filter_in_parent_chain(GF_Filter *parent, GF_Filter *filter); + + +/*! Gets statistics for filter +\param filter filter session +\param stats statistics for filter +\return error code if any +*/ +GF_Err gf_filter_get_stats(GF_Filter *filter, GF_FilterStats *stats); + +/*! @} */ + + +/*! +\addtogroup fs_pid Filter PID +\ingroup filters_grp +\brief Filter Interconnection + + +A PID is a connection between two filters, holding packets to process. Internally, a PID created by a filter (output PID) is different from an input PID to a filter (configure_pid) +but the API has been designed to hide this, so that most PID functions can be called regardless of the input/output nature of the PID. + +All setters functions (gf_filter_pid_set*) will fail on an input PID. + +The generic design of the architecture is that each filter is free to decide how it handle PIDs and their packets. This implies that the filter session has no clue how +an output PID relates to an input PID (same goes for packets). +Developpers must therefore manually copy PID properties that seem relevant, or more practically copy all properties from input PID to output PID and +reassign output PID properties changed by the filter. + +PIDs can be reconfigured multiple times, even potentially changing caps on the fly. The current architecture does not check for capability matching during reconfigure, it is up to +the filter to do so. + +It is also up to filters to decide how to handle a an input PID removal: remove the output PID immediately, keep it open to flush internal data or keep generating data on the output. +The usual practice is to remove the output as soon as the input is removed. + +Once an input PID has been notified to be removed, it shall no longer be used by the filter, as it may be discarded/freed (PID are NOT reference counted). + +@{ + */ + + + +/*! Creates a new output PID for the filter. If the filter has a single input PID already connected, the PID properties are copied by default +\param filter the target filter +\return the new output PID +*/ +GF_FilterPid *gf_filter_pid_new(GF_Filter *filter); + +/*! Removes an output PID from the filter. This will trigger removal of the PID upward in the chain +\param PID the target filter PID to remove +*/ +void gf_filter_pid_remove(GF_FilterPid *PID); + +/*! Creates an output PID for a raw input filter (file, sockets, pipe, etc). This will assign file name, local name, mime and extension properties to the created PID +\param filter the target filter +\param url URL of the source, SHALL be set +\param local_file path on local host, can be NULL +\param mime_type mime type of the content. If none provided and propbe_data is not null, the data will be probed for mime type resolution +\param fext file extension of the content. If NULL, will be extracted from URL +\param probe_data data of the stream to probe in order to solve its mime type +\param probe_size size of the probe data +\param trust_mime if set and mime_type is set, disables data probing +\param out_pid the output PID to create or update. If no referer PID, a new PID will be created otherwise the PID will be updated +\return error code if any +*/ +GF_Err gf_filter_pid_raw_new(GF_Filter *filter, const char *url, const char *local_file, const char *mime_type, const char *fext, u8 *probe_data, u32 probe_size, Bool trust_mime, GF_FilterPid **out_pid); + +/*! Sets a new property on an output PID for built-in property names. +Previous properties (ones set before last packet dispatch) will still be valid. Property with same type/name will be reassigned +You need to remove them one by one using \ref gf_filter_pid_set_property with NULL property, or reset the properties with \ref gf_filter_pid_reset_properties. +Setting a new property will trigger a PID reconfigure at the consumption point of the next dispatched packet. + +Warning: changing a property before the final end of stream (i.e. if no more packets are sent) will have no effect. You must use \ref gf_filter_pid_set_info and \ref gf_filter_pid_get_info for this. + +\param PID the target filter PID +\param prop_4cc the built-in property code to modify +\param value the new value to assign, or NULL if the property is to be removed +\return error code if any +*/ +GF_Err gf_filter_pid_set_property(GF_FilterPid *PID, u32 prop_4cc, const GF_PropertyValue *value); + +/*! Sets a new property on an output PID - see \ref gf_filter_pid_set_property. +\param PID the target filter PID +\param name the name of the property to modify +\param value the new value to assign, or NULL if the property is to be removed +\return error code if any +*/ +GF_Err gf_filter_pid_set_property_str(GF_FilterPid *PID, const char *name, const GF_PropertyValue *value); + +/*! Sets a new property on an output PID - see \ref gf_filter_pid_set_property. +\param PID the target filter PID +\param name the name of the property to modify. The name will be copied to the property, and memory destruction performed by the filter session +\param value the new value to assign, or NULL if the property is to be removed +\return error code if any +*/ +GF_Err gf_filter_pid_set_property_dyn(GF_FilterPid *PID, char *name, const GF_PropertyValue *value); + +/*! Sets a new info property on an output PID for built-in property names. +Similar to \ref gf_filter_pid_set_property, but infos are not copied up the chain and to not trigger PID reconfiguration. +First packet dispatched after calling this function will be marked, and its fetching by the consuming filter will trigger a process_event notification. +If the consumming filter copies properties from source packet to output packet, the flag will be passed to such new output packet. +Note: any property type can be used for info, except \ref GF_PROP_POINTER. + +\param PID the target filter PID +\param prop_4cc the built-in property code to modify +\param value the new value to assign, or NULL if the property is to be removed +\return error code if any +*/ +GF_Err gf_filter_pid_set_info(GF_FilterPid *PID, u32 prop_4cc, const GF_PropertyValue *value); + +/*! Sets a new info property on an output PID - see \ref gf_filter_pid_set_info +See \ref gf_filter_pid_set_info +\param PID the target filter PID +\param name the name of the property to modify +\param value the new value to assign, or NULL if the property is to be removed +\return error code if any +*/ +GF_Err gf_filter_pid_set_info_str(GF_FilterPid *PID, const char *name, const GF_PropertyValue *value); + +/*! Sets a new property on an output PID. +See \ref gf_filter_pid_set_info +\param PID the target filter PID +\param name the name of the property to modify. The name will be copied to the property, and memory destruction performed by the filter session +\param value the new value to assign, or NULL if the property is to be removed +\return error code if any +*/ +GF_Err gf_filter_pid_set_info_dyn(GF_FilterPid *PID, char *name, const GF_PropertyValue *value); + +/*! Sets user data pointer for a PID - see \ref gf_filter_pid_set_info +\param PID the target filter PID +\param udta user data pointer +*/ +void gf_filter_pid_set_udta(GF_FilterPid *PID, void *udta); + +/*! Gets user data pointer for a PID +\param PID the target filter PID +\return udta user data pointer +*/ +void *gf_filter_pid_get_udta(GF_FilterPid *PID); + +/*! Gets PID name. Mostly used for logging purposes +\param PID the target filter PID +\param name the new PID name. function ignored if NULL. +*/ +void gf_filter_pid_set_name(GF_FilterPid *PID, const char *name); + +/*! Gets PID name +\param PID the target filter PID +\return PID name +*/ +const char *gf_filter_pid_get_name(GF_FilterPid *PID); + +/*! Gets filter name of input PID +\param PID the target filter PID +\return name of the filter owning this input PID +*/ +const char *gf_filter_pid_get_filter_name(GF_FilterPid *PID); + +/*! Gets the source arguments of the PID, walking down the chain until the source filter +\param PID the target filter PID +\return argument of the source filter +*/ +const char *gf_filter_pid_orig_src_args(GF_FilterPid *PID); + +/*! Gets the source filter name or class name for the PID, walking down the chain until the source filter (ony the first input PID of each filter is used). +\param PID the target filter PID +\return argument of the source filter +*/ +const char *gf_filter_pid_get_source_filter_name(GF_FilterPid *PID); + +/*! Gets the arguments for the filter +\param PID the target filter PID +\return arguments of the source filter +*/ +const char *gf_filter_pid_get_args(GF_FilterPid *PID); + +/*! Sets max buffer requirement of an output PID. Typically used by audio to make sure several packets can be dispatched on a PID +that would otherwise block after one packet +\param PID the target filter PID +\param total_duration_us buffer max occupancy in us +*/ +void gf_filter_pid_set_max_buffer(GF_FilterPid *PID, u32 total_duration_us); + +/*! Returns max buffer requirement of a PID. +\param PID the target filter PID +\return buffer max in us +*/ +u32 gf_filter_pid_get_max_buffer(GF_FilterPid *PID); + +/*! Checks if a given filter is in the PID parent chain. This is used to identify sources (rather than checking URL/...) +\param PID the target filter PID +\param filter the source filter to check +\return GF_TRUE if filter is a source for that PID, GF_FALSE otherwise +*/ +Bool gf_filter_pid_is_filter_in_parents(GF_FilterPid *PID, GF_Filter *filter); + +/*! Gets current buffer levels of the PID +\param PID the target filter PID +\param max_units maximum number of packets allowed - can be 0 if buffer is measured in time +\param nb_pck number of packets in PID +\param max_duration maximum buffer duration allowed in us - can be 0 if buffer is measured in units +\param duration buffer duration in us +\return GF_TRUE if normal buffer query, GF_FALSE if final session flush, in which case buffer might never complete +*/ +Bool gf_filter_pid_get_buffer_occupancy(GF_FilterPid *PID, u32 *max_units, u32 *nb_pck, u32 *max_duration, u32 *duration); + +/*! Sets loose connect for a PID, avoiding to throw an error if connection of the PID fails. Used by the compositor filter which acts as both sink and filter. +\param PID the target filter PID +*/ +void gf_filter_pid_set_loose_connect(GF_FilterPid *PID); + + +/*! Negotiate a given property on an input PID for built-in properties +Filters may accept some PID connection but may need an adaptaion chain to be able to process packets, eg change pixel format or sample rate +This function will trigger a reconfiguration of the filter chain to try to adapt this. If failing, the filter chain will disconnect +This process is asynchronous, the filter asking for a PID negociation will see the notification through a pid_reconfigure if success. +\param PID the target filter PID - this MUST be an input PID +\param prop_4cc the built-in property code to negotiate +\param value the new value to negotiate, SHALL NOT be NULL +\return error code if any +*/ +GF_Err gf_filter_pid_negociate_property(GF_FilterPid *PID, u32 prop_4cc, const GF_PropertyValue *value); + +/*! Negotiate a given property on an input PID for regular properties +see \ref gf_filter_pid_negociate_property +\param PID the target filter PID - this MUST be an input PID +\param name name of the property to negotiate +\param value the new value to negotiate, SHALL NOT be NULL +\return error code if any +*/ +GF_Err gf_filter_pid_negociate_property_str(GF_FilterPid *PID, const char *name, const GF_PropertyValue *value); + +/*! Negotiate a given property on an input PID for regular properties +see \ref gf_filter_pid_negociate_property +\param PID the target filter PID - this MUST be an input PID +\param name the name of the property to modify. The name will be copied to the property, and memory destruction performed by the filter session +\param value the new value to negotiate, SHALL NOT be NULL +\return error code if any +*/ +GF_Err gf_filter_pid_negociate_property_dyn(GF_FilterPid *PID, char *name, const GF_PropertyValue *value); + +/*! Queries a negotiated built-in capability on an output PID +Filters may check if a property negotiation was done on an output PID, and check the property value. +This can be done on an output PID in a filter->reconfigure_output if the filter accpets caps negotiation +This can be done on an input PID in a generic reconfigure_pid + +\param PID the target filter PID +\param prop_4cc the built-in property code to negotiate +\return the negociated property value +*/ +const GF_PropertyValue *gf_filter_pid_caps_query(GF_FilterPid *PID, u32 prop_4cc); + +/*! Queries a negotiated capability on an output PID - see \ref gf_filter_pid_caps_query +\param PID the target filter PID +\param prop_name the property name to negotiate +\return the negociated property value +*/ +const GF_PropertyValue *gf_filter_pid_caps_query_str(GF_FilterPid *PID, const char *prop_name); + +/*! Statistics for PID*/ +typedef struct +{ + /*! if set, indicates the PID is disconnected and stats are not valid*/ + u32 disconnected; + /*! average process rate on that PID in bits per seconds*/ + u32 average_process_rate; + /*! max process rate on that PID in bits per seconds*/ + u32 max_process_rate; + /*! average bitrate for that PID*/ + u32 avgerage_bitrate; + /*! max bitrate for that PID*/ + u32 max_bitrate; + /*! number of packets processed on that PID*/ + u32 nb_processed; + /*! max packet process time of the filter in us*/ + u32 max_process_time; + /*! total process time of the filter in us*/ + u64 total_process_time; + /*! process time of first packet of the filter in us*/ + u64 first_process_time; + /*! process time of the last packet on that PID in us*/ + u64 last_process_time; + /*! minimum frame duration on that PID in us*/ + u32 min_frame_dur; + /*! number of saps 1/2/3 on that PID*/ + u32 nb_saps; + /*! max process time of SAP packets on that PID in us*/ + u32 max_sap_process_time; + /*! total process time of SAP packets on that PID in us*/ + u64 total_sap_process_time; + + /*! max buffer time in us - only set when querying decoder stats*/ + u64 max_buffer_time; + /*! max playout buffer time in us - only set when querying decoder stats*/ + u64 max_playout_time; + /*! min playout buffer time in us - only set when querying decoder stats*/ + u64 min_playout_time; + /*! current buffer time in us - only set when querying decoder stats*/ + u64 buffer_time; + /*! number of units in input buffer of the filter - only set when querying decoder stats*/ + u32 nb_buffer_units; +} GF_FilterPidStatistics; + +/*! Direction for stats querying*/ +typedef enum +{ + /*! statistics are fetched on the current PID's parent filter. If the PID is an output PID, the statistics are fetched on all the destinations for that PID*/ + GF_STATS_LOCAL = 0, + /*! statistics are fetched on the current PID's parent filter. The statistics are fetched on all input of the parent filter*/ + GF_STATS_LOCAL_INPUTS, + /*! statistics are fetched on all inputs of the next decoder filter up the chain (towards the sink)*/ + GF_STATS_DECODER_SINK, + /*! statistics are fetched on all inputs of the previous decoder filter down the chain (towards the source)*/ + GF_STATS_DECODER_SOURCE, + /*! statistics are fetched on all inputs of the next encoder filter up the chain (towards the sink)*/ + GF_STATS_ENCODER_SINK, + /*! statistics are fetched on all inputs of the previous encoder filter down the chain (towards the source)*/ + GF_STATS_ENCODER_SOURCE +} GF_FilterPidStatsLocation; + +/*! Gets statistics for the PID +\param PID the target filter PID +\param stats the retrieved statistics +\param location indicates where to locate the filter to query stats on it. +\return error code if any +*/ +GF_Err gf_filter_pid_get_statistics(GF_FilterPid *PID, GF_FilterPidStatistics *stats, GF_FilterPidStatsLocation location); + +/*! Resets current properties of the PID +\param PID the target filter PID +\return error code if any +*/ +GF_Err gf_filter_pid_reset_properties(GF_FilterPid *PID); + + +/*! Function protoype for filtering properties. +\param cbk callback data +\param prop_4cc the built-in property code +\param prop_name property name +\param src_prop the property value in the source packet +\return GF_TRUE if the property shall be merged, GF_FALSE otherwise +*/ +typedef Bool (*gf_filter_prop_filter)(void *cbk, u32 prop_4cc, const char *prop_name, const GF_PropertyValue *src_prop); + +/*! Push a new set of properties on destination PID using all properties from source PID. Old properties in destination will be lost (i.e. reset properties is always performed during copy properties) +\param dst_pid the destination filter PID +\param src_pid the source filter PID +\return error code if any +*/ +GF_Err gf_filter_pid_copy_properties(GF_FilterPid *dst_pid, GF_FilterPid *src_pid); + +/*! Push a new set of properties on destination PID, using all properties from source PID, potentially filtering them. Currently defined properties are not reseted. +\param dst_pid the destination filter PID +\param src_pid the source filter PID +\param filter_prop callback filtering function +\param cbk callback data passed to the callback function +\return error code if any +*/ +GF_Err gf_filter_pid_merge_properties(GF_FilterPid *dst_pid, GF_FilterPid *src_pid, gf_filter_prop_filter filter_prop, void *cbk ); + +/*! Gets a built-in property of the PID +Warning: properties are only valid until the next configure_pid is called. Attempting to use a property +value (either the pointer or one of the value) queried before the current configure_pid will result in + unpredictable behaviour, potentially crashes. +\param PID the target filter PID +\param prop_4cc the code of the built-in property to retrieve +\return the property if found or NULL otherwise +*/ +const GF_PropertyValue *gf_filter_pid_get_property(GF_FilterPid *PID, u32 prop_4cc); + +/*! Gets a property of the PID - see \ref gf_filter_pid_get_property +\param PID the target filter PID +\param prop_name the name of the property to retrieve +\return the property if found or NULL otherwise +*/ +const GF_PropertyValue *gf_filter_pid_get_property_str(GF_FilterPid *PID, const char *prop_name); + +/*! Enumerates properties of a PID - see \ref gf_filter_pid_get_property +\param PID the target filter PID +\param idx input/output index of the current property. 0 means first. Incremented by 1 upon success +\param prop_4cc set to the built-in code of the property if built-in +\param prop_name set to the name of the property if not built-in +\return the property if found or NULL otherwise (end of enumeration) +*/ +const GF_PropertyValue *gf_filter_pid_enum_properties(GF_FilterPid *PID, u32 *idx, u32 *prop_4cc, const char **prop_name); + +/*! Enumerates info of a PID +\param PID the target filter PID +\param idx input/output index of the current info. 0 means first. Incremented by 1 upon success +\param prop_4cc set to the built-in code of the info if built-in +\param prop_name set to the name of the info if not built-in +\return the property if found or NULL otherwise (end of enumeration) +*/ +const GF_PropertyValue *gf_filter_pid_enum_info(GF_FilterPid *PID, u32 *idx, u32 *prop_4cc, const char **prop_name); + +/*! Sets PID framing mode. filters can consume packets as they arrive, or may want to only process full frames/files +\param PID the target filter PID +\param requires_full_blocks if GF_TRUE, the packets on the PID will be reaggregated to form complete frame/files. +\return error code if any +*/ +GF_Err gf_filter_pid_set_framing_mode(GF_FilterPid *PID, Bool requires_full_blocks); + +/*! Gets cumulated buffer duration of PID (recursive until source) +\param PID the target filter PID +\param check_pid_full if GF_TRUE, returns 0 if the PID buffer is not yet full +\return the duration in us, or -1 if session is in final flush +*/ +u64 gf_filter_pid_query_buffer_duration(GF_FilterPid *PID, Bool check_pid_full); + +/*! Try to force a synchronous flush of the filter chain downwards this PID. If refetching a packet returns NULL, this failed. +\param PID the target filter PID +*/ +void gf_filter_pid_try_pull(GF_FilterPid *PID); + + +/*! Looks for a built-in property value on a PIDs. This is a recursive call on input chain +Info query is NOT threadsafe in gpac, you +Properties retrieved shall be released using \ref gf_filter_release_property. See \ref gf_filter_pid_get_info for more details. +\param PID the target filter PID to query +\param prop_4cc the code of the built-in property to fetch +\param propentry the property reference object for later release. See \ref gf_filter_pid_get_info for more details. +\return the property if found NULL otherwise +*/ +const GF_PropertyValue *gf_filter_pid_get_info(GF_FilterPid *PID, u32 prop_4cc, GF_PropertyEntry **propentry); + +/*! Looks for a property value on a PIDs. This is a recursive call on both input and ouput chain +Properties retrieved shall be released using \ref gf_filter_release_property. See \ref gf_filter_pid_get_info for more details. +\param PID the target filter PID to query +\param prop_name the name of the property to fetch +\param propentry the property reference object for later release. See \ref gf_filter_pid_get_info for more details. +\return the property if found NULL otherwise +*/ +const GF_PropertyValue *gf_filter_pid_get_info_str(GF_FilterPid *PID, const char *prop_name, GF_PropertyEntry **propentry); + + +/*! Signals end of stream on a PID. Each filter needs to call this when EOS is reached on a given stream since there is no explicit link between input PIDs and output PIDs +\param PID the target filter PID +*/ +void gf_filter_pid_set_eos(GF_FilterPid *PID); + +/*! Checks for end of stream has been signaled a PID input chain. +This is a recursive call on input chain. The function is typically used to abort buffering or synchronisation init in muxers. +\param PID the target filter PID +\return GF_TRUE if end of stream was signaled on the input chain +*/ +Bool gf_filter_pid_has_seen_eos(GF_FilterPid *PID); + +/*! Checks for end of stream signaling on a PID. +\param PID the target filter PID +\return GF_TRUE if end of stream was signaled on that PID +*/ +Bool gf_filter_pid_is_eos(GF_FilterPid *PID); + +/*! Checks if there is a packet ready on an input PID. +\param PID the target filter PID +\return GF_TRUE if no packet in buffers +*/ +Bool gf_filter_pid_first_packet_is_empty(GF_FilterPid *PID); + +/*! Gets the first packet in the input PID buffer. +This may trigger a reconfigure signal on the filter. If reconfigure is not OK, returns NULL and the PID passed to the filter NO LONGER EXISTS (implicit remove) +The packet is still present in the PID buffer until explicitly removed by \ref gf_filter_pid_drop_packet +\param PID the target filter PID +\return packet or NULL of empty or reconfigure error +*/ +GF_FilterPacket * gf_filter_pid_get_packet(GF_FilterPid *PID); + +/*! Fetches the CTS of the first packet in the input PID buffer. +\param PID the target filter PID +\param cts set to the composition time of the first packet, in PID timescale +\return GF_TRUE if cts was fetched, GF_FALSE otherwise +*/ +Bool gf_filter_pid_get_first_packet_cts(GF_FilterPid *PID, u64 *cts); + +/*! Drops the first packet in the input PID buffer. +\param PID the target filter PID +*/ +void gf_filter_pid_drop_packet(GF_FilterPid *PID); + +/*! Gets the number of packets in input PID buffer. +\param PID the target filter PID +\return the number of packets +*/ +u32 gf_filter_pid_get_packet_count(GF_FilterPid *PID); + +/*! Checks the capability of the input PID match its destination filter. +\param PID the target filter PID +\return GF_TRUE if match , GF_FALSE otherwise +*/ +Bool gf_filter_pid_check_caps(GF_FilterPid *PID); + +/*! Checks if the PID would enter a blocking state if a new packet is sent. +This function should be called by eg demuxers to regulate the rate at which they send packets + +Note: PIDs are never fully blocking in GPAC, a filter requesting an output packet should usually get one unless something goes wrong +\param PID the target filter PID +\return GF_TRUE if PID would enter blocking state , GF_FALSE otherwise +*/ +Bool gf_filter_pid_would_block(GF_FilterPid *PID); + +/*! Shortcut to access the timescale of the PID - faster than get property as the timescale is locally cached for buffer management +\param PID the target filter PID +\return the PID timescale +*/ +u32 gf_filter_pid_get_timescale(GF_FilterPid *PID); + +/*! Clears the end of stream flag on a PID. +Note: the end of stream is automatically cleared when a new packet is dispatched; This function is used to clear it asap, before next packet dispacth (period switch in dash for example). +\param PID the target filter PID +\param all_pids if sets, clear end oof stream for all PIDs coming from the same filter as the target PID +*/ +void gf_filter_pid_clear_eos(GF_FilterPid *PID, Bool all_pids); + +/*! Indicates how clock references (PCR of MPEG-2) should be handled. +By default these references are passed from input packets to output packets by the filter session (this assumes the filter doesn't modify composition timestamps). +This default can be changed with this function. +\param PID the target filter PID +\param filter_in_charge if set to GF_TRUE, clock references are not forwarded by the filter session and the filter is in charge of handling them +*/ +void gf_filter_pid_set_clock_mode(GF_FilterPid *PID, Bool filter_in_charge); + +/*! Resolves file template using PID properties and file index. Templates follow the DASH mechanism: + +- $KEYWORD$ or $KEYWORD%0nd$ are replaced in the template with the resolved value, + +- $$ is an escape for $ + +Supported KEYWORD (case insensitive): +- num: replaced by file_number (usually matches GF_PROP_PCK_FILENUM, but this property is not used in the solving mechanism) +- PID: ID of the source PID +- URL: URL of source file +- File: path on disk for source file +- p4cc=ABCD: uses PID property with 4CC ABCD +- pname=VAL: uses PID property with name VAL (either built-in prop name or other peroperty name) + +\param PID the target filter PID +\param szTemplate source template to solve +\param szFinalName buffer for final name +\param file_number number of file to use +\param file_suffix if not null, will be appended after the value of the §File$ keyword if present +\return error if any +*/ +GF_Err gf_filter_pid_resolve_file_template(GF_FilterPid *PID, char szTemplate[GF_MAX_PATH], char szFinalName[GF_MAX_PATH], u32 file_number, const char *file_suffix); + + +/*! Sets discard mode on or off on an input PID. When discard is on, all input packets for this PID are no longer dispatched. + +This only affect the current PID, not the source filter(s) for that PID. + +PID reconfigurations are still forwarded to the filter, so that a filter may decide to re-enable regular mode. + +This is typically needed for filters that stop consuming data for a while (dash forced period duration for example) but may resume +consumption later on (stream moving from period 1 to period 2 for example). + +\param PID the target filter PID +\param discard_on enables/disables discard +\return error if any +*/ +GF_Err gf_filter_pid_set_discard(GF_FilterPid *PID, Bool discard_on); + +/*! Discard blocking mode for PID on end of stream. The filter is blocked when all output PIDs are in end of stream, this function unblocks the filter. +This can be needed for playlist type filters dispatching end of stream at the end of each file but setting up next file in +the following process() call. + +\param PID the target filter PID +*/ +void gf_filter_pid_discard_block(GF_FilterPid *PID); + +/*! Gets URL argument of first destination of PID if any - memory shall be freed by caller. +\param PID the target filter PID +\return destination URL string or NULL if error +*/ +char *gf_filter_pid_get_destination(GF_FilterPid *PID); + +/*! Gets URL argument of first source of PID if any - memory shall be freed by caller. +\param PID the target filter PID +\return source URL string or NULL if error +*/ +char *gf_filter_pid_get_source(GF_FilterPid *PID); + +/*! Indicates that this output PID requires a sourceID on the destination filter to be present. This prevents trying to link to other filters with no source IDs but +accepting the PID +\param PID the target filter PID +\return error code if any +*/ +GF_Err gf_filter_pid_require_source_id(GF_FilterPid *PID); + +/*! Enables decoding time reconstruction on PID for packets with DTS not set. If not enabled (default), dts not set implies dts = cts +\param PID the target filter PID +\param do_recompute if set, dts will be recomputed when not set +*/ +void gf_filter_pid_recompute_dts(GF_FilterPid *PID, Bool do_recompute); + +/*! Queries minimum packet duration as computed from DTS/CTS info on the PID +\param PID the target filter PID +\return minimum packet duration computed +*/ +u32 gf_filter_pid_get_min_pck_duration(GF_FilterPid *PID); + + +/*! Sends an event down the filter chain for input PID, or up the filter chain for output PIDs. +\param PID the target filter PID +\param evt the event to send +*/ +void gf_filter_pid_send_event(GF_FilterPid *PID, GF_FilterEvent *evt); + + +/*! Helper function to init play events, that checks the PID \ref GF_FilterPidPlaybackMode and adjust start/speed accordingly. This does not send the event. +\param PID the target filter PID +\param evt the event to initialize +\param start playback start time of request +\param speed playback speed time of request +\param log_name name used for logs in case of capability mismatched +*/ +void gf_filter_pid_init_play_event(GF_FilterPid *PID, GF_FilterEvent *evt, Double start, Double speed, const char *log_name); + +/*! Check if the given PID is marked as playing or not. +\param PID the target filter PID +\return GF_TRUE if PID is currently playing, GF_FALSE otherwise +*/ +Bool gf_filter_pid_is_playing(GF_FilterPid *PID); + +/*! Enables direct dispatch of packets to connected filters. This mode is useful when a filter may send a very large number of packets +in one process() call; this is for example the case of the isobmff muxer in interleave mode. Using this mode avoids overloading +the PID buffer with packets. +If the session is multi-threaded, this parameter has no effect. +\param PID the target filter PID +\return GF_TRUE if PID is currently playing, GF_FALSE otherwise +*/ +GF_Err gf_filter_pid_allow_direct_dispatch(GF_FilterPid *PID); + +/*! Gets the private stack of the alias filter associated with an input PID, if any + + \note The filter type of the alias is always the type of the filter holding the input PID connection. +\param PID the target filter PID +\return the temporary alias filter private stack, NULL otherwise +*/ +void *gf_filter_pid_get_alias_udta(GF_FilterPid *PID); + +/*! @} */ + + +/*! +\addtogroup fs_pck Filter Packet +\ingroup filters_grp +\brief Filter data exchange + + Packets consist in block of data or reference to such blocks, passed from the source to the sink only. +Internally, a packet created by a filter (output packet) is different from an input packet to a filter (\ref gf_filter_pid_get_packet) +but the API has been designed to hide this, so that most packet functions can be called regardless of the input/output nature of the PID. + +Packets have native attributes (timing, sap state, ...) but may also have any number of properties associated to them. + +The generic design of the architecture is that each filter is free to decide how it handle PIDs and their packets. This implies that the filter session has no clue how +an output packet relates to an input packet. +Developpers must therefore manually copy packet properties that seem relevant, or more practically copy all properties from input packet to output packet and +reassign output packet properties changed by the filter. + +In order to handle reordering of packets, it is possible to keep references to either packets (may block the filter chain), or packet properties. + +Packets shall always be dispatched in their processing order (decode order). If reordering upon reception is needed, or AU interleaving is used, a filter SHALL do the reordering. +However, packets do not have to be send in their creation order: a created packet is not assigned to PID buffers until it is send. + +@{ + */ + + +/*! Keeps a reference to the given input packet. The packet shall be released at some point using \ref gf_filter_pck_unref +\param pck the target input packet +\return error if any +*/ +GF_Err gf_filter_pck_ref(GF_FilterPacket **pck); + +/*! Remove a reference to the given input packet. The packet might be destroyed after that call. +\param pck the target input packet +*/ +void gf_filter_pck_unref(GF_FilterPacket *pck); + +/*! Creates a reference to the packet properties, but not to the data. +This is mostly useful for encoders/decoders/filters with delay, where the input packet needs to be released before getting the corresponding output (frame reordering & co). +This allows merging back packet properties after some delay without blocking the filter chain. +\param pck the target input packet +\return error if any +*/ +GF_Err gf_filter_pck_ref_props(GF_FilterPacket **pck); + + +/*! Allocates a new packet on the output PID with associated allocated data. +The packet has by default no DTS, no CTS, no duration framing set to full frame (start=end=1) and all other flags set to 0 (including SAP type). +\param PID the target output PID +\param data_size the desired size of the packet - can be changed later +\param data set to the writable buffer of the created packet +\return new packet or NULL if error +*/ +GF_FilterPacket *gf_filter_pck_new_alloc(GF_FilterPid *PID, u32 data_size, u8 **data); + + +/*! Allocates a new packet on the output PID referencing internal data. +The packet has by default no DTS, no CTS, no duration framing set to full frame (start=end=1) and all other flags set to 0 (including SAP type). +\param PID the target output PID +\param data the data block to dispatch +\param data_size the size of the data block to dispatch +\param destruct the callback function used to destroy the packet when no longer used - may be NULL +\return new packet or NULL if error +*/ +GF_FilterPacket *gf_filter_pck_new_shared(GF_FilterPid *PID, const u8 *data, u32 data_size, gf_fsess_packet_destructor destruct); + +/*! Allocates a new packet on the output PID referencing data of some input packet. +The packet has by default no DTS, no CTS, no duration framing set to full frame (start=end=1) and all other flags set to 0 (including SAP type). +\param PID the target output PID +\param data the data block to dispatch - if NULL, the entire data of the source packet is used +\param data_size the size of the data block to dispatch - if 0, the entire data of the source packet is used +\param source_packet the source packet this data belongs to (at least from the filter point of view). +\return new packet or NULL if error +*/ +GF_FilterPacket *gf_filter_pck_new_ref(GF_FilterPid *PID, const u8 *data, u32 data_size, GF_FilterPacket *source_packet); + +/*! Allocates a new packet on the output PID with associated allocated data. +The packet has by default no DTS, no CTS, no duration framing set to full frame (start=end=1) and all other flags set to 0 (including SAP type). +\param PID the target output PID +\param data_size the desired size of the packet - can be changed later +\param data set to the writable buffer of the created packet +\param destruct the callback function used to destroy the packet when no longer used - may be NULL +\return new packet or NULL if error +*/ +GF_FilterPacket *gf_filter_pck_new_alloc_destructor(GF_FilterPid *PID, u32 data_size, u8 **data, gf_fsess_packet_destructor destruct); + +/*! Clones a new packet from a source packet and copy all source properties to output. +If the source packet uses a frame interface object or has no associated data, returns a copy of the packet. +If the source packet is referenced more than once (ie more than just the caller), a new packet on the output PID is allocated with source data copied. +Otherwise, the source data is assigned to the output packet. + This is typically called by filter wishing to perform in-place processing of input data. +\param PID the target output PID +\param pck_source the desired source packet to clone +\param data set to the writable buffer of the created packet +\return new packet or NULL if error +*/ +GF_FilterPacket *gf_filter_pck_new_clone(GF_FilterPid *PID, GF_FilterPacket *pck_source, u8 **data); + +/*! Copies a new packet from a source packet and copy all source properties to output. +\param PID the target output PID +\param pck_source the desired source packet to clone +\param data set to the writable buffer of the created packet +\return new packet or NULL if error +*/ +GF_FilterPacket *gf_filter_pck_new_copy(GF_FilterPid *PID, GF_FilterPacket *pck_source, u8 **data); + +/*! Marks memory of a shared packet as non-writable. By default \ref gf_filter_pck_new_shared and \ref gf_filter_pck_new_ref allow +write access to internal memory in case the packet can be cloned (single reference used). If your filter relies on the content of the shared +memory for its internal state, packet must be marked as read-only to avoid later state corruption. +Note that packets created with \ref gf_filter_pck_new_frame_interface are always treated as read-only packets +\param pck the target output packet to send +\return error if any +*/ +GF_Err gf_filter_pck_set_readonly(GF_FilterPacket *pck); + +/*! Sends the packet on its output PID. Packets SHALL be sent in processing order (eg, decoding order for video). +However, packets don't have to be sent in their allocation order. +\param pck the target output packet to send +\return error if any +*/ +GF_Err gf_filter_pck_send(GF_FilterPacket *pck); + +/*! Destructs a packet allocated but that cannot be sent. Shall not be used on packet references. +\param pck the target output packet to send +*/ +void gf_filter_pck_discard(GF_FilterPacket *pck); + +/*! Destructs a packet allocated but that cannot be sent. Shall not be used on packet references. +This is a shortcut to \ref gf_filter_pck_new_ref + \ref gf_filter_pck_merge_properties + \ref gf_filter_pck_send +\param reference the input packet to forward +\param PID the output PID to forward to +\return error code if any +*/ +GF_Err gf_filter_pck_forward(GF_FilterPacket *reference, GF_FilterPid *PID); + +/*! Gets data associated with the packet. +\param pck the target packet +\param size set to the packet data size +\return packet data if any, NULL if empty or if the packet uses a frame interface object. see \ref gf_filter_pck_get_frame_interface +*/ +const u8 *gf_filter_pck_get_data(GF_FilterPacket *pck, u32 *size); + +/*! Sets a built-in property of a packet +\param pck the target packet +\param prop_4cc the code of the built-in property to set +\param value the property value to set +\return error code if any +*/ +GF_Err gf_filter_pck_set_property(GF_FilterPacket *pck, u32 prop_4cc, const GF_PropertyValue *value); + +/*! Sets a property of a packet +\param pck the target packet +\param name the name of the property to set +\param value the property value to set +\return error code if any +*/ +GF_Err gf_filter_pck_set_property_str(GF_FilterPacket *pck, const char *name, const GF_PropertyValue *value); + +/*! Sets a property of a packet +\param pck the target packet +\param name the code of the property to set. The name will be copied to the property, and memory destruction performed by the filter session +\param value the property value to set +\return error code if any +*/ +GF_Err gf_filter_pck_set_property_dyn(GF_FilterPacket *pck, char *name, const GF_PropertyValue *value); + +/*! Merge properties of source packet into destination packet but does NOT reset destination packet properties +\param pck_src source packet +\param pck_dst destination packet +\return error code if any +*/ +GF_Err gf_filter_pck_merge_properties(GF_FilterPacket *pck_src, GF_FilterPacket *pck_dst); + +/*! Same as \ref gf_filter_pck_merge_properties but uses a filter callback to select properties to merge +\param pck_src source packet +\param pck_dst destination packet +\param filter_prop callback filtering function +\param cbk callback data passed to the callback function +\return error code if any +*/ +GF_Err gf_filter_pck_merge_properties_filter(GF_FilterPacket *pck_src, GF_FilterPacket *pck_dst, gf_filter_prop_filter filter_prop, void *cbk); + +/*! Gets built-in property of packet. +\param pck target packet +\param prop_4cc the code of the built-in property +\return the property if found, NULL otherwise +*/ +const GF_PropertyValue *gf_filter_pck_get_property(GF_FilterPacket *pck, u32 prop_4cc); + +/*! Gets property of packet. +\param pck target packet +\param prop_name the name of the property +\return the property if found, NULL otherwise +*/ +const GF_PropertyValue *gf_filter_pck_get_property_str(GF_FilterPacket *pck, const char *prop_name); + +/*! Enumerates properties on packets. +\param pck target packet +\param idx input/output index of the current property. 0 means first. Incremented by 1 upon success +\param prop_4cc set to the code of the built-in property +\param prop_name set to the name of the property +\return the property if found, NULL otherwise +*/ +const GF_PropertyValue *gf_filter_pck_enum_properties(GF_FilterPacket *pck, u32 *idx, u32 *prop_4cc, const char **prop_name); + + +/*! Sets packet framing info. A full frame is a complete entity for the stream type, ie an access unit for media streams and a complete file for file streams +\param pck target packet +\param is_start packet is start of the frame +\param is_end packet is end of the frame +\return error code if any +*/ +GF_Err gf_filter_pck_set_framing(GF_FilterPacket *pck, Bool is_start, Bool is_end); + +/*! Gets packet framing info. A full frame is a complete entity for the stream type, ie an access unit for media streams and a complete file for file streams +\param pck target packet +\param is_start set to GF_TRUE if packet is start of the frame, to GF_FALSE otherwise +\param is_end set to GF_TRUE if packet is end of the frame, to GF_FALSE otherwise +\return error code if any +*/ +GF_Err gf_filter_pck_get_framing(GF_FilterPacket *pck, Bool *is_start, Bool *is_end); + +/*! Sets Decoding Timestamp (DTS) of the packet. Do not set if unknown - automatic packet duration is based on DTS diff if DTS is present, otherwise in CTS diff. +\param pck target packet +\param dts decoding timestamp of packet, in PID timescale units +\return error code if any +*/ +GF_Err gf_filter_pck_set_dts(GF_FilterPacket *pck, u64 dts); + +/*! Gets Decoding Timestamp (DTS) of the packet. +\param pck target packet +\return dts decoding timestamp of packet, in PID timescale units +*/ +u64 gf_filter_pck_get_dts(GF_FilterPacket *pck); + +/*! Sets Composition Timestamp (CTS) of the packet. Do not set if unknown - automatic packet duration is based on DTS diff if DTS is present, otherwise in CTS diff. +\param pck target packet +\param cts composition timestamp of packet, in PID timescale units +\return error code if any +*/ +GF_Err gf_filter_pck_set_cts(GF_FilterPacket *pck, u64 cts); + +/*! Gets Composition Timestamp (CTS) of the packet. +\param pck target packet +\return cts composition timestamp of packet, in PID timescale units +*/ +u64 gf_filter_pck_get_cts(GF_FilterPacket *pck); + +/*! Returns packet timescale (same as PID timescale) +\param pck target packet +\return packet timescale +*/ +u32 gf_filter_pck_get_timescale(GF_FilterPacket *pck); + +/*! Sets packet duration +\param pck target packet +\param duration duration of packet, in PID timescale units +\return error code if any +*/ +GF_Err gf_filter_pck_set_duration(GF_FilterPacket *pck, u32 duration); + +/*! Gets packet duration +\param pck target packet +\return duration duration of packet, in PID timescale units +*/ +u32 gf_filter_pck_get_duration(GF_FilterPacket *pck); + +/*! reallocates packet not yet sent. Returns data start and new range of data. This will reset byte offset information to not available. +\param pck target packet +\param nb_bytes_to_add bytes to add to packet +\param data_start realloc pointer of packet data start - may be NULL if new_range_start is set +\param new_range_start pointer to new (apppended space) data - may be NULL if data_start is set +\param new_size full size of allocated block. - may be be NULL +\return error code if any +*/ +GF_Err gf_filter_pck_expand(GF_FilterPacket *pck, u32 nb_bytes_to_add, u8 **data_start, u8 **new_range_start, u32 *new_size); + +/*! Truncates packet not yet sent to given size +\param pck target packet +\param size new size to truncate to +\return error code if any +*/ +GF_Err gf_filter_pck_truncate(GF_FilterPacket *pck, u32 size); + +/*! SAP types as defined in annex I of ISOBMFF */ +typedef enum +{ + /*! no SAP */ + GF_FILTER_SAP_NONE = 0, + /*! closed gop no leading */ + GF_FILTER_SAP_1, + /*! closed gop leading */ + GF_FILTER_SAP_2, + /*! open gop */ + GF_FILTER_SAP_3, + /*! roll period (GDR or audio roll) - roll distance must be indicated in packet */ + GF_FILTER_SAP_4, + /*! Audio preroll period - roll distance must be indicated in packet */ + GF_FILTER_SAP_4_PROL +} GF_FilterSAPType; + +/*! Sets packet SAP type +\param pck target packet +\param sap_type SAP type of the packet +\return error code if any +*/ +GF_Err gf_filter_pck_set_sap(GF_FilterPacket *pck, GF_FilterSAPType sap_type); + +/*! Sets packet SAP type +\param pck target packet +\return sap_type SAP type of the packet +*/ +GF_FilterSAPType gf_filter_pck_get_sap(GF_FilterPacket *pck); + + +/*! Sets packet video interlacing flag +\param pck target packet +\param is_interlaced set to 0 if not interlaced, 1 for top field first/contains only top field, 2 for bottom field first/contains only bottom field. +\return error code if any +*/ +GF_Err gf_filter_pck_set_interlaced(GF_FilterPacket *pck, u32 is_interlaced); + +/*! Gets packet video interlacing flag +\param pck target packet +\return interlaced flag, set to 0 if not interlaced, 1 for top field first, 2 otherwise. +*/ +u32 gf_filter_pck_get_interlaced(GF_FilterPacket *pck); + +/*! Sets packet corrupted data flag +\param pck target packet +\param is_corrupted indicates if data in packet is corrupted +\return error code if any +*/ +GF_Err gf_filter_pck_set_corrupted(GF_FilterPacket *pck, Bool is_corrupted); + +/*! Gets packet corrupted data flag +\param pck target packet +\return GF_TRUE if data in packet is corrupted +*/ +Bool gf_filter_pck_get_corrupted(GF_FilterPacket *pck); + +/*! Sets seek flag of packet. +For PIDs of stream type FILE with GF_PROP_PID_DISABLE_PROGRESSIVE set, the seek flag set to GF_TRUE indicates +that the packet is a PATCH packet, replacing bytes located at gf_filter_pck_get_byte_offset in file if the interlaced flag of the packet is not set, or +inserting bytes located at gf_filter_pck_get_byte_offset in file if the interlaced flag of the packet is set. +If the corrupted flag is set, this indicates the data will be replaced later on. +A seek packet is not meant to be displayed but is needed for decoding. +\param pck target packet +\param is_seek indicates packet is seek frame +\return error code if any +*/ +GF_Err gf_filter_pck_set_seek_flag(GF_FilterPacket *pck, Bool is_seek); + +/*! Gets packet seek data flag +\param pck target packet +\return GF_TRUE if packet is a seek packet +*/ +Bool gf_filter_pck_get_seek_flag(GF_FilterPacket *pck); + +/*! Sets packet byte offset in source. +byte offset should only be set if the data in the packet is exactly the same as the one at the given byte offset +\param pck target packet +\param byte_offset indicates the byte offset in the source. By default packets are created with no byte offset +\return error code if any +*/ +GF_Err gf_filter_pck_set_byte_offset(GF_FilterPacket *pck, u64 byte_offset); + +/*! Sets packet byte offset in source +\param pck target packet +\return byte offset in the source +*/ +u64 gf_filter_pck_get_byte_offset(GF_FilterPacket *pck); + +/*! Sets packet roll info (number of packets to rewind/forward and decode to get a clean access point). +\param pck target packet +\param roll_count indicates the roll distance of this packet - only used for SAP 4 for now +\return error code if any +*/ +GF_Err gf_filter_pck_set_roll_info(GF_FilterPacket *pck, s16 roll_count); + +/*! Gets packet roll info. +\param pck target packet +\return roll distance of this packet +\return error code if any +*/ +s16 gf_filter_pck_get_roll_info(GF_FilterPacket *pck); + +/*! Crypt flags for packet */ +#define GF_FILTER_PCK_CRYPT 1 + +/*! Sets packet crypt flags +\param pck target packet +\param crypt_flag packet crypt flag +\return error code if any +*/ +GF_Err gf_filter_pck_set_crypt_flags(GF_FilterPacket *pck, u8 crypt_flag); + +/*! Gets packet crypt flags +\param pck target packet +\return packet crypt flag +*/ +u8 gf_filter_pck_get_crypt_flags(GF_FilterPacket *pck); + +/*! Packet clock reference types - used for MPEG-2 TS*/ +typedef enum +{ + /*! packet is not a clock reference */ + GF_FILTER_CLOCK_NONE=0, + /*! packet is a PCR clock reference, expressed in PID timescale */ + GF_FILTER_CLOCK_PCR, + /*! packet is a PCR clock discontinuity, expressed in PID timescale */ + GF_FILTER_CLOCK_PCR_DISC, +} GF_FilterClockType; + +/*! Sets packet clock type +\param pck target packet +\param ctype packet clock flag +\return error code if any +*/ +GF_Err gf_filter_pck_set_clock_type(GF_FilterPacket *pck, GF_FilterClockType ctype); + +/*! Gets last clock type and clock value on PID. Always returns 0 if the source filter manages clock references internally cd \ref gf_filter_pid_set_clock_mode. +\param PID target PID to query for clock +\param clock_val last clock reference found +\param timescale last clock reference timescale +\return ctype packet clock flag +*/ +GF_FilterClockType gf_filter_pid_get_clock_info(GF_FilterPid *PID, u64 *clock_val, u32 *timescale); + +/*! Gets clock type of packet. Always returns 0 if the source filter does NOT manages clock references internally cd \ref gf_filter_pid_set_clock_mode. +\param pck target packet +\return ctype packet clock flag +*/ +GF_FilterClockType gf_filter_pck_get_clock_type(GF_FilterPacket *pck); + +/*! Sets packet carrousel info +\param pck target packet +\param version_number carrousel version number associated with this data chunk +\return error code if any +*/ +GF_Err gf_filter_pck_set_carousel_version(GF_FilterPacket *pck, u8 version_number); + +/*! Gets packet carrousel info +\param pck target packet +\return version_number carrousel version number associated with this data chunk +*/ +u8 gf_filter_pck_get_carousel_version(GF_FilterPacket *pck); + +/*! Sets packet sample dependency flags. + +Sample dependency flags have the same semantics as ISOBMFF:\n +bit(2)is_leading bit(2)sample_depends_on (2)sample_is_depended_on (2)sample_has_redundancy\n +\n +is_leading takes one of the following four values:\n +0: the leading nature of this sample is unknown;\n +1: this sample is a leading sample that has a dependency before the referenced I-picture (and is therefore not decodable);\n +2: this sample is not a leading sample;\n +3: this sample is a leading sample that has no dependency before the referenced I-picture (and is therefore decodable);\n +sample_depends_on takes one of the following four values:\n +0: the dependency of this sample is unknown;\n +1: this sample does depend on others (not an I picture);\n +2: this sample does not depend on others (I picture);\n +3: reserved\n +sample_is_depended_on takes one of the following four values:\n +0: the dependency of other samples on this sample is unknown;\n +1: other samples may depend on this one (not disposable);\n +2: no other sample depends on this one (disposable);\n +3: reserved\n +sample_has_redundancy takes one of the following four values:\n +0: it is unknown whether there is redundant coding in this sample;\n +1: there is redundant coding in this sample;\n +2: there is no redundant coding in this sample;\n +3: reserved\n + +\param pck target packet +\param dep_flags sample dependency flags +\return error code if any +*/ +GF_Err gf_filter_pck_set_dependency_flags(GF_FilterPacket *pck, u8 dep_flags); + +/*! Gets packet sample dependency flags. +\param pck target packet +\return dep_flags sample dependency flags - see \ref gf_filter_pck_set_dependency_flags for syntax. +*/ +u8 gf_filter_pck_get_dependency_flags(GF_FilterPacket *pck); + + +/*! Sets packet sequence number. Shall only be used when a filter handles a PLAY request based on packet sequence number +\param pck target packet +\param seq_num sequence number of packet +\return error code if any +*/ +GF_Err gf_filter_pck_set_seq_num(GF_FilterPacket *pck, u32 seq_num); + +/*! Gets packet sequence number info +\param pck target packet +\return version_number carrousel version number associated with this data chunk +*/ +u32 gf_filter_pck_get_seq_num(GF_FilterPacket *pck); + +/*! Redefinition of GF_Matrix but without the maths.h include which breaks VideoToolBox on OSX/iOS */ +typedef struct __matrix GF_Matrix_unexposed; + +/*! frame interface flags*/ +enum +{ + /*! When set , indicates that the emitting filter will block until this frame is released. + Consumers of such a packet shall drop the packet as soon as possible, since it blocks the emiting filter.*/ + GF_FRAME_IFCE_BLOCKING = 1, + /*! When set , indicates that the associated framebuffer is the main GL framebuffer.*/ + GF_FRAME_IFCE_MAIN_GLFB = 1<<1 +}; + +/*! Frame interface object + +The frame interface object can be used to expose an interface between the packet generated by a filter and its consumers, when dispatching a regular data block is not possible. +This is typically used by decoders exposing the output image planes or by OpenGL filters outputing textures. +Currently only video frames use this interface object. +*/ +typedef struct _gf_filter_frame_interface +{ + /*! get video frame plane + \param frame interface object for the video frame + \param plane_idx plane index, 0: Y or full plane, 1: U or UV plane, 2: V plane + \param outPlane address of target color plane + \param outStride stride in bytes of target color plane + \return error code if any + */ + GF_Err (*get_plane)(struct _gf_filter_frame_interface *frame, u32 plane_idx, const u8 **outPlane, u32 *outStride); + + /*! get video frame plane texture + \param frame interface object for the video frame + \param plane_idx plane index, 0: Y or full plane, 1: U or UV plane, 2: V plane + \param gl_tex_format GL texture format used + \param gl_tex_id GL texture ID used + \param texcoordmatrix texture transform to fill. The texture is expected to be layed out as an image (first pixel is top-first). If not the case, add a vertical flip (eg dispatching an OpenGL FBO). \return error code if any + */ + GF_Err (*get_gl_texture)(struct _gf_filter_frame_interface *frame, u32 plane_idx, u32 *gl_tex_format, u32 *gl_tex_id, GF_Matrix_unexposed * texcoordmatrix); + + /*! Flags for this frame interface.*/ + u32 flags; + + /*! private space for the emitting filter, consumers shall not modify this*/ + void *user_data; +} GF_FilterFrameInterface; + + +/*! Allocates a new packet holding a reference to a frame interface object. +The packet has by default no DTS, no CTS, no duration framing set to full frame (start=end=1) and all other flags set to 0 (including SAP type). +\param PID the target output PID +\param frame_ifce the associated frame interface object +\param destruct the destructor to be called upon packet release +\return new packet +*/ +GF_FilterPacket *gf_filter_pck_new_frame_interface(GF_FilterPid *PID, GF_FilterFrameInterface *frame_ifce, gf_fsess_packet_destructor destruct); + +/*! Gets a frame interface associated with a packet if any. + +Consummers will typically first check if the packet has associated data using \ref gf_filter_pck_get_data. + +\param pck the target packet +\return the associated frame interface object if any, or NULL otherwise +*/ +GF_FilterFrameInterface *gf_filter_pck_get_frame_interface(GF_FilterPacket *pck); + + +/*! Checks if the packet is a blocking reference, i.e. a parent filter in the chain is waiting for its destruction to emit a new packet. +This is typically used by sink filters to decide if they can hold references to input packets without blocking the chain. +\param pck the target packet +\return GF_TRUE if the packet is blocking or is a reference to a blocking packet, GF_FALSE otherwise +*/ +Bool gf_filter_pck_is_blocking_ref(GF_FilterPacket *pck); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif //_GF_FILTERS_H_ + diff --git a/include/gpac/html5_media.h b/include/gpac/html5_media.h index abcfc00..c849c1e 100644 --- a/include/gpac/html5_media.h +++ b/include/gpac/html5_media.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Cyril Concolato - * Copyright (c) Telecom ParisTech 2013- + * Copyright (c) Telecom ParisTech 2013-2019 * All rights reserved * * This file is part of GPAC / HTML Media Element header @@ -33,22 +33,24 @@ extern "C" { /*! - * \file - * \brief Scene graph extensions for HTML5 media. +\file +\brief Scene graph extensions for HTML5 media. */ -/*! \defgroup html5_grp HTML5 - * \ingroup scene_grp - * \brief HTML5 extensions of the scene graph. - * +/*! +\defgroup html5_grp HTML5 +\ingroup scene_grp +\brief HTML5 extensions of the scene graph. */ + /*! - * \addtogroup html5media_grp HTML5 media - * \ingroup html5_grp - * \brief Scene graph extensions for HTML5 media. - * - *This section documents the scene graph extensions used for HTML5 media (video). - * @{ +\addtogroup html5media_grp HTML5 media +\ingroup html5_grp +\brief Scene graph extensions for HTML5 media. + +This section documents the scene graph extensions used for HTML5 media (video). + +@{ */ #include @@ -62,52 +64,49 @@ extern "C" { #include #include -#include #include -#include +#include -#ifdef GPAC_HAS_SPIDERMONKEY -#include -#endif +#ifdef GPAC_ENABLE_HTML5_MEDIA typedef struct { u32 nb_inst; /* Basic classes */ - GF_JSClass arrayBufferClass; + JSClassDef arrayBufferClass; /*HTML Media classes*/ - GF_JSClass htmlVideoElementClass; - GF_JSClass htmlAudioElementClass; - GF_JSClass htmlSourceElementClass; - GF_JSClass htmlTrackElementClass; - GF_JSClass htmlMediaElementClass; - GF_JSClass mediaControllerClass; - GF_JSClass audioTrackListClass; - GF_JSClass audioTrackClass; - GF_JSClass videoTrackListClass; - GF_JSClass videoTrackClass; - GF_JSClass textTrackListClass; - GF_JSClass textTrackClass; - GF_JSClass textTrackCueListClass; - GF_JSClass textTrackCueClass; - GF_JSClass timeRangesClass; - GF_JSClass trackEventClass; - GF_JSClass mediaErrorClass; + JSClassDef htmlVideoElementClass; + JSClassDef htmlAudioElementClass; + JSClassDef htmlSourceElementClass; + JSClassDef htmlTrackElementClass; + JSClassDef htmlMediaElementClass; + JSClassDef mediaControllerClass; + JSClassDef audioTrackListClass; + JSClassDef audioTrackClass; + JSClassDef videoTrackListClass; + JSClassDef videoTrackClass; + JSClassDef textTrackListClass; + JSClassDef textTrackClass; + JSClassDef textTrackCueListClass; + JSClassDef textTrackCueClass; + JSClassDef timeRangesClass; + JSClassDef trackEventClass; + JSClassDef mediaErrorClass; /* Media Source Extensions */ - GF_JSClass mediaSourceClass; - GF_JSClass sourceBufferClass; - GF_JSClass sourceBufferListClass; - GF_JSClass URLClass; + JSClassDef mediaSourceClass; + JSClassDef sourceBufferClass; + JSClassDef sourceBufferListClass; + JSClassDef URLClass; /* Media Capture */ - GF_JSClass mediaStreamClass; - GF_JSClass localMediaStreamClass; - GF_JSClass mediaStreamTrackClass; - GF_JSClass mediaStreamTrackListClass; - GF_JSClass navigatorUserMediaClass; - GF_JSClass navigatorUserMediaErrorClass; + JSClassDef mediaStreamClass; + JSClassDef localMediaStreamClass; + JSClassDef mediaStreamTrackClass; + JSClassDef mediaStreamTrackListClass; + JSClassDef navigatorUserMediaClass; + JSClassDef navigatorUserMediaErrorClass; } GF_HTML_MediaRuntime; /************************************************************ @@ -177,7 +176,7 @@ typedef enum { JSObject *_this;\ /* GPAC-specific properties */\ u32 bin_id; /* track id */\ - LPNETCHANNEL channel; /* channel object used by the terminal */\ + void * channel; /* channel object used by the terminal */\ GF_ObjectDescriptor *od; /* MPEG-4 Object descriptor for this track */\ GF_List *buffer; /* List of MSE Packets */\ u32 packet_index; /* index of MSE Packets*/\ @@ -322,7 +321,7 @@ typedef struct u32 length; char *url; Bool is_init; - + GF_Blob blob; /* used to do proper garbage collection between JS and Terminal */ u32 reference_count; } GF_HTML_ArrayBuffer; @@ -372,10 +371,13 @@ void gf_html_media_controller_del(GF_HTML_MediaController *mc); /* * HTML5 Array Buffer */ -GF_HTML_ArrayBuffer *gf_arraybuffer_new(char *data, u32 length); -JSObject *gf_arraybuffer_js_new(JSContext *c, char *data, u32 length, JSObject *parent); +GF_HTML_ArrayBuffer *gf_arraybuffer_new(u8 *data, u32 length); +JSObject *gf_arraybuffer_js_new(JSContext *c, u8 *data, u32 length, JSObject *parent); void gf_arraybuffer_del(GF_HTML_ArrayBuffer *buffer, Bool del_js); + +#endif //GPAC_ENABLE_HTML5_MEDIA + /*! @} */ #ifdef __cplusplus diff --git a/include/gpac/html5_mse.h b/include/gpac/html5_mse.h index c7e801b..5f056fb 100644 --- a/include/gpac/html5_mse.h +++ b/include/gpac/html5_mse.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Cyril Concolato - * Copyright (c) Telecom ParisTech 2013- + * Copyright (c) Telecom ParisTech 2013-2019 * All rights reserved * * This file is part of GPAC / HTML Media Source header @@ -33,23 +33,24 @@ extern "C" { /*! - * \file - * \brief Scene graph extensions for Media Source Extensions. +\file +\brief Scene graph extensions for Media Source Extensions. */ /*! - * \addtogroup html5mse_grp HTML5 MSE - * \ingroup html5_grp - * \brief Scene graph extensions for HTML5 media source extensions. - * - *This section documents the scene graph extensions used for HTML5 media source extensions. - * @{ - */ +\addtogroup html5mse_grp HTML5 MSE +\ingroup html5_grp +\brief Scene graph extensions for HTML5 media source extensions. + +This section documents the scene graph extensions used for HTML5 media source extensions. +@{ +*/ #include #include -#include + +#ifdef GPAC_ENABLE_HTML5_MEDIA typedef enum { @@ -121,7 +122,7 @@ typedef struct void *prev_buffer; /* Media parser */ - GF_InputService *parser; + void *parser; /* MPEG-4 Object descriptor as returned by the media parser */ GF_ObjectDescriptor *service_desc; @@ -187,7 +188,7 @@ typedef struct _html_mediasource should be NULL when the MediaSource is not open we use only one service object for all sourceBuffers */ - GF_ClientService *service; + void *service; /* SceneGraph to be used before the node is actually attached */ GF_SceneGraph *sg; @@ -229,7 +230,7 @@ typedef struct GF_Err status; } GF_MSE_Packet; -GF_Err gf_mse_proxy(GF_InputService *parser, GF_NetworkCommand *command); +GF_Err gf_mse_proxy(void *parser, void *command); void gf_mse_packet_del(GF_MSE_Packet *packet); GF_Err gf_mse_track_buffer_get_next_packet(GF_HTML_Track *track, @@ -238,6 +239,8 @@ GF_Err gf_mse_track_buffer_get_next_packet(GF_HTML_Track *track, GF_Err *out_reception_status, Bool *is_new_data); GF_Err gf_mse_track_buffer_release_packet(GF_HTML_Track *track); +#endif //GPAC_ENABLE_HTML5_MEDIA + /*! @} */ #ifdef __cplusplus diff --git a/include/gpac/ietf.h b/include/gpac/ietf.h index 136fdaa..856e368 100644 --- a/include/gpac/ietf.h +++ b/include/gpac/ietf.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / IETF RTP/RTSP/SDP sub-project @@ -33,17 +33,19 @@ extern "C" { #endif /*! - * \file - * \brief Tools for real-time streaming over IP using RTP/RTCP/RTSP/SDP . - */ +\file +\brief Tools for real-time streaming over IP using RTP/RTCP/RTSP/SDP . +*/ /*! - * \addtogroup ietf_grp RTP Streaming - * \ingroup media_grp - * \brief Tools for real-time streaming over IP using RTP/RTCP/RTSP/SDP. - * - *This section documents the tools used for real-time streaming over IP using RTP/RTCP/RTSP/SDP. - * @{ +\addtogroup ietf_grp RTP Streaming +\ingroup media_grp +\brief Tools for real-time streaming over IP using RTP/RTCP/RTSP/SDP. + + +This section documents the tools used for real-time streaming over IP using RTP/RTCP/RTSP/SDP. + +@{ */ #include @@ -55,18 +57,11 @@ extern "C" { #include -/**************************************************************************** - - RTSP VERSION 1.0 LIBRARY EXPORTS - -****************************************************************************/ - +/*! RTSP version supported by GPAC*/ #define GF_RTSP_VERSION "RTSP/1.0" -/* - * RTSP NOTIF CODES - */ +/*! RTSP NOTIF CODES */ enum { NC_RTSP_Continue = 100, @@ -119,14 +114,18 @@ enum NC_RTSP_Option_not_support = 551, }; +/*! Gives string descritpion of error code +\param ErrCode the RTSP error code +\return the description of the RTSP error code +*/ const char *gf_rtsp_nc_to_string(u32 ErrCode); /* Common structures between commands and responses */ -/* - RTSP Range information - RTSP Session level only (though this is almost the same +/*! RTSP Range information + RTSP Session level only, although this is almost the same format as an SDP range, this is not used in the SDP lib as "a=range" is not part of SDP but part of RTSP */ @@ -139,13 +138,19 @@ typedef struct { Double FPS; } GF_RTSPRange; -/* -parses a Range line and returns range header structure. can be used for RTSP extension of SDP -NB: Only support for npt for now +/*! parses a Range line and returns range header structure. This can be used for RTSP extension of SDP +Note: Only support for npt for now +\param range_buf the range string +\return a newly allocated RTSP range */ GF_RTSPRange *gf_rtsp_range_parse(char *range_buf); - +/*! creates a new RTSP range +\return a newly allocated RTSP range +*/ GF_RTSPRange *gf_rtsp_range_new(); +/*! destroys a RTSP range +\param range the target RTSP range +*/ void gf_rtsp_range_del(GF_RTSPRange *range); /* @@ -153,14 +158,14 @@ void gf_rtsp_range_del(GF_RTSPRange *range); contains all network info for RTSP sessions (ports, uni/multi-cast, ...) */ -/* - Transport Profiles as defined in RFC 2326 -*/ +/*! RTSP AVP Transport Profile */ #define GF_RTSP_PROFILE_RTP_AVP "RTP/AVP" +/*! RTSP AVP + TCP Transport Profile */ #define GF_RTSP_PROFILE_RTP_AVP_TCP "RTP/AVP/TCP" +/*! RTSP UDP Transport Profile */ #define GF_RTSP_PROFILE_UDP "udp" - +/*! RTSP transport structure*/ typedef struct { /* set to 1 if unicast */ @@ -191,10 +196,18 @@ typedef struct /*Transport protocol. In this version we only support RTP/AVP, the following flag tells us if this is RTP/AVP/TCP or RTP/AVP (default)*/ char *Profile; -} GF_RTSPTransport; + Bool is_sender; +} GF_RTSPTransport; -GF_RTSPTransport *gf_rtsp_transport_clone(GF_RTSPTransport *original); +/*! clones a RTSP transport +\param transp the target RTSP transport +\return an allocated copy RTSP transport +*/ +GF_RTSPTransport *gf_rtsp_transport_clone(GF_RTSPTransport *transp); +/*! destroys a RTSP transport +\param transp the target RTSP transport +*/ void gf_rtsp_transport_del(GF_RTSPTransport *transp); @@ -208,19 +221,30 @@ void gf_rtsp_transport_del(GF_RTSPTransport *transp); */ /*ALL RTSP METHODS - all other methods will be ignored*/ +/*! RTSP DESCRIBE method*/ #define GF_RTSP_DESCRIBE "DESCRIBE" +/*! RTSP SETUP method*/ #define GF_RTSP_SETUP "SETUP" +/*! RTSP PLAY method*/ #define GF_RTSP_PLAY "PLAY" +/*! RTSP PAUSE method*/ #define GF_RTSP_PAUSE "PAUSE" +/*! RTSP RECORD method*/ #define GF_RTSP_RECORD "RECORD" +/*! RTSP TEARDOWN method*/ #define GF_RTSP_TEARDOWN "TEARDOWN" +/*! RTSP GET_PARAMETER method*/ #define GF_RTSP_GET_PARAMETER "GET_PARAMETER" +/*! RTSP SET_PARAMETER method*/ #define GF_RTSP_SET_PARAMETER "SET_PARAMETER" -#define GF_RTSP_OPTIONS "OPTIONS" +/*! RTSP OPTIONS method*/ +#define GF_RTSP_OPTIONS "OPTIONS" +/*! RTSP ANNOUCE method*/ #define GF_RTSP_ANNOUNCE "ANNOUNCE" -#define GF_RTSP_REDIRECTE "REDIRECT" - +/*! RTSP REDIRECT method*/ +#define GF_RTSP_REDIRECT "REDIRECT" +/*! RTSP command structure*/ typedef struct { char *Accept; @@ -283,9 +307,17 @@ typedef struct u32 StatusCode; } GF_RTSPCommand; - +/*! creates an RTSP command +\return the newly allocated RTSP command +*/ GF_RTSPCommand *gf_rtsp_command_new(); +/*! destroys an RTSP command +\param com the target RTSP command +*/ void gf_rtsp_command_del(GF_RTSPCommand *com); +/*! resets an RTSP command +\param com the target RTSP command +*/ void gf_rtsp_command_reset(GF_RTSPCommand *com); @@ -299,8 +331,9 @@ void gf_rtsp_command_reset(GF_RTSPCommand *com); */ -/* - RTP-Info for RTP channels. There may be several RTP-Infos in one response +/*! RTP-Info for RTP channels. + + There may be several RTP-Infos in one response based on the server implementation (DSS/QTSS begaves this way) */ typedef struct @@ -317,9 +350,7 @@ typedef struct -/* - RTSP Response -*/ +/*! RTSP Response */ typedef struct { /* response code*/ @@ -386,126 +417,211 @@ typedef struct char *body; } GF_RTSPResponse; - +/*! creates an RTSP response +\return the newly allocated RTSP response +*/ GF_RTSPResponse *gf_rtsp_response_new(); +/*! deletes an RTSP response +\param rsp the target RTSP response +*/ void gf_rtsp_response_del(GF_RTSPResponse *rsp); +/*! resets an RTSP response +\param rsp the target RTSP response +*/ void gf_rtsp_response_reset(GF_RTSPResponse *rsp); - +/*! RTSP session*/ typedef struct _tag_rtsp_session GF_RTSPSession; +/*! creates a new RTSP session +\param sURL the target RTSP session URL +\param DefaultPort the target RTSP session port +\return a newly allocated RTSP session +*/ GF_RTSPSession *gf_rtsp_session_new(char *sURL, u16 DefaultPort); +/*! destroys an RTSP session +\param sess the target RTSP session +*/ void gf_rtsp_session_del(GF_RTSPSession *sess); +/*! sets TCP buffer size of an RTSP session +\param sess the target RTSP session +\param BufferSize desired buffer size in bytes +\return error if any +*/ GF_Err gf_rtsp_set_buffer_size(GF_RTSPSession *sess, u32 BufferSize); -/*force the IP address the client is using*/ -void gf_rtsp_set_mobile_ip(GF_RTSPSession *sess, char *MobileIP); - -/*Reset state machine, invalidate SessionID +/*! resets state machine, invalidate SessionID NOTE: RFC2326 requires that the session is reseted when all RTP streams are closed. As this lib doesn't maintain the number of valid streams you MUST call reset when all your streams are shutdown (either requested through TEARDOWN or signaled through RTCP BYE packets for RTP, or any other signaling means for other protocols) -reset connection will destroy the socket - this is useful in case of timeouts, because -some servers do not restart with the right CSeq...*/ +\param sess the target RTSP session +\param ResetConnection if set, this will destroy the associated TCP socket. This is useful in case of timeouts, because +some servers do not restart with the right CSeq. +*/ void gf_rtsp_session_reset(GF_RTSPSession *sess, Bool ResetConnection); -u32 gf_rtsp_is_my_session(GF_RTSPSession *sess, char *url); -const char *gf_rtsp_get_last_session_id(GF_RTSPSession *sess); +/*! checks if an RTSP session matches an RTSP URL +\param sess the target RTSP session +\param url the URL to test +\return GF_TRUE if the session matches the URL, GF_FALSE otherwise +*/ +Bool gf_rtsp_is_my_session(GF_RTSPSession *sess, char *url); + +/*! gets server name of an RTSP session +\param sess the target RTSP session +\return the server name +*/ char *gf_rtsp_get_server_name(GF_RTSPSession *sess); -char *gf_rtsp_get_service_name(GF_RTSPSession *sess); +/*! gets server port of an RTSP session +\param sess the target RTSP session +\return the server port +*/ u16 gf_rtsp_get_session_port(GF_RTSPSession *sess); -/*Fetch an RTSP response from the server the GF_RTSPResponse will be reseted before fetch*/ +/*! fetches an RTSP response from the server. the GF_RTSPResponse will be reseted before fetch +\param sess the target RTSP session +\param rsp the response object to fill with the response. This will be reseted before TCP fetch +\return error if any +*/ GF_Err gf_rtsp_get_response(GF_RTSPSession *sess, GF_RTSPResponse *rsp); -/*RTSP State Machine. The only non blocking mode is GF_RTSP_STATE_WAIT_FOR_CONTROL*/ +/*! RTSP States. The only non blocking mode is GF_RTSP_STATE_WAIT_FOR_CONTROL*/ enum { - /*Initialized (connection might be off, but all structures are in place) + /*! Initialized (connection might be off, but all structures are in place) This is the default state between # requests (aka, DESCRIBE and SETUP or SETUP and PLAY ...)*/ GF_RTSP_STATE_INIT = 0, - /*Waiting*/ + /*! Waiting*/ GF_RTSP_STATE_WAITING, - /*PLAY, PAUSE, RECORD. Aggregation is allowed for the same type, you can send several command + /*! PLAY, PAUSE, RECORD. Aggregation is allowed for the same type, you can send several command in a row. However the session will return GF_SERVICE_ERROR if you do not have a valid SessionID in the command You cannot issue a SETUP / DESCRIBE while in this state*/ GF_RTSP_STATE_WAIT_FOR_CONTROL, - /*FATAL ERROR: session is invalidated by server. Call reset and restart from SETUP if needed*/ + /*! FATAL ERROR: session is invalidated by server. Call reset and restart from SETUP if needed*/ GF_RTSP_STATE_INVALIDATED }; +/*! gets the RTSP session state +\param sess the target RTSP session +\return the session state +*/ u32 gf_rtsp_get_session_state(GF_RTSPSession *sess); -/*aggregate command state-machine: the PLAY/PAUSE can be aggregated -(sent before the reply is received). This function gets the last command sent*/ -char *gf_rtsp_get_last_request(GF_RTSPSession *sess); -/*foce a reset in case of pbs*/ +/*! forces a reset of the state to GF_RTSP_STATE_INIT +\param sess the target RTSP session +*/ void gf_rtsp_reset_aggregation(GF_RTSPSession *sess); -/* - Send an RTSP request to the server. +/*! sends an RTSP request to the server. +\param sess the target RTSP session +\param com the RTSP command to send +\return error if any */ GF_Err gf_rtsp_send_command(GF_RTSPSession *sess, GF_RTSPCommand *com); -GF_Err gf_rtsp_set_interleave_callback(GF_RTSPSession *sess, - GF_Err (*SignalData)(GF_RTSPSession *sess, void *cbk_ptr, char *buffer, u32 bufferSize, Bool IsRTCP) - ); +/*! callback function for interleaved RTSP/TCP transport +\param sess the target RTSP session +\param cbk_ptr opaque data +\param buffer RTP or RTCP packet +\param bufferSize packet size in bytes +\param IsRTCP set to GF_TRUE if the packet is an RTCP packet, GF_FALSE otherwise +\return error if any +*/ +typedef GF_Err (*gf_rtsp_interleave_callback)(GF_RTSPSession *sess, void *cbk_ptr, u8 *buffer, u32 bufferSize, Bool IsRTCP); +/*! assigns the callback function for interleaved RTSP/TCP transport +\param sess the target RTSP session +\param SignalData the callback function on each interleaved packet +\return error if any +*/ +GF_Err gf_rtsp_set_interleave_callback(GF_RTSPSession *sess, gf_rtsp_interleave_callback SignalData); +/*! reads RTSP session (response fetch and interleaved RTSP/TCP transport) +\param sess the target RTSP session +\return error if any +*/ GF_Err gf_rtsp_session_read(GF_RTSPSession *sess); +/*! registers a new interleaved RTP channel over an RTSP connection +\param sess the target RTSP session +\param the_ch opaque data passed to \ref gf_rtsp_interleave_callback +\param LowInterID ID of the RTP interleave channel +\param HighInterID ID of the RCTP interleave channel +\return error if any +*/ GF_Err gf_rtsp_register_interleave(GF_RTSPSession *sess, void *the_ch, u8 LowInterID, u8 HighInterID); +/*! unregisters a new interleaved RTP channel over an RTSP connection +\param sess the target RTSP session +\param LowInterID ID of the RTP interleave channel +\return the numbers of registered interleaved channels remaining +*/ u32 gf_rtsp_unregister_interleave(GF_RTSPSession *sess, u8 LowInterID); - -/* - Server side session constructor - create a new RTSP session from an existing socket in listen state. If no pending connection +/*! creates a new RTSP session from an existing socket in listen state. If no pending connection is detected, return NULL +\param rtsp_listener the listening server socket +\return the newly allocated RTSP session if any, NULL otherwise */ GF_RTSPSession *gf_rtsp_session_new_server(GF_Socket *rtsp_listener); -/*fetch an RTSP request. The GF_RTSPCommand will be reseted before fetch*/ +/*! fetches an RTSP request +\param sess the target RTSP session +\param com the RTSP command to fill with the command. This will be reseted before fetch +\return error if any +*/ GF_Err gf_rtsp_get_command(GF_RTSPSession *sess, GF_RTSPCommand *com); -/*unpack the URL, check the service name / server. Typically used when a client sends a - DESCRIBE || SETUP url RTSP/1.0. Server / service name check must be performed by your app as redirection -or services available are unknown here.*/ -GF_Err gf_rtsp_load_service_name(GF_RTSPSession *sess, char *URL); - -/*geenrates a session ID fpor the given session*/ +/*! generates a session ID for the given session +\param sess the target RTSP session +\return an allocated string containing a session ID +*/ char *gf_rtsp_generate_session_id(GF_RTSPSession *sess); -/*send the RTSP response*/ +/*! sends an RTSP response +\param sess the target RTSP session +\param rsp the response to send +\return error if any +*/ GF_Err gf_rtsp_send_response(GF_RTSPSession *sess, GF_RTSPResponse *rsp); -/*gets the IP address of the local host running the session -buffer shall be GF_MAX_IP_NAME_LEN long*/ -GF_Err gf_rtsp_get_session_ip(GF_RTSPSession *sess, char *buffer); - -/*returns the next available ID for interleaving. It is recommended that you use 2 -consecutive IDs for RTP/RTCP interleaving*/ -u8 gf_rtsp_get_next_interleave_id(GF_RTSPSession *sess); +/*! gets the IP address of the local host running the session +\param sess the target RTSP session +\param buffer buffer to store the local host name +\return error if any +*/ +GF_Err gf_rtsp_get_session_ip(GF_RTSPSession *sess, char buffer[GF_MAX_IP_NAME_LEN]); -/*gets the IP address of the connected peer - buffer shall be GF_MAX_IP_NAME_LEN long*/ +/*! gets the IP address of the connected peer +\param sess the target RTSP session +\param buffer buffer to store the connected peer name +\return error if any +*/ GF_Err gf_rtsp_get_remote_address(GF_RTSPSession *sess, char *buffer); +/*! writes a packet on an interleaved channel over RTSP +\param sess the target RTSP session +\param idx ID (RTP or RTCP) of the interleaved channel +\param pck packet data (RTP or RTCP packet) to write +\param pck_size packet size in bytes +\return error if any +*/ +GF_Err gf_rtsp_session_write_interleaved(GF_RTSPSession *sess, u32 idx, u8 *pck, u32 pck_size); /* RTP LIB EXPORTS */ - +/*! RTP header */ typedef struct tagRTP_HEADER { /*version, must be 2*/ u8 Version; @@ -533,11 +649,7 @@ typedef struct tagRTP_HEADER { } GF_RTPHeader; - - -/* - structure containing the rtpmap information -*/ +/*! RTPMap information*/ typedef struct { /*dynamic payload type of this map*/ @@ -551,130 +663,264 @@ typedef struct } GF_RTPMap; - +/*! RTP channel*/ typedef struct __tag_rtp_channel GF_RTPChannel; +/*! creates a new RTP channel +\return a newly allocated RTP channel +*/ GF_RTPChannel *gf_rtp_new(); +/*! destroys an RTP channel +\param ch the target RTP channel +*/ void gf_rtp_del(GF_RTPChannel *ch); -/*you configure a server channel through the transport structure, with the same info as a +/*! setup transport for an RTP channel +A server channelis configured through the transport structure, with the same info as a client channel, the client_port_* info designing the REMOTE client and port_* designing -your server channel*/ +the server channel +\param ch the target RTP channel +\param trans_info the transport info for this channel +\param remote_address the remote / destination address of the channel +\return error if any +*/ GF_Err gf_rtp_setup_transport(GF_RTPChannel *ch, GF_RTSPTransport *trans_info, const char *remote_address); -/*auto-setup of rtp/rtcp transport ports - only effective in unicast, non interleaved cases. -for multicast port setup MUST be done through the above gf_rtp_setup_transport function -this will take care of port reuse*/ +/*! setup of rtp/rtcp transport ports +This only applies in unicast, non interleaved cases. +For multicast port setup MUST be done through the above gf_rtp_setup_transport function +This will take care of port reuse +\param ch the target RTP channel +\param first_port RTP port number of the RTP channel +\return error if any +*/ GF_Err gf_rtp_set_ports(GF_RTPChannel *ch, u16 first_port); -/*init of payload information. only ONE payload per sync source is supported in this -version of the library (a sender cannot switch payload types on a single media)*/ -GF_Err gf_rtp_setup_payload(GF_RTPChannel *ch, GF_RTPMap *map); +/*! init of RTP payload information. Only ONE payload per sync source is supported in this +version of the library (a sender cannot switch payload types on a single media) +\param ch the target RTP channel +\param PayloadType identifier of RTP payload +\param ClockRate clock rate in (1/Hz) of RTP channel +\return error if any +*/ +GF_Err gf_rtp_setup_payload(GF_RTPChannel *ch, u32 PayloadType, u32 ClockRate); -/*enables sending of NAT keep-alive packets for NAT traversal - @nat_timeout: specifies the inactivity period in ms after which NAT keepalive packets are sent. - If 0, disables NAT keep-alive packets +/*! enables sending of NAT keep-alive packets for NAT traversal +\param ch the target RTP channel +\param nat_timeout specifies the inactivity period in ms after which NAT keepalive packets are sent. If 0, disables NAT keep-alive packets */ void gf_rtp_enable_nat_keepalive(GF_RTPChannel *ch, u32 nat_timeout); -/*initialize the RTP channel. - -UDPBufferSize: UDP stack buffer size if configurable by OS/ISP - ignored otherwise +/*! initializes the RTP channel. +\param ch the target RTP channel +\param UDPBufferSize UDP stack buffer size if configurable by OS/ISP - ignored otherwise NOTE: on WinCE devices, this is not configurable on an app bases but for the whole OS you must update the device registry with: +\code [HKEY_LOCAL_MACHINE\Comm\Afd] DgramBuffer=dword:N +\endcode where N is the number of UDP datagrams a socket should be able to buffer. For multimedia app you should set N as large as possible. The device MUST be reseted for the param to take effect -ReorederingSize: max number of packets to queue for reordering. 0 means no reordering -MaxReorderDelay: max time to wait in ms before releasing first packet in reoderer when only one packet is present. -If 0 and reordering size is specified, defaults to 200 ms (usually enough). -IsSource: if true, the channel is a sender (media data, sender report, Receiver report processing) +\param IsSource if true, the channel is a sender (media data, sender report, Receiver report processing) if source, you must specify the Path MTU size. The RTP lib won't send any packet bigger than this size your application shall perform payload size splitting if needed -local_interface_ip: local interface address to use for multicast. If NULL, default address is used +\param PathMTU desired path MTU (max packet size) in bytes +\param ReorederingSize max number of packets to queue for reordering. 0 means no reordering +\param MaxReorderDelay max time to wait in ms before releasing first packet in reoderer when only one packet is present. +If 0 and reordering size is specified, defaults to 200 ms (usually enough). + +\param local_interface_ip local interface address to use for multicast. If NULL, default address is used +\return error if any */ GF_Err gf_rtp_initialize(GF_RTPChannel *ch, u32 UDPBufferSize, Bool IsSource, u32 PathMTU, u32 ReorederingSize, u32 MaxReorderDelay, char *local_interface_ip); -/* stop the RTP channel: destrpys RTP and RTCP sockets as well as packet reorderer*/ +/*! stops the RTP channel. This destrpys RTP and RTCP sockets as well as packet reorderer +\param ch the target RTP channel +\return error if any +*/ GF_Err gf_rtp_stop(GF_RTPChannel *ch); -/*init the RTP info after a PLAY or PAUSE, rtp_time is the rtp TimeStamp of the RTP packet +/*! inits the RTP info after a PLAY or PAUSE, rtp_time is the rtp TimeStamp of the RTP packet with seq_num sequence number. This info is needed to compute the CurrentTime of the RTP channel -ssrc may not be known if sender hasn't indicated it (use 0 then)*/ +ssrc may not be known if sender hasn't indicated it (use 0 then) +\param ch the target RTP channel +\param seq_num the seq num of the next packet to be received +\param rtp_time the time in RTP timescale of the next packet to be received +\param ssrc the SSRC identifier of the next packet to be received +\return error if any +*/ GF_Err gf_rtp_set_info_rtp(GF_RTPChannel *ch, u32 seq_num, u32 rtp_time, u32 ssrc); -/*retrieve current RTP time in sec. If rtp_time was unknown (not on demand media) the time is absolute. +/*! retrieves current RTP time in sec. If rtp_time was unknown (not on demand media) the time is absolute. Otherwise this is the time in ms elapsed since the last PLAY range start value -Not supported yet if played without RTSP (aka RTCP time not supported)*/ +Not supported yet if played without RTSP (aka RTCP time not supported) +\param ch the target RTP channel +\return NTP clock in seconds +*/ Double gf_rtp_get_current_time(GF_RTPChannel *ch); - +/*! resets both sockets and packet reorderer +\param ch the target RTP channel +*/ void gf_rtp_reset_buffers(GF_RTPChannel *ch); -/*read any data on UDP only (not valid for TCP). Performs re-ordering if configured for it -returns amount of data read (raw UDP packet size)*/ -u32 gf_rtp_read_rtp(GF_RTPChannel *ch, char *buffer, u32 buffer_size); -u32 gf_rtp_read_rtcp(GF_RTPChannel *ch, char *buffer, u32 buffer_size); +/*! resets sender SSRC +\param ch the target RTP channel +*/ +void gf_rtp_reset_ssrc(GF_RTPChannel *ch); + +/*! reads any RTP data on UDP only (not valid for TCP). Performs re-ordering if configured for it +\param ch the target RTP channel +\param buffer the buffer where to store the RTP packet +\param buffer_size the size of the buffer +\return amount of data read in bytes +*/ +u32 gf_rtp_read_rtp(GF_RTPChannel *ch, u8 *buffer, u32 buffer_size); + +/*! flushes any pending data in packet reorderer, but does not flush packet reorderer if reorderer timeout is not exceeded +\param ch the target RTP channel +\param buffer the buffer where to store the data +\param buffer_size the size of the buffer +\return amount of data read in bytes +*/ +u32 gf_rtp_flush_rtp(GF_RTPChannel *ch, u8 *buffer, u32 buffer_size); -/*decodes an RTP packet and gets the beginning of the RTP payload*/ -GF_Err gf_rtp_decode_rtp(GF_RTPChannel *ch, char *pck, u32 pck_size, GF_RTPHeader *rtp_hdr, u32 *PayloadStart); +/*! reads any RTCP data on UDP only (not valid for TCP). Performs re-ordering if configured for it +\param ch the target RTP channel +\param buffer the buffer where to store the RTCP packet +\param buffer_size the size of the buffer +\return amount of data read in bytes +*/ +u32 gf_rtp_read_rtcp(GF_RTPChannel *ch, u8 *buffer, u32 buffer_size); -/*decodes an RTCP packet and update timing info, send RR too*/ -GF_Err gf_rtp_decode_rtcp(GF_RTPChannel *ch, char *pck, u32 pck_size, Bool *has_sr); +/*! flushes any pending data in packet reorderer, and flushes packet reorderer if reorderer timeout is not exceeded. Typically called several times until returning 0. +\param ch the target RTP channel +\param buffer the buffer where to store the data +\param buffer_size the size of the buffer +\return amount of data read in bytes +*/ +u32 gf_rtp_read_flush(GF_RTPChannel *ch, u8 *buffer, u32 buffer_size); + +/*! decodes an RTP packet and gets the beginning of the RTP payload +\param ch the target RTP channel +\param pck the RTP packet buffer +\param pck_size the size of the RTP packet +\param rtp_hdr filled with decoded RTP header information +\param PayloadStart set to the offset in bytes of the start of the payload in the RTP packet +\return error if any +*/ +GF_Err gf_rtp_decode_rtp(GF_RTPChannel *ch, u8 *pck, u32 pck_size, GF_RTPHeader *rtp_hdr, u32 *PayloadStart); + +/*! decodes an RTCP packet and update timing info, send ReceiverReport too +\param ch the target RTP channel +\param pck the RTP packet buffer +\param pck_size the size of the RTP packet +\param has_sr set to GF_TRUE if the RTCP packet contained an SenderReport +\return error if any +*/ +GF_Err gf_rtp_decode_rtcp(GF_RTPChannel *ch, u8 *pck, u32 pck_size, Bool *has_sr); -/*computes and send Receiver report. If the channel is a TCP channel, you must specify +/*! computes and send Receiver report. +If the channel is a TCP channel, you must specify the callback function. NOTE: many RTP implementation do NOT process RTCP info received on TCP... the lib will decide whether the report shall be sent or not, therefore you should call -this function at regular times*/ -GF_Err gf_rtp_send_rtcp_report(GF_RTPChannel *ch, - GF_Err (*RTP_TCPCallback)(void *cbk, char *pck, u32 pck_size), - void *rtsp_cbk); +this function at regular times +\param ch the target RTP channel +\return error if any +*/ +GF_Err gf_rtp_send_rtcp_report(GF_RTPChannel *ch); -/*send a BYE info (leaving the session)*/ -GF_Err gf_rtp_send_bye(GF_RTPChannel *ch, - GF_Err (*RTP_TCPCallback)(void *cbk, char *pck, u32 pck_size), - void *rtsp_cbk); +/*! sends a BYE info (leaving the session) +\param ch the target RTP channel +\return error if any +*/ +GF_Err gf_rtp_send_bye(GF_RTPChannel *ch); -/*send RTP packet. In fast_send mode, user passes a pck pointer with 12 bytes available BEFORE pck to -write the header in place*/ -GF_Err gf_rtp_send_packet(GF_RTPChannel *ch, GF_RTPHeader *rtp_hdr, char *pck, u32 pck_size, Bool fast_send); +/*! sends an RTP packet. In fast_send mode, +\param ch the target RTP channel +\param rtp_hdr the RTP header of the packet +\param pck the RTP payload buffer +\param pck_size the RTP payload size +\param fast_send if set, the payload buffer contains 12 bytes available BEFORE its indicated start where the RTP header is written in place +\return error if any +*/ +GF_Err gf_rtp_send_packet(GF_RTPChannel *ch, GF_RTPHeader *rtp_hdr, u8 *pck, u32 pck_size, Bool fast_send); -enum -{ - GF_RTCP_INFO_NAME = 0, - GF_RTCP_INFO_EMAIL, - GF_RTCP_INFO_PHONE, - GF_RTCP_INFO_LOCATION, - GF_RTCP_INFO_TOOL, - GF_RTCP_INFO_NOTE, - GF_RTCP_INFO_PRIV -}; -/*sets RTCP info sent in RTCP reports. info_string shall NOT exceed 255 chars*/ -GF_Err gf_rtp_set_info_rtcp(GF_RTPChannel *ch, u32 InfoCode, char *info_string); +/*! callback used for writing rtp over TCP +\param cbk1 opaque user data +\param cbk2 opaque user data +\param is_rtcp indicates the data is an RTCP packet +\param pck the RTP/RTCP buffer +\param pck_size the RTP/RTCP size +\return error if any +*/ +typedef GF_Err (*gf_rtp_tcp_callback)(void *cbk1, void *cbk2, Bool is_rtcp, u8 *pck, u32 pck_size); + +/*! sets RTP interleaved callback on the RTP channel +\param ch the target RTP channel +\param tcp_callback the callback function +\param cbk1 user data for the callback function +\param cbk2 user data for the callback function +\return error if any +*/ +GF_Err gf_rtp_set_interleave_callbacks(GF_RTPChannel *ch, gf_rtp_tcp_callback tcp_callback, void *cbk1, void *cbk2); +/*! checks if an RTP channel is unicast +\param ch the target RTP channel +\return GF_TRUE if unicast, GF_FALSE otherwise +*/ u32 gf_rtp_is_unicast(GF_RTPChannel *ch); +/*! checks if an RTP channel is interleaved +\param ch the target RTP channel +\return GF_TRUE if interleaved, GF_FALSE otherwise +*/ u32 gf_rtp_is_interleaved(GF_RTPChannel *ch); +/*! gets clockrate/timescale of an RTP channel +\param ch the target RTP channel +\return the RTP clock rate +*/ u32 gf_rtp_get_clockrate(GF_RTPChannel *ch); -u32 gf_rtp_is_active(GF_RTPChannel *ch); +/*! gets the low interleave ID of an RTP channel +\param ch the target RTP channel +\return the low (RTP) interleave ID of the channel, or 0 if not interleaved +*/ u8 gf_rtp_get_low_interleave_id(GF_RTPChannel *ch); +/*! gets the high interleave ID of an RTP channel +\param ch the target RTP channel +\return the high (RTCP) interleave ID of the channel, or 0 if not interleaved +*/ u8 gf_rtp_get_hight_interleave_id(GF_RTPChannel *ch); -GF_RTSPTransport *gf_rtp_get_transport(GF_RTPChannel *ch); -u32 gf_rtp_get_local_ssrc(GF_RTPChannel *ch); +/*! gets the transport associated with an RTP channel +\param ch the target RTP channel +\return the RTSP transport information +*/ +const GF_RTSPTransport *gf_rtp_get_transport(GF_RTPChannel *ch); +/*! gets loss rate of the RTP channel +\param ch the target RTP channel +\return the loss rate of the channel, between 0 and 100 % +*/ Float gf_rtp_get_loss(GF_RTPChannel *ch); +/*! gets number of TCP bytes send for an interleaved channel +\param ch the target RTP channel +\return the number of bytes sent +*/ u32 gf_rtp_get_tcp_bytes_sent(GF_RTPChannel *ch); +/*! gets ports of an non-interleaved RTP channel +\param ch the target RTP channel +\param rtp_port the RTP port number +\param rtcp_port the RCTP port number +*/ void gf_rtp_get_ports(GF_RTPChannel *ch, u16 *rtp_port, u16 *rtcp_port); - - /**************************************************************************** SDP LIBRARY EXPORTS @@ -691,13 +937,10 @@ void gf_rtp_get_ports(GF_RTPChannel *ch, u16 *rtp_port, u16 *rtcp_port); Some quick constructors are available for GF_SDPConnection and GF_SDPMedia in order to set up some specific parameters to their default value - - An extra function gf_sdp_info_check(GF_SDPInfo *sdp) is provided for compliency check - with RFC2327: all requested fields are checked as well as conflicting information forbidden - in RFC 2327 ****************************************************************************/ -/* +/*! Extension Attribute + All attributes x-ZZZZ are considered as extensions attributes. If no "x-" is found the attributes in the RTSP response is SKIPPED. The "x-" radical is removed in the structure when parsing commands / responses @@ -709,9 +952,7 @@ typedef struct } GF_X_Attribute; -/* - Structure for bandwidth info -*/ +/*! SDP bandwidth info*/ typedef struct { /*"CT", "AS" are defined. Private extensions must be "X-*" ( * "are recommended to be short")*/ @@ -720,13 +961,12 @@ typedef struct u32 value; } GF_SDPBandwidth; -/* - Structure for Time info -*/ -/*we do not support more than ... time offsets / zone adjustment -if more are needed, RFC recommends to use several entries rather than a big*/ +/*! SDP maximum time offset +We do not support more than this offset / zone adjustment +if more are needed, RFC recommends to use several entries rather than a big offset*/ #define GF_SDP_MAX_TIMEOFFSET 10 +/*! SDP Timing information*/ typedef struct { /*NPT time in sec*/ @@ -770,7 +1010,7 @@ typedef struct u32 NbZoneOffsets; } GF_SDPTiming; - +/*! SDP Connection information*/ typedef struct { /*only "IN" currently defined*/ @@ -787,8 +1027,9 @@ typedef struct u32 add_count; } GF_SDPConnection; -/* - FMTP: description of dynamic payload types. This is opaque at the SDP level. +/*! SDP FormatTypePayload + + description of dynamic payload types. This is opaque at the SDP level. Each attributes is assumed to be formatted as If not the case the attribute will have an empty value string and only the parameter name. @@ -801,6 +1042,7 @@ typedef struct GF_List *Attributes; } GF_SDP_FMTP; +/*! SDP Media information*/ typedef struct { /*m= @@ -856,6 +1098,7 @@ typedef struct s32 Quality; } GF_SDPMedia; +/*! SDP information*/ typedef struct { /*v=*/ @@ -903,40 +1146,57 @@ typedef struct /* +*/ +/*! creates a new SDP info +\return the newly allocated SDP +*/ +GF_SDPInfo *gf_sdp_info_new(); +/*! destrucs an SDP info Memory Consideration: the destructors free all non-NULL string. You should therefore be carefull while (de-)assigning the strings. The function gf_sdp_info_parse() performs a complete reset of the GF_SDPInfo + +\param sdp the target SDP to destroy */ -/*constructor*/ -GF_SDPInfo *gf_sdp_info_new(); -/*destructor*/ void gf_sdp_info_del(GF_SDPInfo *sdp); -/*reset all structures (destroys substructure too)*/ +/*! resets all structures and destroys substructures too +\param sdp the target SDP to destroy +*/ void gf_sdp_info_reset(GF_SDPInfo *sdp); -/*Parses a memory SDP buffer*/ +/*! parses a memory SDP buffer +\param sdp the target SDP to destroy +\param sdp_text the encoded SDP buffer +\param text_size sizeo if the encoded SDP buffer +\return error if any +*/ GF_Err gf_sdp_info_parse(GF_SDPInfo *sdp, char *sdp_text, u32 text_size); -/*check the consistency of the GF_SDPInfo*/ -GF_Err gf_sdp_info_check(GF_SDPInfo *sdp); -/*write the SDP to a new buffer and returns it. Automatically checks the SDP before calling*/ -GF_Err gf_sdp_info_write(GF_SDPInfo *sdp, char **out_str_buf); -/* - Const/dest for GF_SDPMedia +/*! creates a new SDP media +\return the newly allocated SDP media */ GF_SDPMedia *gf_sdp_media_new(); +/*! destroys an SDP media +\param media the target SDP media +*/ void gf_sdp_media_del(GF_SDPMedia *media); -/* - Const/dest for GF_SDPConnection +/*! creates a new SDP connection +\return the newly allocated SDP connection */ GF_SDPConnection *gf_sdp_conn_new(); +/*! destroys an SDP connection +\param conn the target SDP connection +*/ void gf_sdp_conn_del(GF_SDPConnection *conn); -/* - Const/dest for SDP FMTP +/*! creates a new SDP payload +\return the newly allocated SDP payload */ GF_SDP_FMTP *gf_sdp_fmtp_new(); +/*! destroys an SDP payload +\param fmtp the target SDP payload +*/ void gf_sdp_fmtp_del(GF_SDP_FMTP *fmtp); @@ -945,8 +1205,7 @@ void gf_sdp_fmtp_del(GF_SDP_FMTP *fmtp); RTP packetizer */ - -/*RTP<->SL mapping*/ +/*! Mapping between RTP and GPAC / MPEG-4 Systems SyncLayer */ typedef struct { /*1 - required options*/ @@ -955,7 +1214,7 @@ typedef struct char mode[30]; /*config of the stream if carried in SDP*/ - char *config; + u8 *config; u32 configSize; /* Stream Type*/ u8 StreamType; @@ -964,7 +1223,7 @@ typedef struct /*rvc config of the stream if carried in SDP*/ u16 rvc_predef; - char *rvc_config; + u8 *rvc_config; u32 rvc_config_size; /*2 - optional options*/ @@ -974,8 +1233,8 @@ typedef struct /*duration of AUs if constant, in RTP timescale*/ u32 ConstantDuration; - /* Object Type Indication */ - u8 ObjectTypeIndication; + /* CodecID */ + u32 CodecID; /*audio max displacement when interleaving (eg, de-interleaving window buffer max length) in RTP timescale*/ u32 maxDisplacement; /*de-interleaveBufferSize if not recomputable from maxDisplacement*/ @@ -1008,11 +1267,12 @@ typedef struct /*internal stuff*/ /*len of first AU header in an RTP payload*/ u32 auh_first_min_len; + /*len of non-first AU header in an RTP payload*/ u32 auh_min_len; } GP_RTPSLMap; -/*packetizer config flags - some flags are dynamically re-assigned when detecting multiSL / B-Frames / ...*/ +/*! packetizer config flags - some flags are dynamically re-assigned when detecting multiSL / B-Frames / ...*/ enum { /*forces MPEG-4 generic transport if MPEG-4 systems mapping is available*/ @@ -1059,19 +1319,66 @@ enum Generic packetization tools - used by track hinters and future live tools */ -/*currently supported payload types*/ +/*! Supported payload types*/ enum { - /*not defined*/ - GF_RTP_PAYT_UNKNOWN, - /*use generic MPEG-4 transport - RFC 3016 and RFC 3640*/ - GF_RTP_PAYT_MPEG4, - /*use generic MPEG-1/2 video transport - RFC 2250*/ - GF_RTP_PAYT_MPEG12_VIDEO, + /*assigned payload types*/ + /*cf RFC 3551*/ + GF_RTP_PAYT_PCMU = 0, + /*cf RFC 3551*/ + GF_RTP_PAYT_GSM, + /*cf RFC 3551*/ + GF_RTP_PAYT_G723, + /*cf RFC 3551*/ + GF_RTP_PAYT_DVI4_8K, + /*cf RFC 3551*/ + GF_RTP_PAYT_DVI4_16K, + /*cf RFC 3551*/ + GF_RTP_PAYT_LPC, + /*cf RFC 3551*/ + GF_RTP_PAYT_PCMA, + /*cf RFC 3551*/ + GF_RTP_PAYT_G722, + /*cf RFC 3551*/ + GF_RTP_PAYT_L16_STEREO, + /*cf RFC 3551*/ + GF_RTP_PAYT_L16_MONO, + /*cf RFC 3551*/ + GF_RTP_PAYT_QCELP_BASIC, + /*cf RFC 3389*/ + GF_RTP_PAYT_CN, /*use generic MPEG-1/2 audio transport - RFC 2250*/ GF_RTP_PAYT_MPEG12_AUDIO, + /*cf RFC 3551*/ + GF_RTP_PAYT_G728, + GF_RTP_PAYT_DVI4_11K, + GF_RTP_PAYT_DVI4_22K, + /*cf RFC 3551*/ + GF_RTP_PAYT_G729, + /*cf RFC 2029*/ + GF_RTP_PAYT_CelB = 25, + /*cf RFC 2435*/ + GF_RTP_PAYT_JPEG = 26, + /*cf RFC 3551*/ + GF_RTP_PAYT_nv = 28, + /*cf RFC 4587*/ + GF_RTP_PAYT_H261 = 31, + /* generic MPEG-1/2 video transport - RFC 2250*/ + GF_RTP_PAYT_MPEG12_VIDEO = 32, + /*MPEG-2 TS - RFC 2250*/ + GF_RTP_PAYT_MP2T = 33, /*use H263 transport - RFC 2429*/ - GF_RTP_PAYT_H263, + GF_RTP_PAYT_H263 = 34, + + GF_RTP_PAYT_LAST_STATIC_DEFINED = 35, + + /*not defined*/ + GF_RTP_PAYT_UNKNOWN = 128, + + /*internal types for all dynamic payloads*/ + + /*use generic MPEG-4 transport - RFC 3016 and RFC 3640*/ + GF_RTP_PAYT_MPEG4, /*use AMR transport - RFC 3267*/ GF_RTP_PAYT_AMR, /*use AMR-WB transport - RFC 3267*/ @@ -1086,15 +1393,17 @@ enum GF_RTP_PAYT_H264_AVC, /*use LATM for AAC-LC*/ GF_RTP_PAYT_LATM, - /*use 3GPP DIMS format*/ - GF_RTP_PAYT_3GPP_DIMS, /*use AC3 audio format*/ GF_RTP_PAYT_AC3, /*use H264-SVC transport*/ GF_RTP_PAYT_H264_SVC, /*use HEVC/H265 transport - no RFC yet, only draft*/ GF_RTP_PAYT_HEVC, - GF_RTP_PAYT_LHVC + GF_RTP_PAYT_LHVC, +#if GPAC_ENABLE_3GPP_DIMS_RTP + /*use 3GPP DIMS format*/ + GF_RTP_PAYT_3GPP_DIMS, +#endif }; @@ -1104,117 +1413,26 @@ enum */ - -/* - RTP -> SL packetization tool - You should ONLY modify the GF_SLHeader while packetizing, all the rest is private - to the tool. - Also note that AU start/end is automatically updated, therefore you should only - set CTS-DTS-OCR-sequenceNumber (which is automatically incremented when splitting a payload) - -padding-idle infos - SL flags are computed on the fly, but you may wish to modify them in case of - packet drop/... at the encoder side - -*/ -struct __tag_rtp_packetizer -{ - /*input packet sl header cfg. modify only if needed*/ - GF_SLHeader sl_header; - - /* - - PRIVATE _ DO NOT TOUCH - */ - - /*RTP payload type (RFC type, NOT the RTP hdr payT)*/ - u32 rtp_payt; - /*packetization flags*/ - u32 flags; - /*Path MTU size without 12-bytes RTP header*/ - u32 Path_MTU; - /*max packet duration in RTP TS*/ - u32 max_ptime; - - /*payload type of RTP packets - only one payload type can be used in GPAC*/ - u8 PayloadType; - - /*RTP header of current packet*/ - GF_RTPHeader rtp_header; - - /*RTP packet handling callbacks*/ - void (*OnNewPacket)(void *cbk_obj, GF_RTPHeader *header); - void (*OnPacketDone)(void *cbk_obj, GF_RTPHeader *header); - void (*OnDataReference)(void *cbk_obj, u32 payload_size, u32 offset_from_orig); - void (*OnData)(void *cbk_obj, char *data, u32 data_size, Bool is_header); - void *cbk_obj; - - /********************************* - MPEG-4 Generic hinting - *********************************/ - - /*SL to RTP map*/ - GP_RTPSLMap slMap; - /*SL conf and state*/ - GF_SLConfig sl_config; - - /*set to 1 if firstSL in RTP packet*/ - Bool first_sl_in_rtp; - Bool has_AU_header; - /*current info writers*/ - GF_BitStream *pck_hdr, *payload; - - /*AU SN of last au*/ - u32 last_au_sn; - - /*info for the current packet*/ - u32 auh_size, bytesInPacket; - - /********************************* - ISMACryp info - *********************************/ - Bool force_flush, is_encrypted; - u64 IV, first_AU_IV; - char *key_indicator; - - /********************************* - AVC-H264 info - *********************************/ - /*AVC non-IDR flag: set if all NAL in current packet are non-IDR (disposable)*/ - Bool avc_non_idr; - - /********************************* - AC3 info - *********************************/ - /*ac3 ft flags*/ - u8 ac3_ft; - - /********************************* - HEVC-H265 info - *********************************/ - /*HEVC Payload Header. It will be use in case of Aggreation Packet where we must add payload header for packet after having added of NALU to AP*/ - char hevc_payload_hdr[2]; -}; - -/*generic rtp builder (packetizer)*/ +/*! RTP builder (packetizer)*/ typedef struct __tag_rtp_packetizer GP_RTPPacketizer; -/*creates a new builder - @rtp_payt: rtp payload format, one of the above GF_RTP_PAYT_* - @flags: packetizer flags, one of the above GP_RTP_PCK_* - @slc: user-given SL config to use. If none specified, default RFC config is used - @cbk_obj: callback object passed back in functions - @OnNewPacket: callback function starting new RTP packet - @header: rtp header for new packet - note that RTP header flags are not used until PacketDone is called - @OnPacketDone: callback function closing current RTP packet - @header: final rtp header for packet - @OnDataReference: optional, to call each time data from input buffer is added to current RTP packet. +/*! creates a new builder +\param rtp_payt rtp payload format, one of the above GF_RTP_PAYT_* +\param slc user-given SL config to use. If none specified, default RFC config is used +\param flags packetizer flags, one of the above GP_RTP_PCK_* +\param cbk_obj callback object passed back in functions +\param OnNewPacket callback function starting new RTP packet + header: rtp header for new packet - note that RTP header flags are not used until PacketDone is called +\param OnPacketDone callback function closing current RTP packet + header: final rtp header for packet +\param OnDataReference optional, to call each time data from input buffer is added to current RTP packet. If not set, data must be added through OnData - @payload_size: size of reference data - @offset_from_orig: start offset in input buffer - @OnData: to call each time data is added to current RTP packet (either extra data from payload or + payload_size: size of reference data + offset_from_orig: start offset in input buffer +\param OnData to call each time data is added to current RTP packet (either extra data from payload or data from input when not using referencing) - @is_head: signal the data added MUST be inserted at the beginning of the payload. Otherwise data - is concatenated as received + is_head: signal the data added MUST be inserted at the beginning of the payload. Otherwise data is concatenated as received +\return a new RTP packetizer object */ GP_RTPPacketizer *gf_rtp_builder_new(u32 rtp_payt, GF_SLConfig *slc, @@ -1223,23 +1441,27 @@ GP_RTPPacketizer *gf_rtp_builder_new(u32 rtp_payt, void (*OnNewPacket)(void *cbk, GF_RTPHeader *header), void (*OnPacketDone)(void *cbk, GF_RTPHeader *header), void (*OnDataReference)(void *cbk, u32 payload_size, u32 offset_from_orig), - void (*OnData)(void *cbk, char *data, u32 data_size, Bool is_head) + void (*OnData)(void *cbk, u8 *data, u32 data_size, Bool is_head) ); -/*destroy builder*/ +/*! destroys an RTP packetizer +\param builder the target RTP packetizer +*/ void gf_rtp_builder_del(GP_RTPPacketizer *builder); -/* - init the builder - @MaxPayloadSize: maximum payload size of RTP packets (eg MTU minus IP/UDP/RTP headers) - @max_ptime: maximum packet duration IN RTP TIMESCALE - @StreamType: MPEG-4 system stream type - MUST always be provided for payloads format specifying +/*! inits the RTP packetizer +\param builder the target RTP packetizer +\param PayloadType the payload type to use +\param MaxPayloadSize maximum payload size of RTP packets (eg MTU minus IP/UDP/RTP headers) +\param max_ptime maximum packet duration IN RTP TIMESCALE +\param StreamType MPEG-4 system stream type - MUST always be provided for payloads format specifying audio or video streams - @OTI : MPEG-4 system objectTypeIndication - may be 0 if stream is not mpeg4 systems +\param codecid ID of the media codec +\param PL_ID profile and level identifier for the stream - *** all other params are for MultiSL draft *** + *** all other params are for RFC 3640 *** - @avgSize: average size of an AU. This is not always known (real-time encoding). +\param avgSize average size of an AU. This is not always known (real-time encoding). In this case you should specify a rough compute indicating how many packets could be stored per RTP packet. for ex AAC stereo at 44100 k / 64kbps , one AU ~= 380 bytes so 3 AUs for 1500 MTU is ok - BE CAREFULL: MultiSL adds some SL info on top of the 12 @@ -1250,42 +1472,58 @@ will still be worth it. -Nota: at init, the packetizer can decide to switch to SingleSL if the average size specified is too close to the PathMTU - @maxSize: max size of an AU. If unknown (real-time) set to 0 - @avgTS: average CTS progression (1000/FPS for video) - @maxDTS: maximum DTS offset in case of bidirectional coding. - @IV_length: size (in bytes) of IV when ISMACrypted - @KI_length: size (in bytes) of key indicator when ISMACrypted - @pref_mode: MPEG-4 generic only, specifies the payload mode - can be NULL (mode generic) +\param maxSize max size of an AU. If unknown (real-time) set to 0 +\param avgTS average CTS progression (1000/FPS for video) +\param maxDTS maximum DTS offset in case of bidirectional coding. +\param IV_length size (in bytes) of IV when ISMACrypted +\param KI_length size (in bytes) of key indicator when ISMACrypted +\param pref_mode MPEG-4 generic only, specifies the payload mode - can be NULL (mode generic) */ - void gf_rtp_builder_init(GP_RTPPacketizer *builder, u8 PayloadType, u32 MaxPayloadSize, u32 max_ptime, - u32 StreamType, u32 OTI, u32 PL_ID, + u32 StreamType, u32 codecid, u32 PL_ID, u32 avgSize, u32 maxSize, u32 avgTS, u32 maxDTS, u32 IV_length, u32 KI_length, char *pref_mode); -/*set frame crypto info*/ -void gp_rtp_builder_set_cryp_info(GP_RTPPacketizer *builder, u64 IV, char *key_indicator, Bool is_encrypted); -/*packetize input buffer -@data, @data_size: input buffer -@IsAUEnd: set to one if this buffer is the last of the AU -@FullAUSize: complete access unit size if known, 0 otherwise -@duration: sample duration in rtp timescale (only needed for 3GPP text streams) -@descIndex: sample description index (only needed for 3GPP text streams) +/*! sets frame crypto info (ISMA E&A) for frame starting in next RTP packet +\param builder the target RTP packetizer +\param IV initialization vector +\param key_indicator key indicator +\param is_encrypted encrypted flag */ -GF_Err gf_rtp_builder_process(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration, u8 descIndex); - -/*format the "fmtp: " attribute for the MPEG-4 generic packetizer. sdpline shall be at least 2000 char*/ -GF_Err gf_rtp_builder_format_sdp(GP_RTPPacketizer *builder, char *payload_name, char *sdpLine, char *dsi, u32 dsi_size); -/*formats SDP payload name and media name - both MUST be at least 20 bytes*/ -Bool gf_rtp_builder_get_payload_name(GP_RTPPacketizer *builder, char *szPayloadName, char *szMediaName); - - - +void gf_rtp_builder_set_cryp_info(GP_RTPPacketizer *builder, u64 IV, char *key_indicator, Bool is_encrypted); +/*! packetizes input buffer +\param builder the target RTP packetizer +\param data input buffer +\param data_size input buffer size +\param IsAUEnd set to one if this buffer is the last of the AU +\param FullAUSize complete access unit size if known, 0 otherwise +\param duration sample duration in rtp timescale (mostly needed for 3GPP text streams) +\param descIndex sample description index (mostly needed for 3GPP text streams) +\return error if any +*/ +GF_Err gf_rtp_builder_process(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration, u8 descIndex); + +/*! formats the "fmtp: " attribute for the MPEG-4 generic packetizer. sdpline shall be at least 2000 char +\param builder the target RTP packetizer +\param payload_name name of the payload to use (profile of RFC 3640) +\param sdp_line SDP line buffer to fill +\param dsi decoder config of stream if any, or NULL +\param dsi_size size of the decoder config +\return error if any +*/ +GF_Err gf_rtp_builder_format_sdp(GP_RTPPacketizer *builder, char *payload_name, char *sdp_line, char *dsi, u32 dsi_size); +/*! formats SDP payload name and media name +\param builder the target RTP packetizer +\param payload_name the buffer to fill with the payload name +\param media_name the buffer to fill with the payload name +\return GF_TRUE if success, GF_FALSE otherwise +*/ +Bool gf_rtp_builder_get_payload_name(GP_RTPPacketizer *builder, char payload_name[20], char media_name[20]); -/*rtp payload flags*/ +/*! rtp payload flags*/ enum { /*AU end was detected (eg next packet is AU start)*/ @@ -1294,8 +1532,6 @@ enum GF_RTP_AMR_ALIGN = (1<<1), /*for RFC3016, signals bitstream inspection for RAP discovery*/ GF_RTP_M4V_CHECK_RAP = (1<<2), - /*flag set when unreliable usage of the M bit is detected*/ - GF_RTP_UNRELIABLE_M = (1<<3), /*AWFULL hack at rtp level to cope with ffmpeg h264 crashes when jumping in stream without IDR*/ GF_RTP_AVC_WAIT_RAP = (1<<4), @@ -1307,55 +1543,53 @@ enum GF_RTP_AVC_USE_ANNEX_B = (1<<8) }; -/* - SL -> RTP packetization tool - -*/ -struct __tag_rtp_depacketizer -{ - /*depacketize routine*/ - void (*depacketize)(struct __tag_rtp_depacketizer *rtp, GF_RTPHeader *hdr, char *payload, u32 size); - - /*output packet sl header cfg*/ - GF_SLHeader sl_hdr; - - /*RTP payload type (RFC type, NOT the RTP hdr payT)*/ - u32 payt; - /*depacketization flags*/ - u32 flags; - - /*callback routine*/ - void (*on_sl_packet)(void *udta, char *payload, u32 size, GF_SLHeader *hdr, GF_Err e); - void *udta; - - /*SL <-> RTP map*/ - GP_RTPSLMap sl_map; +/*! Static RTP map definition, for static payload types*/ +typedef struct rtp_static_payt { + u32 fmt; u32 clock_rate; + u32 stream_type; + u32 codec_id; + const char *mime; +} GF_RTPStaticMap; + + +/*! RTP depacketizer callback +\param udta opaque user data +\param payload depacketized payload data +\param size payload size +\param hdr MPEG-4 Sync Layer structure for the payload (with AU start/end flag, timestamps, etc) +\param e error if any +*/ +typedef void (*gf_rtp_packet_cbk)(void *udta, u8 *payload, u32 size, GF_SLHeader *hdr, GF_Err e); - /*inter-packet reconstruction bitstream (for 3GP text and H264)*/ - GF_BitStream *inter_bs; - - /*H264/AVC config*/ - u32 h264_pck_mode; - - /*3GP text reassembler state*/ - u8 nb_txt_frag, cur_txt_frag, sidx, txt_len, nb_mod_frag; - /*ISMACryp*/ - u32 isma_scheme; - char *key; -}; -/*generic rtp builder (packetizer)*/ +/*! RTP parser (depacketizer)*/ typedef struct __tag_rtp_depacketizer GF_RTPDepacketizer; -GF_RTPDepacketizer *gf_rtp_depacketizer_new(GF_SDPMedia *media, void (*sl_packet_cbk)(void *udta, char *payload, u32 size, GF_SLHeader *hdr, GF_Err e), void *udta); -void gf_rtp_depacketizer_del(GF_RTPDepacketizer *rtp); -void gf_rtp_depacketizer_reset(GF_RTPDepacketizer *rtp, Bool full_reset); -void gf_rtp_depacketizer_process(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload, u32 size); - -void gf_rtp_depacketizer_get_slconfig(GF_RTPDepacketizer *rtp, GF_SLConfig *sl); - +/*! creates a new depacketizer +\param media the SDP media structure describing the RTP stream - can be NULL for static payload types +\param hdr_payt the static RTP payload type when no SDP is used +\param sl_packet_cbk callback function of the depacketizer to retrieves payload +\param udta opaque data for the callback function +\return a newly allocated RTP depacketizer, or NULL if not supported*/ +GF_RTPDepacketizer *gf_rtp_depacketizer_new(GF_SDPMedia *media, u32 hdr_payt, gf_rtp_packet_cbk sl_packet_cbk, void *udta); +/*! destroys an RTP depacketizer +\param rtpd the target RTP depacketizer +*/ +void gf_rtp_depacketizer_del(GF_RTPDepacketizer *rtpd); +/*! resets an RTP depacketizer, assuming next packet should be an AU start +\param rtpd the target RTP depacketizer +\param full_reset if set, completely reset the SL header +*/ +void gf_rtp_depacketizer_reset(GF_RTPDepacketizer *rtpd, Bool full_reset); +/*! process an RTP depacketizer +\param rtpd the target RTP depacketizer +\param hdr the RTP packet header +\param payload the RTP packet payload +\param size the RTP packet payload size +*/ +void gf_rtp_depacketizer_process(GF_RTPDepacketizer *rtpd, GF_RTPHeader *hdr, u8 *payload, u32 size); #endif /*GPAC_DISABLE_STREAMING*/ diff --git a/include/gpac/internal/avilib.h b/include/gpac/internal/avilib.h index 9c87440..accc4a8 100644 --- a/include/gpac/internal/avilib.h +++ b/include/gpac/internal/avilib.h @@ -234,6 +234,9 @@ typedef struct void* extradata; unsigned int extradata_size; + + // add a new riff chunk after this amount of bytes + u64 new_riff_threshold; } avi_t; #define AVI_MODE_WRITE 0 @@ -289,6 +292,8 @@ typedef struct #ifndef WAVE_FORMAT_PCM #define WAVE_FORMAT_PCM (0x0001) #endif +#endif + #define WAVE_FORMAT_ADPCM (0x0002) #define WAVE_FORMAT_IBM_CVSD (0x0005) #define WAVE_FORMAT_ALAW (0x0006) @@ -303,27 +308,20 @@ typedef struct #define IBM_FORMAT_MULAW (0x0101) #define IBM_FORMAT_ALAW (0x0102) #define IBM_FORMAT_ADPCM (0x0103) -#endif -avi_t* AVI_open_output_file(char * filename); +avi_t* AVI_open_output_file(char * filename, u64 opendml_threshold); void AVI_set_video(avi_t *AVI, int width, int height, double fps, char *compressor); void AVI_set_audio(avi_t *AVI, int channels, int rate, int bits, int format, int mp3rate); -int AVI_write_frame(avi_t *AVI, char *data, int bytes, int keyframe); -int AVI_dup_frame(avi_t *AVI); -int AVI_write_audio(avi_t *AVI, char *data, int bytes); -int AVI_append_audio(avi_t *AVI, char *data, int bytes); -u64 AVI_bytes_remain(avi_t *AVI); +int AVI_write_frame(avi_t *AVI, u8 *data, int bytes, int keyframe); +int AVI_write_audio(avi_t *AVI, u8 *data, int bytes); int AVI_close(avi_t *AVI); -u64 AVI_bytes_written(avi_t *AVI); avi_t *AVI_open_input_file(char *filename, int getIndex); avi_t *AVI_open_input_indexfile(char *filename, int getIndex, char *indexfile); -avi_t *AVI_open_fd(FILE *fd, int getIndex); avi_t *AVI_open_indexfd(FILE *fd, int getIndex, char *indexfile); int avi_parse_input_file(avi_t *AVI, int getIndex); int avi_parse_index_from_file(avi_t *AVI, char *filename); int AVI_audio_mp3rate(avi_t *AVI); -int AVI_audio_padrate(avi_t *AVI); int AVI_video_frames(avi_t *AVI); int AVI_video_width(avi_t *AVI); int AVI_video_height(avi_t *AVI); @@ -336,28 +334,18 @@ int AVI_audio_format(avi_t *AVI); int AVI_audio_rate(avi_t *AVI); u64 AVI_audio_bytes(avi_t *AVI); int AVI_audio_chunks(avi_t *AVI); -int AVI_can_read_audio(avi_t *AVI); - -int AVI_max_video_chunk(avi_t *AVI); int AVI_frame_size(avi_t *AVI, int frame); int AVI_audio_size(avi_t *AVI, int frame); -int AVI_seek_start(avi_t *AVI); -int AVI_set_video_position(avi_t *AVI, int frame); -u64 AVI_get_video_position(avi_t *AVI, int frame); -int AVI_read_frame(avi_t *AVI, char *vidbuf, int *keyframe); - -int AVI_set_audio_position(avi_t *AVI, int byte); -int AVI_set_audio_bitrate(avi_t *AVI, int bitrate); -int AVI_get_audio_position_index(avi_t *AVI); -int AVI_set_audio_position_index(avi_t *AVI, int indexpos); +u64 AVI_get_video_position(avi_t *AVI, int frame); +int AVI_read_frame(avi_t *AVI, u8 *vidbuf, int *keyframe); -int AVI_read_audio(avi_t *AVI, char *audbuf, int bytes, int *continuous); +int AVI_read_audio(avi_t *AVI, u8 *audbuf, int bytes, int *continuous); -int AVI_read_data(avi_t *AVI, char *vidbuf, int max_vidbuf, - char *audbuf, int max_audbuf, - int *len); +int AVI_seek_start(avi_t *AVI); +int AVI_set_video_position(avi_t *AVI, int frame); +int AVI_set_audio_position(avi_t *AVI, int byte); int AVI_scan(char *name); int AVI_dump(char *name, int mode); @@ -366,16 +354,12 @@ char *AVI_codec2str(short cc); int AVI_file_check(char *import_file); void AVI_info(avi_t *avifile); -u64 AVI_max_size(void); int avi_update_header(avi_t *AVI); int AVI_set_audio_track(avi_t *AVI, u32 track); int AVI_get_audio_track(avi_t *AVI); int AVI_audio_tracks(avi_t *AVI); -void AVI_set_audio_vbr(avi_t *AVI, int is_vbr); -int AVI_get_audio_vbr(avi_t *AVI); - void AVI_set_comment_fd(avi_t *AVI, int fd); int AVI_get_comment_fd(avi_t *AVI); diff --git a/include/gpac/internal/bifs_dev.h b/include/gpac/internal/bifs_dev.h index 308592f..5860a5f 100644 --- a/include/gpac/internal/bifs_dev.h +++ b/include/gpac/internal/bifs_dev.h @@ -97,6 +97,7 @@ struct __tag_bifs_dec BIFSStreamInfo *info; Bool UseName; + Bool has_conditionnals; GF_SceneGraph *scenegraph; /*modified during conditional execution / proto parsing*/ @@ -128,8 +129,6 @@ struct __tag_bifs_dec Bool is_com_dec; Double cts_offset; - char *extraction_path; - char *service_url; }; diff --git a/include/gpac/internal/camera.h b/include/gpac/internal/camera.h index 864bfda..589a6c4 100644 --- a/include/gpac/internal/camera.h +++ b/include/gpac/internal/camera.h @@ -99,7 +99,7 @@ typedef struct _camera u32 flags; /*viewport info*/ - GF_Rect vp; + GF_Rect vp, proj_vp; /*not always same as VP due to aspect ratio*/ Fixed width, height; Fixed z_near, z_far; @@ -173,12 +173,12 @@ void camera_invalidate(GF_Camera *cam); /*updates camera. user transform is only used in 2D to set global user zoom/pan/translate*/ void camera_update(GF_Camera *cam, GF_Matrix2D *user_transform, Bool center_coords); /*updates camera. user transform is only used in 2D to set global user zoom/pan/translate + stereo param*/ -void camera_update_stereo(GF_Camera *cam, GF_Matrix2D *user_transform, Bool center_coords, Fixed horizontal_shift, Fixed viewing_distance, Fixed viewing_distance_offset, u32 camera_layout); +void camera_update_stereo(GF_Camera *cam, GF_Matrix2D *user_transform, Bool center_coords, Fixed horizontal_shift, Fixed viewing_distance, Fixed viewing_distance_offset, u32 camlay); /*reset to last viewport*/ void camera_reset_viewpoint(GF_Camera *cam, Bool animate); /*move camera to given vp*/ void camera_move_to(GF_Camera *cam, SFVec3f pos, SFVec3f target, SFVec3f up); -Bool camera_animate(GF_Camera *cam); +Bool camera_animate(GF_Camera *cam, void *compositor); void camera_stop_anim(GF_Camera *cam); /*start jump mode*/ void camera_jump(GF_Camera *cam); diff --git a/include/gpac/internal/compositor_dev.h b/include/gpac/internal/compositor_dev.h index 313c62f..23d3a44 100644 --- a/include/gpac/internal/compositor_dev.h +++ b/include/gpac/internal/compositor_dev.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2018 * All rights reserved * * This file is part of GPAC / Scene Rendering sub-project @@ -34,9 +34,11 @@ extern "C" { #include /*bridge between the rendering engine and the systems media engine*/ #include +#include +#include /*raster2D API*/ -#include +#include /*font engine API*/ #include /*AV hardware API*/ @@ -48,6 +50,17 @@ extern "C" { #include #endif +#include + +#include +#include + +typedef struct _gf_scene GF_Scene; +typedef struct _gf_addon_media GF_AddonMedia; +typedef struct _object_clock GF_Clock; +//typedef struct _od_manager GF_ObjectManager; + +Bool gf_sc_send_event(GF_Compositor *compositor, GF_Event *evt); /*if defined, events are queued before being processed, otherwise they are handled whenever triggered*/ //#define GF_SR_EVENT_QUEUE @@ -126,23 +139,15 @@ typedef struct Bool npot_texture; Bool rect_texture; Bool point_sprite; - Bool vbo, pbo; + Bool vbo, pbo, fbo; Bool gles2_unpack; - u32 yuv_texture; Bool has_shaders; + Bool npot; s32 max_texture_size; } GLCaps; #endif -#if !defined(GPAC_DISABLE_3D) && !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES2) -# define OPENGL_RASTER -#else -# ifdef OPENGL_RASTER -# undef OPENGL_RASTER -# endif -#endif - #define DOUBLECLICK_TIME_MS 250 @@ -170,36 +175,55 @@ enum GF_SC_DEPTH_GL_NONE=0, GF_SC_DEPTH_GL_POINTS, GF_SC_DEPTH_GL_STRIPS, - GF_SC_DEPTH_GL_VBO, +}; + + +enum +{ + GF_SC_GLMODE_AUTO=0, + GF_SC_GLMODE_OFF, + GF_SC_GLMODE_HYBRID, + GF_SC_GLMODE_ON +}; + + +enum +{ + GF_SC_DRV_OFF=0, + GF_SC_DRV_ON, + GF_SC_DRV_AUTO, }; struct __tag_compositor { - /*the main user*/ - GF_User *user; - /*terminal - only used for InputSensor*/ - GF_Terminal *term; + u32 magic; //must be "comp" + void *magic_ptr; //must point to this structure + + u32 init_flags; + void *os_wnd; + + u32 drv; + GF_Err last_error; /*audio renderer*/ struct _audio_render *audio_renderer; /*video out*/ GF_VideoOutput *video_out; - /*2D rasterizer*/ - GF_Raster2D *rasterizer; - /*all textures (texture handlers)*/ - GF_List *video_listeners; - - /*visual rendering thread if used*/ - GF_Thread *VisualThread; - /*0: not init, 1: running, 2: exit requested, 3: done*/ - u32 video_th_state; + Bool softblt; + Bool discard_input_events; u32 video_th_id; /*compositor exclusive access to the scene and display*/ GF_Mutex *mx; + //args have been updated + Bool reload_config; + + //list of pids we need to monitor at each render pass. For now BIFS and OD only + GF_List *systems_pids; + /*list of modules containing hardcoded proto implementations*/ GF_List *proto_modules; @@ -209,8 +233,9 @@ struct __tag_compositor to draw them*/ GF_List *extra_scenes; - u32 inherit_type_3d; + Bool inherit_type_3d; + Bool force_late_frame_draw; /*all time nodes registered*/ GF_List *time_nodes; /*all textures (texture handlers)*/ @@ -226,13 +251,26 @@ struct __tag_compositor Bool video_setup_failed; - /*simulation frame rate*/ - Double frame_rate; + //dur config option + Double dur; + //simulation frame rate option + GF_Fraction fps; + //timescale option + u32 timescale; + //autofps option + Bool autofps; + //autofps option + Bool vfr; + Bool nojs; + Bool dyn_filter_mode; + Bool noback; + //frame duration in ms, used to match closest frame in input video streams + u32 frame_duration; + Bool frame_was_produced; Bool bench_mode; //0: no frame pending, 1: frame pending, needs clock increase, 2: frames are pending but one frame has been decoded, do not increase clock u32 force_bench_frame; - Bool no_regulation; - u32 frame_duration; + u32 frame_time[GF_SR_FPS_COMPUTE_SIZE]; u32 frame_dur[GF_SR_FPS_COMPUTE_SIZE]; u32 current_frame; @@ -241,11 +279,20 @@ struct __tag_compositor Bool text_edit_changed; //sampled value of audio clock used in bench mode only u32 scene_sampled_clock; + u32 last_frame_ts; + u32 last_click_time; s32 ms_until_next_frame; s32 frame_delay; - Bool video_frame_pending; Bool fullscreen_postponed; + Bool sys_frames_pending; + + Bool amc, async; + u32 asr, ach, alayout, afmt, asize, avol, apan, abuf; + Double max_aspeed, max_vspeed; + u32 buf, rbuf, mbuf, ntpsync; + + u32 ogl, mode2d; /*display size*/ u32 display_width, display_height; @@ -259,7 +306,7 @@ struct __tag_compositor u32 vp_x, vp_y, vp_width, vp_height; /*backbuffer size - in scalable mode, matches display size, otherwise matches scene size*/ u32 output_width, output_height; - Bool output_as_8bit; + Bool out8b; u8 multiview_mode; /*scene size if any*/ u32 scene_width, scene_height; @@ -271,6 +318,35 @@ struct __tag_compositor u32 force_next_frame_redraw; /*freeze_display prevents any screen updates - needed when output driver uses direct video memory access*/ Bool is_hidden, freeze_display; + Bool timed_nodes_valid; + //player option, by default disabled. In player mode the video driver is always loaded + //and no passthrough checks are done + u32 player; + //output pixel format option for passthrough mode, none by default + u32 opfmt; + //allocated framebuffer and size for passthrough mode + u8 *framebuffer; + u32 framebuffer_size, framebuffer_alloc; + + //passthrough texture object - only assigned by background2D + struct _gf_sc_texture_handler *passthrough_txh; + //passthrough packet - this is only created if the associated input packet doesn't use frame interface + //the packet might be using the same buffer as the input data + //otherwise, the resulting packet needs to be created from the framebuffer + GF_FilterPacket *passthrough_pck; + //data associated with the passthrough output - can be the same pointer as input packet or a clone + u8 *passthrough_data; + //timescale of the passthrough pid + u32 passthrough_timescale; + //pixel format of the passthrough pid at last emitted frame + u32 passthrough_pfmt; + //set if inplace processing is used: + //- matching size and pixel format for input packet and output framebuffer + //- inplace data processing (will skip background texture blit) + Bool passthrough_inplace; + //set to true if passthrough object is buffering, in whcih case scene clock has to be updated + //once buffering is done + Bool passthrough_check_buffer; //debug non-immediate mode ny erasing the parts that would have been drawn Bool debug_defer; @@ -282,6 +358,9 @@ struct __tag_compositor /*count number of initialized sensors*/ u32 interaction_sensors; + u32 check_eos_state; + u32 last_check_pass; + /*set whenever 3D HW ctx changes (need to rebuild dlists/textures if any used)*/ u32 reset_graphics; @@ -292,23 +371,25 @@ struct __tag_compositor s32 fonts_pending; /*options*/ - u32 aspect_ratio, antiAlias, texture_text_mode; - Bool high_speed, stress_mode; + u32 aspect_ratio, aa, textxt; + Bool fast, stress; Bool is_opengl; Bool autoconfig_opengl; u32 force_opengl_2d; -#ifdef OPENGL_RASTER - Bool opengl_raster; -#endif - + //in this mode all 2D raster is done through and RGBA canvas except background IO and textures which are done by the GPU. The canvas is then flushed to GPU. //the mode supports defer and immediate rendering Bool hybrid_opengl; + Bool fsize; + /*key modif*/ u32 key_states; u32 interaction_level; + //set whenever a scene has Layer3D or CompositeTexture3D + Bool needs_offscreen_gl; + /*size override when no size info is present flags: 1: size override is requested (cfg) 2: size override has been applied @@ -321,11 +402,10 @@ struct __tag_compositor u32 new_width, new_height; /*current background color*/ - u32 back_color, default_back_color; + u32 back_color, bc, ckey; /*bounding box draw type: none, unit box/rect and sphere (3D only)*/ - u32 draw_bvol; - + u32 bvol; /*list of system colors*/ u32 sys_colors[28]; @@ -345,6 +425,14 @@ struct __tag_compositor */ u32 recompute_ar; + /*to copy!*/ + u32 nbviews, stereo, camlay; + Bool rview; + Fixed dispdist; + char *mvshader; + + GF_PropVec2i osize, dpi; + Bool zoom_changed; /*traversing context*/ @@ -389,13 +477,13 @@ struct __tag_compositor Bool request_video_memory, was_system_memory; /*indicate if overlays were prezsent in the previous frame*/ Bool last_had_overlays; - GF_RasterCallback raster_callbacks; /*options*/ - Bool scalable_zoom; - Bool enable_yuv_hw; + Bool sz; + + Bool yuvhw; /*disables partial hardware blit (eg during dirty rect) to avoid artefacts*/ - Bool disable_partial_hw_blit; + Bool blitp; /*user navigation mode*/ u32 navigate_mode; @@ -422,8 +510,12 @@ struct __tag_compositor /*user rotation angle - ALWAYS CENTERED*/ Fixed rotation; + u32 auto_rotate; + /*0: flush to be done - 1: flush can be skipped - 2: forces flush*/ u32 skip_flush; + Bool flush_pending; + #ifndef GPAC_DISABLE_SVG u32 num_clicks; #endif @@ -431,8 +523,8 @@ struct __tag_compositor /*a dedicated drawable for focus highlight */ struct _drawable *focus_highlight; /*highlight fill and stroke colors (ARGB)*/ - u32 highlight_fill, highlight_stroke; - Fixed highlight_stroke_width; + u32 hlfill, hlline; + Fixed hllinew; Bool disable_focus_highlight; /*picking info*/ @@ -489,33 +581,29 @@ struct __tag_compositor is applied to this texture, black stripes will appear on the borders. If not set video is written through glDrawPixels with bitmap (slow scaling), or converted to po2 texture*/ - Bool emul_pow2; + Bool epow2; /*use openGL for outline rather than vectorial ones*/ - Bool raster_outlines; + Bool linegl; /*disable RECT extensions (except for Bitmap...)*/ - Bool disable_rect_ext; + Bool rext; /*disable RECT extensions (except for Bitmap...)*/ - u32 draw_normals; + u32 norms; /*backface cull type: 0 off, 1: on, 2: on with transparency*/ - u32 backcull; + u32 bcull; /*polygon atialiasing*/ - Bool poly_aa; - /*disable gluScaleImage*/ - Bool disable_glu_scale; + Bool paa; /*wireframe/solid mode*/ - u32 wiremode; + u32 wire; /*collision detection mode*/ u32 collide_mode; /*gravity enabled*/ Bool gravity_on; /*AABB tree-based culling is disabled*/ - Bool disable_gl_cull; - /*YUV textures in OpenGL are disabled (soft YUV->RGB )*/ - Bool disable_yuvgl; + Bool cull; //use PBO to start pushing textures at the beginning of the render pass - Bool enable_pbo; + Bool pbo; - u32 default_navigation_mode; + u32 nav; /*unit box (1.0 size) and unit sphere (1.0 radius)*/ GF_Mesh *unit_bbox; @@ -528,11 +616,12 @@ struct __tag_compositor u32 offscreen_width, offscreen_height; #if !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) - Bool shader_only_mode; + u32 shader_mode_disabled; #endif + char *vertshader, *fragshader; //force video frame packing (0=no packing or GF_3D_STEREO_SIDE or GF_3D_STEREO_TOP) - u32 frame_packing; + u32 fpack; #ifdef GPAC_USE_TINYGL void *tgl_ctx; @@ -540,21 +629,33 @@ struct __tag_compositor Fixed depth_gl_scale, depth_gl_strips_filter; u32 depth_gl_type; - Fixed interoccular_distance; + Fixed iod; /*increase/decrease the standard interoccular offset by the specified distance in cm*/ Fixed interoccular_offset; /*specifies distance the camera focal point and the screen plane : <0 is behind the screen, >0 is in front*/ - Fixed focus_distance; + Fixed focdist; struct _gf_sc_texture_handler *hybgl_txh; GF_Mesh *hybgl_mesh; GF_Mesh *hybgl_mesh_background; Bool force_type_3d; - char *screen_buffer; + u8 *screen_buffer, *line_buffer; u32 screen_buffer_alloc_size; + + u32 tvtn, tvtt; + Bool tvtd, tvtf; + u32 vrhud_mode; + Fixed fov; + + //offscreen rendering + u32 fbo_tx_id, fbo_id, fbo_depth_id; + Bool external_tx_id; + #endif + Bool orientation_sensors_active; + Bool texture_from_decoder_memory; u32 networks_time; @@ -565,10 +666,11 @@ struct __tag_compositor u32 traverse_and_direct_draw_time; u32 indirect_draw_time; + #ifdef GF_SR_USE_VIDEO_CACHE /*video cache size / max size in kbytes*/ - u32 video_cache_current_size, video_cache_max_size; - u32 cache_scale, cache_tolerance; + u32 video_cache_current_size, vcsize; + u32 vcscale, vctol; /*sorted list (by cache priority) of cached groups - permanent for the lifetime of the scene/cache object*/ GF_List *cached_groups; /*list of groups being cached in one frame */ @@ -576,10 +678,47 @@ struct __tag_compositor #endif #ifdef GF_SR_USE_DEPTH - Bool auto_calibration; + Bool autocal; /*display depth in pixels - if -1, it is the height of the display area*/ - s32 display_depth; + s32 dispdepth; #endif + + Bool gazer_enabled, sgaze; + s32 gaze_x, gaze_y; + + Bool validator_mode; + + //moved from old GF_Terminal + struct _gf_scene *root_scene; + Bool drop; + Bool sclock; + u32 timeout; + u32 play_state; + Bool use_step_mode; + Bool reload_scene_size; + //associated filter, used to load input filters + GF_Filter *filter; +// GF_FilterSession *fsess; + GF_FilterPid *vout; + GF_FilterFrameInterface frame_ifce; + GF_VideoSurface fb; + + Bool dbgpvr; + Bool noaudio; + + /*all X3D key/mouse/string sensors*/ + GF_List *x3d_sensors; + /*all input stream decoders*/ + GF_List *input_streams; + + + /*special list used by nodes needing a call to RenderNode but not in the traverese scene graph + (VRML/MPEG-4 protos only). + For such nodes the traverse state will be NULL + This is only used by InputSensor node at the moment + */ + GF_List *nodes_pending; + GF_List *extensions, *unthreaded_extensions; }; typedef struct @@ -593,7 +732,6 @@ typedef struct void gf_sc_queue_dom_event(GF_Compositor *compositor, GF_Node *node, GF_DOM_Event *evt); void gf_sc_queue_dom_event_on_target(GF_Compositor *compositor, GF_DOM_Event *evt, GF_DOMEventTarget *target, GF_SceneGraph *sg); -void gf_sc_queue_event(GF_Compositor *compositor, GF_Event *evt); /*base stack for timed nodes (nodes that activate themselves at given times) @UpdateTimeNode: shall be setup by the node handler and is called once per simulation frame @@ -632,6 +770,9 @@ enum /*special flag indicating the underlying media directly handled by the hardware (decoding and composition)*/ GF_SR_TEXTURE_PRIVATE_MEDIA = (1<<6), + + /*texture blit is disabled, must go through rasterizer*/ + GF_SR_TEXTURE_DISABLE_BLIT = (1<<7), }; typedef struct _gf_sc_texture_handler @@ -649,7 +790,7 @@ typedef struct _gf_sc_texture_handler /*to override by each texture node*/ void (*update_texture_fcnt)(struct _gf_sc_texture_handler *txh); /*needs_release if a visual frame is grabbed (not used by modules)*/ - Bool needs_release; + u32 needs_release; /*stream_finished: indicates stream is over (not used by modules)*/ Bool stream_finished; /*needs_refresh: indicates texture content has been changed - needed by modules performing tile drawing*/ @@ -669,14 +810,13 @@ typedef struct _gf_sc_texture_handler void (*compute_gradient_matrix)(struct _gf_sc_texture_handler *txh, GF_Rect *bounds, GF_Matrix2D *mat, Bool for_3d); /*image data for natural media*/ - char *data; - u32 size, width, height, pixelformat, pixel_ar, stride, stride_chroma; + u8 *data; + //we need a local copy of width/height/etc since some textures may be defined without a stream object + u32 size, width, height, pixelformat, pixel_ar, stride, stride_chroma, nb_planes; Bool is_flipped; - Bool raw_memory; - u8 *pU, *pV; + GF_FilterFrameInterface *frame_ifce; u32 nb_frames, upload_time; - GF_MediaDecoderFrame *frame; #ifndef GPAC_DISABLE_VRML /*if set texture has been transformed by MatteTexture -> disable blit*/ @@ -686,6 +826,7 @@ typedef struct _gf_sc_texture_handler GF_Node *matteTexture; #endif + u32 probe_time_ms; /*user data for video output module, if needed*/ void *vout_udta; } GF_TextureHandler; @@ -708,7 +849,10 @@ GF_Err gf_sc_texture_open(GF_TextureHandler *txh, MFURL *url, Bool lock_scene_ti GF_Err gf_sc_texture_play(GF_TextureHandler *txh, MFURL *url); GF_Err gf_sc_texture_play_from_to(GF_TextureHandler *txh, MFURL *url, Double start_offset, Double end_offset, Bool can_loop, Bool lock_scene_timeline); /*stops associated object*/ +void gf_sc_texture_stop_no_unregister(GF_TextureHandler *txh); +/*stops associated object and unregister it*/ void gf_sc_texture_stop(GF_TextureHandler *txh); + /*restarts associated object - DO NOT CALL stop/start*/ void gf_sc_texture_restart(GF_TextureHandler *txh); /*common routine for all video texture: fetches a frame and update the 2D texture object */ @@ -836,6 +980,10 @@ struct _traversing_state GF_List *viewpoints; #endif + /*disable partial sphere rendrering in VR*/ + Bool disable_partial_sphere; + + Bool reverse_backface; /*current transformation from top-level*/ GF_Matrix2D transform; @@ -972,6 +1120,7 @@ struct _traversing_state GF_Plane clip_planes[MAX_USER_CLIP_PLANES]; u32 num_clip_planes; + Bool camera_was_dirty; /*layer traversal state: set to the first traversed layer3D when picking @@ -995,20 +1144,23 @@ struct _traversing_state }; /* - Audio mixer - MAX 6 CHANNELS SUPPORTED + Audio mixer - MAX GF_AUDIO_MIXER_MAX_CHANNELS CHANNELS SUPPORTED */ +/*max number of channels we support in mixer*/ +#define GF_AUDIO_MIXER_MAX_CHANNELS 24 + /*the audio object as used by the mixer. All audio nodes need to implement this interface*/ typedef struct _audiointerface { /*fetch audio data for a given audio delay (~soundcard drift) - if delay is 0 sync should not be performed (eg intermediate mix) */ - char *(*FetchFrame) (void *callback, u32 *size, u32 audio_delay_ms); + u8 *(*FetchFrame) (void *callback, u32 *size, u32 *planar_stride, u32 audio_delay_ms); /*release a number of bytes in the indicated frame (ts)*/ void (*ReleaseFrame) (void *callback, u32 nb_bytes); /*get media speed*/ Fixed (*GetSpeed)(void *callback); - /*gets volume for each channel - vol = Fixed[6]. returns 1 if volume shall be changed (!= 1.0)*/ + /*gets volume for each channel - vol = Fixed[GF_AUDIO_MIXER_MAX_CHANNELS]. returns 1 if volume shall be changed (!= 1.0)*/ Bool (*GetChannelVolume)(void *callback, Fixed *vol); /*returns 1 if muted*/ Bool (*IsMuted)(void *callback); @@ -1019,7 +1171,11 @@ typedef struct _audiointerface You may return 0 to force parent user invalidation*/ Bool (*GetConfig)(struct _audiointerface *ai, Bool for_reconf); /*updated cfg, or 0 otherwise*/ - u32 chan, bps, samplerate, ch_cfg; + u32 chan, afmt, samplerate; + u64 ch_layout; + Bool forced_layout; + //updated at each frame, used if frame fetch returns NULL + Bool is_buffering; } GF_AudioInterface; typedef struct __audiomix GF_AudioMixer; @@ -1031,6 +1187,8 @@ void gf_mixer_remove_all(GF_AudioMixer *am); void gf_mixer_add_input(GF_AudioMixer *am, GF_AudioInterface *src); void gf_mixer_remove_input(GF_AudioMixer *am, GF_AudioInterface *src); void gf_mixer_lock(GF_AudioMixer *am, Bool lockIt); +void gf_mixer_set_max_speed(GF_AudioMixer *am, Double max_speed); + /*mix inputs in buffer, return number of bytes written to output*/ u32 gf_mixer_get_output(GF_AudioMixer *am, void *buffer, u32 buffer_size, u32 delay_ms); /*reconfig all sources if needed - returns TRUE if main audio config changed @@ -1038,62 +1196,42 @@ NOTE: this is called at each gf_mixer_get_output by the mixer. To call externall reconfiguration only*/ Bool gf_mixer_reconfig(GF_AudioMixer *am); /*retrieves mixer cfg*/ -void gf_mixer_get_config(GF_AudioMixer *am, u32 *outSR, u32 *outCH, u32 *outBPS, u32 *outChCfg); +void gf_mixer_get_config(GF_AudioMixer *am, u32 *outSR, u32 *outCH, u32 *outFMT, u64 *outChCfg); /*called by audio renderer in case the hardware used a different setup than requested*/ -void gf_mixer_set_config(GF_AudioMixer *am, u32 outSR, u32 outCH, u32 outBPS, u32 ch_cfg); +void gf_mixer_set_config(GF_AudioMixer *am, u32 outSR, u32 outCH, u32 outFMT, u64 ch_cfg); Bool gf_mixer_is_src_present(GF_AudioMixer *am, GF_AudioInterface *ifce); u32 gf_mixer_get_src_count(GF_AudioMixer *am); void gf_mixer_force_chanel_out(GF_AudioMixer *am, u32 num_channels); u32 gf_mixer_get_block_align(GF_AudioMixer *am); Bool gf_mixer_must_reconfig(GF_AudioMixer *am); Bool gf_mixer_empty(GF_AudioMixer *am); +Bool gf_mixer_buffering(GF_AudioMixer *am); - -struct _audiofilterentry -{ - struct _audiofilterentry *next; - u32 in_block_size; - char *in_block; - u32 nb_bytes; - u32 delay_ms; - Bool enable, in_place; - GF_AudioFilter *filter; -}; - -typedef struct -{ - Bool enable_filters; - struct _audiofilterentry *filters; - /*filter processing takes place in a temp buffer since we don't know how many - samples a filter will output, and may ned to cache the output between 2 fill_buffer calls*/ - char *tmp_block1, *tmp_block2; - u32 min_block_size, max_block_size, delay_ms; - -} GF_AudioFilterChain; - -GF_Err gf_afc_load(GF_AudioFilterChain *afc, GF_User *user, char *filterstring); -GF_Err gf_afc_setup(GF_AudioFilterChain *afc, u32 bps, u32 sr, u32 chan, u32 ch_cfg, u32 *ch_out, u32 *ch_cfg_out); -u32 gf_afc_process(GF_AudioFilterChain *afc, u32 nb_bytes); -void gf_afc_unload(GF_AudioFilterChain *afc); -void gf_afc_reset(GF_AudioFilterChain *afc); - +//#define ENABLE_AOUT /*the audio renderer*/ typedef struct _audio_render { - GF_AudioOutput *audio_out; - - Bool disable_resync; - Bool disable_multichannel; - Bool clock_use_audio_out; + GF_Compositor *compositor; + + u32 max_bytes_out, samplerate, bytes_per_samp, nb_bytes_out, buffer_size, nb_buffers; + u64 current_time_sr, time_at_last_config_sr; + GF_FilterPid *aout; + u32 video_ts; + Bool scene_ready; + u32 nb_audio_objects; - /*frozen time counter if set*/ - Bool Frozen; /*startup time, used when no audio output is set*/ u64 start_time; /*freeze time, used when no audio output is set*/ u64 freeze_time; +/* Bool disable_resync; + Bool disable_multichannel; +*/ + /*frozen time counter if set*/ + Bool Frozen; + /*system clock compute when audio output is present*/ u32 current_time, bytes_per_second, time_at_last_config; //number of bytes requested by sound card since last reconfig @@ -1102,27 +1240,20 @@ typedef struct _audio_render /*final output*/ GF_AudioMixer *mixer; Bool need_reconfig; - /*client*/ - GF_User *user; - u32 config_forced; - GF_List *audio_listeners; - /*audio thread if output not self-threaded*/ - GF_Thread *th; - /*thread state: 0: not init, 1: running, 2: waiting for stop, 3: done*/ - u32 audio_th_state; + u32 config_forced, wait_for_rcfg; u32 audio_delay, volume, pan, mute; - GF_AudioFilterChain filter_chain; - u32 nb_filled, nb_used; + //set when output is not realtime + Bool non_rt_output; - Bool step_mode; + Fixed yaw, pitch, roll; } GF_AudioRenderer; /*creates audio renderer*/ -GF_AudioRenderer *gf_sc_ar_load(GF_User *user); +GF_AudioRenderer *gf_sc_ar_load(GF_Compositor *compositor, u32 init_flags); /*deletes audio renderer*/ void gf_sc_ar_del(GF_AudioRenderer *ar); @@ -1140,8 +1271,6 @@ void gf_sc_ar_set_pan(GF_AudioRenderer *ar, u32 Balance); /*mute/unmute audio*/ void gf_sc_ar_mute(GF_AudioRenderer *ar, Bool mute); -/*set audio priority*/ -void gf_sc_ar_set_priority(GF_AudioRenderer *ar, u32 priority); /*gets time in msec - this is the only clock used by the whole ESM system - depends on the audio driver*/ u32 gf_sc_ar_get_clock(GF_AudioRenderer *ar); /*reset all input nodes*/ @@ -1151,26 +1280,21 @@ void gf_sc_ar_add_src(GF_AudioRenderer *ar, GF_AudioInterface *source); /*remove audio node*/ void gf_sc_ar_remove_src(GF_AudioRenderer *ar, GF_AudioInterface *source); /*reconfig audio hardware if needed*/ -void gf_sc_ar_reconfig(GF_AudioRenderer *ar); -u32 gf_sc_ar_get_delay(GF_AudioRenderer *ar); +void gf_sc_ar_send_or_reconfig(GF_AudioRenderer *ar); + +void gf_sc_ar_update_video_clock(GF_AudioRenderer *ar, u32 video_ts); -void gf_sc_flush_next_audio(GF_Compositor *compositor); -Bool gf_sc_check_audio_pending(GF_Compositor *compositor); /*the sound node interface for intensity & spatialization*/ typedef struct _soundinterface { - /*gets volume for each channel - vol = Fixed[6]. returns 1 if volume shall be changed (!= 1.0) + /*gets volume for each channel - vol = Fixed[GF_AUDIO_MIXER_MAX_CHANNELS]. returns 1 if volume shall be changed (!= 1.0) if NULL channels are always at full intensity*/ Bool (*GetChannelVolume)(GF_Node *owner, Fixed *vol); - /*get sound priority (0: min, 255: max) - used by mixer to determine*/ - u8 (*GetPriority) (GF_Node *owner); /*node owning the structure*/ GF_Node *owner; } GF_SoundInterface; -typedef struct __audiofilteritem GF_AudioFilterItem; - /*audio common to AudioClip and AudioSource*/ typedef struct { @@ -1188,7 +1312,6 @@ typedef struct Bool register_with_renderer, register_with_parent; GF_SoundInterface *snd; - GF_AudioFilterItem *filter; } GF_AudioInput; /*setup interface with audio renderer - overwrite any functions needed after setup EXCEPT callback object*/ void gf_sc_audio_setup(GF_AudioInput *ai, GF_Compositor *sr, GF_Node *node); @@ -1220,20 +1343,19 @@ void gf_sc_audio_unregister(GF_AudioInput *ai); #ifndef GPAC_DISABLE_SVG -GF_Err gf_term_get_mfurl_from_xlink(GF_Node *node, MFURL *mfurl); +GF_Err gf_sc_get_mfurl_from_xlink(GF_Node *node, MFURL *mfurl); Fixed gf_sc_svg_convert_length_to_display(GF_Compositor *sr, SVG_Length *length); - -char *gf_term_resolve_xlink(GF_Node *node, char *the_url); +char *gf_scene_resolve_xlink(GF_Node *node, char *the_url); #endif GF_Err compositor_2d_set_aspect_ratio(GF_Compositor *sr); void compositor_2d_set_user_transform(GF_Compositor *sr, Fixed zoom, Fixed tx, Fixed ty, Bool is_resize) ; GF_Err compositor_2d_get_video_access(GF_VisualManager *surf); void compositor_2d_release_video_access(GF_VisualManager *surf); -void compositor_2d_init_callbacks(GF_Compositor *compositor); GF_Rect compositor_2d_update_clipper(GF_TraverseState *tr_state, GF_Rect this_clip, Bool *need_restore, GF_Rect *original, Bool for_layer); Bool compositor_2d_check_attached(GF_VisualManager *visual); void compositor_2d_clear_surface(GF_VisualManager *visual, GF_IRect *rc, u32 BackColor, u32 is_offscreen); +void compositor_2d_init_callbacks(GF_Compositor *compositor); #ifndef GPAC_DISABLE_3D void compositor_2d_reset_gl_auto(GF_Compositor *compositor); @@ -1248,8 +1370,6 @@ Bool compositor_get_2d_plane_intersection(GF_Ray *ray, SFVec3f *res); void compositor_send_resize_event(GF_Compositor *compositor, GF_SceneGraph *subscene, Fixed old_z, Fixed old_tx, Fixed old_ty, Bool is_resize); void compositor_set_cache_memory(GF_Compositor *compositor, u32 memory); -/*checks whether the background node is transparent or not*/ -Bool compositor_background_transparent(GF_Node *node); #ifndef GPAC_DISABLE_3D @@ -1268,6 +1388,11 @@ void gf_sc_load_opengl_extensions(GF_Compositor *sr, Bool has_gl_context); Bool gf_sc_fit_world_to_screen(GF_Compositor *compositor); +GF_Err compositor_3d_setup_fbo(u32 width, u32 height, u32 *fbo_id, u32 *tx_id, u32 *depth_id); +void compositor_3d_delete_fbo(u32 *fbo_id, u32 *fbo_tx_id, u32 *fbo_depth_id, Bool keep_tx_id); +u32 compositor_3d_get_fbo_pixfmt(); +void compositor_3d_enable_fbo(GF_Compositor *compositor, Bool enable); + #endif Bool gf_sc_exec_event(GF_Compositor *sr, GF_Event *evt); @@ -1321,8 +1446,6 @@ Bool compositor_svg_evaluate_conditional(GF_Compositor *compositor, SVGAllAttrib to async restart of animation nodes*/ GF_Node *compositor_svg_get_xlink_resource_node(GF_Node *node, XMLRI *xlink); -GF_SceneGraph *gf_sc_animation_get_scenegraph(GF_Node *node); - #endif /*Text handling*/ @@ -1355,6 +1478,8 @@ struct _gf_font Bool not_loaded; struct _gf_ft_mgr *ft_mgr; + + GF_Compositor *compositor; /*list of spans currently using the font - this is needed to allow for dynamic discard of the font*/ GF_List *spans; }; @@ -1407,7 +1532,7 @@ typedef struct __text_span GF_Node *user; } GF_TextSpan; -GF_FontManager *gf_font_manager_new(GF_User *user); +GF_FontManager *gf_font_manager_new(); void gf_font_manager_del(GF_FontManager *fm); GF_Font *gf_font_manager_set_font(GF_FontManager *fm, char **alt_fonts, u32 nb_fonts, u32 styles); @@ -1439,6 +1564,8 @@ u32 gf_sc_focus_switch_ring(GF_Compositor *compositor, Bool move_prev, GF_Node * Bool compositor_handle_navigation(GF_Compositor *compositor, GF_Event *ev); +void compositor_handle_auto_navigation(GF_Compositor *compositor); + void gf_sc_next_frame_state(GF_Compositor *compositor, u32 state); @@ -1447,7 +1574,7 @@ void gf_get_tinygl_depth(GF_TextureHandler *txh); #endif - +/*TODO - remove this !!*/ typedef struct { void *udta; @@ -1457,47 +1584,995 @@ typedef struct void (*on_video_reconfig)(void *udta, u32 width, u32 height, u8 bpp); } GF_VideoListener; -GF_Err gf_sc_add_video_listener(GF_Compositor *compositor, GF_VideoListener *vl); -GF_Err gf_sc_remove_video_listener(GF_Compositor *compositor, GF_VideoListener *vl); +GF_Err gf_sc_set_scene_size(GF_Compositor *compositor, u32 Width, u32 Height, Bool force_size); + +void gf_sc_sys_frame_pending(GF_Compositor *compositor, Double ts_offset, u32 obj_time, GF_Filter *from_filter); + +Bool gf_sc_is_over(GF_Compositor *compositor, GF_SceneGraph *scene_graph); + +/*returns true if scene or current layer accepts tghe requested navigation type, false otherwise*/ +Bool gf_sc_navigation_supported(GF_Compositor *compositor, u32 type); + +u32 gf_sc_check_end_of_scene(GF_Compositor *compositor, Bool skip_interactions); + + +/*add/rem node requiring a call to render without being present in traversed graph (VRML/MPEG-4 protos). +For these nodes, the traverse effect passed will be NULL.*/ +void gf_sc_queue_node_traverse(GF_Compositor *compositor, GF_Node *node); +void gf_sc_unqueue_node_traverse(GF_Compositor *compositor, GF_Node *node); + + +GF_DownloadManager *gf_sc_get_downloader(GF_Compositor *compositor); + +typedef struct +{ + /*od_manager owning service, NULL for services created for remote channels*/ + struct _od_manager *owner; + + /*service url*/ + char *url; + char *url_frag; + GF_Filter *source_filter; + + /*number of attached remote channels ODM (ESD URLs)*/ +// u32 nb_ch_users; + /*number of attached remote ODM (OD URLs)*/ + u32 nb_odm_users; + + /*clock objects. Kept at service level since ESID namespace is the service one*/ + GF_List *clocks; + + Fixed set_speed; + Bool connect_ack; +} GF_SceneNamespace; + + + +/*opens service - performs URL concatenation if parent service specified*/ +GF_SceneNamespace *gf_scene_ns_new(GF_Scene *scene, GF_ObjectManager *owner, const char *url, const char *parent_url); +/*destroy service*/ +void gf_scene_ns_del(GF_SceneNamespace *ns, GF_Scene *scene); + +void gf_scene_ns_connect_object(GF_Scene *scene, GF_ObjectManager *odm, char *serviceURL, char *parent_url); + + + +/* + GF_Scene object. This is the structure handling all scene management, mainly: + - list of resources (media objects) used + - scene time management + - reload/seek + - Dynamic scene (without a scene description)* + Each scene registers itself as the private data of its associated scene graph. + + The scene object is also used by Inline nodes and all media resources of type "scene", eg , +*/ +struct _gf_scene +{ + /*root OD of the subscene, ALWAYS namespace of the parent scene*/ + struct _od_manager *root_od; + + /*all sub resources of this scene (eg, list of GF_ObjectManager), namespace of this scene. This includes + both external resources (urls) and ODs sent in MPEG-4 systems*/ + GF_List *resources; + + /*list of GF_MediaObject - these are the links betwwen scene nodes (URL, xlink:href) and media resources. + We need this link because of MPEG-4 Systems, where an OD (media resource) can be removed or replaced by the server + without the scene being modified*/ + GF_List *scene_objects; + + /*list of extra scene graphs (text streams, generic OSDs, ...)*/ + GF_List *extra_scenes; + /*scene graph*/ + GF_SceneGraph *graph; + /*graph state - if not attached, no traversing of inline + 0: not attached + 1: attached + 2: temp graph attached. The temp graph is generated when waiting for the first scene AU to be processed + */ + u32 graph_attached; + + //indicates a valid object is attached to the scene + Bool object_attached; + + /*set to 1 to force all sub-resources to share the timeline of this scene*/ + Bool force_single_timeline; + + /*set to 1 once the forced size has been sez*/ + Bool force_size_set; + + /*callback to call to dispatch SVG MediaEvent - this is a pointer to function only because of linking issues + with static libgpac (avoids depending on QuickJS and OpenGL32 if not needed)*/ + void (*on_media_event)(GF_Scene *scene, u32 type); + + /*duration of inline scene*/ + u64 duration; + + u32 nb_buffering; + u32 nb_rebuffer; + + /*max timeshift of all objects*/ + u32 timeshift_depth; + + /*WorldInfo node or node*/ + void *world_info; + + /*current IRI fragment if any*/ + char *fragment_uri; + + /*secondary resource scene - this is specific to SVG: a secondary resource scene is <use> or <fonturi>, and all resources + identified in a secondary resource must be checked for existence and created in the primary resource*/ + Bool secondary_resource; + + /*needed by some apps in GPAC which manipulate the scene tree in different locations as the resources*/ + char *redirect_xml_base; + + + /*FIXME - Dynamic scenes are only supported through VRML/BIFS nodes, we should add support for SVG scene graph + generation if needed*/ + Bool is_dynamic_scene; + /*for MPEG-2 TS, indicates the current serviceID played from mux*/ + u32 selected_service_id; + + /*URLs of current video, audio and subs (we can't store objects since they may be destroyed when seeking)*/ + SFURL visual_url, audio_url, text_url, dims_url; + + Bool is_tiled_srd; + u32 srd_type; + s32 srd_min_x, srd_max_x, srd_min_y, srd_max_y; + + u32 ambisonic_type; + + Bool end_of_scene; +#ifndef GPAC_DISABLE_VRML + /*list of externproto libraries*/ + GF_List *extern_protos; + + /*togles inline restart - needed because the restart may be triggered from inside the scene or from + parent scene, hence 2 render passes must be used + special value 2 means scene URL changes (for anchor navigation*/ + u32 needs_restart; + + /*URL of the current parent Inline node, only set during traversal*/ + MFURL *current_url; + + + /*list of M_Storage nodes active*/ + GF_List *storages; + + /*list of M_KeyNavigator nodes*/ + GF_List *keynavigators; + + /*list of attached Inline nodes*/ + GF_List *attached_inlines; +#endif + + + Bool disable_hitcoord_notif; + + u32 addon_position, addon_size_factor; + + GF_List *declared_addons; + //set when content is replaced by an addon (DASH PVR mode) + Bool main_addon_selected; + u32 sys_clock_at_main_activation, obj_clock_at_main_activation; + + //0: no pause - 1: paused and trigger pause command to net, 2: only clocks are paused but commands not sent + u32 first_frame_pause_type; + u32 vr_type; + + GF_Compositor *compositor; + + //only for root scene + GF_List *namespaces; + + Bool has_splicing_addons; +}; + +GF_Scene *gf_scene_new(GF_Compositor *compositor, GF_Scene *parentScene); +void gf_scene_del(GF_Scene *scene); + +struct _od_manager *gf_scene_find_odm(GF_Scene *scene, u16 OD_ID); +void gf_scene_disconnect(GF_Scene *scene, Bool for_shutdown); + +GF_Scene *gf_scene_get_root_scene(GF_Scene *scene); +Bool gf_scene_is_root(GF_Scene *scene); + +void gf_scene_remove_object(GF_Scene *scene, GF_ObjectManager *odm, u32 for_shutdown); +/*browse all (media) channels and send buffering info to the app*/ +void gf_scene_buffering_info(GF_Scene *scene); +void gf_scene_attach_to_compositor(GF_Scene *scene); +struct _mediaobj *gf_scene_get_media_object(GF_Scene *scene, MFURL *url, u32 obj_type_hint, Bool lock_timelines); +struct _mediaobj *gf_scene_get_media_object_ex(GF_Scene *scene, MFURL *url, u32 obj_type_hint, Bool lock_timelines, struct _mediaobj *sync_ref, Bool force_new_if_not_attached, GF_Node *node_ptr); +void gf_scene_setup_object(GF_Scene *scene, GF_ObjectManager *odm); +/*updates scene duration based on sub objects*/ +void gf_scene_set_duration(GF_Scene *scene); +/*updates scene timeshift based on sub objects*/ +void gf_scene_set_timeshift_depth(GF_Scene *scene); +/*locate media object by ODID (non dynamic ODs) or URL (dynamic ODs)*/ +struct _mediaobj *gf_scene_find_object(GF_Scene *scene, u16 ODID, char *url); +/*returns scene time in sec - exact meaning of time depends on standard used*/ +Double gf_scene_get_time(void *_is); +/*register extra scene graph for on-screen display*/ +void gf_scene_register_extra_graph(GF_Scene *scene, GF_SceneGraph *extra_scene, Bool do_remove); +/*forces scene size info (without changing pixel metrics) - this may be needed by modules using extra graphs (like timedtext)*/ +void gf_scene_force_size(GF_Scene *scene, u32 width, u32 height); +/*regenerate a scene graph based on available objects - can only be called for dynamic OD streams*/ +void gf_scene_regenerate(GF_Scene *scene); +/*selects given ODM for dynamic scenes*/ +void gf_scene_select_object(GF_Scene *scene, GF_ObjectManager *odm); +/*restarts dynamic scene from given time: scene graph is not reseted, objects are just restarted +instead of closed and reopened. If a media control is present on inline, from_time is overridden by MC range*/ +void gf_scene_restart_dynamic(GF_Scene *scene, s64 from_time, Bool restart_only, Bool disable_addon_check); + +void gf_scene_insert_pid(GF_Scene *scene, GF_SceneNamespace *sns, GF_FilterPid *pid, Bool is_in_iod); + +/*exported for compositor: handles filtering of "self" parameter indicating anchor only acts on container inline scene +not root one. Returns 1 if handled (cf user.h, navigate event)*/ +Bool gf_scene_process_anchor(GF_Node *caller, GF_Event *evt); +void gf_scene_force_size_to_video(GF_Scene *scene, GF_MediaObject *mo); + +//check clock status. +//If @check_buffering is 0, returns 1 if all clocks have seen eos, 0 otherwise +//If @check_buffering is 1, returns 1 if no clock is buffering, 0 otheriwse +Bool gf_scene_check_clocks(GF_SceneNamespace *ns, GF_Scene *scene, Bool check_buffering); + +void gf_scene_notify_event(GF_Scene *scene, u32 event_type, GF_Node *n, void *dom_evt, GF_Err code, Bool no_queueing); + +void gf_scene_mpeg4_inline_restart(GF_Scene *scene); +void gf_scene_mpeg4_inline_check_restart(GF_Scene *scene); + + +GF_Node *gf_scene_get_subscene_root(GF_Node *inline_node); + +void gf_scene_select_main_addon(GF_Scene *scene, GF_ObjectManager *odm, Bool set_on, u32 current_clock_time); +void gf_scene_reset_addons(GF_Scene *scene); +void gf_scene_reset_addon(GF_AddonMedia *addon, Bool disconnect); + +#ifndef GPAC_DISABLE_VRML + +/*extern proto fetcher*/ +typedef struct +{ + MFURL *url; + GF_MediaObject *mo; +} GF_ProtoLink; + +/*returns true if the given node DEF name is the url target view (eg blabla#myview)*/ +Bool gf_inline_is_default_viewpoint(GF_Node *node); + +GF_SceneGraph *gf_inline_get_proto_lib(void *_is, MFURL *lib_url); + +/*restarts inline scene - care has to be taken not to remove the scene while it is traversed*/ +void gf_inline_restart(GF_Scene *scene); + +#endif + +/*compares object URL with another URL - ONLY USE THIS WITH DYNAMIC ODs*/ +Bool gf_mo_is_same_url(GF_MediaObject *obj, MFURL *an_url, Bool *keep_fragment, u32 obj_hint_type); + +void gf_mo_update_caps(GF_MediaObject *mo); + + +const char *gf_scene_get_fragment_uri(GF_Node *node); +void gf_scene_set_fragment_uri(GF_Node *node, const char *uri); + + +#if FILTER_FIXME + + +/*URI relocators are used for containers like zip or ISO FF with file items. The relocator +is in charge of translating the URI, potentially extracting the associated resource and sending +back the new (local or not) URI. Only the interface is defined, URI translators are free to derive from them + +relocate a URI - if NULL is returned, this relocator is not concerned with the URI +otherwise returns the translated URI +*/ + +#define GF_TERM_URI_RELOCATOR \ + Bool (*relocate_uri)(void *__self, const char *parent_uri, const char *uri, char *out_relocated_uri, char *out_localized_uri); \ + +typedef struct __gf_uri_relocator GF_URIRelocator; + +struct __gf_uri_relocator +{ + GF_TERM_URI_RELOCATOR +}; + +typedef struct +{ + GF_TERM_URI_RELOCATOR + GF_Terminal *term; + char *szAbsRelocatedPath; +} GF_TermLocales; + +#define MAX_SHORTCUTS 200 + +typedef struct +{ + u8 code; + u8 mods; + u8 action; +} GF_Shortcut; typedef struct { void *udta; - /*called when audio frame is ready to be sent to the sound card. - @buffer, @buffer_size: audio buffer - @time: the terminal global clock in ms - @delay: Due to sound card latencies, audio is sent to the sound card delay milliseconds earlier than - its associated video. + /*called when an event should be filtered */ - void (*on_audio_frame)(void *udta, char *buffer, u32 buffer_size, u32 time, u32 delay); - /*called when audio output has been reconfigured*/ - void (*on_audio_reconfig)(void *udta, u32 samplerate, u32 bits_per_sample, u32 nb_channel, u32 channel_cfg); -} GF_AudioListener; + Bool (*on_event)(void *udta, GF_Event *evt, Bool consumed_by_compositor); +} GF_TermEventFilter; -/*adds a new audio listener - the on_audio_reconfig callback will be called before this function returns*/ -GF_Err gf_sc_add_audio_listener(GF_Compositor *compositor, GF_AudioListener *al); -GF_Err gf_sc_remove_audio_listener(GF_Compositor *compositor, GF_AudioListener *al); +GF_Err gf_term_add_event_filter(GF_Terminal *terminal, GF_TermEventFilter *ef); +GF_Err gf_term_remove_event_filter(GF_Terminal *terminal, GF_TermEventFilter *ef); +void gf_scene_register_associated_media(GF_Scene *scene, GF_AssociatedContentLocation *addon_info); +void gf_scene_notify_associated_media_timeline(GF_Scene *scene, GF_AssociatedContentTiming *addon_time); -GF_Err gf_sc_set_scene_size(GF_Compositor *compositor, u32 Width, u32 Height, Bool force_size); +#endif -Bool gf_sc_use_raw_texture(GF_Compositor *compositor); -void gf_sc_get_av_caps(GF_Compositor *compositor, u32 *width, u32 *height, u32 *display_bit_depth, u32 *audio_bpp, u32 *channels, u32 *sample_rate); +/*clock*/ +struct _object_clock +{ + u16 clock_id; + GF_Compositor *compositor; + GF_Mutex *mx; + /*no_time_ctrl : set if ANY stream running on this clock has no time control capabilities - this avoids applying + mediaControl and others that would break stream dependencies*/ + Bool clock_init, has_seen_eos, no_time_ctrl; + u32 init_timestamp, start_time, pause_time, nb_paused; + /*the number of streams buffering on this clock*/ + u32 nb_buffering; + /*associated media control if any*/ + struct _media_control *mc; + /*for MC only (no FlexTime)*/ + Fixed speed; + //whenever speed is changed we store the time at this instant + u32 speed_set_time; + s32 audio_delay; + + u32 last_ts_rendered; + u32 service_id; + + //media time in ms corresponding to the init tmiestamp of the clock + u32 media_time_at_init; + Bool has_media_time_shift; + + u32 ocr_discontinuity_time; + //we increment this one at each reset, and ask the filter chain to mark packets with this flag + u32 timeline_id; +}; -//signals the compositor a system frame is pending on a future frame -void gf_sc_set_system_pending_frame(GF_Compositor *compositor, Bool frame_pending); +/*destroys clock*/ +void gf_clock_del(GF_Clock *ck); +/*finds a clock by ID or by ES_ID*/ +GF_Clock *gf_clock_find(GF_List *Clocks, u16 clockID, u16 ES_ID); +/*attach clock returns a new clock or the clock this stream (ES_ID) depends on (OCR_ES_ID) +hasOCR indicates whether the stream being attached carries object clock references +@clocks: list of clocks in ES namespace (service) +@is: inline scene to solve clock dependencies +*/ +GF_Clock *gf_clock_attach(GF_List *clocks, GF_Scene *scene, u16 OCR_ES_ID, u16 ES_ID, s32 hasOCR); +/*reset clock (only called by channel owning clock)*/ +void gf_clock_reset(GF_Clock *ck); +/*return clock time in ms*/ +u32 gf_clock_time(GF_Clock *ck); +/*return media time in ms*/ +u32 gf_clock_media_time(GF_Clock *ck); + +/*translates from clock time in ms to media time in ms*/ +u32 gf_clock_to_media_time(GF_Clock *ck, u32 clock_val); + +/*return time in ms since clock started - may be different from clock time when seeking or live*/ +u32 gf_clock_elapsed_time(GF_Clock *ck); + +/*sets clock time - FIXME: drift updates for OCRs*/ +void gf_clock_set_time(GF_Clock *ck, u32 TS); +/*return clock time in ms without drift adjustment - used by audio objects only*/ +u32 gf_clock_real_time(GF_Clock *ck); +/*pause the clock*/ +void gf_clock_pause(GF_Clock *ck); +/*resume the clock*/ +void gf_clock_resume(GF_Clock *ck); +/*returns true if clock started*/ +Bool gf_clock_is_started(GF_Clock *ck); +/*toggles buffering on (clock is paused at the first stream buffering) */ +void gf_clock_buffer_on(GF_Clock *ck); +/*toggles buffering off (clock is paused at the last stream restarting) */ +void gf_clock_buffer_off(GF_Clock *ck); +/*set clock speed scaling factor*/ +void gf_clock_set_speed(GF_Clock *ck, Fixed speed); + +/*set audio delay, i.e. amount of ms to delay non-audio streams +audio is usually send to the sound card quite ahead of time, depending on the output compositor settings*/ +void gf_clock_set_audio_delay(GF_Clock *ck, s32 ms_delay); + + +/*OD manager*/ -//indicates a video frame is pending - this is used fo decoders dispatching their internal memory in order to wake up the compositor asap -void gf_sc_set_video_pending_frame(GF_Compositor *compositor); +enum +{ + /*flag set if object cannot be time-controloed*/ + GF_ODM_NO_TIME_CTRL = (1<<1), + /*flag set if subscene uses parent scene timeline*/ + GF_ODM_INHERIT_TIMELINE = (1<<2), + /*flag set if object has been redirected*/ + GF_ODM_REMOTE_OD = (1<<3), + /*flag set if object has profile indications*/ + GF_ODM_HAS_PROFILES = (1<<4), + /*flag set if object governs profile of inline subscenes*/ + GF_ODM_INLINE_PROFILES = (1<<5), + /*flag set if object declared by network service, not from OD stream*/ + GF_ODM_NOT_IN_OD_STREAM = (1<<6), + /*flag set if object is an entry point of the network service*/ + GF_ODM_SERVICE_ENTRY = (1<<7), + + /*flag set if object has been started before any start request from the scene*/ + GF_ODM_PREFETCH = (1<<8), + + /*flag set if object has been deleted*/ + GF_ODM_DESTROYED = (1<<9), + /*dynamic flags*/ + + /*flag set if associated subscene must be regenerated*/ + GF_ODM_REGENERATE_SCENE = (1<<10), + + /*flag set for first play request*/ + GF_ODM_INITIAL_BROADCAST_PLAY = (1<<11), + + /*flag set until ODM is setup*/ + GF_ODM_NOT_SETUP = (1<<12), + + /*flag set when ODM is setup*/ + GF_ODM_PAUSED = (1<<13), + + /*flag set when ODM is setup*/ + GF_ODM_PAUSE_QUEUED = (1<<14), + + /*flag indicates the odm is a target passthrough*/ + GF_ODM_PASSTHROUGH = (1<<15), +}; -Bool gf_sc_is_over(GF_Compositor *compositor, GF_SceneGraph *scene_graph); +enum +{ + GF_ODM_STATE_STOP, + GF_ODM_STATE_PLAY, +}; -/*returns true if scene or current layer accepts tghe requested navigation type, false otherwise*/ -Bool gf_sc_navigation_supported(GF_Compositor *compositor, u32 type); +enum +{ + GF_ODM_ACTION_PLAY, + GF_ODM_ACTION_STOP, + GF_ODM_ACTION_DELETE, + GF_ODM_ACTION_SCENE_DISCONNECT, + GF_ODM_ACTION_SCENE_RECONNECT, + GF_ODM_ACTION_SCENE_INLINE_RESTART, + GF_ODM_ACTION_SETUP +}; + +typedef struct +{ + u32 pid_id; //esid for clock solving + u32 state; + Bool has_seen_eos; + GF_FilterPid *pid; +} GF_ODMExtraPid; + + +struct _od_manager +{ + /*parent scene or NULL for root scene*/ + GF_Scene *parentscene; + /*sub scene for inline or NULL */ + GF_Scene *subscene; + + /*namespace for clocks*/ + GF_SceneNamespace *scene_ns; + + /*clock for this object*/ + GF_Clock *ck; + //one of the pid contributing to this object acts as the clock reference + Bool owns_clock; + + u32 prev_clock_at_discontinuity_plus_one; + + u32 nb_dropped; + + GF_FilterPid *pid; + //object ID for linking with mediaobjects + u32 ID; + u32 pid_id; //esid for clock solving + + //parent service ID as defined from input + u32 ServiceID; + Bool hybrid_layered_coded; + + Bool has_seen_eos; + GF_List *extra_pids; + + Bool clock_inherited; + //0 or 1, except for IOD where we may have several BIFS/OD streams + u32 nb_buffering, nb_rebuffer; + u32 buffer_max_us, buffer_min_us, buffer_playout_us; + Bool blocking_media; + + //internal hash for source allowing to distinguish input PIDs sources + u32 source_id; + + //media type for this object + u32 type, original_oti; + + u32 flags; + + GF_MediaObject *sync_ref; + + /*interface with scene rendering*/ + struct _mediaobj *mo; + + /*number of channels with connection not yet acknowledge*/ + u32 pending_channels; + u32 state; + /* during playback: timing as evaluated by the composition memory or the scene codec - this is the timestamp + media time at clock init*/ + u32 media_current_time; + /*full object duration 0 if unknown*/ + u64 duration; + /* + upon start: media start time as requested by scene compositor (eg not media control) + set to -1 upon stop to postpone stop request + */ + u64 media_start_time; + s64 media_stop_time; + + /*full object timeshift depth in ms, 0 if no timeshift, (u32) -1 is infinity */ + u32 timeshift_depth; + + u32 action_type; + s32 delay; + + Fixed set_speed; + Bool disable_buffer_at_next_play; + +// u32 raw_media_frame_pending; + GF_Semaphore *raw_frame_sema; + +#ifndef GPAC_DISABLE_VRML + /*the one and only media control currently attached to this object*/ + struct _media_control *media_ctrl; + /*the list of media control controling the object*/ + GF_List *mc_stack; + /*the media sensor(s) attached to this object*/ + GF_List *ms_stack; +#endif + + //only set on root OD of addon subscene, which gather all the hybrid resources + GF_AddonMedia *addon; + //set for objects splicing the main content, indicates the media type (usually in @codec but no codec created for splicing) + u32 splice_addon_mtype; + //set to true if this is a scalable addon for an existing object + Bool scalable_addon; + + //for a regular ODM, this indicates that the current scalable_odm associated + struct _od_manager *upper_layer_odm; + //for a scalable ODM, this indicates the lower layer odm associated + struct _od_manager *lower_layer_odm; + + s32 last_drawn_frame_ntp_diff; + u64 last_drawn_frame_ntp_sender, last_drawn_frame_ntp_receive; + u32 ambi_ch_id; + + const char *redirect_url; + /*0: not set, 1: set , 2: set and disconnect was called to remove the object*/ + u32 skip_disconnect_state; + + Bool ignore_sys; + u64 last_filesize_signaled; +}; + +GF_ObjectManager *gf_odm_new(); +void gf_odm_del(GF_ObjectManager *ODMan); + +/*setup OD*/ +void gf_odm_setup_object(GF_ObjectManager *odm, GF_SceneNamespace *parent_ns, GF_FilterPid *for_pid); + +void gf_odm_setup_remote_object(GF_ObjectManager *odm, GF_SceneNamespace *parent_ns, char *remote_url); + +/*disctonnect OD and removes it if desired (otherwise only STOP is propagated)*/ +void gf_odm_disconnect(GF_ObjectManager *odman, u32 do_remove); +/*setup PID attached to this object*/ +GF_Err gf_odm_setup_pid(GF_ObjectManager *odm, GF_FilterPid *pid); + +//if register_only is false, calls gf_odm_setup_object on the given PID +void gf_odm_register_pid(GF_ObjectManager *odm, GF_FilterPid *pid, Bool register_only); + +Bool gf_odm_check_buffering(GF_ObjectManager *odm, GF_FilterPid *pid); + +/*signals end of stream on channels*/ +void gf_odm_on_eos(GF_ObjectManager *odm, GF_FilterPid *pid); + +/*set time shift buffer duration */ +void gf_odm_set_timeshift_depth(GF_ObjectManager *odm, u32 time_shift_ms); + +/*send PLAY request on associated PID*/ +void gf_odm_start(GF_ObjectManager *odm); +/*stop OD streams*/ +void gf_odm_stop(GF_ObjectManager *odm, Bool force_close); +/*send PLAY request to network - needed to properly handle multiplexed inputs +ONLY called by service handler (media manager thread)*/ +void gf_odm_play(GF_ObjectManager *odm); + +/*pause object (mediaControl use only)*/ +void gf_odm_pause(GF_ObjectManager *odm); +/*resume object (mediaControl use only)*/ +void gf_odm_resume(GF_ObjectManager *odm); +/*set object speed*/ +void gf_odm_set_speed(GF_ObjectManager *odm, Fixed speed, Bool adjust_clock_speed); +/*returns the clock of the media stream (video, audio or bifs), NULL otherwise */ +struct _object_clock *gf_odm_get_media_clock(GF_ObjectManager *odm); +/*adds segment descriptors targeted by the URL to the list and sort them - the input list must be empty*/ +void gf_odm_init_segments(GF_ObjectManager *odm, GF_List *list, MFURL *url); +/*returns true if this OD depends on the given clock*/ +Bool gf_odm_shares_clock(GF_ObjectManager *odm, struct _object_clock *ock); + +void gf_odm_update_duration(GF_ObjectManager *odm, GF_FilterPid *pid); + +GF_Segment *gf_odm_find_segment(GF_ObjectManager *odm, char *descName); + +Bool gf_odm_stop_or_destroy(GF_ObjectManager *odm); + +void gf_odm_signal_eos_reached(GF_ObjectManager *odm); + +void gf_odm_reset_media_control(GF_ObjectManager *odm, Bool signal_reset); + +void gf_odm_check_clock_mediatime(GF_ObjectManager *odm); + + +/*! + * Media Object types + * + * This type provides a hint to network modules which may have to generate an service descriptor on the fly. + * They occur only if objects/services used in the scene are not referenced through ObjectDescriptors (MPEG-4) + * but direct through URL +*/ +enum +{ + /*!service descriptor expected is of undefined type. This should be treated like GF_MEDIA_OBJECT_SCENE*/ + GF_MEDIA_OBJECT_UNDEF = 0, + /*!service descriptor expected is of SCENE type and shall contain a scene stream and OD one if needed*/ + GF_MEDIA_OBJECT_SCENE, + /*!service descriptor expected is of SCENE UPDATES type (animation streams)*/ + GF_MEDIA_OBJECT_UPDATES, + /*!service descriptor expected is of VISUAL type*/ + GF_MEDIA_OBJECT_VIDEO, + /*!service descriptor expected is of AUDIO type*/ + GF_MEDIA_OBJECT_AUDIO, + /*!service descriptor expected is of TEXT type (3GPP/MPEG4)*/ + GF_MEDIA_OBJECT_TEXT, + /*!service descriptor expected is of UserInteraction type (MPEG-4 InputSensor)*/ + GF_MEDIA_OBJECT_INTERACT +}; + +/*! All Media Objects inserted through URLs and not MPEG-4 OD Framework use this ODID*/ +#define GF_MEDIA_EXTERNAL_ID 1050 + + +/*GF_MediaObject: link between real object manager and scene. although there is a one-to-one mapping between a +MediaObject and an ObjectManager, we have to keep them separated in order to handle OD remove commands which destroy +ObjectManagers. */ +struct _mediaobj +{ + /*type is as defined in constants.h # GF_MEDIA_OBJECT_* */ + u32 type; + /*one of the above flags*/ + u32 flags; + + + /* private to ESM*/ + + /*media object manager - private to the sync engine*/ + struct _od_manager *odm; + /*OD ID of the object*/ + u32 OD_ID; + /*OD URL for object not using MPEG4 OD urls*/ + MFURL URLs; + /*session join*/ + u32 num_open; + /*shared object restart handling*/ + u32 num_to_restart, num_restart; + Fixed speed; + + //current packet + GF_FilterPacket *pck; + + u32 frame_dur; + u32 last_fetch_time; + Bool first_frame_fetched; + //number of bytes read in the current packet + u32 RenderedLength; + + /*shared object info: if 0 a new frame will be checked, otherwise current is returned*/ + u32 nb_fetch; + /*frame presentation time*/ + u32 timestamp; + /*time in ms until next frame shall be presented*/ + s32 ms_until_next; + s32 ms_until_pres; + /*data frame size*/ + u32 size, framesize; + /*pointer to data frame */ + u8 *frame; + /* Objects implementing the DOM Event Target interface + used to dispatch HTML5 Media and Media Source Events */ + GF_List *evt_targets; + /*pointer to the node responsible for the creation of this media object + ONLY used for scene media type (animationStreams) + Reset upon creation of the decoder. + */ + void *node_ptr; + + Bool is_eos; + Bool config_changed; + + /*currently valid properties of the object*/ + u32 width, height, stride, pixel_ar, pixelformat, bitrate; + Bool is_flipped; + u32 sample_rate, num_channels, afmt, bytes_per_sec; + u64 channel_config; + Bool planar_audio; + u32 srd_x, srd_y, srd_w, srd_h, srd_full_w, srd_full_h; + + u32 quality_degradation_hint; + u32 nb_views; + u32 nb_layers; + u32 view_min_x, view_max_x, view_min_y, view_max_y; + GF_FilterFrameInterface *frame_ifce; + + Bool connect_failure; +}; + +GF_MediaObject *gf_mo_new(); + + +/*media access events */ +void gf_odm_service_media_event(GF_ObjectManager *odm, GF_EventType event_type); +void gf_odm_service_media_event_with_download(GF_ObjectManager *odm, GF_EventType event_type, u64 loaded_size, u64 total_size, u32 bytes_per_sec); + +/*checks the URL and returns the ODID (MPEG-4 od://) or GF_MEDIA_EXTERNAL_ID for all regular URLs*/ +u32 gf_mo_get_od_id(MFURL *url); + +void gf_scene_generate_views(GF_Scene *scene, char *url, char *parent_url); +void gf_scene_generate_mosaic(GF_Scene *scene, char *url, char *parent_path); +//sets pos and size of addon +// size is 1/2 height (0), 1/3 (1) or 1/4 (2) +// pos is bottom-left(0), top-left (1) bottom-right (2) or top-right (3) +void gf_scene_set_addon_layout_info(GF_Scene *scene, u32 position, u32 size_factor); + + +void gf_scene_message(GF_Scene *scene, const char *service, const char *message, GF_Err error); +void gf_scene_message_ex(GF_Scene *scene, const char *service, const char *message, GF_Err error, Bool no_filtering); + +//returns media time in sec for the addon - timestamp_based is set to 1 if no timeline has been found (eg sync is based on direct timestamp comp) +Double gf_scene_adjust_time_for_addon(GF_AddonMedia *addon, Double clock_time, u8 *timestamp_based); +s64 gf_scene_adjust_timestamp_for_addon(GF_AddonMedia *addon, u64 orig_ts); +void gf_scene_select_scalable_addon(GF_Scene *scene, GF_ObjectManager *odm); +/*check if the associated addon has to be restarted, based on the timestamp of the main media (used for scalable addons only). Returns 1 if the addon has been restarted*/ +Bool gf_scene_check_addon_restart(GF_AddonMedia *addon, u64 cts, u64 dts); + +/*callbacks for scene graph library so that all related ESM nodes are properly instanciated*/ +void gf_scene_node_callback(void *_is, GF_SGNodeCbkType type, GF_Node *node, void *param); + + +void gf_scene_switch_quality(GF_Scene *scene, Bool up); + +//exported for gpac.js, resumes to main content +void gf_scene_resume_live(GF_Scene *subscene); + +enum +{ + //addon to overlay + GF_ADDON_TYPE_ADDITIONAL = 0, + //main content duplicated for PVR purposes + GF_ADDON_TYPE_MAIN, + //scalable decoding - reassembly before the decoder(s) + GF_ADDON_TYPE_SCALABLE, + //multiview reconstruction - reassembly after the decoder(s) + GF_ADDON_TYPE_MULTIVIEW, + //addon used for temporary splicing + GF_ADDON_TYPE_SPLICED, +}; + +struct _gf_addon_media +{ + char *url; + GF_ObjectManager *root_od; + s32 timeline_id; + u32 is_splicing; + //in scene time + Double activation_time; + + Bool enabled; + //object(s) have been started (PLAY command sent) - used to filter out AUs in scalabmle addons + Bool started; + Bool timeline_ready; + + u32 media_timescale; + u64 media_timestamp; + u64 media_pts; + + //in case we detect a loop, we store the value of the mediatime in the loop until we actually loop the content + u32 past_media_timescale; + u64 past_media_timestamp; + u64 past_media_pts, past_media_pts_scaled; + Bool loop_detected; + + u32 addon_type; + + Double splice_start, splice_end; + Bool is_over; + Bool splice_in_pts; + u32 nb_splicing; + Bool min_dts_set; + u64 splice_min_dts; +}; + +void gf_scene_toggle_addons(GF_Scene *scene, Bool show_addons); + + +void gf_scene_set_service_id(GF_Scene *scene, u32 service_id); + + +/*post extended user mouse interaction to terminal + X and Y are point coordinates in the display expressed in 2D coord system top-left (0,0), Y increasing towards bottom + @xxx_but_down: specifiy whether the mouse button is down(2) or up (1), 0 if unchanged + @wheel: specifiy current wheel inc (0: unchanged , +1 for one wheel delta forward, -1 for one wheel delta backward) +*/ +/*NOT NEEDED WHEN THE TERMINAL IS HANDLING THE DISPLAY WINDOW (cf user.h)*/ +void gf_sc_input_sensor_mouse_input(GF_Compositor *compositor, GF_EventMouse *event); + +/*post extended user key interaction to terminal + @key_code: GPAC DOM code of input key + @hw_code: hardware code of input key + @isKeyUp: set if key is released +*/ +/*NOT NEEDED WHEN THE TERMINAL IS HANDLING THE DISPLAY WINDOW (cf user.h)*/ +Bool gf_sc_input_sensor_keyboard_input(GF_Compositor *compositor, u32 key_code, u32 hw_code, Bool isKeyUp); + +/*post extended user character interaction to terminal + @character: unicode character input +*/ +/*NOT NEEDED WHEN THE TERMINAL IS HANDLING THE DISPLAY WINDOW (cf user.h)*/ +void gf_sc_input_sensor_string_input(GF_Compositor *compositor, u32 character); + +GF_Err gf_input_sensor_setup_object(GF_ObjectManager *odm, GF_ESD *esd); +void gf_input_sensor_delete(GF_ObjectManager *odm); + +void InitInputSensor(GF_Scene *scene, GF_Node *node); +void InputSensorModified(GF_Node *n); +void InitKeySensor(GF_Scene *scene, GF_Node *node); +void InitStringSensor(GF_Scene *scene, GF_Node *node); + + +GF_Err gf_odm_get_object_info(GF_ObjectManager *odm, GF_MediaInfo *info); + + + +void gf_sc_connect_from_time_ex(GF_Compositor *compositor, const char *URL, u64 startTime, u32 pause_at_first_frame, Bool secondary_scene, const char *parent_path); + +void gf_sc_disconnect(GF_Compositor *compositor); + + +/*restart object and takes care of media control/clock dependencies*/ +void mediacontrol_restart(GF_ObjectManager *odm); +void mediacontrol_pause(GF_ObjectManager *odm); +//resumes object. +//If @resume_to_live is set, will deactivate main addon acting as PVR +void mediacontrol_resume(GF_ObjectManager *odm, Bool resume_to_live); + +Bool MC_URLChanged(MFURL *old_url, MFURL *new_url); + +void mediasensor_update_timing(GF_ObjectManager *odm, Bool is_eos); + +#ifndef GPAC_DISABLE_VRML + +typedef struct +{ + GF_AudioInput input; + GF_TimeNode time_handle; + Double start_time; + Bool set_duration, failure; +} AudioClipStack; + + +typedef struct +{ + GF_AudioInput input; + GF_TimeNode time_handle; + Bool is_active; + Double start_time; +} AudioSourceStack; + + +/*to do: add preroll support*/ +typedef struct _media_control +{ + M_MediaControl *control; + + /*store current values to detect changes*/ + Double media_start, media_stop; + Fixed media_speed; + Bool enabled; + MFURL url; + + GF_Scene *parent; + /*stream owner*/ + GF_MediaObject *stream; + /*stream owner's clock*/ + GF_Clock *ck; + + u32 changed; + Bool is_init; + Bool paused; + u32 prev_active; + + /*stream object list (segments)*/ + GF_List *seg; + /*current active segment index (ie, controling the PLAY range of the media)*/ + u32 current_seg; +} MediaControlStack; +void InitMediaControl(GF_Scene *scene, GF_Node *node); +void MC_Modified(GF_Node *node); + +void MC_GetRange(MediaControlStack *ctrl, Double *start_range, Double *end_range); + +/*assign mediaControl for this object*/ +void gf_odm_set_mediacontrol(GF_ObjectManager *odm, struct _media_control *ctrl); +/*get media control ruling the clock the media is running on*/ +struct _media_control *gf_odm_get_mediacontrol(GF_ObjectManager *odm); +/*removes control from OD context*/ +void gf_odm_remove_mediacontrol(GF_ObjectManager *odm, struct _media_control *ctrl); +/*switches control (propagates enable=FALSE), returns 1 if control associated with OD has changed to new one*/ +Bool gf_odm_switch_mediacontrol(GF_ObjectManager *odm, struct _media_control *ctrl); + +/*returns 1 if this is a segment switch, 0 otherwise - takes care of object restart if segment switch*/ +Bool gf_odm_check_segment_switch(GF_ObjectManager *odm); + +typedef struct _media_sensor +{ + M_MediaSensor *sensor; + + GF_Scene *parent; + + GF_List *seg; + Bool is_init; + /*stream owner*/ + GF_MediaObject *stream; + + /*private cache (avoids browsing all sensor*/ + u32 active_seg; +} MediaSensorStack; + +void InitMediaSensor(GF_Scene *scene, GF_Node *node); +void MS_Modified(GF_Node *node); + +void MS_Stop(MediaSensorStack *st); + + +void InitMediaControl(GF_Scene *scene, GF_Node *node); +void MC_Modified(GF_Node *node); + +void InitMediaSensor(GF_Scene *scene, GF_Node *node); +void MS_Modified(GF_Node *node); + +void gf_init_inline(GF_Scene *scene, GF_Node *node); +void gf_inline_on_modified(GF_Node *node); + +void gf_scene_init_storage(GF_Scene *scene, GF_Node *node); + +#endif /*GPAC_DISABLE_VRML*/ -/*returns true if 3D acceleration is enabled*/ -Bool gf_sc_use_3d(GF_Compositor *compositor); +Bool gf_sc_check_gl_support(GF_Compositor *compositor); +void gf_sc_mo_destroyed(GF_Node *n); +Bool gf_sc_on_event(void *cbck, GF_Event *event); #ifdef __cplusplus } diff --git a/include/gpac/internal/crypt_dev.h b/include/gpac/internal/crypt_dev.h index a9d6a84..63689cc 100644 --- a/include/gpac/internal/crypt_dev.h +++ b/include/gpac/internal/crypt_dev.h @@ -1,27 +1,27 @@ /* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / Crypto Tools sub-project - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ +* GPAC - Multimedia Framework C SDK +* +* Authors: Jean Le Feuvre +* Copyright (c) Telecom ParisTech 2000-2012 +* All rights reserved +* +* This file is part of GPAC / Crypto Tools sub-project +* +* GPAC is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* GPAC is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; see the file COPYING. If not, write to +* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +* +*/ #ifndef _GF_CRYPT_DEV_H_ #define _GF_CRYPT_DEV_H_ @@ -32,127 +32,35 @@ extern "C" { #include <gpac/crypt.h> -#ifndef GPAC_DISABLE_MCRYPT - -/*the samllest version of the lib: only AES-128-CTR and AES-CBC supported*/ -#define GPAC_CRYPT_ISMA_ONLY - - -typedef void (*mcryptfunc)(void*,void*); -typedef GF_Err (*mcrypt_setkeystream)(void *, const void *, int, const void *, int); -typedef GF_Err (*mcrypt_setkeyblock) (void *, const void *, int); -typedef GF_Err (*mcrypt_docrypt) (void *, const void *, int); - /*private - do not use*/ -typedef struct _tag_crypt_stream +struct _gf_crypt_context { - const char *algo_name; - u32 algo_version; - const char *mode_name; - u32 mode_version; - - /* Holds the algorithm's internal key */ - char *akey; - /* holds the mode's internal buffers */ - char *abuf; - /* holds the key */ - char *keyword_given; - - /*all below are static vars for mode and algo - sizes are in bytes*/ - - /*modes access*/ - GF_Err (*_init_mcrypt) (void *, void *, int, void *, int); - void (*_end_mcrypt) (void *); - GF_Err (*_mcrypt) (void *, void *, int, int, void *, mcryptfunc func, mcryptfunc func2); - GF_Err (*_mdecrypt) (void *, void *, int, int, void *, mcryptfunc func, mcryptfunc func2); - GF_Err (*_mcrypt_set_state) (void *, void *, int ); - GF_Err (*_mcrypt_get_state) (void *, void *, int *); - /*algo access*/ - void *a_encrypt; - void *a_decrypt; - void *a_set_key; - - u32 algo_size; - u32 algo_block_size; - u32 key_size; - u32 num_key_sizes; - u32 key_sizes[MAX_KEY_SIZES]; - u32 algo_IV_size; - - u32 mode_size; - Bool is_block_algo, is_block_algo_mode, is_block_mode, has_IV; -} GF_CryptStream; - -/*modes*/ -void gf_crypt_register_cbc(GF_Crypt *td); -void gf_crypt_register_cfb(GF_Crypt *td); -void gf_crypt_register_ctr(GF_Crypt *td); -void gf_crypt_register_ecb(GF_Crypt *td); -void gf_crypt_register_ncfb(GF_Crypt *td); -void gf_crypt_register_nofb(GF_Crypt *td); -void gf_crypt_register_ofb(GF_Crypt *td); -void gf_crypt_register_stream(GF_Crypt *td); -/*algos*/ -void gf_crypt_register_des(GF_Crypt *td); -void gf_crypt_register_3des(GF_Crypt *td); -void gf_crypt_register_rijndael_128(GF_Crypt *td); -void gf_crypt_register_rijndael_192(GF_Crypt *td); -void gf_crypt_register_rijndael_256(GF_Crypt *td); - - -#define rotl32(x,n) (((x) << ((u32)(n))) | ((x) >> (32 - (u32)(n)))) -#define rotr32(x,n) (((x) >> ((u32)(n))) | ((x) << (32 - (u32)(n)))) -#define rotl16(x,n) (((x) << ((u16)(n))) | ((x) >> (16 - (u16)(n)))) -#define rotr16(x,n) (((x) >> ((u16)(n))) | ((x) << (16 - (u16)(n)))) - -/* Use hardware rotations.. when available */ -#ifdef swap32 -# define byteswap32(x) swap32(x) -#else -# ifdef swap_32 -# define byteswap32(x) swap_32(x) -# else -# ifdef bswap_32 -# define byteswap32(x) bswap_32(x) -# else -# define byteswap32(x) ((rotl32(x, 8) & 0x00ff00ff) | (rotr32(x, 8) & 0xff00ff00)) -# endif -# endif -#endif - -#ifdef swap16 -# define byteswap16(x) swap16(x) + GF_CRYPTO_ALGO algo; //single value for now + GF_CRYPTO_MODE mode; //CBC or CTR + + /* Internal context for openSSL or tiny AES*/ + void *context; + + //ptr to encryption function + GF_Err(*_init_crypt) (GF_Crypt *ctx, void*, const void*); + void(*_deinit_crypt) (GF_Crypt *ctx); + void(*_end_crypt) (GF_Crypt *ctx); + void(*_set_key)(GF_Crypt *ctx, void*); + GF_Err(*_crypt) (GF_Crypt *ctx, u8 *buffer, u32 size); + GF_Err(*_decrypt) (GF_Crypt*, u8 *buffer, u32 size); + GF_Err(*_set_state) (GF_Crypt*, const u8 *IV, u32 IV_size); + GF_Err(*_get_state) (GF_Crypt*, u8 *IV, u32 *IV_size); +}; + +#ifdef GPAC_HAS_SSL +GF_Err gf_crypt_open_open_openssl(GF_Crypt* td, GF_CRYPTO_MODE mode); #else -# ifdef swap_16 -# define byteswap16(x) swap_16(x) -# else -# ifdef bswap_16 -# define byteswap16(x) bswap_16(x) -# else -# define byteswap16(x) ((rotl16(x, 8) & 0x00ff) | (rotr16(x, 8) & 0xff00)) -# endif -# endif +GF_Err gf_crypt_open_open_tinyaes(GF_Crypt* td, GF_CRYPTO_MODE mode); #endif -GFINLINE static -void memxor(unsigned char *o1, unsigned char *o2, int length) -{ - int i; - - for (i = 0; i < length; i++) { - o1[i] ^= o2[i]; - } - return; -} - - -#define Bzero(x, y) memset(x, 0, y) - -#endif /*GPAC_DISABLE_MCRYPT*/ #ifdef __cplusplus } #endif #endif /*_GF_CRYPT_DEV_H_*/ - diff --git a/include/gpac/internal/dvb_mpe_dev.h b/include/gpac/internal/dvb_mpe_dev.h index 084bb65..92735d9 100644 --- a/include/gpac/internal/dvb_mpe_dev.h +++ b/include/gpac/internal/dvb_mpe_dev.h @@ -119,7 +119,7 @@ typedef struct __gf_dvb_mpe_ip_platform typedef struct { - char *data; /* Data */ + u8 *data; /* Data */ u32 u32_version; /* IP version */ u32 u32_hdr_length; /* header length by piece of 4 bytes */ u32 u32_total_length; /* the length of the datagram (hdr+payload) in bytes */ @@ -179,7 +179,7 @@ typedef struct mpe_fec_frame /* Get INT table */ -void gf_m2ts_process_int(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_ES *ip_table, unsigned char *data, u32 data_size, u32 table_id); +void gf_m2ts_process_int(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_ES *ip_table, u8 *data, u32 data_size, u32 table_id); void section_DSMCC_INT(GF_M2TS_IP_PLATFORM* ip_platform, u8 *data, u32 data_size); @@ -223,12 +223,12 @@ struct tag_m2ts_section_mpe }; -void gf_m2ts_process_mpe(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_MPE *mpe, unsigned char *data, u32 data_size, u8 table_id); +void gf_m2ts_process_mpe(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_MPE *mpe, u8 *data, u32 data_size, u8 table_id); void gf_m2ts_gather_ipdatagram_information(MPE_FEC_FRAME *mff,GF_M2TS_Demuxer *ts); void socket_simu(GF_M2TS_IP_Packet *ip_packet, GF_M2TS_Demuxer *ts, Bool yield); -void gf_m2ts_mpe_send_datagram(GF_M2TS_Demuxer *ts, u32 pid, unsigned char *data, u32 data_size); +void gf_m2ts_mpe_send_datagram(GF_M2TS_Demuxer *ts, u32 pid, u8 *data, u32 data_size); /* allocate the necessary memory space*/ u32 init_frame(MPE_FEC_FRAME * mff, u32 rows); diff --git a/include/gpac/internal/ietf_dev.h b/include/gpac/internal/ietf_dev.h index b24e4c8..0a4e808 100644 --- a/include/gpac/internal/ietf_dev.h +++ b/include/gpac/internal/ietf_dev.h @@ -30,8 +30,6 @@ #ifndef GPAC_DISABLE_STREAMING -#include <gpac/thread.h> - /* RTP intern */ @@ -83,7 +81,7 @@ void gf_rtp_reorderer_reset(GF_RTPReorder *po); /*Adds a packet to the queue. Packet Data is memcopied*/ GF_Err gf_rtp_reorderer_add(GF_RTPReorder *po, const void * pck, u32 pck_size, u32 pck_seqnum); /*gets the output of the queue. Packet Data IS YOURS to delete*/ -void *gf_rtp_reorderer_get(GF_RTPReorder *po, u32 *pck_size); +void *gf_rtp_reorderer_get(GF_RTPReorder *po, u32 *pck_size, Bool force_flush); /*the RTP channel with both RTP and RTCP sockets and buffers @@ -127,7 +125,7 @@ struct __tag_rtp_channel u32 TimeScale; /*static buffer for RTP sending*/ - char *send_buffer; + u8 *send_buffer; u32 send_buffer_size; u32 pck_sent_since_last_sr; u32 last_pck_ts; @@ -163,6 +161,12 @@ struct __tag_rtp_channel u32 last_SR_rtp_time; /*payload info*/ u32 total_pck, total_bytes; + + GF_BitStream *bs_r, *bs_w; + Bool no_select; + + gf_rtp_tcp_callback send_interleave; + void *interleave_cbk1, *interleave_cbk2; }; /*gets UTC in the channel RTP timescale*/ @@ -185,7 +189,7 @@ void gf_rtp_get_next_report_time(GF_RTPChannel *ch); #define RTSP_WRITE_ALLOC_STR_WITHOUT_CHECK(buf, buf_size, pos, str) \ if (strlen((const char *) str)+pos >= buf_size) { \ - buf_size += RTSP_WRITE_STEPALLOC; \ + buf_size += (u32) strlen((const char *) str); \ buf = (char *) gf_realloc(buf, buf_size); \ } \ strcpy(buf+pos, (const char *) str); \ @@ -204,25 +208,33 @@ void gf_rtp_get_next_report_time(GF_RTPChannel *ch); RTSP_WRITE_ALLOC_STR(buf, buf_size, pos, "\r\n"); \ } \ -#define RTSP_WRITE_INT(buf, buf_size, pos, d, sig) \ +#define RTSP_WRITE_INT(buf, buf_size, pos, d, sig) { \ + char temp[50]; \ if (sig < 0) { \ sprintf(temp, "%d", d); \ } else { \ sprintf(temp, "%d", d); \ } \ - RTSP_WRITE_ALLOC_STR_WITHOUT_CHECK(buf, buf_size, pos, temp); + RTSP_WRITE_ALLOC_STR_WITHOUT_CHECK(buf, buf_size, pos, temp); \ + } -#define RTSP_WRITE_FLOAT_WITHOUT_CHECK(buf, buf_size, pos, d) \ - sprintf(temp, "%.4f", d); \ - RTSP_WRITE_ALLOC_STR_WITHOUT_CHECK(buf, buf_size, pos, temp); +#define RTSP_WRITE_HEX(buf, buf_size, pos, d, sig) { \ + char temp[50]; \ + sprintf(temp, "%X", d); \ + RTSP_WRITE_ALLOC_STR_WITHOUT_CHECK(buf, buf_size, pos, temp);\ + } -#define RTSP_WRITE_FLOAT(buf, buf_size, pos, d) \ +#define RTSP_WRITE_FLOAT_WITHOUT_CHECK(buf, buf_size, pos, d) { \ + char temp[50]; \ sprintf(temp, "%.4f", d); \ - RTSP_WRITE_ALLOC_STR(buf, buf_size, pos, temp); + RTSP_WRITE_ALLOC_STR_WITHOUT_CHECK(buf, buf_size, pos, temp);\ + } -/*default packet size, but resize on the fly if needed*/ -#define RTSP_PCK_SIZE 6000 -#define RTSP_TCP_BUF_SIZE 0x10000ul +#define RTSP_WRITE_FLOAT(buf, buf_size, pos, d) { \ + char temp[50]; \ + sprintf(temp, "%.4f", d); \ + RTSP_WRITE_ALLOC_STR(buf, buf_size, pos, temp); \ + } typedef struct @@ -273,23 +285,20 @@ struct _tag_rtsp_session char RTSPLastRequest[40]; /*current buffer from TCP if any*/ - char TCPBuffer[RTSP_TCP_BUF_SIZE]; + u8 *tcp_buffer; u32 CurrentSize, CurrentPos; /*RTSP interleaving*/ - GF_Err (*RTSP_SignalData)(GF_RTSPSession *sess, void *chan, char *buffer, u32 bufferSize, Bool IsRTCP); + GF_Err (*RTSP_SignalData)(GF_RTSPSession *sess, void *chan, u8 *buffer, u32 bufferSize, Bool IsRTCP); /*buffer for pck reconstruction*/ - char *rtsp_pck_buf; + u8 *rtsp_pck_buf; u32 rtsp_pck_size; u32 pck_start, payloadSize; /*all RTP channels in an interleaved RTP on RTSP session*/ GF_List *TCPChannels; - /*thread-safe, full duplex library for PLAY and RECORD*/ - GF_Mutex *mx; - - char *MobileIP; + Bool interleaved; }; GF_RTSPSession *gf_rtsp_session_new(char *sURL, u16 DefaultPort); @@ -297,7 +306,7 @@ GF_RTSPSession *gf_rtsp_session_new(char *sURL, u16 DefaultPort); /*check connection status*/ GF_Err gf_rtsp_check_connection(GF_RTSPSession *sess); /*send data on RTSP*/ -GF_Err gf_rtsp_send_data(GF_RTSPSession *sess, char *buffer, u32 Size); +GF_Err gf_rtsp_send_data(GF_RTSPSession *sess, u8 *buffer, u32 Size); /* Common RTSP tools @@ -312,9 +321,9 @@ GF_Err gf_rtsp_fill_buffer(GF_RTSPSession *sess); /*force a fill on TCP buffer - used for de-interleaving and TCP-fragmented RTSP messages*/ GF_Err gf_rtsp_refill_buffer(GF_RTSPSession *sess); /*parses a transport string and returns a transport structure*/ -GF_RTSPTransport *gf_rtsp_transport_parse(char *buffer); +GF_RTSPTransport *gf_rtsp_transport_parse(u8 *buffer); /*parsing of header for com and rsp*/ -GF_Err gf_rtsp_parse_header(char *buffer, u32 BufferSize, u32 BodyStart, GF_RTSPCommand *com, GF_RTSPResponse *rsp); +GF_Err gf_rtsp_parse_header(u8 *buffer, u32 BufferSize, u32 BodyStart, GF_RTSPCommand *com, GF_RTSPResponse *rsp); void gf_rtsp_set_command_value(GF_RTSPCommand *com, char *Header, char *Value); void gf_rtsp_set_response_value(GF_RTSPResponse *rsp, char *Header, char *Value); /*deinterleave a data packet*/ @@ -323,22 +332,180 @@ GF_Err gf_rtsp_set_deinterleave(GF_RTSPSession *sess); GF_Err gf_rtsp_http_tunnel_start(GF_RTSPSession *sess, char *UserAgent); + + + +/* + RTP -> SL packetization tool + You should ONLY modify the GF_SLHeader while packetizing, all the rest is private + to the tool. + Also note that AU start/end is automatically updated, therefore you should only + set CTS-DTS-OCR-sequenceNumber (which is automatically incremented when splitting a payload) + -padding-idle infos + SL flags are computed on the fly, but you may wish to modify them in case of + packet drop/... at the encoder side + +*/ +struct __tag_rtp_packetizer +{ + /*input packet sl header cfg. modify only if needed*/ + GF_SLHeader sl_header; + u32 nb_aus; + /* + + PRIVATE _ DO NOT TOUCH + */ + +//! @cond Doxygen_Suppress + + /*RTP payload type (RFC type, NOT the RTP hdr payT)*/ + u32 rtp_payt; + /*packetization flags*/ + u32 flags; + /*Path MTU size without 12-bytes RTP header*/ + u32 Path_MTU; + /*max packet duration in RTP TS*/ + u32 max_ptime; + + /*payload type of RTP packets - only one payload type can be used in GPAC*/ + u8 PayloadType; + + /*RTP header of current packet*/ + GF_RTPHeader rtp_header; + + /*RTP packet handling callbacks*/ + void (*OnNewPacket)(void *cbk_obj, GF_RTPHeader *header); + void (*OnPacketDone)(void *cbk_obj, GF_RTPHeader *header); + void (*OnDataReference)(void *cbk_obj, u32 payload_size, u32 offset_from_orig); + void (*OnData)(void *cbk_obj, u8 *data, u32 data_size, Bool is_header); + void *cbk_obj; + + /********************************* + MPEG-4 Generic hinting + *********************************/ + + /*SL to RTP map*/ + GP_RTPSLMap slMap; + /*SL conf and state*/ + GF_SLConfig sl_config; + + /*set to 1 if firstSL in RTP packet*/ + Bool first_sl_in_rtp; + Bool has_AU_header; + /*current info writers*/ + GF_BitStream *pck_hdr, *payload; + + /*AU SN of last au*/ + u32 last_au_sn; + + /*info for the current packet*/ + u32 auh_size, bytesInPacket; + + /********************************* + ISMACryp info + *********************************/ + Bool force_flush, is_encrypted; + u64 IV, first_AU_IV; + char *key_indicator; + + /********************************* + AVC-H264 info + *********************************/ + /*AVC non-IDR flag: set if all NAL in current packet are non-IDR (disposable)*/ + Bool avc_non_idr; + + /********************************* + AC3 info + *********************************/ + /*ac3 ft flags*/ + u8 ac3_ft; + + /********************************* + HEVC-H265 info + *********************************/ + /*HEVC Payload Header. It will be use in case of Aggreation Packet where we must add payload header for packet after having added of NALU to AP*/ + char hevc_payload_hdr[2]; + +//! @endcond + +}; + /*packetization routines*/ -GF_Err gp_rtp_builder_do_mpeg4(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); -GF_Err gp_rtp_builder_do_h263(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); -GF_Err gp_rtp_builder_do_amr(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_mpeg4(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_h263(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_amr(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); #ifndef GPAC_DISABLE_AV_PARSERS -GF_Err gp_rtp_builder_do_mpeg12_video(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_mpeg12_video(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); #endif -GF_Err gp_rtp_builder_do_mpeg12_audio(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); -GF_Err gp_rtp_builder_do_tx3g(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration, u8 descIndex); -GF_Err gp_rtp_builder_do_avc(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); -GF_Err gp_rtp_builder_do_qcelp(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); -GF_Err gp_rtp_builder_do_smv(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); -GF_Err gp_rtp_builder_do_latm(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration); -GF_Err gp_rtp_builder_do_dims(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration); -GF_Err gp_rtp_builder_do_ac3(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); -GF_Err gp_rtp_builder_do_hevc(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_mpeg12_audio(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_tx3g(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration, u8 descIndex); +GF_Err gp_rtp_builder_do_avc(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_qcelp(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_smv(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_latm(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration); +GF_Err gp_rtp_builder_do_ac3(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_hevc(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); +GF_Err gp_rtp_builder_do_mp2t(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize); + +/*! RTP depacketization tool*/ +struct __tag_rtp_depacketizer +{ + /*! depacketize routine*/ + void (*depacketize)(struct __tag_rtp_depacketizer *rtp, GF_RTPHeader *hdr, u8 *payload, u32 size); + + /*! output packet sl header cfg*/ + GF_SLHeader sl_hdr; + + /*! RTP payload type (RFC type, NOT the RTP hdr payT)*/ + u32 payt; + /*! depacketization flags*/ + u32 flags; + + //! RTP static map may be NULL + const GF_RTPStaticMap *static_map; + + /*! callback routine*/ + gf_rtp_packet_cbk on_sl_packet; + /*! callback udta*/ + void *udta; + + /*! SL <-> RTP map*/ + GP_RTPSLMap sl_map; + /*! RTP clock rate*/ + u32 clock_rate; + + //! clip rect X + u32 x; + //! clip rect Y + u32 y; + //! clip rect or full size width + u32 w; + //! clip rect or full size height + u32 h; + + /*! inter-packet reconstruction bitstream (for 3GP text and H264)*/ + GF_BitStream *inter_bs; + + /*! H264/AVC config*/ + u32 h264_pck_mode; + + /*3GP text reassembler state*/ + /*! number of 3GPP text fragments*/ + u8 nb_txt_frag; + /*! current 3GPP text fragments*/ + u8 cur_txt_frag; + /*! current 3GPP text sample desc index*/ + u8 sidx; + /*! 3GPP text total sample text len*/ + u8 txt_len; + /*! number of 3GPP text modifiers*/ + u8 nb_mod_frag; + + /*! ISMACryp scheme*/ + u32 isma_scheme; + /*! ISMACryp key*/ + char *key; +}; #endif /*GPAC_DISABLE_STREAMING*/ diff --git a/include/gpac/internal/isomedia_dev.h b/include/gpac/internal/isomedia_dev.h index 11e9905..3a18910 100644 --- a/include/gpac/internal/isomedia_dev.h +++ b/include/gpac/internal/isomedia_dev.h @@ -32,94 +32,12 @@ extern "C" { #include <gpac/isomedia.h> -#ifndef GPAC_DISABLE_ISOM - - -#if defined(GPAC_DISABLE_ISOM_FRAGMENTS) && !defined(GPAC_DISABLE_ISOM_ADOBE) -#define GPAC_DISABLE_ISOM_ADOBE -#endif - - /*the default size is 64, cause we need to handle large boxes... - - the other_boxes container is by default NOT created. When parsing a box and adding - a sub-box with gf_isom_box_add_default, the list is created. - This list is destroyed befaore calling the final box destructor - This list is automatically taken into account during size() and write() functions - */ -#define GF_ISOM_BOX \ - u32 type; \ - u64 size; \ - const struct box_registry_entry *registry;\ - GF_List *other_boxes; - -#define GF_ISOM_FULL_BOX \ - GF_ISOM_BOX \ - u8 version; \ - u32 flags; \ - -#define GF_ISOM_UUID_BOX \ - GF_ISOM_BOX \ - u8 uuid[16]; \ - u32 internal_4cc; \ - -typedef struct -{ - GF_ISOM_BOX -} GF_Box; - -typedef struct -{ - GF_ISOM_FULL_BOX -} GF_FullBox; - -typedef struct -{ - GF_ISOM_UUID_BOX -} GF_UUIDBox; - - -#define ISOM_DECL_BOX_ALLOC(__TYPE, __4cc) __TYPE *tmp; \ - GF_SAFEALLOC(tmp, __TYPE); \ - if (tmp==NULL) return NULL; \ - tmp->type = __4cc; - -#define ISOM_DECREASE_SIZE(__ptr, bytes) if (__ptr->size < (bytes) ) {\ - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[isom] not enough bytes in box %s: %d left, reading %d (file %s, line %d)\n", gf_4cc_to_str(__ptr->type), __ptr->size, (bytes), __FILE__, __LINE__ )); \ - return GF_ISOM_INVALID_FILE; \ - }\ - __ptr->size -= bytes; \ - -/*constructor*/ -GF_Box *gf_isom_box_new(u32 boxType); -//some boxes may have different syntax based on container. Use this constructor for this case -GF_Box *gf_isom_box_new_ex(u32 boxType, u32 parentType); - -GF_Err gf_isom_box_write(GF_Box *ptr, GF_BitStream *bs); -GF_Err gf_isom_box_read(GF_Box *ptr, GF_BitStream *bs); -void gf_isom_box_del(GF_Box *ptr); -GF_Err gf_isom_box_size(GF_Box *ptr); - -GF_Err gf_isom_clone_box(GF_Box *src, GF_Box **dst); - -GF_Err gf_isom_box_parse(GF_Box **outBox, GF_BitStream *bs); -GF_Err gf_isom_box_array_read(GF_Box *s, GF_BitStream *bs, GF_Err (*add_box)(GF_Box *par, GF_Box *b)); -GF_Err gf_isom_box_array_read_ex(GF_Box *parent, GF_BitStream *bs, GF_Err (*add_box)(GF_Box *par, GF_Box *b), u32 parent_type); -GF_Err gf_isom_box_add_default(GF_Box *a, GF_Box *subbox); -GF_Err gf_isom_box_parse_ex(GF_Box **outBox, GF_BitStream *bs, u32 parent_type, Bool is_root_box); - -//writes box header - shall be called at the begining of each xxxx_Write function -//this function is not factorized in order to let box serializer modify box type before writing -GF_Err gf_isom_box_write_header(GF_Box *ptr, GF_BitStream *bs); - -//writes box header then version+flags -GF_Err gf_isom_full_box_write(GF_Box *s, GF_BitStream *bs); - -void gf_isom_box_array_del(GF_List *other_boxes); -GF_Err gf_isom_box_array_write(GF_Box *parent, GF_List *list, GF_BitStream *bs); -GF_Err gf_isom_box_array_size(GF_Box *parent, GF_List *list); enum { + //internal code type for unknown boxes + GF_ISOM_BOX_TYPE_UNKNOWN = GF_4CC( 'U', 'N', 'K', 'N' ), + GF_ISOM_BOX_TYPE_CO64 = GF_4CC( 'c', 'o', '6', '4' ), GF_ISOM_BOX_TYPE_STCO = GF_4CC( 's', 't', 'c', 'o' ), GF_ISOM_BOX_TYPE_CTTS = GF_4CC( 'c', 't', 't', 's' ), @@ -181,7 +99,6 @@ enum GF_ISOM_BOX_TYPE_MFRO = GF_4CC( 'm', 'f', 'r', 'o' ), GF_ISOM_BOX_TYPE_TFRA = GF_4CC( 't', 'f', 'r', 'a' ), - GF_ISOM_BOX_TYPE_PSSH = GF_4CC( 'p', 's', 's', 'h' ), GF_ISOM_BOX_TYPE_TENC = GF_4CC( 't', 'e', 'n', 'c' ), //track group @@ -212,6 +129,10 @@ enum GF_ISOM_BOX_TYPE_TRAF = GF_4CC( 't', 'r', 'a', 'f' ), GF_ISOM_BOX_TYPE_TFHD = GF_4CC( 't', 'f', 'h', 'd' ), GF_ISOM_BOX_TYPE_TRUN = GF_4CC( 't', 'r', 'u', 'n' ), +#ifdef GF_ENABLE_CTRN + GF_ISOM_BOX_TYPE_CTRN = GF_4CC( 'c', 't', 'r', 'n' ), +#endif + #endif @@ -247,21 +168,43 @@ enum GF_ISOM_BOX_TYPE_AVC4 = GF_4CC( 'a', 'v', 'c', '4' ), GF_ISOM_BOX_TYPE_SVCC = GF_4CC( 's', 'v', 'c', 'C' ), GF_ISOM_BOX_TYPE_SVC1 = GF_4CC( 's', 'v', 'c', '1' ), + GF_ISOM_BOX_TYPE_SVC2 = GF_4CC( 's', 'v', 'c', '2' ), GF_ISOM_BOX_TYPE_MVCC = GF_4CC( 'm', 'v', 'c', 'C' ), GF_ISOM_BOX_TYPE_MVC1 = GF_4CC( 'm', 'v', 'c', '1' ), + GF_ISOM_BOX_TYPE_MVC2 = GF_4CC( 'm', 'v', 'c', '2' ), + GF_ISOM_BOX_TYPE_MHC1 = GF_4CC( 'm', 'h', 'c', '1' ), + GF_ISOM_BOX_TYPE_MHV1 = GF_4CC( 'm', 'h', 'v', '1' ), GF_ISOM_BOX_TYPE_HVCC = GF_4CC( 'h', 'v', 'c', 'C' ), GF_ISOM_BOX_TYPE_HVC1 = GF_4CC( 'h', 'v', 'c', '1' ), GF_ISOM_BOX_TYPE_HEV1 = GF_4CC( 'h', 'e', 'v', '1' ), GF_ISOM_BOX_TYPE_HVT1 = GF_4CC( 'h', 'v', 't', '1' ), + GF_ISOM_BOX_TYPE_MVCI = GF_4CC( 'm', 'v', 'c', 'i' ), + GF_ISOM_BOX_TYPE_MVCG = GF_4CC( 'm', 'v', 'c', 'g' ), + GF_ISOM_BOX_TYPE_VWID = GF_4CC( 'v', 'w', 'i', 'd' ), GF_ISOM_BOX_TYPE_HVC2 = GF_4CC( 'h', 'v', 'c', '2' ), GF_ISOM_BOX_TYPE_HEV2 = GF_4CC( 'h', 'e', 'v', '2' ), GF_ISOM_BOX_TYPE_LHV1 = GF_4CC( 'l', 'h', 'v', '1' ), GF_ISOM_BOX_TYPE_LHE1 = GF_4CC( 'l', 'h', 'e', '1' ), + GF_ISOM_BOX_TYPE_LHT1 = GF_4CC( 'l', 'h', 't', '1' ), GF_ISOM_BOX_TYPE_LHVC = GF_4CC( 'l', 'h', 'v', 'C' ), + GF_ISOM_BOX_TYPE_AV1C = GF_4CC('a', 'v', '1', 'C'), + GF_ISOM_BOX_TYPE_AV01 = GF_4CC('a', 'v', '0', '1'), + + /*WebM*/ + GF_ISOM_BOX_TYPE_VPCC = GF_4CC('v', 'p', 'c', 'C'), + GF_ISOM_BOX_TYPE_VP08 = GF_4CC('v', 'p', '0', '8'), + GF_ISOM_BOX_TYPE_VP09 = GF_4CC('v', 'p', '0', '9'), + GF_ISOM_BOX_TYPE_SMDM = GF_4CC('S', 'm', 'D', 'm'), + GF_ISOM_BOX_TYPE_COLL = GF_4CC('C', 'o', 'L', 'L'), + + /*Opus*/ + GF_ISOM_BOX_TYPE_OPUS = GF_4CC('O', 'p', 'u', 's'), + GF_ISOM_BOX_TYPE_DOPS = GF_4CC('d', 'O', 'p', 's'), + /*LASeR extension*/ GF_ISOM_BOX_TYPE_LSRC = GF_4CC( 'l', 's', 'r', 'C' ), GF_ISOM_BOX_TYPE_LSR1 = GF_4CC( 'l', 's', 'r', '1' ), @@ -281,6 +224,7 @@ enum GF_ISOM_BOX_TYPE_SSIX = GF_4CC( 's', 's', 'i', 'x' ), GF_ISOM_BOX_TYPE_LEVA = GF_4CC( 'l', 'e', 'v', 'a' ), GF_ISOM_BOX_TYPE_PCRB = GF_4CC( 'p', 'c', 'r', 'b' ), + GF_ISOM_BOX_TYPE_EMSG = GF_4CC( 'e', 'm', 's', 'g' ), /*3GPP text / MPEG-4 StreamingText*/ GF_ISOM_BOX_TYPE_FTAB = GF_4CC( 'f', 't', 'a', 'b' ), @@ -440,6 +384,8 @@ enum GF_ISOM_BOX_TYPE_DAC3 = GF_4CC( 'd', 'a', 'c', '3' ), GF_ISOM_BOX_TYPE_EC3 = GF_4CC( 'e', 'c', '-', '3' ), GF_ISOM_BOX_TYPE_DEC3 = GF_4CC( 'd', 'e', 'c', '3' ), + GF_ISOM_BOX_TYPE_DVCC = GF_4CC( 'd', 'v', 'c', 'C' ), + GF_ISOM_BOX_TYPE_DVHE = GF_4CC( 'd', 'v', 'h', 'e' ), GF_ISOM_BOX_TYPE_SUBS = GF_4CC( 's', 'u', 'b', 's' ), @@ -472,22 +418,25 @@ enum GF_ISOM_BOX_TYPE_IPCO = GF_4CC( 'i', 'p', 'c', 'o' ), GF_ISOM_BOX_TYPE_IPRP = GF_4CC( 'i', 'p', 'r', 'p' ), GF_ISOM_BOX_TYPE_IPMA = GF_4CC( 'i', 'p', 'm', 'a' ), - GF_ISOM_BOX_TYPE_GRPL = GF_4CC('g', 'r', 'p', 'l'), + GF_ISOM_BOX_TYPE_GRPL = GF_4CC( 'g', 'r', 'p', 'l'), GF_ISOM_BOX_TYPE_CCST = GF_4CC( 'c', 'c', 's', 't' ), GF_ISOM_BOX_TYPE_AUXC = GF_4CC( 'a', 'u', 'x', 'C' ), + GF_ISOM_BOX_TYPE_AUXI = GF_4CC( 'a', 'u', 'x', 'i' ), GF_ISOM_BOX_TYPE_OINF = GF_4CC( 'o', 'i', 'n', 'f' ), GF_ISOM_BOX_TYPE_TOLS = GF_4CC( 't', 'o', 'l', 's' ), + /* MIAF Boxes */ + GF_ISOM_BOX_TYPE_CLLI = GF_4CC('c', 'l', 'l', 'i'), + GF_ISOM_BOX_TYPE_MDCV = GF_4CC('m', 'd', 'c', 'v'), + GF_ISOM_BOX_TYPE_ALTR = GF_4CC( 'a', 'l', 't', 'r' ), /*ALL INTERNAL BOXES - NEVER WRITTEN TO FILE!!*/ /*generic handlers*/ - GF_ISOM_BOX_TYPE_GNRM = GF_4CC( 'g', 'n', 'r', 'm' ), - GF_ISOM_BOX_TYPE_GNRV = GF_4CC( 'g', 'n', 'r', 'v' ), - GF_ISOM_BOX_TYPE_GNRA = GF_4CC( 'g', 'n', 'r', 'a' ), - /*storage of AU fragments (for MPEG-4 visual resync marker (video packets), located in stbl.*/ - GF_ISOM_BOX_TYPE_STSF = GF_4CC( 'S', 'T', 'S', 'F' ), + GF_ISOM_BOX_TYPE_GNRM = GF_4CC( 'G', 'N', 'R', 'M' ), + GF_ISOM_BOX_TYPE_GNRV = GF_4CC( 'G', 'N', 'R', 'V' ), + GF_ISOM_BOX_TYPE_GNRA = GF_4CC( 'G', 'N', 'R', 'A' ), /*base constructor of all hint formats (currently only RTP uses it)*/ GF_ISOM_BOX_TYPE_GHNT = GF_4CC( 'g', 'h', 'n', 't' ), /*for compatibility with old files hinted for DSS - needs special parsing*/ @@ -506,16 +455,196 @@ enum GF_ISOM_BOX_TYPE_BLOC = GF_4CC( 'b', 'l', 'o', 'c' ), GF_ISOM_BOX_TYPE_AINF = GF_4CC( 'a', 'i', 'n', 'f' ), - GF_ISOM_BOX_TYPE_UNKNOWN = GF_4CC( 'U', 'N', 'K', 'N' ), + /*JPEG2000*/ + GF_ISOM_BOX_TYPE_MJP2 = GF_4CC('m','j','p','2'), + GF_ISOM_BOX_TYPE_IHDR = GF_4CC('i','h','d','r'), + GF_ISOM_BOX_TYPE_JP = GF_4CC('j','P',' ',' '), + GF_ISOM_BOX_TYPE_JP2H = GF_4CC('j','p','2','h'), + GF_ISOM_BOX_TYPE_JP2K = GF_4CC('j','p','2','k'), + + GF_ISOM_BOX_TYPE_JPEG = GF_4CC('j','p','e','g'), + GF_ISOM_BOX_TYPE_PNG = GF_4CC('p','n','g',' '), + + /* apple QT box */ + GF_QT_BOX_TYPE_ALIS = GF_4CC('a','l','i','s'), + GF_QT_BOX_TYPE_LOAD = GF_4CC('l','o','a','d'), + GF_QT_BOX_TYPE_WIDE = GF_4CC('w','i','d','e'), + GF_QT_BOX_TYPE_GMIN = GF_4CC( 'g', 'm', 'i', 'n' ), + GF_QT_BOX_TYPE_TAPT = GF_4CC( 't', 'a', 'p', 't' ), + GF_QT_BOX_TYPE_CLEF = GF_4CC( 'c', 'l', 'e', 'f' ), + GF_QT_BOX_TYPE_PROF = GF_4CC( 'p', 'r', 'o', 'f' ), + GF_QT_BOX_TYPE_ENOF = GF_4CC( 'e', 'n', 'o', 'f' ), + GF_QT_BOX_TYPE_WAVE = GF_4CC('w','a','v','e'), + GF_QT_BOX_TYPE_CHAN = GF_4CC('c','h','a','n'), + GF_QT_BOX_TYPE_TERMINATOR = 0, + GF_QT_BOX_TYPE_ENDA = GF_4CC('e','n','d','a'), + GF_QT_BOX_TYPE_FRMA = GF_4CC('f','r','m','a'), + GF_QT_BOX_TYPE_TMCD = GF_4CC('t','m','c','d'), + GF_QT_BOX_TYPE_NAME = GF_4CC('n','a','m','e'), + GF_QT_BOX_TYPE_TCMI = GF_4CC('t','c','m','i'), + GF_QT_BOX_TYPE_FIEL = GF_4CC('f','i','e','l'), + GF_QT_BOX_TYPE_GAMA = GF_4CC('g','a','m','a'), + GF_QT_BOX_TYPE_CHRM = GF_4CC('c','h','r','m'), + + /* from drm_sample.c */ + GF_ISOM_BOX_TYPE_264B = GF_4CC('2','6','4','b'), + GF_ISOM_BOX_TYPE_265B = GF_4CC('2','6','5','b'), + + /*MPEG-H 3D audio*/ + GF_ISOM_BOX_TYPE_MHA1 = GF_4CC('m','h','a','1'), + GF_ISOM_BOX_TYPE_MHA2 = GF_4CC('m','h','a','2'), + GF_ISOM_BOX_TYPE_MHM1 = GF_4CC('m','h','m','1'), + GF_ISOM_BOX_TYPE_MHM2 = GF_4CC('m','h','m','2'), + GF_ISOM_BOX_TYPE_MHAC = GF_4CC('m','h','a','C'), + + GF_ISOM_BOX_TYPE_IPCM = GF_4CC('i','p','c','m'), + GF_ISOM_BOX_TYPE_FPCM = GF_4CC('f','p','c','m'), + GF_ISOM_BOX_TYPE_PCMC = GF_4CC('p','c','m','C'), + + GF_ISOM_BOX_TYPE_CHNL = GF_4CC('c','h','n','l'), + + GF_ISOM_BOX_TYPE_AUXV = GF_4CC('A','U','X','V'), + + GF_ISOM_BOX_TYPE_FLAC = GF_4CC( 'f', 'L', 'a', 'C' ), + GF_ISOM_BOX_TYPE_DFLA = GF_4CC( 'd', 'f', 'L', 'a' ), + + //internal only + GF_QT_SUBTYPE_RAW_AUD = GF_4CC('Q','T','R','A'), + GF_QT_SUBTYPE_RAW_VID = GF_4CC('Q','T','R','V'), + + }; +enum +{ + GF_ISOM_SAMPLE_ENTRY_UNKN = 0, + GF_ISOM_SAMPLE_ENTRY_VIDEO = GF_4CC('v','i','d','e'), + GF_ISOM_SAMPLE_ENTRY_AUDIO = GF_4CC('a','u','d','i'), + GF_ISOM_SAMPLE_ENTRY_MP4S = GF_4CC('m','p','4','s') + +}; + + +/* bitstream cookies used by isobmff lib*/ +#define GF_ISOM_BS_COOKIE_NO_LOGS 1 +#define GF_ISOM_BS_COOKIE_VISUAL_TRACK (1<<1) +#define GF_ISOM_BS_COOKIE_QT_CONV (1<<2) +#define GF_ISOM_BS_COOKIE_CLONE_TRACK (1<<3) + + +#ifndef GPAC_DISABLE_ISOM + + +#if defined(GPAC_DISABLE_ISOM_FRAGMENTS) && !defined(GPAC_DISABLE_ISOM_ADOBE) +#define GPAC_DISABLE_ISOM_ADOBE +#endif + +//internal flags (up to 16) +//if flag is set, position checking of child boxes is ignored +#define GF_ISOM_ORDER_FREEZE 1 + + /*the default size is 64, cause we need to handle large boxes... + + the child_boxes container is by default NOT created. When parsing a box and sub-boxes are detected, the list is created. + This list is destroyed before calling the final box destructor + This list is automatically taken into account during size() and write() functions, and + gf_isom_box_write shall not be called in XXXX_box_write on any box registered with the child list + + the full box version field is moved in the base box since we also need some internal flags, as u16 to + also be used for audio and video sample entries version field + */ +#define GF_ISOM_BOX \ + u32 type; \ + u64 size; \ + const struct box_registry_entry *registry;\ + GF_List *child_boxes; \ + u16 internal_flags; \ + u16 version; \ + +#define GF_ISOM_FULL_BOX \ + GF_ISOM_BOX \ + u32 flags; \ + +#define GF_ISOM_UUID_BOX \ + GF_ISOM_BOX \ + u8 uuid[16]; \ + u32 internal_4cc; \ + +typedef struct +{ + GF_ISOM_BOX +} GF_Box; + +typedef struct +{ + GF_ISOM_FULL_BOX +} GF_FullBox; + +typedef struct +{ + GF_ISOM_UUID_BOX +} GF_UUIDBox; + + +#define ISOM_DECL_BOX_ALLOC(__TYPE, __4cc) __TYPE *tmp; \ + GF_SAFEALLOC(tmp, __TYPE); \ + if (tmp==NULL) return NULL; \ + tmp->type = __4cc; + +#define ISOM_DECREASE_SIZE(__ptr, bytes) if (__ptr->size < (bytes) ) {\ + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[isom] not enough bytes in box %s: %d left, reading %d (file %s, line %d)\n", gf_4cc_to_str(__ptr->type), (u32) __ptr->size, (bytes), __FILE__, __LINE__ )); \ + return GF_ISOM_INVALID_FILE; \ + }\ + __ptr->size -= bytes; \ + +/*constructor*/ +GF_Box *gf_isom_box_new(u32 boxType); +//some boxes may have different syntax based on container. Use this constructor for this case +GF_Box *gf_isom_box_new_ex(u32 boxType, u32 parentType, Bool skip_logs, Bool is_root_box); + +GF_Err gf_isom_box_write(GF_Box *ptr, GF_BitStream *bs); +GF_Err gf_isom_box_read(GF_Box *ptr, GF_BitStream *bs); +void gf_isom_box_del(GF_Box *ptr); +GF_Err gf_isom_box_size(GF_Box *ptr); + +GF_Err gf_isom_clone_box(GF_Box *src, GF_Box **dst); + +GF_Err gf_isom_box_parse(GF_Box **outBox, GF_BitStream *bs); +GF_Err gf_isom_box_array_read(GF_Box *s, GF_BitStream *bs, GF_Err (*check_child_box)(GF_Box *par, GF_Box *b)); +GF_Err gf_isom_box_array_read_ex(GF_Box *parent, GF_BitStream *bs, GF_Err (*check_child_box)(GF_Box *par, GF_Box *b), u32 parent_type); + +GF_Err gf_isom_box_parse_ex(GF_Box **outBox, GF_BitStream *bs, u32 parent_type, Bool is_root_box); + +//writes box header - shall be called at the beginning of each xxxx_Write function +//this function is not factorized in order to let box serializer modify box type before writing +GF_Err gf_isom_box_write_header(GF_Box *ptr, GF_BitStream *bs); + +//writes box header then version+flags +GF_Err gf_isom_full_box_write(GF_Box *s, GF_BitStream *bs); + +void gf_isom_box_array_del(GF_List *child_boxes); +GF_Err gf_isom_box_array_write(GF_Box *parent, GF_List *list, GF_BitStream *bs); +GF_Err gf_isom_box_array_size(GF_Box *parent, GF_List *list); + +void gf_isom_check_position(GF_Box *s, GF_Box *child, u32 *pos); +void gf_isom_check_position_list(GF_Box *s, GF_List *childlist, u32 *pos); + +Bool gf_box_valid_in_parent(GF_Box *a, const char *parent_4cc); + +void gf_isom_box_array_del_parent(GF_List **child_boxes, GF_List *boxlist); +void gf_isom_box_array_reset_parent(GF_List **child_boxes, GF_List *boxlist); + +void gf_isom_box_freeze_order(GF_Box *box); + typedef struct { GF_ISOM_BOX /*note: the data is NEVER loaded to the mdat in this lib*/ u64 dataSize; - char *data; + /* store the file offset when parsing to access the raw data */ + u64 bsOffset; + u8 *data; } GF_MediaDataBox; typedef struct @@ -530,7 +659,7 @@ typedef struct typedef struct { GF_ISOM_FULL_BOX - u32 track_id; + GF_ISOTrackID track_id; u8 traf_bits; u8 trun_bits; u8 sample_bits; @@ -554,7 +683,7 @@ typedef struct typedef struct { GF_ISOM_BOX - char *data; + u8 *data; u32 dataSize; u32 original_4cc; } GF_UnknownBox; @@ -562,11 +691,11 @@ typedef struct typedef struct { GF_ISOM_UUID_BOX - char *data; + u8 *data; u32 dataSize; } GF_UnknownUUIDBox; -u32 gf_isom_solve_uuid_box(char *UUID); +u32 gf_isom_solve_uuid_box(u8 *UUID); typedef struct { @@ -576,7 +705,7 @@ typedef struct u32 timeScale; u64 duration; u64 original_duration; - u32 nextTrackID; + GF_ISOTrackID nextTrackID; u32 preferredRate; u16 preferredVolume; char reserved[10]; @@ -609,6 +738,7 @@ typedef struct u64 segmentDuration; s64 mediaTime; u32 mediaRate; + Bool was_empty_dur; } GF_EdtsEntry; typedef struct @@ -629,7 +759,7 @@ typedef struct { u32 boxType; u8 uuid[16]; - GF_List *other_boxes; + GF_List *boxes; } GF_UserDataMap; typedef struct @@ -654,6 +784,12 @@ typedef struct GF_ISOFile *mov; + Bool mvex_after_traks; + //for compressed mov, stores the difference between compressed and uncompressed payload + s32 compressed_diff; + //for compressed mov, indicates the file offset of the moov box start + u64 file_offset; + } GF_MovieBox; typedef struct @@ -661,7 +797,7 @@ typedef struct GF_ISOM_FULL_BOX u64 creationTime; u64 modificationTime; - u32 trackID; + GF_ISOTrackID trackID; u32 reserved1; u64 duration; u32 reserved2[2]; @@ -669,8 +805,9 @@ typedef struct u16 alternate_group; u16 volume; u16 reserved3; - u32 matrix[9]; + s32 matrix[9]; u32 width, height; + u64 initial_duration; } GF_TrackHeaderBox; typedef struct @@ -702,12 +839,12 @@ typedef struct struct __tag_meta_box *meta; GF_TrackGroupBox *groups; + GF_Box *Aperture; + GF_MovieBox *moov; /*private for media padding*/ u32 padding_bytes; /*private for editing*/ - char *name; - /*private for editing*/ Bool is_unpacked; /*private for checking dependency*/ u32 originalFile; @@ -717,14 +854,27 @@ typedef struct struct __sample_encryption_box *sample_encryption; /*private for SVC/MVC extractors resolution*/ - s32 extractor_mode; + GF_ISONaluExtractMode extractor_mode; Bool has_base_layer; + u32 pack_num_samples; + + u64 magic; + u32 index; + +#ifndef GPAC_DISABLE_ISOM_WRITE + u64 first_dts_chunk; + u32 nb_samples_in_cache; + u32 chunk_stsd_idx; + u32 chunk_cache_size; + GF_BitStream *chunk_cache; +#endif #ifndef GPAC_DISABLE_ISOM_FRAGMENTS u64 dts_at_seg_start; u32 sample_count_at_seg_start; Bool first_traf_merged; Bool present_in_scalable_segment; + u32 current_traf_stsd_idx; #endif } GF_TrackBox; @@ -758,6 +908,21 @@ typedef struct __tag_media_box GF_HandlerBox *handler; struct __tag_media_info_box *information; u64 BytesMissing; + + //all the following are only used for NALU-based tracks + //NALU reader + GF_BitStream *nalu_parser; + + GF_BitStream *nalu_out_bs; + GF_BitStream *nalu_ps_bs; + u8 *in_sample_buffer; + u32 in_sample_buffer_alloc; + u8 *tmp_nal_copy_buffer; + u32 tmp_nal_copy_buffer_alloc; + + GF_ISOSample *extracted_samp; + GF_BitStream *extracted_bs; + } GF_MediaBox; typedef struct @@ -772,6 +937,18 @@ typedef struct u64 reserved; } GF_VideoMediaHeaderBox; +typedef struct +{ + GF_ISOM_FULL_BOX + + u16 graphics_mode; + u16 op_color_red; + u16 op_color_green; + u16 op_color_blue; + u16 balance; + u16 reserved; +} GF_GenericMediaHeaderInfoBox; + typedef struct { GF_ISOM_FULL_BOX @@ -816,6 +993,15 @@ typedef struct GF_ISOM_FULL_BOX } GF_SceneMediaHeaderBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + + u32 width; + u32 height; +} GF_ApertureBox; + typedef struct { GF_ISOM_FULL_BOX @@ -842,6 +1028,12 @@ typedef struct GF_ISOM_DATAENTRY_FIELDS } GF_DataEntryURLBox; +typedef struct +{ + GF_ISOM_FULL_BOX + GF_ISOM_DATAENTRY_FIELDS +} GF_DataEntryAliasBox; + typedef struct { GF_ISOM_FULL_BOX @@ -870,6 +1062,9 @@ typedef struct u32 r_FirstSampleInEntry; u32 r_currentEntryIndex; u64 r_CurrentDTS; + + //stats for read + u32 max_ts_delta; } GF_TimeToSampleBox; @@ -894,29 +1089,10 @@ typedef struct /*Cache for read*/ u32 r_currentEntryIndex; u32 r_FirstSampleInEntry; -} GF_CompositionOffsetBox; - -typedef struct -{ - u32 SampleNumber; - u32 fragmentCount; - u16 *fragmentSizes; -} GF_StsfEntry; - -typedef struct -{ - GF_ISOM_FULL_BOX - GF_List *entryList; -#ifndef GPAC_DISABLE_ISOM_WRITE - /*Cache for write*/ - GF_StsfEntry *w_currentEntry; - u32 w_currentEntryIndex; -#endif - /*Cache for read*/ - u32 r_currentEntryIndex; - GF_StsfEntry *r_currentEntry; -} GF_SampleFragmentBox; + //stats for read + s32 max_ts_delta; +} GF_CompositionOffsetBox; #define GF_ISOM_SAMPLE_ENTRY_FIELDS \ @@ -924,9 +1100,8 @@ typedef struct u16 dataReferenceIndex; \ char reserved[ 6 ]; \ u32 internal_type; \ - GF_List *protections; -/*base sample entry box (never used but for typecasting)*/ +/*base sample entry box - used by some generic media sample descriptions of QT*/ typedef struct { GF_ISOM_SAMPLE_ENTRY_FIELDS @@ -942,10 +1117,90 @@ typedef struct /*box type as specified in the file (not this box's type!!)*/ u32 EntryType; - char *data; + u8 *data; u32 data_size; } GF_GenericSampleEntryBox; +typedef struct +{ + GF_ISOM_SAMPLE_ENTRY_FIELDS + + u32 flags; + u32 timescale; + u32 frame_duration; + u8 frames_per_counter_tick; +} GF_TimeCodeSampleEntryBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + + u16 text_font; + u16 text_face; + u16 text_size; + u16 text_color_red, text_color_green, text_color_blue; + u16 back_color_red, back_color_green, back_color_blue; + char *font; +} GF_TimeCodeMediaInformationBox; + +typedef struct +{ + GF_ISOM_BOX + + u8 field_count; + u8 field_order; +} GF_FieldInfoBox; + +typedef struct +{ + GF_ISOM_BOX + u32 gama; +} GF_GamaInfoBox; + +typedef struct +{ + GF_ISOM_BOX + u16 chroma; +} GF_ChromaInfoBox; + +typedef struct +{ + u32 label; + u32 flags; + Float coordinates[3]; +} GF_AudioChannelDescription; + +typedef struct +{ + GF_ISOM_FULL_BOX + + u32 layout_tag; + u32 bitmap; + u32 num_audio_description; + GF_AudioChannelDescription *audio_descs; + u8 *ext_data; + u32 ext_data_size; +} GF_ChannelLayoutInfoBox; + + +typedef struct +{ + GF_ISOM_BOX + + u32 preload_start_time; + u32 preload_duration; + u32 preload_flags; + u32 default_hints; +} GF_TrackLoadBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX + + GF_AudioChannelLayout layout; +} GF_ChannelLayoutBox; + typedef struct { GF_ISOM_FULL_BOX @@ -980,7 +1235,7 @@ typedef struct typedef struct { GF_ISOM_BOX - char *hdr; + u8 *hdr; u32 hdr_size; } GF_LASERConfigurationBox; @@ -1017,7 +1272,18 @@ typedef struct u32 horizOffD; u32 vertOffN; u32 vertOffD; -} GF_CleanAppertureBox; +} GF_CleanApertureBox; + + +typedef struct __ContentLightLevel { + GF_ISOM_BOX + GF_ContentLightLevelInfo clli; +} GF_ContentLightLevelBox; + +typedef struct ___MasteringDisplayColourVolume { + GF_ISOM_BOX + GF_MasteringDisplayColourVolumeInfo mdcv; +} GF_MasteringDisplayColourVolumeBox; typedef struct { @@ -1028,6 +1294,12 @@ typedef struct u32 reserved; } GF_CodingConstraintsBox; +typedef struct +{ + GF_ISOM_FULL_BOX + char *aux_track_type; +} GF_AuxiliaryTypeInfoBox; + typedef struct { GF_ISOM_BOX @@ -1035,16 +1307,26 @@ typedef struct u32 rvc_meta_idx; } GF_RVCConfigurationBox; -enum -{ - GF_ISOM_SAMPLE_ENTRY_UNKN = 0, - GF_ISOM_SAMPLE_ENTRY_VIDEO = GF_4CC('v','i','d','e'), - GF_ISOM_SAMPLE_ENTRY_AUDIO = GF_4CC('a','u','d','i') -}; +typedef struct { + GF_ISOM_BOX + Bool is_jp2; + + u32 colour_type; + u16 colour_primaries; + u16 transfer_characteristics; + u16 matrix_coefficients; + Bool full_range_flag; + u8 *opaque; + u32 opaque_size; + + u8 method, precedence, approx; +} GF_ColourInformationBox; + + +//do NOT extend this structure with boxes, children boxes shall go into the other_box field of the parent #define GF_ISOM_VISUAL_SAMPLE_ENTRY \ GF_ISOM_SAMPLE_ENTRY_FIELDS \ - u16 version; \ u16 revision; \ u32 vendor; \ u32 temporal_quality; \ @@ -1056,11 +1338,8 @@ enum char compressor_name[33]; \ u16 bit_depth; \ s16 color_table_index; \ - GF_PixelAspectRatioBox *pasp; \ - GF_CleanAppertureBox *clap; \ - struct __tag_protect_box *rinf; \ - GF_RVCConfigurationBox *rvcc; \ - + struct __tag_protect_box *rinf; \ + typedef struct { GF_ISOM_VISUAL_SAMPLE_ENTRY @@ -1075,6 +1354,12 @@ void gf_isom_video_sample_entry_size(GF_VisualSampleEntryBox *ent); void gf_isom_sample_entry_predestroy(GF_SampleEntryBox *ptr); + +GF_Box *gf_isom_box_find_child(GF_List *parent_child_list, u32 code); +void gf_isom_box_del_parent(GF_List **parent_child_list, GF_Box*b); +GF_Box *gf_isom_box_new_parent(GF_List **parent_child_list, u32 code); +Bool gf_isom_box_check_unique(GF_List *children, GF_Box *a); + typedef struct { GF_ISOM_BOX @@ -1087,12 +1372,76 @@ typedef struct GF_HEVCConfig *config; } GF_HEVCConfigurationBox; +typedef struct +{ + GF_ISOM_BOX + GF_AV1Config *config; +} GF_AV1ConfigurationBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + GF_VPConfig *config; +} GF_VPConfigurationBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + u16 primaryRChromaticity_x; + u16 primaryRChromaticity_y; + u16 primaryGChromaticity_x; + u16 primaryGChromaticity_y; + u16 primaryBChromaticity_x; + u16 primaryBChromaticity_y; + u16 whitePointChromaticity_x; + u16 whitePointChromaticity_y; + u32 luminanceMax; + u32 luminanceMin; +} GF_SMPTE2086MasteringDisplayMetadataBox; + +typedef struct { + GF_ISOM_FULL_BOX + u16 maxCLL; + u16 maxFALL; +} GF_VPContentLightLevelBox; + +typedef struct { + GF_ISOM_BOX + GF_DOVIDecoderConfigurationRecord DOVIConfig; +} GF_DOVIConfigurationBox; + +/*typedef struct { //extends Box('hvcE') + GF_ISOM_BOX + GF_HEVCConfig HEVCConfig; +} GF_DolbyVisionELHEVCConfigurationBox;*/ + +typedef struct { //extends HEVCSampleEntry('dvhe') + GF_DOVIConfigurationBox config; + //TODO: GF_DolbyVisionELHEVCConfigurationBox ELConfig; // optional +} GF_DolbyVisionHEVCSampleEntry; + typedef struct { GF_ISOM_BOX GF_3GPConfig cfg; } GF_3GPPConfigBox; +typedef struct +{ + GF_ISOM_BOX + + u32 width, height; + u16 nb_comp; + u8 bpc, Comp, UnkC, IPR; +} GF_J2KImageHeaderBox; + +typedef struct +{ + GF_ISOM_BOX + GF_J2KImageHeaderBox *ihdr; + GF_ColourInformationBox *colr; +} GF_J2KHeaderBox; + typedef struct { GF_ISOM_VISUAL_SAMPLE_ENTRY @@ -1107,9 +1456,15 @@ typedef struct /*hevc extension*/ GF_HEVCConfigurationBox *hevc_config; GF_HEVCConfigurationBox *lhvc_config; + /*av1 extension*/ + GF_AV1ConfigurationBox *av1_config; + /*vp8-9 extension*/ + GF_VPConfigurationBox *vp_config; + /*jp2k extension*/ + GF_J2KHeaderBox *jp2h; + /*dolbyvision extension*/ + GF_DOVIConfigurationBox *dovi_config; - /*ext descriptors*/ - GF_MPEG4ExtensionDescriptorsBox *descr; /*internally emulated esd*/ GF_ESD *emul_esd; @@ -1133,24 +1488,33 @@ typedef struct /*box type as specified in the file (not this box's type!!)*/ u32 EntryType; /*opaque description data (ESDS in MP4, SMI in SVQ3, ...)*/ - char *data; + u8 *data; u32 data_size; } GF_GenericVisualSampleEntryBox; +enum +{ + GF_ISOM_AUDIO_QTFF_NONE = 0, + //sample entry is QTFF and data in extensions() is NOT valid (conversion done by libisomedia) + GF_ISOM_AUDIO_QTFF_ON_NOEXT, + //sample entry is QTFF and data in extensions() is valid (import from QT) + GF_ISOM_AUDIO_QTFF_ON_EXT_VALID, + GF_ISOM_AUDIO_QTFF_CONVERT_FLAG = 1<<16 +}; #define GF_ISOM_AUDIO_SAMPLE_ENTRY \ GF_ISOM_SAMPLE_ENTRY_FIELDS \ - u16 version; \ u16 revision; \ u32 vendor; \ u16 channel_count; \ u16 bitspersample; \ u16 compression_id; \ u16 packet_size; \ + u32 qtff_mode; \ u16 samplerate_hi; \ u16 samplerate_lo; \ u8 extensions[36]; \ - + typedef struct { @@ -1170,6 +1534,54 @@ typedef struct GF_AC3Config cfg; } GF_AC3ConfigBox; +typedef struct +{ + GF_ISOM_FULL_BOX + u8 *data; + u32 dataSize; +} GF_FLACConfigBox; + +typedef struct +{ + GF_ISOM_BOX + + /*OpusSpecificBox*/ + /*u8 version; //1, field included in base box structure */ + u8 OutputChannelCount; //same value as the *Output Channel Count* field in the identification header defined in Ogg Opus [3] + u16 PreSkip; //The value of the PreSkip field shall be at least 80 milliseconds' worth of PCM samples even when removing any number of Opus samples which may or may not contain the priming samples. The PreSkip field is not used for discarding the priming samples at the whole playback at all since it is informative only, and that task falls on the Edit List Box. + u32 InputSampleRate; //The InputSampleRate field shall be set to the same value as the *Input Sample Rate* field in the identification header defined in Ogg Opus + s16 OutputGain; //The OutputGain field shall be set to the same value as the *Output Gain* field in the identification header define in Ogg Opus [3]. Note that the value is stored as 8.8 fixed-point. + u8 ChannelMappingFamily; //The ChannelMappingFamily field shall be set to the same value as the *Channel Mapping Family* field in the identification header defined in Ogg Opus [3]. Note that the value 255 may be used for an alternative to map channels by ISO Base Media native mapping. The details are described in 4.5.1. + + u8 StreamCount; // The StreamCount field shall be set to the same value as the *Stream Count* field in the identification header defined in Ogg Opus [3]. + u8 CoupledCount; // The CoupledCount field shall be set to the same value as the *Coupled Count* field in the identification header defined in Ogg Opus [3]. + u8 ChannelMapping[255]; // The ChannelMapping field shall be set to the same octet string as *Channel Mapping* field in the identi- fication header defined in Ogg Opus [3]. + + /*for internal box use only*/ +// int channels; +} GF_OpusSpecificBox; + +GF_Err gf_isom_opus_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_OpusSpecificBox *cfg, char *URLname, char *URNname, u32 *outDescriptionIndex); + +typedef struct +{ + GF_ISOM_BOX + u8 configuration_version; + u8 mha_pl_indication; + u8 reference_channel_layout; + u16 mha_config_size; + char *mha_config; +} GF_MHAConfigBox; + + +typedef struct +{ + GF_ISOM_FULL_BOX + u8 format_flags; + u8 PCM_sample_size; +} GF_PCMConfigBox; + + typedef struct { GF_ISOM_AUDIO_SAMPLE_ENTRY @@ -1181,6 +1593,16 @@ typedef struct //for AC3/EC3 audio GF_AC3ConfigBox *cfg_ac3; + + //for Opus + GF_OpusSpecificBox *cfg_opus; + + //for MPEG-H audio + GF_MHAConfigBox *cfg_mha; + + //for FLAC + GF_FLACConfigBox *cfg_flac; + } GF_MPEGAudioSampleEntryBox; /*this is the default visual sdst (to handle unknown media)*/ @@ -1190,7 +1612,7 @@ typedef struct /*box type as specified in the file (not this box's type!!)*/ u32 EntryType; /*opaque description data (ESDS in MP4, ...)*/ - char *data; + u8 *data; u32 data_size; } GF_GenericAudioSampleEntryBox; @@ -1240,6 +1662,57 @@ typedef struct } GF_MetaDataSampleEntryBox; +typedef struct +{ + u8 entry_type; + union { + u32 trackID; + u32 output_view_id; + u32 start_view_id; + }; + union { + u16 tierID; + u16 view_count; + }; +} MVCIEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + u32 multiview_group_id; + u16 num_entries; + MVCIEntry *entries; +} GF_MultiviewGroupBox; + +typedef struct +{ + u8 dep_comp_idc; + u16 ref_view_id; +} ViewIDRefViewEntry; + +typedef struct +{ + u16 view_id; + u16 view_order_index; + u8 texture_in_stream; + u8 texture_in_track; + u8 depth_in_stream; + u8 depth_in_track; + u8 base_view_type; + u16 num_ref_views; + ViewIDRefViewEntry *view_refs; +} ViewIDEntry; + +typedef struct +{ + GF_ISOM_FULL_BOX + u8 min_temporal_id; + u8 max_temporal_id; + u16 num_views; + ViewIDEntry *views; +} GF_ViewIdentifierBox; + + typedef struct { GF_ISOM_FULL_BOX @@ -1254,6 +1727,10 @@ typedef struct u32 sampleCount; u32 alloc_size; u32 *sizes; + //stats for read + u32 max_size; + u64 total_size; + u32 total_samples; } GF_SampleSizeBox; typedef struct @@ -1293,6 +1770,9 @@ typedef struct u32 firstSampleInCurrentChunk; u32 currentChunk; u32 ghostNumber; + + u32 w_lastSampleNumber; + u32 w_lastChunkNumber; } GF_SampleToChunkBox; typedef struct @@ -1338,7 +1818,7 @@ typedef struct typedef struct { GF_ISOM_FULL_BOX - u32 sampleCount; + u32 sampleCount, sample_alloc; /*each dep type is packed on 1 byte*/ u8 *sample_info; } GF_SampleDependencyTypeBox; @@ -1390,8 +1870,11 @@ typedef struct u32 aux_info_type_parameter; u8 default_sample_info_size; - u32 sample_count; + u32 sample_count, sample_alloc; u8 *sample_info_size; + + u32 cached_sample_num; + u32 cached_prev_size; } GF_SampleAuxiliaryInfoSizeBox; typedef struct @@ -1403,12 +1886,32 @@ typedef struct u8 default_sample_info_size; u32 entry_count; //1 or stco / trun count - u32 *offsets; - u64 *offsets_large; + u32 entry_alloc; + u64 *offsets; u64 offset_first_offset_field; + + u32 total_size; + u8 *cached_data; } GF_SampleAuxiliaryInfoOffsetBox; +typedef struct +{ + u32 sample_num; + u8 *moof_template; + u32 moof_template_size; + u64 seg_start_plus_one; + u64 sidx_start; + u64 sidx_end; + u64 moof_start; + u64 mdat_end; +} GF_TrafMapEntry; + +typedef struct +{ + u32 nb_entries, nb_alloc; + GF_TrafMapEntry *frag_starts; +} GF_TrafToSampleMap; typedef struct { @@ -1426,27 +1929,31 @@ typedef struct GF_DegradationPriorityBox *DegradationPriority; GF_PaddingBitsBox *PaddingBits; GF_SampleDependencyTypeBox *SampleDep; - GF_SampleFragmentBox *Fragments; -// GF_SubSampleInformationBox *SubSamples; + GF_TrafToSampleMap *traf_map; + GF_List *sub_samples; GF_List *sampleGroups; GF_List *sampleGroupsDescription; u32 nb_sgpd_in_stbl; - u32 nb_other_boxes_in_stbl; + u32 nb_stbl_boxes; GF_List *sai_sizes; GF_List *sai_offsets; - u32 MaxSamplePerChunk; + u32 MaxSamplePerChunk, MaxChunkSize; u16 groupID; u16 trackPriority; u32 currentEntryIndex; Bool no_sync_found; + + u32 r_last_chunk_num, r_last_sample_num, r_last_offset_in_chunk; } GF_SampleTableBox; +GF_Err stbl_AppendTrafMap(GF_SampleTableBox *stbl, Bool is_seg_start, u64 seg_start_offset, u64 frag_start_offset, u8 *moof_template, u32 moof_template_size, u64 sidx_start, u64 sidx_end); + typedef struct __tag_media_info_box { GF_ISOM_BOX @@ -1458,16 +1965,22 @@ typedef struct __tag_media_info_box u32 dataEntryIndex; } GF_MediaInformationBox; +GF_Err stbl_SetDependencyType(GF_SampleTableBox *stbl, u32 sampleNumber, u32 isLeading, u32 dependsOn, u32 dependedOn, u32 redundant); GF_Err stbl_AppendDependencyType(GF_SampleTableBox *stbl, u32 isLeading, u32 dependsOn, u32 dependedOn, u32 redundant); typedef struct { GF_ISOM_BOX - char *data; + u8 *data; u32 dataSize; u32 original_4cc; } GF_FreeSpaceBox; +typedef struct +{ + GF_ISOM_BOX +} GF_WideBox; /*Apple*/ + typedef struct { GF_ISOM_FULL_BOX @@ -1500,7 +2013,7 @@ typedef struct GF_ISOM_BOX u32 reference_type; u32 trackIDCount; - u32 *trackIDs; + GF_ISOTrackID *trackIDs; } GF_TrackReferenceTypeBox; typedef struct @@ -1534,7 +2047,7 @@ typedef struct GF_ISOM_FULL_BOX u32 switch_group; u32 alternate_group; - u32 sub_track_id; + GF_ISOTrackID sub_track_id; u64 attribute_count; u32 *attribute_list; } GF_SubTrackInformationBox; @@ -1685,19 +2198,9 @@ typedef struct { GF_ISOM_FULL_BOX u32 data_length; - char *data; + u8 *data; } GF_BinaryXMLBox; -typedef struct -{ - u64 extent_offset; - u64 extent_length; - u64 extent_index; -#ifndef GPAC_DISABLE_ISOM_WRITE - /*for storage only*/ - u64 original_extent_offset; -#endif -} GF_ItemExtentEntry; typedef struct { @@ -1852,6 +2355,7 @@ typedef struct { typedef struct { GF_ISOM_BOX GF_ItemPropertyContainerBox *property_container; + struct __item_association_box *property_association; } GF_ItemPropertiesBox; typedef struct { @@ -1906,19 +2410,24 @@ typedef struct __tag_mvex_box typedef struct { GF_ISOM_FULL_BOX - u32 trackID; + GF_ISOTrackID trackID; u32 def_sample_desc_index; u32 def_sample_duration; u32 def_sample_size; u32 def_sample_flags; GF_TrackBox *track; + + Bool cannot_use_default; + GF_ISOTrackID inherit_from_traf_id; + + GF_TrackFragmentRandomAccessBox *tfra; } GF_TrackExtendsBox; /*the TrackExtends contains default values for the track fragments*/ typedef struct { GF_ISOM_FULL_BOX - u32 trackID; + GF_ISOTrackID trackID; } GF_TrackExtensionPropertiesBox; /*indicates the seq num of this fragment*/ @@ -1934,14 +2443,17 @@ typedef struct GF_ISOM_BOX GF_MovieFragmentHeaderBox *mfhd; GF_List *TrackList; + GF_List *PSSHs; GF_ISOFile *mov; /*offset in the file of moof or mdat (whichever comes first) for this fragment*/ u64 fragment_offset; u32 mdat_size; - char *mdat; + u8 *mdat; + //when moof box was a compressed moof box, indicates the difference between the uncompressed size and the compressed size + s32 compressed_diff; //temp storage of prft box - u32 reference_track_ID; + GF_ISOTrackID reference_track_ID; u64 ntp, timestamp; } GF_MovieFragmentBox; @@ -1961,7 +2473,7 @@ enum typedef struct { GF_ISOM_FULL_BOX - u32 trackID; + GF_ISOTrackID trackID; /* all the following are optional fields */ u64 base_data_offset; u32 sample_desc_index; @@ -1969,7 +2481,6 @@ typedef struct u32 def_sample_size; u32 def_sample_flags; u32 EmptyDuration; - u8 IFrameSwitching; } GF_TrackFragmentHeaderBox; @@ -2006,32 +2517,46 @@ typedef struct GF_TFBaseMediaDecodeTimeBox *tfdt; u64 moof_start_in_bs; +#ifdef GF_ENABLE_CTRN + Bool use_ctrn; + Bool use_inherit; +#endif + + u32 interleave_id; + u8 merge_sample_interleave; + u8 use_sample_interleave; + u8 force_new_trun; + u8 IFrameSwitching; + u8 use_sdtp; } GF_TrackFragmentBox; +GF_TrackFragmentBox *gf_isom_get_traf(GF_ISOFile *mov, GF_ISOTrackID TrackID); + /*FLAGS for TRUN : specify what is written in the SampleTable of TRUN*/ enum { + /*common to both trun and ctrn*/ GF_ISOM_TRUN_DATA_OFFSET = 0x01, + /*trun flags*/ GF_ISOM_TRUN_FIRST_FLAG = 0x04, GF_ISOM_TRUN_DURATION = 0x100, GF_ISOM_TRUN_SIZE = 0x200, GF_ISOM_TRUN_FLAGS = 0x400, - GF_ISOM_TRUN_CTS_OFFSET = 0x800 -}; - -typedef struct -{ - GF_ISOM_FULL_BOX - u32 sample_count; - /*the following are optional fields */ - s32 data_offset; /* unsigned for version 0 */ - u32 first_sample_flags; - /*can be empty*/ - GF_List *entries; + GF_ISOM_TRUN_CTS_OFFSET = 0x800, + +#ifdef GF_ENABLE_CTRN + /*compact trun flags (not all of them, field indices are stored in trun box)*/ + GF_ISOM_CTRN_FIRST_SAMPLE = 1<<1, //0x00000002 + GF_ISOM_CTRN_DATAOFFSET_16 = 1<<2, //0x00000004 + GF_ISOM_CTRN_CTSO_MULTIPLIER = 1<<3, //0x00000008 + + GF_ISOM_CTRN_INHERIT_CTSO = 1<<4, + GF_ISOM_CTRN_INHERIT_FLAGS = 1<<5, + GF_ISOM_CTRN_INHERIT_SIZE = 1<<6, + GF_ISOM_CTRN_INHERIT_DUR = 1<<7 +#endif - /*in write mode with data caching*/ - GF_BitStream *cache; -} GF_TrackFragmentRunBox; +}; typedef struct { @@ -2042,16 +2567,52 @@ typedef struct /*internal*/ u32 SAP_type; + u64 dts; + u32 nb_pack; } GF_TrunEntry; + typedef struct { - GF_ISOM_BOX - u32 majorBrand; - u32 minorVersion; - u32 altCount; - u32 *altBrand; -} GF_SegmentTypeBox; + GF_ISOM_FULL_BOX + u32 sample_count; + /*the following are optional fields */ + /* unsigned for version 0 */ + s32 data_offset; + + u32 nb_samples, sample_alloc; + /*can be empty*/ + GF_TrunEntry *samples; + + /*only for trun, ignored for ctrn*/ + u32 first_sample_flags; + + /*in write mode with data caching*/ + GF_BitStream *cache; + +#ifdef GF_ENABLE_CTRN + /*the remaining is internal for compact trun*/ + /*use compact mode*/ + Bool use_ctrn; + /*we store the ctrn box flags here rather than in flags and swap when writing/dumping. This avoids overwriting the flags + set by the fragment writer*/ + u32 ctrn_flags; + /*set to default sample duration when writing, parsed from box otherwise. If 0, not used*/ + u32 ctso_multiplier; + u8 ctrn_first_dur, ctrn_first_size, ctrn_first_sample_flags, ctrn_first_ctts; + u8 ctrn_dur, ctrn_size, ctrn_sample_flags, ctrn_ctts; + /*use inherit in write mode- in the current version, only size will be set and all other fields inherited*/ + Bool use_inherit; +#endif + + u32 interleave_id; + u32 first_sample_idx; + u32 *sample_order; +} GF_TrackFragmentRunBox; + +#ifdef GF_ENABLE_CTRN +u32 gf_isom_ctrn_field_size_bits(u32 field_idx); +#endif #endif /*GPAC_DISABLE_ISOM_FRAGMENTS*/ @@ -2106,7 +2667,7 @@ typedef struct { GF_ISOM_BOX u8 reserved; - u8 prefered; + u8 preferred; u8 required; } GF_RelyHintBox; @@ -2290,8 +2851,9 @@ typedef struct { GF_ISOM_FULL_BOX u32 reserved; - char *data; + u8 *data; u32 dataSize; + Bool qt_style; } GF_DataBox; typedef struct @@ -2369,7 +2931,7 @@ typedef struct typedef struct { GF_ISOM_FULL_BOX - char *oma_ro; + u8 *oma_ro; u32 oma_ro_size; } GF_OMADRMRightsObjectBox; @@ -2383,7 +2945,6 @@ typedef struct __oma_kms_box GF_OMADRMAUFormatBox *fmt; } GF_OMADRMKMSBox; - typedef struct { Bool reference_type; @@ -2404,32 +2965,43 @@ typedef struct __sidx_box u64 first_offset; u32 nb_refs; GF_SIDXReference *refs; + //for trace only + s32 compressed_diff; } GF_SegmentIndexBox; +GF_Err gf_isom_set_fragment_template(GF_ISOFile *movie, u8 *tpl_data, u32 tpl_size, Bool *has_tfdt, GF_SegmentIndexBox **out_sidx); + +typedef struct +{ + u8 level; + u32 range_size; +} GF_SubsegmentRangeInfo; + typedef struct { u32 range_count; - u8 *levels; - u32 *range_sizes; -} GF_Subsegment; + GF_SubsegmentRangeInfo *ranges; +} GF_SubsegmentInfo; typedef struct __ssix_box { GF_ISOM_FULL_BOX - u32 subsegment_count; - GF_Subsegment *subsegments; + u32 subsegment_count, subsegment_alloc; + GF_SubsegmentInfo *subsegments; + //for trace only + s32 compressed_diff; } GF_SubsegmentIndexBox; typedef struct { - u32 track_id; + GF_ISOTrackID track_id; Bool padding_flag; u8 type; u32 grouping_type; u32 grouping_type_parameter; - u32 sub_track_id; -} GF_LevelAssignment; + GF_ISOTrackID sub_track_id; +} GF_LevelAssignment; typedef struct __leva_box { @@ -2446,6 +3018,21 @@ typedef struct __pcrInfo_box u64 *pcr_values; } GF_PcrInfoBox; + +typedef struct +{ + GF_ISOM_FULL_BOX + char *scheme_id_uri; + char *value; + u32 timescale; + u64 presentation_time_delta; + u32 event_duration; + u32 event_id; + u8 *message_data; + u32 message_data_size; +} GF_EventMessageBox; + + #ifndef GPAC_DISABLE_ISOM_ADOBE /*Adobe specific boxes*/ @@ -2496,6 +3083,7 @@ typedef struct __adobe_bootstrap_info_box GF_List *quality_entry_table; char *drm_data; char *meta_data; + //entries in these two lists are NOT registered with the box child_boxes because of the inbetween 8 bits !! u8 segment_run_table_count; GF_List *segment_run_table_entries; u8 fragment_run_table_count; @@ -2601,6 +3189,11 @@ typedef struct u8 SAP_type; } GF_SAPEntry; +/*SAPEntry - 'sync' type*/ +typedef struct +{ + u8 NALU_type; +} GF_SYNCEntry; /*Operating Points Information - 'oinf' type*/ typedef struct @@ -2681,6 +3274,7 @@ typedef struct } LHEVC_DependentLayer; + /* CENC stuff */ @@ -2691,8 +3285,8 @@ typedef struct u8 crypt_byte_block, skip_byte_block; u8 IsProtected; u8 Per_Sample_IV_size; - bin128 KID; u8 constant_IV_size; + bin128 KID; bin128 constant_IV; } GF_CENCSampleEncryptionGroupEntry; @@ -2722,7 +3316,7 @@ typedef struct __cenc_tenc_box typedef struct __piff_tenc_box { GF_ISOM_UUID_BOX - u8 version; + /*u8 version; field in included in base box version */ u32 flags; u32 AlgorithmID; @@ -2733,7 +3327,7 @@ typedef struct __piff_tenc_box typedef struct { GF_ISOM_UUID_BOX - u8 version; + /*u8 version; field in included in base box version */ u32 flags; bin128 SystemID; @@ -2745,13 +3339,14 @@ typedef struct typedef struct __sample_encryption_box { GF_ISOM_UUID_BOX - u8 version; + /*u8 version; field in included in base box version */ u32 flags; Bool is_piff; GF_List *samp_aux_info; /*GF_CENCSampleAuxInfo*/ u64 bs_offset; + Bool load_needed; #ifndef GPAC_DISABLE_ISOM_FRAGMENTS /*pointer to container traf*/ @@ -2771,7 +3366,7 @@ typedef struct __sample_encryption_box typedef struct __traf_mss_timeext_box { GF_ISOM_UUID_BOX - u8 version; + /*u8 version; field in included in base box version */ u32 flags; u64 absolute_time_in_track_timescale; @@ -2781,14 +3376,14 @@ typedef struct __traf_mss_timeext_box GF_SampleEncryptionBox *gf_isom_create_piff_psec_box(u8 version, u32 flags, u32 AlgorithmID, u8 IV_size, bin128 KID); GF_SampleEncryptionBox * gf_isom_create_samp_enc_box(u8 version, u32 flags); -void gf_isom_cenc_get_default_info_ex(GF_TrackBox *trak, u32 sampleDescriptionIndex, u32 *default_IsEncrypted, u8 *default_IV_size, bin128 *default_KID); -void gf_isom_cenc_get_default_pattern_info_ex(GF_TrackBox *trak, u32 sampleDescriptionIndex, u8 *default_crypt_byte_block, u8 *default_skip_byte_block); +void gf_isom_cenc_get_default_info_ex(GF_TrackBox *trak, u32 sampleDescriptionIndex, u32 *container_type, Bool *default_IsEncrypted, u8 *default_IV_size, bin128 *default_KID, u8 *constant_IV_size, bin128 *constant_IV, u8 *crypt_byte_block, u8 *skip_byte_block); + + #ifndef GPAC_DISABLE_ISOM_FRAGMENTS -GF_Err gf_isom_get_sample_cenc_info_ex(GF_TrackBox *trak, GF_TrackFragmentBox *traf, GF_SampleEncryptionBox *senc, u32 sample_number, u32 *IsEncrypted, u8 *IV_size, bin128 *KID, - u8 *crypt_byte_block, u8 *skip_byte_block, u8 *constant_IV_size, bin128 *constant_IV); +GF_Err gf_isom_get_sample_cenc_info_ex(GF_TrackBox *trak, GF_TrackFragmentBox *traf, GF_SampleEncryptionBox *senc, u32 sample_number, Bool *IsEncrypted, u8 *IV_size, bin128 *KID, u8 *crypt_byte_block, u8 *skip_byte_block, u8 *constant_IV_size, bin128 *constant_IV); GF_Err senc_Parse(GF_BitStream *bs, GF_TrackBox *trak, GF_TrackFragmentBox *traf, GF_SampleEncryptionBox *ptr); #else -GF_Err gf_isom_get_sample_cenc_info_ex(GF_TrackBox *trak, void *traf, uGF_SampleEncryptionBox *senc, 32 sample_number, u32 *IsEncrypted, u8 *IV_size, bin128 *KID, +GF_Err gf_isom_get_sample_cenc_info_ex(GF_TrackBox *trak, void *traf, uGF_SampleEncryptionBox *senc, u32 sample_number, Bool *IsEncrypted, u8 *IV_size, bin128 *KID, u8 *crypt_byte_block, u8 *skip_byte_block, u8 *constant_IV_size, bin128 *constant_IV); GF_Err senc_Parse(GF_BitStream *bs, GF_TrackBox *trak, void *traf, GF_SampleEncryptionBox *ptr); #endif @@ -2806,7 +3401,7 @@ typedef struct __adobe_enc_info_box typedef struct __adobe_flash_access_params_box { GF_ISOM_BOX - char *metadata; /*base-64 encoded metadata used by the DRM client to retrieve decrypted key*/ + u8 *metadata; /*base-64 encoded metadata used by the DRM client to retrieve decrypted key*/ } GF_AdobeFlashAccessParamsBox; typedef struct __adobe_key_info_box @@ -2848,7 +3443,7 @@ typedef struct __adobe_drm_key_management_system_box typedef struct { GF_ISOM_FULL_BOX - u32 refTrackID; + GF_ISOTrackID refTrackID; u64 ntp, timestamp; } GF_ProducerReferenceTimeBox; @@ -2859,17 +3454,6 @@ typedef struct { u32 image_height; } GF_ImageSpatialExtentsPropertyBox; -typedef struct { - GF_ISOM_BOX - u32 colour_type; - u16 colour_primaries; - u16 transfer_characteristics; - u16 matrix_coefficients; - Bool full_range_flag; - u8 *opaque; - u32 opaque_size; -} GF_ColourInformationBox; - typedef struct { GF_ISOM_FULL_BOX u8 num_channels; @@ -2887,13 +3471,19 @@ typedef struct { u8 angle; } GF_ImageRotationBox; +typedef struct +{ + u8 essential; + u32 index; +} GF_ItemPropertyAssociationSlot; + typedef struct { u32 item_id; - GF_List *essential; - GF_List *property_index; + GF_ItemPropertyAssociationSlot *associations; + u32 nb_associations; } GF_ItemPropertyAssociationEntry; -typedef struct { +typedef struct __item_association_box { GF_ISOM_FULL_BOX GF_List *entries; } GF_ItemPropertyAssociationBox; @@ -2903,7 +3493,7 @@ typedef struct { GF_ISOM_FULL_BOX char *aux_urn; u32 data_size; - char *data; + u8 *data; } GF_AuxiliaryTypePropertyBox; typedef struct { @@ -3019,11 +3609,11 @@ typedef struct */ /*regular file IO*/ -#define GF_ISOM_DATA_FILE 0x01 -/*File Maping object, read-only mode on complete files (no download)*/ -#define GF_ISOM_DATA_FILE_MAPPING 0x02 +#define GF_ISOM_DATA_FILE 0x01 /*External file object. Needs implementation*/ -#define GF_ISOM_DATA_FILE_EXTERN 0x03 +#define GF_ISOM_DATA_FILE_EXTERN 0x03 +/*regular memory IO*/ +#define GF_ISOM_DATA_MEM 0x04 /*Data Map modes*/ enum @@ -3048,6 +3638,7 @@ enum u64 curPos; \ u8 mode; \ GF_BitStream *bs;\ + u64 last_read_offset;\ char *szName; typedef struct __tag_data_map @@ -3072,7 +3663,7 @@ typedef struct GF_ISOM_BASE_DATA_HANDLER char *name; u64 file_size; - char *byte_map; + u8 *byte_map; u64 byte_pos; } GF_FileMappingDataMap; @@ -3080,25 +3671,20 @@ GF_Err gf_isom_datamap_new(const char *location, const char *parentPath, u8 mode void gf_isom_datamap_del(GF_DataMap *ptr); GF_Err gf_isom_datamap_open(GF_MediaBox *minf, u32 dataRefIndex, u8 Edit); void gf_isom_datamap_close(GF_MediaInformationBox *minf); -u32 gf_isom_datamap_get_data(GF_DataMap *map, char *buffer, u32 bufferLength, u64 Offset); +u32 gf_isom_datamap_get_data(GF_DataMap *map, u8 *buffer, u32 bufferLength, u64 Offset); /*File-based data map*/ GF_DataMap *gf_isom_fdm_new(const char *sPath, u8 mode); void gf_isom_fdm_del(GF_FileDataMap *ptr); -u32 gf_isom_fdm_get_data(GF_FileDataMap *ptr, char *buffer, u32 bufferLength, u64 fileOffset); +u32 gf_isom_fdm_get_data(GF_FileDataMap *ptr, u8 *buffer, u32 bufferLength, u64 fileOffset); #ifndef GPAC_DISABLE_ISOM_WRITE GF_DataMap *gf_isom_fdm_new_temp(const char *sTempPath); #endif -/*file-mapping, read only*/ -GF_DataMap *gf_isom_fmo_new(const char *sPath, u8 mode); -void gf_isom_fmo_del(GF_FileMappingDataMap *ptr); -u32 gf_isom_fmo_get_data(GF_FileMappingDataMap *ptr, char *buffer, u32 bufferLength, u64 fileOffset); - #ifndef GPAC_DISABLE_ISOM_WRITE u64 gf_isom_datamap_get_offset(GF_DataMap *map); -GF_Err gf_isom_datamap_add_data(GF_DataMap *ptr, char *data, u32 dataSize); +GF_Err gf_isom_datamap_add_data(GF_DataMap *ptr, u8 *data, u32 dataSize); #endif void gf_isom_datamap_flush(GF_DataMap *map); @@ -3125,7 +3711,7 @@ void gf_isom_datamap_flush(GF_DataMap *map); #define GF_ISOM_GET_FRAG_DEPEND_FLAGS(lead, depends, depended, redundant) ( (lead<<26) | (depends<<24) | (depended<<22) | (redundant<<20) ) #define GF_ISOM_RESET_FRAG_DEPEND_FLAGS(flags) flags = flags & 0xFFFFF -GF_TrackExtendsBox *GetTrex(GF_MovieBox *moov, u32 TrackID); +GF_TrackExtendsBox *GetTrex(GF_MovieBox *moov, GF_ISOTrackID TrackID); #endif enum @@ -3154,16 +3740,17 @@ struct __tag_isom { GF_DataMap *editFileMap; /*the interleaving time for dummy mode (in movie TimeScale)*/ u32 interleavingTime; + GF_ISOTrackID last_created_track_id; #endif - u8 openMode; + GF_ISOOpenMode openMode; u8 storageMode; /*if true 3GPP text streams are read as MPEG-4 StreamingText*/ u8 convert_streaming_text; u8 is_jp2; u8 force_co64; - - Bool keep_utc, drop_date_version_info; + u64 next_flush_chunk_time; + Bool keep_utc; /*main boxes for fast access*/ /*moov*/ GF_MovieBox *moov; @@ -3176,7 +3763,16 @@ struct __tag_isom { /*meta box if any*/ GF_MetaBox *meta; - Bool dump_mode_alloc; + s64 read_byte_offset; + u64 bytes_removed; + + Bool disable_odf_translate; + + GF_ISOCompressMode compress_mode; + Bool force_compress; + + void (*progress_cbk)(void *udta, u64 nb_done, u64 nb_total); + void *progress_cbk_udta; #ifndef GPAC_DISABLE_ISOM_FRAGMENTS u32 FragmentsFlags, NextMoofNumber; @@ -3188,12 +3784,14 @@ struct __tag_isom { u64 segment_start; GF_List *moof_list; - Bool use_segments, moof_first, append_segment, styp_written; + Bool use_segments, moof_first, append_segment, styp_written, force_moof_base_offset; /*used when building single-indexed self initializing media segments*/ GF_SegmentIndexBox *root_sidx; u64 root_sidx_offset; u32 root_sidx_index; + Bool dyn_root_sidx; + GF_SubsegmentIndexBox *root_ssix; Bool is_index_segment; @@ -3201,6 +3799,25 @@ struct __tag_isom { /* 0: no moof found yet, 1: 1 moof found, 2: next moof found */ Bool single_moof_mode; u32 single_moof_state; + + Bool sample_groups_in_traf; + + /* optional mfra box used in write mode */ + GF_MovieFragmentRandomAccessBox *mfra; + + Bool store_traf_map; + Bool signal_frag_bounds; + u64 sidx_start_offset, sidx_end_offset; + u64 styp_start_offset; + u64 mdat_end_offset; + GF_Box *seg_ssix, *seg_styp; + + u32 sidx_pts_store_alloc, sidx_pts_store_count; + u64 *sidx_pts_store, *sidx_pts_next_store; + + GF_SegmentIndexBox *main_sidx; + u64 main_sidx_end_pos; + #endif GF_ProducerReferenceTimeBox *last_producer_ref_time; @@ -3211,27 +3828,40 @@ struct __tag_isom { s32 es_id_default_sync; Bool is_smooth; + + GF_Err (*on_block_out)(void *usr_data, u8 *block, u32 block_size); + GF_Err (*on_block_patch)(void *usr_data, u8 *block, u32 block_size, u64 block_offset, Bool is_insert); + void *on_block_out_usr_data; + u32 on_block_out_block_size; + + //in block disptach mode we don't have the full file, keep the position + u64 fragmented_file_pos; + u8 *block_buffer; + u32 block_buffer_size; + + u32 nb_box_init_seg; }; /*time function*/ u64 gf_isom_get_mp4time(); /*set the last error of the file. if file is NULL, set the static error (used for IO errors*/ void gf_isom_set_last_error(GF_ISOFile *the_file, GF_Err error); -GF_Err gf_isom_parse_movie_boxes(GF_ISOFile *mov, u64 *bytesMissing, Bool progressive_mode); +GF_Err gf_isom_parse_movie_boxes(GF_ISOFile *mov, u32 *boxType, u64 *bytesMissing, Bool progressive_mode); GF_ISOFile *gf_isom_new_movie(); /*Movie and Track access functions*/ GF_TrackBox *gf_isom_get_track_from_file(GF_ISOFile *the_file, u32 trackNumber); GF_TrackBox *gf_isom_get_track(GF_MovieBox *moov, u32 trackNumber); -GF_TrackBox *gf_isom_get_track_from_id(GF_MovieBox *moov, u32 trackID); +GF_TrackBox *gf_isom_get_track_from_id(GF_MovieBox *moov, GF_ISOTrackID trackID); GF_TrackBox *gf_isom_get_track_from_original_id(GF_MovieBox *moov, u32 originalID, u32 originalFile); -u32 gf_isom_get_tracknum_from_id(GF_MovieBox *moov, u32 trackID); +u32 gf_isom_get_tracknum_from_id(GF_MovieBox *moov, GF_ISOTrackID trackID); /*open a movie*/ -GF_ISOFile *gf_isom_open_file(const char *fileName, u32 OpenMode, const char *tmp_dir); +GF_ISOFile *gf_isom_open_file(const char *fileName, GF_ISOOpenMode OpenMode, const char *tmp_dir); /*close and delete a movie*/ void gf_isom_delete_movie(GF_ISOFile *mov); + /*StreamDescription reconstruction Functions*/ -GF_Err GetESD(GF_MovieBox *moov, u32 trackID, u32 StreamDescIndex, GF_ESD **outESD); -GF_Err GetESDForTime(GF_MovieBox *moov, u32 trackID, u64 CTS, GF_ESD **outESD); +GF_Err GetESD(GF_MovieBox *moov, GF_ISOTrackID trackID, u32 StreamDescIndex, GF_ESD **outESD); +GF_Err GetESDForTime(GF_MovieBox *moov, GF_ISOTrackID trackID, u64 CTS, GF_ESD **outESD); GF_Err Media_GetSampleDesc(GF_MediaBox *mdia, u32 SampleDescIndex, GF_SampleEntryBox **out_entry, u32 *dataRefIndex); GF_Err Media_GetSampleDescIndex(GF_MediaBox *mdia, u64 DTS, u32 *sampleDescIndex); /*get esd for given sample desc - @@ -3252,8 +3882,15 @@ GF_Err Media_RewriteODFrame(GF_MediaBox *mdia, GF_ISOSample *sample); GF_Err Media_FindDataRef(GF_DataReferenceBox *dref, char *URLname, char *URNname, u32 *dataRefIndex); Bool Media_IsSelfContained(GF_MediaBox *mdia, u32 StreamDescIndex); +typedef enum +{ + ISOM_DREF_MIXED = 0, + ISOM_DREF_SELF, + ISOM_DREF_EXT, +} GF_ISOMDataRefAllType; +GF_ISOMDataRefAllType Media_SelfContainedType(GF_MediaBox *mdia); -GF_TrackBox *GetTrackbyID(GF_MovieBox *moov, u32 TrackID); +GF_TrackBox *GetTrackbyID(GF_MovieBox *moov, GF_ISOTrackID TrackID); /*check the TimeToSample for the given time and return the Sample number if the entry is not found, return the closest sampleNumber in prevSampleNumber and 0 in sampleNumber @@ -3268,14 +3905,12 @@ GF_Err stbl_GetSampleDTS(GF_TimeToSampleBox *stts, u32 SampleNumber, u64 *DTS); GF_Err stbl_GetSampleDTS_and_Duration(GF_TimeToSampleBox *stts, u32 SampleNumber, u64 *DTS, u32 *duration); /*find a RAP or set the prev / next RAPs if vars are passed*/ -GF_Err stbl_GetSampleRAP(GF_SyncSampleBox *stss, u32 SampleNumber, SAPType *IsRAP, u32 *prevRAP, u32 *nextRAP); +GF_Err stbl_GetSampleRAP(GF_SyncSampleBox *stss, u32 SampleNumber, GF_ISOSAPType *IsRAP, u32 *prevRAP, u32 *nextRAP); /*same as above but only look for open-gop RAPs and GDR (roll)*/ -GF_Err stbl_SearchSAPs(GF_SampleTableBox *stbl, u32 SampleNumber, SAPType *IsRAP, u32 *prevRAP, u32 *nextRAP); -GF_Err stbl_GetSampleInfos(GF_SampleTableBox *stbl, u32 sampleNumber, u64 *offset, u32 *chunkNumber, u32 *descIndex, u8 *isEdited); +GF_Err stbl_SearchSAPs(GF_SampleTableBox *stbl, u32 SampleNumber, GF_ISOSAPType *IsRAP, u32 *prevRAP, u32 *nextRAP); +GF_Err stbl_GetSampleInfos(GF_SampleTableBox *stbl, u32 sampleNumber, u64 *offset, u32 *chunkNumber, u32 *descIndex, GF_StscEntry **scsc_entry); GF_Err stbl_GetSampleShadow(GF_ShadowSyncBox *stsh, u32 *sampleNumber, u32 *syncNum); GF_Err stbl_GetPaddingBits(GF_PaddingBitsBox *padb, u32 SampleNumber, u8 *PadBits); -u32 stbl_GetSampleFragmentCount(GF_SampleFragmentBox *stsf, u32 sampleNumber); -u32 stbl_GetSampleFragmentSize(GF_SampleFragmentBox *stsf, u32 sampleNumber, u32 FragmentIndex); GF_Err stbl_GetSampleDepType(GF_SampleDependencyTypeBox *stbl, u32 SampleNumber, u32 *isLeading, u32 *dependsOn, u32 *dependedOn, u32 *redundant); @@ -3295,28 +3930,30 @@ GF_UserDataMap *udta_getEntry(GF_UserDataBox *ptr, u32 box_type, bin128 *uuid); #ifndef GPAC_DISABLE_ISOM_WRITE GF_Err FlushCaptureMode(GF_ISOFile *movie); -GF_Err CanAccessMovie(GF_ISOFile *movie, u32 Mode); -GF_ISOFile *gf_isom_create_movie(const char *fileName, u32 OpenMode, const char *tmp_dir); -void gf_isom_insert_moov(GF_ISOFile *file); +GF_Err CanAccessMovie(GF_ISOFile *movie, GF_ISOOpenMode Mode); +GF_ISOFile *gf_isom_create_movie(const char *fileName, GF_ISOOpenMode OpenMode, const char *tmp_dir); +GF_Err gf_isom_insert_moov(GF_ISOFile *file); -GF_Err WriteToFile(GF_ISOFile *movie); +GF_Err WriteToFile(GF_ISOFile *movie, Bool for_fragments); GF_Err Track_SetStreamDescriptor(GF_TrackBox *trak, u32 StreamDescriptionIndex, u32 DataReferenceIndex, GF_ESD *esd, u32 *outStreamIndex); -u8 RequestTrack(GF_MovieBox *moov, u32 TrackID); +u8 RequestTrack(GF_MovieBox *moov, GF_ISOTrackID TrackID); /*Track-Media setup*/ GF_Err NewMedia(GF_MediaBox **mdia, u32 MediaType, u32 TimeScale); GF_Err Media_ParseODFrame(GF_MediaBox *mdia, const GF_ISOSample *sample, GF_ISOSample **od_samp); GF_Err Media_AddSample(GF_MediaBox *mdia, u64 data_offset, const GF_ISOSample *sample, u32 StreamDescIndex, u32 syncShadowNumber); -GF_Err Media_CreateDataRef(GF_DataReferenceBox *dref, char *URLname, char *URNname, u32 *dataRefIndex); +GF_Err Media_CreateDataRef(GF_ISOFile *file, GF_DataReferenceBox *dref, char *URLname, char *URNname, u32 *dataRefIndex); +GF_Err Media_SetDrefURL(GF_DataEntryURLBox *dref_entry, const char *origName, const char *finalName); + /*update a media sample. ONLY in edit mode*/ GF_Err Media_UpdateSample(GF_MediaBox *mdia, u32 sampleNumber, GF_ISOSample *sample, Bool data_only); GF_Err Media_UpdateSampleReference(GF_MediaBox *mdia, u32 sampleNumber, GF_ISOSample *sample, u64 data_offset); /*addition in the sample tables*/ -GF_Err stbl_AddDTS(GF_SampleTableBox *stbl, u64 DTS, u32 *sampleNumber, u32 LastAUDefDuration); +GF_Err stbl_AddDTS(GF_SampleTableBox *stbl, u64 DTS, u32 *sampleNumber, u32 LastAUDefDuration, u32 nb_pack_samples); GF_Err stbl_AddCTS(GF_SampleTableBox *stbl, u32 sampleNumber, s32 CTSoffset); -GF_Err stbl_AddSize(GF_SampleSizeBox *stsz, u32 sampleNumber, u32 size); +GF_Err stbl_AddSize(GF_SampleSizeBox *stsz, u32 sampleNumber, u32 size, u32 nb_pack_samples); GF_Err stbl_AddRAP(GF_SyncSampleBox *stss, u32 sampleNumber); GF_Err stbl_AddShadow(GF_ShadowSyncBox *stsh, u32 sampleNumber, u32 shadowNumber); -GF_Err stbl_AddChunkOffset(GF_MediaBox *mdia, u32 sampleNumber, u32 StreamDescIndex, u64 offset); +GF_Err stbl_AddChunkOffset(GF_MediaBox *mdia, u32 sampleNumber, u32 StreamDescIndex, u64 offset, u32 nb_pack_samples); /*NB - no add for padding, this is done only through SetPaddingBits*/ GF_Err stbl_AddSampleFragment(GF_SampleTableBox *stbl, u32 sampleNumber, u16 size); @@ -3332,7 +3969,7 @@ GF_Err stbl_SetPaddingBits(GF_SampleTableBox *stbl, u32 SampleNumber, u8 bits); /*for adding fragmented samples*/ GF_Err stbl_SampleSizeAppend(GF_SampleSizeBox *stsz, u32 data_size); /*writing of the final chunk info in edit mode*/ -GF_Err stbl_SetChunkAndOffset(GF_SampleTableBox *stbl, u32 sampleNumber, u32 StreamDescIndex, GF_SampleToChunkBox *the_stsc, GF_Box **the_stco, u64 data_offset, u8 forceNewChunk); +GF_Err stbl_SetChunkAndOffset(GF_SampleTableBox *stbl, u32 sampleNumber, u32 StreamDescIndex, GF_SampleToChunkBox *the_stsc, GF_Box **the_stco, u64 data_offset, Bool forceNewChunk, u32 nb_samp); /*EDIT LIST functions*/ GF_EdtsEntry *CreateEditEntry(u64 EditDuration, u64 MediaTime, u8 EditMode); @@ -3340,27 +3977,27 @@ GF_Err stbl_SetRedundant(GF_SampleTableBox *stbl, u32 sampleNumber); GF_Err stbl_AddRedundant(GF_SampleTableBox *stbl, u32 sampleNumber); /*REMOVE functions*/ -GF_Err stbl_RemoveDTS(GF_SampleTableBox *stbl, u32 sampleNumber, u32 LastAUDefDuration); -GF_Err stbl_RemoveCTS(GF_SampleTableBox *stbl, u32 sampleNumber); -GF_Err stbl_RemoveSize(GF_SampleSizeBox *stsz, u32 sampleNumber); -GF_Err stbl_RemoveChunk(GF_SampleTableBox *stbl, u32 sampleNumber); +GF_Err stbl_RemoveDTS(GF_SampleTableBox *stbl, u32 sampleNumber, u32 nb_samples, u32 LastAUDefDuration); +GF_Err stbl_RemoveCTS(GF_SampleTableBox *stbl, u32 sampleNumber, u32 nb_samples); +GF_Err stbl_RemoveSize(GF_SampleTableBox *stbl, u32 sampleNumber, u32 nb_samples); +GF_Err stbl_RemoveChunk(GF_SampleTableBox *stbl, u32 sampleNumber, u32 nb_samples); GF_Err stbl_RemoveRAP(GF_SampleTableBox *stbl, u32 sampleNumber); -GF_Err stbl_RemoveShadow(GF_ShadowSyncBox *stsh, u32 sampleNumber); +GF_Err stbl_RemoveShadow(GF_SampleTableBox *stbl, u32 sampleNumber); GF_Err stbl_RemovePaddingBits(GF_SampleTableBox *stbl, u32 SampleNumber); -GF_Err stbl_RemoveSampleFragments(GF_SampleTableBox *stbl, u32 sampleNumber); -GF_Err stbl_RemoveRedundant(GF_SampleTableBox *stbl, u32 SampleNumber); +GF_Err stbl_RemoveRedundant(GF_SampleTableBox *stbl, u32 SampleNumber, u32 nb_samples); GF_Err stbl_RemoveSubSample(GF_SampleTableBox *stbl, u32 SampleNumber); GF_Err stbl_RemoveSampleGroup(GF_SampleTableBox *stbl, u32 SampleNumber); #ifndef GPAC_DISABLE_ISOM_FRAGMENTS GF_Err gf_isom_close_fragments(GF_ISOFile *movie); -GF_Err gf_isom_copy_sample_group_entry_to_traf(GF_TrackFragmentBox *traf, GF_SampleTableBox *stbl, u32 grouping_type, u32 grouping_type_parameter, u32 sampleGroupDescriptionIndex, Bool sgpd_in_traf); #endif -Bool gf_isom_is_identical_sgpd(void *ptr1, void *ptr2, u32 grouping_type); +GF_Err gf_isom_flush_sidx(GF_ISOFile *movie, u32 sidx_max_size, Bool exact_range); #endif /*GPAC_DISABLE_ISOM_WRITE*/ +Bool gf_isom_is_identical_sgpd(void *ptr1, void *ptr2, u32 grouping_type); + GF_DefaultSampleGroupDescriptionEntry * gf_isom_get_sample_group_info_entry(GF_ISOFile *the_file, GF_TrackBox *trak, u32 grouping_type, u32 sample_description_index, u32 *default_index, GF_SampleGroupDescriptionBox **out_sgdp); GF_Err GetNextMediaTime(GF_TrackBox *trak, u64 movieTime, u64 *OutMovieTime); @@ -3370,46 +4007,46 @@ Bool IsHintTrack(GF_TrackBox *trak); Bool CheckHintFormat(GF_TrackBox *trak, u32 HintType); u32 GetHintFormat(GF_TrackBox *trak); - -void gf_isom_box_add_for_dump_mode(GF_Box *parent, GF_Box *a); - /*locate a box by its type or UUID*/ GF_ItemListBox *gf_ismo_locate_box(GF_List *list, u32 boxType, bin128 UUID); -GF_Err moov_AddBox(GF_Box *ptr, GF_Box *a); -GF_Err tref_AddBox(GF_Box *ptr, GF_Box *a); -GF_Err trak_AddBox(GF_Box *ptr, GF_Box *a); -GF_Err mvex_AddBox(GF_Box *ptr, GF_Box *a); -GF_Err stsd_AddBox(GF_Box *ptr, GF_Box *a); -GF_Err hnti_AddBox(GF_Box *hnti, GF_Box *a); -GF_Err udta_AddBox(GF_Box *ptr, GF_Box *a); -GF_Err edts_AddBox(GF_Box *s, GF_Box *a); -GF_Err stdp_Read(GF_Box *s, GF_BitStream *bs); -GF_Err stbl_AddBox(GF_Box *ptr, GF_Box *a); -GF_Err sdtp_Read(GF_Box *s, GF_BitStream *bs); -GF_Err dinf_AddBox(GF_Box *s, GF_Box *a); -GF_Err minf_AddBox(GF_Box *s, GF_Box *a); -GF_Err mdia_AddBox(GF_Box *s, GF_Box *a); -GF_Err traf_AddBox(GF_Box *s, GF_Box *a); +GF_Err moov_on_child_box(GF_Box *ptr, GF_Box *a); +GF_Err trak_on_child_box(GF_Box *ptr, GF_Box *a); +GF_Err mvex_on_child_box(GF_Box *ptr, GF_Box *a); +GF_Err stsd_on_child_box(GF_Box *ptr, GF_Box *a); +GF_Err hnti_on_child_box(GF_Box *hnti, GF_Box *a); +GF_Err udta_on_child_box(GF_Box *ptr, GF_Box *a); +GF_Err edts_on_child_box(GF_Box *s, GF_Box *a); +GF_Err stdp_box_read(GF_Box *s, GF_BitStream *bs); +GF_Err stbl_on_child_box(GF_Box *ptr, GF_Box *a); +GF_Err sdtp_box_read(GF_Box *s, GF_BitStream *bs); +GF_Err dinf_on_child_box(GF_Box *s, GF_Box *a); +GF_Err minf_on_child_box(GF_Box *s, GF_Box *a); +GF_Err mdia_on_child_box(GF_Box *s, GF_Box *a); +GF_Err traf_on_child_box(GF_Box *s, GF_Box *a); /*rewrites avcC based on the given esd - this destroys the esd*/ GF_Err AVC_HEVC_UpdateESD(GF_MPEGVisualSampleEntryBox *avc, GF_ESD *esd); void AVC_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *avc, GF_MediaBox *mdia); void AVC_RewriteESDescriptor(GF_MPEGVisualSampleEntryBox *avc); -void HEVC_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *avc, GF_MediaBox *mdia); -void HEVC_RewriteESDescriptor(GF_MPEGVisualSampleEntryBox *avc); - -GF_Err reftype_AddRefTrack(GF_TrackReferenceTypeBox *ref, u32 trackID, u16 *outRefIndex); -GF_XMLBox *gf_isom_get_meta_xml(GF_ISOFile *file, Bool root_meta, u32 track_num, Bool *is_binary); -Bool gf_isom_cenc_has_saiz_saio_track(GF_SampleTableBox *stbl); +void HEVC_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *hevc, GF_MediaBox *mdia); +void HEVC_RewriteESDescriptor(GF_MPEGVisualSampleEntryBox *hevc); +void VP9_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *vp9, GF_MediaBox *mdia); +void VP9_RewriteESDescriptor(GF_MPEGVisualSampleEntryBox *vp9); +void AV1_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *av1, GF_MediaBox *mdia); +void AV1_RewriteESDescriptor(GF_MPEGVisualSampleEntryBox *av1); +GF_Err reftype_AddRefTrack(GF_TrackReferenceTypeBox *ref, GF_ISOTrackID trackID, u16 *outRefIndex); +Bool gf_isom_cenc_has_saiz_saio_track(GF_SampleTableBox *stbl, u32 scheme_type); #ifndef GPAC_DISABLE_ISOM_FRAGMENTS -Bool gf_isom_cenc_has_saiz_saio_traf(GF_TrackFragmentBox *traf); -void gf_isom_cenc_set_saiz_saio(GF_SampleEncryptionBox *senc, GF_SampleTableBox *stbl, GF_TrackFragmentBox *traf, u32 len); +Bool gf_isom_cenc_has_saiz_saio_traf(GF_TrackFragmentBox *traf, u32 scheme_type); +void gf_isom_cenc_set_saiz_saio(GF_SampleEncryptionBox *senc, GF_SampleTableBox *stbl, GF_TrackFragmentBox *traf, u32 len, Bool saio_32bits); #endif -void gf_isom_cenc_merge_saiz_saio(GF_SampleEncryptionBox *senc, GF_SampleTableBox *stbl, u64 offset, u32 len); +GF_Err gf_isom_cenc_merge_saiz_saio(GF_SampleEncryptionBox *senc, GF_SampleTableBox *stbl, u64 offset, u32 len); -void gf_isom_parse_trif_info(const char *data, u32 size, u32 *id, u32 *independent, Bool *full_picture, u32 *x, u32 *y, u32 *w, u32 *h); +void gf_isom_parse_trif_info(const u8 *data, u32 size, u32 *id, u32 *independent, Bool *full_picture, u32 *x, u32 *y, u32 *w, u32 *h); + +Bool gf_isom_is_encrypted_entry(u32 entryType); #ifndef GPAC_DISABLE_ISOM_HINTING @@ -3435,7 +4072,7 @@ typedef struct u8 header_extension_type; u8 content[3]; u32 data_length; - char *data; + u8 *data; } GF_LCTheaderExtension; typedef struct @@ -3464,10 +4101,10 @@ typedef struct typedef struct { GF_ISOM_BOX - + //not registered with child list !! GF_FECInformationBox *feci; u32 data_length; - char *data; + u8 *data; } GF_ExtraDataBox; @@ -3540,7 +4177,7 @@ typedef struct __tag_hint_sample u16 packetCount; u16 reserved; GF_List *packetTable; - char *AdditionalData; + u8 *AdditionalData; u32 dataLength; /*used internally for hinting*/ u64 TransmissionTime; @@ -3548,7 +4185,8 @@ typedef struct __tag_hint_sample GF_List *sample_cache; //for dump - u32 trackID, sampleNumber; + GF_ISOTrackID trackID; + u32 sampleNumber; GF_ExtraDataBox *extra_data; } GF_HintSample; @@ -3564,7 +4202,8 @@ u32 gf_isom_hint_sample_size(GF_HintSample *ptr); Hint Packets (generic packet for future protocol support) *****************************************************/ #define GF_ISOM_BASE_PACKET \ - u32 hint_subtype, trackID, sampleNumber; \ + u32 hint_subtype, sampleNumber; \ + GF_ISOTrackID trackID;\ s32 relativeTransTime; @@ -3621,7 +4260,7 @@ typedef struct //RTCP report u8 Version, Padding, Count, PayloadType; u32 length; - char *data; + u8 *data; } GF_RTCPPacket; GF_RTCPPacket *gf_isom_hint_rtcp_new(); @@ -3651,8 +4290,7 @@ struct _3gpp_text_sample GF_TextKaraokeBox *cur_karaoke; }; -GF_TextSample *gf_isom_parse_texte_sample(GF_BitStream *bs); -GF_TextSample *gf_isom_parse_texte_sample_from_data(char *data, u32 dataLength); +GF_TextSample *gf_isom_parse_text_sample(GF_BitStream *bs); struct _generic_subtitle_sample { @@ -3660,22 +4298,21 @@ struct _generic_subtitle_sample u32 len; }; GF_GenericSubtitleSample *gf_isom_parse_generic_subtitle_sample(GF_BitStream *bs); -GF_GenericSubtitleSample *gf_isom_parse_generic_subtitle_sample_from_data(char *data, u32 dataLength); +GF_GenericSubtitleSample *gf_isom_parse_generic_subtitle_sample_from_data(u8 *data, u32 dataLength); /*do not throw fatal errors if boxes are duplicated, just warn and remove extra ones*/ #define ERROR_ON_DUPLICATED_BOX(__abox, __parent) { \ - char __ptype[5];\ + char __ptype[GF_4CC_MSIZE];\ strcpy(__ptype, gf_4cc_to_str(__parent->type) );\ GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] extra box %s found in %s, deleting\n", gf_4cc_to_str(__abox->type), __ptype)); \ - gf_isom_box_del(a);\ + gf_isom_box_del_parent(& (__parent->child_boxes), __abox);\ return GF_OK;\ } -#ifndef GPAC_DISABLE_VTT +#ifndef GPAC_DISABLE_VTT -GF_Err gf_isom_update_webvtt_description(GF_ISOFile *movie, u32 trackNumber, u32 descriptionIndex, const char *config); GF_ISOSample *gf_isom_webvtt_to_sample(void *samp); typedef struct @@ -3692,8 +4329,6 @@ typedef struct GF_WebVTTSampleEntryBox *gf_webvtt_isom_get_description(GF_ISOFile *movie, u32 trackNumber, u32 descriptionIndex); -GF_List *gf_webvtt_parse_cues_from_data(const char *data, u32 dataLength, u64 start); - #endif /* GPAC_DISABLE_VTT */ //exported for sgpd comparison in traf merge @@ -3704,6 +4339,8 @@ GF_Box *gf_isom_clone_config_box(GF_Box *box); GF_Err gf_isom_box_dump(void *ptr, FILE * trace); GF_Err gf_isom_box_array_dump(GF_List *list, FILE * trace); +void gf_isom_registry_disable(u32 boxCode, Bool disable); + /*Apple extensions*/ GF_MetaBox *gf_isom_apple_get_meta_extensions(GF_ISOFile *mov); @@ -3719,9 +4356,12 @@ void gf_isom_box_dump_done(const char *name, GF_Box *ptr, FILE *trace); Bool gf_isom_box_is_file_level(GF_Box *s); #endif -GF_Box *boxstring_new_with_data(u32 type, const char *string); +GF_Box *boxstring_new_with_data(u32 type, const char *string, GF_List **parent); + +GF_Err gf_isom_read_null_terminated_string(GF_Box *s, GF_BitStream *bs, u64 size, char **out_str); + +GF_Err MergeTrack(GF_TrackBox *trak, GF_TrackFragmentBox *traf, GF_MovieFragmentBox *moof, u64 moof_offset, s32 compresed_diff, u64 *cumulated_offset, Bool is_first_merge); -GF_Err gf_isom_read_null_terminated_string(GF_Box *s, GF_BitStream *bs, u32 size, char **out_str); #endif //GPAC_DISABLE_ISOM diff --git a/include/gpac/internal/laser_dev.h b/include/gpac/internal/laser_dev.h index 1cd06a2..ea78add 100644 --- a/include/gpac/internal/laser_dev.h +++ b/include/gpac/internal/laser_dev.h @@ -90,9 +90,11 @@ struct __tag_laser_codec */ Bool memory_dec; - GF_List *defered_hrefs; - GF_List *defered_anims; - GF_List *defered_listeners; + Bool has_conditionnals; + + GF_List *deferred_hrefs; + GF_List *deferred_anims; + GF_List *deferred_listeners; char *cache_dir, *service_name; GF_List *unresolved_commands; diff --git a/include/gpac/internal/m3u8.h b/include/gpac/internal/m3u8.h index 2a1a2be..c24a513 100644 --- a/include/gpac/internal/m3u8.h +++ b/include/gpac/internal/m3u8.h @@ -115,21 +115,21 @@ typedef struct s_masterPlaylist MasterPlaylist; /** * Parse the given m3u8 playlist file - * \param file The file from cache to parse - * \param playlist The playlist to fill. If argument is null, and file is valid, playlist will be allocated - * \param baseURL The base URL of the playlist - * \return GF_OK if playlist valid +\param file The file from cache to parse +\param playlist The playlist to fill. If argument is null, and file is valid, playlist will be allocated +\param baseURL The base URL of the playlist +\param GF_OK if playlist valid */ GF_Err gf_m3u8_parse_master_playlist(const char *file, MasterPlaylist **playlist, const char *baseURL); /** * Parse the given playlist file as a subplaylist of an existing playlist - * \param file The file from cache to parse - * \param playlist The playlist to fill. - * \param baseURL base URL of the playlist - * \param in_program in which the playlist is parsed - * \param sub_playlist existing subplaylist element in the playlist in which the playlist is parsed - * \return GF_OK if playlist valid +\param file The file from cache to parse +\param playlist The playlist to fill. +\param baseURL base URL of the playlist +\param in_program in which the playlist is parsed +\param sub_playlist existing subplaylist element in the playlist in which the playlist is parsed +\param GF_OK if playlist valid */ GF_Err gf_m3u8_parse_sub_playlist(const char *file, MasterPlaylist **playlist, const char *baseURL, Stream *in_program, PlaylistElement *sub_playlist); diff --git a/include/gpac/internal/media_dev.h b/include/gpac/internal/media_dev.h index a795fe3..9ac2a96 100644 --- a/include/gpac/internal/media_dev.h +++ b/include/gpac/internal/media_dev.h @@ -28,6 +28,7 @@ #define _GF_MEDIA_DEV_H_ #include <gpac/media_tools.h> +#include <gpac/mpeg4_odf.h> #ifndef GPAC_DISABLE_STREAMING #include <gpac/ietf.h> @@ -55,17 +56,17 @@ u32 gf_media_nalu_is_start_code(GF_BitStream *bs); /*returns size of chunk between current and next startcode (excluding startcode sizes), 0 if no more startcodes (eos)*/ u32 gf_media_nalu_next_start_code_bs(GF_BitStream *bs); -/*returns size of chunk between current startcode and end of payload (0x000000) - if no trailing bytes are found, returns the same as gf_media_nalu_next_start_code_bs*/ -u32 gf_media_nalu_payload_end_bs(GF_BitStream *bs); - /*return nb bytes from current data until the next start code and set the size of the next start code (3 or 4 bytes) returns data_len if no startcode found and sets sc_size to 0 (last nal in payload)*/ u32 gf_media_nalu_next_start_code(const u8 *data, u32 data_len, u32 *sc_size); -/*returns NAL unit type - bitstream must be sync'ed!!*/ -u8 AVC_NALUType(GF_BitStream *bs); -Bool SVC_NALUIsSlice(u8 type); +u32 gf_media_nalu_emulation_bytes_remove_count(const u8 *buffer, u32 nal_size); +u32 gf_media_nalu_remove_emulation_bytes(const u8 *buffer_src, u8 *buffer_dst, u32 nal_size); +u32 gf_bs_get_ue(GF_BitStream *bs); +s32 gf_bs_get_se(GF_BitStream *bs); +void gf_bs_set_ue(GF_BitStream *bs, u32 num); +void gf_bs_set_se(GF_BitStream *bs, s32 num); enum { @@ -119,13 +120,13 @@ typedef struct /*to be eventually completed by other vui members*/ } AVC_VUI; -typedef struct +typedef struct { u32 left; u32 right; u32 top; u32 bottom; - + } AVC_CROP; @@ -146,6 +147,7 @@ typedef struct u8 chroma_format; u8 luma_bit_depth_m8; u8 chroma_bit_depth_m8; + u32 ChromaArrayType; s16 offset_for_ref_frame[256]; @@ -158,6 +160,8 @@ typedef struct /*used to discard repeated SPSs - 0: not parsed, 1 parsed, 2 sent*/ u32 state; + u32 sbusps_crc; + /*for SVC stats during import*/ u32 nb_ei, nb_ep, nb_eb; } AVC_SPS; @@ -170,9 +174,15 @@ typedef struct s32 pic_order_present; /* pic_order_present_flag*/ s32 redundant_pic_cnt_present; /* redundant_pic_cnt_present_flag */ u32 slice_group_count; /* num_slice_groups_minus1 + 1*/ + u32 mb_slice_group_map_type; + u32 pic_size_in_map_units_minus1; + u32 slice_group_change_rate_minus1; /*used to discard repeated SPSs - 0: not parsed, 1 parsed, 2 sent*/ u32 status; - + Bool weighted_pred_flag; + u8 weighted_bipred_idc; + Bool deblocking_filter_control_present_flag; + u32 num_ref_idx_l0_default_active_minus1, num_ref_idx_l1_default_active_minus1; } AVC_PPS; typedef struct @@ -214,17 +224,23 @@ typedef struct /*to be eventually completed by other pic_timing members*/ } AVCSeiPicTiming; +typedef struct +{ + Bool rpu_flag; +} AVCSeiItuTT35DolbyVision; + typedef struct { AVCSeiRecoveryPoint recovery_point; AVCSeiPicTiming pic_timing; + AVCSeiItuTT35DolbyVision dovi; /*to be eventually completed by other sei*/ } AVCSei; typedef struct { AVC_SPS sps[32]; /* range allowed in the spec is 0..31 */ - s8 sps_active_idx; /*currently active sps; must be initalized to -1 in order to discard not yet decodable SEIs*/ + s8 sps_active_idx, pps_active_idx; /*currently active sps; must be initalized to -1 in order to discard not yet decodable SEIs*/ AVC_PPS pps[255]; @@ -232,6 +248,8 @@ typedef struct AVCSei sei; Bool is_svc; + u8 last_nal_type_parsed; + s8 last_ps_idx; } AVCState; typedef struct @@ -245,13 +263,12 @@ typedef struct /*return sps ID or -1 if error*/ -s32 gf_media_avc_read_sps(const char *sps_data, u32 sps_size, AVCState *avc, u32 subseq_sps, u32 *vui_flag_pos); +s32 gf_media_avc_read_sps(const u8 *sps_data, u32 sps_size, AVCState *avc, u32 subseq_sps, u32 *vui_flag_pos); +s32 gf_media_avc_read_sps_bs(GF_BitStream *bs, AVCState *avc, u32 subseq_sps, u32 *vui_flag_pos); /*return pps ID or -1 if error*/ -s32 gf_media_avc_read_pps(const char *pps_data, u32 pps_size, AVCState *avc); -/*return sps ID or -1 if error*/ -s32 gf_media_avc_read_sps_ext(const char *spse_data, u32 spse_size); -/*is slice an IDR*/ -Bool gf_media_avc_slice_is_IDR(AVCState *avc); +s32 gf_media_avc_read_pps(const u8 *pps_data, u32 pps_size, AVCState *avc); +s32 gf_media_avc_read_pps_bs(GF_BitStream *bs, AVCState *avc); + /*is slice containing intra MB only*/ Bool gf_media_avc_slice_is_intra(AVCState *avc); /*parses NALU, updates avc state and returns: @@ -259,10 +276,10 @@ Bool gf_media_avc_slice_is_intra(AVCState *avc); 0 if NALU part of prev frame -1 if bitstream error */ -s32 gf_media_avc_parse_nalu(GF_BitStream *bs, u32 nal_hdr, AVCState *avc); +s32 gf_media_avc_parse_nalu(GF_BitStream *bs, AVCState *avc); /*remove SEI messages not allowed in MP4*/ /*nota: 'buffer' remains unmodified but cannot be set const*/ -u32 gf_media_avc_reformat_sei(char *buffer, u32 nal_size, AVCState *avc); +u32 gf_media_avc_reformat_sei(u8 *buffer, u32 nal_size, Bool isobmf_rewrite, AVCState *avc); #ifndef GPAC_DISABLE_ISOM GF_Err gf_media_avc_change_par(GF_AVCConfig *avcc, s32 ar_n, s32 ar_d); @@ -333,7 +350,27 @@ typedef struct Bool poc_proportional_to_timing_flag; u32 num_ticks_poc_diff_one_minus1; + Bool video_full_range_flag; + Bool colour_description_present_flag; + u8 colour_primaries, transfer_characteristic, matrix_coeffs; u32 rep_format_idx; + + u8 sps_ext_or_max_sub_layers_minus1, max_sub_layers_minus1, update_rep_format_flag, sub_layer_ordering_info_present_flag, scaling_list_enable_flag, infer_scaling_list_flag, scaling_list_ref_layer_id, scaling_list_data_present_flag, asymmetric_motion_partitions_enabled_flag, pcm_enabled_flag, strong_intra_smoothing_enable_flag, vui_parameters_present_flag; + u32 log2_diff_max_min_luma_coding_block_size; + u32 log2_min_transform_block_size, log2_min_luma_coding_block_size, log2_max_transform_block_size; + u32 max_transform_hierarchy_depth_inter, max_transform_hierarchy_depth_intra; + + u8 pcm_sample_bit_depth_luma_minus1, pcm_sample_bit_depth_chroma_minus1, pcm_loop_filter_disable_flag; + u32 log2_min_pcm_luma_coding_block_size_minus3, log2_diff_max_min_pcm_luma_coding_block_size; + u8 overscan_info_present, overscan_appropriate, video_signal_type_present_flag, video_format; + + u8 chroma_loc_info_present_flag; + u32 chroma_sample_loc_type_top_field, chroma_sample_loc_type_bottom_field; + + u8 neutra_chroma_indication_flag, field_seq_flag, frame_field_info_present_flag; + u8 default_display_window_flag; + u32 left_offset, right_offset, top_offset, bottom_offset; + u8 hrd_parameters_present_flag; } HEVC_SPS; typedef struct @@ -349,9 +386,16 @@ typedef struct Bool slice_segment_header_extension_present_flag, output_flag_present_flag, lists_modification_present_flag, cabac_init_present_flag; Bool weighted_pred_flag, weighted_bipred_flag, slice_chroma_qp_offsets_present_flag, deblocking_filter_override_enabled_flag, loop_filter_across_slices_enabled_flag, entropy_coding_sync_enabled_flag; Bool loop_filter_across_tiles_enabled_flag; - + s32 pic_init_qp_minus26; u32 num_tile_columns, num_tile_rows; u32 column_width[22], row_height[20]; + + Bool sign_data_hiding_flag, constrained_intra_pred_flag, transform_skip_enabled_flag, cu_qp_delta_enabled_flag, transquant_bypass_enable_flag; + u32 diff_cu_qp_delta_depth, pic_cb_qp_offset, pic_cr_qp_offset; + + Bool deblocking_filter_control_present_flag, pic_disable_deblocking_filter_flag, pic_scaling_list_data_present_flag; + u32 beta_offset_div2, tc_offset_div2, log2_parallel_merge_level_minus2; + } HEVC_PPS; typedef struct RepFormat @@ -417,14 +461,14 @@ typedef struct { AVCSeiRecoveryPoint recovery_point; AVCSeiPicTiming pic_timing; - + AVCSeiItuTT35DolbyVision dovi; } HEVC_SEI; typedef struct { u8 nal_unit_type; - u32 frame_num, poc_lsb, slice_type; - + u32 frame_num, poc_lsb, slice_type, header_size_with_emulation; + s32 redundant_pic_cnt; s32 poc; @@ -437,16 +481,25 @@ typedef struct u8 prev_layer_id_plus1; //bit offset of the num_entry_point (if present) field - s32 entry_point_start_bits; + s32 entry_point_start_bits; + u64 header_size_bits; //byte offset of the payload start (after byte alignment) s32 payload_start_offset; + s32 slice_qp_delta_start_bits; + s32 slice_qp_delta; + HEVC_SPS *sps; HEVC_PPS *pps; } HEVCSliceInfo; typedef struct _hevc_state { + //set by user + Bool full_slice_header_parse; + + //all other vars set by parser + HEVC_SPS sps[16]; /* range allowed in the spec is 0..15 */ s8 sps_active_idx; /*currently active sps; must be initalized to -1 in order to discard not yet decodable SEIs*/ @@ -456,29 +509,244 @@ typedef struct _hevc_state HEVCSliceInfo s_info; HEVC_SEI sei; + //-1 or the value of the vps/sps/pps ID of the nal just parsed s32 last_parsed_vps_id; s32 last_parsed_sps_id; s32 last_parsed_pps_id; + + // Dolby Vision + Bool dv_rpu; + Bool dv_el; } HEVCState; +typedef struct hevc_combine{ + Bool is_hevccombine, first_slice_segment; + s32 buffer_header_src_alloc; // because payload_start_offset is s32, otherwhise it's an u32 + u8 *buffer_header_src; +}Combine; + enum { GF_HEVC_SLICE_TYPE_B = 0, GF_HEVC_SLICE_TYPE_P = 1, GF_HEVC_SLICE_TYPE_I = 2, }; -s32 gf_media_hevc_read_vps(char *data, u32 size, HEVCState *hevc); -s32 gf_media_hevc_read_sps(char *data, u32 size, HEVCState *hevc); -s32 gf_media_hevc_read_pps(char *data, u32 size, HEVCState *hevc); -s32 gf_media_hevc_parse_nalu(char *data, u32 size, HEVCState *hevc, u8 *nal_unit_type, u8 *temporal_id, u8 *layer_id); +s32 gf_media_hevc_read_vps(u8 *data, u32 size, HEVCState *hevc); +s32 gf_media_hevc_read_vps_bs(GF_BitStream *bs, HEVCState *hevc); +s32 gf_media_hevc_read_sps(u8 *data, u32 size, HEVCState *hevc); +s32 gf_media_hevc_read_sps_bs(GF_BitStream *bs, HEVCState *hevc); +s32 gf_media_hevc_read_pps(u8 *data, u32 size, HEVCState *hevc); +s32 gf_media_hevc_read_pps_bs(GF_BitStream *bs, HEVCState *hevc); +s32 gf_media_hevc_parse_nalu(u8 *data, u32 size, HEVCState *hevc, u8 *nal_unit_type, u8 *temporal_id, u8 *layer_id); Bool gf_media_hevc_slice_is_intra(HEVCState *hevc); Bool gf_media_hevc_slice_is_IDR(HEVCState *hevc); //parses VPS and rewrites data buffer after removing VPS extension -s32 gf_media_hevc_read_vps_ex(char *data, u32 *size, HEVCState *hevc, Bool remove_extensions); +s32 gf_media_hevc_read_vps_ex(u8 *data, u32 *size, HEVCState *hevc, Bool remove_extensions); + +void gf_media_hevc_parse_ps(GF_HEVCConfig* hevccfg, HEVCState* hevc, u32 nal_type); +s32 gf_media_hevc_parse_nalu_bs(GF_BitStream *bs, HEVCState *hevc, u8 *nal_unit_type, u8 *temporal_id, u8 *layer_id); + +GF_Err gf_hevc_get_sps_info_with_state(HEVCState *hevc_state, u8 *sps_data, u32 sps_size, u32 *sps_id, u32 *width, u32 *height, s32 *par_n, s32 *par_d); + +/*parses HEVC SEI and fill state accordingly*/ +void gf_media_hevc_parse_sei(char* buffer, u32 nal_size, HEVCState *hevc); + + +GF_Err gf_media_parse_ivf_file_header(GF_BitStream *bs, u32 *width, u32*height, u32 *codec_fourcc, u32 *frame_rate, u32 *time_scale, u32 *num_frames); + + + +#define VP9_MAX_FRAMES_IN_SUPERFRAME 16 + +GF_Err gf_media_vp9_parse_sample(GF_BitStream *bs, GF_VPConfig *vp9_cfg, Bool *key_frame, u32 *FrameWidth, u32 *FrameHeight, u32 *renderWidth, u32 *renderHeight); +GF_Err gf_media_vp9_parse_superframe(GF_BitStream *bs, u64 ivf_frame_size, u32 *num_frames_in_superframe, u32 frame_sizes[VP9_MAX_FRAMES_IN_SUPERFRAME], u32 *superframe_index_size); -GF_Err gf_hevc_get_sps_info_with_state(HEVCState *hevc_state, char *sps_data, u32 sps_size, u32 *sps_id, u32 *width, u32 *height, s32 *par_n, s32 *par_d); + +#define AV1_MAX_TILE_ROWS 64 +#define AV1_MAX_TILE_COLS 64 + +typedef enum { + AV1_KEY_FRAME = 0, + AV1_INTER_FRAME = 1, + AV1_INTRA_ONLY_FRAME = 2, + AV1_SWITCH_FRAME = 3, +} AV1FrameType; + +typedef struct +{ + //offset in bytes after first byte of obu, including its header + u32 obu_start_offset; + u32 size; +} AV1Tile; + +typedef struct +{ + Bool is_first_frame; + Bool seen_frame_header, seen_seq_header; + Bool key_frame, show_frame; + AV1FrameType frame_type; + GF_List *header_obus, *frame_obus; /*GF_AV1_OBUArrayEntry*/ + AV1Tile tiles[AV1_MAX_TILE_ROWS * AV1_MAX_TILE_COLS]; + u32 nb_tiles_in_obu; + u8 refresh_frame_flags; + u8 order_hint; + u8 allow_high_precision_mv; + u8 show_existing_frame, frame_to_show_map_idx; + //indicates the size of the uncompressed_header syntax element. This is set back to 0 at the next OBU parsing + u16 uncompressed_header_bytes; +} AV1StateFrame; + +#define AV1_NUM_REF_FRAMES 8 + +typedef struct +{ + s32 coefs[AV1_NUM_REF_FRAMES][6]; +} AV1GMParams; + +typedef struct +{ + /*importing options*/ + Bool keep_temporal_delim; + /*parser config*/ + //if set only header frames are stored + Bool skip_frames; + //if set, frame OBUs are not pushed to the frame_obus OBU list but are written in the below bitstream + Bool mem_mode; + /*bitstream object for mem mode - this bitstream is NOT destroyed by gf_av1_reset_state(state, GF_TRUE) */ + GF_BitStream *bs; + Bool bs_overread, unframed; + u8 *frame_obus; + u32 frame_obus_alloc; + + /*general sequence information*/ + Bool frame_id_numbers_present_flag; + Bool reduced_still_picture_header; + Bool decoder_model_info_present_flag; + u16 OperatingPointIdc; + u32 width, height, UpscaledWidth; + u32 tb_num, tb_den; + + Bool use_128x128_superblock; + u8 frame_width_bits_minus_1, frame_height_bits_minus_1; + u8 equal_picture_interval; + u8 delta_frame_id_length_minus_2; + u8 additional_frame_id_length_minus_1; + u8 seq_force_integer_mv; + u8 seq_force_screen_content_tools; + Bool enable_superres; + Bool enable_order_hint; + Bool enable_cdef; + Bool enable_restoration; + Bool enable_warped_motion; + u8 OrderHintBits; + Bool enable_ref_frame_mvs; + Bool film_grain_params_present; + u8 buffer_delay_length; + u8 frame_presentation_time_length; + u32 buffer_removal_time_length; + u8 operating_points_count; + u8 decoder_model_present_for_this_op[32]; + u8 operating_point_idc[32]; + + u32 tileRows, tileCols, tileRowsLog2, tileColsLog2; + u8 tile_size_bytes; /*coding tile header size*/ + Bool separate_uv_delta_q; + + /*Needed for RFC6381*/ + Bool still_picture; + u8 bit_depth; + Bool color_description_present_flag; + u8 color_primaries, transfer_characteristics, matrix_coefficients; + Bool color_range; + + /*AV1 config record - shall not be null when parsing - this is NOT destroyed by gf_av1_reset_state(state, GF_TRUE) */ + GF_AV1Config *config; + + /*OBU parsing state, reset at each obu*/ + Bool obu_has_size_field, obu_extension_flag; + u8 temporal_id, spatial_id; + ObuType obu_type; + + /*inter-frames state */ + u8 RefOrderHint[AV1_NUM_REF_FRAMES]; + u8 RefValid[AV1_NUM_REF_FRAMES]; + u8 OrderHints[AV1_NUM_REF_FRAMES]; + + AV1GMParams GmParams; + AV1GMParams PrevGmParams; + AV1GMParams SavedGmParams[AV1_NUM_REF_FRAMES]; + u8 RefFrameType[AV1_NUM_REF_FRAMES]; + + /*frame parsing state*/ + AV1StateFrame frame_state; +} AV1State; + +GF_Err aom_av1_parse_temporal_unit_from_section5(GF_BitStream *bs, AV1State *state); +GF_Err aom_av1_parse_temporal_unit_from_annexb(GF_BitStream *bs, AV1State *state); +GF_Err aom_av1_parse_temporal_unit_from_ivf(GF_BitStream *bs, AV1State *state); + +GF_Err gf_media_parse_ivf_frame_header(GF_BitStream *bs, u64 *frame_size, u64 *pts); + +Bool gf_media_probe_ivf(GF_BitStream *bs); +Bool gf_media_aom_probe_annexb(GF_BitStream *bs); + +/*parses one OBU*/ +GF_Err gf_media_aom_av1_parse_obu(GF_BitStream *bs, ObuType *obu_type, u64 *obu_size, u32 *obu_hdr_size, AV1State *state); + +Bool av1_is_obu_header(ObuType obu_type); + +/*! init av1 frame parsing state +\param state the frame parser +*/ +void gf_av1_init_state(AV1State *state); + +/*! reset av1 frame parsing state - this does not destroy the structure. +\param state the frame parser +\param is_destroy if TRUE, destroy internal reference picture lists +*/ +void gf_av1_reset_state(AV1State *state, Bool is_destroy); + +u64 gf_av1_leb128_read(GF_BitStream *bs, u8 *opt_Leb128Bytes); +u32 gf_av1_leb128_size(u64 value); +u64 gf_av1_leb128_write(GF_BitStream *bs, u64 value); +GF_Err gf_av1_parse_obu_header(GF_BitStream *bs, ObuType *obu_type, Bool *obu_extension_flag, Bool *obu_has_size_field, u8 *temporal_id, u8 *spatial_id); + +typedef struct +{ + u32 picture_size; + u16 deprecated_number_of_slices; + u8 log2_desired_slice_size_in_mb; +} GF_ProResPictureInfo; + +typedef struct +{ + u32 frame_size; + u32 frame_identifier; + u16 frame_hdr_size; + u8 version; + u32 encoder_id; + u16 width; + u16 height; + u8 chroma_format; + u8 interlaced_mode; + u8 aspect_ratio_information; + u8 framerate_code; + u8 color_primaries; + u8 transfer_characteristics; + u8 matrix_coefficients; + u8 alpha_channel_type; + u8 load_luma_quant_matrix, load_chroma_quant_matrix; + u8 luma_quant_matrix[8][8]; + u8 chroma_quant_matrix[8][8]; + + u8 nb_pic; //1 or 2 + //for now we don't parse this +// GF_ProResPictureInfo pictures[2]; +} GF_ProResFrameInfo; + +GF_Err gf_media_prores_parse_bs(GF_BitStream *bs, GF_ProResFrameInfo *prores_frame); #endif /*GPAC_DISABLE_AV_PARSERS*/ @@ -497,7 +765,7 @@ GP_RTPPacketizer *gf_rtp_packetizer_create_and_init_from_file(GF_ISOFile *file, void (*OnNewPacket)(void *cbk, GF_RTPHeader *header), void (*OnPacketDone)(void *cbk, GF_RTPHeader *header), void (*OnDataReference)(void *cbk, u32 payload_size, u32 offset_from_orig), - void (*OnData)(void *cbk, char *data, u32 data_size, Bool is_head), + void (*OnData)(void *cbk, u8 *data, u32 data_size, Bool is_head), u32 Path_MTU, u32 max_ptime, u32 default_rtp_rate, @@ -507,46 +775,59 @@ GP_RTPPacketizer *gf_rtp_packetizer_create_and_init_from_file(GF_ISOFile *file, u32 InterleaveGroupID, u8 InterleaveGroupPriority); -void gf_media_format_ttxt_sdp(GP_RTPPacketizer *builder, char *payload_name, char *sdpLine, GF_ISOFile *file, u32 track); +void gf_media_format_ttxt_sdp(GP_RTPPacketizer *builder, char *payload_name, char *sdpLine, u32 w, u32 h, s32 tx, s32 ty, s16 l, u32 max_w, u32 max_h, char *tx3g_base64); #endif -typedef enum -{ - GF_DASH_TEMPLATE_SEGMENT = 0, - GF_DASH_TEMPLATE_INITIALIZATION, - GF_DASH_TEMPLATE_TEMPLATE, - GF_DASH_TEMPLATE_INITIALIZATION_TEMPLATE, - GF_DASH_TEMPLATE_REPINDEX, -} GF_DashTemplateSegmentType; - -GF_Err gf_media_mpd_format_segment_name(GF_DashTemplateSegmentType seg_type, Bool is_bs_switching, char *segment_name, const char *output_file_name, const char *rep_id, const char *base_url, const char *seg_rad_name, const char *seg_ext, u64 start_time, u32 bandwidth, u32 segment_number, Bool use_segment_timeline); - #ifndef GPAC_DISABLE_VTT typedef struct _webvtt_parser GF_WebVTTParser; typedef struct _webvtt_sample GF_WebVTTSample; GF_WebVTTParser *gf_webvtt_parser_new(); -GF_Err gf_webvtt_parser_init(GF_WebVTTParser *parser, const char *input_file, +GF_Err gf_webvtt_parser_init(GF_WebVTTParser *parser, FILE *vtt_file, s32 unicode_type, Bool is_srt, void *user, GF_Err (*report_message)(void *, GF_Err, char *, const char *), void (*on_sample_parsed)(void *, GF_WebVTTSample *), void (*on_header_parsed)(void *, const char *)); -GF_Err gf_webvtt_parser_parse(GF_WebVTTParser *parser, u32 duration); -u64 gf_webvtt_parser_last_duration(GF_WebVTTParser *parser); +GF_Err gf_webvtt_parser_parse(GF_WebVTTParser *parser); void gf_webvtt_parser_del(GF_WebVTTParser *parser); +void gf_webvtt_parser_suspend(GF_WebVTTParser *vttparser); +void gf_webvtt_parser_restart(GF_WebVTTParser *parser); + +#include <gpac/webvtt.h> +void gf_webvtt_parser_cue_callback(GF_WebVTTParser *parser, void (*on_cue_read)(void *, GF_WebVTTCue *), void *udta); +GF_Err gf_webvtt_merge_cues(GF_WebVTTParser *parser, u64 start, GF_List *cues); +GF_Err gf_webvtt_parser_finalize(GF_WebVTTParser *parser, u64 duration); void gf_webvtt_sample_del(GF_WebVTTSample * samp); u64 gf_webvtt_sample_get_start(GF_WebVTTSample * samp); +u64 gf_webvtt_sample_get_end(GF_WebVTTSample * samp); + + #ifndef GPAC_DISABLE_ISOM GF_Err gf_webvtt_dump_header(FILE *dump, GF_ISOFile *file, u32 track, Bool box_mode, u32 index); -GF_Err gf_webvtt_dump_sample(FILE *dump, GF_WebVTTSample *samp); GF_Err gf_webvtt_parser_dump_done(GF_WebVTTParser *parser, u32 duration); #endif /* GPAC_DISABLE_ISOM */ #endif /* GPAC_DISABLE_VTT */ + +#define M4V_VO_START_CODE 0x00 +#define M4V_VOL_START_CODE 0x20 +#define M4V_VOP_START_CODE 0xB6 +#define M4V_VISOBJ_START_CODE 0xB5 +#define M4V_VOS_START_CODE 0xB0 +#define M4V_GOV_START_CODE 0xB3 +#define M4V_UDTA_START_CODE 0xB2 + + +#define M2V_PIC_START_CODE 0x00 +#define M2V_SEQ_START_CODE 0xB3 +#define M2V_EXT_START_CODE 0xB5 +#define M2V_GOP_START_CODE 0xB8 + + #endif /*_GF_MEDIA_DEV_H_*/ diff --git a/include/gpac/internal/mesh.h b/include/gpac/internal/mesh.h index 76a4582..15ab04b 100644 --- a/include/gpac/internal/mesh.h +++ b/include/gpac/internal/mesh.h @@ -132,6 +132,8 @@ enum MESH_IS_SMOOTHED = 1<<5, /*vertex.color is used with alpha channel*/ MESH_HAS_ALPHA = 1<<6, + /*flag only used by VRgeometry proto*/ + MESH_WAS_VISIBLE = 1<<7, }; /*indexes as used in glDrawElements - note that integer type is not allowed with oglES*/ diff --git a/include/gpac/internal/mpd.h b/include/gpac/internal/mpd.h deleted file mode 100644 index 342397b..0000000 --- a/include/gpac/internal/mpd.h +++ /dev/null @@ -1,478 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - Cyril Concolato - * Copyright (c) Telecom ParisTech 2010-2012 - * All rights reserved - * - * This file is part of GPAC / 3GPP/MPEG Media Presentation Description input module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ -#ifndef _MPD_H_ -#define _MPD_H_ - -#include <gpac/constants.h> -#include <gpac/xml.h> -#include <gpac/media_tools.h> - -#ifndef GPAC_DISABLE_CORE_TOOLS - -/*TODO*/ -typedef struct -{ - u32 dummy; -} GF_MPD_Metrics; - -/*TODO*/ -typedef struct -{ - u32 dummy; -} GF_MPD_ContentComponent; - - -//some elments are typically overloaded in XML, we keep the attributes / childrne nodes here. The attributes list is NULL if no extensions were found, otherwise it is a list of @GF_XMLAttribute. -//The children list is NULL if no extensions were found, otherwise it is a list of @GF_XMLNode -#define MPD_EXTENSIBLE \ - GF_List *attributes; \ - GF_List *children; \ - -typedef struct -{ - MPD_EXTENSIBLE -} GF_MPD_ExtensibleVirtual; - -typedef struct -{ - MPD_EXTENSIBLE - - char *scheme_id_uri; /*MANDATORY*/ - char *value; - char *id; - -} GF_MPD_Descriptor; - -/*TODO*/ -typedef struct -{ - u32 dummy; -} GF_MPD_Subset; - -typedef struct -{ - u64 start_time; - u32 duration; /*MANDATORY*/ - //may be -1 (FIXME this needs further testing) - u32 repeat_count; -} GF_MPD_SegmentTimelineEntry; - -typedef struct -{ - GF_List *entries; -} GF_MPD_SegmentTimeline; - -typedef struct -{ - u64 start_range, end_range; -} GF_MPD_ByteRange; - - -typedef struct -{ - char *URL; - char *service_location; - GF_MPD_ByteRange *byte_range; -} GF_MPD_BaseURL; - -typedef struct -{ - char *sourceURL; - GF_MPD_ByteRange *byte_range; - - //GPAC internal - indicates the URL has already been solved - Bool is_resolved; -} GF_MPD_URL; - -typedef struct -{ - u32 num, den; -} GF_MPD_Fractional; - -typedef struct -{ - u32 trackID; - char *stsd; - s64 mediaOffset; -} GF_MPD_ISOBMFInfo; - -#define GF_MPD_SEGMENT_BASE \ - u32 timescale; \ - u64 presentation_time_offset; \ - u32 time_shift_buffer_depth; /* expressed in milliseconds */ \ - GF_MPD_ByteRange *index_range; \ - Bool index_range_exact; \ - Double availability_time_offset; \ - GF_MPD_URL *initialization_segment; \ - GF_MPD_URL *representation_index; \ - - -typedef struct -{ - GF_MPD_SEGMENT_BASE -} GF_MPD_SegmentBase; - -/*WARNING: duration is expressed in GF_MPD_SEGMENT_BASE@timescale unit*/ -#define GF_MPD_MULTIPLE_SEGMENT_BASE \ - GF_MPD_SEGMENT_BASE \ - u64 duration; \ - u32 start_number; \ - GF_MPD_SegmentTimeline *segment_timeline; \ - GF_MPD_URL *bitstream_switching_url; \ - -typedef struct -{ - GF_MPD_MULTIPLE_SEGMENT_BASE -} GF_MPD_MultipleSegmentBase; - -typedef struct -{ - char *media; - GF_MPD_ByteRange *media_range; - char *index; - GF_MPD_ByteRange *index_range; - u64 duration; - /*HLS only*/ - char *key_url; - bin128 key_iv; - u64 hls_utc_start_time; -} GF_MPD_SegmentURL; - -typedef struct -{ - GF_MPD_MULTIPLE_SEGMENT_BASE - /*list of segments - can be NULL if no segment*/ - GF_List *segment_URLs; - - char *xlink_href; - Bool xlink_actuate_on_load; - - u32 consecutive_xlink_count; -} GF_MPD_SegmentList; - -typedef struct -{ - GF_MPD_MULTIPLE_SEGMENT_BASE - char *media; - char *index; - char *initialization; - char *bitstream_switching; -} GF_MPD_SegmentTemplate; - -typedef enum -{ - GF_MPD_SCANTYPE_UNKNWON, - GF_MPD_SCANTYPE_PROGRESSIVE, - GF_MPD_SCANTYPE_INTERLACED -} GF_MPD_ScanType; - -/*MANDATORY: - mime_type - codecs -*/ -#define GF_MPD_COMMON_ATTRIBUTES_ELEMENTS \ - char *profiles; \ - u32 width; \ - u32 height; \ - GF_MPD_Fractional *sar; \ - GF_MPD_Fractional *framerate; \ - u32 samplerate; \ - char *mime_type; \ - char *segmentProfiles; \ - char *codecs; \ - u32 maximum_sap_period; \ - u32 starts_with_sap; \ - Double max_playout_rate; \ - Bool coding_dependency; \ - GF_MPD_ScanType scan_type; \ - GF_List *frame_packing; \ - GF_List *audio_channels; \ - GF_List *content_protection; \ - GF_List *essential_properties; \ - GF_List *supplemental_properties; \ - GF_List *isobmf_tracks; \ - -typedef struct { - GF_MPD_COMMON_ATTRIBUTES_ELEMENTS -} GF_MPD_CommonAttributes; - -typedef struct { - GF_MPD_COMMON_ATTRIBUTES_ELEMENTS - - u32 level; - char *dependecy_level; - u32 bandwidth; /*MANDATORY if level set*/ - char *content_components; -} GF_MPD_SubRepresentation; - -typedef struct -{ - Bool disabled; - char *cached_init_segment_url; - Bool owned_gmem; - u64 init_start_range, init_end_range; - u32 probe_switch_count; - char *init_segment_data; - u32 init_segment_size; - char *key_url; - bin128 key_IV; - /*previous maximum speed that this representation can be played, or 0 if it has never been played*/ - Double prev_max_available_speed; - /*after switch we may have some buffered segments of the previous representation; so codec stats at this moment is unreliable. we should wait after the codec reset*/ - Bool waiting_codec_reset; -} GF_DASH_RepresentationPlayback; - -typedef struct { - GF_MPD_COMMON_ATTRIBUTES_ELEMENTS - - char *id; /*MANDATORY*/ - u32 bandwidth; /*MANDATORY*/ - u32 quality_ranking; - char *dependency_id; - char *media_stream_structure_id; - - GF_List *base_URLs; - GF_MPD_SegmentBase *segment_base; - GF_MPD_SegmentList *segment_list; - GF_MPD_SegmentTemplate *segment_template; - - GF_List *sub_representations; - - /*index of the next enhancement representation plus 1, 0 is reserved in case of the highest representation*/ - u32 enhancement_rep_index_plus_one; - - /*GPAC playback implementation*/ - GF_DASH_RepresentationPlayback playback; - u32 m3u8_media_seq_min, m3u8_media_seq_max; -} GF_MPD_Representation; - - -typedef struct -{ - GF_MPD_COMMON_ATTRIBUTES_ELEMENTS - - u32 id; - /*default value is -1: not set in MPD*/ - s32 group; - - char *lang; - char *content_type; - GF_MPD_Fractional *par; - u32 min_bandwidth; - u32 max_bandwidth; - u32 min_width; - u32 max_width; - u32 min_height; - u32 max_height; - u32 min_framerate; - u32 max_framerate; - Bool segment_alignment; - Bool bitstream_switching; - Bool subsegment_alignment; - Bool subsegment_starts_with_sap; - - GF_List *accessibility; - GF_List *role; - GF_List *rating; - GF_List *viewpoint; - GF_List *content_component; - - GF_List *base_URLs; - GF_MPD_SegmentBase *segment_base; - GF_MPD_SegmentList *segment_list; - GF_MPD_SegmentTemplate *segment_template; - - GF_List *representations; - - char *xlink_href; - Bool xlink_actuate_on_load; -} GF_MPD_AdaptationSet; - - -typedef struct -{ - char *ID; - u64 start; /* expressed in ms, relative to the start of the MPD */ - u64 duration; /* expressed in ms*/ - Bool bitstream_switching; - - GF_List *base_URLs; - GF_MPD_SegmentBase *segment_base; - GF_MPD_SegmentList *segment_list; - GF_MPD_SegmentTemplate *segment_template; - - GF_List *adaptation_sets; - GF_List *subsets; - char *xlink_href; - Bool xlink_actuate_on_load; -} GF_MPD_Period; - -typedef struct -{ - char *lang; - char *title; - char *source; - char *copyright; - char *more_info_url; -} GF_MPD_ProgramInfo; - - -typedef enum { - GF_MPD_TYPE_STATIC, - GF_MPD_TYPE_DYNAMIC, -} GF_MPD_Type; - -typedef struct { - MPD_EXTENSIBLE - - char *ID; - char *profiles; /*MANDATORY*/ - GF_MPD_Type type; - u64 availabilityStartTime; /* expressed in milliseconds */ /*MANDATORY if type=dynamic*/ - u64 availabilityEndTime;/* expressed in milliseconds */ - u64 publishTime;/* expressed in milliseconds */ - u64 media_presentation_duration; /* expressed in milliseconds */ /*MANDATORY if type=static*/ - u32 minimum_update_period; /* expressed in milliseconds */ - u32 min_buffer_time; /* expressed in milliseconds */ /*MANDATORY*/ - - u32 time_shift_buffer_depth; /* expressed in milliseconds */ - u32 suggested_presentation_delay; /* expressed in milliseconds */ - - u32 max_segment_duration; /* expressed in milliseconds */ - u32 max_subsegment_duration; /* expressed in milliseconds */ - - /*list of GF_MPD_ProgramInfo */ - GF_List *program_infos; - /*list of GF_MPD_BaseURL */ - GF_List *base_URLs; - /*list of strings */ - GF_List *locations; - /*list of Metrics */ - GF_List *metrics; - /*list of GF_MPD_Period */ - GF_List *periods; - - /*set during parsing*/ - const char *xml_namespace; /*won't be freed by GPAC*/ -} GF_MPD; - -GF_Err gf_mpd_init_from_dom(GF_XMLNode *root, GF_MPD *mpd, const char *base_url); -GF_Err gf_mpd_complete_from_dom(GF_XMLNode *root, GF_MPD *mpd, const char *base_url); - -GF_MPD *gf_mpd_new(); -void gf_mpd_del(GF_MPD *mpd); -/*frees a GF_MPD_SegmentURL structure (type-casted to void *)*/ -void gf_mpd_segment_url_free(void *ptr); -void gf_mpd_segment_base_free(void *ptr); - -void gf_mpd_period_free(void *_item); - -GF_Err gf_mpd_write_file(GF_MPD const * const mpd, const char *file_name); - - -typedef struct _gf_file_get GF_FileDownload; -struct _gf_file_get -{ - GF_Err (*new_session)(GF_FileDownload *getter, char *url); - void (*del_session)(GF_FileDownload *getter); - const char *(*get_cache_name)(GF_FileDownload *getter); - - void *udta; - /*created by udta after new_session*/ - void *session; -}; - -/*converts M3U8 to MPD - getter is optional (download will still be processed if NULL)*/ -GF_Err gf_m3u8_to_mpd(const char *m3u8_file, const char *base_url, const char *mpd_file, u32 reload_count, char *mimeTypeForM3U8Segments, Bool do_import, Bool use_mpd_templates, - GF_FileDownload *getter, GF_MPD *mpd, Bool parse_sub_playlist); - -GF_Err gf_m3u8_solve_representation_xlink(GF_MPD_Representation *rep, GF_FileDownload *getter, Bool *is_static, u64 *duration); - -GF_MPD_SegmentList *gf_mpd_solve_segment_list_xlink(GF_MPD *mpd, GF_XMLNode *root); - -GF_Err gf_mpd_init_smooth_from_dom(GF_XMLNode *root, GF_MPD *mpd, const char *default_base_url); - -void gf_mpd_delete_segment_list(GF_MPD_SegmentList *segment_list); - -void gf_mpd_getter_del_session(GF_FileDownload *getter); - - -/*get the number of base URLs for the given representation. This cumuluates all base URLs at MPD, period, AdaptationSet and Representation levels*/ -u32 gf_mpd_get_base_url_count(GF_MPD *mpd, GF_MPD_Period *period, GF_MPD_AdaptationSet *set, GF_MPD_Representation *rep); - -typedef enum -{ - GF_MPD_RESOLVE_URL_MEDIA, - GF_MPD_RESOLVE_URL_INIT, - GF_MPD_RESOLVE_URL_INDEX, - //same as GF_MPD_RESOLVE_URL_MEDIA but does not replace $Time$ and $Number$ - GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE, -} GF_MPD_URLResolveType; - -/*resolves a URL based for a given segment, based on the MPD url, the type of resolution - base_url_index: 0-based index of the baseURL to use - item_index: current downloading index of the segment - nb_segments_removed: number of segments removed when purging the MPD after updates (can be 0). The start number will be offset by this value -*/ -GF_Err gf_mpd_resolve_url(GF_MPD *mpd, GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set, GF_MPD_Period *period, const char *mpd_url, u32 base_url_index, GF_MPD_URLResolveType resolve_type, u32 item_index, u32 nb_segments_removed, - char **out_url, u64 *out_range_start, u64 *out_range_end, u64 *segment_duration, Bool *is_in_base_url, char **out_key_url, bin128 *key_iv); - -/*get duration of the presentation*/ -Double gf_mpd_get_duration(GF_MPD *mpd); - -/*get the duration of media segments*/ -void gf_mpd_resolve_segment_duration(GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set, GF_MPD_Period *period, u64 *out_duration, u32 *out_timescale, u64 *out_pts_offset, GF_MPD_SegmentTimeline **out_segment_timeline); - -/*get the start_time from the segment index of a period/set/rep*/ -GF_Err gf_mpd_get_segment_start_time_with_timescale(s32 in_segment_index, - GF_MPD_Period const * const in_period, GF_MPD_AdaptationSet const * const in_set, GF_MPD_Representation const * const in_rep, - u64 *out_segment_start_time, u64 *out_opt_segment_duration, u32 *out_opt_scale); - -typedef enum { - MPD_SEEK_PREV, /*will return the segment containing the requested time*/ - MPD_SEEK_NEAREST, /*the nearest segment start time, may be the previous or the next one*/ -} MPDSeekMode; - - -/*returns the segment index in the given period for the given time*/ -GF_Err gf_mpd_seek_in_period(Double seek_time, MPDSeekMode seek_mode, - GF_MPD_Period const * const in_period, GF_MPD_AdaptationSet const * const in_set, GF_MPD_Representation const * const in_rep, - u32 *out_segment_index, Double *out_opt_seek_time); - -/*get the index and the period for a given time in the MPD*/ -GF_EXPORT -GF_Err gf_mpd_seek_to_time(Double seek_time, MPDSeekMode seek_mode, - GF_MPD const * const in_mpd, GF_MPD_AdaptationSet const * const in_set, GF_MPD_Representation const * const in_rep, - GF_MPD_Period **out_period, u32 *out_segment_index, Double *out_opt_seek_time); - -/*get duration of the presentation*/ -Double gf_mpd_get_duration(GF_MPD *mpd); - -/*get the duration of media segments in seconds*/ -void gf_mpd_resolve_segment_duration(GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set, GF_MPD_Period *period, u64 *out_duration, u32 *out_timescale, u64 *out_pts_offset, GF_MPD_SegmentTimeline **out_segment_timeline); - -#endif /*GPAC_DISABLE_CORE_TOOLS*/ - -#endif // _MPD_H_ diff --git a/include/gpac/internal/odf_dev.h b/include/gpac/internal/odf_dev.h index e3455b4..cc14eea 100644 --- a/include/gpac/internal/odf_dev.h +++ b/include/gpac/internal/odf_dev.h @@ -54,7 +54,7 @@ GF_Err DumpDescList(GF_List *list, FILE *trace, u32 indent, const char *ListName /*IPMPX tools*/ u32 gf_ipmpx_array_size(GF_BitStream *bs, u32 *array_size); -void gf_ipmpx_write_array(GF_BitStream *bs, char *data, u32 data_len); +void gf_ipmpx_write_array(GF_BitStream *bs, u8 *data, u32 data_len); /*QoS qualifiers base functions*/ GF_Err gf_odf_parse_qos_qual(GF_BitStream *bs, GF_QoS_Default **qos_qual, u32 *qos_size); @@ -143,6 +143,7 @@ GF_Err gf_odf_del_bifs_cfg(GF_BIFSConfig *desc); GF_Err gf_odf_del_ui_cfg(GF_UIConfig *desc); GF_Err gf_odf_del_laser_cfg(GF_LASERConfig *desc); GF_Err gf_odf_del_auxvid(GF_AuxVideoDescriptor *ld); +GF_Err gf_odf_del_ElemMask(GF_ElementaryMask *desc); GF_Err gf_odf_read_iod(GF_BitStream *bs, GF_InitialObjectDescriptor *iod, u32 DescSize); GF_Err gf_odf_read_esd(GF_BitStream *bs, GF_ESD *esd, u32 DescSize); @@ -176,7 +177,6 @@ GF_Err gf_odf_read_smpte_camera(GF_BitStream *bs, GF_SMPTECamera *cpd, u32 DescS GF_Err gf_odf_read_sup_cid(GF_BitStream *bs, GF_SCIDesc *scid, u32 DescSize); GF_Err gf_odf_read_segment(GF_BitStream *bs, GF_Segment *sd, u32 DescSize); GF_Err gf_odf_read_mediatime(GF_BitStream *bs, GF_MediaTime *mt, u32 DescSize); -GF_Err gf_odf_read_muxinfo(GF_BitStream *bs, GF_MuxInfo *mi, u32 DescSize); GF_Err gf_odf_read_ipmp_tool_list(GF_BitStream *bs, GF_IPMP_ToolList *ipmptl, u32 DescSize); GF_Err gf_odf_read_ipmp_tool(GF_BitStream *bs, GF_IPMP_Tool *ipmp, u32 DescSize); GF_Err gf_odf_read_auxvid(GF_BitStream *bs, GF_AuxVideoDescriptor *ld, u32 DescSize); @@ -265,7 +265,6 @@ GF_ODCom *gf_odf_create_command(u8 tag); GF_Err gf_odf_delete_command(GF_ODCom *com); GF_Err gf_odf_parse_command(GF_BitStream *bs, GF_ODCom **com, u32 *com_size); GF_Err gf_odf_read_command(GF_BitStream *bs, GF_ODCom *com, u32 gf_odf_size_command); -GF_Err gf_odf_size_command(GF_ODCom *com, u32 *outSize); GF_Err gf_odf_write_command(GF_BitStream *bs, GF_ODCom *com); GF_ODCom *gf_odf_new_od_remove(); diff --git a/include/gpac/internal/odf_parse_common.h b/include/gpac/internal/odf_parse_common.h index 8d52d74..473c621 100644 --- a/include/gpac/internal/odf_parse_common.h +++ b/include/gpac/internal/odf_parse_common.h @@ -33,11 +33,18 @@ #define GET_S16(field) { s16 d; if (strstr(val, "0x")) { ret += sscanf(val, "%hx", (u16*)&d); if (ret) field = d; } else { ret += sscanf(val, "%hd", &d); if (ret) field = d; } } #define GET_U32(field) { u32 d; if (strstr(val, "0x")) { ret += sscanf(val, "%x", &d); if (ret) field = d; } else { ret += sscanf(val, "%ud", &d); if (ret) field = d; } } #define GET_S32(field) { s32 d; if (strstr(val, "0x")) { ret += sscanf(val, "%x", (u32*)&d); if (ret) field = d; } else { ret += sscanf(val, "%d", &d); if (ret) field = d; } } -#define GET_BOOL(field) { ret = 1; field = (!stricmp(val, "true") || !stricmp(val, "1")) ? GF_TRUE : GF_FALSE; } +#define GET_BOOL(field) { ret = 1; field = (!stricmp(val, "true") || !strcmp(val, "1")) ? GF_TRUE : GF_FALSE; } #define GET_U64(field) { u64 d; if (strstr(val, "0x")) { ret += sscanf(val, LLX, &d); if (ret) field = d; } else { ret += sscanf(val, LLU, &d); if (ret) field = d; } } #define GET_DOUBLE(field) { Float v; ret = 1; sscanf(val, "%f", &v); field = (Double) v;} -#define GET_STRING(field) { ret = 1; field = gf_strdup(val); if (val[0] == '"') strcpy(field, val+1); if (field[strlen(field)-1] == '"') field[strlen(field)-1] = 0; } +#define GET_STRING(field) { \ + ret = 1;\ + field = gf_strdup(val); \ + if (field) { \ + if (val[0] == '"') strcpy(field, val+1); \ + if (field[strlen(field)-1] == '"') field[strlen(field)-1] = 0;\ + }\ + } #endif /* _GF_OD_PARSE_COMMON_H_ */ diff --git a/include/gpac/internal/ogg.h b/include/gpac/internal/ogg.h index c87e1cd..87824e2 100644 --- a/include/gpac/internal/ogg.h +++ b/include/gpac/internal/ogg.h @@ -38,17 +38,17 @@ typedef struct { s32 endbyte; s32 endbit; - unsigned char *buffer; - unsigned char *ptr; + u8 *buffer; + u8 *ptr; s32 storage; } oggpack_buffer; /* ogg_page is used to encapsulate the data in one Ogg bitstream page *****/ typedef struct { - unsigned char *header; + u8 *header; s32 header_len; - unsigned char *body; + u8 *body; s32 body_len; } ogg_page; @@ -83,7 +83,7 @@ typedef struct { s64 packetno; /* sequence number for decode; the framing knows where there's a hole in the data, but we need coupling so that the codec - (which is in a seperate abstraction + (which is in a separate abstraction layer) also knows about the gap */ s64 granulepos; @@ -93,7 +93,7 @@ typedef struct { to a single raw Ogg/Vorbis packet *************************************/ typedef struct { - unsigned char *packet; + u8 *packet; s32 bytes; s32 b_o_s; s32 e_o_s; @@ -103,12 +103,12 @@ typedef struct { s64 packetno; /* sequence number for decode; the framing knows where there's a hole in the data, but we need coupling so that the codec - (which is in a seperate abstraction + (which is in a separate abstraction layer) also knows about the gap */ } ogg_packet; typedef struct { - unsigned char *data; + u8 *data; s32 storage; s32 fill; s32 returned; @@ -128,7 +128,7 @@ void oggpack_writealign(oggpack_buffer *b); void oggpack_writecopy(oggpack_buffer *b,void *source,s32 bits); void oggpack_reset(oggpack_buffer *b); void oggpack_writeclear(oggpack_buffer *b); -void oggpack_readinit(oggpack_buffer *b,unsigned char *buf,s32 bytes); +void oggpack_readinit(oggpack_buffer *b,u8 *buf,s32 bytes); void oggpack_write(oggpack_buffer *b,u32 value,s32 bits); s32 oggpack_look(oggpack_buffer *b,s32 bits); s32 oggpack_look1(oggpack_buffer *b); @@ -138,7 +138,7 @@ s32 oggpack_read(oggpack_buffer *b,s32 bits); s32 oggpack_read1(oggpack_buffer *b); s32 oggpack_bytes(oggpack_buffer *b); s32 oggpack_bits(oggpack_buffer *b); -unsigned char *oggpack_get_buffer(oggpack_buffer *b); +u8 *oggpack_get_buffer(oggpack_buffer *b); void oggpackB_writeinit(oggpack_buffer *b); void oggpackB_writetrunc(oggpack_buffer *b,s32 bits); @@ -146,7 +146,7 @@ void oggpackB_writealign(oggpack_buffer *b); void oggpackB_writecopy(oggpack_buffer *b,void *source,s32 bits); void oggpackB_reset(oggpack_buffer *b); void oggpackB_writeclear(oggpack_buffer *b); -void oggpackB_readinit(oggpack_buffer *b,unsigned char *buf,s32 bytes); +void oggpackB_readinit(oggpack_buffer *b,u8 *buf,s32 bytes); void oggpackB_write(oggpack_buffer *b,u32 value,s32 bits); s32 oggpackB_look(oggpack_buffer *b,s32 bits); s32 oggpackB_look1(oggpack_buffer *b); @@ -156,7 +156,7 @@ s32 oggpackB_read(oggpack_buffer *b,s32 bits); s32 oggpackB_read1(oggpack_buffer *b); s32 oggpackB_bytes(oggpack_buffer *b); s32 oggpackB_bits(oggpack_buffer *b); -unsigned char *oggpackB_get_buffer(oggpack_buffer *b); +u8 *oggpackB_get_buffer(oggpack_buffer *b); /* Ogg BITSTREAM PRIMITIVES: encoding **************************/ @@ -171,7 +171,7 @@ s32 ogg_sync_clear(ogg_sync_state *oy); s32 ogg_sync_reset(ogg_sync_state *oy); s32 ogg_sync_destroy(ogg_sync_state *oy); -char *ogg_sync_buffer(ogg_sync_state *oy, s32 size); +u8 *ogg_sync_buffer(ogg_sync_state *oy, s32 size); s32 ogg_sync_wrote(ogg_sync_state *oy, s32 bytes); s32 ogg_sync_pageseek(ogg_sync_state *oy,ogg_page *og); s32 ogg_sync_pageout(ogg_sync_state *oy, ogg_page *og); diff --git a/include/gpac/internal/scenegraph_dev.h b/include/gpac/internal/scenegraph_dev.h index 52d57d0..1d80707 100644 --- a/include/gpac/internal/scenegraph_dev.h +++ b/include/gpac/internal/scenegraph_dev.h @@ -75,7 +75,7 @@ struct _node_interactive_ext THIS IS DYNAMICALLY CREATED*/ GF_List *routes; -#ifdef GPAC_HAS_SPIDERMONKEY +#ifdef GPAC_HAS_QJS /*JS bindings if any - THIS IS DYNAMICALLY CREATED This speeds up field modif notification (script bindings are listed here)*/ struct _node_js_binding *js_binding; @@ -153,7 +153,7 @@ struct __tag_scene_graph /*callback routines*/ /*node callback*/ - void (*NodeCallback)(void *user_priv, u32 type, GF_Node *node, void *ctxdata); + gf_sg_node_init_callback NodeCallback; /*real scene time callback*/ Double (*GetSceneTime)(void *userpriv); @@ -194,12 +194,12 @@ struct __tag_scene_graph /*all routes available*/ GF_List *Routes; - /*when a proto is instanciated it creates its own scene graph. BIFS/VRML specify that the namespace is the same + /*when a proto is instantiated it creates its own scene graph. BIFS/VRML specify that the namespace is the same (eg cannot reuse a NodeID or route name/ID), but this could be done differently by some other stds if NULL this is the main scenegraph*/ struct _proto_instance *pOwningProto; - /*all first-level protos of the graph (the only ones that can be instanciated in this graph)*/ + /*all first-level protos of the graph (the only ones that can be instantiated in this graph)*/ GF_List *protos; /*all first-level protos of the graph not currently registered - memory handling of graph only*/ GF_List *unregistered_protos; @@ -251,14 +251,14 @@ struct __tag_scene_graph /*listeners to add*/ GF_List *listeners_to_add; -#ifdef GPAC_HAS_SPIDERMONKEY +#ifdef GPAC_HAS_QJS struct __tag_svg_script_ctx *svg_js; struct __tag_html_media_script_ctx *html_media_js; #endif #endif -#ifdef GPAC_HAS_SPIDERMONKEY +#ifdef GPAC_HAS_QJS GF_List *scripts; /* Note about reference counter @@ -275,20 +275,17 @@ struct __tag_scene_graph u32 reference_count; /*DOM nodes*/ GF_List *objects; - /*DOM document*/ - struct JSObject *document; - - Bool dcci_doc; - + struct _dom_js_data *js_data; Bool trigger_gc; + + u32 (*get_element_class)(GF_Node *n); + u32 (*get_document_class)(GF_SceneGraph *n); #endif }; void gf_sg_parent_setup(GF_Node *pNode); void gf_sg_parent_reset(GF_Node *pNode); -void *gf_node_get_name_address(GF_Node*node); - void gf_node_changed_internal(GF_Node *node, GF_FieldInfo *field, Bool notify_scripts); void gf_node_dirty_parent_graph(GF_Node *node); @@ -381,9 +378,6 @@ void gf_sg_sfurl_del(SFURL url); Bool gf_sg_vrml_node_init(GF_Node *node); Bool gf_sg_vrml_node_changed(GF_Node *node, GF_FieldInfo *field); -char *gf_node_vrml_dump_attribute(GF_Node *n, GF_FieldInfo *info); - - // // MF Fields tools @@ -444,7 +438,7 @@ GF_ProtoFieldInterface *gf_sg_proto_new_field_interface(u32 FieldType); GF_Err gf_bifs_proto_field_set_aq_info(GF_ProtoFieldInterface *field, u32 QP_Type, u32 hasMinMax, u32 QPSFType, void *qp_min_value, void *qp_max_value, u32 QP13_NumBits); /*proto field instance. since it is useless to duplicate all coding info, names and the like -we seperate proto declaration and proto instanciation*/ +we separate proto declaration and proto instanciation*/ typedef struct { u8 EventType; @@ -527,7 +521,7 @@ GF_Err gf_sg_proto_get_field_index(GF_ProtoInstance *proto, u32 index, u32 code_ Bool gf_sg_proto_get_aq_info(GF_Node *Node, u32 FieldIndex, u8 *QType, u8 *AType, Fixed *b_min, Fixed *b_max, u32 *QT13_bits); GF_Err gf_sg_proto_get_field_ind_static(GF_Node *Node, u32 inField, u8 IndexMode, u32 *allField); GF_Node *gf_sg_proto_create_node(GF_SceneGraph *scene, GF_Proto *proto, GF_ProtoInstance *from_inst); -void gf_sg_proto_instanciate(GF_ProtoInstance *proto_node); +void gf_sg_proto_instantiate(GF_ProtoInstance *proto_node); /*get tag of first node in proto code - used for validation only*/ u32 gf_sg_proto_get_root_tag(GF_Proto *proto); @@ -554,13 +548,10 @@ GF_Err gf_node_try_destroy(GF_SceneGraph *sg, GF_Node *pNode, GF_Node *parentNod /* reset functions for SVG types */ -void gf_svg_reset_path(SVG_PathData path); void gf_svg_reset_iri(GF_SceneGraph *sg, XMLRI*iri); /* delete functions for SVG types */ void gf_svg_delete_paint (GF_SceneGraph *sg, SVG_Paint *paint); void gf_smil_delete_times (GF_List *l); -void gf_svg_delete_points (GF_List *l); -void gf_svg_delete_coordinates (GF_List *l); /*for keyTimes, keyPoints and keySplines*/ void gf_smil_delete_key_types (GF_List *l); @@ -637,7 +628,6 @@ Bool gf_svg_is_inherit(GF_FieldInfo *a); Bool gf_svg_is_current_color(GF_FieldInfo *a); void gf_svg_reset_animate_values(SMIL_AnimateValues anim_values, GF_SceneGraph *sg); -void gf_svg_reset_animate_value(SMIL_AnimateValue anim_value, GF_SceneGraph *sg); Bool gf_svg_is_timing_tag(u32 tag); Bool gf_svg_is_animation_tag(u32 tag); @@ -692,8 +682,8 @@ struct _smil_timing_rti scene tree traversal.*/ Bool postpone; - void (*evaluate)(struct _smil_timing_rti *rti, Fixed normalized_simple_time, u32 state); - u32 evaluate_status; + void (*evaluate)(struct _smil_timing_rti *rti, Fixed normalized_simple_time, GF_SGSMILTimingEvalState state); + GF_SGSMILTimingEvalState evaluate_status; #if 0 /* is called only when the timed element is active */ @@ -833,37 +823,8 @@ void gf_smil_timing_resume(GF_Node *node); #endif +typedef struct _gf_vrml_script_priv GF_ScriptPriv; -/* - Script node -*/ - -typedef struct -{ - //extra script fields - GF_List *fields; - - //BIFS coding stuff - u32 numIn, numDef, numOut; - -#ifdef GPAC_HAS_SPIDERMONKEY - struct JSContext *js_ctx; - struct JSObject *js_obj; - struct JSObject *js_browser; - /*all attached objects (eg, not created by the script) are stored here so that we don't - allocate them again and again when getting properties. Garbage collection is performed (if needed) - on these objects after each eventIn execution*/ - GF_List *js_cache; - //Event object, whose private is the pointer to current event being executed - struct JSObject *the_event; -#endif - - void (*JS_PreDestroy)(GF_Node *node); - void (*JS_EventIn)(GF_Node *node, GF_FieldInfo *in_field); - - Bool is_loaded; - -} GF_ScriptPriv; /*setup script stack*/ void gf_sg_script_init(GF_Node *node); @@ -890,127 +851,18 @@ struct _scriptfield Double last_route_time; Bool activate_event_out; + s32 magic; }; -#ifdef GPAC_HAS_SPIDERMONKEY +#ifdef GPAC_HAS_QJS #include <gpac/download.h> #include <gpac/network.h> - -struct JSContext *gf_sg_ecmascript_new(GF_SceneGraph *sg); -void gf_sg_ecmascript_del(struct JSContext *); - -GF_Node *gf_sg_js_get_node(struct JSContext *c, struct JSObject *obj); - -void gf_sg_script_init_sm_api(GF_ScriptPriv *sc, GF_Node *script); - -/*GC thing type: 0 for jsval, 1 for jsstring, 2 for jsobject*/ -enum -{ - GF_JSGC_VAL=0, - GF_JSGC_STRING, - GF_JSGC_OBJECT, -}; - -Bool gf_js_add_root(struct JSContext *cx, void *rp, u32 type); -Bool gf_js_add_named_root(struct JSContext *cx, void *rp, u32 type, const char *name); -Bool gf_js_remove_root(struct JSContext *cx, void *rp, u32 type); void gf_js_vrml_flush_event_out(GF_Node *node, GF_ScriptPriv *priv); -#ifdef GPAC_HAS_SPIDERMONKEY -void gf_sg_lock_javascript(struct JSContext *c, Bool LockIt); -Bool gf_sg_try_lock_javascript(struct JSContext *c); -void gf_sg_js_call_gc(struct JSContext *c); -#endif /* GPAC_HAS_SPIDERMONKEY */ - - -typedef struct -{ - GF_FieldInfo field; - GF_Node *owner; - struct JSObject *obj; - - /*JS list for MFFields or NULL*/ - struct JSObject *js_list; - - /*pointer to the SFNode if this is an SFNode or MFNode[i] field */ - GF_Node *node; - /*when creating MFnode from inside the script, the node list is stored here untill attached to an object*/ - GF_ChildNodeItem *temp_list; - /*when not owned by a node*/ - void *field_ptr; - - /*cpontext in which the field was created*/ - struct JSContext *js_ctx; - Bool is_rooted; -} GF_JSField; - - -struct _node_js_binding -{ - void *node; /*GF_JSField for VRML, JSObject otherwise*/ - GF_List *fields; -}; - - -#ifndef GPAC_DISABLE_SVG - -typedef struct __tag_svg_script_ctx -{ - Bool (*script_execute)(struct __tag_scene_graph *sg, char *utf8_script, GF_DOM_Event *event); - Bool (*handler_execute)(GF_Node *n, GF_DOM_Event *event, GF_Node *observer, char *utf8_script); - u32 nb_scripts; - /*global script context for the scene*/ - struct JSContext *js_ctx; - /*global object*/ - struct JSObject *global; - /*global event object - used to update the associated DOMEvent (JS private stack) when dispatching events*/ - struct JSObject *event; - - Bool in_script; - Bool force_gc; -} GF_SVGJS; - -typedef struct __tag_html_media_script_ctx -{ - /*global script context for the scene*/ - struct JSContext *js_ctx; - /*global object*/ - struct JSObject *global; - /*global event object - used to update the associated DOMEvent (JS private stack) when dispatching events*/ - struct JSObject *event; - - Bool force_gc; -} GF_HTMLMediaJS; - -#endif /*GPAC_DISABLE_SVG*/ - -/*initialize DOM Core (subset) + xmlHTTPRequest API. The global object MUST have private data storage -and its private data MUST be a scenegraph. This scenegraph is only used to create new documents -and setup the callback pointers*/ -void dom_js_load(GF_SceneGraph *scene, struct JSContext *c, struct JSObject *global); -/*unloads the DOM core support (to be called upon destruction only, once the JSContext has been destroyed -to releases all resources used by DOM JS)*/ -void dom_js_unload(); -/*unloads DOM core before the JSContext is being destroyed */ -void gf_sg_js_dom_pre_destroy(struct JSContext *c, GF_SceneGraph *sg, GF_Node *script_or_handler_node); - -/*defines a new global object "document" of type Document*/ -void dom_js_define_document(struct JSContext *c, struct JSObject *global, GF_SceneGraph *doc); -/*defines a new global object "evt" of type Event*/ -struct JSObject *dom_js_define_event(struct JSContext *c, struct JSObject *global); - -struct JSObject *gf_dom_new_event(struct JSContext *c); - -struct JSObject *dom_js_get_node_proto(struct JSContext *c); -struct JSObject *dom_js_get_element_proto(struct JSContext *c); -struct JSObject *dom_js_get_document_proto(struct JSContext *c); -struct JSObject *dom_js_get_event_proto(struct JSContext *c); - -void dom_set_class_selector(struct JSContext *c, /*struct JSClass*/void *(*get_element_class)(GF_Node *n), /*struct JSClass*/void *(*get_document_class)(GF_SceneGraph *n) ); enum { @@ -1042,16 +894,10 @@ enum }; -int dom_throw_exception(struct JSContext *c, u32 code); - void gf_sg_handle_dom_event_for_vrml(GF_Node *hdl, GF_DOM_Event *event, GF_Node *observer); -void gf_sg_load_script_extensions(GF_SceneGraph *sg, struct JSContext *c, struct JSObject *obj, Bool unload); - Bool gf_sg_javascript_initialized(); -#endif /*GPAC_HAS_SPIDERMONKEY*/ - -GF_Err gf_sg_reload_xml_doc(const char *src, GF_SceneGraph *scene); +#endif /*GPAC_HAS_QJS*/ SVG_Element *gf_svg_create_node(u32 tag); Bool gf_svg_node_init(GF_Node *node); @@ -1084,20 +930,11 @@ will not be triggered*/ void gf_sg_listener_post_add(GF_Node *obs, GF_Node *listener); /*process all pending add_listener request*/ void gf_dom_listener_process_add(GF_SceneGraph *sg); -void gf_dom_listener_reset_defered(GF_SceneGraph *sg); +void gf_dom_listener_reset_deferred(GF_SceneGraph *sg); void gf_node_delete_attributes(GF_Node *node); - -typedef GF_DOMNode XBL_Element; -const char *gf_xbl_get_element_name(u32 tag); -u32 gf_xbl_get_element_tag(const char *element_name); -XBL_Element *gf_xbl_create_node(u32 ElementTag); -u32 gf_xbl_get_attribute_tag(u32 element_tag, const char *attribute_name); -GF_DOMAttribute *gf_xbl_create_attribute(GF_DOMNode *elt, u32 tag); - - GF_Node *gf_xml_node_clone(GF_SceneGraph *inScene, GF_Node *orig, GF_Node *cloned_parent, char *inst_id, Bool deep); GF_Err gf_dom_listener_del(GF_Node *listener, GF_DOMEventTarget *target); diff --git a/include/gpac/internal/smjs_api.h b/include/gpac/internal/smjs_api.h deleted file mode 100644 index 683dc7e..0000000 --- a/include/gpac/internal/smjs_api.h +++ /dev/null @@ -1,358 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2010 - * All rights reserved - * - * This file is part of GPAC / Scene Management sub-project - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef GPAC_JSAPI -#define GPAC_JSAPI - -#include <gpac/setup.h> - -/*WIN32 and WinCE config (no configure script)*/ -#if defined(WIN32) || defined(_WIN32_WCE) || defined(__SYMBIAN32__) -# ifndef XP_PC -# define XP_PC -# endif -# ifndef XP_WIN -# define XP_WIN -# endif -/*WINCE specific config*/ -# if defined(_WIN32_WCE) -# include <windows.h> -# define XP_WINCE -# endif -#endif - -//Android config -#ifdef GPAC_ANDROID -# ifndef XP_UNIX -# define XP_UNIX -# endif -#endif - -//iOS config -#ifdef GPAC_IPHONE -# ifndef XP_UNIX -# define XP_UNIX -# endif -# ifndef IPHONE_OS -# define IPHONE_OS -# endif -# ifndef DARWIN -# define DARWIN -# endif -#endif - -/*OSX config*/ -#if defined(GPAC_CONFIG_DARWIN) && !defined(GPAC_IPHONE) -# define MOZILLA_1_8_BRANCH -# ifndef XP_UNIX -# define XP_UNIX -# endif -#endif - - -/*other platforms should be setup through ./configure and have appropriated defines in config.h*/ - -#if defined(DEBUG) && defined(GPAC_CONFIG_DARWIN) -#undef DEBUG -#endif - -#include <jsapi.h> - -#ifndef JS_VERSION -#define JS_VERSION 170 -#endif - - -typedef struct -{ - JSClass _class; - JSObject *_proto; -} GF_JSClass; - - - -/*new APIs*/ -#if (JS_VERSION>=185) - -#ifdef USE_FFDEV_18 -#define USE_FFDEV_17 -#endif - -#ifdef USE_FFDEV_17 -#define USE_FFDEV_16 -#endif - -#ifdef USE_FFDEV_16 -#define USE_FFDEV_15 -#endif - -#ifdef USE_FFDEV_15 -#define JSVAL_IS_OBJECT(__v) JSVAL_IS_OBJECT_OR_NULL_IMPL(JSVAL_TO_IMPL(__v)) -#define USE_FFDEV_14 -#endif - -#ifdef USE_FFDEV_14 -#define USE_FFDEV_12 - -#define JS_FinalizeStub NULL -#ifdef USE_FFDEV_16 -#define JS_NEW_OBJ_FOR_CONS(__c, __classp, __args) JS_NewObjectForConstructor(__c, &(__classp)->_class, __args) -#else -#define JS_NEW_OBJ_FOR_CONS(__c, __classp, __args) JS_NewObjectForConstructor(__c, __classp, __args) -#endif - -#else -#define JS_NEW_OBJ_FOR_CONS(__c, __classp, __args) JS_NewObjectForConstructor(__c, __args) -#endif - -#ifdef USE_FFDEV_12 -typedef unsigned uintN; -typedef uint32_t jsuint; -typedef int jsint; -typedef double jsdouble; -#endif - -#define JS_NewDouble(c, v) v -#define JS_PropertyStub_forSetter JS_StrictPropertyStub - -#if defined(USE_FFDEV_17) - -#define SMJS_DECL_FUNC_PROP_SET(func_name) JSBool func_name(JSContext *c, JSHandleObject __hobj, JSHandleId __hid, JSBool strict, JSMutableHandleValue __vp) -#define SMJS_FUNC_PROP_SET(func_name) SMJS_DECL_FUNC_PROP_SET(func_name) { JSObject *obj = *(__hobj._); jsid id = *(__hid._); jsval *vp = __vp._; -#define SMJS_FUNC_PROP_SET_NOVP(func_name) SMJS_DECL_FUNC_PROP_SET(func_name) { JSObject *obj = *(__hobj._); jsid id = *(__hid._); - -#define SMJS_DECL_FUNC_PROP_GET(func_name) JSBool func_name(JSContext *c, JSHandleObject __hobj, JSHandleId __hid, JSMutableHandleValue __vp) -#define SMJS_FUNC_PROP_GET(func_name) SMJS_DECL_FUNC_PROP_GET( func_name ) { JSObject *obj = *(__hobj._); jsid id = *(__hid._); jsval *vp = __vp._; -#define SMJS_CALL_PROP_STUB() JS_PropertyStub(c, __hobj, __hid, __vp) -#define DECL_FINALIZE(func_name) void func_name(JSFreeOp *fop, JSObject *obj) { void *c = NULL; - -#define SMJS_FUNCTION_SPEC(__name, __fun, __argc) JS_FS(__name, __fun, __argc, 0) -#define SMJS_PROPERTY_SPEC(__name, __tinyid, __flags, __getter, __setter) \ - {__name, __tinyid, __flags, JSOP_WRAPPER(__getter), JSOP_WRAPPER(__setter)} - - -#else - -#ifdef USE_FFDEV_15 - -#define SMJS_DECL_FUNC_PROP_SET(func_name) JSBool func_name(JSContext *c, JSHandleObject __hobj, JSHandleId __hid, JSBool strict, jsval *vp) -#define SMJS_FUNC_PROP_SET(func_name) SMJS_DECL_FUNC_PROP_SET(func_name) { JSObject *obj = *(__hobj._); jsid id = *(__hid._); -#define SMJS_DECL_FUNC_PROP_GET(func_name) JSBool func_name(JSContext *c, JSHandleObject __hobj, JSHandleId __hid, jsval *vp) -#define SMJS_FUNC_PROP_GET(func_name) SMJS_DECL_FUNC_PROP_GET( func_name ) { JSObject *obj = *(__hobj._); jsid id = *(__hid._); -#define SMJS_CALL_PROP_STUB() JS_PropertyStub(c, __hobj, __hid, vp) -#define DECL_FINALIZE(func_name) void func_name(JSFreeOp *fop, JSObject *obj) { void *c = NULL; - -#else - -#define SMJS_DECL_FUNC_PROP_SET(func_name) JSBool func_name(JSContext *c, JSObject *obj, jsid id, JSBool strict, jsval *vp) -#define SMJS_FUNC_PROP_SET(func_name) SMJS_DECL_FUNC_PROP_SET( func_name) { -#define SMJS_DECL_FUNC_PROP_GET(func_name) JSBool func_name(JSContext *c, JSObject *obj, jsid id, jsval *vp) -#define SMJS_FUNC_PROP_GET(func_name) SMJS_DECL_FUNC_PROP_GET(func_name) { -#define SMJS_CALL_PROP_STUB() JS_PropertyStub(c, obj, id, vp) -#define DECL_FINALIZE(func_name) void func_name(JSContext *c, JSObject *obj) { - -#endif - -#define SMJS_FUNC_PROP_SET_NOVP SMJS_FUNC_PROP_SET - -#define SMJS_FUNCTION_SPEC(__name, __fun, __argc) {__name, __fun, __argc, 0} -#define SMJS_PROPERTY_SPEC(__name, __tinyid, __flags, __getter, __setter) \ - {__name, __tinyid, __flags, __getter, __setter} - -#endif - -#define SMJS_FUNCTION(__name) __name(JSContext *c, uintN argc, jsval *argsvp) -#define SMJS_FUNCTION_EXT(__name, __ext) __name(JSContext *c, uintN argc, jsval *argsvp, __ext) -#define SMJS_ARGS jsval *argv = JS_ARGV(c, argsvp); -#define SMJS_OBJ JSObject *obj = JS_THIS_OBJECT(c, argsvp); -#define SMJS_SET_RVAL(__rval) JS_SET_RVAL(c, argsvp, __rval) -#define SMJS_GET_RVAL & JS_RVAL(c, argsvp) -#define SMJS_CALL_ARGS c, argc, argsvp -#define SMJS_DECL_RVAL jsval *rval = & JS_RVAL(c, argsvp); - -#define SMJS_CHARS_FROM_STRING(__c, __jsstr) (char *) JS_EncodeString(__c, __jsstr) -#define SMJS_CHARS(__c, __val) SMJS_CHARS_FROM_STRING(__c, JSVAL_TO_STRING(__val)) -#define SMJS_FREE(__c, __str) if (__str) JS_free(__c, __str) - - -#define SMJS_OBJ_CONSTRUCTOR(__classp) \ - JSObject *obj = JS_NEW_OBJ_FOR_CONS(c, __classp, argsvp); \ - SMJS_SET_RVAL(OBJECT_TO_JSVAL(obj));\ - -#define JS_GetFunctionName(_v) (JS_GetFunctionId(_v)!=NULL) ? SMJS_CHARS_FROM_STRING(c, JS_GetFunctionId(_v)) : NULL - -#define SMJS_ID_IS_STRING JSID_IS_STRING -#define SMJS_ID_TO_STRING JSID_TO_STRING -#define SMJS_ID_IS_INT JSID_IS_INT -#define SMJS_ID_TO_INT JSID_TO_INT - -#ifndef JS_THREADSAFE -#define JS_THREADSAFE -#endif - - -#ifdef USE_FFDEV_12 -#define JS_ClearContextThread(__ctx) -#define JS_SetContextThread(__ctx) -#define SMJS_GET_PRIVATE(__ctx, __obj) JS_GetPrivate(__obj) -#define SMJS_SET_PRIVATE(__ctx, __obj, __val) JS_SetPrivate(__obj, __val) -#define SMJS_GET_PARENT(__ctx, __obj) JS_GetParent(__obj) -#define SMJS_GET_CLASS(__ctx, __obj) JS_GetClass(__obj) - - -#ifdef USE_FFDEV_15 -#define JS_GET_CLASS(__ctx, __obj) JS_GetClass(* __obj._) -#else -#define JS_GET_CLASS(__ctx, __obj) JS_GetClass(__obj) -#endif - -#ifdef USE_FFDEV_16 -#define SMJS_CONSTRUCT_OBJECT(__ctx, __class, __parent) JS_New(__ctx, JS_GetConstructor(__ctx, (__class)->_proto), 0, NULL) -#else -#define SMJS_CONSTRUCT_OBJECT(__ctx, __class, __parent) JS_ConstructObject(__ctx, &(__class)->_class, __parent) -#endif - -#else - -#define SMJS_GET_PRIVATE(__ctx, __obj) JS_GetPrivate(__ctx, __obj) -#define SMJS_SET_PRIVATE(__ctx, __obj, __val) JS_SetPrivate(__ctx, __obj, __val) -#define SMJS_GET_PARENT(__ctx, __obj) JS_GetParent(__ctx, __obj) -#define SMJS_GET_CLASS(__ctx, __obj) JS_GetClass(__ctx, __obj) - -#ifdef USE_FFDEV_11 -#define JS_ClearContextThread(__ctx) -#define JS_SetContextThread(__ctx) -#define SMJS_CONSTRUCT_OBJECT(__ctx, __class, __parent) JS_ConstructObject(__ctx, &(__class)->_class, __parent) -#else -#define SMJS_CONSTRUCT_OBJECT(__ctx, __class, __parent) JS_ConstructObject(__ctx, &(__class)->_class, 0, __parent) -#endif - -#endif - - -#else - -/* Windows with extra-libs */ - -#define SMJS_DECL_FUNC_PROP_SET(func_name) JSBool func_name(JSContext *c, JSObject *obj, jsval id, jsval *vp) -#define SMJS_FUNC_PROP_SET(func_name) SMJS_DECL_FUNC_PROP_SET(func_name) { -#define SMJS_FUNC_PROP_SET_NOVP SMJS_FUNC_PROP_SET -#define SMJS_DECL_FUNC_PROP_GET(func_name) JSBool func_name(JSContext *c, JSObject *obj, jsval id, jsval *vp) -#define SMJS_FUNC_PROP_GET(func_name) SMJS_DECL_FUNC_PROP_GET( func_name) { -#define DECL_FINALIZE(func_name) void func_name(JSContext *c, JSObject *obj) { - -#define SMJS_CALL_PROP_STUB() JS_PropertyStub(c, obj, id, vp) -#define SMJS_PROP_SETTER jsval id -#define SMJS_PROP_GETTER jsval id -#define JS_PropertyStub_forSetter JS_PropertyStub -#define SMJS_FUNCTION_SPEC(__name, __fun, __argc) {__name, __fun, __argc, 0, 0} -#define SMJS_PROPERTY_SPEC(__name, __tinyid, __flags, __getter, __setter) {__name, __tinyid, __flags, __getter, __setter} -#define SMJS_FUNCTION(__name) __name(JSContext *c, JSObject *obj, uintN argc, jsval *argv, jsval *rval) -#define SMJS_FUNCTION_EXT(__name, __ext) __name(JSContext *c, JSObject *obj, uintN argc, jsval *argv, jsval *rval, __ext) -#define SMJS_ARGS -#define SMJS_OBJ -#define SMJS_OBJ_CONSTRUCTOR(__classp) -#define SMJS_GET_RVAL rval -#define SMJS_SET_RVAL(__rval) *rval = __rval -#define SMJS_CALL_ARGS c, obj, argc, argv, rval -#define SMJS_DECL_RVAL - -#define SMJS_CHARS_FROM_STRING(__c, __str) JS_GetStringBytes(__str) -#define SMJS_CHARS(__c, __val) JS_GetStringBytes(JSVAL_TO_STRING(__val)) -#define SMJS_FREE(__c, __str) - -#define SMJS_ID_IS_STRING JSVAL_IS_STRING -#define SMJS_ID_TO_STRING JSVAL_TO_STRING -#define SMJS_ID_IS_INT JSVAL_IS_INT -#define SMJS_ID_TO_INT JSVAL_TO_INT - -#define SMJS_CONSTRUCT_OBJECT(__ctx, __class, __parent) JS_ConstructObject(__ctx, &(__class)->_class, 0, __parent) -#define SMJS_GET_PRIVATE(__ctx, __obj) JS_GetPrivate(__ctx, __obj) -#define SMJS_SET_PRIVATE(__ctx, __obj, __val) JS_SetPrivate(__ctx, __obj, __val) -#define SMJS_GET_PARENT(__ctx, __obj) JS_GetParent(__ctx, __obj) -#define SMJS_GET_CLASS(__obj) JS_GetClass(__obj) - -#endif - - -#ifdef __cplusplus -extern "C" { -#endif - -#if (JS_VERSION>=185) -#if defined(USE_FFDEV_18) -JSBool gf_sg_js_has_instance(JSContext *cx, JSHandleObject obj, JSMutableHandleValue vp, JSBool *bp); -#elif defined(USE_FFDEV_15) -JSBool gf_sg_js_has_instance(JSContext *c, JSHandleObject obj,const jsval *val, JSBool *vp); -#else -JSBool gf_sg_js_has_instance(JSContext *c, JSObject *obj,const jsval *val, JSBool *vp); -#endif -#else -JSBool gf_sg_js_has_instance(JSContext *c, JSObject *obj, jsval val, JSBool *vp); -#endif - -#define JS_SETUP_CLASS(the_class, cname, flag, getp, setp, fin) \ - memset(&the_class, 0, sizeof(the_class)); \ - the_class._class.name = cname; \ - the_class._class.flags = flag; \ - the_class._class.addProperty = JS_PropertyStub; \ - the_class._class.delProperty = JS_PropertyStub; \ - the_class._class.getProperty = getp; \ - the_class._class.setProperty = setp; \ - the_class._class.enumerate = JS_EnumerateStub; \ - the_class._class.resolve = JS_ResolveStub; \ - the_class._class.convert = JS_ConvertStub; \ - the_class._class.finalize = (JSFinalizeOp) fin; \ - the_class._class.hasInstance = gf_sg_js_has_instance; - - -#define JS_MAKE_DOUBLE(__c, __double) DOUBLE_TO_JSVAL(JS_NewDouble(__c, __double) ) - - -#define GF_JS_InstanceOf(_a, _b, __class, _d) JS_InstanceOf(_a, _b, & (__class)->_class, NULL) - -#define GF_JS_InitClass(cx, obj, parent_proto, clasp, constructor, nargs, ps, fs, static_ps, static_fs) \ - (clasp)->_proto = JS_InitClass(cx, obj, parent_proto, &(clasp)->_class, constructor, nargs, ps, fs, static_ps, static_fs); - - -#define SMJS_GET_NUMBER(val, _d) { \ - if (!JSVAL_IS_NUMBER(val) || (val == JS_GetNaNValue(c)) ) { \ - GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[JS] Value is not a number while assigning @ file %s line %d \n", __FILE__, __LINE__)); \ - return JS_FALSE; \ - } \ - JS_ValueToNumber(c, val, &_d); \ - if (_d != _d) { \ - GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[JS] Value is not a number while assigning @ file %s line %d \n", __FILE__, __LINE__)); \ - return JS_FALSE; \ - } \ - } \ - -JSObject *gf_sg_js_global_object(JSContext *cx, GF_JSClass *__class); - -#ifdef __cplusplus -} -#endif - -#endif //GPAC_JSAPI diff --git a/include/gpac/internal/swf_dev.h b/include/gpac/internal/swf_dev.h index 84de33e..b5ff36b 100644 --- a/include/gpac/internal/swf_dev.h +++ b/include/gpac/internal/swf_dev.h @@ -186,10 +186,12 @@ struct SWFReader Bool svg_shape_started; /* end of SVG conversion state */ - /* MP4 user */ + /* MP4/SVG user */ void *user; - GF_Err (*add_sample)(void *user, const char *data, u32 length, u64 timestamp, Bool isRap); - GF_Err (*add_header)(void *user, const char *data, u32 length, Bool isHeader); + GF_Err (*add_sample)(void *user, const u8 *data, u32 length, u64 timestamp, Bool isRap); + GF_Err (*add_header)(void *user, const u8 *data, u32 length, Bool isHeader); + + FILE *svg_file; }; @@ -204,18 +206,19 @@ GF_Err swf_to_bifs_init(SWFReader *read); #ifndef GPAC_DISABLE_SVG GF_Err swf_to_svg_init(SWFReader *read, u32 flags, Float angle); -GF_Err swf_svg_write_text_sample(void *user, const char *data, u32 length, u64 timestamp, Bool isRap); -GF_Err swf_svg_write_text_header(void *user, const char *data, u32 length, Bool isHeader); +GF_Err swf_svg_write_text_sample(void *user, const u8 *data, u32 length, u64 timestamp, Bool isRap); +GF_Err swf_svg_write_text_header(void *user, const u8 *data, u32 length, Bool isHeader); #endif SWFReader *gf_swf_reader_new(const char *path, const char *filename); GF_Err gf_swf_read_header(SWFReader *read); void gf_swf_reader_del(SWFReader *read); +GF_Err gf_swf_get_duration(SWFReader *read, u32 *frame_rate, u32 *frame_count); GF_Err gf_swf_reader_set_user_mode(SWFReader *read, void *user, - GF_Err (*add_sample)(void *user, const char *data, u32 length, u64 timestamp, Bool isRap), - GF_Err (*add_header)(void *user, const char *data, u32 length, Bool isheader)); + GF_Err (*add_sample)(void *user, const u8 *data, u32 length, u64 timestamp, Bool isRap), + GF_Err (*add_header)(void *user, const u8 *data, u32 length, Bool isheader)); typedef struct { diff --git a/include/gpac/internal/terminal_dev.h b/include/gpac/internal/terminal_dev.h deleted file mode 100644 index 5b66e15..0000000 --- a/include/gpac/internal/terminal_dev.h +++ /dev/null @@ -1,1313 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / Stream Management sub-project - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef _GF_TERMINAL_DEV_H_ -#define _GF_TERMINAL_DEV_H_ - -#ifdef __cplusplus -extern "C" { -#endif - - -#include <gpac/terminal.h> -#include <gpac/mpeg4_odf.h> - -#include <gpac/modules/service.h> -#include <gpac/modules/codec.h> -#include <gpac/modules/ipmp.h> -#include <gpac/mediaobject.h> -#include <gpac/thread.h> -#include <gpac/modules/term_ext.h> - -typedef struct _scene GF_Scene; -typedef struct _media_manager GF_MediaManager; -typedef struct _object_clock GF_Clock; -typedef struct _es_channel GF_Channel; -typedef struct _generic_codec GF_Codec; -typedef struct _composition_memory GF_CompositionMemory; -typedef struct _gf_addon_media GF_AddonMedia; - - -struct _net_service -{ - /*the module handling this service - must be declared first to typecast with GF_DownloadSession upon deletion*/ - GF_InputService *ifce; - - //function table of service - void (*fn_connect_ack) (GF_ClientService *service, LPNETCHANNEL ns, GF_Err response); - void (*fn_disconnect_ack) (GF_ClientService *service, LPNETCHANNEL ns, GF_Err response); - void (*fn_command) (GF_ClientService *service, GF_NetworkCommand *com, GF_Err response); - void (*fn_data_packet) (GF_ClientService *service, LPNETCHANNEL ns, char *data, u32 data_size, GF_SLHeader *hdr, GF_Err reception_status); - void (*fn_add_media) (GF_ClientService *service, GF_Descriptor *media_desc, Bool no_scene_check); - - - - /*the terminal*/ - struct _tag_terminal *term; - /*service url*/ - char *url; - /*service mime type*/ - char *mime; - /*od_manager owning service, NULL for services created for remote channels*/ - struct _od_manager *owner; - /*number of attached remote channels ODM (ESD URLs)*/ - u32 nb_ch_users; - /*number of attached remote ODM (OD URLs)*/ - u32 nb_odm_users; - - /*clock objects. Kept at service level since ESID namespace is the service one*/ - GF_List *Clocks; - /*all downloaders objects used in this service*/ - GF_List *dnloads; - /*cache asscoiated with service, if any*/ - GF_StreamingCache *cache; - - /*quick hack for avoiding double session creation*/ - GF_DownloadSession *pending_service_session; - - Bool is_paused; - //used when term_set_speed is called before - Fixed set_speed; - - /*used by DASH until we rewrite the input module API: - if set to 1 during a disconnect() call, the root scene of the service and all sub-objects will be disconnected - if set to 2 during a disconnect() call, the call will be skiped - */ - u32 subservice_disconnect; -}; - - -/*opens service - performs URL concatenation if parent service specified*/ -GF_ClientService *gf_term_service_new(GF_Terminal *term, GF_ObjectManager *owner, const char *url, const char *parent_url, GF_Err *ret_code); -/*destroy service*/ -void gf_term_service_del(GF_ClientService *nets); - -/*access to the module interfaces - cf net_api.h GF_InputService for details*/ -GF_Err gf_term_service_command(GF_ClientService *ns, GF_NetworkCommand *com); -Bool gf_term_service_can_handle_url(GF_ClientService *ns, char *url); - -GF_Err gf_term_channel_get_sl_packet(GF_ClientService *ns, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, GF_SLHeader *out_sl_hdr, Bool *is_compressed, GF_Err *out_reception_status, Bool *is_new_data); -GF_Err gf_term_channel_release_sl_packet(GF_ClientService *ns, LPNETCHANNEL channel); - -/*cache open/close*/ -GF_Err gf_term_service_cache_load(GF_ClientService *ns); -GF_Err gf_term_service_cache_close(GF_ClientService *ns, Bool no_save); - -/*forwards all clocks of the given amount of time. Can only be used when terminal is in paused mode -this is mainly designed for movie dumping in MP4Client*/ -GF_Err gf_term_step_clocks(GF_Terminal * term, u32 ms_diff); - -u32 gf_term_sample_clocks(GF_Terminal *term); - -u32 gf_term_check_end_of_scene(GF_Terminal *term, Bool skip_interactions); - -/* - GF_Scene object. This is the structure handling all scene management, mainly: - - list of resources (media objects) used - - scene time management - - reload/seek - - Dynamic scene (without a scene description)* - Each scene registers itself as the private data of its associated scene graph. - - The scene object is also used by Inline nodes and all media resources of type "scene", eg <foreignObject>, <animation> -*/ -struct _scene -{ - /*root OD of the subscene, ALWAYS namespace of the parent scene*/ - struct _od_manager *root_od; - /*scene codec: top level decoder decoding/generating the scene - can be BIFS, VRML parser, etc*/ - struct _generic_codec *scene_codec; - /*OD codec - specific to MPEG-4*/ - struct _generic_codec *od_codec; - - GF_Mutex *mx_resources; - /*all sub resources of this scene (eg, list of GF_ObjectManager), namespace of this scene. This includes - both external resources (urls) and ODs sent in MPEG-4 systems*/ - GF_List *resources; - - /*list of GF_MediaObject - these are the links betwwen scene nodes (URL, xlink:href) and media resources. - We need this link because of MPEG-4 Systems, where an OD (media resource) can be removed or replaced by the server - without the scene being modified*/ - GF_List *scene_objects; - - /*list of extra scene graphs (text streams, generic OSDs, ...)*/ - GF_List *extra_scenes; - /*scene graph*/ - GF_SceneGraph *graph; - /*graph state - if not attached, no traversing of inline - 0: not attached - 1: attached - 2: temp graph attached. The temp graph is generated when waiting for the first scene AU to be processed - */ - u32 graph_attached; - - //indicates a valid object is attached to the scene - Bool object_attached; - - /*set to 1 when single time-line presentation with only static resources (ONE OD AU is detected or no media removal/adding possible) - This allows preventing OD/BIFS streams shutdown/startup when seeking.*/ - Bool static_media_ressources; - - /*set to 1 to force all sub-resources to share the timeline of this scene*/ - Bool force_single_timeline; - - /*set to 1 once the forced size has been sez*/ - Bool force_size_set; - - /*callback to call to dispatch SVG MediaEvent - this is a pointer to function only because of linking issues - with static libgpac (avoids depending on SpiderMonkey and OpenGL32 if not needed)*/ - void (*on_media_event)(GF_Scene *scene, u32 type); - - /*duration of inline scene*/ - u64 duration; - - /*max timeshift of all objects*/ - u32 timeshift_depth; - - /*WorldInfo node or <title> node*/ - void *world_info; - - /*current IRI fragment if any*/ - char *fragment_uri; - - /*secondary resource scene - this is specific to SVG: a secondary resource scene is <use> or <fonturi>, and all resources - identified in a secondary resource must be checked for existence and created in the primary resource*/ - Bool secondary_resource; - - /*needed by some apps in GPAC which manipulate the scene tree in different locations as the resources*/ - char *redirect_xml_base; - - - /*FIXME - Dynamic scenes are only supported through VRML/BIFS nodes, we should add support for SVG scene graph - generation if needed*/ - Bool is_dynamic_scene; - /*for MPEG-2 TS, indicates the current serviceID played from mux*/ - u32 selected_service_id; - /*clock for dynamic scene - current assumption is that all selected streams are synchronized in the dyn scene*/ - GF_Clock *dyn_ck; - /*URLs of current video, audio and subs (we can't store objects since they may be destroyed when seeking)*/ - SFURL visual_url, audio_url, text_url, dims_url; - - Bool is_srd, is_tiled_srd; - s32 srd_min_x, srd_max_x, srd_min_y, srd_max_y; - - - Bool end_of_scene; -#ifndef GPAC_DISABLE_VRML - /*list of externproto libraries*/ - GF_List *extern_protos; - - /*togles inline restart - needed because the restart may be triggered from inside the scene or from - parent scene, hence 2 render passes must be used - special value 2 means scene URL changes (for anchor navigation*/ - u32 needs_restart; - - /*URL of the current parent Inline node, only set during traversal*/ - MFURL *current_url; - - - /*list of M_Storage nodes active*/ - GF_List *storages; - - /*list of M_KeyNavigator nodes*/ - GF_List *keynavigators; -#endif - - Bool disable_hitcoord_notif; - - u32 addon_position, addon_size_factor; - - GF_List *declared_addons; - //set when content is replaced by an addon (DASH PVR mode) - Bool main_addon_selected; - u32 sys_clock_at_main_activation, obj_clock_at_main_activation; - - //0: no pause - 1: paused and trigger pause command to net, 2: only clocks are paused but commands not sent - u32 first_frame_pause_type; - u32 vr_type; -}; - -GF_Scene *gf_scene_new(GF_Scene *parentScene); -void gf_scene_del(GF_Scene *scene); -struct _od_manager *gf_scene_find_odm(GF_Scene *scene, u16 OD_ID); -void gf_scene_disconnect(GF_Scene *scene, Bool for_shutdown); -void gf_scene_remove_object(GF_Scene *scene, GF_ObjectManager *odm, u32 for_shutdown); -/*browse all (media) channels and send buffering info to the app*/ -void gf_scene_buffering_info(GF_Scene *scene); -void gf_scene_attach_to_compositor(GF_Scene *scene); -struct _mediaobj *gf_scene_get_media_object(GF_Scene *scene, MFURL *url, u32 obj_type_hint, Bool lock_timelines); -struct _mediaobj *gf_scene_get_media_object_ex(GF_Scene *scene, MFURL *url, u32 obj_type_hint, Bool lock_timelines, struct _mediaobj *sync_ref, Bool force_new_if_not_attached, GF_Node *node_ptr); -void gf_scene_setup_object(GF_Scene *scene, GF_ObjectManager *odm); -/*updates scene duration based on sub objects*/ -void gf_scene_set_duration(GF_Scene *scene); -/*updates scene timeshift based on sub objects*/ -void gf_scene_set_timeshift_depth(GF_Scene *scene); -/*locate media object by ODID (non dynamic ODs) or URL (dynamic ODs)*/ -struct _mediaobj *gf_scene_find_object(GF_Scene *scene, u16 ODID, char *url); -/*returns scene time in sec - exact meaning of time depends on standard used*/ -Double gf_scene_get_time(void *_is); -/*register extra scene graph for on-screen display*/ -void gf_scene_register_extra_graph(GF_Scene *scene, GF_SceneGraph *extra_scene, Bool do_remove); -/*forces scene size info (without changing pixel metrics) - this may be needed by modules using extra graphs (like timedtext)*/ -void gf_scene_force_size(GF_Scene *scene, u32 width, u32 height); -/*regenerate a scene graph based on available objects - can only be called for dynamic OD streams*/ -void gf_scene_regenerate(GF_Scene *scene); -/*selects given ODM for dynamic scenes*/ -void gf_scene_select_object(GF_Scene *scene, GF_ObjectManager *odm); -/*restarts dynamic scene from given time: scene graph is not reseted, objects are just restarted -instead of closed and reopened. If a media control is present on inline, from_time is overriden by MC range*/ -void gf_scene_restart_dynamic(GF_Scene *scene, s64 from_time, Bool restart_only, Bool disable_addon_check); - -/*exported for compositor: handles filtering of "self" parameter indicating anchor only acts on container inline scene -not root one. Returns 1 if handled (cf user.h, navigate event)*/ -Bool gf_scene_process_anchor(GF_Node *caller, GF_Event *evt); -void gf_scene_force_size_to_video(GF_Scene *scene, GF_MediaObject *mo); - -//check clock status. -//If @check_buffering is 0, returns 1 if all clocks have seen eos, 0 otherwise -//If @check_buffering is 1, returns 1 if no clock is buffering, 0 otheriwse -Bool gf_scene_check_clocks(GF_ClientService *ns, GF_Scene *scene, Bool check_buffering); - -void gf_scene_notify_event(GF_Scene *scene, u32 event_type, GF_Node *n, void *dom_evt, GF_Err code, Bool no_queueing); - -void gf_scene_mpeg4_inline_restart(GF_Scene *scene); -void gf_scene_mpeg4_inline_check_restart(GF_Scene *scene); - - -GF_Node *gf_scene_get_subscene_root(GF_Node *inline_node); - -void gf_scene_select_main_addon(GF_Scene *scene, GF_ObjectManager *odm, Bool set_on, u32 current_clock_time); -void gf_scene_reset_addons(GF_Scene *scene); - -#ifndef GPAC_DISABLE_VRML - -/*extern proto fetcher*/ -typedef struct -{ - MFURL *url; - GF_MediaObject *mo; -} GF_ProtoLink; - -/*returns true if the given node DEF name is the url target view (eg blabla#myview)*/ -Bool gf_inline_is_default_viewpoint(GF_Node *node); - -GF_SceneGraph *gf_inline_get_proto_lib(void *_is, MFURL *lib_url); -Bool gf_inline_is_protolib_object(GF_Scene *scene, GF_ObjectManager *odm); - -/*restarts inline scene - care has to be taken not to remove the scene while it is traversed*/ -void gf_inline_restart(GF_Scene *scene); - -#endif - -/*compares object URL with another URL - ONLY USE THIS WITH DYNAMIC ODs*/ -Bool gf_mo_is_same_url(GF_MediaObject *obj, MFURL *an_url, Bool *keep_fragment, u32 obj_hint_type); - -void gf_mo_update_caps(GF_MediaObject *mo); - - -const char *gf_scene_get_fragment_uri(GF_Node *node); -void gf_scene_set_fragment_uri(GF_Node *node, const char *uri); - -enum -{ - /*threading up to decoder*/ - GF_TERM_THREAD_FREE, - /*single thread for all decoders*/ - GF_TERM_THREAD_SINGLE, - /*all media (image, video, audio) decoders are threaded*/ - GF_TERM_THREAD_MULTI, -}; - -enum -{ - GF_TERM_RUNNING = 1<<20, - GF_TERM_DEAD = 1<<21, - GF_TERM_SINGLE_THREAD = 1<<22, - GF_TERM_MULTI_THREAD = 1<<23, - GF_TERM_DROP_LATE_FRAMES = 1<<24, - GF_TERM_SINGLE_CLOCK = 1<<25 -}; - -/*URI relocators are used for containers like zip or ISO FF with file items. The relocator -is in charge of translating the URI, potentially extracting the associated resource and sending -back the new (local or not) URI. Only the interface is defined, URI translators are free to derive from them - -relocate a URI - if NULL is returned, this relocator is not concerned with the URI -otherwise returns the translated URI -*/ - -#define GF_TERM_URI_RELOCATOR \ - Bool (*relocate_uri)(void *__self, const char *parent_uri, const char *uri, char *out_relocated_uri, char *out_localized_uri); \ - -typedef struct __gf_uri_relocator GF_URIRelocator; - -struct __gf_uri_relocator -{ - GF_TERM_URI_RELOCATOR -}; - -typedef struct -{ - GF_TERM_URI_RELOCATOR - GF_Terminal *term; - char *szAbsRelocatedPath; -} GF_TermLocales; - -#define MAX_SHORTCUTS 200 - -typedef struct -{ - u8 code; - u8 mods; - u8 action; -} GF_Shortcut; - - -struct _tag_terminal -{ - u32 flags; - - /*callback to user application*/ - GF_User *user; - /*scene compositor*/ - struct __tag_compositor *compositor; - /*file downloader*/ - GF_DownloadManager *downloader; - /*top level scene*/ - GF_Scene *root_scene; - - /*Media manager*/ - GF_List *codecs; - /*mutex for decoder access*/ - GF_Mutex *mm_mx; - /*decoding thread*/ - GF_Thread *mm_thread; - /*last codec used in mm loop*/ - u32 last_codec; - /*thread priority*/ - s32 priority; - u32 cumulated_priority; - /*frame duration*/ - u32 frame_duration; - - /*net services*/ - GF_List *net_services; - /*net services to be destroyed*/ - GF_List *net_services_to_remove; - /*connection tasks pending*/ - GF_List *connection_tasks; - /*channels waiting for service CONNECT ack to be setup*/ - GF_List *channels_pending; - /*media objects pending for stop/play*/ - GF_List *media_queue; - /*media_queue lock*/ - GF_Mutex *media_queue_mx; - /*network lock*/ - GF_Mutex *net_mx; - /*all X3D key/mouse/string sensors*/ - GF_List *x3d_sensors; - /*all input stream decoders*/ - GF_List *input_streams; - - /*options (cf config doc)*/ - Bool enable_cache; - /*data timeout for network buffering in ms - if no data is received within this timeout - the initial buffering aborts. */ - u32 net_data_timeout; - - u32 play_state; - - u32 reload_state; - char *reload_url; - - /*special list used by nodes needing a call to RenderNode but not in the traverese scene graph (VRML/MPEG-4 protos only). - For these nodes, the traverse effect passed will be NULL. This is only used by InputSensor node at the moment*/ - GF_List *nodes_pending; - - /*root node of the user prefs*/ - GF_SceneGraph *dcci_doc; - - GF_List *extensions; /*list of all extensions*/ - GF_List *unthreaded_extensions; /*list of extensions to call at each frame*/ - GF_List *event_filters; /*list of extensions filtering events*/ - GF_Mutex *evt_mx; - u32 in_event_filter; - - /*ID of the thread currently in handle_services routine, 0 if none*/ - u32 thread_id_handling_services; - - /*static URI relocator for locales*/ - GF_TermLocales locales; - - GF_List *uri_relocators; /*list of GF_URIRelocator*/ - - GF_Shortcut shortcuts[MAX_SHORTCUTS]; - Fixed speed_ratio; - - /*bench mode type: 0-none 1: regular 2- systems layers only: all decoders inputs are discarded*/ - u32 bench_mode; - - u32 prefered_audio_codec_oti; - - u32 low_latency_buffer_max; - - u32 nb_calls_in_event_proc; - u32 disconnect_request_status; - - Bool orientation_sensors_active; - //set when compositor uses step mode, in order to drop frames even when the clock is paused - Bool use_step_mode; -}; - - - -GF_Err gf_term_init_scheduler(GF_Terminal *term, u32 threading_mode); -void gf_term_stop_scheduler(GF_Terminal *term); -void gf_term_add_codec(GF_Terminal *term, GF_Codec *codec); -void gf_term_remove_codec(GF_Terminal *term, GF_Codec *codec); -void gf_term_start_codec(GF_Codec *codec, Bool is_resume); -/*stops codec, reason if - -0: regular stop - -1: pause - -2: stop codec because end of stream : doesn't reset composition memory - */ -void gf_term_stop_codec(GF_Codec *codec, u32 reason); -void gf_term_set_threading(GF_Terminal *term, u32 mode); -void gf_term_set_priority(GF_Terminal *term, s32 Priority); - - -Bool gf_term_forward_event(GF_Terminal *term, GF_Event *evt, Bool consumed, Bool forward_only); - -/*error report function*/ -void gf_term_message(GF_Terminal *app, const char *service, const char *message, GF_Err error); -/*posts a request to connect a given object*/ -void gf_term_post_connect_object(GF_Terminal *term, GF_ObjectManager *odm, char *serviceURL, char *parent_url); -/*creates service for given channel / URL*/ -GF_Err gf_term_connect_remote_channel(GF_Terminal *app, GF_Channel *ch, char *URL); - -/*called by media manager to perform service maintenance: -servive shutdown: this is needed because service handler may be asynchronous -object Play: this is needed to properly handle multiplexed sources (all channels must be connected before play) -service restart -*/ -void gf_term_handle_services(GF_Terminal *app); -/*close service and queue for delete*/ -void gf_term_close_service(GF_Terminal *app, GF_ClientService *service); - -/*locks media quaue*/ -void gf_term_lock_media_queue(GF_Terminal *app, Bool LockIt); -/*locks net manager*/ -void gf_term_lock_net(GF_Terminal *app, Bool LockIt); - -/*checks no connection on the ODM are pending - of so, remove them*/ -void gf_term_check_connections_for_delete(GF_Terminal *term, GF_ObjectManager *odm); - -/*locks scene compositor*/ -void gf_term_lock_compositor(GF_Terminal *app, Bool LockIt); -/*get scene compositor time - FIXME this is not flexible enough for SMIL/Multiple time containers*/ -u32 gf_term_get_time(GF_Terminal *term); -/*forces scene composition*/ -void gf_term_invalidate_compositor(GF_Terminal *term); - -/*callbacks for scene graph library so that all related ESM nodes are properly instanciated*/ -void gf_term_node_callback(void *_is, u32 type, GF_Node *node, void *param); - -/*add/rem node requiring a call to render without being present in traversed graph (VRML/MPEG-4 protos). -For these nodes, the traverse effect passed will be NULL.*/ -void gf_term_queue_node_traverse(GF_Terminal *term, GF_Node *node); -void gf_term_unqueue_node_traverse(GF_Terminal *term, GF_Node *node); - -Bool gf_term_lock_codec(GF_Codec *codec, Bool lock, Bool try_lock); - -typedef struct -{ - void *udta; - /*called when an event should be filtered - */ - Bool (*on_event)(void *udta, GF_Event *evt, Bool consumed_by_compositor); -} GF_TermEventFilter; - -GF_Err gf_term_add_event_filter(GF_Terminal *terminal, GF_TermEventFilter *ef); -GF_Err gf_term_remove_event_filter(GF_Terminal *terminal, GF_TermEventFilter *ef); - - -/*clock*/ -struct _object_clock -{ - u16 clockID; - GF_Terminal *term; - GF_Mutex *mx; - /*no_time_ctrl : set if ANY stream running on this clock has no time control capabilities - this avoids applying - mediaControl and others that would break stream dependencies*/ - Bool use_ocr, clock_init, has_seen_eos, no_time_ctrl; - u32 init_time, StartTime, PauseTime, Paused; - /*the number of streams buffering on this clock*/ - u32 Buffering; - /*associated media control if any*/ - struct _media_control *mc; - /*for MC only (no FlexTime)*/ - Fixed speed; - u32 discontinuity_time; - s32 drift; - u32 data_timeout; - Bool probe_ocr; - Bool broken_pcr; - u32 last_TS_rendered; - u32 service_id; - - //media time in ms corresponding to the init tmiestamp of the clock - u32 media_time_at_init; - Bool has_media_time_shift; - - u16 ocr_on_esid; - - u64 ts_shift; -}; - -/*destroys clock*/ -void gf_clock_del(GF_Clock *ck); -/*finds a clock by ID or by ES_ID*/ -GF_Clock *gf_clock_find(GF_List *Clocks, u16 clockID, u16 ES_ID); -/*attach clock returns a new clock or the clock this stream (ES_ID) depends on (OCR_ES_ID) -hasOCR indicates whether the stream being attached carries object clock references -@clocks: list of clocks in ES namespace (service) -@is: inline scene to solve clock dependencies -*/ -GF_Clock *gf_clock_attach(GF_List *clocks, GF_Scene *scene, u16 OCR_ES_ID, u16 ES_ID, s32 hasOCR); -/*reset clock (only called by channel owning clock)*/ -void gf_clock_reset(GF_Clock *ck); -/*stops clock (only called for scene clock)*/ -void gf_clock_stop(GF_Clock *ck); -/*return clock time in ms*/ -u32 gf_clock_time(GF_Clock *ck); -/*return media time in ms*/ -u32 gf_clock_media_time(GF_Clock *ck); - -/*return time in ms since clock started - may be different from clock time when seeking or live*/ -u32 gf_clock_elapsed_time(GF_Clock *ck); - -/*sets clock time - FIXME: drift updates for OCRs*/ -void gf_clock_set_time(GF_Clock *ck, u32 TS); -/*return clock time in ms without drift adjustment - used by audio objects only*/ -u32 gf_clock_real_time(GF_Clock *ck); -/*pause the clock*/ -void gf_clock_pause(GF_Clock *ck); -/*resume the clock*/ -void gf_clock_resume(GF_Clock *ck); -/*returns true if clock started*/ -Bool gf_clock_is_started(GF_Clock *ck); -/*toggles buffering on (clock is paused at the first stream buffering) */ -void gf_clock_buffer_on(GF_Clock *ck); -/*toggles buffering off (clock is paused at the last stream restarting) */ -void gf_clock_buffer_off(GF_Clock *ck); -/*set clock speed scaling factor*/ -void gf_clock_set_speed(GF_Clock *ck, Fixed speed); -/*set clock drift - used to resync audio*/ -void gf_clock_adjust_drift(GF_Clock *ck, s32 ms_drift); - -enum -{ - /*channel is setup and waits for connection request*/ - GF_ESM_ES_SETUP = 0, - /*waiting for server reply*/ - GF_ESM_ES_WAIT_FOR_ACK, - /*connection OK*/ - GF_ESM_ES_CONNECTED, - /*data exchange on this service/channel*/ - GF_ESM_ES_RUNNING, - /*deconnection OK - a download channel can automatically disconnect when download is done*/ - GF_ESM_ES_DISCONNECTED, - /*service/channel is not (no longer) available/found and should be removed*/ - GF_ESM_ES_UNAVAILABLE -}; - -enum -{ - GF_ESM_CAROUSEL_NONE = 0, - GF_ESM_CAROUSEL_MPEG4, - GF_ESM_CAROUSEL_MPEG2, -}; - -/*data channel (elementary stream)*/ -struct _es_channel -{ - /*service this channel belongs to*/ - GF_ClientService *service; - /*stream descriptor*/ - GF_ESD *esd; - /*parent OD for this stream*/ - struct _od_manager *odm; - u32 es_state; - Bool is_pulling; - u32 media_padding_bytes; - /*IO mutex*/ - GF_Mutex *mx; - u32 AU_Count; - /*decoding buffers for push mode*/ - struct _decoding_buffer * AU_buffer_first, * AU_buffer_last; - /*static decoding buffer for pull mode*/ - struct _decoding_buffer * AU_buffer_pull; - char *pull_reaggregated_buffer; - /*channel buffer flag*/ - Bool BufferOn; - /*min level to trigger buffering on, max to trigger it off for playback resume, and max amount of prefetch buffer*/ - u32 MinBuffer, MaxBuffer, MaxBufferOccupancy; - /*amount of buffered media - this is the DTS of the last received AU minus the onject clock time, to make sure - we always have MaxBuffer ms ready for composition when resuming the clock*/ - s32 BufferTime; - /*last received AU time - if exceeding a certain time and buffering is on, buffering is turned off. - This is needed for streams with very short duration (less than buffer time) and stream with only one AU (BIFS/OD)*/ - u32 last_au_time; - /*Current reassembling buffer - currently packets are NOT reordered, only AUs are*/ - char *buffer; - u32 len, allocSize; - /*only for last packet of an AU*/ - u8 padingBits; - Bool IsEndOfStream; - /* SL reassembler */ - /*current AU TSs*/ - u32 DTS, CTS; - /*special case for carousels in the past*/ - u32 CTS_past_offset; - /*AU and Packet seq num info*/ - u32 au_sn, pck_sn; - u32 max_au_sn, max_pck_sn; - /*the AU length indicated in the SL Header. */ - u32 AULength; - /*state indicator: 0 OK, 1: not tuned in, 2: has error and needs RAP*/ - u32 stream_state; - /*the AU in reception is RAP*/ - Bool IsRap; - /*the AU in reception is only need for seeking*/ - Bool SeekFlag; - /*signal that next AU is an AU start*/ - Bool NextIsAUStart; - /*if codec resilient, packet drops are not considered as fatal for AU reconstruction (eg no wait for RAP)*/ - u32 codec_resilient; - /*when starting a channel, the first AU is ALWAYS fetched when buffering - this forces - BIFS and OD to be decoded and first frame render, in order to detect media objects that would also need - buffering - note this doesn't affect the clock, it is still paused if buffering*/ - Bool first_au_fetched; - - /* used in Carousel, to skip packets until the end of AU */ - u8 carousel_type; - Bool skip_carousel_au; - - /*discard clock initialization when dispatching pending AU - this is used when TS discontinuities / MPA_TIME happen*/ - Bool skip_time_check_for_pending; - - /* TimeStamp to Media Time mapping*/ - /*TS (in TSResolution) corresponding to the SeedTime of the decoder. Delivered by net, otherwise 0*/ - u64 seed_ts; - /*media time offset corresponding to SeedTS. This is needed when the channel doesn't own the clock*/ - u32 ts_offset; - /*scaling factors to remap to timestamps in milliseconds*/ - u64 ts_res; - Double ocr_scale; - /*clock driving this stream - currently only CTS is supported (no OCR)*/ - struct _object_clock *clock; - /*flag for clock init*/ - Bool IsClockInit; - /*flag for clock init*/ - Bool clock_inherited; - /*indicates that no DTS is signaled and that they should be recomputed if needed (video only)*/ - Bool recompute_dts; - u32 min_ts_inc, min_computed_cts; - - /*duration of last received AU if any, 0 if not known (most of the time)*/ - u32 au_duration; - /*A channel with this flag set considers each incoming packet as a complete AU and assigns timestamps - upon reception matching the reception time, then dispatching it into the decoding buffer (only tested - with audi video). This flag is turned on by setting esd->slconfig->predefined to 'SLPredef_SkipSL' */ - Bool skip_sl; - - /*indicates that decoding can be called directly when receiving a complete AU on this channel - This is used by systems streams in non-seekable (eg broadcast/multicast, MPEG-2 TS multiplexes) to - make sure resources are setup as fast as possible. If the AU is too early, it will be kept in the - decoding buffer*/ - Bool dispatch_after_db; - - /*indicates that decoding is called directly when receiving a packet on this channel - This is used to bypass SL defragmenting and decoding buffer for EIT internal streams*/ - Bool bypass_sl_and_db; - - GF_IPMPTool *ipmp_tool; - Bool is_protected; - - /*indicates that AU received will be copied to the composition memory*/ - u32 is_raw_channel; - - u32 resync_drift; - s32 prev_pcr_diff; - u64 last_pcr; - - /*TSs as received from network - these are used for cache storage*/ - u64 net_dts, net_cts, sender_ntp; - - Bool last_au_was_seek; - Bool no_timestamps; - - Bool pull_forced_buffer; - - u64 ts_shift; -}; - -/*creates a new channel for this stream*/ -GF_Channel *gf_es_new(GF_ESD *esd); -/*destroys channel*/ -void gf_es_del(GF_Channel *ch); -/*(un)locks channel*/ -void gf_es_lock(GF_Channel *ch, u32 LockIt); -/*setup channel for reception of data*/ -GF_Err gf_es_start(GF_Channel *ch); -/*stop channel from receiving data*/ -GF_Err gf_es_stop(GF_Channel *ch); -/*handles reception of an SL PDU*/ -void gf_es_receive_sl_packet(GF_ClientService *serv, GF_Channel *ch, char *StreamBuf, u32 StreamLength, GF_SLHeader *header, GF_Err reception_status); -/*signals end of stream on the channel*/ -void gf_es_on_eos(GF_Channel *ch); -/*fetches first AU available for decoding on this channel*/ -struct _decoding_buffer *gf_es_get_au(GF_Channel *ch); -/*drops first AU on this channel*/ -void gf_es_drop_au(GF_Channel *ch); -/*performs final setup upon connection confirm*/ -void gf_es_on_connect(GF_Channel *ch); -/*reconfigure SL for this channel*/ -void gf_es_reconfig_sl(GF_Channel *ch, GF_SLConfig *slc, Bool use_m2ts_sections); -/*hack for streaming: whenever a time map (media time <-> TS time) event is received on the channel reset decoding buffer -this is needed because all server tested resend packets on already running channel*/ -void gf_es_reset_buffers(GF_Channel *ch); -/*dummy channels are used by scene decoders which don't use ESM but load directly the scene graph themselves -these channels are ALWAYS pulling ones, and this function will init the channel clock if needed*/ -void gf_es_init_dummy(GF_Channel *ch); -/*setup DRM info*/ -void gf_es_config_drm(GF_Channel *ch, GF_NetComDRMConfig *isma_cryp); -/*dispatch raw media AU to the composition buffer and BLOCKS until the AU is consumed by the decoder*/ -void gf_es_dispatch_raw_media_au(GF_Channel *ch, char *payload, u32 payload_size, u32 cts); -/*returns true if this stream owns its clock, false if it simply refers to it*/ -Bool gf_es_owns_clock(GF_Channel *ch); -/*reset all timestamps in decoder buffer to 0*/ -void gf_es_reset_timing(GF_Channel *ch, Bool reset_buffer); -/*reset all timestamps in CB to 0*/ -void gf_cm_reset_timing(GF_CompositionMemory *cb); -/*reset timing of all objects associated with this clock*/ -void gf_clock_discontinuity(GF_Clock *ck, GF_Scene *scene, Bool is_pcr_discontinuity); -//turns on buffering on the stream -void gf_es_buffer_on(GF_Channel *ch); -//turns off buffering on the stream -void gf_es_buffer_off(GF_Channel *ch); -//updates buffer info on the stream -void gf_es_update_buffering(GF_Channel *ch, Bool update_info); - -/* - decoder stuff -*/ -enum -{ - /*stop: the decoder is not playing*/ - GF_ESM_CODEC_STOP = 0, - /*stop: the decoder is playing*/ - GF_ESM_CODEC_PLAY = 1, - /*End Of Stream: when the base layer signals it's done, this triggers media-specific - handling of the CB. - For video, the output is kept alive, For audio, the output is reseted (don't want audio loop ;)*/ - GF_ESM_CODEC_EOS = 2, - /*pause: the decoder is stopped but the CB is kept intact - The decoder will be in PAUSE mode until its CB is full, and it will then move to STOP mode. - If no CB, this is equivalent to STOP mode*/ - GF_ESM_CODEC_PAUSE = 3, - /*Buffer: transition state: the decoder runs (fetch data/decode) but the clock - is not running (no composition). This is used for rebuffering channels (rtp...)*/ - GF_ESM_CODEC_BUFFER = 4 -}; - -enum -{ - GF_ESM_CODEC_HAS_UPSTREAM = 1, - /*the codec uses the interface from another codec (only used by private scene streams to handle - any intern sprite/animation streams)*/ - GF_ESM_CODEC_IS_USE = 1<<1, - /*set for OD codec when static (ressources are declared in OD stream esd a la ISMA*/ - GF_ESM_CODEC_IS_STATIC_OD = 1<<2, - /*set when codec is identified as RAW, meaning all AU comming from the network are directly - dispatched to the composition memory*/ - GF_ESM_CODEC_IS_RAW_MEDIA = 1<<3, - - /*set when input channels have very low buffering requirement, in which case the codec has to discard all possible late data*/ - GF_ESM_CODEC_IS_LOW_LATENCY = 1<<4, -}; - -struct _generic_codec -{ - /*codec type (streamType and OTI from base layer)*/ - u32 type, oti; - u32 flags; - /*decoder module interface */ - GF_BaseDecoder *decio; - - /*base process routine*/ - GF_Err (*process)(GF_Codec *codec, u32 TimeAvailable); - - GF_List *blacklisted; - - /*composition memory for media streams*/ - struct _composition_memory *CB; - /*input media channles*/ - GF_List *inChannels; - /*a pointer to the OD that owns the decoder.*/ - struct _od_manager *odm; - u32 Status; - Bool Muted; - struct _object_clock *ck; - /*priority of this media object. This is ALWAYS the base layer priority - PriorityBoost is set when the CB is under critical limit (for now only audio uses the feature) - and results in a bigger time slice for the codec. Only on/off value for now*/ - u32 Priority, PriorityBoost; - /*last processed DTS - sanity check for scalability*/ - u32 last_unit_dts; - /*last processed CTS on base layer - seeking detection*/ - u32 last_unit_cts; - /*minimum non-0 CTS(k) - CTS(j) used to reconstruct CTS timing when codec handles reordering internally...*/ - u32 min_au_duration; - /*recomputed CTS for next dispatch*/ - u32 recomputed_cts; - Bool first_frame_dispatched, first_frame_processed; - /*SHA signature for the last processed unit. Only for images (Capacity==1)*/ - u8 last_unit_signature[20]; - /*in case the codec performs temporal re-ordering itself*/ - Bool is_reordering; - /*codec will properly handle CTS adjustments*/ - Bool trusted_cts; - u32 prev_au_size; - u32 bytes_per_sec; - Double fps; - u32 nb_dispatch_skipped; - Bool direct_vout, direct_frame_output; - - /*statistics*/ - u32 last_stat_start, cur_bit_size, stat_start; - u32 avg_bit_rate, max_bit_rate; - u32 nb_dec_frames, nb_iframes; - //decode times in us - u64 total_dec_time, total_iframes_time; - u32 max_dec_time, max_iframes_time; - u32 first_frame_time, last_frame_time; - Bool codec_reset; - /*number of frames dropped at the presentation*/ - u32 nb_dropped; - /*we detect if the same image is sent again and again to the decoder (using last_unit_signature)*/ - u32 nb_repeted_frames; - /*min frame duration based on DTS diff*/ - u32 min_frame_dur; - /*speed at which the avg_dec_time was computed*/ - Fixed check_speed; - //when set, only I-frames are decoded, one out of drop_modulo if not 0. - //If set to 2, all frames are discarded - u32 decode_only_rap; - u32 drop_modulo, drop_count; - u32 consecutive_late_frames, consecutive_ontime_frames; - - /*for CTS reconstruction (channels not using SL): we cannot just update timing at each frame, not precise enough - since we use ms and not microsec TSs*/ - u32 cur_audio_bytes, cur_video_frames; - - /*signals that CB should be resized to this value once all units in CB has been consumed (codec config change)*/ - u32 force_cb_resize; - u32 profile_level; - Bool hybrid_layered_coded; -}; - -GF_Codec *gf_codec_new(GF_ObjectManager *odm, GF_ESD *base_layer, s32 PL, GF_Err *e); -void gf_codec_del(GF_Codec *codec); -GF_Err gf_codec_add_channel(GF_Codec *codec, GF_Channel *ch); -/*returns TRUE if stream was present, false otherwise*/ -Bool gf_codec_remove_channel(GF_Codec *codec, GF_Channel *ch); -GF_Err gf_codec_process(GF_Codec *codec, u32 TimeAvailable); -GF_Err gf_codec_get_capability(GF_Codec *codec, GF_CodecCapability *cap); -GF_Err gf_codec_set_capability(GF_Codec *codec, GF_CodecCapability cap); -void gf_codec_set_status(GF_Codec *codec, u32 Status); -/*returns a new codec using an existing loaded decoder - only used by private scene to handle != timelines, for -instance when loading a BT with an animation stream*/ -GF_Codec *gf_codec_use_codec(GF_Codec *codec, GF_ObjectManager *odm); - -GF_Err gf_codec_resize_composition_buffer(GF_Codec *dec, u32 NewSize); -GF_Err gf_codec_change_decoder(GF_Codec *codec); - -/*OD manager*/ - -enum -{ - /*flag set if object cannot be time-controloed*/ - GF_ODM_NO_TIME_CTRL = (1<<1), - /*flag set if subscene uses parent scene timeline*/ - GF_ODM_INHERIT_TIMELINE = (1<<2), - /*flag set if object has been redirected*/ - GF_ODM_REMOTE_OD = (1<<3), - /*flag set if object has profile indications*/ - GF_ODM_HAS_PROFILES = (1<<4), - /*flag set if object governs profile of inline subscenes*/ - GF_ODM_INLINE_PROFILES = (1<<5), - /*flag set if object declared by network service, not from OD stream*/ - GF_ODM_NOT_IN_OD_STREAM = (1<<6), - /*flag set if object is an entry point of the network service*/ - GF_ODM_SERVICE_ENTRY = (1<<7), - - /*flag set if object has been started before any start request from the scene*/ - GF_ODM_PREFETCH = (1<<8), - - /*flag set if object has been deleted*/ - GF_ODM_DESTROYED = (1<<9), - /*dynamic flags*/ - - /*flag set if associated subscene must be regenerated*/ - GF_ODM_REGENERATE_SCENE = (1<<10), - - /*flag set for first play request*/ - GF_ODM_INITIAL_BROADCAST_PLAY = (1<<11), - - /*flag set until ODM is setup*/ - GF_ODM_NOT_SETUP = (1<<12), - - /*flag set when ODM is setup*/ - GF_ODM_PAUSED = (1<<13), - - /*flag set when ODM is setup*/ - GF_ODM_PAUSE_QUEUED = (1<<14), -}; - -enum -{ - GF_ODM_STATE_STOP, - GF_ODM_STATE_PLAY, - GF_ODM_STATE_IN_SETUP, - GF_ODM_STATE_BLOCKED, -}; - -enum -{ - GF_ODM_ACTION_PLAY, - GF_ODM_ACTION_STOP, - GF_ODM_ACTION_DELETE, - GF_ODM_ACTION_SCENE_DISCONNECT, - GF_ODM_ACTION_SCENE_RECONNECT, - GF_ODM_ACTION_SCENE_INLINE_RESTART, -}; - -struct _od_manager -{ - /*pointer to terminal*/ - struct _tag_terminal *term; - /*the service used by this ODM. If the service private data is this ODM, then the service was created for this ODM*/ - GF_ClientService *net_service; - /*parent scene or NULL for root scene*/ - GF_Scene *parentscene; - /*channels associated with this object (media channels, OCR, IPMP, OCI, etc)*/ - GF_List *channels; - /*sub scene for inline or NULL */ - GF_Scene *subscene; - /*object codec (media or BIFS for AnimationStream) attached if any*/ - struct _generic_codec *codec; -#ifndef GPAC_MINIMAL_ODF - /*OCI codec attached if any*/ - struct _generic_codec *oci_codec; -#endif - /*OCR codec attached if any*/ - struct _generic_codec *ocr_codec; - - /*MPEG-4 object descriptor*/ - GF_ObjectDescriptor *OD; - - /*exclusive access is required since rendering and media management don't always take place in the same thread*/ - GF_Mutex *mx; - - u32 flags; - - /*PLs*/ - u8 Audio_PL, Graphics_PL, OD_PL, Scene_PL, Visual_PL; - - /*interface with scene rendering*/ - struct _mediaobj *mo; - - /*number of channels with connection not yet acknowledge*/ - u32 pending_channels; - u32 state; - /* during playback: timing as evaluated by the composition memory or the scene codec - this is the timestamp + media time at clock init*/ - u32 media_current_time; - /*full object duration 0 if unknown*/ - u64 duration; - /* - upon start: media start time as requested by scene compositor (eg not media control) - set to -1 upon stop to postpone stop request - */ - u64 media_start_time; - s64 media_stop_time; - - /*full object timeshift depth in ms, 0 if no timeshift, (u32) -1 is infinity */ - u32 timeshift_depth; - - u32 action_type; - - Fixed set_speed; - -// u32 raw_media_frame_pending; - GF_Semaphore *raw_frame_sema; - -#ifndef GPAC_DISABLE_VRML - /*the one and only media control currently attached to this object*/ - struct _media_control *media_ctrl; - /*the list of media control controling the object*/ - GF_List *mc_stack; - /*the media sensor(s) attached to this object*/ - GF_List *ms_stack; -#endif - - //only set on root OD of addon subscene, which gather all the hybrid resources - GF_AddonMedia *addon; - //set to true if this is a scalable addon for an existing object - Bool scalable_addon; - - //for a regular ODM, this indicates that the current scalable_odm associated - struct _od_manager *upper_layer_odm; - //for a scalable ODM, this indicates the lower layer odm associated - struct _od_manager *lower_layer_odm; -}; - -GF_ObjectManager *gf_odm_new(); -void gf_odm_del(GF_ObjectManager *ODMan); -void gf_odm_lock(GF_ObjectManager *odm, u32 LockIt); - -/*setup service entry point*/ -void gf_odm_setup_entry_point(GF_ObjectManager *odm, const char *sub_url); -/*setup OD*/ -void gf_odm_setup_object(GF_ObjectManager *odm, GF_ClientService *parent_serv); -/*disctonnect OD and removes it if desired (otherwise only STOP is propagated)*/ -void gf_odm_disconnect(GF_ObjectManager *odman, u32 do_remove); -/*setup an ESD*/ -GF_Err gf_odm_setup_es(GF_ObjectManager *odm, GF_ESD *esd, GF_ClientService *service, GF_MediaObject *sync_ref); -/*removes an ESD (this destroys associated channel if any)*/ -void gf_odm_remove_es(GF_ObjectManager *odm, u16 ES_ID); -/*set stream duration - updates object duration accordingly*/ -void gf_odm_set_duration(GF_ObjectManager *odm, GF_Channel *, u64 stream_duration); - -/*set time shift buffer duration */ -void gf_odm_set_timeshift_depth(GF_ObjectManager *odm, GF_Channel *, u32 time_shift_ms); - -/*signals end of stream on channels*/ -void gf_odm_on_eos(GF_ObjectManager *odm, GF_Channel *); -/*start Object streams and queue object for network PLAY -media_queue_state: 0: object was not in media queue and must be queued - 1: object is already in media queue - 2: object shall not be queued bu started directly -*/ -void gf_odm_start(GF_ObjectManager *odm, u32 media_queue_state); -/*stop OD streams*/ -void gf_odm_stop(GF_ObjectManager *odm, Bool force_close); -/*send PLAY request to network - needed to properly handle multiplexed inputs -ONLY called by service handler (media manager thread)*/ -void gf_odm_play(GF_ObjectManager *odm); - -/*pause object (mediaControl use only)*/ -void gf_odm_pause(GF_ObjectManager *odm); -/*resume object (mediaControl use only)*/ -void gf_odm_resume(GF_ObjectManager *odm); -/*set object speed*/ -void gf_odm_set_speed(GF_ObjectManager *odm, Fixed speed, Bool adjust_clock_speed); -/*returns the clock of the media stream (video, audio or bifs), NULL otherwise */ -struct _object_clock *gf_odm_get_media_clock(GF_ObjectManager *odm); -/*adds segment descriptors targeted by the URL to the list and sort them - the input list must be empty*/ -void gf_odm_init_segments(GF_ObjectManager *odm, GF_List *list, MFURL *url); -/*returns true if this OD depends on the given clock*/ -Bool gf_odm_shares_clock(GF_ObjectManager *odm, struct _object_clock *ock); - -GF_Segment *gf_odm_find_segment(GF_ObjectManager *odm, char *descName); -/*locks ODM with destruction check - returns 0 if object manager is not attached to object*/ -Bool gf_odm_lock_mo(struct _mediaobj *mo); - -void gf_odm_signal_eos(GF_ObjectManager *odm); - -void gf_odm_reset_media_control(GF_ObjectManager *odm, Bool signal_reset); - -/*GF_MediaObject: link between real object manager and scene. although there is a one-to-one mapping between a -MediaObject and an ObjectManager, we have to keep them separated in order to handle OD remove commands which destroy -ObjectManagers. */ -struct _mediaobj -{ - /*type is as defined in constants.h # GF_MEDIA_OBJECT_* */ - u32 type; - /*one of the above flags*/ - u32 flags; - - - /* private to ESM*/ - - /*media object manager - private to the sync engine*/ - struct _od_manager *odm; - /*OD ID of the object*/ - u32 OD_ID; - /*OD URL for object not using MPEG4 OD urls*/ - MFURL URLs; - /*session join*/ - u32 num_open; - /*shared object restart handling*/ - u32 num_to_restart, num_restart; - Fixed speed; - - /*shared object info: if 0 a new frame will be checked, otherwise current is returned*/ - u32 nb_fetch; - /*frame presentation time*/ - u32 timestamp; - /*time in ms until next frame shall be presented*/ - s32 ms_until_next; - s32 ms_until_pres; - /*data frame size*/ - u32 framesize; - /*pointer to data frame */ - char *frame; - /* Objects implementing the DOM Event Target interface - used to dispatch HTML5 Media and Media Source Events */ - GF_List *evt_targets; - /*pointer to the node responsible for the creation of this media object - ONLY used for scene media type (animationStreams) - Reset upon creation of the decoder. - */ - void *node_ptr; - - - /*currently valid properties of the object*/ - u32 width, height, stride, pixel_ar, pixelformat; - Bool is_flipped; - u32 sample_rate, num_channels, bits_per_sample, channel_config; - u32 srd_x, srd_y, srd_w, srd_h, srd_full_w, srd_full_h; - - u32 quality_degradation_hint; - u32 nb_views; - u32 nb_layers; - u32 view_min_x, view_max_x, view_min_y, view_max_y; - GF_MediaDecoderFrame *media_frame; -}; - -GF_MediaObject *gf_mo_new(); - - -/*used for delayed channel setup*/ -typedef struct -{ - struct _generic_codec *dec; - struct _es_channel *ch; -} GF_ChannelSetup; - -/*post-poned channel connect*/ -GF_Err gf_odm_post_es_setup(struct _es_channel *ch, struct _generic_codec *dec, GF_Err err); - -/* - special entry point: specify directly a service interface for service input -*/ -void gf_term_attach_service(GF_Terminal *term, GF_InputService *service_hdl); - - -/*media access events */ -void gf_term_service_media_event(GF_ObjectManager *odm, GF_EventType event_type); -void gf_term_service_media_event_with_download(GF_ObjectManager *odm, GF_EventType event_type, u64 loaded_size, u64 total_size, u32 bytes_per_sec); - -/*checks the URL and returns the ODID (MPEG-4 od://) or GF_MEDIA_EXTERNAL_ID for all regular URLs*/ -u32 gf_mo_get_od_id(MFURL *url); - -void gf_scene_generate_views(GF_Scene *scene, char *url, char *parent_url); -//sets pos and size of addon -// size is 1/2 height (0), 1/3 (1) or 1/4 (2) -// pos is bottom-left(0), top-left (1) bottom-right (2) or top-right (3) -void gf_scene_set_addon_layout_info(GF_Scene *scene, u32 position, u32 size_factor); - -void gf_scene_register_associated_media(GF_Scene *scene, GF_AssociatedContentLocation *addon_info); -void gf_scene_notify_associated_media_timeline(GF_Scene *scene, GF_AssociatedContentTiming *addon_time); -//returns media time in sec for the addon - timestamp_based is set to 1 if no timeline has been found (eg sync is based on direct timestamp comp) -Double gf_scene_adjust_time_for_addon(GF_AddonMedia *addon, Double clock_time, u32 *timestamp_based); -s64 gf_scene_adjust_timestamp_for_addon(GF_AddonMedia *addon, u64 orig_ts); -void gf_scene_select_scalable_addon(GF_Scene *scene, GF_ObjectManager *odm); -/*check if the associated addon has to be restarted, based on the timestamp of the main media (used for scalable addons only). Returns 1 if the addon has been restarted*/ -Bool gf_scene_check_addon_restart(GF_AddonMedia *addon, u64 cts, u64 dts); - - -//exported for gpac.js, resumes to main content -void gf_scene_resume_live(GF_Scene *subscene); - -enum -{ - //addon to overlay - GF_ADDON_TYPE_ADDITIONAL = 0, - //main content duplicated for PVR purposes - GF_ADDON_TYPE_MAIN, - //scalable decoding - reassembly before the decoder(s) - GF_ADDON_TYPE_SCALABLE, - //multiview reconstruction - reassembly after the decoder(s) - GF_ADDON_TYPE_MULTIVIEW, -}; - -struct _gf_addon_media -{ - char *url; - GF_ObjectManager *root_od; - s32 timeline_id; - u32 is_splicing; - //in scene time - Double activation_time; - - Bool enabled; - //object(s) have been started (PLAY command sent) - used to filter out AUs in scalabmle addons - Bool started; - Bool timeline_ready; - - u32 media_timescale; - u64 media_timestamp; - u64 media_pts; - - //in case we detect a loop, we store the value of the mediatime in the loop until we actually loop the content - u32 past_media_timescale; - u64 past_media_timestamp; - u64 past_media_pts, past_media_pts_scaled; - Bool loop_detected; - - u32 addon_type; -}; - -void gf_scene_toggle_addons(GF_Scene *scene, Bool show_addons); - - - -GF_Err gf_codec_process_private_media(GF_Codec *codec, u32 TimeAvailable); - - -Bool gf_codec_is_scene_or_image(GF_Codec *codec); - -void gf_scene_set_service_id(GF_Scene *scene, u32 service_id); - -#ifdef __cplusplus -} -#endif - - -#endif /*_GF_TERMINAL_DEV_H_*/ - - diff --git a/include/gpac/internal/vobsub.h b/include/gpac/internal/vobsub.h index 4c3be6e..3db65b8 100644 --- a/include/gpac/internal/vobsub.h +++ b/include/gpac/internal/vobsub.h @@ -47,6 +47,9 @@ typedef struct _tag_vobsub_lang u32 id; char *name; GF_List *subpos; + u32 idx, current; + Bool is_seek; + u64 last_dts; } vobsub_lang; typedef struct _tag_vobsub_file @@ -58,23 +61,12 @@ typedef struct _tag_vobsub_file vobsub_lang langs[32]; } vobsub_file; -GFINLINE static void vobsub_trim_ext(char *filename) -{ - char *pos = strrchr(filename, '.'); - - if (pos != NULL) { - if (!stricmp(pos, ".idx") || !stricmp(pos, ".sub")) { - *pos = '\0'; - } - } -} - s32 vobsub_lang_name(u16 id); char *vobsub_lang_id(char *name); GF_Err vobsub_read_idx(FILE *file, vobsub_file *vobsub, int *version); void vobsub_free(vobsub_file *vobsub); -GF_Err vobsub_get_subpic_duration(char *data, u32 psize, u32 dsize, u32 *duration); -GF_Err vobsub_packetize_subpicture(FILE *fsub, u64 pts, char *data, u32 dataSize); +GF_Err vobsub_get_subpic_duration(u8 *data, u32 psize, u32 dsize, u32 *duration); +GF_Err vobsub_packetize_subpicture(FILE *fsub, u64 pts, u8 *data, u32 dataSize); #ifdef __cplusplus } diff --git a/include/gpac/ismacryp.h b/include/gpac/ismacryp.h deleted file mode 100644 index 361c2d4..0000000 --- a/include/gpac/ismacryp.h +++ /dev/null @@ -1,189 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / Authoring Tools sub-project - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef _GF_ISMACRYP_H_ -#define _GF_ISMACRYP_H_ - - -#ifdef __cplusplus -extern "C" { -#endif - -/*! - * \file <gpac/ismacryp.h> - * \brief Utility tools for ISMA and Common Encryption. - */ - -/*! - * \addtogroup crypt_grp - * \ingroup media_grp - * \brief Utility tools for ISMA and Common Encryption. - * - *This section documents the encryption and decryption of ISOBMF files according to ISMA and CENC specifications. - * @{ - */ - -#include <gpac/isomedia.h> - -/*loads key and salt from a LOCAL gpac-DRM file (cf MP4Box doc)*/ -GF_Err gf_ismacryp_gpac_get_info(u32 stream_id, char *drm_file, char *key, char *salt); - -/*loads key and salt for MPEG4IP protected files*/ -Bool gf_ismacryp_mpeg4ip_get_info(char *kms_uri, char *key, char *salt); - - -enum -{ - /*ISMA E&A encryption*/ - GF_CRYPT_ISMA_CRYPT_TYPE = GF_4CC( 'i', 'A', 'E', 'C' ), - /*CENC CTR-128 encryption*/ - GF_CRYPT_CENC_CRYPT_TYPE = GF_4CC('c','e','n','c'), - /*CENC CBC-128 encryption*/ - GF_CRYPT_CBC1_CRYPT_TYPE = GF_4CC('c','b','c','1'), - /*Adobe CBC-128 encryption*/ - GF_CRYPT_ADOBE_CRYPT_TYPE = GF_4CC('a','d','k','m'), - /*CENC CTR-128 pattern encryption*/ - GF_CRYPT_CENS_CRYPT_TYPE = GF_4CC('c','e','n','s'), - /*CENC CBC-128 pattern encryption*/ - GF_CRYPT_CBCS_CRYPT_TYPE = GF_4CC('c','b','c','s'), -}; - -enum -{ - /*no selective encryption*/ - GF_CRYPT_SELENC_NONE = 0, - /*only encrypts RAP samples*/ - GF_CRYPT_SELENC_RAP = 1, - /*only encrypts non-RAP samples*/ - GF_CRYPT_SELENC_NON_RAP = 2, - /*selective encryption of random samples*/ - GF_CRYPT_SELENC_RAND = 3, - /*selective encryption of a random sample in given range*/ - GF_CRYPT_SELENC_RAND_RANGE = 4, - /*selective encryption of first sample in given range*/ - GF_CRYPT_SELENC_RANGE = 5, - /*encryption of all samples but the preview range*/ - GF_CRYPT_SELENC_PREVIEW = 6, - /*encryption of no samples*/ - GF_CRYPT_SELENC_CLEAR = 7, -}; - -typedef struct -{ - /*0: ISMACryp - 1: OMA DRM*/ - u32 enc_type;//for now only used for ISMACrypt - u32 trackID; - unsigned char key[16]; - unsigned char salt[16]; - - /*the rest is only used for encryption*/ - char KMS_URI[5000]; - char Scheme_URI[5000]; - /*selecive encryption type*/ - u32 sel_enc_type; - u32 sel_enc_range; - /*IPMP signaling: 0: none, 1: IPMP, 2: IPMPX - when IPMP signaling is enabled, the OD stream will be updated with - IPMP Update commands*/ - u32 ipmp_type; - /*if not set and IPMP enabled, defaults to TrackID*/ - u32 ipmp_desc_id; - /*type of box where sample auxiliary informations is saved, or 0 in case of ISMACrypt (it will be written in samples)*/ - u32 sai_saved_box_type; - - /*OMA extensions*/ - /*0: none, 1: AES CBC, 2: AES CTR*/ - u8 encryption; - char TextualHeaders[5000]; - u32 TextualHeadersLen; - char TransactionID[17]; - - /*CENC extensions*/ - u32 IsEncrypted; - u8 IV_size; - bin128 default_KID; - u32 KID_count; - bin128 *KIDs; - bin128 *keys; - /*IV of first sample in track*/ - unsigned char first_IV[16]; - u32 defaultKeyIdx; - u32 keyRoll; - u8 crypt_byte_block, skip_byte_block; - u8 constant_IV_size; - unsigned char constant_IV[16]; - //true if using AES-CTR mode, false if using AES-CBC mode - Bool ctr_mode; - u32 cenc_scheme_type; - - char metadata[5000]; - u32 metadata_len; - -} GF_TrackCryptInfo; - -#if !defined(GPAC_DISABLE_MCRYPT) && !defined(GPAC_DISABLE_ISOM_WRITE) - -/*encrypts track - logs, progress: info callbacks, NULL for default*/ -GF_Err gf_ismacryp_encrypt_track(GF_ISOFile *mp4, GF_TrackCryptInfo *tci, void (*progress)(void *cbk, u64 done, u64 total), void *cbk); - -/*decrypts track - logs, progress: info callbacks, NULL for default*/ -GF_Err gf_ismacryp_decrypt_track(GF_ISOFile *mp4, GF_TrackCryptInfo *tci, void (*progress)(void *cbk, u64 done, u64 total), void *cbk); - -/*Common Encryption*/ -/*AES-CTR*/ -GF_Err gf_cenc_encrypt_track(GF_ISOFile *mp4, GF_TrackCryptInfo *tci, void (*progress)(void *cbk, u64 done, u64 total), void *cbk); -GF_Err gf_cenc_decrypt_track(GF_ISOFile *mp4, GF_TrackCryptInfo *tci, void (*progress)(void *cbk, u64 done, u64 total), void *cbk); -/*AES-CBC*/ -GF_Err gf_cbc_encrypt_track(GF_ISOFile *mp4, GF_TrackCryptInfo *tci, void (*progress)(void *cbk, u64 done, u64 total), void *cbk); -GF_Err gf_cbc_decrypt_track(GF_ISOFile *mp4, GF_TrackCryptInfo *tci, void (*progress)(void *cbk, u64 done, u64 total), void *cbk); - -/*ADOBE*/ -GF_Err gf_adobe_encrypt_track(GF_ISOFile *mp4, GF_TrackCryptInfo *tci, void (*progress)(void *cbk, u64 done, u64 total), void *cbk); -GF_Err gf_adobe_decrypt_track(GF_ISOFile *mp4, GF_TrackCryptInfo *tci, void (*progress)(void *cbk, u64 done, u64 total), void *cbk); - - -/*decrypt a file -@drm_file: location of DRM data (cf MP4Box doc). -@LogMsg: redirection for message or NULL for default -*/ -GF_Err gf_decrypt_file(GF_ISOFile *mp4file, const char *drm_file); - -/*Crypt a the file -@drm_file: location of DRM data. -@LogMsg: redirection for message or NULL for default -*/ -GF_Err gf_crypt_file(GF_ISOFile *mp4file, const char *drm_file); - -#endif /*!defined(GPAC_DISABLE_MCRYPT) && !defined(GPAC_DISABLE_ISOM_WRITE)*/ - -/*! @} */ - -#ifdef __cplusplus -} -#endif - - -#endif /*_GF_ISMACRYP_H_*/ - diff --git a/include/gpac/iso639.h b/include/gpac/iso639.h index 3acc6f6..d8a6bb6 100644 --- a/include/gpac/iso639.h +++ b/include/gpac/iso639.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC @@ -32,51 +32,51 @@ extern "C" { /*! - * \file <gpac/iso639.h> - * \brief Language codes helper tools. +\file <gpac/iso639.h> +\brief Language codes helper tools. */ /*! - * \addtogroup lang_grp Languages - * \ingroup utils_grp - * \brief Language codes helper tools - * - * This section documents the language codes used in GPAC, based in ISO 639 or RFC 5646. - * @{ - */ +\addtogroup lang_grp +\brief Language codes helper tools + +This section documents the language codes used in GPAC, based in ISO 639 or RFC 5646. + +@{ +*/ #include <gpac/setup.h> /*! - * Gets number of supported language codes - * \return the number of supported language codes +Gets number of supported language codes +\return the number of supported language codes */ u32 gf_lang_get_count(); /*! - * Finds language by name or code - * \param lang_or_rfc_5646_code the langauage name, ISO 639 code or RFC 5646 code - * \return the index of the language, or -1 if not supported +Finds language by name or code +\param lang_or_rfc_5646_code the langauage name, ISO 639 code or RFC 5646 code +\return the index of the language, or -1 if not supported */ s32 gf_lang_find(const char *lang_or_rfc_5646_code); /*! - * Gets the langauge name for the given index - * \param lang_idx the langauge 0-based IDX - * \return the name of the language +Gets the langauge name for the given index +\param lang_idx the langauge 0-based IDX +\return the name of the language */ const char *gf_lang_get_name(u32 lang_idx); /*! - * Gets the 2 character code for the given index - * \param lang_idx the langauge 0-based IDX - * \return the 2 character code of the language +Gets the 2 character code for the given index +\param lang_idx the langauge 0-based IDX +\return the 2 character code of the language */ const char *gf_lang_get_2cc(u32 lang_idx); /*! - * Gets the 3 character code for the given index - * \param lang_idx the langauge 0-based IDX - * \return the 3 character code of the language +Gets the 3 character code for the given index +\param lang_idx the langauge 0-based IDX +\return the 3 character code of the language */ const char *gf_lang_get_3cc(u32 lang_idx); diff --git a/include/gpac/isomedia.h b/include/gpac/isomedia.h index 684d7c3..76aa8e2 100644 --- a/include/gpac/isomedia.h +++ b/include/gpac/isomedia.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -34,182 +34,135 @@ extern "C" { #endif /*! - * \file <gpac/isomedia.h> - * \brief ISOBMFF parsing and writing library. - */ - -/*! - * \addtogroup iso_grp ISO Base Media File - * \ingroup isobmf_grp - * \brief ISOBMF, 3GPP, AVC and HEVC file format utilities. - * - *This section documents the reading and writing of ISOBMF, 3GPP, AVC and HEVC file format using GPAC. - * @{ - */ - -#include <gpac/tools.h> - - -/******************************************************************** - FILE FORMAT CONSTANTS -********************************************************************/ +\file <gpac/isomedia.h> +\brief ISOBMFF parsing and writing library. +*/ -/*Modes for file opening - NOTE 1: All the READ function in this API can be used in EDIT/WRITE mode. +/*! +\addtogroup iso_grp ISO Base Media File +\brief ISOBMF, 3GPP, AVC and HEVC file format utilities. + +This section documents the reading and writing of ISOBMF (MP4, 3GPP, AVC, HEVC HEIF ...) +The library supports: +- regular movie read +- regular movie write +- fragmented movie and movie segments read +- fragmented movie and movie segments write +- QT support +- Sample descriptions for most common media found in such files (audio, video, text and subtitles) +- Meta and HEIF read +- Meta and HEIF write +- Common Encryption ISMA E&A and OMA DRM support +- MPEG-4 Systems extensions + + +All the READ function in this API can be used in EDIT/WRITE mode. However, some unexpected errors or values may happen in that case, depending -on how much modifications you made (timing, track with 0 samples, ...) - On the other hand, none of the EDIT/WRITE functions will work in -READ mode. - NOTE 2: The output structure of a edited file will sometimes be different +on how much modifications you made (timing, track with 0 samples, ...). +On the other hand, none of the EDIT/WRITE functions will work in READ mode. + +The output structure of a edited file will sometimes be different from the original file, but the media-data and meta-data will be identical. The only change happens in the file media-data container(s) during edition - NOTE 3: when editing the file, you MUST set the final name of the modified file + +When editing the file, you MUST set the final name of the modified file to something different. This API doesn't allow file overwriting. -*/ -enum -{ - /*Opens file for dumping: same as read-only but keeps all movie fragments info untouched*/ - GF_ISOM_OPEN_READ_DUMP = 0, - /*Opens a file in READ ONLY mode*/ - GF_ISOM_OPEN_READ, - /*Opens a file in WRITE ONLY mode. Media Data is captured on the fly. In this mode, - the editing functions are disabled.*/ - GF_ISOM_OPEN_WRITE, - /*Opens an existing file in EDIT mode*/ - GF_ISOM_OPEN_EDIT, - /*Creates a new file in EDIT mode*/ - GF_ISOM_WRITE_EDIT, - /*Opens an existing file for fragment concatenation*/ - GF_ISOM_OPEN_CAT_FRAGMENTS, -}; -/*Movie Options for file writing*/ -enum -{ - /*FLAT: the MediaData (MPEG4 ESs) is stored at the beginning of the file*/ - GF_ISOM_STORE_FLAT = 1, - /*STREAMABLE: the MetaData (File Info) is stored at the beginning of the file - for fast access during download*/ - GF_ISOM_STORE_STREAMABLE, - /*INTERLEAVED: Same as STREAMABLE, plus the media data is mixed by chunk of fixed duration*/ - GF_ISOM_STORE_INTERLEAVED, - /*INTERLEAVED +DRIFT: Same as INTERLEAVED, and adds time drift control to avoid creating too long chunks*/ - GF_ISOM_STORE_DRIFT_INTERLEAVED, - /*tightly interleaves samples based on their DTS, therefore allowing better placement of samples in the file. - This is used for both http interleaving and Hinting optimizations*/ - GF_ISOM_STORE_TIGHT +@{ + */ + +#include <gpac/tools.h> -}; -/*Some track may depend on other tracks for several reasons. They reference these tracks -through the following Reference Types*/ +/*! Track reference types + +Some track may depend on other tracks for several reasons. They reference these tracks through the following Reference Types +*/ enum { - /*ref type for the OD track dependencies*/ - GF_ISOM_REF_OD = GF_4CC( 'm', 'p', 'o', 'd' ), - /*ref type for stream dependencies*/ + /*! ref type for the OD track dependencies*/ + GF_ISOM_REF_OD = GF_4CC( 'm', 'p', 'o', 'd' ), + /*! ref type for stream dependencies*/ GF_ISOM_REF_DECODE = GF_4CC( 'd', 'p', 'n', 'd' ), - /*ref type for OCR (Object Clock Reference) dependencies*/ - GF_ISOM_REF_OCR = GF_4CC( 's', 'y', 'n', 'c' ), - /*ref type for IPI (Intellectual Property Information) dependencies*/ - GF_ISOM_REF_IPI = GF_4CC( 'i', 'p', 'i', 'r' ), - /*this track describes the referenced tr*/ - GF_ISOM_REF_META = GF_4CC( 'c', 'd', 's', 'c' ), - /*ref type for Hint tracks*/ - GF_ISOM_REF_HINT = GF_4CC( 'h', 'i', 'n', 't' ), - /*ref type for QT Chapter tracks*/ - GF_ISOM_REF_CHAP = GF_4CC( 'c', 'h', 'a', 'p' ), - /*ref type for the SVC and SHVC tracks*/ + /*! ref type for OCR (Object Clock Reference) dependencies*/ + GF_ISOM_REF_OCR = GF_4CC( 's', 'y', 'n', 'c' ), + /*! ref type for IPI (Intellectual Property Information) dependencies*/ + GF_ISOM_REF_IPI = GF_4CC( 'i', 'p', 'i', 'r' ), + /*! this track describes the referenced tr*/ + GF_ISOM_REF_META = GF_4CC( 'c', 'd', 's', 'c' ), + /*! ref type for Hint tracks*/ + GF_ISOM_REF_HINT = GF_4CC( 'h', 'i', 'n', 't' ), + /*! ref type for QT Chapter tracks*/ + GF_ISOM_REF_CHAP = GF_4CC( 'c', 'h', 'a', 'p' ), + /*! ref type for the SVC and SHVC base tracks*/ GF_ISOM_REF_BASE = GF_4CC( 's', 'b', 'a', 's' ), + /*! ref type for the SVC and SHVC extractor reference tracks*/ GF_ISOM_REF_SCAL = GF_4CC( 's', 'c', 'a', 'l' ), + /*! ref type for the SHVC tile base tracks*/ GF_ISOM_REF_TBAS = GF_4CC( 't', 'b', 'a', 's' ), + /*! ref type for the SHVC tile tracks*/ GF_ISOM_REF_SABT = GF_4CC( 's', 'a', 'b', 't' ), + /*! ref type for the SHVC oinf track*/ GF_ISOM_REF_OREF = GF_4CC( 'o', 'r', 'e', 'f' ), - //this track uses fonts carried/defined in the referenced track + /*! this track uses fonts carried/defined in the referenced track*/ GF_ISOM_REF_FONT = GF_4CC( 'f', 'o', 'n', 't' ), - //this track depends on the referenced hint track, i.e., it should only be used if the referenced hint track is used. + /*! this track depends on the referenced hint track, i.e., it should only be used if the referenced hint track is used.*/ GF_ISOM_REF_HIND = GF_4CC( 'h', 'i', 'n', 'd' ), - //this track contains auxiliary depth video information for the referenced video track + /*! this track contains auxiliary depth video information for the referenced video track*/ GF_ISOM_REF_VDEP = GF_4CC( 'v', 'd', 'e', 'p' ), - //this track contains auxiliary parallax video information for the referenced video track + /*! this track contains auxiliary parallax video information for the referenced video track*/ GF_ISOM_REF_VPLX = GF_4CC( 'v', 'p', 'l', 'x' ), - //this track contains subtitle, timed text or overlay graphical information for the referenced track or any track in the alternate group to which the track belongs, if any + /*! this track contains subtitle, timed text or overlay graphical information for the referenced track or any track in the alternate group to which the track belongs, if any*/ GF_ISOM_REF_SUBT = GF_4CC( 's', 'u', 'b', 't' ), - //thumbail track + /*! thumbnail track*/ GF_ISOM_REF_THUMB = GF_4CC( 't', 'h', 'm', 'b' ), /*DRC*/ - //additionnal audio track - GF_ISOM_REF_ADDA = GF_4CC( 'a', 'd', 'd', 'a' ), - //DRC metadata - GF_ISOM_REF_ADRC = GF_4CC( 'a', 'd', 'r', 'c' ), - //item->track location - GF_ISOM_REF_ILOC = GF_4CC( 'i', 'l', 'o', 'c' ), - //AVC dep stream - GF_ISOM_REF_AVCP = GF_4CC( 'a', 'v', 'c', 'p' ), - //AVC switch to - GF_ISOM_REF_SWTO = GF_4CC( 's', 'w', 't', 'o' ), - //AVC switch from - GF_ISOM_REF_SWFR = GF_4CC( 's', 'w', 'f', 'r' ), - - //Time code - GF_ISOM_REF_TMCD = GF_4CC( 't', 'm', 'c', 'd' ), - //Structural dependency - GF_ISOM_REF_CDEP = GF_4CC( 'c', 'd', 'e', 'p' ), - //transcript - GF_ISOM_REF_SCPT = GF_4CC( 's', 'c', 'p', 't' ), - //nonprimary source description - GF_ISOM_REF_SSRC = GF_4CC( 's', 's', 'r', 'c' ), - //layer audio track dependency - GF_ISOM_REF_LYRA = GF_4CC( 'l', 'y', 'r', 'a' ), - -}; - -//defined sample groups in GPAC -enum { - GF_ISOM_SAMPLE_GROUP_ROLL = GF_4CC( 'r', 'o', 'l', 'l'), - GF_ISOM_SAMPLE_GROUP_PROL = GF_4CC( 'p', 'r', 'o', 'l'), - GF_ISOM_SAMPLE_GROUP_RAP = GF_4CC( 'r', 'a', 'p', ' ' ), - GF_ISOM_SAMPLE_GROUP_SEIG = GF_4CC( 's', 'e', 'i', 'g' ), - GF_ISOM_SAMPLE_GROUP_OINF = GF_4CC( 'o', 'i', 'n', 'f'), - GF_ISOM_SAMPLE_GROUP_LINF = GF_4CC( 'l', 'i', 'n', 'f'), - GF_ISOM_SAMPLE_GROUP_TRIF = GF_4CC( 't', 'r', 'i', 'f' ), - GF_ISOM_SAMPLE_GROUP_NALM = GF_4CC( 'n', 'a', 'l', 'm'), - GF_ISOM_SAMPLE_GROUP_TELE = GF_4CC( 't', 'e', 'l', 'e'), - GF_ISOM_SAMPLE_GROUP_SAP = GF_4CC( 's', 'a', 'p', ' '), - GF_ISOM_SAMPLE_GROUP_ALST = GF_4CC( 'a', 'l', 's', 't'), - GF_ISOM_SAMPLE_GROUP_RASH = GF_4CC( 'r', 'a', 's', 'h'), - - GF_ISOM_SAMPLE_GROUP_AVLL = GF_4CC( 'a', 'v', 'l', 'l'), //p15 - GF_ISOM_SAMPLE_GROUP_AVSS = GF_4CC( 'a', 'v', 's', 's'), //p15 - GF_ISOM_SAMPLE_GROUP_DTRT = GF_4CC( 'd', 't', 'r', 't'), //p15 - GF_ISOM_SAMPLE_GROUP_MVIF = GF_4CC( 'm', 'v', 'i', 'f'), //p15 - GF_ISOM_SAMPLE_GROUP_SCIF = GF_4CC( 's', 'c', 'i', 'f'), //p15 - GF_ISOM_SAMPLE_GROUP_SCNM = GF_4CC( 's', 'c', 'n', 'm'), //p15 - GF_ISOM_SAMPLE_GROUP_STSA = GF_4CC( 's', 't', 's', 'a'), //p15 - GF_ISOM_SAMPLE_GROUP_TSAS = GF_4CC( 't', 's', 'a', 's'), //p15 - GF_ISOM_SAMPLE_GROUP_SYNC = GF_4CC( 's', 'y', 'n', 'c'), //p15 - GF_ISOM_SAMPLE_GROUP_TSCL = GF_4CC( 't', 's', 'c', 'l'), //p15 - GF_ISOM_SAMPLE_GROUP_VIPR = GF_4CC( 'v', 'i', 'p', 'r'), //p15 - - GF_ISOM_SAMPLE_GROUP_3GAG = GF_4CC( '3', 'g', 'a', 'g'), //3gpp - GF_ISOM_SAMPLE_GROUP_AVCB = GF_4CC( 'a', 'v', 'c', 'b'), //3gpp + /*! additional audio track*/ + GF_ISOM_REF_ADDA = GF_4CC( 'a', 'd', 'd', 'a' ), + /*! DRC metadata*/ + GF_ISOM_REF_ADRC = GF_4CC( 'a', 'd', 'r', 'c' ), + /*! item->track location*/ + GF_ISOM_REF_ILOC = GF_4CC( 'i', 'l', 'o', 'c' ), + /*! AVC dep stream*/ + GF_ISOM_REF_AVCP = GF_4CC( 'a', 'v', 'c', 'p' ), + /*! AVC switch to*/ + GF_ISOM_REF_SWTO = GF_4CC( 's', 'w', 't', 'o' ), + /*! AVC switch from*/ + GF_ISOM_REF_SWFR = GF_4CC( 's', 'w', 'f', 'r' ), + + /*! Time code*/ + GF_ISOM_REF_TMCD = GF_4CC( 't', 'm', 'c', 'd' ), + /*! Structural dependency*/ + GF_ISOM_REF_CDEP = GF_4CC( 'c', 'd', 'e', 'p' ), + /*! transcript*/ + GF_ISOM_REF_SCPT = GF_4CC( 's', 'c', 'p', 't' ), + /*! nonprimary source description*/ + GF_ISOM_REF_SSRC = GF_4CC( 's', 's', 'r', 'c' ), + /*! layer audio track dependency*/ + GF_ISOM_REF_LYRA = GF_4CC( 'l', 'y', 'r', 'a' ), + /*! File Delivery Item Information Extension */ + GF_ISOM_REF_FDEL = GF_4CC( 'f', 'd', 'e', 'l' ), + /*! Track fragment inherit */ + GF_ISOM_REF_TRIN = GF_4CC( 't', 'r', 'i', 'n' ), }; -/*Track Edition flag*/ -enum { - /*empty segment in the track (no media for this segment)*/ +/*! Track Edit list type*/ +typedef enum { + /*! empty segment in the track (no media for this segment)*/ GF_ISOM_EDIT_EMPTY = 0x00, - /*dwelled segment in the track (one media sample for this segment)*/ + /*! dwelled segment in the track (one media sample for this segment)*/ GF_ISOM_EDIT_DWELL = 0x01, - /*normal segment in the track*/ + /*! normal segment in the track*/ GF_ISOM_EDIT_NORMAL = 0x02 -}; +} GF_ISOEditType; -/*Generic Media Types (YOU HAVE TO USE ONE OF THESE TYPES FOR COMPLIANT ISO MEDIA FILES)*/ +/*! Generic Media Types (YOU HAVE TO USE ONE OF THESE TYPES FOR COMPLIANT ISO MEDIA FILES)*/ enum { /*base media types*/ GF_ISOM_MEDIA_VISUAL = GF_4CC( 'v', 'i', 'd', 'e' ), + GF_ISOM_MEDIA_AUXV = GF_4CC( 'a', 'u', 'x', 'v' ), + GF_ISOM_MEDIA_PICT = GF_4CC( 'p', 'i', 'c', 't' ), GF_ISOM_MEDIA_AUDIO = GF_4CC( 's', 'o', 'u', 'n' ), GF_ISOM_MEDIA_HINT = GF_4CC( 'h', 'i', 'n', 't' ), GF_ISOM_MEDIA_META = GF_4CC( 'm', 'e', 't', 'a' ), @@ -218,7 +171,10 @@ enum GF_ISOM_MEDIA_SUBT = GF_4CC( 's', 'b', 't', 'l' ), GF_ISOM_MEDIA_SUBPIC = GF_4CC( 's', 'u', 'b', 'p' ), GF_ISOM_MEDIA_MPEG_SUBT = GF_4CC( 's', 'u', 'b', 't' ), - + /*closed caption track types for QT/ProRes*/ + GF_ISOM_MEDIA_CLOSED_CAPTION = GF_4CC( 'c', 'l', 'c', 'p' ), + /*timecode metadata for QT/ProRes*/ + GF_ISOM_MEDIA_TIMECODE = GF_4CC( 't', 'm', 'c', 'd' ), /*MPEG-4 media types*/ GF_ISOM_MEDIA_OD = GF_4CC( 'o', 'd', 's', 'm' ), GF_ISOM_MEDIA_OCR = GF_4CC( 'c', 'r', 's', 'm' ), @@ -229,41 +185,19 @@ enum GF_ISOM_MEDIA_MPEGJ = GF_4CC( 'm', 'j', 's', 'm' ), /*GPAC-defined, for any track using MPEG-4 systems signaling but with undefined streaml types*/ GF_ISOM_MEDIA_ESM = GF_4CC( 'g', 'e', 's', 'm' ), - /*DIMS media type (same as scene but with a different mediaInfo)*/ GF_ISOM_MEDIA_DIMS = GF_4CC( 'd', 'i', 'm', 's' ), - - GF_ISOM_MEDIA_FLASH = GF_4CC( 'f', 'l', 's', 'h' ) -}; - - -/*DRM related code points*/ -enum -{ - GF_ISOM_BOX_UUID_PSEC = GF_4CC( 'P', 'S', 'E', 'C' ), - GF_ISOM_BOX_TYPE_SENC = GF_4CC( 's', 'e', 'n', 'c'), - - /* Encryption Scheme Type in the SchemeTypeInfoBox */ - GF_ISOM_ISMACRYP_SCHEME = GF_4CC( 'i', 'A', 'E', 'C' ), - /* Encryption Scheme Type in the SchemeTypeInfoBox */ - GF_ISOM_OMADRM_SCHEME = GF_4CC('o','d','k','m'), - - /* Encryption Scheme Type in the SchemeTypeInfoBox */ - GF_ISOM_CENC_SCHEME = GF_4CC('c','e','n','c'), - - /* Encryption Scheme Type in the SchemeTypeInfoBox */ - GF_ISOM_CBC_SCHEME = GF_4CC('c','b','c','1'), - - /* Encryption Scheme Type in the SchemeTypeInfoBox */ - GF_ISOM_ADOBE_SCHEME = GF_4CC('a','d','k','m'), - - /* Pattern Encryption Scheme Type in the SchemeTypeInfoBox */ - GF_ISOM_CENS_SCHEME = GF_4CC('c','e','n','s'), - GF_ISOM_CBCS_SCHEME = GF_4CC('c','b','c','s'), + /*SWF file embedded in media track*/ + GF_ISOM_MEDIA_FLASH = GF_4CC( 'f', 'l', 's', 'h' ), + /*QTVR track*/ + GF_ISOM_MEDIA_QTVR = GF_4CC( 'q', 't', 'v', 'r' ), + GF_ISOM_MEDIA_JPEG = GF_4CC( 'j', 'p', 'e', 'g' ), + GF_ISOM_MEDIA_JP2 = GF_4CC( 'j', 'p', '2', ' ' ), + GF_ISOM_MEDIA_PNG = GF_4CC( 'p', 'n', 'g', ' ' ), }; -/*specific media sub-types - you shall make sure the media sub type is what you expect*/ +/*! specific media sub-types - you shall make sure the media sub type is what you expect*/ enum { /*reserved, internal use in the lib. Indicates the track complies to MPEG-4 system @@ -282,6 +216,8 @@ enum GF_ISOM_SUBTYPE_AVC4_H264 = GF_4CC( 'a', 'v', 'c', '4' ), GF_ISOM_SUBTYPE_SVC_H264 = GF_4CC( 's', 'v', 'c', '1' ), GF_ISOM_SUBTYPE_MVC_H264 = GF_4CC( 'm', 'v', 'c', '1' ), + + /*HEVC media type*/ GF_ISOM_SUBTYPE_HVC1 = GF_4CC( 'h', 'v', 'c', '1' ), GF_ISOM_SUBTYPE_HEV1 = GF_4CC( 'h', 'e', 'v', '1' ), GF_ISOM_SUBTYPE_HVC2 = GF_4CC( 'h', 'v', 'c', '2' ), @@ -290,6 +226,21 @@ enum GF_ISOM_SUBTYPE_LHE1 = GF_4CC( 'l', 'h', 'e', '1' ), GF_ISOM_SUBTYPE_HVT1 = GF_4CC( 'h', 'v', 't', '1' ), + /*AV1 media type*/ + GF_ISOM_SUBTYPE_AV01 = GF_4CC('a', 'v', '0', '1'), + + /*Opus media type*/ + GF_ISOM_SUBTYPE_OPUS = GF_4CC('O', 'p', 'u', 's'), + GF_ISOM_SUBTYPE_FLAC = GF_4CC( 'f', 'L', 'a', 'C' ), + + /* VP */ + GF_ISOM_SUBTYPE_VP08 = GF_4CC('v', 'p', '0', '8'), + GF_ISOM_SUBTYPE_VP09 = GF_4CC('v', 'p', '0', '9'), + GF_ISOM_SUBTYPE_VP10 = GF_4CC('v', 'p', '1', '0'), + + /* Dolby Vision */ + GF_ISOM_SUBTYPE_DVHE = GF_4CC('d', 'v', 'h', 'e'), + /*3GPP(2) extension subtypes*/ GF_ISOM_SUBTYPE_3GP_H263 = GF_4CC( 's', '2', '6', '3' ), GF_ISOM_SUBTYPE_3GP_AMR = GF_4CC( 's', 'a', 'm', 'r' ), @@ -302,7 +253,10 @@ enum GF_ISOM_SUBTYPE_3GP_DIMS = GF_4CC( 'd', 'i', 'm', 's' ), GF_ISOM_SUBTYPE_AC3 = GF_4CC( 'a', 'c', '-', '3' ), + GF_ISOM_SUBTYPE_EC3 = GF_4CC( 'e', 'c', '-', '3' ), GF_ISOM_SUBTYPE_MP3 = GF_4CC( '.', 'm', 'p', '3' ), + GF_ISOM_SUBTYPE_MP4A = GF_4CC( 'm', 'p', '4', 'a' ), + GF_ISOM_SUBTYPE_MP4S = GF_4CC( 'm', 'p', '4', 's' ), GF_ISOM_SUBTYPE_LSR1 = GF_4CC( 'l', 's', 'r', '1' ), GF_ISOM_SUBTYPE_WVTT = GF_4CC( 'w', 'v', 't', 't' ), @@ -320,35 +274,87 @@ enum GF_ISOM_SUBTYPE_RRTP = GF_4CC( 'r', 'r', 't', 'p' ), GF_ISOM_SUBTYPE_RTCP = GF_4CC( 'r', 't', 'c', 'p' ), GF_ISOM_SUBTYPE_FLUTE = GF_4CC( 'f', 'd', 'p', ' ' ), + + /* Apple XDCAM */ + GF_ISOM_SUBTYPE_XDVB = GF_4CC( 'x', 'd', 'v', 'b' ), + + GF_ISOM_SUBTYPE_H263 = GF_4CC( 'h', '2', '6', '3' ), + + GF_ISOM_SUBTYPE_JPEG = GF_4CC( 'j', 'p', 'e', 'g' ), + GF_ISOM_SUBTYPE_PNG = GF_4CC( 'p', 'n', 'g', ' ' ), + GF_ISOM_SUBTYPE_MJP2 = GF_4CC( 'm', 'j', 'p', '2' ), + GF_ISOM_SUBTYPE_JP2K = GF_4CC('j','p','2','k'), + + GF_ISOM_SUBTYPE_MH3D_MHA1 = GF_4CC( 'm', 'h', 'a', '1' ), + GF_ISOM_SUBTYPE_MH3D_MHA2 = GF_4CC( 'm', 'h', 'a', '2' ), + GF_ISOM_SUBTYPE_MH3D_MHM1 = GF_4CC( 'm', 'h', 'm', '1' ), + GF_ISOM_SUBTYPE_MH3D_MHM2 = GF_4CC( 'm', 'h', 'm', '2' ), + + GF_ISOM_SUBTYPE_IPCM = GF_4CC( 'i', 'p', 'c', 'm' ), + GF_ISOM_SUBTYPE_FPCM = GF_4CC( 'f', 'p', 'c', 'm' ), + + /* on-screen colours */ + GF_ISOM_SUBTYPE_NCLX = GF_4CC( 'n', 'c', 'l', 'x' ), + GF_ISOM_SUBTYPE_NCLC = GF_4CC( 'n', 'c', 'l', 'c' ), + GF_ISOM_SUBTYPE_PROF = GF_4CC( 'p', 'r', 'o', 'f' ), + GF_ISOM_SUBTYPE_RICC = GF_4CC( 'r', 'I', 'C', 'C' ), + + /* QT audio codecs */ + GF_QT_SUBTYPE_RAW = GF_4CC('r','a','w',' '), + GF_QT_SUBTYPE_TWOS = GF_4CC('t','w','o','s'), + GF_QT_SUBTYPE_SOWT = GF_4CC('s','o','w','t'), + GF_QT_SUBTYPE_FL32 = GF_4CC('f','l','3','2'), + GF_QT_SUBTYPE_FL64 = GF_4CC('f','l','6','4'), + GF_QT_SUBTYPE_IN24 = GF_4CC('i','n','2','4'), + GF_QT_SUBTYPE_IN32 = GF_4CC('i','n','3','2'), + GF_QT_SUBTYPE_ULAW = GF_4CC('u','l','a','w'), + GF_QT_SUBTYPE_ALAW = GF_4CC('a','l','a','w'), + GF_QT_SUBTYPE_ADPCM = GF_4CC(0x6D,0x73,0x00,0x02), + GF_QT_SUBTYPE_IMA_ADPCM = GF_4CC(0x6D,0x73,0x00,0x11), + GF_QT_SUBTYPE_DVCA = GF_4CC('d','v','c','a'), + GF_QT_SUBTYPE_QDMC = GF_4CC('Q','D','M','C'), + GF_QT_SUBTYPE_QDMC2 = GF_4CC('Q','D','M','2'), + GF_QT_SUBTYPE_QCELP = GF_4CC('Q','c','l','p'), + GF_QT_SUBTYPE_kMP3 = GF_4CC(0x6D,0x73,0x00,0x55), + + /* QT video codecs */ + GF_QT_SUBTYPE_C608 = GF_4CC( 'c', '6', '0', '8' ), + GF_QT_SUBTYPE_APCH = GF_4CC( 'a', 'p', 'c', 'h' ), + GF_QT_SUBTYPE_APCO = GF_4CC( 'a', 'p', 'c', 'o' ), + GF_QT_SUBTYPE_APCN = GF_4CC( 'a', 'p', 'c', 'n' ), + GF_QT_SUBTYPE_APCS = GF_4CC( 'a', 'p', 'c', 's' ), + GF_QT_SUBTYPE_AP4X = GF_4CC( 'a', 'p', '4', 'x' ), + GF_QT_SUBTYPE_AP4H = GF_4CC( 'a', 'p', '4', 'h' ), + GF_QT_SUBTYPE_YUV422 = GF_4CC('y','u','v','2'), +// GF_QT_SUBTYPE_YUV422 = GF_4CC('2','v','u','Y'), + GF_QT_SUBTYPE_YUV444 = GF_4CC('v','3','0','8'), + GF_QT_SUBTYPE_YUV422_10 = GF_4CC('v','2','1','0'), + GF_QT_SUBTYPE_YUV444_10 = GF_4CC('v','4','1','0'), }; -/*direction for sample search (including SyncSamples search) +/*! direction for sample search (including SyncSamples search) Function using search allways specify the desired time in composition (presentation) time - (Sample N-1) DesiredTime (Sample N) - -FORWARD: will give the next sample given the desired time (eg, N) -BACKWARD: will give the previous sample given the desired time (eg, N-1) -SYNCFORWARD: will search from the desired point in time for a sync sample if any - If no sync info, behaves as FORWARD -SYNCBACKWARD: will search till the desired point in time for a sync sample if any - If no sync info, behaves as BACKWARD -SYNCSHADOW: use the sync shadow information to retrieve the sample. - If no SyncShadow info, behave as SYNCBACKWARD */ -enum +typedef enum { + /*! FORWARD: will give the next sample given the desired time (eg, N)*/ GF_ISOM_SEARCH_FORWARD = 1, + /*! BACKWARD: will give the previous sample given the desired time (eg, N-1)*/ GF_ISOM_SEARCH_BACKWARD = 2, + /*! SYNCFORWARD: will search from the desired point in time for a sync sample if any. If no sync info, behaves as FORWARD*/ GF_ISOM_SEARCH_SYNC_FORWARD = 3, + /*! SYNCBACKWARD: will search till the desired point in time for a sync sample if any. If no sync info, behaves as BACKWARD*/ GF_ISOM_SEARCH_SYNC_BACKWARD = 4, + /*! SYNCSHADOW: use the sync shadow information to retrieve the sample. If no SyncShadow info, behave as SYNCBACKWARD + \warning deprecated in ISOBMFF*/ GF_ISOM_SEARCH_SYNC_SHADOW = 5 -}; +} GF_ISOSearchMode; -/*Predefined File Brand codes (MPEG-4 and JPEG2000)*/ +/*! Predefined File Brand codes (MPEG-4 and JPEG2000)*/ enum { /*file complying to the generic ISO Media File (base specification ISO/IEC 14496-12) @@ -371,6 +377,7 @@ enum /*final version of 3GPP file spec*/ GF_ISOM_BRAND_3GP6 = GF_4CC('3', 'g', 'p', '6'), /*generci 3GPP file (several audio tracks, etc..)*/ + GF_ISOM_BRAND_3GG5 = GF_4CC('3', 'g', 'g', '5'), GF_ISOM_BRAND_3GG6 = GF_4CC('3', 'g', 'g', '6'), /*3GPP2 file spec*/ GF_ISOM_BRAND_3G2A = GF_4CC('3', 'g', '2', 'a'), @@ -383,318 +390,747 @@ enum /* Image File Format */ GF_ISOM_BRAND_MIF1 = GF_4CC('m', 'i', 'f', '1'), GF_ISOM_BRAND_HEIC = GF_4CC('h', 'e', 'i', 'c'), -}; + GF_ISOM_BRAND_HEIM = GF_4CC('h', 'e', 'i', 'm'), + GF_ISOM_BRAND_AVIF = GF_4CC('a', 'v', 'i', 'f'), + GF_ISOM_BRAND_AVCI = GF_4CC('a', 'v', 'c', 'i'), + /*other iso media brands */ + GF_ISOM_BRAND_ISO1 = GF_4CC( 'i', 's', 'o', '1' ), + GF_ISOM_BRAND_ISO3 = GF_4CC( 'i', 's', 'o', '3' ), + GF_ISOM_BRAND_ISO5 = GF_4CC( 'i', 's', 'o', '5' ), + GF_ISOM_BRAND_ISO6 = GF_4CC( 'i', 's', 'o', '6' ), + GF_ISOM_BRAND_ISO7 = GF_4CC( 'i', 's', 'o', '7' ), + GF_ISOM_BRAND_ISO8 = GF_4CC( 'i', 's', 'o', '8' ), + GF_ISOM_BRAND_ISO9 = GF_4CC( 'i', 's', 'o', '9' ), + GF_ISOM_BRAND_ISOA = GF_4CC( 'i', 's', 'o', 'a' ), + + /* QT brand*/ + GF_ISOM_BRAND_QT = GF_4CC( 'q', 't', ' ', ' ' ), + + /* JPEG 2000 Image (.JP2) [ISO 15444-1] */ + GF_ISOM_BRAND_JP2 = GF_4CC( 'j', 'p', '2', ' ' ), + + /* MPEG-4 (.MP4) for SonyPSP */ + GF_ISOM_BRAND_MSNV = GF_4CC( 'M', 'S', 'N', 'V' ), + /* Apple iTunes AAC-LC (.M4A) Audio */ + GF_ISOM_BRAND_M4A = GF_4CC( 'M', '4', 'A', ' ' ), + /* Apple iTunes Video (.M4V) Video */ + GF_ISOM_BRAND_M4V = GF_4CC( 'M', '4', 'V', ' ' ), + + GF_ISOM_BRAND_HVCE = GF_4CC( 'h', 'v', 'c', 'e' ), + GF_ISOM_BRAND_HVCI = GF_4CC( 'h', 'v', 'c', 'i' ), + GF_ISOM_BRAND_HVTI = GF_4CC( 'h', 'v', 't', 'i' ), + + + GF_ISOM_BRAND_AV01 = GF_4CC( 'a', 'v', '0', '1'), + + GF_ISOM_BRAND_OPUS = GF_4CC( 'O', 'p', 'u', 's'), + + GF_ISOM_BRAND_ISMA = GF_4CC( 'I', 'S', 'M', 'A' ), + + /* dash related brands (ISO/IEC 23009-1) */ + GF_ISOM_BRAND_DASH = GF_4CC('d','a','s','h'), + /* Media Segment conforming to the DASH Self-Initializing Media Segment format type */ + GF_ISOM_BRAND_DSMS = GF_4CC('d','s','m','s'), + /* Media Segment conforming to the general format type */ + GF_ISOM_BRAND_MSDH = GF_4CC('m','s','d','h'), + /* Media Segment conforming to the Indexed Media Segment format type */ + GF_ISOM_BRAND_MSIX = GF_4CC('m','s','i','x'), + /* Representation Index Segment used to index MPEG-2 TS based Media Segments */ + GF_ISOM_BRAND_RISX = GF_4CC('r','i','s','x'), + /* last Media Segment indicator for ISO base media file format */ + GF_ISOM_BRAND_LMSG = GF_4CC('l','m','s','g'), + /* Single Index Segment used to index MPEG-2 TS based Media Segments */ + GF_ISOM_BRAND_SISX = GF_4CC('s','i','s','x'), + /* Subsegment Index Segment used to index MPEG-2 TS based Media Segments */ + GF_ISOM_BRAND_SSSS = GF_4CC('s','s','s','s'), + + + /* from ismacryp.c */ + /* OMA DCF DRM Format 2.0 (OMA-TS-DRM-DCF-V2_0-20060303-A) */ + GF_ISOM_BRAND_ODCF = GF_4CC('o','d','c','f'), + /* OMA PDCF DRM Format 2.1 (OMA-TS-DRM-DCF-V2_1-20070724-C) */ + GF_ISOM_BRAND_OPF2 = GF_4CC('o','p','f','2'), +}; -/*MPEG-4 ProfileAndLevel codes*/ -enum +/*! sample roll information type*/ +typedef enum { - GF_ISOM_PL_AUDIO, - GF_ISOM_PL_VISUAL, - GF_ISOM_PL_GRAPHICS, - GF_ISOM_PL_SCENE, - GF_ISOM_PL_OD, - GF_ISOM_PL_MPEGJ, - /*not a profile, just set/unset inlineFlag*/ - GF_ISOM_PL_INLINE, -}; + /*! no roll info associated*/ + GF_ISOM_SAMPLE_ROLL_NONE=0, + /*! roll info describes a roll operation*/ + GF_ISOM_SAMPLE_ROLL, + /*! roll info describes an audio preroll*/ + GF_ISOM_SAMPLE_PREROLL, + /*! roll info describes audio preroll but is not set for this sample*/ + GF_ISOM_SAMPLE_PREROLL_NONE +} GF_ISOSampleRollType; #ifndef GPAC_DISABLE_ISOM #include <gpac/mpeg4_odf.h> -/*the isomedia file*/ +/*! isomedia file*/ typedef struct __tag_isom GF_ISOFile; -/*Random Access Point flag*/ +/*! a track ID value - just a 32 bit value but typedefed for API safety*/ +typedef u32 GF_ISOTrackID; + +/*! @} */ + +/*! +\addtogroup isosample_grp ISO Sample +\ingroup iso_grp + +Media sample for ISOBMFF API. +@{ +*/ + +/*! Random Access Point flag*/ typedef enum { + /*! redundant sync shadow - only set when reading sample*/ RAP_REDUNDANT = -1, + /*! not rap*/ RAP_NO = 0, + /*! sync point (IDR)*/ RAP = 1, + /*! sync point (IDR)*/ SAP_TYPE_1 = 1, + /*! sync point (IDR)*/ SAP_TYPE_2 = 2, + /*! RAP, OpenGOP*/ SAP_TYPE_3 = 3, + /*! RAP, roll info (GDR or audio preroll)*/ SAP_TYPE_4 = 4, - SAP_TYPE_5 = 5, - SAP_TYPE_6 = 6 -} SAPType; +} GF_ISOSAPType; -/*media sample object*/ +/*! media sample object*/ typedef struct { - /*data size*/ + /*! data size*/ u32 dataLength; - /*data with padding if requested*/ - char *data; - /*decoding time*/ + /*! data with padding if requested*/ + u8 *data; + /*! decoding time*/ u64 DTS; - /*relative offset for composition if needed*/ + /*! relative offset for composition if needed*/ s32 CTS_Offset; - SAPType IsRAP; + /*! SAP type*/ + GF_ISOSAPType IsRAP; + /*! allocated data size - used only when using static sample in \ref gf_isom_get_sample_ex*/ + u32 alloc_size; + + /*! number of packed samples in this sample. If 0 or 1, only 1 sample is present + only used for constant size and constant duration samples*/ + u32 nb_pack; } GF_ISOSample; -/*creates a new empty sample*/ +/*! creates a new empty sample +\return the newly allocated ISO sample*/ GF_ISOSample *gf_isom_sample_new(); -/*delete a sample. NOTE:the buffer content will be destroyed by default. +/*! delete a sample. NOTE:the buffer content will be destroyed by default. if you wish to keep the buffer, set dataLength to 0 in the sample before deleting it -the pointer is set to NULL after deletion*/ +the pointer is set to NULL after deletion +\param samp pointer to the target ISO sample +*/ void gf_isom_sample_del(GF_ISOSample **samp); -/******************************************************************** - GENERAL API FUNCTIONS -********************************************************************/ - -/*get the last fatal error that occured in the file -ANY FUNCTION OF THIS API WON'T BE PROCESSED IF THE FILE HAS AN ERROR -Note: some function may return an error while the movie has no error -the last error is a FatalError, and is not always set if a bad -param is specified...*/ -GF_Err gf_isom_last_error(GF_ISOFile *the_file); - -/*indicates if target file is an IsoMedia file - returns 1 if it is a non-special file, 2 if an init segment, 3 if a media segment, - returns 0 otherwise*/ -u32 gf_isom_probe_file(const char *fileName); - -/*Opens an isoMedia File. -tmp_dir: for the 2 edit modes only, specifies a location for temp file. If NULL, the library will use the default -OS temporary file management schemes.*/ -GF_ISOFile *gf_isom_open(const char *fileName, u32 OpenMode, const char *tmp_dir); - -/*close the file, write it if new/edited*/ -GF_Err gf_isom_close(GF_ISOFile *the_file); - -/*delete the movie without saving it.*/ -void gf_isom_delete(GF_ISOFile *the_file); -/*Get the mode of an open file*/ -u8 gf_isom_get_mode(GF_ISOFile *the_file); - -Bool gf_isom_is_JPEG2000(GF_ISOFile *mov); - -u64 gf_isom_get_file_size(GF_ISOFile *the_file); - -Bool gf_isom_moov_first(GF_ISOFile *movie); - -/*sets write cache size for files when creating them. If size is 0, writing -only relies on the underlying OS fwrite/fgetc -If movie is NULL, assigns the default write cache size for any new movie*/ -GF_Err gf_isom_set_output_buffering(GF_ISOFile *movie, u32 size); - -/******************************************************************** - STREAMING API FUNCTIONS -********************************************************************/ -/*open a movie that can be uncomplete in READ_ONLY mode -to use for http streaming & co - -NOTE: you must buffer the data to a local file, this mode DOES NOT handle -http/ftp/... streaming +/*! @} */ -start_range and end_range restricts the media byte range in the URL (used by DASH) -if 0 or end_range<=start_range, the entire URL is used when parsing +/*! +\addtogroup isogen_grp Generic API +\ingroup iso_grp -BytesMissing is the predicted number of bytes missing for the file to be loaded -Note that if the file is not optimized for streaming, this number is not accurate -If the movie is successfully loaded (the_file non-NULL), BytesMissing is zero +Generic API functions +@{ */ -GF_Err gf_isom_open_progressive(const char *fileName, u64 start_range, u64 end_range, GF_ISOFile **the_file, u64 *BytesMissing); -/*If requesting a sample fails with error GF_ISOM_INCOMPLETE_FILE, use this function -to get the number of bytes missing to retrieve the sample*/ -u64 gf_isom_get_missing_bytes(GF_ISOFile *the_file, u32 trackNumber); - - -/*Fragmented movie extensions*/ +/*! Movie file opening modes */ +typedef enum +{ + /*! Opens file for dumping: same as read-only but keeps all movie fragments info untouched*/ + GF_ISOM_OPEN_READ_DUMP = 0, + /*! Opens a file in READ ONLY mode*/ + GF_ISOM_OPEN_READ, + /*! Opens a file in WRITE ONLY mode. Media Data is captured on the fly and storage mode is always flat (moov at end). + In this mode, the editing functions are disabled.*/ + GF_ISOM_OPEN_WRITE, + /*! Opens an existing file in EDIT mode*/ + GF_ISOM_OPEN_EDIT, + /*! Creates a new file in EDIT mode*/ + GF_ISOM_WRITE_EDIT, + /*! Opens an existing file and keep fragment information*/ + GF_ISOM_OPEN_KEEP_FRAGMENTS, +} GF_ISOOpenMode; -/*return 0 if movie isn't fragmented, 1 otherwise*/ -u32 gf_isom_is_fragmented(GF_ISOFile *the_file); -/*return 0 if track isn't fragmented, 1 otherwise*/ -u32 gf_isom_is_track_fragmented(GF_ISOFile *the_file, u32 TrackID); +/*! indicates if target file is an IsoMedia file +\param fileName the target local file name or path to probe, gmem:// or gfio:// resource +\return 1 if it is a non-special file, 2 if an init segment, 3 if a media segment, 0 otherwise +*/ +u32 gf_isom_probe_file(const char *fileName); -/*a file being downloaded may be a fragmented file. In this case only partial info -is available once the file is successfully open (gf_isom_open_progressive), and since there is -no information wrt number fragments (which could actually be generated on the fly -at the sender side), you must call this function on regular bases in order to -load newly downloaded fragments. Note this may result in Track/Movie duration changes -and SampleCount change too ... +/*! indicates if target file is an IsoMedia file +\param fileName the target local file name or path to probe, gmem:// or gfio:// resource +\param start_range the offset in the file to start probing from +\param end_range the offset in the file at which probing shall stop +\return 1 if it is a non-special file, 2 if an init segment, 3 if a media segment, 4 if empty or no file, 0 otherwise +*/ +u32 gf_isom_probe_file_range(const char *fileName, u64 start_range, u64 end_range); -if new_location is set, the previous bitstream is changed to this new location, otherwise it is refreshed (disk flush) +/*! indicates if target file is an IsoMedia file +\param inBuf the buffer to probe +\param inSize the sizeo of the buffer to probe +\returns 1 if it is a non-special file, 2 if an init segment, 3 if a media segment, 0 otherwise (non recognized or too short) +*/ +u32 gf_isom_probe_data(const u8*inBuf, u32 inSize); + +/*! opens an isoMedia File. +If fileName is NULL data will be written in memory ; write with gf_isom_write() ; use gf_isom_get_bs() to get the data ; use gf_isom_delete() to delete the internal data. +\param fileName name of the file to open, , gmem:// or gfio:// resource. The special name "_gpac_isobmff_redirect" is used to indicate that segment shall be written to a memory buffer passed to callback function set through \ref gf_isom_set_write_callback. +\param OpenMode file opening mode +\param tmp_dir for the 2 edit modes only, specifies a location for temp file. If NULL, the library will use the default +OS temporary file management schemes. +\return the created ISO file if no error +*/ +GF_ISOFile *gf_isom_open(const char *fileName, GF_ISOOpenMode OpenMode, const char *tmp_dir); +/*! closes the file, write it if new/edited - equivalent to gf_isom_write()+gf_isom_delete() +\param isom_file the target ISO file +\return error if any */ -GF_Err gf_isom_refresh_fragmented(GF_ISOFile *the_file, u64 *MissingBytes, const char *new_location); +GF_Err gf_isom_close(GF_ISOFile *isom_file); -/*check if file has movie info, eg has tracks & dynamic media. Some files may just use -the base IsoMedia structure without "moov" container*/ -Bool gf_isom_has_movie(GF_ISOFile *file); +/*! deletes the movie without saving it +\param isom_file the target ISO file +*/ +void gf_isom_delete(GF_ISOFile *isom_file); -/* check if the file has a top styp box and returns the brand and version of the first styp found */ -Bool gf_isom_has_segment(GF_ISOFile *file, u32 *brand, u32 *version); -/*get number of movie fragments in the file*/ -u32 gf_isom_segment_get_fragment_count(GF_ISOFile *file); -/*get number of track fragments in the indicated movie fragment (1-based index)*/ -u32 gf_isom_segment_get_track_fragment_count(GF_ISOFile *file, u32 moof_index); -/*returns the track ID and get the tfdt of the given track fragment (1-based index) in the indicated movie fragment (1-based index)*/ -u32 gf_isom_segment_get_track_fragment_decode_time(GF_ISOFile *file, u32 moof_index, u32 traf_index, u64 *decode_time); +/*! gets the last fatal error that occured in the file +ANY FUNCTION OF THIS API WON'T BE PROCESSED IF THE FILE HAS AN ERROR +Note: some function may return an error while the movie has no error +the last error is a FatalError, and is not always set if a bad +param is specified... +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_last_error(GF_ISOFile *isom_file); -/* Indicates that we want to parse only one moof/mdat at a time - in order to proceed to next moof, call gf_isom_reset_data_offset +/*! gets the mode of an open file +\param isom_file the target ISO file +\return open mode of the file */ -void gf_isom_set_single_moof_mode(GF_ISOFile *file, Bool mode); -/******************************************************************** - READING API FUNCTIONS -********************************************************************/ +u8 gf_isom_get_mode(GF_ISOFile *isom_file); -/*return the number of tracks in the movie, or -1 if error*/ -u32 gf_isom_get_track_count(GF_ISOFile *the_file); +/*! checks if file is J2K image +\param isom_file the target ISO file +\return GF_TRUE if file is a j2k image, GF_FALSE otherwise +*/ +Bool gf_isom_is_JPEG2000(GF_ISOFile *isom_file); -/*return the timescale of the movie, 0 if error*/ -u32 gf_isom_get_timescale(GF_ISOFile *the_file); -/*return the computed duration of the movie given the media in the sample tables, 0 if error*/ -u64 gf_isom_get_duration(GF_ISOFile *the_file); +/*! checks if a given four character code matches a known video handler type (vide, auxv, pict, ...) +\param mtype the four character code to check +\return GF_TRUE if the type is a video media type*/ +Bool gf_isom_is_video_handler_type(u32 mtype); -/*return the duration of the movie as written in the file, regardless of the media data*/ -u64 gf_isom_get_original_duration(GF_ISOFile *movie); +/*! gets number of implemented boxes in (including the internal unknown box wrapper). +\note There can be several times the same type returned due to variation of the box (versions or flags) +\return number of implemented boxes +*/ +u32 gf_isom_get_num_supported_boxes(); -/*return the creation info of the movie*/ -GF_Err gf_isom_get_creation_time(GF_ISOFile *the_file, u64 *creationTime, u64 *modificationTime); +/*! gets four character code of box given its index. Index 0 is GPAC internal unknown box handler +\param idx 0-based index of the box +\return four character code of the box +*/ +u32 gf_isom_get_supported_box_type(u32 idx); -/*return the trackID of track number n, or 0 if error*/ -u32 gf_isom_get_track_id(GF_ISOFile *the_file, u32 trackNumber); +#ifndef GPAC_DISABLE_ISOM_DUMP -/*return the track number of the track of specified ID, or 0 if error*/ -u32 gf_isom_get_track_by_id(GF_ISOFile *the_file, u32 trackID); +/*! prints default box syntax of box given its index. Index 0 is GPAC internal unknown box handler +\param idx 0-based index of the box +\param trace the file object to dump to +\return error if any +*/ +GF_Err gf_isom_dump_supported_box(u32 idx, FILE * trace); -/*return the original trackID of the track number n, or 0 if error*/ -u32 gf_isom_get_track_original_id(GF_ISOFile *movie, u32 trackNumber); +#endif -/*gets the enable flag of a track 0: NO, 1: yes, 2: error*/ -u8 gf_isom_is_track_enabled(GF_ISOFile *the_file, u32 trackNumber); +/*! @} */ -/* determines if the track is encrypted 0: NO, 1: yes, 2: error*/ -Bool gf_isom_is_track_encrypted(GF_ISOFile *the_file, u32 trackNumber); +/*! +\addtogroup isoread_grp ISOBMFF Reading +\ingroup iso_grp -/*get the track duration return 0 if bad param*/ -u64 gf_isom_get_track_duration(GF_ISOFile *the_file, u32 trackNumber); +ISOBMF file reading +@{ +*/ -/*return the media type FOUR CHAR code type of the media*/ -u32 gf_isom_get_media_type(GF_ISOFile *the_file, u32 trackNumber); +/*! checks if moov box is before any mdat box +\param isom_file the target ISO file +\return GF_TRUE if if moov box is before any mdat box, GF_FALSE otherwise +*/ +Bool gf_isom_moov_first(GF_ISOFile *isom_file); -/*return the media type FOUR CHAR code type of the media*/ -u32 gf_isom_get_media_subtype(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex); +/*! when reading a file, indicates that file data is missing the indicated bytes +\param isom_file the target ISO file +\param byte_offset number of bytes not present at the begining of the file +\return error if any +*/ +GF_Err gf_isom_set_byte_offset(GF_ISOFile *isom_file, s64 byte_offset); -/*return the media type FOUR CHAR code type of an MPEG4 media (eg, mp4a, mp4v, enca, encv, resv, etc...) -returns 0 if not MPEG-4 subtype*/ -u32 gf_isom_get_mpeg4_subtype(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex); -/*Get the media (composition) time given the absolute time in the Movie -mediaTime is set to 0 if the media is not playing at that time (empty time segment)*/ -GF_Err gf_isom_get_media_time(GF_ISOFile *the_file, u32 trackNumber, u32 movieTime, u64 *MediaTime); +/*! opens a movie that can be uncomplete in READ_ONLY mode +to use for http streaming & co -/*Get the number of "streams" stored in the media - a media can have several stream descriptions...*/ -u32 gf_isom_get_sample_description_count(GF_ISOFile *the_file, u32 trackNumber); +start_range and end_range restricts the media byte range in the URL (used by DASH) +if 0 or end_range<=start_range, the entire URL is used when parsing -/*Get the stream description index (eg, the ESD) for a given time IN MEDIA TIMESCALE -return 0 if error or if empty*/ -u32 gf_isom_get_sample_description_index(GF_ISOFile *the_file, u32 trackNumber, u64 for_time); +If the url indicates a gfio or gmem resource, the file can be played from the associated underlying buffer. For gmem, you must call \ref gf_isom_refresh_fragmented and gf_isom_set_removed_bytes whenever the underlying buffer is modified. -/*returns 1 if samples refering to the given stream description are present in the file -0 otherwise*/ -Bool gf_isom_is_self_contained(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex); +\param fileName the name of the local file or cache to open, gmem:// or gfio:// +\param start_range only loads starting from indicated byte range +\param end_range loading stops at indicated byte range +\param enable_frag_templates loads fragment and segment boundaries in an internal table +\param isom_file pointer set to the opened file if success +\param BytesMissing is set to the predicted number of bytes missing for the file to be loaded +Note that if the file is not optimized for streaming, this number is not accurate +If the movie is successfully loaded (isom_file non-NULL), BytesMissing is zero +\return error if any +*/ +GF_Err gf_isom_open_progressive(const char *fileName, u64 start_range, u64 end_range, Bool enable_frag_templates, GF_ISOFile **isom_file, u64 *BytesMissing); -/*get the media duration (without edit) based on sample table return 0 if no samples (URL streams)*/ -u64 gf_isom_get_media_duration(GF_ISOFile *the_file, u32 trackNumber); -/*get the media duration (without edit) as indicated in the file (regardless of sample tables)*/ -u64 gf_isom_get_media_original_duration(GF_ISOFile *movie, u32 trackNumber); +/*! same as \ref gf_isom_open_progressive but allows fetching the incomplete box type -/*Get the timeScale of the media. */ -u32 gf_isom_get_media_timescale(GF_ISOFile *the_file, u32 trackNumber); +\param fileName the name of the local file or cache to open +\param start_range only loads starting from indicated byte range +\param end_range loading stops at indicated byte range +\param enable_frag_templates loads fragment and segment boundaries in an internal table +\param isom_file pointer set to the opened file if success +\param BytesMissing is set to the predicted number of bytes missing for the file to be loaded +\param topBoxType is set to the 4CC of the incomplete top-level box found - may be NULL +Note that if the file is not optimized for streaming, this number is not accurate +If the movie is successfully loaded (isom_file non-NULL), BytesMissing is zero +\return error if any +*/ +GF_Err gf_isom_open_progressive_ex(const char *fileName, u64 start_range, u64 end_range, Bool enable_frag_templates, GF_ISOFile **isom_file, u64 *BytesMissing, u32 *topBoxType); + +/*! retrieves number of bytes missing. +if requesting a sample fails with error GF_ISOM_INCOMPLETE_FILE, use this function +to get the number of bytes missing to retrieve the sample +\param isom_file the target ISO file +\param trackNumber the desired track to query +\return the number of bytes missing to fetch the sample +*/ +u64 gf_isom_get_missing_bytes(GF_ISOFile *isom_file, u32 trackNumber); -/*gets min, average and max maximum chunk durations (each of them s optional) of the track in media timescale*/ -GF_Err gf_isom_get_chunks_infos(GF_ISOFile *movie, u32 trackNumber, u32 *dur_min, u32 *dur_avg, u32 *dur_max, u32 *size_min, u32 *size_avg, u32 *size_max); +/*! checks if a file has movie info (moov box with tracks & dynamic media). Some files may just use +the base IsoMedia structure without "moov" container +\param isom_file the target ISO file +\return GF_TRUE if the file has a movie, GF_FALSE otherwise +*/ +Bool gf_isom_has_movie(GF_ISOFile *isom_file); + +/*! gets number of tracks +\param isom_file the target ISO file +\return the number of tracks in the movie, or -1 if error*/ +u32 gf_isom_get_track_count(GF_ISOFile *isom_file); + +/*! gets the movie timescale +\param isom_file the target ISO file +\return the timescale of the movie, 0 if error*/ +u32 gf_isom_get_timescale(GF_ISOFile *isom_file); + +/*! gets the movie duration computed based on media samples and edit lists +\param isom_file the target ISO file +\return the computed duration of the movie, 0 if error*/ +u64 gf_isom_get_duration(GF_ISOFile *isom_file); + +/*! gets the original movie duration as written in the file, regardless of the media data +\param isom_file the target ISO file +\return the duration of the movie*/ +u64 gf_isom_get_original_duration(GF_ISOFile *isom_file); + +/*! gets the creation info of the movie +\param isom_file the target ISO file +\param creationTime set to the creation time of the movie +\param modificationTime set to the modification time of the movie +\return error if any +*/ +GF_Err gf_isom_get_creation_time(GF_ISOFile *isom_file, u64 *creationTime, u64 *modificationTime); + +/*! gets the ID of a track +\param isom_file the target ISO file +\param trackNumber the target track +\return the trackID of the track, or 0 if error*/ +GF_ISOTrackID gf_isom_get_track_id(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets a track number by its ID +\param isom_file the target ISO file +\param trackID the target track ID +\return the track number of the track, or 0 if error*/ +u32 gf_isom_get_track_by_id(GF_ISOFile *isom_file, GF_ISOTrackID trackID); + +/*! gets the track original ID (before cloning from another file) +\param isom_file the target ISO file +\param trackNumber the target track +\return the original trackID of the track, or 0 if error*/ +GF_ISOTrackID gf_isom_get_track_original_id(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the enable flag of a track +\param isom_file the target ISO file +\param trackNumber the target track +\return 0: not enabled, 1: enabled, 2: error +*/ +u8 gf_isom_is_track_enabled(GF_ISOFile *isom_file, u32 trackNumber); -/*Get the HandlerDescription name. The outName must be: - (outName != NULL && *outName == NULL) -the handler name is the string version of the MediaTypes*/ -GF_Err gf_isom_get_handler_name(GF_ISOFile *the_file, u32 trackNumber, const char **outName); +/*! gets track flags +\param isom_file the target ISO file +\param trackNumber the target track +\return the track flags +*/ +u32 gf_isom_get_track_flags(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the track duration +\param isom_file the target ISO file +\param trackNumber the target track +\return the track duration in movie timescale, or 0 if error*/ +u64 gf_isom_get_track_duration(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the media type (audio, video, etc) of a track +\param isom_file the target ISO file +\param trackNumber the target track +\return the media type four character code of the media*/ +u32 gf_isom_get_media_type(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets media subtype of a sample description entry +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\return the media type four character code of the given sample description*/ +u32 gf_isom_get_media_subtype(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets the composition time (media time) given the absolute time in the Movie +\param isom_file the target ISO file +\param trackNumber the target track +\param movieTime desired time in movie timescale +\param mediaTime is set to 0 if the media is not playing at that time (empty time segment) +\return error if any*/ +GF_Err gf_isom_get_media_time(GF_ISOFile *isom_file, u32 trackNumber, u32 movieTime, u64 *mediaTime); + +/*! gets the number of sample descriptions in the media - a media can have several stream descriptions (eg different codec configurations, different protetcions, different visual sizes). +\param isom_file the target ISO file +\param trackNumber the target track +\return the number of sample descriptions +*/ +u32 gf_isom_get_sample_description_count(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the stream description index for a given time in media time +\param isom_file the target ISO file +\param trackNumber the target track +\param for_time the desired time in media timescale +\return the sample description index, or 0 if error or if empty*/ +u32 gf_isom_get_sample_description_index(GF_ISOFile *isom_file, u32 trackNumber, u64 for_time); + +/*! checks if a sample stream descritpion is self-contained (samples located in the file) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\return GF_TRUE if samples refering to the given stream description are present in the file, GF_FALSE otherwise*/ +Bool gf_isom_is_self_contained(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets the media duration (without edit) based on sample table +\param isom_file the target ISO file +\param trackNumber the target track +\return the media duration, 0 if no samples +*/ +u64 gf_isom_get_media_duration(GF_ISOFile *isom_file, u32 trackNumber); -/*Check a DataReference of this track (index >= 1) -A Data Reference allows to construct a file without integrating the media data*/ -GF_Err gf_isom_check_data_reference(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex); +/*! gets the original media duration (without edit) as indicated in the file +\param isom_file the target ISO file +\param trackNumber the target track +\return the media duration +*/ +u64 gf_isom_get_media_original_duration(GF_ISOFile *isom_file, u32 trackNumber); -/*get the location of the data. If URL && URN are NULL, the data is in this file -both strings are const: don't free them.*/ -GF_Err gf_isom_get_data_reference(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, const char **outURL, const char **outURN); +/*! gets the media timescale +\param isom_file the target ISO file +\param trackNumber the target track +\return the timeScale of the media +*/ +u32 gf_isom_get_media_timescale(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets media chunking information for non-fragmented files +\param isom_file the target ISO file +\param trackNumber the target track +\param dur_min set to minimum chunk duration in media timescale (optional, can be NULL) +\param dur_avg set to average chunk duration in media timescale (optional, can be NULL) +\param dur_max set to maximum chunk duration in media timescale (optional, can be NULL) +\param size_min set to smallest chunk size in bytes (optional, can be NULL) +\param size_avg set to average chunk size in bytes (optional, can be NULL) +\param size_max set to largest chunk size in bytes (optional, can be NULL) +\return error if any +*/ +GF_Err gf_isom_get_chunks_infos(GF_ISOFile *isom_file, u32 trackNumber, u32 *dur_min, u32 *dur_avg, u32 *dur_max, u32 *size_min, u32 *size_avg, u32 *size_max); -/*Get the number of samples - return 0 if error*/ -u32 gf_isom_get_sample_count(GF_ISOFile *the_file, u32 trackNumber); +/*! gets the handler name. The outName must be: +\param isom_file the target ISO file +\param trackNumber the target track +\param outName set to the handler name (must be non NULL) +\return error if any +*/ +GF_Err gf_isom_get_handler_name(GF_ISOFile *isom_file, u32 trackNumber, const char **outName); + +/*! checks if the data reference for the given track and sample description is valid and supported +(a data Reference allows to construct a file without integrating the media data, however this library only handles local storage external data references) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\return error if any +*/ +GF_Err gf_isom_check_data_reference(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets the location of the data. If both outURL and outURN are set to NULL, the data is in this file +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\param outURL set to URL value of the data reference +\param outURN set to URN value of the data reference +\return error if any +*/ +GF_Err gf_isom_get_data_reference(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, const char **outURL, const char **outURN); + +/*! gets the number of samples in a track +\param isom_file the target ISO file +\param trackNumber the target track +\return the number of samples, or 0 if error*/ +u32 gf_isom_get_sample_count(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the constant sample size for samples of a track +\param isom_file the target ISO file +\param trackNumber the target track +\return constant size of samples or 0 if size not constant +*/ +u32 gf_isom_get_constant_sample_size(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the constant sample duration for samples of a track +\param isom_file the target ISO file +\param trackNumber the target track +\return constant duration of samples, or 0 if duration not constant*/ +u32 gf_isom_get_constant_sample_duration(GF_ISOFile *isom_file, u32 trackNumber); + +/*! sets max audio sample packing in a single ISOSample. +This is mostly used when processing raw audio tracks, for which extracting samples per samples would be too time consuming +\param isom_file the target ISO file +\param trackNumber the target track +\param pack_num_samples the target number of samples to pack in one ISOSample +\return GF_TRUE if packing was successfull, GF_FALSE otherwise (non constant size and non constant duration) +*/ +Bool gf_isom_enable_raw_pack(GF_ISOFile *isom_file, u32 trackNumber, u32 pack_num_samples); -/*Get constant sample size, or 0 if size not constant*/ -u32 gf_isom_get_constant_sample_size(GF_ISOFile *the_file, u32 trackNumber); -/*returns total amount of media bytes in track*/ -u64 gf_isom_get_media_data_size(GF_ISOFile *the_file, u32 trackNumber); +/*! gets the total media data size of a track (whether in the file or not) +\param isom_file the target ISO file +\param trackNumber the target track +\return total amount of media bytes in track +*/ +u64 gf_isom_get_media_data_size(GF_ISOFile *isom_file, u32 trackNumber); -/*It may be desired to fetch samples with a bigger allocated buffer than their real size, in case the decoder +/*! sets sample padding bytes when reading a sample +It may be desired to fetch samples with a bigger allocated buffer than their real size, in case the decoder reads more data than available. This sets the amount of extra bytes to allocate when reading samples from this track -NOTE: the dataLength of the sample does NOT include padding*/ -GF_Err gf_isom_set_sample_padding(GF_ISOFile *the_file, u32 trackNumber, u32 padding_bytes); - -/*return a sample given its number, and set the StreamDescIndex of this sample -this index allows to retrieve the stream description if needed (2 media in 1 track) -return NULL if error*/ -GF_ISOSample *gf_isom_get_sample(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber, u32 *StreamDescriptionIndex); - -/*same as gf_isom_get_sample but doesn't fetch media data -@StreamDescriptionIndex (optional): set to stream description index -@data_offset (optional): set to sample start offset in file. - - NOTE: when both StreamDescriptionIndex and data_offset are NULL, only DTS, CTS_Offset and RAP indications are -retrieved (faster) +NOTE: the dataLength of the sample does NOT include padding +\param isom_file the target ISO file +\param trackNumber the target track +\param padding_bytes the amount of bytes to add at the end of a sample data buffer +\return error if any +*/ +GF_Err gf_isom_set_sample_padding(GF_ISOFile *isom_file, u32 trackNumber, u32 padding_bytes); + +/*! fetches a sample from a track. The sample must be destroyed using \ref gf_isom_sample_del +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\param sampleDescriptionIndex set to the sample description index corresponding to this sample +\return the ISO sample or NULL if not found or end of stream or incomplete file. Use \ref gf_isom_last_error to check the error code */ -GF_ISOSample *gf_isom_get_sample_info(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber, u32 *StreamDescriptionIndex, u64 *data_offset); +GF_ISOSample *gf_isom_get_sample(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 *sampleDescriptionIndex); + +/*! fetches a sample from a track without allocating a new sample. +This function is the same as \ref gf_isom_get_sample except that it fills in the static_sample passed as argument, potentially reallocating buffers + +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\param sampleDescriptionIndex set to the sample description index corresponding to this sample +\param static_sample a caller-allocated ISO sample to use as the returned sample +\param data_offset set to data offset in file / current bitstream - may be NULL +\return the ISO sample or NULL if not found or end of stream or incomplete file. Use \ref gf_isom_last_error to check the error code +\note If the function returns NULL, the static_sample and its associated data are NOT destroyed +*/ +GF_ISOSample *gf_isom_get_sample_ex(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 *sampleDescriptionIndex, GF_ISOSample *static_sample, u64 *data_offset); -/*retrieves given sample DTS*/ -u64 gf_isom_get_sample_dts(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber); +/*! gets sample information. This is the same as \ref gf_isom_get_sample but doesn't fetch media data -/*returns sample duration in media timeScale*/ -u32 gf_isom_get_sample_duration(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber); +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\param sampleDescriptionIndex set to the sample description index corresponding to this sample (optional, can be NULL) +\param data_offset set to the sample start offset in file (optional, can be NULL) +\note: when both sampleDescriptionIndex and data_offset are NULL, only DTS, CTS_Offset and RAP indications are retrieved (faster) +\return the ISO sample without data or NULL if not found or end of stream or incomplete file. Use \ref gf_isom_last_error to check the error code +*/ +GF_ISOSample *gf_isom_get_sample_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 *sampleDescriptionIndex, u64 *data_offset); + +/*! gets sample information with a user-allocated sample. This is the same as \ref gf_isom_get_sample_info but uses a static allocated sample as input +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\param sampleDescriptionIndex set to the sample description index corresponding to this sample (optional, can be NULL) +\param data_offset set to the sample start offset in file (optional, can be NULL) +\note: when both sampleDescriptionIndex and data_offset are NULL, only DTS, CTS_Offset and RAP indications are retrieved (faster) +\param static_sample a caller-allocated ISO sample to use as the returned sample +\return the ISO sample without data or NULL if not found or end of stream or incomplete file. Use \ref gf_isom_last_error to check the error code +\note If the function returns NULL, the static_sample and its associated data if any are NOT destroyed +*/ +GF_ISOSample *gf_isom_get_sample_info_ex(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 *sampleDescriptionIndex, u64 *data_offset, GF_ISOSample *static_sample); -/*returns sample size in bytes*/ -u32 gf_isom_get_sample_size(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber); +/*! get sample decoding time +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\return decoding time in media timescale +*/ +u64 gf_isom_get_sample_dts(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber); + +/*! gets sample duration +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\return sample duration in media timescale*/ +u32 gf_isom_get_sample_duration(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber); + +/*! gets sample size +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\return sample size in bytes*/ +u32 gf_isom_get_sample_size(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber); + +/*! gets maximum sample size in track +\param isom_file the target ISO file +\param trackNumber the target track +\return max size of any sample in track*/ +u32 gf_isom_get_max_sample_size(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets average sample size in a track +\param isom_file the target ISO file +\param trackNumber the target track +\return average size of sample in track +*/ +u32 gf_isom_get_avg_sample_size(GF_ISOFile *isom_file, u32 trackNumber); -/*returns sync flag of sample*/ -u8 gf_isom_get_sample_sync(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber); +/*! gets maximum sample duration in track +\param isom_file the target ISO file +\param trackNumber the target track +\return max sample delta in media timescale +*/ +u32 gf_isom_get_max_sample_delta(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets max sample CTS offset (CTS-DTS) in track +\param isom_file the target ISO file +\param trackNumber the target track +\return max sample cts offset in media timescale*/ +u32 gf_isom_get_max_sample_cts_offset(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets sample sync flag. This does not check other sample groups ('rap ' or 'sap ') +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\return GF_TRUE if sample is sync, GF_FALSE otherwise*/ +Bool gf_isom_get_sample_sync(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber); + +/*! gets sample dependency flags - see ISO/IEC 14496-12 and \ref gf_filter_pck_set_dependency_flags +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\param is_leading set to 1 if sample is a leading picture +\param dependsOn set to the depends_on flag +\param dependedOn set to the depended_on flag +\param redundant set to the redundant flag +\return error if any +*/ +GF_Err gf_isom_get_sample_flags(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 *is_leading, u32 *dependsOn, u32 *dependedOn, u32 *redundant); -GF_Err gf_isom_get_sample_flags(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber, u32 *is_leading, u32 *dependsOn, u32 *dependedOn, u32 *redundant); +/*! gets a sample given a desired decoding time and set the sampleDescriptionIndex of this sample -/*gets a sample given a desired decoding time IN MEDIA TIME SCALE -and set the StreamDescIndex of this sample -this index allows to retrieve the stream description if needed (2 media in 1 track) -return GF_EOS if the desired time exceeds the media duration WARNING: the sample may not be sync even though the sync was requested (depends on the media and the editList) -the SampleNum is optional. If non-NULL, will contain the sampleNumber*/ -GF_Err gf_isom_get_sample_for_media_time(GF_ISOFile *the_file, u32 trackNumber, u64 desiredTime, u32 *StreamDescriptionIndex, u8 SearchMode, GF_ISOSample **sample, u32 *SampleNum); - -/*retrieves given sample DTS*/ -u32 gf_isom_get_sample_from_dts(GF_ISOFile *the_file, u32 trackNumber, u64 dts); +the SampleNum is optional. If non-NULL, will contain the sampleNumber + +\param isom_file the target ISO file +\param trackNumber the target track +\param desiredTime the desired time in media timescale +\param sampleDescriptionIndex set to the sample description index corresponding to this sample (optional, can be NULL) +\param SearchMode the search direction mode +\param sample set to the fetched sample if any. If NULL, sample is not fetched (optional, can be NULL) +\param sample_number set to the fetched sample number if any, set to 0 otherwise (optional, can be NULL) +\param data_offset set to the sample start offset in file (optional, can be NULL) + +\return GF_EOS if the desired time exceeds the media duration or error if any +*/ +GF_Err gf_isom_get_sample_for_media_time(GF_ISOFile *isom_file, u32 trackNumber, u64 desiredTime, u32 *sampleDescriptionIndex, GF_ISOSearchMode SearchMode, GF_ISOSample **sample, u32 *sample_number, u64 *data_offset); -/*get the current tfdt of the track - this can be used to adjust sample time queries when edit list are used*/ -u64 gf_isom_get_current_tfdt(GF_ISOFile *the_file, u32 trackNumber); +/*! gets sample number for a given decode time +\param isom_file the target ISO file +\param trackNumber the target track +\param dts the desired decode time in media timescale +\return the sample number or 0 if not found +*/ +u32 gf_isom_get_sample_from_dts(GF_ISOFile *isom_file, u32 trackNumber, u64 dts); + +/*! get the number of track references of a track for a given ReferenceType +\param isom_file the target ISO file +\param trackNumber the target track +\param referenceType the four character code of the reference to query +\return -1 if error or the number of references*/ +s32 gf_isom_get_reference_count(GF_ISOFile *isom_file, u32 trackNumber, u32 referenceType); + +/*! get the referenced track number for a track and a given ReferenceType and Index +\param isom_file the target ISO file +\param trackNumber the target track +\param referenceType the four character code of the reference to query +\param referenceIndex the 1-based index of the reference to query (see \ref gf_isom_get_reference_count) +\param refTrack set to the track number of the referenced track +\return error if any +*/ +GF_Err gf_isom_get_reference(GF_ISOFile *isom_file, u32 trackNumber, u32 referenceType, u32 referenceIndex, u32 *refTrack); + +/*! get the referenced track ID for a track and a given ReferenceType and Index +\param isom_file the target ISO file +\param trackNumber the target track +\param referenceType the four character code of the reference to query +\param referenceIndex the 1-based index of the reference to query (see \ref gf_isom_get_reference_count) +\param refTrackID set to the track ID of the referenced track +\return error if any +*/ +GF_Err gf_isom_get_reference_ID(GF_ISOFile *isom_file, u32 trackNumber, u32 referenceType, u32 referenceIndex, GF_ISOTrackID *refTrackID); -/*Track Edition functions*/ +/*! checks if a track has a reference of given type to another track +\param isom_file the target ISO file +\param trackNumber the target track +\param referenceType the four character code of the reference to query +\param refTrackID set to the track number of the referenced track +\return the reference index if the given track has a reference of type referenceType to refTreckID, 0 otherwise*/ +u32 gf_isom_has_track_reference(GF_ISOFile *isom_file, u32 trackNumber, u32 referenceType, GF_ISOTrackID refTrackID); -/*return a sample given a desired time in the movie. MovieTime is IN MEDIA TIME SCALE , handles edit list. -and set the StreamDescIndex of this sample -this index allows to retrieve the stream description if needed (2 media in 1 track) -sample must be set to NULL before calling. +/*! fetches a sample for a given movie time, handling possible track edit lists. -result Sample is NULL if an error occured -if no sample is playing, an empty sample is returned with no data and a DTS set to MovieTime when serching in sync modes -if no sample is playing, the closest sample in the edit time-line is returned when serching in regular modes +if no sample is playing, an empty sample is returned with no data and a DTS set to MovieTime when searching in sync modes +if no sample is playing, the closest sample in the edit time-line is returned when searching in regular modes WARNING: the sample may not be sync even though the sync was requested (depends on the media and the editList) @@ -702,667 +1138,2742 @@ Note: this function will handle re-timestamping the sample according to the mapp on the track time-line. The sample TSs (DTS / CTS offset) are expressed in MEDIA TIME SCALE (to match the media stream TS resolution as indicated in media header / SLConfig) -sampleNumber is optional and gives the number of the sample in the media -*/ -GF_Err gf_isom_get_sample_for_movie_time(GF_ISOFile *the_file, u32 trackNumber, u64 movieTime, u32 *StreamDescriptionIndex, u8 SearchMode, GF_ISOSample **sample, u32 *sampleNumber); - -/*return 1 if true edit list, 0 if no edit list or if time-shifting only edit list, in which case mediaOffset is set to the DTS offset value (e.g., your app should add mediaOffset to all sample DTS)*/ -Bool gf_isom_get_edit_list_type(GF_ISOFile *the_file, u32 trackNumber, s64 *mediaOffset); -/*get the number of edited segment*/ -u32 gf_isom_get_edit_segment_count(GF_ISOFile *the_file, u32 trackNumber); - -/*Get the desired segment information*/ -GF_Err gf_isom_get_edit_segment(GF_ISOFile *the_file, u32 trackNumber, u32 SegmentIndex, u64 *EditTime, u64 *SegmentDuration, u64 *MediaTime, u8 *EditMode); +\param isom_file the target ISO file +\param trackNumber the target track +\param movieTime the desired movie time in media timescale +\param sampleDescriptionIndex set to the sample description index corresponding to this sample (optional, can be NULL) +\param SearchMode the search direction mode +\param sample set to the fetched sample if any. If NULL, sample is not fetched (optional, can be NULL) +\param sample_number set to the fetched sample number if any, set to 0 otherwise (optional, can be NULL) +\param data_offset set to the sample start offset in file (optional, can be NULL) +\return error if any +*/ +GF_Err gf_isom_get_sample_for_movie_time(GF_ISOFile *isom_file, u32 trackNumber, u64 movieTime, u32 *sampleDescriptionIndex, GF_ISOSearchMode SearchMode, GF_ISOSample **sample, u32 *sample_number, u64 *data_offset); + +/*! gets edit list type +\param isom_file the target ISO file +\param trackNumber the target track +\param mediaOffset set to the media offset of the edit for time-shifting edits +\return GF_TRUE if complex edit list, GF_FALSE if no edit list or time-shifting only edit list, in which case mediaOffset is set to the CTS of the first sample to present at presentation time 0 +A negative value implies that the samples with CTS between 0 and mediaOffset should not be presented (skip) +A positive value value implies that there is nothing to present between 0 and CTS (hold) +*/ +Bool gf_isom_get_edit_list_type(GF_ISOFile *isom_file, u32 trackNumber, s64 *mediaOffset); -/*get the number of languages for the copyright*/ -u32 gf_isom_get_copyright_count(GF_ISOFile *the_file); -/*get the copyright and its language code given the index*/ -GF_Err gf_isom_get_copyright(GF_ISOFile *the_file, u32 Index, const char **threeCharCodes, const char **notice); -/*get the opaque watermark info if any - returns GF_NOT_SUPPORTED if not present*/ -GF_Err gf_isom_get_watermark(GF_ISOFile *the_file, bin128 UUID, u8** data, u32* length); +/*! gets the number of edits in an edit list +\param isom_file the target ISO file +\param trackNumber the target track +\return number of edits +*/ +u32 gf_isom_get_edits_count(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the desired edit information +\param isom_file the target ISO file +\param trackNumber the target track +\param EditIndex index of the edit to query (1-based index) +\param EditTime set to the edit time in movie timescale +\param SegmentDuration set to the edit duration in movie timescale +\param MediaTime set to the edit media start time in media timescale +\param EditMode set to the mode of the edit +\return error if any +*/ +GF_Err gf_isom_get_edit(GF_ISOFile *isom_file, u32 trackNumber, u32 EditIndex, u64 *EditTime, u64 *SegmentDuration, u64 *MediaTime, GF_ISOEditType *EditMode); + +/*! gets the number of languages for the copyright +\param isom_file the target ISO file +\return number of languages, 0 if no copyright*/ +u32 gf_isom_get_copyright_count(GF_ISOFile *isom_file); + +/*! gets a copyright and its language code +\param isom_file the target ISO file +\param Index the 1-based index of the copyright notice to query +\param threeCharCodes set to the copyright language code +\param notice set to the copyright notice +\return error if any +*/ +GF_Err gf_isom_get_copyright(GF_ISOFile *isom_file, u32 Index, const char **threeCharCodes, const char **notice); -/*get the number of chapter for movie or track if trackNumber !=0*/ -u32 gf_isom_get_chapter_count(GF_ISOFile *the_file, u32 trackNumber); -/*get the given movie or track (trackNumber!=0) chapter time and name - index is 1-based -@chapter_time: retrives start time in milliseconds - may be NULL. -@name: retrieves chapter name - may be NULL - SHALL NOT be destroyed by user +/*! gets the number of chapter for movie or track (chapters can be assigned to tracks or to movies) +\param isom_file the target ISO file +\param trackNumber the target track to queryy. If 0, looks for chapter at the movie level +\return number of chapters */ -GF_Err gf_isom_get_chapter(GF_ISOFile *the_file, u32 trackNumber, u32 Index, u64 *chapter_time, const char **name); +u32 gf_isom_get_chapter_count(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the given chapter time and name for a movie or track +\param isom_file the target ISO file +\param trackNumber the target track to queryy. If 0, looks for chapter at the movie level +\param Index the index of the ckhapter to queryy +\param chapter_time set to chapter start time in milliseconds (optional, may be NULL) +\param name set to chapter name (optional, may be NULL) +\return error if any +*/ +GF_Err gf_isom_get_chapter(GF_ISOFile *isom_file, u32 trackNumber, u32 Index, u64 *chapter_time, const char **name); -/* -return 0 if the media has no sync point info (eg, all samples are RAPs) -return 1 if the media has sync points (eg some samples are RAPs) -return 2 if the media has empty sync point info (eg no samples are RAPs). This will likely only happen - in scalable context +/*! checks if a media has sync points +\param isom_file the target ISO file +\param trackNumber the target track +\return 0 if the media has no sync point info (eg, all samples are RAPs), 1 if the media has sync points (eg some samples are RAPs), 2 if the media has empty sync point info (no samples are RAPs - this will likely only happen + in scalable context) +*/ +u8 gf_isom_has_sync_points(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the number of sync points in a media +\param isom_file the target ISO file +\param trackNumber the target track +\return number of sync points*/ +u32 gf_isom_get_sync_point_count(GF_ISOFile *isom_file, u32 trackNumber); + +/*! checks if a media track hhas composition time offset +\param isom_file the target ISO file +\param trackNumber the target track +\return 1 if the track uses unsigned compositionTime offsets (B-frames or similar), 2 if the track uses signed compositionTime offsets (B-frames or similar), 0 if the track does not use compositionTime offsets (CTS == DTS) +*/ +u32 gf_isom_has_time_offset(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets cts to dts shift value if defined. +This shift is defined only in cases of negative CTS offset (ctts version 1) and not always present in files! +Adding shift to CTS guarantees that the shifted CTS is always greater than the DTS for any sample +\param isom_file the target ISO file +\param trackNumber the target track +\return the shift from composition time to decode time for that track if indicated, or 0 if not found */ -u8 gf_isom_has_sync_points(GF_ISOFile *the_file, u32 trackNumber); +s64 gf_isom_get_cts_to_dts_shift(GF_ISOFile *isom_file, u32 trackNumber); + +/*! checks if a track has sync shadow samples (RAP samples replacing non RAP ones) +\param isom_file the target ISO file +\param trackNumber the target track +\return GF_TRUE if the track has sync shadow samples*/ +Bool gf_isom_has_sync_shadows(GF_ISOFile *isom_file, u32 trackNumber); + +/*! checks if a track has sample dependencoes indicated +\param isom_file the target ISO file +\param trackNumber the target track +\return GF_TRUE if the track has sample dep indications*/ +Bool gf_isom_has_sample_dependency(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets a rough estimation of file size. This only works for completely self-contained files and without fragmentation +for the current time +\param isom_file the target ISO file +\return estimated file size in bytes*/ +u64 gf_isom_estimate_size(GF_ISOFile *isom_file); + +/*! gets next alternate group ID available +\param isom_file the target ISO file +\return next available ID for alternate groups +*/ +u32 gf_isom_get_next_alternate_group_id(GF_ISOFile *isom_file); -/*returns number of sync points*/ -u32 gf_isom_get_sync_point_count(GF_ISOFile *the_file, u32 trackNumber); -/* -returns 1 if the track uses unsigned compositionTime offsets (B-frames or similar) -returns 2 if the track uses signed compositionTime offsets (B-frames or similar) -returns 0 if the track does not use compositionTime offsets (CTS == DTS) -*/ -u32 gf_isom_has_time_offset(GF_ISOFile *the_file, u32 trackNumber); +/*! gets file name of an opened ISO file +\param isom_file the target ISO file +\return the file name*/ +const char *gf_isom_get_filename(GF_ISOFile *isom_file); -/*returns the shift from composition time to decode time for that track if indicated, or 0 if not found -adding shift to CTS guarantees that the shifted CTS is always greater than the DTS for any sample*/ -s64 gf_isom_get_cts_to_dts_shift(GF_ISOFile *the_file, u32 trackNumber); -/*returns 1 if the track has sync shadow samples*/ -Bool gf_isom_has_sync_shadows(GF_ISOFile *the_file, u32 trackNumber); +/*! gets file brand information +The brand is used to +- differenciate MP4, MJPEG2000 and QT while indicating compatibilities +- identify tools that shall be supported for correct parsing of the file -/*returns 1 if the track has sample dep indications*/ -Bool gf_isom_has_sample_dependency(GF_ISOFile *the_file, u32 trackNumber); +The function will set brand, minorVersion and AlternateBrandsCount to 0 if no brand indication is found in the file -/*rough estimation of file size, only works for completely self-contained files and without fragmentation -for the current time*/ -u64 gf_isom_estimate_size(GF_ISOFile *the_file); +\param isom_file the target ISO file +\param brand set to the four character code of the brand +\param minorVersion set to an informative integer for the minor version of the major brand (optional, can be NULL) +\param AlternateBrandsCount set to the number of compatible brands (optional, can be NULL). +\return error if any*/ +GF_Err gf_isom_get_brand_info(GF_ISOFile *isom_file, u32 *brand, u32 *minorVersion, u32 *AlternateBrandsCount); -u32 gf_isom_get_next_alternate_group_id(GF_ISOFile *movie); +/*! gets an alternate brand indication +\note the Major brand should always be indicated in the alternate brands +\param isom_file the target ISO file +\param BrandIndex 1-based index of alternate brand to query (cf \ref gf_isom_get_brand_info) +\param brand set to the four character code of the brand +\return error if any*/ +GF_Err gf_isom_get_alternate_brand(GF_ISOFile *isom_file, u32 BrandIndex, u32 *brand); +/*! gets the internal list of brands +\param isom_file the target ISO file +\return the internal list of brands. DO NOT MODIFY the content +*/ +const u32 *gf_isom_get_brands(GF_ISOFile *isom_file); + +/*! gets the number of padding bits at the end of a given sample if any +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the desired sample number (1-based index) +\param NbBits set to the number of padded bits at the end of the sample +\return error if any*/ +GF_Err gf_isom_get_sample_padding_bits(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u8 *NbBits); + +/*! checks if a track samples use padding bits or not +\param isom_file the target ISO file +\param trackNumber the target track +\return GF_TRUE if samples have padding bits information, GF_FALSE otherwise*/ +Bool gf_isom_has_padding_bits(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets information of a visual track for a given sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\param Width set to the width of the sample description in pixels +\param Height set to the height of the sample description in pixels +\return error if any*/ +GF_Err gf_isom_get_visual_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *Width, u32 *Height); + +/*! gets bit depth of a sample description of a visual track (for uncompressed media usually) +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param bitDepth the bit depth of each pixel (eg 24 for RGB, 32 for RGBA) +\return error if any +*/ +GF_Err gf_isom_get_visual_bit_depth(GF_ISOFile* isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u16 *bitDepth); + +/*! gets information of an audio track for a given sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\param SampleRate set to the audio sample rate of the sample description +\param Channels set to the audio channel count of the sample description +\param bitsPerSample set to the audio bits per sample for raw audio of the sample description +\return error if any*/ +GF_Err gf_isom_get_audio_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *SampleRate, u32 *Channels, u32 *bitsPerSample); + +/*! Audio channel layout description, ISOBMFF style*/ +typedef struct +{ + /*! stream structure flags, 1: has channel layout, 2: has objects*/ + u8 stream_structure; + + /*! defined CICP channel layout*/ + u8 definedLayout; + + /*! number of channels*/ + u32 channels_count; + struct { + /*! speaker position*/ + u8 position; + /*! speaker elevation if position==126*/ + s8 elevation; + /*! speaker azimuth if position==126*/ + s16 azimuth; + } layouts[64]; + /*! bit-map of omitted channels using bit positions defined in CICP - only valid if definedLayout is not 0*/ + u64 omittedChannelsMap; + /*! number of objects in the stream*/ + u8 object_count; +} GF_AudioChannelLayout; + +/*! get channel layout info for an audio track, ISOBMFF style + \param isom_file the target ISO file + \param trackNumber the target track + \param sampleDescriptionIndex the target sample description index (1-based) + \param layout set to the channel/object layout info for this track + \return GF_NOT_FOUND if not set in file, or other error if any*/ +GF_Err gf_isom_get_audio_layout(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_AudioChannelLayout *layout); + + +/*! gets visual track layout information +\param isom_file the target ISO file +\param trackNumber the target track +\param width set to the width of the track in pixels +\param height set to the height of the track in pixels +\param translation_x set to the horizontal translation of the track in pixels +\param translation_y set to the vertical translation of the track in pixels +\param layer set to the z-order of the track +\return error if any*/ +GF_Err gf_isom_get_track_layout_info(GF_ISOFile *isom_file, u32 trackNumber, u32 *width, u32 *height, s32 *translation_x, s32 *translation_y, s16 *layer); + +/*! gets matrix of a visual track +\param isom_file the target ISO file +\param trackNumber the target track +\param matrix set to the track matrix - all coord values are expressed as 16.16 fixed point floats +\return error if any*/ +GF_Err gf_isom_get_track_matrix(GF_ISOFile *isom_file, u32 trackNumber, u32 matrix[9]); + +/*! gets sample (pixel) aspect ratio information of a visual track for a given sample description +The aspect ratio is hSpacing divided by vSpacing +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\param hSpacing horizontal spacing +\param vSpacing vertical spacing +\return error if any*/ +GF_Err gf_isom_get_pixel_aspect_ratio(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *hSpacing, u32 *vSpacing); + +/*! gets color information of a visual track for a given sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\param colour_type set to the four character code of the colour type mode used (nclx, nclc, prof or ricc currently defined) +\param colour_primaries set to the colour primaries for nclc/nclx as defined in ISO/IEC 23001-8 +\param transfer_characteristics set to the colour primaries for nclc/nclx as defined in ISO/IEC 23001-8 +\param matrix_coefficients set to the colour primaries for nclc/nclx as defined in ISO/IEC 23001-8 +\param full_range_flag set to the colour primaries for nclc as defined in ISO/IEC 23001-8 +\return error if any*/ +GF_Err gf_isom_get_color_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *colour_type, u16 *colour_primaries, u16 *transfer_characteristics, u16 *matrix_coefficients, Bool *full_range_flag); + +/*! gets the media language code of a track +\param isom_file the target ISO file +\param trackNumber the target track +\param lang set to a newly allocated string containg 3 chars (if old files) or longer form (BCP-47) - shall be freed by caller +\return error if any*/ +GF_Err gf_isom_get_media_language(GF_ISOFile *isom_file, u32 trackNumber, char **lang); + +/*! gets the number of kind (media role) for a given track +\param isom_file the target ISO file +\param trackNumber the target track +\return number of kind defined +*/ +u32 gf_isom_get_track_kind_count(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets a given kind (media role) information for a given track +\param isom_file the target ISO file +\param trackNumber the target track +\param index the 1-based index of the kind to retrieve +\param scheme set to the scheme of the kind information - shall be freed by caller +\param value set to the value of the kind information - shall be freed by caller +\return error if any*/ +GF_Err gf_isom_get_track_kind(GF_ISOFile *isom_file, u32 trackNumber, u32 index, char **scheme, char **value); + +/*! gets the magic number associated with a track. The magic number is usually set by a file muxer, and is not serialized to file +\param isom_file the target ISO file +\param trackNumber the target track +\return the magic number (0 by default) +*/ +u64 gf_isom_get_track_magic(GF_ISOFile *isom_file, u32 trackNumber); -/* - MPEG-4 Systems extensions +/*! checks if file is a single AV file with max one audio, one video, one text and basic od/bifs +\param isom_file the target ISO file +\return GF_TRUE if file is single AV, GF_FALSE otherwise */ +Bool gf_isom_is_single_av(GF_ISOFile *isom_file); -/*check if files has root OD/IOD or not*/ -Bool gf_isom_has_root_od(GF_ISOFile *the_file); +/*! guesses which specification this file refers to. +\param isom_file the target ISO file +\return possible values are: + GF_ISOM_BRAND_ISOM: unrecognized std + GF_ISOM_BRAND_3GP5: 3GP file (max 1 audio, 1 video) without text track + GF_ISOM_BRAND_3GP6: 3GP file (max 1 audio, 1 video) with text track + GF_ISOM_BRAND_3GG6: 3GP file multitrack file + GF_ISOM_BRAND_3G2A: 3GP2 file + GF_ISOM_BRAND_AVC1: AVC file + FCC("ISMA"): ISMA file (may overlap with 3GP) + GF_ISOM_BRAND_MP42: any generic MP4 file (eg with BIFS/OD/MPEG-4 systems stuff) -/*return the root Object descriptor of the movie (can be NULL, OD or IOD, you have to check its tag) -YOU HAVE TO DELETE THE DESCRIPTOR + for files without movie, returns the file meta handler type */ -GF_Descriptor *gf_isom_get_root_od(GF_ISOFile *the_file); - -/*check the presence of a track in IOD. 0: NO, 1: YES, 2: ERROR*/ -u8 gf_isom_is_track_in_root_od(GF_ISOFile *the_file, u32 trackNumber); +u32 gf_isom_guess_specification(GF_ISOFile *isom_file); + + +/*! gets the nalu_length_field size used for this sample description if NALU-based (AVC/HEVC/...) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\return number of bytes used to code the NALU size, or 0 if not NALU-based*/ +u32 gf_isom_get_nalu_length_field(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets max/average rate information as indicated in ESDS or BTRT boxes. If not found all values are set to 0 +if sampleDescriptionIndex is 0, gather for all sample descriptions + +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\param average_bitrate set to the average bitrate in bits per second of the media +\param max_bitrate set to the maximum bitrate in bits per second of the media +\param decode_buffer_size set to the decoder buffer size in bytes of the media +\return error if any*/ +GF_Err gf_isom_get_bitrate(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *average_bitrate, u32 *max_bitrate, u32 *decode_buffer_size); + + +/*! gets the track template of a track. This serializes track box without serializing sample tables nor sample description info +\param isom_file the destination ISO file +\param trackNumber the destination track +\param output will be set to a newly allocated buffer containing the serialized box - caller shall free it +\param output_size will be set to the size of the allocated buffer +\return error if any +*/ +GF_Err gf_isom_get_track_template(GF_ISOFile *isom_file, u32 trackNumber, u8 **output, u32 *output_size); + +/*! gets the trex template of a track. This serializes trex box +\param isom_file the destination ISO file +\param trackNumber the destination track +\param output will be set to a newly allocated buffer containing the serialized box - caller shall free it +\param output_size will be set to the size of the allocated buffer +\return error if any +*/ +GF_Err gf_isom_get_trex_template(GF_ISOFile *isom_file, u32 trackNumber, u8 **output, u32 *output_size); -/*Get the GF_ESD given the StreamDescriptionIndex - YOU HAVE TO DELETE THE DESCRIPTOR*/ -GF_ESD *gf_isom_get_esd(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex); +/*! sets the number of removed bytes form the input bitstream when using gmem:// url + The number of bytes shall be the total number since the opening of the movie +\param isom_file the target ISO file +\param bytes_removed number of bytes removed +\return error if any +*/ +GF_Err gf_isom_set_removed_bytes(GF_ISOFile *isom_file, u64 bytes_removed); -/*Get the decoderConfigDescriptor given the StreamDescriptionIndex - YOU HAVE TO DELETE THE DESCRIPTOR*/ -GF_DecoderConfig *gf_isom_get_decoder_config(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex); +/*! gets the current file offset of the current incomplete top level box not parsed + This shall be checked to avoid discarding bytes at or after the current top box header +\param isom_file the target ISO file +\param current_top_box_offset set to the offset from file first byte +\return error if any +*/ +GF_Err gf_isom_get_current_top_box_offset(GF_ISOFile *isom_file, u64 *current_top_box_offset); + +/*! purges the given number of samples, starting from the first sample, from a track of a fragmented file. + This avoids having sample tables growing in size when reading a fragmented file in pure streaming mode (no seek). + You should always keep one sample in the track +\param isom_file the target ISO file +\param trackNumber the desired track to purge +\param nb_samples the number of samples to remove +\return error if any +*/ +GF_Err gf_isom_purge_samples(GF_ISOFile *isom_file, u32 trackNumber, u32 nb_samples); -/*sets default TrackID (or ES_ID) for clock references. If trackNumber is 0, default sync track ID is reseted -and will be reassigned at next ESD fetch*/ -void gf_isom_set_default_sync_track(GF_ISOFile *file, u32 trackNumber); -/*Return the number of track references of a track for a given ReferenceType - return -1 if error*/ -s32 gf_isom_get_reference_count(GF_ISOFile *the_file, u32 trackNumber, u32 referenceType); +#ifndef GPAC_DISABLE_ISOM_DUMP -/*Return the referenced track number for a track and a given ReferenceType and Index -return -1 if error, 0 if the reference is a NULL one, or the trackNumber +/*! dumps file structures into XML trace file +\param isom_file the target ISO file +\param trace the file object to dump to +\param skip_init does not dump init segment structure +\param skip_samples does not dump sample tables +\return error if any */ -GF_Err gf_isom_get_reference(GF_ISOFile *the_file, u32 trackNumber, u32 referenceType, u32 referenceIndex, u32 *refTrack); +GF_Err gf_isom_dump(GF_ISOFile *isom_file, FILE *trace, Bool skip_init, Bool skip_samples); -/*Return the referenced track ID for a track and a given ReferenceType and Index -return -1 if error, 0 if the reference is a NULL one, or the trackNumber -*/ -GF_Err gf_isom_get_reference_ID(GF_ISOFile *the_file, u32 trackNumber, u32 referenceType, u32 referenceIndex, u32 *refTrackID); +#endif /*GPAC_DISABLE_ISOM_DUMP*/ -/*Return referenceIndex if the given track has a reference to the given TreckID of a given ReferenceType, 0 otherwise*/ -u32 gf_isom_has_track_reference(GF_ISOFile *movie, u32 trackNumber, u32 referenceType, u32 refTrackID); -u8 gf_isom_get_pl_indication(GF_ISOFile *the_file, u8 PL_Code); +/*! @} */ -/*locates the first ObjectDescriptor using the given track by inspecting any OD tracks*/ -u32 gf_isom_find_od_for_track(GF_ISOFile *file, u32 track); -/*returns file name*/ -const char *gf_isom_get_filename(GF_ISOFile *the_file); +#ifndef GPAC_DISABLE_ISOM_WRITE -/* - Update of the Reading API for IsoMedia Version 2 +/*! +\addtogroup isowrite_grp ISOBMFF Writing +\ingroup iso_grp + +ISOBMF file writing +@{ */ -/*retrieves the brand of the file. The brand is introduced in V2 to differenciate -MP4, MJPEG2000 and QT while indicating compatibilities -the brand is one of the above defined code, or any other registered brand +/*! Movie Storage modes*/ +typedef enum +{ + /*! FLAT: the MediaData is stored at the beginning of the file*/ + GF_ISOM_STORE_FLAT = 1, + /*! STREAMABLE: the MetaData (File Info) is stored at the beginning of the file + for fast access during download*/ + GF_ISOM_STORE_STREAMABLE, + /*! INTERLEAVED: Same as STREAMABLE, plus the media data is mixed by chunk of fixed duration*/ + GF_ISOM_STORE_INTERLEAVED, + /*! INTERLEAVED +DRIFT: Same as INTERLEAVED, and adds time drift control to avoid creating too long chunks*/ + GF_ISOM_STORE_DRIFT_INTERLEAVED, + /*! tightly interleaves samples based on their DTS, therefore allowing better placement of samples in the file. + This is used for both http interleaving and Hinting optimizations*/ + GF_ISOM_STORE_TIGHT, + /*! FASTSTART: same as FLAT but moves moov before mdat at the end*/ + GF_ISOM_STORE_FASTSTART, +} GF_ISOStorageMode; + +/*! writes the file without deleting (see \ref gf_isom_delete) +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_write(GF_ISOFile *isom_file); -minorVersion is an optional parameter (can be set to NULL) , - "informative integer for the minor version of the major brand" -AlternateBrandsCount is an optional parameter (can be set to NULL) , - giving the number of compatible brands. +/*! freezes order of the current box tree in the file. +By default the library always reorder boxes in the recommended order in the various specifications implemented. +New created tracks or meta items will not have a frozen order of boxes, but the function can be called several time +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_freeze_order(GF_ISOFile *isom_file); - The function will set brand to 0 if no brand indication is found in the file +/*! keeps UTC edit times when storing +\param isom_file the target ISO file +\param keep_utc if GF_TRUE, do not edit times */ -GF_Err gf_isom_get_brand_info(GF_ISOFile *the_file, u32 *brand, u32 *minorVersion, u32 *AlternateBrandsCount); +void gf_isom_keep_utc_times(GF_ISOFile *isom_file, Bool keep_utc); -/*gets an alternate brand indication. BrandIndex is 1-based -Note that the Major brand should always be indicated in the alternate brands*/ -GF_Err gf_isom_get_alternate_brand(GF_ISOFile *the_file, u32 BrandIndex, u32 *brand); +/*! sets the timescale of the movie. This rescales times expressed in movie timescale in edit lists and mvex boxes +\param isom_file the target ISO file +\param timeScale the target timescale +\return error if any +*/ +GF_Err gf_isom_set_timescale(GF_ISOFile *isom_file, u32 timeScale); + +/*! loads a set of top-level boxes in moov udta and child boxes. UDTA will be replaced if already present +\param isom_file the target ISO file +\param moov_boxes a serialized array of boxes to add +\param moov_boxes_size the size of the serialized array of boxes +\param udta_only only replace/inject udta box and entries +\return error if any +*/ +GF_Err gf_isom_load_extra_boxes(GF_ISOFile *isom_file, u8 *moov_boxes, u32 moov_boxes_size, Bool udta_only); + +/*! creates a new track +\param isom_file the target ISO file +\param trackID the ID of the track - if 0, the track ID is chosen by the API +\param MediaType the handler type (four character code) of the media +\param TimeScale the time scale of the media +\return the track number or 0 if error*/ +u32 gf_isom_new_track(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 MediaType, u32 TimeScale); + +/*! creates a new track from an encoded trak box. +\param isom_file the target ISO file +\param trackID the ID of the track - if 0, the track ID is chosen by the API +\param MediaType the handler type (four character code) of the media +\param TimeScale the time scale of the media +\param tk_box a serialized trak box to use as template +\param tk_box_size the size of the serialized trak box +\param udta_only only replace/inject udta box and entries +\return the track number or 0 if error*/ +u32 gf_isom_new_track_from_template(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 MediaType, u32 TimeScale, u8 *tk_box, u32 tk_box_size, Bool udta_only); + +/*! removes a track - internal cross dependencies will be updated. +WARNING: any OD streams with references to this track through ODUpdate, ESDUpdate, ESDRemove commands +will be rewritten +\param isom_file the target ISO file +\param trackNumber the target track to remove file +\return error if any +*/ +GF_Err gf_isom_remove_track(GF_ISOFile *isom_file, u32 trackNumber); -/*get the number of padding bits at the end of a given sample if any*/ -GF_Err gf_isom_get_sample_padding_bits(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber, u8 *NbBits); -/*indicates whether the track samples use padding bits or not*/ -Bool gf_isom_has_padding_bits(GF_ISOFile *the_file, u32 trackNumber); +/*! sets the enable flag of a track +\param isom_file the target ISO file +\param trackNumber the target track +\param enableTrack if GF_TRUE, track is enabled, otherwise disabled +\return error if any +*/ +GF_Err gf_isom_set_track_enabled(GF_ISOFile *isom_file, u32 trackNumber, Bool enableTrack); -/*returns width and height of the given visual sample desc - error if not a visual track*/ -GF_Err gf_isom_get_visual_info(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, u32 *Width, u32 *Height); +/*! Track header flags operation type*/ +typedef enum +{ + /*! set flags, erasing previous value*/ + GF_ISOM_TKFLAGS_SET = 0, + /*! add flags*/ + GF_ISOM_TKFLAGS_REM, + /*! remove flags*/ + GF_ISOM_TKFLAGS_ADD, +} GF_ISOMTrackFlagOp; + +/*! toggles track flags on or off +\param isom_file the target ISO file +\param trackNumber the target track +\param flags flags to modify +\param op flag operation mode +\return error if any +*/ +GF_Err gf_isom_set_track_flags(GF_ISOFile *isom_file, u32 trackNumber, u32 flags, GF_ISOMTrackFlagOp op); -/*returns samplerate (no SBR when applicable), channels and bps of the given audio track - error if not a audio track*/ -GF_Err gf_isom_get_audio_info(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, u32 *SampleRate, u32 *Channels, u8 *bitsPerSample); +/*! sets creationTime and modificationTime of the movie to the specified date +\param isom_file the target ISO file +\param time the new time +\return error if any +*/ +GF_Err gf_isom_set_creation_time(GF_ISOFile *isom_file, u64 time); -/*returns track visual info - all coord values are expressed as 16.16 fixed point floats*/ -GF_Err gf_isom_get_track_layout_info(GF_ISOFile *the_file, u32 trackNumber, u32 *width, u32 *height, s32 *translation_x, s32 *translation_y, s16 *layer); +/*! sets creationTime and modificationTime of the track to the specified date +\param isom_file the target ISO file +\param trackNumber the target track +\param time the new time +\return error if any +*/ +GF_Err gf_isom_set_track_creation_time(GF_ISOFile *isom_file,u32 trackNumber, u64 time); + +/*! changes the ID of a track - all track references present in the file are updated +\param isom_file the target ISO file +\param trackNumber the target track +\param trackID the new track ID +\return error if trackID is already in used in the file*/ +GF_Err gf_isom_set_track_id(GF_ISOFile *isom_file, u32 trackNumber, GF_ISOTrackID trackID); + +/*! forces to rewrite all dependencies when track ID changes. Used to check if track references are broken during import of a single track +\param isom_file the target ISO file +\param trackNumber the target track +\return error if any +*/ +GF_Err gf_isom_rewrite_track_dependencies(GF_ISOFile *isom_file, u32 trackNumber); + +/*! adds a sample to a track +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index associated with the sample +\param sample the target sample to add +\return error if any +*/ +GF_Err gf_isom_add_sample(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, const GF_ISOSample *sample); + +/*! copies all sample dependency, subSample and sample group information from the given sampleNumber in source file to the last added sample in dest file +\param dst the destination ISO file +\param dst_track the destination track +\param src the source ISO file +\param src_track the source track +\param sampleNumber the source sample number +\return error if any +*/ +GF_Err gf_isom_copy_sample_info(GF_ISOFile *dst, u32 dst_track, GF_ISOFile *src, u32 src_track, u32 sampleNumber); -/*returns track matrix info - all coord values are expressed as 16.16 fixed point floats*/ -GF_Err gf_isom_get_track_matrix(GF_ISOFile *the_file, u32 trackNumber, u32 matrix[9]); +/*! adds a sync shadow sample to a track. +- There must be a regular sample with the same DTS. +- Sync Shadow samples MUST be RAP and can only use the same sample description as the sample they shadow +- Currently, adding sync shadow must be done in order (no sample insertion) -/*returns width and height of the given visual sample desc - error if not a visual track*/ -GF_Err gf_isom_get_pixel_aspect_ratio(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, u32 *hSpacing, u32 *vSpacing); +\param isom_file the target ISO file +\param trackNumber the target track +\param sample the target shadow sample to add +\return error if any +*/ +GF_Err gf_isom_add_sample_shadow(GF_ISOFile *isom_file, u32 trackNumber, GF_ISOSample *sample); + +/*! adds data to current sample in the track. This will update the data size. +CANNOT be used with OD media type +There shall not be any other +\param isom_file the target ISO file +\param trackNumber the target track +\param data the data to append to the sample +\param data_size the size of the data to append +\return error if any +*/ +GF_Err gf_isom_append_sample_data(GF_ISOFile *isom_file, u32 trackNumber, u8 *data, u32 data_size); + +/*! adds sample references to a track +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index associated with the sample +\param sample the target sample to add +\param dataOffset is the offset in bytes of the data in the referenced file. +\return error if any +*/ +GF_Err gf_isom_add_sample_reference(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_ISOSample *sample, u64 dataOffset); + +/*! sets the duration of the last media sample. If not set, the duration of the last sample is the +duration of the previous one if any, or media TimeScale (default value). This does not modify the edit list if any, +you must modify this using \ref gf_isom_set_edit +\param isom_file the target ISO file +\param trackNumber the target track +\param duration duration of last sample in media timescale +\return error if any +*/ +GF_Err gf_isom_set_last_sample_duration(GF_ISOFile *isom_file, u32 trackNumber, u32 duration); + +/*! sets the duration of the last media sample. If not set, the duration of the last sample is the +duration of the previous one if any, or media TimeScale (default value). This does not modify the edit list if any, +you must modify this using \ref gf_isom_set_edit. +If both dur_num and dur_den are both zero, forces last sample duration to be the same as previous sample +\param isom_file the target ISO file +\param trackNumber the target track +\param dur_num duration num value +\param dur_den duration num value +\return error if any +*/ +GF_Err gf_isom_set_last_sample_duration_ex(GF_ISOFile *isom_file, u32 trackNumber, u32 dur_num, u32 dur_den); -/*gets RVC config of given sample description*/ -GF_Err gf_isom_get_rvc_config(GF_ISOFile *movie, u32 track, u32 sampleDescriptionIndex, u16 *rvc_predefined, char **data, u32 *size, const char **mime); +/*! patches last stts entry to make sure the cumulated duration equals the given next_dts value +\param isom_file the target ISO file +\param trackNumber the target track +\param next_dts target decode time of next sample +\return error if any +*/ +GF_Err gf_isom_patch_last_sample_duration(GF_ISOFile *isom_file, u32 trackNumber, u64 next_dts); + +/*! adds a track reference to another track +\param isom_file the target ISO file +\param trackNumber the target track +\param referenceType the four character code of the reference +\param ReferencedTrackID the ID of the track refered to +\return error if any +*/ +GF_Err gf_isom_set_track_reference(GF_ISOFile *isom_file, u32 trackNumber, u32 referenceType, GF_ISOTrackID ReferencedTrackID); -/* - User Data Manipulation (cf write API too) +/*! removes all track references +\param isom_file the target ISO file +\param trackNumber the target track +\return error if any +*/ +GF_Err gf_isom_remove_track_references(GF_ISOFile *isom_file, u32 trackNumber); + +/*! sets track handler name. +\param isom_file the target ISO file +\param trackNumber the target track +\param nameUTF8 the handler name; either NULL (reset), a UTF-8 formatted string or a UTF8 file resource in the form "file://path/to/file_utf8" +\return error if any +*/ +GF_Err gf_isom_set_handler_name(GF_ISOFile *isom_file, u32 trackNumber, const char *nameUTF8); + +/*! updates the sample size table - this is needed when using \ref gf_isom_append_sample_data in case the resulting samples +are of same sizes (typically in 3GP speech tracks) +\param isom_file the target ISO file +\param trackNumber the target track +\return error if any +*/ +GF_Err gf_isom_refresh_size_info(GF_ISOFile *isom_file, u32 trackNumber); + +/*! updates the duration of the movie +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_update_duration(GF_ISOFile *isom_file); + + +/*! updates a given sample of the media. This function updates both media data of sample and sample properties (DTS, CTS, SAP type) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the number of the sample to update +\param sample the new sample +\param data_only if set to GF_TRUE, only the sample data is updated, not other info +\return error if any +*/ +GF_Err gf_isom_update_sample(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, GF_ISOSample *sample, Bool data_only); + +/*! updates a sample reference in the media. Note that the sample MUST exists, and that sample->data MUST be NULL and sample->dataLength must be NON NULL. +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the number of the sample to update +\param sample the new sample +\param data_offset new offset of sample in referenced file +\return error if any +*/ +GF_Err gf_isom_update_sample_reference(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, GF_ISOSample *sample, u64 data_offset); + +/*! removes a given sample +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the number of the sample to update +\return error if any +*/ +GF_Err gf_isom_remove_sample(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber); + +/*! changes media time scale + +\param isom_file the target ISO file +\param trackNumber the target track +\param new_timescale the new timescale to set +\param new_tsinc if not 0, changes sample duration and composition offsets to new_tsinc/new_timescale. If non-constant sample dur is used, uses the samllest sample dur in the track. Otherwise, only changes the timescale +\param force_rescale if set to GF_TRUE, only the media timescale is changed but media times are not updated. Ignored if new_tsinc is not 0. +\return GF_EOS if no action taken (same config), or error if any +*/ +GF_Err gf_isom_set_media_timescale(GF_ISOFile *isom_file, u32 trackNumber, u32 new_timescale, u32 new_tsinc, Bool force_rescale); + +/*! sets the save file name of the (edited) movie. +If the movie is edited, the default fileName is the open name suffixed with an internally defined extension "%p_isotmp")" +\note you cannot save an edited file under the same name (overwrite not allowed) +If the movie is created (WRITE mode), the default filename is $OPEN_NAME + +\param isom_file the target ISO file +\param filename the new final filename +\return error if any +*/ +GF_Err gf_isom_set_final_name(GF_ISOFile *isom_file, char *filename); + +/*! sets the storage mode of a file (FLAT, STREAMABLE, INTERLEAVED) +\param isom_file the target ISO file +\param storage_mode the target storage mode +\return error if any +*/ +GF_Err gf_isom_set_storage_mode(GF_ISOFile *isom_file, GF_ISOStorageMode storage_mode); + +/*! sets the interleaving time of media data (INTERLEAVED mode only) +\param isom_file the target ISO file +\param InterleaveTime the target interleaving time in movie timescale +\return error if any +*/ +GF_Err gf_isom_set_interleave_time(GF_ISOFile *isom_file, u32 InterleaveTime); + +/*! forces usage of 64 bit chunk offsets +\param isom_file the target ISO file +\param set_on if GF_TRUE, 64 bit chunk offsets are always used; otherwise, they are used only for large files +\return error if any +*/ +GF_Err gf_isom_force_64bit_chunk_offset(GF_ISOFile *isom_file, Bool set_on); + +/*! compression mode of top-level boxes*/ +typedef enum +{ + /*! no compression is used*/ + GF_ISO_COMP_NONE=0, + /*! only moov box is compressed*/ + GF_ISO_COMP_MOOV, + /*! only moof boxes are compressed*/ + GF_ISO_COMP_MOOF, + /*! only moof and sidx boxes are compressed*/ + GF_ISO_COMP_MOOF_SIDX, + /*! only moof, sidx and ssix boxes are compressed*/ + GF_ISO_COMP_MOOF_SSIX, + /*! all (moov, moof, sidx and ssix) boxes are compressed*/ + GF_ISO_COMP_ALL, +} GF_ISOCompressMode; + +/*! sets compression mode of file +\param isom_file the target ISO file +\param compress_mode the desired compress mode +\param force_compress forces compressed box even if compress size is larger than uncompressed size +\return error if any +*/ +GF_Err gf_isom_enable_compression(GF_ISOFile *isom_file, GF_ISOCompressMode compress_mode, Bool force_compress); + +/*! sets the copyright in one language +\param isom_file the target ISO file +\param threeCharCode the ISO three character language code for copyright +\param notice the copyright notice to add +\return error if any +*/ +GF_Err gf_isom_set_copyright(GF_ISOFile *isom_file, const char *threeCharCode, char *notice); + +/*! adds a kind type to a track +\param isom_file the target ISO file +\param trackNumber the target track +\param schemeURI the scheme URI of the added kind +\param value the value of the added kind +\return error if any +*/ +GF_Err gf_isom_add_track_kind(GF_ISOFile *isom_file, u32 trackNumber, const char *schemeURI, const char *value); + +/*! removes a kind type to the track, all if NULL params +\param isom_file the target ISO file +\param trackNumber the target track +\param schemeURI the scheme URI of the removed kind +\param value the value of the removed kind +\return error if any +*/ +GF_Err gf_isom_remove_track_kind(GF_ISOFile *isom_file, u32 trackNumber, const char *schemeURI, const char *value); + +/*! changes the handler type of the media +\warning this may completely breaks the parsing of the media track +\param isom_file the target ISO file +\param trackNumber the target track +\param new_type the new handler four character type +\return error if any +*/ +GF_Err gf_isom_set_media_type(GF_ISOFile *isom_file, u32 trackNumber, u32 new_type); + +/*! changes the type of the sampleDescriptionBox +\warning this may completely breaks the parsing of the media track +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param new_type the new four character code type of the smaple description +\return error if any +*/ +GF_Err gf_isom_set_media_subtype(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 new_type); + +/*! sets a track in an alternate group +\param isom_file the target ISO file +\param trackNumber the target track +\param groupId the alternate group ID +\return error if any +*/ +GF_Err gf_isom_set_alternate_group_id(GF_ISOFile *isom_file, u32 trackNumber, u32 groupId); + +/*! adds chapter info: + +\param isom_file the target ISO file +\param trackNumber the target track. If 0, the chapter info is added to the movie, otherwise to the track +\param timestamp the chapter start time in milliseconds. Chapters are added in order to the file. If a chapter with same timestamp + is found, its name is updated but no entry is created. +\param name the chapter name. If NULL, defaults to 'Chapter N' +\return error if any +*/ +GF_Err gf_isom_add_chapter(GF_ISOFile *isom_file, u32 trackNumber, u64 timestamp, char *name); + +/*! deletes copyright +\param isom_file the target ISO file +\param trackNumber the target track +\param index the 1-based index of the copyright notice to remove, or 0 to remove all copyrights +\return error if any +*/ +GF_Err gf_isom_remove_chapter(GF_ISOFile *isom_file, u32 trackNumber, u32 index); + +/*! updates or inserts a new edit in the track time line. Edits are used to modify +the media normal timing. EditTime and EditDuration are expressed in movie timescale +\note If a segment with EditTime already exists, it is erase +\note If there is a segment before this new one, its duration is adjust to match EditTime of the new segment +\warning The first segment always have an EditTime of 0. You should insert an empty or dwelled segment first + +\param isom_file the target ISO file +\param trackNumber the target track +\param EditTime the start of the edit in movie timescale +\param EditDuration the duration of the edit in movie timecale +\param MediaTime the corresponding media time of the start of the edit, in media timescale. -1 for empty edits +\param EditMode the edit mode +\return error if any +*/ +GF_Err gf_isom_set_edit(GF_ISOFile *isom_file, u32 trackNumber, u64 EditTime, u64 EditDuration, u64 MediaTime, GF_ISOEditType EditMode); + +/*! same as \ref gf_isom_set_edit except only modifies duration type and mediaType +\param isom_file the target ISO file +\param trackNumber the target track +\param edit_index the 1-based index of the edit to update +\param EditDuration duration of the edit in movie timescale +\param MediaTime the corresponding media time of the start of the edit, in media timescale. -1 for empty edits +\param EditMode the edit mode +\return error if any +*/ +GF_Err gf_isom_modify_edit(GF_ISOFile *isom_file, u32 trackNumber, u32 edit_index, u64 EditDuration, u64 MediaTime, GF_ISOEditType EditMode); + +/*! same as \ref gf_isom_modify_edit except only appends new segment +\param isom_file the target ISO file +\param trackNumber the target track +\param EditDuration duration of the edit in movie timescale +\param MediaTime the corresponding media time of the start of the edit, in media timescale. -1 for empty edits +\param EditMode the edit mode +\return error if any +*/ +GF_Err gf_isom_append_edit(GF_ISOFile *isom_file, u32 trackNumber, u64 EditDuration, u64 MediaTime, GF_ISOEditType EditMode); + +/*! removes all edits in the track +\param isom_file the target ISO file +\param trackNumber the target track +\return error if any +*/ +GF_Err gf_isom_remove_edits(GF_ISOFile *isom_file, u32 trackNumber); + +/*! removes the given edit. If this is not the last segment, the next segment duration is updated to maintain a continous timeline +\param isom_file the target ISO file +\param trackNumber the target track +\param edit_index the 1-based index of the edit to update +\return error if any +*/ +GF_Err gf_isom_remove_edit(GF_ISOFile *isom_file, u32 trackNumber, u32 edit_index); + +/*! updates edit list after track edition. All edit entries with a duration or media starttime larger than the media duration are clamped to media duration +\param isom_file the target ISO file +\param trackNumber the target track +\return error if any +*/ +GF_Err gf_isom_update_edit_list_duration(GF_ISOFile *isom_file, u32 trackNumber); + + +/*! remove track, moov or file-level UUID box of matching type +\param isom_file the target ISO file +\param trackNumber the target track for the UUID box; if 0, removes from movie; if 0xFFFFFFFF, removes from file +\param UUID the UUID of the box to remove +\return error if any +*/ +GF_Err gf_isom_remove_uuid(GF_ISOFile *isom_file, u32 trackNumber, bin128 UUID); + +/*! adds track, moov or file-level UUID box +\param isom_file the target ISO file +\param trackNumber the target track for the UUID box; if 0, removes from movie; if 0xFFFFFFFF, removes from file +\param UUID the UUID of the box to remove +\param data the data to add, may be NULL +\param size the size of the data to add, shall be 0 when data is NULL +\return error if any +*/ +GF_Err gf_isom_add_uuid(GF_ISOFile *isom_file, u32 trackNumber, bin128 UUID, const u8 *data, u32 size); + +/*! uses a compact track version for sample size. This is not usually recommended +except for speech codecs where the track has a lot of small samples +compaction is done automatically while writing based on the track's sample sizes +\param isom_file the target ISO file +\param trackNumber the target track for the udta box; if 0, add the udta to the movie; +\param CompactionOn if set to GF_TRUE, compact size tables are used; otherwise regular size tables are used +\return error if any +*/ +GF_Err gf_isom_use_compact_size(GF_ISOFile *isom_file, u32 trackNumber, Bool CompactionOn); + +/*! sets the brand of the movie +\note this automatically adds the major brand to the set of alternate brands if not present +\param isom_file the target ISO file +\param MajorBrand four character code of the major brand to set +\param MinorVersion version of the brand +\return error if any +*/ +GF_Err gf_isom_set_brand_info(GF_ISOFile *isom_file, u32 MajorBrand, u32 MinorVersion); + +/*! adds or removes an alternate brand for the movie. +\note When removing an alternate brand equal to the major brand, the major brand is updated with the first alternate brand remaining, or 'isom' otherwise +\param isom_file the target ISO file +\param Brand four character code of the brand to add or remove +\param AddIt if set to GF_TRUE, the brand is added, otherwise it is removed +\return error if any +*/ +GF_Err gf_isom_modify_alternate_brand(GF_ISOFile *isom_file, u32 Brand, Bool AddIt); + +/*! removes all alternate brands except major brand +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_reset_alt_brands(GF_ISOFile *isom_file); + +/*! set sample dependency flags - see ISO/IEC 14496-12 and \ref gf_filter_pck_set_dependency_flags +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleNumber the target sample number +\param isLeading indicates that the sample is a leading picture +\param dependsOn indicates the sample dependency towards other samples +\param dependedOn indicates the sample dependency from other samples +\param redundant indicates that the sample contains redundant coding +\return error if any +*/ +GF_Err gf_isom_set_sample_flags(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 isLeading, u32 dependsOn, u32 dependedOn, u32 redundant); + +/*! sets size information of a sample description of a visual track +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param Width the width in pixels +\param Height the height in pixels +\return error if any +*/ +GF_Err gf_isom_set_visual_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 Width, u32 Height); + +/*! sets bit depth of a sample description of a visual track (for uncompressed media usually) +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param bitDepth the bit depth of each pixel (eg 24 for RGB, 32 for RGBA) +\return error if any +*/ +GF_Err gf_isom_set_visual_bit_depth(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u16 bitDepth); + +/*! sets a visual track layout info +\param isom_file the target ISO file +\param trackNumber the target track number +\param width the track width in pixels +\param height the track height in pixels +\param translation_x the horizontal translation (from the left) of the track in the movie canvas, expressed as 16.16 fixed point float +\param translation_y the vertical translation (from the top) of the track in the movie canvas, expressed as 16.16 fixed point float +\param layer z order of the track on the canvas +\return error if any +*/ +GF_Err gf_isom_set_track_layout_info(GF_ISOFile *isom_file, u32 trackNumber, u32 width, u32 height, s32 translation_x, s32 translation_y, s16 layer); + +/*! sets track matrix +\param isom_file the target ISO file +\param trackNumber the target track number +\param matrix the transformation matrix of the track on the movie canvas; all coeficients are expressed as 16.16 floating points +\return error if any + */ +GF_Err gf_isom_set_track_matrix(GF_ISOFile *isom_file, u32 trackNumber, s32 matrix[9]); + +/*! sets the pixel aspect ratio for a sample description +\note the aspect ratio is expressed as hSpacing divided by vSpacing; 2:1 means pixel is twice as wide as it is high +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param hSpacing horizontal spacing of the aspect ratio; a value of 0 removes PAR; negative value means 1 +\param vSpacing vertical spacing of the aspect ratio; a value of 0 removes PAR; negative value means 1 +\param force_par if set, forces PAR to 1:1 when hSpacing=vSpacing; otherwise removes PAR when hSpacing=vSpacing +\return error if any +*/ +GF_Err gf_isom_set_pixel_aspect_ratio(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, s32 hSpacing, s32 vSpacing, Bool force_par); + +/*! sets clean apperture (crop window, see ISO/IEC 14496-12) for a sample description +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param cleanApertureWidthN nominator of clean apperture horizontal size +\param cleanApertureWidthD denominator of clean apperture horizontal size +\param cleanApertureHeightN nominator of clean apperture vertical size +\param cleanApertureHeightD denominator of clean apperture vertical size +\param horizOffN nominator of horizontal offset of clean apperture center minus (width-1)/2 (eg 0 sets center to center of video) +\param horizOffD denominator of horizontal offset of clean apperture center minus (width-1)/2 (eg 0 sets center to center of video) +\param vertOffN nominator of vertical offset of clean apperture center minus (height-1)/2 (eg 0 sets center to center of video) +\param vertOffD denominator of vertical offset of clean apperture center minus (height-1)/2 (eg 0 sets center to center of video) +\return error if any +*/ +GF_Err gf_isom_set_clean_aperture(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 cleanApertureWidthN, u32 cleanApertureWidthD, u32 cleanApertureHeightN, u32 cleanApertureHeightD, u32 horizOffN, u32 horizOffD, u32 vertOffN, u32 vertOffD); + +/*! updates track aperture information for QT/ProRes +\param isom_file the target ISO file +\param trackNumber the target track number +\param remove if GF_TRUE, remove track aperture information, otherwise updates it +\return error if any +*/ +GF_Err gf_isom_update_aperture_info(GF_ISOFile *isom_file, u32 trackNumber, Bool remove); + +/*! content light level info*/ +typedef struct { + /*! max content ligth level*/ + u16 max_content_light_level; + /*! max picture average ligth level*/ + u16 max_pic_average_light_level; +} GF_ContentLightLevelInfo; + +/*! mastering display colour volume info*/ +typedef struct { + /*! display primaries*/ + struct { + u16 x; + u16 y; + } display_primaries[3]; + /*! X white point*/ + u16 white_point_x; + /*! Y white point*/ + u16 white_point_y; + u32 max_display_mastering_luminance; + /*! min display mastering luminance*/ + u32 min_display_mastering_luminance; +} GF_MasteringDisplayColourVolumeInfo; + + +/*! sets high dynamic range information for a sample description +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param mdcv the mastering display colour volume to set +\param clli the content light level to set +\return error if any +*/ +GF_Err gf_isom_set_high_dynamic_range_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_MasteringDisplayColourVolumeInfo *mdcv, GF_ContentLightLevelInfo *clli); + +/*! force Dolby Vision profile: mainly used when the bitstream doesn't contain all the necessary information +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param dv_profile the Dolby Vision profile +\return error if any +*/ +GF_Err gf_isom_set_dolby_vision_profile(GF_ISOFile* isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 dv_profile); + + +/*! sets image sequence coding constraints (mostly used for HEIF image files) +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param remove if set to GF_TRUE, removes coding constraints +\param all_ref_pics_intra indicates if all reference pictures are intra frames +\param intra_pred_used indicates if intra prediction is used +\param max_ref_per_pic indicates the max number of reference images per picture +\return error if any + */ +GF_Err gf_isom_set_image_sequence_coding_constraints(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, Bool remove, Bool all_ref_pics_intra, Bool intra_pred_used, u32 max_ref_per_pic); + +/*! sets image sequence alpha flag (mostly used for HEIF image files). The alpha flag indicates the image sequence is an alpha plane +or has an alpha channel +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param remove if set to GF_TRUE, removes coding constraints +\return error if any +*/ +GF_Err gf_isom_set_image_sequence_alpha(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, Bool remove); + +/*! sets colour information for a sample description +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param colour_type the four character code of the colour type to set (nclc, nclx, prof, ricc); if 0, removes all color info +\param colour_primaries the colour primaries for nclc/nclx as defined in ISO/IEC 23001-8 +\param transfer_characteristics the colour primaries for nclc/nclx as defined in ISO/IEC 23001-8 +\param matrix_coefficients the colour primaries for nclc/nclx as defined in ISO/IEC 23001-8 +\param full_range_flag the colour primaries for nclc as defined in ISO/IEC 23001-8 +\param icc_data the icc data pto set for prof and ricc types +\param icc_size the size of the icc data +\return error if any +*/ +GF_Err gf_isom_set_visual_color_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 colour_type, u16 colour_primaries, u16 transfer_characteristics, u16 matrix_coefficients, Bool full_range_flag, u8 *icc_data, u32 icc_size); + + +/*! Audio Sample Description signaling mode*/ +typedef enum { + /*! use ISOBMF sample entry v0*/ + GF_IMPORT_AUDIO_SAMPLE_ENTRY_NOT_SET = 0, + /*! use ISOBMF sample entry v0*/ + GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_BS, + /*! use ISOBMF sample entry v0 and forces channel count to 2*/ + GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_2, + /*! use ISOBMF sample entry v1*/ + GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_MPEG, + /*! use QTFF sample entry v1*/ + GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_QTFF +} GF_AudioSampleEntryImportMode; + + +/*! sets audio format information for a sample description +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param sampleRate the audio sample rate +\param nbChannels the number of audio channels +\param bitsPerSample the number of bits per sample, mostly used for raw audio +\param asemode type of audio entry signaling desired +\return error if any +*/ +GF_Err gf_isom_set_audio_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 sampleRate, u32 nbChannels, u8 bitsPerSample, GF_AudioSampleEntryImportMode asemode); + + +/*! sets audio channel and object layout information for a sample description, ISOBMFF style +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param layout the layout information +\return error if any +*/ +GF_Err gf_isom_set_audio_layout(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_AudioChannelLayout *layout); + +/*! sets CTS unpack mode (used for B-frames & like): in unpack mode, each sample uses one entry in CTTS tables + +\param isom_file the target ISO file +\param trackNumber the target track number +\param unpack if GF_TRUE, sets unpack on, creating a ctts table if none found; if GF_FALSE, sets unpack off and repacks all table info +\return error if any +*/ +GF_Err gf_isom_set_cts_packing(GF_ISOFile *isom_file, u32 trackNumber, Bool unpack); + +/*! shifts all CTS with the given offset. This MUST be called in unpack mode only +\param isom_file the target ISO file +\param trackNumber the target track number +\param offset_shift CTS offset shift in media timescale +\return error if any +*/ +GF_Err gf_isom_shift_cts_offset(GF_ISOFile *isom_file, u32 trackNumber, s32 offset_shift); + +/*! enables negative composition offset in track +\note this will compute the composition to decode time information +\param isom_file the target ISO file +\param trackNumber the target track +\param use_negative_offsets if GF_TRUE, negative offsets are used, otherwise they are disabled +\return error if any +*/ +GF_Err gf_isom_set_composition_offset_mode(GF_ISOFile *isom_file, u32 trackNumber, Bool use_negative_offsets); + +/*! enables negative composition offset in track and shift offsets + +\param isom_file the target ISO file +\param trackNumber the target track +\param ctts_shift shif CTS offsets by the given time in media timescale if positive offsets only are used +\return error if any +*/ +GF_Err gf_isom_set_ctts_v1(GF_ISOFile *isom_file, u32 trackNumber, u32 ctts_shift); + + +/*! sets language for a track +\param isom_file the target ISO file +\param trackNumber the target track number +\param code 3-character code or BCP-47 code media language +\return error if any +*/ +GF_Err gf_isom_set_media_language(GF_ISOFile *isom_file, u32 trackNumber, char *code); + +/*! gets the ID of the last created track +\param isom_file the target ISO file +\return the last created track ID +*/ +GF_ISOTrackID gf_isom_get_last_created_track_id(GF_ISOFile *isom_file); + +/*! applies a box patch to the file. See examples in gpac test suite, media/boxpatch/ +\param isom_file the target ISO file +\param trackID the ID of the track to patch, in case one of the box patch applies to a track +\param box_patch_filename the name of the file containing the box patches +\param for_fragments indicates if the patch targets movie fragments or regular moov +\return error if any +*/ +GF_Err gf_isom_apply_box_patch(GF_ISOFile *isom_file, GF_ISOTrackID trackID, const char *box_patch_filename, Bool for_fragments); + +/*! sets track magic number +\param isom_file the target ISO file +\param trackNumber the target track +\param magic the magic number to set; magic number is not written to file +\return error if any +*/ +GF_Err gf_isom_set_track_magic(GF_ISOFile *isom_file, u32 trackNumber, u64 magic); + +/*! sets track index in moov +\param isom_file the target ISO file +\param trackNumber the target track +\param index the 1-based index to set. Tracks will be reordered after this! +\param track_num_changed callback function used to notify track changes during the call to this function +\param udta opaque user data for the callback function +\return error if any +*/ +GF_Err gf_isom_set_track_index(GF_ISOFile *isom_file, u32 trackNumber, u32 index, void (*track_num_changed)(void *udta, u32 old_track_num, u32 new_track_num), void *udta); + +/*! removes a sample description with the given index +\warning This does not remove any added samples for that stream description, nor rewrite the sample to chunk and other boxes referencing the sample description index ! +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description to remove +\return error if any +*/ +GF_Err gf_isom_remove_stream_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! updates average and max bitrate of a sample description +if both average_bitrate and max_bitrate are 0, this removes any bitrate information +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description +\param average_bitrate the average bitrate of the media for that sample description +\param max_bitrate the maximum bitrate of the media for that sample description +\param decode_buffer_size the decoder buffer size in bytes for that sample description +\return error if any +*/ +GF_Err gf_isom_update_bitrate(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 average_bitrate, u32 max_bitrate, u32 decode_buffer_size); + + +/*! track clone flags*/ +typedef enum +{ + /*! set this flag to keep data reference entries while cloning track*/ + GF_ISOM_CLONE_TRACK_KEEP_DREF = 1, + /*! set this flag to avoid cloning track as a QT track while cloning track*/ + GF_ISOM_CLONE_TRACK_NO_QT = 1<<1 +} GF_ISOTrackCloneFlags; + +/*! clones a track. This clones everything except media data and sample info (DTS, CTS, RAPs, etc...), and also clones sample descriptions +\param orig_file the source ISO file +\param orig_track the source track +\param dest_file the destination ISO file +\param flags flags to use during clone +\param dest_track set to the track number of cloned track +\return error if any +*/ +GF_Err gf_isom_clone_track(GF_ISOFile *orig_file, u32 orig_track, GF_ISOFile *dest_file, GF_ISOTrackCloneFlags flags, u32 *dest_track); + + +/*! sets the GroupID of a track (only used for optimized interleaving). By setting GroupIDs +you can specify the storage order for media data of a group of streams. This is useful +for BIFS presentation so that static resources of the scene can be downloaded before BIFS + +\param isom_file the target ISO file +\param trackNumber the target track +\param GroupID the desired group ID +\return error if any +*/ +GF_Err gf_isom_set_track_interleaving_group(GF_ISOFile *isom_file, u32 trackNumber, u32 GroupID); + +/*! sets the priority of a track within a Group (used for optimized interleaving and hinting). +This allows tracks to be stored before other within a same group, for instance the +hint track data can be stored just before the media data, reducing disk seeking + +\param isom_file the target ISO file +\param trackNumber the target track +\param InversePriority the desired priority. For a same time, within a group of tracks, the track with the lowest InversePriority will +be written first +\return error if any +*/ +GF_Err gf_isom_set_track_priority_in_group(GF_ISOFile *isom_file, u32 trackNumber, u32 InversePriority); + +/*! sets the maximum chunk size for a track +\param isom_file the target ISO file +\param trackNumber the target track +\param maxChunkSize the maximum chunk size in bytes +\return error if any +*/ +GF_Err gf_isom_hint_max_chunk_size(GF_ISOFile *isom_file, u32 trackNumber, u32 maxChunkSize); + +/*! sets up interleaving for storage (shortcut for storeage mode + interleave_time) +\param isom_file the target ISO file +\param TimeInSec the desired interleaving time in seconds +\return error if any +*/ +GF_Err gf_isom_make_interleave(GF_ISOFile *isom_file, Double TimeInSec); + +/*! sets progress callback when writing a file +\param isom_file the target ISO file +\param progress_cbk the progress callback function +\param progress_cbk_udta opaque data passed to the progress callback function +*/ +void gf_isom_set_progress_callback(GF_ISOFile *isom_file, void (*progress_cbk)(void *udta, u64 nb_done, u64 nb_total), void *progress_cbk_udta); + +/*! sets write callback functions for in-memory file writing +\param isom_file the target ISO file +\param on_block_out the block write callback function +\param on_block_patch the block patch callback function +\param usr_data opaque user data passed to callback functions +\param block_size desired block size in bytes +\return error if any +*/ +GF_Err gf_isom_set_write_callback(GF_ISOFile *isom_file, + GF_Err (*on_block_out)(void *cbk, u8 *data, u32 block_size), + GF_Err (*on_block_patch)(void *usr_data, u8 *block, u32 block_size, u64 block_offset, Bool is_insert), + void *usr_data, + u32 block_size); + +/*! @} */ + +#endif // GPAC_DISABLE_ISOM_WRITE + +/*! +\addtogroup isomp4sys_grp ISOBMFF MPEG-4 Systems +\ingroup iso_grp + +MPEG-4 Systems extensions +@{ +*/ + + +/*! MPEG-4 ProfileAndLevel codes*/ +typedef enum +{ + /*! Audio PL*/ + GF_ISOM_PL_AUDIO, + /*! Visual PL*/ + GF_ISOM_PL_VISUAL, + /*! Graphics PL*/ + GF_ISOM_PL_GRAPHICS, + /*! Scene PL*/ + GF_ISOM_PL_SCENE, + /*! OD PL*/ + GF_ISOM_PL_OD, + /*! MPEG-J PL*/ + GF_ISOM_PL_MPEGJ, + /*! not a profile, just set/unset inlineFlag*/ + GF_ISOM_PL_INLINE, +} GF_ISOProfileLevelType; + +/*! gets MPEG-4 subtype of a sample description entry (eg, mp4a, mp4v, enca, encv, resv, etc...) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\return the media type FOUR CHAR code type of an MPEG4 media, or 0 if not MPEG-4 subtype + */ +u32 gf_isom_get_mpeg4_subtype(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! fetches the root OD of a file (can be NULL, OD or IOD, you have to check its tag) +\param isom_file the target ISO file +\return the OD/IOD if any. Caller must destroy the descriptor +*/ +GF_Descriptor *gf_isom_get_root_od(GF_ISOFile *isom_file); + +/*! disable OD conversion from ISOM internal to regular OD tags +\param isom_file the target ISO file +\param disable if TRUE, ODs and ESDs will not be converted +*/ +void gf_isom_disable_odf_conversion(GF_ISOFile *isom_file, Bool disable); + +/*! checks the presence of a track in rood OD/IOD +\param isom_file the target ISO file +\param trackNumber the target track +\return 0: NO, 1: YES, 2: ERROR*/ +u8 gf_isom_is_track_in_root_od(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the GF_ESD given the sampleDescriptionIndex +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\return the ESD associated to the sample description index, or NULL if error or not supported. Caller must destroy the ESD*/ +GF_ESD *gf_isom_get_esd(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets the decoderConfigDescriptor given the sampleDescriptionIndex +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\return the decoder configuration descriptor associated to the sample description index, or NULL if error or not supported. Caller must destroy the descriptor +*/ +GF_DecoderConfig *gf_isom_get_decoder_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! sets default TrackID (or ES_ID) for clock references. +\param isom_file the target ISO file +\param trackNumber the target track to set as a clock reference. If 0, default sync track ID is reseted and will be reassigned at next ESD fetch*/ +void gf_isom_set_default_sync_track(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets the profile and level value for MPEG-4 streams +\param isom_file the target ISO file +\param PL_Code the target profile to query file +\return the profile and level value, 0xFF if not defined +*/ +u8 gf_isom_get_pl_indication(GF_ISOFile *isom_file, GF_ISOProfileLevelType PL_Code); + +/*! finds the first ObjectDescriptor using the given track by inspecting all OD tracks +\param isom_file the target ISO file +\param trackNumber the target track +\return the OD ID if dound, 0 otherwise*/ +u32 gf_isom_find_od_id_for_track(GF_ISOFile *isom_file, u32 trackNumber); + +/*! sets a profile and level indication for the movie iod (created if needed) +\note Use for MPEG-4 Systems only +if the flag is ProfileLevel is 0 this means the movie doesn't require +the specific codec (equivalent to 0xFF value in MPEG profiles) +\param isom_file the target ISO file +\param PL_Code the profile and level code to set +\param ProfileLevel the profile and level value to set +\return error if any +*/ +GF_Err gf_isom_set_pl_indication(GF_ISOFile *isom_file, u8 PL_Code, GF_ISOProfileLevelType ProfileLevel); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! sets the rootOD ID of the movie if you need it. By default, movies are created without root ODs +\note Use for MPEG-4 Systems only +\param isom_file the target ISO file +\param OD_ID ID to assign to the root OD/IOD +\return error if any +*/ +GF_Err gf_isom_set_root_od_id(GF_ISOFile *isom_file, u32 OD_ID); + +/*! sets the rootOD URL of the movie if you need it (only needed to create an empty file pointing +to external ressource) +\note Use for MPEG-4 Systems only +\param isom_file the target ISO file +\param url_string the URL to assign to the root OD/IOD +\return error if any */ +GF_Err gf_isom_set_root_od_url(GF_ISOFile *isom_file, const char *url_string); -//returns the number of entries in UDTA of the track if trackNumber is not 0, or of the movie otherwise -u32 gf_isom_get_udta_count(GF_ISOFile *movie, u32 trackNumber); +/*! removes the root OD +\note Use for MPEG-4 Systems only +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_remove_root_od(GF_ISOFile *isom_file); + +/*! adds a system descriptor to the OD of the movie +\note Use for MPEG-4 Systems only +\param isom_file the target ISO file +\param theDesc the descriptor to add +\return error if any +*/ +GF_Err gf_isom_add_desc_to_root_od(GF_ISOFile *isom_file, const GF_Descriptor *theDesc); + +/*! adds a track to the root OD +\note Use for MPEG-4 Systems only +\param isom_file the target ISO file +\param trackNumber the track to add to the root OD +\return error if any +*/ +GF_Err gf_isom_add_track_to_root_od(GF_ISOFile *isom_file, u32 trackNumber); + +/*! removes a track to the root OD +\note Use for MPEG-4 Systems only +\param isom_file the target ISO file +\param trackNumber the track to remove from the root OD +\return error if any +*/ +GF_Err gf_isom_remove_track_from_root_od(GF_ISOFile *isom_file, u32 trackNumber); + + +/*! creates a new MPEG-4 sample description in a track + +\note Used for MPEG-4 Systems, AAC and MPEG-4 Visual (part 2) + +\param isom_file the target ISO file +\param trackNumber the target track number +\param esd the ESD to use for that sample description +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to index of the new sample description +\return error if any +*/ +GF_Err gf_isom_new_mpeg4_description(GF_ISOFile *isom_file, u32 trackNumber, const GF_ESD *esd, const char *URLname, const char *URNname, u32 *outDescriptionIndex); + +/*! changes an MPEG-4 sample description +\note Used for MPEG-4 Systems, AAC and MPEG-4 Visual (part 2) +\warning This will replace the whole ESD +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description +\param newESD the new ESD to use for that sample description +\return error if any +*/ +GF_Err gf_isom_change_mpeg4_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, const GF_ESD *newESD); + +/*! adds an MPEG-4 systems descriptor to the ESD of a sample description +\note Used for MPEG-4 Systems, AAC and MPEG-4 Visual (part 2) +\warning This will replace the whole ESD +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description +\param theDesc the descriptor to add to the ESD of the sample description +\return error if any +*/ +GF_Err gf_isom_add_desc_to_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, const GF_Descriptor *theDesc); -//returns the type (box 4CC and UUID if any) of the given entry in UDTA of the track if trackNumber is not 0, or of the movie otherwise. udta_idx is 1-based index. -GF_Err gf_isom_get_udta_type(GF_ISOFile *movie, u32 trackNumber, u32 udta_idx, u32 *UserDataType, bin128 *UUID); +/*! clones IOD PLs from orig to dest if any +\param orig_file the source ISO file +\param dest_file the destination ISO file +\return error if any +*/ +GF_Err gf_isom_clone_pl_indications(GF_ISOFile *orig_file, GF_ISOFile *dest_file); -/* Gets the number of UserDataItems with the same ID / UUID in the desired track or -in the movie if trackNumber is set to 0*/ -u32 gf_isom_get_user_data_count(GF_ISOFile *the_file, u32 trackNumber, u32 UserDataType, bin128 UUID); -/* Gets the UserData for the specified item from the track or the movie if trackNumber is set to 0 -data is allocated by the function and is yours to free -you musty pass (userData != NULL && *userData=NULL) +/*deletes chapter (1-based index, index 0 for all)*/ +GF_Err gf_isom_remove_chapter(GF_ISOFile *the_file, u32 trackNumber, u32 index); -if UserDataIndex is 0, all boxes with type==UserDataType will be serialized (including box header and size) in the buffer +/*! associates a given SL config with a given ESD while extracting the OD information +This is useful while reading the IOD / OD stream of an MP4 file. Note however that +only full AUs are extracted, therefore the calling application must SL-packetize the streams + +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex set to the sample description index corresponding to this sample (optional, can be NULL) +\param slConfig the SL configuration descriptor to set. The descriptor is copied by the API for further use. A NULL pointer will result +in using the default SLConfig (predefined = 2) remapped to predefined = 0 +\return error if any */ -GF_Err gf_isom_get_user_data(GF_ISOFile *the_file, u32 trackNumber, u32 UserDataType, bin128 UUID, u32 UserDataIndex, char **userData, u32 *userDataSize); +GF_Err gf_isom_set_extraction_slc(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, const GF_SLConfig *slConfig); +#endif //GPAC_DISABLE_ISOM_WRITE -/*gets the media language code (3 chars if old files, longer if BCP-47 */ -GF_Err gf_isom_get_media_language(GF_ISOFile *the_file, u32 trackNumber, char **lang); -/* gets the i-th track kind (0-based) */ -u32 gf_isom_get_track_kind_count(GF_ISOFile *the_file, u32 trackNumber); -GF_Err gf_isom_get_track_kind(GF_ISOFile *the_file, u32 trackNumber, u32 index, char **scheme, char **value); +/*! @} */ -/*Unknown sample description*/ +/*! +\addtogroup isostsd_grp ISOBMFF Sample Descriptions +\ingroup iso_grp + +Sample Description functions are used to query and set codec parameters of a track + +@{ +*/ + +/*! Unknown sample description*/ typedef struct { - /*codec tag is the containing box's tag, 0 if UUID is used*/ + /*! codec tag is the containing box's tag, 0 if UUID is used*/ u32 codec_tag; - /*entry UUID if no tag is used*/ + /*! entry UUID if no tag is used*/ bin128 UUID; - + /*! codec version*/ u16 version; + /*! codec revision*/ u16 revision; + /*! vendor four character code*/ u32 vendor_code; - /*video codecs only*/ + /*! temporal quality, video codecs only*/ u32 temporal_quality; + /*! spatial quality, video codecs only*/ u32 spatial_quality; - u16 width, height; - u32 h_res, v_res; + /*! width in pixels, video codecs only*/ + u16 width; + /*! height in pixels, video codecs only*/ + u16 height; + /*! horizontal resolution as 16.16 fixed point, video codecs only*/ + u32 h_res; + /*! vertical resolution as 16.16 fixed point, video codecs only*/ + u32 v_res; + /*! bit depth resolution in bits, video codecs only*/ u16 depth; + /*! color table, video codecs only*/ u16 color_table_index; + /*! compressor name, video codecs only*/ char compressor_name[33]; - /*audio codecs only*/ + /*! sample rate, audio codecs only*/ u32 samplerate; + /*! number of channels, audio codecs only*/ u16 nb_channels; + /*! bits per sample, audio codecs only*/ u16 bits_per_sample; + /*! indicates if QTFF signaling should be used, audio codecs only*/ + Bool is_qtff; - /*if present*/ - char *extension_buf; + /*optional, sample description specific configuration*/ + u8 *extension_buf; + /*optional, sample description specific size*/ u32 extension_buf_size; } GF_GenericSampleDescription; -/*returns wrapper for unknown entries - you must delete it yourself*/ -GF_GenericSampleDescription *gf_isom_get_generic_sample_description(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex); - -/*retrieves default values for a track fragment. Each variable is optional and -if set will contain the default value for this track samples*/ -GF_Err gf_isom_get_fragment_defaults(GF_ISOFile *the_file, u32 trackNumber, - u32 *defaultDuration, u32 *defaultSize, u32 *defaultDescriptionIndex, - u32 *defaultRandomAccess, u8 *defaultPadding, u16 *defaultDegradationPriority); - - -/*non standard extensions used for video packets in order to keep AU structure in the file format -(no normative tables for that). Info is NOT written to disk. +/*! gets an unknown sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\return generic sample description information, or NULL if error */ -/*get number of fragments for a sample */ -u32 gf_isom_get_sample_fragment_count(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber); -/*get sample fragment size*/ -u16 gf_isom_get_sample_fragment_size(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber, u32 FragmentIndex); - -/*returns 1 if file is single AV (max one audio, one video, one text and basic od/bifs)*/ -Bool gf_isom_is_single_av(GF_ISOFile *file); - -/*guess which std this file refers to. return value: - GF_ISOM_BRAND_ISOM: unrecognized std - GF_ISOM_BRAND_3GP5: 3GP file (max 1 audio, 1 video) without text track - GF_ISOM_BRAND_3GP6: 3GP file (max 1 audio, 1 video) with text track - GF_ISOM_BRAND_3GG6: 3GP file multitrack file - GF_ISOM_BRAND_3G2A: 3GP2 file - GF_ISOM_BRAND_AVC1: AVC file - FCC("ISMA"): ISMA file (may overlap with 3GP) - GF_ISOM_BRAND_MP42: any generic MP4 file (eg with BIFS/OD/MPEG-4 systems stuff) - - for files without movie, returns the file meta handler type +GF_GenericSampleDescription *gf_isom_get_generic_sample_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets the decoder configuration of a JP2 file +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\param out_dsi set to the decoder configuration - shall be freed by user +\param out_size set to the decoder configuration size +\return error if any */ -u32 gf_isom_guess_specification(GF_ISOFile *file); -/*keeps UTC edit times when storing*/ -void gf_isom_keep_utc_times(GF_ISOFile *file, Bool keep_utc); +GF_Err gf_isom_get_jp2_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u8 **out_dsi, u32 *out_size); -/*drops all modification/creation times and handler/copyright containing GPAC rev info (used for tests)*/ -void gf_isom_no_version_date_info(GF_ISOFile *file, Bool drop_info); -/*gets last UTC/timestamp values indicated for the reference track in the file if any. Returns 0 if no info found*/ -Bool gf_isom_get_last_producer_time_box(GF_ISOFile *file, u32 *refTrackID, u64 *ntp, u64 *timestamp, Bool reset_info); +/*! gets RVC (Reconvigurable Video Coding) config of a track for a given sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index (1-based) +\param rvc_predefined set to a predefined value of RVC +\param data set to the RVC config buffer if not predefined, NULL otherwise +\param size set to the RVC config buffer size +\param mime set to the associated mime type of the stream +\return error if any*/ +GF_Err gf_isom_get_rvc_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u16 *rvc_predefined, u8 **data, u32 *size, const char **mime); #ifndef GPAC_DISABLE_ISOM_WRITE +/*! sets the RVC config for the given track sample description +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description index +\param rvc_predefined the predefined RVC configuration code, 0 if not predefined +\param mime the associated mime type of the video +\param data the RVC configuration data; ignored if rvc_predefined is not 0 +\param size the size of the RVC configuration data; ignored if rvc_predefined is not 0 +\return error if any +*/ +GF_Err gf_isom_set_rvc_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u16 rvc_predefined, char *mime, u8 *data, u32 size); + + +/*! updates fields of given visual sample description - these fields are reserved in ISOBMFF, this should only be used for QT, see QTFF +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description +\param revision revision of the sample description format +\param vendor four character code of the vendor +\param temporalQ temporal quality +\param spatialQ spatial quality +\param horiz_res horizontal resolution as 16.16 fixed point number +\param vert_res vertical resolution as 16.16 fixed point number +\param frames_per_sample number of frames per media samples +\param compressor_name human readable name for the compressor +\param color_table_index color table index, use -1 if no color table (most common case) +\return error if any +*/ +GF_Err gf_isom_update_video_sample_entry_fields(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u16 revision, u32 vendor, u32 temporalQ, u32 spatialQ, u32 horiz_res, u32 vert_res, u16 frames_per_sample, const char *compressor_name, s16 color_table_index); + +/*! updates a sample description from a serialized sample description box. Only child boxes are removed in the process +\param isom_file the target ISO file +\param trackNumber the target track number +\param sampleDescriptionIndex the target sample description +\param data a serialized sample description box +\param size size of the serialized sample description +\return error if any +*/ +GF_Err gf_isom_update_sample_description_from_template(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u8 *data, u32 size); + + +/*! creates a new unknown StreamDescription in the file. +\note use this to store media not currently supported by the ISO media format or media types not implemented in this library +\param isom_file the target ISO file +\param trackNumber the target track +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param udesc generic sample description information to use +\param outDescriptionIndex set to index of the new sample description +\return error if any +*/ +GF_Err gf_isom_new_generic_sample_description(GF_ISOFile *isom_file, u32 trackNumber, const char *URLname, const char *URNname, GF_GenericSampleDescription *udesc, u32 *outDescriptionIndex); + +/*! clones a sample description without inspecting media types +\param isom_file the destination ISO file +\param trackNumber the destination track +\param orig_file the source ISO file +\param orig_track the source track +\param orig_desc_index the source sample description to clone +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to index of the new sample description +\return error if any +*/ +GF_Err gf_isom_clone_sample_description(GF_ISOFile *isom_file, u32 trackNumber, GF_ISOFile *orig_file, u32 orig_track, u32 orig_desc_index, const char *URLname, const char *URNname, u32 *outDescriptionIndex); + +/*! gets the sample description template of a track. This serializes sample description box +\param isom_file the destination ISO file +\param trackNumber the destination track +\param sampleDescriptionIndex the target sample description +\param output will be set to a newly allocated buffer containing the serialized box - caller shall free it +\param output_size will be set to the size of the allocated buffer +\return error if any +*/ +GF_Err gf_isom_get_stsd_template(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u8 **output, u32 *output_size); + +#endif // GPAC_DISABLE_ISOM_WRITE + +/*! checks if sample descriptions are the same. This does include self-contained checking and reserved flags. The specific media cfg (DSI & co) is not analysed, only a memory comparaison is done +\param f1 the first ISO file +\param tk1 the first track +\param sdesc_index1 the first sample description +\param f2 the second ISO file +\param tk2 the second track +\param sdesc_index2 the second sample description +\return GF_TRUE if sample descriptions match, GF_FALSE otherwise +*/ +Bool gf_isom_is_same_sample_description(GF_ISOFile *f1, u32 tk1, u32 sdesc_index1, GF_ISOFile *f2, u32 tk2, u32 sdesc_index2); -/******************************************************************** - EDITING/WRITING API FUNCTIONS -********************************************************************/ - -/*set the timescale of the movie*/ -GF_Err gf_isom_set_timescale(GF_ISOFile *the_file, u32 timeScale); - -/*creates a new Track. If trackID = 0, the trackID is chosen by the API -returns the track number or 0 if error*/ -u32 gf_isom_new_track(GF_ISOFile *the_file, u32 trackID, u32 MediaType, u32 TimeScale); - -/*removes the desired track - internal cross dependancies will be updated. -WARNING: any OD streams with references to this track through ODUpdate, ESDUpdate, ESDRemove commands -will be rewritten*/ -GF_Err gf_isom_remove_track(GF_ISOFile *the_file, u32 trackNumber); - -/*sets the enable flag of a track*/ -GF_Err gf_isom_set_track_enabled(GF_ISOFile *the_file, u32 trackNumber, u8 enableTrack); +/*! Generic 3GP/3GP2 config record*/ +typedef struct +{ + /*GF_4CC record type, one fo the above GF_ISOM_SUBTYPE_3GP_ * subtypes*/ + u32 type; + /*4CC vendor name*/ + u32 vendor; + /*codec version*/ + u8 decoder_version; + /*number of sound frames per IsoMedia sample, >0 and <=15. The very last sample may contain less frames. */ + u8 frames_per_sample; + /*H263 ONLY - Level*/ + u8 H263_level; + /*H263 Profile*/ + u8 H263_profile; + /*AMR(WB) ONLY - num of mode for the codec*/ + u16 AMR_mode_set; + /*AMR(WB) ONLY - changes in codec mode per sample*/ + u8 AMR_mode_change_period; +} GF_3GPConfig; -/*sets creationTime and modificationTime of the movie to the specified date*/ -GF_Err gf_isom_set_creation_time(GF_ISOFile *movie, u64 time); -/*sets creationTime and modificationTime of the track to the specified date*/ -GF_Err gf_isom_set_track_creation_time(GF_ISOFile *movie,u32 trackNumber, u64 time); -/*changes the trackID - all track references present in the file are updated -returns error if trackID is already in used in the file*/ -GF_Err gf_isom_set_track_id(GF_ISOFile *the_file, u32 trackNumber, u32 trackID); +/*! gets a 3GPP sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return the 3GP config for this sample description, NULL if not a 3GPP track +*/ +GF_3GPConfig *gf_isom_3gp_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates a 3GPP sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param config the 3GP config for this sample description +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_3gp_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_3GPConfig *config, const char *URLname, const char *URNname, u32 *outDescriptionIndex); +/*! updates the 3GPP config - subtypes shall NOT differ +\param isom_file the target ISO file +\param trackNumber the target track +\param config the 3GP config for this sample description +\param sampleDescriptionIndex the target sample description index +\return error if any +*/ +GF_Err gf_isom_3gp_config_update(GF_ISOFile *isom_file, u32 trackNumber, GF_3GPConfig *config, u32 sampleDescriptionIndex); +#endif /*GPAC_DISABLE_ISOM_WRITE*/ -/*force to rewrite all dependencies when trackID changes*/ -GF_Err gf_isom_rewrite_track_dependencies(GF_ISOFile *movie, u32 trackNumber); -/*Add samples to a track. Use streamDescriptionIndex to specify the desired stream (if several)*/ -GF_Err gf_isom_add_sample(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, const GF_ISOSample *sample); +/*! gets AVC config for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return the AVC config - user is responsible for deleting it +*/ +GF_AVCConfig *gf_isom_avc_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); +/*! gets SVC config for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return the SVC config - user is responsible for deleting it +*/ +GF_AVCConfig *gf_isom_svc_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); +/*! gets MVC config for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return the SVC config - user is responsible for deleting it +*/ +GF_AVCConfig *gf_isom_mvc_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); -//copies all sample dependency, subSample and sample group information from the given sampleNumber in source file to the last added sample in dest file -GF_Err gf_isom_copy_sample_info(GF_ISOFile *dst, u32 dst_track, GF_ISOFile *src, u32 src_track, u32 sampleNumber); +/*! AVC familiy type*/ +typedef enum +{ + /*! not an AVC codec*/ + GF_ISOM_AVCTYPE_NONE=0, + /*! AVC only*/ + GF_ISOM_AVCTYPE_AVC_ONLY, + /*! AVC+SVC in same track*/ + GF_ISOM_AVCTYPE_AVC_SVC, + /*! SVC only*/ + GF_ISOM_AVCTYPE_SVC_ONLY, + /*! AVC+MVC in same track*/ + GF_ISOM_AVCTYPE_AVC_MVC, + /*! SVC only*/ + GF_ISOM_AVCTYPE_MVC_ONLY, +} GF_ISOMAVCType; -/*Add sync shadow sample to a track. -- There must be a regular sample with the same DTS. -- Sync Shadow samples MUST be RAP -- Currently, adding sync shadow must be done in order (no sample insertion) +/*! gets the AVC family type for a sample description +\param isom_file the target ISO file +\param trackNumber the target hint track +\param sampleDescriptionIndex the target sample description index +\return the type of AVC media */ -GF_Err gf_isom_add_sample_shadow(GF_ISOFile *the_file, u32 trackNumber, GF_ISOSample *sample); - -/*add data to current sample in the track. Use this function for media with -fragmented options such as MPEG-4 video packets. This will update the data size. -CANNOT be used with OD media type*/ -GF_Err gf_isom_append_sample_data(GF_ISOFile *the_file, u32 trackNumber, char *data, u32 data_size); +GF_ISOMAVCType gf_isom_get_avc_svc_type(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); -/*sets RAP flag of last sample added to TRUE*/ -GF_Err gf_isom_set_sample_rap(GF_ISOFile *movie, u32 trackNumber); +/*! HEVC family type*/ +typedef enum +{ + /*! not an HEVC codec*/ + GF_ISOM_HEVCTYPE_NONE=0, + /*! HEVC only*/ + GF_ISOM_HEVCTYPE_HEVC_ONLY, + /*! HEVC+LHVC in same track*/ + GF_ISOM_HEVCTYPE_HEVC_LHVC, + /*! LHVC only*/ + GF_ISOM_HEVCTYPE_LHVC_ONLY, +} GF_ISOMHEVCType; -/*Add sample references to a track. The dataOffset is the offset of the data in the referenced file -you MUST have created a StreamDescription with URL or URN specifying your referenced file -Use streamDescriptionIndex to specify the desired stream (if several)*/ -GF_Err gf_isom_add_sample_reference(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, GF_ISOSample *sample, u64 dataOffset); +/*! gets the HEVC family type for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return the type of HEVC media +*/ +GF_ISOMHEVCType gf_isom_get_hevc_lhvc_type(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); -/*set the duration of the last media sample. If not set, the duration of the last sample is the -duration of the previous one if any, or media TimeScale (default value).*/ -GF_Err gf_isom_set_last_sample_duration(GF_ISOFile *the_file, u32 trackNumber, u32 duration); +/*! gets HEVC config for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return the HEVC config - user is responsible for deleting it +*/ +GF_HEVCConfig *gf_isom_hevc_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); +/*! gets LHVC config for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return the LHVC config - user is responsible for deleting it +*/ +GF_HEVCConfig *gf_isom_lhvc_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); -/*sets a track reference*/ -GF_Err gf_isom_set_track_reference(GF_ISOFile *the_file, u32 trackNumber, u32 referenceType, u32 ReferencedTrackID); +/*! gets AV1 config for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return the AV1 config - user is responsible for deleting it +*/ +GF_AV1Config *gf_isom_av1_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); -/*removes a track reference*/ -GF_Err gf_isom_remove_track_reference(GF_ISOFile *the_file, u32 trackNumber, u32 referenceType, u32 ReferenceIndex); +/*! gets VP8/9 config for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return the VP8/9 config - user is responsible for deleting it +*/ +GF_VPConfig *gf_isom_vp_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); -/*sets track handler name. name is either NULL (reset), a UTF-8 formatted string or a UTF8 file -resource in the form "file://path/to/file_utf8" */ -GF_Err gf_isom_set_handler_name(GF_ISOFile *the_file, u32 trackNumber, const char *nameUTF8); +/*! gets DOVI config for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return the DOVI config - user is responsible for deleting it +*/ +GF_DOVIDecoderConfigurationRecord* gf_isom_dovi_config_get(GF_ISOFile* isom_file, u32 trackNumber, u32 sampleDescriptionIndex); -/*Update the sample size table - this is needed when using @gf_isom_append_sample_data in case the resulting samples -are of same sizes (typically in 3GP speech tracks)*/ -GF_Err gf_isom_refresh_size_info(GF_ISOFile *file, u32 trackNumber); +/*! checks if some tracks in file needs layer reconstruction +\param isom_file the target ISO file +\return GF_TRUE if track dependencies implying extractors or implicit reconstruction are found, GF_FALSE otherwise +*/ +Bool gf_isom_needs_layer_reconstruction(GF_ISOFile *isom_file); -/*return the duration of the movie, 0 if error*/ -GF_Err gf_isom_update_duration(GF_ISOFile *the_file); +/*! NALU extract modes and flags*/ +typedef enum +{ + /*! all extractors are rewritten*/ + GF_ISOM_NALU_EXTRACT_DEFAULT = 0, + /*! all extractors are skipped but NALU data from this track is kept*/ + GF_ISOM_NALU_EXTRACT_LAYER_ONLY, + /*! all extractors are kept (untouched sample) - used for dumping modes*/ + GF_ISOM_NALU_EXTRACT_INSPECT, + /*! above mode is applied and PPS/SPS/... are appended in the front of every IDR*/ + GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG = 1<<16, + /*! above mode is applied and all start codes are rewritten (xPS inband as well)*/ + GF_ISOM_NALU_EXTRACT_ANNEXB_FLAG = 2<<17, + /*! above mode is applied and VDRD NAL unit is inserted before SVC slice*/ + GF_ISOM_NALU_EXTRACT_VDRD_FLAG = 1<<18, + /*! all extractors are skipped and only tile track data is kept*/ + GF_ISOM_NALU_EXTRACT_TILE_ONLY = 1<<19 +} GF_ISONaluExtractMode; -/*Update Sample functions*/ +/*! sets the NALU extraction mode for this track +\param isom_file the target ISO file +\param trackNumber the target track +\param nalu_extract_mode the NALU extraction mode to set +\return error if any +*/ +GF_Err gf_isom_set_nalu_extract_mode(GF_ISOFile *isom_file, u32 trackNumber, GF_ISONaluExtractMode nalu_extract_mode); +/*! gets the NALU extraction mode for this track +\param isom_file the target ISO file +\param trackNumber the target track +\return the NALU extraction mode used +*/ +GF_ISONaluExtractMode gf_isom_get_nalu_extract_mode(GF_ISOFile *isom_file, u32 trackNumber); -/*update a given sample of the media. -@data_only: if set, only the sample data is updated, not other info*/ -GF_Err gf_isom_update_sample(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber, GF_ISOSample *sample, Bool data_only); +/*! gets the composition offset shift if any for track using negative ctts +\param isom_file the target ISO file +\param trackNumber the target track +\return the composition offset shift or 0 +*/ +s32 gf_isom_get_composition_offset_shift(GF_ISOFile *isom_file, u32 trackNumber); -/*update a sample reference in the media. Note that the sample MUST exists, -that sample->data MUST be NULL and sample->dataLength must be NON NULL;*/ -GF_Err gf_isom_update_sample_reference(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber, GF_ISOSample *sample, u64 data_offset); +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates a new AVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the AVC config for this sample description +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_avc_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_AVCConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); +/*! updates an AVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index to update +\param cfg the AVC config for this sample description +\return error if any +*/ +GF_Err gf_isom_avc_config_update(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_AVCConfig *cfg); + +/*! creates a new SVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the SVC config for this sample description +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_svc_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_AVCConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); +/*! updates an SVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index to update +\param cfg the AVC config for this sample description +\param is_additional if set, the SVCConfig will be added to the AVC sample description, otherwise the sample description will be SVC-only +\return error if any +*/ +GF_Err gf_isom_svc_config_update(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_AVCConfig *cfg, Bool is_additional); +/*! deletes an SVC sample description +\warning associated samples if any are NOT deleted +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index to delete +\return error if any +*/ +GF_Err gf_isom_svc_config_del(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! creates a new MVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the SVC config for this sample description +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_mvc_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_AVCConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); + +/*! updates an MVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index to update +\param cfg the AVC config for this sample description +\param is_additional if set, the MVCConfig will be added to the AVC sample description, otherwise the sample description will be MVC-only +\return error if any +*/ +GF_Err gf_isom_mvc_config_update(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_AVCConfig *cfg, Bool is_additional); + +/*! deletes an MVC sample description +\warning associated samples if any are NOT deleted +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index to delete +\return error if any +*/ +GF_Err gf_isom_mvc_config_del(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! sets avc3 entry type (inband SPS/PPS) instead of avc1 (SPS/PPS in avcC box) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param keep_xps if set to GF_TRUE, keeps parameter set in the configuration record otherwise removes them +\return error if any +*/ +GF_Err gf_isom_avc_set_inband_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, Bool keep_xps); + +/*! sets hev1 entry type (inband SPS/PPS) instead of hvc1 (SPS/PPS in hvcC box) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param keep_xps if set to GF_TRUE, keeps parameter set in the configuration record otherwise removes them +\return error if any +*/ +GF_Err gf_isom_hevc_set_inband_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, Bool keep_xps); -/*Remove a given sample*/ -GF_Err gf_isom_remove_sample(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber); +/*! sets lhe1 entry type instead of lhc1 but keep lhcC box intact +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return error if any +*/ +GF_Err gf_isom_lhvc_force_inband_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! sets hvt1 entry type (tile track) or hev2/hvc2 type if is_base_track is set. It is the use responsability to set the tbas track reference to the base hevc track +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param cfg may be set to the tile track configuration to indicate sub-profile of the tile, or NULL +\param is_base_track if set to GF_TRUE, indicates this is a tile base track, otherwise this is a tile track +\return error if any +*/ +GF_Err gf_isom_hevc_set_tile_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_HEVCConfig *cfg, Bool is_base_track); + +/*! creates a new HEVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the HEVC config for this sample description +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_hevc_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_HEVCConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); + +/*! updates an HEVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index to update +\param cfg the HEVC config for this sample description +\return error if any +*/ +GF_Err gf_isom_hevc_config_update(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_HEVCConfig *cfg); -/*changes media time scale - if force_rescale is 1, only the media timescale is changed but media times are not updated */ -GF_Err gf_isom_set_media_timescale(GF_ISOFile *the_file, u32 trackNumber, u32 new_timescale, Bool force_rescale); +/*! Updates L-HHVC config*/ +typedef enum { + //! changes track type to LHV1/LHE1: no base nor extractors in track, just enhancement layers + GF_ISOM_LEHVC_ONLY = 0, + //! changes track type to HVC2/HEV2: base and extractors/enh. in track + GF_ISOM_LEHVC_WITH_BASE, + //! changes track type to HVC1/HEV1 with additional cfg: base and enh. in track no extractors + GF_ISOM_LEHVC_WITH_BASE_BACKWARD, + //! changes track type to HVC2/HEV2 for tile base tracks + GF_ISOM_HEVC_TILE_BASE, +} GF_ISOMLHEVCTrackType; -/*set the save file name of the (edited) movie. -If the movie is edited, the default fileName is avp_#openName) -NOTE: you cannot save an edited file under the same name (overwrite not allowed) -If the movie is created (WRITE mode), the default filename is #openName*/ -GF_Err gf_isom_set_final_name(GF_ISOFile *the_file, char *filename); +/*! updates an HEVC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index to update +\param cfg the LHVC config for this sample description +\param track_type indicates the LHVC track type to set +\return error if any +*/ +GF_Err gf_isom_lhvc_config_update(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_HEVCConfig *cfg, GF_ISOMLHEVCTrackType track_type); + +/*! sets nalu size length +\warning any previously added samples must be rewritten by the caller +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index to update +\param nalu_size_length the new NALU size length in bytes +\return error if any +*/ +GF_Err gf_isom_set_nalu_length_field(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 nalu_size_length); + +/*! creates new VPx config +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the VPx config for this sample description +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\param vpx_type four character code of entry ('vp08', 'vp09' or 'vp10') +\return error if any +*/ +GF_Err gf_isom_vp_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_VPConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex, u32 vpx_type); -/*set the storage mode of a file (FLAT, STREAMABLE, INTERLEAVED)*/ -GF_Err gf_isom_set_storage_mode(GF_ISOFile *the_file, u8 storageMode); -u8 gf_isom_get_storage_mode(GF_ISOFile *the_file); +/*! creates new AV1 config +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the AV1 config for this sample description +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_av1_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_AV1Config *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); -/*set the interleaving time of media data (INTERLEAVED mode only) -InterleaveTime is in MovieTimeScale*/ -GF_Err gf_isom_set_interleave_time(GF_ISOFile *the_file, u32 InterleaveTime); -u32 gf_isom_get_interleave_time(GF_ISOFile *the_file); -/*forces usage of 64 bit chunk offsets*/ -void gf_isom_force_64bit_chunk_offset(GF_ISOFile *the_file, Bool set_on); +#endif /*GPAC_DISABLE_ISOM_WRITE*/ -/*set the copyright in one language.*/ -GF_Err gf_isom_set_copyright(GF_ISOFile *the_file, const char *threeCharCode, char *notice); -/*deletes copyright (1-based indexes)*/ -GF_Err gf_isom_remove_copyright(GF_ISOFile *the_file, u32 index); +/*! Sample entry description for 3GPP DIMS*/ +typedef struct +{ + /*! profile*/ + u8 profile; + /*! level*/ + u8 level; + /*! number of components in path*/ + u8 pathComponents; + /*! full request*/ + Bool fullRequestHost; + /*! stream type*/ + Bool streamType; + /*! has redundant sample (carousel)*/ + u8 containsRedundant; + /*! text encoding string*/ + const char *textEncoding; + /*! content encoding string*/ + const char *contentEncoding; + /*! script string*/ + const char *content_script_types; + /*! mime type string*/ + const char *mime_type; + /*! xml schema location string*/ + const char *xml_schema_loc; +} GF_DIMSDescription; -/*add a kind type to the track */ -GF_Err gf_isom_add_track_kind(GF_ISOFile *movie, u32 trackNumber, const char *schemeURI, const char *value); -/*removes a kind type to the track, all if NULL params */ -GF_Err gf_isom_remove_track_kind(GF_ISOFile *movie, u32 trackNumber, const char *schemeURI, const char *value); +/*! gets a DIMS sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param desc set to the DIMS description +\return error if any +*/ +GF_Err gf_isom_get_dims_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_DIMSDescription *desc); -/*changes the handler type of the media*/ -GF_Err gf_isom_set_media_type(GF_ISOFile *movie, u32 trackNumber, u32 new_type); +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates a DIMS sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param desc the DIMS config for this sample description +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_new_dims_description(GF_ISOFile *isom_file, u32 trackNumber, GF_DIMSDescription *desc, const char *URLname, const char *URNname, u32 *outDescriptionIndex); +#endif /*GPAC_DISABLE_ISOM_WRITE*/ -/*changes the type of the sampleDescriptionBox - USE AT YOUR OWN RISK, the file may not be understood afterwards*/ -GF_Err gf_isom_set_media_subtype(GF_ISOFile *movie, u32 trackNumber, u32 sampleDescriptionIndex, u32 new_type); +/*! AC-3 config record extension for EAC-3 - see dolby specs*/ +struct __ec3_stream +{ + /*! AC3 fs code*/ + u8 fscod; + /*! AC3 bsid code*/ + u8 bsid; + /*! AC3 bs mode*/ + u8 bsmod; + /*! AC3 ac mode*/ + u8 acmod; + /*! LF on*/ + u8 lfon; + /*! asvc mode, only for EC3*/ + u8 asvc; + /*! deps, only for EC3*/ + u8 nb_dep_sub; + /*! channel loc, only for EC3*/ + u8 chan_loc; +}; -GF_Err gf_isom_set_alternate_group_id(GF_ISOFile *movie, u32 trackNumber, u32 groupId); +/*! AC3 config record*/ +typedef struct +{ + /*! indicates if ec3*/ + u8 is_ec3; + /*! number of streams : + 1 for AC3 + max 8 for EC3, main stream is included + */ + u8 nb_streams; + /*! if AC3 this is the bitrate code, otherwise cumulated data rate of EC3 streams*/ + u16 brcode; + struct __ec3_stream streams[8]; +} GF_AC3Config; -/*add chapter info: -if trackNumber is 0, the chapter info is added to the movie, otherwise to the track -@timestamp: chapter start time in milliseconds. Chapters are added in order to the file. If a chapter with same timestamp - is found, its name is updated but no entry is created. -@name: chapter name. If NULL, defaults to 'Chapter N' +/*! gets an AC3 sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return AC-3 config */ -GF_Err gf_isom_add_chapter(GF_ISOFile *the_file, u32 trackNumber, u64 timestamp, char *name); - -/*deletes copyright (1-based index, index 0 for all)*/ -GF_Err gf_isom_remove_chapter(GF_ISOFile *the_file, u32 trackNumber, u32 index); - -/*set watermark info for movie*/ -GF_Err gf_isom_set_watermark(GF_ISOFile *the_file, bin128 UUID, u8* data, u32 length); +GF_AC3Config *gf_isom_ac3_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! parses an AC3/EC3 sample description +\param dsi the encoded config +\param dsi_len the encoded config size +\param is_ec3 indicates that the encoded config is for an EC3 track +\param cfg the AC3/EC3 confgi to fill +\return Error if any +*/ +GF_Err gf_isom_ac3_config_parse(u8 *dsi, u32 dsi_len, Bool is_ec3, GF_AC3Config *cfg); -/*Track Edition functions - used to change the normal playback of the media if desired -NOTE: IT IS THE USER RESPONSABILITY TO CREATE A CONSISTENT TIMELINE FOR THE TRACK -This API provides the basic hooks and some basic consistency checking -but can not check the desired functionality of the track edits +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates an AC3 sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param cfg the AC3 config for this sample description +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\return error if any */ +GF_Err gf_isom_ac3_config_new(GF_ISOFile *isom_file, u32 trackNumber, GF_AC3Config *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex); +#endif /*GPAC_DISABLE_ISOM_WRITE*/ -/*update or insert a new edit segment in the track time line. Edits are used to modify -the media normal timing. EditTime and EditDuration are expressed in Movie TimeScale -If a segment with EditTime already exists, IT IS ERASED -if there is a segment before this new one, its duration is adjust to match EditTime of -the new segment -WARNING: The first segment always have an EditTime of 0. You should insert an empty or dwelled segment first.*/ -GF_Err gf_isom_set_edit_segment(GF_ISOFile *the_file, u32 trackNumber, u64 EditTime, u64 EditDuration, u64 MediaTime, u8 EditMode); +/*! gets a FLAC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param dsi set to the flac decoder config - shall be freeed by caller +\param dsi_size set to the size of the flac decoder config +\return error if any +*/ +GF_Err gf_isom_flac_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u8 **dsi, u32 *dsi_size); -/*same as above except only modifies duartion type and mediaType*/ -GF_Err gf_isom_modify_edit_segment(GF_ISOFile *the_file, u32 trackNumber, u32 seg_index, u64 EditDuration, u64 MediaTime, u8 EditMode); -/*same as above except only appends new segment*/ -GF_Err gf_isom_append_edit_segment(GF_ISOFile *the_file, u32 trackNumber, u64 EditDuration, u64 MediaTime, u8 EditMode); +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates a FLAC sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param metadata the flac decoder config buffer +\param metadata_size the size of flac decoder config +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_flac_config_new(GF_ISOFile *isom_file, u32 trackNumber, u8 *metadata, u32 metadata_size, const char *URLname, const char *URNname, u32 *outDescriptionIndex); +#endif /*GPAC_DISABLE_ISOM_WRITE*/ -/*remove the edit segments for the whole track*/ -GF_Err gf_isom_remove_edit_segments(GF_ISOFile *the_file, u32 trackNumber); +/*! gets a OPUS sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param dsi set to the OPUS decoder config - shall be freeed by caller +\param dsi_size set to the size of the OPUS decoder config +\return error if any +*/ +GF_Err gf_isom_opus_config_get(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u8 **dsi, u32 *dsi_size); -/*remove the given edit segment (1-based index). If this is not the last segment, the next segment duration -is updated to maintain a continous timeline*/ -GF_Err gf_isom_remove_edit_segment(GF_ISOFile *the_file, u32 trackNumber, u32 seg_index); +#ifndef GPAC_DISABLE_ISOM_WRITE -/*Updates edit list after track edition: all edit entries with aduration or media starttime larger than the media duration are clamped to media duration*/ -GF_Err gf_isom_update_edit_list_duration(GF_ISOFile *file, u32 track); +/*! creates a motion jpeg 2000 sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\param dsi the jpeg2000 decoder config buffer +\param dsi_len the size of jpeg2000 decoder config +\return error if any +*/ +GF_Err gf_isom_new_mj2k_description(GF_ISOFile *isom_file, u32 trackNumber, const char *URLname, const char *URNname, u32 *outDescriptionIndex, u8 *dsi, u32 dsi_len); +#endif /*GPAC_DISABLE_ISOM_WRITE*/ -/* - User Data Manipulation +#ifndef GPAC_DISABLE_ISOM_WRITE - You can add specific typed data to either a track or the movie: the UserData - The type must be formated as a FourCC if you have a registered 4CC type - but the usual is to set a UUID (128 bit ID for box type) which never conflict - with existing structures in the format - To manipulate a UUID user data set the UserDataType to 0 and specify a valid UUID. -Otherwise the UUID parameter is ignored - Several items with the same ID or UUID can be added (this allows you to store any - kind/number of private information under a unique ID / UUID) +/*! creates a time code metadata sample description +\note frames_per_counter_tick<0 disables counter flag but signals frames_per_tick +\param isom_file the target ISO file +\param trackNumber the target track +\param fps_num the frame rate numerator +\param fps_den the frame rate denumerator (frame rate numerator will be track media timescale) +\param frames_per_counter_tick if not 0, enables counter mode (sample data is an counter) and use this value as number of frames per counter tick. Otherwise, disables counter mode (sample data write h,m,s,frames) +\param is_drop indicates that the time code in samples is a drop timecode +\param is_counter indicates that the counter flag should be set +\param outDescriptionIndex set to the index of the created sample description +\return error if any */ -/*Add a user data item in the desired track or in the movie if TrackNumber is 0*/ -GF_Err gf_isom_add_user_data(GF_ISOFile *the_file, u32 trackNumber, u32 UserDataType, bin128 UUID, char *data, u32 DataLength); +GF_Err gf_isom_tmcd_config_new(GF_ISOFile *isom_file, u32 trackNumber, u32 fps_num, u32 fps_den, s32 frames_per_counter_tick, Bool is_drop, Bool is_counter, u32 *outDescriptionIndex); -/*remove all user data items from the desired track or from the movie if TrackNumber is 0*/ -GF_Err gf_isom_remove_user_data(GF_ISOFile *the_file, u32 trackNumber, u32 UserDataType, bin128 UUID); +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +/*! gets information of a time code metadata sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param tmcd_flags set to the timecode description flags +\param tmcd_fps_num set to fps numerator of timecode description +\param tmcd_fps_den set to fps denominator of timecode description +\param tmcd_fpt set to the ticks per second for counter mode (tmcd_flags & 0x1) +\return error if any +*/ +GF_Err gf_isom_get_tmcd_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *tmcd_flags, u32 *tmcd_fps_num, u32 *tmcd_fps_den, u32 *tmcd_fpt); + +/*! gets information of a raw PCM sample description, ISOBMFF style +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param flags set to the pcm config flags (0: big endian, 1: little endian) +\param pcm_size set to PCM sample size (per channel, 16, 24, 32, 64 +\return error if any +*/ +GF_Err gf_isom_get_pcm_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *flags, u32 *pcm_size); -/*remove a user data item from the desired track or from the movie if TrackNumber is 0 -use the UDAT read functions to get the item index*/ -GF_Err gf_isom_remove_user_data_item(GF_ISOFile *the_file, u32 trackNumber, u32 UserDataType, bin128 UUID, u32 UserDataIndex); +/*! @} */ -/*remove track, moov (trackNumber=0) or file-level (trackNumber=0xFFFFFFFF) UUID box of matching type*/ -GF_Err gf_isom_remove_uuid(GF_ISOFile *movie, u32 trackNumber, bin128 UUID); -/*adds track, moov (trackNumber=0) or file-level (trackNumber=0xFFFFFFFF) UUID box of given type*/ -GF_Err gf_isom_add_uuid(GF_ISOFile *movie, u32 trackNumber, bin128 UUID, const char *data, u32 data_size); -/*Add a user data item in the desired track or in the movie if TrackNumber is 0, using a serialzed buffer of ISOBMFF boxes*/ -GF_Err gf_isom_add_user_data_boxes(GF_ISOFile *the_file, u32 trackNumber, char *data, u32 DataLength); +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS +/*! +\addtogroup isofragred_grp Fragmented ISOBMFF Read +\ingroup iso_grp -/* - Update of the Writing API for IsoMedia Version 2 +This describes function specific to fragmented ISOBMF files + +@{ */ -/*use a compact track version for sample size. This is not usually recommended -except for speech codecs where the track has a lot of small samples -compaction is done automatically while writing based on the track's sample sizes*/ -GF_Err gf_isom_use_compact_size(GF_ISOFile *the_file, u32 trackNumber, u8 CompactionOn); +/*! checks if a movie file is fragmented +\param isom_file the target ISO file +\return GF_FALSE if movie isn't fragmented, GF_TRUE otherwise +*/ +Bool gf_isom_is_fragmented(GF_ISOFile *isom_file); +/*! checks if a movie file is fragmented +\param isom_file the target ISO file +\param TrackID the target track +\return GF_FALSE if track isn't fragmented, GF_TRUE otherwise*/ +Bool gf_isom_is_track_fragmented(GF_ISOFile *isom_file, GF_ISOTrackID TrackID); + +/*! checks if a file has a top styp box +\param isom_file the target ISO file +\param brand set to the major brand of the styp box +\param version set to version of the styp box +\return GF_TRUE of the file has a styp box, GF_FALSE otherwise +*/ +Bool gf_isom_has_segment(GF_ISOFile *isom_file, u32 *brand, u32 *version); +/*! gets number of movie fragments in the file +\param isom_file the target ISO file +\returns number of movie fragments in the file, 0 if none +*/ +u32 gf_isom_segment_get_fragment_count(GF_ISOFile *isom_file); +/*! gets number of track fragments in the indicated movie fragment +\param isom_file the target ISO file +\param moof_index the target movie fragment (1-based index) +\return number of track fragments, 0 if none +*/ +u32 gf_isom_segment_get_track_fragment_count(GF_ISOFile *isom_file, u32 moof_index); +/*! get the track fragment decode time of a track fragment +\param isom_file the target ISO file +\param moof_index the target movie fragment (1-based index) +\param traf_index the target track fragment (1-based index) +\param decode_time set to the track fragment decode time if present, 0 otherwise +\return the track ID of the track fragment +*/ +u32 gf_isom_segment_get_track_fragment_decode_time(GF_ISOFile *isom_file, u32 moof_index, u32 traf_index, u64 *decode_time); + +/*! enables single moof mode. In single moof mode, file is parsed only one moof/mdat at a time + in order to proceed to next moof, \ref gf_isom_reset_data_offset must be called to parse the next moof +\param isom_file the target ISO file +\param mode if GF_TRUE, enables single moof mode; otherwise disables it +*/ +void gf_isom_set_single_moof_mode(GF_ISOFile *isom_file, Bool mode); -/*sets the brand of the movie*/ -GF_Err gf_isom_set_brand_info(GF_ISOFile *the_file, u32 MajorBrand, u32 MinorVersion); +/*! gets closest file offset for the given time, when the file uses an segment index (sidx) +\param isom_file the target ISO file +\param start_time the start time in seconds +\param offset set to the file offset of the segment containing the desired time +\return error if any +*/ +GF_Err gf_isom_get_file_offset_for_time(GF_ISOFile *isom_file, Double start_time, u64 *offset); -/*adds or remove an alternate brand for the movie*/ -GF_Err gf_isom_modify_alternate_brand(GF_ISOFile *the_file, u32 Brand, u8 AddIt); +/*! gets sidx duration, when the file uses an segment index (sidx) +\param isom_file the target ISO file +\param sidx_dur set to the total duration documented in the segment index +\param sidx_timescale set timescale used to represent the duration in the segment index +\return error if any +*/ +GF_Err gf_isom_get_sidx_duration(GF_ISOFile *isom_file, u64 *sidx_dur, u32 *sidx_timescale); -/*removes all alternate brands except major brand*/ -GF_Err gf_isom_reset_alt_brands(GF_ISOFile *movie); -/*set the number of padding bits at the end of a given sample if needed -if the function is never called the padding bit info is ignored -this MUST be called on an existin sample*/ -GF_Err gf_isom_set_sample_padding_bits(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber, u8 NbBits); +/*! refreshes a fragmented file +A file being downloaded may be a fragmented file. In this case only partial info +is available once the file is successfully open (gf_isom_open_progressive), and since there is +no information wrt number fragments (which could actually be generated on the fly +at the sender side), you must call this function on regular basis in order to +load newly downloaded fragments. Note this may result in Track/Movie duration changes +and SampleCount change too ... +This function should also be called when using memory read (gmem://) to refresh the underlying bitstream after appendin data to your blob. +In the case where the file is not fragmented, no further box parsing will be done. -/*since v2 you must specify w/h of video tracks for authoring tools (no decode the video cfg / first sample)*/ -GF_Err gf_isom_set_visual_info(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, u32 Width, u32 Height); +\param isom_file the target ISO file +\param MissingBytes set to the number of missing bytes to parse the last incomplete top-level box found +\param new_location if set, the previous bitstream is changed to this new location, otherwise it is refreshed (disk flush) +\return error if any +*/ +GF_Err gf_isom_refresh_fragmented(GF_ISOFile *isom_file, u64 *MissingBytes, const char *new_location); -/*mainly used for 3GPP text since most ISO-based formats ignore these (except MJ2K) -all coord values are expressed as 16.16 fixed point floats*/ -GF_Err gf_isom_set_track_layout_info(GF_ISOFile *the_file, u32 trackNumber, u32 width, u32 height, s32 translation_x, s32 translation_y, s16 layer); +/*! gets the current track fragment decode time of the track (the one of the last fragment parsed). +\param isom_file the target ISO file +\param trackNumber the target track +\return the track fragment decode time in media timescale +*/ +u64 gf_isom_get_current_tfdt(GF_ISOFile *isom_file, u32 trackNumber); -/*sets track matrix - all coordinates are expressed as 16.16 floating points*/ -GF_Err gf_isom_set_track_matrix(GF_ISOFile *the_file, u32 trackNumber, u32 matrix[9]); +/*! checks if the movie is a smooth streaming recomputed initial movie +\param isom_file the target ISO file +\return GF_TRUE if the file init segment (moov) was generated from external meta-data (smooth streaming) +*/ +Bool gf_isom_is_smooth_streaming_moov(GF_ISOFile *isom_file); + + +/*! gets default values of samples in a track to use for track fragments default. Each variable is optional and +if set will contain the default value for this track samples + +\param isom_file the target ISO file +\param trackNumber the target track +\param defaultDuration set to the default duration of samples, 0 if not computable +\param defaultSize set to the default size of samples, 0 if not computable +\param defaultDescriptionIndex set to the default sample description index of samples, 0 if not computable +\param defaultRandomAccess set to the default sync flag of samples, 0 if not computable +\param defaultPadding set to the default padding bits of samples, 0 if not computable +\param defaultDegradationPriority set to the default degradation priority of samples, 0 if not computable +\return error if any*/ +GF_Err gf_isom_get_fragment_defaults(GF_ISOFile *isom_file, u32 trackNumber, + u32 *defaultDuration, u32 *defaultSize, u32 *defaultDescriptionIndex, + u32 *defaultRandomAccess, u8 *defaultPadding, u16 *defaultDegradationPriority); -GF_Err gf_isom_set_pixel_aspect_ratio(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, u32 hSpacing, u32 vSpacing); -GF_Err gf_isom_set_clean_apperture(GF_ISOFile *movie, u32 trackNumber, u32 StreamDescriptionIndex, u32 cleanApertureWidthN, u32 cleanApertureWidthD, u32 cleanApertureHeightN, u32 cleanApertureHeightD, u32 horizOffN, u32 horizOffD, u32 vertOffN, u32 vertOffD); -/*set SR & nbChans for audio description*/ -GF_Err gf_isom_set_audio_info(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, u32 sampleRate, u32 nbChannels, u8 bitsPerSample); +/*! gets last UTC/timestamp values indicated for the reference track in the file if any (pfrt box) +\param isom_file the target ISO file +\param refTrackID set to the ID of the reference track used by the pfrt box +\param ntp set to the NTP timestamp found +\param timestamp set to the corresponding media timestamp in refTrackID timescale +\param reset_info if GF_TRUE, discards current NTP mapping info; this will trigger parsing of the next prft box found. If not set, subsequent pfrt boxes will not be parsed until the function is called with reset_info=GF_TRUE +\return GF_FALSE if no info found, GF_TRUE if OK +*/ +Bool gf_isom_get_last_producer_time_box(GF_ISOFile *isom_file, GF_ISOTrackID *refTrackID, u64 *ntp, u64 *timestamp, Bool reset_info); -/*non standard extensions: set/remove a fragment of a sample - this is used for video packets -in order to keep AU structure in the file format (no normative tables for that). Info is NOT written to disk*/ -GF_Err gf_isom_add_sample_fragment(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber, u16 FragmentSize); -GF_Err gf_isom_remove_sample_fragment(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber); -/*remove all sample fragment info for this track*/ -GF_Err gf_isom_remove_sample_fragments(GF_ISOFile *the_file, u32 trackNumber); +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! enables storage of traf templates (serialized sidx/moof/traf without trun/senc) at segment boundaries +This is mostly used to recreate identical segment information when refragmenting a file +\param isom_file the target ISO file +*/ +void gf_isom_enable_traf_map_templates(GF_ISOFile *isom_file); -/*set CTS unpack mode (used for B-frames & like): in unpack mode, each sample uses one entry in CTTS tables -unpack=0: set unpack on - !!creates a CTTS table if none found!! -unpack=1: set unpack off and repacks all table info +/*! Segment boundary information*/ +typedef struct +{ + /*! fragment start offset*/ + u64 frag_start; + /*! mdat end offset*/ + u64 mdat_end; + /*segment start offset plus one: + 0 if regular fragment, 1 if dash sedment, offset indicates start of segment (styp or sidx) + if sidx, it is writtent in the moof_template + */ + u64 seg_start_plus_one; + + /*! serialized array of styp (if present) sidx (if present) and moof with only the current traf*/ + const u8 *moof_template; + /*! size of serialized buffer*/ + u32 moof_template_size; + /*! sidx start, 0 if absent*/ + u64 sidx_start; + /*! sidx end, 0 if absent*/ + u64 sidx_end; +} GF_ISOFragmentBoundaryInfo; + +/*! checks if a sample is a fragment start +Only use this function if \ref gf_isom_enable_traf_map_templates has been called +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNum the target sample number +\param frag_info filled with information on fragment boundaries (optional - can be NULL) +\return GF_TRUE if this sample was the first sample of a traf in the fragmented source file, GF_FALSE otherwise*/ +Bool gf_isom_sample_is_fragment_start(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNum, GF_ISOFragmentBoundaryInfo *frag_info); +#endif //GPAC_DISABLE_ISOM_WRITE + +/*! resets sample information for all tracks setup. This allows keeping the memory footprint low when playing DASH/CMAF segments +\note seeking in the file is then no longer possible +\param isom_file the target ISO file +\param reset_sample_count if GF_TRUE, sets sample count of all tracks back to 0 +\return error if any */ -GF_Err gf_isom_set_cts_packing(GF_ISOFile *the_file, u32 trackNumber, Bool unpack); -/*modify CTS offset of a given sample (used for B-frames) - MUST be called in unpack mode only*/ -GF_Err gf_isom_modify_cts_offset(GF_ISOFile *the_file, u32 trackNumber, u32 sample_number, u32 offset); -/*remove CTS offset table (used for B-frames)*/ -GF_Err gf_isom_remove_cts_info(GF_ISOFile *the_file, u32 trackNumber); +GF_Err gf_isom_reset_tables(GF_ISOFile *isom_file, Bool reset_sample_count); -/*set 3-char or BCP-47 code media language*/ -GF_Err gf_isom_set_media_language(GF_ISOFile *the_file, u32 trackNumber, char *code); +/*! sets the offset for parsing from the input buffer to 0 (used to reclaim input buffer) +\param isom_file the target ISO file +\param top_box_start set to the byte offset in the source buffer of the first top level box +\return error if any +*/ +GF_Err gf_isom_reset_data_offset(GF_ISOFile *isom_file, u64 *top_box_start); -/*removes given stream description*/ -GF_Err gf_isom_remove_sample_description(GF_ISOFile *the_file, u32 trackNumber, u32 streamDescIndex); -/*sets the RVC config for the given track sample description*/ -GF_Err gf_isom_set_rvc_config(GF_ISOFile *movie, u32 track, u32 sampleDescriptionIndex, u16 rvc_predefined, char *mime, char *data, u32 size); +/*! releases current movie segment. This closes the associated file IO object. +\note seeking in the file is no longer possible when tables are rested +\warning the sample count is not reseted after the release of tables. use \ref gf_isom_reset_tables for this -/* - some authoring extensions +\param isom_file the target ISO file +\param reset_tables if set, sample information for all tracks setup as segment are destroyed, along with all PSSH boxes. This allows keeping the memory footprint low when playing segments. +\return error if any */ -/*sets name for authoring - if name is NULL reset authoring name*/ -GF_Err gf_isom_set_track_name(GF_ISOFile *the_file, u32 trackNumber, char *name); -/*gets authoring name*/ -const char *gf_isom_get_track_name(GF_ISOFile *the_file, u32 trackNumber); +GF_Err gf_isom_release_segment(GF_ISOFile *isom_file, Bool reset_tables); -/* - MPEG-4 Extensions -*/ +/*! Flags for gf_isom_open_segment*/ +typedef enum +{ + /*! do not check for movie fragment sequence number*/ + GF_ISOM_SEGMENT_NO_ORDER_FLAG = 1, + /*! the segment contains a scalable layer of the last opened segment*/ + GF_ISOM_SEGMENT_SCALABLE_FLAG = 1<<1, +} GF_ISOSegOpenMode; -/*set a profile and level indication for the movie iod (created if needed) -if the flag is ProfileLevel is 0 this means the movie doesn't require -the specific codec (equivalent to 0xFF value in MPEG profiles)*/ -GF_Err gf_isom_set_pl_indication(GF_ISOFile *the_file, u8 PL_Code, u8 ProfileLevel); +/*! opens a new segment file. Access to samples in previous segments is no longer possible +if end_range>start_range, restricts the URL to the given byterange when parsing -/*set the rootOD ID of the movie if you need it. By default, movies are created without root ODs*/ -GF_Err gf_isom_set_root_od_id(GF_ISOFile *the_file, u32 OD_ID); +\param isom_file the target ISO file +\param fileName the file name of the new segment to open +\param start_range the start offset in bytes in the file of the segment data +\param end_range the end offset in bytes in the file of the segment data +\param flags flags to use when opening the segment +\return error if any +*/ +GF_Err gf_isom_open_segment(GF_ISOFile *isom_file, const char *fileName, u64 start_range, u64 end_range, GF_ISOSegOpenMode flags); -/*set the rootOD URL of the movie if you need it (only needed to create empty file pointing -to external ressource)*/ -GF_Err gf_isom_set_root_od_url(GF_ISOFile *the_file, char *url_string); +/*! returns the track ID of the track containing the highest enhancement layer for the given base track +\param isom_file the target ISO file +\param for_base_track the number of the base track +\return the track ID of the highest enahnacement track +*/ +GF_ISOTrackID gf_isom_get_highest_track_in_scalable_segment(GF_ISOFile *isom_file, u32 for_base_track); -/*remove the root OD*/ -GF_Err gf_isom_remove_root_od(GF_ISOFile *the_file); +/*! resets internal info (track fragement decode time, number of samples, next moof number)used with fragments and segment. +\note This should be called when seeking (with keep_sample_count=0) or when loading a media segments with the same timing as the previously loaded segment +\param isom_file the target ISO file +\param keep_sample_count if GF_TRUE, does not reset the sample count on tracks +*/ +void gf_isom_reset_fragment_info(GF_ISOFile *isom_file, Bool keep_sample_count); -/*Add a system descriptor to the OD of the movie*/ -GF_Err gf_isom_add_desc_to_root_od(GF_ISOFile *the_file, GF_Descriptor *theDesc); +/*! resets sample count to 0 and next moof number to 0. When doing scalable media, should be called before opening the segment containing +the base layer in order to make sure the sample count base number is always the same (ie 1) on all tracks +\param isom_file the target ISO file +*/ +void gf_isom_reset_sample_count(GF_ISOFile *isom_file); +/*! resets moof sequence number to 0 +\param isom_file the target ISO file +*/ +void gf_isom_reset_seq_num(GF_ISOFile *isom_file); -/*add a track to the root OD*/ -GF_Err gf_isom_add_track_to_root_od(GF_ISOFile *the_file, u32 trackNumber); +/*! gets the duration of movie+fragments +\param isom_file the target ISO file +\return the duration in movie timescale, 0 if unknown or if error*/ +u64 gf_isom_get_fragmented_duration(GF_ISOFile *isom_file); -/*remove a track to the root OD*/ -GF_Err gf_isom_remove_track_from_root_od(GF_ISOFile *the_file, u32 trackNumber); +/*! gets the number of fragments or segments when the file is opened in \ref GF_ISOM_OPEN_READ_DUMP mode +\param isom_file the target ISO file +\param segments_only if set to GF_TRUE, counts segments (sidx), otherwise counts fragments +\return the number of segments or fragments +*/ +u32 gf_isom_get_fragments_count(GF_ISOFile *isom_file, Bool segments_only); + +/*! gets total sample number and duration when the file is opened in \ref GF_ISOM_OPEN_READ_DUMP mode +\param isom_file the target ISO file +\param trackID the ID of the target track +\param nb_samples set to the number of samples in the track +\param duration set to the total duration in media timescale +\return error if any +*/ +GF_Err gf_isom_get_fragmented_samples_info(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 *nb_samples, u64 *duration); -/*Create a new StreamDescription (GF_ESD) in the file. The URL and URN are used to -describe external media, this will creat a data reference for the media*/ -GF_Err gf_isom_new_mpeg4_description(GF_ISOFile *the_file, u32 trackNumber, GF_ESD *esd, char *URLname, char *URNname, u32 *outDescriptionIndex); +/*! gets the number of the next moof to be produced +\param isom_file the target ISO file +\return number of the next moof +*/ +u32 gf_isom_get_next_moof_number(GF_ISOFile *isom_file); -/*use carefully. Very useful when you made a lot of changes (IPMP, IPI, OCI, ...) -THIS WILL REPLACE THE WHOLE DESCRIPTOR ...*/ -GF_Err gf_isom_change_mpeg4_description(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, GF_ESD *newESD); +/*! @} */ +#endif //GPAC_DISABLE_ISOM_FRAGMENTS -/*Add a system descriptor to the ESD of a stream - you have to delete the descriptor*/ -GF_Err gf_isom_add_desc_to_description(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, GF_Descriptor *theDesc); -/*updates average and max bitrate - if 0 for max, removes bitrate info*/ -GF_Err gf_isom_update_bitrate(GF_ISOFile *movie, u32 trackNumber, u32 sampleDescriptionIndex, u32 average_bitrate, u32 max_bitrate, u32 decode_buffer_size); +/*! +\addtogroup isoudta_grp ISOBMFF UserData Manipulation +\ingroup iso_grp + User Data Manipulation -/*Default extensions*/ +You can add specific typed data to either a track or the movie: the UserData + The type must be formated as a FourCC if you have a registered 4CC type + but the usual is to set a UUID (128 bit ID for box type) which never conflict + with existing structures in the format + To manipulate a UUID user data set the UserDataType to 0 and specify a valid UUID. +Otherwise the UUID parameter is ignored + Several items with the same ID or UUID can be added (this allows you to store any + kind/number of private information under a unique ID / UUID) -/*Create a new unknown StreamDescription in the file. The URL and URN are used to -describe external media, this will creat a data reference for the media -use this to store media not currently supported by the ISO media format +@{ */ -GF_Err gf_isom_new_generic_sample_description(GF_ISOFile *the_file, u32 trackNumber, char *URLname, char *URNname, GF_GenericSampleDescription *udesc, u32 *outDescriptionIndex); -/*change the data field of an unknown sample description*/ -GF_Err gf_isom_change_generic_sample_description(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, GF_GenericSampleDescription *udesc); +/*! gets number of udta (user data) entries of a movie or track +\param isom_file the target ISO file +\param trackNumber the target track if not 0; if 0, the movie udta is checked +\return the number of entries in UDTA*/ +u32 gf_isom_get_udta_count(GF_ISOFile *isom_file, u32 trackNumber); + +/*! checks type of a given udta entry +\param isom_file the target ISO file +\param trackNumber the target track if not 0; if 0, the movie udta is checked +\param udta_idx 1-based index of the user data to query +\param UserDataType set to the four character code of the user data entry (optional, can be NULL) +\param UUID set to the UUID of the user data entry (optional, can be NULL) +\return error if any*/ +GF_Err gf_isom_get_udta_type(GF_ISOFile *isom_file, u32 trackNumber, u32 udta_idx, u32 *UserDataType, bin128 *UUID); + +/*! gets the number of UserDataItems with the same ID / UUID in the desired track or movie +\param isom_file the target ISO file +\param trackNumber the target track if not 0; if 0, the movie udta is checked +\param UserDataType the four character code of the user data entry to query +\param UUID the UUID of the user data entry +\return number of UDTA entries with the given type*/ +u32 gf_isom_get_user_data_count(GF_ISOFile *isom_file, u32 trackNumber, u32 UserDataType, bin128 UUID); + +/*! gets the UserData for the specified item from the track or the movie +\param isom_file the target ISO file +\param trackNumber the target track if not 0; if 0, the movie udta is checked +\param UserDataType the four character code of the user data entry to query +\param UUID the UUID of the user data entry +\param UserDataIndex 1-based index of the user data of the given type to fetch. If 0, all boxes with type==UserDataType will be serialized (including box header and size) in the output buffer +\param userData set to a newly allocated buffer containing the serialized data - shall be freed by caller, you must pass (userData != NULL && *userData=NULL) +\param userDataSize set to the size of the allocated buffer +\return error if any*/ +GF_Err gf_isom_get_user_data(GF_ISOFile *isom_file, u32 trackNumber, u32 UserDataType, bin128 UUID, u32 UserDataIndex, u8 **userData, u32 *userDataSize); -/* -special shortcut for stream description cloning from a given input file (this avoids inspecting for media type) -@the_file, @trackNumber: destination file and track -@orig_file, @orig_track, @orig_desc_index: orginal file, track and sample description -@URLname, @URNname, @outDescriptionIndex: same usage as with gf_isom_new_mpeg4_description -*/ -GF_Err gf_isom_clone_sample_description(GF_ISOFile *the_file, u32 trackNumber, GF_ISOFile *orig_file, u32 orig_track, u32 orig_desc_index, char *URLname, char *URNname, u32 *outDescriptionIndex); - -/*clones all sampleDescription entries in new track, after an optional reset of existing entries*/ -GF_Err gf_isom_clone_sample_descriptions(GF_ISOFile *the_file, u32 trackNumber, GF_ISOFile *orig_file, u32 orig_track, Bool reset_existing); - -/*special shortcut: clones a track (everything except media data and sample info (DTS, CTS, RAPs, etc...) -also clones sampleDescriptions -@keep_data_ref: if set, all data references are kept (local ones become external pointing to orig_file name), otherwise all external data refs are removed (track media data will be self-contained) -@dest_track: track number of cloned track*/ -GF_Err gf_isom_clone_track(GF_ISOFile *orig_file, u32 orig_track, GF_ISOFile *dest_file, Bool keep_data_ref, u32 *dest_track); -/*special shortcut: clones IOD PLs from orig to dest if any*/ -GF_Err gf_isom_clone_pl_indications(GF_ISOFile *orig, GF_ISOFile *dest); -/*clones root OD from input to output file, without copying root OD track references*/ -GF_Err gf_isom_clone_root_od(GF_ISOFile *input, GF_ISOFile *output); - -/*clones the entire movie file to destination. Tracks can be cloned if clone_tracks is set, in which case hint tracks can be -kept if keep_hint_tracks is set -if keep_pssh, all pssh boxes will be kept -fragment information (mvex) is not kept*/ -GF_Err gf_isom_clone_movie(GF_ISOFile *orig_file, GF_ISOFile *dest_file, Bool clone_tracks, Bool keep_hint_tracks, Bool keep_pssh); - -/*returns true if same set of sample description in both tracks - this does include self-contained checking -and reserved flags. The specific media cfg (DSI & co) is not analysed, only -a brutal memory comparaison is done*/ -Bool gf_isom_is_same_sample_description(GF_ISOFile *f1, u32 tk1, u32 sdesc_index1, GF_ISOFile *f2, u32 tk2, u32 sdesc_index2); +#ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err gf_isom_set_JPEG2000(GF_ISOFile *mov, Bool set_on); +/*! adds a user data item in the desired track or in the movie +\param isom_file the target ISO file +\param trackNumber the target track for the user data; if 0, adds user data to the movie +\param UserDataType the user data four character code type +\param UUID the user data UUID +\param data the data to add, may be NULL +\param size the size of the data to add, shall be 0 when data is NULL +\return error if any +*/ +GF_Err gf_isom_add_user_data(GF_ISOFile *isom_file, u32 trackNumber, u32 UserDataType, bin128 UUID, u8 *data, u32 size); + +/*! removes all user data items from a track or movie +\param isom_file the target ISO file +\param trackNumber the target track for the user data; if 0, adds user data to the movie +\param UserDataType the user data four character code type +\param UUID the user data UUID +\return error if any +*/ +GF_Err gf_isom_remove_user_data(GF_ISOFile *isom_file, u32 trackNumber, u32 UserDataType, bin128 UUID); + +/*! removes a user data item from a track or movie +use the UDAT read functions to get the item index +\param isom_file the target ISO file +\param trackNumber the target track for the user data; if 0, adds user data to the movie +\param UserDataType the user data four character code type +\param UUID the user data UUID +\param UserDataIndex the 1-based index of the user data item to remove - see \ref gf_isom_get_user_data_count +\return error if any +*/ +GF_Err gf_isom_remove_user_data_item(GF_ISOFile *isom_file, u32 trackNumber, u32 UserDataType, bin128 UUID, u32 UserDataIndex); + +/*! adds a user data item in a track or movie using a serialzed buffer of ISOBMFF boxes +\param isom_file the target ISO file +\param trackNumber the target track for the udta box; if 0, add the udta to the movie; +\param data the serialized udta box to add, shall not be NULL +\param size the size of the data to add +\return error if any +*/ +GF_Err gf_isom_add_user_data_boxes(GF_ISOFile *isom_file, u32 trackNumber, u8 *data, u32 size); -/* sample information for all tracks setup are reset. This allows keeping the memory -footprint low when playing segments. Note however that seeking in the file is then no longer possible*/ -GF_Err gf_isom_reset_tables(GF_ISOFile *movie, Bool reset_sample_count); -/* sets the offset for parsing from the input buffer to 0 (used to reclaim input buffer)*/ -GF_Err gf_isom_reset_data_offset(GF_ISOFile *movie, u64 *top_box_start); +/*! gets serialized user data box of a movie +\param isom_file the destination ISO file +\param output will be set to a newly allocated buffer containing the serialized box - caller shall free it +\param output_size will be set to the size of the allocated buffer +\return error if any +*/ +GF_Err gf_isom_get_raw_user_data(GF_ISOFile *isom_file, u8 **output, u32 *output_size); -/*releases current movie segment - this closes the associated file IO object. -If reset_tables is set, sample information for all tracks setup as segment are destroyed, along with all PSSH boxes. This allows keeping the memory -footprint low when playing segments. Note however that seeking in the file is then no longer; possible -WARNING - the sample count is not reset after the release of tables. This means you need to keep counting samples.*/ -GF_Err gf_isom_release_segment(GF_ISOFile *movie, Bool reset_tables); +#endif //GPAC_DISABLE_ISOM_WRITE -/*Flags for gf_isom_open_segment*/ -enum -{ - /*FLAT: the MediaData (MPEG4 ESs) is stored at the beginning of the file*/ - GF_ISOM_SEGMENT_NO_ORDER_FLAG = 1, - GF_ISOM_SEGMENT_SCALABLE_FLAG = 1<<1, -}; +/*! @} */ -/*opens a new segment file. Access to samples in previous segments is no longer possible -if end_range>start_range, restricts the URL to the given byterange when parsing*/ -GF_Err gf_isom_open_segment(GF_ISOFile *movie, const char *fileName, u64 start_range, u64 end_range, u32 flags); -/*returns track ID of the traf containing the highest enhancement layer for the given base track*/ -u32 gf_isom_get_highest_track_in_scalable_segment(GF_ISOFile *movie, u32 for_base_track); +#if !defined(GPAC_DISABLE_ISOM_FRAGMENTS) && !defined(GPAC_DISABLE_ISOM_WRITE) -#ifndef GPAC_DISABLE_ISOM_FRAGMENTS +/*! +\addtogroup isofragwrite_grp Fragmented ISOBMFF Writing +\ingroup iso_grp -/* Movie Fragments Writing API Movie Fragments is a feature of ISO media files for fragmentation of a presentation meta-data and interleaving with its media data. @@ -1389,1004 +3900,1772 @@ u32 gf_isom_get_highest_track_in_scalable_segment(GF_ISOFile *movie, u32 for_bas * Movie Fragments can only be used in GF_ISOM_OPEN_WRITE mode (capturing) and no editing functionalities can be used * the fragmented movie API uses TrackID and not TrackNumber + +@{ */ -/* -setup a track for fragmentation by specifying some default values for -storage efficiency -*TrackID: track identifier -*DefaultStreamDescriptionIndex: the default description used by samples in this track -*DefaultSampleDuration: default duration of samples in this track -*DefaultSampleSize: default size of samples in this track (0 if unknown) -*DefaultSampleIsSync: default key-flag (RAP) of samples in this track -*DefaultSamplePadding: default padding bits for samples in this track -*DefaultDegradationPriority: default degradation priority for samples in this track - -*/ -GF_Err gf_isom_setup_track_fragment(GF_ISOFile *the_file, u32 TrackID, - u32 DefaultStreamDescriptionIndex, +/*! sets up a track for fragmentation by specifying some default values for storage efficiency +\note If all the defaults are 0, traf flags will always be used to signal them. +\param isom_file the target ISO file +\param TrackID ID of the target track +\param DefaultSampleDescriptionIndex the default description used by samples in this track +\param DefaultSampleDuration default duration of samples in this track +\param DefaultSampleSize default size of samples in this track (0 if unknown) +\param DefaultSampleIsSync default key-flag (RAP) of samples in this track +\param DefaultSamplePadding default padding bits for samples in this track +\param DefaultDegradationPriority default degradation priority for samples in this track +\param force_traf_flags if GF_TRUE, will ignore these default in each traf but will still write them in moov +\return error if any +*/ +GF_Err gf_isom_setup_track_fragment(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, + u32 DefaultSampleDescriptionIndex, u32 DefaultSampleDuration, u32 DefaultSampleSize, u8 DefaultSampleIsSync, u8 DefaultSamplePadding, - u16 DefaultDegradationPriority); - -/*change the default parameters of an existing trak fragment - should not be used if samples have -already been added - semantics are the same as in gf_isom_setup_track_fragment*/ -GF_Err gf_isom_change_track_fragment_defaults(GF_ISOFile *movie, u32 TrackID, + u16 DefaultDegradationPriority, + Bool force_traf_flags); + +/*! changes the default parameters of an existing trak fragment +\warning should not be used if samples have already been added + +\param isom_file the target ISO file +\param TrackID ID of the target track +\param DefaultSampleDescriptionIndex the default description used by samples in this track +\param DefaultSampleDuration default duration of samples in this track +\param DefaultSampleSize default size of samples in this track (0 if unknown) +\param DefaultSampleIsSync default key-flag (RAP) of samples in this track +\param DefaultSamplePadding default padding bits for samples in this track +\param DefaultDegradationPriority default degradation priority for samples in this track +\param force_traf_flags if GF_TRUE, will ignore these default in each traf but will still write them in moov +\return error if any +*/ +GF_Err gf_isom_change_track_fragment_defaults(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, u32 DefaultSampleDescriptionIndex, u32 DefaultSampleDuration, u32 DefaultSampleSize, u8 DefaultSampleIsSync, u8 DefaultSamplePadding, - u16 DefaultDegradationPriority); + u16 DefaultDegradationPriority, + u8 force_traf_flags); + +/*! flushes data to disk and prepare movie fragmentation +\param isom_file the target ISO file +\param media_segment_type 0 if no segments, 1 if regular segment, 2 if single segment +\param mvex_after_tracks forces writing mvex box after track boxes +\return error if any +*/ +GF_Err gf_isom_finalize_for_fragment(GF_ISOFile *isom_file, u32 media_segment_type, Bool mvex_after_tracks); + +/*! sets the duration of the movie in case of movie fragments +\param isom_file the target ISO file +\param duration the complete duration (movie and all fragments) in movie timescale +\return error if any +*/ +GF_Err gf_isom_set_movie_duration(GF_ISOFile *isom_file, u64 duration); -/*flushes data to disk and prepare movie fragmentation -@media_segment_type: 0 if no segments, 1 if regular segment, 2 if single segment*/ -GF_Err gf_isom_finalize_for_fragment(GF_ISOFile *the_file, u32 media_segment_type); +/*! fragment creatio option*/ +typedef enum +{ + /*! moof is stored before mdat - will require temporary storage of data in memory*/ + GF_ISOM_FRAG_MOOF_FIRST = 1, +#ifdef GF_ENABLE_CTRN + /*! use compact fragment syntax*/ + GF_ISOM_FRAG_USE_COMPACT = 1<<1, +#endif +} GF_ISOStartFragmentFlags; +/*! starts a new movie fragment +\param isom_file the target ISO file +\param moof_first if GF_TRUE, the moof will be written before the mdat +\return error if any +*/ +GF_Err gf_isom_start_fragment(GF_ISOFile *isom_file, GF_ISOStartFragmentFlags moof_first); -/*sets the duration of the movie in case of movie fragments*/ -GF_Err gf_isom_set_movie_duration(GF_ISOFile *movie, u64 duration); +/*! starts a new segment in the file +\param isom_file the target ISO file +\param SegName if not NULL, the output will be written in the SegName file. If NULL, segment will be created in same file as movie. The special name "_gpac_isobmff_redirect" is used to indicate that segment shall be written to a memory buffer passed to callback function set through \ref gf_isom_set_write_callback +\param memory_mode if set, all samples writing is done in memory rather than on disk. Ignored in callback mode +\return error if any +*/ +GF_Err gf_isom_start_segment(GF_ISOFile *isom_file, const char *SegName, Bool memory_mode); -/*starts a new movie fragment - if force_cache is set, fragment metadata will be written before -fragment media data for all tracks*/ -GF_Err gf_isom_start_fragment(GF_ISOFile *movie, Bool moof_first); +/*! sets the baseMediaDecodeTime of the first sample of the given track +\param isom_file the target ISO file +\param TrackID ID of the target track +\param decode_time the decode time in media timescale +\return error if any +*/ +GF_Err gf_isom_set_traf_base_media_decode_time(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, u64 decode_time); -/*starts a new segment in the file. If SegName is given, the output will be written in the SegName file. If memory_mode is set, all samples writing is done in memory rather than on disk*/ -GF_Err gf_isom_start_segment(GF_ISOFile *movie, const char *SegName, Bool memory_mode); +/*! enables mfra (movie fragment random access computing) when writing movie fragments +\note this should only be used when generating segments in a single file +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_enable_mfra(GF_ISOFile *isom_file); + +/*! sets Microsoft Smooth Streaming traf 'tfxd' box info, written at the end of each traf +\param isom_file the target ISO file +\param reference_track_ID ID of the reference track giving the media timescale +\param decode_traf_time decode time of the first sample in the segment in media timescale (hardcoded to 10MHz in Smooth) +\param traf_duration duration of all samples in the traf in media timescale (hardcoded to 10MHz in Smooth) +\return error if any +*/ +GF_Err gf_isom_set_traf_mss_timeext(GF_ISOFile *isom_file, GF_ISOTrackID reference_track_ID, u64 decode_traf_time, u64 traf_duration); + +/*! closes current segment, producing a segment index box if desired +\param isom_file the target ISO file +\param subsegs_per_sidx number of subsegments per sidx box; a negative value disables sidx, 0 forces a single sidx for the segment (or subsegment) +\param referenceTrackID the ID of the track used as a reference for the segment index box +\param ref_track_decode_time the decode time fo the first sample in the reference track for this segment +\param timestamp_shift the constant difference between media time and presentation time (derived from edit list) +\param ref_track_next_cts the CTS of the first sample in the reference track in the next segment +\param daisy_chain_sidx if GF_TRUE, indicates chained sidx shall be used. Otherwise, an array of indexes is used +\param use_ssix if GF_TRUE, produces an ssix box using I-frames as first level and all other frames as second level +\param last_segment indicates if this is the last segment of the session +\param close_segment_handle if set to GF_TRUE, the associated file if any will be closed +\param segment_marker_4cc a four character code used to insert an empty box at the end of the saegment with the given type. If 0, no such box is inserted +\param index_start_range set to the start offset in bytes of the segment in the media file +\param index_end_range set to the end offset in bytes of the segment in the media file +\param out_seg_size set to the segment size in bytes (optional, can be NULL) +\return error if any +*/ +GF_Err gf_isom_close_segment(GF_ISOFile *isom_file, s32 subsegs_per_sidx, GF_ISOTrackID referenceTrackID, u64 ref_track_decode_time, s32 timestamp_shift, u64 ref_track_next_cts, Bool daisy_chain_sidx, Bool use_ssix, Bool last_segment, Bool close_segment_handle, u32 segment_marker_4cc, u64 *index_start_range, u64 *index_end_range, u64 *out_seg_size); -/*sets the baseMediaDecodeTime of the first sample of the given track*/ -GF_Err gf_isom_set_traf_base_media_decode_time(GF_ISOFile *movie, u32 TrackID, u64 decode_time); +/*! writes any pending fragment to file for low-latency output. +\warning This shall only be used if no SIDX is used: subsegs_per_sidx<0 or flushing all fragments before calling \ref gf_isom_close_segment -/*sets Microsoft Smooth Streaming traf 'tfxd' box info, written at the end of each traf*/ -GF_Err gf_isom_set_traf_mss_timeext(GF_ISOFile *movie, u32 reference_track_ID, u64 ntp_in_10mhz, u64 traf_duration_in_10mhz); +\param isom_file the target ISO file +\param last_segment indicates if this is the last segment of the session +\return error if any +*/ +GF_Err gf_isom_flush_fragments(GF_ISOFile *isom_file, Bool last_segment); + +/*! sets fragment prft box info, written just before the moof +\param isom_file the target ISO file +\param reference_track_ID the ID of the track used as a reference for media timestamps +\param ntp absolute NTP time +\param timestamp media time corresponding to the NTP time, in reference track media timescale +\return error if any +*/ +GF_Err gf_isom_set_fragment_reference_time(GF_ISOFile *isom_file, GF_ISOTrackID reference_track_ID, u64 ntp, u64 timestamp); + +/*! writes an empty sidx in the current movie. + +The SIDX will be forced to have nb_segs entries, and nb_segs shall match the number of calls to +\ref gf_isom_close_segment that will follow. +This avoids wasting time and disk space moving data around. Once \ref gf_isom_close_segment has then been called nb_segs times, +the pre-allocated SIDX is destroyed and successive calls to \ref gf_isom_close_segment will create their own sidx, unless gf_isom_allocate_sidx is called again. + +\param isom_file the target ISO file +\param subsegs_per_sidx reserved to 0, currently ignored +\param daisy_chain_sidx reserved to 0, currently ignored +\param nb_segs number of entries in the segment index +\param frags_per_segment reserved, currently ignored +\param start_range set to the start offset in bytes of the segment index box +\param end_range set to the end offset in bytes of the segment index box +\param use_ssix if GF_TRUE, produces an ssix box using I-frames as first level and all other frames as second level +\return error if any +*/ +GF_Err gf_isom_allocate_sidx(GF_ISOFile *isom_file, s32 subsegs_per_sidx, Bool daisy_chain_sidx, u32 nb_segs, u32 *frags_per_segment, u32 *start_range, u32 *end_range, Bool use_ssix); -/*closes current segment - if fragments_per_sidx is <0, no sidx is used - if fragments_per_sidx is ==0, a single sidx is used -timestamp_shift is the constant difference between media time and presentation time (derived from edit list)*/ -GF_Err gf_isom_close_segment(GF_ISOFile *movie, s32 subsegs_per_sidx, u32 referenceTrackID, u64 ref_track_decode_time, s32 timestamp_shift, u64 ref_track_next_cts, Bool daisy_chain_sidx, Bool last_segment, u32 segment_marker_4cc, u64 *index_start_range, u64 *index_end_range); +/*! sets up track fragment defaults using the given template. The template shall be a serialized array of one or more trex boxes -/*writes any pending fragment to file for low-latency output. shall only be used if no SIDX is used (subsegs_per_sidx<0 or flushing all fragments before calling gf_isom_close_segment)*/ -GF_Err gf_isom_flush_fragments(GF_ISOFile *movie, Bool last_segment); +\param isom_file the target ISO file +\param TrackID ID of the target track +\param boxes serialized array of trex boxes +\param boxes_size size of the serialized array +\param force_traf_flags if GF_TRUE, will ignore these default in each traf but will still write them in moov +\return error if any +*/ +GF_Err gf_isom_setup_track_fragment_template(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, u8 *boxes, u32 boxes_size, u8 force_traf_flags); -/*sets fragment prft box info, written just before the moof*/ -GF_Err gf_isom_set_fragment_reference_time(GF_ISOFile *movie, u32 reference_track_ID, u64 ntp, u64 timestamp); +#ifdef GF_ENABLE_CTRN +/*! enables track fragment inheriting from a given traf. +This shall only be set when the inherited traf shares exactly the same syntax except the sample sizes, this library does not compute which +sample values can be inherited -/*writes an empty sidx in the current movie. The SIDX will be forced to have nb_segs entries - nb_segs shall match the number of calls to -gf_isom_close_segment that will follow. This avoids wasting time and disk space moving data around. Once gf_isom_close_segment has then been called nb_segs times, -the pre-allocated SIDX is destroyed and sucessive calls to gf_isom_close_segment will create their own sidx (unless gf_isom_allocate_sidx is called again). -frags_per_sidx, daisy_chain_sidx and frags_per_segment are currently ignored and reserved for future usages where multiple SIDX could be written -if not NULL, start_range and end_range will contain the byte range of the SIDX box in the movie*/ -GF_Err gf_isom_allocate_sidx(GF_ISOFile *movie, s32 subsegs_per_sidx, Bool daisy_chain_sidx, u32 nb_segs, u32 *frags_per_segment, u32 *start_range, u32 *end_range); +\param isom_file the target ISO file +\param TrackID ID of the target track +\param BaseTrackID ID of the track from which sample values are inherited in track fragments +\return error if any +*/ +GF_Err gf_isom_enable_traf_inherit(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, GF_ISOTrackID BaseTrackID); +#endif -enum +/*! Track fragment options*/ +typedef enum { - /*indicates that the track fragment has no samples but still has a duration + /*! indicates that the track fragment has no samples but still has a duration (silence-detection in audio codecs, ...). param: indicates duration*/ GF_ISOM_TRAF_EMPTY, - /*I-Frame detection: this can reduce file size by detecting I-frames and + /*! I-Frame detection: this can reduce file size by detecting I-frames and optimizing sample flags (padding, priority, ..) param: on/off (0/1)*/ GF_ISOM_TRAF_RANDOM_ACCESS, - /*activate data cache on track fragment. This is useful when writing interleaved + /*! activate data cache on track fragment. This is useful when writing interleaved media from a live source (typically audio-video), and greatly reduces file size param: Number of samples (> 1) to cache before disk flushing. You shouldn't try to cache too many samples since this will load your memory. base that on FPS/SR*/ - GF_ISOM_TRAF_DATA_CACHE -}; - -/*set options. Options can be set at the beginning of each new fragment only, and for the -lifetime of the fragment*/ -GF_Err gf_isom_set_fragment_option(GF_ISOFile *the_file, u32 TrackID, u32 Code, u32 param); - + GF_ISOM_TRAF_DATA_CACHE, + /*! forces moof base offsets when traf based offsets would be chosen + param: on/off (0/1)*/ + GF_ISOM_TFHD_FORCE_MOOF_BASE_OFFSET, + /*! use sdtp box in traf rather than storing sample deps in trun entry. param values are: + 0: disables sdtp + 1: enables sdtp and disables sample dependency flags in trun + 2: enables sdtp and also use sample dependency flags in trun + */ + GF_ISOM_TRAF_USE_SAMPLE_DEPS_BOX, + /*! forces new trun at next sample add + param: ignored*/ + GF_ISOM_TRUN_FORCE, + /*! sets interleave group ID of the next sample add. Samples with lower interleave ID will be stored first, creating new trun whenever a new group is detected + This will enable data cache + param: interleave ID*/ + GF_ISOM_TRUN_SET_INTERLEAVE_ID, +} GF_ISOTrackFragmentOption; + +/*! sets a track fragment option. Options can be set at the beginning of each new fragment only, and for the +lifetime of the fragment +\param isom_file the target ISO file +\param TrackID ID of the target track +\param Code the option type to set +\param param the option value +\return error if any +*/ +GF_Err gf_isom_set_fragment_option(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, GF_ISOTrackFragmentOption Code, u32 param); -/*adds a sample to a fragmented track +/*! adds a sample to a fragmented track -*TrackID: destination track -*sample: sample to add -*StreamDescriptionIndex: stream description for this sample. If 0, the default one +\param isom_file the target ISO file +\param TrackID destination track +\param sample sample to add +\param sampleDescriptionIndex sample description for this sample. If 0, the default one is used -*Duration: sample duration. -Note: because of the interleaved nature of the meta/media data, the sample duration -MUST be provided (in case of regular tracks, this was computed internally by the lib) -*PaddingBits: padding bits for the sample, or 0 -*DegradationPriority for the sample, or 0 -*redundantCoding: indicates this is samples acts as a sync shadow point - +\param Duration sample duration; the sample duration MUST be provided at least for the last sample (for intermediate samples, it is recomputed internally by the lib) +\param PaddingBits padding bits for the sample, or 0 +\param DegradationPriority for the sample, or 0 +\param redundantCoding indicates this is samples acts as a sync shadow point +\return error if any */ - -GF_Err gf_isom_fragment_add_sample(GF_ISOFile *the_file, u32 TrackID, const GF_ISOSample *sample, - u32 StreamDescriptionIndex, +GF_Err gf_isom_fragment_add_sample(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, const GF_ISOSample *sample, + u32 sampleDescriptionIndex, u32 Duration, u8 PaddingBits, u16 DegradationPriority, Bool redundantCoding); -/*appends data into last sample of track for video fragments/other media -CANNOT be used with OD tracks*/ -GF_Err gf_isom_fragment_append_data(GF_ISOFile *the_file, u32 TrackID, char *data, u32 data_size, u8 PaddingBits); - -/*reset internal info used with fragments and segment. Should be called when seeking (with keep_sample_count=0) or when loading a media segments with the same timing as the previously loaded segment*/ -void gf_isom_reset_fragment_info(GF_ISOFile *movie, Bool keep_sample_count); - -/*return the duration of the movie+fragments if known, 0 if error*/ -u64 gf_isom_get_fragmented_duration(GF_ISOFile *movie); -/*returns the number of sidx boxes*/ -u32 gf_isom_get_fragments_count(GF_ISOFile *movie, Bool segments_only); -/*gets total sample number and duration*/ -GF_Err gf_isom_get_fragmented_samples_info(GF_ISOFile *movie, u32 trackID, u32 *nb_samples, u64 *duration); - -GF_Err gf_isom_fragment_add_sai(GF_ISOFile *output, GF_ISOFile *input, u32 TrackID, u32 SampleNum); -GF_Err gf_isom_clone_pssh(GF_ISOFile *output, GF_ISOFile *input, Bool in_moof); - -#endif /*GPAC_DISABLE_ISOM_FRAGMENTS*/ - -GF_Err gf_isom_set_sync_table(GF_ISOFile *the_file, u32 trackNumber); - -/****************************************************************** - GENERIC Publishing API -******************************************************************/ - -/*Removes all sync shadow entries for a given track. The shadow samples are NOT removed; they must be removed -by the user app*/ -GF_Err gf_isom_remove_sync_shadows(GF_ISOFile *the_file, u32 trackNumber); - -/*Use this function to do the shadowing if you use shadowing. -the sample to be shadowed MUST be a non-sync sample (ignored if not) -the sample shadowing must be a Sync sample (error if not)*/ -GF_Err gf_isom_set_sync_shadow(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber, u32 syncSample); - -/*set the GroupID of a track (only used for optimized interleaving). By setting GroupIDs -you can specify the storage order for media data of a group of streams. This is useful -for BIFS presentation so that static resources of the scene can be downloaded before BIFS*/ -GF_Err gf_isom_set_track_interleaving_group(GF_ISOFile *the_file, u32 trackNumber, u32 GroupID); +/*! appends data into last sample of track for video fragments/other media +\warning this shall not be used with OD tracks +\param isom_file the target ISO file +\param TrackID destination track +\param data the data to append +\param data_size the size of the data to append +\param PaddingBits padding bits for the sample, or 0 +\return error if any +*/ +GF_Err gf_isom_fragment_append_data(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, u8 *data, u32 data_size, u8 PaddingBits); + + +/*! sets side information for common encryption for the last added sample +\param isom_file the target ISO file +\param trackID the ID of the target track +\param IV_size the size of the init vector (8 or 16 bytes) +\param sai_b buffer containing the SAI information of the sample +\param sai_b_size size of the SAI buffer. If sai_b is NULL, this indicates the original sample size, and an SAI will be created describing the proper byte ranges if use_subsample is set +\param use_subsample indicates if the media uses CENC subsamples +\param use_saio_32bit indicates if 32-bit saio shall be used +\return error if any +*/ +GF_Err gf_isom_fragment_set_cenc_sai(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 IV_size, u8 *sai_b, u32 sai_b_size, Bool use_subsample, Bool use_saio_32bit); +/*! clones PSSH data between two files +\param dst_file the target ISO file +\param src_file the source ISO file +\param in_moof if GF_TRUE, indicates the pssh should be cloned in current moof box +\return error if any +*/ +GF_Err gf_isom_clone_pssh(GF_ISOFile *dst_file, GF_ISOFile *src_file, Bool in_moof); -/*set the priority of a track within a Group (used for optimized interleaving and hinting). -This allows tracks to be stored before other within a same group, for instance the -hint track data can be stored just before the media data, reducing disk seeking -for a same time, within a group of tracks, the track with the lowest inversePriority will -be written first*/ -GF_Err gf_isom_set_track_priority_in_group(GF_ISOFile *the_file, u32 trackNumber, u32 InversePriority); -/*set the max SamplesPerChunk (for file optimization, mainly in FLAT and STREAMABLE modes)*/ -GF_Err gf_isom_set_max_samples_per_chunk(GF_ISOFile *the_file, u32 trackNumber, u32 maxSamplesPerChunk); +/*! sets roll information for a sample in a track fragment +\param isom_file the target ISO file +\param trackID the ID of the target track +\param sample_number the sample number of the last sample +\param roll_type indicate the sample roll type +\param roll_distance set to the roll distance for a roll sample +\return error if any +*/ +GF_Err gf_isom_fragment_set_sample_roll_group(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 sample_number, GF_ISOSampleRollType roll_type, s16 roll_distance); + +/*! sets rap information for a sample in a track fragment +\param isom_file the target ISO file +\param trackID the ID of the target track +\param sample_number_in_frag the sample number of the sample in the traf +\param is_rap set to GF_TRUE to indicate the sample is a RAP sample (open-GOP), GF_FALSE otherwise +\param num_leading_samples set to the number of leading pictures for a RAP sample +\return error if any +*/ +GF_Err gf_isom_fragment_set_sample_rap_group(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 sample_number_in_frag, Bool is_rap, u32 num_leading_samples); + +/*! sets sample dependency flags in a track fragment - see ISO/IEC 14496-12 and \ref gf_filter_pck_set_dependency_flags +\param isom_file the target ISO file +\param trackID the ID of the target track +\param is_leading indicates that the sample is a leading picture +\param dependsOn indicates the sample dependency towards other samples +\param dependedOn indicates the sample dependency from other samples +\param redundant indicates that the sample contains redundant coding +\return error if any +*/ +GF_Err gf_isom_fragment_set_sample_flags(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 is_leading, u32 dependsOn, u32 dependedOn, u32 redundant); -/*associate a given SL config with a given ESD while extracting the OD information -all the SL params must be fixed by the calling app! -The SLConfig is stored by the API for further use. A NULL pointer will result -in using the default SLConfig (predefined = 2) remapped to predefined = 0 -This is useful while reading the IOD / OD stream of an MP4 file. Note however that -only full AUs are extracted, therefore the calling application must SL-packetize the streams*/ -GF_Err gf_isom_set_extraction_slc(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, GF_SLConfig *slConfig); -GF_Err gf_isom_get_extraction_slc(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, GF_SLConfig **slConfig); +/*! sets the number of the next moof to be produced +\param isom_file the target ISO file +\param value the number of the next moof +*/ +void gf_isom_set_next_moof_number(GF_ISOFile *isom_file, u32 value); -u32 gf_isom_get_track_group(GF_ISOFile *the_file, u32 trackNumber); -u32 gf_isom_get_track_priority_in_group(GF_ISOFile *the_file, u32 trackNumber); -/*stores movie config (storage mode, interleave time, track groupIDs, priorities and names) in UDTA(kept on disk) -if @remove_all is set, removes all stored info, otherwise recompute all stored info*/ -GF_Err gf_isom_store_movie_config(GF_ISOFile *the_file, Bool remove_all); -/*restores movie config (storage mode, interleave time, track groupIDs, priorities and names) if found*/ -GF_Err gf_isom_load_movie_config(GF_ISOFile *the_file); +/*! @} */ +#endif// !defined(GPAC_DISABLE_ISOM_FRAGMENTS) && !defined(GPAC_DISABLE_ISOM_WRITE) -/*setup interleaving for storage (shortcut for storeage mode + interleave_time)*/ -GF_Err gf_isom_make_interleave(GF_ISOFile *mp4file, Double TimeInSec); +/*! +\addtogroup isortp_grp ISOBMFF RTP Hinting +\ingroup iso_grp -/****************************************************************** - GENERIC HINTING WRITING API -******************************************************************/ +@{ +*/ -/*supported hint formats - ONLY RTP now*/ -enum +/*! supported hint formats - ONLY RTP now*/ +typedef enum { + /*! RTP hint type*/ GF_ISOM_HINT_RTP = GF_4CC('r', 't', 'p', ' '), -}; - -#ifndef GPAC_DISABLE_ISOM_HINTING +} GF_ISOHintFormat; +#if !defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_ISOM_HINTING) -/*Setup the resources based on the hint format -This function MUST be called after creating a new hint track and before -any other calls on this track*/ -GF_Err gf_isom_setup_hint_track(GF_ISOFile *the_file, u32 trackNumber, u32 HintType); - -/*Create a HintDescription for the HintTrack -the rely flag indicates whether a reliable transport protocol is desired/required +/*! sets up a hint track based on the hint format +\warning This function MUST be called after creating a new hint track and before any other calls on this track +\param isom_file the target ISO file +\param trackNumber the target hint track +\param HintType the desired hint type +\return error if any +*/ +GF_Err gf_isom_setup_hint_track(GF_ISOFile *isom_file, u32 trackNumber, GF_ISOHintFormat HintType); + +/*! creates a HintDescription for the HintTrack +\param isom_file the target ISO file +\param trackNumber the target hint track +\param HintTrackVersion version of hint track +\param LastCompatibleVersion last compatible version of hint track +\param Rely flag indicating whether a reliable transport protocol is desired/required for data transport 0: not desired (UDP/IP). NB: most RTP streaming servers only support UDP/IP for data 1: preferable (TCP/IP if possible or UDP/IP) 2: required (TCP/IP only) -The HintDescriptionIndex is set, to be used when creating a HINT sample +\param HintDescriptionIndex is set to the newly created hint sample description index +\return error if any */ -GF_Err gf_isom_new_hint_description(GF_ISOFile *the_file, u32 trackNumber, s32 HintTrackVersion, s32 LastCompatibleVersion, u8 Rely, u32 *HintDescriptionIndex); - -/*Starts a new sample for the hint track. A sample is just a collection of packets -the transmissionTime is indicated in the media timeScale of the hint track*/ -GF_Err gf_isom_begin_hint_sample(GF_ISOFile *the_file, u32 trackNumber, u32 HintDescriptionIndex, u32 TransmissionTime); +GF_Err gf_isom_new_hint_description(GF_ISOFile *isom_file, u32 trackNumber, s32 HintTrackVersion, s32 LastCompatibleVersion, u8 Rely, u32 *HintDescriptionIndex); + +/*! starts a new sample for the hint track. A sample is just a collection of packets +the transmissionTime is indicated in the media timeScale of the hint track +\param isom_file the target ISO file +\param trackNumber the target hint track +\param HintDescriptionIndex the target hint sample description index +\param TransmissionTime the target transmission time in hint media timescale +\return error if any +*/ +GF_Err gf_isom_begin_hint_sample(GF_ISOFile *isom_file, u32 trackNumber, u32 HintDescriptionIndex, u32 TransmissionTime); -/*stores the hint sample in the file once all your packets for this sample are done -set IsRandomAccessPoint if you want to indicate that this is a random access point -in the stream*/ -GF_Err gf_isom_end_hint_sample(GF_ISOFile *the_file, u32 trackNumber, u8 IsRandomAccessPoint); +/*! ends an hint sample once all your packets for this sample are done +\param isom_file the target ISO file +\param trackNumber the target hint track +\param IsRandomAccessPoint set to GF_TRUE if you want to indicate that this is a random access point in the stream +\return error if any +*/ +GF_Err gf_isom_end_hint_sample(GF_ISOFile *isom_file, u32 trackNumber, u8 IsRandomAccessPoint); -/****************************************************************** +/*! PacketHandling functions Data can be added at the end or at the beginning of the current packet by setting AtBegin to 1 the data will be added at the beginning This allows constructing the packet payload before any meta-data -******************************************************************/ +*/ -/*adds a blank chunk of data in the sample that is skipped while streaming*/ -GF_Err gf_isom_hint_blank_data(GF_ISOFile *the_file, u32 trackNumber, u8 AtBegin); +/*! adds a blank chunk of data in the sample that is skipped while streaming +\param isom_file the target ISO file +\param trackNumber the target hint track +\param AtBegin indicates if the blank chunk should be at the end or at the begining of the hint packet +\return error if any +*/ +GF_Err gf_isom_hint_blank_data(GF_ISOFile *isom_file, u32 trackNumber, u8 AtBegin); -/*adds a chunk of data in the packet that is directly copied while streaming +/*! adds a chunk of data in the packet that is directly copied while streaming NOTE: dataLength MUST BE <= 14 bytes, and you should only use this function -to add small blocks of data (encrypted parts, specific headers, ...)*/ -GF_Err gf_isom_hint_direct_data(GF_ISOFile *the_file, u32 trackNumber, char *data, u32 dataLength, u8 AtBegin); - -/*adds a reference to some sample data in the packet -SourceTrackID: the ID of the track where the referenced sample is -SampleNumber: the sample number containing the data to be added -DataLength: the length of bytes to copy in the packet -offsetInSample: the offset in bytes in the sample at which to begin copying data - -extra_data: only used when the sample is actually the sample that will contain this packet +to add small blocks of data (encrypted parts, specific headers, ...) +\param isom_file the target ISO file +\param trackNumber the target hint track +\param data buffer to add to the RTP packet +\param dataLength size of buffer to add to the RTP packet +\param AtBegin indicates if the blank chunk should be at the end or at the begining of the hint packet +\return error if any +*/ +GF_Err gf_isom_hint_direct_data(GF_ISOFile *isom_file, u32 trackNumber, u8 *data, u32 dataLength, u8 AtBegin); + +/*! adds a reference to some sample data in the packet +\note if you want to reference a previous HintSample in the hintTrack, you will have to parse the sample yourself ... + +\param isom_file the target ISO file +\param trackNumber the target hint track +\param SourceTrackID the ID of the track where the referenced sample is +\param SampleNumber the sample number containing the data to be added +\param DataLength the length of bytes to copy in the packet +\param offsetInSample the offset in bytes in the sample at which to begin copying data +\param extra_data only used when the sample is actually the sample that will contain this packet (useful to store en encrypted version of a packet only available while streaming) In this case, set SourceTrackID to the HintTrack ID and SampleNumber to 0 In this case, the DataOffset MUST BE NULL and length will indicate the extra_data size - -Note that if you want to reference a previous HintSample in the hintTrack, you will -have to parse the sample yourself ... +\param AtBegin indicates if the blank chunk should be at the end or at the begining of the hint packet +\return error if any */ -GF_Err gf_isom_hint_sample_data(GF_ISOFile *the_file, u32 trackNumber, u32 SourceTrackID, u32 SampleNumber, u16 DataLength, u32 offsetInSample, char *extra_data, u8 AtBegin); +GF_Err gf_isom_hint_sample_data(GF_ISOFile *isom_file, u32 trackNumber, GF_ISOTrackID SourceTrackID, u32 SampleNumber, u16 DataLength, u32 offsetInSample, u8 *extra_data, u8 AtBegin); -/*adds a reference to some stream description data in the packet (headers, ...) -SourceTrackID: the ID of the track where the referenced sample is -StreamDescriptionIndex: the index of the stream description in the desired track -DataLength: the length of bytes to copy in the packet -offsetInDescription: the offset in bytes in the description at which to begin copying data - -Since it is far from being obvious what this offset is, we recommend not using this -function. The ISO Media Format specification is currently being updated to solve -this issue*/ -GF_Err gf_isom_hint_sample_description_data(GF_ISOFile *the_file, u32 trackNumber, u32 SourceTrackID, u32 StreamDescriptionIndex, u16 DataLength, u32 offsetInDescription, u8 AtBegin); +/*! adds a reference to some stream description data in the packet (headers, ...) +\param isom_file the target ISO file +\param trackNumber the target hint track +\param SourceTrackID the ID of the track where the referenced sample is +\param sampleDescriptionIndex the index of the stream description in the desired track +\param DataLength the length of bytes to copy in the packet +\param offsetInDescription the offset in bytes in the description at which to begin copying data. Since it is far from being obvious / interoperable what this offset is, we recommend not using this function and injecting the data instead using \ref gf_isom_hint_direct_data. +\param AtBegin indicates if the blank chunk should be at the end or at the begining of the hint packet +\return error if any +*/ +GF_Err gf_isom_hint_sample_description_data(GF_ISOFile *isom_file, u32 trackNumber, GF_ISOTrackID SourceTrackID, u32 sampleDescriptionIndex, u16 DataLength, u32 offsetInDescription, u8 AtBegin); -/****************************************************************** - RTP SPECIFIC WRITING API -******************************************************************/ -/*Creates a new RTP packet in the HintSample. If a previous packet was created, +/*! creates a new RTP packet in the HintSample. If a previous packet was created, it is stored in the hint sample and a new packet is created. -- relativeTime: RTP time offset of this packet in the HintSample if any - in hint track + +\param isom_file the target ISO file +\param trackNumber the target hint track +\param relativeTime RTP time offset of this packet in the HintSample if any - in hint track time scale. Used for data smoothing by servers. -- PackingBit: the 'P' bit of the RTP packet header -- eXtensionBit: the'X' bit of the RTP packet header -- MarkerBit: the 'M' bit of the RTP packet header -- PayloadType: the payload type, on 7 bits, format 0x0XXXXXXX -- B_frame: indicates if this is a B-frame packet. Can be skipped by a server -- IsRepeatedPacket: indicates if this is a duplicate packet of a previous one. -Can be skipped by a server -- SequenceNumber: the RTP base sequence number of the packet. Because of support for repeated -packets, you have to set the sequence number yourself.*/ -GF_Err gf_isom_rtp_packet_begin(GF_ISOFile *the_file, u32 trackNumber, s32 relativeTime, u8 PackingBit, u8 eXtensionBit, u8 MarkerBit, u8 PayloadType, u8 B_frame, u8 IsRepeatedPacket, u16 SequenceNumber); - -/*set the flags of the RTP packet*/ -GF_Err gf_isom_rtp_packet_set_flags(GF_ISOFile *the_file, u32 trackNumber, u8 PackingBit, u8 eXtensionBit, u8 MarkerBit, u8 disposable_packet, u8 IsRepeatedPacket); - -/*set the time offset of this packet. This enables packets to be placed in the hint track +\param PackingBit the 'P' bit of the RTP packet header +\param eXtensionBit the'X' bit of the RTP packet header +\param MarkerBit the 'M' bit of the RTP packet header +\param PayloadType the payload type, on 7 bits, format 0x0XXXXXXX +\param disposable_packet indicates if this packet can be skipped by a server +\param IsRepeatedPacket indicates if this is a duplicate packet of a previous one and can be skipped by a server +\param SequenceNumber the RTP base sequence number of the packet. Because of support for repeated packets, you have to set the sequence number yourself. +\return error if any +*/ +GF_Err gf_isom_rtp_packet_begin(GF_ISOFile *isom_file, u32 trackNumber, s32 relativeTime, u8 PackingBit, u8 eXtensionBit, u8 MarkerBit, u8 PayloadType, u8 disposable_packet, u8 IsRepeatedPacket, u16 SequenceNumber); + +/*! sets the flags of the RTP packet +\param isom_file the target ISO file +\param trackNumber the target hint track +\param PackingBit the 'P' bit of the RTP packet header +\param eXtensionBit the'X' bit of the RTP packet header +\param MarkerBit the 'M' bit of the RTP packet header +\param disposable_packet indicates if this packet can be skipped by a server +\param IsRepeatedPacket indicates if this is a duplicate packet of a previous one and can be skipped by a server +\return error if any*/ +GF_Err gf_isom_rtp_packet_set_flags(GF_ISOFile *isom_file, u32 trackNumber, u8 PackingBit, u8 eXtensionBit, u8 MarkerBit, u8 disposable_packet, u8 IsRepeatedPacket); + +/*! sets the time offset of this packet. This enables packets to be placed in the hint track in decoding order, but have their presentation time-stamp in the transmitted packet in a different order. Typically used for MPEG video with B-frames +\param isom_file the target ISO file +\param trackNumber the target hint track +\param timeOffset time offset in RTP media timescale +\return error if any */ -GF_Err gf_isom_rtp_packet_set_offset(GF_ISOFile *the_file, u32 trackNumber, s32 timeOffset); +GF_Err gf_isom_rtp_packet_set_offset(GF_ISOFile *isom_file, u32 trackNumber, s32 timeOffset); -/*set some specific info in the HintDescription for RTP*/ - -/*sets the RTP TimeScale that the server use to send packets +/*! sets the RTP TimeScale that the server use to send packets some RTP payloads may need a specific timeScale that is not the timeScale in the file format -the default timeScale choosen by the API is the MediaTimeScale of the hint track*/ -GF_Err gf_isom_rtp_set_timescale(GF_ISOFile *the_file, u32 trackNumber, u32 HintDescriptionIndex, u32 TimeScale); -/*sets the RTP TimeOffset that the server will add to the packets -if not set, the server adds a random offset*/ -GF_Err gf_isom_rtp_set_time_offset(GF_ISOFile *the_file, u32 trackNumber, u32 HintDescriptionIndex, u32 TimeOffset); -/*sets the RTP SequenceNumber Offset that the server will add to the packets -if not set, the server adds a random offset*/ -GF_Err gf_isom_rtp_set_time_sequence_offset(GF_ISOFile *the_file, u32 trackNumber, u32 HintDescriptionIndex, u32 SequenceNumberOffset); - - - -/****************************************************************** - SDP SPECIFIC WRITING API -******************************************************************/ -/*add an SDP line to the SDP container at the track level (media-specific SDP info) -NOTE: the \r\n end of line for SDP is automatically inserted*/ -GF_Err gf_isom_sdp_add_track_line(GF_ISOFile *the_file, u32 trackNumber, const char *text); -/*remove all SDP info at the track level*/ -GF_Err gf_isom_sdp_clean_track(GF_ISOFile *the_file, u32 trackNumber); - -/*add an SDP line to the SDP container at the movie level (presentation SDP info) -NOTE: the \r\n end of line for SDP is automatically inserted*/ -GF_Err gf_isom_sdp_add_line(GF_ISOFile *the_file, const char *text); -/*remove all SDP info at the movie level*/ -GF_Err gf_isom_sdp_clean(GF_ISOFile *the_file); - -#endif /*GPAC_DISABLE_ISOM_HINTING*/ - - -#endif /*GPAC_DISABLE_ISOM_WRITE*/ - -#ifndef GPAC_DISABLE_ISOM_DUMP +the default timeScale choosen by the API is the MediaTimeScale of the hint track +\param isom_file the target ISO file +\param trackNumber the target hint track +\param HintDescriptionIndex the target hint sample description index +\param TimeScale the RTP timescale to use +\return error if any +*/ +GF_Err gf_isom_rtp_set_timescale(GF_ISOFile *isom_file, u32 trackNumber, u32 HintDescriptionIndex, u32 TimeScale); + +/*! sets the RTP TimeOffset that the server will add to the packets +if not set, the server adds a random offset +\param isom_file the target ISO file +\param trackNumber the target hint track +\param HintDescriptionIndex the target hint sample description index +\param TimeOffset the time offset in RTP timescale +\return error if any +*/ +GF_Err gf_isom_rtp_set_time_offset(GF_ISOFile *isom_file, u32 trackNumber, u32 HintDescriptionIndex, u32 TimeOffset); + +/*! sets the RTP SequenceNumber Offset that the server will add to the packets +if not set, the server adds a random offset +\param isom_file the target ISO file +\param trackNumber the target hint track +\param HintDescriptionIndex the target hint sample description index +\param SequenceNumberOffset the sequence number offset +\return error if any +*/ +GF_Err gf_isom_rtp_set_time_sequence_offset(GF_ISOFile *isom_file, u32 trackNumber, u32 HintDescriptionIndex, u32 SequenceNumberOffset); + +/*! adds an SDP line to the SDP container at the track level (media-specific SDP info) +\note the CRLF end of line for SDP is automatically inserted +\param isom_file the target ISO file +\param trackNumber the target hint track +\param text the SDP text to add the target hint track +\return error if any +*/ +GF_Err gf_isom_sdp_add_track_line(GF_ISOFile *isom_file, u32 trackNumber, const char *text); +/*! removes all SDP info at the track level +\param isom_file the target ISO file +\param trackNumber the target hint track +\return error if any +*/ +GF_Err gf_isom_sdp_clean_track(GF_ISOFile *isom_file, u32 trackNumber); -/*dumps file structures into XML trace file */ -GF_Err gf_isom_dump(GF_ISOFile *file, FILE *trace); -/*gets number of implemented boxes in GPAC. There can be several times the same type returned due to variation of the box (versions or flags)*/ -u32 gf_isom_get_num_supported_boxes(); -/*gets 4cc of box given its index. Index 0 is GPAC internal unknown box handler.*/ -u32 gf_isom_get_supported_box_type(u32 idx); -/*prints default box syntax of box given its index. Index 0 is GPAC internal unknown box handler*/ -GF_Err gf_isom_dump_supported_box(u32 idx, FILE * trace); +/*! adds an SDP line to the SDP container at the movie level (presentation SDP info) +NOTE: the CRLF end of line for SDP is automatically inserted +\param isom_file the target ISO file +\param text the SDP text to add the target hint track +\return error if any +*/ +GF_Err gf_isom_sdp_add_line(GF_ISOFile *isom_file, const char *text); +/*! removes all SDP info at the movie level +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_sdp_clean(GF_ISOFile *isom_file); -#endif /*GPAC_DISABLE_ISOM_DUMP*/ +#endif// !defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_ISOM_HINTING) #ifndef GPAC_DISABLE_ISOM_HINTING #ifndef GPAC_DISABLE_ISOM_DUMP -/*dumps RTP hint samples structure into XML trace file - @trackNumber, @SampleNum: hint track and hint sample number - @trace: output +/*! dumps RTP hint samples structure into XML trace file +\param isom_file the target ISO file +\param trackNumber the target track +\param SampleNum the target sample number +\param trace the file object to dump to +\return error if any */ -GF_Err gf_isom_dump_hint_sample(GF_ISOFile *the_file, u32 trackNumber, u32 SampleNum, FILE * trace); +GF_Err gf_isom_dump_hint_sample(GF_ISOFile *isom_file, u32 trackNumber, u32 SampleNum, FILE * trace); #endif -/*Get SDP info at the movie level*/ -GF_Err gf_isom_sdp_get(GF_ISOFile *the_file, const char **sdp, u32 *length); -/*Get SDP info at the track level*/ -GF_Err gf_isom_sdp_track_get(GF_ISOFile *the_file, u32 trackNumber, const char **sdp, u32 *length); - -u32 gf_isom_get_payt_count(GF_ISOFile *the_file, u32 trackNumber); -const char *gf_isom_get_payt_info(GF_ISOFile *the_file, u32 trackNumber, u32 index, u32 *payID); - - - - -/*small hint reader - performs data caching*/ - -/*resets hint reading parameters, returns an error if the hint type is not supported for reading -packet sequence number is always reseted to 0 -@sample_start: indicates from where the packets should be read (regular 1-based sample number) -@ts_offset: constant offset for timestamps, must be expressed in media timescale (which is the hint timescale). - usually 0 (no offset) -@sn_offset: offset for packet sequence number (first packet will have a SN of 1 + sn_offset) - usually 0 -@ssrc: sync source identifier for RTP +/*! gets SDP info at the movie level +\param isom_file the target ISO file +\param sdp set to the sdp text - do not modify +\param length set to the sdp length +\return error if any */ -GF_Err gf_isom_reset_hint_reader(GF_ISOFile *the_file, u32 trackNumber, u32 sample_start, u32 ts_offset, u32 sn_offset, u32 ssrc); - -/*reads next hint packet. ALl packets are read in transmission (decoding) order -returns an error if not supported, or GF_EOS when no more packets are available -currently only RTP reader is supported -@pck_data, @pck_size: output packet data (must be freed by caller) - contains all info to be sent - on the wire, eg for RTP contains the RTP header and the data -@disposable (optional): indicates that the packet can be dropped when late (B-frames & co) -@repeated (optional): indicates this is a repeated packet (same one has already been sent) -@trans_ts (optional): indicates the transmission time of the packet, expressed in hint timescale, taking into account -the ts_offset specified in gf_isom_reset_hint_reader. Depending on packets this may not be the same -as the hint sample timestamp + ts_offset, some packets may need to be sent earlier (B-frames) -@sample_num (optional): indicates hint sample number the packet belongs to +GF_Err gf_isom_sdp_get(GF_ISOFile *isom_file, const char **sdp, u32 *length); +/*! gets SDP info at the track level +\param isom_file the target ISO file +\param trackNumber the target track +\param sdp set to the sdp text - do not modify +\param length set to the sdp length +\return error if any */ -GF_Err gf_isom_next_hint_packet(GF_ISOFile *the_file, u32 trackNumber, char **pck_data, u32 *pck_size, Bool *disposable, Bool *repeated, u32 *trans_ts, u32 *sample_num); - -#endif /*GPAC_DISABLE_ISOM_HINTING*/ - - - -/* - 3GPP specific extensions - NOTE: MPEG-4 OD Framework cannot be used with 3GPP files. - Stream Descriptions are not GF_ESD, just generic config options as specified in this file +GF_Err gf_isom_sdp_track_get(GF_ISOFile *isom_file, u32 trackNumber, const char **sdp, u32 *length); +/*! gets number of payload type defines for an RTP hint track +\param isom_file the target ISO file +\param trackNumber the target track +\return the number of payload types defined */ +u32 gf_isom_get_payt_count(GF_ISOFile *isom_file, u32 trackNumber); +/*! gets payload type information for an RTP hint track +\param isom_file the target ISO file +\param trackNumber the target track +\param index the payload type 1_based index +\param payID set to the ID of the payload type +\return the sdp fmtp attribute describing the payload +*/ +const char *gf_isom_get_payt_info(GF_ISOFile *isom_file, u32 trackNumber, u32 index, u32 *payID); -/*Generic 3GP/3GP2 config record*/ -typedef struct -{ - /*GF_4CC record type, one fo the above GF_ISOM_SUBTYPE_3GP_ * subtypes*/ - u32 type; - /*4CC vendor name*/ - u32 vendor; - /*codec version*/ - u8 decoder_version; - /*number of sound frames per IsoMedia sample, >0 and <=15. The very last sample may contain less frames. */ - u8 frames_per_sample; - - /*H263 ONLY - Level and profile*/ - u8 H263_level, H263_profile; - - /*AMR(WB) ONLY - num of mode for the codec*/ - u16 AMR_mode_set; - /*AMR(WB) ONLY - changes in codec mode per sample*/ - u8 AMR_mode_change_period; -} GF_3GPConfig; - - -/*return the 3GP config for this tream description, NULL if not a 3GPP track*/ -GF_3GPConfig *gf_isom_3gp_config_get(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex); -#ifndef GPAC_DISABLE_ISOM_WRITE -/*create the track config*/ -GF_Err gf_isom_3gp_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_3GPConfig *config, char *URLname, char *URNname, u32 *outDescriptionIndex); -/*update the track config - subtypes shall NOT differ*/ -GF_Err gf_isom_3gp_config_update(GF_ISOFile *the_file, u32 trackNumber, GF_3GPConfig *config, u32 DescriptionIndex); -#endif /*GPAC_DISABLE_ISOM_WRITE*/ - -/*AVC/H264 extensions - GF_AVCConfig is defined in mpeg4_odf.h*/ - -/*gets uncompressed AVC config - user is responsible for deleting it*/ -GF_AVCConfig *gf_isom_avc_config_get(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex); -/*gets uncompressed SVC config - user is responsible for deleting it*/ -GF_AVCConfig *gf_isom_svc_config_get(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex); -/*gets uncompressed MVC config - user is responsible for deleting it*/ -GF_AVCConfig *gf_isom_mvc_config_get(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex); - -typedef enum -{ - GF_ISOM_AVCTYPE_NONE=0, - GF_ISOM_AVCTYPE_AVC_ONLY, - GF_ISOM_AVCTYPE_AVC_SVC, - GF_ISOM_AVCTYPE_SVC_ONLY, - GF_ISOM_AVCTYPE_AVC_MVC, - GF_ISOM_AVCTYPE_MVC_ONLY, -} GF_ISOMAVCType; - -u32 gf_isom_get_avc_svc_type(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex); - - -typedef enum -{ - GF_ISOM_HEVCTYPE_NONE=0, - GF_ISOM_HEVCTYPE_HEVC_ONLY, - GF_ISOM_HEVCTYPE_HEVC_LHVC, - GF_ISOM_HEVCTYPE_LHVC_ONLY, -} GF_ISOMHEVCType; - -u32 gf_isom_get_hevc_lhvc_type(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex); - -/*gets HEVC config - user is responsible for deleting it*/ -GF_HEVCConfig *gf_isom_hevc_config_get(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex); -/*gets LHVC config - user is responsible for deleting it*/ -GF_HEVCConfig *gf_isom_lhvc_config_get(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex); - -/*return true if track dependencies implying extractors or implicit reconstruction are found*/ -Bool gf_isom_needs_layer_reconstruction(GF_ISOFile *file); - -enum -{ - /*all extractors are rewritten*/ - GF_ISOM_NALU_EXTRACT_DEFAULT = 0, - /*all extractors are skipped but NALU data from this track is kept*/ - GF_ISOM_NALU_EXTRACT_LAYER_ONLY, - /*all extractors are kept (untouched sample) - used for dumping modes*/ - GF_ISOM_NALU_EXTRACT_INSPECT, - /*above mode is applied and PPS/SPS/... are appended in the front of every IDR*/ - GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG = 1<<16, - /*above mode is applied and all start codes are rewritten (xPS inband as well)*/ - GF_ISOM_NALU_EXTRACT_ANNEXB_FLAG = 2<<17, - /*above mode is applied and VDRD NAL unit is inserted before SVC slice*/ - GF_ISOM_NALU_EXTRACT_VDRD_FLAG = 1<<18, - /*all extractors are skipped and only tile track data is kept*/ - GF_ISOM_NALU_EXTRACT_TILE_ONLY = 1<<19 -}; -GF_Err gf_isom_set_nalu_extract_mode(GF_ISOFile *the_file, u32 trackNumber, u32 nalu_extract_mode); -u32 gf_isom_get_nalu_extract_mode(GF_ISOFile *the_file, u32 trackNumber); +#endif /*GPAC_DISABLE_ISOM_HINTING*/ -#ifndef GPAC_DISABLE_ISOM_WRITE -/*creates new AVC config*/ -GF_Err gf_isom_avc_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_AVCConfig *cfg, char *URLname, char *URNname, u32 *outDescriptionIndex); -/*updates AVC config*/ -GF_Err gf_isom_avc_config_update(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_AVCConfig *cfg); -/*updates SVC config. If is_additional is set, the SVCConfig will be added to the AVC sample description, otherwise the sample description will be SVC-only*/ -GF_Err gf_isom_svc_config_update(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_AVCConfig *cfg, Bool is_additional); -/*creates new SVC config*/ -GF_Err gf_isom_svc_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_AVCConfig *cfg, char *URLname, char *URNname, u32 *outDescriptionIndex); -/*deletes SVC config*/ -GF_Err gf_isom_svc_config_del(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex); - -/*updates MVC config. If is_additional is set, the MVCConfig will be added to the AVC sample description, otherwise the sample description will be MVC-only*/ -GF_Err gf_isom_mvc_config_update(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_AVCConfig *cfg, Bool is_additional); -/*creates new MVC config*/ -GF_Err gf_isom_mvc_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_AVCConfig *cfg, char *URLname, char *URNname, u32 *outDescriptionIndex); -/*deletes MVC config*/ -GF_Err gf_isom_mvc_config_del(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex); - -/*sets avc3 entry type (inband SPS/PPS) instead of of avc1 (SPS/PPS in avcC box)*/ -GF_Err gf_isom_avc_set_inband_config(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex); - -/*sets hev1 entry type (inband SPS/PPS) instead of of hvc1 (SPS/PPS in avcC box)*/ -GF_Err gf_isom_hevc_set_inband_config(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex); - -/*sets hvt1 entry type (tile track) or hev2/hvc2 type if is_base_track is set - cfg may be set to indicate sub-profile of the tile. It is the use responsability to set the tbas track reference to the base hevc track*/ -GF_Err gf_isom_hevc_set_tile_config(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_HEVCConfig *cfg, Bool is_base_track); - - -/*creates new HEVC config*/ -GF_Err gf_isom_hevc_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_HEVCConfig *cfg, char *URLname, char *URNname, u32 *outDescriptionIndex); -/*updates HEVC config*/ -GF_Err gf_isom_hevc_config_update(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_HEVCConfig *cfg); - -/*updates L-HHVC config*/ -typedef enum { - //changes track type to LHV1/LHE1: no base nor extractors in track, just enhancement layers - GF_ISOM_LEHVC_ONLY = 0, - //changes track type to HVC2/HEV2: base and extractors/enh. in track - GF_ISOM_LEHVC_WITH_BASE, - //changes track type to HVC1/HEV1 with additionnal cfg: base and enh. in track no extractors - GF_ISOM_LEHVC_WITH_BASE_BACKWARD, -} GF_ISOMLHEVCTrackType; -GF_Err gf_isom_lhvc_config_update(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_HEVCConfig *cfg, GF_ISOMLHEVCTrackType track_type); +/*! @} */ -#endif /*GPAC_DISABLE_ISOM_WRITE*/ +/*! +\addtogroup isotxt_grp Subtitles and Timed Text +\ingroup iso_grp +@{ +*/ -/* - 3GP timed text handling - NOTE: currently only writing API is developped, the reading one is not used in MPEG-4 since - MPEG-4 maps 3GP timed text to MPEG-4 Streaming Text (part 17) +/*! sets streaming text reading mode (MPEG-4 text vs 3GPP) +\param isom_file the target ISO file +\param do_convert is set, all text samples will be retrieved as TTUs and ESD will be emulated for text tracks +\return error if any */ - -/*set streamihng text reading mode: if do_convert is set, all text samples will be retrieved as TTUs -and ESD will be emulated for text tracks.*/ -GF_Err gf_isom_text_set_streaming_mode(GF_ISOFile *the_file, Bool do_convert); +GF_Err gf_isom_text_set_streaming_mode(GF_ISOFile *isom_file, Bool do_convert); #ifndef GPAC_DISABLE_ISOM_DUMP -/*exports text track to given format -@dump_type: 0 for TTXT, 1 for srt, 2 for SVG -*/ +/*! text track export type*/ typedef enum { + /*! dump as TTXT XML*/ GF_TEXTDUMPTYPE_TTXT = 0, - GF_TEXTDUMPTYPE_TTXT_BOXES = 1, - GF_TEXTDUMPTYPE_SRT = 2, - GF_TEXTDUMPTYPE_SVG = 3, + /*! dump as TTXT XML with box */ + GF_TEXTDUMPTYPE_TTXT_BOXES, + /*! dump as SRT*/ + GF_TEXTDUMPTYPE_SRT, + /*! dump as SVG*/ + GF_TEXTDUMPTYPE_SVG, + /*! dump as TTXT chapters (omits empty text samples)*/ + GF_TEXTDUMPTYPE_TTXT_CHAP, + /*! dump as OGG chapters*/ + GF_TEXTDUMPTYPE_OGG_CHAP, + /*! dump as Zoom chapters*/ + GF_TEXTDUMPTYPE_ZOOM_CHAP } GF_TextDumpType; -GF_Err gf_isom_text_dump(GF_ISOFile *the_file, u32 track, FILE *dump, GF_TextDumpType dump_type); -#endif - -/*returns encoded TX3G box (text sample description for 3GPP text streams) as needed by RTP or other standards: - @sidx: 1-based stream description index - @sidx_offset: - if 0, the sidx will NOT be written before the encoded TX3G - if not 0, the sidx will be written before the encoded TX3G, with the given offset. Offset sshould be at - least 128 for most commmon usage of TX3G (RTP, MPEG-4 timed text, etc) - +/*! dumps a text track to a file +\param isom_file the target ISO file +\param trackNumber the target track +\param dump the file object to write to (binary open mode) +\param dump_type the dump type mode +\return error if any */ -GF_Err gf_isom_text_get_encoded_tx3g(GF_ISOFile *file, u32 track, u32 sidx, u32 sidx_offset, char **tx3g, u32 *tx3g_size); +GF_Err gf_isom_text_dump(GF_ISOFile *isom_file, u32 trackNumber, FILE *dump, GF_TextDumpType dump_type); +#endif -/*checks if this text description is already inserted -@outDescIdx: set to 0 if not found, or descIndex -@same_style, @same_box: indicates if default styles and box are used +/*! gets encoded TX3G box (text sample description for 3GPP text streams) as needed by RTP or other standards: +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param sidx_offset if 0, the sidx will NOT be written before the encoded TX3G. If not 0, the sidx will be written before the encoded TX3G, with the given offset. Offset sshould be at least 128 for most commmon usage of TX3G (RTP, MPEG-4 timed text, etc) +\param tx3g set to a newly allocated buffer containing the encoded tx3g - to be freed by caller +\param tx3g_size set to the size of the encoded config +\return error if any */ -GF_Err gf_isom_text_has_similar_description(GF_ISOFile *the_file, u32 trackNumber, GF_TextSampleDescriptor *desc, u32 *outDescIdx, Bool *same_box, Bool *same_styles); +GF_Err gf_isom_text_get_encoded_tx3g(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 sidx_offset, u8 **tx3g, u32 *tx3g_size); -/*text sample formatting*/ +/*! text sample formatting*/ typedef struct _3gpp_text_sample GF_TextSample; -/*creates text sample handle*/ +/*! creates text sample handle +\return a newly allocated text sample +*/ GF_TextSample *gf_isom_new_text_sample(); -/*destroy text sample handle*/ +/*! destroys text sample handle +\param tx_samp the target text sample +*/ void gf_isom_delete_text_sample(GF_TextSample *tx_samp); -/*generic subtitle sample formatting*/ +/*! generic subtitle sample formatting*/ typedef struct _generic_subtitle_sample GF_GenericSubtitleSample; -/*creates generic subtitle sample handle*/ +/*! creates generic subtitle sample handle +\return a newly allocated generic subtitle sample +*/ GF_GenericSubtitleSample *gf_isom_new_generic_subtitle_sample(); -/*destroy generic subtitle sample handle*/ +/*! destroys generic subtitle sample handle +\param generic_subtitle_samp the target generic subtitle sample +*/ void gf_isom_delete_generic_subtitle_sample(GF_GenericSubtitleSample *generic_subtitle_samp); #ifndef GPAC_DISABLE_VTT -GF_Err gf_isom_new_webvtt_description(GF_ISOFile *movie, u32 trackNumber, GF_TextSampleDescriptor *desc, char *URLname, char *URNname, u32 *outDescriptionIndex); -GF_Err gf_isom_update_webvtt_description(GF_ISOFile *movie, u32 trackNumber, u32 descriptionIndex, const char *config); +/*! creates new WebVTT config +\param isom_file the target ISO file +\param trackNumber the target track +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\param config the WebVTT configuration string +\return error if any +*/ +GF_Err gf_isom_new_webvtt_description(GF_ISOFile *isom_file, u32 trackNumber, const char *URLname, const char *URNname, u32 *outDescriptionIndex, const char *config); #endif -GF_Err gf_isom_stxt_get_description(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, const char **mime, const char **encoding, const char **config); -GF_Err gf_isom_new_stxt_description(GF_ISOFile *movie, u32 trackNumber, u32 type, const char *mime, const char *encoding, const char *config, u32 *outDescriptionIndex); -GF_Err gf_isom_update_stxt_description(GF_ISOFile *movie, u32 trackNumber, const char *encoding, const char *config, u32 DescriptionIndex); +/*! gets WebVTT config for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\return the WebVTT configuration string +*/ +const char *gf_isom_get_webvtt_config(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets simple streaming text config for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param mime set to the mime type (optional, can be NULL) +\param encoding set to the text encoding type (optional, can be NULL) +\param config set to the WebVTT configuration string (optional, can be NULL) +\return error if any +*/ +GF_Err gf_isom_stxt_get_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, const char **mime, const char **encoding, const char **config); -GF_Err gf_isom_xml_subtitle_get_description(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates new simple streaming text config +\param isom_file the target ISO file +\param trackNumber the target track +\param type the four character code of the simple text sample description (sbtt, stxt, mett) +\param mime the mime type +\param encoding the text encoding, if any +\param config the configuration string, if any +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_new_stxt_description(GF_ISOFile *isom_file, u32 trackNumber, u32 type, const char *mime, const char *encoding, const char *config, u32 *outDescriptionIndex); + +#endif // GPAC_DISABLE_ISOM_WRITE + +/*! gets XML streaming text config for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param xmlnamespace set to the XML namespace (optional, can be NULL) +\param xml_schema_loc set to the XML schema location (optional, can be NULL) +\param mimes set to the associated mime(s) types (optional, can be NULL) +\return error if any +*/ +GF_Err gf_isom_xml_subtitle_get_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, const char **xmlnamespace, const char **xml_schema_loc, const char **mimes); -GF_Err gf_isom_new_xml_subtitle_description(GF_ISOFile *movie, u32 trackNumber, + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates a new XML streaming text config +\param isom_file the target ISO file +\param trackNumber the target track +\param xmlnamespace the XML namespace +\param xml_schema_loc the XML schema location (optional, can be NULL) +\param auxiliary_mimes the associated mime(s) types (optional, can be NULL) +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_new_xml_subtitle_description(GF_ISOFile *isom_file, u32 trackNumber, const char *xmlnamespace, const char *xml_schema_loc, const char *auxiliary_mimes, u32 *outDescriptionIndex); -GF_Err gf_isom_update_xml_subtitle_description(GF_ISOFile *movie, u32 trackNumber, - u32 descriptionIndex, GF_GenericSubtitleSampleDescriptor *desc); +#endif // GPAC_DISABLE_ISOM_WRITE + + +/*! gets XML metadata for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param xmlnamespace set to the XML namespace (optional, can be NULL) +\param schema_loc set to the XML schema location (optional, can be NULL) +\param content_encoding set to the content encoding string (optional, can be NULL) +\return error if any +*/ +GF_Err gf_isom_get_xml_metadata_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, const char **xmlnamespace, const char **schema_loc, const char **content_encoding); + +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates a new timed metadata sample description for this track +\param isom_file the target ISO file +\param trackNumber the target track +\param xmlnamespace the XML namespace +\param schema_loc the XML schema location (optional, can be NULL) +\param content_encoding the content encoding string (optional, can be NULL) +\param outDescriptionIndex set to the index of the created sample description +\return error if any +*/ +GF_Err gf_isom_new_xml_metadata_description(GF_ISOFile *isom_file, u32 trackNumber, const char *xmlnamespace, const char *schema_loc, const char *content_encoding, u32 *outDescriptionIndex); +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +/*! text flags operation type*/ typedef enum { GF_ISOM_TEXT_FLAGS_OVERWRITE = 0, GF_ISOM_TEXT_FLAGS_TOGGLE, GF_ISOM_TEXT_FLAGS_UNTOGGLE, } GF_TextFlagsMode; -//sets text display flags according to given mode. If SampleDescriptionIndex is 0, sets the flags for all text descriptions. -GF_Err gf_isom_text_set_display_flags(GF_ISOFile *file, u32 track, u32 SampleDescriptionIndex, u32 flags, GF_TextFlagsMode op_type); +/*! sets text display flags according to given mode. +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index. If 0, sets the flags for all text descriptions +\param flags the flag to set +\param op_type the flag toggle mode +\return error if any +*/ +GF_Err gf_isom_text_set_display_flags(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 flags, GF_TextFlagsMode op_type); + +/*! gets text description of a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the target sample description index +\param out_desc set to a newly allocated text sample descriptor - shall be freeed by user +\return error if any +*/ +GF_Err gf_isom_get_text_description(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, GF_TextSampleDescriptor **out_desc); #ifndef GPAC_DISABLE_ISOM_WRITE -/*Create a new TextSampleDescription in the file. -The URL and URN are used to describe external media, this will create a data reference for the media -GF_TextSampleDescriptor is defined in mpeg4_odf.h +/*! creates a new TextSampleDescription in the file. +\param isom_file the target ISO file +\param trackNumber the target track +\param desc the text sample description +\param URLname URL value of the data reference, NULL if no data reference (media in the file) +\param URNname URN value of the data reference, NULL if no data reference (media in the file) +\param outDescriptionIndex set to the index of the created sample description +\return error if any */ -GF_Err gf_isom_new_text_description(GF_ISOFile *the_file, u32 trackNumber, GF_TextSampleDescriptor *desc, char *URLname, char *URNname, u32 *outDescriptionIndex); -/*change the text sample description*/ -GF_Err gf_isom_update_text_description(GF_ISOFile *movie, u32 trackNumber, u32 descriptionIndex, GF_TextSampleDescriptor *desc); +GF_Err gf_isom_new_text_description(GF_ISOFile *isom_file, u32 trackNumber, GF_TextSampleDescriptor *desc, const char *URLname, const char *URNname, u32 *outDescriptionIndex); -/*reset text sample content*/ +/*! resets text sample content +\param tx_samp the target text sample +\return error if any +*/ GF_Err gf_isom_text_reset(GF_TextSample * tx_samp); -/*reset text sample styles but keep text*/ -GF_Err gf_isom_text_reset_styles(GF_TextSample * samp); - -/*sets UTF16 marker for text data. This MUST be called on an empty sample. If text data added later -on (cf below) is not formatted as UTF16 data(2 bytes char) the resulting text sample won't be compliant, -but this library WON'T WARN*/ -GF_Err gf_isom_text_set_utf16_marker(GF_TextSample * samp); -/*append text to sample - text_len is the number of bytes to be written from text_data. This allows -handling UTF8 and UTF16 strings in a transparent manner*/ -GF_Err gf_isom_text_add_text(GF_TextSample * tx_samp, char *text_data, u32 text_len); -/*append style modifyer to sample*/ -GF_Err gf_isom_text_add_style(GF_TextSample * tx_samp, GF_StyleRecord *rec); -/*appends highlight modifier for the sample - @start_char: first char highlighted, - @end_char: first char not highlighted*/ -GF_Err gf_isom_text_add_highlight(GF_TextSample * samp, u16 start_char, u16 end_char); -/*sets highlight color for the whole sample*/ -GF_Err gf_isom_text_set_highlight_color(GF_TextSample * samp, u8 r, u8 g, u8 b, u8 a); -GF_Err gf_isom_text_set_highlight_color_argb(GF_TextSample * samp, u32 argb); -/*appends a new karaoke sequence in the sample - @start_time: karaoke start time expressed in text stream timescale, but relative to the sample media time -*/ -GF_Err gf_isom_text_add_karaoke(GF_TextSample * samp, u32 start_time); -/*appends a new segment in the current karaoke sequence - you must build sequences in order to be compliant - @end_time: segment end time expressed in text stream timescale, but relative to the sample media time - @start_char: first char highlighted, - @end_char: first char not highlighted -*/ -GF_Err gf_isom_text_set_karaoke_segment(GF_TextSample * samp, u32 end_time, u16 start_char, u16 end_char); -/*sets scroll delay for the whole sample (scrolling is enabled through GF_TextSampleDescriptor.DisplayFlags) - @scroll_delay: delay for scrolling expressed in text stream timescale -*/ -GF_Err gf_isom_text_set_scroll_delay(GF_TextSample * samp, u32 scroll_delay); -/*appends hyperlinking for the sample - @URL: ASCII url - @altString: ASCII hint (tooltip, ...) for end user - @start_char: first char hyperlinked, - @end_char: first char not hyperlinked -*/ -GF_Err gf_isom_text_add_hyperlink(GF_TextSample * samp, char *URL, char *altString, u16 start_char, u16 end_char); -/*sets current text box (display pos&size within the text track window) for the sample*/ -GF_Err gf_isom_text_set_box(GF_TextSample * samp, s16 top, s16 left, s16 bottom, s16 right); -/*appends blinking for the sample - @start_char: first char blinking, - @end_char: first char not blinking -*/ -GF_Err gf_isom_text_add_blink(GF_TextSample * samp, u16 start_char, u16 end_char); -/*sets wrap flag for the sample - currently only 0 (no wrap) and 1 ("soft wrap") are allowed in 3GP*/ -GF_Err gf_isom_text_set_wrap(GF_TextSample * samp, u8 wrap_flags); - -/*formats sample as a regular GF_ISOSample. The resulting sample will always be marked as random access -text sample content is kept untouched*/ -GF_ISOSample *gf_isom_text_to_sample(GF_TextSample * tx_samp); +/*! resets text sample styles but keep text +\param tx_samp the target text sample +\return error if any +*/ +GF_Err gf_isom_text_reset_styles(GF_TextSample *tx_samp); + +/*! appends text to sample - text_len is the number of bytes to be written from text_data. This allows +handling UTF8 and UTF16 strings in a transparent manner +\param tx_samp the target text sample +\param text_data the text data to add +\param text_len the size of the data to add +\return error if any +*/ +GF_Err gf_isom_text_add_text(GF_TextSample *tx_samp, char *text_data, u32 text_len); +/*! appends style modifyer to sample +\param tx_samp the target text sample +\param rec the style record to add +\return error if any +*/ +GF_Err gf_isom_text_add_style(GF_TextSample *tx_samp, GF_StyleRecord *rec); +/*! appends highlight modifier for the sample +\param tx_samp the target text sample +\param start_char first char highlighted, +\param end_char first char not highlighted +\return error if any +*/ +GF_Err gf_isom_text_add_highlight(GF_TextSample *tx_samp, u16 start_char, u16 end_char); + +/*! sets highlight color for the whole sample +\param tx_samp the target text sample +\param argb color value +\return error if any +*/ +GF_Err gf_isom_text_set_highlight_color(GF_TextSample *tx_samp, u32 argb); +/*! appends a new karaoke sequence in the sample +\param tx_samp the target text sample +\param start_time karaoke start time expressed in text stream timescale, but relative to the sample media time +\return error if any +*/ +GF_Err gf_isom_text_add_karaoke(GF_TextSample *tx_samp, u32 start_time); +/*! appends a new segment in the current karaoke sequence - you must build sequences in order to be compliant +\param tx_samp the target text sample +\param end_time segment end time expressed in text stream timescale, but relative to the sample media time +\param start_char first char highlighted, +\param end_char first char not highlighted +\return error if any +*/ +GF_Err gf_isom_text_set_karaoke_segment(GF_TextSample *tx_samp, u32 end_time, u16 start_char, u16 end_char); +/*! sets scroll delay for the whole sample (scrolling is enabled through GF_TextSampleDescriptor.DisplayFlags) +\param tx_samp the target text sample +\param scroll_delay delay for scrolling expressed in text stream timescale +\return error if any +*/ +GF_Err gf_isom_text_set_scroll_delay(GF_TextSample *tx_samp, u32 scroll_delay); +/*! appends hyperlinking for the sample +\param tx_samp the target text sample +\param URL UTF-8 url +\param altString UTF-8 hint (tooltip, ...) for end user +\param start_char first char hyperlinked, +\param end_char first char not hyperlinked +\return error if any +*/ +GF_Err gf_isom_text_add_hyperlink(GF_TextSample *tx_samp, char *URL, char *altString, u16 start_char, u16 end_char); +/*! sets current text box (display pos&size within the text track window) for the sample +\param tx_samp the target text sample +\param top top coordinate of box +\param left left coordinate of box +\param bottom bottom coordinate of box +\param right right coordinate of box +\return error if any +*/ +GF_Err gf_isom_text_set_box(GF_TextSample *tx_samp, s16 top, s16 left, s16 bottom, s16 right); +/*! appends blinking for the sample +\param tx_samp the target text sample +\param start_char first char blinking, +\param end_char first char not blinking +\return error if any +*/ +GF_Err gf_isom_text_add_blink(GF_TextSample *tx_samp, u16 start_char, u16 end_char); +/*! sets wrap flag for the sample +\param tx_samp the target text sample +\param wrap_flags text wrap flags - currently only 0 (no wrap) and 1 ("soft wrap") are allowed in 3GP +\return error if any +*/ +GF_Err gf_isom_text_set_wrap(GF_TextSample *tx_samp, u8 wrap_flags); + +/*! formats sample as a regular GF_ISOSample payload in a bitstream object. +\param tx_samp the target text sample +\param bs thetarget bitstream +\return error if any +*/ +GF_Err gf_isom_text_sample_write_bs(const GF_TextSample *tx_samp, GF_BitStream *bs); + +/*! formats sample as a regular GF_ISOSample. +The resulting sample will always be marked as random access +\param tx_samp the target text sample +\return the corresponding serialized ISO sample +*/ +GF_ISOSample *gf_isom_text_to_sample(const GF_TextSample *tx_samp); +/*! gets the serialized size of the text sample +\param tx_samp the target text sample +\return the serialized size +*/ +u32 gf_isom_text_sample_size(GF_TextSample *tx_samp); +/*! creates a new XML subtitle sample +\return a new XML subtitle sample +*/ GF_GenericSubtitleSample *gf_isom_new_xml_subtitle_sample(); -void gf_isom_delete_xml_subtitle_sample(GF_GenericSubtitleSample * samp); -GF_Err gf_isom_xml_subtitle_reset(GF_GenericSubtitleSample *samp); -GF_ISOSample *gf_isom_xml_subtitle_to_sample(GF_GenericSubtitleSample * tx_samp); -GF_Err gf_isom_xml_subtitle_sample_add_text(GF_GenericSubtitleSample *samp, char *text_data, u32 text_len); +/*! deletes an XML subtitle sample +\param subt_samp the target XML subtitle sample +*/ +void gf_isom_delete_xml_subtitle_sample(GF_GenericSubtitleSample *subt_samp); +/*! resets content of an XML subtitle sample +\param subt_samp the target XML subtitle sample +\return error if any +*/ +GF_Err gf_isom_xml_subtitle_reset(GF_GenericSubtitleSample *subt_samp); +/*! the corresponding serialized ISO sample +\param subt_samp the target XML subtitle sample +\return the corresponding serialized ISO sample +*/ +GF_ISOSample *gf_isom_xml_subtitle_to_sample(GF_GenericSubtitleSample *subt_samp); +/*! appends text to an XML subtitle sample +\param subt_samp the target XML subtitle sample +\param text_data the UTF-8 or UTF-16 data to add +\param text_len the size of the text to add in bytes +\return error if any +*/ +GF_Err gf_isom_xml_subtitle_sample_add_text(GF_GenericSubtitleSample *subt_samp, char *text_data, u32 text_len); #endif /*GPAC_DISABLE_ISOM_WRITE*/ -/***************************************************** - ISMACryp Samples -*****************************************************/ -/*flags for GF_ISMASample*/ +/*! @} */ + + +/*! +\addtogroup isocrypt_grp Content Protection +\ingroup iso_grp + +@{ +*/ + +/*! DRM related code points*/ enum { - /*signals the stream the sample belongs to uses selective encryption*/ + /*! Storage location of CENC sample auxiliary in PSEC UUID box*/ + GF_ISOM_BOX_UUID_PSEC = GF_4CC( 'P', 'S', 'E', 'C' ), + /*! Storage location of CENC sample auxiliary in senc box*/ + GF_ISOM_BOX_TYPE_SENC = GF_4CC( 's', 'e', 'n', 'c'), + /*! PSSH box type */ + GF_ISOM_BOX_TYPE_PSSH = GF_4CC( 'p', 's', 's', 'h'), + /*! ISMA Encryption Scheme Type in the SchemeTypeInfoBox */ + GF_ISOM_ISMACRYP_SCHEME = GF_4CC( 'i', 'A', 'E', 'C' ), + /*! OMA DRM Encryption Scheme Type in the SchemeTypeInfoBox */ + GF_ISOM_OMADRM_SCHEME = GF_4CC('o','d','k','m'), + /*! CENC AES-CTR Encryption Scheme Type in the SchemeTypeInfoBox */ + GF_ISOM_CENC_SCHEME = GF_4CC('c','e','n','c'), + /*! CENC AES-CBC Encryption Scheme Type in the SchemeTypeInfoBox */ + GF_ISOM_CBC_SCHEME = GF_4CC('c','b','c','1'), + /*! Adobe Encryption Scheme Type in the SchemeTypeInfoBox */ + GF_ISOM_ADOBE_SCHEME = GF_4CC('a','d','k','m'), + /*! CENC AES-CTR Pattern Encryption Scheme Type in the SchemeTypeInfoBox */ + GF_ISOM_CENS_SCHEME = GF_4CC('c','e','n','s'), + /*! CENC AES-CBC Pattern Encryption Scheme Type in the SchemeTypeInfoBox */ + GF_ISOM_CBCS_SCHEME = GF_4CC('c','b','c','s'), + /*! PIFF Scheme Type in the SchemeTypeInfoBox */ + GF_ISOM_PIFF_SCHEME = GF_4CC('p','i','f','f'), +}; + +/*! checks if a track is encrypted or protected +\param isom_file the target ISO file +\param trackNumber the target track +\return GF_TRUE if track is protected, GF_FALSE otherwise*/ +Bool gf_isom_is_track_encrypted(GF_ISOFile *isom_file, u32 trackNumber); + + +/*! flags for GF_ISMASample*/ +typedef enum +{ + /*! signals the stream the sample belongs to uses selective encryption*/ GF_ISOM_ISMA_USE_SEL_ENC = 1, - /*signals the sample is encrypted*/ + /*! signals the sample is encrypted*/ GF_ISOM_ISMA_IS_ENCRYPTED = 2, -}; +} GF_ISOISMACrypFlags; +/*! ISMA sample*/ typedef struct { - /*IV in ISMACryp is Byte Stream Offset*/ + /*! IV in ISMACryp is Byte Stream Offset*/ u64 IV; - u8 IV_length;/*repeated from sampleDesc for convenience*/ + /*! IV size in bytes, repeated from sampleDesc for convenience*/ + u8 IV_length; + /*! key indicator*/ u8 *key_indicator; - u8 KI_length;/*repeated from sampleDesc for convenience*/ + /*! key indicator size, repeated from sampleDesc for convenience*/ + u8 KI_length; + /*! payload size*/ u32 dataLength; - char *data; + /*! payload*/ + u8 *data; + /*! flags*/ u32 flags; } GF_ISMASample; -/** - * creates a new empty ISMA sample - */ +/*! creates a new empty ISMA sample +\return a new empty ISMA sample +*/ GF_ISMASample *gf_isom_ismacryp_new_sample(); -/*delete an ISMA sample. NOTE:the buffers content will be destroyed by default. -if you wish to keep the buffer, set dataLength to 0 in the sample before deleting it*/ +/*! delete an ISMA sample. +\note the buffer content will be destroyed by default. If you wish to keep the buffer, set dataLength to 0 in the sample before deleting it +\param samp the target ISMA sample +*/ void gf_isom_ismacryp_delete_sample(GF_ISMASample *samp); -/*decodes ISMACryp sample based on all info in ISMACryp sample description*/ -GF_ISMASample *gf_isom_ismacryp_sample_from_data(char *data, u32 dataLength, Bool use_selective_encryption, u8 KI_length, u8 IV_length); -/*rewrites samp content from s content*/ -GF_Err gf_isom_ismacryp_sample_to_sample(GF_ISMASample *s, GF_ISOSample *dest); - -/*decodes ISMACryp sample based on sample and its descrition index - returns NULL if not an ISMA sample -Note: input sample is NOT destroyed*/ -GF_ISMASample *gf_isom_get_ismacryp_sample(GF_ISOFile *the_file, u32 trackNumber, GF_ISOSample *samp, u32 sampleDescriptionIndex); - -/*returns whether the given media is a protected one or not - return scheme protection 4CC*/ -u32 gf_isom_is_media_encrypted(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex); - -/*returns whether the given media is a protected ISMACryp one or not*/ -Bool gf_isom_is_ismacryp_media(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex); - -/*returns whether the given media is a protected ISMACryp one or not*/ -Bool gf_isom_is_omadrm_media(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex); - -GF_Err gf_isom_get_omadrm_info(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *outOriginalFormat, +/*! decodes ISMACryp sample based on all info in ISMACryp sample description +\param data sample data +\param dataLength sample data size in bytes +\param use_selective_encryption set to GF_TRUE if sample uses selective encryption +\param KI_length set to the size in bytes of the key indicator - 0 means no key roll +\param IV_length set to the size in bytes of the initialization vector +\return a newly allocated ISMA sample with the parsed data +*/ +GF_ISMASample *gf_isom_ismacryp_sample_from_data(u8 *data, u32 dataLength, Bool use_selective_encryption, u8 KI_length, u8 IV_length); + +/*! decodes ISMACryp sample based on sample and its descrition index +\param isom_file the target ISO file +\param trackNumber the target track +\param samp the sample to decode +\param sampleDescriptionIndex the sample description index of the sample to decode +\return the ISMA sample or NULL if not an ISMA sample or error +*/ +GF_ISMASample *gf_isom_get_ismacryp_sample(GF_ISOFile *isom_file, u32 trackNumber, const GF_ISOSample *samp, u32 sampleDescriptionIndex); + +/*! checks if sample description is protected or not +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index. If 0, checks all sample descriptions for protected ones +\return scheme protection 4CC or 0 if not protected*/ +u32 gf_isom_is_media_encrypted(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! checks if sample description is protected with ISMACryp +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\return GF_TRUE if ISMA protection is used*/ +Bool gf_isom_is_ismacryp_media(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! checks if sample description is protected with OMA DRM +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\return GF_TRUE if OMA DRM protection is used*/ +Bool gf_isom_is_omadrm_media(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets OMA DRM configuration - all output parameters are optional and may be NULL +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param outOriginalFormat four character code of the unprotected sample description +\param outSchemeType set to four character code of the protection scheme type +\param outSchemeVersion set to scheme protection version +\param outContentID set to associated ID of content +\param outRightsIssuerURL set to the rights issuer (license server) URL +\param outTextualHeaders set to OMA textual headers +\param outTextualHeadersLen set to the size in bytes of OMA textual headers +\param outPlaintextLength set to the size in bytes of clear data in file +\param outEncryptionType set to the OMA encryption type used +\param outSelectiveEncryption set to GF_TRUE if sample description uses selective encryption +\param outIVLength set to the size of the initialization vector +\param outKeyIndicationLength set to the size of the key indicator +\return error if any +*/ +GF_Err gf_isom_get_omadrm_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *outOriginalFormat, u32 *outSchemeType, u32 *outSchemeVersion, const char **outContentID, const char **outRightsIssuerURL, const char **outTextualHeaders, u32 *outTextualHeadersLen, u64 *outPlaintextLength, u32 *outEncryptionType, Bool *outSelectiveEncryption, u32 *outIVLength, u32 *outKeyIndicationLength); -/*retrieves ISMACryp info for the given track & SDI - all output parameters are optional - URIs SHALL NOT BE MODIFIED BY USER - @outOriginalFormat: retrieves orginal protected media format - usually GF_ISOM_SUBTYPE_MPEG4 - @outSchemeType: retrieves 4CC of protection scheme (GF_ISOM_ISMACRYP_SCHEME = iAEC in ISMACryp 1.0) - outSchemeVersion: retrieves version of protection scheme (1 in ISMACryp 1.0) - outSchemeURI: retrieves URI location of scheme - outKMS_URI: retrieves URI location of key management system - only valid with ISMACryp 1.0 - outSelectiveEncryption: specifies whether sample-based encryption is used in media - only valid with ISMACryp 1.0 - outIVLength: specifies length of Initial Vector - only valid with ISMACryp 1.0 - outKeyIndicationLength: specifies length of key indicator - only valid with ISMACryp 1.0 - outSelectiveEncryption, outIVLength and outKeyIndicationLength are usually not needed to decode an - ISMA sample when using gf_isom_get_ismacryp_sample fct above +/*! retrieves ISMACryp info for the given track & SDI - all output parameters are optional - URIs SHALL NOT BE MODIFIED BY USER + +\note outSelectiveEncryption, outIVLength and outKeyIndicationLength are usually not needed to decode an ISMA sample when using \ref gf_isom_get_ismacryp_sample + +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param outOriginalFormat set to orginal unprotected media format +\param outSchemeType set to 4CC of protection scheme (GF_ISOM_ISMACRYP_SCHEME = iAEC in ISMACryp 1.0) +\param outSchemeVersion set to version of protection scheme (1 in ISMACryp 1.0) +\param outSchemeURI set to URI location of scheme +\param outKMS_URI set to URI location of key management system - only valid with ISMACryp 1.0 +\param outSelectiveEncryption set to whether sample-based encryption is used in media - only valid with ISMACryp 1.0 +\param outIVLength set to length of Initial Vector - only valid with ISMACryp 1.0 +\param outKeyIndicationLength set to length of key indicator - only valid with ISMACryp 1.0 +\return error if any */ -GF_Err gf_isom_get_ismacryp_info(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *outOriginalFormat, u32 *outSchemeType, u32 *outSchemeVersion, const char **outSchemeURI, const char **outKMS_URI, Bool *outSelectiveEncryption, u32 *outIVLength, u32 *outKeyIndicationLength); - -/*returns original format type of a protected media file*/ -GF_Err gf_isom_get_original_format_type(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *outOriginalFormat); +GF_Err gf_isom_get_ismacryp_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *outOriginalFormat, u32 *outSchemeType, u32 *outSchemeVersion, const char **outSchemeURI, const char **outKMS_URI, Bool *outSelectiveEncryption, u32 *outIVLength, u32 *outKeyIndicationLength); + +/*! gets original format four character code type of a protected media sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index. If 0, checks all sample descriptions for a protected one +\param outOriginalFormat set to orginal unprotected media format +\return error if any +*/ +GF_Err gf_isom_get_original_format_type(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *outOriginalFormat); +/*! CENC subsample entry*/ typedef struct { - u16 bytes_clear_data; + /*! size of bytes in clear - 16 bit stored but we use 32*/ + u32 bytes_clear_data; + /*! size of bytes encrypted*/ u32 bytes_encrypted_data; } GF_CENCSubSampleEntry; +/*! CENC auxiliary info*/ typedef struct __cenc_sample_aux_info { - u8 IV_size; //0, 8 or 16; it MUST NOT be written to file - bin128 IV; /*can be 0, 64 or 128 bits - if 64, bytes 0-7 are used and 8-15 are 0-padded*/ + /*! IV size: 0, 8 or 16; it MUST NOT be written to file*/ + u8 IV_size; + /*! IV can be 0, 64 or 128 bits - if 64, bytes 0-7 are used and 8-15 are 0-padded*/ + bin128 IV; + /*! number of subsamples*/ u16 subsample_count; + /*! subsamples*/ GF_CENCSubSampleEntry *subsamples; } GF_CENCSampleAuxInfo; #ifndef GPAC_DISABLE_ISOM_WRITE -/*removes protection info (does not perform decryption :), for ISMA, OMA and CENC*/ -GF_Err gf_isom_remove_track_protection(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex); -/*creates ISMACryp protection info (does not perform encryption :)*/ -GF_Err gf_isom_set_ismacryp_protection(GF_ISOFile *the_file, u32 trackNumber, u32 desc_index, u32 scheme_type, +/*! creates ISMACryp protection info for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param scheme_type 4CC of protection scheme (GF_ISOM_ISMACRYP_SCHEME = iAEC in ISMACryp 1.0) +\param scheme_version version of protection scheme (1 in ISMACryp 1.0) +\param scheme_uri URI location of scheme +\param kms_URI URI location of key management system - only valid with ISMACryp 1.0 +\param selective_encryption whether sample-based encryption is used in media - only valid with ISMACryp 1.0 +\param KI_length length of key indicator - only valid with ISMACryp 1.0 +\param IV_length length of Initial Vector - only valid with ISMACryp 1.0 +\return error if any +*/ +GF_Err gf_isom_set_ismacryp_protection(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 scheme_type, u32 scheme_version, char *scheme_uri, char *kms_URI, Bool selective_encryption, u32 KI_length, u32 IV_length); -/*change scheme URI and/or KMS URI for crypted files. Other params cannot be changed once the media is crypted - @scheme_uri: new scheme URI, or NULL to keep previous - @kms_uri: new KMS URI, or NULL to keep previous +/*! changes scheme URI and/or KMS URI for crypted files. Other params cannot be changed once the media is crypted +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param scheme_uri new scheme URI, or NULL to keep previous +\param kms_uri new KMS URI, or NULL to keep previous +\return error if any */ -GF_Err gf_isom_change_ismacryp_protection(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, char *scheme_uri, char *kms_uri); - - -GF_Err gf_isom_set_oma_protection(GF_ISOFile *the_file, u32 trackNumber, u32 desc_index, +GF_Err gf_isom_change_ismacryp_protection(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, char *scheme_uri, char *kms_uri); + + +/*! creates OMA DRM protection for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param contentID associated ID of content +\param kms_URI the rights issuer (license server) URL +\param encryption_type the OMA encryption type used +\param plainTextLength the size in bytes of clear data in file +\param textual_headers OMA textual headers +\param textual_headers_len the size in bytes of OMA textual headers +\param selective_encryption GF_TRUE if sample description uses selective encryption +\param KI_length the size of the key indicator +\param IV_length the size of the initialization vector +\return error if any +*/ +GF_Err gf_isom_set_oma_protection(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, char *contentID, char *kms_URI, u32 encryption_type, u64 plainTextLength, char *textual_headers, u32 textual_headers_len, Bool selective_encryption, u32 KI_length, u32 IV_length); - -GF_Err gf_isom_cenc_allocate_storage(GF_ISOFile *the_file, u32 trackNumber, u32 container_type, u32 AlgorithmID, u8 IV_size, bin128 KID); -GF_Err gf_isom_track_cenc_add_sample_info(GF_ISOFile *the_file, u32 trackNumber, u32 container_type, u8 IV_size, char *buf, u32 len); - - - -GF_Err gf_isom_set_cenc_protection(GF_ISOFile *the_file, u32 trackNumber, u32 desc_index, u32 scheme_type, +/*! creates a generic protection for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param scheme_type 4CC of protection scheme (GF_ISOM_ISMACRYP_SCHEME = iAEC in ISMACryp 1.0) +\param scheme_version version of protection scheme (1 in ISMACryp 1.0) +\param scheme_uri URI location of scheme +\param kms_URI the rights issuer (license server) URL +\return error if any +*/ +GF_Err gf_isom_set_generic_protection(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 scheme_type, u32 scheme_version, char *scheme_uri, char *kms_URI); + +/*! allocates storage for CENC side data in a senc or UUID box +\param isom_file the target ISO file +\param trackNumber the target track +\param container_type the code of the container (currently 'senc' for CENC or 'PSEC' for smooth) +\param AlgorithmID algorith ID, usually 0 +\param IV_size the size of the init vector +\param KID the default Key ID +\return error if any +*/ +GF_Err gf_isom_cenc_allocate_storage(GF_ISOFile *isom_file, u32 trackNumber, u32 container_type, u32 AlgorithmID, u8 IV_size, bin128 KID); + +/*! adds cenc SAI for the last sample added to a track +\param isom_file the target ISO file +\param trackNumber the target track +\param container_type the code of the container (currently 'senc' for CENC or 'PSEC' for smooth) +\param IV_size the size of the init vector +\param buf the SAI buffer +\param len the size of the SAI buffer. If buf is NULL but len is given, this adds an unencrypted entry. otherwise, buf && len represent the sai cenc info to add +\param use_subsamples if GF_TRUE, the media format uses CENC subsamples +\param clear_IV the IV used for clear samples (when buf is null) +\param use_saio_32bit forces usage of 32-bit saio boxes +\return error if any +*/ +GF_Err gf_isom_track_cenc_add_sample_info(GF_ISOFile *isom_file, u32 trackNumber, u32 container_type, u8 IV_size, u8 *buf, u32 len, Bool use_subsamples, u8 *clear_IV, Bool use_saio_32bit); + + + +/*! creates CENC protection for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param scheme_type 4CC of protection scheme (GF_ISOM_ISMACRYP_SCHEME = iAEC in ISMACryp 1.0) +\param scheme_version version of protection scheme (1 in ISMACryp 1.0) +\param default_IsEncrypted default isEncrypted flag +\param default_IV_size default IV size flag +\param default_KID default key ID used +\param default_crypt_byte_block default crypt block size for pattern encryption +\param default_skip_byte_block default skip block size for pattern encryption +\param default_constant_IV_size default constant IV size +\param default_constant_IV default constant IV +\return error if any +*/ +GF_Err gf_isom_set_cenc_protection(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 scheme_type, u32 scheme_version, u32 default_IsEncrypted, u8 default_IV_size, bin128 default_KID, - u8 default_crypt_byte_block, u8 default_skip_byte_block, + u8 default_crypt_byte_block, u8 default_skip_byte_block, u8 default_constant_IV_size, bin128 default_constant_IV); +/*! adds PSSH info for a file, can be called several time per system ID +\param isom_file the target ISO file +\param systemID the ID of the protection system +\param version the version of the protection system +\param KID_count the number of key IDs +\param KID the list of key IDs +\param data opaque data for the protection system +\param len size of the opaque data +\param use_piff if GF_TRUE, use PIFF pssh UUID box when creating the pssh +\return error if any +*/ +GF_Err gf_cenc_set_pssh(GF_ISOFile *isom_file, bin128 systemID, u32 version, u32 KID_count, bin128 *KID, u8 *data, u32 len, Bool use_piff); -GF_Err gf_cenc_set_pssh(GF_ISOFile *mp4, bin128 systemID, u32 version, u32 KID_count, bin128 *KID, char *data, u32 len); +/*! removes CENC senc box info +\param isom_file the target ISO file +\param trackNumber the target track +\return error if any +*/ +GF_Err gf_isom_remove_samp_enc_box(GF_ISOFile *isom_file, u32 trackNumber); +/*! removes all CENC sample groups +\param isom_file the target ISO file +\param trackNumber the target track +\return error if any +*/ +GF_Err gf_isom_remove_samp_group_box(GF_ISOFile *isom_file, u32 trackNumber); -GF_Err gf_isom_remove_cenc_saiz(GF_ISOFile *the_file, u32 trackNumber); -GF_Err gf_isom_remove_cenc_saio(GF_ISOFile *the_file, u32 trackNumber); -GF_Err gf_isom_remove_samp_enc_box(GF_ISOFile *the_file, u32 trackNumber); -GF_Err gf_isom_remove_samp_group_box(GF_ISOFile *the_file, u32 trackNumber); -GF_Err gf_isom_remove_pssh_box(GF_ISOFile *the_file); +#endif //GPAC_DISABLE_ISOM_WRITE -Bool gf_isom_is_adobe_protection_media(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex); -GF_Err gf_isom_get_adobe_protection_info(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *outOriginalFormat, u32 *outSchemeType, u32 *outSchemeVersion); -GF_Err gf_isom_set_adobe_protection(GF_ISOFile *the_file, u32 trackNumber, u32 desc_index, u32 scheme_type, u32 scheme_version, Bool is_selective_enc, char *metadata, u32 len); +/*! checks if sample description is protected with Adobe systems +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\return GF_TRUE if ADOBE protection is used +*/ +Bool gf_isom_is_adobe_protection_media(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! gets adobe protection information for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param outOriginalFormat set to orginal unprotected media format +\param outSchemeType set to 4CC of protection scheme (GF_ISOM_ISMACRYP_SCHEME = iAEC in ISMACryp 1.0) +\param outSchemeVersion set to version of protection scheme (1 in ISMACryp 1.0) +\param outMetadata set to adobe metadata string +\return GF_TRUE if ADOBE protection is used +*/ +GF_Err gf_isom_get_adobe_protection_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *outOriginalFormat, u32 *outSchemeType, u32 *outSchemeVersion, const char **outMetadata); -void gf_isom_ipmpx_remove_tool_list(GF_ISOFile *the_file); +#ifndef GPAC_DISABLE_ISOM_WRITE +/*! creates an adobe protection for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param scheme_type 4CC of protection scheme (GF_ISOM_ISMACRYP_SCHEME = iAEC in ISMACryp 1.0) +\param scheme_version version of protection scheme (1 in ISMACryp 1.0) +\param is_selective_enc indicates if selective encryption is used +\param metadata metadata information +\param len size of metadata information in bytes +\return error if any +*/ +GF_Err gf_isom_set_adobe_protection(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 scheme_type, u32 scheme_version, Bool is_selective_enc, char *metadata, u32 len); #endif /*GPAC_DISABLE_ISOM_WRITE*/ -Bool gf_isom_is_cenc_media(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex); -GF_Err gf_isom_get_cenc_info(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *outOriginalFormat, u32 *outSchemeType, u32 *outSchemeVersion, u32 *outIVLength); +/*! checks of sample description is protected with CENC +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index. If 0, checks all sample descriptions for protected ones +\return GF_TRUE if sample protection is CENC +*/ +Bool gf_isom_is_cenc_media(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); +/*! gets CENC information of a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param outOriginalFormat set to orginal unprotected media format +\param outSchemeType set to 4CC of protection scheme (GF_ISOM_ISMACRYP_SCHEME = iAEC in ISMACryp 1.0) +\param outSchemeVersion set to version of protection scheme (1 in ISMACryp 1.0) +\param outIVLength set to the IV size in bytes +\return error if any +*/ +GF_Err gf_isom_get_cenc_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *outOriginalFormat, u32 *outSchemeType, u32 *outSchemeVersion, u32 *outIVLength); +/*! destroys a CENC sample auxiliary structure +\param samp_aux_info the target auxiliary buffer +*/ void gf_isom_cenc_samp_aux_info_del(GF_CENCSampleAuxInfo *samp_aux_info); -/*boxType is type of box which contains the sample auxiliary information. Now we have two type: GF_ISOM_BOX_UUID_PSEC and GF_ISOM_BOX_TYPE_SENC*/ -GF_Err gf_isom_cenc_get_sample_aux_info(GF_ISOFile *the_file, u32 trackNumber, u32 sampleNumber, GF_CENCSampleAuxInfo **sai, u32 *container_type); - -void gf_isom_cenc_get_default_info(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *default_IsEncrypted, u8 *default_IV_size, bin128 *default_KID); - -Bool gf_isom_cenc_is_pattern_mode(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex); -void gf_isom_cenc_get_default_pattern_info(GF_ISOFile *the_file, u32 trackNumber, u32 sampleDescriptionIndex, u8 *default_crypt_byte_block, u8 *default_skip_byte_block); +/*! gets CENC auxiliary info of a sample +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample +\param sampleDescIndex the target sample description index +\param sai set to the auxiliary info - shall be freeed by caller +\param container_type set to type of box which contains the sample auxiliary information. Now we have two type: GF_ISOM_BOX_UUID_PSEC and GF_ISOM_BOX_TYPE_SENC +\return error if any +*/ +GF_Err gf_isom_cenc_get_sample_aux_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 sampleDescIndex, GF_CENCSampleAuxInfo **sai, u32 *container_type); + +/*! gets CENC auxiliary info of a sample as a buffer +\note the serialized buffer format is IV on IV_size bytes, then subsample count if any an [clear_bytes(u16), crypt_bytes(u32)] subsamples + +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample +\param sampleDescIndex the sample description index +\param container_type is type of box which contains the sample auxiliary information. Now we have two type: GF_ISOM_BOX_UUID_PSEC and GF_ISOM_BOX_TYPE_SENC +\param out_buffer set to a newly allocated buffer, or reallocated buffer if not NULL +\param outSize set to the size of the serialized buffer. If an existing buffer was passed, the passed value shall be the allocated buffer size (the returned value is still the buffer size) +\return error if any +*/ +GF_Err gf_isom_cenc_get_sample_aux_info_buffer(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 sampleDescIndex, u32 *container_type, u8 **out_buffer, u32 *outSize); + +/*! gets CENC default info for a sample description +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\param container_type set to the container type of SAI data +\param default_IsEncrypted set to default isEncrypted flag +\param default_IV_size set to default IV size flag +\param default_KID set to default key ID used +\param constant_IV_size set to default constant IV size +\param constant_IV set to default constant IV +\param crypt_byte_block set to default crypt block size for pattern encryption +\param skip_byte_block set to default skip block size for pattern encryption +*/ +void gf_isom_cenc_get_default_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex, u32 *container_type, Bool *default_IsEncrypted, u8 *default_IV_size, bin128 *default_KID, u8 *constant_IV_size, bin128 *constant_IV, u8 *crypt_byte_block, u8 *skip_byte_block); -u32 gf_isom_get_pssh_count(GF_ISOFile *file); -/*index is 1-based, all pointers shall not be free*/ -GF_Err gf_isom_get_pssh_info(GF_ISOFile *file, u32 pssh_index, bin128 SystemID, u32 *KID_count, const bin128 **KIDs, const u8 **private_data, u32 *private_data_size); +/*! checks if CENC protection uses pattern encryption +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleDescriptionIndex the sample description index +\return GF_TRUE if protection uses pattern encryption +*/ +Bool gf_isom_cenc_is_pattern_mode(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); +/*! gets the number of PSSH defined +\param isom_file the target ISO file +\return number of PSSH defined +*/ +u32 gf_isom_get_pssh_count(GF_ISOFile *isom_file); + +/*! gets PSS info +\param isom_file the target ISO file +\param pssh_index 1-based index of PSSH to query, see \ref gf_isom_get_pssh_count +\param SystemID set to the protection system ID +\param version set to the protection system version +\param KID_count set to the number of key IDs defined +\param KIDs array of defined key IDs +\param private_data set to a buffer containing system ID private data +\param private_data_size set to the size of the system ID private data +\return error if any +*/ +GF_Err gf_isom_get_pssh_info(GF_ISOFile *isom_file, u32 pssh_index, bin128 SystemID, u32 *version, u32 *KID_count, const bin128 **KIDs, const u8 **private_data, u32 *private_data_size); #ifndef GPAC_DISABLE_ISOM_DUMP -/*xml dumpers*/ -GF_Err gf_isom_dump_ismacryp_protection(GF_ISOFile *the_file, u32 trackNumber, FILE * trace); -GF_Err gf_isom_dump_ismacryp_sample(GF_ISOFile *the_file, u32 trackNumber, u32 SampleNum, FILE *trace); +/*! dumps ismacrypt protection of sample descriptions to xml trace +\param isom_file the target ISO file +\param trackNumber the target track +\param trace the file object to dump to +\return error if any +*/ +GF_Err gf_isom_dump_ismacryp_protection(GF_ISOFile *isom_file, u32 trackNumber, FILE * trace); +/*! dumps ismacrypt sample to xml trace +\param isom_file the target ISO file +\param trackNumber the target track +\param SampleNum the target sample number +\param trace the file object to dump to +\return error if any +*/ +GF_Err gf_isom_dump_ismacryp_sample(GF_ISOFile *isom_file, u32 trackNumber, u32 SampleNum, FILE *trace); #endif +/*! gets CENC configuration for a given sample +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param IsEncrypted set to GF_TRUE if the sample is encrypted, GF_FALSE otherwise (optional can be NULL) +\param IV_size set to IV size in bytes used for the sample (optional can be NULL) +\param KID set to key ID used for the sample (optional can be NULL) +\param crypt_byte_block set to crypt block count for pattern encryption (optional can be NULL) +\param skip_byte_block set to skip block count for pattern encryption (optional can be NULL) +\param constant_IV_size set to constant IV size (optional can be NULL) +\param constant_IV set to constant IV (optional can be NULL) +\return error if any +*/ +GF_Err gf_isom_get_sample_cenc_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, Bool *IsEncrypted, u8 *IV_size, bin128 *KID, u8 *crypt_byte_block, u8 *skip_byte_block, u8 *constant_IV_size, bin128 *constant_IV); -/******************************************************************** - GENERAL META API FUNCTIONS - - Meta can be stored at several places in the file layout: - * root level (like moov, ftyp and co) - * moov level - * track level - Meta API uses the following parameters for all functions: - - gf_isom_*_meta_*(GF_ISOFile *file, Bool root_meta, u32 track_num, ....) with: - @root_meta: if set, accesses file root meta - @track_num: if root_meta not set, specifies whether the target meta is at the - moov level (track_num=0) or at the track level. - -********************************************************************/ -/*gets meta type. Returned value: 0 if no meta found, or four char code of meta (eg, "mp21", "smil", ...)*/ -u32 gf_isom_get_meta_type(GF_ISOFile *file, Bool root_meta, u32 track_num); +/*! @} */ -/*indicates if the meta has an XML container (note that XML data can also be included as items). -return value: 0 (no XML or error), 1 (XML text), 2 (BinaryXML, eg BiM) */ -u32 gf_isom_has_meta_xml(GF_ISOFile *file, Bool root_meta, u32 track_num); +/*! +\addtogroup isometa_grp Meta and Image File Format +\ingroup iso_grp -/*extracts XML (if any) from given meta - @outName: output file path and location for writing - @is_binary: indicates if XML is Bim or regular XML +@{ */ -GF_Err gf_isom_extract_meta_xml(GF_ISOFile *file, Bool root_meta, u32 track_num, char *outName, Bool *is_binary); -/*returns number of items described in this meta*/ -u32 gf_isom_get_meta_item_count(GF_ISOFile *file, Bool root_meta, u32 track_num); - -/*gets item info for the given item - @item_num: 1-based index of item to query - @itemID (optional): item ID in file - @is_self_reference: item is the file itself - @item_name (optional): item name - @item_mime_type (optional): item mime type - @item_encoding (optional): item content encoding type - @item_url, @item_urn (optional): url/urn of external resource containing this item data if any. - When item is fully contained in file, these are set to NULL +/*! gets meta type +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\return 0 if no meta found, or four char code of meta (eg, "mp21", "smil", ...)*/ +u32 gf_isom_get_meta_type(GF_ISOFile *isom_file, Bool root_meta, u32 track_num); + +/*! checks if the meta has an XML container (note that XML data can also be included as items). +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\return 0 (no XML or error), 1 (XML text), 2 (BinaryXML, eg BiM) */ +u32 gf_isom_has_meta_xml(GF_ISOFile *isom_file, Bool root_meta, u32 track_num); + +/*! extracts XML (if any) from given meta +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param outName output file path and location for writing +\param is_binary indicates if XML is Bim or regular XML +\return error if any +*/ +GF_Err gf_isom_extract_meta_xml(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, char *outName, Bool *is_binary); + +/*! checks the number of items in a meta +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\return number of items*/ +u32 gf_isom_get_meta_item_count(GF_ISOFile *isom_file, Bool root_meta, u32 track_num); + +/*! gets item info for the given item +\note When an item is fully contained in file, both item_url and item_urn are set to NULL + +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param item_num 1-based index of item to query +\param itemID set to item ID in file (optional, can be NULL) +\param type set to item 4CC type +\param protection_idx set to the 1-based index of the protection in used, 0 if not protected +\param is_self_reference set to item is the file itself +\param item_name set to the item name (optional, can be NULL) +\param item_mime_type set to the item mime type (optional, can be NULL) +\param item_encoding set to the item content encoding type (optional, can be NULL) +\param item_url set to the URL of external resource containing this item data if any. +\param item_urn set to the URN of external resource containing this item data if any. +\return error if any */ -GF_Err gf_isom_get_meta_item_info(GF_ISOFile *file, Bool root_meta, u32 track_num, u32 item_num, - u32 *itemID, u32 *protection_idx, Bool *is_self_reference, +GF_Err gf_isom_get_meta_item_info(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_num, + u32 *itemID, u32 *type, u32 *protection_idx, Bool *is_self_reference, const char **item_name, const char **item_mime_type, const char **item_encoding, const char **item_url, const char **item_urn); -/*gets item idx from item ID*/ -u32 gf_isom_get_meta_item_by_id(GF_ISOFile *file, Bool root_meta, u32 track_num, u32 item_ID); - -/*extracts item from given meta - @item_num: 1-based index of item to query - @dump_file_name: if NULL, use item name for dumping +/*! gets item index from item ID +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param item_ID ID of the item to search +\return item index if found, 0 otherwise */ -GF_Err gf_isom_extract_meta_item(GF_ISOFile *file, Bool root_meta, u32 track_num, u32 item_num, const char *dump_file_name); - -/*extracts item from given meta in memory - @item_num: 1-based index of item to query +u32 gf_isom_get_meta_item_by_id(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_ID); + +/*! extracts an item from given meta +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param item_num 1-based index of item to query +\param dump_file_name if NULL, uses item name for dumping, otherwise dumps in given file object (binary write mode) +\return error if any */ -GF_Err gf_isom_extract_meta_item_mem(GF_ISOFile *file, Bool root_meta, u32 track_num, u32 item_id, char **out_data, u32 *out_size, const char **mime_type); - -/*retirves primary item ID, 0 if none found (primary can also be stored through meta XML)*/ -u32 gf_isom_get_meta_primary_item_id(GF_ISOFile *file, Bool root_meta, u32 track_num); - -#ifndef GPAC_DISABLE_ISOM_WRITE - -/*sets meta type (four char int, eg "mp21", ... - Creates a meta box if none found - if metaType is 0, REMOVES META +GF_Err gf_isom_extract_meta_item(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_num, const char *dump_file_name); + +/*! extracts item from given meta in memory +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param item_id the ID of the item to dump +\param out_data set to allocated buffer containing the item, shall be freeed by user +\param out_size set to the size of the allocated buffer +\param out_alloc_size set to the allocated size of the buffer (this allows passing an existing buffer without always reallocating it) +\param mime_type set to the mime type of the item +\param use_annex_b for image items based on NALU formats (AVC, HEVC) indicates to extract the data as Annex B format (with start codes) +\return error if any */ -GF_Err gf_isom_set_meta_type(GF_ISOFile *file, Bool root_meta, u32 track_num, u32 metaType); +GF_Err gf_isom_extract_meta_item_mem(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_id, u8 **out_data, u32 *out_size, u32 *out_alloc_size, const char **mime_type, Bool use_annex_b); -/*removes meta XML info if any*/ -GF_Err gf_isom_remove_meta_xml(GF_ISOFile *file, Bool root_meta, u32 track_num); - -/*set meta XML data from file - erase any previously (Binary)XML info*/ -GF_Err gf_isom_set_meta_xml(GF_ISOFile *file, Bool root_meta, u32 track_num, char *XMLFileName, Bool IsBinaryXML); -/*set meta XML data from memory - erase any previously (Binary)XML info*/ -GF_Err gf_isom_set_meta_xml_memory(GF_ISOFile *file, Bool root_meta, u32 track_num, unsigned char *data, u32 data_size, Bool IsBinaryXML); +/*! gets primary item ID +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\return primary item ID, 0 if none found (primary can also be stored through meta XML)*/ +u32 gf_isom_get_meta_primary_item_id(GF_ISOFile *isom_file, Bool root_meta, u32 track_num); +/*! item tile mode*/ typedef enum { + /*! not a tile item*/ TILE_ITEM_NONE = 0, + /*! a tile item without base*/ TILE_ITEM_ALL_NO_BASE, + /*! a tile item with base*/ TILE_ITEM_ALL_BASE, + /*! a tile item grid*/ TILE_ITEM_ALL_GRID, + /*! a tile item single*/ TILE_ITEM_SINGLE } GF_TileItemMode; +/*! Image item properties*/ typedef struct { - u32 width, height; - u32 hSpacing, vSpacing; - u32 hOffset, vOffset; + /*! width in pixels*/ + u32 width; + /*! height in pixless*/ + u32 height; + /*! pixel aspect ratio numerator*/ + u32 hSpacing; + /*! pixel aspect ratio denominator*/ + u32 vSpacing; + /*! horizontal offset in pixels*/ + u32 hOffset; + /*! vertical offset in pixels*/ + u32 vOffset; + /*! angle in radians*/ u32 angle; + /*! hidden flag*/ Bool hidden; + /*! pointer to configuration box*/ void *config; - GF_TileItemMode tile_mode; + /*! tile item mode*/ + GF_TileItemMode tile_mode; + /*! tile number */ u32 single_tile_number; + /*! time for importing*/ + double time; + /*! file containg iCC data for importing*/ + char iccPath[GF_MAX_PATH]; + /*! is alpha*/ + Bool alpha; + /*! number of channels*/ + u8 num_channels; + /*! bits per channels in bits*/ + u8 bits_per_channel[3]; } GF_ImageItemProperties; -GF_Err gf_isom_meta_get_next_item_id(GF_ISOFile *file, Bool root_meta, u32 track_num, u32 *item_id); - -GF_Err gf_isom_add_meta_item(GF_ISOFile *file, Bool root_meta, u32 track_num, Bool self_reference, char *resource_path, const char *item_name, u32 item_id, u32 item_type, const char *mime_type, const char *content_encoding, const char *URL, const char *URN, GF_ImageItemProperties *imgprop); -/*same as above excepts take the item directly in memory*/ -GF_Err gf_isom_add_meta_item_memory(GF_ISOFile *file, Bool root_meta, u32 track_num, const char *item_name, u32 item_id, u32 item_type, const char *mime_type, const char *content_encoding, GF_ImageItemProperties *image_props, char *data, u32 data_len, GF_List *item_extent_refs); -GF_Err gf_isom_iff_create_image_item_from_track(GF_ISOFile *movie, Bool root_meta, u32 meta_track_number, u32 track, const char *item_name, u32 item_id, GF_ImageItemProperties *image_props, GF_List *item_extent_refs); +#ifndef GPAC_DISABLE_ISOM_WRITE -/*removes item from meta*/ -GF_Err gf_isom_remove_meta_item(GF_ISOFile *file, Bool root_meta, u32 track_num, u32 item_num); +/*! sets meta type (four char int, eg "mp21", ...), creating a meta box if not found +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param metaType the type of meta to create. If 0, removes the meta box +\return error if any +*/ +GF_Err gf_isom_set_meta_type(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 metaType); -/*sets the given item as the primary one. You SHALL NOT use this if the meta has a valid XML data*/ -GF_Err gf_isom_set_meta_primary_item(GF_ISOFile *file, Bool root_meta, u32 track_num, u32 item_num); +/*! removes meta XML info if any +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\return error if any +*/ +GF_Err gf_isom_remove_meta_xml(GF_ISOFile *isom_file, Bool root_meta, u32 track_num); + +/*! sets meta XML data from file or memory - erase any previously (Binary)XML info +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param XMLFileName the XML file to import as XML item, or NULL if data is specified +\param data buffer containing XML data, or NULL if file is specified +\param data_size size of buffer in bytes, ignored if file is specified +\param IsBinaryXML indicates if the content of the XML file is binary XML (BIM) or not +\return error if any +*/ +GF_Err gf_isom_set_meta_xml(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, char *XMLFileName, unsigned char *data, u32 data_size, Bool IsBinaryXML); + +/*! gets next available item ID in a meta +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param item_id set to the next available item ID +\return error if any +*/ +GF_Err gf_isom_meta_get_next_item_id(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 *item_id); + +/*! adds an item to a meta box from file +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param self_reference if GF_TRUE, indicates that the item is in fact the entire container file +\param resource_path path to the file to add +\param item_name name of the item +\param item_id ID of the item, can be 0 +\param item_type four character code of item type +\param mime_type mime type of the item, can be NULL +\param content_encoding content encoding of the item, can be NULL +\param URL URL of the item for external data reference (data is not contained in meta parent file) +\param URN URN of the item for external data reference (data is not contained in meta parent file) +\param image_props image properties information for image items +\return error if any +*/ +GF_Err gf_isom_add_meta_item(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, Bool self_reference, char *resource_path, const char *item_name, u32 item_id, u32 item_type, const char *mime_type, const char *content_encoding, const char *URL, const char *URN, GF_ImageItemProperties *image_props); -GF_Err gf_isom_meta_add_item_ref(GF_ISOFile *file, Bool root_meta, u32 trackID, u32 from_id, u32 to_id, u32 type, u64 *ref_index); +/*! item extend description*/ +typedef struct +{ + /*! offset of extent in file*/ + u64 extent_offset; + /*! size of extent*/ + u64 extent_length; + /*! index of extent*/ + u64 extent_index; +#ifndef GPAC_DISABLE_ISOM_WRITE + /*! for storage only, original offset in source file*/ + u64 original_extent_offset; +#endif +} GF_ItemExtentEntry; + + +/*! adds an item to a meta box from memory +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param item_name name of the item +\param item_id ID of the item, can be 0 +\param item_type four character code of item type +\param mime_type mime type of the item, can be NULL +\param content_encoding content encoding of the item, can be NULL +\param image_props image properties information for image items +\param data buffer containing the item data +\param data_len size of item data buffer in bytes +\param item_extent_refs list of item extend description, or NULL +\return error if any +*/ +GF_Err gf_isom_add_meta_item_memory(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, const char *item_name, u32 item_id, u32 item_type, const char *mime_type, const char *content_encoding, GF_ImageItemProperties *image_props, char *data, u32 data_len, GF_List *item_extent_refs); + +/*! creates image item(s) from samples of a media track +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if meta_track_number is 0 +\param meta_track_number if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param media_track track number to import samples from +\param item_name name of the item +\param item_id ID of the item, can be 0 +\param image_props image properties information for image items +\param item_extent_refs list of item extend description, or NULL +\return error if any +*/ +GF_Err gf_isom_iff_create_image_item_from_track(GF_ISOFile *isom_file, Bool root_meta, u32 meta_track_number, u32 media_track, const char *item_name, u32 item_id, GF_ImageItemProperties *image_props, GF_List *item_extent_refs); + +/*! removes item from meta +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param item_num 1-based index of the item to remove +\return error if any +*/ +GF_Err gf_isom_remove_meta_item(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_num); + +/*! sets the given item as the primary one +\warning This SHALL NOT be used if the meta has a valid XML data +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param item_num 1-based index of the item to remove +\return error if any +*/ +GF_Err gf_isom_set_meta_primary_item(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_num); + +/*! adds an item reference to another item +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param from_id ID of item the reference is from +\param to_id ID of item the reference is to +\param type four character code of reference +\param ref_index set to the 1-based index of the reference +\return error if any +*/ +GF_Err gf_isom_meta_add_item_ref(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 from_id, u32 to_id, u32 type, u64 *ref_index); #endif /*GPAC_DISABLE_ISOM_WRITE*/ +/*! +\param isom_file the target ISO file +\param root_meta if GF_TRUE uses meta at the file, otherwise uses meta at the movie level if track number is 0 +\param track_num if GF_TRUE and root_meta is GF_FALSE, uses meta at the track level +\param item_id ID of the item +\param out_image_props set to the image properties information of the item +\return error if any +*/ +GF_Err gf_isom_get_meta_image_props(GF_ISOFile *isom_file, Bool root_meta, u32 track_num, u32 item_id, GF_ImageItemProperties *out_image_props); -/******************************************************************** - Timed Meta-Data extensions -********************************************************************/ - -GF_Err gf_isom_get_xml_metadata_description(GF_ISOFile *file, u32 track, u32 sampleDescription, const char **_namespace, const char **schema_loc, const char **content_encoding); +/*! @} */ -#ifndef GPAC_DISABLE_ISOM_WRITE -/*create a new timed metat data sample description for this track*/ -GF_Err gf_isom_new_xml_metadata_description(GF_ISOFile *movie, u32 trackNumber, const char *_namespace, const char *schema_loc, const char *content_encoding, u32 *outDescriptionIndex); -#endif /*GPAC_DISABLE_ISOM_WRITE*/ +/*! +\addtogroup isotags_grp iTunes tagging +\ingroup iso_grp +@{ +*/ -/******************************************************************** - iTunes info tags -********************************************************************/ -enum +/*! iTunes info tags */ +typedef enum { /*probe is only used to check if iTunes info are present*/ GF_ISOM_ITUNE_PROBE = 0, - /*probe is only used to remove all tags*/ + /*all is only used to remove all tags*/ GF_ISOM_ITUNE_ALL = 1, GF_ISOM_ITUNE_ALBUM = GF_4CC( 0xA9, 'a', 'l', 'b' ), GF_ISOM_ITUNE_ARTIST = GF_4CC( 0xA9, 'A', 'R', 'T' ), @@ -2408,187 +5687,356 @@ enum GF_ISOM_ITUNE_ENCODER = GF_4CC( 0xA9, 'e', 'n', 'c' ), GF_ISOM_ITUNE_ALBUM_ARTIST = GF_4CC( 'a', 'A', 'R', 'T' ), GF_ISOM_ITUNE_GAPLESS = GF_4CC( 'p', 'g', 'a', 'p' ), -}; -/*get the given tag info. -!! 'genre' may be coded by ID, the libisomedia doesn't translate the ID. In such a case, the result data is set to NULL -and the data_len to the genre ID -returns GF_URL_ERROR if no tag is present in the file + GF_ISOM_ITUNE_CONDUCTOR = GF_4CC( 0xA9, 'c', 'o', 'n' ), +} GF_ISOiTunesTag; + +/*! gets the given itunes tag info. +\warning 'genre' may be coded by ID, the libisomedia doesn't translate the ID. In such a case, the result data is set to NULL and the data_len to the genre ID + +\param isom_file the target ISO file +\param tag the tag to query +\param data set to the tag data pointer - do not modify +\param data_len set to the size of the tag data +\return error if any (GF_URL_ERROR if no tag is present in the file) */ -GF_Err gf_isom_apple_get_tag(GF_ISOFile *mov, u32 tag, const char **data, u32 *data_len); +GF_Err gf_isom_apple_get_tag(GF_ISOFile *isom_file, GF_ISOiTunesTag tag, const u8 **data, u32 *data_len); + #ifndef GPAC_DISABLE_ISOM_WRITE -/*set the given tag info. If data and data_len are 0, removes the given tag -For 'genre', data may be NULL in which case the genre ID taken from the data_len parameter +/*! sets the given tag info. + +\warning For 'genre', data may be NULL in which case the genre ID taken from the data_len parameter + +\param isom_file the target ISO file +\param tag the tag to set +\param data tag data buffer +\param data_len size of the tag data buffer. If data and data_len are 0, removes the given tag +\return error if any */ -GF_Err gf_isom_apple_set_tag(GF_ISOFile *mov, u32 tag, const char *data, u32 data_len); +GF_Err gf_isom_apple_set_tag(GF_ISOFile *isom_file, GF_ISOiTunesTag tag, const u8 *data, u32 data_len); -/*sets compatibility tag on AVC tracks (needed by iPod to play files... hurray for standards)*/ -GF_Err gf_isom_set_ipod_compatible(GF_ISOFile *the_file, u32 trackNumber); +/*! sets compatibility tag on AVC tracks (needed by iPod to play files... hurray for standards) +\param isom_file the target ISO file +\param trackNumber the target track +\return error if any +*/ +GF_Err gf_isom_set_ipod_compatible(GF_ISOFile *isom_file, u32 trackNumber); #endif /*GPAC_DISABLE_ISOM_WRITE*/ +/*! @} */ -/*3GPP Alternate Group API - (c) 2007 Telecom ParisTech*/ +/*! +\addtogroup isogrp_grp Track Groups +\ingroup iso_grp -/*gets the number of switching groups declared in this track if any: -trackNumber: track number -alternateGroupID: alternate group id of track if speciifed, 0 otherwise -nb_groups: number of switching groups defined for this track +@{ */ -GF_Err gf_isom_get_track_switch_group_count(GF_ISOFile *movie, u32 trackNumber, u32 *alternateGroupID, u32 *nb_groups); -/*returns the list of criteria (expressed as 4CC IDs, cf 3GPP TS 26.244) -trackNumber: track number -group_index: 1-based index of the group to inspect -switchGroupID: ID of the switch group if any, 0 otherwise (alternate-only group) -criteriaListSize: number of criteria items in returned list +/*! gets the number of switching groups declared in this track if any +\param isom_file the target ISO file +\param trackNumber the target track +\param alternateGroupID alternate group id of track if speciifed, 0 otherwise +\param nb_groups set to number of switching groups defined for this track +\return error if any */ -const u32 *gf_isom_get_track_switch_parameter(GF_ISOFile *movie, u32 trackNumber, u32 group_index, u32 *switchGroupID, u32 *criteriaListSize); +GF_Err gf_isom_get_track_switch_group_count(GF_ISOFile *isom_file, u32 trackNumber, u32 *alternateGroupID, u32 *nb_groups); + +/*! get the list of criteria (expressed as 4CC IDs, cf 3GPP TS 26.244) +\param isom_file the target ISO file +\param trackNumber the track number +\param group_index the 1-based index of the group to inspect +\param switchGroupID set to the ID of the switch group if any, 0 otherwise (alternate-only group) +\param criteriaListSize set to the number of criteria items in returned list +\return list of criteria (four character codes, cf 3GPP TS 26.244) for the switch group +*/ +const u32 *gf_isom_get_track_switch_parameter(GF_ISOFile *isom_file, u32 trackNumber, u32 group_index, u32 *switchGroupID, u32 *criteriaListSize); #ifndef GPAC_DISABLE_ISOM_WRITE -/*sets a new (switch) group for this track -trackNumber: track -trackRefGroup: number of a track belonging to the same alternate group. If 0, a new alternate group will be created for this track -is_switch_group: if set, indicates that a switch group identifier shall be assigned to the created group. Otherwise, the criteria list is associated with the entire alternate group -switchGroupID: SHALL NOT BE NULL - input: specifies the desired switchGroupID to use. If value is 0, next available switchGroupID in file is used. - output: indicates the switchGroupID used. -criteriaList, criteriaListCount: criteria list and size. Criterias are expressed as 4CC IDs, cf 3GPP TS 26.244 +/*! sets a new (switch) group for this track +\param isom_file the target ISO file +\param trackNumber the target track +\param trackRefGroup number of a track belonging to the same alternate group. If 0, a new alternate group will be created for this track +\param is_switch_group if set, indicates that a switch group identifier shall be assigned to the created group. Otherwise, the criteria list is associated with the entire alternate group +\param switchGroupID set to the ID of the switch group. On input, specifies the desired switchGroupID to use; if value is 0, next available switchGroupID in file is used. On output, is set to the switchGroupID used. +\param criteriaList list of four character codes used as criteria - cf 3GPP TS 26.244 +\param criteriaListCount number of criterias in list +\return error if any */ -GF_Err gf_isom_set_track_switch_parameter(GF_ISOFile *movie, u32 trackNumber, u32 trackRefGroup, Bool is_switch_group, u32 *switchGroupID, u32 *criteriaList, u32 criteriaListCount); - -/*resets track switch group information for the track or for the entire alternate group this track belongs to if reset_all_group is set*/ -GF_Err gf_isom_reset_track_switch_parameter(GF_ISOFile *movie, u32 trackNumber, Bool reset_all_group); - -/*resets ALL track switch group information in the entire movie*/ -GF_Err gf_isom_reset_switch_parameters(GF_ISOFile *movie); - -#endif /*GPAC_DISABLE_ISOM_WRITE*/ +GF_Err gf_isom_set_track_switch_parameter(GF_ISOFile *isom_file, u32 trackNumber, u32 trackRefGroup, Bool is_switch_group, u32 *switchGroupID, u32 *criteriaList, u32 criteriaListCount); +/*! resets track switch group information +\param isom_file the target ISO file +\param trackNumber the target track +\param reset_all_group if GF_TRUE, resets the entire alternate group this track belongs to; otherwise, resets switch group for the track only +\return error if any +*/ +GF_Err gf_isom_reset_track_switch_parameter(GF_ISOFile *isom_file, u32 trackNumber, Bool reset_all_group); -typedef struct -{ - u8 profile; - u8 level; - u8 pathComponents; - Bool fullRequestHost; - Bool streamType; - u8 containsRedundant; - const char *textEncoding; - const char *contentEncoding; - const char *content_script_types; - const char *mime_type; - const char *xml_schema_loc; -} GF_DIMSDescription; +/*! resets all track switch group information in the entire movie +\param isom_file the target ISO file +\return error if any +*/ +GF_Err gf_isom_reset_switch_parameters(GF_ISOFile *isom_file); + +/*! sets track in group of a given type and ID +\param isom_file the target ISO file +\param trackNumber the target track +\param track_group_id ID of the track group +\param group_type four character code of the track group +\param do_add if GF_FALSE, track is removed from that group, otherwise it is added +\return error if any +*/ +GF_Err gf_isom_set_track_group(GF_ISOFile *isom_file, u32 trackNumber, u32 track_group_id, u32 group_type, Bool do_add); -GF_Err gf_isom_get_dims_description(GF_ISOFile *movie, u32 trackNumber, u32 descriptionIndex, GF_DIMSDescription *desc); -#ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err gf_isom_new_dims_description(GF_ISOFile *movie, u32 trackNumber, GF_DIMSDescription *desc, char *URLname, char *URNname, u32 *outDescriptionIndex); -GF_Err gf_isom_update_dims_description(GF_ISOFile *movie, u32 trackNumber, GF_DIMSDescription *desc, char *URLname, char *URNname, u32 DescriptionIndex); #endif /*GPAC_DISABLE_ISOM_WRITE*/ +/*! @} */ +/*! +\addtogroup isosubs_grp Subsamples +\ingroup iso_grp -struct __ec3_stream -{ - u8 fscod; - u8 bsid; - u8 bsmod; - u8 acmod; - u8 lfon; - /*only for EC3*/ - u8 nb_dep_sub; - u8 chan_loc; -}; - -/*AC3 config record*/ -typedef struct -{ - u8 is_ec3; - u8 nb_streams; //1 for AC3, max 8 for EC3 - u16 brcode; //if AC3 is bitrate code, otherwise cumulated datarate of EC3 streams - struct __ec3_stream streams[8]; -} GF_AC3Config; +@{ +*/ -GF_AC3Config *gf_isom_ac3_config_get(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex); +/*! gets serialized subsample info for the sample +The buffer is formatted as N times [(u32)flags(u32)sub_size(u32)codec_param(u8)priority(u8) discardable] +If several subsample info are present, they are gathered by flags + +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param osize set to output buffer size +\return the serialized buffer, or NULL oif no associated subsample*/ +u8 *gf_isom_sample_get_subsamples_buffer(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 *osize); + +/*! checks if a sample has subsample information +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param flags the subsample flags to query (may be 0) +\return the number of subsamples in the given sample for the given flags*/ +u32 gf_isom_sample_has_subsamples(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 flags); + +/*! gets subsample information on a sample +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param flags the subsample flags to query (may be 0) +\param subSampleNumber the 1-based index of the subsample (see \ref gf_isom_sample_has_subsamples) +\param size set to the subsample size +\param priority set to the subsample priority +\param reserved set to the subsample reserved value (may be used by derived specifications) +\param discardable set to GF_TRUE if subsample is discardable +\return error if any*/ +GF_Err gf_isom_sample_get_subsample(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 flags, u32 subSampleNumber, u32 *size, u8 *priority, u32 *reserved, Bool *discardable); #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err gf_isom_ac3_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_AC3Config *cfg, char *URLname, char *URNname, u32 *outDescriptionIndex); -#endif /*GPAC_DISABLE_ISOM_WRITE*/ - +/*! adds subsample information to a given sample. Subsample information shall be added in increasing order of sampleNumbers, insertion of information is not supported + +\note it is possible to add subsample information for samples not yet added to the file +\note specifying 0 as subSampleSize will remove the last subsample information if any +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param flags the subsample flags to query (may be 0) +\param subSampleSize size of the subsample. If 0, this will remove the last subsample information if any +\param priority the subsample priority +\param reserved the subsample reserved value (may be used by derived specifications) +\param discardable indicates if the subsample is discardable +\return error if any*/ +GF_Err gf_isom_add_subsample(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 flags, u32 subSampleSize, u8 priority, u32 reserved, Bool discardable); -/*returns the number of subsamples in the given sample for the given flags*/ -u32 gf_isom_sample_has_subsamples(GF_ISOFile *movie, u32 track, u32 sampleNumber, u32 flags); -GF_Err gf_isom_sample_get_subsample(GF_ISOFile *movie, u32 track, u32 sampleNumber, u32 flags, u32 subSampleNumber, u32 *size, u8 *priority, u32 *reserved, Bool *discardable); -#ifndef GPAC_DISABLE_ISOM_WRITE -/*adds subsample information to a given sample. Subsample information shall be added in increasing order of sampleNumbers, insertion of information is not supported. -Note that you may add subsample information for samples not yet added to the file -specifying 0 as subSampleSize will remove the last subsample information if any*/ -GF_Err gf_isom_add_subsample(GF_ISOFile *movie, u32 track, u32 sampleNumber, u32 flags, u32 subSampleSize, u8 priority, u32 reserved, Bool discardable); -#endif -/*add subsample information for the latest sample added to the current track fragment*/ -GF_Err gf_isom_fragment_add_subsample(GF_ISOFile *movie, u32 TrackID, u32 flags, u32 subSampleSize, u8 priority, u32 reserved, Bool discardable); - -/*copy over the subsample and sampleToGroup information of the given sample from the source track/file to the last sample added to the current track fragment of the destination file*/ -GF_Err gf_isom_fragment_copy_subsample(GF_ISOFile *dest, u32 TrackID, GF_ISOFile *orig, u32 track, u32 sampleNumber, Bool sgpd_in_traf); +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS +/*! adds subsample information for the latest sample added to the current track fragment +\param isom_file the target ISO file +\param TrackID the ID of the target track +\param flags the subsample flags to query (may be 0) +\param subSampleSize size of the subsample. If 0, this will remove the last subsample information if any +\param priority the subsample priority +\param reserved the subsample reserved value (may be used by derived specifications) +\param discardable indicates if the subsample is discardable +\return error if any*/ +GF_Err gf_isom_fragment_add_subsample(GF_ISOFile *isom_file, GF_ISOTrackID TrackID, u32 flags, u32 subSampleSize, u8 priority, u32 reserved, Bool discardable); +#endif // GPAC_DISABLE_ISOM_FRAGMENTS + +#endif //GPAC_DISABLE_ISOM_WRITE -/*gets the number of the next moof to be produced*/ -u32 gf_isom_get_next_moof_number(GF_ISOFile *movie); -/*Sets the number of the next moof to be produced*/ -void gf_isom_set_next_moof_number(GF_ISOFile *movie, u32 value); +/*! @} */ +/*! +\addtogroup isosgdp_grp Sample Groups +\ingroup iso_grp -/*returns 'rap ' and 'roll' group info for the given sample*/ -GF_Err gf_isom_get_sample_rap_roll_info(GF_ISOFile *the_file, u32 trackNumber, u32 sample_number, Bool *is_rap, Bool *has_roll, s32 *roll_distance); +@{ +*/ -/*returns opaque data of sample group*/ -Bool gf_isom_get_sample_group_info(GF_ISOFile *the_file, u32 trackNumber, u32 sample_description_index, u32 grouping_type, u32 *default_index, const char **data, u32 *size); +/*! defined sample groups in GPAC*/ +enum { + GF_ISOM_SAMPLE_GROUP_ROLL = GF_4CC( 'r', 'o', 'l', 'l'), + GF_ISOM_SAMPLE_GROUP_PROL = GF_4CC( 'p', 'r', 'o', 'l'), + GF_ISOM_SAMPLE_GROUP_RAP = GF_4CC( 'r', 'a', 'p', ' ' ), + GF_ISOM_SAMPLE_GROUP_SEIG = GF_4CC( 's', 'e', 'i', 'g' ), + GF_ISOM_SAMPLE_GROUP_OINF = GF_4CC( 'o', 'i', 'n', 'f'), + GF_ISOM_SAMPLE_GROUP_LINF = GF_4CC( 'l', 'i', 'n', 'f'), + GF_ISOM_SAMPLE_GROUP_TRIF = GF_4CC( 't', 'r', 'i', 'f' ), + GF_ISOM_SAMPLE_GROUP_NALM = GF_4CC( 'n', 'a', 'l', 'm'), + GF_ISOM_SAMPLE_GROUP_TELE = GF_4CC( 't', 'e', 'l', 'e'), + GF_ISOM_SAMPLE_GROUP_SAP = GF_4CC( 's', 'a', 'p', ' '), + GF_ISOM_SAMPLE_GROUP_ALST = GF_4CC( 'a', 'l', 's', 't'), + GF_ISOM_SAMPLE_GROUP_RASH = GF_4CC( 'r', 'a', 's', 'h'), + GF_ISOM_SAMPLE_GROUP_AVLL = GF_4CC( 'a', 'v', 'l', 'l'), //p15 + GF_ISOM_SAMPLE_GROUP_AVSS = GF_4CC( 'a', 'v', 's', 's'), //p15 + GF_ISOM_SAMPLE_GROUP_DTRT = GF_4CC( 'd', 't', 'r', 't'), //p15 + GF_ISOM_SAMPLE_GROUP_MVIF = GF_4CC( 'm', 'v', 'i', 'f'), //p15 + GF_ISOM_SAMPLE_GROUP_SCIF = GF_4CC( 's', 'c', 'i', 'f'), //p15 + GF_ISOM_SAMPLE_GROUP_SCNM = GF_4CC( 's', 'c', 'n', 'm'), //p15 + GF_ISOM_SAMPLE_GROUP_STSA = GF_4CC( 's', 't', 's', 'a'), //p15 + GF_ISOM_SAMPLE_GROUP_TSAS = GF_4CC( 't', 's', 'a', 's'), //p15 + GF_ISOM_SAMPLE_GROUP_SYNC = GF_4CC( 's', 'y', 'n', 'c'), //p15 + GF_ISOM_SAMPLE_GROUP_TSCL = GF_4CC( 't', 's', 'c', 'l'), //p15 + GF_ISOM_SAMPLE_GROUP_VIPR = GF_4CC( 'v', 'i', 'p', 'r'), //p15 + GF_ISOM_SAMPLE_GROUP_LBLI = GF_4CC( 'l', 'b', 'l', 'i'), //p15 + GF_ISOM_SAMPLE_GROUP_3GAG = GF_4CC( '3', 'g', 'a', 'g'), //3gpp + GF_ISOM_SAMPLE_GROUP_AVCB = GF_4CC( 'a', 'v', 'c', 'b'), //3gpp +}; -/*returns tile info */ -Bool gf_isom_get_tile_info(GF_ISOFile *file, u32 trackNumber, u32 sample_description_index, u32 *default_sample_group_index, u32 *id, u32 *independent, Bool *full_frame, u32 *x, u32 *y, u32 *w, u32 *h); +/*! gets 'rap ' and 'roll' group info for the given sample +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param is_rap set to GF_TRUE if sample is a rap (open gop), GF_FALSE otherwise +\param has_roll set to GF_ISOM_SAMPLE_ROLL if sample has roll information, GF_ISOM_SAMPLE_PREROLL if sample has preroll information, GF_ISOM_SAMPLE_ROLL_NONE otherwise +\param roll_distance if sample has roll information, set to roll distance +\return error if any*/ +GF_Err gf_isom_get_sample_rap_roll_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, Bool *is_rap, GF_ISOSampleRollType *roll_type, s32 *roll_distance); + +/*! returns opaque data of sample group +\param isom_file the target ISO file +\param trackNumber the target track +\param sample_group_description_index index of sample group description entry to query +\param grouping_type four character code of grouping typpe of sample group description to query +\param default_index set to the default index for this sample group description if any, 0 otherwise (no defaults) +\param data set to the internal sample group description data buffer +\param size set to size of the sample group description data buffer +\return GF_TRUE if found, GF_FALSE otherwise*/ +Bool gf_isom_get_sample_group_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sample_group_description_index, u32 grouping_type, u32 *default_index, const u8 **data, u32 *size); + +/*! checks if a track as a CENC seig sample group used for key rolling +\param isom_file the target ISO file +\param trackNumber the target track +\return GF_TRUE if found, GF_FALSE otherwise*/ +Bool gf_isom_has_cenc_sample_group(GF_ISOFile *isom_file, u32 trackNumber); + +/*! gets HEVC tiling info +\param isom_file the target ISO file +\param trackNumber the target track +\param sample_group_description_index index of sample group description entry to query +\param default_sample_group_index set to the default index for this sample group description if any, 0 otherwise (no defaults) +\param id set to the tile group ID +\param independent set to independent flag of the tile group (0: not constrained, 1: constrained in layer, 2: all intra slices) +\param full_frame set to GF_TRUE if the tile corresponds to the entire picture +\param x set to the horizontal position in pixels +\param y set to the vertical position in pixels +\param w set to the width in pixels +\param h set to the height in pixels +\return GF_TRUE if found, GF_FALSE otherwise*/ +Bool gf_isom_get_tile_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sample_group_description_index, u32 *default_sample_group_index, u32 *id, u32 *independent, Bool *full_frame, u32 *x, u32 *y, u32 *w, u32 *h); -/*returns true if date/version info droping is enabled */ -Bool gf_isom_drop_date_version_info_enabled(GF_ISOFile *file); -/*sample groups information*/ #ifndef GPAC_DISABLE_ISOM_WRITE -/*sets rap flag for sample_number - this is used by non-IDR RAPs in AVC (also in USAC) were SYNC flag (stss table) cannot be used -num_leading_sample is the number of samples to after this RAP that have dependences on samples before this RAP and hence should be discarded -- currently sample group info MUST be added in order (no insertion in the tables)*/ -GF_Err gf_isom_set_sample_rap_group(GF_ISOFile *movie, u32 track, u32 sample_number, u32 num_leading_samples); -/*sets roll_distance info for sample_number (number of frames before (<0) or after (>0) this sample to have a complete refresh of the decoded data (used by GDR in AVC) -- currently sample group info MUST be added in order (no insertion in the tables)*/ -GF_Err gf_isom_set_sample_roll_group(GF_ISOFile *movie, u32 track, u32 sample_number, s16 roll_distance); -/*set encryption group for a sample_number; see GF_CENCSampleEncryptionGroupEntry for the parameters*/ -GF_Err gf_isom_set_sample_cenc_group(GF_ISOFile *movie, u32 track, u32 sample_number, u8 isEncrypted, u8 IV_size, bin128 KeyID, - u8 crypt_byte_block, u8 skip_byte_block, u8 constant_IV_size, bin128 constant_IV); +/*! sets rap flag for sample_number - this is used by non-IDR RAPs in AVC (also in USAC) were SYNC flag (stss table) cannot be used +\warning sample group info MUST be added in order (no insertion in the tables) -GF_Err gf_isom_set_composition_offset_mode(GF_ISOFile *file, u32 track, Bool use_negative_offsets); - -//adds the given blob as a sample group description of the given grouping type. If default is set, the sample grouping will be marked as default. -//sampleGroupDescriptionIndex is optional, used to retrieve the index -GF_Err gf_isom_add_sample_group_info(GF_ISOFile *movie, u32 track, u32 grouping_type, void *data, u32 data_size, Bool is_default, u32 *sampleGroupDescriptionIndex); +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param is_rap indicates if the sample is a RAP (open gop) sample +\param num_leading_samples indicates the number of leading samples (samples after this RAP that have dependences on samples before this RAP and hence should be discarded when tuning in) +\return error if any +*/ +GF_Err gf_isom_set_sample_rap_group(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, Bool is_rap, u32 num_leading_samples); -//remove a sample group description of the give grouping type (if found) -GF_Err gf_isom_remove_sample_group(GF_ISOFile *movie, u32 track, u32 grouping_type); +/*! sets roll_distance info for sample_number (number of frames before (<0) or after (>0) this sample to have a complete refresh of the decoded data (used by GDR in AVC) -//tags the sample in the grouping adds the given blob as a sample group description of the given grouping type. If default is set, the sample grouping will be marked as default -GF_Err gf_isom_add_sample_info(GF_ISOFile *movie, u32 track, u32 sample_number, u32 grouping_type, u32 sampleGroupDescriptionIndex, u32 grouping_type_parameter); +\warning sample group info MUST be added in order (no insertion in the tables) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param roll_type indicates the sample roll recovery type +\param roll_distance indicates the roll distance before a correct decoding is produced +\return error if any +*/ +GF_Err gf_isom_set_sample_roll_group(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, GF_ISOSampleRollType roll_type, s16 roll_distance); + +/*! sets encryption group for a sample number +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param isEncrypted isEncrypted flag +\param IV_size IV size, can be 0 if not encrypted +\param KeyID key ID used +\param crypt_byte_block crypt block size for pattern encryption, can be 0 +\param skip_byte_block skip block size for pattern encryption, can be 0 +\param constant_IV_size constant IV size, can be 0 +\param constant_IV constant IV, can be 0 +\return error if any +*/ +GF_Err gf_isom_set_sample_cenc_group(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u8 isEncrypted, u8 IV_size, bin128 KeyID, u8 crypt_byte_block, u8 skip_byte_block, u8 constant_IV_size, bin128 constant_IV); +/*! sets a sample using the default CENC parameters in a CENC saig sample group SEIG, creating a sample group description if needed (when seig is already defined) +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\return error if any +*/ +GF_Err gf_isom_set_sample_cenc_default_group(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber); + +/*! adds the given blob as a sample group description entry of the given grouping type. +\param isom_file the target ISO file +\param trackNumber the target track +\param grouping_type the four character code of the grouping type +\param data the payload of the sample group description +\param data_size the size of the payload +\param is_default if GF_TRUE, thie created entry will be marked as the default entry for the sample group description +\param sampleGroupDescriptionIndex is set to the sample group description index (optional, can be NULL) +\return error if any +*/ +GF_Err gf_isom_add_sample_group_info(GF_ISOFile *isom_file, u32 trackNumber, u32 grouping_type, void *data, u32 data_size, Bool is_default, u32 *sampleGroupDescriptionIndex); -//sets track in group of type group_type and id track_group_id. If do_add is GF_FALSE, track is removed from that group -GF_Err gf_isom_set_track_group(GF_ISOFile *file, u32 track_number, u32 track_group_id, u32 group_type, Bool do_add); +/*! removes a sample group description of the give grouping type, if found +\param isom_file the target ISO file +\param trackNumber the target track +\param grouping_type the four character code of the grouping type +\return error if any +*/ +GF_Err gf_isom_remove_sample_group(GF_ISOFile *isom_file, u32 trackNumber, u32 grouping_type); + +/*! adds a sample to the given sample group +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param grouping_type the four character code of the grouping type +\param sampleGroupDescriptionIndex the 1-based index of the sample group description entry +\param grouping_type_parameter the grouping type paramter (see ISO/IEC 14496-12) +\return error if any +*/ +GF_Err gf_isom_add_sample_info(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, u32 grouping_type, u32 sampleGroupDescriptionIndex, u32 grouping_type_parameter); +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS +/*! sets sample group descriptions storage in trafs and not in initial movie (Smooth compatibility) +\param isom_file the target ISO file +\return error if any*/ +GF_Err gf_isom_set_sample_group_in_traf(GF_ISOFile *isom_file); #endif -GF_Err gf_isom_get_sample_cenc_info(GF_ISOFile *movie, u32 track, u32 sample_number, u32 *IsEncrypted, u8 *IV_size, bin128 *KID, - u8 *crypt_byte_block, u8 *skip_byte_block, u8 *constant_IV_size, bin128 *constant_IV); - +#endif // GPAC_DISABLE_ISOM_WRITE -#endif /*GPAC_DISABLE_ISOM*/ /*! @} */ +#endif /*GPAC_DISABLE_ISOM*/ #ifdef __cplusplus } diff --git a/include/gpac/laser.h b/include/gpac/laser.h index b009cd1..521b645 100644 --- a/include/gpac/laser.h +++ b/include/gpac/laser.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2006-2012 + * Copyright (c) Telecom ParisTech 2006-2019 * All rights reserved * * This file is part of GPAC / BIFS codec sub-project @@ -32,17 +32,18 @@ extern "C" { /*! - * \file <gpac/laser.h> - * \brief MPEG-4 LASeR encoding and decoding. - */ +\file <gpac/laser.h> +\brief MPEG-4 LASeR encoding and decoding. +*/ /*! - * \addtogroup laser_grp MPEG-4 LASER - * \ingroup mpeg4sys_grp - * \brief MPEG-4 LASeR encoding and decoding - * - *This section documents the LASeR encoding and decoding functions of the GPAC framework. For scene graph documentation, check scenegraph.h - * @{ +\addtogroup laser_grp MPEG-4 LASER +\ingroup mpeg4sys_grp +\brief MPEG-4 LASeR encoding and decoding + +This section documents the LASeR encoding and decoding functions of the GPAC framework. For scene graph documentation, check scenegraph.h + +@{ */ #include <gpac/nodes_svg.h> @@ -52,45 +53,108 @@ extern "C" { /*for LASeRConfig*/ #include <gpac/mpeg4_odf.h> +/*! a LASeR codec*/ typedef struct __tag_laser_codec GF_LASeRCodec; -/*LASeR decoder constructor*/ +/*! creates a new LASeR decoder +\param scenegraph the scenegraph on which the decoder operates +\return a newly allocated LASeR decoder*/ GF_LASeRCodec *gf_laser_decoder_new(GF_SceneGraph *scenegraph); +/*! destroys a LASeR decoder +\param codec the target LASeR decoder +*/ void gf_laser_decoder_del(GF_LASeRCodec *codec); -/*sets the scene time. Scene time is the real clock of the bifs stream in secs*/ +/*! sets the scene time. Scene time is the real clock of the bifs stream in secs +\param codec the target LASeR decoder +\param GetSceneTime the scene clock query callback function +\param st_cbk opaque data for the callback function +*/ void gf_laser_decoder_set_clock(GF_LASeRCodec *codec, Double (*GetSceneTime)(void *st_cbk), void *st_cbk ); -/*setup a stream*/ -GF_Err gf_laser_decoder_configure_stream(GF_LASeRCodec *codec, u16 ESID, char *DecoderSpecificInfo, u32 DecoderSpecificInfoLength); -/*removes a stream*/ +/*! sets up a stream +\param codec the target LASeR decoder +\param ESID the ESID of the stream +\param DecoderSpecificInfo the decoder configuration data of the LASeR stream +\param DecoderSpecificInfoLength the size in bytes of the decoder configuration data +\return error if any +*/ +GF_Err gf_laser_decoder_configure_stream(GF_LASeRCodec *codec, u16 ESID, u8 *DecoderSpecificInfo, u32 DecoderSpecificInfoLength); +/*! removes a stream +\param codec the target LASeR decoder +\param ESID the ESID of the stream +\return error if any +*/ GF_Err gf_laser_decoder_remove_stream(GF_LASeRCodec *codec, u16 ESID); -/*decode a laser AU and applies it to the graph (non-memory mode only)*/ -GF_Err gf_laser_decode_au(GF_LASeRCodec *codec, u16 ESID, const char *data, u32 data_length); - -/*Memory laser decoding - fills the command list with the content of the AU - cf scenegraph_vrml.h for commands usage - @ESID: ID of input stream - @data, @data_length: BIFS AU - @com_list: target list for decoded commands +/*! decodes a LASeR AU and applies it to the graph (non-memory mode only) +\param codec the target LASeR decoder +\param ESID the ESID of the stream +\param data the access unit payload +\param data_length the access unit size in bytes +\return error if any */ -GF_Err gf_laser_decode_command_list(GF_LASeRCodec *codec, u16 ESID, char *data, u32 data_length, GF_List *com_list); - +GF_Err gf_laser_decode_au(GF_LASeRCodec *codec, u16 ESID, const u8 *data, u32 data_length); + +/*! decodes a LASeR AU in memory - fills the command list with the content of the AU - cf scenegraph_vrml.h for commands usage +\param codec the target LASeR decoder +\param ESID the ESID of the stream +\param data the access unit payload +\param data_length the access unit size in bytes +\param com_list a list to be filled with decoded commands +\return error if any +*/ +GF_Err gf_laser_decode_command_list(GF_LASeRCodec *codec, u16 ESID, u8 *data, u32 data_length, GF_List *com_list); -/*constructor - @graph: scene graph being encoded*/ -GF_LASeRCodec *gf_laser_encoder_new(GF_SceneGraph *graph); -/*destructor*/ +/*! checks if a LASeR decoder has associated conditionnals + \param codec the target LASeR decoder +\return GF_TRUE if conditionnals are attached to this decoder +*/ +Bool gf_laser_decode_has_conditionnals(GF_LASeRCodec *codec); + +/*! creates a new LASeR encoder +\param scenegraph the scenegraph on which the encoder operates +\return a newly allocated LASeR encoder*/ +GF_LASeRCodec *gf_laser_encoder_new(GF_SceneGraph *scenegraph); +/*! destroys a LASeR encoder +\param codec the target LASeR encoder +*/ void gf_laser_encoder_del(GF_LASeRCodec *codec); -/*setup a destination stream*/ +/*! sets up a destination stream +\param codec the target LASeR encoder +\param ESID the ID of the stream +\param cfg the LASeR configuration descriptor +\return error if any +*/ GF_Err gf_laser_encoder_new_stream(GF_LASeRCodec *codec, u16 ESID, GF_LASERConfig *cfg); -/*encodes a list of commands for the given stream in the output buffer - data is dynamically allocated for output*/ -GF_Err gf_laser_encode_au(GF_LASeRCodec *codec, u16 ESID, GF_List *command_list, Bool reset_encoding_context, char **out_data, u32 *out_data_length); -/*returns encoded config desc*/ -GF_Err gf_laser_encoder_get_config(GF_LASeRCodec *codec, u16 ESID, char **out_data, u32 *out_data_length); -/*Encodes current graph as a scene replace*/ -GF_Err gf_laser_encoder_get_rap(GF_LASeRCodec *codec, char **out_data, u32 *out_data_length); +/*! encodes a list of commands for the given stream in the output buffer - data is dynamically allocated for output +\param codec the target LASeR encoder +\param ESID the ID of the stream +\param command_list a list of commands to encode +\param reset_encoding_context if GF_TRUE, resets all color and font tables of LASeR streams (used for clean RAPs) +\param out_data set to an allocated buffer containing the encoded AU - shall be destroyed by caller +\param out_data_length set to the size of the encoded AU +\return error if any +*/ +GF_Err gf_laser_encode_au(GF_LASeRCodec *codec, u16 ESID, GF_List *command_list, Bool reset_encoding_context, u8 **out_data, u32 *out_data_length); +/*! gets a stream encoded config description +\param codec the target LASeR encoder +\param ESID the ID of the stream +\param out_data set to an allocated buffer containing the encoded configuration - shall be destroyed by caller +\param out_data_length set to the size of the encoded configuration +\return error if any +*/ +GF_Err gf_laser_encoder_get_config(GF_LASeRCodec *codec, u16 ESID, u8 **out_data, u32 *out_data_length); + +/*! encodes current graph as a scene replace +\param codec the target LASeR encoder +\param out_data set to an allocated buffer containing the encoded scene replace AU - shall be destroyed by caller +\param out_data_length set to the size of the encoded AU +\return error if any +*/ +GF_Err gf_laser_encoder_get_rap(GF_LASeRCodec *codec, u8 **out_data, u32 *out_data_length); #endif /*GPAC_DISABLE_LASER*/ diff --git a/include/gpac/list.h b/include/gpac/list.h index bfdaeba..db22d9a 100644 --- a/include/gpac/list.h +++ b/include/gpac/list.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -31,206 +31,196 @@ extern "C" { #endif /*! - * \file <gpac/list.h> - * \brief Generic list of objects. +\file <gpac/list.h> +\brief Generic list of objects. */ /*! - * \addtogroup list_grp List - * \ingroup utils_grp - * \brief List object - * - * This section documents the list object of the GPAC framework. - * @{ +\addtogroup list_grp +\brief List object + +This section documents the list object of the GPAC framework. + +@{ */ #include <gpac/tools.h> +/*! list object*/ typedef struct _tag_array GF_List; /*! - * \brief list constructor - * - * Constructs a new list object - * \return new list object +\brief list constructor + +Constructs a new list object +\return new list object */ GF_List *gf_list_new(); /*! - * \brief list destructor - * - * Destructs a list object - * \param ptr list object to destruct - * \note It is the caller responsability to destroy the content of the list if needed +\brief list destructor + +Destructs a list object +\param ptr list object to destruct +\note It is the caller responsability to destroy the content of the list if needed */ void gf_list_del(GF_List *ptr); /*! - * \brief get count - * - * Returns number of items in the list - * \param ptr target list object - * \return number of items in the list +\brief get count + +Returns number of items in the list +\param ptr target list object +\return number of items in the list */ u32 gf_list_count(const GF_List *ptr); /*! - * \brief add item - * - * Adds an item at the end of the list - * \param ptr target list object - * \param item item to add +\brief add item + +Adds an item at the end of the list +\param ptr target list object +\param item item to add +\return error if any */ GF_Err gf_list_add(GF_List *ptr, void* item); /*! - * \brief inserts item - * - * Insert an item in the list - * \param ptr target list object - * \param item item to add - * \param position insertion position. It is expressed between 0 and gf_list_count-1, and any bigger value is equivalent to gf_list_add +\brief inserts item + +Insert an item in the list +\param ptr target list object +\param item item to add +\param position insertion position. It is expressed between 0 and gf_list_count-1, and any bigger value is equivalent to gf_list_add +\return error if any */ GF_Err gf_list_insert(GF_List *ptr, void *item, u32 position); /*! - * \brief removes item - * - * Removes an item from the list given its position - * \param ptr target list object - * \param position position of the item to remove. It is expressed between 0 and gf_list_count-1. - * \note It is the caller responsability to destroy the content of the list if needed +\brief removes item + +Removes an item from the list given its position +\param ptr target list object +\param position position of the item to remove. It is expressed between 0 and gf_list_count-1. +\return error if any +\note It is the caller responsability to destroy the content of the list if needed */ GF_Err gf_list_rem(GF_List *ptr, u32 position); /*! - * \brief gets item - * - * Gets an item from the list given its position - * \param ptr target list object - * \param position position of the item to get. It is expressed between 0 and gf_list_count-1. +\brief gets item + +Gets an item from the list given its position +\param ptr target list object +\param position position of the item to get. It is expressed between 0 and gf_list_count-1. +\return the item or NULL if not found */ void *gf_list_get(GF_List *ptr, u32 position); /*! - * \brief finds item - * - * Finds an item in the list - * \param ptr target list object. - * \param item the item to find. - * \return 0-based item position in the list, or -1 if the item could not be found. +\brief finds item + +Finds an item in the list +\param ptr target list object. +\param item the item to find. +\return 0-based item position in the list, or -1 if the item could not be found. */ s32 gf_list_find(GF_List *ptr, void *item); /*! - * \brief deletes item - * - * Deletes an item from the list - * \param ptr target list object. - * \param item the item to find. - * \return 0-based item position in the list before removal, or -1 if the item could not be found. +\brief deletes item + +Deletes an item from the list +\param ptr target list object. +\param item the item to find. +\return 0-based item position in the list before removal, or -1 if the item could not be found. */ s32 gf_list_del_item(GF_List *ptr, void *item); /*! - * \brief resets list - * - * Resets the content of the list - * \param ptr target list object. - * \note It is the caller responsability to destroy the content of the list if needed +\brief resets list + +Resets the content of the list +\param ptr target list object. +\note It is the caller responsability to destroy the content of the list if needed */ void gf_list_reset(GF_List *ptr); /*! - * \brief gets last item - * - * Gets last item o fthe list - * \param ptr target list object +\brief gets last item + +Gets last item o fthe list +\param ptr target list object +\return the last item */ void *gf_list_last(GF_List *ptr); /*! - * \brief reverses the order of the elements in the list container. - * - * reverses the order of the elements in the list container. - * \param ptr target list object - */ -void gf_list_reverse(GF_List *ptr); +\brief removes last item -/*! - * \brief removes last item - * - * Removes the last item of the list - * \param ptr target list object - * \note It is the caller responsability to destroy the content of the list if needed +Removes the last item of the list +\param ptr target list object +\return error if any +\note It is the caller responsability to destroy the content of the list if needed */ GF_Err gf_list_rem_last(GF_List *ptr); /*! - * \brief list enumerator - * - * Retrieves given list item and increment current position - * \param ptr target list object - * \param pos target item position. The position is automatically incremented regardless of the return value - * \note A typical enumeration will start with a value of 0 until NULL is returned. - */ +\brief list enumerator + +Retrieves given list item and increment current position +\param ptr target list object +\param pos target item position. The position is automatically incremented regardless of the return value +\note A typical enumeration will start with a value of 0 until NULL is returned. +\return the current item for the given index +*/ void *gf_list_enum(GF_List *ptr, u32 *pos); /*! - * \brief list enumerator in reversed order - * - * Retrieves the given list item in reversed order and update current position - * \param ptr target list object - * \param pos target item position. The position is automatically decrelented regardless of the return value - * \note A typical enumeration in reversed order will start with a value of 0 until NULL is returned. - */ -void *gf_list_rev_enum(GF_List *ptr, u32 *pos); +\brief list swap -/*! - * \brief list swap - * - * Swaps content of two lists - * \param l1 first list object - * \param l2 second list object +Swaps content of two lists +\param l1 first list object +\param l2 second list object +\return error if any */ GF_Err gf_list_swap(GF_List *l1, GF_List *l2); /*! - * \brief list transfer - * - * Transfer content between lists - * \param l1 destination list object - * \param l2 source list object +\brief list transfer + +Transfer content between lists +\param dst destination list object +\param src source list object +\return error if any */ -GF_Err gf_list_transfer(GF_List *l1, GF_List *l2); +GF_Err gf_list_transfer(GF_List *dst, GF_List *src); /*! - * \brief clone list - * - * Returns a new list as an exact copy of the given list - * \param ptr the list to clone - * \return the cloned list +\brief clone list + +Returns a new list as an exact copy of the given list +\param ptr the list to clone +\return the cloned list */ GF_List* gf_list_clone(GF_List *ptr); /*! - * \brief Pop the first element in the list - * - * Removes the first element in the list container, effectively reducing its size by one - * and returns the popped element. - * \param ptr the list to pop - * \return the popped element +\brief Pop the first element in the list + +Removes the first element in the list container, effectively reducing its size by one and returns the popped element. +\param ptr the list to pop +\return the popped element */ void* gf_list_pop_front(GF_List *ptr); /*! - * \brief Pop the last element in the list - * - * Removes the last element in the list container, effectively reducing the container size by one - * and return the popped element. - * \param ptr the list to pop - * \return the popped element +\brief Pop the last element in the list + +Removes the last element in the list container, effectively reducing the container size by one and return the popped element. +\param ptr the list to pop +\return the popped element */ void* gf_list_pop_back(GF_List *ptr); diff --git a/include/gpac/main.h b/include/gpac/main.h new file mode 100644 index 0000000..bca5f6e --- /dev/null +++ b/include/gpac/main.h @@ -0,0 +1,253 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _GF_MAIN_H_ +#define _GF_MAIN_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +\file <gpac/main.h> +\brief main() macro for win32. + */ + +/*! +\addtogroup sysmain_grp +@{ + +Thiis section decribes functions useful when developping an application using libgpac such as: +- quick UTF8 conversion of arguments for main() on windows +- setting, checking and printing libgpac arguments as given from command line +*/ + +#include <gpac/setup.h> +#include <gpac/utf.h> + + + + +#if defined(WIN32) && !defined(NO_WMAIN) +/*! macro for main() with wide to char conversion on windows platforms*/ +#define GF_MAIN_FUNC(__fun) \ +int wmain( int argc, wchar_t** wargv )\ +{\ + int i;\ + int res;\ + size_t len;\ + size_t res_len;\ + char **argv;\ + argv = (char **)malloc(argc*sizeof(wchar_t *));\ + for (i = 0; i < argc; i++) {\ + wchar_t *src_str = wargv[i];\ + len = UTF8_MAX_BYTES_PER_CHAR*gf_utf8_wcslen(wargv[i]);\ + argv[i] = (char *)malloc(len + 1);\ + res_len = gf_utf8_wcstombs(argv[i], len, &src_str);\ + argv[i][res_len] = 0;\ + if (res_len > len) {\ + fprintf(stderr, "Length allocated for conversion of wide char to UTF-8 not sufficient\n");\ + return -1;\ + }\ + }\ + res = __fun(argc, argv);\ + for (i = 0; i < argc; i++) {\ + free(argv[i]);\ + }\ + free(argv);\ + return res;\ +} + +#else + +/*! macro for main() with wide to char conversion on windows platforms*/ +#define GF_MAIN_FUNC(__fun) \ +int main(int argc, char **argv) {\ + return __fun( argc, argv ); \ +} + +#endif //win32 + +/*! structure holding a gpac arg (not a filter arg)*/ +typedef struct +{ + /*! name of arg*/ + const char *name; + /*! alternate name of arg*/ + const char *altname; + /*! description of arg*/ + const char *description; + /*! default value of arg*/ + const char *val; + /*! possible value of arg*/ + const char *values; + /*! argument type for UI construction - note that argument values are not parsed and shall be set as strings*/ + u16 type; + /*! argument flags*/ + u16 flags; +} GF_GPACArg; + +//these 3 values match argument hints of filters +/*! argument is of advanced type*/ +#define GF_ARG_HINT_ADVANCED (1<<1) +/*! argument is of expert type*/ +#define GF_ARG_HINT_EXPERT (1<<2) +/*! argument should not be presented in UIs*/ +#define GF_ARG_HINT_HIDE (1<<3) +/*! argument is highly experimental*/ +#define GF_ARG_HINT_EXPERIMENTAL (1<<4) + +/*! argument applies to the libgpac core subsystem*/ +#define GF_ARG_SUBSYS_CORE (1<<5) +/*! argument applies to the log subsystem*/ +#define GF_ARG_SUBSYS_LOG (1<<6) +/*! argument applies to the filter subsystem*/ +#define GF_ARG_SUBSYS_FILTERS (1<<7) +/*! argument applies to the HTTP subsystem*/ +#define GF_ARG_SUBSYS_HTTP (1<<8) +/*! argument applies to the video subsystem*/ +#define GF_ARG_SUBSYS_VIDEO (1<<9) +/*! argument applies to the audio subsystem*/ +#define GF_ARG_SUBSYS_AUDIO (1<<10) +/*! argument applies to the font and text subsystem*/ +#define GF_ARG_SUBSYS_TEXT (1<<11) +/*! argument applies to the remotery subsystem*/ +#define GF_ARG_SUBSYS_RMT (1<<12) + +/*! argument is a boolean*/ +#define GF_ARG_BOOL 0 +/*! argument is a 32 bit integer*/ +#define GF_ARG_INT 1 +/*! argument is a double*/ +#define GF_ARG_DOUBLE 2 +/*! argument is a string*/ +#define GF_ARG_STRING 3 +/*! argument is a camma-separated list of strings*/ +#define GF_ARG_STRINGS 4 + +/*! macros for defining a GF_GPACArg argument*/ +#define GF_DEF_ARG(_a, _b, _c, _d, _e, _f, _g) {_a, _b, _c, _d, _e, _f, _g} + +/*! gets the options defined for libgpac +\return array of options*/ +const GF_GPACArg *gf_sys_get_options(); + +/*! check if the given option is a libgpac argument +\param arg_name name of the argument +\return 0 if not a libgpac core option, 1 if option not consuming an argument, 2 if option consuming an argument*/ +u32 gf_sys_is_gpac_arg(const char *arg_name); + +/*! parses config string and update config accordingly +\param opt_string section/key/val formatted as Section:Key (discard key), Section:Key=null (discard key), Section:Key=Val (set key) or Section:*=null (discard section) +\return GF_TRUE if update is OK, GF_FALSE otherwise*/ +Bool gf_sys_set_cfg_option(const char *opt_string); + +/*! argument dump hint options */ +typedef enum +{ + /*! only dumps simple arguments*/ + GF_ARGMODE_BASE=0, + /*! only dumps advanced arguments*/ + GF_ARGMODE_ADVANCED, + /*! only dumps expert arguments*/ + GF_ARGMODE_EXPERT, + /*! dumps all arguments*/ + GF_ARGMODE_ALL +} GF_SysArgMode; + +/*! flags for help formating*/ +typedef enum +{ + /*! first word in format string should be highlighted */ + GF_PRINTARG_HIGHLIGHT_FIRST = 1, + /*! prints <br/> instead of new line*/ + GF_PRINTARG_NL_TO_BR = 1<<1, + /*! first word in format string is an option descritptor*/ + GF_PRINTARG_OPT_DESC = 1<<2, + /*! the format string is an application string, not a gpac core one*/ + GF_PRINTARG_IS_APP = 1<<3, + /*! insert an extra '-' at the begining*/ + GF_PRINTARG_ADD_DASH = 1<<4, + /*! do not insert '-' before arg name*/ + GF_PRINTARG_NO_DASH = 1<<5, + /*! insert '-: before arg name*/ + GF_PRINTARG_COLON = 1<<6, + /*! the generation is for markdown*/ + GF_PRINTARG_MD = 1<<16, + /*! the generation is for man pages*/ + GF_PRINTARG_MAN = 1<<17, + /*! XML < and > should be escaped (for markdown generation only) */ + GF_PRINTARG_ESCAPE_XML = 1<<18, + /*! '|' should be escaped (for markdown generation only) */ + GF_PRINTARG_ESCAPE_PIPE = 1<<19, +} GF_SysPrintArgFlags; + + +/*! prints a argument +\param helpout destination file - if NULL, uses stderr +\param flags dump flags +\param arg argument to print +\param arg_subsystem name of subsystem of argument (core, gpac, filter name) for localization) +*/ +void gf_sys_print_arg(FILE *helpout, GF_SysPrintArgFlags flags, const GF_GPACArg *arg, const char *arg_subsystem); + +/*! prints libgpac help for builton core options to stderr +\param helpout destination file - if NULL, uses stderr +\param flags dump flags +\param mode filtering mode based on argument type +\param subsystem_flags filtering mode based on argument subsytem flags +*/ +void gf_sys_print_core_help(FILE *helpout, GF_SysPrintArgFlags flags, GF_SysArgMode mode, u32 subsystem_flags); + +/*! gets localized version of string identified by module name and identifier. +\param sec_name name of the module to query, such as "gpac", "core", or filter name +\param str_name name of string to query, such as acore/app option or a filter argument +\param def_val default value to return if no locaization exists +\return localized version of the string +*/ +const char *gf_sys_localized(const char *sec_name, const char *str_name, const char *def_val); + +/*! formats help to output +\param output output file to dump to +\param flags help formatting flags +\param fmt arguments of the format +*/ +void gf_sys_format_help(FILE *output, GF_SysPrintArgFlags flags, const char *fmt, ...); + +/*! very basic word match, check the number of source characters in order in dest +\param orig word to test +\param dst word to compare to +\return GF_TRUE if words are similar, GF_FALSE otherwise +*/ +Bool gf_sys_word_match(const char *orig, const char *dst); + +/*! @} */ + +#ifdef __cplusplus +} +#endif + +#endif //_GF_MAIN_H_ + diff --git a/include/gpac/map.h b/include/gpac/map.h deleted file mode 100644 index 1414555..0000000 --- a/include/gpac/map.h +++ /dev/null @@ -1,213 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jerome Gorin - * Copyright (c) Telecom ParisTech 2000-2013 - * All rights reserved - * - * This file is part of GPAC / dashcast-Debug@dashcast_build - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef _GF_MAP_H_ -#define _GF_MAP_H_ -#ifdef __cplusplus -extern "C" { -#endif - -/*! - * \file <map.h> - * \brief Hash Map. - */ - -/*! - * \addtogroup map_grp Hash map - * \ingroup utils_grp - * \brief Hash Map - * - * This section documents the map object of the GPAC framework - * \note The map use a random function for hashing. Collisions are resolved by using a GF_List on each slot. - * @{ - */ - -#include <gpac/tools.h> - - -/** - * \struct GF_Map - * \brief Map Object. - * - * A GF_Map associate key to values. - */ -typedef struct _tag_map GF_Map; - - -/* Pair structure */ - -/** - * \struct GF_Pair - * \brief Pair structure. - * - * Define the association Key/value. - */ -typedef struct { - char *key; - void *value; -} GF_Pair; - - -/** - * \struct GF_Iterator - * \brief Map iterator Object. - * - * Allows to go through each pair key/value in the map. - */ -typedef struct _it_map { - /* The current map*/ - GF_Map* map; - - /* The current pair */ - GF_Pair* pair; - - /* The current list */ - u32 ilist; - - /* The current index in the hasmap*/ - u32 hash; - -} GF_It_Map; - -/*! - * \brief map constructor - * - * Constructs a new map object - * - * \param hash_capacity the number of slot in the hash table - * \note a zero hash_capacity is not allowed, a hash_capacity of 1 is - * equivalent to a gf_list_find from GF_List with a complexity search in o(n) - * \return new map object - */ -GF_Map *gf_map_new(u32 hash_capacity); - -/*! - * \brief map destructor - * - * Destructs a map object - * \param ptr map object to destruct - * \note The caller is only responsible to destroy the content of the map, not the key - */ -void gf_map_del(GF_Map *ptr); - -/*! - * \brief Set a new map iterator - * - * Associate a map iterator to a map - * - * \param map the map associated to the iterator - * \param it the resulting iterator - * \return GF_OK if iterator has been set properly, otherwise a GF_Err - */ -GF_Err gf_map_iter_set(GF_Map* map, GF_It_Map* it); - -/*! - * \brief return the next value in the map - * - * Return the next value of a GF_Pair in the map - * \param it the map iterator object - * \return the next value of the map if exists, otherwise NULL - */ -void* gf_map_iter_has_next(GF_It_Map* it); - -/*! - * \brief Reset the iterator in the map - * - * Reinitalize the iterator to the beginning of the map - * \param it the map iterator object - * \return GF_OK if the iterator has been correctly reinitialize, otherwise a GF_Err - */ -GF_Err gf_map_iter_reset(GF_It_Map* it); - -/*! - * \brief get count - * - * Returns number of items in the map - * \param ptr target map object - * \return number of items in the map - */ -u32 gf_map_count(const GF_Map *ptr); - -/*! - * \brief add item - * - * Adds an item in the map with an associated key - * - * \param ptr target map object - * \param key the identified key - * \param item item to add - * \return GF_OF if insertion occurs properly, GF_NOT_SUPPORTED if the key already exists, a GF_Err in other cases - * \note the caller is responsible for the deallocation of the key as it is copyied in the map - */ -GF_Err gf_map_insert(GF_Map *ptr, const char* key, void* item); - -/*! - * \brief removes the couple key/item from the map - * - * Removes an item from the map given to its key if exists - * \param ptr target map object - * \param key the key of the item. - * \return GF_TRUE if the key has been delete, otherwise GF_FALSE - * \note It is the caller responsability to destroy the content of the list if needed - */ -Bool gf_map_rem(GF_Map *ptr, const char* key); - -/*! - * \brief finds item - * - * Finds a key in the map - * - * \param ptr target map object. - * \param key the key to find. - * \return a pointer to the corresponding value if found, otherwise NULL. - */ -void* gf_map_find(GF_Map *ptr, const char* key); - -/*! - * \brief Check if map contains key - * - * \param ptr target map object. - * \param key the key to check. - * \return GF_TRUE if map contains keys, otherwise GF_FALSE - */ -Bool gf_map_has_key(GF_Map *ptr, const char* key); - -/*! - * \brief resets map - * - * Resets the content of the map - * \param ptr target map object. - * \note It is the caller responsability to destroy the content of the list if needed - */ -void gf_map_reset(GF_Map *ptr); - - -/*! @} */ - -#ifdef __cplusplus -} -#endif - -#endif /* _GF_MAP_H_ */ diff --git a/include/gpac/maths.h b/include/gpac/maths.h index 392a267..5cc4daa 100644 --- a/include/gpac/maths.h +++ b/include/gpac/maths.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -31,8 +31,8 @@ extern "C" { #endif /*! - * \file <gpac/maths.h> - * \brief Mathematics and Trigonometric. +\file <gpac/maths.h> +\brief Mathematics and Trigonometric. */ #include <gpac/setup.h> @@ -41,20 +41,18 @@ extern "C" { /*! - *\addtogroup math_grp Math - *\ingroup utils_grp - *\brief Mathematics and Trigonometric - * - *This section documents the math and trigo functions used in the GPAC framework. GPAC can be compiled with - *fixed-point support, representing float values on a 16.16 signed integer, which implies a developer - *must take care of float computations when using GPAC.\n - *A developper should not need to know in which mode the framework has been compiled as long as he uses - *the math functions of GPAC which work in both float and fixed-point mode.\n - *Using fixed-point version is decided at compilation time and cannot be changed. The feature is signaled - *through the following macros: - *- GPAC_FIXED_POINT: when defined, GPAC has been compiled in fixed-point mode - *- GPAC_NO_FIXED_POINT: when defined, GPAC has been compiled in regular (float) mode - * @{ +\addtogroup math_grp +\brief Mathematics and Trigonometric + +This section documents the math and trigo functions used in the GPAC framework. GPAC can be compiled with + fixed-point support, representing float values on a 16.16 signed integer, which implies a developer + must take care of float computations when using GPAC.\n + A developper should not need to know in which mode the framework has been compiled as long as he uses + the math functions of GPAC which work in both float and fixed-point mode.\n + Using fixed-point version is decided at compilation time and cannot be changed. The feature is signaled + through the GPAC_FIXED_POINT macro: when defined, GPAC has been compiled in fixed-point mode + +@{ */ @@ -66,8 +64,8 @@ extern "C" { #ifdef GPAC_FIXED_POINT /*! - *Fixed 16.16 number - *\hideinitializer +Fixed 16.16 number +\hideinitializer \note This documentation has been generated for a fixed-point version of the GPAC framework. */ typedef s32 Fixed; @@ -203,33 +201,39 @@ typedef Float Fixed; 2*PI expressed as Fixed */ +/*! compute the difference between two angles, with a result in [-PI, PI] +\param a first angle +\param b first angle +\return angle difference +*/ Fixed gf_angle_diff(Fixed a, Fixed b); /*! - * \brief Field bit-size - * - * Gets the number of bits needed to represent the value. - * \param MaxVal Maximum value to be represented. - * \return number of bits required to represent the value. +\brief Field bit-size + +Gets the number of bits needed to represent the value. +\param MaxVal Maximum value to be represented. +\return number of bits required to represent the value. */ u32 gf_get_bit_size(u32 MaxVal); /*! - * \brief Get power of 2 - * - * Gets the closest power of 2 greater or equal to the value. - * \param val value to be used. - * \return requested power of 2. +\brief Get power of 2 + +Gets the closest power of 2 greater or equal to the value. +\param val value to be used. +\return requested power of 2. */ u32 gf_get_next_pow2(u32 val); /*! - *\addtogroup math2d_grp Math 2d - *\ingroup math_grp - *\brief 2D Mathematics - * - *This section documents mathematic tools for 2D geometry and color matrices operations - * @{ +\addtogroup math2d_grp Math 2d +\ingroup math_grp +\brief 2D Mathematics + +This section documents mathematic tools for 2D geometry and color matrices operations + +@{ */ /*!\brief 2D point @@ -242,32 +246,36 @@ typedef struct __vec2f Fixed y; } GF_Point2D; /*! - *\brief get 2D vector length - * - *Gets the length of a 2D vector - *\return length of the vector +\brief get 2D vector length + +Gets the length of a 2D vector +\param vec the target vector +\return length of the vector */ Fixed gf_v2d_len(GF_Point2D *vec); /*! - *\brief get distance between 2 points - * - *Gets the distance between the 2 points - *\return distance +\brief get distance between 2 points + +Gets the distance between the 2 points +\param a first point +\param b second point +\return distance */ Fixed gf_v2d_distance(GF_Point2D *a, GF_Point2D *b); /*! - *\brief 2D vector from polar coordinates - * - *Constructs a 2D vector from its polar coordinates - *\param length the length of the vector - *\param angle the angle of the vector in radians - *\return the 2D vector +\brief 2D vector from polar coordinates + +Constructs a 2D vector from its polar coordinates +\param length the length of the vector +\param angle the angle of the vector in radians +\return the 2D vector */ GF_Point2D gf_v2d_from_polar(Fixed length, Fixed angle); -/*!\brief rectangle 2D - * - *The 2D rectangle used in the GPAC project. +/*! +\brief rectangle 2D + +The 2D rectangle used in the GPAC project. */ typedef struct { @@ -284,44 +292,44 @@ typedef struct /*! \brief rectangle union - * - *Gets the union of two rectangles. - *\param rc1 first rectangle of the union. Upon return, this rectangle will contain the result of the union - *\param rc2 second rectangle of the union + +Gets the union of two rectangles. +\param rc1 first rectangle of the union. Upon return, this rectangle will contain the result of the union +\param rc2 second rectangle of the union */ void gf_rect_union(GF_Rect *rc1, GF_Rect *rc2); /*! \brief centers a rectangle - * - *Builds a rectangle centered on the origin - *\param w width of the rectangle - *\param h height of the rectangle - *\return centered rectangle object + +Builds a rectangle centered on the origin +\param w width of the rectangle +\param h height of the rectangle +\return centered rectangle object */ GF_Rect gf_rect_center(Fixed w, Fixed h); /*! \brief rectangle overlap test - * - *Tests if two rectangles overlap. - *\param rc1 first rectangle to test - *\param rc2 second rectangle to test - *\return 1 if rectangles overlap, 0 otherwise + +Tests if two rectangles overlap. +\param rc1 first rectangle to test +\param rc2 second rectangle to test +\return 1 if rectangles overlap, 0 otherwise */ Bool gf_rect_overlaps(GF_Rect rc1, GF_Rect rc2); /*! - \brief rectangle identity test - * - *Tests if two rectangles are identical. - *\param rc1 first rectangle to test - *\param rc2 second rectangle to test - *\return 1 if rectangles are identical, 0 otherwise +\brief rectangle identity test + +Tests if two rectangles are identical. +\param rc1 first rectangle to test +\param rc2 second rectangle to test +\return 1 if rectangles are identical, 0 otherwise */ -Bool gf_rect_equal(GF_Rect rc1, GF_Rect rc2); +Bool gf_rect_equal(GF_Rect *rc1, GF_Rect *rc2); /*! - *\brief pixel-aligned rectangle - * - *Pixel-aligned rectangle used in the GPAC framework. This is usually needed for 2D drawing algorithms. +\brief pixel-aligned rectangle + +Pixel-aligned rectangle used in the GPAC framework. This is usually needed for 2D drawing algorithms. */ typedef struct { @@ -336,19 +344,19 @@ typedef struct s32 height; } GF_IRect; /*! - *\brief gets the pixelized version of a rectangle - * - *Returns the smallest pixel-aligned rectangle completely containing a rectangle - *\param r the rectangle to transform - *\return the pixel-aligned transformed rectangle +\brief gets the pixelized version of a rectangle + +Gets the smallest pixel-aligned rectangle completely containing a rectangle +\param r the rectangle to transform +\return the pixel-aligned transformed rectangle */ GF_IRect gf_rect_pixelize(GF_Rect *r); /*! - *\brief 2D matrix - * - *The 2D affine matrix object usied in GPAC. The transformation of P(x,y) in P'(X, Y) is: +\brief 2D matrix + +The 2D affine matrix object usied in GPAC. The transformation of P(x,y) in P'(X, Y) is: \code X = m[0]*x + m[1]*y + m[2]; Y = m[3]*x + m[4]*y + m[5]; @@ -360,135 +368,135 @@ typedef struct } GF_Matrix2D; /*!\brief matrix initialization - *\hideinitializer - * - *Inits the matrix to the identity matrix +\hideinitializer + +Inits the matrix to the identity matrix */ #define gf_mx2d_init(_obj) { memset((_obj).m, 0, sizeof(Fixed)*6); (_obj).m[0] = (_obj).m[4] = FIX_ONE; } /*!\brief matrix copy - *\hideinitializer - * - *Copies the matrix _from to the matrix _obj +\hideinitializer + +Copies the matrix _from to the matrix _obj */ #define gf_mx2d_copy(_obj, from) memcpy((_obj).m, (from).m, sizeof(Fixed)*6) /*!\brief matrix identity testing - *\hideinitializer - * - *This macro evaluates to 1 if the matrix _obj is the identity matrix, 0 otherwise +\hideinitializer + +This macro evaluates to 1 if the matrix _obj is the identity matrix, 0 otherwise */ #define gf_mx2d_is_identity(_obj) ((!(_obj).m[1] && !(_obj).m[2] && !(_obj).m[3] && !(_obj).m[5] && ((_obj).m[0]==FIX_ONE) && ((_obj).m[4]==FIX_ONE)) ? 1 : 0) /*!\brief 2D matrix multiplication - * - *Multiplies two 2D matrices from*_this - *\param _this matrix being transformed. Once the function is called, _this contains the result matrix - *\param from transformation matrix to add + +Multiplies two 2D matrices from*_this +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param from transformation matrix to add */ void gf_mx2d_add_matrix(GF_Matrix2D *_this, GF_Matrix2D *from); /*!\brief 2D matrix pre-multiplication - * - *Multiplies two 2D matrices _this*from - *\param _this matrix being transformed. Once the function is called, _this contains the result matrix - *\param from transformation matrix to add + +Multiplies two 2D matrices _this*from +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param from transformation matrix to add */ void gf_mx2d_pre_multiply(GF_Matrix2D *_this, GF_Matrix2D *from); /*!\brief matrix translating - * - *Translates a 2D matrix - *\param _this matrix being transformed. Once the function is called, _this contains the result matrix - *\param cx horizontal translation - *\param cy vertical translation + +Translates a 2D matrix +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param cx horizontal translation +\param cy vertical translation */ void gf_mx2d_add_translation(GF_Matrix2D *_this, Fixed cx, Fixed cy); /*!\brief matrix rotating - * - *Rotates a 2D matrix - *\param _this matrix being transformed. Once the function is called, _this contains the result matrix - *\param cx horizontal rotation center coordinate - *\param cy vertical rotation center coordinate - *\param angle rotation angle in radians + +Rotates a 2D matrix +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param cx horizontal rotation center coordinate +\param cy vertical rotation center coordinate +\param angle rotation angle in radians */ void gf_mx2d_add_rotation(GF_Matrix2D *_this, Fixed cx, Fixed cy, Fixed angle); /*!\brief matrix scaling - * - *Scales a 2D matrix - *\param _this matrix being transformed. Once the function is called, _this contains the result matrix - *\param scale_x horizontal scaling factor - *\param scale_y vertical scaling factor + +Scales a 2D matrix +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param scale_x horizontal scaling factor +\param scale_y vertical scaling factor */ void gf_mx2d_add_scale(GF_Matrix2D *_this, Fixed scale_x, Fixed scale_y); /*!\brief matrix uncentered scaling - * - *Scales a 2D matrix with a non-centered scale - *\param _this matrix being transformed. Once the function is called, _this contains the result matrix - *\param scale_x horizontal scaling factor - *\param scale_y vertical scaling factor - *\param cx horizontal scaling center coordinate - *\param cy vertical scaling center coordinate - *\param angle scale orienttion angle in radians + +Scales a 2D matrix with a non-centered scale +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param scale_x horizontal scaling factor +\param scale_y vertical scaling factor +\param cx horizontal scaling center coordinate +\param cy vertical scaling center coordinate +\param angle scale orienttion angle in radians */ void gf_mx2d_add_scale_at(GF_Matrix2D *_this, Fixed scale_x, Fixed scale_y, Fixed cx, Fixed cy, Fixed angle); /*!\brief matrix skewing - * - *Skews a 2D matrix - *\param _this matrix being transformed. Once the function is called, _this contains the result matrix - *\param skew_x horizontal skew factor - *\param skew_y vertical skew factor + +Skews a 2D matrix +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param skew_x horizontal skew factor +\param skew_y vertical skew factor */ void gf_mx2d_add_skew(GF_Matrix2D *_this, Fixed skew_x, Fixed skew_y); /*!\brief matrix horizontal skewing - * - *Skews a 2D matrix horizontally by a given angle - *\param _this matrix being transformed. Once the function is called, _this contains the result matrix - *\param angle horizontal skew angle in radians + +Skews a 2D matrix horizontally by a given angle +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param angle horizontal skew angle in radians */ void gf_mx2d_add_skew_x(GF_Matrix2D *_this, Fixed angle); /*!\brief matrix vertical skewing - * - *Skews a 2D matrix vertically by a given angle - *\param _this matrix being transformed. Once the function is called, _this contains the result matrix - *\param angle vertical skew angle in radians + +Skews a 2D matrix vertically by a given angle +\param _this matrix being transformed. Once the function is called, _this contains the result matrix +\param angle vertical skew angle in radians */ void gf_mx2d_add_skew_y(GF_Matrix2D *_this, Fixed angle); /*!\brief matrix inversing - * - *Inverses a 2D matrix - *\param _this matrix being transformed. Once the function is called, _this contains the result matrix + +Inverses a 2D matrix +\param _this matrix being transformed. Once the function is called, _this contains the result matrix */ void gf_mx2d_inverse(GF_Matrix2D *_this); /*!\brief matrix coordinate transformation - * - *Applies a 2D matrix transformation to coordinates - *\param _this transformation matrix - *\param x pointer to horizontal coordinate. Once the function is called, x contains the transformed horizontal coordinate - *\param y pointer to vertical coordinate. Once the function is called, y contains the transformed vertical coordinate + +Applies a 2D matrix transformation to coordinates +\param _this transformation matrix +\param x pointer to horizontal coordinate. Once the function is called, x contains the transformed horizontal coordinate +\param y pointer to vertical coordinate. Once the function is called, y contains the transformed vertical coordinate */ void gf_mx2d_apply_coords(GF_Matrix2D *_this, Fixed *x, Fixed *y); /*!\brief matrix point transformation - * - *Applies a 2D matrix transformation to a 2D point - *\param _this transformation matrix - *\param pt pointer to 2D point. Once the function is called, pt contains the transformed point + +Applies a 2D matrix transformation to a 2D point +\param _this transformation matrix +\param pt pointer to 2D point. Once the function is called, pt contains the transformed point */ void gf_mx2d_apply_point(GF_Matrix2D *_this, GF_Point2D *pt); /*!\brief matrix rectangle transformation - * - *Applies a 2D matrix transformation to a rectangle, giving the enclosing rectangle of the transformed one - *\param _this transformation matrix - *\param rc pointer to rectangle. Once the function is called, rc contains the transformed rectangle + +Applies a 2D matrix transformation to a rectangle, giving the enclosing rectangle of the transformed one +\param _this transformation matrix +\param rc pointer to rectangle. Once the function is called, rc contains the transformed rectangle */ void gf_mx2d_apply_rect(GF_Matrix2D *_this, GF_Rect *rc); /*!\brief matrix decomposition - * - *Decomposes a 2D matrix M as M=Scale x Rotation x Translation if possible - *\param _this transformation matrix - *\param scale resulting scale part - *\param rotate resulting rotation part - *\param translate resulting translation part - *\return 0 if matrix cannot be decomposed, 1 otherwise + +Decomposes a 2D matrix M as M=Scale x Rotation x Translation if possible +\param _this transformation matrix +\param scale resulting scale part +\param rotate resulting rotation part +\param translate resulting translation part +\return 0 if matrix cannot be decomposed, 1 otherwise */ Bool gf_mx2d_decompose(GF_Matrix2D *_this, GF_Point2D *scale, Fixed *rotate, GF_Point2D *translate); @@ -496,17 +504,18 @@ Bool gf_mx2d_decompose(GF_Matrix2D *_this, GF_Point2D *scale, Fixed *rotate, GF_ /*! - *\addtogroup math3d_grp Math 3d - *\ingroup math_grp - *\brief 3D Mathematics - * - *This section documents mathematic tools for 3D geometry operations - * @{ +\addtogroup math3d_grp Math 3d +\ingroup math_grp +\brief 3D Mathematics + +This section documents mathematic tools for 3D geometry operations + +@{ */ /*!\brief 3D point or vector - * - *The 3D point object is used in all the GPAC framework for both point and vector representation. + +The 3D point object is used in all the GPAC framework for both point and vector representation. */ typedef struct __vec3f { @@ -526,55 +535,105 @@ typedef struct __vec3f #define gf_vec_add(res, v1, v2) { (res).x = (v1).x + (v2).x; (res).y = (v1).y + (v2).y; (res).z = (v1).z + (v2).z; } /*! - *\brief get 3D vector length - * - *Gets the length of a 3D vector - *\return length of the vector +\brief get 3D vector length + +Gets the length of a 3D vector +\param v the target vector +\return length of the vector */ Fixed gf_vec_len(GF_Vec v); + /*! - *\brief get 3D vector square length - * - *Gets the square length of a 3D vector - *\return square length of the vector +\brief get 3D vector length + +Gets the length of a 3D vector +\param v the target vector +\return length of the vector + */ +Fixed gf_vec_len_p(GF_Vec *v); + +/*! +\brief get 3D vector square length + +Gets the square length of a 3D vector +\param v the target vector +\return square length of the vector */ Fixed gf_vec_lensq(GF_Vec v); + /*! - *\brief get 3D vector dot product - * - *Gets the dot product of two vectors - *\return dot product of the vectors +\brief get 3D vector square length + +Gets the square length of a 3D vector +\param v the target vector +\return square length of the vector + */ +Fixed gf_vec_lensq_p(GF_Vec *v); +/*! +\brief get 3D vector dot product + +Gets the dot product of two vectors +\param v1 first vector +\param v2 second vector +\return dot product of the vectors */ Fixed gf_vec_dot(GF_Vec v1, GF_Vec v2); /*! - *\brief vector normalization - * - *Norms the vector, eg make its length equal to \ref FIX_ONE - *\param v vector to normalize +\brief get 3D vector dot product + +Gets the dot product of two vectors +\param v1 first vector +\param v2 second vector +\return dot product of the vectors + */ +Fixed gf_vec_dot_p(GF_Vec *v1, GF_Vec *v2); +/*! +\brief vector normalization + +Normalize the vector, eg make its length equal to \ref FIX_ONE +\param v vector to normalize */ void gf_vec_norm(GF_Vec *v); /*! - *\brief vector scaling - * - *Scales a vector by a given amount - *\param v vector to scale - *\param f scale factor - *\return scaled vector +\brief vector scaling + +Scales a vector by a given amount +\param v vector to scale +\param f scale factor +\return scaled vector */ GF_Vec gf_vec_scale(GF_Vec v, Fixed f); /*! - *\brief vector cross product - * - *Gets the cross product of two vectors - *\param v1 first vector - *\param v2 second vector - *\return cross-product vector +\brief vector scaling + +Scales a vector by a given amount +\param v vector to scale +\param f scale factor +\return scaled vector + */ +GF_Vec gf_vec_scale_p(GF_Vec *v, Fixed f); +/*! +\brief vector cross product + +Gets the cross product of two vectors +\param v1 first vector +\param v2 second vector +\return cross-product vector */ GF_Vec gf_vec_cross(GF_Vec v1, GF_Vec v2); +/*! +\brief vector cross product + +Gets the cross product of two vectors +\param v1 first vector +\param v2 second vector +\return cross-product vector + */ +GF_Vec gf_vec_cross_p(GF_Vec *v1, GF_Vec *v2); /*!\brief 4D vector - * - *The 4D vector object is used in all the GPAC framework for 4 dimension vectors, VRML Rotations and quaternions representation. + +The 4D vector object is used in all the GPAC framework for 4 dimension vectors, VRML Rotations and quaternions representation. */ typedef struct __vec4f { @@ -586,8 +645,8 @@ typedef struct __vec4f /*!\brief 3D matrix - * - *The 3D matrix object used in GPAC. The matrix is oriented like OpenGL matrices (column-major ordering), with + +The 3D matrix object used in GPAC. The matrix is oriented like OpenGL matrices (column-major ordering), with the translation part at the end of the coefficients list. \note Unless specified otherwise, the matrix object is always expected to represent an affine transformation. */ @@ -606,58 +665,61 @@ typedef struct __matrix } \ /*!\brief quaternion to rotation - * - *Transforms a quaternion to a Rotation, expressed as a 4 dimension vector with x,y,z for axis and q for rotation angle - *\param quat the quaternion to transform - *\return the rotation value + +Transforms a quaternion to a Rotation, expressed as a 4 dimension vector with x,y,z for axis and q for rotation angle +\param quat the quaternion to transform +\return the rotation value */ GF_Vec4 gf_quat_to_rotation(GF_Vec4 *quat); /*!\brief quaternion from rotation - * - *Transforms a Rotation to a quaternion - *\param rot the rotation to transform - *\return the quaternion value + +Transforms a Rotation to a quaternion +\param rot the rotation to transform +\return the quaternion value */ GF_Vec4 gf_quat_from_rotation(GF_Vec4 rot); -/*!inverses a quaternion*/ +/*! Inverses a quaternion +\param quat the quaternion to inverse +\return the inverted quaternion +*/ GF_Vec4 gf_quat_get_inv(GF_Vec4 *quat); /*!\brief quaternion multiplication - * - *Multiplies two quaternions - *\param q1 the first quaternion - *\param q2 the second quaternion - *\return the resulting quaternion + +Multiplies two quaternions +\param q1 the first quaternion +\param q2 the second quaternion +\return the resulting quaternion */ GF_Vec4 gf_quat_multiply(GF_Vec4 *q1, GF_Vec4 *q2); /*!\brief quaternion vector rotating - * - *Rotates a vector with a quaternion - *\param quat the quaternion modelizing the rotation - *\param vec the vector to rotate - *\return the resulting vector + +Rotates a vector with a quaternion +\param quat the quaternion modelizing the rotation +\param vec the vector to rotate +\return the resulting vector */ GF_Vec gf_quat_rotate(GF_Vec4 *quat, GF_Vec *vec); /*!\brief quaternion from axis and cos - * - *Constructs a quaternion from an axis and a cosinus value (shortcut to \ref gf_quat_from_rotation) - *\param axis the rotation axis - *\param cos_a the rotation cosinus value - *\return the resulting quaternion + +Constructs a quaternion from an axis and a cosinus value (shortcut to \ref gf_quat_from_rotation) +\param axis the rotation axis +\param cos_a the rotation cosinus value +\return the resulting quaternion */ GF_Vec4 gf_quat_from_axis_cos(GF_Vec axis, Fixed cos_a); /*!\brief quaternion interpolation - * - *Interpolates two quaternions using spherical linear interpolation - *\param q1 the first quaternion - *\param q2 the second quaternion - *\param frac the fraction of the interpolation, between 0 and \ref FIX_ONE - *\return the interpolated quaternion + +Interpolates two quaternions using spherical linear interpolation +\param q1 the first quaternion +\param q2 the second quaternion +\param frac the fraction of the interpolation, between 0 and \ref FIX_ONE +\return the interpolated quaternion */ GF_Vec4 gf_quat_slerp(GF_Vec4 q1, GF_Vec4 q2, Fixed frac); /*!\brief 3D Bounding Box - * - *The 3D Bounding Box is a 3D Axis-Aligned Bounding Box used to in various tools of the GPAC framework for bounds + +The 3D Bounding Box is a 3D Axis-Aligned Bounding Box used to in various tools of the GPAC framework for bounds estimation of a 3D object. It features an axis-aligned box and a sphere bounding volume for fast intersection tests. */ typedef struct @@ -674,226 +736,291 @@ typedef struct /*!the bbox center and radius are valid*/ Bool is_set; } GF_BBox; -/*!updates information of the bounding box based on the edge information*/ +/*! updates information of the bounding box based on the edge information +\param b the target bounding box +*/ void gf_bbox_refresh(GF_BBox *b); -/*!builds a bounding box from a 2D rectangle*/ +/*!builds a bounding box from a 2D rectangle +\param box the bounding box to build +\param rc the source rectangle +*/ void gf_bbox_from_rect(GF_BBox *box, GF_Rect *rc); -/*!builds a rectangle from a 3D bounding box.\note The z dimension is lost and no projection is performed*/ +/*!builds a rectangle from a 3D bounding box. +\note The z dimension is lost and no projection is performed +\param rc the destination rectangle +\param box the source bounding box +*/ void gf_rect_from_bbox(GF_Rect *rc, GF_BBox *box); /*!\brief bounding box expansion - * - *Checks if a point is inside a bounding box and updates the bounding box to include it if not the case - *\param box the bounding box object - *\param pt the 3D point to check + +Checks if a point is inside a bounding box and updates the bounding box to include it if not the case +\param box the bounding box object +\param pt the 3D point to check */ void gf_bbox_grow_point(GF_BBox *box, GF_Vec pt); -/*!performs the union of two bounding boxes*/ +/*!performs the union of two bounding boxes +\param b1 the first bounding box +\param b2 the bounding box to add +*/ void gf_bbox_union(GF_BBox *b1, GF_BBox *b2); -/*!checks if two bounding boxes are equal or not*/ +/*!checks if two bounding boxes are equal or not +\param b1 the first bounding box +\param b2 the second bounding box +\return GF_TRUE if equal +*/ Bool gf_bbox_equal(GF_BBox *b1, GF_BBox *b2); -/*!checks if a point is inside a bounding box or not*/ +/*!checks if a point is inside a bounding box or not +\param box the bounding box +\param p the point to check +\return GF_TRUE if point is inside +*/ Bool gf_bbox_point_inside(GF_BBox *box, GF_Vec *p); /*!\brief get box vertices - * - *Returns the 8 bounding box vertices given the minimum and maximum edge. Vertices are ordered to respect + +Returns the 8 bounding box vertices given the minimum and maximum edge. Vertices are ordered to respect "p-vertex indexes", (vertex from a box closest to plane) and so that n-vertex (vertex from a box farthest from plane) is 7-p_vx_idx - *\param bmin minimum edge of the box - *\param bmax maximum edge of the box - *\param vecs list of 8 3D points used to store the vertices. +\param bmin minimum edge of the box +\param bmax maximum edge of the box +\param vecs list of 8 3D points used to store the vertices. */ void gf_bbox_get_vertices(GF_Vec bmin, GF_Vec bmax, GF_Vec *vecs); /*!\brief matrix initialization - *\hideinitializer - * - *Inits the matrix to the identity matrix + \hideinitializer + +Inits the matrix to the identity matrix */ #define gf_mx_init(_obj) { memset((_obj).m, 0, sizeof(Fixed)*16); (_obj).m[0] = (_obj).m[5] = (_obj).m[10] = (_obj).m[15] = FIX_ONE; } +/*! macro to check if a matrix is the identity matrix*/ #define gf_mx_is_identity(_obj) ((!(_obj).m[1] && !(_obj).m[2] && !(_obj).m[3] && !(_obj).m[4] && !(_obj).m[6] && !(_obj).m[7] && !(_obj).m[8] && !(_obj).m[9] && !(_obj).m[11] && !(_obj).m[12] && !(_obj).m[13] && !(_obj).m[14] && ((_obj).m[0]==FIX_ONE) && ((_obj).m[5]==FIX_ONE)&& ((_obj).m[10]==FIX_ONE)&& ((_obj).m[15]==FIX_ONE)) ? 1 : 0) /*!\brief matrix copy - *\hideinitializer - * - *Copies the matrix _from to the matrix _obj + \hideinitializer + + Copies the matrix _from to the matrix _obj */ #define gf_mx_copy(_obj, from) memcpy(&(_obj), &(from), sizeof(GF_Matrix)); /*!\brief matrix constructor from 2D - * - *Initializes a 3D matrix from a 2D matrix.\note all z-related coefficients will be set to default. + +Initializes a 3D matrix from a 2D matrix.\note all z-related coefficients will be set to default. +\param mx the target matrix to initialize +\param mat2D the source 2D matrix */ void gf_mx_from_mx2d(GF_Matrix *mx, GF_Matrix2D *mat2D); -/*!\brief matrix identity testing - * - *Tests if two matrices are equal or not. - \return 1 if matrices are same, 0 otherwise +/*!\brief matrix equality testing + +Tests if two matrices are equal or not. +\param mx1 the first matrix +\param mx2 the first matrix +\return GF_TRUE if matrices are same, GF_FALSE otherwise */ Bool gf_mx_equal(GF_Matrix *mx1, GF_Matrix *mx2); /*!\brief matrix translation - * - *Translates a matrix - *\param mx the matrix being transformed. Once the function is called, contains the result matrix - *\param tx horizontal translation - *\param ty vertical translation - *\param tz depth translation + +Translates a matrix +\param mx the matrix being transformed. Once the function is called, contains the result matrix +\param tx horizontal translation +\param ty vertical translation +\param tz depth translation */ void gf_mx_add_translation(GF_Matrix *mx, Fixed tx, Fixed ty, Fixed tz); /*!\brief matrix scaling - * - *Scales a matrix - *\param mx the matrix being transformed. Once the function is called, contains the result matrix - *\param sx horizontal translation scaling - *\param sy vertical translation scaling - *\param sz depth translation scaling + +Scales a matrix +\param mx the matrix being transformed. Once the function is called, contains the result matrix +\param sx horizontal translation scaling +\param sy vertical translation scaling +\param sz depth translation scaling */ void gf_mx_add_scale(GF_Matrix *mx, Fixed sx, Fixed sy, Fixed sz); /*!\brief matrix rotating - * - *Rotates a matrix - *\param mx the matrix being transformed. Once the function is called, contains the result matrix - *\param angle rotation angle in radians - *\param x horizontal coordinate of rotation axis - *\param y vertical coordinate of rotation axis - *\param z depth coordinate of rotation axis + +Rotates a matrix +\param mx the matrix being transformed. Once the function is called, contains the result matrix +\param angle rotation angle in radians +\param x horizontal coordinate of rotation axis +\param y vertical coordinate of rotation axis +\param z depth coordinate of rotation axis */ void gf_mx_add_rotation(GF_Matrix *mx, Fixed angle, Fixed x, Fixed y, Fixed z); /*!\brief matrices multiplication - * - *Multiplies a matrix with another one mx = mx*mul - *\param mx the matrix being transformed. Once the function is called, contains the result matrix - *\param mul the matrix to add + +Multiplies a matrix with another one mx = mx*mul +\param mx the matrix being transformed. Once the function is called, contains the result matrix +\param mul the matrix to add */ void gf_mx_add_matrix(GF_Matrix *mx, GF_Matrix *mul); /*!\brief 2D matrix multiplication - * - *Adds a 2D affine matrix to a matrix - *\param mx the matrix - *\param mat2D the matrix to premultiply + +Adds a 2D affine matrix to a matrix +\param mx the matrix +\param mat2D the matrix to premultiply */ void gf_mx_add_matrix_2d(GF_Matrix *mx, GF_Matrix2D *mat2D); /*!\brief affine matrix inversion - * - *Inverses an affine matrix.\warning Results are undefined if the matrix is not an affine one - *\param mx the matrix to inverse + +Inverses an affine matrix.\warning Results are undefined if the matrix is not an affine one +\param mx the matrix to inverse */ void gf_mx_inverse(GF_Matrix *mx); /*!\brief transpose 4x4 matrix - * - *Transposes a 4x4 matrix - *\param mx the matrix to transpose + +Transposes a 4x4 matrix +\param mx the matrix to transpose */ void gf_mx_transpose(GF_Matrix *mx); /*!\brief matrix point transformation - * - *Applies a 3D matrix transformation to a 3D point - *\param mx transformation matrix - *\param pt pointer to 3D point. Once the function is called, pt contains the transformed point + +Applies a 3D matrix transformation to a 3D point +\param mx transformation matrix +\param pt pointer to 3D point. Once the function is called, pt contains the transformed point */ void gf_mx_apply_vec(GF_Matrix *mx, GF_Vec *pt); /*!\brief matrix rectangle transformation - * - *Applies a 3D matrix transformation to a rectangle, giving the enclosing rectangle of the transformed one.\note all depth information are discarded. - *\param _this transformation matrix - *\param rc pointer to rectangle. Once the function is called, rc contains the transformed rectangle + +Applies a 3D matrix transformation to a rectangle, giving the enclosing rectangle of the transformed one.\note all depth information are discarded. +\param _this transformation matrix +\param rc pointer to rectangle. Once the function is called, rc contains the transformed rectangle */ void gf_mx_apply_rect(GF_Matrix *_this, GF_Rect *rc); /*!\brief ortho matrix construction - * - *Creates an orthogonal projection matrix - *\param mx matrix to initialize - *\param left min horizontal coordinate of viewport - *\param right max horizontal coordinate of viewport - *\param bottom min vertical coordinate of viewport - *\param top max vertical coordinate of viewport - *\param z_near min depth coordinate of viewport - *\param z_far max depth coordinate of viewport + +Creates an orthogonal projection matrix. This assume the NDC Z lies in [-1,1] +\param mx matrix to initialize +\param left min horizontal coordinate of viewport +\param right max horizontal coordinate of viewport +\param bottom min vertical coordinate of viewport +\param top max vertical coordinate of viewport +\param z_near min depth coordinate of viewport +\param z_far max depth coordinate of viewport */ void gf_mx_ortho(GF_Matrix *mx, Fixed left, Fixed right, Fixed bottom, Fixed top, Fixed z_near, Fixed z_far); + +/*!\brief ortho matrix with reverse Z construction + +Creates an orthogonal projection matrix with reverse Z. This assume the NDC Z lies in [0,1], not [-1,1] +\param mx matrix to initialize +\param left min horizontal coordinate of viewport +\param right max horizontal coordinate of viewport +\param bottom min vertical coordinate of viewport +\param top max vertical coordinate of viewport +\param z_near min depth coordinate of viewport +\param z_far max depth coordinate of viewport +*/ +void gf_mx_ortho_reverse_z(GF_Matrix *mx, Fixed left, Fixed right, Fixed bottom, Fixed top, Fixed z_near, Fixed z_far); + /*!\brief perspective matrix construction - * - *Creates a perspective projection matrix - *\param mx matrix to initialize - *\param fov camera field of view angle in radian - *\param aspect_ratio viewport aspect ratio - *\param z_near min depth coordinate of viewport - *\param z_far max depth coordinate of viewport + +Creates a perspective projection matrix. This assume the NDC Z lies in [-1,1] +\param mx matrix to initialize +\param fov camera field of view angle in radian +\param aspect_ratio viewport aspect ratio +\param z_near min depth coordinate of viewport +\param z_far max depth coordinate of viewport */ void gf_mx_perspective(GF_Matrix *mx, Fixed fov, Fixed aspect_ratio, Fixed z_near, Fixed z_far); + +/*!\brief perspective matrix with reverse Z construction + +Creates a perspective projection matrix with reverse Z. This assume the NDC Z lies in [0,1] +\param mx matrix to initialize +\param fov camera field of view angle in radian +\param aspect_ratio viewport aspect ratio +\param z_near min depth coordinate of viewport +\param z_far max depth coordinate of viewport +*/ +void gf_mx_perspective_reverse_z(GF_Matrix *mx, Fixed fov, Fixed aspect_ratio, Fixed z_near, Fixed z_far); + /*!\brief creates look matrix - * - *Creates a transformation matrix looking at a given direction from a given point (camera matrix). - *\param mx matrix to initialize - *\param position position - *\param target look direction - *\param up_vector vector describing the up direction + +Creates a transformation matrix looking at a given direction from a given point (camera matrix). +\param mx matrix to initialize +\param position position +\param target look direction +\param up_vector vector describing the up direction */ void gf_mx_lookat(GF_Matrix *mx, GF_Vec position, GF_Vec target, GF_Vec up_vector); /*!\brief matrix box transformation - * - *Applies a 3D matrix transformation to a bounding box, giving the enclosing box of the transformed one - *\param mx transformation matrix - *\param b pointer to bounding box. Once the function is called, contains the transformed bounding box + +Applies a 3D matrix transformation to a bounding box, giving the enclosing box of the transformed one +\param mx transformation matrix +\param b pointer to bounding box. Once the function is called, contains the transformed bounding box */ void gf_mx_apply_bbox(GF_Matrix *mx, GF_BBox *b); /*!\brief matrix box sphere transformation - * - *Applies a 3D matrix transformation to a bounding box, computing only the enclosing sphere of the transformed one. - *\param mx transformation matrix - *\param box pointer to bounding box. Once the function is called, contains the transformed bounding sphere + +Applies a 3D matrix transformation to a bounding box, computing only the enclosing sphere of the transformed one. +\param mx transformation matrix +\param box pointer to bounding box. Once the function is called, contains the transformed bounding sphere */ void gf_mx_apply_bbox_sphere(GF_Matrix *mx, GF_BBox *box); /*!\brief non-affine matrix multiplication - * - *Multiplies two non-affine matrices mx = mx*mul + +Multiplies two non-affine matrices mx = mx*mul +\param mat the target matrix +\param mul the matrix we multiply with */ void gf_mx_add_matrix_4x4(GF_Matrix *mat, GF_Matrix *mul); /*!\brief non-affine matrix inversion - * - *Inverses a non-affine matrices - *\return 1 if inversion was done, 0 if inversion not possible. + +Inverses a non-affine matrices +\param mx the target matrix +\return 1 if inversion was done, 0 if inversion not possible. */ Bool gf_mx_inverse_4x4(GF_Matrix *mx); /*!\brief matrix 4D vector transformation - * - *Applies a 3D non-affine matrix transformation to a 4 dimension vector - *\param mx transformation matrix - *\param vec pointer to the vector. Once the function is called, contains the transformed vector + +Applies a 3D non-affine matrix transformation to a 4 dimension vector +\param mx transformation matrix +\param vec pointer to the vector. Once the function is called, contains the transformed vector */ void gf_mx_apply_vec_4x4(GF_Matrix *mx, GF_Vec4 *vec); + +/*!\brief matrix yaw pitch roll decomposition + +Extracts yaw, pitch and roll info from a matrix +\param mx the matrix to decompose +\param yaw the extracted yaw angle in radians +\param pitch the extracted pitch angle in radians +\param roll the extracted roll angle in radians +*/ +void gf_mx_get_yaw_pitch_roll(GF_Matrix *mx, Fixed *yaw, Fixed *pitch, Fixed *roll); + /*!\brief matrix decomposition - * - *Decomposes a matrix into translation, scale, shear and rotate - *\param mx the matrix to decompose - *\param translate the decomposed translation part - *\param scale the decomposed scaling part - *\param rotate the decomposed rotation part, expressed as a Rotataion (axis + angle) - *\param shear the decomposed shear part + +Decomposes a matrix into translation, scale, shear and rotate +\param mx the matrix to decompose +\param translate the decomposed translation part +\param scale the decomposed scaling part +\param rotate the decomposed rotation part, expressed as a Rotataion (axis + angle) +\param shear the decomposed shear part */ void gf_mx_decompose(GF_Matrix *mx, GF_Vec *translate, GF_Vec *scale, GF_Vec4 *rotate, GF_Vec *shear); /*!\brief matrix vector rotation - * - *Rotates a vector with a given matrix, ignoring any translation. - *\param mx transformation matrix - *\param pt pointer to 3D vector. Once the function is called, pt contains the transformed vector + +Rotates a vector with a given matrix, ignoring any translation. +\param mx transformation matrix +\param pt pointer to 3D vector. Once the function is called, pt contains the transformed vector */ void gf_mx_rotate_vector(GF_Matrix *mx, GF_Vec *pt); /*!\brief matrix initialization from vectors - * - *Inits a matrix to rotate the local axis in the given vectors - \param mx matrix to initialize - \param x_axis target normalized X axis - \param y_axis target normalized Y axis - \param z_axis target normalized Z axis + +Inits a matrix to rotate the local axis in the given vectors +\param mx matrix to initialize +\param x_axis target normalized X axis +\param y_axis target normalized Y axis +\param z_axis target normalized Z axis */ void gf_mx_rotation_matrix_from_vectors(GF_Matrix *mx, GF_Vec x_axis, GF_Vec y_axis, GF_Vec z_axis); /*!\brief matrix to 2D matrix - * - *Inits a 2D matrix by removing all depth info from a 3D matrix - *\param mx2d 2D matrix to initialize - *\param mx 3D matrix to use + +Inits a 2D matrix by removing all depth info from a 3D matrix +\param mx2d 2D matrix to initialize +\param mx 3D matrix to use */ void gf_mx2d_from_mx(GF_Matrix2D *mx2d, GF_Matrix *mx); @@ -906,45 +1033,45 @@ typedef struct Fixed d; } GF_Plane; /*!\brief matrix plane transformation - * - *Transorms a plane by a given matrix - *\param mx the matrix to use - *\param plane pointer to 3D plane. Once the function is called, plane contains the transformed plane + +Transorms a plane by a given matrix +\param mx the matrix to use +\param plane pointer to 3D plane. Once the function is called, plane contains the transformed plane */ void gf_mx_apply_plane(GF_Matrix *mx, GF_Plane *plane); /*!\brief point to plane distance - * - *Gets the distance between a point and a plne - *\param plane the plane to use - *\param p pointer to ^point to check - *\return the distance between the place and the point + +Gets the distance between a point and a plne +\param plane the plane to use +\param p pointer to ^point to check +\return the distance between the place and the point */ Fixed gf_plane_get_distance(GF_Plane *plane, GF_Vec *p); /*!\brief closest point on a line - * - *Gets the closest point on a line from a given point in space - *\param line_pt a point of the line to test - *\param line_vec the normalized direction vector of the line - *\param pt the point to check - *\return the closest point on the line to the desired point + +Gets the closest point on a line from a given point in space +\param line_pt a point of the line to test +\param line_vec the normalized direction vector of the line +\param pt the point to check +\return the closest point on the line to the desired point */ GF_Vec gf_closest_point_to_line(GF_Vec line_pt, GF_Vec line_vec, GF_Vec pt); /*!\brief box p-vertex index - * - *Gets the p-vertex index for a given plane. The p-vertex index is the index of the closest vertex of a bounding box to the plane. The vertices of a box are always + +Gets the p-vertex index for a given plane. The p-vertex index is the index of the closest vertex of a bounding box to the plane. The vertices of a box are always *ordered in GPAC? cf \ref gf_bbox_get_vertices - *\param p the plane to check - *\return the p-vertex index value, ranging from 0 to 7 +\param p the plane to check +\return the p-vertex index value, ranging from 0 to 7 */ u32 gf_plane_get_p_vertex_idx(GF_Plane *p); /*!\brief plane line intersection - * - *Checks for the intersection of a plane and a line - *\param plane plane to test - *\param linepoint a point on the line to test - *\param linevec normalized direction vector of the line to test - *\param outPoint optional pointer to retrieve the intersection point, NULL otherwise - *\return 1 if line and plane intersect, 0 otherwise + +Checks for the intersection of a plane and a line +\param plane plane to test +\param linepoint a point on the line to test +\param linevec normalized direction vector of the line to test +\param outPoint optional pointer to retrieve the intersection point, NULL otherwise +\return 1 if line and plane intersect, 0 otherwise */ Bool gf_plane_intersect_line(GF_Plane *plane, GF_Vec *linepoint, GF_Vec *linevec, GF_Vec *outPoint); @@ -959,17 +1086,17 @@ enum GF_BBOX_BACK }; /*!\brief box-plane relation - * - *Gets the spatial relation between a box and a plane - *\param box the box to check - *\param p the plane to check - *\return the relation type + +Gets the spatial relation between a box and a plane +\param box the box to check +\param p the plane to check +\return the relation type */ u32 gf_bbox_plane_relation(GF_BBox *box, GF_Plane *p); /*!\brief 3D Ray - * - *The 3D ray object is used in GPAC for all collision and mouse interaction tests + +The 3D ray object is used in GPAC for all collision and mouse interaction tests */ typedef struct { @@ -980,57 +1107,51 @@ typedef struct } GF_Ray; /*!\brief ray constructor - * - *Constructs a ray object - *\param start starting point of the ray - *\param end end point of the ray, or any point on the ray - *\return the ray object + +Constructs a ray object +\param start starting point of the ray +\param end end point of the ray, or any point on the ray +\return the ray object */ GF_Ray gf_ray(GF_Vec start, GF_Vec end); /*!\brief matrix ray transformation - * - *Transforms a ray by a given transformation matrix - *\param mx the matrix to use - *\param r pointer to the ray. Once the function is called, contains the transformed ray + +Transforms a ray by a given transformation matrix +\param mx the matrix to use +\param r pointer to the ray. Once the function is called, contains the transformed ray */ void gf_mx_apply_ray(GF_Matrix *mx, GF_Ray *r); /*!\brief ray box intersection test - * - *Checks if a ray intersects a box or not - *\param ray the ray to check - *\param min_edge the minimum edge of the box to check - *\param max_edge the maximum edge of the box to check - *\param out_point optional location of a 3D point to store the intersection, NULL otherwise. - *\return retuns 1 if the ray intersects the box, 0 otherwise + +Checks if a ray intersects a box or not +\param ray the ray to check +\param min_edge the minimum edge of the box to check +\param max_edge the maximum edge of the box to check +\param out_point optional location of a 3D point to store the intersection, NULL otherwise. +\return retuns 1 if the ray intersects the box, 0 otherwise */ Bool gf_ray_hit_box(GF_Ray *ray, GF_Vec min_edge, GF_Vec max_edge, GF_Vec *out_point); /*!\brief ray sphere intersection test - * - *Checks if a ray intersects a box or not - *\param ray the ray to check - *\param center the center of the sphere to check. If NULL, the origin (0,0,0)is used - *\param radius the radius of the sphere to check - *\param out_point optional location of a 3D point to store the intersection, NULL otherwise - *\return retuns 1 if the ray intersects the sphere, 0 otherwise + +Checks if a ray intersects a box or not +\param ray the ray to check +\param center the center of the sphere to check. If NULL, the origin (0,0,0)is used +\param radius the radius of the sphere to check +\param out_point optional location of a 3D point to store the intersection, NULL otherwise +\return retuns 1 if the ray intersects the sphere, 0 otherwise */ Bool gf_ray_hit_sphere(GF_Ray *ray, GF_Vec *center, Fixed radius, GF_Vec *out_point); /*!\brief ray triangle intersection test - * - *Checks if a ray intersects a triangle or not - *\param ray the ray to check - *\param v0 first vertex of the triangle - *\param v1 second vertex of the triangle - *\param v2 third vertex of the triangle - *\param dist optional location of a fixed number to store the intersection distance from ray origin if any, NULL otherwise - *\return retuns 1 if the ray intersects the triangle, 0 otherwise + +Checks if a ray intersects a triangle or not +\param ray the ray to check +\param v0 first vertex of the triangle +\param v1 second vertex of the triangle +\param v2 third vertex of the triangle +\param dist optional location of a fixed number to store the intersection distance from ray origin if any, NULL otherwise +\return retuns 1 if the ray intersects the triangle, 0 otherwise */ Bool gf_ray_hit_triangle(GF_Ray *ray, GF_Vec *v0, GF_Vec *v1, GF_Vec *v2, Fixed *dist); -/*same as above and performs backface cull (solid meshes)*/ -/*!\brief ray triangle intersection test - * - *Checks if a ray intersects a triangle or not, performing backface culling. For parameters details, look at \ref gf_ray_hit_triangle_backcull - */ -Bool gf_ray_hit_triangle_backcull(GF_Ray *ray, GF_Vec *v0, GF_Vec *v1, GF_Vec *v2, Fixed *dist); /*! @} */ diff --git a/include/gpac/media_tools.h b/include/gpac/media_tools.h index df9efc0..a037b20 100644 --- a/include/gpac/media_tools.h +++ b/include/gpac/media_tools.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / Authoring Tools sub-project @@ -27,8 +27,8 @@ #define _GF_MEDIA_H_ /*! - * \file <gpac/media_tools.h> - * \brief media tools helper for importing, exporting and analysing +\file <gpac/media_tools.h> +\brief media tools helper for importing, exporting and analysing */ #ifdef __cplusplus @@ -38,82 +38,109 @@ extern "C" { #include <gpac/isomedia.h> #include <gpac/avparse.h> #include <gpac/config_file.h> +#include <gpac/filters.h> /*! - * \addtogroup mt_grp ISOBMF Helper tools - * \ingroup media_grp - * \brief media tools helper for importing, exporting and analysing. - * - *This section documents media tools functions . - * @{ +\addtogroup mt_grp ISOBMF Helper tools +\ingroup media_grp +\brief media tools helper for importing, exporting and analysing. + +This section documents media tools functions . + +@{ */ /*! - *computes file hash. If file is ISO-based, computre hash according to OMA (P)DCF (without MutableDRMInformation box) - * \param file the source file to hash - * \param hash the 20 bytes buffer in which sha128 is performed for this file - * \return error if any. + *computes file hash. + If file is ISOBMFF based, computes hash according to OMA (P)DCF (without MutableDRMInformation box). + Otherwise this is equivalent to \ref gf_sha1_file + +\param file the source file to hash +\param hash the 20 bytes buffer in which sha128 is performed for this file +\return error if any. */ GF_Err gf_media_get_file_hash(const char *file, u8 hash[20]); #ifndef GPAC_DISABLE_ISOM /*! Creates (if needed) a GF_ESD for the given track - THIS IS RESERVED for local playback -only, since the OTI used when emulated is not standard... - * \param mp4 source file - * \param track track for which the esd is to be emulated - * \return rebuilt ESD. It is the caller responsibility to delete it. +only, since the OTI/codecid used when emulated is not standard... +\param isom_file source file +\param trackNumber track for which the esd is to be emulated +\param sampleDescriptionIndex indicates the default sample description to map. 0 is equivalent to 1, first sample description +\return rebuilt ESD. It is the caller responsibility to delete it. */ -GF_ESD *gf_media_map_esd(GF_ISOFile *mp4, u32 track); +GF_ESD *gf_media_map_esd(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleDescriptionIndex); + +/*! +Creates (if needed) a GF_ESD for the given image item - THIS IS RESERVED for local playback +only, since the OTI/codecid used when emulated is not standard... +\param mp4 source file +\param item_id item for which the esd is to be emulated +\return rebuilt ESD. It is the caller responsibility to delete it. +*/ +GF_ESD *gf_media_map_item_esd(GF_ISOFile *mp4, u32 item_id); /*! * Get RFC 6381 description for a given track. - * \param movie source movie - * \param track track to check - * \param szCodec a pointer to an already allocated string of size 20 bytes (to refine). - * \param force_inband_xps force inband signaling of parameter sets. - * \param force_sbr forces using explicit signaling for SBR. - * \return error if any. +\param isom_file source ISOBMF file +\param trackNumber track to check +\param szCodec a pointer to an already allocated string of size RFC6381_CODEC_NAME_SIZE_MAX bytes. +\param force_inband_xps force inband signaling of parameter sets. +\param force_sbr forces using explicit signaling for SBR. +\return error if any. */ -GF_Err gf_media_get_rfc_6381_codec_name(GF_ISOFile *movie, u32 track, char *szCodec, Bool force_inband_xps, Bool force_sbr); +GF_Err gf_media_get_rfc_6381_codec_name(GF_ISOFile *isom_file, u32 trackNumber, char *szCodec, Bool force_inband_xps, Bool force_sbr); #endif #ifndef GPAC_DISABLE_ISOM_WRITE /*! *Changes pixel aspect ratio for visual tracks if supported. Negative values remove any PAR info - * \param file target ISOBMF file - * \param track target track - * \param ar_num aspect ratio numerator - * \param ar_den aspect ratio denominator - * \return error if any +\param isom_file target ISOBMF file +\param trackNumber target track +\param ar_num aspect ratio numerator. A value of 0 removes PAR info, a value of -1 gets info from video bitstream for BOTH num and den +\param ar_den aspect ratio denominator. A value of 0 removes PAR info, a value of -1 gets info from video bitstream for BOTH num and den +\param force_par aspect ratio is always written even when 1:1, otherwise aspect ratio info is removed if 1:1 +\param rewrite_par aspect ratio is modified in bitstream. Ignored if ar_num or ar_den are not strictly positive +\return error if any */ -GF_Err gf_media_change_par(GF_ISOFile *file, u32 track, s32 ar_num, s32 ar_den); +GF_Err gf_media_change_par(GF_ISOFile *isom_file, u32 trackNumber, s32 ar_num, s32 ar_den, Bool force_par, Bool rewrite_par); /*! *Removes all non rap samples (sync and other RAP sample group info) from the track. - * \param file target movie - * \param track target track - * \return error if any +\param isom_file target ISOBMF file +\param trackNumber target track +\param non_ref_only if GF_TRUE, removes only non-reference pictures +\return error if any */ -GF_Err gf_media_remove_non_rap(GF_ISOFile *file, u32 track); +GF_Err gf_media_remove_non_rap(GF_ISOFile *isom_file, u32 trackNumber, Bool non_ref_only); + +/*! + *updates bitrate info on given track. +\param isom_file target ISOBMF file +\param trackNumber target track + */ +void gf_media_update_bitrate(GF_ISOFile *isom_file, u32 trackNumber); + #endif /*! @} */ /*! - * \addtogroup mimp_grp ISOBMF Importers - * \ingroup media_grp - * \brief Media importing. - * - *This section documents media tools helper functions for importing, exporting and analysing. - * @{ - */ +\addtogroup mimp_grp ISOBMF Importers +\ingroup media_grp +\brief Media importing. + +This section documents media tools helper functions for importing, exporting and analysing. + +@{ +*/ /*! -* Default import FPS for video when no VUI/timing information is found -* \hideinitializer +Default import FPS for video when no VUI/timing information is found +\hideinitializer */ #define GF_IMPORT_DEFAULT_FPS 25.0 @@ -127,10 +154,10 @@ GF_Err gf_media_remove_non_rap(GF_ISOFile *file, u32 track); NOTE: if an ESD is specified, its decoderSpecificInfo is also updated */ - + /*! - * Track importer flags - * \hideinitializer +Track importer flags +\hideinitializer */ enum { @@ -150,8 +177,7 @@ enum /*! special flag for text import at run time (never set on probe), indicates to leave the text box empty so that we dynamically adapt to display size*/ GF_IMPORT_SKIP_TXT_BOX = 1<<6, - /*! indicates to keep unknown tracks from .MOV/.IsoMedia files*/ - GF_IMPORT_KEEP_ALL_TRACKS = 1<<7, + /*! uses compact size in .MOV/.IsoMedia files*/ GF_IMPORT_USE_COMPACT_SIZE = 1<<8, /*! don't add a final empty sample when importing text tracks from srt*/ @@ -169,13 +195,12 @@ enum GF_IMPORT_OVSBR = 1<<14, /*! set subsample information with SVC*/ GF_IMPORT_SET_SUBSAMPLES = 1<<15, - /*! force to mark non-IDR frmaes with sync data (I slices,) to be marked as sync points points + /*! force to mark non-IDR frames with sync data (I slices,) to be marked as sync points points THE RESULTING FILE IS NOT COMPLIANT*/ GF_IMPORT_FORCE_SYNC = 1<<16, /*! keep trailing 0 bytes in AU payloads when any*/ GF_IMPORT_KEEP_TRAILING = 1<<17, - /*! forces inband parameter sets*/ - GF_IMPORT_FORCE_XPS_INBAND = 1<<18, + /*! do not compute edit list for B-frames video tracks*/ GF_IMPORT_NO_EDIT_LIST = 1<<19, /*! when set, only updates tracks info and return*/ @@ -192,20 +217,22 @@ enum GF_IMPORT_NO_VPS_EXTENSIONS = 1<<25, /*! when set no SEI messages are imported*/ GF_IMPORT_NO_SEI = 1<<26, - - /*! when set by user during import, will abort*/ - GF_IMPORT_DO_ABORT = 1<<31 + /*! keeps track references when importing a single track*/ + GF_IMPORT_KEEP_REFS = 1<<27, + /*! keeps AV1 temporal delimiter OBU in the samples*/ + GF_IMPORT_KEEP_AV1_TEMPORAL_OBU = 1<<28, + /*! imports sample dependencies information*/ + GF_IMPORT_SAMPLE_DEPS = 1<<29, + + //GF_IMPORT_FILTER_STATS = 0x80000000 //(=1<<31) }; - - -#define GF_IMPORT_AUTO_FPS 10000.0 - +/*! max supported numbers of tracks in importer*/ #define GF_IMPORT_MAX_TRACKS 100 - /*! - * Track info for video media - * \hideinitializer - */ +/*! +Track info for video media +\hideinitializer + */ struct __track_video_info { /*! video width in coded samples*/ @@ -214,12 +241,14 @@ struct __track_video_info u32 height; /*! pixel aspect ratio expressed as 32 bits, high 16 bits being the numerator and low ones being the denominator*/ u32 par; + /*! temporal enhancement flag*/ + Bool temporal_enhancement; /*! Video frame rate*/ Double FPS; }; /*! -* Track info for audio media -* \hideinitializer +Track info for audio media +\hideinitializer */ struct __track_audio_info { @@ -227,6 +256,8 @@ struct __track_audio_info u32 sample_rate; /*! number of channels*/ u32 nb_channels; + /*! samples per frame*/ + u32 samples_per_frame; }; /*! @@ -236,12 +267,13 @@ struct __track_import_info { /*! ID of the track (PID, TrackID, etc ...)*/ u32 track_num; - /*! track type (one of GF_ISOM_MEDIA_XXXX)*/ - u32 type; - /*! media type expressed as 4CC ('MPG1', 'MPG2', ISO 4CC, AVI 4CC)*/ - u32 media_type; - /*! import flags supported by the importer*/ - u32 flags; + /*! stream type (one of GF_STREAM_XXXX)*/ + u32 stream_type; + /*! codec ID ( one of GF_CODECID_XXX*)*/ + u32 codecid; + /*! GF_ISOM_MEDIA_* : vide, auxv, pict*/ + u32 media_subtype; + Bool is_chapter; /*! video format info*/ struct __track_video_info video_info; /*! audio format info*/ @@ -290,7 +322,7 @@ typedef struct __track_import /*! media source - selects importer type based on extension*/ char *in_name; /*! import duration if any*/ - u32 duration; + GF_Fraction duration; /*! importer flags*/ u32 flags; /*! importer swf flags*/ @@ -298,7 +330,7 @@ typedef struct __track_import /*! importer swf flatten angle when converting curves*/ Float swf_flatten_angle; /*! Forced video FPS (CMP, AVI, OGG, H264) - also used by SUB import. Ignored if 0*/ - Double video_fps; + GF_Fraction video_fps; /*! optional ESD to be updated by the importer (used for BT/XMT import)*/ GF_ESD *esd; /*! optional format indication for media source (used in IM1 reference software)*/ @@ -347,128 +379,173 @@ typedef struct __track_import struct __program_import_info pg_info[GF_IMPORT_MAX_TRACKS]; /*! last error encountered during import, internal to the importer*/ GF_Err last_error; + /*! any filter options to pass to source*/ + const char *filter_src_opts; + /*! any filter options to pass to sink*/ + const char *filter_dst_opts; + /*! filter chain to insert before destination, formated as f1[:args]@@f2[:args] options to pass to sink*/ + const char *filter_chain; + + + GF_AudioSampleEntryImportMode asemode; + + Bool audio_roll_change; + s16 audio_roll; + + Bool is_alpha; + Bool keep_audelim; + u32 xps_inband; + u32 print_stats_graph; + u32 prog_id; + + /*magic number for identifying source, will be set to the destination track. Only the low 32 bits are used + the high 32 bits are updated by the importer as follows: + 1<<33: if bit is set, source was an isobmff file + */ + u64 source_magic; + GF_FilterSession *run_in_session; + char *update_mux_args; + u32 track_index; } GF_MediaImporter; -/*! +/*! * Imports a media file - \param importer the importer object - \return error if any +\param importer the importer object +\return error if any */ GF_Err gf_media_import(GF_MediaImporter *importer); /*! Adds chapter info contained in file - \param file target movie - \param chap_file target chapter file - \param import_fps specifies the chapter frame rate (optional, ignored if 0 - defaults to 25). Most formats don't use this feature - \return error if any +\param isom_file target ISOBMF file +\param chap_file target chapter file +\param import_fps specifies the chapter frame rate (optional, ignored if 0 - defaults to 25). Most formats don't use this feature +\param for_qt use QT signaling for chapter tracks +\return error if any */ -GF_Err gf_media_import_chapters(GF_ISOFile *file, char *chap_file, Double import_fps); +GF_Err gf_media_import_chapters(GF_ISOFile *isom_file, char *chap_file, GF_Fraction import_fps, Bool for_qt); /*! Make the file ISMA compliant: creates ISMA BIFS / OD tracks if needed, and update audio/video IDs the file should not contain more than one audio and one video track - \param file the target movie - \param keepESIDs if true, ES IDs are not changed. - \param keepImage if true, keeps image tracks - \param no_ocr if true, doesn't write clock references in MPEG-4 system info - \return error if any +\param isom_file the target ISOBMF file +\param keepESIDs if true, ES IDs are not changed. +\param keepImage if true, keeps image tracks +\param no_ocr if true, doesn't write clock references in MPEG-4 system info +\return error if any */ -GF_Err gf_media_make_isma(GF_ISOFile *file, Bool keepESIDs, Bool keepImage, Bool no_ocr); +GF_Err gf_media_make_isma(GF_ISOFile *isom_file, Bool keepESIDs, Bool keepImage, Bool no_ocr); -/*! +/*! Make the file 3GP compliant && sets profile - \param file the target movie - \return error if any +\param isom_file the target ISOBMF file +\return error if any */ -GF_Err gf_media_make_3gpp(GF_ISOFile *file); +GF_Err gf_media_make_3gpp(GF_ISOFile *isom_file); /*! make the file playable on a PSP - \param file the target movie - \return error if any +\param isom_file the target ISOBMF file +\return error if any */ -GF_Err gf_media_make_psp(GF_ISOFile *file); +GF_Err gf_media_make_psp(GF_ISOFile *isom_file); + +/*! + adjust file params for QT prores +\param qt_file the target QT file +\return error if any +*/ +GF_Err gf_media_check_qt_prores(GF_ISOFile *qt_file); /*! @} */ /*! - * \addtogroup mnal_grp AVC and HEVC ISOBMFF tools - * \ingroup media_grp - * \brief Manipulation AVC and HEVC tracks in ISOBMFF. - * - *This section documents functions for manipulating AVC and HEVC tracks in ISOBMFF. - * @{ - */ +\addtogroup mnal_grp AVC and HEVC ISOBMFF tools +\ingroup media_grp +\brief Manipulation AVC and HEVC tracks in ISOBMFF. + +This section documents functions for manipulating AVC and HEVC tracks in ISOBMFF. + +@{ +*/ /*! Changes the profile (if not 0) and level (if not 0) indication of the media - only AVC/H264 supported for now - \param file the target movie - \param track the target track - \param profile the new profile to set - \param level the new level to set - \return error if any +\param isom_file the target ISOBMF file +\param trackNumber the target track +\param profile the new profile to set +\param level the new level to set +\return error if any */ -GF_Err gf_media_change_pl(GF_ISOFile *file, u32 track, u32 profile, u32 level); - -/*! - Rewrite AVC samples if nalu size_length has to be changed - \param file the target movie - \param track the target track - \param prev_size_in_bits previous size in bits of the NALU length field in the track - \param new_size_in_bits new size in bits of the NALU length field in the track - \return error if any +GF_Err gf_media_change_pl(GF_ISOFile *isom_file, u32 trackNumber, u32 profile, u32 level); + +/*! + Rewrite NAL-based samples (AVC/HEVC/...) samples if nalu size_length has to be changed +\param isom_file the target ISOBMF file +\param trackNumber the target track +\param new_size_in_bits new size in bits of the NALU length field in the track, for all samples description of the track +\return error if any */ -GF_Err gf_media_avc_rewrite_samples(GF_ISOFile *file, u32 track, u32 prev_size_in_bits, u32 new_size_in_bits); +GF_Err gf_media_nal_rewrite_samples(GF_ISOFile *isom_file, u32 trackNumber, u32 new_size_in_bits); /*! - Split SVC layers - \param file the target movie - \param track the target track - \param splitAll if set each layers will be in a single track, otherwise all non-base layers will be in the same track - \return error if any + Split SVC layers +\param isom_file the target ISOBMF file +\param trackNumber the target track +\param splitAll if set each layers will be in a single track, otherwise all non-base layers will be in the same track +\return error if any */ -GF_Err gf_media_split_svc(GF_ISOFile *file, u32 track, Bool splitAll); +GF_Err gf_media_split_svc(GF_ISOFile *isom_file, u32 trackNumber, Bool splitAll); /*! Merge SVC layers - \param file the target movie - \param track the target track - \param mergeAll if set all layers will be merged a single track, otherwise all non-base layers will be merged in the same track - \return error if any +\param isom_file the target ISOBMF file +\param trackNumber the target track +\param mergeAll if set all layers will be merged a single track, otherwise all non-base layers will be merged in the same track +\return error if any */ -GF_Err gf_media_merge_svc(GF_ISOFile *file, u32 track, Bool mergeAll); - -/* ! - Split L-HEVC layers - \param file the target movie - \param track the target track - \param for_temporal_sublayers if set only temporal sublayers are split, otherwise layers are split - \param splitAll if set each layers will be in a single track, otherwise all non-base layers will be in the same track - \param use_extractors if set, extractors are used in the enhancement layers. - \return error if any +GF_Err gf_media_merge_svc(GF_ISOFile *isom_file, u32 trackNumber, Bool mergeAll); + +/*! LHVC extractor mode*/ +typedef enum +{ + /*! use extractors*/ + GF_LHVC_EXTRACTORS_ON, + /*! don't use extractors and keep base track inband/outofband param set signaling*/ + GF_LHVC_EXTRACTORS_OFF, + /*! don't use extractors and force inband signaling in enhancement layer (for ATSC3)*/ + GF_LHVC_EXTRACTORS_OFF_FORCE_INBAND +} GF_LHVCExtractoreMode; + +/*! Split L-HEVC layers +\param isom_file the target ISOBMF file +\param trackNumber the target track +\param for_temporal_sublayers if set only temporal sublayers are split, otherwise layers are split +\param splitAll if set each layers will be in a single track, otherwise all non-base layers will be in the same track +\param extractor_mode extractor mode +\return error if any */ -GF_Err gf_media_split_lhvc(GF_ISOFile *file, u32 track, Bool for_temporal_sublayers, Bool splitAll, Bool use_extractors); +GF_Err gf_media_split_lhvc(GF_ISOFile *isom_file, u32 trackNumber, Bool for_temporal_sublayers, Bool splitAll, GF_LHVCExtractoreMode extractor_mode); -/* ! +/*! Split HEVC tiles into different tracks - \param file the target movie - \param signal_only if set to 1 or 2, inserts tile description and NAL->tile mapping but does not create separate tracks. If 2, NAL->tile mapping uses RLE - \return error if any +\param isom_file the target ISOBMF file +\param signal_only if set to 1 or 2, inserts tile description and NAL->tile mapping but does not create separate tracks. If 2, NAL->tile mapping uses RLE +\return error if any */ -GF_Err gf_media_split_hevc_tiles(GF_ISOFile *file, u32 signal_only); +GF_Err gf_media_split_hevc_tiles(GF_ISOFile *isom_file, u32 signal_only); - -/* ! + +/*! Filter HEVC/L-HEVC NALUs by temporal IDs and layer IDs, removing all NALUs above the desired levels. - \param file the target movie - \param track the target track - \param max_temporal_id_plus_one max temporal ID plus 1 of all NALUs to be removed. - \param max_layer_id_plus_one max layer ID plus 1 of all NALUs to be removed. - \return error if any +\param isom_file the target ISOBMF file +\param trackNumber the target track +\param max_temporal_id_plus_one max temporal ID plus 1 of all NALUs to be removed. +\param max_layer_id_plus_one max layer ID plus 1 of all NALUs to be removed. +\return error if any */ -GF_Err gf_media_filter_hevc(GF_ISOFile *file, u32 track, u8 max_temporal_id_plus_one, u8 max_layer_id_plus_one); +GF_Err gf_media_filter_hevc(GF_ISOFile *isom_file, u32 trackNumber, u8 max_temporal_id_plus_one, u8 max_layer_id_plus_one); #endif /*GPAC_DISABLE_MEDIA_IMPORT*/ @@ -477,13 +554,14 @@ GF_Err gf_media_filter_hevc(GF_ISOFile *file, u32 track, u8 max_temporal_id_plus /*! - * \addtogroup dashseg_grp DASH Segmenter - * \ingroup media_grp - * \brief MPEG-DASH creation. - * - *This section documents media functions for MPEG-DASH creation. - * @{ - */ +\addtogroup dashseg_grp DASH Segmenter +\ingroup media_grp +\brief MPEG-DASH creation. + +This section documents media functions for MPEG-DASH creation. + +@{ +*/ /*! * DASH segmenter information per source @@ -496,6 +574,8 @@ typedef struct char *representationID; /*! ID of the period. If not set, assigned automatically*/ char *periodID; + /*! ID of the adaptation set. If not set, assigned automatically.*/ + u32 asID; /*! forced media duration.*/ Double media_duration; /*! number of base URLs in the baseURL structure*/ @@ -528,22 +608,44 @@ typedef struct u32 bandwidth; /*! forced period duration (used when using empty periods or xlink periods without content)*/ Double period_duration; + /*! forced dash target duration for this rep*/ + Double dash_duration; + /*! sets default start number for this representation. if not set, assigned automatically */ + u32 startNumber; //TODO: start number, template + /*! overrides template for this input*/ + char *seg_template; + /*! sets name of HLS playlist*/ + char *hls_pl; + /*! if true and only one media stream in target segment, the moov will use the media stream timescale*/ + Bool sscale; + /*! only imports this track from the source*/ + u32 track_id; + /*! non legacy options passed to dasher for source */ + char *source_opts; + /*! filter chain to instantiate between this source and the dasher*/ + char *filter_chain; + /*! period order, internal only*/ + u32 period_order; } GF_DashSegmenterInput; /*! - * DASH profile constants - * \hideinitializer +DASH profile constants +\hideinitializer + +Matches profile enum of dasher module: auto|live|onDemand|main|full|hbbtv1.5.live|dashavc264.live|dashavc264.onDemand */ typedef enum { - /*! Full dash (no profile)*/ - GF_DASH_PROFILE_FULL = 0, + /*! auto profile, internal use only*/ + GF_DASH_PROFILE_AUTO = 0, /*! Live dash profile for: live for ISOFF, SIMPLE for M2TS */ GF_DASH_PROFILE_LIVE, /*! onDemand profile*/ GF_DASH_PROFILE_ONDEMAND, /*! main profile*/ GF_DASH_PROFILE_MAIN, + /*! Full dash (no profile)*/ + GF_DASH_PROFILE_FULL, /*! industry profile HbbTV 1.5 ISOBMFF Live */ GF_DASH_PROFILE_HBBTV_1_5_ISOBMF_LIVE, @@ -551,15 +653,12 @@ typedef enum GF_DASH_PROFILE_AVC264_LIVE, /*! industry profile DASH-IF ISOBMFF onDemand */ GF_DASH_PROFILE_AVC264_ONDEMAND, - - /*! Unknown profile, internal use only*/ - GF_DASH_PROFILE_UNKNOWN } GF_DashProfile; /*! - * DASH bitstream switching selector - * \hideinitializer +DASH bitstream switching selector +\hideinitializer */ typedef enum { @@ -579,8 +678,8 @@ typedef enum /*! - * DASH media presentation type - * \hideinitializer +DASH media presentation type +\hideinitializer */ typedef enum { @@ -595,8 +694,8 @@ typedef enum } GF_DashDynamicMode; /*! - * DASH selector for content protection descriptor location - * \hideinitializer +DASH selector for content protection descriptor location +\hideinitializer */ typedef enum { @@ -608,207 +707,230 @@ typedef enum GF_DASH_CPMODE_BOTH, } GF_DASH_ContentLocationMode; +/*! DASH segmenter*/ typedef struct __gf_dash_segmenter GF_DASHSegmenter; -/*! +/*! Create a new DASH segmenter - * \param mpdName target MPD file name, cannot be changed - * \param profile target DASH profile, cannot be changed - * \param tmp_dir temp dir for file generation, OS NULL for default - * \param timescale timescale used to specif most of the dash timings. If 0, 1000 is used - * \param dasher_context_file config file used to store the context of the DASH segmenter. This allows destroying the segmenter and restarting it later on with the right DASH segquence numbers, MPD and and timing info - * \return the DASH segmenter object +\param mpdName target MPD file name, cannot be changed +\param profile target DASH profile, cannot be changed +\param tmp_dir temp dir for file generation, OS NULL for default +\param timescale timescale used to specif most of the dash timings. If 0, 1000 is used +\param dasher_context_file config file used to store the context of the DASH segmenter. This allows destroying the segmenter and restarting it later on with the right DASH segquence numbers, MPD and and timing info +\return the DASH segmenter object */ -GF_DASHSegmenter *gf_dasher_new(const char *mpdName, GF_DashProfile profile, const char *tmp_dir, u32 timescale, GF_Config *dasher_context_file); +GF_DASHSegmenter *gf_dasher_new(const char *mpdName, GF_DashProfile profile, const char *tmp_dir, u32 timescale, const char *dasher_context_file); /*! Deletes a DASH segmenter - \param dasher the DASH segmenter object +\param dasher the DASH segmenter object */ void gf_dasher_del(GF_DASHSegmenter *dasher); /*! Removes the DASH inputs. Re-add new ones with gf_dasher_add_input() - * \param dasher the DASH segmenter object +\param dasher the DASH segmenter object */ void gf_dasher_clean_inputs(GF_DASHSegmenter *dasher); /*! Sets MPD info - * \param dasher the DASH segmenter object - * \param title MPD title - * \param copyright MPD copyright - * \param moreInfoURL MPD "more info" URL - * \param sourceInfo MPD source info - * \return error code if any +\param dasher the DASH segmenter object +\param title MPD title +\param copyright MPD copyright +\param moreInfoURL MPD "more info" URL +\param sourceInfo MPD source info +\param lang MPD language for title +\return error code if any */ -GF_Err gf_dasher_set_info(GF_DASHSegmenter *dasher, const char *title, const char *copyright, const char *moreInfoURL, const char *sourceInfo); +GF_Err gf_dasher_set_info(GF_DASHSegmenter *dasher, const char *title, const char *copyright, const char *moreInfoURL, const char *sourceInfo, const char *lang); /*! Sets MPD Location. This is useful to distrubute a dynamic MPD by mail or any non-HTTP mean - * \param dasher the DASH segmenter object - * \param location the URL where this MPD can be found - * \return error code if any +\param dasher the DASH segmenter object +\param location the URL where this MPD can be found +\return error code if any */ GF_Err gf_dasher_set_location(GF_DASHSegmenter *dasher, const char *location); /*! Adds a base URL to the MPD - * \param dasher the DASH segmenter object - * \param base_url base url to add - * \return error code if any +\param dasher the DASH segmenter object +\param base_url base url to add +\return error code if any */ GF_Err gf_dasher_add_base_url(GF_DASHSegmenter *dasher, const char *base_url); /*! - Enable URL template - - may be overriden by the current profile - * \param dasher the DASH segmenter object - * \param enable enable usage of URL template - * \param default_template template for the segment name - * \param default_extension extension for the segment name - * \return error code if any + Enable URL template - - may be overridden by the current profile +\param dasher the DASH segmenter object +\param enable enable usage of URL template +\param default_template template for the segment name +\param default_extension extension for the segment name +\param default_init_extension extension for the initialization segment name +\return error code if any */ - -GF_Err gf_dasher_enable_url_template(GF_DASHSegmenter *dasher, Bool enable, const char *default_template, const char *default_extension); + +GF_Err gf_dasher_enable_url_template(GF_DASHSegmenter *dasher, Bool enable, const char *default_template, const char *default_extension, const char *default_init_extension); /*! - Enable Segment Timeline template - may be overriden by the current profile - * \param dasher the DASH segmenter object - * \param enable enable or disable - * \return error code if any + Enable Segment Timeline template - may be overridden by the current profile +\param dasher the DASH segmenter object +\param enable enable or disable +\return error code if any */ GF_Err gf_dasher_enable_segment_timeline(GF_DASHSegmenter *dasher, Bool enable); /*! - Enables single segment - may be overriden by the current profile - * \param dasher the DASH segmenter object - * \param enable enable or disable - * \return error code if any + Enables single segment - may be overridden by the current profile +\param dasher the DASH segmenter object +\param enable enable or disable +\return error code if any */ GF_Err gf_dasher_enable_single_segment(GF_DASHSegmenter *dasher, Bool enable); -/*! - Enable single file (with multiple segments) - may be overriden by the current profile - * \param dasher the DASH segmenter object - * \param enable enable or disable - * \return error code if any +/*! + Enable single file (with multiple segments) - may be overridden by the current profile +\param dasher the DASH segmenter object +\param enable enable or disable +\return error code if any */ GF_Err gf_dasher_enable_single_file(GF_DASHSegmenter *dasher, Bool enable); /*! - Sets bitstream switching mode - may be overriden by the current profile - * \param dasher the DASH segmenter object - * \param bitstream_switching mode to use for bitstream switching - * \return error code if any + Sets bitstream switching mode - may be overridden by the current profile +\param dasher the DASH segmenter object +\param bitstream_switching mode to use for bitstream switching +\return error code if any */ GF_Err gf_dasher_set_switch_mode(GF_DASHSegmenter *dasher, GF_DashSwitchingMode bitstream_switching); /*! Sets segment and fragment durations. - * \param dasher the DASH segmenter object - * \param default_segment_duration the duration of a dash segment - * \param segment_duration_strict indicated is the duration is strict (otherwise we consider the mean duration) - * \param default_fragment_duration the duration of a dash fragment - if 0, same as default_segment_duration - * \return error code if any +\param dasher the DASH segmenter object +\param default_segment_duration the duration of a dash segment +\param default_fragment_duration the duration of a dash fragment - if 0, same as default_segment_duration +\param sub_duration the duration in seconds of media to DASH. If 0, the whole sources will be processed. +\return error code if any */ -GF_Err gf_dasher_set_durations(GF_DASHSegmenter *dasher, Double default_segment_duration, Bool segment_duration_strict, Double default_fragment_duration); +GF_Err gf_dasher_set_durations(GF_DASHSegmenter *dasher, Double default_segment_duration, Double default_fragment_duration, Double sub_duration); /*! Enables spliting at RAP boundaries - * \param dasher the DASH segmenter object - * \param segments_start_with_rap segments will be split at RAP boundaries - * \param fragments_start_with_rap fragments will be split at RAP boundaries - * \return error code if any +\param dasher the DASH segmenter object +\param segments_start_with_rap segments will be split at RAP boundaries +\param fragments_start_with_rap fragments will be split at RAP boundaries +\return error code if any */ GF_Err gf_dasher_enable_rap_splitting(GF_DASHSegmenter *dasher, Bool segments_start_with_rap, Bool fragments_start_with_rap); /*! Enables segment marker - * \param dasher the DASH segmenter object - * \param segment_marker_4cc 4CC code of the segment marker box - * \return error code if any +\param dasher the DASH segmenter object +\param segment_marker_4cc 4CC code of the segment marker box +\return error code if any */ GF_Err gf_dasher_set_segment_marker(GF_DASHSegmenter *dasher, u32 segment_marker_4cc); /*! Enables segment indexes - * \param dasher the DASH segmenter object - * \param enable_sidx enable or disable - * \param subsegs_per_sidx number of subsegments per segment - * \param daisy_chain_sidx enable daisy chaining of sidx - * \return error code if any +\param dasher the DASH segmenter object +\param enable_sidx enable or disable +\param subsegs_per_sidx number of subsegments per segment +\param daisy_chain_sidx enable daisy chaining of sidx +\param use_ssix enables ssix generation, level 1 for I-frames, the rest of the segment not mapped +\return error code if any */ -GF_Err gf_dasher_enable_sidx(GF_DASHSegmenter *dasher, Bool enable_sidx, u32 subsegs_per_sidx, Bool daisy_chain_sidx); +GF_Err gf_dasher_enable_sidx(GF_DASHSegmenter *dasher, Bool enable_sidx, u32 subsegs_per_sidx, Bool daisy_chain_sidx, Bool use_ssix); /*! Sets mode for the dash segmenter. - * \param dasher the DASH segmenter object - * \param dash_mode the mode to use. Currently switching from static mode to dynamic mode is not well supported and may produce non-compliant MPDs - * \param mpd_update_time time between MPD refresh, in seconds. Used for dynamic mode, may be 0 if \p mpd_live_duration is set - * \param time_shift_depth the depth of the time shift buffer in seconds, -1 for infinite time shift. - * \param mpd_live_duration total duration of the DASH session in dynamic mode, in seconds. May be set to 0 if \p mpd_update_time is set - * \return error code if any +\param dasher the DASH segmenter object +\param dash_mode the mode to use. Currently switching from static mode to dynamic mode is not well supported and may produce non-compliant MPDs +\param mpd_update_time time between MPD refresh, in seconds. Used for dynamic mode, may be 0 if \p mpd_live_duration is set +\param time_shift_depth the depth of the time shift buffer in seconds, -1 for infinite time shift. +\param mpd_live_duration total duration of the DASH session in dynamic mode, in seconds. May be set to 0 if \p mpd_update_time is set +\return error code if any */ GF_Err gf_dasher_set_dynamic_mode(GF_DASHSegmenter *dasher, GF_DashDynamicMode dash_mode, Double mpd_update_time, s32 time_shift_depth, Double mpd_live_duration); /*! Sets the minimal buffer desired. - * \param dasher the DASH segmenter object - * \param min_buffer min buffer time in seconds for the DASH session. Currently the minimal buffer is NOT computed from the source material and must be set to an appropriate value. - * \return error code if any +\param dasher the DASH segmenter object +\param min_buffer min buffer time in seconds for the DASH session. Currently the minimal buffer is NOT computed from the source material and must be set to an appropriate value. +\return error code if any */ GF_Err gf_dasher_set_min_buffer(GF_DASHSegmenter *dasher, Double min_buffer); /*! Sets the availability start time offset. - * \param dasher the DASH segmenter object - * \param ast_offset ast offset in milliseconds. If >0, the DASH session availabilityStartTime will be earlier than UTC by the amount of seconds specified. If <0, the media representation will have an availabilityTimeOffset of the amount of seconds specified, instructing the client that segments may be accessed earlier. - * \return error code if any +\param dasher the DASH segmenter object +\param ast_offset ast offset in milliseconds. If >0, the DASH session availabilityStartTime will be earlier than UTC by the amount of seconds specified. If <0, the media representation will have an availabilityTimeOffset of the amount of seconds specified, instructing the client that segments may be accessed earlier. +\return error code if any */ GF_Err gf_dasher_set_ast_offset(GF_DASHSegmenter *dasher, s32 ast_offset); /*! Enables memory fragmenting: fragments will be written to disk only once completed - * \param dasher the DASH segmenter object - * \param enable Enables or disables. Default is disabled. - * \return error code if any +\param dasher the DASH segmenter object +\param enable Enables or disables. Default is disabled. +\return error code if any */ GF_Err gf_dasher_enable_memory_fragmenting(GF_DASHSegmenter *dasher, Bool enable); /*! Sets initial values for ISOBMFF sequence number and TFDT in movie fragments. - * \param dasher the DASH segmenter object - * \param initial_moof_sn sequence number of the first moof to be generated. Default value is 1. - * \param initial_tfdt initial tfdt of the first traf to be generated, in DASH segmenter timescale units. Default value is 0. - * \return error code if any +\param dasher the DASH segmenter object +\param initial_moof_sn sequence number of the first moof to be generated. Default value is 1. +\param initial_tfdt initial tfdt of the first traf to be generated, in DASH segmenter timescale units. Default value is 0. +\return error code if any */ GF_Err gf_dasher_set_initial_isobmf(GF_DASHSegmenter *dasher, u32 initial_moof_sn, u64 initial_tfdt); + +/*! DASH PSSH storage mode*/ +typedef enum +{ + //! PSSH box in moov only + GF_DASH_PSSH_MOOV = 0, + //! PSSH box in moof only + GF_DASH_PSSH_MOOF, + //! PSSH box in moov and MPD + GF_DASH_PSSH_MOOV_MPD, + //! PSSH box in moof and MPD + GF_DASH_PSSH_MOOF_MPD, + //! PSSH box in MPD only + GF_DASH_PSSH_MPD +} GF_DASHPSSHMode; + /*! Configure how default values for ISOBMFF are stored - * \param dasher the DASH segmenter object - * \param no_fragments_defaults if set, fragments default values are repeated in each traf and not set in trex. Default value is GF_FALSE - * \param pssh_moof if set, PSSH is stored in each moof, and not set in init segment. Default value is GF_FALSE - * \param samplegroups_in_traf if set, all sample group definitions are stored in each traf and not set in init segment. Default value is GF_FALSE - * \param single_traf_per_moof if set, each moof will contain a single traf, even if source media is multiplexed. Default value is GF_FALSE - * \return error code if any +\param dasher the DASH segmenter object +\param no_fragments_defaults if set, fragments default values are repeated in each traf and not set in trex. Default value is GF_FALSE +\param pssh_mode sets the storage mode of PSSH in moov/moof/mpd. +\param samplegroups_in_traf if set, all sample group definitions are stored in each traf and not set in init segment. Default value is GF_FALSE +\param single_traf_per_moof if set, each moof will contain a single traf, even if source media is multiplexed. Default value is GF_FALSE +\param tfdt_per_traf if set, each traf will contain a tfdt. Only applicable when single_traf_per_moof is GF_TRUE. Default value is GF_FALSE +\param mvex_after_traks if set, the mvex box will be written after all track boxes +\param sdtp_in_traf mode for sdtp storage in traf (smooth compatibility): 0: not allowed, 1: only stdp, no trun flags, 2: both (trun for sync, stdp for the rest) +\return error code if any */ -GF_Err gf_dasher_configure_isobmf_default(GF_DASHSegmenter *dasher, Bool no_fragments_defaults, Bool pssh_moof, Bool samplegroups_in_traf, Bool single_traf_per_moof); +GF_Err gf_dasher_configure_isobmf_default(GF_DASHSegmenter *dasher, Bool no_fragments_defaults, GF_DASHPSSHMode pssh_mode, Bool samplegroups_in_traf, Bool single_traf_per_moof, Bool tfdt_per_traf, Bool mvex_after_traks, u32 sdtp_in_traf); /*! - Enables insertion of UTC reference in the begining of segments - * \param dasher the DASH segmenter object - * \param insert_utc if set, UTC will be inserted. Default value is disabled. - * \return error code if any + Enables insertion of UTC reference in the beginning of segments +\param dasher the DASH segmenter object +\param insert_utc if set, UTC will be inserted. Default value is disabled. +\return error code if any */ GF_Err gf_dasher_enable_utc_ref(GF_DASHSegmenter *dasher, Bool insert_utc); /*! Enables real-time generation of media segments. - * \param dasher the DASH segmenter object - * \param real_time if set, segemnts are generated in real time. Only supported for single representation (potentially multiplexed) DASH session. Default is disabled. - * \return error code if any +\param dasher the DASH segmenter object +\param real_time if set, segments are generated in real time. Only supported for single representation (potentially multiplexed) DASH session. Default is disabled. +\return error code if any */ GF_Err gf_dasher_enable_real_time(GF_DASHSegmenter *dasher, Bool real_time); @@ -822,79 +944,146 @@ GF_Err gf_dasher_set_content_protection_location_mode(GF_DASHSegmenter *dasher, /*! Sets profile extension as used by DASH-IF and DVB. - * \param dasher the DASH segmenter object - * \param dash_profile_extension specifies a string of profile extensions, as used by DASH-IF and DVB. - * \return error code if any +\param dasher the DASH segmenter object +\param dash_profile_extension specifies a string of profile extensions, as used by DASH-IF and DVB. +\return error code if any */ GF_Err gf_dasher_set_profile_extension(GF_DASHSegmenter *dasher, const char *dash_profile_extension); +/*! + Enable/Disable cached inputs . +\param dasher the DASH segmenter object +\param no_cache if true, input file will be reopen each time the dasher process function is called . +\return error code if any +*/ +GF_Err gf_dasher_enable_cached_inputs(GF_DASHSegmenter *dasher, Bool no_cache); + +/*! + Enable/Disable loop inputs . +\param dasher the DASH segmenter object +\param do_loop if true, input files will be looped at the end of the file in a live simulation. Otherwise a new period will be created. +\return error code if any +*/ +GF_Err gf_dasher_enable_loop_inputs(GF_DASHSegmenter *dasher, Bool do_loop); + + +/*! +DASH selector for segment split mode +\hideinitializer + */ +typedef enum +{ + /*! segment start time is greater than or equal to theoretical segment start (segment_duration*segment_number)*/ + GF_DASH_SPLIT_OUT=0, + /*! segment start time is as close as possible to theoretical segment start (segment_duration*segment_number), but may be greater*/ + GF_DASH_SPLIT_CLOSEST, + /*! segment start time is less than or equal to theoretical segment start (segment_duration*segment_number) so that the theoretical start time is always present in the segment*/ + GF_DASH_SPLIT_IN, +} GF_DASH_SplitMode; + +/*! + Enable/Disable split on bound mode. +\param dasher the DASH segmenter object +\param split_mode the desired segmentation mode +\return error code if any +*/ +GF_Err gf_dasher_set_split_mode(GF_DASHSegmenter *dasher, GF_DASH_SplitMode split_mode); + +/*! + Sets m3u8 file name - if not set, no m3u8 output +\param dasher the DASH segmenter object +\param insert_clock if true UTC clock is inserted in variant playlists in live +\return error code if any +*/ +GF_Err gf_dasher_set_hls_clock(GF_DASHSegmenter *dasher, Bool insert_clock); + +/*! + Sets cue file for the session. +\param dasher the DASH segmenter object +\param cues_file name of the cue file. This is an XML document with root 'DASHCues' element, one or multiple 'Stream' elements with attribute ID (trackID) + and timescale (trackTimescale), and a set of 'cues' elements per Stream with attributes sampleNumber, dts or cts. +\param strict_cues if true will fail if one cue doesn't match a timestamp in the stream or if the split sample is not RAP +\return error code if any +*/ +GF_Err gf_dasher_set_cues(GF_DASHSegmenter *dasher, const char *cues_file, Bool strict_cues); + /*! Adds a media input to the DASHer - * \param dasher the DASH segmenter object - * \param input media source to add - * \return error code if any +\param dasher the DASH segmenter object +\param input media source to add +\return error code if any */ -GF_Err gf_dasher_add_input(GF_DASHSegmenter *dasher, GF_DashSegmenterInput *input); +GF_Err gf_dasher_add_input(GF_DASHSegmenter *dasher, const GF_DashSegmenterInput *input); /*! Process the media source and generate segments - * \param dasher the DASH segmenter object - * \param sub_duration the duration in seconds of media to DASH. If 0, the whole sources will be processed. - * \return error code if any +\param dasher the DASH segmenter object +\return error code if any */ -GF_Err gf_dasher_process(GF_DASHSegmenter *dasher, Double sub_duration); +GF_Err gf_dasher_process(GF_DASHSegmenter *dasher); -/*! +/*! Returns time to wait until end of currently generated segments - * \param dasher the DASH segmenter object - * \param ms_ins_session if set, retrives the number of ms since the start of the dash session - * \return time to wait in milliseconds +\param dasher the DASH segmenter object +\param ms_ins_session if set, retrives the number of ms since the start of the dash session +\return time to wait in milliseconds */ u32 gf_dasher_next_update_time(GF_DASHSegmenter *dasher, u64 *ms_ins_session); -/*! +/*! Sets dasher start date, rather than use current time. Used for debugging purposes, such as simulating long lasting sessions. - * \param dasher the DASH segmenter object - * \param dash_utc_start_date start date as UTC timstamp. If 0, current time is used +\param dasher the DASH segmenter object +\param dash_utc_start_date start date as as xs:date, eg YYYY-MM-DDTHH:MM:SSZ. If 0, current time is used +*/ +void gf_dasher_set_start_date(GF_DASHSegmenter *dasher, const char *dash_utc_start_date); + + +/*! + Sets print flags for filter session +\param dasher the DASH segmenter object +\param fs_print_flags flags for statistics (1) and graph (2) printing +\return error if any */ -void gf_dasher_set_start_date(GF_DASHSegmenter *dasher, u64 dash_utc_start_date); +GF_Err gf_dasher_print_session_info(GF_DASHSegmenter *dasher, u32 fs_print_flags); #ifndef GPAC_DISABLE_ISOM_FRAGMENTS /*! save file as fragmented movie - \param file the target file to be fragmented - \param output_file name of the output file - \param max_duration_sec max fragment duration in seconds - \return error if any +\param isom_file the target file to be fragmented +\param output_file name of the output file +\param max_duration_sec max fragment duration in seconds +\param use_mfra insert track fragment movie fragments +\return error if any */ -GF_Err gf_media_fragment_file(GF_ISOFile *file, const char *output_file, Double max_duration_sec); +GF_Err gf_media_fragment_file(GF_ISOFile *isom_file, const char *output_file, Double max_duration_sec, Bool use_mfra); #endif /*! @} */ /*! - * \addtogroup mexp_grp Media Exporter - * \ingroup media_grp - * \brief Media exporting and extraction. - * - *This section documents functions for media exporting and extraction. - * @{ +\addtogroup mexp_grp Media Exporter +\ingroup media_grp +\brief Media exporting and extraction. + +This section documents functions for media exporting and extraction. + +@{ */ #ifndef GPAC_DISABLE_MEDIA_EXPORT /*! - * Track dumper formats and flags - * \hideinitializer +Track dumper formats and flags +\hideinitializer */ enum { /*! track dumper types are formatted as flags for conveniency for authoring tools, but never used as a OR'ed set*/ /*native format (JPG, PNG, MP3, etc) if supported*/ - GF_EXPORT_NATIVE = GF_TRUE, + GF_EXPORT_NATIVE = 1, /*! raw samples (including hint tracks for rtp)*/ GF_EXPORT_RAW_SAMPLES = (1<<1), /*! NHNT format (any MPEG-4 media)*/ @@ -920,25 +1109,24 @@ enum for raw extraction, indicates data shall be appended at the end of output file if present */ GF_EXPORT_MERGE = (1<<10), + /*! don't infer file extension */ + GF_EXPORT_NO_FILE_EXT = (1 << 11), /*! indicates QCP file format possible as well as native (EVRC and SMV audio only)*/ - GF_EXPORT_USE_QCP = (1<<11), + GF_EXPORT_USE_QCP = (1<<12), /*! indicates full NHML dump*/ - GF_EXPORT_NHML_FULL = (1<<11), + GF_EXPORT_NHML_FULL = (1<<13), /*! exports a single svc layer*/ - GF_EXPORT_SVC_LAYER = (1<<12), + GF_EXPORT_SVC_LAYER = (1<<14), /*! Don't merge identical cues in consecutive samples */ - GF_EXPORT_WEBVTT_NOMERGE = (1<<13), + GF_EXPORT_WEBVTT_NOMERGE = (1<<15), /*! Experimental Streaming Instructions */ - GF_EXPORT_SIX = (1<<14), - - /*! Experimental Streaming Instructions */ - GF_EXPORT_FORCE_EXT = (1<<15), + GF_EXPORT_SIX = (1<<16), /*! ony probes extraction format*/ GF_EXPORT_PROBE_ONLY = (1<<30), /*when set by user during export, will abort*/ - GF_EXPORT_DO_ABORT = (1<<31), + GF_EXPORT_DO_ABORT = 0x80000000 //(1<<31) }; /*! @@ -948,7 +1136,7 @@ typedef struct __track_exporter { /*! source ISOBMF file */ GF_ISOFile *file; - /*! ID of track to be dumped*/ + /*! ID of track/PID/... to be dumped*/ u32 trackID; /*! sample number to export for GF_EXPORT_RAW_SAMPLES only*/ u32 sample_num; @@ -958,23 +1146,27 @@ typedef struct __track_exporter u32 flags; /*! non-ISOBMF source file (AVI, TS)*/ char *in_name; - /*! set to TRUE if no additionnal files are to be created*/ - Bool nhml_only; - /*! optionnal FILE for output*/ + /*! optional FILE for output*/ FILE *dump_file; + u32 print_stats_graph; } GF_MediaExporter; /*! Dumps a given media track - \param dump the track dumper object - \return error if any +\param dump the track dumper object +\return error if any */ GF_Err gf_media_export(GF_MediaExporter *dump); -GF_Err gf_media_export_nhml(GF_MediaExporter *dumper, Bool dims_doc); - #ifndef GPAC_DISABLE_VTT -GF_Err gf_webvtt_dump_iso_track(GF_MediaExporter *dumper, char *szName, u32 track, Bool merge, Bool box_dump); +/*! dumps a webvtt tracl to a given file +\param dumper media dumper object +\param trackNumber the target track to dump +\param merge if GF_TRUE, merge vtt cues while dumping them +\param box_dump if GF_TRUE, dumps box structures +\return error if any +*/ +GF_Err gf_webvtt_dump_iso_track(GF_MediaExporter *dumper, u32 trackNumber, Bool merge, Bool box_dump); #endif #endif /*GPAC_DISABLE_MEDIA_EXPORT*/ @@ -983,12 +1175,13 @@ GF_Err gf_webvtt_dump_iso_track(GF_MediaExporter *dumper, char *szName, u32 trac /*! - * \addtogroup mhint_grp Media Hinting - * \ingroup media_grp - * \brief ISOBMFF file hinting. - * - *This section documents functions for ISOBMFF file hinting. - * @{ +\addtogroup mhint_grp Media Hinting +\ingroup media_grp +\brief ISOBMFF file hinting. + + This section documents functions for ISOBMFF file hinting. + +@{ */ @@ -996,72 +1189,80 @@ GF_Err gf_webvtt_dump_iso_track(GF_MediaExporter *dumper, char *szName, u32 trac /* RTP IsoMedia file hinting */ +/*! ISOBMFF RTP hinter object*/ typedef struct __tag_isom_hinter GF_RTPHinter; /*! Creates a new track hinter object - * \param file the target ISOBMF file - * \param track the track to hint - * \param Path_MTU max RTP packet size (excluding IP/UDP/IP headers) - * \param max_ptime max packet duration in RTP timescale, can be set to 0 for auto compute - * \param default_rtp_rate RTP rate for the track, can be set to 0 for auto compute - * \param hint_flags RTP flags as defined in <gpac/ietf.h> - * \param PayloadID RTP payload ID, can be set to 0 for auto compute - * \param copy_media if set, media is copied inside the hint samples, otherwise only referenced from the media track - * \param InterleaveGroupID sets the group ID of this track for interleaving - same semantics as in gf_isom_set_track_interleaving_group - * \param InterleaveGroupPriority sets the group priority of this track for interleaving - same semantics as in gf_isom_set_track_priority_in_group - * \param e output error code if any - * \return the hinter object +\param isom_file the target ISOBMF file +\param trackNumber the track to hint +\param Path_MTU max RTP packet size (excluding IP/UDP/IP headers) +\param max_ptime max packet duration in RTP timescale, can be set to 0 for auto compute +\param default_rtp_rate RTP rate for the track, can be set to 0 for auto compute +\param hint_flags RTP flags as defined in <gpac/ietf.h> +\param PayloadID RTP payload ID, can be set to 0 for auto compute +\param copy_media if set, media is copied inside the hint samples, otherwise only referenced from the media track +\param InterleaveGroupID sets the group ID of this track for interleaving - same semantics as in gf_isom_set_track_interleaving_group +\param InterleaveGroupPriority sets the group priority of this track for interleaving - same semantics as in gf_isom_set_track_priority_in_group +\param e output error code if any +\return the hinter object */ -GF_RTPHinter *gf_hinter_track_new(GF_ISOFile *file, u32 track, +GF_RTPHinter *gf_hinter_track_new(GF_ISOFile *isom_file, u32 trackNumber, u32 Path_MTU, u32 max_ptime, u32 default_rtp_rate, u32 hint_flags, u8 PayloadID, Bool copy_media, u32 InterleaveGroupID, u8 InterleaveGroupPriority, GF_Err *e); /*! Delete the track hinter - \param tkHinter track hinter object +\param tkHinter track hinter object */ void gf_hinter_track_del(GF_RTPHinter *tkHinter); -/*! +/*! hints all samples in the media track - \param tkHinter track hinter object - \return error if any +\param tkHinter track hinter object +\return error if any */ GF_Err gf_hinter_track_process(GF_RTPHinter *tkHinter); -/*! +/*! Gets media bandwidth in kbps - \param tkHinter track hinter object - \return media bandwidth in kbps +\param tkHinter track hinter object +\return media bandwidth in kbps */ u32 gf_hinter_track_get_bandwidth(GF_RTPHinter *tkHinter); +/*! + Force file to use no random offsets for sequence number and time, if supported by server +\param tkHinter track hinter object +\return error if any +*/ +GF_Err gf_hinter_track_force_no_offsets(GF_RTPHinter *tkHinter); + /*! Gets track hinter flags - \param tkHinter track hinter object - \return hint flags for this object +\param tkHinter track hinter object +\return hint flags for this object */ u32 gf_hinter_track_get_flags(GF_RTPHinter *tkHinter); -/*! +/*! Gets rtp payload name - \param tkHinter track hinter object - \param payloadName static buffer for retrieval, minimum 30 bytes +\param tkHinter track hinter object +\param payloadName static buffer for retrieval, minimum 30 bytes */ void gf_hinter_track_get_payload_name(GF_RTPHinter *tkHinter, char *payloadName); /*! Finalizes hinting process for the track (setup flags, write SDP for RTP, ...) - \param tkHinter track hinter object - \param AddSystemInfo if true, systems info are duplicated in the SDP (decoder cfg, PL IDs ..) - \return error if any +\param tkHinter track hinter object +\param AddSystemInfo if true, systems info are duplicated in the SDP (decoder cfg, PL IDs ..) +\return error if any */ GF_Err gf_hinter_track_finalize(GF_RTPHinter *tkHinter, Bool AddSystemInfo); /*! - * SDP IOD Profile - * \hideinitializer +SDP IOD Profile +\hideinitializer */ typedef enum { @@ -1078,20 +1279,21 @@ typedef enum /*! Finalizes hinting process for the file (setup flags, write SDP for RTP, ...) - \param file target ISOBMF file - \param IOD_Profile the IOD profile to use for SDP - \param bandwidth total bandwidth in kbps of all hinted tracks, 0 means no bandwidth info at session level +\param isom_file target ISOBMF file +\param IOD_Profile the IOD profile to use for SDP +\param bandwidth total bandwidth in kbps of all hinted tracks, 0 means no bandwidth info at session level +\return error if any */ -GF_Err gf_hinter_finalize(GF_ISOFile *file, GF_SDP_IODProfile IOD_Profile, u32 bandwidth); +GF_Err gf_hinter_finalize(GF_ISOFile *isom_file, GF_SDP_IODProfile IOD_Profile, u32 bandwidth); /*! Check if the given data fits in an ESD url - \param data data to be encoded - \param data_size size of data to be encoded - \param streamType systems stream type needed to signal data mime-type (OD, BIFS or any) - \return GF_TRUE if the encoded data fits in an ESD url +\param data data to be encoded +\param data_size size of data to be encoded +\param streamType systems stream type needed to signal data mime-type (OD, BIFS or any) +\return GF_TRUE if the encoded data fits in an ESD url */ -Bool gf_hinter_can_embbed_data(char *data, u32 data_size, u32 streamType); +Bool gf_hinter_can_embbed_data(u8 *data, u32 data_size, u32 streamType); #endif /*GPAC_DISABLE_ISOM_HINTING*/ @@ -1099,12 +1301,13 @@ Bool gf_hinter_can_embbed_data(char *data, u32 data_size, u32 streamType); /*! - * \addtogroup msaf_grp LASeR SAF creation - * \ingroup media_grp - * \brief LASeR SAF multiplexing. - * - *This section documents functions for LASeR SAF multiplexing. - * @{ +\addtogroup msaf_grp LASeR SAF creation +\ingroup media_grp +\brief LASeR SAF multiplexing. + +This section documents functions for LASeR SAF multiplexing. + +@{ */ @@ -1112,63 +1315,63 @@ Bool gf_hinter_can_embbed_data(char *data, u32 data_size, u32 streamType); typedef struct __saf_muxer GF_SAFMuxer; /*! Creates a new SAF Multiplexer - \return the SAF multiplexer object +\return the SAF multiplexer object */ GF_SAFMuxer *gf_saf_mux_new(); -/*! +/*! SAF Multiplexer destructor - \param mux the SAF multiplexer object +\param mux the SAF multiplexer object */ void gf_saf_mux_del(GF_SAFMuxer *mux); /*! Adds a new stream in the SAF multiplex - \param mux the SAF multiplexer object - \param stream_id ID of the SAF stream to create - \param ts_res timestamp resolution for AUs in this stream - \param buffersize_db size of decoding buffer in bytes - \param stream_type MPEG-4 systems stream type of this stream - \param object_type MPEG-4 systems object type indication of this stream - \param mime_type MIME type for this stream, NULL if unknown - \param dsi Decoder specific info for this stream - \param dsi_len specific info size for this stream - \param remote_url URL of the SAF stream if not embedded in the multiplex - \return error if any +\param mux the SAF multiplexer object +\param stream_id ID of the SAF stream to create +\param ts_res timestamp resolution for AUs in this stream +\param buffersize_db size of decoding buffer in bytes +\param stream_type MPEG-4 systems stream type of this stream +\param object_type MPEG-4 systems object type indication of this stream +\param mime_type MIME type for this stream, NULL if unknown +\param dsi Decoder specific info for this stream +\param dsi_len specific info size for this stream +\param remote_url URL of the SAF stream if not embedded in the multiplex +\return error if any */ GF_Err gf_saf_mux_stream_add(GF_SAFMuxer *mux, u32 stream_id, u32 ts_res, u32 buffersize_db, u8 stream_type, u8 object_type, char *mime_type, char *dsi, u32 dsi_len, char *remote_url); -/*! - Removes a stream from the SAF multiplex - \param mux the SAF multiplexer object - \param stream_id ID of the SAF stream to remove - \return error if any - */ -GF_Err gf_saf_mux_stream_rem(GF_SAFMuxer *mux, u32 stream_id); -/*! +/*! adds an AU to the given Warning, AU data will be freed by the multiplexer. AUs are NOT re-sorted by CTS, in order to enable audio interleaving. - \param mux the SAF multiplexer object - \param stream_id ID of the SAF stream to remove - \param CTS composition timestamp of the AU - \param data payload of the AU - \param data_len payload size of the AU - \param is_rap set to GF_TRUE to signal a random access point - \return error if any +\param mux the SAF multiplexer object +\param stream_id ID of the SAF stream to remove +\param CTS composition timestamp of the AU +\param data payload of the AU +\param data_len payload size of the AU +\param is_rap set to GF_TRUE to signal a random access point +\return error if any */ GF_Err gf_saf_mux_add_au(GF_SAFMuxer *mux, u32 stream_id, u32 CTS, char *data, u32 data_len, Bool is_rap); /*! Gets the content of the multiplexer for the given time. - \param mux the SAF multiplexer object - \param time_ms target mux time in ms - \param force_end_of_session if set to GF_TRUE, this flushes the SAF Session - no more operations will be allowed on the muxer - \param out_data output SAF data - \param out_size output SAF data size - \return error if any +\param mux the SAF multiplexer object +\param time_ms target mux time in ms +\param force_end_of_session if set to GF_TRUE, this flushes the SAF Session - no more operations will be allowed on the muxer +\param out_data output SAF data +\param out_size output SAF data size +\return error if any */ -GF_Err gf_saf_mux_for_time(GF_SAFMuxer *mux, u32 time_ms, Bool force_end_of_session, char **out_data, u32 *out_size); +GF_Err gf_saf_mux_for_time(GF_SAFMuxer *mux, u32 time_ms, Bool force_end_of_session, u8 **out_data, u32 *out_size); +/*! + Gets timescale and TS increment from double FPS value. +\param fps the target fps +\param timescale output timescale value +\param ts_inc output timestamp increment value +*/ +void gf_media_get_video_timing(Double fps, u32 *timescale, u32 *ts_inc); #ifdef __cplusplus } diff --git a/include/gpac/mediaobject.h b/include/gpac/mediaobject.h index db4261b..d617609 100644 --- a/include/gpac/mediaobject.h +++ b/include/gpac/mediaobject.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / Stream Management sub-project @@ -33,86 +33,133 @@ extern "C" { #endif /*! - * \file <gpac/mediaobject.h> - * \brief Interface between compositor and decoding engine for media data access. +\file <gpac/mediaobject.h> +\brief Interface between compositor and decoding engine for media data access. */ /*! - *\addtogroup mobj_grp MediaObject - *\ingroup playback_grp - *\brief Interface between compositor and decoding engine for media data access. - * - *This section documents the API betwwen the compositor of GPAC and the decoding engine (terminal) - * @{ +\addtogroup mobj_grp MediaObject +\ingroup playback_grp +\brief Interface between compositor and decoding engine for media data access. + +This section documents the API betwwen the compositor of GPAC and the decoding engine (terminal) + +@{ */ +#include <gpac/filters.h> #include <gpac/scenegraph_vrml.h> -#include <gpac/modules/codec.h> -/* - Media Object +/*! Media Object opaque handler for all natural media objects (audio, video, image) so that compositor and systems engine are not too tied up. NOTE: the media object location relies on the node parent graph (this is to deal with namespaces in OD framework) therefore it is the task of the media management app to setup clear links between the scene graph and its ressources (but this is not mandatory, cf URLs in VRML ) - - TODO - add interface for shape coding positioning in mediaObject and in the decoder API */ - typedef struct _mediaobj GF_MediaObject; -/*locate media object related to the given node - url designes the object to find - returns NULL if +/*! locates media object related to the given node - url designes the object to find - returns NULL if URL cannot be handled - note that until the mediaObject.isInit member is true, the media object is not valid -(and could actually never be) */ +(and could actually never be) +\param node node querying the URL +\param url the URL to open +\param lock_timelines if GF_TRUE, forces media timelines in remote URL to be locked to the node timeline +\param force_new_res if GF_TRUE, forces loading of a new resource +\return new media object or NULL of URL not valid + */ GF_MediaObject *gf_mo_register(GF_Node *node, MFURL *url, Bool lock_timelines, Bool force_new_res); -/*unregister the node from the media object*/ +/*! unregisters the node from the media object +\param node node querying the URL +\param mo the media object to unregister - can be destroyed during this function call +*/ void gf_mo_unregister(GF_Node *node, GF_MediaObject *mo); -/*opens media object*/ +/*! opens media object +\param mo the target media object +\param clipBegin the playback start time in seconds +\param clipEnd the playback end time in seconds +\param can_loop if GF_TRUE, indicates the playback can be looped +*/ void gf_mo_play(GF_MediaObject *mo, Double clipBegin, Double clipEnd, Bool can_loop); -/*stops media object - video memory is not reset, last frame is kept*/ -Bool gf_mo_stop(GF_MediaObject *mo); -/*restarts media object - shall be used for all looping media instead of stop/play for mediaControl -to restart appropriated objects*/ -void gf_mo_restart(GF_MediaObject *mo); +/*! stops media object. Object is set to null if stop triggers a removal. +If not removed, video memory is not reset, last frame is kept +\param mo the target media object +*/ +void gf_mo_stop(GF_MediaObject **mo); + +/*! restarts media object - shall be used for all looping media instead of stop/play for mediaControl +to restart appropriated objects +\param mo the target media object +*/ +void gf_mo_restart(GF_MediaObject *mo); +/*! pauses a media object +\param mo the target media object +*/ void gf_mo_pause(GF_MediaObject *mo); +/*! resumes a media object +\param mo the target media object +*/ void gf_mo_resume(GF_MediaObject *mo); -/* +/*! Note on mediaControl: mediaControl is the media management app responsability, therefore is hidden from the rendering app. Since MediaControl overrides default settings of the node (speed and loop) you must use the gf_mo_get_speed and gf_mo_get_loop in order to know whether the related field applies or not */ -/*set speed of media - speed is not always applied, depending on media control settings. -NOTE: audio pitching is the responsability of the rendering app*/ +/*! sets speed of media - speed is not always applied, depending on media control settings. +NOTE: audio pitching is the responsability of the rendering app +\param mo the target media object +\param speed the playback speed to set +*/ void gf_mo_set_speed(GF_MediaObject *mo, Fixed speed); -/*returns current speed of media - in_speed is the speed of the media as set in the node (MovieTexture, -AudioClip and AudioSource) - the return value is the real speed of the media as overloaded by mediaControl if any*/ +/*! gets current speed of media +\param mo the target media object +\param in_speed is the speed of the media as set in the node (MovieTexture, AudioClip and AudioSource) +\return the real speed of the media as overloaded by mediaControl if any*/ Fixed gf_mo_get_speed(GF_MediaObject *mo, Fixed in_speed); -/*returns looping flag of media - in_loop is the looping flag of the media as set in the node (MovieTexture, -AudioClip) - the return value is the real loop flag of the media as overloaded by mediaControl if any*/ +/*! gets current looping flag of media +\param mo the target media object +\param in_loop is the looping flag of the media as set in the node (MovieTexture, AudioClip) +\return the real loop flag of the media as overloaded by mediaControl if any*/ Bool gf_mo_get_loop(GF_MediaObject *mo, Bool in_loop); -/*returns media object duration*/ +/*! get media object duration +\param mo the target media object +\return media object duration*/ Double gf_mo_get_duration(GF_MediaObject *mo); -/*returns whether the object should be deactivated (stop) or not - this checks object status as well as -mediaControl status */ +/*! checks if object should be deactivated (stop) or not - this checks object status as well as +mediaControl status +\param mo the target media object +\return GF_TRUE if should be deactivated*/ Bool gf_mo_should_deactivate(GF_MediaObject *mo); -/*checks whether the target object is changed - you MUST use this in order to detect url changes*/ +/*! checks whether the target object is changed - you MUST use this in order to detect url changes +\param mo the target media object +\param url URL to compare to object current URL +\return GF_TRUE if URL changed +*/ Bool gf_mo_url_changed(GF_MediaObject *mo, MFURL *url); -/*checks whether the target object is changed - you MUST use this in order to detect url changes*/ -Bool gf_mo_is_raw_memory(GF_MediaObject *mo); -GF_Err gf_mo_get_raw_image_planes(GF_MediaObject *mo, u8 **pY_or_RGB, u8 **pU, u8 **pV, u32 *stride_luma_rgb, u32 *stride_chroma); - -/*returns min frame duration for his object or 0 if unknown*/ +/*! gets minimum frame duration for an object +\param mo the target media object +\return min frame duration or 0 if unknown*/ u32 gf_mo_get_min_frame_dur(GF_MediaObject *mo); +/*! map a timestamp to the object clock +\param mo the target media object +\param ts a timestamp in media object clock base +\return the timestamp in system time base +*/ +u32 gf_mo_map_timestamp_to_sys_clock(GF_MediaObject *mo, u32 ts); +/*! checks if object is buffering +\param mo the target media object +\return GF_TRUE if object is buffering*/ +Bool gf_mo_is_buffering(GF_MediaObject *mo); +/*! frame fetch mode for media object*/ typedef enum { //never resync the content of the decoded media buffer (used fo audio) @@ -125,73 +172,134 @@ typedef enum GF_MO_FETCH_PAUSED } GF_MOFetchMode; -/*fetch media data - +/*! fetches media data +\param mo the target media object +\param resync resync mode for fetch operation +\param upload_time_ms average time needed to push frame on GPU +\param eos set to end of stream status of the media +\param timestamp set to the frmae timestamp in object time +\param size set to the frame data size +\param ms_until_pres set to the number of milliseconds until presentation is due +\param ms_until_next set to the number of milliseconds until presentation of next frame is due +\param outFrame set to the associated frame interface object if any +\param planar_size set to the planar size of audio data, or 0 +\return frame data */ -char *gf_mo_fetch_data(GF_MediaObject *mo, GF_MOFetchMode resync, u32 upload_time_ms, Bool *eos, u32 *timestamp, u32 *size, s32 *ms_until_pres, s32 *ms_until_next, GF_MediaDecoderFrame **outFrame); +u8 *gf_mo_fetch_data(GF_MediaObject *mo, GF_MOFetchMode resync, u32 upload_time_ms, Bool *eos, u32 *timestamp, u32 *size, s32 *ms_until_pres, s32 *ms_until_next, GF_FilterFrameInterface **outFrame, u32 *planar_size); -/*release given amount of media data - nb_bytes is used for audio - drop_mode can take the following values: +/*! releases given amount of media data - nb_bytes is used for audio +\param mo the target media object +\param nb_bytes number of audio bytes to remove (ignored for other media types) +\param drop_mode can take the following values: -1: do not drop 0: do not force drop: the unlocked frame it will be dropped based on object time (typically video) 1: force drop : the unlocked frame will be dropped if all bytes are consumed (typically audio) 2: the frame will be stated as a discraded frame */ void gf_mo_release_data(GF_MediaObject *mo, u32 nb_bytes, s32 drop_mode); -/*get media time*/ -void gf_mo_get_media_time(GF_MediaObject *mo, u32 *media_time, u32 *media_dur); -/*get object clock*/ +/*! gets object clock +\param mo the target media object +\param obj_time set to the object current time in milliseconds in object time base +*/ void gf_mo_get_object_time(GF_MediaObject *mo, u32 *obj_time); -/*returns mute flag of media - if muted the media shouldn't be displayed*/ +/*! checks if object is muted. Muted media shouldn't be displayed +\param mo the target media object +\return GF_TRUE if muted +*/ Bool gf_mo_is_muted(GF_MediaObject *mo); -/*returns end of stream state*/ +/*! checks if a media object is done +\param mo the target media object +\return GF_TRUE if end of stream*/ Bool gf_mo_is_done(GF_MediaObject *mo); -/*resyncs clock - only audio objects are allowed to use this*/ +/*! adjusts clock sync (only audio objects are allowed to use this) +\param mo the target media object +\param ms_drift drift in milliseconds between object clock and actual rendering time +*/ void gf_mo_adjust_clock(GF_MediaObject *mo, s32 ms_drift); -u32 gf_mo_get_last_frame_time(GF_MediaObject *mo); - +/*! checks if a media object is started +\param mo the target media object +\return GF_TRUE if started*/ +Bool gf_mo_is_started(GF_MediaObject *mo); + +/*! gets visual information of a media object +\param mo the target media object +\param width set to width in pixels +\param height set to height in pixels +\param stride set to stride in bytes for visual objects with data frame, 0 if unknown +\param pixel_ar set to the pixel aspect ratio as \code (PAR_NUM<<16)|PAR_DEN \endcode +\param pixelFormat set to the pixel format of the video +\param is_flipped set to GF_TRUE if the pixels are vertically flipped (happens when reading back openGL textures) +\return GF_TRUE if success*/ Bool gf_mo_get_visual_info(GF_MediaObject *mo, u32 *width, u32 *height, u32 *stride, u32 *pixel_ar, u32 *pixelFormat, Bool *is_flipped); -void gf_mo_get_nb_views(GF_MediaObject *mo, int * nb_views); - -void gf_mo_get_nb_layers(GF_MediaObject *mo, int * nb_layers); - -Bool gf_mo_get_audio_info(GF_MediaObject *mo, u32 *sample_rate, u32 *bits_per_sample, u32 *num_channels, u32 *channel_config); +/*! gets number of views for 3D video object +\param mo the target media object +\param nb_views set to the number of views in the object +*/ +void gf_mo_get_nb_views(GF_MediaObject *mo, u32 *nb_views); +/*! gets number of layers for 2D scalable video object +\param mo the target media object +\param nb_layers set to the number of layers in the object +*/ +void gf_mo_get_nb_layers(GF_MediaObject *mo, u32 *nb_layers); +/*! gets visual information of a media object +\param mo the target media object +\param sample_rate set to the sampling frequency of the object +\param afmt set to the decoded PCM audio format +\param num_channels set to the number of channels +\param channel_config set to the channel configuration +\param forced_layout set to GF_TRUE if the channel layout is forced (prevents recomputing the layout for mono/stereo setups) +\return GF_TRUE if success*/ +Bool gf_mo_get_audio_info(GF_MediaObject *mo, u32 *sample_rate, u32 *afmt, u32 *num_channels, u64 *channel_config, Bool *forced_layout); + +/*! gets current playback speed of a media object +\param mo the target media object +\return the playback speed*/ Fixed gf_mo_get_current_speed(GF_MediaObject *mo); -/*checks if the service associated withthis object has an audio stream -returns 0 if no audio is associated, 1 if there is an audio object associated, 2 if the service is not yet ready (not connected)*/ +/*! checks if the service associated withthis object has an audio stream +\param mo the target media object +\return 0 if no audio is associated, 1 if there is an audio object associated, 2 if the service is not yet ready (not connected)*/ u32 gf_mo_has_audio(GF_MediaObject *mo); -/*checks if the service associated withthis object has an audio stream*/ -Bool gf_mo_is_private_media(GF_MediaObject *mo); - -/*set destination window for harware codecs directly outputing to video out - returns 1 if video size has changed*/ -Bool gf_mo_set_position(GF_MediaObject *mo, GF_Window *src, GF_Window *dst); - -s32 gf_mo_get_clock_drift(GF_MediaObject *mo); - -enum +/*! media object user flags*/ +typedef enum { - /*this is set to 0 by the OD manager whenever a change occur in the media (w/h change, SR change, etc) - as a hint for the compositor*/ - GF_MO_IS_INIT = (1<<1), /*used by animation stream to remove TEXT from display upon delete and URL change*/ - GF_MO_DISPLAY_REMOVE = (1<<2), -}; - -u32 gf_mo_get_flags(GF_MediaObject *mo); -void gf_mo_set_flag(GF_MediaObject *mo, u32 flag, Bool set_on); - -/*loads a new resource as indicated in the xlink:href attribute of the node. If this points to a fragment -of the current document, returns NULL. This will automatically trigger a play request on the resource*/ + GF_MO_DISPLAY_REMOVE = (1<<1), + /*used when resyncing a stream (droping late frames)*/ + GF_MO_IN_RESYNC = (1<<2), +} GF_MOUserFlags; +/*! sets flags on a media object +\param mo the target media object +\param flag the flag(s) to adjust +\param set_on if GF_TRUE, set the flag(s), otherwise removes the flag(s) +*/ +void gf_mo_set_flag(GF_MediaObject *mo, GF_MOUserFlags flag, Bool set_on); + +/*! loads a new resource as indicated in the xlink:href attribute of the node. If this points to a fragment +of the current document, returns NULL. This will automatically trigger a play request on the resource +\param node node with xlink:href attribute to a media resource +\param primary_resource indicates if this is a primary resource (foreign object in SVG) +\param clipBegin the playback start time in seconds +\param clipEnd the playback end time in seconds +\return the media object for this resource +*/ GF_MediaObject *gf_mo_load_xlink_resource(GF_Node *node, Bool primary_resource, Double clipBegin, Double clipEnd); +/*! unloads a media object associated with a given node through xlink:href +\param node node with xlink:href attribute to the media resource +\param mo the target media object +*/ void gf_mo_unload_xlink_resource(GF_Node *node, GF_MediaObject *mo); -/*returns scene graph associated with a scene/document object, or NULL if wrong type or not loaded*/ +/*! gets the scene graph associated with a scene/document object +\param mo the target media object +\return the scene graph, or NULL if wrong type or not loaded +*/ GF_SceneGraph *gf_mo_get_scenegraph(GF_MediaObject *mo); - +/*! VR and SRD (Spatial Relationship Description) information of media object*/ typedef struct { u32 vr_type; @@ -208,32 +316,98 @@ typedef struct u32 scene_width; u32 scene_height; + Bool has_full_coverage; Bool is_tiled_srd; } GF_MediaObjectVRInfo; -//get SRD and VR info for this object. Returns FALSE if no VR and no SRD info +/*! gets SRD and VR info for a media object. Returns FALSE if no VR and no SRD info +\param mo the target media object +\param vr_info set to the VR and SRD info of the media object +\return GF_TRUE if SRD info present +*/ Bool gf_mo_get_srd_info(GF_MediaObject *mo, GF_MediaObjectVRInfo *vr_info); -/*sets quality degradation hint for this media object - quality_rank is between 0 (max quality) and 100 (worst quality)*/ +/*! sets quality degradation hint for a media object +\param mo the target media object +\param quality_degradation quality hint value between 0 (max quality) and 100 (worst quality) +*/ void gf_mo_hint_quality_degradation(GF_MediaObject *mo, u32 quality_degradation); -/*sets visible rectangle for the object - only used in 360 videos for now*/ +/*! sets visible rectangle for a media object, only used in 360 videos for now +\param mo the target media object +\param min_x minimum horizontal coordinate of visible region, in pixel in the media frame (0 being left column) +\param max_x maximum horizontal coordinate of visible region, in pixel in the media frame (width being right column) +\param min_y minimum vertical coordinate of visible region, in pixel in the media frame (0 being top row) +\param max_y maximum vertical coordinate of visible region, in pixel in the media frame (height being bottom row) +*/ void gf_mo_hint_visible_rect(GF_MediaObject *mo, u32 min_x, u32 max_x, u32 min_y, u32 max_y); +/*! sets gaze position in a media object, only used in 360 videos for now +\param mo the target media object +\param gaze_x minimum horizontal coordinate of visible region, in pixel in the media frame (0 being left column, width being right column) +\param gaze_y maximum horizontal coordinate of visible region, in pixel in the media frame (0 being top row, bottom row) +*/ +void gf_mo_hint_gaze(GF_MediaObject *mo, u32 gaze_x, u32 gaze_y); + #include <gpac/scenegraph_svg.h> +/*! destroys a media object +\param mo the target media object +*/ void gf_mo_del(GF_MediaObject *mo); -GF_DOMEventTarget *gf_mo_event_target_add_node(GF_MediaObject *mo, GF_Node *n); -GF_Err gf_mo_event_target_remove(GF_MediaObject *mo, GF_DOMEventTarget *target); -GF_Err gf_mo_event_target_remove_by_node(GF_MediaObject *mo, GF_Node *node); -GF_Err gf_mo_event_target_remove_by_index(GF_MediaObject *mo, u32 i); -GF_DOMEventTarget *gf_mo_event_target_get(GF_MediaObject *mo, u32 i); -void gf_mo_event_target_reset(GF_MediaObject *mo); -u32 gf_mo_event_target_count(GF_MediaObject *mo); -s32 gf_mo_event_target_find_by_node(GF_MediaObject *mo, GF_Node *node); -GF_Node *gf_mo_event_target_enum_node(GF_MediaObject *mo, u32 *i); - -GF_Node *gf_event_target_get_node(GF_DOMEventTarget *target); +/*! adds event target node to a media object +\param mo the target media object +\param node the event target node +\return a DOM event target interface object +*/ +GF_DOMEventTarget *gf_mo_event_target_add_node(GF_MediaObject *mo, GF_Node *node); +/*! removes event target node from a media object +\param mo the target media object +\param node the event target node +\return error if any +*/ +GF_Err gf_mo_event_target_remove_by_node(GF_MediaObject *mo, GF_Node *node); +/*! counts number of event targets associated with a media object +\param mo the target media object +\return number of event targets +*/ +u32 gf_mo_event_target_count(GF_MediaObject *mo); + +/*! removes event target node from a media object by index +\param mo the target media object +\param index 0-based index of the event target to remove +\return error if any +*/ +GF_Err gf_mo_event_target_remove_by_index(GF_MediaObject *mo, u32 index); +/*! gets event target node of a media object by index +\param mo the target media object +\param index 0-based index of the event target to get +\return a DOM event target interface object +*/ +GF_DOMEventTarget *gf_mo_event_target_get(GF_MediaObject *mo, u32 index); + +/*! resets all event targets of a media object +\param mo the target media object +*/ +void gf_mo_event_target_reset(GF_MediaObject *mo); + +/*! finds an even target interface of a media object associated to a given node +\param mo the target media object +\param node the node associated with the event target +\return the index of the event target interface object +*/ +s32 gf_mo_event_target_find_by_node(GF_MediaObject *mo, GF_Node *node); +/*! enumerates event target nodes associated with a media object +\param mo the target media object +\param i current index to query, incremented upon function return +\return an event target node +*/ +GF_Node *gf_mo_event_target_enum_node(GF_MediaObject *mo, u32 *i); +/*! gets the event target node of an event target interface +\param target the target event target interface +\return the associated node, NULL if error +*/ +GF_Node *gf_event_target_get_node(GF_DOMEventTarget *target); /*! @} */ diff --git a/include/gpac/module.h b/include/gpac/module.h index 9aff59f..7a75354 100644 --- a/include/gpac/module.h +++ b/include/gpac/module.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -31,48 +31,47 @@ extern "C" { #endif /*! - * \file <gpac/module.h> - * \brief plugable dynamic module. +\file <gpac/module.h> +\brief plugable dynamic module. */ /*! - * \ingroup mods_grp - * \brief Plugable Dynamic Modules - * - *This section documents the plugable module functions of the GPAC framework. - *A module is a dynamic/shared library providing one or several interfaces to the GPAC framework. - *A module cannot provide several interfaces of the same type. Each module must export the following functions: +\ingroup mods_grp +\brief Plugable Dynamic Modules + +This section documents the plugable module functions of the GPAC framework. +A module is a dynamic/shared library providing one or several interfaces to the GPAC framework. +A module cannot provide several interfaces of the same type. Each module must export the following functions: \code - * u32 *QueryInterfaces(u32 interface_type); + u32 *QueryInterfaces(u32 interface_type); \endcode - * This function is used to query supported interfaces. It returns a zero-terminated array of supported interface types.\n + This function is used to query supported interfaces. It returns a zero-terminated array of supported interface types.\n \code GF_BaseInterface *LoadInterface(u32 interface_type); \endcode - * This function is used to load an interface. It returns the interface object, NULL if error.\n + This function is used to load an interface. It returns the interface object, NULL if error.\n \code void ShutdownInterface(GF_BaseInterface *interface); \endcode - *This function is used to destroy an interface.\n\n - *Each interface must begin with the interface macro in order to be type-casted to the base interface structure. + This function is used to destroy an interface.\n\n + Each interface must begin with the interface macro in order to be type-casted to the base interface structure. \code struct { GF_DECL_MODULE_INTERFACE extensions; }; \endcode - * @{ + +@{ */ #include <gpac/config_file.h> -typedef struct __tag_mod_man GF_ModuleManager; - /*! - *\brief common module interface - *\hideinitializer - * - *This is the module interface declaration macro. It must be placed first in an interface structure declaration. +\brief common module interface +\hideinitializer + +This is the module interface declaration macro. It must be placed first in an interface structure declaration. */ #define GF_DECL_MODULE_INTERFACE \ u32 InterfaceType; \ @@ -81,7 +80,7 @@ typedef struct __tag_mod_man GF_ModuleManager; void *HPLUG; \ /*! - *\brief Base Interface +\brief Base Interface * *This structure represent a base interface, e.g. the minimal interface declaration without functionalities. Each interface is *type-casted to this structure and shall always be checked against its interface type. API Versioning is taken care of in the @@ -93,16 +92,16 @@ typedef struct } GF_BaseInterface; /*! - *\brief module interface registration - *\hideinitializer - * - *This is the module interface registration macro. A module must call this macro whenever creating a new interface. - *- \e _ifce: interface being registered - *- \e _ifce_type: the four character code defining the interface type. - *- \e _ifce_name: a printable string giving the interface name (const char *). - *- \e _ifce_author: a printable string giving the author name (const char *). - *\n - *This is a sample GPAC codec interface declaration: +\brief module interface registration +\hideinitializer + +This is the module interface registration macro. A module must call this macro whenever creating a new interface. + - \e _ifce: interface being registered + - \e _ifce_type: the four character code defining the interface type. + - \e _ifce_name: a printable string giving the interface name (const char *). + - \e _ifce_author: a printable string giving the author name (const char *). + \n + This is a sample GPAC codec interface declaration: \code GF_BaseInterface *MyDecoderInterfaceLoad() { GF_MediaDecoder *ifce; @@ -119,9 +118,8 @@ GF_BaseInterface *MyDecoderInterfaceLoad() { _ifce->author_name = _ifce_author ? _ifce_author : "gpac distribution"; \ /*! - *\brief module interface function export. Modules that can be compiled in libgpac rather than in sharde libraries shall use this macro to declare the 3 exported functions - *\hideinitializer - * +\brief static module declaration + \hideinitializer */ #ifdef GPAC_STATIC_MODULES #define GPAC_MODULE_EXPORT static @@ -130,22 +128,27 @@ GF_BaseInterface *MyDecoderInterfaceLoad() { #endif /*! - *\brief Interface Registry - * - *This structure represent a base interface loader, when not using dynamic / shared libraries +\brief Interface Registry + +This structure represent a base interface loader, when not using dynamic / shared libraries */ typedef struct { + /*! name of interface*/ const char *name; + /*! query interface callback*/ const u32 *(*QueryInterfaces) (); + /*! load interface callback*/ GF_BaseInterface * (*LoadInterface) (u32 InterfaceType); + /*! shutdown interface callback*/ void (*ShutdownInterface) (GF_BaseInterface *interface_obj); } GF_InterfaceRegister; /*! - *\brief module interface function export. Modules that can be compiled in libgpac rather than in sharde libraries shall use this macro to declare the 3 exported functions - *\hideinitializer - * +\brief module interface +\hideinitializer + +Module interface function export. Modules that can be compiled in libgpac rather than in sharde libraries shall use this macro to declare the 3 exported functions */ #ifdef GPAC_STATIC_MODULES @@ -164,72 +167,22 @@ typedef struct #else #define GPAC_MODULE_STATIC_DECLARATION(__name) #endif -/*! - *\brief module manager construtcor - * - *Constructs a module manager object. - *\param directory absolute path to the directory where the manager shall look for modules - *\param cfgFile GPAC configuration file handle. If this is NULL, the modules won't be able to share the configuration - *file with the rest of the GPAC framework. - *\return the module manager object -*/ -GF_ModuleManager *gf_modules_new(const char *directory, GF_Config *cfgFile); /*! - *\brief module manager destructor - * - *Destroys the module manager - *\param pm the module manager - */ -void gf_modules_del(GF_ModuleManager *pm); +\brief load a static module given its interface function -/*! - *\brief load a static module given its interface function - * - *\param pm the module manager - *\param register_module the register interface function +\param register_module the register interface function +\return error if any */ -GF_Err gf_module_load_static(GF_ModuleManager *pm, GF_InterfaceRegister *(*register_module)()); +GF_Err gf_module_load_static(GF_InterfaceRegister *(*register_module)()); /*! - *\brief declare a module for loading - * - * When using GPAC as a static library, if GPAC_MODULE_CUSTOM_LOAD is - * defined, this macro can be used with GF_MODULE_STATIC_DECLARE() and - * gf_module_refresh() to load individual modules. - * - * It's first needed to call GF_MODULE_STATIC_DECLARE() with the name - * of the module you need to load outside of any function. This macro - * will declare the prototype of the module registration function so - * it should only be used outside functions. - * - * Then in your GPAC initialization code, you need to call - * GF_MODULE_LOAD_STATIC() with your GPAC module manager and the - * module name. - * - * Finally, you'll need to call gf_modules_refresh() with your module - * manager. - * - * \code - * GF_MODULE_STATIC_DECLARE(aac_in); - * GF_MODULE_STATIC_DECLARE(audio_filter); - * GF_MODULE_STATIC_DECLARE(ffmpeg); - * ... - * - * void prepare() { - * GF_User user; - * ... - * user.modules = gf_modules_new("/data/gpac/modules", user.config); - * - * GF_MODULE_LOAD_STATIC(user.modules, aac_in); - * GF_MODULE_LOAD_STATIC(user.modules, audio_filter); - * GF_MODULE_LOAD_STATIC(user.modules, ffmpeg); - * ... - * gf_modules_refresh(user.modules); - * ... - * } - * \endcode - * \see GF_MODULE_LOAD_STATIC() gf_modules_refresh() +\brief declare a module for loading + +When using GPAC as a static library, if GPAC_MODULE_CUSTOM_LOAD is +defined, this macro can be used with GF_MODULE_STATIC_DECLARE() and +gf_module_refresh() to load individual modules. +\warning function load_all_modules You will need to patch src/utils/module.c */ #ifdef __cplusplus #define GF_MODULE_STATIC_DECLARE(_name) \ @@ -239,128 +192,103 @@ GF_Err gf_module_load_static(GF_ModuleManager *pm, GF_InterfaceRegister *(*regis GF_InterfaceRegister *gf_register_module_##_name() #endif /*! - *\brief load a static module given its name - * - * Use this function to load a statically compiled - * module. GF_MODULE_STATIC_DECLARE() should be called before and - * gf_modules_refresh() after loading all the needed modules. - * - *\param _pm the module manager - *\param _name the module name - *\see GF_MODULE_STATIC_DECLARE() gf_modules_refresh() - */ -#define GF_MODULE_LOAD_STATIC(_pm, _name) \ - gf_module_load_static(_pm,gf_register_module_##_name) +\brief load a static module given its name +Use this function to load a statically compiled module. GF_MODULE_STATIC_DECLARE() should be called before and gf_modules_refresh() after loading all the needed modules. -/*! - *\brief refreshes modules - * - *Refreshes all modules in the manager directory and load unloaded ones - *\param pm the module manager - *\return the number of loaded modules +\param _name the module name +\see GF_MODULE_STATIC_DECLARE() gf_modules_refresh() */ -u32 gf_modules_refresh(GF_ModuleManager *pm); +#define GF_MODULE_LOAD_STATIC(_name) \ + gf_module_load_static(gf_register_module_##_name) /*! - *\brief get module count - * - *Gets the number of modules found in the manager directory - *\param pm the module manager - *\return the number of loaded modules +\brief get module count + +Gets the number of modules found in the manager directory +\return the number of loaded modules */ -u32 gf_modules_get_count(GF_ModuleManager *pm); +u32 gf_modules_count(); /*! - *\brief get all modules directories - * - * Update module manager with all modules directories - *\param pm the module manager - *\param num_dirs the number of module directories - *\return The list of modules directories +\brief get all modules directories + +Update module manager with all modules directories +\param num_dirs the number of module directories +\return The list of modules directories */ -const char **gf_modules_get_module_directories(GF_ModuleManager *pm, u32* num_dirs); +const char **gf_modules_get_module_directories(u32* num_dirs); /*! - *\brief get module file name - * - *Gets a module shared library file name based on its index - *\param pm the module manager - *\param index the 0-based index of the module to query - *\return the name of the shared library module +\brief get module file name + +Gets a module shared library file name based on its index +\param index the 0-based index of the module to query +\return the name of the shared library module */ -const char *gf_modules_get_file_name(GF_ModuleManager *pm, u32 index); +const char *gf_modules_get_file_name(u32 index); /*! - *\brief get module file name - * - *Gets a module shared library file name based on its index - *\param ifce the module instance to query - *\return the name of the shared library module +\brief get module file name + +Gets a module shared library file name based on its index +\param ifce the module instance to query +\return the name of the shared library module */ const char *gf_module_get_file_name(GF_BaseInterface *ifce); /*! - *\brief loads an interface - * - *Loads an interface in the desired module. - *\param pm the module manager - *\param index the 0-based index of the module to load the interface from - *\param InterfaceFamily type of the interface to load - *\return the interface object if found and loaded, NULL otherwise. +\brief loads an interface + +Loads an interface in the desired module. +\param index the 0-based index of the module to load the interface from +\param InterfaceFamily type of the interface to load +\return the interface object if found and loaded, NULL otherwise. */ -GF_BaseInterface *gf_modules_load_interface(GF_ModuleManager *pm, u32 index, u32 InterfaceFamily); +GF_BaseInterface *gf_modules_load(u32 index, u32 InterfaceFamily); /*! - *\brief loads an interface by module name - * - *Loads an interface in the desired module - *\param pm the module manager - *\param mod_name the name of the module (shared library file) or of the interface as declared when registered. - *\param InterfaceFamily type of the interface to load - *\return the interface object if found and loaded, NULL otherwise. +\brief loads an interface by module name + +Loads an interface in the desired module +\param mod_name the name of the module (shared library file) or of the interface as declared when registered. +\param InterfaceFamily type of the interface to load +\return the interface object if found and loaded, NULL otherwise. */ -GF_BaseInterface *gf_modules_load_interface_by_name(GF_ModuleManager *pm, const char *mod_name, u32 InterfaceFamily); +GF_BaseInterface *gf_modules_load_by_name(const char *mod_name, u32 InterfaceFamily); /*! - *\brief interface shutdown - * - *Closes an interface +\brief interface shutdown - *\param interface_obj the interface to close +Closes an interface +\param interface_obj the interface to close +\return error if any */ GF_Err gf_modules_close_interface(GF_BaseInterface *interface_obj); + /*! - *\brief interface option query - * - *Gets an option from the config file associated with the module manager - *\param interface_obj the interface object used - *\param secName the desired key parent section name - *\param keyName the desired key name - *\return the desired key value if found, NULL otherwise. - */ -const char *gf_modules_get_option(GF_BaseInterface *interface_obj, const char *secName, const char *keyName); -/*! - *\brief interface option update - * - *Sets an option in the config file associated with the module manager - *\param interface_obj the interface object used - *\param secName the desired key parent section name - *\param keyName the desired key name - *\param keyValue the desired key value - *\note this will also create both section and key if they are not found in the configuration file - */ -GF_Err gf_modules_set_option(GF_BaseInterface *interface_obj, const char *secName, const char *keyName, const char *keyValue); +\brief module load + +Loads a module based on a preferred name. +If not found, check for predefined names for the given interface type in fthe global config and loads by predefined name. +If still not found, enumerate modules. +\param ifce_type type of module interface to load +\param name name of preferred module +\return the loaded module, or NULL if not found +*/ +GF_BaseInterface *gf_module_load(u32 ifce_type, const char *name); + /*! - *\brief get config file - * - *Gets the configuration file for the module instance - *\param ifce the interface object used - *\return handle to the config file - */ -GF_Config *gf_modules_get_config(GF_BaseInterface *ifce); +\brief filter module load + +Loads a filter module (not using GF_BaseInterface) from its index. Returns NULL if no such filter. +\param index index of the filter module to load +\param fsess opaque handle passed to the filter loader +\return the loaded filter register, or NULL if not found +*/ +void *gf_modules_load_filter(u32 index, void *fsess); /*! @} */ diff --git a/include/gpac/modules/audio_out.h b/include/gpac/modules/audio_out.h index 20fd282..e4baef7 100644 --- a/include/gpac/modules/audio_out.h +++ b/include/gpac/modules/audio_out.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2018 * All rights reserved * * This file is part of GPAC / modules interfaces @@ -41,6 +41,8 @@ extern "C" { /*include event system*/ #include <gpac/module.h> +/*include audio formats*/ +#include <gpac/constants.h> /* @@ -48,7 +50,7 @@ extern "C" { */ /*interface name and version for audio output*/ -#define GF_AUDIO_OUTPUT_INTERFACE GF_4CC('G','A','O', '1') +#define GF_AUDIO_OUTPUT_INTERFACE GF_4CC('G','A','O', '5') /*interface returned on query interface*/ typedef struct _audiooutput @@ -72,15 +74,15 @@ typedef struct _audiooutput possible sampleRate able to handle NbChannels and NbBitsPerSample - if it doesn't handle the NbChannels the internal mixer will do it */ - GF_Err (*QueryOutputSampleRate)(struct _audiooutput *aout, u32 *io_desired_samplerate, u32 *io_NbChannels, u32 *io_nbBitsPerSample); + GF_Err (*QueryOutputSampleRate)(struct _audiooutput *aout, u32 *io_desired_samplerate, u32 *io_NbChannels, u32 *io_AudioFormat); /*set output config - if audio is not running, driver must start it - *SampleRate, *NbChannels, *nbBitsPerSample: + *SampleRate, *NbChannels, *audioFormat: input: desired value output: final values - channel_cfg is the channels output cfg, eg set of flags as specified in constants.h + channel_layout is the channels output cfg, eg set of flags as specified in constants.h */ - GF_Err (*ConfigureOutput) (struct _audiooutput *aout, u32 *SampleRate, u32 *NbChannels, u32 *nbBitsPerSample, u32 channel_cfg); + GF_Err (*Configure) (struct _audiooutput *aout, u32 *SampleRate, u32 *NbChannels, u32 *audioFormat, u64 channel_layout); /*returns total buffer size used in ms. This is needed to compute the min size of audio decoders output*/ u32 (*GetTotalBufferTime)(struct _audiooutput *aout); @@ -118,69 +120,12 @@ typedef struct _audiooutput /*these are assigned by the audio renderer once module is loaded*/ /*fills the buffer with audio data, returns effective bytes written - the rest is filled with 0*/ - u32 (*FillBuffer) (void *audio_renderer, char *buffer, u32 buffer_size); + u32 (*FillBuffer) (void *audio_renderer, u8 *buffer, u32 buffer_size); void *audio_renderer; } GF_AudioOutput; -/* - Audio hardware output module -*/ - -/*interface name and version for audio output*/ -#define GF_AUDIO_FILTER_INTERFACE GF_4CC('G','A','F', '1') - -/*interface returned on query interface*/ -typedef struct _tag_audio_filter GF_AudioFilter; - -struct _tag_audio_filter -{ - /* interface declaration*/ - GF_DECL_MODULE_INTERFACE - - /*sets the current filter. The filterstring is opaque to libgpac and is taken as given - in the GPAC configuration file, where filters are listed as a ';;' separated list in the "Filter" key of - the [Audio] section. - @returns: 1 is this module can handle the filterstring, 0 otherwise. - */ - Bool (*SetFilter)(GF_AudioFilter *af, char *filterstring); - /*configures the filter: - @samplerate: samplerate of data - this cannot be modified by a filter - @bits_per_sample: sample format (8 or 16 bit signed PCM data) of data - this cannot be modified by a filter - @input_channel_number: number of input channels - @input_channel_layout: channel layout of input data - cf <gpac/constants.h> - @output_channel_number: number of ouput channels - @output_channel_layout: channel layout of output data - cf <gpac/constants.h> - &output_block_size_in_samples: size in blocks of the data to be sent to this filter. - If 0, data will not be reframed and blocks of any number of samples will be processed - @delay_ms: delay in ms introduced by this filter - @inplace_processing_capable: if set to 1, this filter is capable of processing data inplace, in which case - the same buffer is passed for in_data and out_data in the process call - */ - GF_Err (*Configure)(GF_AudioFilter *af, u32 samplerate, u32 bits_per_sample, u32 input_channel_number, u32 input_channel_layout, u32 *output_channel_number, u32 *output_channel_layout, u32 *output_block_size_in_samples, u32 *delay_ms, Bool *inplace_processing_capable); - /*process a chunk of audio data. - @in_data: input sample buffer - @in_data_size: input sample buffer size. If block len was set in the configure stage, there will be block len sample - @out_data: output sample buffer - if inplace was set in the configure stage, same as in_data. - NOTE: Outputing more samples that input ones may crash the system, the buffer only contains space for - the same amount of samples (including channels added/removed by the filter) - @out_data_size: data size written to output. Usually 0 or in_data_size. - */ - GF_Err (*Process)(GF_AudioFilter *af, void *in_data, u32 in_data_size, void *out_data, u32 *out_data_size); - - /*gets an option from the filter - currently not implemented */ - const char *(*GetOption)(GF_AudioFilter *af, char *option); - /*sets an option to the filter - currently not implemented */ - Bool (*SetOption)(GF_AudioFilter *af, char *option, char *value); - - /*Indicates the filter should be reset (audio stop or seek )*/ - void (*Reset)(GF_AudioFilter *af); - - /*private user data for the module*/ - void *udta; -}; - #ifdef __cplusplus } #endif diff --git a/include/gpac/modules/codec.h b/include/gpac/modules/codec.h index 8f2955b..4c4008f 100644 --- a/include/gpac/modules/codec.h +++ b/include/gpac/modules/codec.h @@ -62,7 +62,7 @@ enum GF_CODEC_RESILIENT_AFTER_FIRST_RAP=2 }; -/*Define codec matrix*/ +/*Define codec matrix*/ typedef struct __matrix GF_CodecMatrix; /*the structure for capabilities*/ @@ -152,6 +152,7 @@ enum /*switches up (1), max (2), down (0) or min (-1) media quality for scalable coding. */ GF_CODEC_MEDIA_SWITCH_QUALITY, + GF_CODEC_MEDIA_LAYER_DETACH, //notifies the codec all streams have been attached for the current time and that it may start init //this is require for L-HEVC+AVC @@ -218,102 +219,13 @@ user defined. u32 (*CanHandleStream)(IFCE_NAME, u32 StreamType, GF_ESD *esd, u8 ProfileLevelIndication);\ const char *(*GetName)(IFCE_NAME);\ void *privateStack; \ - + typedef struct _basedecoder { GF_CODEC_BASE_INTERFACE(struct _basedecoder *) } GF_BaseDecoder; -/*interface name and version for media decoder */ -#define GF_MEDIA_DECODER_INTERFACE GF_4CC('G', 'M', 'D', '3') - -typedef struct _mediadecoderframe -{ - //release media frame - void (*Release)(struct _mediadecoderframe *frame); - //get media frame plane - // @plane_idx: plane index, 0: Y or full plane, 1: U or UV plane, 2: V plane - // @outPlane: adress of target color plane - // @outStride: stride in bytes of target color plane - GF_Err (*GetPlane)(struct _mediadecoderframe *frame, u32 plane_idx, const char **outPlane, u32 *outStride); - - GF_Err (*GetGLTexture)(struct _mediadecoderframe *frame, u32 plane_idx, u32 *gl_tex_format, u32 *gl_tex_id, GF_CodecMatrix * texcoordmatrix); - - //allocated space by the decoder - void *user_data; -} GF_MediaDecoderFrame; - -/*the media module interface. A media module MUST be implemented in synchronous mode as time -and resources management is done by the terminal*/ -typedef struct _mediadecoder -{ - GF_CODEC_BASE_INTERFACE(struct _basedecoder *) - - /*Process the media data in inAU. - @inBuffer, inBufferLength: encoded input data (complete framing of encoded data) - @ES_ID: stream this data belongs too (scalable object) - @outBuffer, outBufferLength: allocated data for decoding - if outBufferLength is not enough - you must set the size in outBufferLength and GF_BUFFER_TOO_SMALL - - @PaddingBits is the padding at the end of the buffer (some codecs need this info) - @mmlevel: speed indicator for the decoding - cf above for values*/ - GF_Err (*ProcessData)(struct _mediadecoder *, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel); - - - /*optional (may be null), retrievs internal output frame of decoder. this function is called only if the decoder returns GF_OK on a SetCapabilities GF_CODEC_RAW_MEMORY*/ - GF_Err (*GetOutputBuffer)(struct _mediadecoder *, u16 ES_ID, u8 **pY_or_RGB, u8 **pU, u8 **pV); - - /*optional (may be null), retrievs internal output frame object of decoder. this function is called only if the decoder returns GF_OK on a SetCapabilities GF_CODEC_FRAME_OUTPUT*/ - GF_Err (*GetOutputFrame)(struct _mediadecoder *, u16 ES_ID, GF_MediaDecoderFrame **frame, Bool *needs_resize); -} GF_MediaDecoder; - - -/* - WARNING - DO NOT MODIFY THE POSITION OF ProcessData IN SCENE OR NODE DECODER, AS THE BOTH STRUCTURES - ARE TYPE_CASTED BY THE TERMINAL WHEN CALLING ProcessData -*/ -typedef struct _scene *LPSCENE; - -/*interface name and version for scene decoder */ -#define GF_SCENE_DECODER_INTERFACE GF_4CC('G', 'S', 'D', '3') - -typedef struct _scenedecoder -{ - GF_CODEC_BASE_INTERFACE(struct _basedecoder *) - - /*Process the scene data in inAU. - @inBuffer, inBufferLength: encoded input data (complete framing of encoded data) - @ES_ID: stream this data belongs too (scalable object) - @AU_Time: specifies the current AU time. This is usually unused, however is needed for decoder - handling the scene graph without input data (cf below). In this case the buffer passed is always NULL and the AU - time caries the time of the scene (or of the stream object attached to the scene decoder, cf below) - @mmlevel: speed indicator for the decoding - cf above for values*/ - GF_Err (*ProcessData)(struct _scenedecoder *, const char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 AU_Time, u32 mmlevel); - - - /*attaches scene to the decoder - a scene may be attached to several decoders of several types - (BIFS or others scene dec, ressource decoders (OD), etc. - is: inline scene owning graph (and not just graph), defined in intern/terminal_dev.h. With inline scene - the complete terminal is exposed so there's pretty much everything doable in a scene decoder - @is_scene_root: set to true if this decoder is the root of the scene, false otherwise (either another decoder - or a re-entrant call, cf below) - This is called once upon creation of the decoder (several times if re-entrant) - */ - GF_Err (*AttachScene)(struct _scenedecoder *, LPSCENE scene, Bool is_scene_root); - /*releases scene. If the decoder manages nodes / resources in the scene, - THESE MUST BE DESTROYED. May be NULL if decoder doesn't manage nodes but only create them (like BIFS, OD) and - doesn't have to be instructed the scene is about to be resumed - This is called each time the scene is about to be reseted (eg, seek and destroy) - */ - GF_Err (*ReleaseScene)(struct _scenedecoder *); -} GF_SceneDecoder; - /*interface name and version for node decoder mainly used by AFX*/ #define GF_NODE_DECODER_INTERFACE GF_4CC('G', 'N', 'D', '3') @@ -348,35 +260,66 @@ typedef struct __input_device /* interface declaration*/ GF_DECL_MODULE_INTERFACE - Bool (*RegisterDevice)(struct __input_device *, const char *urn, GF_BitStream *dsi, void (*AddField)(struct __input_device *_this, u32 fieldType, const char *name)); + Bool (*RegisterDevice)(struct __input_device *, const char *urn, const char *dsi, u32 dsi_size, void (*AddField)(struct __input_device *_this, u32 fieldType, const char *name)); void (*Start)(struct __input_device *); void (*Stop)(struct __input_device *); void *udta; /*this is set upon loading and shall not be modified*/ - GF_BaseDecoder *input_decoder; - void (*DispatchFrame)(struct __input_device *, u8 *data, u32 data_len); + void *input_stream_context; + void (*DispatchFrame)(struct __input_device *, const u8 *data, u32 data_len); } GF_InputSensorDevice; - /*interface name and version for media decoder */ -#define GF_PRIVATE_MEDIA_DECODER_INTERFACE GF_4CC('G', 'P', 'M', '2') +#define GF_MEDIA_DECODER_INTERFACE GF_4CC('G', 'M', 'D', '3') + +typedef struct _mediadecoderframe +{ + //release media frame + void (*Release)(struct _mediadecoderframe *frame); + //get media frame plane + // @plane_idx: plane index, 0: Y or full plane, 1: U or UV plane, 2: V plane + // @outPlane: adress of target color plane + // @outStride: stride in bytes of target color plane + GF_Err (*GetPlane)(struct _mediadecoderframe *frame, u32 plane_idx, const char **outPlane, u32 *outStride); + + GF_Err (*GetGLTexture)(struct _mediadecoderframe *frame, u32 plane_idx, u32 *gl_tex_format, u32 *gl_tex_id, GF_CodecMatrix * texcoordmatrix); + + //allocated space by the decoder + void *user_data; +} GF_MediaDecoderFrame; /*the media module interface. A media module MUST be implemented in synchronous mode as time and resources management is done by the terminal*/ -typedef struct _private_mediadecoder +typedef struct _mediadecoder { GF_CODEC_BASE_INTERFACE(struct _basedecoder *) - /*Control media decoder. - @mute: set mute or not - @x, y, w, h: video output position in screen coordinate - */ - GF_Err (*Control)(struct _private_mediadecoder *, Bool mute, GF_Window *src_rect, GF_Window *dst_rect); -} GF_PrivateMediaDecoder; + /*Process the media data in inAU. + @inBuffer, inBufferLength: encoded input data (complete framing of encoded data) + @ES_ID: stream this data belongs too (scalable object) + @outBuffer, outBufferLength: allocated data for decoding - if outBufferLength is not enough + you must set the size in outBufferLength and GF_BUFFER_TOO_SMALL + + @PaddingBits is the padding at the end of the buffer (some codecs need this info) + @mmlevel: speed indicator for the decoding - cf above for values*/ + GF_Err (*ProcessData)(struct _mediadecoder *, + char *inBuffer, u32 inBufferLength, + u16 ES_ID, u32 *CTS, + char *outBuffer, u32 *outBufferLength, + u8 PaddingBits, u32 mmlevel); + + + /*optional (may be null), retrievs internal output frame of decoder. this function is called only if the decoder returns GF_OK on a SetCapabilities GF_CODEC_RAW_MEMORY*/ + GF_Err (*GetOutputBuffer)(struct _mediadecoder *, u16 ES_ID, u8 **pY_or_RGB, u8 **pU, u8 **pV); + + /*optional (may be null), retrievs internal output frame object of decoder. this function is called only if the decoder returns GF_OK on a SetCapabilities GF_CODEC_FRAME_OUTPUT*/ + GF_Err (*GetOutputFrame)(struct _mediadecoder *, u16 ES_ID, GF_MediaDecoderFrame **frame, Bool *needs_resize); +} GF_MediaDecoder; + #ifdef __cplusplus } diff --git a/include/gpac/modules/term_ext.h b/include/gpac/modules/compositor_ext.h similarity index 69% rename from include/gpac/modules/term_ext.h rename to include/gpac/modules/compositor_ext.h index b017b28..381f4ba 100644 --- a/include/gpac/modules/term_ext.h +++ b/include/gpac/modules/compositor_ext.h @@ -24,65 +24,64 @@ */ -#ifndef _GF_MODULE_TERM_EXT_H_ -#define _GF_MODULE_TERM_EXT_H_ +#ifndef _GF_MODULE_COMPOSITOR_EXT_H_ +#define _GF_MODULE_COMPOSITOR_EXT_H_ #ifdef __cplusplus extern "C" { #endif -#include <gpac/terminal.h> +#include <gpac/compositor.h> /*interface name and version for Terminal Extensions services*/ -#define GF_TERM_EXT_INTERFACE GF_4CC('G','T','E', '1') +#define GF_COMPOSITOR_EXT_INTERFACE GF_4CC('C','O','X', '1') -typedef struct _term_ext GF_TermExt; +typedef struct _gf_compositor_ext GF_CompositorExt; typedef struct { void *scenegraph; void *ctx; - void *global; Bool unload; -} GF_TermExtJS; +} GF_CompositorExtJS; enum { /*start terminal extension. If 0 is returned, the module will be unloaded - associated param: GF_Terminal *term + associated param: GF_Compositor *compositor @return: 1 if OK, 0 otherwise (in which case the extensions will be removed without calling stop) */ - GF_TERM_EXT_START = 1, + GF_COMPOSITOR_EXT_START = 1, /*stop terminal extension associated param: NULL @return: ignored */ - GF_TERM_EXT_STOP, + GF_COMPOSITOR_EXT_STOP, - /*process extension - only called GF_TERM_EXTENSION_NOT_THREADED capability is set + /*process extension - only called GF_COMPOSITOR_EXTENSION_NOT_THREADED capability is set associated param: NULL @return: ignored */ - GF_TERM_EXT_PROCESS, + GF_COMPOSITOR_EXT_PROCESS, /*load/unload js bindings of this extension - associated param: GF_TermExtJS *jsext + associated param: GF_CompositorExtJS *jsext @return: ignored */ - GF_TERM_EXT_JSBIND, + GF_COMPOSITOR_EXT_JSBIND, }; enum { /*signal the extension is to be called on regular basis (once per simulation tick). This MUST be set during - the GF_TERM_EXT_START command and cannot be changed at run-time*/ - GF_TERM_EXTENSION_NOT_THREADED = 1<<1, + the GF_gf_compositor_ext_START command and cannot be changed at run-time*/ + GF_COMPOSITOR_EXTENSION_NOT_THREADED = 1<<1, - GF_TERM_EXTENSION_JS = 1<<2, + GF_COMPOSITOR_EXTENSION_JS = 1<<2, }; -struct _term_ext +struct _gf_compositor_ext { /* interface declaration*/ GF_DECL_MODULE_INTERFACE @@ -95,7 +94,7 @@ struct _term_ext action: action type of this call param: associated param of the call */ - Bool (*process)(GF_TermExt *termext, u32 action, void *param); + Bool (*process)(GF_CompositorExt *comp_ext, u32 action, void *param); /*module private*/ void *udta; @@ -108,5 +107,5 @@ struct _term_ext #endif -#endif /*#define _GF_MODULE_TERM_EXT_H_*/ +#endif /*#define _GF_MODULE_COMPOSITOR_EXT_H_*/ diff --git a/include/gpac/modules/font.h b/include/gpac/modules/font.h index 4078e62..a42287a 100644 --- a/include/gpac/modules/font.h +++ b/include/gpac/modules/font.h @@ -87,7 +87,7 @@ enum #define GF_FONT_WEIGHT_MASK 0xFFFFFC00 /*interface name and version for font engine*/ -#define GF_FONT_READER_INTERFACE GF_4CC('G','F','T', '1') +#define GF_FONT_READER_INTERFACE GF_4CC('G','F','T', '4') typedef struct _font_reader diff --git a/include/gpac/modules/hardcoded_proto.h b/include/gpac/modules/hardcoded_proto.h index b0d08b7..0ea689e 100644 --- a/include/gpac/modules/hardcoded_proto.h +++ b/include/gpac/modules/hardcoded_proto.h @@ -36,7 +36,7 @@ extern "C" { #include <gpac/compositor.h> /*interface name and version for Built-in proto User Extensions*/ -#define GF_HARDCODED_PROTO_INTERFACE GF_4CC('G','H','P', '2') +#define GF_HARDCODED_PROTO_INTERFACE GF_4CC('G','H','P', '4') typedef struct _hc_proto_mod { diff --git a/include/gpac/modules/js_usr.h b/include/gpac/modules/js_usr.h deleted file mode 100644 index 1b2031b..0000000 --- a/include/gpac/modules/js_usr.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / modules interfaces - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#ifndef _GF_MODULE_JS_USR_H_ -#define _GF_MODULE_JS_USR_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <gpac/module.h> -#include <gpac/scenegraph.h> - -/*interface name and version for JavaScript User Extensions*/ -#define GF_JS_USER_EXT_INTERFACE GF_4CC('G','J','S', '1') - -typedef struct _js_usr_ext GF_JSUserExtension; - -typedef struct JSContext GF_JSContext; -typedef struct JSObject GF_JSObject; - -struct _js_usr_ext -{ - /* interface declaration*/ - GF_DECL_MODULE_INTERFACE - - /*load JS extension - doc: scene graph in which the extension is loaded - jsctx: JavaScript context in which the extension is loaded. - For BIFS/VRML/X3D, one context is created per script node - For other graphs, one context is created per scene/document - global: JavaScript global object for the context - unload: if true, the extension should be unloaded from the JavaScript context (called upon destroy). Otherwise it should be loaded - */ - void (*load)(GF_JSUserExtension *jsext, GF_SceneGraph *doc, GF_JSContext *jsctx, GF_JSObject *global, Bool unload); - /*module private*/ - void *udta; -}; - - -#ifdef __cplusplus -} -#endif - - -#endif /*#define _GF_MODULE_JS_USR_H_*/ - diff --git a/include/gpac/modules/raster2d.h b/include/gpac/modules/raster2d.h deleted file mode 100644 index 6596fae..0000000 --- a/include/gpac/modules/raster2d.h +++ /dev/null @@ -1,262 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / modules interfaces - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#ifndef _GF_MODULE_RASTER2D_H_ -#define _GF_MODULE_RASTER2D_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <gpac/path2d.h> -#include <gpac/module.h> -#include <gpac/color.h> - - -/*stencil types*/ -typedef enum -{ - /*solid color stencil*/ - GF_STENCIL_SOLID = 0, - /*linear color gradient stencil*/ - GF_STENCIL_LINEAR_GRADIENT, - /*radial color gradient stencil*/ - GF_STENCIL_RADIAL_GRADIENT, - /*texture stencil*/ - GF_STENCIL_VERTEX_GRADIENT, - /*texture stencil*/ - GF_STENCIL_TEXTURE, -} GF_StencilType; - - -/*gradient filling modes*/ -typedef enum -{ - /*edge colors are repeated until path is filled*/ - GF_GRADIENT_MODE_PAD, - /*pattern is inversed each time it's repeated*/ - GF_GRADIENT_MODE_SPREAD, - /*pattern is repeated to fill path*/ - GF_GRADIENT_MODE_REPEAT -} GF_GradientMode; - - -/*texture tiling flags*/ -typedef enum -{ - /*texture is repeated in its horizontal direction*/ - GF_TEXTURE_REPEAT_S = (1<<1), - /*texture is repeated in its horizontal direction*/ - GF_TEXTURE_REPEAT_T = (1<<2), - /*texture is fliped vertically*/ - GF_TEXTURE_FLIP = (1<<3), -} GF_TextureTiling; - -/*filter levels for texturing - up to the graphics engine but the following levels are used by -the client*/ -typedef enum -{ - /*high speed mapping (ex, no filtering applied)*/ - GF_TEXTURE_FILTER_HIGH_SPEED, - /*compromise between speed and quality (ex, filter to nearest pixel)*/ - GF_TEXTURE_FILTER_MID, - /*high quality mapping (ex, bi-linear/bi-cubic interpolation)*/ - GF_TEXTURE_FILTER_HIGH_QUALITY -} GF_TextureFilter; - -/* rasterizer antialiasing depending on the graphics engine*/ -typedef enum -{ - /*raster shall use no antialiasing */ - GF_RASTER_HIGH_SPEED, - /*raster should use fast mode and good quality if possible*/ - GF_RASTER_MID, - /*raster should use full antialiasing*/ - GF_RASTER_HIGH_QUALITY -} GF_RasterLevel; - - -/*user routines for raserizer. common syntaxes: - @cbk: user defined callback - @x, y: first pixel position of the run, in device memory (top-left) coordinates - @run_h_len: number of pixels to fill on line - @color: color to fill pixel with. USER MUST IGNORE THE ALPHA COMPONENT OF THIS COLOR, the final - alpha is computed by the lib - @alpha: blending amount (0->0xFF) for the pixels -*/ - -typedef void (*raster_cbk_fill_run_alpha) (void *, u32, u32, u32, GF_Color, u8); -typedef void (*raster_cbk_fill_run_no_alpha) (void *, u32, u32, u32, GF_Color); -typedef void (*raster_cbk_fill_rect)(void *cbk, u32 x, u32 y, u32 width, u32 height, GF_Color color); - -typedef struct -{ - void *cbk; - /*fills line pixels without any blending operation*/ - raster_cbk_fill_run_no_alpha fill_run_no_alpha; - /* fills line pixels with blending operation - alpha combines both fill color and anti-aliasing blending */ - raster_cbk_fill_run_alpha fill_run_alpha; - /*fills rectangle*/ - raster_cbk_fill_rect fill_rect; -} GF_RasterCallback; - - - -/*opaque handler for all stencils*/ -typedef void *GF_STENCIL; - -/*visual surface handler*/ -typedef void *GF_SURFACE; - -/*interface name and version for raster2D*/ -#define GF_RASTER_2D_INTERFACE GF_4CC('G','R','2', '3') - -/*graphics driver*/ -typedef struct _raster2d_interface -{ - /* interface declaration*/ - GF_DECL_MODULE_INTERFACE - - GF_STENCIL (*stencil_new) (struct _raster2d_interface *, GF_StencilType type); - /*common destructor for all stencils*/ - void (*stencil_delete) (GF_STENCIL _this); - /*set stencil transformation matrix*/ - GF_Err (*stencil_set_matrix) (GF_STENCIL _this, GF_Matrix2D *mat); - /*solid brush - set brush color*/ - GF_Err (*stencil_set_brush_color) (GF_STENCIL _this, GF_Color c); - /*gradient brushes*/ - /*sets gradient repeat mode - return GF_NOT_SUPPORTED if driver doesn't support this to let the app compute repeat patterns - this may be called before the gradient is setup*/ - GF_Err (*stencil_set_gradient_mode) (GF_STENCIL _this, GF_GradientMode mode); - /*set linear gradient. line is defined by start and end, and you can give interpolation colors at specified positions*/ - GF_Err (*stencil_set_linear_gradient) (GF_STENCIL _this, Fixed start_x, Fixed start_y, Fixed end_x, Fixed end_y); - /*radial gradient brush center point, focal point and radius - colors can only be set through set_interpolation */ - GF_Err (*stencil_set_radial_gradient) (GF_STENCIL _this, Fixed cx, Fixed cy, Fixed fx, Fixed fy, Fixed x_radius, Fixed y_radius); - /*radial and linear gradient (not used with vertex) - set color interpolation at given points, - @pos[i]: distance from (center for radial, start for linear) expressed between 0 and 1 (1 being the gradient bounds) - @col[i]: associated color - NOTE 1: the colors at 0 and 1.0 MUST be provided - NOTE 2: colors shall be fed in order from 0 to 1 - NOTE 3: this overrides the colors provided for linear gradient - */ - GF_Err (*stencil_set_gradient_interpolation) (GF_STENCIL _this, Fixed *pos, GF_Color *col, u32 count); - - /*vertex gradient : set limit path */ - GF_Err (*stencil_set_vertex_path) (GF_STENCIL _this, GF_Path *path); - /*set the center of the gradient*/ - GF_Err (*stencil_set_vertex_center) (GF_STENCIL _this, Fixed cx, Fixed cy, u32 color); - /*set the center of the gradient*/ - GF_Err (*stencil_set_vertex_colors) (GF_STENCIL _this, u32 *colors, u32 nbCol); - - /*sets global alpha blending level for stencil (texture and gradients) - the alpha channel shall be combined with the color matrix if any*/ - GF_Err (*stencil_set_alpha) (GF_STENCIL _this, u8 alpha); - - /*set stencil texture - @pixels: texture data, from top to bottom - @width, @height: texture size - @stride: texture horizontal pitch (bytes to skip to get to next row) - @pixelFormat: texture pixel format as defined in file constants.h - @destination_format_hint: this is the current pixel format of the destination surface, and is given - as a hint in case the texture needs to be converted by the stencil - @no_copy: if set, specifies the texture data shall not be cached by the module (eg it must be able - to directly modify the given memory - NOTE: this stencil acts as a data wrapper, the pixel data is not required to be locally copied - data is not required to be available for texturing until the stencil is used in a draw operation - */ - GF_Err (*stencil_set_texture) (GF_STENCIL _this, char *pixels, u32 width, u32 height, u32 stride, GF_PixelFormat pixelFormat, GF_PixelFormat destination_format_hint, Bool no_copy); - /*signals the texture has been modified (internal texture only)*/ - void (*stencil_texture_modified) (GF_STENCIL _this); - - /*sets texture tile mode*/ - GF_Err (*stencil_set_tiling) (GF_STENCIL _this, GF_TextureTiling mode); - /*sets texture filtering mode*/ - GF_Err (*stencil_set_filter) (GF_STENCIL _this, GF_TextureFilter filter_mode); - /*set stencil color matrix - texture stencils only. If matrix is NULL, resets current color matrix*/ - GF_Err (*stencil_set_color_matrix) (GF_STENCIL _this, GF_ColorMatrix *cmat); - - /*creates surface object*/ - /* @center_coords: true indicates mathematical-like coord system, - false indicates computer-like coord system */ - GF_SURFACE (*surface_new) (struct _raster2d_interface *, Bool center_coords); - /* delete surface object */ - void (*surface_delete) (GF_SURFACE _this); - - /* attach surface object to device object (Win32: HDC) width and height are target surface size*/ - GF_Err (*surface_attach_to_device) (GF_SURFACE _this, void *os_handle, u32 width, u32 height); - /* attach surface object to stencil object*/ - GF_Err (*surface_attach_to_texture) (GF_SURFACE _this, GF_STENCIL sten); - /* attach surface object to memory buffer if supported - @pixels: texture data - @width, @height: texture size - @pitch_x: texture horizontal pitch (bytes to skip to get to next pixel). O means linear frame buffer (eg pitch_x==bytes per pixel) - @pitch_y: texture vertical pitch (bytes to skip to get to next line) - @pixelFormat: texture pixel format - */ - GF_Err (*surface_attach_to_buffer) (GF_SURFACE _this, char *pixels, u32 width, u32 height, s32 pitch_x, s32 pitch_y, GF_PixelFormat pixelFormat); - - GF_Err (*surface_attach_to_callbacks) (GF_SURFACE _this, GF_RasterCallback *callbacks, u32 width, u32 height); - - /* detach surface object */ - void (*surface_detach) (GF_SURFACE _this); - - /*sets rasterizer precision */ - GF_Err (*surface_set_raster_level) (GF_SURFACE _this, GF_RasterLevel RasterSetting); - /* set the given matrix as the current transformations for all drawn paths - if NULL reset the current transformation */ - GF_Err (*surface_set_matrix) (GF_SURFACE _this, GF_Matrix2D *mat); - /* set the given rectangle as a clipper - nothing will be drawn outside this clipper - if the clipper is NULL then no clipper is set - NB: the clipper is not affected by the surface matrix and is given in pixels - CF ABOVE NOTE ON CLIPPERS*/ - GF_Err (*surface_set_clipper) (GF_SURFACE _this, GF_IRect *rc); - - /*sets the given path as the current one for drawing - the surface transform is NEVER changed between - setting the path and filling, only the clipper may change*/ - GF_Err (*surface_set_path) (GF_SURFACE _this, GF_Path *path); - /*fills the current path using the given stencil - can be called several times with the same current path*/ - GF_Err (*surface_fill) (GF_SURFACE _this, GF_STENCIL stencil); - - /*flushes to surface*/ - GF_Err (*surface_flush) (GF_SURFACE _this); - - /*clears given pixel rect on the surface with the given color - REQUIRED - the given rect is formatted as a clipper - CF ABOVE NOTE ON CLIPPERS*/ - GF_Err (*surface_clear)(GF_SURFACE _this, GF_IRect *rc, GF_Color col); - - /*private:*/ - void *internal; -} GF_Raster2D; - - - -#ifdef __cplusplus -} -#endif - - -#endif /*_GF_MODULE_RASTER2D_H_*/ - diff --git a/include/gpac/modules/service.h b/include/gpac/modules/service.h deleted file mode 100644 index 30db105..0000000 --- a/include/gpac/modules/service.h +++ /dev/null @@ -1,848 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / modules interfaces - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef _GF_SERVICE_H_ -#define _GF_SERVICE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -/*for SL, ESD and OD*/ -#include <gpac/mpeg4_odf.h> -#include <gpac/events.h> -#include <gpac/download.h> -#include <gpac/module.h> - -/*handle to service*/ -typedef struct _net_service GF_ClientService; - -/*handle to channel*/ -typedef void *LPNETCHANNEL; - -typedef enum -{ - /*channel control, app->module. Note that most modules don't need to handle pause/resume/set_speed*/ - GF_NET_CHAN_PLAY, - GF_NET_CHAN_STOP, - - /* pause channel (can be sent from service to terminal send from a pull-mode module to indicate buffer on/off) */ - GF_NET_CHAN_PAUSE, - GF_NET_CHAN_RESUME, - GF_NET_CHAN_SET_SPEED, - /*channel configuration, app->module*/ - GF_NET_CHAN_CONFIG, - /*channel duration, app<->module (in case duration is not known at setup)*/ - GF_NET_CHAN_DURATION, - /*queries channel buffer min/max, app->module. Default values are passed and can be overriden by the module. - PULL channels will get this command but buffering has to be handled by the service*/ - GF_NET_CHAN_BUFFER, - /*channel buffer query, app<-module*/ - GF_NET_CHAN_BUFFER_QUERY, - /*retrieves DSI from channel (DSI may be caried by net with a != value than OD), app->module*/ - GF_NET_CHAN_GET_DSI, - /*set media padding for all AUs fetched (pull mode only). - If not supported the channel will have to run in push mode. app->module*/ - GF_NET_CHAN_SET_PADDING, - /*sets input channel to pull mode if possible, app->module*/ - GF_NET_CHAN_SET_PULL, - /*query channel capability to pause/resume and seek(play from an arbitrary range) - a non-interactive channel doesn't have to handle SET_SPEED, PAUSE and RESUME commands but can - still work in pull mode*/ - GF_NET_CHAN_INTERACTIVE, - /*map net time (OTB) to media time (up only) - this is needed by some signaling protocols when the - real play range is not the requested one */ - GF_NET_CHAN_MAP_TIME, - /*same as map time, but does not trigger realignment of timestamps in the terminal - only used to solve TS->OTB for display purposes*/ - GF_NET_CHAN_SET_MEDIA_TIME, - /*reconfiguration of channel comming from network (up only) - this is used to override the SL config - if it differs from the one specified at config*/ - GF_NET_CHAN_RECONFIG, - /*signal channel is ISMACryp'ted (net->term only)*/ - GF_NET_CHAN_DRM_CFG, - - /*reset channel - this is need when we turn off this channel in rtp/mpeg2ts*/ - GF_NET_CHAN_RESET, - - /*retrieves ESD for channel - net->term only, for cache configuration*/ - GF_NET_CHAN_GET_ESD, - /*retrieves visual PAR as indicated in container if any*/ - GF_NET_CHAN_GET_PIXEL_AR, - /*retrieves visual SRD as indicated in container if any*/ - GF_NET_CHAN_GET_SRD, - - /*service buffer query (for all channels running in service), app<-module*/ - GF_NET_BUFFER_QUERY, - /*retrieves network stats for service/channel; app->module*/ - GF_NET_GET_STATS, - /*retrieves whether service can be cached (rtp, http streaming radios, etc) or not. No associated struct*/ - GF_NET_IS_CACHABLE, - - /*sets info for service - net->term only*/ - GF_NET_SERVICE_INFO, - /*checks if there is an audio stream in the service - term->net only*/ - GF_NET_SERVICE_HAS_AUDIO, - /*checks if the service can support reverse playback (speed<0) - term->service only*/ - GF_NET_SERVICE_CAN_REVERSE_PLAYBACK, - /*send by the terminal to indicate the channel(s) on this service need more data - term->net only*/ - GF_NET_SERVICE_FLUSH_DATA, - - /*checks if there is a forced video size - if yes, info is stored in GF_NetComPixelAR - term->net only*/ - GF_NET_SERVICE_HAS_FORCED_VIDEO_SIZE, - /*instructs the service to get the migration info - term->net only*/ - GF_NET_SERVICE_MIGRATION_INFO, - - /*switches quality up or down or a given ID*/ - GF_NET_SERVICE_QUALITY_SWITCH, - - /*queries quality in the service*/ - GF_NET_SERVICE_QUALITY_QUERY, - - /*When using DASH or playlists, query the next file to concatenate to thecurrent one net->proxy only*/ - GF_NET_SERVICE_QUERY_NEXT, - /*When using DASH, query the media range of the url passed in ConnectService - this is only used for local - playback/validation of DASH sequences*/ - GF_NET_SERVICE_QUERY_INIT_RANGE, - /* When using proxy between an input module and the terminal, exchange status using this command: input -> proxy */ - GF_NET_SERVICE_STATUS_PROXY, - - /*When using DASH or playlists, indicates that DATA (chunk or complete file ) has been received by the playlist proxy*/ - GF_NET_SERVICE_PROXY_DATA_RECEIVE, - - /*query screen capabilities*/ - GF_NET_SERVICE_MEDIA_CAP_QUERY, - - /*signal an associated content is announced (from service to term). This does not have to be filtered by the service - the terminal will handle this.*/ - GF_NET_ASSOCIATED_CONTENT_LOCATION, - /*signal associated content timeline (from service to term)*/ - GF_NET_ASSOCIATED_CONTENT_TIMING, - - /*event send from service*/ - GF_NET_SERVICE_EVENT, - //sets nalu mode - GF_NET_CHAN_NALU_MODE, - - /*indicates visible part of the visual object associated with the channel*/ - GF_NET_CHAN_VISIBILITY_HINT, - /*request current position in TSB - 0 means 'at the live point'*/ - GF_NET_GET_TIMESHIFT, - - /*seek request from service on all channels*/ - GF_NET_SERVICE_SEEK, - - /*query codec statistic on all channels*/ - GF_NET_SERVICE_CODEC_STAT_QUERY, - /* Used in DASH: submodules can query the DASH module for the computed UTC delay between client and server */ - GF_NET_SERVICE_QUERY_UTC_DELAY, -} GF_NET_CHAN_CMD; - -/*channel command for all commands that don't need params: -GF_NET_CHAN_SET_PULL: module shall return GF_OK or GF_NOT_SUPPORTED -GF_NET_CHAN_INTERACTIVE: module shall return GF_OK or GF_NOT_SUPPORTED -*/ -typedef struct -{ - /*command type*/ - u32 command_type; - /*channel*/ - LPNETCHANNEL on_channel; -} GF_NetComBase; - -/*GF_NET_CHAN_PLAY, GF_NET_CHAN_SET_SPEED*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL on_channel; - /*params for GF_NET_CHAN_PLAY, ranges in sec - if range is <0, then it is ignored (eg [2, -1] with speed>0 means 2 +oo) */ - Double start_range, end_range; - /*params for GF_NET_CHAN_PLAY and GF_NET_CHAN_SPEED*/ - Double speed; - Bool dash_segment_switch; - /*indicates this is the first PLAY on an elemnt inserted from bcast*/ - Bool initial_broadcast_play; - /* - 0: range is in media time - 1: range is in timesatmps - 2: range is in media time but timestamps should not be shifted (hybrid dash only for now) - */ - u32 timestamp_based; -} GF_NetComPlay; - - -/*GF_NET_CHAN_CONFIG, GF_NET_CHAN_RECONFIG -channel config may happen as soon as the channel is open, even if the module hasn't acknowledge creation -channel config can also be used from network to app, with GF_NET_CHAN_RECONFIG type - only the SL config is then used -*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL on_channel; - - /*SL config of the stream as delivered in OD (app->channel) or by network (channel->app)*/ - GF_SLConfig sl_config; - /*stream priority packet drops are more tolerable if low priority - app->channel only*/ - u32 priority; - /*sync ID: all channels with the same sync ID run on the same timeline, thus the module should - try to match this - note this may not be possible (typically RTP/RTSP)*/ - u32 sync_id; - /*audio frame duration and sample rate if any - this is needed by some RTP payload*/ - u32 frame_duration, sample_rate; - /*do we use MPEG-2 section for SL packets ? this field is not coded*/ - Bool use_m2ts_sections; -} GF_NetComConfig; - -/*GF_NET_CHAN_BUFFER, GF_NET_CHAN_BUFFER_QUERY*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL on_channel; - /*the recommended buffering limits in ms - this depends on the modules preferences and on the service - type (multicast, vod, ...) - below buffer_min the stream will pause if possible until buffer_max is reached - note the app will fill in default values before querying*/ - u32 min, max; - /*only used with GF_NET_CHAN_BUFFER_QUERY and GF_NET_BUFFER_QUERY- amount of media in decoding buffer, in ms. This value is adjusted by the current playback speed, eg if playing at 2x the occupancy is (media time in buffers) / 2 */ - u32 occupancy; - /*set to GF_TRUE if a channel is buffering for the required service*/ - Bool buffering; -} GF_NetComBuffer; - -/*GF_NET_CHAN_DURATION*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL on_channel; - /*duration in sec*/ - Double duration; - /*time shift buffer depth in ms, (u32) -1 is infinity*/ - u32 time_shift_buffer; -} GF_NetComDuration; - -/*GF_NET_CHAN_GET_DSI*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL on_channel; - /*carries specific info for codec - data shall be allocated by service and is freed by user*/ - char *dsi; - u32 dsi_len; -} GF_NetComGetDSI; - -/*GF_NET_CHAN_SET_PADDING*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL on_channel; - u32 padding_bytes; -} GF_NetComPadding; - -/*GF_NET_GET_TIMESHIFT*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL on_channel; - //time in sec in the timeshift buffer - 0 means live point - Double time; -} GF_NetComTimeShift; - - - -/*GF_NET_SERVICE_PROXY_DATA_RECEIVE*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL on_channel; - Bool is_chunk; - Bool is_live; -} GF_NetComProxyData; - -/*GF_NET_CHAN_MAP_TIME*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL on_channel; - /*MediaTime at this timestamp*/ - Double media_time; - /*TS where mapping is done (in SL TS resolution)*/ - u64 timestamp; - /*specifies whether decoder input data shall be discarded or only have its timing updated*/ - Bool reset_buffers; -} GF_NetComMapTime; - - -typedef struct -{ - bin128 SystemID; - u32 KID_count; - bin128 *KIDs; - u32 private_data_size; - u8 *private_data; -} GF_NetComDRMConfigPSSH; - -/*GF_NET_CHAN_ISMACRYP_CFG*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL on_channel; - - - /*per channel, regardless of DRM schemes (ISMA, OMA, )*/ - u32 scheme_version; - u32 scheme_type; - const char *scheme_uri; - const char *kms_uri; - /*OMA DRM info*/ - const char *contentID; - u32 oma_drm_crypt_type; - Bool oma_drm_use_pad, oma_drm_use_hdr; - const char *oma_drm_textual_headers; - u32 oma_drm_textual_headers_len; - - /*SHA-1 file hash*/ - u8 hash[20]; - - /*CENC PSSH data*/ - u32 PSSH_count; - GF_NetComDRMConfigPSSH *PSSHs; -} GF_NetComDRMConfig; - -/*GF_NET_CHAN_GET_ESD*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL on_channel; - const GF_ESD *esd; - Bool is_iod_stream; -} GF_NetComGetESD; - -/*GF_NET_GET_STATS -Notes -1: only channels using network must reply. All channels fetching data through a -file downloader (cf below) shall NOT answer, the app manages downloader bandwidth internally. -2: BANDWIDTH USED BY SIGNALING PROTOCOL IS IGNORED IN GPAC -*/ -typedef struct __netstatcom -{ - u32 command_type; - /*MAY BE NULL, in which case the module must fill in ONLY the control channel part. This - is not used yet, but could be with a protocol using a single control socket for N media channels.*/ - LPNETCHANNEL on_channel; - /*percentage of packet loss from network. This cannot be figured out by the app since there is no - one-to-one mapping between the protocol packets and the final SL packet (cf RTP payloads)*/ - Float pck_loss_percentage; - /*channel port, control channel port if any (eg RTCP)*/ - u16 port, ctrl_port; - /*bandwidth used by channel & its control channel if any (both up and down) - expressed in bits per second - for HTTP connections, typically only bw_down is used*/ - u32 bw_up, bw_down, ctrl_bw_down, ctrl_bw_up; - /*set to 0 if channel is not part of a multiplex. Otherwise set to the multiplex port, and - above port info shall be identifiers in the multiplex - note that multiplexing overhead is ignored - in GPAC for the current time*/ - u16 multiplex_port; -} GF_NetComStats; - -/*GF_NET_CHAN_GET_PIXEL_AR and GF_NET_SERVICE_HAS_FORCED_VIDEO_SIZE*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL on_channel; - u32 hSpacing, vSpacing; - u32 width, height, pixel_format; - Bool is_srd; -} GF_NetComPixelAR; - -/*GF_NET_CHAN_GET_SRD*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL on_channel; - u32 w,h,x,y, width, height; - u32 dependent_group_index; -} GF_NetComSRDInfo; - -/*GF_NET_CHAN_VISIBILITY_HINT*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL on_channel; - //gives min_max coords of the visible rectangle associated with channels. - //min_x may be greater than max_x in case of 360 videos - u32 min_x, max_x, min_y, max_y; -} GF_NetComVisibililityHint; - - -/*GF_NET_SERVICE_INFO*/ -typedef struct __netinfocom -{ - u32 command_type; - /*currently NULL only*/ - LPNETCHANNEL on_channel; - u32 service_id; - /*packed trackNumber(16 bits)/totaltrack(16 bits)*/ - u32 track_info; - u32 genre; - const char *album; - const char *artist; - const char *comment; - const char *composer; - const char *name; - const char *writer; - const char *provider; - //as in MPEG_DASH role - const char *role; - const char *accessibility; - const char *rating; -} GF_NetComInfo; - -/*GF_NET_SERVICE_HAS_AUDIO*/ -typedef struct -{ - u32 command_type; - char *base_url; -} GF_NetComHasAudio; - -/*GF_NET_SERVICE_MIGRATION_INFO*/ -typedef struct -{ - u32 command_type; - - /*out: migration data, allocated and freed by the plugin*/ - char *data; - u32 data_len; -} GF_NetComMigration; - -/*GF_NET_SERVICE_EVENT*/ -typedef struct -{ - u32 command_type; - - /*type of the event being sent*/ - GF_Event evt; - Bool res; -} GF_NetComSendEvent; - -/*GF_NET_SERVICE_QUERY_NEXT*/ -typedef struct -{ - u32 command_type; - //input - - /*drops first segment */ - Bool drop_first_segment; - /*in : cache index we want to query; by defaut is 0*/ - u32 dependent_representation_index; - - //output - - /*out: next url to play after current one*/ - const char *next_url; - /*out: range in given URL to be played - usually 0-0 as segments are downloaded to cache - but can be non-zero when playing local files*/ - u64 start_range, end_range; - /*indicates discontinuity type at segment switch: - 0: no discontinuity (eg, follow-up of previous segment - 1: segment switch discontinuity (eg, bitrate/codec change) - 2: time discontinuity - seeking has occured and some segments were skipped - */ - u32 discontinuity_type; - /*out: initialization/switching segment of next URL to play, if different from previous init segment*/ - const char *next_url_init_or_switch_segment; - u64 switch_start_range, switch_end_range; - - /*set to key URL for current segment, or NULL if none*/ - const char *key_url; - /*set to key IV for current segment, or NULL if none*/ - bin128 *key_IV; - - Bool has_next; - /*module->proxy: indicates that currently downloaded segment should be checked. - proxy->module: indicates that the return URL is the currently downloaded one, not yet completed (progressive mode only). - */ - Bool current_download; - /*indicates that there is new data in the cache for the current URL (progressive mode only) - only valid if is_current_download is set.*/ - Bool has_new_data; - /*indicates that end of period is being reached (no new segements returned until period activates)*/ - Bool in_end_of_period; - -} GF_NetURLQuery; - -/*GF_NET_SERVICE_QUALITY_SWITCH*/ -typedef struct -{ - u32 command_type; - /*NULL only when request, or channel for witch quality has been changed for notif*/ - LPNETCHANNEL on_channel; - //switch quality up or down - request only - Bool up; - - Bool set_auto; - //0: current group, otherwise index of the depending_on group - u32 dependent_group_index; - //or ID of the quality to switch, as indicated in query quality - const char *ID; - //1+tile mode adaptation (doesn't change other selections) - u32 set_tile_mode_plus_one; - - u32 quality_degradation; -} GF_NetQualitySwitch; - - -/*GF_NET_SERVICE_QUALITY_QUERY*/ -typedef struct -{ - u32 command_type; - /*media object for which qualities are checked*/ - LPNETCHANNEL on_channel; - - //1-based index of quality to query - //if 0, the command is used to query the number of quality for the object and the number of objects depending on this one in dependent_group_index - u32 index; - //if not 0, the parameters are queried for the depending on grup with the given index - u32 dependent_group_index; - - //all out params - u32 bandwidth; - const char *ID; - const char *mime; - const char *codec; - u32 width; - u32 height; - Bool interlaced; - Double fps; - u32 par_num; - u32 par_den; - u32 sample_rate; - u32 nb_channels; - Bool disabled; - Bool is_selected; - Bool automatic; - u32 tile_adaptation_mode; -} GF_NetQualityQuery; - -/*GF_NET_SERVICE_STATUS_PROXY*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL channel; - GF_Err e; - Bool is_disconnect; - Bool is_add_media; - GF_Descriptor *desc; -} GF_NetServiceStatus; - -/*GF_NET_SERVICE_STATUS_PROXY*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL channel; - u32 width; - u32 height; - //max bits per color channel - u32 display_bit_depth; - u32 audio_bpp; - u32 channels; - u32 sample_rate; - const char *mime_query; - const char *mime_params; - //set upon reply - Bool mime_supported; -} GF_MediaCapQuery; - - -/*GF_NET_ASSOCIATED_CONTENT_LOCATION*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL channel; - //negative values mean "timeline is ready no need for timing message" - s32 timeline_id; - const char *external_URL; - Bool is_announce, is_splicing; - Bool reload_external; - Bool enable_if_defined; - Bool disable_if_defined; - Double activation_countdown; -} GF_AssociatedContentLocation; - -/*GF_NET_ASSOCIATED_CONTENT_TIMING*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL channel; - - u32 timeline_id; - u32 media_timescale; - u64 media_timestamp; - //for now only used in MPEG-2, so media_pts is in 90khz scale - u64 media_pts; - Bool force_reload; - Bool is_paused; - Bool is_discontinuity; - u64 ntp; -} GF_AssociatedContentTiming; - -/*GF_NET_CHAN_NALU_MODE*/ -typedef struct -{ - u32 command_type; - LPNETCHANNEL channel; - - //mode 0: extract in ISOBMF format (nalu size field + nalu) - //mode 1: extract in Annex B format (start code + nalu) - //mode 2: extract in Annex B format (start code + nalu) and disable sync sample seek mode (only used for scalable hybrid) - u32 extract_mode; -} GF_NALUExtractMode; - -/*GF_NET_SERVICE_CODEC_STAT_QUERY*/ -typedef struct -{ - /*avg_dec_time is the maximum average decoding time of all channels; - max_dec_time, irap_avg_dec_time and irap_max_dec_time is the maximum decoding time of a frame and a I frame in this channel*/ - u32 avg_dec_time, max_dec_time, irap_avg_dec_time, irap_max_dec_time; - /*flag codec has been reset*/ - Bool codec_reset; - Bool decode_only_rap; -} GF_CodecStat; - -/*GF_NET_SERVICE_QUERY_UTC_DELAY*/ -typedef struct -{ - s32 delay; -} GF_UTCDelay; - -typedef union __netcommand -{ - GF_NET_CHAN_CMD command_type; - GF_NetComBase base; - GF_NetComPlay play; - GF_NetComConfig cfg; - GF_NetComBuffer buffer; - GF_NetComDuration duration; - GF_NetComGetDSI get_dsi; - GF_NetComPadding pad; - GF_NetComTimeShift timeshift; - GF_NetComMapTime map_time; - GF_NetComStats net_stats; - GF_NetComDRMConfig drm_cfg; - GF_NetComGetESD cache_esd; - GF_NetComInfo info; - GF_NetComPixelAR par; - GF_NetComHasAudio audio; - GF_NetComMigration migrate; - GF_NetURLQuery url_query; - GF_NetQualitySwitch switch_quality; - GF_NetServiceStatus status; - GF_MediaCapQuery mcaps; - GF_NetComProxyData proxy_data; - GF_AssociatedContentLocation addon_info; - GF_AssociatedContentTiming addon_time; - GF_NALUExtractMode nalu_mode; - GF_NetComSendEvent send_event; - GF_NetQualityQuery quality_query; - GF_CodecStat codec_stat; - GF_NetComSRDInfo srd; - GF_NetComVisibililityHint visibility_hint; - GF_UTCDelay utc_delay; -} GF_NetworkCommand; - -/* - network modules -*/ - -/*interface name and version for input service*/ -#define GF_NET_CLIENT_INTERFACE GF_4CC('G', 'I', 'S', '1') - -typedef struct _netinterface -{ - /* interface declaration*/ - GF_DECL_MODULE_INTERFACE - - /*returns 1 if module can process this URL, 0 otherwise. This is only called when the file extension/mimeType cannot be - retrieved in the cfg file, otherwise the mime type/file ext is used to load service. Typically a module would - register its mime types in this function (cf gf_service_register_mime below) - */ - Bool (*CanHandleURL)(struct _netinterface *, const char *url); - - /*connects the service to the desired URL - the service handle is used for callbacks. - Only one service can be connected to a loaded interface. - */ - GF_Err (*ConnectService) (struct _netinterface *, GF_ClientService *serv, const char *url); - - /*disconnects service - the module is no longer used after this call - if immediate_shutdown is set the module - shall not attempt to get confirmation from remote side, it will be deleted right away - - NOTE: depending on how the client/server exchange is happening, it may happen that the CloseService is called - in the same context as a reply from your module. This can result into deadlocks if you're using threads. - You should therefore only try to destroy threads used in the interface shutdown process, which is guarantee - to be in a different context call. - */ - GF_Err (*CloseService) (struct _netinterface *); - - /*retrieves service decsriptor (expressed as an MPEG4 OD/IOD) for accessing this service - descriptor is allocated by plugin and destroyed by user - the IOD shall refer to the service attached to the module - @expect_type is a hint in case the service regenerates an IOD. It indicates whether the entry point expected is - INLINE, BIFS animation stream, video, audio or input sensor. - @sub_url: indicates fetching of an IOD for a given object in the service. - Only used for services handling the optional CanHandleURLInService below - NULL for main service - service extension for sub-service (cf CanHandleURLInService below). For ex, - "rtsp://myserver/file.mp4/ES_ID=3" and "rtsp://myserver/file.mp4/ES_ID=4" - or "file.avi#audio" and "file.avi#video".In this case a partial IOD for the desired object is expected - Note: once a service is acknowledged as connected, this function must be executed synchronously - The service can return NULL for a descriptor: - * if the expected media type is a single media, this means the media couldn't be found - * if the expected media type is a scene, this means the terminalk shall create and manage the scene - */ - GF_Descriptor *(*GetServiceDescriptor) (struct _netinterface *, u32 expect_type, const char *sub_url); - - - /*sends command to the service / channel - cf command structure*/ - GF_Err (*ServiceCommand) (struct _netinterface *, GF_NetworkCommand *com); - - /*data channel setup - url is either - "ES_ID=ID" where ID is the stream ID in this service - or a control string depending on the service/stream. The URL is first used to load a module able to handle it, - so the module has no redirection to handle - */ - GF_Err (*ConnectChannel) (struct _netinterface *, LPNETCHANNEL channel, const char *url, Bool upstream); - /*teardown of data channel*/ - GF_Err (*DisconnectChannel) (struct _netinterface *, LPNETCHANNEL channel); - - /*optional - fetch MPEG4 data from channel - data shall not be duplicated and must be released at ReleaseData - SL info shall be written to provided header - if the data is a real SL packet the flag sl_compressed shall be - set to signal the app this is a full SL pdu (@out_sl_hdr is then ignored) - set to NULL if not supported - */ - GF_Err (*ChannelGetSLP) (struct _netinterface *, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, GF_SLHeader *out_sl_hdr, Bool *sl_compressed, GF_Err *out_reception_status, Bool *is_new_data); - - /*optional - release SLP data allocated on channel by the previous call, if any - set to NULL if not supported*/ - GF_Err (*ChannelReleaseSLP) (struct _netinterface *, LPNETCHANNEL channel); - - /*this is needed for modules to query other modules, the typical case being 2 ESD URLs pointing to the - same media (audio and video streams in an RTSP session). This is always used on loaded modules but - doesn't have to be declared*/ - Bool (*CanHandleURLInService)(struct _netinterface *, const char *url); - - /*private*/ - void *priv; - - /*proxy stuff*/ - GF_Err (*query_proxy)(struct _netinterface *, GF_NetworkCommand *param); - void *proxy_udta; - Bool proxy_type; /* 0 corresponds to a proxy without full command support */ - /*! - * This is needed for modules supporting mime types, when this method is called, - * the module has to call gf_service_register_mime() for all the mime-types - * its supports. - * \return The number of declared mime types - * \see gf_service_register_mime(GF_InputService *, const char *, const char *, const char *) - */ - u32 (*RegisterMimeTypes) (const struct _netinterface *); -} GF_InputService; - -/*callback functions - these can be linked with non-LGPL modules*/ - -/*to call on service (if channel is NULL) or channel connect completed*/ -void gf_service_connect_ack(GF_ClientService *service, LPNETCHANNEL ns, GF_Err response); -/*to call on service (if channel is NULL) or channel disconnect completed*/ -void gf_service_disconnect_ack(GF_ClientService *service, LPNETCHANNEL ns, GF_Err response); -/* acknowledgement of service command - service commands handle both services and channels -Most of the time commands are NOT acknowledged, typical acknowledgement are needed for setup and control -with remote servers. -command can also be triggered from the service (QoS, broadcast announcements) -cf above for command usage -*/ -void gf_service_command(GF_ClientService *service, GF_NetworkCommand *com, GF_Err response); -/*to call when data packet is received. -@data, data_size: data received -@hdr: uncompressed SL header passed with data for stream sync - if not present then data shall be a valid SL packet - (header + PDU). Note that using an SLConfig resulting in an empty GF_SLHeader allows sending raw data directly -@reception_status: data reception status. To signal end of stream, set this to GF_EOS -*/ -void gf_service_send_packet(GF_ClientService *service, LPNETCHANNEL ns, char *data, u32 data_size, GF_SLHeader *hdr, GF_Err reception_status); -/*returns URL associated with service (so that you don't need to store it)*/ -const char *gf_service_get_url(GF_ClientService *service); - -/*adds a new media from network. !! The media descriptor is then owned/destroyed by the term!! -media_desc: object descriptor for the new media. May be NULL to force scene rebuilt. -no_scene_check: specifies if the scene description shall be rebuilt or not. -*/ -void gf_service_declare_media(GF_ClientService *service, GF_Descriptor *media_desc, Bool no_scene_update); -//returns module interface of the service -GF_InputService *gf_service_get_interface(GF_ClientService *service); - - -/*check if @fileExt extension is supported for given mimeType, and if associated with module. If mimeType not registered, register it for given module*/ -Bool gf_service_check_mime_register(GF_InputService *ifce, const char *mimeType, const char *extList, const char *description, const char *fileExt); -/*register mime types & file extensions - most modules should only need the check version above*/ -void gf_service_register_mime(const GF_InputService *ifce, const char *mimeType, const char *extList, const char *description); - -/*file downloading - can and MUST be used by any module (regardless of license) in order not to interfere -with net management*/ -/*creates a new downloading session in the given service - if url is relative, it will be interpreted through -the service URL*/ -GF_DownloadSession * gf_service_download_new(GF_ClientService *service, const char *url, u32 flags, gf_dm_user_io user_io, void *cbk); -/*closes the downloading session*/ -void gf_service_download_del(GF_DownloadSession * dnload); -/*send progress and connection messages to user...*/ -void gf_service_download_update_stats(GF_DownloadSession * sess); - - -/*MPEG-4 media cache interface name*/ -#define GF_STREAMING_MEDIA_CACHE GF_4CC('G', 'M', 'C', 0x01) - -typedef struct _cacheinterface -{ - /* interface declaration*/ - GF_DECL_MODULE_INTERFACE - - /*opens media cache at given place - extension is handled by cache module - @serv: service owning cache (eg, where to send data when requested) - @keep_existing_files: don't overwrite previously recorded sessions*/ - GF_Err (*Open)(struct _cacheinterface *, GF_ClientService *serv, const char *location_and_name, Bool keep_existing_files); - /*closes media cache, delete file(s) if desired*/ - GF_Err (*Close)(struct _cacheinterface *, Bool delete_cache); - /*writes data to cache. data is always a complete AU as reconstructed by gpac core - If first time data is written, user should query channel desc through service commands*/ - GF_Err (*Write)(struct _cacheinterface *, LPNETCHANNEL ch, char *data, u32 data_size, GF_SLHeader *sl_hdr); - - /*same as reader, except they MUST be provided - in other words, only PULL mode is supported for cache - at the current time*/ - GF_Err (*ServiceCommand) (struct _cacheinterface *, GF_NetworkCommand *com); - GF_Err (*ChannelGetSLP) (struct _cacheinterface *, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, GF_SLHeader *out_sl_hdr, Bool *sl_compressed, GF_Err *out_reception_status, Bool *is_new_data); - GF_Err (*ChannelReleaseSLP) (struct _cacheinterface *, LPNETCHANNEL channel); - - /*module private*/ - void *priv; -} GF_StreamingCache; - - -#ifdef __cplusplus -} -#endif - -#endif /*_GF_SERVICE_H_*/ diff --git a/include/gpac/modules/video_out.h b/include/gpac/modules/video_out.h index af9cd98..7ea84a1 100644 --- a/include/gpac/modules/video_out.h +++ b/include/gpac/modules/video_out.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / modules interfaces @@ -77,8 +77,6 @@ enum /*HW supports line blitting*/ GF_VIDEO_HW_HAS_LINE_BLIT = (1<<15), - /*HW supports locking a surface by device context (Win32 only)*/ - GF_VIDEO_HW_HAS_HWND_HDC = (1<<16), /*HW only supports direct rendering mode*/ GF_VIDEO_HW_DIRECT_ONLY = (1<<17), }; @@ -92,7 +90,7 @@ typedef struct typedef struct _gf_sc_texture_handler GF_TextureH; /*interface name and version for video output*/ -#define GF_VIDEO_OUTPUT_INTERFACE GF_4CC('G','V','O','1') +#define GF_VIDEO_OUTPUT_INTERFACE GF_4CC('G','V','O','5') /* video output interface @@ -222,7 +220,7 @@ typedef struct _video_out /*maximum pixel disparity*/ u32 disparity; /*nominal display viewing distance in cm*/ - Fixed view_distance; + Fixed dispdist; /*driver private*/ void *opaque; diff --git a/include/gpac/mpd.h b/include/gpac/mpd.h new file mode 100644 index 0000000..a35cc6c --- /dev/null +++ b/include/gpac/mpd.h @@ -0,0 +1,1136 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre - Cyril Concolato + * Copyright (c) Telecom ParisTech 2010-2012 + * All rights reserved + * + * This file is part of GPAC / 3GPP/MPEG Media Presentation Description input module + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ +#ifndef _MPD_H_ +#define _MPD_H_ + +#include <gpac/constants.h> +#include <gpac/xml.h> +#include <gpac/media_tools.h> + +#ifndef GPAC_DISABLE_CORE_TOOLS + +/*! +\file <gpac/mpd.h> +\brief Utility tools for dash, smooth and HLS manifest loading. +*/ + +/*! +\addtogroup mpd_grp MPD/M3U8/Smooth Manifest Parsing +\ingroup media_grp +\brief Utility tools DASH manifests + +This section documents the DASH, Smooth and HLS manifest parsing functions of the GPAC framework. +@{ +*/ + +/*! DASH template resolution mode*/ +typedef enum +{ + /*! resolve template for segment*/ + GF_DASH_TEMPLATE_SEGMENT = 0, + /*! resolve template for initialization segment*/ + GF_DASH_TEMPLATE_INITIALIZATION, + /*! resolve template for segment template*/ + GF_DASH_TEMPLATE_TEMPLATE, + /*! resolve template for segment template with %d padding*/ + GF_DASH_TEMPLATE_TEMPLATE_WITH_PATH, + /*! resolve template for initialization segment template*/ + GF_DASH_TEMPLATE_INITIALIZATION_TEMPLATE, + /*! resolve template for segment index*/ + GF_DASH_TEMPLATE_REPINDEX, + /*! resolve template for segment index template*/ + GF_DASH_TEMPLATE_REPINDEX_TEMPLATE, + /*! resolve template for segment index template with %d padding*/ + GF_DASH_TEMPLATE_REPINDEX_TEMPLATE_WITH_PATH, + /*! same as GF_DASH_TEMPLATE_INITIALIZATION but skip default "init" concatenation */ + GF_DASH_TEMPLATE_INITIALIZATION_SKIPINIT, + /*! same as GF_DASH_TEMPLATE_INITIALIZATION_TEMPLATE but skip default "init" concatenation*/ + GF_DASH_TEMPLATE_INITIALIZATION_TEMPLATE_SKIPINIT, +} GF_DashTemplateSegmentType; + +/*! formats the segment name according to its template +\param seg_type the desired format mode +\param is_bs_switching set to GF_TRUE to indicate the target segment is a bitstream switching segment +\param segment_name target buffer where the segment name is formatted - size must be GF_MAX_PATH +\param rep_id ID of the target representation +\param base_url base URL, may be NULL +\param seg_rad_name base name of the output segements (eg, myfile_ZZZ), shall not be NULL, may be empty ("") +\param seg_ext segment extensions +\param start_time start time of the segment in MPD timescale +\param bandwidth bandwidth used for the representation +\param segment_number number of the target segment +\param use_segment_timeline indicates if segmentTimeline is used for segment addressing in the MPD +\return error if any +*/ +GF_Err gf_media_mpd_format_segment_name(GF_DashTemplateSegmentType seg_type, Bool is_bs_switching, char *segment_name, const char *rep_id, const char *base_url, const char *seg_rad_name, const char *seg_ext, u64 start_time, u32 bandwidth, u32 segment_number, Bool use_segment_timeline); + +/*! metrics, not yet supported*/ +typedef struct +{ + u32 dummy; +} GF_MPD_Metrics; + +/*! Content Component description*/ +typedef struct +{ + /*! content component ID*/ + u32 id; + /*! content component mime type*/ + char *type; + /*! content component language*/ + char *lang; +} GF_MPD_ContentComponent; + + +/*! macro for extensible MPD element +Some elments are typically overloaded in XML, we keep the attributes / children nodes here. The attributes list is NULL if no extensions were found, otherwise it is a list of GF_XMLAttribute. +The children list is NULL if no extensions were found, otherwise it is a list of GF_XMLNode +*/ +#define MPD_EXTENSIBLE \ + GF_List *attributes; \ + GF_List *children; \ + +/*! basic extensible MPD element*/ +typedef struct +{ + MPD_EXTENSIBLE +} GF_MPD_ExtensibleVirtual; + +/*! basic extensible MPD descritpor*/ +typedef struct +{ + MPD_EXTENSIBLE + /*! mandatory schemeid URL*/ + char *scheme_id_uri; + /*! associated value, may be NULL*/ + char *value; + /*! associated ID, may be NULL*/ + char *id; +} GF_MPD_Descriptor; + +/*! subset, not yet supported*/ +typedef struct +{ + u32 dummy; +} GF_MPD_Subset; + +/*! SegmentTimeline entry*/ +typedef struct +{ + /*! startTime in representation's MPD timescale*/ + u64 start_time; + /*! duration in representation's MPD timescale - mandatory*/ + u32 duration; /*MANDATORY*/ + /*! may be 0xFFFFFFFF (-1) (\warning this needs further testing)*/ + u32 repeat_count; +} GF_MPD_SegmentTimelineEntry; + +/*! Segment Timeline*/ +typedef struct +{ + /*! list of entries*/ + GF_List *entries; +} GF_MPD_SegmentTimeline; + +/*! Byte range info*/ +typedef struct +{ + /*! start range (offset of first byte in associated resource)*/ + u64 start_range; + /*! start range (offset of last byte in associated resource)*/ + u64 end_range; +} GF_MPD_ByteRange; + + +/*! base URL*/ +typedef struct +{ + /*! URL*/ + char *URL; + /*! service location if any*/ + char *service_location; + /*! byte range if any*/ + GF_MPD_ByteRange *byte_range; + + /*!GPAC internal: redirection for that URL */ + char *redirection; +} GF_MPD_BaseURL; + +/*! MPD URL*/ +typedef struct +{ + /*! URL of source*/ + char *sourceURL; + /*! byte range if any*/ + GF_MPD_ByteRange *byte_range; + + /*! GPAC internal - indicates the URL has already been solved*/ + Bool is_resolved; +} GF_MPD_URL; + +/*! MPD fraction*/ +typedef struct +{ + /*! numerator*/ + u32 num; + /*! denominator*/ + u32 den; +} GF_MPD_Fractional; + +/*! GPAC internal, ISOBMFF info for base64 and other embedding, see N. Bouzakaria thesis*/ +typedef struct +{ + /*! ID of track/pid*/ + u32 trackID; + /*! base64 STSD entry*/ + char *stsd; + /*! media offset*/ + s64 mediaOffset; +} GF_MPD_ISOBMFInfo; + + +/*! other XML descriptors*/ +typedef struct +{ + /*! list of XML descriptors*/ + char *xml_desc; +} GF_MPD_other_descriptors; + + +/*! macro for MPD segment base*/ +#define GF_MPD_SEGMENT_BASE \ + u32 timescale; \ + u64 presentation_time_offset; \ + u32 time_shift_buffer_depth; /* expressed in milliseconds */ \ + GF_MPD_ByteRange *index_range; \ + Bool index_range_exact; \ + Double availability_time_offset; \ + GF_MPD_URL *initialization_segment; \ + GF_MPD_URL *representation_index; \ + + + +/*! MPD segment base*/ +typedef struct +{ + /*! inherits segment base*/ + GF_MPD_SEGMENT_BASE +} GF_MPD_SegmentBase; + +/*! macro for multiple segment base +WARNING: duration is expressed in GF_MPD_SEGMENT_BASE timescale unit + startnumber=(u32)-1 if unused, 1 bydefault. +*/ +#define GF_MPD_MULTIPLE_SEGMENT_BASE \ + GF_MPD_SEGMENT_BASE \ + u64 duration; \ + u32 start_number; \ + GF_MPD_SegmentTimeline *segment_timeline; \ + GF_MPD_URL *bitstream_switching_url; \ + +/*! Multiple segment base*/ +typedef struct +{ + /*! inherits multiple segment base*/ + GF_MPD_MULTIPLE_SEGMENT_BASE +} GF_MPD_MultipleSegmentBase; + +/*! segment URL*/ +typedef struct +{ + /*! media URL if any*/ + char *media; + /*! media range if any*/ + GF_MPD_ByteRange *media_range; + /*! index url if any*/ + char *index; + /*! index range if any*/ + GF_MPD_ByteRange *index_range; + /*! duration of segment*/ + u64 duration; + /*! key URL of segment, HLS only*/ + char *key_url; + /*! key IV of segment, HLS only*/ + bin128 key_iv; + /*! UTC start time of segment, HLS only*/ + u64 hls_utc_start_time; +} GF_MPD_SegmentURL; + +/*! SegmentList*/ +typedef struct +{ + /*! inherits multiple segment base*/ + GF_MPD_MULTIPLE_SEGMENT_BASE + /*! list of segments - can be NULL if no segment*/ + GF_List *segment_URLs; + /*! xlink URL for external list*/ + char *xlink_href; + /*! xlink evaluation on load if set, otherwise on use*/ + Bool xlink_actuate_on_load; + + /*! GPAC intenral, number of consecutive xlink while solving*/ + u32 consecutive_xlink_count; + /*! GPAC intenral, we store the segment template here*/ + char *dasher_segment_name; +} GF_MPD_SegmentList; + +/*! SegmentTemplate*/ +typedef struct +{ + /*! inherits multiple segment base*/ + GF_MPD_MULTIPLE_SEGMENT_BASE + /*! media segment template*/ + char *media; + /*! index segment template*/ + char *index; + /*! init segment template*/ + char *initialization; + /*! bitstream switching segment template*/ + char *bitstream_switching; +} GF_MPD_SegmentTemplate; + +/*! MPD scan types*/ +typedef enum +{ + /*! unknown*/ + GF_MPD_SCANTYPE_UNKNOWN, + /*! progressive*/ + GF_MPD_SCANTYPE_PROGRESSIVE, + /*! interlaced*/ + GF_MPD_SCANTYPE_INTERLACED +} GF_MPD_ScanType; + +/*! Macro for common attributes and elements (representation, AdaptationSet, Preselection, ...) + +not yet implemented; + GF_List *inband_event_stream; \ + GF_List *switching; \ + GF_List *random_access; \ + GF_List *group_labels; \ + GF_List *labels; \ + GF_List *content_popularity; \ + +MANDATORY: + mime_type + codecs +*/ +#define GF_MPD_COMMON_ATTRIBUTES_ELEMENTS \ + char *profiles; \ + u32 width; \ + u32 height; \ + GF_MPD_Fractional *sar; \ + GF_MPD_Fractional *framerate; \ + u32 samplerate; \ + char *mime_type; \ + char *segmentProfiles; \ + char *codecs; \ + u32 maximum_sap_period; \ + u32 starts_with_sap; \ + Double max_playout_rate; \ + Bool coding_dependency; \ + GF_MPD_ScanType scan_type; \ + u32 selection_priority; \ + char *tag; \ + GF_List *frame_packing; \ + GF_List *audio_channels; \ + GF_List *content_protection; \ + GF_List *essential_properties; \ + GF_List *supplemental_properties; \ + GF_List *producer_reference_time; \ + GF_List *isobmf_tracks; \ + +/*! common attributes*/ +typedef struct { + /*! inherits common attributes*/ + GF_MPD_COMMON_ATTRIBUTES_ELEMENTS +} GF_MPD_CommonAttributes; + +/*! type of reference clock */ +typedef enum +{ + GF_MPD_PRODUCER_REF_ENCODER = 0, + GF_MPD_PRODUCER_REF_CAPTURED, + GF_MPD_PRODUCER_REF_APPLICATION, +} GF_MPD_ProducerRefType; + +/*! producer reference time*/ +typedef struct { + /*! ID of producer */ + u32 ID; + /*! is timing inband (prft in segment) */ + Bool inband; + /*! clock type*/ + GF_MPD_ProducerRefType type; + /*! scheme for application ref type*/ + char *scheme; + /*! wallclock time as UTC timestamp*/ + char *wallclock; + /*! presentation time in timescale of the Representation*/ + u64 presentation_time; + /*! UTC timing desc if any/*/ + GF_MPD_Descriptor *utc_timing; +} GF_MPD_ProducerReferenceTime; + + +/*! SubRepresentation*/ +typedef struct { + /*! inherits common attributes*/ + GF_MPD_COMMON_ATTRIBUTES_ELEMENTS + /*! level of subrepresentation*/ + u32 level; + /*! dependency level of subrepresentation*/ + char *dependecy_level; + /*! bandwidth of subrepresentation, MANDATORY if level set*/ + u32 bandwidth; + /*! content comonents string*/ + char *content_components; +} GF_MPD_SubRepresentation; + +/*! State for representation playback, GPAC internal*/ +typedef struct +{ + /*! disabled*/ + Bool disabled; + /*! name of cahed init segemnt URL (usually local cache or gmem:// url)*/ + char *cached_init_segment_url; + /*! if set indicates the associated gmem memory is owned by this representation*/ + Bool owned_gmem; + /*! start range of the init segment*/ + u64 init_start_range; + /*! end range of the init segment*/ + u64 init_end_range; + /*! number of switching probes*/ + u32 probe_switch_count; + /*! init segment blob*/ + GF_Blob init_segment; + /*! associated key URL if any, for HLS*/ + char *key_url; + /*! associated key IV if any, for HLS*/ + bin128 key_IV; + /*! previous maximum speed that this representation can be played, or 0 if it has never been played*/ + Double prev_max_available_speed; + /*! after switch we may have some buffered segments of the previous representation; so codec stats at this moment is unreliable. we should wait after the codec reset*/ + Bool waiting_codec_reset; + /*! BOLA Utility*/ + Double bola_v; + + /*! index of the next enhancement representation plus 1, 0 is reserved in case of the highest representation*/ + u32 enhancement_rep_index_plus_one; + /*! set to true if the representation comes from a broadcast link (ATSC3, eMBMS)*/ + Bool broadcast_flag; + /*! opaque data*/ + void *udta; +} GF_DASH_RepresentationPlayback; + +/*! segment context used by the dasher, GPAC internal*/ +typedef struct +{ + /*! ID of active period*/ + char *period_id; + /*! start of active period*/ + Double period_start; + /*! duration of active period*/ + Double period_duration; + /*! if GF_TRUE, representation is over*/ + Bool done; + /*! niumber of last packet processed (to resume dashing)*/ + u64 last_pck_idx; + /*! number of last produced segment*/ + u32 seg_number; + /*! source URL*/ + char *src_url; + /*! name of init segment*/ + char *init_seg; + /*! segment template (half-resolved, no more %s in it)*/ + char *template_seg; + /*! index template (half-resolved, no more %s in it)*/ + char *template_idx; + /*! ID of output PID*/ + u32 pid_id; + /*! ID of source PID*/ + u32 source_pid; + /*! ID of source dependent PID*/ + u32 dep_pid_id; + /*! indicates if this representation drives the AS segmentation*/ + Bool owns_set; + /*! indicates if uses multi PID (eg, multiple sample descriptions in init segment)*/ + Bool multi_pids; + /*! target segment duration for this stream*/ + Double dash_dur; + /*! estimated next segment start time in MPD timescale*/ + u64 next_seg_start; + /*! first CTS of stream in stream timescale*/ + u64 first_cts; + /*! first DCTS of stream in stream timescale*/ + u64 first_dts; + /*! number of past repetitions of the stream */ + u32 nb_repeat; + /*! timestamp offset (in stream timescale) due to repetitions*/ + u64 ts_offset; + /*! mpd timescale of the stream*/ + u32 mpd_timescale; + /*! estimated next DTS of the stream in media timescale*/ + u64 est_next_dts; + /*! cumulated sub duration of the stream (to handle partial file dashing)*/ + Double cumulated_subdur; + /*! cumulated duration of the stream (to handle loops)*/ + Double cumulated_dur; + /*! space-separated list of PID IDs of streams muxed with this stream in a multiplex representation*/ + char *mux_pids; + /*! number of segments purged from the timeline and from disk*/ + u32 segs_purged; + /*! cumulated duration of segments purged*/ + Double dur_purged; + /*! next moof sequence number*/ + u32 moof_sn; + /*! next moof sequence number increment*/ + u32 moof_sn_inc; + /*! ID of last dynamic period in manifest*/ + u32 last_dyn_period_id; + /*! one subdur was forced on this rep due to looping*/ + Bool subdur_forced; +} GF_DASH_SegmenterContext; + +/*! Segment context - GPAC internal, used to produce HLS manifests and segment lists/timeline*/ +typedef struct +{ + /*! time in mpd timescale*/ + u64 time; + /*! duration in mpd timescale*/ + u64 dur; + /*! name as printed in segment lists / m3u8*/ + char *filename; + /*! full path of file*/ + char *filepath; + /*! file size in bytes*/ + u32 file_size; + /*! file offset in bytes*/ + u64 file_offset; + /*! index size in bytes*/ + u32 index_size; + /*! index offset in bytes*/ + u64 index_offset; + /*! segment number */ + u32 seg_num; +} GF_DASH_SegmentContext; + +/*! Representation*/ +typedef struct { + /*! inherits common attributes*/ + GF_MPD_COMMON_ATTRIBUTES_ELEMENTS + + /*! ID of representation, mandatory*/ + char *id; + /*! bandwidth in bits per secon, mandatory*/ + u32 bandwidth; + /*! quality ranking*/ + u32 quality_ranking; + /*! dependency IDs of dependent representations*/ + char *dependency_id; + /*! stream structure ID, not used by GPAC*/ + char *media_stream_structure_id; + /*! list of baseURLs if any*/ + GF_List *base_URLs; + + /*! segment base of representation, or NULL if list or template is used*/ + GF_MPD_SegmentBase *segment_base; + /*! segment list of representation, or NULL if base or template is used*/ + GF_MPD_SegmentList *segment_list; + /*! segment template of representation, or NULL if base or list is used*/ + GF_MPD_SegmentTemplate *segment_template; + /*! number of subrepresentation*/ + GF_List *sub_representations; + + /*! other MPD descriptors*/ + GF_List *other_descriptors; + + /*! all the below members are GPAC internal*/ + + /*! GPAC playback implementation*/ + GF_DASH_RepresentationPlayback playback; + /*! internal, HLS: min sequence number of segments in playlist*/ + u32 m3u8_media_seq_min; + /*! internal, HLS: max sequence number of segments in playlist*/ + u32 m3u8_media_seq_max; + + /*! GPAC dasher context*/ + GF_DASH_SegmenterContext *dasher_ctx; + /*! list of segment states*/ + GF_List *state_seg_list; + /*! segment timescale (for HLS)*/ + u32 timescale; + /*! stream type (for HLS)*/ + u32 streamtype; + /*! segment manifest timescale (for HLS)*/ + u32 timescale_mpd; + /*! dash duration*/ + Double dash_dur; + /*! init segment name for HLS single file*/ + const char *hls_single_file_name; + /*! number of audio channels - HLS only*/ + u32 nb_chan; + /*! video FPS - HLS only*/ + Double fps; + /*! groupID (for HLS)*/ + const char *groupID; + + /*! user assigned m3u8 name for this representation*/ + const char *m3u8_name; + /*! generated m3u8 name if no user-assigned one*/ + char *m3u8_var_name; + /*! temp file for m3u8 generation*/ + FILE *m3u8_var_file; +} GF_MPD_Representation; + +/*! AdaptationSet*/ +typedef struct +{ + /*! inherits common attributes*/ + GF_MPD_COMMON_ATTRIBUTES_ELEMENTS + /*! ID of this set*/ + u32 id; + /*! group ID for this set, default value is -1: not set in MPD*/ + s32 group; + /*! language*/ + char *lang; + /*! mime type*/ + char *content_type; + /*! picture aspect ratio*/ + GF_MPD_Fractional *par; + /*! min bandwidth in bps*/ + u32 min_bandwidth; + /*! max bandwidth in bps*/ + u32 max_bandwidth; + /*! min width in pixels*/ + u32 min_width; + /*! max width in pixels*/ + u32 max_width; + /*! min height in pixels*/ + u32 min_height; + /*! max height in pixels*/ + u32 max_height; + /*! min framerate*/ + GF_MPD_Fractional min_framerate; + /*! max framerate*/ + GF_MPD_Fractional max_framerate; + /*! set if segment boundaries are time-aligned across qualities*/ + Bool segment_alignment; + /*! set if a single init segment is needed (no reinit at quality switch)*/ + Bool bitstream_switching; + /*! set if subsegment boundaries are time-aligned across qualities*/ + Bool subsegment_alignment; + /*! set if subsegment all start with given SAP type, 0 otherwise*/ + u32 subsegment_starts_with_sap; + /*! accessibility descriptor list if any*/ + GF_List *accessibility; + /*! role descriptor list if any*/ + GF_List *role; + /*! rating descriptor list if any*/ + GF_List *rating; + /*! viewpoint descriptor list if any*/ + GF_List *viewpoint; + /*! content component descriptor list if any*/ + GF_List *content_component; + + /*! base URL (alternate location) list if any*/ + GF_List *base_URLs; + /*! segment base of representation, or NULL if list or template is used*/ + GF_MPD_SegmentBase *segment_base; + /*! segment list of representation, or NULL if base or template is used*/ + GF_MPD_SegmentList *segment_list; + /*! segment template of representation, or NULL if base or list is used*/ + GF_MPD_SegmentTemplate *segment_template; + /*! list of representations*/ + GF_List *representations; + /*! xlink URL for the adaptation set*/ + char *xlink_href; + /*! xlink evaluation on load if set, otherwise on use*/ + Bool xlink_actuate_on_load; + /*! other descriptors*/ + GF_List *other_descriptors; + + /*! user private, eg used by dasher*/ + void *udta; +} GF_MPD_AdaptationSet; + +/*! MPD offering type*/ +typedef enum { + /*! content is statically available*/ + GF_MPD_TYPE_STATIC, + /*! content is dynamically available*/ + GF_MPD_TYPE_DYNAMIC, + /*! content is the last if a dynamical offering, converts MPD to static (GPAC internal)*/ + GF_MPD_TYPE_DYNAMIC_LAST, +} GF_MPD_Type; + +/*! Period*/ +typedef struct +{ + /*! ID of period*/ + char *ID; + /*! start time in milliseconds, relative to the start of the MPD */ + u64 start; + /*! duration in milliseconds*/ + u64 duration; + /*! set to GF_TRUE if adaptation sets in the period don't need reinit when switching quality*/ + Bool bitstream_switching; + + /*! base URL (alternate location) list if any*/ + GF_List *base_URLs; + /*! segment base of representation, or NULL if list or template is used*/ + GF_MPD_SegmentBase *segment_base; + /*! segment list of representation, or NULL if base or template is used*/ + GF_MPD_SegmentList *segment_list; + /*! segment template of representation, or NULL if base or list is used*/ + GF_MPD_SegmentTemplate *segment_template; + /*! list of adaptation sets*/ + GF_List *adaptation_sets; + /*! list of subsets (not yet implemented)*/ + GF_List *subsets; + /*! xlink URL for the period*/ + char *xlink_href; + /*! xlink evaluation on load if set, otherwise on use*/ + Bool xlink_actuate_on_load; + + /*! other descriptors*/ + GF_List *other_descriptors; + /*! original xlink URL before resolution, used to identify already resolved xlinks in MPD updates - GPAC internal*/ + char *origin_base_url; + /*! type of the period - GPAC internal*/ + GF_MPD_Type type; +} GF_MPD_Period; + +/*! Program info*/ +typedef struct +{ + /*! languae*/ + char *lang; + /*! title*/ + char *title; + /*! source*/ + char *source; + /*! copyright*/ + char *copyright; + /*! URL to get more info*/ + char *more_info_url; +} GF_MPD_ProgramInfo; + +/*! MPD*/ +typedef struct { + /*! inherits from extensible*/ + MPD_EXTENSIBLE + /*! ID of the MPD*/ + char *ID; + /*! profile, mandatory*/ + char *profiles; + /*! offering type*/ + GF_MPD_Type type; + /*! UTC of availability start anchor, expressed in milliseconds, MANDATORY if type=dynamic*/ + u64 availabilityStartTime; + /*! UTC of availability end anchor, expressed in milliseconds*/ + u64 availabilityEndTime; + /*! UTC of last publishing of the manifest*/ + u64 publishTime; + /*! presentation duration in milliseconds, MANDATORY if type=static*/ + u64 media_presentation_duration; + /*! refresh rate of MPD for dynamic offering, in milliseconds */ + u32 minimum_update_period; + /*! minimum buffer time in milliseconds, MANDATORY*/ + u32 min_buffer_time; + + /*! time shift depth in milliseconds */ + u32 time_shift_buffer_depth; + /*! presentation delay in milliseconds */ + u32 suggested_presentation_delay; + /*! maximum segment duration in milliseconds */ + u32 max_segment_duration; + /*! maximum subsegment duration in milliseconds */ + u32 max_subsegment_duration; + + /*! list of GF_MPD_ProgramInfo */ + GF_List *program_infos; + /*! list of GF_MPD_BaseURL */ + GF_List *base_URLs; + /*! list of strings */ + GF_List *locations; + /*! list of Metrics */ + GF_List *metrics; + /*! list of GF_MPD_Period */ + GF_List *periods; + + /*! set during parsing, to set during authoring, won't be freed by GPAC*/ + const char *xml_namespace; + + /*! UTC timing desc if any/*/ + GF_List *utc_timings; + + /* internal variables for dasher*/ + + /*! dasher init NTP clock in ms - GPAC internal*/ + u64 gpac_init_ntp_ms; + /*! dasher next generation time NTP clock in ms - GPAC internal*/ + u64 gpac_next_ntp_ms; + /*! dasher current MPD time in milliseconds - GPAC internal*/ + u64 gpac_mpd_time; + + /*! indicates the GPAC state info should be written*/ + Bool write_context; + /*! indicates this is the last static serialization of a previously dynamic MPD*/ + Bool was_dynamic; + /*! indicates the HLS variant files shall be created, otherwise temp files are used*/ + Bool create_m3u8_files; + /*! indicates to insert clock reference in variant playlists*/ + Bool m3u8_time; +} GF_MPD; + +/*! parses an MPD Element (and subtree) from DOM +\param root root of DOM parsing result +\param mpd MPD structure to fill +\param base_url base URL of the DOM document +\return error if any +*/ +GF_Err gf_mpd_init_from_dom(GF_XMLNode *root, GF_MPD *mpd, const char *base_url); +/*! parses an MPD Period element (and subtree) from DOM +\param root root of DOM parsing result +\param mpd MPD structure to fill +\param base_url base URL of the DOM document +\return error if any +*/ +GF_Err gf_mpd_complete_from_dom(GF_XMLNode *root, GF_MPD *mpd, const char *base_url); +/*! MPD constructor +\return a new MPD*/ +GF_MPD *gf_mpd_new(); +/*! MPD destructor +\param mpd the target MPD*/ +void gf_mpd_del(GF_MPD *mpd); +/*! frees a GF_MPD_SegmentURL structure (type-casted to void *) +\param ptr the target GF_MPD_SegmentURL +*/ +void gf_mpd_segment_url_free(void *ptr); +/*! frees a GF_MPD_SegmentBase structure (type-casted to void *) +\param ptr the target GF_MPD_SegmentBase +*/ +void gf_mpd_segment_base_free(void *ptr); +/*! parses a new GF_MPD_SegmentURL from its DOM description +\param container the container list where to insert the segment URL +\param root the DOM description of the segment URL +*/ +void gf_mpd_parse_segment_url(GF_List *container, GF_XMLNode *root); + +/*! parses a xsDateTime +\param attr the date time value +\return value as UTC timestamp +*/ +u64 gf_mpd_parse_date(const char * const attr); + +/*! frees a GF_MPD_URL structure (type-casted to void *) +\param _item the target GF_MPD_URL +*/ +void gf_mpd_url_free(void *_item); + +/*! MPD Period constructor +\return a new MPD Period*/ +GF_MPD_Period *gf_mpd_period_new(); +/*! MPD Period destructor +\param _item the MPD Period to free*/ +void gf_mpd_period_free(void *_item); +/*! writes an MPD to a file stream +GF_Err gf_mpd_write(GF_MPD const * const mpd, FILE *out, Bool compact); +\param mpd the target MPD to write +\param out the target file object +\param compact if set, removes all new line and indentation in the output +\return error if any +*/ +GF_Err gf_mpd_write(GF_MPD const * const mpd, FILE *out, Bool compact); +/*! writes an MPD to a local file +GF_Err gf_mpd_write(GF_MPD const * const mpd, FILE *out, Bool compact); +\param mpd the target MPD to write +\param file_name the target file name +\return error if any +*/ +GF_Err gf_mpd_write_file(GF_MPD const * const mpd, const char *file_name); + +/*! writes an MPD to a m3u8 playlist +GF_Err gf_mpd_write(GF_MPD const * const mpd, FILE *out, Bool compact); +\param mpd the target MPD to write +\param out the target file object +\param m3u8_name the base m3u8 name to use (needed when generating variant playlist file names) +\param period the MPD period for that m3u8 +\return error if any +*/ +GF_Err gf_mpd_write_m3u8_master_playlist(GF_MPD const * const mpd, FILE *out, const char* m3u8_name, GF_MPD_Period *period); + +/*! parses an MPD Period and appends it to the MPD period list +GF_Err gf_mpd_write(GF_MPD const * const mpd, FILE *out, Bool compact); +\param mpd the target MPD to write +\param root the DOM element describing the period +\return error if any +*/ +GF_Err gf_mpd_parse_period(GF_MPD *mpd, GF_XMLNode *root); + +/*! creates a new MPD descriptor +\param id the descriptor ID, may be NULL +\param uri the descriptor schemeid URI, mandatory +\param value the descriptor value, may be NULL +\return the new descriptor or NULL if error +*/ +GF_MPD_Descriptor *gf_mpd_descriptor_new(const char *id, const char *uri, const char *value); + +/*! creates a new MPD AdaptationSet +\return the new GF_MPD_AdaptationSet or NULL if error +*/ +GF_MPD_AdaptationSet *gf_mpd_adaptation_set_new(); + +/*! file download abstraction object*/ +typedef struct _gf_file_get GF_FileDownload; +/*! file download abstraction object*/ +struct _gf_file_get +{ + /*! callback function for session creation, fetches the given URL*/ + GF_Err (*new_session)(GF_FileDownload *getter, char *url); + /*! callback function to destroy the session*/ + void (*del_session)(GF_FileDownload *getter); + /*! callback function to get the local file name*/ + const char *(*get_cache_name)(GF_FileDownload *getter); + /*! user private*/ + void *udta; + /*! created by user after new_session*/ + void *session; +}; + +/*! converts M3U8 to MPD - getter is optional (download will still be processed if NULL) +\param m3u8_file the path to the local m3u8 master playlist file +\param base_url the original URL of the file if any +\param mpd_file the destination MPD file, or NULL when filling an MPD structure +\param reload_count number of times the manifest was reloaded +\param mimeTypeForM3U8Segments default mime type for the segments in case not found in the m3u8 +\param do_import if GF_TRUE, will try to load the media segments to extract more info +\param use_mpd_templates if GF_TRUE, will use MPD SegmentTemplate instead of SegmentList +\param use_segment_timeline if GF_TRUE, uses SegmentTimeline to describe the varying duration of segments +\param getter HTTP interface object +\param mpd MPD structure to fill, or NULL if converting to file +\param parse_sub_playlist if GF_TRUE, parses sub playlists, otherwise only the master playlist is parsed and xlink are added on each representation to the target m3u8 sub playlist +\param keep_files if GF_TRUE, will not delete any files downloaded in the conversion process +\return error if any +*/ +GF_Err gf_m3u8_to_mpd(const char *m3u8_file, const char *base_url, const char *mpd_file, u32 reload_count, char *mimeTypeForM3U8Segments, Bool do_import, Bool use_mpd_templates, Bool use_segment_timeline, GF_FileDownload *getter, GF_MPD *mpd, Bool parse_sub_playlist, Bool keep_files); + +/*! solves an m3u8 xlink on a representation, and fills the SegmentList accordingly +\param rep the target representation +\param getter HTTP interface object +\param is_static set to GF_TRUE if the variant subplaylist is on demand +\param duration set to the duration of the parsed subplaylist +\return error if any +*/ +GF_Err gf_m3u8_solve_representation_xlink(GF_MPD_Representation *rep, GF_FileDownload *getter, Bool *is_static, u64 *duration); + +/*! creates a segment list from a remote segment list DOM root +\param mpd the target MPD to write +\param root the DOM element describing the segment list +\return a new segment list, or NULL if error +*/ +GF_MPD_SegmentList *gf_mpd_solve_segment_list_xlink(GF_MPD *mpd, GF_XMLNode *root); + +/*! inits an MPD from a smooth manifest root node +\param root the root node of a smooth manifest +\param mpd the MPD to fill +\param default_base_url the default URL of the smooth manifest +\return error if any +*/ +GF_Err gf_mpd_init_smooth_from_dom(GF_XMLNode *root, GF_MPD *mpd, const char *default_base_url); + +/*! deletes a segment list +\param segment_list the segment list to delete*/ +void gf_mpd_delete_segment_list(GF_MPD_SegmentList *segment_list); + +/*! deletes a list content and optionaly destructs the list +\param list the target list +\param __destructor the destructor function to use to destroy list items +\param reset_only if GF_TRUE, does not destroy the target list +*/ +void gf_mpd_del_list(GF_List *list, void (*__destructor)(void *), Bool reset_only); +/*! deletes a GF_MPD_Descriptor object +\param item the descriptor to delete +*/ +void gf_mpd_descriptor_free(void *item); + +/*! splits all adaptation sets of a source MPD, creating one adaptation set per quality of each orgingal adaptation sets +\param mpd the target MPD +\return error if any +*/ +GF_Err gf_mpd_split_adaptation_sets(GF_MPD *mpd); + +/*! converts a smooth manifest (local file) to an MPD +\param smooth_file local path to the smooth manifest +\param mpd MPD structure to fill +\param default_base_url the default URL of the smooth manifest +\return error if any +*/ +GF_Err gf_mpd_smooth_to_mpd(char * smooth_file, GF_MPD *mpd, const char *default_base_url); + +/*! get the number of base URLs for the given representation. This cumuluates all base URLs at MPD, period, AdaptationSet and Representation levels +\param mpd the target MPD +\param period the target period +\param set the target adaptation set +\param rep the target representation +\return the number of base URLs +*/ +u32 gf_mpd_get_base_url_count(GF_MPD *mpd, GF_MPD_Period *period, GF_MPD_AdaptationSet *set, GF_MPD_Representation *rep); + +/*! MPD URL resolutio mode*/ +typedef enum +{ + /*resolves template for a media segment*/ + GF_MPD_RESOLVE_URL_MEDIA, + /*resolves template for an init segment*/ + GF_MPD_RESOLVE_URL_INIT, + /*resolves template for an index segment*/ + GF_MPD_RESOLVE_URL_INDEX, + /*! same as GF_MPD_RESOLVE_URL_MEDIA but does not replace $Time$ and $Number$*/ + GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE, + /*! same as GF_MPD_RESOLVE_URL_MEDIA but does not use startNumber*/ + GF_MPD_RESOLVE_URL_MEDIA_NOSTART, +} GF_MPD_URLResolveType; + +/*! resolves a URL based for a given segment, based on the MPD url, the type of resolution +\param mpd the target MPD +\param rep the target Representation +\param set the target AdaptationSet +\param period the target Period +\param mpd_url the original URL of the MPD +\param base_url_index 0-based index of the baseURL to use +\param resolve_type the type of URL resolution desired +\param item_index the index of the target segment (startNumber based) +\param nb_segments_removed number of segments removed when purging the MPD after updates (can be 0). The start number will be offset by this value +\param out_url set to the resolved URL, to be freed by caller +\param out_range_start set to the resolved start range, 0 if no range +\param out_range_end set to the resolved end range, 0 if no range +\param segment_duration set to the resolved segment duartion, 0 if unknown +\param is_in_base_url set to GF_TRUE if the resuloved URL is a sub-part of the baseURL (optional, may be NULL) +\param out_key_url set to the key URL for the segment for HLS (optional, may be NULL) +\param key_iv set to the key IV for the segment for HLS (optional, may be NULL) +\return error if any +*/ +GF_Err gf_mpd_resolve_url(GF_MPD *mpd, GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set, GF_MPD_Period *period, const char *mpd_url, u32 base_url_index, GF_MPD_URLResolveType resolve_type, u32 item_index, u32 nb_segments_removed, + char **out_url, u64 *out_range_start, u64 *out_range_end, u64 *segment_duration, Bool *is_in_base_url, char **out_key_url, bin128 *key_iv); + +/*! get duration of the presentation +\param mpd the target MPD +\return the duration in seconds +*/ +Double gf_mpd_get_duration(GF_MPD *mpd); + +/*! gets the duration of media segments +\param rep the target Representation +\param set the target AdaptationSet +\param period the target Period +\param out_duration set to the average media segment duration +\param out_timescale set to the MPD timescale used by this representation +\param out_pts_offset set to the presentation time offset if any (optional, may be NULL) +\param out_segment_timeline set to the segment timeline description if any (optional, may be NULL) +*/ +void gf_mpd_resolve_segment_duration(GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set, GF_MPD_Period *period, u64 *out_duration, u32 *out_timescale, u64 *out_pts_offset, GF_MPD_SegmentTimeline **out_segment_timeline); + +/*! gets the start_time from the segment index of a period/set/rep +\param in_segment_index the index of the target segment (startNumber based) +\param in_period the target Period +\param in_set the target AdaptationSet +\param in_rep the target Representation +\param out_segment_start_time set to the MPD start time of the segment +\param out_opt_segment_duration set to the segment duration (optional, may be NULL) +\param out_opt_scale set to the MPD timescale for this segment (optional, may be NULL) +\return error if any +*/ +GF_Err gf_mpd_get_segment_start_time_with_timescale(s32 in_segment_index, + GF_MPD_Period const * const in_period, GF_MPD_AdaptationSet const * const in_set, GF_MPD_Representation const * const in_rep, + u64 *out_segment_start_time, u64 *out_opt_segment_duration, u32 *out_opt_scale); + +/*! MPD seek mode*/ +typedef enum { + /*! will return the segment containing the requested time*/ + MPD_SEEK_PREV, + /*! will return the nearest segment start time, may be the previous or the next one*/ + MPD_SEEK_NEAREST, +} MPDSeekMode; + +/*! returns the segment index in the given period for the given time +\param seek_time the desired time in seconds +\param seek_mode the desired seek mode +\param in_period the target Period +\param in_set the target AdaptationSet +\param in_rep the target Representation +\param out_segment_index the corresponding segment index +\param out_opt_seek_time the corresponding seek time (start time of segment in seconds) (optional, may be NULL) +\return error if any +*/ +GF_Err gf_mpd_seek_in_period(Double seek_time, MPDSeekMode seek_mode, + GF_MPD_Period const * const in_period, GF_MPD_AdaptationSet const * const in_set, GF_MPD_Representation const * const in_rep, + u32 *out_segment_index, Double *out_opt_seek_time); + +/*! deletes a GF_MPD_BaseURL structure (type-casted to void *) +\param _item the GF_MPD_BaseURL to free +*/ +void gf_mpd_base_url_free(void *_item); +/*! creates a new GF_MPD_Representation +\return the new representation*/ +GF_MPD_Representation *gf_mpd_representation_new(); +/*! deletes a GF_MPD_Representation structure (type-casted to void *) +\param _item the GF_MPD_Representation to free +*/ +void gf_mpd_representation_free(void *_item); + +/*! creates a new GF_MPD_SegmentTimeline +\return the new segment timeline*/ +GF_MPD_SegmentTimeline *gf_mpd_segmentimeline_new(); + +/*! DASHer cues information*/ +typedef struct +{ + /*! target splice point sample number (1-based), or 0 if not set*/ + u32 sample_num; + /*! target splice point dts in cues timescale */ + u64 dts; + /*! target splice point cts in cues timescale */ + s64 cts; + /*! internal flag indicating if the cues is processed or not */ + Bool is_processed; +} GF_DASHCueInfo; + +/*! loads a cue file and allocates cues as needed +\param cues_file the XML cue file to load +\param stream_id the ID of the stream for which we load cues (typically, TrackID or GF_PROP_PID_ID) +\param cues_timescale set to the timescale used in the cues document +\param use_edit_list set to GF_TRUE if the cts values of cues have edit list applied (i.e. are ISOBMFF presentation times) +\param out_cues set to a newly allocated list of cues, to free by the caller +\param nb_cues set to the number of cues parsed +\return error if any +*/ +GF_Err gf_mpd_load_cues(const char *cues_file, u32 stream_id, u32 *cues_timescale, Bool *use_edit_list, GF_DASHCueInfo **out_cues, u32 *nb_cues); + + +/*! @} */ +#endif /*GPAC_DISABLE_CORE_TOOLS*/ + +#endif // _MPD_H_ diff --git a/include/gpac/mpeg4_odf.h b/include/gpac/mpeg4_odf.h index 780c8d7..f652e24 100644 --- a/include/gpac/mpeg4_odf.h +++ b/include/gpac/mpeg4_odf.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / MPEG-4 Object Descriptor sub-project @@ -31,30 +31,29 @@ extern "C" { #endif /*! - * \file <gpac/mpeg4_odf.h> - * \brief MPEG-4 Object Descriptor Framework. +\file <gpac/mpeg4_odf.h> +\brief MPEG-4 Object Descriptor Framework. */ - -/*! \defgroup mpeg4sys_grp MPEG-4 Systems - * \brief MPEG-4 Systems. + +/*! +\addtogroup mpeg4sys_grp MPEG-4 Systems +\brief MPEG-4 Systems. */ - + /*! - * \addtogroup odf_grp MPEG-4 OD - * \ingroup mpeg4sys_grp - * \brief MPEG-4 Object Descriptor Framework - * - *This section documents the MPEG-4 OD, OCI and IPMPX functions of the GPAC framework. - * @{ +\addtogroup odf_grp MPEG-4 OD +\ingroup mpeg4sys_grp +\brief MPEG-4 Object Descriptor Framework + +This section documents the MPEG-4 OD, OCI and IPMPX functions of the GPAC framework. +@{ */ #include <gpac/list.h> #include <gpac/bitstream.h> #include <gpac/sync_layer.h> -/*************************************** - Descriptors Tag -***************************************/ +/*! Tags for MPEG-4 systems descriptors */ enum { GF_ODF_OD_TAG = 0x01, @@ -135,14 +134,15 @@ enum /*descriptor for aucilary video data*/ - GF_ODF_AUX_VIDEO_DATA = GF_ODF_EXT_BEGIN_TAG + 1 + GF_ODF_AUX_VIDEO_DATA = GF_ODF_EXT_BEGIN_TAG + 1, + GF_ODF_GPAC_LANG = GF_ODF_EXT_BEGIN_TAG + 2 }; /*************************************** Descriptors ***************************************/ - +/*! macro defining a base descriptor*/ #define BASE_DESCRIPTOR \ u8 tag; @@ -160,7 +160,7 @@ typedef struct { BASE_DESCRIPTOR u32 dataLength; - char *data; + u8 *data; } GF_DefaultDescriptor; /*! Object Descriptor*/ @@ -182,10 +182,12 @@ typedef struct GF_List *IPMP_Descriptors; GF_List *extensionDescriptors; /*MPEG-2 (or other service mux formats) service ID*/ - u16 ServiceID; - /*pointer to the service interface (GF_InputService) of the service having declared the object - only used for DASH*/ - void *service_ifce; + u32 ServiceID; + /*for ATSC, instructs client to keep OD alive even though URL string is set*/ + Bool RedirectOnly; + /*set to true for fake remote ODs in BT/XMT (remote ODs created for OD with ESD with MuxInfo)*/ + Bool fake_remote; + } GF_ObjectDescriptor; /*! GF_InitialObjectDescriptor - WARNING: even though the bitstream IOD is not @@ -203,9 +205,10 @@ typedef struct GF_List *extensionDescriptors; /*MPEG-2 (or other service mux formats) service ID*/ u16 ServiceID; - /*pointer to the service interface (GF_InputService) of the service having declared the object - only used for DASH*/ - void *service_ifce; + /*for ATSC, instructs client to keep OD alive even though URL string is set*/ + Bool RedirectOnly; + /*set to true for fake remote ODs in BT/XMT (remote ODs created for OD with ESD with MuxInfo)*/ + Bool fake_remote; /*IOD extensions*/ u8 inlineProfileFlag; @@ -229,6 +232,13 @@ typedef struct GF_List *IPMP_Descriptors; GF_List *extensionDescriptors; GF_List *ES_ID_IncDescriptors; + + /*MPEG-2 (or other service mux formats) service ID*/ + u32 ServiceID; + /*for ATSC, instructs client to keep OD alive even though URL string is set*/ + Bool RedirectOnly; + /*set to true for fake remote ODs in BT/XMT (remote ODs created for OD with ESD with MuxInfo)*/ + Bool fake_remote; } GF_IsomObjectDescriptor; /*! File Format Initial Object Descriptor - same remark as IOD*/ @@ -345,7 +355,7 @@ enum u8 tag; \ u8 version; \ u32 dataID; \ - + /*! IPMPX base object used for type casting in many function*/ typedef struct { @@ -358,7 +368,7 @@ typedef struct { u8 IPMP_DescriptorID; u16 IPMPS_Type; /*if IPMPS_Type=0, NULL-terminated URL, else if IPMPS_Type is not IPMPX, opaque data*/ - char *opaque_data; + u8 *opaque_data; /*if IPMPS_Type=0, irrelevant (strlen(URL)), else if IPMPS_Type is not IPMPX, opaque data size*/ u32 opaque_data_size; @@ -446,7 +456,7 @@ typedef struct s16 top, left, bottom, right; } GF_BoxRecord; -/*scroll flags for text*/ +/*! scroll flags for text*/ enum { GF_TXT_SCROLL_CREDITS = 0, @@ -455,7 +465,7 @@ enum GF_TXT_SCROLL_RIGHT = 3 }; -/* display flags for text*/ +/*! display flags for text*/ enum { GF_TXT_SCROLL_IN = 0x00000020, @@ -607,7 +617,7 @@ typedef struct /*string sensor deletion char*/ char delChar; /*device-specific data*/ - char *ui_data; + u8 *ui_data; u32 ui_data_length; } GF_UIConfig; @@ -653,6 +663,7 @@ typedef struct { } GF_QoS_Descriptor; +/*! macro defining a base QOS qualifier*/ #define QOS_BASE_QUALIFIER \ u8 tag; \ u32 size; @@ -668,7 +679,7 @@ typedef struct { u32 MaxDelay; } GF_QoS_MaxDelay; -/*! QoS Prefered Max Delay Qualifier*/ +/*! QoS preferred Max Delay Qualifier*/ typedef struct { QOS_BASE_QUALIFIER u32 PrefMaxDelay; @@ -709,7 +720,7 @@ typedef struct { QOS_BASE_QUALIFIER /*! max size class is 2^28 - 1*/ u32 DataLength; - char *Data; + u8 *Data; } GF_QoS_Private; @@ -718,7 +729,7 @@ typedef struct { BASE_DESCRIPTOR u32 formatIdentifier; u32 dataLength; - char *additionalIdentificationInfo; + u8 *additionalIdentificationInfo; } GF_Registration; /*! Language Descriptor*/ @@ -908,9 +919,10 @@ typedef struct { typedef struct { u16 size; - char *data; + u8 *data; /* used of AVC/SVC detection */ s32 id; + u32 crc; } GF_AVCConfigSlot; /*! AVC config record - not a real MPEG-4 descriptor @@ -935,6 +947,8 @@ typedef struct u8 chroma_bit_depth; /*may be NULL*/ GF_List *sequenceParameterSetExtensions; + + Bool write_annex_b; } GF_AVCConfig; @@ -978,13 +992,82 @@ typedef struct GF_List *param_array; //used in LHVC config Bool complete_representation; - + //following are internal to libgpac and NEVER serialized - + //set by libisomedia at import/export/parsing time to differentiate between lhcC and hvcC time Bool is_lhvc; + + Bool write_annex_b; + } GF_HEVCConfig; +/*! used for storing AV1 OBUs*/ +typedef struct +{ + u64 obu_length; + int obu_type; /*ObuType*/ + u8 *obu; +} GF_AV1_OBUArrayEntry; + +/*! AV1 config record - not a real MPEG-4 descriptor*/ +typedef struct +{ + Bool marker; + u8 version; /*7 bits*/ + u8 seq_profile; /*3 bits*/ + u8 seq_level_idx_0; /*5 bits*/ + Bool seq_tier_0; + Bool high_bitdepth; + Bool twelve_bit; + Bool monochrome; + Bool chroma_subsampling_x; + Bool chroma_subsampling_y; + u8 chroma_sample_position; /*2 bits*/ + + Bool initial_presentation_delay_present; + u8 initial_presentation_delay_minus_one; /*4 bits*/ + GF_List *obu_array; /*GF_AV1_OBUArrayEntry*/ +} GF_AV1Config; + +/*! max number of reference frames for VP9*/ +#define VP9_NUM_REF_FRAMES 8 + +/*! VP8-9 config vpcC */ +typedef struct +{ + u8 profile; + u8 level; + + u8 bit_depth; /*4 bits*/ + u8 chroma_subsampling; /*3 bits*/ + Bool video_fullRange_flag; + + u8 colour_primaries; + u8 transfer_characteristics; + u8 matrix_coefficients; + + /* MUST be 0 for VP8 and VP9 */ + u16 codec_initdata_size; + u8* codec_initdata; + + /* parsing state information - not used for vpcC*/ + int RefFrameWidth[VP9_NUM_REF_FRAMES]; + int RefFrameHeight[VP9_NUM_REF_FRAMES]; +} GF_VPConfig; + +/*! DolbyVision config dvcC */ +typedef struct { + u8 dv_version_major; + u8 dv_version_minor; + u8 dv_profile; //7 bits + u8 dv_level; //6 bits + Bool rpu_present_flag; + Bool el_present_flag; + Bool bl_present_flag; + //const unsigned int (32)[5] reserved = 0; +} GF_DOVIDecoderConfigurationRecord; + /*! Media Segment Descriptor used for Media Control Extensions*/ typedef struct { @@ -1022,6 +1105,7 @@ enum GF_ODF_COM_USER_END_TAG = 0xFE }; +/*! macro defining a base OD command*/ #define BASE_OD_COMMAND \ u8 tag; @@ -1034,7 +1118,7 @@ typedef struct { typedef struct { BASE_OD_COMMAND u32 dataSize; - char *data; + u8 *data; } GF_BaseODCom; /*! MPEG-4 SYSTEMS OD - Object Descriptor Update @@ -1099,69 +1183,70 @@ typedef struct tagODCoDec /*! OD codec construction - \return new codec object*/ +\return new codec object*/ GF_ODCodec *gf_odf_codec_new(); /*! OD codec destruction - \param codec OD codec to destroy +\param codec OD codec to destroy */ void gf_odf_codec_del(GF_ODCodec *codec); -/*! add a command to the codec command list. - \param codec target codec - \param command command to add - \return error if any +/*! add a command to the codec command list. +\param codec target codec +\param command command to add +\return error if any */ GF_Err gf_odf_codec_add_com(GF_ODCodec *codec, GF_ODCom *command); /*! encode the current command list. - \param codec target codec - \param cleanup_type specifies what to do with the command after encoding. The following values are accepted: +\param codec target codec +\param cleanup_type specifies what to do with the command after encoding. The following values are accepted: 0: commands are removed from the list but not destroyed 1: commands are removed from the list and destroyed 2: commands are kept in the list and not destroyed - \return error if any +\return error if any */ GF_Err gf_odf_codec_encode(GF_ODCodec *codec, u32 cleanup_type); /*! get the encoded AU. - \param codec target codec - \param outAU output buffer allocated by the codec, user is responsible of freeing the allocated space - \param au_length size of the AU (allocated buffer) - \return error if any - */ -GF_Err gf_odf_codec_get_au(GF_ODCodec *codec, char **outAU, u32 *au_length); -/* !set the encoded AU to the codec - \param codec target codec - \param au target AU to decode - \param au_length size in bytes of the AU to decode - \return error if any - */ -GF_Err gf_odf_codec_set_au(GF_ODCodec *codec, const char *au, u32 au_length); +\param codec target codec +\param outAU output buffer allocated by the codec, user is responsible of freeing the allocated space +\param au_length size of the AU (allocated buffer) +\return error if any + */ +GF_Err gf_odf_codec_get_au(GF_ODCodec *codec, u8 **outAU, u32 *au_length); +/*! set the encoded AU to the codec +\param codec target codec +\param au target AU to decode +\param au_length size in bytes of the AU to decode +\return error if any + */ +GF_Err gf_odf_codec_set_au(GF_ODCodec *codec, const u8 *au, u32 au_length); /*! decode the previously set-up AU - \param codec target codec - \return error if any +\param codec target codec +\return error if any */ GF_Err gf_odf_codec_decode(GF_ODCodec *codec); /*! get the first OD command in the list. Once called, the command is removed from the command list. Return NULL when commandList is empty - \param codec target codec - \return deocded command or NULL +\param codec target codec +\return deocded command or NULL */ GF_ODCom *gf_odf_codec_get_com(GF_ODCodec *codec); /*! apply a command to the codec command list. Command is duplicated if needed This is used for state maintenance and RAP generation. - \param codec target codec - \param command the command to apply - \return error if any +\param codec target codec +\param command the command to apply +\return error if any */ GF_Err gf_odf_codec_apply_com(GF_ODCodec *codec, GF_ODCom *command); /*! MPEG-4 SYSTEMS OD Command Creation - \param tag type of command to create - \return the created command or NULL +\param tag type of command to create +\return the created command or NULL */ GF_ODCom *gf_odf_com_new(u8 tag); /*! MPEG-4 SYSTEMS OD Command Destruction - \param com the command to delete. Pointer is set back to NULL +\param com the command to delete. Pointer is set back to NULL +\return error if any */ GF_Err gf_odf_com_del(GF_ODCom **com); @@ -1171,151 +1256,267 @@ GF_Err gf_odf_com_del(GF_ODCom **com); ************************************************************/ /*! Descriptors Creation - \param tag type of descriptor to create - \return created descriptor or NULL +\param tag type of descriptor to create +\return created descriptor or NULL */ GF_Descriptor *gf_odf_desc_new(u8 tag); /*! Descriptors Destruction - \param desc the descriptor to destroy +\param desc the descriptor to destroy */ void gf_odf_desc_del(GF_Descriptor *desc); /*! helper for building a preformatted GF_ESD with decoderConfig, decoderSpecificInfo with no data and SLConfig descriptor with predefined - \param sl_predefined type of predefined sl config - \return the ESD created +\param sl_predefined type of predefined sl config +\return the ESD created */ GF_ESD *gf_odf_desc_esd_new(u32 sl_predefined); /*! special function for authoring - convert DSI to BIFSConfig - \param dsi BIFS decoder specific info - \param oti BIFS object type indication - \return decoded BIFS Config descriptor - It is the caller responsability of freeing it +\param dsi BIFS decoder specific info +\param codecid BIFS codecid/object type indication +\return decoded BIFS Config descriptor - It is the caller responsability of freeing it */ -GF_BIFSConfig *gf_odf_get_bifs_config(GF_DefaultDescriptor *dsi, u8 oti); +GF_BIFSConfig *gf_odf_get_bifs_config(GF_DefaultDescriptor *dsi, u32 codecid); /*! special function for authoring - convert DSI to LASERConfig - \param dsi LASER decoder specific info - \param cfg the LASER config object to be filled - \return error if any +\param dsi LASER decoder specific info +\param cfg the LASER config object to be filled +\return error if any */ GF_Err gf_odf_get_laser_config(GF_DefaultDescriptor *dsi, GF_LASERConfig *cfg); /*! sepcial function for authoring - convert DSI to TextConfig - \param dsi TEXT decoder specific info - \param oti TEXT object type indication - \param cfg the text config object to be filled - \return error if any - */ -GF_Err gf_odf_get_text_config(GF_DefaultDescriptor *dsi, u8 oti, GF_TextConfig *cfg); -/*! special function for authoring - convert DSI to UIConfig - \param dsi text decoder specific info - \param cfg the text config object to be filled - \return error if any - */ -GF_Err gf_odf_get_ui_config(GF_DefaultDescriptor *dsi, GF_UIConfig *cfg); +\param data TEXT decoder config block +\param data_len TEXT decoder config block size +\param codecid TEXT codecid/object type indication +\param cfg the text config object to be filled +\return error if any + */ +GF_Err gf_odf_get_text_config(u8 *data, u32 data_len, u32 codecid, GF_TextConfig *cfg); /*! converts UIConfig to dsi - does not destroy input descr but does create output one - \param cfg the UI config object - \param out_dsi the decoder specific info created. It is the caller responsability of freeing it - \return error if any +\param cfg the UI config object +\param out_dsi the decoder specific info created. It is the caller responsability of freeing it +\return error if any */ GF_Err gf_odf_encode_ui_config(GF_UIConfig *cfg, GF_DefaultDescriptor **out_dsi); /*! AVC config constructor - \return the created AVC config*/ +\return the created AVC config*/ GF_AVCConfig *gf_odf_avc_cfg_new(); /*! AVC config destructor - \param cfg the AVC config to destroy*/ +\param cfg the AVC config to destroy*/ void gf_odf_avc_cfg_del(GF_AVCConfig *cfg); /*! gets GF_AVCConfig from MPEG-4 DSI - \param dsi encoded AVC decoder specific info - \param dsi_size encoded AVC decoder specific info size - \return the decoded AVC config +\param dsi encoded AVC decoder specific info +\param dsi_size encoded AVC decoder specific info size +\return the decoded AVC config */ -GF_AVCConfig *gf_odf_avc_cfg_read(char *dsi, u32 dsi_size); -/*! writes GF_AVCConfig as MPEG-4 DSI - \param cfg the AVC config to encode - \param outData encoded dsi buffer - it is the caller responsability to free this - \param outSize encoded dsi buffer size - \return error if any +GF_AVCConfig *gf_odf_avc_cfg_read(u8 *dsi, u32 dsi_size); +/*! writes GF_AVCConfig +\param cfg the AVC config to encode +\param outData encoded dsi buffer - it is the caller responsability to free this +\param outSize encoded dsi buffer size +\return error if any */ -GF_Err gf_odf_avc_cfg_write(GF_AVCConfig *cfg, char **outData, u32 *outSize); +GF_Err gf_odf_avc_cfg_write(GF_AVCConfig *cfg, u8 **outData, u32 *outSize); +/*! writes GF_AVCConfig to bitstream +\param cfg the AVC config to encode +\param bs bitstream in WRITE mode +\return error if any + */ +GF_Err gf_odf_avc_cfg_write_bs(GF_AVCConfig *cfg, GF_BitStream *bs); + +/*! writes GF_TextSampleDescriptor +\param cfg the text config to encode +\param outData address of output buffer (internal alloc, user to free it) +\param outSize size of the allocated output +\return error if any + */ +GF_Err gf_odf_tx3g_write(GF_TextSampleDescriptor *cfg, u8 **outData, u32 *outSize); + +/*! gets GF_TextSampleDescriptor from buffer +\param dsi encoded text sample description +\param dsi_size size of encoded description +\return the decoded GF_TextSampleDescriptor config + */ +GF_TextSampleDescriptor *gf_odf_tx3g_read(u8 *dsi, u32 dsi_size); /*! HEVC config constructor \return the created HEVC config*/ GF_HEVCConfig *gf_odf_hevc_cfg_new(); + /*! HEVC config destructor - \param cfg the HEVC config to destroy*/ +\param cfg the HEVC config to destroy*/ void gf_odf_hevc_cfg_del(GF_HEVCConfig *cfg); + /*! writes GF_HEVCConfig as MPEG-4 DSI in a bitstream object - \param cfg the HEVC config to encode - \param bs output bitstream object in which the config is written - \return error if any +\param cfg the HEVC config to encode +\param bs output bitstream object in which the config is written +\return error if any */ GF_Err gf_odf_hevc_cfg_write_bs(GF_HEVCConfig *cfg, GF_BitStream *bs); + /*! writes GF_HEVCConfig as MPEG-4 DSI - \param cfg the HEVC config to encode - \param outData encoded dsi buffer - it is the caller responsability to free this - \param outSize encoded dsi buffer size - \return error if any +\param cfg the HEVC config to encode +\param outData encoded dsi buffer - it is the caller responsability to free this +\param outSize encoded dsi buffer size +\return error if any */ -GF_Err gf_odf_hevc_cfg_write(GF_HEVCConfig *cfg, char **outData, u32 *outSize); +GF_Err gf_odf_hevc_cfg_write(GF_HEVCConfig *cfg, u8 **outData, u32 *outSize); + /*! gets GF_HEVCConfig from bitstream MPEG-4 DSI - \param bs bitstream containing the encoded HEVC decoder specific info - \param is_lhvc if GF_TRUE, indicates if the dsi is LHVC - \return the decoded HEVC config +\param bs bitstream containing the encoded HEVC decoder specific info +\param is_lhvc if GF_TRUE, indicates if the dsi is LHVC +\return the decoded HEVC config */ GF_HEVCConfig *gf_odf_hevc_cfg_read_bs(GF_BitStream *bs, Bool is_lhvc); + /*! gets GF_HEVCConfig from MPEG-4 DSI - \param dsi encoded HEVC decoder specific info - \param dsi_size encoded HEVC decoder specific info size - \param is_lhvc if GF_TRUE, indicates if the dsi is LHVC - \return the decoded HEVC config +\param dsi encoded HEVC decoder specific info +\param dsi_size encoded HEVC decoder specific info size +\param is_lhvc if GF_TRUE, indicates if the dsi is LHVC +\return the decoded HEVC config */ -GF_HEVCConfig *gf_odf_hevc_cfg_read(char *dsi, u32 dsi_size, Bool is_lhvc); +GF_HEVCConfig *gf_odf_hevc_cfg_read(u8 *dsi, u32 dsi_size, Bool is_lhvc); + + +/*! AV1 config constructor +\return the created AV1 config*/ +GF_AV1Config *gf_odf_av1_cfg_new(); + +/*! AV1 config destructor +\param cfg the AV1 config to destroy*/ +void gf_odf_av1_cfg_del(GF_AV1Config *cfg); + +/*! writes AV1 config to buffer +\param cfg the AV1 config to write +\param outData set to an allocated encoded buffer - it is the caller responsability to free this +\param outSize set to the encoded dsi buffer size +\return error if any +*/ +GF_Err gf_odf_av1_cfg_write(GF_AV1Config *cfg, u8 **outData, u32 *outSize); + +/*! writes AV1 config to bitstream +\param cfg the AV1 config to write +\param bs bitstream containing the encoded AV1 decoder specific info +\return error code if any +*/ +GF_Err gf_odf_av1_cfg_write_bs(GF_AV1Config *cfg, GF_BitStream *bs); + +/*! gets AV1 config from bitstream +\param bs bitstream containing the encoded AV1 decoder specific info +\return the decoded AV1 config +*/ +GF_AV1Config *gf_odf_av1_cfg_read_bs(GF_BitStream *bs); + +/*! gets AV1 config to bitstream +\param bs bitstream containing the encoded AV1 decoder specific info +\param size size of encoded structure in the bitstream. A value of 0 means "until the end", equivalent to gf_odf_av1_cfg_read_bs +\return the decoded AV1 config +*/ +GF_AV1Config *gf_odf_av1_cfg_read_bs_size(GF_BitStream *bs, u32 size); + +/*! gets AV1 config from buffer +\param dsi encoded AV1 config +\param dsi_size size of encoded AV1 config +\return the decoded AV1 config +*/ +GF_AV1Config *gf_odf_av1_cfg_read(u8 *dsi, u32 dsi_size); + + +/*! creates a VPx (VP8, VP9) descriptor +\return a newly allocated descriptor +*/ +GF_VPConfig *gf_odf_vp_cfg_new(); +/*! destroys an VPx config +\param cfg the VPx config to destroy*/ +void gf_odf_vp_cfg_del(GF_VPConfig *cfg); +/*! writes VPx config to bitstream +\param cfg the VPx config to write +\param bs bitstream containing the encoded VPx decoder specific info +\param is_v0 if GF_TRUE, this is a version 0 config +\return error code if any +*/ +GF_Err gf_odf_vp_cfg_write_bs(GF_VPConfig *cfg, GF_BitStream *bs, Bool is_v0); +/*! writes VPx config to buffer +\param cfg the VPx config to write +\param outData set to an allocated encoded buffer - it is the caller responsability to free this +\param outSize set to the encoded buffer size +\param is_v0 if GF_TRUE, this is a version 0 config +\return error if any +*/ +GF_Err gf_odf_vp_cfg_write(GF_VPConfig *cfg, u8 **outData, u32 *outSize, Bool is_v0); +/*! gets VPx config to bitstream +\param bs bitstream containing the encoded VPx decoder specific info +\param is_v0 if GF_TRUE, this is a version 0 config +\return the decoded VPx config +*/ +GF_VPConfig *gf_odf_vp_cfg_read_bs(GF_BitStream *bs, Bool is_v0); +/*! gets VPx config from buffer +\param dsi encoded VPx config +\param dsi_size size of encoded VPx config +\return the decoded VPx config +*/ +GF_VPConfig *gf_odf_vp_cfg_read(u8 *dsi, u32 dsi_size); + +/*! gets DolbyVision config to bitstream +\param bs bitstream containing the encoded VPx decoder specific info +\return the decoded DolbyVision config +*/ +GF_DOVIDecoderConfigurationRecord *gf_odf_dovi_cfg_read_bs(GF_BitStream *bs); +/*! writes DolbyVision config to buffer +\param cfg the DolbyVision config to write +\param bs the bitstream object in which to write the config +\return error if any +*/ +GF_Err gf_odf_dovi_cfg_write_bs(GF_DOVIDecoderConfigurationRecord *cfg, GF_BitStream *bs); +/*! destroys a DolbyVision config +\param cfg the DolbyVision config to destroy*/ +void gf_odf_dovi_cfg_del(GF_DOVIDecoderConfigurationRecord *cfg); /*! destroy the descriptors in a list but not the list - \param descList descriptor list to destroy - \return error if any +\param descList descriptor list to destroy +\return error if any */ GF_Err gf_odf_desc_list_del(GF_List *descList); /*! use this function to decode a standalone descriptor the raw descriptor MUST be formatted with tag and size field!!! a new desc is created and you must delete it when done - \param raw_desc encoded descriptor to decode - \param descSize size of descriptor to decode - \param outDesc output decoded descriptor - it is the caller responsability to free this - \return error if any +\param raw_desc encoded descriptor to decode +\param descSize size of descriptor to decode +\param outDesc output decoded descriptor - it is the caller responsability to free this +\return error if any */ -GF_Err gf_odf_desc_read(char *raw_desc, u32 descSize, GF_Descriptor **outDesc); +GF_Err gf_odf_desc_read(u8 *raw_desc, u32 descSize, GF_Descriptor **outDesc); /*! use this function to encode a standalone descriptor the desc will be formatted with tag and size field the output buffer is allocated and you must delete it when done - \param desc descriptor to encode - \param outEncDesc output encoded descriptor - it is the caller responsability to free this - \param outSize size of encoded descriptor - \return error if any +\param desc descriptor to encode +\param outEncDesc output encoded descriptor - it is the caller responsability to free this +\param outSize size of encoded descriptor +\return error if any */ -GF_Err gf_odf_desc_write(GF_Descriptor *desc, char **outEncDesc, u32 *outSize); +GF_Err gf_odf_desc_write(GF_Descriptor *desc, u8 **outEncDesc, u32 *outSize); /*! use this function to encode a standalone descriptor in a bitstream object the desc will be formatted with tag and size field - \param desc descriptor to encode - \param bs the bitstream object in write mode - \return error if any +\param desc descriptor to encode +\param bs the bitstream object in write mode +\return error if any */ GF_Err gf_odf_desc_write_bs(GF_Descriptor *desc, GF_BitStream *bs); /*! use this function to get the size of a standalone descriptor (including tag and size fields) - \param desc descriptor to encode - \return 0 if error or encoded desc size otherwise*/ +\param desc descriptor to encode +\return 0 if error or encoded desc size otherwise*/ u32 gf_odf_desc_size(GF_Descriptor *desc); /*! duplicate descriptors - \param inDesc descriptor to copy - \param outDesc copied descriptor - it is the caller responsability to free this - \return error if any +\param inDesc descriptor to copy +\param outDesc copied descriptor - it is the caller responsability to free this +\return error if any */ GF_Err gf_odf_desc_copy(GF_Descriptor *inDesc, GF_Descriptor **outDesc); @@ -1323,36 +1524,12 @@ GF_Err gf_odf_desc_copy(GF_Descriptor *inDesc, GF_Descriptor **outDesc); and adds it. NO DUPLICATION of the descriptor, so once a desc is added to its parent, destroying the parent WILL DESTROY this descriptor - \param parentDesc parent descriptor - \param newDesc descriptor to add to parent - \return error if any +\param parentDesc parent descriptor +\param newDesc descriptor to add to parent +\return error if any */ GF_Err gf_odf_desc_add_desc(GF_Descriptor *parentDesc, GF_Descriptor *newDesc); -/*! Gets a textual description of a stream - \param esd target descriptor - \return textural description of the descriptor - */ -const char *gf_esd_get_textual_description(GF_ESD *esd); - -/*! Gets a textual description of an AFX stream type - \param afx_code target stream type descriptor - \return textural description of the AFX stream -*/ -const char *gf_afx_get_type_description(u8 afx_code); - -/*! Gets the stream type name based on stream type - \param streamType stream type GF_STREAM_XXX as defined in constants.h - \return NULL if unknown, otherwise value - */ -const char *gf_odf_stream_type_name(u32 streamType); - -/*! Gets the stream type based on stream type name - \param streamType name of the stream type - \return stream type GF_STREAM_XXX as defined in constants.h, 0 if unknown - */ -u32 gf_odf_stream_type_by_name(const char *streamType); - /*! Since IPMP V2, we introduce a new set of functions to read / write a list of descriptors that have no containers (a bit like an OD command, but for descriptors) This is useful for IPMPv2 DecoderSpecificInfo which contains a set of IPMP_Declarators @@ -1361,31 +1538,33 @@ you must create the list yourself, the functions just encode/decode from/to the /*! uncompress an encoded list of descriptors. You must pass an empty GF_List structure to know exactly what was in the buffer - \param raw_list encoded list of descriptors - \param raw_size size of the encoded list of descriptors - \param descList list in which the decoded descriptors will be placed - \return error if any +\param raw_list encoded list of descriptors +\param raw_size size of the encoded list of descriptors +\param descList list in which the decoded descriptors will be placed +\return error if any */ -GF_Err gf_odf_desc_list_read(char *raw_list, u32 raw_size, GF_List *descList); +GF_Err gf_odf_desc_list_read(u8 *raw_list, u32 raw_size, GF_List *descList); /*! compress all descriptors in the list into a single buffer. The buffer is allocated by the lib and must be destroyed by your app you must pass (outEncList != NULL && *outEncList == NULL) - \param descList list of descriptors to be encoded - \param outEncList buffer of encoded descriptors - \param outSize size of buffer of encoded descriptors - \return error if any +\param descList list of descriptors to be encoded +\param outEncList buffer of encoded descriptors +\param outSize size of buffer of encoded descriptors +\return error if any */ -GF_Err gf_odf_desc_list_write(GF_List *descList, char **outEncList, u32 *outSize); +GF_Err gf_odf_desc_list_write(GF_List *descList, u8 **outEncList, u32 *outSize); /*! returns size of encoded desc list - \param descList list of descriptors to be encoded - \param outSize size of buffer of encoded descriptors - \return error if any +\param descList list of descriptors to be encoded +\param outSize size of buffer of encoded descriptors +\return error if any */ GF_Err gf_odf_desc_list_size(GF_List *descList, u32 *outSize); +//! @cond Doxygen_Suppress + #ifndef GPAC_MINIMAL_ODF @@ -1394,13 +1573,13 @@ GF_Err gf_odf_desc_list_size(GF_List *descList, u32 *outSize); ************************************************************/ /*! QoS Qualifiers constructor - \param tag tag of QoS descriptor to create - \return created QoS descriptor +\param tag tag of QoS descriptor to create +\return created QoS descriptor */ GF_QoS_Default *gf_odf_qos_new(u8 tag); /*! QoS Qualifiers destructor - \param qos descriptor to destroy. The pointer is set back to NULL upon destruction - \return error if any +\param qos descriptor to destroy. The pointer is set back to NULL upon destruction +\return error if any */ GF_Err gf_odf_qos_del(GF_QoS_Default **qos); @@ -1408,9 +1587,9 @@ GF_Err gf_odf_qos_del(GF_QoS_Default **qos); therefore, use gf_odf_desc_read and gf_odf_desc_write for QoS*/ /*! Adds a QoS qualificator to a parent QoS descriptor - \param desc parent QoS descriptor - \param qualif QoS qualificator - \return error if any +\param desc parent QoS descriptor +\param qualif QoS qualificator +\return error if any */ GF_Err gf_odf_qos_add_qualif(GF_QoS_Descriptor *desc, GF_QoS_Default *qualif); @@ -1455,77 +1634,73 @@ GF_Err gf_oci_codec_add_event(OCICodec *codec, OCIEvent *event); WARNING: once this function called, the codec event List is empty and events destroyed you must set *outAU = NULL*/ -GF_Err gf_oci_codec_encode(OCICodec *codec, char **outAU, u32 *au_length); +GF_Err gf_oci_codec_encode(OCICodec *codec, u8 **outAU, u32 *au_length); /*Decoder: decode the previously set-up AU the input buffer is cleared once decoded*/ -GF_Err gf_oci_codec_decode(OCICodec *codec, char *au, u32 au_length); +GF_Err gf_oci_codec_decode(OCICodec *codec, u8 *au, u32 au_length); /*get the first OCI Event in the list. Once called, the event is removed from the event list. Return NULL when the event List is empty you MUST delete events */ OCIEvent *gf_oci_codec_get_event(OCICodec *codec); +/*! Dumps an OCI event +\param ev OCI event to dump +\param trace destination file for dumping +\param indent number of spaces to use as base index +\param XMTDump if GF_TRUE dumpos as XMT, otherwise as BT + */ +GF_Err gf_oci_dump_event(OCIEvent *ev, FILE *trace, u32 indent, Bool XMTDump); +/*! Dumps an OCI AU +\param version version of the OCI stream +\param au OCI AU to dump +\param au_length size of the OCI AU to dump +\param trace destination file for dumping +\param indent number of spaces to use as base index +\param XMTDump if GF_TRUE dumpos as XMT, otherwise as BT + */ +GF_Err gf_oci_dump_au(u8 version, u8 *au, u32 au_length, FILE *trace, u32 indent, Bool XMTDump); + +#endif /*GPAC_MINIMAL_ODF*/ + +//! @endcond #ifndef GPAC_DISABLE_OD_DUMP /*! Dumps an OD AU - \param data encoded OD AU - \param dataLength encoded OD AU size - \param trace destination file for dumping - \param indent number of spaces to use as base index - \param XMTDump - */ -GF_Err gf_odf_dump_au(char *data, u32 dataLength, FILE *trace, u32 indent, Bool XMTDump); -/*! Dumps an OD AU - \param com OD command to dump - \param trace destination file for dumping - \param indent number of spaces to use as base index - \param XMTDump if GF_TRUE dumpos as XMT, otherwise as BT +\param com OD command to dump +\param trace destination file for dumping +\param indent number of spaces to use as base index +\param XMTDump if GF_TRUE dumpos as XMT, otherwise as BT +\return error if any */ GF_Err gf_odf_dump_com(GF_ODCom *com, FILE *trace, u32 indent, Bool XMTDump); /*! Dumps an OD Descriptor - \param desc descriptor to dump - \param trace destination file for dumping - \param indent number of spaces to use as base index - \param XMTDump if GF_TRUE dumpos as XMT, otherwise as BT +\param desc descriptor to dump +\param trace destination file for dumping +\param indent number of spaces to use as base index +\param XMTDump if GF_TRUE dumpos as XMT, otherwise as BT +\return error if any */ GF_Err gf_odf_dump_desc(GF_Descriptor *desc, FILE *trace, u32 indent, Bool XMTDump); /*! Dumps an OD Descriptor - \param commandList descriptor list to dump - \param trace destination file for dumping - \param indent number of spaces to use as base index - \param XMTDump if GF_TRUE dumpos as XMT, otherwise as BT +\param commandList descriptor list to dump +\param trace destination file for dumping +\param indent number of spaces to use as base index +\param XMTDump if GF_TRUE dumpos as XMT, otherwise as BT +\return error if any */ GF_Err gf_odf_dump_com_list(GF_List *commandList, FILE *trace, u32 indent, Bool XMTDump); -/*! Dumps an OCI event - \param ev OCI event to dump - \param trace destination file for dumping - \param indent number of spaces to use as base index - \param XMTDump if GF_TRUE dumpos as XMT, otherwise as BT - */ -GF_Err gf_oci_dump_event(OCIEvent *ev, FILE *trace, u32 indent, Bool XMTDump); -/*! Dumps an OCI AU - \param version version of the OCI stream - \param au OCI AU to dump - \param au_length size of the OCI AU to dump - \param trace destination file for dumping - \param indent number of spaces to use as base index - \param XMTDump if GF_TRUE dumpos as XMT, otherwise as BT - */ -GF_Err gf_oci_dump_au(u8 version, char *au, u32 au_length, FILE *trace, u32 indent, Bool XMTDump); - #endif /*GPAC_DISABLE_OD_DUMP*/ -#endif /*GPAC_MINIMAL_ODF*/ - /*! Gets descriptor tag by name - \param descName target descriptor name - \return descriptor tag or 0 if error +\param descName target descriptor name +\return descriptor tag or 0 if error */ u32 gf_odf_get_tag_by_name(char *descName); @@ -1548,22 +1723,23 @@ typedef enum GF_ODF_FT_IPMPX_BA_LIST = 6 } GF_ODF_FieldType; /*! Gets ODF field type by name - \param desc target descriptor - \param fieldName descriptor field name - \return the descriptor field type +\param desc target descriptor +\param fieldName descriptor field name +\return the descriptor field type */ GF_ODF_FieldType gf_odf_get_field_type(GF_Descriptor *desc, char *fieldName); /*! Set non-descriptor field value - value string shall be presented without ' or " characters - \param desc target descriptor - \param fieldName descriptor field name - \param val field value to parse - \return error if any +\param desc target descriptor +\param fieldName descriptor field name +\param val field value to parse +\return error if any */ GF_Err gf_odf_set_field(GF_Descriptor *desc, char *fieldName, char *val); #ifndef GPAC_MINIMAL_ODF +//! @cond Doxygen_Suppress /* @@ -1574,12 +1750,12 @@ GF_Err gf_odf_set_field(GF_Descriptor *desc, char *fieldName, char *val); typedef struct { u32 length; - char *data; + u8 *data; } GF_IPMPX_ByteArray; #define GF_IPMPX_AUTH_DESC \ u8 tag; \ - + /*! IPMPX authentication descriptor*/ typedef struct { @@ -1597,7 +1773,7 @@ enum typedef struct { GF_IPMPX_AUTH_DESC - char *keyBody; + u8 *keyBody; u32 keyBodyLength; } GF_IPMPX_AUTH_KeyDescriptor; @@ -1751,6 +1927,7 @@ typedef struct */ typedef struct { + GF_IPMPX_DATA_BASE GF_List *ipmp_tools; } GF_IPMPX_GetToolsResponse; @@ -2065,12 +2242,12 @@ typedef struct u8 chroma_format; u32 wmPayloadLen; - char *wmPayload; + u8 *wmPayload; u16 wmRecipientId; u32 opaqueDataSize; - char *opaqueData; + u8 *opaqueData; } GF_IPMPX_WatermarkingInit; @@ -2155,6 +2332,7 @@ GF_Err gf_ipmpx_set_byte_array(GF_IPMPX_Data *p, char *field, char *str); /*! ipmpx dumper*/ GF_Err gf_ipmpx_dump_data(GF_IPMPX_Data *_p, FILE *trace, u32 indent, Bool XMTDump); +//! @endcond #endif /*GPAC_MINIMAL_ODF*/ diff --git a/include/gpac/mpegts.h b/include/gpac/mpegts.h index 1aa470d..a736725 100644 --- a/include/gpac/mpegts.h +++ b/include/gpac/mpegts.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre, Cyril Concolato, Romain Bouqueau - * Copyright (c) Telecom ParisTech 2006-2012 + * Copyright (c) Telecom ParisTech 2006-2019 * * This file is part of GPAC / MPEG2-TS sub-project * @@ -27,18 +27,19 @@ #define _GF_MPEG_TS_H_ /*! - * \file <gpac/mpegts.h> - * \brief MPEG-TS demultiplexer and multiplexer APIs - */ +\file <gpac/mpegts.h> +\brief MPEG-TS demultiplexer and multiplexer APIs +*/ /*! - * \addtogroup m2ts_grp MPEG-2 TS - * \ingroup media_grp - * \brief MPEG-TS demultiplexer and multiplexer APIs. - * - *This section documents the MPEG-TS demultiplexer and multiplexer APIs. - * @{ - */ +\addtogroup m2ts_grp MPEG-2 TS +\ingroup media_grp +\brief MPEG-TS demultiplexer and multiplexer APIs. + +This section documents the MPEG-TS demultiplexer and multiplexer APIs. + +@{ +*/ #include <gpac/list.h> @@ -47,8 +48,18 @@ #include <gpac/internal/odf_dev.h> +#ifdef __cplusplus +extern "C" { +#endif + +/*! metadata types for GF_M2TS_METADATA_POINTER_DESCRIPTOR*/ +enum { + GF_M2TS_META_ID3 = GF_4CC('I','D','3',' '), +}; + -/*MPEG-2 Descriptor tags*/ + +/*! MPEG-2 Descriptor tags*/ enum { /* ... */ @@ -128,7 +139,7 @@ enum GF_M2TS_DVB_LOGICAL_CHANNEL_DESCRIPTOR = 0x83, }; -/* Reserved PID values */ +/*! Reserved PID values */ enum { GF_M2TS_PID_PAT = 0x0000, GF_M2TS_PID_CAT = 0x0001, @@ -148,7 +159,7 @@ enum { GF_M2TS_PID_SIT = 0x001F }; -/* max size includes first header, second header, payload and CRC */ +/*! max size includes first header, second header, payload and CRC */ enum { GF_M2TS_TABLE_ID_PAT = 0x00, GF_M2TS_TABLE_ID_CAT = 0x01, @@ -201,8 +212,8 @@ enum { /* 0xff reserved */ }; -/*MPEG-2 TS Media types*/ -enum +/*! MPEG-2 TS Media types*/ +typedef enum { GF_M2TS_VIDEO_MPEG1 = 0x01, GF_M2TS_VIDEO_MPEG2 = 0x02, @@ -230,11 +241,23 @@ enum GF_M2TS_VIDEO_H264 = 0x1B, GF_M2TS_VIDEO_SVC = 0x1F, GF_M2TS_VIDEO_HEVC = 0x24, + GF_M2TS_VIDEO_HEVC_TEMPORAL = 0x25, + GF_M2TS_VIDEO_MVCD = 0x26, + GF_M2TS_TEMI = 0x27, GF_M2TS_VIDEO_SHVC = 0x28, GF_M2TS_VIDEO_SHVC_TEMPORAL = 0x29, GF_M2TS_VIDEO_MHVC = 0x2A, GF_M2TS_VIDEO_MHVC_TEMPORAL = 0x2B, + GF_M2TS_GREEN = 0x2C, + GF_M2TS_MHAS_MAIN = 0x2D, + GF_M2TS_MHAS_AUX = 0x2E, + + GF_M2TS_QUALITY_SEC = 0x2F, + GF_M2TS_MORE_SEC = 0x30, + + GF_M2TS_VIDEO_HEVC_MCTS = 0x31, + GF_M2TS_VIDEO_DCII = 0x80, GF_M2TS_AUDIO_AC3 = 0x81, GF_M2TS_AUDIO_DTS = 0x8A, @@ -248,11 +271,20 @@ enum GF_M2TS_DVB_VBI = 0x153, GF_M2TS_DVB_SUBTITLE = 0x154, GF_M2TS_METADATA_ID3_HLS = 0x155, -}; +} GF_M2TSStreamType; + +/*! MPEG-2 TS Registration codes types*/ +enum +{ + GF_M2TS_RA_STREAM_AC3 = GF_4CC('A','C','-','3'), + GF_M2TS_RA_STREAM_VC1 = GF_4CC('V','C','-','1'), + + GF_M2TS_RA_STREAM_GPAC = GF_4CC('G','P','A','C') +}; -/*MPEG-2 Descriptor tags*/ +/*! MPEG-2 Descriptor tags*/ enum { GF_M2TS_AFDESC_TIMELINE_DESCRIPTOR = 0x04, @@ -260,31 +292,30 @@ enum GF_M2TS_AFDESC_BASEURL_DESCRIPTOR = 0x06, }; -#define SECTION_HEADER_LENGTH 3 /* header till the last bit of the section_length field */ -#define SECTION_ADDITIONAL_HEADER_LENGTH 5 /* header from the last bit of the section_length field to the payload */ +/*! header till the last bit of the section_length field */ +#define SECTION_HEADER_LENGTH 3 +/*! header from the last bit of the section_length field to the payload */ +#define SECTION_ADDITIONAL_HEADER_LENGTH 5 +/*! CRC32 length*/ #define CRC_LENGTH 4 #ifndef GPAC_DISABLE_MPEG2TS - -#include <time.h> - +/*! MPEG-2 TS demuxer*/ typedef struct tag_m2ts_demux GF_M2TS_Demuxer; +/*! MPEG-2 TS demuxer elementary stream*/ typedef struct tag_m2ts_es GF_M2TS_ES; +/*! MPEG-2 TS demuxer elementary stream section*/ typedef struct tag_m2ts_section_es GF_M2TS_SECTION_ES; -#ifdef GPAC_HAS_LINUX_DVB -typedef struct __gf_dvb_tuner GF_Tuner; -#endif - -/*Maximum number of streams in a TS*/ +/*! Maximum number of streams in a TS*/ #define GF_M2TS_MAX_STREAMS 8192 -/*Maximum number of service in a TS*/ +/*! Maximum number of service in a TS*/ #define GF_M2TS_MAX_SERVICES 65535 -/*Maximum size of the buffer in UDP */ +/*! Maximum size of the buffer in UDP */ #ifdef WIN32 #define GF_M2TS_UDP_BUFFER_SIZE 0x80000 #else @@ -293,21 +324,36 @@ typedef struct __gf_dvb_tuner GF_Tuner; #define GF_M2TS_UDP_BUFFER_SIZE 0x40000 #endif +/*! Maximum PCR value */ #define GF_M2TS_MAX_PCR 2576980377811ULL -/*returns readable name for given stream type*/ -const char *gf_m2ts_get_stream_name(u32 streamType); +/*! gets the stream name for an MPEG-2 stream type +\param streamType the target stream type +\return name of the stream type*/ +const char *gf_m2ts_get_stream_name(GF_M2TSStreamType streamType); -/*returns 1 if file is an MPEG-2 TS */ +/*! probes a file for MPEG-2 TS format +\param fileName name / path of the file to brobe +\return GF_TRUE if file is an MPEG-2 TS */ Bool gf_m2ts_probe_file(const char *fileName); -/*shifts all timing by the given value -@is_pes: array of GF_M2TS_MAX_STREAMS u8 set to 1 for PES PIDs to be restamped +/*! probes a buffer for MPEG-2 TS format +\param data data buffer to probe +\param size size of buffer to probe +\return GF_TRUE if file is an MPEG-2 TS */ +Bool gf_m2ts_probe_data(const u8 *data, u32 size); + +/*! restamps a set of TS packets by shifting all timing by the given value +\param buffer data buffer to restamp +\param size size of buffer +\param ts_shift clock shift in 90 kHz +\param is_pes array of GF_M2TS_MAX_STREAMS u8 set to 1 for PES PIDs to be restamped, 0 to stream to left untouched +\return error if any */ -GF_Err gf_m2ts_restamp(char *buffer, u32 size, s64 ts_shift, u8 *is_pes); +GF_Err gf_m2ts_restamp(u8 *buffer, u32 size, s64 ts_shift, u8 is_pes[GF_M2TS_MAX_STREAMS]); -/*PES data framing modes*/ -enum +/*! PES data framing modes*/ +typedef enum { /*skip pes processing: all transport packets related to this stream are discarded*/ GF_M2TS_PES_FRAMING_SKIP, @@ -316,181 +362,202 @@ enum /*don't use data framing: all packets are raw PES packets*/ GF_M2TS_PES_FRAMING_RAW, + //TODO - remove this one, we no longer reframe in the TS demuxer /*use data framing: recompute start of AUs (data frames)*/ GF_M2TS_PES_FRAMING_DEFAULT, - /*same as defualt PES framing but forces nal-per-nal dispatch for AVC/HEVC (default mode may dispatch complete frames)*/ - GF_M2TS_PES_FRAMING_DEFAULT_NAL, -}; +} GF_M2TSPesFraming; -/*PES packet flags*/ +/*! PES packet flags*/ enum { + /*! PES packet is RAP*/ GF_M2TS_PES_PCK_RAP = 1, + /*! PES packet contains an AU start*/ GF_M2TS_PES_PCK_AU_START = 1<<1, - /*visual frame starting in this packet is an I frame or IDR (AVC/H264)*/ + /*! visual frame starting in this packet is an I frame or IDR (AVC/H264)*/ GF_M2TS_PES_PCK_I_FRAME = 1<<2, - /*visual frame starting in this packet is a P frame*/ + /*! visual frame starting in this packet is a P frame*/ GF_M2TS_PES_PCK_P_FRAME = 1<<3, - /*visual frame starting in this packet is a B frame*/ + /*! visual frame starting in this packet is a B frame*/ GF_M2TS_PES_PCK_B_FRAME = 1<<4, - /*Possible PCR discontinuity from this packet on*/ + /*! Possible PCR discontinuity from this packet on*/ GF_M2TS_PES_PCK_DISCONTINUITY = 1<<5 }; -/*Events used by the MPEGTS demuxer*/ +/*! Events used by the MPEGTS demuxer*/ enum { - /*PAT has been found (service connection) - no assoctiated parameter*/ + /*! PAT has been found (service connection) - no associated parameter*/ GF_M2TS_EVT_PAT_FOUND = 0, - /*PAT has been updated - no assoctiated parameter*/ + /*! PAT has been updated - no associated parameter*/ GF_M2TS_EVT_PAT_UPDATE, - /*repeated PAT has been found (carousel) - no assoctiated parameter*/ + /*! repeated PAT has been found (carousel) - no associated parameter*/ GF_M2TS_EVT_PAT_REPEAT, - /*PMT has been found (service tune-in) - assoctiated parameter: new PMT*/ + /*! PMT has been found (service tune-in) - associated parameter: new PMT*/ GF_M2TS_EVT_PMT_FOUND, - /*repeated PMT has been found (carousel) - assoctiated parameter: updated PMT*/ + /*! repeated PMT has been found (carousel) - associated parameter: updated PMT*/ GF_M2TS_EVT_PMT_REPEAT, - /*PMT has been changed - assoctiated parameter: updated PMT*/ + /*! PMT has been changed - associated parameter: updated PMT*/ GF_M2TS_EVT_PMT_UPDATE, - /*SDT has been received - assoctiated parameter: none*/ + /*! SDT has been received - associated parameter: none*/ GF_M2TS_EVT_SDT_FOUND, - /*repeated SDT has been found (carousel) - assoctiated parameter: none*/ + /*! repeated SDT has been found (carousel) - associated parameter: none*/ GF_M2TS_EVT_SDT_REPEAT, - /*SDT has been received - assoctiated parameter: none*/ + /*! SDT has been received - associated parameter: none*/ GF_M2TS_EVT_SDT_UPDATE, - /*INT has been received - assoctiated parameter: none*/ + /*! INT has been received - associated parameter: none*/ GF_M2TS_EVT_INT_FOUND, - /*repeated INT has been found (carousel) - assoctiated parameter: none*/ + /*! repeated INT has been found (carousel) - associated parameter: none*/ GF_M2TS_EVT_INT_REPEAT, - /*INT has been received - assoctiated parameter: none*/ + /*! INT has been received - associated parameter: none*/ GF_M2TS_EVT_INT_UPDATE, - /*PES packet has been received - assoctiated parameter: PES packet*/ + /*! PES packet has been received - associated parameter: PES packet*/ GF_M2TS_EVT_PES_PCK, - /*PCR has been received - associated parameter: PES packet with no data*/ + /*! PCR has been received - associated parameter: PES packet with no data*/ GF_M2TS_EVT_PES_PCR, - /*PTS/DTS/PCR info - assoctiated parameter: PES packet with no data*/ + /*! PTS/DTS/PCR info - associated parameter: PES packet with no data*/ GF_M2TS_EVT_PES_TIMING, - /*An MPEG-4 SL Packet has been received in a section - assoctiated parameter: SL packet */ + /*! An MPEG-4 SL Packet has been received in a section - associated parameter: SL packet */ GF_M2TS_EVT_SL_PCK, - /*An IP datagram has been received in a section - assoctiated parameter: IP datagram */ + /*! An IP datagram has been received in a section - associated parameter: IP datagram */ GF_M2TS_EVT_IP_DATAGRAM, - /*Duration has been estimated - assoctiated parameter: PES packet with no data, PTS is duration in msec*/ + /*! Duration has been estimated - associated parameter: PES packet with no data, PTS is duration in msec*/ GF_M2TS_EVT_DURATION_ESTIMATED, + /*! TS packet processed - associated parameter: pointer to a GF_M2TS_TSPCK structure*/ + GF_M2TS_EVT_PCK, - /*AAC config has been extracted - associated parameter: PES Packet with encoded M4ADecSpecInfo in its data + /*! AAC config has been extracted - associated parameter: PES Packet with encoded M4ADecSpecInfo in its data THIS MUST BE CLEANED UP */ GF_M2TS_EVT_AAC_CFG, #if 0 - /* An EIT message for the present or following event on this TS has been received */ + /*! An EIT message for the present or following event on this TS has been received */ GF_M2TS_EVT_EIT_ACTUAL_PF, - /* An EIT message for the schedule of this TS has been received */ + /*! An EIT message for the schedule of this TS has been received */ GF_M2TS_EVT_EIT_ACTUAL_SCHEDULE, - /* An EIT message for the present or following event of an other TS has been received */ + /*! An EIT message for the present or following event of an other TS has been received */ GF_M2TS_EVT_EIT_OTHER_PF, - /* An EIT message for the schedule of an other TS has been received */ + /*! An EIT message for the schedule of an other TS has been received */ GF_M2TS_EVT_EIT_OTHER_SCHEDULE, #endif - /* A message to inform about the current date and time in the TS */ + /*! A message to inform about the current date and time in the TS */ GF_M2TS_EVT_TDT, - /* A message to inform about the current time offset in the TS */ + /*! A message to inform about the current time offset in the TS */ GF_M2TS_EVT_TOT, - /* A generic event message for EIT, TDT, TOT etc */ + /*! A generic event message for EIT, TDT, TOT etc */ GF_M2TS_EVT_DVB_GENERAL, - /* MPE / MPE-FEC frame extraction and IP datagrams decryptation */ + /*! MPE / MPE-FEC frame extraction and IP datagrams decryptation */ GF_M2TS_EVT_DVB_MPE, - /*CAT has been found (service tune-in) - assoctiated parameter: new CAT*/ + /*! CAT has been found (service tune-in) - associated parameter: new CAT*/ GF_M2TS_EVT_CAT_FOUND, - /*repeated CAT has been found (carousel) - assoctiated parameter: updated CAT*/ + /*! repeated CAT has been found (carousel) - associated parameter: updated CAT*/ GF_M2TS_EVT_CAT_REPEAT, - /*PMT has been changed - assoctiated parameter: updated PMT*/ + /*! CAT has been changed - associated parameter: updated PMT*/ GF_M2TS_EVT_CAT_UPDATE, - /*AIT has been found (carousel) */ + /*! AIT has been found (carousel) */ GF_M2TS_EVT_AIT_FOUND, - /*DSCM-CC has been found (carousel) */ + /*! DSCM-CC has been found (carousel) */ GF_M2TS_EVT_DSMCC_FOUND, - /*a TEMI locator has been found or repeated*/ + /*! a TEMI locator has been found or repeated*/ GF_M2TS_EVT_TEMI_LOCATION, - /*a TEMI timecode has been found*/ - GF_M2TS_EVT_TEMI_TIMECODE, - - GF_M2TS_EVT_EOS, + /*! a TEMI timecode has been found*/ + GF_M2TS_EVT_TEMI_TIMECODE }; +/*! table parsing state*/ enum { + /*! flag set for start of the table */ GF_M2TS_TABLE_START = 1, + /*! flag set for end of the table */ GF_M2TS_TABLE_END = 1<<1, + /*! flag set when first occurence of the table */ GF_M2TS_TABLE_FOUND = 1<<2, + /*! flag set when update of the table */ GF_M2TS_TABLE_UPDATE = 1<<3, - //both update and repeat flags may be set if data has changed + /*! flag set when repetition of the table - both update and repeat flags may be set if data has changed*/ GF_M2TS_TABLE_REPEAT = 1<<4, }; -typedef void (*gf_m2ts_section_callback)(GF_M2TS_Demuxer *ts, GF_M2TS_SECTION_ES *es, GF_List *sections, u8 table_id, u16 ex_table_id, u8 version_number, u8 last_section_number, u32 status); +/*! section callback function +\param demux the target MPEG-2 TS demultiplexer +\param es the target section stream +\param sections the list of gathered sections +\param table_id the ID of the table +\param ex_table_id the extended ID of the table +\param version_number the version number of the table +\param last_section_number the last section number of the table for fragmented cases +\param status the parsing status flags +*/ +typedef void (*gf_m2ts_section_callback)(GF_M2TS_Demuxer *demux, GF_M2TS_SECTION_ES *es, GF_List *sections, u8 table_id, u16 ex_table_id, u8 version_number, u8 last_section_number, u32 status); +/*! MPEG-2 TS demuxer section*/ typedef struct __m2ts_demux_section { - unsigned char *data; + /*! section data*/ + u8 *data; + /*! section data size in bytes*/ u32 data_size; } GF_M2TS_Section; +/*! MPEG-2 TS demuxer table*/ typedef struct __m2ts_demux_table { + /*! pointer to next table/section*/ struct __m2ts_demux_table *next; - + /*! set when first table completely received*/ u8 is_init; + /*! set when repeated section*/ u8 is_repeat; - - /*table id*/ + /*! table id*/ u8 table_id; + /*! extended table id*/ u16 ex_table_id; - - /*reassembler state*/ + /*! current section version number*/ u8 version_number; + /*! last received version number*/ u8 last_version_number; - + /*! current/next indicator (cf MPEG-2 TS spec)*/ u8 current_next_indicator; - + /*! current section number*/ u8 section_number; + /*! last section number to get the complete table*/ u8 last_section_number; - + /*! list of sections for this table*/ GF_List *sections; - + /*! total table size*/ u32 table_size; } GF_M2TS_Table; +/*! MPEG-2 TS demuxer section filter*/ typedef struct GF_M2TS_SectionFilter { - /*section reassembler*/ + /*! section reassembler*/ s16 cc; - /*section buffer (max 4096)*/ - char *section; - /*current section length as indicated in section header*/ + /*! section buffer (max 4096)*/ + u8 *section; + /*! current section length as indicated in section header*/ u16 length; - /*number of bytes received from current section*/ + /*! number of bytes received from current section*/ u16 received; - - /*section->table aggregator*/ + /*! section->table aggregator*/ GF_M2TS_Table *table; - - /* indicates that the section and last_section_number do not need to be checked */ + /*! indicates that the section and last_section_number do not need to be checked */ Bool process_individual; - - /* indicates that the section header with table id and extended table id ... is + /*! indicates that the section header with table id and extended table id ... is not parsed by the TS demuxer and left for the application */ Bool direct_dispatch; - - /* this field is used for AIT sections, to link the AIT with the program */ + /*! this field is used for AIT sections, to link the AIT with the program */ u32 service_id; - + /*! section callback*/ gf_m2ts_section_callback process_section; - + /*! flag indicatin the demultiplexer has been restarted*/ Bool demux_restarted; } GF_M2TS_SectionFilter; +/*! metadata carriage types*/ enum metadata_carriage { METADATA_CARRIAGE_SAME_TS = 0, METADATA_CARRIAGE_DIFFERENT_TS = 1, @@ -498,6 +565,7 @@ enum metadata_carriage { METADATA_CARRIAGE_OTHER = 3 }; +/*! MPEG-2 TS demuxer metadat pointer*/ typedef struct tag_m2ts_metadata_pointer_descriptor { u16 application_format; u32 application_format_identifier; @@ -511,12 +579,14 @@ typedef struct tag_m2ts_metadata_pointer_descriptor { u16 program_number; u16 ts_location; u16 ts_id; - char *data; + u8 *data; u32 data_size; } GF_M2TS_MetadataPointerDescriptor; +/*! MPEG-2 TS demuxer TEMI location*/ typedef struct { + u32 pid; u32 timeline_id; //for now we only support one URL announcement const char *external_URL; @@ -525,8 +595,10 @@ typedef struct Double activation_countdown; } GF_M2TS_TemiLocationDescriptor; +/*! MPEG-2 TS demuxer TEMI timecode*/ typedef struct { + u32 pid; u32 timeline_id; u32 media_timescale; u64 media_timestamp; @@ -538,82 +610,87 @@ typedef struct } GF_M2TS_TemiTimecodeDescriptor; -/*MPEG-2 TS program object*/ +/*! MPEG-2 TS program object*/ typedef struct { + /*! parent demuxer*/ GF_M2TS_Demuxer *ts; - + /*! list of streams (PES and sections)*/ GF_List *streams; + /*! PID of PMT*/ u32 pmt_pid; + /*! PID of PCR*/ u32 pcr_pid; + /*! program number*/ u32 number; - + /*! MPEG-4 IOD if any*/ GF_InitialObjectDescriptor *pmt_iod; - - /*list of additional ODs found per program !! used by media importer only , refine this !! + /*! list of additional ODs found per program this list is only created when MPEG-4 over MPEG-2 is detected - the list AND the ODs contained in it are destroyed when destroying the demuxer + the list AND the ODs contained in it are destroyed when destroying the program/demuxer */ GF_List *additional_ods; - /*first dts found on this program - this is used by parsers, but not setup by the lib*/ + /*! first dts found on this program - this is used by parsers, but not setup by the lib*/ u64 first_dts; - - /* Last PCR value received for this program and associated packet number */ + /*! Last PCR value received for this program and associated packet number */ u64 last_pcr_value; + /*! packet number of last PCR value received*/ u32 last_pcr_value_pck_number; - /* PCR value before the last received one for this program and associated packet number + /*! PCR value before the last received one for this program and associated packet number used to compute PCR interpolation value*/ u64 before_last_pcr_value; + /*! packet number of before last PCR value received*/ u32 before_last_pcr_value_pck_number; - - /*for hybrid use-cases we need to know if TDT has already been processed*/ + /*! for hybrid use-cases we need to know if TDT has already been processed*/ Bool tdt_found; - + /*! indicates if PID is playing. Used in scalable streams only to toggle quality switch*/ u32 pid_playing; + /*! */ Bool is_scalable; - + /*! metadata descriptor pointer*/ GF_M2TS_MetadataPointerDescriptor *metadata_pointer_descriptor; - - /*continuity counter check for pure PCR PIDs*/ + /*! continuity counter check for pure PCR PIDs*/ s16 pcr_cc; + + void *user; } GF_M2TS_Program; -/*ES flags*/ +/*! ES flags*/ enum { - /*ES is a PES stream*/ + /*! ES is a PES stream*/ GF_M2TS_ES_IS_PES = 1, - /*ES is a section stream*/ + /*! ES is a section stream*/ GF_M2TS_ES_IS_SECTION = 1<<1, - /*ES is an mpeg-4 flexmux stream*/ + /*! ES is an mpeg-4 flexmux stream*/ GF_M2TS_ES_IS_FMC = 1<<2, - /*ES is an mpeg-4 SL-packetized stream*/ + /*! ES is an mpeg-4 SL-packetized stream*/ GF_M2TS_ES_IS_SL = 1<<3, - /*ES is an mpeg-4 Object Descriptor SL-packetized stream*/ + /*! ES is an mpeg-4 Object Descriptor SL-packetized stream*/ GF_M2TS_ES_IS_MPEG4_OD = 1<<4, - /*ES is a DVB MPE stream*/ + /*! ES is a DVB MPE stream*/ GF_M2TS_ES_IS_MPE = 1<<5, - /*stream is used to send PCR to upper layer*/ + /*! stream is used to send PCR to upper layer*/ GF_M2TS_INHERIT_PCR = 1<<6, - /*siugnals the stream is used to send the PCR, but is not the original PID carrying it*/ + /*! signals the stream is used to send the PCR, but is not the original PID carrying it*/ GF_M2TS_FAKE_PCR = 1<<7, + /*! signals the stream type is a gpac codec id*/ + GF_M2TS_GPAC_CODEC_ID = 1<<8, + /*! signals the stream type is a gpac codec id*/ + GF_M2TS_ES_IS_PMT = 1<<9, - /*all flags above this mask are used by importers & co*/ + /*! all flags above this mask are used by demultiplexer users*/ GF_M2TS_ES_STATIC_FLAGS_MASK = 0x0000FFFF, - /*always send sections regardless of their version_number*/ + /*! always send sections regardless of their version_number*/ GF_M2TS_ES_SEND_REPEATED_SECTIONS = 1<<16, - /*Flag used by importers*/ - GF_M2TS_ES_FIRST_DTS = 1<<17, - - /*flag used to signal next discontinuity on stream should be ignored*/ - GF_M2TS_ES_IGNORE_NEXT_DISCONTINUITY = 1<<18, - - /*Flag used by importers/readers to mark streams that have been seen already in PMT process (update/found)*/ - GF_M2TS_ES_ALREADY_DECLARED = 1<<19 + /*! flag used to signal next discontinuity on stream should be ignored*/ + GF_M2TS_ES_IGNORE_NEXT_DISCONTINUITY = 1<<17, + /*! flag used by importers/readers to mark streams that have been seen already in PMT process (update/found)*/ + GF_M2TS_ES_ALREADY_DECLARED = 1<<18 }; -/*Abstract Section/PES stream object, only used for type casting*/ +/*! macro for abstract Section/PES stream object, only used for type casting*/ #define ABSTRACT_ES \ GF_M2TS_Program *program; \ u32 flags; \ @@ -623,32 +700,44 @@ enum GF_SLConfig *slcfg; \ s16 component_tag; \ void *user; \ + GF_List *props; \ u64 first_dts; \ u32 service_id; +/*! abstract Section/PES stream object*/ struct tag_m2ts_es { ABSTRACT_ES }; +/*! MPEG-2 TS muxer PES header*/ typedef struct { + /*! stream ID*/ u8 id; + /*! packet len, 0 if unknown*/ u16 pck_len; + /*! data alignment flag*/ u8 data_alignment; - u64 PTS, DTS; + /*! packet PTS in 90khz*/ + u64 PTS; + /*! packet DTS in 90khz*/ + u64 DTS; + /*! size of PES header*/ u8 hdr_data_len; } GF_M2TS_PESHeader; +/*! Section elementary stream*/ struct tag_m2ts_section_es { + /*! derive from base M2TS stream*/ ABSTRACT_ES + /*! section reassembler*/ GF_M2TS_SectionFilter *sec; }; - -/*******************************************************************************/ +/*! MPEG-2 TS muxer DVB subtitling descriptor*/ typedef struct tag_m2ts_dvb_sub { char language[3]; @@ -657,6 +746,7 @@ typedef struct tag_m2ts_dvb_sub u16 ancillary_page_id; } GF_M2TS_DVB_Subtitling_Descriptor; +/*! MPEG-2 TS muxer DVB teletext descriptor*/ typedef struct tag_m2ts_dvb_teletext { char language[3]; @@ -665,6 +755,7 @@ typedef struct tag_m2ts_dvb_teletext u8 page_number; } GF_M2TS_DVB_Teletext_Descriptor; +/*! MPEG-2 TS muxer metadata descriptor*/ typedef struct tag_m2ts_metadata_descriptor { u16 application_format; u32 application_format_identifier; @@ -676,82 +767,96 @@ typedef struct tag_m2ts_metadata_descriptor { u8 service_id_record_length; char *service_id_record; u8 decoder_config_length; - char *decoder_config; + u8 *decoder_config; u8 decoder_config_id_length; - char *decoder_config_id; + u8 *decoder_config_id; u8 decoder_config_service_id; } GF_M2TS_MetadataDescriptor; -/*MPEG-2 TS ES object*/ +//! @cond Doxygen_Suppress + +/*! MPEG-2 TS ES object*/ typedef struct tag_m2ts_pes { + /*! derive from base M2TS stream*/ ABSTRACT_ES - /*continuity counter check*/ + /*! continuity counter check*/ s16 cc; + /*! language tag*/ u32 lang; - - /*object info*/ - u32 vid_w, vid_h, vid_par, aud_sr, aud_nb_ch, aud_aac_obj_type, aud_aac_sr_idx; - + /*! PID of stream this stream depends on*/ u32 depends_on_pid; - /*user private*/ - - - /*mpegts lib private - do not touch :)*/ - /*PES re-assembler*/ - unsigned char *pck_data; - /*amount of bytes allocated for data */ + /*mpegts lib private - do not touch*/ + /*! PES re-assembler data*/ + u8 *pck_data; + /*! amount of bytes allocated for data */ u32 pck_alloc_len; - /*amount of bytes received in the current PES packet (NOT INCLUDING ANY PENDING BYTES)*/ + /*! amount of bytes received in the current PES packet (NOT INCLUDING ANY PENDING BYTES)*/ u32 pck_data_len; - /*size of the PES packet being received, as indicated in pes header length field - can be 0 if unknown*/ + /*! size of the PES packet being received, as indicated in pes header length field - can be 0 if unknown*/ u32 pes_len; + /*! RAP flag*/ Bool rap; - u64 PTS, DTS; - u32 pes_end_packet_number; - /*bytes not consumed from previous PES - shall be less than 9*/ - unsigned char *prev_data; - /*number of bytes not consumed from previous PES - shall be less than 9*/ + /*! PES PTS in 90khz*/ + u64 PTS; + /*! PES DTS in 90khz*/ + u64 DTS; + /*! bytes not consumed from previous PES - shall be less than 9*/ + u8 *prev_data; + /*! number of bytes not consumed from previous PES - shall be less than 9*/ u32 prev_data_len; - + /*! number of TS packet containing the start of the current PES*/ u32 pes_start_packet_number; - /* PCR info related to the PES start */ - /* Last PCR value received for this program and associated packet number */ + /*! number of TS packet containing the end of the current PES*/ + u32 pes_end_packet_number; + /*! Last PCR value received for this program*/ u64 last_pcr_value; + /*! packet number of last PCR*/ u32 last_pcr_value_pck_number; - /* PCR value before the last received one for this program and associated packet number - used to compute PCR interpolation value*/ + /*! PCR value before the last received one for this program (used to compute PCR interpolation value)*/ u64 before_last_pcr_value; + /*! packet number of before last PCR*/ u32 before_last_pcr_value_pck_number; - /*PES reframer - if NULL, pes processing is skiped*/ - /*returns the number of bytes NOT consummed from the input data buffer - these bytes are kept when reassembling the next PES packet*/ - u32 (*reframe)(struct tag_m2ts_demux *ts, struct tag_m2ts_pes *pes, Bool same_pts, unsigned char *data, u32 data_len, GF_M2TS_PESHeader *hdr); + /*! PES reframer callback. If NULL, pes processing is skiped - Bool single_nal_mode; - /*used by several reframers to store their parsing state*/ - u32 frame_state; - /*LATM stuff - should be moved out of mpegts*/ - unsigned char *buf, *reassemble_buf; - u32 buf_len; - u32 reassemble_len, reassemble_alloc; - u64 prev_PTS; + returns the number of bytes NOT consummed from the input data buffer - these bytes are kept when reassembling the next PES packet*/ + u32 (*reframe)(struct tag_m2ts_demux *ts, struct tag_m2ts_pes *pes, Bool same_pts, u8 *data, u32 data_len, GF_M2TS_PESHeader *hdr); + /*! DVB subtitling info*/ GF_M2TS_DVB_Subtitling_Descriptor sub; + /*! Metadata descriptor (for ID3)*/ GF_M2TS_MetadataDescriptor *metadata_descriptor; - //pointer to last received temi - char *temi_tc_desc; - u32 temi_tc_desc_len, temi_tc_desc_alloc_size; + /*! last received TEMI payload*/ + u8 *temi_tc_desc; + /*! last received TEMI payload size*/ + u32 temi_tc_desc_len; + /*! allocated size of TEMI reception buffer*/ + u32 temi_tc_desc_alloc_size; - //last decoded temi (may be one ahead of time as the last received TEMI) + /*! last decoded temi (may be one ahead of time as the last received TEMI)*/ GF_M2TS_TemiTimecodeDescriptor temi_tc; + /*! flag set to indicate a TEMI descriptor should be flushed with next packet*/ Bool temi_pending; } GF_M2TS_PES; -/*SDT information object*/ +/*! reserved streamID for PES headers*/ +enum +{ + GF_M2_STREAMID_PROGRAM_STREAM_MAP = 0xBC, + GF_M2_STREAMID_PADDING = 0xBE, + GF_M2_STREAMID_PRIVATE_2 = 0xBF, + GF_M2_STREAMID_ECM = 0xF0, + GF_M2_STREAMID_EMM = 0xF1, + GF_M2_STREAMID_PROGRAM_STREAM_DIRECTORY = 0xFF, + GF_M2_STREAMID_DSMCC = 0xF2, + GF_M2_STREAMID_H222_TYPE_E = 0xF8 +}; + +/*! SDT (service description table) information*/ typedef struct { u16 original_network_id; @@ -765,6 +870,7 @@ typedef struct char *provider, *service; } GF_M2TS_SDT; +/*! NIT (network information table) information*/ typedef struct { u16 network_id; @@ -776,6 +882,7 @@ typedef struct u32 logical_channel_number; } GF_M2TS_NIT; +/*! TDT/TOT (Time and Date table) information*/ typedef struct { u16 year; @@ -786,41 +893,44 @@ typedef struct u8 second; } GF_M2TS_TDT_TOT; -#define GF_M2TS_BASE_DESCRIPTOR u32 tag; - +/*! DVB Content Description information*/ typedef struct { u8 content_nibble_level_1, content_nibble_level_2, user_nibble; } GF_M2TS_DVB_Content_Descriptor; +/*! DVB Rating information*/ typedef struct { char country_code[3]; u8 value; } GF_M2TS_DVB_Rating_Descriptor; +/*! DVB Short Event information*/ typedef struct { - unsigned char lang[3]; - unsigned char *event_name, *event_text; + u8 lang[3]; + char *event_name, *event_text; } GF_M2TS_DVB_Short_Event_Descriptor; +/*! DVB Extended Event entry*/ typedef struct { - unsigned char *item; - unsigned char *description; + char *item; + char *description; } GF_M2TS_DVB_Extended_Event_Item; +/*! DVB Extended Event information*/ typedef struct { - unsigned char lang[3]; + u8 lang[3]; u32 last; GF_List *items; - unsigned char *text; + char *text; } GF_M2TS_DVB_Extended_Event_Descriptor; -/*EIT information objects*/ +/*! DVB EIT Date and Time information*/ typedef struct { time_t unix_time; /* local time offset descriptor data (unused ...) */ - char country_code[3]; + u8 country_code[3]; u8 country_region_id; s32 local_time_offset_seconds; time_t unix_next_toc; @@ -828,14 +938,16 @@ typedef struct } GF_M2TS_DateTime_Event; +/*! DVB EIT component information*/ typedef struct { u8 stream_content; u8 component_type; u8 component_tag; - char language_code[3]; - unsigned char *text; + u8 language_code[3]; + char *text; } GF_M2TS_Component; +/*! DVB EIT Event information*/ typedef struct { u16 event_id; @@ -852,6 +964,7 @@ typedef struct GF_List *ratings; } GF_M2TS_EIT_Event; +/*! DVB EIT information*/ typedef struct { u32 original_network_id; @@ -861,10 +974,10 @@ typedef struct u8 table_id; } GF_M2TS_EIT; -/*MPEG-2 TS packet*/ +/*! MPEG-2 TS packet*/ typedef struct { - char *data; + u8 *data; u32 data_len; u32 flags; u64 PTS, DTS; @@ -872,170 +985,226 @@ typedef struct GF_M2TS_PES *stream; } GF_M2TS_PES_PCK; -/*MPEG-4 SL packet from MPEG-2 TS*/ +/*! MPEG-4 SL packet from MPEG-2 TS*/ typedef struct { - char *data; + u8 *data; u32 data_len; u8 version_number; /*parent stream */ GF_M2TS_ES *stream; } GF_M2TS_SL_PCK; -/*MPEG-2 TS demuxer*/ -struct tag_m2ts_demux +/*! MPEG-4 SL packet from MPEG-2 TS*/ +typedef struct { - /* From M2TSIn */ - GF_List *requested_progs; - GF_List *requested_pids; - - /*demuxer thread*/ - GF_Thread *th; - u32 run_state; - - Bool force_file_refresh; - /*net playing*/ - GF_Socket *sock; - Bool sock_is_delegate; - -#ifdef GPAC_HAS_LINUX_DVB - /*dvb playing*/ - GF_Tuner *tuner; -#endif - /*local file playing*/ - FILE *file; - char filename[GF_MAX_PATH]; - //start/end in ms - u32 start_range, end_range; - u64 file_size; - u64 pos_in_stream; - Double duration; - u32 nb_pck; - Bool loop_demux; - const char *ts_data_chunk; - u32 ts_data_chunk_size; - - const char *record_to; - /* "Network" = "MobileIP", "DefaultMCastInterface" */ - Bool MobileIPEnabled; - const char *network_type; - //for sockets, we need to reopen them after resume/restart.... - char *socket_url; - /* Set it to 1 if the TS is meant to be played during the demux */ - Bool demux_and_play; - /* End of M2TSIn */ + /*packet start (first byte is TS sync marker)*/ + u8 *data; + /*packet PID*/ + u32 pid; + /*parent stream if any/already declared*/ + GF_M2TS_ES *stream; +} GF_M2TS_TSPCK; + +typedef struct +{ + u8 version_number; + u8 table_id; + u16 ex_table_id; +} GF_M2TS_SectionInfo; +/*! MPEG-2 TS demuxer*/ +struct tag_m2ts_demux +{ + /*! PIDs*/ GF_M2TS_ES *ess[GF_M2TS_MAX_STREAMS]; + /*! programs*/ GF_List *programs; + /*! number of PMT received*/ u32 nb_prog_pmt_received; + /*! set to GF_TRUE if all PMT received*/ Bool all_prog_pmt_received; + /*! set to GF_TRUE if all programs are setup*/ Bool all_prog_processed; - /*keep it seperate for now - TODO check if we're sure of the order*/ + /*! received SDTs - keep it separate for now*/ GF_List *SDTs; - GF_M2TS_TDT_TOT *TDT_time; /*UTC time from both TDT and TOT (we currently ignore local offset)*/ + /*! UTC time from both TDT and TOT (we currently ignore local offset)*/ + GF_M2TS_TDT_TOT *TDT_time; - /*user callback - MUST NOT BE NULL*/ + /*! user callback - MUST NOT BE NULL*/ void (*on_event)(struct tag_m2ts_demux *ts, u32 evt_type, void *par); - /*private user data*/ + /*! private user data*/ void *user; - /*private resync buffer*/ - char *buffer; - u32 buffer_size, alloc_size; - /*default transport PID filters*/ - GF_M2TS_SectionFilter *pat, *cat, *nit, *sdt, *eit, *tdt_tot; - + /*! private resync buffer*/ + u8 *buffer; + /*! private resync size*/ + u32 buffer_size; + /*! private alloc size*/ + u32 alloc_size; + /*! PAT filter*/ + GF_M2TS_SectionFilter *pat; + /*! CAT filter*/ + GF_M2TS_SectionFilter *cat; + /*! NIT filter*/ + GF_M2TS_SectionFilter *nit; + /*! SDT filter*/ + GF_M2TS_SectionFilter *sdt; + /*! EIT filter*/ + GF_M2TS_SectionFilter *eit; + /*! TDT/TOT filter*/ + GF_M2TS_SectionFilter *tdt_tot; + /*! MPEG-4 over 2 is present*/ Bool has_4on2; - /* analyser */ + /*! analyser output */ FILE *pes_out; - + /*! when set, only pmt and PAT are parsed*/ + Bool seek_mode; + /*! 192 packet mode with prefix*/ Bool prefix_present; - + /*! MPE direct flag*/ Bool direct_mpe; - + /*! DVB-H demu enabled*/ Bool dvb_h_demux; + /*! sends packet for at PES header start*/ Bool notify_pes_timing; - /*user callback - MUST NOT BE NULL*/ + /*! user callback - MUST NOT BE NULL*/ void (*on_mpe_event)(struct tag_m2ts_demux *ts, u32 evt_type, void *par); - /* Structure to hold all the INT tables if the TS contains IP streams */ + /*! structure to hold all the INT tables if the TS contains IP streams */ struct __gf_dvb_mpe_ip_platform *ip_platform; - + /*! current TS packet number*/ u32 pck_number; - /*remote file handling - created and destroyed by user*/ + /*! remote file handling - created and destroyed by user*/ struct __gf_download_session *dnload; - + /*! channel config path*/ const char *dvb_channels_conf_path; - /*AIT*/ + /*! AIT*/ GF_List* ChannelAppList; - /*Carousel*/ + /*! carousel enabled*/ Bool process_dmscc; + /*! carousel root dir*/ char* dsmcc_root_dir; + /*! DSM-CC objects*/ GF_List* dsmcc_controler; - - Bool abort_parsing; + /*! triggers all table reset*/ Bool table_reset; - //duration estimation - u64 first_pcr_found; - u16 pcr_pid; - u64 nb_pck_at_pcr; - - Bool paused; + /*! raw mode only parses PAT and PMT, and forward each packet as a GF_M2TS_EVT_PCK event, except PAT packets which must be recreated + if set, on_event shall be non-null + */ + Bool split_mode; }; +//! @endcond + +/*! creates a new MPEG-2 TS demultiplexer +\return a new demultiplexer*/ GF_M2TS_Demuxer *gf_m2ts_demux_new(); -void gf_m2ts_demux_del(GF_M2TS_Demuxer *ts); -void gf_m2ts_reset_parsers(GF_M2TS_Demuxer *ts); -void gf_m2ts_reset_parsers_for_program(GF_M2TS_Demuxer *ts, GF_M2TS_Program *prog); -GF_ESD *gf_m2ts_get_esd(GF_M2TS_ES *es); -GF_Err gf_m2ts_set_pes_framing(GF_M2TS_PES *pes, u32 mode); -u32 gf_m2ts_pes_get_framing_mode(GF_M2TS_PES *pes); -void gf_m2ts_es_del(GF_M2TS_ES *es, GF_M2TS_Demuxer *ts); -GF_Err gf_m2ts_process_data(GF_M2TS_Demuxer *ts, char *data, u32 data_size); -u32 gf_dvb_get_freq_from_url(const char *channels_config_path, const char *url); -void gf_m2ts_demux_dmscc_init(GF_M2TS_Demuxer *ts); +/*! destroys a new MPEG-2 TS demultiplexer +\param demux the target MPEG-2 TS demultiplexer +*/ +void gf_m2ts_demux_del(GF_M2TS_Demuxer *demux); + +/*! resets all parsers (PES, sections) of the demultiplexer and trash any pending data in the demux input +\param demux the target MPEG-2 TS demultiplexer +*/ +void gf_m2ts_reset_parsers(GF_M2TS_Demuxer *demux); +/*! resets all parsers (PES, sections) of a given program +\param demux the target MPEG-2 TS demultiplexer +\param program the target MPEG-2 TS program +*/ +void gf_m2ts_reset_parsers_for_program(GF_M2TS_Demuxer *demux, GF_M2TS_Program *program); +/*! sets PES framing mode of a stream +\param pes the target MPEG-2 TS stream +\param mode the desired PES framing mode +\return error if any +*/ +GF_Err gf_m2ts_set_pes_framing(GF_M2TS_PES *pes, GF_M2TSPesFraming mode); -void gf_m2ts_pause_demux(GF_M2TS_Demuxer *ts, Bool do_pause); +/*! processes input buffer. This will resync the input data to a TS packet start if needed +\param demux the target MPEG-2 TS demultiplexer +\param data the data to process +\param data_size size of the date to process +\return error if any +*/ +GF_Err gf_m2ts_process_data(GF_M2TS_Demuxer *demux, u8 *data, u32 data_size); + +/*! initializes DSM-CC object carousel reception +\param demux the target MPEG-2 TS demultiplexer +*/ +void gf_m2ts_demux_dmscc_init(GF_M2TS_Demuxer *demux); -GF_M2TS_SDT *gf_m2ts_get_sdt_info(GF_M2TS_Demuxer *ts, u32 program_id); +/*! gets SDT info for a given program +\param demux the target MPEG-2 TS demultiplexer +\param program_id ID of the target MPEG-2 TS program +\return an SDT description, or NULL if not found +*/ +GF_M2TS_SDT *gf_m2ts_get_sdt_info(GF_M2TS_Demuxer *demux, u32 program_id); -Bool gf_m2ts_crc32_check(char *data, u32 len); +/*! flushes a given stream. This is used to flush internal demultiplexer buffers on end of stream +\param demux the target MPEG-2 demultiplexer +\param pes the target stream to flush +*/ +void gf_m2ts_flush_pes(GF_M2TS_Demuxer *demux, GF_M2TS_PES *pes); -/*aborts parsing of the current data (typically needed when parsing done by a different thread). If force_reset_pes is set, all pending pes data is discarded*/ -void gf_m2ts_abort_parsing(GF_M2TS_Demuxer *ts, Bool force_reset_pes); +/*! flushes all streams in the mux. This is used to flush internal demultiplexer buffers on end of stream +\param demux the target MPEG-2 demultiplexer +*/ +void gf_m2ts_flush_all(GF_M2TS_Demuxer *demux); +/*! MPEG-2 TS packet header*/ typedef struct { + /*! sync byte 0x47*/ u8 sync; + /*! error indicator*/ u8 error; + /*! payload start flag (start of PES or section)*/ u8 payload_start; + /*! priority flag*/ u8 priority; + /*! PID of packet */ u16 pid; + /*! scrambling flag ( 0 not scrambled, 1/2 scrambled, 3 reserved)*/ u8 scrambling_ctrl; + /*! adaptation field type*/ u8 adaptation_field; + /*! continuity counter*/ u8 continuity_counter; } GF_M2TS_Header; +/*! MPEG-2 TS packet adaptation field*/ typedef struct { + /*! discontunity indicator (for timeline splicing)*/ u32 discontinuity_indicator; + /*! random access indicator*/ u32 random_access_indicator; + /*! priority indicator*/ u32 priority_indicator; - + /*! PCR present flag*/ u32 PCR_flag; - u64 PCR_base, PCR_ext; - + /*! PCR base value*/ + u64 PCR_base; + /*! PCR extended value*/ + u64 PCR_ext; + /*! original PCR flag*/ u32 OPCR_flag; - u64 OPCR_base, OPCR_ext; - + /*! OPCR base value*/ + u64 OPCR_base; + /*! OPCR extended value*/ + u64 OPCR_ext; + /*! splicing point flag*/ u32 splicing_point_flag; + /*! transport private data flag*/ u32 transport_private_data_flag; + /*! AF extension flag*/ u32 adaptation_field_extension_flag; /* u32 splice_countdown; @@ -1053,358 +1222,708 @@ typedef struct } GF_M2TS_AdaptationField; +#endif /*GPAC_DISABLE_MPEG2TS*/ + + +#ifndef GPAC_DISABLE_MPEG2TS_MUX + +/*! +\addtogroup esi_grp ES Interface +\ingroup media_grp +\brief Basic stream interface API used by MPEG-2 TS muxer. -void gf_m2ts_print_info(GF_M2TS_Demuxer *ts); +This section documents the ES interface used by the MPEG-2 TS muxer. This interface is used to +describe streams and packets consummed by the TS muxer independently from the rest of GPAC (filter packets) -/*demuxes the TS file using a complete number of 1888 blocks - refresh_type is one of the following: -0 - this is a new file -1 - this is a refresh of a previous file -2 - this is the last refresh of a previous file +@{ */ -GF_Err gf_m2ts_demux_file(GF_M2TS_Demuxer *ts, const char *fileName, u64 start_byterange, u64 end_byterange, u32 refresh_type, Bool signal_end_of_stream); -#endif /*GPAC_DISABLE_MPEG2TS*/ +/*! ESI input control commands*/ +enum +{ + /*! forces a data flush from interface to dest (caller) - used for non-threaded interfaces + corresponding parameter: unused + */ + GF_ESI_INPUT_DATA_FLUSH, + /*! pulls a COMPLETE AU from the stream + corresponding parameter: pointer to a GF_ESIPacket to fill. The input data_len in the packet is used to indicate any padding in bytes + */ + GF_ESI_INPUT_DATA_PULL, + /*! releases the currently pulled AU from the stream - AU cannot be pulled after that, unless seek happens + corresponding parameter: unused + */ + GF_ESI_INPUT_DATA_RELEASE, + /*! destroys any allocated resource by the stream interface*/ + GF_ESI_INPUT_DESTROY, +}; -#ifndef GPAC_DISABLE_MPEG2TS_MUX +/*! ESI output control commands*/ +enum +{ + /*! forces a data flush from interface to dest (caller) - used for non-threaded interfaces + corresponding parameter: GF_ESIPacket + */ + GF_ESI_OUTPUT_DATA_DISPATCH +}; + +/*! data packet flags*/ +enum +{ + /*! packet is an access unit start*/ + GF_ESI_DATA_AU_START = 1, + /*! packet is an access unit end*/ + GF_ESI_DATA_AU_END = 1<<1, + /*! packet has valid CTS*/ + GF_ESI_DATA_HAS_CTS = 1<<2, + /*! packet has valid DTS*/ + GF_ESI_DATA_HAS_DTS = 1<<3, + /*! packet contains a carousel repeated AU*/ + GF_ESI_DATA_REPEAT = 1<<4, +}; + +/*! stream interface for MPEG-2 TS muxer*/ +typedef struct __data_packet_ifce +{ + /*! packet flags*/ + u16 flags; + /*! SAP type*/ + u8 sap_type; + /*! payload*/ + u8 *data; + /*! payload size*/ + u32 data_len; + /*! DTS expressed in media timescale*/ + u64 dts; + /*! CTS/PTS expressed in media timescale*/ + u64 cts; + /*! duration expressed in media timescale*/ + u32 duration; + /*! packet sequence number*/ + u32 pck_sn; + /*! MPEG-4 Access Unit sequence number (use for carousel of BIFS/OD AU)*/ + u32 au_sn; + /*! ISM BSO for for packets using ISMACrypt/OMA/3GPP based crypto*/ + u32 isma_bso; + /*! serialized list of AF descriptors*/ + u8 *mpeg2_af_descriptors; + /*! size of serialized list of AF descriptors*/ + u32 mpeg2_af_descriptors_size; +} GF_ESIPacket; + +/*! video information for stream interface*/ +struct __esi_video_info +{ + /*! video width in pixels*/ + u32 width; + /*! video height in pixels*/ + u32 height; + /*! pixel aspect ratio as (num<<16)|den*/ + u32 par; + /*! video framerate*/ + Double FPS; +}; +/*! audio information for stream interface*/ +struct __esi_audio_info +{ + /*! sample rate*/ + u32 sample_rate; + /*! number of channels*/ + u32 nb_channels; +}; + +/*! ES interface capabilities*/ +enum +{ + /*! data can be pulled from this stream*/ + GF_ESI_AU_PULL_CAP = 1, + /*! no more data to expect from this stream*/ + GF_ESI_STREAM_IS_OVER = 1<<2, + /*! stream is not signaled through MPEG-4 Systems (OD stream) */ + GF_ESI_STREAM_WITHOUT_MPEG4_SYSTEMS = 1<<3, + /*! stream is not signaled through MPEG-4 Systems (OD stream) */ + GF_ESI_AAC_USE_LATM = 1<<4, +}; + +/*! elementary stream information*/ +typedef struct __elementary_stream_ifce +{ + /*! misc caps of the stream*/ + u32 caps; + /*! matches PID for MPEG2, ES_ID for MPEG-4*/ + u32 stream_id; + /*! MPEG-4 ST*/ + u8 stream_type; + /*! codec ID*/ + u32 codecid; + /*! packed 3-char language code (4CC with last byte ' ')*/ + u32 lang; + /*! media timescale*/ + u32 timescale; + /*! duration in ms - 0 if unknown*/ + Double duration; + /*! average bit rate in bit/sec - 0 if unknown*/ + u32 bit_rate; + /*! repeat rate in ms for carrouseling - 0 if no repeat*/ + u32 repeat_rate; + /*! decoder configuration*/ + u8 *decoder_config; + /*! decoder configuration size*/ + u32 decoder_config_size; + + /*! MPEG-4 SL Config if any*/ + GF_SLConfig *sl_config; + + /*! input ES control from caller*/ + GF_Err (*input_ctrl)(struct __elementary_stream_ifce *_self, u32 ctrl_type, void *param); + /*! input user data of interface - usually set by interface owner*/ + void *input_udta; + + /*! output ES control of destination*/ + GF_Err (*output_ctrl)(struct __elementary_stream_ifce *_self, u32 ctrl_type, void *param); + /*! output user data of interface - usually set during interface setup*/ + void *output_udta; + /*! stream dependency ID*/ + u32 depends_on_stream; +} GF_ESInterface; -#include <gpac/esi.h> +/*! @} */ /* MPEG-2 TS Multiplexer */ +/*! adaptation field type*/ enum { + /*! reserved*/ GF_M2TS_ADAPTATION_RESERVED = 0, + /*! no adaptation (payload only)*/ GF_M2TS_ADAPTATION_NONE = 1, + /*! adaptation only (no payload)*/ GF_M2TS_ADAPTATION_ONLY = 2, + /*! adaptation and payload*/ GF_M2TS_ADAPTATION_AND_PAYLOAD = 3, }; - - +/*! MPEG-2 TS muxer program*/ typedef struct __m2ts_mux_program GF_M2TS_Mux_Program; +/*! MPEG-2 TS muxer*/ typedef struct __m2ts_mux GF_M2TS_Mux; +/*! MPEG-2 TS muxer section*/ typedef struct __m2ts_section { + /*! pointer to next section*/ struct __m2ts_section *next; + /*! section data*/ u8 *data; + /*! section size*/ u32 length; } GF_M2TS_Mux_Section; +/*! MPEG-2 TS muxer table*/ typedef struct __m2ts_table { + /*! pointer to next table*/ struct __m2ts_table *next; + /*! table ID (we don't handle extended table ID as part of the section muxer*/ u8 table_id; + /*! version number of the table*/ u8 version_number; + /*! list of sections for the table (one or more)*/ struct __m2ts_section *section; } GF_M2TS_Mux_Table; +/*! MPEG-2 TS muxer time information*/ typedef struct { + /*! seconds*/ u32 sec; + /*! nanoseconds*/ u32 nanosec; } GF_M2TS_Time; +/*! MPEG-2 TS muxer packet*/ typedef struct __m2ts_mux_pck { + /*! pointer to next packet (we queue packets if needed)*/ struct __m2ts_mux_pck *next; - char *data; + /*! payload*/ + u8 *data; + /*! payload size in bytes*/ u32 data_len; - u32 flags; - u64 cts, dts; + /*! flags*/ + u16 flags; + /*! sap type*/ + u8 sap_type; + /*! CTS in 90 khz*/ + u64 cts; + /*! DTS in 90 khz*/ + u64 dts; + /*! duration in 90 khz*/ u32 duration; - - char *mpeg2_af_descriptors; + /*! serialized list of adaptation field descriptors if any*/ + u8 *mpeg2_af_descriptors; + /*! size of serialized list of adaptation field descriptors*/ u32 mpeg2_af_descriptors_size; } GF_M2TS_Packet; - +/*! MPEG-2 TS muxer stream*/ typedef struct __m2ts_mux_stream { + /*! next stream*/ struct __m2ts_mux_stream *next; - + /*! pid*/ u32 pid; + /*! CC of the stream*/ u8 continuity_counter; + /*! parent program*/ struct __m2ts_mux_program *program; - - /*average stream bit-rate in bit/sec*/ + /*! average stream bit-rate in bit/sec*/ u32 bit_rate; - - /*multiplexer time - NOT THE PCR*/ + /*! multiplexer time - NOT THE PCR*/ GF_M2TS_Time time; - + /*! next time for packet*/ GF_M2TS_Time next_time; + /*! allow PCR only packets*/ Bool pcr_only_mode; - - /*table tools*/ + /*! tables for section PIDs*/ GF_M2TS_Mux_Table *tables; - /*total table sizes for bitrate estimation (PMT/PAT/...)*/ + /*! init verision of table*/ + u8 initial_version_number; + /*! total table sizes for bitrate estimation (PMT/PAT/...)*/ u32 total_table_size; - /* used for on-the-fly packetization of sections */ + /*! current table - used for on-the-fly packetization of sections */ GF_M2TS_Mux_Table *current_table; + /*! active section*/ GF_M2TS_Mux_Section *current_section; + /*! current section offset*/ u32 current_section_offset; + /*! carousel rate in ms*/ u32 refresh_rate_ms; + /*! table needs updating*/ Bool table_needs_update; + /*! table needs send*/ Bool table_needs_send; + /*! force one AU per PES*/ Bool force_single_au; + /*! force initial discontinuity*/ + Bool set_initial_disc; + /*! force registration descriptor*/ + Bool force_reg_desc; - /*minimal amount of bytes we are allowed to copy frome next AU in the current PES. If no more than this + /*! minimal amount of bytes we are allowed to copy frome next AU in the current PES. If no more than this is available in PES, don't copy from next*/ u32 min_bytes_copy_from_next; - /*process PES or table update/framing + /*! process PES or table update/framing returns the priority of the stream, 0 meaning not scheduled, 1->N highest priority sent first*/ u32 (*process)(struct __m2ts_mux *muxer, struct __m2ts_mux_stream *stream); - /*PES tools*/ - void *pes_packetizer; - u32 mpeg2_stream_type; + /*! stream type*/ + GF_M2TSStreamType mpeg2_stream_type; + /*! stream ID*/ u32 mpeg2_stream_id; + /*! priority*/ u32 scheduling_priority; - GF_ESIPacket curr_pck; /*current packet being processed - does not belong to the packet fifo*/ + /*! current packet being processed - does not belong to the packet fifo*/ + GF_ESIPacket curr_pck; + /*! offset in packet*/ u32 pck_offset; - u32 next_payload_size, copy_from_next_packets, next_next_payload_size; - u32 pes_data_len, pes_data_remain; + /*! size of next payload, 0 if unknown*/ + u32 next_payload_size; + /*! number of bytes to copy from next packet*/ + u32 copy_from_next_packets; + /*! size of nex next payload, 0 if unknown*/ + u32 next_next_payload_size; + /*! size of packetized packet*/ + u32 pes_data_len; + /*! remaining bytes to send as TS packets*/ + u32 pes_data_remain; + /*! if set forces a new PES packet at next packet*/ Bool force_new; + /*! flag set to indicate packet data shall be freed (rewrite of source packet)*/ Bool discard_data; - - u32 next_pck_flags; - u64 next_pck_cts, next_pck_dts; - + /*! flags of next packet*/ + u16 next_pck_flags; + /*! SAP type of next packet*/ + u8 next_pck_sap; + /*! CTS of next packet*/ + u64 next_pck_cts; + /*! DTS of next packet*/ + u64 next_pck_dts; + /*! overhead of reframing in bytes (ex, ADTS header or LATM rewrite)*/ u32 reframe_overhead; - - Bool start_pes_at_rap, prevent_two_au_start_in_pes; - + /*! if set, forces a PES start at each RAP*/ + Bool start_pes_at_rap; + /*! if set, do not have more than one AU start in a PES packet*/ + Bool prevent_two_au_start_in_pes; + /*! ES interface of stream*/ struct __elementary_stream_ifce *ifce; + /*! timescale scale factor (90khz / es_ifce.timeScale)*/ GF_Fraction ts_scale; - /*packet fifo*/ - GF_M2TS_Packet *pck_first, *pck_last; - /*packet reassembler (PES packets are most of the time full frames)*/ + /*! packet fifo head*/ + GF_M2TS_Packet *pck_first; + /*! packet fifo tail*/ + GF_M2TS_Packet *pck_last; + /*! packet reassembler (PES packets are most of the time full frames)*/ GF_M2TS_Packet *pck_reassembler; + /*! mutex if enabled*/ GF_Mutex *mx; - /*avg bitrate compute*/ + /*! avg bitrate computed*/ u64 last_br_time; - u32 bytes_since_last_time, pes_since_last_time; + /*! number of bytes sent since last bitrate estimation*/ + u32 bytes_since_last_time; + /*! number of PES packets sent since last bitrate estimation*/ + u32 pes_since_last_time; + /*! last DTS sent*/ u64 last_dts; - /*MPEG-4 over MPEG-2*/ + /*! MPEG-4 over MPEG-2 sections table id (BIFS, OD, ...)*/ u8 table_id; + /*! MPEG-4 SyncLayer header of the packet*/ GF_SLHeader sl_header; - - u32 last_aac_time; - /*list of GF_M2TSDescriptor to add to the MPEG-2 stream. By default set to NULL*/ + /*! last time when the AAC config was sent for LATM*/ + u32 latm_last_aac_time; + /*! list of GF_M2TSDescriptor to add to the MPEG-2 stream. By default set to NULL*/ GF_List *loop_descriptors; + + /*! packet SAP type when segmenting the TS*/ + u32 pck_sap_type; + /*! packet SAP time (=PTS) when segmenting the TS*/ + u64 pck_sap_time; } GF_M2TS_Mux_Stream; +/*! MPEG-4 systems signaling mode*/ enum { + /*! no MPEG-4 signaling*/ GF_M2TS_MPEG4_SIGNALING_NONE = 0, + /*! Full MPEG-4 signaling*/ GF_M2TS_MPEG4_SIGNALING_FULL, /*MPEG-4 over MPEG-2 profile where PES media streams may be refered to by the scene without SL-packetization*/ GF_M2TS_MPEG4_SIGNALING_SCENE }; +/*! MPEG-2 TS base descriptor*/ typedef struct __m2ts_base_descriptor { + /*! descriptor tag*/ u8 tag; + /*! descriptor size*/ u8 data_len; - char *data; + /*! descriptor payload*/ + u8 *data; } GF_M2TSDescriptor; +/*! MPEG-2 TS muxer program info */ struct __m2ts_mux_program { + /*! next program*/ struct __m2ts_mux_program *next; - + /*! parent muxer*/ struct __m2ts_mux *mux; + /*! program number*/ u16 number; - /*all streams but PMT*/ + /*! all streams but PMT*/ GF_M2TS_Mux_Stream *streams; - /*PMT*/ + /*! PMT stream*/ GF_M2TS_Mux_Stream *pmt; - /*pointer to PCR stream*/ + /*! pointer to PCR stream*/ GF_M2TS_Mux_Stream *pcr; - - /*TS time at pcr init*/ + /*! TS time at pcr init*/ GF_M2TS_Time ts_time_at_pcr_init; - u64 pcr_init_time, num_pck_at_pcr_init; + /*! pcr init time (rand or fixed by user)*/ + u64 pcr_init_time; + /*! number of TS packet at PCR init*/ + u64 num_pck_at_pcr_init; + /*! last PCR value in 27 mhz*/ u64 last_pcr; + /*! last DTS value in 90 khz*/ u64 last_dts; - //high res clock at last PCR + /*! system clock in microseconds at last PCR*/ u64 sys_clock_at_last_pcr; + /*! number of packets at last PCR*/ u64 nb_pck_last_pcr; + /*! min initial DTS of all streams*/ u64 initial_ts; - Bool initial_ts_set; + /*! indicates that min initial DTS of all streams is set*/ + u32 initial_ts_set; + /*! if set, injects a PCR offset*/ Bool pcr_init_time_set; - u32 pcr_offset; - + /*! PCR offset to inject*/ + u64 pcr_offset; + /*! Forced value of first packet CTS*/ + u64 force_first_pts; + /*! indicates the initial discontinuity flag should be set on first packet of all streams*/ + Bool initial_disc_set; + /*! MPEG-4 IOD for 4on2*/ GF_Descriptor *iod; - /*list of GF_M2TSDescriptor to add to the program descriptor loop. By default set to NULL, if non null will be reset and destroyed upon cleanup*/ + /*! list of GF_M2TSDescriptor to add to the program descriptor loop. By default set to NULL, if non null will be reset and destroyed upon cleanup*/ GF_List *loop_descriptors; + /*! MPEG-4 signaling type*/ u32 mpeg4_signaling; + /*! if set, use MPEG-4 signaling only for BIFS and OD streams*/ Bool mpeg4_signaling_for_scene_only; - - char *name, *provider; -}; - -enum -{ - GF_SEG_BOUNDARY_NONE=0, - GF_SEG_BOUNDARY_START, - GF_SEG_BOUNDARY_FORCE_PAT, - GF_SEG_BOUNDARY_FORCE_PMT, - GF_SEG_BOUNDARY_FORCE_PCR, + /*! program name (for SDT)*/ + char *name; + /*! program provider (for SDT)*/ + char *provider; + /*! CTS offset in 90khz*/ + u32 cts_offset; }; -/*AU packing per pes configuration*/ +/*! AU packing per pes configuration*/ typedef enum { - /*only audio AUs are packed in a single PES, video and systems are not (recommended default)*/ - GF_M2TS_PACK_AUDIO_ONLY, - /*never pack AUs in a single PES*/ + /*! only audio AUs are packed in a single PES, video and systems are not (recommended default)*/ + GF_M2TS_PACK_AUDIO_ONLY=0, + /*! never pack AUs in a single PES*/ GF_M2TS_PACK_NONE, - /*always try to pack AUs in a single PES*/ + /*! always try to pack AUs in a single PES*/ GF_M2TS_PACK_ALL } GF_M2TS_PackMode; +/*! MPEG-2 TS muxer*/ struct __m2ts_mux { + /*! list of programs*/ GF_M2TS_Mux_Program *programs; + /*! PAT stream*/ GF_M2TS_Mux_Stream *pat; + /*! SDT stream*/ GF_M2TS_Mux_Stream *sdt; - + /*! TS ID*/ u16 ts_id; - + /*! signals reconfigure is needed (PAT)*/ Bool needs_reconfig; - /* used to indicate that the input data is pushed to the muxer (i.e. not read from a file) + /*! used to indicate that the input data is pushed to the muxer (i.e. not read from a file) or that the output data is sent on sockets (not written to a file) */ Bool real_time; - /* indicates if the multiplexer shall target a fix bit rate (monitoring timing and produce padding packets) + /*! indicates if the multiplexer shall target a fix bit rate (monitoring timing and produce padding packets) or if the output stream will contain only input data*/ Bool fixed_rate; - /*output bit-rate in bit/sec*/ + /*! output bit-rate in bit/sec*/ u32 bit_rate; - /*init value for PCRs on all streams if 0, random value is used*/ + /*! init value for PCRs on all streams if 0, random value is used*/ u64 init_pcr_value; - + /*! PCR update rate in milliseconds*/ u32 pcr_update_ms; + /*! destination TS packet*/ + char dst_pck[188]; + /*! destination NULL TS packet*/ + char null_pck[188]; - char dst_pck[188], null_pck[188]; - - /*multiplexer time, incremented each time a packet is sent + /*! multiplexer time, incremented each time a packet is sent used to monitor the sending of muxer related data (PAT, ...) */ GF_M2TS_Time time; - /* Time of the muxer when the first call to process is made (first packet sent?) */ + /*! Time of the muxer when the first call to process is made (first packet sent?) */ GF_M2TS_Time init_ts_time; - /* System time high res when the muxer is started */ + /*! System time in microsenconds when the muxer is started */ u64 init_sys_time; - + /*! forces PAT injection at next packet*/ Bool force_pat; - + /*! AU per PES packing mode*/ GF_M2TS_PackMode au_pes_mode; - + /*! enable forced PCR (PCR only packets)*/ Bool enable_forced_pcr; - - Bool eos_found; + /*! last rate estimation time in microseconds*/ u64 last_br_time_us; + /*! number of TS packets over the bitrate estimation period*/ u32 pck_sent_over_br_window; - u64 tot_pck_sent, tot_pad_sent, tot_pes_pad_bytes; - - + /*! number of TS packets sent*/ + u64 tot_pck_sent; + /*! number of TS NULL packets sent*/ + u64 tot_pad_sent; + /*! number of PES bytes sent*/ + u64 tot_pes_pad_bytes; + + /*! average rate in kbps*/ u32 average_birate_kbps; + /*! if set, flushes all streams in a program when a PES RAP is found (eg sent remaining audios and co)*/ Bool flush_pes_at_rap; - /*cf enum above*/ + /*! state for forced injection of PAT/PMT/PCR*/ u32 force_pat_pmt_state; + + /*! static write bitstream object for formatting packets*/ + GF_BitStream *pck_bs; + /*set to TRUE if the packet output is the first packet of a SAP AU (used when dashing)*/ + Bool sap_inserted; + /*! SAP time (used when dashing)*/ + u64 sap_time; + /*! SAP type (used when dashing)*/ + u32 sap_type; + /*! last PTS in 90khz (used when dashing to build sidx)*/ + u64 last_pts; + /*! last PID (used when dashing to build sidx)*/ + u32 last_pid; }; +/*! default refresh rate for PSI data*/ +#define GF_M2TS_PSI_DEFAULT_REFRESH_RATE 200 +/*! creates a new MPEG-2 TS multiplexer +\param mux_rate target mux rate in kbps, can be 0 for VBR +\param pat_refresh_rate PAT refresh rate in ms +\param real_time indicates if real-time production should be used +\return a new MPEG-2 TS multiplexer + */ +GF_M2TS_Mux *gf_m2ts_mux_new(u32 mux_rate, u32 pat_refresh_rate, Bool real_time); +/*! destroys a TS muxer +\param muxer the target MPEG-2 TS multiplexer +*/ +void gf_m2ts_mux_del(GF_M2TS_Mux *muxer); +/*! sets max interval between two PCR. Default/max interval is 100 milliseconds +\param muxer the target MPEG-2 TS multiplexer +\param pcr_update_ms PCR interval in milliseconds +*/ +void gf_m2ts_mux_set_pcr_max_interval(GF_M2TS_Mux *muxer, u32 pcr_update_ms); +/*! adds a new program to the muxer +\param muxer the target MPEG-2 TS multiplexer +\param program_number number of program to add +\param pmt_pid value of program PMT packet identifier +\param pmt_refresh_rate refresh rate in milliseconds of PMT. 0 only sends PMT once +\param pcr_offset initial offset in 90 kHz to add to PCR values (usually to compensate for large frame sending times) +\param mpeg4_signaling type of MPEG-4 signaling used +\param pmt_version initial version of the PMT +\param initial_disc if GF_TRUE, signals packet discontinuity on the first packet of eact stream in the program +\param force_first_pts if not 0, the first PTS written in the program will have the indicated value (in 90khz) +\return a TS multiplexer program +*/ +GF_M2TS_Mux_Program *gf_m2ts_mux_program_add(GF_M2TS_Mux *muxer, u32 program_number, u32 pmt_pid, u32 pmt_refresh_rate, u64 pcr_offset, u32 mpeg4_signaling, u32 pmt_version, Bool initial_disc, u64 force_first_pts); +/*! adds a stream to a program +\param program the target program +\param ifce the stream interface object for packet and properties query +\param pid the packet identifier for the stream +\param is_pcr if GF_TRUE, this stream is the clock reference of the program +\param force_pes_mode if GF_TRUE, forces PES packetization (used for streams carried over PES or sections, such as metadata and MPEG-4) +\param needs_mutex indicates a mutex is required before calling the stream interface object +\return a TS multiplexer program +*/ +GF_M2TS_Mux_Stream *gf_m2ts_program_stream_add(GF_M2TS_Mux_Program *program, GF_ESInterface *ifce, u32 pid, Bool is_pcr, Bool force_pes_mode, Bool needs_mutex); +/*! updates muxer configuration +\param muxer the target MPEG-2 TS multiplexer +\param reset_time if GF_TRUE, resets multiplexer time to 0 +*/ +void gf_m2ts_mux_update_config(GF_M2TS_Mux *muxer, Bool reset_time); +/*! removes stream from program, triggering PMT update as well +\param stream the target stream to remove +*/ +void gf_m2ts_program_stream_remove(GF_M2TS_Mux_Stream *stream); +/*! finds a program by its number +\param muxer the target MPEG-2 TS multiplexer +\param program_number number of program to retrieve +\return a TS multiplexer program or NULL of not found +*/ +GF_M2TS_Mux_Program *gf_m2ts_mux_program_find(GF_M2TS_Mux *muxer, u32 program_number); +/*! gets number of program in a multiplex +\param muxer the target MPEG-2 TS multiplexer +\return number of programs +*/ +u32 gf_m2ts_mux_program_count(GF_M2TS_Mux *muxer); +/*! gets number of stream in a program +\param program the target program +\return number of streams +*/ +u32 gf_m2ts_mux_program_get_stream_count(GF_M2TS_Mux_Program *program); +/*! gets the packet identifier of the PMT of a program +\param program the target program +\return PID of the PMT +*/ +u32 gf_m2ts_mux_program_get_pmt_pid(GF_M2TS_Mux_Program *program); +/*! gets the packet identifier of the PCR stream of a program +\param program the target program +\return PID of the PCR +*/ +u32 gf_m2ts_mux_program_get_pcr_pid(GF_M2TS_Mux_Program *program); -enum +/*! state of multiplexer*/ +typedef enum { + /*! multiplexer is idle, nothing to produce (real-time mux only)*/ GF_M2TS_STATE_IDLE, + /*! multiplexer is sending data*/ GF_M2TS_STATE_DATA, + /*! multiplexer is sending padding (no input data and fixed rate required)*/ GF_M2TS_STATE_PADDING, + /*! multiplexer is done*/ GF_M2TS_STATE_EOS, -}; - -#define GF_M2TS_PSI_DEFAULT_REFRESH_RATE 200 -/*! - * mux_rate en kbps - */ -GF_M2TS_Mux *gf_m2ts_mux_new(u32 mux_rate, u32 pat_refresh_rate, Bool real_time); -void gf_m2ts_mux_del(GF_M2TS_Mux *mux); -//sets max interval between two PCR. Default/max interval is 100 ms -void gf_m2ts_mux_set_pcr_max_interval(GF_M2TS_Mux *muxer, u32 pcr_update_ms); -GF_M2TS_Mux_Program *gf_m2ts_mux_program_add(GF_M2TS_Mux *muxer, u32 program_number, u32 pmt_pid, u32 pmt_refresh_rate, u32 pcr_offset, Bool mpeg4_signaling); -GF_M2TS_Mux_Stream *gf_m2ts_program_stream_add(GF_M2TS_Mux_Program *program, GF_ESInterface *ifce, u32 pid, Bool is_pcr, Bool force_pes_mode); -void gf_m2ts_mux_update_config(GF_M2TS_Mux *mux, Bool reset_time); - -GF_M2TS_Mux_Program *gf_m2ts_mux_program_find(GF_M2TS_Mux *muxer, u32 program_number); +} GF_M2TSMuxState; -const char *gf_m2ts_mux_process(GF_M2TS_Mux *muxer, u32 *status, u32 *usec_till_next); +/*! produces one packet of the multiplex +\param muxer the target MPEG-2 TS multiplexer +\param status set to the current state of the multiplexer +\param usec_till_next the target MPEG-2 TS multiplexer +\return packet produced or NULL if error or idle +*/ +const u8 *gf_m2ts_mux_process(GF_M2TS_Mux *muxer, GF_M2TSMuxState *status, u32 *usec_till_next); +/*! gets the system clock of the multiplexer (time ellapsed since start) +\param muxer the target MPEG-2 TS multiplexer +\return system clock of the multiplexer in milliseconds +*/ u32 gf_m2ts_get_sys_clock(GF_M2TS_Mux *muxer); +/*! gets the multiplexer clock (computed from mux rate and packet sent) +\param muxer the target MPEG-2 TS multiplexer +\return multiplexer clock in milliseconds +*/ u32 gf_m2ts_get_ts_clock(GF_M2TS_Mux *muxer); +/*! gets the multiplexer clock (computed from mux rate and packet sent) +\param muxer the target MPEG-2 TS multiplexer +\return multiplexer clock in 90kHz scale +*/ +u64 gf_m2ts_get_ts_clock_90k(GF_M2TS_Mux *muxer); + +/*! set AU per PES packetization mode +\param muxer the target MPEG-2 TS multiplexer +\param au_pes_mode AU packetization mode to use +\return error if any +*/ GF_Err gf_m2ts_mux_use_single_au_pes_mode(GF_M2TS_Mux *muxer, GF_M2TS_PackMode au_pes_mode); + +/*! sets initial PCR value for all programs in multiplex. If this is not called, each program will use a random initial PCR +\param muxer the target MPEG-2 TS multiplexer +\param init_pcr_value initial PCR value in 90kHz +\return error if any +*/ GF_Err gf_m2ts_mux_set_initial_pcr(GF_M2TS_Mux *muxer, u64 init_pcr_value); -GF_Err gf_m2ts_mux_enable_pcr_only_packets(GF_M2TS_Mux *muxer, Bool enable_forced_pcr); +/*! enables pure PCR packets (no payload). This can be needed for some DVB constraints on PCR refresh rate (less than one frame duration) when next video frame is not yet ready to be muxed. -/*user inteface functions*/ -GF_Err gf_m2ts_program_stream_update_ts_scale(GF_ESInterface *_self, u32 time_scale); +\param muxer the target MPEG-2 TS multiplexer +\param enable_forced_pcr if GF_TRUE, the multiplexer may use PCR-only packets; otherwise, it will wait for a frame to be ready on the PCR stream before sending a PCR +\return error if any +*/ +GF_Err gf_m2ts_mux_enable_pcr_only_packets(GF_M2TS_Mux *muxer, Bool enable_forced_pcr); +/*! sets program name and provider +\param program the target program +\param program_name the program name (UTF-8) +\param mux_provider_name the provider name (UTF-8) +*/ void gf_m2ts_mux_program_set_name(GF_M2TS_Mux_Program *program, const char *program_name, const char *mux_provider_name); -void gf_m2ts_mux_enable_sdt(GF_M2TS_Mux *mux, u32 refresh_rate_ms); +/*! enables sending DVB SDT +\param muxer the target program +\param refresh_rate_ms the refresh rate for the SDT. A value of 0 only sends the SDT once +*/ +void gf_m2ts_mux_enable_sdt(GF_M2TS_Mux *muxer, u32 refresh_rate_ms); #endif /*GPAC_DISABLE_MPEG2TS_MUX*/ -/******************* Demux DVB ****************************/ - - - -#ifdef GPAC_HAS_LINUX_DVB -#include <fcntl.h> -#include <sys/ioctl.h> -#include <sys/stat.h> -#include <linux/dvb/dmx.h> -#include <linux/dvb/frontend.h> - -struct __gf_dvb_tuner { - u32 freq; - u16 vpid; - u16 apid; - fe_spectral_inversion_t specInv; - fe_modulation_t modulation; - fe_bandwidth_t bandwidth; - fe_transmit_mode_t TransmissionMode; - fe_guard_interval_t guardInterval; - fe_code_rate_t HP_CodeRate; - fe_code_rate_t LP_CodeRate; - fe_hierarchy_t hierarchy; - - int ts_fd; -}; - - -#endif //GPAC_HAS_LINUX_DVB - #ifndef GPAC_DISABLE_MPEG2TS -GF_Err gf_m2ts_demuxer_setup(GF_M2TS_Demuxer *ts, const char *url, Bool loop); -GF_Err gf_m2ts_demuxer_play(GF_M2TS_Demuxer *ts); -GF_Err gf_m2ts_demuxer_close(GF_M2TS_Demuxer *ts); - #endif /*GPAC_DISABLE_MPEG2TS*/ -/*quick hack to get M2TS over IP or UDP socket*/ -GF_Err gf_m2ts_get_socket(const char *url, const char *mcast_ifce_or_mobileip, u32 buf_size, GF_Socket **out_socket); - /*! @} */ + +#ifdef __cplusplus +} +#endif + + #endif //_GF_MPEG_TS_H_ diff --git a/include/gpac/network.h b/include/gpac/network.h index e51fea2..77f7cd2 100644 --- a/include/gpac/network.h +++ b/include/gpac/network.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -29,482 +29,634 @@ #ifdef __cplusplus extern "C" { #endif +#include <gpac/setup.h> +#include <gpac/tools.h> /*! - * \file <gpac/network.h> - * \brief Networking. - */ +\file <gpac/network.h> +\brief Networking. +*/ /*! -* \addtogroup net_grp Network -* \ingroup utils_grp -* \brief Networking tools (URL resolution, TCP/UDP sockets) -* -*This section documents the IP network functions of the GPAC framework. -* @{ -*/ +\addtogroup net_grp +\brief Networking tools (URL resolution, TCP/UDP sockets) -#include <gpac/tools.h> +This section documents the IP network functions of the GPAC framework. +\defgroup sock_grp Socket +\ingroup net_grp +\defgroup sockgrp_grp Socket Group +\ingroup net_grp +\defgroup nett_grp Network Time and helpers +\ingroup net_grp +\defgroup url_grp URL tools +\ingroup net_grp + +*/ /*! - *\brief URL local test - * - *Tests whether a URL describes a local file or not - *\param url the url to analyze - *\return 1 if the URL describes a local file, 0 otherwise +\addtogroup url_grp +\brief URL manipulation tools +@{ +*/ + +/*! +\brief URL local test + +Tests whether a URL describes a local file or not +\param url the url to analyze +\return 1 if the URL describes a local file, 0 otherwise */ Bool gf_url_is_local(const char *url); /*! - *\brief gets absolute file path - * - *Gets the absolute file path from a relative path and its parent absolute one. This can only be used with file paths. - *\param pathName the relative path name of a file - *\param parentPath the absolute parent path name - *\return absolute path name of the file, or NULL if bad paths are provided. +\brief gets absolute file path + +Gets the absolute file path from a relative path and its parent absolute one. This can only be used with file paths. +\param pathName the relative path name of a file +\param parentPath the absolute parent path name +\return absolute path name of the file, or NULL if bad paths are provided. \note the returned string must be freed by user */ char *gf_url_get_absolute_path(const char *pathName, const char *parentPath); /*concatenates URL and gets back full URL - returned string must be freed by user*/ /*! - *\brief URL concatenation - * - *Concatenates a relative URL with its parent URL - *\param parentName URL of the parent service - *\param pathName URL of the service - *\return absolute path name of the service, or NULL if bad paths are provided or if the service path is already an absolute one. +\brief URL concatenation + +Concatenates a relative URL with its parent URL +\param parentName URL of the parent service +\param pathName URL of the service +\return absolute path name of the service, or NULL if bad paths are provided or if the service path is already an absolute one. \note the returned string must be freed by user */ char *gf_url_concatenate(const char *parentName, const char *pathName); /*! - *\brief URL encodin - * - *Encodes URL by replacing special characters with their % encodings. - *\param path URL of the service - *\return encoded path name , or NULL if bad paths are provided. +\brief URL concatenation + +Same as \ref gf_url_concatenate but if both paths are relative, resolved url is relative to parent path. +\param parentName URL of the parent service +\param pathName URL of the service +\return absolute path name of the service, or NULL if bad paths are provided or if the service path is already an absolute one. + \note the returned string must be freed by user + */ +char *gf_url_concatenate_parent(const char *parentName, const char *pathName); + +/*! +\brief URL encoding + +Encodes URL by replacing special characters with their % encodings. +\param path URL of the service +\return encoded path name , or NULL if bad paths are provided. \note the returned string must be freed by user */ char *gf_url_percent_encode(const char *path); /*! - *\brief URL to file system - * - *Converts a local URL to a file system value. Removes all white spaces and similar - *\param url url to convert +\brief URL to file system + +Converts a local URL to a file system value. Removes all white spaces and similar +\param url url to convert */ void gf_url_to_fs_path(char *url); +/*! +\brief get first after a filename/path + +Returns a pointer to the first colon at the end of a filename or URL, if any +\param URL path or URL to inspect +\return position of first colon, or NULL +*/ +char* gf_url_colon_suffix(const char* URL); + /*! - *\brief Extract resource name from URL - * - * Extracts the resource name from the URL - *\param url input url - *\return resource name. +\brief Extract resource name from URL + +Extracts the resource name from the URL +\param url input url +\return resource name. */ const char *gf_url_get_resource_name(const char *url); /*! - *\brief Extract resource path from URL - * - *Extracts the reource path from the URL - *\param url input url - *\param res_path buffer for resulting path storage - *\return 1 if path was extracted, 0 if url is a single file name. +\brief Extract resource path from URL + +Extracts the reource path from the URL +\param url input url +\param res_path buffer for resulting path storage +\return 1 if path was extracted, 0 if url is a single file name. */ Bool gf_url_get_resource_path(const char *url, char *res_path); +/*! @} */ + /*! - *\brief Remove last delimenter from URL - * - * Remove last delimenter from URL - *\param sURL input url - *\param res_path buffer for resulting path storage - *\return GF_TRUE if delimiter was extracted, otherwise GF_FALSE. +\addtogroup nett_grp +\brief Network tools (time, byte ordering, ...) +@{ +*/ + + +/*! +\brief gets ipv6 support + +Returns IPV6 support information. +\return 2 if the machine has IPV6 support, 1 if the library was compiled with IPV6 support, 0 otherwise */ -Bool gf_url_remove_last_delimiter(const char *sURL, char *res_path); +u32 gf_net_has_ipv6(); + /*! - *\brief Extract extension from a resource path in URL - * - * Extract a resource path of URL and analyze its extension - *\param sURL input url - *\return The corresponding extension if exists, otherwise NULL. +\brief checks address type + +Checks if an address is an IPV6 or IPV4 one. +\param address address to check +\return true 1 if address is IPV6 one, 0 otherwise */ -const char* gf_url_get_ressource_extension(const char *sURL); +Bool gf_net_is_ipv6(const char *address); + /*! - *\brief Portable version of UNIX gettimeofday() +host to network conversion of integer + +\param val integrer to convert +\return converted integer */ -struct timeval; -s32 gf_gettimeofday(struct timeval *tp, void *tz); +u32 gf_htonl(u32 val); +/*! +network to host conversion of integer +\param val integrer to convert +\return converted integer + */ +u32 gf_ntohl(u32 val); /*! - *\brief gets UTC time - * - *Gets UTC time since midnight Jan 1970 - *\param sec number of seconds - *\param msec number of milliseconds +host to network conversion of short integer +\param val short integrer to convert +\return converted integer + */ +u16 gf_htons(u16 val); +/*! +network to host conversion of short integer +\param val short integrer to convert +\return converted integer + */ +u16 gf_ntohs(u16 val); + +/*! @} */ + +/*! +\addtogroup time_grp +\brief Time manipulation tools +@{ +*/ + +/*! +\brief gets UTC time + +Gets UTC time since midnight Jan 1970 +\param sec number of seconds +\param msec number of milliseconds */ void gf_utc_time_since_1970(u32 *sec, u32 *msec); /*! - * \brief NTP seconds from 1900 to 1970 - * \hideinitializer - * - * Macro giving the number of seconds from from 1900 to 1970 +\brief NTP seconds from 1900 to 1970 +\hideinitializer + +Macro giving the number of seconds from 1900 to 1970 */ #define GF_NTP_SEC_1900_TO_1970 2208988800ul /*! - *\brief gets NTP time - * - *Gets NTP (Network Time Protocol) in seconds and fractional side - \param sec NTP time in seconds - \param frac fractional NTP time expressed in 1 / (1<<32 - 1) seconds units +\brief gets NTP time + +Gets NTP (Network Time Protocol) in seconds and fractional side +\param sec NTP time in seconds +\param frac fractional NTP time expressed in 1 / (1<<32 - 1) seconds units */ void gf_net_get_ntp(u32 *sec, u32 *frac); /*! - *\brief offsets NTP time by a given amount of seconds - * - *Offsets NTP time of the system by a given amount of seconds in the future or the past (default value is 0). - \param sec seconds to add or remove to the system NTP +*\brief gets NTP time in milliseconds + +\return NTP time in milliseconds +*/ +u64 gf_net_get_ntp_ms(); + +/*! +\brief offsets NTP time by a given amount of seconds + +Offsets NTP time of the system by a given amount of seconds in the future or the past (default value is 0). +\param sec seconds to add or remove to the system NTP */ void gf_net_set_ntp_shift(s32 sec); /*! - *\brief gets NTP time - * - *Gets NTP (Network Time Protocol) timestamp (high 32 bit is seconds, low 32 bit is fraction) - \return NTP timestamp +\brief gets NTP time + +Gets NTP (Network Time Protocol) timestamp (high 32 bit is seconds, low 32 bit is fraction) +\return NTP timestamp */ u64 gf_net_get_ntp_ts(); /*! - * - *Gets diff in milliseconds between NTP time and current time - \param ntp NTP timestamp - \return diff in milliseconds with the current time + +Gets diff in milliseconds between NTP time and current time +\param ntp NTP timestamp +\return diff in milliseconds with the current time */ s32 gf_net_get_ntp_diff_ms(u64 ntp); + +/*! + +Gets diff in milliseconds between two NTP times time and current time +\param ntp_a first NTP timestamp +\param ntp_b second NTP timestamp +\return diff ntp_a minus ntp_b in milliseconds + */ +s32 gf_net_ntp_diff_ms(u64 ntp_a, u64 ntp_b); + +/*! @} */ + +/*! +\addtogroup sock_grp +\brief Sockets (TCP, UDP unicast, multicast) +@{ +*/ + /*! - * Socket options - * \hideinitializer +\brief error code description + +Returns text description of given errno code +\param errnoval the error value to test +\return its description + */ +const char *gf_errno_str(int errnoval); + + +/*! +Socket options +\hideinitializer */ enum { /*!Reuses port.*/ GF_SOCK_REUSE_PORT = 1, /*!Forces IPV6 if available.*/ - GF_SOCK_FORCE_IPV6 = 1<<1 + GF_SOCK_FORCE_IPV6 = 1<<1, + /*!Does not perfom the actual bind, only keeps address and port.*/ + GF_SOCK_FAKE_BIND = 1<<2 }; /*! - *\brief abstracted socket object - * - *The abstracted socket object allows you to build client and server applications very simply - *with support for unicast and multicast (no IPv6 yet) +\brief abstracted socket object + +The abstracted socket object allows you to build client and server applications very simply with support for unicast and multicast +Available tools are socket and socket groups and some helper functions for network time */ typedef struct __tag_socket GF_Socket; +/*! +\brief abstracted socket group object + +The abstracted socket group object allows querying multiple sockets in a group +*/ +typedef struct __tag_sock_group GF_SockGroup; + /*!Buffer size to pass for IP address retrieval*/ -#define GF_MAX_IP_NAME_LEN 516 +#define GF_MAX_IP_NAME_LEN 1025 /*!socket is a TCP socket*/ #define GF_SOCK_TYPE_TCP 0x01 /*!socket is a UDP socket*/ #define GF_SOCK_TYPE_UDP 0x02 +#ifdef GPAC_HAS_SOCK_UN +/*!socket is a TCP socket*/ +#define GF_SOCK_TYPE_TCP_UN 0x03 +/*!socket is a TCP socket*/ +#define GF_SOCK_TYPE_UDP_UN 0x04 +#endif + /*! - *\brief socket constructor - * - *Constructs a socket object - *\param SocketType the socket type to create, either UDP or TCP - *\return the socket object or NULL if network initialization failure +\brief socket constructor + +Constructs a socket object +\param SocketType the socket type to create, either UDP or TCP +\return the socket object or NULL if network initialization failure */ GF_Socket *gf_sk_new(u32 SocketType); /*! - *\brief socket destructor - * - *Deletes a socket object - *\param sock the socket object +\brief socket destructor + +Deletes a socket object +\param sock the socket object */ void gf_sk_del(GF_Socket *sock); /*! - *\brief reset internal buffer - * - *Forces the internal socket buffer to be reseted (discarded) - *\param sock the socket object +\brief reset internal buffer + +Forces the internal socket buffer to be reseted (discarded) +\param sock the socket object */ void gf_sk_reset(GF_Socket *sock); /*! - *\brief socket buffer size control - * - *Sets the size of the internal buffer of the socket. The socket MUST be bound or connected before. - *\param sock the socket object - *\param send_buffer if 0, sets the size of the reception buffer, otherwise sets the size of the emission buffer - *\param new_size new size of the buffer in bytes. - *\warning This operation may fail depending on the provider, hardware... +\brief socket buffer size control + +Sets the size of the internal buffer of the socket. The socket MUST be bound or connected before. +\warning This operation may fail depending on the provider, hardware... +\param sock the socket object +\param send_buffer if 0, sets the size of the reception buffer, otherwise sets the size of the emission buffer +\param new_size new size of the buffer in bytes. +\return error if any */ GF_Err gf_sk_set_buffer_size(GF_Socket *sock, Bool send_buffer, u32 new_size); /*! - *\brief blocking mode control - * - *Sets the blocking mode of a socket on or off. A blocking socket will wait for the net operation to be possible - *while a non-blocking one would return an error. By default, sockets are created in blocking mode - *\param sock the socket object - *\param NonBlockingOn set to 1 to use on-blocking sockets, 0 otherwise +\brief blocking mode control + +Sets the blocking mode of a socket on or off. A blocking socket will wait for the net operation to be possible +while a non-blocking one would return an error. By default, sockets are created in blocking mode +\param sock the socket object +\param NonBlockingOn set to 1 to use non-blocking sockets, 0 otherwise +\return error if any */ GF_Err gf_sk_set_block_mode(GF_Socket *sock, Bool NonBlockingOn); /*! - *\brief socket binding - * - *Binds the given socket to the specified port. - *\param local_ip the local interface IP address if desired. If NULL, the default interface will be used. - *\param sock the socket object - *\param port port number to bind this socket to - *\param peer_name the remote server address, if NULL, will use localhost - *\param peer_port remote port number to connect the socket to - *\param options list of option for the bind operation. +\brief socket binding + +Binds the given socket to the specified port. +\param local_ip the local interface IP address if desired. If NULL, the default interface will be used. +\param sock the socket object +\param port port number to bind this socket to +\param peer_name the remote server address, if NULL, will use localhost +\param peer_port remote port number to connect the socket to +\param options list of option for the bind operation. +\return error if any */ GF_Err gf_sk_bind(GF_Socket *sock, const char *local_ip, u16 port, const char *peer_name, u16 peer_port, u32 options); /*! - *\brief connects a socket - * - *Connects a socket to a remote peer on a given port - *\param sock the socket object - *\param peer_name the remote server address (IP or DNS) - *\param port remote port number to connect the socket to - *\param local_ip the local (client) address (IP or DNS) if any, NULL otherwise. +\brief connects a socket + +Connects a socket to a remote peer on a given port +\param sock the socket object +\param peer_name the remote server address (IP or DNS) +\param port remote port number to connect the socket to +\param local_ip the local (client) address (IP or DNS) if any, NULL otherwise. +\return error if any */ GF_Err gf_sk_connect(GF_Socket *sock, const char *peer_name, u16 port, const char *local_ip); /*! - *\brief data emission - * - *Sends a buffer on the socket. The socket must be in a bound or connected mode - *\param sock the socket object - *\param buffer the data buffer to send - *\param length the data length to send +\brief data emission + +Sends a buffer on the socket. The socket must be in a bound or connected mode +\param sock the socket object +\param buffer the data buffer to send +\param length the data length to send +\return error if any */ -GF_Err gf_sk_send(GF_Socket *sock, const char *buffer, u32 length); +GF_Err gf_sk_send(GF_Socket *sock, const u8 *buffer, u32 length); /*! - *\brief data reception - * - *Fetches data on a socket. The socket must be in a bound or connected state - *\param sock the socket object - *\param buffer the reception buffer where data is written - *\param length the allocated size of the reception buffer - *\param start_from the offset in the reception buffer where to start writing - *\param read the actual number of bytes received +\brief data reception + +Fetches data on a socket. The socket must be in a bound or connected state +\param sock the socket object +\param buffer the reception buffer where data is written. Passing NULL will only probe for data in socket (eg only do select() ). +\param length the allocated size of the reception buffer +\param read the actual number of bytes received +\return error if any, GF_IP_NETWORK_EMPTY if nothing to read */ -GF_Err gf_sk_receive(GF_Socket *sock, char *buffer, u32 length, u32 start_from, u32 *read); +GF_Err gf_sk_receive(GF_Socket *sock, u8 *buffer, u32 length, u32 *read); + /*! - *\brief socket listening - * - *Sets the socket in a listening state. This socket must have been bound to a port before - *\param sock the socket object - *\param max_conn the maximum number of simultaneous connection this socket will accept +\brief socket listening + +Sets the socket in a listening state. This socket must have been bound to a port before +\param sock the socket object +\param max_conn the maximum number of simultaneous connection this socket will accept +\return error if any */ GF_Err gf_sk_listen(GF_Socket *sock, u32 max_conn); /*! - *\brief socket accept - * - *Accepts an incoming connection on a listening socket - *\param sock the socket object - *\param new_conn the resulting connection socket object +\brief socket accept + +Accepts an incoming connection on a listening socket +\param sock the socket object +\param new_conn the resulting connection socket object +\return error if any, GF_IP_NETWORK_EMPTY if nothing to read */ GF_Err gf_sk_accept(GF_Socket *sock, GF_Socket **new_conn); /*! - *\brief server socket mode - * - *Disable the Nable algo (e.g. set TCP_NODELAY) and set the KEEPALIVE on - *\param sock the socket object - *\param server_on sets server mode on or off +\brief server socket mode + +Disable the Nable algo (e.g. set TCP_NODELAY) and set the KEEPALIVE on +\param sock the socket object +\param server_on sets server mode on or off +\return error if any */ GF_Err gf_sk_server_mode(GF_Socket *sock, Bool server_on); /*! - *\brief get local host name - * - *Retrieves local host name. - *\param buffer destination buffer for name. Buffer must be GF_MAX_IP_NAME_LEN long +\brief get local host name + +Retrieves local host name. +\param buffer destination buffer for name. Buffer must be GF_MAX_IP_NAME_LEN long +\return error if any */ GF_Err gf_sk_get_host_name(char *buffer); /*! - *\brief get local IP - * - *Gets local IP address of a connected socket, typically used for server after an ACCEPT - *\param sock the socket object - *\param buffer destination buffer for IP address. Buffer must be GF_MAX_IP_NAME_LEN long +\brief get local IP + +Gets local IP address of a connected socket, typically used for server after an ACCEPT +\param sock the socket object +\param buffer destination buffer for IP address. Buffer must be GF_MAX_IP_NAME_LEN long +\return error if any */ GF_Err gf_sk_get_local_ip(GF_Socket *sock, char *buffer); /*! - *\brief get local info - * - *Gets local socket info of a socket - *\param sock the socket object - *\param port local port number of the socket - *\param sock_type socket type (UDP or TCP) +\brief get local info + +Gets local socket info of a socket +\param sock the socket object +\param port local port number of the socket +\param sock_type socket type (UDP or TCP) +\return error if any */ GF_Err gf_sk_get_local_info(GF_Socket *sock, u16 *port, u32 *sock_type); /*! - *\brief get remote address - * - *Gets the remote address of a peer. The socket MUST be connected. - *\param sock the socket object - *\param buffer destination buffer for IP address. Buffer must be GF_MAX_IP_NAME_LEN long +\brief get remote address + +Gets the remote address of a peer. The socket MUST be connected. +\param sock the socket object +\param buffer destination buffer for IP address. Buffer must be GF_MAX_IP_NAME_LEN long +\return error if any */ GF_Err gf_sk_get_remote_address(GF_Socket *sock, char *buffer); /*! - *\brief set remote address - * - *Sets the remote address of a socket. This is used by connectionless sockets using SendTo and ReceiveFrom - *\param sock the socket object - *\param address the remote peer address - *\param port the remote peer port +\brief set remote address + +Sets the remote address of a socket. This is used by connectionless sockets using SendTo and ReceiveFrom +\param sock the socket object +\param address the remote peer address +\param port the remote peer port +\return error if any */ GF_Err gf_sk_set_remote(GF_Socket *sock, char *address, u16 port); /*! - *\brief multicast setup - * - *Performs multicast setup (BIND and JOIN) for the socket object - *\param sock the socket object - *\param multi_ip_add the multicast IP address - *\param multi_port the multicast port number - *\param TTL the multicast TTL (Time-To-Live) - *\param no_bind if sets, only join the multicast - *\param local_interface_ip the local interface IP address if desired. If NULL, the default interface will be used. +\brief multicast setup + +Performs multicast setup (BIND and JOIN) for the socket object +\param sock the socket object +\param multi_ip_add the multicast IP address +\param multi_port the multicast port number +\param TTL the multicast TTL (Time-To-Live) +\param no_bind if sets, only join the multicast +\param local_interface_ip the local interface IP address if desired. If NULL, the default interface will be used. +\return error if any */ GF_Err gf_sk_setup_multicast(GF_Socket *sock, const char *multi_ip_add, u16 multi_port, u32 TTL, Bool no_bind, char *local_interface_ip); /*! - *brief multicast address test - * - *tests whether an IP address is a multicast one or not - *\param multi_ip_add the multicast IP address to test - *\return 1 if the address is a multicast one, 0 otherwise + \brief multicast address test + +Tests whether an IP address is a multicast one or not +\param multi_ip_add the multicast IP address to test +\return 1 if the address is a multicast one, 0 otherwise */ u32 gf_sk_is_multicast_address(const char *multi_ip_add); /*! - *\brief send data with wait delay - * - *Sends data with a max wait delay. This is used for http / ftp sockets mainly. The socket must be connected. - *\param sock the socket object - *\param buffer the data buffer to send - *\param length the data length to send - *\param delay_sec the maximum delay in second to wait before aborting - *\return If the operation timed out, the function will return a GF_IP_SOCK_WOULD_BLOCK error. - */ -GF_Err gf_sk_send_wait(GF_Socket *sock, const char *buffer, u32 length, u32 delay_sec); +\brief send data with wait delay + +Sends data with a max wait delay. This is used for http / ftp sockets mainly. The socket must be connected. +\param sock the socket object +\param buffer the data buffer to send +\param length the data length to send +\param delay_sec the maximum delay in second to wait before aborting +\return If the operation timed out, the function will return a GF_IP_SOCK_WOULD_BLOCK error. + */ +GF_Err gf_sk_send_wait(GF_Socket *sock, const u8 *buffer, u32 length, u32 delay_sec); /* receive data with a max wait delay of Second - used for http / ftp sockets mainly*/ /*! - *\brief receive data with wait delay - * - *Fetches data with a max wait delay. This is used for http / ftp sockets mainly. The socket must be connected. - *\param sock the socket object - *\param buffer the reception buffer where data is written - *\param length the allocated size of the reception buffer - *\param start_from the offset in the reception buffer where to start writing - *\param read the actual number of bytes received - *\param delay_sec the maximum delay in second to wait before aborting - *\return If the operation timed out, the function will return a GF_IP_SOCK_WOULD_BLOCK error. +\brief receive data with wait delay + +Fetches data with a max wait delay. This is used for http / ftp sockets mainly. The socket must be connected. +\param sock the socket object +\param buffer the reception buffer where data is written +\param length the allocated size of the reception buffer +\param read the actual number of bytes received +\param delay_sec the maximum delay in second to wait before aborting +\return If the operation timed out, the function will return a GF_IP_SOCK_WOULD_BLOCK error. */ -GF_Err gf_sk_receive_wait(GF_Socket *sock, char *buffer, u32 length, u32 start_from, u32 *read, u32 delay_sec); +GF_Err gf_sk_receive_wait(GF_Socket *sock, u8 *buffer, u32 length, u32 *read, u32 delay_sec); /*! - *\brief gets socket handle - * - *Gets the socket low-level handle as used by OpenSSL. - *\param sock the socket object - *\return the socket handle +\brief gets socket handle + +Gets the socket low-level handle as used by OpenSSL. +\param sock the socket object +\return the socket handle */ s32 gf_sk_get_handle(GF_Socket *sock); - /*! - *\brief gets ipv6 support - * - *Returns IPV6 support information. - *\return 2 if the machine has IPV6 support, 1 if the library was compiled with IPV6 support, 0 otherwise +Sets the socket wait time in microseconds. Default wait time is 500 microseconds. Any value >= 1000000 will reset to default. +\param sock the socket object +\param usec_wait wait time in microseconds */ -u32 gf_net_has_ipv6(); +void gf_sk_set_usec_wait(GF_Socket *sock, u32 usec_wait); +/*! +Fetches data on a socket without performing any select (wait), to be used with socket group on sockets that are set in the selected socket group +\param sock the socket object +\param buffer the reception buffer where data is written +\param length the allocated size of the reception buffer +\param read the actual number of bytes received +\return error if any, GF_IP_NETWORK_EMPTY if nothing to read + */ +GF_Err gf_sk_receive_no_select(GF_Socket *sock, u8 *buffer, u32 length, u32 *read); /*! - *\brief checks address type - * - *Checks if an address is an IPV6 or IPV4 one. - *\param address Adress to check - *\return true 1 if address is IPV6 one, 0 otherwise +Checks if connection has been closed by remote peer +\param sock the socket object +\return error if any (GF_IP_CONNECTION_CLOSED if connection is closed) */ -Bool gf_net_is_ipv6(const char *address); +GF_Err gf_sk_probe(GF_Socket *sock); +/*! @} */ /*! - *host to network conversion of integer - * - *\param val integrer to convert - *\return converted integer +\addtogroup sockgrp_grp +\brief Socket IO polling +@{ +*/ + +/*! +Creates a new socket group +\return socket group object */ -u32 gf_htonl(u32 val); +GF_SockGroup *gf_sk_group_new(); /*! - *network to host conversion of integer - * - *\param val integrer to convert - *\return converted integer +Deletes a socket group +\param sg socket group object */ -u32 gf_ntohl(u32 val); +void gf_sk_group_del(GF_SockGroup *sg); /*! - *host to network conversion of short integer - * - *\param val short integrer to convert - *\return converted integer +Registers a socket to a socket group +\param sg socket group object +\param sk socket object to register */ -u16 gf_htons(u16 val); +void gf_sk_group_register(GF_SockGroup *sg, GF_Socket *sk); /*! - *network to host conversion of short integer - * - *\param val short integrer to convert - *\return converted integer +Unregisters a socket from a socket group +\param sg socket group object +\param sk socket object to unregister */ -u16 gf_tohs(u16 val); +void gf_sk_group_unregister(GF_SockGroup *sg, GF_Socket *sk); + +/*! socket selection mode*/ +typedef enum +{ + /*! select for both read and write operations */ + GF_SK_SELECT_BOTH=0, + /*! select for both read operations */ + GF_SK_SELECT_READ, + /*! select for both write operations */ + GF_SK_SELECT_WRITE, +} GF_SockSelectMode; /*! - * \brief MobileIP Callback - * - * The gf_net_mobileip_ctrl_cbk type is the type for the callback of the \ref gf_net_mobileip_set_callback function. By default no mobileip is used - * \param cbck Opaque user data. - * \param start boolean indicating wether the MobileIP subsystem should be started or stopped. - * \return Error code if needed. - * +Performs a select (wait) on the socket group +\param sg socket group object +\param wait_usec microseconds to wait (can be larger than one second) +\param mode the operation mode desired +\return error if any */ -typedef GF_Err (*gf_net_mobileip_ctrl_cbk)(Bool start); +GF_Err gf_sk_group_select(GF_SockGroup *sg, u32 wait_usec, GF_SockSelectMode mode); /*! - *\brief Assigns MobileIP callback - * - *Assigns the MobileIP control callback. - *\param _mobip_cbk MobileIP control callback - *\param MobileIP MobileIP address +Checks if given socket is selected and can be read. This shall be called after gf_sk_group_select +\param sg socket group object +\param sk socket object to check +\param mode the operation mode desired +\return GF_TRUE if socket is ready to read, 0 otherwise */ -void gf_net_mobileip_set_callback(gf_net_mobileip_ctrl_cbk _mobip_cbk, const char *MobileIP); +Bool gf_sk_group_sock_is_set(GF_SockGroup *sg, GF_Socket *sk, GF_SockSelectMode mode); /*! @} */ + #ifdef __cplusplus } #endif diff --git a/include/gpac/nodes_mpeg4.h b/include/gpac/nodes_mpeg4.h index 14a9b37..f51260d 100644 --- a/include/gpac/nodes_mpeg4.h +++ b/include/gpac/nodes_mpeg4.h @@ -3302,7 +3302,8 @@ enum { NDT_SFSBSiteNode, NDT_SFBaseMeshNode, NDT_SFSubdivSurfaceSectorNode, - NDT_SFMusicScoreNode + NDT_SFMusicScoreNode, + NDT_LAST }; /*All BIFS versions handled*/ diff --git a/include/gpac/nodes_svg.h b/include/gpac/nodes_svg.h index f2769fd..d2cfbb9 100644 --- a/include/gpac/nodes_svg.h +++ b/include/gpac/nodes_svg.h @@ -116,202 +116,6 @@ enum { TAG_SVG_UndefinedElement }; -/* Definition of SVG attribute internal tags - 200 defined */ -/* TAG names are made of "TAG_SVG_ATT_" + SVG attribute name (with - replaced by _) */ -enum { - TAG_SVG_ATT_id = TAG_SVG_ATT_RANGE_FIRST, - TAG_SVG_ATT__class, - - TAG_SVG_ATT_requiredFeatures, - TAG_SVG_ATT_requiredExtensions, - TAG_SVG_ATT_requiredFormats, - TAG_SVG_ATT_requiredFonts, - TAG_SVG_ATT_systemLanguage, - TAG_SVG_ATT_display, - TAG_SVG_ATT_visibility, - TAG_SVG_ATT_image_rendering, - TAG_SVG_ATT_pointer_events, - TAG_SVG_ATT_shape_rendering, - TAG_SVG_ATT_text_rendering, - TAG_SVG_ATT_audio_level, - TAG_SVG_ATT_viewport_fill, - TAG_SVG_ATT_viewport_fill_opacity, - TAG_SVG_ATT_overflow, - TAG_SVG_ATT_fill_opacity, - TAG_SVG_ATT_stroke_opacity, - TAG_SVG_ATT_fill, - TAG_SVG_ATT_fill_rule, - TAG_SVG_ATT_filter, - TAG_SVG_ATT_stroke, - TAG_SVG_ATT_stroke_dasharray, - TAG_SVG_ATT_stroke_dashoffset, - TAG_SVG_ATT_stroke_linecap, - TAG_SVG_ATT_stroke_linejoin, - TAG_SVG_ATT_stroke_miterlimit, - TAG_SVG_ATT_stroke_width, - TAG_SVG_ATT_color, - TAG_SVG_ATT_color_rendering, - TAG_SVG_ATT_vector_effect, - TAG_SVG_ATT_solid_color, - TAG_SVG_ATT_solid_opacity, - TAG_SVG_ATT_display_align, - TAG_SVG_ATT_line_increment, - TAG_SVG_ATT_stop_color, - TAG_SVG_ATT_stop_opacity, - TAG_SVG_ATT_font_family, - TAG_SVG_ATT_font_size, - TAG_SVG_ATT_font_style, - TAG_SVG_ATT_font_variant, - TAG_SVG_ATT_font_weight, - TAG_SVG_ATT_text_anchor, - TAG_SVG_ATT_text_align, - TAG_SVG_ATT_text_decoration, - TAG_SVG_ATT_focusHighlight, - TAG_SVG_ATT_externalResourcesRequired, - TAG_SVG_ATT_focusable, - TAG_SVG_ATT_nav_next, - TAG_SVG_ATT_nav_prev, - TAG_SVG_ATT_nav_up, - TAG_SVG_ATT_nav_up_right, - TAG_SVG_ATT_nav_right, - TAG_SVG_ATT_nav_down_right, - TAG_SVG_ATT_nav_down, - TAG_SVG_ATT_nav_down_left, - TAG_SVG_ATT_nav_left, - TAG_SVG_ATT_nav_up_left, - TAG_SVG_ATT_transform, - TAG_SVG_ATT_target, - TAG_SVG_ATT_attributeName, - TAG_SVG_ATT_attributeType, - TAG_SVG_ATT_begin, - TAG_SVG_ATT_dur, - TAG_SVG_ATT_end, - TAG_SVG_ATT_repeatCount, - TAG_SVG_ATT_repeatDur, - TAG_SVG_ATT_restart, - TAG_SVG_ATT_smil_fill, - TAG_SVG_ATT_min, - TAG_SVG_ATT_max, - TAG_SVG_ATT_to, - TAG_SVG_ATT_calcMode, - TAG_SVG_ATT_values, - TAG_SVG_ATT_keyTimes, - TAG_SVG_ATT_keySplines, - TAG_SVG_ATT_from, - TAG_SVG_ATT_by, - TAG_SVG_ATT_additive, - TAG_SVG_ATT_accumulate, - TAG_SVG_ATT_path, - TAG_SVG_ATT_keyPoints, - TAG_SVG_ATT_rotate, - TAG_SVG_ATT_origin, - TAG_SVG_ATT_transform_type, - TAG_SVG_ATT_clipBegin, - TAG_SVG_ATT_clipEnd, - TAG_SVG_ATT_syncBehavior, - TAG_SVG_ATT_syncTolerance, - TAG_SVG_ATT_syncMaster, - TAG_SVG_ATT_syncReference, - TAG_SVG_ATT_x, - TAG_SVG_ATT_y, - TAG_SVG_ATT_width, - TAG_SVG_ATT_height, - TAG_SVG_ATT_preserveAspectRatio, - TAG_SVG_ATT_initialVisibility, - TAG_SVG_ATT_type, - TAG_SVG_ATT_cx, - TAG_SVG_ATT_cy, - TAG_SVG_ATT_r, - TAG_SVG_ATT_cursorManager_x, - TAG_SVG_ATT_cursorManager_y, - TAG_SVG_ATT_rx, - TAG_SVG_ATT_ry, - TAG_SVG_ATT_horiz_adv_x, - TAG_SVG_ATT_horiz_origin_x, - TAG_SVG_ATT_font_stretch, - TAG_SVG_ATT_unicode_range, - TAG_SVG_ATT_panose_1, - TAG_SVG_ATT_widths, - TAG_SVG_ATT_bbox, - TAG_SVG_ATT_units_per_em, - TAG_SVG_ATT_stemv, - TAG_SVG_ATT_stemh, - TAG_SVG_ATT_slope, - TAG_SVG_ATT_cap_height, - TAG_SVG_ATT_x_height, - TAG_SVG_ATT_accent_height, - TAG_SVG_ATT_ascent, - TAG_SVG_ATT_descent, - TAG_SVG_ATT_ideographic, - TAG_SVG_ATT_alphabetic, - TAG_SVG_ATT_mathematical, - TAG_SVG_ATT_hanging, - TAG_SVG_ATT_underline_position, - TAG_SVG_ATT_underline_thickness, - TAG_SVG_ATT_strikethrough_position, - TAG_SVG_ATT_strikethrough_thickness, - TAG_SVG_ATT_overline_position, - TAG_SVG_ATT_overline_thickness, - TAG_SVG_ATT_d, - TAG_SVG_ATT_unicode, - TAG_SVG_ATT_glyph_name, - TAG_SVG_ATT_arabic_form, - TAG_SVG_ATT_lang, - TAG_SVG_ATT_u1, - TAG_SVG_ATT_g1, - TAG_SVG_ATT_u2, - TAG_SVG_ATT_g2, - TAG_SVG_ATT_k, - TAG_SVG_ATT_opacity, - TAG_SVG_ATT_x1, - TAG_SVG_ATT_y1, - TAG_SVG_ATT_x2, - TAG_SVG_ATT_y2, - TAG_SVG_ATT_filterUnits, - TAG_SVG_ATT_gradientUnits, - TAG_SVG_ATT_spreadMethod, - TAG_SVG_ATT_gradientTransform, - TAG_SVG_ATT_pathLength, - TAG_SVG_ATT_points, - TAG_SVG_ATT_mediaSize, - TAG_SVG_ATT_mediaTime, - TAG_SVG_ATT_mediaCharacterEncoding, - TAG_SVG_ATT_mediaContentEncodings, - TAG_SVG_ATT_bandwidth, - TAG_SVG_ATT_fx, - TAG_SVG_ATT_fy, - TAG_SVG_ATT_size, - TAG_SVG_ATT_choice, - TAG_SVG_ATT_delta, - TAG_SVG_ATT_offset, - TAG_SVG_ATT_syncBehaviorDefault, - TAG_SVG_ATT_syncToleranceDefault, - TAG_SVG_ATT_viewBox, - TAG_SVG_ATT_zoomAndPan, - TAG_SVG_ATT_version, - TAG_SVG_ATT_baseProfile, - TAG_SVG_ATT_contentScriptType, - TAG_SVG_ATT_snapshotTime, - TAG_SVG_ATT_timelineBegin, - TAG_SVG_ATT_playbackOrder, - TAG_SVG_ATT_editable, - TAG_SVG_ATT_text_x, - TAG_SVG_ATT_text_y, - TAG_SVG_ATT_text_rotate, - TAG_SVG_ATT_transformBehavior, - TAG_SVG_ATT_overlay, - TAG_SVG_ATT_fullscreen, - TAG_SVG_ATT_motionTransform, - - TAG_SVG_ATT_filter_transfer_type, - TAG_SVG_ATT_filter_table_values, - TAG_SVG_ATT_filter_intercept, - TAG_SVG_ATT_filter_amplitude, - TAG_SVG_ATT_filter_exponent - -}; - - struct _all_atts { XML_Space *xml_space; XMLRI *xml_base; diff --git a/include/gpac/nodes_xbl.h b/include/gpac/nodes_xbl.h deleted file mode 100644 index 4e0bdff..0000000 --- a/include/gpac/nodes_xbl.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Cyril Concolato - * Copyright (c)2004-2012 Telecom ParisTech - All rights reserved - * - * This file is part of GPAC / XBL Elements - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#ifndef _GF_XBL_NODES_H -#define _GF_XBL_NODES_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include <gpac/internal/scenegraph_dev.h> -#include <gpac/svg_types.h> - -#define XBL_String_datatype SVG_String_datatype - -struct _all_atts { - DOM_String *id; - DOM_String *extends; - DOM_String *display; - DOM_String *inheritstyle; - DOM_String *includes; - DOM_String *name; - DOM_String *implements; - DOM_String *type; - DOM_String *readonly; - DOM_String *onget; - DOM_String *onset; - DOM_String *event; - DOM_String *action; - DOM_String *phase; - DOM_String *button; - DOM_String *modifiers; - DOM_String *keycode; - DOM_String *key; - DOM_String *charcode; - DOM_String *clickcount; - DOM_String *command; - DOM_String *preventdefault; - DOM_String *src; -}; -#ifdef __cplusplus -} -#endif - - - -#endif /*_GF_SVG_NODES_H*/ - diff --git a/include/gpac/options.h b/include/gpac/options.h index d25585c..fd9259f 100644 --- a/include/gpac/options.h +++ b/include/gpac/options.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / Stream Management sub-project @@ -34,295 +34,295 @@ extern "C" { /*! - * \file <gpac/options.h> - * \brief Compositor and terminal options. - */ +\file <gpac/options.h> +\brief Compositor and terminal options. +*/ /*! - *\addtogroup compose_grp Compositor - *\ingroup playback_grp - *\brief GPAC A/V/2D/3D compositor/rendering. - * - *This section documents the compositor of GPAC? in charge of assembling audio, images, video, text, 2D and 3D graphics with in a timed way - * @{ - */ - -/*AspectRatio Type */ -enum -{ - GF_ASPECT_RATIO_KEEP = 0, /*keep AR*/ - GF_ASPECT_RATIO_16_9, /*keep 16/9*/ - GF_ASPECT_RATIO_4_3, /*keep 4/3*/ - GF_ASPECT_RATIO_FILL_SCREEN /*none (all rendering area used)*/ -}; +\addtogroup compose_grp Compositor +\ingroup playback_grp +\brief GPAC A/V/2D/3D compositor/rendering. -/*AntiAlias settings*/ -enum -{ - GF_ANTIALIAS_NONE = 0, /*no antialiasing*/ - GF_ANTIALIAS_TEXT, /*only text has antialiasing*/ - GF_ANTIALIAS_FULL /*full antialiasing*/ -}; +This section documents the compositor of GPAC? in charge of assembling audio, images, video, text, 2D and 3D graphics with in a timed way -/*GF_StreamingCache settings*/ +@{ + */ + +/*! AspectRatio Type */ enum { - GF_MEDIA_CACHE_ENABLED = 0, /*cache on (with SET option, turns it on if possible)*/ - GF_MEDIA_CACHE_DISABLED, /*cache off (with SET option saves current cache if any)*/ - GF_MEDIA_CACHE_DISCARD, /*only used for SET option: turns cache off and discards current cache if any*/ - GF_MEDIA_CACHE_RUNNING, /*only used in GET option: caching is enabled and currently running*/ + /*! keep AR*/ + GF_ASPECT_RATIO_KEEP = 0, + /*! keep 16/9*/ + GF_ASPECT_RATIO_16_9, + /*! keep 4/3*/ + GF_ASPECT_RATIO_4_3, + /*! none (all rendering area used)*/ + GF_ASPECT_RATIO_FILL_SCREEN }; -/*PlayState settings*/ +/*! AntiAlias settings*/ enum { - GF_STATE_PLAYING = 0, /*terminal is playing*/ - GF_STATE_PAUSED, /*terminal is paused*/ - GF_STATE_STEP_PAUSE, /*get/set only: terminal will pause after next frame (simulation tick). On get, indicates that rendering step hasn't performed yet*/ - - GF_STATE_PLAY_LIVE = 10 //set only: indicates resume shall restart from live point if any rather than pause point + /*! no antialiasing*/ + GF_ANTIALIAS_NONE = 0, + /*! only text has antialiasing*/ + GF_ANTIALIAS_TEXT, + /*! full antialiasing*/ + GF_ANTIALIAS_FULL }; -/*refresh mode*/ +/*! PlayState settings*/ enum { - GF_REFRESH_NORMAL = 0, /*posts normal redraw message */ - GF_REFRESH_FULL, /*posts full redraw message, including reset of hardware resources*/ + /*! terminal is playing (get only)*/ + GF_STATE_PLAYING = 0, + /*! terminal is paused (get only)*/ + GF_STATE_PAUSED, + /*! On set, terminal will pause after next frame (simulation tick). On get, indicates that rendering step hasn't performed yet*/ + GF_STATE_STEP_PAUSE, + /*! indicates resume shall restart from live point if any rather than pause point (set only)*/ + GF_STATE_PLAY_LIVE = 10 }; -/*interaction level settings*/ +/*! interaction level settings +\note GF_INTERACT_NORMAL and GF_INTERACT_NAVIGATION filter events. If set, any event processed by these 2 modules won't be forwarded to the user +*/ enum { - /*regular interactions enabled (touch sensors)*/ + /*! regular interactions enabled (touch sensors)*/ GF_INTERACT_NORMAL = 1, - /*InputSensor interactions enabled (mouse and keyboard)*/ + /*! InputSensor interactions enabled (mouse and keyboard)*/ GF_INTERACT_INPUT_SENSOR = 2, - /*all navigation interactions enabled (mouse and keyboard)*/ + /*! all navigation interactions enabled (mouse and keyboard)*/ GF_INTERACT_NAVIGATION = 4, - - /*NOTE: GF_INTERACT_NORMAL and GF_INTERACT_NAVIGATION filter events. If set, any event processed by - these 2 modules won't be forwarded to the user*/ }; -/*BoundingVolume settings*/ +/*! BoundingVolume settings*/ enum { - GF_BOUNDS_NONE = 0, /*doesn't draw bounding volume*/ - GF_BOUNDS_BOX, /*draw object bounding box / rect*/ - GF_BOUNDS_AABB /*draw object AABB tree (3D only) */ + /*! doesn't draw bounding volume*/ + GF_BOUNDS_NONE = 0, + /*! draw object bounding box / rect*/ + GF_BOUNDS_BOX, + /*! draw object AABB tree (3D only) */ + GF_BOUNDS_AABB }; -/*Wireframe settings*/ +/*! Wireframe settings*/ enum { - GF_WIREFRAME_NONE = 0, /*draw solid volumes*/ - GF_WIREFRAME_ONLY, /*draw only wireframe*/ - GF_WIREFRAME_SOLID /*draw wireframe on solid object*/ + /*! draw solid volumes*/ + GF_WIREFRAME_NONE = 0, + /*! draw only wireframe*/ + GF_WIREFRAME_ONLY, + /*! draw wireframe on solid object*/ + GF_WIREFRAME_SOLID }; -/*navigation type*/ +/*! navigation type*/ enum { - /*navigation is disabled by content and cannot be forced by user*/ + /*! navigation is disabled by content and cannot be forced by user*/ GF_NAVIGATE_TYPE_NONE, - /*2D navigation modes only can be used*/ + /*! 2D navigation modes only can be used*/ GF_NAVIGATE_TYPE_2D, - /*3D navigation modes only can be used*/ + /*! 3D navigation modes only can be used*/ GF_NAVIGATE_TYPE_3D }; -/*navigation modes - non-VRML ones are simply blaxxun contact ones*/ +/*! navigation modes - non-VRML ones are simply blaxxun contact ones*/ enum { - /*no navigation*/ + /*! no navigation*/ GF_NAVIGATE_NONE = 0, - /*3D navigation modes*/ - /*walk navigation*/ + /*! 3D navigation modes*/ + /*! walk navigation*/ GF_NAVIGATE_WALK, - /*fly navigation*/ + /*! fly navigation*/ GF_NAVIGATE_FLY, - /*pan navigation*/ + /*! pan navigation*/ GF_NAVIGATE_PAN, - /*game navigation*/ + /*! game navigation*/ GF_NAVIGATE_GAME, - /*slide navigation, for 2D and 3D*/ + /*! slide navigation, for 2D and 3D*/ GF_NAVIGATE_SLIDE, - /*all modes below disable collision detection & gravity in 3D*/ + /*! all modes below disable collision detection & gravity in 3D*/ /*examine navigation, for 2D and 3D */ GF_NAVIGATE_EXAMINE, - /*orbit navigation - 3D only*/ + /*! orbit navigation - 3D only*/ GF_NAVIGATE_ORBIT, - /*QT-VR like navigation - 3D only*/ + /*! QT-VR like navigation - 3D only*/ GF_NAVIGATE_VR, }; -/*collision flags*/ +/*! collision flags*/ enum { - /*no collision*/ + /*! no collision*/ GF_COLLISION_NONE, - /*regular collision*/ + /*! regular collision*/ GF_COLLISION_NORMAL, - /*collision with camera displacement*/ + /*! collision with camera displacement*/ GF_COLLISION_DISPLACEMENT, }; -/*TextTexturing settings*/ +/*! TextTexturing settings*/ enum { - GF_TEXTURE_TEXT_DEFAULT = 0, /*text drawn as texture in 3D mode, regular in 2D mode*/ - GF_TEXTURE_TEXT_NEVER, /*text never drawn as texture*/ - GF_TEXTURE_TEXT_ALWAYS /*text always drawn*/ + /*! text drawn as texture in 3D mode, regular in 2D mode*/ + GF_TEXTURE_TEXT_DEFAULT = 0, + /*! text never drawn as texture*/ + GF_TEXTURE_TEXT_NEVER, + /*! text always drawn*/ + GF_TEXTURE_TEXT_ALWAYS }; -/*Normal drawing settings*/ +/*! Normal drawing settings*/ enum { - GF_NORMALS_NONE = 0, /*normals never drawn*/ - GF_NORMALS_FACE, /*normals drawn per face (at barycenter)*/ - GF_NORMALS_VERTEX /*normals drawn per vertex*/ + /*! normals never drawn*/ + GF_NORMALS_NONE = 0, + /*! normals drawn per face (at barycenter)*/ + GF_NORMALS_FACE, + /*! normals drawn per vertex*/ + GF_NORMALS_VERTEX }; -/*Back-face culling mode*/ +/*! Back-face culling mode*/ enum { - GF_BACK_CULL_OFF = 0, /*backface culling disabled*/ - GF_BACK_CULL_ON, /*backface culliong enabled*/ - GF_BACK_CULL_ALPHA, /*backface culling enabled alos for transparent meshes*/ + /*! backface culling disabled*/ + GF_BACK_CULL_OFF = 0, + /*! backface culliong enabled*/ + GF_BACK_CULL_ON, + /*! backface culling enabled alos for transparent meshes*/ + GF_BACK_CULL_ALPHA, }; +/*! 2D drawing mode*/ enum { + /*! defer draw mode (only modified parts of the canvas are drawn)*/ GF_DRAW_MODE_DEFER=0, - GF_DRAW_MODE_DEFER_DEBUG, + /*! immediate draw mode (canvas always redrawn)*/ GF_DRAW_MODE_IMMEDIATE, + /*! defer debug draw mode (only modified parts of the canvas are drawn, the rest of the canvas is erased)*/ + GF_DRAW_MODE_DEFER_DEBUG, }; -/*high-level options*/ +/*! high-level options*/ enum { - /*set/get antialias flag (value: one of the AntiAlias enum) - may be ignored in OpenGL mode depending on graphic cards*/ + /*! set/get antialias flag (value: one of the AntiAlias enum) - may be ignored in OpenGL mode depending on graphic cards*/ GF_OPT_ANTIALIAS =0, - /*set/get fast mode (value: boolean) */ + /*! set/get fast mode (value: boolean) */ GF_OPT_HIGHSPEED, - /*set/get fullscreen flag (value: boolean) */ + /*! set/get fullscreen flag (value: boolean) */ GF_OPT_FULLSCREEN, - /*reset top-level transform to original (value: boolean)*/ + /*! reset top-level transform to original (value: boolean)*/ GF_OPT_ORIGINAL_VIEW, - /*overrides BIFS size info for simple AV - this is not recommended since + /*! overrides BIFS size info for simple AV - this is not recommended since it will resize the window to the size of the biggest texture (thus some elements may be lost)*/ GF_OPT_OVERRIDE_SIZE, - /*set / get audio volume (value is intensity between 0 and 100) */ + /*! set / get audio volume (value is intensity between 0 and 100) */ GF_OPT_AUDIO_VOLUME, - /*set / get audio pan (value is pan between 0 (all left) and 100(all right) )*/ + /*! set / get audio pan (value is pan between 0 (all left) and 100(all right) )*/ GF_OPT_AUDIO_PAN, - /*set / get audio mute*/ + /*! set / get audio mute*/ GF_OPT_AUDIO_MUTE, - /*get javascript flag (no set, depends on compil) - value: boolean, true if JS enabled in build*/ + /*! get javascript flag (no set, depends on compil) - value: boolean, true if JS enabled in build*/ GF_OPT_HAS_JAVASCRIPT, - /*get selectable stream flag (no set) - value: boolean, true if audio/video/subtitle stream selection is + /*! get selectable stream flag (no set) - value: boolean, true if audio/video/subtitle stream selection is possible with content (if an MPEG-4 scene description is not present). Use regular OD browsing to get streams*/ GF_OPT_CAN_SELECT_STREAMS, - /*set/get control interaction, OR'ed combination of interaction flags*/ + /*! set/get control interaction, OR'ed combination of interaction flags*/ GF_OPT_INTERACTION_LEVEL, - /*set display window visible / get show/hide state*/ + /*! set display window visible / get show/hide state*/ GF_OPT_VISIBLE, - /*set freeze display on/off / get freeze state freeze_display prevents any screen updates + /*! set freeze display on/off / get freeze state freeze_display prevents any screen updates needed when output driver uses direct video memory access*/ GF_OPT_FREEZE_DISPLAY, - /*Returns 1 if file playback is considered as done (all streams finished, no active time sensors + /*! Returns 1 if file playback is considered as done (all streams finished, no active time sensors and no user interactions in the scene)*/ GF_OPT_IS_FINISHED, - /*Returns 1 if file timeline is considered as done (all streams finished, no active time sensors)*/ + /*! Returns 1 if file timeline is considered as done (all streams finished, no active time sensors)*/ GF_OPT_IS_OVER, - /*set/get aspect ratio (value: one of AspectRatio enum) */ + /*! set/get aspect ratio (value: one of AspectRatio enum) */ GF_OPT_ASPECT_RATIO, - /*send a redraw message (SetOption only): all graphics info (display list, vectorial path) is + /*! send a redraw message (SetOption only): all graphics info (display list, vectorial path) is recomputed, and textures are reloaded in HW*/ GF_OPT_REFRESH, - /*set/get stress mode (value: boolean) - in stress mode a GF_OPT_FORCE_REDRAW is emulated at each frame*/ + /*! set/get stress mode (value: boolean) - in stress mode a GF_OPT_FORCE_REDRAW is emulated at each frame*/ GF_OPT_STRESS_MODE, - /*get/set bounding volume drawing (value: one of the above option)*/ + /*! get/set bounding volume drawing (value: one of the above option)*/ GF_OPT_DRAW_BOUNDS, - /*get/set texture text option - when enabled and usable (that depends on content), text is first rendered + /*! get/set texture text option - when enabled and usable (that depends on content), text is first rendered to a texture and only the texture is drawn, rather than drawing all the text each time (CPU intensive)*/ GF_OPT_TEXTURE_TEXT, - /*fake option, reload config file (set only), including drivers. Plugins configs are not reloaded*/ + /*! reload config file (set only), including drivers. Plugins configs are not reloaded*/ GF_OPT_RELOAD_CONFIG, - /*get: returns whether the content enable navigation and if it's 2D or 3D. + /*! get: returns whether the content enable navigation and if it's 2D or 3D. set: reset viewpoint (whatever value is given)*/ GF_OPT_NAVIGATION_TYPE, - /*get current navigation mode - set navigation mode if allowed by content - this is not a resident + /*! get current navigation mode - set navigation mode if allowed by content - this is not a resident option (eg not stored in cfg)*/ GF_OPT_NAVIGATION, - /*get/set GF_StreamingCache state - cf above states for set*/ - GF_OPT_MEDIA_CACHE, - /*get/set Play state - cf above states for set*/ + /*! get/set Play state - cf above states for set*/ GF_OPT_PLAY_STATE, - /*get only: returns 1 if main addon is playing, 0 if regular scene is playing*/ + /*! get only: returns 1 if main addon is playing, 0 if regular scene is playing*/ GF_OPT_MAIN_ADDON, - /*get/set bench mode - if enabled, video frames are drawn as soon as possible witthout checking synchronisation*/ + /*! get/set bench mode - if enabled, video frames are drawn as soon as possible witthout checking synchronisation*/ GF_OPT_VIDEO_BENCH, - /*get/set OpenGL force mode - returns error if OpenGL is not supported*/ + /*! get/set OpenGL force mode - returns error if OpenGL is not supported*/ GF_OPT_USE_OPENGL, - - /*set/get draw mode. - In immediate mode, the screen is entirely redrawn at each frame - In defer mode, only the changed ares are redrawn - In defer-debug mode, unchanged areas are erased and changed ares are redrawn - value: enum - */ + /*! set/get draw mode*/ GF_OPT_DRAW_MODE, - /*set/get scalable zoom (value: boolean)*/ + /*! set/get scalable zoom (value: boolean)*/ GF_OPT_SCALABLE_ZOOM, - /*set/get YUV acceleration (value: boolean) */ + /*! set/get YUV acceleration (value: boolean) */ GF_OPT_YUV_HARDWARE, - /*get (set not supported yet) hardware YUV format (value: YUV 4CC) */ + /*! get (set not supported yet) hardware YUV format (value: YUV 4CC) */ GF_OPT_YUV_FORMAT, - - /*max video cache size in kbytes*/ + /*! max video cache size in kbytes*/ GF_OPT_VIDEO_CACHE_SIZE, - - /*max HTTP download rate in bits per second, 0 if no limit*/ + /*! max HTTP download rate in bits per second, 0 if no limit*/ GF_OPT_HTTP_MAX_RATE, - - /*set only (value: boolean). If set, the main audio mixer can no longer be reconfigured. */ + /*! set only (value: boolean). If set, the main audio mixer can no longer be reconfigured. */ GF_OPT_FORCE_AUDIO_CONFIG, - /* 3D ONLY OPTIONS */ - /*set/get raster outline flag (value: boolean) - when set, no vectorial outlining is done, only - openGL raster outline*/ + /*! 3D ONLY OPTIONS */ + /*! set/get raster outline flag (value: boolean) - when set, no vectorial outlining is done, only openGL raster outline*/ GF_OPT_RASTER_OUTLINES, - /*set/get pow2 emulation flag (value: boolean) - when set, video textures with non power of 2 dimensions + /*! set/get pow2 emulation flag (value: boolean) - when set, video textures with non power of 2 dimensions are emulated as pow2 by expanding the video buffer (image is not scaled). Otherwise the entire image is rescaled. This flag does not affect image textures, which are always rescaled*/ GF_OPT_EMULATE_POW2, - /*get/set polygon antialiasing flag (value: boolean) (may be ugly with some cards)*/ + /*! get/set polygon antialiasing flag (value: boolean) (may be ugly with some cards)*/ GF_OPT_POLYGON_ANTIALIAS, - /*get/set wireframe flag (value: cf above) (may be ugly with some cards)*/ + /*! get/set wireframe flag (value: cf above) (may be ugly with some cards)*/ GF_OPT_WIREFRAME, - /*get/set wireframe flag (value: cf above) (may be ugly with some cards)*/ + /*! get/set wireframe flag (value: cf above) (may be ugly with some cards)*/ GF_OPT_NORMALS, - /*disable backface culling*/ + /*! disable backface culling*/ GF_OPT_BACK_CULL, - /*get/set RECT Ext flag (value: boolean) - when set, GL rectangular texture extension is not used + /*! get/set RECT Ext flag (value: boolean) - when set, GL rectangular texture extension is not used (but NPO2 texturing is if available)*/ GF_OPT_NO_RECT_TEXTURE, - /*set/get headlight (value: boolean)*/ + /*! set/get headlight (value: boolean)*/ GF_OPT_HEADLIGHT, - /*set/get collision (value: cf above)*/ + /*! set/get collision (value: cf above)*/ GF_OPT_COLLISION, - /*set/get gravity*/ + /*! set/get gravity*/ GF_OPT_GRAVITY, - - /*get the number of offscreen views in stereo mode, or 1 if no offscreen stereo views are available*/ + /*! get the number of offscreen views in stereo mode, or 1 if no offscreen stereo views are available*/ GF_OPT_NUM_STEREO_VIEWS, - /*set the mode of display of HEVC multiview videos, 0 to display the two views/layers and 1 to display just the first view/layer*/ + /*! set the mode of display of HEVC multiview videos, 0 to display the two views/layers and 1 to display just the first view/layer*/ GF_OPT_MULTIVIEW_MODE, - /*get orientation sensors flag, true if sensors are activated false if not*/ + /*! get orientation sensors flag, true if sensors are activated false if not*/ GF_OPT_ORIENTATION_SENSORS_ACTIVE, }; diff --git a/include/gpac/path2d.h b/include/gpac/path2d.h index 383ffdf..1b22e76 100644 --- a/include/gpac/path2d.h +++ b/include/gpac/path2d.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -28,9 +28,9 @@ #define _GF_PATH2D_H_ /*! - * \file <gpac/path2d.h> - * \brief 2D Vectorial Path. - */ +\file <gpac/path2d.h> +\brief 2D Vectorial Path. +*/ @@ -43,19 +43,19 @@ extern "C" { /*! - *\addtogroup path_grp 2D Vector Path - *\ingroup math_grp - *\brief Vectorial 2D Path manipulation - * - *This section documents the 2D path object used in the GPAC framework. - * @{ - */ +\addtogroup path_grp 2D Vector Path +\ingroup math_grp +\brief Vectorial 2D Path manipulation + +This section documents the 2D path object used in the GPAC framework. + +@{ +*/ /*!\brief 2D Path Object - * - *The 2D path object is used to construct complex 2D shapes for later drawing - * or outlining. + +The 2D path object is used to construct complex 2D shapes for later drawing or outlining. */ typedef struct { @@ -79,325 +79,335 @@ typedef struct /*! - * \brief path constructor - * - * Constructs an empty 2D path object - * \return new path object +\brief path constructor + +Constructs an empty 2D path object +\return new path object */ GF_Path *gf_path_new(); /*! - * \brief path destructor - * - * Destructs a 2D path object - * \param gp the target path +\brief path destructor + +Destructs a 2D path object +\param gp the target path */ void gf_path_del(GF_Path *gp); /*! - * \brief path reset - * - * Resets the 2D path object - * \param gp the target path +\brief path reset + +Resets the 2D path object +\param gp the target path */ void gf_path_reset(GF_Path *gp); /*! - * \brief path copy constuctor - * - * Resets a copy of a 2D path object - * \param gp the target path - * \return new path copy +\brief path copy constuctor + +Resets a copy of a 2D path object +\param gp the target path +\return new path copy */ GF_Path *gf_path_clone(GF_Path *gp); /*! - * \brief path close - * - * Closes current path contour - * \param gp the target path - * \return error code if any error, \ref GF_OK otherwise +\brief path close + +Closes current path contour +\param gp the target path +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_close(GF_Path *gp); /*! - * \brief path moveTo - * - * Starts a new contour from the specified point - * \param gp the target path - * \param x x-coordinate of the new point - * \param y y-coordinate of the new point - * \return error code if any error, \ref GF_OK otherwise +\brief path moveTo + +Starts a new contour from the specified point +\param gp the target path +\param x x-coordinate of the new point +\param y y-coordinate of the new point +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_add_move_to(GF_Path *gp, Fixed x, Fixed y); /*! - * \brief starts new contour - * - * Starts a new contour from the specified point - * \param gp the target path - * \param pt pointer to the new start point - * \return error code if any error, \ref GF_OK otherwise +\brief starts new contour + +Starts a new contour from the specified point +\param gp the target path +\param pt pointer to the new start point +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_add_move_to_vec(GF_Path *gp, GF_Point2D *pt); /*! - * \brief adds line to path - * - * Adds a line from the current point in path to the specified point - * \param gp the target path - * \param x x-coordinate of the line end - * \param y y-coordinate of the line end - * \return error code if any error, \ref GF_OK otherwise +\brief adds line to path + +Adds a line from the current point in path to the specified point +\param gp the target path +\param x x-coordinate of the line end +\param y y-coordinate of the line end +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_add_line_to(GF_Path *gp, Fixed x, Fixed y); /*! - * \brief adds line to path - * - * Adds a line from the current point in path to the specified point - * \param gp the target path - * \param pt line end - * \return error code if any error, \ref GF_OK otherwise +\brief adds line to path + +Adds a line from the current point in path to the specified point +\param gp the target path +\param pt line end +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_add_line_to_vec(GF_Path *gp, GF_Point2D *pt); /*! - * \brief adds cubic to path - * - * Adds a cubic bezier curve to the current contour, starting from the current path point - * \param gp the target path - * \param c1_x x-coordinate of the first control point of the cubic curve - * \param c1_y y-coordinate of the first control point of the cubic curve - * \param c2_x x-coordinate of the second control point of the cubic curve - * \param c2_y y-coordinate of the second control point of the cubic curve - * \param x x-coordinate of the end point of the cubic curve - * \param y y-coordinate of the end point of the cubic curve - * \return error code if any error, \ref GF_OK otherwise +\brief adds cubic to path + +Adds a cubic bezier curve to the current contour, starting from the current path point +\param gp the target path +\param c1_x x-coordinate of the first control point of the cubic curve +\param c1_y y-coordinate of the first control point of the cubic curve +\param c2_x x-coordinate of the second control point of the cubic curve +\param c2_y y-coordinate of the second control point of the cubic curve +\param x x-coordinate of the end point of the cubic curve +\param y y-coordinate of the end point of the cubic curve +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_add_cubic_to(GF_Path *gp, Fixed c1_x, Fixed c1_y, Fixed c2_x, Fixed c2_y, Fixed x, Fixed y); /*! - * \brief adds cubic to path - * - * Adds a cubic bezier curve to the current contour, starting from the current path point - * \param gp the target path - * \param c1 first control point of the cubic curve - * \param c2 second control point of the cubic curve - * \param pt end point of the cubic curve - * \return error code if any error, \ref GF_OK otherwise +\brief adds cubic to path + +Adds a cubic bezier curve to the current contour, starting from the current path point +\param gp the target path +\param c1 first control point of the cubic curve +\param c2 second control point of the cubic curve +\param pt end point of the cubic curve +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_add_cubic_to_vec(GF_Path *gp, GF_Point2D *c1, GF_Point2D *c2, GF_Point2D *pt); /*! - * \brief adds quadratic to path - * - * Adds a quadratic bezier curve to the current contour, starting from the current path point - * \param gp the target path - * \param c_x x-coordinate of the control point of the quadratic curve - * \param c_y y-coordinate of the control point of the quadratic curve - * \param x x-coordinate of the end point of the cubic quadratic - * \param y y-coordinate of the end point of the cubic quadratic - * \return error code if any error, \ref GF_OK otherwise +\brief adds quadratic to path + +Adds a quadratic bezier curve to the current contour, starting from the current path point +\param gp the target path +\param c_x x-coordinate of the control point of the quadratic curve +\param c_y y-coordinate of the control point of the quadratic curve +\param x x-coordinate of the end point of the cubic quadratic +\param y y-coordinate of the end point of the cubic quadratic +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_add_quadratic_to(GF_Path *gp, Fixed c_x, Fixed c_y, Fixed x, Fixed y); /*! - * \brief adds quadratic to path - * - * Adds a quadratic bezier curve to the current contour, starting from the current path point - * \param gp the target path - * \param c control point of the quadratic curve - * \param pt end point of the cubic quadratic - * \return error code if any error, \ref GF_OK otherwise +\brief adds quadratic to path + +Adds a quadratic bezier curve to the current contour, starting from the current path point +\param gp the target path +\param c control point of the quadratic curve +\param pt end point of the cubic quadratic +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_add_quadratic_to_vec(GF_Path *gp, GF_Point2D *c, GF_Point2D *pt); /*! - * \brief adds rectangle to path - * - * Adds a rectangle contour to the path - * \param gp the target path - * \param cx x-coordinate of the rectangle center - * \param cy y-coordinate of the rectangle center - * \param w width of the rectangle - * \param h height of the rectangle - * \return error code if any error, \ref GF_OK otherwise +\brief adds rectangle to path + +Adds a rectangle contour to the path +\param gp the target path +\param cx x-coordinate of the rectangle center +\param cy y-coordinate of the rectangle center +\param w width of the rectangle +\param h height of the rectangle +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_add_rect_center(GF_Path *gp, Fixed cx, Fixed cy, Fixed w, Fixed h); /*! - * \brief adds rectangle to path - * - * Adds a rectangle contour to the path - * \param gp the target path - * \param ox left-most coordinate of the rectangle - * \param oy top-most coordinate of the rectangle - * \param w width of the rectangle - * \param h height of the rectangle - * \return error code if any error, \ref GF_OK otherwise +\brief adds rectangle to path + +Adds a rectangle contour to the path +\param gp the target path +\param ox left-most coordinate of the rectangle +\param oy top-most coordinate of the rectangle +\param w width of the rectangle +\param h height of the rectangle +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_add_rect(GF_Path *gp, Fixed ox, Fixed oy, Fixed w, Fixed h); /*! - * \brief adds ellipse to path - * - * Adds an ellipse contour to the path - * \param gp the target path - * \param cx x-coordinate of the ellipse center - * \param cy y-coordinate of the ellipse center - * \param a_axis length of the horizontal ellipse axis - * \param b_axis length of the vertical ellipse axis - * \return error code if any error, \ref GF_OK otherwise +\brief adds ellipse to path + +Adds an ellipse contour to the path +\param gp the target path +\param cx x-coordinate of the ellipse center +\param cy y-coordinate of the ellipse center +\param a_axis length of the horizontal ellipse axis +\param b_axis length of the vertical ellipse axis +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_add_ellipse(GF_Path *gp, Fixed cx, Fixed cy, Fixed a_axis, Fixed b_axis); /*! - * \brief adds N-bezier curve to path - * - * Adds an N-degree bezier curve to the path, starting from the current point - * \param gp the target path - * \param pts points used to define the curve - * \param nb_pts number of points used to define the curve. The degree of the curve is therefore (nb_pts-1). - * \return error code if any error, \ref GF_OK otherwise - * \note the fineness of the path must be set before calling this function. +\brief adds N-1 bezier curve to path + +Adds an N-degree bezier curve to the path, starting from the current point +\param gp the target path +\param pts points used to define the curve +\param nb_pts number of points used to define the curve. The degree of the curve is therefore (nb_pts-1). +\return error code if any error, \ref GF_OK otherwise +\note the fineness of the path must be set before calling this function. */ GF_Err gf_path_add_bezier(GF_Path *gp, GF_Point2D *pts, u32 nb_pts); /*! - * \brief adds arc as described in MPEG-4 BIFS to path - * - * Adds an arc contour to the path from focal and end points. - * \param gp the target path - * \param end_x x-coordinate of the arc end point - * \param end_y y-coordinate of the arc end point - * \param fa_x x-coordinate of the arc first focal point - * \param fa_y y-coordinate of the arc first focal point - * \param fb_x x-coordinate of the arc second focal point - * \param fb_y y-coordinate of the arc second focal point - * \param cw if 1, the arc will be clockwise, otherwise counter-clockwise. - * \return error code if any error, \ref GF_OK otherwise +\brief adds arc as described in MPEG-4 BIFS to path + +Adds an arc contour to the path from focal and end points. +\param gp the target path +\param end_x x-coordinate of the arc end point +\param end_y y-coordinate of the arc end point +\param fa_x x-coordinate of the arc first focal point +\param fa_y y-coordinate of the arc first focal point +\param fb_x x-coordinate of the arc second focal point +\param fb_y y-coordinate of the arc second focal point +\param cw if 1, the arc will be clockwise, otherwise counter-clockwise. +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_add_arc_to(GF_Path *gp, Fixed end_x, Fixed end_y, Fixed fa_x, Fixed fa_y, Fixed fb_x, Fixed fb_y, Bool cw); /*! - * \brief adds arc as described in SVG to path - * - * Adds an arc contour to the path from end point, radii and 3 parameters. - * \param gp the target path - * \param end_x x-coordinate of the arc end point - * \param end_y y-coordinate of the arc end point - * \param r_x x-axis radius - * \param r_y y-axis radius - * \param x_axis_rotation angle for the x-axis - * \param large_arc_flag large or short arc selection - * \param sweep_flag if 1, the arc will be clockwise, otherwise counter-clockwise. - * \return error code if any error, \ref GF_OK otherwise +\brief adds arc as described in SVG to path + +Adds an arc contour to the path from end point, radii and 3 parameters. +\param gp the target path +\param end_x x-coordinate of the arc end point +\param end_y y-coordinate of the arc end point +\param r_x x-axis radius +\param r_y y-axis radius +\param x_axis_rotation angle for the x-axis +\param large_arc_flag large or short arc selection +\param sweep_flag if 1, the arc will be clockwise, otherwise counter-clockwise. +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_add_svg_arc_to(GF_Path *gp, Fixed end_x, Fixed end_y, Fixed r_x, Fixed r_y, Fixed x_axis_rotation, Bool large_arc_flag, Bool sweep_flag); + +/*! Close type for arc*/ +typedef enum +{ + /*! Arc is left open*/ + GF_PATH2D_ARC_OPEN=0, + /*! Arc is closed to its starting point*/ + GF_PATH2D_ARC_CLOSE, + /*! Arc is closed as a pie centered on arc center*/ + GF_PATH2D_ARC_PIE, +} GF_Path2DArcCloseType; /*! - * \brief adds arc to path - * - * Adds an arc contour to the path. - * \param gp the target path - * \param radius radius of the arc - * \param start_angle start angle of the arc in radians - * \param end_angle end angle of the arc in radians - * \param close_type closing type: 0 for open arc, 1 for close arc, 2 for pie - * \return error code if any error, \ref GF_OK otherwise +\brief adds arc to path + +Adds an arc contour to the path. +\param gp the target path +\param radius radius of the arc +\param start_angle start angle of the arc in radians +\param end_angle end angle of the arc in radians +\param close_type closing type: 0 for open arc, 1 for close arc, 2 for pie +\return error code if any error, \ref GF_OK otherwise */ -GF_Err gf_path_add_arc(GF_Path *gp, Fixed radius, Fixed start_angle, Fixed end_angle, u32 close_type); +GF_Err gf_path_add_arc(GF_Path *gp, Fixed radius, Fixed start_angle, Fixed end_angle, GF_Path2DArcCloseType close_type); /*! - * \brief concatenates path - * - * Adds a sub-path to the path with a given transform. - * \param gp the target path - * \param subpath the path to add - * \param mx Matrix for subpath - * \return error code if any error, \ref GF_OK otherwise +\brief concatenates path + +Adds a sub-path to the path with a given transform. +\param gp the target path +\param subpath the path to add +\param mx Matrix for subpath +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_add_subpath(GF_Path *gp, GF_Path *subpath, GF_Matrix2D *mx); /*! - * \brief gets path control bounds - * - * Gets the path control bounds, i.e. the rectangle covering all lineTo and bezier control points. - * \param gp the target path - * \param rc pointer to rectangle receiving the control rectangle - * \return error code if any error, \ref GF_OK otherwise +\brief gets path control bounds + +Gets the path control bounds, i.e. the rectangle covering all lineTo and bezier control points. +\param gp the target path +\param rc pointer to rectangle receiving the control rectangle +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_get_control_bounds(GF_Path *gp, GF_Rect *rc); /*! - * \brief gets path bounds - * - * Gets the path bounds, i.e. the rectangle covering all points in path except bezier control points. - * \param gp the target path - * \param rc pointer to rectangle receiving the control rectangle - * \return error code if any error, \ref GF_OK otherwise +\brief gets path bounds + +Gets the path bounds, i.e. the rectangle covering all points in path except bezier control points. +\param gp the target path +\param rc pointer to rectangle receiving the control rectangle +\return error code if any error, \ref GF_OK otherwise */ GF_Err gf_path_get_bounds(GF_Path *gp, GF_Rect *rc); /*! - * \brief flattens path - * - * Flattens the path, i.e. transform all bezier curves to lines according to the path flatness. - * \param gp the target path +\brief flattens path + +Flattens the path, i.e. transform all bezier curves to lines according to the path flatness. +\param gp the target path */ void gf_path_flatten(GF_Path *gp); /*! - * \brief gets flatten copy of path - * - * Gets a flatten copy of the path. - * \param gp the target path - * \return the flatten path +\brief gets flatten copy of path + +Gets a flatten copy of the path. +\param gp the target path +\return the flatten path */ GF_Path *gf_path_get_flatten(GF_Path *gp); /*! - * \brief point over path testing - * - * Tests if a point is over a path or not, according to the path filling rule. - * \param gp the target path - * \param x x-coordinate of the point to check - * \param y y-coordinate of the point to check - * \return 1 if the point is over the path, 0 otherwise. +\brief point over path testing + +Tests if a point is over a path or not, according to the path filling rule. +\param gp the target path +\param x x-coordinate of the point to check +\param y y-coordinate of the point to check +\return 1 if the point is over the path, 0 otherwise. */ Bool gf_path_point_over(GF_Path *gp, Fixed x, Fixed y); /*! - * \brief path init testing - * - * Tests if the path is empty or not. - * \param gp the target path - * \return 1 if the path is empty, 0 otherwise. +\brief path init testing + +Tests if the path is empty or not. +\param gp the target path +\return 1 if the path is empty, 0 otherwise. */ Bool gf_path_is_empty(GF_Path *gp); /*! - * \brief path iterator - * - * The path iterator object is used to compute the length of a given path as well - * as transformation matrices along this path. +\brief path iterator + +The path iterator object is used to compute the length of a given path as well as transformation matrices along this path. */ typedef struct _path_iterator GF_PathIterator; /*! - * \brief path iterator constructor - * - * Creates a new path iterator from a given path - * \param gp the target path - * \return the path iterator object. +\brief path iterator constructor + +Creates a new path iterator from a given path +\param gp the target path +\return the path iterator object. */ GF_PathIterator *gf_path_iterator_new(GF_Path *gp); /*! - * \brief path iterator destructor - * - * Destructs the path iterator object - * \param it the target path iterator +\brief path iterator destructor + +Destructs the path iterator object +\param it the target path iterator */ void gf_path_iterator_del(GF_PathIterator *it); /*! - * \brief get path length - * - * Gets a path length from its iterator - * \param it the target path iterator - * \return the length of the path +\brief get path length + +Gets a path length from its iterator +\param it the target path iterator +\return the length of the path */ Fixed gf_path_iterator_get_length(GF_PathIterator *it); /*! - *\brief gets transformation matrix at given point on path - * - * Gets the transformation of a given point on the path, given by offset from origin. - *The transform is so that a local system is translated to the given point, its x-axis tangent - *to the path and in the same direction. The path direction is from first point to last point - *of the path. - * \param it the target path iterator - * \param offset length on the path in local system unit - * \param follow_tangent indicates if transformation shall be computed if offset indicates a point outside the path (<0 or >path_length). In which case the path shall be virtually extended by the tangent at origin (offset <0) or at end (offset>path_length). Otherwise the transformation is not computed and 0 is returned. - * \param mat matrix to be transformed (transformation shall be appended) - the matrix shall not be initialized - * \param smooth_edges indicates if discontinuities shall be smoothed. If not set, the rotation angle THETA is the slope (DX/DY) of the current segment found. - * \param length_after_point if set and smooth_edges is set, the amount of the object that lies on next segment shall be computed according to length_after_point. +\brief gets transformation matrix at given point on path + +Gets the transformation of a given point on the path, given by offset from origin. +The transform is so that a local system is translated to the given point, its x-axis tangent to the path and in the same direction. +The path direction is from first point to last point of the path. +\param it the target path iterator +\param offset length on the path in local system unit +\param follow_tangent indicates if transformation shall be computed if offset indicates a point outside the path (<0 or >path_length). In which case the path shall be virtually extended by the tangent at origin (offset <0) or at end (offset>path_length). Otherwise the transformation is not computed and 0 is returned. +\param mat matrix to be transformed (transformation shall be appended) - the matrix shall not be initialized +\param smooth_edges indicates if discontinuities shall be smoothed. If not set, the rotation angle THETA is the slope (DX/DY) of the current segment found. +\param length_after_point if set and smooth_edges is set, the amount of the object that lies on next segment shall be computed according to length_after_point. + \code Let: len_last: length of current checked segment @@ -408,18 +418,18 @@ Fixed gf_path_iterator_get_length(GF_PathIterator *it); Of course care must be taken for PI/2 angles and similar situations \endcode - * \return 1 if matrix has been updated, 0 otherwise, if failure or if point is out of path without tangent extension. +\return GF_TRUE if matrix has been updated, GF_FALSE otherwise, if failure or if point is out of path without tangent extension. + */ Bool gf_path_iterator_get_transform(GF_PathIterator *it, Fixed offset, Bool follow_tangent, GF_Matrix2D *mat, Bool smooth_edges, Fixed length_after_point); /*! brief gets convexity type for a 2D polygon - * - * Gets the convexity type of the given 2D polygon - * \param pts the points of the polygon - * \param nb_pts number of points in the polygon - * \return the convexity type of the polygon +Gets the convexity type of the given 2D polygon +\param pts the points of the polygon +\param nb_pts number of points in the polygon +\return the convexity type of the polygon */ u32 gf_polygone2d_get_convexity(GF_Point2D *pts, u32 nb_pts); @@ -427,8 +437,8 @@ u32 gf_polygone2d_get_convexity(GF_Point2D *pts, u32 nb_pts); /* 2D Path constants */ /*! - *2D Path point tags - * \hideinitializer +2D Path point tags +\hideinitializer */ enum { @@ -444,8 +454,8 @@ enum /*! - *2D Path flags - * \hideinitializer +2D Path flags +\hideinitializer */ enum { @@ -460,8 +470,8 @@ enum }; /*! - * 2D Polygon convexity type - * \hideinitializer +2D Polygon convexity type +\hideinitializer */ enum { @@ -480,8 +490,8 @@ enum }; /*! - * Stencil alignment type for outlining - * \hideinitializer +Stencil alignment type for outlining +\hideinitializer */ enum { @@ -494,8 +504,8 @@ enum }; /*! - * Line cap type for outlining - * \hideinitializer +Line cap type for outlining +\hideinitializer */ enum { @@ -510,8 +520,8 @@ enum }; /*! - * Line join type for outlining - * \hideinitializer +Line join type for outlining +\hideinitializer */ enum { @@ -526,8 +536,8 @@ enum }; /*! - * Dash types for outlining - * \hideinitializer +Dash types for outlining +\hideinitializer */ enum { @@ -552,8 +562,7 @@ enum /*!\brief Custom dash pattern - * - *The custom dash pattern object is used to specify custom dashes when outlining a path. +The custom dash pattern object is used to specify custom dashes when outlining a path. */ typedef struct { @@ -563,14 +572,11 @@ typedef struct u32 num_dash; /*! Value of the pattern dashes. Unit depends on the dash type*/ Fixed *dashes; - /*! SVG/CSS unit for the dashes */ - u8 *dash_units; } GF_DashSettings; /*!\brief Pen properties - * - *The pen properties object is used to specify several parameters used when building - *the vectorial outline of a path. + +The pen properties object is used to specify several parameters used when building the vectorial outline of a path. */ typedef struct { @@ -589,18 +595,18 @@ typedef struct /*! The initial dash offset in the outline. All points before this offset will be * ignored when building the outline*/ Fixed dash_offset; - /*! The dash pattern used for curstom dashing*/ + /*! The dash pattern used for custom dashing*/ GF_DashSettings *dash_set; /*! The author-specified path length. Ignored if <= 0*/ Fixed path_length; } GF_PenSettings; /*! brief builds the vectorial outline of a path - * - * Builds the vectorial outline of a path for the given settings. The outline of a path is a path. - * \param path the desired path to outline - * \param pen the properties of the virtual pen used for outlining - * \return the outline of the path + +Builds the vectorial outline of a path for the given settings. The outline of a path is a path. +\param path the desired path to outline +\param pen the properties of the virtual pen used for outlining +\return the outline of the path */ GF_Path *gf_path_get_outline(GF_Path *path, GF_PenSettings pen); diff --git a/include/gpac/ringbuffer.h b/include/gpac/ringbuffer.h deleted file mode 100644 index bec75ca..0000000 --- a/include/gpac/ringbuffer.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Pierre Souchay - * Copyright (c) Telecom ParisTech 2010-2012 - * All rights reserved - * - * This file is part of GPAC / common tools sub-project - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef _GF_RINGBUFFER_H -#define _GF_RINGBUFFER_H - -#include <gpac/tools.h> -#include <gpac/thread.h> - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct -{ - u8 *buf; - volatile u32 write_ptr; - volatile u32 read_ptr; - u32 size; - u32 size_mask; - GF_Mutex * mx; -} -GF_Ringbuffer ; - - -/*! - * Creates a new ringbuffer with specified size. The caller has the - * reponsability to free the ringbuffer using gf_ringbuffer_del() - * - * \param sz the ringbuffer size in bytes - * - * \return a pointer to a new ringbuffer if successful, NULL otherwise. - */ -GF_Ringbuffer * gf_ringbuffer_new(u32 sz); - -/*! - * Frees a previously allocated ringbuffer - * \param ringbuffer The ringbuffer to free - */ -void gf_ringbuffer_del(GF_Ringbuffer * ringbuffer); - -/*! - * Reads bytes from ringbuffer - * \param rb The ringbuffer to read from - * \param dest The destination - * \param szDest Size of destination - * \return the number of bytes readen - */ -u32 gf_ringbuffer_read(GF_Ringbuffer *rb, u8 *dest, u32 szDest); - -/*! - * Return the number of bytes available for reading. This is the - * number of bytes in front of the read pointer and behind the write - * pointer. - * \param rb The ringbuffer - * \return the number of bytes available for reading - */ -u32 gf_ringbuffer_available_for_read (GF_Ringbuffer * rb); - -/*! - * Copy at most sz bytes to rb from src. - * \param rb The ringbuffer to write to - * \param src The source buffer - * \param sz the size of source - * \return Returns the actual number of bytes copied, may be lower than sz if ringbuffer is already full - */ -u32 gf_ringbuffer_write (GF_Ringbuffer * rb, const u8 * src, u32 sz); - -#ifdef __cplusplus -} -#endif -#endif /* _GF_RINGBUFFER_H */ diff --git a/include/gpac/rtp_streamer.h b/include/gpac/rtp_streamer.h index 6545543..fc88dc9 100644 --- a/include/gpac/rtp_streamer.h +++ b/include/gpac/rtp_streamer.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / media tools sub-project @@ -31,125 +31,216 @@ extern "C" { #endif /*! - * \file <gpac/rtp_streamer.h> - * \brief RTP streaming (packetizer and RTP socket). - */ +\file <gpac/rtp_streamer.h> +\brief RTP streaming (packetizer and RTP socket). +*/ /*! - * \ingroup media_grp - * \brief RTPStreamer object - * - * This section documents the RTP streamer object of the GPAC framework. - * @{ +\addtogroup rtp_grp RTP Streamer +\ingroup media_grp +\brief RTPStreamer object + +This section documents the RTP streamer object of the GPAC framework. + +@{ */ + + #include <gpac/ietf.h> -#include <gpac/isomedia.h> #if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_ISOM) +/*! RTP streamer object*/ typedef struct __rtp_streamer GF_RTPStreamer; -/*! - * \brief RTP Streamer constructor - * - * Constructs a new RTP file streamer - *\param streamType type of the stream (GF_STREAM_* as defined in <gpac/constants.h>) - *\param oti MPEG-4 object type indication for the stream (GPAC_OTI_* as defined in <gpac/constants.h>) - *\param timeScale unit to express timestamps - *\param ip_dest IP address of the destination - *\param port port number of the destination - *\param MTU Maximum Transmission Unit size to use - *\param TTL Time To Leave - *\param ifce_addr IP of the local interface to use (may be NULL) - *\param flags - *\param dsi MPEG-4 Decoder Specific Info for the stream - *\param dsi_len length of the dsi parameter - *\return new object - */ - -GF_RTPStreamer *gf_rtp_streamer_new(u32 streamType, u32 oti, u32 timeScale, - const char *ip_dest, u16 port, u32 MTU, u8 TTL, const char *ifce_addr, - u32 flags, char *dsi, u32 dsi_len); /*! - * \brief RTP Streamer constructor with extended parameters - * - * Constructs a new RTP file streamer - *\param streamType type of the stream (GF_STREAM_* as defined in <gpac/constants.h>) - *\param oti MPEG-4 object type indication for the stream (GPAC_OTI_* as defined in <gpac/constants.h>) - *\param timeScale unit to express timestamps - *\param ip_dest IP address of the destination - *\param port port number of the destination - *\param MTU Maximum Transmission Unit size to use - *\param TTL Time To Leave - *\param ifce_addr IP of the local interface to use (may be NULL) - *\param flags - *\param dsi MPEG-4 Decoder Specific Info for the stream - *\param dsi_len length of the dsi parameter - *\param PayloadType RTP payload type - *\param sample_rate audio sample rate - *\param nb_ch number of channels in audio streams - *\param is_crypted Boolean indicating if the stream is crypted - *\param IV_length lenght of the Initialisation Vector used for encryption - *\param KI_length length of the key index - *\param MinSize - *\param MaxSize - *\param avgTS - *\param maxDTSDelta - *\param const_dur - *\param bandwidth - *\param max_ptime - *\param au_sn_len length of the MPEG-4 SL descriptor AU sequence number field - *\return new object +\brief RTP Streamer constructor with extended parameters + +Constructs a new RTP file streamer +\param streamType type of the stream (GF_STREAM_* as defined in <gpac/constants.h>) +\param codecid codec ID for the stream (GF_CODEC_* as defined in <gpac/constants.h>) +\param timeScale unit to express timestamps of access units +\param ip_dest IP address of the destination +\param port port number of the destination +\param MTU Maximum Transmission Unit size to use +\param TTL Time To Leave +\param ifce_addr IP of the local interface to use (may be NULL) +\param flags set of RTP flags passed to the streamer +\param dsi MPEG-4 Decoder Specific Info for the stream +\param dsi_len length of the dsi parameter +\param PayloadType RTP payload type +\param sample_rate audio sample rate +\param nb_ch number of channels in audio streams +\param is_crypted Boolean indicating if the stream is crypted +\param IV_length lenght of the Initialisation Vector used for encryption +\param KI_length length of the key index +\param MinSize minimum AU size, 0 if unknown +\param MaxSize maximum AU size, 0 if unknown +\param avgTS average TS delta in timeScale, 0 if unknown +\param maxDTSDelta maximum DTS delta in timeScale, 0 if unknown +\param const_dur constant duration in timeScale, 0 if unknown +\param bandwidth bandwidth, 0 if unknown +\param max_ptime maximum packet duration in timeScale, 0 if unknown +\param au_sn_len length of the MPEG-4 SL descriptor AU sequence number field, 0 if unknown +\param for_rtsp indicates this is an RTP channel in an RTSP session, RTP channel will not be created, use \ref gf_rtp_streamer_init_rtsp +\return a new RTP streamer, or NULL of error or not supported */ -GF_RTPStreamer *gf_rtp_streamer_new_extended(u32 streamType, u32 oti, u32 timeScale, +GF_RTPStreamer *gf_rtp_streamer_new(u32 streamType, u32 codecid, u32 timeScale, const char *ip_dest, u16 port, u32 MTU, u8 TTL, const char *ifce_addr, - u32 flags, char *dsi, u32 dsi_len, + u32 flags, const u8 *dsi, u32 dsi_len, u32 PayloadType, u32 sample_rate, u32 nb_ch, Bool is_crypted, u32 IV_length, u32 KI_length, - u32 MinSize, u32 MaxSize, u32 avgTS, u32 maxDTSDelta, u32 const_dur, u32 bandwidth, u32 max_ptime, - u32 au_sn_len); + u32 MinSize, u32 MaxSize, u32 avgTS, u32 maxDTSDelta, u32 const_dur, u32 bandwidth, u32 max_ptime, u32 au_sn_len, Bool for_rtsp); /*! - * \brief RTP file streamer destructor - * - * Destructs an RTP file streamer - * \param streamer object to destruct +\brief RTP file streamer destructor + +Destructs an RTP file streamer +\param streamer the target RTP streamer */ void gf_rtp_streamer_del(GF_RTPStreamer *streamer); /*! - * \brief gets the SDP file - * - * Gets the SDP asscoiated with all media in the streaming session (only media parts are returned) - * \param rtp RTP streamer object - * \param ESID The MPEG-4 elementary stream id of the stream to process - * \param dsi The MPEG-4 decoder specific info data - * \param dsi_len length of The MPEG-4 decoder specific info data - * \param KMS_URI URI of the Key Management System - * \param out_sdp_buffer location to the SDP buffer to allocate and fill +\brief gets the SDP file + +Gets the SDP asscoiated with all media in the streaming session (only media parts are returned) +\param rtp the target RTP streamer +\param ESID The MPEG-4 elementary stream id of the stream to process +\param dsi The decoder specific info data +\param dsi_len length of the decoder specific info data +\param KMS_URI URI of the Key Management System +\param out_sdp_buffer location to the SDP buffer to allocate and fill +\return error if any */ -GF_Err gf_rtp_streamer_append_sdp(GF_RTPStreamer *rtp, u16 ESID, char *dsi, u32 dsi_len, char *KMS_URI, char **out_sdp_buffer); - -GF_Err gf_rtp_streamer_append_sdp_extended(GF_RTPStreamer *rtp, u16 ESID, char *dsi, u32 dsi_len, GF_ISOFile *isofile, u32 isotrack, char *KMS_URI, u32 width, u32 height, char **out_sdp_buffer) ; - -GF_Err gf_rtp_streamer_append_sdp_decoding_dependency(GF_ISOFile *isofile, u32 isotrack, u8 *payload_type, char **out_sdp_buffer); - -GF_Err gf_rtp_streamer_send_au(GF_RTPStreamer *rtp, char *data, u32 size, u64 cts, u64 dts, Bool is_rap); - -GF_Err gf_rtp_streamer_send_au_with_sn(GF_RTPStreamer *rtp, char *data, u32 size, u64 cts, u64 dts, Bool is_rap, u32 inc_au_sn); - -GF_Err gf_rtp_streamer_send_data(GF_RTPStreamer *rtp, char *data, u32 size, u32 fullsize, u64 cts, u64 dts, Bool is_rap, Bool au_start, Bool au_end, u32 au_sn, u32 sampleDuration, u32 sampleDescIndex); +GF_Err gf_rtp_streamer_append_sdp(GF_RTPStreamer *rtp, u16 ESID, const u8 *dsi, u32 dsi_len, char *KMS_URI, char **out_sdp_buffer); +/*! +\brief gets the SDP file + +Gets the SDP asscoiated with all media in the streaming session (only media parts are returned) +\param rtp the target RTP streamer +\param ESID The MPEG-4 elementary stream id of the stream to process +\param dsi decoder specific info data +\param dsi_len length of the decoder specific info data +\param dsi_enh enhancement layer decoder specific info data +\param dsi_enh_len length of enhancement layer decoder specific info data +\param KMS_URI URI of the Key Management System +\param width video width +\param height video height +\param tw text window width +\param th text window height +\param tx text window horizontal offset +\param ty text window vertical offset +\param tl text window z-index +\param for_rtsp if GF_TRUE, produces the SDP for an RTSP describe (no port info) +\param out_sdp_buffer location to the SDP buffer to allocate and fill +\return error if any + */ +GF_Err gf_rtp_streamer_append_sdp_extended(GF_RTPStreamer *rtp, u16 ESID, const u8 *dsi, u32 dsi_len, const u8 *dsi_enh, u32 dsi_enh_len, char *KMS_URI, u32 width, u32 height, u32 tw, u32 th, s32 tx, s32 ty, s16 tl, Bool for_rtsp, char **out_sdp_buffer); + +/*! sends a full Access Unit over RTP +\param rtp the target RTP streamer +\param data AU payload +\param size AU payload size +\param cts composition timestamp in timeScale unit +\param dts decoding timestamp in timeScale unit +\param is_rap indicates if the AU is a random access +\return error if any +*/ +GF_Err gf_rtp_streamer_send_au(GF_RTPStreamer *rtp, u8 *data, u32 size, u64 cts, u64 dts, Bool is_rap); + +/*! sends a full Access Unit over RTP +\param rtp the target RTP streamer +\param data AU payload +\param size AU payload size +\param cts composition timestamp in timeScale unit +\param dts decoding timestamp in timeScale unit +\param is_rap indicates if the AU is a random access +\param inc_au_sn increments the AU sequence number by the given value before packetizing (used for MPEG-4 systems carousel) +\return error if any +*/ +GF_Err gf_rtp_streamer_send_au_with_sn(GF_RTPStreamer *rtp, u8 *data, u32 size, u64 cts, u64 dts, Bool is_rap, u32 inc_au_sn); + +/*! sends a full or partial Access Unit over RTP +\param streamer the target RTP streamer +\param data AU payload +\param size AU payload size +\param fullsize the size of the complete AU +\param cts composition timestamp in timeScale unit +\param dts decoding timestamp in timeScale unit +\param is_rap indicates if the AU is a random access +\param au_start indicates if this chunk is the start of the AU +\param au_end indicates if this chunk is the end of the AU +\param au_sn indicates the AU sequence number +\param duration indicates the AU duration +\param sampleDescriptionIndex indicates the ISOBMFF sampleDescriptionIndex for this AU (needed for 3GPP timed text) +\return error if any +*/ +GF_Err gf_rtp_streamer_send_data(GF_RTPStreamer *streamer, u8 *data, u32 size, u32 fullsize, u64 cts, u64 dts, Bool is_rap, Bool au_start, Bool au_end, u32 au_sn, u32 duration, u32 sampleDescriptionIndex); + +/*! formats a generic SDP header +\param app_name application name, may be NULL +\param ip_dest destination IP address, shall NOT be NULL +\param session_name session name, may be NULL +\param iod64 base64 encoded MPEG-4 IOD, may be NULL +\return the SDP header - shall be destroyed by caller +*/ char *gf_rtp_streamer_format_sdp_header(char *app_name, char *ip_dest, char *session_name, char *iod64); +/*! disables RTCP keepalive +\param streamer the target RTP streamer +*/ void gf_rtp_streamer_disable_auto_rtcp(GF_RTPStreamer *streamer); +/*! sends RTCP bye packet +\param streamer the target RTP streamer +\return error if any +*/ +GF_Err gf_rtp_streamer_send_bye(GF_RTPStreamer *streamer); + +/*! sends RTCP server report +\param streamer the target RTP streamer +\param force_ts if GF_TRUE, forces using the indicatet ts +\param rtp_ts the force RTP timestamp to use +\param force_ntp_type if 0, computes NTP while sending. If 1 or 2, uses ntp_sec and ntp_frac for report. If 2, forces sending reports right away +\param ntp_sec NTP seconds +\param ntp_frac NTP fractional part +\return error if any +*/ GF_Err gf_rtp_streamer_send_rtcp(GF_RTPStreamer *streamer, Bool force_ts, u32 rtp_ts, u32 force_ntp_type, u32 ntp_sec, u32 ntp_frac); +/*! gets associated payload type +\param streamer the target RTP streamer +\return the payload type +*/ u8 gf_rtp_streamer_get_payload_type(GF_RTPStreamer *streamer); +/*! initializes the RTP channel for RTSP setup +\param streamer the target RTP streamer +\param path_mtu MTU path size in bytes +\param tr the RTSP transport description +\param ifce_addr IP address of network interface to use +\return error if any +*/ +GF_Err gf_rtp_streamer_init_rtsp(GF_RTPStreamer *streamer, u32 path_mtu, GF_RTSPTransport *tr, const char *ifce_addr); + +/*! gets the sequence number of the next RTP packet to be sent +\param streamer the target RTP streamer +\return the sequence number +*/ +u16 gf_rtp_streamer_get_next_rtp_sn(GF_RTPStreamer *streamer); + +/*! sets callback functions for RTP over RTSP sending +\param streamer the target RTP streamer +\param RTP_TCPCallback the callback function +\param cbk1 first opaque data passed to callback function +\param cbk2 second opaque data passed to callback function +\return error if any +*/ +GF_Err gf_rtp_streamer_set_interleave_callbacks(GF_RTPStreamer *streamer, GF_Err (*RTP_TCPCallback)(void *cbk1, void *cbk2, Bool is_rtcp, u8 *pck, u32 pck_size), void *cbk1, void *cbk2); + /*! @} */ #ifdef __cplusplus diff --git a/include/gpac/scene_engine.h b/include/gpac/scene_engine.h index 6a60db1..d4084bc 100644 --- a/include/gpac/scene_engine.h +++ b/include/gpac/scene_engine.h @@ -2,7 +2,7 @@ * GPAC Multimedia Framework * * Authors: Cyril Concolato - Jean le Feuvre - * Copyright (c) Telecom ParisTech 2005-2012 + * Copyright (c) Telecom ParisTech 2005-2019 * All rights reserved * * This file is part of GPAC @@ -33,182 +33,135 @@ extern "C" { /*! - * \file <gpac/scene_engine.h> - * \brief Live scene encoding engine with RAP generation support. - */ +\file <gpac/scene_engine.h> +\brief Live scene encoding engine with RAP generation support. +*/ /*! - * \addtogroup seng Scene Engine - * \ingroup scene_grp - * \brief Live scene encoding engine with RAP generation support. - * - *This section documents the live scene encoding tools of GPAC. - * @{ +\addtogroup seng Scene Engine +\ingroup scene_grp +\brief Live scene encoding engine with RAP generation support. + +This section documents the live scene encoding tools of GPAC. + +@{ */ #include <gpac/scene_manager.h> #ifndef GPAC_DISABLE_SENG +/*! scene encoding engine object*/ typedef struct __tag_scene_engine GF_SceneEngine; - -typedef void (*gf_seng_callback)(void *udta, u16 ESID, char *data, u32 size, u64 ts); - -/** - * \param calling_object is the calling object on which call back will be called - * \param inputContext is the name of a scene file (bt, xmt or mp4) to initialize the coding context - * \param load_type is the prefered loader type for the content (e.g. SVG vs DIMS) - * \param dump_path is the path where scenes are dumped - * \param embed_resources indicates if images and scripts should be encoded inlined with the content - * - * must be called only one time (by process calling the DLL) before other calls - */ +/*! callback function prototype for scene engine*/ +typedef void (*gf_seng_callback)(void *udta, u16 ESID, u8 *data, u32 size, u64 ts); + +/*! creates a scene engine +\param calling_object is the calling object on which call back will be called +\param inputContext is the name of a scene file (bt, xmt or mp4) to initialize the coding context +\param load_type is the preferred loader type for the content (e.g. SVG vs DIMS) +\param dump_path is the path where scenes are dumped +\param embed_resources indicates if images and scripts should be encoded inlined with the content +\return e scene engine object +*/ GF_SceneEngine *gf_seng_init(void *calling_object, char *inputContext, u32 load_type, char *dump_path, Bool embed_resources); -/** - * \param calling_object is the calling object on which call back will be called - * \param inputContext is an UTF-8 scene description (with or without IOD) in BT or XMT-A format - * \param load_type is the prefered loader type for the content (e.g. SVG vs DIMS) - * \param width width of scene if no IOD is given in the context. - * \param height height of scene if no IOD is given in the context. - * \param usePixelMetrics metrics system used in the scene, if no IOD is given in the context. - * \param dump_path the path where scenes are dumped - * - * must be called only one time (by process calling the DLL) before other calls - */ -GF_SceneEngine *gf_seng_init_from_string(void *calling_object, char *inputContext, u32 load_type, u32 width, u32 height, Bool usePixelMetrics, char *dump_path); - - -/** - * \param calling_object the calling object on which call back will be called - * \param ctx an already loaded scene manager - * \param dump_path the path where scenes are dumped - * - * must be called only one time (by process calling the DLL) before other calls - */ -GF_SceneEngine *gf_seng_init_from_context(void *calling_object, GF_SceneManager *ctx, char *dump_path); - -/** - * \param seng pointer to the GF_SceneEngine returned by gf_seng_init() - * - * must be called after gf_seng_init() - */ +/*! get the number of streams in the scene +\param seng the target scene engine +\return the number of streams +*/ u32 gf_seng_get_stream_count(GF_SceneEngine *seng); -/** - * \param seng pointer to the GF_SceneEngine returned by gf_seng_init() - * \param ESID ID of the stream - * \param carousel_period pointer to store the carousel_period - * \param aggregate_on_es_id pointer to store the target carousel stream ID - * - * must be called after gf_seng_init() +/*! gets carousel nformation for a stream +\param seng the target scene engine +\param ESID ID of the stream to query +\param carousel_period set to the carousel_period in millisenconds +\param aggregate_on_es_id set to the target carousel stream ID +\return error if any */ GF_Err gf_seng_get_stream_carousel_info(GF_SceneEngine *seng, u16 ESID, u32 *carousel_period, u16 *aggregate_on_es_id); -/** - * \param seng pointer to the GF_SceneEngine returned by gf_seng_init() - * \param idx stream index - * \param ESID pointer to the stream ID - * \param config pointer to the encoded BIFS config (memory is not allocated) - * \param config_len length of the buffer - * \param streamType pointer to get stream type - * \param objectType pointer to get object type - * \param timeScale pointer to get time scale - * - * must be called after gf_seng_init() - */ -GF_Err gf_seng_get_stream_config(GF_SceneEngine *seng, u32 idx, u16 *ESID, char ** const config, u32 *config_len, u32 *streamType, u32 *objectType, u32 *timeScale); - -/** - * Encodes the AU context which is not encoded when calling BENC_EncodeAUFromString/File - * Should be called after Aggregate. - * - * \param seng pointer to the GF_SceneEngine returned by gf_seng_init() - * \param callback pointer on a callback function to get the result of the coding the AU using the current context - * - */ +/*! gets the stream decoder configuration +\param seng the target scene engine +\param idx stream index +\param ESID set to the stream ID +\param config set to the encoded BIFS config (memory is not allocated) +\param config_len length of the buffer +\param streamType pointer to get stream type +\param objectType pointer to get object type +\param timeScale pointer to get time scale +\return error if any +*/ +GF_Err gf_seng_get_stream_config(GF_SceneEngine *seng, u32 idx, u16 *ESID, const u8 **config, u32 *config_len, u32 *streamType, u32 *objectType, u32 *timeScale); + +/*! Encodes the AU context which is not encoded when calling BENC_EncodeAUFromString/File. This should be called after Aggregate. +\param seng the target scene engine +\param callback pointer on a callback function to get the result of the coding the AU using the current context +\return error if any +*/ GF_Err gf_seng_encode_context(GF_SceneEngine *seng, gf_seng_callback callback); -/** - * \param seng pointer to the GF_SceneEngine returned by gf_seng_init() - * \param ESID target streams when no indication is present in the file (eg, no atES_ID ) - * \param disable_aggregation - * \param auFile name of a file containing a description for an access unit (BT or XMT) - * \param callback pointer on a callback function to get the result of the coding the AU using the current context - * - */ +/*! Encodes an AU from file +\param seng the target scene engine +\param ESID target streams when no indication is present in the file (eg, no atES_ID ) +\param disable_aggregation do not aggregate the access unit on its target stream +\param auFile name of a file containing a description for an access unit (BT or XMT) +\param callback pointer on a callback function to get the result of the coding the AU using the current context +\return error if any +*/ GF_Err gf_seng_encode_from_file(GF_SceneEngine *seng, u16 ESID, Bool disable_aggregation, char *auFile, gf_seng_callback callback); -/** - * \param seng pointer to the GF_SceneEngine returned by gf_seng_init() - * \param ESID target streams when no indication is present in the file (eg, no atES_ID ) - * \param disable_aggregation - * \param auString a char string to encode (must one or several complete nodes in BT - * \param callback pointer on a callback function to get the result of the coding the AU using the current context - * - */ +/*! Encodes an AU from string +\param seng the target scene engine +\param ESID target streams when no indication is present in the file (eg, no atES_ID ) +\param disable_aggregation +\param auString a char string to encode (must one or several complete nodes in BT +\param callback pointer on a callback function to get the result of the coding the AU using the current context +\return error if any +*/ GF_Err gf_seng_encode_from_string(GF_SceneEngine *seng, u16 ESID, Bool disable_aggregation, char *auString, gf_seng_callback callback); -/** - * \param seng pointer to the GF_SceneEngine returned by gf_seng_init() - * \param ESID indicates the stream to which these commands apply (0 if first scene stream) - * \param disable_aggregation - * \param time - * \param commands the list of commands to encode - * \param callback pointer on a callback function to get the result of the coding the AU using the current context - * - */ -GF_Err gf_seng_encode_from_commands(GF_SceneEngine *seng, u16 ESID, Bool disable_aggregation, u32 time, GF_List *commands, gf_seng_callback callback); -/** - * \param seng pointer to the GF_SceneEngine returned by gf_seng_init() - * \param ctxFileName name of the file to save the current state of the BIFS scene to - * - * save the current context of the seng. - * if you want to save an aggregate context, use BENC_AggregateCurrentContext before - * - */ +/*! saves the live context to a file. Use BENC_AggregateCurrentContext before to save an aggregated context. +\param seng the target scene engine +\param ctxFileName name of the file to save the current state of the BIFS scene to +\return error if any +*/ GF_Err gf_seng_save_context(GF_SceneEngine *seng, char *ctxFileName); -/** - * \param seng pointer to the GF_SceneEngine returned by gf_seng_init() - * \param ESID stream ID - * \param onESID set stream aggragation on to the specified stream, or off if onESID is 0 - * - * marks the stream as carrying its own "rap" in the first AU of the stream - */ +/*! marks the stream as carrying its own "rap" in the first AU of the stream +\param seng the target scene engine +\param ESID stream ID +\param onESID set stream aggragation on to the specified stream, or off if onESID is 0 +\return error if any +*/ GF_Err gf_seng_enable_aggregation(GF_SceneEngine *seng, u16 ESID, u16 onESID); -/** - * \param seng pointer to the GF_SceneEngine returned by gf_seng_init() - * \param ESID stream ID - * - * aggregates the current context of the seng, creates a scene replace - * if ESID is specified, only aggregate commands for this stream - */ +/*! aggregates the current context of the seng, creates a scene replace +\param seng the target scene engine +\param ESID stream ID. If not 0, only aggregate commands for this stream; otherwise aggregates for the all streams +\return error if any +*/ GF_Err gf_seng_aggregate_context(GF_SceneEngine *seng, u16 ESID); -/** - * \param seng pointer to the GF_SceneEngine returned by gf_seng_init() - * - * release the memory used by this seng, no more call on the seng should happen after this - * - */ +/*! destroys the scene engine +\param seng the target scene engine +*/ void gf_seng_terminate(GF_SceneEngine *seng); -/** - * \param seng pointer to the GF_SceneEngine returned by gf_seng_init() - * - * encodes the IOD for this BIFS Engine into Base64 - * +/*! encodes the IOD for this scene engine into Base64 +\param seng the target scene engine +\return the base64 encoded IOD, shall be freed by the caller */ char *gf_seng_get_base64_iod(GF_SceneEngine *seng); +/*! returns the IOD for a scene engine +\param seng the target scene engine +\return the IOD object - shall be destroyed by caller +*/ GF_Descriptor *gf_seng_get_iod(GF_SceneEngine *seng); -GF_Err gf_seng_dump_rap_on(GF_SceneEngine *seng, Bool dump_rap); - #endif /*GPAC_DISABLE_SENG*/ /*! @} */ diff --git a/include/gpac/scene_manager.h b/include/gpac/scene_manager.h index 2a057ee..5456292 100644 --- a/include/gpac/scene_manager.h +++ b/include/gpac/scene_manager.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / Authoring Tools sub-project @@ -33,17 +33,18 @@ extern "C" { /*! - * \file <gpac/scene_manager.h> - * \brief Scene management for importing/encoding of BIFS, XMT, LASeR scenes - */ +\file <gpac/scene_manager.h> +\brief Scene management for importing/encoding of BIFS, XMT, LASeR scenes +*/ /*! - * \addtogroup smgr Scene Manager - * \ingroup scene_grp - * \brief Scene management for importing/encoding of BIFS, XMT, LASeR scenes. - * - *This section documents the Scene manager used for importing/encoding of BIFS, XMT, LASeR scenes. - * @{ +\addtogroup smgr Scene Manager +\ingroup scene_grp +\brief Scene management for importing/encoding of BIFS, XMT, LASeR scenes. + +This section documents the Scene manager used for importing/encoding of BIFS, XMT, LASeR scenes. + +@{ */ #include <gpac/isomedia.h> @@ -55,13 +56,17 @@ extern "C" { */ -/*NDT check - return 1 if node belongs to given NDT. Handles proto, and assumes undefined nodes -always belong to the desired NDT*/ +/*! Node data type check. This handles prototype nodes, and assumes undefined nodes always belong to the desired NDT +\param node the node to check +\param NDTType the parent NDT type +\return GF_TRUE if node belongs to given NDT. +*/ Bool gf_node_in_table(GF_Node *node, u32 NDTType); +/*! Scene manager AU flags*/ enum { - /*AU is RAP - random access indication - may be overriden by encoder*/ + /*AU is RAP - random access indication - may be overridden by encoder*/ GF_SM_AU_RAP = 1, /*AU will not be aggregated (only used by scene engine)*/ GF_SM_AU_NOT_AGGREGATED = 1<<1, @@ -71,7 +76,7 @@ enum GF_SM_AU_CAROUSEL = 1<<3 }; -/*generic systems access unit context*/ +/*! Generic systems access unit context*/ typedef struct { /*AU timing in TimeStampResolution*/ @@ -87,7 +92,7 @@ typedef struct struct _stream_context *owner; } GF_AUContext; -/*generic stream context*/ +/*! Generic stream context*/ typedef struct _stream_context { /*ESID of stream, or 0 if unknown in which case it is automatically updated at encode stage*/ @@ -97,7 +102,7 @@ typedef struct _stream_context /*stream type - used as a hint, the encoder(s) may override it*/ u8 streamType; - u8 objectType; + u32 codec_id; u32 timeScale; GF_List *AUs; @@ -107,7 +112,7 @@ typedef struct _stream_context Bool in_root_od; /*number of previous AUs (used in live scene encoder only)*/ u32 current_au_count; - char *dec_cfg; + u8 *dec_cfg; u32 dec_cfg_len; /*time offset when exporting (dumping), max AU time created when importing*/ @@ -118,7 +123,7 @@ typedef struct _stream_context Bool disable_aggregation; } GF_StreamContext; -/*generic presentation context*/ +/*! Generic presentation context*/ typedef struct { /*the one and only scene graph used by the scene manager.*/ @@ -134,49 +139,69 @@ typedef struct Bool is_pixel_metrics; /*BIFS encoding - these is needed for: - - protos in protos which define subscene graph, hence seperate namespace, but are coded with the same IDs + - protos in protos which define subscene graph, hence separate namespace, but are coded with the same IDs - route insertions which are not tracked by the scene graph we could do this by hand (counting protos & route insert) but let's be lazy */ u32 max_node_id, max_route_id, max_proto_id; } GF_SceneManager; -/*scene manager constructor - @scene_graph: scene graph used by the manager. */ +/*! scene manager constructor +\param scene_graph scene graph used by the manager +\return a new scene manager object*/ GF_SceneManager *gf_sm_new(GF_SceneGraph *scene_graph); -/*scene manager destructor - does not destroy the attached scene graph*/ -void gf_sm_del(GF_SceneManager *ctx); -/*retrive or create a stream context in the presentation context -WARNING: if a stream with the same streamType and no ESID already exists in the context, -it is assigned the requested ES_ID - this is needed to solve base layer*/ -GF_StreamContext *gf_sm_stream_new(GF_SceneManager *ctx, u16 ES_ID, u8 streamType, u8 objectType); -/*removes and destroy stream context from presentation context*/ -void gf_sm_stream_del(GF_SceneManager *ctx, GF_StreamContext *sc); -/*locate a stream based on its id*/ -GF_StreamContext *gf_sm_stream_find(GF_SceneManager *ctx, u16 ES_ID); -/*create a new AU context in the given stream context*/ +/*! scene manager destructor - does not destroy the attached scene graph +\param sman the target scene manager*/ +void gf_sm_del(GF_SceneManager *sman); +/*! retrive or create a stream context in the presentation context +\warning if a stream with the same streamType and no ESID already exists in the context, it is assigned the requested ES_ID - this is needed to solve base layer +\param sman the target scene manager +\param ES_ID ID of the new stream +\param streamType stream type of the new stream +\param codec_id codec ID of the new stream +\return a new scene manager stream context +*/ +GF_StreamContext *gf_sm_stream_new(GF_SceneManager *sman, u16 ES_ID, u8 streamType, u32 codec_id); +/*! locates a stream based on its id +\param sman the target scene manager +\param ES_ID ID of the new stream +\return the stream context or NULL if not found +*/ +GF_StreamContext *gf_sm_stream_find(GF_SceneManager *sman, u16 ES_ID); +/*! create a new AU context in the given stream context +\param stream the traget stream context +\param timing the timing in stream timescale +\param time_ms the timing in ms +\param isRap indicates of the AU is a RAP or not +\return a new scene manager AU context +*/ GF_AUContext *gf_sm_stream_au_new(GF_StreamContext *stream, u64 timing, Double time_ms, Bool isRap); +/*! locates a MuxInfo descriptor in an EDD +\param src the target ESD +\return a mux info descriptor or NULL if none found*/ GF_MuxInfo *gf_sm_get_mux_info(GF_ESD *src); -/*reset the context: -- purge all access units on all streams -- destroy root OD +/*! applies all commands in all streams (only BIFS for now): the context manager will only have maximum one AU per +stream, this AU being a random access for the stream + +\param sman the target scene manager +\param ESID if set, aggregation is only performed on the given stream, otherwise on all streams +\return error if any */ -void gf_sm_reset(GF_SceneManager *ctx); +GF_Err gf_sm_aggregate(GF_SceneManager *sman, u16 ESID); -/*applies all commands in all streams (only BIFS for now): the context manager will only have maximum one AU per -stream, this AU being a random access for the stream -if @ESID is set, aggregation is only performed on the given stream*/ -GF_Err gf_sm_aggregate(GF_SceneManager *ctx, u16 ESID); +/*! translates SRT/SUB (TTXT not supported) source into BIFS command stream source -/*translates SRT/SUB (TTXT not supported) source into BIFS command stream source - @src: GF_ESD of new stream (MUST be created before to store TS resolution) - @mux: GF_MuxInfo of src stream - shall contain a valid file, and at least the textNode member set +\param sman the target scene manager +\param src GF_ESD of new stream (MUST be created before to store TS resolution) +\param mux GF_MuxInfo of src stream - shall contain a valid file, and at least the textNode member set +\return error if any */ -GF_Err gf_sm_import_bifs_subtitle(GF_SceneManager *ctx, GF_ESD *src, GF_MuxInfo *mux); +GF_Err gf_sm_import_bifs_subtitle(GF_SceneManager *sman, GF_ESD *src, GF_MuxInfo *mux); -/*SWF to MPEG-4 flags*/ +/*! SWF to MPEG-4 flags*/ enum { /*all data in dictionary is in first frame*/ @@ -204,7 +229,7 @@ enum GF_SM_SWF_USE_SVG = (1<<11), }; -/*general loader flags*/ +/*! general loader flags*/ enum { /*if set, always load MPEG-4 nodes, otherwise X3D versions are used for vrml/x3d*/ @@ -223,10 +248,10 @@ enum /* in this mode, each root svg tag will be interpreted as a REPLACE SCENE */ GF_SM_LOAD_CONTEXT_STREAMING = 1<<3, /*indicates that external resources in the content should be embedded as if possible*/ - GF_SM_LOAD_EMBEDS_RES = 1<<4, + GF_SM_LOAD_EMBEDS_RES = 1<<4 }; -/*loader type, usually detected based on file ext*/ +/*! loader type, usually detected based on file ext*/ typedef enum { GF_SM_LOAD_BT = 1, /*BT loader*/ @@ -240,78 +265,87 @@ typedef enum GF_SM_LOAD_SWF, /*SWF->MPEG-4 converter*/ GF_SM_LOAD_QT, /*MOV->MPEG-4 converter (only cubic QTVR for now)*/ GF_SM_LOAD_MP4, /*MP4 memory loader*/ - GF_SM_LOAD_XBL } GF_SceneManager_LoadType; -typedef struct __scene_loader GF_SceneLoader; - -struct __scene_loader +/*! Scenegraph loader*/ +typedef struct __scene_loader { - /*loader type, one of the above value. If not set, detected based on file extension*/ + /*! loader type, one of the above value. If not set, detected based on file extension*/ GF_SceneManager_LoadType type; - /*scene graph worked on - may be NULL if ctx is present*/ + /*! scene graph worked on - may be NULL if ctx is present*/ GF_SceneGraph *scene_graph; + /*! inline scene*/ + struct _gf_scene *is; - struct _scene *is; - - /*context manager to load (MUST BE RESETED BEFORE if needed) - may be NULL for loaders not using commands, + /*! context manager to load (MUST BE RESETED BEFORE if needed) - may be NULL for loaders not using commands, in which case the graph will be directly updated*/ GF_SceneManager *ctx; - /*file to import except IsoMedia files*/ + /*! file to import except IsoMedia files*/ const char *fileName; - //original URL for the file or NULL if same as fileName + //! original URL for the file or NULL if same as fileName const char *src_url; #ifndef GPAC_DISABLE_ISOM - /*IsoMedia file to import (we need to be able to load from an opened file for scene stats)*/ + /*! IsoMedia file to import (we need to be able to load from an opened file for scene stats)*/ GF_ISOFile *isom; #endif - /*swf import flags*/ + /*! swf import flags*/ u32 swf_import_flags; - /*swf flatten limit: angle limit below which 2 lines are considered as aligned, + /*! swf flatten limit: angle limit below which 2 lines are considered as aligned, in which case the lines are merged as one. If 0, no flattening happens*/ Float swf_flatten_limit; - /*swf extraction path: if set, swf media (mp3, jpeg) are extracted to this path. If not set + /*! swf extraction path: if set, swf media (mp3, jpeg) are extracted to this path. If not set media are extracted to original file directory*/ const char *localPath; - /* carrying svgOutFile when the loader is used by a SceneDumper */ + /*! carrying svgOutFile when the loader is used by a SceneDumper */ const char *svgOutFile; - /*loader flags*/ + /*! loader flags*/ u32 flags; + /*! force stream ID*/ u16 force_es_id; +//! @cond Doxygen_Suppress /*private to loader*/ void *loader_priv; - GF_Err (*process)(GF_SceneLoader *loader); - void (*done)(GF_SceneLoader *loader); - GF_Err (*parse_string)(GF_SceneLoader *loader, const char *str); - GF_Err (*suspend)(GF_SceneLoader *loader, Bool suspend); -}; + GF_Err (*process)(struct __scene_loader *loader); + void (*done)(struct __scene_loader *loader); + GF_Err (*parse_string)(struct __scene_loader *loader, const char *str); + GF_Err (*suspend)(struct __scene_loader *loader, Bool suspend); +//! @endcond + +} GF_SceneLoader; -/*initializes the context loader - this will load any IOD and the first frame of the main scene*/ -GF_Err gf_sm_load_init(GF_SceneLoader *load); -/*completely loads context*/ -GF_Err gf_sm_load_run(GF_SceneLoader *load); -/*terminates the context loader*/ -void gf_sm_load_done(GF_SceneLoader *load); -/*suspends/resume context loading*/ -GF_Err gf_sm_load_suspend(GF_SceneLoader *load, Bool suspend); - -/*parses memory scene (any textural format) into the context -!! THE LOADER TYPE MUST BE ASSIGNED (BT/WRL/XMT/X3D/SVG only) !! -The string MUST be at least 4 bytes long in order to detect BOM (unicode encoding). -The string can ba either UTF-8 or UTF-16 data -if clean_at_end is set, associated parser is destroyed. Otherwise, a call to gf_sm_load_done must be done -to clean ressources (needed for SAX progressive loading) +/*! initializes the context loader - this will load any IOD and the first frame of the main scene +\param sload a preallocated, uninitialized loader +\return error if any */ -GF_Err gf_sm_load_string(GF_SceneLoader *load, const char *str, Bool clean_at_end); +GF_Err gf_sm_load_init(GF_SceneLoader *sload); +/*! completely loads context +\param sload the target scene loader +\return error if any +*/ +GF_Err gf_sm_load_run(GF_SceneLoader *sload); +/*! terminates the context loader +\param sload the target scene loader +*/ +void gf_sm_load_done(GF_SceneLoader *sload); + +/*! parses memory scene (any textural format) into the context +\warning LOADER TYPE MUST BE ASSIGNED (BT/WRL/XMT/X3D/SVG only) + +\param sload the target scene loader +\param str the string to load; MUST be at least 4 bytes long in order to detect BOM (unicode encoding); can be either UTF-8 or UTF-16 data +\param clean_at_end if GF_TRUE, associated parser is terminated. Otherwise, a call to gf_sm_load_done must be done to clean ressources (needed for SAX progressive loading) +\return error if any +*/ +GF_Err gf_sm_load_string(GF_SceneLoader *sload, const char *str, Bool clean_at_end); #ifndef GPAC_DISABLE_SCENE_ENCODER -/*encoding flags*/ +/*! encoding flags*/ enum { /*if flag set, DEF names are encoded*/ @@ -322,6 +356,7 @@ enum GF_SM_ENCODE_RAP_SHADOW = 4, }; +/*! Scenegraph Encoder options*/ typedef struct { /*encoding flags*/ @@ -345,11 +380,13 @@ typedef struct const char *src_url; } GF_SMEncodeOptions; -/* -encodes scene context into @mp4. -if @log is set, generates BIFS encoder log file +/*! encodes scene context into a destination MP4 file +\param sman the target scene manager +\param mp4 the destination ISOBMFF file +\param opt the encoding options +\return error if any */ -GF_Err gf_sm_encode_to_file(GF_SceneManager *ctx, GF_ISOFile *mp4, GF_SMEncodeOptions *opt); +GF_Err gf_sm_encode_to_file(GF_SceneManager *sman, GF_ISOFile *mp4, GF_SMEncodeOptions *opt); #endif /*GPAC_DISABLE_SCENE_ENCODER*/ @@ -357,7 +394,7 @@ GF_Err gf_sm_encode_to_file(GF_SceneManager *ctx, GF_ISOFile *mp4, GF_SMEncodeOp /*Dumping tools*/ #ifndef GPAC_DISABLE_SCENE_DUMP -/*scene dump mode*/ +/*! Scenegraph dump mode*/ typedef enum { /*BT*/ @@ -384,40 +421,56 @@ typedef enum GF_SM_DUMP_NONE } GF_SceneDumpFormat; -/*dumps scene context to a given format -@rad_name: file name & loc without extension - if NULL dump will happen in stdout -@is_final_name: if set, no extension is added to the filename -@dump_mode: one of the above*/ -GF_Err gf_sm_dump(GF_SceneManager *ctx, char *rad_name, Bool is_final_name, GF_SceneDumpFormat dump_mode); +/*! dumps scene context to a given format +\param sman the target scene manager +\param rad_name file name & loc without extension - if NULL dump will happen in stdout +\param is_final_name if set, no extension is added to the filename +\param dump_mode scene dum mode +\return error if any +*/ +GF_Err gf_sm_dump(GF_SceneManager *sman, char *rad_name, Bool is_final_name, GF_SceneDumpFormat dump_mode); +/*! Scenegraph dumper*/ typedef struct _scenedump GF_SceneDumper; -/*create a scene dumper -@graph: scene graph being dumped -@rad_name: file radical (NULL for stdout) - if not NULL MUST BE GF_MAX_PATH length -@is_final_name: if set, rad_name is the final name (no extension added) -@indent_char: indent format -@dump_mode: if set, dumps in XML format otherwise regular text -returns NULL if can't create a file +/*! creates a scene dumper +\param graph scene graph being dumped +\param rad_name file radical (NULL for stdout) - if not NULL MUST BE GF_MAX_PATH length +\param is_final_name if set, rad_name is the final name (no extension added) +\param indent_char indent format +\param dump_mode if set, dumps in XML format otherwise regular text +\return a new scene dumper object, or NULL if error +*/ +GF_SceneDumper *gf_sm_dumper_new(GF_SceneGraph *graph, char *rad_name, Bool is_final_name, char indent_char, GF_SceneDumpFormat dump_mode); +/*! destroys a scene dumper +\param sdump the target scene dumper*/ +void gf_sm_dumper_del(GF_SceneDumper *sdump); +/*! adds extra graph to scene dumper (all graphs will be dumped) +\param sdump the target scene dumper +\param extra scene graph to be added */ -GF_SceneDumper *gf_sm_dumper_new(GF_SceneGraph *graph, char *_rad_name, Bool is_final_name, char indent_char, GF_SceneDumpFormat dump_mode); void gf_sm_dumper_set_extra_graph(GF_SceneDumper *sdump, GF_SceneGraph *extra); -void gf_sm_dumper_del(GF_SceneDumper *bd); -/*gets a pointer to the filename (rad+ext) of the dumped file -returns null if no file has been dumped +/*! gets a pointer to the filename (rad+ext) of the dumped file +\param sdump the target scene dumper +\return null if no file has been dumped */ -char *gf_sm_dump_get_name(GF_SceneDumper *bd); - -/*dumps commands list -@indent: indent to use -@skip_replace_scene_header: if set and dumping in BT mode, the initial REPLACE SCENE header is skipped +char *gf_sm_dump_get_name(GF_SceneDumper *sdump); + +/*! dumps commands list +\param sdump the target scene dumper +\param comList list of commands to dump +\param indent indent to use +\param skip_first_replace if GF_TRUE and dumping in BT mode, the initial REPLACE SCENE header is skipped +\return error if any */ GF_Err gf_sm_dump_command_list(GF_SceneDumper *sdump, GF_List *comList, u32 indent, Bool skip_first_replace); -/*dumps complete graph - -@skip_proto: proto declarations are skipped -@skip_routes: routes are not dumped +/*! dumps complete graph +\param sdump the target scene dumper +\param skip_proto proto declarations are skipped +\param skip_routes routes are not dumped +\return error if any */ GF_Err gf_sm_dump_graph(GF_SceneDumper *sdump, Bool skip_proto, Bool skip_routes); @@ -425,9 +478,7 @@ GF_Err gf_sm_dump_graph(GF_SceneDumper *sdump, Bool skip_proto, Bool skip_routes #ifndef GPAC_DISABLE_SCENE_STATS -/*stat object - to refine :)*/ - -/*store nodes or proto stats*/ +/*! Node statistics object*/ typedef struct { /*node type or protoID*/ @@ -441,6 +492,7 @@ typedef struct u32 nb_del; } GF_NodeStats; +/*! Scene statistics object*/ typedef struct _scenestat { GF_List *node_stats; @@ -479,26 +531,44 @@ typedef struct _scenestat GF_StreamContext *base_layer; } GF_SceneStatistics; +/*! Scenegraph statistics manager*/ typedef struct _statman GF_StatManager; -/*creates new stat handler*/ +/*! creates new statitistics manager +\return a new statitistics manager*/ GF_StatManager *gf_sm_stats_new(); -/*deletes stat handler*/ -void gf_sm_stats_del(GF_StatManager *stat); -/*reset statistics*/ -void gf_sm_stats_reset(GF_StatManager *stat); - -/*get statistics - do NOT modify the returned structure*/ -GF_SceneStatistics *gf_sm_stats_get(GF_StatManager *stat); - -/*produces stat report for a complete graph*/ -GF_Err gf_sm_stats_for_graph(GF_StatManager *stat, GF_SceneGraph *sg); +/*! destroys a statitistics manager +\param sstat the target statitistics manager*/ +void gf_sm_stats_del(GF_StatManager *sstat); +/*! resets statistics +\param sstat the target statitistics manager*/ +void gf_sm_stats_reset(GF_StatManager *sstat); + +/*! gets statistics report +\param sstat the target statitistics manager +\return a scene statistic report or NULL if error*/ +const GF_SceneStatistics *gf_sm_stats_get(GF_StatManager *sstat); + +/*! computes statistitics for a complete graph +\param sstat the target statitistics manager +\param sg the scene graph to analyze +\return error if any +*/ +GF_Err gf_sm_stats_for_graph(GF_StatManager *sstat, GF_SceneGraph *sg); -/*produces stat report for the full scene*/ -GF_Err gf_sm_stats_for_scene(GF_StatManager *stat, GF_SceneManager *sm); +/*! computes statistitics for the full scene +\param sstat the target statitistics manager +\param sman the scene manager to analyze +\return error if any +*/ +GF_Err gf_sm_stats_for_scene(GF_StatManager *sstat, GF_SceneManager *sman); -/*produces stat report for the given command*/ -GF_Err gf_sm_stats_for_command(GF_StatManager *stat, GF_Command *com); +/*! computes statistitics for the given command +\param sstat the target statitistics manager +\param com the scene command to analyze +\return error if any +*/ +GF_Err gf_sm_stats_for_command(GF_StatManager *sstat, GF_Command *com); #endif /*GPAC_DISABLE_SCENE_STATS*/ diff --git a/include/gpac/scenegraph.h b/include/gpac/scenegraph.h index 396f86c..3ffef98 100644 --- a/include/gpac/scenegraph.h +++ b/include/gpac/scenegraph.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / Scene Graph sub-project @@ -33,33 +33,33 @@ extern "C" { /*! - * \file <gpac/scenegraph.h> - * \brief Scenegraph used for manipulating scenes (parsing, traversing, cleaning node status, ...) - */ +\file <gpac/scenegraph.h> +\brief Scenegraph used for manipulating scenes (parsing, traversing, cleaning node status, ...) +*/ -/*! \defgroup scene_grp Scene Graph - * \brief Scene graph management. - * +/*! +\addtogroup scene_grp Scene Graph +\brief Scene graph management. */ /*! - * \addtogroup sscene Base Scenegraph - * \ingroup scene_grp - * \brief Scenegraph used for manipulating scenes. - * - *This section documents the Scenegraph used in GPAC for all interactive scenes. - * @{ +\addtogroup sscene Base Scenegraph +\ingroup scene_grp +\brief Scenegraph used for manipulating scenes. + +This section documents the Scenegraph used in GPAC for all interactive scenes. + +@{ */ #include <gpac/list.h> #include <gpac/maths.h> -/* - TAG definitions are static, in order to be able to mix nodes from different standard - in a single scenegraph. These TAGs are only used internally (they do not match any - binary encoding) +/*! Tags of scene graph nodes +TAG definitions are static, in order to be able to mix nodes from different standard in a single scenegraph. +These TAGs are only used internally (they do not match any binary encoding) */ enum { /*undefined node: just the base node class, used for parsing*/ @@ -98,42 +98,21 @@ enum { GF_NODE_RANGE_FIRST_SVG, GF_NODE_RANGE_LAST_SVG = GF_NODE_RANGE_FIRST_SVG+100, - /*range for XBL*/ - GF_NODE_RANGE_FIRST_XBL, - TAG_XBL_bindings = GF_NODE_RANGE_FIRST_XBL, - TAG_XBL_binding, - TAG_XBL_content, - TAG_XBL_children, - TAG_XBL_implementation, - TAG_XBL_constructor, - TAG_XBL_destructor, - TAG_XBL_field, - TAG_XBL_property, - TAG_XBL_getter, - TAG_XBL_setter, - TAG_XBL_method, - TAG_XBL_parameter, - TAG_XBL_body, - TAG_XBL_handlers, - TAG_XBL_handler, - TAG_XBL_resources, - TAG_XBL_stylesheet, - TAG_XBL_image, - GF_NODE_RANGE_LAST_XBL, }; -/*private handler for this library on all nodes*/ +/*! macro for defining base node (apply to all nodes)*/ #define BASE_NODE struct _nodepriv *sgprivate; -/*base node type*/ +/*! base node type*/ typedef struct _base_node { BASE_NODE } GF_Node; -/*child storage - this is not integrated in the base node, because of VRML/MPEG-4 USE: a node +/*! child storage + This is not integrated in the base node, because of VRML/MPEG-4 USE: a node may be present at different places in the tree, hence have different "next" siblings.*/ typedef struct _child_node { @@ -141,118 +120,224 @@ typedef struct _child_node GF_Node *node; } GF_ChildNodeItem; -/*grouping nodes macro : +/*! grouping nodes macro children: list of children SFNodes */ - #define CHILDREN \ struct _child_node *children; +/*! generic parent node*/ typedef struct { BASE_NODE CHILDREN } GF_ParentNode; -/*adds a child to a given container*/ +/*! adds a child to a given container +\param list pointer to target child list +\param n node to add +\return error if any +*/ GF_Err gf_node_list_add_child(GF_ChildNodeItem **list, GF_Node *n); -/*adds a child to a given container, updating last position*/ +/*! adds a child to a given container, updating last position +\param list pointer to target child list +\param n node to add +\param last_child set to position of add child +\return error if any +*/ GF_Err gf_node_list_add_child_last(GF_ChildNodeItem **list, GF_Node *n, GF_ChildNodeItem **last_child); -/*inserts a child to a given container - if pos doesn't match, append the child*/ +/*! inserts a child to a given container - if pos doesn't match, append the child +\param list pointer to target child list +\param n node to insert +\param pos 0-based index at which to insert +\return error if any +*/ GF_Err gf_node_list_insert_child(GF_ChildNodeItem **list, GF_Node *n, u32 pos); -/*removes a child to a given container - return 0 if child not found*/ +/*! removes a child to a given container +\param list pointer to target child list +\param n node to remove +\return GF_TRUE if OK, GF_FALSE if not found +*/ Bool gf_node_list_del_child(GF_ChildNodeItem **list, GF_Node *n); -/*finds a child in a given container, returning its 0-based index if found, -1 otherwise*/ +/*! finds a child in a given container +\param list target child list +\param n node to find +\return 0-based index if found, -1 otherwise +*/ s32 gf_node_list_find_child(GF_ChildNodeItem *list, GF_Node *n); -/*finds a child in a given container given its index, returning the child or NULL if not found -if pos is <0, returns the last child*/ +/*! finds a child in a given container given its index. if pos is <0, returns the last child +\param list target child list +\param pos 0-based index at which to insert +\return the child or NULL if not found +*/ GF_Node *gf_node_list_get_child(GF_ChildNodeItem *list, s32 pos); -/*gets the number of children in a given container*/ +/*! gets the number of children in a given list +\param list target child list +\return the number of children +*/ u32 gf_node_list_get_count(GF_ChildNodeItem *list); -/*deletes node entry at given idx, returning node if found, NULL otherwise*/ +/*! removes node at given idx +\param list pointer to target child list +\param pos 0-based index at which to insert +\return the removed node, or NULL if not found +*/ GF_Node *gf_node_list_del_child_idx(GF_ChildNodeItem **list, u32 pos); -/*tag is set upon creation and cannot be modified*/ -u32 gf_node_get_tag(GF_Node*); -/*set node def - @ID: node ID, !=0 set def node - if a different node with the same ID exists, returns error. -You may change the node ID by recalling the function with a different ID value. You may get a node ID -by calling the gf_sg_get_next_available_node_id function - @defName: optional readable name (script, MPEGJ). To change the name, recall the function with a different name and the same ID +/*! gets tag of node (tag is set upon creation and cannot be modified) +\param n the target node +\return the node tag +*/ +u32 gf_node_get_tag(GF_Node*n); + +/*! set node ID/def +If a different node with the same ID exists, returns error. +You may change the node ID by recalling the function with a different ID value. You may get a node ID by calling \ref gf_sg_get_next_available_node_id + +\param n the target node +\param nodeID ID to set to the node, ignored if 0. +\param nodeDEFName optional readable name (script, MPEGJ). To change the name, recall the function with a different name and the same ID +\return error if any */ GF_Err gf_node_set_id(GF_Node*n, u32 nodeID, const char *nodeDEFName); -/*get def name of the node , NULL if not set*/ -const char *gf_node_get_name(GF_Node*); -/*get def name of the node , or the string representation of the node pointer if not set*/ -const char *gf_node_get_log_name(GF_Node*); -/*get def ID of the node, 0 if node not def*/ -u32 gf_node_get_id(GF_Node*); -/* gets node built-in name (eg 'Appearance', ..) */ -const char *gf_node_get_class_name(GF_Node *Node); +/*! gets def name of the node +\param n the target node +\return node name or NULL if not set +*/ +const char *gf_node_get_name(GF_Node*n); +/*! gets def name of the node, or the string representation of the node pointer if not set +\param n the target node +\return node name or NULL if error +*/ +const char *gf_node_get_log_name(GF_Node*n); +/*! gets def ID of the node +\param n the target node +\return the ID of the node, 0 if node not def +*/ +u32 gf_node_get_id(GF_Node*n); +/*! gets node built-in name (eg 'Appearance', ..) +\param n the target node +\return node class name +*/ +const char *gf_node_get_class_name(GF_Node *n); -u32 gf_sg_node_get_tag_by_class_name(const char *name, u32 xmlns); +/*! unsets the node ID +\param n the target node +\return error if any +*/ +GF_Err gf_node_remove_id(GF_Node *n); -/*unset the node ID*/ -GF_Err gf_node_remove_id(GF_Node *p); +/*! gets user private of node +\param n the target node +\return user data if any, NULL if error or not assigned +*/ +void *gf_node_get_private(GF_Node*n); +/*! sets user private of node +\param n the target node +\param udta user data to assign to the node +*/ +void gf_node_set_private(GF_Node*n, void *udta); -/*get/set user private stack*/ -void *gf_node_get_private(GF_Node*); -void gf_node_set_private(GF_Node*, void *); +/*! node callback function +\param n the target node +\param traverse_state opaque data passed during traversal +\param is_destroy set when the node is about to be destroyed +*/ +typedef void (*gf_sg_node_callback)(GF_Node *n, void *traverse_state, Bool is_destroy); -/*set traversal callback function. If a node has no associated callback, the traversing of the +/*! sets traversal callback function. If a node has no associated callback, the traversing of the graph won't propagate below it. It is the app responsability to setup traversing functions as needed -VRML/MPEG4: Instanciated Protos are handled internally as well as interpolators, valuators and scripts -@is_destroy: set when the node is about to be destroyed +VRML/MPEG4: Instantiated Protos are handled internally as well as interpolators, valuators and scripts +\param n the target node +\param NodeFunction the callback function +\return error if any */ -GF_Err gf_node_set_callback_function(GF_Node *, void (*NodeFunction)(GF_Node *node, void *traverse_state, Bool is_destroy) ); +GF_Err gf_node_set_callback_function(GF_Node *n, gf_sg_node_callback NodeFunction); -/*register a node (DEFed or not), specifying parent if any. +/*! registers a node (DEFed or not), specifying parent if any. A node must be registered whenever used by something (a parent node, a command, whatever) to prevent its destruction (think of it as a reference counting). -NOTE: NODES ARE CREATED WITHOUT BEING REGISTERED +\warning NODES ARE CREATED WITHOUT BEING REGISTERED +\param n the target node +\param parent_node the parent node this node should be registered with +\return error if any */ -GF_Err gf_node_register(GF_Node *node, GF_Node *parent_node); +GF_Err gf_node_register(GF_Node *n, GF_Node *parent_node); -/*unregister a node from parent (node may or not be DEF'ed). Parent may be NULL (DEF root node, commands). +/*! unregister a node from parent (node may or not be DEF'ed). Parent may be NULL (DEF root node, commands). This MUST be called whenever a node is destroyed (removed from a parent node) If this is the last instance of the node, the node is destroyed -NOTE: NODES ARE CREATED WITHOUT BEING REGISTERED, hence they MUST be registered at least once before +\warning NODES ARE CREATED WITHOUT BEING REGISTERED, hence they MUST be registered at least once before being destroyed +\param n the target node +\param parent_node the parent node this node should be unregistered from +\return error if any +*/ +GF_Err gf_node_unregister(GF_Node *n, GF_Node *parent_node); + +/*! unregisters all children in the given list +\param node the target parent node owning the list +\param childrenlist the list of children to unregister */ -GF_Err gf_node_unregister(GF_Node *node, GF_Node *parent_node); -/*deletes all node instances in the given list*/ void gf_node_unregister_children(GF_Node *node, GF_ChildNodeItem *childrenlist); -/*get all parents of the node and replace the old_node by the new node in all parents -Note: if the new node is not DEFed, only the first instance of "old_node" will be replaced, the other ones deleted*/ +/*! gets all parents of the node and replace the old_node by the new node in all parents +\note if the new node is not DEFed, only the first instance of "old_node" will be replaced, the other ones deleted +\param old_node old node to replace +\param new_node new node to replace with +\param updateOrderedGroup if GF_TRUE, update the order field of parent OrderdedGroup nodes +\return error if any +*/ GF_Err gf_node_replace(GF_Node *old_node, GF_Node *new_node, Bool updateOrderedGroup); -/*returns number of instances for this node*/ -u32 gf_node_get_num_instances(GF_Node *node); - +/*! gets the number of node instances +\param n the target node +\return the number of node instances +*/ +u32 gf_node_get_num_instances(GF_Node *n); -/*calls node traverse callback routine on this node*/ -void gf_node_traverse(GF_Node *node, void *udta); -/*allows a node to be re-rendered - by default a node in its render phase will never be retraversed a second time. -Use this function to enable a second traverse for this node while traversing the node*/ -void gf_node_allow_cyclic_traverse(GF_Node *node); -/*sets the cyclic travers flag - returns 0 if flag was already set*/ -Bool gf_node_set_cyclic_traverse_flag(GF_Node *node, Bool on); +/*! calls node traverse callback routine on this node +\param n the target node +\param udta opaque data passed to the node traverse callback +*/ +void gf_node_traverse(GF_Node *n, void *udta); -/*blindly calls traverse callback on all children nodes */ -void gf_node_traverse_children(GF_Node *node, void *renderStack); -/*returns number of parent for this node (parent are kept regardless of DEF state)*/ -u32 gf_node_get_parent_count(GF_Node *node); -/*returns desired parent for this node (parent are kept regardless of DEF state) -idx is 0-based parent index*/ -GF_Node *gf_node_get_parent(GF_Node *node, u32 idx); +/*! allows a node to be re-rendered - by default a node in its render callback will never be retraversed a second time. +Use this function to enable a second traverse for this node while traversing it +\param n the target node +*/ +void gf_node_allow_cyclic_traverse(GF_Node *n); -/*returns 1 if target node is in the subtree below the node, 0 otherwise*/ -Bool gf_node_parent_of(GF_Node *node, GF_Node *target); +/*! sets the cyclic traverse flag +\param n the target node +\param on indicates if cyclic traverse shall be turned on or off +\return GF_FALSE if flag was already set, GF_TRUE otherwise*/ +Bool gf_node_set_cyclic_traverse_flag(GF_Node *n, Bool on); +/*! blindly calls traverse callback on all children nodes +\param n the target node +\param udta opaque data passed to the node traverse callback +*/ +void gf_node_traverse_children(GF_Node *n, void *udta); +/*! get the number of parent for this node (parent are kept regardless of DEF state) +\param n the target node +\return the number of parents*/ +u32 gf_node_get_parent_count(GF_Node *n); +/*! returns desired parent for this node (parent are kept regardless of DEF state) +\param n the target node +\param idx 0-based parent index +\return the parent node, or NULL of error*/ +GF_Node *gf_node_get_parent(GF_Node *n, u32 idx); + +/*! checks if a node belongs to a subtree +\param parent the target parent node +\param target the target node +\return GF_TRUE if target is in the subtree below parent, GF_FALSE otherwise*/ +Bool gf_node_parent_of(GF_Node *parent, GF_Node *target); + +/*! node dirty flags*/ enum { /*flag set whenever a field of the node has been modified*/ @@ -302,101 +387,147 @@ enum GF_SG_SVG_XLINK_HREF_DIRTY = 1<<27, }; -/*set dirty flags. -if @flags is 0, sets the base flags on (GF_SG_NODE_DIRTY). -if @flags is not 0, adds the flags to the node dirty state - -If @invalidate_parents is set, all parent subtrees for this node are marked as GF_SG_CHILD_DIRTY -Note: parent subtree marking aborts if a node in the subtree is already marked with GF_SG_CHILD_DIRTY +/*! sets dirty flags +\param n the target node +\param flags if 0, sets the base flags on (GF_SG_NODE_DIRTY); otherwise, adds the flags to the node dirty state +\param dirty_parents if GF_TRUE, all parent subtrees for this node are marked as GF_SG_CHILD_DIRTY +\note parent subtree marking aborts if a node in the subtree is already marked with GF_SG_CHILD_DIRTY which means tat if you never clean the dirty flags, no propagation will take place */ -void gf_node_dirty_set(GF_Node *node, u32 flags, Bool dirty_parents); +void gf_node_dirty_set(GF_Node *n, u32 flags, Bool dirty_parents); -/*mark all parent subtrees for this node as GF_SG_CHILD_DIRTY -Note: parent subtree marking aborts if a node in the subtree is already marked with GF_SG_CHILD_DIRTY +/*! marks all parent subtrees for this node as GF_SG_CHILD_DIRTY +\note parent subtree marking aborts if a node in the subtree is already marked with GF_SG_CHILD_DIRTY which means that if you never clean the dirty flags, no propagation will take place +\param n the target node */ -void gf_node_dirty_parents(GF_Node *node); +void gf_node_dirty_parents(GF_Node *n); -/*set dirty flag off. It is the user responsability to clear dirty flags -if @flags is 0, all flags are set off -if @flags is not 0, removes the indicated flags from the node dirty state +/*! sets dirty flag off. It is the user responsability to clear dirty flags +\param n the target node +\param flags if 0, all flags are set off; otherwise, removes the indicated flags from the node dirty state */ -void gf_node_dirty_clear(GF_Node *node, u32 flags); +void gf_node_dirty_clear(GF_Node *n, u32 flags); -/*if the node is in a dirty state, resets it and the state of all its children if desired*/ -void gf_node_dirty_reset(GF_Node *node, Bool reset_children); +/*! reset dirty state of a node +\param n the target node +\param reset_children if GF_TRUE and node was dirty, resets the state of all its children +*/ +void gf_node_dirty_reset(GF_Node *n, Bool reset_children); -/*get dirty flag value*/ -u32 gf_node_dirty_get(GF_Node *node); +/*! gets dirty flag value +\param n the target node +\return the dirty fkags +*/ +u32 gf_node_dirty_get(GF_Node *n); -/*Notes on GF_FieldInfo +/*! VRML/BIFS route object*/ +typedef struct _route GF_Route; + + +/*! Node Field/attribute information for VRML/BIFS/SVG +Note: all scene graph implementations should answer node field query with this interface. In case an implementation does not use this: - the implementation shall handle the parent node dirty flag itself most of the time - the implementation shall NOT allow referencing of a graph node in a parent graph node (when inlining content) otherwise the app is guaranteed to crash. -*/ - -typedef struct _route GF_Route; - -/*other fieldTypes may be ignored by implmentation not using VRML/MPEG4 native types*/ +other fieldTypes may be ignored by implmentation not using VRML/MPEG4 native types +*/ typedef struct { - /*0-based index of the field in the node*/ + /*! 0-based index of the field in the node*/ u32 fieldIndex; - /*field type - VRML/MPEG4 types are listed in scenegraph_vrml.h*/ + /*! field type - VRML/MPEG4 types are listed in scenegraph_vrml.h*/ u32 fieldType; - /*far ptr to the field (eg GF_Node **, GF_List**, MFInt32 *, ...)*/ + /*! far ptr to the field (eg GF_Node **, GF_List**, MFInt32 *, ...)*/ void *far_ptr; - /*field name*/ + /*! field name*/ const char *name; - /*NDT type in case of SF/MFNode field - cf BIFS specific tools*/ + /*! NDT type in case of SF/MFNode field - cf BIFS specific tools*/ u32 NDTtype; - /*event type*/ + /*! event type*/ u32 eventType; - /*eventin handler if any*/ + /*! eventin handler if any*/ void (*on_event_in)(GF_Node *pNode, GF_Route *from_route); } GF_FieldInfo; -/*returns number of field for this node*/ -u32 gf_node_get_field_count(GF_Node *node); +/*! returns number of field for this node. +For BIFS/VRML/X3D, this is the number of defined fields by the spec. +For SVG/DOM, this is the number of attributes defined for the node. +\param n the target node +\return the number of defined fields +*/ +u32 gf_node_get_field_count(GF_Node *n); -/*fill the field info structure for the given field*/ -GF_Err gf_node_get_field(GF_Node *node, u32 FieldIndex, GF_FieldInfo *info); +/*! fills the field info structure for the given field +\param n the target node +\param FieldIndex the 0-based index of the target field +\param info filled with field info +\return error if any +*/ +GF_Err gf_node_get_field(GF_Node *n, u32 FieldIndex, GF_FieldInfo *info); -/*get the field by its name*/ -GF_Err gf_node_get_field_by_name(GF_Node *node, char *name, GF_FieldInfo *field); +/*! gets the field by its name +\param n the target node +\param name name of the target field +\param field filled with field info +\return error if any +*/ +GF_Err gf_node_get_field_by_name(GF_Node *n, char *name, GF_FieldInfo *field); +/*! Scenegraph structure*/ typedef struct __tag_scene_graph GF_SceneGraph; -/*scene graph constructor*/ +/*! creates a new scene graph +\return a new scene graph +*/ GF_SceneGraph *gf_sg_new(); -/*creates a sub scene graph (typically used with Inline node): independent graph with same private stack, +/*! creates a sub scene graph (typically used with Inline node): independent graph with same private stack, and user callbacks as parent. All routes triggered in this subgraph are executed in the parent graph (this means you only have to activate routes on the main graph) NOTE: the resulting graph is not destroyed when the parent graph is +\param scene the parent scene graph +\return a new scene graph */ GF_SceneGraph *gf_sg_new_subscene(GF_SceneGraph *scene); -/*destructor*/ +/*! destroys a scene graph +\param sg the target scene graph +*/ void gf_sg_del(GF_SceneGraph *sg); -/*reset the full graph - all nodes, routes and protos are destroyed*/ +/*! resets the graph - all nodes, routes and protos are destroyed +\param sg the target scene graph +*/ void gf_sg_reset(GF_SceneGraph *sg); -/*parses the given XML document and returns a scene graph composed of GF_DOMFullNode*/ -GF_Err gf_sg_new_from_xml_doc(const char *src, GF_SceneGraph **scene); - -/*set/get user private data*/ -void gf_sg_set_private(GF_SceneGraph *sg, void *user_priv); +/*! sets user private data for scene graph +\param sg the target scene graph +\param udta user private data to set +*/ +void gf_sg_set_private(GF_SceneGraph *sg, void *udta); +/*! gets user private data of scene graph +\param sg the target scene graph +\return user private data +*/ void *gf_sg_get_private(GF_SceneGraph *sg); -/*set the scene timer (fct returns time in sec)*/ -void gf_sg_set_scene_time_callback(GF_SceneGraph *scene, Double (*GetSceneTime)(void *user_priv) ); +/*! sets the scene time query callback (functions returns time in sec) +\param sg the target scene graph +\param GetSceneTime the scene time query callback +*/ +void gf_sg_set_scene_time_callback(GF_SceneGraph *sg, Double (*GetSceneTime)(void *user_priv) ); -enum +/*! gets the parent scene graph of a graph +\param sg the target scene graph +\return the parent graph or NULL if none defined +*/ +GF_SceneGraph *gf_sg_get_parent(GF_SceneGraph *sg); + +/*! node callback type*/ +typedef enum { /*function called upon node creation. ctxdata is not used*/ @@ -409,65 +540,130 @@ enum GF_SG_CALLBACK_GRAPH_DIRTY, //node is being destroyed GF_SG_CALLBACK_NODE_DESTROY, -}; +} GF_SGNodeCbkType; + +/*! node callback function for scene graph +\param udta user private data of scene graph, see \ref gf_sg_set_private +\param type the type of callback +\param node the target node for the callback +\param ctxdata associated data, type depends on the callback type +*/ +typedef void (*gf_sg_node_init_callback)(void *udta, GF_SGNodeCbkType type, GF_Node *node, void *ctxdata); -/*set node callback: function called upon node creation. -Application should instanciate the node rendering stack and any desired callback*/ -void gf_sg_set_node_callback(GF_SceneGraph *sg, void (*NodeCallback)(void *user_priv, u32 type, GF_Node *node, void *ctxdata) ); +/*! sets node callback: function called upon node creation. +Application should instantiate the node rendering stack and any desired callback +\param sg the target scene graph +\param NodeCallback the node callback function +*/ +void gf_sg_set_node_callback(GF_SceneGraph *sg, gf_sg_node_init_callback NodeCallback); -/*get/set the root node of the graph*/ +/*! gets the root node of the graph +\param sg the target scene graph +\return root node of the scene graph, NULL otherwise +*/ GF_Node *gf_sg_get_root_node(GF_SceneGraph *sg); +/*! sets the root node of the graph +\param sg the target scene graph +\param node root node of the scene graph +*/ void gf_sg_set_root_node(GF_SceneGraph *sg, GF_Node *node); -/*finds a registered node by ID*/ +/*! finds a registered node by ID +\param sg the target scene graph +\param nodeID ID of the node to find +\return node if found, NULL otherwise +*/ GF_Node *gf_sg_find_node(GF_SceneGraph *sg, u32 nodeID); -/*finds a registered node by DEF name*/ +/*! finds a registered node by DEF name +\param sg the target scene graph +\param name name of the node to find +\return node if found, NULL otherwise +*/ GF_Node *gf_sg_find_node_by_name(GF_SceneGraph *sg, char *name); -/*used to signal modification of a node, indicating which field is modified - exposed for BIFS codec, -should not be needed by other apps*/ -void gf_node_changed(GF_Node *node, GF_FieldInfo *fieldChanged); - -/*returns the graph this node belongs to*/ -GF_SceneGraph *gf_node_get_graph(GF_Node *node); +/*! signals node has been modified, indicating which field is modified +\note this is exposed for BIFS codec and BIFS/VRML rendering, it should not be needed by other apps +\param n the target node +\param fieldChanged the modified field, may be NULL of global node modification +*/ +void gf_node_changed(GF_Node *n, GF_FieldInfo *fieldChanged); -/*Set size info for the graph - by default graphs have no size and are in meter metrics (VRML like) -if any of width or height is 0, the graph has no size info*/ -void gf_sg_set_scene_size_info(GF_SceneGraph *sg, u32 width, u32 Height, Bool usePixelMetrics); -/*returns 1 if pixelMetrics*/ +/*! gets the scene graph of a node +\param n the target node +\return the parent scene graph +*/ +GF_SceneGraph *gf_node_get_graph(GF_Node *n); + +/*! sets size info for the graph - by default graphs have no size and are in meter metrics (VRML like) +if any of width or height is 0, the graph has no size info +\param sg the target scene graph +\param width width in pixels +\param height height in pixels +\param usePixelMetrics indicates coordinates in graph are given in pixels +*/ +void gf_sg_set_scene_size_info(GF_SceneGraph *sg, u32 width, u32 height, Bool usePixelMetrics); +/*! checks if pixel metrics is used +\param sg the target scene graph +\return GF_TRUE if pixelMetrics*/ Bool gf_sg_use_pixel_metrics(GF_SceneGraph *sg); -/*returns 0 if no size info, otherwise 1 and set width/height*/ -Bool gf_sg_get_scene_size_info(GF_SceneGraph *sg, u32 *width, u32 *Height); -/*creates a node of the given tag. sg is the parent scenegraph of the node, +/*! gets size information of graph +\param sg the target scene graph +\param width set to width of scene +\param height set to height of scene +\return GF_FALSE if no size info, otherwise GF_TRUE and set width/height*/ +Bool gf_sg_get_scene_size_info(GF_SceneGraph *sg, u32 *width, u32 *height); + +/*! creates a node of the given tag. sg is the parent scenegraph of the node, eg the root one for scene nodes or the proto one for proto code (cf proto) -Note: +\note - NODE IS NOT REGISTERED (no instances) AND CANNOT BE DESTROYED UNTIL REGISTERED - this doesn't perform application setup for the node, this must be done by the caller + +\param sg the target scene graph +\param tag tag of node to create +\return new node */ GF_Node *gf_node_new(GF_SceneGraph *sg, u32 tag); -/*inits node (either internal stack or user-defined) - usually called once the node has been fully loaded*/ -void gf_node_init(GF_Node *node); - -/*clones a node in the given graph and register with parent cloned. The cloning handles ID based on id_suffix: - id_suffix = NULL: all IDs are removed from the cloned subtree, (each node instance will become a hard copy) - id_suffix = "": ID will be kept exactly as they where in the original subtree - this may lead to errors due to - the presence of the same ID depending on the standard (DOM, ...). - id_suffix = anything: all IDs are translated ($(name) -> $(name)id_suffix) and bynary IDs are generated on the fly +/*! inits node (either internal stack or user-defined) - usually called once the node has been fully loaded +\param n the target node +*/ +void gf_node_init(GF_Node *n); + +/*! clones a node in the given graph and register with parent cloned. +\param inScene the target scene graph in which the node should be cloned +\param orig the target node to clone +\param cloned_parent the parent of the node to clone +\param id_suffix if NULL, all IDs are removed from the cloned subtree, (each node instance will become a hard copy). If empty string "", ID will be kept exactly as they where in the original subtree (this may lead to errors due to the presence of the same ID depending on the standard). Otherwise, all IDs are translated ($(name) -> $(name)id_suffix) and binary IDs are generated on the fly +\param deep clones children as well +\return the cloned node */ GF_Node *gf_node_clone(GF_SceneGraph *inScene, GF_Node *orig, GF_Node *cloned_parent, char *id_suffix, Bool deep); -/*gets scene time for scene this node belongs too, 0 if timeline not specified*/ -Double gf_node_get_scene_time(GF_Node *node); +/*! gets scene time for scene this node belongs too +\param n the target node +\return the scene time for the node, 0 if timeline not specified*/ +Double gf_node_get_scene_time(GF_Node *n); -/*retuns next available NodeID*/ +/*! gets next available NodeID +\param sg the target scene graph +\return next available NodeID +*/ u32 gf_sg_get_next_available_node_id(GF_SceneGraph *sg); -/*retuns max ID used in graph*/ +/*! gets max ID used in graph +\param sg the target scene graph +\return max NodeID +*/ u32 gf_sg_get_max_node_id(GF_SceneGraph *sg); -const char *gf_node_get_name_and_id(GF_Node*node, u32 *id); - +/*! gets node ID and name +\param n the target node +\param ID set to the node ID or to 0 if not defined +\return the node name or NULL if not defined +*/ +const char *gf_node_get_name_and_id(GF_Node*n, u32 *ID); +/*! SVG focus types*/ enum { GF_SG_FOCUS_AUTO = 1, @@ -483,6 +679,7 @@ enum GF_SG_FOCUS_NORTH_WEST }; +/*! JS API Url structure*/ typedef struct { /*for GF_JSAPI_OP_RESOLVE_URI, @@ -496,6 +693,7 @@ typedef struct u32 nb_params; } GF_JSAPIURI; +/*! JS API structure for GPAC config file*/ typedef struct { const char *section; @@ -503,14 +701,14 @@ typedef struct const char *key_val; } GF_JSAPIOPT; -/*for script message option*/ +/*! JS API structure for script message*/ typedef struct { GF_Err e; const char *msg; } GF_JSAPIINFO; - +/*! JS API parameter type*/ typedef union { u32 opt; @@ -525,11 +723,12 @@ typedef union GF_Node *node; struct __gf_download_manager *dnld_man; GF_SceneGraph *scene; - void *term; + void *compositor; GF_JSAPIINFO info; } GF_JSAPIParam; -enum +/*! JS API action types*/ +typedef enum { /*!push message from script engine.*/ GF_JSAPI_OP_MESSAGE, @@ -579,15 +778,13 @@ enum GF_JSAPI_OP_GET_FPS, /*!set current title*/ GF_JSAPI_OP_SET_TITLE, - /*!gets DCCI scenegraph if any*/ - GF_JSAPI_OP_GET_DCCI, /*!gets subscene for current node if any*/ GF_JSAPI_OP_GET_SUBSCENE, /*!resolves relative Xlink based on xml:base*/ GF_JSAPI_OP_RESOLVE_XLINK, - /*!gets GPAC terminal*/ - GF_JSAPI_OP_GET_TERM, + /*!gets parent filter*/ + GF_JSAPI_OP_GET_COMPOSITOR, /*!pauses an SVG element*/ GF_JSAPI_OP_PAUSE_SVG, @@ -600,38 +797,38 @@ enum /*!gets the DPI*/ GF_JSAPI_OP_GET_DPI_X, GF_JSAPI_OP_GET_DPI_Y, -}; -/* -interface to various get/set options: - type: operand type, one of the above - node: target node, scene root node or NULL - param: i/o param, depending on operand type +} GF_JSAPIActionType; + +/*! interface to various get/set options: +\param callback opaque data passed to the function +\param type operand type, one of the above +\param node target node, scene root node or NULL +\param param i/o param, depending on operand type +\return GF_TRUE if success, GF_FALSE otherwise */ -typedef Bool (*gf_sg_script_action)(void *callback, u32 type, GF_Node *node, GF_JSAPIParam *param); +typedef Bool (*gf_sg_script_action)(void *callback, GF_JSAPIActionType type, GF_Node *node, GF_JSAPIParam *param); -/*assign API to scene graph - by default, sub-graphs inherits the API if set*/ -void gf_sg_set_script_action(GF_SceneGraph *scene, gf_sg_script_action script_act, void *cbk); +/*! assigns API to scene graph - by default, sub-graphs inherits the API if set +\param sg the target scene graph +\param script_act the script action callback +\param cbk opaque data to pass to the script action callback +*/ +void gf_sg_set_script_action(GF_SceneGraph *sg, gf_sg_script_action script_act, void *cbk); -/*load script into engine - this should be called only for script in main scene, loading of scripts -in protos is done internally when instanciating the proto*/ +/*! loads script into engine - this should be called only for script in main scene, loading of scripts +in protos is done internally when instanciating the proto +\param script the target script node*/ void gf_sg_script_load(GF_Node *script); -/*returns true if current lib has javascript support*/ +/*! checks if scripting is supported in GPAC (built-in and run-time enabled) +\return GF_TRUE if javascript is supported*/ Bool gf_sg_has_scripting(); +/*! Scene command tags -char *gf_node_dump_attribute(GF_Node *elt, GF_FieldInfo *info); - - -/* - scene graph command tools used for BIFS and LASeR - These are used to store updates in memory without applying changes to the graph, - for dumpers, encoders ... - The commands can then be applied through this lib -*/ - -/* - Currently defined possible modifications +scene graph command tools used for BIFS and LASeR +These are used to store updates in memory without applying changes to the graph, for dumpers, encoders ... +The commands can then be applied through this lib */ enum { @@ -686,17 +883,7 @@ enum }; -/* - single command wrapper - - NOTE: In order to maintain node registry, the nodes replaced/inserted MUST be registered with - their parents even when the command is never applied. Registering shall be performed - with gf_node_register (see below). - If you fail to do so, a node may be destroyed when destroying a command while still used - in another command or in the graph - this will just crash. -*/ - -/*structure used to store field info, pos and static pointers to GF_Node/MFNode in commands*/ +/*! structure used to store field info, pos and static pointers to GF_Node/MFNode in commands*/ typedef struct { u32 fieldIndex; @@ -713,6 +900,15 @@ typedef struct GF_ChildNodeItem *node_list; } GF_CommandField; +/*! structure used to store decoded BIFS command + +\note In order to maintain node registry, the nodes replaced/inserted MUST be registered with + their parents even when the command is never applied. Registering shall be performed + with gf_node_register (see below). + If you fail to do so, a node may be destroyed when destroying a command while still used + in another command or in the graph - this will just crash. + +*/ typedef struct { GF_SceneGraph *in_scene; @@ -781,22 +977,55 @@ typedef struct } GF_Command; -/*creates command - graph only needed for SceneReplace*/ -GF_Command *gf_sg_command_new(GF_SceneGraph *in_scene, u32 tag); -/*deletes command*/ +/*! creates a command - graph is only needed for SceneReplace +\param sg parent scene graph of the command, only needed for SceneReplace +\param tag the command tag +\return a new command +*/ +GF_Command *gf_sg_command_new(GF_SceneGraph *sg, u32 tag); +/*! destroys a command +\param com the target command +*/ void gf_sg_command_del(GF_Command *com); -/*apply command to graph - the command content is kept unchanged for authoring purposes - THIS NEEDS TESTING AND FIXING -@time_offset: offset for time fields if desired*/ -GF_Err gf_sg_command_apply(GF_SceneGraph *inScene, GF_Command *com, Double time_offset); -/*apply list if command to graph - the command content is kept unchanged for authoring purposes -@time_offset: offset for time fields if desired*/ -GF_Err gf_sg_command_apply_list(GF_SceneGraph *graph, GF_List *comList, Double time_offset); -/*returns new commandFieldInfo structure and registers it with command*/ +/*! applies command to graph - the command content is kept unchanged for authoring purposes - THIS NEEDS TESTING AND FIXING +\param sg the target scene graph where to apply the command +\param com the target command +\param time_offset time offset in seconds for time fields if desired +\return error if any +*/ +GF_Err gf_sg_command_apply(GF_SceneGraph *sg, GF_Command *com, Double time_offset); + +/*! applies list if command to graph - the command content is kept unchanged for authoring purposes +\param sg the target scene graph where to apply the command +\param comList the list of commands +\param time_offset time offset in seconds for time fields if desired +\return error if any +*/ +GF_Err gf_sg_command_apply_list(GF_SceneGraph *sg, GF_List *comList, Double time_offset); + +/*! creates a new command field structire (some commands may target multiple fields at once) and registers it with command +\param com the parent command +\return new commandFieldInfo structure*/ GF_CommandField *gf_sg_command_field_new(GF_Command *com); -/* Executes JS code in the root context of the scene graph */ +/*! executes JS code in the root context of the scene graph +\param sg the target scene graph where to execute the script +\param com javascript code to execute +\return error if any +*/ GF_Err gf_scene_execute_script(GF_SceneGraph *sg, const char *com); +/*! XML node from DOM parser */ +typedef struct _xml_node *GF_DOMXMLNODE; + +/*! Creates a GF_SceneGraph from an XML root node. This is mostly used to allow using DOM API on an XML doc + \param document an empty scene graph object + \param root_node the root node of an XML document + \return error if any + */ +GF_Err gf_sg_init_from_xml_node(GF_SceneGraph *document, GF_DOMXMLNODE root_node); + + /*! @} */ #ifdef __cplusplus diff --git a/include/gpac/scenegraph_svg.h b/include/gpac/scenegraph_svg.h index 2bedb6f..a81c5bd 100644 --- a/include/gpac/scenegraph_svg.h +++ b/include/gpac/scenegraph_svg.h @@ -1,8 +1,8 @@ /* * GPAC - Multimedia Framework C SDK * - * Authors: Jean Le Feuvre - Cyril COncolato - * Copyright (c) Telecom ParisTech 2004-2012 + * Authors: Jean Le Feuvre - Cyril Concolato + * Copyright (c) Telecom ParisTech 2004-2019 * All rights reserved * * This file is part of GPAC / SVG Scene Graph sub-project @@ -31,17 +31,18 @@ extern "C" { #endif /*! - * \file <gpac/scenegraph_svg.h> - * \brief Scenegraph for SVG files - */ +\file <gpac/scenegraph_svg.h> +\brief Scenegraph for SVG files +*/ /*! - * \addtogroup ssvg SVG Scenegraph - * \ingroup scene_grp - * \brief Scenegraph for SVG files. - * - *This section documents the Scenegraph for SVG files. - * @{ +\addtogroup ssvg SVG Scenegraph +\ingroup scene_grp +\brief Scenegraph for SVG files. + +This section documents the Scenegraph for SVG files. + +@{ */ @@ -54,7 +55,7 @@ extern "C" { * DOM base scene graph * *******************************************************************************/ - +/*! DOM attributes tags for xmlspace, xmlev, xlink, SVG and LASeR*/ enum { /*should never be used, this is only a parsing error*/ @@ -101,56 +102,223 @@ enum TAG_LSR_ATT_svg_width, TAG_LSR_ATT_svg_height, TAG_LSR_ATT_textContent, - /*WHAT THE HECK IS THIS THING IN THE SDL BUT NOWHERE IN THE SPEC ?*/ TAG_LSR_ATT_text_display, TAG_SVG_ATT_RANGE_FIRST, - TAG_XBL_ATT_RANGE_FIRST = TAG_SVG_ATT_RANGE_FIRST + 256, - TAG_XBL_ATT_id = TAG_XBL_ATT_RANGE_FIRST, - TAG_XBL_ATT_extends, - TAG_XBL_ATT_display, - TAG_XBL_ATT_inheritstyle, - TAG_XBL_ATT_includes, - TAG_XBL_ATT_name, - TAG_XBL_ATT_implements, - TAG_XBL_ATT_type, - TAG_XBL_ATT_readonly, - TAG_XBL_ATT_onget, - TAG_XBL_ATT_onset, - TAG_XBL_ATT_event, - TAG_XBL_ATT_action, - TAG_XBL_ATT_phase, - TAG_XBL_ATT_button, - TAG_XBL_ATT_modifiers, - TAG_XBL_ATT_keycode, - TAG_XBL_ATT_key, - TAG_XBL_ATT_charcode, - TAG_XBL_ATT_clickcount, - TAG_XBL_ATT_command, - TAG_XBL_ATT_preventdefault, - TAG_XBL_ATT_src, + TAG_SVG_ATT_id = TAG_SVG_ATT_RANGE_FIRST, + TAG_SVG_ATT__class, + TAG_SVG_ATT_requiredFeatures, + TAG_SVG_ATT_requiredExtensions, + TAG_SVG_ATT_requiredFormats, + TAG_SVG_ATT_requiredFonts, + TAG_SVG_ATT_systemLanguage, + TAG_SVG_ATT_display, + TAG_SVG_ATT_visibility, + TAG_SVG_ATT_image_rendering, + TAG_SVG_ATT_pointer_events, + TAG_SVG_ATT_shape_rendering, + TAG_SVG_ATT_text_rendering, + TAG_SVG_ATT_audio_level, + TAG_SVG_ATT_viewport_fill, + TAG_SVG_ATT_viewport_fill_opacity, + TAG_SVG_ATT_overflow, + TAG_SVG_ATT_fill_opacity, + TAG_SVG_ATT_stroke_opacity, + TAG_SVG_ATT_fill, + TAG_SVG_ATT_fill_rule, + TAG_SVG_ATT_filter, + TAG_SVG_ATT_stroke, + TAG_SVG_ATT_stroke_dasharray, + TAG_SVG_ATT_stroke_dashoffset, + TAG_SVG_ATT_stroke_linecap, + TAG_SVG_ATT_stroke_linejoin, + TAG_SVG_ATT_stroke_miterlimit, + TAG_SVG_ATT_stroke_width, + TAG_SVG_ATT_color, + TAG_SVG_ATT_color_rendering, + TAG_SVG_ATT_vector_effect, + TAG_SVG_ATT_solid_color, + TAG_SVG_ATT_solid_opacity, + TAG_SVG_ATT_display_align, + TAG_SVG_ATT_line_increment, + TAG_SVG_ATT_stop_color, + TAG_SVG_ATT_stop_opacity, + TAG_SVG_ATT_font_family, + TAG_SVG_ATT_font_size, + TAG_SVG_ATT_font_style, + TAG_SVG_ATT_font_variant, + TAG_SVG_ATT_font_weight, + TAG_SVG_ATT_text_anchor, + TAG_SVG_ATT_text_align, + TAG_SVG_ATT_text_decoration, + TAG_SVG_ATT_focusHighlight, + TAG_SVG_ATT_externalResourcesRequired, + TAG_SVG_ATT_focusable, + TAG_SVG_ATT_nav_next, + TAG_SVG_ATT_nav_prev, + TAG_SVG_ATT_nav_up, + TAG_SVG_ATT_nav_up_right, + TAG_SVG_ATT_nav_right, + TAG_SVG_ATT_nav_down_right, + TAG_SVG_ATT_nav_down, + TAG_SVG_ATT_nav_down_left, + TAG_SVG_ATT_nav_left, + TAG_SVG_ATT_nav_up_left, + TAG_SVG_ATT_transform, + TAG_SVG_ATT_target, + TAG_SVG_ATT_attributeName, + TAG_SVG_ATT_attributeType, + TAG_SVG_ATT_begin, + TAG_SVG_ATT_dur, + TAG_SVG_ATT_end, + TAG_SVG_ATT_repeatCount, + TAG_SVG_ATT_repeatDur, + TAG_SVG_ATT_restart, + TAG_SVG_ATT_smil_fill, + TAG_SVG_ATT_min, + TAG_SVG_ATT_max, + TAG_SVG_ATT_to, + TAG_SVG_ATT_calcMode, + TAG_SVG_ATT_values, + TAG_SVG_ATT_keyTimes, + TAG_SVG_ATT_keySplines, + TAG_SVG_ATT_from, + TAG_SVG_ATT_by, + TAG_SVG_ATT_additive, + TAG_SVG_ATT_accumulate, + TAG_SVG_ATT_path, + TAG_SVG_ATT_keyPoints, + TAG_SVG_ATT_rotate, + TAG_SVG_ATT_origin, + TAG_SVG_ATT_transform_type, + TAG_SVG_ATT_clipBegin, + TAG_SVG_ATT_clipEnd, + TAG_SVG_ATT_syncBehavior, + TAG_SVG_ATT_syncTolerance, + TAG_SVG_ATT_syncMaster, + TAG_SVG_ATT_syncReference, + TAG_SVG_ATT_x, + TAG_SVG_ATT_y, + TAG_SVG_ATT_width, + TAG_SVG_ATT_height, + TAG_SVG_ATT_preserveAspectRatio, + TAG_SVG_ATT_initialVisibility, + TAG_SVG_ATT_type, + TAG_SVG_ATT_cx, + TAG_SVG_ATT_cy, + TAG_SVG_ATT_r, + TAG_SVG_ATT_cursorManager_x, + TAG_SVG_ATT_cursorManager_y, + TAG_SVG_ATT_rx, + TAG_SVG_ATT_ry, + TAG_SVG_ATT_horiz_adv_x, + TAG_SVG_ATT_horiz_origin_x, + TAG_SVG_ATT_font_stretch, + TAG_SVG_ATT_unicode_range, + TAG_SVG_ATT_panose_1, + TAG_SVG_ATT_widths, + TAG_SVG_ATT_bbox, + TAG_SVG_ATT_units_per_em, + TAG_SVG_ATT_stemv, + TAG_SVG_ATT_stemh, + TAG_SVG_ATT_slope, + TAG_SVG_ATT_cap_height, + TAG_SVG_ATT_x_height, + TAG_SVG_ATT_accent_height, + TAG_SVG_ATT_ascent, + TAG_SVG_ATT_descent, + TAG_SVG_ATT_ideographic, + TAG_SVG_ATT_alphabetic, + TAG_SVG_ATT_mathematical, + TAG_SVG_ATT_hanging, + TAG_SVG_ATT_underline_position, + TAG_SVG_ATT_underline_thickness, + TAG_SVG_ATT_strikethrough_position, + TAG_SVG_ATT_strikethrough_thickness, + TAG_SVG_ATT_overline_position, + TAG_SVG_ATT_overline_thickness, + TAG_SVG_ATT_d, + TAG_SVG_ATT_unicode, + TAG_SVG_ATT_glyph_name, + TAG_SVG_ATT_arabic_form, + TAG_SVG_ATT_lang, + TAG_SVG_ATT_u1, + TAG_SVG_ATT_g1, + TAG_SVG_ATT_u2, + TAG_SVG_ATT_g2, + TAG_SVG_ATT_k, + TAG_SVG_ATT_opacity, + TAG_SVG_ATT_x1, + TAG_SVG_ATT_y1, + TAG_SVG_ATT_x2, + TAG_SVG_ATT_y2, + TAG_SVG_ATT_filterUnits, + TAG_SVG_ATT_gradientUnits, + TAG_SVG_ATT_spreadMethod, + TAG_SVG_ATT_gradientTransform, + TAG_SVG_ATT_pathLength, + TAG_SVG_ATT_points, + TAG_SVG_ATT_mediaSize, + TAG_SVG_ATT_mediaTime, + TAG_SVG_ATT_mediaCharacterEncoding, + TAG_SVG_ATT_mediaContentEncodings, + TAG_SVG_ATT_bandwidth, + TAG_SVG_ATT_fx, + TAG_SVG_ATT_fy, + TAG_SVG_ATT_size, + TAG_SVG_ATT_choice, + TAG_SVG_ATT_delta, + TAG_SVG_ATT_offset, + TAG_SVG_ATT_syncBehaviorDefault, + TAG_SVG_ATT_syncToleranceDefault, + TAG_SVG_ATT_viewBox, + TAG_SVG_ATT_zoomAndPan, + TAG_SVG_ATT_version, + TAG_SVG_ATT_baseProfile, + TAG_SVG_ATT_contentScriptType, + TAG_SVG_ATT_snapshotTime, + TAG_SVG_ATT_timelineBegin, + TAG_SVG_ATT_playbackOrder, + TAG_SVG_ATT_editable, + TAG_SVG_ATT_text_x, + TAG_SVG_ATT_text_y, + TAG_SVG_ATT_text_rotate, + TAG_SVG_ATT_transformBehavior, + TAG_SVG_ATT_overlay, + TAG_SVG_ATT_fullscreen, + TAG_SVG_ATT_motionTransform, + + TAG_SVG_ATT_filter_transfer_type, + TAG_SVG_ATT_filter_table_values, + TAG_SVG_ATT_filter_intercept, + TAG_SVG_ATT_filter_amplitude, + TAG_SVG_ATT_filter_exponent, + TAG_GSVG_ATT_useAsPrimary, TAG_GSVG_ATT_depthOffset, TAG_GSVG_ATT_depthGain, }; - +/*! macro for DOM base attribute*/ #define GF_DOM_BASE_ATTRIBUTE \ u16 tag; /*attribute identifier*/ \ u16 data_type; /*attribute datatype*/ \ void *data; /*data pointer*/ \ struct __dom_base_attribute *next; /*next attribute*/ +/*! macro for DOM full attribute*/ #define GF_DOM_FULL_ATTRIBUTE \ GF_DOM_ATTRIBUTE \ - + +/*! DOM attribute*/ typedef struct __dom_base_attribute { GF_DOM_BASE_ATTRIBUTE } GF_DOMAttribute; +/*! DOM full attribute*/ typedef struct __dom_full_attribute { GF_DOM_BASE_ATTRIBUTE @@ -158,16 +326,19 @@ typedef struct __dom_full_attribute char *name; /*attribute name - in this case, the data field is the attribute literal value*/ } GF_DOMFullAttribute; +/*! macro for DOM base node*/ #define GF_DOM_BASE_NODE \ BASE_NODE \ CHILDREN \ GF_DOMAttribute *attributes; +/*! DOM base node*/ typedef struct __dom_base_node { GF_DOM_BASE_NODE } GF_DOMNode; +/*! DOM full node*/ typedef struct __dom_full_node { GF_DOM_BASE_NODE @@ -175,6 +346,7 @@ typedef struct __dom_full_node u32 ns; } GF_DOMFullNode; +/*! DOM built-in namespaces*/ typedef enum { /*XMLNS is undefined*/ @@ -192,32 +364,77 @@ typedef enum /*any other namespace uses the CRC32 of the namespace as an identifier*/ } GF_NamespaceType; -/*returns the built-in XMLNS id for this namespace if known, otherwise returns GF_XMLNS_UNDEFINED*/ +/*! gets built-in XMLNS id for this namespace +\param name name of the namespace +\return namespace ID if known, otherwise GF_XMLNS_UNDEFINED*/ GF_NamespaceType gf_xml_get_namespace_id(char *name); - +/*! adds a new namespace +\param sg the target scene graph +\param name name of the namespace (full URL) +\param qname QName of the namespace (short name in doc, eg "ev") +\return error if any +*/ GF_Err gf_sg_add_namespace(GF_SceneGraph *sg, char *name, char *qname); +/*! removes a new namespace +\param sg the target scene graph +\param name name of the namespace +\param qname QName of the namespace +\return error if any +*/ GF_Err gf_sg_remove_namespace(GF_SceneGraph *sg, char *name, char *qname); -u32 gf_sg_get_namespace_code(GF_SceneGraph *sg, char *qname); -u32 gf_sg_get_namespace_code_from_name(GF_SceneGraph *sg, char *name); -const char *gf_sg_get_namespace_qname(GF_SceneGraph *sg, GF_NamespaceType xmlns_id); +/*! gets namespace code +\param sg the target scene graph +\param qname QName of the namespace +\return namespace code +*/ +GF_NamespaceType gf_sg_get_namespace_code(GF_SceneGraph *sg, char *qname); +/*! gets namespace code from name +\param sg the target scene graph +\param name name of the namespace +\return namespace code +*/ +GF_NamespaceType gf_sg_get_namespace_code_from_name(GF_SceneGraph *sg, char *name); -GF_NamespaceType gf_xml_get_element_namespace(GF_Node *n); +/*! gets namespace qname from ID +\param sg the target scene graph +\param xmlns_id ID of the namespace +\return namespace qname +*/ +const char *gf_sg_get_namespace_qname(GF_SceneGraph *sg, GF_NamespaceType xmlns_id); +/*! gets namespace from ID +\param sg the target scene graph +\param xmlns_id ID of the namespace +\return namespace name +*/ const char *gf_sg_get_namespace(GF_SceneGraph *sg, GF_NamespaceType xmlns_id); - +/*! push namespace parsing state +\param elt the current node being parsed +*/ void gf_xml_push_namespaces(GF_DOMNode *elt); +/*! pop namespace parsing state +\param elt the current node being parsed +*/ void gf_xml_pop_namespaces(GF_DOMNode *elt); +/*! gets namespace of an element +\param n the target node +\return namespace ID +*/ +GF_NamespaceType gf_xml_get_element_namespace(GF_Node *n); - +/*! DOM text node type*/ enum { + /*! regular text*/ GF_DOM_TEXT_REGULAR = 0, + /*! CDATA section*/ GF_DOM_TEXT_CDATA, - /*inserted text node (typically external script)*/ + /*! inserted text node (typically external script)*/ GF_DOM_TEXT_INSERTED }; +/*! DOM text node*/ typedef struct { BASE_NODE @@ -226,18 +443,30 @@ typedef struct u32 type; } GF_DOMText; -/*creates a new text node, assign string (does NOT duplicate it) and register node with parent if desired*/ +/*! creates a new text node, assign string (does NOT duplicate it) and register node with parent if desired +\param parent the target parent node +\param text_data UTF-8 data to add as a text node +\return the new inserted DOM text node or NULL if error +*/ GF_DOMText *gf_dom_add_text_node(GF_Node *parent, char *text_data); -/*replace text content of node by the specified string - if string is NULL, only resets the children of the node*/ +/*! replaces text content of node by the specified string +\param n the target DOM node +\param text the replacement string in UTF-8. If NULL, only resets the children of the node data to add as a text node +*/ void gf_dom_set_textContent(GF_Node *n, char *text); -/*flatten text content of the node and returns the result - result shall be free'ed by the caller*/ +/*! flattens text content of the node +\param n the target DOM node +\return the flattened text - shall be free'ed by the caller*/ char *gf_dom_flatten_textContent(GF_Node *n); -/*creates a new text node - this DOES NOT register the node at all*/ +/*! creates a new text node - this DOES NOT register the node +\param sg the target scene graph for the node +\return the new text node*/ GF_DOMText *gf_dom_new_text_node(GF_SceneGraph *sg); +/*! DOM update (DIMS/LASeR) node*/ typedef struct { BASE_NODE @@ -247,12 +476,13 @@ typedef struct GF_List *updates; } GF_DOMUpdates; -/*creates a new updates node and register node with parent*/ +/*! creates a new updates node and register node with parent +\param parent the target parent node +\return a new DOM updates node +*/ GF_DOMUpdates *gf_dom_add_updates_node(GF_Node *parent); -/* - DOM event handling -*/ +/*! DOM Event phases*/ typedef enum { GF_DOM_EVENT_PHASE_CAPTURE = 1, @@ -268,7 +498,7 @@ typedef enum GF_DOM_EVENT_PHASE_PREVENT = 1<<7, } GF_DOMEventPhase; -/*possible event targets*/ +/*! DOM Event possible targets*/ typedef enum { GF_DOM_EVENT_TARGET_NODE, @@ -280,17 +510,33 @@ typedef enum } GF_DOMEventTargetType; -/* Structure representing the DOM EventTarget Interface */ +/*! DOM EventTarget Interface*/ typedef struct { - GF_List *listeners; // list of SVG Listener nodes attached to this Event Target - void *ptr; // pointer to the object implementing the DOM Event Target Interface - GF_DOMEventTargetType ptr_type; // type of the object implementing the DOM Event Target Interface + /*! list of SVG Listener nodes attached to this Event Target*/ + GF_List *listeners; + /*! pointer to the object implementing the DOM Event Target Interface*/ + void *ptr; + /*! type of the object implementing the DOM Event Target Interface*/ + GF_DOMEventTargetType ptr_type; } GF_DOMEventTarget; +/*! creates a new event target +\param type the type of the event target +\param obj opaque data passed to the event target +\return a new DOM event target +*/ GF_DOMEventTarget *gf_dom_event_target_new(GF_DOMEventTargetType type, void *obj); -GF_Err gf_sg_listener_add(GF_Node *listener, GF_DOMEventTarget *evt_target); +/*! associates a listener node and a event target node + - adds the listener node in the list of event listener nodes for the target node + - sets the target node as the user of the listener +\param listener the target listener +\param evt_target the event target +\return error if any +*/ +GF_Err gf_sg_listener_associate(GF_Node *listener, GF_DOMEventTarget *evt_target); +/*! DOM Event media information*/ typedef struct { Bool bufferValid; @@ -301,7 +547,7 @@ typedef struct u64 loaded_size, total_size; } GF_DOMMediaEvent; -/* Structure representing a DOM Event */ +/*! DOM Event structure*/ typedef struct { /*event type, as defined in <gpac/events.h>*/ @@ -367,48 +613,71 @@ typedef struct const char *addon_url; } GF_DOM_Event; -/*fires event on the specified node -BE CAREFULL: event execution may very well destroy ANY node, especially the event target node !! +/*! fires an event on the specified node +\warning event execution may very well destroy ANY node, especially the event target node !! +\param node the target node +\param event the DOM event +\return GF_TRUE if event is consumed/aborted after this call, GF_FALSE otherwise (event now pending) */ Bool gf_dom_event_fire(GF_Node *node, GF_DOM_Event *event); +/*! fires a DOM event on the specified node +\warning event execution may very well destroy ANY node, especially the event target node !! +\param et the event target +\param event the dom event +\param sg the parent graph +\param n the event current target, can be NULL +\return GF_TRUE if event is consumed/aborted after this call, GF_FALSE otherwise (event now pending) +*/ Bool gf_sg_fire_dom_event(GF_DOMEventTarget *et, GF_DOM_Event *event, GF_SceneGraph *sg, GF_Node *n); -/*fires event on the specified node -BE CAREFULL: event execution may very well destroy ANY node, especially the event target node !! -use_stack: a list of parent node/use node pairs for bubbling phase - may be NULL +/*! fires event on the specified node +\warning event execution may very well destroy ANY node, especially the event target node !! +\param node the target node +\param event the DOM event +\param use_stack a list of parent node/use node pairs for bubbling phase - may be NULL +\return GF_TRUE if event is consumed/aborted after this call, GF_FALSE otherwise (event now pending) */ Bool gf_dom_event_fire_ex(GF_Node *node, GF_DOM_Event *event, GF_List *use_stack); +/*! gets event type by name +\param name the event name +\return the event type*/ GF_EventType gf_dom_event_type_by_name(const char *name); +/*! gets event name by type +\param type the event type +\return the event name*/ const char *gf_dom_event_get_name(GF_EventType type); +/*! gets key name by type +\param key_identifier the key type +\return the key name*/ const char *gf_dom_get_key_name(GF_KeyCode key_identifier); +/*! gets key type by name +\param key_name the key name +\return the key type*/ GF_KeyCode gf_dom_get_key_type(char *key_name); -/*listener is simply a node added to the node events list. +/*! macro for DOM listener +DOM listener is simply a node added to the node events list. Only one observer can be attached to a listener. The listener will remove itself from the observer event list when destructed.*/ #define GF_DOM_BASE_LISTENER \ /* JavaScript context in which the listener is applicable */ \ - void *js_context; \ - /*target EventListener object (this) */ \ - void *evt_listen_obj; \ - /*function value for spidermonkey - we cannot use JS_CallFunction since it does not work on closures \ - we use 64 bits to store the value for portability safety*/\ - u64 js_fun_val;\ - /*compiled function for the case were CallFunction is needed*/\ - void *js_fun; \ + struct js_handler_context *js_data;\ /* text content of the callback */ \ char *callback; +/*! DOM Event handler*/ typedef struct __xml_ev_handler { GF_DOM_BASE_NODE + /*! handler callback function*/ void (*handle_event)(GF_Node *hdl, GF_DOM_Event *event, GF_Node *observer); GF_DOM_BASE_LISTENER } GF_DOMHandler; +/*! DOM Event category*/ typedef enum { GF_DOM_EVENT_UNKNOWN_CATEGORY, @@ -440,33 +709,76 @@ typedef enum /*Internal GPAC events*/ GF_DOM_EVENT_GPAC = 1<<30, /*fake events - these events are NEVER fired*/ - GF_DOM_EVENT_FAKE = 1<<31, + GF_DOM_EVENT_FAKE = 0x80000000 //1<<31 } GF_DOMEventCategory; + +/*! gets category of DOM event +\param type type of event +\return event category +*/ GF_DOMEventCategory gf_dom_event_get_category(GF_EventType type); -u32 gf_sg_get_dom_event_filter(GF_SceneGraph *sg); -u32 gf_node_get_dom_event_filter(GF_Node *node); +/*! registers an event category with the scene graph. Event with unregistered categories will not be fired +\param sg the target scene graph +\param category the DOM event category to add +*/ void gf_sg_register_event_type(GF_SceneGraph *sg, GF_DOMEventCategory category); +/*! unregisters an event category with the scene graph +\param sg the target scene graph +\param category the DOM event category to add +*/ void gf_sg_unregister_event_type(GF_SceneGraph *sg, GF_DOMEventCategory category); -/*adds a listener to the node. -The listener node is NOT registered with the node (it may very well not be a direct child of the node) -@listener is a listenerElement (XML event) +/*! adds a listener to the node. +\param n the target node +\param listener a listenerElement (XML event). The listener node is NOT registered with the node (it may very well not be a direct child of the node) +\return error if any +*/ +GF_Err gf_node_dom_listener_add(GF_Node *n, GF_Node *listener); +/*! gets number of listener of a node +\param n the target node +\return number of listeners +*/ +u32 gf_dom_listener_count(GF_Node *n); +/*! gets a listener of a node +\param n the target node +\param idx 0-based index of the listener to query +\return listener node or NULL if error +*/ +GF_Node *gf_dom_listener_get(GF_Node *n, u32 idx); + +/*! creates a default listener/handler for the given event on the given node +Listener/handler are stored at the node level +\param observer the observer node +\param event_type the event type +\param event_param the event parameter +\return the created handler element to allow for handler function override */ -GF_Err gf_node_dom_listener_add(GF_Node *node, GF_Node *listener); -u32 gf_dom_listener_count(GF_Node *node); -GF_Node *gf_dom_listener_get(GF_Node *node, u32 i); - -/*creates a default listener/handler for the given event on the given node, and return the -handler element to allow for handler function override -Listener/handler are stored at the node level*/ GF_DOMHandler *gf_dom_listener_build(GF_Node *observer, GF_EventType event_type, u32 event_param); - +/*! registers a XML IRI with the scene graph - this is needed to handle replacement of IRI (SMIL anim, DOM / LASeR updates) +\warning \ref gf_node_unregister_iri shall be called when the parent node of the IRI is destroyed +\param sg the target scene graph +\param iri the IRI to register +*/ void gf_node_register_iri(GF_SceneGraph *sg, XMLRI *iri); +/*! unregisters a XML IRI with the scene graph +\param sg the target scene graph +\param iri the IRI to unregister +*/ void gf_node_unregister_iri(GF_SceneGraph *sg, XMLRI *iri); -u32 gf_node_animation_count(GF_Node *node); +/*! gets number of animation targeting this node +\param n the target node +\return the number of animations +*/ +u32 gf_node_animation_count(GF_Node *n); +/*! writes data embedded in IRI (such as base64 data) to the indicated cahce directory, and updates the IRI accordingly +\param iri the IRI to store +\param cache_dir location of the directory to which the data should be extracted +\param base_filename location of the file the IRI was declared in +\return error if any +*/ GF_Err gf_node_store_embedded_data(XMLRI *iri, const char *cache_dir, const char *base_filename); @@ -474,6 +786,7 @@ GF_Err gf_node_store_embedded_data(XMLRI *iri, const char *cache_dir, const char * SVG's styling properties (see 6.1 in REC 1.1) * *************************************************/ +/*! SVG properties of node*/ typedef struct { /* Tiny 1.2 properties*/ SVG_Paint *color; @@ -561,57 +874,177 @@ typedef struct { */ } SVGPropertiesPointers; -/************************************* - * Generic SVG element functions * - *************************************/ +/*! initializes an SVGPropertiesPointers +\param svg_props pointer to structure to initialize +*/ void gf_svg_properties_init_pointers(SVGPropertiesPointers *svg_props); +/*! resets an SVGPropertiesPointers +\param svg_props pointer to structure to reset +*/ void gf_svg_properties_reset_pointers(SVGPropertiesPointers *svg_props); -void gf_svg_apply_animations(GF_Node *node, SVGPropertiesPointers *render_svg_props); +/*! applies animations for the node +\param n the target node +\param render_svg_props pointer to rendering SVG properties of the node +*/ +void gf_svg_apply_animations(GF_Node *n, SVGPropertiesPointers *render_svg_props); +/*! check if appearance flag is set +\param flags flags to test +\return GF_TRUE if appearance dirty flag is set +*/ Bool gf_svg_has_appearance_flag_dirty(u32 flags); +/*! checks if node tag indicates a transformable elemnt +\param tag tag to check +\return GF_TRUE if element is transformable +*/ Bool gf_svg_is_element_transformable(u32 tag); - +/*! creates an SVG attribute value for the given type +\param attribute_type type of attribute to delete +\return newly allocated attribute value*/ void *gf_svg_create_attribute_value(u32 attribute_type); -void gf_svg_delete_attribute_value(u32 type, void *value, GF_SceneGraph *sg); -/* a == b */ +/*! destroys an SVG attribute value +\param attribute_type type of attribute to delete +\param value the value to destroy +\param sg the parent scenegraph (needed for IRI registration) +*/ +void gf_svg_delete_attribute_value(u32 attribute_type, void *value, GF_SceneGraph *sg); + +/*! checks if two SVG attributes are equal +\param a first attribute +\param b second attribute +\return GF_TRUE if equal +*/ Bool gf_svg_attributes_equal(GF_FieldInfo *a, GF_FieldInfo *b); -/* a = b */ +/*! copies attributes (a = b) +\param a destination attribute +\param b source attribute +\param clamp if GF_TRUE, the interpolated value is clamped to its min/max possible values +\return error if any +*/ GF_Err gf_svg_attributes_copy(GF_FieldInfo *a, GF_FieldInfo *b, Bool clamp); -/* c = a + b */ +/*! adds attributes ( c = a + b) +\param a first attribute +\param b second attribute +\param c destination attribute +\param clamp if GF_TRUE, the interpolated value is clamped to its min/max possible values +\return error if any + */ GF_Err gf_svg_attributes_add(GF_FieldInfo *a, GF_FieldInfo *b, GF_FieldInfo *c, Bool clamp); -Bool gf_svg_attribute_is_interpolatable(u32 type) ; -/* c = coef * a + (1 - coef) * b */ +/*! checks if attribute type can be interpolated +\param type attribute type +\return GF_TRUE if type can be interpolation*/ +Bool gf_svg_attribute_is_interpolatable(u32 type); +/*! interpolates attributes ( c = coef * a + (1 - coef) * b ) +\param a first attribute +\param b second attribute +\param c destination attribute +\param coef interpolation coeficient +\param clamp if GF_TRUE, the interpolated value is clamped to its min/max possible values +\return error if any +*/ GF_Err gf_svg_attributes_interpolate(GF_FieldInfo *a, GF_FieldInfo *b, GF_FieldInfo *c, Fixed coef, Bool clamp); -/* c = alpha * a + beta * b */ +/*! multiply and add attributes (c = alpha * a + beta * b) +\param a first attribute +\param alpha first multiplier +\param b second attribute +\param beta second multiplier +\param c destination attribute +\param clamp if GF_TRUE, the interpolated value is clamped to its min/max possible values +\return error if any +*/ GF_Err gf_svg_attributes_muladd(Fixed alpha, GF_FieldInfo *a, Fixed beta, GF_FieldInfo *b, GF_FieldInfo *c, Bool clamp); - -GF_Err gf_node_get_attribute_by_tag(GF_Node *node, u32 attribute_tag, Bool create_if_not_found, Bool set_default, GF_FieldInfo *field); - -char *gf_svg_attribute_type_to_string(u32 att_type); +/*! gets an attribute by its tag (built-in name) +\param n the target node +\param attribute_tag the attribute tag +\param create_if_not_found if GF_TRUE, adds the attribute if not found +\param set_default if GF_TRUE, sets the attribute to default when creating it +\param field set to the attribute information +\return error if any +*/ +GF_Err gf_node_get_attribute_by_tag(GF_Node *n, u32 attribute_tag, Bool create_if_not_found, Bool set_default, GF_FieldInfo *field); + +/*! gets name of an attribute type +\param att_type the attribute type +\return the attribute name*/ +const char *gf_svg_attribute_type_to_string(u32 att_type); +/*! parses an attribute +\param n the target node +\param info the attribute information +\param attribute_content the UTF-8 string to parse +\param anim_value_type the SMIL animation value type for this attribute, 0 if not animatable +\return error if any +*/ GF_Err gf_svg_parse_attribute(GF_Node *n, GF_FieldInfo *info, char *attribute_content, u8 anim_value_type); +/*! parses an SVG style +\param n the target node +\param style the UTF-8 style string to parse +*/ void gf_svg_parse_style(GF_Node *n, char *style); -char *gf_svg_dump_attribute(GF_Node *elt, GF_FieldInfo *info); -char *gf_svg_dump_attribute_indexed(GF_Node *elt, GF_FieldInfo *info); +/*! dumps an SVG attribute +\param n the target node +\param info the attribute information +\return the textural representation of the value - shall be destroyed by caller +*/ +char *gf_svg_dump_attribute(GF_Node *n, GF_FieldInfo *info); +/*! dumps an SVG indexed attribute (special 1-D version of a N dimensional attribute, used by LASeR and DOM updates) +\param n the target node +\param info the attribute information +\return the textural representation of the value - shall be destroyed by caller +*/ +char *gf_svg_dump_attribute_indexed(GF_Node *n, GF_FieldInfo *info); +#if USE_GF_PATH +/*! builds a path from its SVG representation +\param path a 2D graphical path object +\param commands a list of SVG path commands +\param points a list of SVG path points +*/ void gf_svg_path_build(GF_Path *path, GF_List *commands, GF_List *points); - +#endif +/*! parses an SVG element ID +\param n the target node +\param nodename the node name to parse +\param warning_if_defined if GF_TRUE, throws a warning when the node name is already defined +\return error if any +*/ GF_Err gf_svg_parse_element_id(GF_Node *n, const char *nodename, Bool warning_if_defined); +/*! gets the name of a paint server type +\param paint_type the paint server type +\return paint server name*/ const char *gf_svg_get_system_paint_server_name(u32 paint_type); +/*! gets the type of a paint server by name +\param name the paint server name +\return paint server type*/ u32 gf_svg_get_system_paint_server_type(const char *name); - +/*! notifies the scene time to all the timed elements in the given scene graph (including sub-scenes). +\param sg the target scene graph +\return the number of active timed elements, or 0 if no changes in SMIL timing nodes (no redraw/reevaluate needed) +*/ Bool gf_smil_notify_timed_elements(GF_SceneGraph *sg); -void gf_smil_timing_insert_clock(GF_Node *elt, Bool is_end, Double clock); - +/*! inserts a new resolved time instant in the begin or end attribute. + The insertion preserves the sorting and removes the previous insertions which have become obsolete +\warning Only used for inserting time when an <a> element, whose target is a timed element, is activated +\param n the target element +\param is_end if GF_TRUE, the SMIL clock is an end clock +\param clock clock time in seconds + */ +void gf_smil_timing_insert_clock(GF_Node *n, Bool is_end, Double clock); +/*! parses a transformation list into a 2D matrix +\param mat the matrix to fill +\param attribute_content the UTF8 string representin the SVG transformation +\return GF_TRUE if success*/ Bool gf_svg_parse_transformlist(GF_Matrix2D *mat, char *attribute_content); +/*! SMIL runtime timing information*/ typedef struct _smil_timing_rti SMIL_Timing_RTI; -enum +/*! SMIL timing evaluation state*/ +typedef enum { SMIL_TIMING_EVAL_NONE = 0, SMIL_TIMING_EVAL_UPDATE, @@ -624,16 +1057,39 @@ enum SMIL_TIMING_EVAL_ACTIVATE, /*signaled the animation element has been removed from the DOM tree*/ SMIL_TIMING_EVAL_DEACTIVATE, -}; - -void gf_smil_set_evaluation_callback(GF_Node *smil_time, - void (*smil_evaluate)(struct _smil_timing_rti *rti, Fixed normalized_simple_time, u32 state)); +} GF_SGSMILTimingEvalState; +/*! SMIL timing evaluation callback +\param rti SMIL runtime info +\param normalized_simple_time SMIL normalized time +\param state SMIL evaluation state +*/ +typedef void gf_sg_smil_evaluate(struct _smil_timing_rti *rti, Fixed normalized_simple_time, GF_SGSMILTimingEvalState state); +/*! sets the SMIL evaluation callback for a node +\param smil_time the target SMIL node +\param smil_evaluate the callback function +*/ +void gf_smil_set_evaluation_callback(GF_Node *smil_time, gf_sg_smil_evaluate smil_evaluate); +/*! assigns media duration for a SMIL node +\param rti the target SMIL runtime info of the node +\param media_duration the media duration in seconds*/ void gf_smil_set_media_duration(SMIL_Timing_RTI *rti, Double media_duration); +/*! gets media duration of a SMIL node +\param rti the target SMIL runtime info of the node +\return the media duration in seconds*/ Double gf_smil_get_media_duration(SMIL_Timing_RTI *rti); +/*! gets node from a SMIL runtime info +\param rti the target SMIL runtime info of the node +\return the associated node*/ GF_Node *gf_smil_get_element(SMIL_Timing_RTI *rti); - +/*! checks if a SMIL node is active +\param node the target node +\return GF_TRUE if node is active*/ Bool gf_smil_timing_is_active(GF_Node *node); +/*! notifies a SMIL node that it has been modified +\param node the target SMIL node +\param field the field information describing the modified attribute, may be NULL +*/ void gf_smil_timing_modified(GF_Node *node, GF_FieldInfo *field); /******************************************************************************* @@ -643,25 +1099,61 @@ void gf_smil_timing_modified(GF_Node *node, GF_FieldInfo *field); *******************************************************************************/ /*SVG attributes are just DOM ones*/ +/*! SVG attribute - same as DOM attribute*/ typedef struct __dom_base_attribute SVGAttribute; +/*! SVG extended attribute - same as DOM extended attribute*/ typedef struct __dom_full_attribute SVGExtendedAttribute; +/*! SVG node - same as DOM node*/ typedef struct __dom_base_node SVG_Element; - +/*! SVG handler node - same as DOM Event handler*/ typedef struct __xml_ev_handler SVG_handlerElement; - - +/*! SVG all attributes*/ typedef struct _all_atts SVGAllAttributes; -void gf_svg_flatten_attributes(SVG_Element *e, SVGAllAttributes *all_atts); -const char *gf_svg_get_attribute_name(GF_Node *elt, u32 tag); +/*! flattens all SVG attributes in a structure +\param n the target SVG node +\param all_atts filled with all SVG attributes defined fo node +*/ +void gf_svg_flatten_attributes(SVG_Element *n, SVGAllAttributes *all_atts); +/*! gets SVG attribute name for a node +\param n the target SVG node +\param tag the attribute tag +\return the attribute name +*/ +const char *gf_svg_get_attribute_name(GF_Node *n, u32 tag); +/*! applies inheritance of SVG attributes to a render_svg_props +\note Some properties (audio-level, display, opacity, solid*, stop*, vector-effect, viewport*) are inherited only when they are specified with the value 'inherit' otherwise they default to their initial value which for the function below means NULL, the compositor will take care of the rest + +\param all_atts the flatten SVG attributes of the node +\param render_svg_props the set of properties applying to the parent node (initialize this one to 0 for root element) +\return the set of inherited flags (from node dirty flags) +*/ u32 gf_svg_apply_inheritance(SVGAllAttributes *all_atts, SVGPropertiesPointers *render_svg_props) ; -/*creates a DOMAttribute for the given tag - THE ATTRIOBUTE IS NOT ADDED TO THE NODE'S ATTRIBUTES*/ -GF_DOMAttribute *gf_xml_create_attribute(GF_Node *node, u32 tag); +/*! creates a DOM Attribute for the given tag +\warning the attribute is not added to the node's attributes +\param n the target node +\param tag the target built-in tag +\return a new DOM Attribute +*/ +GF_DOMAttribute *gf_xml_create_attribute(GF_Node *n, u32 tag); +/*! gets the type of a built-in XML/SVG/SMIL attribute tag +\param tag the target attribute tag +\return the attribute type*/ u32 gf_xml_get_attribute_type(u32 tag); -u32 gf_xml_get_attribute_tag(GF_Node *node, char *attribute_name, u32 ns); -u32 gf_xml_get_element_tag(const char *element_name, u32 xmlns); +/*! gets the built-in tag of a node's attribute +\param n the target node +\param attribute_name the target attribute name +\param ns the target namespace +\return the attribute tag*/ +u32 gf_xml_get_attribute_tag(GF_Node *n, char *attribute_name, GF_NamespaceType ns); + +/*! gets the built-in tag of a node +\param node_name the target node name +\param xmlns the target namespace +\return the node tag*/ +u32 gf_xml_get_element_tag(const char *node_name, u32 xmlns); /*! @} */ diff --git a/include/gpac/scenegraph_vrml.h b/include/gpac/scenegraph_vrml.h index d00fb07..5dc9b84 100644 --- a/include/gpac/scenegraph_vrml.h +++ b/include/gpac/scenegraph_vrml.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / Scene Graph sub-project @@ -32,17 +32,18 @@ extern "C" { #endif /*! - * \file <gpac/scenegraph_vrml.h> - * \brief Scenegraph for VRML files - */ +\file <gpac/scenegraph_vrml.h> +\brief Scenegraph for VRML files +*/ /*! - * \addtogroup svrml BIFS/VRML/X3D Scenegraph - * \ingroup scene_grp - * \brief Scenegraph for VRML files. - * - *This section documents the Scenegraph for VRML files. - * @{ +\addtogroup svrml BIFS/VRML/X3D Scenegraph +\ingroup scene_grp +\brief Scenegraph for VRML files. + +This section documents the Scenegraph for VRML files. + +@{ */ @@ -53,10 +54,10 @@ extern "C" { All extensions for VRML/MPEG-4/X3D graph structure */ -/*reserved NDT for MPEG4 (match binary coding)*/ +/*! reserved NDT for MPEG4 (match binary coding)*/ #define MPEG4_RESERVED_NDT 200 -/*the NDTs used in X3D not defined in MPEG4*/ +/*! the NDTs used in X3D not defined in MPEG4*/ enum { NDT_SFMetadataNode = MPEG4_RESERVED_NDT+1, @@ -74,8 +75,7 @@ enum VRML / BIFS TYPES DEFINITION */ -/* - event types, as defined in the specs +/*! event types of fields, as defined in the specs this should not be needed by non binary codecs */ enum @@ -86,10 +86,14 @@ enum GF_SG_EVENT_OUT = 3, GF_SG_EVENT_UNKNOWN = 4 }; +/*! gets the event type name +\param EventType the event type +\param forX3D for X3D dumping +\return the event name +*/ const char *gf_sg_vrml_get_event_type_name(u32 EventType, Bool forX3D); -/* - field coding mode +/*! field coding mode BIFS defines the bitstream syntax contextually, and therefore sometimes refer to fields as indexed in the node ("all" mode) or just as a sub-set (in, out, def, dyn modes) of similar types @@ -108,29 +112,41 @@ enum GF_SG_FIELD_CODING_DYN = 4 }; -/*get the number of field in the given mode (BIFS specific)*/ -u32 gf_node_get_num_fields_in_mode(GF_Node *Node, u8 IndexMode); +/*! gets the number of field in the given mode (BIFS specific) +\param n the target node +\param IndexMode the field indexing mode +\return the number of field in this mode*/ +u32 gf_node_get_num_fields_in_mode(GF_Node *n, u8 IndexMode); /* SF Types */ +/*! Boolean*/ typedef u32 SFBool; +/*! Integer*/ typedef s32 SFInt32; +/*! Integer*/ typedef s32 SFInt; +/*! Float*/ typedef Fixed SFFloat; +/*! Double*/ typedef Double SFDouble; +/*! String*/ typedef struct { char* buffer; } SFString; +/*! Time*/ typedef Double SFTime; +/*! RGB color*/ typedef struct { Fixed red; Fixed green; Fixed blue; } SFColor; +/*! RGBA color*/ typedef struct { Fixed red; Fixed green; @@ -138,16 +154,19 @@ typedef struct { Fixed alpha; } SFColorRGBA; +/*! URL*/ typedef struct { u32 OD_ID; char *url; } SFURL; +/*! 2D vector (double)*/ typedef struct { Double x; Double y; } SFVec2d; +/*! 3D vector (double)*/ typedef struct { Double x; Double y; @@ -155,30 +174,39 @@ typedef struct { } SFVec3d; /*typedef's to main math tools*/ + +/*! 2D vector (float)*/ typedef struct __vec2f SFVec2f; +/*! 3D vector (float)*/ typedef struct __vec3f SFVec3f; +/*! rotation (float)*/ typedef struct __vec4f SFRotation; +/*! 4D vector (float)*/ typedef struct __vec4f SFVec4f; +/*! Image data (rgb pixels)*/ typedef struct { u32 width; u32 height; u8 numComponents; unsigned char* pixels; } SFImage; +/*! BIFS Command Buffer*/ typedef struct { u32 bufferSize; - unsigned char* buffer; + u8* buffer; /*uncompressed command list*/ GF_List *commandList; } SFCommandBuffer; -/*Note on SFScript: the javascript or vrml script is handled in its decompressed (textual) format +/*! Script + Note: the javascript or vrml script is handled in its decompressed (textual) format since most JS interpreter work with text*/ typedef struct { char* script_text; } SFScript; +/*! BIFS Attribute Reference*/ typedef struct { GF_Node *node; u32 fieldIndex; @@ -186,92 +214,111 @@ typedef struct { /* MF Types */ -/*generic MF field: all MF fields use the same syntax except MFNode which uses GF_List. You can thus use +/*! generic MF field: all MF fields use the same syntax except MFNode which uses GF_List. You can thus use this structure to safely typecast MF field pointers*/ typedef struct { u32 count; - char *array; + u8 *array; } GenMFField; - +/*! Interger array*/ typedef struct { u32 count; s32* vals; } MFInt32; +/*! Interger array*/ typedef struct { u32 count; s32* vals; } MFInt; +/*! Float array*/ typedef struct { u32 count; Fixed *vals; } MFFloat; +/*! Double array*/ typedef struct { u32 count; Double *vals; } MFDouble; +/*! Boolean array*/ typedef struct { u32 count; u32* vals; } MFBool; +/*! Color RGB array*/ typedef struct { u32 count; SFColor* vals; } MFColor; +/*! Color RGBA array*/ typedef struct { u32 count; SFColorRGBA* vals; } MFColorRGBA; +/*! Rotation array*/ typedef struct { u32 count; SFRotation* vals; } MFRotation; +/*! Time array*/ typedef struct { u32 count; Double* vals; } MFTime; +/*! 2D Vector (float) array*/ typedef struct { u32 count; SFVec2f* vals; } MFVec2f; +/*! 2D Vector (double) array*/ typedef struct { u32 count; SFVec2d* vals; } MFVec2d; +/*! 3D Vector (float) array*/ typedef struct { u32 count; SFVec3f* vals; } MFVec3f; +/*! 3D Vector (double) array*/ typedef struct { u32 count; SFVec3d* vals; } MFVec3d; +/*! 4D Vector (float) array*/ typedef struct { u32 count; SFVec4f* vals; } MFVec4f; +/*! URL array*/ typedef struct { u32 count; SFURL* vals; } MFURL; +/*! String array*/ typedef struct { u32 count; char** vals; } MFString; +/*! Script array*/ typedef struct { u32 count; SFScript *vals; } MFScript; +/*! Attribute Reference array*/ typedef struct { u32 count; SFAttrRef* vals; } MFAttrRef; - +/*! converts an SFColor to an SFColorRGBA by setting the alpha component to 1 +\param val the input color +\return the RGBA color*/ SFColorRGBA gf_sg_sfcolor_to_rgba(SFColor val); -/*field types, as defined in BIFS encoding (used for scripts and proto coding)*/ +/*! field types, as defined in BIFS encoding (used for scripts and proto coding)*/ enum { GF_SG_VRML_SFBOOL = 0, @@ -339,167 +386,268 @@ enum GF_SG_VRML_UNKNOWN }; - +/*! checks if a field is an SF field +\param FieldType the tragte filed type +\return GF_TRUE if field is a single field*/ Bool gf_sg_vrml_is_sf_field(u32 FieldType); -/*translates MF/SF to SF type*/ +/*! translates MF/SF to SF type +\param FieldType the tragte filed type +\return SF field type*/ u32 gf_sg_vrml_get_sf_type(u32 FieldType); - -/*Insert (+alloc) a slot in the MFField with a specified position for insertion and sets the ptr -to the newly created slot -@InsertAt is the 0-based index for the new slot +/*! inserts (+alloc) a slot in the MFField with a specified position for insertion and sets the ptr to the newly created slot +\param mf pointer to the MF field +\param FieldType the MF field type +\param new_ptr set to the allocated slot (do not free) +\param InsertAt is the 0-based index for the new slot +\return error if any */ GF_Err gf_sg_vrml_mf_insert(void *mf, u32 FieldType, void **new_ptr, u32 InsertAt); -/*remove all items of the MFField*/ +/*! removes all items of the MFField +\param mf pointer to the MF field +\param FieldType the MF field type +\return error if any +*/ GF_Err gf_sg_vrml_mf_reset(void *mf, u32 FieldType); -/*exported for URL handling in compositor*/ +/*! deletes an MFUrl field +\note exported for URL handling in compositor +\param url the MF url field to reset +*/ void gf_sg_mfurl_del(MFURL url); +/*! copies MFUrl field +\note exported for URL handling in compositor +\param dst the destination MF url field to copy +\param src the source MF url field to copy +*/ void gf_sg_vrml_copy_mfurl(MFURL *dst, MFURL *src); -/*exported for 3D camera in compositor*/ -SFRotation gf_sg_sfrotation_interpolate(SFRotation kv1, SFRotation kv2, Fixed fraction); - -/*adds a new node to the "children" field -position is the 0-BASED index in the list of children, -1 means end of list (append) -DOES NOT CHECK CHILD/PARENT type compatibility +/*! interpolates SFRotation +\note exported for 3D camera in compositor +\param kv1 start value for interpolation +\param kv2 end value for interpolation +\param f interpolation factor +\return the interpolated result +*/ +SFRotation gf_sg_sfrotation_interpolate(SFRotation kv1, SFRotation kv2, Fixed f); + +/*! adds a new node to the "children" field +\warning DOES NOT CHECK CHILD/PARENT type compatibility +\param parent the target parent node +\param new_child the child to insert +\param pos the 0-BASED index in the list of children, -1 means end of list (append) +\return error if any */ -GF_Err gf_node_insert_child(GF_Node *parent, GF_Node *new_child, s32 Position); -/*removes an existing node from the "children" field*/ -GF_Err gf_node_remove_child(GF_Node *parent, GF_Node *toremove_child); -/*remove and replace given child by specified node. If node is NULL, only delete target node -position is the 0-BASED index in the list of children, -1 means end of list (append) -DOES NOT CHECK CHILD/PARENT type compatibility +GF_Err gf_node_insert_child(GF_Node *parent, GF_Node *new_child, s32 pos); + +/*! removes and replace given child by specified node. If node is NULL, only delete target node +\warning DOES NOT CHECK CHILD/PARENT type compatibility +\param node the target node to replace +\param container the container list +\param pos the 0-BASED index in the list of children, -1 means end of list (append) +\param newNode the new node to use +\return error if any */ GF_Err gf_node_replace_child(GF_Node *node, GF_ChildNodeItem **container, s32 pos, GF_Node *newNode); -/*set proto loader - callback is the same as simulation time callback - GetExternProtoLib is a pointer to the proto lib loader - this callback shall return the LPSCENEGRAPH -of the extern proto lib if found and loaded, NULL if not found and GF_SG_INTERNAL_PROTO for internal -hardcoded protos (extensions of MPEG-4 scene graph used for module deveopment) -*/ + +/*! internal prototype*/ #define GF_SG_INTERNAL_PROTO (PTR_TO_U_CAST -1) #ifndef GPAC_DISABLE_VRML - - -/*VRML grouping nodes macro - note we have inverted the children field to be +/*! VRML grouping nodes macro - note we have inverted the children field to be compatible with the base GF_ParentNode node All grouping nodes (with "children" field) implement the following: addChildren: chain containing nodes to add passed as eventIn - handled internally through ROUTE void (*on_addChildren)(GF_Node *pNode): add feventIn signaler - this is handled internally by the scene_graph and SHALL -NOT BE OVERRIDEN since it takes care of node(s) routing +NOT BE overridden since it takes care of node(s) routing removeChildren: chain containing nodes to remove passed as eventIn - handled internally through ROUTE void (*on_removeChildren)(GF_Node *pNode): remove eventIn signaler - this is handled internally by the scene_graph and SHALL -NOT BE OVERRIDEN since it takes care of node(s) routing +NOT BE overridden since it takes care of node(s) routing children: list of children SFNodes */ - #define VRML_CHILDREN \ CHILDREN \ GF_ChildNodeItem *addChildren; \ void (*on_addChildren)(GF_Node *pNode, struct _route *route); \ GF_ChildNodeItem *removeChildren; \ void (*on_removeChildren)(GF_Node *pNode, struct _route *route); \ - + +/*! generic VRML parent node*/ typedef struct { BASE_NODE VRML_CHILDREN } GF_VRMLParent; -void gf_sg_vrml_parent_setup(GF_Node *pNode); -void gf_sg_vrml_parent_destroy(GF_Node *pNode); - - +/*! setup a vrml parent +\param n the target node*/ +void gf_sg_vrml_parent_setup(GF_Node *n); +/*! resets all children in a vrml parent node (but does not destroy the node) +\param n the target node*/ +void gf_sg_vrml_parent_destroy(GF_Node *n); + +/*! checks if a given node tag is in a given NDT table +\param tag the node tag +\param NDTType the NDT type +\return GF_TRUE if node tag is in the NDT*/ Bool gf_node_in_table_by_tag(u32 tag, u32 NDTType); - - -const char *gf_sg_vrml_get_field_type_by_name(u32 FieldType); - - -/* -allocates a new field and gets it back. - NOTE: - GF_SG_VRML_MFNODE will return a pointer to a GF_List structure (eg GF_List *) - GF_SG_VRML_SFNODE will return NULL -*/ +/*! gets field type name +\param FieldType the field type +\return the field name*/ +const char *gf_sg_vrml_get_field_type_name(u32 FieldType); + +/*! allocates a new field +\note GF_SG_VRML_MFNODE will return a pointer to a GF_List structure (eg GF_List *), GF_SG_VRML_SFNODE will return NULL +\param FieldType the field type +\return the new field pointer*/ void *gf_sg_vrml_field_pointer_new(u32 FieldType); -/*deletes a field pointer (including SF an,d MF nodes)*/ +/*! deletes a field pointer (including SF an,d MF nodes) +\param field the field pointer value +\param FieldType the field type +*/ void gf_sg_vrml_field_pointer_del(void *field, u32 FieldType); - -/*adds at the end and gets the ptr*/ +/*! adds at the end of an MF field +\param mf pointer to the MF field +\param FieldType the MF field type +\param new_ptr set to the allocated SF field slot - do not destroy +\return error if any +*/ GF_Err gf_sg_vrml_mf_append(void *mf, u32 FieldType, void **new_ptr); -/*remove the desired item*/ +/*! removes the desired item of an MF field +\param mf pointer to the MF field +\param FieldType the MF field type +\param RemoveFrom the 0-based index of item to remove +\return error if any +*/ GF_Err gf_sg_vrml_mf_remove(void *mf, u32 FieldType, u32 RemoveFrom); -/*alloc a fixed array*/ +/*! allocates an MF array +\param mf pointer to the MF field +\param FieldType the MF field type +\param NbItems number of items to allocate +\return error if any +*/ GF_Err gf_sg_vrml_mf_alloc(void *mf, u32 FieldType, u32 NbItems); -/*get the item in the array*/ +/*! gets the item in the MF array +\param mf pointer to the MF field +\param FieldType the MF field type +\param new_ptr set to the SF field slot - do not destroy +\param ItemPos the 0-based index of item to remove +\return error if any +*/ GF_Err gf_sg_vrml_mf_get_item(void *mf, u32 FieldType, void **new_ptr, u32 ItemPos); -/*copies a field content EXCEPT SF/MFNode. Pointers to field shall be used -@dest, @orig: pointers to field -@FieldType: type of the field +/*! copies a field content EXCEPT SF/MFNode. Pointers to field shall be used +\param dest pointer to the MF field +\param orig pointer to the MF field +\param FieldType the MF field type */ void gf_sg_vrml_field_copy(void *dest, void *orig, u32 FieldType); -/*clones a field content EXCEPT SF/MFNode. Pointers to field shall be used -@dest, @orig: pointers to field -@FieldType: type of the field -@inScene: target scene graph for SFCommandBuffers cloning +/*! clones a field content EXCEPT SF/MFNode. Pointers to field shall be used +\param dest pointer to the MF field +\param orig pointer to the MF field +\param FieldType the MF field type +\param inScene target scene graph for SFCommandBuffers cloning */ void gf_sg_vrml_field_clone(void *dest, void *orig, u32 FieldType, GF_SceneGraph *inScene); -/*indicates whether 2 fields of same type EXCEPT SF/MFNode are equal -@dest, @orig: pointers to field -@FieldType: type of the field +/*! indicates whether 2 fields of same type EXCEPT SF/MFNode are equal +\param dest pointer to the MF field +\param orig pointer to the MF field +\param FieldType the MF field type +\return GF_TRUE if fields equal */ Bool gf_sg_vrml_field_equal(void *dest, void *orig, u32 FieldType); -/*GF_Route manip: routes are used to pass events between nodes. Event handling is managed by the scene graph +/*GF_Route manipultaion : routes are used to pass events between nodes. Event handling is managed by the scene graph however only the nodes overloading the EventIn handler associated with the event will process the eventIn*/ -/*creates a new route: - @fromNode: @fromField: address of the eventOut field triggering the route - @toNode: @toField: address of the destination eventIn field -NOTE: routes are automatically destroyed if either the target or origin node of the route is destroyed +/*! creates a new route +\note routes are automatically destroyed if either the target or origin node of the route is destroyed +\param sg the target scene graph of the route +\param fromNode the source node triggering the event out +\param fromField the source field triggering the event out +\param toNode the destination node accepting the event in +\param toField the destination field accepting the event in +\return a new route object */ GF_Route *gf_sg_route_new(GF_SceneGraph *sg, GF_Node *fromNode, u32 fromField, GF_Node *toNode, u32 toField); -/*delete route*/ +/*! destroys a route +\param route the target route +*/ void gf_sg_route_del(GF_Route *route); +/*! destroys a route by ID +\param sg the scene graph of the route +\param routeID the ID of the route to destroy +\return error if any +*/ GF_Err gf_sg_route_del_by_id(GF_SceneGraph *sg,u32 routeID); -/*locate route by ID/name*/ +/*! locate a route by ID +\param sg the scene graph of the route +\param RouteID the ID of the route +\return the route object or NULL if not found +*/ GF_Route *gf_sg_route_find(GF_SceneGraph *sg, u32 RouteID); +/*! locate a route by name +\param sg the scene graph of the route +\param name the name of the route +\return the route object or NULL if not found +*/ GF_Route *gf_sg_route_find_by_name(GF_SceneGraph *sg, char *name); -/*assign route ID - fails if a route with same ID already exist*/ +/*! assigns a route ID +\param route the target route +\param ID the ID to assign +\return error if any - fails if a route with same ID already exists*/ GF_Err gf_sg_route_set_id(GF_Route *route, u32 ID); -u32 gf_sg_route_get_id(GF_Route *route); -/*assign route name if desired*/ + +/*! assign a route name +\param route the target route +\param name the name to assign +\return error if any - fails if a route with same name already exists +*/ GF_Err gf_sg_route_set_name(GF_Route *route, char *name); +/*! gets route name +\param route the target route +\return the route name or NULL if not set +*/ char *gf_sg_route_get_name(GF_Route *route); -/*retuns next available RouteID - Note this doesn't track inserted routes, that's the user responsability*/ +/*! retuns next available RouteID +\note this doesn't track inserted routes, that's the caller responsability +\param sg the target scene graph of the route +\return the next available ID for routes +*/ u32 gf_sg_get_next_available_route_id(GF_SceneGraph *sg); -/*set max defined route ID used in the scene - used to handle RouteInsert commands +/*! sets max defined route ID used in the scene - used to handle RouteInsert commands note that this must be called by the user to be effective,; otherwise the max route ID is computed -from the routes present in scene*/ +from the routes present in scene +\param sg the target scene graph of the route +\param ID the value of the max defined route ID +*/ void gf_sg_set_max_defined_route_id(GF_SceneGraph *sg, u32 ID); -/*create a new route from a node output to a given callback/function*/ +/*! creates a new route from a node output to a given callback/function +\param sg the target scene graph of the route +\param fromNode the source node emiting the event out +\param fromField the source field emiting the event out +\param cbk opaque data to pass to the callback +\param route_callback route callback function to call +*/ void gf_sg_route_new_to_callback(GF_SceneGraph *sg, GF_Node *fromNode, u32 fromField, void *cbk, void ( *route_callback) (void *param, GF_FieldInfo *from_field) ); -/*activates all routes currently triggered - this follows the event cascade model of VRML/MPEG4: +/*! activates all routes currently triggered - this follows the event cascade model of VRML/MPEG4: - routes are collected during eventOut generation - routes are activated. If eventOuts are generated during activation the cycle goes on. @@ -507,7 +655,9 @@ void gf_sg_route_new_to_callback(GF_SceneGraph *sg, GF_Node *fromNode, u32 fromF ONCE AND ONLY ONCE per simulation tick Note that children scene graphs register their routes with the top-level graph, so only the main -scene graph needs to be activated*/ +scene graph needs to be activated +\param sg the target scene graph of the route +*/ void gf_sg_activate_routes(GF_SceneGraph *sg); @@ -515,64 +665,99 @@ void gf_sg_activate_routes(GF_SceneGraph *sg); proto handling The lib allows you to construct prototype nodes as defined in VRML/MPEG4 by constructing - proto interfaces and instanciating them. An instanciated proto is handled as a single node for + proto interfaces and instantiating them. An instantiated proto is handled as a single node for rendering, thus an application will never handle proto instances for rendering */ -/*opaque handler for a proto object (declaration)*/ +/*! proto object*/ typedef struct _proto GF_Proto; -/*opaque handler for a proto field object (declaration)*/ +/*! proto field object*/ typedef struct _protofield GF_ProtoFieldInterface; -/*retuns next available NodeID*/ +/*! retuns next available proto ID +\param sg the target scene graph of the proto +\return the next available proto ID +*/ u32 gf_sg_get_next_available_proto_id(GF_SceneGraph *sg); -/*proto constructor identified by ID/name in the given scene +/*! constructs a new proto identified by ID/name in the given scene 2 protos in the same scene may not have the same ID/name -@unregistered: used for memory handling of scene graph only, the proto is not stored -in the graph main proto list but in an alternate list. Several protos with the same ID/Name can be stored unregistered +\param sg the target scene graph in which the proto is created +\param ProtoID ID of the proto to create +\param name name of the proto to create +\param unregistered if GF_TRUE, the proto is not stored in the graph main proto list but in an alternate list (used for memory handling of scene graph only). Several protos with the same ID/Name can be stored unregistered +\return a new proto object */ -GF_Proto *gf_sg_proto_new(GF_SceneGraph *inScene, u32 ProtoID, char *name, Bool unregistered); +GF_Proto *gf_sg_proto_new(GF_SceneGraph *sg, u32 ProtoID, char *name, Bool unregistered); -/*destroy proto interface - can be used even if instances of the proto are still present*/ +/*! destroys a proto - can be used even if instances of the proto are still present +\param proto the target proto +\return error if any +*/ GF_Err gf_sg_proto_del(GF_Proto *proto); -/*used for memory handling of scene graph only. move proto from off-graph to in-graph or reverse*/ -GF_Err gf_sg_proto_set_in_graph(GF_Proto *proto, GF_SceneGraph *inScene, Bool set_in); - -/*returns graph associated with this proto. Such a graph cannot be used for rendering but is needed during -construction of proto dictionaries in case of nested protos*/ +/*! returns the graph associated with this proto. Such a graph cannot be used for rendering but is needed during +construction of proto dictionaries in case of nested protos +\param proto the target proto +\return associated scene graph proto the target proto +*/ GF_SceneGraph *gf_sg_proto_get_graph(GF_Proto *proto); -/*get/set private data*/ -void gf_sg_proto_set_private(GF_Proto *proto, void *ptr, void (*OnDelete)(void *ptr) ); -void *gf_sg_proto_get_private(GF_Proto *proto); - -/*add node code - a proto is build of several nodes, the first node is used for rendering -and the others are kept private. This set of nodes is refered to as the proto "node code"*/ -GF_Err gf_sg_proto_add_node_code(GF_Proto *proto, GF_Node *pNode); +/*! adds node code - a proto is build of several nodes, the first node is used for rendering +and the others are kept private. This set of nodes is refered to as the proto "node code" +\param proto the target proto +\param n the node to add to the proto code +\return error if any +*/ +GF_Err gf_sg_proto_add_node_code(GF_Proto *proto, GF_Node *n); -/*gets number of field in the proto interface*/ +/*! gets number of field in the proto interface +\param proto the target proto +\return the number of fields +*/ u32 gf_sg_proto_get_field_count(GF_Proto *proto); -/*locates field declaration by name*/ +/*! locates a field declaration by name +\param proto the target proto +\param fieldName the name of the field +\return the proto field interface or NULL if not found +*/ GF_ProtoFieldInterface *gf_sg_proto_field_find_by_name(GF_Proto *proto, char *fieldName); -/*locates field declaration by index (0-based)*/ +/*! locates field declaration by index +\param proto the target proto +\param fieldIndex 0-based index of the field to query +\return the proto field interface or NULL if not found +*/ GF_ProtoFieldInterface *gf_sg_proto_field_find(GF_Proto *proto, u32 fieldIndex); -/*creates a new field declaration in the proto. of given fieldtype and eventType -fieldName can be NULL, if so the name will be fieldN, N being the index of the created field*/ +/*! creates a new field declaration in the proto. of given fieldtype and eventType +fieldName can be NULL, if so the name will be fieldN, N being the index of the created field +\param proto the target proto +\param fieldType the data type of the field to create +\param eventType the event type of the field to create +\param fieldName the name of the field to create (may be NULL) +\return the new proto field interface +*/ GF_ProtoFieldInterface *gf_sg_proto_field_new(GF_Proto *proto, u32 fieldType, u32 eventType, char *fieldName); -/*assign the node field to a field of the proto (the node field IS the proto field) +/*! assigns the node field to a field of the proto (the node field IS the proto field) the node shall be a node of the proto scenegraph, and the fieldtype/eventType of both fields shall match -(except SF/MFString and MF/SFURL which are allowed) due to BIFS semantics*/ +(except SF/MFString and MF/SFURL which are allowed) due to BIFS semantics + +\param proto the target proto +\param protoFieldIndex the proto field index to assign +\param node the node (shall be part of the proto node code) to link to +\param nodeFieldIndex the field index of the node to link to +\return error if any +*/ GF_Err gf_sg_proto_field_set_ised(GF_Proto *proto, u32 protoFieldIndex, GF_Node *node, u32 nodeFieldIndex); -/*set/get user private data for the proto field declaration*/ -void gf_sg_proto_field_set_private(GF_ProtoFieldInterface *field, void *ptr, void (*OnDelete)(void *ptr) ); -void *gf_sg_proto_field_get_private(GF_ProtoFieldInterface *field); -/*returns field info of the field - this is typically used to setup the default value of the field*/ + +/*! returns field info of the field - this is typically used to setup the default value of the field +\param field the proto field interface to query +\param info filled with the proto field interface info +\return error if any +*/ GF_Err gf_sg_proto_field_get_field(GF_ProtoFieldInterface *field, GF_FieldInfo *info); /* @@ -586,86 +771,155 @@ GF_Err gf_sg_proto_field_get_field(GF_ProtoFieldInterface *field, GF_FieldInfo * when nested protos (protos used as building blocks of their parent proto) are used. */ -/*creates the proto interface without the proto code.*/ +/*! creates the proto instance without the proto code +\param sg the target scene graph of the node +\param proto the proto to instanciate +\return the new prototype instance node +*/ GF_Node *gf_sg_proto_create_instance(GF_SceneGraph *sg, GF_Proto *proto); -/*lodes code in this instance - all subprotos are automatically created, thus you must only instanciate +/*! loads code in this instance - all subprotos are automatically created, thus you must only instantiate top-level protos. VRML/BIFS doesn't allow for non top-level proto instanciation in the main graph -All nodes created in this proto will be forwarded to the app for initialization*/ +All nodes created in this proto will be forwarded to the app for initialization +\param proto_inst the proto instance to load +\return error if any +*/ GF_Err gf_sg_proto_load_code(GF_Node *proto_inst); -/*locate a prototype definition by ID or by name. when looking by name, ID is ignored*/ +/*! locates a prototype definition by ID or by name. when looking by name, ID is ignored +\param sg the target scene graph of the proto +\param ProtoID the ID of the proto to locate +\param name the name of the proto to locate +\return the proto node or NULL if not found +*/ GF_Proto *gf_sg_find_proto(GF_SceneGraph *sg, u32 ProtoID, char *name); -/*deletes all protos in given scene - does NOT delete instances of protos, only the proto object is destroyed */ -GF_Err gf_sg_delete_all_protos(GF_SceneGraph *scene); - +/*! deletes all protos in given scene - does NOT delete instances of protos, only the proto object is destroyed +\param sg the target scene graph +\return error if any +*/ +GF_Err gf_sg_delete_all_protos(GF_SceneGraph *sg); -/*tools for hardcoded proto*/ -/*gets proto of this node - if the node is not a prototype instance, returns NULL*/ +/*! gets proto of a prototype instance node +\param node the target prototype instance node +\return the proto node or NULL if the node is not a prototype instance or the source proto was destroyed*/ GF_Proto *gf_node_get_proto(GF_Node *node); -/*returns the ID of the proto*/ +/*! returns the ID of a proto +\param proto the target proto +\return the proto ID +*/ u32 gf_sg_proto_get_id(GF_Proto *proto); -/*returns proto name*/ +/*! returns the name of a proto +\param proto the target proto +\return the proto name +*/ const char *gf_sg_proto_get_class_name(GF_Proto *proto); -/*Returns 1 if the given field is ISed to a startTime/stopTime field (MPEG-4 specific for updates)*/ +/*! checks if a proto instance field is an SFTime routed to a startTime/stopTime field in the proto code (MPEG-4 specific for updates) +\param node the target prototype instance node +\param field the target field info +\return GF_TRUE if this is the case +*/ Bool gf_sg_proto_field_is_sftime_offset(GF_Node *node, GF_FieldInfo *field); -/*set an ISed field in a proto instance (not a proto) - this is needed with dynamic node creation inside a proto -instance (conditionals)*/ +/*! sets an ISed (route between proto instance and internal proto code) field in a proto instance (not a proto) - this is needed with dynamic node creation inside a proto instance (conditionals) +\param protoinst the target prototype instance node +\param protoFieldIndex field index in prototype instance node +\param node the target node +\param nodeFieldIndex field index in the target node +\return error if any +*/ GF_Err gf_sg_proto_instance_set_ised(GF_Node *protoinst, u32 protoFieldIndex, GF_Node *node, u32 nodeFieldIndex); -/*returns root node (the one and only one being traversed) of this proto instance if any*/ +/*! returns root node (the one and only one being traversed) of this proto instance if any +\param node the target prototype instance node +\return the root node of the proto code - may be NULL +*/ GF_Node *gf_node_get_proto_root(GF_Node *node); -/*returns parent ProtoInstance node if this node is in a proto*/ -GF_Node *gf_node_get_proto_parent(GF_Node *node); +/*! indicates proto field has been parsed and its value is valid - this is needed for externProtos not specifying default +values +\param node the target prototype instance node +\param info the target field info +*/ +void gf_sg_proto_mark_field_loaded(GF_Node *node, GF_FieldInfo *info); + + +/*! sets proto loader callback - callback user data is the same as simulation time callback + +GetExternProtoLib is a pointer to the proto lib loader - this callback shall return the LPSCENEGRAPH +of the extern proto lib if found and loaded, NULL if not found and GF_SG_INTERNAL_PROTO for internal +hardcoded protos (extensions of MPEG-4 scene graph used for module deveopment) +\param sg the target scene graph +\param GetExternProtoLib the callback function +*/ +void gf_sg_set_proto_loader(GF_SceneGraph *sg, GF_SceneGraph *(*GetExternProtoLib)(void *SceneCallback, MFURL *lib_url)); + +/*! gets a pointer to the MF URL field for externProto info - DO NOT TOUCH THIS FIELD +\param proto the target proto field +\return the MFURL associated with an extern proto, or NULL if proto is not an extern proto +*/ +MFURL *gf_sg_proto_get_extern_url(GF_Proto *proto); -/*indicates proto field has been parsed and its value is valid - this is needed for externProtos not specifying default -values*/ -void gf_sg_proto_mark_field_loaded(GF_Node *proto_inst, GF_FieldInfo *info); /* JavaScript tools */ -/*script fields type don't have the same value as the bifs ones...*/ +/*! script fields type don't have the same value as the bifs ones...*/ enum { GF_SG_SCRIPT_TYPE_FIELD = 0, GF_SG_SCRIPT_TYPE_EVENT_IN, GF_SG_SCRIPT_TYPE_EVENT_OUT, }; - +/*! script field object*/ typedef struct _scriptfield GF_ScriptField; -/*creates new sript field - script fields are dynamically added to the node, and thus can be accessed through the -same functions as other GF_Node fields*/ +/*! creates new sript field - script fields are dynamically added to the node, and thus can be accessed through the +same functions as other GF_Node fields +\param script the script node +\param eventType the event type of the new field +\param fieldType the data type of the new field +\param name the name of the new field +\return a new scritp field +*/ GF_ScriptField *gf_sg_script_field_new(GF_Node *script, u32 eventType, u32 fieldType, const char *name); -/*retrieves field info, useful to get the field index*/ +/*! retrieves field info of a script field object, useful to get the field index +\param field the script field to query +\param info filled with the field info +\return error if any +*/ GF_Err gf_sg_script_field_get_info(GF_ScriptField *field, GF_FieldInfo *info); -/*activate eventIn for script node - needed for BIFS field replace*/ -void gf_sg_script_event_in(GF_Node *node, GF_FieldInfo *in_field); - - - -/*set the scene proto loader function for externProto - callback is the same as the scene callback*/ -void gf_sg_set_proto_loader(GF_SceneGraph *scene, GF_SceneGraph *(*GetExternProtoLib)(void *SceneCallback, MFURL *lib_url)); - -/*get a pointer to the MF URL field for externProto info - DO NOT TOUCH THIS FIELD*/ -MFURL *gf_sg_proto_get_extern_url(GF_Proto *proto); +/*! activates eventIn for script node - needed for BIFS field replace +\param script the target script node +\param in_field the field info of the activated event in field +*/ +void gf_sg_script_event_in(GF_Node *script, GF_FieldInfo *in_field); -/*signals eventOut has been set. FieldIndex/eventName identify the eventOut field. Routes are automatically triggered -when the event is signaled*/ -void gf_node_event_out(GF_Node *node, u32 FieldIndex); -void gf_node_event_out_str(GF_Node *node, const char *eventName); +/*! signals eventOut has been set by field index +\note Routes are automatically triggered when the event is signaled +\param n the target node emitin the event +\param FieldIndex the field index emiting the event +*/ +void gf_node_event_out(GF_Node *n, u32 FieldIndex); +/*! signals eventOut has been set by event name. +\note Routes are automatically triggered when the event is signaled +\param n the target node emitin the event +\param eventName the name of the field emiting the event +*/ +void gf_node_event_out_str(GF_Node *n, const char *eventName); -/*exported for parsers*/ +/*! gets MPEG-4 / VRML node tag by class name +\param node_name the node name +\return the node tag*/ u32 gf_node_mpeg4_type_by_class_name(const char *node_name); #ifndef GPAC_DISABLE_X3D +/*! gets X3D node tag by class name +\param node_name the node name +\return the node tag*/ u32 gf_node_x3d_type_by_class_name(const char *node_name); #endif @@ -673,14 +927,24 @@ u32 gf_node_x3d_type_by_class_name(const char *node_name); #endif /*GPAC_DISABLE_VRML*/ -/*returns 1 if proto is a hardcoded proto acting as a grouping node*/ -Bool gf_node_proto_is_grouping(GF_Node *node); +/*! check if a hardcoded prototype node acts as a grouping node +\param n the target prototype instance node +\return GF_TRUE if acting as a grouping node*/ +Bool gf_node_proto_is_grouping(GF_Node *n); -/*tags a hardcoded proto as being a grouping node*/ -GF_Err gf_node_proto_set_grouping(GF_Node *node); +/*! tags a hardcoded proto as being a grouping node +\param n the target prototype instance node +\return error if any +*/ +GF_Err gf_node_proto_set_grouping(GF_Node *n); -/*assigns callback to an eventIn field of an hardcoded proto*/ -GF_Err gf_node_set_proto_eventin_handler(GF_Node *node, u32 fieldIndex, void (*event_in_cbk)(GF_Node *pThis, struct _route *route) ); +/*! assigns callback to an eventIn field of an hardcoded proto +\param n the target prototype instance node +\param fieldIndex the target field index +\param event_in_cbk the event callback function +\return error if any +*/ +GF_Err gf_node_set_proto_eventin_handler(GF_Node *n, u32 fieldIndex, void (*event_in_cbk)(GF_Node *pThis, struct _route *route) ); /*! @} */ diff --git a/include/gpac/setup.h b/include/gpac/setup.h index 2d8bc1a..cc101f3 100644 --- a/include/gpac/setup.h +++ b/include/gpac/setup.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / general OS configuration file @@ -31,20 +31,25 @@ extern "C" { #endif -/*! \file "gpac/setup.h" - * \brief base data types of GPAC. - * - * This file contains the base data types of GPAC, depending on the platform. +/*! +\file "gpac/setup.h" +\brief Base data types of GPAC. + +This file contains the base data types of GPAC, depending on the platform. + + */ -/*! \addtogroup setup_grp base data types - * \ingroup utils_grp - * \brief Base data types of GPAC. - * - * This section documents the base data types of GPAC. - * @{ - */ +/*! +\addtogroup setup_grp +\brief Base data types of GPAC. + +This section documents the base data types of GPAC, as well as some macros wrapping platform-specific functionalities. +For better portability, only use the base data types defined here. + +@{ +*/ /*This is to handle cases where config.h is generated at the root of the gpac build tree (./configure) This is only needed when building libgpac and modules when libgpac is not installed*/ @@ -61,27 +66,41 @@ This is only needed when building libgpac and modules when libgpac is not instal /*common win32 parts*/ #include <stdio.h> #include <stdlib.h> +#include <stddef.h> #if defined(_WIN64) && !defined(GPAC_64_BITS) +/*! macro defined for 64-bits platforms*/ #define GPAC_64_BITS #endif +/*! 64 bit unsigned integer*/ typedef unsigned __int64 u64; -typedef unsigned int u32; -typedef unsigned short u16; -typedef unsigned char u8; +/*! 64 bit signed integer*/ typedef __int64 s64; +/*! 32 bit unsigned integer*/ +typedef unsigned int u32; +/*! 32 bit signed integer*/ typedef int s32; +/*! 16 bit unsigned integer*/ +typedef unsigned short u16; +/*! 16 bit signed integer*/ typedef short s16; +/*! 8 bit unsigned integer*/ +typedef unsigned char u8; +/*! 8 bit signed integer*/ typedef char s8; #if defined(__GNUC__) +/*! macro for cross-platform inlining of functions*/ #define GFINLINE inline #else +/*! macro for cross-platform inlining of functions*/ #define GFINLINE __inline #endif +/*! default path separator of the current platform*/ #define GF_PATH_SEPARATOR '\\' +/*! default max filesystem path size of the current platform*/ #define GF_MAX_PATH 1024 /*WINCE config*/ @@ -89,9 +108,7 @@ typedef char s8; /*win32 assert*/ #ifndef assert - void CE_Assert(u32 valid, char *file, u32 line); - #ifndef NDEBUG #define assert( t ) CE_Assert((unsigned int) (t), __FILE__, __LINE__ ) #else @@ -158,14 +175,19 @@ typedef unsigned int size_t; /*start SYMBIAN config*/ #elif defined(__SYMBIAN32__) +/*! macro for cross-platform inlining of functions*/ #define GFINLINE inline +/*! default path separator of the current platform*/ #define GF_PATH_SEPARATOR '\\' /*we must explicitely export our functions...*/ + +/*! macro for cross-platform signaling of exported function of libgpac*/ #define GF_EXPORT EXPORT_C #include <stdio.h> #include <stdlib.h> +#include <stddef.h> #include <stdarg.h> #include <limits.h> #include <ctype.h> @@ -174,7 +196,9 @@ typedef unsigned int size_t; #ifdef __SERIES60_3X__ +/*! 64 bit unsigned integer*/ typedef unsigned __int64 u64; +/*! 64 bit signed integer*/ typedef __int64 s64; #else @@ -185,21 +209,30 @@ typedef unsigned long long u64; typedef long long s64; */ +/*! 64 bit unsigned integer*/ typedef unsigned int u64; +/*! 64 bit signed integer*/ typedef signed int s64; #endif /*symbian 8*/ +/*! 32 bit unsigned integer*/ typedef unsigned int u32; -typedef unsigned short u16; -typedef unsigned char u8; +/*! 32 bit signed integer*/ typedef int s32; +/*! 16 bit unsigned integer*/ +typedef unsigned short u16; +/*! 16 bit signed integer*/ typedef short s16; +/*! 8 bit unsigned integer*/ +typedef unsigned char u8; +/*! 8 bit signed integer*/ typedef signed char s8; #pragma mpwc_relax on +/*! default max filesystem path size of the current platform*/ #define GF_MAX_PATH 260 /*sorry this was developed under w32 :)*/ @@ -236,13 +269,15 @@ char * my_str_lwr(char *str); /*UNIX likes*/ -/*force large file support*/ +/*! max file offset bits*/ #ifndef _FILE_OFFSET_BITS #define _FILE_OFFSET_BITS 64 #endif +/*! largefile*/ #ifndef _LARGEFILE_SOURCE #define _LARGEFILE_SOURCE #endif +/*! largefile64*/ #ifndef _LARGEFILE64_SOURCE #define _LARGEFILE64_SOURCE #endif @@ -250,6 +285,7 @@ char * my_str_lwr(char *str); #include <stdio.h> #include <stdint.h> #include <stdlib.h> +#include <stddef.h> #include <stdarg.h> #include <limits.h> #include <float.h> @@ -257,202 +293,245 @@ char * my_str_lwr(char *str); #include <string.h> #include <assert.h> -#if __APPLE__ && defined GPAC_IPHONE +#if __APPLE__ && defined GPAC_CONFIG_IOS #include <TargetConditionals.h> #endif +/*! 64 bit unsigned integer*/ typedef uint64_t u64; -typedef uint32_t u32; -typedef uint16_t u16; -typedef uint8_t u8; +/*! 64 bit signed integer*/ typedef int64_t s64; +/*! 32 bit unsigned integer*/ +typedef uint32_t u32; +/*! 32 bit signed integer*/ typedef int32_t s32; +/*! 16 bit unsigned integer*/ +typedef uint16_t u16; +/*! 16 bit signed integer*/ typedef int16_t s16; +/*! 8 bit unsigned integer*/ +typedef uint8_t u8; +/*! 8 bit signed integer*/ typedef int8_t s8; +/*! macro for cross-platform inlining of functions*/ #define GFINLINE inline -/*sorry this was developed under w32 :)*/ +/*! use stricmp */ #define stricmp strcasecmp +/*! use strnicmp */ #define strnicmp strncasecmp #ifndef strupr -char * my_str_upr(char *str); +/*! gets upper case +\param str input string +\return uper case to free*/ +char *my_str_upr(char *str); +/*! use strupr */ #define strupr my_str_upr #endif #ifndef strlwr +/*! gets lower case +\param str input string +\return lower case to free*/ char * my_str_lwr(char *str); +/*! use strulwr */ #define strlwr my_str_lwr #endif +/*! default path separator of the current platform*/ #define GF_PATH_SEPARATOR '/' #ifdef PATH_MAX +/*! default max filesystem path size of the current platform*/ #define GF_MAX_PATH PATH_MAX #else -/*PATH_MAX not defined*/ +/*! default max filesystem path size of the current platform*/ #define GF_MAX_PATH 1023 #endif #endif /* end platform specific Win32/WinCE/UNIX*/ + +//! @cond Doxygen_Suppress + /*define what's missing*/ + #ifndef NULL #define NULL 0 #endif +//! @endcond + +/*! Double-precision floating point number*/ typedef double Double; +/*! Single-precision floating point number*/ typedef float Float; -/* 128 bit IDs */ +/*! 128 bit IDs */ typedef u8 bin128[16]; - +/*! max positive possible value for Double*/ #define GF_MAX_DOUBLE DBL_MAX +/*! max negative possible value for Double*/ #define GF_MIN_DOUBLE -GF_MAX_DOUBLE +/*! max positive possible value for Float*/ #define GF_MAX_FLOAT FLT_MAX +/*! max negative possible value for Float*/ #define GF_MIN_FLOAT -GF_MAX_FLOAT +/*! smallest possible value for float*/ #define GF_EPSILON_FLOAT FLT_EPSILON +/*! max possible value for s16*/ #define GF_SHORT_MAX SHRT_MAX +/*! min possible value for s16*/ #define GF_SHORT_MIN SHRT_MIN +/*! max possible value for u32*/ #define GF_UINT_MAX UINT_MAX +/*! max possible value for s32*/ #define GF_INT_MAX INT_MAX +/*! min possible value for s32*/ #define GF_INT_MIN INT_MIN #ifndef MIN +/*! get the smallest of two numbers*/ #define MIN(X, Y) ((X)<(Y)?(X):(Y)) #endif #ifndef MAX +/*! get the biggest of two numbers*/ #define MAX(X, Y) ((X)>(Y)?(X):(Y)) #endif +/*! get the absolute difference betwee two numbers*/ #define ABSDIFF(a, b) ( ( (a) > (b) ) ? ((a) - (b)) : ((b) - (a)) ) #ifndef ABS +/*! get the absolute value of a number*/ #define ABS(a) ( ( (a) > 0 ) ? (a) : - (a) ) #endif #ifndef Bool +/*! boolean value*/ typedef enum { GF_FALSE = 0, GF_TRUE } Bool; #endif +/*! 32 bit fraction*/ typedef struct { s32 num; u32 den; } GF_Fraction; -/*GPAC memory tracking*/ -#if defined(GPAC_MEMORY_TRACKING) - -void *gf_mem_malloc(size_t size, const char *filename, int line); -void *gf_mem_calloc(size_t num, size_t size_of, const char *filename, int line); -void *gf_mem_realloc(void *ptr, size_t size, const char *filename, int line); -void gf_mem_free(void *ptr, const char *filename, int line); -char *gf_mem_strdup(const char *str, const char *filename, int line); -void gf_memory_print(void); /*prints the state of current allocations*/ -u64 gf_memory_size(); /*gets memory allocated in bytes*/ - -#define gf_free(ptr) gf_mem_free(ptr, __FILE__, __LINE__) -#define gf_malloc(size) gf_mem_malloc(size, __FILE__, __LINE__) -#define gf_calloc(num, size_of) gf_mem_calloc(num, size_of, __FILE__, __LINE__) -#define gf_strdup(s) gf_mem_strdup(s, __FILE__, __LINE__) -#define gf_realloc(ptr1, size) gf_mem_realloc(ptr1, size, __FILE__, __LINE__) - -#else - -void* gf_malloc(size_t size); -void* gf_calloc(size_t num, size_t size_of); -void* gf_realloc(void *ptr, size_t size); -void gf_free(void *ptr); -char* gf_strdup(const char *str); - -#endif - - -/*end GPAC memory tracking*/ +/*! 64 bit fraction*/ +typedef struct { + s64 num; + u64 den; +} GF_Fraction64; #if (defined (WIN32) || defined (_WIN32_WCE)) && (defined(__MINGW32__) || !defined(__GNUC__)) -#define LLD "%I64d" -#define LLU "%I64u" -#define LLX "%I64x" -#define LLXPAD( pad ) "%" pad "I64x" -#define LLD_CAST -#define LLU_CAST +/*! macro for cross-platform suffix used for formating s64 integers in logs and printf routines*/ +#define LLD_SUF "I64d" +/*! macro for cross-platform suffix used for formating u64 integers in logs and printf routines*/ +#define LLU_SUF "I64u" +/*! macro for cross-platform suffix used for formating u64 integers as hex in logs and printf routines*/ +#define LLX_SUF "I64x" + #ifdef _WIN64 +/*! macro for cross-platform casting a pointer to an integer*/ #define PTR_TO_U_CAST (u64) #else +/*! macro for cross-platform casting a pointer to an integer*/ #define PTR_TO_U_CAST (u32) #endif #elif defined (__SYMBIAN32__) -#define LLD "%d" -#define LLU "%u" -#define LLX "%x" -#define LLXPAD( pad ) "%" pad "x" -#define LLD_CAST (u32) -#define LLU_CAST (s32) +/*! macro for cross-platform suffix used for formating s64 integers in logs and printf routines*/ +#define LLD_SUF "d" +/*! macro for cross-platform suffix used for formating u64 integers in logs and printf routines*/ +#define LLU_SUF "u" +/*! macro for cross-platform suffix used for formating u64 integers as hex in logs and printf routines*/ +#define LLX_SUF "x" + +/*! macro for cross-platform casting a pointer to an integer*/ #define PTR_TO_U_CAST (u32) /*seems that even though _LP64 is defined in OSX, %ll modifiers are still needed*/ #elif defined(__DARWIN__) || defined(__APPLE__) -#define LLD "%lld" -#define LLU "%llu" -#define LLX "%llx" -#define LLXPAD( pad ) "%" pad "llx" +/*! macro for cross-platform suffix used for formating s64 integers in logs and printf routines*/ +#define LLD_SUF "lld" +/*! macro for cross-platform suffix used for formating u64 integers in logs and printf routines*/ +#define LLU_SUF "llu" +/*! macro for cross-platform suffix used for formating u64 integers as hex in logs and printf routines*/ +#define LLX_SUF "llx" #ifdef __LP64__ /* Mac OS 64 bits */ +/*! macro for cross-platform casting a pointer to an integer*/ #define PTR_TO_U_CAST (u64) #else +/*! macro for cross-platform casting a pointer to an integer*/ #define PTR_TO_U_CAST (u32) #endif #elif defined(_LP64) /*Unix 64 bits*/ -#define LLD "%ld" -#define LLU "%lu" -#define LLX "%lx" -#define LLXPAD( pad ) "%" pad "lx" +/*! macro for cross-platform suffix used for formating s64 integers in logs and printf routines*/ +#define LLD_SUF "ld" +/*! macro for cross-platform suffix used for formating u64 integers in logs and printf routines*/ +#define LLU_SUF "lu" +/*! macro for cross-platform suffix used for formating u64 integers as hex in logs and printf routines*/ +#define LLX_SUF "lx" + +/*! macro for cross-platform casting a pointer to an integer*/ #define PTR_TO_U_CAST (u64) #else /*Unix 32 bits*/ -#define LLD "%lld" -#define LLU "%llu" -#define LLX "%llx" -#define LLXPAD( pad ) "%" pad "llx" -#define PTR_TO_U_CAST (u32) - -#endif +/*! macro for cross-platform suffix used for formating s64 integers in logs and printf routines*/ +#define LLD_SUF "lld" +/*! macro for cross-platform suffix used for formating u64 integers in logs and printf routines*/ +#define LLU_SUF "llu" +/*! macro for cross-platform suffix used for formating u64 integers as hex in logs and printf routines*/ +#define LLX_SUF "llx" -#ifndef LLD_CAST -#define LLD_CAST -#endif +/*! macro for cross-platform casting a pointer to an integer*/ +#define PTR_TO_U_CAST (u32) -#ifndef LLU_CAST -#define LLU_CAST #endif #ifndef PTR_TO_U_CAST +/*! macro for cross-platform casting a pointer to an integer*/ #define PTR_TO_U_CAST #endif +/*! macro for cross-platform formatting of s64 integers in logs and printf routines*/ +#define LLD "%" LLD_SUF +/*! macro for cross-platform formatting of u64 integers in logs and printf routines*/ +#define LLU "%" LLU_SUF +/*! macro for cross-platform formatting of u64 integers as hexadecimal in logs and printf routines*/ +#define LLX "%" LLX_SUF + + #if !defined(GF_EXPORT) -#if defined(__GNUC__) && __GNUC__ >= 4 && !defined(GPAC_IPHONE) +#if defined(__GNUC__) && __GNUC__ >= 4 && !defined(GPAC_CONFIG_IOS) +/*! macro for cross-platform signaling of exported function of libgpac*/ #define GF_EXPORT __attribute__((visibility("default"))) #else /*use def files for windows or let compiler decide*/ + +/*! macro for cross-platform signaling of exported function of libgpac*/ #define GF_EXPORT #endif #endif -#if defined(GPAC_IPHONE) + +//! @cond Doxygen_Suppress + +#if defined(GPAC_CONFIG_IOS) #define GPAC_STATIC_MODULES #endif @@ -495,12 +574,6 @@ char* gf_strdup(const char *str); # ifndef GPAC_DISABLE_ISOM_HINTING # define GPAC_DISABLE_ISOM_HINTING # endif -# ifndef GPAC_DISABLE_MEDIA_IMPORT -# define GPAC_DISABLE_MEDIA_IMPORT -# endif -# ifndef GPAC_DISABLE_MPEG2TS_MUX -# define GPAC_DISABLE_MPEG2TS_MUX -# endif #endif #ifdef GPAC_DISABLE_ISOM @@ -563,14 +636,92 @@ char* gf_strdup(const char *str); # endif #endif -#if !defined(GPAC_HAS_SPIDERMONKEY) || defined(GPAC_DISABLE_SVG) -# ifndef GPAC_DISABLE_MSE -# define GPAC_DISABLE_MSE -# endif +//we currently disable all extra IPMP/IPMPX/OCI/extra MPEG-4 descriptors parsing +#ifndef GPAC_MINIMAL_ODF +#define GPAC_MINIMAL_ODF #endif +//define this to remove most of built-in doc of libgpac - for now filter description and help is removed, but argument help is not +//#define GPAC_DISABLE_DOC + + +//! @endcond + /*! @} */ +/*! +\addtogroup mem_grp +\brief Memory management + +GPAC can use its own memory tracker, depending on compilation option. It is recommended to use only the functions +defined in this section to allocate and free memory whenever developping within the GPAC library. + +\warning these functions shall only be used after initializing the library using \ref gf_sys_init +@{ +*/ +/*GPAC memory tracking*/ +#if defined(GPAC_MEMORY_TRACKING) + + +void *gf_mem_malloc(size_t size, const char *filename, int line); +void *gf_mem_calloc(size_t num, size_t size_of, const char *filename, int line); +void *gf_mem_realloc(void *ptr, size_t size, const char *filename, int line); +void gf_mem_free(void *ptr, const char *filename, int line); +char *gf_mem_strdup(const char *str, const char *filename, int line); +void gf_memory_print(void); /*prints the state of current allocations*/ +u64 gf_memory_size(); /*gets memory allocated in bytes*/ + +/*! free memory allocated with gpac*/ +#define gf_free(ptr) gf_mem_free(ptr, __FILE__, __LINE__) +/*! allocates memory, shall be freed using \ref gf_free*/ +#define gf_malloc(size) gf_mem_malloc(size, __FILE__, __LINE__) +/*! allocates memory array, shall be freed using \ref gf_free*/ +#define gf_calloc(num, size_of) gf_mem_calloc(num, size_of, __FILE__, __LINE__) +/*! duplicates string, shall be freed using \ref gf_free*/ +#define gf_strdup(s) gf_mem_strdup(s, __FILE__, __LINE__) +/*! reallocates memory, shall be freed using \ref gf_free*/ +#define gf_realloc(ptr1, size) gf_mem_realloc(ptr1, size, __FILE__, __LINE__) + +#else + +/*! free memory allocated with gpac +\param ptr same as free() +*/ +void gf_free(void *ptr); + +/*! allocates memory, shall be freed using \ref gf_free +\param size same as malloc() +\return adress of allocated block +*/ +void* gf_malloc(size_t size); + +/*! allocates memory array, shall be freed using \ref gf_free +\param num same as calloc() +\param size_of same as calloc() +\return adress of allocated block +*/ +void* gf_calloc(size_t num, size_t size_of); + +/*! duplicates string, shall be freed using \ref gf_free +\param str same as strdup() +\return duplicated string +*/ +char* gf_strdup(const char *str); + +/*! reallocates memory, shall be freed using \ref gf_free +\param ptr same as realloc() +\param size same as realloc() +\return adress of reallocated block +*/ +void* gf_realloc(void *ptr, size_t size); + +#endif +/*! @} */ + + +/*end GPAC memory tracking*/ + + #ifdef __cplusplus } #endif diff --git a/include/gpac/svg_types.h b/include/gpac/svg_types.h index 10eb91e..cd83e59 100644 --- a/include/gpac/svg_types.h +++ b/include/gpac/svg_types.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Cyril Concolato - Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2004-2012 + * Copyright (c) Telecom ParisTech 2004-2019 * All rights reserved * * This file is part of GPAC / Scene Graph sub-project @@ -31,17 +31,18 @@ extern "C" { #endif /*! - * \file <gpac/svg_types.h> - * \brief Data types used for SVG scene graph - */ +\file <gpac/svg_types.h> +\brief Data types used for SVG scene graph +*/ /*! - * \ingroup ssvg - * \brief Data types used for SVG scene graph. - * - *This section documents the data types used for SVG scene graph. - * @{ - */ +\ingroup ssvg +\brief Data types used for SVG scene graph. + +This section documents the data types used for SVG scene graph. + +@{ +*/ #include <gpac/path2d.h> #include <gpac/events_constants.h> @@ -65,7 +66,7 @@ extern "C" { on_event_in: unused in SVG */ -/* SVG attribute types */ +/*! SVG attribute types */ enum { SVG_Unknown_datatype = 0, @@ -173,8 +174,11 @@ enum { /*LASeR types*/ LASeR_Choice_datatype, LASeR_Size_datatype, + + SVG_Matrix2D_datatype, }; +//! @cond Doxygen_Suppress /* Definition of SVG base data types */ typedef char *DOM_String; typedef DOM_String SVG_String; @@ -208,7 +212,10 @@ typedef GF_List *SMIL_KeySplines; typedef GF_Matrix2D SVG_Motion; -/* SMIL Anim types */ +//! @endcond + + +/*! SMIL Anim types */ typedef struct { /*field type*/ u32 type; @@ -220,6 +227,7 @@ typedef struct { u32 tag; } SMIL_AttributeName; +/*! SMIL time types */ enum { /*clock time*/ GF_SMIL_TIME_CLOCK = 0, @@ -233,9 +241,12 @@ enum { GF_SMIL_TIME_INDEFINITE = 4, }; +/*! macro to check if a SMIL time is a clock value*/ #define GF_SMIL_TIME_IS_CLOCK(v) (v<=GF_SMIL_TIME_EVENT_RESOLVED) +/*! macro to check if a SMIL time is a resolved clock value*/ #define GF_SMIL_TIME_IS_SPECIFIED_CLOCK(v) (v<GF_SMIL_TIME_EVENT_RESOLVED) +/*! XML event*/ typedef struct { GF_EventType type; @@ -243,6 +254,7 @@ typedef struct u32 parameter; } XMLEV_Event; +/*! SMIL time*/ typedef struct { /* Type of timing value*/ u8 type; @@ -265,6 +277,7 @@ typedef struct { } SMIL_Time; +/*! SMIL duration types */ enum { SMIL_DURATION_AUTO = 0, SMIL_DURATION_INDEFINITE, @@ -272,58 +285,72 @@ enum { SMIL_DURATION_NONE, SMIL_DURATION_DEFINED, }; +/*! SMIL duration*/ typedef struct { u8 type; Double clock_value; } SMIL_Duration; +/*! SMIL retart types */ enum { SMIL_RESTART_ALWAYS = 0, SMIL_RESTART_NEVER, SMIL_RESTART_WHENNOTACTIVE, }; +/*! SMIL restart*/ typedef u8 SMIL_Restart; +/*! SMIL fill types */ enum { SMIL_FILL_FREEZE=0, SMIL_FILL_REMOVE, }; +/*! SMIL fill*/ typedef u8 SMIL_Fill; +/*! SMIL repeatcount types */ enum { SMIL_REPEATCOUNT_INDEFINITE = 0, SMIL_REPEATCOUNT_DEFINED = 1, /* used only for static allocation of SVG attributes */ SMIL_REPEATCOUNT_UNSPECIFIED = 2 }; +/*! SMIL repeat count*/ typedef struct { u8 type; Fixed count; } SMIL_RepeatCount; +/*! SMIL animate value*/ typedef struct { u8 type; void *value; } SMIL_AnimateValue; +/*! SMIL animate values*/ typedef struct { - u8 type; + u8 type, laser_strings; GF_List *values; } SMIL_AnimateValues; +/*! SMIL additive types */ enum { SMIL_ADDITIVE_REPLACE = 0, SMIL_ADDITIVE_SUM }; +/*! SMIL additive*/ typedef u8 SMIL_Additive; +/*! SMIL accumulate types */ enum { SMIL_ACCUMULATE_NONE = 0, SMIL_ACCUMULATE_SUM }; +/*! SMIL accumulate*/ typedef u8 SMIL_Accumulate; +/*! SMIL calcmode types */ enum { /*WARNING: default value is linear, order changed for LASeR coding*/ SMIL_CALCMODE_DISCRETE = 0, @@ -331,24 +358,31 @@ enum { SMIL_CALCMODE_PACED, SMIL_CALCMODE_SPLINE }; +/*! SMIL calc mode*/ typedef u8 SMIL_CalcMode; /* end of SMIL Anim types */ +/*! XMLRI types */ enum { XMLRI_ELEMENTID = 0, XMLRI_STRING, XMLRI_STREAMID }; +/*! XMLRI object*/ typedef struct __xml_ri { u8 type; char *string; void *target; u32 lsr_stream_id; + u32 node_id; } XMLRI; -/*the same structure is used to watch for IDREF changes (LASeR node replace)*/ +/*! XML IDREF object + Note: the same structure is used to watch for IDREF changes (LASeR node replace) +*/ typedef struct __xml_ri XML_IDREF; +/*! SVG focus types */ enum { SVG_FOCUS_AUTO = 0, @@ -356,31 +390,38 @@ enum SVG_FOCUS_IRI, }; +/*! SVG focus attribute*/ typedef struct { u8 type; XMLRI target; } SVG_Focus; +/*! SVG fontfamiliy types */ enum { SVG_FONTFAMILY_INHERIT = 0, SVG_FONTFAMILY_VALUE }; +/*! SVG font attribute*/ typedef struct { u8 type; SVG_String value; } SVG_FontFamily; +/*! SVG fontstyle types */ enum { SVG_FONTSTYLE_INHERIT = 0, SVG_FONTSTYLE_ITALIC = 1, SVG_FONTSTYLE_NORMAL = 2, SVG_FONTSTYLE_OBLIQUE = 3 }; +/*! SVG fontstyle attribute*/ typedef u8 SVG_FontStyle; -/*the values are chosen to match LASeR code points*/ +/*! SVG path commands types +\note the values are chosen to match LASeR code points +*/ enum { SVG_PATHCOMMAND_M = 3, SVG_PATHCOMMAND_L = 2, @@ -392,57 +433,70 @@ enum { SVG_PATHCOMMAND_Z = 8 }; +/*! macros to use GF_Path directly as SVG path*/ #define USE_GF_PATH 1 #if USE_GF_PATH +/*! SVG path data*/ typedef GF_Path SVG_PathData; #else +/*! SVG path data*/ typedef struct { GF_List *commands; GF_List *points; } SVG_PathData; #endif +/*! SVG point*/ typedef struct { Fixed x, y; } SVG_Point; +/*! SVG point+angle*/ typedef struct { Fixed x, y, angle; } SVG_Point_Angle; +/*! SVG ViewBox*/ typedef struct { Bool is_set; Fixed x, y, width, height; } SVG_ViewBox; +/*! SVG Boolean*/ typedef Bool SVG_Boolean; -/*WARNING - THESE ARE PATH FLAGS, CHECK IF WORKING*/ +/*! SVG fill rule types */ enum { SVG_FILLRULE_EVENODD= 0, SVG_FILLRULE_NONZERO, SVG_FILLRULE_INHERIT }; +/*! SVG fill rule*/ typedef u8 SVG_FillRule; +/*! SVG stroke line join types */ enum { SVG_STROKELINEJOIN_MITER = GF_LINE_JOIN_MITER_SVG, SVG_STROKELINEJOIN_ROUND = GF_LINE_JOIN_ROUND, SVG_STROKELINEJOIN_BEVEL = GF_LINE_JOIN_BEVEL, SVG_STROKELINEJOIN_INHERIT = 100 }; +/*! SVG stroke line join*/ typedef u8 SVG_StrokeLineJoin; -/* Warning: GPAC naming is not the same as SVG naming for line cap Flat = butt and Butt = square*/ +/*! SVG stroke line cap types +\warning GPAC naming is not the same as SVG naming for line cap Flat = butt and Butt = square*/ enum { SVG_STROKELINECAP_BUTT = GF_LINE_CAP_FLAT, SVG_STROKELINECAP_ROUND = GF_LINE_CAP_ROUND, SVG_STROKELINECAP_SQUARE = GF_LINE_CAP_SQUARE, SVG_STROKELINECAP_INHERIT = 100 }; +/*! SVG stroke line cap*/ typedef u8 SVG_StrokeLineCap; +/*! SVG overflow types */ enum { SVG_OVERFLOW_INHERIT = 0, SVG_OVERFLOW_VISIBLE = 1, @@ -450,8 +504,10 @@ enum { SVG_OVERFLOW_SCROLL = 3, SVG_OVERFLOW_AUTO = 4 }; +/*! SVG overflow*/ typedef u8 SVG_Overflow; +/*! SVG color types */ enum { SVG_COLOR_RGBCOLOR = 0, SVG_COLOR_INHERIT, @@ -486,11 +542,13 @@ enum { SVG_COLOR_WINDOW_TEXT /* Text in windows.*/ }; +/*! SVG color*/ typedef struct { u8 type; Fixed red, green, blue; } SVG_Color; +/*! SVG paint types */ enum { SVG_PAINT_NONE = 0, SVG_PAINT_COLOR = 1, @@ -498,12 +556,18 @@ enum { SVG_PAINT_INHERIT = 3 }; -typedef struct { +/*! SVG paint attribute*/ +struct __svg_color{ u8 type; SVG_Color color; XMLRI iri; -} SVG_Paint, SVG_SVGColor; +}; +/*! SVG paint attribute*/ +typedef struct __svg_color SVG_Paint; +/*! SVG color attribute*/ +typedef struct __svg_color SVG_SVGColor; +/*! SVG number types */ enum { SVG_NUMBER_VALUE = 0, SVG_NUMBER_PERCENTAGE = 1, @@ -520,20 +584,28 @@ enum { SVG_NUMBER_AUTO_REVERSE = 12 }; -typedef struct { +struct __svg_number { u8 type; Fixed value; -} SVG_Number, -SVG_FontSize, -SVG_Length, -SVG_Coordinate, -SVG_Rotate; - +}; +/*! SVG number*/ +typedef struct __svg_number SVG_Number; +/*! SVG font size*/ +typedef struct __svg_number SVG_FontSize; +/*! SVG length*/ +typedef struct __svg_number SVG_Length; +/*! SVG coordinate*/ +typedef struct __svg_number SVG_Coordinate; +/*! SVG rotation*/ +typedef struct __svg_number SVG_Rotate; + +/*! SVG transform*/ typedef struct { u8 is_ref; GF_Matrix2D mat; } SVG_Transform; +/*! SVG transform types */ enum { SVG_TRANSFORM_MATRIX = 0, SVG_TRANSFORM_TRANSLATE = 1, @@ -543,8 +615,10 @@ enum { SVG_TRANSFORM_SKEWY = 5 }; +/*! SVG transform type*/ typedef u8 SVG_TransformType; +/*! SVG fontweight types */ enum { SVG_FONTWEIGHT_100 = 0, SVG_FONTWEIGHT_200, @@ -561,23 +635,29 @@ enum { SVG_FONTWEIGHT_LIGHTER, SVG_FONTWEIGHT_NORMAL }; +/*! SVG font weight*/ typedef u8 SVG_FontWeight; +/*! SVG fontvariant types */ enum { SVG_FONTVARIANT_INHERIT = 0, SVG_FONTVARIANT_NORMAL = 1, SVG_FONTVARIANT_SMALLCAPS = 2 }; +/*! SVG font variant*/ typedef u8 SVG_FontVariant; +/*! SVG visibility types */ enum { SVG_VISIBILITY_HIDDEN = 0, SVG_VISIBILITY_INHERIT = 1, SVG_VISIBILITY_VISIBLE = 2, SVG_VISIBILITY_COLLAPSE = 3 }; +/*! SVG visibility*/ typedef u8 SVG_Visibility; +/*! SVG display types */ enum { SVG_DISPLAY_INHERIT = 0, SVG_DISPLAY_NONE = 1, @@ -598,8 +678,10 @@ enum { SVG_DISPLAY_TABLE_CELL, SVG_DISPLAY_TABLE_CAPTION }; +/*! SVG display*/ typedef u8 SVG_Display; +/*! SVG display-align types */ enum { SVG_DISPLAYALIGN_INHERIT = 0, SVG_DISPLAYALIGN_AUTO = 1, @@ -607,41 +689,50 @@ enum { SVG_DISPLAYALIGN_BEFORE = 3, SVG_DISPLAYALIGN_CENTER = 4 }; +/*! SVG display alignment*/ typedef u8 SVG_DisplayAlign; +/*! SVG text-align types */ enum { SVG_TEXTALIGN_INHERIT = 0, SVG_TEXTALIGN_START = 1, SVG_TEXTALIGN_CENTER = 2, SVG_TEXTALIGN_END = 3 }; +/*! SVG text alignment*/ typedef u8 SVG_TextAlign; +/*! SVG stroke dash array types */ enum { SVG_STROKEDASHARRAY_NONE = 0, SVG_STROKEDASHARRAY_INHERIT = 1, SVG_STROKEDASHARRAY_ARRAY = 2 }; +/*! SVG unit array*/ typedef struct { u32 count; Fixed* vals; u8 *units; } UnitArray; +/*! SVG stroke dash array*/ typedef struct { u8 type; UnitArray array; } SVG_StrokeDashArray; +/*! SVG text anchor types */ enum { SVG_TEXTANCHOR_INHERIT = 0, SVG_TEXTANCHOR_END = 1, SVG_TEXTANCHOR_MIDDLE = 2, SVG_TEXTANCHOR_START = 3 }; +/*! SVG text anchor*/ typedef u8 SVG_TextAnchor; +/*! SVG angle types */ enum { SVG_ANGLETYPE_UNKNOWN = 0, SVG_ANGLETYPE_UNSPECIFIED = 1, @@ -650,12 +741,14 @@ enum { SVG_ANGLETYPE_GRAD = 4 }; +/*! SVG unit-type types */ enum { SVG_UNIT_TYPE_UNKNOWN = 0, SVG_UNIT_TYPE_USERSPACEONUSE = 1, SVG_UNIT_TYPE_OBJECTBOUNDINGBOX = 2 }; +/*! SVG preserve aspect ratio types */ enum { // Alignment Types SVG_PRESERVEASPECTRATIO_NONE = 1, @@ -670,65 +763,73 @@ enum { SVG_PRESERVEASPECTRATIO_XMAXYMAX = 9 }; +/*! SVG meet or slice types */ enum { // Meet_or_slice Types SVG_MEETORSLICE_MEET = 0, SVG_MEETORSLICE_SLICE = 1 }; +/*! SVG preserve aspect ratio*/ typedef struct { Bool defer; u8 align; u8 meetOrSlice; } SVG_PreserveAspectRatio; +/*! SVG zoom and pan types */ enum { SVG_ZOOMANDPAN_DISABLE = 0, SVG_ZOOMANDPAN_MAGNIFY, }; +/*! SVG zoom and pan*/ typedef u8 SVG_ZoomAndPan; +/*! SVG lenght adjust types */ enum { LENGTHADJUST_UNKNOWN = 0, LENGTHADJUST_SPACING = 1, LENGTHADJUST_SPACINGANDGLYPHS = 2 }; +/*! SVG textPath methods types */ enum { - // textPath Method Types TEXTPATH_METHODTYPE_UNKNOWN = 0, TEXTPATH_METHODTYPE_ALIGN = 1, TEXTPATH_METHODTYPE_STRETCH = 2 }; + +/*! SVG textPath spacing types */ enum { - // textPath Spacing Types TEXTPATH_SPACINGTYPE_UNKNOWN = 0, TEXTPATH_SPACINGTYPE_AUTO = 1, TEXTPATH_SPACINGTYPE_EXACT = 2 }; +/*! SVG Marker Unit types */ enum { - // Marker Unit Types SVG_MARKERUNITS_UNKNOWN = 0, SVG_MARKERUNITS_USERSPACEONUSE = 1, SVG_MARKERUNITS_STROKEWIDTH = 2 }; + +/*! SVG Marker Orientation types */ enum { - // Marker Orientation Types SVG_MARKER_ORIENT_UNKNOWN = 0, SVG_MARKER_ORIENT_AUTO = 1, SVG_MARKER_ORIENT_ANGLE = 2 }; +/*! SVG Spread Method types */ enum { - // Spread Method Types SVG_SPREADMETHOD_UNKNOWN = 0, SVG_SPREADMETHOD_PAD = 1, SVG_SPREADMETHOD_REFLECT = 2, SVG_SPREADMETHOD_REPEAT = 3 }; +/*! SVG pointer events types */ enum { SVG_POINTEREVENTS_INHERIT = 0, SVG_POINTEREVENTS_ALL = 1, @@ -742,8 +843,10 @@ enum { SVG_POINTEREVENTS_VISIBLESTROKE = 9, SVG_POINTEREVENTS_BOUNDINGBOX = 10 }; +/*! SVG pointer events*/ typedef u8 SVG_PointerEvents; +/*! SVG rendering hints types */ enum { SVG_RENDERINGHINT_INHERIT = 0, SVG_RENDERINGHINT_AUTO = 1, @@ -754,34 +857,44 @@ enum { SVG_RENDERINGHINT_GEOMETRICPRECISION = 6, }; +/*! SVG rendering hints*/ typedef u8 SVG_RenderingHint; +/*! SVG vector effect types */ enum { SVG_VECTOREFFECT_INHERIT = 0, SVG_VECTOREFFECT_NONE = 1, SVG_VECTOREFFECT_NONSCALINGSTROKE = 2, }; +/*! SVG vector effect*/ typedef u8 SVG_VectorEffect; +/*! XML event propagate types */ enum { XMLEVENT_PROPAGATE_CONTINUE = 0, XMLEVENT_PROPAGATE_STOP = 1 }; +/*! DOM Event propagate*/ typedef u8 XMLEV_Propagate; +/*! XML event default action types */ enum { XMLEVENT_DEFAULTACTION_CANCEL = 0, XMLEVENT_DEFAULTACTION_PERFORM, }; +/*! DOM Event default action*/ typedef u8 XMLEV_DefaultAction; +/*! XML event phase types */ enum { XMLEVENT_PHASE_DEFAULT = 0, XMLEVENT_PHASE_CAPTURE = 1 }; +/*! DOM Event phase*/ typedef u8 XMLEV_Phase; +/*! SMIL sync behaviour types */ enum { SMIL_SYNCBEHAVIOR_INHERIT = 0, /*LASeR order*/ @@ -790,63 +903,81 @@ enum { SMIL_SYNCBEHAVIOR_INDEPENDENT, SMIL_SYNCBEHAVIOR_LOCKED, }; +/*! SMIL sync behaviour*/ typedef u8 SMIL_SyncBehavior; +/*! SMIL sync tolerance types */ enum { SMIL_SYNCTOLERANCE_INHERIT = 0, SMIL_SYNCTOLERANCE_DEFAULT = 1, SMIL_SYNCTOLERANCE_VALUE = 2 }; +/*! SMIL sync tolerance*/ typedef struct { u8 type; SVG_Clock value; } SMIL_SyncTolerance; +/*! SMIL attributeType types */ enum { SMIL_ATTRIBUTETYPE_CSS = 0, SMIL_ATTRIBUTETYPE_XML, SMIL_ATTRIBUTETYPE_AUTO, }; +/*! SMIL attribute type*/ typedef u8 SMIL_AttributeType; +/*! SVG playbackorder types */ enum { SVG_PLAYBACKORDER_ALL = 0, SVG_PLAYBACKORDER_FORWARDONLY = 1, }; +/*! SVG playback order*/ typedef u8 SVG_PlaybackOrder; +/*! SVG timeline begin types */ enum { SVG_TIMELINEBEGIN_ONLOAD=0, SVG_TIMELINEBEGIN_ONSTART, }; +/*! SVG timeline begin*/ typedef u8 SVG_TimelineBegin; +/*! XML space types */ enum { XML_SPACE_DEFAULT = 0, XML_SPACE_PRESERVE = 1 }; +/*! XML space type*/ typedef u8 XML_Space; +/*! SVG gradient units types */ enum { SVG_GRADIENTUNITS_OBJECT = 0, SVG_GRADIENTUNITS_USER = 1 }; +/*! SVG gradient unit*/ typedef u8 SVG_GradientUnit; +/*! SVG focus highlight types */ enum { SVG_FOCUSHIGHLIGHT_AUTO = 0, SVG_FOCUSHIGHLIGHT_NONE = 1 }; +/*! SVG focus highlight*/ typedef u8 SVG_FocusHighlight; +/*! SVG initial visibility types */ enum { SVG_INITIALVISIBILTY_WHENSTARTED = 0, SVG_INITIALVISIBILTY_ALWAYS = 1 }; +/*! SVG initial visibility*/ typedef u8 SVG_InitialVisibility; +/*! SVG transform behaviour types */ enum { SVG_TRANSFORMBEHAVIOR_GEOMETRIC = 0, SVG_TRANSFORMBEHAVIOR_PINNED, @@ -854,21 +985,27 @@ enum { SVG_TRANSFORMBEHAVIOR_PINNED270, SVG_TRANSFORMBEHAVIOR_PINNED90, }; +/*! SVG transform behaviour*/ typedef u8 SVG_TransformBehavior; +/*! SVG overlay types */ enum { SVG_OVERLAY_NONE = 0, SVG_OVERLAY_TOP, }; +/*! SVG overlay*/ typedef u8 SVG_Overlay; +/*! SVG focusable types */ enum { SVG_FOCUSABLE_AUTO = 0, SVG_FOCUSABLE_FALSE, SVG_FOCUSABLE_TRUE, }; +/*! SVG focusable*/ typedef u8 SVG_Focusable; +/*! SVG filter transfer types */ enum { SVG_FILTER_TRANSFER_IDENTITY, @@ -877,27 +1014,32 @@ enum SVG_FILTER_TRANSFER_LINEAR, SVG_FILTER_TRANSFER_GAMMA }; +/*! SVG filter transfer type*/ typedef u8 SVG_Filter_TransferType; +/*! SVG spread types */ enum { SVG_SPREAD_PAD = 0, SVG_SPREAD_REFLECT, SVG_SPREAD_REPEAT, }; +/*! SVG spread method*/ typedef u8 SVG_SpreadMethod; +/*! LASeR choice types */ enum { LASeR_CHOICE_ALL = 0, LASeR_CHOICE_NONE = 1, LASeR_CHOICE_N = 2 }; -typedef u8 LASeR_Choice_enum; +/*! LASeR choice*/ typedef struct { u32 type; u32 choice_index; } LASeR_Choice; +/*! LASeR size*/ typedef struct { Fixed width, height; } LASeR_Size; diff --git a/include/gpac/sync_layer.h b/include/gpac/sync_layer.h index 2b83843..8a2eb9a 100644 --- a/include/gpac/sync_layer.h +++ b/include/gpac/sync_layer.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / SL header file @@ -31,16 +31,16 @@ extern "C" { #endif /*! - * \file <gpac/sync_layer.h> - * \brief MPEG-4 Object Descriptor Framework Sync Layer. - */ +\file <gpac/sync_layer.h> +\brief MPEG-4 Object Descriptor Framework Sync Layer. +*/ /*! - * \ingroup odf_grp - * \brief MPEG-4 Object Descriptor Framework Sync Layer - * - *This section documents the MPEG-4 OD Sync Layer used in GPAC. - * @{ +\ingroup odf_grp +\brief MPEG-4 Object Descriptor Framework Sync Layer + +This section documents the MPEG-4 OD Sync Layer used in GPAC. +@{ */ /*the Sync Layer config descriptor*/ @@ -72,25 +72,25 @@ typedef struct u16 CUDuration; u64 startDTS; u64 startCTS; - + //internal Bool no_dts_signaling; + u32 carousel_version; } GF_SLConfig; -/*************************************** - SLConfig Tag -***************************************/ +/*! SL config predefined values*/ enum { SLPredef_Null = 0x01, SLPredef_MP4 = 0x02, - /*intern to GPAC, means NO SL at all (for streams unable to handle AU reconstruction a timing)*/ - SLPredef_SkipSL = 0xF0 }; -/*set SL predefined (assign all fields according to sl->predefined value)*/ +/*! sets SL predefined (assign all fields according to sl->predefined value) +\param sl the target SL config descriptor +\return error if any +*/ GF_Err gf_odf_slc_set_pref(GF_SLConfig *sl); - +/*! MPEG-4 sync layer header information*/ typedef struct { u8 accessUnitStartFlag; @@ -125,9 +125,8 @@ typedef struct u64 isma_BSO; /*CENC extensions*/ u8 cenc_encrypted; - char *sai; u8 IV_size; - u32 saiz; + //for CENC pattern encryption mode u8 crypt_byte_block, skip_byte_block; u8 constant_IV_size; @@ -142,16 +141,35 @@ typedef struct u64 sender_ntp; //set for AUs which should be decodedd but not presented during seek u8 seekFlag; + + u32 samplerate, channels; } GF_SLHeader; -/*packetize SL-PDU. If PDU is NULL or size 0, only writes the SL header*/ -void gf_sl_packetize(GF_SLConfig* slConfig, GF_SLHeader *Header, char *PDU, u32 size, char **outPacket, u32 *OutSize); -/*gets SL header size in bytes*/ +/*! packetizes SL-PDU. If PDU is NULL or size 0, only writes the SL header +\param slConfig the target SL config descriptor +\param Header the SL header for this data block +\param PDU the payload to packetize +\param size the payload size +\param outPacket set to an allocated buffer containing the serialized SL packet - to be destroyed by caller +\param OutSize set to the size of the serialized SL packer +*/ +void gf_sl_packetize(GF_SLConfig* slConfig, GF_SLHeader *Header, u8 *PDU, u32 size, u8 **outPacket, u32 *OutSize); +/*! gets SL header size +\param slConfig the target SL config descriptor +\param Header the SL header for this data block +\return the size of the SL header in bytes +*/ u32 gf_sl_get_header_size(GF_SLConfig* slConfig, GF_SLHeader *Header); -/*depacketize SL-PDU*/ -void gf_sl_depacketize(GF_SLConfig *slConfig, GF_SLHeader *Header, const char *PDU, u32 PDULength, u32 *HeaderLen); +/*! depacketizes SL-PDU +\param slConfig the target SL config descriptor +\param Header filled with the decoded SL header +\param PDU the SL packet to parse +\param PDULength the size of the SL packet +\param HeaderLen set to size of the SL header - payload will start at PDU + *HeaderLen +*/ +void gf_sl_depacketize(GF_SLConfig *slConfig, GF_SLHeader *Header, const u8 *PDU, u32 PDULength, u32 *HeaderLen); /*! @} */ diff --git a/include/gpac/term_info.h b/include/gpac/term_info.h index b3628e8..7b6ebbd 100644 --- a/include/gpac/term_info.h +++ b/include/gpac/term_info.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / Stream Management sub-project @@ -34,17 +34,18 @@ extern "C" { #endif /*! - * \file <gpac/term_info.h> - * \brief GPAC media object browsing of the player. - */ +\file <gpac/term_info.h> +\brief GPAC media object browsing of the player. +*/ /*! - *\addtogroup terminfo_grp Terminal Info - *\ingroup playback_grp - *\brief GPAC media object browsing of the player. - * - *This section documents how a user can get information on running media object in the player. - * @{ +\addtogroup terminfo_grp Terminal Info +\ingroup playback_grp +\brief GPAC media object browsing of the player. + +This section documents how a user can get information on running media object in the player. + +@{ */ @@ -56,44 +57,72 @@ extern "C" { #include <gpac/mpeg4_odf.h> #include <gpac/terminal.h> -/*returns top-level OD of the presentation*/ +/*! Media object manager*/ +typedef struct _od_manager GF_ObjectManager; + +/*! gets top-level OD of the presentation +\param term the target terminal +\return the root object descriptor or NULL if not connected*/ GF_ObjectManager *gf_term_get_root_object(GF_Terminal *term); -/*returns number of sub-ODs in the current root. scene_od must be an inline OD*/ + +/*! gets number of objects in sub scene of object +\param term the target terminal +\param scene_od the OD to browse for resources. This must be an inline OD +\return the number of objects in the scene described by the OD*/ u32 gf_term_get_object_count(GF_Terminal *term, GF_ObjectManager *scene_od); -/*returns indexed (0-based) OD manager in the scene*/ + +/*! gets an OD manager in the scene +\param term the target terminal +\param scene_od the OD to browse for resources. This must be an inline OD +\param index 0-based index of the obhect to query +\return the object manager or NULL if not found +*/ GF_ObjectManager *gf_term_get_object(GF_Terminal *term, GF_ObjectManager *scene_od, u32 index); -/*return values: - 0: regular media object, not inline - 1: root scene - 2: inline scene - 3: externProto library + +/*! gets the type of the associated subscene +\param term the target terminal +\param odm the object to query +\return 0: regular media object, not inline, 1: root scene, 2: inline scene, 3: externProto library */ u32 gf_term_object_subscene_type(GF_Terminal *term, GF_ObjectManager *odm); -/*select given object when stream selection is available*/ +/*! selects a given object when stream selection is available +\param term the target terminal +\param odm the object to select +*/ void gf_term_select_object(GF_Terminal *term, GF_ObjectManager *odm); -/*select service by given ID for multiplexed services (MPEG-2 TS)*/ +/*! selects service by given ID for multiplexed services (MPEG-2 TS) +\param term the target terminal +\param odm an object in the scene attached to the same multiplex +\param service_id the service ID to select +*/ void gf_term_select_service(GF_Terminal *term, GF_ObjectManager *odm, u32 service_id); -/*sets addon on or off (only one addon possible for now). When OFF , the associated service is shut down*/ +/*! sets addon on or off (only one addon possible for now). When OFF, the associated service is shut down +\param term the target terminal +\param show_addons if GF_TRUE, displays addons otheriwse hides the addons +*/ void gf_term_toggle_addons(GF_Terminal *term, Bool show_addons); +/*! checks for an existing service ID in a session +\param term the target terminal +\param odm an object in the scene attached to the same multiplex +\param service_id the ID of the service to find +\return GF_TRUE if given service ID is declared*/ +Bool gf_term_find_service(GF_Terminal *term, GF_ObjectManager *odm, u32 service_id); + +/*! Media Object information*/ typedef struct { - GF_ObjectDescriptor *od; + u32 ODID; + u32 ServiceID; + u32 pid_id, ocr_id; Double duration; Double current_time; /*0: stopped, 1: playing, 2: paused, 3: not setup, 4; setup failed.*/ u32 status; - /*if set, the PL flags are valid*/ - Bool has_profiles; - Bool inline_pl; - u8 OD_pl; - u8 scene_pl; - u8 audio_pl; - u8 visual_pl; - u8 graphics_pl; + Bool raw_media; Bool generated_scene; @@ -101,6 +130,8 @@ typedef struct const char *service_handler; /*name of service*/ const char *service_url; + /*service redirect object*/ + const char *remote_url; /*set if the service is owned by this object*/ Bool owns_service; @@ -114,22 +145,25 @@ typedef struct /*number of AUs in DB (cumulated on all input channels)*/ u32 db_unit_count; /*number of CUs in composition memory (if any) and CM capacity*/ - u16 cb_unit_count, cb_max_count; + u32 cb_unit_count, cb_max_count; /*inidciate that thye composition memory is bypassed for this decoder (video only) */ Bool direct_video_memory; - /*clock drift in ms of object clock: this is the delay set by the audio renderer to keep AV in sync*/ + /*clock delay in ms of object clock: this is the delay set by the audio renderer to keep AV in sync + and corresponds to the amount of ms to delay non-audio streams to keep sync*/ s32 clock_drift; /*codec name*/ const char *codec_name; /*object type - match streamType (cf constants.h)*/ u32 od_type; /*audio properties*/ - u32 sample_rate, bits_per_sample, num_channels; + u32 sample_rate, afmt, num_channels; /*video properties (w & h also used for scene codecs)*/ u32 width, height, pixelFormat, par; - /*average birate over last second and max bitrate over one second at decoder input - expressed in bits per sec*/ - u32 avg_bitrate, instant_bitrate, max_bitrate; + /*average birate over last second and max bitrate over one second at decoder input according to media timeline - expressed in bits per sec*/ + u32 avg_bitrate, max_bitrate; + /*average birate over last second and max bitrate over one second at decoder input according to processing time - expressed in bits per sec*/ + u32 avg_process_bitrate, max_process_bitrate; u32 nb_dec_frames, nb_dropped; u32 first_frame_time, last_frame_time; u64 total_dec_time, irap_total_dec_time; @@ -149,45 +183,110 @@ typedef struct const char *media_url; } GF_MediaInfo; -/*fills the GF_MediaInfo structure describing the OD manager*/ +/*! gets information on an object +\param term the target terminal +\param odm the object manager to query +\param info filled with object manager properties +\return error if any +*/ GF_Err gf_term_get_object_info(GF_Terminal *term, GF_ObjectManager *odm, GF_MediaInfo *info); -/*gets current downloads info for the service - only use if ODM owns thesrevice, returns 0 otherwise. - @d_enum: in/out current enum - shall start to 0, incremented at each call. fct returns 0 if no more - downloads - @server: server name - @path: file/data location on server - @bytes_done, @total_bytes: file info. total_bytes may be 0 (eg http streaming) - @bytes_per_sec: guess what + +/*! gets current downloads info for the service - only use if ODM owns the service, returns 0 otherwise. +\param term the target terminal +\param odm the object manager to query +\param d_enum in/out current enum - shall start to 0, incremented at each call. fct returns 0 if no more downloads +\param url service URL +\param bytes_done bytes downloaded for this service +\param total_bytes total bytes for this service, may be 0 (eg http streaming) +\param bytes_per_sec download speed +\return GF_TRUE if success, GF_FALSE otherwise */ -Bool gf_term_get_download_info(GF_Terminal *term, GF_ObjectManager *odm, u32 *d_enum, const char **server, const char **path, u32 *bytes_done, u32 *total_bytes, u32 *bytes_per_sec); - -/*same principles as above , struct __netcom is defined in service.h*/ -typedef struct __netstatcom NetStatCommand; -Bool gf_term_get_channel_net_info(GF_Terminal *term, GF_ObjectManager *odm, u32 *d_enum, u32 *chid, NetStatCommand *netcom, GF_Err *ret_code); - -/*same principles as above , struct __netinfo is defined in service.h*/ -typedef struct __netinfocom NetInfoCommand; -GF_Err gf_term_get_service_info(GF_Terminal *term, GF_ObjectManager *odm, NetInfoCommand *netcom); - -/*retrieves world info of the scene @od belongs to. -If @odm is or points to an inlined OD the world info of the inlined content is retrieved -If @odm is NULL the world info of the main scene is retrieved -returns NULL if no WorldInfo available -returns world title if available -@descriptions: any textual descriptions is stored here - strings are not allocated +Bool gf_term_get_download_info(GF_Terminal *term, GF_ObjectManager *odm, u32 *d_enum, const char **url, u32 *bytes_done, u32 *total_bytes, u32 *bytes_per_sec); + +/*! Net statistics gathering object*/ +typedef struct +{ + /*percentage of packet loss from network. This cannot be figured out by the app since there is no + one-to-one mapping between the protocol packets and the final SL packet (cf RTP payloads)*/ + Float pck_loss_percentage; + /*channel port, control channel port if any (eg RTCP)*/ + u16 port, ctrl_port; + /*bandwidth used by channel & its control channel if any (both up and down) - expressed in bits per second + for HTTP connections, typically only bw_down is used*/ + u32 bw_up, bw_down, ctrl_bw_down, ctrl_bw_up; + /*set to 0 if channel is not part of a multiplex. Otherwise set to the multiplex port, and + above port info shall be identifiers in the multiplex - note that multiplexing overhead is ignored + in GPAC for the current time*/ + u16 multiplex_port; +} GF_TermNetStats; + +/*! gets network statistics for the given channel in the given object +\param term the target terminal +\param odm the object manager to query +\param d_enum in/out current enum - shall start to 0, incremented at each call. fct returns 0 if no more downloads +\param chid set to the channel identifier +\param net_stats filled with the channel network statistics +\param ret_code set to the error code if error +\return GF_TRUE if success, GF_FALSE otherwise +*/ +Bool gf_term_get_channel_net_info(GF_Terminal *term, GF_ObjectManager *odm, u32 *d_enum, u32 *chid, GF_TermNetStats *net_stats, GF_Err *ret_code); + +/*! URL information for current session*/ +typedef struct +{ + u32 service_id; + u16 track_num; + u16 track_total; + u32 genre; + const char *album; + const char *artist; + const char *comment; + const char *composer; + const char *name; + const char *writer; + const char *provider; + //as in MPEG_DASH role + const char *role; + const char *accessibility; + const char *rating; +} GF_TermURLInfo; +/*! gets meta information about the service +\param term the target terminal +\param odm the object manager to query +\param info filled with service information +\return error if any +*/ +GF_Err gf_term_get_service_info(GF_Terminal *term, GF_ObjectManager *odm, GF_TermURLInfo *info); + +/*! retrieves world info of the scene of an object. +\param term the target terminal +\param scene_od the object manager to query. If this is an inlined OD, the world info of the inlined content is retrieved. If NULL, the world info of the main scene is retrieved +\param descriptions any textual descriptions is stored here (const strings not allocated, do NOT modify) +\return NULL if no WorldInfo available or world title if available */ const char *gf_term_get_world_info(GF_Terminal *term, GF_ObjectManager *scene_od, GF_List *descriptions); -/*dumps scene graph in specified file, in BT or XMT format -@rad_name: file radical (NULL for stdout) - if not NULL MUST BE GF_MAX_PATH length -@filename [out]: if not null, returns the complete filename (rad + ext); MUST BE FREED BY THE CALLER -if @skip_proto is set proto declarations are not dumped -If @odm is or points to an inlined OD the inlined scene is dumped -If @odm is NULL the main scene is dumped +/*! dumps scene graph in specified file, in BT or XMT format +\param term the target terminal +\param rad_name file radical (NULL for stdout) - if not NULL MUST BE GF_MAX_PATH length +\param filename sets to the complete filename (rad + ext) and shall be destroyed by caller (optional can be NULL) +\param xml_dump if GF_TRUE, duimps using XML format (XMT-A, X3D) for scene graphs having both XML and simple text representations +\param skip_proto is GF_TRUE, proto declarations are not dumped +\param odm if this is an inlined OD, the inlined scene is dumped; if this is NULL, the main scene is dumped; otherwise the parent scene is dumped +\return error if any */ GF_Err gf_term_dump_scene(GF_Terminal *term, char *rad_name, char **filename, Bool xml_dump, Bool skip_proto, GF_ObjectManager *odm); +/*! prints filter session statistics as log tool app log level debuf +\param term the target terminal +*/ +void gf_term_print_stats(GF_Terminal *term); + +/*! prints filter session graph as log tool app log level debuf +\param term the target terminal +*/ +void gf_term_print_graph(GF_Terminal *term); + /*! @} */ #ifdef __cplusplus diff --git a/include/gpac/terminal.h b/include/gpac/terminal.h index 5a7f01c..54255a3 100644 --- a/include/gpac/terminal.h +++ b/include/gpac/terminal.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / Stream Management sub-project @@ -33,115 +33,192 @@ extern "C" { /*! - * \file <gpac/terminal.h> - * \brief GPAC media player API. +\file <gpac/terminal.h> +\brief GPAC media player API. */ -/*! \defgroup playback_grp Media Player - * \brief GPAC media player (streaming engine and compositor). - * +/*! +\addtogroup playback_grp Media Player +\brief GPAC media player (streaming engine and compositor). */ /*! - *\addtogroup terminal_grp Terminal - *\ingroup playback_grp - *\brief GPAC media player APIs. - * - *This section documents the user-level API of the GPAC media player. - * @{ - */ +\addtogroup terminal_grp Terminal +\ingroup playback_grp +\brief GPAC media player APIs. + +This section documents the user-level API of the GPAC media player. + +@{ +*/ #include <gpac/user.h> -/*creates a new terminal for a userApp callback*/ +/*! creates a new terminal for a userApp callback +\param user a user description +\return a new terminal*/ GF_Terminal *gf_term_new(GF_User *user); -/*delete the app - stop is done automatically, you don't have to do it before deleting the app -returns GF_IO_ERR if client couldn't be shutdown normally*/ +/*! deletes a terminal - stop is done automatically +\param term the target terminal +\return error if any (GF_IO_ERR if client couldn't be shutdown normally)*/ GF_Err gf_term_del(GF_Terminal *term); -/* Browses all registered relocators (ZIP-based, ISOFF-based or file-system-based to relocate a URL based on the user locale */ -Bool gf_term_relocate_url(GF_Terminal *term, const char *service_url, const char *parent_url, char *relocated_url, char *localized_url); -/*connects to a URL*/ +/*! connects to a URL - connection OK or error is acknowledged via the user callback +\param term the target terminal +\param URL the target URL +*/ void gf_term_connect(GF_Terminal *term, const char *URL); -/*disconnects the url*/ +/*! disconnects the current url +\param term the target terminal +*/ void gf_term_disconnect(GF_Terminal *term); -/*navigates to a given destination or shutdown/restart the current one if any. + +/*! navigates to a given destination or shutdown/restart the current one if any. This is the only safe way of restarting/jumping a presentation from inside the EventProc -where doing a disconnect/connect could deadlock if toURL is NULL, uses the current URL*/ +where doing a disconnect/connect could deadlock if toURL is NULL, uses the current URL +\param term the target terminal +\param toURL the new target URL +*/ void gf_term_navigate_to(GF_Terminal *term, const char *toURL); -/*restarts url from given time (in ms). -@pause_at_first_frame: if 1, pauses at the first frame. If 2, pauses at the first frame only if the terminal is in paused state. -Return value: - 0: service is not connected yet - 1: service has no seeking capabilities - 2: service has been seeked + +/*! restarts url from given time (in ms). +\param term the target terminal +\param from_time restart time in milliseconds +\param pause_at_first_frame if 1, pauses at the first frame. If 2, pauses at the first frame only if the terminal is in paused state. +\return 0: service is not connected yet, 1: service has no seeking capabilities, 2: service has been seeked */ u32 gf_term_play_from_time(GF_Terminal *term, u64 from_time, u32 pause_at_first_frame); -/*connect URL and seek right away - only needed when reloading the complete player (avoids waiting + +/*! connects URL and seek right away - only needed when reloading the complete player (avoids waiting for connection and post a seek..) -pause_at_first_frame: if 1, pauses rendering and streaming when starting, if 2 pauses only rendering + +\param term the target terminal +\param URL the target URL +\param time_in_ms connect time in milliseconds +\param pause_at_first_frame if 1, pauses rendering and streaming when starting, if 2 pauses only rendering */ void gf_term_connect_from_time(GF_Terminal *term, const char *URL, u64 time_in_ms, u32 pause_at_first_frame); -/*same as gf_term_connect but specifies a parent path*/ +/*! connects a URL but specify a parent path for this URL +\param term the target terminal +\param URL the target URL +\param parent_URL the parent URL of the connected service +*/ void gf_term_connect_with_path(GF_Terminal *term, const char *URL, const char *parent_URL); -/*returns current framerate - if @absoluteFPS is set, the return value is the absolute framerate, eg NbFrameCount/NbTimeSpent regardless of -whether a frame has been drawn or not, which means the FPS returned can be much greater than the target rendering -framerate - if @absoluteFPS is not set, the return value is the FPS taking into account not drawn frames (eg, less than - or equal to compositor FPS) +/*! gets framerate +\param term the target terminal +\param absoluteFPS if GF_TRUE, the return value is the absolute framerate, eg NbFrameCount/NbTimeSpent regardless of whether a frame has been drawn or not, which means the FPS returned can be much greater than the target rendering framerate. If GF_FALSE, the return value is the FPS taking into account not drawn frames (eg, less than or equal to compositor FPS) +\return current framerate */ Double gf_term_get_framerate(GF_Terminal *term, Bool absoluteFPS); -/*get main scene current time in milliseconds*/ +/*! gets main scene current time +\param term the target terminal +\return time in milliseconds +*/ u32 gf_term_get_time_in_ms(GF_Terminal *term); -/*get elapsed time in milliseconds since loading of the scene - may be different from scene time when seeking or live content*/ +/*! gets elapsed time since loading of the scene - may be different from scene time when seeking or live content +\param term the target terminal +\return time ellapsed in milliseconds +*/ u32 gf_term_get_elapsed_time_in_ms(GF_Terminal *term); -/*returns current URL address*/ +/*! gets current URL +\param term the target terminal +\return the current URL +*/ const char *gf_term_get_url(GF_Terminal *term); -/*get viewpoints/viewports for main scene - idx is 1-based, and if greater than number of viewpoints return GF_EOS*/ +/*! gets viewpoints/viewports for main scene +\param term the target terminal +\param viewpoint_idx 1-based index of the viewport to query +\param outName set to the name of the viewport +\param is_bound set to GF_TRUE of that viewport is bound +\return error if any, GF_EOS if index is greater than number of viewpoints +*/ GF_Err gf_term_get_viewpoint(GF_Terminal *term, u32 viewpoint_idx, const char **outName, Bool *is_bound); -/*set active viewpoints/viewports for main scene given its name - idx is 1-based, or 0 to set by viewpoint name -if only one viewpoint is present in the scene, this will bind/unbind it*/ + +/*! sets active viewpoints/viewports for main scene given its name +\note if only one viewpoint is present in the scene +\param term the target terminal +\param viewpoint_idx 1-based index of the viewport to set or 0 to set by viewpoint name +\param viewpoint_name name of the viewport +\return error if any, GF_EOS if index is greater than number of viewpoints +*/ GF_Err gf_term_set_viewpoint(GF_Terminal *term, u32 viewpoint_idx, const char *viewpoint_name); -/*adds an object to the scene - only possible if scene has selectable streams (cf GF_OPT_CAN_SELECT_STREAMS option)*/ +/*! adds an object to the scene - only possible if scene has selectable streams (cf GF_OPT_CAN_SELECT_STREAMS option) +\param term the target terminal +\param url the URL of the object to inject +\param auto_play selcts the object for playback when inserting it +\return error if any +*/ GF_Err gf_term_add_object(GF_Terminal *term, const char *url, Bool auto_play); - -/*set/set option - most of the terminal cfg is done through options, please refer to options.h for details*/ +/*! sets option - most of the terminal cfg is done through options, please refer to options.h for details +\param term the target terminal +\param opt_type option to set +\param opt_value option value +\return error if any +*/ GF_Err gf_term_set_option(GF_Terminal *term, u32 opt_type, u32 opt_value); -u32 gf_term_get_option(GF_Terminal *term, u32 opt_type); -/*checks if given URL is understood by client. -if use_parent_url is set, relative URLs are solved against the current presentation URL*/ -Bool gf_term_is_supported_url(GF_Terminal *term, const char *fileName, Bool use_parent_url, Bool no_mime_check); +/*! gets option - most of the terminal cfg is done through options, please refer to options.h for details +\param term the target terminal +\param opt_type option to set +\return option value +*/ +u32 gf_term_get_option(GF_Terminal *term, u32 opt_type); -/*returns the current service ID for MPEG-2 TS mux - returns 0 if no service ID is associated (or not loaded yet)*/ +/*! checks if given URL is understood by client. +\param term the target terminal +\param URL the URL to check +\param use_parent_url if GF_TRUE, relative URLs are solved against the current presentation URL +\param no_mime_check if GF_TRUE, checks by file extension only +\return GF_TRUE if client should be able to handle the URL*/ +Bool gf_term_is_supported_url(GF_Terminal *term, const char *URL, Bool use_parent_url, Bool no_mime_check); + +/*! get the current service ID for MPEG-2 TS mux +\param term the target terminal +\return service ID or 0 if no service ID is associated or not loaded yet +*/ u32 gf_term_get_current_service_id(GF_Terminal *term); -/*sets simulation frame rate*/ -GF_Err gf_term_set_simulation_frame_rate(GF_Terminal * term, Double frame_rate); -/*gets simulation frame rate*/ +/*! gets simulation frame rate +\param term the target terminal +\param nb_frames_drawn set to the number of frames drawn +\return simulation frames per seconds +*/ Double gf_term_get_simulation_frame_rate(GF_Terminal *term, u32 *nb_frames_drawn); -/*gets simulation frame rate*/ +/*! gets visual output size (current window/display size) +\param term the target terminal +\param width set to the display width +\param height set to the display height +\return error if any +*/ GF_Err gf_term_get_visual_output_size(GF_Terminal *term, u32 *width, u32 *height); -/*process shortcuts*/ +/*! process shortcuts for key events +\param term the target terminal +\param ev the event to process +*/ void gf_term_process_shortcut(GF_Terminal *term, GF_Event *ev); +/*! sets playback speed +\param term the target terminal +\param speed the requested speed +\return error if any +*/ GF_Err gf_term_set_speed(GF_Terminal *term, Fixed speed); -/*sends a set of scene commands (BT, XMT, X3D, LASeR+XML) to the scene -type indicates the language used - accepted values are +/*! sends a set of scene commands (BT, XMT, X3D, LASeR+XML) to the scene +\param term the target terminal +\param type indicates the language used - accepted values are "model/x3d+xml" or "x3d": commands is an X3D+XML scene "model/x3d+vrml" or "xrdv": commands is an X3D+VRML scene "model/vrml" or "vrml": commands is an VRML scene @@ -150,98 +227,103 @@ type indicates the language used - accepted values are "image/svg+xml" or "svg": commands is an SVG scene "application/x-laser+xml" or "laser": commands is an SVG/LASeR+XML scene or a list of LASeR+XML updates if not specified, the type will be guessed from the current root node if any +\param com the textual update +\return error if any */ GF_Err gf_term_scene_update(GF_Terminal *term, char *type, char *com); - -/*request visual output size change: +/*! sets visual output size change: * NOT NEEDED WHEN THE TERMINAL IS HANDLING THE DISPLAY WINDOW (cf user.h) * if the user app manages the output window it shall resize it before calling this + +\param term the target terminal +\param NewWidth the output width in pixels +\param NewHeight the output height in pixels +\return error if any */ GF_Err gf_term_set_size(GF_Terminal *term, u32 NewWidth, u32 NewHeight); -/*returns current text selection if any, or NULL otherwise. If probe mode is set, returns a non-NULL string ("") -if some text is selected*/ +/*! returns current text selection. +\param term the target terminal +\param probe_only if GF_TRUE, simply returns a non-NULL string ("") if some text is selected +\return text selection if any, or NULL otherwise. */ const char *gf_term_get_text_selection(GF_Terminal *term, Bool probe_only); -/*pastes text into current selection if any. If probe mode is set, only check if text is currently edited -if some text is selected*/ -GF_Err gf_term_paste_text(GF_Terminal *term, const char *txt, Bool probe_only); - -/*decodes pending media and render frame. -NOTE: This can only be used when the terminal runs without visual thread (GF_TERM_NO_VISUAL_THREAD flag set) -returns estimated time left until next frame should be drawn. If GF_TERM_NO_REGULATION is not set, the function will sleep -for until next frame should be drawn before returning. +/*! pastes text into current selection if any. +\param term the target terminal +\param txt the text to set +\param probe_only if GF_TRUE, only check if text is currently edited +\return error if any */ -u32 gf_term_process_step(GF_Terminal *term); +GF_Err gf_term_paste_text(GF_Terminal *term, const char *txt, Bool probe_only); -/*decodes all pending media and render frame until no scene changes are detected and no clocks is buffering. -This does not flush the backbuffer to the front buffer, you have to call @gf_term_process_flush_video for this -NOTE: This can only be used when the terminal runs without visual thread (GF_TERM_NO_VISUAL_THREAD flag set) +/*! processes pending tasks in the media session +\note If filter session regulation is not disabled, the function will sleep for until next frame should be drawn before returning. +\param term the target terminal +\return GF_TRUE if a new frame was drawn, GF_FALSE otherwise */ -GF_Err gf_term_process_flush(GF_Terminal *term); +Bool gf_term_process_step(GF_Terminal *term); -/*flushes video backbuffer to screen. This is typically used after gf_term_process_flush, once the screen buffer is no longer needed -NOTE: This can only be used when the terminal runs without visual thread (GF_TERM_NO_VISUAL_THREAD flag set) +/*! post user interaction to terminal +\param term the target terminal +\param event the event to post +\return GF_TRUE if event was directly consummed */ -GF_Err gf_term_process_flush_video(GF_Terminal *term); - -/*post user interaction to terminal*/ -/*NOT NEEDED WHEN THE TERMINAL IS HANDLING THE DISPLAY WINDOW (cf user.h)*/ Bool gf_term_user_event(GF_Terminal *term, GF_Event *event); -/*post event to terminal*/ -Bool gf_term_send_event(GF_Terminal *term, GF_Event *evt); - -/*post extended user mouse interaction to terminal - X and Y are point coordinates in the display expressed in 2D coord system top-left (0,0), Y increasing towards bottom - @xxx_but_down: specifiy whether the mouse button is down(2) or up (1), 0 if unchanged - @wheel: specifiy current wheel inc (0: unchanged , +1 for one wheel delta forward, -1 for one wheel delta backward) -*/ -/*NOT NEEDED WHEN THE TERMINAL IS HANDLING THE DISPLAY WINDOW (cf user.h)*/ -void gf_term_mouse_input(GF_Terminal *term, GF_EventMouse *event); - -/*post extended user key interaction to terminal - @key_code: GPAC DOM code of input key - @hw_code: hardware code of input key - @isKeyUp: set if key is released +/*! post event to terminal +\warning NOT NEEDED WHEN THE TERMINAL IS HANDLING THE DISPLAY WINDOW (cf user.h) +\param term the target terminal +\param evt the event to post +\return GF_TRUE if event was directly consummed */ -/*NOT NEEDED WHEN THE TERMINAL IS HANDLING THE DISPLAY WINDOW (cf user.h)*/ -Bool gf_term_keyboard_input(GF_Terminal *term, u32 key_code, u32 hw_code, Bool isKeyUp); +Bool gf_term_send_event(GF_Terminal *term, GF_Event *evt); -/*post extended user character interaction to terminal - @character: unicode character input -*/ -/*NOT NEEDED WHEN THE TERMINAL IS HANDLING THE DISPLAY WINDOW (cf user.h)*/ -void gf_term_string_input(GF_Terminal *term, u32 character); /*framebuffer access*/ #include <gpac/color.h> -/*gets screen buffer - this locks the scene graph too until released is called*/ +/*! gets screen buffer +\note the screen buffer is released by calling gf_term_release_screen_buffer +\param term the target terminal +\param framebuffer set to the framebuffer information +\return error if any +*/ GF_Err gf_term_get_screen_buffer(GF_Terminal *term, GF_VideoSurface *framebuffer); -/*releases screen buffer and unlocks graph*/ -GF_Err gf_term_release_screen_buffer(GF_Terminal *term, GF_VideoSurface *framebuffer); - -/*gets view buffer - this locks the scene graph too until released is called -view_idx ranges from 0 to GF_OPT_NUM_STEREO_VIEWS value -the buffer is released by calling gf_term_release_screen_buffer*/ -GF_Err gf_term_get_offscreen_buffer(GF_Terminal *term, GF_VideoSurface *framebuffer, u32 view_idx, u32 depth_buffer_type); +/*! gets view buffer - this locks the scene graph too until released is called +\note the screen buffer is released by calling gf_term_release_screen_buffer +\param term the target terminal +\param framebuffer set to the framebuffer information +\param view_idx indicates the view index, and ranges from 0 to GF_OPT_NUM_STEREO_VIEWS value +\param depth_buffer_type indicates the depth buffer mode +\return error if any +*/ +GF_Err gf_term_get_offscreen_buffer(GF_Terminal *term, GF_VideoSurface *framebuffer, u32 view_idx, GF_CompositorGrabMode depth_buffer_type); -/*ObjectManager used by both terminal and object browser (term_info.h)*/ -typedef struct _od_manager GF_ObjectManager; +/*! releases screen buffer and unlocks graph +\param term the target terminal +\param framebuffer the pointer passed to \ref gf_term_get_screen_buffer +\return error if any +*/ +GF_Err gf_term_release_screen_buffer(GF_Terminal *term, GF_VideoSurface *framebuffer); -/*switches quality up or down - can be called several time in the same direction +/*! switches quality up or down - can be called several time in the same direction this will call all decoders to adjust their quality levels -VERY BASIC INTERFACE*/ -void gf_term_switch_quality(GF_Terminal *term, Bool up); +\param term the target terminal +\param up if GF_TRUE, switches quality up,otherwise down +*/ +void gf_term_switch_quality(GF_Terminal *term, Bool up); -/*get global clock in milliseconds*/ -u32 gf_term_get_clock(GF_Terminal *term); +/*! checks if a mime type is supported +\param term the target terminal +\param mime the mime type to check +\return GF_TRUE if supported +*/ +Bool gf_term_is_type_supported(GF_Terminal *term, const char* mime); /*! @} */ diff --git a/include/gpac/thread.h b/include/gpac/thread.h index 6e28e3d..b749971 100644 --- a/include/gpac/thread.h +++ b/include/gpac/thread.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -31,26 +31,97 @@ extern "C" { #endif /*! - * \file <gpac/thread.h> - * \brief Threading and Mutual Exclusion - */ +\file <gpac/thread.h> +\brief Threading and Mutual Exclusion +*/ /*! -* \addtogroup thr_grp Threading -* \ingroup utils_grp -* \brief Threading and Mutual Exclusion -* -*This section documents the threading of the GPAC framework. These provide an easy way to implement -*safe multithreaded tools. -* @{ +\addtogroup thr_grp +\brief Threading and Mutual Exclusion + +This section documents the threading of the GPAC framework. These provide an easy way to implement +safe multithreaded tools. + +Available tools are thread, mutex and semaphore + +\defgroup thread_grp Thread +\ingroup thr_grp +\defgroup mutex_grp Mutex +\ingroup thr_grp +\defgroup sema_grp Semaphore +\ingroup thr_grp +*/ + +/*! +\addtogroup thread_grp +\brief Thread processing + +The thread object allows executing some code independently of the main process of your application. +@{ */ #include <gpac/tools.h> + + //atomic ref_count++ / ref_count-- +#if defined(WIN32) || defined(_WIN32_WCE) +#include <windows.h> +#include <winbase.h> + +/*! atomic integer increment */ +#define safe_int_inc(__v) InterlockedIncrement((int *) (__v)) +/*! atomic integer decrement */ +#define safe_int_dec(__v) InterlockedDecrement((int *) (__v)) +/*! atomic integer addition */ +#define safe_int_add(__v, inc_val) InterlockedAdd((int *) (__v), inc_val) +/*! atomic integer substraction */ +#define safe_int_sub(__v, dec_val) InterlockedAdd((int *) (__v), -dec_val) +/*! atomic large integer addition */ +#define safe_int64_add(__v, inc_val) InterlockedAdd64((LONG64 *) (__v), inc_val) +/*! atomic large integer substraction */ +#define safe_int64_sub(__v, dec_val) InterlockedAdd64((LONG64 *) (__v), -dec_val) + +#else + +#ifdef GPAC_NEED_LIBATOMIC + +/*! atomic integer increment */ +#define safe_int_inc(__v) __atomic_add_fetch((int *) (__v), 1, __ATOMIC_SEQ_CST) +/*! atomic integer decrement */ +#define safe_int_dec(__v) __atomic_sub_fetch((int *) (__v), 1, __ATOMIC_SEQ_CST) +/*! atomic integer addition */ +#define safe_int_add(__v, inc_val) __atomic_add_fetch((int *) (__v), inc_val, __ATOMIC_SEQ_CST) +/*! atomic integer substraction */ +#define safe_int_sub(__v, dec_val) __atomic_sub_fetch((int *) (__v), dec_val, __ATOMIC_SEQ_CST) +/*! atomic large integer addition */ +#define safe_int64_add(__v, inc_val) __atomic_add_fetch((int64_t *) (__v), inc_val, __ATOMIC_SEQ_CST) +/*! atomic large integer substraction */ +#define safe_int64_sub(__v, dec_val) __atomic_sub_fetch((int64_t *) (__v), dec_val, __ATOMIC_SEQ_CST) + +#else + +/*! atomic integer increment */ +#define safe_int_inc(__v) __sync_add_and_fetch((int *) (__v), 1) +/*! atomic integer decrement */ +#define safe_int_dec(__v) __sync_sub_and_fetch((int *) (__v), 1) +/*! atomic integer addition */ +#define safe_int_add(__v, inc_val) __sync_add_and_fetch((int *) (__v), inc_val) +/*! atomic integer substraction */ +#define safe_int_sub(__v, dec_val) __sync_sub_and_fetch((int *) (__v), dec_val) +/*! atomic large integer addition */ +#define safe_int64_add(__v, inc_val) __sync_add_and_fetch((int64_t *) (__v), inc_val) +/*! atomic large integer substraction */ +#define safe_int64_sub(__v, dec_val) __sync_sub_and_fetch((int64_t *) (__v), dec_val) + +#endif //GPAC_NEED_LIBATOMIC + +#endif + + /*! - *\brief Thread states +\brief Thread states * - *Inidcates the execution status of a thread + *Indicates the execution status of a thread */ enum { @@ -63,65 +134,63 @@ enum }; /*! - *\brief abstracted thread object - * - *The abstracted thread object allows you to execute some code independently of the main process of your application. +\brief abstracted thread object */ typedef struct __tag_thread GF_Thread; /*! - *\brief thread constructor - * - *Constructs a new thread object - *\param name log name of the thread if any +\brief thread constructor + +Constructs a new thread object +\param name log name of the thread if any +\return new thread object */ GF_Thread *gf_th_new(const char *name); /*! - *\brief thread destructor - * - * Kills the thread if running and destroys the object - *\param th the thread object +\brief thread destructor + +Kills the thread if running and destroys the object +\param th the thread object */ void gf_th_del(GF_Thread *th); /*! - * \brief thread run function callback - * - *The gf_thread_run type is the type for the callback of the \ref gf_thread_run function - *\param par opaque user data - *\return exit code of the thread, usually 1 for error and 0 if normal execution - * +\brief thread run function callback + +The gf_thread_run type is the type for the callback of the \ref gf_thread_run function +\param par opaque user data +\return exit code of the thread, usually 1 for error and 0 if normal execution */ typedef u32 (*gf_thread_run)(void *par); /*! - *\brief thread execution - * - *Executes the thread with the given function - *\param th the thread object - *\param run the function this thread will call - *\param par the argument to the function the thread will call - *\note A thread may be run several times but cannot be run twice in the same time. +\brief thread execution + +Executes the thread with the given function +\note A thread may be run several times but cannot be run twice in the same time. +\param th the thread object +\param run the function this thread will call +\param par the argument to the function the thread will call +\return error if any */ GF_Err gf_th_run(GF_Thread *th, gf_thread_run run, void *par); /*! - *\brief thread stopping - * - *Waits for the thread exit until return - *\param th the thread object +\brief thread stopping + +Waits for the thread exit until return +\param th the thread object */ void gf_th_stop(GF_Thread *th); /*! - *\brief thread status query - * - *Gets the thread status - *\param th the thread object +\brief thread status query + +Gets the thread status +\param th the thread object +\return thread status */ u32 gf_th_status(GF_Thread *th); -/*! - * thread priorities - */ +/*! thread priorities */ enum { /*!Idle Priority*/ @@ -145,137 +214,156 @@ enum }; /*! - *\brief thread priority - * - *Sets the thread execution priority level. - *\param th the thread object - *\param priority the desired priority - *\note this should be used with caution, especially use of real-time priorities. +\brief thread priority + +Sets the thread execution priority level. +\param th the thread object +\param priority the desired priority +\note this should be used with caution, especially use of real-time priorities. */ void gf_th_set_priority(GF_Thread *th, s32 priority); /*! - *\brief current thread ID - * - *Gets the ID of the current thread the caller is in. +\brief current thread ID + +Gets the ID of the current thread the caller is in. +\return thread ID */ u32 gf_th_id(); -#ifdef GPAC_ANDROID -/*! - * Register a function that will be called before pthread_exist is called - */ +#ifdef GPAC_CONFIG_ANDROID +/*! Register a function that will be called before pthread_exist is called */ GF_Err gf_register_before_exit_function(GF_Thread *t, u32 (*toRunBeforePthreadExit)(void *param)); /*! Get the current Thread if any. May return NULL */ GF_Thread * gf_th_current(); -#endif /* GPAC_ANDROID */ +#endif /* GPAC_CONFIG_ANDROID */ + +/*! @} */ /*! - *\brief abstracted mutex object - * - *The abstracted mutex object allows you to make sure that portions of the code (typically access to variables) cannot be executed - *by two threads (or a thread and the main process) at the same time. +\addtogroup mutex_grp +\brief Mutual exclusion + +The mutex object allows ensuring that portions of the code (typically access to variables) cannot be executed by two threads (or a thread and the main process) at the same time. + +@{ */ + +/*! +\brief abstracted mutex object*/ typedef struct __tag_mutex GF_Mutex; -/* - *\brief mutex constructor - * - *Contructs a new mutex object - *\param log name of the thread if any +/*! +\brief mutex constructor + +Contructs a new mutex object +\param name log name of the thread if any +\return new mutex */ GF_Mutex *gf_mx_new(const char *name); -/* - *\brief mutex denstructor - * - *Destroys a mutex object. This will wait for the mutex to be released if needed. - *\param mx the mutex object +/*! +\brief mutex denstructor + +Destroys a mutex object. This will wait for the mutex to be released if needed. +\param mx the mutex object */ void gf_mx_del(GF_Mutex *mx); -/* - *\brief mutex locking - * - *Locks the mutex object, making sure that another thread locking this mutex cannot execute until the mutex is unlocked. - *\param mx the mutex object - *\return 1 if success, 0 if error locking the mutex (which should never happen) +/*! +\brief mutex locking + +Locks the mutex object, making sure that another thread locking this mutex cannot execute until the mutex is unlocked. +\param mx the mutex object +\return 1 if success, 0 if error locking the mutex (which should never happen) */ u32 gf_mx_p(GF_Mutex *mx); -/* - *\brief mutex unlocking - * - *Unlocks the mutex object, allowing other threads waiting on this mutex to continue their execution - *\param mx the mutex object +/*! +\brief mutex unlocking + +Unlocks the mutex object, allowing other threads waiting on this mutex to continue their execution +\param mx the mutex object */ void gf_mx_v(GF_Mutex *mx); -/* - *\brief mutex non-blocking lock - * - *Attemps to lock the mutex object without blocking until the object is released. - *\param mx the mutex object - *\return 1 if the mutex has been successfully locked, in which case it shall then be unlocked, or 0 if the mutex is locked by another thread. +/*! +\brief mutex non-blocking lock + +Attemps to lock the mutex object without blocking until the object is released. +\param mx the mutex object +\return GF_TRUE if the mutex has been successfully locked, in which case it shall then be unlocked, or GF_FALSE if the mutex is locked by another thread. */ Bool gf_mx_try_lock(GF_Mutex *mx); -/* - *\brief get mutex number of locks - * - *Returns the number of locks on the mutex if the caller thread is holding the mutex. - *\param mx the mutex object - *\return -1 if the mutex is not hold by the calling thread, or the number of locks (possibly 0) otherwise. +/*! +\brief get mutex number of locks + +Returns the number of locks on the mutex if the caller thread is holding the mutex. +\param mx the mutex object +\return -1 if the mutex is not hold by the calling thread, or the number of locks (possibly 0) otherwise. */ s32 gf_mx_get_num_locks(GF_Mutex *mx); +/*! @} */ + +/*! +\addtogroup sema_grp +\brief Semaphore + + +The semaphore object allows controling how portions of the code (typically access to variables) are + executed by two threads (or a thread and the main process) at the same time. The best image for a semaphore is a limited set + of money coins (always easy to understand hmm?). If no money is in the set, nobody can buy anything until a coin is put back in the set. + When the set is full, the money is wasted (call it "the bank"...). + +@{ +*/ + /********************************************************************* Semaphore Object **********************************************************************/ /*! - *\brief abstracted semaphore object - * - *The abstracted semaphore object allows you to control how portions of the code (typically access to variables) are executed - *by two threads (or a thread and the main process) at the same time. The best image for a semaphore is a limited set - *of money coins (always easy to understand hmm?). If no money is in the set, nobody can buy anything until a coin is - *put back in the set. When the set is full, the money is wasted (call it "the bank"...). +\brief abstracted semaphore object */ typedef struct __tag_semaphore GF_Semaphore; -/* - *\brief semaphore constructor - * - *Constructs a new semaphore object - *\param MaxCount the maximum notification count of this semaphore - *\param InitCount the initial notification count of this semaphore upon construction - *\return the semaphore object +/*! +\brief semaphore constructor + +Constructs a new semaphore object +\param MaxCount the maximum notification count of this semaphore +\param InitCount the initial notification count of this semaphore upon construction +\return the semaphore object */ GF_Semaphore *gf_sema_new(u32 MaxCount, u32 InitCount); -/* - *\brief semaphore destructor - * - *Destructs the semaphore object. This will wait for the semaphore to be released if needed. +/*! +\brief semaphore destructor + +Destructs the semaphore object. This will wait for the semaphore to be released if needed. +\param sm the semaphore object */ void gf_sema_del(GF_Semaphore *sm); -/* - *\brief semaphore notification. - * - *Notifies the semaphore of a certain amount of releases. - *\param sm the semaphore object - *\param nb_rel sm the number of release to notify - *\return GF_TRUE if success, GF_FALSE otherwise +/*! +\brief semaphore notification. + +Notifies the semaphore of a certain amount of releases. +\param sm the semaphore object +\param nb_rel sm the number of release to notify +\return GF_TRUE if success, GF_FALSE otherwise */ Bool gf_sema_notify(GF_Semaphore *sm, u32 nb_rel); -/* - *\brief semaphore wait - * - *Waits for the semaphore to be accessible (eg, may wait an infinite time). - *\param sm the semaphore object +/*! +\brief semaphore wait + +Waits for the semaphore to be accessible (eg, may wait an infinite time). +\param sm the semaphore object +\return GF_TRUE if successfull wait, GF_FALSE if wait failed */ -void gf_sema_wait(GF_Semaphore *sm); -/* - *\brief semaphore time wait - * - *Waits for a certain for the semaphore to be accessible, and returns when semaphore is accessible or wait time has passed. - *\param sm the semaphore object - *\param time_out the amount of time to wait for the release in milliseconds - *\return returns 1 if the semaphore was released before the timeout, 0 if the semaphore is still not released after the timeout. +Bool gf_sema_wait(GF_Semaphore *sm); +/*! +\brief semaphore time wait + +Waits for a certain for the semaphore to be accessible, and returns when semaphore is accessible or wait time has passed. +\param sm the semaphore object +\param time_out the amount of time to wait for the release in milliseconds +\return returns 1 if the semaphore was released before the timeout, 0 if the semaphore is still not released after the timeout. */ Bool gf_sema_wait_for(GF_Semaphore *sm, u32 time_out); @@ -288,4 +376,3 @@ Bool gf_sema_wait_for(GF_Semaphore *sm, u32 time_out); #endif /*_GF_THREAD_H_*/ - diff --git a/include/gpac/token.h b/include/gpac/token.h index a1bb8e1..6f4b7b3 100644 --- a/include/gpac/token.h +++ b/include/gpac/token.h @@ -31,67 +31,67 @@ extern "C" { #endif /*! - * \file <gpac/token.h> - * \brief String Tokenizer - */ +\file <gpac/token.h> +\brief String Tokenizer +*/ /*! -* \addtogroup tok_grp Tokenizer -* \ingroup utils_grp -* \brief String Tokenizer -* -*This section documents the basic string tokenizer of the GPAC framework. -* @{ +\addtogroup tok_grp +\brief String Tokenizer + +This section documents the basic string tokenizer of the GPAC framework. + +@{ */ #include <gpac/tools.h> /*! - *\brief get string component - * - *Gets the next string component comprised in a given set of characters - *\param Buffer source string to scan - *\param Start char offset from beginning of buffer where tokenization shall start - *\param Separator separator characters to use - *\param Container output buffer location - *\param ContainerSize output buffer allocated size - *\return position of the first char in the buffer after the last terminating separator, or -1 if token could not be found +\brief get string component + +Gets the next string component comprised in a given set of characters +\param Buffer source string to scan +\param Start char offset from beginning of buffer where tokenization shall start +\param Separator separator characters to use +\param Container output buffer location +\param ContainerSize output buffer allocated size +\return position of the first char in the buffer after the last terminating separator, or -1 if token could not be found */ s32 gf_token_get(const char* Buffer, s32 Start, const char* Separator, char* Container, s32 ContainerSize); /*! - *\brief get string component without delimitting characters - * - *Gets the next string component comprised in a given set of characters, removing surrounding characters - *\param Buffer source string to scan - *\param Start char offset from beginning of buffer where tokenization shall start - *\param Separator separator characters to use - *\param strip_set surrounding characters to remove - *\param Container output buffer location - *\param ContainerSize output buffer allocated size - *\return position of the first char in the buffer after the last terminating separator, or -1 if token could not be found +\brief get string component without delimitting characters + +Gets the next string component comprised in a given set of characters, removing surrounding characters +\param Buffer source string to scan +\param Start char offset from beginning of buffer where tokenization shall start +\param Separator separator characters to use +\param strip_set surrounding characters to remove +\param Container output buffer location +\param ContainerSize output buffer allocated size +\return position of the first char in the buffer after the last terminating separator, or -1 if token could not be found */ s32 gf_token_get_strip(const char* Buffer, s32 Start, const char* Separator, const char* strip_set, char* Container, s32 ContainerSize); /*! - *\brief line removal - * - *Gets one line from buffer and remove delimiters CR, LF and CRLF - *\param buffer source string to scan - *\param start char offset from beginning of buffer where tokenization shall start - *\param size size of the input buffer to analyze - *\param line_buffer output buffer location - *\param line_buffer_size output buffer allocated size - *\return position of the first char in the buffer after the last line delimiter, or -1 if no line could be found +\brief line removal + +Gets one line from buffer and remove delimiters CR, LF and CRLF +\param buffer source string to scan +\param start char offset from beginning of buffer where tokenization shall start +\param size size of the input buffer to analyze +\param line_buffer output buffer location +\param line_buffer_size output buffer allocated size +\return position of the first char in the buffer after the last line delimiter, or -1 if no line could be found */ s32 gf_token_get_line(const char *buffer, u32 start, u32 size, char *line_buffer, u32 line_buffer_size); /*! - *\brief pattern location - * - *Locates a pattern in the buffer - *\param Buffer source string to scan - *\param Start char offset from beginning of buffer where tokenization shall start - *\param Size size of the input buffer to analyze - *\param Pattern pattern to locate - *\return position of the first char in the buffer after the pattern, or -1 if pattern could not be found +\brief pattern location + +Locates a pattern in the buffer +\param Buffer source string to scan +\param Start char offset from beginning of buffer where tokenization shall start +\param Size size of the input buffer to analyze +\param Pattern pattern to locate +\return position of the first char in the buffer after the pattern, or -1 if pattern could not be found */ s32 gf_token_find(const char* Buffer, u32 Start, u32 Size, const char* Pattern); diff --git a/include/gpac/tools.h b/include/gpac/tools.h index a799f8c..80674fe 100644 --- a/include/gpac/tools.h +++ b/include/gpac/tools.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -32,181 +32,79 @@ extern "C" { #include <gpac/setup.h> #include <gpac/version.h> +#include <time.h> -/*! \file "gpac/tools.h" - * \brief Core definitions and tools of GPAC. - * - * This file contains basic functions and core definitions of the GPAC framework. This file is - * usually included by all GPAC header files since it contains the error definitions. -*/ - -/*! \defgroup utils_grp Core Tools - * \brief Core definitions and tools of GPAC. - * - * You will find in this module the documentation of the core tools used in GPAC. -*/ - -/*! - * \ingroup utils_grp - * \brief Base definitions and functions of GPAC. - * - * This section documents some very basic functions and core definitions of the GPAC framework. - * @{ - */ - /*! - * \brief Stringizer - * \hideinitializer - * - * Macro transforming its input name into a string -*/ -#define gf_stringizer(x) #x +\file "gpac/tools.h" +\brief Core definitions and tools of GPAC. -/*! - * \brief Memory allocation for a structure - * \hideinitializer - * - * Macro allocating memory and zero-ing it +This file contains basic functions and core definitions of the GPAC framework. This file is +usually included by all GPAC header files since it contains the error definitions. */ -#define GF_SAFEALLOC(__ptr, __struct) { __ptr = (__struct *) gf_malloc(sizeof(__struct)); if (__ptr) memset((void *) __ptr, 0, sizeof(__struct)); } /*! - * \brief Memory allocation for an array of n structs - * \hideinitializer - * - * Macro allocating memory for n structures and zero-ing it -*/ -#define GF_SAFE_ALLOC_N(__ptr, __n, __struct) { __ptr = (__struct *) gf_malloc( __n * sizeof(__struct)); if (__ptr) memset((void *) __ptr, 0, __n * sizeof(__struct)); } +\addtogroup utils_grp Core Tools +\brief Core definitions and tools of GPAC. -/*! - * \brief 4CC Formatting - * \hideinitializer - * - * Macro formating a 4-character code (or 4CC) "abcd" as 0xAABBCCDD +You will find in this module the documentation of the core tools used in GPAC. */ -#ifndef GF_4CC -#define GF_4CC(a,b,c,d) (((a)<<24)|((b)<<16)|((c)<<8)|(d)) -#endif /*! - * \brief GPAC feature list - * - * returns the list of features enabled/disabled in this GPAC build. -*/ -const char *gpac_features(); +\addtogroup setup_grp +@{ + */ /*! - * \brief 4CC Printing - * - * returns a 4CC printable form -*/ -const char *gf_4cc_to_str(u32 type); +\brief Stringizer +\hideinitializer -/*! - * \brief asprintf() portable implementation - * - * similar to sprintf, except it allows the string on the - * \note asprintf implementation for windows +Macro transforming its input name into a string */ -int gf_asprintf(char **buffer, const char *fmt, ...); - - -size_t gf_fread(void *ptr, size_t size, size_t nmemb, FILE *stream); +#define gf_stringizer(x) #x /*! - * \brief file writing helper - * - * Wrapper to properly handle calls to fwrite() - * Ensures proper error handling is invoked when it fails. - * \return Same as gf_fwrite - * -*/ -size_t gf_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); +\brief 4CC Formatting +\hideinitializer -/*! - * \brief large file opening - * - * Opens a large file (>4GB) - * \param file_name Same semantics as fopen - * \param mode Same semantics as fopen - * \return stream handle of the file object - * \note You only need to call this function if you're suspecting the file to be a large one (usually only media files), otherwise use regular stdio. +Macro formating a 4-character code (or 4CC) "abcd" as 0xAABBCCDD */ -FILE *gf_fopen(const char *file_name, const char *mode); +#ifndef GF_4CC +#define GF_4CC(a,b,c,d) ((((u32)a)<<24)|(((u32)b)<<16)|(((u32)c)<<8)|((u32)d)) +#endif -/*! - * \brief file closing - * - * Closes a file - * \param file file to close - * \note You only need to call this function if you're suspecting the file to be a large one (usually only media files), otherwise use regular stdio. -*/ -s32 gf_fclose(FILE *file); +/*! minimum buffer size to hold any 4CC in string format*/ +#define GF_4CC_MSIZE 10 -/*! - * \brief large file position query - * - * Queries the current read/write position in a large file - * \param f Same semantics as ftell - * \return position in the file - * \note You only need to call this function if you're suspecting the file to be a large one (usually only media files), otherwise use regular stdio. -*/ -u64 gf_ftell(FILE *f); -/*! - * \brief large file seeking - * - * Seeks the current read/write position in a large file - * \param f Same semantics as fseek - * \param pos Same semantics as fseek - * \param whence Same semantics as fseek - * \return new position in the file - * \note You only need to call this function if you're suspecting the file to be a large one (usually only media files), otherwise use regular stdio. +/*! converts four character code to string +\param type a four character code +\return a printable form of the code */ -u64 gf_fseek(FILE *f, s64 pos, s32 whence); +const char *gf_4cc_to_str(u32 type); -/*! - * \brief get basename from filename/path - * - * Returns a pointer to the start of a filepath basename or null - * \param filename Path of the file, can be an absolute path -*/ -char* gf_file_basename(const char* filename); +/*! @} */ /*! - * \brief get extension from filename - * - * Returns a pointer to the start of a filepath extension or null - * \param filename Path of the file, can be an absolute path -*/ -char* gf_file_ext_start(const char* filename); - - -/*! @} */ +\addtogroup errors_grp +\brief Error Types +This section documents all error codes used in the GPAC framework. Most of the GPAC's functions will use these as + return values, and some of these errors are also used for state communication with the different modules of the framework. -/*! \addtogroup errors_grp Error codes - * \ingroup utils_grp - * \brief Errors used in GPAC. - * - * This section documents all error codes used in the GPAC framework. Most of the GPAC's functions will use these as - * return values, and some of these errors are also used for state communication with the different modules of the framework. - * @{ +@{ */ -/*! - * GPAC Error - * \hideinitializer - * - * positive values are warning and info, 0 means no error and negative values are errors +/*! GPAC Error +\hideinitializer + +Positive values are warning and info, 0 means no error and negative values are errors */ typedef enum { /*!Message from any scripting engine used in the presentation (ECMAScript, MPEG-J, ...) (Info).*/ GF_SCRIPT_INFO = 3, - /*!Indicates an data frame has several AU packed (not MPEG-4 compliant). This is used by decoders to force - multiple decoding of the same data frame (Info).*/ - GF_PACKED_FRAMES = 2, + /*!Indicates a send packet is not dispatched due to pending connections.*/ + GF_PENDING_PACKET = 2, /*!Indicates the end of a stream or of a file (Info).*/ GF_EOS = 1, /*! @@ -235,8 +133,8 @@ typedef enum GF_BUFFER_TOO_SMALL = -9, /*! Bitstream is not compliant to the specfication it refers to*/ GF_NON_COMPLIANT_BITSTREAM = -10, - /*! No decoders could be found to handle the desired media type*/ - GF_CODEC_NOT_FOUND = -11, + /*! No filter could be found to handle the desired media type*/ + GF_FILTER_NOT_FOUND = -11, /*! The URL is not properly formatted or cannot be found*/ GF_URL_ERROR = -12, /*! An service error has occured at the local side*/ @@ -284,40 +182,314 @@ typedef enum GF_AUTHENTICATION_FAILURE = -50, /*! Script not ready for playback */ GF_SCRIPT_NOT_READY = -51, - /*! Bad configuration for the current contex */ + /*! Bad configuration for the current context */ GF_INVALID_CONFIGURATION = -52, /*! The element has not been found */ GF_NOT_FOUND = -53, /*! Unexpected format of data */ GF_PROFILE_NOT_SUPPORTED = -54, - /*! the decoder buffers were filled, it is necessary to recuperate decoded data*/ - GF_CODEC_BUFFER_UNAVAILABLE = -55, + /*! filter PID config requires new instance of filter */ + GF_REQUIRES_NEW_INSTANCE = -56, + /*! filter PID config cannot be supported by this filter, no use trying to find an alternate input filter chain*/ + GF_FILTER_NOT_SUPPORTED = -57 } GF_Err; /*! - * \brief Error Printing - * - * Returns a printable version of a given error - * \param e Error code requested - * \return String representing the error +\brief Error Printing + +Returns a printable version of a given error +\param e Error code requested +\return String representing the error */ const char *gf_error_to_string(GF_Err e); /*! @} */ -/*! \addtogroup log_grp Logging tools - * \ingroup utils_grp - * \brief Logging system of GPAC - * @{ + +/*! +\addtogroup mem_grp +@{ */ /*! - * GPAC Log Levels - * \hideinitializer - * - * These levels describes messages priority used when filtering logs +\brief Memory allocation for a structure +\hideinitializer + +Macro allocating memory and zero-ing it +*/ +#define GF_SAFEALLOC(__ptr, __struct) {\ + __ptr = (__struct *) gf_malloc(sizeof(__struct));\ + if (__ptr) {\ + memset((void *) __ptr, 0, sizeof(__struct));\ + }\ + } + +/*! +\brief Memory allocation for an array of n structs +\hideinitializer + +Macro allocating memory for n structures and zero-ing it +*/ +#define GF_SAFE_ALLOC_N(__ptr, __n, __struct) {\ + __ptr = (__struct *) gf_malloc( __n * sizeof(__struct));\ + if (__ptr) {\ + memset((void *) __ptr, 0, __n * sizeof(__struct));\ + }\ + } + + +/*! +\brief dynamic string concatenation + +Dynamic concatenation of string with optional separator +\param str pointer to destination string pointer +\param to_append string to append +\param sep optional separator string to insert before concatenation. If set and initial string is NULL, will not be appended +\return error code + */ +GF_Err gf_dynstrcat(char **str, const char *to_append, const char *sep); + +/*! @} */ + +/*! +\addtogroup libsys_grp +\brief Library configuration + +These functions are used to initialize, shutdown and configure libgpac. + +The library shall be initialized using \ref gf_sys_init and terminated using gf_sys_close + +The library can usually be configured from command line if your program uses \ref gf_sys_set_args. + +The library can also be configured from your program using \ref gf_opts_set_key and related functions right after initializing the library. + +For more information on configuration options, see \code gpac -hx core \endcode and https://wiki.gpac.io/core_options + +For more information on filters configuration options, see https://wiki.gpac.io/Filters + +@{ + */ + +/*! +Selection flags for memory tracker +\hideinitializer + */ +typedef enum +{ + /*! No memory tracking*/ + GF_MemTrackerNone = 0, + /*! Memory tracking without backtrace*/ + GF_MemTrackerSimple, + /*! Memory tracking with backtrace*/ + GF_MemTrackerBackTrace, +} GF_MemTrackerType; + +/*! +\brief System setup + +Inits the system high-resolution clock if any, CPU usage manager, random number and GPAC global config. It is strongly recommended to call this function before calling any other GPAC functions, since on some systems (like winCE) it may result in a better memory usage estimation. + +The profile allows using a different global config file than the default, and may be a name (without / or \\) or point to an existing config file. +\note This can be called several times but only the first call will result in system setup. +\param mem_tracker_type memory tracking mode +\param profile name of the profile to load, NULL for default. +\return Error code if any + */ +GF_Err gf_sys_init(GF_MemTrackerType mem_tracker_type, const char *profile); +/*! +\brief System closing +Closes the system high-resolution clock and any CPU associated ressources. +\note This can be called several times but the system will be closed when no more users are counted. + */ +void gf_sys_close(); + +/*! +\brief System arguments + +Sets the user app arguments (used by GUI mode) +\param argc Number of arguments +\param argv Array of arguments +\return error code if any, GF_OK otherwise + */ +GF_Err gf_sys_set_args(s32 argc, const char **argv); + +/*! +\brief Get number of args + +Gets the number of argument of the user application if any +\return number of argument of the user application + */ +u32 gf_sys_get_argc(); + +/*! +\brief Get program arguments + +Gets the arguments of the user application if any +\return argument of the user application + */ +const char **gf_sys_get_argv(); + +/*! +\brief Get number of args + +Gets the number of argument of the user application if any +\param arg Index of argument to retrieve +\return number of argument of the user application + */ +const char *gf_sys_get_arg(u32 arg); + + +/*! +\brief Mark arg as used + +Marks the argument at given index as used. By default all args are marked as not used when assigning args +\param arg_idx Index of argument to mark +\param used flag to set +*/ +void gf_sys_mark_arg_used(s32 arg_idx, Bool used); + +/*! +\brief Check if arg is marked as used + +Marks the argument at given index as used +\param arg_idx Index of argument to mark +\return used flag of the arg +*/ +Bool gf_sys_is_arg_used(s32 arg_idx); + +/*! +\brief checks if test mode is enabled + +Checks if test mode is enabled (no date nor GPAC version should be written). +\return GF_TRUE if test mode is enabled, GF_FALSE otherwise. + */ +Bool gf_sys_is_test_mode(); + +/*! +\brief checks if compatibility with old arch is enabled + +Checks if compatibility with old arch is enabled - this function will be removed when master will be moved to filters branch +\return GF_TRUE if old arch compat is enabled, GF_FALSE otherwise. + */ +Bool gf_sys_old_arch_compat(); + +#ifdef GPAC_ENABLE_COVERAGE +/*! +\brief checks if coverage tests are enabled + +Checks if coverage tests are enabled +\return GF_TRUE if coverage is enabled, GF_FALSE otherwise. + */ +Bool gf_sys_is_cov_mode(); +#endif + +/*! +\brief checks if running in quiet mode + +Checks if quiet mode is enabled +\return 2 if quiet mode is enabled, 1 if quiet mode not enabled but progress is disabled, 0 otherwise. + */ +u32 gf_sys_is_quiet(); + +/*! gets GPAC feature list in this GPAC build +\param disabled if GF_TRUE, gets disabled features, otherwise gets enabled features +\return the list of features. +*/ +const char *gf_sys_features(Bool disabled); + +/*! callback function for remotery profiler + \param udta user data passed by \ref gf_sys_profiler_set_callback + \param text string sent by webbrowser client +*/ +typedef void (*gf_rmt_user_callback)(void *udta, const char* text); + +/*! Enables remotery profiler callback. If remotery is enabled, commands sent via webbrowser client will be forwarded to the callback function specified +\param udta user data +\param rmt_usr_cbk callback function +\return GF_OK if success, GF_BAD_PARAM if profiler is not running, GF_NOT_SUPPORTED if profiler not supported +*/ +GF_Err gf_sys_profiler_set_callback(void *udta, gf_rmt_user_callback rmt_usr_cbk); + + +/*! Sends a message to remotery web client +\param msg text message to send. The message format should be json +\return GF_OK if success, GF_BAD_PARAM if profiler is not running, GF_NOT_SUPPORTED if profiler not supported +*/ +GF_Err gf_sys_profiler_send(const char *msg); + +/*! Enables sampling times in RMT + \param enable if GF_TRUE, sampling will be enabled, otherwise disabled*/ +void gf_sys_profiler_enable_sampling(Bool enable); + +/*! Checks if sampling is enabled in RMT. Sampling is by default enabled when enabling remotery + \return GF_TRUE if sampling is enabled, GF_FALSE otherwise*/ +Bool gf_sys_profiler_sampling_enabled(); + +/*! +GPAC Log tools +\hideinitializer + +Describes the color code for console print */ typedef enum +{ + /*!reset color*/ + GF_CONSOLE_RESET=0, + /*!set text to red*/ + GF_CONSOLE_RED, + /*!set text to green*/ + GF_CONSOLE_GREEN, + /*!set text to blue*/ + GF_CONSOLE_BLUE, + /*!set text to yellow*/ + GF_CONSOLE_YELLOW, + /*!set text to cyan*/ + GF_CONSOLE_CYAN, + /*!set text to white*/ + GF_CONSOLE_WHITE, + /*!set text to magenta*/ + GF_CONSOLE_MAGENTA, + /*!reset all console text*/ + GF_CONSOLE_CLEAR, + /*!save console state*/ + GF_CONSOLE_SAVE, + /*!restore console state*/ + GF_CONSOLE_RESTORE, + + /*!set text to bold modifier*/ + GF_CONSOLE_BOLD = 1<<16, + /*!set text to italic*/ + GF_CONSOLE_ITALIC = 1<<17, + /*!set text to underlined*/ + GF_CONSOLE_UNDERLINED = 1<<18, + /*!set text to strikethrough*/ + GF_CONSOLE_STRIKE = 1<<19 +} GF_ConsoleCodes; + +/*! sets console code +\param std the output stream (stderr or stdout) +\param code the console code to set +*/ +void gf_sys_set_console_code(FILE *std, GF_ConsoleCodes code); + + +/*! @} */ + + +/*! +\addtogroup log_grp +\brief Logging System + +@{ +*/ + +/*! +\brief GPAC Log Levels +\hideinitializer + +These levels describes messages priority used when filtering logs +*/ +typedef enum { /*! Disable all Log message*/ GF_LOG_QUIET = 0, @@ -332,28 +504,27 @@ typedef enum } GF_LOG_Level; /*! - * \brief Log exits at first error assignment - * - * When GF_LOG_ERROR happens, program leaves with instruction exit(1); - * \param strict strict behaviour when encoutering a serious error. - * +\brief Log exits at first error assignment + +When GF_LOG_ERROR happens, program leaves with instruction exit(1); +\param strict strict behaviour when encoutering a serious error. +\return old value before the call. */ -void gf_log_set_strict_error(Bool strict); +Bool gf_log_set_strict_error(Bool strict); /*! - * \brief gets string-formated log tools - * - * Returns the string-formatted log tools and levels. Returned string shall be freed by the caller. - * \return string-formatted log tools. - * +\brief gets string-formated log tools + +Gets the string-formatted log tools and levels. Returned string shall be freed by the caller. +\return string-formatted log tools. */ -char *gf_log_get_tools_levels(); +char *gf_log_get_tools_levels(void); /*! - * GPAC Log tools - * \hideinitializer - * - * These flags describes which sub-part of GPAC generates the log and are used when filtering logs +\brief GPAC Log tools +\hideinitializer + +These flags describes which sub-part of GPAC generates the log and are used when filtering logs */ typedef enum { @@ -365,6 +536,8 @@ typedef enum GF_LOG_CONTAINER, /*! Log message from the network/service stack (messages & co)*/ GF_LOG_NETWORK, + /*! Log message from the HTTP stack*/ + GF_LOG_HTTP, /*! Log message from the RTP/RTCP stack (TS info) and packet structure & hinting (debug)*/ GF_LOG_RTP, /*! Log message from authoring subsystem (file manip, import/export)*/ @@ -405,12 +578,16 @@ typedef enum GF_LOG_CONDITION, /*! Log for all HTTP streaming */ GF_LOG_DASH, + /*! Log for all messages coming from filters */ + GF_LOG_FILTER, + /*! Log for filter scheduler only */ + GF_LOG_SCHEDULER, + /*! Log for all ATSC3 message */ + GF_LOG_ATSC, /*! Log for all messages coming from GF_Terminal or script alert()*/ GF_LOG_CONSOLE, /*! Log for all messages coming the application, not used by libgpac or the modules*/ GF_LOG_APP, - /*! Log for all messages coming from the scheduler */ - GF_LOG_SCHEDULER, /*! special value used to set a level for all tools*/ GF_LOG_ALL, @@ -418,35 +595,35 @@ typedef enum } GF_LOG_Tool; /*! - * \brief Log modules assignment - * - * Sets the tools to be checked for log filtering. By default no logging is performed. - * \param tool tool to be logged. - * \param level level of logging for this tool. +\brief Log modules assignment + +Sets the tools to be checked for log filtering. By default no logging is performed. +\param log_tool the tool to be logged +\param log_level the level of logging for this tool * */ -void gf_log_set_tool_level(GF_LOG_Tool tool, GF_LOG_Level level); +void gf_log_set_tool_level(GF_LOG_Tool log_tool, GF_LOG_Level log_level); /*! - * \brief Log Message Callback - * - * The gf_log_cbk type is the type for the callback of the \ref gf_log_set_callback function. By default all logs are redirected to stderr - * \param cbck Opaque user data. - * \param log_level level of the log. This value is not guaranteed in multi-threaded context. - * \param log_tool tool emitting the log. This value is not guaranteed in multi-threaded context. - * \param fmt message log format. - * \param vlist message log param. +\brief Log Message Callback + +The gf_log_cbk type is the type for the callback of the \ref gf_log_set_callback function. By default all logs are redirected to stderr +\param cbck Opaque user data. +\param log_level level of the log. This value is not guaranteed in multi-threaded context. +\param log_tool tool emitting the log. This value is not guaranteed in multi-threaded context. +\param fmt message log format. +\param vlist message log param. * */ typedef void (*gf_log_cbk)(void *cbck, GF_LOG_Level log_level, GF_LOG_Tool log_tool, const char* fmt, va_list vlist); /*! - * \brief Log overwrite - * - * Assigns a user-defined callback for printing log messages. By default all logs are redirected to stderr - * \param usr_cbk Opaque user data - * \param cbk Callback log function - * \return previous callback function +\brief Log overwrite + +Assigns a user-defined callback for printing log messages. By default all logs are redirected to stderr +\param usr_cbk Opaque user data +\param cbk Callback log function +\return previous callback function */ gf_log_cbk gf_log_set_callback(void *usr_cbk, gf_log_cbk cbk); @@ -468,368 +645,347 @@ gf_log_cbk gf_log_set_callback(void *usr_cbk, gf_log_cbk cbk); */ -/*this is all a bit ugly, but most compilers don't properly handle variadic macros...*/ +/*! \cond PRIVATE */ void gf_log(const char *fmt, ...); void gf_log_lt(GF_LOG_Level ll, GF_LOG_Tool lt); void gf_log_va_list(GF_LOG_Level level, GF_LOG_Tool tool, const char *fmt, va_list vl); - +/*! \endcond */ /*! - * \brief Log level checking - * - * Checks if a given tool is logged for the given level - * \param log_tool tool to check - * \param log_level level to check - * \return 1 if logged, 0 otherwise +\brief Log level checking + +Checks if a given tool is logged for the given level +\param log_tool tool to check +\param log_level level to check +\return 1 if logged, 0 otherwise */ Bool gf_log_tool_level_on(GF_LOG_Tool log_tool, GF_LOG_Level log_level); /*! - * \brief Set log tools and levels - * - * Set log tools and levels according to the log_tools_levels string. All previous log settings are discarded. - * \param log_tools_levels string specifying the tools and levels. It is formatted as logToolX\@logLevelX:logToolZ\@logLevelZ:... - * \return GF_OK or GF_BAD_PARAM +\brief Log level getter + +Gets log level of a given tool +\param log_tool tool to check +\return log level of tool */ -GF_Err gf_log_set_tools_levels(const char *log_tools_levels); +u32 gf_log_get_tool_level(GF_LOG_Tool log_tool); /*! - * \brief Modify log tools and levels - * - * Modify log tools and levels according to the log_tools_levels string. Previous log settings are kept. - * \param val string specifying the tools and levels. It is formatted as logToolX\@logLevelX:logToolZ\@logLevelZ:... - * \return GF_OK or GF_BAD_PARAM +\brief Set log tools and levels + +Set log tools and levels according to the log_tools_levels string. +\param log_tools_levels string specifying the tools and levels. It is formatted as logToolX\@logLevelX:logToolZ\@logLevelZ:... +\param reset_all if GF_TRUE, all previous log settings are discarded. +\return GF_OK or GF_BAD_PARAM +*/ +GF_Err gf_log_set_tools_levels(const char *log_tools_levels, Bool reset_all); + +/*! +\brief Modify log tools and levels + +Modify log tools and levels according to the log_tools_levels string. Previous log settings are kept. +\param val string specifying the tools and levels. It is formatted as logToolX\@logLevelX:logToolZ\@logLevelZ:... +\return GF_OK or GF_BAD_PARAM */ GF_Err gf_log_modify_tools_levels(const char *val); /*! - * \brief Set log level for a given tool - * - * Set log level for a given tool. - * \param tool tool to log - * \param level log level for this tool +\brief Checks if color logs is enabled + +Checks if color logs is enabled +\return GF_TRUE if color logs are used */ -void gf_log_set_tool_level(GF_LOG_Tool tool, GF_LOG_Level level); +Bool gf_log_use_color(); + +/*! +\brief Checks if logs are stored to file + +Checks if logs are stored to file +\return GF_TRUE if logs are stored to file +*/ +Bool gf_log_use_file(); #ifdef GPAC_DISABLE_LOG #define GF_LOG(_ll, _lm, __args) #else /*! - * \brief Message logging - * \hideinitializer - * - * Macro for logging messages. Usage is GF_LOG(log_lev, log_module, (fmt, ...)). The log function is only called if log filtering allows it. This avoids fetching logged parameters when the tool is not being logged. +\brief Message logging +\hideinitializer + +Macro for logging messages. Usage is GF_LOG(log_lev, log_module, (fmt, ...)). The log function is only called if log filtering allows it. This avoids fetching logged parameters when the tool is not being logged. */ #define GF_LOG(_log_level, _log_tools, __args) if (gf_log_tool_level_on(_log_tools, _log_level) ) { gf_log_lt(_log_level, _log_tools); gf_log __args ;} #endif +/*! +\brief Resets log file +Resets log file if any log file name was specified, by closing and reopening a new file. +*/ +void gf_log_reset_file(); + + + +/*! @} */ /*! - * \brief PseudoRandom Integer Generation Initialization - * - * Sets the starting point for generating a series of pseudorandom integers. - * \param Reset Re-initializes the random number generator +\addtogroup miscsys_grp +\brief System time CPU + +This section documents time functionalities and CPU management in GPAC. + +@{ + */ + + +/*! +\brief PseudoRandom Integer Generation Initialization + +Sets the starting point for generating a series of pseudorandom integers. +\param Reset Re-initializes the random number generator */ void gf_rand_init(Bool Reset); -/*! - * \brief PseudoRandom Integer Generation - * - * Returns a pseudorandom integer. +/*! PseudoRandom integer generation +\return a pseudorandom integer */ u32 gf_rand(); -/*! - * \brief user name - * - * Gets current user (login) name. +/*! gets user name +\param buf buffer set to current user (login) name if available. */ -void gf_get_user_name(char *buf, u32 buf_size); +void gf_get_user_name(char buf[1024]); +/*! +\brief Progress formatting -/*!\brief FileEnum info object +Signals progress in GPAC's operations. Note that progress signaling with this function is not thread-safe, the main purpose is to use it for authoring tools only. +\param title title string of the progress, or NULL for no progress +\param done Current amount performed of the action. +\param total Total amount of the action. + */ +void gf_set_progress(const char *title, u64 done, u64 total); + +/*! +\brief Progress Callback + +The gf_on_progress_cbk type is the type for the callback of the \ref gf_set_progress_callback function +\param cbck Opaque user data. +\param title preogress title. +\param done Current amount performed of the action +\param total Total amount of the action. * - *The FileEnumInfo object is used to get file attributes upon enumeration of a directory. -*/ -typedef struct -{ - /*!File is marked as hidden*/ - Bool hidden; - /*!File is a directory*/ - Bool directory; - /*!File is a drive mountpoint*/ - Bool drive; - /*!File is a system file*/ - Bool system; - /*!File size in bytes*/ - u64 size; - /*!File last modif time in UTC seconds*/ - u64 last_modified; -} GF_FileEnumInfo; + */ +typedef void (*gf_on_progress_cbk)(const void *cbck, const char *title, u64 done, u64 total); /*! - * \brief Directory Enumeration Callback - * - * The gf_enum_dir_item type is the type for the callback of the \ref gf_enum_directory function - * \param cbck Opaque user data. - * \param item_name File or directory name. - * \param item_path File or directory full path and name from filesystem root. - * \param file_info information for the file or directory. - * \return 1 to abort enumeration, 0 to continue enumeration. - * +\brief Progress overwriting +Overwrites the progress signaling function by a user-defined one. +\param user_cbk Opaque user data +\param prog_cbk new callback function to use. Passing NULL restore default GPAC stderr notification. */ -typedef Bool (*gf_enum_dir_item)(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info); -/*! - * \brief Directory enumeration - * - * Enumerates a directory content. Feedback is provided by the enum_dir_item function - * \param dir Directory to enumerate - * \param enum_directory If set, only directories will be enumerated, otherwise only files are. - * \param enum_dir \ref gf_enum_dir_item callback function for enumeration. - * \param cbck Opaque user data passed to callback function. - * \param filter optional filter for file extensions. If a file extension without the dot '.' character is not found in the - * filter the file will be skipped. -*/ -GF_Err gf_enum_directory(const char *dir, Bool enum_directory, gf_enum_dir_item enum_dir, void *cbck, const char *filter); +void gf_set_progress_callback(void *user_cbk, gf_on_progress_cbk prog_cbk); /*! - * \brief File Deletion - * - * Deletes a file from the disk. - * \param fileName absolute name of the file or name relative to the current working directory. -*/ -GF_Err gf_delete_file(const char *fileName); +\brief Prompt checking -/*! - * \brief File Move - * - * Moves or renames a file or directory. - * \param fileName absolute path of the file / directory to move or rename - * \param newFileName absolute new path/name of the file / directory +Checks if a character is pending in the prompt buffer. +\return 1 if a character is ready to be fetched, 0 otherwise. +\note Function not available under WindowsCE nor SymbianOS */ -GF_Err gf_move_file(const char *fileName, const char *newFileName); - -/*! - * \brief Temporary File Creation - * - * Creates a new temporary file in binary mode - * \param fileName if not NULL, strdup() of the temporary filename when created by GPAC (NULL otherwise as the system automatically removes its own tmp files) - * \return stream handle to the new file ressoucre - */ -FILE *gf_temp_file_new(char ** const fileName); - +Bool gf_prompt_has_input(); /*! - * \brief File Modification Time - * - * Returns the modification time of the given file. The exact meaning of this value is system dependent - * \param filename file to check - * \return modification time of the file - */ -u64 gf_file_modification_time(const char *filename); +\brief Prompt character flush -/*! - * \brief File existence check - * - * Moves or renames a file or directory. - * \param fileName absolute path of the file / directory to move or rename - * \return GF_TRUE if file exists - */ -Bool gf_file_exists(const char *fileName); +Gets the current character entered at prompt if any. +\return value of the character. +\note Function not available under WindowsCE nor SymbianOS +*/ +char gf_prompt_get_char(); /*! - * \brief Progress formatting - * - * Signals progress in GPAC's operations. Note that progress signaling with this function is not thread-safe, the main purpose is to use it for authoring tools only. - * \param title title string of the progress, or NULL for no progress - * \param done Current amount performed of the action. - * \param total Total amount of the action. - */ -void gf_set_progress(const char *title, u64 done, u64 total); +\brief Get prompt terminal size -/*! - * \brief Progress Callback - * - * The gf_on_progress_cbk type is the type for the callback of the \ref gf_set_progress_callback function - * \param cbck Opaque user data. - * \param title preogress title. - * \param done Current amount performed of the action - * \param total Total amount of the action. - * - */ -typedef void (*gf_on_progress_cbk)(const void *cbck, const char *title, u64 done, u64 total); +Gets the stdin prompt size (columns and rows) +\param width set to number of rows in the terminal +\param height set to number of columns in the terminal +\return error if any +*/ +GF_Err gf_prompt_get_size(u32 *width, u32 *height); /*! - * \brief Progress overwriting - * - * Iverwrites the progress signaling function by a user-defined one. - * \param user_cbk Opaque user data - * \param prog_cbk new callback function to use. Passing NULL restore default GPAC stderr notification. - */ -void gf_set_progress_callback(void *user_cbk, gf_on_progress_cbk prog_cbk); +\brief turns prompt echo on/off - -/*! - * \brief Prompt checking - * - * Checks if a character is pending in the prompt buffer. - * \return 1 if a character is ready to be fetched, 0 otherwise. - * \note Function not available under WindowsCE nor SymbianOS +Turns the prompt character echo on/off - this is useful when entering passwords. +\param echo_off indicates whether echo should be turned on or off. +\note Function not available under WindowsCE nor SymbianOS */ -Bool gf_prompt_has_input(); +void gf_prompt_set_echo_off(Bool echo_off); -/*! - * \brief Prompt character flush - * - * Returns the current character entered at prompt if any. - * \return value of the character. - * \note Function not available under WindowsCE nor SymbianOS -*/ -char gf_prompt_get_char(); +/*! @} */ -/*! - * \brief turns prompt echo on/off - * - * Turns the prompt character echo on/off - this is useful when entering passwords. - * \param echo_off indicates whether echo should be turned on or off. - * \note Function not available under WindowsCE nor SymbianOS +/*! gets battery state +\param onBattery set to GF_TRUE if running on battery +\param onCharge set to GF_TRUE if battery is charging +\param level set to battery charge percent +\param batteryLifeTime set to battery lifetime +\param batteryFullLifeTime set to battery full lifetime +\return GF_TRUE if success */ -void gf_prompt_set_echo_off(Bool echo_off); +Bool gf_sys_get_battery_state(Bool *onBattery, u32 *onCharge, u32 *level, u32 *batteryLifeTime, u32 *batteryFullLifeTime); -/*! @} */ /*! - *\addtogroup cpu_grp System time CPU and Memory tools - *\ingroup utils_grp - *\brief System time CPU and Memory functions - * - *This section documents time functionalities and CPU management in GPAC. - * @{ - */ +\brief parses 128 bit from string -/*! - * Selection flags for memory tracker - * \hideinitializer +Parses 128 bit from string +\param string the string containing the value in hexa. Non alphanum characters are skipped +\param value the value parsed +\return error code if any */ -typedef enum -{ - /*! No memory tracking*/ - GF_MemTrackerNone = 0, - /*! Memory tracking without backtrace*/ - GF_MemTrackerSimple, - /*! Memory tracking with backtrace*/ - GF_MemTrackerBackTrace, -} GF_MemTrackerType; +GF_Err gf_bin128_parse(const char *string, bin128 value); /*! - * \brief System setup - * - * Inits the system high-resolution clock if any, and CPU usage manager. It is strongly recommended to call this - * function before calling any other GPAC functions, since on some systems (like winCE) it may result in a better memory usage estimation. - * \note This can be called several times but only the first call will result in system setup. - */ -void gf_sys_init(GF_MemTrackerType mem_tracker_type); -/*! - * \brief System closing - * - * Closes the system high-resolution clock and any CPU associated ressources. - * \note This can be called several times but the system will be closed when no more users are counted. + * Blob structure used to pass data pointer around */ -void gf_sys_close(); - -/*! - * \brief System arguments - * - * Sets the user app arguments (used by GUI mode) - * \param argc Number of arguments - * \param argv Array of arguments +typedef struct +{ + /*! data block of blob */ + u8 *data; + /*! size of blob */ + u32 size; +} GF_Blob; + +/*! + * Retrieves data associated with a blob url +\param blob_url URL of blob object (ie gmem://%p) +\param out_data if success, set to blob data pointer +\param out_size if success, set to blob data size +\return error code */ -void gf_sys_set_args(s32 argc, const char **argv); +GF_Err gf_blob_get_data(const char *blob_url, u8 **out_data, u32 *out_size); /*! - * \brief Get number of args - * - * Gets the number of argument of the user application if any - * \return number of argument of the user application - */ -u32 gf_sys_get_argc(); +\addtogroup time_grp +\brief Time manipulation tools +@{ +*/ /*! - * \brief Get number of args - * - * Gets the number of argument of the user application if any - * \param arg Index of argument to retrieve - * \return number of argument of the user application - */ -const char *gf_sys_get_arg(u32 arg); +\brief System clock query -/*! - * \brief System clock query - * - * Gets the system clock time. - * \return System clock value since GPAC initialization in milliseconds. +Gets the system clock time. +\return System clock value since GPAC initialization in milliseconds. */ u32 gf_sys_clock(); /*! - * \brief High precision system clock query - * - * Gets the hight precision system clock time. - * \return System clock value since GPAC initialization in microseconds. +\brief High precision system clock query + +Gets the hight precision system clock time. +\return System clock value since GPAC initialization in microseconds. */ u64 gf_sys_clock_high_res(); /*! - * \brief Sleeps thread/process - * - * Locks calling thread/process execution for a given time. - * \param ms Amount of time to sleep in milliseconds. +\brief Sleeps thread/process + +Locks calling thread/process execution for a given time. +\param ms Amount of time to sleep in milliseconds. */ void gf_sleep(u32 ms); #ifdef WIN32 /*! - * \brief WINCE time constant - * \hideinitializer - * - * time between jan 1, 1601 and jan 1, 1970 in units of 100 nanoseconds +\brief WINCE time constant +\hideinitializer + +time between jan 1, 1601 and jan 1, 1970 in units of 100 nanoseconds */ #define TIMESPEC_TO_FILETIME_OFFSET (((LONGLONG)27111902 << 32) + (LONGLONG)3577643008) #endif /*! - *\brief gets UTC time in milliseconds - * - * Gets UTC clock in milliseconds - * \return UTC time in milliseconds +\brief gets UTC time in milliseconds + +Gets UTC clock in milliseconds +\return UTC time in milliseconds */ u64 gf_net_get_utc(); /*! - *\brief parses date and returns UTC value for this date. Date format is an XSD dateTime format or any of the supported formats from HTTP 1.1: +\brief converts an ntp timestamp into UTC time in milliseconds + +Converts NTP 64-bit timestamp to UTC clock in milliseconds +\u64 ntp NTP timestamp +\return UTC time in milliseconds + */ +u64 gf_net_ntp_to_utc(u64 ntp); + +/*! +\brief parses date + +Parses date and gets UTC value for this date. Date format is an XSD dateTime format or any of the supported formats from HTTP 1.1: Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() formatgets UTC time in milliseconds - * - * \param date string containing the date to parse - * \return UTC time in milliseconds + +\param date string containing the date to parse +\return UTC time in milliseconds */ u64 gf_net_parse_date(const char *date); /*! - *\brief gets timezone adjustment in seconds - * - * Gets timezone adjustment in seconds, with localtime - timezone = UTC time - * \return timezone shift in seconds +\brief returns 64-bit UTC timestamp from year, month, day, hour, min and sec +\param year the year +\param month the month +\param day the day +\param hour the hour +\param min the min +\param sec the sec +\return UTC time in milliseconds + */ +u64 gf_net_get_utc_ts(u32 year, u32 month, u32 day, u32 hour, u32 min, u32 sec); + +/*! +\brief gets timezone adjustment in seconds + +Gets timezone adjustment in seconds, with localtime - timezone = UTC time +\return timezone shift in seconds */ s32 gf_net_get_timezone(); +/*! +\brief gets time from UTC timestamp + +Gets time from UTC timestamp +\param time timestamp value - see gmtime +\return time description structure - see gmtime + */ +struct tm *gf_gmtime(const time_t *time); + +/*! @} */ + +/*! +\addtogroup thr_grp +\brief Time manipulation tools +@{ +*/ + +/*! +\brief Gets process ID + +Gets ID of the process running this gpac instance. +\return the ID of the main process +*/ +u32 gf_sys_get_process_id(); + /*!\brief run-time system info object - * - *The Run-Time Info object is used to get CPU and memory occupation of the calling process. - *All time values are expressed in milliseconds (accuracy is not guaranteed). + +The Run-Time Info object is used to get CPU and memory occupation of the calling process. +All time values are expressed in milliseconds (accuracy is not guaranteed). */ typedef struct { @@ -868,8 +1024,8 @@ typedef struct } GF_SystemRTInfo; /*! - * Selection flags for run-time info retrieval - * \hideinitializer +Selection flags for run-time info retrieval +\hideinitializer */ enum { @@ -884,190 +1040,930 @@ enum }; /*! - * \brief Gets Run-Time info - * - * Gets CPU and memory usage info for the calling process and the system. Information gathering - * is controled through timeout values. - * \param refresh_time_ms refresh time period in milliseconds. If the last sampling was done less than this period ago, the run-time info is not refreshed. - * \param rti holder to the run-time info structure to update. - * \param flags specify which info is to be retrieved. - * \return 1 if info has been updated, 0 otherwise. - * \note You should not try to use a too small refresh time. Typical values are 500 ms or one second. +\brief Gets Run-Time info + +Gets CPU and memory usage info for the calling process and the system. Information gathering is controled through timeout values. +\param refresh_time_ms refresh time period in milliseconds. If the last sampling was done less than this period ago, the run-time info is not refreshed. +\param rti holder to the run-time info structure to update. +\param flags specify which info is to be retrieved. +\return 1 if info has been updated, 0 otherwise. +\note You should not try to use a too small refresh time. Typical values are 500 ms or one second. */ Bool gf_sys_get_rti(u32 refresh_time_ms, GF_SystemRTInfo *rti, u32 flags); +/*! @} */ -Bool gf_sys_get_battery_state(Bool *onBattery, u32 *onCharge, u32 *level, u32 *batteryLifeTime, u32 *batteryFullLifeTime); +/*! +\addtogroup osfile_grp +\brief File System tools + +This section documents file system tools used in GPAC. + +FILE objects are wrapped in GPAC for direct memory or callback operations. All file functions not using stderr/stdout must use the gf_ prefixed versions, eg: +\code +//bad design, will fail when using wrapped memory IOs +FILE *t = fopen(url, "rb"); +fputs(t, mystring); +fclose(t); -typedef struct _GF_GlobalLock_opaque GF_GlobalLock; +//good design, will work fine when using wrapped memory IOs +FILE *t = gf_fopen(url, "rb"); +gf_fputs(t, mystring); +gf_fclose(t); +\endcode + +@{ + */ /*! - * This function allows the user to create a global lock for all GPAC instances. - * This allow to disable some features for other instances for instance. - * \param resourceName The name of the resource to lock - * \return false if resource has been locked, true if resource could not be locked +\brief reads a file into memory + +Reads a local file into memory, in binary open mode. +\param file_name path on disk of the file to read +\param out_data pointer to allocted address, to be freed by caller +\param out_size pointer to allocted size +\return error code if any */ -GF_GlobalLock * gf_global_resource_lock(const char * resourceName); +GF_Err gf_file_load_data(const char *file_name, u8 **out_data, u32 *out_size); /*! - * Unlock a previouly locked resource - * \param lock The resource to unlock - * \return GF_OK if evertything went fine +\brief reads a file into memory + +Reads a local file into memory, in binary open mode. +\param file stream object to read (no seek is performed) +\param out_data pointer to allocted address, to be freed by caller +\param out_size pointer to allocted size +\return error code if any */ -GF_Err gf_global_resource_unlock(GF_GlobalLock * lock); +GF_Err gf_file_load_data_filep(FILE *file, u8 **out_data, u32 *out_size); -/*! @} */ +/*! +\brief Delete Directory + +Delete a dir within the full path. +\param DirPathName the file path name. +\return error if any + */ +GF_Err gf_rmdir(const char *DirPathName); /*! - *\addtogroup osfile_grp File System - *\ingroup utils_grp - *\brief File System tools - * - *This section documents time functionalities and CPU management in GPAC. - * @{ +\brief Create Directory + +Create a directory within the full path. +\param DirPathName the dir path name. +\return error if any */ +GF_Err gf_mkdir(const char* DirPathName); +/*! +\brief Check Directory Exists +Create a directory within the full path. +\param DirPathName the dir path name. +\return GF_TRUE if directory exists + */ +Bool gf_dir_exists(const char *DirPathName); /*! - *\brief parses 128 bit from string - * - * Parses 128 bit from string - * - * \param string the string containing the value in hexa. Non alphanum characters are skipped - * \param value the value parsed - * \return error code if any +\brief Create Directory + +Cleanup a directory within the full path, removing all the files and the directories. +\param DirPathName the dir path name. +\return error if any + */ +GF_Err gf_cleanup_dir(const char* DirPathName); + + +/** +Gets a newly allocated string containing the default cache directory. +It is the responsibility of the caller to free the string. +\return a fully qualified path to the default cache directory + */ +const char * gf_get_default_cache_directory(); + +/** +Gets the number of open file handles (gf_fopen/gf_fclose only). +\return number of open file handles */ -GF_Err gf_bin128_parse(char *string, bin128 value); +u32 gf_file_handles_count(); + +/*! +\brief file writing helper + +Wrapper to properly handle calls to fwrite(), ensuring proper error handling is invoked when it fails. +\param ptr data buffer to write +\param nb_bytes number of bytes to write +\param stream stream object +\return number of bytes to written +*/ +size_t gf_fwrite(const void *ptr, size_t nb_bytes, FILE *stream); + +/*! +\brief file reading helper + +Wrapper to properly handle calls to fread() +\param ptr data buffer to read +\param nbytes number of bytes to read +\param stream stream object +\return number of bytes read +*/ +size_t gf_fread(void *ptr, size_t nbytes, FILE *stream); + +/*! +\brief file reading helper + +Wrapper to properly handle calls to fgets() +\param buf same as fgets +\param size same as fgets +\param stream same as fgets +\return same as fgets +*/ +char *gf_fgets(char *buf, size_t size, FILE *stream); +/*! +\brief file reading helper + +Wrapper to properly handle calls to fgetc() +\param stream same as fgetc +\return same as fgetc +*/ +int gf_fgetc(FILE *stream); +/*! +\brief file writing helper + +Wrapper to properly handle calls to fputc() +\param val same as fputc +\param stream same as fputc +\return same as fputc +*/ +int gf_fputc(int val, FILE *stream); +/*! +\brief file writing helper + +Wrapper to properly handle calls to fputs() +\param buf same as fputs +\param stream same as fputs +\return same as fputs +*/ +int gf_fputs(const char *buf, FILE *stream); +/*! +\brief file writing helper + +Wrapper to properly handle calls to fprintf() +\param stream same as fprintf +\param format same as fprintf +\return same as fprintf +*/ +int gf_fprintf(FILE *stream, const char *format, ...); +/*! +\brief file flush helper + +Wrapper to properly handle calls to fflush() +\param stream same as fflush +\return same as fflush +*/ +int gf_fflush(FILE *stream); +/*! +\brief end of file helper + +Wrapper to properly handle calls to feof() +\param stream same as feof +\return same as feof +*/ +int gf_feof(FILE *stream); +/*! +\brief file error helper + +Wrapper to properly handle calls to ferror() +\param stream same as ferror +\return same as ferror +*/ +int gf_ferror(FILE *stream); /*! - * \brief Delete Directory +\brief file size helper + +Gets the file size given a FILE object. The FILE object position will be reset to 0 after this call +\param fp FILE object to check +\return file size in bytes +*/ +u64 gf_fsize(FILE *fp); + +/*! +\brief file IO helper + +Checks if the given FILE object is a native FILE or a GF_FileIO wrapper. +\param fp FILE object to check +\return GF_TRUE if the FILE object is a wrapper to GF_FileIO +*/ +Bool gf_fileio_check(FILE *fp); + +/*! +\brief file opening + +Opens a file, potentially bigger than 4GB. if filename identifies a blob (gmem://), the blob will be opened +\param file_name same as fopen +\param mode same as fopen +\return stream handle of the file object +*/ +FILE *gf_fopen(const char *file_name, const char *mode); + +/*! +\brief file opening + +Opens a file, potentially using file IO if the parent URL is a File IO wrapper +\param file_name same as fopen +\param parent_url URL of parent file. If not a file io wrapper (gfio://), the function is equivalent to gf_fopen +\param mode same as fopen +\return stream handle of the file object +\note You only need to call this function if you're suspecting the file to be a large one (usually only media files), otherwise use regular stdio. +\return stream habdle of the file or file IO object*/ +FILE *gf_fopen_ex(const char *file_name, const char *parent_url, const char *mode); + +/*! +\brief file closing + +Closes a file +\note You only need to call this function if you're suspecting the file to be a large one (usually only media files), otherwise use regular stdio. +\param file file to close +\return same as flcose +*/ +s32 gf_fclose(FILE *file); + +/*! +\brief large file position query + +Queries the current read/write position in a large file +\param f Same semantics as gf_ftell +\return position in the file +\note You only need to call this function if you're suspecting the file to be a large one (usually only media files), otherwise use regular stdio. +*/ +u64 gf_ftell(FILE *f); +/*! +\brief large file seeking + +Seeks the current read/write position in a large file +\param f Same semantics as fseek +\param pos Same semantics as fseek +\param whence Same semantics as fseek +\return 0 if success, -1 if error +\note You only need to call this function if you're suspecting the file to be a large one (usually only media files), otherwise use regular stdio. +*/ +s32 gf_fseek(FILE *f, s64 pos, s32 whence); + + +/*! gets basename from filename/path +\param filename Path of the file, can be an absolute path +\return a pointer to the start of a filepath basename or null +*/ +char* gf_file_basename(const char* filename); + +/*! gets extension from filename +\param filename Path of the file, can be an absolute path +\return a pointer to the start of a filepath extension or null +*/ +char* gf_file_ext_start(const char* filename); + + +/*!\brief FileEnum info object + +The FileEnumInfo object is used to get file attributes upon enumeration of a directory. +*/ +typedef struct +{ + /*!File is marked as hidden*/ + Bool hidden; + /*!File is a directory*/ + Bool directory; + /*!File is a drive mountpoint*/ + Bool drive; + /*!File is a system file*/ + Bool system; + /*!File size in bytes*/ + u64 size; + /*!File last modif time in UTC seconds*/ + u64 last_modified; +} GF_FileEnumInfo; + +/*! +\brief Directory Enumeration Callback + +The gf_enum_dir_item type is the type for the callback of the \ref gf_enum_directory function +\param cbck Opaque user data. +\param item_name File or directory name. +\param item_path File or directory full path and name from filesystem root. +\param file_info information for the file or directory. +\return 1 to abort enumeration, 0 to continue enumeration. * - * Delete a dir within the full path. - * \param DirPathName the file path name. */ -GF_Err gf_rmdir(char *DirPathName); +typedef Bool (*gf_enum_dir_item)(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info); +/*! +\brief Directory enumeration + +Enumerates a directory content. Feedback is provided by the enum_dir_item function +\param dir Directory to enumerate +\param enum_directory If set, only directories will be enumerated, otherwise only files are. +\param enum_dir gf_enum_dir_item callback function for enumeration. +\param cbck Opaque user data passed to callback function. +\param filter optional filter for file extensions. If a file extension without the dot '.' character is not found in the + * filter the file will be skipped. +\return error if any + */ +GF_Err gf_enum_directory(const char *dir, Bool enum_directory, gf_enum_dir_item enum_dir, void *cbck, const char *filter); + /*! - * \brief Create Directory - * - * Create a directory within the full path. - * \param DirPathName the dir path name. +\brief File Deletion + +Deletes a file from the disk. +\param fileName absolute name of the file or name relative to the current working directory. +\return error if any +*/ +GF_Err gf_file_delete(const char *fileName); + +/*! +\brief File Move + +Moves or renames a file or directory. +\param fileName absolute path of the file / directory to move or rename +\param newFileName absolute new path/name of the file / directory +\return error if any +*/ +GF_Err gf_file_move(const char *fileName, const char *newFileName); + +/*! +\brief Temporary File Creation + +Creates a new temporary file in binary mode +\param fileName if not NULL, strdup() of the temporary filename when created by GPAC (NULL otherwise as the system automatically removes its own tmp files) +\return stream handle to the new file ressoucre */ -GF_Err gf_mkdir(char* DirPathName); +FILE *gf_file_temp(char ** const fileName); + /*! - * \brief Check Directory Exists - * - * Create a directory within the full path. - * \param DirPathName the dir path name. +\brief File Modification Time + +Gets the modification time of the given file. The exact meaning of this value is system dependent +\param filename file to check +\return modification time of the file */ -Bool gf_dir_exists(char *DirPathName); +u64 gf_file_modification_time(const char *filename); /*! - * \brief Create Directory - * - * Cleanup a directory within the full path, removing all the files and the directories. - * \param DirPathName the dir path name. +\brief File existence check + +Checks if file with given name exists +\param fileName path of the file to check +\return GF_TRUE if file exists */ +Bool gf_file_exists(const char *fileName); + +/*! +\brief File existence check + +Checks if file with given name exists, for regular files or File IO wrapper +\param file_name path of the file to check +\param par_name name of the parent file +\return GF_TRUE if file exists */ +Bool gf_file_exists_ex(const char *file_name, const char *par_name); + +/*! File IO wrapper object*/ +typedef struct __gf_file_io GF_FileIO; + + +/*! open proc for memory file IO +\param fileio_ref reference file_io. A file can be opened multiple times for the same reference, your code must handle this +\param url target file name. +\param mode opening mode of file, same as fopen mode. The following additionnal modes are defined: + - "ref": indicates this FileIO object is used by some part of the code and must not be destroyed upon closing of the file. Associated URL is null + - "unref": indicates this FileIO object is not used by some part of the code and may be destroyed if no more references to this object are set. Associated URL is null + - "url": indicates to create a new FileIO object for the given URL without opening the output file. The resulting FileIO object must be garbage collected by the app in case its is never used by the callers + - "probe": checks if the file exists, but no need to open the file. The function should return NULL in this case. If file does not exist, set out_error to GF_URL_ERROR +\param out_error must be set to error code if any (never NULL) +\return the opened GF_FileIO if success, or NULL otherwise */ -GF_Err gf_cleanup_dir(char* DirPathName); +typedef GF_FileIO *(*gfio_open_proc)(GF_FileIO *fileio_ref, const char *url, const char *mode, GF_Err *out_error); +/*! seek proc for memory file IO +\param fileio target file IO object +\param offset offset in file +\param whence position from offset, same as fseek +\return error if any + */ +typedef GF_Err (*gfio_seek_proc)(GF_FileIO *fileio, u64 offset, s32 whence); -/** - * Gets a newly allocated string containing the default cache directory. - * It is the responsibility of the caller to free the string. - * \return a fully qualified path to the default cache directory +/*! read proc for memory file IO +\param fileio target file IO object +\param buffer buffer to read. +\param bytes number of bytes to read. +\return number of bytes read, 0 if error. */ -char * gf_get_default_cache_directory(); +typedef u32 (*gfio_read_proc)(GF_FileIO *fileio, u8 *buffer, u32 bytes); -/** - * Gets the number of open file handles (gf_fopen/gf_fclose only). - * \return number of open file handles +/*! write proc for memory file IO +\param fileio target file IO object +\param buffer buffer to write. +\param bytes number of bytes to write. If 0, acts as fflush +\return number of bytes write, 0 if error */ -u32 gf_file_handles_count(); +typedef u32 (*gfio_write_proc)(GF_FileIO *fileio, u8 *buffer, u32 bytes); + +/*! positon tell proc for memory file IO +\param fileio target file IO object +\return position in bytes from file start + */ +typedef s64 (*gfio_tell_proc)(GF_FileIO *fileio); +/*! end of file proc for memory file IO +\param fileio target file IO object +\return GF_TRUE if end of file, GF_FALSE otherwise + */ +typedef Bool (*gfio_eof_proc)(GF_FileIO *fileio); +/*! printf proc for memory file IO +\param fileio target file IO object +\param format format string to use +\param args variable argument list for printf, already initialized (va_start called) +\return same as vfprint + */ +typedef int (*gfio_printf_proc)(GF_FileIO *fileio, const char *format, va_list args); + +/*! Creates a new file IO object + +There is no guarantee that the corresponding resource will be opened by the framework, it is therefore the caller responsability to track objects created by +gf_fileio_new or as a response to open with mode "url". + +\param url the original URL this file IO object wraps +\param udta opaque data for caller +\param open open proc for IO, must not be NULL +\param seek seek proc for IO, must not be NULL +\param read read proc for IO - if NULL the file is considered write only +\param write write proc for IO - if NULL the file is considered read only +\param tell tell proc for IO, must not be NULL +\param eof eof proc for IO, must not be NULL +\param printf printf proc for IO, may be NULL +\return the newly created file IO wrapper + */ +GF_FileIO *gf_fileio_new(char *url, void *udta, + gfio_open_proc open, + gfio_seek_proc seek, + gfio_read_proc read, + gfio_write_proc write, + gfio_tell_proc tell, + gfio_eof_proc eof, + gfio_printf_proc printf); + +/*! Deletes a new fileIO object +\param fileio the File IO object to delete +*/ +void gf_fileio_del(GF_FileIO *fileio); + +/*! Gets associated user data of a fileIO object +\param fileio target file IO object +\return the associated user data +*/ +void *gf_fileio_get_udta(GF_FileIO *fileio); + +/*! Gets URL of a fileIO object. + The url uses the protocol scheme "gfio://" +\param fileio target file IO object +\return the file IO url to use +*/ +const char * gf_fileio_url(GF_FileIO *fileio); + +/*! Sets statistics on a fileIO object. +\param fileio target file IO object +\param bytes_done number of bytes fetched for this file +\param file_size total size of this file, 0 if unknown +\param cache_complete if GF_TRUE, means the file is completely available +\param bytes_per_sec reception bytes per second, 0 if unknown +*/ +void gf_fileio_set_stats(GF_FileIO *fileio, u64 bytes_done, u64 file_size, Bool cache_complete, u32 bytes_per_sec); + +/*! Gets statistics on a fileIO object. +\param fileio target file IO object +\param bytes_done number of bytes fetched for this file (may be NULL) +\param file_size total size of this file, 0 if unknown (may be NULL) +\param cache_complete if GF_TRUE, means the file is completely available (may be NULL) +\param bytes_per_sec reception bytes per second, 0 if unknown (may be NULL) +\return GF_TRUE if success, GF_FALSE otherwise +*/ +Bool gf_fileio_get_stats(GF_FileIO *fileio, u64 *bytes_done, u64 *file_size, Bool *cache_complete, u32 *bytes_per_sec); + +/*! Checks if a FileIO object can write +\param fileio target file IO object +\param url the original resource URL to open +\param mode the desired open mode +\param out_err set to error code if any, must not be NULL +\return file IO object for this resource +*/ +GF_FileIO *gf_fileio_open_url(GF_FileIO *fileio, const char *url, const char *mode, GF_Err *out_err); + +/*! Gets GF_FileIO object from its URL + The url uses the protocol scheme "gfio://" +\param url the URL of the File IO object +\return the file IO object +*/ +GF_FileIO *gf_fileio_from_url(const char *url); + +/*! Constructs a new GF_FileIO object from a URL + The url can be absolute or relative to the parent GF_FileIO. This is typcically needed by filters (dash input, dasher, NHML/NHNT writers...) generating or consuming additionnal files associated with the main file IO object but being written or read by other filters. + The function will not open the associated resource, only create the file IO wrapper for later usage + If you need to create a new fileIO to be opened immediately, use \ref gf_fopen_ex. + +\param fileio parent file IO object +\param new_res_url the original URL of the new object to create +\return the url (gfio://) of the created file IO object, NULL otherwise +*/ +const char *gf_fileio_factory(GF_FileIO *fileio, const char *new_res_url); + +/*! Translates a FileIO object URL into the original resource URL +\param url the URL of the File IO object +\return the original resource URL associated with the file IO object +*/ +const char * gf_fileio_translate_url(const char *url); +/*! Gets a FileIO original resource URL +\param fileio target file IO object +\return the original resource URL associated with the file IO object +*/ +const char * gf_fileio_resource_url(GF_FileIO *fileio); + +/*! Checks if a FileIO object can read +\param fileio target file IO object +\return GF_TRUE if read is enabled on this object +*/ +Bool gf_fileio_read_mode(GF_FileIO *fileio); +/*! Checks if a FileIO object can write +\param fileio target file IO object +\return GF_TRUE if write is enabled on this object +*/ +Bool gf_fileio_write_mode(GF_FileIO *fileio); /*! @} */ /*! - *\addtogroup hash_grp RawData Misc - *\ingroup utils_grp - *\brief Data integrity and parsing - * - *This section documents misc data functions such as integrity and parsing such as SHA-1 hashing CRC checksum, 128 bit ID parsing... - * @{ +\addtogroup hash_grp +\brief Data hashing, integrity and generic compression + +This section documents misc data functions such as integrity and parsing such as SHA-1 hashing CRC checksum, 128 bit ID parsing... + +@{ */ /*! - * \brief CRC32 compute - * - * Computes the CRC32 value of a buffer. - * \param data buffer - * \param size buffer size - * \return computed CRC32 +\brief CRC32 compute + +Computes the CRC32 value of a buffer. +\param data buffer +\param size buffer size +\return computed CRC32 + */ +u32 gf_crc_32(const u8 *data, u32 size); + + +/** +Compresses a data buffer in place using zlib/deflate. Buffer may be reallocated in the process. +\param data pointer to the data buffer to be compressed +\param data_len length of the data buffer to be compressed +\param out_size pointer for output buffer size +\return error if any + */ +GF_Err gf_gz_compress_payload(u8 **data, u32 data_len, u32 *out_size); + +/** +Compresses a data buffer in place using zlib/deflate. Buffer may be reallocated in the process. +\param data pointer to the data buffer to be compressed +\param data_len length of the data buffer to be compressed +\param out_size pointer for output buffer size +\param data_offset offset in source buffer - the input payload size is data_len - data_offset +\param skip_if_larger if GF_TRUE, will not override source buffer if compressed version is larger than input data +\param out_comp_data if not NULL, the compressed result is set in this pointer rather than doing inplace compression +\return error if any */ -u32 gf_crc_32(const char *data, u32 size); +GF_Err gf_gz_compress_payload_ex(u8 **data, u32 data_len, u32 *out_size, u8 data_offset, Bool skip_if_larger, u8 **out_comp_data); +/** +Decompresses a data buffer using zlib/deflate. +\param data data buffer to be decompressed +\param data_len length of the data buffer to be decompressed +\param uncompressed_data pointer to the uncompressed data buffer. It is the responsibility of the caller to free this buffer. +\param out_size size of the uncompressed buffer +\return error if any + */ +GF_Err gf_gz_decompress_payload(u8 *data, u32 data_len, u8 **uncompressed_data, u32 *out_size); /** - * Compresses a data buffer in place using zlib. Buffer may be reallocated in the process. - * \param data pointer to the data buffer to be compressed - * \param data_len length of the data buffer to be compressed - * \param out_size pointer for output buffer size - * \return GF_OK if evertything went fine +Compresses a data buffer in place using LZMA. Buffer may be reallocated in the process. +\param data pointer to the data buffer to be compressed +\param data_len length of the data buffer to be compressed +\param out_size pointer for output buffer size +\return error if any */ -GF_Err gf_gz_compress_payload(char **data, u32 data_len, u32 *out_size); +GF_Err gf_lz_compress_payload(u8 **data, u32 data_len, u32 *out_size); /** - * Decompresses a data buffer using zlib. - * \param data data buffer to be decompressed - * \param data_len length of the data buffer to be decompressed - * \param uncompressed_data pointer to the uncompressed data buffer. It is the responsibility of the caller to free this buffer. - * \param out_size size of the uncompressed buffer - * \return GF_OK if evertything went fine +Decompresses a data buffer using LZMA. +\param data data buffer to be decompressed +\param data_len length of the data buffer to be decompressed +\param uncompressed_data pointer to the uncompressed data buffer. It is the responsibility of the caller to free this buffer. +\param out_size size of the uncompressed buffer +\return error if any + */ +GF_Err gf_lz_decompress_payload(u8 *data, u32 data_len, u8 **uncompressed_data, u32 *out_size); + + +#ifndef GPAC_DISABLE_ZLIB +/*! Wrapper around gzseek, same parameters +\param file target gzfile +\param offset offset in file +\param whence same as gzseek +\return same as gzseek +*/ +u64 gf_gzseek(void *file, u64 offset, int whence); +/*! Wrapper around gf_gztell, same parameters + \param file target gzfile + \return postion in file + */ +u64 gf_gztell(void *file); +/*! Wrapper around gzrewind, same parameters + \param file target gzfile + \return same as gzrewind */ -GF_Err gf_gz_decompress_payload(char *data, u32 data_len, char **uncompressed_data, u32 *out_size); +s64 gf_gzrewind(void *file); +/*! Wrapper around gzeof, same parameters + \param file target gzfile + \return same as gzeof + */ +int gf_gzeof(void *file); +/*! Wrapper around gzclose, same parameters +\param file target gzfile +\return same as gzclose +*/ +int gf_gzclose(void *file); +/*! Wrapper around gzerror, same parameters +\param file target gzfile +\param errnum same as gzerror +\return same as gzerror + */ +const char * gf_gzerror (void *file, int *errnum); +/*! Wrapper around gzclearerr, same parameters + \param file target gzfile + */ +void gf_gzclearerr(void *file); +/*! Wrapper around gzopen, same parameters +\param path the file name to open +\param mode the file open mode +\return open file + */ +void *gf_gzopen(const char *path, const char *mode); +/*! Wrapper around gzread, same parameters + \param file target gzfile + \param buf same as gzread + \param len same as gzread + \return same as gzread + */ +int gf_gzread(void *file, void *buf, unsigned len); +/*! Wrapper around gzdirect, same parameters + \param file target gzfile + \return same as gzdirect + */ +int gf_gzdirect(void *file); +/*! Wrapper around gzgetc, same parameters + \param file target gzfile + \return same as gzgetc + */ +int gf_gzgetc(void *file); +/*! Wrapper around gzgets, same parameters + \param file target gzfile + \param buf same as gzread + \param len same as gzread + \return same as gzgets +*/ +char * gf_gzgets(void *file, char *buf, int len); +#endif -/*SHA1*/ +/*! SHA1 context*/ typedef struct __sha1_context GF_SHA1Context; +/*! SHA1 message size */ #define GF_SHA1_DIGEST_SIZE 20 -#define GF_SHA1_DIGEST_SIZE_HEXA 41 -/* Create SHA-1 context */ + +/*! create SHA-1 context +\return the SHA1 context*/ GF_SHA1Context *gf_sha1_starts(); -/* Adds byte to the SHA-1 context */ +/*! adds byte to the SHA-1 context +\param ctx the target SHA1 context +\param input data to hash +\param length size of data in bytes +*/ void gf_sha1_update(GF_SHA1Context *ctx, u8 *input, u32 length); -/* Generates SHA-1 of all bytes ingested */ +/*! generates SHA-1 of all bytes ingested +\param ctx the target SHA1 context +\param digest buffer to store message digest +*/ void gf_sha1_finish(GF_SHA1Context *ctx, u8 digest[GF_SHA1_DIGEST_SIZE] ); -/* - * Output SHA-1(file contents), returns 0 if successful. - */ -int gf_sha1_file(const char *filename, u8 digest[GF_SHA1_DIGEST_SIZE]); +/*! gets SHA1 message digest of a file +\param filename name of file to hash +\param digest buffer to store message digest +\return error if any +*/ +GF_Err gf_sha1_file(const char *filename, u8 digest[GF_SHA1_DIGEST_SIZE]); -/* - * Gets SHA-1 of input buffer +/*! gets SHA-1 of input buffer +\param buf input buffer to hash +\param buflen sizeo of input buffer in bytes +\param digest buffer to store message digest */ void gf_sha1_csum(u8 *buf, u32 buflen, u8 digest[GF_SHA1_DIGEST_SIZE]); -/* - * Gets SHA-1 of input buffer into hexa form +/*! @} */ + + +/*! +\addtogroup libsys_grp +@{ +*/ + +/*! gets a global config key value from its section and name. +\param secName the desired key parent section name +\param keyName the desired key name +\return the desired key value if found, NULL otherwise. +*/ +const char *gf_opts_get_key(const char *secName, const char *keyName); + +/*! sets a global config key value from its section and name. +\param secName the desired key parent section name +\param keyName the desired key name +\param keyValue the desired key value +\note this will also create both section and key if they are not found in the configuration file +\return error if any +*/ +GF_Err gf_opts_set_key(const char *secName, const char *keyName, const char *keyValue); + +/*! removes all entries in the given section of the global config +\param secName the target section +*/ +void gf_opts_del_section(const char *secName); +/*! gets the number of sections in the global config +\return the number of sections +*/ +u32 gf_opts_get_section_count(); +/*! gets a section name based on its index in the global config +\param secIndex 0-based index of the section to query +\return the section name if found, NULL otherwise +*/ +const char *gf_opts_get_section_name(u32 secIndex); + +/*! gets the number of keys in a section of the global config +\param secName the target section +\return the number of keys in the section +*/ +u32 gf_opts_get_key_count(const char *secName); +/*! gets the number of keys in a section of the global config +\param secName the target section +\param keyIndex 0-based index of the key in the section +\return the key name if found, NULL otherwise +*/ +const char *gf_opts_get_key_name(const char *secName, u32 keyIndex); + +/*! gets a global config boolean value from its section and name. +\param secName the desired key parent section name +\param keyName the desired key name +\return the desired key value if found, GF_FALSE otherwise. +*/ +Bool gf_opts_get_bool(const char *secName, const char *keyName); + +/*! gets a global config integer value from its section and name. +\param secName the desired key parent section name +\param keyName the desired key name +\return the desired key value if found, 0 otherwise. +*/ +u32 gf_opts_get_int(const char *secName, const char *keyName); + +/*! gets a global config key value from its section and name. +\param secName the desired key parent section name +\param keyName the desired key name +\return the desired key value if found and if the key is not restricted, NULL otherwise. +*/ +const char *gf_opts_get_key_restricted(const char *secName, const char *keyName); + +/*! + * Do not save modification to global options +\return error code + */ +GF_Err gf_opts_discard_changes(); + +/*! + * Returns file name of global config +\return file name of global config or NULL if libgpac is not initialized + */ +const char *gf_opts_get_filename(); + +/*! + * Gets GPAC shared directory (gui, shaders, etc ..) +\param path_buffer GF_MAX_PATH buffer to store output +\return GF_TRUE if success, GF_FALSE otherwise */ -void gf_sha1_csum_hexa(u8 *buf, u32 buflen, u8 digest[GF_SHA1_DIGEST_SIZE_HEXA]); +Bool gf_opts_default_shared_directory(char *path_buffer); /*! @} */ +//! @cond Doxygen_Suppress + +#ifdef GPAC_DISABLE_3D +#define GPAC_DISABLE_REMOTERY 1 +#endif + +#ifdef GPAC_DISABLE_REMOTERY +#define RMT_ENABLED 0 +#else +#define RMT_USE_OPENGL 1 +#endif + +#include <gpac/Remotery.h> + +#define GF_RMT_AGGREGATE RMTSF_Aggregate +/*! begins remotery CPU sample*/ +#define gf_rmt_begin rmt_BeginCPUSample +/*! begins remotery CPU sample with hash*/ +#define gf_rmt_begin_hash rmt_BeginCPUSampleStore +/*! ends remotery CPU sample*/ +#define gf_rmt_end rmt_EndCPUSample +/*! sets remotery thread name*/ +#define gf_rmt_set_thread_name rmt_SetCurrentThreadName +/*! logs remotery text*/ +#define gf_rmt_log_text rmt_LogText +/*! begins remotery OpenGL sample*/ +#define gf_rmt_begin_gl rmt_BeginOpenGLSample +/*! begins remotery OpenGL sample with hash*/ +#define gf_rmt_begin_gl_hash rmt_BeginOpenGLSampleStore +/*!ends remotery OpenGL sample*/ +#define gf_rmt_end_gl rmt_EndOpenGLSample + +//! @endcond + /* \cond dummy */ -#ifdef GPAC_ANDROID +#ifdef GPAC_CONFIG_ANDROID typedef void (*fm_callback_func)(void *cbk_obj, u32 type, u32 param, int *value); extern void gf_fm_request_set_callback(void *cbk_obj, fm_callback_func cbk_func); void gf_fm_request_call(u32 type, u32 param, int *value); -#endif //GPAC_ANDROID +#endif //GPAC_CONFIG_ANDROID + +/*to call whenever the OpenGL library is opened - this function is needed to bind openGL and remotery, and to load +openGL extensions on windows +not exported, and not included in src/compositor/gl_inc.h since it may be needed even when no OpenGL +calls are made by the caller*/ +void gf_opengl_init(); + +typedef enum +{ + //pbo not enabled + GF_GL_PBO_NONE=0, + //pbo enabled, both push and glTexImage textures are done in gf_gl_txw_upload + GF_GL_PBO_BOTH, + //push only is done in gf_gl_txw_upload + GF_GL_PBO_PUSH, + // glTexImage textures only is done in gf_gl_txw_upload + GF_GL_PBO_TEXIMG, +} GF_GLPBOState; + +typedef struct _gl_texture_wrap +{ + u32 textures[4]; + u32 PBOs[4]; + + u32 nb_textures; + u32 width, height, pix_fmt, stride, uv_stride; + Bool is_yuv; + u32 bit_depth, uv_w, uv_h; + u32 scale_10bit; + + u32 gl_format; + u32 bytes_per_pix; + Bool has_alpha; + Bool internal_textures; + Bool uniform_setup; + u32 memory_format; + struct _gf_filter_frame_interface *frame_ifce; + Bool first_tx_load; + + //PBO state - must be managed by caller, especially if using seperated push and texImg steps through gf_gl_txw_setup calls + GF_GLPBOState pbo_state; + Bool flip; +} GF_GLTextureWrapper; + +Bool gf_gl_txw_insert_fragment_shader(u32 pix_fmt, const char *tx_name, char **f_source); +Bool gf_gl_txw_setup(GF_GLTextureWrapper *tx, u32 pix_fmt, u32 width, u32 height, u32 stride, u32 uv_stride, Bool linear_interp, struct _gf_filter_frame_interface *frame_ifce); +Bool gf_gl_txw_upload(GF_GLTextureWrapper *tx, const u8 *data, struct _gf_filter_frame_interface *frame_ifce); +Bool gf_gl_txw_bind(GF_GLTextureWrapper *tx, const char *tx_name, u32 gl_program, u32 texture_unit); +void gf_gl_txw_reset(GF_GLTextureWrapper *tx); /* \endcond */ +/*! macros to get the size of an array of struct*/ +#define GF_ARRAY_LENGTH(a) (sizeof(a) / sizeof((a)[0])) + #ifdef __cplusplus } #endif diff --git a/include/gpac/unicode.h b/include/gpac/unicode.h deleted file mode 100644 index 2379a58..0000000 --- a/include/gpac/unicode.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / common tools sub-project - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef _GF_UNICODE_H_ -#define _GF_UNICODE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -/*! - * \file <gpac/unicode.h> - * \brief Unicode conversion. - */ - -/*! - *\addtogroup uni_grp Unicode and UTF - *\ingroup utils_grp - *\brief Unicode and UTF string management - @{ -*/ - -#include <gpac/setup.h> - -/*! - * \brief Unicode conversion from UTF-8 to UCS-4 - * \param ucs4_buf The UCS-4 buffer to fill - * \param utf8_len The length of the UTF-8 buffer - * \param utf8_buf The buffer containing the UTF-8 data - * \return the length of the ucs4_buf. Note that the ucs4_buf should be allocated by parent and should be at least utf8_len * 4 - */ -u32 utf8_to_ucs4 (u32 *ucs4_buf, u32 utf8_len, unsigned char *utf8_buf); - -/* @} */ - -#ifdef __cplusplus -} -#endif - -#endif /*_GF_UNICODE_H_*/ - diff --git a/include/gpac/user.h b/include/gpac/user.h index b6a1676..d6c55f3 100644 --- a/include/gpac/user.h +++ b/include/gpac/user.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / Stream Management sub-project @@ -34,97 +34,84 @@ extern "C" { /*! - * \file <gpac/user.h> - * \brief GPAC terminal <-> user API. - */ - +\file <gpac/user.h> +\brief GPAC terminal <-> user API. +*/ + /*! - *\addtogroup termuser_grp Terminal User - *\ingroup playback_grp - *\brief GPAC terminal <-> user API. - * - *This section documents the user-level API of the GPAC media player. - * @{ - */ - +\addtogroup termuser_grp Terminal User +\ingroup playback_grp +\brief GPAC terminal <-> user API. + +This section documents the user-level API of the GPAC media player. + +@{ +*/ + #include <gpac/events.h> #include <gpac/module.h> -/*GPAC client terminal*/ +/*! GPAC client terminal*/ typedef struct _tag_terminal GF_Terminal; +/*! GPAC user structure*/ typedef struct _tag_user GF_User; - +/*! terminal creation flags*/ enum { /*display should be hidden upon initialization*/ GF_TERM_INIT_HIDE = 1, /*no audio renderer will be created*/ GF_TERM_NO_AUDIO = 1<<1, - /*terminal is used without visual threading: - * media codecs are not threaded - * all composition memories are filled before rendering - * rendering is done after media decoding - * the user is responsible for updating the terminal - */ - GF_TERM_NO_DECODER_THREAD = 1<<2, - - /*works with no visual thread for the composition - compositor is driven by the media manager - if GF_TERM_NO_DECODER_THREAD, equivalent to GF_TERM_NO_COMPOSITOR_THREAD - */ - GF_TERM_NO_VISUAL_THREAD = 1<<3, - - /*works with no visual thread for the composition - compositor is driven by gf_term_process*/ - GF_TERM_NO_COMPOSITOR_THREAD = 1<<4, - - /*disables frame-rate regulation (used when dumping content)*/ - GF_TERM_NO_REGULATION = 1<<5, - - /*uses audio hardware clock rather than system clock. The clock is increased at the rate of audio sample consumption. This should only be used for extraction - purposes, as it may result in non-smooth visual playback (time is not continuously increasing)*/ - GF_TERM_USE_AUDIO_HW_CLOCK = 1<<6, - + /*initializes client without a default audio out - used for dump modes where audio playback is not needed*/ + GF_TERM_NO_DEF_AUDIO_OUT = 1<<2, + /*disables video output module - used for bench mode without video*/ + GF_TERM_NO_VIDEO = 1<<3, /*works without window thread*/ - GF_TERM_WINDOW_NO_THREAD = 1<<10, + GF_TERM_WINDOW_NO_THREAD = 1<<4, /*lets the main user handle window events (needed for browser plugins)*/ - GF_TERM_NO_WINDOWPROC_OVERRIDE = 1<<11, + GF_TERM_NO_WINDOWPROC_OVERRIDE = 1<<5, /*works without title bar*/ - GF_TERM_WINDOW_NO_DECORATION = 1<<12, - + GF_TERM_WINDOW_NO_DECORATION = 1<<6, /*framebuffer works in 32 bit alpha mode - experimental, only supported on Win32*/ - GF_TERM_WINDOW_TRANSPARENT = 1<<20, + GF_TERM_WINDOW_TRANSPARENT = 1<<7, /*works in windowless mode - experimental, only supported on Win32*/ - GF_TERM_WINDOWLESS = 1<<21, + GF_TERM_WINDOWLESS = 1<<8 }; /*user object for all callbacks*/ struct _tag_user { - /*user defined callback for all functions - cannot be NULL*/ + /*! user defined callback for all functions - cannot be NULL*/ void *opaque; - /*the event proc. Return value depend on the event type, usually 0 + /*! the event proc. Return value depend on the event type, usually 0 cannot be NULL if os_window_handler is specified and dont_override_window_proc is set may be NULL otherwise*/ Bool (*EventProc)(void *opaque, GF_Event *event); - /*config file of client - cannot be NULL*/ - GF_Config *config; - /*modules manager - cannot be NULL - owned by the user (to allow selection of module directory)*/ - GF_ModuleManager *modules; - /*optional os window handler (HWND on win32/winCE, XWindow for X11) + /*! optional os window handler (HWND on win32/winCE, XWindow for X11) if not set the video outut will create and manage the display window.*/ void *os_window_handler; - /*for now, only used by X11 (indicates display the window is on)*/ + /*! for now, only used by X11 (indicates display the window is on)*/ void *os_display; - /*init flags bypassing GPAC config file */ + /*! init flags bypassing GPAC config file */ u32 init_flags; }; +/*! compositor screen buffer grab mode*/ +typedef enum +{ + GF_SC_GRAB_DEPTH_NONE = 0, + GF_SC_GRAB_DEPTH_ONLY = 1, + GF_SC_GRAB_DEPTH_RGBD = 2, + GF_SC_GRAB_DEPTH_RGBDS = 3 +} GF_CompositorGrabMode; + /*! @} */ #ifdef __cplusplus diff --git a/include/gpac/utf.h b/include/gpac/utf.h index 7bac66c..40f0003 100644 --- a/include/gpac/utf.h +++ b/include/gpac/utf.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -31,66 +31,100 @@ extern "C" { #endif /*! - * \file <gpac/utf.h> - * \brief UTF functions. +\file <gpac/utf.h> +\brief UTF functions. */ /*! - * \addtogroup uni_grp - * \ingroup utfuni_grp - * \brief UTF encoding functions - * - *This section documents the UTF functions of the GPAC framework.\n - *The wide characters in GPAC are unsignad shorts, in other words GPAC only supports UTF8 and UTF16 coding styles. - *\note these functions are just ports of libutf8 library tools into GPAC. - * @{ +\addtogroup utf_grp +\brief UTF and Unicode-related functions + +This section documents the UTF functions of the GPAC framework.\n +The wide characters in GPAC are unsignad shorts, in other words GPAC only supports UTF8 and UTF16 coding styles. + +\note these functions are just ports of libutf8 library tools into GPAC. + +@{ */ #include <gpac/tools.h> /*! - *\brief wide-char to multibyte conversion - * - *Converts a wide-char string to a multibyte string - *\param dst multibyte destination buffer - *\param dst_len multibyte destination buffer size - *\param srcp address of the wide-char string. This will be set to the next char to be converted in the input buffer if not enough space in the destination, or NULL if conversion was completed. - *\return length (in byte) of the multibyte string or -1 if error. +\brief wide-char to multibyte conversion + +Converts a wide-char string to a multibyte string +\param dst multibyte destination buffer +\param dst_len multibyte destination buffer size +\param srcp address of the wide-char string. This will be set to the next char to be converted in the input buffer if not enough space in the destination, or NULL if conversion was completed. +\return length (in byte) of the multibyte string or -1 if error. */ size_t gf_utf8_wcstombs(char* dst, size_t dst_len, const unsigned short** srcp); -/*converts UTF8 string to wide char string - returns (-1) if error. set @srcp to next char to be -converted if not enough space*/ + /*! - *\brief multibyte to wide-char conversion - * - *Converts a multibyte string to a wide-char string - *\param dst wide-char destination buffer - *\param dst_len wide-char destination buffer size - *\param srcp address of the multibyte character buffer. This will be set to the next char to be converted in the input buffer if not enough space in the destination, or NULL if conversion was completed. - *\return length (in unsigned short) of the wide-char string or -1 if error. +\brief multibyte to wide-char conversion + +Converts a multibyte string to a wide-char string +\param dst wide-char destination buffer +\param dst_len wide-char destination buffer size +\param srcp address of the multibyte character buffer. This will be set to the next char to be converted in the input buffer if not enough space in the destination, or NULL if conversion was completed. +\return length (in unsigned short) of the wide-char string or -1 if error. */ size_t gf_utf8_mbstowcs(unsigned short* dst, size_t dst_len, const char** srcp); + /*! - *\brief wide-char string length - * - *Returns the length in character of a wide-char string - *\param s the wide-char string - *\return the wide-char string length +\brief wide-char string length + +Gets the length in character of a wide-char string +\param s the wide-char string +\return the wide-char string length */ size_t gf_utf8_wcslen(const unsigned short *s); /*! - *\brief string bidi reordering - * - *Performs a simple reordering of words in the string based on each word direction, so that glyphs are sorted in display order. - *\param utf_string the wide-char string - *\param len the len of the wide-char string - *\return 1 if the main direction is right-to-left, 0 otherwise +\brief returns a UTF8 string from a string started with BOM + +Returns the length in character of a wide-char string +\param data the string or wide-char string +\param size of the data buffer + size of the data buffer +\param out_ptr set to an allocated buffer if needed for conversion, shall be destroyed by caller +\return the UTF8 string corresponding + */ +char *gf_utf_get_utf8_string_from_bom(u8 *data, u32 size, char **out_ptr); + +/*! +\brief string bidi reordering + +Performs a simple reordering of words in the string based on each word direction, so that glyphs are sorted in display order. +\param utf_string the wide-char string +\param len the len of the wide-char string +\return 1 if the main direction is right-to-left, 0 otherwise */ Bool gf_utf8_reorder_bidi(u16 *utf_string, u32 len); +/*! maximum character size in bytes*/ static const size_t UTF8_MAX_BYTES_PER_CHAR = 4; + +/*! +\brief Unicode conversion from UTF-8 to UCS-4 +\param ucs4_buf The UCS-4 buffer to fill +\param utf8_len The length of the UTF-8 buffer +\param utf8_buf The buffer containing the UTF-8 data +\return the length of the ucs4_buf. Note that the ucs4_buf should be allocated by parent and should be at least utf8_len * 4 + */ +u32 utf8_to_ucs4 (u32 *ucs4_buf, u32 utf8_len, unsigned char *utf8_buf); + + + + +#if defined(WIN32) + +wchar_t* gf_utf8_to_wcs(const char* str); +char* gf_wcs_to_utf8(const wchar_t* str); + +#endif + /*! @} */ #ifdef __cplusplus diff --git a/include/gpac/version.h b/include/gpac/version.h index e1ceaf6..9779903 100644 --- a/include/gpac/version.h +++ b/include/gpac/version.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2012 + * Copyright (c) Telecom ParisTech 2012-2019 * All rights reserved * * This file is part of GPAC @@ -25,35 +25,45 @@ #ifndef _GF_VERSION_H -/*! \file "gpac/version.h" - * \brief GPAC version. - * -*/ - /*! - * \ingroup utils_grp - * \brief GPAC version definition. - * - * @{ - */ +\file "gpac/version.h" +\brief GPAC version. +\addtogroup cst_grp + +@{ +*/ /*! - * \brief GPAC Version - * \hideinitializer - * - * Macro giving GPAC version expressed as a printable string +\brief GPAC Version +\hideinitializer */ /* KEEP SPACE SEPARATORS FOR MAKE / GREP (SEE MAIN MAKEFILE & CONFIGURE & CO) * NO SPACE in GPAC_VERSION / GPAC_FULL_VERSION for proper install * SONAME versions must be digits (not strings) */ -#define GPAC_VERSION "0.7.1" -#define GPAC_VERSION_MAJOR 7 -#define GPAC_VERSION_MINOR 5 +/*! Macro giving GPAC version name expressed as a printable string*/ +#define GPAC_VERSION "1.0.1" +/*! ABI Major number of libgpac */ +#define GPAC_VERSION_MAJOR 10 +/*! ABI Minor number of libgpac */ +#define GPAC_VERSION_MINOR 1 +/*! ABI Micro number of libgpac */ #define GPAC_VERSION_MICRO 0 -#include <gpac/revision.h> -#define GPAC_FULL_VERSION GPAC_VERSION "-rev" GPAC_GIT_REVISION +/*! gets GPAC full version including GIT revision +\return GPAC full version +*/ +const char *gf_gpac_version(); + +/*! gets GPAC copyright +\return GPAC copyright +*/ +const char *gf_gpac_copyright(); + +/*! gets GPAC copyright + citations DOI +\return GPAC copyright +*/ +const char *gf_gpac_copyright_cite(); /*! @} */ diff --git a/include/gpac/webvtt.h b/include/gpac/webvtt.h index 86fafdd..46898c7 100644 --- a/include/gpac/webvtt.h +++ b/include/gpac/webvtt.h @@ -1,8 +1,8 @@ /* * GPAC - Multimedia Framework C SDK * - * Authors: Cyril Concolato - * Copyright (c) Telecom ParisTech 2013- + * Authors: Cyril Concolato, Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2013-2019 * All rights reserved * * This file is part of GPAC / WebVTT header @@ -31,19 +31,21 @@ extern "C" { #endif /*! - * \file <gpac/webvtt.h> - * \brief Helper functions for WebVTT parsing. +\file <gpac/webvtt.h> +\brief Helper functions for WebVTT parsing. */ /*! - * \ingroup media_grp - * \brief Helper functions for WebVTT parsing - * - *This section documents the audio and video parsing functions of the GPAC framework. - * @{ +\addtogroup wvtt_grp WebVTT parsing +\ingroup media_grp +\brief Helper functions for WebVTT parsing + +This section documents the audio and video parsing functions of the GPAC framework. + +@{ */ -/* WebVTT types */ +/*! WebVTT types */ typedef enum { WEBVTT_ID, WEBVTT_SETTINGS, @@ -52,13 +54,27 @@ typedef enum { WEBVTT_PRECUE_TEXT, } GF_WebVTTCuePropertyType; +/*! WebVTT timestamp information*/ typedef struct _webvtt_timestamp { u32 hour, min, sec, ms; } GF_WebVTTTimestamp; -u64 gf_webvtt_timestamp_get(GF_WebVTTTimestamp *ts); -void gf_webvtt_timestamp_set(GF_WebVTTTimestamp *ts, u64 value); -void gf_webvtt_timestamp_dump(GF_WebVTTTimestamp *ts, FILE *dump, Bool dump_hour); - +/*! gets webvtt timestamp +\param wvtt_ts the target WebVTT timestamp +\return timestamp in millisecond*/ +u64 gf_webvtt_timestamp_get(GF_WebVTTTimestamp *wvtt_ts); +/*! sets webvtt timestamp +\param wvtt_ts the target WebVTT timestamp +\param value timestamp in millisecond +*/ +void gf_webvtt_timestamp_set(GF_WebVTTTimestamp *wvtt_ts, u64 value); +/*! dumps webvtt timestamp +\param wvtt_ts the target WebVTT timestamp +\param dump the output file to write to +\param dump_hour if GF_TRUE, dumps hours +*/ +void gf_webvtt_timestamp_dump(GF_WebVTTTimestamp *wvtt_ts, FILE *dump, Bool dump_hour); + +/*! WebVTT cue structure*/ typedef struct _webvtt_cue { GF_WebVTTTimestamp start; @@ -74,22 +90,29 @@ typedef struct _webvtt_cue GF_WebVTTTimestamp orig_start; GF_WebVTTTimestamp orig_end; } GF_WebVTTCue; - +/*! destroys a WebVTT cue +\param cue the target WebVTT cue +*/ void gf_webvtt_cue_del(GF_WebVTTCue * cue); #ifndef GPAC_DISABLE_VTT -GF_Err gf_webvtt_dump_header_boxed(FILE *dump, const char *data, u32 dataLength, u32 *printLength); +/*! dumps webvtt sample boxes +\param dump the output file to write to +\param data the WebVTT ISOBMFF sample payload +\param dataLength the size of the payload in bytes +\param printLength set to the printed size in bytes +\return error if any +*/ +GF_Err gf_webvtt_dump_header_boxed(FILE *dump, const u8 *data, u32 dataLength, u32 *printLength); #endif -#ifdef GPAC_HAS_SPIDERMONKEY -#include <gpac/internal/scenegraph_dev.h> - -GF_Err gf_webvtt_js_addCue(GF_Node *node, const char *id, - const char *start, const char *end, - const char *settings, - const char *payload); -GF_Err gf_webvtt_js_removeCues(GF_Node *node); -#endif +/*! dumps webvtt sample boxes +\param data the WebVTT ISOBMFF sample payload +\param dataLength the size of the payload in bytes +\param start the start time in milliseconds of the WebVTT cue +\return new list of cues, to destroy by caller +*/ +GF_List *gf_webvtt_parse_cues_from_data(const u8 *data, u32 dataLength, u64 start); /*! @} */ diff --git a/include/gpac/xml.h b/include/gpac/xml.h index e3af3d7..912f87a 100644 --- a/include/gpac/xml.h +++ b/include/gpac/xml.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / common tools sub-project @@ -32,262 +32,378 @@ extern "C" { #include <gpac/tools.h> #include <gpac/list.h> +#include <gpac/bitstream.h> #ifndef GPAC_DISABLE_CORE_TOOLS /*! - * \file <gpac/xml.h> - * \brief XML functions. +\file <gpac/xml.h> +\brief XML functions. */ /*! - * \addtogroup xml_grp XML - * \ingroup utils_grp - * \brief XML Parsing functions - * - *This section documents the XML functions of the GPAC framework. - * @{ - */ +\addtogroup xml_grp +\brief XML Parsing functions +This section documents the XML functions (full doc parsing and SAX parsing) of the GPAC framework. + +\defgroup sax_grp SAX Parsing +\ingroup xml_grp +\defgroup dom_grp DOM Parsing +\ingroup xml_grp +\defgroup xmlb_grp XML Binary Formating +\ingroup xml_grp +*/ + +/*! +\addtogroup xml_grp +@{ +*/ +/*! Structure containing a parsed attribute*/ typedef struct { - /*name or namespace:name*/ + /*! name or namespace:name*/ char *name; - /*value*/ + /*! value*/ char *value; } GF_XMLAttribute; -/*XML node types*/ +/*! XML node types*/ enum { + + /*! XML node*/ GF_XML_NODE_TYPE = 0, + /*! text node (including carriage return between XML nodes)*/ GF_XML_TEXT_TYPE, + /*! CDATA node*/ GF_XML_CDATA_TYPE, }; +/*! Structure containing a parsed XML node*/ typedef struct _xml_node { + /*! Type of the node*/ u32 type; - /* + /*! For DOM nodes: name For other (text, css, cdata), element content */ char *name; - /*for DOM nodes only*/ - char *ns; /*namespace*/ + /*! namespace of the node, for XML node type only*/ + char *ns; + /*! list of attributes of the node, for XML node type only*/ GF_List *attributes; + /*! list of children nodes of the node, for XML node type only*/ GF_List *content; } GF_XMLNode; +/*! @} */ -/* - SAX XML Parser +/*! +\addtogroup sax_grp + + ! SAX XML Parser API + GPAC can do progressive loading of XML document using this SAX api. +@{ */ +/*! SAX XML Parser object*/ typedef struct _tag_sax_parser GF_SAXParser; +/*! SAX XML node start callback + \param sax_cbck user data passed durinc creation of SAX parser + \param node_name name of the XML node starting + \param name_space namespace of the XML node starting + \param attributes array of attributes declared for that XML node + \param nb_attributes number of items in array +*/ typedef void (*gf_xml_sax_node_start)(void *sax_cbck, const char *node_name, const char *name_space, const GF_XMLAttribute *attributes, u32 nb_attributes); +/*! SAX XML node end callback + \param sax_cbck user data passed durinc creation of SAX parser + \param node_name name of the XML node starting + \param name_space namespace of the XML node starting +*/ typedef void (*gf_xml_sax_node_end)(void *sax_cbck, const char *node_name, const char *name_space); +/*! SAX text content callback + \param sax_cbck user data passed durinc creation of SAX parser + \param content text content of the node + \param is_cdata if TRUE the content was ancapsulated in CDATA; otherwise this isthe content of a text node +*/ typedef void (*gf_xml_sax_text_content)(void *sax_cbck, const char *content, Bool is_cdata); - -typedef void (*gf_xml_sax_progress)(void *cbck, u64 done, u64 tot); - -/*creates new sax parser - all callbacks are optionals*/ +/*! SAX progress callback + \param cbck user data passed durinc creation of SAX parser + \param done amount of bytes parsed from the file + \param total total number of bytes in the file +*/ +typedef void (*gf_xml_sax_progress)(void *cbck, u64 done, u64 total); + +/*! creates new sax parser - all callbacks are optionals +\param on_node_start callback for XML node start +\param on_node_end callback for XML node end +\param on_text_content callback for text content +\param cbck user data passed to callback functions +\return a SAX parser object +*/ GF_SAXParser *gf_xml_sax_new(gf_xml_sax_node_start on_node_start, gf_xml_sax_node_end on_node_end, gf_xml_sax_text_content on_text_content, void *cbck); -/*destroys sax parser */ +/*! destroys sax parser +\param parser the SAX parser to destroy +*/ void gf_xml_sax_del(GF_SAXParser *parser); -/*inits parser with BOM. BOM must be 4 char string with 0 terminaison. If BOM is NULL, parsing will -assume UTF-8 compatible coding*/ +/*! Inits the parser with string containing BOM, if any. BOM must be 4 char string with 0 terminaison. If BOM is NULL, parsing will assume UTF-8 compatible coding +\param parser the SAX parser to init +\param BOM the 4 character with 0 terminaison BOM or NULL +\return error code if any +*/ GF_Err gf_xml_sax_init(GF_SAXParser *parser, unsigned char *BOM); -/*parses input string data. string data MUST be terminated by the 0 character (eg 2 0s for UTF-16)*/ +/*! Parses input string data. string data MUST be terminated by the 0 character (eg 2 0s for UTF-16) +\param parser the SAX parser to use +\param string_bytes the string to parse +\return error code if any +*/ GF_Err gf_xml_sax_parse(GF_SAXParser *parser, const void *string_bytes); -/*suspends/resume sax parsing. +/*! Suspends or resumes SAX parsing. When resuming on file, the function will run until suspended/end of file/error When resuming on steram, the function will simply return + +\param parser the SAX parser to use +\param do_suspend if GF_TRUE, SAX parsing is suspended, otherwise SAX parsing is resumed +\return error code if any */ GF_Err gf_xml_sax_suspend(GF_SAXParser *parser, Bool do_suspend); -/*parses file (potentially gzipped). OnProgress is optional, used to get progress callback*/ +/*! parses file (potentially gzipped). OnProgress is optional, used to get progress callback +\param parser the SAX parser to use +\param fileName the file to parse +\param OnProgress the progress function to use. The callback for the progress function is the one assigned at SAX parser creation \ref gf_xml_sax_new +\return error code if any +*/ GF_Err gf_xml_sax_parse_file(GF_SAXParser *parser, const char *fileName, gf_xml_sax_progress OnProgress); -/*get current line number*/ +/*! Gets current line number, useful for inspecting errors +\param parser the SAX parser to use +\return current line number of SAX parser +*/ u32 gf_xml_sax_get_line(GF_SAXParser *parser); -/*get file size - may be inaccurate if gzipped (only compressed file size is known)*/ -u32 gf_xml_sax_get_file_size(GF_SAXParser *parser); -/*get current file position*/ -u32 gf_xml_sax_get_file_pos(GF_SAXParser *parser); -/*peeks a node forward in the file. May be used to pick the attribute of the first node found matching a given (attributeName, attributeValue) couple*/ +/*! Peeks a node forward in the file. This may be used to pick the attribute of the first node found matching a given (attributeName, attributeValue) couple +\param parser SAX parser to use +\param att_name attribute name to look for +\param att_value value for this attribute +\param substitute gives the name of an additional XML node type to inspect to match the node. May be NULL. +\param get_attr gives the name of the attribute in the substitute node that matches the condition. If substitue node with name atribute is found, the content of the name attribute is returned. May be NULL. +\param end_pattern gives a string indicating where to stop looking in the document. May be NULL. +\param is_substitute is set to GF_TRUE if the return value corresponds to the content of the name attribute of the substitute element +\return name of the XML node found, or NULL if no match. This string has to be freed by the caller using gf_free + +*/ char *gf_xml_sax_peek_node(GF_SAXParser *parser, char *att_name, char *att_value, char *substitute, char *get_attr, char *end_pattern, Bool *is_substitute); -/*file mode only, returns 1 if file is compressed, 0 otherwise*/ +/*! For file mode only, indicates if a file is compressed or not +\param parser SAX parser to use +\return 1 if file is compressed, 0 otherwise +*/ Bool gf_xml_sax_binary_file(GF_SAXParser *parser); +/*! Returns the last error found during parsing +\param parser SAX parser +\return the last SAX error encountered +*/ const char *gf_xml_sax_get_error(GF_SAXParser *parser); +/*! Returns the name of the root XML element +\param file the XML file to inspect +\param ret_code return error code if any +\return the name of the root XML element. This string has to be freed by the caller using gf_free +*/ char *gf_xml_get_root_type(const char *file, GF_Err *ret_code); +/*! Returns the position in bytes of the start of the current node being parsed. The byte offset points to the first < character in the opening tag. +\param parser SAX parser +\return the 0-based position of the current XML node +*/ u32 gf_xml_sax_get_node_start_pos(GF_SAXParser *parser); +/*! Returns the position in bytes of the end of the current node being parsed. The byte offset points to the last > character in the closing tag. +\param parser SAX parser +\return the 0-based position of the current XML node +*/ u32 gf_xml_sax_get_node_end_pos(GF_SAXParser *parser); +/*! @} */ + +/*! +\addtogroup dom_grp + + ! DOM Full XML document Parsing API + GPAC can do one-pass full document parsing of XML document using this DOM API. +@{ +*/ + +/*! the DOM loader. GPAC can also load complete XML document in memory, using a DOM-like approach. This is a simpler +approach for document parsing not requiring progressive loading*/ typedef struct _tag_dom_parser GF_DOMParser; + +/*! the DOM loader constructor +\return the created DOM loader, NULL if memory error +*/ GF_DOMParser *gf_xml_dom_new(); + +/*! the DOM loader constructor +\param parser the DOM parser to destroy +*/ void gf_xml_dom_del(GF_DOMParser *parser); + +/*! Parses an XML document or fragment contained in a file +\param parser the DOM parser to use +\param file the file to parse +\param OnProgress an optional callback for the parser +\param cbk an optional user data for the progress callback +\return error code if any +*/ GF_Err gf_xml_dom_parse(GF_DOMParser *parser, const char *file, gf_xml_sax_progress OnProgress, void *cbk); -GF_Err gf_xml_dom_parse_string(GF_DOMParser *dom, char *string); +/*! Parses an XML document or fragment contained in memory +\param parser the DOM parser to use +\param string the string to parse +\return error code if any +*/ +GF_Err gf_xml_dom_parse_string(GF_DOMParser *parser, char *string); +/*! Gets the last error that happened during the parsing. The parser aborts at the first error found within a SAX callback +\param parser the DOM parser to use +\return last error code if any +*/ const char *gf_xml_dom_get_error(GF_DOMParser *parser); +/*! Gets the current line of the parser. Used for error logging +\param parser the DOM parser to use +\return last loaded line number +*/ u32 gf_xml_dom_get_line(GF_DOMParser *parser); +/*! Gets the number of root nodes in the document (not XML compliant, but used in DASH for remote periods) +\param parser the DOM parser to use +\return the number of root elements in the document +*/ u32 gf_xml_dom_get_root_nodes_count(GF_DOMParser *parser); +/*! Gets the root node at the given index. +\param parser the DOM parser to use +\param idx index of the root node to get (0 being the first node) +\return the root element at the given index, or NULL if error +*/ GF_XMLNode *gf_xml_dom_get_root_idx(GF_DOMParser *parser, u32 idx); -/* - *\brief Serialize a node - * - * Flush a node in a char - * - *\param node the node to flush - *\param content_only Whether to include or not the parent node - *\return The resulting serialization +/*! Serialize a node to a string +\param node the node to flush +\param content_only Whether to include or not the parent node +\return The resulting serialization. The string has to be freed with gf_free */ char *gf_xml_dom_serialize(GF_XMLNode *node, Bool content_only); -/* - *\brief Create the root element of the DOM - * - * Create the root element -- the only top level element -- of the document. - * - *\param parser the DOM structure - *\return The created node if creation occurs properly, otherwise NULL; - */ -GF_XMLNode *gf_xml_dom_create_root(GF_DOMParser *parser, const char* name); - -/* - *\brief Get the root element of the DOM - * - * Get the root element -- the only top level element -- of the document. - * - *\param parser the DOM structure - *\return The corresponding node if exists, otherwise NULL; +/*! Get the root element -- the only top level element -- of the document. +\param parser the DOM structure +\return The corresponding node if exists, otherwise NULL; */ GF_XMLNode *gf_xml_dom_get_root(GF_DOMParser *parser); -/* - *\brief Return and detach the root element of the DOM - * - * Return and detach the root element of the DOM - * - *\param parser the DOM structure - *\return The corresponding node if exists, otherwise NULL; - */ -GF_XMLNode *gf_xml_dom_detach_root(GF_DOMParser *parser); +/*! +Creates an attribute with the given name and value. +\param name the attribute name +\param value the value +\return The created attribute ; +*/ +GF_XMLAttribute *gf_xml_dom_create_attribute(const char* name, const char* value); -/* - *\brief Sets an attribute value for this element. - * - * Sets an attribute value for this element - * - *\param node the GF_XMLNode node - *\param name the name of the attribute - *\param value the value of the attribute - *\return The created attribute if setting occurs properly, otherwise NULL; +/*! Adds the node to the end of the list of children of this node. +\param node the GF_XMLNode node +\param child the GF_XMLNode child to append +\return Error code if any, otherwise GF_OK */ -GF_XMLAttribute *gf_xml_dom_set_attribute(GF_XMLNode *node, const char* name, const char* value); +GF_Err gf_xml_dom_append_child(GF_XMLNode *node, GF_XMLNode *child); -/* - *\brief Gets the attribute for this element with the given name. - * - * Gets the attribute for this element with the given name. - * - *\param node the GF_XMLNode node - *\param name the attribute name - *\return The corresponding attribute if exists, otherwise NULL; - */ -GF_XMLAttribute *gf_xml_dom_get_attribute(GF_XMLNode *node, const char* name); +/*! +\brief Removes the node to the list of children of this node. -/* - *\brief Adds the node to the end of the list of children of this node. - * - * Adds the node to the end of the list of children of this node. - * - *\param node the GF_XMLNode node - *\param child the GF_XMLNode child to append - *\return GF_OK if append occurs properly, otherwise a GF_Err - */ -GF_Err gf_xml_dom_append_child(GF_XMLNode *node, GF_XMLNode *child); +Removes the node to the list of children of this node. +\warning Doesn't free the memory of the removed children. -/* - *\brief Removes the node to the list of children of this node. - * - * Removes the node to the list of children of this node. - * Doesn't free the memory of the removed children. - * - *\param node the GF_XMLNode node - *\param child the GF_XMLNode child to remove - *\return GF_OK if removal occurs properly, otherwise a GF_Err +\param node the GF_XMLNode node +\param child the GF_XMLNode child to remove +\return Error code if any, otherwise GF_OK */ GF_Err gf_xml_dom_rem_child(GF_XMLNode *node, GF_XMLNode *child); -/* - *\brief Node constructor. - * - * Creates a node with the given name and namespace URI. - * - *\param ns the node namespace - *\param name the name namespace - *\return The created GF_XMLNode if creation occurs properly, otherwise NULL; - */ -GF_XMLNode* gf_xml_dom_node_new(const char* ns, const char* name); +/*! Destroys a node, its attributes and its children -/* - *\brief Node destructor. - * - * Free a node, its attributes and its childs. - * - *\param node the node to free +\param node the node to free */ void gf_xml_dom_node_del(GF_XMLNode *node); -/* - *\brief bitsequence parser. - * - * inspects all child elements of the node and converts <BS> children into bits. BS take the following attributes:. - *bits: value gives the number of bits used to code a value or a length - *value: value is a 32 bit signed value - *dataOffset: value gives an offset into a file - *dataLength: value gives the number of bits bytes to copy in a file - *dataFile: value gives the name of the source file - *textmode: indicates whether the file shall be opened in text or binary mode before reading - *text: or string: value gives a string (length is first coded on number of bits in bits attribute) - *fcc: value gives a four character code, coded on 32 bits - *ID128: value gives a 128 bit vlue in hexadecimal - *data64: value gives data coded as base64 - *data: value gives data coded in hexa - * - * - * - *\param node the root node of the bitstream to create - *\param out_data pointer to output buffer allocated by the function to store the result - *\param out_data_size pointer to output buffer size allocated by the function to store the result - *\return error code or GF_OK - */ -GF_Err gf_xml_parse_bit_sequence(GF_XMLNode *bsroot, char **out_data, u32 *out_data_size); -/* - *\brief get the element and check that the namespace is known ('xmlns'-only supported for now) - *\param n the node to process - *\param expected_node_name optional expected name for node n - *\param expected_ns_prefix optional expected namespace prefix for node n - *\return error code or GF_OK +/*! Gets the element and check that the namespace is known ('xmlns'-only supported for now) +\param n the node to process +\param expected_node_name optional expected name for node n +\param expected_ns_prefix optional expected namespace prefix for node n +\return error code or GF_OK */ GF_Err gf_xml_get_element_check_namespace(const GF_XMLNode *n, const char *expected_node_name, const char *expected_ns_prefix); +/*! Writes a string to an xml file and replaces forbidden chars with xml entities + *\param file the xml output file + *\param before optional string prefix (assumed xml-valid, pass NULL if not needed) + *\param str the string to dump and escape + *\param after optional string suffix (assumed xml-valid, pass NULL if not needed) + */ +void gf_xml_dump_string(FILE* file, const char* before, const char* str, const char* after); + +/*! @} */ + + +/*! +\addtogroup xmlb_grp + +Binarization using XML in GPAC + +GPAC uses a special node name BS (for BitSequence) in XML documents to transform text data into sequences of bits. + This function inspects all child elements of the node and converts children node names BS into bits. BS take the following attributes: + + bits: value gives the number of bits used to code a value or a length + + value: value is a 32 bit signed value + + dataOffset: value gives an offset into a file + + dataLength: value gives the number of bits bytes to copy in a file + + dataFile: value gives the name of the source file + + textmode: indicates whether the file shall be opened in text or binary mode before reading + + text: or string: value gives a string (length is first coded on number of bits in bits attribute) + + fcc: value gives a four character code, coded on 32 bits + + ID128: value gives a 128 bit vlue in hexadecimal + + data64: value gives data coded as base64 + + data: value gives data coded in hexa + +@{ +*/ + +/*!\brief bit-sequence XML parser +\param bsroot the root node of XML document describing the bitstream to create +\param parent_url URL of the parent document +\param out_data pointer to output buffer allocated by the function to store the result +\param out_data_size pointer to output buffer size allocated by the function to store the result +\return error code if any or GF_OK + */ +GF_Err gf_xml_parse_bit_sequence(GF_XMLNode *bsroot, const char *parent_url, u8 **out_data, u32 *out_data_size); + +/*!\brief bit-sequence XML parser + +Parses XML bit sequence in an existing bitstream object. The syntax for the XML is the same as in \ref gf_xml_parse_bit_sequence +\param bsroot the root node of XML document describing the bitstream to create +\param parent_url URL of the parent document +\param bs target bitstream to write the result into. The bitstream must be a dynamic write bitstream object +\return error code or GF_OK + */ +GF_Err gf_xml_parse_bit_sequence_bs(GF_XMLNode *bsroot, const char *parent_url, GF_BitStream *bs); + + /*! @} */ #ifdef __cplusplus diff --git a/mkdmg.sh b/mkdmg.sh index 432a512..0332efa 100755 --- a/mkdmg.sh +++ b/mkdmg.sh @@ -31,26 +31,26 @@ if [ -d tmpdmg ] then rm -fr tmpdmg fi -mkdir -p tmpdmg/Osmo4.app -rsync -r --exclude=.git $source_path/build/osxdmg/Osmo4.app/ ./tmpdmg/Osmo4.app/ +mkdir -p tmpdmg/GPAC.app +rsync -r --exclude=.git $source_path/packagers/osx/GPAC.app/ ./tmpdmg/GPAC.app/ ln -s /Applications ./tmpdmg/Applications cp $source_path/README.md ./tmpdmg cp $source_path/COPYING ./tmpdmg -mkdir -p tmpdmg/Osmo4.app/Contents/MacOS/modules -mkdir -p tmpdmg/Osmo4.app/Contents/MacOS/lib +mkdir -p tmpdmg/GPAC.app/Contents/MacOS/modules +mkdir -p tmpdmg/GPAC.app/Contents/MacOS/lib -cp bin/gcc/gm* tmpdmg/Osmo4.app/Contents/MacOS/modules -cp bin/gcc/libgpac.dylib tmpdmg/Osmo4.app/Contents/MacOS/lib -cp bin/gcc/MP4Client tmpdmg/Osmo4.app/Contents/MacOS/Osmo4 -cp bin/gcc/MP4Box tmpdmg/Osmo4.app/Contents/MacOS/MP4Box -cp bin/gcc/MP42TS tmpdmg/Osmo4.app/Contents/MacOS/MP42TS -if [ -f bin/gcc/DashCast ] -then -cp bin/gcc/DashCast tmpdmg/Osmo4.app/Contents/MacOS/DashCast +cp bin/gcc/gm* tmpdmg/GPAC.app/Contents/MacOS/modules +cp bin/gcc/gf_* tmpdmg/GPAC.app/Contents/MacOS/modules +cp bin/gcc/libgpac.dylib tmpdmg/GPAC.app/Contents/MacOS/lib +if [ -f bin/gcc/libopenhevc.1.dylib ]; then + cp bin/gcc/libopenhevc.1.dylib tmpdmg/GPAC.app/Contents/MacOS/lib fi +cp bin/gcc/MP4Client tmpdmg/GPAC.app/Contents/MacOS/Osmo4 +cp bin/gcc/MP4Box tmpdmg/GPAC.app/Contents/MacOS/MP4Box +cp bin/gcc/gpac tmpdmg/GPAC.app/Contents/MacOS/gpac -cd tmpdmg/Osmo4.app/Contents/MacOS/ +cd tmpdmg/GPAC.app/Contents/MacOS/ #check all external deps, and copy them echo rewriting DYLIB dependencies @@ -59,29 +59,23 @@ do rewrite_deps $dylib done -if [ -f DashCast ] -then - rewrite_deps DashCast -fi - echo rewriting APPS dependencies install_name_tool -change /usr/local/lib/libgpac.dylib @executable_path/lib/libgpac.dylib Osmo4 install_name_tool -change /usr/local/lib/libgpac.dylib @executable_path/lib/libgpac.dylib MP4Box -install_name_tool -change /usr/local/lib/libgpac.dylib @executable_path/lib/libgpac.dylib MP42TS +install_name_tool -change /usr/local/lib/libgpac.dylib @executable_path/lib/libgpac.dylib gpac install_name_tool -change ../bin/gcc/libgpac.dylib @executable_path/lib/libgpac.dylib Osmo4 install_name_tool -change ../bin/gcc/libgpac.dylib @executable_path/lib/libgpac.dylib MP4Box -install_name_tool -change ../bin/gcc/libgpac.dylib @executable_path/lib/libgpac.dylib MP42TS - -if [ -f DashCast ] -then -install_name_tool -change /usr/local/lib/libgpac.dylib @executable_path/lib/libgpac.dylib DashCast -install_name_tool -change ../bin/gcc/libgpac.dylib @executable_path/lib/libgpac.dylib DashCast -fi +install_name_tool -change ../bin/gcc/libgpac.dylib @executable_path/lib/libgpac.dylib gpac cd ../../../.. echo Copying GUI -rsync -r --exclude=.git $source_path/gui ./tmpdmg/Osmo4.app/Contents/MacOS/ +rsync -r --exclude=.git $source_path/share/res ./tmpdmg/GPAC.app/Contents/MacOS/share/ +rsync -r --exclude=.git $source_path/share/gui ./tmpdmg/GPAC.app/Contents/MacOS/share/ +rsync -r --exclude=.git $source_path/share/vis ./tmpdmg/GPAC.app/Contents/MacOS/share/ +rsync -r --exclude=.git $source_path/share/shaders ./tmpdmg/GPAC.app/Contents/MacOS/share/ +rsync -r --exclude=.git $source_path/share/scripts ./tmpdmg/GPAC.app/Contents/MacOS/share/ +cp $source_path/share/default.cfg ./tmpdmg/GPAC.app/Contents/MacOS/share/ echo Building DMG version=`grep '#define GPAC_VERSION ' $source_path/include/gpac/version.h | cut -d '"' -f 2` @@ -103,28 +97,31 @@ else $rev = $(date +%Y%m%d) fi -sed 's/<string>.*<\/string><!-- VERSION_REV_REPLACE -->/<string>'"$version"'<\/string>/' tmpdmg/Osmo4.app/Contents/Info.plist > tmpdmg/Osmo4.app/Contents/Info.plist.new && sed 's/<string>.*<\/string><!-- BUILD_REV_REPLACE -->/<string>'"$rev"'<\/string>/' tmpdmg/Osmo4.app/Contents/Info.plist.new > tmpdmg/Osmo4.app/Contents/Info.plist && rm tmpdmg/Osmo4.app/Contents/Info.plist.new +sed 's/<string>.*<\/string><!-- VERSION_REV_REPLACE -->/<string>'"$version"'<\/string>/' tmpdmg/GPAC.app/Contents/Info.plist > tmpdmg/GPAC.app/Contents/Info.plist.new && sed 's/<string>.*<\/string><!-- BUILD_REV_REPLACE -->/<string>'"$rev"'<\/string>/' tmpdmg/GPAC.app/Contents/Info.plist.new > tmpdmg/GPAC.app/Contents/Info.plist && rm tmpdmg/GPAC.app/Contents/Info.plist.new + +#GPAC.app now ready, build pkg +echo "Building PKG" +find ./tmpdmg/ -name '*.DS_Store' -type f -delete +chmod -R 755 ./tmpdmg/ +xattr -rc ./tmpdmg/ -#create dmg -hdiutil create ./gpac.dmg -volname "GPAC for OSX" -srcfolder tmpdmg -ov -rm -rf ./tmpdmg +pkgbuild --install-location /Applications --component ./tmpdmg/GPAC.app --scripts ./packagers/osx/scripts --ownership preserve ./tmppkg.pkg +cat ./packagers/osx/res/preamble.txt > ./packagers/osx/res/full_license.txt +cat ./COPYING >> ./packagers/osx/res/full_license.txt -#add SLA -echo "Adding licence" -hdiutil convert -format UDCO -o gpac_sla.dmg gpac.dmg -rm gpac.dmg -hdiutil unflatten gpac_sla.dmg -Rez /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/Headers/*.r $source_path/build/osxdmg/SLA.r -a -o gpac_sla.dmg -hdiutil flatten gpac_sla.dmg -hdiutil internet-enable -yes gpac_sla.dmg +productbuild --distribution packagers/osx/distribution.xml --resources ./packagers/osx/res --package-path ./tmppkg.pkg gpac.pkg -pck_name="gpac-$full_version.dmg" +rm ./packagers/osx/res/full_license.txt + +pck_name="gpac-$full_version.pkg" if [ "$1" = "snow-leopard" ]; then -pck_name="gpac-$full_version-$1.dmg" +pck_name="gpac-$full_version-$1.pkg" fi +mv gpac.pkg $pck_name +chmod 755 $pck_name echo "$pck_name ready" -chmod o+rx gpac_sla.dmg -chmod g+rx gpac_sla.dmg -mv gpac_sla.dmg $pck_name +rm -rf tmpdmg +rm -rf tmppkg.pkg + diff --git a/modules/Makefile b/modules/Makefile index 0576ce2..5ea6a4f 100644 --- a/modules/Makefile +++ b/modules/Makefile @@ -1,77 +1,46 @@ include ../config.mak #all OS and lib independent -PLUGDIRS=aac_in ac3_in amr_dec audio_filter bifs_dec dummy_in soft_raster mp3_in isom_in odf_dec rtp_in timedtext img_in saf_in ismacryp raw_out validator +PLUGDIRS=validator -ifeq ($(DISABLE_DASH_CLIENT), no) -PLUGDIRS+=mpd_in -ifeq ($(CONFIG_JS),no) -else -PLUGDIRS+=mse_in -endif -endif +#disabled for now +#netctrl -ifeq ($(DISABLE_MEDIA_IMPORT), no) -PLUGDIRS+=mpegts_in -endif - -ifeq ($(DISABLE_SMGR), no) -PLUGDIRS+=ctx_load svg_in -endif - -ifeq ($(DISABLE_SVG), no) -PLUGDIRS+=laser_dec svg_in -ifeq ($(DISABLE_TTXT), no) -PLUGDIRS+=vtt_in -endif - -ifneq ($(CONFIG_ZLIB), no) -PLUGDIRS+=widgetman +ifneq ($(CONFIG_ZLIB),no) +#disabled for now +#PLUGDIRS+=widgetman ifeq ($(DISABLE_VRML),no) ifeq ($(DISABLE_LOADER_BT),no) -PLUGDIRS+=osd -endif -endif +#disabled for now +#PLUGDIRS+=osd endif endif -ifeq ($(CONFIG_FT), no) -else -PLUGDIRS+=ft_font endif -ifeq ($(CONFIG_XVID), no) -else -PLUGDIRS+=xvid_dec -endif -ifeq ($(CONFIG_OGG), no) +ifeq ($(CONFIG_FT),no) else -PLUGDIRS+=ogg -endif - -ifeq ($(CONFIG_FREENECT), no) -else -PLUGDIRS+=freenect +PLUGDIRS+=ft_font endif -ifeq ($(CONFIG_OSS_AUDIO), no) +ifeq ($(CONFIG_FREENECT),no) else -PLUGDIRS+=oss_audio +#PLUGDIRS+=freenect endif -ifeq ($(CONFIG_ALSA), yes) +ifeq ($(CONFIG_ALSA),yes) PLUGDIRS+=alsa endif -ifeq ($(CONFIG_JACK), yes) +ifeq ($(CONFIG_JACK),yes) PLUGDIRS+=jack endif -ifeq ($(CONFIG_SDL), yes) +ifeq ($(CONFIG_SDL),yes) PLUGDIRS+=sdl_out endif -ifeq ($(CONFIG_PULSEAUDIO), yes) +ifeq ($(CONFIG_PULSEAUDIO),yes) PLUGDIRS+=pulseaudio endif @@ -83,13 +52,12 @@ ifeq ($(CONFIG_DIRECTFB),yes) PLUGDIRS+=directfb_out endif -ifeq ($(CONFIG_JS),no) -else -PLUGDIRS+=gpac_js +ifeq ($(CONFIG_HID),yes) +#PLUGDIRS+=psvr endif -ifeq ($(CONFIG_OPENSVC),yes) -PLUGDIRS+=opensvc_dec +ifeq ($(CONFIG_OPENHEVC),yes) +PLUGDIRS+=dec_openhevc endif #w32 plugins @@ -100,75 +68,37 @@ PLUGDIRS+=dx_hw endif endif -#OSX plugins -ifeq ($(CONFIG_DARWIN),yes) -PLUGDIRS+=vtb_decode +#other linux +ifeq ($(CONFIG_LINUX),yes) endif -ifneq ($(CONFIG_FFMPEG), no) -ifeq ($(CONFIG_OPENHEVC),no) -PLUGDIRS+=ffmpeg_in -else -ifneq ($(STATIC_MODULES), yes) -PLUGDIRS+=ffmpeg_in -endif -endif -endif - -#openHEVC is disabled in static build #if static modules are used, disable all previous modules (built-in in libgpac) -ifeq ($(STATIC_MODULES), yes) +ifeq ($(STATIC_MODULES),yes) PLUGDIRS= - -else - -ifeq ($(CONFIG_OPENHEVC),yes) -PLUGDIRS+=openhevc_dec -endif - endif - #all modules below are not (yet) included in static build -ifeq ($(CONFIG_AMR_NB_FT), yes) -PLUGDIRS+=amr_float_dec -else -ifeq ($(CONFIG_AMR_WB_FT), yes) -PLUGDIRS+=amr_float_dec -endif -endif - -ifeq ($(RENOIR_ENABLE),yes) -PLUGDIRS+=viren_out -endif - - -ifeq ($(CONFIG_PLATINUM),yes) -PLUGDIRS+=platinum -endif - -ifeq ($(CONFIG_AVCAP),yes) -PLUGDIRS+=avcap -endif - +#ifeq ($(CONFIG_PLATINUM),yes) +#PLUGDIRS+=platinum +#endif -ifeq ($(DISABLE_PLAYER), yes) +ifeq ($(DISABLE_PLAYER),yes) PLUGDIRS= endif all: plugs -plugs: - set -e; for i in $(PLUGDIRS) ; do $(MAKE) -C $$i all; done +plugs: + set -e; for i in $(PLUGDIRS) ; do $(MAKE) -C $$i all; done dep: - set -e; for i in $(PLUGDIRS) ; do $(MAKE) -C $$i dep; done + set -e; for i in $(PLUGDIRS) ; do $(MAKE) -C $$i dep; done clean: - set -e; for i in $(PLUGDIRS) ; do $(MAKE) -C $$i clean; done + set -e; for i in $(PLUGDIRS) ; do $(MAKE) -C $$i clean; done -distclean: - set -e; for i in $(PLUGDIRS) ; do $(MAKE) -C $$i distclean; done +distclean: + set -e; for i in $(PLUGDIRS) ; do $(MAKE) -C $$i distclean; done diff --git a/modules/aac_in/Makefile b/modules/aac_in/Makefile deleted file mode 100644 index de1b341..0000000 --- a/modules/aac_in/Makefile +++ /dev/null @@ -1,62 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/aac_in - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS= aac_in.o - -SRCS := $(OBJS:.o=.c) - -#faad config -ifeq ($(CONFIG_FAAD), no) -else -OBJS+=faad_dec.o -CFLAGS+=-DGPAC_HAS_FAAD -#local faad lib -ifeq ($(CONFIG_FAAD), local) -EXTRALIBS+=-L../../extra_lib/lib/gcc -CFLAGS+= -I"$(LOCAL_INC_PATH)/faad" -endif -EXTRALIBS+= -lfaad -endif - -LIB=gm_aac_in$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols aac_in.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) -L../../bin/gcc -lgpac $(EXTRALIBS) $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_aac_in-static$(DYN_LIB_SUFFIX) $(OBJS) -L../../bin/gcc -lgpac_static $(EXTRALIBS) $(LDFLAGS) -endif - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/aac_in/aac_in.c b/modules/aac_in/aac_in.c deleted file mode 100644 index 0026be5..0000000 --- a/modules/aac_in/aac_in.c +++ /dev/null @@ -1,999 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / AAC reader module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ -#ifndef DONT_USE_TERMINAL_MODULE_API -#include <gpac/modules/service.h> -#endif - -#include <gpac/modules/codec.h> -#include <gpac/avparse.h> -#include <gpac/constants.h> -#include <gpac/download.h> - -#include <time.h> - -#ifndef GPAC_DISABLE_AV_PARSERS - -typedef struct -{ -#ifndef DONT_USE_TERMINAL_MODULE_API - GF_ClientService *service; - GF_InputService *input; - LPNETCHANNEL ch; -#endif - - Bool is_remote; - - FILE *stream; - u32 duration; - - Bool needs_connection; - u32 pad_bytes; - Bool done; - u32 is_inline; - - char *data; - u32 data_size; - GF_SLHeader sl_hdr; - - u32 sample_rate, oti, sr_idx, nb_ch, prof; - Double start_range, end_range; - u32 current_time, nb_samp; - /*file downloader*/ - GF_DownloadSession *dnload; -#ifdef DONT_USE_TERMINAL_MODULE_API - GF_DownloadManager *dm; -#endif - - Bool is_live; - char prev_data[1000]; - u32 prev_size; - - char *icy_name; - char *icy_genre; - char *icy_track_name; - - Bool hybrid_on; - u64 prev_map_time; -} AACReader; - -typedef struct -{ - Bool is_mp2, no_crc; - u32 profile, sr_idx, nb_ch, frame_size, hdr_size; -} ADTSHeader; - -#ifndef DONT_USE_TERMINAL_MODULE_API - -static const char * AAC_MIMES[] = { "audio/x-m4a", "audio/aac", "audio/aacp", "audio/x-aac", 0 }; - -static const char * AAC_EXTENSIONS = "aac mp4a"; - -static const char * AAC_DESC = "MPEG-4 AAC Music"; - -static u32 AAC_RegisterMimeTypes(const GF_InputService *plug) { - int i; - if (!plug) - return 0; - for (i = 0 ; AAC_MIMES[i] ; i++) - gf_service_register_mime( plug, AAC_MIMES[i], AAC_EXTENSIONS, AAC_DESC); - return i; -} - - -static Bool AAC_CanHandleURL(GF_InputService *plug, const char *url) -{ - char *sExt; - if (!plug || !url) - return GF_FALSE; - sExt = strrchr(url, '.'); - if (!strnicmp(url, "rtsp://", 7)) return GF_FALSE; - { - int i; - for (i = 0 ; AAC_MIMES[i] ; i++) - if (gf_service_check_mime_register(plug, AAC_MIMES[i], AAC_EXTENSIONS, AAC_DESC, sExt)) - return GF_TRUE; - } - return GF_FALSE; -} -#endif - -static GF_ESD *AAC_GetESD(AACReader *read) -{ - GF_BitStream *dsi; - GF_ESD *esd; - u32 i, sbr_sr_idx, sbr_oti; - - esd = gf_odf_desc_esd_new(0); - if (!esd) - return NULL; - esd->decoderConfig->streamType = GF_STREAM_AUDIO; - esd->decoderConfig->objectTypeIndication = read->oti; - esd->ESID = 1; - esd->OCRESID = 1; - esd->slConfig->timestampResolution = read->sample_rate; - if (read->is_live) esd->slConfig->useAccessUnitEndFlag = esd->slConfig->useAccessUnitStartFlag = 1; - dsi = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - - /*write as regular AAC*/ - gf_bs_write_int(dsi, read->prof, 5); - gf_bs_write_int(dsi, read->sr_idx, 4); - gf_bs_write_int(dsi, read->nb_ch, 4); - gf_bs_align(dsi); - - sbr_sr_idx = 0; - sbr_oti = 0; - /*always signal implicit SBR for <=24000, in case it's used*/ - if (read->sample_rate<=24000) { - sbr_sr_idx = read->sr_idx; - for (i=0; i<16; i++) { - if (GF_M4ASampleRates[i] == (u32) 2*read->sample_rate) { - sbr_sr_idx = i; - break; - } - } - } - gf_bs_write_int(dsi, 0x2b7, 11); - gf_bs_write_int(dsi, sbr_oti, 5); - gf_bs_write_int(dsi, 1, 1); - gf_bs_write_int(dsi, sbr_sr_idx, 4); - - /* always signal implicit PS in case it's used ??? */ - gf_bs_write_int(dsi, 0x548, 11); - gf_bs_write_int(dsi, 1, 1); - - gf_bs_align(dsi); - gf_bs_get_content(dsi, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength); - gf_bs_del(dsi); - return esd; -} - -#ifndef DONT_USE_TERMINAL_MODULE_API -static void AAC_SetupObject(AACReader *read) -{ - GF_ESD *esd; - GF_ObjectDescriptor *od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - od->objectDescriptorID = 0; - od->service_ifce = read->input; - esd = AAC_GetESD(read); - esd->OCRESID = 0; - gf_list_add(od->ESDescriptors, esd); - - gf_service_declare_media(read->service, (GF_Descriptor*)od, GF_FALSE); -} -#endif - -static Bool ADTS_SyncFrame(GF_BitStream *bs, Bool is_complete, ADTSHeader *hdr) -{ - u32 val; - u64 start_pos, pos; - - start_pos = gf_bs_get_position(bs); - while (gf_bs_available(bs)) { - val = gf_bs_read_u8(bs); - if (val!=0xFF) continue; - val = gf_bs_read_int(bs, 4); - if (val != 0x0F) { - gf_bs_read_int(bs, 4); - continue; - } - hdr->is_mp2 = (Bool)gf_bs_read_int(bs, 1); - gf_bs_read_int(bs, 2); - hdr->no_crc = (Bool)gf_bs_read_int(bs, 1); - pos = gf_bs_get_position(bs) - 2; - - hdr->profile = 1 + gf_bs_read_int(bs, 2); - hdr->sr_idx = gf_bs_read_int(bs, 4); - gf_bs_read_int(bs, 1); - hdr->nb_ch = gf_bs_read_int(bs, 3); - gf_bs_read_int(bs, 4); - hdr->frame_size = gf_bs_read_int(bs, 13); - gf_bs_read_int(bs, 11); - gf_bs_read_int(bs, 2); - hdr->hdr_size = 7; - if (!hdr->no_crc) { - gf_bs_read_u16(bs); - hdr->hdr_size = 9; - } - if (hdr->frame_size < hdr->hdr_size) { - gf_bs_seek(bs, pos+1); - continue; - } - hdr->frame_size -= hdr->hdr_size; - if (is_complete && (gf_bs_available(bs) == hdr->frame_size)) return GF_TRUE; - else if (gf_bs_available(bs) <= hdr->frame_size) break; - - gf_bs_skip_bytes(bs, hdr->frame_size); - val = gf_bs_read_u8(bs); - if (val!=0xFF) { - gf_bs_seek(bs, pos+1); - continue; - } - val = gf_bs_read_int(bs, 4); - if (val!=0x0F) { - gf_bs_read_int(bs, 4); - gf_bs_seek(bs, pos+1); - continue; - } - gf_bs_seek(bs, pos+hdr->hdr_size); - return GF_TRUE; - } - gf_bs_seek(bs, start_pos); - return GF_FALSE; -} - -static Bool AAC_ConfigureFromFile(AACReader *read) -{ - Bool sync; - GF_BitStream *bs; - ADTSHeader hdr; - if (!read || !read->stream) return GF_FALSE; - bs = gf_bs_from_file(read->stream, GF_BITSTREAM_READ); - - sync = ADTS_SyncFrame(bs, !read->is_remote, &hdr); - if (!sync) { - gf_bs_del(bs); - return GF_FALSE; - } - read->nb_ch = hdr.nb_ch; - read->prof = hdr.profile; - read->sr_idx = hdr.sr_idx; - read->oti = hdr.is_mp2 ? read->prof+GPAC_OTI_AUDIO_AAC_MPEG2_MP : GPAC_OTI_AUDIO_AAC_MPEG4; - read->sample_rate = GF_M4ASampleRates[read->sr_idx]; - - read->duration = 0; - - if (!read->is_remote) { - read->duration = 1024; - gf_bs_skip_bytes(bs, hdr.frame_size); - while (ADTS_SyncFrame(bs, !read->is_remote, &hdr)) { - read->duration += 1024; - gf_bs_skip_bytes(bs, hdr.frame_size); - } - } - gf_bs_del(bs); - gf_fseek(read->stream, 0, SEEK_SET); - return 1; -} - -#ifndef DONT_USE_TERMINAL_MODULE_API -static void AAC_RegulateDataRate(AACReader *read) -{ - GF_NetworkCommand com; - if (!read) - return; - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.command_type = GF_NET_CHAN_BUFFER_QUERY; - com.base.on_channel = read->ch; - while (read->ch) { - gf_service_command(read->service, &com, GF_OK); - if (com.buffer.occupancy < com.buffer.max) break; - gf_sleep(2); - } -} -#endif - -static void AAC_OnLiveData(AACReader *read, const char *data, u32 data_size) -{ - u32 pos; - Bool sync; - GF_BitStream *bs; - ADTSHeader hdr; - assert( read->data || (!read->data && !read->data_size)); - read->data = gf_realloc(read->data, sizeof(char)*(read->data_size+data_size) ); - memcpy(read->data + read->data_size, data, sizeof(char)*data_size); - read->data_size += data_size; - - if (read->needs_connection) { - read->needs_connection = 0; - bs = gf_bs_new((const char *)read->data, read->data_size, GF_BITSTREAM_READ); - sync = ADTS_SyncFrame(bs, 0, &hdr); - gf_bs_del(bs); - if (!sync) return; - read->nb_ch = hdr.nb_ch; - read->prof = hdr.profile; - read->sr_idx = hdr.sr_idx; - read->oti = hdr.is_mp2 ? read->prof+GPAC_OTI_AUDIO_AAC_MPEG2_MP-1 : GPAC_OTI_AUDIO_AAC_MPEG4; - read->sample_rate = GF_M4ASampleRates[read->sr_idx]; - read->is_live = 1; - memset(&read->sl_hdr, 0, sizeof(GF_SLHeader)); -#ifndef DONT_USE_TERMINAL_MODULE_API - gf_service_connect_ack(read->service, NULL, GF_OK); - AAC_SetupObject(read); -#endif - } -#ifndef DONT_USE_TERMINAL_MODULE_API - if (!read->ch) return; -#endif - - /*need a full adts header*/ - if (read->data_size<=7) return; - - bs = gf_bs_new((const char *)read->data, read->data_size, GF_BITSTREAM_READ); - hdr.frame_size = pos = 0; - while (ADTS_SyncFrame(bs, 0, &hdr)) { - pos = (u32) gf_bs_get_position(bs); - read->sl_hdr.accessUnitStartFlag = 1; - read->sl_hdr.accessUnitEndFlag = 1; - read->sl_hdr.AU_sequenceNumber++; - read->sl_hdr.compositionTimeStampFlag = 1; - read->sl_hdr.compositionTimeStamp += 1024; -#ifndef DONT_USE_TERMINAL_MODULE_API - gf_service_send_packet(read->service, read->ch, read->data + pos, hdr.frame_size, &read->sl_hdr, GF_OK); -#else - SampleCallBack(audio_prog, AUDIO_DATA_ESID, read->data + pos, hdr.frame_size, read->sl_hdr.compositionTimeStamp); -#endif - gf_bs_skip_bytes(bs, hdr.frame_size); - -#ifndef DONT_USE_TERMINAL_MODULE_API -#ifndef _WIN32_WCE - /* For Hybrid scenarios*/ - if (read->hybrid_on && (!read->prev_map_time || (read->sl_hdr.compositionTimeStamp - read->prev_map_time) > read->sample_rate*5)) { - Double hybrid_delay = 0.0; - time_t now; - struct tm *now_tm; - const char *opt; - - opt = gf_modules_get_option((GF_BaseInterface *)read->input, "HybRadio", "AudioDelay"); - if (opt) { - hybrid_delay = atof(opt); - } - time(&now); - now += (s32)hybrid_delay; - now_tm = gmtime(&now); - { - GF_NetworkCommand com; - memset(&com, 0, sizeof(com)); - com.command_type = GF_NET_CHAN_MAP_TIME; - com.map_time.media_time = now_tm->tm_hour*3600+now_tm->tm_min*60+now_tm->tm_sec + hybrid_delay; - com.map_time.timestamp = read->sl_hdr.compositionTimeStamp; - com.map_time.reset_buffers = 0; - com.base.on_channel = read->ch; - gf_service_command(read->service, &com, GF_OK); - GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[AAC In] Mapping WC Time %04d/%02d/%02d %02d:%02d:%02d and AAC time "LLD" (audio delay %f)\n", - (now_tm->tm_year + 1900), (now_tm->tm_mon + 1), now_tm->tm_mday, now_tm->tm_hour, now_tm->tm_min, now_tm->tm_sec, - com.map_time.timestamp, hybrid_delay)); - - } - read->prev_map_time = read->sl_hdr.compositionTimeStamp; - } -#endif -#endif - } - - pos = (u32) gf_bs_get_position(bs); - gf_bs_del(bs); - - if (pos) { - char *d; - assert( read->data); - assert( read->data_size >= pos); - read->data_size -= pos; - d = gf_malloc(sizeof(char) * read->data_size); - assert( d ); - memcpy(d, read->data + pos, sizeof(char) * read->data_size); - gf_free(read->data); - read->data = d; - } -#ifndef DONT_USE_TERMINAL_MODULE_API - AAC_RegulateDataRate(read); -#endif -} - -static void AAC_disconnect_from_http_and_free(AACReader * read) -{ - if (!read) - return; - if (read->dnload) { - gf_dm_sess_abort(read->dnload); -#ifdef DONT_USE_TERMINAL_MODULE_API - gf_dm_sess_del( read->dnload); -#else - gf_service_download_del(read->dnload); -#endif /* DONT_USE_TERMINAL_MODULE_API */ - } - read->dnload = NULL; -} - -void AAC_NetIO(void *cbk, GF_NETIO_Parameter *param) -{ - GF_Err e; - const char *szCache; - u32 total_size, bytes_done; - AACReader *read = (AACReader *) cbk; - - e = param->error; - /*done*/ - if (param->msg_type==GF_NETIO_DATA_TRANSFERED) { - if (read->stream) { - read->is_remote = 0; - e = GF_EOS; - } else if (!read->needs_connection) { - return; - } - } else if (param->msg_type==GF_NETIO_PARSE_HEADER) { - if (!strcmp(param->name, "icy-name")) { - if (read->icy_name) gf_free(read->icy_name); - read->icy_name = gf_strdup(param->value); - } - if (!strcmp(param->name, "icy-genre")) { - if (read->icy_genre) gf_free(read->icy_genre); - read->icy_genre = gf_strdup(param->value); - } - if (!strcmp(param->name, "icy-meta")) { -#ifndef DONT_USE_TERMINAL_MODULE_API - GF_NetworkCommand com; -#endif - char *meta; - if (read->icy_track_name) gf_free(read->icy_track_name); - read->icy_track_name = NULL; - meta = param->value; - while (meta && meta[0]) { - char *sep = strchr(meta, ';'); - if (sep) sep[0] = 0; - - if (!strnicmp(meta, "StreamTitle=", 12)) { - read->icy_track_name = gf_strdup(meta+12); - if (!strcmp(read->icy_track_name, "''")) { - /* On some servers, '' means not track name */ - gf_free(read->icy_track_name); - read->icy_track_name = NULL; - } - } - if (!sep) break; - sep[0] = ';'; - meta = sep+1; - } -#ifndef DONT_USE_TERMINAL_MODULE_API - com.base.command_type = GF_NET_SERVICE_INFO; - gf_service_command(read->service, &com, GF_OK); -#endif - } - return; - } else { - /*handle service message*/ -#ifndef DONT_USE_TERMINAL_MODULE_API - gf_service_download_update_stats(read->dnload); -#endif - if (param->msg_type!=GF_NETIO_DATA_EXCHANGE) return; - } - - /*data fetching or EOS*/ - if (e >= GF_OK) { - if (read->needs_connection) { - gf_dm_sess_get_stats(read->dnload, NULL, NULL, &total_size, NULL, NULL, NULL); - if (!total_size) read->is_live = 1; - } - if (read->is_live) { - if (!e) AAC_OnLiveData(read, param->data, param->size); - return; - } - if (read->stream) return; - - /*open service*/ - szCache = gf_dm_sess_get_cache_name(read->dnload); - if (!szCache) e = GF_IO_ERR; - else { - read->stream = gf_fopen((char *) szCache, "rb"); - if (!read->stream) e = GF_SERVICE_ERROR; - else { - /*if full file at once (in cache) parse duration*/ - if (e==GF_EOS) read->is_remote = 0; - e = GF_OK; - /*not enough data*/ - if (!AAC_ConfigureFromFile(read)) { - /*get amount downloaded and check*/ - gf_dm_sess_get_stats(read->dnload, NULL, NULL, NULL, &bytes_done, NULL, NULL); - if (bytes_done>10*1024) { - e = GF_CORRUPTED_DATA; - } else { - gf_fclose(read->stream); - read->stream = NULL; - return; - } - } - } - } - } - /*OK confirm*/ - if (read->needs_connection) { - read->needs_connection = 0; -#ifndef DONT_USE_TERMINAL_MODULE_API - gf_service_connect_ack(read->service, NULL, e); - if (!e) AAC_SetupObject(read); -#else - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("Failed to initialize AAC reader\n")); - } -#endif - } -} - -void aac_download_file(AACReader *read, char *url) -{ - read->needs_connection = 1; - AAC_disconnect_from_http_and_free(read); -#ifndef DONT_USE_TERMINAL_MODULE_API - read->dnload = gf_service_download_new(read->service, url, 0, AAC_NetIO, read); -#else - { - GF_Err e; - if (!read->dm) - read->dm = gf_dm_new(NULL); - assert( read->dm ); - read->dnload = gf_dm_sess_new_simple(read->dm, url, 0, AAC_NetIO, read, &e); - } -#endif - - if (!read->dnload ) { - read->needs_connection = 0; -#ifndef DONT_USE_TERMINAL_MODULE_API - gf_service_connect_ack(read->service, NULL, GF_NOT_SUPPORTED); -#endif - } - -#ifndef DONT_USE_TERMINAL_MODULE_API - /*start our download (threaded)*/ - gf_dm_sess_process(read->dnload); -#endif - - /*service confirm is done once fetched*/ -} - -static void AAC_Reader_del(AACReader * read) -{ - if (!read) - return; - AAC_disconnect_from_http_and_free(read); -#ifdef DONT_USE_TERMINAL_MODULE_API - if (read->dm) - gf_dm_del(read->dm); - read->dm = NULL; -#endif - if (read->icy_name) - gf_free(read->icy_name); - if (read->icy_genre) - gf_free(read->icy_genre); - if (read->icy_track_name) - gf_free(read->icy_track_name); - read->icy_name = read->icy_genre = read->icy_track_name = NULL; - if (read->stream) - gf_fclose(read->stream); - if (read->data) - gf_free(read->data); - read->data = NULL; - read->stream = NULL; -#ifndef DONT_USE_TERMINAL_MODULE_API - gf_free( read ); -#endif -} - -static AACReader * AAC_Reader_new() { - AACReader *reader = gf_malloc(sizeof(AACReader)); - memset(reader, 0, sizeof(AACReader)); - return reader; -} - - -#ifndef DONT_USE_TERMINAL_MODULE_API -static Bool aac_is_local(const char *url) -{ - if (!strnicmp(url, "file://", 7)) return 1; - if (strstr(url, "://")) return 0; - return 1; -} - -static GF_Err AAC_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url) -{ - char szURL[2048]; - char *ext; - const char *opt; - GF_Err reply; - AACReader *read = plug->priv; - read->service = serv; - read->input = plug; - - opt = gf_modules_get_option((GF_BaseInterface *)read->input, "HybRadio", "Activated"); - if (opt && !strcmp(opt, "true")) { - read->hybrid_on = 1; - } - - AAC_disconnect_from_http_and_free(read); - - strcpy(szURL, url); - ext = strrchr(szURL, '#'); - if (ext) ext[0] = 0; - - /*remote fetch*/ - read->is_remote = !aac_is_local(szURL); - if (read->is_remote) { - aac_download_file(read, (char *) szURL); - return GF_OK; - } - - reply = GF_OK; - read->stream = gf_fopen(szURL, "rb"); - if (!read->stream) { - reply = GF_URL_ERROR; - } else if (!AAC_ConfigureFromFile(read)) { - gf_fclose(read->stream); - read->stream = NULL; - reply = GF_NOT_SUPPORTED; - } - gf_service_connect_ack(serv, NULL, reply); - if (!reply && read->is_inline ) AAC_SetupObject(read); - return GF_OK; -} - -static GF_Err AAC_CloseService(GF_InputService *plug) -{ - AACReader *read; - if (!plug) - return GF_BAD_PARAM; - read = plug->priv; - if (!read) - return GF_BAD_PARAM; - gf_service_disconnect_ack(read->service, NULL, GF_OK); - AAC_Reader_del( read ); - plug->priv = NULL; - return GF_OK; -} - -static GF_Descriptor *AAC_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) -{ - AACReader *read = plug->priv; - /*since we don't handle multitrack in aac, we don't need to check sub_url, only use expected type*/ - - /*override default*/ - if (expect_type==GF_MEDIA_OBJECT_UNDEF) expect_type=GF_MEDIA_OBJECT_AUDIO; - - /*audio object*/ - if (expect_type==GF_MEDIA_OBJECT_AUDIO) { - GF_ESD *esd; - GF_ObjectDescriptor *od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - od->objectDescriptorID = 1; - esd = AAC_GetESD(read); - esd->OCRESID = 0; - gf_list_add(od->ESDescriptors, esd); - return (GF_Descriptor *) od; - } - read->is_inline = 1; - /*inline scene: no specific service*/ - return NULL; -} - -static GF_Err AAC_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) -{ - u32 ES_ID=0; - GF_Err e; - AACReader *read = plug->priv; - - e = GF_SERVICE_ERROR; - if (read->ch==channel) { - GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[AAC_IN] read->ch==channel = %p, GF_SERVICE_ERROR\n", channel)); - goto exit; - } - - e = GF_STREAM_NOT_FOUND; - if (strstr(url, "ES_ID")) { - sscanf(url, "ES_ID=%ud", &ES_ID); - } - /*URL setup*/ - else if (!read->ch && AAC_CanHandleURL(plug, url)) ES_ID = 1; - - if (ES_ID==1) { - read->ch = channel; - e = GF_OK; - } - -exit: - gf_service_connect_ack(read->service, channel, e); - return e; -} - -static GF_Err AAC_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) -{ - AACReader *read = plug->priv; - - GF_Err e = GF_STREAM_NOT_FOUND; - if (read->ch == channel) { - AAC_disconnect_from_http_and_free(read); - read->ch = NULL; - if (read->data) - gf_free(read->data); - read->data = NULL; - e = GF_OK; - } - gf_service_disconnect_ack(read->service, channel, e); - return GF_OK; -} - -static GF_Err AAC_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) -{ - AACReader *read = plug->priv; - - - if (com->base.command_type==GF_NET_SERVICE_INFO) { - com->info.name = read->icy_track_name ? read->icy_track_name : read->icy_name; - com->info.comment = read->icy_genre; - return GF_OK; - } - - if (!com->base.on_channel) { - /*if live session we may cache*/ - if (read->is_live && (com->command_type==GF_NET_IS_CACHABLE)) return GF_OK; - return GF_NOT_SUPPORTED; - } - switch (com->command_type) { - case GF_NET_CHAN_SET_PULL: - if ((read->ch == com->base.on_channel) && read->is_live) return GF_NOT_SUPPORTED; - return GF_OK; - case GF_NET_CHAN_INTERACTIVE: - if ((read->ch == com->base.on_channel) && read->is_live) return GF_NOT_SUPPORTED; - return GF_OK; - case GF_NET_CHAN_BUFFER: - if ((read->ch == com->base.on_channel) && read->is_live) { - if (com->buffer.max<1000) com->buffer.max = 1000; - com->buffer.min = com->buffer.max/2; - } - return GF_OK; - case GF_NET_CHAN_SET_PADDING: - read->pad_bytes = com->pad.padding_bytes; - return GF_OK; - case GF_NET_CHAN_DURATION: - com->duration.duration = read->duration; - com->duration.duration /= read->sample_rate; - return GF_OK; - case GF_NET_CHAN_PLAY: - read->start_range = com->play.start_range; - read->end_range = com->play.end_range; - read->current_time = 0; - if (read->stream) gf_fseek(read->stream, 0, SEEK_SET); - - if (read->ch == com->base.on_channel) { - read->done = 0; - /*PLAY after complete download, estimate duration*/ - if (!read->is_remote && !read->duration) { - AAC_ConfigureFromFile(read); - if (read->duration) { - GF_NetworkCommand rcfg; - rcfg.base.on_channel = read->ch; - rcfg.base.command_type = GF_NET_CHAN_DURATION; - rcfg.duration.duration = read->duration; - rcfg.duration.duration /= read->sample_rate; - gf_service_command(read->service, &rcfg, GF_OK); - } - } - } - return GF_OK; - case GF_NET_CHAN_STOP: - return GF_OK; - default: - return GF_OK; - } -} - - -static GF_Err AAC_ChannelGetSLP(GF_InputService *plug, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, GF_SLHeader *out_sl_hdr, Bool *sl_compressed, GF_Err *out_reception_status, Bool *is_new_data) -{ - u64 pos; - u32 start_from; - Bool sync; - GF_BitStream *bs; - ADTSHeader hdr; - AACReader *read = plug->priv; - - *out_reception_status = GF_OK; - *sl_compressed = 0; - *is_new_data = 0; - - memset(&read->sl_hdr, 0, sizeof(GF_SLHeader)); - read->sl_hdr.randomAccessPointFlag = 1; - read->sl_hdr.compositionTimeStampFlag = 1; - - if (read->ch != channel) return GF_STREAM_NOT_FOUND; - - /*fetching es data*/ - if (read->done) { - *out_reception_status = GF_EOS; - return GF_OK; - } - - if (!read->data) { - if (!read->stream) { - *out_data_ptr = NULL; - *out_data_size = 0; - return GF_OK; - } - bs = gf_bs_from_file(read->stream, GF_BITSTREAM_READ); - *is_new_data = 1; - -fetch_next: - pos = gf_ftell(read->stream); - sync = ADTS_SyncFrame(bs, !read->is_remote, &hdr); - if (!sync) { - gf_bs_del(bs); - if (!read->dnload) { - if (read->input->query_proxy) { - GF_NetworkCommand param; - param.command_type = GF_NET_SERVICE_QUERY_NEXT; - if ((read->input->query_proxy(read->input, ¶m)==GF_OK) - && param.url_query.next_url - ) { - gf_fclose(read->stream); - read->stream = gf_fopen(param.url_query.next_url, "rb"); - *out_reception_status = GF_OK; - return GF_OK; - } - } - - *out_reception_status = GF_EOS; - read->done = 1; - } else { - gf_fseek(read->stream, pos, SEEK_SET); - *out_reception_status = GF_OK; - } - return GF_OK; - } - - if (!hdr.frame_size) { - gf_bs_del(bs); - *out_reception_status = GF_EOS; - read->done = 1; - return GF_OK; - } - read->data_size = hdr.frame_size; - read->nb_samp = 1024; - /*we're seeking*/ - if (read->start_range && read->duration) { - start_from = (u32) (read->start_range * read->sample_rate); - if (read->current_time + read->nb_samp < start_from) { - read->current_time += read->nb_samp; - goto fetch_next; - } else { - read->start_range = 0; - } - } - - read->sl_hdr.compositionTimeStamp = read->current_time; - assert( !read->data); - read->data = gf_malloc(sizeof(char) * (read->data_size+read->pad_bytes)); - gf_bs_read_data(bs, read->data, read->data_size); - if (read->pad_bytes) memset(read->data + read->data_size, 0, sizeof(char) * read->pad_bytes); - gf_bs_del(bs); - } - *out_sl_hdr = read->sl_hdr; - *out_data_ptr = read->data; - *out_data_size = read->data_size; - return GF_OK; -} - -static GF_Err AAC_ChannelReleaseSLP(GF_InputService *plug, LPNETCHANNEL channel) -{ - AACReader *read = plug->priv; - - if (read->ch == channel) { - if (!read->data) return GF_BAD_PARAM; - gf_free(read->data); - read->data = NULL; - read->current_time += read->nb_samp; - return GF_OK; - } - return GF_BAD_PARAM; -} - -GF_InputService *AAC_Load() -{ - - GF_InputService *plug = gf_malloc(sizeof(GF_InputService)); - memset(plug, 0, sizeof(GF_InputService)); - GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC AAC Reader", "gpac distribution") - - plug->CanHandleURL = AAC_CanHandleURL; - plug->ConnectService = AAC_ConnectService; - plug->CloseService = AAC_CloseService; - plug->GetServiceDescriptor = AAC_GetServiceDesc; - plug->ConnectChannel = AAC_ConnectChannel; - plug->DisconnectChannel = AAC_DisconnectChannel; - plug->ServiceCommand = AAC_ServiceCommand; - /*we do support pull mode*/ - plug->ChannelGetSLP = AAC_ChannelGetSLP; - plug->ChannelReleaseSLP = AAC_ChannelReleaseSLP; - plug->RegisterMimeTypes = AAC_RegisterMimeTypes; - plug->priv = AAC_Reader_new(); - return plug; -} - -void AAC_Delete(void *ifce) -{ - AACReader *read; - GF_InputService *plug = (GF_InputService *) ifce; - if (!plug) - return; - read = plug->priv; - if (read) { - AAC_Reader_del(read); - plug->priv = NULL; - } - gf_free(plug); -} - -#endif -#endif - -#ifndef DONT_USE_TERMINAL_MODULE_API - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifndef GPAC_DISABLE_AV_PARSERS - GF_NET_CLIENT_INTERFACE, -#endif -#ifdef GPAC_HAS_FAAD - GF_MEDIA_DECODER_INTERFACE, -#endif - 0 - }; - - return si; -} - -#ifdef GPAC_HAS_FAAD -GF_BaseDecoder *NewFAADDec(); -void DeleteFAADDec(GF_BaseDecoder *ifcg); -#endif - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ -#ifndef GPAC_DISABLE_AV_PARSERS - if (InterfaceType == GF_NET_CLIENT_INTERFACE) return (GF_BaseInterface *)AAC_Load(); -#endif -#ifdef GPAC_HAS_FAAD - if (InterfaceType == GF_MEDIA_DECODER_INTERFACE) return (GF_BaseInterface *)NewFAADDec(); -#endif - return NULL; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#ifdef GPAC_HAS_FAAD - case GF_MEDIA_DECODER_INTERFACE: - DeleteFAADDec((GF_BaseDecoder *)ifce); - break; -#endif -#ifndef GPAC_DISABLE_AV_PARSERS - case GF_NET_CLIENT_INTERFACE: - AAC_Delete(ifce); - break; -#endif - } -} - -GPAC_MODULE_STATIC_DECLARATION( aac_in ) - -#endif diff --git a/modules/aac_in/faad_dec.c b/modules/aac_in/faad_dec.c deleted file mode 100644 index 494ba96..0000000 --- a/modules/aac_in/faad_dec.c +++ /dev/null @@ -1,499 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / AAC reader module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include <gpac/setup.h> - -#ifdef GPAC_HAS_FAAD - -#ifdef __GNUC__ -#pragma GCC diagnostic ignored "-Wunknown-pragmas" -#endif - -#if !defined(__GNUC__) -# if defined(_WIN32_WCE) || defined (WIN32) -# pragma comment(lib, "libfaad") -# endif -#endif - -#include <faad.h> - -#include <gpac/modules/codec.h> -#include <gpac/constants.h> -#include <gpac/avparse.h> - - -typedef struct -{ - faacDecHandle codec; - faacDecFrameInfo info; - u32 sample_rate, out_size, num_samples; - u8 num_channels; - /*no support for scalability in FAAD yet*/ - u16 ES_ID; - Bool signal_mc; - Bool is_sbr; - - char ch_reorder[16]; - GF_ESD *esd; -} FAADDec; - - -#define FAADCTX() FAADDec *ctx = (FAADDec *) ifcg->privateStack - -static GF_Err FAAD_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ -#ifndef GPAC_DISABLE_AV_PARSERS - GF_Err e; - GF_M4ADecSpecInfo a_cfg; -#endif - FAADCTX(); - - if (ctx->ES_ID && ctx->ES_ID!=esd->ESID) return GF_NOT_SUPPORTED; - if (!esd->decoderConfig->decoderSpecificInfo || !esd->decoderConfig->decoderSpecificInfo->dataLength) return GF_NON_COMPLIANT_BITSTREAM; - - if (!ctx->esd) { - ctx->esd = esd; - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[FAAD] Attaching stream %d\n", esd->ESID)); - } - - if (ctx->codec) faacDecClose(ctx->codec); - ctx->codec = faacDecOpen(); - if (!ctx->codec) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FAAD] Error initializing decoder\n")); - return GF_IO_ERR; - } - -#ifndef GPAC_DISABLE_AV_PARSERS - e = gf_m4a_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &a_cfg); - if (e) return e; -#endif - if (faacDecInit2(ctx->codec, (unsigned char *)esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, (unsigned long*)&ctx->sample_rate, (u8*)&ctx->num_channels) < 0) - { -#ifndef GPAC_DISABLE_AV_PARSERS - s8 res; - char *dsi, *s_base_object_type; - u32 dsi_len; - switch (a_cfg.base_object_type) { - case GF_M4A_AAC_MAIN: - s_base_object_type = gf_stringizer(GF_M4A_AAC_MAIN); - goto base_object_type_error; - case GF_M4A_AAC_LC: - s_base_object_type = gf_stringizer(GF_M4A_AAC_LC); - goto base_object_type_error; - case GF_M4A_AAC_SSR: - s_base_object_type = gf_stringizer(GF_M4A_AAC_SSR); - goto base_object_type_error; - case GF_M4A_AAC_LTP: - s_base_object_type = gf_stringizer(GF_M4A_AAC_LTP); - goto base_object_type_error; - case GF_M4A_AAC_SBR: - s_base_object_type = gf_stringizer(GF_M4A_AAC_SBR); - goto base_object_type_error; - case GF_M4A_AAC_PS: - s_base_object_type = gf_stringizer(GF_M4A_AAC_PS); -base_object_type_error: /*error case*/ - GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[FAAD] Error: unsupported %s format for stream %d - defaulting to AAC LC\n", s_base_object_type, esd->ESID)); - default: - break; - } - a_cfg.base_object_type = GF_M4A_AAC_LC; - a_cfg.has_sbr = GF_FALSE; - a_cfg.nb_chan = a_cfg.nb_chan > 2 ? 1 : a_cfg.nb_chan; - - gf_m4a_write_config(&a_cfg, &dsi, &dsi_len); - res = faacDecInit2(ctx->codec, (unsigned char *) dsi, dsi_len, (unsigned long *) &ctx->sample_rate, (u8 *) &ctx->num_channels); - gf_free(dsi); - if (res < 0) -#endif - { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FAAD] Error when initializing AAC decoder for stream %d\n", esd->ESID)); - return GF_NOT_SUPPORTED; - } - } - -#ifndef GPAC_DISABLE_AV_PARSERS - ctx->is_sbr = a_cfg.has_sbr; -#endif - ctx->num_samples = 1024; - ctx->out_size = 2 * ctx->num_samples * ctx->num_channels; - ctx->ES_ID = esd->ESID; - ctx->signal_mc = ctx->num_channels>2 ? GF_TRUE : GF_FALSE; - return GF_OK; -} - -static GF_Err FAAD_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - FAADCTX(); - if (ES_ID != ctx->ES_ID) return GF_BAD_PARAM; - if (ctx->codec) faacDecClose(ctx->codec); - ctx->codec = NULL; - ctx->ES_ID = 0; - ctx->sample_rate = ctx->out_size = ctx->num_samples = 0; - ctx->num_channels = 0; - return GF_OK; -} -static GF_Err FAAD_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - u32 i; - FAADCTX(); - switch (capability->CapCode) { - /*not tested yet*/ - case GF_CODEC_RESILIENT: - capability->cap.valueInt = 1; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = ctx->out_size; - break; - case GF_CODEC_SAMPLERATE: - capability->cap.valueInt = ctx->sample_rate; - break; - case GF_CODEC_NB_CHAN: - capability->cap.valueInt = ctx->num_channels; - break; - case GF_CODEC_BITS_PER_SAMPLE: - capability->cap.valueInt = 16; - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = 4; - break; - case GF_CODEC_BUFFER_MAX: - capability->cap.valueInt = 12; - break; - /*by default AAC access unit lasts num_samples (timescale being sampleRate)*/ - case GF_CODEC_CU_DURATION: - capability->cap.valueInt = ctx->num_samples; - break; - /*to refine, it seems that 4 bytes padding is not enough on all streams ?*/ - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 8; - break; - case GF_CODEC_CHANNEL_CONFIG: - capability->cap.valueInt = 0; - for (i=0; i<ctx->num_channels; i++) { - switch (ctx->info.channel_position[i]) { - case FRONT_CHANNEL_CENTER: - capability->cap.valueInt |= GF_AUDIO_CH_FRONT_CENTER; - break; - case FRONT_CHANNEL_LEFT: - capability->cap.valueInt |= GF_AUDIO_CH_FRONT_LEFT; - break; - case FRONT_CHANNEL_RIGHT: - capability->cap.valueInt |= GF_AUDIO_CH_FRONT_RIGHT; - break; - case SIDE_CHANNEL_LEFT: - capability->cap.valueInt |= GF_AUDIO_CH_SIDE_LEFT; - break; - case SIDE_CHANNEL_RIGHT: - capability->cap.valueInt |= GF_AUDIO_CH_SIDE_RIGHT; - break; - case BACK_CHANNEL_LEFT: - capability->cap.valueInt |= GF_AUDIO_CH_BACK_LEFT; - break; - case BACK_CHANNEL_RIGHT: - capability->cap.valueInt |= GF_AUDIO_CH_BACK_RIGHT; - break; - case BACK_CHANNEL_CENTER: - capability->cap.valueInt |= GF_AUDIO_CH_BACK_CENTER; - break; - case LFE_CHANNEL: - capability->cap.valueInt |= GF_AUDIO_CH_LFE; - break; - default: - break; - } - } - break; - default: - capability->cap.valueInt = 0; - break; - } - return GF_OK; -} -static GF_Err FAAD_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - /*return unsupported to avoid confusion by the player (like SR changing ...) */ - return GF_NOT_SUPPORTED; -} - -static s8 FAAD_GetChannelPos(FAADDec *ffd, u32 ch_cfg) -{ - u32 i; - for (i=0; i<ffd->info.channels; i++) { - switch (ffd->info.channel_position[i]) { - case FRONT_CHANNEL_CENTER: - if (ch_cfg==GF_AUDIO_CH_FRONT_CENTER) return i; - break; - case FRONT_CHANNEL_LEFT: - if (ch_cfg==GF_AUDIO_CH_FRONT_LEFT) return i; - break; - case FRONT_CHANNEL_RIGHT: - if (ch_cfg==GF_AUDIO_CH_FRONT_RIGHT) return i; - break; - case SIDE_CHANNEL_LEFT: - if (ch_cfg==GF_AUDIO_CH_SIDE_LEFT) return i; - break; - case SIDE_CHANNEL_RIGHT: - if (ch_cfg==GF_AUDIO_CH_SIDE_RIGHT) return i; - break; - case BACK_CHANNEL_LEFT: - if (ch_cfg==GF_AUDIO_CH_BACK_LEFT) return i; - break; - case BACK_CHANNEL_RIGHT: - if (ch_cfg==GF_AUDIO_CH_BACK_RIGHT) return i; - break; - case BACK_CHANNEL_CENTER: - if (ch_cfg==GF_AUDIO_CH_BACK_CENTER) return i; - break; - case LFE_CHANNEL: - if (ch_cfg==GF_AUDIO_CH_LFE) return i; - break; - } - } - return -1; -} - -static GF_Err FAAD_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ - void *buffer; - unsigned short *conv_in, *conv_out; - u32 i, j; - FAADCTX(); - - /*check not using scalabilty*/ - if (ctx->ES_ID != ES_ID) return GF_BAD_PARAM; - - /*if late or seeking don't decode*/ - switch (mmlevel) { - case GF_CODEC_LEVEL_SEEK: -// case GF_CODEC_LEVEL_DROP: - *outBufferLength = 0; - return GF_OK; - default: - break; - } - - if (ctx->out_size > *outBufferLength) { - *outBufferLength = ctx->out_size; - return GF_BUFFER_TOO_SMALL; - } - - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[FAAD] Decoding AU\n")); - buffer = faacDecDecode(ctx->codec, &ctx->info, (unsigned char *) inBuffer, inBufferLength); - if (ctx->info.error>0) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[FAAD] Error decoding AU %s\n", faacDecGetErrorMessage(ctx->info.error) )); - *outBufferLength = 0; - //reinit if error - FAAD_AttachStream((GF_BaseDecoder *)ifcg, ctx->esd); - return GF_NON_COMPLIANT_BITSTREAM; - } - if (!ctx->info.samples || !buffer || !ctx->info.bytesconsumed) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[FAAD] empty/non complete AU\n")); - *outBufferLength = 0; - return GF_OK; - } - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[FAAD] AU decoded\n")); - - /*FAAD froces us to decode a frame to get channel cfg*/ - if (ctx->signal_mc) { - s32 ch, idx; - ctx->signal_mc = GF_FALSE; - idx = 0; - /*NOW WATCH OUT!! FAAD may very well decide to output more channels than indicated!!!*/ - ctx->num_channels = ctx->info.channels; - - /*get cfg*/ - ch = FAAD_GetChannelPos(ctx, GF_AUDIO_CH_FRONT_LEFT); - if (ch>=0) { - ctx->ch_reorder[idx] = ch; - idx++; - } - ch = FAAD_GetChannelPos(ctx, GF_AUDIO_CH_FRONT_RIGHT); - if (ch>=0) { - ctx->ch_reorder[idx] = ch; - idx++; - } - ch = FAAD_GetChannelPos(ctx, GF_AUDIO_CH_FRONT_CENTER); - if (ch>=0) { - ctx->ch_reorder[idx] = ch; - idx++; - } - ch = FAAD_GetChannelPos(ctx, GF_AUDIO_CH_LFE); - if (ch>=0) { - ctx->ch_reorder[idx] = ch; - idx++; - } - ch = FAAD_GetChannelPos(ctx, GF_AUDIO_CH_BACK_LEFT); - if (ch>=0) { - ctx->ch_reorder[idx] = ch; - idx++; - } - ch = FAAD_GetChannelPos(ctx, GF_AUDIO_CH_BACK_RIGHT); - if (ch>=0) { - ctx->ch_reorder[idx] = ch; - idx++; - } - ch = FAAD_GetChannelPos(ctx, GF_AUDIO_CH_BACK_CENTER); - if (ch>=0) { - ctx->ch_reorder[idx] = ch; - idx++; - } - ch = FAAD_GetChannelPos(ctx, GF_AUDIO_CH_SIDE_LEFT); - if (ch>=0) { - ctx->ch_reorder[idx] = ch; - idx++; - } - ch = FAAD_GetChannelPos(ctx, GF_AUDIO_CH_SIDE_RIGHT); - if (ch>=0) { - ctx->ch_reorder[idx] = ch; - idx++; - } - - *outBufferLength = ctx->out_size; - if (sizeof(short) * ctx->info.samples > *outBufferLength) { - *outBufferLength = ctx->out_size = (u32) (sizeof(short)*ctx->info.samples); - } - return GF_BUFFER_TOO_SMALL; - } - if (sizeof(short) * ctx->info.samples > *outBufferLength) { - *outBufferLength = (u32) (sizeof(short)*ctx->info.samples); - return GF_BUFFER_TOO_SMALL; - } - - /*we assume left/right order*/ - if (ctx->num_channels<=2) { - memcpy(outBuffer, buffer, sizeof(short)* ctx->info.samples); - *outBufferLength = (u32) (sizeof(short)*ctx->info.samples); - return GF_OK; - } - conv_in = (unsigned short *) buffer; - conv_out = (unsigned short *) outBuffer; - for (i=0; i<ctx->info.samples; i+=ctx->info.channels) { - for (j=0; j<ctx->info.channels; j++) { - conv_out[i + j] = conv_in[i + ctx->ch_reorder[j]]; - } - } - *outBufferLength = (u32) (sizeof(short)*ctx->info.samples); - return GF_OK; -} - -static const char *FAAD_GetCodecName(GF_BaseDecoder *ifcg) -{ - FAADCTX(); - if (ctx->is_sbr) return "FAAD2 " FAAD2_VERSION " SBR mode"; - return "FAAD2 " FAAD2_VERSION; -} - -static u32 FAAD_CanHandleStream(GF_BaseDecoder *dec, u32 StreamType, GF_ESD *esd, u8 PL) -{ -#ifndef GPAC_DISABLE_AV_PARSERS - GF_M4ADecSpecInfo a_cfg; -#endif - - /*audio decs*/ - if (StreamType != GF_STREAM_AUDIO) return GF_CODEC_NOT_SUPPORTED; - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - - switch (esd->decoderConfig->objectTypeIndication) { - /*MPEG2 aac*/ - case GPAC_OTI_AUDIO_AAC_MPEG2_MP: - case GPAC_OTI_AUDIO_AAC_MPEG2_LCP: - case GPAC_OTI_AUDIO_AAC_MPEG2_SSRP: - /*MPEG4 aac*/ - case GPAC_OTI_AUDIO_AAC_MPEG4: - break; - default: - return GF_CODEC_NOT_SUPPORTED; - } - /*this codec currently requires AAC config in ESD*/ - if (!esd->decoderConfig->decoderSpecificInfo || !esd->decoderConfig->decoderSpecificInfo->data) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[FAAD] DecoderSpecificInfo missing - cannot initialize\n")); - return GF_CODEC_NOT_SUPPORTED; - } -#ifndef GPAC_DISABLE_AV_PARSERS - if (gf_m4a_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &a_cfg) != GF_OK) return GF_CODEC_NOT_SUPPORTED; - - switch (a_cfg.base_object_type) { - case GF_M4A_AAC_MAIN: - case GF_M4A_AAC_LC: - case GF_M4A_AAC_SSR: - case GF_M4A_AAC_LTP: - case GF_M4A_AAC_SBR: - return GF_CODEC_SUPPORTED; - case GF_M4A_ER_AAC_LC: - case GF_M4A_ER_AAC_LTP: - case GF_M4A_ER_AAC_SCALABLE: - case GF_M4A_ER_AAC_LD: - case GF_M4A_AAC_PS: - return GF_CODEC_MAYBE_SUPPORTED; - default: - return GF_CODEC_NOT_SUPPORTED; - } -#endif - - return GF_CODEC_SUPPORTED; -} - -GF_BaseDecoder *NewFAADDec() -{ - GF_MediaDecoder *ifce; - FAADDec *dec; - - GF_SAFEALLOC(ifce, GF_MediaDecoder); - if (!ifce) return NULL; - GF_SAFEALLOC(dec, FAADDec); - if (!dec) { - gf_free(dec); - return NULL; - } - GF_REGISTER_MODULE_INTERFACE(ifce, GF_MEDIA_DECODER_INTERFACE, "FAAD2 Decoder", "gpac distribution") - - ifce->privateStack = dec; - - /*setup our own interface*/ - ifce->AttachStream = FAAD_AttachStream; - ifce->DetachStream = FAAD_DetachStream; - ifce->GetCapabilities = FAAD_GetCapabilities; - ifce->SetCapabilities = FAAD_SetCapabilities; - ifce->ProcessData = FAAD_ProcessData; - ifce->CanHandleStream = FAAD_CanHandleStream; - ifce->GetName = FAAD_GetCodecName; - return (GF_BaseDecoder *) ifce; -} - -void DeleteFAADDec(GF_BaseDecoder *ifcg) -{ - FAADCTX(); - if (ctx->codec) faacDecClose(ctx->codec); - gf_free(ctx); - gf_free(ifcg); -} - - -#endif diff --git a/modules/ac3_in/Makefile b/modules/ac3_in/Makefile deleted file mode 100644 index 3ad404e..0000000 --- a/modules/ac3_in/Makefile +++ /dev/null @@ -1,59 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/ac3_in - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS= ac3_in.o - -SRCS := $(OBJS:.o=.c) - -#faad config -ifeq ($(CONFIG_A52), no) -else -OBJS+=liba52_dec.o -CFLAGS+=-DGPAC_HAS_LIBA52 -#local faad lib -ifeq ($(CONFIG_FAAD), local) -EXTRALIBS+=-L../../extra_lib/lib/gcc -CFLAGS+= -I"$(LOCAL_INC_PATH)" -endif -EXTRALIBS+= -la52 -endif - -LIB=gm_ac3_in$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols ac3_in.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) -L../../bin/gcc -lgpac $(EXTRALIBS) $(LDFLAGS) - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/ac3_in/ac3_in.c b/modules/ac3_in/ac3_in.c deleted file mode 100644 index c205ce6..0000000 --- a/modules/ac3_in/ac3_in.c +++ /dev/null @@ -1,704 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / AC3 reader module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include <gpac/modules/service.h> -#include <gpac/modules/codec.h> -#include <gpac/avparse.h> -#include <gpac/constants.h> - -#ifndef GPAC_DISABLE_AV_PARSERS - -typedef struct -{ - GF_ClientService *service; - - Bool is_remote; - - FILE *stream; - u32 duration; - - Bool needs_connection; - u32 pad_bytes; - Bool done; - u32 is_inline; - LPNETCHANNEL ch; - - unsigned char *data; - u32 data_size; - GF_SLHeader sl_hdr; - - u32 sample_rate, nb_ch; - Double start_range, end_range; - u32 current_time, nb_samp; - /*file downloader*/ - GF_DownloadSession * dnload; - - Bool is_live; - char prev_data[1000]; - u32 prev_size; - - char *icy_name; - char *icy_genre; - char *icy_track_name; -} AC3Reader; - -static const char * AC3_MIMES[] = { "audio/ac3", "audio/x-ac3", NULL }; - -static const char * AC3_EXTS = "ac3"; - -static const char * AC3_DESC = "AC3 Music"; - -static u32 AC3_RegisterMimeTypes(const GF_InputService *plug) -{ - u32 i; - for (i = 0 ; AC3_MIMES[i]; i++) - gf_service_register_mime(plug, AC3_MIMES[i], AC3_EXTS, AC3_DESC); - return i; -} - -static Bool AC3_CanHandleURL(GF_InputService *plug, const char *url) -{ - char *sExt; - u32 i; - sExt = strrchr(url, '.'); - for (i = 0 ; AC3_MIMES[i]; i++) { - if (gf_service_check_mime_register(plug, AC3_MIMES[i], AC3_EXTS, AC3_DESC, sExt)) return GF_TRUE; - } - return GF_FALSE; -} - -static Bool ac3_is_local(const char *url) -{ - if (!strnicmp(url, "file://", 7)) return GF_TRUE; - if (strstr(url, "://")) return GF_FALSE; - return GF_TRUE; -} - -static GF_ESD *AC3_GetESD(AC3Reader *read) -{ - GF_ESD *esd; - esd = gf_odf_desc_esd_new(0); - esd->decoderConfig->streamType = GF_STREAM_AUDIO; - esd->decoderConfig->objectTypeIndication = GPAC_OTI_AUDIO_AC3; - esd->ESID = 1; - esd->OCRESID = 1; - esd->slConfig->timestampResolution = read->sample_rate; - if (read->is_live) esd->slConfig->useAccessUnitEndFlag = esd->slConfig->useAccessUnitStartFlag = 1; - return esd; -} - -static void AC3_SetupObject(AC3Reader *read) -{ - GF_ESD *esd; - GF_ObjectDescriptor *od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - od->objectDescriptorID = 1; - esd = AC3_GetESD(read); - esd->OCRESID = 0; - gf_list_add(od->ESDescriptors, esd); - gf_service_declare_media(read->service, (GF_Descriptor*)od, GF_FALSE); -} - - -static Bool AC3_ConfigureFromFile(AC3Reader *read) -{ - Bool sync; - GF_BitStream *bs; - GF_AC3Header hdr; - memset(&hdr, 0, sizeof(GF_AC3Header)); - if (!read->stream) return GF_FALSE; - bs = gf_bs_from_file(read->stream, GF_BITSTREAM_READ); - - - sync = gf_ac3_parser_bs(bs, &hdr, GF_TRUE); - if (!sync) { - gf_bs_del(bs); - return GF_FALSE; - } - read->nb_ch = hdr.channels; - read->sample_rate = hdr.sample_rate; - read->duration = 0; - - if (!read->is_remote) { - read->duration = 1536; - gf_bs_skip_bytes(bs, hdr.framesize); - while (gf_ac3_parser_bs(bs, &hdr, GF_FALSE)) { - read->duration += 1536; - gf_bs_skip_bytes(bs, hdr.framesize); - } - } - gf_bs_del(bs); - gf_fseek(read->stream, 0, SEEK_SET); - return GF_TRUE; -} - -static void AC3_RegulateDataRate(AC3Reader *read) -{ - GF_NetworkCommand com; - - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.command_type = GF_NET_CHAN_BUFFER_QUERY; - com.base.on_channel = read->ch; - while (read->ch) { - gf_service_command(read->service, &com, GF_OK); - if (com.buffer.occupancy < com.buffer.max) break; - gf_sleep(2); - } -} - -static void AC3_OnLiveData(AC3Reader *read, const char *data, u32 data_size) -{ - u64 pos; - Bool sync; - GF_BitStream *bs; - GF_AC3Header hdr; - - memset(&hdr, 0, sizeof(GF_AC3Header)); - - read->data = (unsigned char*)gf_realloc(read->data, sizeof(char)*(read->data_size+data_size) ); - memcpy(read->data + read->data_size, data, sizeof(char)*data_size); - read->data_size += data_size; - - if (read->needs_connection) { - read->needs_connection = GF_FALSE; - bs = gf_bs_new((char *) read->data, read->data_size, GF_BITSTREAM_READ); - sync = gf_ac3_parser_bs(bs, &hdr, GF_TRUE); - gf_bs_del(bs); - if (!sync) return; - read->nb_ch = hdr.channels; - read->sample_rate = hdr.sample_rate; - read->is_live = GF_TRUE; - memset(&read->sl_hdr, 0, sizeof(GF_SLHeader)); - gf_service_connect_ack(read->service, NULL, GF_OK); - AC3_SetupObject(read); - } - if (!read->ch) return; - - /*need a full ac3 header*/ - if (read->data_size<=7) return; - - bs = gf_bs_new((char *) read->data, read->data_size, GF_BITSTREAM_READ); - hdr.framesize = 0; - - while (gf_ac3_parser_bs(bs, &hdr, GF_FALSE)) { - pos = gf_bs_get_position(bs); - read->sl_hdr.accessUnitStartFlag = 1; - read->sl_hdr.accessUnitEndFlag = 1; - read->sl_hdr.AU_sequenceNumber++; - read->sl_hdr.compositionTimeStampFlag = 1; - read->sl_hdr.compositionTimeStamp += 1536; - gf_service_send_packet(read->service, read->ch, (char *) read->data + pos, hdr.framesize, &read->sl_hdr, GF_OK); - gf_bs_skip_bytes(bs, hdr.framesize); - } - - pos = gf_bs_get_position(bs); - gf_bs_del(bs); - - if (pos) { - u8 *d; - read->data_size -= (u32) pos; - d = (u8*)gf_malloc(sizeof(char) * read->data_size); - memcpy(d, read->data + pos, sizeof(char) * read->data_size); - gf_free(read->data); - read->data = d; - } - AC3_RegulateDataRate(read); -} - -void AC3_NetIO(void *cbk, GF_NETIO_Parameter *param) -{ - GF_Err e; - const char *szCache; - u32 total_size, bytes_done; - AC3Reader *read = (AC3Reader *) cbk; - - e = param->error; - /*done*/ - if (param->msg_type==GF_NETIO_DATA_TRANSFERED) { - if (read->stream) { - read->is_remote = GF_FALSE; - e = GF_EOS; - } else if (!read->needs_connection) { - return; - } - } else if (param->msg_type==GF_NETIO_PARSE_HEADER) { - if (!strcmp(param->name, "icy-name")) { - if (read->icy_name) gf_free(read->icy_name); - read->icy_name = gf_strdup(param->value); - } - if (!strcmp(param->name, "icy-genre")) { - if (read->icy_genre) gf_free(read->icy_genre); - read->icy_genre = gf_strdup(param->value); - } - if (!strcmp(param->name, "icy-meta")) { - GF_NetworkCommand com; - char *meta; - if (read->icy_track_name) gf_free(read->icy_track_name); - read->icy_track_name = NULL; - meta = param->value; - while (meta && meta[0]) { - char *sep = strchr(meta, ';'); - if (sep) sep[0] = 0; - - if (!strnicmp(meta, "StreamTitle=", 12)) { - read->icy_track_name = gf_strdup(meta+12); - } - if (!sep) break; - sep[0] = ';'; - meta = sep+1; - } - - com.base.command_type = GF_NET_SERVICE_INFO; - gf_service_command(read->service, &com, GF_OK); - } - return; - } else { - /*handle service message*/ - gf_service_download_update_stats(read->dnload); - if (param->msg_type!=GF_NETIO_DATA_EXCHANGE) return; - } - - /*data fetching or EOS*/ - if (e >= GF_OK) { - if (read->needs_connection) { - gf_dm_sess_get_stats(read->dnload, NULL, NULL, &total_size, NULL, NULL, NULL); - if (!total_size) read->is_live = GF_TRUE; - } - if (read->is_live) { - if (!e) AC3_OnLiveData(read, param->data, param->size); - return; - } - if (read->stream) return; - - /*open service*/ - szCache = gf_dm_sess_get_cache_name(read->dnload); - if (!szCache) e = GF_IO_ERR; - else { - read->stream = gf_fopen((char *) szCache, "rb"); - if (!read->stream) e = GF_SERVICE_ERROR; - else { - /*if full file at once (in cache) parse duration*/ - if (e==GF_EOS) read->is_remote = GF_FALSE; - e = GF_OK; - /*not enough data*/ - if (!AC3_ConfigureFromFile(read)) { - /*get amount downloaded and check*/ - gf_dm_sess_get_stats(read->dnload, NULL, NULL, NULL, &bytes_done, NULL, NULL); - if (bytes_done>10*1024) { - e = GF_CORRUPTED_DATA; - } else { - gf_fclose(read->stream); - read->stream = NULL; - return; - } - } - } - } - } - /*OK confirm*/ - if (read->needs_connection) { - read->needs_connection = GF_FALSE; - gf_service_connect_ack(read->service, NULL, e); - if (!e) AC3_SetupObject(read); - } -} - -void ac3_download_file(GF_InputService *plug, char *url) -{ - AC3Reader *read = (AC3Reader*) plug->priv; - - read->needs_connection = GF_TRUE; - - read->dnload = gf_service_download_new(read->service, url, 0, AC3_NetIO, read); - if (!read->dnload ) { - read->needs_connection = GF_FALSE; - gf_service_connect_ack(read->service, NULL, GF_NOT_SUPPORTED); - } else { - /*start our download (threaded)*/ - gf_dm_sess_process(read->dnload); - } - /*service confirm is done once fetched*/ -} - - -static GF_Err AC3_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url) -{ - char szURL[2048]; - char *ext; - GF_Err reply; - AC3Reader *read = (AC3Reader*)plug->priv; - read->service = serv; - - if (read->dnload) gf_service_download_del(read->dnload); - read->dnload = NULL; - - strcpy(szURL, url); - ext = strrchr(szURL, '#'); - if (ext) ext[0] = 0; - - /*remote fetch*/ - read->is_remote = !ac3_is_local(szURL); - if (read->is_remote) { - ac3_download_file(plug, (char *) szURL); - return GF_OK; - } - - reply = GF_OK; - read->stream = gf_fopen(szURL, "rb"); - if (!read->stream) { - reply = GF_URL_ERROR; - } else if (!AC3_ConfigureFromFile(read)) { - gf_fclose(read->stream); - read->stream = NULL; - reply = GF_NOT_SUPPORTED; - } - gf_service_connect_ack(serv, NULL, reply); - if (!reply && read->is_inline ) AC3_SetupObject(read); - return GF_OK; -} - -static GF_Err AC3_CloseService(GF_InputService *plug) -{ - AC3Reader *read = (AC3Reader*)plug->priv; - if (read->stream) gf_fclose(read->stream); - read->stream = NULL; - if (read->dnload) gf_service_download_del(read->dnload); - read->dnload = NULL; - - if (read->data) gf_free(read->data); - read->data = NULL; - gf_service_disconnect_ack(read->service, NULL, GF_OK); - return GF_OK; -} - -static GF_Descriptor *AC3_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) -{ - AC3Reader *read = (AC3Reader*)plug->priv; - /*since we don't handle multitrack in ac3, we don't need to check sub_url, only use expected type*/ - - /*override default*/ - if (expect_type==GF_MEDIA_OBJECT_UNDEF) expect_type=GF_MEDIA_OBJECT_AUDIO; - - /*audio object*/ - if (expect_type==GF_MEDIA_OBJECT_AUDIO) { - GF_ESD *esd; - GF_ObjectDescriptor *od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - od->objectDescriptorID = 1; - esd = AC3_GetESD(read); - esd->OCRESID = 0; - gf_list_add(od->ESDescriptors, esd); - return (GF_Descriptor *) od; - } - read->is_inline = 1; - /*inline scene: no specific service*/ - return NULL; -} - -static GF_Err AC3_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) -{ - u32 ES_ID=0; - GF_Err e; - AC3Reader *read = (AC3Reader*)plug->priv; - - e = GF_SERVICE_ERROR; - if (read->ch==channel) goto exit; - - e = GF_STREAM_NOT_FOUND; - if (strstr(url, "ES_ID")) { - sscanf(url, "ES_ID=%ud", &ES_ID); - } - /*URL setup*/ - else if (!read->ch && AC3_CanHandleURL(plug, url)) ES_ID = 1; - - if (ES_ID==1) { - read->ch = channel; - e = GF_OK; - } - -exit: - gf_service_connect_ack(read->service, channel, e); - return e; -} - -static GF_Err AC3_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) -{ - AC3Reader *read = (AC3Reader*)plug->priv; - - GF_Err e = GF_STREAM_NOT_FOUND; - if (read->ch == channel) { - read->ch = NULL; - if (read->data) gf_free(read->data); - read->data = NULL; - e = GF_OK; - } - gf_service_disconnect_ack(read->service, channel, e); - return GF_OK; -} - -static GF_Err AC3_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) -{ - AC3Reader *read = (AC3Reader*)plug->priv; - - if (com->base.command_type==GF_NET_SERVICE_INFO) { - com->info.name = read->icy_track_name ? read->icy_track_name : read->icy_name; - com->info.comment = read->icy_genre; - return GF_OK; - } - - if (!com->base.on_channel) { - /*if live session we may cache*/ - if (read->is_live && (com->command_type==GF_NET_IS_CACHABLE)) return GF_OK; - return GF_NOT_SUPPORTED; - } - switch (com->command_type) { - case GF_NET_CHAN_SET_PULL: - if ((read->ch == com->base.on_channel) && read->is_live) return GF_NOT_SUPPORTED; - return GF_OK; - case GF_NET_CHAN_INTERACTIVE: - if ((read->ch == com->base.on_channel) && read->is_live) return GF_NOT_SUPPORTED; - return GF_OK; - case GF_NET_CHAN_BUFFER: - if ((read->ch == com->base.on_channel) && read->is_live) { - if (com->buffer.max<1000) com->buffer.max = 1000; - com->buffer.min = com->buffer.max/2; - } - return GF_OK; - case GF_NET_CHAN_SET_PADDING: - read->pad_bytes = com->pad.padding_bytes; - return GF_OK; - case GF_NET_CHAN_DURATION: - com->duration.duration = read->duration; - com->duration.duration /= read->sample_rate; - return GF_OK; - case GF_NET_CHAN_PLAY: - read->start_range = com->play.start_range; - read->end_range = com->play.end_range; - read->current_time = 0; - if (read->stream) gf_fseek(read->stream, 0, SEEK_SET); - - if (read->ch == com->base.on_channel) { - read->done = GF_FALSE; - /*PLAY after complete download, estimate duration*/ - if (!read->is_remote && !read->duration) { - AC3_ConfigureFromFile(read); - if (read->duration) { - GF_NetworkCommand rcfg; - rcfg.base.on_channel = read->ch; - rcfg.base.command_type = GF_NET_CHAN_DURATION; - rcfg.duration.duration = read->duration; - rcfg.duration.duration /= read->sample_rate; - gf_service_command(read->service, &rcfg, GF_OK); - } - } - } - return GF_OK; - case GF_NET_CHAN_STOP: - return GF_OK; - default: - return GF_OK; - } -} - - -static GF_Err AC3_ChannelGetSLP(GF_InputService *plug, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, GF_SLHeader *out_sl_hdr, Bool *sl_compressed, GF_Err *out_reception_status, Bool *is_new_data) -{ - u64 pos, start_from; - Bool sync; - GF_BitStream *bs; - GF_AC3Header hdr; - AC3Reader *read = (AC3Reader*)plug->priv; - - *out_reception_status = GF_OK; - *sl_compressed = GF_FALSE; - *is_new_data = GF_FALSE; - memset(&hdr, 0, sizeof(GF_AC3Header)); - - memset(&read->sl_hdr, 0, sizeof(GF_SLHeader)); - read->sl_hdr.randomAccessPointFlag = 1; - read->sl_hdr.compositionTimeStampFlag = 1; - - if (read->ch != channel) return GF_STREAM_NOT_FOUND; - - /*fetching es data*/ - if (read->done) { - *out_reception_status = GF_EOS; - return GF_OK; - } - - if (!read->data) { - if (!read->stream) { - *out_data_ptr = NULL; - *out_data_size = 0; - return GF_OK; - } - bs = gf_bs_from_file(read->stream, GF_BITSTREAM_READ); - *is_new_data = GF_TRUE; - -fetch_next: - pos = gf_ftell(read->stream); - sync = gf_ac3_parser_bs(bs, &hdr, GF_FALSE); - if (!sync) { - gf_bs_del(bs); - if (!read->dnload) { - *out_reception_status = GF_EOS; - read->done = GF_TRUE; - } else { - gf_fseek(read->stream, pos, SEEK_SET); - *out_reception_status = GF_OK; - } - return GF_OK; - } - - if (!hdr.framesize) { - gf_bs_del(bs); - *out_reception_status = GF_EOS; - read->done = GF_TRUE; - return GF_OK; - } - read->data_size = hdr.framesize; - read->nb_samp = 1536; - /*we're seeking*/ - if (read->start_range && read->duration) { - start_from = (u32) (read->start_range * read->sample_rate); - if (read->current_time + read->nb_samp < start_from) { - read->current_time += read->nb_samp; - goto fetch_next; - } else { - read->start_range = 0; - } - } - - read->sl_hdr.compositionTimeStamp = read->current_time; - - read->data = (unsigned char*)gf_malloc(sizeof(char) * (read->data_size+read->pad_bytes)); - gf_bs_read_data(bs, (char *) read->data, read->data_size); - if (read->pad_bytes) memset(read->data + read->data_size, 0, sizeof(char) * read->pad_bytes); - gf_bs_del(bs); - } - *out_sl_hdr = read->sl_hdr; - *out_data_ptr =(char *) read->data; - *out_data_size = read->data_size; - return GF_OK; -} - -static GF_Err AC3_ChannelReleaseSLP(GF_InputService *plug, LPNETCHANNEL channel) -{ - AC3Reader *read = (AC3Reader*)plug->priv; - - if (read->ch == channel) { - if (!read->data) return GF_BAD_PARAM; - gf_free(read->data); - read->data = NULL; - read->current_time += read->nb_samp; - return GF_OK; - } - return GF_BAD_PARAM; -} - -GF_InputService *AC3_Load() -{ - AC3Reader *reader; - GF_InputService *plug = (GF_InputService*)gf_malloc(sizeof(GF_InputService)); - memset(plug, 0, sizeof(GF_InputService)); - GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC AC3 Reader", "gpac distribution") - - plug->RegisterMimeTypes = AC3_RegisterMimeTypes; - plug->CanHandleURL = AC3_CanHandleURL; - plug->ConnectService = AC3_ConnectService; - plug->CloseService = AC3_CloseService; - plug->GetServiceDescriptor = AC3_GetServiceDesc; - plug->ConnectChannel = AC3_ConnectChannel; - plug->DisconnectChannel = AC3_DisconnectChannel; - plug->ServiceCommand = AC3_ServiceCommand; - /*we do support pull mode*/ - plug->ChannelGetSLP = AC3_ChannelGetSLP; - plug->ChannelReleaseSLP = AC3_ChannelReleaseSLP; - - reader = (AC3Reader*)gf_malloc(sizeof(AC3Reader)); - memset(reader, 0, sizeof(AC3Reader)); - plug->priv = reader; - return plug; -} - -void AC3_Delete(void *ifce) -{ - GF_InputService *plug = (GF_InputService *) ifce; - AC3Reader *read = (AC3Reader*)plug->priv; - gf_free(read); - gf_free(plug); -} - -#endif /*GPAC_DISABLE_AV_PARSERS*/ - -#ifdef GPAC_HAS_LIBA52 -GF_BaseDecoder *NewAC3Dec(); -void DeleteAC3Dec(GF_BaseDecoder *ifcg); -#endif - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifndef GPAC_DISABLE_AV_PARSERS - GF_NET_CLIENT_INTERFACE, -#endif -#ifdef GPAC_HAS_LIBA52 - GF_MEDIA_DECODER_INTERFACE, -#endif - 0 - }; - - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ -#ifndef GPAC_DISABLE_AV_PARSERS - if (InterfaceType == GF_NET_CLIENT_INTERFACE) return (GF_BaseInterface *)AC3_Load(); -#endif -#ifdef GPAC_HAS_LIBA52 - if (InterfaceType == GF_MEDIA_DECODER_INTERFACE) return (GF_BaseInterface *)NewAC3Dec(); -#endif - return NULL; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#ifdef GPAC_HAS_LIBA52 - case GF_MEDIA_DECODER_INTERFACE: - DeleteAC3Dec((GF_BaseDecoder *)ifce); - break; -#endif -#ifndef GPAC_DISABLE_AV_PARSERS - case GF_NET_CLIENT_INTERFACE: - AC3_Delete(ifce); - break; -#endif - } -} - -GPAC_MODULE_STATIC_DECLARATION( ac3 ) diff --git a/modules/ac3_in/liba52_dec.c b/modules/ac3_in/liba52_dec.c deleted file mode 100644 index 9e4704e..0000000 --- a/modules/ac3_in/liba52_dec.c +++ /dev/null @@ -1,374 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / AAC reader module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifdef GPAC_HAS_LIBA52 - -#include <gpac/modules/codec.h> -#include <gpac/constants.h> - -#if !defined(__GNUC__) -# if defined(_WIN32_WCE) || defined (WIN32) -# pragma comment(lib, "liba52") -# endif -#endif - - -#ifndef uint32_t -#define uint32_t u32 -#endif -#ifndef uint8_t -#define uint8_t u8 -#endif - -#include <a52dec/mm_accel.h> -#include <a52dec/a52.h> - - -typedef struct -{ - a52_state_t *codec; - sample_t* samples; - - u32 sample_rate, num_samples, out_size, flags; - u8 num_channels; - /*no support for scalability in FAAD yet*/ - u16 ES_ID; - - char ch_reorder[16]; -} AC3Dec; - - -#define A52CTX() AC3Dec *ctx = (AC3Dec *) ifcg->privateStack - -static GF_Err AC3_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ - A52CTX(); - - if (ctx->ES_ID && ctx->ES_ID!=esd->ESID) return GF_NOT_SUPPORTED; - - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[A52] Attaching stream %d\n", esd->ESID)); - - if (ctx->codec) a52_free(ctx->codec); - ctx->codec = a52_init(MM_ACCEL_DJBFFT); - if (!ctx->codec) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[A52] Error initializing decoder\n")); - return GF_IO_ERR; - } - ctx->samples = a52_samples(ctx->codec); - if (!ctx->samples) { - a52_free(ctx->codec); - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[A52] Error initializing decoder\n")); - return GF_IO_ERR; - } - - ctx->num_channels = 0; - ctx->sample_rate = 0; - ctx->out_size = 0; - ctx->num_samples = 1536; - ctx->ES_ID = esd->ESID; - return GF_OK; -} - -static GF_Err AC3_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - A52CTX(); - if (ES_ID != ctx->ES_ID) return GF_BAD_PARAM; - if (ctx->codec) a52_free(ctx->codec); - ctx->codec = NULL; - ctx->ES_ID = 0; - ctx->sample_rate = ctx->out_size = ctx->num_samples = 0; - ctx->num_channels = 0; - return GF_OK; -} -static GF_Err AC3_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - A52CTX(); - switch (capability->CapCode) { - /*not tested yet*/ - case GF_CODEC_RESILIENT: - capability->cap.valueInt = 1; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = ctx->out_size; - break; - case GF_CODEC_SAMPLERATE: - capability->cap.valueInt = ctx->sample_rate; - break; - case GF_CODEC_NB_CHAN: - capability->cap.valueInt = ctx->num_channels; - break; - case GF_CODEC_BITS_PER_SAMPLE: - capability->cap.valueInt = 16; - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = 4; - break; - case GF_CODEC_BUFFER_MAX: - capability->cap.valueInt = 12; - break; - /*by default AAC access unit lasts num_samples (timescale being sampleRate)*/ - case GF_CODEC_CU_DURATION: - capability->cap.valueInt = ctx->num_samples; - break; - /*to refine, it seems that 4 bytes padding is not enough on all streams ?*/ - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 8; - break; - case GF_CODEC_CHANNEL_CONFIG: - capability->cap.valueInt = 0; - switch (ctx->flags & A52_CHANNEL_MASK) { - case A52_CHANNEL1: - case A52_CHANNEL2: - case A52_MONO: - capability->cap.valueInt = GF_AUDIO_CH_FRONT_CENTER; - break; - case A52_CHANNEL: - case A52_STEREO: - capability->cap.valueInt = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT; - break; - case A52_DOLBY: - break; - case A52_3F: - capability->cap.valueInt = GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT; - break; - case A52_2F1R: - capability->cap.valueInt = GF_AUDIO_CH_BACK_CENTER | GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT; - break; - case A52_3F1R: - capability->cap.valueInt = GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_BACK_CENTER; - break; - case A52_2F2R: - capability->cap.valueInt = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_BACK_LEFT | GF_AUDIO_CH_BACK_RIGHT; - break; - case A52_3F2R: - capability->cap.valueInt = GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_BACK_LEFT | GF_AUDIO_CH_BACK_RIGHT; - break; - } - if (ctx->flags & A52_LFE) - capability->cap.valueInt |= GF_AUDIO_CH_LFE; - break; - default: - capability->cap.valueInt = 0; - break; - } - return GF_OK; -} -static GF_Err AC3_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - /*return unsupported to avoid confusion by the player (like SR changing ...) */ - return GF_NOT_SUPPORTED; -} - -#if 0 -static s8 AC3_GetChannelPos(AC3Dec *ffd, u32 ch_cfg) -{ - u32 i; - for (i=0; i<ffd->info.channels; i++) { - switch (ffd->info.channel_position[i]) { - case FRONT_CHANNEL_CENTER: - if (ch_cfg==GF_AUDIO_CH_FRONT_CENTER) return i; - break; - case FRONT_CHANNEL_LEFT: - if (ch_cfg==GF_AUDIO_CH_FRONT_LEFT) return i; - break; - case FRONT_CHANNEL_RIGHT: - if (ch_cfg==GF_AUDIO_CH_FRONT_RIGHT) return i; - break; - case SIDE_CHANNEL_LEFT: - if (ch_cfg==GF_AUDIO_CH_SIDE_LEFT) return i; - break; - case SIDE_CHANNEL_RIGHT: - if (ch_cfg==GF_AUDIO_CH_SIDE_RIGHT) return i; - break; - case BACK_CHANNEL_LEFT: - if (ch_cfg==GF_AUDIO_CH_BACK_LEFT) return i; - break; - case BACK_CHANNEL_RIGHT: - if (ch_cfg==GF_AUDIO_CH_BACK_RIGHT) return i; - break; - case BACK_CHANNEL_CENTER: - if (ch_cfg==GF_AUDIO_CH_BACK_CENTER) return i; - break; - case LFE_CHANNEL: - if (ch_cfg==GF_AUDIO_CH_LFE) return i; - break; - } - } - return -1; -} -#endif - -/**** the following two functions comes from a52dec */ -static GFINLINE s32 blah (s32 i) -{ - if (i > 0x43c07fff) - return 32767; - else if (i < 0x43bf8000) - return -32768; - return i - 0x43c00000; -} - -static GFINLINE void float_to_int (float * _f, s16 *samps, int nchannels) -{ - int i, j, c; - s32 * f = (s32 *) _f; // XXX assumes IEEE float format - - j = 0; - nchannels *= 256; - for (i = 0; i < 256; i++) { - for (c = 0; c < nchannels; c += 256) - samps[j++] = blah (f[i + c]); - } -} - -/**** end */ - -static const int ac3_channels[8] = { - 2, 1, 2, 3, 3, 4, 4, 5 -}; - -static GF_Err AC3_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ - short *out_samples; - int i, len, bit_rate; - sample_t level; - A52CTX(); - - /*check not using scalabilty*/ - if (ctx->ES_ID != ES_ID) return GF_BAD_PARAM; - - /*if late or seeking don't decode*/ - switch (mmlevel) { - case GF_CODEC_LEVEL_SEEK: - case GF_CODEC_LEVEL_DROP: - *outBufferLength = 0; - return GF_OK; - default: - break; - } - - if (ctx->out_size > *outBufferLength) { - *outBufferLength = ctx->out_size; - return GF_BUFFER_TOO_SMALL; - } - - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[A52] Decoding AU\n")); - - len = a52_syncinfo(inBuffer, &ctx->flags, &ctx->sample_rate, &bit_rate); - if (!len) return GF_NON_COMPLIANT_BITSTREAM; - - /*init decoder*/ - if (!ctx->out_size) { - ctx->num_channels = ac3_channels[ctx->flags & 7]; - if (ctx->flags & A52_LFE) ctx->num_channels++; - ctx->flags |= A52_ADJUST_LEVEL; - - ctx->out_size = ctx->num_channels * sizeof(short) * 1536; - *outBufferLength = ctx->out_size; - return GF_BUFFER_TOO_SMALL; - } - - level = 1; - if ( a52_frame(ctx->codec, inBuffer, &ctx->flags, &level, 384)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[A52] Error decoding AU\n" )); - *outBufferLength = 0; - return GF_NON_COMPLIANT_BITSTREAM; - } - - out_samples = (short*)outBuffer; - for (i=0; i<6; i++) { - if (a52_block(ctx->codec)) - return GF_NON_COMPLIANT_BITSTREAM; - - float_to_int(ctx->samples, out_samples + i * 256 * ctx->num_channels, ctx->num_channels); - } - - *outBufferLength = 6 * ctx->num_channels * 256 * sizeof(short); - - return GF_OK; -} - -static const char *AC3_GetCodecName(GF_BaseDecoder *ifcg) -{ - return "LIBA52 AC3 Decoder"; -} - -static u32 AC3_CanHandleStream(GF_BaseDecoder *dec, u32 StreamType, GF_ESD *esd, u8 PL) -{ - /*audio decs*/ - if (StreamType != GF_STREAM_AUDIO) return GF_CODEC_NOT_SUPPORTED; - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - switch (esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_AUDIO_AC3: - return GF_CODEC_SUPPORTED; - } - return GF_CODEC_NOT_SUPPORTED; -} - -GF_BaseDecoder *NewAC3Dec() -{ - GF_MediaDecoder *ifce; - AC3Dec *dec; - - GF_SAFEALLOC(ifce, GF_MediaDecoder); - GF_SAFEALLOC(dec, AC3Dec); - GF_REGISTER_MODULE_INTERFACE(ifce, GF_MEDIA_DECODER_INTERFACE, "LIBA52 AC3 Decoder", "gpac distribution") - - ifce->privateStack = dec; - - /*setup our own interface*/ - ifce->AttachStream = AC3_AttachStream; - ifce->DetachStream = AC3_DetachStream; - ifce->GetCapabilities = AC3_GetCapabilities; - ifce->SetCapabilities = AC3_SetCapabilities; - ifce->ProcessData = AC3_ProcessData; - ifce->CanHandleStream = AC3_CanHandleStream; - ifce->GetName = AC3_GetCodecName; - return (GF_BaseDecoder *) ifce; -} - -void DeleteAC3Dec(GF_BaseDecoder *ifcg) -{ - AC3Dec *ctx; - if (!ifcg) - return; - ctx = (AC3Dec *) ifcg->privateStack; - if (ctx) { - if (ctx->codec) - a52_free(ctx->codec); - ctx->codec = NULL; - gf_free(ctx); - ifcg->privateStack = NULL; - } - gf_free(ifcg); -} - - -#endif diff --git a/modules/alsa/Makefile b/modules/alsa/Makefile index 22d7554..e91aace 100644 --- a/modules/alsa/Makefile +++ b/modules/alsa/Makefile @@ -2,15 +2,15 @@ include ../../config.mak vpath %.c $(SRC_PATH)/modules/alsa -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" $(OSS_CFLAGS) +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) $(OSS_CFLAGS) LDFLAGS+=$(OSS_LDFLAGS) -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/modules/alsa/alsa.c b/modules/alsa/alsa.c index e8cc51e..9f8a1eb 100644 --- a/modules/alsa/alsa.c +++ b/modules/alsa/alsa.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / alsa audio output module @@ -45,16 +45,14 @@ typedef struct static GF_Err ALSA_Setup(GF_AudioOutput*dr, void *os_handle, u32 num_buffers, u32 total_duration) { int err; - const char *opt; ALSAContext *ctx = (ALSAContext*)dr->opaque; - opt = gf_modules_get_option((GF_BaseInterface *)dr, "ALSA", "ForceSampleRate"); - if (opt) ctx->force_sr = atoi(opt); - ctx->dev_name = gf_modules_get_option((GF_BaseInterface *)dr, "ALSA", "DeviceName"); + ctx->force_sr = gf_opts_get_int("core", "force-alsarate"); + ctx->dev_name = gf_opts_get_key("core", "alsa-devname"); if (!ctx->dev_name) { ctx->dev_name = "hw:0,0"; - gf_modules_set_option((GF_BaseInterface *)dr, "ALSA", "DeviceName", ctx->dev_name); + gf_opts_set_key("core", "alsa-devname", ctx->dev_name); } /*test device*/ @@ -80,7 +78,7 @@ static void ALSA_Shutdown(GF_AudioOutput*dr) ctx->wav_buf = NULL; } -static GF_Err ALSA_ConfigureOutput(GF_AudioOutput*dr, u32 *SampleRate, u32 *NbChannels, u32 *nbBitsPerSample, u32 channel_cfg) +static GF_Err ALSA_Configure(GF_AudioOutput*dr, u32 *SampleRate, u32 *NbChannels, u32 *audioFormat, u64 channel_cfg) { snd_pcm_hw_params_t *hw_params = NULL; int err; @@ -122,12 +120,20 @@ static GF_Err ALSA_ConfigureOutput(GF_AudioOutput*dr, u32 *SampleRate, u32 *NbCh /*set output format*/ ctx->nb_ch = (int) (*NbChannels); ctx->block_align = ctx->nb_ch; - if ((*nbBitsPerSample) == 16) { + + //only support for PCM 8/16/24/32 packet mode + switch (*audioFormat) { + case GF_AUDIO_FMT_U8: + err = snd_pcm_hw_params_set_format(ctx->playback_handle, hw_params, SND_PCM_FORMAT_U8); + break; + default: + *audioFormat = GF_AUDIO_FMT_S16; + case GF_AUDIO_FMT_S16: ctx->block_align *= 2; err = snd_pcm_hw_params_set_format(ctx->playback_handle, hw_params, SND_PCM_FORMAT_S16_LE); - } else { - err = snd_pcm_hw_params_set_format(ctx->playback_handle, hw_params, SND_PCM_FORMAT_U8); + break; } + if (err < 0) { GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[ALSA] Cannot set sample format: %s\n", snd_strerror (err)) ); goto err_exit; @@ -256,18 +262,6 @@ static void ALSA_WriteAudio(GF_AudioOutput*dr) } } -static void ALSA_SetVolume(GF_AudioOutput*dr, u32 Volume) -{ -} - -static void ALSA_SetPan(GF_AudioOutput*dr, u32 Pan) -{ -} - -static void ALSA_SetPriority(GF_AudioOutput*dr, u32 Priority) -{ -} - static u32 ALSA_GetAudioDelay(GF_AudioOutput*dr) { ALSAContext *ctx = (ALSAContext*)dr->opaque; @@ -327,11 +321,8 @@ void *NewALSAOutput() driv->SelfThreaded = 0; driv->Setup = ALSA_Setup; driv->Shutdown = ALSA_Shutdown; - driv->ConfigureOutput = ALSA_ConfigureOutput; + driv->Configure = ALSA_Configure; driv->GetAudioDelay = ALSA_GetAudioDelay; - driv->SetVolume = ALSA_SetVolume; - driv->SetPan = ALSA_SetPan; - driv->SetPriority = ALSA_SetPriority; driv->QueryOutputSampleRate = ALSA_QueryOutputSampleRate; driv->WriteAudio = ALSA_WriteAudio; GF_REGISTER_MODULE_INTERFACE(driv, GF_AUDIO_OUTPUT_INTERFACE, "ALSA Audio Output", "gpac distribution"); diff --git a/modules/amr_dec/Makefile b/modules/amr_dec/Makefile deleted file mode 100644 index a2ad1b1..0000000 --- a/modules/amr_dec/Makefile +++ /dev/null @@ -1,80 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/amr_dec - -#note: __MSDOS__ defined is the same cfg as linux. There may be some pbs on other platforms, to check... -CFLAGS= $(OPTFLAGS) -w -I"$(SRC_PATH)/include" -D__MSDOS__ -DMMS_IO -DWMOPS=0 -DVAD1 - - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -DDEBUG -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS=amr_in.o -NAME=amr_in - -ifeq ($(CONFIG_AMR_NB), yes) -NAME=amr_dec -OBJS+= amr_dec.o \ - ./amr_nb/agc.o ./amr_nb/autocorr.o ./amr_nb/az_lsp.o ./amr_nb/bits2prm.o ./amr_nb/cl_ltp.o \ - ./amr_nb/convolve.o ./amr_nb/c1035pf.o ./amr_nb/d_plsf.o ./amr_nb/d_plsf_5.o ./amr_nb/d_gain_c.o \ - ./amr_nb/d_gain_p.o ./amr_nb/dec_lag6.o ./amr_nb/d1035pf.o ./amr_nb/cor_h.o ./amr_nb/enc_lag3.o \ - ./amr_nb/enc_lag6.o ./amr_nb/g_code.o ./amr_nb/g_pitch.o ./amr_nb/int_lpc.o ./amr_nb/inter_36.o \ - ./amr_nb/inv_sqrt.o ./amr_nb/lag_wind.o ./amr_nb/levinson.o ./amr_nb/lsp_az.o ./amr_nb/lsp_lsf.o \ - ./amr_nb/ol_ltp.o ./amr_nb/pitch_fr.o ./amr_nb/pitch_ol.o ./amr_nb/pow2.o ./amr_nb/pre_big.o \ - ./amr_nb/pre_proc.o ./amr_nb/pred_lt.o ./amr_nb/preemph.o ./amr_nb/prm2bits.o ./amr_nb/pstfilt.o \ - ./amr_nb/q_gain_c.o ./amr_nb/q_gain_p.o ./amr_nb/q_plsf.o ./amr_nb/q_plsf_5.o ./amr_nb/lsfwt.o \ - ./amr_nb/reorder.o ./amr_nb/residu.o ./amr_nb/lsp.o ./amr_nb/lpc.o ./amr_nb/ec_gains.o \ - ./amr_nb/spreproc.o ./amr_nb/syn_filt.o ./amr_nb/weight_a.o ./amr_nb/qua_gain.o \ - ./amr_nb/gc_pred.o ./amr_nb/q_plsf_3.o ./amr_nb/post_pro.o ./amr_nb/dec_lag3.o ./amr_nb/dec_gain.o \ - ./amr_nb/d_plsf_3.o ./amr_nb/d4_17pf.o ./amr_nb/c4_17pf.o ./amr_nb/d3_14pf.o ./amr_nb/c3_14pf.o \ - ./amr_nb/d2_11pf.o ./amr_nb/c2_11pf.o ./amr_nb/d2_9pf.o ./amr_nb/c2_9pf.o ./amr_nb/cbsearch.o \ - ./amr_nb/spstproc.o ./amr_nb/gain_q.o ./amr_nb/cod_amr.o ./amr_nb/dec_amr.o ./amr_nb/sp_enc.o \ - ./amr_nb/sp_dec.o ./amr_nb/ph_disp.o ./amr_nb/g_adapt.o ./amr_nb/calc_en.o ./amr_nb/qgain795.o \ - ./amr_nb/qgain475.o ./amr_nb/sqrt_l.o ./amr_nb/set_sign.o ./amr_nb/s10_8pf.o ./amr_nb/bgnscd.o \ - ./amr_nb/gmed_n.o ./amr_nb/mac_32.o ./amr_nb/ex_ctrl.o ./amr_nb/c_g_aver.o ./amr_nb/lsp_avg.o \ - ./amr_nb/int_lsf.o ./amr_nb/c8_31pf.o ./amr_nb/d8_31pf.o ./amr_nb/p_ol_wgh.o ./amr_nb/ton_stab.o \ - ./amr_nb/vad1.o ./amr_nb/dtx_enc.o ./amr_nb/dtx_dec.o ./amr_nb/a_refl.o ./amr_nb/b_cn_cod.o \ - ./amr_nb/calc_cor.o ./amr_nb/hp_max.o ./amr_nb/vadname.o ./amr_nb/vad2.o ./amr_nb/r_fft.o \ - ./amr_nb/lflg_upd.o ./amr_nb/e_homing.o ./amr_nb/d_homing.o ./amr_nb/basicop2.o \ - ./amr_nb/count.o ./amr_nb/oper_32b.o ./amr_nb/copy.o ./amr_nb/log2.o \ - ./amr_nb/set_zero.o ./amr_nb/strfunc.o ./amr_nb/n_proc.o ./amr_nb/sid_sync.o - -else - -CFLAGS+=-DGPAC_AMR_IN_STANDALONE - -endif - - -SRCS := $(OBJS:.o=.c) - -LIB=gm_$(NAME)$(DYN_LIB_SUFFIX) - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) $(CFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) - - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/amr_dec/amr_dec.c b/modules/amr_dec/amr_dec.c deleted file mode 100644 index 98c760c..0000000 --- a/modules/amr_dec/amr_dec.c +++ /dev/null @@ -1,342 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / AMR decoder module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -/*include AMR stuff*/ -//#include "amr_nb/sp_dec.h" -//#include "amr_nb/d_homing.h" -/*remove AMR types to avoid any typedef warning/error*/ -#undef Float -#undef Bool - -/*decoder Interface*/ -#include <gpac/modules/codec.h> -#include <gpac/modules/service.h> -#include <gpac/constants.h> - -#ifdef GPAC_HAS_AMR_NB_3GPP - -/*default size in CU of composition memory for audio*/ -#define DEFAULT_AUDIO_CM_SIZE 12 -/*default critical size in CU of composition memory for audio*/ -#define DEFAULT_AUDIO_CM_TRIGGER 4 - -/*our own headers for AMR NB*/ -#if (defined(WIN32) || defined(_WIN32_WCE)) -#include "amr_nb_api.h" -#if !defined(__GNUC__) -#pragma comment(lib, "libamrnb") -#endif -#else -#include "amr_nb/sp_dec.h" -#include "amr_nb/d_homing.h" -#endif - -typedef struct -{ - u32 out_size; - /*AMR NB state vars*/ - __Speech_Decode_FrameState * speech_decoder_state; - enum RXFrameType rx_type; - enum Mode mode; - - s16 reset_flag, reset_flag_old; - -} AMRDec; - -#define AMRCTX() AMRDec *ctx = (AMRDec *) ifcg->privateStack - - -static GF_Err AMR_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ - u32 packed_size; - AMRCTX(); - if (esd->dependsOnESID || !esd->decoderConfig->decoderSpecificInfo) return GF_NOT_SUPPORTED; - - if (strnicmp(esd->decoderConfig->decoderSpecificInfo->data, "samr", 4) - && strnicmp(esd->decoderConfig->decoderSpecificInfo->data, "amr ", 4)) return GF_NOT_SUPPORTED; - - ctx->reset_flag = 0; - ctx->reset_flag_old = 1; - ctx->mode = 0; - ctx->rx_type = 0; - ctx->speech_decoder_state = NULL; - if (Speech_Decode_Frame_init(&ctx->speech_decoder_state, "Decoder")) return GF_IO_ERR; - - packed_size = (u32) (esd->decoderConfig->decoderSpecificInfo->dataLength>14) ? esd->decoderConfig->decoderSpecificInfo->data[13] : 0; - /*max possible frames in a sample are seen in MP4, that's 15*/ - if (!packed_size) packed_size = 15; - - ctx->out_size = packed_size * 2 * 160; - return GF_OK; -} - -static GF_Err AMR_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - AMRCTX(); - Speech_Decode_Frame_exit(&ctx->speech_decoder_state); - ctx->speech_decoder_state = NULL; - return GF_OK; -} -static GF_Err AMR_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - AMRCTX(); - switch (capability->CapCode) { - /*not tested yet*/ - case GF_CODEC_RESILIENT: - capability->cap.valueInt = 1; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = ctx->out_size; - break; - case GF_CODEC_SAMPLERATE: - capability->cap.valueInt = 8000; - break; - case GF_CODEC_NB_CHAN: - capability->cap.valueInt = 1; - break; - case GF_CODEC_BITS_PER_SAMPLE: - capability->cap.valueInt = 16; - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = DEFAULT_AUDIO_CM_TRIGGER; - break; - case GF_CODEC_BUFFER_MAX: - capability->cap.valueInt = DEFAULT_AUDIO_CM_SIZE; - break; - case GF_CODEC_CU_DURATION: - capability->cap.valueInt = 160; - break; - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 4; - break; - case GF_CODEC_CHANNEL_CONFIG: - capability->cap.valueInt = GF_AUDIO_CH_FRONT_CENTER; - break; - default: - capability->cap.valueInt = 0; - break; - } - return GF_OK; -} - -static GF_Err AMR_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - /*return unsupported to avoid confusion by the player (like SR changing ...) */ - return GF_NOT_SUPPORTED; -} - - -/* frame size in serial bitstream file (frame type + serial stream + flags) */ -#define SERIAL_FRAMESIZE (270) - -static GF_Err AMR_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ - u32 offset = 0; - u8 toc, q, ft; - s16 *synth; - u8 *packed_bits; - s16 serial[SERIAL_FRAMESIZE]; - s32 i; - AMRCTX(); - - /*if late or seeking don't decode (each frame is a RAP)*/ - /* switch (mmlevel) { - case GF_CODEC_LEVEL_SEEK: - case GF_CODEC_LEVEL_DROP: - *outBufferLength = 0; - return GF_OK; - default: - break; - } - */ - if (ctx->out_size > *outBufferLength) { - *outBufferLength = ctx->out_size; - return GF_BUFFER_TOO_SMALL; - } - - synth = (s16 *) outBuffer; - *outBufferLength = 0; - - while (inBufferLength) { - toc = inBuffer[0]; - /* read rest of the frame based on ToC byte */ - q = (toc >> 2) & 0x01; - ft = (toc >> 3) & 0x0F; - - packed_bits = inBuffer + 1; - offset = 1 + GF_AMR_FRAME_SIZE[ft]; - - /*Unsort and unpack bits*/ - ctx->rx_type = UnpackBits(q, ft, packed_bits, &ctx->mode, &serial[1]); - - if (ctx->rx_type == RX_NO_DATA) { - ctx->mode = ctx->speech_decoder_state->prev_mode; - } else { - ctx->speech_decoder_state->prev_mode = ctx->mode; - } - - /* if homed: check if this frame is another homing frame */ - if (ctx->reset_flag_old == 1) { - /* only check until end of first subframe */ - ctx->reset_flag = decoder_homing_frame_test_first(&serial[1], ctx->mode); - } - /* produce encoder homing frame if homed & input=decoder homing frame */ - if ((ctx->reset_flag != 0) && (ctx->reset_flag_old != 0)) { - for (i = 0; i < L_FRAME; i++) { - synth[i] = EHF_MASK; - } - } else { - /* decode frame */ - Speech_Decode_Frame(ctx->speech_decoder_state, ctx->mode, &serial[1], ctx->rx_type, synth); - } - - *outBufferLength += 160*2; - synth += 160; - - /* if not homed: check whether current frame is a homing frame */ - if (ctx->reset_flag_old == 0) { - ctx->reset_flag = decoder_homing_frame_test(&serial[1], ctx->mode); - } - /* reset decoder if current frame is a homing frame */ - if (ctx->reset_flag != 0) { - Speech_Decode_Frame_reset(&ctx->speech_decoder_state); - } - ctx->reset_flag_old = ctx->reset_flag; - - if (inBufferLength < offset) return GF_OK; - inBufferLength -= offset; - inBuffer += offset; - - if (*outBufferLength > ctx->out_size) return GF_NON_COMPLIANT_BITSTREAM; - } - return GF_OK; -} - - -static u32 AMR_CanHandleStream(GF_BaseDecoder *dec, u32 StreamType, GF_ESD *esd, u8 PL) -{ - char *dsi; - /*we handle audio only*/ - if (StreamType!=GF_STREAM_AUDIO) return GF_CODEC_NOT_SUPPORTED; - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - - if (esd->decoderConfig->objectTypeIndication != GPAC_OTI_MEDIA_GENERIC) return GF_CODEC_NOT_SUPPORTED; - dsi = esd->decoderConfig->decoderSpecificInfo ? esd->decoderConfig->decoderSpecificInfo->data : NULL; - if (!dsi) return GF_CODEC_NOT_SUPPORTED; - if (esd->decoderConfig->decoderSpecificInfo->dataLength < 4) return GF_CODEC_NOT_SUPPORTED; - - if (!strnicmp(dsi, "samr", 4) || !strnicmp(dsi, "amr ", 4)) return GF_CODEC_SUPPORTED; - return GF_CODEC_NOT_SUPPORTED; -} - - -static const char *AMR_GetCodecName(GF_BaseDecoder *dec) -{ - return "3GPP Fixed-Point AMR NB"; -} - -GF_MediaDecoder *NewAMRDecoder() -{ - AMRDec *dec; - GF_MediaDecoder *ifce; - GF_SAFEALLOC(ifce , GF_MediaDecoder); - dec = (AMRDec*)gf_malloc(sizeof(AMRDec)); - memset(dec, 0, sizeof(AMRDec)); - ifce->privateStack = dec; - ifce->CanHandleStream = AMR_CanHandleStream; - - /*setup our own interface*/ - ifce->AttachStream = AMR_AttachStream; - ifce->DetachStream = AMR_DetachStream; - ifce->GetCapabilities = AMR_GetCapabilities; - ifce->SetCapabilities = AMR_SetCapabilities; - ifce->ProcessData = AMR_ProcessData; - ifce->GetName = AMR_GetCodecName; - - GF_REGISTER_MODULE_INTERFACE(ifce, GF_MEDIA_DECODER_INTERFACE, "AMR-NB 3GPP decoder", "gpac distribution"); - - return ifce; -} - -void DeleteAMRDecoder(GF_BaseDecoder *ifcg) -{ - AMRCTX(); - gf_free(ctx); - gf_free(ifcg); -} - -#endif // GPAC_HAS_AMR_NB_3GPP - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { - GF_NET_CLIENT_INTERFACE, - GF_MEDIA_DECODER_INTERFACE, - 0 - }; - return si; -} - -GF_InputService *NewAESReader(); -void DeleteAESReader(void *ifce); - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - switch (InterfaceType) { -#ifdef GPAC_HAS_AMR_NB_3GPP - case GF_MEDIA_DECODER_INTERFACE: - return (GF_BaseInterface *) NewAMRDecoder(); -#endif - case GF_NET_CLIENT_INTERFACE: - return (GF_BaseInterface *) NewAESReader(); - default: - return NULL; - } -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#ifdef GPAC_HAS_AMR_NB_3GPP - case GF_MEDIA_DECODER_INTERFACE: - DeleteAMRDecoder((GF_BaseDecoder *)ifce); - break; -#endif - case GF_NET_CLIENT_INTERFACE: - DeleteAESReader(ifce); - break; - } -} - -GPAC_MODULE_STATIC_DECLARATION( amr_dec ) diff --git a/modules/amr_dec/amr_in.c b/modules/amr_dec/amr_in.c deleted file mode 100644 index f22811f..0000000 --- a/modules/amr_dec/amr_in.c +++ /dev/null @@ -1,620 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / AMR&EVRC&SMV reader module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include <gpac/modules/service.h> -#include <gpac/modules/codec.h> -#include <gpac/constants.h> -#include <gpac/isomedia.h> - - -enum -{ - TYPE_AMR = GF_4CC('s','a','m','r'), - TYPE_AMR_WB = GF_4CC('s','a','w','b'), - TYPE_EVRC = GF_4CC('e','v','r','c'), - TYPE_SMV = GF_4CC('s','m','v',' ') -}; - -typedef struct -{ - GF_ClientService *service; - - Bool is_remote; - u32 start_offset; - u32 mtype, sample_rate, block_size; - - FILE *stream; - u32 duration; - - Bool needs_connection; - u32 pad_bytes; - Bool done; - u32 is_inline; - LPNETCHANNEL ch; - - unsigned char *data; - u32 data_size; - GF_SLHeader sl_hdr; - - Double start_range, end_range; - u32 current_time; - /*file downloader*/ - GF_DownloadSession * dnload; - - //Bool is_live; -} AMR_Reader; - -static u32 AMR_RegisterMimeTypes(const GF_InputService *plug) -{ - gf_service_register_mime(plug, "audio/amr", "amr awb", "AMR Speech Data"); - gf_service_register_mime(plug, "audio/evrc", "evc", "EVRC Speech Data"); - gf_service_register_mime(plug, "audio/smv", "smv", "SMV Speech Data"); - return 3; -} - -static GF_ESD *AMR_GetESD(AMR_Reader *read) -{ - GF_BitStream *dsi; - GF_ESD *esd; - esd = gf_odf_desc_esd_new(0); - esd->decoderConfig->streamType = GF_STREAM_AUDIO; - esd->ESID = 1; - esd->OCRESID = 0; - esd->slConfig->timestampResolution = read->sample_rate; - /*all packets are complete AUs*/ - esd->slConfig->useAccessUnitEndFlag = esd->slConfig->useAccessUnitStartFlag = 0; - esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1; - - if ((read->mtype==TYPE_AMR) || (read->mtype==TYPE_AMR_WB)) { - esd->decoderConfig->objectTypeIndication = GPAC_OTI_MEDIA_GENERIC; - dsi = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - gf_bs_write_u32(dsi, read->mtype); - gf_bs_write_u32(dsi, (read->mtype==TYPE_AMR) ? 8000 : 16000); - gf_bs_write_u16(dsi, 1); - gf_bs_write_u16(dsi, (read->mtype==TYPE_AMR) ? 160 : 320); - gf_bs_write_u8(dsi, 16); - gf_bs_write_u8(dsi, 1); - gf_bs_get_content(dsi, & esd->decoderConfig->decoderSpecificInfo->data, & esd->decoderConfig->decoderSpecificInfo->dataLength); - gf_bs_del(dsi); - } - else if (read->mtype==TYPE_EVRC) esd->decoderConfig->objectTypeIndication = GPAC_OTI_AUDIO_EVRC_VOICE; - else if (read->mtype==TYPE_SMV) esd->decoderConfig->objectTypeIndication = GPAC_OTI_AUDIO_SMV_VOICE; - return esd; -} - -static void AMR_SetupObject(AMR_Reader *read) -{ - GF_ESD *esd; - GF_ObjectDescriptor *od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - od->objectDescriptorID = 1; - esd = AMR_GetESD(read); - esd->OCRESID = 0; - gf_list_add(od->ESDescriptors, esd); - gf_service_declare_media(read->service, (GF_Descriptor*)od, GF_FALSE); -} - -static Bool AMR_CanHandleURL(GF_InputService *plug, const char *url) -{ - char *sExt; - sExt = strrchr(url, '.'); - if (!sExt) return GF_FALSE; - if (gf_service_check_mime_register(plug, "audio/amr", "amr awb", "AMR Speech Data", sExt)) return GF_TRUE; - if (gf_service_check_mime_register(plug, "audio/evrc", "evc", "EVRC Speech Data", sExt)) return GF_TRUE; - if (gf_service_check_mime_register(plug, "audio/smv", "smv", "SMV Speech Data", sExt)) return GF_TRUE; - return GF_FALSE; -} - -static Bool file_is_local(const char *url) -{ - if (!strnicmp(url, "file://", 7)) return GF_TRUE; - if (strstr(url, "://")) return GF_FALSE; - return GF_TRUE; -} - - -static Bool AMR_ConfigureFromFile(AMR_Reader *read) -{ - u32 i; - char magic[20]; - - if (!read->stream) return GF_FALSE; - read->mtype = 0; - read->start_offset = 6; - read->sample_rate = 8000; - read->block_size = 160; - i = (u32) fread(magic, 1, 20, read->stream); - if (i != 20) return GF_FALSE; - - if (!strnicmp(magic, "#!AMR\n", 6)) { - fseek(read->stream, 6, SEEK_SET); - read->mtype = TYPE_AMR; - } - else if (!strnicmp(magic, "#!EVRC\n", 7)) { - fseek(read->stream, 7, SEEK_SET); - read->start_offset = 7; - read->mtype = TYPE_EVRC; - } - else if (!strnicmp(magic, "#!SMV\n", 6)) { - fseek(read->stream, 6, SEEK_SET); - read->mtype = TYPE_SMV; - } - else if (!strnicmp(magic, "#!AMR-WB\n", 9)) { - read->mtype = TYPE_AMR_WB; - read->start_offset = 9; - read->sample_rate = 16000; - read->block_size = 320; - fseek(read->stream, 9, SEEK_SET); - } - else if (!strnicmp(magic, "#!AMR_MC1.0\n", 12)) return GF_FALSE; - else if (!strnicmp(magic, "#!AMR-WB_MC1.0\n", 15)) return GF_FALSE; - else return GF_FALSE; - - - read->duration = 0; - - if (!read->is_remote) { - u32 size=0; - while (!feof(read->stream)) { - u8 ft = fgetc(read->stream); - switch (read->mtype) { - case TYPE_AMR: - case TYPE_AMR_WB: - ft = (ft >> 3) & 0x0F; - size = (read->mtype==TYPE_AMR_WB) ? GF_AMR_WB_FRAME_SIZE[ft] : GF_AMR_FRAME_SIZE[ft]; - break; - default: - for (i=0; i<GF_SMV_EVRC_RATE_TO_SIZE_NB; i++) { - if (GF_SMV_EVRC_RATE_TO_SIZE[2*i]==ft) { - /*remove rate_type byte*/ - size = GF_SMV_EVRC_RATE_TO_SIZE[2*i+1] - 1; - break; - } - } - break; - } - - if (size) fseek(read->stream, size, SEEK_CUR); - read->duration += read->block_size; - } - } - fseek(read->stream, read->start_offset, SEEK_SET); - return GF_TRUE; -} - -static void AMR_NetIO(void *cbk, GF_NETIO_Parameter *param) -{ - GF_Err e; - const char *szCache; - u32 bytes_done; - AMR_Reader *read = (AMR_Reader *) cbk; - - e = param->error; - /*done*/ - if (param->msg_type==GF_NETIO_DATA_TRANSFERED) { - if (read->stream) { - read->is_remote = GF_FALSE; - e = GF_EOS; - } else { - return; - } - } else { - /*handle service message*/ - gf_service_download_update_stats(read->dnload); - if (param->msg_type!=GF_NETIO_DATA_EXCHANGE) return; - } - - /*data fetching*/ - if (e >= GF_OK) { - if (read->stream) return; - - /*open service*/ - szCache = gf_dm_sess_get_cache_name(read->dnload); - if (!szCache) e = GF_IO_ERR; - else { - read->stream = gf_fopen((char *) szCache, "rb"); - if (!read->stream) e = GF_SERVICE_ERROR; - else { - /*if full file at once (in cache) parse duration*/ - if (e==GF_EOS) read->is_remote = GF_FALSE; - e = GF_OK; - /*not enough data*/ - if (!AMR_ConfigureFromFile(read)) { - /*bad data...*/ - gf_dm_sess_get_stats(read->dnload, NULL, NULL, NULL, &bytes_done, NULL, NULL); - if (bytes_done>10*1024) { - e = GF_CORRUPTED_DATA; - } else { - gf_fclose(read->stream); - read->stream = NULL; - return; - } - } - } - } - } - /*OK confirm*/ - if (read->needs_connection) { - read->needs_connection = GF_FALSE; - gf_service_connect_ack(read->service, NULL, e); - if (!e) AMR_SetupObject(read); - } -} - -static void AMR_DownloadFile(GF_InputService *plug, char *url) -{ - AMR_Reader *read = (AMR_Reader*) plug->priv; - - read->needs_connection = GF_TRUE; - - read->dnload = gf_service_download_new(read->service, url, 0, AMR_NetIO, read); - if (!read->dnload) { - read->needs_connection = GF_FALSE; - gf_service_connect_ack(read->service, NULL, GF_NOT_SUPPORTED); - } else { - /*start our download (threaded)*/ - gf_dm_sess_process(read->dnload); - } - /*service confirm is done once fetched*/ -} - - -static GF_Err AMR_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url) -{ - char szURL[2048]; - char *ext; - GF_Err reply; - AMR_Reader *read = (AMR_Reader*)plug->priv; - read->service = serv; - - if (read->dnload) gf_service_download_del(read->dnload); - read->dnload = NULL; - - strcpy(szURL, url); - ext = strrchr(szURL, '#'); - if (ext) ext[0] = 0; - - /*remote fetch*/ - read->is_remote = !file_is_local(szURL); - if (read->is_remote) { - AMR_DownloadFile(plug, (char *) szURL); - return GF_OK; - } - - reply = GF_OK; - read->stream = gf_fopen(szURL, "rb"); - if (!read->stream) { - reply = GF_URL_ERROR; - } else if (!AMR_ConfigureFromFile(read)) { - gf_fclose(read->stream); - read->stream = NULL; - reply = GF_NOT_SUPPORTED; - } - gf_service_connect_ack(serv, NULL, reply); - if (!reply && read->is_inline) AMR_SetupObject(read); - return GF_OK; -} - -static GF_Err AMR_CloseService(GF_InputService *plug) -{ - AMR_Reader *read = (AMR_Reader*)plug->priv; - if (read->stream) gf_fclose(read->stream); - read->stream = NULL; - if (read->dnload) gf_service_download_del(read->dnload); - read->dnload = NULL; - - if (read->data) gf_free(read->data); - read->data = NULL; - gf_service_disconnect_ack(read->service, NULL, GF_OK); - return GF_OK; -} - -static GF_Descriptor *AMR_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) -{ - AMR_Reader *read = (AMR_Reader*)plug->priv; - /*since we don't handle multitrack in aac, we don't need to check sub_url, only use expected type*/ - - /*override default*/ - if (expect_type==GF_MEDIA_OBJECT_UNDEF) expect_type=GF_MEDIA_OBJECT_AUDIO; - - /*audio object*/ - if (expect_type==GF_MEDIA_OBJECT_AUDIO) { - GF_ObjectDescriptor *od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - GF_ESD *esd = AMR_GetESD(read); - od->objectDescriptorID = 1; - gf_list_add(od->ESDescriptors, esd); - return (GF_Descriptor *) od; - } - /*let player handle scene description*/ - read->is_inline = 1; - return NULL; -} - -static GF_Err AMR_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) -{ - u32 ES_ID; - GF_Err e; - AMR_Reader *read = (AMR_Reader*)plug->priv; - - e = GF_SERVICE_ERROR; - if (read->ch==channel) goto exit; - - e = GF_STREAM_NOT_FOUND; - ES_ID=0; - if (strstr(url, "ES_ID")) { - sscanf(url, "ES_ID=%d", &ES_ID); - } - /*URL setup*/ - else if (!read->ch && AMR_CanHandleURL(plug, url)) ES_ID = 1; - - if (ES_ID==1) { - read->ch = channel; - e = GF_OK; - } - -exit: - gf_service_connect_ack(read->service, channel, e); - return e; -} - -static GF_Err AMR_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) -{ - AMR_Reader *read = (AMR_Reader*)plug->priv; - - GF_Err e = GF_STREAM_NOT_FOUND; - if (read->ch == channel) { - read->ch = NULL; - if (read->data) gf_free(read->data); - read->data = NULL; - e = GF_OK; - } - gf_service_disconnect_ack(read->service, channel, e); - return GF_OK; -} - -static GF_Err AMR_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) -{ - AMR_Reader *read = (AMR_Reader*)plug->priv; - - if (!com->base.on_channel) return GF_NOT_SUPPORTED; - switch (com->command_type) { - case GF_NET_CHAN_SET_PULL: - return GF_OK; - case GF_NET_CHAN_INTERACTIVE: - return GF_OK; - case GF_NET_CHAN_BUFFER: - return GF_OK; - case GF_NET_CHAN_SET_PADDING: - read->pad_bytes = com->pad.padding_bytes; - return GF_OK; - case GF_NET_CHAN_DURATION: - com->duration.duration = read->duration; - com->duration.duration /= read->sample_rate; - return GF_OK; - case GF_NET_CHAN_PLAY: - read->start_range = com->play.start_range; - read->end_range = com->play.end_range; - read->current_time = 0; - if (read->stream) fseek(read->stream, read->start_offset, SEEK_SET); - - if (read->ch == com->base.on_channel) { - read->done = GF_FALSE; - /*PLAY after complete download, estimate duration*/ - if (!read->is_remote && !read->duration) { - AMR_ConfigureFromFile(read); - if (read->duration) { - GF_NetworkCommand rcfg; - rcfg.base.on_channel = read->ch; - rcfg.base.command_type = GF_NET_CHAN_DURATION; - rcfg.duration.duration = read->duration; - rcfg.duration.duration /= read->sample_rate; - gf_service_command(read->service, &rcfg, GF_OK); - } - } - } - return GF_OK; - case GF_NET_CHAN_STOP: - return GF_OK; - default: - return GF_OK; - } -} - - -static GF_Err AMR_ChannelGetSLP(GF_InputService *plug, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, GF_SLHeader *out_sl_hdr, Bool *sl_compressed, GF_Err *out_reception_status, Bool *is_new_data) -{ - u32 start_from, i; - u8 toc, ft; - AMR_Reader *read = (AMR_Reader*)plug->priv; - - *out_reception_status = GF_OK; - *sl_compressed = GF_FALSE; - *is_new_data = GF_FALSE; - - memset(&read->sl_hdr, 0, sizeof(GF_SLHeader)); - read->sl_hdr.randomAccessPointFlag = 1; - read->sl_hdr.compositionTimeStampFlag = 1; - if (read->ch != channel) return GF_STREAM_NOT_FOUND; - - /*fetching es data*/ - if (read->done) { - *out_reception_status = GF_EOS; - return GF_OK; - } - - if (!read->data) { - if (!read->stream) { - *out_data_ptr = NULL; - *out_data_size = 0; - return GF_OK; - } - *is_new_data = GF_TRUE; - -fetch_next: - //pos = (u32) ftell(read->stream); - if (feof(read->stream)) { - read->done = GF_TRUE; - *out_reception_status = GF_EOS; - return GF_OK; - } - - toc = fgetc(read->stream); - if (feof(read->stream)) { - read->done = GF_TRUE; - *out_reception_status = GF_EOS; - return GF_OK; - } - - switch (read->mtype) { - case TYPE_AMR: - ft = (toc >> 3) & 0x0F; - read->data_size = (u32)GF_AMR_FRAME_SIZE[ft]; - break; - case TYPE_AMR_WB: - ft = (toc >> 3) & 0x0F; - read->data_size = (u32)GF_AMR_WB_FRAME_SIZE[ft]; - break; - default: - for (i=0; i<GF_SMV_EVRC_RATE_TO_SIZE_NB; i++) { - if (GF_SMV_EVRC_RATE_TO_SIZE[2*i]==toc) { - /*remove rate_type byte*/ - read->data_size = (u32)GF_SMV_EVRC_RATE_TO_SIZE[2*i+1] - 1; - break; - } - } - break; - } - /*we're seeking*/ - if (read->start_range && read->duration) { - start_from = (u32) (read->start_range * read->sample_rate); - if (read->current_time + read->block_size < start_from) { - read->current_time += read->block_size; - fseek(read->stream, read->data_size, SEEK_CUR); - goto fetch_next; - } else { - read->start_range = 0; - } - } - assert(read->data_size); - read->data_size++; - read->sl_hdr.compositionTimeStamp = read->current_time; - read->data = (unsigned char*)gf_malloc(sizeof(char) * (read->data_size+read->pad_bytes)); - read->data[0] = toc; - if (read->data_size>1) { - u32 bytes_read = (u32) fread(read->data + 1, read->data_size-1, 1, read->stream); - if (bytes_read != read->data_size - 1) read->data_size = bytes_read+1; - } - if (read->pad_bytes) memset(read->data + read->data_size, 0, sizeof(char) * read->pad_bytes); - } - *out_sl_hdr = read->sl_hdr; - *out_data_ptr = (char*)read->data; - *out_data_size = read->data_size; - return GF_OK; -} - -static GF_Err AMR_ChannelReleaseSLP(GF_InputService *plug, LPNETCHANNEL channel) -{ - AMR_Reader *read = (AMR_Reader*)plug->priv; - - if (read->ch == channel) { - if (!read->data) return GF_BAD_PARAM; - gf_free(read->data); - read->data = NULL; - read->current_time += read->block_size; - return GF_OK; - } - return GF_OK; -} - -GF_InputService *NewAESReader() -{ - AMR_Reader *reader; - GF_InputService *plug = (GF_InputService*)gf_malloc(sizeof(GF_InputService)); - memset(plug, 0, sizeof(GF_InputService)); - GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC AMR/EVRC/SMV Reader", "gpac distribution") - - plug->RegisterMimeTypes = AMR_RegisterMimeTypes; - plug->CanHandleURL = AMR_CanHandleURL; - plug->ConnectService = AMR_ConnectService; - plug->CloseService = AMR_CloseService; - plug->GetServiceDescriptor = AMR_GetServiceDesc; - plug->ConnectChannel = AMR_ConnectChannel; - plug->DisconnectChannel = AMR_DisconnectChannel; - plug->ServiceCommand = AMR_ServiceCommand; - /*we do support pull mode*/ - plug->ChannelGetSLP = AMR_ChannelGetSLP; - plug->ChannelReleaseSLP = AMR_ChannelReleaseSLP; - - reader = (AMR_Reader*)gf_malloc(sizeof(AMR_Reader)); - memset(reader, 0, sizeof(AMR_Reader)); - plug->priv = reader; - return plug; -} - -void DeleteAESReader(void *ifce) -{ - GF_InputService *plug = (GF_InputService *) ifce; - AMR_Reader *read = (AMR_Reader*)plug->priv; - gf_free(read); - gf_free(plug); -} - - -#ifdef GPAC_AMR_IN_STANDALONE - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { - GF_NET_CLIENT_INTERFACE, - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - switch (InterfaceType) { - case GF_NET_CLIENT_INTERFACE: - return (GF_BaseInterface *) NewAESReader(); - default: - return NULL; - } -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { - case GF_NET_CLIENT_INTERFACE: - DeleteAESReader(ifce); - break; - } -} - -GPAC_MODULE_STATIC_DECLARATION(amr_in) - -#endif - diff --git a/modules/amr_dec/amr_nb/typedefs.h b/modules/amr_dec/amr_nb/typedefs.h deleted file mode 100644 index 2e42acb..0000000 --- a/modules/amr_dec/amr_nb/typedefs.h +++ /dev/null @@ -1,195 +0,0 @@ -/* -******************************************************************************** -* -* GSM AMR-NB speech codec R98 Version 7.6.0 December 12, 2001 -* R99 Version 3.3.0 -* REL-4 Version 4.1.0 -* -******************************************************************************** -* -* File : typedefs.h -* Description : Definition of platform independent data -* types and constants -* -* -* The following platform independent data types and corresponding -* preprocessor (#define) constants are defined: -* -* defined type meaning corresponding constants -* ---------------------------------------------------------- -* Char character (none) -* Bool boolean true, false -* Word8 8-bit signed minWord8, maxWord8 -* UWord8 8-bit unsigned minUWord8, maxUWord8 -* Word16 16-bit signed minWord16, maxWord16 -* UWord16 16-bit unsigned minUWord16, maxUWord16 -* Word32 32-bit signed minWord32, maxWord32 -* UWord32 32-bit unsigned minUWord32, maxUWord32 -* Float floating point minFloat, maxFloat -* -* -* The following compile switches are #defined: -* -* PLATFORM string indicating platform progam is compiled on -* possible values: "OSF", "PC", "SUN" -* -* OSF only defined if the current platform is an Alpha -* PC only defined if the current platform is a PC -* SUN only defined if the current platform is a Sun -* -* LSBFIRST is defined if the byte order on this platform is -* "least significant byte first" -> defined on DEC Alpha -* and PC, undefined on Sun -* -******************************************************************************** -*/ -#ifndef typedefs_h -#define typedefs_h "$Id $" - -/* -******************************************************************************** -* INCLUDE FILES -******************************************************************************** -*/ -#include <float.h> -#include <limits.h> - - - -/* -******************************************************************************** -* DEFINITION OF CONSTANTS -******************************************************************************** -*/ -/* - ********* define char type - */ -typedef char Char; - -/* - ********* define 8 bit signed/unsigned types & constants - */ -#if SCHAR_MAX == 127 -typedef signed char Word8; -#define minWord8 SCHAR_MIN -#define maxWord8 SCHAR_MAX - -typedef unsigned char UWord8; -#define minUWord8 0 -#define maxUWord8 UCHAR_MAX -#else -#error cannot find 8-bit type -#endif - - -/* - ********* define 16 bit signed/unsigned types & constants - */ -#if INT_MAX == 32767 -typedef int Word16; -#define minWord16 INT_MIN -#define maxWord16 INT_MAX -typedef unsigned int UWord16; -#define minUWord16 0 -#define maxUWord16 UINT_MAX -#elif SHRT_MAX == 32767 -typedef short Word16; -#define minWord16 SHRT_MIN -#define maxWord16 SHRT_MAX -typedef unsigned short UWord16; -#define minUWord16 0 -#define maxUWord16 USHRT_MAX -#else -#error cannot find 16-bit type -#endif - - -/* - ********* define 32 bit signed/unsigned types & constants - */ -#if INT_MAX == 2147483647 -typedef int Word32; -#define minWord32 INT_MIN -#define maxWord32 INT_MAX -typedef unsigned int UWord32; -#define minUWord32 0 -#define maxUWord32 UINT_MAX -#elif LONG_MAX == 2147483647 -typedef long Word32; -#define minWord32 LONG_MIN -#define maxWord32 LONG_MAX -typedef unsigned long UWord32; -#define minUWord32 0 -#define maxUWord32 ULONG_MAX -#else -#error cannot find 32-bit type -#endif - -/* - ********* define floating point type & constants - */ -/* use "#if 0" below if Float should be double; - use "#if 1" below if Float should be float - */ -#if 0 -//typedef float Float; -#define Float float -#define maxFloat FLT_MAX -#define minFloat FLT_MIN -#else -//typedef double Float; -#define Float double -#define maxFloat DBL_MAX -#define minFloat DBL_MIN -#endif - -/* - ********* define complex type - */ -typedef struct { - Float r; /* real part */ - Float i; /* imaginary part */ -} CPX; - -/* - ********* define boolean type - */ -#define Bool int -#define false 0 -#define true 1 - -/* - ********* Check current platform - */ -#if defined(__MSDOS__) -#define PC -#define PLATFORM "PC" -#define LSBFIRST -#elif defined(__osf__) -#define OSF -#define PLATFORM "OSF" -#define LSBFIRST -#elif defined(__sun__) || defined(__sun) -#define SUN -#define PLATFORM "SUN" -#undef LSBFIRST -#elif defined(linux) && defined(i386) -#define PC -#define PLATFORM "PC" -#define LSBFIRST -#elif defined(WIN32) || defined(_WIN32_WCE) -#define PC -#define PLATFORM "PC" -#define LSBFIRST -#else -#error "can't determine architecture; adapt typedefs.h to your platform" -#endif - -#if defined(_WIN32_WCE) -# ifndef abort -# define abort() -# endif -#endif - - -#endif diff --git a/modules/amr_dec/amr_nb_api.h b/modules/amr_dec/amr_nb_api.h deleted file mode 100644 index fcdfa37..0000000 --- a/modules/amr_dec/amr_nb_api.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef _AMR_NB_API_H -#define _AMR_NB_API_H - -enum Mode { MR475 = 0, - MR515, - MR59, - MR67, - MR74, - MR795, - MR102, - MR122, - - MRDTX, - - N_MODES /* number of (SPC) modes */ - - }; - -enum RXFrameType { RX_SPEECH_GOOD = 0, - RX_SPEECH_DEGRADED, - RX_ONSET, - RX_SPEECH_BAD, - RX_SID_FIRST, - RX_SID_UPDATE, - RX_SID_BAD, - RX_NO_DATA, - RX_N_FRAMETYPES /* number of frame types */ - }; - -typedef struct { - void* decoder_amrState; - void* post_state; - void* postHP_state; - enum Mode prev_mode; - - int complexityCounter; /* Only for complexity computation */ -} __Speech_Decode_FrameState; - -int Speech_Decode_Frame_init (__Speech_Decode_FrameState **st, char *id); -void Speech_Decode_Frame_exit (__Speech_Decode_FrameState **st); -void Speech_Decode_Frame (__Speech_Decode_FrameState *st, enum Mode mode, short *serial, enum RXFrameType frame_type, short *synth); -int Speech_Decode_Frame_reset (__Speech_Decode_FrameState **st); - -s16 decoder_homing_frame_test_first (s16 input_frame[], enum Mode mode); -s16 decoder_homing_frame_test (s16 input_frame[], enum Mode mode); - -#ifdef MMS_IO - -enum RXFrameType UnpackBits ( - s8 q, /* i : Q-bit (i.e. BFI) */ - s16 ft, /* i : frame type (i.e. mode) */ - u8 packed_bits[], /* i : sorted & packed bits */ - enum Mode *mode, /* o : mode information */ - s16 bits[] /* o : serial bits */ -); -#endif - -#define EHF_MASK 0x0008 /* encoder homing frame pattern */ -#define L_FRAME 160 /* Frame size */ - -#endif //_AMR_NB_API_H \ No newline at end of file diff --git a/modules/amr_float_dec/Makefile b/modules/amr_float_dec/Makefile deleted file mode 100644 index 0dea4bd..0000000 --- a/modules/amr_float_dec/Makefile +++ /dev/null @@ -1,62 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/amr_float_dec - -#note: __MSDOS__ defined is the same cfg as linux. There may be some pbs on other platforms, to check... -CFLAGS= $(OPTFLAGS) -w -I"$(SRC_PATH)/include" -D__MSDOS__ - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -DDEBUG -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS=amr_float_dec.o - -#AMR NB obj -CFLAGS+=-DGPAC_HAS_AMR_FT -ifeq ($(CONFIG_AMR_NB_FT), yes) -OBJS+=./amr_nb_ft/interf_dec.o ./amr_nb_ft/interf_enc.o ./amr_nb_ft/sp_dec.o ./amr_nb_ft/sp_enc.o -endif - - -#AMR WB obj -CFLAGS+=-DGPAC_HAS_AMR_FT_WB -ifeq ($(CONFIG_AMR_WB_FT), yes) -OBJS+=./amr_wb_ft/dec_acelp.o ./amr_wb_ft/dec_dtx.o ./amr_wb_ft/dec_gain.o ./amr_wb_ft/dec_if.o ./amr_wb_ft/dec_lpc.o ./amr_wb_ft/dec_main.o \ - ./amr_wb_ft/dec_rom.o ./amr_wb_ft/dec_util.o ./amr_wb_ft/enc_acelp.o ./amr_wb_ft/enc_dtx.o ./amr_wb_ft/enc_gain.o ./amr_wb_ft/enc_if.o \ - ./amr_wb_ft/enc_lpc.o ./amr_wb_ft/enc_main.o ./amr_wb_ft/enc_rom.o ./amr_wb_ft/enc_util.o ./amr_wb_ft/if_rom.o -endif - - -SRCS := $(OBJS:.o=.c) - -LIB=gm_amr_float_dec$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols amr_float_dec.def -endif - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/amr_float_dec/amr_api.h b/modules/amr_float_dec/amr_api.h deleted file mode 100644 index ab60cd1..0000000 --- a/modules/amr_float_dec/amr_api.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef _AMR_API_H -#define _AMR_API_H - -/*AMR*/ -void Decoder_Interface_Decode( void *st, -#ifndef ETSI - unsigned char *bits, -#else - short *bits, -#endif - short *synth, int bfi ); - -void *Decoder_Interface_init( void ); - - -void Decoder_Interface_exit( void *state ); - - - -/*AMR WB*/ - - -#define NB_SERIAL_MAX 61 /* max serial size */ -#define L_FRAME16k 320 /* Frame size at 16kHz */ - -#define _good_frame 0 -#define _bad_frame 1 -#define _lost_frame 2 -#define _no_frame 3 - -void D_IF_decode(void *st, unsigned char *bits, short *synth, long bfi); -void * D_IF_init(void); -void D_IF_exit(void *state); - -#endif //_AMR_API_H \ No newline at end of file diff --git a/modules/amr_float_dec/amr_float_dec.c b/modules/amr_float_dec/amr_float_dec.c deleted file mode 100644 index c8f6580..0000000 --- a/modules/amr_float_dec/amr_float_dec.c +++ /dev/null @@ -1,343 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / AMR decoder module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - - -/*decoder Interface*/ -#include <gpac/modules/codec.h> -#include <gpac/modules/service.h> -#include <gpac/constants.h> - -#if (defined(WIN32) || defined (_WIN32_WCE)) && defined(GPAC_HAS_AMR_FT) -/*AMR NB*/ -#include "amr_api.h" - -#ifndef GPAC_HAS_AMR_FT_WB -#define GPAC_HAS_AMR_FT_WB -#endif - -#if !defined(__GNUC__) -#pragma comment(lib, "libamrfloat") -#endif - -#else - -#ifdef GPAC_HAS_AMR_FT -/*AMR WB*/ -#include "amr_nb_ft/interf_dec.h" -#endif - -#ifdef GPAC_HAS_AMR_FT_WB -/*AMR WB*/ -#include "amr_wb_ft/dec_if.h" -#endif - -#endif - -/*default size in CU of composition memory for audio*/ -#define DEFAULT_AUDIO_CM_SIZE 12 -/*default critical size in CU of composition memory for audio*/ -#define DEFAULT_AUDIO_CM_TRIGGER 4 - -typedef struct -{ - Bool is_amr_wb; - u32 sample_rate, out_size, num_samples; - u8 num_channels; - - /*AMR NB state vars*/ - int *nb_destate; - void *wb_destate; -} AMRFTDec; - -#define AMRFTCTX() AMRFTDec *ctx = (AMRFTDec *) ifcg->privateStack - - -static GF_Err AMR_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ - GF_BitStream *bs; - u32 packed; - AMRFTCTX(); - if (esd->dependsOnESID || !esd->decoderConfig->decoderSpecificInfo) return GF_NOT_SUPPORTED; - - /*AMRWB dec is another module*/ - if (!strnicmp(esd->decoderConfig->decoderSpecificInfo->data, "sawb", 4)) ctx->is_amr_wb = GF_TRUE; - else if (!strnicmp(esd->decoderConfig->decoderSpecificInfo->data, "samr", 4) || !strnicmp(esd->decoderConfig->decoderSpecificInfo->data, "amr ", 4)) ctx->is_amr_wb = GF_FALSE; - else return GF_NOT_SUPPORTED; - - - bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ); - gf_bs_read_u32(bs); - gf_bs_read_u32(bs); - ctx->num_channels = gf_bs_read_u16(bs); - gf_bs_read_u16(bs); - gf_bs_read_u8(bs); - packed = gf_bs_read_u8(bs); - gf_bs_del(bs); - /*max possible frames in a sample are seen in MP4, that's 15*/ - if (!packed) packed = 15; - - if (ctx->is_amr_wb) { -#ifdef GPAC_HAS_AMR_FT_WB - ctx->wb_destate = D_IF_init(); - if (!ctx->wb_destate) return GF_IO_ERR; -#else - return GF_NOT_SUPPORTED; -#endif - ctx->num_samples = 320; - ctx->sample_rate = 16000; - } else { -#ifdef GPAC_HAS_AMR_FT - ctx->nb_destate = Decoder_Interface_init(); - if (!ctx->nb_destate) return GF_IO_ERR; -#else - return GF_NOT_SUPPORTED; -#endif - ctx->num_samples = 160; - ctx->sample_rate = 8000; - } - - ctx->out_size = packed * 2 * ctx->num_samples * ctx->num_channels; - return GF_OK; -} - -static GF_Err AMR_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - AMRFTCTX(); - -#ifdef GPAC_HAS_AMR_FT - if (ctx->nb_destate) Decoder_Interface_exit(ctx->nb_destate); -#endif - ctx->nb_destate = NULL; -#ifdef GPAC_HAS_AMR_FT_WB - if (ctx->wb_destate) D_IF_exit(ctx->wb_destate); -#endif - ctx->wb_destate = NULL; - ctx->sample_rate = ctx->out_size = ctx->num_samples = 0; - ctx->num_channels = 0; - return GF_OK; -} -static GF_Err AMR_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - AMRFTCTX(); - switch (capability->CapCode) { - /*not tested yet*/ - case GF_CODEC_RESILIENT: - capability->cap.valueInt = 1; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = ctx->out_size; - break; - case GF_CODEC_SAMPLERATE: - capability->cap.valueInt = ctx->sample_rate; - break; - case GF_CODEC_NB_CHAN: - capability->cap.valueInt = ctx->num_channels; - break; - case GF_CODEC_BITS_PER_SAMPLE: - capability->cap.valueInt = 16; - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = DEFAULT_AUDIO_CM_TRIGGER; - break; - case GF_CODEC_BUFFER_MAX: - capability->cap.valueInt = DEFAULT_AUDIO_CM_SIZE; - break; - /*FIXME: get exact sampling window*/ - case GF_CODEC_CU_DURATION: - capability->cap.valueInt = ctx->num_samples; - break; - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 4; - break; - case GF_CODEC_CHANNEL_CONFIG: - if (ctx->num_channels==1) { - capability->cap.valueInt = GF_AUDIO_CH_FRONT_CENTER; - } else { - capability->cap.valueInt = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT; - } - break; - default: - capability->cap.valueInt = 0; - break; - } - return GF_OK; -} - -static GF_Err AMR_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - /*return unsupported to avoid confusion by the player (like SR changing ...) */ - return GF_NOT_SUPPORTED; -} - - -static GF_Err AMR_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ - u32 offset; - u8 toc, ft; - AMRFTCTX(); - - /*if late or seeking don't decode (each frame is a RAP)*/ - /* switch (mmlevel) { - case GF_CODEC_LEVEL_SEEK: - case GF_CODEC_LEVEL_DROP: - *outBufferLength = 0; - return GF_OK; - default: - break; - } - */ - if (ctx->out_size > *outBufferLength) { - *outBufferLength = ctx->out_size; - return GF_BUFFER_TOO_SMALL; - } - - *outBufferLength = 0; - - while (inBufferLength) { - toc = inBuffer[0]; - ft = (toc >> 3) & 0x0F; - - offset = 0; - if (ctx->is_amr_wb) { -#ifdef GPAC_HAS_AMR_FT_WB - D_IF_decode(ctx->wb_destate, inBuffer, (s16 *) outBuffer, 0); - *outBufferLength += 320*2; - outBuffer += 320*2; - offset = (u32)GF_AMR_WB_FRAME_SIZE[ft] + 1; -#endif - } else { -#ifdef GPAC_HAS_AMR_FT - Decoder_Interface_Decode(ctx->nb_destate, inBuffer, (s16 *) outBuffer, 0); - *outBufferLength += 160*2; - outBuffer += 160*2; - offset = (u32)GF_AMR_FRAME_SIZE[ft] + 1; -#endif - } - /*don't complain but...*/ - if (inBufferLength<offset) return GF_OK; - inBuffer += offset; - inBufferLength -= offset; - } - return GF_OK; -} - - -static u32 AMR_CanHandleStream(GF_BaseDecoder *dec, u32 StreamType, GF_ESD *esd, u8 PL) -{ - char *dsi; - /*we handle audio only*/ - if (StreamType!=GF_STREAM_AUDIO) return GF_CODEC_NOT_SUPPORTED; - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - - if (esd->decoderConfig->objectTypeIndication != GPAC_OTI_MEDIA_GENERIC) return GF_CODEC_NOT_SUPPORTED; - dsi = esd->decoderConfig->decoderSpecificInfo ? esd->decoderConfig->decoderSpecificInfo->data : NULL; - if (!dsi) return GF_CODEC_NOT_SUPPORTED; - if (esd->decoderConfig->decoderSpecificInfo->dataLength < 4) return GF_CODEC_NOT_SUPPORTED; - -#ifdef GPAC_HAS_AMR_FT - if (!strnicmp(dsi, "samr", 4) || !strnicmp(dsi, "amr ", 4)) return GF_CODEC_SUPPORTED; -#endif -#ifdef GPAC_HAS_AMR_FT_WB - if (!strnicmp(dsi, "sawb", 4)) return GF_CODEC_SUPPORTED; -#endif - return GF_CODEC_NOT_SUPPORTED; -} - - -static const char *AMR_GetCodecName(GF_BaseDecoder *ifcg) -{ - AMRFTCTX(); - if (ctx->is_amr_wb) return "3GPP Floating-point AMR Wideband"; - return "3GPP Floating-point AMR"; -} - -GF_MediaDecoder *NewAMRFTDecoder() -{ - AMRFTDec *dec; - GF_MediaDecoder *ifce; - GF_SAFEALLOC(ifce , GF_MediaDecoder); - dec = (AMRFTDec*)gf_malloc(sizeof(AMRFTDec)); - memset(dec, 0, sizeof(AMRFTDec)); - ifce->privateStack = dec; - ifce->CanHandleStream = AMR_CanHandleStream; - - /*setup our own interface*/ - ifce->AttachStream = AMR_AttachStream; - ifce->DetachStream = AMR_DetachStream; - ifce->GetCapabilities = AMR_GetCapabilities; - ifce->SetCapabilities = AMR_SetCapabilities; - ifce->ProcessData = AMR_ProcessData; - ifce->GetName = AMR_GetCodecName; - - GF_REGISTER_MODULE_INTERFACE(ifce, GF_MEDIA_DECODER_INTERFACE, "AMR-FT 3GPP decoder", "gpac distribution"); - - return ifce; -} - -void DeleteAMRFTDecoder(GF_BaseDecoder *ifcg) -{ - AMRFTCTX(); - gf_free(ctx); - gf_free(ifcg); -} - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { - GF_MEDIA_DECODER_INTERFACE, - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - switch (InterfaceType) { - case GF_MEDIA_DECODER_INTERFACE: - return (GF_BaseInterface *)NewAMRFTDecoder(); - default: - return NULL; - } -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { - case GF_MEDIA_DECODER_INTERFACE: - DeleteAMRFTDecoder((GF_BaseDecoder *)ifce); - break; - } -} - -GPAC_MODULE_STATIC_DECLARATION( amr_float ) diff --git a/modules/bifs_dec/Makefile b/modules/bifs_dec/Makefile deleted file mode 100644 index e62ebff..0000000 --- a/modules/bifs_dec/Makefile +++ /dev/null @@ -1,50 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/bifs_dec - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS= bifs_dec.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_bifs_dec$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols bifs_dec.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_bifs_dec-static$(DYN_LIB_SUFFIX) $(OBJS) -L../../bin/gcc -lgpac_static $(LDFLAGS) -endif - - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/bifs_dec/bifs_dec.c b/modules/bifs_dec/bifs_dec.c deleted file mode 100644 index 5c8a2ee..0000000 --- a/modules/bifs_dec/bifs_dec.c +++ /dev/null @@ -1,220 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / BIFS decoder module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include <gpac/internal/terminal_dev.h> -#include <gpac/bifs.h> -#include <gpac/constants.h> - -#ifndef GPAC_DISABLE_BIFS - -typedef struct -{ - GF_Scene *pScene; - GF_Terminal *app; - GF_BifsDecoder *codec; - u32 PL, nb_streams; -} BIFSPriv; - - - -static GF_Err BIFS_GetCapabilities(GF_BaseDecoder *plug, GF_CodecCapability *cap) -{ - cap->cap.valueInt = 0; - return GF_NOT_SUPPORTED; -} - -static GF_Err BIFS_SetCapabilities(GF_BaseDecoder *plug, const GF_CodecCapability capability) -{ - return GF_OK; -} - -GF_Err BIFS_AttachScene(GF_SceneDecoder *plug, GF_Scene *scene, Bool is_scene_decoder) -{ - BIFSPriv *priv = (BIFSPriv *)plug->privateStack; - if (priv->codec) return GF_BAD_PARAM; - priv->pScene = scene; - priv->app = scene->root_od->term; - - priv->codec = gf_bifs_decoder_new(scene->graph, GF_FALSE); - gf_bifs_decoder_set_extraction_path(priv->codec, (char *) gf_modules_get_option((GF_BaseInterface *)plug, "General", "CacheDirectory"), scene->root_od->net_service->url); - /*ignore all size info on anim streams*/ - if (!is_scene_decoder) gf_bifs_decoder_ignore_size_info(priv->codec); - return GF_OK; -} - -GF_Err BIFS_ReleaseScene(GF_SceneDecoder *plug) -{ - BIFSPriv *priv = (BIFSPriv *)plug->privateStack; - if (!priv->codec || priv->nb_streams) return GF_BAD_PARAM; - gf_bifs_decoder_del(priv->codec); - priv->codec = NULL; - return GF_OK; -} - -static GF_Err BIFS_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd) -{ - BIFSPriv *priv = (BIFSPriv *)plug->privateStack; - GF_Err e; - if (esd->decoderConfig->upstream) return GF_NOT_SUPPORTED; - if (!esd->decoderConfig->decoderSpecificInfo) return GF_BAD_PARAM; - e = gf_bifs_decoder_configure_stream(priv->codec, esd->ESID, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, esd->decoderConfig->objectTypeIndication); - if (!e) priv->nb_streams++; - return e; -} - -static GF_Err BIFS_DetachStream(GF_BaseDecoder *plug, u16 ES_ID) -{ - GF_Err e; - BIFSPriv *priv = (BIFSPriv *)plug->privateStack; - e = gf_bifs_decoder_remove_stream(priv->codec, ES_ID); - if (e) return e; - priv->nb_streams--; - return GF_OK; -} - -static GF_Err BIFS_ProcessData(GF_SceneDecoder*plug, const char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 AU_time, u32 mmlevel) -{ - Double ts_offset; - s32 time; - GF_Err e = GF_OK; - BIFSPriv *priv = (BIFSPriv *)plug->privateStack; - - time = (s32) AU_time; - ts_offset = ((Double)time)/1000.0; - e = gf_bifs_decode_au(priv->codec, ES_ID, inBuffer, inBufferLength, ts_offset); - - /*if scene not attached do it*/ - if (e == GF_OK) { - gf_scene_attach_to_compositor(priv->pScene); - } - return e; -} - -static u32 BIFS_CanHandleStream(GF_BaseDecoder *ifce, u32 StreamType, GF_ESD *esd, u8 PL) -{ - BIFSPriv *priv = (BIFSPriv *)ifce->privateStack; - if (StreamType!=GF_STREAM_SCENE) return GF_CODEC_NOT_SUPPORTED; - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - - switch (esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_SCENE_BIFS: - case GPAC_OTI_SCENE_BIFS_V2: - /*Streams with this value with a StreamType indicating a systems stream (values 1,2,3, 6, 7, 8, 9) - shall be treated as if the ObjectTypeIndication had been set to 0x01*/ - case 0xFF: - priv->PL = PL; - return GF_CODEC_SUPPORTED; - default: - return GF_CODEC_NOT_SUPPORTED; - } -} - - -void DeleteBIFSDec(GF_BaseDecoder *plug) -{ - BIFSPriv *priv; - if (!plug) - return; - priv = (BIFSPriv *)plug->privateStack; - if (priv) { - /*in case something went wrong*/ - if (priv->codec) gf_bifs_decoder_del(priv->codec); - priv->codec = NULL; - gf_free(priv); - plug->privateStack = NULL; - } - gf_free(plug); -} - -GF_BaseDecoder *NewBIFSDec() -{ - BIFSPriv *priv; - GF_SceneDecoder *tmp; - - GF_SAFEALLOC(tmp, GF_SceneDecoder); - if (!tmp) return NULL; - GF_SAFEALLOC(priv, BIFSPriv); - if (!priv) { - gf_free(tmp); - return NULL; - } - priv->codec = NULL; - tmp->privateStack = priv; - tmp->AttachStream = BIFS_AttachStream; - tmp->DetachStream = BIFS_DetachStream; - tmp->GetCapabilities = BIFS_GetCapabilities; - tmp->SetCapabilities = BIFS_SetCapabilities; - tmp->ProcessData = BIFS_ProcessData; - tmp->AttachScene = BIFS_AttachScene; - tmp->CanHandleStream = BIFS_CanHandleStream; - tmp->ReleaseScene = BIFS_ReleaseScene; - GF_REGISTER_MODULE_INTERFACE(tmp, GF_SCENE_DECODER_INTERFACE, "GPAC BIFS Decoder", "gpac distribution") - return (GF_BaseDecoder *) tmp; -} - - -#endif /*GPAC_DISABLE_BIFS*/ - - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifndef GPAC_DISABLE_BIFS - GF_SCENE_DECODER_INTERFACE, -#endif - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - switch (InterfaceType) { -#ifndef GPAC_DISABLE_BIFS - case GF_SCENE_DECODER_INTERFACE: - return (GF_BaseInterface *)NewBIFSDec(); -#endif - default: - return NULL; - } -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#ifndef GPAC_DISABLE_BIFS - case GF_SCENE_DECODER_INTERFACE: - DeleteBIFSDec((GF_BaseDecoder *)ifce); - break; -#endif - } -} - -GPAC_MODULE_STATIC_DECLARATION( bifs ) diff --git a/modules/ctx_load/Makefile b/modules/ctx_load/Makefile deleted file mode 100644 index 14521a6..0000000 --- a/modules/ctx_load/Makefile +++ /dev/null @@ -1,49 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/ctx_load - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS= ctx_load.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_ctx_load$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols ctx_load.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_ctx_load-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac_static $(LDFLAGS) -endif - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/dec_openhevc/Makefile b/modules/dec_openhevc/Makefile new file mode 100644 index 0000000..03b732b --- /dev/null +++ b/modules/dec_openhevc/Makefile @@ -0,0 +1,59 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/dec_openhevc + +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) $(OHEVC_CFLAGS) + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +ifeq ($(CONFIG_DARWIN),yes) +LINKFLAGS+=-L../../bin/gcc $(OHEVC_LDFLAGS) +else +LINKFLAGS+=-L../../bin/gcc $(OHEVC_LDFLAGS) -Wl,-Bsymbolic +endif + +#common obj +OBJS=dec_openhevc.o +SRCS=$(SRC_PATH)/src/filters/dec_openhevc.c + + +ifeq ($(CONFIG_WIN32),yes) +else +EXTRALIBS+=-ldl +endif + +LINKFLAGS+=-lgpac + +LIB=gf_openhevc_dec$(DYN_LIB_SUFFIX) + +all: $(LIB) + +dec_openhevc.o : $(SRC_PATH)/src/filters/dec_openhevc.c + @echo " CC $<" + $(CC) $(CFLAGS) -c -o $@ $< + + +$(LIB): $(OBJS) + $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(LINKFLAGS) $(LDFLAGS) + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/dektec_out/Makefile b/modules/dektec_out/Makefile index 6baccde..0dcfe02 100644 --- a/modules/dektec_out/Makefile +++ b/modules/dektec_out/Makefile @@ -2,23 +2,23 @@ include ../../config.mak vpath %.c $(SRC_PATH)/modules/dektec_out -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -DGPAC_HAVE_CONFIG_H -I../../ +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif -ifeq ($(ENABLE_JOYSTICK), yes) +ifeq ($(ENABLE_JOYSTICK),yes) CFLAGS+=-DENABLE_JOYSTICK endif -ifeq ($(ENABLE_JOYSTICK_NO_CURSOR), yes) +ifeq ($(ENABLE_JOYSTICK_NO_CURSOR),yes) CFLAGS+=-DENABLE_JOYSTICK_NO_CURSOR endif diff --git a/modules/dektec_out/dektec_video.cpp b/modules/dektec_out/dektec_video.cpp index b027981..f6f5367 100644 --- a/modules/dektec_out/dektec_video.cpp +++ b/modules/dektec_out/dektec_video.cpp @@ -1,361 +1,803 @@ /* - * GPAC - Multimedia Framework C SDK - * - * Authors: Romain Bouqueau - * Copyright (c) Romain Bouqueau @ GPAC Licensing - * All rights reserved - * - * This file is part of GPAC / Dektec SDI video render module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#define _DTAPI_DISABLE_AUTO_LINK -#include <DTAPI.h> - -extern "C" { +* GPAC - Multimedia Framework C SDK +* +* Authors: Romain Bouqueau, Jean Le Feuvre +* Copyright (c) 2014-2016 GPAC Licensing +* Copyright (c) 2016-2020 Telecom Paris +* All rights reserved +* +* This file is part of GPAC / Dektec SDI video output filter +* +* GPAC is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* GPAC is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; see the file COPYING. If not, write to +* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +* +*/ + +#include "dektec_video.h" + +#ifdef GPAC_HAS_DTAPI + +#ifndef FAKE_DT_API + +static void OnNewFrameAudio(DtMxData *pData, const DtCbkCtx *ctx) +{ + // Must have a valid frame + DtMxRowData &OurRow = pData->m_Rows[0]; + if (OurRow.m_CurFrame->m_Status != DT_FRMSTATUS_OK) { + return; // Frame is not valid so we have to assume the frame buffers are unusable + } - /*driver interfaces*/ -#include <gpac/modules/video_out.h> -#include <gpac/modules/audio_out.h> -#include <gpac/constants.h> -#include <gpac/setup.h> + // Get frame audio data + DtMxAudioData& AudioData = OurRow.m_CurFrame->m_Audio; + + // Init channel status word for all (valid) audio channels + AudioData.InitChannelStatus(); +} + +static void OnNewFrameVideo(DtMxData *pData, const DtCbkCtx *cbck) +{ + u64 now = gf_sys_clock_high_res(); + const DtMxRowData& OurRow = pData->m_Rows[0]; + GF_DTOutCtx *ctx = cbck->ctx; + GF_FilterPacket *pck; + + if (!ctx->is_configured || ctx->is_eos) return; + if (pData->m_NumSkippedFrames > 0) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DekTecOut] [%lld] #skipped frames=%d\n", pData->m_Frame, pData->m_NumSkippedFrames)); + } + if (!ctx->init_clock) { + ctx->init_clock = ctx->last_frame_time = now; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DekTecOut] output frame %d at %d ms - %d since last\n", ctx->frameNum, (u32)(now - ctx->init_clock) / 1000, now - ctx->last_frame_time)); -//#define DEKTEC_DUMP_UYVY -#ifdef NDEBUG -#define tru 1 -#else -#define tru 0 -#endif + ctx->last_frame_time = now; -//TODO: use gf_stretch_bits and get values from config file -#if 1 -#define DWIDTH 1280 -#define DHEIGHT 720 -#define DFORMAT DTAPI_IOCONFIG_720P59_94 -#define DNUMFIELDS 1 -#else -#define DWIDTH 1920 -#define DHEIGHT 1080 -#define DFORMAT DTAPI_IOCONFIG_1080I50 -#define DNUMFIELDS 2 -#endif + if (!cbck->video) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] no connected pid !\n")); + return; + } - typedef struct + // Must have a valid frame + if (OurRow.m_CurFrame->m_Status != DT_FRMSTATUS_OK) { - char *pixels; - unsigned char *pixels_UYVY; - u32 width, height, pixel_format, bpp; + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Not valid frame 4k\n")); + return; // Frame is not valid so we have to assume the frame buffers are unusable + } + if (pData->m_NumSkippedFrames > 0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] [%lld] #skipped frames=%d\n", pData->m_Frame, pData->m_NumSkippedFrames)); + } - DtDevice *dvc; - DtFrameBuffer *dtf; - bool isSending; - s64 frameNum; - } DtContext; + pck = gf_filter_pid_get_packet(cbck->video); + if (!pck) { + if (gf_filter_pid_is_eos(cbck->video)) { + ctx->is_eos = GF_TRUE; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] no packet ready !\n")); + } + return; + } + ctx->is_eos = GF_FALSE; + + u8 *pY, *pU, *pV; + u32 pck_size = 0; + u32 stride = ctx->stride, stride_uv= ctx->stride_uv; + pU = pV = NULL; + pY = (u8 *) gf_filter_pck_get_data(pck, &pck_size); + if (!pY) { + GF_FilterFrameInterface *hwframe = gf_filter_pck_get_frame_interface(pck); + if (hwframe) { + hwframe->get_plane(hwframe, 0, (const u8 **) &pY, &stride); + if (ctx->nb_planes>1) + hwframe->get_plane(hwframe, 1, (const u8 **) &pU, &stride_uv); + if (ctx->nb_planes>2) + hwframe->get_plane(hwframe, 2, (const u8 **) &pV, &stride_uv); + } + } + if (!pY) { + gf_filter_pid_drop_packet(cbck->video); + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] No data associated with packet\n")); + return; + } + if (!pU && !pV) { + if (ctx->nb_planes>1) + pU = pY + stride * ctx->height; + if (ctx->nb_planes>2) + pV = pU + stride_uv * ctx->uv_height; + } + //TODO - move all this into color convertion code in libgpac ! - void dx_copy_pixels(GF_VideoSurface *dst_s, const GF_VideoSurface *src_s, const GF_Window *src_wnd); //FIXME: referenced from dx - static GF_Err Dektec_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window *src_wnd, GF_Window *dst_wnd, u32 overlay_type) - { - DtContext *dtc = (DtContext*)dr->opaque; - GF_VideoSurface temp_surf; - memset(&temp_surf, 0, sizeof(GF_VideoSurface)); - temp_surf.pixel_format = GF_PIXEL_UYVY; - temp_surf.video_buffer = (char*)dtc->pixels_UYVY; - temp_surf.pitch_y = dtc->width*2; - dx_copy_pixels(&temp_surf, video_src, src_wnd); - GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Dektec Out] Blit\n")); - return GF_OK; - } + const DtMxVideoBuf& VideoBuf = OurRow.m_CurFrame->m_Video[0]; + u8* pDstY = VideoBuf.m_Planes[0].m_pBuf; + u8* pDstU = VideoBuf.m_Planes[1].m_pBuf; + u8* pDstV = VideoBuf.m_Planes[2].m_pBuf; - static GF_Err Dektec_Flush(GF_VideoOutput *dr, GF_Window *dest) - { - DtContext *dtc = (DtContext*)dr->opaque; - DtFrameBuffer *dtf = dtc->dtf; - DTAPI_RESULT res; - if (dest->w != DWIDTH || dest->h != DHEIGHT) { - GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Dektec Out] Discard data with wrong dimensions\n")); - if (tru) return GF_OK; - } - if (!dtc->isSending) { - // Start transmission - res = dtf->Start(); - if (tru && res != DTAPI_OK) { - GF_LOG(GF_LOG_WARNING, GF_LOG_MODULE, ("[Dektec Out] Can't start transmission: %s.\n", DtapiResult2Str(res))); - } else { - GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Dektec Out] Transmission started.\n")); - dtc->isSending = true; + if (ctx->is_10b) { + u32 nb_pix = 2 * ctx->width*ctx->height; + int lineC = 2 * ctx->width / 2; + unsigned char *pSrcY = pY; + unsigned char *pSrcU = pU; + unsigned char *pSrcV = pV; + + if (ctx->clip) { + const u16 nYRangeMin = 4; + const u16 nYRangeMax = 1019; + const __m128i mMin = _mm_set1_epi16(nYRangeMin); + const __m128i mMax = _mm_set1_epi16(nYRangeMax); + + for (u32 i = 0; i < nb_pix; i += 16) { + _mm_storeu_si128((__m128i *)&pDstY[i], _mm_max_epi16(mMin, _mm_min_epi16(mMax, _mm_loadu_si128((__m128i const *)&pSrcY[i])))); + } + + for (u32 h = 0; h < ctx->height / 2; h++) { + for (u32 i = 0; i < ctx->width; i += 16) { + _mm_storeu_si128((__m128i *)&pDstU[i], _mm_max_epi16(mMin, _mm_min_epi16(mMax, _mm_loadu_si128((__m128i const *)&pSrcU[i])))); + } + //420->422: copy over each U and V line + memcpy(pDstU + lineC, pDstU, lineC); + pSrcU += lineC; + pDstU += 2 * lineC; } - } - s64 firstSafeFrame=-1, lastSafeFrame=-1; - res = dtf->WaitFrame(firstSafeFrame, lastSafeFrame); - if (tru && res != DTAPI_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't get the next frame: %s\n", DtapiResult2Str(res))); - return GF_BAD_PARAM; - } - if (dtc->frameNum == -1) - dtc->frameNum = firstSafeFrame; + //NV12 formats + if (!pSrcV) pSrcV = pU + 1; - int numLines=-1, numWritten=DWIDTH*DHEIGHT*2/DNUMFIELDS; - res = dtf->WriteVideo(dtc->frameNum, dtc->pixels_UYVY, numWritten, DTAPI_SDI_FIELD1, DTAPI_SDI_8B, 1, numLines); - if (tru && res != DTAPI_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't write frame "LLD": %s\n", dtc->frameNum, DtapiResult2Str(res))); - return GF_BAD_PARAM; + for (u32 h = 0; h < ctx->height / 2; h++) { + for (u32 i = 0; i < ctx->width; i += 16) { + _mm_storeu_si128((__m128i *)&pDstV[i], _mm_max_epi16(mMin, _mm_min_epi16(mMax, _mm_loadu_si128((__m128i const *)&pSrcV[i])))); + } + memcpy(pDstV + lineC, pDstV, lineC); + pSrcV += lineC; + pDstV += 2 * lineC; + } } - if (DNUMFIELDS > 1) { - assert(DNUMFIELDS == 2); - numLines=-1, numWritten=DWIDTH*DHEIGHT*2/DNUMFIELDS; - res = dtf->WriteVideo(dtc->frameNum, dtc->pixels_UYVY+numWritten, numWritten, DTAPI_SDI_FIELD2, DTAPI_SDI_8B, 1, numLines); - if (tru && res != DTAPI_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't write frame "LLD": %s\n", dtc->frameNum, DtapiResult2Str(res))); - return GF_BAD_PARAM; + else { + memcpy(pDstY, pY, stride * ctx->height); + + //420->422: copy over each U and V line + for (u32 h = 0; h < ctx->height / 2; h++) { + memcpy(pDstU, pSrcU, lineC); + memcpy(pDstU + lineC, pSrcU, lineC); + pSrcU += lineC; + pDstU += 2 * lineC; + } + for (u32 h = 0; h < ctx->height / 2; h++) { + memcpy(pDstV, pSrcV, lineC); + memcpy(pDstV + lineC, pSrcV, lineC); + pSrcV += lineC; + pDstV += 2 * lineC; } } + } + else { + const int lineC = ctx->width / 2; + u32 nb_pix = ctx->width*ctx->height; + unsigned char *pSrcY = pY; + unsigned char *pSrcU = pU; + unsigned char *pSrcV = pV; + + const u16 nYRangeMin = 4; + const u16 nYRangeMax = 1019; + + for (u32 i = 0; i < nb_pix; i++) { + u16 srcy = ((u16)(*pSrcY)) << 2; + if (ctx->clip) { + if (srcy < nYRangeMin) srcy = nYRangeMin; + else if (srcy > nYRangeMax) srcy = nYRangeMax; + } + *(short *)pDstY = srcy; - GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Dektec Out] Written frame "LLD" [(0x%X,0x%X,0x%X,0x%X)(0x%X,0x%X,0x%X,0x%X)(0x%X,0x%X,0x%X,0x%X)(0x%X,0x%X,0x%X,0x%X)]\n", dtc->frameNum, DWIDTH, DHEIGHT, - dtc->pixels_UYVY[0], dtc->pixels_UYVY[1], dtc->pixels_UYVY[2], dtc->pixels_UYVY[3], dtc->pixels_UYVY[4], dtc->pixels_UYVY[5], dtc->pixels_UYVY[6], dtc->pixels_UYVY[7], - dtc->pixels_UYVY[8], dtc->pixels_UYVY[9], dtc->pixels_UYVY[10], dtc->pixels_UYVY[11], dtc->pixels_UYVY[12], dtc->pixels_UYVY[13], dtc->pixels_UYVY[14], dtc->pixels_UYVY[15])); - - // commit HANC/VANC - mandatory to form a valid frame - res = dtf->AncCommit(dtc->frameNum); - if (tru && res != DTAPI_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't write VANC for frame "LLD": %s\n", dtc->frameNum, DtapiResult2Str(res))); - return GF_BAD_PARAM; + pSrcY++; + pDstY += sizeof(short); //char type but short buffer } - dtc->frameNum++; + u32 hw = ctx->width / 2; + u32 hh = ctx->height / 2; + + for (u32 i = 0; i < hh; i++) { + //420->422: copy over each U and V line + u8 *p_dst_u = pDstU + sizeof(short) * lineC * i * 2; + u8 *p_dst_v = pDstV + sizeof(short) * lineC * i * 2; + u8 *p_src_u = NULL; + u8 *p_src_v = NULL; + u8 *op_dst_u = p_dst_u; + u8 *op_dst_v = p_dst_v; + + //NV12 formats + if (!pV) { + p_src_u = pSrcU + 2 * ctx->stride_uv * i; + p_src_v = p_src_u + 1; + } + else { + p_src_u = pSrcU + ctx->stride_uv * i; + p_src_v = pSrcV + ctx->stride_uv * i; + } -#ifdef DEKTEC_DUMP_UYVY - char szName[1024]; - sprintf(szName, "test.uyvy"); - FILE *f = fopen(szName, "ab"); - fwrite(dtc->pixels_UYVY, dtc->width*dtc->height*2, 1, f); - fclose(f); -#endif - return GF_OK; + for (u32 j = 0; j < hw; j++) { + u16 srcu = ((u16)(*p_src_u)) << 2; + u16 srcv = ((u16)(*p_src_v)) << 2; + + if (ctx->clip) { + if (srcu < nYRangeMin) srcu = nYRangeMin; + else if (srcu > nYRangeMax) srcu = nYRangeMax; + + if (srcv < nYRangeMin) srcv = nYRangeMin; + else if (srcv > nYRangeMax) srcv = nYRangeMax; + } + + *(short *)p_dst_u = srcu; + p_src_u += 2; + p_dst_u += sizeof(short); //char type but short buffer + + *(short *)p_dst_v = srcv; + p_src_v += 2; + p_dst_v += sizeof(short); //char type but short buffer + } + memcpy(op_dst_u + lineC * sizeof(short), op_dst_u, lineC * sizeof(short)); + memcpy(op_dst_v + lineC * sizeof(short), op_dst_v, lineC * sizeof(short)); + } } - static GF_Err Dektec_LockBackBuffer(GF_VideoOutput *dr, GF_VideoSurface *vi, Bool do_lock) - { - DtContext *dtc = (DtContext*)dr->opaque; - - if (do_lock) { - if (!vi) return GF_BAD_PARAM; - memset(vi, 0, sizeof(GF_VideoSurface)); - vi->height = dtc->height; - vi->width = dtc->width; - vi->video_buffer = dtc->pixels; - vi->pitch_x = dtc->bpp; - vi->pitch_y = dtc->bpp * vi->width;/*the correct value here is dtc->pixel_format, but the soft raster only supports RGB*/ - vi->pixel_format = GF_PIXEL_RGB_24; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DekTecOut] wrote YUV data in "LLU" us\n", gf_sys_clock_high_res() - now)); + gf_filter_pid_drop_packet(cbck->video); + ctx->frameNum++; +} + +static void OnNewFrame(DtMxData *pData, void *pOpaque) { + const DtCbkCtx *ctx = ((const DtCbkCtx*)pOpaque); + if (ctx->video) + OnNewFrameVideo(pData, ctx); + else if (ctx->audio) + OnNewFrameAudio(pData, ctx); +} + +static void dtout_disconnect(GF_DTOutCtx *ctx) +{ + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DekTecOut] Disconnecting\n")); + DtMxProcess *Matrix = ctx->dt_matrix; + ctx->is_configured = GF_FALSE; + + DTAPI_RESULT res = Matrix->Stop(); + if (res != DTAPI_OK) + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't stop transmission: %s\n", DtapiResult2Str(res))); + + Matrix->Reset(); + ctx->dt_device->Detach(); + ctx->is_sending = GF_FALSE; +} + + +static GF_Err dtout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + DTAPI_RESULT res; + int port = 1; + Bool send_play = GF_FALSE; + Bool is4K = GF_FALSE; + Bool is_audio = GF_FALSE; + GF_DTOutCtx *ctx = (GF_DTOutCtx*)gf_filter_get_udta(filter); + const GF_PropertyValue *p; + DtDevice *dt_device = ctx->dt_device; + + if (is_remove) { + if (ctx->audio_cbk.audio == pid) { + ctx->audio_cbk.audio = NULL; + } + else if (ctx->video_cbk.video == pid) { + ctx->video_cbk.video = NULL; + } + if (ctx->is_sending && !ctx->audio_cbk.audio && !ctx->video_cbk.video) { + dtout_disconnect(ctx); } return GF_OK; } - static void Dektec_Shutdown(GF_VideoOutput *dr) - { - GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Dektec Out] Dektec_Shutdown\n")); - DtContext *dtc = (DtContext*)dr->opaque; - DtFrameBuffer *dtf = dtc->dtf; - DTAPI_RESULT res = dtf->Start(false); - if (tru && res != DTAPI_OK) - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't stop transmission: %s\n", DtapiResult2Str(res))); - dtf->Detach(); - dtc->dvc->Detach(); - dtc->isSending = false; - } - - static GF_Err Dektec_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt) - { - DtFrameBuffer *dtf = (DtFrameBuffer*)(((DtContext*)dr->opaque)->dtf); - - if (evt) { - switch (evt->type) { - case GF_EVENT_VIDEO_SETUP: - //FIXME: we do not know how to resize - Dektec_Shutdown(dr); Dektec_attach_start(dr); - GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Dektec Out] resize (%ux%u) received.\n", evt->size.width, evt->size.height)); - if (evt->size.width != DWIDTH || evt->size.height != DHEIGHT) { - GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Dektec Out] Bad resize (%ux%u) received. Expected %dx%d.\n", evt->size.width, evt->size.height, DWIDTH, DHEIGHT)); - if (evt->size.width*evt->size.height > DWIDTH*DHEIGHT) //FIXME - return GF_BAD_PARAM; - } - return GF_OK; - } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (!p) return GF_NOT_SUPPORTED; + if (p->value.uint == GF_STREAM_VISUAL) { + if (!ctx->video_cbk.video) { + ctx->video_cbk.video = pid; + send_play = GF_TRUE; + } + else if (ctx->video_cbk.video != pid) return GF_REQUIRES_NEW_INSTANCE; + } + else if (p->value.uint == GF_STREAM_AUDIO) { + if (!ctx->audio_cbk.audio) { + ctx->audio_cbk.audio = pid; + send_play = GF_TRUE; } + else if (ctx->audio_cbk.audio != pid) return GF_REQUIRES_NEW_INSTANCE; + is_audio=GF_TRUE; + } + else { + return GF_NOT_SUPPORTED; + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p || (p->value.uint != GF_CODECID_RAW)) return GF_NOT_SUPPORTED; + + + if (send_play) { + GF_FilterEvent fevt; + //set a minimum buffer (although we don't buffer) + GF_FEVT_INIT(fevt, GF_FEVT_BUFFER_REQ, pid); + fevt.buffer_req.max_buffer_us = 200000; + gf_filter_pid_send_event(pid, &fevt); + + gf_filter_pid_init_play_event(pid, &fevt, ctx->start, 1.0, "DekTecOut"); + gf_filter_pid_send_event(pid, &fevt); + } + + if (is_audio) { + //todo + return GF_NOT_SUPPORTED; + } + + u32 width = 0; + u32 height = 0; + u32 pix_fmt = 0; + u32 stride = 0; + u32 stride_uv = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + if (p) width = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + if (p) height = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); + if (p) pix_fmt = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE); + if (p) stride = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE_UV); + if (p) stride_uv = p->value.uint; + + //not configured yet + if (!width || !height || !pix_fmt) return GF_OK; + + Bool is_10_bits = GF_FALSE; + switch (pix_fmt) { + case GF_PIXEL_YUV: + break; + case GF_PIXEL_YUV_10: + is_10_bits = GF_TRUE; + break; + case GF_PIXEL_YUV422: + break; + case GF_PIXEL_YUV422_10: + is_10_bits = GF_TRUE; + break; + case GF_PIXEL_YUV444: + break; + case GF_PIXEL_YUV444_10: + is_10_bits = GF_TRUE; + break; + case GF_PIXEL_NV12: + case GF_PIXEL_NV21: + break; + case GF_PIXEL_NV12_10: + case GF_PIXEL_NV21_10: + is_10_bits = GF_TRUE; + break; + default: + GF_PropertyValue val; + val.type = GF_PROP_UINT; + val.value.uint = GF_PIXEL_YUV; + gf_filter_pid_negociate_property(pid, GF_PROP_PID_PIXFMT, &val); return GF_OK; } + if (!stride) stride = width * (is_10_bits ? 2 : 1); + if (!stride_uv) stride_uv = stride / 2; - static GF_Err Dektec_resize(GF_VideoOutput *dr, u32 w, u32 h) - { - DtContext *dtc = (DtContext*)dr->opaque; - if (dtc->pixels) gf_free(dtc->pixels); - if (dtc->pixels_UYVY) gf_free(dtc->pixels_UYVY); - dtc->width = w; - dtc->height = h; - dtc->pixels = (char*)gf_malloc(dtc->bpp * w * h); - if (!dtc->pixels) return GF_OUT_OF_MEM; - memset(dtc->pixels, 0, dtc->bpp * w * h); - dtc->pixels_UYVY = (unsigned char*)gf_malloc(2 * w * h); - if (!dtc->pixels_UYVY) return GF_OUT_OF_MEM; - memset(dtc->pixels_UYVY, 0, 2 * w * h); + GF_Fraction fps = { 30, 1 }; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (p) fps = p->value.frac; + + if ((ctx->width == width) && (ctx->height == height) && (ctx->pix_fmt == pix_fmt) && (ctx->framerate.num * fps.den == ctx->framerate.den * fps.num) && (ctx->stride==stride) && ctx->is_configured) return GF_OK; + + ctx->width = width; + ctx->height = height; + ctx->pix_fmt = pix_fmt; + ctx->framerate = fps; + ctx->is_10b = is_10_bits; + ctx->stride = stride; + ctx->stride_uv = stride_uv; + + gf_pixel_get_size_info((GF_PixelFormat) pix_fmt, width, height, NULL, &ctx->stride, &ctx->stride_uv, &ctx->nb_planes, &ctx->uv_height); + + int dFormat = -1, linkStd = -1; + + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DekTecOut] Reconfigure to %dx%d @ %g FPS %d/%d bits src - SDI clipping: %s\n", width, height, fps.num, fps.den, is_10_bits ? "10" : "8", ctx->clip ? "yes" : "no")); + if (ctx->is_configured) { + DtMxProcess *Matrix = ctx->dt_matrix; + if (ctx->is_sending) { + res = Matrix->Stop(); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't stop transmission: %s\n", DtapiResult2Str(res))); + } + } + Matrix->Reset(); + ctx->is_configured = GF_FALSE; + ctx->is_sending = GF_FALSE; } - GF_Err Dektec_Setup(GF_VideoOutput *dr, void *os_handle, void *os_display, u32 init_flags) - { - GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Dektec Out] Dektec_Setup\n")); - DtContext *dtc = (DtContext*)dr->opaque; - dtc->bpp = 3; //lock_buffer expects RGB - dtc->pixel_format = GF_PIXEL_YV12; - dtc->isSending = false; - Dektec_resize(dr, DWIDTH, DHEIGHT); - - int port = 1; - const char *opt; - opt = gf_modules_get_option((GF_BaseInterface *)dr, "DektecVideo", "SDIOutput"); - if (opt) { - port = atoi(opt); - GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Dektec Out] Using port %d (%s)\n", port, opt)); - } else { - GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Dektec Out] No port specified, using default port: %d\n", port)); + if (fps.num == 30 * fps.den) { + ctx->frame_scale = 30; + ctx->frame_dur = 1; + if ((ctx->width == 1280) && (ctx->height == 720)) { + dFormat = DTAPI_IOCONFIG_720P30; + } + else if ((ctx->width == 1920) && (ctx->height == 1080)) { + dFormat = DTAPI_IOCONFIG_1080P30; + } + else if ((ctx->width == 3840) && (ctx->height == 2160)) { + dFormat = DTAPI_VIDSTD_2160P30; + } + } + else if (fps.num == 50 * fps.den) { + ctx->frame_scale = 50; + ctx->frame_dur = 1; + if ((ctx->width == 1280) && (ctx->height == 720)) { + dFormat = DTAPI_IOCONFIG_720P50; + } + else if ((ctx->width == 1920) && (ctx->height == 1080)) { + dFormat = DTAPI_IOCONFIG_1080P50; + } + else if ((ctx->width == 3840) && (ctx->height == 2160)) { + dFormat = DTAPI_VIDSTD_2160P50; + } + } + else if (fps.num == 60 * fps.den) { + ctx->frame_scale = 60; + ctx->frame_dur = 1; + if ((ctx->width == 1280) && (ctx->height == 720)) { + dFormat = DTAPI_IOCONFIG_720P60; } + else if ((ctx->width == 1920) && (ctx->height == 1080)) { + dFormat = DTAPI_IOCONFIG_1080P60; + } + else if ((ctx->width == 3840) && (ctx->height == 2160)) { + dFormat = DTAPI_VIDSTD_2160P60; + } + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] Dealing with 60 fps video is not stable, use with caution.\n")); + } + else if (fps.num*100 == 5994 * fps.den) { + ctx->frame_scale = 3000; + ctx->frame_dur = 1001; - DtDevice *dvc = dtc->dvc; - DtFrameBuffer *dtf = dtc->dtf; - DTAPI_RESULT res; - res = dvc->AttachToType(2154); - if (tru && res != DTAPI_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] No DTA-2154 in system: %s\n", DtapiResult2Str(res))); - return GF_BAD_PARAM; + if ((ctx->width == 1280) && (ctx->height == 720)) { + dFormat = DTAPI_IOCONFIG_720P59_94; + } + else if ((ctx->width == 1920) && (ctx->height == 1080)) { + dFormat = DTAPI_IOCONFIG_1080P59_94; + } + else if ((ctx->width == 3840) && (ctx->height == 2160)) { + dFormat = DTAPI_VIDSTD_2160P59_94; + } + } + else if (fps.num == 24 * fps.den) { + ctx->frame_scale = 24; + ctx->frame_dur = 1; + if ((ctx->width == 1280) && (ctx->height == 720)) { + dFormat = DTAPI_IOCONFIG_720P24; + } + else if ((ctx->width == 1920) && (ctx->height == 1080)) { + dFormat = DTAPI_IOCONFIG_1080P24; } + else if ((ctx->width == 3840) && (ctx->height == 2160)) { + dFormat = DTAPI_VIDSTD_2160P24; + } + } + else if (fps.num == 25 * fps.den) { + ctx->frame_scale = 25; + ctx->frame_dur = 1; + if ((ctx->width == 1280) && (ctx->height == 720)) { + dFormat = DTAPI_IOCONFIG_720P25; + } + else if ((ctx->width == 1920) && (ctx->height == 1080)) { + dFormat = DTAPI_IOCONFIG_1080P25; + } + else if ((ctx->width == 3840) && (ctx->height == 2160)) { + dFormat = DTAPI_VIDSTD_2160P25; + } + } + + if (dFormat == -1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Unsupported frame rate %d/%d / resolution %dx%d\n", fps.num, fps.den, ctx->width, ctx->height)); + return GF_FILTER_NOT_SUPPORTED; + } + + if ((ctx->width == 3840) && (ctx->height == 2160)) { + is4K = GF_TRUE; + linkStd = DTAPI_VIDLNK_4K_SMPTE425B; + } + + port = ctx->port ? ctx->port : 1; - res = dvc->SetIoConfig(port, DTAPI_IOCONFIG_IODIR, DTAPI_IOCONFIG_OUTPUT, DTAPI_IOCONFIG_OUTPUT); - if (tru && res != DTAPI_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't set I/O config for the device: %s\n", DtapiResult2Str(res))); + DtMxPort mxPort; + if (!is4K) + { + res = dt_device->SetIoConfig(port, DTAPI_IOCONFIG_IODIR, DTAPI_IOCONFIG_OUTPUT, DTAPI_IOCONFIG_OUTPUT); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't set I/O config for the device: %s\n", DtapiResult2Str(res))); return GF_BAD_PARAM; } - res = dtf->AttachToOutput(dvc, port, 0); - if (tru && res != DTAPI_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't attach to port %d: %s\n", port, DtapiResult2Str(res))); + int IoStdValue = -1, IoStdSubValue = -1; + res = DtapiVidStd2IoStd(dFormat, IoStdValue, IoStdSubValue); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Unknown VidStd: %s\n", DtapiResult2Str(res))); return GF_BAD_PARAM; } - int IoStdValue=-1, IoStdSubValue=-1; - res = DtapiVidStd2IoStd(DFORMAT, IoStdValue, IoStdSubValue); - if (tru && res != DTAPI_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Unknown VidStd: %s\n", DtapiResult2Str(res))); + res = dt_device->SetIoConfig(port, DTAPI_IOCONFIG_IOSTD, IoStdValue, IoStdSubValue); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't set I/O config: %s\n", DtapiResult2Str(res))); return GF_BAD_PARAM; } - - res = dtf->SetIoConfig(DTAPI_IOCONFIG_IOSTD, IoStdValue, IoStdSubValue); - if (tru && res != DTAPI_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't set I/O config: %s\n", DtapiResult2Str(res))); + res = mxPort.AddPhysicalPort(dt_device, port); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed to add port %d to logical port", port)); return GF_BAD_PARAM; } - - dtc->frameNum = -1; - - return GF_OK; } - - GF_VideoOutput *NewDektecVideoOutput() + else { - GF_VideoOutput *driv = (GF_VideoOutput *) gf_malloc(sizeof(GF_VideoOutput)); - memset(driv, 0, sizeof(GF_VideoOutput)); - GF_REGISTER_MODULE_INTERFACE(driv, GF_VIDEO_OUTPUT_INTERFACE, "Dektec Video Output", "gpac distribution") - - DtDevice *dvc = new DtDevice; - DtFrameBuffer *dtf = new DtFrameBuffer; - if (!dtf || !dvc) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] DTA API couldn't be initialized.\n")); - delete dvc; - delete dtf; - gf_free(driv); - return NULL; + DtMxPort mxPort4k(dFormat, linkStd); + for (int i = 0; i < 4; i++) + { + res = dt_device->SetIoConfig(i + 1, DTAPI_IOCONFIG_IODIR, DTAPI_IOCONFIG_OUTPUT, DTAPI_IOCONFIG_OUTPUT); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't set I/O config for the device: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } } - DtContext *dtc = new DtContext; - memset(dtc, 0, sizeof(DtContext)); - dtc->dvc = dvc; - dtc->dtf = dtf; - driv->opaque = (void*)dtc; + int IoStdValue = -1, IoStdSubValue = -1; + res = DtapiVidStd2IoStd(dFormat, linkStd, IoStdValue, IoStdSubValue); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Unknown VidStd: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } - driv->Flush = Dektec_Flush; - driv->LockBackBuffer = Dektec_LockBackBuffer; - driv->Setup = Dektec_Setup; - driv->Shutdown = Dektec_Shutdown; - driv->ProcessEvent = Dektec_ProcessEvent; - driv->Blit = Dektec_Blit; + for (int i = 0; i < 4; i++) + { + res = dt_device->SetIoConfig(i + 1, DTAPI_IOCONFIG_IOSTD, IoStdValue, IoStdSubValue); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't set I/O config: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + } + for (int i = 0; i < 4; i++) + { + res = mxPort4k.AddPhysicalPort(dt_device, i + 1); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed to add port %d to logical 4k port", (i + 1))); + return GF_BAD_PARAM; + } + } - driv->hw_caps |= GF_VIDEO_HW_HAS_YUV_OVERLAY | GF_VIDEO_HW_HAS_YUV; + DtIoConfig Cfg; + std::vector<DtIoConfig> IoConfigs; + DtHwFuncDesc* pHwf = dt_device->m_pHwf; + for (int i = 0; i < dt_device->m_pHwf->m_DvcDesc.m_NumPorts; i++, pHwf++) + { + // Check if the port has GENNREF capability + if ((pHwf->m_Flags & DTAPI_CAP_GENREF) == 0) + continue; // No GENREF cap + + Cfg.m_Port = pHwf->m_Port; + Cfg.m_Group = DTAPI_IOCONFIG_GENREF; + + // Check if this is the internal GENREF (has DTAPI_CAP_VIRTUAL)? + if ((pHwf->m_Flags & DTAPI_CAP_VIRTUAL) != 0) + Cfg.m_Value = DTAPI_IOCONFIG_TRUE; // The internal => enable as GENREF + else + Cfg.m_Value = DTAPI_IOCONFIG_FALSE; // Not the internal => disable as GENREF + Cfg.m_SubValue = -1; + Cfg.m_ParXtra[0] = Cfg.m_ParXtra[1] = -1; + // Add to list + IoConfigs.push_back(Cfg); + + // Set the IO-standard for 'THE' GENREF port + if (Cfg.m_Value != DTAPI_IOCONFIG_TRUE) + continue; + Cfg.m_Group = DTAPI_IOCONFIG_IOSTD; + // Set a standard with matching frame rate + DtVidStdInfo VidInfo; + res = ::DtapiGetVidStdInfo(dFormat, linkStd, VidInfo); + Cfg.m_Value = DTAPI_IOCONFIG_HDSDI; + switch ((int)VidInfo.m_Fps) + { + case 23: Cfg.m_SubValue = DTAPI_IOCONFIG_720P23_98; break; + case 24: Cfg.m_SubValue = DTAPI_IOCONFIG_720P24; break; + case 25: Cfg.m_SubValue = DTAPI_IOCONFIG_720P25; break; + case 29: Cfg.m_SubValue = DTAPI_IOCONFIG_720P29_97; break; + case 30: Cfg.m_SubValue = DTAPI_IOCONFIG_720P30; break; + case 50: Cfg.m_SubValue = DTAPI_IOCONFIG_720P50; break; + case 59: Cfg.m_SubValue = DTAPI_IOCONFIG_720P59_94; break; + case 60: Cfg.m_SubValue = DTAPI_IOCONFIG_720P60; break; + } + Cfg.m_ParXtra[0] = Cfg.m_ParXtra[1] = -1; + // Add to list + IoConfigs.push_back(Cfg); + } + res = dt_device->SetIoConfig(IoConfigs.data(), (int)IoConfigs.size()); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed to apply genref\n")); + return GF_BAD_PARAM; + } + mxPort = mxPort4k; + } - return driv; + res = ctx->dt_matrix->AttachRowToOutput(0, mxPort); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Can't attach to port %d: %s\n", port, DtapiResult2Str(res))); + return GF_BAD_PARAM; } - void DeleteDektecVideoOutput(void *ifce) + /* --- Set row configuration --- */ + DtMxRowConfig RowConfig; + RowConfig.m_Enable = true; + RowConfig.m_RowSize = 1; // Keep a history of one frame + + // Enable the video + RowConfig.m_VideoEnable = true; + //todo, make this configurable ! + RowConfig.m_Video.m_PixelFormat = DT_PXFMT_YUV422P_16B; // Use a planar format (16-bit) + RowConfig.m_Video.m_StartLine1 = RowConfig.m_Video.m_StartLine2 = 1; + RowConfig.m_Video.m_NumLines1 = RowConfig.m_Video.m_NumLines2 = -1; + RowConfig.m_Video.m_Scaling = DTAPI_SCALING_OFF; // No scaling for this row + RowConfig.m_Video.m_LineAlignment = 8; // Want an 8-byte alignment + + // Enable audio + RowConfig.m_AudioEnable = true; + // We will add four channels (2x stereo) + for (int i = 0; i < 4; i++) { - GF_VideoOutput *driv = (GF_VideoOutput *) ifce; - DtContext *dtc = (DtContext*)driv->opaque; - gf_free(dtc->pixels); - gf_free(dtc->pixels_UYVY); - delete(dtc->dtf); - delete(dtc->dvc); - delete(dtc); - gf_free(driv); + DtMxAudioConfig AudioConfig; + AudioConfig.m_Index = i; + AudioConfig.m_DeEmbed = false; // Nothing to de-embed for an output only row + AudioConfig.m_OutputMode = DT_OUTPUT_MODE_ADD; // We want to add audio samples + AudioConfig.m_Format = DT_AUDIO_SAMPLE_PCM; // We will add PCM samples + + // Add to config list + RowConfig.m_Audio.push_back(AudioConfig); } + // Apply the row configuration + res = ctx->dt_matrix->SetRowConfig(0, RowConfig); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed to set row 0 config")); + return GF_BAD_PARAM; + } -#ifndef GPAC_STANDALONE_RENDER_2D + /* --- Final Configuration --- */ + // Determine the default recommended end-to-end delay for this configuration + + int DefEnd2EndDelay = 0; + double CbFrames = 0.0; // Time available for the call-back method + ctx->dt_matrix->GetDefEndToEndDelay(DefEnd2EndDelay, CbFrames); + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DekTecOut] Default end-to-end delay = %d Frames\n", DefEnd2EndDelay)); + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DekTecOut] Time-for-Callback = %.1f Frame Period\n", CbFrames)); + // Apply the default end-to-end delay + res = ctx->dt_matrix->SetEndToEndDelay(DefEnd2EndDelay); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed to set the end-to-end delay (delay=%d)", DefEnd2EndDelay)); + return GF_BAD_PARAM; + } - /*interface query*/ - GPAC_MODULE_EXPORT - const u32 *QueryInterfaces() - { - static u32 si [] = { - GF_VIDEO_OUTPUT_INTERFACE, - 0 - }; - return si; + res = ctx->dt_matrix->AddMatrixCbFunc(OnNewFrame, &ctx->audio_cbk); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed register audio callback function")); + return GF_BAD_PARAM; + } + res = ctx->dt_matrix->AddMatrixCbFunc(OnNewFrame, &ctx->video_cbk); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] Failed register video callback function")); + return GF_BAD_PARAM; } - /*interface create*/ - GPAC_MODULE_EXPORT - GF_BaseInterface *LoadInterface(u32 InterfaceType) - { - if (InterfaceType == GF_VIDEO_OUTPUT_INTERFACE) return (GF_BaseInterface *) NewDektecVideoOutput(); - return NULL; + ctx->frameNum = 1; + ctx->is_configured = GF_TRUE; + // Start transmission right now - this function is called during the blit(), and the object clock is initialized + //after the blit, starting now will reduce the number of skipped frames at the dektec startup + res = ctx->dt_matrix->Start(); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DekTecOut] Can't start transmission: %s.\n", DtapiResult2Str(res))); + } + else { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DekTecOut] Transmission started.\n")); + ctx->is_sending = GF_TRUE; + } + return GF_OK; +} + +static GF_Err dtout_initialize(GF_Filter *filter) +{ + GF_DTOutCtx *ctx = (GF_DTOutCtx*)gf_filter_get_udta(filter); + DTAPI_RESULT res; + + ctx->dt_device = new DtDevice; + ctx->dt_matrix = new DtMxProcess; + if (!ctx->dt_matrix || !ctx->dt_device) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] DTA API couldn't be initialized.\n")); + if (ctx->dt_device) delete ctx->dt_device; + if (ctx->dt_matrix) delete ctx->dt_matrix; + ctx->dt_device = NULL; + ctx->dt_matrix = NULL; + return GF_IO_ERR; } - /*interface destroy*/ - GPAC_MODULE_EXPORT - void ShutdownInterface(GF_BaseInterface *ifce) - { - switch (ifce->InterfaceType) { - case GF_VIDEO_OUTPUT_INTERFACE: - DeleteDektecVideoOutput((GF_VideoOutput *)ifce); - break; + ctx->audio_cbk.ctx = ctx; + ctx->video_cbk.ctx = ctx; + + if ((ctx->bus >= 0) && (ctx->slot >= 0)) { + res = ctx->dt_device->AttachToSlot(ctx->bus, ctx->slot); + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] No DTA found on PIC bus %d slot %d: %s - trying enumerating cards\n", ctx->bus, ctx->slot, DtapiResult2Str(res))); + } + else { + return GF_OK; } } + res = ctx->dt_device->AttachToType(2174); + if (res != DTAPI_OK) res = ctx->dt_device->AttachToType(2154); + + if (res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DekTecOut] No DTA 2174 or 2154 in system: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + return GF_OK; +} + +static void dtout_finalize(GF_Filter *filter) +{ + GF_DTOutCtx *ctx = (GF_DTOutCtx*)gf_filter_get_udta(filter); + + if (ctx->is_sending) { + dtout_disconnect(ctx); + } + delete(ctx->dt_matrix); + delete(ctx->dt_device); +} + +#endif //FAKE_DT_API + +static GF_Err dtout_process(GF_Filter *filter) +{ + GF_DTOutCtx *ctx = (GF_DTOutCtx*)gf_filter_get_udta(filter); + if (!ctx->is_configured && !ctx->is_sending) { +#ifndef FAKE_DT_API + if (ctx->audio_cbk.audio) gf_filter_pid_get_packet(ctx->audio_cbk.audio); + if (ctx->video_cbk.video) gf_filter_pid_get_packet(ctx->video_cbk.video); +#endif + } + if (ctx->is_eos) + return GF_EOS; + gf_filter_ask_rt_reschedule(filter, 200000); + return GF_OK; +} - GPAC_MODULE_STATIC_DECLARATION( dektec_out ) +#endif //GPAC_HAS_DTAPI +#ifdef __cplusplus +extern "C" { #endif -} /* extern "C" */ +#include "dektec_video_decl.c" + + +#ifdef __cplusplus +} +#endif diff --git a/modules/dektec_out/dektec_video.h b/modules/dektec_out/dektec_video.h new file mode 100644 index 0000000..825ba14 --- /dev/null +++ b/modules/dektec_out/dektec_video.h @@ -0,0 +1,98 @@ +/* +* GPAC - Multimedia Framework C SDK +* +* Authors: Romain Bouqueau, Jean Le Feuvre +* Copyright (c) 2014-2016 GPAC Licensing +* Copyright (c) 2016-2020 Telecom Paris +* All rights reserved +* +* This file is part of GPAC / Dektec SDI video output filter +* +* GPAC is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* GPAC is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; see the file COPYING. If not, write to +* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +* +*/ + +#ifndef _GF_DEKTECVID_H_ +#define _GF_DEKTECVID_H_ + +#include <gpac/filters.h> + + + +//#define FAKE_DT_API + +#if defined(GPAC_HAS_DTAPI) && !defined(FAKE_DT_API) +#include <DTAPI.h> +#endif + +#include <gpac/constants.h> +#include <gpac/color.h> +#include <gpac/thread.h> + +#if defined(GPAC_HAS_DTAPI) && !defined(FAKE_DT_API) + +#if defined(WIN32) && !defined(__GNUC__) +# include <intrin.h> +#else +# include <emmintrin.h> +#endif + +#endif + + +typedef struct { + struct _dtout_ctx *ctx; + GF_FilterPid *video; //null if audio callback + GF_FilterPid *audio; //null if video callback +} DtCbkCtx; + +typedef struct _dtout_ctx +{ + //opts + s32 bus, slot; + GF_Fraction fps; + Bool clip; + u32 port; + Double start; + + u32 width, height, pix_fmt, stride, stride_uv, uv_height, nb_planes; + GF_Fraction framerate; + Bool is_sending, is_configured, is_10b, is_eos; + +#if defined(GPAC_HAS_DTAPI) && !defined(FAKE_DT_API) + DtMxProcess *dt_matrix; + DtDevice *dt_device; + DtCbkCtx audio_cbk, video_cbk; +#endif + + s64 frameNum; + u64 init_clock, last_frame_time; + + u32 frame_dur, frame_scale; + Bool needs_reconfigure; +} GF_DTOutCtx; + + +#if defined(GPAC_HAS_DTAPI) +GF_Err dtout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove); +GF_Err dtout_initialize(GF_Filter *filter); +void dtout_finalize(GF_Filter *filter); +GF_Err dtout_process(GF_Filter *filter); +#endif + + +#endif //_GF_DEKTECVID_H_ + + diff --git a/modules/dektec_out/dektec_video_decl.c b/modules/dektec_out/dektec_video_decl.c new file mode 100644 index 0000000..5766a9f --- /dev/null +++ b/modules/dektec_out/dektec_video_decl.c @@ -0,0 +1,111 @@ +/* +* GPAC - Multimedia Framework C SDK +* +* Authors: Romain Bouqueau, Jean Le Feuvre +* Copyright (c) 2014-2016 GPAC Licensing +* Copyright (c) 2016-2020 Telecom Paris +* All rights reserved +* +* This file is part of GPAC / Dektec SDI video output filter +* +* GPAC is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* GPAC is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; see the file COPYING. If not, write to +* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +* +*/ + +#include "dektec_video.h" + + +#ifdef GPAC_HAS_DTAPI +#define OFFS(_n) #_n, offsetof(GF_DTOutCtx, _n) +#else +#define OFFS(_n) #_n, -1 + +static GF_Err dtout_process_dummy(GF_Filter *filter) +{ + return GF_OK; +} +#endif + +static const GF_FilterArgs DTOutArgs[] = +{ + { OFFS(bus), "PCI bus number - if not set, device discovery is used", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(slot), "PCI bus number - if not set, device discovery is used", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_HINT_EXPERT }, + { OFFS(fps), "default FPS to use if input stream fps cannot be detected", GF_PROP_FRACTION, "30/1", NULL, GF_FS_ARG_HINT_ADVANCED }, + { OFFS(clip), "clip YUV data to valid SDI range, slower", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED }, + { OFFS(port), "set sdi output port of card", GF_PROP_UINT, "1", NULL, GF_FS_ARG_HINT_ADVANCED }, + { OFFS(start), "set playback start offset, [-1, 0] means percent of media dur, eg -1 == dur", GF_PROP_DOUBLE, "0.0", NULL, GF_FS_ARG_HINT_NORMAL }, + { 0 } +}; + +static GF_FilterCapability DTOutCaps[3]; + +GF_FilterRegister DTOutRegister; + +#ifdef GPAC_HAS_DTAPI +GPAC_MODULE_EXPORT +GF_FilterRegister *RegisterFilter(GF_FilterSession *session) +#else +GF_FilterRegister *dtout_register(GF_FilterSession *session) +#endif +{ + memset(DTOutCaps, 0, sizeof(DTOutCaps)); + memset(&DTOutRegister, 0, sizeof(GF_FilterRegister)); + +#ifndef GPAC_HAS_DTAPI + if (!gf_opts_get_bool("temp", "gendoc")) + return NULL; + DTOutRegister.version = "! Warning: DekTek SDK NOT AVAILABLE IN THIS BUILD !"; +#endif + + DTOutCaps[0].code = GF_PROP_PID_STREAM_TYPE; + DTOutCaps[0].flags = GF_CAPS_INPUT; + DTOutCaps[0].val.type = GF_PROP_UINT; + DTOutCaps[0].val.value.uint = GF_STREAM_VISUAL; + + DTOutCaps[1].code = GF_PROP_PID_STREAM_TYPE; + DTOutCaps[1].flags = GF_CAPS_INPUT; + DTOutCaps[1].val.type = GF_PROP_UINT; + DTOutCaps[1].val.value.uint = GF_STREAM_AUDIO; + + DTOutCaps[2].code = GF_PROP_PID_CODECID; + DTOutCaps[2].flags = GF_CAPS_INPUT; + DTOutCaps[2].val.type = GF_PROP_UINT; + DTOutCaps[2].val.value.uint = GF_CODECID_RAW; + + DTOutRegister.name = "dtout"; +#ifndef GPAC_DISABLE_DOC + DTOutRegister.description = "DekTec SDIOut"; + DTOutRegister.help = "This filter provides SDI output to be used with __DTA 2174__ or __DTA 2154__ cards."; +#endif + DTOutRegister.private_size = sizeof(GF_DTOutCtx); + DTOutRegister.args = DTOutArgs; + DTOutRegister.caps = DTOutCaps; + DTOutRegister.nb_caps = 3; + +#if defined(GPAC_HAS_DTAPI) && !defined(FAKE_DT_API) + DTOutRegister.initialize = dtout_initialize; + DTOutRegister.finalize = dtout_finalize; + DTOutRegister.configure_pid = dtout_configure_pid; + DTOutRegister.process = dtout_process; +#else + DTOutRegister.process = dtout_process_dummy; + +#ifdef GPAC_ENABLE_COVERAGE + dtout_process_dummy(NULL); +#endif + +#endif + return &DTOutRegister; +} diff --git a/modules/dektec_out/dektec_video_old.cpp b/modules/dektec_out/dektec_video_old.cpp new file mode 100644 index 0000000..2358bad --- /dev/null +++ b/modules/dektec_out/dektec_video_old.cpp @@ -0,0 +1,367 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Romain Bouqueau + * Copyright (c) Romain Bouqueau @ GPAC Licensing + * All rights reserved + * + * This file is part of GPAC / Dektec SDI video render module + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#define _DTAPI_DISABLE_AUTO_LINK +#include <DTAPI.h> + +extern "C" { + + /*driver interfaces*/ +#include <gpac/modules/video_out.h> +#include <gpac/modules/audio_out.h> +#include <gpac/constants.h> +#include <gpac/setup.h> + + +//#define DEKTEC_DUMP_UYVY +#ifdef NDEBUG +#define tru 1 +#else +#define tru 1 +#endif + +//TODO: use gf_stretch_bits and get values from config file +#if 1 +#define DWIDTH 1280 +#define DHEIGHT 720 +#define DFORMAT DTAPI_IOCONFIG_720P59_94 +#define DNUMFIELDS 1 +#else +#define DWIDTH 1920 +#define DHEIGHT 1080 +#define DFORMAT DTAPI_IOCONFIG_1080I50 +#define DNUMFIELDS 2 +#endif + + typedef struct + { + char *pixels; + unsigned char *pixels_UYVY; + u32 width, height, pixel_format, bpp; + + DtDevice *dvc; + DtFrameBuffer *dtf; + bool isSending; + s64 frameNum; + } DtContext; + + + void dx_copy_pixels(GF_VideoSurface *dst_s, const GF_VideoSurface *src_s, const GF_Window *src_wnd); //FIXME: referenced from dx + static GF_Err Dektec_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window *src_wnd, GF_Window *dst_wnd, u32 overlay_type) + { + DtContext *dtc = (DtContext*)dr->opaque; + GF_VideoSurface temp_surf; + memset(&temp_surf, 0, sizeof(GF_VideoSurface)); + temp_surf.pixel_format = GF_PIXEL_UYVY; + temp_surf.video_buffer = (char*)dtc->pixels_UYVY; + temp_surf.pitch_y = dtc->width*2; + dx_copy_pixels(&temp_surf, video_src, src_wnd); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Dektec Out] Blit\n")); + return GF_OK; + } + + static GF_Err Dektec_Flush(GF_VideoOutput *dr, GF_Window *dest) + { + DtContext *dtc = (DtContext*)dr->opaque; + DtFrameBuffer *dtf = dtc->dtf; + DTAPI_RESULT res; + if (dest->w != DWIDTH || dest->h != DHEIGHT) { + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Dektec Out] Discard data with wrong dimensions\n")); + if (tru) return GF_OK; + } + if (!dtc->isSending) { + // Start transmission + res = dtf->Start(); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MODULE, ("[Dektec Out] Can't start transmission: %s.\n", DtapiResult2Str(res))); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Dektec Out] Transmission started.\n")); + dtc->isSending = true; + } + } + s64 firstSafeFrame=-1, lastSafeFrame=-1; + res = dtf->WaitFrame(firstSafeFrame, lastSafeFrame); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't get the next frame: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + if (dtc->frameNum == -1) + dtc->frameNum = firstSafeFrame; + + int numLines=-1, numWritten=DWIDTH*DHEIGHT*2/DNUMFIELDS; + res = dtf->WriteVideo(dtc->frameNum, dtc->pixels_UYVY, numWritten, DTAPI_SDI_FIELD1, DTAPI_SDI_8B, 1, numLines); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't write frame "LLD": %s\n", dtc->frameNum, DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + if (DNUMFIELDS > 1) { + assert(DNUMFIELDS == 2); + numLines=-1, numWritten=DWIDTH*DHEIGHT*2/DNUMFIELDS; + res = dtf->WriteVideo(dtc->frameNum, dtc->pixels_UYVY+numWritten, numWritten, DTAPI_SDI_FIELD2, DTAPI_SDI_8B, 1, numLines); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't write frame "LLD": %s\n", dtc->frameNum, DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Dektec Out] Written frame "LLD" [(0x%X,0x%X,0x%X,0x%X)(0x%X,0x%X,0x%X,0x%X)(0x%X,0x%X,0x%X,0x%X)(0x%X,0x%X,0x%X,0x%X)]\n", dtc->frameNum, DWIDTH, DHEIGHT, + dtc->pixels_UYVY[0], dtc->pixels_UYVY[1], dtc->pixels_UYVY[2], dtc->pixels_UYVY[3], dtc->pixels_UYVY[4], dtc->pixels_UYVY[5], dtc->pixels_UYVY[6], dtc->pixels_UYVY[7], + dtc->pixels_UYVY[8], dtc->pixels_UYVY[9], dtc->pixels_UYVY[10], dtc->pixels_UYVY[11], dtc->pixels_UYVY[12], dtc->pixels_UYVY[13], dtc->pixels_UYVY[14], dtc->pixels_UYVY[15])); + + // commit HANC/VANC - mandatory to form a valid frame + res = dtf->AncCommit(dtc->frameNum); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't write VANC for frame "LLD": %s\n", dtc->frameNum, DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + dtc->frameNum++; + +#ifdef DEKTEC_DUMP_UYVY + char szName[1024]; + sprintf(szName, "test.uyvy"); + FILE *f = fopen(szName, "ab"); + fwrite(dtc->pixels_UYVY, dtc->width*dtc->height*2, 1, f); + fclose(f); +#endif + return GF_OK; + } + + static GF_Err Dektec_LockBackBuffer(GF_VideoOutput *dr, GF_VideoSurface *vi, Bool do_lock) + { + DtContext *dtc = (DtContext*)dr->opaque; + + if (do_lock) { + if (!vi) return GF_BAD_PARAM; + memset(vi, 0, sizeof(GF_VideoSurface)); + vi->height = dtc->height; + vi->width = dtc->width; + vi->video_buffer = dtc->pixels; + vi->pitch_x = dtc->bpp; + vi->pitch_y = dtc->bpp * vi->width;/*the correct value here is dtc->pixel_format, but the soft raster only supports RGB*/ + vi->pixel_format = GF_PIXEL_RGB_24; + } + return GF_OK; + } + + static void Dektec_Shutdown(GF_VideoOutput *dr) + { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Dektec Out] Dektec_Shutdown\n")); + DtContext *dtc = (DtContext*)dr->opaque; + DtFrameBuffer *dtf = dtc->dtf; + DTAPI_RESULT res = dtf->Start(false); + if (tru && res != DTAPI_OK) + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't stop transmission: %s\n", DtapiResult2Str(res))); + dtf->Detach(); + dtc->dvc->Detach(); + dtc->isSending = false; + } + + static GF_Err Dektec_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt) + { + //DtFrameBuffer *dtf = (DtFrameBuffer*)(((DtContext*)dr->opaque)->dtf); + + if (evt) { + switch (evt->type) { + case GF_EVENT_VIDEO_SETUP: + //FIXME: we do not know how to resize - Dektec_Shutdown(dr); Dektec_attach_start(dr); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Dektec Out] resize (%ux%u) received.\n", evt->size.width, evt->size.height)); + if (evt->size.width != DWIDTH || evt->size.height != DHEIGHT) { + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Dektec Out] Bad resize (%ux%u) received. Expected %dx%d.\n", evt->size.width, evt->size.height, DWIDTH, DHEIGHT)); + if (evt->size.width*evt->size.height > DWIDTH*DHEIGHT) //FIXME + return GF_BAD_PARAM; + } + return GF_OK; + } + } + return GF_OK; + } + + static GF_Err Dektec_resize(GF_VideoOutput *dr, u32 w, u32 h) + { + DtContext *dtc = (DtContext*)dr->opaque; + if (dtc->pixels) gf_free(dtc->pixels); + if (dtc->pixels_UYVY) gf_free(dtc->pixels_UYVY); + dtc->width = w; + dtc->height = h; + dtc->pixels = (char*)gf_malloc(dtc->bpp * w * h); + if (!dtc->pixels) return GF_OUT_OF_MEM; + memset(dtc->pixels, 0, dtc->bpp * w * h); + dtc->pixels_UYVY = (unsigned char*)gf_malloc(2 * w * h); + if (!dtc->pixels_UYVY) return GF_OUT_OF_MEM; + memset(dtc->pixels_UYVY, 0, 2 * w * h); + return GF_OK; + } + + GF_Err Dektec_Setup(GF_VideoOutput *dr, void *os_handle, void *os_display, u32 init_flags) + { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Dektec Out] Dektec_Setup\n")); + DtContext *dtc = (DtContext*)dr->opaque; + dtc->bpp = 3; //lock_buffer expects RGB + dtc->pixel_format = GF_PIXEL_YV12; + dtc->isSending = false; + Dektec_resize(dr, DWIDTH, DHEIGHT); + + int port = 1; + const char *opt; + opt = gf_opts_get_key("DektecVideo", "SDIOutput"); + if (opt) { + port = atoi(opt); + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Dektec Out] Using port %d (%s)\n", port, opt)); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Dektec Out] No port specified, using default port: %d\n", port)); + } + + DtDevice *dvc = dtc->dvc; + DtFrameBuffer *dtf = dtc->dtf; + DTAPI_RESULT res; + + res = dvc->AttachToType(2174); + if (res != DTAPI_OK) res = dvc->AttachToType(2154); + if (res == DTAPI_OK) res = dvc->AttachToType(-1); + if (res == DTAPI_OK) res = dvc->AttachToSlot(10, 33); + if (res == DTAPI_OK) res = dvc->AttachToSerial(2174000447); + + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] No DTA 2174 or 2154 in system: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + res = dvc->SetIoConfig(port, DTAPI_IOCONFIG_IODIR, DTAPI_IOCONFIG_OUTPUT, DTAPI_IOCONFIG_OUTPUT); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't set I/O config for the device: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + res = dtf->AttachToOutput(dvc, port, 0); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't attach to port %d: %s\n", port, DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + int IoStdValue=-1, IoStdSubValue=-1; + res = DtapiVidStd2IoStd(DFORMAT, IoStdValue, IoStdSubValue); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Unknown VidStd: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + res = dtf->SetIoConfig(DTAPI_IOCONFIG_IOSTD, IoStdValue, IoStdSubValue); + if (tru && res != DTAPI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] Can't set I/O config: %s\n", DtapiResult2Str(res))); + return GF_BAD_PARAM; + } + + dtc->frameNum = -1; + + return GF_OK; + } + + GF_VideoOutput *NewDektecVideoOutput() + { + GF_VideoOutput *driv = (GF_VideoOutput *) gf_malloc(sizeof(GF_VideoOutput)); + memset(driv, 0, sizeof(GF_VideoOutput)); + GF_REGISTER_MODULE_INTERFACE(driv, GF_VIDEO_OUTPUT_INTERFACE, "Dektec Video Output", "gpac distribution") + + DtDevice *dvc = new DtDevice; + DtFrameBuffer *dtf = new DtFrameBuffer; + if (!dtf || !dvc) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Dektec Out] DTA API couldn't be initialized.\n")); + delete dvc; + delete dtf; + gf_free(driv); + return NULL; + } + + DtContext *dtc = new DtContext; + memset(dtc, 0, sizeof(DtContext)); + dtc->dvc = dvc; + dtc->dtf = dtf; + driv->opaque = (void*)dtc; + + driv->Flush = Dektec_Flush; + driv->LockBackBuffer = Dektec_LockBackBuffer; + driv->Setup = Dektec_Setup; + driv->Shutdown = Dektec_Shutdown; + driv->ProcessEvent = Dektec_ProcessEvent; + driv->Blit = Dektec_Blit; + + driv->hw_caps |= GF_VIDEO_HW_HAS_YUV_OVERLAY | GF_VIDEO_HW_HAS_YUV; + + return driv; + } + + void DeleteDektecVideoOutput(void *ifce) + { + GF_VideoOutput *driv = (GF_VideoOutput *) ifce; + DtContext *dtc = (DtContext*)driv->opaque; + gf_free(dtc->pixels); + gf_free(dtc->pixels_UYVY); + delete(dtc->dtf); + delete(dtc->dvc); + delete(dtc); + gf_free(driv); + } + + +#ifndef GPAC_STANDALONE_RENDER_2D + + /*interface query*/ + GPAC_MODULE_EXPORT + const u32 *QueryInterfaces() + { + static u32 si [] = { + GF_VIDEO_OUTPUT_INTERFACE, + 0 + }; + return si; + } + + /*interface create*/ + GPAC_MODULE_EXPORT + GF_BaseInterface *LoadInterface(u32 InterfaceType) + { + if (InterfaceType == GF_VIDEO_OUTPUT_INTERFACE) return (GF_BaseInterface *) NewDektecVideoOutput(); + return NULL; + } + + /*interface destroy*/ + GPAC_MODULE_EXPORT + void ShutdownInterface(GF_BaseInterface *ifce) + { + switch (ifce->InterfaceType) { + case GF_VIDEO_OUTPUT_INTERFACE: + DeleteDektecVideoOutput((GF_VideoOutput *)ifce); + break; + } + } + + + GPAC_MODULE_STATIC_DECLARATION( dektec_out ) + +#endif + +} /* extern "C" */ diff --git a/modules/dektec_out/out_dektec.vcxproj b/modules/dektec_out/out_dektec.vcxproj new file mode 100644 index 0000000..859a1e1 --- /dev/null +++ b/modules/dektec_out/out_dektec.vcxproj @@ -0,0 +1,286 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{042E3638-67F3-4C6C-8C00-CD9BFA416974}</ProjectGuid> + <RootNamespace>out_dektec</RootNamespace> + <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion> + <ProjectName>out_dektec</ProjectName> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseOfMfc>false</UseOfMfc> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseOfMfc>false</UseOfMfc> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseOfMfc>false</UseOfMfc> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseOfMfc>false</UseOfMfc> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup> + <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../../bin/$(Platform)\$(Configuration)/</OutDir> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">../../bin/$(Platform)\$(Configuration)/</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\obj\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\obj\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</LinkIncremental> + <GenerateManifest Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</GenerateManifest> + <GenerateManifest Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</GenerateManifest> + <EmbedManifest Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</EmbedManifest> + <EmbedManifest Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</EmbedManifest> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">../../bin/$(Platform)\$(Configuration)/</OutDir> + <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">../../bin/$(Platform)\$(Configuration)/</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\obj\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\obj\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental> + <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">gf_$(ProjectName)</TargetName> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">gf_$(ProjectName)</TargetName> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">gf_$(ProjectName)</TargetName> + <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">gf_$(ProjectName)</TargetName> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Midl> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MkTypLibCompatible>true</MkTypLibCompatible> + <SuppressStartupBanner>true</SuppressStartupBanner> + <TargetEnvironment>Win32</TargetEnvironment> + <TypeLibraryName>.\obj/out_dektec_deb/out_dektec.tlb</TypeLibraryName> + <HeaderFileName> + </HeaderFileName> + </Midl> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>../../include;../../extra_lib/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>true</MinimalRebuild> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <PrecompiledHeaderOutputFile>$(IntDir)$(ProjectName).pch</PrecompiledHeaderOutputFile> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <SuppressStartupBanner>true</SuppressStartupBanner> + <AdditionalLibraryDirectories>../../extra_lib/lib/$(Platform)\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ModuleDefinitionFile> + </ModuleDefinitionFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)gf_$(ProjectName).pdb</ProgramDatabaseFile> + <ImportLibrary>$(IntDir)gf_$(ProjectName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </Link> + <Manifest> + <OutputManifestFile>$(IntDir)$(TargetFileName).embed.manifest</OutputManifestFile> + </Manifest> + <Bscmake> + <SuppressStartupBanner>true</SuppressStartupBanner> + </Bscmake> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Midl> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MkTypLibCompatible>true</MkTypLibCompatible> + <SuppressStartupBanner>true</SuppressStartupBanner> + <TypeLibraryName>.\obj/out_dektec_deb/out_dektec.tlb</TypeLibraryName> + <HeaderFileName> + </HeaderFileName> + </Midl> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>../../include;../../extra_lib/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <PrecompiledHeaderOutputFile>$(IntDir)$(ProjectName).pch</PrecompiledHeaderOutputFile> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <SuppressStartupBanner>true</SuppressStartupBanner> + <AdditionalLibraryDirectories>../../extra_lib/lib/$(Platform)\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ModuleDefinitionFile> + </ModuleDefinitionFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)gf_$(ProjectName).pdb</ProgramDatabaseFile> + <ImportLibrary>$(IntDir)gf_$(ProjectName).lib</ImportLibrary> + </Link> + <Manifest> + <OutputManifestFile>$(IntDir)$(TargetFileName).embed.manifest</OutputManifestFile> + </Manifest> + <Bscmake> + <SuppressStartupBanner>true</SuppressStartupBanner> + </Bscmake> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Midl> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MkTypLibCompatible>true</MkTypLibCompatible> + <SuppressStartupBanner>true</SuppressStartupBanner> + <TargetEnvironment>Win32</TargetEnvironment> + <TypeLibraryName>.\obj/out_dektec_rel/out_dektec.tlb</TypeLibraryName> + <HeaderFileName> + </HeaderFileName> + </Midl> + <ClCompile> + <Optimization>MaxSpeed</Optimization> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <AdditionalIncludeDirectories>../../include;../../extra_lib/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <PrecompiledHeaderOutputFile>$(IntDir)$(ProjectName).pch</PrecompiledHeaderOutputFile> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <SuppressStartupBanner>true</SuppressStartupBanner> + <AdditionalLibraryDirectories>../../extra_lib/lib/$(Platform)/$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ModuleDefinitionFile> + </ModuleDefinitionFile> + <ProgramDatabaseFile>$(IntDir)gf_$(ProjectName).pdb</ProgramDatabaseFile> + <ImportLibrary>$(IntDir)gf_$(ProjectName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </Link> + <Bscmake> + <SuppressStartupBanner>true</SuppressStartupBanner> + <OutputFile>$(IntDir)$(ProjectName).bsc</OutputFile> + </Bscmake> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <Midl> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MkTypLibCompatible>true</MkTypLibCompatible> + <SuppressStartupBanner>true</SuppressStartupBanner> + <TypeLibraryName>.\obj/out_dektec_rel/out_dektec.tlb</TypeLibraryName> + <HeaderFileName> + </HeaderFileName> + </Midl> + <ClCompile> + <Optimization>Full</Optimization> + <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion> + <AdditionalIncludeDirectories>../../include;../../extra_lib/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_SCL_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <PrecompiledHeaderOutputFile>$(IntDir)$(ProjectName).pch</PrecompiledHeaderOutputFile> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <SuppressStartupBanner>true</SuppressStartupBanner> + <AdditionalLibraryDirectories>../../extra_lib/lib/$(Platform)/$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ModuleDefinitionFile> + </ModuleDefinitionFile> + <ProgramDatabaseFile>$(IntDir)gf_$(ProjectName).pdb</ProgramDatabaseFile> + <ImportLibrary>$(IntDir)gf_$(ProjectName).lib</ImportLibrary> + </Link> + <Bscmake> + <SuppressStartupBanner>true</SuppressStartupBanner> + <OutputFile>$(IntDir)$(ProjectName).bsc</OutputFile> + </Bscmake> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="..\..\modules\dektec_out\dektec_video.cpp" /> + <ClCompile Include="..\..\modules\filter_export.cpp" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\build\msvc14\libgpac_dll.vcxproj"> + <Project>{d3540754-e0cf-4604-ac11-82de9bd4d814}</Project> + </ProjectReference> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project> \ No newline at end of file diff --git a/modules/demo_is/Makefile b/modules/demo_is/Makefile index 291bf60..09b25c2 100644 --- a/modules/demo_is/Makefile +++ b/modules/demo_is/Makefile @@ -2,14 +2,14 @@ include ../../config.mak vpath %.c $(SRC_PATH)/modules/demo_is -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/modules/demo_is/demo_is.c b/modules/demo_is/demo_is.c index fbfff14..c083eee 100644 --- a/modules/demo_is/demo_is.c +++ b/modules/demo_is/demo_is.c @@ -27,7 +27,7 @@ #include <gpac/modules/codec.h> #include <gpac/scenegraph_vrml.h> -static Bool DEV_RegisterDevice(struct __input_device *ifce, const char *urn, GF_BitStream *dsi, void (*AddField)(struct __input_device *_this, u32 fieldType, const char *name)) +static Bool DEV_RegisterDevice(struct __input_device *ifce, const char *urn, const char *dsi, u32 dsi_size, void (*AddField)(struct __input_device *_this, u32 fieldType, const char *name)) { if (strcmp(urn, "DemoSensor")) return 0; diff --git a/modules/epoc_hw/epoc_aout.cpp b/modules/deprecated/epoc_hw/epoc_aout.cpp similarity index 100% rename from modules/epoc_hw/epoc_aout.cpp rename to modules/deprecated/epoc_hw/epoc_aout.cpp diff --git a/modules/epoc_hw/epoc_codec.cpp b/modules/deprecated/epoc_hw/epoc_codec.cpp similarity index 100% rename from modules/epoc_hw/epoc_codec.cpp rename to modules/deprecated/epoc_hw/epoc_codec.cpp diff --git a/modules/epoc_hw/epoc_vout.cpp b/modules/deprecated/epoc_hw/epoc_vout.cpp similarity index 98% rename from modules/epoc_hw/epoc_vout.cpp rename to modules/deprecated/epoc_hw/epoc_vout.cpp index 9904fcf..29572b1 100644 --- a/modules/epoc_hw/epoc_vout.cpp +++ b/modules/deprecated/epoc_hw/epoc_vout.cpp @@ -196,7 +196,7 @@ static GF_Err EVID_InitSurface(GF_VideoOutput *dr) // Define properties for the wanted EGLSurface EGLint atts[7]; - const char *opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "GLNbBitsDepth"); + const char *opt = gf_opts_get_key("core", "gl-bits-depth"); atts[0] = EGL_BUFFER_SIZE; atts[1] = gl_buffer_size; @@ -297,7 +297,7 @@ static GF_Err EVID_SetupOGL_ES_Offscreen(GF_VideoOutput *dr, u32 width, u32 heig // Define properties for the wanted EGLSurface EGLint atts[13]; - const char *opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "GLNbBitsDepth"); + const char *opt = gf_opts_get_key("core", "gl-bits-depth"); atts[0] = EGL_RED_SIZE; atts[1] = 8; atts[2] = EGL_GREEN_SIZE; diff --git a/modules/audio_filter/Makefile b/modules/deprecated/old_arch/audio_filter/Makefile similarity index 91% rename from modules/audio_filter/Makefile rename to modules/deprecated/old_arch/audio_filter/Makefile index 47cc244..489b52d 100644 --- a/modules/audio_filter/Makefile +++ b/modules/deprecated/old_arch/audio_filter/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/modules/audio_filter CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/modules/audio_filter/audio_filter.c b/modules/deprecated/old_arch/audio_filter/audio_filter.c similarity index 100% rename from modules/audio_filter/audio_filter.c rename to modules/deprecated/old_arch/audio_filter/audio_filter.c diff --git a/modules/avcap/Makefile b/modules/deprecated/old_arch/avcap/Makefile similarity index 92% rename from modules/avcap/Makefile rename to modules/deprecated/old_arch/avcap/Makefile index 970cb2d..61ed51e 100644 --- a/modules/avcap/Makefile +++ b/modules/deprecated/old_arch/avcap/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/modules/avcap CFLAGS= $(CXXFLAGS) -I"$(SRC_PATH)/include" $(AVCAP_CFLAGS) -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/modules/avcap/avcap.cpp b/modules/deprecated/old_arch/avcap/avcap.cpp similarity index 98% rename from modules/avcap/avcap.cpp rename to modules/deprecated/old_arch/avcap/avcap.cpp index aa7e927..2a75087 100644 --- a/modules/avcap/avcap.cpp +++ b/modules/deprecated/old_arch/avcap/avcap.cpp @@ -173,7 +173,7 @@ GF_Err AVCap_ConnectService(GF_InputService *plug, GF_ClientService *serv, const vcap->state = 0; vcap->service = serv; - opt = gf_modules_get_option((GF_BaseInterface *)plug, "AVCap", "FlipVideo"); + opt = gf_opts_get_key("AVCap", "FlipVideo"); if (opt && !strcmp(opt, "yes")) vcap->flip_video = GF_TRUE; if (!vcap->device_desc) { @@ -218,7 +218,7 @@ GF_Err AVCap_ConnectService(GF_InputService *plug, GF_ClientService *serv, const vcap->device->getFormatMgr()->setFramerate(30); default_4cc = 0; - opt = gf_modules_get_option((GF_BaseInterface *)plug, "AVCap", "Default4CC"); + opt = gf_opts_get_key("AVCap", "Default4CC"); if (opt) { default_4cc = GF_4CC(opt[0], opt[1], opt[2], opt[3]); vcap->device->getFormatMgr()->setFormat(default_4cc); diff --git a/modules/ffmpeg_in/Makefile b/modules/deprecated/old_arch/ffmpeg_in/Makefile similarity index 82% rename from modules/ffmpeg_in/Makefile rename to modules/deprecated/old_arch/ffmpeg_in/Makefile index f8c9591..73041ea 100644 --- a/modules/ffmpeg_in/Makefile +++ b/modules/deprecated/old_arch/ffmpeg_in/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/modules/ffmpeg_in CFLAGS=-fPIC $(OPTFLAGS) -Wno-deprecated-declarations -I"$(SRC_PATH)/include" $(ffmpeg_cflags) -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif @@ -37,33 +37,33 @@ LINKFLAGS+=-lbz2 endif #old ffmpeg lib -ifeq ($(CONFIG_FFMPEG_OLD), yes) +ifeq ($(CONFIG_FFMPEG_OLD),yes) CFLAGS+=-DFFMPEG_OLD_HEADERS -I/usr/include else LINKFLAGS+=-lswscale endif #local ffmpeg lib -ifeq ($(CONFIG_FFMPEG), local) +ifeq ($(CONFIG_FFMPEG),local) LOCAL_LIB=-L../../extra_lib/lib/gcc CFLAGS+= -I"$(LOCAL_INC_PATH)" endif -ifeq ($(CONFIG_ZLIB), local) +ifeq ($(CONFIG_ZLIB),local) LOCAL_LIB=-L../../extra_lib/lib/gcc endif -SRCS := $(OBJS:.o=.c) +SRCS := $(OBJS:.o=.c) LIB=gm_ffmpeg_in$(DYN_LIB_SUFFIX) ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols ffmpeg_in.def +#LDFLAGS+=-export-symbols ffmpeg_in.def endif #need to add cross-libraries if not local ifeq ($(CROSS_COMPILING),yes) -LINKFLAGS+=-L$(prefix)/lib +LINKFLAGS+=-L$(prefix)/$(libdir) endif @@ -73,13 +73,13 @@ all: $(LIB) $(LIB): $(OBJS) $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) -L../../bin/gcc $(LOCAL_LIB) $(LDFLAGS) $(LINKFLAGS) -clean: +clean: rm -f $(OBJS) ../../bin/gcc/$(LIB) dep: depend depend: - rm -f .depend + rm -f .depend $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend distclean: clean diff --git a/modules/ffmpeg_in/ffmpeg_decode.c b/modules/deprecated/old_arch/ffmpeg_in/ffmpeg_decode.c similarity index 95% rename from modules/ffmpeg_in/ffmpeg_decode.c rename to modules/deprecated/old_arch/ffmpeg_in/ffmpeg_decode.c index d76091d..696081e 100644 --- a/modules/ffmpeg_in/ffmpeg_decode.c +++ b/modules/deprecated/old_arch/ffmpeg_in/ffmpeg_decode.c @@ -34,12 +34,16 @@ #include <libavutil/opt.h> #endif -#if defined(GPAC_ANDROID) || defined(GPAC_IPHONE) +#if defined(GPAC_CONFIG_ANDROID) || defined(GPAC_CONFIG_IOS) #define NO_10bit #endif //#define FFMPEG_DIRECT_DISPATCH +#ifndef AV_INPUT_BUFFER_PADDING_SIZE +#define AV_INPUT_BUFFER_PADDING_SIZE 0 +#endif + /** * Allocates data for FFMPEG decoding * \param oldBuffer The oldBuffer (freed if not NULL) @@ -49,7 +53,7 @@ static uint8_t * ffmpeg_realloc_buffer(uint8_t * oldBuffer, u32 size) { uint8_t * buffer; /* Size of buffer must be larger, see avcodec_decode_video2 documentation */ - u32 allocatedSz = sizeof( char ) * (FF_INPUT_BUFFER_PADDING_SIZE + size); + u32 allocatedSz = sizeof( char ) * (AV_INPUT_BUFFER_PADDING_SIZE + size); if (oldBuffer) gf_free(oldBuffer); buffer = (uint8_t*)gf_malloc( allocatedSz ); @@ -67,7 +71,7 @@ static AVCodec *ffmpeg_get_codec(u32 codec_4cc) codec = avcodec_find_decoder(codec_4cc); if (codec) return codec; - + codec = avcodec_find_decoder_by_name(name); if (codec) return codec; strupr(name); @@ -85,7 +89,7 @@ static AVCodec *ffmpeg_get_codec(u32 codec_4cc) else if (!stricmp(name, "samr") || !stricmp(name, "amr ")) codec = avcodec_find_decoder(CODEC_ID_AMR_NB); else if (!stricmp(name, "sawb")) codec = avcodec_find_decoder(CODEC_ID_AMR_WB); - + return codec; } @@ -274,6 +278,16 @@ static GF_Err FFDEC_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd) codec_id = CODEC_ID_PNG; ffd->is_image = GF_TRUE; break; +#ifdef CODEC_ID_VP9 + case GPAC_OTI_VIDEO_VP9: + codec_id = CODEC_ID_VP9; + break; +#endif +#ifdef CODEC_ID_AV1 + case GPAC_OTI_VIDEO_AV1: + codec_id = CODEC_ID_AV1; + break; +#endif case 0xFF: codec_id = CODEC_ID_SVQ3; break; @@ -298,6 +312,12 @@ static GF_Err FFDEC_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd) case GPAC_OTI_AUDIO_AAC_MPEG4: codec_id = CODEC_ID_AAC; break; +#ifdef CODEC_ID_OPUS + case GPAC_OTI_MEDIA_OPUS: + codec_id = CODEC_ID_OPUS; + (*ctx)->frame_size = 2880;//use max frame size + break; +#endif } } else if ((ffd->st==GF_STREAM_ND_SUBPIC) && (ffd->oti==0xe0)) { @@ -305,16 +325,24 @@ static GF_Err FFDEC_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd) } *codec = avcodec_find_decoder(codec_id); - if (*codec && (codec_id == CODEC_ID_AAC)) { - bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ); - FFDEC_LoadDSI(ffd, bs, *codec, *ctx, GF_FALSE); - gf_bs_del(bs); - } + if (*codec) { + switch (codec_id) { + case CODEC_ID_AAC: +#ifdef CODEC_ID_OPUS + case CODEC_ID_OPUS: + bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ); + FFDEC_LoadDSI(ffd, bs, *codec, *ctx, GF_FALSE); + gf_bs_del(bs); +#endif + break; + + } + } } /*should never happen*/ if (! (*codec)) return GF_OUT_OF_MEM; - + /*not sure this is the right way to do so, no doc in ffmpeg on this topic */ #if 0 /*check HW accel*/ @@ -334,7 +362,11 @@ static GF_Err FFDEC_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd) /*setup MPEG-4 video streams*/ if (ffd->st==GF_STREAM_VISUAL) { /*for all MPEG-4 variants get size*/ - if ((ffd->oti==GPAC_OTI_VIDEO_MPEG4_PART2) || (ffd->oti == GPAC_OTI_VIDEO_AVC) || (ffd->oti == GPAC_OTI_VIDEO_HEVC)) { + if ((ffd->oti==GPAC_OTI_VIDEO_MPEG4_PART2) + || (ffd->oti == GPAC_OTI_VIDEO_AVC) + || (ffd->oti == GPAC_OTI_VIDEO_HEVC) + || (ffd->oti == GPAC_OTI_VIDEO_AV1) + ) { /*if not set this may be a remap of non-mpeg4 transport (eg, transport on MPEG-TS) where the DSI is carried in-band*/ if (esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->data) { @@ -379,21 +411,21 @@ static GF_Err FFDEC_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd) GF_SystemRTInfo rti; u32 nb_threads, detected_nb_threads = 1; const char *sOpt; - sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "OpenHEVC", "ThreadingType"); + sOpt = gf_opts_get_key("OpenHEVC", "ThreadingType"); if (sOpt && !strcmp(sOpt, "wpp")) av_opt_set(*ctx, "thread_type", "slice", 0); else if (sOpt && !strcmp(sOpt, "frame+wpp")) av_opt_set(*ctx, "thread_type", "frameslice", 0); else { av_opt_set(*ctx, "thread_type", "frame", 0); - if (!sOpt) gf_modules_set_option((GF_BaseInterface *)plug, "OpenHEVC", "ThreadingType", "frame"); + if (!sOpt) gf_opts_set_key("OpenHEVC", "ThreadingType", "frame"); } if (gf_sys_get_rti(0, &rti, 0) ) { detected_nb_threads = (rti.nb_cores>1) ? rti.nb_cores-1 : 1; } - sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "OpenHEVC", "NumThreads"); + sOpt = gf_opts_get_key("OpenHEVC", "NumThreads"); if (!sOpt) { char szO[100]; sprintf(szO, "%d", detected_nb_threads); - gf_modules_set_option((GF_BaseInterface *)plug, "OpenHEVC", "NumThreads", szO); + gf_opts_set_key("OpenHEVC", "NumThreads", szO); nb_threads = detected_nb_threads; } else { nb_threads = atoi(sOpt); @@ -409,8 +441,8 @@ static GF_Err FFDEC_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd) /* Set the decoder id */ //av_opt_set_int(openHevcContext->c->priv_data, "decoder-id", i, 0); - sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "OpenHEVC", "CBUnits"); - if (!sOpt) gf_modules_set_option((GF_BaseInterface *)plug, "OpenHEVC", "CBUnits", "4"); + sOpt = gf_opts_get_key("OpenHEVC", "CBUnits"); + if (!sOpt) gf_opts_set_key("OpenHEVC", "CBUnits", "4"); if (sOpt) ffd->output_cb_size = atoi(sOpt); } #endif //HAS_HEVC @@ -427,7 +459,7 @@ static GF_Err FFDEC_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd) if (avcodec_open((*ctx), (*codec) )<0) return GF_NON_COMPLIANT_BITSTREAM; #endif } - + /*setup audio streams*/ if (ffd->st==GF_STREAM_AUDIO) { if ((*codec)->id == CODEC_ID_MP2) { @@ -562,7 +594,7 @@ static GF_Err FFDEC_DetachStream(GF_BaseDecoder *plug, u16 ES_ID) *sws = NULL; } #endif - + return GF_OK; } @@ -577,7 +609,7 @@ static GF_Err FFDEC_GetCapabilities(GF_BaseDecoder *plug, GF_CodecCapability *ca capability->cap.valueInt = 1; return GF_OK; case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = FF_INPUT_BUFFER_PADDING_SIZE; + capability->cap.valueInt = AV_INPUT_BUFFER_PADDING_SIZE; return GF_OK; case GF_CODEC_REORDER: capability->cap.valueInt = 1; @@ -669,7 +701,7 @@ static GF_Err FFDEC_GetCapabilities(GF_BaseDecoder *plug, GF_CodecCapability *ca break; case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = FF_INPUT_BUFFER_PADDING_SIZE; + capability->cap.valueInt = AV_INPUT_BUFFER_PADDING_SIZE; break; default: capability->cap.valueInt = 0; @@ -706,7 +738,7 @@ static GF_Err FFDEC_GetOutputPixelFromat (u32 pix_fmt, u32 * out_pix_fmt, FFDec { if (!out_pix_fmt || !ffd) return GF_BAD_PARAM; - + switch (pix_fmt) { case PIX_FMT_YUV420P: *out_pix_fmt = GF_PIXEL_YV12; @@ -735,13 +767,13 @@ static GF_Err FFDEC_GetOutputPixelFromat (u32 pix_fmt, u32 * out_pix_fmt, FFDec case PIX_FMT_BGR24: *out_pix_fmt = GF_PIXEL_BGR_24; break; - + default: return GF_NOT_SUPPORTED; - + } return GF_OK; - + } @@ -765,7 +797,7 @@ static GF_Err FFDEC_ProcessAudio(FFDec *ffd, pkt.data = (uint8_t *)inBuffer; pkt.size = inBufferLength; #endif - + (*outBufferLength) = 0; /*seeking don't decode*/ @@ -807,7 +839,7 @@ redecode: /*first config*/ if (!ffd->out_size) { u32 bpp = 2; - + if (ctx->channels * ctx->frame_size * bpp < gotpic) ctx->frame_size = gotpic / (bpp * ctx->channels); ffd->out_size = ctx->channels * ctx->frame_size * bpp; } @@ -1015,7 +1047,8 @@ static GF_Err FFDEC_ProcessVideo(FFDec *ffd, /*we have a valid frame not yet dispatched*/ if (ffd->had_pic) { ffd->had_pic = GF_FALSE; - gotpic = 1; + gotpic = ffd->needs_output_resize ? 0 : 1; + ffd->needs_output_resize = 0; } else { if (ffd->check_h264_isma) { /*for AVC bitstreams after ISMA decryption, in case (as we do) the decryption DRM tool @@ -1124,19 +1157,19 @@ static GF_Err FFDEC_ProcessVideo(FFDec *ffd, } stride = frame->linesize[0]; - + ret = FFDEC_GetOutputPixelFromat(ctx->pix_fmt, &out_pix_fmt, ffd); if ( ret < 0) { return ret; } - + /*recompute outsize in case on-the-fly change*/ if ((w != ctx->width) || (h != ctx->height) || (ffd->direct_output_mode && (stride != ffd->stride)) || (ffd->out_pix_fmt != out_pix_fmt) //need to realloc the conversion buffer - + ) { ffd->stride = (ffd->direct_output_mode) ? frame->linesize[0] : ctx->width; @@ -1151,14 +1184,14 @@ static GF_Err FFDEC_ProcessVideo(FFDec *ffd, { outsize = ffd->stride * ctx->height * 3 /2 ; ffd->out_pix_fmt = GF_PIXEL_YV12; - + } - + else if (ctx->pix_fmt == PIX_FMT_YUV422P) { outsize = ffd->stride * ctx->height * 2 ; ffd->out_pix_fmt = GF_PIXEL_YUV422; - + } else if (ctx->pix_fmt == PIX_FMT_YUV444P) { @@ -1183,8 +1216,8 @@ static GF_Err FFDEC_ProcessVideo(FFDec *ffd, outsize = ffd->stride * ctx->height * 3; } #endif - - + + if (ffd->depth_codec) { outsize = 5 * ctx->width * ctx->height / 2; @@ -1200,7 +1233,7 @@ static GF_Err FFDEC_ProcessVideo(FFDec *ffd, video in the output buffer*/ if (!gotpic) { ffd->needs_output_resize = GF_TRUE; - return GF_OK; +// return GF_OK; } *outBufferLength = ffd->out_size; if (ffd->check_h264_isma) { @@ -1217,7 +1250,7 @@ static GF_Err FFDEC_ProcessVideo(FFDec *ffd, if (ffd->direct_output_mode) { ffd->frame_size_changed = GF_TRUE; - + } else { return GF_BUFFER_TOO_SMALL; } @@ -1262,7 +1295,7 @@ static GF_Err FFDEC_ProcessVideo(FFDec *ffd, return GF_OK; } - if (ES_ID == ffd->depth_ES_ID) { + if (ES_ID && (ES_ID == ffd->depth_ES_ID)) { s32 i; u8 *pYO, *pYD; @@ -1320,7 +1353,7 @@ static GF_Err FFDEC_ProcessVideo(FFDec *ffd, pict.data[0] = (uint8_t *)outBuffer; pict.linesize[0] = 4*ctx->width; pix_out = PIX_FMT_RGBA; - } else { + } else { if (ffd->out_pix_fmt == GF_PIXEL_YV12) { pict.data[0] = (uint8_t *)outBuffer; pict.data[1] = (uint8_t *)outBuffer + ffd->stride * ctx->height; @@ -1427,7 +1460,7 @@ static GF_Err FFDEC_GetOutputBuffer(GF_MediaDecoder *ifcg, u16 ES_ID, u8 **pY_or AVFrame *frame; if (ffd->direct_output_mode != 1) return GF_BAD_PARAM; - + if (ES_ID && (ffd->depth_ES_ID==ES_ID)) { frame = ffd->depth_frame; *pY_or_RGB = frame->data[0]; @@ -1489,9 +1522,9 @@ static GF_Err FFDEC_GetOutputFrame(GF_MediaDecoder *ifcg, u16 ES_ID, GF_MediaDec FFDec *ffd = (FFDec*)ifcg->privateStack; *needs_resize = GF_FALSE; - + if (!ffd->base_frame) return GF_BAD_PARAM; - + GF_SAFEALLOC(a_frame, GF_MediaDecoderFrame); if (!a_frame) return GF_OUT_OF_MEM; GF_SAFEALLOC(ff_frame, FF_Frame); @@ -1505,7 +1538,7 @@ static GF_Err FFDEC_GetOutputFrame(GF_MediaDecoder *ifcg, u16 ES_ID, GF_MediaDec a_frame->Release = FFFrame_Release; a_frame->GetPlane = FFFrame_GetPlane; - + *frame = a_frame; if (ffd->frame_size_changed) { ffd->frame_size_changed = GF_FALSE; @@ -1535,7 +1568,7 @@ static u32 FFDEC_CanHandleStream(GF_BaseDecoder *plug, u32 StreamType, GF_ESD *e codec_id = 0; check_4cc = GF_FALSE; - + /*private from FFMPEG input*/ if (ffd->oti == GPAC_OTI_MEDIA_FFMPEG) { bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ); @@ -1548,7 +1581,7 @@ static u32 FFDEC_CanHandleStream(GF_BaseDecoder *plug, u32 StreamType, GF_ESD *e codec_id = gf_bs_read_u32(bs); check_4cc = GF_TRUE; gf_bs_del(bs); - + if (codec_id == GF_4CC('s','a','m','r')) { codec_id = CODEC_ID_AMR_NB; } @@ -1577,6 +1610,12 @@ static u32 FFDEC_CanHandleStream(GF_BaseDecoder *plug, u32 StreamType, GF_ESD *e if (avcodec_find_decoder(codec_id) != NULL) return GF_CODEC_MAYBE_SUPPORTED; break; +#ifdef CODEC_ID_OPUS + case GPAC_OTI_MEDIA_OPUS: + if (avcodec_find_decoder(CODEC_ID_OPUS) != NULL) + return GF_CODEC_MAYBE_SUPPORTED; +#endif + return GF_CODEC_NOT_SUPPORTED; } } @@ -1651,6 +1690,20 @@ static u32 FFDEC_CanHandleStream(GF_BaseDecoder *plug, u32 StreamType, GF_ESD *e return GF_CODEC_MAYBE_SUPPORTED; return GF_CODEC_NOT_SUPPORTED; +#ifdef CODEC_ID_VP9 + case GPAC_OTI_VIDEO_VP9: + if (avcodec_find_decoder(AV_CODEC_ID_VP9) != NULL) + return GF_CODEC_MAYBE_SUPPORTED; + return GF_CODEC_NOT_SUPPORTED; +#endif + +#ifdef CODEC_ID_AV1 + case GPAC_OTI_VIDEO_AV1: + if (avcodec_find_decoder(CODEC_ID_AV1) != NULL) + return GF_CODEC_MAYBE_SUPPORTED; +#endif + return GF_CODEC_NOT_SUPPORTED; + default: return GF_CODEC_NOT_SUPPORTED; } @@ -1728,7 +1781,7 @@ void *FFDEC_Load() #if defined(USE_AVCTX3) && defined(FFMPEG_DIRECT_DISPATCH) ptr->GetOutputFrame = FFDEC_GetOutputFrame; #endif - + GF_REGISTER_MODULE_INTERFACE(ptr, GF_MEDIA_DECODER_INTERFACE, "FFMPEG decoder", "gpac distribution"); return (GF_BaseInterface *) ptr; } diff --git a/modules/ffmpeg_in/ffmpeg_demux.c b/modules/deprecated/old_arch/ffmpeg_in/ffmpeg_demux.c similarity index 98% rename from modules/ffmpeg_in/ffmpeg_demux.c rename to modules/deprecated/old_arch/ffmpeg_in/ffmpeg_demux.c index a674c68..9fe48d5 100644 --- a/modules/ffmpeg_in/ffmpeg_demux.c +++ b/modules/deprecated/old_arch/ffmpeg_in/ffmpeg_demux.c @@ -61,7 +61,7 @@ #endif -#if defined(GPAC_ANDROID) && (LIBAVFORMAT_VERSION_MAJOR <= 52) +#if defined(GPAC_CONFIG_ANDROID) && (LIBAVFORMAT_VERSION_MAJOR <= 52) #ifndef FF_API_CLOSE_INPUT_FILE #define FF_API_CLOSE_INPUT_FILE 1 @@ -227,7 +227,7 @@ static Bool FFD_CanHandleURL(GF_InputService *plug, const char *url) AVFormatContext *ctx; AVOutputFormat *fmt_out; Bool ret = GF_FALSE; - char *ext, szName[1000], szExt[20]; + char *ext, szName[1024], szExt[20]; const char *szExtList; FFDemux *ffd; if (!plug || !url) @@ -243,6 +243,9 @@ static Bool FFD_CanHandleURL(GF_InputService *plug, const char *url) ffd = (FFDemux*)plug->priv; + if (strlen(url) >= sizeof(szName)) + return GF_FALSE; + strcpy(szName, url); ext = strrchr(szName, '#'); if (ext) ext[0] = 0; @@ -252,7 +255,7 @@ static Bool FFD_CanHandleURL(GF_InputService *plug, const char *url) ext = strrchr(szName, '.'); if (ext && strlen(ext) > 19) ext = NULL; - if (ext && strlen(ext) > 1) { + if (ext && strlen(ext) > 1 && strlen(ext) <= sizeof(szExt)) { strcpy(szExt, &ext[1]); strlwr(szExt); #ifndef FFMPEG_DEMUX_ENABLE_MPEG2TS @@ -287,7 +290,7 @@ static Bool FFD_CanHandleURL(GF_InputService *plug, const char *url) ctx = NULL; if (open_file(&ctx, szName, NULL, ffd->options ? &ffd->options : NULL)<0) { - AVInputFormat *av_in = NULL;; + AVInputFormat *av_in = NULL; /*some extensions not supported by ffmpeg*/ if (ext && !strcmp(szExt, "cmp")) av_in = av_find_input_format("m4v"); @@ -328,7 +331,7 @@ static Bool FFD_CanHandleURL(GF_InputService *plug, const char *url) strcpy(szExt, &ext[1]); strlwr(szExt); - szExtList = gf_modules_get_option((GF_BaseInterface *)plug, "MimeTypes", "application/x-ffmpeg"); + szExtList = gf_opts_get_key("MimeTypes", "application/x-ffmpeg"); if (!szExtList) { gf_service_register_mime(plug, "application/x-ffmpeg", szExt, "Other Movies (FFMPEG)"); } else if (!strstr(szExtList, szExt)) { @@ -338,7 +341,7 @@ static Bool FFD_CanHandleURL(GF_InputService *plug, const char *url) buf = (char*)gf_malloc(sizeof(char)*len); sprintf(buf, "\"%s ", szExt); strcat(buf, &szExtList[1]); - gf_modules_set_option((GF_BaseInterface *)plug, "MimeTypes", "application/x-ffmpeg", buf); + gf_opts_set_key("MimeTypes", "application/x-ffmpeg", buf); gf_free(buf); } } @@ -601,7 +604,7 @@ static GF_Err FFD_ConnectService(GF_InputService *plug, GF_ClientService *serv, /*setup wraper for FFMPEG I/O*/ ffd->buffer_size = 8192; - sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "FFMPEG", "IOBufferSize"); + sOpt = gf_opts_get_key("FFMPEG", "IOBufferSize"); if (sOpt) ffd->buffer_size = atoi(sOpt); ffd->buffer = (char*)gf_malloc(sizeof(char)*ffd->buffer_size); #ifdef FFMPEG_DUMP_REMOTE @@ -717,7 +720,7 @@ static GF_Err FFD_ConnectService(GF_InputService *plug, GF_ClientService *serv, } - sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "FFMPEG", "DataBufferMS"); + sOpt = gf_opts_get_key("FFMPEG", "DataBufferMS"); ffd->data_buffer_ms = 0; if (sOpt) ffd->data_buffer_ms = atoi(sOpt); if (!ffd->data_buffer_ms) ffd->data_buffer_ms = FFD_DATA_BUFFER; diff --git a/modules/ffmpeg_in/ffmpeg_in.h b/modules/deprecated/old_arch/ffmpeg_in/ffmpeg_in.h similarity index 95% rename from modules/ffmpeg_in/ffmpeg_in.h rename to modules/deprecated/old_arch/ffmpeg_in/ffmpeg_in.h index 990d66f..952d972 100644 --- a/modules/ffmpeg_in/ffmpeg_in.h +++ b/modules/deprecated/old_arch/ffmpeg_in/ffmpeg_in.h @@ -135,7 +135,7 @@ typedef struct u32 raw_pix_fmt; Bool flipped; u32 direct_output_mode; - + u32 stride; u32 output_cb_size; @@ -143,7 +143,7 @@ typedef struct u32 frame_start; char audio_buf[192000]; Bool check_h264_isma; - + Bool frame_size_changed; u32 base_ES_ID; @@ -288,6 +288,17 @@ void Delete_FFMPEG_Demux(void *ifce); #define CODEC_ID_AMR_NB AV_CODEC_ID_AMR_NB #define CODEC_ID_AMR_WB AV_CODEC_ID_AMR_WB +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 92, 100 ) +#define CODEC_ID_VP9 AV_CODEC_ID_VP9 +#endif + +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 1, 100 ) +#define CODEC_ID_OPUS AV_CODEC_ID_OPUS +#endif + +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(58, 0, 0 ) +#define CODEC_ID_AV1 AV_CODEC_ID_AV1 +#endif #endif diff --git a/modules/ffmpeg_in/ffmpeg_load.c b/modules/deprecated/old_arch/ffmpeg_in/ffmpeg_load.c similarity index 100% rename from modules/ffmpeg_in/ffmpeg_load.c rename to modules/deprecated/old_arch/ffmpeg_in/ffmpeg_load.c diff --git a/modules/freenect/Makefile b/modules/deprecated/old_arch/freenect/Makefile similarity index 78% rename from modules/freenect/Makefile rename to modules/deprecated/old_arch/freenect/Makefile index 7cadc98..07477e6 100644 --- a/modules/freenect/Makefile +++ b/modules/deprecated/old_arch/freenect/Makefile @@ -2,14 +2,14 @@ include ../../config.mak vpath %.c $(SRC_PATH)/modules/freenect -CFLAGS= $(CXXFLAGS) -I"$(SRC_PATH)/include" $(FREENECT_CFLAGS) +CFLAGS= $(CXXFLAGS) -I"$(SRC_PATH)/include" -DGPAC_HAVE_CONFIG_H -I../.. $(FREENECT_CFLAGS) -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif @@ -17,7 +17,7 @@ endif LINKLIBS= -ifeq ($(CONFIG_FREENECT), local) +ifeq ($(CONFIG_FREENECT),local) LINKLIBS+=-L../../extra_lib/lib/gcc CFLAGS+= -I"$(LOCAL_INC_PATH)" endif diff --git a/modules/freenect/freenect.c b/modules/deprecated/old_arch/freenect/freenect.c similarity index 100% rename from modules/freenect/freenect.c rename to modules/deprecated/old_arch/freenect/freenect.c diff --git a/modules/gapi/gapi.cpp b/modules/deprecated/old_arch/gapi/gapi.cpp similarity index 99% rename from modules/gapi/gapi.cpp rename to modules/deprecated/old_arch/gapi/gapi.cpp index 7d6a619..78b18f0 100644 --- a/modules/gapi/gapi.cpp +++ b/modules/deprecated/old_arch/gapi/gapi.cpp @@ -759,7 +759,7 @@ GF_Err GAPI_SetupOGL_ES(GF_VideoOutput *dr) atts[i++] = (gctx->pixel_format==GF_PIXEL_RGB_24) ? 8 : (gctx->pixel_format==GF_PIXEL_RGB_565) ? 6 : 5; atts[i++] = EGL_BLUE_SIZE; atts[i++] = (gctx->pixel_format==GF_PIXEL_RGB_24) ? 8 : 5; - opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "GLNbBitsDepth"); + opt = gf_opts_get_key("core", "gl-bits-depth"); atts[i++] = EGL_DEPTH_SIZE; atts[i++] = opt ? atoi(opt) : 16; atts[i++] = EGL_SURFACE_TYPE; @@ -873,7 +873,7 @@ GF_Err GAPI_SetupOGL_ES_Offscreen(GF_VideoOutput *dr, u32 width, u32 height) atts[5] = 8; atts[6] = EGL_ALPHA_SIZE; atts[7] = (dr->hw_caps & GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA) ? 8 : EGL_DONT_CARE; - opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "GLNbBitsDepth"); + opt = gf_opts_get_key("core", "gl-bits-depth"); atts[8] = EGL_DEPTH_SIZE; atts[9] = opt ? atoi(opt) : 16; @@ -1476,7 +1476,7 @@ static GF_Err GAPI_InitBackBuffer(GF_VideoOutput *dr, u32 VideoWidth, u32 VideoH gctx->off_y = rc.top; gctx->erase_dest = GF_TRUE; - const char *opt = gf_modules_get_option((GF_BaseInterface *)dr, "GAPI", "FBAccess"); + const char *opt = gf_opts_get_key("core", "gapi-fbaccess"); if (!opt || !strcmp(opt, "raw")) gx_mode = 2; else if (opt && !strcmp(opt, "gx")) gx_mode = 1; else gx_mode = 0; diff --git a/modules/gapi/gapi.h b/modules/deprecated/old_arch/gapi/gapi.h similarity index 100% rename from modules/gapi/gapi.h rename to modules/deprecated/old_arch/gapi/gapi.h diff --git a/modules/gdip_raster/gdip_font.cpp b/modules/deprecated/old_arch/gdip_raster/gdip_font.cpp similarity index 97% rename from modules/gdip_raster/gdip_font.cpp rename to modules/deprecated/old_arch/gdip_raster/gdip_font.cpp index d680424..ce71192 100644 --- a/modules/gdip_raster/gdip_font.cpp +++ b/modules/deprecated/old_arch/gdip_raster/gdip_font.cpp @@ -38,11 +38,11 @@ GF_Err gdip_init_font_engine(GF_FontReader *dr) const char *sOpt; FontPriv *ctx = (FontPriv *)dr->udta; - sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "FontEngine", "FontSerif"); + sOpt = gf_opts_get_key("FontCache", "FontSerif"); strcpy(ctx->font_serif, sOpt ? sOpt : "Times New Roman"); - sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "FontEngine", "FontSans"); + sOpt = gf_opts_get_key("FontCache", "FontSans"); strcpy(ctx->font_sans, sOpt ? sOpt : "Arial"); - sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "FontEngine", "FontFixed"); + sOpt = gf_opts_get_key("FontCache", "FontFixed"); strcpy(ctx->font_fixed, sOpt ? sOpt : "Courier New"); return GF_OK; diff --git a/modules/gdip_raster/gdip_grad.cpp b/modules/deprecated/old_arch/gdip_raster/gdip_grad.cpp similarity index 100% rename from modules/gdip_raster/gdip_grad.cpp rename to modules/deprecated/old_arch/gdip_raster/gdip_grad.cpp diff --git a/modules/gdip_raster/gdip_priv.h b/modules/deprecated/old_arch/gdip_raster/gdip_priv.h similarity index 100% rename from modules/gdip_raster/gdip_priv.h rename to modules/deprecated/old_arch/gdip_raster/gdip_priv.h diff --git a/modules/gdip_raster/gdip_rend.cpp b/modules/deprecated/old_arch/gdip_raster/gdip_rend.cpp similarity index 99% rename from modules/gdip_raster/gdip_rend.cpp rename to modules/deprecated/old_arch/gdip_raster/gdip_rend.cpp index 4c6b88b..28304fb 100644 --- a/modules/gdip_raster/gdip_rend.cpp +++ b/modules/deprecated/old_arch/gdip_raster/gdip_rend.cpp @@ -203,11 +203,11 @@ GF_Err gdip_attach_surface_to_buffer(GF_SURFACE _this, char *pixels, u32 width, pFormat = PixelFormat16bppRGB565; if (pitch_x != 2) return GF_NOT_SUPPORTED; break; - case GF_PIXEL_RGB_24: + case GF_PIXEL_RGB: pFormat = PixelFormat24bppRGB; if (pitch_x != 3) return GF_NOT_SUPPORTED; break; - case GF_PIXEL_RGB_32: + case GF_PIXEL_RGBX: pFormat = PixelFormat32bppRGB; if (pitch_x != 4) return GF_NOT_SUPPORTED; break; diff --git a/modules/gdip_raster/gdip_texture.cpp b/modules/deprecated/old_arch/gdip_raster/gdip_texture.cpp similarity index 96% rename from modules/gdip_raster/gdip_texture.cpp rename to modules/deprecated/old_arch/gdip_raster/gdip_texture.cpp index b28605f..725f92f 100644 --- a/modules/gdip_raster/gdip_texture.cpp +++ b/modules/deprecated/old_arch/gdip_raster/gdip_texture.cpp @@ -68,18 +68,18 @@ GF_Err gdip_set_texture(GF_STENCIL _this, char *pixels, u32 width, u32 height, u pFormat = PixelFormat16bppRGB565; BPP = 2; break; - case GF_PIXEL_RGB_24: + case GF_PIXEL_RGB: pFormat = PixelFormat24bppRGB; BPP = 3; /*one day I'll hope to understand how color management works with GDIplus bitmaps...*/ isBGR = 1; // copy = 1; break; - case GF_PIXEL_BGR_24: + case GF_PIXEL_BGR: pFormat = PixelFormat24bppRGB; BPP = 3; break; - case GF_PIXEL_RGB_32: + case GF_PIXEL_RGBX: pFormat = PixelFormat32bppRGB; BPP = 4; break; @@ -93,11 +93,9 @@ GF_Err gdip_set_texture(GF_STENCIL _this, char *pixels, u32 width, u32 height, u copy = GF_TRUE; _sten->orig_buf = (unsigned char *) pixels; break; - case GF_PIXEL_YV12: - case GF_PIXEL_IYUV: - case GF_PIXEL_I420: + case GF_PIXEL_YUV: if ( (width*3)%4) return GF_NOT_SUPPORTED; - _sten->orig_format = GF_PIXEL_YV12; + _sten->orig_format = GF_PIXEL_YUV; is_yuv = GF_TRUE; break; case GF_PIXEL_YUVA: @@ -157,8 +155,8 @@ GF_Err gdip_set_texture(GF_STENCIL _this, char *pixels, u32 width, u32 height, u col = COL_565(val); break; /*scan0 uses bgr...*/ - case GF_PIXEL_BGR_24: - case GF_PIXEL_RGB_24: + case GF_PIXEL_BGR: + case GF_PIXEL_RGB: r = *ptr++; g = *ptr++; b = *ptr++; @@ -169,13 +167,13 @@ GF_Err gdip_set_texture(GF_STENCIL _this, char *pixels, u32 width, u32 height, u } break; /*NOTE: we assume little-endian only for GDIplus platforms, so BGRA/BGRX*/ - case GF_PIXEL_RGB_32: + case GF_PIXEL_RGBX: case GF_PIXEL_ARGB: b = *ptr++; g = *ptr++; r = *ptr++; a = *ptr++; - if (pixelFormat==GF_PIXEL_RGB_32) a = 0xFF; + if (pixelFormat==GF_PIXEL_RGBX) a = 0xFF; col = GF_COL_ARGB(a, r, g, b); break; case GF_PIXEL_RGBA: @@ -361,9 +359,9 @@ void gdip_convert_texture(struct _stencil *sten) u32 BPP, format; GF_VideoSurface src, dst; - if (sten->orig_format == GF_PIXEL_YV12) { + if (sten->orig_format == GF_PIXEL_YUV) { BPP = 3; - dst.pixel_format = GF_PIXEL_BGR_24; + dst.pixel_format = GF_PIXEL_BGR; format = PixelFormat24bppRGB; } else { BPP = 4; diff --git a/modules/hyb_in/Makefile b/modules/deprecated/old_arch/hyb_in/Makefile similarity index 93% rename from modules/hyb_in/Makefile rename to modules/deprecated/old_arch/hyb_in/Makefile index 4683969..67404cb 100644 --- a/modules/hyb_in/Makefile +++ b/modules/deprecated/old_arch/hyb_in/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/modules/hyb_in CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/modules/hyb_in/fm_fake_pull.c b/modules/deprecated/old_arch/hyb_in/fm_fake_pull.c similarity index 100% rename from modules/hyb_in/fm_fake_pull.c rename to modules/deprecated/old_arch/hyb_in/fm_fake_pull.c diff --git a/modules/hyb_in/fm_fake_push.c b/modules/deprecated/old_arch/hyb_in/fm_fake_push.c similarity index 100% rename from modules/hyb_in/fm_fake_push.c rename to modules/deprecated/old_arch/hyb_in/fm_fake_push.c diff --git a/modules/hyb_in/fm_mmbtools.c b/modules/deprecated/old_arch/hyb_in/fm_mmbtools.c similarity index 100% rename from modules/hyb_in/fm_mmbtools.c rename to modules/deprecated/old_arch/hyb_in/fm_mmbtools.c diff --git a/modules/hyb_in/hyb_in.c b/modules/deprecated/old_arch/hyb_in/hyb_in.c similarity index 99% rename from modules/hyb_in/hyb_in.c rename to modules/deprecated/old_arch/hyb_in/hyb_in.c index 8db7996..c77183a 100644 --- a/modules/hyb_in/hyb_in.c +++ b/modules/deprecated/old_arch/hyb_in/hyb_in.c @@ -33,7 +33,7 @@ extern GF_HYBMEDIA master_fm_fake_push; GF_HYBMEDIA* hyb_masters[] = { &master_fm_fake_pull, &master_fm_fake_push, -#ifdef GPAC_ANDROID +#ifdef GPAC_CONFIG_ANDROID &master_fm_mmbtools, #endif }; diff --git a/modules/hyb_in/hyb_in.h b/modules/deprecated/old_arch/hyb_in/hyb_in.h similarity index 100% rename from modules/hyb_in/hyb_in.h rename to modules/deprecated/old_arch/hyb_in/hyb_in.h diff --git a/modules/libplayer/Makefile b/modules/deprecated/old_arch/libplayer/Makefile similarity index 91% rename from modules/libplayer/Makefile rename to modules/deprecated/old_arch/libplayer/Makefile index d4dc479..c322ae2 100644 --- a/modules/libplayer/Makefile +++ b/modules/deprecated/old_arch/libplayer/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/modules/libplayer CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/modules/libplayer/libplayer.c b/modules/deprecated/old_arch/libplayer/libplayer.c similarity index 100% rename from modules/libplayer/libplayer.c rename to modules/deprecated/old_arch/libplayer/libplayer.c diff --git a/modules/mse_in/Makefile b/modules/deprecated/old_arch/mse_in/Makefile similarity index 84% rename from modules/mse_in/Makefile rename to modules/deprecated/old_arch/mse_in/Makefile index 5292ee7..b063b8d 100644 --- a/modules/mse_in/Makefile +++ b/modules/deprecated/old_arch/mse_in/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/modules/mse_in CFLAGS= $(OPTFLAGS) -w -I"$(SRC_PATH)/include" -Wall -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g -DDEBUG LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif @@ -19,12 +19,12 @@ ifeq ($(CONFIG_JS),no) else CFLAGS+=$(JS_FLAGS) ifeq ($(CONFIG_JS),local) -NEED_LOCAL_LIB="yes" +NEED_LOCAL_LIB=yes LOCAL_LIB+=-L../../extra_lib/lib/gcc endif ifeq ($(CONFIG_JS),prefix) -NEED_LOCAL_LIB="yes" -LOCAL_LIB+=-L$(prefix)/lib +NEED_LOCAL_LIB=yes +LOCAL_LIB+=-L$(prefix)/$(libdir) endif LINKLIBS+= $(JS_LIBS) endif @@ -32,7 +32,7 @@ endif #common obj OBJS=mse_in.o -SRCS := $(OBJS:.o=.c) +SRCS := $(OBJS:.o=.c) LIB=gm_mse_in$(DYN_LIB_SUFFIX) @@ -44,13 +44,13 @@ ifeq ($(STATICBUILD),yes) $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_mse_in-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac_static $(LDFLAGS) endif -clean: +clean: rm -f $(OBJS) ../../bin/gcc/$(LIB) dep: depend depend: - rm -f .depend + rm -f .depend $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend distclean: clean diff --git a/modules/mse_in/mse_in.c b/modules/deprecated/old_arch/mse_in/mse_in.c similarity index 99% rename from modules/mse_in/mse_in.c rename to modules/deprecated/old_arch/mse_in/mse_in.c index 6e4d4df..ab7ab75 100644 --- a/modules/mse_in/mse_in.c +++ b/modules/deprecated/old_arch/mse_in/mse_in.c @@ -64,8 +64,12 @@ static GF_HTML_SourceBuffer *MSE_GetSourceBufferForChannel(GF_HTML_MediaSource * u32 i; GF_InputService *parser; ch = (GF_Channel *) channel; - assert(ch->odm && ch->odm->OD); + assert(ch->odm && ch->odm->ID); +#if FILTER_FIXME parser = (GF_InputService *) ch->odm->OD->service_ifce; +#else + parser=NULL; +#endif if (!parser) return NULL; for (i = 0; i < gf_list_count(mediasource->sourceBuffers.list); i++) { GF_HTML_SourceBuffer *sb = (GF_HTML_SourceBuffer *)gf_list_get(mediasource->sourceBuffers.list, i); @@ -212,8 +216,6 @@ static GF_Err MSE_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) return GF_OK; case GF_NET_SERVICE_QUALITY_SWITCH: return GF_NOT_SUPPORTED; - case GF_NET_SERVICE_FLUSH_DATA: - return GF_NOT_SUPPORTED; default: break; } diff --git a/modules/deprecated/old_arch/netctrl/Makefile b/modules/deprecated/old_arch/netctrl/Makefile new file mode 100644 index 0000000..3272103 --- /dev/null +++ b/modules/deprecated/old_arch/netctrl/Makefile @@ -0,0 +1,47 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/netctrl + +CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +LINKLIBS= -lgpac +LOCAL_LIB=../../bin/gcc + +#common objects +OBJS=netctrl.o + +SRCS := $(OBJS:.o=.c) + +LIB=gm_netctrl$(DYN_LIB_SUFFIX) + + +all: $(LIB) + + +$(LIB): $(OBJS) + $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L$(LOCAL_LIB) $(LINKLIBS) $(LDFLAGS) + + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/deprecated/old_arch/netctrl/netctrl.c b/modules/deprecated/old_arch/netctrl/netctrl.c new file mode 100644 index 0000000..0668062 --- /dev/null +++ b/modules/deprecated/old_arch/netctrl/netctrl.c @@ -0,0 +1,212 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2011-2012 + * All rights reserved + * + * This file is part of GPAC / Sampe On-Scvreen Display sub-project + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <gpac/modules/term_ext.h> +#include <gpac/internal/terminal_dev.h> +#include <gpac/internal/compositor_dev.h> +#include <gpac/nodes_mpeg4.h> +#include <gpac/network.h> + +#define XMIN 160 +#define XMAX 480 + +#define YMAX 360 +#define YMIN 120 + +typedef struct +{ + GF_Terminal *term; + GF_Socket *sock; + Bool mouse_down; + u32 cnt; + Float last_min_x, last_max_x, last_min_y, last_max_y; +} GF_NetControl; + +static Bool netctrl_process(GF_TermExt *termext, u32 action, void *param) +{ + const char *sOpt, *server_ip; + int port; + Float face_min_x, face_max_x, face_min_y, face_max_y, gaze_x, gaze_y; + GF_Event event; + GF_NetControl *netctrl = termext->udta; + char message[1024]; + GF_Err e; + u32 bytes; + u32 face = 0; + + switch (action) { + case GF_TERM_EXT_START: + netctrl->term = (GF_Terminal *) param; + + sOpt = gf_opts_get_key("NetControler", "Enabled"); + if (!sOpt || strcmp(sOpt, "yes")) return 0; + sOpt = gf_opts_get_key("NetControler", "ServerIP"); + if (sOpt) server_ip = sOpt; + else server_ip = "127.0.0.1"; + sOpt = gf_opts_get_key("NetControler", "Port"); + if (sOpt) port = atoi(sOpt); + else port = 20320; + + termext->caps |= GF_TERM_EXTENSION_NOT_THREADED; + netctrl->sock = gf_sk_new(GF_SOCK_TYPE_UDP); + + if (netctrl->sock < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[NetControl] Failed to open socket for %s:%d\n", server_ip, port)); + return 0; + } + + e = gf_sk_bind(netctrl->sock, server_ip, port, NULL, 0, 0); + if (e != GF_OK) { + if (netctrl->sock) gf_sk_del(netctrl->sock); + netctrl->sock = NULL; + GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[NetControl] Failed to bind to socket %s:%d\n", server_ip, port)); + return 0; + } + + return 1; + + case GF_TERM_EXT_STOP: + if (netctrl->sock) gf_sk_del(netctrl->sock); + break; + + case GF_TERM_EXT_PROCESS: + gf_sk_receive(netctrl->sock, message, 1024, 0, &bytes); + if (!bytes) break; + message[bytes] = '\0'; + GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[NetControl] received message %s\n", message)); + + if (!strncmp(message, "gpac splice ", 12) || !strncmp(message, "gpac add ", 9) + || !strncmp(message, "gpac select ", 12) + ) { + gf_term_scene_update(netctrl->term, NULL, message); + break; + } + + if (strncmp(message, "gpac:face=", 10)) break; + + sscanf(message, "gpac:face=%d,%f,%f,%f,%f", &face, &face_min_x, &face_max_x, &face_min_y, &face_max_y); + + + memset(&event, 0, sizeof(GF_Event)); + event.mouse.button = GF_MOUSE_LEFT; + if (face == 0) { + if (netctrl->last_min_x < 0.01) { + netctrl->term->compositor->auto_rotate = 2; + } else if (netctrl->last_max_x > 0.90) { + netctrl->term->compositor->auto_rotate = 1; + } else if (netctrl->last_min_y < 0.01) { + netctrl->term->compositor->auto_rotate = 4; + } else if (netctrl->last_max_y > 0.90) { + netctrl->term->compositor->auto_rotate = 3; + } else { + netctrl->cnt++; + if ((netctrl->cnt>=50) && (netctrl->mouse_down)) { + netctrl->term->compositor->auto_rotate = 0; + netctrl->mouse_down = GF_FALSE; + event.type = GF_EVENT_MOUSEUP; + netctrl->term->compositor->video_out->on_event(netctrl->term->compositor->video_out->evt_cbk_hdl, &event); + } + } + break; + } + + netctrl->last_min_x = face_min_x; + netctrl->last_max_x = face_max_x; + netctrl->last_min_y = face_min_y; + netctrl->last_max_y = face_max_y; + + gaze_x = (face_min_x+face_max_x)/2; + gaze_y = (face_min_y+face_max_y)/2; + + event.mouse.x = (1-gaze_x) * netctrl->term->compositor->display_width; + event.mouse.y = (1-gaze_y) * netctrl->term->compositor->display_height; + + if (!netctrl->mouse_down) { + //don't grab if mouse is down + if (netctrl->term->compositor->navigation_state) break; + netctrl->mouse_down = GF_TRUE; + event.type = GF_EVENT_MOUSEDOWN; + netctrl->term->compositor->video_out->on_event(netctrl->term->compositor->video_out->evt_cbk_hdl, &event); + netctrl->cnt = 0; + } + event.type = GF_EVENT_MOUSEMOVE; + netctrl->term->compositor->video_out->on_event(netctrl->term->compositor->video_out->evt_cbk_hdl, &event); + break; + } + return 0; +} + + +GF_TermExt *netctrl_new() +{ + GF_TermExt *dr; + GF_NetControl *netctrl; + dr = (GF_TermExt*)gf_malloc(sizeof(GF_TermExt)); + memset(dr, 0, sizeof(GF_TermExt)); + GF_REGISTER_MODULE_INTERFACE(dr, GF_TERM_EXT_INTERFACE, "GPAC NetControl", "gpac distribution"); + + GF_SAFEALLOC(netctrl, GF_NetControl); + dr->process = netctrl_process; + dr->udta = netctrl; + return dr; +} + + +void netctrl_delete(GF_BaseInterface *ifce) +{ + GF_TermExt *dr = (GF_TermExt *) ifce; + GF_NetControl *netctrl = dr->udta; + gf_free(netctrl); + gf_free(dr); +} + +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_TERM_EXT_INTERFACE, + 0 + }; + return si; +} + +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + if (InterfaceType == GF_TERM_EXT_INTERFACE) return (GF_BaseInterface *)netctrl_new(); + return NULL; +} + +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *ifce) +{ + switch (ifce->InterfaceType) { + case GF_TERM_EXT_INTERFACE: + netctrl_delete(ifce); + break; + } +} + +GPAC_MODULE_STATIC_DECLARATION( netctrl ) diff --git a/modules/opencv_is/Makefile b/modules/deprecated/old_arch/opencv_is/Makefile similarity index 92% rename from modules/opencv_is/Makefile rename to modules/deprecated/old_arch/opencv_is/Makefile index 1d07500..3affb06 100644 --- a/modules/opencv_is/Makefile +++ b/modules/deprecated/old_arch/opencv_is/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/modules/opencv_is CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/modules/opencv_is/demo-sensor.bt b/modules/deprecated/old_arch/opencv_is/demo-sensor.bt similarity index 100% rename from modules/opencv_is/demo-sensor.bt rename to modules/deprecated/old_arch/opencv_is/demo-sensor.bt diff --git a/modules/opencv_is/haarcascade_frontalface_default.xml b/modules/deprecated/old_arch/opencv_is/haarcascade_frontalface_default.xml similarity index 100% rename from modules/opencv_is/haarcascade_frontalface_default.xml rename to modules/deprecated/old_arch/opencv_is/haarcascade_frontalface_default.xml diff --git a/modules/opencv_is/opencv_is.c b/modules/deprecated/old_arch/opencv_is/opencv_is.c similarity index 100% rename from modules/opencv_is/opencv_is.c rename to modules/deprecated/old_arch/opencv_is/opencv_is.c diff --git a/modules/osd/Makefile b/modules/deprecated/old_arch/osd/Makefile similarity index 92% rename from modules/osd/Makefile rename to modules/deprecated/old_arch/osd/Makefile index 5b8e711..b181a9f 100644 --- a/modules/osd/Makefile +++ b/modules/deprecated/old_arch/osd/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/modules/osd CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/modules/osd/osd.c b/modules/deprecated/old_arch/osd/osd.c similarity index 98% rename from modules/osd/osd.c rename to modules/deprecated/old_arch/osd/osd.c index 47ef7a8..d61d789 100644 --- a/modules/osd/osd.c +++ b/modules/deprecated/old_arch/osd/osd.c @@ -174,7 +174,7 @@ Bool osd_load_scene(GF_OSD *osd) strcpy(osd->statBuffer, "Hello World !"); osd->text->string.vals[0] = osd->statBuffer; - opt = gf_cfg_get_key(osd->term->user->config, "OSD", "Visible"); + opt = gf_opts_get_key("OSD", "Visible"); if (!opt || strcmp(opt, "yes")) osd->visible->whichChoice = -1; @@ -212,7 +212,7 @@ static Bool osd_process(GF_TermExt *termext, u32 action, void *param) switch (action) { case GF_TERM_EXT_START: osd->term = (GF_Terminal *) param; - opt = gf_modules_get_option((GF_BaseInterface*)termext, "OSD", "Enabled"); + opt = gf_opts_get_key("OSD", "Enabled"); if (!opt || strcmp(opt, "yes")) return 0; /*load scene*/ diff --git a/modules/platinum/GPACFileMediaServer.cpp b/modules/deprecated/old_arch/platinum/GPACFileMediaServer.cpp similarity index 100% rename from modules/platinum/GPACFileMediaServer.cpp rename to modules/deprecated/old_arch/platinum/GPACFileMediaServer.cpp diff --git a/modules/platinum/GPACFileMediaServer.h b/modules/deprecated/old_arch/platinum/GPACFileMediaServer.h similarity index 100% rename from modules/platinum/GPACFileMediaServer.h rename to modules/deprecated/old_arch/platinum/GPACFileMediaServer.h diff --git a/modules/platinum/GPACMediaController.cpp b/modules/deprecated/old_arch/platinum/GPACMediaController.cpp similarity index 100% rename from modules/platinum/GPACMediaController.cpp rename to modules/deprecated/old_arch/platinum/GPACMediaController.cpp diff --git a/modules/platinum/GPACMediaController.h b/modules/deprecated/old_arch/platinum/GPACMediaController.h similarity index 100% rename from modules/platinum/GPACMediaController.h rename to modules/deprecated/old_arch/platinum/GPACMediaController.h diff --git a/modules/platinum/GPACMediaRenderer.cpp b/modules/deprecated/old_arch/platinum/GPACMediaRenderer.cpp similarity index 99% rename from modules/platinum/GPACMediaRenderer.cpp rename to modules/deprecated/old_arch/platinum/GPACMediaRenderer.cpp index 6c9ab2a..90033bd 100644 --- a/modules/platinum/GPACMediaRenderer.cpp +++ b/modules/deprecated/old_arch/platinum/GPACMediaRenderer.cpp @@ -101,9 +101,9 @@ GPAC_MediaRenderer::SetupServices() // GetMediaInfo m_pAVService->SetStateVariable("NumberOfTracks", "0"); - m_pAVService->SetStateVariable("CurrentMediaDuration", "00:00:00");; + m_pAVService->SetStateVariable("CurrentMediaDuration", "00:00:00"); m_pAVService->SetStateVariable("AVTransportURI", ""); - m_pAVService->SetStateVariable("AVTransportURIMetadata", "");; + m_pAVService->SetStateVariable("AVTransportURIMetadata", ""); m_pAVService->SetStateVariable("NextAVTransportURI", "NOT_IMPLEMENTED"); m_pAVService->SetStateVariable("NextAVTransportURIMetadata", "NOT_IMPLEMENTED"); m_pAVService->SetStateVariable("PlaybackStorageMedium", "NONE"); diff --git a/modules/platinum/GPACMediaRenderer.h b/modules/deprecated/old_arch/platinum/GPACMediaRenderer.h similarity index 100% rename from modules/platinum/GPACMediaRenderer.h rename to modules/deprecated/old_arch/platinum/GPACMediaRenderer.h diff --git a/modules/platinum/GPACPlatinum.cpp b/modules/deprecated/old_arch/platinum/GPACPlatinum.cpp similarity index 96% rename from modules/platinum/GPACPlatinum.cpp rename to modules/deprecated/old_arch/platinum/GPACPlatinum.cpp index 513e260..d142ae7 100644 --- a/modules/platinum/GPACPlatinum.cpp +++ b/modules/deprecated/old_arch/platinum/GPACPlatinum.cpp @@ -365,14 +365,14 @@ void GF_UPnP::Load(GF_Terminal *term) gf_sk_get_host_name((char*)hostname); - opt = gf_cfg_get_key(m_pTerm->user->config, "UPnP", "AllowedDevicesIP"); + opt = gf_opts_get_key("UPnP", "AllowedDevicesIP"); if (!opt) { gf_cfg_set_key(m_pTerm->user->config, "UPnP", "AllowedDevicesIP", ""); opt=""; } m_IPFilter = opt; - opt = gf_cfg_get_key(m_pTerm->user->config, "UPnP", "IgnoreCreatedDevices"); + opt = gf_opts_get_key("UPnP", "IgnoreCreatedDevices"); if (!opt || !strcmp(opt, "yes")) { ignore_local_devices = GF_TRUE; if (!opt) gf_cfg_set_key(m_pTerm->user->config, "UPnP", "IgnoreCreatedDevices", "yes"); @@ -380,31 +380,31 @@ void GF_UPnP::Load(GF_Terminal *term) - opt = gf_cfg_get_key(m_pTerm->user->config, "UPnP", "IgnoreCreatedDevices"); + opt = gf_opts_get_key("UPnP", "IgnoreCreatedDevices"); if (!opt || !strcmp(opt, "yes")) { ignore_local_devices = GF_TRUE; if (!opt) gf_cfg_set_key(m_pTerm->user->config, "UPnP", "IgnoreCreatedDevices", "yes"); } - opt = gf_cfg_get_key(m_pTerm->user->config, "UPnP", "SaveUUIDs"); + opt = gf_opts_get_key("UPnP", "SaveUUIDs"); if (opt && !strcmp(opt, "yes")) save_uuids = GF_TRUE; - opt = gf_cfg_get_key(m_pTerm->user->config, "UPnP", "MediaRendererEnabled"); + opt = gf_opts_get_key("UPnP", "MediaRendererEnabled"); if (!opt || !strcmp(opt, "yes")) { if (!opt) gf_cfg_set_key(m_pTerm->user->config, "UPnP", "MediaRendererEnabled", "yes"); - name = gf_cfg_get_key(m_pTerm->user->config, "UPnP", "MediaRendererName"); + name = gf_opts_get_key("UPnP", "MediaRendererName"); if (!name) { sprintf(friendly_name, "GPAC @ %s", hostname); name = friendly_name; } port = 0; - opt = gf_cfg_get_key(m_pTerm->user->config, "UPnP", "MediaRendererPort"); + opt = gf_opts_get_key("UPnP", "MediaRendererPort"); if (opt) port = atoi(opt); - uuid = gf_cfg_get_key(m_pTerm->user->config, "UPnP", "MediaRendererUUID"); + uuid = gf_opts_get_key("UPnP", "MediaRendererUUID"); if (uuid) { m_pMediaRenderer = new GPAC_MediaRenderer(this, name, false, uuid, port); } else { @@ -417,30 +417,30 @@ void GF_UPnP::Load(GF_Terminal *term) PLT_DeviceHostReference device(m_pMediaRenderer ); device->m_ModelDescription = "GPAC Media Renderer"; - device->m_ModelURL = "http://gpac.sourceforge.net"; - device->m_ModelNumber = GPAC_FULL_VERSION; + device->m_ModelURL = "http://gpac.io"; + device->m_ModelNumber = gf_gpac_version(); device->m_ModelName = "GPAC Media Renderer"; device->m_Manufacturer = "Telecom ParisTech"; device->m_ManufacturerURL = "http://www.telecom-paristech.fr/"; m_pPlatinum->AddDevice(device); } - opt = gf_cfg_get_key(m_pTerm->user->config, "UPnP", "MediaServerEnabled"); + opt = gf_opts_get_key("UPnP", "MediaServerEnabled"); if (!opt || !strcmp(opt, "yes")) { char *media_root; if (!opt) gf_cfg_set_key(m_pTerm->user->config, "UPnP", "MediaServerEnabled", "yes"); - name = gf_cfg_get_key(m_pTerm->user->config, "UPnP", "MediaServerName"); + name = gf_opts_get_key("UPnP", "MediaServerName"); if (!name) { sprintf(friendly_name, "GPAC @ %s", hostname); name = friendly_name; } port = 0; - opt = gf_cfg_get_key(m_pTerm->user->config, "UPnP", "MediaServerPort"); + opt = gf_opts_get_key("UPnP", "MediaServerPort"); if (opt) port = atoi(opt); - uuid = gf_cfg_get_key(m_pTerm->user->config, "UPnP", "MediaServerUUID"); + uuid = gf_opts_get_key("UPnP", "MediaServerUUID"); if (uuid) { m_pMediaServer = new GPAC_FileMediaServer(name, false, uuid, port); } else { @@ -450,7 +450,7 @@ void GF_UPnP::Load(GF_Terminal *term) gf_cfg_set_key(m_pTerm->user->config, "UPnP", "MediaServerUUID", uuid); } } - media_root = (char *) gf_cfg_get_key(m_pTerm->user->config, "UPnP", "MediaServerRoot"); + media_root = (char *) gf_opts_get_key("UPnP", "MediaServerRoot"); if (!media_root) { gf_cfg_set_key(m_pTerm->user->config, "UPnP", "MediaServerRoot", "all:/"); m_pMediaServer->AddSharedDirectory("/", "all"); @@ -476,15 +476,15 @@ void GF_UPnP::Load(GF_Terminal *term) } PLT_DeviceHostReference device(m_pMediaServer); device->m_ModelDescription = "GPAC Media Server"; - device->m_ModelURL = "http://gpac.sourceforge.net"; - device->m_ModelNumber = GPAC_FULL_VERSION; + device->m_ModelURL = "http://gpac.io"; + device->m_ModelNumber = gf_gpac_version(); device->m_ModelName = "GPAC Media Server"; device->m_Manufacturer = "Telecom ParisTech"; device->m_ManufacturerURL = "http://www.telecom-paristech.fr/"; m_pPlatinum->AddDevice(device); } - opt = gf_cfg_get_key(m_pTerm->user->config, "UPnP", "GenericControllerEnabled"); + opt = gf_opts_get_key("UPnP", "GenericControllerEnabled"); if (!opt || !strcmp(opt, "yes")) { if (!opt) gf_cfg_set_key(m_pTerm->user->config, "UPnP", "GenericControllerEnabled", "yes"); /*create our generic control point*/ @@ -495,7 +495,7 @@ void GF_UPnP::Load(GF_Terminal *term) m_pGenericController = new GPAC_GenericController(m_ctrlPtRef, this); } - opt = gf_cfg_get_key(m_pTerm->user->config, "UPnP", "AVCPEnabled"); + opt = gf_opts_get_key("UPnP", "AVCPEnabled"); if (!opt || !strcmp(opt, "yes")) { if (!opt) gf_cfg_set_key(m_pTerm->user->config, "UPnP", "AVCPEnabled", "yes"); @@ -1294,7 +1294,7 @@ static JSBool SMJS_FUNCTION(upnp_share_virtual_resource) } mime = NULL; if (argc==3) mime = SMJS_CHARS(c, argv[2]); - if ((argc==4) && JSVAL_IS_BOOLEAN(argv[3]) && (JSVAL_TO_BOOLEAN(argv[3])==JS_TRUE) ) temp = GF_TRUE;; + if ((argc==4) && JSVAL_IS_BOOLEAN(argv[3]) && (JSVAL_TO_BOOLEAN(argv[3])==JS_TRUE) ) temp = GF_TRUE; upnp->m_pMediaServer->ShareVirtualResource(res_url, res_val, mime ? mime : "application/octet-stream", temp); SMJS_FREE(c, res_url); @@ -1387,13 +1387,13 @@ static JSBool SMJS_FUNCTION(upnp_device_start) str = NULL; if (JS_LookupProperty(device->m_pUPnP->m_pJSCtx, obj, "ModelURL", &sval) && JSVAL_IS_STRING(sval)) str = SMJS_CHARS(c, sval); - device->m_ModelURL = str ? str : "http://gpac.sourceforge.net"; + device->m_ModelURL = str ? str : "http://gpac.io"; SMJS_FREE(c, str); str = NULL; if (JS_LookupProperty(device->m_pUPnP->m_pJSCtx, obj, "ModelNumber", &sval) && JSVAL_IS_STRING(sval)) str = SMJS_CHARS(c, sval); - device->m_ModelNumber = str ? str : GPAC_FULL_VERSION; + device->m_ModelNumber = str ? str : gf_gpac_version(); SMJS_FREE(c, str); str = NULL; @@ -1406,7 +1406,7 @@ static JSBool SMJS_FUNCTION(upnp_device_start) device->m_ManufacturerURL = "http://www.telecom-paristech.fr/"; if (device->m_pUPnP->m_pGenericController) { - const char *opt = gf_cfg_get_key(device->m_pUPnP->m_pTerm->user->config, "UPnP", "IgnoreCreatedDevices"); + const char *opt = gf_opts_get_key("UPnP", "IgnoreCreatedDevices"); if (!opt || !strcmp(opt, "yes")) { device->m_pUPnP->m_pGenericController->m_CtrlPoint->IgnoreUUID(device->GetUUID()); } @@ -1598,11 +1598,11 @@ Bool GF_UPnP::LoadJS(GF_TermExtJS *param) upnp_init_time = gf_sys_clock(); - count = gf_cfg_get_key_count(m_pTerm->user->config, "UPnPDevices"); + count = gf_opts_get_key_count("UPnPDevices"); for (i=0; i<count; i++) { char szFriendlyName[1024], szFile[1024], *sep; - const char *device_id = gf_cfg_get_key_name(m_pTerm->user->config, "UPnPDevices", i); - const char *dev = gf_cfg_get_key(m_pTerm->user->config, "UPnPDevices", device_id); + const char *device_id = gf_opts_get_key_name("UPnPDevices", i); + const char *dev = gf_opts_get_key("UPnPDevices", device_id); if (!strncmp(dev, "off;", 4)) continue; @@ -1653,12 +1653,12 @@ static Bool upnp_process(GF_TermExt *termext, u32 action, void *param) switch (action) { case GF_TERM_EXT_START: - opt = gf_modules_get_option((GF_BaseInterface*)termext, "UPnP", "Enabled"); + opt = gf_opts_get_key("UPnP", "Enabled"); if (!opt) { //UPnP is disabled by default on all platforms until we have a more stable state on load and exit opt = "no"; GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("[UPnP] Disabling UPnP - to enable it, modify section [UPnP] key \"Enabled\" in GPAC config file\n")); - gf_modules_set_option((GF_BaseInterface*)termext, "UPnP", "Enabled", opt); + gf_opts_set_key("UPnP", "Enabled", opt); } if (!strcmp(opt, "yes")) { upnp->Load((GF_Terminal *)param); diff --git a/modules/platinum/GPACPlatinum.h b/modules/deprecated/old_arch/platinum/GPACPlatinum.h similarity index 100% rename from modules/platinum/GPACPlatinum.h rename to modules/deprecated/old_arch/platinum/GPACPlatinum.h diff --git a/modules/platinum/GenericDevice.cpp b/modules/deprecated/old_arch/platinum/GenericDevice.cpp similarity index 100% rename from modules/platinum/GenericDevice.cpp rename to modules/deprecated/old_arch/platinum/GenericDevice.cpp diff --git a/modules/platinum/GenericDevice.h b/modules/deprecated/old_arch/platinum/GenericDevice.h similarity index 100% rename from modules/platinum/GenericDevice.h rename to modules/deprecated/old_arch/platinum/GenericDevice.h diff --git a/modules/platinum/Makefile b/modules/deprecated/old_arch/platinum/Makefile similarity index 78% rename from modules/platinum/Makefile rename to modules/deprecated/old_arch/platinum/Makefile index e8355c6..103c7d3 100644 --- a/modules/platinum/Makefile +++ b/modules/deprecated/old_arch/platinum/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/modules/platinum CFLAGS= $(CXXFLAGS) -I"$(SRC_PATH)/include" -I$(SRC_PATH)/extra_lib/include/platinum/ -DGPAC_HAVE_CONFIG_H -I../.. -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif @@ -18,26 +18,28 @@ LINKLIBS= -lgpac -lPlatinum -lPltMediaServer -lPltMediaConnect -lPltMediaRendere LOCAL_LIB=../../bin/gcc -L../../extra_lib/lib/gcc #common objects -OBJS=GPACFileMediaServer.o GPACMediaController.o GPACMediaRenderer.o GenericDevice.o GPACPlatinum.o +OBJS=GPACFileMediaServer.o GPACMediaController.o GPACMediaRenderer.o GenericDevice.o GPACPlatinum.o ifeq ($(CONFIG_JS),no) else CFLAGS+= $(JS_FLAGS) ifeq ($(CONFIG_JS),local) -NEED_LOCAL_LIB="yes" +NEED_LOCAL_LIB=yes LOCAL_LIB+=-L../../extra_lib/lib/gcc -endif +else LINKLIBS+= $(JS_LIBS) endif +endif -SRCS := $(OBJS:.o=.c) +SRCS := $(OBJS:.o=.c) LIB=gm_platinum$(DYN_LIB_SUFFIX) ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols platinum.def +#LDFLAGS+=-export-symbols platinum.def +LINKLIBS+= -lwsock32 -lws2_32 -static-libstdc++ -static-libgcc endif -ifeq ($(CONFIG_DARWIN), yes) +ifeq ($(CONFIG_DARWIN),yes) LDFLAGS+=-framework Foundation endif @@ -48,13 +50,13 @@ all: $(LIB) $(LIB): $(OBJS) $(CXX) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L$(LOCAL_LIB) $(LINKLIBS) $(LDFLAGS) -clean: +clean: rm -f $(OBJS) ../../bin/gcc/$(LIB) dep: depend depend: - rm -f .depend + rm -f .depend $(CXX) -MM $(CFLAGS) $(SRCS) 1>.depend distclean: clean diff --git a/modules/deprecated/old_arch/psvr/Makefile b/modules/deprecated/old_arch/psvr/Makefile new file mode 100644 index 0000000..f027056 --- /dev/null +++ b/modules/deprecated/old_arch/psvr/Makefile @@ -0,0 +1,47 @@ +include ../../config.mak + +vpath %.c $(SRC_PATH)/modules/psvr + +CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" + +ifeq ($(DEBUGBUILD),yes) +CFLAGS+=-g +LDFLAGS+=-g +endif + +ifeq ($(GPROFBUILD),yes) +CFLAGS+=-pg +LDFLAGS+=-pg +endif + +LINKLIBS= -lgpac -lhidapi-hidraw +LOCAL_LIB=../../bin/gcc + +#common objects +OBJS=psvr.o + +SRCS := $(OBJS:.o=.c) + +LIB=gm_psvr$(DYN_LIB_SUFFIX) + + +all: $(LIB) + + +$(LIB): $(OBJS) + $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L$(LOCAL_LIB) $(LINKLIBS) $(LDFLAGS) + + +clean: + rm -f $(OBJS) ../../bin/gcc/$(LIB) + +dep: depend + +depend: + rm -f .depend + $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend + +distclean: clean + rm -f Makefile.bak .depend + +-include .depend diff --git a/modules/deprecated/old_arch/psvr/psvr.c b/modules/deprecated/old_arch/psvr/psvr.c new file mode 100644 index 0000000..f0eb536 --- /dev/null +++ b/modules/deprecated/old_arch/psvr/psvr.c @@ -0,0 +1,213 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2011-2012 + * All rights reserved + * + * This file is part of GPAC / PSVR input sub-project + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <gpac/modules/term_ext.h> +#include <gpac/internal/terminal_dev.h> +#include <gpac/internal/compositor_dev.h> +#include <gpac/nodes_mpeg4.h> + +#include <hidapi/hidapi.h> + +typedef struct +{ + GF_ObjectManager *odm; + GF_Terminal *term; + GF_Thread *th; + Bool do_run; + void * device; + + Float yaw, pitch, roll; + GF_TermEventFilter evt_filter; + Bool reset_mx; + Bool is_active; +} GF_PSVR; + +#define PSVR_VENDOR_ID 0x054c +#define PSVR_PRODUCT_ID 0x09af + +#define ACCELERATION_COEF 0.00003125f * GF_PI / 180 + +static s16 read_s16(unsigned char *buffer, int offset) +{ + int16_t v; + v = buffer[offset]; + v |= buffer[offset+1] << 8; + return v; +} + +Bool psvr_on_event(void *udta, GF_Event *event, Bool consumed_by_compositor) +{ + GF_PSVR *psvr = (GF_PSVR *)udta; + if (event->type==GF_EVENT_KEYDOWN) { + if (event->key.key_code==GF_KEY_HOME) { + psvr->reset_mx = GF_TRUE; + } + } + else if (event->type==GF_EVENT_SENSOR_REQUEST) { + if (event->activate_sensor.sensor_type==GF_EVENT_SENSOR_ORIENTATION) { + psvr->is_active = event->activate_sensor.activate; + return GF_TRUE; + } + } + return GF_FALSE; +} + +static u32 psvr_run(void *__psvr) +{ + GF_PSVR *psvr = (GF_PSVR *)__psvr; + + hid_init(); + + psvr->device = hid_open(PSVR_VENDOR_ID, PSVR_PRODUCT_ID, 0); + if(!psvr->device) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PSVR] Failed to open PSVR HID device\n")); + return 0; + } + + psvr->evt_filter.udta = psvr; + psvr->evt_filter.on_event = psvr_on_event; + gf_term_add_event_filter(psvr->term, &psvr->evt_filter); + + psvr->is_active = GF_TRUE; + psvr->do_run = GF_TRUE; + while (psvr->do_run && psvr->device) { + unsigned char buffer[64]; + //read device with 1 ms timeout + int size = hid_read_timeout(psvr->device, buffer, 64, 1); + + if (size == 64) { + GF_Event evt; + s32 x_acc = read_s16(buffer, 20) + read_s16(buffer, 36); + s32 y_acc = read_s16(buffer, 22) + read_s16(buffer, 38); + s32 z_acc = read_s16(buffer, 24) + read_s16(buffer, 40); + + if (psvr->reset_mx) { + psvr->reset_mx = 0; + psvr->yaw = psvr->pitch = psvr->roll = 0; + } + + psvr->roll += -y_acc * ACCELERATION_COEF; + psvr->yaw += x_acc * ACCELERATION_COEF; + psvr->pitch += z_acc * ACCELERATION_COEF; + + if (!psvr->is_active ) continue; + + //post our event + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_SENSOR_ORIENTATION; + evt.sensor.x = psvr->yaw - GF_PI; + evt.sensor.y = psvr->pitch; + evt.sensor.z = GF_PI2 - psvr->roll; + evt.sensor.w = 0; + + gf_term_user_event(psvr->term, &evt); + } + } + if (psvr->device) { + hid_close(psvr->device); + psvr->device = 0; + } + gf_term_remove_event_filter(psvr->term, &psvr->evt_filter); + psvr->term = NULL; + hid_exit(); + return 0; +} + +static Bool psvr_process(GF_TermExt *termext, u32 action, void *param) +{ + const char *opt; + GF_PSVR *psvr = termext->udta; + + switch (action) { + case GF_TERM_EXT_START: + psvr->term = (GF_Terminal *) param; + opt = gf_opts_get_key("PSVR", "Enabled"); + if (!opt || strcmp(opt, "yes")) return 0; + + gf_th_run(psvr->th, psvr_run, psvr); + return 1; + + case GF_TERM_EXT_STOP: + psvr->do_run = GF_FALSE; + break; + } + return 0; +} + + +GF_TermExt *psvr_new() +{ + GF_TermExt *dr; + GF_PSVR *psvr; + dr = (GF_TermExt*)gf_malloc(sizeof(GF_TermExt)); + memset(dr, 0, sizeof(GF_TermExt)); + GF_REGISTER_MODULE_INTERFACE(dr, GF_TERM_EXT_INTERFACE, "PSVR Input", "gpac distribution"); + + GF_SAFEALLOC(psvr, GF_PSVR); + dr->process = psvr_process; + dr->udta = psvr; + psvr->th = gf_th_new("PSVR"); + + return dr; +} + + +void psvr_delete(GF_BaseInterface *ifce) +{ + GF_TermExt *dr = (GF_TermExt *) ifce; + GF_PSVR *psvr= dr->udta; + gf_th_del(psvr->th); + gf_free(psvr); + gf_free(dr); +} + +GPAC_MODULE_EXPORT +const u32 *QueryInterfaces() +{ + static u32 si [] = { + GF_TERM_EXT_INTERFACE, + 0 + }; + return si; +} + +GPAC_MODULE_EXPORT +GF_BaseInterface *LoadInterface(u32 InterfaceType) +{ + if (InterfaceType == GF_TERM_EXT_INTERFACE) return (GF_BaseInterface *)psvr_new(); + return NULL; +} + +GPAC_MODULE_EXPORT +void ShutdownInterface(GF_BaseInterface *ifce) +{ + switch (ifce->InterfaceType) { + case GF_TERM_EXT_INTERFACE: + psvr_delete(ifce); + break; + } +} + +GPAC_MODULE_STATIC_DECLARATION( psvr ) diff --git a/modules/rvc_dec/Makefile b/modules/deprecated/old_arch/rvc_dec/Makefile similarity index 91% rename from modules/rvc_dec/Makefile rename to modules/deprecated/old_arch/rvc_dec/Makefile index 43e3f02..3ca4395 100644 --- a/modules/rvc_dec/Makefile +++ b/modules/deprecated/old_arch/rvc_dec/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/modules/rvc_dec CFLAGS= $(OPTFLAGS) -I$(SRC_PATH)/include -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/modules/rvc_dec/rvc_dec.c b/modules/deprecated/old_arch/rvc_dec/rvc_dec.c similarity index 98% rename from modules/rvc_dec/rvc_dec.c rename to modules/deprecated/old_arch/rvc_dec/rvc_dec.c index e25cb4d..6a591d7 100644 --- a/modules/rvc_dec/rvc_dec.c +++ b/modules/deprecated/old_arch/rvc_dec/rvc_dec.c @@ -84,7 +84,7 @@ static GF_Err RVCD_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) ctx->ES_ID = esd->ESID; ctx->width = ctx->height = ctx->out_size = 0; - VTLFolder = (char *)gf_modules_get_option((GF_BaseInterface *)ifcg, "RVCDecoder", "VTLPath"); + VTLFolder = (char *)gf_opts_get_key("RVCDecoder", "VTLPath"); if (!VTLFolder) { GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[RVC Decoder] Cannot locate VTL: path is unknown. Please indicate path in GPAC config file:\n[RVCDecoder]\nVTLPath=PATH\n")); return GF_SERVICE_ERROR; @@ -98,7 +98,7 @@ static GF_Err RVCD_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) FILE *f; u32 size; sprintf(opt, "Predefined_%d", esd->decoderConfig->predefined_rvc_config); - path = (char *) gf_modules_get_option((GF_BaseInterface *)ifcg, "RVCDecoder", (const char *)opt); + path = (char *) gf_opts_get_key("RVCDecoder", (const char *)opt); if (!opt) return GF_NOT_SUPPORTED; f = gf_fopen(path, "rt"); if (!f) return GF_NOT_SUPPORTED; diff --git a/modules/ui_rec/Makefile b/modules/deprecated/old_arch/ui_rec/Makefile similarity index 92% rename from modules/ui_rec/Makefile rename to modules/deprecated/old_arch/ui_rec/Makefile index 4ed60d6..3fdb6b8 100644 --- a/modules/ui_rec/Makefile +++ b/modules/deprecated/old_arch/ui_rec/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/modules/ui_rec CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/modules/ui_rec/readme.txt b/modules/deprecated/old_arch/ui_rec/readme.txt similarity index 100% rename from modules/ui_rec/readme.txt rename to modules/deprecated/old_arch/ui_rec/readme.txt diff --git a/modules/ui_rec/ui_rec.c b/modules/deprecated/old_arch/ui_rec/ui_rec.c similarity index 96% rename from modules/ui_rec/ui_rec.c rename to modules/deprecated/old_arch/ui_rec/ui_rec.c index 40cf0ba..d258856 100644 --- a/modules/ui_rec/ui_rec.c +++ b/modules/deprecated/old_arch/ui_rec/ui_rec.c @@ -149,9 +149,9 @@ static Bool uir_process(GF_TermExt *termext, u32 action, void *param) switch (action) { case GF_TERM_EXT_START: uir->term = (GF_Terminal *) param; - opt = gf_modules_get_option((GF_BaseInterface*)termext, "UIRecord", "Mode"); + opt = gf_opts_get_key("UIRecord", "Mode"); if (!opt) return GF_FALSE; - uifile = gf_modules_get_option((GF_BaseInterface*)termext, "UIRecord", "File"); + uifile = gf_opts_get_key("UIRecord", "File"); if (!uifile) return GF_FALSE; if (!strcmp(opt, "Play")) { @@ -184,7 +184,7 @@ static Bool uir_process(GF_TermExt *termext, u32 action, void *param) gf_term_remove_event_filter(uir->term, &uir->evt_filter); uir->term = NULL; /*auto-disable the plugin by default*/ - gf_modules_set_option((GF_BaseInterface*)termext, "UIRecord", "Mode", "Disable"); + gf_opts_set_key("UIRecord", "Mode", "Disable"); break; case GF_TERM_EXT_PROCESS: diff --git a/modules/widgetman/Makefile b/modules/deprecated/old_arch/widgetman/Makefile similarity index 78% rename from modules/widgetman/Makefile rename to modules/deprecated/old_arch/widgetman/Makefile index 9046281..83515b8 100644 --- a/modules/widgetman/Makefile +++ b/modules/deprecated/old_arch/widgetman/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/modules/widgetman CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif @@ -20,7 +20,7 @@ LOCAL_LIB=-L../../bin/gcc #common objects OBJS=widgetman.o unzip.o widget.o wgt_load.o -ifeq ($(CONFIG_ZLIB), local) +ifeq ($(CONFIG_ZLIB),local) CFLAGS+=-I"$(LOCAL_INC_PATH)/zlib" LOCAL_LIB+= -L../../extra_lib/lib/gcc endif @@ -29,17 +29,19 @@ ifeq ($(CONFIG_JS),no) else CFLAGS+=$(JS_FLAGS) ifeq ($(CONFIG_JS),local) -NEED_LOCAL_LIB="yes" +# if local it is staticly linked with libgpac +NEED_LOCAL_LIB=yes LOCAL_LIB+= -L../../extra_lib/lib/gcc -endif +else LINKLIBS+= $(JS_LIBS) endif +endif -SRCS := $(OBJS:.o=.c) +SRCS := $(OBJS:.o=.c) LIB=gm_widgetman$(DYN_LIB_SUFFIX) ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols widgetman.def +#LDFLAGS+=-export-symbols widgetman.def endif @@ -49,13 +51,13 @@ all: $(LIB) $(LIB): $(OBJS) $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) $(LOCAL_LIB) $(LINKLIBS) $(LDFLAGS) -clean: +clean: rm -f $(OBJS) ../../bin/gcc/$(LIB) dep: depend depend: - rm -f .depend + rm -f .depend $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend distclean: clean diff --git a/modules/widgetman/unzip.c b/modules/deprecated/old_arch/widgetman/unzip.c similarity index 100% rename from modules/widgetman/unzip.c rename to modules/deprecated/old_arch/widgetman/unzip.c diff --git a/modules/widgetman/unzip.h b/modules/deprecated/old_arch/widgetman/unzip.h similarity index 100% rename from modules/widgetman/unzip.h rename to modules/deprecated/old_arch/widgetman/unzip.h diff --git a/modules/widgetman/wgt_load.c b/modules/deprecated/old_arch/widgetman/wgt_load.c similarity index 98% rename from modules/widgetman/wgt_load.c rename to modules/deprecated/old_arch/widgetman/wgt_load.c index 1691f78..32cafb7 100644 --- a/modules/widgetman/wgt_load.c +++ b/modules/deprecated/old_arch/widgetman/wgt_load.c @@ -118,7 +118,7 @@ static GF_Err WGT_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u32 i n = gf_node_new(wgtload->scene->graph, TAG_SVG_script); gf_node_register(n, root); gf_node_list_add_child_last(&((GF_ParentNode *)root)->children, n, &last); - path = gf_modules_get_option((GF_BaseInterface *)plug, "Widgets", "WidgetLoadScript"); + path = gf_opts_get_key("Widgets", "WidgetLoadScript"); jsfile = path ? gf_fopen(path, "rt") : NULL; if (jsfile) { gf_fclose(jsfile); @@ -141,7 +141,7 @@ static GF_Err WGT_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u32 i gf_node_init(n); - wmpath = gf_modules_get_option((GF_BaseInterface *)plug, "Widgets", "WidgetManagerScript"); + wmpath = gf_opts_get_key("Widgets", "WidgetManagerScript"); jsfile = wmpath ? gf_fopen(wmpath, "rt") : NULL; if (jsfile) { gf_fclose(jsfile); diff --git a/modules/widgetman/wgt_load_base.js b/modules/deprecated/old_arch/widgetman/wgt_load_base.js similarity index 100% rename from modules/widgetman/wgt_load_base.js rename to modules/deprecated/old_arch/widgetman/wgt_load_base.js diff --git a/modules/widgetman/widget.c b/modules/deprecated/old_arch/widgetman/widget.c similarity index 98% rename from modules/widgetman/widget.c rename to modules/deprecated/old_arch/widgetman/widget.c index f4f129a..cbed972 100644 --- a/modules/widgetman/widget.c +++ b/modules/deprecated/old_arch/widgetman/widget.c @@ -342,7 +342,7 @@ if (!strcmp(prop_name, "viewMode")) { *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, "floating") ); } else if (!strcmp(prop_name, "locale")) { - opt = gf_cfg_get_key(wid->widget->wm->term->user->config, "Systems", "Language2CC"); + opt = gf_opts_get_key("core", "lang"); if (!opt) opt = "und"; *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, opt) ); } @@ -368,11 +368,11 @@ else if (!strcmp(prop_name, "description")) { if (wid->widget->description) *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, wid->widget->description) ); } else if (!strcmp(prop_name, "width")) { - opt = gf_cfg_get_key(wid->widget->wm->term->user->config, (const char *) wid->secname, "width"); + opt = gf_opts_get_key((const char *) wid->secname, "width"); *vp = INT_TO_JSVAL( (opt ? atoi(opt) : 0) ); } else if (!strcmp(prop_name, "height")) { - opt = gf_cfg_get_key(wid->widget->wm->term->user->config, (const char *) wid->secname, "height"); + opt = gf_opts_get_key((const char *) wid->secname, "height"); *vp = INT_TO_JSVAL( (opt ? atoi(opt) : 0) ); } else if (!strcmp(prop_name, "preferences")) { diff --git a/modules/widgetman/widgetman.c b/modules/deprecated/old_arch/widgetman/widgetman.c similarity index 98% rename from modules/widgetman/widgetman.c rename to modules/deprecated/old_arch/widgetman/widgetman.c index 1881a34..6d5e41e 100644 --- a/modules/widgetman/widgetman.c +++ b/modules/deprecated/old_arch/widgetman/widgetman.c @@ -135,7 +135,7 @@ static void widget_package_extract_file(GF_WidgetPackage *wpack, GF_WidgetPackag Bool self_ref; const char *item_name; - gf_isom_get_meta_item_info(isom, GF_TRUE, 0, i+1, &ID, NULL, &self_ref, &item_name, NULL, &enc, &url, &urn); + gf_isom_get_meta_item_info(isom, GF_TRUE, 0, i+1, &ID, NULL, NULL, &self_ref, &item_name, NULL, &enc, &url, &urn); if (strcmp(res->inner_path, item_name)) continue; gf_isom_extract_meta_item(isom, GF_TRUE, 0, ID, res->extracted_path); @@ -185,7 +185,7 @@ static Bool widget_package_relocate_uri(void *__self, const char *parent_uri, co } /* First try to locate the resource in the locales folder */ - opt = gf_cfg_get_key(wpack->wm->term->user->config, "Systems", "Language2CC"); + opt = gf_opts_get_key("core", "lang"); if (opt) { if (!strcmp(opt, "*") || !strcmp(opt, "un") ) opt = NULL; @@ -266,7 +266,7 @@ static GF_WidgetPackage *widget_isom_new(GF_WidgetManager *wm, const char *path) wzip->wm = wm; wzip->relocate_uri = widget_package_relocate_uri; wzip->resources = gf_list_new(); - dir = gf_cfg_get_key(wm->term->user->config, "General", "CacheDirectory"); + dir = gf_opts_get_key("core", "cache"); /* create the extracted path for the package root using: the cache dir + a CRC of the file path and the instance*/ sprintf(wzip->root_extracted_path, "%s%p", path, wzip); @@ -307,7 +307,7 @@ static GF_WidgetPackage *widget_isom_new(GF_WidgetManager *wm, const char *path) char *sep; const char *item_name; - gf_isom_get_meta_item_info(isom, GF_TRUE, 0, i+1, &ID, NULL, &self_ref, &item_name, NULL, &enc, &url, &urn); + gf_isom_get_meta_item_info(isom, GF_TRUE, 0, i+1, &ID, NULL, NULL, &self_ref, &item_name, NULL, &enc, &url, &urn); sep = strrchr(item_name, '/'); if (!sep) sep = strrchr(item_name, '\\'); @@ -357,7 +357,7 @@ static GF_WidgetPackage *widget_zip_new(GF_WidgetManager *wm, const char *path) wzip->relocate_uri = widget_package_relocate_uri; wzip->resources = gf_list_new(); wzip->package_path = gf_strdup(path); - dir = gf_cfg_get_key(wm->term->user->config, "General", "CacheDirectory"); + dir = gf_opts_get_key("core", "cache"); /* create the extracted path for the package root using: the cache dir + a CRC of the file path and the instance*/ sprintf(wzip->root_extracted_path, "%s%p", path, wzip); @@ -1130,7 +1130,7 @@ static void on_widget_activated(JSContext *c, JSObject *obj) /*get stored value for this preference*/ - value = gf_cfg_get_key(wid->widget->wm->term->user->config, (const char *)wid->secname, pref->name); + value = gf_opts_get_key((const char *)wid->secname, pref->name); /*if none found, use preference*/ if (!value) value = pref->value; @@ -1646,7 +1646,7 @@ static JSBool SMJS_FUNCTION(wm_widget_get_context) } /*read from config*/ else { - att = (char *)gf_cfg_get_key(wid->widget->wm->term->user->config, (const char *) wid->secname, pref->name); + att = (char *)gf_opts_get_key((const char *) wid->secname, pref->name); if (!att) att = pref->value; if (att) gf_bs_write_data(bs, (const char *) att, (u32) strlen(att) ); @@ -1909,7 +1909,7 @@ else if (!strcmp(prop_name, "originating_device_ip") else { char szName[1024]; sprintf(szName, "WM:%s", prop_name); - opt = gf_cfg_get_key(wid->widget->wm->term->user->config, (const char *) wid->secname, szName); + opt = gf_opts_get_key((const char *) wid->secname, szName); if (opt) { Double val=0; if (!strcmp(opt, "true")) *vp = BOOLEAN_TO_JSVAL(JS_TRUE); @@ -2354,7 +2354,7 @@ if (!strcmp(prop_name, "num_widgets")) { *vp = INT_TO_JSVAL(gf_list_count(wm->widget_instances)); } else if (!strcmp(prop_name, "last_widget_dir")) { - const char *opt = gf_cfg_get_key(wm->term->user->config, "Widgets", "last_widget_dir"); + const char *opt = gf_opts_get_key("Widgets", "last_widget_dir"); if (!opt) opt = "/"; *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, opt)); } @@ -2876,7 +2876,7 @@ static Bool wm_relocate_url(GF_WidgetManager *wm, const char *widget_path, const e = gf_dm_sess_process(sess); gf_dm_sess_del(sess); if (e==GF_OK) { - const char *opt = gf_cfg_get_key(wm->term->user->config, "Systems", "Language2CC"); + const char *opt = gf_opts_get_key("core", "lang"); strcpy(relocated_name, res_url); if (opt) sprintf(localized_res_name, "%s/%s", opt, res_name); @@ -3006,7 +3006,7 @@ GF_WidgetInstance *wm_load_widget(GF_WidgetManager *wm, const char *path, u32 In GF_DownloadSession *sess = NULL; const char *widget_ns_prefix = NULL; const char *mpegu_ns_prefix = NULL; - const char *user_locale = gf_cfg_get_key(wm->term->user->config, "Systems", "Language2CC"); + const char *user_locale = gf_opts_get_key("core", "lang"); /* Try to see if this widget is already loaded */ e = GF_OK; @@ -3538,14 +3538,14 @@ static JSBool SMJS_FUNCTION(wm_initialize) //SMJS_ARGS GF_WidgetManager *wm = (GF_WidgetManager *)SMJS_GET_PRIVATE(c, obj); - count = gf_cfg_get_key_count(wm->term->user->config, "Widgets"); + count = gf_opts_get_key_count("Widgets"); for (i=0; i<count; i++) { - const char *name = gf_cfg_get_key_name(wm->term->user->config, "Widgets", i); + const char *name = gf_opts_get_key_name("Widgets", i); /*this is a previously loaded widgets - reload it*/ if (!strnicmp(name, "Widget#", 7)) { - const char *manifest = gf_cfg_get_key(wm->term->user->config, name, "WM:Manifest"); + const char *manifest = gf_opts_get_key(name, "WM:Manifest"); if (manifest) { - const char *ID = gf_cfg_get_key(wm->term->user->config, name, "WM:InstanceID"); + const char *ID = gf_opts_get_key(name, "WM:InstanceID"); u32 instID = ID ? atoi(ID) : 0; GF_WidgetInstance *wi = wm_load_widget(wm, manifest, instID, 0); if (wi) { @@ -3556,7 +3556,7 @@ static JSBool SMJS_FUNCTION(wm_initialize) } } - opt = gf_cfg_get_key(wm->term->user->config, "Widgets", "WidgetStore"); + opt = gf_opts_get_key("Widgets", "WidgetStore"); if (opt) gf_enum_directory(opt, 1, wm_enum_dir, wm, NULL); return JS_TRUE; diff --git a/modules/widgetman/widgetman.h b/modules/deprecated/old_arch/widgetman/widgetman.h similarity index 100% rename from modules/widgetman/widgetman.h rename to modules/deprecated/old_arch/widgetman/widgetman.h diff --git a/modules/wiiis/Makefile b/modules/deprecated/old_arch/wiiis/Makefile similarity index 93% rename from modules/wiiis/Makefile rename to modules/deprecated/old_arch/wiiis/Makefile index 29f4d0e..f402485 100644 --- a/modules/wiiis/Makefile +++ b/modules/deprecated/old_arch/wiiis/Makefile @@ -4,12 +4,12 @@ vpath %.c $(SRC_PATH)/modules/wiiis CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/modules/wiiis/test_wii.bt b/modules/deprecated/old_arch/wiiis/test_wii.bt similarity index 100% rename from modules/wiiis/test_wii.bt rename to modules/deprecated/old_arch/wiiis/test_wii.bt diff --git a/modules/wiiis/wiiis.c b/modules/deprecated/old_arch/wiiis/wiiis.c similarity index 95% rename from modules/wiiis/wiiis.c rename to modules/deprecated/old_arch/wiiis/wiiis.c index 405660e..66a337e 100644 --- a/modules/wiiis/wiiis.c +++ b/modules/deprecated/old_arch/wiiis/wiiis.c @@ -47,7 +47,7 @@ static Bool WII_RegisterDevice(struct __input_device *ifce, const char *urn, GF_ if (strcmp(urn, "WiiMote")) return 0; /*init wiiuse lib*/ - opt = gf_modules_get_option((GF_BaseInterface*)ifce, "WII", "MaxWiimotes"); + opt = gf_opts_get_key("WII", "MaxWiimotes"); if (opt) wii->nb_wiimotes = atoi(opt); if (!wii->nb_wiimotes) wii->nb_wiimotes = 1; @@ -96,7 +96,7 @@ static u32 WII_Run(void *par) scan_delay = 5; - opt = gf_modules_get_option((GF_BaseInterface*)ifce, "WII", "ScanDelay"); + opt = gf_opts_get_key("WII", "ScanDelay"); if (opt) scan_delay = atoi(opt); /*locate the wiimotes*/ @@ -109,14 +109,14 @@ static u32 WII_Run(void *par) GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[Wii] Failed to connect to any wiimote\n")); } - opt = gf_modules_get_option((GF_BaseInterface*)ifce, "WII", "MotionSensing"); + opt = gf_opts_get_key("WII", "MotionSensing"); /*enable motion sensing*/ if (!opt || !strcmp(opt, "yes")) { Float smooth_alpha = 0.5; Float ori_threshold = 10.0; - opt = gf_modules_get_option((GF_BaseInterface*)ifce, "WII", "OrientationThreshold"); + opt = gf_opts_get_key("WII", "OrientationThreshold"); if (opt) ori_threshold = (Float) atof(opt); - opt = gf_modules_get_option((GF_BaseInterface*)ifce, "WII", "SmoothAlpha"); + opt = gf_opts_get_key("WII", "SmoothAlpha"); if (opt) { smooth_alpha = (Float) atof(opt); if (smooth_alpha<0) smooth_alpha = 0.5; diff --git a/modules/oss_audio/Makefile b/modules/deprecated/oss_audio/Makefile similarity index 88% rename from modules/oss_audio/Makefile rename to modules/deprecated/oss_audio/Makefile index 97ebd36..075d997 100644 --- a/modules/oss_audio/Makefile +++ b/modules/deprecated/oss_audio/Makefile @@ -5,22 +5,22 @@ vpath %.c $(SRC_PATH)/modules/oss_audio CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" $(OSS_CFLAGS) LDFLAGS+=$(OSS_LDFLAGS) -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif -ifeq ($(OSS_INC_TYPE), yes) +ifeq ($(OSS_INC_TYPE),yes) else CFLAGS+=-DOSS_FIX_INC endif -ifeq ($(TARGET_ARCH_ARMV4L), yes) +ifeq ($(TARGET_ARCH_ARMV4L),yes) CFLAGS+=-DFORCE_SR_LIMIT endif diff --git a/modules/oss_audio/oss.c b/modules/deprecated/oss_audio/oss.c similarity index 94% rename from modules/oss_audio/oss.c rename to modules/deprecated/oss_audio/oss.c index d2307be..95e9acc 100644 --- a/modules/oss_audio/oss.c +++ b/modules/deprecated/oss_audio/oss.c @@ -69,7 +69,7 @@ static GF_Err OSS_Setup(GF_AudioOutput*dr, void *os_handle, u32 num_buffers, u32 const char *opt; OSSCTX(); - opt = gf_modules_get_option((GF_BaseInterface *)dr, "OSS", "ForceSampleRate"); + opt = gf_opts_get_key("core", "force-alsarate"); if (opt) ctx->force_sr = atoi(opt); /*open OSS in non-blocking mode*/ @@ -97,7 +97,7 @@ static void OSS_Shutdown(GF_AudioOutput*dr) } -static GF_Err OSS_ConfigureOutput(GF_AudioOutput*dr, u32 *SampleRate, u32 *NbChannels, u32 *nbBitsPerSample, u32 channel_cfg) +static GF_Err OSS_Configure(GF_AudioOutput*dr, u32 *SampleRate, u32 *NbChannels, u32 *audioFormat, u32 channel_cfg) { int format, blockalign, nb_bufs, frag_spec; long flags; @@ -122,11 +122,19 @@ static GF_Err OSS_ConfigureOutput(GF_AudioOutput*dr, u32 *SampleRate, u32 *NbCha if (ioctl(ctx->audio_dev, SNDCTL_DSP_CHANNELS, &ctx->nb_ch)==-1) return GF_IO_ERR; blockalign = ctx->nb_ch; - if ((*nbBitsPerSample) == 16) { + + //only support for PCM 8/16/24/32 packet mode + switch (*audioFormat) { + case GF_AUDIO_FMT_U8: + format = AFMT_S8; + break; + default: + //otherwise force PCM16 + *audioFormat = GF_AUDIO_FMT_S16; + case GF_AUDIO_FMT_S16: blockalign *= 2; format = AFMT_S16_LE; - } else { - format = AFMT_S8; + break; } if(ioctl(ctx->audio_dev, SNDCTL_DSP_SETFMT,&format)==-1) return GF_IO_ERR; ctx->sr = (*SampleRate); @@ -233,7 +241,7 @@ void *NewOSSRender() driv->SelfThreaded = 0; driv->Setup = OSS_Setup; driv->Shutdown = OSS_Shutdown; - driv->ConfigureOutput = OSS_ConfigureOutput; + driv->Configure = OSS_Configure; driv->GetAudioDelay = OSS_GetAudioDelay; driv->SetVolume = OSS_SetVolume; driv->SetPan = OSS_SetPan; diff --git a/modules/directfb_out/Makefile b/modules/directfb_out/Makefile index 2f16c0f..2d70d08 100644 --- a/modules/directfb_out/Makefile +++ b/modules/directfb_out/Makefile @@ -2,16 +2,15 @@ include ../../config.mak vpath %.c $(SRC_PATH)/modules/directfb_out -CFLAGS= $(OPTFLAGS) -CFLAGS+=-I"$(SRC_PATH)/include" -I$(DIRECTFB_INC_PATH) +CFLAGS=-I"$(SRC_PATH)/include" -I$(DIRECTFB_INC_PATH) $(OPTFLAGS) LDFLAGS+=$(DIRECTFB_LIB) -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/modules/directfb_out/directfb_out.c b/modules/directfb_out/directfb_out.c index 0cd58ca..faaf441 100755 --- a/modules/directfb_out/directfb_out.c +++ b/modules/directfb_out/directfb_out.c @@ -2,7 +2,7 @@ * GPAC Multimedia Framework * * Authors: Romain Bouqueau - Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2010-2012 + * Copyright (c) Telecom ParisTech 2010-2020 * All rights reserved * * This file is part of GPAC / DirectFB video output module @@ -32,51 +32,6 @@ #define DirectFBVID() DirectFBVidCtx *ctx = (DirectFBVidCtx *)driv->opaque -/** - * function DirectFBVid_DrawHLine - * - using hardware accelerator to a draw horizontal line - **/ -static void DirectFBVid_DrawHLine(GF_VideoOutput *driv, u32 x, u32 y, u32 length, GF_Color color) -{ - u8 r = GF_COL_R(color); - u8 g = GF_COL_G(color); - u8 b = GF_COL_B(color); - DirectFBVID(); - - DirectFBVid_DrawHLineWrapper(ctx, x, y, length, r, g, b); -} - - -/** - * function DirectFBVid_DrawHLineAlpha - * - using hardware accelerator to draw a horizontal line with alpha - **/ -static void DirectFBVid_DrawHLineAlpha(GF_VideoOutput *driv, u32 x, u32 y, u32 length, GF_Color color, u8 alpha) -{ - u8 r = GF_COL_R(color); - u8 g = GF_COL_G(color); - u8 b = GF_COL_B(color); - DirectFBVID(); - - DirectFBVid_DrawHLineAlphaWrapper(ctx, x, y, length, r, g, b, alpha); -} - - -/** - * function DirectFBVid_DrawRectangle - * - using hardware accelerator to fill a rectangle - **/ -static void DirectFBVid_DrawRectangle(GF_VideoOutput *driv, u32 x, u32 y, u32 width, u32 height, GF_Color color) -{ - u8 r = GF_COL_R(color); - u8 g = GF_COL_G(color); - u8 b = GF_COL_B(color); - u8 a = GF_COL_A(color); - DirectFBVID(); - - DirectFBVid_DrawRectangleWrapper(ctx, x, y, width, height, r, g, b, a); -} - /** * function DirectFBVid_Setup * - DirectFB setup @@ -93,41 +48,41 @@ GF_Err DirectFBVid_Setup(GF_VideoOutput *driv, void *os_handle, void *os_display // check window mode used - SDL or X11 { WINDOW_MODE window_mode = 0; - opt = gf_modules_get_option((GF_BaseInterface *)driv, "DirectFB", "WindowMode"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)driv, "DirectFB", "WindowMode", "X11"); + opt = gf_opts_get_key("DirectFB", "WindowMode"); + if (!opt) gf_opts_set_key("DirectFB", "WindowMode", "X11"); if (!opt || !strcmp(opt, "X11")) window_mode = WINDOW_X11; - else if (opt && !strcmp(opt, "SDL")) window_mode = WINDOW_SDL; + else if (!strcmp(opt, "SDL")) window_mode = WINDOW_SDL; DirectFBVid_InitAndCreateSurface(ctx, window_mode); } // check hardware accelerator configuration DirectFBVid_CtxSetDisableAcceleration(ctx, 0); - opt = gf_modules_get_option((GF_BaseInterface *)driv, "DirectFB", "DisableAcceleration"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)driv, "DirectFB", "DisableAcceleration", "no"); + opt = gf_opts_get_key("DirectFB", "DisableAcceleration"); + if (!opt) gf_opts_set_key("DirectFB", "DisableAcceleration", "no"); if (opt && !strcmp(opt, "yes")) DirectFBVid_CtxSetDisableAcceleration(ctx, 1); // check for display configuration DirectFBVid_CtxSetDisableDisplay(ctx, 0); - opt = gf_modules_get_option((GF_BaseInterface *)driv, "DirectFB", "DisableDisplay"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)driv, "DirectFB", "DisableDisplay", "no"); + opt = gf_opts_get_key("DirectFB", "DisableDisplay"); + if (!opt) gf_opts_set_key("DirectFB", "DisableDisplay", "no"); if (opt && !strcmp(opt, "yes")) DirectFBVid_CtxSetDisableDisplay(ctx, 1); // set flip mode { FLIP_MODE flip_mode = 0; - opt = gf_modules_get_option((GF_BaseInterface *)driv, "DirectFB", "FlipSyncMode"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)driv, "DirectFB", "FlipSyncMode", "waitsync"); + opt = gf_opts_get_key("DirectFB", "FlipSyncMode"); + if (!opt) gf_opts_set_key("DirectFB", "FlipSyncMode", "waitsync"); if (!opt || !strcmp(opt, "waitsync")) flip_mode |= FLIP_WAITFORSYNC; - else if (opt && !strcmp(opt, "wait")) flip_mode |= FLIP_WAIT; - else if (opt && !strcmp(opt, "sync")) flip_mode |= FLIP_ONSYNC; - else if (opt && !strcmp(opt, "swap")) flip_mode |= FLIP_SWAP; + else if (!strcmp(opt, "wait")) flip_mode |= FLIP_WAIT; + else if (!strcmp(opt, "sync")) flip_mode |= FLIP_ONSYNC; + else if (!strcmp(opt, "swap")) flip_mode |= FLIP_SWAP; DirectFBVid_CtxSetFlipMode(ctx, flip_mode); } // enable/disable blit - opt = gf_modules_get_option((GF_BaseInterface *)driv, "DirectFB", "DisableBlit"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)driv, "DirectFB", "DisableBlit", "no"); + opt = gf_opts_get_key("DirectFB", "DisableBlit"); + if (!opt) gf_opts_set_key("DirectFB", "DisableBlit", "no"); if (opt && !strcmp(opt, "all")) { driv->hw_caps &= ~(GF_VIDEO_HW_HAS_RGB | GF_VIDEO_HW_HAS_RGBA | GF_VIDEO_HW_HAS_YUV); } @@ -135,18 +90,6 @@ GF_Err DirectFBVid_Setup(GF_VideoOutput *driv, void *os_handle, void *os_display else if (opt && !strcmp(opt, "rgb")) driv->hw_caps &= ~GF_VIDEO_HW_HAS_RGB; else if (opt && !strcmp(opt, "rgba")) driv->hw_caps &= ~GF_VIDEO_HW_HAS_RGBA; - if (!DirectFBVid_CtxGetDisableAcceleration(ctx)) { - // check for functions that are hardware accelerated - DirectFBVid_CtxPrimaryProcessGetAccelerationMask(ctx); - - driv->hw_caps |= GF_VIDEO_HW_HAS_LINE_BLIT; - driv->DrawHLine = DirectFBVid_DrawHLine; - driv->DrawHLineAlpha = DirectFBVid_DrawHLineAlpha; - driv->DrawRectangle = DirectFBVid_DrawRectangle; - - //GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DirectFB] hardware acceleration mask %08x - Line: %d Rectangle: %d\n", mask, ctx->accel_drawline, ctx->accel_fillrect)); - } - // end of initialization DirectFBVid_CtxSetIsInit(ctx, 1); GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DirectFB] Initialization success - HW caps %08x\n", driv->hw_caps)); @@ -244,7 +187,7 @@ static GF_Err DirectFBVid_ProcessEvent(GF_VideoOutput *driv, GF_Event *evt) return GF_OK; case GF_EVENT_VIDEO_SETUP: - if (evt->setup.opengl_mode) return GF_NOT_SUPPORTED; + if (evt->setup.use_opengl) return GF_NOT_SUPPORTED; if ((DirectFBVid_CtxGetWidth(ctx) !=evt->setup.width) || (DirectFBVid_CtxGetHeight(ctx) != evt->setup.height)) { GF_Event gpac_evt; diff --git a/modules/directfb_out/directfb_wrapper.c b/modules/directfb_out/directfb_wrapper.c index 3c242cb..ddf3f37 100644 --- a/modules/directfb_out/directfb_wrapper.c +++ b/modules/directfb_out/directfb_wrapper.c @@ -578,9 +578,9 @@ u32 DirectFBVid_TranslatePixelFormatToGPAC(u32 dfbpf) case DSPF_RGB555: return GF_PIXEL_RGB_555; case DSPF_RGB24: - return GF_PIXEL_RGB_24; + return GF_PIXEL_RGB; case DSPF_RGB32: - return GF_PIXEL_RGB_32; + return GF_PIXEL_RGBX; case DSPF_ARGB: return GF_PIXEL_ARGB; default: @@ -599,19 +599,19 @@ u32 DirectFBVid_TranslatePixelFormatFromGPAC(u32 gpacpf) return DSPF_RGB16; case GF_PIXEL_RGB_555 : return DSPF_RGB555; - case GF_PIXEL_BGR_24 : + case GF_PIXEL_BGR: return DSPF_RGB24; - case GF_PIXEL_RGB_24 : + case GF_PIXEL_RGB: return DSPF_RGB24; - case GF_PIXEL_RGB_32 : + case GF_PIXEL_RGBX: return DSPF_RGB32; case GF_PIXEL_ARGB: return DSPF_ARGB; case GF_PIXEL_RGBA: return DSPF_ARGB; - case GF_PIXEL_YUY2 : + case GF_PIXEL_UYVY : return DSPF_YUY2; - case GF_PIXEL_YV12 : + case GF_PIXEL_YUV: return DSPF_YV12; default: ;//GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DirectFB] pixel format %s not supported\n", gf_4cc_to_str(gpacpf))); diff --git a/modules/droid_audio/droidaudio.c b/modules/droid_audio/droidaudio.c index 81ecc6c..3ec1b9c 100644 --- a/modules/droid_audio/droidaudio.c +++ b/modules/droid_audio/droidaudio.c @@ -109,8 +109,6 @@ static GF_Err WAV_Setup(GF_AudioOutput *dr, void *os_handle, u32 num_buffers, u3 { DroidContext *ctx = (DroidContext *)dr->opaque; JNIEnv* env = GetEnv(); - int channels; - int bytes; LOGV("[Android Audio] Setup for %d buffers", num_buffers); ctx->force_config = (num_buffers && total_duration) ? 1 : 0; @@ -176,7 +174,7 @@ static void WAV_Shutdown(GF_AudioOutput *dr) /*we assume what was asked is what we got*/ /* Called by the audio thread */ -static GF_Err WAV_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *nbBitsPerSample, u32 channel_cfg) +static GF_Err WAV_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *nbBitsPerSample, u64 channel_cfg) { JNIEnv* env = NULL; u32 i; @@ -260,21 +258,21 @@ static void WAV_WriteAudio(GF_AudioOutput *dr) #ifdef DROID_EXTREME_LOGS LOGV("[Android Audio] WAV_WriteAudio() : entering",ctx->sampleRateInHz); #endif /* DROID_EXTREME_LOGS */ - + if ( ctx->audioFormat == ENCODING_PCM_8BIT ) pBuffer = (*env)->GetByteArrayElements(env, ctx->buff, NULL); else pBuffer = (*env)->GetShortArrayElements(env, ctx->buff, NULL); - + if (pBuffer) { written = dr->FillBuffer(dr->audio_renderer, pBuffer, ctx->mbufferSizeInBytes); - + if ( ctx->audioFormat == ENCODING_PCM_8BIT ) (*env)->ReleaseByteArrayElements(env, ctx->buff, pBuffer, 0); else (*env)->ReleaseShortArrayElements(env, ctx->buff, pBuffer, 0); - + if (written) { if ( ctx->audioFormat == ENCODING_PCM_8BIT ) @@ -358,9 +356,11 @@ static void WAV_SetPan(GF_AudioOutput *dr, u32 Pan) /* Called by the audio thread */ static GF_Err WAV_QueryOutputSampleRate(GF_AudioOutput *dr, u32 *desired_samplerate, u32 *NbChannels, u32 *nbBitsPerSample) { +#ifdef TEST_QUERY_SAMPLE DroidContext *ctx = (DroidContext *)dr->opaque; JNIEnv* env = ctx->env; u32 sampleRateInHz, channelConfig, audioFormat; +#endif LOGV("Query sample=%d", *desired_samplerate ); @@ -460,7 +460,7 @@ void *NewWAVRender() driv->SelfThreaded = 0; driv->Setup = WAV_Setup; driv->Shutdown = WAV_Shutdown; - driv->ConfigureOutput = WAV_ConfigureOutput; + driv->Configure = WAV_ConfigureOutput; driv->GetAudioDelay = WAV_GetAudioDelay; driv->GetTotalBufferTime = WAV_GetTotalBufferTime; driv->SetVolume = WAV_SetVolume; diff --git a/modules/droid_cam/droid_cam.c b/modules/droid_cam/droid_cam.c index 5246608..0c1f221 100644 --- a/modules/droid_cam/droid_cam.c +++ b/modules/droid_cam/droid_cam.c @@ -265,6 +265,7 @@ void unloadCameraControler(ISOMReader *read) u32 unregisterFunc(void* data) { unloadCameraControler(globReader); + return 0; } void loadCameraControler(ISOMReader *read) @@ -279,7 +280,8 @@ void loadCameraControler(ISOMReader *read) if ( res == JNI_EDETACHED ) { GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] The current thread is not attached to the VM, assuming native thread\n")); - if ( res = (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL) ) + res = (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); + if ( res ) { GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[ANDROID_CAMERA] Attach current thread failed: %d\n", res)); return; @@ -369,20 +371,19 @@ u32 getBitsPerPix(ISOMReader *read); static GF_Descriptor *CAM_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) { u32 trackID; - GF_ESD *esd; ISOMReader *read; - GF_ObjectDescriptor *od; - GF_BitStream *bs; char *buf; u32 buf_size; if (!plug || !plug->priv) return NULL; read = (ISOMReader *) plug->priv; - trackID = 0; trackID = read->base_track_id; read->base_track_id = 0; if (trackID && (expect_type==GF_MEDIA_OBJECT_VIDEO) ) { + GF_BitStream *bs; + GF_ESD *esd; + GF_ObjectDescriptor *od; od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); od->objectDescriptorID = 1; @@ -459,18 +460,23 @@ GF_Err CAM_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) void Java_com_gpac_Osmo4_Preview_processFrameBuf( JNIEnv* env, jobject thiz, jbyteArray arr) { - u8* data; - u32 datasize; ISOMReader* ctx = globReader; - GF_SLHeader hdr; - u32 cts = 0; - u32 convTime = 0; - u32 j = 0; - jbyte *jdata; - jsize len; - - if ( ctx && ctx->started && ctx->term && ctx->term->compositor && ctx->term->compositor->audio_renderer) - { + if ( ctx + && ctx->started + && ctx->term + && ctx->term->compositor + && ctx->term->compositor->audio_renderer + ) { + + u8* data; + u32 datasize; + GF_SLHeader hdr; + u32 cts = 0; + //u32 convTime = 0; + //u32 j = 0; + jbyte *jdata; + jsize len; + len = (*env)->GetArrayLength(env, arr); if ( len <= 0 ) return; jdata = (*env)->GetByteArrayElements(env,arr,0); @@ -508,8 +514,8 @@ void CallCamMethod(ISOMReader *read, jmethodID methodID) if ( res == JNI_EDETACHED ) { GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] The current thread is not attached to the VM, assuming native thread\n")); - if ( res = (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL) ) - { + res = (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); + if ( res ) { GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[ANDROID_CAMERA] Attach current thread failed: %d\n", res)); return; } @@ -527,7 +533,7 @@ void CallCamMethod(ISOMReader *read, jmethodID methodID) void camStartCamera(ISOMReader *read) { - JNIEnv* env = NULL; + JNIEnv* env; jboolean isPortrait = JNI_FALSE; GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] startCamera: %d\n", gf_th_id())); @@ -563,7 +569,7 @@ void resumeCamera(ISOMReader *read) u32 getWidth(ISOMReader *read) { - JNIEnv* env = NULL; + JNIEnv* env; GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] getWidth: %d\n", gf_th_id())); @@ -575,7 +581,7 @@ u32 getWidth(ISOMReader *read) u32 getHeight(ISOMReader *read) { - JNIEnv* env = NULL; + JNIEnv* env; GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] getHeight: %d\n", gf_th_id())); @@ -587,7 +593,7 @@ u32 getHeight(ISOMReader *read) u32 getFormat(ISOMReader *read) { - JNIEnv* env = NULL; + JNIEnv* env; u32 pixel_format; GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] getFormat: %d\n", gf_th_id())); @@ -608,7 +614,7 @@ u32 getFormat(ISOMReader *read) u32 getBitsPerPix(ISOMReader *read) { - JNIEnv* env = NULL; + JNIEnv* env; GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[ANDROID_CAMERA] getBitsPerPix: %d\n", gf_th_id())); @@ -621,9 +627,6 @@ u32 getBitsPerPix(ISOMReader *read) GF_Err CAM_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) { ISOMReader *read; - GF_BitStream *bs; - char *buf; - u32 buf_size; if (!plug || !plug->priv || !com) return GF_SERVICE_ERROR; read = (ISOMReader *) plug->priv; @@ -650,6 +653,8 @@ GF_Err CAM_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) /*nothing to do on MP4 for channel config*/ case GF_NET_CHAN_CONFIG: return GF_OK; + default: + break; } return GF_NOT_SUPPORTED; } diff --git a/modules/droid_mpegv/droid_mpegv.c b/modules/droid_mpegv/droid_mpegv.c index 6278e2d..dc096c3 100644 --- a/modules/droid_mpegv/droid_mpegv.c +++ b/modules/droid_mpegv/droid_mpegv.c @@ -177,7 +177,8 @@ void loadSensorControler(MPEGVSensorContext *rc) if ( res == JNI_EDETACHED ) { GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[MPEG-V_IN] The current thread is not attached to the VM, assuming native thread\n")); - if ( res = (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL) ) + res = (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); + if ( res ) { GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[MPEG-V_IN] Attach current thread failed: %d\n", res)); return; @@ -206,7 +207,7 @@ void loadSensorControler(MPEGVSensorContext *rc) } } -Bool MPEGVS_RegisterDevice(struct __input_device *dr, const char *urn, GF_BitStream *dsi, void (*AddField)(struct __input_device *_this, u32 fieldType, const char *name)) +Bool MPEGVS_RegisterDevice(struct __input_device *dr, const char *urn, const char *dsi, u32 dsi_size, void (*AddField)(struct __input_device *_this, u32 fieldType, const char *name)) { MPEGVSCTX; @@ -305,9 +306,9 @@ Bool MPEGVS_RegisterDevice(struct __input_device *dr, const char *urn, GF_BitStr u32 MPEGVS_OnData(struct __input_device * dr, const char* data) { GF_BitStream *bs; - char *buf; + u8 *buf; u32 buf_size; - float x, y, z, q, a, b; + float x, y, z, /*q,*/ a, b; MPEGVSCTX; bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); @@ -435,8 +436,8 @@ void MPEGVS_Stop(struct __input_device * dr) GF_InputSensorDevice* NewMPEGVSInputSesor() { - MPEGVSensorContext* ctx = NULL; - GF_InputSensorDevice* driv = NULL; + MPEGVSensorContext* ctx; + GF_InputSensorDevice* driv; driv = (GF_InputSensorDevice *) gf_malloc(sizeof(GF_InputSensorDevice)); memset(driv, 0, sizeof(GF_InputSensorDevice)); diff --git a/modules/droid_out/droid_vout-bitmap.c b/modules/droid_out/droid_vout-bitmap.c index a1cf493..b33d884 100644 --- a/modules/droid_out/droid_vout-bitmap.c +++ b/modules/droid_out/droid_vout-bitmap.c @@ -57,8 +57,6 @@ GF_Err RAW_Setup(GF_VideoOutput *dr, void *os_handle, void *os_display, u32 init { AndroidBitmapInfo info; RAWCTX; - void * pixels; - int ret; GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout RAW_Setup\n")); @@ -101,7 +99,6 @@ static GF_Err RAW_Flush(GF_VideoOutput *dr, GF_Window *dest) static GF_Err RAW_LockBackBuffer(GF_VideoOutput *dr, GF_VideoSurface *vi, Bool do_lock) { RAWCTX; - int ret; void * pixels; GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout RAW_LockBackBuffer: %d\n", do_lock)); @@ -110,8 +107,9 @@ static GF_Err RAW_LockBackBuffer(GF_VideoOutput *dr, GF_VideoSurface *vi, Bool d GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout locked_data: %d\n", rc->locked_data)); if (!rc->locked_data) { + int ret; if ((ret = AndroidBitmap_lockPixels(rc->env, *(rc->bitmap), &pixels)) < 0) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout lock failed\n")); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("Android vout lock failed (%d)\n", ret)); } rc->locked_data = pixels; } diff --git a/modules/droid_out/droid_vout.c b/modules/droid_out/droid_vout.c index 7517bd6..734db28 100644 --- a/modules/droid_out/droid_vout.c +++ b/modules/droid_out/droid_vout.c @@ -55,9 +55,7 @@ typedef struct u32 tex_width, tex_height; - GLuint texID; - GLuint framebuff; - GLuint depthbuff; + GLint texID; GLubyte* texData; @@ -250,7 +248,7 @@ static Bool initGLES2(AndroidContext *rc) { static void load_matrix_shaders(GLuint program, Fixed *mat, const char *name) { - GLint loc=-1; + GLint loc; #ifdef GPAC_FIXED_POINT Float _mat[16]; u32 i; @@ -353,13 +351,13 @@ void initGL(AndroidContext *rc) void gluPerspective(GLfloat fovy, GLfloat aspect, GLfloat zNear, GLfloat zFar) { +#ifndef GPAC_USE_GLES2 GLfloat xmin, xmax, ymin, ymax; ymax = zNear * (GLfloat)tan(fovy * PI / 360); ymin = -ymax; xmin = ymin * aspect; xmax = ymax * aspect; -#ifndef GPAC_USE_GLES2 glFrustumx((GLfixed)(xmin * 65536), (GLfixed)(xmax * 65536), (GLfixed)(ymin * 65536), (GLfixed)(ymax * 65536), (GLfixed)(zNear * 65536), (GLfixed)(zFar * 65536)); @@ -369,16 +367,10 @@ void gluPerspective(GLfloat fovy, GLfloat aspect, void resizeWindow(AndroidContext *rc) { GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("resizeWindow : start")); - /* Height / width ration */ - GLfloat ratio; /* Protect against a divide by zero */ if (rc->height==0) - { - rc->height=1; - } - - ratio=(GLfloat)rc->width/(GLfloat)rc->height; + rc->height = 1; /* Setup our viewport. */ glViewport(0, 0, (GLsizei)rc->width, (GLsizei)rc->height); @@ -412,21 +404,22 @@ void drawGLScene(AndroidContext *rc) GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("drawGLScene : start")); #endif /* DROID_EXTREME_LOGS */ #ifdef GPAC_USE_GLES2 - GLuint loc_vertex_array, loc_texcoord_array; - loc_vertex_array = loc_texcoord_array = -1; + GLint loc_vertex_array, loc_texcoord_array; #endif GLfloat vertices[4][3]; GLfloat texcoord[4][2]; // int i, j; +#ifndef GPAC_USE_GLES2 float rgba[4]; +#endif gl_check_error(); // Reset states +#ifndef GPAC_USE_GLES2 rgba[0] = rgba[1] = rgba[2] = 0.f; rgba[0] = 1.f; -#ifndef GPAC_USE_GLES2 glColor4f(1.f, 1.f, 1.f, 1.f); glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, rgba); glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, rgba); @@ -464,9 +457,9 @@ void drawGLScene(AndroidContext *rc) gl_check_error(); if ( rc->draw_texture ) { +#ifndef GPAC_USE_GLES2 gl_check_error(); int cropRect[4] = {0,rc->height,rc->width,-rc->height}; -#ifndef GPAC_USE_GLES2 glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect); glDrawTexsOES(0, 0, 0, rc->width, rc->height); #endif @@ -655,8 +648,6 @@ static GF_Err droid_Resize(GF_VideoOutput *dr, u32 w, u32 h) GF_Err droid_Setup(GF_VideoOutput *dr, void *os_handle, void *os_display, u32 init_flags) { RAWCTX; - void * pixels; - Bool res = GF_FALSE; GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android Setup: %d", init_flags)); @@ -664,6 +655,7 @@ GF_Err droid_Setup(GF_VideoOutput *dr, void *os_handle, void *os_display, u32 in #ifdef GPAC_USE_GLES2 if ( rc->out_3d_type == 0 ) { + Bool res = GF_FALSE; GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("We are in OpenGL: disable mode")); res = initGLES2(rc); if(res==GF_FALSE) { @@ -716,9 +708,6 @@ static GF_Err droid_Flush(GF_VideoOutput *dr, GF_Window *dest) static GF_Err droid_LockBackBuffer(GF_VideoOutput *dr, GF_VideoSurface *vi, Bool do_lock) { RAWCTX; - int ret; - void * pixels; - int i,j,t; #ifdef DROID_EXTREME_LOGS GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android LockBackBuffer: %d", do_lock)); #endif /* DROID_EXTREME_LOGS */ @@ -759,73 +748,66 @@ static GF_Err droid_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt) { RAWCTX; - if (evt) { - switch (evt->type) { - case GF_EVENT_SIZE: - GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("GF_EVENT_SIZE( %d x %d)", evt->setup.width, evt->setup.height)); - //if (evt->setup.opengl_mode) return GF_OK; - //in fullscreen mode: do not change viewport; just update perspective - if (rc->fullscreen) { + if (!evt) return GF_OK; + + switch (evt->type) { + case GF_EVENT_SIZE: + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("GF_EVENT_SIZE( %d x %d)", evt->setup.width, evt->setup.height)); + //in fullscreen mode: do not change viewport; just update perspective + if (rc->fullscreen) { #ifdef GPAC_USE_GLES2 - gl_check_error(); - glUseProgram(rc->base_program); - calculate_ortho(0, INT2FIX(rc->width), 0, INT2FIX(rc->height), INT2FIX(-1), INT2FIX(1), rc); - load_matrix_shaders(rc->base_program, (Fixed *) rc->ortho.m, "gfProjectionMatrix"); - load_matrix_shaders(rc->base_program, (Fixed *) rc->identity.m, "gfModelViewMatrix"); - gl_check_error(); + gl_check_error(); + glUseProgram(rc->base_program); + calculate_ortho(0, INT2FIX(rc->width), 0, INT2FIX(rc->height), INT2FIX(-1), INT2FIX(1), rc); + load_matrix_shaders(rc->base_program, (Fixed *) rc->ortho.m, "gfProjectionMatrix"); + load_matrix_shaders(rc->base_program, (Fixed *) rc->identity.m, "gfModelViewMatrix"); + gl_check_error(); #else - /* change to the projection matrix and set our viewing volume. */ - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); + /* change to the projection matrix and set our viewing volume. */ + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); - /* Set our perspective */ - glOrthox(0, INT2FIX(rc->width), 0, INT2FIX(rc->height), INT2FIX(-1), INT2FIX(1)); + /* Set our perspective */ + glOrthox(0, INT2FIX(rc->width), 0, INT2FIX(rc->height), INT2FIX(-1), INT2FIX(1)); - /* Make sure we're chaning the model view and not the projection */ - glMatrixMode(GL_MODELVIEW); + /* Make sure we're chaning the model view and not the projection */ + glMatrixMode(GL_MODELVIEW); - /* Reset The View */ - glLoadIdentity(); + /* Reset The View */ + glLoadIdentity(); #endif - return GF_OK; - } else - return droid_Resize(dr, evt->setup.width, evt->setup.height); - - case GF_EVENT_VIDEO_SETUP: - GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android OpenGL mode: %d", evt->setup.opengl_mode)); - switch (evt->setup.opengl_mode) - { - case 0: - rc->out_3d_type = 0; + return GF_OK; + } else + return droid_Resize(dr, evt->setup.width, evt->setup.height); + + case GF_EVENT_VIDEO_SETUP: + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Android OpenGL mode: %d", evt->setup.use_opengl)); + if (!evt->setup.use_opengl) { + rc->out_3d_type = 0; // initGL(rc); - droid_Resize(dr, evt->setup.width, evt->setup.height); - return GF_OK; - case 1: - rc->out_3d_type = 1; - droid_Resize(dr, evt->setup.width, evt->setup.height); - return GF_OK; - case 2: - rc->out_3d_type = 2; - droid_Resize(dr, evt->setup.width, evt->setup.height); - return GF_OK; - case GF_EVENT_SET_CURSOR: - GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("GF_EVENT_SET_CURSOR")); - return GF_OK; - case GF_EVENT_SET_CAPTION: - GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("GF_EVENT_SET_CAPTION")); - return GF_OK; - case GF_EVENT_SHOWHIDE: - GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("GF_EVENT_SHOWHIDE")); - return GF_OK; - default: - GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Process Unknown Event: %d", evt->type)); - return GF_OK; - } - break; - case GF_EVENT_TEXT_EDITING_START: - case GF_EVENT_TEXT_EDITING_END: - return GF_NOT_SUPPORTED; + droid_Resize(dr, evt->setup.width, evt->setup.height); + } else { + rc->out_3d_type = 1; + droid_Resize(dr, evt->setup.width, evt->setup.height); } + return GF_OK; + + case GF_EVENT_SET_CURSOR: + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("GF_EVENT_SET_CURSOR")); + return GF_OK; + case GF_EVENT_SET_CAPTION: + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("GF_EVENT_SET_CAPTION")); + return GF_OK; + case GF_EVENT_SHOWHIDE: + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("GF_EVENT_SHOWHIDE")); + return GF_OK; + default: + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("Process Unknown Event: %d", evt->type)); + return GF_OK; + + case GF_EVENT_TEXT_EDITING_START: + case GF_EVENT_TEXT_EDITING_END: + return GF_NOT_SUPPORTED; } return GF_OK; } @@ -852,8 +834,6 @@ GF_VideoOutput *NewAndroidVideoOutput() memset(pCtx, 0, sizeof(AndroidContext)); pCtx->texID = -1; - pCtx->framebuff = -1; - pCtx->depthbuff = -1; driv->opaque = pCtx; driv->Flush = droid_Flush; diff --git a/modules/dummy_in/Makefile b/modules/dummy_in/Makefile deleted file mode 100644 index cf1834b..0000000 --- a/modules/dummy_in/Makefile +++ /dev/null @@ -1,49 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/dummy_in - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS= dummy_in.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_dummy_in$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols dummy_in.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_dummy_in-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac_static $(LDFLAGS) -endif - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/dummy_in/dummy_in.c b/modules/dummy_in/dummy_in.c deleted file mode 100644 index 94b164c..0000000 --- a/modules/dummy_in/dummy_in.c +++ /dev/null @@ -1,527 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / Dummy input module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include <gpac/modules/service.h> -/*for GF_STREAM_PRIVATE_SCENE definition*/ -#include <gpac/constants.h> -#include <gpac/download.h> -#include <gpac/xml.h> - -typedef struct -{ - u32 ESID; - LPNETCHANNEL ch; - u32 start, end; -} DummyChannel; - -typedef struct -{ - /*the service we're responsible for*/ - GF_ClientService *service; - char *url; - u32 oti; - Bool is_views_url; - GF_List *channels; - - /*file downloader*/ - GF_DownloadSession * dnload; - Bool is_service_connected; -} DCReader; - -DummyChannel *DC_GetChannel(DCReader *read, LPNETCHANNEL ch) -{ - DummyChannel *dc; - u32 i=0; - while ((dc = (DummyChannel *)gf_list_enum(read->channels, &i))) { - if (dc->ch && dc->ch==ch) return dc; - } - return NULL; -} - -Bool DC_RemoveChannel(DCReader *read, LPNETCHANNEL ch) -{ - DummyChannel *dc; - u32 i=0; - while ((dc = (DummyChannel *)gf_list_enum(read->channels, &i))) { - if (dc->ch && dc->ch==ch) { - gf_list_rem(read->channels, i-1); - gf_free(dc); - return GF_TRUE; - } - } - return GF_FALSE; -} - -static const char * DC_MIME_TYPES[] = { - /* "application/x-xmta", "xmta xmta.gz xmtaz", "MPEG-4 Text (XMT)" */ - "application/x-bt", "bt bt.gz btz", "MPEG-4 Text (BT)", - "application/x-xmt", "xmt xmt.gz xmtz", "MPEG-4 Text (XMT)", - "model/vrml", "wrl wrl.gz", "VRML World", - "x-model/x-vrml", "wrl wrl.gz", "VRML World", - "model/x3d+vrml", "x3dv x3dv.gz x3dvz", "X3D/VRML World", - "model/x3d+xml", "x3d x3d.gz x3dz", "X3D/XML World", - "application/x-shockwave-flash", "swf", "Macromedia Flash Movie", - "image/svg+xml", "svg svg.gz svgz", "SVG Document", - "image/x-svgm", "svgm", "SVGM Document", - "application/x-LASeR+xml", "xsr", "LASeR Document", - "application/widget", "wgt", "W3C Widget Package", - "application/x-mpegu-widget", "mgt", "MPEG-U Widget Package", - NULL -}; - - -static u32 DC_RegisterMimeTypes(const GF_InputService *plug) { - u32 i; - if (!plug) - return 0; - for (i = 0 ; DC_MIME_TYPES[i] ; i+=3) - gf_service_register_mime(plug, DC_MIME_TYPES[i], DC_MIME_TYPES[i+1], DC_MIME_TYPES[i+2]); - return i / 3; -} - -Bool DC_CanHandleURL(GF_InputService *plug, const char *url) -{ - char *sExt; - if (!plug || !url) - return GF_FALSE; - sExt = strrchr(url, '.'); - if (sExt) { - Bool ok = GF_FALSE; - char *cgi_par; - if (!strnicmp(sExt, ".gz", 3)) sExt = strrchr(sExt, '.'); - if (!strnicmp(url, "rtsp://", 7)) return GF_FALSE; - - cgi_par = strchr(sExt, '?'); - if (cgi_par) cgi_par[0] = 0; - { - u32 i; - for (i = 0 ; DC_MIME_TYPES[i] ; i+=3) - if (0 != (ok = gf_service_check_mime_register(plug, DC_MIME_TYPES[i], DC_MIME_TYPES[i+1], DC_MIME_TYPES[i+2], sExt))) - break; - } - if (cgi_par) cgi_par[0] = '?'; - if (ok) return GF_TRUE; - } - /*views:// internal URI*/ - if (!strnicmp(url, "views://", 8)) - return GF_TRUE; - - if (!strncmp(url, "\\\\", 2)) return GF_FALSE; - - if (!strnicmp(url, "file://", 7) || !strstr(url, "://")) { - char *rtype = gf_xml_get_root_type(url, NULL); - if (rtype) { - Bool handled = GF_FALSE; - if (!strcmp(rtype, "SAFSession")) handled = GF_TRUE; - else if (!strcmp(rtype, "XMT-A")) handled = GF_TRUE; - else if (!strcmp(rtype, "X3D")) handled = GF_TRUE; - else if (!strcmp(rtype, "svg")) handled = GF_TRUE; - else if (!strcmp(rtype, "bindings")) handled = GF_TRUE; - else if (!strcmp(rtype, "widget")) handled = GF_TRUE; - gf_free(rtype); - return handled; - } - } - return GF_FALSE; -} - -void DC_NetIO(void *cbk, GF_NETIO_Parameter *param) -{ - GF_Err e; - DCReader *read = (DCReader *) cbk; - - /*handle service message*/ - gf_service_download_update_stats(read->dnload); - - e = param->error; - - if (param->msg_type==GF_NETIO_DATA_TRANSFERED) { - } else if (param->msg_type==GF_NETIO_PARSE_HEADER) { - if (!strcmp(param->name, "Content-Type")) { - if (strstr(param->value, "application/x-bt")) read->oti = GPAC_OTI_PRIVATE_SCENE_GENERIC; - if (strstr(param->value, "application/x-xmt")) read->oti = GPAC_OTI_PRIVATE_SCENE_GENERIC; - if (strstr(param->value, "model/vrml")) read->oti = GPAC_OTI_PRIVATE_SCENE_GENERIC; - if (strstr(param->value, "model/x3d+vrml")) read->oti = GPAC_OTI_PRIVATE_SCENE_GENERIC; - if (strstr(param->value, "application/x-shockwave-flash")) read->oti = GPAC_OTI_PRIVATE_SCENE_GENERIC; - if (strstr(param->value, "image/svg+xml")) read->oti = GPAC_OTI_PRIVATE_SCENE_SVG; - if (strstr(param->value, "image/x-svgm")) read->oti = GPAC_OTI_PRIVATE_SCENE_SVG; - if (strstr(param->value, "application/x-LASeR+xml")) read->oti = GPAC_OTI_PRIVATE_SCENE_GENERIC; - if (strstr(param->value, "application/widget")) read->oti = GPAC_OTI_PRIVATE_SCENE_WGT; - if (strstr(param->value, "application/x-mpegu-widget")) read->oti = GPAC_OTI_PRIVATE_SCENE_WGT; - } - return; - } else if (!e && (param->msg_type!=GF_NETIO_DATA_EXCHANGE)) return; - - if (!e && !read->oti) - return; - - /*OK confirm*/ - if (!read->is_service_connected) { - if (!gf_dm_sess_get_cache_name(read->dnload)) e = GF_IO_ERR; - if (e>0) e = GF_OK; - gf_service_connect_ack(read->service, NULL, e); - read->is_service_connected = GF_TRUE; - } -} - -void DC_DownloadFile(GF_InputService *plug, char *url) -{ - DCReader *read = (DCReader *) plug->priv; - - read->dnload = gf_service_download_new(read->service, url, 0, DC_NetIO, read); - if (!read->dnload) { - gf_service_connect_ack(read->service, NULL, GF_NOT_SUPPORTED); - } else { - /*start our download (threaded)*/ - gf_dm_sess_process(read->dnload); - } -} - - -GF_Err DC_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url) -{ - DCReader *read = (DCReader *) plug->priv; - FILE *test; - char *tmp, *ext; - - if (!read || !serv || !url) return GF_BAD_PARAM; - - if (read->dnload) gf_service_download_del(read->dnload); - read->dnload = NULL; - - read->url = gf_strdup(url); - ext = strchr(read->url, '#'); - if (ext) { - char *anext; - ext[0] = 0; - anext = strrchr(read->url, '.'); - ext[0] = '#'; - ext = anext; - } else { - ext = strrchr(read->url, '.'); - } - if (ext && !stricmp(ext, ".gz")) { - char *anext; - ext[0] = 0; - anext = strrchr(read->url, '.'); - ext[0] = '.'; - ext = anext; - } - read->service = serv; - - if (!strnicmp(url, "views://", 8)) { - read->is_views_url = GF_TRUE; - gf_service_connect_ack(serv, NULL, GF_OK); - read->is_service_connected = GF_TRUE; - return GF_OK; - } - - if (ext) { - char *cgi_par = NULL; - ext += 1; - if (ext) { - tmp = strchr(ext, '#'); - if (tmp) tmp[0] = 0; - /* Warning the '?' sign should not be present in local files but it is convenient to have it - to test web content locally */ - cgi_par = strchr(ext, '?'); - if (cgi_par) cgi_par[0] = 0; - } - if (!stricmp(ext, "bt") || !stricmp(ext, "btz") || !stricmp(ext, "bt.gz") - || !stricmp(ext, "xmta") - || !stricmp(ext, "xmt") || !stricmp(ext, "xmt.gz") || !stricmp(ext, "xmtz") - || !stricmp(ext, "wrl") || !stricmp(ext, "wrl.gz") - || !stricmp(ext, "x3d") || !stricmp(ext, "x3d.gz") || !stricmp(ext, "x3dz") - || !stricmp(ext, "x3dv") || !stricmp(ext, "x3dv.gz") || !stricmp(ext, "x3dvz") - || !stricmp(ext, "swf") - ) - read->oti = GPAC_OTI_PRIVATE_SCENE_GENERIC; - - else if (!stricmp(ext, "svg") || !stricmp(ext, "svgz") || !stricmp(ext, "svg.gz")) { - read->oti = GPAC_OTI_PRIVATE_SCENE_SVG; - } - /*XML LASeR*/ - else if (!stricmp(ext, "xsr")) - read->oti = GPAC_OTI_PRIVATE_SCENE_LASER; - else if (!stricmp(ext, "xbl")) - read->oti = GPAC_OTI_PRIVATE_SCENE_XBL; - else if (!stricmp(ext, "wgt") || !stricmp(ext, "mgt")) - read->oti = GPAC_OTI_PRIVATE_SCENE_WGT; - - if (cgi_par) cgi_par[0] = '?'; - } - - if (!read->oti && (!strnicmp(url, "file://", 7) || !strstr(url, "://"))) { - char *rtype = gf_xml_get_root_type(url, NULL); - if (rtype) { - if (!strcmp(rtype, "SAFSession")) read->oti = GPAC_OTI_PRIVATE_SCENE_LASER; - else if (!strcmp(rtype, "svg")) read->oti = GPAC_OTI_PRIVATE_SCENE_SVG; - else if (!strcmp(rtype, "XMT-A")) read->oti = GPAC_OTI_PRIVATE_SCENE_GENERIC; - else if (!strcmp(rtype, "X3D")) read->oti = GPAC_OTI_PRIVATE_SCENE_GENERIC; - else if (!strcmp(rtype, "bindings")) read->oti = GPAC_OTI_PRIVATE_SCENE_XBL; - else if (!strcmp(rtype, "widget")) read->oti = GPAC_OTI_PRIVATE_SCENE_WGT; - gf_free(rtype); - } - } - - /*remote fetch*/ - if (!strnicmp(url, "file://", 7)) { - } - else if (strstr(url, "://")) { - DC_DownloadFile(plug, read->url); - return GF_OK; - } - - test = gf_fopen(read->url, "rt"); - if (!test) { - gf_service_connect_ack(serv, NULL, GF_URL_ERROR); - return GF_OK; - } - gf_fclose(test); - if (!read->is_service_connected) { - gf_service_connect_ack(serv, NULL, GF_OK); - read->is_service_connected = GF_TRUE; - } - return GF_OK; -} - -GF_Err DC_CloseService(GF_InputService *plug) -{ - DCReader *read = (DCReader *) plug->priv; - if (read->dnload) gf_service_download_del(read->dnload); - read->dnload = NULL; - gf_service_disconnect_ack(read->service, NULL, GF_OK); - return GF_OK; -} - -/*Dummy input just send a file name, no multitrack to handle so we don't need to check sub_url nor expected type*/ -static GF_Descriptor *DC_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) -{ - u32 size = 0; - char *uri; - GF_ESD *esd; - GF_BitStream *bs; - DCReader *read = (DCReader *) plug->priv; - GF_InitialObjectDescriptor *iod = (GF_InitialObjectDescriptor *) gf_odf_desc_new(GF_ODF_IOD_TAG); - iod->scene_profileAndLevel = 1; - iod->graphics_profileAndLevel = 1; - iod->OD_profileAndLevel = 1; - iod->audio_profileAndLevel = 0xFE; - iod->visual_profileAndLevel = 0xFE; - iod->objectDescriptorID = 1; - - if (read->is_views_url) { - iod->URLString = gf_strdup(read->url); - return (GF_Descriptor *)iod; - } - - esd = gf_odf_desc_esd_new(0); - esd->slConfig->timestampResolution = 1000; - esd->slConfig->useTimestampsFlag = 1; - esd->ESID = 0xFFFE; - esd->decoderConfig->streamType = GF_STREAM_PRIVATE_SCENE; - esd->decoderConfig->objectTypeIndication = read->oti; - if (read->dnload) { - uri = (char *) gf_dm_sess_get_cache_name(read->dnload); - gf_dm_sess_get_stats(read->dnload, NULL, NULL, &size, NULL, NULL, NULL); - } else { - FILE *f = gf_fopen(read->url, "rt"); - gf_fseek(f, 0, SEEK_END); - size = (u32) gf_ftell(f); - gf_fclose(f); - uri = read->url; - } - bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - gf_bs_write_u32(bs, size); - gf_bs_write_data(bs, uri, (u32) strlen(uri)); - gf_bs_get_content(bs, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength); - gf_bs_del(bs); - - gf_list_add(iod->ESDescriptors, esd); - return (GF_Descriptor *)iod; -} - - -GF_Err DC_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) -{ - DCReader *read = (DCReader *) plug->priv; - DummyChannel *dc; - - if (!com->base.on_channel) return GF_NOT_SUPPORTED; - - dc = DC_GetChannel(read, com->base.on_channel); - if (!dc) return GF_STREAM_NOT_FOUND; - - switch (com->command_type) { - case GF_NET_CHAN_SET_PULL: - return GF_OK; - case GF_NET_CHAN_INTERACTIVE: - return GF_OK; - /*since data is file-based, no padding is needed (decoder plugin will handle it itself)*/ - case GF_NET_CHAN_SET_PADDING: - return GF_OK; - case GF_NET_CHAN_BUFFER: - com->buffer.max = com->buffer.min = 0; - return GF_OK; - case GF_NET_CHAN_DURATION: - /*this module is not made for updates, use undefined duration*/ - com->duration.duration = 0; - return GF_OK; - case GF_NET_CHAN_PLAY: - dc->start = (u32) (1000 * com->play.start_range); - dc->end = (u32) (1000 * com->play.end_range); - return GF_OK; - case GF_NET_CHAN_STOP: - return GF_OK; - case GF_NET_CHAN_CONFIG: - return GF_OK; - case GF_NET_CHAN_GET_DSI: - com->get_dsi.dsi = NULL; - com->get_dsi.dsi_len = 0; - return GF_OK; - default: - break; - } - return GF_OK; -} - -GF_Err DC_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) -{ - u32 ESID; - DCReader *read = (DCReader *) plug->priv; - - sscanf(url, "ES_ID=%ud", &ESID); - if (!ESID) { - gf_service_connect_ack(read->service, channel, GF_STREAM_NOT_FOUND); - } else { - DummyChannel *dc; - GF_SAFEALLOC(dc, DummyChannel); - if (!dc) return GF_OUT_OF_MEM; - dc->ch = channel; - dc->ESID = ESID; - gf_list_add(read->channels, dc); - gf_service_connect_ack(read->service, channel, GF_OK); - } - return GF_OK; -} - -GF_Err DC_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) -{ - Bool had_ch; - DCReader *read = (DCReader *) plug->priv; - - had_ch = DC_RemoveChannel(read, channel); - gf_service_disconnect_ack(read->service, channel, had_ch ? GF_OK : GF_STREAM_NOT_FOUND); - return GF_OK; -} - -GF_Err DC_ChannelGetSLP(GF_InputService *plug, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, GF_SLHeader *out_sl_hdr, Bool *sl_compressed, GF_Err *out_reception_status, Bool *is_new_data) -{ - DummyChannel *dc; - DCReader *read = (DCReader *) plug->priv; - dc = DC_GetChannel(read, channel); - if (!dc) return GF_STREAM_NOT_FOUND; - - memset(out_sl_hdr, 0, sizeof(GF_SLHeader)); - out_sl_hdr->compositionTimeStampFlag = 1; - out_sl_hdr->compositionTimeStamp = dc->start; - out_sl_hdr->accessUnitStartFlag = 1; - *sl_compressed = GF_FALSE; - *out_reception_status = GF_OK; - *is_new_data = GF_TRUE; - return GF_OK; -} - -GF_Err DC_ChannelReleaseSLP(GF_InputService *plug, LPNETCHANNEL channel) -{ - return GF_OK; -} - -Bool DC_CanHandleURLInService(GF_InputService *plug, const char *url) -{ - return GF_FALSE; -} - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { - GF_NET_CLIENT_INTERFACE, - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - DCReader *read; - GF_InputService *plug; - if (InterfaceType != GF_NET_CLIENT_INTERFACE) return NULL; - - GF_SAFEALLOC(plug, GF_InputService); - if (!plug) return NULL; - GF_SAFEALLOC(read, DCReader); - if (!read) { - gf_free(plug); - return NULL; - } - read->channels = gf_list_new(); - plug->priv = read; - - GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC Dummy Loader", "gpac distribution") - - - plug->RegisterMimeTypes = DC_RegisterMimeTypes; - plug->CanHandleURL = DC_CanHandleURL; - plug->ConnectService = DC_ConnectService; - plug->CloseService = DC_CloseService; - plug->GetServiceDescriptor = DC_GetServiceDesc; - plug->ConnectChannel = DC_ConnectChannel; - plug->DisconnectChannel = DC_DisconnectChannel; - plug->ServiceCommand = DC_ServiceCommand; - plug->CanHandleURLInService = DC_CanHandleURLInService; - plug->ChannelGetSLP = DC_ChannelGetSLP; - plug->ChannelReleaseSLP = DC_ChannelReleaseSLP; - - return (GF_BaseInterface *)plug; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *bi) -{ - GF_InputService *ifcn = (GF_InputService*)bi; - if (ifcn->InterfaceType==GF_NET_CLIENT_INTERFACE) { - DCReader *read = (DCReader*)ifcn->priv; - assert(!gf_list_count(read->channels)); - gf_list_del(read->channels); - if( read->url) gf_free(read->url); - gf_free(read); - gf_free(bi); - } -} - -GPAC_MODULE_STATIC_DECLARATION( dummy_in ) diff --git a/modules/dx_hw/Makefile b/modules/dx_hw/Makefile index 4d2311f..dc8f588 100644 --- a/modules/dx_hw/Makefile +++ b/modules/dx_hw/Makefile @@ -3,19 +3,19 @@ include ../../config.mak vpath %.c $(SRC_PATH)/modules/dx_hw #DIRECTSOUND_VERSION is needed for GCC compil.. -CFLAGS= $(OPTFLAGS) -w -I"$(SRC_PATH)/include" -DDIRECTSOUND_VERSION=0x0500 +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) -w -DDIRECTSOUND_VERSION=0x0500 -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif -ifeq ($(DX_PATH), system) +ifeq ($(DX_PATH),system) LDFLAGS_DX=-ldsound -ldxguid -lddraw -lole32 -lgdi32 -lopengl32 else CFLAGS+= -I$(DX_PATH)/include @@ -24,7 +24,7 @@ endif #common obj -OBJS=dx_audio.o dx_video.o dx_window.o dx_2d.o copy_pixels.o +OBJS=dx_audio.o dx_video.o dx_window.o dx_2d.o SRCS := $(OBJS:.o=.c) diff --git a/modules/dx_hw/copy_pixels.c b/modules/dx_hw/copy_pixels.c deleted file mode 100644 index 744c1ca..0000000 --- a/modules/dx_hw/copy_pixels.c +++ /dev/null @@ -1,736 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / 2D rendering module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include <gpac/setup.h> -#include <gpac/constants.h> -#include <gpac/color.h> - - -static u32 get_yuv_base(u32 in_pf) -{ - switch (in_pf) { - case GF_PIXEL_I420: - case GF_PIXEL_IYUV: - case GF_PIXEL_YV12: - return GF_PIXEL_YV12; - case GF_PIXEL_Y422: - case GF_PIXEL_UYNV: - case GF_PIXEL_UYVY: - return GF_PIXEL_UYVY; - case GF_PIXEL_YUNV: - case GF_PIXEL_V422: - case GF_PIXEL_YUY2: - return GF_PIXEL_YUY2; - case GF_PIXEL_YVYU: - return GF_PIXEL_YVYU; - case GF_PIXEL_YV12_10: - return GF_PIXEL_YV12_10; - case GF_PIXEL_YUV444: - return GF_PIXEL_YUV444; - case GF_PIXEL_YUV444_10: - return GF_PIXEL_YUV444_10; - case GF_PIXEL_YUV422_10: - return GF_PIXEL_YUV422_10; - case GF_PIXEL_YUV422: - return GF_PIXEL_YUV422; - default: - return 0; - } -} - -static Bool format_is_yuv(u32 in_pf) -{ - switch (in_pf) { - case GF_PIXEL_YUY2: - case GF_PIXEL_YVYU: - case GF_PIXEL_UYVY: - case GF_PIXEL_VYUY: - case GF_PIXEL_Y422: - case GF_PIXEL_UYNV: - case GF_PIXEL_YUNV: - case GF_PIXEL_V422: - case GF_PIXEL_YV12: - case GF_PIXEL_IYUV: - case GF_PIXEL_I420: - return GF_TRUE; - /*not supported yet*/ - case GF_PIXEL_YUVA: - default: - return GF_FALSE; - } -} - -static Bool is_planar_yuv(u32 pf) -{ - switch (pf) { - case GF_PIXEL_YV12: - case GF_PIXEL_I420: - case GF_PIXEL_IYUV: - return GF_TRUE; - } - return GF_FALSE; -} - - -static void write_yv12_to_yuv(GF_VideoSurface *vs, unsigned char *pY, u32 src_stride, u32 src_pf, - u32 src_width, u32 src_height, const GF_Window *src_wnd, u8 *pU, u8 *pV) -{ - - if (!pU) { - pU = pY + src_stride * src_height; - pV = pY + 5*src_stride * src_height/4; - } - - - pY = pY + src_stride * src_wnd->y + src_wnd->x; - /*because of U and V downsampling by 2x2, working with odd Y offset will lead to a half-line shift between Y and UV components. We - therefore force an even Y offset for U and V planes.*/ - pU = pU + (src_stride * (src_wnd->y / 2) + src_wnd->x) / 2; - pV = pV + (src_stride * (src_wnd->y / 2) + src_wnd->x) / 2; - - - if (is_planar_yuv(vs->pixel_format)) { - /*complete source copy*/ - if ( (vs->pitch_y == (s32) src_stride) && (src_wnd->w == src_width) && (src_wnd->h == src_height)) { - assert(!src_wnd->x); - assert(!src_wnd->y); - memcpy(vs->video_buffer, pY, sizeof(unsigned char)*src_width*src_height); - if (vs->pixel_format == GF_PIXEL_YV12) { - memcpy(vs->video_buffer + vs->pitch_y * vs->height, pV, sizeof(unsigned char)*src_width*src_height/4); - memcpy(vs->video_buffer + 5 * vs->pitch_y * vs->height/4, pU, sizeof(unsigned char)*src_width*src_height/4); - } else { - memcpy(vs->video_buffer + vs->pitch_y * vs->height, pU, sizeof(unsigned char)*src_width*src_height/4); - memcpy(vs->video_buffer + 5 * vs->pitch_y * vs->height/4, pV, sizeof(unsigned char)*src_width*src_height/4); - } - } else { - u32 i; - unsigned char *dst, *src, *dst2, *src2, *dst3, *src3; - - src = pY; - dst = (unsigned char*)vs->video_buffer; - - src2 = (vs->pixel_format != GF_PIXEL_YV12) ? pU : pV; - dst2 = (unsigned char*)vs->video_buffer + vs->pitch_y * vs->height; - src3 = (vs->pixel_format != GF_PIXEL_YV12) ? pV : pU; - dst3 = (unsigned char*)vs->video_buffer + 5*vs->pitch_y * vs->height/4; - for (i=0; i<src_wnd->h; i++) { - memcpy(dst, src, src_wnd->w); - src += src_stride; - dst += vs->pitch_y; - if (i<src_wnd->h/2) { - memcpy(dst2, src2, src_wnd->w/2); - src2 += src_stride/2; - dst2 += vs->pitch_y/2; - memcpy(dst3, src3, src_wnd->w/2); - src3 += src_stride/2; - dst3 += vs->pitch_y/2; - } - } - } - } else if (get_yuv_base(vs->pixel_format)==GF_PIXEL_UYVY) { - u32 i, j; - unsigned char *dst, *y, *u, *v; - for (i=0; i<src_wnd->h; i++) { - y = pY + i*src_stride; - u = pU + (i/2) * src_stride/2; - v = pV + (i/2) * src_stride/2; - dst = (unsigned char*)vs->video_buffer + i*vs->pitch_y; - - for (j=0; j<src_wnd->w/2; j++) { - *dst = *u; - dst++; - u++; - *dst = *y; - dst++; - y++; - *dst = *v; - dst++; - v++; - *dst = *y; - dst++; - y++; - } - } - } else if (get_yuv_base(vs->pixel_format)==GF_PIXEL_YUY2) { - u32 i, j; - unsigned char *dst, *y, *u, *v; - for (i=0; i<src_wnd->h; i++) { - y = pY + i*src_stride; - u = pU + (i/2) * src_stride/2; - v = pV + (i/2) * src_stride/2; - dst = (unsigned char*)vs->video_buffer + i*vs->pitch_y; - - for (j=0; j<src_wnd->w/2; j++) { - *dst = *y; - dst++; - y++; - *dst = *u; - dst++; - u++; - *dst = *y; - dst++; - y++; - *dst = *v; - dst++; - v++; - } - } - } else if (vs->pixel_format==GF_PIXEL_YVYU) { - u32 i, j; - unsigned char *dst, *y, *u, *v; - for (i=0; i<src_wnd->h; i++) { - y = pY + i*src_stride; - u = pU + (i/2) * src_stride/2; - v = pV + (i/2) * src_stride/2; - dst = (unsigned char*)vs->video_buffer + i*vs->pitch_y; - - for (j=0; j<src_wnd->w/2; j++) { - *dst = *y; - dst++; - y++; - *dst = *v; - dst++; - v++; - *dst = *y; - dst++; - y++; - *dst = *u; - dst++; - u++; - } - } - } -} -static void write_yuv422_to_yuv(GF_VideoSurface *vs, unsigned char *pY, u32 src_stride, u32 src_pf, - u32 src_width, u32 src_height, const GF_Window *src_wnd, u8 *pU, u8 *pV) -{ - - if (!pU) { - pU = pY + src_stride * src_height; - pV = pY + 3 * src_stride * src_height/2; - } - - - pY = pY + src_stride * src_wnd->y + src_wnd->x; - pU = pU + (src_stride * src_wnd->y + src_wnd->x)/2; - pV = pV + (src_stride *src_wnd->y + src_wnd->x)/2; - - - if (is_planar_yuv(vs->pixel_format)) { - /*complete source copy*/ - u32 i; - unsigned char *dst, *src, *dst2, *src2, *dst3, *src3, *_src2, *_src3; - - src = pY; - _src2 = (vs->pixel_format != GF_PIXEL_YV12) ? pU : pV; - _src3 = (vs->pixel_format != GF_PIXEL_YV12) ? pV : pU; - dst = (unsigned char*)vs->video_buffer; - dst2 = (unsigned char*)vs->video_buffer + vs->pitch_y * vs->height; - dst3 = (unsigned char*)vs->video_buffer + 5 * vs->pitch_y * vs->height / 4 ; - for (i = 0; i<src_wnd->h; i++) { - memcpy(dst, src, src_wnd->w); - src += src_stride; - dst += vs->pitch_y; - if (i < src_wnd->h / 2) - { - src2 = _src2 + i*src_stride; - src3 = _src3 + i*src_stride; - memcpy(dst2, src2, src_wnd->w / 2); - memcpy(dst3, src3, src_wnd->w / 2); - dst2 += vs->pitch_y / 2; - dst3 += vs->pitch_y / 2; - - } - - } - - - } - -} -static void write_yuv444_to_yuv(GF_VideoSurface *vs, unsigned char *pY, u32 src_stride, u32 src_pf, - u32 src_width, u32 src_height, const GF_Window *src_wnd, u8 *pU, u8 *pV) -{ - - if (!pU) { - pU = pY + src_stride * src_height; - pV = pY + 2 * src_stride * src_height ; - } - - - pY = pY + src_stride * src_wnd->y + src_wnd->x; - pU = pU + src_stride * src_wnd->y + src_wnd->x; - pV = pV + src_stride *src_wnd->y + src_wnd->x; - - - if (is_planar_yuv(vs->pixel_format)) { - /*complete source copy*/ - u32 i,j; - unsigned char *dst, *src, *dst2, *src2, *dst3, *src3,*_src2,*_src3; - - src = pY; - _src2 = (vs->pixel_format != GF_PIXEL_YV12) ? pU : pV; - _src3 = (vs->pixel_format != GF_PIXEL_YV12) ? pV : pU; - dst = (unsigned char*)vs->video_buffer; - - for (i = 0; i<src_wnd->h; i++) { - memcpy(dst, src, src_wnd->w); - src += src_stride; - dst += vs->pitch_y; - - } - for (i = 0; i < src_wnd->h / 2; i++) { - src2 = _src2 + 2*i*src_stride ; - dst2 = (unsigned char*)vs->video_buffer + vs->pitch_y * vs->height + i* vs->pitch_y / 2; - src3 = _src3 + 2*i*src_stride; - dst3 = (unsigned char*)vs->video_buffer + 5 * vs->pitch_y * vs->height / 4 + i* vs->pitch_y / 2; - for (j = 0; j<src_wnd->w/2; j++) { - *dst2 = *src2; - dst2++; - src2 += 2; - - *dst3 = *src3; - dst3++; - src3 += 2; - } - - } - - - } - -} - -static void write_yvyu_to_yuv(GF_VideoSurface *vs, unsigned char *src, u32 src_stride, u32 src_pf, - u32 src_width, u32 src_height, const GF_Window *src_wnd) -{ - u32 i, j, base_pf; - unsigned char *pY, *pU, *pV; - - switch (get_yuv_base(src_pf) ) { - case GF_PIXEL_UYVY: - pU = src + src_stride * src_wnd->y + src_wnd->x; - pY = src + src_stride * src_wnd->y + src_wnd->x + 1; - pV = src + src_stride * src_wnd->y + src_wnd->x + 3; - break; - case GF_PIXEL_YUY2: - pY = src + src_stride * src_wnd->y + src_wnd->x; - pU = src + src_stride * src_wnd->y + src_wnd->x + 1; - pV = src + src_stride * src_wnd->y + src_wnd->x + 3; - break; - case GF_PIXEL_YVYU: - pY = src + src_stride * src_wnd->y + src_wnd->x; - pV = src + src_stride * src_wnd->y + src_wnd->x + 1; - pU = src + src_stride * src_wnd->y + src_wnd->x + 3; - break; - default: - return; - } - - if (is_planar_yuv(vs->pixel_format)) { - u32 i, j; - unsigned char *dst_y, *dst_u, *dst_v; - - dst_y = (unsigned char*)vs->video_buffer; - if (vs->pixel_format == GF_PIXEL_YV12) { - dst_v = (unsigned char*)vs->video_buffer + vs->pitch_y * vs->height; - dst_u = (unsigned char*)vs->video_buffer + 5*vs->pitch_y * vs->height/4; - } else { - dst_u = (unsigned char*)vs->video_buffer + vs->pitch_y * vs->height; - dst_v = (unsigned char*)vs->video_buffer + 5*vs->pitch_y * vs->height/4; - } - for (i=0; i<src_wnd->h; i++) { - for (j=0; j<src_wnd->w; j+=2) { - *dst_y = * pY; - *(dst_y+1) = * (pY+2); - dst_y += 2; - pY += 4; - if (i%2) continue; - - *dst_u = (*pU + *(pU + src_stride)) / 2; - *dst_v = (*pV + *(pV + src_stride)) / 2; - dst_u++; - dst_v++; - pU += 4; - pV += 4; - } - if (i%2) { - pU += src_stride; - pV += src_stride; - } - } - return; - } - - if (get_yuv_base(src_pf) == get_yuv_base(vs->pixel_format)) { - u32 i; - for (i=0; i<src_wnd->h; i++) { - char *dst = vs->video_buffer + i*vs->pitch_y; - pY = src + src_stride * (i+src_wnd->y) + src_wnd->x; - memcpy(dst, pY, sizeof(char)*2*src_wnd->w); - } - return; - } - - base_pf = get_yuv_base(vs->pixel_format); - for (i=0; i<src_wnd->h; i++) { - char *dst = vs->video_buffer + i*vs->pitch_y; - char *y = (char*)pY + src_stride * i; - char *u = (char*)pU + src_stride * i; - char *v = (char*)pV + src_stride * i; - switch (base_pf) { - case GF_PIXEL_UYVY: - for (j=0; j<src_wnd->w; j+=2) { - dst[0] = *u; - dst[1] = *y; - dst[2] = *v; - dst[3] = *(y+2); - dst += 4; - y+=4; - u+=4; - v+=4; - } - break; - case GF_PIXEL_YVYU: - for (j=0; j<src_wnd->w; j+=2) { - dst[0] = *y; - dst[1] = *v; - dst[2] = *(y+2); - dst[3] = *u; - dst += 4; - y+=4; - u+=4; - v+=4; - } - break; - case GF_PIXEL_YUY2: - for (j=0; j<src_wnd->w; j+=2) { - dst[0] = *y; - dst[1] = *u; - dst[2] = *(y+2); - dst[3] = *v; - dst += 4; - y+=4; - u+=4; - v+=4; - } - break; - } - } -} - -u32 get_bpp(u32 pf) -{ - switch (pf) { - case GF_PIXEL_RGB_555: - case GF_PIXEL_RGB_565: - return 2; - case GF_PIXEL_RGB_24: - case GF_PIXEL_RGBS: - case GF_PIXEL_BGR_24: - return 3; - case GF_PIXEL_RGB_32: - case GF_PIXEL_BGR_32: - case GF_PIXEL_ARGB: - case GF_PIXEL_RGBAS: - case GF_PIXEL_RGBD: - case GF_PIXEL_RGBDS: - return 4; - } - return 0; -} - -void rgb_to_24(GF_VideoSurface *vs, unsigned char *src, u32 src_stride, u32 src_w, u32 src_h, u32 src_pf, const GF_Window *src_wnd) -{ - u32 i; - u32 BPP = get_bpp(src_pf); - if (!BPP) return; - - /*go to start of src*/ - src += src_stride*src_wnd->y + BPP * src_wnd->x; - - if (src_pf==vs->pixel_format) { - for (i=0; i<src_wnd->h; i++) { - memcpy(vs->video_buffer + i*vs->pitch_y, src, sizeof(unsigned char) * BPP * src_wnd->w); - src += src_stride; - } - return; - } -} - - -void rgb_to_555(GF_VideoSurface *vs, unsigned char *src, u32 src_stride, u32 src_w, u32 src_h, u32 src_pf, const GF_Window *src_wnd) -{ - u32 i, j, r, g, b; - u32 BPP = get_bpp(src_pf); - unsigned char *dst, *cur; - if (!BPP) return; - - /*go to start of src*/ - src += src_stride*src_wnd->y + BPP * src_wnd->x; - - if (src_pf==vs->pixel_format) { - for (i=0; i<src_wnd->h; i++) { - memcpy(vs->video_buffer + i*vs->pitch_y, src, sizeof(unsigned char) * BPP * src_wnd->w); - } - return; - } - switch (src_pf) { - case GF_PIXEL_RGB_24: - case GF_PIXEL_RGBS: - /*nope get all pixels*/ - for (i=0; i<src_wnd->h; i++) { - dst = (unsigned char*)vs->video_buffer + i*vs->pitch_y; - cur = src + i*src_stride; - for (j=0; j<src_wnd->w; j++) { - r = *cur++; - g = *cur++; - b = *cur++; - * ((unsigned short *)dst) = GF_COL_555(r, g, b); - dst += 2; - } - } - break; - } -} - -void rgb_to_565(GF_VideoSurface *vs, unsigned char *src, u32 src_stride, u32 src_w, u32 src_h, u32 src_pf, const GF_Window *src_wnd) -{ - u32 i, j, r, g, b; - u32 BPP = get_bpp(src_pf); - unsigned char *dst, *cur; - if (!BPP) return; - - /*go to start of src*/ - src += src_stride*src_wnd->y + BPP * src_wnd->x; - - if (src_pf==vs->pixel_format) { - for (i=0; i<src_wnd->h; i++) { - memcpy(vs->video_buffer + i*vs->pitch_y, src, sizeof(unsigned char) * BPP * src_wnd->w); - } - return; - } - /*nope get all pixels*/ - switch (src_pf) { - case GF_PIXEL_RGB_24: - case GF_PIXEL_RGBS: - for (i=0; i<src_wnd->h; i++) { - dst = (unsigned char*)vs->video_buffer + i*vs->pitch_y; - cur = src + i*src_stride; - for (j=0; j<src_wnd->w; j++) { - r = *cur++; - g = *cur++; - b = *cur++; - * ((unsigned short *)dst) = GF_COL_565(r, g, b); - dst += 2; - } - } - break; - } -} - -void rgb_to_32(GF_VideoSurface *vs, unsigned char *src, u32 src_stride, u32 src_w, u32 src_h, u32 src_pf, const GF_Window *src_wnd) -{ - u32 i, j; - Bool isBGR; - u32 BPP = get_bpp(src_pf); - unsigned char *dst, *cur; - if (!BPP) return; - - /*go to start of src*/ - src += src_stride*src_wnd->y + BPP * src_wnd->x; - - if (src_pf==vs->pixel_format) { - for (i=0; i<src_wnd->h; i++) { - memcpy(vs->video_buffer + i*vs->pitch_y, src, sizeof(unsigned char) * BPP * src_wnd->w); - } - return; - } - /*get all pixels*/ - isBGR = (vs->pixel_format==GF_PIXEL_BGR_32) ? GF_TRUE : GF_FALSE; - if (isBGR) { - switch (src_pf) { - case GF_PIXEL_RGB_24: - case GF_PIXEL_RGBS: - for (i=0; i<src_wnd->h; i++) { - dst = (unsigned char*)vs->video_buffer + i*vs->pitch_y; - cur = src + i*src_stride; - for (j=0; j<src_wnd->w; j++) { - dst[0] = *cur++; - dst[1] = *cur++; - dst[2] = *cur++; - dst += 4; - } - } - break; - case GF_PIXEL_RGBDS: - case GF_PIXEL_RGBD: - for (i=0; i<src_wnd->h; i++) { - dst = (unsigned char*)vs->video_buffer + i*vs->pitch_y; - cur = src + i*src_stride; - for (j=0; j<src_wnd->w; j++) { - dst[0] = *cur++; - dst[1] = *cur++; - dst[2] = *cur++; - cur++; - dst += 4; - } - } - break; - case GF_PIXEL_BGR_24: - for (i=0; i<src_wnd->h; i++) { - dst = (unsigned char*)vs->video_buffer + i*vs->pitch_y; - cur = src + i*src_stride; - for (j=0; j<src_wnd->w; j++) { - dst[2] = *cur++; - dst[1] = *cur++; - dst[0] = *cur++; - dst += 4; - } - } - break; - } - } else { - switch (src_pf) { - case GF_PIXEL_RGB_24: - case GF_PIXEL_RGBS: - for (i=0; i<src_wnd->h; i++) { - dst = (unsigned char*)vs->video_buffer + i*vs->pitch_y; - cur = src + i*src_stride; - for (j=0; j<src_wnd->w; j++) { - dst[2] = *cur++; - dst[1] = *cur++; - dst[0] = *cur++; - dst += 4; - } - } - break; - case GF_PIXEL_RGBD: - case GF_PIXEL_RGBDS: - for (i=0; i<src_wnd->h; i++) { - dst = (unsigned char*)vs->video_buffer + i*vs->pitch_y; - cur = src + i*src_stride; - for (j=0; j<src_wnd->w; j++) { - dst[2] = *cur++; - dst[1] = *cur++; - dst[0] = *cur++; - cur++; - dst += 4; - } - } - break; - case GF_PIXEL_BGR_24: - for (i=0; i<src_wnd->h; i++) { - dst = (unsigned char*)vs->video_buffer + i*vs->pitch_y; - cur = src + i*src_stride; - for (j=0; j<src_wnd->w; j++) { - dst[0] = *cur++; - dst[1] = *cur++; - dst[2] = *cur++; - dst += 4; - } - } - break; - } - } -} - -void dx_copy_pixels(GF_VideoSurface *dst_s, const GF_VideoSurface *src_s, const GF_Window *src_wnd) -{ - /*handle YUV input*/ - if (get_yuv_base(src_s->pixel_format)==GF_PIXEL_YV12) { - if (format_is_yuv(dst_s->pixel_format)) { - /*generic YV planar to YUV (planar or not) */ - write_yv12_to_yuv(dst_s, src_s->video_buffer, src_s->pitch_y, src_s->pixel_format, src_s->width, src_s->height, src_wnd, src_s->u_ptr, src_s->v_ptr); - return; - } - } else if (get_yuv_base(src_s->pixel_format)==GF_PIXEL_YV12_10) { - if (format_is_yuv(dst_s->pixel_format)) { - /*generic YV planar to YUV (planar or not) */ - gf_color_write_yv12_10_to_yuv(dst_s, src_s->video_buffer, src_s->u_ptr, src_s->v_ptr, src_s->pitch_y, src_s->width, src_s->height, src_wnd, 1); - return; - } - } - else if (get_yuv_base(src_s->pixel_format) == GF_PIXEL_YUV444) { - if (format_is_yuv(dst_s->pixel_format)) { - /*generic YV planar to YUV (planar or not) */ - write_yuv444_to_yuv(dst_s, src_s->video_buffer, src_s->pitch_y, src_s->pixel_format, src_s->width, src_s->height, src_wnd, src_s->u_ptr, src_s->v_ptr); - return; - } - } - else if (get_yuv_base(src_s->pixel_format) == GF_PIXEL_YUV422) { - if (format_is_yuv(dst_s->pixel_format)) { - /*generic YV planar to YUV (planar or not) */ - write_yuv422_to_yuv(dst_s, src_s->video_buffer, src_s->pitch_y, src_s->pixel_format, src_s->width, src_s->height, src_wnd, src_s->u_ptr, src_s->v_ptr); - return; - } - } - else if (get_yuv_base(src_s->pixel_format) == GF_PIXEL_YUV422_10) { - if (format_is_yuv(dst_s->pixel_format)) { - /*generic YV planar to YUV (planar or not) */ - gf_color_write_yuv422_10_to_yuv(dst_s, src_s->video_buffer, src_s->u_ptr, src_s->v_ptr, src_s->pitch_y, src_s->width, src_s->height, src_wnd, 1); - - return; - } - } - else if (get_yuv_base(src_s->pixel_format) == GF_PIXEL_YUV444_10) { - if (format_is_yuv(dst_s->pixel_format)) { - /*generic YV planar to YUV (planar or not) */ - gf_color_write_yuv444_10_to_yuv(dst_s, src_s->video_buffer, src_s->u_ptr, src_s->v_ptr, src_s->pitch_y, src_s->width, src_s->height, src_wnd, 1); - - return; - } - } - else if (format_is_yuv(src_s->pixel_format)) { - if (format_is_yuv(dst_s->pixel_format)) { - write_yvyu_to_yuv(dst_s, src_s->video_buffer, src_s->pitch_y, src_s->pixel_format, src_s->width, src_s->height, src_wnd); - return; - } - } else { - switch (dst_s->pixel_format) { - case GF_PIXEL_RGB_555: - rgb_to_555(dst_s, src_s->video_buffer, src_s->pitch_y, src_s->width, src_s->height, src_s->pixel_format, src_wnd); - return; - case GF_PIXEL_RGB_565: - rgb_to_565(dst_s, src_s->video_buffer, src_s->pitch_y, src_s->width, src_s->height, src_s->pixel_format, src_wnd); - return; - case GF_PIXEL_RGB_24: - case GF_PIXEL_RGBS: - case GF_PIXEL_BGR_24: - rgb_to_24(dst_s, src_s->video_buffer, src_s->pitch_y, src_s->width, src_s->height, src_s->pixel_format, src_wnd); - return; - case GF_PIXEL_RGB_32: - case GF_PIXEL_RGBD: - case GF_PIXEL_RGBDS: - case GF_PIXEL_BGR_32: - rgb_to_32(dst_s, src_s->video_buffer, src_s->pitch_y, src_s->width, src_s->height, src_s->pixel_format, src_wnd); - return; - } - } - - gf_stretch_bits(dst_s, (GF_VideoSurface*) src_s, NULL, (GF_Window *)src_wnd, 0xFF, GF_FALSE, NULL, NULL); -} - - diff --git a/modules/dx_hw/dx_2d.c b/modules/dx_hw/dx_2d.c index 17e1f32..1e54ab3 100644 --- a/modules/dx_hw/dx_2d.c +++ b/modules/dx_hw/dx_2d.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / DirectX audio and video render module @@ -29,28 +29,42 @@ #define DDCONTEXT DDContext *dd = (DDContext *)dr->opaque; #define DDBACK DDSurface *pBack = (DDSurface *) gf_list_get(dd->surfaces, 0); +enum +{ + GF_PIXEL_IYUV = GF_4CC('I', 'Y', 'U', 'V'), + GF_PIXEL_I420 = GF_4CC('I', '4', '2', '0'), + /*!YUV packed format*/ + GF_PIXEL_UYNV = GF_4CC('U', 'Y', 'N', 'V'), + /*!YUV packed format*/ + GF_PIXEL_YUNV = GF_4CC('Y', 'U', 'N', 'V'), + /*!YUV packed format*/ + GF_PIXEL_V422 = GF_4CC('V', '4', '2', '2'), + + GF_PIXEL_YV12 = GF_4CC('Y', 'V', '1', '2'), + GF_PIXEL_Y422 = GF_4CC('Y', '4', '2', '2'), + GF_PIXEL_YUY2 = GF_4CC('Y', 'U', 'Y', '2'), +}; static Bool pixelformat_yuv(u32 pixel_format) { switch (pixel_format) { - case GF_PIXEL_YUY2: + case GF_PIXEL_YUYV: case GF_PIXEL_YVYU: case GF_PIXEL_UYVY: case GF_PIXEL_VYUY: - case GF_PIXEL_Y422: case GF_PIXEL_UYNV: case GF_PIXEL_YUNV: case GF_PIXEL_V422: - case GF_PIXEL_YV12: + case GF_PIXEL_YUV: case GF_PIXEL_IYUV: case GF_PIXEL_I420: - case GF_PIXEL_YV12_10: + case GF_PIXEL_YUV_10: case GF_PIXEL_YUV444: case GF_PIXEL_YUV444_10: - case GF_PIXEL_YUV422_10: case GF_PIXEL_YUV422: + case GF_PIXEL_YUV422_10: return GF_TRUE; default: return GF_FALSE; @@ -61,7 +75,7 @@ static Bool pixelformat_yuv(u32 pixel_format) static u32 get_win_4CC(u32 pixel_format) { switch (pixel_format) { - case GF_PIXEL_YUY2: + case GF_PIXEL_YUYV: return mmioFOURCC('Y', 'U', 'Y', '2'); case GF_PIXEL_YVYU: return mmioFOURCC('Y', 'V', 'Y', 'U'); @@ -69,7 +83,7 @@ static u32 get_win_4CC(u32 pixel_format) return mmioFOURCC('U', 'Y', 'V', 'Y'); case GF_PIXEL_VYUY: return mmioFOURCC('V', 'Y', 'U', 'Y'); - case GF_PIXEL_Y422: + case GF_PIXEL_YUV422: return mmioFOURCC('Y', '4', '2', '2'); case GF_PIXEL_UYNV: return mmioFOURCC('U', 'Y', 'N', 'V'); @@ -77,13 +91,13 @@ static u32 get_win_4CC(u32 pixel_format) return mmioFOURCC('Y', 'U', 'N', 'V'); case GF_PIXEL_V422: return mmioFOURCC('V', '4', '2', '2'); - case GF_PIXEL_YV12: + case GF_PIXEL_YUV: return mmioFOURCC('Y', 'V', '1', '2'); case GF_PIXEL_IYUV: return mmioFOURCC('I', 'Y', 'U', 'V'); case GF_PIXEL_I420: return mmioFOURCC('I', '4', '2', '0'); - case GF_PIXEL_YV12_10: + case GF_PIXEL_YUV_10: return mmioFOURCC('P', '0', '1', '0'); case GF_PIXEL_YUV444: return mmioFOURCC('Y', '4', '4', '4'); @@ -91,13 +105,16 @@ static u32 get_win_4CC(u32 pixel_format) return mmioFOURCC('Y', '4', '1', '0'); case GF_PIXEL_YUV422_10: return mmioFOURCC('Y', '2', '1', '0'); - case GF_PIXEL_YUV422: - return mmioFOURCC('Y', '4', '4', '2'); default: return 0; } } +/*!\hideinitializer transfoms a 32-bits color into a 16-bits one.\note alpha component is lost*/ +#define GF_COL_TO_565(c) (((GF_COL_R(c) & 248)<<8) + ((GF_COL_G(c) & 252)<<3) + (GF_COL_B(c)>>3)) +/*!\hideinitializer transfoms a 32-bits color into a 15-bits one.\note alpha component is lost*/ +#define GF_COL_TO_555(c) (((GF_COL_R(c) & 248)<<7) + ((GF_COL_G(c) & 248)<<2) + (GF_COL_B(c)>>3)) + static GF_Err DD_ClearBackBuffer(GF_VideoOutput *dr, u32 color) { HRESULT hr; @@ -137,13 +154,13 @@ GF_Err CreateBackBuffer(GF_VideoOutput *dr, u32 Width, u32 Height, Bool use_syst DDCONTEXT; - opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "HardwareMemory"); + opt = gf_opts_get_key("core", "hwvmem"); if (opt) { if (use_system_memory) { - if (!strcmp(opt, "Always")) use_system_memory = GF_FALSE; + if (!strcmp(opt, "always")) use_system_memory = GF_FALSE; } else { - if (!strcmp(opt, "Never")) use_system_memory = GF_TRUE; - else if (!strcmp(opt, "Auto")) dd->force_video_mem_for_yuv = GF_TRUE; + if (!strcmp(opt, "never")) use_system_memory = GF_TRUE; + else if (!strcmp(opt, "auto")) dd->force_video_mem_for_yuv = GF_TRUE; } } @@ -279,16 +296,16 @@ GF_Err InitDirectDraw(GF_VideoOutput *dr, u32 Width, u32 Height) break; case 24: if ((pixelFmt.dwRBitMask == 0x0000FF) && (pixelFmt.dwGBitMask == 0x00FF00) && (pixelFmt.dwBBitMask == 0xFF0000)) - dd->pixelFormat = GF_PIXEL_BGR_24; + dd->pixelFormat = GF_PIXEL_BGR; else if ((pixelFmt.dwRBitMask == 0xFF0000) && (pixelFmt.dwGBitMask == 0x00FF00) && (pixelFmt.dwBBitMask == 0x0000FF)) - dd->pixelFormat = GF_PIXEL_RGB_24; + dd->pixelFormat = GF_PIXEL_RGB; dd->video_bpp = 24; break; case 32: if ((pixelFmt.dwRBitMask == 0x0000FF) && (pixelFmt.dwGBitMask == 0x00FF00) && (pixelFmt.dwBBitMask == 0xFF0000)) { - dd->pixelFormat = dd->force_alpha ? GF_PIXEL_RGBA : GF_PIXEL_BGR_32; + dd->pixelFormat = dd->force_alpha ? GF_PIXEL_RGBA : GF_PIXEL_BGRX; } else if ((pixelFmt.dwRBitMask == 0xFF0000) && (pixelFmt.dwGBitMask == 0x00FF00) && (pixelFmt.dwBBitMask == 0x0000FF)) { - dd->pixelFormat = dd->force_alpha ? GF_PIXEL_ARGB : GF_PIXEL_RGB_32; + dd->pixelFormat = dd->force_alpha ? GF_PIXEL_ARGB : GF_PIXEL_RGBX; } dd->video_bpp = 32; break; @@ -563,7 +580,7 @@ static GF_Err DD_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window } /*get RGB or YUV pool surface*/ //if (video_src->pixel_format==GF_PIXEL_YUVD) return GF_NOT_SUPPORTED; - if (video_src->pixel_format==GF_PIXEL_YUVD) video_src->pixel_format=GF_PIXEL_YV12; + if (video_src->pixel_format==GF_PIXEL_YUVD) video_src->pixel_format = GF_PIXEL_YUV; pool = DD_GetSurface(dr, w, h, video_src->pixel_format, GF_FALSE); if (!pool) return GF_IO_ERR; @@ -574,9 +591,9 @@ static GF_Err DD_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window if (e) return e; /*copy pixels to pool*/ - dx_copy_pixels(&temp_surf, video_src, src_wnd); + e = gf_stretch_bits(&temp_surf, video_src, NULL, src_wnd, 0xFF, GF_FALSE, NULL, NULL); - e = DD_UnlockSurface(dd, pool->pSurface); + DD_UnlockSurface(dd, pool->pSurface); if (e) return e; if (overlay_type) { @@ -595,9 +612,7 @@ static GF_Err DD_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window dst.y = pt.y; MAKERECT(dst_rc, (&dst)); - GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DX] Blit surface to dest %d x %d - overlay type %s\n", dst.w, dst.h, - (overlay_type==0)? "none" : ((overlay_type==1) ? "Top-Level" : "ColorKey") - )); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DX] Blit surface to dest %d x %d - overlay type %s\n", dst.w, dst.h, (overlay_type==1) ? "Top-Level" : "ColorKey" )); #if 1 if (overlay_type==1) { @@ -642,16 +657,15 @@ static GF_Err DD_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window } - static GFINLINE u32 is_yuv_supported(u32 win_4cc) { - if (win_4cc==get_win_4CC(GF_PIXEL_YV12)) return GF_PIXEL_YV12; + if (win_4cc==get_win_4CC(GF_PIXEL_YV12)) return GF_PIXEL_YUV; else if (win_4cc==get_win_4CC(GF_PIXEL_I420)) return GF_PIXEL_I420; else if (win_4cc==get_win_4CC(GF_PIXEL_IYUV)) return GF_PIXEL_IYUV; else if (win_4cc==get_win_4CC(GF_PIXEL_UYVY)) return GF_PIXEL_UYVY; - else if (win_4cc==get_win_4CC(GF_PIXEL_Y422)) return GF_PIXEL_Y422; + else if (win_4cc==get_win_4CC(GF_PIXEL_Y422)) return GF_PIXEL_YUV422; else if (win_4cc==get_win_4CC(GF_PIXEL_UYNV)) return GF_PIXEL_UYNV; - else if (win_4cc==get_win_4CC(GF_PIXEL_YUY2)) return GF_PIXEL_YUY2; + else if (win_4cc==get_win_4CC(GF_PIXEL_YUY2)) return GF_PIXEL_YUYV; else if (win_4cc==get_win_4CC(GF_PIXEL_YUNV)) return GF_PIXEL_YUNV; else if (win_4cc==get_win_4CC(GF_PIXEL_V422)) return GF_PIXEL_V422; else if (win_4cc==get_win_4CC(GF_PIXEL_YVYU)) return GF_PIXEL_YVYU; @@ -661,9 +675,14 @@ static GFINLINE u32 is_yuv_supported(u32 win_4cc) static GFINLINE Bool is_yuv_planar(u32 format) { switch (format) { - case GF_PIXEL_YV12: + case GF_PIXEL_YUV: + case GF_PIXEL_YUV_10: case GF_PIXEL_I420: case GF_PIXEL_IYUV: + case GF_PIXEL_YUV422: + case GF_PIXEL_YUV422_10: + case GF_PIXEL_YUV444: + case GF_PIXEL_YUV444_10: return GF_TRUE; default: return GF_FALSE; @@ -691,13 +710,13 @@ void DD_InitYUV(GF_VideoOutput *dr) h = 576; - opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "PreferedYUV4CC"); + opt = gf_opts_get_key("core", "pref-yuv4cc"); if (opt) { char a, b, c, d; if (sscanf(opt, "%c%c%c%c", &a, &b, &c, &d)==4) { dr->yuv_pixel_format = GF_4CC(a, b, c, d); dr->hw_caps |= GF_VIDEO_HW_HAS_YUV; - opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "HasOverlay"); + opt = gf_opts_get_key("core", "yuv-overlay"); if (opt && !strcmp(opt, "yes")) dr->hw_caps |= GF_VIDEO_HW_HAS_YUV_OVERLAY; dd->yuv_init = GF_TRUE; @@ -737,11 +756,9 @@ void DD_InitYUV(GF_VideoOutput *dr) /*then check packed */ if (checkPacked && is_yuv_planar(formats[i])) goto go_on; - if (dd->yuv_pool.pSurface) { - SAFE_DD_RELEASE(dd->yuv_pool.pSurface); - memset(&dd->yuv_pool, 0, sizeof(DDSurface)); - dd->yuv_pool.is_yuv = GF_TRUE; - } + SAFE_DD_RELEASE(dd->yuv_pool.pSurface); + memset(&dd->yuv_pool, 0, sizeof(DDSurface)); + dd->yuv_pool.is_yuv = GF_TRUE; dr->yuv_pixel_format = formats[i]; if (DD_GetSurface(dr, w, h, dr->yuv_pixel_format, GF_TRUE) == NULL) @@ -754,7 +771,7 @@ void DD_InitYUV(GF_VideoOutput *dr) goto rem_fmt; } now = gf_sys_clock() - now; - if (formats[i]== GF_PIXEL_YV12) + if (formats[i]== GF_PIXEL_YUV) force_yv12 = GF_TRUE; if (!checkPacked) { @@ -784,11 +801,9 @@ rem_fmt: num_yuv--; } - if (dd->yuv_pool.pSurface) { - SAFE_DD_RELEASE(dd->yuv_pool.pSurface); - memset(&dd->yuv_pool, 0, sizeof(DDSurface)); - dd->yuv_pool.is_yuv = GF_TRUE; - } + SAFE_DD_RELEASE(dd->yuv_pool.pSurface); + memset(&dd->yuv_pool, 0, sizeof(DDSurface)); + dd->yuv_pool.is_yuv = GF_TRUE; if (best_planar && (min_planar <= min_packed )) { dr->yuv_pixel_format = best_planar; @@ -797,16 +812,16 @@ rem_fmt: dr->yuv_pixel_format = best_packed; } if (force_yv12) - dr->yuv_pixel_format = GF_PIXEL_YV12; + dr->yuv_pixel_format = GF_PIXEL_YUV; /*store our options*/ sprintf(szOpt, "%c%c%c%c", (dr->yuv_pixel_format>>24) & 0xFF, (dr->yuv_pixel_format>>16) & 0xFF, (dr->yuv_pixel_format>>8) & 0xFF, (dr->yuv_pixel_format) & 0xFF); - gf_modules_set_option((GF_BaseInterface *)dr, "Video", "PreferedYUV4CC", szOpt); - gf_modules_set_option((GF_BaseInterface *)dr, "Video", "HasOverlay", (dr->hw_caps & GF_VIDEO_HW_HAS_YUV_OVERLAY) ? "yes" : "no"); + gf_opts_set_key("core", "pref-yuv4cc", szOpt); + gf_opts_set_key("core", "yuv-overlay", (dr->hw_caps & GF_VIDEO_HW_HAS_YUV_OVERLAY) ? "yes" : "no"); } - opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "HardwareMemory"); - if (opt && !strcmp(opt, "Never")) num_yuv = 0; + opt = gf_opts_get_key("core", "hwvmem"); + if (opt && !strcmp(opt, "never")) num_yuv = 0; /*too bad*/ if (!num_yuv) { @@ -819,21 +834,21 @@ rem_fmt: GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DX Out] Picked YUV format %s - drawn in %d ms\n", gf_4cc_to_str(dr->yuv_pixel_format), min_planar)); /*enable YUV->RGB on the backbuffer ?*/ - opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "EnableOffscreenYUV"); + opt = gf_opts_get_key("core", "offscreen-yuv"); /*no set is the default*/ if (!opt) { opt = "yes"; - gf_modules_set_option((GF_BaseInterface *)dr, "Video", "EnableOffscreenYUV", "yes"); + gf_opts_set_key("core", "offscreen-yuv", "yes"); } if (opt && strcmp(opt, "yes")) dr->hw_caps &= ~GF_VIDEO_HW_HAS_YUV; else dd->offscreen_yuv_to_rgb = GF_TRUE; /*get YUV overlay key*/ - opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "OverlayColorKey"); + opt = gf_opts_get_key("core", "overlay-color-key"); /*no set is the default*/ if (!opt) { opt = "0101FE"; - gf_modules_set_option((GF_BaseInterface *)dr, "Video", "OverlayColorKey", "0101FE"); + gf_opts_set_key("core", "overlay-color-key", "0101FE"); } sscanf(opt, "%06x", &dr->overlay_color_key); if (dr->overlay_color_key) dr->overlay_color_key |= 0xFF000000; @@ -848,7 +863,7 @@ GF_Err DD_SetBackBufferSize(GF_VideoOutput *dr, u32 width, u32 height, Bool use_ GF_Err e; DDCONTEXT; #ifndef GPAC_DISABLE_3D - if (dd->output_3d_type) return GF_BAD_PARAM; + if (dd->output_3d) return GF_BAD_PARAM; #endif if (!dd->ddraw_init) { e = InitDirectDraw(dr, width, height); diff --git a/modules/dx_hw/dx_audio.c b/modules/dx_hw/dx_audio.c index 241fdfa..fe004d7 100644 --- a/modules/dx_hw/dx_audio.c +++ b/modules/dx_hw/dx_audio.c @@ -75,7 +75,6 @@ void DS_WriteAudio_Notifs(GF_AudioOutput *dr); static GF_Err DS_Setup(GF_AudioOutput *dr, void *os_handle, u32 num_buffers, u32 total_duration) { - DWORD flags; HRESULT hr; DSCONTEXT(); @@ -91,7 +90,7 @@ static GF_Err DS_Setup(GF_AudioOutput *dr, void *os_handle, u32 num_buffers, u32 if (ctx->cfg_num_buffers <= 1) ctx->cfg_num_buffers = 2; if ( FAILED( hr = ctx->DirectSoundCreate( NULL, &ctx->pDS, NULL ) ) ) return GF_IO_ERR; - flags = DSSCL_EXCLUSIVE; + if( FAILED( hr = ctx->pDS->lpVtbl->SetCooperativeLevel(ctx->pDS, ctx->hWnd, DSSCL_EXCLUSIVE) ) ) { SAFE_DS_RELEASE( ctx->pDS ); return GF_IO_ERR; @@ -135,11 +134,10 @@ static void DS_Shutdown(GF_AudioOutput *dr) } /*we assume what was asked is what we got*/ -static GF_Err DS_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *nbBitsPerSample, u32 channel_cfg) +static GF_Err DS_Configure(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *audioFormat, u64 channel_cfg) { u32 i; HRESULT hr; - const char *sOpt; DSBUFFERDESC dsbBufferDesc; IDirectSoundNotify *pNotify; #ifdef USE_WAVE_EX @@ -148,15 +146,29 @@ static GF_Err DS_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbCha DSCONTEXT(); DS_ReleaseBuffer(dr); - + //only support for PCM 8/16/24/32 packet mode + switch (*audioFormat) { + case GF_AUDIO_FMT_U8: + case GF_AUDIO_FMT_S16: + case GF_AUDIO_FMT_S24: + case GF_AUDIO_FMT_S32: + break; + default: + //otherwise force PCM16 + *audioFormat = GF_AUDIO_FMT_S16; + break; + } ctx->format.nChannels = *NbChannels; - ctx->format.wBitsPerSample = *nbBitsPerSample; + ctx->format.wBitsPerSample = gf_audio_fmt_bit_depth( *audioFormat); ctx->format.nSamplesPerSec = *SampleRate; ctx->format.cbSize = sizeof (WAVEFORMATEX); ctx->format.wFormatTag = WAVE_FORMAT_PCM; ctx->format.nBlockAlign = ctx->format.nChannels * ctx->format.wBitsPerSample / 8; ctx->format.nAvgBytesPerSec = ctx->format.nSamplesPerSec * ctx->format.nBlockAlign; + if (!ctx->format.nBlockAlign) + return GF_BAD_PARAM; + if (!ctx->force_config) { ctx->buffer_size = ctx->format.nBlockAlign * 1024; ctx->num_audio_buffer = 2; @@ -169,8 +181,8 @@ static GF_Err DS_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbCha while (ctx->buffer_size % ctx->format.nBlockAlign) ctx->buffer_size++; ctx->use_notif = GF_TRUE; - sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "Audio", "DisableNotification"); - if (sOpt && !stricmp(sOpt, "yes")) ctx->use_notif = GF_FALSE; + if (gf_opts_get_bool("core", "ds-disable-notif")) + ctx->use_notif = GF_FALSE; memset(&dsbBufferDesc, 0, sizeof(DSBUFFERDESC)); dsbBufferDesc.dwSize = sizeof (DSBUFFERDESC); @@ -185,17 +197,19 @@ static GF_Err DS_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbCha format_ex.Format = ctx->format; format_ex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE); format_ex.SubFormat = GPAC_KSDATAFORMAT_SUBTYPE_PCM; - format_ex.Samples.wValidBitsPerSample = *nbBitsPerSample; + format_ex.Samples.wValidBitsPerSample = gf_audio_fmt_bit_depth(*audioFormat); + format_ex.dwChannelMask = 0; if (channel_cfg & GF_AUDIO_CH_FRONT_LEFT) format_ex.dwChannelMask |= SPEAKER_FRONT_LEFT; if (channel_cfg & GF_AUDIO_CH_FRONT_RIGHT) format_ex.dwChannelMask |= SPEAKER_FRONT_RIGHT; if (channel_cfg & GF_AUDIO_CH_FRONT_CENTER) format_ex.dwChannelMask |= SPEAKER_FRONT_CENTER; if (channel_cfg & GF_AUDIO_CH_LFE) format_ex.dwChannelMask |= SPEAKER_LOW_FREQUENCY; - if (channel_cfg & GF_AUDIO_CH_BACK_LEFT) format_ex.dwChannelMask |= SPEAKER_BACK_LEFT; - if (channel_cfg & GF_AUDIO_CH_BACK_RIGHT) format_ex.dwChannelMask |= SPEAKER_BACK_RIGHT; - if (channel_cfg & GF_AUDIO_CH_BACK_CENTER) format_ex.dwChannelMask |= SPEAKER_BACK_CENTER; - if (channel_cfg & GF_AUDIO_CH_SIDE_LEFT) format_ex.dwChannelMask |= SPEAKER_SIDE_LEFT; - if (channel_cfg & GF_AUDIO_CH_SIDE_RIGHT) format_ex.dwChannelMask |= SPEAKER_SIDE_RIGHT; + if (channel_cfg & GF_AUDIO_CH_SURROUND_LEFT) format_ex.dwChannelMask |= SPEAKER_BACK_LEFT; + if (channel_cfg & GF_AUDIO_CH_SURROUND_RIGHT) format_ex.dwChannelMask |= SPEAKER_BACK_RIGHT; + if (channel_cfg & GF_AUDIO_CH_REAR_CENTER) format_ex.dwChannelMask |= SPEAKER_BACK_CENTER; + if (channel_cfg & GF_AUDIO_CH_SIDE_SURROUND_LEFT) format_ex.dwChannelMask |= SPEAKER_SIDE_LEFT; + if (channel_cfg & GF_AUDIO_CH_SIDE_SURROUND_RIGHT) format_ex.dwChannelMask |= SPEAKER_SIDE_RIGHT; + dsbBufferDesc.lpwfxFormat = (WAVEFORMATEX *) &format_ex; } #endif @@ -204,7 +218,7 @@ static GF_Err DS_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbCha hr = ctx->pDS->lpVtbl->CreateSoundBuffer(ctx->pDS, &dsbBufferDesc, &ctx->pOutput, NULL ); if (FAILED(hr)) { retry: - if (ctx->use_notif) gf_modules_set_option((GF_BaseInterface *)dr, "Audio", "DisableNotification", "yes"); + if (ctx->use_notif) gf_opts_set_key("core", "ds-disable-notif", "yes"); ctx->use_notif = GF_FALSE; dsbBufferDesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS; hr = ctx->pDS->lpVtbl->CreateSoundBuffer(ctx->pDS, &dsbBufferDesc, &ctx->pOutput, NULL ); @@ -212,7 +226,7 @@ retry: if (ctx->format.nChannels>2) { GF_LOG(GF_LOG_ERROR, GF_LOG_AUDIO, ("[DirectSound] failed to configure output for %d channels (error %08x) - falling back to stereo\n", *NbChannels, hr)); *NbChannels = 2; - return DS_ConfigureOutput(dr, SampleRate, NbChannels, nbBitsPerSample, 0); + return DS_Configure(dr, SampleRate, NbChannels, audioFormat, 0); } return GF_IO_ERR; } @@ -334,6 +348,7 @@ void DS_WriteAudio(GF_AudioOutput *dr) u32 retry; DWORD in_play, cur_play; DSCONTEXT(); + if (!ctx->pOutput) return; /*wait for end of current play buffer*/ if (ctx->pOutput->lpVtbl->GetCurrentPosition(ctx->pOutput, &in_play, NULL) != DS_OK ) { @@ -385,7 +400,8 @@ static void DS_SetVolume(GF_AudioOutput *dr, u32 Volume) { LONG Vol; DSCONTEXT(); - if (Volume > 100) Volume = DSBVOLUME_MAX; + if (!ctx->pOutput) return; + if (Volume > 100) Vol = DSBVOLUME_MAX; else if(Volume == 0) Vol = DSBVOLUME_MIN; else Vol = DSBVOLUME_MIN/2 + Volume * (DSBVOLUME_MAX-DSBVOLUME_MIN/2) / 100; ctx->pOutput->lpVtbl->SetVolume(ctx->pOutput, Vol); @@ -395,6 +411,7 @@ static void DS_SetPan(GF_AudioOutput *dr, u32 Pan) { LONG dspan; DSCONTEXT(); + if (!ctx->pOutput) return; if (Pan > 100) Pan = 100; if (Pan > 50) { @@ -458,7 +475,7 @@ void *NewAudioOutput() driv->Setup = DS_Setup; driv->Shutdown = DS_Shutdown; - driv->ConfigureOutput = DS_ConfigureOutput; + driv->Configure = DS_Configure; driv->SetVolume = DS_SetVolume; driv->SetPan = DS_SetPan; driv->Play = DS_Play; diff --git a/modules/dx_hw/dx_hw.h b/modules/dx_hw/dx_hw.h index 3342161..0af339e 100644 --- a/modules/dx_hw/dx_hw.h +++ b/modules/dx_hw/dx_hw.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / DirectX audio and video render module @@ -36,10 +36,14 @@ #include <gpac/modules/video_out.h> #include <gpac/list.h> #include <gpac/constants.h> -#include <gpac/thread.h> +//define before thread.h which includes windows.h and winbase.h #define INITGUID +#include <gpac/thread.h> + + + /* #include <windows.h> */ @@ -171,11 +175,10 @@ typedef struct #else HDC gl_HDC, pb_HDC; HGLRC gl_HRC, pb_HRC; - void *pbuffer; Bool glext_init; #endif - u32 output_3d_type; - HWND gl_hwnd, bound_hwnd; + Bool output_3d; + HWND bound_hwnd; Bool gl_double_buffer; /*0: not init, 1: used, 2: not used*/ u32 mode_high_bpp; @@ -211,8 +214,6 @@ GF_Err DD_SetBackBufferSize(GF_VideoOutput *dr, u32 width, u32 height, Bool use_ GF_Err DD_FlushEx(GF_VideoOutput *dr, GF_Window *dest, Bool wait_for_sync); -void dx_copy_pixels(GF_VideoSurface *dst_s, const GF_VideoSurface *src_s, const GF_Window *src_wnd); - #define MAKERECT(rc, dest) { rc.left = dest->x; rc.top = dest->y; rc.right = rc.left + dest->w; rc.bottom = rc.top + dest->h; } /*this is REALLY ugly, to pass the HWND to DSound when we create the window in this module*/ diff --git a/modules/dx_hw/dx_hw.rc b/modules/dx_hw/dx_hw.rc index fb3903d..d49b695 100644 --- a/modules/dx_hw/dx_hw.rc +++ b/modules/dx_hw/dx_hw.rc @@ -57,7 +57,7 @@ IDC_COLLIDE CURSOR "collide.cur" // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. -IDI_OSMO_ICON ICON "../../doc/osmo4.ico" +IDI_OSMO_ICON ICON "../../share/doc/osmo4.ico" #endif // Français (France) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/modules/dx_hw/dx_video.c b/modules/dx_hw/dx_video.c index 9b68a1b..68a78f5 100644 --- a/modules/dx_hw/dx_video.c +++ b/modules/dx_hw/dx_video.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / DirectX audio and video render module @@ -87,77 +87,9 @@ typedef Bool (APIENTRY *GETPIXELFORMATATTRIBIV)(HDC hdc, int iPixelFormat, int i static GETPIXELFORMATATTRIBIV wglGetPixelFormatAttribivARB = NULL; -typedef void *(APIENTRY *CREATEPBUFFERARB)(HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); -static CREATEPBUFFERARB wglCreatePbufferARB = NULL; - -typedef void (APIENTRY *DESTROYBUFFERARB)(void *pb); -static DESTROYBUFFERARB wglDestroyPbufferARB = NULL; - -typedef HDC (APIENTRY *GETPBUFFERDCARB)(void *pb); -static GETPBUFFERDCARB wglGetPbufferDCARB = NULL; - -typedef HDC (APIENTRY *RELEASEPBUFFERDCARB)(void *pb, HDC dc); -static RELEASEPBUFFERDCARB wglReleasePbufferDCARB = NULL; - typedef BOOL (APIENTRY *PFNWGLSWAPINTERVALFARPROC)( int ); PFNWGLSWAPINTERVALFARPROC wglSwapIntervalEXT = NULL; -static void dd_init_gl_offscreen(GF_VideoOutput *driv) -{ - const char *opt; - DDContext *dd = (DDContext*)driv->opaque; - - opt = gf_modules_get_option((GF_BaseInterface *)driv, "Video", "GLOffscreenMode"); - -#ifndef _WIN32_WCE - wglChoosePixelFormatARB = (CHOOSEPFFORMATARB) wglGetProcAddress("wglChoosePixelFormatARB"); - - wglCreatePbufferARB = (CREATEPBUFFERARB) wglGetProcAddress("wglCreatePbufferARB"); - if (opt && strcmp(opt, "PBuffer")) wglCreatePbufferARB = NULL; - - if (wglCreatePbufferARB) { - wglDestroyPbufferARB = (DESTROYBUFFERARB) wglGetProcAddress("wglDestroyPbufferARB"); - wglGetPbufferDCARB = (GETPBUFFERDCARB ) wglGetProcAddress("wglGetPbufferDCARB"); - wglReleasePbufferDCARB = (RELEASEPBUFFERDCARB ) wglGetProcAddress("wglReleasePbufferDCARB"); - - GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DX] Using PBuffer for OpenGL Offscreen Rendering\n")); - driv->hw_caps |= GF_VIDEO_HW_OPENGL_OFFSCREEN | GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA; - - if (!opt) gf_modules_set_option((GF_BaseInterface *)driv, "Video", "GLOffscreenMode", "PBuffer"); - } else -#endif - { - u32 gl_type = 1; - - opt = gf_modules_get_option((GF_BaseInterface *)driv, "Video", "GLOffscreenMode"); - if (opt) { - if (!strcmp(opt, "Window")) gl_type = 1; - else if (!strcmp(opt, "VisibleWindow")) gl_type = 2; - else gl_type = 0; - } else { - gf_modules_set_option((GF_BaseInterface *)driv, "Video", "GLOffscreenMode", "Window"); - } - - if (gl_type) { -#ifdef _WIN32_WCE - dd->gl_hwnd = CreateWindow(_T("GPAC DirectDraw Output"), _T("GPAC OpenGL Offscreen"), WS_POPUP, 0, 0, 120, 100, NULL, NULL, GetModuleHandle(_T("gm_dx_hw.dll")), NULL); -#else -#ifdef UNICODE - dd->gl_hwnd = CreateWindow(L"GPAC DirectDraw Output", L"GPAC OpenGL Offscreen", WS_POPUP, 0, 0, 120, 100, NULL, NULL, GetModuleHandle(L"gm_dx_hw.dll"), NULL); -#else - dd->gl_hwnd = CreateWindow("GPAC DirectDraw Output", "GPAC OpenGL Offscreen", WS_POPUP, 0, 0, 120, 100, NULL, NULL, GetModuleHandle("gm_dx_hw.dll"), NULL); -#endif -#endif - if (!dd->gl_hwnd) - return; - - ShowWindow(dd->gl_hwnd, (gl_type == 2) ? SW_SHOW : SW_HIDE); - GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[DX] Using %s window for OpenGL Offscreen Rendering\n", (gl_type == 2) ? "Visible" : "Hidden")); - driv->hw_caps |= GF_VIDEO_HW_OPENGL_OFFSCREEN | GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA; - } - } - -} #endif @@ -167,7 +99,7 @@ static void RestoreWindow(DDContext *dd) dd->NeedRestore = GF_FALSE; #ifndef GPAC_DISABLE_3D - if (dd->output_3d_type==1) { + if (dd->output_3d) { #ifndef _WIN32_WCE ChangeDisplaySettings(NULL,0); #endif @@ -200,9 +132,6 @@ void DestroyObjectsEx(DDContext *dd, Bool only_3d) #ifdef GPAC_DISABLE_3D } #else - - /*do not destroy associated GL context*/ - if (dd->output_3d_type==2) return; } /*delete openGL context*/ @@ -229,10 +158,6 @@ void DestroyObjectsEx(DDContext *dd, Bool only_3d) ReleaseDC(dd->bound_hwnd, dd->pb_HDC); dd->pb_HDC = NULL; } - if (dd->pbuffer) { - wglDestroyPbufferARB(dd->pbuffer); - dd->pbuffer = NULL; - } if (dd->gl_HRC) { //wglMakeCurrent(NULL, NULL); @@ -258,7 +183,9 @@ void DestroyObjects(DDContext *dd) GF_Err DD_SetupOpenGL(GF_VideoOutput *dr, u32 offscreen_width, u32 offscreen_height) { +#if defined(GPAC_USE_GLES1X) || defined(GPAC_USE_GLES2) || !defined(_WIN32_WCE) const char *sOpt; +#endif GF_Event evt; Bool hw_reset = GF_FALSE; DDCONTEXT @@ -271,7 +198,7 @@ GF_Err DD_SetupOpenGL(GF_VideoOutput *dr, u32 offscreen_width, u32 offscreen_hei u32 i=0; static int egl_atts[20]; - sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "GLNbBitsPerComponent"); + sOpt = gf_opts_get_key("core", "gl-bits-comp"); nb_bits = sOpt ? atoi(sOpt) : 5; egl_atts[i++] = EGL_RED_SIZE; @@ -283,7 +210,7 @@ GF_Err DD_SetupOpenGL(GF_VideoOutput *dr, u32 offscreen_width, u32 offscreen_hei /*alpha for compositeTexture*/ egl_atts[i++] = EGL_ALPHA_SIZE; egl_atts[i++] = 1; - sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "GLNbBitsDepth"); + sOpt = gf_opts_get_key("core", "gl-bits-depth"); nb_bits = sOpt ? atoi(sOpt) : 5; egl_atts[i++] = EGL_DEPTH_SIZE; egl_atts[i++] = nb_bits; @@ -335,13 +262,13 @@ GF_Err DD_SetupOpenGL(GF_VideoOutput *dr, u32 offscreen_width, u32 offscreen_hei PIXELFORMATDESCRIPTOR pfd; s32 pixelformat; HWND highbpp_hwnd = NULL; - HWND target_hwnd = NULL; + HWND target_hwnd; int bits_depth = 16; u32 i; Bool use_double_buffer; /*already setup*/ - target_hwnd = (dd->gl_hwnd && (dd->output_3d_type==2)) ? dd->gl_hwnd : dd->cur_hwnd; + target_hwnd = dd->cur_hwnd; if ((dd->bound_hwnd == target_hwnd) && dd->gl_HRC) goto exit; @@ -349,16 +276,16 @@ GF_Err DD_SetupOpenGL(GF_VideoOutput *dr, u32 offscreen_width, u32 offscreen_hei dd->bound_hwnd = target_hwnd; /*cleanup*/ - DestroyObjectsEx(dd, (dd->output_3d_type==1) ? GF_FALSE : GF_TRUE); + DestroyObjectsEx(dd, dd->output_3d ? GF_FALSE : GF_TRUE); //first time we init GL: create a dummy window to select pixel format for high bpp - we must do this because //- we must get a valid GL context to query the extensions for bpp > 8 (regular choosePixelFormat does not work for them) //- we must call SetPixelFormat to create the GL context //- it is not possible to call several time SetPixelFormat on the same window with different PF properties ... if (!dd->mode_high_bpp) { - sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "GLNbBitsPerComponent"); + sOpt = gf_opts_get_key("core", "gl-bits-comp"); if (!sOpt) { - gf_modules_set_option((GF_BaseInterface *)dr, "Video", "GLNbBitsPerComponent", "8"); + gf_opts_set_key("core", "gl-bits-comp", "8"); dd->bpp = 8; } else { dd->bpp = atoi(sOpt); @@ -384,6 +311,9 @@ GF_Err DD_SetupOpenGL(GF_VideoOutput *dr, u32 offscreen_width, u32 offscreen_hei dd->gl_HRC = wglCreateContext(dd->gl_HDC); if (!dd->gl_HRC) return GF_IO_ERR; + HGLRC cur = wglGetCurrentContext(); + if (cur) wglShareLists(cur, dd->gl_HRC); + wglMakeCurrent(dd->gl_HDC, dd->gl_HRC); wglChoosePixelFormatARB = (CHOOSEPFFORMATARB) wglGetProcAddress("wglChoosePixelFormatARB"); wglGetPixelFormatAttribivARB = (GETPIXELFORMATATTRIBIV) wglGetProcAddress("wglGetPixelFormatAttribivARB"); @@ -408,11 +338,11 @@ GF_Err DD_SetupOpenGL(GF_VideoOutput *dr, u32 offscreen_width, u32 offscreen_hei if (dd->gl_double_buffer ) { use_double_buffer = dd->gl_double_buffer; } else { - sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "UseGLDoubleBuffering"); + sOpt = gf_opts_get_key("core", "gl-doublebuf"); if (!sOpt || !strcmp(sOpt, "yes")) use_double_buffer = GF_TRUE; } - sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "GLNbBitsDepth"); + sOpt = gf_opts_get_key("core", "gl-bits-depth"); if (sOpt) bits_depth = atoi(sOpt); memset(&pfd, 0, sizeof(pfd)); @@ -423,7 +353,6 @@ GF_Err DD_SetupOpenGL(GF_VideoOutput *dr, u32 offscreen_width, u32 offscreen_hei int pformats[200]; u32 nbformats=0; Bool found = GF_FALSE; - float fattribs[1] = { 0.0f }; int hdcAttributes[] = { WGL_SUPPORT_OPENGL_ARB, TRUE, @@ -498,7 +427,6 @@ GF_Err DD_SetupOpenGL(GF_VideoOutput *dr, u32 offscreen_width, u32 offscreen_hei if (!dd->glext_init) { dd->glext_init = GF_TRUE; wglMakeCurrent(dd->gl_HDC, dd->gl_HRC); - dd_init_gl_offscreen(dr); } if (dd->disable_vsync) { @@ -510,39 +438,8 @@ GF_Err DD_SetupOpenGL(GF_VideoOutput *dr, u32 offscreen_width, u32 offscreen_hei } } - if ((dd->output_3d_type!=2) || dd->gl_hwnd) { - if (!wglMakeCurrent(dd->gl_HDC, dd->gl_HRC)) return GF_IO_ERR; - } else if (wglCreatePbufferARB) { - const int pbufferAttributes [50] = { - WGL_TEXTURE_FORMAT_ARB, WGL_TEXTURE_RGBA_ARB, - WGL_TEXTURE_TARGET_ARB, WGL_TEXTURE_2D_ARB, - 0 - }; - int pformats[20]; - u32 nbformats=0; - int hdcAttributes[] = { - WGL_SUPPORT_OPENGL_ARB, TRUE, - WGL_DRAW_TO_PBUFFER_ARB, TRUE, - WGL_RED_BITS_ARB, 8, - WGL_GREEN_BITS_ARB, 8, - WGL_BLUE_BITS_ARB, 8, - WGL_DEPTH_BITS_ARB, bits_depth, - 0 - }; - wglChoosePixelFormatARB(dd->gl_HDC, hdcAttributes, NULL, 20, pformats, &nbformats); - // Create the PBuffer - for (i=0; i<nbformats; i++) { - dd->pbuffer = wglCreatePbufferARB(dd->gl_HDC, pformats[i], offscreen_width, offscreen_height, pbufferAttributes); - if (dd->pbuffer) break; - } - if (!dd->pbuffer) return GF_IO_ERR; + if (!wglMakeCurrent(dd->gl_HDC, dd->gl_HRC)) return GF_IO_ERR; - dd->pb_HDC = wglGetPbufferDCARB(dd->pbuffer); - dd->pb_HRC = wglCreateContext(dd->pb_HDC); - if (!wglMakeCurrent(dd->pb_HDC, dd->pb_HRC)) return GF_IO_ERR; - - - } #endif /*special care for Firefox: XUL and OpenGL do not go well together, there is a stack overflow in WM_PAINT @@ -554,11 +451,11 @@ GF_Err DD_SetupOpenGL(GF_VideoOutput *dr, u32 offscreen_width, u32 offscreen_hei SetWindowLong(dd->os_hwnd, GWL_WNDPROC, (DWORD) DD_WindowProc); #endif -#if !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_GLES2) +#if !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_GLES2) && !defined(_WIN32_WCE) exit: #endif - if (dd->output_3d_type==1) { + if (dd->output_3d) { memset(&evt, 0, sizeof(GF_Event)); evt.type = GF_EVENT_VIDEO_SETUP; evt.setup.hw_reset = hw_reset; @@ -575,9 +472,14 @@ GF_Err DD_Setup(GF_VideoOutput *dr, void *os_handle, void *os_display, u32 init_ { RECT rc; DDCONTEXT - const char *opt; - dd->os_hwnd = (HWND) os_handle; + if (dd->cur_hwnd) { + if (!(init_flags & GF_TERM_INIT_HIDE)) { + ShowWindow(dd->cur_hwnd, SW_SHOW); + } + return GF_OK; + } + dd->os_hwnd = (HWND) os_handle; DD_SetupWindow(dr, init_flags); /*fatal error*/ if (!dd->os_hwnd) return GF_IO_ERR; @@ -592,12 +494,11 @@ GF_Err DD_Setup(GF_VideoOutput *dr, void *os_handle, void *os_display, u32 init_ } #ifndef GPAC_DISABLE_3D - dd->output_3d_type = 0; + dd->output_3d = 0; #endif GetWindowRect(dd->cur_hwnd, &rc); - opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "DisableVSync"); - if (opt && !strcmp(opt, "yes")) dd->disable_vsync = GF_TRUE; + dd->disable_vsync = gf_opts_get_bool("core", "disable-vsync"); return GF_OK; } @@ -607,9 +508,6 @@ static void DD_Shutdown(GF_VideoOutput *dr) DDCONTEXT /*force destroy of opengl*/ -#ifndef GPAC_DISABLE_3D - if (dd->output_3d_type) dd->output_3d_type = 1; -#endif DestroyObjects(dd); DD_ShutdownWindow(dr); @@ -638,8 +536,6 @@ void DD_ShowTaskbar(Bool show) static GF_Err DD_SetFullScreen(GF_VideoOutput *dr, Bool bOn, u32 *outWidth, u32 *outHeight) { GF_Err e; - const char *sOpt; - u32 MaxWidth, MaxHeight; DDCONTEXT; if (bOn == dd->fullscreen) return GF_OK; @@ -653,16 +549,11 @@ static GF_Err DD_SetFullScreen(GF_VideoOutput *dr, Bool bOn, u32 *outWidth, u32 on the dest pixel format)*/ dd->yuv_init = GF_FALSE; if (dd->fullscreen) { - const char *sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "SwitchResolution"); - if (sOpt && !stricmp(sOpt, "yes")) dd->switch_res = GF_TRUE; + dd->switch_res = gf_opts_get_bool("core", "switch-vres"); /*get current or best fitting mode*/ if (GetDisplayMode(dd) != GF_OK) return GF_IO_ERR; } - MaxWidth = MaxHeight = 0; - sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "MaxResolution"); - if (sOpt) sscanf(sOpt, "%dx%d", &MaxWidth, &MaxHeight); - if (dd->NeedRestore) RestoreWindow(dd); /*destroy all objects*/ if (dd->os_hwnd!=dd->fs_hwnd) DestroyObjects(dd); @@ -711,11 +602,6 @@ static GF_Err DD_SetFullScreen(GF_VideoOutput *dr, Bool bOn, u32 *outWidth, u32 } #endif - /*change display mode*/ - if ((MaxWidth && (dd->fs_width >= MaxWidth)) || (MaxHeight && (dd->fs_height >= MaxHeight)) ) { - dd->fs_width = MaxWidth; - dd->fs_height = MaxHeight; - } SetWindowPos(dd->cur_hwnd, NULL, X, Y, dd->fs_width, dd->fs_height, SWP_SHOWWINDOW | SWP_NOZORDER /*| SWP_ASYNCWINDOWPOS*/); } else if (dd->os_hwnd==dd->fs_hwnd) { SetWindowPos(dd->os_hwnd, NULL, 0, 0, dd->store_width+dd->off_w, dd->store_height+dd->off_h, SWP_SHOWWINDOW | SWP_NOZORDER /*| SWP_ASYNCWINDOWPOS*/); @@ -726,7 +612,7 @@ static GF_Err DD_SetFullScreen(GF_VideoOutput *dr, Bool bOn, u32 *outWidth, u32 #ifndef GPAC_DISABLE_3D - if (dd->output_3d_type==1) { + if (dd->output_3d) { e = DD_SetupOpenGL(dr, 0, 0); } else #endif @@ -773,7 +659,7 @@ GF_Err DD_Flush(GF_VideoOutput *dr, GF_Window *dest) #ifndef GPAC_DISABLE_3D - if (dd->output_3d_type==1) { + if (dd->output_3d) { #if defined(GPAC_USE_GLES1X) || defined(GPAC_USE_GLES2) if (dd->surface) eglSwapBuffers(dd->egldpy, dd->surface); #else @@ -800,7 +686,7 @@ GF_Err DD_Flush(GF_VideoOutput *dr, GF_Window *dest) } if (!dd->disable_vsync) - hr = dd->pDD->lpVtbl->WaitForVerticalBlank(dd->pDD, DDWAITVB_BLOCKBEGIN, NULL); + dd->pDD->lpVtbl->WaitForVerticalBlank(dd->pDD, DDWAITVB_BLOCKBEGIN, NULL); if (dest) { POINT pt; @@ -898,7 +784,7 @@ static void *NewDXVideoOutput() driv->max_screen_width = GetSystemMetrics(SM_CXSCREEN); driv->max_screen_height = GetSystemMetrics(SM_CYSCREEN); driv->max_screen_bpp = 8; - driv->hw_caps = GF_VIDEO_HW_OPENGL | GF_VIDEO_HW_OPENGL_OFFSCREEN | GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA | GF_VIDEO_HW_HAS_HWND_HDC; + driv->hw_caps = GF_VIDEO_HW_OPENGL | GF_VIDEO_HW_OPENGL_OFFSCREEN | GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA; DD_SetupDDraw(driv); diff --git a/modules/dx_hw/dx_window.c b/modules/dx_hw/dx_window.c index 9d378da..81f8e35 100644 --- a/modules/dx_hw/dx_window.c +++ b/modules/dx_hw/dx_window.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / DirectX audio and video render module @@ -59,374 +59,141 @@ static void DD_GetCoordinates(LPARAM lParam, GF_Event *evt) } #endif -static void w32_translate_key(WPARAM wParam, LPARAM lParam, GF_EventKey *evt) +typedef struct { - evt->flags = 0; - evt->hw_code = (u32) wParam; - switch (wParam) { - case VK_BACK: - evt->key_code = GF_KEY_BACKSPACE; - break; - case VK_TAB: - evt->key_code = GF_KEY_TAB; - break; - case VK_CLEAR: - evt->key_code = GF_KEY_CLEAR; - break; - case VK_RETURN: - evt->key_code = GF_KEY_ENTER; - break; - case VK_SHIFT: - evt->key_code = GF_KEY_SHIFT; - break; - case VK_CONTROL: - evt->key_code = GF_KEY_CONTROL; - break; - case VK_MENU: - evt->key_code = GF_KEY_ALT; - break; - case VK_PAUSE: - evt->key_code = GF_KEY_PAUSE; - break; - case VK_CAPITAL: - evt->key_code = GF_KEY_CAPSLOCK; - break; - case VK_KANA: - evt->key_code = GF_KEY_KANAMODE; - break; - case VK_JUNJA: - evt->key_code = GF_KEY_JUNJAMODE; - break; - case VK_FINAL: - evt->key_code = GF_KEY_FINALMODE; - break; - case VK_KANJI: - evt->key_code = GF_KEY_KANJIMODE; - break; - case VK_ESCAPE: - evt->key_code = GF_KEY_ESCAPE; - break; - case VK_CONVERT: - evt->key_code = GF_KEY_CONVERT; - break; - case VK_SPACE: - evt->key_code = GF_KEY_SPACE; - break; - case VK_PRIOR: - evt->key_code = GF_KEY_PAGEUP; - break; - case VK_NEXT: - evt->key_code = GF_KEY_PAGEDOWN; - break; - case VK_END: - evt->key_code = GF_KEY_END; - break; - case VK_HOME: - evt->key_code = GF_KEY_HOME; - break; - case VK_LEFT: - evt->key_code = GF_KEY_LEFT; - break; - case VK_UP: - evt->key_code = GF_KEY_UP; - break; - case VK_RIGHT: - evt->key_code = GF_KEY_RIGHT; - break; - case VK_DOWN: - evt->key_code = GF_KEY_DOWN; - break; - case VK_SELECT: - evt->key_code = GF_KEY_SELECT; - break; - case VK_PRINT: - case VK_SNAPSHOT: - evt->key_code = GF_KEY_PRINTSCREEN; - break; - case VK_EXECUTE: - evt->key_code = GF_KEY_EXECUTE; - break; - case VK_INSERT: - evt->key_code = GF_KEY_INSERT; - break; - case VK_DELETE: - evt->key_code = GF_KEY_DEL; - break; - case VK_HELP: - evt->key_code = GF_KEY_HELP; - break; + u32 win_key; + u32 gf_key; + u32 gf_flags; +} WINKeyToGPAC; +static WINKeyToGPAC WINKeys[] = +{ + {VK_BACK, GF_KEY_BACKSPACE, 0}, + {VK_TAB, GF_KEY_TAB, 0 }, + {VK_CLEAR, GF_KEY_CLEAR, 0 }, + {VK_RETURN, GF_KEY_ENTER, 0 }, + {VK_SHIFT, GF_KEY_SHIFT, 0 }, + {VK_CONTROL, GF_KEY_CONTROL, 0 }, + {VK_MENU, GF_KEY_ALT, 0 }, + {VK_PAUSE, GF_KEY_PAUSE, 0 }, + {VK_CAPITAL, GF_KEY_CAPSLOCK, 0 }, + {VK_KANA, GF_KEY_KANAMODE, 0 }, + {VK_JUNJA, GF_KEY_JUNJAMODE, 0 }, + {VK_FINAL, GF_KEY_FINALMODE, 0 }, + {VK_KANJI, GF_KEY_KANJIMODE, 0 }, + {VK_ESCAPE, GF_KEY_ESCAPE, 0 }, + {VK_CONVERT, GF_KEY_CONVERT, 0 }, + {VK_SPACE, GF_KEY_SPACE, 0 }, + {VK_PRIOR, GF_KEY_PAGEUP, 0 }, + {VK_NEXT, GF_KEY_PAGEDOWN, 0 }, + {VK_END, GF_KEY_END, 0 }, + {VK_HOME, GF_KEY_HOME, 0 }, + {VK_LEFT, GF_KEY_LEFT, 0 }, + {VK_UP, GF_KEY_UP, 0 }, + {VK_RIGHT, GF_KEY_RIGHT, 0 }, + {VK_DOWN, GF_KEY_DOWN, 0 }, + {VK_SELECT, GF_KEY_SELECT, 0 }, + {VK_PRINT, GF_KEY_PRINTSCREEN, 0 }, + { VK_SNAPSHOT, GF_KEY_PRINTSCREEN, 0 }, + {VK_EXECUTE, GF_KEY_EXECUTE, 0 }, + {VK_INSERT, GF_KEY_INSERT, 0 }, + {VK_DELETE, GF_KEY_DEL, 0 }, + {VK_HELP, GF_KEY_HELP, 0 }, #if !defined(_WIN32_WCE) && !defined(__GNUC__) - case VK_OEM_PLUS: - evt->key_code = GF_KEY_PLUS; - break; - case VK_OEM_MINUS: - evt->key_code = GF_KEY_PLUS; - break; + {VK_OEM_PLUS, GF_KEY_PLUS, 0 }, + {VK_OEM_MINUS, GF_KEY_PLUS, 0 }, #endif - #ifndef _WIN32_WCE - case VK_NONCONVERT: - evt->key_code = GF_KEY_NONCONVERT; - break; - case VK_ACCEPT: - evt->key_code = GF_KEY_ACCEPT; - break; - case VK_MODECHANGE: - evt->key_code = GF_KEY_MODECHANGE; - break; + {VK_NONCONVERT, GF_KEY_NONCONVERT, 0 }, + {VK_ACCEPT, GF_KEY_ACCEPT, 0 }, + {VK_MODECHANGE, GF_KEY_MODECHANGE, 0 }, #endif - - /* case '!': evt->key_code = GF_KEY_EXCLAMATION; break; - case '"': evt->key_code = GF_KEY_QUOTATION; break; - case '#': evt->key_code = GF_KEY_NUMBER; break; - case '$': evt->key_code = GF_KEY_DOLLAR; break; - case '&': evt->key_code = GF_KEY_AMPERSAND; break; - case '\'': evt->key_code = GF_KEY_APOSTROPHE; break; - case '(': evt->key_code = GF_KEY_LEFTPARENTHESIS; break; - case ')': evt->key_code = GF_KEY_RIGHTPARENTHESIS; break; - case ',': evt->key_code = GF_KEY_COMMA; break; - case ':': evt->key_code = GF_KEY_COLON; break; - case ';': evt->key_code = GF_KEY_SEMICOLON; break; - case '<': evt->key_code = GF_KEY_LESSTHAN; break; - case '>': evt->key_code = GF_KEY_GREATERTHAN; break; - case '?': evt->key_code = GF_KEY_QUESTION; break; - case '@': evt->key_code = GF_KEY_AT; break; - case '[': evt->key_code = GF_KEY_LEFTSQUAREBRACKET; break; - case ']': evt->key_code = GF_KEY_RIGHTSQUAREBRACKET; break; - case '\\': evt->key_code = GF_KEY_BACKSLASH; break; - case '_': evt->key_code = GF_KEY_UNDERSCORE; break; - case '`': evt->key_code = GF_KEY_GRAVEACCENT; break; - case ' ': evt->key_code = GF_KEY_SPACE; break; - case '/': evt->key_code = GF_KEY_SLASH; break; - case '*': evt->key_code = GF_KEY_STAR; break; - case '-': evt->key_code = GF_KEY_HIPHEN; break; - case '+': evt->key_code = GF_KEY_PLUS; break; - case '=': evt->key_code = GF_KEY_EQUALS; break; - case '^': evt->key_code = GF_KEY_CIRCUM; break; - case '{': evt->key_code = GF_KEY_LEFTCURLYBRACKET; break; - case '}': evt->key_code = GF_KEY_RIGHTCURLYBRACKET; break; - case '|': evt->key_code = GF_KEY_PIPE; break; - */ - - - /* case VK_LWIN: return ; - case VK_RWIN: return ; - case VK_APPS: return ; - */ - case VK_NUMPAD0: - evt->key_code = GF_KEY_0; - evt->flags = GF_KEY_EXT_NUMPAD; - break; - case VK_NUMPAD1: - evt->key_code = GF_KEY_1; - evt->flags = GF_KEY_EXT_NUMPAD; - break; - case VK_NUMPAD2: - evt->key_code = GF_KEY_2; - evt->flags = GF_KEY_EXT_NUMPAD; - break; - case VK_NUMPAD3: - evt->key_code = GF_KEY_3; - evt->flags = GF_KEY_EXT_NUMPAD; - break; - case VK_NUMPAD4: - evt->key_code = GF_KEY_4; - evt->flags = GF_KEY_EXT_NUMPAD; - break; - case VK_NUMPAD5: - evt->key_code = GF_KEY_5; - evt->flags = GF_KEY_EXT_NUMPAD; - break; - case VK_NUMPAD6: - evt->key_code = GF_KEY_6; - evt->flags = GF_KEY_EXT_NUMPAD; - break; - case VK_NUMPAD7: - evt->key_code = GF_KEY_7; - evt->flags = GF_KEY_EXT_NUMPAD; - break; - case VK_NUMPAD8: - evt->key_code = GF_KEY_8; - evt->flags = GF_KEY_EXT_NUMPAD; - break; - case VK_NUMPAD9: - evt->key_code = GF_KEY_9; - evt->flags = GF_KEY_EXT_NUMPAD; - break; - case VK_MULTIPLY: - evt->key_code = GF_KEY_STAR; - evt->flags = GF_KEY_EXT_NUMPAD; - break; - case VK_ADD: - evt->key_code = GF_KEY_PLUS; - evt->flags = GF_KEY_EXT_NUMPAD; - break; - case VK_SEPARATOR: - evt->key_code = GF_KEY_FULLSTOP; - evt->flags = GF_KEY_EXT_NUMPAD; - break; - case VK_SUBTRACT: - evt->key_code = GF_KEY_HYPHEN; - evt->flags = GF_KEY_EXT_NUMPAD; - break; - case VK_DECIMAL: - evt->key_code = GF_KEY_COMMA; - evt->flags = GF_KEY_EXT_NUMPAD; - break; - case VK_DIVIDE: - evt->key_code = GF_KEY_SLASH; - evt->flags = GF_KEY_EXT_NUMPAD; - break; - case VK_F1: - evt->key_code = GF_KEY_F1; - break; - case VK_F2: - evt->key_code = GF_KEY_F2; - break; - case VK_F3: - evt->key_code = GF_KEY_F3; - break; - case VK_F4: - evt->key_code = GF_KEY_F4; - break; - case VK_F5: - evt->key_code = GF_KEY_F5; - break; - case VK_F6: - evt->key_code = GF_KEY_F6; - break; - case VK_F7: - evt->key_code = GF_KEY_F7; - break; - case VK_F8: - evt->key_code = GF_KEY_F8; - break; - case VK_F9: - evt->key_code = GF_KEY_F9; - break; - case VK_F10: - evt->key_code = GF_KEY_F10; - break; - case VK_F11: - evt->key_code = GF_KEY_F11; - break; - case VK_F12: - evt->key_code = GF_KEY_F12; - break; - case VK_F13: - evt->key_code = GF_KEY_F13; - break; - case VK_F14: - evt->key_code = GF_KEY_F14; - break; - case VK_F15: - evt->key_code = GF_KEY_F15; - break; - case VK_F16: - evt->key_code = GF_KEY_F16; - break; - case VK_F17: - evt->key_code = GF_KEY_F17; - break; - case VK_F18: - evt->key_code = GF_KEY_F18; - break; - case VK_F19: - evt->key_code = GF_KEY_F19; - break; - case VK_F20: - evt->key_code = GF_KEY_F20; - break; - case VK_F21: - evt->key_code = GF_KEY_F21; - break; - case VK_F22: - evt->key_code = GF_KEY_F22; - break; - case VK_F23: - evt->key_code = GF_KEY_F23; - break; - case VK_F24: - evt->key_code = GF_KEY_F24; - break; - - case VK_NUMLOCK: - evt->key_code = GF_KEY_NUMLOCK; - break; - case VK_SCROLL: - evt->key_code = GF_KEY_SCROLL; - break; - - /* - * VK_L* & VK_R* - left and right Alt, Ctrl and Shift virtual keys. - * Used only as parameters to GetAsyncKeyState() and GetKeyState(). - * No other API or message will distinguish left and right keys in this way. - */ - case VK_LSHIFT: - evt->key_code = GF_KEY_SHIFT; - evt->flags = GF_KEY_EXT_LEFT; - break; - case VK_RSHIFT: - evt->key_code = GF_KEY_SHIFT; - evt->flags = GF_KEY_EXT_RIGHT; - break; - case VK_LCONTROL: - evt->key_code = GF_KEY_CONTROL; - evt->flags = GF_KEY_EXT_LEFT; - break; - case VK_RCONTROL: - evt->key_code = GF_KEY_CONTROL; - evt->flags = GF_KEY_EXT_RIGHT; - break; - case VK_LMENU: - evt->key_code = GF_KEY_ALT; - evt->flags = GF_KEY_EXT_LEFT; - break; - case VK_RMENU: - evt->key_code = GF_KEY_ALT; - evt->flags = GF_KEY_EXT_RIGHT; - break; - + {VK_NUMPAD0, GF_KEY_0, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD1, GF_KEY_1, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD2, GF_KEY_2, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD3, GF_KEY_3, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD4, GF_KEY_4, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD5, GF_KEY_5, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD6, GF_KEY_6, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD7, GF_KEY_7, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD8, GF_KEY_8, GF_KEY_EXT_NUMPAD }, + {VK_NUMPAD9, GF_KEY_9, GF_KEY_EXT_NUMPAD }, + {VK_MULTIPLY, GF_KEY_STAR, GF_KEY_EXT_NUMPAD }, + {VK_ADD, GF_KEY_PLUS, GF_KEY_EXT_NUMPAD }, + {VK_SEPARATOR, GF_KEY_FULLSTOP, GF_KEY_EXT_NUMPAD }, + {VK_SUBTRACT, GF_KEY_HYPHEN, GF_KEY_EXT_NUMPAD }, + {VK_DECIMAL, GF_KEY_COMMA, GF_KEY_EXT_NUMPAD }, + {VK_DIVIDE, GF_KEY_SLASH, GF_KEY_EXT_NUMPAD }, + {VK_F1, GF_KEY_F1, 0}, + {VK_F2, GF_KEY_F2, 0 }, + {VK_F3, GF_KEY_F3, 0 }, + {VK_F4, GF_KEY_F4, 0 }, + {VK_F5, GF_KEY_F5, 0 }, + {VK_F6, GF_KEY_F6, 0 }, + {VK_F7, GF_KEY_F7, 0 }, + {VK_F8, GF_KEY_F8, 0 }, + {VK_F9, GF_KEY_F9, 0 }, + {VK_F10, GF_KEY_F10, 0 }, + {VK_F11, GF_KEY_F11, 0 }, + {VK_F12, GF_KEY_F12, 0 }, + {VK_F13, GF_KEY_F13, 0 }, + {VK_F14, GF_KEY_F14, 0 }, + {VK_F15, GF_KEY_F15, 0 }, + {VK_F16, GF_KEY_F16, 0 }, + {VK_F17, GF_KEY_F17, 0 }, + {VK_F18, GF_KEY_F18, 0 }, + {VK_F19, GF_KEY_F19, 0 }, + {VK_F20, GF_KEY_F20, 0 }, + {VK_F21, GF_KEY_F21, 0 }, + {VK_F22, GF_KEY_F22, 0 }, + {VK_F23, GF_KEY_F23, 0 }, + {VK_F24, GF_KEY_F24, 0 }, + {VK_NUMLOCK, GF_KEY_NUMLOCK, 0 }, + {VK_SCROLL, GF_KEY_SCROLL, 0 }, + {VK_LSHIFT, GF_KEY_SHIFT, GF_KEY_EXT_LEFT}, + {VK_RSHIFT, GF_KEY_SHIFT, GF_KEY_EXT_RIGHT }, + {VK_LCONTROL, GF_KEY_CONTROL, GF_KEY_EXT_LEFT }, + {VK_RCONTROL, GF_KEY_CONTROL, GF_KEY_EXT_RIGHT }, + {VK_LMENU, GF_KEY_ALT, GF_KEY_EXT_LEFT }, + {VK_RMENU, GF_KEY_ALT, GF_KEY_EXT_RIGHT }, #if(WINVER >= 0x0400) - case VK_PROCESSKEY: - evt->key_code = GF_KEY_PROCESS; - break; + {VK_PROCESSKEY, GF_KEY_PROCESS, 0}, #endif /* WINVER >= 0x0400 */ + {VK_ATTN, GF_KEY_ATTN, 0 }, + {VK_CRSEL, GF_KEY_CRSEL, 0 }, + {VK_EXSEL, GF_KEY_EXSEL, 0 }, + {VK_EREOF, GF_KEY_ERASEEOF, 0 }, + {VK_PLAY, GF_KEY_PLAY, 0 }, + {VK_ZOOM, GF_KEY_ZOOM, 0 }, + //{VK_NONAME, GF_KEY_NONAME, 0}, + //{VK_PA1, GF_KEY_PA1, 0}, + {VK_OEM_CLEAR, GF_KEY_CLEAR, 0 }, +}; + +static u32 num_win_keys = sizeof(WINKeys) / sizeof(WINKeyToGPAC); - case VK_ATTN: - evt->key_code = GF_KEY_ATTN; - break; - case VK_CRSEL: - evt->key_code = GF_KEY_CRSEL; - break; - case VK_EXSEL: - evt->key_code = GF_KEY_EXSEL; - break; - case VK_EREOF: - evt->key_code = GF_KEY_ERASEEOF; - break; - case VK_PLAY: - evt->key_code = GF_KEY_PLAY; - break; - case VK_ZOOM: - evt->key_code = GF_KEY_ZOOM; - break; - //case VK_NONAME: evt->key_code = GF_KEY_NONAME; break; - //case VK_PA1: evt->key_code = GF_KEY_PA1; break; - case VK_OEM_CLEAR: - evt->key_code = GF_KEY_CLEAR; - break; +static void w32_translate_key(WPARAM wParam, LPARAM lParam, GF_EventKey *evt) +{ + u32 i; + evt->flags = 0; + evt->hw_code = (u32)wParam; - /*thru VK_9 are the same as ASCII '0' thru '9' (0x30 - 0x39) */ - /* VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (0x41 - 0x5A) */ - default: - if ((wParam>=0x30) && (wParam<=0x39)) evt->key_code = GF_KEY_0 + (u32) (wParam-0x30); - else if ((wParam>=0x41) && (wParam<=0x5A)) evt->key_code = GF_KEY_A + (u32) (wParam-0x41); - /*DOM 3 Events: Implementations that are unable to identify a key must use the key identifier "Unidentified".*/ - else - evt->key_code = GF_KEY_UNIDENTIFIED; - break; + for (i = 0; i<num_win_keys; i++) { + if (WINKeys[i].win_key == (u32) wParam) { + evt->key_code = WINKeys[i].gf_key; + evt->flags = WINKeys[i].gf_flags; + return; + } + } + + if ((wParam>=0x30) && (wParam<=0x39)) + evt->key_code = GF_KEY_0 + (u32) (wParam-0x30); + else if ((wParam>=0x41) && (wParam<=0x5A)) + evt->key_code = GF_KEY_A + (u32) (wParam-0x41); + /*DOM 3 Events: Implementations that are unable to identify a key must use the key identifier "Unidentified".*/ + else { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[DX] Unrecognized key %X\n", wParam)); + evt->key_code = GF_KEY_UNIDENTIFIED; } } @@ -476,14 +243,14 @@ LRESULT APIENTRY DD_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam case WM_SIZE: /*always notify GPAC since we're not sure the owner of the window is listening to these events*/ if (wParam==SIZE_MINIMIZED) { - evt.type = GF_EVENT_SHOWHIDE_NOTIF; + evt.type = GF_EVENT_SHOWHIDE; evt.show.show_type = 0; ctx->hidden = GF_TRUE; vout->on_event(vout->evt_cbk_hdl, &evt); } else { if (ctx->hidden && wParam==SIZE_RESTORED) { ctx->hidden = GF_FALSE; - evt.type = GF_EVENT_SHOWHIDE_NOTIF; + evt.type = GF_EVENT_SHOWHIDE; evt.show.show_type = 1; vout->on_event(vout->evt_cbk_hdl, &evt); } @@ -494,7 +261,7 @@ LRESULT APIENTRY DD_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam } break; case WM_MOVE: - evt.type = GF_EVENT_MOVE_NOTIF; + evt.type = GF_EVENT_MOVE; evt.move.x = LOWORD(lParam); evt.move.y = HIWORD(lParam); vout->on_event(vout->evt_cbk_hdl, &evt); @@ -530,11 +297,8 @@ LRESULT APIENTRY DD_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam case WM_ACTIVATE: if (!ctx->on_secondary_screen && ctx->fullscreen && (LOWORD(wParam)==WA_INACTIVE) && (hWnd==ctx->fs_hwnd) -#ifndef GPAC_DISABLE_3D - && (ctx->output_3d_type!=2) -#endif ) { - evt.type = GF_EVENT_SHOWHIDE_NOTIF; + evt.type = GF_EVENT_SHOWHIDE; vout->on_event(vout->evt_cbk_hdl, &evt); } /*fallthrough*/ @@ -576,7 +340,6 @@ LRESULT APIENTRY DD_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam case WM_DROPFILES: { char szFile[GF_MAX_PATH]; - GF_Event evt; u32 i; HDROP hDrop = (HDROP) wParam; @@ -752,11 +515,11 @@ LRESULT APIENTRY DD_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam LPTSTR lptstrCopy = (wchar_t *) GlobalLock(hglbCopy); evt.type = GF_EVENT_PASTE_TEXT; /* TODO: Convert to UTF-8 */ - evt.message.message = lptstrCopy; + evt.clipboard.text = lptstrCopy; #else LPTSTR lptstrCopy = (char *)GlobalLock(hglbCopy); evt.type = GF_EVENT_PASTE_TEXT; - evt.message.message = lptstrCopy; + evt.clipboard.text = lptstrCopy; #endif ret = vout->on_event(vout->evt_cbk_hdl, &evt); GlobalUnlock(hglbCopy); @@ -766,7 +529,7 @@ LRESULT APIENTRY DD_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam } else if (ctx->ctrl_down && (evt.type==GF_EVENT_KEYUP) && (evt.key.key_code==GF_KEY_C)) { evt.type = GF_EVENT_COPY_TEXT; - if ((vout->on_event(vout->evt_cbk_hdl, &evt)==GF_TRUE) && evt.message.message) { + if ((vout->on_event(vout->evt_cbk_hdl, &evt)==GF_TRUE) && evt.clipboard.text) { size_t len; HGLOBAL hglbCopy; LPTSTR lptstrCopy; @@ -774,23 +537,24 @@ LRESULT APIENTRY DD_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam if (!OpenClipboard(ctx->cur_hwnd)) break; EmptyClipboard(); - len = strlen(evt.message.message); + len = strlen(evt.clipboard.text); if (!len) break; hglbCopy = GlobalAlloc(GMEM_MOVEABLE, (len + 1) * sizeof(char)); #ifdef UNICODE /* TODO fix encoding*/ lptstrCopy = (wchar_t *)GlobalLock(hglbCopy); - memcpy(lptstrCopy, evt.message.message, len * sizeof(char)); + memcpy(lptstrCopy, evt.clipboard.text, len * sizeof(char)); lptstrCopy[len] = 0; #else lptstrCopy = (char *) GlobalLock(hglbCopy); - memcpy(lptstrCopy, evt.message.message, len * sizeof(char)); + memcpy(lptstrCopy, evt.clipboard.text, len * sizeof(char)); lptstrCopy[len] = 0; #endif GlobalUnlock(hglbCopy); SetClipboardData(CF_TEXT, hglbCopy); CloseClipboard(); + gf_free(evt.clipboard.text); break; } } @@ -840,6 +604,8 @@ static void SetWindowless(GF_VideoOutput *vout, HWND hWnd) GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[DX Out] Windowloess mode not supported on Window CE\n")); return; #else +/* use VerifyVersionInfo() instead of GetVersionEx? */ +#pragma warning(disable : 4996) const char *opt; u32 a, r, g, b; COLORREF ckey; @@ -868,9 +634,9 @@ static void SetWindowless(GF_VideoOutput *vout, HWND hWnd) SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_LAYERED); /*get background ckey*/ - opt = gf_modules_get_option((GF_BaseInterface *)vout, "Compositor", "ColorKey"); + opt = gf_opts_get_key("filter@compositor", "ckey"); if (!opt) { - gf_modules_set_option((GF_BaseInterface *)vout, "Compositor", "ColorKey", "FFFEFEFE"); + gf_opts_set_key("filter@compositor", "ckey", "FFFEFEFE"); opt = "FFFEFEFE"; } @@ -929,7 +695,9 @@ Bool DD_InitWindows(GF_VideoOutput *vout, DDContext *ctx) ctx->force_alpha = (flags & GF_TERM_WINDOW_TRANSPARENT) ? GF_TRUE : GF_FALSE; if (!ctx->os_hwnd) { +#ifndef _WIN32_WCE u32 styles; +#endif if (flags & GF_TERM_WINDOWLESS) ctx->windowless = GF_TRUE; @@ -1130,10 +898,6 @@ void DD_ShutdownWindow(GF_VideoOutput *dr) #endif } -#ifndef GPAC_DISABLE_3D - if (ctx->gl_hwnd) dd_closewindow(ctx->gl_hwnd); -#endif - if (ctx->th) { while (ctx->th_state!=2) gf_sleep(10); @@ -1161,9 +925,6 @@ void DD_ShutdownWindow(GF_VideoOutput *dr) #endif ctx->os_hwnd = NULL; ctx->fs_hwnd = NULL; -#ifndef GPAC_DISABLE_3D - ctx->gl_hwnd = NULL; -#endif the_video_output = NULL; } @@ -1296,6 +1057,17 @@ GF_Err DD_ProcessEvent(GF_VideoOutput*dr, GF_Event *evt) } } break; + case GF_EVENT_SET_GL: +#ifndef GPAC_DISABLE_3D + if (ctx->pb_HDC) { + if (!wglMakeCurrent(ctx->pb_HDC, ctx->pb_HRC)) + return GF_IO_ERR; + return GF_OK; + } + if (!wglMakeCurrent(ctx->gl_HDC, ctx->gl_HRC)) + return GF_IO_ERR; + return GF_OK; +#endif /*HW setup*/ case GF_EVENT_VIDEO_SETUP: if (ctx->dd_lost) { @@ -1303,32 +1075,23 @@ GF_Err DD_ProcessEvent(GF_VideoOutput*dr, GF_Event *evt) DestroyObjects(ctx); } ctx->is_setup = GF_TRUE; - switch (evt->setup.opengl_mode) { - case 0: + if (!evt->setup.use_opengl) { #ifndef GPAC_DISABLE_3D - ctx->output_3d_type = 0; + ctx->output_3d = GF_FALSE; #endif return DD_SetBackBufferSize(dr, evt->setup.width, evt->setup.height, evt->setup.system_memory); + } else { #ifndef GPAC_DISABLE_3D - case 1: - ctx->output_3d_type = 1; + ctx->output_3d = GF_TRUE; ctx->width = evt->setup.width; ctx->height = evt->setup.height; ctx->gl_double_buffer = evt->setup.back_buffer; return DD_SetupOpenGL(dr, 0, 0); - case 2: - ctx->output_3d_type = 2; - GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DX Out] Attempting to resize Offscreen OpenGL window to %d x %d\n", evt->size.width, evt->size.height)); - if (ctx->gl_hwnd) - SetWindowPos(ctx->gl_hwnd, NULL, 0, 0, evt->size.width, evt->size.height, SWP_NOZORDER | SWP_NOMOVE | SWP_ASYNCWINDOWPOS); - GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[DX Out] Resizing Offscreen OpenGL window to %d x %d\n", evt->size.width, evt->size.height)); - SetForegroundWindow(ctx->cur_hwnd); - ctx->gl_double_buffer = evt->setup.back_buffer; - return DD_SetupOpenGL(dr, evt->size.width, evt->size.height); #else return GF_NOT_SUPPORTED; #endif } + case GF_EVENT_SYS_COLORS: evt->sys_cols.sys_colors[0] = get_sys_col(COLOR_ACTIVEBORDER); evt->sys_cols.sys_colors[1] = get_sys_col(COLOR_ACTIVECAPTION); diff --git a/modules/mediacodec_dec/mediacodec_dec.h b/modules/filter_export.cpp similarity index 66% rename from modules/mediacodec_dec/mediacodec_dec.h rename to modules/filter_export.cpp index fa18781..262ad9d 100644 --- a/modules/mediacodec_dec/mediacodec_dec.h +++ b/modules/filter_export.cpp @@ -2,10 +2,10 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2018 * All rights reserved * - * This file is part of GPAC / codec pack module + * This file is part of GPAC * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -23,13 +23,14 @@ * */ -#ifndef _MEDIACODEC_DEC_H_ -#define _MEDIACODEC_DEC_H_ -#include <gpac/modules/codec.h> +/*this file is only used with Win32&MSVC to export the module interface symbols from each module DLL*/ +#include <gpac/setup.h> -GF_Err MCDec_CreateSurface (ANativeWindow ** window, u32 *gl_tex_id, Bool * surface_rendering); -GF_Err MCFrame_UpdateTexImage(); -GF_Err MCFrame_GetTransformMatrix(GF_CodecMatrix * mx); -GF_Err MCDec_DeleteSurface(); -#endif //_MEDIACODEC_DEC_H_ +#if defined(_WIN32_WCE) || defined(_WIN64) +#define EXPORT_SYMBOL(a) "/export:"#a +#else +#define EXPORT_SYMBOL(a) "/export:_"#a +#endif + +#pragma comment (linker, EXPORT_SYMBOL(RegisterFilter) ) diff --git a/modules/ft_font/Makefile b/modules/ft_font/Makefile index eda8da2..7800201 100644 --- a/modules/ft_font/Makefile +++ b/modules/ft_font/Makefile @@ -2,14 +2,14 @@ include ../../config.mak vpath %.c $(SRC_PATH)/modules/ft_font -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" $(FT_CFLAGS) +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) $(FT_CFLAGS) -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/modules/ft_font/ft_font.c b/modules/ft_font/ft_font.c index c546e8a..f59e5b4 100644 --- a/modules/ft_font/ft_font.c +++ b/modules/ft_font/ft_font.c @@ -170,7 +170,7 @@ static Bool ft_enum_fonts(void *cbck, char *file_name, char *file_path, GF_FileE if (bold) strcat(szfont, " Bold"); if (italic) strcat(szfont, " Italic"); } - gf_modules_set_option((GF_BaseInterface *)dr, "FontEngine", szfont, file_path); + gf_opts_set_key("FontCache", szfont, file_path); /*try to assign default fixed fonts*/ if (!bold && !italic) { @@ -207,24 +207,12 @@ static Bool ft_enum_fonts_dir(void *cbck, char *file_name, char *file_path, GF_F static void ft_rescan_fonts(GF_FontReader *dr) { u32 i, count; - GF_Config *cfg = gf_modules_get_config((GF_BaseInterface *)dr); FTBuilder *ftpriv = (FTBuilder *)dr->udta; GF_LOG(GF_LOG_INFO, GF_LOG_PARSER, ("[FreeType] Rescaning %d font directories\n", gf_list_count(ftpriv->font_dirs) )); - count = gf_cfg_get_key_count(cfg, "FontEngine"); - for (i=0; i<count; i++) { - const char *key = gf_cfg_get_key_name(cfg, "FontEngine", i); - if (!strcmp(key, "FontReader")) continue; - if (!strcmp(key, "FontDirectory")) continue; - if (!strcmp(key, "RescanFonts")) continue; - /*any other persistent options should go here*/ - - gf_cfg_set_key(cfg, "FontEngine", key, NULL); - count--; - i--; - } - gf_modules_set_option((GF_BaseInterface *)dr, "FontEngine", "RescanFonts", "no"); + gf_opts_del_section("FontCache"); + gf_opts_set_key("core", "rescan-fonts", "no"); if (ftpriv->font_fixed) gf_free(ftpriv->font_fixed); ftpriv->font_fixed = NULL; @@ -255,31 +243,30 @@ static void ft_rescan_fonts(GF_FontReader *dr) ftpriv->font_serif = NULL; /* let's check we have fonts that match our default Bold/Italic/BoldItalic conventions*/ - count = gf_cfg_get_key_count(cfg, "FontEngine"); + count = gf_opts_get_key_count("FontCache"); for (i=0; i<count; i++) { const char *opt; char fkey[GF_MAX_PATH]; - const char *key = gf_cfg_get_key_name(cfg, "FontEngine", i); - opt = gf_cfg_get_key(cfg, "FontEngine", key); + const char *key = gf_opts_get_key_name("FontCache", i); + opt = gf_opts_get_key("FontCache", key); if (!strchr(opt, '/') && !strchr(opt, '\\')) continue; - if (!strcmp(key, "FontDirectory")) continue; if (strstr(key, "Bold")) continue; if (strstr(key, "Italic")) continue; strcpy(fkey, key); strcat(fkey, " Italic"); - opt = gf_cfg_get_key(cfg, "FontEngine", fkey); + opt = gf_opts_get_key("FontCache", fkey); if (!opt) continue; strcpy(fkey, key); strcat(fkey, " Bold"); - opt = gf_cfg_get_key(cfg, "FontEngine", fkey); + opt = gf_opts_get_key("FontCache", fkey); if (!opt) continue; strcpy(fkey, key); strcat(fkey, " Bold Italic"); - opt = gf_cfg_get_key(cfg, "FontEngine", fkey); + opt = gf_opts_get_key("FontCache", fkey); if (!opt) continue; strcpy(fkey, key); @@ -306,9 +293,9 @@ static void ft_rescan_fonts(GF_FontReader *dr) if (!ftpriv->font_sans) ftpriv->font_sans = gf_strdup(ftpriv->font_default ? ftpriv->font_default : ""); if (!ftpriv->font_fixed) ftpriv->font_fixed = gf_strdup(ftpriv->font_default ? ftpriv->font_default : ""); - gf_modules_set_option((GF_BaseInterface *)dr, "FontEngine", "FontFixed", ftpriv->font_fixed); - gf_modules_set_option((GF_BaseInterface *)dr, "FontEngine", "FontSerif", ftpriv->font_serif); - gf_modules_set_option((GF_BaseInterface *)dr, "FontEngine", "FontSans", ftpriv->font_sans); + gf_opts_set_key("FontCache", "FontFixed", ftpriv->font_fixed); + gf_opts_set_key("FontCache", "FontSerif", ftpriv->font_serif); + gf_opts_set_key("FontCache", "FontSans", ftpriv->font_sans); GF_LOG(GF_LOG_INFO, GF_LOG_PARSER, ("[FreeType] Font directories scanned\n")); } @@ -318,11 +305,14 @@ static void ft_rescan_fonts(GF_FontReader *dr) static GF_Err ft_init_font_engine(GF_FontReader *dr) { const char *sOpt; + Bool rescan = GF_FALSE; FTBuilder *ftpriv = (FTBuilder *)dr->udta; - sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "FontEngine", "FontDirectory"); - if (!sOpt) return GF_BAD_PARAM; - + sOpt = gf_opts_get_key("core", "font-dirs"); + if (!sOpt) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[FreeType] No fonts directory indicated!")); + return GF_BAD_PARAM; + } /*inits freetype*/ if (FT_Init_FreeType(&ftpriv->library) ) { GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[FreeType] Cannot initialize FreeType\n")); @@ -351,23 +341,29 @@ static GF_Err ft_init_font_engine(GF_FontReader *dr) sep[0] = ','; sOpt = sep+1; } + if (!gf_opts_get_key_count("FontCache")) + rescan = GF_TRUE; + else { + sOpt = gf_opts_get_key("core", "rescan-fonts"); + if (!sOpt || !strcmp(sOpt, "yes") ) + rescan = GF_TRUE; + } - sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "FontEngine", "RescanFonts"); - if (!sOpt || !strcmp(sOpt, "yes") ) + if (rescan) ft_rescan_fonts(dr); if (!ftpriv->font_serif) { - sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "FontEngine", "FontSerif"); + sOpt = gf_opts_get_key("FontCache", "FontSerif"); ftpriv->font_serif = gf_strdup(sOpt ? sOpt : ""); } if (!ftpriv->font_sans) { - sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "FontEngine", "FontSans"); + sOpt = gf_opts_get_key("FontCache", "FontSans"); ftpriv->font_sans = gf_strdup(sOpt ? sOpt : ""); } if (!ftpriv->font_fixed) { - sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "FontEngine", "FontFixed"); + sOpt = gf_opts_get_key("FontCache", "FontFixed"); ftpriv->font_fixed = gf_strdup(sOpt ? sOpt : ""); } GF_LOG(GF_LOG_DEBUG, GF_LOG_PARSER, ("[FreeType] Init OK - %d font directory (first %s)\n", gf_list_count(ftpriv->font_dirs), gf_list_get(ftpriv->font_dirs, 0) )); @@ -382,8 +378,7 @@ static GF_Err ft_shutdown_font_engine(GF_FontReader *dr) ftpriv->active_face = NULL; /*reset loaded fonts*/ while (gf_list_count(ftpriv->loaded_fonts)) { - FT_Face face = gf_list_get(ftpriv->loaded_fonts, 0); - gf_list_rem(ftpriv->loaded_fonts, 0); + FT_Face face = gf_list_pop_front(ftpriv->loaded_fonts); FT_Done_Face(face); } @@ -451,6 +446,9 @@ static GF_Err ft_set_font(GF_FontReader *dr, const char *OrigFontName, u32 style fontName = (char *) OrigFontName; ftpriv->active_face = NULL; + opt = gf_opts_get_key("temp_freetype", OrigFontName); + if (opt) return GF_NOT_SUPPORTED; + if (!fontName || !strlen(fontName) || !stricmp(fontName, "SERIF")) { fontName = ftpriv->font_serif; } @@ -477,7 +475,7 @@ checkFont: if (styles & GF_FONT_WEIGHT_BOLD & checkStyles) strcat(fname, " Bold"); if (styles & GF_FONT_ITALIC & checkStyles) strcat(fname, " Italic"); - opt = gf_modules_get_option((GF_BaseInterface *)dr, "FontEngine", fname); + opt = gf_opts_get_key("FontCache", fname); if (opt) { FT_Face face; @@ -502,8 +500,9 @@ checkFont: } } - GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[FreeType] Font '%s' (%s) not found\n", fontName, fname)); + GF_LOG(GF_LOG_INFO, GF_LOG_PARSER, ("[FreeType] Font %s (%s) not found\n", fontName, fname)); gf_free(fname); + gf_opts_set_key("temp_freetype", OrigFontName, "not found"); return GF_NOT_SUPPORTED; } @@ -573,7 +572,7 @@ typedef struct s32 last_x, last_y; } ft_outliner; -#if defined(GPAC_IPHONE) || defined(GPAC_ANDROID) +#if defined(GPAC_CONFIG_IOS) || defined(GPAC_CONFIG_ANDROID) #define FTCST #else #define FTCST const @@ -644,10 +643,8 @@ static GF_Glyph *ft_load_glyph(GF_FontReader *dr, u32 glyph_name) FT_Get_Glyph(ftpriv->active_face->glyph, (FT_Glyph*)&outline); -#ifdef FT_GLYPH_FORMAT_OUTLINE /*oops not vectorial...*/ - if (outline->root.format != FT_GLYPH_FORMAT_OUTLINE) return NULL; -#endif + if (outline->root.format==FT_GLYPH_FORMAT_BITMAP) return NULL; GF_SAFEALLOC(glyph, GF_Glyph); diff --git a/modules/gpac_js/Makefile b/modules/gpac_js/Makefile deleted file mode 100644 index ea89c3d..0000000 --- a/modules/gpac_js/Makefile +++ /dev/null @@ -1,63 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/gpac_js - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -LINKLIBS= -lgpac -LOCAL_LIB=../../bin/gcc - -#common objects -OBJS=gpac_js.o - -ifeq ($(CONFIG_JS),no) -else -CFLAGS+=$(JS_FLAGS) -ifeq ($(CONFIG_JS),local) -NEED_LOCAL_LIB="yes" -LOCAL_LIB+=-L../../extra_lib/lib/gcc -endif -ifeq ($(CONFIG_JS),prefix) -NEED_LOCAL_LIB="yes" -LOCAL_LIB+=-L$(prefix)/lib -endif -LINKLIBS+= $(JS_LIBS) -endif - -SRCS := $(OBJS:.o=.c) - -LIB=gm_gpac_js$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols gpac_js.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L$(LOCAL_LIB) $(LINKLIBS) $(LDFLAGS) - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/gpac_js/gpac_js.c b/modules/gpac_js/gpac_js.c deleted file mode 100644 index 22be65a..0000000 --- a/modules/gpac_js/gpac_js.c +++ /dev/null @@ -1,2517 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2007-2012 - * All rights reserved - * - * This file is part of GPAC / Scene Graph sub-project - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include <gpac/setup.h> - -#ifdef GPAC_HAS_SPIDERMONKEY - -/*base SVG type*/ -#include <gpac/nodes_svg.h> -#include <gpac/nodes_mpeg4.h> -#include <gpac/nodes_x3d.h> -/*dom events*/ -#include <gpac/events.h> - -#include <gpac/download.h> -#include <gpac/network.h> -#include <gpac/options.h> -#include <gpac/xml.h> - - -#if defined(GPAC_ANDROID) && !defined(XP_UNIX) -#define XP_UNIX -#endif - -#if !defined(__GNUC__) -# if defined(_WIN32_WCE) -# pragma comment(lib, "js32") -# elif defined (_WIN64) -# pragma comment(lib, "js") -# elif defined (WIN32) -# pragma comment(lib, "js") -# endif -#endif - -#include <gpac/internal/scenegraph_dev.h> -#include <gpac/internal/smjs_api.h> - -#include <gpac/modules/js_usr.h> -#include <gpac/internal/terminal_dev.h> -#include <gpac/internal/compositor_dev.h> -#include <gpac/term_info.h> - -#define GPAC_JS_RTI_REFRESH_RATE 200 - -typedef struct -{ - u32 nb_loaded; - GF_Terminal *term; - - GF_JSClass gpacClass; - GF_JSClass gpacEvtClass; - GF_JSClass odmClass; - GF_JSClass anyClass; - - GF_JSClass storageClass; - - jsval evt_fun; - GF_TermEventFilter evt_filter; - GF_Event *evt; - - JSContext *c; - JSObject *evt_filter_obj, *evt_obj; - JSObject *gpac_obj; - - u32 rti_refresh_rate; - GF_SystemRTInfo rti; - - //list of config files for storage - GF_List *storages; - - GF_List *event_queue; - GF_Mutex *event_mx; - -} GF_GPACJSExt; - -enum { - GJS_OM_PROP_ID = -1, - GJS_OM_PROP_NB_RES = -2, - GJS_OM_PROP_URL = -3, - GJS_OM_PROP_DUR = -4, - GJS_OM_PROP_CLOCK = -5, - GJS_OM_PROP_DRIFT = -6, - GJS_OM_PROP_STATUS = -7, - GJS_OM_PROP_BUFFER = -8, - GJS_OM_PROP_DB_COUNT = -9, - GJS_OM_PROP_CB_COUNT = -10, - GJS_OM_PROP_CB_CAP = -11, - GJS_OM_PROP_TYPE = -12, - GJS_OM_PROP_SAMPLERATE = -13, - GJS_OM_PROP_CHANNELS = -14, - GJS_OM_PROP_LANG = -15, - GJS_OM_PROP_WIDTH = -16, - GJS_OM_PROP_HEIGHT = -17, - GJS_OM_PROP_PIXELFORMAT = -18, - GJS_OM_PROP_PAR = -19, - GJS_OM_PROP_DEC_FRAMES = -20, - GJS_OM_PROP_DROP_FRAMES = -21, - GJS_OM_PROP_DEC_TIME_MAX = -22, - GJS_OM_PROP_DEC_TIME_TOTAL = -23, - GJS_OM_PROP_AVG_RATE = -24, - GJS_OM_PROP_MAX_RATE = -25, - GJS_OM_PROP_SERVICE_HANDLER = -26, - GJS_OM_PROP_CODEC = -27, - GJS_OM_PROP_NB_QUALITIES = -28, - GJS_OM_PROP_MAX_BUFFER = -29, - GJS_OM_PROP_MIN_BUFFER = -30, - GJS_OM_PROP_FRAME_DUR = -31, - GJS_OM_PROP_NB_IRAP = -32, - GJS_OM_PROP_IRAP_DEC_TIME = -33, - GJS_OM_PROP_IRAP_MAX_TIME = -34, - GJS_OM_PROP_SERVICE_ID = -35, - GJS_OM_PROP_SELECTED_SERVICE = -36, - GJS_OM_PROP_BANDWIDTH_DOWN = -37, - GJS_OM_PROP_NB_HTTP = -38, - GJS_OM_PROP_TIMESHIFT_DEPTH = -39, - GJS_OM_PROP_TIMESHIFT_TIME = -40, - GJS_OM_PROP_IS_ADDON = -41, - GJS_OM_PROP_MAIN_ADDON_ON = -42, - GJS_OM_PROP_IS_OVER = -43, - GJS_OM_PROP_IS_PULLING = -44, - GJS_OM_PROP_DYNAMIC_SCENE = -45, - GJS_OM_PROP_SERVICE_NAME = -46, - GJS_OM_PROP_NTP_DIFF = -47, - GJS_OM_PROP_MAIN_ADDON_URL = -48, - GJS_OM_PROP_REVERSE_PLAYBACK = -49, - GJS_OM_PROP_SCALABLE_ENHANCEMENT = -50, - GJS_OM_PROP_MAIN_ADDON_MEDIATIME = -51, - GJS_OM_PROP_DEPENDENT_GROUPS = -52, - GJS_OM_PROP_IS_VR_SCENE = -53, -}; - -enum { - GJS_GPAC_PROP_LAST_WORK_DIR = -1, - GJS_GPAC_PROP_SCALE_X = -2, - GJS_GPAC_PROP_SCALE_Y = -3, - GJS_GPAC_PROP_TRANSLATION_X = -4, - GJS_GPAC_PROP_TRANSLATION_Y = -5, - GJS_GPAC_PROP_RECT_TEXTURES = -6, - GJS_GPAC_PROP_BATTERY_ON = -7, - GJS_GPAC_PROP_BATTERY_CHARGE = -8, - GJS_GPAC_PROP_BATTERY_PERCENT = -9, - GJS_GPAC_PROP_BATTERY_LIFETIME = -10, - GJS_GPAC_PROP_BATTERY_LIFETIME_FULL = -11, - GJS_GPAC_PROP_HOSTNAME = -12, - GJS_GPAC_PROP_FULLSCREEN = -13, - GJS_GPAC_PROP_CURRENT_PATH = -14, - GJS_GPAC_PROP_VOLUME = -15, - GJS_GPAC_PROP_NAVIGATION = -16, - GJS_GPAC_PROP_NAVIGATION_TYPE = -17, - GJS_GPAC_PROP_HARDWARE_YUV = -18, - GJS_GPAC_PROP_HARDWARE_RGB = -19, - GJS_GPAC_PROP_HARDWARE_RGBA = -20, - GJS_GPAC_PROP_HARDWARE_STRETCH = -21, - GJS_GPAC_PROP_SCREEN_WIDTH = -22, - GJS_GPAC_PROP_SCREEN_HEIGHT = -23, - GJS_GPAC_PROP_HTTP_MAX_RATE = -24, - GJS_GPAC_PROP_HTTP_RATE = -25, - GJS_GPAC_PROP_FPS = -26, - GJS_GPAC_PROP_CPU = -27, - GJS_GPAC_PROP_NB_CORES = -28, - GJS_GPAC_PROP_MEMORY_SYSTEM = -29, - GJS_GPAC_PROP_MEMORY = -30, - GJS_GPAC_PROP_ARGC = -31, - GJS_GPAC_PROP_CAPTION = -32, - GJS_GPAC_PROP_FOCUS_HIGHLIGHT = -33, - GJS_GPAC_PROP_DPI_X = -34, - GJS_GPAC_PROP_DPI_Y = -35, - GJS_GPAC_PROP_SENSORS_ACTIVE = -36, - GJS_GPAC_PROP_SIM_FPS = -37, - -}; - -enum { - GJS_EVT_PROP_KEYCODE = -1, - GJS_EVT_PROP_MOUSE_X = -2, - GJS_EVT_PROP_MOUSE_Y = -3, - GJS_EVT_PROP_PICKED = -4, - GJS_EVT_PROP_WHEEL = -5, - GJS_EVT_PROP_BUTTON = -6, - GJS_EVT_PROP_TYPE = -7, - GJS_EVT_PROP_NAME = -8, - GJS_EVT_PROP_HWKEY = -9, -}; - - -static GF_Terminal *gpac_get_term(JSContext *c, JSObject *obj) -{ - GF_GPACJSExt *ext = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj); - return ext ? ext->term : NULL; -} - -static SMJS_FUNC_PROP_GET( gpac_getProperty) - -const char *res; -s32 prop_id; -GF_Terminal *term = gpac_get_term(c, obj); -if (!term) return JS_FALSE; - -if (!SMJS_ID_IS_INT(id)) return JS_TRUE; -prop_id = SMJS_ID_TO_INT(id); - -switch (prop_id) { -case GJS_GPAC_PROP_LAST_WORK_DIR: - res = gf_cfg_get_key(term->user->config, "General", "LastWorkingDir"); - if (!res) res = gf_cfg_get_key(term->user->config, "General", "ModulesDirectory"); - *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, res)); - break; - -case GJS_GPAC_PROP_SCALE_X: - *vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, FIX2FLT(term->compositor->scale_x)) ); - break; - -case GJS_GPAC_PROP_SCALE_Y: - *vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, FIX2FLT(term->compositor->scale_y)) ); - break; - -case GJS_GPAC_PROP_TRANSLATION_X: - *vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, FIX2FLT(term->compositor->trans_x)) ); - break; - -case GJS_GPAC_PROP_TRANSLATION_Y: - *vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, FIX2FLT(term->compositor->trans_y)) ); - break; - -case GJS_GPAC_PROP_RECT_TEXTURES: -{ - Bool any_size = GF_FALSE; -#ifndef GPAC_DISABLE_3D - if (term->compositor->gl_caps.npot_texture || term->compositor->gl_caps.rect_texture) - any_size = GF_TRUE; -#endif - *vp = BOOLEAN_TO_JSVAL( any_size ? JS_TRUE : JS_FALSE ); -} -break; - -case GJS_GPAC_PROP_BATTERY_ON: -{ - Bool on_battery = GF_FALSE; - gf_sys_get_battery_state(&on_battery, NULL, NULL, NULL, NULL); - *vp = BOOLEAN_TO_JSVAL( on_battery ? JS_TRUE : JS_FALSE ); -} -break; - -case GJS_GPAC_PROP_BATTERY_CHARGE: -{ - u32 on_charge = 0; - gf_sys_get_battery_state(NULL, &on_charge, NULL, NULL, NULL); - *vp = BOOLEAN_TO_JSVAL( on_charge ? JS_TRUE : JS_FALSE ); -} -break; - -case GJS_GPAC_PROP_BATTERY_PERCENT: -{ - u32 level = 0; - gf_sys_get_battery_state(NULL, NULL, &level, NULL, NULL); - *vp = INT_TO_JSVAL( level ); -} -break; - -case GJS_GPAC_PROP_BATTERY_LIFETIME: -{ - u32 level = 0; - gf_sys_get_battery_state(NULL, NULL, NULL, &level, NULL); - *vp = INT_TO_JSVAL( level ); -} -break; - -case GJS_GPAC_PROP_BATTERY_LIFETIME_FULL: -{ - u32 level = 0; - gf_sys_get_battery_state(NULL, NULL, NULL, NULL, &level); - *vp = INT_TO_JSVAL( level ); -} -break; - -case GJS_GPAC_PROP_HOSTNAME: -{ - char hostname[100]; - gf_sk_get_host_name((char*)hostname); - *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, hostname)); -} -break; - -case GJS_GPAC_PROP_FULLSCREEN: - *vp = BOOLEAN_TO_JSVAL( term->compositor->fullscreen ? JS_TRUE : JS_FALSE); - break; - -case GJS_GPAC_PROP_CURRENT_PATH: -{ - char *url = gf_url_concatenate(term->root_scene->root_od->net_service->url, ""); - if (!url) url = gf_strdup(""); - *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, url)); - gf_free(url); -} -break; - -case GJS_GPAC_PROP_VOLUME: - *vp = INT_TO_JSVAL( gf_term_get_option(term, GF_OPT_AUDIO_VOLUME)); - break; - -case GJS_GPAC_PROP_NAVIGATION: - *vp = INT_TO_JSVAL( gf_term_get_option(term, GF_OPT_NAVIGATION)); - break; - -case GJS_GPAC_PROP_NAVIGATION_TYPE: - *vp = INT_TO_JSVAL( gf_term_get_option(term, GF_OPT_NAVIGATION_TYPE) ); - break; - -case GJS_GPAC_PROP_HARDWARE_YUV: - *vp = INT_TO_JSVAL( (term->compositor->video_out->hw_caps & GF_VIDEO_HW_HAS_YUV) ? 1 : 0 ); - break; - -case GJS_GPAC_PROP_HARDWARE_RGB: - *vp = INT_TO_JSVAL( (term->compositor->video_out->hw_caps & GF_VIDEO_HW_HAS_RGB) ? 1 : 0 ); - break; - -case GJS_GPAC_PROP_HARDWARE_RGBA: -{ - u32 has_rgba = (term->compositor->video_out->hw_caps & GF_VIDEO_HW_HAS_RGBA) ? 1 : 0; -#ifndef GPAC_DISABLE_3D - if (term->compositor->hybrid_opengl || term->compositor->is_opengl) has_rgba = 1; -#endif - *vp = INT_TO_JSVAL( has_rgba ); -} -break; - -case GJS_GPAC_PROP_HARDWARE_STRETCH: - *vp = INT_TO_JSVAL( (term->compositor->video_out->hw_caps & GF_VIDEO_HW_HAS_STRETCH) ? 1 : 0 ); - break; - -case GJS_GPAC_PROP_SCREEN_WIDTH: - *vp = INT_TO_JSVAL( term->compositor->video_out->max_screen_width); - break; - -case GJS_GPAC_PROP_SCREEN_HEIGHT: - *vp = INT_TO_JSVAL( term->compositor->video_out->max_screen_height); - break; - -case GJS_GPAC_PROP_HTTP_MAX_RATE: - *vp = INT_TO_JSVAL( gf_dm_get_data_rate(term->downloader)); - break; - -case GJS_GPAC_PROP_HTTP_RATE: - *vp = INT_TO_JSVAL( gf_dm_get_global_rate(term->downloader) / 1000); - break; - -case GJS_GPAC_PROP_FPS: - *vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, gf_term_get_framerate(term, 0) ) ); - break; - -case GJS_GPAC_PROP_SIM_FPS: - *vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, term->compositor->frame_rate) ); - break; - -case GJS_GPAC_PROP_CPU: -{ - GF_GPACJSExt *ext = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj); - gf_sys_get_rti(ext->rti_refresh_rate, &ext->rti, 0); - *vp = INT_TO_JSVAL(ext->rti.process_cpu_usage); -} -break; - -case GJS_GPAC_PROP_NB_CORES: -{ - GF_GPACJSExt *ext = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj); - gf_sys_get_rti(ext->rti_refresh_rate, &ext->rti, 0); - *vp = INT_TO_JSVAL(ext->rti.nb_cores); -} -break; - -case GJS_GPAC_PROP_MEMORY_SYSTEM: -{ - GF_GPACJSExt *ext = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj); - gf_sys_get_rti(ext->rti_refresh_rate, &ext->rti, 0); - *vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, (Double) ext->rti.physical_memory ) ); -} -break; - -case GJS_GPAC_PROP_MEMORY: -{ - GF_GPACJSExt *ext = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj); - gf_sys_get_rti(ext->rti_refresh_rate, &ext->rti, 0); - *vp = INT_TO_JSVAL(ext->rti.process_memory); -} -break; - -case GJS_GPAC_PROP_ARGC: - *vp = INT_TO_JSVAL(gf_sys_get_argc() ); - break; - -case GJS_GPAC_PROP_DPI_X: - *vp = INT_TO_JSVAL(term->compositor->video_out->dpi_x); - break; - -case GJS_GPAC_PROP_DPI_Y: - *vp = INT_TO_JSVAL(term->compositor->video_out->dpi_y); - break; - -case GJS_GPAC_PROP_SENSORS_ACTIVE: - *vp = BOOLEAN_TO_JSVAL(term->orientation_sensors_active); - break; -} - -return JS_TRUE; -} - - -static SMJS_FUNC_PROP_SET( gpac_setProperty) - -s32 prop_id; -char *prop_val; -GF_Terminal *term = gpac_get_term(c, obj); -if (!term) return JS_FALSE; - -if (!SMJS_ID_IS_INT(id)) return JS_TRUE; -prop_id = SMJS_ID_TO_INT(id); - -switch (prop_id) { -case GJS_GPAC_PROP_LAST_WORK_DIR: - if (!JSVAL_IS_STRING(*vp)) { - return JS_FALSE; - } - prop_val = SMJS_CHARS(c, *vp); - gf_cfg_set_key(term->user->config, "General", "LastWorkingDir", prop_val); - SMJS_FREE(c, prop_val); - break; -case GJS_GPAC_PROP_CAPTION: -{ - GF_Event evt; - char *caption; - if (!JSVAL_IS_STRING(*vp)) { - return JS_FALSE; - } - evt.type = GF_EVENT_SET_CAPTION; - caption = SMJS_CHARS(c, *vp); - if (!strnicmp(caption, "gpac://", 7)) { - evt.caption.caption = caption + 7; - } else { - evt.caption.caption = caption; - } - - gf_term_user_event(term, &evt); - SMJS_FREE(c, (char*)caption); -} -break; -case GJS_GPAC_PROP_FULLSCREEN: -{ - /*no fullscreen for iOS (always on)*/ -#ifndef GPAC_IPHONE - Bool res = (JSVAL_TO_BOOLEAN(*vp)==JS_TRUE) ? 1 : 0; - if (term->compositor->fullscreen != res) { - gf_term_set_option(term, GF_OPT_FULLSCREEN, res); - } -#endif -} -break; -case GJS_GPAC_PROP_VOLUME: - if (JSVAL_IS_NUMBER(*vp)) { - jsdouble d; - SMJS_GET_NUMBER(*vp, d); - gf_term_set_option(term, GF_OPT_AUDIO_VOLUME, (u32) d); - } else if (JSVAL_IS_INT(*vp)) { - gf_term_set_option(term, GF_OPT_AUDIO_VOLUME, JSVAL_TO_INT(*vp)); - } - break; - -case GJS_GPAC_PROP_NAVIGATION: - gf_term_set_option(term, GF_OPT_NAVIGATION, JSVAL_TO_INT(*vp) ); - break; -case GJS_GPAC_PROP_NAVIGATION_TYPE: - gf_term_set_option(term, GF_OPT_NAVIGATION_TYPE, 0); - break; -case GJS_GPAC_PROP_HTTP_MAX_RATE: -{ - u32 new_rate = JSVAL_TO_INT(*vp); - gf_dm_set_data_rate(term->downloader, new_rate); -} -break; -case GJS_GPAC_PROP_FOCUS_HIGHLIGHT: - term->compositor->disable_focus_highlight = JSVAL_TO_BOOLEAN(*vp) ? 0 : 1; - break; -case GJS_GPAC_PROP_SENSORS_ACTIVE: -{ - GF_Event evt; - term->orientation_sensors_active = JSVAL_TO_BOOLEAN(*vp); - //send activation for sensors - memset(&evt, 0, sizeof(GF_Event)); - evt.type = GF_EVENT_SENSOR_REQUEST; - evt.activate_sensor.activate = term->orientation_sensors_active; - evt.activate_sensor.sensor_type = GF_EVENT_SENSOR_ORIENTATION; - if (gf_term_send_event(term, &evt) == GF_FALSE) { - term->orientation_sensors_active = GF_FALSE; - } -} - break; -} -return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gpac_get_option) -{ - const char *opt = NULL; - char *sec_name, *key_name; - s32 idx = -1; - JSString *s; - SMJS_OBJ - SMJS_ARGS - GF_Terminal *term = gpac_get_term(c, obj); - if (!term) return JS_FALSE; - - if (argc < 2) return JS_FALSE; - - if (!JSVAL_IS_STRING(argv[0])) return JS_FALSE; - if (!JSVAL_IS_STRING(argv[1]) && !JSVAL_IS_INT(argv[1])) return JS_FALSE; - - sec_name = SMJS_CHARS(c, argv[0]); - key_name = NULL; - if (JSVAL_IS_INT(argv[1])) { - idx = JSVAL_TO_INT(argv[1]); - } else if (JSVAL_IS_STRING(argv[1]) ) { - key_name = SMJS_CHARS(c, argv[1]); - } - - if (!stricmp(sec_name, "audiofilters")) { - if (!term->compositor->audio_renderer->filter_chain.enable_filters - || !term->compositor->audio_renderer->filter_chain.filters->filter->GetOption) { - SMJS_FREE(c, key_name); - SMJS_FREE(c, sec_name); - return JS_TRUE; - } - opt = term->compositor->audio_renderer->filter_chain.filters->filter->GetOption(term->compositor->audio_renderer->filter_chain.filters->filter, key_name); - } else if (key_name) { - opt = gf_cfg_get_key(term->user->config, sec_name, key_name); - } else if (idx>=0) { - opt = gf_cfg_get_key_name(term->user->config, sec_name, idx); - } else { - opt = NULL; - } - if (key_name) { - SMJS_FREE(c, key_name); - } - SMJS_FREE(c, sec_name); - - if (opt) { - s = JS_NewStringCopyZ(c, opt); - SMJS_SET_RVAL( STRING_TO_JSVAL(s) ); - } else { - SMJS_SET_RVAL( JSVAL_NULL ); - } - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gpac_set_option) -{ - char *sec_name, *key_name, *key_val; - SMJS_OBJ - SMJS_ARGS - GF_Terminal *term = gpac_get_term(c, obj); - if (!term) return JS_FALSE; - if (argc < 3) return JS_FALSE; - - if (!JSVAL_IS_STRING(argv[0])) return JS_FALSE; - if (!JSVAL_IS_STRING(argv[1])) return JS_FALSE; - - sec_name = SMJS_CHARS(c, argv[0]); - key_name = SMJS_CHARS(c, argv[1]); - key_val = NULL; - if (JSVAL_IS_STRING(argv[2])) - key_val = SMJS_CHARS(c, argv[2]); - - if (!stricmp(sec_name, "audiofilters")) { - if (term->compositor->audio_renderer->filter_chain.enable_filters - && term->compositor->audio_renderer->filter_chain.filters->filter->SetOption) { - term->compositor->audio_renderer->filter_chain.filters->filter->SetOption(term->compositor->audio_renderer->filter_chain.filters->filter, key_name, key_val); - } - } else { - gf_cfg_set_key(term->user->config, sec_name, key_name, key_val); - if (!strcmp(sec_name, "Audio") && !strcmp(key_name, "Filter")) { - gf_sc_reload_audio_filters(term->compositor); - } - if (!strcmp(sec_name, "Compositor") && !strcmp(key_name, "StereoType")) { - gf_sc_reload_config(term->compositor); - } - } - SMJS_FREE(c, sec_name); - SMJS_FREE(c, key_name); - if (key_val) { - SMJS_FREE(c, key_val); - } - return JS_TRUE; -} - - -static JSBool SMJS_FUNCTION(gpac_get_arg) -{ - SMJS_OBJ - SMJS_ARGS - GF_Terminal *term = gpac_get_term(c, obj); - if (!term) return JS_FALSE; - - if (argc < 1) return JS_FALSE; - - if (JSVAL_IS_INT(argv[0])) { - u32 idx = JSVAL_TO_INT(argv[0]); - const char *arg = gf_sys_get_arg(idx); - if (arg) { - SMJS_SET_RVAL( STRING_TO_JSVAL(JS_NewStringCopyZ(c, arg)) ); - } else { - SMJS_SET_RVAL( JSVAL_NULL ); - } - return JS_TRUE; - } else { - return JS_FALSE; - } - return JS_TRUE; -} - - -static JSBool SMJS_FUNCTION(gpac_set_back_color) -{ - SMJS_OBJ - SMJS_ARGS - u32 r, g, b, a, i; - jsdouble d; - GF_Terminal *term = gpac_get_term(c, obj); - if (!term) return JS_FALSE; - - if (argc < 3) return JS_FALSE; - r = g = b = 0; - a = 255; - for (i=0; i<argc; i++) { - u32 v=0; - if (JSVAL_IS_DOUBLE(argv[i])) { - SMJS_GET_NUMBER(argv[i], d); - v = (u32) (255 * d); - } else if (JSVAL_IS_INT(argv[i])) { - v = 255 * JSVAL_TO_INT(argv[i]); - } - if (i==0) r = v; - else if (i==1) g = v; - else if (i==2) b = v; - else if (i==3) a = v; - } - term->compositor->back_color = term->compositor->default_back_color = GF_COL_ARGB(a, r, g, b); - gf_sc_invalidate(term->compositor, NULL); - return JS_TRUE; -} - - -static JSBool SMJS_FUNCTION(gpac_switch_quality) -{ - SMJS_OBJ - SMJS_ARGS - GF_Terminal *term = gpac_get_term(c, obj); - if (!term) return JS_FALSE; - - if (argc < 1) return JS_FALSE; - - if (JSVAL_IS_BOOLEAN(argv[0])) { - Bool up = (JSVAL_TO_BOOLEAN(argv[0])==JS_TRUE) ? GF_TRUE : GF_FALSE; - gf_term_switch_quality(term, up); - return JS_TRUE; - } else { - return JS_FALSE; - } - return JS_TRUE; -} - - -static JSBool SMJS_FUNCTION(gpac_reload) -{ - SMJS_OBJ - GF_Event evt; - GF_Terminal *term = gpac_get_term(c, obj); - if (!term) return JS_FALSE; - - memset(&evt, 0, sizeof(GF_Event)); - evt.type = GF_EVENT_RELOAD; - term->user->EventProc(term->user->opaque, &evt); - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gpac_navigation_supported) -{ - SMJS_OBJ - SMJS_ARGS - u32 type; - GF_Terminal *term = gpac_get_term(c, obj); - if (!term) return JS_FALSE; - - if (argc < 1) return JS_FALSE; - - if (! JSVAL_IS_INT(argv[0]) ) { - SMJS_SET_RVAL( BOOLEAN_TO_JSVAL(JS_FALSE) ); - return JS_TRUE; - } - type = JSVAL_TO_INT(argv[0]); - SMJS_SET_RVAL( BOOLEAN_TO_JSVAL( gf_sc_navigation_supported(term->compositor, type) ? JS_TRUE : JS_FALSE ) ); - return JS_TRUE; -} - -typedef struct -{ - JSContext *c; - JSObject *array; - Bool is_dir; -} enum_dir_cbk; - -static Bool enum_dir_fct(void *cbck, char *file_name, char *file_path, GF_FileEnumInfo *file_info) -{ - u32 i, len; - char *sep; - JSString *s; - jsuint idx; - jsval v; - JSObject *obj; - enum_dir_cbk *cbk = (enum_dir_cbk*)cbck; - - if (file_name && (file_name[0]=='.')) return 0; - - obj = JS_NewObject(cbk->c, 0, 0, 0); - s = JS_NewStringCopyZ(cbk->c, file_name); - JS_DefineProperty(cbk->c, obj, "name", STRING_TO_JSVAL(s), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - - sep=NULL; - len = (u32) strlen(file_path); - for (i=0; i<len; i++) { - sep = strchr("/\\", file_path[len-i-1]); - if (sep) { - sep = file_path+len-i-1; - break; - } - } - if (sep) { - sep[0] = '/'; - sep[1] = 0; - s = JS_NewStringCopyZ(cbk->c, file_path); - } else { - s = JS_NewStringCopyZ(cbk->c, file_path); - } - JS_DefineProperty(cbk->c, obj, "path", STRING_TO_JSVAL(s), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(cbk->c, obj, "directory", BOOLEAN_TO_JSVAL(cbk->is_dir ? JS_TRUE : JS_FALSE), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(cbk->c, obj, "drive", BOOLEAN_TO_JSVAL(file_info->drive ? JS_TRUE : JS_FALSE), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(cbk->c, obj, "hidden", BOOLEAN_TO_JSVAL(file_info->hidden ? JS_TRUE : JS_FALSE), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(cbk->c, obj, "system", BOOLEAN_TO_JSVAL(file_info->system ? JS_TRUE : JS_FALSE), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(cbk->c, obj, "size", INT_TO_JSVAL(file_info->size), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(cbk->c, obj, "last_modified", INT_TO_JSVAL(file_info->last_modified), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - - - JS_GetArrayLength(cbk->c, cbk->array, &idx); - v = OBJECT_TO_JSVAL(obj); - JS_SetElement(cbk->c, cbk->array, idx, &v); - return 0; -} - -static JSBool SMJS_FUNCTION(gpac_enum_directory) -{ - GF_Err err; - enum_dir_cbk cbk; - char *url = NULL; - char *dir = NULL; - char *filter = NULL; - char *an_url; - Bool dir_only = 0; - Bool browse_root = 0; - GF_Terminal *term; - SMJS_OBJ - SMJS_ARGS - - if ((argc >= 1) && JSVAL_IS_STRING(argv[0])) { - dir = SMJS_CHARS(c, argv[0]); - if (!strcmp(dir, "/")) browse_root = 1; - } - if ((argc >= 2) && JSVAL_IS_STRING(argv[1])) { - filter = SMJS_CHARS(c, argv[1]); - if (!strcmp(filter, "dir")) { - dir_only = 1; - filter = NULL; - } else if (!strlen(filter)) { - SMJS_FREE(c, filter); - filter=NULL; - } - } - if ((argc >= 3) && JSVAL_IS_BOOLEAN(argv[2])) { - if (JSVAL_TO_BOOLEAN(argv[2])==JS_TRUE) { - url = gf_url_concatenate(dir, ".."); - if (dir && ( !strcmp(url, "..") || (url[0]==0) )) { - if ((dir[1]==':') && ((dir[2]=='/') || (dir[2]=='\\')) ) browse_root = 1; - else if (!strcmp(dir, "/")) browse_root = 1; - } - if (!strcmp(url, "/")) browse_root = 1; - } - } - - if ( (!dir || !strlen(dir) ) && (!url || !strlen(url))) browse_root = 1; - - if (browse_root) { - cbk.c = c; - cbk.array = JS_NewArrayObject(c, 0, 0); - cbk.is_dir = 1; - gf_enum_directory("/", 1, enum_dir_fct, &cbk, NULL); - SMJS_SET_RVAL( OBJECT_TO_JSVAL(cbk.array) ); - if (url) gf_free(url); - SMJS_FREE(c, dir); - SMJS_FREE(c, filter); - return JS_TRUE; - } - - cbk.c = c; - cbk.array = JS_NewArrayObject(c, 0, 0); - - cbk.is_dir = 1; - - term = gpac_get_term(c, obj); - /*concatenate with service url*/ - an_url = gf_url_concatenate(term->root_scene->root_od->net_service->url, url ? url : dir); - if (an_url) { - gf_free(url); - url = an_url; - } - err = gf_enum_directory(url ? url : dir, 1, enum_dir_fct, &cbk, NULL); - if (err) return JS_FALSE; - - if (!dir_only) { - cbk.is_dir = 0; - err = gf_enum_directory(url ? url : dir, 0, enum_dir_fct, &cbk, filter); - if (dir_only && (err==GF_IO_ERR)) { - GF_Terminal *term = gpac_get_term(c, obj); - /*try to concatenate with service url*/ - char *an_url = gf_url_concatenate(term->root_scene->root_od->net_service->url, url ? url : dir); - gf_free(url); - url = an_url; - gf_enum_directory(url ? url : dir, 0, enum_dir_fct, &cbk, filter); - } - } - - SMJS_SET_RVAL( OBJECT_TO_JSVAL(cbk.array) ); - if (url) gf_free(url); - SMJS_FREE(c, dir); - SMJS_FREE(c, filter); - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gpac_set_size) -{ - Bool override_size_info = 0; - u32 w, h; - jsdouble d; - SMJS_OBJ - SMJS_ARGS - GF_Terminal *term = gpac_get_term(c, obj); - if (!term) return JS_FALSE; - - w = h = 0; - if ((argc >= 1) && JSVAL_IS_NUMBER(argv[0])) { - SMJS_GET_NUMBER(argv[0], d); - w = (u32) d; - } - if ((argc >= 2) && JSVAL_IS_NUMBER(argv[1])) { - SMJS_GET_NUMBER(argv[1], d); - h = (u32) d; - } - if ((argc >= 3) && JSVAL_IS_BOOLEAN(argv[2]) && (JSVAL_TO_BOOLEAN(argv[2])==JS_TRUE) ) - override_size_info = 1; - - if (w && h) { - GF_Event evt; - if (override_size_info) { - term->compositor->scene_width = w; - term->compositor->scene_height = h; - term->compositor->has_size_info = 1; - return JS_TRUE; - } - if (term->user->os_window_handler) { - evt.type = GF_EVENT_SCENE_SIZE; - evt.size.width = w; - evt.size.height = h; - gf_term_send_event(term, &evt); - } else { - gf_sc_set_size(term->compositor, w, h); - } - } else if (override_size_info) { - term->compositor->has_size_info = 0; - term->compositor->recompute_ar = 1; - } - - return JS_TRUE; -} - - -static JSBool SMJS_FUNCTION(gpac_exit) -{ - GF_Event evt; - SMJS_OBJ - GF_Terminal *term = gpac_get_term(c, obj); - memset(&evt, 0, sizeof(GF_Event)); - evt.type = GF_EVENT_QUIT; - gf_term_send_event(term, &evt); - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gpac_set_3d) -{ - u32 type_3d = 0; - SMJS_OBJ - SMJS_ARGS - GF_Terminal *term = gpac_get_term(c, obj); - if (argc && JSVAL_IS_INT(argv[0])) type_3d = JSVAL_TO_INT(argv[0]); - if (term->compositor->inherit_type_3d != type_3d) { - term->compositor->inherit_type_3d = type_3d; - term->compositor->root_visual_setup = 0; - gf_sc_reset_graphics(term->compositor); - } - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gpac_move_window) -{ - SMJS_OBJ - SMJS_ARGS - GF_Event evt; - GF_Terminal *term = gpac_get_term(c, obj); - if (argc < 2) return JS_TRUE; - if (!JSVAL_IS_INT(argv[0]) || !JSVAL_IS_INT(argv[1])) return JS_TRUE; - - evt.type = GF_EVENT_MOVE; - evt.move.relative = 1; - evt.move.x = JSVAL_TO_INT(argv[0]); - evt.move.y = JSVAL_TO_INT(argv[1]); - term->compositor->video_out->ProcessEvent(term->compositor->video_out, &evt); - - return JS_TRUE; -} - - -static JSBool SMJS_FUNCTION(gpac_error_string) -{ - const char *str; - SMJS_ARGS - - if (argc < 1) return JS_TRUE; - if (!JSVAL_IS_INT(argv[0]) ) return JS_TRUE; - str = gf_error_to_string(JSVAL_TO_INT(argv[0])); - - SMJS_SET_RVAL( STRING_TO_JSVAL(JS_NewStringCopyZ(c, str)) ); - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gpac_get_scene_time) -{ - SMJS_OBJ - SMJS_ARGS - GF_SceneGraph *sg = NULL; - GF_Terminal *term = gpac_get_term(c, obj); - if (!argc || !JSVAL_IS_OBJECT(argv[0])) { - sg = term->root_scene->graph; - } else { - GF_Node *n = gf_sg_js_get_node(c, JSVAL_TO_OBJECT(argv[0])); - sg = n ? n->sgprivate->scenegraph : term->root_scene->graph; - } - SMJS_SET_RVAL( DOUBLE_TO_JSVAL( JS_NewDouble(c, sg->GetSceneTime(sg->userpriv) ) ) ); - - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gpac_trigger_gc) -{ - SMJS_OBJ - GF_SceneGraph *sg = NULL; - GF_Terminal *term = gpac_get_term(c, obj); - - sg = term->root_scene->graph; - sg->trigger_gc = GF_TRUE; - return JS_TRUE; -} - - -static JSBool SMJS_FUNCTION(gpac_migrate_url) -{ - char *url; - u32 i, count; - GF_NetworkCommand com; - SMJS_OBJ - SMJS_ARGS - GF_Terminal *term = gpac_get_term(c, obj); - if (!argc || !JSVAL_IS_STRING(argv[0])) return JS_FALSE; - - url = SMJS_CHARS(c, argv[0]); - if (!url) return JS_FALSE; - - count = gf_list_count(term->root_scene->resources); - for (i=0; i<count; i++) { - GF_ObjectManager *odm = gf_list_get(term->root_scene->resources, i); - if (!odm->net_service) continue; - if (!strstr(url, odm->net_service->url)) continue; - - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.command_type = GF_NET_SERVICE_MIGRATION_INFO; - odm->net_service->ifce->ServiceCommand(odm->net_service->ifce, &com); - if (com.migrate.data) { - SMJS_SET_RVAL( STRING_TO_JSVAL(JS_NewStringCopyZ(c, com.migrate.data)) ); - } else { - SMJS_SET_RVAL( STRING_TO_JSVAL(JS_NewStringCopyZ(c, odm->net_service->url)) ); - } - break; - } - SMJS_FREE(c, url); - return JS_TRUE; -} - -static SMJS_FUNC_PROP_GET( odm_getProperty) - -GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj); -GF_MediaInfo odi; -s32 prop_id; -char *str; -if (!SMJS_ID_IS_INT(id)) return JS_TRUE; - -prop_id = SMJS_ID_TO_INT(id); -gf_term_get_object_info(odm->term, odm, &odi); - -switch (prop_id) { -case GJS_OM_PROP_ID: - *vp = INT_TO_JSVAL(odi.od->objectDescriptorID); - break; -case GJS_OM_PROP_NB_RES: - *vp = INT_TO_JSVAL(odm->subscene ? gf_list_count(odm->subscene->resources) : 0); - break; -case GJS_OM_PROP_URL: - *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, odi.service_url)); - break; -case GJS_OM_PROP_DUR: - *vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, odi.duration) ); - break; -case GJS_OM_PROP_CLOCK: - *vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, odi.current_time) ); - break; -case GJS_OM_PROP_DRIFT: - *vp = INT_TO_JSVAL( odi.clock_drift); - break; -case GJS_OM_PROP_STATUS: - if (odi.status==0) str = "Stopped"; - else if (odi.status==1) str = "Playing"; - else if (odi.status==2) str = "Paused"; - else if (odi.status==3) str = "Not Setup"; - else str = "Setup Failed"; - *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, str)); - break; -case GJS_OM_PROP_BUFFER: - *vp = INT_TO_JSVAL( odi.buffer); - break; -case GJS_OM_PROP_DB_COUNT: - *vp = INT_TO_JSVAL( odi.db_unit_count); - break; -case GJS_OM_PROP_CB_COUNT: - *vp = INT_TO_JSVAL( odi.cb_unit_count); - break; -case GJS_OM_PROP_CB_CAP: - *vp = INT_TO_JSVAL( odi.cb_max_count); - break; -case GJS_OM_PROP_TYPE: - if (odi.od_type==GF_STREAM_SCENE) str = "Scene"; - else if (odi.od_type==GF_STREAM_OD) str = "Object Descriptor"; - else if (odi.od_type==GF_STREAM_VISUAL) str = "Video"; - else if (odi.od_type==GF_STREAM_AUDIO) str = "Audio"; - else if (odi.od_type==GF_STREAM_TEXT) str = "Text"; - else if (odm->subscene) str = "Subscene"; - else str = "Unknown"; - *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, str)); - break; -case GJS_OM_PROP_SAMPLERATE: - *vp = INT_TO_JSVAL( odi.sample_rate); - break; -case GJS_OM_PROP_CHANNELS: - *vp = INT_TO_JSVAL( odi.num_channels); - break; -case GJS_OM_PROP_LANG: - *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, odi.lang_code ? odi.lang_code : gf_4cc_to_str(odi.lang) ) ); - break; -case GJS_OM_PROP_WIDTH: - *vp = INT_TO_JSVAL( odi.width); - break; -case GJS_OM_PROP_HEIGHT: - *vp = INT_TO_JSVAL( odi.height); - break; -case GJS_OM_PROP_PIXELFORMAT: - *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, gf_4cc_to_str(odi.pixelFormat) ) ); - break; -case GJS_OM_PROP_PAR: - if (odi.par) { - char szPar[50]; - sprintf(szPar, "%d:%d", (odi.par>>16)&0xFF, (odi.par)&0xFF ); - *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, szPar ) ); - } else { - *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, "1:1" ) ); - } - break; -case GJS_OM_PROP_DEC_FRAMES: - *vp = INT_TO_JSVAL(odi.nb_dec_frames); - break; -case GJS_OM_PROP_DROP_FRAMES: - *vp = INT_TO_JSVAL(odi.nb_dropped); - break; -case GJS_OM_PROP_DEC_TIME_MAX: - *vp = INT_TO_JSVAL(odi.max_dec_time); - break; -case GJS_OM_PROP_DEC_TIME_TOTAL: - *vp = INT_TO_JSVAL(odi.total_dec_time); - break; -case GJS_OM_PROP_AVG_RATE: - *vp = INT_TO_JSVAL(odi.avg_bitrate); - break; -case GJS_OM_PROP_MAX_RATE: - *vp = INT_TO_JSVAL(odi.max_bitrate); - break; -case GJS_OM_PROP_SERVICE_HANDLER: - *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, odi.service_handler)); - break; -case GJS_OM_PROP_CODEC: - *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, odi.codec_name)); - break; -case GJS_OM_PROP_NB_QUALITIES: -{ - //first check network - GF_NetworkCommand com; - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.command_type = GF_NET_SERVICE_QUALITY_QUERY; - com.base.on_channel = gf_list_get(odm->channels, 0); - - gf_term_service_command(odm->net_service, &com); - if (com.quality_query.index) { - *vp = INT_TO_JSVAL(com.quality_query.index); - break; - } -#if 0 - //use input channels - if (odm->codec->type==GF_STREAM_VISUAL) { - u32 count = gf_list_count(odm->codec->inChannels); - if (count>1) { - *vp = INT_TO_JSVAL(count); - break; - } - } - //use number of scalable addons - if (odm->upper_layer_odm) { - u32 count = 0; - while (odm) { - odm = odm->upper_layer_odm; - count++; - } - *vp = INT_TO_JSVAL(count); - break; - } -#endif - *vp = INT_TO_JSVAL(1); - break; -} -case GJS_OM_PROP_MAX_BUFFER: - *vp = INT_TO_JSVAL(odi.max_buffer); - break; -case GJS_OM_PROP_MIN_BUFFER: - *vp = INT_TO_JSVAL(odi.min_buffer); - break; -case GJS_OM_PROP_FRAME_DUR: - *vp = INT_TO_JSVAL(odi.au_duration); - break; -case GJS_OM_PROP_NB_IRAP: - *vp = INT_TO_JSVAL(odi.nb_iraps); - break; -case GJS_OM_PROP_IRAP_DEC_TIME: - *vp = INT_TO_JSVAL(odi.irap_total_dec_time); - break; -case GJS_OM_PROP_IRAP_MAX_TIME: - *vp = INT_TO_JSVAL(odi.irap_max_dec_time); - break; -case GJS_OM_PROP_SERVICE_ID: - *vp = INT_TO_JSVAL(odi.od ? odi.od->ServiceID : 0); - break; -case GJS_OM_PROP_SELECTED_SERVICE: - *vp = INT_TO_JSVAL( (!odm->addon && odm->subscene) ? odm->subscene->selected_service_id : odm->parentscene->selected_service_id); - break; -case GJS_OM_PROP_BANDWIDTH_DOWN: -{ - GF_NetworkCommand com; - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.command_type = GF_NET_GET_STATS; - com.base.on_channel = gf_list_get(odm->channels, 0); - gf_term_service_command(odm->net_service, &com); - *vp = INT_TO_JSVAL(com.net_stats.bw_down/1000); -} -break; -case GJS_OM_PROP_NB_HTTP: - *vp = INT_TO_JSVAL(gf_list_count(odm->net_service->dnloads) ); - break; -case GJS_OM_PROP_TIMESHIFT_DEPTH: - if ((s32) odm->timeshift_depth > 0) { - *vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, ((Double) odm->timeshift_depth) / 1000.0 ) ); - } else { - *vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, 0.0) ); - } - break; -case GJS_OM_PROP_TIMESHIFT_TIME: -{ - GF_NetworkCommand com; - GF_Scene *scene; - Double res = 0.0; - - if (!odm->timeshift_depth) { - *vp = INT_TO_JSVAL(0); - break; - } - - scene = odm->subscene ? odm->subscene : odm->parentscene; - - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.command_type = GF_NET_GET_TIMESHIFT; - - //we may need to check the main addon for timeshifting - if (scene->main_addon_selected) { - u32 i, count = gf_list_count(scene->resources); - for (i=0; i < count; i++) { - GF_ObjectManager *an_odm = gf_list_get(scene->resources, i); - if (an_odm && an_odm->addon && (an_odm->addon->addon_type==GF_ADDON_TYPE_MAIN)) { - odm = an_odm; - } - } - } - - if (odm->timeshift_depth) { - //can be NULL - com.base.on_channel = gf_list_get(odm->channels, 0); - gf_term_service_command(odm->net_service, &com); - res = com.timeshift.time; - } else if (scene->main_addon_selected) { - GF_Clock *ck; - ck = scene->dyn_ck; - if (scene->scene_codec) ck = scene->scene_codec->ck; - if (ck) { - u32 now = gf_clock_time(scene->dyn_ck) ; - u32 live = scene->obj_clock_at_main_activation + gf_sys_clock() - scene->sys_clock_at_main_activation; - res = ((Double) live) / 1000.0; - res -= ((Double) now) / 1000.0; - - if (res<0) { - GF_Event evt; - memset(&evt, 0, sizeof(evt)); - evt.type = GF_EVENT_TIMESHIFT_UNDERRUN; - gf_term_send_event(odm->term, &evt); - res=0; - } else if (res && res*1000 > scene->timeshift_depth) { - GF_Event evt; - memset(&evt, 0, sizeof(evt)); - evt.type = GF_EVENT_TIMESHIFT_OVERFLOW; - gf_term_send_event(odm->term, &evt); - res=scene->timeshift_depth; - res/=1000; - } - } - } - *vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, res) ); -} -break; -case GJS_OM_PROP_IS_ADDON: - *vp = BOOLEAN_TO_JSVAL( (odm->addon || (!odm->subscene && odm->parentscene->root_od->addon)) ? JS_TRUE : JS_FALSE); - break; -case GJS_OM_PROP_MAIN_ADDON_ON: -{ - GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; - *vp = BOOLEAN_TO_JSVAL( scene->main_addon_selected ? JS_TRUE : JS_FALSE); -} -break; -case GJS_OM_PROP_IS_OVER: -{ - GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; - *vp = BOOLEAN_TO_JSVAL( gf_sc_is_over(odm->term->compositor, scene->graph) ? JS_TRUE : JS_FALSE); -} -break; -case GJS_OM_PROP_IS_PULLING: -{ - GF_Channel *ch = gf_list_get(odm->channels, 0); - *vp = BOOLEAN_TO_JSVAL((ch && ch->is_pulling) ? JS_TRUE : JS_FALSE); -} -break; -case GJS_OM_PROP_DYNAMIC_SCENE: - *vp = BOOLEAN_TO_JSVAL(odm->subscene && odm->subscene->is_dynamic_scene ? JS_TRUE : JS_FALSE); - break; -case GJS_OM_PROP_SERVICE_NAME: -{ - GF_NetworkCommand com; - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.command_type = GF_NET_SERVICE_INFO; - com.info.service_id = odi.od->ServiceID; - gf_term_service_command(odm->net_service, &com); - if (com.info.name) { - *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, com.info.name ) ); - } else { - *vp = JSVAL_NULL; - } -} -break; -case GJS_OM_PROP_NTP_DIFF: - *vp = INT_TO_JSVAL( odi.ntp_diff); - break; - -case GJS_OM_PROP_MAIN_ADDON_URL: -{ - GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; - u32 i, count = gf_list_count(scene->resources); - *vp = JSVAL_NULL; - for (i=0; i < count; i++) { - GF_ObjectManager *an_odm = gf_list_get(scene->resources, i); - if (an_odm && an_odm->addon && (an_odm->addon->addon_type==GF_ADDON_TYPE_MAIN)) { - if (!strstr(an_odm->addon->url, "://")) { - char szURL[GF_MAX_PATH]; - sprintf(szURL, "gpac://%s", an_odm->addon->url); - *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, szURL) ); - } else { - *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, an_odm->addon->url) ); - } - } - } -} -break; -case GJS_OM_PROP_REVERSE_PLAYBACK: -{ - u32 i, count; - GF_Err e; - GF_NetworkCommand com; - GF_Scene *scene; - - scene = odm->subscene ? odm->subscene : odm->parentscene; - - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.command_type = GF_NET_SERVICE_CAN_REVERSE_PLAYBACK; - - //we may need to check the main addon for timeshifting - count = gf_list_count(scene->resources); - for (i=0; i < count; i++) { - GF_ObjectManager *an_odm = gf_list_get(scene->resources, i); - if (an_odm && an_odm->addon && (an_odm->addon->addon_type==GF_ADDON_TYPE_MAIN)) { - odm = an_odm; - } - } - - //can be NULL - com.base.on_channel = gf_list_get(odm->channels, 0); - e = gf_term_service_command(odm->net_service, &com); - *vp = BOOLEAN_TO_JSVAL((e==GF_OK) ? GF_TRUE : GF_FALSE ); -} -break; -case GJS_OM_PROP_SCALABLE_ENHANCEMENT: - *vp = BOOLEAN_TO_JSVAL(odm && (odm->lower_layer_odm || odm->scalable_addon) ? JS_TRUE : JS_FALSE); - break; -case GJS_OM_PROP_MAIN_ADDON_MEDIATIME: -{ - GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; - u32 i, count = gf_list_count(scene->resources); - - *vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, -1) ); - if (! scene->main_addon_selected) break; - - for (i=0; i < count; i++) { - GF_ObjectManager *an_odm = gf_list_get(scene->resources, i); - if (an_odm && an_odm->addon && (an_odm->addon->addon_type==GF_ADDON_TYPE_MAIN)) { - if (an_odm->duration) { - Double now = gf_clock_time(scene->dyn_ck) / 1000.0; - now -= ((Double) an_odm->addon->media_pts) / 90000.0; - now += ((Double) an_odm->addon->media_timestamp) / an_odm->addon->media_timescale; - *vp = DOUBLE_TO_JSVAL(JS_NewDouble(c, now) ); - } - } - } -} -break; - -case GJS_OM_PROP_DEPENDENT_GROUPS: -{ - GF_NetworkCommand com; - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.command_type = GF_NET_SERVICE_QUALITY_QUERY; - com.base.on_channel = gf_list_get(odm->channels, 0); - - gf_term_service_command(odm->net_service, &com); - if (com.quality_query.index) { - *vp = INT_TO_JSVAL(com.quality_query.dependent_group_index); - } else { - *vp = INT_TO_JSVAL(0); - } - break; -} - -case GJS_OM_PROP_IS_VR_SCENE: - *vp = BOOLEAN_TO_JSVAL(odm->subscene && odm->subscene->vr_type ? JS_TRUE : JS_FALSE); - break; -} -return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gjs_odm_get_quality) -{ - GF_NetworkCommand com; - SMJS_OBJ - SMJS_ARGS - GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj); - s32 idx, dep_idx=0; - - if (!odm) return JS_TRUE; - if (argc<1) return JS_TRUE; - if (! JSVAL_IS_INT(argv[0]) ) return JS_TRUE; - idx = JSVAL_TO_INT(argv[0]); - if (argc>=2) dep_idx = JSVAL_TO_INT(argv[1]); - - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.command_type = GF_NET_SERVICE_QUALITY_QUERY; - com.quality_query.index = 1 + idx; - com.quality_query.dependent_group_index = dep_idx; - - com.base.on_channel = gf_list_get(odm->channels, 0); - - if (gf_term_service_command(odm->net_service, &com) == GF_OK) { - JSObject *a = JS_NewObject(c, NULL, NULL, NULL); - - JS_DefineProperty(c, a, "ID", STRING_TO_JSVAL(JS_NewStringCopyZ(c, com.quality_query.ID ? com.quality_query.ID : "")) , 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "mime", STRING_TO_JSVAL(JS_NewStringCopyZ(c, com.quality_query.mime ? com.quality_query.mime : "")) , 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "codec", STRING_TO_JSVAL(JS_NewStringCopyZ(c, com.quality_query.codec ? com.quality_query.codec : "")) , 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "width", INT_TO_JSVAL(com.quality_query.width), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "height", INT_TO_JSVAL(com.quality_query.height), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "bandwidth", INT_TO_JSVAL(com.quality_query.bandwidth), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "interlaced", BOOLEAN_TO_JSVAL(com.quality_query.interlaced), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "fps", DOUBLE_TO_JSVAL( JS_NewDouble(c, com.quality_query.fps) ), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "samplerate", INT_TO_JSVAL(com.quality_query.sample_rate), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "channels", INT_TO_JSVAL(com.quality_query.nb_channels), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "par_num", INT_TO_JSVAL(com.quality_query.par_num), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "par_den", INT_TO_JSVAL(com.quality_query.par_den), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "disabled", BOOLEAN_TO_JSVAL(com.quality_query.disabled), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "is_selected", BOOLEAN_TO_JSVAL(com.quality_query.is_selected), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "automatic", BOOLEAN_TO_JSVAL(com.quality_query.automatic), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "tile_mode", INT_TO_JSVAL(com.quality_query.tile_adaptation_mode), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "dependent_groups", INT_TO_JSVAL(com.quality_query.dependent_group_index), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - - - SMJS_SET_RVAL( OBJECT_TO_JSVAL(a) ); - } else { - SMJS_SET_RVAL(JSVAL_NULL); - } - - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gjs_odm_get_srd) -{ - GF_NetworkCommand com; - SMJS_OBJ - SMJS_ARGS - GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj); - s32 dep_idx=0; - s32 x, y, w, h; - - if (!odm) return JS_TRUE; - - x = y = w = h = 0; - if (argc && JSVAL_IS_INT(argv[0]) ) { - dep_idx = JSVAL_TO_INT(argv[0]); - - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.command_type = GF_NET_CHAN_GET_SRD; - com.base.on_channel = gf_list_get(odm->channels, 0); - com.srd.dependent_group_index = dep_idx; - - if (gf_term_service_command(odm->net_service, &com) == GF_OK) { - x = com.srd.x; - y = com.srd.y; - w = com.srd.w; - h = com.srd.h; - } - } else if (odm && odm->mo && odm->mo->srd_w && odm->mo->srd_h) { - x = odm->mo->srd_x; - y = odm->mo->srd_y; - w = odm->mo->srd_w; - h = odm->mo->srd_h; - } - - if (w && h) { - JSObject *a = JS_NewObject(c, NULL, NULL, NULL); - - JS_DefineProperty(c, a, "x", INT_TO_JSVAL(x), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "y", INT_TO_JSVAL(y), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "w", INT_TO_JSVAL(w), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, a, "h", INT_TO_JSVAL(h), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - SMJS_SET_RVAL( OBJECT_TO_JSVAL(a) ); - } else { - SMJS_SET_RVAL(JSVAL_NULL); - } - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gjs_odm_select_quality) -{ - GF_NetworkCommand com; - char *ID = NULL; - s32 tile_mode = -1; - u32 dep_idx = 0; - SMJS_OBJ - SMJS_ARGS - GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj); - - if (!odm) return JS_TRUE; - - if ((argc>=1) && JSVAL_IS_STRING(argv[0])) { - ID = SMJS_CHARS(c, argv[0]); - if ((argc>=2) && JSVAL_IS_INT(argv[1])) { - dep_idx = JSVAL_TO_INT( argv[1]); - } - } - if ((argc==1) && JSVAL_IS_INT(argv[0])) { - tile_mode = JSVAL_TO_INT( argv[0]); - } - if (!ID && (tile_mode<0) ) { - return JS_TRUE; - } - - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.command_type = GF_NET_SERVICE_QUALITY_SWITCH; - com.base.on_channel = gf_list_get(odm->channels, 0); - - if (tile_mode>=0) { - com.switch_quality.set_tile_mode_plus_one = 1 + tile_mode; - } else { - com.switch_quality.dependent_group_index = dep_idx; - if (!strcmp(ID, "auto")) { - com.switch_quality.set_auto = 1; - } else { - com.switch_quality.ID = ID; - } - } - - gf_term_service_command(odm->net_service, &com); - - SMJS_FREE(c, ID); - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gjs_odm_disable_main_addon) -{ - SMJS_OBJ - GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj); - - if (!odm || !odm->subscene || !odm->subscene->main_addon_selected) return JS_TRUE; - - gf_scene_resume_live(odm->subscene); - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gjs_odm_select_service) -{ - u32 sid; - SMJS_OBJ - SMJS_ARGS - GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj); - - if (!odm) return JS_TRUE; - if (argc<1) return JS_TRUE; - if (! JSVAL_IS_INT(argv[0]) ) return JS_TRUE; - sid = JSVAL_TO_INT(argv[0]); - - gf_term_select_service(odm->term, odm->subscene ? odm : odm->parentscene->root_od, sid); - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gjs_odm_select) -{ - SMJS_OBJ - GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj); - - if (!odm) return JS_TRUE; - -#ifndef GPAC_DISABLE_PLAYER - gf_scene_select_object(odm->parentscene, odm); -#endif - return JS_TRUE; -} - - -static JSBool SMJS_FUNCTION(gjs_odm_get_resource) -{ - GF_ObjectManager *an_odm = NULL; - u32 idx; - SMJS_OBJ - SMJS_ARGS - GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj); - - if (!odm) return JS_TRUE; - if (argc<1) return JS_TRUE; - if (! JSVAL_IS_INT(argv[0]) ) return JS_TRUE; - idx = JSVAL_TO_INT(argv[0]); - - if (odm->subscene) { - an_odm = gf_list_get(odm->subscene->resources, idx); - } - if (an_odm && an_odm->net_service) { - JSObject *anobj; - JSClass *_class = JS_GET_CLASS(c, obj); - anobj = JS_NewObject(c, _class, 0, 0); - SMJS_SET_PRIVATE(c, anobj, an_odm); - SMJS_SET_RVAL( OBJECT_TO_JSVAL(anobj) ); - } else { - SMJS_SET_RVAL(JSVAL_NULL); - } - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gjs_odm_addon_layout) -{ - u32 pos, size; - SMJS_OBJ - SMJS_ARGS - GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj); - - if (argc<2) return JS_TRUE; - if (! JSVAL_IS_INT(argv[0]) ) return JS_TRUE; - if (! JSVAL_IS_INT(argv[1]) ) return JS_TRUE; - pos = JSVAL_TO_INT(argv[0]); - size = JSVAL_TO_INT(argv[1]); - - gf_scene_set_addon_layout_info(odm->subscene, pos, size); - return JS_TRUE; -} - -static void do_enable_addon(GF_ObjectManager *odm, char *addon_url, Bool enable_if_defined, Bool disable_if_defined ) -{ - GF_AssociatedContentLocation addon_info; - memset(&addon_info, 0, sizeof(GF_AssociatedContentLocation)); - addon_info.external_URL = addon_url; - addon_info.timeline_id = -100; - addon_info.enable_if_defined = enable_if_defined; - addon_info.disable_if_defined = disable_if_defined; - gf_scene_register_associated_media(odm->subscene ? odm->subscene : odm->parentscene, &addon_info); -} - -static JSBool SMJS_FUNCTION(gjs_odm_enable_addon) -{ - Bool do_disable = GF_FALSE; - char *addon_url = NULL; - SMJS_OBJ - SMJS_ARGS - GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj); - - if (! JSVAL_IS_STRING(argv[0]) ) return JS_TRUE; - if ((argc==2) && JSVAL_IS_BOOLEAN(argv[1])) { - do_disable = (Bool) JSVAL_TO_BOOLEAN(argv[1]); - } - - - addon_url = SMJS_CHARS(c, argv[0]); - if (addon_url) { - do_enable_addon(odm, addon_url, GF_TRUE, do_disable); - } - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gjs_odm_declare_addon) -{ - char *addon_url = NULL; - SMJS_OBJ - SMJS_ARGS - GF_ObjectManager *odm = (GF_ObjectManager *)SMJS_GET_PRIVATE(c, obj); - - if (! JSVAL_IS_STRING(argv[0]) ) return JS_TRUE; - - addon_url = SMJS_CHARS(c, argv[0]); - if (addon_url) { - do_enable_addon(odm, addon_url, GF_FALSE, GF_FALSE); - } - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gpac_get_object_manager) -{ - JSObject *anobj; - u32 i, count; - GF_ObjectManager *odm = NULL; - char *service_url = NULL; - SMJS_OBJ - SMJS_ARGS - GF_GPACJSExt *gjs = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj); - GF_Terminal *term = gpac_get_term(c, obj); - GF_Scene *scene = term->root_scene; - - if (JSVAL_IS_STRING(argv[0]) ) { - char *url, *an_url; - u32 url_len; - url = service_url = SMJS_CHARS(c, argv[0]); - if (!service_url) { - SMJS_SET_RVAL(JSVAL_NULL); - return JS_TRUE; - } - if (!strncmp(service_url, "gpac://", 7)) url = service_url + 7; - if (!strncmp(service_url, "file://", 7)) url = service_url + 7; - url_len = (u32) strlen(url); - an_url = strchr(url, '#'); - if (an_url) url_len -= (u32) strlen(an_url); - - count = gf_list_count(scene->resources); - for (i=0; i<count; i++) { - odm = gf_list_get(scene->resources, i); - if (odm->net_service) { - an_url = odm->net_service->url; - if (!strncmp(an_url, "gpac://", 7)) an_url = an_url + 7; - if (!strncmp(an_url, "file://", 7)) an_url = an_url + 7; - if (!strncmp(an_url, url, url_len)) - break; - } - odm = NULL; - } - } - - SMJS_FREE(c, service_url); - - if (!odm) { - SMJS_SET_RVAL(JSVAL_NULL); - return JS_TRUE; - } - - anobj = JS_NewObject(c, &gjs->odmClass._class, 0, 0); - SMJS_SET_PRIVATE(c, anobj, odm); - - SMJS_SET_RVAL( OBJECT_TO_JSVAL(anobj) ); - return JS_TRUE; -} - - -static JSBool SMJS_FUNCTION(gpac_new_storage) -{ - char szFile[GF_MAX_PATH]; - JSObject *anobj; - GF_Config *storage = NULL; - char *storage_url = NULL; - u8 hash[20]; - char temp[3]; - SMJS_OBJ - SMJS_ARGS - GF_GPACJSExt *gjs = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj); - - if (JSVAL_IS_STRING(argv[0]) ) { - u32 i, count; - storage_url = SMJS_CHARS(c, argv[0]); - if (!storage_url) { - SMJS_SET_RVAL(JSVAL_NULL); - return JS_TRUE; - } - - szFile[0]=0; - gf_sha1_csum((u8 *)storage_url, (u32) strlen(storage_url), hash); - for (i=0; i<20; i++) { - sprintf(temp, "%02X", hash[i]); - strcat(szFile, temp); - } - strcat(szFile, ".cfg"); - - count = gf_list_count(gjs->storages); - for (i=0; i<count; i++) { - GF_Config *a_cfg = gf_list_get(gjs->storages, i); - char *cfg_name = gf_cfg_get_filename(a_cfg); - - if (strstr(cfg_name, szFile)) { - storage = a_cfg; - gf_free(cfg_name); - break; - } - gf_free(cfg_name); - } - - if (!storage) { - const char *storage_dir = gf_cfg_get_key(gjs->term->user->config, "General", "StorageDirectory"); - - storage = gf_cfg_force_new(storage_dir, szFile); - if (storage) { - gf_cfg_set_key(storage, "GPAC", "StorageURL", storage_url); - gf_list_add(gjs->storages, storage); - } - } - - SMJS_FREE(c, storage_url); - } - - if (!storage) { - SMJS_SET_RVAL(JSVAL_NULL); - return JS_TRUE; - } - anobj = JS_NewObject(c, &gjs->storageClass._class, 0, 0); - SMJS_SET_PRIVATE(c, anobj, storage); - - - SMJS_SET_RVAL( OBJECT_TO_JSVAL(anobj) ); - return JS_TRUE; -} - -static SMJS_FUNC_PROP_GET( gpacevt_getProperty) - -GF_GPACJSExt *gjs = SMJS_GET_PRIVATE(c, obj); -GF_Event *evt = gjs->evt; -if (!evt) return 0; - -if (SMJS_ID_IS_INT(id)) { - switch (SMJS_ID_TO_INT(id)) { - case GJS_EVT_PROP_KEYCODE: -#ifndef GPAC_DISABLE_SVG - *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, gf_dom_get_key_name(evt->key.key_code) )); -#endif - break; - case GJS_EVT_PROP_MOUSE_X: - *vp = INT_TO_JSVAL(evt->mouse.x); - break; - case GJS_EVT_PROP_MOUSE_Y: - *vp = INT_TO_JSVAL(evt->mouse.y); - break; - case GJS_EVT_PROP_PICKED: - if (gjs->term->compositor->hit_appear) *vp = BOOLEAN_TO_JSVAL(JS_TRUE); - else if (gf_list_count(gjs->term->compositor->previous_sensors) ) *vp = BOOLEAN_TO_JSVAL(JS_TRUE); - else if (gjs->term->compositor->text_selection) *vp = BOOLEAN_TO_JSVAL(JS_TRUE); - else *vp = BOOLEAN_TO_JSVAL(JS_FALSE); - break; - case GJS_EVT_PROP_WHEEL: - *vp = DOUBLE_TO_JSVAL( JS_NewDouble(c, FIX2FLT(evt->mouse.wheel_pos)) ); - break; - case GJS_EVT_PROP_BUTTON: - *vp = INT_TO_JSVAL( evt->mouse.button); - break; - case GJS_EVT_PROP_TYPE: - *vp = INT_TO_JSVAL(evt->type); - break; - case GJS_EVT_PROP_NAME: -#ifndef GPAC_DISABLE_SVG - *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(c, gf_dom_event_get_name(evt->type) )); -#endif - break; - case GJS_EVT_PROP_HWKEY: - *vp = INT_TO_JSVAL(evt->key.hw_code); - break; - } -} else if (SMJS_ID_IS_STRING(id)) { - char *name = SMJS_CHARS_FROM_STRING(c, SMJS_ID_TO_STRING(id)); - if (!strcmp(name, "target_url")) { - *vp = STRING_TO_JSVAL( JS_NewStringCopyZ(c, evt->navigate.to_url) ); - } - else if (!strcmp(name, "files")) { - u32 i, idx; - jsval v; - JSObject *files_array = JS_NewArrayObject(c, 0, NULL); - for (i=0; i<evt->open_file.nb_files; i++) { - if (evt->open_file.files[i]) { - JS_GetArrayLength(c, files_array, &idx); - v = STRING_TO_JSVAL( JS_NewStringCopyZ(c, evt->open_file.files[i]) ); - JS_SetElement(c, files_array, idx, &v); - } - } - *vp = OBJECT_TO_JSVAL(files_array); - } - SMJS_FREE(c, name); -} - -return JS_TRUE; -} - -static Bool gjs_event_filter_process(GF_GPACJSExt *gjs, GF_Event *evt) -{ - Bool res; - jsval argv[1], rval; - rval = JSVAL_VOID; - - gjs->evt = evt; - SMJS_SET_PRIVATE(gjs->c, gjs->evt_obj, gjs); - argv[0] = OBJECT_TO_JSVAL(gjs->evt_obj); - rval = BOOLEAN_TO_JSVAL(JS_TRUE); - JS_CallFunctionValue(gjs->c, gjs->evt_filter_obj, gjs->evt_fun, 1, argv, &rval); - SMJS_SET_PRIVATE(gjs->c, gjs->evt_obj, NULL); - gjs->evt = NULL; - - res = 0; - if (JSVAL_IS_BOOLEAN(rval) ) { - res = (JSVAL_TO_BOOLEAN(rval)==JS_TRUE) ? GF_TRUE : GF_FALSE; - } else if (JSVAL_IS_INT(rval) ) { - res = (JSVAL_TO_INT(rval)) ? 1 : 0; - } - return res; -} - -static Bool gjs_event_filter(void *udta, GF_Event *evt, Bool consumed_by_compositor) -{ - u32 lock_fail; - Bool res; - GF_GPACJSExt *gjs = (GF_GPACJSExt *)udta; - if (consumed_by_compositor) return 0; - - if (gjs->evt != NULL) return 0; - - lock_fail=0; - res = gf_mx_try_lock(gjs->term->compositor->mx); - if (!res) { - lock_fail=1; - } else { - res = gf_sg_try_lock_javascript(gjs->c); - if (!res) lock_fail=2; - } - if (lock_fail) { - GF_Event *evt_clone; - gf_mx_p(gjs->event_mx); - evt_clone = gf_malloc(sizeof(GF_Event));; - memcpy(evt_clone, evt, sizeof(GF_Event)); - gf_list_add(gjs->event_queue, evt_clone); - GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[GPACJS] Couldn't lock % mutex, queing event\n", (lock_fail==2) ? "JavaScript" : "Compositor")); - gf_mx_v(gjs->event_mx); - - if (lock_fail==2){ - gf_mx_v(gjs->term->compositor->mx); - } - return 0; - } - - gf_mx_p(gjs->event_mx); - while (gf_list_count(gjs->event_queue)) { - GF_Event *an_evt = (GF_Event *) gf_list_pop_front(gjs->event_queue); - gjs_event_filter_process(gjs, an_evt); - gf_free(an_evt); - } - gf_mx_v(gjs->event_mx); - - res = gjs_event_filter_process(gjs, evt); - - gf_mx_v(gjs->term->compositor->mx); - gf_sg_lock_javascript(gjs->c, 0); - return res; -} - -static JSBool SMJS_FUNCTION(gpac_set_event_filter) -{ - SMJS_OBJ - SMJS_ARGS - GF_GPACJSExt *gjs = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj); - if (!argc || !JSVAL_IS_OBJECT(argv[0])) return JS_FALSE; - - if (! JSVAL_IS_NULL(gjs->evt_fun) ) return JS_TRUE; - - gjs->evt_fun = argv[0]; - gjs->evt_filter_obj = obj; - gjs->c = c; - gjs->evt_filter.udta = gjs; - gjs->evt_filter.on_event = gjs_event_filter; - gf_term_add_event_filter(gjs->term, &gjs->evt_filter); - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gpac_set_focus) -{ - GF_Node *elt; - SMJS_OBJ - SMJS_ARGS - GF_Terminal *term = gpac_get_term(c, obj); - if (!argc) return JS_FALSE; - - if (JSVAL_IS_NULL(argv[0])) { - gf_sc_focus_switch_ring(term->compositor, 0, NULL, 0); - return JS_TRUE; - } - - if (JSVAL_IS_STRING(argv[0])) { - char *focus_type = SMJS_CHARS(c, argv[0]); - if (!stricmp(focus_type, "previous")) { - gf_sc_focus_switch_ring(term->compositor, 1, NULL, 0); - } - else if (!stricmp(focus_type, "next")) { - gf_sc_focus_switch_ring(term->compositor, 0, NULL, 0); - } - SMJS_FREE(c, focus_type); - } else if (JSVAL_IS_OBJECT(argv[0])) { - elt = gf_sg_js_get_node(c, JSVAL_TO_OBJECT(argv[0])); - if (!elt) return JS_TRUE; - - gf_sc_focus_switch_ring(term->compositor, 0, elt, 2); - } - - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gpac_get_scene) -{ - GF_Node *elt; - u32 w, h; - JSObject *scene_obj; -#ifndef GPAC_DISABLE_SCENEGRAPH - GF_SceneGraph *sg; -#endif - GF_Scene *scene=NULL; - - SMJS_OBJ - SMJS_ARGS - GF_GPACJSExt *gjs = (GF_GPACJSExt *)SMJS_GET_PRIVATE(c, obj); - if (!gjs || !argc || !JSVAL_IS_OBJECT(argv[0])) return JS_FALSE; - - elt = gf_sg_js_get_node(c, JSVAL_TO_OBJECT(argv[0])); - if (!elt) return JS_TRUE; - switch (elt->sgprivate->tag) { -#ifndef GPAC_DISABLE_VRML - case TAG_MPEG4_Inline: - scene = (GF_Scene *)gf_node_get_private(elt); - break; -#endif - -#ifndef GPAC_DISABLE_X3D - case TAG_X3D_Inline: - scene = (GF_Scene *)gf_node_get_private(elt); - break; -#endif - -#ifndef GPAC_DISABLE_SVG - case TAG_SVG_animation: - sg = gf_sc_animation_get_scenegraph(elt); - scene = (GF_Scene *)gf_sg_get_private(sg); - break; -#endif - default: - return JS_TRUE; - } - if (!scene) return JS_TRUE; - - - scene_obj = JS_NewObject(c, &gjs->anyClass._class, 0, 0); - SMJS_SET_PRIVATE(c, scene_obj, scene); - gf_sg_get_scene_size_info(scene->graph, &w, &h); - JS_DefineProperty(c, scene_obj, "width", INT_TO_JSVAL(w), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, scene_obj, "height", INT_TO_JSVAL(h), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineProperty(c, scene_obj, "connected", BOOLEAN_TO_JSVAL(scene->graph ? JS_TRUE : JS_FALSE), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - SMJS_SET_RVAL( OBJECT_TO_JSVAL(scene_obj) ); - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gpac_show_keyboard) -{ - SMJS_OBJ - SMJS_ARGS - GF_Terminal *term = gpac_get_term(c, obj); - if (!argc) return JS_FALSE; - - if (JSVAL_IS_BOOLEAN(argv[0])) { - Bool show = JSVAL_TO_BOOLEAN(argv[0])==JS_TRUE; - GF_Event evt; - memset(&evt, 0, sizeof(GF_Event)); - evt.type = show ? GF_EVENT_TEXT_EDITING_START : GF_EVENT_TEXT_EDITING_END; - gf_term_user_event(term, &evt); - } - return JS_TRUE; -} - - -static JSBool SMJS_FUNCTION(gjs_storage_get_option) -{ - const char *opt = NULL; - char *sec_name, *key_name; - s32 idx = -1; - JSString *s; - SMJS_OBJ - SMJS_ARGS - GF_Config *config = (GF_Config *)SMJS_GET_PRIVATE(c, obj); - if (!config) return JS_FALSE; - - if (argc < 2) return JS_FALSE; - - if (!JSVAL_IS_STRING(argv[0])) return JS_FALSE; - if (!JSVAL_IS_STRING(argv[1]) && !JSVAL_IS_INT(argv[1])) return JS_FALSE; - - sec_name = SMJS_CHARS(c, argv[0]); - key_name = NULL; - if (JSVAL_IS_INT(argv[1])) { - idx = JSVAL_TO_INT(argv[1]); - } else if (JSVAL_IS_STRING(argv[1]) ) { - key_name = SMJS_CHARS(c, argv[1]); - } - - if (key_name) { - opt = gf_cfg_get_key(config, sec_name, key_name); - } else if (idx>=0) { - opt = gf_cfg_get_key_name(config, sec_name, idx); - } else { - opt = NULL; - } - if (key_name) { - SMJS_FREE(c, key_name); - } - SMJS_FREE(c, sec_name); - - if (opt) { - s = JS_NewStringCopyZ(c, opt); - SMJS_SET_RVAL( STRING_TO_JSVAL(s) ); - } else { - SMJS_SET_RVAL( JSVAL_NULL ); - } - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gjs_storage_set_option) -{ - char *sec_name, *key_name, *key_val; - SMJS_OBJ - SMJS_ARGS - GF_Config *config = (GF_Config *)SMJS_GET_PRIVATE(c, obj); - if (!config) return JS_FALSE; - - if (argc < 3) return JS_FALSE; - - if (!JSVAL_IS_STRING(argv[0])) return JS_FALSE; - if (!JSVAL_IS_STRING(argv[1])) return JS_FALSE; - - sec_name = SMJS_CHARS(c, argv[0]); - key_name = SMJS_CHARS(c, argv[1]); - key_val = NULL; - if (JSVAL_IS_STRING(argv[2])) - key_val = SMJS_CHARS(c, argv[2]); - - gf_cfg_set_key(config, sec_name, key_name, key_val); - - SMJS_FREE(c, sec_name); - SMJS_FREE(c, key_name); - if (key_val) { - SMJS_FREE(c, key_val); - } - return JS_TRUE; -} - -static JSBool SMJS_FUNCTION(gjs_storage_save) -{ - SMJS_OBJ - GF_Config *config = (GF_Config *)SMJS_GET_PRIVATE(c, obj); - if (!config) return JS_FALSE; - - gf_cfg_save(config); - return JS_TRUE; -} - -static void gjs_load(GF_JSUserExtension *jsext, GF_SceneGraph *scene, JSContext *c, JSObject *global, Bool unload) -{ - GF_GPACJSExt *gjs; - GF_JSAPIParam par; - - JSPropertySpec gpacEvtClassProps[] = { - SMJS_PROPERTY_SPEC("keycode", GJS_EVT_PROP_KEYCODE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("mouse_x", GJS_EVT_PROP_MOUSE_X, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("mouse_y", GJS_EVT_PROP_MOUSE_Y, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("picked", GJS_EVT_PROP_PICKED, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("wheel", GJS_EVT_PROP_WHEEL, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("button", GJS_EVT_PROP_BUTTON, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("type", GJS_EVT_PROP_TYPE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("name", GJS_EVT_PROP_NAME, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("hwkey", GJS_EVT_PROP_HWKEY, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC(0, 0, 0, 0, 0) - }; - JSFunctionSpec gpacEvtClassFuncs[] = { - SMJS_FUNCTION_SPEC(0, 0, 0) - }; - - JSPropertySpec gpacClassProps[] = { - - SMJS_PROPERTY_SPEC("last_working_directory", GJS_GPAC_PROP_LAST_WORK_DIR, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , 0, 0), - SMJS_PROPERTY_SPEC("scale_x", GJS_GPAC_PROP_SCALE_X, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("scale_y", GJS_GPAC_PROP_SCALE_Y, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("translation_x", GJS_GPAC_PROP_TRANSLATION_X, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("translation_y", GJS_GPAC_PROP_TRANSLATION_Y, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("rectangular_textures", GJS_GPAC_PROP_RECT_TEXTURES, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("batteryOn", GJS_GPAC_PROP_BATTERY_ON, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("batteryCharging", GJS_GPAC_PROP_BATTERY_CHARGE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("batteryPercent", GJS_GPAC_PROP_BATTERY_PERCENT, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("batteryLifeTime", GJS_GPAC_PROP_BATTERY_LIFETIME, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("batteryFullLifeTime", GJS_GPAC_PROP_BATTERY_LIFETIME_FULL, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("hostname", GJS_GPAC_PROP_HOSTNAME, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("fullscreen", GJS_GPAC_PROP_FULLSCREEN, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, 0, 0), - SMJS_PROPERTY_SPEC("current_path", GJS_GPAC_PROP_CURRENT_PATH, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("volume", GJS_GPAC_PROP_VOLUME, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , 0, 0), - SMJS_PROPERTY_SPEC("navigation", GJS_GPAC_PROP_NAVIGATION, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , 0, 0), - SMJS_PROPERTY_SPEC("navigation_type", GJS_GPAC_PROP_NAVIGATION_TYPE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , 0, 0), - SMJS_PROPERTY_SPEC("hardware_yuv", GJS_GPAC_PROP_HARDWARE_YUV, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("hardware_rgb", GJS_GPAC_PROP_HARDWARE_RGB, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("hardware_rgba", GJS_GPAC_PROP_HARDWARE_RGBA, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("hardware_stretch", GJS_GPAC_PROP_HARDWARE_STRETCH, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("screen_width", GJS_GPAC_PROP_SCREEN_WIDTH, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("screen_height", GJS_GPAC_PROP_SCREEN_HEIGHT, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("http_max_bitrate", GJS_GPAC_PROP_HTTP_MAX_RATE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED , 0, 0), - SMJS_PROPERTY_SPEC("http_bitrate", GJS_GPAC_PROP_HTTP_RATE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("fps", GJS_GPAC_PROP_FPS, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("sim_fps", GJS_GPAC_PROP_SIM_FPS, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("cpu_load", GJS_GPAC_PROP_CPU, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("cpu", GJS_GPAC_PROP_CPU, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("nb_cores", GJS_GPAC_PROP_NB_CORES, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("system_memory", GJS_GPAC_PROP_MEMORY_SYSTEM, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("memory", GJS_GPAC_PROP_MEMORY, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("argc", GJS_GPAC_PROP_ARGC, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("caption", GJS_GPAC_PROP_CAPTION, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, 0, 0), - SMJS_PROPERTY_SPEC("focus_highlight", GJS_GPAC_PROP_FOCUS_HIGHLIGHT, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, 0, 0), - SMJS_PROPERTY_SPEC("dpi_x", GJS_GPAC_PROP_DPI_X, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("dpi_y", GJS_GPAC_PROP_DPI_Y, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("sensors_active", GJS_GPAC_PROP_SENSORS_ACTIVE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED, 0, 0), - - SMJS_PROPERTY_SPEC(0, 0, 0, 0, 0) - }; - JSFunctionSpec gpacClassFuncs[] = { - SMJS_FUNCTION_SPEC("get_option", gpac_get_option, 3), - SMJS_FUNCTION_SPEC("set_option", gpac_set_option, 4), - SMJS_FUNCTION_SPEC("get_arg", gpac_get_arg, 1), - SMJS_FUNCTION_SPEC("enum_directory", gpac_enum_directory, 1), - SMJS_FUNCTION_SPEC("set_size", gpac_set_size, 1), - SMJS_FUNCTION_SPEC("exit", gpac_exit, 0), - SMJS_FUNCTION_SPEC("set_3d", gpac_set_3d, 1), - SMJS_FUNCTION_SPEC("move_window", gpac_move_window, 2), - SMJS_FUNCTION_SPEC("get_scene_time", gpac_get_scene_time, 1), - SMJS_FUNCTION_SPEC("migrate_url", gpac_migrate_url, 1), - SMJS_FUNCTION_SPEC("set_event_filter", gpac_set_event_filter, 1), - SMJS_FUNCTION_SPEC("set_focus", gpac_set_focus, 1), - SMJS_FUNCTION_SPEC("get_scene", gpac_get_scene, 1), - SMJS_FUNCTION_SPEC("error_string", gpac_error_string, 1), - SMJS_FUNCTION_SPEC("show_keyboard", gpac_show_keyboard, 1), - SMJS_FUNCTION_SPEC("trigger_gc", gpac_trigger_gc, 1), - SMJS_FUNCTION_SPEC("get_object_manager", gpac_get_object_manager, 1), - SMJS_FUNCTION_SPEC("new_storage", gpac_new_storage, 1), - SMJS_FUNCTION_SPEC("switch_quality", gpac_switch_quality, 1), - SMJS_FUNCTION_SPEC("reload", gpac_reload, 1), - SMJS_FUNCTION_SPEC("navigation_supported", gpac_navigation_supported, 1), - SMJS_FUNCTION_SPEC("set_back_color", gpac_set_back_color, 3), - - - SMJS_FUNCTION_SPEC(0, 0, 0) - }; - - JSPropertySpec odmClassProps[] = { - SMJS_PROPERTY_SPEC("ID", GJS_OM_PROP_ID, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("nb_resources", GJS_OM_PROP_NB_RES, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("service_url", GJS_OM_PROP_URL, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("duration", GJS_OM_PROP_DUR, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("clock_time", GJS_OM_PROP_CLOCK, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("clock_drift", GJS_OM_PROP_DRIFT, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("status", GJS_OM_PROP_STATUS, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("buffer", GJS_OM_PROP_BUFFER, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("db_unit_count", GJS_OM_PROP_DB_COUNT, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("cb_unit_count", GJS_OM_PROP_CB_COUNT, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("cb_capacity", GJS_OM_PROP_CB_CAP, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("type", GJS_OM_PROP_TYPE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("samplerate", GJS_OM_PROP_SAMPLERATE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("channels", GJS_OM_PROP_CHANNELS, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("lang", GJS_OM_PROP_LANG, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("width", GJS_OM_PROP_WIDTH, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("height", GJS_OM_PROP_HEIGHT, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("pixelformt", GJS_OM_PROP_PIXELFORMAT, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("par", GJS_OM_PROP_PAR, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("dec_frames", GJS_OM_PROP_DEC_FRAMES, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("drop_frames", GJS_OM_PROP_DROP_FRAMES, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("max_dec_time", GJS_OM_PROP_DEC_TIME_MAX, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("total_dec_time", GJS_OM_PROP_DEC_TIME_TOTAL, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("avg_bitrate", GJS_OM_PROP_AVG_RATE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("max_bitrate", GJS_OM_PROP_MAX_RATE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("service_handler", GJS_OM_PROP_SERVICE_HANDLER, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("codec", GJS_OM_PROP_CODEC, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("nb_qualities", GJS_OM_PROP_NB_QUALITIES, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("max_buffer", GJS_OM_PROP_MAX_BUFFER, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("min_buffer", GJS_OM_PROP_MIN_BUFFER, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("frame_duration", GJS_OM_PROP_FRAME_DUR, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("irap_frames", GJS_OM_PROP_NB_IRAP, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("irap_dec_time", GJS_OM_PROP_IRAP_DEC_TIME, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("irap_max_time", GJS_OM_PROP_IRAP_MAX_TIME, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("service_id", GJS_OM_PROP_SERVICE_ID, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("selected_service", GJS_OM_PROP_SELECTED_SERVICE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("bandwidth_down", GJS_OM_PROP_BANDWIDTH_DOWN, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("nb_http", GJS_OM_PROP_NB_HTTP, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("timeshift_depth", GJS_OM_PROP_TIMESHIFT_DEPTH, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("timeshift_time", GJS_OM_PROP_TIMESHIFT_TIME, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("is_addon", GJS_OM_PROP_IS_ADDON, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("main_addon_on", GJS_OM_PROP_MAIN_ADDON_ON, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("is_over", GJS_OM_PROP_IS_OVER, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("is_pulling", GJS_OM_PROP_IS_PULLING, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("dynamic_scene", GJS_OM_PROP_DYNAMIC_SCENE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("service_name", GJS_OM_PROP_SERVICE_NAME, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("ntp_diff", GJS_OM_PROP_NTP_DIFF, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("main_addon_url", GJS_OM_PROP_MAIN_ADDON_URL, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("reverse_playback_supported", GJS_OM_PROP_REVERSE_PLAYBACK, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("scalable_enhancement", GJS_OM_PROP_SCALABLE_ENHANCEMENT, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("main_addon_media_time", GJS_OM_PROP_MAIN_ADDON_MEDIATIME, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("dependent_groups", GJS_OM_PROP_DEPENDENT_GROUPS, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - SMJS_PROPERTY_SPEC("vr_scene", GJS_OM_PROP_IS_VR_SCENE, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_SHARED | JSPROP_READONLY, 0, 0), - - SMJS_PROPERTY_SPEC(0, 0, 0, 0, 0) - }; - - JSFunctionSpec odmClassFuncs[] = { - SMJS_FUNCTION_SPEC("declare_addon", gjs_odm_declare_addon, 1), - SMJS_FUNCTION_SPEC("enable_addon", gjs_odm_enable_addon, 1), - SMJS_FUNCTION_SPEC("addon_layout", gjs_odm_addon_layout, 1), - SMJS_FUNCTION_SPEC("get_resource", gjs_odm_get_resource, 1), - SMJS_FUNCTION_SPEC("get_quality", gjs_odm_get_quality, 1), - SMJS_FUNCTION_SPEC("select_service", gjs_odm_select_service, 1), - SMJS_FUNCTION_SPEC("select_quality", gjs_odm_select_quality, 1), - SMJS_FUNCTION_SPEC("disable_main_addon", gjs_odm_disable_main_addon, 1), - SMJS_FUNCTION_SPEC("select", gjs_odm_select, 1), - SMJS_FUNCTION_SPEC("get_srd", gjs_odm_get_srd, 1), - - SMJS_FUNCTION_SPEC(0, 0, 0) - }; - - - JSPropertySpec storageClassProps[] = { - SMJS_PROPERTY_SPEC(0, 0, 0, 0, 0) - }; - JSFunctionSpec storageClassFuncs[] = { - SMJS_FUNCTION_SPEC("get_option", gjs_storage_get_option, 2), - SMJS_FUNCTION_SPEC("set_option", gjs_storage_set_option, 3), - SMJS_FUNCTION_SPEC("save", gjs_storage_save, 0), - SMJS_FUNCTION_SPEC(0, 0, 0) - }; - - - gjs = jsext->udta; - /*nothing to do on unload*/ - if (unload) { - gjs->nb_loaded--; - /*if we destroy the script context holding the gpac event filter (only one for the time being), remove the filter*/ - if ((gjs->c==c) && gjs->evt_filter.udta) { - gf_term_remove_event_filter(gjs->term, &gjs->evt_filter); - gjs->evt_filter.udta = NULL; - } - return; - } - - gjs->nb_loaded++; - - if (!scene) return; - - JS_SETUP_CLASS(gjs->gpacClass, "GPAC", JSCLASS_HAS_PRIVATE, gpac_getProperty, gpac_setProperty, JS_FinalizeStub); - - if (!gjs->gpac_obj) { - GF_JS_InitClass(c, global, 0, &gjs->gpacClass, 0, 0, gpacClassProps, gpacClassFuncs, 0, 0); - gjs->gpac_obj = JS_DefineObject(c, global, "gpac", &gjs->gpacClass._class, 0, 0); - - if (scene->script_action) { - if (scene->script_action(scene->script_action_cbck, GF_JSAPI_OP_GET_TERM, scene->RootNode, &par)) { - gjs->term = par.term; - } - } - SMJS_SET_PRIVATE(c, gjs->gpac_obj, gjs); - - JS_SETUP_CLASS(gjs->odmClass, "ODM", JSCLASS_HAS_PRIVATE, odm_getProperty, JS_PropertyStub_forSetter, JS_FinalizeStub); - GF_JS_InitClass(c, global, 0, &gjs->odmClass, 0, 0, odmClassProps, odmClassFuncs, 0, 0); - - - JS_SETUP_CLASS(gjs->storageClass, "Storage", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, JS_FinalizeStub); - GF_JS_InitClass(c, global, 0, &gjs->storageClass, 0, 0, storageClassProps, storageClassFuncs, 0, 0); - } else { - JS_DefineProperty(c, global, "gpac", OBJECT_TO_JSVAL(gjs->gpac_obj), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - } - - if (!gjs->evt_obj) { - JS_SETUP_CLASS(gjs->gpacEvtClass, "GPACEVT", JSCLASS_HAS_PRIVATE, gpacevt_getProperty, JS_PropertyStub_forSetter, JS_FinalizeStub); - GF_JS_InitClass(c, global, 0, &gjs->gpacEvtClass, 0, 0, gpacEvtClassProps, gpacEvtClassFuncs, 0, 0); - gjs->evt_obj = JS_DefineObject(c, global, "gpacevt", &gjs->gpacEvtClass._class, 0, 0); - -#define DECLARE_GPAC_CONST(name) \ - JS_DefineProperty(c, global, #name, INT_TO_JSVAL(name), 0, 0, JSPROP_READONLY | JSPROP_PERMANENT); - - DECLARE_GPAC_CONST(GF_EVENT_CLICK); - DECLARE_GPAC_CONST(GF_EVENT_MOUSEUP); - DECLARE_GPAC_CONST(GF_EVENT_MOUSEDOWN); - DECLARE_GPAC_CONST(GF_EVENT_MOUSEMOVE); - DECLARE_GPAC_CONST(GF_EVENT_MOUSEWHEEL); - DECLARE_GPAC_CONST(GF_EVENT_DBLCLICK); - DECLARE_GPAC_CONST(GF_EVENT_KEYUP); - DECLARE_GPAC_CONST(GF_EVENT_KEYDOWN); - DECLARE_GPAC_CONST(GF_EVENT_TEXTINPUT); - DECLARE_GPAC_CONST(GF_EVENT_CONNECT); - DECLARE_GPAC_CONST(GF_EVENT_NAVIGATE_INFO); - DECLARE_GPAC_CONST(GF_EVENT_NAVIGATE); - DECLARE_GPAC_CONST(GF_EVENT_DROPFILE); - DECLARE_GPAC_CONST(GF_EVENT_ADDON_DETECTED); - DECLARE_GPAC_CONST(GF_EVENT_QUALITY_SWITCHED); - DECLARE_GPAC_CONST(GF_EVENT_TIMESHIFT_DEPTH); - DECLARE_GPAC_CONST(GF_EVENT_TIMESHIFT_UPDATE); - DECLARE_GPAC_CONST(GF_EVENT_TIMESHIFT_OVERFLOW); - DECLARE_GPAC_CONST(GF_EVENT_TIMESHIFT_UNDERRUN); - DECLARE_GPAC_CONST(GF_EVENT_QUIT); - DECLARE_GPAC_CONST(GF_EVENT_MAIN_ADDON_STATE); - DECLARE_GPAC_CONST(GF_EVENT_SCENE_SIZE); - - DECLARE_GPAC_CONST(GF_NAVIGATE_NONE); - DECLARE_GPAC_CONST(GF_NAVIGATE_WALK); - DECLARE_GPAC_CONST(GF_NAVIGATE_FLY); - DECLARE_GPAC_CONST(GF_NAVIGATE_PAN); - DECLARE_GPAC_CONST(GF_NAVIGATE_GAME); - DECLARE_GPAC_CONST(GF_NAVIGATE_SLIDE); - DECLARE_GPAC_CONST(GF_NAVIGATE_EXAMINE); - DECLARE_GPAC_CONST(GF_NAVIGATE_ORBIT); - DECLARE_GPAC_CONST(GF_NAVIGATE_VR); - - DECLARE_GPAC_CONST(GF_NAVIGATE_TYPE_NONE); - DECLARE_GPAC_CONST(GF_NAVIGATE_TYPE_2D); - DECLARE_GPAC_CONST(GF_NAVIGATE_TYPE_3D); - - JS_SETUP_CLASS(gjs->anyClass, "GPACOBJECT", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub_forSetter, JS_FinalizeStub); - GF_JS_InitClass(c, global, 0, &gjs->anyClass, 0, 0, 0, 0, 0, 0); - - gjs->evt_fun = JSVAL_NULL; - } -} - - - - - -GF_JSUserExtension *gjs_new() -{ - GF_JSUserExtension *dr; - GF_GPACJSExt *gjs; - GF_SAFEALLOC(dr, GF_JSUserExtension); - if (!dr) return NULL; - GF_REGISTER_MODULE_INTERFACE(dr, GF_JS_USER_EXT_INTERFACE, "GPAC JavaScript Bindings", "gpac distribution"); - - GF_SAFEALLOC(gjs, GF_GPACJSExt); - if (!gjs) { - gf_free(dr); - return NULL; - } - gjs->rti_refresh_rate = GPAC_JS_RTI_REFRESH_RATE; - gjs->evt_fun = JSVAL_NULL; - gjs->storages = gf_list_new(); - gjs->event_queue = gf_list_new(); - gjs->event_mx = gf_mx_new("GPACJSEvt"); - dr->load = gjs_load; - dr->udta = gjs; - return dr; -} - - -void gjs_delete(GF_BaseInterface *ifce) -{ - GF_JSUserExtension *dr = (GF_JSUserExtension *) ifce; - GF_GPACJSExt *gjs = dr->udta; - - while (gf_list_count(gjs->storages)) { - GF_Config *cfg = (GF_Config *) gf_list_pop_back(gjs->storages); - gf_cfg_discard_changes(cfg); - gf_cfg_del(cfg); - } - gf_list_del(gjs->storages); - - while (gf_list_count(gjs->event_queue)) { - GF_Event *evt = (GF_Event *) gf_list_pop_back(gjs->event_queue); - gf_free(evt); - } - gf_list_del(gjs->event_queue); - gf_mx_del(gjs->event_mx); - - gf_free(gjs); - gf_free(dr); -} - -#endif - - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifdef GPAC_HAS_SPIDERMONKEY - GF_JS_USER_EXT_INTERFACE, -#endif - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ -#ifdef GPAC_HAS_SPIDERMONKEY - if (InterfaceType == GF_JS_USER_EXT_INTERFACE) return (GF_BaseInterface *)gjs_new(); -#endif - return NULL; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#ifdef GPAC_HAS_SPIDERMONKEY - case GF_JS_USER_EXT_INTERFACE: - gjs_delete(ifce); - break; -#endif - } -} - -GPAC_MODULE_STATIC_DECLARATION( gpac_js ) diff --git a/modules/img_in/Makefile b/modules/img_in/Makefile deleted file mode 100644 index 890d936..0000000 --- a/modules/img_in/Makefile +++ /dev/null @@ -1,78 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/img_in - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -NEED_LOCAL_LIB="no" -LOCAL_LIB=../../bin/gcc -ifneq ($(STATICBUILD),yes) -LINKLIBS= -lgpac -else -LINKLIBS= -lgpac_static -endif - -#common objects -OBJS=img_dec.o img_in.o bmp_dec.o png_dec.o jpeg_dec.o - -#openjpeg config -ifeq ($(CONFIG_JP2), no) -else -OBJS+=jp2_dec.o -LINKLIBS+= -lopenjpeg -CFLAGS+=-DGPAC_HAS_JP2 -#local openjpeg lib -ifeq ($(CONFIG_JP2), local) -NEED_LOCAL_LIB="yes" -CFLAGS+= -I"$(LOCAL_INC_PATH)/openjpeg" -endif -endif - - -#add local lib path -ifeq ($(NEED_LOCAL_LIB), "yes") -LOCAL_LIB+=-L../../extra_lib/lib/gcc -endif - - - -SRCS := $(OBJS:.o=.c) - -LIB=gm_img_in$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -LINKLIBS+=-lwinmm -#LDFLAGS+=-export-symbols img_in.def -endif - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L$(LOCAL_LIB) $(LINKLIBS) $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_img_in-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) -L$(LOCAL_LIB) $(LINKLIBS) $(LDFLAGS) -endif - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/img_in/bmp_dec.c b/modules/img_in/bmp_dec.c deleted file mode 100644 index f0a3f64..0000000 --- a/modules/img_in/bmp_dec.c +++ /dev/null @@ -1,210 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / image format module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include "img_in.h" - -#if defined(WIN32) || defined(_WIN32_WCE) || defined(__SYMBIAN32__) -#else -#include <netinet/in.h> -#endif - -typedef struct -{ - u16 ES_ID; - u32 width, height, out_size, pixel_format; -} BMPDec; - -#define BMPCTX() BMPDec *ctx = (BMPDec *) ((IMGDec *)ifcg->privateStack)->opaque - - -static GF_Err BMP_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ - BMPCTX(); - if (ctx->ES_ID && ctx->ES_ID!=esd->ESID) return GF_NOT_SUPPORTED; - if (esd->decoderConfig->upstream) return GF_NOT_SUPPORTED; - ctx->ES_ID = esd->ESID; - return GF_OK; -} -static GF_Err BMP_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - BMPCTX(); - if (ctx->ES_ID != ES_ID) return GF_BAD_PARAM; - ctx->ES_ID = ES_ID; - return GF_OK; -} -static GF_Err BMP_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - BMPCTX(); - switch (capability->CapCode) { - case GF_CODEC_WIDTH: - capability->cap.valueInt = ctx->width; - break; - case GF_CODEC_HEIGHT: - capability->cap.valueInt = ctx->height; - break; - case GF_CODEC_STRIDE: - capability->cap.valueInt = (ctx->pixel_format == GF_PIXEL_RGB_24) ? 3 : 4; - capability->cap.valueInt *= ctx->width; - break; - case GF_CODEC_FPS: - capability->cap.valueFloat = 0; - break; - case GF_CODEC_PIXEL_FORMAT: - capability->cap.valueInt = ctx->pixel_format; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = ctx->out_size; - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = 0; - break; - case GF_CODEC_BUFFER_MAX: - capability->cap.valueInt = IMG_CM_SIZE; - break; - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 0; - break; - case GF_CODEC_PAR: - capability->cap.valueInt = 0; - break; - case GF_CODEC_REORDER: - capability->cap.valueInt = 0; - break; - default: - return GF_NOT_SUPPORTED; - } - return GF_OK; -} -static GF_Err BMP_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - /*return unsupported to avoid confusion by the player (like color space changing ...) */ - return GF_NOT_SUPPORTED; -} - -static GF_Err BMP_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ - char *pix; - u32 i, j, irow, in_stride, out_stride, BPP; - GF_BitStream *bs; - BITMAPFILEHEADER fh; - BITMAPINFOHEADER fi; - - BMPCTX(); - if (inBufferLength<54) return GF_NON_COMPLIANT_BITSTREAM; - bs = gf_bs_new(inBuffer, inBufferLength, GF_BITSTREAM_READ); - -#if defined(WIN32) || defined(_WIN32_WCE) || defined(__SYMBIAN32__) - gf_bs_read_data(bs, (char *) &fh, 14); -#else - fh.bfType = gf_bs_read_u16(bs); - fh.bfSize = gf_bs_read_u32(bs); - fh.bfReserved1 = gf_bs_read_u16(bs); - fh.bfReserved2 = gf_bs_read_u16(bs); - fh.bfOffBits = gf_bs_read_u32(bs); - fh.bfOffBits = ntohl(fh.bfOffBits); -#endif - gf_bs_read_data(bs, (char *) &fi, 40); - gf_bs_del(bs); - - if ((fi.biCompression != BI_RGB) || (fi.biPlanes!=1)) return GF_NOT_SUPPORTED; - if ((fi.biBitCount!=24) && (fi.biBitCount!=32)) return GF_NOT_SUPPORTED; - - BPP = (fi.biBitCount==24) ? 3 : 4; - ctx->width = fi.biWidth; - ctx->height = fi.biHeight; - ctx->pixel_format = (fi.biBitCount==24) ? GF_PIXEL_RGB_24 : GF_PIXEL_RGBA; - - /*new cfg, reset*/ - if (ctx->out_size != ctx->width * ctx->height * BPP) { - ctx->out_size = ctx->width * ctx->height * BPP; - *outBufferLength = ctx->out_size; - return GF_BUFFER_TOO_SMALL; - } - out_stride = ctx->width*BPP; - in_stride = out_stride; - while (in_stride % 4) in_stride++; - - /*read*/ - if (fi.biBitCount==24) { - for (i=0; i<ctx->height; i++) { - irow = (ctx->height-1-i)*out_stride; - pix = inBuffer + fh.bfOffBits + i*in_stride; - for (j=0; j<out_stride; j+=3) { - outBuffer[j + irow] = pix[2]; - outBuffer[j+1 + irow] = pix[1]; - outBuffer[j+2 + irow] = pix[0]; - pix += 3; - } - } - } else { - for (i=0; i<ctx->height; i++) { - irow = (ctx->height-1-i)*out_stride; - pix = inBuffer + fh.bfOffBits + i*in_stride; - for (j=0; j<out_stride; j+=4) { - outBuffer[j + irow] = pix[2]; - outBuffer[j+1 + irow] = pix[1]; - outBuffer[j+2 + irow] = pix[0]; - outBuffer[j+3 + irow] = pix[3]; - pix += 4; - } - } - } - *outBufferLength = ctx->out_size; - return GF_OK; -} - -static const char *BMP_GetCodecName(GF_BaseDecoder *dec) -{ - return "BMP Decoder"; -} - -Bool NewBMPDec(GF_BaseDecoder *ifcd) -{ - IMGDec *wrap = (IMGDec *) ifcd->privateStack; - BMPDec *dec = (BMPDec *) gf_malloc(sizeof(BMPDec)); - memset(dec, 0, sizeof(BMPDec)); - wrap->opaque = dec; - wrap->type = DEC_BMP; - - /*setup our own interface*/ - ifcd->AttachStream = BMP_AttachStream; - ifcd->DetachStream = BMP_DetachStream; - ifcd->GetCapabilities = BMP_GetCapabilities; - ifcd->SetCapabilities = BMP_SetCapabilities; - ifcd->GetName = BMP_GetCodecName; - ((GF_MediaDecoder *)ifcd)->ProcessData = BMP_ProcessData; - return GF_TRUE; -} - -void DeleteBMPDec(GF_BaseDecoder *ifcg) -{ - BMPCTX(); - gf_free(ctx); -} diff --git a/modules/img_in/img_dec.c b/modules/img_in/img_dec.c deleted file mode 100644 index 1ed3748..0000000 --- a/modules/img_in/img_dec.c +++ /dev/null @@ -1,159 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / image format module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include "img_in.h" - - -static u32 DEC_CanHandleStream(GF_BaseDecoder *dec, u32 StreamType, GF_ESD *esd, u8 PL) -{ - if (StreamType != GF_STREAM_VISUAL) return GF_CODEC_NOT_SUPPORTED; - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - - switch (esd->decoderConfig->objectTypeIndication) { -#ifdef GPAC_HAS_PNG - case GPAC_OTI_IMAGE_PNG: - if (NewPNGDec(dec)) return GF_CODEC_SUPPORTED; - return GF_CODEC_NOT_SUPPORTED; -#endif -#ifdef GPAC_HAS_JPEG - case GPAC_OTI_IMAGE_JPEG: - if (NewJPEGDec(dec)) return GF_CODEC_SUPPORTED; - return GF_CODEC_NOT_SUPPORTED; -#endif -#ifdef GPAC_HAS_JP2 - case GPAC_OTI_IMAGE_JPEG_2000: - if (NewJP2Dec(dec)) return GF_CODEC_SUPPORTED; - return GF_CODEC_NOT_SUPPORTED; -#endif - case GPAC_BMP_OTI: - if (NewBMPDec(dec)) return GF_CODEC_SUPPORTED; - return GF_CODEC_NOT_SUPPORTED; - - default: -#ifdef GPAC_HAS_JP2 - { - char *dsi = esd->decoderConfig->decoderSpecificInfo ? esd->decoderConfig->decoderSpecificInfo->data : NULL; - if (dsi && (dsi[0]=='m') && (dsi[1]=='j') && (dsi[2]=='p') && (dsi[3]=='2')) - if (NewJP2Dec(dec)) return GF_CODEC_SUPPORTED; - return GF_CODEC_NOT_SUPPORTED; - } -#endif - return GF_CODEC_NOT_SUPPORTED; - } - return GF_CODEC_NOT_SUPPORTED; -} - - -GF_BaseDecoder *NewBaseDecoder() -{ - GF_MediaDecoder *ifce; - IMGDec *wrap; - GF_SAFEALLOC(ifce, GF_MediaDecoder); - if (!ifce) return NULL; - GF_SAFEALLOC(wrap, IMGDec); - if (!wrap) { - gf_free(ifce); - return NULL; - } - ifce->privateStack = wrap; - ifce->CanHandleStream = DEC_CanHandleStream; - - GF_REGISTER_MODULE_INTERFACE(ifce, GF_MEDIA_DECODER_INTERFACE, "GPAC Image Decoder", "gpac distribution") - - /*other interfaces will be setup at run time*/ - return (GF_BaseDecoder *)ifce; -} - -void DeleteBaseDecoder(GF_BaseDecoder *ifcd) -{ - IMGDec *wrap; - if (!ifcd) - return; - wrap = (IMGDec *)ifcd->privateStack; - if (!wrap) - return; - switch (wrap->type) { - case DEC_PNG: - DeletePNGDec(ifcd); - break; - case DEC_JPEG: - DeleteJPEGDec(ifcd); - break; -#ifdef GPAC_HAS_JP2 - case DEC_JP2: - DeleteJP2Dec(ifcd); - break; -#endif - case DEC_BMP: - DeleteBMPDec(ifcd); - break; - default: - break; - } - gf_free(wrap); - ifcd->privateStack = NULL; - gf_free(ifcd); -} - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { - GF_MEDIA_DECODER_INTERFACE, - GF_NET_CLIENT_INTERFACE, - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - switch (InterfaceType) { - case GF_MEDIA_DECODER_INTERFACE: - return (GF_BaseInterface *)NewBaseDecoder(); - case GF_NET_CLIENT_INTERFACE: - return (GF_BaseInterface *)NewLoaderInterface(); - default: - return NULL; - } -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { - case GF_MEDIA_DECODER_INTERFACE: - DeleteBaseDecoder((GF_BaseDecoder *)ifce); - break; - case GF_NET_CLIENT_INTERFACE: - DeleteLoaderInterface(ifce); - break; - } -} - -GPAC_MODULE_STATIC_DECLARATION( img_in ) diff --git a/modules/img_in/img_in.c b/modules/img_in/img_in.c deleted file mode 100644 index fb1ccad..0000000 --- a/modules/img_in/img_in.c +++ /dev/null @@ -1,461 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / image format module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "img_in.h" -#include <gpac/avparse.h> - -enum -{ - IMG_JPEG = 1, - IMG_PNG, - IMG_BMP, - IMG_PNGD, - IMG_PNGDS, - IMG_PNGS, -}; - -typedef struct -{ - GF_ClientService *service; - /*service*/ - u32 srv_type; - - FILE *stream; - u32 img_type; - - u32 pad_bytes; - Bool done; - LPNETCHANNEL ch; - - Bool is_inline; - char *data; - u32 data_size; - - GF_SLHeader sl_hdr; - - /*file downloader*/ - GF_DownloadSession * dnload; -} IMGLoader; - - -GF_ESD *IMG_GetESD(IMGLoader *read) -{ - GF_ESD *esd = gf_odf_desc_esd_new(0); - esd->slConfig->timestampResolution = 1000; - esd->decoderConfig->streamType = GF_STREAM_VISUAL; - esd->ESID = 1; - - if (read->img_type == IMG_BMP) - esd->decoderConfig->objectTypeIndication = GPAC_BMP_OTI; - else { - u8 OTI=0; - GF_BitStream *bs = gf_bs_from_file(read->stream, GF_BITSTREAM_READ); -#ifndef GPAC_DISABLE_AV_PARSERS - u32 mtype, w, h; - gf_img_parse(bs, &OTI, &mtype, &w, &h, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength); -#endif - gf_bs_del(bs); - - if (!OTI) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[IMG_IN] Unable to guess format image - assigning from extension\n")); - if (read->img_type==IMG_JPEG) OTI = GPAC_OTI_IMAGE_JPEG; - else if (read->img_type==IMG_PNG) OTI = GPAC_OTI_IMAGE_PNG; - } - esd->decoderConfig->objectTypeIndication = OTI; - - if (read->img_type == IMG_PNGD) { - GF_Descriptor *d = gf_odf_desc_new(GF_ODF_AUX_VIDEO_DATA); - ((GF_AuxVideoDescriptor*)d)->aux_video_type = 1; - gf_list_add(esd->extensionDescriptors, d); - } - else if (read->img_type == IMG_PNGDS) { - GF_Descriptor *d = gf_odf_desc_new(GF_ODF_AUX_VIDEO_DATA); - ((GF_AuxVideoDescriptor*)d)->aux_video_type = 2; - gf_list_add(esd->extensionDescriptors, d); - } - else if (read->img_type == IMG_PNGS) { - GF_Descriptor *d = gf_odf_desc_new(GF_ODF_AUX_VIDEO_DATA); - ((GF_AuxVideoDescriptor*)d)->aux_video_type = 3; - gf_list_add(esd->extensionDescriptors, d); - } - } - return esd; -} - -const char * IMG_MIME_TYPES[] = { - "image/jpeg", "jpeg jpg", "JPEG Images", - "image/jp2", "jp2", "JPEG2000 Images", - "image/png", "png", "PNG Images", - "image/bmp", "bmp", "MS Bitmap Images", - "image/x-png+depth", "pngd", "PNG+Depth Images", - "image/x-png+depth+mask", "pngds", "PNG+Depth+Mask Images", - "image/x-png+stereo", "pngs", "Stereo PNG Images", - NULL -}; - -static u32 IMG_RegisterMimeTypes(const GF_InputService *plug) { - u32 i; - if (!plug) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("IMG_RegisterMimeTypes : plug is NULL !!\n")); - } - for (i = 0 ; IMG_MIME_TYPES[i]; i+=3) - gf_service_register_mime(plug, IMG_MIME_TYPES[i], IMG_MIME_TYPES[i+1], IMG_MIME_TYPES[i+2]); - return i/3; -} - - -static Bool IMG_CanHandleURL(GF_InputService *plug, const char *url) -{ - char *sExt; - u32 i; - GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("IMG_CanHandleURL(%s)\n", url)); - if (!plug || !url) - return GF_FALSE; - sExt = strrchr(url, '.'); - for (i = 0 ; IMG_MIME_TYPES[i]; i+=3) { - if (gf_service_check_mime_register(plug, IMG_MIME_TYPES[i], IMG_MIME_TYPES[i+1], IMG_MIME_TYPES[i+2], sExt)) - return GF_TRUE; - } - return GF_FALSE; -} - -static Bool jp_is_local(const char *url) -{ - if (!strnicmp(url, "file://", 7)) return GF_TRUE; - if (strstr(url, "://")) return GF_FALSE; - return GF_TRUE; -} - - -static void IMG_SetupObject(IMGLoader *read) -{ - if (!read->ch) { - GF_ObjectDescriptor *od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - GF_ESD *esd = IMG_GetESD(read); - od->objectDescriptorID = 1; - gf_list_add(od->ESDescriptors, esd); - gf_service_declare_media(read->service, (GF_Descriptor *)od, GF_FALSE); - } -} - - -void IMG_NetIO(void *cbk, GF_NETIO_Parameter *param) -{ - GF_Err e; - const char *szCache; - IMGLoader *read = (IMGLoader *) cbk; - if (!read->dnload) return; - - /*handle service message*/ - gf_service_download_update_stats(read->dnload); - - e = param->error; - /*wait to get the whole file*/ - if (!e && (param->msg_type!=GF_NETIO_DATA_TRANSFERED)) return; - if ((e==GF_EOS) && (param->msg_type==GF_NETIO_DATA_EXCHANGE)) return; - - if (param->msg_type==GF_NETIO_DATA_TRANSFERED) { - szCache = gf_dm_sess_get_cache_name(read->dnload); - if (!szCache) e = GF_IO_ERR; - else { - if (read->stream) gf_fclose(read->stream); - read->stream = gf_fopen((char *) szCache, "rb"); - if (!read->stream) e = GF_SERVICE_ERROR; - else { - e = GF_OK; - gf_fseek(read->stream, 0, SEEK_END); - read->data_size = (u32) gf_ftell(read->stream); - gf_fseek(read->stream, 0, SEEK_SET); - } - } - } - /*OK confirm*/ - gf_service_connect_ack(read->service, NULL, e); - if (!e) IMG_SetupObject(read); -} - -void jp_download_file(GF_InputService *plug, const char *url) -{ - IMGLoader *read = (IMGLoader *) plug->priv; - - read->dnload = gf_service_download_new(read->service, url, 0, IMG_NetIO, read); - if (!read->dnload) { - gf_service_connect_ack(read->service, NULL, GF_NOT_SUPPORTED); - } else { - /*start our download (threaded)*/ - gf_dm_sess_process(read->dnload); - } - /*service confirm is done once fetched*/ -} - -static GF_Err IMG_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url) -{ - char *sExt, *frag; - IMGLoader *read = (IMGLoader *)plug->priv; - - read->service = serv; - if (!url) - return GF_BAD_PARAM; - - frag = strrchr(url, '#'); - if (frag) frag[0] = 0; - - sExt = strrchr(url, '.'); - if (!stricmp(sExt, ".jpeg") || !stricmp(sExt, ".jpg")) read->img_type = IMG_JPEG; - else if (!stricmp(sExt, ".png")) read->img_type = IMG_PNG; - else if (!stricmp(sExt, ".pngd")) read->img_type = IMG_PNGD; - else if (!stricmp(sExt, ".pngds")) read->img_type = IMG_PNGDS; - else if (!stricmp(sExt, ".pngs")) read->img_type = IMG_PNGS; - else if (!stricmp(sExt, ".bmp")) read->img_type = IMG_BMP; - - if (read->dnload) gf_service_download_del(read->dnload); - read->dnload = NULL; - - /*remote fetch*/ - if (!jp_is_local(url)) { - jp_download_file(plug, url); - if (frag) frag[0] = '#'; - return GF_OK; - } - - read->stream = gf_fopen(url, "rb"); - if (read->stream) { - gf_fseek(read->stream, 0, SEEK_END); - read->data_size = (u32) gf_ftell(read->stream); - gf_fseek(read->stream, 0, SEEK_SET); - } - if (frag) frag[0] = '#'; - gf_service_connect_ack(serv, NULL, read->stream ? GF_OK : GF_URL_ERROR); - if (read->stream && read->is_inline) IMG_SetupObject(read); - return GF_OK; -} - -static GF_Err IMG_CloseService(GF_InputService *plug) -{ - IMGLoader *read; - if (!plug) - return GF_BAD_PARAM; - read = (IMGLoader *)plug->priv; - if (!read) - return GF_BAD_PARAM; - if (read->stream) gf_fclose(read->stream); - read->stream = NULL; - if (read->dnload) gf_service_download_del(read->dnload); - read->dnload = NULL; - if (read->service) - gf_service_disconnect_ack(read->service, NULL, GF_OK); - return GF_OK; -} - -static GF_Descriptor *IMG_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) -{ - GF_ESD *esd; - IMGLoader *read = (IMGLoader *)plug->priv; - - /*override default*/ - if (expect_type==GF_MEDIA_OBJECT_UNDEF) expect_type=GF_MEDIA_OBJECT_VIDEO; - read->srv_type = expect_type; - - /*visual object*/ - if (expect_type==GF_MEDIA_OBJECT_VIDEO) { - GF_ObjectDescriptor *od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - od->objectDescriptorID = 1; - esd = IMG_GetESD(read); - gf_list_add(od->ESDescriptors, esd); - return (GF_Descriptor *) od; - } - read->is_inline = GF_TRUE; - return NULL; -} - -static GF_Err IMG_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) -{ - u32 ES_ID=0; - GF_Err e; - IMGLoader *read; - if (!plug) - return GF_OK; - read = (IMGLoader *)plug->priv; - - e = GF_SERVICE_ERROR; - if (read->ch==channel) goto exit; - if (!url) - goto exit; - e = GF_STREAM_NOT_FOUND; - if (strstr(url, "ES_ID")) { - sscanf(url, "ES_ID=%ud", &ES_ID); - } - /*URL setup*/ - else if (!read->ch && IMG_CanHandleURL(plug, url)) ES_ID = 1; - - if (ES_ID==1) { - read->ch = channel; - e = GF_OK; - } - -exit: - gf_service_connect_ack(read->service, channel, e); - return e; -} - -static GF_Err IMG_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) -{ - GF_Err e = GF_STREAM_NOT_FOUND; - IMGLoader *read = (IMGLoader *)plug->priv; - - if (read->ch == channel) { - read->ch = NULL; - e = GF_OK; - } - gf_service_disconnect_ack(read->service, channel, e); - return GF_OK; -} - -static GF_Err IMG_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) -{ - IMGLoader *read = (IMGLoader *)plug->priv; - - if (com->command_type==GF_NET_SERVICE_HAS_AUDIO) return GF_NOT_SUPPORTED; - - if (!com->base.on_channel) return GF_NOT_SUPPORTED; - switch (com->command_type) { - case GF_NET_CHAN_SET_PADDING: - read->pad_bytes = com->pad.padding_bytes; - return GF_OK; - case GF_NET_CHAN_DURATION: - com->duration.duration = 0; - return GF_OK; - case GF_NET_CHAN_PLAY: - /*note we don't handle range since we're only dealing with images*/ - if (read->ch == com->base.on_channel) { - read->done = GF_FALSE; - } - return GF_OK; - case GF_NET_CHAN_STOP: - return GF_OK; - default: - return GF_OK; - } -} - - -static GF_Err IMG_ChannelGetSLP(GF_InputService *plug, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, GF_SLHeader *out_sl_hdr, Bool *sl_compressed, GF_Err *out_reception_status, Bool *is_new_data) -{ - IMGLoader *read = (IMGLoader *)plug->priv; - - *out_reception_status = GF_OK; - *sl_compressed = GF_FALSE; - *is_new_data = GF_FALSE; - - memset(&read->sl_hdr, 0, sizeof(GF_SLHeader)); - read->sl_hdr.randomAccessPointFlag = 1; - read->sl_hdr.compositionTimeStampFlag = 1; - *out_sl_hdr = read->sl_hdr; - - /*fetching es data*/ - if (read->ch == channel) { - if (read->done) { - *out_reception_status = GF_EOS; - return GF_OK; - } - if (!read->data) { - if (!read->stream) { - *out_data_ptr = NULL; - *out_data_size = 0; - return GF_OK; - } - *is_new_data = GF_TRUE; - gf_fseek(read->stream, 0, SEEK_SET); - read->data = (char*) gf_malloc(sizeof(char) * (read->data_size + read->pad_bytes)); - read->data_size = (u32) fread(read->data, sizeof(char), read->data_size, read->stream); - if ((s32) read->data_size<0) return GF_IO_ERR; - gf_fseek(read->stream, 0, SEEK_SET); - if (read->pad_bytes) memset(read->data + read->data_size, 0, sizeof(char) * read->pad_bytes); - - } - *out_data_ptr = read->data; - *out_data_size = read->data_size; - return GF_OK; - } - return GF_STREAM_NOT_FOUND; -} - -static GF_Err IMG_ChannelReleaseSLP(GF_InputService *plug, LPNETCHANNEL channel) -{ - IMGLoader *read = (IMGLoader *)plug->priv; - - if (read->ch == channel) { - if (!read->data) return GF_BAD_PARAM; - gf_free(read->data); - read->data = NULL; - read->done = GF_TRUE; - return GF_OK; - } - return GF_OK; -} - - -void *NewLoaderInterface() -{ - IMGLoader *priv; - GF_InputService *plug; - GF_SAFEALLOC(plug, GF_InputService); - if (!plug) return NULL; - GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC Image Reader", "gpac distribution") - - GF_SAFEALLOC(priv, IMGLoader); - if (!priv) { - gf_free(plug); - return NULL; - } - plug->priv = priv; - - plug->RegisterMimeTypes = IMG_RegisterMimeTypes; - plug->CanHandleURL = IMG_CanHandleURL; - plug->CanHandleURLInService = NULL; - plug->ConnectService = IMG_ConnectService; - plug->CloseService = IMG_CloseService; - plug->GetServiceDescriptor = IMG_GetServiceDesc; - plug->ConnectChannel = IMG_ConnectChannel; - plug->DisconnectChannel = IMG_DisconnectChannel; - plug->ChannelGetSLP = IMG_ChannelGetSLP; - plug->ChannelReleaseSLP = IMG_ChannelReleaseSLP; - plug->ServiceCommand = IMG_ServiceCommand; - - return plug; -} - -void DeleteLoaderInterface(void *ifce) -{ - IMGLoader *read; - GF_InputService *plug = (GF_InputService *) ifce; - if (!plug) - return; - read = (IMGLoader *)plug->priv; - if (read) - gf_free(read); - plug->priv = NULL; - gf_free(plug); -} diff --git a/modules/img_in/img_in.h b/modules/img_in/img_in.h deleted file mode 100644 index fcc4b1b..0000000 --- a/modules/img_in/img_in.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / image format module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#ifndef _IMG_IN_H -#define _IMG_IN_H - -/*all codecs are regular */ -#include <gpac/modules/codec.h> -#include <gpac/modules/service.h> -#include <gpac/constants.h> - -enum -{ - DEC_RESERVED = 0, - DEC_PNG, - DEC_JPEG, - DEC_JP2, - DEC_BMP, -}; - -typedef struct -{ - u32 type; - void *opaque; -} IMGDec; - -/*all constructors shall setup the wraper type and handle - return 1 for success, 0 otherwise -all destructors only destroy their private stacks (eg not the interface nor the wraper) -*/ -Bool NewPNGDec(GF_BaseDecoder *dec); -void DeletePNGDec(GF_BaseDecoder *dec); - -Bool NewJPEGDec(GF_BaseDecoder *dec); -void DeleteJPEGDec(GF_BaseDecoder *dec); - -#ifdef GPAC_HAS_JP2 -Bool NewJP2Dec(GF_BaseDecoder *dec); -void DeleteJP2Dec(GF_BaseDecoder *dec); -#endif - - -#if defined(WIN32) || defined(_WIN32_WCE) -#include <windows.h> -#else -typedef struct tagBITMAPFILEHEADER -{ - u16 bfType; - u32 bfSize; - u16 bfReserved1; - u16 bfReserved2; - u32 bfOffBits; -} BITMAPFILEHEADER; - -typedef struct tagBITMAPINFOHEADER { - u32 biSize; - s32 biWidth; - s32 biHeight; - u16 biPlanes; - u16 biBitCount; - u32 biCompression; - u32 biSizeImage; - s32 biXPelsPerMeter; - s32 biYPelsPerMeter; - u32 biClrUsed; - u32 biClrImportant; -} BITMAPINFOHEADER; - -#define BI_RGB 0L - -#endif - -#define GPAC_BMP_OTI 0x82 - -Bool NewBMPDec(GF_BaseDecoder *dec); -void DeleteBMPDec(GF_BaseDecoder *dec); - -#define IMG_CM_SIZE 1 - - -void *NewLoaderInterface(); -void DeleteLoaderInterface(void *ifce); - -#endif diff --git a/modules/img_in/jp2_dec.c b/modules/img_in/jp2_dec.c deleted file mode 100644 index 4a16929..0000000 --- a/modules/img_in/jp2_dec.c +++ /dev/null @@ -1,469 +0,0 @@ -/* -* GPAC - Multimedia Framework C SDK -* - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 -* All rights reserved -* -* This file is part of GPAC / image format module -* -* GPAC is free software; you can redistribute it and/or modify -* it under the terms of the GNU Lesser General Public License as published by -* the Free Software Foundation; either version 2, or (at your option) -* any later version. -* -* GPAC is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; see the file COPYING. If not, write to -* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. -* -*/ - -#include "img_in.h" - -#ifdef GPAC_HAS_JP2 - -#if !defined(__GNUC__) -# if defined(_WIN32_WCE) || defined (WIN32) -# if defined(_DEBUG) -# pragma comment(lib, "LibOpenJPEGd") -# else -# pragma comment(lib, "LibOpenJPEG") -# endif -# endif -#endif - - -#include <openjpeg.h> - -typedef struct -{ - /*no support for scalability with JPEG (progressive JPEG to test)*/ - u32 bpp, nb_comp, width, height, out_size, pixel_format, dsi_size; - char *dsi; - opj_image_t *image; -} JP2Dec; - -#define JP2CTX() JP2Dec *ctx = (JP2Dec *) ((IMGDec *)ifcg->privateStack)->opaque - - -static GF_Err JP2_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ - GF_BitStream *bs; - JP2CTX(); - if (esd->dependsOnESID || esd->decoderConfig->upstream) return GF_NOT_SUPPORTED; - - if (!esd->decoderConfig->decoderSpecificInfo) return GF_OK; - - if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_IMAGE_JPEG_2000) { - bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ); - ctx->height = gf_bs_read_u32(bs); - ctx->width = gf_bs_read_u32(bs); - ctx->nb_comp = gf_bs_read_u16(bs); - ctx->bpp = 1 + gf_bs_read_u8(bs); - ctx->out_size = ctx->width * ctx->height * ctx->nb_comp /* * ctx->bpp / 8 */; - gf_bs_del(bs); - - switch (ctx->nb_comp) { - case 1: - ctx->pixel_format = GF_PIXEL_GREYSCALE; - break; - case 2: - ctx->pixel_format = GF_PIXEL_ALPHAGREY; - break; - case 3: - ctx->pixel_format = GF_PIXEL_RGB_24; - break; - case 4: - ctx->pixel_format = GF_PIXEL_RGBA; - break; - default: - return GF_NOT_SUPPORTED; - } - } else { - bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ); - gf_bs_read_u32(bs); - ctx->width = gf_bs_read_u16(bs); - ctx->height = gf_bs_read_u16(bs); - gf_bs_del(bs); - - bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - gf_bs_write_u32(bs, 12); - gf_bs_write_u32(bs, GF_4CC('j','P',' ',' ') ); - gf_bs_write_u32(bs, 0x0D0A870A); - gf_bs_write_u32(bs, 20); - gf_bs_write_u32(bs, GF_4CC('f','t','y','p') ); - gf_bs_write_u32(bs, GF_4CC('j','p','2',' ') ); - gf_bs_write_u32(bs, 0); - gf_bs_write_u32(bs, GF_4CC('j','p','2',' ') ); - - gf_bs_write_data(bs, esd->decoderConfig->decoderSpecificInfo->data+8, esd->decoderConfig->decoderSpecificInfo->dataLength-8); - gf_bs_get_content(bs, &ctx->dsi, &ctx->dsi_size); - gf_bs_del(bs); - - ctx->nb_comp = 3; - ctx->out_size = 3*ctx->width*ctx->height/2; - ctx->pixel_format = GF_PIXEL_YV12; - } - - return GF_OK; -} -static GF_Err JP2_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - JP2CTX(); - if (ctx->image) { - opj_image_destroy(ctx->image); - ctx->image = NULL; - } - return GF_OK; -} -static GF_Err JP2_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - JP2CTX(); - switch (capability->CapCode) { - case GF_CODEC_WIDTH: - capability->cap.valueInt = ctx->width; - break; - case GF_CODEC_HEIGHT: - capability->cap.valueInt = ctx->height; - break; - case GF_CODEC_STRIDE: - if (ctx->pixel_format == GF_PIXEL_YV12) { - capability->cap.valueInt = ctx->width; - } else { - capability->cap.valueInt = ctx->width * ctx->nb_comp; - } - break; - case GF_CODEC_FPS: - capability->cap.valueFloat = 0; - break; - case GF_CODEC_PIXEL_FORMAT: - capability->cap.valueInt = ctx->pixel_format; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = ctx->out_size; - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = 0; - break; - case GF_CODEC_BUFFER_MAX: - capability->cap.valueInt = (ctx->pixel_format == GF_PIXEL_YV12) ? 4 : 1; - break; - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 4; - break; - case GF_CODEC_PAR: - capability->cap.valueInt = 0; - break; - default: - capability->cap.valueInt = 0; - return GF_NOT_SUPPORTED; - } - return GF_OK; -} -static GF_Err JP2_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - /*return unsupported to avoid confusion by the player (like color space changing ...) */ - return GF_NOT_SUPPORTED; -} - -/** -sample error callback expecting a FILE* client object -*/ -void error_callback(const char *msg, void *client_data) -{ - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[OpenJPEG] Error %s", msg)); -} -/** -sample warning callback expecting a FILE* client object -*/ -void warning_callback(const char *msg, void *client_data) -{ - GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[OpenJPEG] Warning %s", msg)); -} -/** -sample debug callback expecting no client object -*/ -void info_callback(const char *msg, void *client_data) -{ - GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[OpenJPEG] Info %s", msg)); -} - -/* -* Divide an integer by a power of 2 and round upwards. -* -* a divided by 2^b -*/ -static int int_ceildivpow2(int a, int b) { - return (a + (1 << b) - 1) >> b; -} - -static GF_Err JP2_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ - u32 i, w, wr, h, hr, wh; - opj_dparameters_t parameters; /* decompression parameters */ - opj_event_mgr_t event_mgr; /* event manager */ - opj_dinfo_t* dinfo = NULL; /* handle to a decompressor */ - opj_cio_t *cio = NULL; - opj_codestream_info_t cinfo; - - JP2CTX(); - -#if 1 - switch (mmlevel) { - case GF_CODEC_LEVEL_SEEK: - case GF_CODEC_LEVEL_DROP: - *outBufferLength = 0; - return GF_OK; - } -#endif - - if (!ctx->image) { - /* configure the event callbacks (not required) */ - memset(&event_mgr, 0, sizeof(opj_event_mgr_t)); - event_mgr.error_handler = error_callback; - event_mgr.warning_handler = warning_callback; - event_mgr.info_handler = info_callback; - - /* set decoding parameters to default values */ - opj_set_default_decoder_parameters(¶meters); - - /* get a decoder handle */ - dinfo = opj_create_decompress(CODEC_JP2); - - /* catch events using our callbacks and give a local context */ - opj_set_event_mgr((opj_common_ptr)dinfo, &event_mgr, stderr); - - /* setup the decoder decoding parameters using the current image and user parameters */ - opj_setup_decoder(dinfo, ¶meters); - - - /* open a byte stream */ - if (ctx->dsi) { - char *data; - - data = (char*)gf_malloc(sizeof(char) * (ctx->dsi_size+inBufferLength)); - memcpy(data, ctx->dsi, ctx->dsi_size); - memcpy(data+ctx->dsi_size, inBuffer, inBufferLength); - cio = opj_cio_open((opj_common_ptr)dinfo, data, ctx->dsi_size+inBufferLength); - /* decode the stream and fill the image structure */ - ctx->image = opj_decode(dinfo, cio); - gf_free(data); - } else { - cio = opj_cio_open((opj_common_ptr)dinfo, inBuffer, inBufferLength); - /* decode the stream and fill the image structure */ - ctx->image = opj_decode_with_info(dinfo, cio, &cinfo); - } - - //Fill the ctx info because dsi was not present - if (ctx->image) { - ctx->nb_comp = cinfo.numcomps; - ctx->width = cinfo.image_w; - ctx->height = cinfo.image_h; - ctx->bpp = ctx->nb_comp * 8; - ctx->out_size = ctx->width * ctx->height * ctx->nb_comp /* * ctx->bpp / 8 */; - - switch (ctx->nb_comp) { - case 1: - ctx->pixel_format = GF_PIXEL_GREYSCALE; - break; - case 2: - ctx->pixel_format = GF_PIXEL_ALPHAGREY; - break; - case 3: - ctx->pixel_format = GF_PIXEL_RGB_24; - break; - case 4: - ctx->pixel_format = GF_PIXEL_RGBA; - break; - default: - return GF_NOT_SUPPORTED; - } - - if ( *outBufferLength < ctx->out_size ) { - *outBufferLength = ctx->out_size; - opj_destroy_decompress(dinfo); - opj_cio_close(cio); - return GF_BUFFER_TOO_SMALL; - } - } - - if(!ctx->image) { - opj_destroy_decompress(dinfo); - opj_cio_close(cio); - return GF_IO_ERR; - } - - /* close the byte stream */ - opj_cio_close(cio); - cio = NULL; - - /* gf_free( remaining structures */ - if(dinfo) { - opj_destroy_decompress(dinfo); - dinfo = NULL; - } - } - - w = ctx->image->comps[0].w; - wr = int_ceildivpow2(ctx->image->comps[0].w, ctx->image->comps[0].factor); - h = ctx->image->comps[0].h; - hr = int_ceildivpow2(ctx->image->comps[0].h, ctx->image->comps[0].factor); - wh = wr*hr; - - if (ctx->nb_comp==1) { - if ((w==wr) && (h==hr)) { - for (i=0; i<wh; i++) { - outBuffer[i] = ctx->image->comps[0].data[i]; - } - } else { - for (i=0; i<wh; i++) { - outBuffer[i] = ctx->image->comps[0].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; - } - } - } - else if (ctx->nb_comp==3) { - - if ((ctx->image->comps[0].w==2*ctx->image->comps[1].w) && (ctx->image->comps[1].w==ctx->image->comps[2].w) - && (ctx->image->comps[0].h==2*ctx->image->comps[1].h) && (ctx->image->comps[1].h==ctx->image->comps[2].h)) { - - if (ctx->pixel_format != GF_PIXEL_YV12) { - ctx->pixel_format = GF_PIXEL_YV12; - ctx->out_size = 3*ctx->width*ctx->height/2; - *outBufferLength = ctx->out_size; - return GF_BUFFER_TOO_SMALL; - } - - if ((w==wr) && (h==hr)) { - for (i=0; i<wh; i++) { - *outBuffer = ctx->image->comps[0].data[i]; - outBuffer++; - } - w = ctx->image->comps[1].w; - wr = int_ceildivpow2(ctx->image->comps[1].w, ctx->image->comps[1].factor); - h = ctx->image->comps[1].h; - hr = int_ceildivpow2(ctx->image->comps[1].h, ctx->image->comps[1].factor); - wh = wr*hr; - for (i=0; i<wh; i++) { - *outBuffer = ctx->image->comps[1].data[i]; - outBuffer++; - } - for (i=0; i<wh; i++) { - *outBuffer = ctx->image->comps[2].data[i]; - outBuffer++; - } - } else { - for (i=0; i<wh; i++) { - *outBuffer = ctx->image->comps[0].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; - } - w = ctx->image->comps[1].w; - wr = int_ceildivpow2(ctx->image->comps[1].w, ctx->image->comps[1].factor); - h = ctx->image->comps[1].h; - hr = int_ceildivpow2(ctx->image->comps[1].h, ctx->image->comps[1].factor); - wh = wr*hr; - for (i=0; i<wh; i++) { - *outBuffer = ctx->image->comps[1].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; - } - for (i=0; i<wh; i++) { - *outBuffer = ctx->image->comps[2].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; - } - } - - - } else if ((ctx->image->comps[0].w==ctx->image->comps[1].w) && (ctx->image->comps[1].w==ctx->image->comps[2].w) - && (ctx->image->comps[0].h==ctx->image->comps[1].h) && (ctx->image->comps[1].h==ctx->image->comps[2].h)) { - - if ((w==wr) && (h==hr)) { - for (i=0; i<wh; i++) { - u32 idx = 3*i; - outBuffer[idx] = ctx->image->comps[0].data[i]; - outBuffer[idx+1] = ctx->image->comps[1].data[i]; - outBuffer[idx+2] = ctx->image->comps[2].data[i]; - } - } else { - for (i=0; i<wh; i++) { - u32 idx = 3*i; - outBuffer[idx] = ctx->image->comps[0].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; - outBuffer[idx+1] = ctx->image->comps[1].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; - outBuffer[idx+2] = ctx->image->comps[2].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; - } - } - } - } - else if (ctx->nb_comp==4) { - if ((ctx->image->comps[0].w==ctx->image->comps[1].w) && (ctx->image->comps[1].w==ctx->image->comps[2].w) && (ctx->image->comps[2].w==ctx->image->comps[3].w) - && (ctx->image->comps[0].h==ctx->image->comps[1].h) && (ctx->image->comps[1].h==ctx->image->comps[2].h) && (ctx->image->comps[2].h==ctx->image->comps[3].h)) { - - if ((w==wr) && (h==hr)) { - for (i=0; i<wh; i++) { - u32 idx = 4*i; - outBuffer[idx] = ctx->image->comps[0].data[i]; - outBuffer[idx+1] = ctx->image->comps[1].data[i]; - outBuffer[idx+2] = ctx->image->comps[2].data[i]; - outBuffer[idx+3] = ctx->image->comps[3].data[i]; - } - } else { - for (i=0; i<wh; i++) { - u32 idx = 4*i; - outBuffer[idx] = ctx->image->comps[0].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; - outBuffer[idx+1] = ctx->image->comps[1].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; - outBuffer[idx+2] = ctx->image->comps[2].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; - outBuffer[idx+3] = ctx->image->comps[3].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; - } - } - } - } - - /* gf_free( image data structure */ - if (ctx->image) { - opj_image_destroy(ctx->image); - ctx->image = NULL; - } - - *outBufferLength = ctx->out_size; - return GF_OK; -} - -static const char *JP2_GetCodecName(GF_BaseDecoder *dec) -{ -#ifdef OPENJPEG_VERSION - return "OpenJPEG "OPENJPEG_VERSION ; -#else - return "OpenJPEG" ; -#endif -} - - -Bool NewJP2Dec(GF_BaseDecoder *ifcd) -{ - IMGDec *wrap = (IMGDec *) ifcd->privateStack; - JP2Dec *dec = (JP2Dec *) gf_malloc(sizeof(JP2Dec)); - memset(dec, 0, sizeof(JP2Dec)); - wrap->opaque = dec; - wrap->type = DEC_JPEG; - - /*setup our own interface*/ - ifcd->AttachStream = JP2_AttachStream; - ifcd->DetachStream = JP2_DetachStream; - ifcd->GetCapabilities = JP2_GetCapabilities; - ifcd->SetCapabilities = JP2_SetCapabilities; - ifcd->GetName = JP2_GetCodecName; - ((GF_MediaDecoder *)ifcd)->ProcessData = JP2_ProcessData; - return GF_TRUE; -} - -void DeleteJP2Dec(GF_BaseDecoder *ifcg) -{ - JP2CTX(); - gf_free(ctx); -} - -#endif diff --git a/modules/img_in/jpeg_dec.c b/modules/img_in/jpeg_dec.c deleted file mode 100644 index b7a7bd4..0000000 --- a/modules/img_in/jpeg_dec.c +++ /dev/null @@ -1,156 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / image format module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "img_in.h" -#include <gpac/avparse.h> - -typedef struct -{ - /*no support for scalability with JPEG (progressive JPEG to test)*/ - u16 ES_ID; - u32 BPP, width, height, out_size, pixel_format; -} JPEGDec; - -#define JPEGCTX() JPEGDec *ctx = (JPEGDec *) ((IMGDec *)ifcg->privateStack)->opaque - -static GF_Err JPEG_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ - JPEGCTX(); - if (ctx->ES_ID && ctx->ES_ID!=esd->ESID) return GF_NOT_SUPPORTED; - ctx->ES_ID = esd->ESID; - ctx->BPP = 3; - return GF_OK; -} -static GF_Err JPEG_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - JPEGCTX(); - if (ctx->ES_ID != ES_ID) return GF_BAD_PARAM; - ctx->ES_ID = ES_ID; - return GF_OK; -} -static GF_Err JPEG_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - JPEGCTX(); - switch (capability->CapCode) { - case GF_CODEC_WIDTH: - capability->cap.valueInt = ctx->width; - break; - case GF_CODEC_HEIGHT: - capability->cap.valueInt = ctx->height; - break; - case GF_CODEC_STRIDE: - capability->cap.valueInt = ctx->width * ctx->BPP; - break; - case GF_CODEC_FPS: - capability->cap.valueFloat = 0; - break; - case GF_CODEC_PIXEL_FORMAT: - capability->cap.valueInt = ctx->pixel_format; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = ctx->out_size; - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = 0; - break; - case GF_CODEC_BUFFER_MAX: - capability->cap.valueInt = IMG_CM_SIZE; - break; - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 4; - break; - case GF_CODEC_PAR: - capability->cap.valueInt = 0; - break; - case GF_CODEC_REORDER: - capability->cap.valueInt = 0; - break; - default: - capability->cap.valueInt = 0; - return GF_NOT_SUPPORTED; - } - return GF_OK; -} -static GF_Err JPEG_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - /*return unsupported to avoid confusion by the player (like color space changing ...) */ - return GF_NOT_SUPPORTED; -} - -static GF_Err JPEG_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ -#ifndef GPAC_DISABLE_AV_PARSERS - GF_Err e; - JPEGCTX(); - - e = gf_img_jpeg_dec(inBuffer, inBufferLength, &ctx->width, &ctx->height, &ctx->pixel_format, outBuffer, outBufferLength, ctx->BPP); - switch (ctx->pixel_format) { - case GF_PIXEL_GREYSCALE: - ctx->BPP = 1; - break; - case GF_PIXEL_RGB_24: - ctx->BPP = 3; - break; - } - ctx->out_size = *outBufferLength; - return e; -#else - return GF_NOT_SUPPORTED; -#endif //GPAC_DISABLE_AV_PARSERS -} - -static const char *JPEG_GetCodecName(GF_BaseDecoder *dec) -{ - return "JPEG 6b IJG"; -} - - -Bool NewJPEGDec(GF_BaseDecoder *ifcd) -{ - IMGDec *wrap = (IMGDec *) ifcd->privateStack; - JPEGDec *dec = (JPEGDec *) gf_malloc(sizeof(JPEGDec)); - memset(dec, 0, sizeof(JPEGDec)); - wrap->opaque = dec; - wrap->type = DEC_JPEG; - - /*setup our own interface*/ - ifcd->AttachStream = JPEG_AttachStream; - ifcd->DetachStream = JPEG_DetachStream; - ifcd->GetCapabilities = JPEG_GetCapabilities; - ifcd->SetCapabilities = JPEG_SetCapabilities; - ifcd->GetName = JPEG_GetCodecName; - ((GF_MediaDecoder *)ifcd)->ProcessData = JPEG_ProcessData; - return GF_TRUE; -} - -void DeleteJPEGDec(GF_BaseDecoder *ifcg) -{ - JPEGCTX(); - gf_free(ctx); -} diff --git a/modules/img_in/png_dec.c b/modules/img_in/png_dec.c deleted file mode 100644 index 18a0bbe..0000000 --- a/modules/img_in/png_dec.c +++ /dev/null @@ -1,181 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / image format module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include "img_in.h" -#include <gpac/avparse.h> - -typedef struct -{ - u16 ES_ID; - u32 BPP, width, height, out_size, pixel_format; - u32 aux_type; -} PNGDec; - -#define PNGCTX() PNGDec *ctx = (PNGDec *) ((IMGDec *)ifcg->privateStack)->opaque - - -static GF_Err PNG_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ - u32 i = 0; - GF_Descriptor *d = NULL; - PNGCTX(); - if (ctx->ES_ID && ctx->ES_ID!=esd->ESID) return GF_NOT_SUPPORTED; - ctx->ES_ID = esd->ESID; - - while ((d = (GF_Descriptor*)gf_list_enum(esd->extensionDescriptors, &i))) { - if (d->tag == GF_ODF_AUX_VIDEO_DATA) { - ctx->aux_type = ((GF_AuxVideoDescriptor*)d)->aux_video_type; - break; - } - } - return GF_OK; -} -static GF_Err PNG_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - PNGCTX(); - if (ctx->ES_ID != ES_ID) return GF_BAD_PARAM; - ctx->ES_ID = ES_ID; - return GF_OK; -} -static GF_Err PNG_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - PNGCTX(); - switch (capability->CapCode) { - case GF_CODEC_WIDTH: - if (ctx->aux_type==3) { - capability->cap.valueInt = ctx->width/2; - } else { - capability->cap.valueInt = ctx->width; - } - break; - case GF_CODEC_HEIGHT: - capability->cap.valueInt = ctx->height; - break; - case GF_CODEC_STRIDE: - capability->cap.valueInt = ctx->width * ctx->BPP; - break; - case GF_CODEC_FPS: - capability->cap.valueFloat = 0; - break; - case GF_CODEC_PIXEL_FORMAT: - capability->cap.valueInt = ctx->pixel_format; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = ctx->out_size; - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = 0; - break; - case GF_CODEC_BUFFER_MAX: - capability->cap.valueInt = IMG_CM_SIZE; - break; - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 0; - break; - case GF_CODEC_PAR: - capability->cap.valueInt = 0; - break; - case GF_CODEC_REORDER: - capability->cap.valueInt = 0; - break; - default: - return GF_NOT_SUPPORTED; - } - return GF_OK; -} -static GF_Err PNG_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - /*return unsupported to avoid confusion by the player (like color space changing ...) */ - return GF_NOT_SUPPORTED; -} - - -static GF_Err PNG_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ -#ifndef GPAC_DISABLE_AV_PARSERS - GF_Err e; - PNGCTX(); - - e = gf_img_png_dec(inBuffer, inBufferLength, &ctx->width, &ctx->height, &ctx->pixel_format, outBuffer, outBufferLength); - - switch (ctx->pixel_format) { - case GF_PIXEL_GREYSCALE: - ctx->BPP = 1; - break; - case GF_PIXEL_ALPHAGREY: - ctx->BPP = 2; - break; - case GF_PIXEL_RGB_24: - ctx->BPP = 3; - if (ctx->aux_type==3) ctx->pixel_format = GF_PIXEL_RGBS; - break; - case GF_PIXEL_RGBA: - case GF_PIXEL_RGBD: - ctx->BPP = 4; - if (ctx->aux_type==1) ctx->pixel_format = GF_PIXEL_RGBD; - else if (ctx->aux_type==2) ctx->pixel_format = GF_PIXEL_RGBDS; - else if (ctx->aux_type==3) ctx->pixel_format = GF_PIXEL_RGBAS; - break; - } - ctx->out_size = *outBufferLength; - return e; -#else - return GF_NOT_SUPPORTED; -#endif //GPAC_DISABLE_AV_PARSERS -} - -static const char *PNG_GetCodecName(GF_BaseDecoder *dec) -{ - return "LibPNG"; -} - -Bool NewPNGDec(GF_BaseDecoder *ifcd) -{ - IMGDec *wrap = (IMGDec *) ifcd->privateStack; - PNGDec *dec = (PNGDec *) gf_malloc(sizeof(PNGDec)); - memset(dec, 0, sizeof(PNGDec)); - wrap->opaque = dec; - wrap->type = DEC_PNG; - - /*setup our own interface*/ - ifcd->AttachStream = PNG_AttachStream; - ifcd->DetachStream = PNG_DetachStream; - ifcd->GetCapabilities = PNG_GetCapabilities; - ifcd->SetCapabilities = PNG_SetCapabilities; - ifcd->GetName = PNG_GetCodecName; - ((GF_MediaDecoder *)ifcd)->ProcessData = PNG_ProcessData; - return GF_TRUE; -} - -void DeletePNGDec(GF_BaseDecoder *ifcg) -{ - PNGCTX(); - gf_free(ctx); -} diff --git a/modules/ios_cam/ios_cam.c b/modules/ios_cam/ios_cam.c index df253b9..16466fb 100644 --- a/modules/ios_cam/ios_cam.c +++ b/modules/ios_cam/ios_cam.c @@ -132,10 +132,7 @@ u32 getHeight(IOSCamCtx *read); static GF_Descriptor *CAM_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) { u32 trackID; - GF_ESD *esd; IOSCamCtx *read; - GF_ObjectDescriptor *od; - GF_BitStream *bs; char *buf; u32 buf_size; s32 color; @@ -147,6 +144,9 @@ static GF_Descriptor *CAM_GetServiceDesc(GF_InputService *plug, u32 expect_type, read->base_track_id = 0; if (trackID && (expect_type==GF_MEDIA_OBJECT_VIDEO) ) { + GF_ESD *esd; + GF_ObjectDescriptor *od; + GF_BitStream *bs; od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); od->objectDescriptorID = 1; @@ -223,13 +223,13 @@ GF_Err CAM_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) int* decodeYUV420SP( char* yuv420sp, int width, int height) { int frameSize = width * height; - int j, yp, uvp, i, y, y1192, r, g, b, u, v; + int j, yp, i, y, y1192, r, g, b; int ti, tj; int* rgb = (int*)gf_malloc(width*height*4); for (j = 0, yp = 0, tj=height-1; j < height; j++, tj--) { - uvp = frameSize + (j >> 1) * width, u = 0, v = 0; + int uvp = frameSize + (j >> 1) * width, u = 0, v = 0; for (i = 0, ti=0; i < width; i++, yp++, ti+=width) { y = (0xff & ((int) yuv420sp[yp])) - 16; @@ -315,7 +315,6 @@ void resumeCamera(IOSCamCtx *read) GF_Err CAM_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) { IOSCamCtx *read; - GF_BitStream *bs; char *buf; u32 buf_size; if (!plug || !plug->priv || !com) return GF_SERVICE_ERROR; @@ -348,6 +347,7 @@ GF_Err CAM_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) return 1<<16;//gf_isom_get_pixel_aspect_ratio(read->mov, ch->track, 1, &com->par.hSpacing, &com->par.vSpacing); case GF_NET_CHAN_GET_DSI: { + GF_BitStream *bs; s32 color; s32 stride; GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Cam get DSI\n")); diff --git a/modules/ios_mpegv/ios_mpegv.c b/modules/ios_mpegv/ios_mpegv.c index f39ee55..40bf267 100644 --- a/modules/ios_mpegv/ios_mpegv.c +++ b/modules/ios_mpegv/ios_mpegv.c @@ -51,7 +51,7 @@ typedef struct #define MPEGVSCTX MPEGVSensorContext *rc = (MPEGVSensorContext *)dr->udta -Bool MPEGVS_RegisterDevice(struct __input_device *dr, const char *urn, GF_BitStream *dsi, void (*AddField)(struct __input_device *_this, u32 fieldType, const char *name)) +Bool MPEGVS_RegisterDevice(struct __input_device *dr, const char *urn, const char *dsi, u32 dsi_size, void (*AddField)(struct __input_device *_this, u32 fieldType, const char *name)) { MPEGVSCTX; @@ -244,8 +244,8 @@ void MPEGVS_Stop(struct __input_device * dr) GF_InputSensorDevice* NewMPEGVSInputSesor() { - MPEGVSensorContext* ctx = NULL; - GF_InputSensorDevice* driv = NULL; + MPEGVSensorContext* ctx; + GF_InputSensorDevice* driv; driv = (GF_InputSensorDevice *) gf_malloc(sizeof(GF_InputSensorDevice)); memset(driv, 0, sizeof(GF_InputSensorDevice)); diff --git a/modules/ismacryp/Makefile b/modules/ismacryp/Makefile deleted file mode 100644 index 5235fcf..0000000 --- a/modules/ismacryp/Makefile +++ /dev/null @@ -1,48 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/ismacryp - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS=isma_ea.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_ismacryp$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols ismacryp.def -endif - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_ismacryp-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac_static $(LDFLAGS) -endif - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/ismacryp/isma_ea.c b/modules/ismacryp/isma_ea.c deleted file mode 100644 index 28715dc..0000000 --- a/modules/ismacryp/isma_ea.c +++ /dev/null @@ -1,658 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2012 - * All rights reserved - * - * This file is part of GPAC / LASeR decoder module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include <gpac/ismacryp.h> -#include <gpac/crypt.h> -#include <gpac/base_coding.h> -#include <gpac/download.h> -#include <gpac/internal/terminal_dev.h> -#include <gpac/modules/ipmp.h> - -#ifndef GPAC_DISABLE_MCRYPT - -#define OMA_DRM_MP4MC - - -enum -{ - ISMAEA_STATE_ERROR, - ISMAEA_STATE_SETUP, - ISMAEA_STATE_PLAY, -}; - -typedef struct -{ - GF_Crypt *crypt; - char key[16], salt[8]; - u64 last_IV; - u32 state; - u32 nb_allow_play; - Bool is_oma; - u32 preview_range; - /*for Common Encryption*/ - Bool is_cenc; - Bool is_cbc; - Bool first_crypted_samp; - u32 KID_count; - bin128 *KIDs; - bin128 *keys; -} ISMAEAPriv; - - -static void ISMA_KMS_NetIO(void *cbck, GF_NETIO_Parameter *par) -{ -} - -static GF_Err ISMA_GetGPAC_KMS(ISMAEAPriv *priv, GF_Channel *ch, const char *kms_url) -{ - GF_Err e; - FILE *t; - GF_DownloadSession * sess; - if (!strnicmp(kms_url, "(ipmp)", 6)) return GF_NOT_SUPPORTED; - else if (!strnicmp(kms_url, "(uri)", 5)) kms_url += 5; - else if (!strnicmp(kms_url, "file://", 7)) kms_url += 7; - - /*try local*/ - t = (strstr(kms_url, "://") == NULL) ? gf_fopen(kms_url, "r") : NULL; - if (t) { - gf_fclose(t); - return gf_ismacryp_gpac_get_info(ch->esd->ESID, (char *)kms_url, priv->key, priv->salt); - } - /*note that gpac doesn't have TLS support -> not really usefull. As a general remark, ISMACryp - is supported as a proof of concept, crypto and IPMP being the last priority on gpac...*/ - GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[CENC/ISMA] Fetching ISMACryp key for channel %d\n", ch->esd->ESID) ); - - sess = gf_service_download_new(ch->service, kms_url, 0, ISMA_KMS_NetIO, ch); - if (!sess) return GF_IO_ERR; - /*start our download (threaded)*/ - gf_dm_sess_process(sess); - - while (1) { - e = gf_dm_sess_get_stats(sess, NULL, NULL, NULL, NULL, NULL, NULL); - if (e) break; - } - if (e==GF_EOS) { - e = gf_ismacryp_gpac_get_info(ch->esd->ESID, (char *) gf_dm_sess_get_cache_name(sess), priv->key, priv->salt); - } - gf_service_download_del(sess); - return e; -} - - -static GF_Err ISMA_Setup(ISMAEAPriv *priv, GF_IPMPEvent *evt) -{ - GF_Err e; - GF_ISMACrypConfig *cfg = (GF_ISMACrypConfig*)evt->config_data; - - priv->state = ISMAEA_STATE_ERROR; - - if (cfg->scheme_type != GF_4CC('i','A','E','C')) return GF_NOT_SUPPORTED; - if (cfg->scheme_version != 1) return GF_NOT_SUPPORTED; - - if (!cfg->kms_uri) return GF_NON_COMPLIANT_BITSTREAM; - - /*try to fetch the keys*/ - /*base64 inband encoding*/ - if (!strnicmp(cfg->kms_uri, "(key)", 5)) { - char data[100]; - gf_base64_decode((char*)cfg->kms_uri+5, (u32)strlen(cfg->kms_uri)-5, data, 100); - memcpy(priv->key, data, sizeof(char)*16); - memcpy(priv->salt, data+16, sizeof(char)*8); - } - /*hexadecimal inband encoding*/ - else if (!strnicmp(cfg->kms_uri, "(key-hexa)", 10)) { - u32 v; - char szT[3], *k; - u32 i; - szT[2] = 0; - if (strlen(cfg->kms_uri) < 10+32+16) return GF_NON_COMPLIANT_BITSTREAM; - - k = (char *)cfg->kms_uri + 10; - for (i=0; i<16; i++) { - szT[0] = k[2*i]; - szT[1] = k[2*i + 1]; - sscanf(szT, "%X", &v); - priv->key[i] = v; - } - - k = (char *)cfg->kms_uri + 10 + 32; - for (i=0; i<8; i++) { - szT[0] = k[2*i]; - szT[1] = k[2*i + 1]; - sscanf(szT, "%X", &v); - priv->salt[i] = v; - } - } - /*MPEG4-IP KMS*/ - else if (!stricmp(cfg->kms_uri, "AudioKey") || !stricmp(cfg->kms_uri, "VideoKey")) { - if (!gf_ismacryp_mpeg4ip_get_info((char *) cfg->kms_uri, priv->key, priv->salt)) { - return GF_BAD_PARAM; - } - } - /*gpac default scheme is used, fetch file from KMS and load keys*/ - else if (cfg->scheme_uri && !stricmp(cfg->scheme_uri, "urn:gpac:isma:encryption_scheme")) { - e = ISMA_GetGPAC_KMS(priv, evt->channel, cfg->kms_uri); - if (e) return e; - } - /*hardcoded keys*/ - else { - static u8 mysalt[] = { 8,7,6,5,4,3,2,1, 0,0,0,0,0,0,0,0 }; - static u8 mykey[][16] = { - { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 - } - }; - memcpy(priv->salt, mysalt, sizeof(char)*8); - memcpy(priv->key, mykey, sizeof(char)*16); - } - priv->state = ISMAEA_STATE_SETUP; - //priv->nb_allow_play = 1; - return GF_OK; -} - -static GF_Err ISMA_Access(ISMAEAPriv *priv, GF_IPMPEvent *evt) -{ - GF_Err e; - char IV[16]; - - if (evt->event_type==GF_IPMP_TOOL_GRANT_ACCESS) { - if (priv->state != ISMAEA_STATE_SETUP) return GF_SERVICE_ERROR; - assert(!priv->crypt); - - //if (!priv->nb_allow_play) return GF_AUTHENTICATION_FAILURE; - //priv->nb_allow_play--; - - /*init decrypter*/ - priv->crypt = gf_crypt_open("AES-128", "CTR"); - if (!priv->crypt) return GF_IO_ERR; - - memset(IV, 0, sizeof(char)*16); - memcpy(IV, priv->salt, sizeof(char)*8); - e = gf_crypt_init(priv->crypt, priv->key, 16, IV); - if (e) return e; - - priv->state = ISMAEA_STATE_PLAY; - return GF_OK; - } - if (evt->event_type==GF_IPMP_TOOL_RELEASE_ACCESS) { - if (priv->state != ISMAEA_STATE_PLAY) return GF_SERVICE_ERROR; - if (priv->crypt) gf_crypt_close(priv->crypt); - priv->crypt = NULL; - priv->state = ISMAEA_STATE_SETUP; - return GF_OK; - } - return GF_BAD_PARAM; -} - -static GF_Err ISMA_ProcessData(ISMAEAPriv *priv, GF_IPMPEvent *evt) -{ - if (!priv->crypt) return GF_SERVICE_ERROR; - - if (!evt->is_encrypted) return GF_OK; - - /*resync IV*/ - if (!priv->last_IV || (priv->last_IV != evt->isma_BSO)) { - char IV[17]; - u64 count; - u32 remain; - GF_BitStream *bs; - count = evt->isma_BSO / 16; - remain = (u32) (evt->isma_BSO % 16); - - /*format IV to begin of counter*/ - bs = gf_bs_new(IV, 17, GF_BITSTREAM_WRITE); - gf_bs_write_u8(bs, 0); /*begin of counter*/ - gf_bs_write_data(bs, priv->salt, 8); - gf_bs_write_u64(bs, (s64) count); - gf_bs_del(bs); - gf_crypt_set_state(priv->crypt, IV, 17); - - /*decrypt remain bytes*/ - if (remain) { - char dummy[20]; - gf_crypt_decrypt(priv->crypt, dummy, remain); - } - priv->last_IV = evt->isma_BSO; - } - /*decrypt*/ - gf_crypt_decrypt(priv->crypt, evt->data, evt->data_size); - priv->last_IV += evt->data_size; - return GF_OK; -} - - -#ifdef OMA_DRM_MP4MC -static GF_Err OMA_DRM_Setup(ISMAEAPriv *priv, GF_IPMPEvent *evt) -{ - u32 hdr_pos; - GF_OMADRM2Config *cfg = (GF_OMADRM2Config*)evt->config_data; - - priv->state = ISMAEA_STATE_ERROR; - - if (cfg->scheme_type != GF_4CC('o','d','k','m')) return GF_NOT_SUPPORTED; - if (cfg->scheme_version != 0x00000200) return GF_NOT_SUPPORTED; - - hdr_pos = 0; - while (hdr_pos<cfg->oma_drm_textual_headers_len) { - u32 len; - char *sep; - if (!strncmp(cfg->oma_drm_textual_headers + hdr_pos, "PreviewRange", 12)) { - sep = strchr(cfg->oma_drm_textual_headers + hdr_pos, ':'); - if (sep) priv->preview_range = atoi(sep+1); - } - len = (u32) strlen(cfg->oma_drm_textual_headers + hdr_pos); - hdr_pos += len+1; - } - priv->is_oma = GF_TRUE; - - /*TODO: call DRM agent, fetch keys*/ - if (!cfg->kms_uri) return GF_NON_COMPLIANT_BITSTREAM; - priv->state = ISMAEA_STATE_SETUP; - //priv->nb_allow_play = 1; - - /*we have preview*/ - if (priv->preview_range) return GF_OK; - return GF_NOT_SUPPORTED; -} -#endif - -static GF_Err CENC_Setup(ISMAEAPriv *priv, GF_IPMPEvent *evt) -{ - GF_CENCConfig *cfg = (GF_CENCConfig*)evt->config_data; - u32 i; - Bool is_playing = (priv->state == ISMAEA_STATE_PLAY) ? GF_TRUE : GF_FALSE; - - priv->state = ISMAEA_STATE_ERROR; - - if ((cfg->scheme_type != GF_4CC('c', 'e', 'n', 'c')) && (cfg->scheme_type != GF_4CC('c','b','c','1')) && (cfg->scheme_type != GF_4CC('c', 'e', 'n', 's')) && (cfg->scheme_type != GF_4CC('c','b','c','s'))) - return GF_NOT_SUPPORTED; - if (cfg->scheme_version != 0x00010000) return GF_NOT_SUPPORTED; - - for (i = 0; i < cfg->PSSH_count; i++) { - GF_NetComDRMConfigPSSH *pssh = &cfg->PSSHs[i]; - char szSystemID[33]; - u32 j; - - memset(szSystemID, 0, 33); - for (j=0; j<16; j++) { - sprintf(szSystemID+j*2, "%02X", (unsigned char) pssh->SystemID[j]); - } - - /*SystemID for GPAC Player: 67706163-6365-6E63-6472-6D746F6F6C31*/ - if (strcmp(szSystemID, "6770616363656E6364726D746F6F6C31")) { - GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[CENC/ISMA] System ID %s not supported\n", szSystemID)); - continue; - } - else { - u8 cypherOffset; - bin128 cypherKey, cypherIV; - GF_Crypt *mc; - - /*GPAC DRM TEST system info, used to validate cypher offset in CENC packager - keyIDs as usual (before private data) - URL len on 8 bits - URL - keys, cyphered with our magic key :) - */ - cypherOffset = pssh->private_data[0] + 1; - gf_bin128_parse("0x6770616363656E6364726D746F6F6C31", cypherKey); - gf_bin128_parse("0x00000000000000000000000000000001", cypherIV); - - mc = gf_crypt_open("AES-128", "CTR"); - if (!mc) { - GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC/ISMA] Cannot open AES-128 CTR\n")); - return GF_IO_ERR; - } - gf_crypt_init(mc, cypherKey, 16, cypherIV); - gf_crypt_decrypt(mc, pssh->private_data+cypherOffset, pssh->private_data_size-cypherOffset); - gf_crypt_close(mc); - - /*now we search a key*/ - priv->KID_count = pssh->KID_count; - if (priv->KIDs) { - gf_free(priv->KIDs); - priv->KIDs = NULL; - } - priv->KIDs = (bin128 *)gf_malloc(pssh->KID_count*sizeof(bin128)); - if (priv->keys) { - gf_free(priv->keys); - priv->keys = NULL; - } - priv->keys = (bin128 *)gf_malloc(pssh->KID_count*sizeof(bin128)); - - memmove(priv->KIDs, pssh->KIDs, pssh->KID_count*sizeof(bin128)); - memmove(priv->keys, pssh->private_data + cypherOffset, pssh->KID_count*sizeof(bin128)); - } - } - - if ((cfg->scheme_type == GF_4CC('c', 'e', 'n', 'c')) || (cfg->scheme_type == GF_4CC('c', 'e', 'n', 's'))) - priv->is_cenc = GF_TRUE; - else - priv->is_cbc = GF_TRUE; - - priv->state = is_playing ? ISMAEA_STATE_PLAY : ISMAEA_STATE_SETUP; - //priv->nb_allow_play = 1; - return GF_OK; -} - -static GF_Err CENC_Access(ISMAEAPriv *priv, GF_IPMPEvent *evt) -{ - if (evt->event_type==GF_IPMP_TOOL_GRANT_ACCESS) { - if (priv->state != ISMAEA_STATE_SETUP) return GF_SERVICE_ERROR; - assert(!priv->crypt); - - //if (!priv->nb_allow_play) return GF_AUTHENTICATION_FAILURE; - //priv->nb_allow_play--; - - /*open decrypter - we do NOT initialize decrypter; it wil be done when we decrypt the first crypted sample*/ - if (priv->is_cenc) - priv->crypt = gf_crypt_open("AES-128", "CTR"); - else - priv->crypt = gf_crypt_open("AES-128", "CBC"); - if (!priv->crypt) return GF_IO_ERR; - - priv->first_crypted_samp = GF_TRUE; - - priv->state = ISMAEA_STATE_PLAY; - return GF_OK; - } - if (evt->event_type==GF_IPMP_TOOL_RELEASE_ACCESS) { - if (priv->state != ISMAEA_STATE_PLAY) return GF_SERVICE_ERROR; - if (priv->crypt) gf_crypt_close(priv->crypt); - priv->crypt = NULL; - priv->state = ISMAEA_STATE_SETUP; - return GF_OK; - } - return GF_BAD_PARAM; -} - -static GF_Err CENC_ProcessData(ISMAEAPriv *priv, GF_IPMPEvent *evt) -{ - GF_Err e; - GF_BitStream *pleintext_bs, *cyphertext_bs, *sai_bs; - char IV[17]; - bin128 KID; - char *buffer; - u32 max_size, i, subsample_count; - GF_CENCSampleAuxInfo *sai; - - pleintext_bs = cyphertext_bs = sai_bs = NULL; - buffer = NULL; - max_size = 4096; - - if (!priv->crypt) return GF_SERVICE_ERROR; - - if (!evt->is_encrypted) return GF_OK; - - cyphertext_bs = gf_bs_new(evt->data, evt->data_size, GF_BITSTREAM_READ); - sai_bs = gf_bs_new(evt->sai, evt->saiz, GF_BITSTREAM_READ); - pleintext_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - buffer = (char*)gf_malloc(sizeof(char) * max_size); - - sai = (GF_CENCSampleAuxInfo *)gf_malloc(sizeof(GF_CENCSampleAuxInfo)); - if (!sai) { - e = GF_IO_ERR; - goto exit; - } - memset(sai, 0, sizeof(GF_CENCSampleAuxInfo)); - sai->IV_size = evt->IV_size; - /*read sample auxiliary information from bitstream*/ - gf_bs_read_data(sai_bs, (char *)KID, 16); - gf_bs_read_data(sai_bs, (char *)sai->IV, sai->IV_size); - sai->subsample_count = gf_bs_read_u16(sai_bs); - if (sai->subsample_count) { - sai->subsamples = (GF_CENCSubSampleEntry *)gf_malloc(sai->subsample_count*sizeof(GF_CENCSubSampleEntry)); - for (i = 0; i < sai->subsample_count; i++) { - sai->subsamples[i].bytes_clear_data = gf_bs_read_u16(sai_bs); - sai->subsamples[i].bytes_encrypted_data = gf_bs_read_u32(sai_bs); - } - } - - - for (i = 0; i < priv->KID_count; i++) { - if (!strncmp((const char *)KID, (const char *)priv->KIDs[i], 16)) { - memmove(priv->key, priv->keys[i], 16); - break; - } - } - - if (priv->first_crypted_samp) { - if (evt->IV_size) { - memmove(IV, sai->IV, sai->IV_size); - if (sai->IV_size == 8) - memset(IV+8, 0, sizeof(char)*8); - } else { - memmove(IV, evt->constant_IV, evt->constant_IV_size); - if (evt->constant_IV_size == 8) - memset(IV+8, 0, sizeof(char)*8); - } - e = gf_crypt_init(priv->crypt, priv->key, 16, IV); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Cannot initialize AES-128 AES-128 %s (%s)\n", priv->is_cenc ? "CTR" : "CBC", gf_error_to_string(e)) ); - e = GF_IO_ERR; - goto exit; - } - priv->first_crypted_samp = GF_FALSE; - } else { - if (priv->is_cenc) { - GF_BitStream *bs; - bs = gf_bs_new(IV, 17, GF_BITSTREAM_WRITE); - gf_bs_write_u8(bs, 0); /*begin of counter*/ - gf_bs_write_data(bs,(char *)sai->IV, sai->IV_size); - if (sai->IV_size == 8) - gf_bs_write_u64(bs, 0); /*0-padded if IV_size == 8*/ - gf_bs_del(bs); - gf_crypt_set_state(priv->crypt, IV, 17); - } - e = gf_crypt_set_key(priv->crypt, priv->key, 16, IV); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Cannot set key AES-128 %s (%s)\n", priv->is_cenc ? "CTR" : "CBC", gf_error_to_string(e)) ); - e = GF_IO_ERR; - goto exit; - } - } - - //sub-sample encryption - if (sai->subsample_count) { - subsample_count = 0; - while (gf_bs_available(cyphertext_bs)) { - if (subsample_count >= sai->subsample_count) - break; - if (!evt->IV_size) { - memmove(IV, evt->constant_IV, evt->constant_IV_size); - if (evt->constant_IV_size == 8) - memset(IV+8, 0, sizeof(char)*8); - gf_crypt_set_state(priv->crypt, IV, 16); - } - - /*read clear data and write it to pleintext bitstream*/ - if (max_size < sai->subsamples[subsample_count].bytes_clear_data) { - buffer = (char*)gf_realloc(buffer, sizeof(char)*sai->subsamples[subsample_count].bytes_clear_data); - max_size = sai->subsamples[subsample_count].bytes_clear_data; - } - gf_bs_read_data(cyphertext_bs, buffer, sai->subsamples[subsample_count].bytes_clear_data); - gf_bs_write_data(pleintext_bs, buffer, sai->subsamples[subsample_count].bytes_clear_data); - - /*now read encrypted data, decrypted it and write to pleintext bitstream*/ - if (max_size < sai->subsamples[subsample_count].bytes_encrypted_data) { - buffer = (char*)gf_realloc(buffer, sizeof(char)*sai->subsamples[subsample_count].bytes_encrypted_data); - max_size = sai->subsamples[subsample_count].bytes_encrypted_data; - } - gf_bs_read_data(cyphertext_bs, buffer, sai->subsamples[subsample_count].bytes_encrypted_data); - //pattern decryption - if (evt->crypt_byte_block && evt->skip_byte_block) { - u32 pos = 0; - u32 res = sai->subsamples[subsample_count].bytes_encrypted_data; - while (res) { - gf_crypt_decrypt(priv->crypt, buffer+pos, res >= (u32) (16*evt->crypt_byte_block) ? 16*evt->crypt_byte_block : res); - if (res >= (u32) (16 * (evt->crypt_byte_block + evt->skip_byte_block))) { - pos += 16 * (evt->crypt_byte_block + evt->skip_byte_block); - res -= 16 * (evt->crypt_byte_block + evt->skip_byte_block); - } else { - res = 0; - } - } - } else { - gf_crypt_decrypt(priv->crypt, buffer, sai->subsamples[subsample_count].bytes_encrypted_data); - } - gf_bs_write_data(pleintext_bs, buffer, sai->subsamples[subsample_count].bytes_encrypted_data); - - subsample_count++; - } - if (buffer) gf_free(buffer); - gf_bs_get_content(pleintext_bs, &buffer, &evt->data_size); - } - //full sample encryption - else { - if (max_size < evt->data_size) { - buffer = (char*)gf_realloc(buffer, sizeof(char)*evt->data_size); - } - gf_bs_read_data(cyphertext_bs, buffer, evt->data_size); - if (priv->is_cenc) { - gf_crypt_decrypt(priv->crypt, buffer, evt->data_size); - } else { - u32 ret = evt->data_size % 16; - if (evt->data_size >= 16) { - gf_crypt_decrypt(priv->crypt, buffer, evt->data_size-ret); - } - } - } - memmove(evt->data, buffer, evt->data_size); - -exit: - if (pleintext_bs) gf_bs_del(pleintext_bs); - if (sai_bs) gf_bs_del(sai_bs); - if (cyphertext_bs) gf_bs_del(cyphertext_bs); - if (buffer) gf_free(buffer); - if (sai && sai->subsamples) gf_free(sai->subsamples); - if (sai) gf_free(sai); - return e; -} - - -static GF_Err IPMP_Process(GF_IPMPTool *plug, GF_IPMPEvent *evt) -{ - ISMAEAPriv *priv = (ISMAEAPriv *)plug->udta; - - switch (evt->event_type) { - case GF_IPMP_TOOL_SETUP: - if (evt->config_data_code == GF_4CC('i','s','m','a')) return ISMA_Setup(priv, evt); -#ifdef OMA_DRM_MP4MC - if (evt->config_data_code == GF_4CC('o','d','r','m')) return OMA_DRM_Setup(priv, evt); -#endif - if((evt->config_data_code == GF_4CC('c', 'e', 'n', 'c')) || (evt->config_data_code == GF_4CC('c','b','c','1')) || (evt->config_data_code == GF_4CC('c', 'e', 'n', 's')) || (evt->config_data_code == GF_4CC('c','b','c','s'))) - return CENC_Setup(priv, evt); - return GF_NOT_SUPPORTED; - - case GF_IPMP_TOOL_GRANT_ACCESS: - case GF_IPMP_TOOL_RELEASE_ACCESS: - if (priv->is_cenc || priv->is_cbc) { - return CENC_Access(priv, evt); - } else if (priv->is_oma) { - } else { - return ISMA_Access(priv, evt); - } - break; - case GF_IPMP_TOOL_PROCESS_DATA: - if (priv->is_cenc || priv->is_cbc) { - return CENC_ProcessData(priv, evt); - } else if (priv->is_oma) { - if (evt->is_encrypted) { - evt->restart_requested = GF_TRUE; - return GF_EOS; - } - return GF_OK; - } - return ISMA_ProcessData(priv, evt); - } - return GF_OK; -} - -void DeleteIPMPTool(GF_IPMPTool *plug) -{ - ISMAEAPriv *priv = (ISMAEAPriv *)plug->udta; - /*in case something went wrong*/ - if (priv->crypt) gf_crypt_close(priv->crypt); - if (priv->KIDs) gf_free(priv->KIDs); - if (priv->keys) gf_free(priv->keys); - gf_free(priv); - gf_free(plug); -} - -GF_IPMPTool *NewIPMPTool() -{ - ISMAEAPriv *priv; - GF_IPMPTool *tmp; - - GF_SAFEALLOC(tmp, GF_IPMPTool); - if (!tmp) return NULL; - GF_SAFEALLOC(priv, ISMAEAPriv); - tmp->udta = priv; - tmp->process = IPMP_Process; - GF_REGISTER_MODULE_INTERFACE(tmp, GF_IPMP_TOOL_INTERFACE, "GPAC ISMACryp tool", "gpac distribution") - return (GF_IPMPTool *) tmp; -} - -#endif /*GPAC_DISABLE_MCRYPT*/ - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifndef GPAC_DISABLE_MCRYPT - GF_IPMP_TOOL_INTERFACE, -#endif - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - switch (InterfaceType) { -#ifndef GPAC_DISABLE_MCRYPT - case GF_IPMP_TOOL_INTERFACE: - return (GF_BaseInterface *)NewIPMPTool(); -#endif - default: - return NULL; - } -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#ifndef GPAC_DISABLE_MCRYPT - case GF_IPMP_TOOL_INTERFACE: - DeleteIPMPTool((GF_IPMPTool *)ifce); - break; -#endif - } -} - -GPAC_MODULE_STATIC_DECLARATION( isma_ea ) - diff --git a/modules/isom_in/Makefile b/modules/isom_in/Makefile deleted file mode 100644 index 21dd5e6..0000000 --- a/modules/isom_in/Makefile +++ /dev/null @@ -1,49 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/isom_in - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS= load.o read.o read_ch.o isom_cache.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_isom_in$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols isom_in.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) -L../../bin/gcc -lgpac $(EXTRALIBS) $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_isom_in-static$(DYN_LIB_SUFFIX) $(OBJS) -L../../bin/gcc -lgpac_static $(EXTRALIBS) $(LDFLAGS) -endif - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/isom_in/isom_cache.c b/modules/isom_in/isom_cache.c deleted file mode 100644 index e7cd6aa..0000000 --- a/modules/isom_in/isom_cache.c +++ /dev/null @@ -1,295 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / MP4 cache module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "isom_in.h" - -#ifndef GPAC_DISABLE_ISOM_WRITE - -static GF_Err ISOW_Open(GF_StreamingCache *mc, GF_ClientService *serv, const char *location_and_name, Bool keep_existing_files) -{ - char szRoot[GF_MAX_PATH], szPath[GF_MAX_PATH], *ext; - ISOMReader *cache = (ISOMReader *)mc->priv; - if (cache->mov || cache->service) return GF_BAD_PARAM; - - strcpy(szRoot, location_and_name); - ext = strrchr(szRoot, '.'); - if (ext) ext[0] = 0; - strcpy(szPath, szRoot); - strcat(szPath, ".mp4"); - if (keep_existing_files) { - FILE *f = gf_fopen(szPath, "rb"); - if (f) { - u32 i=0; - gf_fclose(f); - while (1) { - sprintf(szPath, "%s_%04d.mp4", szRoot, i); - f = gf_fopen(szPath, "rb"); - if (!f) break; - gf_fclose(f); - i++; - } - } - } - - /*create a new movie in write mode (eg no editing)*/ - cache->mov = gf_isom_open(szPath, GF_ISOM_OPEN_WRITE, NULL); - if (!cache->mov) return gf_isom_last_error(NULL); - cache->service = serv; - return GF_OK; -} - -static GF_Err ISOW_Close(GF_StreamingCache *mc, Bool delete_cache) -{ - GF_Err e; - ISOMReader *cache = (ISOMReader *)mc->priv; - if (!cache->mov || !cache->service) return GF_BAD_PARAM; - - while (gf_list_count(cache->channels)) { - ISOMChannel *ch = (ISOMChannel *)gf_list_get(cache->channels, 0); - gf_list_rem(cache->channels, 0); - if (ch->cache_sample) { - gf_isom_add_sample(cache->mov, ch->track, 1, ch->cache_sample); - gf_isom_sample_del(&ch->cache_sample); - } - gf_free(ch); - } - if (delete_cache) { - gf_isom_delete(cache->mov); - e = GF_OK; - } else { - e = gf_isom_close(cache->mov); - } - cache->mov = NULL; - cache->service = NULL; - return e; -} -static GF_Err ISOW_Write(GF_StreamingCache *mc, LPNETCHANNEL ch, char *data, u32 data_size, GF_SLHeader *sl_hdr) -{ - ISOMChannel *mch; - GF_ESD *esd; - u32 di, mtype; - u64 DTS, CTS; - ISOMReader *cache = (ISOMReader *)mc->priv; - if (!cache->mov || !cache->service) return GF_BAD_PARAM; - - mch = isor_get_channel(cache, ch); - if (!mch) { - Bool mapped; - GF_NetworkCommand com; - com.base.on_channel = ch; - com.base.command_type = GF_NET_CHAN_GET_ESD; - gf_service_command(cache->service, &com, GF_OK); - if (!com.cache_esd.esd) return GF_SERVICE_ERROR; - - esd = (GF_ESD *)com.cache_esd.esd; - switch (esd->decoderConfig->streamType) { - case GF_STREAM_OD: - mtype = GF_ISOM_MEDIA_OD; - break; - case GF_STREAM_SCENE: - mtype = GF_ISOM_MEDIA_SCENE; - break; - case GF_STREAM_VISUAL: - mtype = GF_ISOM_MEDIA_VISUAL; - break; - case GF_STREAM_AUDIO: - mtype = GF_ISOM_MEDIA_AUDIO; - break; - case GF_STREAM_MPEG7: - mtype = GF_ISOM_MEDIA_MPEG7; - break; - case GF_STREAM_OCI: - mtype = GF_ISOM_MEDIA_OCI; - break; - case GF_STREAM_IPMP: - mtype = GF_ISOM_MEDIA_IPMP; - break; - case GF_STREAM_MPEGJ: - mtype = GF_ISOM_MEDIA_MPEGJ; - break; - case GF_STREAM_TEXT: - mtype = GF_ISOM_MEDIA_TEXT; - break; - default: - return GF_NOT_SUPPORTED; - } - GF_SAFEALLOC(mch, ISOMChannel); - if (!mch) { - return GF_OUT_OF_MEM; - } - mch->time_scale = esd->slConfig->timestampResolution; - mch->streamType = esd->decoderConfig->streamType; - mch->track = gf_isom_new_track(cache->mov, com.cache_esd.esd->ESID, mtype, mch->time_scale); - mch->is_playing = GF_TRUE; - mch->channel = ch; - mch->owner = cache; - gf_isom_set_track_enabled(cache->mov, mch->track, 1); - /*translate 3GP streams to MP4*/ - mapped = GF_FALSE; - if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_MEDIA_GENERIC) { - char szCode[5]; - strncpy(szCode, esd->decoderConfig->decoderSpecificInfo->data, 4); - szCode[4]=0; - if (!stricmp(szCode, "samr") || !stricmp(szCode, "amr ") || !stricmp(szCode, "sawb")) { - GF_3GPConfig amrc; - mapped = GF_TRUE; - memset(&amrc, 0, sizeof(GF_3GPConfig)); - - amrc.frames_per_sample = (u32) esd->decoderConfig->decoderSpecificInfo->data[13]; - amrc.type = (!stricmp(szCode, "sawb")) ? GF_ISOM_SUBTYPE_3GP_AMR_WB : GF_ISOM_SUBTYPE_3GP_AMR; - amrc.vendor = GF_4CC('G','P','A','C'); - gf_isom_3gp_config_new(cache->mov, mch->track, &amrc, NULL, NULL, &di); - } else if (!stricmp(szCode, "h263")) { - GF_3GPConfig h263c; - memset(&h263c, 0, sizeof(GF_3GPConfig)); - h263c.type = GF_ISOM_SUBTYPE_3GP_H263; - h263c.vendor = GF_4CC('G','P','A','C'); - gf_isom_3gp_config_new(cache->mov, mch->track, &h263c, NULL, NULL, &di); - mapped = GF_TRUE; - } - } - if (!mapped) gf_isom_new_mpeg4_description(cache->mov, mch->track, esd, NULL, NULL, &di); - if (com.cache_esd.is_iod_stream) gf_isom_add_track_to_root_od(cache->mov, mch->track); - gf_list_add(cache->channels, mch); - } - - /*first sample, cache it*/ - if (!mch->cache_sample) { - mch->cache_seed_ts = sl_hdr->decodingTimeStamp; - mch->cache_sample = gf_isom_sample_new(); - mch->cache_sample->IsRAP = sl_hdr->randomAccessPointFlag; - mch->cache_sample->dataLength = data_size; - mch->cache_sample->data = (char*)gf_malloc(sizeof(char)*data_size); - memcpy(mch->cache_sample->data, data, sizeof(char)*data_size); - return GF_OK; - } - - /*adjust DTS/CTS*/ - DTS = sl_hdr->decodingTimeStamp - mch->cache_seed_ts; - - if ((mch->streamType==GF_STREAM_VISUAL) && (DTS<=mch->cache_sample->DTS)) { - assert(DTS>mch->prev_dts); - CTS = mch->cache_sample->DTS + mch->cache_sample->CTS_Offset; - mch->cache_sample->CTS_Offset = 0; - - /*first time, shift all CTS*/ - if (!mch->frame_cts_offset) { - u32 i, count = gf_isom_get_sample_count(cache->mov, mch->track); - mch->frame_cts_offset = (u32) (DTS-mch->prev_dts); - for (i=0; i<count; i++) { - gf_isom_modify_cts_offset(cache->mov, mch->track, i+1, mch->frame_cts_offset); - } - mch->cache_sample->CTS_Offset += mch->frame_cts_offset; - } - mch->cache_sample->DTS = mch->prev_dts + mch->frame_cts_offset; - mch->cache_sample->CTS_Offset += (u32) (CTS-mch->cache_sample->DTS); - } - /*deal with reference picture insertion: if no CTS offset and biggest CTS until now, this is - a reference insertion - we must check that in order to make sure we have strictly increasing DTSs*/ - if (mch->max_cts && !mch->cache_sample->CTS_Offset && (mch->cache_sample->DTS+mch->cache_sample->CTS_Offset > mch->max_cts)) { - assert(mch->cache_sample->DTS > mch->prev_dts + mch->frame_cts_offset); - CTS = mch->cache_sample->DTS + mch->cache_sample->CTS_Offset; - mch->cache_sample->DTS = mch->prev_dts + mch->frame_cts_offset; - mch->cache_sample->CTS_Offset = (u32) (CTS-mch->cache_sample->DTS); - } - if (mch->cache_sample->CTS_Offset) - mch->max_cts = mch->cache_sample->DTS+mch->cache_sample->CTS_Offset; - - /*add cache*/ - gf_isom_add_sample(cache->mov, mch->track, 1, mch->cache_sample); - assert(!mch->prev_dts || (mch->prev_dts < mch->cache_sample->DTS)); - mch->prev_dts = mch->cache_sample->DTS; - mch->duration = MAX(mch->max_cts, mch->prev_dts); - gf_isom_sample_del(&mch->cache_sample); - - /*store sample*/ - mch->cache_sample = gf_isom_sample_new(); - mch->cache_sample->IsRAP = sl_hdr->randomAccessPointFlag; - mch->cache_sample->DTS = DTS + mch->frame_cts_offset; - mch->cache_sample->CTS_Offset = (u32) (sl_hdr->compositionTimeStamp - mch->cache_seed_ts - DTS); - mch->cache_sample->dataLength = data_size; - mch->cache_sample->data = (char*)gf_malloc(sizeof(char)*data_size); - memcpy(mch->cache_sample->data, data, sizeof(char)*data_size); - return GF_OK; -} -static GF_Err ISOW_ServiceCommand(GF_StreamingCache *mc, GF_NetworkCommand *com) -{ - ISOMReader *cache = (ISOMReader *)mc->priv; - if (!cache->mov || !cache->service) return GF_BAD_PARAM; - - return GF_OK; -} -static GF_Err ISOW_ChannelGetSLP(GF_StreamingCache *mc, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, GF_SLHeader *out_sl_hdr, Bool *sl_compressed, GF_Err *out_reception_status, Bool *is_new_data) -{ - ISOMReader *cache = (ISOMReader *)mc->priv; - if (!cache->mov || !cache->service) return GF_BAD_PARAM; - - return GF_OK; -} -static GF_Err ISOW_ChannelReleaseSLP(GF_StreamingCache *mc, LPNETCHANNEL channel) -{ - ISOMReader *cache = (ISOMReader *)mc->priv; - if (!cache->mov || !cache->service) return GF_BAD_PARAM; - - return GF_OK; -} - -GF_BaseInterface *isow_load_cache() -{ - ISOMReader *cache; - GF_StreamingCache *plug; - GF_SAFEALLOC(plug, GF_StreamingCache); - if (!plug) return NULL; - GF_REGISTER_MODULE_INTERFACE(plug, GF_STREAMING_MEDIA_CACHE, "GPAC IsoMedia Cache", "gpac distribution") - - GF_SAFEALLOC(cache, ISOMReader); - if (!cache) { - gf_free(plug); - return NULL; - } - cache->channels = gf_list_new(); - plug->priv = cache; - - plug->Open = ISOW_Open; - plug->Close = ISOW_Close; - plug->Write = ISOW_Write; - plug->ChannelGetSLP = ISOW_ChannelGetSLP; - plug->ChannelReleaseSLP = ISOW_ChannelReleaseSLP; - plug->ServiceCommand = ISOW_ServiceCommand; - - return (GF_BaseInterface *) plug; -} - -void isow_delete_cache(GF_BaseInterface *bi) -{ - GF_StreamingCache *mc = (GF_StreamingCache*) bi; - ISOMReader *cache = (ISOMReader *)mc->priv; - gf_list_del(cache->channels); - gf_free(cache); - gf_free(bi); -} - -#endif /*GPAC_DISABLE_ISOM_WRITE*/ - diff --git a/modules/isom_in/isom_in.h b/modules/isom_in/isom_in.h deleted file mode 100644 index 289a007..0000000 --- a/modules/isom_in/isom_in.h +++ /dev/null @@ -1,166 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / MP4 reader module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#ifndef _ISMO_IN_H_ -#define _ISMO_IN_H_ - -#include <gpac/constants.h> -#include <gpac/modules/service.h> -#include <gpac/thread.h> - -#ifndef GPAC_DISABLE_ISOM - -#include <gpac/media_tools.h> -/* - reader module - -*/ - -//#define DASH_USE_PULL - -typedef struct -{ - GF_InputService *input; - /*the service we're responsible for*/ - GF_ClientService *service; - - /*current channels*/ - GF_List *channels; - - /*input file*/ - GF_ISOFile *mov; - u32 time_scale; - u32 nb_playing; - - /*remote file handling*/ - GF_DownloadSession * dnload; - u64 missing_bytes, last_size; - Bool no_service_desc; - u32 play_only_track_id; - - /*0: not fragmented - 1 fragmented - 2 fragmented and last fragment received*/ - u32 frag_type; - Bool waiting_for_data, reset_frag_state; - GF_Mutex *segment_mutex; - - u32 pending_scalable_enhancement_segment_index; - - Bool use_memory; - /*0: segment is not opened - 1: segment is opened but can be refreshed incomplete file) - 2: segment is fully parsed, no need for refresh*/ - u32 seg_opened; - Bool drop_next_segment; - Bool in_data_flush; - u32 has_pending_segments, nb_force_flush; - - Bool clock_discontinuity; - Bool disconnected; - Bool no_order_check; - - u64 last_sender_ntp, cts_for_last_sender_ntp; - Double remain_at_buffering_start; - Bool buffering; -} ISOMReader; - - -typedef struct -{ - u32 track, track_id; - /*base track if scalable media, 0 otherwise*/ - u32 base_track; - u32 next_track; - LPNETCHANNEL channel; - ISOMReader *owner; - u64 duration; - - Bool wait_for_segment_switch; - /*current sample*/ - GF_ISOSample *sample; - GF_SLHeader current_slh; - GF_Err last_state; - - Bool is_pulling; - - Bool has_edit_list; - u32 sample_num; - s64 dts_offset; - Bool do_dts_shift_test; - /*for edit lists*/ - u32 edit_sync_frame; - u64 sample_time, start, end; - Double speed; - - u32 time_scale; - Bool to_init, is_playing, has_rap; - u8 streamType; - - Bool is_encrypted, is_cenc; - - /*cache stuff*/ - u64 cache_seed_ts; - u32 frame_cts_offset; - u64 prev_dts, max_cts; - GF_ISOSample *cache_sample; - - Bool buffering; - u32 buffer_min, buffer_max; - - Bool disable_seek; - u32 nalu_extract_mode; - - u32 last_sample_desc_index; -} ISOMChannel; - -void isor_reset_reader(ISOMChannel *ch); -void isor_reader_get_sample(ISOMChannel *ch); -void isor_reader_release_sample(ISOMChannel *ch); - -ISOMChannel *isor_get_channel(ISOMReader *reader, LPNETCHANNEL channel); - -GF_InputService *isor_client_load(); -void isor_client_del(GF_BaseInterface *bi); - -GF_Descriptor *isor_emulate_iod(ISOMReader *read); -/*uses nero chapter info and remaps to MPEG-4 OCI if no OCI present in descriptor*/ -void isor_emulate_chapters(GF_ISOFile *file, GF_InitialObjectDescriptor *iod); - -void isor_declare_objects(ISOMReader *read); - - -void send_proxy_command(ISOMReader *read, Bool is_disconnect, Bool is_add_media, GF_Err e, GF_Descriptor *desc, LPNETCHANNEL channel); - -void isor_send_cenc_config(ISOMChannel *ch); - -void isor_flush_data(ISOMReader *read, Bool check_buffer_level, Bool is_chunk_flush); - -#ifndef GPAC_DISABLE_ISOM_WRITE -GF_BaseInterface *isow_load_cache(); -void isow_delete_cache(GF_BaseInterface *bi); -#endif - -#endif /*GPAC_DISABLE_ISOM*/ - -#endif /*_ISMO_IN_H_*/ - diff --git a/modules/isom_in/load.c b/modules/isom_in/load.c deleted file mode 100644 index 9678a71..0000000 --- a/modules/isom_in/load.c +++ /dev/null @@ -1,331 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / MP4 reader module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "isom_in.h" -#include <gpac/iso639.h> - -#ifndef GPAC_DISABLE_ISOM - -void isor_emulate_chapters(GF_ISOFile *file, GF_InitialObjectDescriptor *iod) -{ - GF_Segment *prev_seg; - u64 prev_start; - u64 start; - u32 i, count; - if (!iod || gf_list_count(iod->OCIDescriptors)) return; - count = gf_isom_get_chapter_count(file, 0); - if (!count) return; - - prev_seg = NULL; - start = prev_start = 0; - for (i=0; i<count; i++) { - const char *name; - GF_Segment *seg; - gf_isom_get_chapter(file, 0, i+1, &start, &name); - seg = (GF_Segment *) gf_odf_desc_new(GF_ODF_SEGMENT_TAG); - seg->startTime = (Double) (s64) start; - seg->startTime /= 1000; - seg->SegmentName = gf_strdup(name); - gf_list_add(iod->OCIDescriptors, seg); - if (prev_seg) { - prev_seg->Duration = (Double) (s64) (start - prev_start); - prev_seg->Duration /= 1000; - } else if (start) { - prev_seg = (GF_Segment *) gf_odf_desc_new(GF_ODF_SEGMENT_TAG); - prev_seg->startTime = 0; - prev_seg->Duration = (Double) (s64) (start); - prev_seg->Duration /= 1000; - gf_list_insert(iod->OCIDescriptors, prev_seg, 0); - } - prev_seg = seg; - prev_start = start; - } - if (prev_seg) { - start = 1000*gf_isom_get_duration(file); - start /= gf_isom_get_timescale(file); - if (start>prev_start) { - prev_seg->Duration = (Double) (s64) (start - prev_start); - prev_seg->Duration /= 1000; - } - } -} - -/*emulate a default IOD for all other files (3GP, weird MP4, QT )*/ -GF_Descriptor *isor_emulate_iod(ISOMReader *read) -{ - /*generate an IOD with our private dynamic OD stream*/ - GF_InitialObjectDescriptor *fake_iod = (GF_InitialObjectDescriptor *) gf_odf_desc_new(GF_ODF_IOD_TAG); - isor_emulate_chapters(read->mov, fake_iod); - read->no_service_desc = GF_TRUE; - return (GF_Descriptor *)fake_iod; -} - -void isor_declare_objects(ISOMReader *read) -{ - GF_ObjectDescriptor *od; - GF_ESD *esd; - const char *tag; - u32 i, count, ocr_es_id, tlen, base_track, j, track_id; - Bool highest_stream; - - ocr_es_id = 0; - - /*TODO check for alternate tracks*/ - count = gf_isom_get_track_count(read->mov); - for (i=0; i<count; i++) { - u32 m_subtype; - if (!gf_isom_is_track_enabled(read->mov, i+1)) - continue; - - switch (gf_isom_get_media_type(read->mov, i+1)) { - case GF_ISOM_MEDIA_AUDIO: - case GF_ISOM_MEDIA_VISUAL: - case GF_ISOM_MEDIA_TEXT: - case GF_ISOM_MEDIA_SUBT: - case GF_ISOM_MEDIA_SUBPIC: - break; - case GF_ISOM_MEDIA_SCENE: - case GF_ISOM_MEDIA_OD: - if (read->no_service_desc) continue; - break; - default: - continue; - } - //some subtypes are not declared as readable objects - m_subtype = gf_isom_get_media_subtype(read->mov, i+1, 1); - switch (m_subtype) { - case GF_ISOM_SUBTYPE_HVT1: - continue; - default: - break; - } - - /*we declare only the highest video track (i.e the track we play)*/ - highest_stream = GF_TRUE; - track_id = gf_isom_get_track_id(read->mov, i+1); - if (read->play_only_track_id && (read->play_only_track_id != track_id)) continue; - - for (j = 0; j < count; j++) { - if (gf_isom_has_track_reference(read->mov, j+1, GF_ISOM_REF_SCAL, track_id) > 0) { - highest_stream = GF_FALSE; - break; - } - } - //no SCAL found, check for sbas (lhvc with avc base) - for (j = 0; j < count; j++) { - if (gf_isom_has_track_reference(read->mov, j+1, GF_ISOM_REF_BASE, track_id) > 0) { - highest_stream = GF_FALSE; - break; - } - } - - if ((gf_isom_get_media_type(read->mov, i+1) == GF_ISOM_MEDIA_VISUAL) && !highest_stream) - continue; - esd = gf_media_map_esd(read->mov, i+1); - if (esd) { - GF_ESD *base_esd=NULL; - Bool external_base=GF_FALSE; - gf_isom_get_reference(read->mov, i+1, GF_ISOM_REF_BASE, 1, &base_track); - - if (base_track) { - u32 base_subtype=0; - switch (m_subtype) { - case GF_ISOM_SUBTYPE_LHV1: - case GF_ISOM_SUBTYPE_LHE1: - base_subtype = gf_isom_get_media_subtype(read->mov, base_track, 1); - switch (base_subtype) { - case GF_ISOM_SUBTYPE_HVC1: - case GF_ISOM_SUBTYPE_HEV1: - case GF_ISOM_SUBTYPE_HVC2: - case GF_ISOM_SUBTYPE_HEV2: - break; - default: - external_base=GF_TRUE; - break; - } - } - if (external_base) { - base_esd = gf_media_map_esd(read->mov, base_track); - if (base_esd) esd->dependsOnESID = base_esd->ESID; - esd->has_scalable_layers = GF_TRUE; - } else { - esd->dependsOnESID=0; - switch (gf_isom_get_hevc_lhvc_type(read->mov, i+1, 1)) { - case GF_ISOM_HEVCTYPE_HEVC_LHVC: - case GF_ISOM_HEVCTYPE_LHVC_ONLY: - esd->has_scalable_layers = GF_TRUE; - break; - //this is likely temporal sublayer of base - case GF_ISOM_HEVCTYPE_HEVC_ONLY: - esd->has_scalable_layers = GF_FALSE; - if (gf_isom_get_reference_count(read->mov, i+1, GF_ISOM_REF_SCAL)<=0) { - base_esd = gf_media_map_esd(read->mov, base_track); - if (base_esd) esd->dependsOnESID = base_esd->ESID; - } - break; - } - } - } else { - switch (gf_isom_get_hevc_lhvc_type(read->mov, i+1, 1)) { - case GF_ISOM_HEVCTYPE_HEVC_LHVC: - case GF_ISOM_HEVCTYPE_LHVC_ONLY: - esd->has_scalable_layers = GF_TRUE; - break; - } - } - - if (!esd->langDesc) { - esd->langDesc = (GF_Language *) gf_odf_desc_new(GF_ODF_LANG_TAG); - gf_isom_get_media_language(read->mov, i+1, &esd->langDesc->full_lang_code); - } - - od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - od->service_ifce = read->input; - od->objectDescriptorID = 0; - - if (base_esd) { - if (!ocr_es_id) ocr_es_id = base_esd->ESID; - base_esd->OCRESID = ocr_es_id; - gf_list_add(od->ESDescriptors, base_esd); - } - if (!ocr_es_id) ocr_es_id = esd->ESID; - esd->OCRESID = ocr_es_id; - gf_list_add(od->ESDescriptors, esd); - - if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) { - send_proxy_command(read, GF_FALSE, GF_TRUE, GF_OK, (GF_Descriptor*)od, NULL); - } else { - gf_service_declare_media(read->service, (GF_Descriptor*)od, GF_TRUE); - } - } - } - /*if cover art, extract it in cache*/ - if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_COVER_ART, &tag, &tlen)==GF_OK) { - const char *cdir = gf_modules_get_option((GF_BaseInterface *)gf_service_get_interface(read->service), "General", "CacheDirectory"); - if (cdir) { - char szName[GF_MAX_PATH]; - const char *sep; - FILE *t; - sep = strrchr(gf_isom_get_filename(read->mov), '\\'); - if (!sep) sep = strrchr(gf_isom_get_filename(read->mov), '/'); - if (!sep) sep = gf_isom_get_filename(read->mov); - - if ((cdir[strlen(cdir)-1] != '\\') && (cdir[strlen(cdir)-1] != '/')) { - sprintf(szName, "%s/%s_cover.%s", cdir, sep, (tlen & 0x80000000) ? "png" : "jpg"); - } else { - sprintf(szName, "%s%s_cover.%s", cdir, sep, (tlen & 0x80000000) ? "png" : "jpg"); - } - - t = gf_fopen(szName, "wb"); - - if (t) { - Bool isom_contains_video = GF_FALSE; - - /*write cover data*/ - assert(!(tlen & 0x80000000)); - gf_fwrite(tag, tlen & 0x7FFFFFFF, 1, t); - gf_fclose(t); - - /*don't display cover art when video is present*/ - for (i=0; i<gf_isom_get_track_count(read->mov); i++) { - if (!gf_isom_is_track_enabled(read->mov, i+1)) - continue; - if (gf_isom_get_media_type(read->mov, i+1) == GF_ISOM_MEDIA_VISUAL) { - isom_contains_video = GF_TRUE; - break; - } - } - - if (!isom_contains_video) { - od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - od->service_ifce = read->input; - od->objectDescriptorID = GF_MEDIA_EXTERNAL_ID; - od->URLString = gf_strdup(szName); - if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) { - send_proxy_command(read, GF_FALSE, GF_TRUE, GF_OK, (GF_Descriptor*)od, NULL); - } else { - gf_service_declare_media(read->service, (GF_Descriptor*)od, GF_TRUE); - } - } - } - } - } - if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) { - send_proxy_command(read, GF_FALSE, GF_TRUE, GF_OK, NULL, NULL); - } else { - gf_service_declare_media(read->service, NULL, GF_FALSE); - } -} - -#endif /*GPAC_DISABLE_ISOM*/ - - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifndef GPAC_DISABLE_ISOM - GF_NET_CLIENT_INTERFACE, -#endif -#ifndef GPAC_DISABLE_ISOM_WRITE - GF_STREAMING_MEDIA_CACHE, -#endif - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ -#ifndef GPAC_DISABLE_ISOM - if (InterfaceType == GF_NET_CLIENT_INTERFACE) - return (GF_BaseInterface *)isor_client_load(); -#endif -#ifndef GPAC_DISABLE_ISOM_WRITE - if (InterfaceType == GF_STREAMING_MEDIA_CACHE) - return (GF_BaseInterface *)isow_load_cache(); -#endif - return NULL; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#ifndef GPAC_DISABLE_ISOM - case GF_NET_CLIENT_INTERFACE: - isor_client_del(ifce); - break; -#endif -#ifndef GPAC_DISABLE_ISOM_WRITE - case GF_STREAMING_MEDIA_CACHE: - isow_delete_cache(ifce); - break; -#endif - } -} - -GPAC_MODULE_STATIC_DECLARATION( isom ) diff --git a/modules/isom_in/read.c b/modules/isom_in/read.c deleted file mode 100644 index af34df1..0000000 --- a/modules/isom_in/read.c +++ /dev/null @@ -1,1282 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / IsoMedia reader module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "isom_in.h" - -#ifndef GPAC_DISABLE_ISOM - -#include <gpac/ismacryp.h> - -ISOMChannel *isor_get_channel(ISOMReader *reader, LPNETCHANNEL channel) -{ - u32 i=0; - ISOMChannel *ch; - while ((ch = (ISOMChannel *)gf_list_enum(reader->channels, &i))) { - if (ch->channel == channel) return ch; - } - return NULL; -} - -static void isor_delete_channel(ISOMReader *reader, ISOMChannel *ch) -{ - u32 i=0; - ISOMChannel *ch2; - while ((ch2 = (ISOMChannel *)gf_list_enum(reader->channels, &i))) { - if (ch2 == ch) { - isor_reset_reader(ch); - gf_free(ch); - gf_list_rem(reader->channels, i-1); - return; - } - } -} - -static GFINLINE Bool isor_is_local(const char *url) -{ - if (!strnicmp(url, "file://", 7)) return GF_TRUE; - if (!strnicmp(url, "gmem://", 7)) return GF_TRUE; - if (!strnicmp(url, "isobmff://", 10)) return GF_TRUE; - if (strstr(url, "://")) return GF_FALSE; - /*the rest is local (mounted on FS)*/ - return GF_TRUE; -} - -static const char * ISOR_MIME_TYPES[] = { - /* First one is specific because we probe */ - "application/x-isomedia", "*", "IsoMedia Files", - "video/mp4", "mp4 mpg4", "MPEG-4 Movies", - "audio/mp4", "m4a mp4 mpg4", "MPEG-4 Music", - "application/mp4", "m4i mp4 mpg4", "MPEG-4 Applications", - "video/3gpp", "3gp 3gpp", "3GPP/MMS Movies", - "audio/3gpp", "3gp 3gpp", "3GPP/MMS Music", - "video/3gpp2", "3g2 3gp2", "3GPP2/MMS Movies", - "audio/3gpp2", "3g2 3gp2", "3GPP2/MMS Music", - "video/iso.segment", "iso", "ISOBMF Fragments", - "audio/iso.segment", "iso", "ISOBMF Fragments", - NULL -}; - -u32 ISOR_RegisterMimeTypes(const GF_InputService *plug) { - u32 i; - for (i = 0 ; ISOR_MIME_TYPES[i]; i+=3) - gf_service_register_mime(plug, ISOR_MIME_TYPES[i], ISOR_MIME_TYPES[i+1], ISOR_MIME_TYPES[i+2]); - return i/3; -} - -Bool ISOR_CanHandleURL(GF_InputService *plug, const char *url) -{ - char *ext; - if (!strnicmp(url, "rtsp://", 7)) return GF_FALSE; - ext = (char *)strrchr(url, '.'); - { - u32 i; - /* We don't start at 0 since first one is specific */ - for (i = 3 ; ISOR_MIME_TYPES[i]; i+=3) - if (gf_service_check_mime_register(plug, ISOR_MIME_TYPES[i], ISOR_MIME_TYPES[i+1], ISOR_MIME_TYPES[i+2], ext)) - return GF_TRUE; - } - - if (ext && gf_isom_probe_file(url)) { - gf_service_check_mime_register(plug, ISOR_MIME_TYPES[0], ext+1, ISOR_MIME_TYPES[2], ext); - return GF_TRUE; - } - return GF_FALSE; -} - -void send_proxy_command(ISOMReader *read, Bool is_disconnect, Bool is_add_media, GF_Err e, GF_Descriptor *desc, LPNETCHANNEL channel) -{ - GF_NetworkCommand command; - command.command_type = GF_NET_SERVICE_STATUS_PROXY; - command.status.e = e; - command.status.channel = channel; - command.status.is_disconnect = is_disconnect; - command.status.is_add_media = is_add_media; - command.status.desc = desc; - read->input->query_proxy(read->input, &command); -} - -void isor_check_buffer_level(ISOMReader *read) -{ - Double dld_time_remaining, mov_rate; - GF_NetworkCommand com; - u32 i, total, done, Bps; - u64 dur; - GF_NetIOStatus status; - Bool do_buffer = GF_FALSE; - if (!read->dnload) return; - if (!read->mov) return; - - gf_dm_sess_get_stats(read->dnload, NULL, NULL, &total, &done, &Bps, &status); - if (!Bps) return; - - - gf_mx_p(read->segment_mutex); - - dld_time_remaining = total-done; - dld_time_remaining /= Bps; - - mov_rate = total; - if (read->frag_type) { - u64 bytesMissing=0; - gf_isom_refresh_fragmented(read->mov, &bytesMissing, NULL); - mov_rate = (Double) (done + bytesMissing); - } - dur = gf_isom_get_duration(read->mov); - if (dur) { - mov_rate /= dur; - mov_rate *= gf_isom_get_timescale(read->mov); - } - - for (i=0; i<gf_list_count(read->channels); i++) { - ISOMChannel *ch = gf_list_get(read->channels, i); - Double time_remain_ch = (Double) gf_isom_get_media_duration(read->mov, ch->track); - u32 buffer_level=0; - if (total==done) { - time_remain_ch = 0; - do_buffer = GF_FALSE; - } else if (ch->last_state == GF_EOS) { - time_remain_ch = 0; - do_buffer = GF_TRUE; - } else { - u64 data_offset; - u32 di, sn = ch->sample_num ? ch->sample_num : 1; - GF_ISOSample *samp = gf_isom_get_sample_info(read->mov, ch->track, sn, &di, &data_offset); - if (!samp) { - continue; - } - - data_offset += samp->dataLength; - -#if 1 - //we don't have enough data - if (((data_offset + ch->buffer_min * mov_rate/1000 > done))) { - do_buffer = GF_TRUE; - } - //we have enough buffer - else if ((data_offset + ch->buffer_max * mov_rate/1000 <= done)) { - do_buffer = GF_FALSE; - } else { - do_buffer = ch->buffering; - } - buffer_level = (u32) ( (done - data_offset) / mov_rate * 1000); -#else - //we only send buffer on/off based on remainging playback time in channel - time_remain_ch -= (samp->DTS + samp->CTS_Offset); - if (time_remain_ch<0) time_remain_ch=0; - - time_remain_ch /= ch->time_scale; - if (time_remain_ch && (time_remain_ch < dld_time_remaining)) { - do_buffer = GF_TRUE; - if (!read->remain_at_buffering_start || (read->remain_at_buffering_start < dld_time_remaining)) { - buffer_level = 0; - read->remain_at_buffering_start = dld_time_remaining; - } else { - buffer_level = (u32) (100 * (read->remain_at_buffering_start - dld_time_remaining) / (read->remain_at_buffering_start - time_remain_ch) ); - } - } else { - do_buffer = GF_FALSE; - } -#endif - - gf_isom_sample_del(&samp); - } - - if (do_buffer != ch->buffering) { - GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[IsoMedia] Buffering %s at %d: %g sec still to download and %g sec still to play on track %d (movie rate %g - download rate %g kbps)\n", do_buffer ? "on" : "off", gf_sys_clock(), dld_time_remaining , time_remain_ch, ch->track_id, mov_rate*8/1000, Bps*8.0/1000)); - - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.command_type = do_buffer ? GF_NET_CHAN_PAUSE : GF_NET_CHAN_RESUME; - com.buffer.on_channel = ch->channel; - com.buffer.min = ch->buffer_min; - com.buffer.max = ch->buffer_max; - gf_service_command(read->service, &com, GF_OK); - ch->buffering = do_buffer; - read->buffering = do_buffer; - } else if (ch->buffering) { - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.command_type = GF_NET_CHAN_BUFFER; - com.buffer.on_channel = ch->channel; - com.buffer.min = ch->buffer_min; - com.buffer.max = ch->buffer_max; - com.buffer.occupancy = buffer_level; - gf_service_command(read->service, &com, GF_OK); - } - } - gf_mx_v(read->segment_mutex); -} - -void isor_net_io(void *cbk, GF_NETIO_Parameter *param) -{ - GF_Err e; - u32 size = 0; - char *local_name; - ISOMReader *read = (ISOMReader *) cbk; - - /*handle service message*/ - if (!read->buffering) - gf_service_download_update_stats(read->dnload); - - if (param->msg_type==GF_NETIO_DATA_TRANSFERED) { - e = GF_EOS; - } else if (param->msg_type==GF_NETIO_DATA_EXCHANGE) { - e = GF_OK; - size = param->size; - } else { - e = param->error; - } - - if (e<GF_OK) { - /*error opening service*/ - if (!read->mov) { - /* if there is an intermediate between this module and the terminal, report to it */ - if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) { - send_proxy_command(read, GF_FALSE, GF_FALSE, e, NULL, NULL); - } else { - gf_service_connect_ack(read->service, NULL, e); - } - } - return; - } - - /*open file if not done yet (bad interleaving)*/ - if (e==GF_EOS) { - const char *local_name; - if (read->mov) return; - local_name = gf_dm_sess_get_cache_name(read->dnload); - if (!local_name) { - if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) { - send_proxy_command(read, GF_FALSE, GF_FALSE, GF_SERVICE_ERROR, NULL, NULL); - } else { - gf_service_connect_ack(read->service, NULL, GF_SERVICE_ERROR); - } - return; - } - read->mov = gf_isom_open(local_name, GF_ISOM_OPEN_READ, NULL); - if (!read->mov) gf_isom_last_error(NULL); - else read->time_scale = gf_isom_get_timescale(read->mov); - read->frag_type = gf_isom_is_fragmented(read->mov) ? 1 : 0; - if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) { - send_proxy_command(read, GF_FALSE, GF_FALSE, GF_OK, NULL, NULL); - } else { - gf_service_connect_ack(read->service, NULL, GF_OK); - } - if (read->no_service_desc) isor_declare_objects(read); - } - - if (!size) return; - - /*service is opened, nothing to do*/ - if (read->mov) { - isor_check_buffer_level(read); - - /*end of chunk*/ - if (read->frag_type && (param->reply==1) ) { - u64 bytesMissing = 0; - gf_mx_p(read->segment_mutex); - gf_isom_refresh_fragmented(read->mov, &bytesMissing, NULL); - gf_mx_v(read->segment_mutex); - } - return; - } - - /*try to open the service*/ - local_name = (char *)gf_dm_sess_get_cache_name(read->dnload); - if (!local_name) { - if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) { - send_proxy_command(read, GF_FALSE, GF_FALSE, GF_SERVICE_ERROR, NULL, NULL); - } else { - gf_service_connect_ack(read->service, NULL, GF_SERVICE_ERROR); - } - return; - } - - /*not enogh data yet*/ - if (read->missing_bytes && (read->missing_bytes > size) ) { - read->missing_bytes -= size; - return; - } - - e = gf_isom_open_progressive(local_name, 0, 0, &read->mov, &read->missing_bytes); - switch (e) { - case GF_ISOM_INCOMPLETE_FILE: - return; - case GF_OK: - break; - default: - if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) { - send_proxy_command(read, GF_FALSE, GF_FALSE, e, NULL, NULL); - } else { - gf_service_connect_ack(read->service, NULL, e); - } - return; - } - read->frag_type = gf_isom_is_fragmented(read->mov) ? 1 : 0; - - /*ok let's go, we can setup the decoders */ - read->time_scale = gf_isom_get_timescale(read->mov); - if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) { - send_proxy_command(read, GF_FALSE, GF_FALSE, GF_OK, NULL, NULL); - } else { - gf_service_connect_ack(read->service, NULL, GF_OK); - } - - if (read->no_service_desc) isor_declare_objects(read); -} - - -void isor_setup_download(GF_InputService *plug, const char *url) -{ - ISOMReader *read = (ISOMReader *) plug->priv; - read->dnload = gf_service_download_new(read->service, url, 0, isor_net_io, read); - if (!read->dnload) { - if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) { - send_proxy_command(read, GF_FALSE, GF_FALSE, GF_NOT_SUPPORTED, NULL, NULL); - } else { - gf_service_connect_ack(read->service, NULL, GF_NOT_SUPPORTED); - } - } else { - /*start our download (threaded)*/ - gf_dm_sess_process(read->dnload); - } - /*service confirm is done once IOD can be fetched*/ -} - -GF_Err ISOR_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url) -{ - char szURL[2048]; - char *tmp; - ISOMReader *read; - if (!plug || !plug->priv || !serv) return GF_SERVICE_ERROR; - read = (ISOMReader *) plug->priv; - - read->input = plug; - read->service = serv; - - if (read->dnload) gf_service_download_del(read->dnload); - read->dnload = NULL; - - if (!url) return GF_URL_ERROR; - - strcpy(szURL, url); - tmp = strrchr(szURL, '.'); - if (tmp) { - tmp = strchr(tmp, '#'); - if (tmp) { - if (!strnicmp(tmp, "#trackID=", 9)) { - read->play_only_track_id = atoi(tmp+9); - } else { - read->play_only_track_id = atoi(tmp+1); - } - tmp[0] = 0; - } - } - - if (isor_is_local(szURL)) { - GF_Err e; - u64 start_range, end_range; - start_range = end_range = 0; - if (plug->query_proxy) { - GF_NetworkCommand param; - param.command_type = GF_NET_SERVICE_QUERY_INIT_RANGE; - if (read->input->query_proxy(read->input, ¶m)==GF_OK) { - start_range = param.url_query.start_range; - end_range = param.url_query.end_range; - } - } - e = gf_isom_open_progressive(szURL, start_range, end_range, &read->mov, &read->missing_bytes); - if (e != GF_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[IsoMedia] error while opening %s, error=%s\n", szURL, gf_error_to_string(e))); - if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) { - send_proxy_command(read, 0, 0, e, NULL, NULL); - } else { - gf_service_connect_ack(read->service, NULL, e); - } - return GF_OK; - } - read->frag_type = gf_isom_is_fragmented(read->mov) ? 1 : 0; - read->seg_opened = 2; - - read->time_scale = gf_isom_get_timescale(read->mov); - /*reply to user*/ - if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) { - send_proxy_command(read, GF_FALSE, GF_FALSE, GF_OK, NULL, NULL); - } else { - gf_service_connect_ack(read->service, NULL, GF_OK); - } - - if (read->no_service_desc) isor_declare_objects(read); - - } else { - /*setup downloader*/ - isor_setup_download(plug, szURL); - } - return GF_OK; -} - -GF_Err ISOR_CloseService(GF_InputService *plug) -{ - GF_Err reply; - ISOMReader *read; - if (!plug || !plug->priv) return GF_SERVICE_ERROR; - read = (ISOMReader *) plug->priv; - reply = GF_OK; - - read->disconnected = GF_TRUE; - - while (gf_list_count(read->channels)) { - ISOMChannel *ch = (ISOMChannel *)gf_list_get(read->channels, 0); - gf_list_rem(read->channels, 0); - isor_delete_channel(read, ch); - } - - if (read->dnload) gf_service_download_del(read->dnload); - read->dnload = NULL; - - if (read->mov) gf_isom_close(read->mov); - read->mov = NULL; - - if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) { - send_proxy_command(read, GF_TRUE, GF_FALSE, reply, NULL, NULL); - } else { - gf_service_disconnect_ack(read->service, NULL, reply); - } - return GF_OK; -} - -static Bool check_mpeg4_systems(GF_InputService *plug, GF_ISOFile *mov) -{ - char *opt, *bname, *br, *next; - u32 i, count, brand; - Bool has_mpeg4; - GF_Err e; - e = gf_isom_get_brand_info(mov, &brand, &i, &count); - /*no brand == MP4 v1*/ - if (e || !brand) return GF_TRUE; - - has_mpeg4 = GF_FALSE; - if ((brand==GF_ISOM_BRAND_MP41) || (brand==GF_ISOM_BRAND_MP42)) has_mpeg4 = GF_TRUE; - - opt = (char*) gf_modules_get_option((GF_BaseInterface *)plug, "ISOReader", "IgnoreMPEG-4ForBrands"); - if (!opt) { - gf_modules_set_option((GF_BaseInterface *)plug, "ISOReader", "IgnoreMPEG-4ForBrands", "nd*"); - opt = (char*) gf_modules_get_option((GF_BaseInterface *)plug, "ISOReader", "IgnoreMPEG-4ForBrands"); - } - - for (i=0; i<count; i++) { - e = gf_isom_get_alternate_brand(mov, i+1, &brand); - if (e) return GF_FALSE; - if ((brand==GF_ISOM_BRAND_MP41) || (brand==GF_ISOM_BRAND_MP42)) { - has_mpeg4 = GF_TRUE; - continue; - } - bname = (char*)gf_4cc_to_str(brand); - br = opt; - while (br) { - Bool ignore = GF_FALSE; - u32 orig_len, len; - next = strchr(br, ' '); - if (next) next[0] = 0; - len = orig_len = (u32) strlen(br); - - while (len) { - if (br[len-1]=='*') { - br[len-1]=0; - len--; - } else { - break; - } - } - /*ignor all brands*/ - if (!len) ignore = GF_TRUE; - else if (!strncmp(bname, br, len)) ignore = GF_TRUE; - - while (len<orig_len) { - br[len] = '*'; - len++; - } - if (next) { - next[0] = ' '; - br = next + 1; - } else { - br = NULL; - } - if (ignore) return GF_FALSE; - } - } - return has_mpeg4; -} - -static u32 get_track_id(GF_ISOFile *mov, u32 media_type, u32 idx) -{ - u32 i, count, cur; - cur=0; - count = gf_isom_get_track_count(mov); - for (i=0; i<count; i++) { - if (gf_isom_get_media_type(mov, i+1) != media_type) continue; - if (!idx) return gf_isom_get_track_id(mov, i+1); - cur++; - if (cur==idx) return gf_isom_get_track_id(mov, i+1); - } - return GF_FALSE; -} - -/*fixme, this doesn't work properly with respect to @expect_type*/ -static GF_Descriptor *ISOR_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) -{ - u32 count, nb_st, i, trackID; - GF_ESD *esd; - ISOMReader *read; - GF_InitialObjectDescriptor *iod; - if (!plug || !plug->priv) return NULL; - read = (ISOMReader *) plug->priv; - if (!read->mov) return NULL; - - /*no matter what always read text as TTUs*/ - gf_isom_text_set_streaming_mode(read->mov, 1); - - if (!sub_url) { - trackID = read->play_only_track_id; - read->play_only_track_id = 0; - } else { - char *ext = (char *)strrchr(sub_url, '#'); - if (!ext) { - trackID = 0; - } else { - if (!strnicmp(ext, "#trackID=", 9)) trackID = atoi(ext+9); - else if (!stricmp(ext, "#video")) trackID = get_track_id(read->mov, GF_ISOM_MEDIA_VISUAL, 0); - else if (!strnicmp(ext, "#video", 6)) { - trackID = atoi(ext+6); - trackID = get_track_id(read->mov, GF_ISOM_MEDIA_VISUAL, trackID); - } - else if (!stricmp(ext, "#audio")) trackID = get_track_id(read->mov, GF_ISOM_MEDIA_AUDIO, 0); - else if (!strnicmp(ext, "#audio", 6)) { - trackID = atoi(ext+6); - trackID = get_track_id(read->mov, GF_ISOM_MEDIA_AUDIO, trackID); - } - else trackID = atoi(ext+1); - - /*if trackID is 0, assume this is a fragment identifier*/ - } - } - - if (!trackID && (expect_type!=GF_MEDIA_OBJECT_SCENE) && (expect_type!=GF_MEDIA_OBJECT_UNDEF)) { - for (i=0; i<gf_isom_get_track_count(read->mov); i++) { - u32 type = gf_isom_get_media_type(read->mov, i+1); - if ( - ((type==GF_ISOM_MEDIA_VISUAL) && (expect_type==GF_MEDIA_OBJECT_VIDEO)) - || ((type==GF_ISOM_MEDIA_AUDIO) && (expect_type==GF_MEDIA_OBJECT_AUDIO)) ) { - trackID = gf_isom_get_track_id(read->mov, i+1); - break; - } - - } - } - if (trackID && (expect_type!=GF_MEDIA_OBJECT_SCENE) ) { - u32 track = gf_isom_get_track_by_id(read->mov, trackID); - if (!track) return NULL; - esd = gf_media_map_esd(read->mov, track); - esd->OCRESID = 0; - iod = (GF_InitialObjectDescriptor *) gf_isom_get_root_od(read->mov); - if (!iod) { - iod = (GF_InitialObjectDescriptor *) gf_odf_desc_new(GF_ODF_IOD_TAG); - iod->OD_profileAndLevel = iod->audio_profileAndLevel = iod->graphics_profileAndLevel = iod->scene_profileAndLevel = iod->visual_profileAndLevel = 0xFE; - } else { - while (gf_list_count(iod->ESDescriptors)) { - GF_ESD *old = (GF_ESD *)gf_list_get(iod->ESDescriptors, 0); - gf_odf_desc_del((GF_Descriptor *) old); - gf_list_rem(iod->ESDescriptors, 0); - } - } - gf_list_add(iod->ESDescriptors, esd); - isor_emulate_chapters(read->mov, iod); - return (GF_Descriptor *) iod; - } - - iod = NULL; - if (check_mpeg4_systems(plug, read->mov)) { - iod = (GF_InitialObjectDescriptor *) gf_isom_get_root_od(read->mov); - if (!iod) { -#ifndef GPAC_DISABLE_LOG - GF_Err e = gf_isom_last_error(read->mov); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[IsoMedia] Cannot fetch MPEG-4 IOD (error %s) - generating one\n", gf_error_to_string(e) )); - } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("[IsoMedia] No MPEG-4 IOD found in file - generating one\n")); - } -#endif - } - } - if (!iod) return isor_emulate_iod(read); - - count = gf_list_count(iod->ESDescriptors); - if (!count) { - gf_odf_desc_del((GF_Descriptor*) iod); - return isor_emulate_iod(read); - } - if (count==1) { - esd = (GF_ESD *)gf_list_get(iod->ESDescriptors, 0); - switch (esd->decoderConfig->streamType) { - case GF_STREAM_SCENE: - case GF_STREAM_PRIVATE_SCENE: - break; - case GF_STREAM_VISUAL: - if (expect_type!=GF_MEDIA_OBJECT_VIDEO) { - gf_odf_desc_del((GF_Descriptor*) iod); - return isor_emulate_iod(read); - } - break; - case GF_STREAM_AUDIO: - /*we need a fake scene graph*/ - if (expect_type!=GF_MEDIA_OBJECT_AUDIO) { - gf_odf_desc_del((GF_Descriptor*) iod); - return isor_emulate_iod(read); - } - break; - default: - gf_odf_desc_del((GF_Descriptor*) iod); - return NULL; - } - } - /*check IOD is not badly formed (eg mixing audio, video or text streams)*/ - nb_st = 0; - for (i=0; i<count; i++) { - esd = (GF_ESD *)gf_list_get(iod->ESDescriptors, i); - switch (esd->decoderConfig->streamType) { - case GF_STREAM_VISUAL: - nb_st |= 1; - break; - case GF_STREAM_AUDIO: - nb_st |= 2; - break; - case GF_STREAM_TEXT: - nb_st |= 4; - break; - } - } - if ( (nb_st & 1) + (nb_st & 2) + (nb_st & 4) > 1) { - gf_odf_desc_del((GF_Descriptor*) iod); - return isor_emulate_iod(read); - } - - isor_emulate_chapters(read->mov, iod); - return (GF_Descriptor *) iod; -} - - -void isor_send_cenc_config(ISOMChannel *ch) -{ - GF_NetworkCommand com; - u32 i; - - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.on_channel = ch->channel; - com.command_type = GF_NET_CHAN_DRM_CFG; - ch->is_encrypted = GF_TRUE; - - gf_isom_get_cenc_info(ch->owner->mov, ch->track, 1, NULL, &com.drm_cfg.scheme_type, &com.drm_cfg.scheme_version, NULL); - - com.drm_cfg.PSSH_count = gf_isom_get_pssh_count(ch->owner->mov); - com.drm_cfg.PSSHs = gf_malloc(sizeof(GF_NetComDRMConfigPSSH)*(com.drm_cfg.PSSH_count) ); - - /*fill PSSH in the structure. We will free it in CENC_Setup*/ - for (i=0; i<com.drm_cfg.PSSH_count; i++) { - GF_NetComDRMConfigPSSH *pssh = &com.drm_cfg.PSSHs[i]; - gf_isom_get_pssh_info(ch->owner->mov, i+1, pssh->SystemID, &pssh->KID_count, (const bin128 **) & pssh->KIDs, (const u8 **) &pssh->private_data, &pssh->private_data_size); - } - //fixme - check MSE and EME -#if 0 - if (read->input->query_proxy && read->input->proxy_udta) { - read->input->query_proxy(read->input, &com); - } else -#endif - gf_service_command(ch->owner->service, &com, GF_OK); - //free our PSSH - if (com.drm_cfg.PSSHs) gf_free(com.drm_cfg.PSSHs); -} - - - -GF_Err ISOR_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) -{ - u32 ESID; - ISOMChannel *ch; - GF_NetworkCommand com; - u32 track; - Bool is_esd_url; - GF_Err e; - ISOMReader *read; - if (!plug || !plug->priv) return GF_SERVICE_ERROR; - read = (ISOMReader *) plug->priv; - - track = 0; - ch = NULL; - is_esd_url = GF_FALSE; - e = GF_OK; - if (upstream) { - e = GF_ISOM_INVALID_FILE; - goto exit; - } - if (!read->mov) return GF_SERVICE_ERROR; - - if (strstr(url, "ES_ID")) { - sscanf(url, "ES_ID=%ud", &ESID); - } else { - /*handle url like mypath/myfile.mp4#trackID*/ - char *track_id = (char *)strrchr(url, '.'); - if (track_id) { - track_id = (char *)strchr(url, '#'); - if (track_id) track_id ++; - } - is_esd_url = GF_TRUE; - - ESID = 0; - /*if only one track ok*/ - if (gf_isom_get_track_count(read->mov)==1) ESID = gf_isom_get_track_id(read->mov, 1); - else if (track_id) { - ESID = atoi(track_id); - track = gf_isom_get_track_by_id(read->mov, (u32) ESID); - if (!track) ESID = 0; - } - - } - if (!ESID) { - e = GF_NOT_SUPPORTED; - goto exit; - } - - /*a channel cannot be open twice, it has to be closed before - NOTE a track is NOT a channel and the user can open - several times the same track as long as a dedicated channel is used*/ - ch = isor_get_channel(read, channel); - if (ch) { - e = GF_SERVICE_ERROR; - goto exit; - } - track = gf_isom_get_track_by_id(read->mov, (u32) ESID); - if (!track) { - e = GF_STREAM_NOT_FOUND; - goto exit; - } - - GF_SAFEALLOC(ch, ISOMChannel); - if (!ch) { - e = GF_OUT_OF_MEM; - goto exit; - } - ch->owner = read; - ch->channel = channel; - gf_list_add(read->channels, ch); - ch->track = track; - ch->track_id = gf_isom_get_track_id(read->mov, ch->track); - switch (gf_isom_get_media_type(ch->owner->mov, ch->track)) { - case GF_ISOM_MEDIA_OCR: - ch->streamType = GF_STREAM_OCR; - break; - case GF_ISOM_MEDIA_SCENE: - ch->streamType = GF_STREAM_SCENE; - break; - case GF_ISOM_MEDIA_VISUAL: - gf_isom_get_reference(ch->owner->mov, ch->track, GF_ISOM_REF_BASE, 1, &ch->base_track); - //use base track only if avc/svc or hevc/lhvc. If avc+lhvc we need different rules - if ( gf_isom_get_avc_svc_type(ch->owner->mov, ch->base_track, 1) == GF_ISOM_AVCTYPE_AVC_ONLY) { - if ( gf_isom_get_hevc_lhvc_type(ch->owner->mov, ch->track, 1) >= GF_ISOM_HEVCTYPE_HEVC_ONLY) { - ch->base_track=0; - } - } - - ch->next_track = 0; - /*in scalable mode add SPS/PPS in-band*/ - ch->nalu_extract_mode = GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG /*| GF_ISOM_NALU_EXTRACT_ANNEXB_FLAG*/; - gf_isom_set_nalu_extract_mode(ch->owner->mov, ch->track, ch->nalu_extract_mode); - break; - } - - ch->has_edit_list = gf_isom_get_edit_list_type(ch->owner->mov, ch->track, &ch->dts_offset) ? GF_TRUE : GF_FALSE; - ch->has_rap = (gf_isom_has_sync_points(ch->owner->mov, ch->track)==1) ? GF_TRUE : GF_FALSE; - ch->time_scale = gf_isom_get_media_timescale(ch->owner->mov, ch->track); - -exit: - if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) { - send_proxy_command(read, GF_FALSE, GF_FALSE, e, NULL, channel); - } else { - gf_service_connect_ack(read->service, channel, e); - } - /*if esd url reconfig SL layer*/ - if (!e && is_esd_url) { - GF_ESD *esd; - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.on_channel = channel; - com.command_type = GF_NET_CHAN_RECONFIG; - esd = gf_isom_get_esd(read->mov, ch->track, 1); - if (esd) { - memcpy(&com.cfg.sl_config, esd->slConfig, sizeof(GF_SLConfig)); - gf_odf_desc_del((GF_Descriptor *)esd); - } else { - com.cfg.sl_config.tag = GF_ODF_SLC_TAG; - com.cfg.sl_config.timestampLength = 32; - com.cfg.sl_config.timestampResolution = ch->time_scale; - com.cfg.sl_config.useRandomAccessPointFlag = 1; - } - if (read->input->query_proxy && read->input->proxy_udta) { - read->input->query_proxy(read->input, &com); - } else { - gf_service_command(read->service, &com, GF_OK); - } - } - if (!e && track && gf_isom_is_track_encrypted(read->mov, track)) { - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.on_channel = channel; - com.command_type = GF_NET_CHAN_DRM_CFG; - ch->is_encrypted = GF_TRUE; - if (gf_isom_is_ismacryp_media(read->mov, track, 1)) { - gf_isom_get_ismacryp_info(read->mov, track, 1, NULL, &com.drm_cfg.scheme_type, &com.drm_cfg.scheme_version, &com.drm_cfg.scheme_uri, &com.drm_cfg.kms_uri, NULL, NULL, NULL); - if (read->input->query_proxy && read->input->proxy_udta) { - read->input->query_proxy(read->input, &com); - } else { - gf_service_command(read->service, &com, GF_OK); - } - } else if (gf_isom_is_omadrm_media(read->mov, track, 1)) { - gf_isom_get_omadrm_info(read->mov, track, 1, NULL, &com.drm_cfg.scheme_type, &com.drm_cfg.scheme_version, &com.drm_cfg.contentID, &com.drm_cfg.kms_uri, &com.drm_cfg.oma_drm_textual_headers, &com.drm_cfg.oma_drm_textual_headers_len, NULL, &com.drm_cfg.oma_drm_crypt_type, NULL, NULL, NULL); - - gf_media_get_file_hash(gf_isom_get_filename(read->mov), com.drm_cfg.hash); - if (read->input->query_proxy && read->input->proxy_udta) { - read->input->query_proxy(read->input, &com); - } else { - gf_service_command(read->service, &com, GF_OK); - } - } else if (gf_isom_is_cenc_media(read->mov, track, 1)) { - ch->is_cenc = GF_TRUE; - isor_send_cenc_config(ch); - } - } - return e; -} - -GF_Err ISOR_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) -{ - ISOMChannel *ch; - GF_Err e; - ISOMReader *read; - if (!plug || !plug->priv) return GF_SERVICE_ERROR; - read = (ISOMReader *) plug->priv; - if (!read->mov) return GF_SERVICE_ERROR; - - gf_mx_p(read->segment_mutex); - e = GF_OK; - ch = isor_get_channel(read, channel); - assert(ch); - if (!ch) { - e = GF_STREAM_NOT_FOUND; - goto exit; - } - /*signal the service is broken but still process the delete*/ - isor_delete_channel(read, ch); - assert(!isor_get_channel(read, channel)); - -exit: - if (read->input->query_proxy && read->input->proxy_udta && read->input->proxy_type) { - send_proxy_command(read, 1, 0, e, NULL, channel); - } else { - gf_service_disconnect_ack(read->service, channel, e); - } - gf_mx_v(read->segment_mutex); - return e; -} - -GF_Err ISOR_ChannelGetSLP(GF_InputService *plug, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, GF_SLHeader *out_sl_hdr, Bool *sl_compressed, GF_Err *out_reception_status, Bool *is_new_data) -{ - ISOMChannel *ch; - ISOMReader *read; - if (!plug || !plug->priv) return GF_SERVICE_ERROR; - /*cannot read native SL-PDUs*/ - if (!out_sl_hdr) return GF_NOT_SUPPORTED; - read = (ISOMReader *) plug->priv; - if (!read->mov) return GF_SERVICE_ERROR; - - *out_data_ptr = NULL; - *out_data_size = 0; - *sl_compressed = GF_FALSE; - *out_reception_status = GF_OK; - *is_new_data = GF_FALSE; - ch = isor_get_channel(read, channel); - if (!ch) return GF_STREAM_NOT_FOUND; - if (!ch->is_playing) return GF_OK; - - *is_new_data = GF_FALSE; - if (!ch->sample) { - /*get sample*/ - gf_mx_p(read->segment_mutex); - isor_reader_get_sample(ch); - gf_mx_v(read->segment_mutex); - *is_new_data = ch->sample ? GF_TRUE : GF_FALSE; - } - - if (ch->sample) { - *out_data_ptr = ch->sample->data; - *out_data_size = ch->sample->dataLength; - *out_sl_hdr = ch->current_slh; - } - *out_reception_status = ch->last_state; - if (read->waiting_for_data) - *out_reception_status = GF_BUFFER_TOO_SMALL; - - return GF_OK; -} - -GF_Err ISOR_ChannelReleaseSLP(GF_InputService *plug, LPNETCHANNEL channel) -{ - ISOMChannel *ch; - ISOMReader *read; - if (!plug || !plug->priv) return GF_SERVICE_ERROR; - read = (ISOMReader *) plug->priv; - if (!read->mov) return GF_SERVICE_ERROR; - ch = isor_get_channel(read, channel); - if (!ch) return GF_STREAM_NOT_FOUND; - if (!ch->is_playing) return GF_SERVICE_ERROR; - - if (ch->sample) { - isor_reader_release_sample(ch); - /*release sample*/ - } - return GF_OK; -} - -/*switch channel quality. Return next channel or current channel if error*/ -static -u32 gf_channel_switch_quality(ISOMChannel *ch, GF_ISOFile *the_file, Bool switch_up) -{ - u32 i, count, next_track, trackID, cur_track; - s32 ref_count; - - cur_track = ch->next_track ? ch->next_track : ch->track; - count = gf_isom_get_track_count(the_file); - trackID = gf_isom_get_track_id(the_file, cur_track); - next_track = 0; - - if (switch_up) - { - for (i = 0; i < count; i++) - { - ref_count = gf_isom_get_reference_count(the_file, i+1, GF_ISOM_REF_SCAL); - if (ref_count < 0) - return cur_track; //error - else if (ref_count == 0) - continue; - /*next track is the one that has the last reference of type GF_ISOM_REF_SCAL refers to this current track*/ - else if ((u32)ref_count == gf_isom_has_track_reference(the_file, i+1, GF_ISOM_REF_SCAL, trackID)) - { - next_track = i+1; - break; - } - } - /*this is the highest quality*/ - if (!next_track) - return cur_track; - } - else - { - if (cur_track == ch->base_track) - return cur_track; - ref_count = gf_isom_get_reference_count(the_file, cur_track, GF_ISOM_REF_SCAL); - if (ref_count <= 0) - return cur_track; - gf_isom_get_reference(the_file, cur_track, GF_ISOM_REF_SCAL, ref_count, &next_track); - if (!next_track) - return cur_track; - } - - /*in scalable mode add SPS/PPS in-band*/ - gf_isom_set_nalu_extract_mode(the_file, next_track, ch->nalu_extract_mode); - - return next_track; -} - - -GF_Err ISOR_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) -{ - Double track_dur, media_dur; - ISOMChannel *ch; - ISOMReader *read; - u32 count, i; - - if (!plug || !plug->priv || !com) return GF_SERVICE_ERROR; - read = (ISOMReader *) plug->priv; - if (read->disconnected) return GF_OK; - - if (com->command_type==GF_NET_SERVICE_INFO) { - u32 tag_len; - const char *tag; - if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_NAME, &tag, &tag_len)==GF_OK) com->info.name = tag; - if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_ARTIST, &tag, &tag_len)==GF_OK) com->info.artist = tag; - if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_ALBUM, &tag, &tag_len)==GF_OK) com->info.album = tag; - if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_COMMENT, &tag, &tag_len)==GF_OK) com->info.comment = tag; - if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_TRACK, &tag, &tag_len)==GF_OK) { - com->info.track_info = (((tag[2]<<8)|tag[3]) << 16) | ((tag[4]<<8)|tag[5]); - } - if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_COMPOSER, &tag, &tag_len)==GF_OK) com->info.composer = tag; - if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_WRITER, &tag, &tag_len)==GF_OK) com->info.writer = tag; - if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_GENRE, &tag, &tag_len)==GF_OK) { - if (tag[0]) { - com->info.genre = 0; - } else { - com->info.genre = (tag[0]<<8) | tag[1]; - } - } - return GF_OK; - } - if (com->command_type==GF_NET_SERVICE_HAS_AUDIO) { - u32 i, count; - count = gf_isom_get_track_count(read->mov); - for (i=0; i<count; i++) { - if (gf_isom_get_media_type(read->mov, i+1) == GF_ISOM_MEDIA_AUDIO) return GF_OK; - } - return GF_NOT_SUPPORTED; - } - - if (com->command_type == GF_NET_SERVICE_QUALITY_SWITCH) - { - count = gf_list_count(read->channels); - for (i = 0; i < count; i++) - { - ch = (ISOMChannel *)gf_list_get(read->channels, i); - if (ch->base_track && gf_isom_needs_layer_reconstruction(read->mov)) { - ch->next_track = gf_channel_switch_quality(ch, read->mov, com->switch_quality.up); - } - } - return GF_OK; - } - if (com->command_type == GF_NET_SERVICE_PROXY_DATA_RECEIVE) { - isor_flush_data(read, 1, com->proxy_data.is_chunk); - return GF_OK; - } - if (com->command_type == GF_NET_SERVICE_FLUSH_DATA) { - if (read->nb_playing && plug->query_proxy) - isor_flush_data(read, 0, 0); - return GF_OK; - } - if (com->command_type == GF_NET_SERVICE_CAN_REVERSE_PLAYBACK) - return GF_OK; - - if (!com->base.on_channel) return GF_NOT_SUPPORTED; - - ch = isor_get_channel(read, com->base.on_channel); - if (!ch) return GF_STREAM_NOT_FOUND; - - switch (com->command_type) { - case GF_NET_CHAN_SET_PADDING: - if (!ch->track) return GF_OK; - gf_isom_set_sample_padding(read->mov, ch->track, com->pad.padding_bytes); - return GF_OK; - case GF_NET_CHAN_SET_PULL: - //we don't pull in DASH base services, we flush as soon as we have a complete segment -#ifndef DASH_USE_PULL - if (read->input->proxy_udta && !read->input->proxy_type) - return GF_NOT_SUPPORTED; -#endif - - ch->is_pulling = 1; - return GF_OK; - case GF_NET_CHAN_INTERACTIVE: - return GF_OK; - case GF_NET_CHAN_BUFFER: - //dash or HTTP, do rebuffer if not disabled - if (plug->query_proxy) { - } else if (read->dnload) { - ch->buffer_min = com->buffer.min; - ch->buffer_max = com->buffer.max; - } else { - com->buffer.max = com->buffer.min = 0; - } - return GF_OK; - case GF_NET_CHAN_DURATION: - if (!ch->track) { - com->duration.duration = 0; - return GF_OK; - } - ch->duration = gf_isom_get_track_duration(read->mov, ch->track); - track_dur = (Double) (s64) ch->duration; - track_dur /= read->time_scale; - if (gf_isom_get_edit_segment_count(read->mov, ch->track)) { - com->duration.duration = (Double) track_dur; - ch->duration = (u32) (track_dur * ch->time_scale); - } else { - /*some file indicate a wrong TrackDuration, get the longest*/ - ch->duration = gf_isom_get_media_duration(read->mov, ch->track); - media_dur = (Double) (s64) ch->duration; - media_dur /= ch->time_scale; - com->duration.duration = MAX(track_dur, media_dur); - } - return GF_OK; - case GF_NET_CHAN_PLAY: - - gf_mx_p(read->segment_mutex); - isor_reset_reader(ch); - ch->speed = com->play.speed; - read->reset_frag_state = 1; - if (read->frag_type) - read->frag_type = 1; - gf_mx_v(read->segment_mutex); - - ch->start = ch->end = 0; - if (com->play.speed>0) { - Double t; - if (com->play.start_range>=0) { - t = com->play.start_range; - t *= ch->time_scale; - ch->start = (u64) t; - } - if (com->play.end_range >= com->play.start_range) { - t = com->play.end_range; - t *= ch->time_scale; - ch->end = (u64) t; - } - } else if (com->play.speed<0) { - Double end = com->play.end_range; - if (end==-1) end = 0; - ch->start = (u64) (s64) (com->play.start_range * ch->time_scale); - if (end <= com->play.start_range) - ch->end = (u64) (s64) (end * ch->time_scale); - } - ch->is_playing = 1; - if (com->play.dash_segment_switch) ch->wait_for_segment_switch = 1; - GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("[IsoMedia] Starting channel playback "LLD" to "LLD" (%g to %g)\n", ch->start, ch->end, com->play.start_range, com->play.end_range)); - - //and check buffer level on play request - isor_check_buffer_level(read); - read->nb_playing++; - return GF_OK; - case GF_NET_CHAN_STOP: - if (read->nb_playing) read->nb_playing--; - isor_reset_reader(ch); - return GF_OK; - - case GF_NET_CHAN_SET_SPEED: - gf_mx_p(read->segment_mutex); - ch->speed = com->play.speed; - gf_mx_v(read->segment_mutex); - return GF_OK; - /*nothing to do on MP4 for channel config*/ - case GF_NET_CHAN_CONFIG: - return GF_OK; - case GF_NET_CHAN_GET_PIXEL_AR: - return gf_isom_get_pixel_aspect_ratio(read->mov, ch->track, 1, &com->par.hSpacing, &com->par.vSpacing); - case GF_NET_CHAN_GET_DSI: - { - /*it may happen that there are conflicting config when using ESD URLs...*/ - GF_DecoderConfig *dcd = gf_isom_get_decoder_config(read->mov, ch->track, 1); - com->get_dsi.dsi = NULL; - com->get_dsi.dsi_len = 0; - if (dcd) { - if (dcd->decoderSpecificInfo) { - com->get_dsi.dsi = dcd->decoderSpecificInfo->data; - com->get_dsi.dsi_len = dcd->decoderSpecificInfo->dataLength; - dcd->decoderSpecificInfo->data = NULL; - } - gf_odf_desc_del((GF_Descriptor *) dcd); - } - return GF_OK; - } - case GF_NET_CHAN_NALU_MODE: - ch->nalu_extract_mode = GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG; - - //when this is set, we work in real scalable (eg N streams reassembled by the player) so only extract the layer. This wll need refinements if we plan to support - //several scalable layers ... - if (com->nalu_mode.extract_mode>=1) { - if (com->nalu_mode.extract_mode==2) { - ch->disable_seek = 1; - } - ch->nalu_extract_mode |= GF_ISOM_NALU_EXTRACT_ANNEXB_FLAG | GF_ISOM_NALU_EXTRACT_VDRD_FLAG | GF_ISOM_NALU_EXTRACT_LAYER_ONLY; - } - gf_isom_set_nalu_extract_mode(ch->owner->mov, ch->track, ch->nalu_extract_mode); - break; - default: - break; - } - return GF_NOT_SUPPORTED; -} - -static Bool ISOR_CanHandleURLInService(GF_InputService *plug, const char *url) -{ - char szURL[2048], *sep; - ISOMReader *read = (ISOMReader *)plug->priv; - const char *this_url = gf_service_get_url(read->service); - if (!this_url || !url) return GF_FALSE; - - if (!strcmp(this_url, url)) return GF_TRUE; - - strcpy(szURL, this_url); - sep = strrchr(szURL, '#'); - if (sep) sep[0] = 0; - - /*direct addressing in service*/ - if (url[0] == '#') return GF_TRUE; - if (strnicmp(szURL, url, sizeof(char)*strlen(szURL))) return GF_FALSE; - return GF_TRUE; -} - -GF_InputService *isor_client_load() -{ - ISOMReader *reader; - GF_InputService *plug; - GF_SAFEALLOC(plug, GF_InputService); - if (!plug) return NULL; - GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC IsoMedia Reader", "gpac distribution") - - GF_SAFEALLOC(reader, ISOMReader); - if (!reader) { - gf_free(plug); - return NULL; - } - reader->channels = gf_list_new(); - reader->segment_mutex = gf_mx_new("ISO Segment"); - - plug->priv = reader; - - plug->RegisterMimeTypes = ISOR_RegisterMimeTypes; - plug->CanHandleURL = ISOR_CanHandleURL; - plug->ConnectService = ISOR_ConnectService; - plug->CloseService = ISOR_CloseService; - plug->GetServiceDescriptor = ISOR_GetServiceDesc; - plug->ConnectChannel = ISOR_ConnectChannel; - plug->DisconnectChannel = ISOR_DisconnectChannel; - plug->ServiceCommand = ISOR_ServiceCommand; - plug->CanHandleURLInService = ISOR_CanHandleURLInService; - /*we do support pull mode*/ - plug->ChannelGetSLP = ISOR_ChannelGetSLP; - plug->ChannelReleaseSLP = ISOR_ChannelReleaseSLP; - return plug; -} - -void isor_client_del(GF_BaseInterface *bi) -{ - GF_InputService *plug = (GF_InputService *) bi; - ISOMReader *read = (ISOMReader *)plug->priv; - - if (read->segment_mutex) gf_mx_del(read->segment_mutex); - gf_list_del(read->channels); - gf_free(read); - gf_free(bi); -} - -#endif /*GPAC_DISABLE_ISOM*/ diff --git a/modules/isom_in/read_ch.c b/modules/isom_in/read_ch.c deleted file mode 100644 index 78ae5ec..0000000 --- a/modules/isom_in/read_ch.c +++ /dev/null @@ -1,946 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / MP4 reader module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include "isom_in.h" -#include <gpac/network.h> -#include <time.h> - -#ifndef GPAC_DISABLE_ISOM - - -void isor_reset_reader(ISOMChannel *ch) -{ - ch->last_state = GF_OK; - isor_reader_release_sample(ch); - - ch->sample = NULL; - ch->sample_num = 0; - ch->speed = 1.0; - ch->start = ch->end = 0; - ch->to_init = 1; - ch->is_playing = 0; - memset(&ch->current_slh, 0, sizeof(GF_SLHeader)); -} - -void isor_check_producer_ref_time(ISOMReader *read) -{ - u32 trackID; - u64 ntp; - u64 timestamp; - - if (gf_isom_get_last_producer_time_box(read->mov, &trackID, &ntp, ×tamp, GF_TRUE)) { -#if !defined(_WIN32_WCE) && !defined(GPAC_DISABLE_LOG) - - if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_DEBUG)) { - time_t secs; - struct tm t; - - s32 diff = gf_net_get_ntp_diff_ms(ntp); - - if (read->input->query_proxy) { - GF_NetworkCommand param; - GF_Err e; - memset(¶m, 0, sizeof(GF_NetworkCommand)); - param.command_type = GF_NET_SERVICE_QUERY_UTC_DELAY; - e = read->input->query_proxy(read->input, ¶m); - if (e == GF_OK) { - diff -= param.utc_delay.delay; - } - } - - secs = (ntp>>32) - GF_NTP_SEC_1900_TO_1970; - t = *gmtime(&secs); - - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] TrackID %d: Timestamp "LLU" matches sender NTP time %d-%02d-%02dT%02d:%02d:%02dZ - NTP clock diff (local - remote): %d ms\n", trackID, timestamp, 1900+t.tm_year, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, diff)); - } -#endif - - read->last_sender_ntp = ntp; - read->cts_for_last_sender_ntp = timestamp; - } -} - -/* - refresh type: - 0: not progressive - 1: progressive - 2: not progressive and don't check current download -*/ -void isor_segment_switch_or_refresh(ISOMReader *read, Bool do_refresh) -{ - GF_NetworkCommand param; - u32 i, count; - Bool scalable_segment = GF_FALSE; - GF_Err e; - - /*access to the segment switching must be protected in case several decoders are threaded on the file using GetSLPacket */ - gf_mx_p(read->segment_mutex); - - if (!read->frag_type || !read->input->query_proxy ) { - gf_mx_v(read->segment_mutex); - return; - } - - memset(¶m, 0, sizeof(GF_NetworkCommand)); - param.command_type = GF_NET_SERVICE_QUERY_NEXT; - //always check current download - this might be ignored by the dash client depending on lowLatency settings - param.url_query.current_download = 1; - - count = gf_list_count(read->channels); - - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Refresh seg: do_refresh %d - seg opened %d\n", do_refresh, read->seg_opened)); - - if (do_refresh && !read->seg_opened) - do_refresh = 0; - - if ((read->seg_opened==1) && !do_refresh) { - gf_mx_v(read->segment_mutex); - return; - } - - if (read->drop_next_segment) { - read->drop_next_segment = 0; - param.url_query.drop_first_segment = 1; - } - - //if first time trying to fetch next segment, check if we have to discard it - if (!do_refresh && (read->seg_opened==2) && !read->pending_scalable_enhancement_segment_index) { -#ifdef DASH_USE_PULL - for (i=0; i<count; i++) { - ISOMChannel *ch = gf_list_get(read->channels, i); - /*check all playing channels are waiting for next segment*/ - if (ch->is_playing && !ch->wait_for_segment_switch) { - gf_mx_v(read->segment_mutex); - return; - } - } -#endif - - /*close current segment*/ - gf_isom_release_segment(read->mov, 1); - read->seg_opened = 0; - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Done playing segment - querying new one\n")); - - /*drop this segment*/ - param.url_query.drop_first_segment = 1; - } - - if (read->pending_scalable_enhancement_segment_index) { - do_refresh = 0; - param.url_query.drop_first_segment = 0; - param.url_query.dependent_representation_index = read->pending_scalable_enhancement_segment_index; - scalable_segment = 1; - read->pending_scalable_enhancement_segment_index = 0; - } - - -next_segment: - /*update current fragment if any*/ - e = read->input->query_proxy(read->input, ¶m); - if (e == GF_OK) { - u32 trackID = 0; - if (param.url_query.next_url) { - u32 flags; - - //previously loaded file has been aborted, reload segment ! - if (do_refresh && param.url_query.discontinuity_type) { - gf_isom_release_segment(read->mov, 1); - gf_isom_reset_fragment_info(read->mov, 1); - do_refresh = 0; - } - - if (read->reset_frag_state) { - read->reset_frag_state = 0; - gf_isom_reset_fragment_info(read->mov, 0); - } - - //refresh file - if (do_refresh) { - //the url is the current download or we just finished downloaded it, refresh the parsing. - if ((param.url_query.current_download || (read->seg_opened==1)) && param.url_query.has_new_data) { - u64 bytesMissing=0; - e = gf_isom_refresh_fragmented(read->mov, &bytesMissing, read->use_memory ? param.url_query.next_url : NULL); - - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Failed to reparse segment %s: %s\n", param.url_query.next_url, gf_error_to_string(e) )); - } else { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[IsoMedia] LowLatency mode: Reparsing segment %s boxes at UTC "LLU" - "LLU" bytes still missing\n", param.url_query.next_url, gf_net_get_utc(), bytesMissing )); - } -#ifndef GPAC_DISABLE_LOG - if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_DEBUG)) { - for (i=0; i<count; i++) { - ISOMChannel *ch = gf_list_get(read->channels, i); - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] refresh track %d fragment - cur sample %d - new sample count %d\n", ch->track, ch->sample_num, gf_isom_get_sample_count(ch->owner->mov, ch->track) )); - } - } -#endif - isor_check_producer_ref_time(read); - } - //we did the last refresh and the segment is downloaded, move to fully parsed mode - if (! param.url_query.current_download) { - read->seg_opened = 2; - read->waiting_for_data = GF_FALSE; - } - gf_mx_v(read->segment_mutex); - return; - } - - //we have to open the file - - if (param.url_query.discontinuity_type==2) { - gf_isom_reset_fragment_info(read->mov, 0); - read->clock_discontinuity = 1; - } - e = GF_OK; - if (param.url_query.next_url_init_or_switch_segment) { - u64 tfdt = gf_isom_get_current_tfdt(read->mov, 1); - char *tfdt_val = strstr(param.url_query.next_url_init_or_switch_segment, "tfdt="); - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Switching between files - opening new init segment %s (time offset="LLU")\n", param.url_query.next_url_init_or_switch_segment, tfdt)); - if (tfdt_val) { - sprintf(tfdt_val+5, LLU, tfdt); - } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[IsoMedia] Error finding init time for init segment %s at UTC "LLU"\n", param.url_query.next_url_init_or_switch_segment, gf_net_get_utc() )); - } - if (read->mov) gf_isom_close(read->mov); - e = gf_isom_open_progressive(param.url_query.next_url_init_or_switch_segment, param.url_query.switch_start_range, param.url_query.switch_end_range, &read->mov, &read->missing_bytes); - if (e < 0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Error opening init segment %s at UTC "LLU": %s\n", param.url_query.next_url_init_or_switch_segment, gf_net_get_utc(), gf_error_to_string(e) )); - } - } - if (!e) { - flags = 0; - if (read->no_order_check) flags |= GF_ISOM_SEGMENT_NO_ORDER_FLAG; - if (scalable_segment) flags |= GF_ISOM_SEGMENT_SCALABLE_FLAG; - e = gf_isom_open_segment(read->mov, param.url_query.next_url, param.url_query.start_range, param.url_query.end_range, flags); - - if (param.url_query.current_download && (e==GF_ISOM_INCOMPLETE_FILE)) { - e = GF_OK; - } - -#ifndef GPAC_DISABLE_LOG - if (e<0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Error opening new segment %s at UTC "LLU": %s\n", param.url_query.next_url, gf_net_get_utc(), gf_error_to_string(e) )); - } else if (param.url_query.end_range) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Playing new range in %s: "LLU"-"LLU"\n", param.url_query.next_url, param.url_query.start_range, param.url_query.end_range )); - } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] playing new segment %s (has next dep %d)\n", param.url_query.next_url, param.url_query.has_next)); - } -#endif - } - - if (e<0) { - gf_isom_release_segment(read->mov, 1); - //gf_isom_reset_fragment_info(read->mov, 1); - read->drop_next_segment = 1; - //error opening the segment, reset everything ... - gf_isom_reset_fragment_info(read->mov, 0); - for (i=0; i<count; i++) { - ISOMChannel *ch = gf_list_get(read->channels, i); - if (ch) - ch->sample_num = 0; - } - - //cannot open file, don't change our state - gf_mx_v(read->segment_mutex); - return; - } - - //segment is the first in our cache, we may need a refresh - if (param.url_query.current_download ) { - read->seg_opened = 1; - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Opening current segment in progressive mode (download in progress)\n")); - } else { - read->seg_opened = 2; - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Opening current segment in non-progressive mode (completely downloaded)\n")); - } - - isor_check_producer_ref_time(read); - - for (i=0; i<count; i++) { - ISOMChannel *ch = gf_list_get(read->channels, i); - ch->wait_for_segment_switch = 0; - - if (ch->base_track) { - if (scalable_segment) { - trackID = gf_isom_get_highest_track_in_scalable_segment(read->mov, ch->base_track); - if (trackID) { - ch->track_id = trackID; - ch->track = gf_isom_get_track_by_id(read->mov, ch->track_id); - } - } else { - ch->track = ch->base_track; - ch->track_id = gf_isom_get_track_id(read->mov, ch->track); - } - } - - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Track %d - cur sample %d - new sample count %d\n", ch->track, ch->sample_num, gf_isom_get_sample_count(ch->owner->mov, ch->track) )); - if (param.url_query.next_url_init_or_switch_segment) { - ch->track = gf_isom_get_track_by_id(read->mov, ch->track_id); - if (!ch->track) { - if (gf_isom_get_track_count(read->mov)==1) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Mismatch between track IDs of different representations\n")); - ch->track = 1; - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Mismatch between track IDs of different representations\n")); - } - } - - /*we changed our moov structure, sample_num now starts from 0*/ - ch->sample_num = 0; - } - //a loop was detected, our timing is no longer reliable if we use edit lists - just reset the sample time to tfdt ... - else if (param.url_query.discontinuity_type==2) { - ch->sample_num = 0; - if (ch->has_edit_list) { - ch->sample_time = gf_isom_get_current_tfdt(read->mov, ch->track); - //next read will query sample for ch->sample_time + 1 - if (ch->sample_time) ch->sample_time--; - } - } - /*rewrite all upcoming SPS/PPS into the samples*/ - gf_isom_set_nalu_extract_mode(read->mov, ch->track, ch->nalu_extract_mode); - ch->last_state = GF_OK; - - - if (ch->is_cenc) { - isor_send_cenc_config(ch); - } - - } - - read->use_memory = !strncmp(param.url_query.next_url, "gmem://", 7) ? GF_TRUE : GF_FALSE; - } - if (param.url_query.has_next) { - param.url_query.drop_first_segment = GF_FALSE; - param.url_query.dependent_representation_index++; - scalable_segment = 1; - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Enhancement layer available in cache - refreshing it\n")); - goto next_segment; - } - read->waiting_for_data = GF_FALSE; - } else if (e==GF_EOS) { - /*consider we are done*/ - read->frag_type = 2; - read->waiting_for_data = GF_FALSE; - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] No more segments - done playing file\n")); - } else if (e==GF_BUFFER_TOO_SMALL) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Next segment is not yet available\n")); - read->waiting_for_data = GF_TRUE; - read->pending_scalable_enhancement_segment_index = param.url_query.dependent_representation_index; - } else { - /*consider we are done*/ - read->frag_type = 2; - read->waiting_for_data = GF_FALSE; - GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Error fetching next DASH segment: no more segments\n")); - } - gf_mx_v(read->segment_mutex); -} - -static void init_reader(ISOMChannel *ch) -{ - u32 sample_desc_index=0; - if (ch->is_pulling && ch->wait_for_segment_switch) { - isor_segment_switch_or_refresh(ch->owner, 0); - if (ch->wait_for_segment_switch) - return; - } - - ch->current_slh.accessUnitEndFlag = 1; - ch->current_slh.accessUnitStartFlag = 1; - ch->current_slh.AU_sequenceNumber = 1; - ch->current_slh.compositionTimeStampFlag = 1; - ch->current_slh.decodingTimeStampFlag = 1; - ch->current_slh.packetSequenceNumber = 1; - ch->current_slh.randomAccessPointFlag = 0; - - assert(ch->sample==NULL); - - if (ch->streamType==GF_STREAM_OCR) { - assert(!ch->sample); - ch->sample = gf_isom_sample_new(); - ch->sample->IsRAP = RAP; - ch->sample->DTS = ch->start; - ch->last_state=GF_OK; - } else { - //if seek is disabled, get the next closest sample for this time; otherwose, get the previous RAP sample for this time - u32 mode = ch->disable_seek ? GF_ISOM_SEARCH_BACKWARD : GF_ISOM_SEARCH_SYNC_BACKWARD; - - /*take care of seeking out of the track range*/ - if (!ch->owner->frag_type && (ch->duration<ch->start)) { - ch->last_state = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->duration, &sample_desc_index, mode, &ch->sample, &ch->sample_num); - } else { - ch->last_state = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->start, &sample_desc_index, mode, &ch->sample, &ch->sample_num); - } - ch->last_state = GF_OK; - - if (ch->has_rap && ch->has_edit_list) { - ch->edit_sync_frame = ch->sample_num; - } - } - - /*no sample means we're not in the track range - stop*/ - if (!ch->sample) { - /*incomplete file - check if we're still downloading or not*/ - if (gf_isom_get_missing_bytes(ch->owner->mov, ch->track)) { - GF_NetIOStatus net_status; - gf_dm_sess_get_stats(ch->owner->dnload, NULL, NULL, NULL, NULL, NULL, &net_status); - if (net_status == GF_NETIO_DATA_EXCHANGE) { - ch->last_state = GF_OK; - return; - } - ch->last_state = GF_ISOM_INCOMPLETE_FILE; - } else if (ch->sample_num) { - ch->last_state = (ch->owner->frag_type==1) ? GF_OK : GF_EOS; - ch->to_init = 0; - } - return; - } - - if (ch->has_edit_list) { - ch->sample_time = ch->sample->DTS; - } else { - //store movie time in media timescale in the sample time, eg no edit list is used but we may have a shift (dts_offset) between - //movie and media timelines - - if ((ch->dts_offset<0) && (ch->sample->DTS < (u64) -ch->dts_offset)) { - ch->sample_time = 0; - ch->do_dts_shift_test = GF_TRUE; - } else { - ch->sample_time = ch->sample->DTS + ch->dts_offset; - } - } - ch->to_init = GF_FALSE; - - ch->current_slh.seekFlag = 0; - if (ch->disable_seek) { - ch->current_slh.decodingTimeStamp = ch->sample->DTS; - ch->current_slh.compositionTimeStamp = ch->sample->DTS + ch->sample->CTS_Offset; - ch->start = 0; - } else { - ch->current_slh.decodingTimeStamp = ch->start; - ch->current_slh.compositionTimeStamp = ch->start; - - //TODO - we need to notify scene decoder how many secs elapsed between RAP and seek point - if (ch->current_slh.compositionTimeStamp != ch->sample->DTS + ch->sample->CTS_Offset) { - ch->current_slh.seekFlag = 1; - } - } - ch->current_slh.randomAccessPointFlag = ch->sample ? ch->sample->IsRAP : 0; - if (!sample_desc_index) sample_desc_index = 1; - ch->last_sample_desc_index = sample_desc_index; - ch->owner->no_order_check = ch->speed < 0 ? GF_TRUE : GF_FALSE; -} - - - -void isor_reader_get_sample(ISOMChannel *ch) -{ - GF_Err e; - u32 sample_desc_index; - if (ch->sample) return; - - if (ch->next_track) { - ch->track = ch->next_track; - ch->next_track = 0; - } - - if ((ch->owner->seg_opened==1) && ch->is_pulling) { - isor_segment_switch_or_refresh(ch->owner, GF_TRUE); - } - - if (ch->to_init) { - init_reader(ch); - sample_desc_index = ch->last_sample_desc_index; - } else if (ch->speed < 0) { - if (!ch->sample_time) { - ch->last_state = GF_EOS; - return; - } else { - e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->sample_time - 1, &sample_desc_index, GF_ISOM_SEARCH_SYNC_BACKWARD, &ch->sample, &ch->sample_num); - if (e) { - if ((e==GF_EOS) && !ch->owner->frag_type) { - ch->last_state = GF_EOS; - } - return; - } - } - if (ch->sample->DTS + ch->dts_offset == ch->sample_time) { - if (!ch->owner->frag_type) { - ch->last_state = GF_EOS; - } else { - if (ch->sample) - gf_isom_sample_del(&ch->sample); - } - } - if (ch->sample) { - if ((ch->dts_offset<0) && (ch->sample->DTS < (u64) -ch->dts_offset)) //should not happen - ch->sample_time = 0; - else - ch->sample_time = ch->sample->DTS + ch->dts_offset; - } - - } else if (ch->has_edit_list) { - u32 prev_sample = ch->sample_num; - e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->sample_time + 1, &sample_desc_index, GF_ISOM_SEARCH_FORWARD, &ch->sample, &ch->sample_num); - - if (e == GF_OK) { - - /*we are in forced seek mode: fetch all samples before the one matching the sample time*/ - if (ch->edit_sync_frame) { - ch->edit_sync_frame++; - if (ch->edit_sync_frame < ch->sample_num) { - gf_isom_sample_del(&ch->sample); - ch->sample = gf_isom_get_sample(ch->owner->mov, ch->track, ch->edit_sync_frame, &sample_desc_index); - ch->sample->DTS = ch->sample_time; - ch->sample->CTS_Offset = 0; - } else { - ch->edit_sync_frame = 0; - if (ch->sample) ch->sample_time = ch->sample->DTS; - } - } else { - /*if we get the same sample, figure out next interesting time (current sample + DTS gap to next sample should be a good bet)*/ - if (prev_sample == ch->sample_num) { - if (ch->owner->frag_type && (ch->sample_num==gf_isom_get_sample_count(ch->owner->mov, ch->track))) { - if (ch->sample) - gf_isom_sample_del(&ch->sample); - } else { - u32 time_diff = 2; - u32 sample_num = ch->sample_num ? ch->sample_num : 1; - GF_ISOSample *s1 = gf_isom_get_sample(ch->owner->mov, ch->track, sample_num, NULL); - GF_ISOSample *s2 = gf_isom_get_sample(ch->owner->mov, ch->track, sample_num+1, NULL); - - gf_isom_sample_del(&ch->sample); - - if (s2 && s1) { - assert(s2->DTS >= s1->DTS); - time_diff = (u32) (s2->DTS - s1->DTS); - /*e = */gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->sample_time + time_diff, &sample_desc_index, GF_ISOM_SEARCH_FORWARD, &ch->sample, &ch->sample_num); - } else if (s1 && !s2) { - /*e = GF_EOS;*/ - } - gf_isom_sample_del(&s1); - gf_isom_sample_del(&s2); - - } - } - - /*we jumped to another segment - if RAP is needed look for closest rap in decoding order and - force seek mode*/ - if (ch->sample && !ch->sample->IsRAP && ch->has_rap && (ch->sample_num != prev_sample+1)) { - GF_ISOSample *found = ch->sample; - u32 samp_num = ch->sample_num; - ch->sample = NULL; - e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->sample_time + 1, &sample_desc_index, GF_ISOM_SEARCH_SYNC_BACKWARD, &ch->sample, &ch->sample_num); - assert (e == GF_OK); - /*if no sync point in the past, use the first non-sync for the given time*/ - if (!ch->sample || !ch->sample->data) { - gf_isom_sample_del(&ch->sample); - ch->sample = found; - ch->sample_time = ch->sample->DTS; - ch->sample_num = samp_num; - } else { - gf_isom_sample_del(&found); - ch->edit_sync_frame = ch->sample_num; - ch->sample->DTS = ch->sample_time; - ch->sample->CTS_Offset = 0; - } - } else { - if (ch->sample) ch->sample_time = ch->sample->DTS; - } - } - } - } else { - ch->sample_num++; - - ch->sample = gf_isom_get_sample(ch->owner->mov, ch->track, ch->sample_num, &sample_desc_index); - /*if sync shadow / carousel RAP skip*/ - if (ch->sample && (ch->sample->IsRAP==RAP_REDUNDANT)) { - gf_isom_sample_del(&ch->sample); - ch->sample_num++; - isor_reader_get_sample(ch); - return; - } - } - - //check scalable track change - if (ch->sample && ch->sample->IsRAP && ch->next_track) { - ch->track = ch->next_track; - ch->next_track = 0; - gf_isom_sample_del(&ch->sample); - isor_reader_get_sample(ch); - return; - } - - if (!ch->sample) { - /*incomplete file - check if we're still downloading or not*/ - if (gf_isom_get_missing_bytes(ch->owner->mov, ch->track)) { - ch->last_state = GF_ISOM_INCOMPLETE_FILE; - if (ch->owner->dnload) { - GF_NetIOStatus net_status; - gf_dm_sess_get_stats(ch->owner->dnload, NULL, NULL, NULL, NULL, NULL, &net_status); - if (net_status == GF_NETIO_DATA_EXCHANGE) { - ch->last_state = GF_OK; - if (!ch->has_edit_list) - ch->sample_num--; - } - } - else if (ch->owner->input->query_proxy) { - ch->last_state = GF_OK; - if (!ch->has_edit_list && ch->sample_num) - ch->sample_num--; - } - } - else if (!ch->sample_num - || ((ch->speed >= 0) && (ch->sample_num >= gf_isom_get_sample_count(ch->owner->mov, ch->track))) - || ((ch->speed < 0) && (ch->sample_time == gf_isom_get_current_tfdt(ch->owner->mov, ch->track) + ch->dts_offset)) - ) { - - if (ch->owner->frag_type==1) { - //if segment is fully opened and no more data, this track is done, wait for next segment - if (!ch->wait_for_segment_switch && ch->owner->input->query_proxy && (ch->owner->seg_opened==2) ) { - ch->wait_for_segment_switch = GF_TRUE; - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Track #%d end of segment reached - waiting for sample %d - current count %d\n", ch->track, ch->sample_num, gf_isom_get_sample_count(ch->owner->mov, ch->track) )); - } - /*if sample cannot be found and file is fragmented, rewind sample*/ - if (ch->sample_num) ch->sample_num--; - ch->last_state = GF_OK; - } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Track #%d end of stream reached\n", ch->track)); - ch->last_state = GF_EOS; - } - } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Track #%d fail to fetch sample %d / %d: %s\n", ch->track, ch->sample_num, gf_isom_get_sample_count(ch->owner->mov, ch->track), gf_error_to_string(gf_isom_last_error(ch->owner->mov)) )); - } - if (ch->wait_for_segment_switch && ch->is_pulling) { - isor_segment_switch_or_refresh(ch->owner, 0); - if (ch->owner->seg_opened) { - isor_reader_get_sample(ch); - return; - } - } - return; - } - - if (sample_desc_index != ch->last_sample_desc_index) { - u32 mtype = gf_isom_get_media_type(ch->owner->mov, ch->track); - switch (mtype) { - case GF_ISOM_MEDIA_VISUAL: - //code is here as a reminder, but by default we use inband param set extraction so no need for it -#if 0 - if ( ! (ch->nalu_extract_mode & GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG) ) { - u32 extract_mode = ch->nalu_extract_mode | GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG; - - gf_isom_sample_del(&ch->sample); - ch->sample = NULL; - gf_isom_set_nalu_extract_mode(ch->owner->mov, ch->track, extract_mode); - ch->sample = gf_isom_get_sample(ch->owner->mov, ch->track, ch->sample_num, &ch->last_sample_desc_index); - - gf_isom_set_nalu_extract_mode(ch->owner->mov, ch->track, ch->nalu_extract_mode); - } -#endif - break; - case GF_ISOM_MEDIA_TEXT: - case GF_ISOM_MEDIA_SUBPIC: - case GF_ISOM_MEDIA_MPEG_SUBT: - break; - default: - //TODO: do we want to support codec changes ? - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[IsoMedia] Change of sample description (%d->%d) for media type %s not supported\n", ch->last_sample_desc_index, sample_desc_index, gf_4cc_to_str(mtype) )); - gf_isom_sample_del(&ch->sample); - ch->sample = NULL; - ch->last_state = GF_NOT_SUPPORTED; - return; - } - } - - ch->last_state = GF_OK; - ch->current_slh.accessUnitEndFlag = ch->current_slh.accessUnitStartFlag = 1; - ch->current_slh.accessUnitLength = ch->sample->dataLength; - ch->current_slh.au_duration = gf_isom_get_sample_duration(ch->owner->mov, ch->track, ch->sample_num); - - //update timestamp when single edit - if (ch->sample && ch->dts_offset) { - if (ch->do_dts_shift_test) { - s64 DTS, CTS; - DTS = (s64) ch->sample->DTS + ch->dts_offset; - CTS = (s64) ch->sample->DTS + ch->dts_offset + (s32) ch->sample->CTS_Offset; - if (DTS<0) - DTS=0; - else - ch->do_dts_shift_test = GF_FALSE; - - if (CTS<0) CTS=0; - ch->sample->DTS = (u64) DTS; - ch->sample->CTS_Offset = (s32) (CTS - DTS); - } else { - ch->sample->DTS = ch->sample->DTS + ch->dts_offset; - } - } - - /*still seeking or not ? - 1- when speed is negative, the RAP found is "after" the seek point in playback order since we used backward RAP search: nothing to do - 2- otherwise set DTS+CTS to start value - */ - if ((ch->speed < 0) || (ch->start <= ch->sample->DTS + ch->sample->CTS_Offset)) { - ch->current_slh.decodingTimeStamp = ch->sample->DTS; - ch->current_slh.compositionTimeStamp = ch->sample->DTS + ch->sample->CTS_Offset; - ch->current_slh.seekFlag = 0; - } else { - ch->current_slh.compositionTimeStamp = ch->start; - ch->current_slh.seekFlag = 1; - ch->current_slh.decodingTimeStamp = ch->start; - } - ch->current_slh.randomAccessPointFlag = ch->sample->IsRAP; - ch->current_slh.OCRflag = ch->owner->clock_discontinuity ? 2 : 0; - ch->owner->clock_discontinuity = 0; - - //handle negative ctts - if (ch->current_slh.decodingTimeStamp > ch->current_slh.compositionTimeStamp) - ch->current_slh.decodingTimeStamp = ch->current_slh.compositionTimeStamp; - - if (ch->end && (ch->end < ch->sample->DTS + ch->sample->CTS_Offset)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] End of Channel "LLD" (CTS "LLD")\n", ch->end, ch->sample->DTS + ch->sample->CTS_Offset)); - ch->last_state = GF_EOS; - } - if (ch->owner->last_sender_ntp && ch->current_slh.compositionTimeStamp==ch->owner->cts_for_last_sender_ntp) { - ch->current_slh.sender_ntp = ch->owner->last_sender_ntp; - } else { - ch->current_slh.sender_ntp = 0; - } - - - if (ch->is_encrypted) { - GF_ISMASample *ismasamp = gf_isom_get_ismacryp_sample(ch->owner->mov, ch->track, ch->sample, 1); - if (ismasamp) { - gf_free(ch->sample->data); - ch->sample->data = ismasamp->data; - ch->sample->dataLength = ismasamp->dataLength; - ismasamp->data = NULL; - ismasamp->dataLength = 0; - ch->current_slh.isma_encrypted = (ismasamp->flags & GF_ISOM_ISMA_IS_ENCRYPTED) ? 1 : 0; - ch->current_slh.isma_BSO = ismasamp->IV; - gf_isom_ismacryp_delete_sample(ismasamp); - } else { - ch->current_slh.isma_encrypted = 0; - /*in case of CENC: we write sample auxiliary information to slh->sai; its size is in saiz*/ - if (gf_isom_is_cenc_media(ch->owner->mov, ch->track, 1)) { - GF_CENCSampleAuxInfo *sai; - u32 Is_Encrypted; - u8 IV_size; - bin128 KID; - u8 crypt_bytr_block, skip_byte_block; - u8 constant_IV_size; - bin128 constant_IV; - - gf_isom_get_sample_cenc_info(ch->owner->mov, ch->track, ch->sample_num, &Is_Encrypted, &IV_size, &KID, &crypt_bytr_block, &skip_byte_block, &constant_IV_size, &constant_IV); - ch->current_slh.IV_size = IV_size; - ch->current_slh.crypt_byte_block = crypt_bytr_block; - ch->current_slh.skip_byte_block = skip_byte_block; - if (Is_Encrypted && !ch->current_slh.IV_size) { - ch->current_slh.constant_IV_size = constant_IV_size; - memmove(ch->current_slh.constant_IV, constant_IV, ch->current_slh.constant_IV_size); - } - if (Is_Encrypted) { - ch->current_slh.cenc_encrypted = 1; - sai = NULL; - gf_isom_cenc_get_sample_aux_info(ch->owner->mov, ch->track, ch->sample_num, &sai, NULL); - if (sai) { - u32 i; - GF_BitStream *bs; - bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - /*write sample auxiliary information*/ - gf_bs_write_data(bs, (char *)KID, 16); - gf_bs_write_data(bs, (char *)sai->IV, IV_size); - gf_bs_write_u16(bs, sai->subsample_count); - for (i = 0; i < sai->subsample_count; i++) { - gf_bs_write_u16(bs, sai->subsamples[i].bytes_clear_data); - gf_bs_write_u32(bs, sai->subsamples[i].bytes_encrypted_data); - } - gf_bs_get_content(bs, &ch->current_slh.sai, &ch->current_slh.saiz); - gf_bs_del(bs); - gf_isom_cenc_samp_aux_info_del(sai); - sai = NULL; - } - } - else - ch->current_slh.cenc_encrypted = 0; - } - } - } -} - -void isor_reader_release_sample(ISOMChannel *ch) -{ - if (ch->current_slh.sai) { - gf_free(ch->current_slh.sai); - ch->current_slh.sai = NULL; - } - if (ch->sample) gf_isom_sample_del(&ch->sample); - ch->sample = NULL; - ch->current_slh.AU_sequenceNumber++; - ch->current_slh.packetSequenceNumber++; -} - - -void isor_flush_data(ISOMReader *read, Bool check_buffer_level, Bool is_chunk_flush) -{ - u32 i, count; - GF_Err e = GF_OK; - Bool do_refresh; - GF_NetworkCommand com; - ISOMChannel *ch; - - if (read->in_data_flush) { - if (check_buffer_level && !is_chunk_flush) read->has_pending_segments++; - return; - } - - //if another thread grabs the mutex at the same time, just return - if (!gf_mx_try_lock(read->segment_mutex)) { - if (check_buffer_level && !is_chunk_flush) read->has_pending_segments++; - return; - } - read->in_data_flush = 1; - count = gf_list_count(read->channels); - - do_refresh = (read->seg_opened==1) ? 1 : 0; - - if (do_refresh) { - //query from terminal - do nothing if we are not done downloading the segment - otherwise we could access media data while it is reallocated by the downloader - if (!check_buffer_level) { - read->in_data_flush = 0; - gf_mx_v(read->segment_mutex); - return; - } - //otherwise this is new chunk or end of chunk, process - } - //this is a new file, check buffer level - else if (/*!is_chunk_flush && */ check_buffer_level) { - Bool buffer_full = 1; - for (i=0; i<count; i++) { - ch = (ISOMChannel *)gf_list_get(read->channels, i); - /*query buffer level on each channel, don't sleep if too low*/ - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.command_type = GF_NET_BUFFER_QUERY; - com.base.on_channel = ch->channel; - gf_service_command(read->service, &com, GF_OK); - if ((com.buffer.occupancy < MAX(com.buffer.max, 1000) )) { - buffer_full = 0; - break; - } - } - if (buffer_full) { - read->has_pending_segments++; - read->in_data_flush = 0; - gf_mx_v(read->segment_mutex); - if (count) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Buffer level %d ms higher than max allowed %d ms - skipping dispatch\n", com.buffer.occupancy, com.buffer.max)); - } - return; - } - } - //flush request from terminal: only process if nothing is opened and we have pending segments - //we have to keep the polling event when no segments are pending, in order to detect period switch - we therefore tolerate a couble of requests even though no segments are pending - if (!check_buffer_level && read->seg_opened && (!read->has_pending_segments || (read->nb_force_flush > 2)) && !read->drop_next_segment) { - read->in_data_flush = 0; - gf_mx_v(read->segment_mutex); - return; - } - - //if this is a request from terminal to flush pending segments, try to refresh - if (!check_buffer_level && !do_refresh) - do_refresh = 1; - - //update data - isor_segment_switch_or_refresh(read, do_refresh); - - //segment not ready - if ( read->pending_scalable_enhancement_segment_index) { - read->in_data_flush = 0; - gf_mx_v(read->segment_mutex); - return; - } - - - //for all channels, fetch and send ... - count = gf_list_count(read->channels); - for (i=0; i<count; i++) { - ch = (ISOMChannel *)gf_list_get(read->channels, i); - - while (!ch->sample) { - isor_reader_get_sample(ch); - if (!ch->sample) break; - if (ch->is_playing) - gf_service_send_packet(read->service, ch->channel, ch->sample->data, ch->sample->dataLength, &ch->current_slh, GF_OK); - isor_reader_release_sample(ch); - } - if (!ch->sample && (ch->last_state==GF_EOS)) { - gf_service_send_packet(read->service, ch->channel, NULL, 0, NULL, GF_EOS); - } - } - - //done playing the file after notification from DASH client: - if (read->seg_opened==2) { - GF_NetworkCommand param; - //drop this segment - memset(¶m, 0, sizeof(GF_NetworkCommand)); - param.command_type = GF_NET_SERVICE_QUERY_NEXT; - param.url_query.dependent_representation_index = 0; - param.url_query.drop_first_segment = 1; - e = read->input->query_proxy(read->input, ¶m); - - read->nb_force_flush = 0; - if (read->has_pending_segments) { - read->has_pending_segments--; - } - - if (e==GF_EOS) { - for (i=0; i<count; i++) { - ch = (ISOMChannel *)gf_list_get(read->channels, i); - gf_service_send_packet(read->service, ch->channel, NULL, 0, NULL, GF_EOS); - } - } - //not enough data - else if (param.url_query.in_end_of_period) { - //act as if we have a pending segment to process at next call until we get to the next period - if (!read->has_pending_segments) - read->has_pending_segments++; - } - - /*close current segment*/ - gf_isom_release_segment(read->mov, 1); - read->seg_opened = 0; - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Done playing segment - closing it\n")); - } - read->in_data_flush = 0; - - if (!read->has_pending_segments) { - read->nb_force_flush ++; - } - gf_mx_v(read->segment_mutex); -} - -#endif /*GPAC_DISABLE_ISOM*/ diff --git a/modules/jack/Makefile b/modules/jack/Makefile index 8577e14..6423d76 100644 --- a/modules/jack/Makefile +++ b/modules/jack/Makefile @@ -2,14 +2,14 @@ include ../../config.mak vpath %.c $(SRC_PATH)/modules/jack -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" $(OSS_CFLAGS) -Wno-deprecated-declarations +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) $(OSS_CFLAGS) -Wno-deprecated-declarations -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif @@ -25,7 +25,7 @@ LIB=gm_jack$(DYN_LIB_SUFFIX) all: $(LIB) $(LIB): $(OBJS) - $(CC) $(SHFLAGS) $(LDFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac -L/usr/lib -ljack + $(CC) $(SHFLAGS) $(LDFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac -L$(prefix)/$(libdir) -ljack clean: rm -f $(OBJS) ../../bin/gcc/$(LIB) diff --git a/modules/jack/jack.c b/modules/jack/jack.c index 78ef71a..a869d1e 100644 --- a/modules/jack/jack.c +++ b/modules/jack/jack.c @@ -1,14 +1,9 @@ /* * GPAC - Multimedia Framework C SDK * - * Copyright (c) Pierre Souchay 2008 - * History: - * - * 2008/02/19 - v1.1 (Pierre Souchay) - * first revision - * 2008/03/11 - v1.2 (Pierre Souchay) - * added volume control - * fixed possible bug in latency computation (did not return the max value) + * Authors: Pierre Souchay , Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2008-2019 + * All rights reserved * * Jack audio output module : output audio thru the jackd daemon * @@ -38,22 +33,6 @@ #include <jack/ringbuffer.h> #include <gpac/modules/audio_out.h> -#ifndef WIN32 -#include <unistd.h> -int -getPid () -{ - return getpid (); -} -#else -#include <windows.h> -int -getPid () -{ - return GetCurrentProcessId(); -} -#endif - #define MAX_JACK_CLIENT_NAME_SZ 128 /* * This structure defines the handle to a Jack driver @@ -68,27 +47,19 @@ typedef struct char *buffer; u32 bufferSz; u32 bytesPerSample; - char isActive; - char autoConnect; - char autoStartJackd; + Bool isActive; + Bool autoConnect; jack_default_audio_sample_t **channels; float volume; } JackContext; -#ifndef TRUE -#define TRUE 1 -#endif -#ifndef FALSE -#define FALSE 0 -#endif - static void Jack_cleanup (JackContext * ctx) { - u32 channels = 0; + u32 channels; if (ctx == NULL) return; - ctx->isActive = 0; + GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO, ("[Jack] Jack_cleanup\n")); if (ctx->jack != NULL && ctx->isActive) { @@ -124,6 +95,7 @@ Jack_cleanup (JackContext * ctx) ctx->currentBlockSize = 0; memset (ctx->jackClientName, 0, MAX_JACK_CLIENT_NAME_SZ); ctx->jack = NULL; + ctx->isActive = 0; } /** @@ -133,10 +105,8 @@ static int process_callback (jack_nframes_t nframes, void *arg) { unsigned int channel, i; - short *tmpBuffer; size_t toRead; size_t bytesToRead; - size_t readen; GF_AudioOutput *dr = (GF_AudioOutput *) arg; JackContext *ctx; if (dr == NULL) @@ -147,11 +117,12 @@ process_callback (jack_nframes_t nframes, void *arg) ctx = dr->opaque; toRead = nframes * ctx->numChannels; bytesToRead = toRead * ctx->bytesPerSample; - readen = dr->FillBuffer (dr->audio_renderer, (void *) ctx->buffer, + dr->FillBuffer (dr->audio_renderer, (void *) ctx->buffer, bytesToRead); - toRead = readen / ctx->bytesPerSample; + if (ctx->bytesPerSample == 2) { + short *tmpBuffer; tmpBuffer = (short *) ctx->buffer; for (channel = 0; channel < nframes; channel += ctx->numChannels) { @@ -233,73 +204,24 @@ onBufferSizeChanged (jack_nframes_t nframes, void *arg) return 0; } -static const char *MODULE_NAME = "Jack"; - -static const char *AUTO_CONNECT_OPTION = "AutoConnect"; - -static const char *AUTO_START_JACKD_OPTION = "AutoStartJackd"; - -static const char *TRUE_OPTION = "true"; - -static const char *YES_OPTION = "yes"; - -static char -optionIsTrue (const char *optionValue) -{ - return (0 == strcasecmp (TRUE_OPTION, optionValue) || - 0 == strcasecmp (YES_OPTION, optionValue) - || 0 == strcmp ("1", optionValue)); -} - - static GF_Err Jack_Setup (GF_AudioOutput * dr, void *os_handle, u32 num_buffers, u32 total_duration) { - const char *opt; JackContext *ctx = (JackContext *) dr->opaque; jack_status_t status; jack_options_t options = JackNullOption; memset (ctx->jackClientName, 0, MAX_JACK_CLIENT_NAME_SZ); - snprintf (ctx->jackClientName, MAX_JACK_CLIENT_NAME_SZ, "gpac-%d", - getPid ()); + snprintf (ctx->jackClientName, MAX_JACK_CLIENT_NAME_SZ, "gpac-%d", gf_sys_get_process_id() ); - opt = - gf_modules_get_option ((GF_BaseInterface *) dr, MODULE_NAME, - AUTO_CONNECT_OPTION); - if (opt != NULL) - { - if (optionIsTrue (opt)) - ctx->autoConnect = TRUE; - else - ctx->autoConnect = FALSE; - } - else - { - ctx->autoConnect = TRUE; - gf_modules_set_option ((GF_BaseInterface *) dr, MODULE_NAME, - AUTO_CONNECT_OPTION, YES_OPTION); - } - opt = gf_modules_get_option ((GF_BaseInterface *) dr, MODULE_NAME, - AUTO_START_JACKD_OPTION); - if (opt != NULL) - { - if (optionIsTrue (opt)) - ctx->autoStartJackd = TRUE; - else - ctx->autoStartJackd = FALSE; - } - else - { - ctx->autoStartJackd = TRUE; - gf_modules_set_option ((GF_BaseInterface *) dr, MODULE_NAME, - AUTO_START_JACKD_OPTION, YES_OPTION); - } - if (!ctx->autoStartJackd) - { + ctx->autoConnect = GF_TRUE; + if (gf_opts_get_bool("Jack", "NoAutoConnect")) + ctx->autoConnect = GF_FALSE; + + if (gf_opts_get_bool("Jack", "NoStartServer")) options |= JackNoStartServer; - } + ctx->jack = jack_client_open (ctx->jackClientName, options, &status, NULL); if (status & JackNameNotUnique) { @@ -330,8 +252,7 @@ Jack_Shutdown (GF_AudioOutput * dr) #define JACK_PORT_NAME_MAX_SZ 128 static GF_Err -Jack_ConfigureOutput (GF_AudioOutput * dr, u32 * SampleRate, u32 * NbChannels, - u32 * nbBitsPerSample, u32 channel_cfg) +Jack_Configure(GF_AudioOutput * dr, u32 * SampleRate, u32 * NbChannels, u32 *audioFormat, u64 channel_cfg) { u32 channels; u32 i; @@ -339,18 +260,24 @@ Jack_ConfigureOutput (GF_AudioOutput * dr, u32 * SampleRate, u32 * NbChannels, JackContext *ctx = (JackContext *) dr->opaque; if (!ctx) return GF_BAD_PARAM; - ctx->bytesPerSample = *nbBitsPerSample / 8; - if (ctx->bytesPerSample > 2 || ctx->bytesPerSample < 1) - { - GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO, - ("[Jack] Jack-ConfigureOutput : unable to use %d bits/sample.\n")); - return GF_BAD_PARAM; + + //only support for PCM 8/16/24/32 packet mode + switch (*audioFormat) { + case GF_AUDIO_FMT_U8: + ctx->bytesPerSample = 1; + break; + default: + //otherwise force PCM16 + *audioFormat = GF_AUDIO_FMT_S16; + case GF_AUDIO_FMT_S16: + ctx->bytesPerSample = 2; + break; } ctx->numChannels = *NbChannels; *SampleRate = jack_get_sample_rate (ctx->jack); GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO, - ("[Jack] Jack_ConfigureOutput channels=%d, srate=%d bits/sample=%d\n", - *NbChannels, *SampleRate, *nbBitsPerSample)); + ("[Jack] Jack_ConfigureOutput channels=%d, srate=%d audio format %s\n", + *NbChannels, *SampleRate, gf_audio_fmt_name(*audioFormat) )); if (ctx->jackPorts == NULL) ctx->jackPorts = gf_calloc (ctx->numChannels, sizeof (jack_port_t *)); if (ctx->jackPorts == NULL) @@ -369,7 +296,9 @@ Jack_ConfigureOutput (GF_AudioOutput * dr, u32 * SampleRate, u32 * NbChannels, if (ctx->jackPorts[channels] == NULL) goto exit_cleanup; } - onBufferSizeChanged (jack_get_buffer_size (ctx->jack), dr); +// onBufferSizeChanged (jack_get_buffer_size (ctx->jack), dr); +// if (!ctx->jack) return GF_IO_ERR; + jack_set_buffer_size_callback (ctx->jack, onBufferSizeChanged, dr); jack_set_process_callback (ctx->jack, process_callback, dr); } @@ -403,7 +332,7 @@ Jack_ConfigureOutput (GF_AudioOutput * dr, u32 * SampleRate, u32 * NbChannels, } } } - ctx->isActive = TRUE; + ctx->isActive = GF_TRUE; } return GF_OK; exit_cleanup: @@ -501,7 +430,7 @@ NewJackOutput () driv->SelfThreaded = 1; driv->Setup = Jack_Setup; driv->Shutdown = Jack_Shutdown; - driv->ConfigureOutput = Jack_ConfigureOutput; + driv->Configure = Jack_Configure; driv->GetAudioDelay = Jack_GetAudioDelay; driv->SetVolume = Jack_SetVolume; driv->SetPan = Jack_SetPan; @@ -516,9 +445,8 @@ NewJackOutput () ctx->buffer = NULL; ctx->bufferSz = 0; ctx->bytesPerSample = 0; - ctx->isActive = FALSE; - ctx->autoConnect = FALSE; - ctx->autoStartJackd = FALSE; + ctx->isActive = GF_FALSE; + ctx->autoConnect = GF_FALSE; ctx->volume = 1.0; GF_REGISTER_MODULE_INTERFACE (driv, GF_AUDIO_OUTPUT_INTERFACE, diff --git a/modules/laser_dec/Makefile b/modules/laser_dec/Makefile deleted file mode 100644 index b63e166..0000000 --- a/modules/laser_dec/Makefile +++ /dev/null @@ -1,50 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/laser_dec - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS=laser_dec.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_laser_dec$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols laser_dec.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_laser_dec-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac_static $(LDFLAGS) -endif - - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/laser_dec/laser_dec.c b/modules/laser_dec/laser_dec.c deleted file mode 100644 index 11f71f1..0000000 --- a/modules/laser_dec/laser_dec.c +++ /dev/null @@ -1,202 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2012 - * All rights reserved - * - * This file is part of GPAC / LASeR decoder module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include <gpac/internal/terminal_dev.h> -#include <gpac/laser.h> -#include <gpac/constants.h> - -#ifndef GPAC_DISABLE_LASER - -typedef struct -{ - GF_Scene *pScene; - GF_Terminal *app; - GF_LASeRCodec *codec; - u32 PL, nb_streams; -} LSRPriv; - - - -static GF_Err LSR_GetCapabilities(GF_BaseDecoder *plug, GF_CodecCapability *cap) -{ - cap->cap.valueInt = 0; - return GF_NOT_SUPPORTED; -} - -static GF_Err LSR_SetCapabilities(GF_BaseDecoder *plug, const GF_CodecCapability capability) -{ - return GF_OK; -} - -GF_Err LSR_AttachScene(GF_SceneDecoder *plug, GF_Scene *scene, Bool is_scene_decoder) -{ - LSRPriv *priv = (LSRPriv *)plug->privateStack; - if (priv->codec) return GF_BAD_PARAM; - priv->pScene = scene; - priv->app = scene->root_od->term; - - priv->codec = gf_laser_decoder_new(scene->graph); - /*attach the clock*/ - gf_laser_decoder_set_clock(priv->codec, gf_scene_get_time, scene); - return GF_OK; -} - -GF_Err LSR_ReleaseScene(GF_SceneDecoder *plug) -{ - LSRPriv *priv = (LSRPriv *)plug->privateStack; - if (!priv->codec || priv->nb_streams) return GF_BAD_PARAM; - gf_laser_decoder_del(priv->codec); - priv->codec = NULL; - return GF_OK; -} - -static GF_Err LSR_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd) -{ - LSRPriv *priv = (LSRPriv *)plug->privateStack; - GF_Err e; - if (esd->decoderConfig->upstream) return GF_NOT_SUPPORTED; - e = gf_laser_decoder_configure_stream(priv->codec, esd->ESID, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength); - if (!e) priv->nb_streams++; - return e; -} - -static GF_Err LSR_DetachStream(GF_BaseDecoder *plug, u16 ES_ID) -{ - GF_Err e; - LSRPriv *priv = (LSRPriv *)plug->privateStack; - e = gf_laser_decoder_remove_stream(priv->codec, ES_ID); - if (e) return e; - priv->nb_streams--; - return GF_OK; -} - -static GF_Err LSR_ProcessData(GF_SceneDecoder*plug, const char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 AU_time, u32 mmlevel) -{ - GF_Err e = GF_OK; - LSRPriv *priv = (LSRPriv *)plug->privateStack; - - e = gf_laser_decode_au(priv->codec, ES_ID, inBuffer, inBufferLength); - - /*if scene not attached do it*/ - gf_scene_attach_to_compositor(priv->pScene); - return e; -} - -static u32 LSR_CanHandleStream(GF_BaseDecoder *ifce, u32 StreamType, GF_ESD *esd, u8 PL) -{ - if (StreamType!=GF_STREAM_SCENE) return GF_CODEC_NOT_SUPPORTED; - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - if (esd->decoderConfig->objectTypeIndication == GPAC_OTI_SCENE_LASER) return GF_CODEC_SUPPORTED; - return GF_CODEC_NOT_SUPPORTED; -} - - -void DeleteLSRDec(GF_BaseDecoder *plug) -{ - LSRPriv *priv; - if (!plug) - return; - priv = (LSRPriv *)plug->privateStack; - if (priv) { - /*in case something went wrong*/ - if (priv->codec) - gf_laser_decoder_del(priv->codec); - priv->codec = NULL; - gf_free(priv); - plug->privateStack = NULL; - } - gf_free(plug); -} - -GF_BaseDecoder *NewLSRDec() -{ - LSRPriv *priv; - GF_SceneDecoder *tmp; - - GF_SAFEALLOC(tmp, GF_SceneDecoder); - if (!tmp) return NULL; - GF_SAFEALLOC(priv, LSRPriv); - if (!priv) { - gf_free(tmp); - return NULL; - } - priv->codec = NULL; - tmp->privateStack = priv; - tmp->AttachStream = LSR_AttachStream; - tmp->DetachStream = LSR_DetachStream; - tmp->GetCapabilities = LSR_GetCapabilities; - tmp->SetCapabilities = LSR_SetCapabilities; - tmp->ProcessData = LSR_ProcessData; - tmp->AttachScene = LSR_AttachScene; - tmp->CanHandleStream = LSR_CanHandleStream; - tmp->ReleaseScene = LSR_ReleaseScene; - GF_REGISTER_MODULE_INTERFACE(tmp, GF_SCENE_DECODER_INTERFACE, "GPAC LASeR Decoder", "gpac distribution") - return (GF_BaseDecoder *) tmp; -} - -#endif - - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifndef GPAC_DISABLE_LASER - GF_SCENE_DECODER_INTERFACE, -#endif - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - switch (InterfaceType) { -#ifndef GPAC_DISABLE_LASER - case GF_SCENE_DECODER_INTERFACE: - return (GF_BaseInterface *)NewLSRDec(); -#endif - default: - return NULL; - } -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#ifndef GPAC_DISABLE_LASER - case GF_SCENE_DECODER_INTERFACE: - DeleteLSRDec((GF_BaseDecoder *)ifce); - break; -#endif - } -} - - -GPAC_MODULE_STATIC_DECLARATION( laser ) diff --git a/modules/mediacodec_dec/Makefile b/modules/mediacodec_dec/Makefile deleted file mode 100755 index 4d1c523..0000000 --- a/modules/mediacodec_dec/Makefile +++ /dev/null @@ -1,58 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/mediacodec_dec - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -DGPAC_HAVE_CONFIG_H -I../../ - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -ifeq ($(ENABLE_JOYSTICK), yes) -CFLAGS+=-DENABLE_JOYSTICK -endif - -ifeq ($(ENABLE_JOYSTICK_NO_CURSOR), yes) -CFLAGS+=-DENABLE_JOYSTICK_NO_CURSOR -endif - -#common obj -OBJS= mediacodec_dec.o mediacodec_dec_jni.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_mediacodec_dec$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols raw_out.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_mediacodec_dec-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac_static $(LDFLAGS) -endif - - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/mediacodec_dec/mediacodec_dec.c b/modules/mediacodec_dec/mediacodec_dec.c deleted file mode 100644 index 4842c5c..0000000 --- a/modules/mediacodec_dec/mediacodec_dec.c +++ /dev/null @@ -1,1104 +0,0 @@ -#include <gpac/internal/media_dev.h> -#include <gpac/constants.h> - -#include "../../src/compositor/gl_inc.h" - -#include <jni.h> - -#include "media/NdkMediaCodec.h" -#include "media/NdkMediaExtractor.h" -#include "media/NdkMediaFormat.h" -#include "mediacodec_dec.h" - -typedef struct -{ - AMediaCodec *codec; - AMediaFormat *format; - ANativeWindow * window; - char * frame; - Bool use_gl_textures; - u32 dequeue_timeout; - - u32 width, height, stride, out_size; - u32 pixel_ar, pix_fmt; - u32 crop_left, crop_right, crop_top, crop_bottom; - int crop_unitX, crop_unitY; - - const char *mime; - - u8 chroma_format, luma_bit_depth, chroma_bit_depth; - GF_ESD *esd; - Float frame_rate; - Bool surface_rendering; - Bool frame_size_changed; - Bool inputEOS, outputEOS; - Bool raw_frame_dispatch; - //NAL-based specific - GF_List *SPSs, *PPSs; - s32 active_sps, active_pps; - char *sps, *pps; - u32 sps_size, pps_size; - AVCState avc; - u32 decoded_frames_pending; - Bool reconfig_needed; - u32 nalu_size_length; - - //hevc - char *vps; - u32 luma_bpp, chroma_bpp, dec_frames; - u8 chroma_format_idc; - u16 ES_ID; - u32 vps_size; - AMediaCodecBufferInfo info; - ssize_t outIndex; - u32 gl_tex_id; - -} MCDec; -typedef struct { - char * frame; - MCDec * ctx; - ssize_t outIndex; - Bool flushed; - -} MC_Frame; -u8 sdkInt() -{ - char sdk_str[3] = "0"; - __system_property_get("ro.build.version.sdk", sdk_str, "0"); - return atoi(sdk_str); -} - - -//prepend the inBuffer with the start code i.e. 0x00 0x00 0x00 0x01 -void prependStartCode(char *inBuffer, char *outBuffer, u32 *size) -{ - *size += 4; - u32 i; - - for(i = 0; i < *size; i++) { - if(i < 3) - outBuffer[i] = 0x00; - - else if (i == 3) - outBuffer[i] = 0x01; - - else outBuffer[i] = inBuffer[i-4]; - } -} - - -void initMediaFormat(MCDec *ctx, AMediaFormat *format) -{ - AMediaFormat_setString(ctx->format, AMEDIAFORMAT_KEY_MIME, ctx->mime); - AMediaFormat_setInt32(ctx->format, AMEDIAFORMAT_KEY_WIDTH, ctx->width); - AMediaFormat_setInt32(ctx->format, AMEDIAFORMAT_KEY_HEIGHT, ctx->height); - AMediaFormat_setInt32(ctx->format, AMEDIAFORMAT_KEY_STRIDE, ctx->stride); - AMediaFormat_setInt32(ctx->format, AMEDIAFORMAT_KEY_MAX_INPUT_SIZE, ctx->width * ctx->height); -} - - -GF_Err MCDec_InitAvcDecoder(MCDec *ctx) -{ - s32 idx; - char *dsi_data = NULL; - u32 dsi_data_size = 0; - - if (gf_list_count(ctx->SPSs) && gf_list_count(ctx->PPSs)) { - u32 i; - GF_AVCConfig *cfg; - GF_AVCConfigSlot *sps = NULL; - GF_AVCConfigSlot *pps = NULL; - - for (i=0; i<gf_list_count(ctx->SPSs); i++) { - sps = gf_list_get(ctx->SPSs, i); - if (ctx->active_sps<0) ctx->active_sps = sps->id; - - if (sps->id==ctx->active_sps) break; - sps = NULL; - } - if (!sps) return GF_NON_COMPLIANT_BITSTREAM; - for (i=0; i<gf_list_count(ctx->PPSs); i++) { - pps = gf_list_get(ctx->PPSs, i); - if (ctx->active_pps<0) ctx->active_pps = pps->id; - - if (pps->id==ctx->active_pps) break; - pps = NULL; - } - if (!pps) return GF_NON_COMPLIANT_BITSTREAM; - ctx->reconfig_needed = GF_FALSE; - - idx = ctx->active_sps; - ctx->width = ctx->avc.sps[idx].width; - ctx->height = ctx->avc.sps[idx].height; - ctx->crop_left = ctx->avc.sps[idx].crop.left; - ctx->crop_right = ctx->avc.sps[idx].crop.right; - ctx->crop_top = ctx->avc.sps[idx].crop.top; - ctx->crop_bottom = ctx->avc.sps[idx].crop.bottom; - ctx->crop_left = ctx->avc.sps[idx].crop.left; - ctx->width += ctx->crop_left + ctx->crop_right; - ctx->height += ctx->crop_top + ctx->crop_bottom; - ctx->out_size = ctx->height * ctx->width * 3/2 ; - - if (ctx->avc.sps[idx].vui.par_num && ctx->avc.sps[idx].vui.par_den) { - ctx->pixel_ar = ctx->avc.sps[idx].vui.par_num; - ctx->pixel_ar <<= 16; - ctx->pixel_ar |= ctx->avc.sps[idx].vui.par_den; - } - ctx->chroma_format = ctx->avc.sps[idx].chroma_format; - ctx->luma_bit_depth = 8 + ctx->avc.sps[idx].luma_bit_depth_m8; - ctx->chroma_bit_depth = 8 + ctx->avc.sps[idx].chroma_bit_depth_m8; - switch (ctx->chroma_format) { - case 2: - if (ctx->luma_bit_depth>8) { - ctx->pix_fmt = GF_PIXEL_YUV422_10; - } else { - ctx->pix_fmt = GF_PIXEL_YUV422; - } - break; - case 3: - if (ctx->luma_bit_depth>8) { - ctx->pix_fmt = GF_PIXEL_YUV444_10; - } else { - ctx->pix_fmt = GF_PIXEL_YUV444; - } - break; - default: - if (ctx->luma_bit_depth>8) { - ctx->pix_fmt = GF_PIXEL_YV12_10; - } - break; - } - } - ctx->frame_size_changed = GF_TRUE; - return GF_OK; -} - - -GF_Err MCDec_InitMpeg4Decoder(MCDec *ctx) -{ - char *dsi_data = NULL; - u32 dsi_data_size = 0; - - if (!ctx->esd->decoderConfig->decoderSpecificInfo) { - ctx->esd->decoderConfig->decoderSpecificInfo = (GF_DefaultDescriptor *) gf_odf_desc_new(GF_ODF_DSI_TAG); - } - - if (ctx->esd->decoderConfig->decoderSpecificInfo->data) { - GF_M4VDecSpecInfo vcfg; - GF_BitStream *bs; - - gf_m4v_get_config(ctx->esd->decoderConfig->decoderSpecificInfo->data, ctx->esd->decoderConfig->decoderSpecificInfo->dataLength, &vcfg); - ctx->width = vcfg.width; - ctx->height = vcfg.height; - - if (ctx->esd->slConfig) { - ctx->esd->slConfig->predefined = 2; - } - - bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - gf_bs_write_u32(bs, 0); - gf_odf_desc_write_bs((GF_Descriptor *) ctx->esd, bs); - gf_bs_get_content(bs, &dsi_data, &dsi_data_size); - gf_bs_del(bs); - - ctx->mime = "video/mp4v-es"; - - char *esds = (char *)malloc(dsi_data_size); - memcpy(esds, dsi_data, dsi_data_size); - - esds[0] = 0x00; - esds[1] = 0x00; - esds[2] = 0x00; - esds[3] = 0x01; - - AMediaFormat_setBuffer(ctx->format, "csd-0", esds, dsi_data_size); - - gf_free(dsi_data); - return GF_OK; - } - return GF_NOT_SUPPORTED; -} - - -GF_Err MCDec_InitHevcDecoder(MCDec *ctx) -{ - u32 i, j; - GF_HEVCConfig *cfg = NULL; - - ctx->ES_ID = ctx->esd->ESID; - ctx->width = ctx->height = ctx->out_size = ctx->luma_bpp = ctx->chroma_bpp = ctx->chroma_format_idc = 0; - - if (ctx->esd->decoderConfig->decoderSpecificInfo && ctx->esd->decoderConfig->decoderSpecificInfo->data) { - HEVCState hevc; - memset(&hevc, 0, sizeof(HEVCState)); - - cfg = gf_odf_hevc_cfg_read(ctx->esd->decoderConfig->decoderSpecificInfo->data, ctx->esd->decoderConfig->decoderSpecificInfo->dataLength, GF_FALSE); - if (!cfg) return GF_NON_COMPLIANT_BITSTREAM; - ctx->nalu_size_length = cfg->nal_unit_size; - - for (i=0; i< gf_list_count(cfg->param_array); i++) { - GF_HEVCParamArray *ar = (GF_HEVCParamArray *)gf_list_get(cfg->param_array, i); - for (j=0; j< gf_list_count(ar->nalus); j++) { - GF_AVCConfigSlot *sl = (GF_AVCConfigSlot *)gf_list_get(ar->nalus, j); - s32 idx; - u16 hdr = sl->data[0] << 8 | sl->data[1]; - - if (ar->type==GF_HEVC_NALU_SEQ_PARAM) { - idx = gf_media_hevc_read_sps(sl->data, sl->size, &hevc); - ctx->width = MAX(hevc.sps[idx].width, ctx->width); - ctx->height = MAX(hevc.sps[idx].height, ctx->height); - ctx->luma_bpp = MAX(hevc.sps[idx].bit_depth_luma, ctx->luma_bpp); - ctx->chroma_bpp = MAX(hevc.sps[idx].bit_depth_chroma, ctx->chroma_bpp); - ctx->chroma_format_idc = hevc.sps[idx].chroma_format_idc; - - ctx->sps = (char *) malloc(4 + sl->size); - ctx->sps_size = sl->size; - prependStartCode(sl->data, ctx->sps, &ctx->sps_size); - } - else if (ar->type==GF_HEVC_NALU_VID_PARAM) { - gf_media_hevc_read_vps(sl->data, sl->size, &hevc); - - ctx->vps = (char *) malloc(4 + sl->size); - ctx->vps_size = sl->size; - prependStartCode(sl->data, ctx->vps, &ctx->vps_size); - } - else if (ar->type==GF_HEVC_NALU_PIC_PARAM) { - gf_media_hevc_read_pps(sl->data, sl->size, &hevc); - - ctx->pps = (char *) malloc(4 + sl->size); - ctx->pps_size = sl->size; - prependStartCode(sl->data, ctx->pps, &ctx->pps_size); - } - } - } - gf_odf_hevc_cfg_del(cfg); - } else { - ctx->nalu_size_length = 0; - } - - ctx->stride = ((ctx->luma_bpp==8) && (ctx->chroma_bpp==8)) ? ctx->width : ctx->width * 2; - if ( ctx->chroma_format_idc == 1) { // 4:2:0 - ctx->out_size = ctx->stride * ctx->height * 3 / 2; - } - else if ( ctx->chroma_format_idc == 2) { // 4:2:2 - ctx->out_size = ctx->stride * ctx->height * 2 ; - } - else if ( ctx->chroma_format_idc == 3) { // 4:4:4 - ctx->out_size = ctx->stride * ctx->height * 3; - } - else { - return GF_NOT_SUPPORTED; - } - - ctx->mime = "video/hevc"; - - u32 csd0_size = ctx->sps_size + ctx-> pps_size + ctx->vps_size; - char *csd0 = (char *) malloc(csd0_size); - - u32 k; - - for(k = 0; k < csd0_size; k++) { - - if(k < ctx->vps_size) { - csd0[k] = ctx->vps[k]; - } - else if (k < ctx-> vps_size + ctx->sps_size ) { - csd0[k] = ctx->sps[k - ctx->vps_size]; - } - else csd0[k] = ctx->pps[k - ctx->vps_size - ctx->sps_size]; - } - - AMediaFormat_setBuffer(ctx->format, "csd-0", csd0, csd0_size); - return GF_OK; -} - - -static GF_Err MCDec_InitDecoder(MCDec *ctx) { - - GF_Err err; - ctx->format = AMediaFormat_new(); - - if(!ctx->format) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC,("AMediaFormat_new failed")); - return GF_CODEC_NOT_FOUND; - } - - ctx->pix_fmt = GF_PIXEL_NV12; - - switch (ctx->esd->decoderConfig->objectTypeIndication) { - - case GPAC_OTI_VIDEO_AVC : - err = MCDec_InitAvcDecoder(ctx); - break; - - case GPAC_OTI_VIDEO_MPEG4_PART2 : - err = MCDec_InitMpeg4Decoder(ctx); - break; - - case GPAC_OTI_VIDEO_HEVC: - err = MCDec_InitHevcDecoder(ctx); - break; - - default: - return GF_NOT_SUPPORTED; - } - - if(err != GF_OK){ - return err; - } - - ctx->frame_rate = 50.0f; - ctx->dequeue_timeout = 5000; - ctx->stride = ctx->width; - initMediaFormat(ctx, ctx->format); - - ctx->codec = AMediaCodec_createDecoderByType(ctx->mime); - - if(!ctx->codec) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC,("AMediaCodec_createDecoderByType failed")); - return GF_CODEC_NOT_FOUND; - } - - if(!ctx->window) - MCDec_CreateSurface(&ctx->window,&ctx->gl_tex_id, &ctx->surface_rendering); - - if( AMediaCodec_configure( - ctx->codec, // codec - ctx->format, // format - (ctx->surface_rendering) ? ctx->window : NULL, //surface - NULL, // crypto - 0 // flags - ) != AMEDIA_OK) - { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC,("AMediaCodec_configure failed")); - return GF_BAD_PARAM; - } - - if( AMediaCodec_start(ctx->codec) != AMEDIA_OK){ - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC,("AMediaCodec_start failed")); - return GF_BAD_PARAM; - } - - ctx->inputEOS = GF_FALSE; - ctx->outputEOS = GF_FALSE; - GF_LOG(GF_LOG_INFO, GF_LOG_CODEC,("Video size: %d x %d", ctx->width, ctx->height)); - return GF_OK; -} -static void MCDec_RegisterParameterSet(MCDec *ctx, char *data, u32 size, Bool is_sps) -{ - Bool add = GF_TRUE; - u32 i, count; - s32 ps_id; - GF_List *dest = is_sps ? ctx->SPSs : ctx->PPSs; - - if (is_sps) { - ps_id = gf_media_avc_read_sps(data, size, &ctx->avc, 0, NULL); - if (ps_id<0) return; - } - else { - ps_id = gf_media_avc_read_pps(data, size, &ctx->avc); - if (ps_id<0) return; - } - count = gf_list_count(dest); - for (i = 0; i<count; i++) { - GF_AVCConfigSlot *a_slc = gf_list_get(dest, i); - if (a_slc->id != ps_id) continue; - //not same size or different content but same ID, remove old xPS - if ((a_slc->size != size) || memcmp(a_slc->data, data, size)) { - gf_free(a_slc->data); - gf_free(a_slc); - gf_list_rem(dest, i); - break; - } - else { - add = GF_FALSE; - } - break; - } - if (add) { - GF_AVCConfigSlot *slc; - GF_SAFEALLOC(slc, GF_AVCConfigSlot); - slc->data = gf_malloc(size); - memcpy(slc->data, data, size); - slc->size = size; - slc->id = ps_id; - gf_list_add(dest, slc); - - //force re-activation of sps/pps - if (is_sps) ctx->active_sps = -1; - else ctx->active_pps = -1; - } -} - -static GF_Err MCDec_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ - MCDec *ctx = (MCDec *)ifcg->privateStack; - ctx->esd = esd; - GF_Err e; - - //check AVC config - if (esd->decoderConfig->objectTypeIndication == GPAC_OTI_VIDEO_AVC) { - ctx->SPSs = gf_list_new(); - ctx->PPSs = gf_list_new(); - ctx->mime = "video/avc"; - ctx->avc.sps_active_idx = -1; - ctx->active_sps = ctx->active_pps = -1; - if (!esd->decoderConfig->decoderSpecificInfo || !esd->decoderConfig->decoderSpecificInfo->data) { - ctx->width=ctx->height=128; - ctx->out_size = ctx->width*ctx->height*3/2; - ctx->pix_fmt = GF_PIXEL_NV12; - return GF_OK; - } else { - u32 i; - GF_AVCConfigSlot *slc; - GF_AVCConfig *cfg = gf_odf_avc_cfg_read(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength); - for (i = 0; i<gf_list_count(cfg->sequenceParameterSets); i++) { - slc = gf_list_get(cfg->sequenceParameterSets, i); - slc->id = -1; - MCDec_RegisterParameterSet(ctx, slc->data, slc->size, GF_TRUE); - } - - for (i = 0; i<gf_list_count(cfg->pictureParameterSets); i++) { - slc = gf_list_get(cfg->pictureParameterSets, i); - slc->id = -1; - MCDec_RegisterParameterSet(ctx, slc->data, slc->size, GF_FALSE); - } - - slc = gf_list_get(ctx->SPSs, 0); - if (slc) ctx->active_sps = slc->id; - - slc = gf_list_get(ctx->PPSs, 0); - if (slc) ctx->active_pps = slc->id; - - ctx->nalu_size_length = cfg->nal_unit_size; - - - if (gf_list_count(ctx->SPSs) && gf_list_count(ctx->PPSs)) { - e = MCDec_InitDecoder(ctx); - } - else { - e = GF_OK; - } - gf_odf_avc_cfg_del(cfg); - return e; - } - } - - if (esd->decoderConfig->objectTypeIndication == GPAC_OTI_VIDEO_MPEG4_PART2) { - if (!esd->decoderConfig->decoderSpecificInfo || !esd->decoderConfig->decoderSpecificInfo->data) { - ctx->width=ctx->height=128; - ctx->out_size = ctx->width*ctx->height*3/2; - ctx->pix_fmt = GF_PIXEL_NV12; - } else { - GF_M4VDecSpecInfo vcfg; - gf_m4v_get_config(ctx->esd->decoderConfig->decoderSpecificInfo->data, ctx->esd->decoderConfig->decoderSpecificInfo->dataLength, &vcfg); - ctx->width = vcfg.width; - ctx->height = vcfg.height; - ctx->out_size = ctx->width*ctx->height*3/2; - ctx->pix_fmt = GF_PIXEL_NV12; - - return MCDec_InitDecoder(ctx); - } - } - - if (esd->decoderConfig->objectTypeIndication == GPAC_OTI_VIDEO_HEVC) { - ctx->esd= esd; - if (!esd->decoderConfig->decoderSpecificInfo || !esd->decoderConfig->decoderSpecificInfo->data) { - ctx->width=ctx->height=128; - ctx->out_size = ctx->width*ctx->height*3/2; - ctx->pix_fmt = GF_PIXEL_NV12; - } else { - return MCDec_InitDecoder(ctx); - } - } - - return MCDec_InitDecoder(ctx); -} - - - -static GF_Err MCDec_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - MCDec *ctx = (MCDec *)ifcg->privateStack; - - if(AMediaCodec_stop(ctx->codec) != AMEDIA_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC,("AMediaCodec_stop failed")); - } - MCDec_DeleteSurface(); - - return GF_OK; -} - - -static GF_Err MCDec_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - MCDec *ctx = (MCDec *)ifcg->privateStack; - - switch (capability->CapCode) { - case GF_CODEC_RESILIENT: - capability->cap.valueInt = 1; - break; - case GF_CODEC_WIDTH: - capability->cap.valueInt = ctx->width; - break; - case GF_CODEC_HEIGHT: - capability->cap.valueInt = ctx->height; - break; - case GF_CODEC_STRIDE: - capability->cap.valueInt = ctx->stride; - break; - case GF_CODEC_FPS: - capability->cap.valueFloat = ctx->frame_rate; - break; - case GF_CODEC_PAR: - capability->cap.valueInt = ctx->pixel_ar; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = ctx->out_size; - break; - case GF_CODEC_PIXEL_FORMAT: - capability->cap.valueInt = ctx->pix_fmt; - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = 1; - break; - case GF_CODEC_BUFFER_MAX: - capability->cap.valueInt = 4; - break; - /*by default we use 4 bytes padding (otherwise it happens that XviD crashes on some videos...)*/ - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 0; - break; - /*reorder is up to mediacodec*/ - case GF_CODEC_REORDER: - capability->cap.valueInt = 1; - break; - case GF_CODEC_WANTS_THREAD: - capability->cap.valueInt = 0; - break; - case GF_CODEC_FRAME_OUTPUT: - capability->cap.valueInt = 1; - break; - case GF_CODEC_FORCE_ANNEXB: - capability->cap.valueInt = 1; - break; - case GF_CODEC_TRUSTED_CTS: - capability->cap.valueInt = 1; - break; - /*not known at our level...*/ - case GF_CODEC_CU_DURATION: - default: - capability->cap.valueInt = 0; - break; - } - return GF_OK; -} - -static GF_Err MCDec_ParseNALs(MCDec *ctx, char *inBuffer, u32 inBufferLength, char **out_buffer, u32 *out_size) -{ - u32 i, sc_size; - char *ptr = inBuffer; - u32 nal_size; - GF_Err e = GF_OK; - GF_BitStream *bs = NULL; - - if (out_buffer) { - *out_buffer = NULL; - *out_size = 0; - } - - if (!ctx->nalu_size_length) { - sc_size = 0; - nal_size = gf_media_nalu_next_start_code((u8 *)inBuffer, inBufferLength, &sc_size); - if (!sc_size) return GF_NON_COMPLIANT_BITSTREAM; - ptr += nal_size + sc_size; - assert(inBufferLength >= nal_size + sc_size); - inBufferLength -= nal_size + sc_size; - } - - while (inBufferLength) { - Bool add_nal = GF_TRUE; - u8 nal_type, nal_hdr; - GF_BitStream *nal_bs = NULL; - if (ctx->nalu_size_length) { - nal_size = 0; - for (i = 0; i<ctx->nalu_size_length; i++) { - nal_size = (nal_size << 8) + ((u8)ptr[i]); - } - ptr += ctx->nalu_size_length; - } - else { - nal_size = gf_media_nalu_next_start_code(ptr, inBufferLength, &sc_size); - } - nal_bs = gf_bs_new(ptr, nal_size, GF_BITSTREAM_READ); - nal_hdr = gf_bs_read_u8(nal_bs); - nal_type = nal_hdr & 0x1F; - switch (nal_type) { - case GF_AVC_NALU_SEQ_PARAM: - MCDec_RegisterParameterSet(ctx, ptr, nal_size, GF_TRUE); - add_nal = GF_FALSE; - break; - case GF_AVC_NALU_PIC_PARAM: - MCDec_RegisterParameterSet(ctx, ptr, nal_size, GF_FALSE); - add_nal = GF_FALSE; - break; - case GF_AVC_NALU_ACCESS_UNIT: - case GF_AVC_NALU_END_OF_SEQ: - case GF_AVC_NALU_END_OF_STREAM: - case GF_AVC_NALU_FILLER_DATA: - add_nal = GF_FALSE; - break; - default: - break; - } - gf_media_avc_parse_nalu(nal_bs, nal_hdr, &ctx->avc); - gf_bs_del(nal_bs); - if ((nal_type <= GF_AVC_NALU_IDR_SLICE) && ctx->avc.s_info.sps) { - if (ctx->avc.sps_active_idx != ctx->active_sps) { - ctx->reconfig_needed = 1; - ctx->active_sps = ctx->avc.sps_active_idx; - ctx->active_pps = ctx->avc.s_info.pps->id; - return GF_OK; - } - } - - //if sps and pps are ready, init decoder - if (!ctx->codec && gf_list_count(ctx->SPSs) && gf_list_count(ctx->PPSs)) { - e = MCDec_InitDecoder(ctx); - if (e) return e; - } - if (!out_buffer) add_nal = GF_FALSE; - else if (add_nal && !ctx->codec) add_nal = GF_FALSE; - if (add_nal) { - if (!bs) bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - gf_bs_write_u32(bs, nal_size); - gf_bs_write_data(bs, ptr, nal_size); - } - - ptr += nal_size; - if (ctx->nalu_size_length) { - if (inBufferLength < nal_size + ctx->nalu_size_length) break; - inBufferLength -= nal_size + ctx->nalu_size_length; - } - else { - - if (!sc_size || (inBufferLength < nal_size + sc_size)) break; - inBufferLength -= nal_size + sc_size; - ptr += sc_size; - } - } - - if (bs) { - gf_bs_get_content(bs, out_buffer, out_size); - gf_bs_del(bs); - } - return e; -} -static GF_Err MCDec_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - MCDec *ctx = (MCDec *)ifcg->privateStack; - - switch (capability.CapCode) { - case GF_CODEC_FRAME_OUTPUT: - ctx->raw_frame_dispatch = capability.cap.valueInt ? GF_TRUE : GF_FALSE; - if (ctx->raw_frame_dispatch && (capability.cap.valueInt==2)) - ctx->use_gl_textures = GF_TRUE; - - return GF_OK; - } - - /*return unsupported to avoid confusion by the player (like color space changing ...) */ - return GF_NOT_SUPPORTED; - -} - - -static GF_Err MCDec_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ - MCDec *ctx = (MCDec *)ifcg->privateStack; - ctx->nalu_size_length = 0; - Bool mcdec_buffer_available = GF_FALSE; - if (!ctx->reconfig_needed) - MCDec_ParseNALs(ctx, inBuffer, inBufferLength, NULL, NULL); - - if (ctx->reconfig_needed) { - if (ctx->raw_frame_dispatch && ctx->decoded_frames_pending) { - *outBufferLength = 1; - return GF_BUFFER_TOO_SMALL; - } - - if (ctx->codec) { - AMediaCodec_flush(ctx->codec); - AMediaCodec_stop(ctx->codec); - AMediaCodec_delete(ctx->codec); - ctx->codec = NULL; - } - MCDec_InitDecoder(ctx); - if (ctx->out_size != *outBufferLength) { - *outBufferLength = ctx->out_size; - return GF_BUFFER_TOO_SMALL; - } - } - - if(!ctx->inputEOS) { - ssize_t inIndex = AMediaCodec_dequeueInputBuffer(ctx->codec,ctx->dequeue_timeout); - - if (inIndex >= 0) { - - size_t inSize; - char *buffer = (char *)AMediaCodec_getInputBuffer(ctx->codec, inIndex, &inSize); - - if (inBufferLength > inSize) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC,("The returned buffer is too small")); - return GF_BUFFER_TOO_SMALL; - } - - switch (ctx->esd->decoderConfig->objectTypeIndication) { - - case GPAC_OTI_VIDEO_AVC: - memcpy(buffer, inBuffer, inBufferLength); - break; - - - case GPAC_OTI_VIDEO_HEVC: - - if(inBuffer[4] == 0x40) { //check for vps - u32 start = ctx->vps_size + ctx->sps_size + ctx->pps_size; - u32 i; - - for (i = start; i < inBufferLength ; ++i) { - buffer[i - start] = inBuffer[i]; - } - buffer[0] = 0x00; - buffer[1] = 0x00; - buffer[2] = 0x00; - buffer[3] = 0x01; - } - else { - memcpy(buffer, inBuffer, inBufferLength); - } - - break; - - default: - memcpy(buffer, inBuffer, inBufferLength); - break; - } - - if(!inBuffer || inBufferLength == 0){ - GF_LOG(GF_LOG_INFO, GF_LOG_CODEC,("AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM input")); - ctx->inputEOS = GF_TRUE; - } - if(AMediaCodec_queueInputBuffer(ctx->codec, - inIndex, - 0, - inBufferLength, - *CTS, - inBuffer ? 0 : AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM - ) != AMEDIA_OK) - { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC,("AMediaCodec_queueInputBuffer failed")); - return GF_BAD_PARAM; - } - mcdec_buffer_available = GF_TRUE; - } - } - - if(!ctx->outputEOS) { - - - ctx->outIndex = AMediaCodec_dequeueOutputBuffer(ctx->codec, &ctx->info, ctx->dequeue_timeout); - *outBufferLength=0; - - switch(ctx->outIndex) { - case AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED: - GF_LOG(GF_LOG_INFO, GF_LOG_CODEC,("AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED")); - ctx->format = AMediaCodec_getOutputFormat(ctx->codec); - break; - - case AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED: - GF_LOG(GF_LOG_INFO, GF_LOG_CODEC,("AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED")); - break; - - case AMEDIACODEC_INFO_TRY_AGAIN_LATER: - GF_LOG(GF_LOG_INFO, GF_LOG_CODEC,("AMEDIACODEC_INFO_TRY_AGAIN_LATER")); - break; - - default: - if (ctx->outIndex >= 0) { - *CTS = ctx->info.presentationTimeUs; - if(ctx->info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) { - GF_LOG(GF_LOG_INFO, GF_LOG_CODEC,("AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM output")); - ctx->outputEOS = true; - } - - if(!ctx->surface_rendering) { - size_t outSize; - uint8_t * buffer = AMediaCodec_getOutputBuffer(ctx->codec, ctx->outIndex, &outSize); - ctx->frame = buffer + ctx->info.offset; - - if(!ctx->frame) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC,("AMediaCodec_getOutputBuffer failed")); - *outBufferLength = 0; - } - } - - if(ctx->surface_rendering || ctx->frame) { - if(ctx->info.size < ctx->out_size) - *outBufferLength = ctx->info.size; - else *outBufferLength = ctx->out_size; - } - } - break; - - } - - } - - return (mcdec_buffer_available) ? GF_OK : GF_CODEC_BUFFER_UNAVAILABLE; -} - - -static u32 MCDec_CanHandleStream(GF_BaseDecoder *dec, u32 StreamType, GF_ESD *esd, u8 PL) -{ - if (StreamType != GF_STREAM_VISUAL) return GF_CODEC_NOT_SUPPORTED; - - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - - switch (esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_VIDEO_AVC: - return GF_CODEC_SUPPORTED; - case GPAC_OTI_VIDEO_HEVC: - return GF_CODEC_NOT_SUPPORTED; - case GPAC_OTI_VIDEO_MPEG4_PART2: - return GF_CODEC_SUPPORTED; - } - - return GF_CODEC_NOT_SUPPORTED; -} -void MCFrame_Release(GF_MediaDecoderFrame *frame) -{ - MC_Frame *f = (MC_Frame *)frame->user_data; - if(f->ctx->codec && !f->flushed) { - if ( AMediaCodec_releaseOutputBuffer(f->ctx->codec, f->outIndex, GF_FALSE) != AMEDIA_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC,("NOT Release Output Buffer Index: %d", f->outIndex)); - } - } - f->ctx->decoded_frames_pending--; - gf_free(f); - gf_free(frame); -} -GF_Err MCFrame_GetPlane(GF_MediaDecoderFrame *frame, u32 plane_idx, const char **outPlane, u32 *outStride) -{ - - GF_Err e; - MC_Frame *f = (MC_Frame *)frame->user_data; - int color; - if (! outPlane || !outStride) return GF_BAD_PARAM; - *outPlane = NULL; - *outStride = 0; - f->ctx->format = AMediaCodec_getOutputFormat(f->ctx->codec); - AMediaFormat_getInt32(f->ctx->format, AMEDIAFORMAT_KEY_STRIDE, outStride); - - switch(plane_idx) { - case 0 : - *outPlane = f->frame ; - break; - case 1 : - *outPlane = f->frame + *outStride * f->ctx->height ; - *outStride/=2; - break; - case 2: - *outPlane = f->frame + 5 * *outStride * f->ctx->height/4 ; - *outStride/=2; - break; - default : - return GF_BAD_PARAM; - - } - - return GF_OK; -} - -GF_Err MCFrame_GetGLTexture(GF_MediaDecoderFrame *frame, u32 plane_idx, u32 *gl_tex_format, u32 *gl_tex_id, GF_CodecMatrix * texcoordmatrix) -{ - MC_Frame *f = (MC_Frame *)frame->user_data; - int i = 0; - *gl_tex_format = GL_TEXTURE_EXTERNAL_OES; - *gl_tex_id = f->ctx->gl_tex_id; - - if(!f->flushed && f->ctx->codec) { - if (AMediaCodec_releaseOutputBuffer(f->ctx->codec, f->outIndex, GF_TRUE) != AMEDIA_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC,("NOT Release Output Buffer Index: %d to surface", f->outIndex)); - return GF_OK; - } - MCFrame_UpdateTexImage(); - MCFrame_GetTransformMatrix(texcoordmatrix); - f->flushed = GF_TRUE; - } - return GF_OK; -} - -GF_Err MCDec_GetOutputFrame(GF_MediaDecoder *dec, u16 ES_ID, GF_MediaDecoderFrame **frame, Bool *needs_resize) -{ - GF_MediaDecoderFrame *a_frame; - MC_Frame *mc_frame; - ssize_t outSize; - MCDec *ctx = (MCDec *)dec->privateStack; - - *needs_resize = GF_FALSE; - - if(ctx->outIndex < 0 || (!ctx->frame && !ctx->surface_rendering)) return GF_BAD_PARAM; - GF_SAFEALLOC(a_frame, GF_MediaDecoderFrame); - if (!a_frame) return GF_OUT_OF_MEM; - GF_SAFEALLOC(mc_frame, MC_Frame); - if (!mc_frame) { - gf_free(a_frame); - return GF_OUT_OF_MEM; - } - a_frame->user_data = mc_frame; - mc_frame->ctx = ctx; - mc_frame->frame = ctx->frame; - mc_frame->outIndex = ctx->outIndex; - mc_frame->flushed = GF_FALSE; - ctx->frame = NULL; - a_frame->Release = MCFrame_Release; - a_frame->GetPlane = MCFrame_GetPlane; - if (ctx->use_gl_textures) - a_frame->GetGLTexture = MCFrame_GetGLTexture; - - *frame = a_frame; - if (ctx->frame_size_changed) { - ctx->frame_size_changed = GF_FALSE; - *needs_resize = GF_TRUE; - } - ctx->decoded_frames_pending++; - return GF_OK; -} - -static const char *MCDec_GetCodecName(GF_BaseDecoder *dec) -{ - MCDec *ctx = (MCDec *) dec->privateStack; - - if(!strcmp(ctx->mime, "video/avc")) - return "MediaCodec hardware AVC|H264"; - - else if(!strcmp(ctx->mime, "video/hevc")) - return "MediaCodec hardware HEVC|H265"; - - else if(!strcmp(ctx->mime, "video/mp4v-es")) - return "MediaCodec hardware MPEG-4 Part2"; - - else return "MediaCodec not supported"; -} - - -GF_BaseDecoder *NewMCDec() -{ - GF_MediaDecoder *ifcd; - MCDec *dec; - - GF_SAFEALLOC(ifcd, GF_MediaDecoder); - if (!ifcd) return NULL; - GF_SAFEALLOC(dec, MCDec); - if (!dec) { - gf_free(ifcd); - return NULL; - } - GF_REGISTER_MODULE_INTERFACE(ifcd, GF_MEDIA_DECODER_INTERFACE, "Android MediaCodec Decoder", "gpac distribution") - - ifcd->privateStack = dec; - - /*setup our own interface*/ - ifcd->AttachStream = MCDec_AttachStream; - ifcd->DetachStream = MCDec_DetachStream; - ifcd->GetCapabilities = MCDec_GetCapabilities; - ifcd->SetCapabilities = MCDec_SetCapabilities; - ifcd->GetName = MCDec_GetCodecName; - ifcd->CanHandleStream = MCDec_CanHandleStream; - ifcd->ProcessData = MCDec_ProcessData; - ifcd->GetOutputFrame = MCDec_GetOutputFrame; - return (GF_BaseDecoder *) ifcd; -} -static void MCDec_DelParamList(GF_List *list) -{ - while (gf_list_count(list)) { - GF_AVCConfigSlot *slc = gf_list_get(list, 0); - gf_free(slc->data); - gf_free(slc); - gf_list_rem(list, 0); - } - - gf_list_del(list); - -} - -void DeleteMCDec(GF_BaseDecoder *ifcg) -{ - MCDec *ctx = (MCDec *)ifcg->privateStack; - - if(ctx->format && AMediaFormat_delete(ctx->format) != AMEDIA_OK){ - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC,("AMediaFormat_delete failed")); - } - - if(ctx->codec && AMediaCodec_delete(ctx->codec) != AMEDIA_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC,("AMediaCodec_delete failed")); - } - - if(ctx->window) { - ANativeWindow_release(ctx->window); - ctx->window = NULL; - } - - - gf_free(ctx); - gf_free(ifcg); - - MCDec_DelParamList(ctx->SPSs); - ctx->SPSs = NULL; - MCDec_DelParamList(ctx->PPSs); - ctx->PPSs = NULL; -} - - - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifndef GPAC_DISABLE_AV_PARSERS - GF_MEDIA_DECODER_INTERFACE, -#endif - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - -#ifndef GPAC_DISABLE_AV_PARSERS - if (InterfaceType == GF_MEDIA_DECODER_INTERFACE) return (GF_BaseInterface *)NewMCDec(); -#endif - return NULL; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#ifndef GPAC_DISABLE_AV_PARSERS - case GF_MEDIA_DECODER_INTERFACE: - DeleteMCDec((GF_BaseDecoder*)ifce); - break; -#endif - } -} - -GPAC_MODULE_STATIC_DECLARATION( mc ) diff --git a/modules/mediacodec_dec/mediacodec_dec_jni.c b/modules/mediacodec_dec/mediacodec_dec_jni.c deleted file mode 100644 index b1f44b3..0000000 --- a/modules/mediacodec_dec/mediacodec_dec_jni.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / codec pack module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include <gpac/tools.h> -#include <gpac/constants.h> -// for native window JNI -#include <android/native_window_jni.h> -#include <android/asset_manager.h> -#include <android/asset_manager_jni.h> -#include <jni.h> -#ifdef GPAC_USE_GLES2 -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> -#else -#include <GLES/gl.h> -#include <GLES/glext.h> -#endif -#include "mediacodec_dec.h" - -static jclass cSurfaceTexture = NULL; -static jclass cSurface = NULL; -static jobject oSurfaceTex = NULL; -static jobject oSurface = NULL; -static jmethodID mUpdateTexImage; -static jmethodID mSurfaceTexConstructor; -static jmethodID mSurfaceConstructor; -static jmethodID mSurfaceRelease; -static jmethodID mGetTransformMatrix; -static JavaVM* javaVM = 0; - -jint JNI_OnLoad(JavaVM* vm, void* reserved) -{ - javaVM = vm; - return JNI_VERSION_1_2; -} - -JNIEnv* GetEnv() -{ - JNIEnv* env = 0; - if (javaVM) (*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_2); - return env; -} - -JavaVM* GetJavaVM() -{ - return javaVM; -} - -GF_Err MCDec_CreateSurface (ANativeWindow ** window, u32 *gl_tex_id, Bool * surface_rendering) -{ - JNIEnv* env = NULL; - jobject otmp= NULL; - jclass ctmp = NULL; - jint res = 0; - - res = (*GetJavaVM())->GetEnv(GetJavaVM(), (void**)&env, JNI_VERSION_1_2); - if ( res == JNI_EDETACHED ) { - (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); - } - if (!env) return GF_BAD_PARAM; - - // cache classes - if (!cSurfaceTexture) { - ctmp = (*env)->FindClass(env, "android/graphics/SurfaceTexture"); - cSurfaceTexture = (*env)->NewGlobalRef(env, ctmp); - if(!cSurfaceTexture) GF_BAD_PARAM; - } - if (!cSurface) { - ctmp = (*env)->FindClass(env, "android/view/Surface"); - cSurface = (*env)->NewGlobalRef(env, ctmp); - if (!cSurface) return GF_BAD_PARAM; - } - - /* TOFIX : here we call glGenTextures without creating or getting opengl context*/ - //glGenTextures(1, gl_tex_id); The texture is created by the HW - - // cache methods - if(!mSurfaceTexConstructor) { - mSurfaceTexConstructor = (*env)->GetMethodID(env, cSurfaceTexture, "<init>", "(I)V"); - if(!mSurfaceTexConstructor) return GF_BAD_PARAM; - } - if(!mSurfaceConstructor) { - mSurfaceConstructor = (*env)->GetMethodID(env, cSurface, "<init>", "(Landroid/graphics/SurfaceTexture;)V"); - if(!mSurfaceConstructor) return GF_BAD_PARAM; - } - if(!mSurfaceRelease) { - mSurfaceRelease = (*env)->GetMethodID(env, cSurface, "release", "()V"); - if(!mSurfaceRelease) return GF_BAD_PARAM; - } - if(!mUpdateTexImage) { - mUpdateTexImage = (*env)->GetMethodID(env, cSurfaceTexture, "updateTexImage", "()V"); - if(!mUpdateTexImage) return GF_BAD_PARAM; - } - if(!mGetTransformMatrix) { - mGetTransformMatrix = (*env)->GetMethodID(env, cSurfaceTexture, "getTransformMatrix", "([F)V"); - if(!mGetTransformMatrix) return GF_BAD_PARAM; - } - - // create objects - otmp = (*env)->NewObject(env, cSurfaceTexture, mSurfaceTexConstructor, *gl_tex_id); - oSurfaceTex = (jobject) (*env)->NewGlobalRef(env,otmp); - if(!oSurfaceTex) return GF_BAD_PARAM; - - oSurface = (*env)->NewObject(env, cSurface, mSurfaceConstructor, oSurfaceTex); - if(!oSurface) return GF_BAD_PARAM; - *window = ANativeWindow_fromSurface(env, oSurface); - *surface_rendering = (*window) ? GF_TRUE : GF_FALSE; - (*env)->CallVoidMethod(env, oSurface, mSurfaceRelease); - - return GF_OK; -} - -GF_Err MCFrame_UpdateTexImage() -{ - JNIEnv* env = NULL; - jint res = 0; - res = (*GetJavaVM())->GetEnv(GetJavaVM(), (void**)&env, JNI_VERSION_1_2); - if ( res == JNI_EDETACHED ) { - (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); - } - if (!env) return GF_BAD_PARAM; - - if(oSurfaceTex) { - (*env)->CallVoidMethod(env, oSurfaceTex, mUpdateTexImage); - } - return GF_OK; -} -GF_Err MCFrame_GetTransformMatrix(GF_CodecMatrix * mx) -{ - JNIEnv* env = NULL; - jint res = 0; - int i =0; - jfloatArray texMx; - - res = (*GetJavaVM())->GetEnv(GetJavaVM(), (void**)&env, JNI_VERSION_1_2); - if ( res == JNI_EDETACHED ) { - (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); - } - if (!env) return GF_BAD_PARAM; - - if(oSurfaceTex) { - texMx = (*env)->NewFloatArray(env,16); - if (texMx == NULL) { - return GF_BAD_PARAM; /* out of memory error thrown */ - } - - (*env)->CallVoidMethod(env, oSurfaceTex, mGetTransformMatrix,texMx); - - jsize len = (*env)->GetArrayLength(env, texMx); - - jfloat *body = (*env)->GetFloatArrayElements(env, texMx, 0); - - for (i=0; i<len; i++) { - mx->m[i] = FLT2FIX(body[i]); - } - - (*env)->ReleaseFloatArrayElements(env, texMx, body, 0); - - } - return GF_OK; -} - -GF_Err MCDec_DeleteSurface() -{ - JNIEnv* env = NULL; - jint res = 0; - - res = (*GetJavaVM())->GetEnv(GetJavaVM(), (void**)&env, JNI_VERSION_1_2); - if ( res == JNI_EDETACHED ) { - (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); - } - if (!env) return GF_BAD_PARAM; - (*env)->DeleteGlobalRef(env,cSurface); - cSurface = NULL; - (*env)->DeleteGlobalRef(env,cSurfaceTexture); - cSurfaceTexture = NULL; - (*env)->DeleteGlobalRef(env,oSurfaceTex); - oSurfaceTex = NULL; - - return GF_OK; -} - diff --git a/modules/mp3_in/Makefile b/modules/mp3_in/Makefile deleted file mode 100644 index 6792f80..0000000 --- a/modules/mp3_in/Makefile +++ /dev/null @@ -1,65 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/mp3_in - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS= mp3_in.o - - - -#mad config -ifeq ($(CONFIG_MAD), no) -else -OBJS+=mad_dec.o -CFLAGS+=-DGPAC_HAS_MAD -#local mad lib -ifeq ($(CONFIG_MAD), local) -EXTRALIBS+=-L../../extra_lib/lib/gcc -CFLAGS+= -I"$(LOCAL_INC_PATH)/mad" -endif -EXTRALIBS+= -lmad -endif - -SRCS := $(OBJS:.o=.c) - -LIB=gm_mp3_in$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols mp3_in.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) -L../../bin/gcc -lgpac $(EXTRALIBS) $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_mp3_in-static$(DYN_LIB_SUFFIX) $(OBJS) -L../../bin/gcc -lgpac_static $(EXTRALIBS) $(LDFLAGS) -endif - - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/mp3_in/mad_dec.c b/modules/mp3_in/mad_dec.c deleted file mode 100644 index 3901409..0000000 --- a/modules/mp3_in/mad_dec.c +++ /dev/null @@ -1,377 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2012 - * All rights reserved - * - * This file is part of GPAC / codec pack module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include <gpac/setup.h> - -#ifdef GPAC_HAS_MAD - -#include <gpac/modules/codec.h> -#include <gpac/constants.h> - -#if defined(_WIN32_WCE) || defined(_WIN64) || defined(__SYMBIAN32__) -#ifndef FPM_DEFAULT -#define FPM_DEFAULT -#endif -#endif - -#include <mad.h> - -#if !defined(__GNUC__) -# if defined(_WIN32_WCE) || defined (WIN32) -# pragma comment(lib, "libmad") -# endif -#endif - - -typedef struct -{ - Bool configured; - - u32 sample_rate, out_size, num_samples; - u8 num_channels; - /*no support for scalability in FAAD yet*/ - u16 ES_ID; - u32 cb_size, cb_trig; - - unsigned char *buffer; - u32 len; - Bool first; - - - struct mad_frame frame; - struct mad_stream stream; - struct mad_synth synth; - -} MADDec; - -#define MADCTX() MADDec *ctx = NULL; if (ifcg) { ctx = (MADDec *) ifcg->privateStack; } - -static GF_Err MAD_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ - MADCTX(); - assert( ctx ); - if (ctx->ES_ID && ctx->ES_ID!=esd->ESID) return GF_NOT_SUPPORTED; - - if (ctx->configured) { - mad_stream_finish(&ctx->stream); - mad_frame_finish(&ctx->frame); - mad_synth_finish(&ctx->synth); - } - mad_stream_init(&ctx->stream); - mad_frame_init(&ctx->frame); - mad_synth_init(&ctx->synth); - ctx->configured = GF_TRUE; - - ctx->buffer = (unsigned char*)gf_malloc(sizeof(char) * 2*MAD_BUFFER_MDLEN); - - /*we need a frame to init, so use default values*/ - ctx->num_samples = 1152; - ctx->num_channels = 0; - ctx->sample_rate = 0; - ctx->out_size = 2 * ctx->num_samples * ctx->num_channels; - ctx->ES_ID = esd->ESID; - ctx->first = GF_TRUE; - return GF_OK; -} - -static GF_Err MAD_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - MADCTX(); - assert( ctx ); - if (ES_ID != ctx->ES_ID) return GF_BAD_PARAM; - assert( ifcg ); - assert( ctx ); - ctx->ES_ID = 0; - if (ctx->buffer) gf_free(ctx->buffer); - ctx->buffer = NULL; - ctx->sample_rate = ctx->out_size = ctx->num_samples = 0; - ctx->num_channels = 0; - if (ctx->configured) { - mad_stream_finish(&ctx->stream); - mad_frame_finish(&ctx->frame); - mad_synth_finish(&ctx->synth); - } - ctx->configured = GF_FALSE; - return GF_OK; -} -static GF_Err MAD_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - MADCTX(); - assert( ctx ); - switch (capability->CapCode) { - /*not tested yet*/ - case GF_CODEC_RESILIENT: - capability->cap.valueInt = 1; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = ctx->out_size; - break; - case GF_CODEC_SAMPLERATE: - capability->cap.valueInt = ctx->sample_rate; - break; - case GF_CODEC_NB_CHAN: - capability->cap.valueInt = ctx->num_channels; - break; - case GF_CODEC_BITS_PER_SAMPLE: - capability->cap.valueInt = 16; - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = ctx->cb_trig; - break; - case GF_CODEC_BUFFER_MAX: - capability->cap.valueInt = ctx->cb_size; - break; - /*FIXME: get exact sampling window*/ - case GF_CODEC_CU_DURATION: - capability->cap.valueInt = ctx->num_samples; - break; - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 0; - break; - case GF_CODEC_CHANNEL_CONFIG: - if (ctx->num_channels==1) { - capability->cap.valueInt = GF_AUDIO_CH_FRONT_CENTER; - } else { - capability->cap.valueInt = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT; - } - break; - default: - capability->cap.valueInt = 0; - break; - } - return GF_OK; -} - -static GF_Err MAD_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - MADCTX(); - assert( ctx ); - switch (capability.CapCode) { - /*reset storage buffer*/ - case GF_CODEC_WAIT_RAP: - ctx->first = GF_TRUE; - ctx->len = 0; - if (ctx->configured) { - mad_stream_finish(&ctx->stream); - mad_frame_finish(&ctx->frame); - mad_synth_finish(&ctx->synth); - - mad_stream_finish(&ctx->stream); - mad_frame_finish(&ctx->frame); - mad_synth_finish(&ctx->synth); - } - return GF_OK; - default: - /*return unsupported to avoid confusion by the player (like SR changing ...) */ - return GF_NOT_SUPPORTED; - } -} - -/*from miniMad.c*/ -#define MAD_SCALE(ret, s_chan) \ - chan = s_chan; \ - chan += (1L << (MAD_F_FRACBITS - 16)); \ - if (chan >= MAD_F_ONE) \ - chan = MAD_F_ONE - 1; \ - else if (chan < -MAD_F_ONE) \ - chan = -MAD_F_ONE; \ - ret = chan >> (MAD_F_FRACBITS + 1 - 16); \ - -static GF_Err MAD_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ - mad_fixed_t *left_ch, *right_ch, chan; - char *ptr; - u32 num, outSize; - MADCTX(); - assert( ctx ); - - /*check not using scalabilty*/ - assert(ctx->ES_ID == ES_ID); - - if (ctx->ES_ID != ES_ID) - return GF_BAD_PARAM; - - /*if late or seeking don't decode*/ - switch (mmlevel) { - case GF_CODEC_LEVEL_SEEK: - *outBufferLength = 0; - return GF_OK; - default: - break; - } - - if (ctx->out_size > *outBufferLength) { - *outBufferLength = ctx->out_size; - return GF_BUFFER_TOO_SMALL; - } - - if (ctx->first) { - ctx->first = GF_FALSE; - memcpy(ctx->buffer, inBuffer, inBufferLength); - ctx->len = inBufferLength; - *outBufferLength = 0; - return GF_OK; - } - memcpy(ctx->buffer + ctx->len, inBuffer, inBufferLength); - ctx->len += inBufferLength; - mad_stream_buffer(&ctx->stream, ctx->buffer, ctx->len); - - if (mad_frame_decode(&ctx->frame, &ctx->stream) == -1) { - memcpy(ctx->buffer, inBuffer, inBufferLength); - ctx->len = inBufferLength; - *outBufferLength = 0; - return GF_NON_COMPLIANT_BITSTREAM; - } - - - /*first cfg, reconfig composition buffer*/ - if (!ctx->sample_rate) { - mad_synth_frame(&ctx->synth, &ctx->frame); - ctx->len -= inBufferLength; - ctx->sample_rate = ctx->synth.pcm.samplerate; - ctx->num_channels = (u8) ctx->synth.pcm.channels; - ctx->num_samples = ctx->synth.pcm.length; - ctx->out_size = 2 * ctx->num_samples * ctx->num_channels; - *outBufferLength = ctx->out_size; - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[MAD] decoder initialized - MP3 sample rate %d - %d channel(s)", ctx->sample_rate, ctx->num_channels)); - return GF_BUFFER_TOO_SMALL; - } - - if (ctx->stream.next_frame) { - ctx->len = (u32) (&ctx->buffer[ctx->len] - ctx->stream.next_frame); - memmove(ctx->buffer, ctx->stream.next_frame, ctx->len); - } - - - mad_synth_frame(&ctx->synth, &ctx->frame); - num = ctx->synth.pcm.length; - ptr = (char *) outBuffer; - left_ch = ctx->synth.pcm.samples[0]; - right_ch = ctx->synth.pcm.samples[1]; - outSize = 0; - - while (num--) { - s32 rs; - MAD_SCALE(rs, (*left_ch++) ); - - *ptr = (rs >> 0) & 0xff; - ptr++; - *ptr = (rs >> 8) & 0xff; - ptr++; - outSize += 2; - - if (ctx->num_channels == 2) { - MAD_SCALE(rs, (*right_ch++) ); - *ptr = (rs >> 0) & 0xff; - ptr++; - *ptr = (rs >> 8) & 0xff; - ptr++; - outSize += 2; - } - } - *outBufferLength = outSize; - return GF_OK; -} - -static const char *MAD_GetCodecName(GF_BaseDecoder *dec) -{ - return "MAD " \ - MAD_VERSION; -} - -static u32 MAD_CanHandleStream(GF_BaseDecoder *dec, u32 StreamType, GF_ESD *esd, u8 PL) -{ - /*audio decs*/ - if (StreamType != GF_STREAM_AUDIO) return GF_CODEC_NOT_SUPPORTED; - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - - switch (esd->decoderConfig->objectTypeIndication) { - /*MPEG1 audio*/ - case GPAC_OTI_AUDIO_MPEG2_PART3: - /*MPEG2 audio*/ - case GPAC_OTI_AUDIO_MPEG1: - return GF_CODEC_SUPPORTED; - } - return GF_CODEC_NOT_SUPPORTED; -} - -GF_BaseDecoder *NewMADDec() -{ - GF_MediaDecoder *ifce; - MADDec *dec; - - GF_SAFEALLOC(ifce, GF_MediaDecoder); - if (!ifce) return NULL; - GF_SAFEALLOC(dec, MADDec); - if (!dec) { - gf_free(ifce); - return NULL; - } - GF_REGISTER_MODULE_INTERFACE(ifce, GF_MEDIA_DECODER_INTERFACE, "MAD Decoder", "gpac distribution") - ifce->privateStack = dec; - - dec->cb_size = 12; - dec->cb_trig = 4; - - /*setup our own interface*/ - ifce->AttachStream = MAD_AttachStream; - ifce->DetachStream = MAD_DetachStream; - ifce->GetCapabilities = MAD_GetCapabilities; - ifce->SetCapabilities = MAD_SetCapabilities; - ifce->GetName = MAD_GetCodecName; - ifce->ProcessData = MAD_ProcessData; - ifce->CanHandleStream = MAD_CanHandleStream; - return (GF_BaseDecoder *)ifce; -} - -void DeleteMADDec(GF_MediaDecoder *ifcg) -{ - MADDec *ctx; - if (!ifcg) - return; - ctx = (MADDec *) ifcg->privateStack; - ifcg->privateStack = NULL; - if (ctx) { - if (ctx->configured) { - mad_stream_finish(&ctx->stream); - mad_frame_finish(&ctx->frame); - mad_synth_finish(&ctx->synth); - } - ctx->configured = GF_FALSE; - ctx->sample_rate = ctx->out_size = ctx->num_samples = 0; - ctx->num_channels = 0; - gf_free(ctx); - } - gf_free(ifcg); -} - -#endif - diff --git a/modules/mp3_in/mp3_in.c b/modules/mp3_in/mp3_in.c deleted file mode 100644 index ab9085e..0000000 --- a/modules/mp3_in/mp3_in.c +++ /dev/null @@ -1,781 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2012 - * All rights reserved - * - * This file is part of GPAC / MP3 reader module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include <gpac/modules/service.h> -#include <gpac/avparse.h> -#include <gpac/constants.h> -#include <gpac/modules/codec.h> - -#ifndef GPAC_DISABLE_AV_PARSERS - -typedef struct -{ - GF_ClientService *service; - - u32 needs_connection; - Bool is_remote; - - FILE *stream; - u32 duration; - - u32 pad_bytes; - Bool done; - LPNETCHANNEL ch; - - char *data; - u32 data_size; - - GF_SLHeader sl_hdr; - - Bool is_inline; - - u32 sample_rate, oti; - Double start_range, end_range; - u32 current_time, nb_samp; - /*file downloader*/ - GF_DownloadSession * dnload; - - Bool is_live; - char * liveDataCopy; - u32 liveDataCopySize; - char prev_data[1000]; - u32 prev_size; - - - char *icy_name; - char *icy_genre; - char *icy_track_name; -} MP3Reader; - - -static const char * MP3_MIME_TYPES[] = { "audio/mpeg", "audio/x-mpeg", "audio/mp3", NULL}; - -static const char * MP3_EXTENSIONS = "mp2 mp3 mpga mpega"; - -static const char * MP3_DESC = "MP3 Music"; - - -static u32 MP3_RegisterMimeTypes(const GF_InputService *plug) -{ - u32 i; - for (i =0 ; MP3_MIME_TYPES[i] ; i++) - gf_service_register_mime(plug, MP3_MIME_TYPES[i], MP3_EXTENSIONS, MP3_DESC); - return i; -} - -static Bool MP3_CanHandleURL(GF_InputService *plug, const char *url) -{ - char *sExt; - if (!plug || !url) - return GF_FALSE; - sExt = strrchr(url, '.'); - if (!strnicmp(url, "rtsp://", 7)) return GF_FALSE; - { - u32 i; - for (i =0 ; MP3_MIME_TYPES[i] ; i++) - if (gf_service_check_mime_register(plug, MP3_MIME_TYPES[i], MP3_EXTENSIONS, MP3_DESC, sExt)) - return GF_TRUE; - } - return GF_FALSE; -} - -static Bool mp3_is_local(const char *url) -{ - if (!strnicmp(url, "file://", 7)) return GF_TRUE; - if (strstr(url, "://")) return GF_FALSE; - return GF_TRUE; -} - - -static GF_ESD *MP3_GetESD(MP3Reader *read) -{ - GF_ESD *esd = gf_odf_desc_esd_new(0); - esd->slConfig->timestampResolution = read->sample_rate; - esd->decoderConfig->streamType = GF_STREAM_AUDIO; - esd->decoderConfig->objectTypeIndication = read->oti; - esd->ESID = 1; - return esd; -} - -static void mp3_setup_object(MP3Reader *read) -{ - if (read->is_inline) { - GF_ESD *esd; - GF_ObjectDescriptor *od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - od->objectDescriptorID = 1; - esd = MP3_GetESD(read); - gf_list_add(od->ESDescriptors, esd); - gf_service_declare_media(read->service, (GF_Descriptor*)od, GF_FALSE); - } -} - -/** - * Returns TRUE if file is ready to be read, FALSE otherwise - * @param read Reader - * @param minSizeToRead How much bytes do we need to start reading at minimum - */ -static Bool MP3_ConfigureFromFile(MP3Reader *read, u32 *minSizeToRead) -{ - unsigned char id3v2[10]; - u32 hdr, size; - u64 pos; - if (!read->stream) return GF_FALSE; - /* ID3VVFFFFSIZE = 13bytes - * ID3 string - * VV = Version - * F = Flags - * SIZE = 32bits size with first Most Significant bit set to 0 -> 28 bits - * Size starts AFTER this header, meaning we have to add 10 bytes - */ - pos = fread(id3v2, sizeof(unsigned char), 10, read->stream); - *minSizeToRead = 0; - if (pos == 10) { - /* Did we read an ID3v2 ? */ - if (id3v2[0] == 'I' && id3v2[1] == 'D' && id3v2[2] == '3') { - int sz = 10 + ((id3v2[9] & 0x7f) + ((id3v2[8] & 0x7f) << 7) + ((id3v2[7] & 0x7f) << 14) + ((id3v2[6] & 0x7f) << 21)); - //printf("Size of id3v2 header = %d\n", sz); - *minSizeToRead = sz; - } - } - gf_fseek(read->stream, 0, SEEK_SET); - hdr = gf_mp3_get_next_header(read->stream); - if (!hdr) return GF_FALSE; - read->sample_rate = gf_mp3_sampling_rate(hdr); - read->oti = gf_mp3_object_type_indication(hdr); - gf_fseek(read->stream, 0, SEEK_SET); - if (!read->oti) return GF_FALSE; - - /*we don't have the full file...*/ - if (read->is_remote) return GF_TRUE; - -// return 1; - - gf_fseek(read->stream, 0, SEEK_SET); - read->duration = 0; - while (1) { - hdr = gf_mp3_get_next_header(read->stream); - if (!hdr) break; - read->duration += gf_mp3_window_size(hdr); - size = gf_mp3_frame_size(hdr); - pos = gf_ftell(read->stream); - gf_fseek(read->stream, pos + size - 4, SEEK_SET); - } - gf_fseek(read->stream, 0, SEEK_SET); - return GF_TRUE; -} - -static void MP3_RegulateDataRate(MP3Reader *read) -{ - GF_NetworkCommand com; - - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.command_type = GF_NET_CHAN_BUFFER_QUERY; - com.base.on_channel = read->ch; - while (read->ch) { - gf_service_command(read->service, &com, GF_OK); - if (com.buffer.occupancy < com.buffer.max) break; - gf_sleep(2); - } -} - -static void MP3_OnLiveData(MP3Reader *read, char *data, u32 data_size) -{ - u32 hdr, size, pos; - - if (read->needs_connection) { - hdr = gf_mp3_get_next_header_mem(data, data_size, &pos); - if (!hdr) return; - - read->sample_rate = gf_mp3_sampling_rate(hdr); - read->oti = gf_mp3_object_type_indication(hdr); - read->is_live = GF_TRUE; - memset(&read->sl_hdr, 0, sizeof(GF_SLHeader)); - - read->needs_connection = 0; - gf_service_connect_ack(read->service, NULL, GF_OK); - mp3_setup_object(read); - } - if (!data_size) return; - - read->data = (char*)gf_realloc(read->data, sizeof(char)*(read->data_size+data_size) ); - memcpy(read->data + read->data_size, data, sizeof(char)*data_size); - read->data_size += data_size; - if (!read->ch) return; - - - data = read->data; - data_size = read->data_size; - - while (1) { - hdr = gf_mp3_get_next_header_mem(data, data_size, &pos); - - if (hdr) size = gf_mp3_frame_size(hdr); - - /*not enough data, copy over*/ - if (!hdr || (pos+size>data_size)) { - char *d = (char*)gf_malloc(sizeof(char) * data_size); - memcpy(d, data, sizeof(char) * data_size); - gf_free(read->data); - read->data = d; - read->data_size = data_size; - - MP3_RegulateDataRate(read); - return; - } - - read->sl_hdr.accessUnitStartFlag = 1; - read->sl_hdr.accessUnitEndFlag = 1; - read->sl_hdr.AU_sequenceNumber++; - read->sl_hdr.compositionTimeStampFlag = 1; - read->sl_hdr.compositionTimeStamp += gf_mp3_window_size(hdr); - gf_service_send_packet(read->service, read->ch, data + pos, size, &read->sl_hdr, GF_OK); - data += pos + size; - assert(data_size>=pos+size); - data_size -= pos+size; - } -} - -void MP3_NetIO(void *cbk, GF_NETIO_Parameter *param) -{ - GF_Err e; - const char *szCache; - u32 total_size, bytes_done; - MP3Reader *read = (MP3Reader *) cbk; - - e = param->error; - /*done*/ - if (param->msg_type==GF_NETIO_DATA_TRANSFERED) { - if (read->stream) { - read->is_remote = GF_FALSE; - e = GF_EOS; - } else { - return; - } - } - else if (param->msg_type==GF_NETIO_PARSE_HEADER) { - if (!strcmp(param->name, "icy-name")) { - if (read->icy_name) gf_free(read->icy_name); - read->icy_name = gf_strdup(param->value); - } - if (!strcmp(param->name, "icy-genre")) { - if (read->icy_genre) gf_free(read->icy_genre); - read->icy_genre = gf_strdup(param->value); - } - if (!strcmp(param->name, "icy-meta")) { - GF_NetworkCommand com; - char *meta; - meta = param->value; - while (meta && meta[0]) { - char *sep = strchr(meta, ';'); - if (sep) sep[0] = 0; - - if (!strnicmp(meta, "StreamTitle=", 12)) { - if (read->icy_track_name) gf_free(read->icy_track_name); - read->icy_track_name = NULL; - read->icy_track_name = gf_strdup(meta+12); - if (!strcmp(read->icy_track_name, "''")) { - /* On some servers, '' means not track name */ - gf_free(read->icy_track_name); - read->icy_track_name = NULL; - } - } - if (!sep) break; - sep[0] = ';'; - meta = sep+1; - } - - com.base.command_type = GF_NET_SERVICE_INFO; - gf_service_command(read->service, &com, GF_OK); - } - return; - } else { - /*handle service message*/ - gf_service_download_update_stats(read->dnload); - if (param->msg_type!=GF_NETIO_DATA_EXCHANGE) return; - } - - if (e >= GF_OK) { - if (read->needs_connection) { - gf_dm_sess_get_stats(read->dnload, NULL, NULL, &total_size, NULL, NULL, NULL); - if (!total_size) read->is_live = GF_TRUE; - } - /*looks like a live stream*/ - if (read->is_live) { - if (read->liveDataCopySize < param->size) { - read->liveDataCopy = (char*)gf_realloc(read->liveDataCopy, param->size); - } - memcpy(read->liveDataCopy, param->data, param->size); - if (!e) MP3_OnLiveData(read, read->liveDataCopy, param->size); - return; - } - - if (read->stream) return; - - /*open service*/ - szCache = gf_dm_sess_get_cache_name(read->dnload); - if (!szCache) e = GF_IO_ERR; - else { - read->stream = gf_fopen((char *) szCache, "rb"); - if (!read->stream) e = GF_SERVICE_ERROR; - else { - u32 minSizeToRead = 0; - /*if full file at once (in cache) parse duration*/ - if (e==GF_EOS) read->is_remote = GF_FALSE; - e = GF_OK; - /*not enough data*/ - if (!MP3_ConfigureFromFile(read, &minSizeToRead)) { - gf_dm_sess_get_stats(read->dnload, NULL, NULL, NULL, &bytes_done, NULL, NULL); - /*bad data - there's likely some ID3 around...*/ - if (bytes_done>(100*1024 + minSizeToRead)) { - e = GF_CORRUPTED_DATA; - } else { - gf_fclose(read->stream); - read->stream = NULL; - return; - } - } - } - } - } - - /*OK confirm*/ - if (read->needs_connection) { - read->needs_connection = 0; - gf_service_connect_ack(read->service, NULL, e); - if (!e) mp3_setup_object(read); - } -} - -void mp3_download_file(GF_InputService *plug, char *url) -{ - MP3Reader *read = (MP3Reader*) plug->priv; - - read->needs_connection = 1; - - read->dnload = gf_service_download_new(read->service, url, 0, MP3_NetIO, read); - if (!read->dnload) { - read->needs_connection = 0; - gf_service_connect_ack(read->service, NULL, GF_NOT_SUPPORTED); - } else { - /*start our download (threaded)*/ - gf_dm_sess_process(read->dnload); - } - /*service confirm is done once fetched*/ -} - - -static GF_Err MP3_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url) -{ - char szURL[2048]; - u32 minSizeToRead = 0; - char *ext; - GF_Err reply; - MP3Reader *read = (MP3Reader*)plug->priv; - read->service = serv; - - if (read->dnload) gf_service_download_del(read->dnload); - read->dnload = NULL; - - strcpy(szURL, url); - ext = strrchr(szURL, '#'); - if (ext) ext[0] = 0; - - /*remote fetch*/ - read->is_remote = !mp3_is_local(szURL); - if (read->is_remote) { - mp3_download_file(plug, (char *) szURL); - return GF_OK; - } - - reply = GF_OK; - read->stream = gf_fopen(szURL, "rb"); - if (!read->stream) { - reply = GF_URL_ERROR; - } else if (!MP3_ConfigureFromFile(read, &minSizeToRead)) { - gf_fclose(read->stream); - read->stream = NULL; - reply = GF_NOT_SUPPORTED; - } - gf_service_connect_ack(serv, NULL, reply); - if (!reply) mp3_setup_object(read); - return GF_OK; -} - -static GF_Err MP3_CloseService(GF_InputService *plug) -{ - MP3Reader *read = (MP3Reader*)plug->priv; - if (read->stream) gf_fclose(read->stream); - read->stream = NULL; - - if (read->dnload) gf_service_download_del(read->dnload); - read->dnload = NULL; - - if (read->data) gf_free(read->data); - read->data = NULL; - if (read->liveDataCopy) { - gf_free(read->liveDataCopy); - read->liveDataCopy = NULL; - read->liveDataCopySize = 0; - } - if (read->icy_name) - gf_free(read->icy_name); - read->icy_name = NULL; - if (read->icy_genre) - gf_free(read->icy_genre); - read->icy_genre = NULL; - if (read->icy_track_name) - gf_free(read->icy_track_name); - read->icy_track_name = NULL; - gf_service_disconnect_ack(read->service, NULL, GF_OK); - return GF_OK; -} - -static GF_Descriptor *MP3_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) -{ - MP3Reader *read = (MP3Reader*)plug->priv; - - /*override default*/ - if (expect_type==GF_MEDIA_OBJECT_UNDEF) expect_type=GF_MEDIA_OBJECT_AUDIO; - - /*audio object*/ - if (expect_type==GF_MEDIA_OBJECT_AUDIO) { - GF_ObjectDescriptor *od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - GF_ESD *esd = MP3_GetESD(read); - od->objectDescriptorID = 1; - gf_list_add(od->ESDescriptors, esd); - return (GF_Descriptor *) od; - } - read->is_inline = GF_TRUE; - return NULL; -} - -static GF_Err MP3_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) -{ - u32 ES_ID=0; - GF_Err e; - MP3Reader *read = (MP3Reader*)plug->priv; - - e = GF_SERVICE_ERROR; - if (read->ch==channel) goto exit; - - e = GF_STREAM_NOT_FOUND; - if (strstr(url, "ES_ID")) { - sscanf(url, "ES_ID=%ud", &ES_ID); - } - /*URL setup*/ - else if (!read->ch && MP3_CanHandleURL(plug, url)) ES_ID = 1; - - if (ES_ID==1) { - read->ch = channel; - e = GF_OK; - } - -exit: - gf_service_connect_ack(read->service, channel, e); - return e; -} - -static GF_Err MP3_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) -{ - MP3Reader *read = (MP3Reader*)plug->priv; - GF_Err e = GF_STREAM_NOT_FOUND; - if (read->ch == channel) { - read->ch = NULL; - if (read->data) gf_free(read->data); - read->data = NULL; - e = GF_OK; - } - gf_service_disconnect_ack(read->service, channel, e); - return GF_OK; -} - -static GF_Err MP3_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) -{ - MP3Reader *read = (MP3Reader*)plug->priv; - - if (com->base.command_type==GF_NET_SERVICE_INFO) { - com->info.name = read->icy_track_name ? read->icy_track_name : read->icy_name; - com->info.comment = read->icy_genre; - return GF_OK; - } - - if (!com->base.on_channel) { - /*if live session we may cache*/ - if (read->is_live && (com->command_type==GF_NET_IS_CACHABLE)) return GF_OK; - return GF_NOT_SUPPORTED; - } - switch (com->command_type) { - case GF_NET_CHAN_SET_PULL: - if ((read->ch == com->base.on_channel) && read->is_live) return GF_NOT_SUPPORTED; - return GF_OK; - case GF_NET_CHAN_INTERACTIVE: - if ((read->ch == com->base.on_channel) && read->is_live) return GF_NOT_SUPPORTED; - return GF_OK; - case GF_NET_CHAN_BUFFER: - if ((read->ch == com->base.on_channel) && read->is_live) { - if (com->buffer.max<2000) com->buffer.max = 2000; - com->buffer.min = com->buffer.max/2; - } - return GF_OK; - case GF_NET_CHAN_SET_PADDING: - read->pad_bytes = com->pad.padding_bytes; - return GF_OK; - case GF_NET_CHAN_DURATION: - com->duration.duration = read->duration; - com->duration.duration /= read->sample_rate; - return GF_OK; - case GF_NET_CHAN_PLAY: - read->start_range = com->play.start_range; - read->end_range = com->play.end_range; - read->current_time = 0; - if (read->stream) gf_fseek(read->stream, 0, SEEK_SET); - - if (read->ch == com->base.on_channel) { - read->done = GF_FALSE; - /*PLAY after complete download, estimate duration*/ - if (!read->is_remote && !read->duration) { - u32 minSizeToRead = 0; - MP3_ConfigureFromFile(read, &minSizeToRead); - if (read->duration) { - GF_NetworkCommand rcfg; - rcfg.base.on_channel = read->ch; - rcfg.base.command_type = GF_NET_CHAN_DURATION; - rcfg.duration.duration = read->duration; - rcfg.duration.duration /= read->sample_rate; - gf_service_command(read->service, &rcfg, GF_OK); - } - } - } - return GF_OK; - case GF_NET_CHAN_STOP: - return GF_OK; - default: - return GF_OK; - } -} - - -static GF_Err MP3_ChannelGetSLP(GF_InputService *plug, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, GF_SLHeader *out_sl_hdr, Bool *sl_compressed, GF_Err *out_reception_status, Bool *is_new_data) -{ - u64 pos; - u32 hdr, start_from; - MP3Reader *read = (MP3Reader*)plug->priv; - - if (read->ch != channel) - return GF_STREAM_NOT_FOUND; - - *out_reception_status = GF_OK; - *sl_compressed = GF_FALSE; - *is_new_data = GF_FALSE; - - memset(&read->sl_hdr, 0, sizeof(GF_SLHeader)); - read->sl_hdr.randomAccessPointFlag = 1; - read->sl_hdr.compositionTimeStampFlag = 1; - - /*fetching es data*/ - if (read->done) { - *out_reception_status = GF_EOS; - return GF_OK; - } - if (!read->data) { - if (!read->stream) { - *out_data_ptr = NULL; - *out_data_size = 0; - return GF_OK; - } - *is_new_data = GF_TRUE; - - pos = gf_ftell(read->stream); - hdr = gf_mp3_get_next_header(read->stream); - if (!hdr) { - if (!read->dnload) { - *out_reception_status = GF_EOS; - read->done = GF_TRUE; - } else { - gf_fseek(read->stream, pos, SEEK_SET); - *out_reception_status = GF_OK; - } - return GF_OK; - } - read->data_size = gf_mp3_frame_size(hdr); - if (!read->data_size) { - *out_reception_status = GF_EOS; - read->done = GF_TRUE; - return GF_OK; - } - - - /*we're seeking*/ - if (read->start_range && read->duration) { - read->current_time = 0; - start_from = (u32) (read->start_range * read->sample_rate); - gf_fseek(read->stream, 0, SEEK_SET); - while (read->current_time<start_from) { - hdr = gf_mp3_get_next_header(read->stream); - if (!hdr) { - read->start_range = 0; - *out_reception_status = GF_EOS; - return GF_OK; - } - read->current_time += gf_mp3_window_size(hdr); - read->data_size = gf_mp3_frame_size(hdr); - gf_fseek(read->stream, read->data_size-4, SEEK_CUR); - } - read->start_range = 0; - GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("[MP3Demux] Seeking to frame size %d - TS %d - file pos %d\n", read->data_size, read->current_time, gf_ftell(read->stream))); - } - - read->sl_hdr.compositionTimeStamp = read->current_time; - GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("[MP3Demux] Found new frame size %d - TS %d - file pos %d\n", read->data_size, read->current_time, gf_ftell(read->stream))); - - read->current_time += gf_mp3_window_size(hdr); - - read->data = (char*)gf_malloc(sizeof(char) * (read->data_size+read->pad_bytes)); - read->data[0] = (hdr >> 24) & 0xFF; - read->data[1] = (hdr >> 16) & 0xFF; - read->data[2] = (hdr >> 8) & 0xFF; - read->data[3] = (hdr ) & 0xFF; - /*end of file*/ - if (fread(&read->data[4], 1, read->data_size - 4, read->stream) != read->data_size-4) { - gf_free(read->data); - read->data = NULL; - if (read->is_remote) { - gf_fseek(read->stream, pos, SEEK_SET); - *out_reception_status = GF_OK; - } else { - *out_reception_status = GF_EOS; - } - return GF_OK; - } - if (read->pad_bytes) memset(read->data + read->data_size, 0, sizeof(char) * read->pad_bytes); - } - *out_sl_hdr = read->sl_hdr; - *out_data_ptr = read->data; - *out_data_size = read->data_size; - return GF_OK; -} - -static GF_Err MP3_ChannelReleaseSLP(GF_InputService *plug, LPNETCHANNEL channel) -{ - MP3Reader *read = (MP3Reader*)plug->priv; - - if (read->ch == channel) { - if (!read->data) return GF_BAD_PARAM; - gf_free(read->data); - read->data = NULL; - return GF_OK; - } - return GF_OK; -} - -GF_InputService *MP3_Load() -{ - MP3Reader *reader; - GF_InputService *plug = (GF_InputService*)gf_malloc(sizeof(GF_InputService)); - memset(plug, 0, sizeof(GF_InputService)); - GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC MP3 Reader", "gpac distribution") - plug->CanHandleURL = MP3_CanHandleURL; - plug->ConnectService = MP3_ConnectService; - plug->CloseService = MP3_CloseService; - plug->GetServiceDescriptor = MP3_GetServiceDesc; - plug->ConnectChannel = MP3_ConnectChannel; - plug->DisconnectChannel = MP3_DisconnectChannel; - plug->ServiceCommand = MP3_ServiceCommand; - plug->RegisterMimeTypes = MP3_RegisterMimeTypes; - /*we do support pull mode*/ - plug->ChannelGetSLP = MP3_ChannelGetSLP; - plug->ChannelReleaseSLP = MP3_ChannelReleaseSLP; - - reader = (MP3Reader*)gf_malloc(sizeof(MP3Reader)); - memset(reader, 0, sizeof(MP3Reader)); - plug->priv = reader; - return plug; -} - -void MP3_Delete(void *ifce) -{ - MP3Reader *read; - GF_InputService *plug = (GF_InputService *) ifce; - if (!plug) - return; - read = (MP3Reader*)plug->priv; - if (read) - gf_free(read); - plug->priv = NULL; - gf_free(plug); -} - -#endif /*GPAC_DISABLE_AV_PARSERS*/ - -#ifdef GPAC_HAS_MAD -GF_BaseDecoder *NewMADDec(); -void DeleteMADDec(GF_BaseDecoder *ifcg); -#endif - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifndef GPAC_DISABLE_AV_PARSERS - GF_NET_CLIENT_INTERFACE, -#endif -#ifdef GPAC_HAS_MAD - GF_MEDIA_DECODER_INTERFACE, -#endif - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ -#ifndef GPAC_DISABLE_AV_PARSERS - if (InterfaceType == GF_NET_CLIENT_INTERFACE) return (GF_BaseInterface *)MP3_Load(); -#endif -#ifdef GPAC_HAS_MAD - if (InterfaceType == GF_MEDIA_DECODER_INTERFACE) return (GF_BaseInterface *)NewMADDec(); -#endif - return NULL; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#ifdef GPAC_HAS_MAD - case GF_MEDIA_DECODER_INTERFACE: - DeleteMADDec((GF_BaseDecoder *) ifce); - break; -#endif -#ifndef GPAC_DISABLE_AV_PARSERS - case GF_NET_CLIENT_INTERFACE: - MP3_Delete(ifce); - break; -#endif - } -} - -GPAC_MODULE_STATIC_DECLARATION( mp3_in ) diff --git a/modules/mpd_in/Makefile b/modules/mpd_in/Makefile deleted file mode 100644 index f9ac1d4..0000000 --- a/modules/mpd_in/Makefile +++ /dev/null @@ -1,46 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/mpd_in - -CFLAGS= $(OPTFLAGS) -w -I"$(SRC_PATH)/include" -Wall - - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -DDEBUG -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS=mpd_in.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_mpd_in$(DYN_LIB_SUFFIX) - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_mpegts_in-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac_static $(LDFLAGS) -endif - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/mpd_in/mpd_in.c b/modules/mpd_in/mpd_in.c deleted file mode 100644 index 73a3e07..0000000 --- a/modules/mpd_in/mpd_in.c +++ /dev/null @@ -1,1668 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Cyril Concolato, Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2010- - * All rights reserved - * - * This file is part of GPAC / 3GPP/MPEG Media Presentation Description input module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include <gpac/modules/service.h> - -#ifndef GPAC_DISABLE_DASH_CLIENT - -#include <gpac/dash.h> -#include <gpac/internal/terminal_dev.h> - -typedef enum -{ - MPDIN_BUFFER_NONE=0, - MPDIN_BUFFER_MIN=1, - MPDIN_BUFFER_SEGMENTS=2 -} MpdInBuffer; - -typedef struct __mpd_module -{ - /* GPAC Service object (i.e. how this module is seen by the terminal)*/ - GF_ClientService *service; - GF_InputService *plug; - - GF_DashClient *dash; - Bool closed; - /*interface to mpd parser*/ - GF_DASHFileIO dash_io; - - Bool connection_ack_sent; - Bool memory_storage; - Bool use_max_res, immediate_switch, allow_http_abort; - u32 use_low_latency; - MpdInBuffer buffer_mode; - u32 nb_playing; - - GF_DASHAdaptationAlgorithm adaptation_algorithm; - - /*max width & height in all active representations*/ - u32 width, height; - - Double seek_request; - Double media_start_range; - //we store here all callbacks to the parent services we need to intercept, and we will override our own ones - void (*fn_connect_ack) (GF_ClientService *service, LPNETCHANNEL ns, GF_Err response); - void (*fn_data_packet) (GF_ClientService *service, LPNETCHANNEL ns, char *data, u32 data_size, GF_SLHeader *hdr, GF_Err reception_status); -} GF_MPD_In; - -typedef struct -{ - GF_MPD_In *mpdin; - GF_InputService *segment_ifce; - Bool service_connected; - Bool service_descriptor_fetched; - Bool netio_assigned; - Bool has_new_data; - u32 idx; - GF_DownloadSession *sess; - Bool is_timestamp_based, pto_setup; - u32 timescale; - s64 pto; - s64 max_cts_in_period; - bin128 key_IV; -} GF_MPDGroup; - -const char * MPD_MPD_DESC = "MPEG-DASH Streaming"; -const char * MPD_MPD_EXT = "3gm mpd"; -const char * MPD_M3U8_DESC = "Apple HLS Streaming"; -const char * MPD_M3U8_EXT = "m3u8 m3u"; - -const char * MPD_SMOOTH_DESC = "Microsoft Smooth Streaming"; -const char * MPD_SMOOTH_EXT = "ism"; -const char * MPD_SMOOTH_URL_EXT = ".ism/Manifest"; - -static u32 MPD_RegisterMimeTypes(const GF_InputService *plug) -{ - u32 i, c=0; - for (i=0; GF_DASH_MPD_MIME_TYPES[i]; i++) - gf_service_register_mime (plug, GF_DASH_MPD_MIME_TYPES[i], MPD_MPD_EXT, MPD_MPD_DESC); - c += i; - - for (i=0; GF_DASH_M3U8_MIME_TYPES[i]; i++) - gf_service_register_mime(plug, GF_DASH_M3U8_MIME_TYPES[i], MPD_M3U8_EXT, MPD_M3U8_DESC); - c += i; - - for (i=0; GF_DASH_SMOOTH_MIME_TYPES[i]; i++) - gf_service_register_mime(plug, GF_DASH_SMOOTH_MIME_TYPES[i], MPD_SMOOTH_EXT, MPD_SMOOTH_DESC); - c += i; - return c; -} - -Bool MPD_CanHandleURL(GF_InputService *plug, const char *url) -{ - u32 i; - char *sExt; - if (!plug || !url) - return GF_FALSE; - sExt = strrchr(url, '.'); - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] Received Can Handle URL request from terminal for %s\n", url)); - for (i=0; GF_DASH_MPD_MIME_TYPES[i]; i++) { - if (gf_service_check_mime_register(plug, GF_DASH_MPD_MIME_TYPES[i], MPD_MPD_EXT, MPD_MPD_DESC, sExt)) - return GF_TRUE; - } - for (i=0; GF_DASH_M3U8_MIME_TYPES[i]; i++) { - if (gf_service_check_mime_register(plug, GF_DASH_M3U8_MIME_TYPES[i], MPD_M3U8_EXT, MPD_M3U8_DESC, sExt)) - return GF_TRUE; - } - - for (i=0; GF_DASH_SMOOTH_MIME_TYPES[i]; i++) { - if (gf_service_check_mime_register(plug, GF_DASH_SMOOTH_MIME_TYPES[i], MPD_SMOOTH_EXT, MPD_SMOOTH_DESC, sExt)) - return GF_TRUE; - } - - return gf_dash_check_mpd_root_type(url) || strstr(url, MPD_SMOOTH_URL_EXT); -} - - -static s32 gf_dash_get_group_idx_from_service(GF_MPD_In *mpdin, GF_InputService *ifce) -{ - s32 i; - - for (i=0; (u32) i < gf_dash_get_group_count(mpdin->dash); i++) { - GF_MPDGroup *group = gf_dash_get_group_udta(mpdin->dash, i); - if (!group) continue; - if (group->segment_ifce == ifce) { - return i; - } - } - return -1; -} - - -static void mpdin_connect_ack(GF_ClientService *service, LPNETCHANNEL ns, GF_Err response) -{ - GF_MPD_In *mpdin = (GF_MPD_In*) service->ifce->priv; - //do not send connect error, we may have other running services - this has to be clean up - if (response==GF_OK) - mpdin->fn_connect_ack(mpdin->service, ns, response); -} - -void mpdin_data_packet(GF_ClientService *service, LPNETCHANNEL ns, char *data, u32 data_size, GF_SLHeader *hdr, GF_Err reception_status) -{ - s32 i; - GF_MPD_In *mpdin = (GF_MPD_In*) service->ifce->priv; - GF_Channel *ch; - GF_MPDGroup *group; - Bool do_map_time = GF_FALSE; - if (!ns || !hdr) { - mpdin->fn_data_packet(service, ns, data, data_size, hdr, reception_status); - return; - } - - ch = (GF_Channel *) ns; - assert(ch->odm && ch->odm->OD); - - i = gf_dash_get_group_idx_from_service(mpdin, (GF_InputService *) ch->odm->OD->service_ifce); - if (i<0) { - mpdin->fn_data_packet(service, ns, data, data_size, hdr, reception_status); - return; - } - - group = gf_dash_get_group_udta(mpdin->dash, i); - - if (gf_dash_is_m3u8(mpdin->dash)) { - mpdin->fn_data_packet(service, ns, data, data_size, hdr, reception_status); - if (!group->pto_setup) { - GF_NetworkCommand com; - memset(&com, 0, sizeof(com)); - com.command_type = GF_NET_CHAN_SET_MEDIA_TIME; - com.map_time.media_time = mpdin->media_start_range; - com.map_time.timestamp = hdr->compositionTimeStamp; - com.base.on_channel = ns; - gf_service_command(service, &com, GF_OK); - group->pto_setup = GF_TRUE; - } - return; - } - - //if sync is based on timestamps do not adjust the timestamps back - if (! group->is_timestamp_based) { - if (!group->pto_setup) { - Double scale; - s64 start, dur; - u64 pto; - gf_dash_group_get_presentation_time_offset(mpdin->dash, i, &pto, &group->timescale); - group->pto = (s64) pto; - group->pto_setup = 1; - - if (group->timescale && (group->timescale != ch->esd->slConfig->timestampResolution)) { - group->pto *= ch->esd->slConfig->timestampResolution; - group->pto /= group->timescale; - } - scale = ch->esd->slConfig->timestampResolution; - scale /= 1000; - - dur = (u64) (scale * gf_dash_get_period_duration(mpdin->dash)); - if (dur) { - group->max_cts_in_period = group->pto + dur; - } else { - group->max_cts_in_period = 0; - } - - start = (u64) (scale * gf_dash_get_period_start(mpdin->dash)); - group->pto -= start; - } - - //filter any packet outside the current period - if (group->max_cts_in_period && (s64) hdr->compositionTimeStamp > group->max_cts_in_period) { -// GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Packet timestamp "LLU" larger than max CTS in period "LLU" - skipping\n", hdr->compositionTimeStamp, group->max_cts_in_period)); -// return; - } - - //remap timestamps to our timeline - if (hdr->decodingTimeStampFlag) { - if ((s64) hdr->decodingTimeStamp >= group->pto) - hdr->decodingTimeStamp -= group->pto; - else { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Packet DTS "LLU" less than PTO "LLU" - forcing DTS to 0\n", hdr->decodingTimeStamp, group->pto)); - hdr->decodingTimeStamp = 0; - hdr->seekFlag = 1; - } - } - if (hdr->compositionTimeStampFlag) { - if ((s64) hdr->compositionTimeStamp >= group->pto) - hdr->compositionTimeStamp -= group->pto; - else { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Packet CTS "LLU" less than PTO "LLU" - forcing CTS to 0\n", hdr->compositionTimeStamp, group->pto)); - hdr->compositionTimeStamp = 0; - hdr->seekFlag = 1; - } - } - - if (hdr->OCRflag) { - u32 scale = hdr->m2ts_pcr ? 300 : 1; - u64 pto = scale*group->pto; - if (hdr->objectClockReference >= pto) { - hdr->objectClockReference -= pto; - } - //keep 4 sec between the first received PCR and the first allowed PTS to be used in the period. - else if (pto - hdr->objectClockReference < 108000000) { - hdr->objectClockReference = 0; - } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Packet OCR/PCR "LLU" less than PTO "LLU" - discarding PCR\n", hdr->objectClockReference/scale, group->pto)); - return; - } - } - - } else if (!group->pto_setup) { - do_map_time = 1; - group->pto_setup = 1; - } - - mpdin->fn_data_packet(service, ns, data, data_size, hdr, reception_status); - - if (do_map_time) { - GF_NetworkCommand com; - memset(&com, 0, sizeof(com)); - com.command_type = GF_NET_CHAN_SET_MEDIA_TIME; - com.map_time.media_time = mpdin->media_start_range; - com.map_time.timestamp = hdr->compositionTimeStamp; - com.base.on_channel = ns; - gf_service_command(service, &com, GF_OK); - } -} - - -static void MPD_NotifyData(GF_MPDGroup *group, Bool chunk_flush) -{ - GF_NetworkCommand com; - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.proxy_data.command_type = GF_NET_SERVICE_PROXY_DATA_RECEIVE; - com.proxy_data.is_chunk = chunk_flush; - com.proxy_data.is_live = gf_dash_is_dynamic_mpd(group->mpdin->dash); - group->segment_ifce->ServiceCommand(group->segment_ifce, &com); -} - -static GF_Err MPD_ClientQuery(GF_InputService *ifce, GF_NetworkCommand *param) -{ - u32 i; - GF_Err e; - GF_MPD_In *mpdin = (GF_MPD_In *) ifce->proxy_udta; - if (!param || !ifce || !ifce->proxy_udta) return GF_BAD_PARAM; - - /*gets byte range of init segment (for local validation)*/ - if (param->command_type==GF_NET_SERVICE_QUERY_INIT_RANGE) { - param->url_query.next_url = NULL; - param->url_query.start_range = 0; - param->url_query.end_range = 0; - - for (i=0; i<gf_dash_get_group_count(mpdin->dash); i++) { - GF_MPDGroup *group; - if (!gf_dash_is_group_selectable(mpdin->dash, i)) continue; - group = gf_dash_get_group_udta(mpdin->dash, i); - if (!group) continue; - - if (group->segment_ifce == ifce) { - gf_dash_group_get_segment_init_url(mpdin->dash, i, ¶m->url_query.start_range, ¶m->url_query.end_range); - param->url_query.current_download = 0; - - param->url_query.key_url = gf_dash_group_get_segment_init_keys(mpdin->dash, i, &group->key_IV); - if (param->url_query.key_url) { - param->url_query.key_IV = &group->key_IV; - } - return GF_OK; - } - } - return GF_SERVICE_ERROR; - } - - /*gets URL and byte range of next segment - if needed, adds bitstream switching segment info*/ - if (param->command_type==GF_NET_SERVICE_QUERY_NEXT) { - Bool group_done; - u32 nb_segments_cached; - u32 group_idx=0; - GF_MPDGroup *group=NULL; - const char *src_url; - Bool discard_first_cache_entry = param->url_query.drop_first_segment; - Bool check_current_download = param->url_query.current_download; - u32 timer = gf_sys_clock(); - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] Received Service Query Next request from input service %s\n", ifce->module_name)); - - - param->url_query.current_download = 0; - param->url_query.discontinuity_type = 0; - - for (i=0; i<gf_dash_get_group_count(mpdin->dash); i++) { - if (!gf_dash_is_group_selected(mpdin->dash, i)) continue; - group = gf_dash_get_group_udta(mpdin->dash, i); - if (group->segment_ifce == ifce) { - group_idx = i; - break; - } - group=NULL; - } - - if (!group) { - return GF_SERVICE_ERROR; - } - - //update group idx - if (group->idx != group_idx) { - group->idx = group_idx; - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] New AdaptationSet detected after MPD update ?\n")); - } - - if (discard_first_cache_entry) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] Discarding first segment in cache\n")); - gf_dash_group_discard_segment(mpdin->dash, group_idx); - } - - while (gf_dash_is_running(mpdin->dash)) { - group_done=0; - nb_segments_cached = gf_dash_group_get_num_segments_ready(mpdin->dash, group_idx, &group_done); - if (nb_segments_cached>=1) - break; - - if (group_done) { - if (!gf_dash_get_period_switch_status(mpdin->dash) && !gf_dash_in_last_period(mpdin->dash, GF_TRUE)) { - GF_NetworkCommand com; - param->url_query.in_end_of_period = 1; - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.command_type = GF_NET_BUFFER_QUERY; - if (gf_dash_get_period_switch_status(mpdin->dash) != 1) { - gf_service_command(mpdin->service, &com, GF_OK); - //we only switch period once no more data is in our buffers - if (!com.buffer.occupancy) { - param->url_query.in_end_of_period = 0; - gf_dash_request_period_switch(mpdin->dash); - } - } - if (param->url_query.in_end_of_period) - return GF_BUFFER_TOO_SMALL; - } else { - return GF_EOS; - } - } - - if (check_current_download && mpdin->use_low_latency) { - Bool is_switched=GF_FALSE; - gf_dash_group_probe_current_download_segment_location(mpdin->dash, group_idx, ¶m->url_query.next_url, NULL, ¶m->url_query.next_url_init_or_switch_segment, &src_url, &is_switched); - - if (param->url_query.next_url) { - param->url_query.current_download = 1; - param->url_query.has_new_data = group->has_new_data; - param->url_query.discontinuity_type = is_switched ? 1 : 0; - if (gf_dash_group_loop_detected(mpdin->dash, group_idx)) - param->url_query.discontinuity_type = 2; - group->has_new_data = 0; - return GF_OK; - } - return GF_BUFFER_TOO_SMALL; - } - return GF_BUFFER_TOO_SMALL; - } - - param->url_query.current_download = 0; - nb_segments_cached = gf_dash_group_get_num_segments_ready(mpdin->dash, group_idx, &group_done); - if (nb_segments_cached < 1) { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[MPD_IN] No more file in cache, EOS\n")); - return GF_EOS; - } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] Had to wait for %u ms for the only cache file to be downloaded\n", (gf_sys_clock() - timer))); - } - - e = gf_dash_group_get_next_segment_location(mpdin->dash, group_idx, param->url_query.dependent_representation_index, ¶m->url_query.next_url, ¶m->url_query.start_range, ¶m->url_query.end_range, - NULL, ¶m->url_query.next_url_init_or_switch_segment, ¶m->url_query.switch_start_range , ¶m->url_query.switch_end_range, - &src_url, ¶m->url_query.has_next, ¶m->url_query.key_url, &group->key_IV); - if (e) - return e; - - param->url_query.key_IV = &group->key_IV; - - if (gf_dash_group_loop_detected(mpdin->dash, group_idx)) - param->url_query.discontinuity_type = 2; - - -#ifndef GPAC_DISABLE_LOG - { - u32 timer2 = gf_sys_clock() - timer; - if (timer2 > 1000) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] Waiting for download to end took a long time : %u ms\n", timer2)); - } - if (param->url_query.end_range) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] Next Segment is %s bytes "LLD"-"LLD"\n", src_url, param->url_query.start_range, param->url_query.end_range)); - } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] Next Segment is %s\n", src_url)); - } - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] Waited %d ms - Elements in cache: %u/%u\n\tCache file name %s\n\tsegment start time %g sec\n", timer2, gf_dash_group_get_num_segments_ready(mpdin->dash, group_idx, &group_done), gf_dash_group_get_max_segments_in_cache(mpdin->dash, group_idx), param->url_query.next_url, gf_dash_group_current_segment_start_time(mpdin->dash, group_idx))); - } -#endif - } - - if (param->command_type == GF_NET_SERVICE_QUERY_UTC_DELAY) { - param->utc_delay.delay = gf_dash_get_utc_drift_estimate(mpdin->dash); - return GF_OK; - } - - return GF_OK; -} - -/*locates input service (demuxer) based on mime type or segment name*/ -static GF_Err MPD_LoadMediaService(GF_MPD_In *mpdin, u32 group_index, const char *mime, const char *init_segment_name) -{ - GF_InputService *segment_ifce; - u32 i; - const char *sPlug; - if (mime) { - /* Check MIME type to start the right InputService */ - sPlug = gf_cfg_get_key(mpdin->service->term->user->config, "MimeTypes", mime); - if (sPlug) sPlug = strrchr(sPlug, '"'); - if (sPlug) { - sPlug += 2; - segment_ifce = (GF_InputService *) gf_modules_load_interface_by_name(mpdin->service->term->user->modules, sPlug, GF_NET_CLIENT_INTERFACE); - if (segment_ifce) { - GF_MPDGroup *group; - GF_SAFEALLOC(group, GF_MPDGroup); - if (!group) return GF_OUT_OF_MEM; - - group->segment_ifce = segment_ifce; - group->segment_ifce->proxy_udta = mpdin; - group->segment_ifce->query_proxy = MPD_ClientQuery; - group->mpdin = mpdin; - group->idx = group_index; - gf_dash_set_group_udta(mpdin->dash, group_index, group); - return GF_OK; - } - } - } - if (init_segment_name) { - for (i=0; i< gf_modules_get_count(mpdin->service->term->user->modules); i++) { - GF_InputService *ifce = (GF_InputService *) gf_modules_load_interface(mpdin->service->term->user->modules, i, GF_NET_CLIENT_INTERFACE); - if (!ifce) continue; - - if (ifce->CanHandleURL && ifce->CanHandleURL(ifce, init_segment_name)) { - GF_MPDGroup *group; - GF_SAFEALLOC(group, GF_MPDGroup); - if (!group) return GF_OUT_OF_MEM; - group->segment_ifce = ifce; - group->segment_ifce->proxy_udta = mpdin; - group->segment_ifce->query_proxy = MPD_ClientQuery; - group->mpdin = mpdin; - group->idx = group_index; - gf_dash_set_group_udta(mpdin->dash, group_index, group); - return GF_OK; - } - gf_modules_close_interface((GF_BaseInterface *) ifce); - } - } - GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[MPD_IN] Error locating plugin for segment - mime type %s - name %s\n", mime, init_segment_name)); - return GF_CODEC_NOT_FOUND; -} - - - -GF_InputService *MPD_GetInputServiceForChannel(GF_MPD_In *mpdin, LPNETCHANNEL channel) -{ - GF_Channel *ch; - if (!channel) { - u32 i; - for (i=0; i<gf_dash_get_group_count(mpdin->dash); i++) { - if (gf_dash_is_group_selectable(mpdin->dash, i)) { - GF_MPDGroup *mudta = gf_dash_get_group_udta(mpdin->dash, i); - if (mudta && mudta->segment_ifce) return mudta->segment_ifce; - } - } - return NULL; - } - - ch = (GF_Channel *) channel; - assert(ch->odm && ch->odm->OD); - return (GF_InputService *) ch->odm->OD->service_ifce; -} - -s32 MPD_GetGroupIndexForChannel(GF_MPD_In *mpdin, LPNETCHANNEL channel) -{ - u32 i; - GF_InputService *ifce = MPD_GetInputServiceForChannel(mpdin, channel); - if (!ifce) return -1; - - for (i=0; i<gf_dash_get_group_count(mpdin->dash); i++) { - GF_MPDGroup *group = gf_dash_get_group_udta(mpdin->dash, i); - if (!group) continue; - if (group->segment_ifce == ifce) return i; - } - return -1; -} - -GF_Err MPD_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) -{ - GF_MPD_In *mpdin = (GF_MPD_In*) plug->priv; - GF_InputService *segment_ifce = MPD_GetInputServiceForChannel(mpdin, channel); - if (!plug || !plug->priv || !segment_ifce) return GF_SERVICE_ERROR; - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] Received Channel Connection (%p) request from terminal for %s\n", channel, url)); - return segment_ifce->ConnectChannel(segment_ifce, channel, url, upstream); -} - -GF_Err MPD_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) -{ - GF_MPD_In *mpdin = (GF_MPD_In*) plug->priv; - GF_InputService *segment_ifce = MPD_GetInputServiceForChannel(mpdin, channel); - if (!plug || !plug->priv || !segment_ifce) return GF_SERVICE_ERROR; - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] Received Disconnect channel (%p) request from terminal \n", channel)); - - return segment_ifce->DisconnectChannel(segment_ifce, channel); -} - - -static void mpdin_dash_segment_netio(void *cbk, GF_NETIO_Parameter *param) -{ - GF_MPDGroup *group = (GF_MPDGroup *)cbk; - - if (param->msg_type == GF_NETIO_PARSE_HEADER) { - if (!strcmp(param->name, "Dash-Newest-Segment")) { - gf_dash_resync_to_segment(group->mpdin->dash, param->value, gf_dm_sess_get_header(param->sess, "Dash-Oldest-Segment")); - } - } - - if (param->msg_type == GF_NETIO_DATA_EXCHANGE) { - group->has_new_data = 1; - - if (param->reply) { - u32 bytes_per_sec; - const char *url; - gf_dm_sess_get_stats(group->sess, NULL, &url, NULL, NULL, &bytes_per_sec, NULL); - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] End of chunk received for %s at UTC "LLU" ms - estimated bandwidth %d kbps - chunk start at UTC "LLU"\n", url, gf_net_get_utc(), 8*bytes_per_sec/1000, gf_dm_sess_get_utc_start(group->sess))); - - if (group->mpdin->use_low_latency) - MPD_NotifyData(group, 1); - } else if (group->mpdin->use_low_latency==2) { - MPD_NotifyData(group, 1); - } - - if (group->mpdin->allow_http_abort) - gf_dash_group_check_bandwidth(group->mpdin->dash, group->idx); - } - if (param->msg_type == GF_NETIO_DATA_TRANSFERED) { - u32 bytes_per_sec; - const char *url; - gf_dm_sess_get_stats(group->sess, NULL, &url, NULL, NULL, &bytes_per_sec, NULL); - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] End of file %s download at UTC "LLU" ms - estimated bandwidth %d kbps - started file or last chunk at UTC "LLU"\n", url, gf_net_get_utc(), 8*bytes_per_sec/1000, gf_dm_sess_get_utc_start(group->sess))); - } -} - -void mpdin_dash_io_delete_cache_file(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, const char *cache_url) -{ - gf_dm_delete_cached_file_entry_session((GF_DownloadSession *)session, cache_url); -} - -GF_DASHFileIOSession mpdin_dash_io_create(GF_DASHFileIO *dashio, Bool persistent, const char *url, s32 group_idx) -{ - GF_MPDGroup *group = NULL; - GF_DownloadSession *sess; - u32 flags = GF_NETIO_SESSION_NOT_THREADED; - GF_MPD_In *mpdin = (GF_MPD_In *)dashio->udta; - if (mpdin->memory_storage) - flags |= GF_NETIO_SESSION_MEMORY_CACHE; - - if (persistent) flags |= GF_NETIO_SESSION_PERSISTENT; - - if (group_idx>=0) { - group = gf_dash_get_group_udta(mpdin->dash, group_idx); - } - if (group) { - group->netio_assigned = GF_TRUE; - group->sess = sess = gf_service_download_new(mpdin->service, url, flags, mpdin_dash_segment_netio, group); - } else { - sess = gf_service_download_new(mpdin->service, url, flags, NULL, NULL); - } - return (GF_DASHFileIOSession) sess; -} -void mpdin_dash_io_del(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) -{ - gf_service_download_del((GF_DownloadSession *)session); -} -void mpdin_dash_io_abort(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) -{ - gf_dm_sess_abort((GF_DownloadSession *)session); -} -GF_Err mpdin_dash_io_setup_from_url(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, const char *url, s32 group_idx) -{ - if (group_idx>=0) { - GF_MPD_In *mpdin = (GF_MPD_In *)dashio->udta; - GF_MPDGroup *group = gf_dash_get_group_udta(mpdin->dash, group_idx); - if (group && !group->netio_assigned) { - group->netio_assigned = GF_TRUE; - group->sess = (GF_DownloadSession *)session; - gf_dm_sess_reassign((GF_DownloadSession *)session, 0xFFFFFFFF, mpdin_dash_segment_netio, group); - } - } - return gf_dm_sess_setup_from_url((GF_DownloadSession *)session, url); -} -GF_Err mpdin_dash_io_set_range(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, u64 start_range, u64 end_range, Bool discontinue_cache) -{ - return gf_dm_sess_set_range((GF_DownloadSession *)session, start_range, end_range, discontinue_cache); -} -GF_Err mpdin_dash_io_init(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) -{ - return gf_dm_sess_process_headers((GF_DownloadSession *)session); -} -GF_Err mpdin_dash_io_run(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) -{ - return gf_dm_sess_process((GF_DownloadSession *)session); -} -const char *mpdin_dash_io_get_url(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) -{ - return gf_dm_sess_get_resource_name((GF_DownloadSession *)session); -} -const char *mpdin_dash_io_get_cache_name(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) -{ - return gf_dm_sess_get_cache_name((GF_DownloadSession *)session); -} -const char *mpdin_dash_io_get_mime(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) -{ - return gf_dm_sess_mime_type((GF_DownloadSession *)session); -} - -const char *mpdin_dash_io_get_header_value(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, const char *header_name) -{ - return gf_dm_sess_get_header((GF_DownloadSession *)session, header_name); -} - -u64 mpdin_dash_io_get_utc_start_time(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) -{ - return gf_dm_sess_get_utc_start((GF_DownloadSession *)session); -} - - - -u32 mpdin_dash_io_get_bytes_per_sec(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) -{ - u32 bps=0; -// GF_DownloadSession *sess = (GF_DownloadSession *)session; - if (session) { - gf_dm_sess_get_stats((GF_DownloadSession *)session, NULL, NULL, NULL, NULL, &bps, NULL); - } else { - GF_MPD_In *mpdin = (GF_MPD_In *)dashio->udta; - bps = gf_dm_get_data_rate(mpdin->service->term->downloader); - bps/=8; - } - return bps; -} -u32 mpdin_dash_io_get_total_size(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) -{ - u32 size=0; -// GF_DownloadSession *sess = (GF_DownloadSession *)session; - gf_dm_sess_get_stats((GF_DownloadSession *)session, NULL, NULL, &size, NULL, NULL, NULL); - return size; -} -u32 mpdin_dash_io_get_bytes_done(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) -{ - u32 size=0; -// GF_DownloadSession *sess = (GF_DownloadSession *)session; - gf_dm_sess_get_stats((GF_DownloadSession *)session, NULL, NULL, NULL, &size, NULL, NULL); - return size; -} - - -GF_Err mpdin_dash_io_on_dash_event(GF_DASHFileIO *dashio, GF_DASHEventType dash_evt, s32 group_idx, GF_Err error_code) -{ - GF_Err e; - u32 i; - GF_MPD_In *mpdin = (GF_MPD_In *)dashio->udta; - - if (dash_evt==GF_DASH_EVENT_PERIOD_SETUP_ERROR) { - if (!mpdin->connection_ack_sent) { - mpdin->fn_connect_ack(mpdin->service, NULL, error_code); - mpdin->connection_ack_sent= GF_TRUE; - } - return GF_OK; - } - - if (dash_evt==GF_DASH_EVENT_SELECT_GROUPS) { - //configure buffer in dynamic mode without low latency: we indicate how much the player will buffer - if (gf_dash_is_dynamic_mpd(mpdin->dash) && !mpdin->use_low_latency) { - u32 buffer_ms = 0; - const char *opt = gf_modules_get_option((GF_BaseInterface *)mpdin->plug, "Network", "BufferLength"); - if (opt) buffer_ms = atoi(opt); - - //use min buffer from MPD - if (mpdin->buffer_mode>=MPDIN_BUFFER_MIN) { - u32 mpd_buffer_ms = gf_dash_get_min_buffer_time(mpdin->dash); - if (mpd_buffer_ms > buffer_ms) - buffer_ms = mpd_buffer_ms; - } - - if (buffer_ms) { - gf_dash_set_user_buffer(mpdin->dash, buffer_ms); - } - } - //let the player decide which group to play: we declare everything - return GF_OK; - } - - /*for all selected groups, create input service and connect to init/first segment*/ - if (dash_evt==GF_DASH_EVENT_CREATE_PLAYBACK) { - /*select input services if possible*/ - for (i=0; i<gf_dash_get_group_count(mpdin->dash); i++) { - const char *mime, *init_segment; - u32 j; - Bool playable = GF_TRUE; - //let the player decide which group to play - if (!gf_dash_is_group_selectable(mpdin->dash, i)) - continue; - - j=0; - while (1) { - const char *desc_id, *desc_scheme, *desc_value; - if (! gf_dash_group_enum_descriptor(mpdin->dash, i, GF_MPD_DESC_ESSENTIAL_PROPERTIES, j, &desc_id, &desc_scheme, &desc_value)) - break; - j++; - if (!strcmp(desc_scheme, "urn:mpeg:dash:srd:2014")) { - } else { - playable = GF_FALSE; - break; - } - } - if (!playable) { - gf_dash_group_select(mpdin->dash, i, GF_FALSE); - continue; - } - - if (gf_dash_group_has_dependent_group(mpdin->dash, i) >=0 ) { - gf_dash_group_select(mpdin->dash, i, GF_TRUE); - continue; - } - - mime = gf_dash_group_get_segment_mime(mpdin->dash, i); - init_segment = gf_dash_group_get_segment_init_url(mpdin->dash, i, NULL, NULL); - e = MPD_LoadMediaService(mpdin, i, mime, init_segment); - if (e != GF_OK) { - gf_dash_group_select(mpdin->dash, i, GF_FALSE); - } else { - u32 w, h; - /*connect our media service*/ - GF_MPDGroup *group = gf_dash_get_group_udta(mpdin->dash, i); - gf_dash_group_get_video_info(mpdin->dash, i, &w, &h); - if (w && h && w>mpdin->width && h>mpdin->height) { - mpdin->width = w; - mpdin->height = h; - } - if (gf_dash_group_get_srd_max_size_info(mpdin->dash, i, &w, &h)) { - mpdin->width = w; - mpdin->height = h; - } - - e = group->segment_ifce->ConnectService(group->segment_ifce, mpdin->service, init_segment); - if (e) { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD_IN] Unable to connect input service to %s\n", init_segment)); - gf_dash_group_select(mpdin->dash, i, GF_FALSE); - } else { - group->service_connected = 1; - } - if (mpdin->closed) return GF_OK; - } - } - - if (!mpdin->connection_ack_sent) { - mpdin->fn_connect_ack(mpdin->service, NULL, GF_OK); - mpdin->connection_ack_sent=1; - } - - //we had a seek outside of the period we were setting up, during period setup ! - //request the seek again from the player - if (mpdin->seek_request>=0) { - GF_NetworkCommand com; - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.command_type = GF_NET_SERVICE_SEEK; - com.play.start_range = mpdin->seek_request; - mpdin->seek_request = 0; - gf_service_command(mpdin->service, &com, GF_OK); - } - return GF_OK; - } - - /*for all running services, stop service*/ - if (dash_evt==GF_DASH_EVENT_DESTROY_PLAYBACK) { - - mpdin->service->subservice_disconnect = 1; - gf_service_disconnect_ack(mpdin->service, NULL, GF_OK); - mpdin->service->subservice_disconnect = 2; - - for (i=0; i<gf_dash_get_group_count(mpdin->dash); i++) { - GF_MPDGroup *group = gf_dash_get_group_udta(mpdin->dash, i); - if (!group) continue; - if (group->segment_ifce) { - if (group->service_connected) { - group->segment_ifce->CloseService(group->segment_ifce); - group->service_connected = 0; - } - gf_modules_close_interface((GF_BaseInterface *) group->segment_ifce); - } - gf_free(group); - gf_dash_set_group_udta(mpdin->dash, i, NULL); - } - mpdin->service->subservice_disconnect = 0; - return GF_OK; - } - - if (dash_evt==GF_DASH_EVENT_BUFFERING) { - u32 tot, done; - gf_dash_get_buffer_info(mpdin->dash, &tot, &done); - return GF_OK; - } - if (dash_evt==GF_DASH_EVENT_SEGMENT_AVAILABLE) { - if (group_idx>=0) { - GF_MPDGroup *group = gf_dash_get_group_udta(mpdin->dash, group_idx); - if (group) MPD_NotifyData(group, 0); - } - return GF_OK; - } - if (dash_evt==GF_DASH_EVENT_QUALITY_SWITCH) { - if (group_idx>=0) { - GF_MPDGroup *group = gf_dash_get_group_udta(mpdin->dash, group_idx); - if (!group) { - group_idx = gf_dash_group_has_dependent_group(mpdin->dash, group_idx); - group = gf_dash_get_group_udta(mpdin->dash, group_idx); - } - if (group) { - GF_NetworkCommand com; - memset(&com, 0, sizeof(GF_NetworkCommand)); - - com.command_type = GF_NET_SERVICE_EVENT; - com.send_event.evt.type = GF_EVENT_QUALITY_SWITCHED; - gf_service_command(mpdin->service, &com, GF_OK); - } - } - return GF_OK; - } - if (dash_evt==GF_DASH_EVENT_TIMESHIFT_OVERFLOW) { - GF_NetworkCommand com; - com.command_type = GF_NET_SERVICE_EVENT; - com.send_event.evt.type = (group_idx>=0) ? GF_EVENT_TIMESHIFT_OVERFLOW : GF_EVENT_TIMESHIFT_UNDERRUN; - gf_service_command(mpdin->service, &com, GF_OK); - } - if (dash_evt==GF_DASH_EVENT_TIMESHIFT_UPDATE) { - GF_NetworkCommand com; - com.command_type = GF_NET_SERVICE_EVENT; - com.send_event.evt.type = GF_EVENT_TIMESHIFT_UPDATE; - gf_service_command(mpdin->service, &com, GF_OK); - } - - if (dash_evt==GF_DASH_EVENT_CODEC_STAT_QUERY) { - GF_NetworkCommand com; - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.command_type = GF_NET_SERVICE_CODEC_STAT_QUERY; - gf_service_command(mpdin->service, &com, GF_OK); - gf_dash_group_set_codec_stat(mpdin->dash, group_idx, com.codec_stat.avg_dec_time, com.codec_stat.max_dec_time, com.codec_stat.irap_avg_dec_time, com.codec_stat.irap_max_dec_time, com.codec_stat.codec_reset, com.codec_stat.decode_only_rap); - - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.command_type = GF_NET_BUFFER_QUERY; - gf_service_command(mpdin->service, &com, GF_OK); - gf_dash_group_set_buffer_levels(mpdin->dash, group_idx, com.buffer.min, com.buffer.max, com.buffer.occupancy); - } - - return GF_OK; -} - -/*check in all groups if the service can support reverse playback (speed<0); return GF_OK only if service is supported in all groups*/ -static GF_Err mpdin_dash_can_reverse_playback(GF_MPD_In *mpdin) -{ - u32 i; - GF_Err e = GF_NOT_SUPPORTED; - for (i=0; i<gf_dash_get_group_count(mpdin->dash); i++) { - if (gf_dash_is_group_selectable(mpdin->dash, i)) { - GF_MPDGroup *mudta = gf_dash_get_group_udta(mpdin->dash, i); - if (mudta && mudta->segment_ifce) { - GF_NetworkCommand com; - com.command_type = GF_NET_SERVICE_CAN_REVERSE_PLAYBACK; - e = mudta->segment_ifce->ServiceCommand(mudta->segment_ifce, &com); - if (GF_OK != e) - return e; - } - } - } - - return e; -} - -GF_Err MPD_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url) -{ - GF_MPD_In *mpdin = (GF_MPD_In*) plug->priv; - const char *opt; - GF_Err e; - s32 shift_utc_ms, debug_adaptation_set; - u32 max_cache_duration, auto_switch_count, init_timeshift, tiles_rate_decrease; - Bool use_server_utc; - GF_DASHInitialSelectionMode first_select_mode; - GF_DASHTileAdaptationMode tile_adapt_mode; - Bool keep_files, disable_switching, use_threads; - - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] Received Service Connection request (%p) from terminal for %s\n", serv, url)); - - if (!mpdin || !serv || !url) - return GF_BAD_PARAM; - - mpdin->service = serv; - mpdin->seek_request = -1; - - mpdin->dash_io.udta = mpdin; - mpdin->dash_io.delete_cache_file = mpdin_dash_io_delete_cache_file; - mpdin->dash_io.create = mpdin_dash_io_create; - mpdin->dash_io.del = mpdin_dash_io_del; - mpdin->dash_io.abort = mpdin_dash_io_abort; - mpdin->dash_io.setup_from_url = mpdin_dash_io_setup_from_url; - mpdin->dash_io.set_range = mpdin_dash_io_set_range; - mpdin->dash_io.init = mpdin_dash_io_init; - mpdin->dash_io.run = mpdin_dash_io_run; - mpdin->dash_io.get_url = mpdin_dash_io_get_url; - mpdin->dash_io.get_cache_name = mpdin_dash_io_get_cache_name; - mpdin->dash_io.get_mime = mpdin_dash_io_get_mime; - mpdin->dash_io.get_header_value = mpdin_dash_io_get_header_value; - mpdin->dash_io.get_utc_start_time = mpdin_dash_io_get_utc_start_time; - mpdin->dash_io.get_bytes_per_sec = mpdin_dash_io_get_bytes_per_sec; - mpdin->dash_io.get_total_size = mpdin_dash_io_get_total_size; - mpdin->dash_io.get_bytes_done = mpdin_dash_io_get_bytes_done; - mpdin->dash_io.on_dash_event = mpdin_dash_io_on_dash_event; - - max_cache_duration = 0; - opt = gf_modules_get_option((GF_BaseInterface *)plug, "Network", "BufferLength"); - if (opt) max_cache_duration = atoi(opt); - - auto_switch_count = 0; - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "AutoSwitchCount"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "AutoSwitchCount", "0"); - if (opt) auto_switch_count = atoi(opt); - - keep_files = GF_FALSE; - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "KeepFiles"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "KeepFiles", "no"); - if (opt && !strcmp(opt, "yes")) keep_files = GF_TRUE; - - disable_switching = 0; - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "NetworkAdaptation"); - if (!opt) { - opt = "buffer"; - gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "NetworkAdaptation", opt); - } - if (!strcmp(opt, "disabled")) { - disable_switching = 2; - mpdin->adaptation_algorithm = GF_DASH_ALGO_NONE; - } - else if (!strcmp(opt, "bandwidth")) { - mpdin->adaptation_algorithm = GF_DASH_ALGO_GPAC_LEGACY_RATE; - } - else if (!strcmp(opt, "buffer")) { - mpdin->adaptation_algorithm = GF_DASH_ALGO_GPAC_LEGACY_BUFFER; - } - else if (!strcmp(opt, "test")) { - mpdin->adaptation_algorithm = GF_DASH_ALGO_GPAC_TEST; - } - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "StartRepresentation"); - if (!opt) { - gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "StartRepresentation", "minBandwidth"); - opt = "minBandwidth"; - } - if (opt && !strcmp(opt, "maxBandwidth")) first_select_mode = GF_DASH_SELECT_BANDWIDTH_HIGHEST; - else if (opt && !strcmp(opt, "maxBandwidthTiles")) first_select_mode = GF_DASH_SELECT_BANDWIDTH_HIGHEST_TILES; - else if (opt && !strcmp(opt, "minQuality")) first_select_mode = GF_DASH_SELECT_QUALITY_LOWEST; - else if (opt && !strcmp(opt, "maxQuality")) first_select_mode = GF_DASH_SELECT_QUALITY_HIGHEST; - else first_select_mode = GF_DASH_SELECT_BANDWIDTH_LOWEST; - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "MemoryStorage"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "MemoryStorage", "yes"); - mpdin->memory_storage = (!opt || !strcmp(opt, "yes")) ? GF_TRUE : GF_FALSE; - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "UseMaxResolution"); - if (!opt) { - opt = "yes"; - gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "UseMaxResolution", opt); - } - mpdin->use_max_res = !strcmp(opt, "yes") ? GF_TRUE : GF_FALSE; - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "ImmediateSwitching"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "ImmediateSwitching", "no"); - mpdin->immediate_switch = (opt && !strcmp(opt, "yes")) ? GF_TRUE : GF_FALSE; - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "BufferingMode"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "BufferingMode", "minBuffer"); - - if (opt && !strcmp(opt, "segments")) mpdin->buffer_mode = MPDIN_BUFFER_SEGMENTS; - else if (opt && !strcmp(opt, "none")) mpdin->buffer_mode = MPDIN_BUFFER_NONE; - else mpdin->buffer_mode = MPDIN_BUFFER_MIN; - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "LowLatency"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "LowLatency", "no"); - - if (opt && !strcmp(opt, "chunk")) mpdin->use_low_latency = 1; - else if (opt && !strcmp(opt, "always")) mpdin->use_low_latency = 2; - else mpdin->use_low_latency = 0; - - - use_threads = GF_FALSE; - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "ThreadedDownload"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "ThreadedDownload", "no"); - if (opt && !strcmp(opt, "yes")) use_threads = GF_TRUE; - - if (mpdin->use_low_latency) use_threads = GF_TRUE; - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "AllowAbort"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "AllowAbort", "no"); - mpdin->allow_http_abort = (opt && !strcmp(opt, "yes")) ? GF_TRUE : GF_FALSE; - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "ShiftClock"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "ShiftClock", "0"); - shift_utc_ms = opt ? atoi(opt) : 0; - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "UseServerUTC"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "UseServerUTC", "yes"); - use_server_utc = (opt && !strcmp(opt, "yes")) ? 1 : 0; - - mpdin->nb_playing = 0; - - init_timeshift = 0; - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "InitialTimeshift"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "InitialTimeshift", "0"); - if (opt) init_timeshift = atoi(opt); - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "TileAdaptation"); - if (!opt) { - gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "TileAdaptation", "none"); - opt = "none"; - } - if (!strcmp(opt, "none")) tile_adapt_mode = GF_DASH_ADAPT_TILE_NONE; - else if (!strcmp(opt, "rows")) tile_adapt_mode = GF_DASH_ADAPT_TILE_ROWS; - else if (!strcmp(opt, "reverseRows")) tile_adapt_mode = GF_DASH_ADAPT_TILE_ROWS_REVERSE; - else if (!strcmp(opt, "middleRows")) tile_adapt_mode = GF_DASH_ADAPT_TILE_ROWS_MIDDLE; - else if (!strcmp(opt, "columns")) tile_adapt_mode = GF_DASH_ADAPT_TILE_COLUMNS; - else if (!strcmp(opt, "reverseColumns")) tile_adapt_mode = GF_DASH_ADAPT_TILE_COLUMNS_REVERSE; - else if (!strcmp(opt, "middleColumns")) tile_adapt_mode = GF_DASH_ADAPT_TILE_COLUMNS_MIDDLE; - else if (!strcmp(opt, "center")) tile_adapt_mode = GF_DASH_ADAPT_TILE_CENTER; - else if (!strcmp(opt, "edges")) tile_adapt_mode = GF_DASH_ADAPT_TILE_EDGES; - else { - GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPDIn] Unrecognized tile adaptation mode %s - defaulting to none\n", opt)); - tile_adapt_mode = GF_DASH_ADAPT_TILE_NONE; - } - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "TileRateDecrease"); - if (!opt) { - gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "TileRateDecrease", "100"); - opt = "100"; - } - tiles_rate_decrease = atoi(opt); - - //override all service callbacks - mpdin->fn_connect_ack = serv->fn_connect_ack; - serv->fn_connect_ack = mpdin_connect_ack; - mpdin->fn_data_packet = serv->fn_data_packet; - serv->fn_data_packet = mpdin_data_packet; - - mpdin->dash = gf_dash_new(&mpdin->dash_io, max_cache_duration, auto_switch_count, keep_files, disable_switching, first_select_mode, (mpdin->buffer_mode == MPDIN_BUFFER_SEGMENTS) ? 1 : 0, init_timeshift); - gf_dash_set_algo(mpdin->dash, mpdin->adaptation_algorithm); - - if (!mpdin->dash) { - GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[MPD_IN] Error - cannot create DASH Client for %s\n", url)); - mpdin->fn_connect_ack(mpdin->service, NULL, GF_IO_ERR); - return GF_OK; - } - - gf_dash_set_utc_shift(mpdin->dash, shift_utc_ms); - gf_dash_enable_utc_drift_compensation(mpdin->dash, use_server_utc); - gf_dash_set_tile_adaptation_mode(mpdin->dash, tile_adapt_mode, tiles_rate_decrease); - gf_dash_set_threaded_download(mpdin->dash, use_threads); - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "UseScreenResolution"); - //default mode is no for the time being - if (!opt) gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "UseScreenResolution", "no"); - if (!opt || !strcmp(opt, "yes")) { - GF_NetworkCommand com; - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.command_type = GF_NET_SERVICE_MEDIA_CAP_QUERY; - gf_service_command(serv, &com, GF_OK); - - if (com.mcaps.width && com.mcaps.height) { - gf_dash_set_max_resolution(mpdin->dash, com.mcaps.width, com.mcaps.height, com.mcaps.display_bit_depth); - } - } - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "TimeBetween404"); - if (opt) { - gf_dash_set_min_timeout_between_404(mpdin->dash, atoi(opt)); - } - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "SegmentExpirationThreshold"); - if (opt) { - gf_dash_set_segment_expiration_threshold(mpdin->dash, atoi(opt)); - } - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "SwitchProbeCount"); - if (opt) { - gf_dash_set_switching_probe_count(mpdin->dash, atoi(opt)); - } else { - gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "SwitchProbeCount", "1"); - } - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "AgressiveSwitching"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "AgressiveSwitching", "no"); - gf_dash_set_agressive_adaptation(mpdin->dash, (opt && !strcmp(opt, "yes")) ? GF_TRUE : GF_FALSE); - - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "DebugAdaptationSet"); - if (!opt) gf_modules_set_option((GF_BaseInterface *)plug, "DASH", "DebugAdaptationSet", "-1"); - debug_adaptation_set = opt ? atoi(opt) : -1; - - gf_dash_debug_group(mpdin->dash, debug_adaptation_set); - - /*by default, speed adaptation and display adaptation are enable*/ - opt = gf_modules_get_option((GF_BaseInterface *)plug, "DASH", "SpeedAdaptation"); - if (opt && !strcmp(opt, "yes")) - gf_dash_disable_speed_adaptation(mpdin->dash, GF_FALSE); - else - gf_dash_disable_speed_adaptation(mpdin->dash, GF_TRUE); - - - /*dash thread starts at the end of gf_dash_open */ - e = gf_dash_open(mpdin->dash, url); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[MPD_IN] Error - cannot initialize DASH Client for %s: %s\n", url, gf_error_to_string(e))); - mpdin->fn_connect_ack(mpdin->service, NULL, e); - return GF_OK; - } - return GF_OK; -} - -static GF_Descriptor *MPD_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) -{ - u32 i; - GF_MPD_In *mpdin = (GF_MPD_In*) plug->priv; - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] Received Service Description request from terminal for %s\n", sub_url)); - for (i=0; i<gf_dash_get_group_count(mpdin->dash); i++) { - GF_Descriptor *desc; - GF_MPDGroup *mudta; -#if 0 - if (!gf_dash_is_group_selected(mpdin->dash, i)) - continue; -#endif - mudta = gf_dash_get_group_udta(mpdin->dash, i); - if (!mudta) continue; - if (mudta->service_descriptor_fetched) continue; - - desc = mudta->segment_ifce->GetServiceDescriptor(mudta->segment_ifce, expect_type, sub_url); - if (desc) mudta->service_descriptor_fetched = 1; - gf_odf_desc_del(desc); - } - return NULL; -} - - -GF_Err MPD_CloseService(GF_InputService *plug) -{ - GF_MPD_In *mpdin = (GF_MPD_In*) plug->priv; - assert(mpdin); - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] Received Close Service (%p) request from terminal\n", mpdin->service)); - - mpdin->closed = 1; - - if (mpdin->dash) - gf_dash_close(mpdin->dash); - - gf_service_disconnect_ack(mpdin->service, NULL, GF_OK); - - return GF_OK; -} - -GF_Err MPD_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) -{ - s32 idx; - GF_Err e; - GF_MPD_In *mpdin = (GF_MPD_In*) plug->priv; - GF_InputService *segment_ifce = NULL; - - if (!plug || !plug->priv || !com) return GF_SERVICE_ERROR; - - segment_ifce = MPD_GetInputServiceForChannel(mpdin, com->base.on_channel); - - switch (com->command_type) { - case GF_NET_SERVICE_INFO: - { - s32 idx; - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] Received Info command from terminal on Service (%p)\n", mpdin->service)); - - e = GF_OK; - if (segment_ifce) { - /* defer to the real input service */ - e = segment_ifce->ServiceCommand(segment_ifce, com); - } - - if (e!= GF_OK || !com->info.name || 2 > strlen(com->info.name)) { - gf_dash_get_info(mpdin->dash, &com->info.name, &com->info.comment); - } - idx = MPD_GetGroupIndexForChannel(mpdin, com->play.on_channel); - if (idx>=0) { - //gather role and co - if (!com->info.role) { - gf_dash_group_enum_descriptor(mpdin->dash, idx, GF_MPD_DESC_ROLE, 0, NULL, NULL, &com->info.role); - } - if (!com->info.accessibility) { - gf_dash_group_enum_descriptor(mpdin->dash, idx, GF_MPD_DESC_ACCESSIBILITY, 0, NULL, NULL, &com->info.accessibility); - } - if (!com->info.rating) { - gf_dash_group_enum_descriptor(mpdin->dash, idx, GF_MPD_DESC_RATING, 0, NULL, NULL, &com->info.rating); - } - } - return GF_OK; - } - /*we could get it from MPD*/ - case GF_NET_SERVICE_HAS_AUDIO: - case GF_NET_SERVICE_FLUSH_DATA: - if (segment_ifce) { - /* defer to the real input service */ - return segment_ifce->ServiceCommand(segment_ifce, com); - } - return GF_NOT_SUPPORTED; - - case GF_NET_SERVICE_HAS_FORCED_VIDEO_SIZE: - com->par.width = mpdin->use_max_res ? mpdin->width : 0; - com->par.height = mpdin->use_max_res ? mpdin->height : 0; - return GF_OK; - - case GF_NET_SERVICE_QUALITY_SWITCH: - if (com->switch_quality.set_tile_mode_plus_one) { - GF_BaseInterface *pl = (GF_BaseInterface *)plug; - GF_DASHTileAdaptationMode tile_mode = com->switch_quality.set_tile_mode_plus_one - 1; - gf_dash_set_tile_adaptation_mode(mpdin->dash, tile_mode, 100); - - switch (tile_mode) { - case GF_DASH_ADAPT_TILE_ROWS: gf_modules_set_option(pl, "DASH", "TileAdaptation", "rows"); break; - case GF_DASH_ADAPT_TILE_ROWS_REVERSE: gf_modules_set_option(pl, "DASH", "TileAdaptation", "reverseRows"); break; - case GF_DASH_ADAPT_TILE_ROWS_MIDDLE: gf_modules_set_option(pl, "DASH", "TileAdaptation", "middleRows"); break; - case GF_DASH_ADAPT_TILE_COLUMNS: gf_modules_set_option(pl, "DASH", "TileAdaptation", "columns"); break; - case GF_DASH_ADAPT_TILE_COLUMNS_REVERSE: gf_modules_set_option(pl, "DASH", "TileAdaptation", "reverseColumns"); break; - case GF_DASH_ADAPT_TILE_COLUMNS_MIDDLE: gf_modules_set_option(pl, "DASH", "TileAdaptation", "middleColumns"); break; - case GF_DASH_ADAPT_TILE_CENTER: gf_modules_set_option(pl, "DASH", "TileAdaptation", "center"); break; - case GF_DASH_ADAPT_TILE_EDGES: gf_modules_set_option(pl, "DASH", "TileAdaptation", "edges"); break; - case GF_DASH_ADAPT_TILE_NONE: - default: - gf_modules_set_option(pl, "DASH", "TileAdaptation", "none"); - break; - } - } else if (com->switch_quality.set_auto) { - gf_dash_set_automatic_switching(mpdin->dash, 1); - } else if (com->base.on_channel) { - segment_ifce = MPD_GetInputServiceForChannel(mpdin, com->base.on_channel); - if (!segment_ifce) return GF_NOT_SUPPORTED; - idx = MPD_GetGroupIndexForChannel(mpdin, com->play.on_channel); - if (idx < 0) return GF_BAD_PARAM; - - gf_dash_group_set_quality_degradation_hint(mpdin->dash, idx, com->switch_quality.quality_degradation); - if (! com->switch_quality.ID) return GF_OK; - - if (com->switch_quality.dependent_group_index) { - if (com->switch_quality.dependent_group_index > gf_dash_group_get_num_groups_depending_on(mpdin->dash, idx)) - return GF_BAD_PARAM; - - idx = gf_dash_get_dependent_group_index(mpdin->dash, idx, com->switch_quality.dependent_group_index-1); - if (idx==-1) return GF_BAD_PARAM; - } - - gf_dash_set_automatic_switching(mpdin->dash, 0); - gf_dash_group_select_quality(mpdin->dash, idx, com->switch_quality.ID); - } else { - gf_dash_switch_quality(mpdin->dash, com->switch_quality.up, mpdin->immediate_switch); - } - return GF_OK; - - case GF_NET_GET_TIMESHIFT: - com->timeshift.time = gf_dash_get_timeshift_buffer_pos(mpdin->dash); - return GF_OK; - case GF_NET_SERVICE_CAN_REVERSE_PLAYBACK: - return mpdin_dash_can_reverse_playback(mpdin); - - case GF_NET_ASSOCIATED_CONTENT_TIMING: - gf_dash_override_ntp(mpdin->dash, com->addon_time.ntp); - return GF_OK; - default: - break; - } - /*not supported*/ - if (!com->base.on_channel) return GF_NOT_SUPPORTED; - - segment_ifce = MPD_GetInputServiceForChannel(mpdin, com->base.on_channel); - if (!segment_ifce) return GF_NOT_SUPPORTED; - - switch (com->command_type) { - case GF_NET_CHAN_INTERACTIVE: - /* TODO - we are interactive if not live without timeshift */ - return GF_OK; - - case GF_NET_CHAN_GET_SRD: - { - Bool res; - idx = MPD_GetGroupIndexForChannel(mpdin, com->base.on_channel); - if (idx < 0) return GF_BAD_PARAM; - if (com->srd.dependent_group_index) { - if (com->srd.dependent_group_index > gf_dash_group_get_num_groups_depending_on(mpdin->dash, idx)) - return GF_BAD_PARAM; - - idx = gf_dash_get_dependent_group_index(mpdin->dash, idx, com->srd.dependent_group_index-1); - } - res = gf_dash_group_get_srd_info(mpdin->dash, idx, NULL, &com->srd.x, &com->srd.y, &com->srd.w, &com->srd.h, &com->srd.width, &com->srd.height); - return res ? GF_OK : GF_NOT_SUPPORTED; - } - - case GF_NET_GET_STATS: - { - idx = MPD_GetGroupIndexForChannel(mpdin, com->base.on_channel); - if (idx < 0) return GF_BAD_PARAM; - com->net_stats.bw_down = 8 * gf_dash_group_get_download_rate(mpdin->dash, idx); - } - return GF_OK; - - case GF_NET_SERVICE_QUALITY_QUERY: - { - GF_DASHQualityInfo qinfo; - GF_Err e; - u32 count, g_idx; - idx = MPD_GetGroupIndexForChannel(mpdin, com->quality_query.on_channel); - if (idx < 0) return GF_BAD_PARAM; - count = gf_dash_group_get_num_qualities(mpdin->dash, idx); - if (!com->quality_query.index && !com->quality_query.dependent_group_index) { - com->quality_query.index = count; - com->quality_query.dependent_group_index = gf_dash_group_get_num_groups_depending_on(mpdin->dash, idx); - return GF_OK; - } - if (com->quality_query.dependent_group_index) { - if (com->quality_query.dependent_group_index > gf_dash_group_get_num_groups_depending_on(mpdin->dash, idx)) - return GF_BAD_PARAM; - - g_idx = gf_dash_get_dependent_group_index(mpdin->dash, idx, com->quality_query.dependent_group_index-1); - if (g_idx==(u32)-1) return GF_BAD_PARAM; - count = gf_dash_group_get_num_qualities(mpdin->dash, g_idx); - if (com->quality_query.index>count) return GF_BAD_PARAM; - } else { - if (com->quality_query.index>count) return GF_BAD_PARAM; - g_idx = idx; - } - - e = gf_dash_group_get_quality_info(mpdin->dash, g_idx, com->quality_query.index-1, &qinfo); - if (e) return e; - - com->quality_query.bandwidth = qinfo.bandwidth; - com->quality_query.ID = qinfo.ID; - com->quality_query.mime = qinfo.mime; - com->quality_query.codec = qinfo.codec; - com->quality_query.width = qinfo.width; - com->quality_query.height = qinfo.height; - com->quality_query.interlaced = qinfo.interlaced; - if (qinfo.fps_den) { - com->quality_query.fps = qinfo.fps_num; - com->quality_query.fps /= qinfo.fps_den; - } else { - com->quality_query.fps = qinfo.fps_num; - } - com->quality_query.par_num = qinfo.par_num; - com->quality_query.par_den = qinfo.par_den; - com->quality_query.sample_rate = qinfo.sample_rate; - com->quality_query.nb_channels = qinfo.nb_channels; - com->quality_query.disabled = qinfo.disabled; - com->quality_query.is_selected = qinfo.is_selected; - com->quality_query.automatic = gf_dash_get_automatic_switching(mpdin->dash); - com->quality_query.tile_adaptation_mode = (u32) gf_dash_get_tile_adaptation_mode(mpdin->dash); - return GF_OK; - } - case GF_NET_CHAN_VISIBILITY_HINT: - idx = MPD_GetGroupIndexForChannel(mpdin, com->base.on_channel); - if (idx < 0) return GF_BAD_PARAM; - - return gf_dash_group_set_visible_rect(mpdin->dash, idx, com->visibility_hint.min_x, com->visibility_hint.max_x, com->visibility_hint.min_y, com->visibility_hint.max_y); - - case GF_NET_CHAN_BUFFER: - /*get it from MPD minBufferTime - if not in low latency mode, indicate the value given in MPD (not possible to fetch segments earlier) - to be more precise we should get the min segment duration for this group*/ - if (/* !mpdin->use_low_latency && */ (mpdin->buffer_mode>=MPDIN_BUFFER_MIN)) { - u32 max = gf_dash_get_min_buffer_time(mpdin->dash); - if (max>com->buffer.max) - com->buffer.max = max; - - if (!com->buffer.min && ! gf_dash_is_dynamic_mpd(mpdin->dash)) { - com->buffer.min = 1; - } - } - idx = MPD_GetGroupIndexForChannel(mpdin, com->play.on_channel); - if (idx >= 0) { - gf_dash_group_set_max_buffer_playout(mpdin->dash, idx, com->buffer.max); - } - return GF_OK; - - case GF_NET_CHAN_DURATION: - com->duration.duration = gf_dash_get_duration(mpdin->dash); - idx = MPD_GetGroupIndexForChannel(mpdin, com->play.on_channel); - if (idx >= 0) { - com->duration.time_shift_buffer = gf_dash_group_get_time_shift_buffer_depth(mpdin->dash, idx); - } - return GF_OK; - - case GF_NET_CHAN_PLAY: - - idx = MPD_GetGroupIndexForChannel(mpdin, com->play.on_channel); - if (idx < 0) return GF_BAD_PARAM; - - //adjust play range from media timestamps to MPD time - if (com->play.timestamp_based) { - u32 timescale; - u64 pto; - Double offset; - GF_MPDGroup *group = gf_dash_get_group_udta(mpdin->dash, idx); - - if (com->play.timestamp_based==1) { - gf_dash_group_get_presentation_time_offset(mpdin->dash, idx, &pto, ×cale); - offset = (Double) pto; - offset /= timescale; - com->play.start_range -= offset; - if (com->play.start_range < 0) com->play.start_range = 0; - } - - group->is_timestamp_based = 1; - group->pto_setup = 0; - mpdin->media_start_range = com->play.start_range; - } else { - GF_MPDGroup *group = gf_dash_get_group_udta(mpdin->dash, idx); - group->is_timestamp_based = 0; - group->pto_setup = 0; - if (com->play.start_range<0) com->play.start_range = 0; - //in m3u8, we need also media start time for mapping time - if (gf_dash_is_m3u8(mpdin->dash)) - mpdin->media_start_range = com->play.start_range; - } - - //we cannot handle seek request outside of a period being setup, this messes up our internal service setup - //we postpone the seek and will request it later on ... - if (gf_dash_in_period_setup(mpdin->dash)) { - u64 p_end = gf_dash_get_period_duration(mpdin->dash); - if (p_end) { - p_end += gf_dash_get_period_start(mpdin->dash); - if (p_end<1000*com->play.start_range) { - mpdin->seek_request = com->play.start_range; - return GF_OK; - } - } - } - - - gf_dash_set_speed(mpdin->dash, com->play.speed); - - /*don't seek if this command is the first PLAY request of objects declared by the subservice, unless start range is not default one (0) */ - if (!mpdin->nb_playing && (!com->play.initial_broadcast_play || (com->play.start_range>1.0) )) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] Received Play command from terminal on channel %p on Service (%p)\n", com->base.on_channel, mpdin->service)); - - if (com->play.end_range<=0) { - u32 ms = (u32) ( 1000 * (-com->play.end_range) ); - if (ms<1000) ms = 0; - gf_dash_set_timeshift(mpdin->dash, ms); - } - gf_dash_seek(mpdin->dash, com->play.start_range); - - //to remove once we manage to keep the service alive - /*don't forward commands if a switch of period is to be scheduled, we are killing the service anyway ...*/ - if (gf_dash_get_period_switch_status(mpdin->dash)) return GF_OK; - } - - //check if current segment playback should be aborted - com->play.dash_segment_switch = gf_dash_group_segment_switch_forced(mpdin->dash, idx); - - gf_dash_group_select(mpdin->dash, idx, GF_TRUE); - gf_dash_set_group_done(mpdin->dash, (u32) idx, 0); - - //adjust start range from MPD time to media time - { - u64 pto; - u32 timescale; - com->play.start_range = gf_dash_group_get_start_range(mpdin->dash, idx); - gf_dash_group_get_presentation_time_offset(mpdin->dash, idx, &pto, ×cale); - com->play.start_range += ((Double)pto) / timescale; - } - - mpdin->nb_playing++; - return segment_ifce->ServiceCommand(segment_ifce, com); - - case GF_NET_CHAN_STOP: - { - s32 idx = MPD_GetGroupIndexForChannel(mpdin, com->play.on_channel); - if (idx>=0) { - gf_dash_set_group_done(mpdin->dash, (u32) idx, 1); - } - if (mpdin->nb_playing) - mpdin->nb_playing--; - } - return segment_ifce->ServiceCommand(segment_ifce, com); - - /*we could get it from MPD*/ - case GF_NET_CHAN_GET_PIXEL_AR: - /* defer to the real input service */ - return segment_ifce->ServiceCommand(segment_ifce, com); - - case GF_NET_CHAN_SET_SPEED: - gf_dash_set_speed(mpdin->dash, com->play.speed); - return segment_ifce->ServiceCommand(segment_ifce, com); - - default: - return segment_ifce->ServiceCommand(segment_ifce, com); - } -} - -GF_Err MPD_ChannelGetSLP(GF_InputService *plug, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, GF_SLHeader *out_sl_hdr, Bool *sl_compressed, GF_Err *out_reception_status, Bool *is_new_data) -{ - GF_MPD_In *mpdin = (GF_MPD_In*) plug->priv; - GF_InputService *segment_ifce = MPD_GetInputServiceForChannel(mpdin, channel); - if (!plug || !plug->priv || !segment_ifce) return GF_SERVICE_ERROR; - return segment_ifce->ChannelGetSLP(segment_ifce, channel, out_data_ptr, out_data_size, out_sl_hdr, sl_compressed, out_reception_status, is_new_data); -} - -GF_Err MPD_ChannelReleaseSLP(GF_InputService *plug, LPNETCHANNEL channel) -{ - GF_MPD_In *mpdin = (GF_MPD_In*) plug->priv; - GF_InputService *segment_ifce = MPD_GetInputServiceForChannel(mpdin, channel); - if (!plug || !plug->priv || !segment_ifce) return GF_SERVICE_ERROR; - return segment_ifce->ChannelReleaseSLP(segment_ifce, channel); -} - -Bool MPD_CanHandleURLInService(GF_InputService *plug, const char *url) -{ - /** - * May arrive when using pid:// URLs into a TS stream - */ - GF_MPD_In *mpdin = (GF_MPD_In*) plug->priv; - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD_IN] Received Can Handle URL In Service (%p) request from terminal for %s\n", mpdin->service, url)); - if (!plug || !plug->priv || !mpdin->dash) return GF_FALSE; - if (gf_dash_get_url(mpdin->dash) && !strcmp(gf_dash_get_url(mpdin->dash) , url)) { - return 1; - } else { - GF_MPDGroup *mudta; - u32 i; - for (i=0; i<gf_dash_get_group_count(mpdin->dash); i++) { - if (!gf_dash_is_group_selected(mpdin->dash, i)) continue; - - mudta = gf_dash_get_group_udta(mpdin->dash, i); - if (mudta && mudta->segment_ifce && mudta->segment_ifce->CanHandleURLInService) { - return mudta->segment_ifce->CanHandleURLInService(plug, url); - } - } - return 0; - } -} - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { - GF_NET_CLIENT_INTERFACE, - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - GF_MPD_In *mpdin; - GF_InputService *plug; - if (InterfaceType != GF_NET_CLIENT_INTERFACE) return NULL; - - GF_SAFEALLOC(plug, GF_InputService); - if (!plug) return NULL; - GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC MPD Loader", "gpac distribution") - - GF_SAFEALLOC(mpdin, GF_MPD_In); - if (!mpdin) { - gf_free(plug); - return NULL; - } - plug->priv = mpdin; - mpdin->plug = plug; - - plug->RegisterMimeTypes = MPD_RegisterMimeTypes; - plug->CanHandleURL = MPD_CanHandleURL; - plug->ConnectService = MPD_ConnectService; - plug->CloseService = MPD_CloseService; - plug->GetServiceDescriptor = MPD_GetServiceDesc; - plug->ConnectChannel = MPD_ConnectChannel; - plug->DisconnectChannel = MPD_DisconnectChannel; - plug->ServiceCommand = MPD_ServiceCommand; - plug->CanHandleURLInService = MPD_CanHandleURLInService; - plug->ChannelGetSLP = MPD_ChannelGetSLP; - plug->ChannelReleaseSLP = MPD_ChannelReleaseSLP; - - return (GF_BaseInterface *)plug; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *bi) -{ - GF_MPD_In *mpdin; - - if (bi->InterfaceType!=GF_NET_CLIENT_INTERFACE) return; - - mpdin = (GF_MPD_In*) ((GF_InputService*)bi)->priv; - assert(mpdin); - - if (mpdin->dash) - gf_dash_del(mpdin->dash); - - gf_free(mpdin); - gf_free(bi); -} - - -GPAC_MODULE_STATIC_DECLARATION( mpd_in ) - -#endif //GPAC_DISABLE_DASH_CLIENT diff --git a/modules/mpegts_in/Makefile b/modules/mpegts_in/Makefile deleted file mode 100644 index e7b0638..0000000 --- a/modules/mpegts_in/Makefile +++ /dev/null @@ -1,50 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/mpegts_in - -CFLAGS= $(OPTFLAGS) -w -I"$(SRC_PATH)/include" - - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -DDEBUG -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS=mpegts_in.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_mpegts_in$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols mpegts_in.def -endif - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_mpegts_in-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac_static $(LDFLAGS) -endif - - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/mpegts_in/mpegts_in.c b/modules/mpegts_in/mpegts_in.c deleted file mode 100644 index a0e6f07..0000000 --- a/modules/mpegts_in/mpegts_in.c +++ /dev/null @@ -1,1750 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre, Cyril Concolato, Romain Bouqueau - * Copyright (c) Telecom ParisTech 2005-2012 - * All rights reserved - * - * This file is part of GPAC / M2TS reader module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include <gpac/modules/service.h> -#include <gpac/modules/codec.h> -#include <gpac/mpegts.h> -#include <gpac/thread.h> -#include <gpac/network.h> -#include <gpac/constants.h> - -#ifndef GPAC_DISABLE_MPEG2TS - -static const char * MIMES[] = { "video/mpeg-2", "video/mp2t", "video/mpeg", NULL}; -static const char * M2TS_EXTENSIONS = "ts m2t mts dmb trp"; - -//when regulating data rate from file using PCR, this is the maximum sleep we tolerate -#define M2TS_MAX_SLEEP 200 - -typedef struct { - char *fragment; - u32 id; - /*if only pid is requested*/ - u32 pid; -} M2TSIn_Prog; - -typedef struct -{ - GF_M2TS_Demuxer *ts; - - GF_InputService *owner; - - GF_ClientService *service; - - Bool ts_setup; - Bool request_all_pids; - Bool is_connected; - Bool low_latency_mode; - Bool in_segment_download, first_chunk_in_seg_parsed; - Bool first_segment_fetched; - - Bool epg_requested; - Bool has_eit; - LPNETCHANNEL eit_channel; - - GF_Mutex *mx; - - Bool mpeg4on2_scene_only; - char * network_buffer; - u32 network_buffer_size; - - /*pick first pcr pid for regulation*/ - u32 regulation_pcr_pid; - - Bool skip_regulation; - Bool has_pending_segments; - - Bool in_data_flush; - - Bool hybrid_on; - - Bool flush_sdt; - - u64 pcr_last; - u32 stb_at_last_pcr; - - u32 nb_paused; - Bool file_regulate; - u32 nb_playing; - u32 nb_prog_to_setup; - - u32 map_media_time_on_prog_id; - Double media_start_range; - - const char *force_temi_url; -} M2TSIn; - - -static void M2TS_GetNetworkType(GF_InputService *plug,M2TSIn *reader); - -static Bool M2TS_CanHandleURL(GF_InputService *plug, const char *url) -{ - char *sExt; - if (!plug || !url) - return 0; - if (!strnicmp(url, "udp://", 6) - || !strnicmp(url, "mpegts-udp://", 13) - || !strnicmp(url, "mpegts-tcp://", 13) -#ifdef GPAC_HAS_LINUX_DVB - || !strnicmp(url, "dvb://", 6) -#endif - ) { - return 1; - } - - sExt = strrchr(url, '.'); - { - int i=0; - for (i = 0 ; NULL != MIMES[i]; i++) - if (gf_service_check_mime_register(plug, MIMES[i], M2TS_EXTENSIONS, "MPEG-2 TS", sExt)) - return 1; - } - return 0; -} - -static Bool M2TS_CanHandleURLInService(GF_InputService *plug, const char *url) -{ - Bool ret = 0; - M2TSIn *m2ts; - - if (!plug || !url) - return 0; - m2ts = (M2TSIn *)plug->priv; - if (!m2ts) - return 0; - - if (!strnicmp(url, "pid://", 6)) { - u32 pid = atoi(url+6); - if (pid>=GF_M2TS_MAX_STREAMS) return 0; - if (m2ts->ts->ess[pid]) return 1; - return 0; - } - -#ifdef GPAC_HAS_LINUX_DVB - if (!stricmp(url, "dvb://EPG")) return 1; - if (!strnicmp(url, "dvb://", 6)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[DVBIn] Checking reuse of the same tuner for %s\n", url)); - const char *chan_conf = gf_modules_get_option((GF_BaseInterface *)plug, "DVB", "ChannelsFile"); - if (!chan_conf) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[DVBIn] Cannot locate channel configuration file\n")); - ret = 0; - } - - /* if the tuner is already tuned to the same frequence, nothing needs to be done */ - else if (m2ts->ts->tuner->freq != 0) { - char *frag = strchr(url, '#'); - if (frag) frag[0] = 0; - if (m2ts->ts->tuner->freq == gf_dvb_get_freq_from_url(chan_conf, url)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[DVBIn] Reusing the same tuner for %s\n", url)); - ret = 1; - } - if (frag) frag[0] = '#'; - } - } else -#endif - if (!strnicmp(url, "udp://", 6) - || !strnicmp(url, "mpegts-udp://", 13) - || !strnicmp(url, "mpegts-tcp://", 13)) - { - /* TODO: check IP address ...*/ - ret = 0; - } else { - char *frag = strchr(url, '#'); - if (frag) frag[0] = 0; - if (!strlen(url) || !strcmp(url, m2ts->ts->filename)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[DVBIn] Reusing the same input file for %s\n", url)); - ret = 1; - } - if (frag) frag[0] = '#'; - } - return ret; -} - -static GF_ESD *MP2TS_GetESD(M2TSIn *m2ts, GF_M2TS_PES *stream, char *dsi, u32 dsi_size) -{ - GF_ESD *esd; - - esd = gf_odf_desc_esd_new(0); - esd->ESID = stream->mpeg4_es_id ? stream->mpeg4_es_id : stream->pid; - - switch (stream->stream_type) { - case GF_M2TS_VIDEO_MPEG1: - esd->decoderConfig->streamType = GF_STREAM_VISUAL; - esd->decoderConfig->objectTypeIndication = GPAC_OTI_VIDEO_MPEG1; - break; - case GF_M2TS_VIDEO_MPEG2: - case GF_M2TS_VIDEO_DCII: - esd->decoderConfig->streamType = GF_STREAM_VISUAL; - esd->decoderConfig->objectTypeIndication = GPAC_OTI_VIDEO_MPEG2_422; - break; - case GF_M2TS_VIDEO_MPEG4: - esd->decoderConfig->streamType = GF_STREAM_VISUAL; - esd->decoderConfig->objectTypeIndication = GPAC_OTI_VIDEO_MPEG4_PART2; - break; - case GF_M2TS_VIDEO_H264: - esd->decoderConfig->streamType = GF_STREAM_VISUAL; - esd->decoderConfig->objectTypeIndication = GPAC_OTI_VIDEO_AVC; - break; - case GF_M2TS_VIDEO_SVC: - esd->decoderConfig->streamType = GF_STREAM_VISUAL; - esd->decoderConfig->objectTypeIndication = GPAC_OTI_VIDEO_SVC; - break; - case GF_M2TS_VIDEO_HEVC: - esd->decoderConfig->streamType = GF_STREAM_VISUAL; - esd->decoderConfig->objectTypeIndication = GPAC_OTI_VIDEO_HEVC; - break; - case GF_M2TS_VIDEO_SHVC: - case GF_M2TS_VIDEO_SHVC_TEMPORAL: - case GF_M2TS_VIDEO_MHVC: - case GF_M2TS_VIDEO_MHVC_TEMPORAL: - esd->decoderConfig->streamType = GF_STREAM_VISUAL; - esd->decoderConfig->objectTypeIndication = GPAC_OTI_VIDEO_LHVC; - break; - case GF_M2TS_AUDIO_MPEG1: - esd->decoderConfig->streamType = GF_STREAM_AUDIO; - esd->decoderConfig->objectTypeIndication = GPAC_OTI_AUDIO_MPEG1; - break; - case GF_M2TS_AUDIO_MPEG2: - esd->decoderConfig->streamType = GF_STREAM_AUDIO; - esd->decoderConfig->objectTypeIndication = GPAC_OTI_AUDIO_MPEG2_PART3; - break; - case GF_M2TS_AUDIO_LATM_AAC: - case GF_M2TS_AUDIO_AAC: - case GPAC_OTI_AUDIO_AAC_MPEG2_MP: - case GPAC_OTI_AUDIO_AAC_MPEG2_LCP: - case GPAC_OTI_AUDIO_AAC_MPEG2_SSRP: - if (!dsi) { - /*turn on parsing to get AAC config - NB: we removed "no file regulation" since we may get broken files where PMT declares an AAC stream but no AAC PID is in the MUX - (filtered out). In this case, "no regulation" will make the entire TS to be read as fast as possible - */ - if (m2ts->ts->file) { - m2ts->file_regulate = 2; - gf_m2ts_pause_demux(m2ts->ts, 0); - } - gf_m2ts_set_pes_framing(stream, GF_M2TS_PES_FRAMING_DEFAULT); - gf_odf_desc_del((GF_Descriptor *)esd); - return NULL; - } - esd->decoderConfig->streamType = GF_STREAM_AUDIO; - esd->decoderConfig->objectTypeIndication = GPAC_OTI_AUDIO_AAC_MPEG4; - break; - case GF_M2TS_AUDIO_AC3: - esd->decoderConfig->streamType = GF_STREAM_AUDIO; - esd->decoderConfig->objectTypeIndication = GPAC_OTI_AUDIO_AC3; - break; - case GF_M2TS_AUDIO_EC3: - esd->decoderConfig->streamType = GF_STREAM_AUDIO; - esd->decoderConfig->objectTypeIndication = GPAC_OTI_AUDIO_EAC3; - break; - case GF_M2TS_SYSTEMS_MPEG4_SECTIONS: - default: - gf_odf_desc_del((GF_Descriptor *)esd); - return NULL; - } - esd->decoderConfig->bufferSizeDB = 0; - - /*we only use AUstart indicator*/ - esd->slConfig->useAccessUnitStartFlag = 1; - esd->slConfig->useAccessUnitEndFlag = 0; - esd->slConfig->useRandomAccessPointFlag = 1; - esd->slConfig->AUSeqNumLength = 0; - esd->slConfig->timestampResolution = 90000; - - /*ASSIGN PCR here*/ - esd->OCRESID = stream->program->pcr_pid; - if (stream->pid == stream->program->pcr_pid) { - esd->slConfig->OCRResolution = 27000000; - } - - /*decoder config*/ - if (dsi) { - esd->decoderConfig->decoderSpecificInfo->data = gf_malloc(sizeof(char)*dsi_size); - memcpy(esd->decoderConfig->decoderSpecificInfo->data, dsi, sizeof(char)*dsi_size); - esd->decoderConfig->decoderSpecificInfo->dataLength = dsi_size; - } - return esd; -} - -static GF_ObjectDescriptor *MP2TS_GetOD(M2TSIn *m2ts, GF_M2TS_PES *stream, char *dsi, u32 dsi_size, u32 *streamType) -{ - GF_ObjectDescriptor *od; - GF_ESD *esd; - u32 cur_ES, i, count; - - /*create a stream description for this channel*/ - esd = MP2TS_GetESD(m2ts, stream, dsi, dsi_size); - if (!esd) return NULL; - - if (stream->program->is_scalable) - esd->has_scalable_layers = GF_TRUE; - - /*declare object to terminal*/ - od = (GF_ObjectDescriptor*)gf_odf_desc_new(GF_ODF_OD_TAG); - gf_list_add(od->ESDescriptors, esd); - od->objectDescriptorID = 0; - if (streamType) *streamType = esd->decoderConfig->streamType; - /*remember program number for service/program selection*/ - od->ServiceID = stream->program->number; - od->service_ifce = m2ts->owner; - - /*create a stream description for the channels depending on this channel, set esd->dependsOnESID and add to od*/ - cur_ES = esd->ESID; - count = gf_list_count(stream->program->streams); - for (i = 0; i < count; i++) { - GF_M2TS_ES *es = (GF_M2TS_ES *)gf_list_get(stream->program->streams, i); - if ((es->flags & GF_M2TS_ES_IS_PES) && ( ((GF_M2TS_PES *)es)->depends_on_pid == cur_ES)) { - GF_ESD *the_esd; - the_esd = MP2TS_GetESD(m2ts, (GF_M2TS_PES *)es, dsi, dsi_size); - if (the_esd) { - if ((esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_AVC) && (the_esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_LHVC)) { - esd->has_scalable_layers = GF_FALSE; - } - the_esd->has_scalable_layers = GF_TRUE; - - the_esd->dependsOnESID = cur_ES; - gf_list_add(od->ESDescriptors, the_esd); - } - cur_ES = the_esd->ESID; - } - } - - return od; -} - -static void MP2TS_DeclareStream(M2TSIn *m2ts, GF_M2TS_PES *stream, char *dsi, u32 dsi_size) -{ - GF_ObjectDescriptor *od = MP2TS_GetOD(m2ts, stream, dsi, dsi_size, NULL); - if (!od) return; - /*declare but don't regenerate scene*/ - gf_service_declare_media(m2ts->service, (GF_Descriptor*)od, 1); -} - -static void MP2TS_SetupProgram(M2TSIn *m2ts, GF_M2TS_Program *prog, Bool regenerate_scene, Bool no_declare) -{ - u32 i, count; - - count = gf_list_count(prog->streams); -#ifdef GPAC_HAS_LINUX_DVB - if (m2ts->ts->tuner) { - Bool found = 0; - for (i=0; i<count; i++) { - GF_M2TS_PES *pes = gf_list_get(prog->streams, i); - if (pes->pid==m2ts->ts->tuner->vpid) found = 1; - else if (pes->pid==m2ts->ts->tuner->apid) found = 1; - } - if (!found) return; - } -#endif - - /*TS is a file, start regulation regardless of how the TS is access (with or without fragment URI)*/ - if (m2ts->ts->file || m2ts->ts->dnload) { - m2ts->file_regulate = 1; - if (!m2ts->nb_playing) gf_m2ts_pause_demux(m2ts->ts, 1); - } - - for (i=0; i<count; i++) { - GF_M2TS_ES *es = gf_list_get(prog->streams, i); - if (es->pid==prog->pmt_pid) continue; - if (((GF_M2TS_PES *)es)->depends_on_pid ) { - prog->is_scalable = GF_TRUE; - break; - } - } - - for (i=0; i<count; i++) { - GF_M2TS_ES *es = gf_list_get(prog->streams, i); - if (es->pid==prog->pmt_pid) continue; - if ((es->flags & GF_M2TS_ES_IS_PES) && ((GF_M2TS_PES *)es)->depends_on_pid ) - continue; - - /*move to skip mode for all ES until asked for playback*/ - if (!es->user) - gf_m2ts_set_pes_framing((GF_M2TS_PES *)es, GF_M2TS_PES_FRAMING_SKIP); - - if (!prog->pmt_iod && !no_declare) { - if (! (es->flags & GF_M2TS_ES_ALREADY_DECLARED)) { - MP2TS_DeclareStream(m2ts, (GF_M2TS_PES *)es, NULL, 0); - es->flags |= GF_M2TS_ES_ALREADY_DECLARED; - } - } - /*if IOD, streams not declared through OD framework are refered to by pid:// scheme, and will be declared upon - request by the terminal through GetServiceDesc*/ - } - - /*force scene regeneration*/ - if (!prog->pmt_iod && regenerate_scene) - gf_service_declare_media(m2ts->service, NULL, 0); - - - if (m2ts->nb_prog_to_setup) { - m2ts->nb_prog_to_setup--; - } -} - -static void MP2TS_SendPacket(M2TSIn *m2ts, GF_M2TS_PES_PCK *pck) -{ - GF_SLHeader slh; - - /*pcr not initialized, don't send any data*/ - if (! pck->stream->program->first_dts) return; - if (!pck->stream->user) return; - - memset(&slh, 0, sizeof(GF_SLHeader)); - slh.accessUnitStartFlag = (pck->flags & GF_M2TS_PES_PCK_AU_START) ? 1 : 0; - if (slh.accessUnitStartFlag) { - slh.compositionTimeStampFlag = 1; - slh.compositionTimeStamp = pck->PTS; - if (pck->DTS != pck->PTS) { - slh.decodingTimeStampFlag = 1; - slh.decodingTimeStamp = pck->DTS; - } - slh.randomAccessPointFlag = (pck->flags & GF_M2TS_PES_PCK_RAP) ? 1 : 0; - } - gf_service_send_packet(m2ts->service, pck->stream->user, pck->data, pck->data_len, &slh, GF_OK); - -} - -static GFINLINE void MP2TS_SendSLPacket(M2TSIn *m2ts, GF_M2TS_SL_PCK *pck) -{ - GF_SLHeader SLHeader, *slh = NULL; - u32 SLHdrLen = 0; - - /*build a SL Header*/ - if (((GF_M2TS_ES*)pck->stream)->slcfg) { - gf_sl_depacketize(((GF_M2TS_ES*)pck->stream)->slcfg, &SLHeader, pck->data, pck->data_len, &SLHdrLen); - SLHeader.m2ts_version_number_plus_one = pck->version_number + 1; - slh = &SLHeader; - } - gf_service_send_packet(m2ts->service, pck->stream->user, pck->data+SLHdrLen, pck->data_len-SLHdrLen, slh, GF_OK); -} - -static GF_ObjectDescriptor *M2TS_GenerateEPG_OD(M2TSIn *m2ts) -{ - /* declaring a special stream for displaying eit */ - GF_ObjectDescriptor *od; - GF_ESD *esd; - - /*create a stream description for this channel*/ - esd = gf_odf_desc_esd_new(0); - esd->ESID = GF_M2TS_PID_EIT_ST_CIT; - esd->OCRESID = GF_M2TS_PID_EIT_ST_CIT; - esd->decoderConfig->streamType = GF_STREAM_PRIVATE_SCENE; - esd->decoderConfig->objectTypeIndication = GPAC_OTI_PRIVATE_SCENE_EPG; - esd->decoderConfig->bufferSizeDB = 0; - - /*we only use AUstart indicator*/ - esd->slConfig->useAccessUnitStartFlag = 1; - esd->slConfig->useRandomAccessPointFlag = 1; - esd->slConfig->timestampResolution = 90000; - - /*declare object to terminal*/ - od = (GF_ObjectDescriptor*)gf_odf_desc_new(GF_ODF_OD_TAG); - gf_list_add(od->ESDescriptors, esd); - od->objectDescriptorID = 0; - od->service_ifce = m2ts->owner; - return od; -} - -static void M2TS_FlushRequested(M2TSIn *m2ts) -{ - u32 i, j, req_prog_count, count, prog_id, found; - - gf_mx_p(m2ts->mx); - - count = gf_list_count(m2ts->ts->programs); - for (j=0; j<count; j++) { - GF_M2TS_Program *ts_prog = gf_list_get(m2ts->ts->programs, j); - //PMT not yet received, do not allow playback regulation - if (!ts_prog->pcr_pid) { - m2ts->file_regulate = 0; - gf_m2ts_pause_demux(m2ts->ts, 0); - gf_mx_v(m2ts->mx); - return; - } - } - - - found = 0; - count = gf_list_count(m2ts->ts->requested_pids); - for (i=0; i<count; i++) { - M2TSIn_Prog *req_pid = gf_list_get(m2ts->ts->requested_pids, i); - GF_M2TS_ES *es = m2ts->ts->ess[req_pid->pid]; - if (es==NULL) continue; - - /*move to skip mode for all PES until asked for playback*/ - if (!(es->flags & GF_M2TS_ES_IS_SECTION) && !es->user) - gf_m2ts_set_pes_framing((GF_M2TS_PES *)es, GF_M2TS_PES_FRAMING_SKIP); - MP2TS_DeclareStream(m2ts, (GF_M2TS_PES *)es, NULL, 0); - gf_list_rem(m2ts->ts->requested_pids, i); - gf_free(req_pid); - i--; - count--; - found++; - } - req_prog_count = gf_list_count(m2ts->ts->requested_progs); - for (i = 0; i < req_prog_count; i++) { - M2TSIn_Prog *req_prog = gf_list_get(m2ts->ts->requested_progs, i); - - prog_id = atoi(req_prog->fragment); - count = gf_list_count(m2ts->ts->SDTs); - for (j=0; j<count; j++) { - GF_M2TS_SDT *sdt = gf_list_get(m2ts->ts->SDTs, j); - if (!stricmp((const char *) sdt->service, req_prog->fragment)) req_prog->id = sdt->service_id; - else if (sdt->service_id==prog_id) req_prog->id = sdt->service_id; - } - if (!req_prog->id) { - count = gf_list_count(m2ts->ts->programs); - for (j=0; j<count; j++) { - GF_M2TS_Program *ts_prog = gf_list_get(m2ts->ts->programs, j); - if (prog_id==ts_prog->number) { - req_prog->id = prog_id; - break; - } - } - } - if (req_prog->id) { - GF_M2TS_Program *ts_prog; - count = gf_list_count(m2ts->ts->programs); - for (j=0; j<count; j++) { - ts_prog = gf_list_get(m2ts->ts->programs, j); - if (ts_prog->number==req_prog->id) { - MP2TS_SetupProgram(m2ts, ts_prog, 0, 0); - found++; - gf_free(req_prog->fragment); - gf_free(req_prog); - gf_list_rem(m2ts->ts->requested_progs, i); - req_prog_count--; - i--; - break; - } - } - } - } - - if (m2ts->epg_requested) { - if (!m2ts->has_eit) { - GF_ObjectDescriptor *od = M2TS_GenerateEPG_OD(m2ts); - /*declare but don't regenerate scene*/ - gf_service_declare_media(m2ts->service, (GF_Descriptor*)od, 0); - m2ts->has_eit = 1; - } - } else { - /*force scene regeneration only when EPG is not requested*/ - if (found) - gf_service_declare_media(m2ts->service, NULL, 0); - } - - gf_mx_v(m2ts->mx); -} - -static void forward_m2ts_event(M2TSIn *m2ts, u32 evt_type, void *param) -{ - GF_NetworkCommand com; - memset(&com, 0, sizeof(com)); - - com.command_type = GF_NET_SERVICE_EVENT; - com.send_event.evt.type = GF_EVENT_FROM_SERVICE; - com.send_event.evt.from_service.forward_type = GF_EVT_MPEG2; - com.send_event.evt.from_service.service_event_type = evt_type; - com.send_event.evt.from_service.param = param; - gf_service_command(m2ts->service, &com, GF_OK); -} - -static void M2TS_OnEvent(GF_M2TS_Demuxer *ts, u32 evt_type, void *param) -{ - M2TSIn *m2ts = (M2TSIn *) ts->user; - - switch (evt_type) { - case GF_M2TS_EVT_PAT_UPDATE: - /* example code showing how to forward an event from MPEG-2 TS input service to GPAC user*/ -#if 0 - send_m2ts_event(m2ts, evt_type, param); -#endif - break; - case GF_M2TS_EVT_AIT_FOUND: - forward_m2ts_event(m2ts, evt_type, param); - break; - case GF_M2TS_EVT_PAT_FOUND: - /* In case the TS has one program, wait for the PMT to send connect, in case of IOD in PMT */ - if (gf_list_count(m2ts->ts->programs) != 1) { - gf_service_connect_ack(m2ts->service, NULL, GF_OK); - m2ts->is_connected = 1; - } - /* Send the TS to the a user if needed. Useful to check the number of received programs*/ - forward_m2ts_event(m2ts, evt_type, param); - - m2ts->nb_prog_to_setup = gf_list_count(m2ts->ts->programs) ; - - break; - case GF_M2TS_EVT_DSMCC_FOUND: - forward_m2ts_event(m2ts, evt_type, param); - break; - case GF_M2TS_EVT_PMT_FOUND: - if (gf_list_count(m2ts->ts->programs) == 1) { - gf_service_connect_ack(m2ts->service, NULL, GF_OK); - m2ts->is_connected = 1; - } - /*do not declare if single program was requested for playback*/ - MP2TS_SetupProgram(m2ts, param, m2ts->request_all_pids, m2ts->request_all_pids ? 0 : 1); - M2TS_FlushRequested(m2ts); - /* Send the TS to the a user if needed. Useful to check the number of received programs*/ - forward_m2ts_event(m2ts, evt_type, param); - break; - case GF_M2TS_EVT_PMT_REPEAT: - M2TS_FlushRequested(m2ts); - break; - case GF_M2TS_EVT_PMT_UPDATE: - MP2TS_SetupProgram(m2ts, param, GF_TRUE, GF_FALSE); - break; - - case GF_M2TS_EVT_SDT_FOUND: - case GF_M2TS_EVT_SDT_UPDATE: - m2ts->flush_sdt = 1; - case GF_M2TS_EVT_SDT_REPEAT: - M2TS_FlushRequested(m2ts); - if (m2ts->flush_sdt) { - GF_NetworkCommand com; - m2ts->flush_sdt = 0; - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.command_type = GF_NET_SERVICE_INFO; - gf_service_command(m2ts->service, &com, GF_OK); - } - break; - case GF_M2TS_EVT_DVB_GENERAL: - if (m2ts->eit_channel) { - GF_M2TS_SL_PCK *pck = (GF_M2TS_SL_PCK *)param; - gf_service_send_packet(m2ts->service, m2ts->eit_channel, pck->data, pck->data_len, NULL, GF_OK); - } - break; - case GF_M2TS_EVT_PES_PCK: - MP2TS_SendPacket(m2ts, param); - break; - case GF_M2TS_EVT_SL_PCK: /* DMB specific */ - MP2TS_SendSLPacket(m2ts, param); - break; - case GF_M2TS_EVT_EOS: - gf_service_send_packet(m2ts->service, ((GF_M2TS_PES *)param)->user, NULL, 0, NULL, GF_EOS); - break; - case GF_M2TS_EVT_AAC_CFG: - { - GF_M2TS_PES_PCK *pck = (GF_M2TS_PES_PCK*)param; - if (!pck->stream->first_dts) { - gf_m2ts_set_pes_framing(pck->stream, GF_M2TS_PES_FRAMING_SKIP_NO_RESET); - MP2TS_DeclareStream(m2ts, pck->stream, pck->data, pck->data_len); - if (ts->file || ts->dnload) { - m2ts->file_regulate = 1; - if (!m2ts->nb_playing) gf_m2ts_pause_demux(m2ts->ts, 1); - - } - pck->stream->first_dts=1; - /*force scene regeneration*/ - gf_service_declare_media(m2ts->service, NULL, 0); - } - } - break; - case GF_M2TS_EVT_PES_PCR: - { - Bool discontinuity = ( ((GF_M2TS_PES_PCK *) param)->flags & GF_M2TS_PES_PCK_DISCONTINUITY) ? 1 : 0; - GF_M2TS_PES_PCK *pck; - /*send pcr*/ - if (((GF_M2TS_PES_PCK *) param)->stream && ((GF_M2TS_PES_PCK *) param)->stream->user) { - GF_SLHeader slh; - memset(&slh, 0, sizeof(GF_SLHeader) ); - slh.OCRflag = 1; - slh.m2ts_pcr = discontinuity ? 2 : 1; - slh.objectClockReference = ((GF_M2TS_PES_PCK *) param)->PTS; - - //check if our buffer level is low enough otherwise we would send the OCR_disc way too early - //we have to do this because the terminal doesn't "queue" clock discontinuities - if (m2ts->file_regulate && discontinuity) { - /*query buffer level, don't sleep if too low*/ - GF_NetworkCommand com; - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.command_type = GF_NET_BUFFER_QUERY; - com.base.on_channel = NULL; - while (ts->run_state) { - gf_service_command(m2ts->service, &com, GF_OK); - if (!com.buffer.occupancy || com.buffer.buffering) { - break; - } - gf_sleep(1); - } - } - - gf_service_send_packet(m2ts->service, ((GF_M2TS_PES_PCK *) param)->stream->user, NULL, 0, &slh, GF_OK); - - if ( m2ts->map_media_time_on_prog_id && !ts->start_range && (m2ts->map_media_time_on_prog_id==((GF_M2TS_PES_PCK *) param)->stream->program->number)) { - GF_NetworkCommand com; - memset(&com, 0, sizeof(com)); - com.command_type = GF_NET_CHAN_SET_MEDIA_TIME; - com.map_time.media_time = m2ts->media_start_range; - com.map_time.timestamp = slh.objectClockReference / 300; - com.base.on_channel = ((GF_M2TS_PES_PCK *) param)->stream->user; - gf_service_command(m2ts->service, &com, GF_OK); - m2ts->map_media_time_on_prog_id = 0; - m2ts->pcr_last = 0; - } - } - pck = (GF_M2TS_PES_PCK *) param; - if (pck->stream && pck->stream->program) pck->stream->program->first_dts = 1; - - if (discontinuity) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[M2TS In] PCR discontinuity - switching from old STB "LLD" to new one "LLD"\n", m2ts->pcr_last, ((GF_M2TS_PES_PCK *) param)->PTS)); - if (m2ts->pcr_last) { - m2ts->pcr_last = pck->PTS; - m2ts->stb_at_last_pcr = gf_sys_clock(); - } - /*FIXME - we need to find a way to treat PCR discontinuities correctly while ignoring broken PCR discontinuities - seen in many HLS solutions*/ - return; - } - - if (m2ts->file_regulate) { - u64 pcr = pck->PTS; - u32 stb = gf_sys_clock(); - - if (pck->stream) { - if (m2ts->regulation_pcr_pid==0) { - /*we pick the first PCR PID for file regulation - we don't need to make sure this is the PCR of a program being played as we - only check buffer levels, not DTS/PTS of the streams in the regulation step*/ - m2ts->regulation_pcr_pid = pck->stream->pid; - } else if (m2ts->regulation_pcr_pid != pck->stream->pid) { - return; - } - } - - - if (m2ts->pcr_last) { - s32 diff; - if (pcr < m2ts->pcr_last) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[M2TS In] PCR "LLU" less than previous PCR "LLU"\n", ((GF_M2TS_PES_PCK *) param)->PTS, m2ts->pcr_last)); - m2ts->pcr_last = pcr; - m2ts->stb_at_last_pcr = gf_sys_clock(); - diff = 0; - } else { - u64 pcr_diff = (pcr - m2ts->pcr_last); - pcr_diff /= 27000; - if (pcr_diff>1000) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[M2TS In] PCR diff too big: "LLU" ms - PCR "LLU" - previous PCR "LLU" - error in TS ?\n", pcr_diff, ((GF_M2TS_PES_PCK *) param)->PTS, m2ts->pcr_last)); - diff = 100; - } else { - diff = (u32) pcr_diff - (stb - m2ts->stb_at_last_pcr); - } - } - if (diff<-400) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[M2TS In] Demux not going fast enough according to PCR (drift %d, pcr: "LLU", last pcr: "LLU")\n", diff, pcr, m2ts->pcr_last)); - } else if (diff>0) { - u32 sleep_for=1; -#ifndef GPAC_DISABLE_LOG - u32 nb_sleep=0; -#endif - /*query buffer level, don't sleep if too low*/ - GF_NetworkCommand com; - com.command_type = GF_NET_BUFFER_QUERY; - com.base.on_channel = NULL; - while (ts->run_state) { - gf_service_command(m2ts->service, &com, GF_OK); - if (!com.buffer.occupancy || (com.buffer.occupancy < com.buffer.max)) { - GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[M2TS In] Demux not going to sleep: buffer occupancy %d ms\n", com.buffer.occupancy)); - break; - } - /*We don't sleep for the entire buffer occupancy, because we would take - the risk of starving the audio chains. We try to keep buffers half full*/ - sleep_for = MIN(com.buffer.occupancy/2, M2TS_MAX_SLEEP); - -#ifndef GPAC_DISABLE_LOG - if (!nb_sleep) { - GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[M2TS In] Demux going to sleep for %d ms (buffer occupancy %d ms)\n", sleep_for, com.buffer.occupancy)); - } - nb_sleep++; -#endif - gf_sleep(sleep_for); - } -#ifndef GPAC_DISABLE_LOG - if (nb_sleep) { - GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[M2TS In] Demux resume after %d ms - current buffer occupancy %d ms\n", sleep_for*nb_sleep, com.buffer.occupancy)); - } -#endif - m2ts->pcr_last = pcr; - m2ts->stb_at_last_pcr = gf_sys_clock(); - } else { - GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[M2TS In] Demux drift according to PCR (drift %d, pcr: "LLD", last pcr: "LLD")\n", diff, pcr, m2ts->pcr_last)); - } - } else { - m2ts->pcr_last = pcr; - m2ts->stb_at_last_pcr = gf_sys_clock(); - } - } - } - break; - case GF_M2TS_EVT_TDT: - if (m2ts->hybrid_on) { - u32 i, count; - GF_M2TS_TDT_TOT *tdt = (GF_M2TS_TDT_TOT *)param; - GF_NetworkCommand com; - memset(&com, 0, sizeof(com)); - com.command_type = GF_NET_CHAN_MAP_TIME; - com.map_time.media_time = tdt->hour*3600+tdt->minute*60+tdt->second; - com.map_time.reset_buffers = 0; - count = gf_list_count(ts->programs); - for (i=0; i<count; i++) { - GF_M2TS_Program *prog = gf_list_get(ts->programs, i); - u32 j, count2; - if (prog->tdt_found || !prog->last_pcr_value) /*map TDT one time, after we received a PCR*/ - continue; - prog->tdt_found = 1; - count2 = gf_list_count(prog->streams); - com.map_time.timestamp = prog->last_pcr_value/300; - for (j=0; j<count2; j++) { - GF_M2TS_ES * stream = gf_list_get(prog->streams, j); - if (stream->user) { - com.base.on_channel = stream->user; - gf_service_command(m2ts->service, &com, GF_OK); - } - } - GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[M2TS In] Mapping TDT Time %04d/%02d/%02d %02d:%02d:%02d and PCR time "LLD" on program %d\n", - tdt->year, tdt->month, tdt->day, tdt->hour, tdt->minute, tdt->second, com.map_time.timestamp, prog->number)); - } - } - break; - case GF_M2TS_EVT_TOT: - break; - - case GF_M2TS_EVT_DURATION_ESTIMATED: - { - u32 i, count; - GF_NetworkCommand com; - memset(&com, 0, sizeof(com)); - com.command_type = GF_NET_CHAN_DURATION; - com.duration.duration = (Double) ((GF_M2TS_PES_PCK *) param)->PTS; - com.duration.duration /= 1000; - count = gf_list_count(ts->programs); - for (i=0; i<count; i++) { - GF_M2TS_Program *prog = gf_list_get(ts->programs, i); - u32 j, count2; - count2 = gf_list_count(prog->streams); - for (j=0; j<count2; j++) { - GF_M2TS_ES * stream = gf_list_get(prog->streams, j); - if (stream->user) { - com.base.on_channel = stream->user; - gf_service_command(m2ts->service, &com, GF_OK); - } - } - } - } - break; - - case GF_M2TS_EVT_TEMI_LOCATION: - { - GF_NetworkCommand com; - memset(&com, 0, sizeof(com)); - com.addon_info.command_type = GF_NET_ASSOCIATED_CONTENT_LOCATION; - com.addon_info.external_URL = ((GF_M2TS_TemiLocationDescriptor*)param)->external_URL; - if (m2ts->force_temi_url) - com.addon_info.external_URL = m2ts->force_temi_url; - com.addon_info.is_announce = ((GF_M2TS_TemiLocationDescriptor*)param)->is_announce; - com.addon_info.is_splicing = ((GF_M2TS_TemiLocationDescriptor*)param)->is_splicing; - com.addon_info.activation_countdown = ((GF_M2TS_TemiLocationDescriptor*)param)->activation_countdown; -// com.addon_info.reload_external = ((GF_M2TS_TemiLocationDescriptor*)param)->reload_external; - com.addon_info.timeline_id = ((GF_M2TS_TemiLocationDescriptor*)param)->timeline_id; - gf_service_command(m2ts->service, &com, GF_OK); - } - break; - case GF_M2TS_EVT_TEMI_TIMECODE: - { - GF_NetworkCommand com; - memset(&com, 0, sizeof(com)); - com.addon_time.command_type = GF_NET_ASSOCIATED_CONTENT_TIMING; - com.addon_time.timeline_id = ((GF_M2TS_TemiTimecodeDescriptor*)param)->timeline_id; - com.addon_time.media_pts = ((GF_M2TS_TemiTimecodeDescriptor*)param)->pes_pts; - com.addon_time.media_timescale = ((GF_M2TS_TemiTimecodeDescriptor*)param)->media_timescale; - com.addon_time.media_timestamp = ((GF_M2TS_TemiTimecodeDescriptor*)param)->media_timestamp; - com.addon_time.force_reload = ((GF_M2TS_TemiTimecodeDescriptor*)param)->force_reload; - com.addon_time.is_paused = ((GF_M2TS_TemiTimecodeDescriptor*)param)->is_paused; - com.addon_time.ntp = ((GF_M2TS_TemiTimecodeDescriptor*)param)->ntp; - gf_service_command(m2ts->service, &com, GF_OK); - } - break; - } -} - -void m2ts_net_io(void *cbk, GF_NETIO_Parameter *param) -{ - GF_Err e; - M2TSIn *m2ts = (M2TSIn *) cbk; - assert( m2ts ); - /*handle service message*/ - gf_service_download_update_stats(m2ts->ts->dnload); - - if (param->msg_type==GF_NETIO_DATA_TRANSFERED) { - e = GF_EOS; - } else if (param->msg_type==GF_NETIO_DATA_EXCHANGE) { - e = GF_OK; - assert( m2ts->ts); - if (param->size > 0) { - /*process chunk*/ - assert(param->data); - if (m2ts->network_buffer_size < param->size) { - m2ts->network_buffer = gf_realloc(m2ts->network_buffer, sizeof(char) * param->size); - m2ts->network_buffer_size = param->size; - } - assert( m2ts->network_buffer ); - memcpy(m2ts->network_buffer, param->data, param->size); - gf_m2ts_process_data(m2ts->ts, m2ts->network_buffer, param->size); - } - - /*if asked to regulate, wait until we get a play request*/ - if (m2ts->ts->paused) { - while (m2ts->ts->run_state && m2ts->ts->paused ) { - gf_sleep(50); - continue; - } - } else { - gf_sleep(1); - } -#if 1 //see commit 3642: crashes when reload quickly with http - if (!m2ts->ts->run_state) { - if (m2ts->ts->dnload) - gf_service_download_del( m2ts->ts->dnload ); - m2ts->ts->dnload = NULL; - } -#endif - - } else { - e = param->error; - } - - switch (e) { - case GF_EOS: - if (!m2ts->is_connected) { - gf_service_connect_ack(m2ts->service, NULL, GF_OK); - } - return; - case GF_OK: - return; - default: - if (!m2ts->ts_setup) { - m2ts->ts_setup = 1; - } - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER,("[MPEGTSIn] : Error while getting data : %s\n", gf_error_to_string(e))); - gf_service_connect_ack(m2ts->service, NULL, e); - } -} - -/*for DASH - query_type is: - 0: query init range - 1: query next segment - 2: query next segment except currently downloading one - 3: drop next segment -*/ -typedef enum { - INIT_RANGE = 0, - NEXT_SEGMENT, - NEXT_SEGMENT_EXCEPT_DL, - DROP_NEXT_SEGMENT, -} DASHQueryType; -static GF_Err M2TS_QueryNextFile(void *udta, DASHQueryType query_type, const char **out_url, u64 *out_start_range, u64 *out_end_range, u32 *refresh_type) -{ - GF_NetworkCommand param; - GF_Err query_ret; - M2TSIn *m2ts = (M2TSIn *) udta; - assert(m2ts->owner); - assert( m2ts->owner->query_proxy); - - if (out_url) *out_url = NULL; - if (out_start_range) *out_start_range = 0; - if (out_end_range) *out_end_range = 0; - - memset(¶m, 0, sizeof(GF_NetworkCommand)); - param.command_type = (query_type == INIT_RANGE) ? GF_NET_SERVICE_QUERY_INIT_RANGE : GF_NET_SERVICE_QUERY_NEXT; - param.url_query.drop_first_segment = (query_type == DROP_NEXT_SEGMENT) ? 1 : 0; - param.url_query.current_download = (query_type == NEXT_SEGMENT_EXCEPT_DL) ? 0 : 1; - - //we are downloading the segment we play, don't delete it - if (m2ts->in_segment_download) - param.url_query.drop_first_segment = 0; - - query_ret = m2ts->owner->query_proxy(m2ts->owner, ¶m); - - if ((query_ret==GF_BUFFER_TOO_SMALL) && query_type && !param.url_query.next_url) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[M2TS In] Cannot query next file: not yet downloaded\n")); - } else if ((query_ret==GF_OK) && query_type && !param.url_query.next_url) { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[M2TS In] Cannot query next file: no file provided but no error raised\n")); - } else if (query_ret) { - GF_LOG((query_ret<0) ? GF_LOG_ERROR : GF_LOG_INFO, GF_LOG_DASH, ("[M2TS In] Cannot query next file: error: %s\n", gf_error_to_string(query_ret))); - } else { - if (out_url) *out_url = param.url_query.next_url; - if (out_start_range) *out_start_range = param.url_query.start_range; - if (out_end_range) *out_end_range = param.url_query.end_range; - - /*the segment is being downloaded now, start monitoring refresh*/ - if (param.url_query.current_download) { - m2ts->low_latency_mode = 1; - if (refresh_type) *refresh_type = 1; - if (!m2ts->in_segment_download || param.url_query.has_new_data) { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[M2TS In] progressive loading of TS segments\n")); - } - //state "in progress" - m2ts->in_segment_download = 1; - if (!m2ts->first_chunk_in_seg_parsed) { - m2ts->first_chunk_in_seg_parsed=1; - m2ts->ts->pos_in_stream = 0; - } - } else { - if (refresh_type) { - //segment done downloading but was in progress mode: do a final refresh - if (m2ts->in_segment_download) { - GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[M2TS In] End of progressive loading of TS segments\n")); - *refresh_type = 2; - } else { - *refresh_type = 0; - } - } - m2ts->first_chunk_in_seg_parsed=0; - m2ts->in_segment_download = 0; - } - } - return query_ret; -} - -enum -{ - GF_M2TS_PUSH_SEGMENT, - GF_M2TS_PUSH_CHUNK, - GF_M2TS_FLUSH_DATA -}; - -void m2ts_flush_data(M2TSIn *m2ts, u32 flush_type) -{ - u64 start_byterange, end_byterange; - GF_Err e = GF_OK; - u32 refresh_type = 0; - const char *url; - - if (m2ts->in_data_flush) { - if (flush_type==GF_M2TS_PUSH_SEGMENT) - m2ts->has_pending_segments++; - return; - } - if (! gf_mx_try_lock(m2ts->mx)) { - if (flush_type==GF_M2TS_PUSH_SEGMENT) - m2ts->has_pending_segments++; - return; - } - m2ts->in_data_flush = 1; - - //check buffer level when start of new segment - if (flush_type<=GF_M2TS_PUSH_CHUNK) { - GF_NetworkCommand com; - /*query buffer level on each channel, don't sleep if too low*/ - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.command_type = GF_NET_BUFFER_QUERY; - gf_service_command(m2ts->service, &com, GF_OK); - if (com.buffer.occupancy && (com.buffer.occupancy >= com.buffer.max)) { - //count completed segment that were not dispatched - if (flush_type==GF_M2TS_PUSH_SEGMENT) - m2ts->has_pending_segments++; - - m2ts->in_data_flush = 0; - gf_mx_v(m2ts->mx); - return; - } - } - else if (0 && flush_type==GF_M2TS_FLUSH_DATA) { - if (! m2ts->has_pending_segments) { - m2ts->in_data_flush = 0; - gf_mx_v(m2ts->mx); - return; - } - } - - e = M2TS_QueryNextFile(m2ts, (flush_type==GF_M2TS_FLUSH_DATA) ? NEXT_SEGMENT_EXCEPT_DL : NEXT_SEGMENT, &url, &start_byterange, &end_byterange, &refresh_type); - if (e) { - m2ts->in_data_flush = 0; - if (e==GF_EOS) { - gf_m2ts_demux_file(m2ts->ts, NULL, 0, 0, 0, 1); - } - gf_mx_v(m2ts->mx); - return; - } - gf_m2ts_demux_file(m2ts->ts, url, start_byterange, end_byterange, refresh_type, 0); - - //done, drop segment - if (!m2ts->in_segment_download) { - e = M2TS_QueryNextFile(m2ts, DROP_NEXT_SEGMENT, &url, &start_byterange, &end_byterange, &refresh_type); - - if (m2ts->has_pending_segments) - m2ts->has_pending_segments--; - - if (e==GF_EOS) { - gf_m2ts_demux_file(m2ts->ts, NULL, 0, 0, 0, 1); - } - //we are currently downloading this segment, parse it ONLY IF WE ARE CALLED FROM THE PROXY THREAD - //TODO: we should fix this to allow concurrent access to gmem:// - else if (m2ts->in_segment_download && (flush_type != GF_M2TS_FLUSH_DATA) ) { - gf_m2ts_demux_file(m2ts->ts, url, start_byterange, end_byterange, refresh_type, 0); - } - - } - m2ts->in_data_flush = 0; - gf_mx_v(m2ts->mx); -} - -static GF_Err M2TS_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url) -{ - GF_Err e; - const char *opt; - M2TSIn *m2ts = plug->priv; - - M2TS_GetNetworkType(plug,m2ts); - - m2ts->owner = plug; - - opt = gf_modules_get_option((GF_BaseInterface *)m2ts->owner, "HybRadio", "Activated"); - if (opt && !strcmp(opt, "true")) { - m2ts->hybrid_on = 1; - } - - m2ts->ts->record_to = gf_modules_get_option((GF_BaseInterface *)m2ts->owner, "M2TS", "RecordTo"); - - m2ts->service = serv; - - m2ts->force_temi_url = gf_modules_get_option((GF_BaseInterface *)m2ts->owner, "M2TS", "ForceTEMILocation"); - if (m2ts->force_temi_url && !strlen(m2ts->force_temi_url)) m2ts->force_temi_url = NULL; - - opt = gf_modules_get_option((GF_BaseInterface *)m2ts->owner, "DSMCC", "Activated"); - if (opt && !strcmp(opt, "yes")) { - gf_m2ts_demux_dmscc_init(m2ts->ts); - } - - if (url && !strnicmp(url, "http://", 7)) { - m2ts->ts->dnload = gf_service_download_new(m2ts->service, url, GF_NETIO_SESSION_NOT_THREADED | GF_NETIO_SESSION_NOT_CACHED | GF_NETIO_SESSION_NOTIFY_DATA, m2ts_net_io, m2ts); - if (!m2ts->ts->dnload) { - gf_service_connect_ack(m2ts->service, NULL, GF_NOT_SUPPORTED); - return GF_OK; - } else { - e = gf_m2ts_demuxer_play(m2ts->ts); - } - } else { - //dash & HLS - run in non-threaded mode - if (plug->query_proxy) { - //get byte range if any (local playback) - if (url) { - u64 start_byterange, end_byterange; - gf_mx_p(m2ts->mx); - m2ts->in_data_flush = 1; - M2TS_QueryNextFile(m2ts, INIT_RANGE, NULL, &start_byterange, &end_byterange, NULL); - e = gf_m2ts_demux_file(m2ts->ts, url, start_byterange, end_byterange, 0, 0); - M2TS_QueryNextFile(m2ts, DROP_NEXT_SEGMENT, NULL, NULL, NULL, NULL); - m2ts->in_data_flush = 0; - gf_mx_v(m2ts->mx); - } else { - e = GF_OK; - } - m2ts->ts->run_state = 1; - } else { - e = gf_m2ts_demuxer_setup(m2ts->ts,url,0); - } - } - - if (e) { - gf_service_connect_ack(m2ts->service, NULL, e); - } - return e; -} - -static GF_Err M2TS_CloseService(GF_InputService *plug) -{ - M2TSIn *m2ts = plug->priv; - GF_M2TS_Demuxer* ts = m2ts->ts; - - if (!plug->query_proxy) - gf_m2ts_demuxer_close(ts); - - m2ts->ts->run_state = 0; - - if (ts->dnload) gf_service_download_del(ts->dnload); - ts->dnload = NULL; - - gf_service_disconnect_ack(m2ts->service, NULL, GF_OK); - return GF_OK; -} - -static GF_Descriptor *M2TS_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) -{ - M2TSIn *m2ts = plug->priv; - GF_Descriptor *desc = NULL; - char *frag; - M2TSIn_Prog *prog; - - if (sub_url && !strnicmp(sub_url, "pid://", 6)) { - GF_ObjectDescriptor *od; - u32 pid = atoi(sub_url+6); - if (pid>=GF_M2TS_MAX_STREAMS) return NULL; - od = MP2TS_GetOD(m2ts, (GF_M2TS_PES*) m2ts->ts->ess[pid], NULL, 0, NULL); - return (GF_Descriptor *) od; - } - - frag = sub_url ? strrchr(sub_url, '#') : NULL; - if (frag) frag++; - - /* consider the channel name in DVB URL as a fragment */ - if (!frag && sub_url && !strncmp(sub_url, "dvb://", 6)) { - frag = (char*)sub_url + 6; - } - - if (!frag) { - m2ts->request_all_pids = 1; - } else { - /*we need exclusive access*/ - gf_mx_p(m2ts->mx); - if (!strnicmp(frag, "pid=", 4)) { - GF_SAFEALLOC(prog, M2TSIn_Prog); - if (!prog) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[M2TSIn] Fail to allocate pid playback request")); - return NULL; - } - prog->pid = atoi(frag+4); - gf_list_add(m2ts->ts->requested_pids, prog); - } else if (!strnicmp(frag, "EPG", 3)) { - m2ts->epg_requested = 1; - } else { - u32 i, count; - count = gf_list_count(m2ts->ts->requested_progs); - prog = NULL; - for (i=0; i<count; i++) { - prog = gf_list_get(m2ts->ts->requested_progs, i); - if (!strcmp(prog->fragment, frag)) - break; - prog = NULL; - } - if (!prog) { - GF_SAFEALLOC(prog, M2TSIn_Prog); - if (!prog) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[M2TSIn] Fail to allocate URI fragment playback request")); - return NULL; - } - gf_list_add(m2ts->ts->requested_progs, prog); - prog->fragment = gf_strdup(frag); - } - } - gf_mx_v(m2ts->mx); - } - - /*if type is undefined, check the PMT for an IOD*/ - if (expect_type<=GF_MEDIA_OBJECT_SCENE) { - if (gf_list_count(m2ts->ts->programs) == 1) { - GF_M2TS_Program *prog = gf_list_get(m2ts->ts->programs, 0); - if (prog->pmt_iod) { - m2ts->request_all_pids = 0; - gf_odf_desc_copy((GF_Descriptor *)prog->pmt_iod, &desc); - ((GF_InitialObjectDescriptor *)desc)->service_ifce = m2ts->owner; - return desc; - } - } - /*if we expect scene, return NULL and repost a connection ack when we get the PMT*/ - if (expect_type==GF_MEDIA_OBJECT_SCENE) - return NULL; - if (m2ts->epg_requested) { - GF_ObjectDescriptor *od = M2TS_GenerateEPG_OD(m2ts); - m2ts->epg_requested = 0; - return (GF_Descriptor *)od; - } else { - /*returning an empty IOD means "no scene description", let the terminal handle all media objects*/ - desc = gf_odf_desc_new(GF_ODF_IOD_TAG); - ((GF_ObjectDescriptor *) desc)->objectDescriptorID = 1; - return desc; - } - } - - /* restart the thread if the same service is reused and if the previous thread terminated */ - if (!plug->query_proxy) { - if (m2ts->ts->run_state == 2) { - m2ts->file_regulate = 0; - gf_m2ts_demuxer_play(m2ts->ts); - } - } - return NULL; -} - -static GF_Err M2TS_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) -{ - u32 ES_ID; - GF_Err e; - M2TSIn *m2ts = plug->priv; - - e = GF_STREAM_NOT_FOUND; - if (strstr(url, "ES_ID")) { - sscanf(url, "ES_ID=%d", &ES_ID); - - /* In case there is a real IOD, we need to translate PID into ESID */ - if (gf_list_count(m2ts->ts->programs) == 1) { - GF_M2TS_Program *prog = gf_list_get(m2ts->ts->programs, 0); - if (prog->pmt_iod) { - /* IOD is present */ - u32 i; - for (i=0; i<GF_M2TS_MAX_STREAMS; i++) { - GF_M2TS_PES *pes = (GF_M2TS_PES *)m2ts->ts->ess[i]; - if (!pes || (pes->pid==pes->program->pmt_pid)) continue; - if ((pes->mpeg4_es_id == ES_ID) - /*for pid:// url*/ - || (!pes->mpeg4_es_id && (pes->pid == ES_ID)) - ) { - if (pes->user) { - e = GF_SERVICE_ERROR; - gf_service_connect_ack(m2ts->service, channel, e); - return e; - } else { - pes->user = channel; - e = GF_OK; - gf_service_connect_ack(m2ts->service, channel, e); - return e; - } - } - } - /* Stream not found */ - return e; - } - } - - /* No IOD */ - if ((ES_ID == 18) && (!m2ts->ts->ess[18] || !(m2ts->ts->ess[18]->flags & GF_M2TS_ES_IS_PES)) ) { - e = GF_OK; /* 18 is the PID of EIT packets */ - m2ts->eit_channel = channel; - } else if (ES_ID<GF_M2TS_MAX_STREAMS) { - u32 i, j, count, count2; - e = GF_SERVICE_ERROR; - count = gf_list_count(m2ts->ts->programs); - for (i=0; i<count; i++) { - GF_M2TS_Program *prog = gf_list_get(m2ts->ts->programs, i); - count2 = gf_list_count(prog->streams); - for (j=0; j<count2; j++) { - GF_M2TS_PES *pes = (GF_M2TS_PES *)gf_list_get(prog->streams, j); - if ((pes->pid == ES_ID) && !pes->user) { - pes->user = channel; - /*we try to play the highest layer*/ - if (pes->program->pid_playing < pes->pid) - pes->program->pid_playing = pes->pid; - e = GF_OK; - break; - } - } - } - } - } - gf_service_connect_ack(m2ts->service, channel, e); - return e; -} - -static GF_M2TS_PES *M2TS_GetChannel(M2TSIn *m2ts, LPNETCHANNEL channel) -{ - u32 i, j, count, count2; - count = gf_list_count(m2ts->ts->programs); - for (i=0; i<count; i++) { - GF_M2TS_Program *prog = gf_list_get(m2ts->ts->programs, i); - count2 = gf_list_count(prog->streams); - for (j=0; j<count2; j++) { - GF_M2TS_PES *pes = (GF_M2TS_PES *)gf_list_get(prog->streams, j); - if (pes->user == channel) return pes; - } - } - return NULL; -} - -static GF_M2TS_PES *M2TS_GetFirstRunningChannel(M2TSIn *m2ts) -{ - u32 i; - for (i=0; i<GF_M2TS_MAX_STREAMS; i++) { - GF_M2TS_PES *pes = (GF_M2TS_PES *)m2ts->ts->ess[i]; - if (!pes || (pes->pid==pes->program->pmt_pid)) continue; - if (!pes->user) continue; - - if (gf_m2ts_pes_get_framing_mode(pes) >= GF_M2TS_PES_FRAMING_DEFAULT) - return pes; - } - return NULL; -} - -static GF_Err M2TS_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) -{ - M2TSIn *m2ts = plug->priv; - GF_Err e = GF_STREAM_NOT_FOUND; - GF_M2TS_PES *pes = M2TS_GetChannel(m2ts, channel); - if (pes) { - pes->user = NULL; - e = GF_OK; - } - gf_service_disconnect_ack(m2ts->service, channel, e); - return GF_OK; -} - -static void gf_m2ts_switch_quality(GF_M2TS_Program *prog, GF_M2TS_Demuxer *ts, Bool switch_up) -{ - GF_M2TS_ES *es; - u32 i, count; - GF_NetworkCommand com; - - if (!prog->is_scalable) - return; - - if (switch_up) { - for (i = 0; i < GF_M2TS_MAX_STREAMS; i++) { - es = ts->ess[i]; - if (es && (es->flags & GF_M2TS_ES_IS_PES) && (((GF_M2TS_PES *)es)->depends_on_pid == prog->pid_playing)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("Turn on ES%d\n", es->pid)); - gf_m2ts_set_pes_framing((GF_M2TS_PES *)ts->ess[es->pid], GF_M2TS_PES_FRAMING_DEFAULT); - prog->pid_playing = es->pid; - return; - } - } - } - else { - count = gf_list_count(prog->streams); - for (i = 0; i < count; i++) { - es = (GF_M2TS_ES *)gf_list_get(prog->streams, i); - if (es && (es->pid == prog->pid_playing) && ((GF_M2TS_PES *)es)->depends_on_pid) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("Turn off ES%d - playing ES%d\n", es->pid, ((GF_M2TS_PES *)es)->depends_on_pid)); - gf_m2ts_set_pes_framing((GF_M2TS_PES *)ts->ess[es->pid], GF_M2TS_PES_FRAMING_SKIP); - memset(&com, 0, sizeof(com)); - com.command_type = GF_NET_CHAN_RESET; - com.base.on_channel = ((GF_M2TS_PES *)es)->user; - gf_service_command(((M2TSIn *)ts->user)->service, &com, GF_OK); - prog->pid_playing = ((GF_M2TS_PES *)es)->depends_on_pid; - return; - } - } - } -} - -static GF_Err M2TS_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) -{ - GF_M2TS_PES *pes; - M2TSIn *m2ts = plug->priv; - GF_M2TS_Demuxer *ts = m2ts->ts; - - if (com->command_type==GF_NET_SERVICE_HAS_AUDIO) { - char *frag = strchr(com->audio.base_url, '#'); - if (frag && !strnicmp(frag, "#pid=", 5)) return GF_NOT_SUPPORTED; - return GF_OK; - } - if (com->command_type == GF_NET_SERVICE_QUALITY_SWITCH) { - u32 i, count; - count = gf_list_count(ts->programs); - for (i = 0; i < count; i++) { - GF_M2TS_Program *prog = (GF_M2TS_Program *)gf_list_get(ts->programs, i); - gf_m2ts_switch_quality(prog, ts, com->switch_quality.up); - } - return GF_OK; - } - if (com->command_type == GF_NET_SERVICE_PROXY_DATA_RECEIVE) { - m2ts_flush_data(m2ts, com->proxy_data.is_chunk ? GF_M2TS_PUSH_CHUNK : GF_M2TS_PUSH_SEGMENT); - return GF_OK; - } - if (com->command_type == GF_NET_SERVICE_FLUSH_DATA) { - if (plug->query_proxy) - m2ts_flush_data(m2ts, GF_M2TS_FLUSH_DATA); - return GF_OK; - } - //get info on the first running program - if (com->command_type==GF_NET_SERVICE_INFO) { - u32 id = com->info.service_id; - if (!id) { - pes = M2TS_GetFirstRunningChannel(m2ts); - if (pes) id = pes->program->number; - } - if (id) { - GF_M2TS_SDT *sdt = gf_m2ts_get_sdt_info(ts, id); - if (sdt) { - com->info.name = sdt->service; - com->info.provider = sdt->provider; - } - } else { - m2ts->flush_sdt = 1; - } - return GF_OK; - } - - - if (!com->base.on_channel) return GF_NOT_SUPPORTED; - switch (com->command_type) { - /*we cannot pull complete AUs from the stream*/ - case GF_NET_CHAN_SET_PULL: - return GF_NOT_SUPPORTED; - /*we cannot seek stream by stream*/ - case GF_NET_CHAN_INTERACTIVE: - if (m2ts->ts->file) return GF_OK; - return GF_NOT_SUPPORTED; - case GF_NET_CHAN_BUFFER: - //do not override config - if (ts->dnload || plug->query_proxy) { - if (!com->buffer.max) com->buffer.max = 1000; - } else if (ts->file) { - //use default small buffer for PCR regulation when from file - com->buffer.max = com->buffer.occupancy = M2TS_MAX_SLEEP; - } - return GF_OK; - case GF_NET_CHAN_DURATION: - com->duration.duration = ts->duration; - return GF_OK; - case GF_NET_CHAN_PLAY: - pes = M2TS_GetChannel(m2ts, com->base.on_channel); - if (!pes) { - if (com->base.on_channel == m2ts->eit_channel) { - return GF_OK; - } - return GF_STREAM_NOT_FOUND; - } - /*mark pcr as not initialized*/ - if (pes->program->pcr_pid==pes->pid) pes->program->first_dts=0; - gf_m2ts_set_pes_framing(pes, GF_M2TS_PES_FRAMING_DEFAULT); - GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[M2TSIn] Setting default reframing for PID %d\n", pes->pid)); - /*this is a multplex, only trigger the play command for the first stream activated*/ - if (!m2ts->nb_playing) { - if (ts->file) { - ts->start_range = (u32) (com->play.start_range*1000); - ts->end_range = (com->play.end_range>0) ? (u32) (com->play.end_range*1000) : 0; - m2ts->media_start_range = com->play.start_range; - } - if (!m2ts->map_media_time_on_prog_id) - m2ts->map_media_time_on_prog_id = pes->program->number; - - if (com->play.dash_segment_switch) { - /*this is used in case of HLS stream while we need update media start time for mapping*/ - m2ts->media_start_range = com->play.start_range; - gf_m2ts_abort_parsing(ts, GF_TRUE); - } - - /*start demuxer*/ - if (!plug->query_proxy) { - if (ts->run_state!=1) { - gf_m2ts_demuxer_play(ts); - } - } - } - //remap media time for this program - else if (!m2ts->map_media_time_on_prog_id) { - m2ts->media_start_range = com->play.start_range; - m2ts->map_media_time_on_prog_id = pes->program->number; - } - if (!m2ts->nb_playing) { - gf_m2ts_pause_demux(ts, 0); - } - m2ts->nb_playing++; - return GF_OK; - case GF_NET_CHAN_STOP: - pes = M2TS_GetChannel(m2ts, com->base.on_channel); - if (!pes) { - if (com->base.on_channel == m2ts->eit_channel) { - return GF_OK; - } - return GF_STREAM_NOT_FOUND; - } - /* In case of EOS, we may receive a stop command after no one is playing */ - if (m2ts->nb_playing) - m2ts->nb_playing--; - /*stop demuxer*/ - if (!plug->query_proxy) { - if (!m2ts->nb_playing && (ts->run_state==1) && !m2ts->nb_prog_to_setup) { - ts->run_state=0; - while (ts->run_state!=2) gf_sleep(2); - if (gf_list_count(m2ts->ts->requested_progs)) { - m2ts->file_regulate = 0; - gf_m2ts_set_pes_framing(pes, GF_M2TS_PES_FRAMING_SKIP); - return gf_m2ts_demuxer_play(ts); - } - } - } - gf_m2ts_set_pes_framing(pes, GF_M2TS_PES_FRAMING_SKIP); - return GF_OK; - case GF_NET_CHAN_CONFIG: - pes = M2TS_GetChannel(m2ts, com->base.on_channel); - /*filter all sections carrying SL data for the app to signal the version number of the section*/ - if (pes && pes->flags & GF_M2TS_ES_IS_SECTION) { - if (pes->slcfg) gf_free(pes->slcfg); - pes->slcfg = gf_malloc(sizeof(GF_SLConfig)); - memcpy(pes->slcfg, &com->cfg.sl_config, sizeof(GF_SLConfig)); - com->cfg.use_m2ts_sections = 1; - pes->flags |= GF_M2TS_ES_SEND_REPEATED_SECTIONS; - } - return GF_OK; - case GF_NET_CHAN_PAUSE: - if (!m2ts->nb_paused) { - gf_m2ts_pause_demux(m2ts->ts, 1); - } - m2ts->nb_paused++; - return GF_OK; - case GF_NET_CHAN_RESUME: - if (m2ts->nb_paused) { - m2ts->nb_paused--; - if (!m2ts->nb_paused) { - m2ts->pcr_last = 0; - gf_m2ts_pause_demux(m2ts->ts, 0); - - } - } - return GF_OK; - - default: - return GF_OK; - } -} - -static u32 M2TS_RegisterMimeTypes(const GF_InputService * service) { - int i; - if (service == NULL) - return 0; - for (i = 0 ; MIMES[i]; i++) - gf_service_register_mime( service, MIMES[i], M2TS_EXTENSIONS, "MPEG-2 TS"); - return i; -} - -static void M2TS_GetNetworkType(GF_InputService *plug,M2TSIn *reader) -{ - const char *mob_on; - const char *mcast_ifce; - - mob_on = gf_modules_get_option((GF_BaseInterface*)plug, "Network", "MobileIPEnabled"); - if(mob_on && !strcmp(mob_on, "yes")) { - reader->ts->MobileIPEnabled = 1; - reader->ts->network_type = gf_modules_get_option((GF_BaseInterface*)plug, "Network", "MobileIP"); - } - - mcast_ifce = gf_modules_get_option((GF_BaseInterface*)plug, "Network", "DefaultMCastInterface"); - if(mcast_ifce) reader->ts->network_type = mcast_ifce; -} - -GF_InputService *NewM2TSReader() -{ - M2TSIn *reader; - GF_InputService *plug = gf_malloc(sizeof(GF_InputService)); - memset(plug, 0, sizeof(GF_InputService)); - GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC MPEG-2 TS Reader", "gpac distribution") - - plug->CanHandleURL = M2TS_CanHandleURL; - plug->CanHandleURLInService = M2TS_CanHandleURLInService; - plug->ConnectService = M2TS_ConnectService; - plug->CloseService = M2TS_CloseService; - plug->GetServiceDescriptor = M2TS_GetServiceDesc; - plug->ConnectChannel = M2TS_ConnectChannel; - plug->DisconnectChannel = M2TS_DisconnectChannel; - plug->ServiceCommand = M2TS_ServiceCommand; - plug->RegisterMimeTypes = M2TS_RegisterMimeTypes; - - reader = gf_malloc(sizeof(M2TSIn)); - memset(reader, 0, sizeof(M2TSIn)); - plug->priv = reader; - reader->ts = gf_m2ts_demux_new(); - reader->ts->on_event = M2TS_OnEvent; - reader->ts->user = reader; - reader->ts->demux_and_play = 1; - reader->ts->th = gf_th_new("MPEG-2 TS Demux"); - - reader->mx = gf_mx_new("MPEG2 Demux"); - - return plug; -} - -void DeleteM2TSReader(void *ifce) -{ - u32 i, count; - M2TSIn *m2ts; - GF_InputService *plug = (GF_InputService *) ifce; - if (!ifce) - return; - m2ts = plug->priv; - if (!m2ts) - return; - if( m2ts->ts->requested_progs ) { - count = gf_list_count(m2ts->ts->requested_progs); - for (i = 0; i < count; i++) { - M2TSIn_Prog *prog = gf_list_get(m2ts->ts->requested_progs, i); - gf_free(prog->fragment); - gf_free(prog); - } - gf_list_del(m2ts->ts->requested_progs); - m2ts->ts->requested_progs = NULL; - } - if( m2ts->ts->requested_pids ) { - count = gf_list_count(m2ts->ts->requested_pids); - for (i = 0; i < count; i++) { - M2TSIn_Prog *prog = gf_list_get(m2ts->ts->requested_pids, i); - gf_free(prog); - } - gf_list_del(m2ts->ts->requested_pids); - m2ts->ts->requested_pids = NULL; - } - if (m2ts->network_buffer) - gf_free(m2ts->network_buffer); - - m2ts->network_buffer = NULL; - gf_m2ts_demux_del(m2ts->ts); - gf_mx_del(m2ts->mx); - gf_free(m2ts); - - gf_free(plug); -} - -#endif - - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifndef GPAC_DISABLE_MPEG2TS - GF_NET_CLIENT_INTERFACE, -#endif - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - switch (InterfaceType) { -#ifndef GPAC_DISABLE_MPEG2TS - case GF_NET_CLIENT_INTERFACE: - return (GF_BaseInterface *) NewM2TSReader(); -#endif - default: - return NULL; - } -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#ifndef GPAC_DISABLE_MPEG2TS - case GF_NET_CLIENT_INTERFACE: - DeleteM2TSReader(ifce); - break; -#endif - } -} - -GPAC_MODULE_STATIC_DECLARATION( mpegts_in ) diff --git a/modules/odf_dec/Makefile b/modules/odf_dec/Makefile deleted file mode 100644 index 95a222c..0000000 --- a/modules/odf_dec/Makefile +++ /dev/null @@ -1,49 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/odf_dec - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS= odf_dec.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_odf_dec$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols odf_dec.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_odf_dec-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac_static $(LDFLAGS) -endif - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/odf_dec/odf_dec.c b/modules/odf_dec/odf_dec.c deleted file mode 100644 index e682341..0000000 --- a/modules/odf_dec/odf_dec.c +++ /dev/null @@ -1,323 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2012 - * All rights reserved - * - * This file is part of GPAC / OD decoder module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include <gpac/internal/terminal_dev.h> -#include <gpac/constants.h> - -typedef struct -{ - GF_Scene *scene; - u32 PL; -} ODPriv; - -static GF_Err ODF_GetCapabilities(GF_BaseDecoder *plug, GF_CodecCapability *cap) -{ - cap->cap.valueInt = 0; - return GF_NOT_SUPPORTED; -} - -static GF_Err ODF_SetCapabilities(GF_BaseDecoder *plug, const GF_CodecCapability capability) -{ - return GF_OK; -} - - -static GF_Err ODF_AttachScene(GF_SceneDecoder *plug, GF_Scene *scene, Bool is_inline_scene) -{ - ODPriv *priv = (ODPriv *)plug->privateStack; - if (priv && !priv->scene) priv->scene = scene; - return GF_OK; -} - -static void ODS_SetupOD(GF_Scene *scene, GF_ObjectDescriptor *od) -{ - GF_ObjectManager *odm; - odm = gf_scene_find_odm(scene, od->objectDescriptorID); - /*remove the old OD*/ - if (odm) gf_odm_disconnect(odm, 1); - odm = gf_odm_new(); - odm->OD = od; - odm->term = scene->root_od->term; - odm->parentscene = scene; - gf_mx_p(scene->mx_resources); - gf_list_add(scene->resources, odm); - gf_mx_v(scene->mx_resources); - - /*locate service owner*/ - gf_odm_setup_object(odm, scene->root_od->net_service); -} - -static GF_Err ODS_ODUpdate(ODPriv *priv, GF_ODUpdate *odU) -{ - GF_ObjectDescriptor *od; - u32 count; - - /*extract all our ODs and compare with what we already have...*/ - count = gf_list_count(odU->objectDescriptors); - if (count > 255) return GF_ODF_INVALID_DESCRIPTOR; - - while (count) { - od = (GF_ObjectDescriptor *)gf_list_get(odU->objectDescriptors, 0); - gf_list_rem(odU->objectDescriptors, 0); - count--; - ODS_SetupOD(priv->scene, od); - } - return GF_OK; -} - - - -static GF_Err ODS_RemoveOD(ODPriv *priv, GF_ODRemove *odR) -{ - u32 i; - for (i=0; i< odR->NbODs; i++) { - GF_ObjectManager *odm = gf_scene_find_odm(priv->scene, odR->OD_ID[i]); - if (odm) gf_odm_disconnect(odm, 1); - } - return GF_OK; -} - -static GF_Err ODS_UpdateESD(ODPriv *priv, GF_ESDUpdate *ESDs) -{ - GF_ESD *esd, *prev; - GF_ObjectManager *odm; - u32 count, i; - - odm = gf_scene_find_odm(priv->scene, ESDs->ODID); - /*spec: "ignore"*/ - if (!odm) return GF_OK; - - count = gf_list_count(ESDs->ESDescriptors); - - while (count) { - esd = (GF_ESD*)gf_list_get(ESDs->ESDescriptors, 0); - /*spec: "ES_Descriptors with ES_IDs that have already been received within the same name scope shall be ignored."*/ - prev = NULL; - i=0; - while ((prev = (GF_ESD*)gf_list_enum(odm->OD->ESDescriptors, &i))) { - if (prev->ESID == esd->ESID) break; - } - if (prev) { - gf_odf_desc_del((GF_Descriptor *)esd); - } else { - /*and register new stream*/ - gf_list_add(odm->OD->ESDescriptors, esd); - gf_odm_setup_es(odm, esd, odm->net_service, NULL); - } - - /*remove the desc from the AU*/ - gf_list_rem(ESDs->ESDescriptors, 0); - count--; - } - /*resetup object since a new ES has been inserted - (typically an empty object first sent, then a stream added - cf how ogg demuxer works)*/ - gf_scene_setup_object(priv->scene, odm); - return GF_OK; -} - - -static GF_Err ODS_RemoveESD(ODPriv *priv, GF_ESDRemove *ESDs) -{ - GF_ObjectManager *odm; - u32 i; - odm = gf_scene_find_odm(priv->scene, ESDs->ODID); - /*spec: "ignore"*/ - if (!odm) return GF_OK; - - for (i=0; i<ESDs->NbESDs; i++) { - /*blindly call remove*/ - gf_odm_remove_es(odm, ESDs->ES_ID[i]); - } - return GF_OK; -} - -static GF_Err ODF_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd) -{ - return esd->decoderConfig->upstream ? GF_NOT_SUPPORTED : GF_OK; -} - - -static GF_Err ODF_DetachStream(GF_BaseDecoder *plug, u16 ES_ID) -{ - return GF_OK; -} - - -static GF_Err ODF_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 AU_time, u32 mmlevel) -{ - GF_Err e; - GF_ODCom *com; - GF_ODCodec *oddec; - ODPriv *priv = (ODPriv *)plug->privateStack; - - oddec = gf_odf_codec_new(); - - e = gf_odf_codec_set_au(oddec, inBuffer, inBufferLength); - if (!e) e = gf_odf_codec_decode(oddec); - if (e) goto err_exit; - - //3- process all the commands in this AU, in order - while (1) { - com = gf_odf_codec_get_com(oddec); - if (!com) break; - - //ok, we have a command - switch (com->tag) { - case GF_ODF_OD_UPDATE_TAG: - e = ODS_ODUpdate(priv, (GF_ODUpdate *) com); - break; - case GF_ODF_OD_REMOVE_TAG: - e = ODS_RemoveOD(priv, (GF_ODRemove *) com); - break; - case GF_ODF_ESD_UPDATE_TAG: - e = ODS_UpdateESD(priv, (GF_ESDUpdate *)com); - break; - case GF_ODF_ESD_REMOVE_TAG: - e = ODS_RemoveESD(priv, (GF_ESDRemove *)com); - break; - case GF_ODF_IPMP_UPDATE_TAG: -#if 0 - { - GF_IPMPUpdate *ipmpU = (GF_IPMPUpdate *)com; - while (gf_list_count(ipmpU->IPMPDescList)) { - GF_IPMP_Descriptor *ipmp = gf_list_get(ipmpU->IPMPDescList, 0); - gf_list_rem(ipmpU->IPMPDescList, 0); - IS_UpdateIPMP(priv->scene, ipmp); - } - e = GF_OK; - } -#else - e = GF_OK; -#endif - break; - case GF_ODF_IPMP_REMOVE_TAG: - e = GF_NOT_SUPPORTED; - break; - /*should NEVER exist outside the file format*/ - case GF_ODF_ESD_REMOVE_REF_TAG: - e = GF_NON_COMPLIANT_BITSTREAM; - break; - default: - if (com->tag >= GF_ODF_COM_ISO_BEGIN_TAG && com->tag <= GF_ODF_COM_ISO_END_TAG) { - e = GF_ODF_FORBIDDEN_DESCRIPTOR; - } else { - /*we don't process user commands*/ - e = GF_OK; - } - break; - } - gf_odf_com_del(&com); - if (e) break; - } - -err_exit: - gf_odf_codec_del(oddec); - return e; -} - - -static u32 ODF_CanHandleStream(GF_BaseDecoder *ifce, u32 StreamType, GF_ESD *esd, u8 PL) -{ - ODPriv *priv = (ODPriv *)ifce->privateStack; - if (StreamType==GF_STREAM_OD) { - priv->PL = PL; - return GF_CODEC_SUPPORTED; - } - return GF_CODEC_NOT_SUPPORTED; -} - - -void DeleteODDec(GF_BaseDecoder *plug) -{ - ODPriv *priv; - if (!plug) - return; - priv = (ODPriv *)plug->privateStack; - if (priv) { - gf_free(priv); - plug->privateStack = NULL; - } - gf_free(plug); -} - -GF_BaseDecoder *NewODDec() -{ - GF_SceneDecoder *tmp; - ODPriv *priv; - - GF_SAFEALLOC(tmp, GF_SceneDecoder); - if (!tmp) return NULL; - GF_SAFEALLOC(priv, ODPriv); - - tmp->privateStack = priv; - tmp->AttachStream = ODF_AttachStream; - tmp->DetachStream = ODF_DetachStream; - tmp->GetCapabilities = ODF_GetCapabilities; - tmp->SetCapabilities = ODF_SetCapabilities; - tmp->ProcessData = ODF_ProcessData; - tmp->AttachScene = ODF_AttachScene; - tmp->CanHandleStream = ODF_CanHandleStream; - - GF_REGISTER_MODULE_INTERFACE(tmp, GF_SCENE_DECODER_INTERFACE, "GPAC OD Decoder", "gpac distribution") - return (GF_BaseDecoder *) tmp; -} - - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { - GF_SCENE_DECODER_INTERFACE, - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - switch (InterfaceType) { - case GF_SCENE_DECODER_INTERFACE: - return (GF_BaseInterface *)NewODDec(); - default: - return NULL; - } -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - if (!ifce) - return; - switch (ifce->InterfaceType) { - case GF_SCENE_DECODER_INTERFACE: - DeleteODDec((GF_BaseDecoder *)ifce); - break; - } -} - - -GPAC_MODULE_STATIC_DECLARATION( odf_dec ) diff --git a/modules/ogg/Makefile b/modules/ogg/Makefile deleted file mode 100644 index 4694046..0000000 --- a/modules/ogg/Makefile +++ /dev/null @@ -1,83 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/ogg - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS= ogg_in.o ogg_load.o - -SRCS := $(OBJS:.o=.c) - -NEED_LOCAL_LIB="no" -LOCAL_LIB=../../bin/gcc -LINKLIBS= -lgpac -logg - -ifeq ($(CONFIG_OGG), local) -NEED_LOCAL_LIB="yes" -endif - -ifeq ($(CONFIG_VORBIS), no) -else -OBJS+= vorbis_dec.o -LINKLIBS+= -lvorbis -CFLAGS+=-DGPAC_HAS_VORBIS -ifeq ($(CONFIG_VORBIS), local) -NEED_LOCAL_LIB="yes" -CFLAGS+= -I"$(LOCAL_INC_PATH)" -endif -endif - -ifeq ($(CONFIG_THEORA), no) -else -OBJS+= theora_dec.o -LINKLIBS+= -ltheora -CFLAGS+=-DGPAC_HAS_THEORA -ifeq ($(CONFIG_THEORA), local) -NEED_LOCAL_LIB="yes" -CFLAGS+= -I"$(LOCAL_INC_PATH)" -endif -endif - - -#add local lib path -ifeq ($(NEED_LOCAL_LIB), "yes") -LOCAL_LIB+=-L../../extra_lib/lib/gcc -endif - - -LIB=gm_ogg_xiph$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols ogg.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L$(LOCAL_LIB) $(LINKLIBS) $(LDFLAGS) - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/ogg/ogg_in.c b/modules/ogg/ogg_in.c deleted file mode 100644 index 37e370a..0000000 --- a/modules/ogg/ogg_in.c +++ /dev/null @@ -1,1015 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / XIPH.org module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "ogg_in.h" -#include <ogg/ogg.h> - -#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_OGG) - -typedef struct -{ - u32 streamType; /*MPEG-4 streamType*/ - u32 num_init_headers; - u32 sample_rate, bitrate; - - u32 theora_kgs; - Float frame_rate; - u32 frame_rate_base; - - u32 type; -} OGGInfo; - -typedef struct -{ - ogg_stream_state os; - u32 serial_no; - /*DSI for ogg in mp4 - cf constants.h*/ - char *dsi; - u32 dsi_len; - - OGGInfo info; - Bool got_headers; - s64 seek_granule, last_granule; - - Bool is_running; - u32 parse_headers; - LPNETCHANNEL ch; - u16 ESID; - Bool eos_detected, map_time; - - u32 ogg_ts; - - GF_VorbisParser vp; -} OGGStream; - -typedef struct -{ - GF_ClientService *service; - GF_Thread *demuxer; - - GF_List *streams; - - FILE *ogfile; - u64 file_size; - Bool is_remote, is_inline; - u32 nb_playing, kill_demux, do_seek, service_type, init_remain, bos_done; - - /*ogg ogfile state*/ - ogg_sync_state oy; - - OGGStream *resync_stream; - - Bool has_video, has_audio, is_single_media; - - Double dur; - u32 data_buffer_ms; - - Bool needs_connection; - Double start_range, end_range; - /*file downloader*/ - GF_DownloadSession * dnload; - Bool is_live; - u32 tune_in_time; -} OGGReader; - - -void OGG_EndOfFile(OGGReader *read) -{ - OGGStream *st; - u32 i=0; - while ((st = (OGGStream*)gf_list_enum(read->streams, &i))) { - gf_service_send_packet(read->service, st->ch, NULL, 0, NULL, GF_EOS); - } -} - -#define OGG_BUFFER_SIZE 4096 - -static Bool OGG_ReadPage(OGGReader *read, ogg_page *oggpage) -{ - char buf[OGG_BUFFER_SIZE]; - GF_Err e; - - /*remote file, check if we use cache*/ - if (read->is_remote) { - u32 total_size; - GF_NetIOStatus status; - e = gf_dm_sess_get_stats(read->dnload, NULL, NULL, &total_size, NULL, NULL, &status); - /*not ready*/ - if ((e<GF_OK) || (status > GF_NETIO_DATA_EXCHANGE)) return GF_FALSE; - if (status == GF_NETIO_DATA_EXCHANGE) { - if (!total_size && !read->is_live) { - read->is_live = GF_TRUE; - read->tune_in_time = gf_sys_clock(); - } - else if (!read->is_live && !read->ogfile) { - const char *szCache = gf_dm_sess_get_cache_name(read->dnload); - if (!szCache) return GF_FALSE; - read->ogfile = gf_fopen((char *) szCache, "rb"); - if (!read->ogfile) return GF_FALSE; - } - } - } - - while (ogg_sync_pageout(&read->oy, oggpage ) != 1 ) { - char *buffer; - u32 bytes; - - if (read->ogfile) { - if (feof(read->ogfile)) { - OGG_EndOfFile(read); - return GF_FALSE; - } - bytes = (u32) fread(buf, 1, OGG_BUFFER_SIZE, read->ogfile); - } else { - e = gf_dm_sess_fetch_data(read->dnload, buf, OGG_BUFFER_SIZE, &bytes); - if (e) return GF_FALSE; - } - if (!bytes) return GF_FALSE; - buffer = ogg_sync_buffer(&read->oy, bytes); - memcpy(buffer, buf, bytes); - ogg_sync_wrote(&read->oy, bytes); - } - return GF_TRUE; -} - -static OGGStream *OGG_FindStreamForPage(OGGReader *read, ogg_page *oggpage) -{ - u32 i, count; - count = gf_list_count(read->streams); - for (i=0; i<count; i++) { - OGGStream *st = (OGGStream*)gf_list_get(read->streams, i); - if (ogg_stream_pagein(&st->os, oggpage) == 0) return st; - } - return NULL; -} - - - -static GF_ObjectDescriptor *OGG_GetOD(OGGStream *st) -{ - GF_ObjectDescriptor *od; - GF_ESD *esd; - - od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - od->objectDescriptorID = (st->info.streamType==GF_STREAM_AUDIO) ? 3 : 2; - esd = gf_odf_desc_esd_new(0); - esd->decoderConfig->streamType = st->info.streamType; - esd->decoderConfig->objectTypeIndication = GPAC_OTI_MEDIA_OGG; - esd->decoderConfig->avgBitrate = st->info.bitrate; - esd->ESID = st->ESID; - - esd->slConfig->useTimestampsFlag = 1; - esd->slConfig->useAccessUnitEndFlag = esd->slConfig->useAccessUnitStartFlag = 1; - esd->slConfig->timestampResolution = st->info.sample_rate ? st->info.sample_rate : (u32) (1000*st->info.frame_rate); - if (st->info.sample_rate) esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1; - else esd->slConfig->useRandomAccessPointFlag = 1; - - esd->decoderConfig->decoderSpecificInfo->dataLength = st->dsi_len; - esd->decoderConfig->decoderSpecificInfo->data = (char *) gf_malloc(sizeof(char) * st->dsi_len); - memcpy(esd->decoderConfig->decoderSpecificInfo->data, st->dsi, sizeof(char) * st->dsi_len); - gf_list_add(od->ESDescriptors, esd); - return od; -} - -u64 OGG_GranuleToTime(OGGInfo *cfg, s64 granule) -{ - if (cfg->sample_rate) { - return granule; - } - if (cfg->frame_rate) { - s64 iframe = granule>>cfg->theora_kgs; - s64 pframe = granule - (iframe<<cfg->theora_kgs); - pframe += iframe; - pframe *= cfg->frame_rate_base; - return (u64) (pframe / cfg->frame_rate); - } - return 0; -} - -Double OGG_GranuleToMediaTime(OGGInfo *cfg, s64 granule) -{ - Double t = (Double) (s64) OGG_GranuleToTime(cfg, granule); - if (cfg->sample_rate) t /= cfg->sample_rate; - else t /= cfg->frame_rate_base; - return t; -} - - -static void OGG_GetStreamInfo(ogg_packet *oggpacket, OGGInfo *info) -{ - oggpack_buffer opb; - - memset(info, 0, sizeof(OGGInfo)); - - /*vorbis*/ - if ((oggpacket->bytes >= 7) && !strncmp((char *) &oggpacket->packet[1], "vorbis", 6)) { - info->streamType = GF_STREAM_AUDIO; - oggpack_readinit(&opb, oggpacket->packet, oggpacket->bytes); - oggpack_adv( &opb, 88); - oggpack_adv( &opb, 8); /*nb chan*/ - info->sample_rate = oggpack_read(&opb, 32); - oggpack_adv( &opb, 32); /*max rate*/ - info->bitrate = oggpack_read(&opb, 32); - info->num_init_headers = 3; - info->type = OGG_VORBIS; - } - /*speex*/ - else if ((oggpacket->bytes >= 7) && !strncmp((char *) &oggpacket->packet[0], "Speex", 5)) { - info->streamType = GF_STREAM_AUDIO; - oggpack_readinit(&opb, oggpacket->packet, oggpacket->bytes); - oggpack_adv(&opb, 224); - oggpack_adv(&opb, 32); - oggpack_adv( &opb, 32); - info->sample_rate = oggpack_read(&opb, 32); - info->type = OGG_SPEEX; - info->num_init_headers = 1; - } - /*flac*/ - else if ((oggpacket->bytes >= 4) && !strncmp((char *) &oggpacket->packet[0], "fLaC", 4)) { - info->streamType = GF_STREAM_AUDIO; - info->type = 3; - info->num_init_headers = OGG_FLAC; - } - /*theora*/ - else if ((oggpacket->bytes >= 7) && !strncmp((char *) &oggpacket->packet[1], "theora", 6)) { - GF_BitStream *bs; - u32 fps_numerator, fps_denominator, keyframe_freq_force; - - info->streamType = GF_STREAM_VISUAL; - info->type = OGG_THEORA; - bs = gf_bs_new((char *) oggpacket->packet, oggpacket->bytes, GF_BITSTREAM_READ); - gf_bs_read_int(bs, 56); - gf_bs_read_int(bs, 8); /* major version num */ - gf_bs_read_int(bs, 8); /* minor version num */ - gf_bs_read_int(bs, 8); /* subminor version num */ - gf_bs_read_int(bs, 16) /*<< 4*/; /* width */ - gf_bs_read_int(bs, 16) /*<< 4*/; /* height */ - gf_bs_read_int(bs, 24); /* frame width */ - gf_bs_read_int(bs, 24); /* frame height */ - gf_bs_read_int(bs, 8); /* x offset */ - gf_bs_read_int(bs, 8); /* y offset */ - fps_numerator = gf_bs_read_u32(bs); - fps_denominator = gf_bs_read_u32(bs); - gf_bs_read_int(bs, 24); /* aspect_numerator */ - gf_bs_read_int(bs, 24); /* aspect_denominator */ - gf_bs_read_int(bs, 8); /* colorspace */ - gf_bs_read_int(bs, 24);/* bitrate */ - gf_bs_read_int(bs, 6); /* quality */ - - keyframe_freq_force = 1 << gf_bs_read_int(bs, 5); - info->theora_kgs = 0; - keyframe_freq_force--; - while (keyframe_freq_force) { - info->theora_kgs ++; - keyframe_freq_force >>= 1; - } - info->frame_rate = ((Float)fps_numerator) / fps_denominator; - info->num_init_headers = 3; - gf_bs_del(bs); - info->frame_rate_base = fps_denominator; - } -} - -static void OGG_ResetupStream(OGGReader *read, OGGStream *st, ogg_page *oggpage) -{ - ogg_stream_clear(&st->os); - ogg_stream_init(&st->os, st->serial_no); - ogg_stream_pagein(&st->os, oggpage); - st->parse_headers = st->info.num_init_headers; - - if (st->info.sample_rate) { - st->seek_granule = (s64) (read->start_range * st->info.sample_rate); - } else if (st->info.frame_rate) { - s64 seek = (s64) (read->start_range * st->info.frame_rate) - 1; - if (seek<0) seek=0; - st->seek_granule = (seek)<<st->info.theora_kgs; - } - st->last_granule = -1; -} - -static void OGG_NewStream(OGGReader *read, ogg_page *oggpage) -{ - ogg_packet oggpacket; - u32 serial_no, i; - OGGStream *st; - - /*reannounce of stream (caroussel in live streams) - until now I don't think icecast uses this*/ - serial_no = ogg_page_serialno(oggpage); - i=0; - while ((st = (OGGStream*)gf_list_enum(read->streams, &i))) { - if (st->serial_no==serial_no) { - OGG_ResetupStream(read, st, oggpage); - return; - } - } - - /*look if we have the same stream defined (eg, reuse first stream dead with same header page)*/ - i=0; - while ((st = (OGGStream*)gf_list_enum(read->streams, &i))) { - if (st->eos_detected) { - ogg_stream_state os; - ogg_stream_init(&os, serial_no); - ogg_stream_pagein(&os, oggpage); - ogg_stream_packetpeek(&os, &oggpacket); - if (st->dsi && !memcmp(st->dsi, oggpacket.packet, oggpacket.bytes)) { - ogg_stream_clear(&os); - st->serial_no = serial_no; - OGG_ResetupStream(read, st, oggpage); - return; - } - ogg_stream_clear(&os); - /*nope streams are different, signal eos on this one*/ - gf_service_send_packet(read->service, st->ch, NULL, 0, NULL, GF_EOS); - } - } - - GF_SAFEALLOC(st, OGGStream); - if (!st) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[OGG] Failed to allocate stream for demux\n")); - return; - } - st->serial_no = serial_no; - ogg_stream_init(&st->os, st->serial_no); - ogg_stream_pagein(&st->os, oggpage); - - ogg_stream_packetpeek(&st->os, &oggpacket); - OGG_GetStreamInfo(&oggpacket, &st->info); - - /*check we don't discard audio or visual streams*/ - if ( ((read->service_type==1) && (st->info.streamType==GF_STREAM_AUDIO)) - || ((read->service_type==2) && (st->info.streamType==GF_STREAM_VISUAL)) ) - { - ogg_stream_clear(&st->os); - gf_free(st); - return; - } - - gf_list_add(read->streams, st); - st->ESID = 2 + gf_list_count(read->streams); - st->parse_headers = st->info.num_init_headers; - if (st->parse_headers) read->init_remain++; - - if (st->info.sample_rate) { - st->seek_granule = (s64) (read->start_range * st->info.sample_rate); - } else if (st->info.frame_rate) { - s64 seek = (s64) (read->start_range * st->info.frame_rate) - 1; - if (seek<0) seek=0; - st->seek_granule = (seek)<<st->info.theora_kgs; - } - st->last_granule = -1; - - if (st->info.streamType==GF_STREAM_VISUAL) { - read->has_video = GF_TRUE; - } else { - read->has_audio = GF_TRUE; - } - if (st->got_headers && read->is_inline) gf_service_declare_media(read->service, (GF_Descriptor*) OGG_GetOD(st), GF_FALSE); -} - -void OGG_SignalEndOfStream(OGGReader *read, OGGStream *st) -{ - if (st->eos_detected) { - gf_service_send_packet(read->service, st->ch, NULL, 0, NULL, GF_EOS); - ogg_stream_clear(&st->os); - } -} - -/*GFINLINE*/ -void OGG_SendPackets(OGGReader *read, OGGStream *st, ogg_packet *oggpacket) -{ - GF_SLHeader slh; - memset(&slh, 0, sizeof(GF_SLHeader)); - if (st->info.type==OGG_VORBIS) { - slh.accessUnitEndFlag = slh.accessUnitStartFlag = 1; - slh.randomAccessPointFlag = 1; - slh.compositionTimeStampFlag = 1; - slh.compositionTimeStamp = st->ogg_ts; - gf_service_send_packet(read->service, st->ch, (char *) oggpacket->packet, oggpacket->bytes, &slh, GF_OK); - st->ogg_ts += gf_vorbis_check_frame(&st->vp, (char *) oggpacket->packet, oggpacket->bytes); - } - else if (st->info.type==OGG_THEORA) { - oggpack_buffer opb; - oggpackB_readinit(&opb, oggpacket->packet, oggpacket->bytes); - /*new frame*/ - if (oggpackB_read(&opb, 1) == 0) { - slh.accessUnitStartFlag = slh.accessUnitEndFlag = 1; - /*add packet*/ - slh.randomAccessPointFlag = oggpackB_read(&opb, 1) ? 0 : 1; - slh.compositionTimeStampFlag = 1; - slh.compositionTimeStamp = st->ogg_ts; - gf_service_send_packet(read->service, st->ch, (char *) oggpacket->packet, oggpacket->bytes, &slh, GF_OK); - st->ogg_ts += 1000; - } - } -} - -void OGG_Process(OGGReader *read) -{ - OGGStream *st; - ogg_packet oggpacket; - ogg_page oggpage; - - if (read->resync_stream) { - st = read->resync_stream; - read->resync_stream = NULL; - goto process_stream; - } - - if (!OGG_ReadPage(read, &oggpage)) { - return; - } - - if (ogg_page_bos(&oggpage)) { - OGG_NewStream(read, &oggpage); - return; - } - - st = OGG_FindStreamForPage(read, &oggpage); - if (!st) { - if (!read->bos_done && read->is_live) { - u32 now = gf_sys_clock(); - if (now-read->tune_in_time > 1000) { - GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[OGG]: Waiting for tune in...\n")); - read->tune_in_time = now; - } - } - return; - } - - if (ogg_page_eos(&oggpage)) - st->eos_detected = GF_TRUE; - - if (st->parse_headers && !st->got_headers) { - while (ogg_stream_packetout(&st->os, &oggpacket ) > 0 ) { - GF_BitStream *bs; - if (st->info.type==OGG_VORBIS) - gf_vorbis_parse_header(&st->vp, (char *) oggpacket.packet, oggpacket.bytes); - - bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - if (st->dsi) { - gf_bs_write_data(bs, st->dsi, st->dsi_len); - gf_free(st->dsi); - st->dsi = NULL; - st->dsi_len=0; - } - gf_bs_write_u16(bs, oggpacket.bytes); - gf_bs_write_data(bs, (char *) oggpacket.packet, oggpacket.bytes); - gf_bs_get_content(bs, (char **)&st->dsi, &st->dsi_len); - gf_bs_del(bs); - st->parse_headers--; - if (!st->parse_headers) { - st->got_headers = GF_TRUE; - if (read->is_inline) - gf_service_declare_media(read->service, (GF_Descriptor*) OGG_GetOD(st), GF_FALSE); - break; - } - } - if (!st->got_headers) return; - assert(read->init_remain); - read->init_remain--; - if (!read->init_remain) read->bos_done = 1; - return; - } - /*from here we should have passed all headers*/ - if (read->init_remain) return; - -process_stream: - /*live insertion (not supported yet, just a reminder)*/ - if (!st->ch) { - read->resync_stream = st; - return; - } - - while (ogg_stream_packetout(&st->os, &oggpacket ) > 0 ) { - if (oggpacket.granulepos != -1) { - st->last_granule = oggpacket.granulepos; - } - /*stream reinit, don't resend headers*/ - if (st->parse_headers) st->parse_headers--; - else if (st->map_time) { - Double t; - if (read->start_range && (oggpacket.granulepos==-1)) continue; - t = OGG_GranuleToMediaTime(&st->info, st->last_granule); - if (t>=read->start_range) { - GF_NetworkCommand map; - map.command_type = GF_NET_CHAN_MAP_TIME; - map.map_time.on_channel = st->ch; - map.map_time.reset_buffers = (read->start_range>0.2) ? GF_TRUE : GF_FALSE; - map.map_time.timestamp = st->ogg_ts = 0; - map.map_time.media_time = t; - gf_service_command(read->service, &map, GF_OK); - st->map_time = GF_FALSE; - OGG_SendPackets(read, st, &oggpacket); - } - } - else { - OGG_SendPackets(read, st, &oggpacket); - } - } -} - -static u32 OggDemux(void *par) -{ - GF_NetworkCommand com; - Bool go; - u32 i, count; - OGGStream *st; - OGGReader *read = (OGGReader *) par; - - read->bos_done = 0; - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.command_type = GF_NET_CHAN_BUFFER_QUERY; - - if (read->needs_connection) { - read->needs_connection = GF_FALSE; - gf_service_connect_ack(read->service, NULL, GF_OK); - } - - ogg_sync_init(&read->oy); - - - while (!read->kill_demux) { - OGG_Process(read); - - if (!read->bos_done) continue; - - /*wait for stream connection*/ - while (!read->kill_demux && !read->nb_playing) { - gf_sleep(20); - } - - /*(re)starting, seek*/ - if (read->do_seek) { - read->do_seek = 0; - ogg_sync_clear(&read->oy); - ogg_sync_init(&read->oy); -// OGG_SendStreams(read); - - if (read->ogfile) { - u32 seek_to = 0; - read->resync_stream = NULL; - if (read->dur) seek_to = (u32) (read->file_size * (read->start_range/read->dur) * 0.6f); - if ((s32) seek_to > gf_ftell(read->ogfile) ) { - gf_fseek(read->ogfile, seek_to, SEEK_SET); - } else { - gf_fseek(read->ogfile, 0, SEEK_SET); - } - } - } - - - /*sleep untill the buffer occupancy is too low - note that this work because all streams in this - demuxer are synchronized*/ - go = read->nb_playing; - while (go && !read->kill_demux) { - count = gf_list_count(read->streams); - for (i=0; i<count; i++) { - st = (OGGStream*)gf_list_get(read->streams, i); - if (!st->ch) continue; - com.base.on_channel = st->ch; - gf_service_command(read->service, &com, GF_OK); - if (com.buffer.occupancy < read->data_buffer_ms) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[OGG] channel %d needs fill (%d ms data, %d max buffer)\n", st->ESID, com.buffer.occupancy, read->data_buffer_ms)); - go = GF_FALSE; - break; - } - } - if (!i || !read->nb_playing) break; - gf_sleep(0); - } - } - ogg_sync_clear(&read->oy); - read->kill_demux=2; - return 0; -} - -/*get streams & duration*/ -Bool OGG_CheckFile(OGGReader *read) -{ - OGGInfo info, the_info; - ogg_page oggpage; - ogg_packet oggpacket; - ogg_stream_state os, the_os; - u64 max_gran; - Bool has_stream = GF_FALSE; - gf_fseek(read->ogfile, 0, SEEK_SET); - - ogg_sync_init(&read->oy); - memset(&the_info, 0, sizeof(OGGInfo)); - max_gran = 0; - while (1) { - if (!OGG_ReadPage(read, &oggpage)) break; - - if (ogg_page_bos(&oggpage)) { - ogg_stream_init(&os, ogg_page_serialno(&oggpage)); - if (ogg_stream_pagein(&os, &oggpage) >= 0 ) { - ogg_stream_packetpeek(&os, &oggpacket); - if (ogg_stream_pagein(&os, &oggpage) >= 0 ) { - ogg_stream_packetpeek(&os, &oggpacket); - OGG_GetStreamInfo(&oggpacket, &info); - } - if (!has_stream) { - has_stream = GF_TRUE; - ogg_stream_init(&the_os, ogg_page_serialno(&oggpage)); - the_info = info; - } - } - ogg_stream_clear(&os); - continue; - } - if (has_stream && (ogg_stream_pagein(&the_os, &oggpage) >= 0) ) { - while (ogg_stream_packetout(&the_os, &oggpacket ) > 0 ) { - if ((oggpacket.granulepos>=0) && ((u64) oggpacket.granulepos>max_gran) ) { - max_gran = oggpacket.granulepos; - } - } - } - } - ogg_sync_clear(&read->oy); - read->file_size = gf_ftell(read->ogfile); - gf_fseek(read->ogfile, 0, SEEK_SET); - read->dur = 0; - if (has_stream) { - ogg_stream_clear(&the_os); - read->dur = (Double) (s64) OGG_GranuleToTime(&the_info, max_gran); - if (the_info.sample_rate) read->dur /= the_info.sample_rate; - else read->dur /= the_info.frame_rate_base; - } - return has_stream; -} - -static const char * OGG_MIMES_AUDIO[] = { - "audio/ogg", "audio/x-ogg", "audio/x-vorbis+ogg", NULL -}; - -static const char * OGG_MIMES_AUDIO_EXT = "oga spx"; - -static const char * OGG_MIMES_AUDIO_DESC = "Xiph.org OGG Music"; - -static const char * OGG_MIMES_VIDEO[] = { - "application/ogg", "application/x-ogg", "video/ogg", "video/x-ogg", "video/x-ogm+ogg", NULL -}; - -static const char * OGG_MIMES_VIDEO_DESC = "Xiph.org OGG Movie"; - -static const char * OGG_MIMES_VIDEO_EXT = "ogg ogv oggm"; - -static u32 OGG_RegisterMimeTypes(const GF_InputService *plug) { - u32 i, c; - for (i = 0 ; OGG_MIMES_AUDIO[i]; i++) - gf_service_register_mime(plug, OGG_MIMES_AUDIO[i], OGG_MIMES_AUDIO_EXT, OGG_MIMES_AUDIO_DESC); - c = i; - for (i = 0 ; OGG_MIMES_VIDEO[i]; i++) - gf_service_register_mime(plug, OGG_MIMES_VIDEO[i], OGG_MIMES_VIDEO_EXT, OGG_MIMES_VIDEO_DESC); - return c+i; -} - -static Bool OGG_CanHandleURL(GF_InputService *plug, const char *url) -{ - char *sExt; - u32 i; - sExt = strrchr(url, '.'); - for (i = 0 ; OGG_MIMES_AUDIO[i]; i++) { - if (gf_service_check_mime_register(plug, OGG_MIMES_AUDIO[i], OGG_MIMES_AUDIO_EXT, OGG_MIMES_AUDIO_DESC, sExt)) - return GF_TRUE; - } - for (i = 0 ; OGG_MIMES_VIDEO[i]; i++) { - if (gf_service_check_mime_register(plug, OGG_MIMES_VIDEO[i], OGG_MIMES_VIDEO_EXT, OGG_MIMES_VIDEO_DESC, sExt)) - return GF_TRUE; - } - return GF_FALSE; -} - -static Bool ogg_is_local(const char *url) -{ - if (!strnicmp(url, "file://", 7)) return GF_TRUE; - if (strstr(url, "://")) return GF_FALSE; - return GF_TRUE; -} - -void OGG_NetIO(void *cbk, GF_NETIO_Parameter *param) -{ - OGGReader *read = (OGGReader *) cbk; - - gf_service_download_update_stats(read->dnload); - - /*done*/ - if ((param->msg_type==GF_NETIO_DATA_TRANSFERED) && read->ogfile) { - read->is_remote = GF_FALSE; - /*reload file*/ - OGG_CheckFile(read); - return; - } - if (param->error && read->needs_connection) { - read->needs_connection = GF_FALSE; - read->kill_demux = 2; - gf_service_connect_ack(read->service, NULL, param->error); - } - /*we never receive data from here since the downloader is not threaded*/ -} - -void OGG_DownloadFile(GF_InputService *plug, char *url) -{ - OGGReader *read = (OGGReader*) plug->priv; - - read->dnload = gf_service_download_new(read->service, url, GF_NETIO_SESSION_NOT_THREADED, OGG_NetIO, read); - if (!read->dnload) { - read->kill_demux=2; - read->needs_connection = GF_FALSE; - gf_service_connect_ack(read->service, NULL, GF_NOT_SUPPORTED); - } - /*service confirm is done once fetched, but start the demuxer thread*/ - gf_th_run(read->demuxer, OggDemux, read); -} - - -static GF_Err OGG_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url) -{ - char szURL[2048]; - char *ext; - GF_Err reply=GF_OK; - OGGReader *read = (OGGReader*)plug->priv; - read->service = serv; - - if (read->dnload) gf_service_download_del(read->dnload); - read->dnload = NULL; - - read->service_type = 0; - strcpy(szURL, url); - ext = strrchr(szURL, '#'); - if (ext) { - if (!strcmp(ext, "#video")) read->service_type = 1; - else if (!strcmp(ext, "#audio")) read->service_type = 2; - ext[0] = 0; - } - - /*remote fetch*/ - read->is_remote = !ogg_is_local(szURL); - if (read->is_remote) { - read->needs_connection = GF_TRUE; - OGG_DownloadFile(plug, szURL); - return GF_OK; - } else { - read->ogfile = gf_fopen(szURL, "rb"); - if (!read->ogfile) { - reply = GF_URL_ERROR; - } else { - /*init ogg file in local mode*/ - if (!OGG_CheckFile(read)) { - gf_fclose(read->ogfile); - reply = GF_NON_COMPLIANT_BITSTREAM; - } else { - read->needs_connection = GF_TRUE; - /*start the demuxer thread*/ - gf_th_run(read->demuxer, OggDemux, read); - return GF_OK; - } - } - } - /*error*/ - read->kill_demux=2; - gf_service_connect_ack(serv, NULL, reply); - return GF_OK; -} - -static GF_Err OGG_CloseService(GF_InputService *plug) -{ - OGGReader *read = (OGGReader*)plug->priv; - if (!read->kill_demux) { - read->kill_demux = 1; - while (read->kill_demux!=2) gf_sleep(2); - } - - if (read->ogfile) gf_fclose(read->ogfile); - read->ogfile = NULL; - if (read->dnload) gf_service_download_del(read->dnload); - read->dnload = NULL; - gf_service_disconnect_ack(read->service, NULL, GF_OK); - return GF_OK; -} - -static GF_Descriptor *OGG_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) -{ - u32 i; - GF_ObjectDescriptor *od; - OGGStream *st; - OGGReader *read = (OGGReader*)plug->priv; - /*since we don't handle multitrack in ogg yes, we don't need to check sub_url, only use expected type*/ - - /*single object*/ - if ((expect_type==GF_MEDIA_OBJECT_AUDIO) || (expect_type==GF_MEDIA_OBJECT_VIDEO)) { - if ((expect_type==GF_MEDIA_OBJECT_AUDIO) && !read->has_audio) return NULL; - if ((expect_type==GF_MEDIA_OBJECT_VIDEO) && !read->has_video) return NULL; - i=0; - while ((st = (OGGStream*)gf_list_enum(read->streams, &i))) { - if ((expect_type==GF_MEDIA_OBJECT_AUDIO) && (st->info.streamType!=GF_STREAM_AUDIO)) continue; - if ((expect_type==GF_MEDIA_OBJECT_VIDEO) && (st->info.streamType!=GF_STREAM_VISUAL)) continue; - - od = OGG_GetOD(st); - read->is_single_media = GF_TRUE; - return (GF_Descriptor *) od; - } - /*not supported yet - we need to know what's in the ogg stream for that*/ - } - read->is_inline = GF_TRUE; - return NULL; -} - -static GF_Err OGG_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) -{ - u32 ES_ID, i; - GF_Err e; - OGGStream *st; - OGGReader *read = (OGGReader*)plug->priv; - - ES_ID=0; - e = GF_STREAM_NOT_FOUND; - if (strstr(url, "ES_ID")) { - sscanf(url, "ES_ID=%u", &ES_ID); - } - /*URL setup*/ -// else if (!read->es_ch && OGG_CanHandleURL(plug, url)) ES_ID = 3; - - i=0; - while ((st = (OGGStream*)gf_list_enum(read->streams, &i))) { - if (st->ESID==ES_ID) { - st->ch = channel; - e = GF_OK; - break; - } - } - gf_service_connect_ack(read->service, channel, e); - return e; -} - -static GF_Err OGG_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) -{ - GF_Err e; - OGGStream *st; - u32 i=0; - OGGReader *read = (OGGReader*)plug->priv; - - e = GF_STREAM_NOT_FOUND; - while ((st = (OGGStream*)gf_list_enum(read->streams, &i))) { - if (st->ch==channel) { - st->ch = NULL; - e = GF_OK; - break; - } - } - gf_service_disconnect_ack(read->service, channel, e); - return GF_OK; -} - -static GF_Err OGG_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) -{ - OGGStream *st; - u32 i; - OGGReader *read = (OGGReader*)plug->priv; - - if (!com->base.on_channel) { - /*if live session we may cache*/ - if (read->is_live && (com->command_type==GF_NET_IS_CACHABLE)) return GF_OK; - return GF_NOT_SUPPORTED; - } - switch (com->command_type) { - case GF_NET_CHAN_SET_PULL: - /*no way to demux streams independently, and we keep OD as dynamic ogfile to handle - chained streams*/ - return GF_NOT_SUPPORTED; - case GF_NET_CHAN_INTERACTIVE: - //live: return GF_NOT_SUPPORTED; - return GF_OK; - case GF_NET_CHAN_BUFFER: - com->buffer.min = com->buffer.max = 0; - if (read->is_live) com->buffer.max = read->data_buffer_ms; - return GF_OK; - case GF_NET_CHAN_SET_PADDING: - return GF_NOT_SUPPORTED; - - case GF_NET_CHAN_DURATION: - com->duration.duration = read->dur; - return GF_OK; - case GF_NET_CHAN_PLAY: - read->start_range = com->play.start_range; - read->end_range = com->play.end_range; - i=0; - while ((st = (OGGStream*)gf_list_enum(read->streams, &i))) { - if (st->ch == com->base.on_channel) { - st->is_running = GF_TRUE; - st->map_time = read->dur ? GF_TRUE : GF_FALSE; - if (!read->nb_playing) read->do_seek = 1; - read->nb_playing ++; - break; - } - } - /*recfg duration in case*/ - if (!read->is_remote && read->dur) { - GF_NetworkCommand rcfg; - rcfg.base.on_channel = NULL; - rcfg.base.command_type = GF_NET_CHAN_DURATION; - rcfg.duration.duration = read->dur; - gf_service_command(read->service, &rcfg, GF_OK); - } - return GF_OK; - case GF_NET_CHAN_STOP: - i=0; - while ((st = (OGGStream*)gf_list_enum(read->streams, &i))) { - if (st->ch == com->base.on_channel) { - st->is_running = GF_FALSE; - read->nb_playing --; - break; - } - } - return GF_OK; - default: - return GF_OK; - } -} - -static Bool OGG_CanHandleURLInService(GF_InputService *plug, const char *url) -{ - char szURL[2048], *sep; - OGGReader *read = (OGGReader *)plug->priv; - const char *this_url = gf_service_get_url(read->service); - if (!this_url || !url) return GF_FALSE; - - strcpy(szURL, this_url); - sep = strrchr(szURL, '#'); - if (sep) sep[0] = 0; - - if ((url[0] != '#') && strnicmp(szURL, url, sizeof(char)*strlen(szURL))) return GF_FALSE; - sep = strrchr(url, '#'); - if (!stricmp(sep, "#video") && (read->has_video)) return GF_TRUE; - if (!stricmp(sep, "#audio") && (read->has_audio)) return GF_TRUE; - return GF_FALSE; -} - -GF_InputService *OGG_LoadDemux() -{ - OGGReader *reader; - GF_InputService *plug = (GF_InputService*)gf_malloc(sizeof(GF_InputService)); - memset(plug, 0, sizeof(GF_InputService)); - GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC OGG Reader", "gpac distribution") - plug->RegisterMimeTypes = OGG_RegisterMimeTypes; - plug->CanHandleURL = OGG_CanHandleURL; - plug->ConnectService = OGG_ConnectService; - plug->CloseService = OGG_CloseService; - plug->GetServiceDescriptor = OGG_GetServiceDesc; - plug->ConnectChannel = OGG_ConnectChannel; - plug->DisconnectChannel = OGG_DisconnectChannel; - plug->ServiceCommand = OGG_ServiceCommand; - plug->CanHandleURLInService = OGG_CanHandleURLInService; - - reader = (OGGReader*)gf_malloc(sizeof(OGGReader)); - memset(reader, 0, sizeof(OGGReader)); - reader->streams = gf_list_new(); - reader->demuxer = gf_th_new("OGGDemux"); - reader->data_buffer_ms = 1000; - - plug->priv = reader; - return plug; -} - -void OGG_DeleteDemux(void *ifce) -{ - GF_InputService *plug = (GF_InputService *) ifce; - OGGReader *read = (OGGReader*)plug->priv; - gf_th_del(read->demuxer); - - /*just in case something went wrong*/ - while (gf_list_count(read->streams)) { - OGGStream *st = (OGGStream*)gf_list_get(read->streams, 0); - gf_list_rem(read->streams, 0); - ogg_stream_clear(&st->os); - if (st->dsi) gf_free(st->dsi); - gf_free(st); - } - gf_list_del(read->streams); - gf_free(read); - gf_free(plug); -} - -#endif /* !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_OGG)*/ diff --git a/modules/ogg/ogg_in.h b/modules/ogg/ogg_in.h deleted file mode 100644 index 824f474..0000000 --- a/modules/ogg/ogg_in.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / XIPH.org module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#ifndef OGG_IN_H -#define OGG_IN_H - -#include <gpac/modules/service.h> -#include <gpac/modules/codec.h> -#include <gpac/avparse.h> -#include <gpac/constants.h> -#include <gpac/thread.h> - -GF_InputService *OGG_LoadDemux(); -void OGG_DeleteDemux(void *ifce); - - -enum -{ - OGG_VORBIS = 1, - OGG_SPEEX, - OGG_FLAC, - OGG_THEORA -}; - -typedef struct -{ - u32 type; - void *opaque; -} OGGWraper; - -#ifdef GPAC_HAS_VORBIS -u32 NewVorbisDecoder(GF_BaseDecoder *ifcd); -void DeleteVorbisDecoder(GF_BaseDecoder *ifcg); -#endif - -#ifdef GPAC_HAS_THEORA -u32 NewTheoraDecoder(GF_BaseDecoder *ifcd); -void DeleteTheoraDecoder(GF_BaseDecoder *ifcg); -#endif - -#endif - diff --git a/modules/ogg/ogg_load.c b/modules/ogg/ogg_load.c deleted file mode 100644 index 0970eaa..0000000 --- a/modules/ogg/ogg_load.c +++ /dev/null @@ -1,165 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / XIPH.org module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "ogg_in.h" - - -#if !defined(__GNUC__) -# if (defined(_WIN32_WCE) || defined (WIN32)) -# pragma comment(lib, "libogg_static") -# pragma comment(lib, "libvorbis_static") -# pragma comment(lib, "libtheora_static") -# endif -#endif - - -static u32 OGG_CanHandleStream(GF_BaseDecoder *dec, u32 StreamType, GF_ESD *esd, u8 PL) -{ - /*media type query*/ - if (!esd) return ((StreamType == GF_STREAM_VISUAL) || (StreamType == GF_STREAM_AUDIO)) ? GF_CODEC_STREAM_TYPE_SUPPORTED : GF_CODEC_NOT_SUPPORTED; - /*video decs*/ - if (StreamType == GF_STREAM_VISUAL) { -#ifdef GPAC_HAS_THEORA - char *dsi = esd->decoderConfig->decoderSpecificInfo ? esd->decoderConfig->decoderSpecificInfo->data : NULL; -#endif - switch (esd->decoderConfig->objectTypeIndication) { -#ifdef GPAC_HAS_THEORA - case GPAC_OTI_MEDIA_OGG: - if (dsi && (esd->decoderConfig->decoderSpecificInfo->dataLength>=9) && !strncmp((char *) &dsi[3], "theora", 6)) { - if (NewTheoraDecoder(dec)) return GF_CODEC_SUPPORTED; - } - return GF_CODEC_NOT_SUPPORTED; -#endif - default: - return GF_CODEC_NOT_SUPPORTED; - } - } - /*audio decs*/ - if (StreamType == GF_STREAM_AUDIO) { -#ifdef GPAC_HAS_VORBIS - char *dsi = esd->decoderConfig->decoderSpecificInfo ? esd->decoderConfig->decoderSpecificInfo->data : NULL; -#endif - switch (esd->decoderConfig->objectTypeIndication) { -#ifdef GPAC_HAS_VORBIS - case GPAC_OTI_MEDIA_OGG: - if (dsi && (esd->decoderConfig->decoderSpecificInfo->dataLength>=9) && !strncmp((char *) &dsi[3], "vorbis", 6)) { - if (NewVorbisDecoder(dec)) return GF_CODEC_SUPPORTED; - } - return GF_CODEC_NOT_SUPPORTED; -#endif - default: - return GF_CODEC_NOT_SUPPORTED; - } - } - return GF_CODEC_NOT_SUPPORTED; -} - - -GF_BaseDecoder *OGG_LoadDecoder() -{ - GF_MediaDecoder *ifce; - OGGWraper *wrap; - GF_SAFEALLOC(ifce, GF_MediaDecoder); - if (!ifce) return NULL; - GF_SAFEALLOC(wrap, OGGWraper); - if (!wrap) { - gf_free(ifce); - return NULL; - } - ifce->privateStack = wrap; - ifce->CanHandleStream = OGG_CanHandleStream; - GF_REGISTER_MODULE_INTERFACE(ifce, GF_MEDIA_DECODER_INTERFACE, "GPAC XIPH.org package", "gpac distribution") - - /*other interfaces will be setup at run time*/ - return (GF_BaseDecoder *)ifce; -} - -void DeleteOGGDecoder(GF_BaseDecoder *ifcd) -{ - OGGWraper *wrap; - if (!ifcd) - return; - wrap = (OGGWraper *)ifcd->privateStack; - if (wrap) { - switch (wrap->type) { -#ifdef GPAC_HAS_VORBIS - case OGG_VORBIS: - DeleteVorbisDecoder(ifcd); - break; -#endif -#ifdef GPAC_HAS_THEORA - case OGG_THEORA: - DeleteTheoraDecoder(ifcd); - break; -#endif - default: - break; - } - gf_free(wrap); - ifcd->privateStack = NULL; - } - gf_free(ifcd); -} - - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_OGG) - GF_NET_CLIENT_INTERFACE, -#endif - GF_MEDIA_DECODER_INTERFACE, - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ -#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_OGG) - if (InterfaceType == GF_NET_CLIENT_INTERFACE) return (GF_BaseInterface *)OGG_LoadDemux(); -#endif - if (InterfaceType == GF_MEDIA_DECODER_INTERFACE) return (GF_BaseInterface *)OGG_LoadDecoder(); - return NULL; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_OGG) - case GF_NET_CLIENT_INTERFACE: - OGG_DeleteDemux(ifce); - break; -#endif - case GF_MEDIA_DECODER_INTERFACE: - DeleteOGGDecoder((GF_BaseDecoder *) ifce); - break; - } -} - -GPAC_MODULE_STATIC_DECLARATION( ogg_in ) diff --git a/modules/ogg/theora_dec.c b/modules/ogg/theora_dec.c deleted file mode 100644 index d3d6a7b..0000000 --- a/modules/ogg/theora_dec.c +++ /dev/null @@ -1,235 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / XIPH.org module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include "ogg_in.h" - -#ifdef GPAC_HAS_THEORA - -#include <theora/theora.h> - -typedef struct -{ - theora_info ti; - theora_state td; - theora_comment tc; - ogg_packet op; - - u16 ES_ID; - Bool has_reconfigured; -} TheoraDec; - -#define THEORACTX() TheoraDec *ctx = (TheoraDec *) ((OGGWraper *)ifcg->privateStack)->opaque - -static GF_Err THEO_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ - ogg_packet oggpacket; - GF_BitStream *bs; - - THEORACTX(); - if (ctx->ES_ID) return GF_BAD_PARAM; - - if (!esd->decoderConfig->decoderSpecificInfo) return GF_NON_COMPLIANT_BITSTREAM; - - if (esd->decoderConfig->objectTypeIndication != GPAC_OTI_MEDIA_OGG) return GF_NON_COMPLIANT_BITSTREAM; - if ( (esd->decoderConfig->decoderSpecificInfo->dataLength<9) || strncmp(&esd->decoderConfig->decoderSpecificInfo->data[3], "theora", 6)) return GF_NON_COMPLIANT_BITSTREAM; - - oggpacket.granulepos = -1; - oggpacket.b_o_s = 1; - oggpacket.e_o_s = 0; - oggpacket.packetno = 0; - - ctx->ES_ID = esd->ESID; - - theora_info_init(&ctx->ti); - theora_comment_init(&ctx->tc); - - - bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ); - while (gf_bs_available(bs)) { - oggpacket.bytes = gf_bs_read_u16(bs); - oggpacket.packet = gf_malloc(sizeof(char) * oggpacket.bytes); - gf_bs_read_data(bs, oggpacket.packet, oggpacket.bytes); - if (theora_decode_header(&ctx->ti, &ctx->tc, &oggpacket) < 0 ) { - gf_free(oggpacket.packet); - gf_bs_del(bs); - return GF_NON_COMPLIANT_BITSTREAM; - } - gf_free(oggpacket.packet); - } - theora_decode_init(&ctx->td, &ctx->ti); - gf_bs_del(bs); - return GF_OK; -} - -static GF_Err THEO_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - THEORACTX(); - if (ctx->ES_ID != ES_ID) return GF_BAD_PARAM; - - theora_clear(&ctx->td); - theora_info_clear(&ctx->ti); - theora_comment_clear(&ctx->tc); - - ctx->ES_ID = 0; - return GF_OK; -} -static GF_Err THEO_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - THEORACTX(); - switch (capability->CapCode) { - case GF_CODEC_WIDTH: - capability->cap.valueInt = ctx->ti.width; - break; - case GF_CODEC_HEIGHT: - capability->cap.valueInt = ctx->ti.height; - break; - case GF_CODEC_STRIDE: - capability->cap.valueInt = ctx->ti.width; - break; - case GF_CODEC_FPS: - capability->cap.valueFloat = (Float) ctx->ti.fps_numerator; - capability->cap.valueFloat /= ctx->ti.fps_denominator; - break; - case GF_CODEC_PIXEL_FORMAT: - capability->cap.valueInt = GF_PIXEL_YV12; - break; - case GF_CODEC_REORDER: - capability->cap.valueInt = 0; - break; - case GF_CODEC_RESILIENT: - capability->cap.valueInt = 1; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = 3*ctx->ti.width * ctx->ti.height / 2; - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = 1; - break; - case GF_CODEC_BUFFER_MAX: - capability->cap.valueInt = 4; - break; - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 0; - break; - default: - capability->cap.valueInt = 0; - break; - } - return GF_OK; -} - -static GF_Err THEO_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - /*return unsupported to avoid confusion by the player (like SR changing ...) */ - return GF_NOT_SUPPORTED; -} - - -static GF_Err THEO_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ - ogg_packet op; - yuv_buffer yuv; - u32 i; - char *pYO, *pUO, *pVO; - unsigned char *pYD, *pUD, *pVD; - - THEORACTX(); - /*not using scalabilty*/ - assert(ctx->ES_ID == ES_ID); - - op.granulepos = -1; - op.b_o_s = 0; - op.e_o_s = 0; - op.packetno = 0; - op.packet = inBuffer; - op.bytes = inBufferLength; - - - *outBufferLength = 0; - - if (theora_decode_packetin(&ctx->td, &op)<0) return GF_NON_COMPLIANT_BITSTREAM; - if (mmlevel == GF_CODEC_LEVEL_SEEK) return GF_OK; - if (theora_decode_YUVout(&ctx->td, &yuv)<0) return GF_OK; - - pYO = yuv.y; - pUO = yuv.u; - pVO = yuv.v; - pYD = outBuffer; - pUD = outBuffer + ctx->ti.width * ctx->ti.height; - pVD = outBuffer + 5 * ctx->ti.width * ctx->ti.height / 4; - - for (i=0; i<(u32)yuv.y_height; i++) { - memcpy(pYD, pYO, sizeof(char) * yuv.y_width); - pYD += ctx->ti.width; - pYO += yuv.y_stride; - if (i%2) continue; - - memcpy(pUD, pUO, sizeof(char) * yuv.uv_width); - memcpy(pVD, pVO, sizeof(char) * yuv.uv_width); - pUD += ctx->ti.width/2; - pVD += ctx->ti.width/2; - pUO += yuv.uv_stride; - pVO += yuv.uv_stride; - } - *outBufferLength = 3*ctx->ti.width*ctx->ti.height/2; - return GF_OK; -} - - - -static const char *THEO_GetCodecName(GF_BaseDecoder *dec) -{ - return "Theora Decoder"; -} - -u32 NewTheoraDecoder(GF_BaseDecoder *ifcd) -{ - TheoraDec *dec; - GF_SAFEALLOC(dec, TheoraDec); - ((OGGWraper *)ifcd->privateStack)->opaque = dec; - ((OGGWraper *)ifcd->privateStack)->type = OGG_THEORA; - /*setup our own interface*/ - ifcd->AttachStream = THEO_AttachStream; - ifcd->DetachStream = THEO_DetachStream; - ifcd->GetCapabilities = THEO_GetCapabilities; - ifcd->SetCapabilities = THEO_SetCapabilities; - ((GF_MediaDecoder*)ifcd)->ProcessData = THEO_ProcessData; - ifcd->GetName = THEO_GetCodecName; - return 1; -} - -void DeleteTheoraDecoder(GF_BaseDecoder *ifcg) -{ - THEORACTX(); - gf_free(ctx); -} - -#endif - diff --git a/modules/ogg/vorbis_dec.c b/modules/ogg/vorbis_dec.c deleted file mode 100644 index 49692f3..0000000 --- a/modules/ogg/vorbis_dec.c +++ /dev/null @@ -1,278 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / XIPH.org module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include "ogg_in.h" - -#ifdef GPAC_HAS_VORBIS - -#include <vorbis/codec.h> - -typedef struct -{ - vorbis_info vi; - vorbis_dsp_state vd; - vorbis_block vb; - vorbis_comment vc; - ogg_packet op; - - u16 ES_ID; - Bool has_reconfigured; -} VorbDec; - -#define VORBISCTX() VorbDec *ctx = (VorbDec *) ((OGGWraper *)ifcg->privateStack)->opaque - -static GF_Err VORB_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ - ogg_packet oggpacket; - GF_BitStream *bs; - - VORBISCTX(); - if (ctx->ES_ID) return GF_BAD_PARAM; - - if (!esd->decoderConfig->decoderSpecificInfo || !esd->decoderConfig->decoderSpecificInfo->data) return GF_NON_COMPLIANT_BITSTREAM; - if (esd->decoderConfig->objectTypeIndication != GPAC_OTI_MEDIA_OGG) return GF_NON_COMPLIANT_BITSTREAM; - if ((esd->decoderConfig->decoderSpecificInfo->dataLength<9) || strncmp(&esd->decoderConfig->decoderSpecificInfo->data[3], "vorbis", 6)) return GF_NON_COMPLIANT_BITSTREAM; - - ctx->ES_ID = esd->ESID; - - vorbis_info_init(&ctx->vi); - vorbis_comment_init(&ctx->vc); - - oggpacket.granulepos = -1; - oggpacket.b_o_s = 1; - oggpacket.e_o_s = 0; - oggpacket.packetno = 0; - - bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ); - while (gf_bs_available(bs)) { - oggpacket.bytes = gf_bs_read_u16(bs); - oggpacket.packet = gf_malloc(sizeof(char) * oggpacket.bytes); - gf_bs_read_data(bs, oggpacket.packet, oggpacket.bytes); - if (vorbis_synthesis_headerin(&ctx->vi, &ctx->vc, &oggpacket) < 0 ) { - gf_free(oggpacket.packet); - gf_bs_del(bs); - return GF_NON_COMPLIANT_BITSTREAM; - } - gf_free(oggpacket.packet); - } - vorbis_synthesis_init(&ctx->vd, &ctx->vi); - vorbis_block_init(&ctx->vd, &ctx->vb); - gf_bs_del(bs); - - return GF_OK; -} - -static GF_Err VORB_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - VORBISCTX(); - if (ctx->ES_ID != ES_ID) return GF_BAD_PARAM; - - vorbis_block_clear(&ctx->vb); - vorbis_dsp_clear(&ctx->vd); - vorbis_info_clear(&ctx->vi); - vorbis_comment_clear(&ctx->vc); - - ctx->ES_ID = 0; - return GF_OK; -} -static GF_Err VORB_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - VORBISCTX(); - switch (capability->CapCode) { - case GF_CODEC_RESILIENT: - capability->cap.valueInt = 1; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = vorbis_info_blocksize(&ctx->vi, 1) * 2 * ctx->vi.channels; - break; - case GF_CODEC_SAMPLERATE: - capability->cap.valueInt = ctx->vi.rate; - break; - case GF_CODEC_NB_CHAN: - capability->cap.valueInt = ctx->vi.channels; - break; - case GF_CODEC_BITS_PER_SAMPLE: - capability->cap.valueInt = 16; - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = 4; - break; - case GF_CODEC_BUFFER_MAX: - capability->cap.valueInt = ctx->vi.rate / 4 / vorbis_info_blocksize(&ctx->vi, 0); - /*blocks are not of fixed size, so indicate a default CM size*/ - //capability->cap.valueInt = 12 * vorbis_info_blocksize(&ctx->vi, 1) / vorbis_info_blocksize(&ctx->vi, 0); - break; - case GF_CODEC_CU_DURATION: - /*this CANNOT work with vorbis, blocks are not of fixed size*/ - capability->cap.valueInt = 0; - break; - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 0; - break; - case GF_CODEC_CHANNEL_CONFIG: - switch (ctx->vi.channels) { - case 1: - capability->cap.valueInt = GF_AUDIO_CH_FRONT_CENTER; - break; - case 2: - capability->cap.valueInt = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT; - break; - case 3: - capability->cap.valueInt = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER; - break; - case 4: - capability->cap.valueInt = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_BACK_LEFT | GF_AUDIO_CH_BACK_RIGHT; - break; - case 5: - capability->cap.valueInt = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_BACK_LEFT | GF_AUDIO_CH_BACK_RIGHT; - break; - case 6: - capability->cap.valueInt = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_BACK_LEFT | GF_AUDIO_CH_BACK_RIGHT | GF_AUDIO_CH_LFE; - break; - } - break; - default: - capability->cap.valueInt = 0; - break; - } - return GF_OK; -} - -static GF_Err VORB_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - /*return unsupported to avoid confusion by the player (like SR changing ...) */ - return GF_NOT_SUPPORTED; -} - - -static GFINLINE void vorbis_to_intern(u32 samples, Float **pcm, char *buf, u32 channels) -{ - u32 i, j; - s32 val; - ogg_int16_t *ptr, *data = (ogg_int16_t*)buf ; - Float *mono; - - for (i=0 ; i<channels ; i++) { - ptr = &data[i]; - if (channels>2) { - /*center is third in gpac*/ - if (i==1) ptr = &data[2]; - /*right is 2nd in gpac*/ - else if (i==2) ptr = &data[1]; - /*LFE is 4th in gpac*/ - if ((channels==6) && (i>3)) { - if (i==6) ptr = &data[4]; /*LFE*/ - else ptr = &data[i+1]; /*back l/r*/ - } - } - - mono = pcm[i]; - for (j=0; j<samples; j++) { - val = (s32) (mono[j] * 32767.f); - if (val > 32767) val = 32767; - if (val < -32768) val = -32768; - *ptr = val; - ptr += channels; - } - } -} - -static GF_Err VORB_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ - ogg_packet op; - Float **pcm; - u32 samples, total_samples, total_bytes; - - VORBISCTX(); - /*not using scalabilty*/ - assert(ctx->ES_ID == ES_ID); - - op.granulepos = -1; - op.b_o_s = 0; - op.e_o_s = 0; - op.packetno = 0; - op.packet = inBuffer; - op.bytes = inBufferLength; - - - *outBufferLength = 0; - - if (vorbis_synthesis(&ctx->vb, &op) == 0) - vorbis_synthesis_blockin(&ctx->vd, &ctx->vb) ; - - /*trust vorbis max block info*/ - total_samples = 0; - total_bytes = 0; - while ((samples = vorbis_synthesis_pcmout(&ctx->vd, &pcm)) > 0) { - vorbis_to_intern(samples, pcm, (char*) outBuffer + total_bytes, ctx->vi.channels); - total_bytes += samples * 2 * ctx->vi.channels; - total_samples += samples; - vorbis_synthesis_read(&ctx->vd, samples); - } - *outBufferLength = total_bytes; - return GF_OK; -} - - -static const char *VORB_GetCodecName(GF_BaseDecoder *dec) -{ - return "Vorbis Decoder"; -} - -u32 NewVorbisDecoder(GF_BaseDecoder *ifcd) -{ - VorbDec *dec; - GF_SAFEALLOC(dec, VorbDec); - ((OGGWraper *)ifcd->privateStack)->opaque = dec; - ((OGGWraper *)ifcd->privateStack)->type = OGG_VORBIS; - /*setup our own interface*/ - ifcd->AttachStream = VORB_AttachStream; - ifcd->DetachStream = VORB_DetachStream; - ifcd->GetCapabilities = VORB_GetCapabilities; - ifcd->SetCapabilities = VORB_SetCapabilities; - ((GF_MediaDecoder*)ifcd)->ProcessData = VORB_ProcessData; - ifcd->GetName = VORB_GetCodecName; - return 1; -} - -void DeleteVorbisDecoder(GF_BaseDecoder *ifcg) -{ - VorbDec *ctx; - if (!ifcg || !ifcg->privateStack) - return; - ctx = (VorbDec *) ((OGGWraper *)ifcg->privateStack)->opaque; - if (ctx) { - gf_free(ctx); - ((OGGWraper *)ifcg->privateStack)->opaque = NULL; - } -} - -#endif - diff --git a/modules/openhevc_dec/Makefile b/modules/openhevc_dec/Makefile deleted file mode 100644 index 23d0307..0000000 --- a/modules/openhevc_dec/Makefile +++ /dev/null @@ -1,58 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/openhevc_dec - -CFLAGS=$(OPTFLAGS) -I"$(SRC_PATH)/include" $(OHEVC_CFLAGS) - -ifeq ($(CONFIG_LINUX), yes) -LDFLAGS+=-Wl,-Bsymbolic -endif - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -LINKFLAGS+=-L../../bin/gcc $(OHEVC_LDFLAGS) - -ifeq ($(STATICBUILD),yes) -##include static modules and other deps for libgpac -include ../../static.mak -LINKFLAGS+=-lgpac_static -LINKFLAGS+=$(EXTRALIBS) -else -LINKFLAGS+=-lgpac -endif - -#common obj -OBJS= openhevc_dec.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_openhevc_dec$(DYN_LIB_SUFFIX) - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(LINKFLAGS) $(LDFLAGS) - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/openhevc_dec/openhevc_dec.c b/modules/openhevc_dec/openhevc_dec.c deleted file mode 100644 index b250195..0000000 --- a/modules/openhevc_dec/openhevc_dec.c +++ /dev/null @@ -1,931 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2010-2012 - * All rights reserved - * - * This file is part of GPAC / OpenSVC Decoder module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include <gpac/modules/codec.h> -#include <gpac/avparse.h> -#include <gpac/constants.h> -#include <gpac/internal/media_dev.h> -#include <openHevcWrapper.h> - - -#ifndef GPAC_DISABLE_AV_PARSERS - -#define OPEN_SHVC - -#if defined(WIN32) && !defined(_WIN32_WCE) && !defined(__GNUC__) -# pragma comment(lib, "libLibOpenHevcWrapper") - -#if !defined _WIN64 -void libOpenHevcSetViewLayers(OpenHevc_Handle openHevcHandle, int val) -{ -} -#endif - -#endif -// -typedef struct -{ - u16 ES_ID; - u32 width, stride, height, out_size, pixel_ar, layer, nb_threads, luma_bpp, chroma_bpp; - - Bool is_init; - Bool had_pic; - - u32 hevc_nalu_size_length; - u32 threading_type; - Bool direct_output, has_pic; - - GF_ESD *esd; - OpenHevc_Handle openHevcHandle; - u32 nb_layers, cur_layer; - u32 output_cb_size; - - Bool decoder_started; - - u32 frame_idx; - Bool pack_mode; - u32 dec_frames; - u8 chroma_format_idc; - - u32 nb_views; - -#ifdef OPENHEVC_HAS_AVC_BASE - u32 avc_base_id; - u32 avc_nalu_size_length; - char *avc_base; - u32 avc_base_size; - u32 avc_base_pts; -#endif - - FILE *raw_out; -} HEVCDec; - -static GF_Err HEVC_ConfigurationScalableStream(HEVCDec *ctx, GF_ESD *esd) -{ - GF_HEVCConfig *cfg = NULL; - char *data; - u32 data_len; - GF_BitStream *bs; - u32 i, j; - - if (!ctx->openHevcHandle) return GF_NOT_SUPPORTED; - if (! esd->has_scalable_layers) return GF_OK; - - if (!esd->decoderConfig->decoderSpecificInfo || !esd->decoderConfig->decoderSpecificInfo->data) { - ctx->nb_layers++; - ctx->cur_layer++; - libOpenHevcSetActiveDecoders(ctx->openHevcHandle, ctx->cur_layer-1); - libOpenHevcSetViewLayers(ctx->openHevcHandle, ctx->cur_layer-1); - return GF_OK; - } - - if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_LHVC) { - cfg = gf_odf_hevc_cfg_read(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_FALSE); - } else { - cfg = gf_odf_hevc_cfg_read(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_FALSE); - } - - if (!cfg) return GF_NON_COMPLIANT_BITSTREAM; - if (!ctx->hevc_nalu_size_length) ctx->hevc_nalu_size_length = cfg->nal_unit_size; - else if (ctx->hevc_nalu_size_length != cfg->nal_unit_size) - return GF_NON_COMPLIANT_BITSTREAM; - - ctx->nb_layers++; - ctx->cur_layer++; - libOpenHevcSetActiveDecoders(ctx->openHevcHandle, ctx->nb_layers-1); - libOpenHevcSetViewLayers(ctx->openHevcHandle, ctx->nb_layers-1); - -#ifdef OPENHEVC_HAS_AVC_BASE - //LHVC mode with base AVC layer: set extradata for LHVC - if (ctx->avc_base_id) { - libOpenShvcCopyExtraData(ctx->openHevcHandle, NULL, (u8 *) esd->decoderConfig->decoderSpecificInfo->data, 0, esd->decoderConfig->decoderSpecificInfo->dataLength); - } else -#endif - //LHVC mode with base HEVC layer: decode the LHVC SPS/PPS/VPS - { - bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - for (i=0; i< gf_list_count(cfg->param_array); i++) { - GF_HEVCParamArray *ar = (GF_HEVCParamArray *)gf_list_get(cfg->param_array, i); - for (j=0; j< gf_list_count(ar->nalus); j++) { - GF_AVCConfigSlot *sl = (GF_AVCConfigSlot *)gf_list_get(ar->nalus, j); - gf_bs_write_int(bs, sl->size, 8*ctx->hevc_nalu_size_length); - gf_bs_write_data(bs, sl->data, sl->size); - } - } - - gf_bs_get_content(bs, &data, &data_len); - gf_bs_del(bs); - //the decoder may not be already started - if (!ctx->decoder_started) { - libOpenHevcStartDecoder(ctx->openHevcHandle); - ctx->decoder_started=1; - } - - libOpenHevcDecode(ctx->openHevcHandle, (u8 *)data, data_len, 0); - if (ctx->raw_out) fwrite((u8 *)data, 1, data_len, ctx->raw_out); - - gf_free(data); - } - return GF_OK; -} - -#if defined(OPENHEVC_HAS_AVC_BASE) && !defined(GPAC_DISABLE_LOG) -void openhevc_log_callback(void *udta, int l, const char*fmt, va_list vl) -{ - u32 level = GF_LOG_DEBUG; - if (l <= OHEVC_LOG_ERROR) l = GF_LOG_ERROR; - else if (l <= OHEVC_LOG_WARNING) l = GF_LOG_WARNING; - else if (l <= OHEVC_LOG_INFO) l = GF_LOG_INFO; - else if (l >= OHEVC_LOG_VERBOSE) return; - - if (gf_log_tool_level_on(GF_LOG_CODEC, level)) { - gf_log_va_list(level, GF_LOG_CODEC, fmt, vl); - } -} -#endif - - -static GF_Err HEVC_ConfigureStream(HEVCDec *ctx, GF_ESD *esd) -{ - u32 i, j, stride_mul=1; - ctx->ES_ID = esd->ESID; - ctx->width = ctx->height = ctx->out_size = ctx->luma_bpp = ctx->chroma_bpp = ctx->chroma_format_idc = 0; - - ctx->nb_layers = 1; - ctx->cur_layer = 1; - - if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_AVC) -#ifdef OPENHEVC_HAS_AVC_BASE - ctx->avc_base_id=esd->ESID; -#else - return GF_NOT_SUPPORTED; -#endif - - if (esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->data) { -#ifdef OPENHEVC_HAS_AVC_BASE - if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_AVC) { - GF_AVCConfig *avcc = NULL; - AVCState avc; - memset(&avc, 0, sizeof(AVCState)); - - avcc = gf_odf_avc_cfg_read(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength); - if (!avcc) return GF_NON_COMPLIANT_BITSTREAM; - ctx->avc_nalu_size_length = avcc->nal_unit_size; - - for (i=0; i< gf_list_count(avcc->sequenceParameterSets); i++) { - GF_AVCConfigSlot *sl = (GF_AVCConfigSlot *)gf_list_get(avcc->sequenceParameterSets, i); - s32 idx = gf_media_avc_read_sps(sl->data, sl->size, &avc, 0, NULL); - ctx->width = MAX(avc.sps[idx].width, ctx->width); - ctx->height = MAX(avc.sps[idx].height, ctx->height); - ctx->luma_bpp = avcc->luma_bit_depth; - ctx->chroma_bpp = avcc->chroma_bit_depth; - ctx->chroma_format_idc = avcc->chroma_format; - } - gf_odf_avc_cfg_del(avcc); - } else - -#endif - { - GF_HEVCConfig *hvcc = NULL; - HEVCState hevc; - memset(&hevc, 0, sizeof(HEVCState)); - - hvcc = gf_odf_hevc_cfg_read(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_FALSE); - if (!hvcc) return GF_NON_COMPLIANT_BITSTREAM; - ctx->hevc_nalu_size_length = hvcc->nal_unit_size; - - for (i=0; i< gf_list_count(hvcc->param_array); i++) { - GF_HEVCParamArray *ar = (GF_HEVCParamArray *)gf_list_get(hvcc->param_array, i); - for (j=0; j< gf_list_count(ar->nalus); j++) { - GF_AVCConfigSlot *sl = (GF_AVCConfigSlot *)gf_list_get(ar->nalus, j); - s32 idx; - u16 hdr = sl->data[0] << 8 | sl->data[1]; - - if (ar->type==GF_HEVC_NALU_SEQ_PARAM) { - idx = gf_media_hevc_read_sps(sl->data, sl->size, &hevc); - ctx->width = MAX(hevc.sps[idx].width, ctx->width); - ctx->height = MAX(hevc.sps[idx].height, ctx->height); - ctx->luma_bpp = MAX(hevc.sps[idx].bit_depth_luma, ctx->luma_bpp); - ctx->chroma_bpp = MAX(hevc.sps[idx].bit_depth_chroma, ctx->chroma_bpp); - ctx->chroma_format_idc = hevc.sps[idx].chroma_format_idc; - - if (hdr & 0x1f8) { - ctx->nb_layers ++; - } - } - else if (ar->type==GF_HEVC_NALU_VID_PARAM) { - gf_media_hevc_read_vps(sl->data, sl->size, &hevc); - } - else if (ar->type==GF_HEVC_NALU_PIC_PARAM) { - gf_media_hevc_read_pps(sl->data, sl->size, &hevc); - } - } - } - gf_odf_hevc_cfg_del(hvcc); - } - - } - -#ifdef OPENHEVC_HAS_AVC_BASE - if (ctx->avc_base_id) { - ctx->openHevcHandle = libOpenShvcInit(ctx->nb_threads, ctx->threading_type); - } else -#endif - { - ctx->openHevcHandle = libOpenHevcInit(ctx->nb_threads, ctx->threading_type); - } - -#if defined(OPENHEVC_HAS_AVC_BASE) && !defined(GPAC_DISABLE_LOG) - - if (gf_log_tool_level_on(GF_LOG_CODEC, GF_LOG_DEBUG) ) { - libOpenHevcSetDebugMode(ctx->openHevcHandle, OHEVC_LOG_DEBUG); - } else if (gf_log_tool_level_on(GF_LOG_CODEC, GF_LOG_INFO) ) { - libOpenHevcSetDebugMode(ctx->openHevcHandle, OHEVC_LOG_INFO); - } else if (gf_log_tool_level_on(GF_LOG_CODEC, GF_LOG_WARNING) ) { - libOpenHevcSetDebugMode(ctx->openHevcHandle, OHEVC_LOG_WARNING); - } else { - libOpenHevcSetDebugMode(ctx->openHevcHandle, OHEVC_LOG_ERROR); - } - libOpenHevcSetLogCallback(ctx->openHevcHandle, openhevc_log_callback); -#endif - - if (esd->decoderConfig && esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->data) { - libOpenHevcSetActiveDecoders(ctx->openHevcHandle, 1); - libOpenHevcSetViewLayers(ctx->openHevcHandle, 0); -#ifdef OPENHEVC_HAS_AVC_BASE - if (ctx->avc_base_id) { - libOpenShvcCopyExtraData(ctx->openHevcHandle, (u8 *) esd->decoderConfig->decoderSpecificInfo->data, NULL, esd->decoderConfig->decoderSpecificInfo->dataLength, 0); - } else -#endif - libOpenHevcCopyExtraData(ctx->openHevcHandle, (u8 *) esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength); - }else{ - //decode and display layer 0 by default - will be changed when attaching enhancement layers - - //has_scalable_layers is set, the esd describes a set of HEVC stream but we don't know how many - for now only two decoders so easy, - //but should be fixed in the future - if (esd->has_scalable_layers) { - ctx->nb_layers = 2; - ctx->cur_layer = 2; - } - libOpenHevcSetActiveDecoders(ctx->openHevcHandle, ctx->cur_layer-1); - libOpenHevcSetViewLayers(ctx->openHevcHandle, ctx->cur_layer-1); - } - - //in case we don't have a config record - if (!ctx->chroma_format_idc) ctx->chroma_format_idc = 1; - - //we start decoder on the first frame - - //FIXME - we need to get the views info from the decoder ... - if (ctx->nb_views>1) stride_mul = ctx->nb_layers; - - ctx->stride = ((ctx->luma_bpp==8) && (ctx->chroma_bpp==8)) ? ctx->width : ctx->width * 2; - if ( ctx->chroma_format_idc == 1) { // 4:2:0 - ctx->out_size = stride_mul * ctx->stride * ctx->height * 3 / 2; - } - else if ( ctx->chroma_format_idc == 2) { // 4:2:2 - ctx->out_size = stride_mul * ctx->stride * ctx->height * 2 ; - } - else if ( ctx->chroma_format_idc == 3) { // 4:4:4 - ctx->out_size = stride_mul * ctx->stride * ctx->height * 3; - } - else { - return GF_NOT_SUPPORTED; - } - - - ctx->dec_frames = 0; - return GF_OK; -} - -static GF_Err HEVC_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ - GF_SystemRTInfo rti; - const char *sOpt; - u32 nb_threads = 1; - HEVCDec *ctx = (HEVCDec*) ifcg->privateStack; - - /*scalable layer, configure enhancement only*/ - if (esd->dependsOnESID) { - HEVC_ConfigurationScalableStream(ctx, esd); - return GF_OK; - } - - if (gf_sys_get_rti(0, &rti, 0) ) { - nb_threads = (rti.nb_cores>1) ? rti.nb_cores-1 : 1; - } - - sOpt = gf_modules_get_option((GF_BaseInterface *)ifcg, "Compositor", "NumViews"); - ctx->nb_views = atoi(sOpt); - sOpt = gf_modules_get_option((GF_BaseInterface *)ifcg, "OpenHEVC", "NumThreads"); - if (!sOpt) { - char szO[100]; - sprintf(szO, "%d", nb_threads); - gf_modules_set_option((GF_BaseInterface *)ifcg, "OpenHEVC", "NumThreads", szO); - ctx->nb_threads = nb_threads; - GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[OpenHEVC] Initializing with %d threads\n", ctx->nb_threads)); - } else { - ctx->nb_threads = atoi(sOpt); - if (ctx->nb_threads > nb_threads) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[OpenHEVC] Initializing with %d threads but only %d available cores detected on the system\n", ctx->nb_threads, rti.nb_cores)); - } - } - - sOpt = gf_modules_get_option((GF_BaseInterface *)ifcg, "OpenHEVC", "ThreadingType"); - if (sOpt && !strcmp(sOpt, "wpp")) ctx->threading_type = 2; - else if (sOpt && !strcmp(sOpt, "frame+wpp")) ctx->threading_type = 4; - else { - ctx->threading_type = 1; - if (!sOpt) gf_modules_set_option((GF_BaseInterface *)ifcg, "OpenHEVC", "ThreadingType", "frame"); - } - - sOpt = gf_modules_get_option((GF_BaseInterface *)ifcg, "OpenHEVC", "CBUnits"); - if (!sOpt) gf_modules_set_option((GF_BaseInterface *)ifcg, "OpenHEVC", "CBUnits", "4"); - if (sOpt) ctx->output_cb_size = atoi(sOpt); - if (!ctx->output_cb_size) ctx->output_cb_size = 4; - - sOpt = gf_modules_get_option((GF_BaseInterface *)ifcg, "OpenHEVC", "PackHFR"); - if (sOpt && !strcmp(sOpt, "yes") && !ctx->direct_output ) ctx->pack_mode = GF_TRUE; - else if (!sOpt) gf_modules_set_option((GF_BaseInterface *)ifcg, "OpenHEVC", "PackHFR", "no"); - - if (!ctx->raw_out) { - sOpt = gf_modules_get_option((GF_BaseInterface *)ifcg, "OpenHEVC", "InputRipFile"); - if (sOpt) ctx->raw_out = fopen(sOpt, "wb"); - } - - ctx->esd = esd; - return HEVC_ConfigureStream(ctx, esd); -} - - -static GF_Err HEVC_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - HEVCDec *ctx = (HEVCDec*) ifcg->privateStack; - - if (ctx->openHevcHandle) { - libOpenHevcClose(ctx->openHevcHandle); - ctx->openHevcHandle = NULL; - } - ctx->is_init = GF_FALSE; - ctx->width = ctx->height = ctx->out_size = 0; - return GF_OK; -} - -static u32 HEVC_GetPixelFormat( u32 luma_bpp, u8 chroma_format_idc) -{ - u32 ret = 0; - if (chroma_format_idc == 1) - { - ret = (luma_bpp==10) ? GF_PIXEL_YV12_10 : GF_PIXEL_YV12; - } - else if (chroma_format_idc == 2) - { - ret = (luma_bpp==10) ? GF_PIXEL_YUV422_10 : GF_PIXEL_YUV422; - } - else if (chroma_format_idc == 3) - { - ret = (luma_bpp==10) ? GF_PIXEL_YUV444_10 : GF_PIXEL_YUV444; - } - - return ret; - -} - - -static GF_Err HEVC_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - HEVCDec *ctx = (HEVCDec*) ifcg->privateStack; - - switch (capability->CapCode) { - case GF_CODEC_RESILIENT: - capability->cap.valueInt = 1; - break; - case GF_CODEC_WIDTH: - capability->cap.valueInt = ctx->width; - if (ctx->pack_mode) { - capability->cap.valueInt *= 2; - } - break; - case GF_CODEC_HEIGHT: - capability->cap.valueInt = ctx->height; - if (ctx->pack_mode) { - capability->cap.valueInt *= 2; - } - break; - case GF_CODEC_STRIDE: - capability->cap.valueInt = ctx->stride; - if (ctx->pack_mode) { - capability->cap.valueInt *= 2; - } - break; - case GF_CODEC_PAR: - capability->cap.valueInt = ctx->pixel_ar; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = ctx->out_size; - if (ctx->pack_mode) { - capability->cap.valueInt *= 4; - } - break; - case GF_CODEC_NBVIEWS: - capability->cap.valueInt = ctx->nb_views; - break; - case GF_CODEC_NBLAYERS: - capability->cap.valueInt = ctx->nb_layers; - break; - case GF_CODEC_PIXEL_FORMAT: - capability->cap.valueInt = HEVC_GetPixelFormat(ctx->luma_bpp, ctx->chroma_format_idc); - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = 1; - break; - case GF_CODEC_BUFFER_MAX: - capability->cap.valueInt = ctx->output_cb_size; - break; - case GF_CODEC_WANTS_THREAD: - capability->cap.valueBool= GF_TRUE; - break; - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 32; - break; - case GF_CODEC_REORDER: - capability->cap.valueInt = 1; - break; - case GF_CODEC_TRUSTED_CTS: - capability->cap.valueInt = 1; - break; - case GF_CODEC_RAW_MEMORY: - capability->cap.valueBool = GF_TRUE; - break; - /*not known at our level...*/ - case GF_CODEC_CU_DURATION: - default: - capability->cap.valueInt = 0; - break; - } - return GF_OK; -} - -static GF_Err HEVC_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - HEVCDec *ctx = (HEVCDec*) ifcg->privateStack; - switch (capability.CapCode) { - case GF_CODEC_WAIT_RAP: - if (ctx->dec_frames) { - //quick hack, we have an issue with openHEVC resuming after being flushed ... - ctx->had_pic = GF_FALSE; - libOpenHevcClose(ctx->openHevcHandle); - ctx->openHevcHandle = NULL; - ctx->decoder_started = GF_FALSE; - ctx->is_init = GF_FALSE; - HEVC_ConfigureStream(ctx, ctx->esd); - } - return GF_OK; - case GF_CODEC_MEDIA_SWITCH_QUALITY: - if (ctx->nb_layers==1) return GF_OK; - if (ctx->nb_layers==1 || ctx->nb_views!=1) return GF_OK; - /*switch up*/ - if (capability.cap.valueInt > 0) { - if (ctx->cur_layer>=ctx->nb_layers) return GF_OK; - ctx->cur_layer++; - } else { - if (ctx->cur_layer<=1) return GF_OK; - ctx->cur_layer--; - } - libOpenHevcSetViewLayers(ctx->openHevcHandle, ctx->cur_layer-1); - libOpenHevcSetActiveDecoders(ctx->openHevcHandle, ctx->cur_layer-1); - return GF_OK; - case GF_CODEC_RAW_MEMORY: - ctx->direct_output = GF_TRUE; - ctx->pack_mode = GF_FALSE; - return GF_OK; - case GF_CODEC_CAN_INIT: - if (!ctx->decoder_started && ctx->openHevcHandle) { - ctx->decoder_started=1; - libOpenHevcStartDecoder(ctx->openHevcHandle); - } - return GF_OK; - } - /*return unsupported to avoid confusion by the player (like color space changing ...) */ - return GF_NOT_SUPPORTED; - -} - -static GF_Err HEVC_flush_picture(HEVCDec *ctx, char *outBuffer, u32 *outBufferLength, u32 *CTS) -{ - unsigned int a_w, a_h, a_stride, bit_depth; - OpenHevc_Frame_cpy openHevcFrame_FL, openHevcFrame_SL; - int chromat_format; - - if (ctx->direct_output){ - libOpenHevcGetPictureInfo(ctx->openHevcHandle, &openHevcFrame_FL.frameInfo); - }else{ - libOpenHevcGetPictureInfoCpy(ctx->openHevcHandle, &openHevcFrame_FL.frameInfo); - if (ctx->nb_layers == 2) libOpenHevcGetPictureInfoCpy(ctx->openHevcHandle, &openHevcFrame_SL.frameInfo); - } - - a_w = openHevcFrame_FL.frameInfo.nWidth; - a_h = openHevcFrame_FL.frameInfo.nHeight; - a_stride = openHevcFrame_FL.frameInfo.nYPitch; - bit_depth = openHevcFrame_FL.frameInfo.nBitDepth; - chromat_format = openHevcFrame_FL.frameInfo.chromat_format; - *CTS = (u32) openHevcFrame_FL.frameInfo.nTimeStamp; - - if (!ctx->out_size || (ctx->width != a_w) || (ctx->height!=a_h) || (ctx->stride != a_stride) || (ctx->luma_bpp!= bit_depth) || (ctx->chroma_bpp != bit_depth) || (ctx->chroma_format_idc != (chromat_format + 1)) ){ - ctx->width = a_w; - ctx->stride = a_stride; - ctx->height = a_h; - if( chromat_format == YUV420 ) { - ctx->out_size = ctx->stride * ctx->height * 3 / 2; - } else if ( chromat_format == YUV422 ) { - ctx->out_size = ctx->stride * ctx->height * 2; - } else if ( chromat_format == YUV444 ) { - ctx->out_size = ctx->stride * ctx->height * 3; - } - ctx->had_pic = GF_TRUE; - ctx->luma_bpp = ctx->chroma_bpp = bit_depth; - ctx->chroma_format_idc = chromat_format + 1; - /*always force layer resize*/ - *outBufferLength = ctx->out_size; - - return GF_BUFFER_TOO_SMALL; - } - if (ctx->direct_output) { - *outBufferLength = ctx->out_size; - ctx->has_pic = GF_TRUE; - return GF_OK; - } - - - if (ctx->pack_mode) { - OpenHevc_Frame openHFrame; - u8 *pY, *pU, *pV; - - u32 idx_w, idx_h; - idx_w = ((ctx->frame_idx==0) || (ctx->frame_idx==2)) ? 0 : ctx->stride; - idx_h = ((ctx->frame_idx==0) || (ctx->frame_idx==1)) ? 0 : ctx->height*2*ctx->stride; - - pY = (u8*) (outBuffer + idx_h + idx_w ); - - - if (chromat_format == YUV422) { - pU = (u8*)(outBuffer + 4 * ctx->stride * ctx->height + idx_w / 2 + idx_h / 2); - pV = (u8*)(outBuffer + 4 * (3 * ctx->stride * ctx->height /2) + idx_w / 2 + idx_h / 2); - } else if (chromat_format == YUV444) { - pU = (u8*)(outBuffer + 4 * ctx->stride * ctx->height + idx_w + idx_h); - pV = (u8*)(outBuffer + 4 * ( 2 * ctx->stride * ctx->height) + idx_w + idx_h); - } else { - pU = (u8*)(outBuffer + 2 * ctx->stride * 2 * ctx->height + idx_w / 2 + idx_h / 4); - pV = (u8*)(outBuffer + 4 * ( 5 *ctx->stride * ctx->height / 4) + idx_w / 2 + idx_h / 4); - - } - - *outBufferLength = 0; - if (libOpenHevcGetOutput(ctx->openHevcHandle, 1, &openHFrame)) { - u32 i, s_stride, hs_stride, qs_stride, d_stride, dd_stride, hd_stride; - - s_stride = openHFrame.frameInfo.nYPitch; - qs_stride = s_stride / 4; - hs_stride = s_stride / 2; - - d_stride = ctx->stride; - dd_stride = 2*ctx->stride; - hd_stride = ctx->stride/2; - - if (chromat_format == YUV422) { - - for (i = 0; i < ctx->height; i++) { - - memcpy(pY, (u8 *)openHFrame.pvY + i*s_stride, d_stride); - pY += dd_stride; - - memcpy(pU, (u8 *)openHFrame.pvU + i*hs_stride, hd_stride); - pU += d_stride; - - memcpy(pV, (u8 *)openHFrame.pvV + i*hs_stride, hd_stride); - pV += d_stride; - } - } - else if (chromat_format == YUV444) { - - for (i = 0; i < ctx->height; i++) { - - memcpy(pY, (u8 *)openHFrame.pvY + i*s_stride, d_stride); - pY += dd_stride; - - memcpy(pU, (u8 *)openHFrame.pvU + i*s_stride, d_stride); - pU += dd_stride; - - memcpy(pV, (u8 *)openHFrame.pvV + i*s_stride, d_stride); - pV += dd_stride; - } - } - else { - for (i = 0; i<ctx->height; i++) { - memcpy(pY, (u8 *)openHFrame.pvY + i*s_stride, d_stride); - pY += dd_stride; - - if (!(i % 2)) { - memcpy(pU, (u8 *)openHFrame.pvU + i*qs_stride, hd_stride); - pU += d_stride; - - memcpy(pV, (u8 *)openHFrame.pvV + i*qs_stride, hd_stride); - pV += d_stride; - } - } - } - - ctx->frame_idx++; - if (ctx->frame_idx==4) { - *outBufferLength = 4 * ctx->out_size; - ctx->frame_idx = 0; - } - } - return GF_OK; - } - - - *outBufferLength = 0; - openHevcFrame_FL.pvY = (void*) outBuffer; - if (ctx->nb_layers==2 && ctx->nb_views>1 && !ctx->direct_output){ - int out1, out2; - if( chromat_format == YUV420){ - openHevcFrame_SL.pvY = (void*) (outBuffer + ctx->stride * ctx->height); - openHevcFrame_FL.pvU = (void*) (outBuffer + 2*ctx->stride * ctx->height); - openHevcFrame_SL.pvU = (void*) (outBuffer + 9*ctx->stride * ctx->height/4); - openHevcFrame_FL.pvV = (void*) (outBuffer + 5*ctx->stride * ctx->height/2); - openHevcFrame_SL.pvV = (void*) (outBuffer + 11*ctx->stride * ctx->height/4); - } - - libOpenHevcSetViewLayers(ctx->openHevcHandle, 0); - out1 = libOpenHevcGetOutputCpy(ctx->openHevcHandle, 1, &openHevcFrame_FL); - libOpenHevcSetViewLayers(ctx->openHevcHandle, 1); - out2 = libOpenHevcGetOutputCpy(ctx->openHevcHandle, 1, &openHevcFrame_SL); - - if (out1 && out2) *outBufferLength = ctx->out_size*2; - - }else{ - openHevcFrame_FL.pvU = (void*) (outBuffer + ctx->stride * ctx->height); - if( chromat_format == YUV420) { - openHevcFrame_FL.pvV = (void*) (outBuffer + 5*ctx->stride * ctx->height/4); - } else if (chromat_format == YUV422) { - openHevcFrame_FL.pvV = (void*) (outBuffer + 3*ctx->stride * ctx->height/2); - } else if ( chromat_format == YUV444) { - openHevcFrame_FL.pvV = (void*) (outBuffer + 2*ctx->stride * ctx->height); - } - - if (libOpenHevcGetOutputCpy(ctx->openHevcHandle, 1, &openHevcFrame_FL)) - *outBufferLength = ctx->out_size; - } - return GF_OK; -} - -static GF_Err HEVC_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ - GF_Err e; - int got_pic; - HEVCDec *ctx = (HEVCDec*) ifcg->privateStack; - - if (!ctx->decoder_started) { - libOpenHevcStartDecoder(ctx->openHevcHandle); - ctx->decoder_started=1; - } - - if (!inBuffer) { -#ifdef OPENHEVC_HAS_AVC_BASE - if (ctx->avc_base_id) - got_pic = libOpenShvcDecode2(ctx->openHevcHandle, NULL, NULL, 0, 0, 0, 0); - else -#endif - got_pic = libOpenHevcDecode(ctx->openHevcHandle, NULL, 0, 0); - - if ( got_pic ) { - return HEVC_flush_picture(ctx, outBuffer, outBufferLength, CTS); - } - //quick hack, we have an issue with openHEVC resuming after being flushed ... - ctx->had_pic = GF_FALSE; - libOpenHevcClose(ctx->openHevcHandle); - ctx->openHevcHandle = NULL; - ctx->is_init = GF_FALSE; - HEVC_ConfigureStream(ctx, ctx->esd); - - return GF_OK; - } - - if (!ES_ID) { - *outBufferLength = 0; - return GF_OK; - } - - if (*outBufferLength < ctx->out_size) { - *outBufferLength = ctx->out_size; - return GF_BUFFER_TOO_SMALL; - } - - *outBufferLength = 0; - - if (ctx->had_pic) { - ctx->had_pic = GF_FALSE; - return HEVC_flush_picture(ctx, outBuffer, outBufferLength, CTS); - } - ctx->dec_frames++; - got_pic = 0; - -#ifdef OPENHEVC_HAS_AVC_BASE - if (ctx->avc_base_id) { - if (ctx->avc_base_id == ES_ID) { - if (ctx->cur_layer<=1) { - got_pic = libOpenShvcDecode2(ctx->openHevcHandle, (u8 *) inBuffer, NULL, inBufferLength, 0, *CTS, 0); - if (ctx->avc_base) { - gf_free(ctx->avc_base); - ctx->avc_base=NULL; - ctx->avc_base_size=0; - ctx->avc_base_pts=0; - } - } else { - ctx->avc_base = gf_realloc(ctx->avc_base, inBufferLength); - memcpy(ctx->avc_base, inBuffer, inBufferLength); - ctx->avc_base_size = inBufferLength; - ctx->avc_base_pts = *CTS; - } - } else if (ctx->cur_layer>1) { - got_pic = libOpenShvcDecode2(ctx->openHevcHandle, (u8*)ctx->avc_base, (u8 *) inBuffer, ctx->avc_base_size, inBufferLength, ctx->avc_base_pts , *CTS); - if (ctx->avc_base) { - gf_free(ctx->avc_base); - ctx->avc_base = NULL; - ctx->avc_base_size = 0; - } - } - } - else -#endif - got_pic = libOpenHevcDecode(ctx->openHevcHandle, (u8 *) inBuffer, inBufferLength, *CTS); - - if (ctx->raw_out) fwrite((u8 *) inBuffer, 1, inBufferLength, ctx->raw_out); - - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[HEVC Decoder] Decode CTS %d - size %d - got pic %d\n", *CTS, inBufferLength, got_pic)); - if (got_pic>0) { - e = HEVC_flush_picture(ctx, outBuffer, outBufferLength, CTS); - if (e) return e; - } - - return GF_OK; -} - - -static GF_Err HEVC_GetOutputBuffer(GF_MediaDecoder *ifcg, u16 ESID, u8 **pY_or_RGB, u8 **pU, u8 **pV) -{ - s32 res; - OpenHevc_Frame openHevcFrame; - HEVCDec *ctx = (HEVCDec*) ifcg->privateStack; - if (!ctx->has_pic) return GF_BAD_PARAM; - ctx->has_pic = GF_FALSE; - - res = libOpenHevcGetOutput(ctx->openHevcHandle, 1, &openHevcFrame); - if ((res<=0) || !openHevcFrame.pvY || !openHevcFrame.pvU || !openHevcFrame.pvV) - return GF_SERVICE_ERROR; - - *pY_or_RGB = (u8 *) openHevcFrame.pvY; - *pU = (u8 *) openHevcFrame.pvU; - *pV = (u8 *) openHevcFrame.pvV; - return GF_OK; -} - - - -static u32 HEVC_CanHandleStream(GF_BaseDecoder *dec, u32 StreamType, GF_ESD *esd, u8 PL) -{ - if (StreamType != GF_STREAM_VISUAL) return GF_CODEC_NOT_SUPPORTED; - - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - - switch (esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_VIDEO_HEVC: - case GPAC_OTI_VIDEO_LHVC: - return GF_CODEC_SUPPORTED; - case GPAC_OTI_VIDEO_AVC: -#ifdef OPENHEVC_HAS_AVC_BASE - return GF_CODEC_MAYBE_SUPPORTED; -#else - return GF_CODEC_NOT_SUPPORTED; -#endif - } - return GF_CODEC_NOT_SUPPORTED; -} - -static const char *HEVC_GetCodecName(GF_BaseDecoder *dec) -{ - HEVCDec *ctx = (HEVCDec*) dec->privateStack; -#ifdef OPENHEVC_HAS_AVC_BASE - if (ctx->avc_base_id) { - if (ctx->cur_layer==1) return "OpenHEVC v"NV_VERSION" - H264"; - else return "OpenHEVC v"NV_VERSION" - H264+LHVC"; - } - if (ctx->cur_layer==1) return "OpenHEVC v"NV_VERSION; - else return "OpenHEVC v"NV_VERSION" - LHVC"; -#else - return libOpenHevcVersion(ctx->openHevcHandle); -#endif -} - -GF_BaseDecoder *NewHEVCDec() -{ - GF_MediaDecoder *ifcd; - HEVCDec *dec; - - GF_SAFEALLOC(ifcd, GF_MediaDecoder); - if (!ifcd) return NULL; - GF_SAFEALLOC(dec, HEVCDec); - if (!dec) { - gf_free(ifcd); - return NULL; - } - GF_REGISTER_MODULE_INTERFACE(ifcd, GF_MEDIA_DECODER_INTERFACE, "HEVC Decoder", "gpac distribution") - - ifcd->privateStack = dec; - - /*setup our own interface*/ - ifcd->AttachStream = HEVC_AttachStream; - ifcd->DetachStream = HEVC_DetachStream; - ifcd->GetCapabilities = HEVC_GetCapabilities; - ifcd->SetCapabilities = HEVC_SetCapabilities; - ifcd->GetName = HEVC_GetCodecName; - ifcd->CanHandleStream = HEVC_CanHandleStream; - ifcd->ProcessData = HEVC_ProcessData; - ifcd->GetOutputBuffer = HEVC_GetOutputBuffer; - return (GF_BaseDecoder *) ifcd; -} - -void DeleteHEVCDec(GF_BaseDecoder *ifcg) -{ - HEVCDec *ctx = (HEVCDec*) ifcg->privateStack; - if (ctx->raw_out) fclose(ctx->raw_out); - - gf_free(ctx); - gf_free(ifcg); -} - -#endif - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifndef GPAC_DISABLE_AV_PARSERS - GF_MEDIA_DECODER_INTERFACE, -#endif - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ -#ifndef GPAC_DISABLE_AV_PARSERS - if (InterfaceType == GF_MEDIA_DECODER_INTERFACE) return (GF_BaseInterface *)NewHEVCDec(); -#endif - return NULL; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#ifndef GPAC_DISABLE_AV_PARSERS - case GF_MEDIA_DECODER_INTERFACE: - DeleteHEVCDec((GF_BaseDecoder*)ifce); - break; -#endif - } -} - -GPAC_MODULE_STATIC_DECLARATION( openhevc ) diff --git a/modules/opensvc_dec/Makefile b/modules/opensvc_dec/Makefile deleted file mode 100644 index d35e1a5..0000000 --- a/modules/opensvc_dec/Makefile +++ /dev/null @@ -1,45 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/opensvc_dec - -CFLAGS=$(OPTFLAGS) -I"$(SRC_PATH)/include" $(OSVC_CFLAGS) - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -EXTRALIBS+=-L../../bin/gcc -lgpac $(OSVC_LDFLAGS) - -#common obj -OBJS= opensvc_dec.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_opensvc_dec$(DYN_LIB_SUFFIX) - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) $(LDFLAGS) - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/opensvc_dec/opensvc_dec.c b/modules/opensvc_dec/opensvc_dec.c deleted file mode 100644 index 41202d0..0000000 --- a/modules/opensvc_dec/opensvc_dec.c +++ /dev/null @@ -1,458 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2010-2012 - * All rights reserved - * - * This file is part of GPAC / OpenSVC Decoder module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include <gpac/modules/codec.h> -#include <gpac/avparse.h> -#include <gpac/constants.h> -#include <gpac/internal/media_dev.h> - - -#if (defined(WIN32) || defined(_WIN32_WCE)) && !defined(__GNUC__) -# pragma comment(lib, "OpenSVCDecoder") -#endif - -#include <OpenSVCDecoder/SVCDecoder_ietr_api.h> - -typedef struct -{ - u16 ES_ID; - u16 baseES_ID; - u32 width, stride, height, out_size, pixel_ar; - - u32 nalu_size_length; - Bool init_layer_set; - Bool state_found; - - /*OpenSVC things*/ - void *codec; - int CurrentDqId; - int MaxDqId; - int DqIdTable[8]; - int TemporalId; - int TemporalCom; -} OSVCDec; - -static GF_Err OSVC_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ - u32 i, count; - s32 res; - OPENSVCFRAME Picture; - int Layer[4]; - OSVCDec *ctx = (OSVCDec*) ifcg->privateStack; - - /*todo: we should check base layer of this stream is indeed our base layer*/ - if (!ctx->ES_ID) { - ctx->ES_ID = esd->ESID; - ctx->width = ctx->height = ctx->out_size = 0; - if (!esd->dependsOnESID) ctx->baseES_ID = esd->ESID; - } - - if (esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->data) { - GF_AVCConfig *cfg = gf_odf_avc_cfg_read(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength); - if (!cfg) return GF_NON_COMPLIANT_BITSTREAM; - if (!esd->dependsOnESID) { - ctx->nalu_size_length = cfg->nal_unit_size; - if (SVCDecoder_init(&ctx->codec) == SVC_STATUS_ERROR) return GF_IO_ERR; - } - - /*decode all NALUs*/ - count = gf_list_count(cfg->sequenceParameterSets); - SetCommandLayer(Layer, 255, 0, &res, 0);//bufindex can be reset without pb - for (i=0; i<count; i++) { - u32 w=0, h=0, sid; - s32 par_n=0, par_d=0; - GF_AVCConfigSlot *slc = (GF_AVCConfigSlot*)gf_list_get(cfg->sequenceParameterSets, i); - -#ifndef GPAC_DISABLE_AV_PARSERS - gf_avc_get_sps_info(slc->data, slc->size, &sid, &w, &h, &par_n, &par_d); -#endif - /*by default use the base layer*/ - if (!i) { - if ((ctx->width<w) || (ctx->height<h)) { - ctx->width = w; - ctx->height = h; - if ( ((s32)par_n>0) && ((s32)par_d>0) ) - ctx->pixel_ar = (par_n<<16) || par_d; - } - } - res = decodeNAL(ctx->codec, (unsigned char *) slc->data, slc->size, &Picture, Layer); - if (res<0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[SVC Decoder] Error decoding SPS %d\n", res)); - } - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[SVC Decoder] Attach: SPS id=\"%d\" code=\"%d\" size=\"%d\"\n", slc->id, slc->data[0] & 0x1F, slc->size)); - } - - count = gf_list_count(cfg->pictureParameterSets); - for (i=0; i<count; i++) { - u32 sps_id, pps_id; - GF_AVCConfigSlot *slc = (GF_AVCConfigSlot*)gf_list_get(cfg->pictureParameterSets, i); - gf_avc_get_pps_info(slc->data, slc->size, &pps_id, &sps_id); - res = decodeNAL(ctx->codec, (unsigned char *) slc->data, slc->size, &Picture, Layer); - if (res<0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[SVC Decoder] Error decoding PPS %d\n", res)); - } - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[SVC Decoder] Attach: PPS id=\"%d\" code=\"%d\" size=\"%d\" sps_id=\"%d\"\n", pps_id, slc->data[0] & 0x1F, slc->size, sps_id)); - } - ctx->state_found = GF_TRUE; - gf_odf_avc_cfg_del(cfg); - } else { - if (ctx->nalu_size_length) { - return GF_NOT_SUPPORTED; - } - ctx->nalu_size_length = 0; - if (!esd->dependsOnESID) { - if (SVCDecoder_init(&ctx->codec) == SVC_STATUS_ERROR) return GF_IO_ERR; - } - ctx->pixel_ar = (1<<16) || 1; - } - ctx->stride = ctx->width + 32; - ctx->CurrentDqId = ctx->MaxDqId = 0; - ctx->out_size = ctx->stride * ctx->height * 3 / 2; - return GF_OK; -} -static GF_Err OSVC_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - OSVCDec *ctx = (OSVCDec*) ifcg->privateStack; - - if (ctx->codec) SVCDecoder_close(ctx->codec); - ctx->codec = NULL; - ctx->width = ctx->height = ctx->out_size = 0; - return GF_OK; -} -static GF_Err OSVC_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - OSVCDec *ctx = (OSVCDec*) ifcg->privateStack; - - switch (capability->CapCode) { - case GF_CODEC_RESILIENT: - capability->cap.valueInt = 1; - break; - case GF_CODEC_WIDTH: - capability->cap.valueInt = ctx->width; - break; - case GF_CODEC_HEIGHT: - capability->cap.valueInt = ctx->height; - break; - case GF_CODEC_STRIDE: - capability->cap.valueInt = ctx->stride; - break; - case GF_CODEC_PAR: - capability->cap.valueInt = ctx->pixel_ar; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = ctx->out_size; - break; - case GF_CODEC_PIXEL_FORMAT: - capability->cap.valueInt = GF_PIXEL_YV12; - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = 1; - break; - case GF_CODEC_BUFFER_MAX: - capability->cap.valueInt = 4; - break; - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 32; - break; - case GF_CODEC_REORDER: - capability->cap.valueInt = 1; - break; - /*not known at our level...*/ - case GF_CODEC_CU_DURATION: - default: - capability->cap.valueInt = 0; - break; - } - return GF_OK; -} -static GF_Err OSVC_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - OSVCDec *ctx = (OSVCDec*) ifcg->privateStack; - switch (capability.CapCode) { - case GF_CODEC_MEDIA_SWITCH_QUALITY: - - if (capability.cap.valueInt) { - if (ctx->CurrentDqId < ctx->MaxDqId) - // set layer up (command=1) - UpdateLayer( ctx->DqIdTable, &ctx->CurrentDqId, &ctx->TemporalCom, &ctx->TemporalId, ctx->MaxDqId, 1 ); - } else { - if (ctx->CurrentDqId > 0) - // set layer down (command=0) - UpdateLayer( ctx->DqIdTable, &ctx->CurrentDqId, &ctx->TemporalCom, &ctx->TemporalId, ctx->MaxDqId, 0 ); - } - return GF_OK; - } - /*return unsupported to avoid confusion by the player (like color space changing ...) */ - return GF_NOT_SUPPORTED; - -} - -static GF_Err OSVC_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ - - s32 got_pic; - OPENSVCFRAME pic; - int Layer[4]; - u32 i, nalu_size, sc_size; - u8 *ptr; - OSVCDec *ctx = (OSVCDec*) ifcg->privateStack; - u32 curMaxDqId = ctx->MaxDqId; - - if (!ES_ID || !ctx->codec) { - *outBufferLength = 0; - return GF_OK; - } - if (*outBufferLength < ctx->out_size) { - *outBufferLength = ctx->out_size; - return GF_BUFFER_TOO_SMALL; - } - - ctx->MaxDqId = GetDqIdMax((unsigned char *) inBuffer, inBufferLength, ctx->nalu_size_length, ctx->DqIdTable, ctx->nalu_size_length ? 1 : 0); - if (!ctx->init_layer_set) { - //AVC stream in a h264 file - if (ctx->MaxDqId == -1) - ctx->MaxDqId = 0; - - ctx->CurrentDqId = ctx->MaxDqId; - ctx->init_layer_set = GF_TRUE; - } - if (curMaxDqId != ctx->MaxDqId) - ctx->CurrentDqId = ctx->MaxDqId; - /*decode only current layer*/ - SetCommandLayer(Layer, ctx->MaxDqId, ctx->CurrentDqId, &ctx->TemporalCom, ctx->TemporalId); - - got_pic = 0; - nalu_size = 0; - ptr = (u8 *) inBuffer; - sc_size = 0; - - if (!ctx->nalu_size_length) { - u32 size; - size = gf_media_nalu_next_start_code((u8 *) inBuffer, inBufferLength, &sc_size); - if (sc_size) { - ptr += size+sc_size; - assert(inBufferLength >= size+sc_size); - inBufferLength -= size+sc_size; - } else { - /*no annex-B start-code found, discard */ - *outBufferLength = 0; - return GF_OK; - } - } - - while (inBufferLength) { - if (ctx->nalu_size_length) { - for (i=0; i<ctx->nalu_size_length; i++) { - nalu_size = (nalu_size<<8) + ptr[i]; - } - ptr += ctx->nalu_size_length; - } else { - nalu_size = gf_media_nalu_next_start_code(ptr, inBufferLength, &sc_size); - } -#ifndef GPAC_DISABLE_LOG - switch (ptr[0] & 0x1F) { - case GF_AVC_NALU_SEQ_PARAM: - case GF_AVC_NALU_SVC_SUBSEQ_PARAM: - { - u32 sps_id; - gf_avc_get_sps_info((char *)ptr, nalu_size, &sps_id, NULL, NULL, NULL, NULL); - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[SVC Decoder] ES%d: SPS id=\"%d\" code=\"%d\" size=\"%d\"\n", ES_ID, sps_id, ptr[0] & 0x1F, nalu_size)); - } - break; - case GF_AVC_NALU_PIC_PARAM: - { - u32 sps_id, pps_id; - gf_avc_get_pps_info((char *)ptr, nalu_size, &pps_id, &sps_id); - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[SVC Decoder] ES%d: PPS id=\"%d\" code=\"%d\" size=\"%d\" sps_id=\"%d\"\n", ES_ID, pps_id, ptr[0] & 0x1F, nalu_size, sps_id)); - } - break; - default: - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[SVC Decoder] ES%d: NALU code=\"%d\" size=\"%d\"\n", ES_ID, ptr[0] & 0x1F, nalu_size)); - } -#endif - if (!ctx->state_found) { - u8 nal_type = (ptr[0] & 0x1F) ; - switch (nal_type) { - case GF_AVC_NALU_SEQ_PARAM: - case GF_AVC_NALU_PIC_PARAM: - if (ctx->baseES_ID == ES_ID) - ctx->state_found = GF_TRUE; - break; - } - } - - if (ctx->state_found) { - if (!got_pic) - got_pic = decodeNAL(ctx->codec, ptr, nalu_size, &pic, Layer); - else - decodeNAL(ctx->codec, ptr, nalu_size, &pic, Layer); - } - - ptr += nalu_size; - if (ctx->nalu_size_length) { - if (inBufferLength < nalu_size + ctx->nalu_size_length) break; - inBufferLength -= nalu_size + ctx->nalu_size_length; - } else { - if (!sc_size || (inBufferLength < nalu_size + sc_size)) break; - inBufferLength -= nalu_size + sc_size; - ptr += sc_size; - } - } - - if (got_pic!=1) { - *outBufferLength = 0; - return GF_OK; - } - - if ((curMaxDqId != ctx->MaxDqId) || (pic.Width != ctx->width) || (pic.Height!=ctx->height)) { - GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[SVC Decoder] Resizing from %dx%d to %dx%d\n", ctx->width, ctx->height, pic.Width, pic.Height )); - ctx->width = pic.Width; - ctx->stride = pic.Width + 32; - ctx->height = pic.Height; - ctx->out_size = ctx->stride * ctx->height * 3 / 2; - /*always force layer resize*/ - *outBufferLength = ctx->out_size; - return GF_BUFFER_TOO_SMALL; - } - *outBufferLength = ctx->out_size; - memcpy(outBuffer, pic.pY[0], ctx->stride*ctx->height); - memcpy(outBuffer + ctx->stride * ctx->height, pic.pU[0], ctx->stride*ctx->height/4); - memcpy(outBuffer + 5*ctx->stride * ctx->height/4, pic.pV[0], ctx->stride*ctx->height/4); - - return GF_OK; -} - -static u32 OSVC_CanHandleStream(GF_BaseDecoder *dec, u32 StreamType, GF_ESD *esd, u8 PL) -{ - if (StreamType != GF_STREAM_VISUAL) return GF_CODEC_NOT_SUPPORTED; - - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - - switch (esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_VIDEO_AVC: - case GPAC_OTI_VIDEO_SVC: - if (esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->data) { - Bool is_svc = GF_FALSE; - u32 i, count; - GF_AVCConfig *cfg = gf_odf_avc_cfg_read(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength); - if (!cfg) return GF_CODEC_NOT_SUPPORTED; - - if (esd->has_scalable_layers) - is_svc = GF_TRUE; - - /*decode all NALUs*/ - count = gf_list_count(cfg->sequenceParameterSets); - for (i=0; i<count; i++) { - GF_AVCConfigSlot *slc = (GF_AVCConfigSlot*)gf_list_get(cfg->sequenceParameterSets, i); - u8 nal_type = slc->data[0] & 0x1F; - - if (nal_type==GF_AVC_NALU_SVC_SUBSEQ_PARAM) { - is_svc = GF_TRUE; - break; - } - } - gf_odf_avc_cfg_del(cfg); - return is_svc ? GF_CODEC_SUPPORTED : GF_CODEC_MAYBE_SUPPORTED; - } - return esd->has_scalable_layers ? GF_CODEC_SUPPORTED : GF_CODEC_MAYBE_SUPPORTED; - } - return GF_CODEC_NOT_SUPPORTED; -} - -static const char *OSVC_GetCodecName(GF_BaseDecoder *dec) -{ - return "OpenSVC Decoder"; -} - -GF_BaseDecoder *NewOSVCDec() -{ - GF_MediaDecoder *ifcd; - OSVCDec *dec; - - GF_SAFEALLOC(ifcd, GF_MediaDecoder); - GF_SAFEALLOC(dec, OSVCDec); - GF_REGISTER_MODULE_INTERFACE(ifcd, GF_MEDIA_DECODER_INTERFACE, "OpenSVC Decoder", "gpac distribution") - - ifcd->privateStack = dec; - - /*setup our own interface*/ - ifcd->AttachStream = OSVC_AttachStream; - ifcd->DetachStream = OSVC_DetachStream; - ifcd->GetCapabilities = OSVC_GetCapabilities; - ifcd->SetCapabilities = OSVC_SetCapabilities; - ifcd->GetName = OSVC_GetCodecName; - ifcd->CanHandleStream = OSVC_CanHandleStream; - ifcd->ProcessData = OSVC_ProcessData; - return (GF_BaseDecoder *) ifcd; -} - -void DeleteOSVCDec(GF_BaseDecoder *ifcg) -{ - OSVCDec *ctx = (OSVCDec*) ifcg->privateStack; - gf_free(ctx); - gf_free(ifcg); -} - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifndef GPAC_DISABLE_AV_PARSERS - GF_MEDIA_DECODER_INTERFACE, -#endif - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ -#ifndef GPAC_DISABLE_AV_PARSERS - if (InterfaceType == GF_MEDIA_DECODER_INTERFACE) return (GF_BaseInterface *)NewOSVCDec(); -#endif - return NULL; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#ifndef GPAC_DISABLE_AV_PARSERS - case GF_MEDIA_DECODER_INTERFACE: - DeleteOSVCDec((GF_BaseDecoder*)ifce); - break; -#endif - } -} - -GPAC_MODULE_STATIC_DECLARATION( opensvc ) - diff --git a/modules/pulseaudio/Makefile b/modules/pulseaudio/Makefile index d31b0bc..a65a1d2 100644 --- a/modules/pulseaudio/Makefile +++ b/modules/pulseaudio/Makefile @@ -2,14 +2,14 @@ include ../../config.mak vpath %.c $(SRC_PATH)/modules/pulseaudio -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/modules/pulseaudio/pulseaudio.c b/modules/pulseaudio/pulseaudio.c index 0952d0b..233efa3 100644 --- a/modules/pulseaudio/pulseaudio.c +++ b/modules/pulseaudio/pulseaudio.c @@ -1,12 +1,10 @@ /* * GPAC - Multimedia Framework C SDK * - * Copyright (c) Pierre Souchay 2008 + * Authors: Pierre Souchay , Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2008-2019 + * All rights reserved * - * History: - * - * 2008/03/30 - v1.1 (Pierre Souchay) - * first revision * * PulseAudio output module : output audio thru the PulseAudio daemon * @@ -61,16 +59,6 @@ free_pulseaudio_resources (GF_AudioOutput * dr) ctx->playback_handle = NULL; } -static const char *MODULE_NAME = "PulseAudio"; - -static const char *OUTPUT_NAME = "OutputName"; - -static const char *OUTPUT_DESCRIPTION = "OutputDescription"; - -static const char *DEFAULT_OUTPUT_NAME = "GPAC"; - -static const char *DEFAULT_OUTPUT_DESCRIPTION = "GPAC Output"; - static GF_Err PulseAudio_Setup (GF_AudioOutput * dr, void *os_handle, u32 num_buffers, u32 total_duration) @@ -79,30 +67,11 @@ PulseAudio_Setup (GF_AudioOutput * dr, void *os_handle, PulseAudioContext *ctx = (PulseAudioContext *) dr->opaque; if (ctx == NULL) return GF_BAD_PARAM; - opt = gf_modules_get_option ((GF_BaseInterface *) dr, MODULE_NAME, - OUTPUT_NAME); - ctx->output_name = DEFAULT_OUTPUT_NAME; - if (opt != NULL) - { - ctx->output_name = opt; - } - else - { - gf_modules_set_option ((GF_BaseInterface *) dr, MODULE_NAME, - OUTPUT_NAME, DEFAULT_OUTPUT_NAME); - } - opt = gf_modules_get_option ((GF_BaseInterface *) dr, MODULE_NAME, - OUTPUT_DESCRIPTION); - ctx->output_description = DEFAULT_OUTPUT_DESCRIPTION; - if (opt != NULL) - { - ctx->output_description = opt; - } - else - { - gf_modules_set_option ((GF_BaseInterface *) dr, MODULE_NAME, - OUTPUT_DESCRIPTION, DEFAULT_OUTPUT_DESCRIPTION); - } + opt = gf_opts_get_key("PulseAudio", "Name"); + ctx->output_name = opt ? ctx->output_name : "GPAC"; + + opt = gf_opts_get_key("PulseAudio", "Description"); + ctx->output_description = opt ? opt : "GPAC Output"; return GF_OK; } @@ -129,9 +98,7 @@ PulseAudio_Shutdown (GF_AudioOutput * dr) } static GF_Err -PulseAudio_ConfigureOutput (GF_AudioOutput * dr, u32 * SampleRate, - u32 * NbChannels, u32 * nbBitsPerSample, - u32 channel_cfg) +PulseAudio_Configure(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *audioFormat, u64 channel_cfg) { int pa_error = 0; PulseAudioContext *ctx = (PulseAudioContext *) dr->opaque; @@ -144,6 +111,10 @@ PulseAudio_ConfigureOutput (GF_AudioOutput * dr, u32 * SampleRate, pa_simple_free (ctx->playback_handle); ctx->playback_handle = NULL; } + + //only support for PCM 16 + *audioFormat = GF_AUDIO_FMT_S16; + ctx->consecutive_zero_reads = 0; ctx->sample_spec.format = PA_SAMPLE_S16NE; ctx->sample_spec.channels = *NbChannels; @@ -190,7 +161,7 @@ PulseAudio_WriteAudio (GF_AudioOutput * dr) if (written <= 0) { ctx->consecutive_zero_reads++; - if (ctx->consecutive_zero_reads > 5) { + if (ctx->consecutive_zero_reads < 5) { gf_sleep(5); } else if (ctx->consecutive_zero_reads < 25) { gf_sleep(10); @@ -200,7 +171,7 @@ PulseAudio_WriteAudio (GF_AudioOutput * dr) return; } ctx->consecutive_zero_reads = 0; - written = pa_simple_write (ctx->playback_handle, data, written, &pa_error); + /*written = */pa_simple_write (ctx->playback_handle, data, written, &pa_error); if (pa_error != 0) { if (ctx->errors < 1) @@ -214,23 +185,6 @@ PulseAudio_WriteAudio (GF_AudioOutput * dr) } } -static void -PulseAudio_SetVolume (GF_AudioOutput * dr, u32 Volume) -{ - GF_LOG (GF_LOG_WARNING, GF_LOG_MMIO, - ("[PulseAudio] Set volume to %lu: not yet implemented.\n", Volume)); -} - -static void -PulseAudio_SetPan (GF_AudioOutput * dr, u32 Pan) -{ -} - -static void -PulseAudio_SetPriority (GF_AudioOutput * dr, u32 Priority) -{ -} - static u32 PulseAudio_GetAudioDelay (GF_AudioOutput * dr) { @@ -258,15 +212,6 @@ PulseAudio_GetAudioDelay (GF_AudioOutput * dr) return ms_delay; } -static GF_Err -PulseAudio_QueryOutputSampleRate (GF_AudioOutput * dr, u32 * desired_sr, - u32 * NbChannels, u32 * nbBitsPerSample) -{ - /** - * PulseAudio can do the resampling by itself and play any number of channels - */ - return GF_OK; -} void * NewPulseAudioOutput () @@ -289,14 +234,10 @@ NewPulseAudioOutput () driv->SelfThreaded = 0; driv->Setup = PulseAudio_Setup; driv->Shutdown = PulseAudio_Shutdown; - driv->ConfigureOutput = PulseAudio_ConfigureOutput; + driv->Configure = PulseAudio_Configure; driv->GetAudioDelay = PulseAudio_GetAudioDelay; - driv->SetVolume = PulseAudio_SetVolume; - driv->SetPan = PulseAudio_SetPan; - driv->SetPriority = PulseAudio_SetPriority; - driv->QueryOutputSampleRate = PulseAudio_QueryOutputSampleRate; driv->WriteAudio = PulseAudio_WriteAudio; - GF_REGISTER_MODULE_INTERFACE (driv, GF_AUDIO_OUTPUT_INTERFACE, MODULE_NAME, "gpac distribution"); + GF_REGISTER_MODULE_INTERFACE (driv, GF_AUDIO_OUTPUT_INTERFACE, "PulseAudio", "gpac distribution"); return driv; } diff --git a/modules/raw_out/Makefile b/modules/raw_out/Makefile deleted file mode 100644 index b7c9c57..0000000 --- a/modules/raw_out/Makefile +++ /dev/null @@ -1,58 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/raw_out - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -DGPAC_HAVE_CONFIG_H -I../../ - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -ifeq ($(ENABLE_JOYSTICK), yes) -CFLAGS+=-DENABLE_JOYSTICK -endif - -ifeq ($(ENABLE_JOYSTICK_NO_CURSOR), yes) -CFLAGS+=-DENABLE_JOYSTICK_NO_CURSOR -endif - -#common obj -OBJS= raw_video.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_raw_out$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols raw_out.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_raw_out-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac_static $(LDFLAGS) -endif - - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/raw_out/raw_video.c b/modules/raw_out/raw_video.c deleted file mode 100644 index cd58b1f..0000000 --- a/modules/raw_out/raw_video.c +++ /dev/null @@ -1,321 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / DirectX audio and video render module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - - -/*driver interfaces*/ -#include <gpac/modules/video_out.h> -#include <gpac/modules/audio_out.h> -#include <gpac/user.h> -#include <gpac/list.h> -#include <gpac/constants.h> - -#include <gpac/setup.h> - -typedef struct -{ - char *pixels; - u32 width, height; - u32 pixel_format, bpp; - Bool passthrough; - - u32 sample_rate, nb_channels, chan_cfg; -} RawContext; - -#define RAWCTX RawContext *rc = (RawContext *)dr->opaque - -static GF_Err raw_resize(GF_VideoOutput *dr, u32 w, u32 h) -{ - RAWCTX; - if (rc->pixels) gf_free(rc->pixels); - rc->width = w; - rc->height = h; - rc->pixels = (char*)gf_malloc(sizeof(char) * rc->bpp * w * h); - if (!rc->pixels) return GF_OUT_OF_MEM; - return GF_OK; -} - -static GF_Err RAW_BlitPassthrough(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window *src_wnd, GF_Window *dst_wnd, u32 overlay_type) -{ - return GF_OK; -} - -GF_Err RAW_Setup(GF_VideoOutput *dr, void *os_handle, void *os_display, u32 init_flags) -{ - const char *opt; - RAWCTX; - - opt = gf_modules_get_option((GF_BaseInterface *)dr, "RAWVideo", "RawOutput"); - if (opt && !strcmp(opt, "null")) { - rc->passthrough = GF_TRUE; - dr->Blit = RAW_BlitPassthrough; - dr->hw_caps |= GF_VIDEO_HW_HAS_RGB | GF_VIDEO_HW_HAS_RGBA | GF_VIDEO_HW_HAS_STRETCH | GF_VIDEO_HW_HAS_YUV | GF_VIDEO_HW_OPENGL | GF_VIDEO_HW_HAS_YUV_OVERLAY; - } - - if (init_flags & GF_TERM_WINDOW_TRANSPARENT) { - rc->bpp = 4; - rc->pixel_format = GF_PIXEL_ARGB; - } else { - rc->bpp = 3; - rc->pixel_format = GF_PIXEL_RGB_24; - opt = gf_modules_get_option((GF_BaseInterface *)dr, "RAWVideo", "PixelFormat"); - if (opt) { - if (!strcmp(opt, "555")) { - rc->bpp = 2; - rc->pixel_format = GF_PIXEL_RGB_555; - } else if (!strcmp(opt, "565")) { - rc->bpp = 2; - rc->pixel_format = GF_PIXEL_RGB_565; - } else if (!strcmp(opt, "bgr")) { - rc->bpp = 3; - rc->pixel_format = GF_PIXEL_BGR_24; - } else if (!strcmp(opt, "rgb")) { - rc->bpp = 3; - rc->pixel_format = GF_PIXEL_RGB_24; - } else if (!strcmp(opt, "bgr32")) { - rc->bpp = 4; - rc->pixel_format = GF_PIXEL_BGR_32; - } else if (!strcmp(opt, "rgb32")) { - rc->bpp = 4; - rc->pixel_format = GF_PIXEL_RGB_32; - } else if (!strcmp(opt, "rgba")) { - rc->bpp = 4; - rc->pixel_format = GF_PIXEL_RGBA; - } else if (!strcmp(opt, "argb")) { - rc->bpp = 4; - rc->pixel_format = GF_PIXEL_ARGB; - } - } - } - raw_resize(dr, 100, 100); - return GF_OK; -} - - -static void RAW_Shutdown(GF_VideoOutput *dr) -{ - RAWCTX; - if (rc->pixels) gf_free(rc->pixels); - rc->pixels = NULL; -} - - -static GF_Err RAW_Flush(GF_VideoOutput *dr, GF_Window *dest) -{ -#if 0 - RAWCTX; - char szName[1024]; - sprintf(szName, "test%d.png", gf_sys_clock()); - gf_img_png_enc_file(rc->pixels, rc->width, rc->height, rc->width*rc->bpp, rc->pixel_format, szName); -#endif - return GF_OK; -} - -static GF_Err RAW_LockBackBuffer(GF_VideoOutput *dr, GF_VideoSurface *vi, Bool do_lock) -{ - RAWCTX; - if (do_lock) { - if (!vi) return GF_BAD_PARAM; - memset(vi, 0, sizeof(GF_VideoSurface)); - vi->height = rc->height; - vi->width = rc->width; - vi->video_buffer = rc->pixels; - vi->pitch_x = rc->bpp; - vi->pitch_y = rc->bpp * vi->width; - vi->pixel_format = rc->pixel_format; - } - return GF_OK; -} - -static GF_Err RAW_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt) -{ - if (evt) { - switch (evt->type) { - case GF_EVENT_VIDEO_SETUP: - if (evt->setup.opengl_mode) return GF_NOT_SUPPORTED; - return raw_resize(dr, evt->setup.width, evt->setup.height); - } - } - return GF_OK; -} - -GF_VideoOutput *NewRawVideoOutput() -{ - RawContext *pCtx; - GF_VideoOutput *driv = (GF_VideoOutput *) gf_malloc(sizeof(GF_VideoOutput)); - memset(driv, 0, sizeof(GF_VideoOutput)); - GF_REGISTER_MODULE_INTERFACE(driv, GF_VIDEO_OUTPUT_INTERFACE, "Raw Video Output", "gpac distribution") - - pCtx = (RawContext*)gf_malloc(sizeof(RawContext)); - memset(pCtx, 0, sizeof(RawContext)); - - driv->opaque = pCtx; - - driv->Flush = RAW_Flush; - driv->LockBackBuffer = RAW_LockBackBuffer; - driv->Setup = RAW_Setup; - driv->Shutdown = RAW_Shutdown; - driv->ProcessEvent = RAW_ProcessEvent; - return driv; -} - -void DeleteRawVideoOutput(void *ifce) -{ - RawContext *rc; - GF_VideoOutput *driv = (GF_VideoOutput *) ifce; - rc = (RawContext *)driv->opaque; - gf_free(rc); - gf_free(driv); -} - - -static GF_Err RAW_AudioSetup(GF_AudioOutput *dr, void *os_handle, u32 num_buffers, u32 total_duration) -{ - return GF_OK; -} - -static void RAW_AudioShutdown(GF_AudioOutput *dr) -{ -} - -/*we assume what was asked is what we got*/ -static GF_Err RAW_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *nbBitsPerSample, u32 channel_cfg) -{ - RAWCTX; - rc->sample_rate = *SampleRate; - rc->nb_channels = *NbChannels; - rc->chan_cfg = channel_cfg; - return GF_OK; -} - -static void RAW_WriteAudio(GF_AudioOutput *dr) -{ - char buf[4096]; - dr->FillBuffer(dr->audio_renderer, buf, 4096); -} - -static void RAW_Play(GF_AudioOutput *dr, u32 PlayType) -{ -} - -static void RAW_SetVolume(GF_AudioOutput *dr, u32 Volume) -{ -} - -static void RAW_SetPan(GF_AudioOutput *dr, u32 Pan) -{ -} - -static GF_Err RAW_QueryOutputSampleRate(GF_AudioOutput *dr, u32 *desired_samplerate, u32 *NbChannels, u32 *nbBitsPerSample) -{ - return GF_OK; -} - -static u32 RAW_GetAudioDelay(GF_AudioOutput *dr) -{ - return 0; -} - -static u32 RAW_GetTotalBufferTime(GF_AudioOutput *dr) -{ - return 0; -} - -void *NewRawAudioOutput() -{ - RawContext *ctx; - GF_AudioOutput *driv; - ctx = (RawContext*)gf_malloc(sizeof(RawContext)); - memset(ctx, 0, sizeof(RawContext)); - driv = (GF_AudioOutput*)gf_malloc(sizeof(GF_AudioOutput)); - memset(driv, 0, sizeof(GF_AudioOutput)); - GF_REGISTER_MODULE_INTERFACE(driv, GF_AUDIO_OUTPUT_INTERFACE, "Raw Audio Output", "gpac distribution") - - driv->opaque = ctx; - - driv->SelfThreaded = GF_FALSE; - driv->Setup = RAW_AudioSetup; - driv->Shutdown = RAW_AudioShutdown; - driv->ConfigureOutput = RAW_ConfigureOutput; - driv->GetAudioDelay = RAW_GetAudioDelay; - driv->GetTotalBufferTime = RAW_GetTotalBufferTime; - driv->SetVolume = RAW_SetVolume; - driv->SetPan = RAW_SetPan; - driv->Play = RAW_Play; - driv->QueryOutputSampleRate = RAW_QueryOutputSampleRate; - driv->WriteAudio = RAW_WriteAudio; - return driv; -} - -static void DeleteAudioOutput(void *ifce) -{ - GF_AudioOutput *dr = (GF_AudioOutput *) ifce; - RawContext *ctx = (RawContext*)dr->opaque; - gf_free(ctx); - gf_free(dr); -} - - -#ifndef GPAC_STANDALONE_RENDER_2D - -/*interface query*/ -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { - GF_VIDEO_OUTPUT_INTERFACE, - GF_AUDIO_OUTPUT_INTERFACE, - 0 - }; - return si; -} - -/*interface create*/ -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - if (InterfaceType == GF_VIDEO_OUTPUT_INTERFACE) return (GF_BaseInterface *) NewRawVideoOutput(); - if (InterfaceType == GF_AUDIO_OUTPUT_INTERFACE) return (GF_BaseInterface *) NewRawAudioOutput(); - return NULL; -} - -/*interface destroy*/ -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { - case GF_VIDEO_OUTPUT_INTERFACE: - DeleteRawVideoOutput((GF_VideoOutput *)ifce); - break; - case GF_AUDIO_OUTPUT_INTERFACE: - DeleteAudioOutput((GF_AudioOutput *)ifce); - break; - } -} - - -GPAC_MODULE_STATIC_DECLARATION( raw_out ) - -#endif diff --git a/modules/redirect_av/Makefile b/modules/redirect_av/Makefile deleted file mode 100644 index 969bbb7..0000000 --- a/modules/redirect_av/Makefile +++ /dev/null @@ -1,53 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/redirect_av - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" $(ffmpeg_cflags) - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -LINKLIBS= -lgpac $(ffmpeg_lflags) -LOCAL_LIB=../../bin/gcc - -#common objects -OBJS=redirect_av.o - -ifeq ($(CONFIG_DARWIN), yes) -#fixme - use proper detection of libavdevice dependencies -LINKLIBS+=-lbz2 -endif - - - -SRCS := $(OBJS:.o=.c) - -LIB=gm_redirect_av$(DYN_LIB_SUFFIX) - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L$(LOCAL_LIB) $(LDFLAGS) $(LINKLIBS) - - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/redirect_av/ffmpeg_ts_muxer.c b/modules/redirect_av/ffmpeg_ts_muxer.c deleted file mode 100644 index e325e19..0000000 --- a/modules/redirect_av/ffmpeg_ts_muxer.c +++ /dev/null @@ -1,417 +0,0 @@ -#include "ts_muxer.h" -#include <libavformat/avformat.h> -#include <libavcodec/avcodec.h> - -#ifdef AVIO_FLAG_WRITE -#include <libavutil/samplefmt.h> -#define url_fopen avio_open -#define url_fclose avio_close -#define URL_WRONLY AVIO_FLAG_WRITE -#define av_write_header(__a) avformat_write_header(__a, NULL) -#define dump_format av_dump_format - -#define SAMPLE_FMT_S16 AV_SAMPLE_FMT_S16 -#endif - - - -#define STREAM_FRAME_RATE 25 /* 25 images/s */ -#define STREAM_NB_FRAMES ((int)(STREAM_DURATION * STREAM_FRAME_RATE)) -#define STREAM_PIX_FMT PIX_FMT_YUV420P /* default pix_fmt */ - -#define PACKETS_BUFFER_LEN 1024 - -struct avr_ts_muxer { - AVFormatContext *oc; - AVStream *audio_st, *video_st; - AVPacketList * videoPackets; - AVPacketList * audioPackets; - volatile Bool encode; - GF_Mutex *videoMx, *audioMx; - GF_Thread * tsEncodingThread; - const char * destination; -}; - -static Bool has_packet_ready(GF_AbstractTSMuxer* ts, GF_Mutex * mx, AVPacketList ** pkts) { - Bool ret; - gf_mx_p(mx); - ret = (*pkts) != NULL; - gf_mx_v(mx); - return ret; -} - - -/*! - * Wait for a packet on a given queue - * \param ts The muxer - * \param mx The mutex to use - * \param pkts The list of packets to watch - * \param idx a pointer to the index in the list - * \return NULL if encoding is ended, the first packet available otherwise - */ -static AVPacketList * wait_for_packet(GF_AbstractTSMuxer* ts, GF_Mutex * mx, AVPacketList ** pkts) { - AVPacketList * p; - gf_mx_p(mx); - while (ts->encode && !(*pkts)) { - gf_mx_v(mx); - gf_mx_p(mx); - gf_sleep(1); - } - if (!ts->encode) { - gf_mx_v(mx); - return NULL; - } - p = *pkts; - *pkts = p->next; - gf_mx_v(mx); - return p; -} - - -static u32 ts_interleave_thread_run(void *param) { - GF_AbstractTSMuxer * mux = (GF_AbstractTSMuxer *) param; - AVStream * video_st = mux->video_st; - AVStream * audio_st = mux->audio_st; - u64 audio_pts, video_pts; - u64 audioSize, videoSize, videoKbps, audioKbps; - u32 pass; - u32 now, start; - /* open the output file, if needed */ - if (!(mux->oc->oformat->flags & AVFMT_NOFILE)) { - if (url_fopen(&mux->oc->pb, mux->destination, URL_WRONLY) < 0) { - fprintf(stderr, "Could not open '%s'\n", mux->destination); - return 0; - } - } - /* write the stream header, if any */ - av_write_header(mux->oc); - audio_pts = video_pts = 0; - // Buffering... - gf_sleep(1000); - now = start = gf_sys_clock(); - audioSize = videoSize = 0; - audioKbps = videoKbps = 0; - pass = 0; - while ( mux->encode) { - pass++; - if (0== (pass%16)) { - now = gf_sys_clock(); - if (now - start > 1000) { - videoKbps = videoSize * 8000 / (now-start) / 1024; - audioKbps = audioSize * 8000 / (now-start) / 1024; - audioSize = videoSize = 0; - start = now; - GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("\rPTS audio="LLU" ("LLU"kbps), video="LLU" ("LLU"kbps)", audio_pts, audioKbps, video_pts, videoKbps)); - } - } - /* write interleaved audio and video frames */ - if (!video_st || - (audio_pts == AV_NOPTS_VALUE && has_packet_ready(mux, mux->audioMx, &mux->audioPackets)) || - ((audio_st && audio_pts < video_pts && audio_pts!= AV_NOPTS_VALUE))) { - AVPacketList * pl = wait_for_packet(mux, mux->audioMx, &mux->audioPackets); - if (!pl) - goto exit; - audio_pts = pl->pkt.pts ; - audioSize+=pl->pkt.size; - if (pl->pkt.pts == AV_NOPTS_VALUE) { - pl->pkt.pts = 0; - } - if (av_interleaved_write_frame(mux->oc, &(pl->pkt)) < 0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[AVRedirect] : failed to write audio interleaved frame audio_pts="LLU", video_pts="LLU"\n", audio_pts, video_pts)); - } - gf_free(pl); - } else { - AVPacketList * pl = wait_for_packet(mux, mux->videoMx, &mux->videoPackets); - if (!pl) - goto exit; - video_pts = pl->pkt.pts; - /* write the compressed frame in the media file */ - if (0 && audio_pts != AV_NOPTS_VALUE && audio_pts > video_pts && pl->next) { - u32 skipped = 0; - u64 first = video_pts; - /* We may be too slow... */ - gf_mx_p(mux->videoMx); - while (video_pts < audio_pts && pl->next) { - AVPacketList * old = pl; - // We skip frames... - pl = pl->next; - video_pts = pl->pkt.pts; - skipped++; - gf_free(old); - } - mux->videoPackets = pl->next; - gf_mx_v(mux->videoMx); - if (skipped > 0) - GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("Skipped %u video frames, frame was "LLU", but is now "LLU"\n", skipped, first, video_pts)); - } - videoSize+=pl->pkt.size; - video_pts = pl->pkt.pts; // * video_st->time_base.num / video_st->time_base.den; - assert( video_pts); - if (av_interleaved_write_frame(mux->oc, &(pl->pkt)) < 0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[AVRedirect] : failed to write video interleaved frame audio_pts="LLU", video_pts="LLU"\n", audio_pts, video_pts)); - } - gf_free(pl); - } - gf_sleep(1); - } -exit: - GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[AVRedirect] Ending TS thread...\n")); - av_write_trailer(mux->oc); - if (!(mux->oc->oformat->flags & AVFMT_NOFILE)) { - /* close the output file */ - url_fclose(mux->oc->pb); - } - return 0; -} - - -#if ((LIBAVFORMAT_VERSION_MAJOR == 52) && (LIBAVFORMAT_VERSION_MINOR <= 47)) || (LIBAVFORMAT_VERSION_MAJOR < 52) -#define GUESS_FORMAT guess_stream_format -#else -#define GUESS_FORMAT av_guess_format -#endif - -GF_AbstractTSMuxer * ts_amux_new(GF_AVRedirect * avr, u32 videoBitrateInBitsPerSec, u32 width, u32 height, u32 audioBitRateInBitsPerSec) { - GF_AbstractTSMuxer * ts = (GF_AbstractTSMuxer*)gf_malloc( sizeof(GF_AbstractTSMuxer)); - memset( ts, 0, sizeof( GF_AbstractTSMuxer)); - ts->oc = avformat_alloc_context(); - ts->destination = avr->destination; - av_register_all(); - ts->oc->oformat = GUESS_FORMAT(NULL, avr->destination, NULL); - if (!ts->oc->oformat) - ts->oc->oformat = GUESS_FORMAT("mpegts", NULL, NULL); - assert( ts->oc->oformat); -#if REDIRECT_AV_AUDIO_ENABLED - -#ifdef FF_API_AVFRAME_LAVC - ts->audio_st = avformat_new_stream(ts->oc, avr->audioCodec); -#else - ts->audio_st = av_new_stream(ts->oc, avr->audioCodec->id); -#endif - { - AVCodecContext * c = ts->audio_st->codec; - c->codec_id = avr->audioCodec->id; - c->codec_type = AVMEDIA_TYPE_AUDIO; - /* put sample parameters */ - c->sample_fmt = SAMPLE_FMT_S16; - c->bit_rate = audioBitRateInBitsPerSec; - c->sample_rate = avr->audioSampleRate; - c->channels = 2; - c->time_base.num = 1; - c->time_base.den = 1000; - // some formats want stream headers to be separate - if (ts->oc->oformat->flags & AVFMT_GLOBALHEADER) - c->flags |= CODEC_FLAG_GLOBAL_HEADER; - } -#endif - -#ifdef FF_API_AVFRAME_LAVC - ts->video_st = avformat_new_stream(ts->oc, avr->videoCodec); -#else - ts->video_st = av_new_stream(ts->oc, avr->videoCodec->id); -#endif - { - AVCodecContext * c = ts->video_st->codec; - c->codec_id = avr->videoCodec->id; - c->codec_type = AVMEDIA_TYPE_VIDEO; - - /* put sample parameters */ - c->bit_rate = videoBitrateInBitsPerSec; - /* resolution must be a multiple of two */ - c->width = width; - c->height = height; - /* time base: this is the fundamental unit of time (in seconds) in terms - of which frame timestamps are represented. for fixed-fps content, - timebase should be 1/framerate and timestamp increments should be - identically 1. */ - c->time_base.den = STREAM_FRAME_RATE; - c->time_base.num = 1; - c->gop_size = 12; /* emit one intra frame every twelve frames at most */ - c->pix_fmt = STREAM_PIX_FMT; - if (c->codec_id == CODEC_ID_MPEG2VIDEO) { - /* just for testing, we also add B frames */ - c->max_b_frames = 2; - } - if (c->codec_id == CODEC_ID_MPEG1VIDEO) { - /* Needed to avoid using macroblocks in which some coeffs overflow. - This does not happen with normal video, it just happens here as - the motion of the chroma plane does not match the luma plane. */ - c->mb_decision=2; - } - // some formats want stream headers to be separate - if (ts->oc->oformat->flags & AVFMT_GLOBALHEADER) - c->flags |= CODEC_FLAG_GLOBAL_HEADER; - - } - //av_set_pts_info(ts->audio_st, 33, 1, audioBitRateInBitsPerSec); - -#ifndef AVIO_FLAG_WRITE - /* set the output parameters (must be done even if no - parameters). */ - if (av_set_parameters(ts->oc, NULL) < 0) { - fprintf(stderr, "Invalid output format parameters\n"); - return NULL; - } -#endif - - dump_format(ts->oc, 0, avr->destination, 1); - GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[AVRedirect] DUMPING to %s...\n", ts->destination)); - -#if (LIBAVCODEC_VERSION_MAJOR<55) - if (avcodec_open(ts->video_st->codec, avr->videoCodec) < 0) { -#else - if (avcodec_open2(ts->video_st->codec, avr->videoCodec, NULL) < 0) { -#endif - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[AVRedirect] failed to open video codec\n")); - return NULL; - } -#if REDIRECT_AV_AUDIO_ENABLED -#if (LIBAVCODEC_VERSION_MAJOR<55) - if (avcodec_open(ts->audio_st->codec, avr->audioCodec) < 0) { -#else - if (avcodec_open2(ts->audio_st->codec, avr->audioCodec, NULL) < 0) { -#endif - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[AVRedirect] failed to open audio codec\n")); - return NULL; - } - ts->audioMx = gf_mx_new("TS_AudioMx"); -#endif - ts->videoMx = gf_mx_new("TS_VideoMx"); - ts->tsEncodingThread = gf_th_new("ts_interleave_thread_run"); - ts->encode = GF_TRUE; - ts->audioPackets = NULL; - ts->videoPackets = NULL; - gf_th_run(ts->tsEncodingThread, ts_interleave_thread_run, ts); - return ts; -} - -void ts_amux_del(GF_AbstractTSMuxer * muxerToDelete) { - if (!muxerToDelete) - return; - muxerToDelete->encode = GF_FALSE; - gf_sleep(100); - gf_th_stop(muxerToDelete->tsEncodingThread); - muxerToDelete->tsEncodingThread = NULL; -#if REDIRECT_AV_AUDIO_ENABLED - gf_mx_del(muxerToDelete->audioMx); - muxerToDelete->audioMx = NULL; -#endif - gf_mx_del(muxerToDelete->videoMx); - muxerToDelete->videoMx = NULL; - if (muxerToDelete->video_st) { - avcodec_close(muxerToDelete->video_st->codec); - muxerToDelete->video_st = NULL; - } -#if REDIRECT_AV_AUDIO_ENABLED - if (muxerToDelete->audio_st) { - avcodec_close(muxerToDelete->audio_st->codec); - muxerToDelete->audio_st = NULL; - } -#endif - /* write the trailer, if any. the trailer must be written - * before you close the CodecContexts open when you wrote the - * header; otherwise write_trailer may try to use memory that - * was freed on av_codec_close() */ - if (muxerToDelete->oc) { - u32 i; - /* free the streams */ - for (i = 0; i < muxerToDelete->oc->nb_streams; i++) { - av_freep(&muxerToDelete->oc->streams[i]->codec); - av_freep(&muxerToDelete->oc->streams[i]); - } - - /* free the stream */ - av_free(muxerToDelete->oc); - muxerToDelete->oc = NULL; - } -} - -Bool ts_encode_audio_frame(GF_AbstractTSMuxer * ts, uint8_t * data, int encoded, u64 pts) { - AVPacketList *pl; - AVPacket * pkt; - if (!ts->encode) - return GF_TRUE; - pl = (AVPacketList*)gf_malloc(sizeof(AVPacketList)); - pl->next = NULL; - pkt = &(pl->pkt); - av_init_packet(pkt); - assert( ts->audio_st); - assert( ts->audio_st->codec); - pkt->flags = 0; - if (ts->audio_st->codec->coded_frame) { - if (ts->audio_st->codec->coded_frame->key_frame) - pkt->flags = AV_PKT_FLAG_KEY; - if (ts->audio_st->codec->coded_frame->pts != AV_NOPTS_VALUE) { - pkt->pts = av_rescale_q(ts->audio_st->codec->coded_frame->pts, ts->audio_st->codec->time_base, ts->audio_st->time_base); - } else { - if (pts == AV_NOPTS_VALUE) - pkt->pts = AV_NOPTS_VALUE; - else { - pkt->pts = av_rescale_q(pts, ts->audio_st->codec->time_base, ts->audio_st->time_base); - } - } - } else { - if (pts == AV_NOPTS_VALUE) - pkt->pts = AV_NOPTS_VALUE; - else - pkt->pts = av_rescale_q(pts, ts->audio_st->codec->time_base, ts->audio_st->time_base); - } - pkt->stream_index= ts->audio_st->index; - pkt->data = data; - pkt->size = encoded; - //fprintf(stderr, "AUDIO PTS="LLU" was: "LLU" (%p)\n", pkt->pts, pts, pl); - gf_mx_p(ts->audioMx); - if (!ts->audioPackets) - ts->audioPackets = pl; - else { - AVPacketList * px = ts->audioPackets; - while (px->next) - px = px->next; - px->next = pl; - } - gf_mx_v(ts->audioMx); - return GF_FALSE; -} - -Bool ts_encode_video_frame(GF_AbstractTSMuxer* ts, uint8_t* data, int encoded) { - AVPacketList *pl; - AVPacket * pkt; - if (!ts->encode) - return GF_TRUE; - pl = (AVPacketList*)gf_malloc(sizeof(AVPacketList)); - pl->next = NULL; - pkt = &(pl->pkt); - - av_init_packet(pkt); - - if (ts->video_st->codec->coded_frame->pts != AV_NOPTS_VALUE) { - //pkt->pts= av_rescale_q(ts->video_st->codec->coded_frame->pts, ts->video_st->codec->time_base, ts->video_st->time_base); - pkt->pts = ts->video_st->codec->coded_frame->pts * ts->video_st->time_base.den / ts->video_st->time_base.num / 1000; - //pkt->pts = ts->video_st->codec->coded_frame->pts; - } - if (ts->video_st->codec->coded_frame->key_frame) - pkt->flags |= AV_PKT_FLAG_KEY; - pkt->stream_index= ts->video_st->index; - pkt->data= data; - pkt->size= encoded; - //fprintf(stderr, "VIDEO PTS="LLU" was: "LLU" (%p)\n", pkt->pts, ts->video_st->codec->coded_frame->pts, pl); - gf_mx_p(ts->videoMx); - if (!ts->videoPackets) - ts->videoPackets = pl; - else { - AVPacketList * px = ts->videoPackets; - while (px->next) - px = px->next; - px->next = pl; - } - gf_mx_v(ts->videoMx); - return GF_FALSE; -} - -AVCodecContext * ts_get_video_codec_context(GF_AbstractTSMuxer * ts) { - return !ts ? NULL : ts->video_st->codec; -} - -AVCodecContext * ts_get_audio_codec_context(GF_AbstractTSMuxer * ts) { - return !ts ? NULL : ts->audio_st->codec; -} diff --git a/modules/redirect_av/gpac_ts_muxer.c b/modules/redirect_av/gpac_ts_muxer.c deleted file mode 100644 index c79638a..0000000 --- a/modules/redirect_av/gpac_ts_muxer.c +++ /dev/null @@ -1,178 +0,0 @@ -#include "ts_muxer.h" -#include <gpac/mpegts.h> - -struct avr_ts_muxer { - GF_M2TS_Mux *muxer; - GF_ESIPacket videoCurrentTSPacket; - GF_ESIPacket audioCurrentTSPacket; - GF_ESInterface * video, * audio; - u64 frameTimeSentOverTS; - GF_Socket * ts_output_udp_sk; -}; - -/*! - * Sends the TS mux to socket - * \param avr The AVRedirect structure pointer - */ -static GF_Err sendTSMux(GF_AbstractTSMuxer * ts) -{ - u32 status; - const char * pkt; - GF_Err e; - u32 padding, data; - padding = data = 0; - while ( (NULL!= ( pkt = gf_m2ts_mux_process ( ts->muxer, &status )))) - { - switch (status) { - case GF_M2TS_STATE_IDLE: - break; - case GF_M2TS_STATE_DATA: - data+=188; - break; - case GF_M2TS_STATE_PADDING: - padding+=188; - break; - default: - break; - } - if (ts->ts_output_udp_sk) { - e = gf_sk_send ( ts->ts_output_udp_sk, pkt, 188); - if ( e ) - { - GF_LOG ( GF_LOG_ERROR, GF_LOG_MODULE, ( "[AVRedirect] Unable to send TS data : %s\n", gf_error_to_string(e)) ); - return e; - } - } - } - if (data || padding) - GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[AVRedirect] : Sent TS data=%u/padding=%u\n", data, padding)); - return GF_OK; -} - -//Bool *add_video_stream(GF_AbstractTSMuxer * ts, int codec_id) - -Bool ts_encode_audio_frame(GF_AbstractTSMuxer * ts, AVFrame * encodedFrame, uint8_t * data, int encoded) { - ts->audioCurrentTSPacket.data = data; - ts->audioCurrentTSPacket.data_len = encoded; - ts->audioCurrentTSPacket.flags = GF_ESI_DATA_AU_START|GF_ESI_DATA_AU_END | GF_ESI_DATA_HAS_CTS | GF_ESI_DATA_HAS_DTS; - //avr->audioCurrentTSPacket.cts = avr->audioCurrentTSPacket.dts = myTime; - ts->videoCurrentTSPacket.dts = ts->videoCurrentTSPacket.cts = gf_m2ts_get_sys_clock(ts->muxer); - ts->audio->output_ctrl(ts->audio, GF_ESI_OUTPUT_DATA_DISPATCH, &(ts->audioCurrentTSPacket)); - return 0; -} - -static GF_Err void_input_ctrl(GF_ESInterface *ifce, u32 act_type, void *param) -{ - GF_AbstractTSMuxer * ts = (GF_AbstractTSMuxer *) ifce->input_udta; - if (!ts) - return GF_BAD_PARAM; - switch (act_type) { - case GF_ESI_INPUT_DATA_FLUSH: - break; - case GF_ESI_INPUT_DESTROY: - break; - /* case GF_ESI_INPUT_DATA_PULL: - gf_mx_p(ts->encodingMutex); - if (ts->frameTimeEncoded > ts->frameTimeSentOverTS) { - //fprintf(stderr, "Data PULL, avr=%p, avr->video=%p, encoded="LLU", sent over TS="LLU"\n", avr, &avr->video, avr->frameTimeEncoded, avr->frameTimeSentOverTS); - ts->video->output_ctrl( ts->video, GF_ESI_OUTPUT_DATA_DISPATCH, &(ts->videoCurrentTSPacket)); - ts->frameTimeSentOverTS = ts->frameTime; - } else { - //fprintf(stderr, "Data PULL IGNORED : encoded = "LLU", sent on TS="LLU"\n", avr->frameTimeEncoded, avr->frameTimeSentOverTS); - } - gf_mx_v(avr->encodingMutex); - break;*/ - case GF_ESI_INPUT_DATA_RELEASE: - break; - default: - fprintf(stderr, "Asking unknown : %u\n", act_type); - } - return GF_OK; -} - -Bool ts_encode_video_frame(GF_AbstractTSMuxer * ts, AVFrame * encodedFrame, uint8_t * data, int encoded, AVCodecContext * videoCodecContext) { - if ( ts->muxer ) - { // currentFrameTimeProcessed / 1000; // - ts->videoCurrentTSPacket.dts = videoCodecContext->coded_frame->coded_picture_number; - ts->videoCurrentTSPacket.cts = videoCodecContext->coded_frame->display_picture_number; - //avr->videoCurrentTSPacket.dts = avr->videoCurrentTSPacket.cts = sysclock; - //ts->videoCurrentTSPacket.dts = ts->videoCurrentTSPacket.cts = currentFrameTimeProcessed; - ts->videoCurrentTSPacket.dts = ts->videoCurrentTSPacket.cts = gf_m2ts_get_sys_clock(ts->muxer); - ts->videoCurrentTSPacket.data = data; - ts->videoCurrentTSPacket.data_len = encoded; - ts->videoCurrentTSPacket.flags = GF_ESI_DATA_HAS_CTS | GF_ESI_DATA_HAS_DTS; - //if (ts_packets_sent == 0) { - ts->videoCurrentTSPacket.flags|=GF_ESI_DATA_AU_START|GF_ESI_DATA_AU_END ; - //} - void_input_ctrl(ts->video, GF_ESI_INPUT_DATA_PULL, NULL); - sendTSMux(ts); - } - return 0; -} - -GF_AbstractTSMuxer * ts_amux_new(GF_AVRedirect * avr, u32 videoBitrateInBitsPerSec, u32 audioBitRateInBitsPerSec, GF_Socket * ts_output_udp_sk) { - GF_AbstractTSMuxer * ts = malloc(sizeof(GF_AbstractTSMuxer)); - ts->muxer = gf_m2ts_mux_new ( videoBitrateInBitsPerSec * audioBitRateInBitsPerSec + 1000000, 0, 1 ); - ts->ts_output_udp_sk = ts_output_udp_sk; - { - //u32 cur_pid = 100; /*PIDs start from 100*/ - GF_M2TS_Mux_Program *program = gf_m2ts_mux_program_add ( ts->muxer, 1, 100, 0, 0 ); - - ts->video = gf_malloc( sizeof( GF_ESInterface)); - memset( ts->video, 0, sizeof( GF_ESInterface)); - //audio.stream_id = 101; - //gf_m2ts_program_stream_add ( program, &audifero, 101, 1 ); - ts->video->stream_id = 101; - ts->video->stream_type = GF_STREAM_VISUAL; - ts->video->bit_rate = 410000; - /* ms resolution */ - ts->video->timescale = 1000; - - ts->video->object_type_indication = GPAC_OTI_VIDEO_MPEG2_SIMPLE; - ts->video->input_udta = ts; -#ifdef TS_MUX_MODE_PUT - ts->video->caps = GF_ESI_SIGNAL_DTS; -#else - avr->video->input_ctrl = void_input_ctrl; - avr->video->caps = GF_ESI_AU_PULL_CAP | GF_ESI_SIGNAL_DTS; -#endif /* TS_MUX_MODE_PUT */ - - - ts->audio = gf_malloc( sizeof( GF_ESInterface)); - memset( ts->audio, 0, sizeof( GF_ESInterface)); - ts->audio->stream_id = 102; - ts->audio->stream_type = GF_STREAM_AUDIO; - ts->audio->bit_rate = audioBitRateInBitsPerSec; - /* ms resolution */ - ts->audio->timescale = 1000; - - ts->audio->object_type_indication = GPAC_OTI_AUDIO_MPEG2_PART3; - ts->audio->input_udta = ts; -#ifdef TS_MUX_MODE_PUT - ts->audio->caps = 0; -#else - ts->audio->input_ctrl = void_input_ctrl; - ts->audio->caps = GF_ESI_AU_PULL_CAP; -#endif /* TS_MUX_MODE_PUT */ - - gf_m2ts_program_stream_add ( program, ts->video, 101, 0 ); - gf_m2ts_program_stream_add ( program, ts->audio, 102, 1 ); - - assert(program->streams->mpeg2_stream_type == GF_M2TS_VIDEO_MPEG2 || - program->streams->mpeg2_stream_type == GF_M2TS_VIDEO_DCII); - } - gf_m2ts_mux_update_config ( ts->muxer, 1 ); - return ts; -} - -void ts_amux_del(GF_AbstractTSMuxer * ts) { - if (!ts) - return; - if ( ts->ts_output_udp_sk ) - { - gf_sk_del ( ts->ts_output_udp_sk ); - ts->ts_output_udp_sk = NULL; - } - - -} diff --git a/modules/redirect_av/redirect_av.c b/modules/redirect_av/redirect_av.c deleted file mode 100644 index 5d3148b..0000000 --- a/modules/redirect_av/redirect_av.c +++ /dev/null @@ -1,865 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean le Feuvre - * Copyright (c) 2010-20XX Telecom ParisTech - * All rights reserved - * - * This file is part of GPAC / AVI Recorder demo module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ -#include <stdlib.h> - -#if (defined(WIN32) || defined(_WIN32_WCE)) && !defined(__MINGW32__) - -#define EMULATE_INTTYPES -#define EMULATE_FAST_INT -#ifndef inline -#define inline __inline -#endif - -#if defined(__SYMBIAN32__) -#define EMULATE_INTTYPES -#endif - - -#ifndef __MINGW32__ -#define __attribute__(s) -#endif - - -/*include FFMPEG APIs*/ -#ifdef _WIN32_WCE -#define inline __inline -#endif - - -# define INT64_C(x) (x ## i64) -# define UINT64_C(x) (x ## Ui64) - - -#endif - - -#include "ts_muxer.h" - -//#define USE_GPAC_MPEG2TS -#undef USE_GPAC_MPEG2TS - -//#define MULTITHREAD_REDIRECT_AV - -#if (LIBAVCODEC_VERSION_MAJOR<56) -#define REDIRECT_AV_AUDIO_ENABLED 1 -#endif - -#ifdef USE_GPAC_MPEG2TS -#include "gpac_ts_muxer.c" -#else -#include "ffmpeg_ts_muxer.c" -#endif /* USE_GPAC_MPEG2TS */ - -#include <libavcodec/avcodec.h> -#include <libswscale/swscale.h> -#include <libavutil/avutil.h> - - -#if (defined(WIN32) || defined(_WIN32_WCE)) && !defined(__GNUC__) - -#if defined(_WIN32_WCE) -#pragma comment(lib, "toolhelp") -#pragma comment(lib, "winsock") -#endif - -#define _TOSTR(_val) #_val -#define TOSTR(_val) _TOSTR(_val) - -#endif - - - - -/* This number * 188 should be lower than the UDP packet size */ -#define TS_PACKETS_PER_UDP_PACKET 1 - -#define audioCodecBitrate 96000 - -#if !defined(WIN32) && !defined(_WIN32_WCE) -#include <unistd.h> -#endif - -static const u32 maxFPS = 25; - -static const char * moduleName = "AVRedirect"; - -static const char * AVR_VIDEO_CODEC_OPTION = "VideoCodec"; - - -#define AVR_TIMERS_LENGHT 25 - -#define U32_ABS( a, b ) (a > b ? a - b : b - a) - -#define DUMP_MP3 -#undef DUMP_MP3 - -#if REDIRECT_AV_AUDIO_ENABLED -/** - * This thread sends the frame to TS mux - * \param param The GF_AVRedirect pointer - */ -static u32 audio_encoding_thread_run(void *param) -{ - u8 * inBuff; - u8 * outBuff; - u32 inBuffSize, outBuffSize, samplesReaden, toRead; - u64 myTime = 0; - u32 frameCountSinceReset = 0; - u32 lastCurrentTime; - Bool sendPts = GF_TRUE; - GF_AVRedirect * avr = (GF_AVRedirect*) param; - AVCodecContext * ctx = NULL; - assert( avr ); - - outBuffSize = FF_MIN_BUFFER_SIZE; - - outBuff = (u8*)gf_malloc(outBuffSize* sizeof(u8)); - inBuff = NULL; -#ifdef DUMP_MP3 - FILE * mp3 = gf_fopen("/tmp/dump.mp3", "w"); -#endif /* DUMP_MP3 */ - sendPts = GF_TRUE; - gf_sc_add_audio_listener ( avr->term->compositor, &avr->audio_listen ); - while (avr->is_running && !ctx) { - ctx = ts_get_audio_codec_context(avr->ts_implementation); - gf_sleep(16); - } - if (!ctx) { - goto exit; - } - - fprintf(stderr, "******* Audio Codec Context = %d/%d, start="LLU", frame_size=%u\n", ctx->time_base.num, ctx->time_base.den, ctx->timecode_frame_start, ctx->frame_size); - samplesReaden = ctx->frame_size * ctx->channels; - - //fprintf(stderr, "SETUP input sample size=%u, output samplesize=%u, buffsize=%u, samplesReaden=%u\n", ctx->input_sample_size, ctx->frame_size, sizeof(outbuf), samplesReaden); - // 2 chars are needed for each short - toRead = samplesReaden * 2; - inBuffSize = toRead; - inBuff = (u8*)gf_malloc(inBuffSize * sizeof(u8)); - while (avr->is_running && !avr->audioCurrentTime) { - gf_sleep(16); - } - myTime = lastCurrentTime = avr->audioCurrentTime; - frameCountSinceReset = 0; - while (avr->is_running) { - u32 readen; - if (U32_ABS(avr->audioCurrentTime, myTime) > 25000) { - //GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[AVRedirect] Drift in audio : audioCurrentTime = %u, myTime=%u, resync...\n", avr->audioCurrentTime, myTime)); - //myTime = lastCurrentTime = avr->audioCurrentTime; - //frameCountSinceReset = 0; - sendPts = GF_TRUE; - } - while (toRead <= gf_ringbuffer_available_for_read(avr->pcmAudio) ) { - memset( inBuff, 0, inBuffSize); - memset( outBuff, 0, outBuffSize); - readen = gf_ringbuffer_read(avr->pcmAudio, inBuff, toRead); - assert( readen == toRead ); - if (avr->encode) - { - //u32 oldFrameSize = ctx->frame_size; - //ctx->frame_size = readable / (2 * ctx->channels); - //assert( oldFrameSize <= ctx->frame_size ); - /* buf_size * input_sample_size / output_sample_size */ - int encoded = avcodec_encode_audio(ctx, outBuff, outBuffSize, (const short *) inBuff); - - if (encoded < 0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[RedirectAV]: failed to encode audio, buffer size=%u, readen=%u, frame_size=%u\n", outBuffSize, readen, ctx->frame_size)); - } else if (encoded > 0) { - ts_encode_audio_frame(avr->ts_implementation, outBuff, encoded, sendPts ? myTime : AV_NOPTS_VALUE ); - frameCountSinceReset++; -#ifdef DUMP_MP3 - gf_fwrite( outBuff, sizeof(char), encoded, mp3); -#endif /* DUMP_MP3 */ - //if (ctx->codec->id == CODEC_ID_MP3) { - /* It seems the MP3 codec only fetches 50% of data... */ - // myTime= lastCurrentTime + (frameCountSinceReset * ctx->frame_size * 500 / ctx->sample_rate); - //} else - //myTime = lastCurrentTime + (frameCountSinceReset * toRead * 1000 / ctx->sample_rate/ ctx->channels / 4); - /* Avoid overflow , multiply by 10 and divide sample rate by 100 instead of *1000 / sampleRate */ - myTime = lastCurrentTime + (frameCountSinceReset * toRead * 10 / (ctx->sample_rate / 100) / ctx->channels / 4); - //sendPts = 0; - sendPts = GF_TRUE; - } else { - GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[RedirectAV]: no encoded frame.\n")); - //frameCountSinceReset++; - } - //ctx->frame_size = oldFrameSize; - } - } - gf_sleep(1); - } -exit: - GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[AVRedirect] Ending audio encoding thread...\n")); - if (inBuff) - gf_free( inBuff ); - if (outBuff) - gf_free( outBuff ); -#ifdef DUMP_MP3 - if (mp3) - gf_fclose(mp3); -#endif /* DUMP_MP3 */ - if (avr->term) - gf_sc_remove_audio_listener ( avr->term->compositor, &avr->audio_listen ); - return GF_OK; -} -#endif - -/** - * This thread sends the frame to TS mux - * \param param The GF_AVRedirect pointer - */ -static u32 video_encoding_thread_run(void *param) -{ - GF_AVRedirect * avr = (GF_AVRedirect*) param; - u64 currentFrameTimeProcessed = 0, lastEncodedFrameTime = 0; - AVCodecContext * ctx = NULL; - assert( avr ); - gf_sc_add_video_listener ( avr->term->compositor, &avr->video_listen ); - while (avr->is_running && (!ctx || !avr->swsContext)) { - ctx = ts_get_video_codec_context(avr->ts_implementation); - gf_sleep(16); - } - if (!ctx) { - goto exit; - } - fprintf(stderr, "******* Video Codec Context = %d/%d, start="LLU"\n", ctx->time_base.num, ctx->time_base.den, ctx->timecode_frame_start); - while (avr->is_running) { - { - gf_mx_p(avr->frameMutex); - while (!avr->frameTime || currentFrameTimeProcessed == avr->frameTime) { - gf_mx_v(avr->frameMutex); - if (!avr->is_running) { - goto exit; - } - gf_mx_p(avr->frameMutex); - gf_sleep(1); - } - assert( currentFrameTimeProcessed != avr->frameTime); - currentFrameTimeProcessed = avr->frameTime; - { - avpicture_fill ( ( AVPicture * ) avr->RGBpicture, (const uint8_t*)avr->frame, PIX_FMT_RGB24, avr->srcWidth, avr->srcHeight ); - assert( avr->swsContext ); - sws_scale ( avr->swsContext, -#ifdef USE_AVCODEC2 - ( const uint8_t * const * ) -#else - ( uint8_t ** ) -#endif /* USE_AVCODEC2 */ - avr->RGBpicture->data, avr->RGBpicture->linesize, - 0, avr->srcHeight, - avr->YUVpicture->data, avr->YUVpicture->linesize ); -#ifdef AVR_DUMP_RAW_AVI - if ( AVI_write_frame ( avr->avi_out, avr->frame, avr->size, 1 ) <0 ) - { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ( "[AVRedirect] Error writing video frame\n" ) ); - } -#endif /* AVR_DUMP_RAW_AVI */ - gf_mx_v(avr->frameMutex); - if (avr->encode) - { - int written; - u32 sysclock_begin = gf_sys_clock(), sysclock_end=0; - avr->YUVpicture->pts = currentFrameTimeProcessed; - { - int got_packet = 0; - AVPacket pkt; - av_init_packet(&pkt); - pkt.data = avr->videoOutbuf; - pkt.size = avr->videoOutbufSize; - written = avcodec_encode_video2(ctx, &pkt, avr->YUVpicture, &got_packet); - if (written >= 0) { - written = pkt.size; - if (got_packet) { - ctx->coded_frame->pts = pkt.pts; - ctx->coded_frame->key_frame = !!(pkt.flags & AV_PKT_FLAG_KEY); - } - } - } - sysclock_end = gf_sys_clock(); - GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("Encoding frame PTS="LLD", frameNum=%u, time="LLU", size=%d\t in %ums\n", avr->YUVpicture->pts, avr->YUVpicture->coded_picture_number, currentFrameTimeProcessed, written, sysclock_end-sysclock_begin)); - ctx->coded_frame->pts = currentFrameTimeProcessed; - - if (written<0) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ( "[AVRedirect] Error while encoding video frame =%d\n", written ) ); - } else { - if (written>0) - ts_encode_video_frame(avr->ts_implementation, avr->videoOutbuf, written); - lastEncodedFrameTime = currentFrameTimeProcessed; - } - } - } - } - avr->frameTimeEncoded = currentFrameTimeProcessed; - gf_sleep(1); - } /* End of main loop */ -exit: - GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[AVRedirect] Ending video encoding thread...\n")); - if (avr->term) - gf_sc_remove_video_listener ( avr->term->compositor, &avr->video_listen ); - return 0; -} - -#define VIDEO_RATE 400000 - -static Bool start_if_needed(GF_AVRedirect *avr) { - enum PixelFormat pxlFormatForCodec = PIX_FMT_YUV420P; - if (avr->is_open) - return GF_FALSE; - gf_mx_p(avr->frameMutex); - if (avr->is_open) { - gf_mx_v(avr->frameMutex); - return GF_FALSE; - } - if (!avr->srcWidth || !avr->srcHeight -#if REDIRECT_AV_AUDIO_ENABLED - || !avr->audioSampleRate || !avr->audioChannels -#endif - ) { - gf_mx_v(avr->frameMutex); - return 3; - } -#ifdef AVR_DUMP_RAW_AVI - avr->avi_out = AVI_open_output_file ( "dump.avi" ); - if ( !avr->avi_out ) - { - gf_mx_v(avr->frameMutex); - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ( "[AVRedirect] Error opening output AVI file\n" ) ); - return 4; - } -#endif /* AVR_DUMP_RAW_AVI */ - GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[AVRedirect] Initializing...\n")); - if (!avr->pcmAudio) - avr->pcmAudio = gf_ringbuffer_new(48000*2*2); //1s of 16b stereo 48000Hz - - /* Setting up the video encoding ... */ - { - avr->YUVpicture = avr->RGBpicture = NULL; - avr->videoOutbuf = NULL; -#if REDIRECT_AV_AUDIO_ENABLED - if ( !avr->audioCodec) - { - gf_mx_v(avr->frameMutex); - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ( "[AVRedirect] Cannot find audio codec.\n" ) ); - return GF_TRUE; - } -#endif - - if ( !avr->videoCodec ) - { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ( "[AVRedirect] Cannot find video codec.\n" ) ); - return 2; - } - - if (avr->videoCodec->id == CODEC_ID_MJPEG) { - pxlFormatForCodec = PIX_FMT_YUVJ420P; - } - -#if !defined(FF_API_AVFRAME_LAVC) - avr->RGBpicture = avcodec_alloc_frame(); -#else - avr->RGBpicture = av_frame_alloc(); -#endif - assert ( avr->RGBpicture ); - avr->RGBpicture->data[0] = NULL; -#if !defined(FF_API_AVFRAME_LAVC) - avr->YUVpicture = avcodec_alloc_frame(); -#else - avr->YUVpicture = av_frame_alloc(); -#endif - assert ( avr->YUVpicture ); - { - u32 sz = sizeof ( uint8_t ) * avpicture_get_size ( pxlFormatForCodec, avr->srcWidth, avr->srcHeight ); - avr->yuv_data = (uint8_t*)av_malloc ( sz ); /* size for YUV 420 */ - assert ( avr->yuv_data ); - memset ( avr->yuv_data, 0, sz ); - avpicture_fill ( ( AVPicture* ) avr->YUVpicture, avr->yuv_data, pxlFormatForCodec, avr->srcWidth, avr->srcHeight ); - avr->YUVpicture->coded_picture_number = 0; - } - avr->videoOutbufSize = avr->srcHeight * avr->srcWidth * 4; - avr->videoOutbuf = (uint8_t*)gf_malloc ( avr->videoOutbufSize ); - memset ( avr->videoOutbuf, 0, avr->videoOutbufSize ); - } -#ifdef AVR_DUMP_RAW_AVI - GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ( "[AVRedirect] Open output AVI file %s\n", "dump.avi" ) ); -#endif /* AVR_DUMP_RAW_AVI */ - /* Setting up the TS mux */ - { - /* - GF_Socket * ts_output_udp_sk = gf_sk_new ( GF_SOCK_TYPE_UDP ); - if ( gf_sk_is_multicast_address ( avr->udp_address ) ) - { - e = gf_sk_setup_multicast ( ts_output_udp_sk, avr->udp_address, avr->udp_port, 0, 0, NULL ); - } - else - { - e = gf_sk_bind ( ts_output_udp_sk, NULL, avr->udp_port, avr->udp_address, avr->udp_port, GF_SOCK_REUSE_PORT ); - } - gf_sk_set_buffer_size(ts_output_udp_sk, 0, 188 * TS_PACKETS_PER_UDP_PACKET); - gf_sk_set_block_mode(ts_output_udp_sk, 0);*/ - assert( avr->destination ); - avr->ts_implementation = ts_amux_new(avr, VIDEO_RATE, avr->srcWidth, avr->srcHeight, audioCodecBitrate); - } - /* - if ( e ) - { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ( "[AVRedirect] Error while initializing UDP address %s\n", gf_error_to_string ( e ) ) ); - return e; - } - */ - avr->is_open = avr->ts_implementation != NULL; - gf_mx_v(avr->frameMutex); - return avr->is_open ? GF_TRUE : GF_FALSE; -} - - -static GF_Err avr_open ( GF_AVRedirect *avr ) -{ - assert( avr ); - if ( avr->is_open) - return GF_OK; - if (!avr->is_running) { - avr->is_running = GF_TRUE; - gf_th_run(avr->encodingThread, video_encoding_thread_run, avr); -#if REDIRECT_AV_AUDIO_ENABLED - gf_th_run(avr->audioEncodingThread, audio_encoding_thread_run, avr); -#endif - } - return GF_OK; -} - - - -static void avr_on_audio_frame ( void *udta, char *buffer, u32 buffer_size, u32 time, u32 delay_ms ) -{ - GF_AVRedirect *avr = ( GF_AVRedirect * ) udta; - if (start_if_needed(avr)) - return; -#ifdef AVR_DUMP_RAW_AVI - AVI_write_audio ( avr->avi_out, buffer, buffer_size ); -#endif /* AVR_DUMP_RAW_AVI */ - { - gf_ringbuffer_write(avr->pcmAudio, buffer, buffer_size); - avr->audioCurrentTime = time - delay_ms; - } -} - -static void avr_on_audio_reconfig ( void *udta, u32 samplerate, u32 bits_per_sample, u32 nb_channel, u32 channel_cfg ) -{ - GF_AVRedirect *avr = ( GF_AVRedirect * ) udta; - if (avr->audioSampleRate != samplerate || avr->audioChannels != nb_channel) { - GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[AVRedirect] Audio %u Hz, bitsPerSample=%u, nbchannels=%u\n", samplerate, bits_per_sample, nb_channel)); -#ifdef AVR_DUMP_RAW_AVI - AVI_set_audio ( avr->avi_out, nb_channel, samplerate, bits_per_sample, WAVE_FORMAT_PCM, 0 ); -#endif /* AVR_DUMP_RAW_AVI */ - avr->audioChannels = nb_channel; - avr->audioSampleRate = samplerate; - } -} - -static void avr_on_video_frame ( void *udta, u32 time ) -{ - u32 i, j; - GF_Err e; - GF_VideoSurface fb; - GF_AVRedirect *avr = ( GF_AVRedirect * ) udta; - - if (start_if_needed(avr)) - return; - gf_mx_p(avr->frameMutex); - e = gf_sc_get_screen_buffer ( avr->term->compositor, &fb, 0 ); - if ( e ) - { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ( "[AVRedirect] Error grabing frame buffer %s\n", gf_error_to_string ( e ) ) ); - return; - } - /*convert colorspace for input frame*/ - if (fb.pixel_format == GF_PIXEL_ARGB) { - /*ARGB -> RGB24*/ - for (i=0; i<fb.height; i++) { - char *dst = avr->frame + i * fb.width * 3; - char *src = fb.video_buffer + i * fb.pitch_y; - for (j=0; j<fb.width; j++) { - dst[0] = src[2]; - dst[1] = src[1]; - dst[2] = src[0]; - src+=4; - dst += 3; - } - } - } else { - assert(fb.pixel_format == GF_PIXEL_RGB_24); - for (i=0; i<fb.height; i++) - memcpy(avr->frame+i*fb.width*3, fb.video_buffer+i*fb.pitch_y, fb.width*3); - } - avr->frameTime = time; - gf_mx_v(avr->frameMutex); - gf_sc_release_screen_buffer ( avr->term->compositor, &fb ); - GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ( "[AVRedirect] Writing video frame\n" ) ); -} - -static void avr_on_video_reconfig ( void *udta, u32 width, u32 height, u8 bpp ) -{ - - GF_AVRedirect *avr = ( GF_AVRedirect * ) udta; - - if ((avr->srcHeight != height || avr->srcWidth != width)) - { -#ifdef AVR_DUMP_RAW_AVI - char comp[5]; - comp[0] = comp[1] = comp[2] = comp[3] = comp[4] = 0; - AVI_set_video ( avr->avi_out, width, height, 30, comp ); -#endif /* AVR_DUMP_RAW_AVI */ - GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ( "[AVRedirect] Video reconfig width %d height %d\n", width, height ) ); - gf_mx_p(avr->frameMutex); - if ( avr->frame ) gf_free ( avr->frame ); - avr->size = 3*width*height; - avr->frame = (char*)gf_malloc ( sizeof ( char ) *avr->size ); - avr->srcWidth = width; - avr->srcHeight = height; - avr->swsContext = sws_getCachedContext ( avr->swsContext, avr->srcWidth, avr->srcHeight, PIX_FMT_RGB24, avr->srcWidth, avr->srcHeight, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL ); - gf_mx_v(avr->frameMutex); - } -} - - -#ifdef MULTITHREAD_REDIRECT_AV -/** - * This thread sends the TS packets over the network - * \param Parameter The GF_AVRedirect pointer - */ -static Bool ts_thread_run(void *param) -{ - GF_Err e; - GF_AVRedirect * avr = (GF_AVRedirect*) param; - assert( avr ); - gf_mx_p(avr->tsMutex); - while (!avr->frameTimeEncoded && avr->is_running) { - gf_mx_v(avr->tsMutex); - gf_sleep(1); - gf_mx_p(avr->tsMutex); - } - fprintf(stderr, "avr->frameTimeEncoded="LLU"\n", avr->frameTimeEncoded); - - while (avr->is_running) { - e = sendTSMux(avr); - gf_mx_v(avr->tsMutex); - gf_mx_p(avr->tsMutex); - } - gf_mx_v(avr->tsMutex); - return 0; -} -#endif /* MULTITHREAD_REDIRECT_AV */ - -static const char * possibleCodecs = "supported codecs : MPEG-1, MPEG-2, MPEG-4, MSMPEG-4, H263, H263I, H263P, RV10, MJPEG"; - -/*#define DEFAULT_UDP_OUT_ADDRESS "127.0.0.1" -#define DEFAULT_UDP_OUT_PORT 1234 -#define DEFAULT_UDP_OUT_PORT_STR "1234"*/ - -#define AVR_DEFAULT_DESTINATION "udp://224.0.0.1:1234" - -#include <libavutil/pixfmt.h> - - -static void avr_close ( GF_AVRedirect *avr ) -{ - if ( !avr->is_open ) return; - avr->is_open = GF_FALSE; -#ifdef AVR_DUMP_RAW_AVI - GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ( "[AVRedirect] Closing output AVI file\n" ) ); - AVI_close ( avr->avi_out ); - avr->avi_out = NULL; -#endif /* AVR_DUMP_RAW_AVI */ -} - -static Bool avr_on_event ( void *udta, GF_Event *evt, Bool consumed_by_compositor ) -{ - GF_AVRedirect *avr = (GF_AVRedirect*)udta; - switch ( evt->type ) - { - case GF_EVENT_CONNECT: - if ( evt->connect.is_connected ) - { - avr_open ( avr ); - } - else - { - avr_close ( avr ); - } - break; - } - return GF_FALSE; -} - -static const char * AVR_ENABLED_OPTION = "Enabled"; - -//static const char * AVR_UDP_PORT_OPTION = "udp.port"; - -//static const char * AVR_UDP_ADDRESS_OPTION = "udp.address"; - -static const char * AVR_DESTINATION = "destination"; - -static Bool avr_process ( GF_TermExt *termext, u32 action, void *param ) -{ - const char *opt; - GF_AVRedirect *avr = (GF_AVRedirect*)termext->udta; - - switch ( action ) - { - case GF_TERM_EXT_START: - avr->term = ( GF_Terminal * ) param; - opt = gf_modules_get_option ( ( GF_BaseInterface* ) termext, moduleName, AVR_ENABLED_OPTION ); - if ( !opt ) - gf_modules_set_option ( ( GF_BaseInterface* ) termext, moduleName, AVR_ENABLED_OPTION, "no" ); - if ( !opt || strcmp ( opt, "yes" ) ) return GF_FALSE; - opt = gf_modules_get_option ( ( GF_BaseInterface* ) termext, moduleName, AVR_VIDEO_CODEC_OPTION ); - avr->globalLock = gf_global_resource_lock("AVRedirect:output"); - if (!avr->globalLock) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("Failed to lock global resource 'AVRedirect:output', another GPAC instance must be running, disabling AVRedirect\n")); - return GF_FALSE; - } -#ifndef AVIO_FLAG_WRITE - /* must be called before using avcodec lib */ - avcodec_init(); -#endif - - /* register all the codecs */ - avcodec_register_all(); - if ( !opt ) - gf_modules_set_option ( ( GF_BaseInterface* ) termext, moduleName, AVR_VIDEO_CODEC_OPTION, possibleCodecs ); - { - if ( !opt ) - { - avr->videoCodec = avcodec_find_encoder ( CODEC_ID_MPEG2VIDEO ); - } - else if ( !strcmp ( "MPEG-1", opt ) ) - { - avr->videoCodec=avcodec_find_encoder ( CODEC_ID_MPEG1VIDEO ); - } - else if ( !strcmp ( "MPEG-2", opt ) ) - { - avr->videoCodec=avcodec_find_encoder ( CODEC_ID_MPEG2VIDEO ); - } - else if ( !strcmp ( "MPEG-4", opt ) ) - { - avr->videoCodec=avcodec_find_encoder ( CODEC_ID_MPEG4 ); - } - else if ( !strcmp ( "MSMPEG-4", opt ) ) - { - avr->videoCodec=avcodec_find_encoder ( CODEC_ID_MSMPEG4V3 ); - } - else if ( !strcmp ( "H263", opt ) ) - { - avr->videoCodec=avcodec_find_encoder ( CODEC_ID_H263 ); - } - else if ( !strcmp ( "RV10", opt ) ) - { - avr->videoCodec = avcodec_find_encoder ( CODEC_ID_RV10 ); - } - else if ( !strcmp ( "H263P", opt ) ) - { - avr->videoCodec=avcodec_find_encoder ( CODEC_ID_H263P ); - } - else if ( !strcmp ( "H263I", opt ) ) - { - avr->videoCodec=avcodec_find_encoder ( CODEC_ID_H263I ); - } - else if ( !strcmp( "MJPEG", opt)) - { - avr->videoCodec=avcodec_find_encoder( CODEC_ID_MJPEG); - } - else - { - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ( "[AVRedirect] Not a known Video codec : %s, using MPEG-2 video instead, %s\n", opt, possibleCodecs ) ); - avr->videoCodec = avcodec_find_encoder ( CODEC_ID_MPEG2VIDEO ); - } - } -#if REDIRECT_AV_AUDIO_ENABLED - avr->audioCodec = avcodec_find_encoder(CODEC_ID_MP2); -#endif - /* - opt = gf_modules_get_option ( ( GF_BaseInterface* ) termext, moduleName, AVR_UDP_ADDRESS_OPTION); - if (!opt) { - gf_modules_set_option ( ( GF_BaseInterface* ) termext, moduleName, AVR_UDP_ADDRESS_OPTION, DEFAULT_UDP_OUT_ADDRESS); - avr->udp_address = DEFAULT_UDP_OUT_ADDRESS; - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ( "[AVRedirect] %s not set, using %s\n", AVR_UDP_ADDRESS_OPTION, DEFAULT_UDP_OUT_ADDRESS ) ); - } else - avr->udp_address = opt; - opt = gf_modules_get_option ( ( GF_BaseInterface* ) termext, moduleName, AVR_UDP_PORT_OPTION); - if (opt) { - char *endPtr = NULL; - unsigned int x = strtoul(opt, &endPtr, 10); - if (endPtr == opt || x > 65536) { - fprintf(stderr, "Failed to parse : %s\n", opt); - opt = NULL; - } else - avr->udp_port = (u16) x; - } - if (!opt) { - gf_modules_set_option ( ( GF_BaseInterface* ) termext, moduleName, AVR_UDP_PORT_OPTION, DEFAULT_UDP_OUT_PORT_STR); - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ( "[AVRedirect] %s is not set or valid, using %s\n", AVR_UDP_PORT_OPTION, DEFAULT_UDP_OUT_PORT_STR ) ); - avr->udp_port = DEFAULT_UDP_OUT_PORT; - } - */ - opt = gf_modules_get_option ( ( GF_BaseInterface* ) termext, moduleName, AVR_DESTINATION); - if (!opt) { - gf_modules_set_option ( ( GF_BaseInterface* ) termext, moduleName, AVR_DESTINATION, AVR_DEFAULT_DESTINATION); - avr->destination = AVR_DEFAULT_DESTINATION; - GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ( "[AVRedirect] %s not set, using %s\n", AVR_DESTINATION, AVR_DEFAULT_DESTINATION ) ); - } else - avr->destination = opt; - avr->audio_listen.udta = avr; - avr->audio_listen.on_audio_frame = avr_on_audio_frame; - avr->audio_listen.on_audio_reconfig = avr_on_audio_reconfig; - avr->video_listen.udta = avr; - avr->video_listen.on_video_frame = avr_on_video_frame; - avr->video_listen.on_video_reconfig = avr_on_video_reconfig; - - avr->term_listen.udta = avr; - avr->term_listen.on_event = avr_on_event; - gf_term_add_event_filter ( avr->term, &avr->term_listen ); - return GF_TRUE; - - case GF_TERM_EXT_STOP: - gf_term_remove_event_filter ( avr->term, &avr->term_listen ); - avr->term = NULL; - break; - - /*if not threaded, perform our tasks here*/ - case GF_TERM_EXT_PROCESS: - break; - } - return GF_FALSE; -} - - -GF_TermExt *avr_new() -{ - GF_TermExt *dr; - GF_AVRedirect *uir; - GF_SAFEALLOC ( dr, GF_TermExt ); - GF_REGISTER_MODULE_INTERFACE ( dr, GF_TERM_EXT_INTERFACE, "GPAC Output Recorder", "gpac distribution" ); - - GF_SAFEALLOC ( uir, GF_AVRedirect ); - dr->process = avr_process; - dr->udta = uir; - uir->encodingMutex = gf_mx_new("RedirectAV_encodingMutex"); - assert( uir->encodingMutex); - uir->frameMutex = gf_mx_new("RedirectAV_frameMutex"); - uir->encodingThread = gf_th_new("RedirectAV_EncodingThread"); - uir->audioEncodingThread = gf_th_new("RedirectAV_AudioEncodingThread"); - uir->encode = GF_TRUE; - uir->is_open = GF_FALSE; - uir->is_running = GF_FALSE; - return dr; -} - - -void avr_delete ( GF_BaseInterface *ifce ) -{ - GF_TermExt *dr = ( GF_TermExt * ) ifce; - GF_AVRedirect *avr = (GF_AVRedirect*)dr->udta; - avr->is_running = GF_FALSE; - /* Ensure encoding is finished */ - gf_mx_p(avr->frameMutex); - gf_mx_v(avr->frameMutex); - gf_sleep(200); - gf_th_stop(avr->encodingThread); - gf_mx_del(avr->frameMutex); - avr->frameMutex = NULL; - gf_th_del(avr->encodingThread); - avr->encodingThread = NULL; - gf_mx_del(avr->encodingMutex); - avr->encodingMutex = NULL; - if ( avr->ts_implementation ) - { - ts_amux_del(avr->ts_implementation); - avr->ts_implementation = NULL; - } - avr->videoCodec = NULL; - if ( avr->YUVpicture ) - { - av_free ( avr->YUVpicture ); - } - if ( avr->yuv_data ) - av_free ( avr->yuv_data ); - avr->yuv_data = NULL; - avr->YUVpicture = NULL; - if ( avr->RGBpicture ) - { - av_free ( avr->RGBpicture ); - } - avr->RGBpicture = NULL; - if ( avr->swsContext ) - sws_freeContext ( avr->swsContext ); - avr->swsContext = NULL; - if ( avr->videoOutbuf ) - gf_free ( avr->videoOutbuf ); - avr->videoOutbuf = NULL; - if ( avr->pcmAudio ) - gf_ringbuffer_del(avr->pcmAudio); - avr->pcmAudio = NULL; - gf_global_resource_unlock(avr->globalLock); - avr->globalLock = NULL; - if (avr->audioEncodingThread) { - gf_th_stop(avr->audioEncodingThread); - gf_th_del(avr->audioEncodingThread); - } - avr->audioEncodingThread = NULL; - gf_free ( avr ); - gf_free ( dr ); -} - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = - { - GF_TERM_EXT_INTERFACE, - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface ( u32 InterfaceType ) -{ - if ( InterfaceType == GF_TERM_EXT_INTERFACE ) return ( GF_BaseInterface * ) avr_new(); - return NULL; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface ( GF_BaseInterface *ifce ) -{ - switch ( ifce->InterfaceType ) - { - case GF_TERM_EXT_INTERFACE: - avr_delete ( ifce ); - break; - } -} - - -GPAC_MODULE_STATIC_DECLARATION( redirect_av ) diff --git a/modules/redirect_av/ts_muxer.h b/modules/redirect_av/ts_muxer.h deleted file mode 100644 index 566f317..0000000 --- a/modules/redirect_av/ts_muxer.h +++ /dev/null @@ -1,161 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean le Feuvre - * Copyright (c) 2010-20XX Telecom ParisTech - * All rights reserved - * - * This file is part of GPAC / AVI Recorder demo module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ -#ifndef _AVR_TS_MUXER_H_ -#define _AVR_TS_MUXER_H_ - -#include <gpac/modules/term_ext.h> -#include <gpac/internal/terminal_dev.h> -#include <gpac/internal/compositor_dev.h> -#include <gpac/network.h> -#include <gpac/tools.h> -#include <libavcodec/avcodec.h> - -#if ((LIBAVCODEC_VERSION_MAJOR == 52) && (LIBAVCODEC_VERSION_MINOR <= 20)) || (LIBAVCODEC_VERSION_MAJOR < 52) -#undef USE_AVCODEC2 -#else -#define USE_AVCODEC2 1 -#endif - -#define TS_MUX_MODE_PUT - -#undef AVR_DUMP_RAW_AVI - -#ifdef AVR_DUMP_RAW_AVI -/*sample raw AVI writing*/ -#include <gpac/internal/avilib.h> -#endif /* AVR_DUMP_RAW_AVI */ - -typedef struct avr_ts_muxer GF_AbstractTSMuxer; - -#if LIBAVUTIL_VERSION_MAJOR<51 -#define AVMEDIA_TYPE_AUDIO CODEC_TYPE_AUDIO -#define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO -#define AV_PKT_FLAG_KEY PKT_FLAG_KEY -#endif - - - -/*TODO - we need to cleanup the ffmpeg code to align with only latest version and remove old compatibility code*/ -#ifndef CODEC_ID_MJPEG - -#define CODEC_ID_SVQ3 AV_CODEC_ID_SVQ3 -#define CODEC_ID_MPEG4 AV_CODEC_ID_MPEG4 -#define CODEC_ID_H264 AV_CODEC_ID_H264 -#define CODEC_ID_MPEG2VIDEO AV_CODEC_ID_MPEG2VIDEO -#define CODEC_ID_MJPEG AV_CODEC_ID_MJPEG -#define CODEC_ID_MP2 AV_CODEC_ID_MP2 -#define CODEC_ID_AC3 AV_CODEC_ID_AC3 -#define CODEC_ID_EAC3 AV_CODEC_ID_EAC3 -#define CODEC_ID_DVD_SUBTITLE AV_CODEC_ID_DVD_SUBTITLE -#define CODEC_ID_RAWVIDEO AV_CODEC_ID_RAWVIDEO -#define CODEC_ID_MJPEGB AV_CODEC_ID_MJPEGB -#define CODEC_ID_LJPEG AV_CODEC_ID_LJPEG -#define CODEC_ID_GIF AV_CODEC_ID_GIF -#define CODEC_ID_H263 AV_CODEC_ID_H263 -#define CODEC_ID_MP3 AV_CODEC_ID_MP3 -#define CODEC_ID_AAC AV_CODEC_ID_AAC -#define CODEC_ID_MPEG1VIDEO AV_CODEC_ID_MPEG1VIDEO -#define CODEC_ID_MSMPEG4V3 AV_CODEC_ID_MSMPEG4V3 -#define CODEC_ID_RV10 AV_CODEC_ID_RV10 -#define CODEC_ID_H263P AV_CODEC_ID_H263P -#define CODEC_ID_H263I AV_CODEC_ID_H263I -#endif - -#ifndef PIX_FMT_YUV420P -#define PIX_FMT_YUV420P AV_PIX_FMT_YUV420P -#define PIX_FMT_YUV420P10LE AV_PIX_FMT_YUV420P10LE -#define PIX_FMT_BGR24 AV_PIX_FMT_BGR24 -#define PIX_FMT_RGB24 AV_PIX_FMT_RGB24 -#define PIX_FMT_YUVJ420P AV_PIX_FMT_YUVJ420P -#endif - -#if (LIBAVCODEC_VERSION_MAJOR>56) -#ifndef FF_API_AVFRAME_LAVC -#define FF_API_AVFRAME_LAVC -#endif - -#endif - -#include <gpac/ringbuffer.h> - -typedef struct -{ - GF_Terminal *term; - - Bool is_open; - GF_AudioListener audio_listen; - GF_VideoListener video_listen; - GF_TermEventFilter term_listen; -#ifdef AVR_DUMP_RAW_AVI - avi_t *avi_out; -#endif - char *frame; - u32 size; - GF_AbstractTSMuxer * ts_implementation; - Bool encode; - AVCodec *audioCodec; - AVCodec *videoCodec; - AVFrame *YUVpicture, *RGBpicture; - struct SwsContext * swsContext; - uint8_t * yuv_data; - uint8_t * videoOutbuf; - u32 videoOutbufSize; - GF_Ringbuffer * pcmAudio; - u32 audioCurrentTime; - GF_Thread * encodingThread; - GF_Thread * audioEncodingThread; - GF_Mutex * frameMutex; - GF_Mutex * encodingMutex; - volatile Bool is_running; - u64 frameTime; - u64 frameTimeEncoded; - /** - * Audio parameters for encoding - */ - u32 audioSampleRate; - u16 audioChannels; - /** - * Video parameters for encoding - */ - u32 srcWidth; - u32 srcHeight; - const char * destination; - GF_GlobalLock * globalLock; - Bool started; -} GF_AVRedirect; - -GF_AbstractTSMuxer * ts_amux_new(GF_AVRedirect * avr, u32 videoBitrateInBitsPerSec, u32 width, u32 height, u32 audioBitRateInBitsPerSec); - -void ts_amux_del(GF_AbstractTSMuxer * muxerToDelete); - -Bool ts_encode_audio_frame(GF_AbstractTSMuxer * ts, uint8_t * data, int encoded, u64 pts); - -Bool ts_encode_video_frame(GF_AbstractTSMuxer* ts, uint8_t* data, int encoded); - -AVCodecContext * ts_get_video_codec_context(GF_AbstractTSMuxer * ts); - -AVCodecContext * ts_get_audio_codec_context(GF_AbstractTSMuxer * ts); - -#endif /* _AVR_TS_MUXER_H_ */ diff --git a/modules/rtp_in/Makefile b/modules/rtp_in/Makefile deleted file mode 100644 index f811b50..0000000 --- a/modules/rtp_in/Makefile +++ /dev/null @@ -1,51 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/rtp_in - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS= rtp_in.o rtp_session.o rtp_signaling.o rtp_stream.o sdp_fetch.o sdp_load.o - - -SRCS := $(OBJS:.o=.c) - -LIB=gm_rtp_in$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols rtp_in.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) -L../../bin/gcc -lgpac $(EXTRALIBS) $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_rtp_in-static$(DYN_LIB_SUFFIX) $(OBJS) -L../../bin/gcc -lgpac_static $(EXTRALIBS) $(LDFLAGS) -endif - - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/rtp_in/rtp_in.c b/modules/rtp_in/rtp_in.c deleted file mode 100644 index 0b01d83..0000000 --- a/modules/rtp_in/rtp_in.c +++ /dev/null @@ -1,1014 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / RTP input module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "rtp_in.h" - -#ifndef GPAC_DISABLE_STREAMING - -#define RTP_DEFAULT_TIMEOUT 10000 -#define RTSP_DEFAULT_TIMEOUT 2000 - -static void RT_LoadPrefs(GF_InputService *plug, RTPClient *rtp) -{ - const char *sOpt; - - sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "Streaming", "DefaultPort"); - if (sOpt) { - rtp->default_port = atoi(sOpt); - } else { - rtp->default_port = 554; - } - if ((rtp->default_port == 80) || (rtp->default_port == 8080)) - gf_modules_set_option((GF_BaseInterface *)plug, "Streaming", "RTPoverRTSP", "yes"); - - sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "Streaming", "RTPoverRTSP"); - if (sOpt && !stricmp(sOpt, "yes")) { - rtp->transport_mode = 1; - } else if (sOpt && !stricmp(sOpt, "OnlyCritical")) { - rtp->transport_mode = 2; - } else { - rtp->transport_mode = 0; - } - - /* - get heneral network config for UDP - */ - /*if UDP not available don't try it*/ - sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "Network", "UDPNotAvailable"); - if (sOpt && !stricmp(sOpt, "yes")) { - if (!rtp->transport_mode) rtp->transport_mode = 1; - /*turn it off*/ - gf_modules_set_option((GF_BaseInterface *)plug, "Network", "UDPNotAvailable", "no"); - } - - if (!rtp->transport_mode) { - sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "Network", "UDPTimeout"); - if (sOpt ) { - rtp->udp_time_out = atoi(sOpt); - } else { - rtp->udp_time_out = RTP_DEFAULT_TIMEOUT; - } - } - - sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "Streaming", "RTSPTimeout"); - if (sOpt ) { - rtp->time_out = atoi(sOpt); - } else { - rtp->time_out = RTSP_DEFAULT_TIMEOUT; - } - - /*packet drop emulation*/ - sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "Streaming", "FirstPacketDrop"); - if (sOpt) { - rtp->first_packet_drop = atoi(sOpt); - } else { - rtp->first_packet_drop = 0; - } - sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "Streaming", "PacketDropFrequency"); - if (sOpt) { - rtp->frequency_drop = atoi(sOpt); - } else { - rtp->frequency_drop = 0; - } - -// rtp->handle_announce = 0; -} - -static void RP_cleanup(RTPClient *rtp) -{ - RTSPSession *sess; - - while (gf_list_count(rtp->channels)) { - RTPStream *ch = (RTPStream *)gf_list_get(rtp->channels, 0); - gf_list_rem(rtp->channels, 0); - RP_DeleteStream(ch); - } - - while ( (sess = (RTSPSession *)gf_list_last(rtp->sessions)) ) { - gf_list_rem_last(rtp->sessions); - RP_DelSession(sess); - } - - if (rtp->session_desc) gf_odf_desc_del(rtp->session_desc); - rtp->session_desc = NULL; - - if (rtp->sdp_temp) { - gf_free(rtp->sdp_temp->remote_url); - gf_free(rtp->sdp_temp); - } - rtp->sdp_temp = NULL; -} - -u32 RP_Thread(void *param) -{ - u32 i; - GF_NetworkCommand com; - RTSPSession *sess; - RTPStream *ch; - RTPClient *rtp = (RTPClient *)param; - - rtp->th_state = 1; - com.command_type = GF_NET_CHAN_BUFFER_QUERY; - while (rtp->th_state) { - gf_mx_p(rtp->mx); - - /*fecth data on udp*/ - i=0; - while ((ch = (RTPStream *)gf_list_enum(rtp->channels, &i))) { - if ((ch->flags & RTP_EOS) || (ch->status!=RTP_Running) ) continue; - /*for interleaved channels don't read too fast, query the buffer occupancy*/ - if (ch->flags & RTP_INTERLEAVED) { - com.base.on_channel = ch->channel; - gf_service_command(rtp->service, &com, GF_OK); - /*if no buffering, use a default value (3 sec of data should do it)*/ - if (!com.buffer.max) com.buffer.max = 3000; - if (com.buffer.occupancy <= com.buffer.max) ch->rtsp->flags |= RTSP_TCP_FLUSH; - } else { - RP_ReadStream(ch); - } - } - - /*and process commands / flush TCP*/ - i=0; - while ((sess = (RTSPSession *)gf_list_enum(rtp->sessions, &i))) { - RP_ProcessCommands(sess); - - if (sess->connect_error) { - gf_service_connect_ack(sess->owner->service, NULL, sess->connect_error); - sess->connect_error = GF_OK; - } - - } - - gf_mx_v(rtp->mx); - - gf_sleep(1); - } - - if (rtp->dnload) gf_service_download_del(rtp->dnload); - rtp->dnload = NULL; - - rtp->th_state = 2; - return 0; -} - -static const char * sdp_mime = "application/sdp"; - -static const char * sdp_exts = "sdp"; - -static const char * sdp_desc = "OnDemand Media/Multicast Session"; - -static u32 RP_RegisterMimeTypes(const GF_InputService *plug) { - if (!plug) - return 0; - gf_service_register_mime(plug, sdp_mime, sdp_exts, sdp_desc); - return 1; -} - -static Bool RP_CanHandleURL(GF_InputService *plug, const char *url) -{ - char *sExt; - if (!plug || !url) - return GF_FALSE; - sExt = strrchr(url, '.'); - - if (sExt && gf_service_check_mime_register(plug, sdp_mime, sdp_exts, sdp_desc, sExt)) return GF_TRUE; - - /*local */ - if (strstr(url, "data:application/sdp")) return GF_TRUE; - /*embedded data*/ - if (strstr(url, "data:application/mpeg4-od-au;base64") || - strstr(url, "data:application/mpeg4-bifs-au;base64") || - strstr(url, "data:application/mpeg4-es-au;base64")) return GF_TRUE; - - /*we need rtsp/tcp , rtsp/udp or direct RTP sender (no control)*/ - if (!strnicmp(url, "rtsp://", 7) || !strnicmp(url, "rtspu://", 8) || !strnicmp(url, "rtp://", 6) || !strnicmp(url, "satip://", 6)) - return GF_TRUE; - /*we don't check extensions*/ - return GF_FALSE; -} - -//simplified version of RTSP_UnpackURL for SAT>IP -static void Satip_GetServerIP(const char *sURL, char *Server) -{ - char schema[10], *test, text[1024], *retest; - u32 i, len; - Bool is_ipv6; - - strcpy(Server, ""); - - //extract the schema - i = 0; - while (i <= strlen(sURL)) { - if (sURL[i] == ':') - goto found; - schema[i] = sURL[i]; - i += 1; - } - return; - -found: - schema[i] = 0; - if (stricmp(schema, "satip")) { - GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTP] Wrong SATIP schema %s - not setting up\n", schema)); - return; - } - test = strstr(sURL, "://"); - test += 3; - //check for service - retest = strstr(test, "/"); - - //check for port - retest = strrchr(test, ':'); - /*IPV6 address*/ - if (retest && strchr(retest, ']')) retest = NULL; - - if (retest && strstr(retest, "/")) { - retest += 1; - i = 0; - while (i<strlen(retest)) { - if (retest[i] == '/') break; - text[i] = retest[i]; - i += 1; - } - text[i] = 0; - } - //get the server name - is_ipv6 = GF_FALSE; - len = (u32)strlen(test); - i = 0; - while (i<len) { - if (test[i] == '[') is_ipv6 = GF_TRUE; - else if (test[i] == ']') is_ipv6 = GF_FALSE; - if ((test[i] == '/') || (!is_ipv6 && (test[i] == ':'))) break; - text[i] = test[i]; - i += 1; - } - text[i] = 0; - strcpy(Server, text); -} - -GF_Err RP_ConnectServiceEx(GF_InputService *plug, GF_ClientService *serv, const char *url, Bool skip_migration) -{ - char *session_cache; - RTSPSession *sess; - RTPClient *priv = (RTPClient *)plug->priv; - - /*store user address*/ - priv->service = serv; - - if (priv->dnload) gf_service_download_del(priv->dnload); - priv->dnload = NULL; - - GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTP] Opening service %s\n", url)); - - /*load preferences*/ - RT_LoadPrefs(plug, priv); - - /*start thread*/ - gf_th_run(priv->th, RP_Thread, priv); - - if (!skip_migration) { - session_cache = (char *) gf_modules_get_option((GF_BaseInterface *) plug, "Streaming", "SessionMigrationFile"); - if (session_cache && session_cache[0]) { - FILE *f = gf_fopen(session_cache, "rb"); - if (f) { - gf_fclose(f); - GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTP] Restarting RTSP session from %s\n", session_cache)); - RP_FetchSDP(priv, (char *) session_cache, NULL, (char *) url); - return GF_OK; - } - if (!strncmp(session_cache, "http://", 7)) { - GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTP] Restarting RTSP session from %s\n", session_cache)); - RP_FetchSDP(priv, (char *) session_cache, NULL, (char *) url); - return GF_OK; - } - } - } - - - /*local or remote SDP*/ - if (strstr(url, "data:application/sdp") || (strnicmp(url, "rtsp", 4) && strstr(url, ".sdp")) ) { - RP_FetchSDP(priv, (char *) url, NULL, NULL); - return GF_OK; - } - - /*rtsp and rtsp over udp*/ - if (!strnicmp(url, "rtsp://", 7) || !strnicmp(url, "rtspu://", 8) || !strnicmp(url, "satip://", 8)) { - char *the_url = gf_strdup(url); - char *the_ext = strrchr(the_url, '#'); - if (the_ext) { - if (!stricmp(the_ext, "#audio")) priv->media_type = GF_MEDIA_OBJECT_AUDIO; - else if (!stricmp(the_ext, "#video")) priv->media_type = GF_MEDIA_OBJECT_VIDEO; - the_ext[0] = 0; - } - sess = RP_NewSession(priv, (char *) the_url); - if (!strnicmp(url, "satip://", 8)) { - sess->satip = GF_TRUE; - sess->satip_server = gf_malloc(GF_MAX_PATH); - Satip_GetServerIP(url, sess->satip_server); - } - gf_free(the_url); - if (!sess) { - gf_service_connect_ack(serv, NULL, GF_NOT_SUPPORTED); - } else { - RP_Describe(sess, 0, NULL); - } - return GF_OK; - } - - /*direct RTP (no control) or embedded data - this means the service is attached to a single channel (no IOD) - reply right away*/ - gf_service_connect_ack(serv, NULL, GF_OK); - RP_SetupObjects(priv); - return GF_OK; -} - -GF_Err RP_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url) -{ - return RP_ConnectServiceEx(plug, serv, url, GF_FALSE); -} - -static void RP_FlushCommands(RTPClient *rtp) -{ - u32 i, nb_com; - RTSPSession *sess; - /*process teardown on all sessions*/ - while (1) { - nb_com = 0; - i=0; - while ((sess = (RTSPSession *)gf_list_enum(rtp->sessions, &i))) { - if (!sess->connect_error) - nb_com += gf_list_count(sess->rtsp_commands); - } - if (!nb_com) break; - gf_sleep(10); - } -} - -static GF_Err RP_CloseService(GF_InputService *plug) -{ - u32 i; - const char *opt; - RTSPSession *sess; - RTPClient *rtp = (RTPClient *)plug->priv; - GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Closing service\n")); - - RP_FlushCommands(rtp); - - if (rtp->session_migration) { - opt = gf_modules_get_option((GF_BaseInterface *) plug, "Streaming", "SessionMigrationPause"); - if (opt && !strcmp(opt, "yes")) { - GF_NetworkCommand com; - com.command_type = GF_NET_CHAN_PAUSE; - com.base.on_channel = NULL; - /*send pause on all sessions*/ - i=0; - while ((sess = (RTSPSession *)gf_list_enum(rtp->sessions, &i))) { - RP_UserCommand(sess, NULL, &com); - } - } - RP_SaveSessionState(rtp); - } else { - /*remove session state file*/ - if (rtp->session_state_data) { - gf_free(rtp->session_state_data); - rtp->session_state_data = NULL; - } - - /*send teardown on all sessions*/ - i=0; - while ((sess = (RTSPSession *)gf_list_enum(rtp->sessions, &i))) { - RP_Teardown(sess, NULL); - } - } - RP_FlushCommands(rtp); - - /*shutdown thread*/ - if (rtp->th_state==1) rtp->th_state = 0; - - /*confirm close*/ - gf_service_disconnect_ack(rtp->service, NULL, GF_OK); - return GF_OK; -} - -static GF_Descriptor *RP_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) -{ - GF_Descriptor *desc; - RTSPSession *sess = NULL; - RTPClient *priv = (RTPClient *)plug->priv; - - GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Fetching service descriptor\n")); - - sess = gf_list_get(priv->sessions, 0); - if (sess && sess->satip) { - RTPStream *ch = gf_list_get(priv->channels, 0); - if (!ch) return NULL; - return ch->satip_m2ts_ifce->GetServiceDescriptor(ch->satip_m2ts_ifce, expect_type, sub_url); - } - - if ((expect_type!=GF_MEDIA_OBJECT_UNDEF) && (expect_type!=GF_MEDIA_OBJECT_SCENE) && (expect_type!=GF_MEDIA_OBJECT_UPDATES)) { - /*ignore the SDP IOD and regenerate one*/ - if (priv->session_desc) gf_odf_desc_del(priv->session_desc); - priv->session_desc = NULL; - priv->media_type = expect_type; - return RP_EmulateIOD(priv, sub_url); - } - - desc = priv->session_desc; - priv->session_desc = NULL; - return desc; -} - -static GF_Err RP_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) -{ - u32 ESID, i; - RTPStream *ch, *next_ch; - RTSPSession *sess = NULL; - char *es_url; - RTPClient *priv = (RTPClient *)plug->priv; - if (upstream) return GF_NOT_SUPPORTED; - - GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Connecting channel @%08x - %s\n", channel, url)); - - sess = gf_list_get(priv->sessions, 0); - if (sess && sess->satip) { - RTPStream *ch = gf_list_get(priv->channels, 0); - if (!ch) return GF_SERVICE_ERROR; - return ch->satip_m2ts_ifce->ConnectChannel(ch->satip_m2ts_ifce, channel, url, upstream); - } - - ch = RP_FindChannel(priv, channel, 0, (char *) url, GF_FALSE); - if (ch && (ch->status != RTP_Disconnected) ) return GF_SERVICE_ERROR; - - es_url = NULL; - sess = NULL; - if (strstr(url, "ES_ID=")) { - sscanf(url, "ES_ID=%ud", &ESID); - /*first case: simple URL (same namespace)*/ - ch = RP_FindChannel(priv, NULL, ESID, NULL, GF_FALSE); - /*this should not happen, the sdp must describe all streams in the service*/ - if (!ch) return GF_STREAM_NOT_FOUND; - - /*search for next stream*/ - ch->next_stream = 0; - for (i = 0; i < gf_list_count(priv->channels); i++) { - next_ch = (RTPStream *)gf_list_get(priv->channels, i); - if (next_ch->prev_stream == ch->mid) - { - ch->next_stream = next_ch->mid; - break; - } - } - - /*assign app channel*/ - ch->channel = channel; - sess = ch->rtsp; - } - /*rtsp url - create a session if needed*/ - else if (!strnicmp(url, "rtsp://", 7) || !strnicmp(url, "rtspu://", 8) || !strnicmp(url, "satip://", 8)) { - sess = RP_CheckSession(priv, (char *) url); - if (!sess) sess = RP_NewSession(priv, (char *) url); - es_url = (char *) url; - } - /*data: url*/ - else if (strstr(url, "data:application/mpeg4-od-au;base64") - || strstr(url, "data:application/mpeg4-bifs-au;base64") - || strstr(url, "data:application/mpeg4-es-au;base64") - ) { - - GF_SAFEALLOC(ch, RTPStream); - if (!ch) return GF_OUT_OF_MEM; - - ch->control = gf_strdup(url); - ch->owner = priv; - ch->channel = channel; - ch->status = RTP_Connected; - /*register*/ - gf_list_add(priv->channels, ch); - RP_ConfirmChannelConnect(ch, GF_OK); - - return GF_OK; - } - /*session migration resume - don't send data to the server*/ - if (ch && (ch->status==RTP_SessionResume)) { - ch->flags |= RTP_CONNECTED; - RP_InitStream(ch, GF_FALSE); - RP_ConfirmChannelConnect(ch, GF_OK); - return GF_OK; - } - /*send a DESCRIBE (not a setup) on the channel. If the channel is already created then the - describe is skipped and a SETUP is sent directly, otherwise the channel is first described then setup*/ - if (sess) RP_Describe(sess, es_url, channel); - /*otherwise confirm channel connection*/ - else RP_ConfirmChannelConnect(ch, GF_OK); - - return GF_OK; -} - - -static GF_Err RP_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) -{ - RTPStream *ch; - RTPClient *priv = (RTPClient *)plug->priv; - RTSPSession *sess = NULL; - - GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Disconnecting channel @%08x\n", channel)); - - sess = gf_list_get(priv->sessions, 0); - if (sess && sess->satip) { - RTPStream *ch = gf_list_get(priv->channels, 0); - if (!ch) return GF_SERVICE_ERROR; - return ch->satip_m2ts_ifce->DisconnectChannel(ch->satip_m2ts_ifce, channel); - } - - ch = RP_FindChannel(priv, channel, 0, NULL, GF_FALSE); - if (!ch) return GF_STREAM_NOT_FOUND; - gf_mx_p(priv->mx); - /*disconnect stream BUT DO NOT DELETE IT since we don't store SDP*/ - ch->flags &= ~RTP_CONNECTED; - ch->channel = NULL; - gf_mx_v(priv->mx); - gf_service_disconnect_ack(priv->service, channel, GF_OK); - return GF_OK; -} - -static void gf_rtp_switch_quality(RTPClient *rtp, Bool switch_up) -{ - u32 i,count; - RTPStream *ch, *cur_ch; - GF_NetworkCommand com; - - count = gf_list_count(rtp->channels); - /*find the current stream*/ - ch = cur_ch = NULL; - for (i = 0; i < count; i++) { - cur_ch = (RTPStream *) gf_list_get(rtp->channels, i); - if (cur_ch->mid != rtp->cur_mid) { - cur_ch=NULL; - continue; - } - break; - } - if (!cur_ch) return; - - if (switch_up) - { - /*this is the highest stream*/ - if (!cur_ch->next_stream) - { - cur_ch->status = RTP_Running; - return; - } - else - { - for (i = 0; i < count; i++) { - ch = (RTPStream *) gf_list_get(rtp->channels, i); - if (ch->mid == cur_ch->next_stream) - { - /*resume streaming next channel*/ - gf_mx_p(rtp->mx); - RP_InitStream(ch, GF_FALSE); - gf_mx_v(rtp->mx); - ch->status = RTP_Running; - rtp->cur_mid = ch->mid; - break; - } - - } - } - } - else - { - /*this is the lowest stream i.e base layer*/ - if (!cur_ch->prev_stream) - { - cur_ch->status = RTP_Running; - return; - } - else - { - for (i = 0; i < count; i++) { - ch = (RTPStream *) gf_list_get(rtp->channels, i); - if (ch->mid == cur_ch->prev_stream) - { - /*stop streaming current channel*/ - gf_rtp_stop(cur_ch->rtp_ch); - cur_ch->status = RTP_Connected; - com.command_type = GF_NET_CHAN_RESET; - com.base.on_channel = cur_ch; - gf_service_command(rtp->service, &com, GF_OK); - rtp->cur_mid = ch->mid; - break; - } - } - } - } - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("Switch from ES%d to ES %d\n", cur_ch->mid, ch->mid)); - return; -} - -static GF_Err RP_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) -{ - RTPStream *ch; - RTPClient *priv = (RTPClient *)plug->priv; - RTSPSession *sess = NULL; - - sess = gf_list_get(priv->sessions, 0); - if (sess && sess->satip) { - RTPStream *ch = gf_list_get(priv->channels, 0); - if (!ch) return GF_SERVICE_ERROR; - return ch->satip_m2ts_ifce->ServiceCommand(ch->satip_m2ts_ifce, com); - } - - if (com->command_type==GF_NET_SERVICE_HAS_AUDIO) { - u32 i; - for (i=0; i<gf_list_count(priv->channels); i++) { - ch = (RTPStream*)gf_list_get(priv->channels, i); - if (ch->depacketizer->sl_map.StreamType==GF_STREAM_AUDIO) - return GF_OK; - } - return GF_NOT_SUPPORTED; - } - if (com->command_type==GF_NET_SERVICE_MIGRATION_INFO) { - RP_SaveSessionState(priv); - priv->session_migration = GF_TRUE; - if (priv->session_state_data) { - com->migrate.data = priv->session_state_data; - com->migrate.data_len = (u32) strlen(priv->session_state_data); - return GF_OK; - } - return GF_NOT_SUPPORTED; - } - - if (com->command_type == GF_NET_SERVICE_QUALITY_SWITCH) - { - gf_rtp_switch_quality(priv, com->switch_quality.up); - return GF_OK; - } - - /*ignore commands other than channels one*/ - if (!com->base.on_channel) { - if (com->command_type==GF_NET_IS_CACHABLE) return GF_OK; - return GF_NOT_SUPPORTED; - } - - ch = RP_FindChannel(priv, com->base.on_channel, 0, NULL, GF_FALSE); - if (!ch) return GF_STREAM_NOT_FOUND; - - switch (com->command_type) { - case GF_NET_CHAN_SET_PULL: - if (ch->rtp_ch || ch->rtsp || !ch->control) return GF_NOT_SUPPORTED; - /*embedded channels work in pull mode*/ - if (strstr(ch->control, "data:application/")) return GF_OK; - return GF_NOT_SUPPORTED; - case GF_NET_CHAN_INTERACTIVE: - /*looks like pure RTP / multicast etc, not interactive*/ - if (!ch->control) return GF_NOT_SUPPORTED; - /*emulated broadcast mode*/ - else if (ch->flags & RTP_FORCE_BROADCAST) return GF_NOT_SUPPORTED; - /*regular rtsp mode*/ - else if (ch->flags & RTP_HAS_RANGE) return GF_OK; - /*embedded data*/ - else if (strstr(ch->control, "application")) return GF_OK; - return GF_NOT_SUPPORTED; - case GF_NET_CHAN_BUFFER: - if (!(ch->rtp_ch || ch->rtsp || !ch->control)) { - com->buffer.max = com->buffer.min = 0; - } - //otherwise use default player config - return GF_OK; - case GF_NET_CHAN_DURATION: - com->duration.duration = (ch->flags & RTP_HAS_RANGE) ? (ch->range_end - ch->range_start) : 0; - return GF_OK; - /*RTP channel config is done upon connection, once the complete SL mapping is known - however we must store some info not carried in SDP*/ - case GF_NET_CHAN_CONFIG: - if (com->cfg.frame_duration) ch->depacketizer->sl_hdr.au_duration = com->cfg.frame_duration; - ch->ts_res = com->cfg.sl_config.timestampResolution; - return GF_OK; - - case GF_NET_CHAN_PLAY: - GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Processing play on channel @%08x - %s\n", ch, ch->rtsp ? "RTSP control" : "No control (RTP)" )); - /*is this RTSP or direct RTP?*/ - ch->flags &= ~RTP_EOS; - if (ch->rtsp) { - if (ch->status==RTP_SessionResume) { - const char *opt = gf_modules_get_option((GF_BaseInterface *) plug, "Streaming", "SessionMigrationPause"); - if (opt && !strcmp(opt, "yes")) { - ch->status = RTP_Connected; - com->play.start_range = ch->current_start; - } else { - ch->status = RTP_Running; - return GF_OK; - } - } - RP_UserCommand(ch->rtsp, ch, com); - } else { - ch->status = RTP_Running; - if (!ch->next_stream) - priv->cur_mid = ch->mid; - - if (ch->rtp_ch) { - /*technically we shouldn't attempt to synchronize streams based on RTP, we should use RTCP/ However it - may happen that the RTCP traffic is absent ...*/ - //ch->check_rtp_time = RTP_SET_TIME_RTP; - ch->rtcp_init = GF_FALSE; - gf_mx_p(priv->mx); - RP_InitStream(ch, (ch->flags & RTP_CONNECTED) ? GF_TRUE : GF_FALSE); - gf_mx_v(priv->mx); - gf_rtp_set_info_rtp(ch->rtp_ch, 0, 0, 0); - } else { - /*direct channel, store current start*/ - ch->current_start = com->play.start_range; - ch->flags |= GF_RTP_NEW_AU; - gf_rtp_depacketizer_reset(ch->depacketizer, GF_FALSE); - } - } - return GF_OK; - case GF_NET_CHAN_STOP: - /*is this RTSP or direct RTP?*/ - if (ch->rtsp) { - if (! ch->owner->session_migration) { - RP_UserCommand(ch->rtsp, ch, com); - } - } else { - ch->status = RTP_Connected; - ch->owner->last_ntp = 0; - } - ch->rtcp_init = GF_FALSE; - return GF_OK; - case GF_NET_CHAN_SET_SPEED: - case GF_NET_CHAN_PAUSE: - case GF_NET_CHAN_RESUME: - assert(ch->rtsp); - RP_UserCommand(ch->rtsp, ch, com); - return GF_OK; - - case GF_NET_CHAN_GET_DSI: - if (ch->depacketizer && ch->depacketizer->sl_map.configSize) { - com->get_dsi.dsi_len = ch->depacketizer->sl_map.configSize; - com->get_dsi.dsi = (char*)gf_malloc(sizeof(char)*com->get_dsi.dsi_len); - memcpy(com->get_dsi.dsi, ch->depacketizer->sl_map.config, sizeof(char)*com->get_dsi.dsi_len); - } else { - com->get_dsi.dsi = NULL; - com->get_dsi.dsi_len = 0; - } - return GF_OK; - - - case GF_NET_GET_STATS: - memset(&com->net_stats, 0, sizeof(GF_NetComStats)); - if (ch->rtp_ch) { - u32 time; - Float bps; - com->net_stats.pck_loss_percentage = gf_rtp_get_loss(ch->rtp_ch); - if (ch->flags & RTP_INTERLEAVED) { - com->net_stats.multiplex_port = gf_rtsp_get_session_port(ch->rtsp->session); - com->net_stats.port = gf_rtp_get_low_interleave_id(ch->rtp_ch); - com->net_stats.ctrl_port = gf_rtp_get_hight_interleave_id(ch->rtp_ch); - } else { - com->net_stats.multiplex_port = 0; - gf_rtp_get_ports(ch->rtp_ch, &com->net_stats.port, &com->net_stats.ctrl_port); - } - if (ch->stat_stop_time) { - time = ch->stat_stop_time - ch->stat_start_time; - } else { - time = gf_sys_clock() - ch->stat_start_time; - } - bps = 8.0f * ch->rtp_bytes; - bps *= 1000; - bps /= time; - com->net_stats.bw_down = (u32) bps; - bps = 8.0f * ch->rtcp_bytes; - bps *= 1000; - bps /= time; - com->net_stats.ctrl_bw_down = (u32) bps; - bps = 8.0f * gf_rtp_get_tcp_bytes_sent(ch->rtp_ch); - bps *= 1000; - bps /= time; - com->net_stats.ctrl_bw_up = (u32) bps; - } - return GF_OK; - default: - break; - } - return GF_NOT_SUPPORTED; -} - -static GF_Err RP_ChannelGetSLP(GF_InputService *plug, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, GF_SLHeader *out_sl_hdr, Bool *sl_compressed, GF_Err *out_reception_status, Bool *is_new_data) -{ - char *data; - RTPStream *ch; - RTPClient *priv = (RTPClient *)plug->priv; - - ch = RP_FindChannel(priv, channel, 0, NULL, GF_FALSE); - if (!ch) return GF_STREAM_NOT_FOUND; - if (ch->rtp_ch || ch->rtsp || !ch->control) return GF_SERVICE_ERROR; - if (ch->status != RTP_Running) return GF_SERVICE_ERROR; - data = strstr(ch->control, ";base64"); - if (!data) return GF_SERVICE_ERROR; - - if (ch->current_start>=0) { - *sl_compressed = GF_FALSE; - memset(out_sl_hdr, 0, sizeof(GF_SLHeader)); - out_sl_hdr->accessUnitEndFlag = 1; - out_sl_hdr->accessUnitStartFlag = 1; - out_sl_hdr->compositionTimeStamp = (u64) (ch->current_start * ch->ts_res); - out_sl_hdr->compositionTimeStampFlag = 1; - out_sl_hdr->randomAccessPointFlag = 1; - *out_reception_status = GF_OK; - *is_new_data = (ch->flags & GF_RTP_NEW_AU) ? GF_TRUE : GF_FALSE; - - /*decode data*/ - data = strstr(data, ","); - data += 1; - *out_data_size = gf_base64_decode(data, (u32) strlen(data), ch->buffer, RTP_BUFFER_SIZE); - /*FIXME - currently only support for empty SL header*/ - *out_data_ptr = ch->buffer; - ch->flags &= ~GF_RTP_NEW_AU; - } else { - *out_data_ptr = NULL; - *out_data_size = 0; - *out_reception_status = GF_EOS; - ch->flags |= RTP_EOS; - } - return GF_OK; -} - -static GF_Err RP_ChannelReleaseSLP(GF_InputService *plug, LPNETCHANNEL channel) -{ - RTPStream *ch; - RTPClient *priv = (RTPClient *)plug->priv; - - ch = RP_FindChannel(priv, channel, 0, NULL, GF_FALSE); - if (!ch) return GF_STREAM_NOT_FOUND; - if (ch->rtp_ch || ch->rtsp || !ch->control) return GF_SERVICE_ERROR; - if (ch->status != RTP_Running) return GF_SERVICE_ERROR; - - /*this will trigger EOS at next fetch*/ - ch->current_start = -1.0; - return GF_OK; -} - -static Bool RP_CanHandleURLInService(GF_InputService *plug, const char *url) -{ - RTSPSession *sess; - RTPClient *priv = (RTPClient *)plug->priv; - - if (strstr(url, "data:application/mpeg4-od-au;base64") - || strstr(url, "data:application/mpeg4-bifs-au;base64") - || strstr(url, "data:application/mpeg4-es-au;base64") - ) return GF_TRUE; - - if (url[0]=='#') { - Bool st_type = GF_FALSE; - if (!stricmp(url, "#video")) st_type = GF_STREAM_VISUAL; - else if (!stricmp(url, "#audio")) st_type = GF_STREAM_AUDIO; - - if (st_type) { - u32 i=0; - RTPStream *st; - while ((st = (RTPStream *)gf_list_enum(priv->channels, &i))) { - if (st->depacketizer && (st->depacketizer->sl_map.StreamType==st_type)) - return GF_TRUE; - } - } - return GF_FALSE; - } else if (!RP_CanHandleURL(plug, url)) { - return GF_FALSE; - } - /*if this URL is part of a running session then ok*/ - sess = RP_CheckSession(priv, (char *) url); - if (sess) return GF_TRUE; - return GF_FALSE; -} - - -GF_InputService *RTP_Load() -{ - RTPClient *priv; - GF_InputService *plug; - GF_SAFEALLOC(plug, GF_InputService); - if (!plug) return NULL; - GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC RTP/RTSP Client", "gpac distribution") - - GF_SAFEALLOC(priv, RTPClient); - if (!priv) { - gf_free(plug); - return NULL; - } - - plug->CanHandleURL = RP_CanHandleURL; - plug->CanHandleURLInService = RP_CanHandleURLInService; - plug->ConnectService = RP_ConnectService; - plug->CloseService = RP_CloseService; - plug->GetServiceDescriptor = RP_GetServiceDesc; - plug->ConnectChannel = RP_ConnectChannel; - plug->DisconnectChannel = RP_DisconnectChannel; - plug->ServiceCommand = RP_ServiceCommand; - plug->RegisterMimeTypes = RP_RegisterMimeTypes; - - /*PULL mode for embedded streams*/ - plug->ChannelGetSLP = RP_ChannelGetSLP; - plug->ChannelReleaseSLP = RP_ChannelReleaseSLP; - - priv->sessions = gf_list_new(); - priv->channels = gf_list_new(); - - plug->priv = priv; - - priv->time_out = RTSP_DEFAULT_TIMEOUT; - priv->mx = gf_mx_new("RTPDemux"); - priv->th = gf_th_new("RTPDemux"); - - return plug; -} - - -void RTP_Delete(GF_BaseInterface *bi) -{ - RTPClient *rtp; - u32 retry; - GF_InputService *plug = (GF_InputService *) bi; - rtp = (RTPClient *)plug->priv; - - /*shutdown thread*/ - if (rtp->th_state==1) rtp->th_state = 0; - retry = 20; - while ((rtp->th_state==1) && retry) { - gf_sleep(10); - retry--; - } - assert(retry); - - if (rtp->session_state_data) gf_free(rtp->session_state_data); - - RP_cleanup(rtp); - gf_th_del(rtp->th); - gf_mx_del(rtp->mx); - gf_list_del(rtp->sessions); - gf_list_del(rtp->channels); - gf_free(rtp); - gf_free(bi); -} - -#endif - - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifndef GPAC_DISABLE_STREAMING - GF_NET_CLIENT_INTERFACE, -#endif - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ -#ifndef GPAC_DISABLE_STREAMING - if (InterfaceType == GF_NET_CLIENT_INTERFACE) return (GF_BaseInterface *)RTP_Load(); -#endif - return NULL; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#ifndef GPAC_DISABLE_STREAMING - case GF_NET_CLIENT_INTERFACE: - RTP_Delete(ifce); - break; -#endif - } -} - -GPAC_MODULE_STATIC_DECLARATION( rtp_in ) diff --git a/modules/rtp_in/rtp_signaling.c b/modules/rtp_in/rtp_signaling.c deleted file mode 100644 index 60b7e18..0000000 --- a/modules/rtp_in/rtp_signaling.c +++ /dev/null @@ -1,922 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / RTP input module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "rtp_in.h" -#include <gpac/internal/ietf_dev.h> - -#ifndef GPAC_DISABLE_STREAMING - -#define GPAC_SATIP_PORT 1400 - -Bool channel_is_valid(RTPClient *rtp, RTPStream *ch) -{ - u32 i=0; - RTPStream *st; - while ((st = (RTPStream *)gf_list_enum(rtp->channels, &i))) { - if (st == ch) return GF_TRUE; - } - return GF_FALSE; -} - -void RP_StopChannel(RTPStream *ch) -{ - if (!ch || !ch->rtsp) return; - - ch->flags &= ~RTP_SKIP_NEXT_COM; - //ch->status = RTP_Disconnected; - //remove interleaved - if (gf_rtp_is_interleaved(ch->rtp_ch)) { - gf_rtsp_unregister_interleave(ch->rtsp->session, gf_rtp_get_low_interleave_id(ch->rtp_ch)); - } -} - -/*this prevent sending teardown on session with running channels*/ -Bool RP_SessionActive(RTPStream *ch) -{ - RTPStream *ach; - u32 i, count; - i = count = 0; - while ((ach = (RTPStream *)gf_list_enum(ch->owner->channels, &i))) { - if (ach->rtsp != ch->rtsp) continue; - /*count only active channels*/ - if (ach->status == RTP_Running) count++; - } - return count ? GF_TRUE : GF_FALSE; -} - -static void RP_QueueCommand(RTSPSession *sess, RTPStream *ch, GF_RTSPCommand *com, Bool needs_sess_id) -{ - if (needs_sess_id) { - com->Session = sess->session_id; - } - if (gf_mx_try_lock(sess->owner->mx)) { - gf_list_add(sess->rtsp_commands, com); - gf_mx_v(sess->owner->mx); - } else { - gf_list_add(sess->rtsp_commands, com); - } -} - - - -/* - channel setup functions - */ - -void RP_Setup(RTPStream *ch) -{ - u16 def_first_port; - const char *opt; - GF_RTSPCommand *com; - GF_RTSPTransport *trans; - - com = gf_rtsp_command_new(); - com->method = gf_strdup(GF_RTSP_SETUP); - - def_first_port = 0; - opt = gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(ch->owner->service), "Streaming", "ForceFirstPort"); - if (opt) def_first_port = atoi(opt); - - opt = gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(ch->owner->service), "Streaming", "ForceMulticastIP"); - - //setup ports if unicast non interleaved or multicast - if (gf_rtp_is_unicast(ch->rtp_ch) && (ch->owner->transport_mode != 1) && !gf_rtp_is_interleaved(ch->rtp_ch) ) { - gf_rtp_set_ports(ch->rtp_ch, def_first_port); - } else if (opt) { - gf_rtp_set_ports(ch->rtp_ch, def_first_port); - } - - trans = gf_rtsp_transport_clone(gf_rtp_get_transport(ch->rtp_ch)); - - /*some servers get confused when trying to resetup on the same remote ports, so reset info*/ - trans->port_first = trans->port_last = 0; - trans->SSRC = 0; - - /*override transport: */ - /*1: multicast forced*/ - opt = gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(ch->owner->service), "Streaming", "ForceMulticastIP"); - if (opt) { - trans->IsUnicast = GF_FALSE; - trans->destination = gf_strdup(opt); - opt = gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(ch->owner->service), "Streaming", "ForceMulticastTTL"); - trans->TTL = opt ? atoi(opt) : 127; - if (trans->Profile) gf_free(trans->Profile); - trans->Profile = gf_strdup(GF_RTSP_PROFILE_RTP_AVP); - if (!(ch->rtsp->flags & RTSP_DSS_SERVER) ) { - trans->port_first = trans->client_port_first; - trans->port_last = trans->client_port_last; - /*this is correct but doesn't work with DSS: the server expects "client_port" to indicate - the multicast port, not "port" - this will send both*/ - //trans->client_port_first = trans->client_port_last = 0; - } - gf_rtp_setup_transport(ch->rtp_ch, trans, NULL); - } - /*2: RTP over RTSP forced*/ - else if (ch->rtsp->flags & RTSP_FORCE_INTER) { - if (trans->Profile) gf_free(trans->Profile); - trans->Profile = gf_strdup(GF_RTSP_PROFILE_RTP_AVP_TCP); - //some servers expect the interleaved to be set during the setup request - trans->IsInterleaved = GF_TRUE; - trans->rtpID = gf_list_find(ch->owner->channels, ch); - trans->rtcpID = trans->rtpID+1; - gf_rtp_setup_transport(ch->rtp_ch, trans, NULL); - } - - if (trans->source) { - gf_free(trans->source); - trans->source = NULL; - } - - /*turn off interleaving in case of re-setup, some servers don't like it (we still signal it - through RTP/AVP/TCP profile so it's OK)*/ -// trans->IsInterleaved = 0; - gf_list_add(com->Transports, trans); - if (strlen(ch->control)) com->ControlString = gf_strdup(ch->control); - - com->user_data = ch; - ch->status = RTP_WaitingForAck; - - RP_QueueCommand(ch->rtsp, ch, com, GF_TRUE); -} - -/*filter setup if no session (rtp only)*/ -GF_Err RP_SetupChannel(RTPStream *ch, ChannelDescribe *ch_desc) -{ - GF_Err resp; - - /*assign ES_ID of the channel*/ - if (ch_desc && !ch->ES_ID && ch_desc->ES_ID) ch->ES_ID = ch_desc->ES_ID; - - ch->status = RTP_Setup; - - /*assign channel handle if not done*/ - if (ch_desc && ch->channel) { - assert(ch->channel == ch_desc->channel); - } else if (!ch->channel && ch->rtsp && !ch->rtsp->satip) { - assert(ch_desc); - assert(ch_desc->channel); - ch->channel = ch_desc->channel; - } - - /*no session , setup for pure rtp*/ - if (!ch->rtsp) { - ch->flags |= RTP_CONNECTED; - /*init rtp*/ - resp = RP_InitStream(ch, GF_FALSE); - /*send confirmation to user*/ - RP_ConfirmChannelConnect(ch, resp); - } else { - RP_Setup(ch); - } - return GF_OK; -} - -void RP_ProcessSetup(RTSPSession *sess, GF_RTSPCommand *com, GF_Err e) -{ - RTPStream *ch; - u32 i; - GF_RTSPTransport *trans; - - ch = (RTPStream *)com->user_data; - if (e) goto exit; - - switch (sess->rtsp_rsp->ResponseCode) { - case NC_RTSP_OK: - break; - case NC_RTSP_Not_Found: - e = GF_STREAM_NOT_FOUND; - goto exit; - default: - e = GF_SERVICE_ERROR; - goto exit; - } - e = GF_SERVICE_ERROR; - if (!ch) goto exit; - - /*assign session ID*/ - if (!sess->rtsp_rsp->Session) { - e = GF_SERVICE_ERROR; - goto exit; - } - if (!sess->session_id) sess->session_id = gf_strdup(sess->rtsp_rsp->Session); - assert(!ch->session_id); - - /*transport setup: break at the first correct transport */ - i=0; - while ((trans = (GF_RTSPTransport *)gf_list_enum(sess->rtsp_rsp->Transports, &i))) { - /*copy over previous ports (hack for some servers overriding client ports)*/ - const char *opt = gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(ch->owner->service), "Streaming", "ForceClientPorts"); - if (opt && !stricmp(opt, "yes")) - gf_rtp_get_ports(ch->rtp_ch, &trans->client_port_first, &trans->client_port_last); - - if (gf_rtp_is_interleaved(ch->rtp_ch) && !trans->IsInterleaved) { - GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTSP] Requested interleaved RTP over RTSP but server did not setup interleave - cannot process command\n")); - e = GF_REMOTE_SERVICE_ERROR; - continue; - } - - e = gf_rtp_setup_transport(ch->rtp_ch, trans, gf_rtsp_get_server_name(sess->session)); - if (!e) break; - } - if (e) goto exit; - - e = RP_InitStream(ch, GF_FALSE); - if (e) goto exit; - ch->status = RTP_Connected; - - //in case this is TCP channel, setup callbacks - ch->flags &= ~RTP_INTERLEAVED; - if (gf_rtp_is_interleaved(ch->rtp_ch)) { - ch->flags |= RTP_INTERLEAVED; - gf_rtsp_set_interleave_callback(sess->session, RP_DataOnTCP); - } - - if (sess->satip) { - ChannelControl *ch_ctrl = NULL; - GF_RTSPCommand *com = gf_rtsp_command_new(); - com->method = gf_strdup(GF_RTSP_PLAY); - GF_SAFEALLOC(ch_ctrl, ChannelControl); - ch_ctrl->ch = ch; - com->user_data = ch_ctrl; - RP_QueueCommand(sess, ch, com, GF_TRUE); - } - -exit: - /*confirm only on first connect, otherwise this is a re-SETUP of the rtsp session, not the channel*/ - if (ch && ! (ch->flags & RTP_CONNECTED) ) { - if (!e) - ch->flags |= RTP_CONNECTED; - RP_ConfirmChannelConnect(ch, e); - } - com->user_data = NULL; -} - - -/* - session/channel describe functions - */ -/*filter describe commands in case of ESD URLs*/ -Bool RP_PreprocessDescribe(RTSPSession *sess, GF_RTSPCommand *com) -{ - RTPStream *ch; - ChannelDescribe *ch_desc; - /*not a channel describe*/ - if (!com->user_data) { - RP_SendMessage(sess->owner->service, GF_OK, "Connecting..."); - return GF_TRUE; - } - - ch_desc = (ChannelDescribe *)com->user_data; - ch = RP_FindChannel(sess->owner, NULL, ch_desc->ES_ID, ch_desc->esd_url, GF_FALSE); - if (!ch) return GF_TRUE; - - /*channel has been described already, skip describe and send setup directly*/ - RP_SetupChannel(ch, ch_desc); - - if (ch_desc->esd_url) gf_free(ch_desc->esd_url); - gf_free(ch_desc); - return GF_FALSE; -} - -/*process describe reply*/ -GF_Err RP_ProcessDescribe(RTSPSession *sess, GF_RTSPCommand *com, GF_Err e) -{ - RTPStream *ch; - ChannelDescribe *ch_desc; - - ch = NULL; - ch_desc = (ChannelDescribe *)com->user_data; - if (e) goto exit; - - switch (sess->rtsp_rsp->ResponseCode) { - //TODO handle all 3xx codes (redirections) - case NC_RTSP_Multiple_Choice: - e = ch_desc ? GF_STREAM_NOT_FOUND : GF_URL_ERROR; - goto exit; - case NC_RTSP_Not_Found: - e = GF_URL_ERROR; - goto exit; - case NC_RTSP_OK: - break; - default: - //we should have a basic error code mapping here - e = GF_SERVICE_ERROR; - goto exit; - } - - ch = NULL; - if (ch_desc) { - ch = RP_FindChannel(sess->owner, ch_desc->channel, ch_desc->ES_ID, ch_desc->esd_url, GF_FALSE); - } else { - RP_SendMessage(sess->owner->service, GF_OK, "Connected"); - } - - /*error on loading SDP is done internally*/ - RP_LoadSDP(sess->owner, sess->rtsp_rsp->body, sess->rtsp_rsp->Content_Length, ch); - - if (!ch_desc) goto exit; - if (!ch) { - e = GF_STREAM_NOT_FOUND; - goto exit; - } - e = RP_SetupChannel(ch, ch_desc); - -exit: - com->user_data = NULL; - if (e) { - if (!ch_desc) { - sess->connect_error = e; - return e; - } else if (ch) { - RP_ConfirmChannelConnect(ch, e); - } else { - gf_service_connect_ack(sess->owner->service, ch_desc->channel, e); - } - } - if (ch_desc) gf_free(ch_desc); - return GF_OK; -} - -/*send describe*/ -void RP_Describe(RTSPSession *sess, char *esd_url, LPNETCHANNEL channel) -{ - const char *opt; - RTPStream *ch; - ChannelDescribe *ch_desc; - GF_RTSPCommand *com; - - /*locate the channel by URL - if we have one, this means the channel is already described - this happens when 2 ESD with URL use the same RTSP service - skip describe and send setup*/ - if (esd_url || channel) { - ch = RP_FindChannel(sess->owner, channel, 0, esd_url, GF_FALSE); - if (ch) { - if (!ch->channel) ch->channel = channel; - switch (ch->status) { - case RTP_Connected: - case RTP_Running: - RP_ConfirmChannelConnect(ch, GF_OK); - return; - default: - break; - } - ch_desc = (ChannelDescribe *)gf_malloc(sizeof(ChannelDescribe)); - ch_desc->esd_url = esd_url ? gf_strdup(esd_url) : NULL; - ch_desc->channel = channel; - RP_SetupChannel(ch, ch_desc); - - if (esd_url) gf_free(ch_desc->esd_url); - gf_free(ch_desc); - return; - } - /*channel not found, send describe on service*/ - } - - /*send describe*/ - com = gf_rtsp_command_new(); - if (!sess->satip) { - com->method = gf_strdup(GF_RTSP_DESCRIBE); - } else { - GF_Err e; - GF_RTSPTransport *trans; - RTPStream *ch = NULL; - - com->method = gf_strdup(GF_RTSP_SETUP); - - /*setup transport ports*/ - GF_SAFEALLOC(trans, GF_RTSPTransport); - trans->IsUnicast = GF_TRUE; - trans->client_port_first = GPAC_SATIP_PORT; - trans->client_port_last = GPAC_SATIP_PORT+1; - trans->Profile = gf_strdup(GF_RTSP_PROFILE_RTP_AVP); - gf_list_add(com->Transports, trans); - - /*hardcoded channel*/ - ch = RP_NewSatipStream(sess->owner, sess->satip_server); - if (!ch) { - GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("SAT>IP: couldn't create the RTP stream.\n")); - return; - } - e = RP_AddStream(sess->owner, ch, "*"); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("SAT>IP: couldn't add the RTP stream.\n")); - return; - } - com->user_data = ch; - } - - if (channel || esd_url) { - com->Accept = gf_strdup("application/sdp"); - com->ControlString = esd_url ? gf_strdup(esd_url) : NULL; - - ch_desc = (ChannelDescribe *)gf_malloc(sizeof(ChannelDescribe)); - ch_desc->esd_url = esd_url ? gf_strdup(esd_url) : NULL; - ch_desc->channel = channel; - - com->user_data = ch_desc; - } else { - //always accept both SDP and IOD - com->Accept = gf_strdup("application/sdp, application/mpeg4-iod"); -// com->Accept = gf_strdup("application/sdp"); - } - - /*need better tuning ...*/ - opt = (char *) gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(sess->owner->service), "Network", "Bandwidth"); - if (opt && !stricmp(opt, "yes")) com->Bandwidth = atoi(opt); - - RP_QueueCommand(sess, NULL, com, GF_FALSE); -} - - -static void SkipCommandOnSession(RTPStream *ch) -{ - u32 i; - RTPStream *a_ch; - if (!ch || (ch->flags & RTP_SKIP_NEXT_COM) || !(ch->rtsp->flags & RTSP_AGG_CONTROL) ) return; - i=0; - while ((a_ch = (RTPStream *)gf_list_enum(ch->owner->channels, &i))) { - if ((ch == a_ch) || (a_ch->rtsp != ch->rtsp) ) continue; - if (a_ch->status>=RTP_Connected) - a_ch->flags |= RTP_SKIP_NEXT_COM; - } -} - - -/* - channel control functions - */ -/*remove command if session is using aggregated control*/ -Bool RP_PreprocessUserCom(RTSPSession *sess, GF_RTSPCommand *com) -{ - ChannelControl *ch_ctrl; - RTPStream *ch; - GF_Err e; - Bool skip_it; - - ch_ctrl = NULL; - if (strcmp(com->method, GF_RTSP_TEARDOWN)) ch_ctrl = (ChannelControl *)com->user_data; - if (!ch_ctrl || !ch_ctrl->ch) return GF_TRUE; - ch = ch_ctrl->ch; - - if (!sess->satip) { - if (!ch->channel || !channel_is_valid(sess->owner, ch)) { - gf_free(ch_ctrl); - com->user_data = NULL; - return GF_FALSE; - } - - assert(ch->rtsp == sess); - assert(ch->channel == ch_ctrl->com.base.on_channel); - } - - skip_it = GF_FALSE; - if (!com->Session) { - /*re-SETUP failed*/ - if (!strcmp(com->method, GF_RTSP_PLAY) || !strcmp(com->method, GF_RTSP_PAUSE)) { - e = GF_SERVICE_ERROR; - goto err_exit; - } - /*this is a stop, no need for SessionID just skip*/ - skip_it = GF_TRUE; - } else { - SkipCommandOnSession(ch); - } - - /*check if aggregation discards this command*/ - if (skip_it || ( (sess->flags & RTSP_AGG_CONTROL) && (ch->flags & RTP_SKIP_NEXT_COM) )) { - ch->flags &= ~RTP_SKIP_NEXT_COM; - gf_service_command(sess->owner->service, &ch_ctrl->com, GF_OK); - gf_free(ch_ctrl); - com->user_data = NULL; - return GF_FALSE; - } - return GF_TRUE; - -err_exit: - gf_rtsp_reset_aggregation(ch->rtsp->session); - ch->status = RTP_Disconnected; - ch->check_rtp_time = RTP_SET_TIME_NONE; - gf_service_command(sess->owner->service, &ch_ctrl->com, e); - gf_free(ch_ctrl); - com->user_data = NULL; - return GF_FALSE; -} - -void RP_ProcessUserCommand(RTSPSession *sess, GF_RTSPCommand *com, GF_Err e) -{ - ChannelControl *ch_ctrl; - RTPStream *ch, *agg_ch; - u32 i, count; - GF_RTPInfo *info; - - ch_ctrl = (ChannelControl *)com->user_data; - ch = ch_ctrl->ch; - - if (sess->satip) { - if (!strcmp(com->method, GF_RTSP_PLAY)) { - char url[64]; - snprintf(url, 64, "mpegts-sk://%p", ch->rtp_ch->rtp); - e = ch->satip_m2ts_ifce->ConnectService(ch->satip_m2ts_ifce, sess->owner->service, url); - if (e) { - assert(0); - GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[SATIP] Couldn't connect the M2TS service.\n")); - return; - } else { - ch->satip_m2ts_service_connected = GF_TRUE; - } - } else if (!strcmp(com->method, GF_RTSP_PLAY)) { - if (ch->satip_m2ts_service_connected) { - ch->satip_m2ts_ifce->CloseService(ch->satip_m2ts_ifce); - ch->satip_m2ts_service_connected = GF_FALSE; - } - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[SATIP] Unhandled RTSP command: %s\n", com->method)); - return; - } - return; - } - - if (ch) { - if (!ch->channel || !channel_is_valid(sess->owner, ch)) { - gf_free(ch_ctrl); - com->user_data = NULL; - return; - } - - assert(ch->channel==ch_ctrl->com.base.on_channel); - } - - /*some consistency checking: on interleaved sessions, some servers do NOT reply to the - teardown. If our command is STOP just skip the error notif*/ - if (e) { - if (!strcmp(com->method, GF_RTSP_TEARDOWN)) { - goto process_reply; - } else { - /*spec is not really clear about what happens if the server doesn't support - non aggregated operations. Since this happen only on pause/play, we consider - that no error occured and wait for next play*/ - if (sess->rtsp_rsp->ResponseCode == NC_RTSP_Only_Aggregate_Operation_Allowed) { - sess->flags |= RTSP_AGG_ONLY; - sess->rtsp_rsp->ResponseCode = NC_RTSP_OK; - } else { - goto err_exit; - } - } - } - - switch (sess->rtsp_rsp->ResponseCode) { - //handle all 3xx codes (redirections) - case NC_RTSP_Method_Not_Allowed: - e = GF_NOT_SUPPORTED; - goto err_exit; - case NC_RTSP_OK: - break; - default: - //we should have a basic error code mapping here - e = GF_SERVICE_ERROR; - goto err_exit; - } - -process_reply: - - gf_service_command(sess->owner->service, &ch_ctrl->com, GF_OK); - - if ( (ch_ctrl->com.command_type==GF_NET_CHAN_PLAY) - || (ch_ctrl->com.command_type==GF_NET_CHAN_SET_SPEED) - || (ch_ctrl->com.command_type==GF_NET_CHAN_RESUME) ) { - - //auto-detect any aggregated control if not done yet - if (gf_list_count(sess->rtsp_rsp->RTP_Infos) > 1) { - sess->flags |= RTSP_AGG_CONTROL; - } - - //process all RTP infos - count = gf_list_count(sess->rtsp_rsp->RTP_Infos); - for (i=0; i<count; i++) { - info = (GF_RTPInfo*)gf_list_get(sess->rtsp_rsp->RTP_Infos, i); - agg_ch = RP_FindChannel(sess->owner, NULL, 0, info->url, GF_FALSE); - - if (!agg_ch || (agg_ch->rtsp != sess) ) continue; - /*channel is already playing*/ - if (agg_ch->status == RTP_Running) { - gf_rtp_set_info_rtp(agg_ch->rtp_ch, info->seq, info->rtp_time, info->ssrc); - agg_ch->check_rtp_time = RTP_SET_TIME_RTP; - continue; - } - - /*if play/seeking we must send update RTP/NPT link*/ - if (ch_ctrl->com.command_type != GF_NET_CHAN_RESUME) { - agg_ch->check_rtp_time = RTP_SET_TIME_RTP; - } - /*this is used to discard RTP packets re-sent on resume*/ - else { - agg_ch->check_rtp_time = RTP_SET_TIME_RTP_SEEK; - } - /* reset the buffers */ - RP_InitStream(agg_ch, GF_TRUE); - - gf_rtp_set_info_rtp(agg_ch->rtp_ch, info->seq, info->rtp_time, info->ssrc); - agg_ch->status = RTP_Running; - - /*skip next play command on this channel if aggregated control*/ - if ((ch != agg_ch) && ch && (ch->rtsp->flags & RTSP_AGG_CONTROL) ) agg_ch->flags |= RTP_SKIP_NEXT_COM; - - - if (gf_rtp_is_interleaved(agg_ch->rtp_ch)) { - gf_rtsp_register_interleave(sess->session, - agg_ch, - gf_rtp_get_low_interleave_id(agg_ch->rtp_ch), - gf_rtp_get_hight_interleave_id(agg_ch->rtp_ch)); - } - } - /*no rtp info (just in case), no time mapped - set to 0 and specify we're not interactive*/ - if (ch && !i) { - ch->current_start = 0.0; - ch->check_rtp_time = RTP_SET_TIME_RTP; - RP_InitStream(ch, GF_TRUE); - ch->status = RTP_Running; - if (gf_rtp_is_interleaved(ch->rtp_ch)) { - gf_rtsp_register_interleave(sess->session, - ch, gf_rtp_get_low_interleave_id(ch->rtp_ch), gf_rtp_get_hight_interleave_id(ch->rtp_ch)); - } - } - if (ch) ch->flags &= ~RTP_SKIP_NEXT_COM; - } else if (ch_ctrl->com.command_type == GF_NET_CHAN_PAUSE) { - if (ch) { - SkipCommandOnSession(ch); - ch->flags &= ~RTP_SKIP_NEXT_COM; - - } - } else if (ch_ctrl->com.command_type == GF_NET_CHAN_STOP) { - } - gf_free(ch_ctrl); - com->user_data = NULL; - return; - - -err_exit: - gf_service_command(sess->owner->service, &ch_ctrl->com, e); - if (ch) { - ch->status = RTP_Disconnected; - gf_rtsp_reset_aggregation(ch->rtsp->session); - ch->check_rtp_time = RTP_SET_TIME_NONE; - } - gf_free(ch_ctrl); - com->user_data = NULL; -} - -#if 0 -static void RP_FlushAndTearDown(RTSPSession *sess) -{ - GF_RTSPCommand *com; - gf_mx_p(sess->owner->mx); - - while (gf_list_count(sess->rtsp_commands)) { - com = (GF_RTSPCommand *)gf_list_get(sess->rtsp_commands, 0); - gf_list_rem(sess->rtsp_commands, 0); - gf_rtsp_command_del(com); - } - if (sess->flags & RTSP_WAIT_REPLY) { - GF_Err e; - while (1) { - e = gf_rtsp_get_response(sess->session, sess->rtsp_rsp); - if (e!= GF_IP_NETWORK_EMPTY) break; - } - sess->flags &= ~RTSP_WAIT_REPLY; - } - gf_mx_v(sess->owner->mx); - - - /*no private stack on teardown - shutdown now*/ - com = gf_rtsp_command_new(); - com->method = gf_strdup(GF_RTSP_TEARDOWN); - RP_QueueCommand(sess, NULL, com, 1); -} -#endif - - -void RP_UserCommand(RTSPSession *sess, RTPStream *ch, GF_NetworkCommand *command) -{ - RTPStream *a_ch; - ChannelControl *ch_ctrl; - u32 i; - Bool needs_setup = GF_FALSE; - GF_RTSPCommand *com; - GF_RTSPRange *range; - - switch (command->command_type) { - case GF_NET_CHAN_PLAY: - case GF_NET_CHAN_RESUME: - needs_setup = GF_TRUE; - break; - case GF_NET_CHAN_PAUSE: - case GF_NET_CHAN_STOP: - break; - default: - gf_service_command(sess->owner->service, command, GF_NOT_SUPPORTED); - return; - } - - - /*we may need to re-setup stream/session*/ - if (needs_setup) { - if (ch->status == RTP_Disconnected) { - if (sess->flags & RTSP_AGG_CONTROL) { - i=0; - while ((a_ch = (RTPStream *)gf_list_enum(sess->owner->channels, &i))) { - if (a_ch->rtsp != sess) continue; - if (a_ch->status == RTP_Disconnected) - RP_Setup(a_ch); - } - } else { - RP_Setup(ch); - } - } - } - - com = gf_rtsp_command_new(); - range = NULL; - - if ( (command->command_type==GF_NET_CHAN_PLAY) || (command->command_type==GF_NET_CHAN_RESUME) ) { - - range = gf_rtsp_range_new(); - range->start = ch->range_start; - range->end = ch->range_end; - - com->method = gf_strdup(GF_RTSP_PLAY); - - ch->paused = GF_FALSE; - - /*specify pause range on resume - this is not mandatory but most servers need it*/ - if (command->command_type==GF_NET_CHAN_RESUME) { - range->start = ch->current_start; - - ch->stat_start_time -= ch->stat_stop_time; - ch->stat_start_time += gf_sys_clock(); - ch->stat_stop_time = 0; - } else { - range->start = ch->range_start; - if (command->play.start_range>=0) range->start += command->play.start_range; - range->end = ch->range_start; - if (command->play.end_range >=0) { - range->end += command->play.end_range; - if (range->end > ch->range_end) range->end = ch->range_end; - } - - ch->stat_start_time = gf_sys_clock(); - ch->stat_stop_time = 0; - } - /*if aggregated the command is sent once, so store info at session level*/ - if (ch->flags & RTP_SKIP_NEXT_COM) { - ch->current_start = ch->rtsp->last_range; - } else { - ch->rtsp->last_range = range->start; - ch->current_start = range->start; - } - /*some RTSP servers don't accept Range=npt:0.0- (for ex, broadcast only...), so skip it if: - - a range was given in initial describe - - the command is not a RESUME - */ - if (!(ch->flags & RTP_HAS_RANGE) && (command->command_type != GF_NET_CHAN_RESUME) ) { - gf_rtsp_range_del(range); - com->Range = NULL; - } else { - com->Range = range; - } - - if (sess->flags & RTSP_AGG_CONTROL) - SkipCommandOnSession(ch); - else if (strlen(ch->control)) - com->ControlString = gf_strdup(ch->control); - - if (RP_SessionActive(ch)) { - if (!com->ControlString && ch->control) com->ControlString = gf_strdup(ch->control); - } else { - if (com->ControlString) { - gf_free(com->ControlString); - com->ControlString=NULL; - } - } - - } else if (command->command_type==GF_NET_CHAN_PAUSE) { - com->method = gf_strdup(GF_RTSP_PAUSE); - if (ch) { - range = gf_rtsp_range_new(); - /*update current time*/ - ch->current_start += gf_rtp_get_current_time(ch->rtp_ch); - ch->stat_stop_time = gf_sys_clock(); - range->start = ch->current_start; - range->end = -1.0; - com->Range = range; - - if (sess->flags & RTSP_AGG_CONTROL) - SkipCommandOnSession(ch); - else if (strlen(ch->control)) - com->ControlString = gf_strdup(ch->control); - - ch->paused = GF_TRUE; - } - } - else if (command->command_type==GF_NET_CHAN_STOP) { - ch->current_start = 0; - ch->stat_stop_time = gf_sys_clock(); - - ch->status = RTP_Connected; - RP_InitStream(ch, GF_TRUE); - - /*if server only support aggregation on pause, skip the command or issue - a teardown if last active stream*/ - if (ch->rtsp->flags & RTSP_AGG_ONLY) { - RP_StopChannel(ch); - if (com) gf_rtsp_command_del(com); - if (!RP_SessionActive(ch)) - RP_Teardown(sess, ch); - return; - } - /* otherwise send a PAUSE on the stream */ - else { - if (ch->paused) { - if (com) gf_rtsp_command_del(com); - return; - } - range = gf_rtsp_range_new(); - range->start = 0; - range->end = -1; - com->method = gf_strdup(GF_RTSP_PAUSE); - com->Range = range; - /*only pause the specified stream*/ - if (ch->control) com->ControlString = gf_strdup(ch->control); - } - } else { - gf_service_command(sess->owner->service, command, GF_NOT_SUPPORTED); - gf_rtsp_command_del(com); - return; - } - - ch_ctrl = (ChannelControl *)gf_malloc(sizeof(ChannelControl)); - ch_ctrl->ch = ch; - memcpy(&ch_ctrl->com, command, sizeof(GF_NetworkCommand)); - com->user_data = ch_ctrl; - - RP_QueueCommand(sess, ch, com, GF_TRUE); - return; -} - - -/* - session/channel teardown functions - */ -void RP_ProcessTeardown(RTSPSession *sess, GF_RTSPCommand *com, GF_Err e) -{ - RTPStream *ch = (RTPStream *)com->user_data; - if (ch) { - if (ch->session_id) gf_free(ch->session_id); - ch->session_id = NULL; - } else { - if (sess->session_id) gf_free(sess->session_id); - sess->session_id = NULL; - } -} - -void RP_Teardown(RTSPSession *sess, RTPStream *ch) -{ - GF_RTSPCommand *com; - if (sess->owner->session_migration) return; - /*we need a session id*/ - if (!sess->session_id) return; - /*ignore teardown on channels*/ - if ((sess->flags & RTSP_AGG_CONTROL) && ch) return; - - com = gf_rtsp_command_new(); - com->method = gf_strdup(GF_RTSP_TEARDOWN); - /*this only works in RTSP2*/ - if (ch && ch->control) { - com->ControlString = gf_strdup(ch->control); - com->user_data = ch; - } - - RP_QueueCommand(sess, ch, com, GF_TRUE); -} - -#endif /*GPAC_DISABLE_STREAMING*/ diff --git a/modules/rtp_in/rtp_stream.c b/modules/rtp_in/rtp_stream.c deleted file mode 100644 index 67e2533..0000000 --- a/modules/rtp_in/rtp_stream.c +++ /dev/null @@ -1,643 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / RTP input module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "rtp_in.h" -#include <gpac/internal/ietf_dev.h> -#include <gpac/internal/terminal_dev.h> //for SAT>IP: we need to instantiate the M2TS demuxer - -#ifndef GPAC_DISABLE_STREAMING - -void RP_ConfirmChannelConnect(RTPStream *ch, GF_Err e) -{ - GF_NetworkCommand com; - - /*in case the channel has been disconnected while SETUP was issued&processed. We also could - clean up the command stack*/ - if (!ch->channel) return; - - gf_service_connect_ack(ch->owner->service, ch->channel, e); - if (e != GF_OK || !ch->rtp_ch) return; - - /*success, overwrite SL config*/ - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.command_type = GF_NET_CHAN_RECONFIG; - com.base.on_channel = ch->channel; - - gf_rtp_depacketizer_get_slconfig(ch->depacketizer, &com.cfg.sl_config); - /*reconfig*/ - gf_service_command(ch->owner->service, &com, GF_OK); - - /*ISMACryp config*/ - if (ch->depacketizer->flags & GF_RTP_HAS_ISMACRYP) { - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.on_channel = ch->channel; - com.command_type = GF_NET_CHAN_DRM_CFG; - com.drm_cfg.scheme_type = ch->depacketizer->isma_scheme; - com.drm_cfg.scheme_version = 1; - /*not transported in SDP!!!*/ - com.drm_cfg.scheme_uri = NULL; - com.drm_cfg.kms_uri = ch->depacketizer->key; - gf_service_command(ch->owner->service, &com, GF_OK); - } -} - -GF_Err RP_InitStream(RTPStream *ch, Bool ResetOnly) -{ - gf_rtp_depacketizer_reset(ch->depacketizer, !ResetOnly); - - if (!ResetOnly) { - const char *ip_ifce = NULL; - u32 reorder_size = 0; - if (!ch->owner->transport_mode) { - const char *sOpt = gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(ch->owner->service), "Streaming", "ReorderSize"); - if (sOpt) reorder_size = atoi(sOpt); - else reorder_size = 10; - - ip_ifce = gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(ch->owner->service), "Network", "DefaultMCastInterface"); - if (!ip_ifce) { - const char *mob_on = gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(ch->owner->service), "Network", "MobileIPEnabled"); - if (mob_on && !strcmp(mob_on, "yes")) { - ip_ifce = gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(ch->owner->service), "Network", "MobileIP"); - ch->flags |= RTP_MOBILEIP; - } - - } - } - return gf_rtp_initialize(ch->rtp_ch, RTP_BUFFER_SIZE, GF_FALSE, 0, reorder_size, 200, (char *)ip_ifce); - } - //just reset the sockets - gf_rtp_reset_buffers(ch->rtp_ch); - return GF_OK; -} - -void RP_DeleteStream(RTPStream *ch) -{ - if (ch->rtsp) { - if (ch->status == RTP_Running) { - RP_Teardown(ch->rtsp, ch); - ch->status = RTP_Disconnected; - } - RP_RemoveStream(ch->owner, ch); - } else { - RP_FindChannel(ch->owner, ch->channel, 0, NULL, GF_TRUE); - } - - if (ch->depacketizer) gf_rtp_depacketizer_del(ch->depacketizer); - if (ch->rtp_ch) gf_rtp_del(ch->rtp_ch); - if (ch->control) gf_free(ch->control); - if (ch->session_id) gf_free(ch->session_id); - if (ch->satip_m2ts_ifce) { - if (ch->satip_m2ts_service_connected) { - ch->satip_m2ts_ifce->CloseService(ch->satip_m2ts_ifce); - } - gf_modules_close_interface((GF_BaseInterface *)ch->satip_m2ts_ifce); - } - gf_free(ch); -} - - -static void rtp_sl_packet_cbk(void *udta, char *payload, u32 size, GF_SLHeader *hdr, GF_Err e) -{ - u64 cts, dts; - RTPStream *ch = (RTPStream *)udta; - - if (!ch->rtcp_init) { - if (!ch->rtcp_check_start) { - ch->rtcp_check_start = gf_sys_clock(); - return; - } - else if (gf_sys_clock() - ch->rtcp_check_start <= RTCP_DEFAULT_TIMEOUT_MS) { - return; - } - GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTP] Timeout for RTCP: no SR recevied after %d ms - forcing playback, sync may be broken\n", RTCP_DEFAULT_TIMEOUT_MS)); - ch->rtcp_init = GF_TRUE; - } - - cts = hdr->compositionTimeStamp; - dts = hdr->decodingTimeStamp; - - if (hdr->compositionTimeStamp < ch->ts_offset) { - hdr->compositionTimeStamp = hdr->decodingTimeStamp = 0; - hdr->seekFlag = 1; - } else { - hdr->seekFlag = 0; - hdr->compositionTimeStamp -= ch->ts_offset; - - if (hdr->decodingTimeStamp) { - hdr->decodingTimeStamp -= ch->ts_offset; - } - } - - if (ch->rtp_ch->packet_loss) e = GF_REMOTE_SERVICE_ERROR; - - if (ch->owner->first_packet_drop && (hdr->packetSequenceNumber >= ch->owner->first_packet_drop) ) { - if ( (hdr->packetSequenceNumber - ch->owner->first_packet_drop) % ch->owner->frequency_drop) - gf_service_send_packet(ch->owner->service, ch->channel, payload, size, hdr, e); - } else { - gf_service_send_packet(ch->owner->service, ch->channel, payload, size, hdr, e); - } - hdr->compositionTimeStamp = cts; - hdr->decodingTimeStamp = dts; -} - -RTPStream *RP_NewSatipStream(RTPClient *rtp, const char *server_ip) -{ - char *ctrl; - GF_RTPMap map; - GF_RTSPTransport trans; - RTPStream *tmp; - GF_SAFEALLOC(tmp, RTPStream); - if (!tmp) return NULL; - tmp->owner = rtp; - - /*create an RTP channel*/ - tmp->rtp_ch = gf_rtp_new(); - tmp->control = gf_strdup("*"); - - memset(&trans, 0, sizeof(GF_RTSPTransport)); - trans.Profile = "RTP/AVP"; - trans.source = gf_strdup(server_ip); - trans.IsUnicast = GF_TRUE; - trans.client_port_first = 0; - trans.client_port_last = 0; - trans.port_first = 0; - trans.port_last = 0; - - if (gf_rtp_setup_transport(tmp->rtp_ch, &trans, NULL) != GF_OK) { - RP_DeleteStream(tmp); - return NULL; - } - - /*setup channel*/ - memset(&map, 0, sizeof(GF_RTPMap)); - map.PayloadType = 33; - map.ClockRate = 90000; - gf_rtp_setup_payload(tmp->rtp_ch, &map); - - ctrl = (char *)gf_modules_get_option((GF_BaseInterface *)gf_service_get_interface(rtp->service), "Streaming", "DisableRTCP"); - if (!ctrl || stricmp(ctrl, "yes")) tmp->flags |= RTP_ENABLE_RTCP; - - /*setup NAT keep-alive*/ - ctrl = (char *)gf_modules_get_option((GF_BaseInterface *)gf_service_get_interface(rtp->service), "Streaming", "NATKeepAlive"); - if (ctrl) { - gf_rtp_enable_nat_keepalive(tmp->rtp_ch, atoi(ctrl)); - } else { - gf_rtp_enable_nat_keepalive(tmp->rtp_ch, 30000); /*keep-alive every 30s, SAT>IP max is 60s*/ - } - - tmp->range_start = 0; - tmp->range_end = 0; - - tmp->satip_m2ts_ifce = (GF_InputService*)gf_modules_load_interface_by_name(rtp->service->term->user->modules, "GPAC MPEG-2 TS Reader", GF_NET_CLIENT_INTERFACE); - if (!tmp->satip_m2ts_ifce) { - GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[SAT>IP] Couldn't load the M2TS demuxer.\n")); - RP_DeleteStream(tmp); - return NULL; - } - tmp->satip_m2ts_ifce->proxy_udta = rtp; - - return tmp; -} - -RTPStream *RP_NewStream(RTPClient *rtp, GF_SDPMedia *media, GF_SDPInfo *sdp, RTPStream *input_stream) -{ - GF_RTSPRange *range; - RTPStream *tmp; - GF_RTPMap *map; - u32 i, ESID, ODID, ssrc, rtp_seq, rtp_time; - Bool force_bcast = GF_FALSE; - Double Start, End; - Float CurrentTime; - u16 rvc_predef = 0; - char *rvc_config_att = NULL; - u32 s_port_first, s_port_last; - GF_X_Attribute *att; - Bool is_migration = GF_FALSE; - char *ctrl; - GF_SDPConnection *conn; - GF_RTSPTransport trans; - u32 mid, prev_stream, base_stream; - - //extract all relevant info from the GF_SDPMedia - Start = 0.0; - End = -1.0; - CurrentTime = 0.0f; - ODID = 0; - ESID = 0; - ctrl = NULL; - range = NULL; - s_port_first = s_port_last = 0; - ssrc = rtp_seq = rtp_time = 0; - mid = prev_stream = base_stream = 0; - i=0; - while ((att = (GF_X_Attribute*)gf_list_enum(media->Attributes, &i))) { - if (!stricmp(att->Name, "control")) ctrl = att->Value; - else if (!stricmp(att->Name, "gpac-broadcast")) force_bcast = GF_TRUE; - else if (!stricmp(att->Name, "mpeg4-esid") && att->Value) ESID = atoi(att->Value); - else if (!stricmp(att->Name, "mpeg4-odid") && att->Value) ODID = atoi(att->Value); - else if (!stricmp(att->Name, "range") && !range) range = gf_rtsp_range_parse(att->Value); - else if (!stricmp(att->Name, "x-stream-state") ) { - sscanf(att->Value, "server-port=%u-%u;ssrc=%X;npt=%g;seq=%u;rtptime=%u", - &s_port_first, &s_port_last, &ssrc, &CurrentTime, &rtp_seq, &rtp_time); - is_migration = GF_TRUE; - } - else if (!stricmp(att->Name, "x-server-port") ) { - sscanf(att->Value, "%u-%u", &s_port_first, &s_port_last); - } else if (!stricmp(att->Name, "rvc-config-predef")) { - rvc_predef = atoi(att->Value); - } else if (!stricmp(att->Name, "rvc-config")) { - rvc_config_att = att->Value; - } else if (!stricmp(att->Name, "mid")) { - sscanf(att->Value, "L%d", &mid); - } else if (!stricmp(att->Name, "depend")) { - char buf[3000]; - memset(buf, 0, 3000); - sscanf(att->Value, "%*d lay L%d %*s %s", &base_stream, buf); - if (!strlen(buf)) - sscanf(att->Value, "%*d lay %s", buf); - sscanf(buf, "L%d", &prev_stream); - } - } - - if (range) { - Start = range->start; - End = range->end; - gf_rtsp_range_del(range); - } - - /*check connection*/ - conn = sdp->c_connection; - if (conn && (!conn->host || !strcmp(conn->host, "0.0.0.0"))) conn = NULL; - - if (!conn) conn = (GF_SDPConnection*)gf_list_get(media->Connections, 0); - if (conn && (!conn->host || !strcmp(conn->host, "0.0.0.0"))) conn = NULL; - - if (!conn) { - /*RTSP RFC recommends an empty "c= " line but some server don't send it. Use session info (o=)*/ - if (!sdp->o_net_type || !sdp->o_add_type || strcmp(sdp->o_net_type, "IN")) return NULL; - if (strcmp(sdp->o_add_type, "IP4") && strcmp(sdp->o_add_type, "IP6")) return NULL; - } else { - if (strcmp(conn->net_type, "IN")) return NULL; - if (strcmp(conn->add_type, "IP4") && strcmp(conn->add_type, "IP6")) return NULL; - } - /*do we support transport*/ - if (strcmp(media->Profile, "RTP/AVP") && strcmp(media->Profile, "RTP/AVP/TCP") - && strcmp(media->Profile, "RTP/SAVP") && strcmp(media->Profile, "RTP/SAVP/TCP") - ) return NULL; - - /*check RTP map. For now we only support 1 RTPMap*/ - if (media->fmt_list || (gf_list_count(media->RTPMaps) > 1)) return NULL; - - /*check payload type*/ - map = (GF_RTPMap*)gf_list_get(media->RTPMaps, 0); - - /*this is an ESD-URL setup, we likely have namespace conflicts so overwrite given ES_ID - by the app one (client side), but keep control (server side) if provided*/ - if (input_stream) { - ESID = input_stream->ES_ID; - if (!ctrl) ctrl = input_stream->control; - tmp = input_stream; - } else { - tmp = RP_FindChannel(rtp, NULL, ESID, NULL, GF_FALSE); - if (tmp) return NULL; - - GF_SAFEALLOC(tmp, RTPStream); - if (!tmp) return NULL; - tmp->owner = rtp; - } - - /*create an RTP channel*/ - tmp->rtp_ch = gf_rtp_new(); - if (ctrl) tmp->control = gf_strdup(ctrl); - tmp->ES_ID = ESID; - tmp->OD_ID = ODID; - tmp->mid = mid; - tmp->prev_stream = prev_stream; - tmp->base_stream = base_stream; - - memset(&trans, 0, sizeof(GF_RTSPTransport)); - trans.Profile = media->Profile; - trans.source = conn ? conn->host : sdp->o_address; - trans.IsUnicast = gf_sk_is_multicast_address(trans.source) ? GF_FALSE : GF_TRUE; - if (!trans.IsUnicast) { - trans.port_first = media->PortNumber; - trans.port_last = media->PortNumber + 1; - trans.TTL = conn ? conn->TTL : 0; - } else { - trans.client_port_first = media->PortNumber; - trans.client_port_last = media->PortNumber + 1; - trans.port_first = s_port_first ? s_port_first : trans.client_port_first; - trans.port_last = s_port_last ? s_port_last : trans.client_port_last; - } - - if (gf_rtp_setup_transport(tmp->rtp_ch, &trans, NULL) != GF_OK) { - RP_DeleteStream(tmp); - return NULL; - } - /*setup depacketizer*/ - tmp->depacketizer = gf_rtp_depacketizer_new(media, rtp_sl_packet_cbk, tmp); - if (!tmp->depacketizer) { - RP_DeleteStream(tmp); - return NULL; - } - /*setup channel*/ - gf_rtp_setup_payload(tmp->rtp_ch, map); - -// tmp->status = NM_Disconnected; - - ctrl = (char *) gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(rtp->service), "Streaming", "DisableRTCP"); - if (!ctrl || stricmp(ctrl, "yes")) tmp->flags |= RTP_ENABLE_RTCP; - - /*setup NAT keep-alive*/ - ctrl = (char *) gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(rtp->service), "Streaming", "NATKeepAlive"); - if (ctrl) gf_rtp_enable_nat_keepalive(tmp->rtp_ch, atoi(ctrl)); - - tmp->range_start = Start; - tmp->range_end = End; - if (End != -1.0) tmp->flags |= RTP_HAS_RANGE; - - if (force_bcast) tmp->flags |= RTP_FORCE_BROADCAST; - - if (is_migration) { - tmp->current_start = (Double) CurrentTime; - tmp->check_rtp_time = RTP_SET_TIME_RTP; - gf_rtp_set_info_rtp(tmp->rtp_ch, rtp_seq, rtp_time, ssrc); - tmp->status = RTP_SessionResume; - } - - if (rvc_predef) { - tmp->depacketizer->sl_map.rvc_predef = rvc_predef ; - } else if (rvc_config_att) { - char *rvc_data=NULL; - u32 rvc_size; - Bool is_gz = GF_FALSE; - if (!strncmp(rvc_config_att, "data:application/rvc-config+xml", 32) && strstr(rvc_config_att, "base64") ) { - char *data = strchr(rvc_config_att, ','); - if (data) { - rvc_size = (u32) strlen(data) * 3 / 4 + 1; - rvc_data = (char*)gf_malloc(sizeof(char) * rvc_size); - rvc_size = gf_base64_decode(data, (u32) strlen(data), rvc_data, rvc_size); - rvc_data[rvc_size] = 0; - } - if (!strncmp(rvc_config_att, "data:application/rvc-config+xml+gz", 35)) is_gz = GF_TRUE; - } else if (!strnicmp(rvc_config_att, "http://", 7) || !strnicmp(rvc_config_att, "https://", 8) ) { - char *mime; - if (gf_dm_get_file_memory(rvc_config_att, &rvc_data, &rvc_size, &mime) == GF_OK) { - if (mime && strstr(mime, "+gz")) is_gz = GF_TRUE; - if (mime) gf_free(mime); - } - } - if (rvc_data) { - if (is_gz) { -#ifdef GPAC_DISABLE_ZLIB - fprintf(stderr, "Error: no zlib support - RVC not supported in RTP\n"); - return NULL; -#endif - gf_gz_decompress_payload(rvc_data, rvc_size, &tmp->depacketizer->sl_map.rvc_config, &tmp->depacketizer->sl_map.rvc_config_size); - gf_free(rvc_data); - } else { - tmp->depacketizer->sl_map.rvc_config = rvc_data; - tmp->depacketizer->sl_map.rvc_config_size = rvc_size; - } - } - } - - return tmp; -} - - -static void RP_ProcessRTP(RTPStream *ch, char *pck, u32 size) -{ - GF_NetworkCommand com; - GF_Err e; - GF_RTPHeader hdr; - u32 PayloadStart; - ch->rtp_bytes += size; - - /*first decode RTP*/ - e = gf_rtp_decode_rtp(ch->rtp_ch, pck, size, &hdr, &PayloadStart); - - /*corrupted or NULL data*/ - if (e || (PayloadStart >= size)) { - //gf_service_send_packet(ch->owner->service, ch->channel, NULL, 0, NULL, GF_CORRUPTED_DATA); - return; - } - - /*if we must notify some timing, do it now. If the channel has no range, this should NEVER be called*/ - if (ch->check_rtp_time /*&& gf_rtp_is_active(ch->rtp_ch)*/) { - Double ch_time; - - /*it may happen that we still receive packets from a previous "play" request. If this is the case, - filter until we reach the indicated rtptime*/ - if (ch->rtp_ch->rtp_time - && (ch->rtp_ch->rtp_first_SN > hdr.SequenceNumber) - && (ch->rtp_ch->rtp_time < hdr.TimeStamp) - ) { - GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTP] Rejecting too early packet (TS %d vs signaled rtp time %d - diff %d ms)\n", - hdr.TimeStamp, ch->rtp_ch->rtp_time, ((hdr.TimeStamp - ch->rtp_ch->rtp_time)*1000) / ch->rtp_ch->TimeScale)); - return; - } - - ch_time = gf_rtp_get_current_time(ch->rtp_ch); - - /*this is the first packet on the channel (no PAUSE)*/ - if (ch->check_rtp_time == RTP_SET_TIME_RTP) { - /*Note: in a SEEK with RTSP, the rtp-info time given by the server is - the rtp time of the desired range. But the server may (and should) send from - the previous I frame on video, so the time of the first rtp packet after - a SEEK can actually be less than CurrentStart. We don't drop these - packets in order to see the maximum video. We could drop it, this would mean - wait for next RAP...*/ - - memset(&com, 0, sizeof(com)); - com.command_type = GF_NET_CHAN_MAP_TIME; - com.base.on_channel = ch->channel; - if (ch->rtsp) { - com.map_time.media_time = ch->current_start + ch_time; - } else { - com.map_time.media_time = 0; - } - - com.map_time.timestamp = hdr.TimeStamp; - com.map_time.reset_buffers = GF_FALSE; - gf_service_command(ch->owner->service, &com, GF_OK); - - GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTP] Mapping RTP Time seq %d TS %d Media Time %g - rtp info seq %d TS %d\n", - hdr.SequenceNumber, hdr.TimeStamp, com.map_time.media_time, ch->rtp_ch->rtp_first_SN, ch->rtp_ch->rtp_time - )); - - /*skip RTCP clock init when RTSP is used*/ - if (ch->rtsp) ch->rtcp_init = GF_TRUE; - -// if (ch->depacketizer->payt==GF_RTP_PAYT_H264_AVC) ch->depacketizer->flags |= GF_RTP_AVC_WAIT_RAP; - } - /*this is RESUME on channel, filter packet based on time (darwin seems to send - couple of packet before) - do not fetch if we're below 10 ms or <0, because this means we already have - this packet - as the PAUSE is issued with the RTP currentTime*/ - else if (ch_time <= 0.021) { - return; - } - ch->check_rtp_time = RTP_SET_TIME_NONE; - } - - gf_rtp_depacketizer_process(ch->depacketizer, &hdr, pck + PayloadStart, size - PayloadStart); - - /*last check: signal EOS if we're close to end range in case the server do not send RTCP BYE*/ - if ((ch->flags & RTP_HAS_RANGE) && !(ch->flags & RTP_EOS) ) { - /*also check last CTS*/ - Double ts = (Double) ((u32) ch->depacketizer->sl_hdr.compositionTimeStamp - hdr.TimeStamp); - ts /= gf_rtp_get_clockrate(ch->rtp_ch); - if (ABSDIFF(ch->range_end, (ts + ch->current_start + gf_rtp_get_current_time(ch->rtp_ch)) ) < 0.2) { - ch->flags |= RTP_EOS; - ch->stat_stop_time = gf_sys_clock(); - gf_service_send_packet(ch->owner->service, ch->channel, NULL, 0, NULL, GF_EOS); - } - } -} - -void RP_ProcessRTCP(RTPStream *ch, char *pck, u32 size) -{ - Bool has_sr; - GF_Err e; - - if (ch->status == RTP_Connected) return; - - ch->rtcp_bytes += size; - - e = gf_rtp_decode_rtcp(ch->rtp_ch, pck, size, &has_sr); - if (e<0) return; - - /*update sync if on pure RTP*/ - if (!ch->rtcp_init && has_sr) { - Double ntp_clock; - - ntp_clock = ch->rtp_ch->last_SR_NTP_sec; - ntp_clock += ((Double)ch->rtp_ch->last_SR_NTP_frac)/0xFFFFFFFF; - - if (!ch->owner->last_ntp) { - //add safety in case this RTCP report is received before another report - //that was supposed to come in earlier (with earlier NTP) - //Double safety_offset, time = ch->rtp_ch->last_SR_rtp_time; - //time /= ch->rtp_ch->TimeScale; - //safety_offset = time/2; - ch->owner->last_ntp = ntp_clock; - } - - if (ntp_clock >= ch->owner->last_ntp) { - ntp_clock -= ch->owner->last_ntp; - } else { - ntp_clock = 0; - } - - //assert(ch->rtp_ch->last_SR_rtp_time >= (u64) (ntp_clock * ch->rtp_ch->TimeScale)); - ch->ts_offset = ch->rtp_ch->last_SR_rtp_time; - ch->ts_offset -= (s64) (ntp_clock * ch->rtp_ch->TimeScale); - - - GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTCP] At %d Using Sender Report to map RTP TS %d to NTP clock %g - new TS offset "LLD" \n", - gf_sys_clock(), ch->rtp_ch->last_SR_rtp_time, ntp_clock, ch->ts_offset - )); - - ch->rtcp_init = GF_TRUE; - ch->check_rtp_time = RTP_SET_TIME_NONE; - } - - if (e == GF_EOS) { - ch->flags |= RTP_EOS; - ch->stat_stop_time = gf_sys_clock(); - gf_service_send_packet(ch->owner->service, ch->channel, NULL, 0, NULL, GF_EOS); - } -} - -GF_Err RP_DataOnTCP(GF_RTSPSession *sess, void *cbk, char *buffer, u32 bufferSize, Bool IsRTCP) -{ - RTPStream *ch = (RTPStream *) cbk; - if (!ch) return GF_OK; - if (IsRTCP) { - RP_ProcessRTCP(ch, buffer, bufferSize); - } else { - RP_ProcessRTP(ch, buffer, bufferSize); - } - return GF_OK; -} - - -static GF_Err SendTCPData(void *par, char *pck, u32 pck_size) -{ - return GF_OK; -} - - -void RP_ReadStream(RTPStream *ch) -{ - u32 size, tot_size; - - if (!ch->rtp_ch) return; - - /*NOTE: A weird bug on windows wrt to select(): if both RTP and RTCP are in the same loop - there is a hudge packet drop on RTP. We therefore split RTP and RTCP reading, this is not a big - deal as the RTCP traffic is far less than RTP, and we should never have more than one RTCP - packet reading per RTP reading loop - NOTE2: a better implementation would be to use select() to get woken up... - */ - tot_size = 0; - - while (1) { - size = gf_rtp_read_rtcp(ch->rtp_ch, ch->buffer, RTP_BUFFER_SIZE); - if (!size) break; - tot_size += size; - RP_ProcessRTCP(ch, ch->buffer, size); - } - - while (1) { - size = gf_rtp_read_rtp(ch->rtp_ch, ch->buffer, RTP_BUFFER_SIZE); - if (!size) break; - tot_size += size; - RP_ProcessRTP(ch, ch->buffer, size); - } - /*and send the report*/ - if (ch->flags & RTP_ENABLE_RTCP) gf_rtp_send_rtcp_report(ch->rtp_ch, SendTCPData, ch); - - if (tot_size) ch->owner->udp_time_out = 0; - - /*detect timeout*/ - if (ch->owner->udp_time_out) { - if (!ch->last_udp_time) { - ch->last_udp_time = gf_sys_clock(); - } else if (ch->rtp_ch->net_info.IsUnicast && !(ch->flags & RTP_MOBILEIP) ) { - u32 diff = gf_sys_clock() - ch->last_udp_time; - if (diff >= ch->owner->udp_time_out) { - char szMessage[1024]; - GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTP] UDP Timeout after %d ms\n", diff)); - sprintf(szMessage, "No data received in %d ms", diff); - RP_SendMessage(ch->owner->service, GF_IP_UDP_TIMEOUT, szMessage); - ch->status = RTP_Unavailable; - } - } - } -} - -#endif /*GPAC_DISABLE_STREAMING*/ diff --git a/modules/rtp_in/sdp_fetch.c b/modules/rtp_in/sdp_fetch.c deleted file mode 100644 index 27c5097..0000000 --- a/modules/rtp_in/sdp_fetch.c +++ /dev/null @@ -1,186 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / RTP input module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "rtp_in.h" - -#ifndef GPAC_DISABLE_STREAMING - - -void RP_SDPFromData(RTPClient *rtp, char *s_url, RTPStream *stream) -{ - char *url; - char buf[2000]; - u32 size; - - url = strstr(s_url, ","); - if (!url) { - gf_service_connect_ack(rtp->service, NULL, GF_URL_ERROR); - return; - } - url += 1; - if (strstr(url, ";base64")) { - //decode - size = gf_base64_decode(url, (u32) strlen(url), buf, 2000); - buf[size] = 0; - url = buf; - } - RP_LoadSDP(rtp, url, (u32) strlen(url), stream); -} - -void RP_SDPFromFile(RTPClient *rtp, char *file_name, RTPStream *stream) -{ - FILE *_sdp; - char *sdp_buf; - u32 sdp_size; - - sdp_buf = NULL; - - if (file_name && strstr(file_name, "file://")) file_name += strlen("file://"); - if (!file_name || !(_sdp = gf_fopen(file_name, "rt")) ) { - gf_service_connect_ack(rtp->service, NULL, GF_URL_ERROR); - return; - } - - gf_fseek(_sdp, 0, SEEK_END); - sdp_size = (u32) gf_ftell(_sdp); - gf_fseek(_sdp, 0, SEEK_SET); - sdp_buf = (char*)gf_malloc(sdp_size); - if (1 > fread(sdp_buf, 1, sdp_size, _sdp)) { - gf_service_connect_ack(rtp->service, NULL, GF_URL_ERROR); - } else { - RP_LoadSDP(rtp, sdp_buf, sdp_size, stream); - } - gf_fclose(_sdp); - gf_free(sdp_buf); -} - -void SDP_NetIO(void *cbk, GF_NETIO_Parameter *param) -{ - GF_Err e; - RTPClient *rtp = (RTPClient *)cbk; - SDPFetch *sdp = rtp->sdp_temp; - - gf_service_download_update_stats(rtp->dnload); - - e = param->error; - switch (param->msg_type) { - case GF_NETIO_GET_METHOD: - if (sdp->original_url) - param->name = "POST"; - return; - case GF_NETIO_GET_CONTENT: - if (sdp->original_url) { - char szBody[4096], *opt; - opt = (char *) gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(rtp->service), "Network", "MobileIP"); - sprintf(szBody, "ipadd\n%s\n\nurl\n%s\n\n", opt, sdp->original_url); - param->data = szBody; - param->size = (u32) strlen(szBody); - } - return; - case GF_NETIO_DATA_TRANSFERED: - if (sdp->original_url) { - u32 sdp_size; - e = gf_dm_sess_get_stats(rtp->dnload, NULL, NULL, &sdp_size, NULL, NULL, NULL); - if (sdp_size) { - const char *szFile = gf_dm_sess_get_cache_name(rtp->dnload); - if (!szFile) { - e = GF_SERVICE_ERROR; - } else { - RP_SDPFromFile(rtp, (char *) szFile, sdp->chan); - gf_free(sdp->remote_url); - if (sdp->original_url) gf_free(sdp->original_url); - gf_free(sdp); - rtp->sdp_temp = NULL; - return; - } - } - } - break; - default: - if (e == GF_OK) return; - } - - if (sdp->original_url) { - char *url = sdp->original_url; - gf_free(sdp->remote_url); - gf_free(sdp); - rtp->sdp_temp = NULL; - RP_SendMessage(rtp->service, e, "Error fetching session state - restarting"); - RP_ConnectServiceEx(gf_service_get_interface(rtp->service), rtp->service, url, GF_TRUE); - gf_free(url); - return; - } - - /*error*/ - if (sdp->chan) { - gf_service_connect_ack(rtp->service, sdp->chan->channel, e); - } else { - gf_service_connect_ack(rtp->service, NULL, e); - rtp->sdp_temp = NULL; - } - gf_free(sdp->remote_url); - if (sdp->original_url) gf_free(sdp->original_url); - gf_free(sdp); - rtp->sdp_temp = NULL; -} - -void RP_FetchSDP(RTPClient *rtp, char *url, RTPStream *stream, char *original_url) -{ - SDPFetch *sdp; - /*if local URL get file*/ - if (strstr(url, "data:application/sdp")) { - RP_SDPFromData(rtp, url, stream); - return; - } - if (!strnicmp(url, "file://", 7) || !strstr(url, "://")) { - RP_SDPFromFile(rtp, url, stream); - return; - } - - sdp = (SDPFetch*)gf_malloc(sizeof(SDPFetch)); - memset(sdp, 0, sizeof(SDPFetch)); - sdp->client = rtp; - sdp->remote_url = gf_strdup(url); - sdp->chan = stream; - if (original_url) { - sdp->original_url = gf_strdup(original_url); - } - - /*otherwise setup download*/ - if (rtp->dnload) gf_service_download_del(rtp->dnload); - rtp->dnload = NULL; - - rtp->sdp_temp = sdp; - rtp->dnload = gf_service_download_new(rtp->service, url, 0, SDP_NetIO, rtp); - if (!rtp->dnload) { - gf_service_connect_ack(rtp->service, NULL, GF_NOT_SUPPORTED); - } else { - /*start our download (threaded)*/ - gf_dm_sess_process(rtp->dnload); - } - /*service confirm is done once fetched*/ -} - -#endif /*GPAC_DISABLE_STREAMING*/ diff --git a/modules/rtp_in/sdp_load.c b/modules/rtp_in/sdp_load.c deleted file mode 100644 index f25a9fd..0000000 --- a/modules/rtp_in/sdp_load.c +++ /dev/null @@ -1,585 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / RTP input module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "rtp_in.h" -#include <gpac/internal/ietf_dev.h> - -#ifndef GPAC_DISABLE_STREAMING - -GF_Err RP_SetupSDP(RTPClient *rtp, GF_SDPInfo *sdp, RTPStream *stream) -{ - GF_Err e; - GF_SDPMedia *media; - Double Start, End; - u32 i; - char *sess_ctrl, *session_id, *url; - GF_X_Attribute *att; - GF_RTSPRange *range; - RTPStream *ch; - RTSPSession *migrate_sess = NULL; - - Start = 0.0; - End = -1.0; - - sess_ctrl = NULL; - range = NULL; - session_id = url = NULL; - - i=0; - while ((att = (GF_X_Attribute*)gf_list_enum(sdp->Attributes, &i))) { - //session-level control string. Keep it in the current session if any - if (!strcmp(att->Name, "control") && att->Value) sess_ctrl = att->Value; - //NPT range only for now - else if (!strcmp(att->Name, "range") && !range) range = gf_rtsp_range_parse(att->Value); - /*session migration*/ - else if (!strcmp(att->Name, "x-session-name")) url = att->Value; - else if (!strcmp(att->Name, "x-session-id")) session_id = att->Value; - /*we have the H264-SVC streams*/ - else if (!strcmp(att->Name, "group") && !strncmp(att->Value, "DDP", 3)) rtp->is_scalable = GF_TRUE; - } - if (range) { - Start = range->start; - End = range->end; - gf_rtsp_range_del(range); - } - - if (url) { - migrate_sess = RP_NewSession(rtp, url); - if (migrate_sess && session_id) { - migrate_sess->session_id = gf_strdup(session_id); - } - sess_ctrl = url; - } - - //setup all streams - i=0; - while ((media = (GF_SDPMedia*)gf_list_enum(sdp->media_desc, &i))) { - ch = RP_NewStream(rtp, media, sdp, stream); - //do not generate error if the channel is not created, just assume - //1 - this is not an MPEG-4 configured channel -> not needed - //2 - this is a 2nd describe and the channel was already created - if (!ch) continue; - - e = RP_AddStream(rtp, ch, sess_ctrl); - if (e) { - RP_DeleteStream(ch); - return e; - } - - if (!(ch->flags & RTP_HAS_RANGE)) { - ch->range_start = Start; - ch->range_end = End; - if (End > 0) ch->flags |= RTP_HAS_RANGE; - } - - /*force interleaving whenever needed*/ - if (ch->rtsp) { - switch (ch->depacketizer->sl_map.StreamType) { - case GF_STREAM_VISUAL: - case GF_STREAM_AUDIO: - if ((rtp->transport_mode==1) && ! (ch->rtsp->flags & RTSP_FORCE_INTER) ) { - gf_rtsp_set_buffer_size(ch->rtsp->session, RTSP_TCP_BUFFER_SIZE); - ch->rtsp->flags |= RTSP_FORCE_INTER; - } - break; - default: - if (rtp->transport_mode && ! (ch->rtsp->flags & RTSP_FORCE_INTER) ) { - gf_rtsp_set_buffer_size(ch->rtsp->session, RTSP_TCP_BUFFER_SIZE); - ch->rtsp->flags |= RTSP_FORCE_INTER; - } - break; - } - } - - } - return GF_OK; -} - -/*load iod from data:application/mpeg4-iod;base64*/ -GF_Err RP_SDPLoadIOD(RTPClient *rtp, char *iod_str) -{ - char buf[2000]; - u32 size; - - if (rtp->session_desc) return GF_SERVICE_ERROR; - /*the only IOD format we support*/ - iod_str += 1; - if (!strnicmp(iod_str, "data:application/mpeg4-iod;base64", strlen("data:application/mpeg4-iod;base64"))) { - char *buf64; - u32 size64; - - buf64 = strstr(iod_str, ","); - if (!buf64) return GF_URL_ERROR; - buf64 += 1; - size64 = (u32) strlen(buf64) - 1; - - size = gf_base64_decode(buf64, size64, buf, 2000); - if (!size) return GF_SERVICE_ERROR; - } else if (!strnicmp(iod_str, "data:application/mpeg4-iod;base16", strlen("data:application/mpeg4-iod;base16"))) { - char *buf16; - u32 size16; - - buf16 = strstr(iod_str, ","); - if (!buf16) return GF_URL_ERROR; - buf16 += 1; - size16 = (u32) strlen(buf16) - 1; - - size = gf_base16_decode(buf16, size16, buf, 2000); - if (!size) return GF_SERVICE_ERROR; - } else { - return GF_NOT_SUPPORTED; - } - - gf_odf_desc_read(buf, size, &rtp->session_desc); - return GF_OK; -} - - -static u32 get_stream_type_from_hint(u32 ht) -{ - switch (ht) { - case GF_MEDIA_OBJECT_VIDEO: - return GF_STREAM_VISUAL; - case GF_MEDIA_OBJECT_AUDIO: - return GF_STREAM_AUDIO; - case GF_MEDIA_OBJECT_TEXT: - return GF_STREAM_TEXT; - default: - return 0; - } -} - -static GF_ESD *RP_GetChannelESD(RTPStream *ch, u32 ch_idx) -{ - GF_ESD *esd; - - if (!ch->ES_ID) ch->ES_ID = ch_idx + 1; - if (!ch->depacketizer) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("RTP Stream channel %u has no depacketizer\n", ch_idx)); - return NULL; - } - - esd = gf_odf_desc_esd_new(0); - esd->slConfig->timestampResolution = gf_rtp_get_clockrate(ch->rtp_ch); - esd->slConfig->useRandomAccessPointFlag = 1; - esd->slConfig->useTimestampsFlag = 1; - esd->slConfig->no_dts_signaling = ch->depacketizer->sl_map.DTSDeltaLength ? GF_FALSE : GF_TRUE; - esd->ESID = ch->ES_ID; - esd->OCRESID = 0; - if (ch->mid) - esd->has_scalable_layers = GF_TRUE; - - esd->decoderConfig->streamType = ch->depacketizer->sl_map.StreamType; - esd->decoderConfig->objectTypeIndication = ch->depacketizer->sl_map.ObjectTypeIndication; - if (ch->depacketizer->sl_map.config) { - esd->decoderConfig->decoderSpecificInfo->data = (char*)gf_malloc(sizeof(char) * ch->depacketizer->sl_map.configSize); - memcpy(esd->decoderConfig->decoderSpecificInfo->data, ch->depacketizer->sl_map.config, sizeof(char) * ch->depacketizer->sl_map.configSize); - esd->decoderConfig->decoderSpecificInfo->dataLength = ch->depacketizer->sl_map.configSize; - } - if (ch->depacketizer->sl_map.rvc_predef) { - esd->decoderConfig->predefined_rvc_config = ch->depacketizer->sl_map.rvc_predef; - } else if (ch->depacketizer->sl_map.rvc_config) { - esd->decoderConfig->rvc_config = (GF_DefaultDescriptor *) gf_odf_desc_new(GF_ODF_DSI_TAG); - esd->decoderConfig->rvc_config->data = ch->depacketizer->sl_map.rvc_config; - esd->decoderConfig->rvc_config->dataLength = ch->depacketizer->sl_map.rvc_config_size; - ch->depacketizer->sl_map.rvc_config = NULL; - ch->depacketizer->sl_map.rvc_config_size = 0; - } - - return esd; -} - -static GF_ObjectDescriptor *RP_GetChannelOD(RTPStream *ch, u32 ch_idx) -{ - GF_ESD *esd; - GF_ObjectDescriptor *od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - - esd = RP_GetChannelESD(ch, ch_idx); - - od->objectDescriptorID = ch->OD_ID ? ch->OD_ID : ch->ES_ID; - - gf_list_add(od->ESDescriptors, esd); - - // for each channel depending on this channel, get esd, set esd->dependsOnESID and add to od - if (ch->owner->is_scalable) { - u32 i, count; - - count = gf_list_count(ch->owner->channels); - for (i = 0; i < count; i++) { - RTPStream *the_channel; - GF_ESD *the_esd; - - the_channel = (RTPStream *)gf_list_get(ch->owner->channels, i); - if (the_channel->base_stream == ch->mid) { - the_esd = RP_GetChannelESD(the_channel, i); - the_esd->dependsOnESID = the_channel->prev_stream; - gf_list_add(od->ESDescriptors, the_esd); - } - } - } - - return od; -} - -GF_Descriptor *RP_EmulateIOD(RTPClient *rtp, const char *sub_url) -{ - GF_ObjectDescriptor *the_od; - RTPStream *a_str, *ch; - u32 i; - - if (rtp->media_type==GF_MEDIA_OBJECT_INTERACT) return NULL; - if (rtp->media_type==GF_MEDIA_OBJECT_UPDATES) return NULL; - - /*single object generation*/ - a_str = NULL; - if (sub_url || ((rtp->media_type != GF_MEDIA_OBJECT_SCENE) && (rtp->media_type != GF_MEDIA_OBJECT_UNDEF)) ) { - i=0; - while ((ch = (RTPStream *)gf_list_enum(rtp->channels, &i))) { - if (ch->depacketizer->sl_map.StreamType != get_stream_type_from_hint(rtp->media_type)) continue; - - if (!sub_url || (ch->control && strstr(sub_url, ch->control)) ) { - the_od = RP_GetChannelOD(ch, i-1); - if (!the_od) continue; - return (GF_Descriptor *) the_od; - } - if (!a_str) a_str = ch; - } - if (a_str) { - the_od = RP_GetChannelOD(a_str, gf_list_find(rtp->channels, a_str) ); - return (GF_Descriptor *) the_od; - } - return NULL; - } - return NULL; -} - - -void RP_SetupObjects(RTPClient *rtp) -{ - GF_ObjectDescriptor *od; - RTPStream *ch; - u32 i; - - /*add everything*/ - i=0; - while ((ch = (RTPStream *)gf_list_enum(rtp->channels, &i))) { - if (ch->control && !strnicmp(ch->control, "data:", 5)) continue; - if (ch->prev_stream) continue; - - if (!rtp->media_type) { - od = RP_GetChannelOD(ch, i); - if (!od) continue; - gf_service_declare_media(rtp->service, (GF_Descriptor*)od, GF_TRUE); - } else if (rtp->media_type==ch->depacketizer->sl_map.StreamType) { - od = RP_GetChannelOD(ch, i); - if (!od) continue; - gf_service_declare_media(rtp->service, (GF_Descriptor*)od, GF_TRUE); - rtp->media_type = 0; - break; - } - } - gf_service_declare_media(rtp->service, NULL, GF_FALSE); -} - -void RP_LoadSDP(RTPClient *rtp, char *sdp_text, u32 sdp_len, RTPStream *stream) -{ - GF_Err e; - u32 i; - GF_SDPInfo *sdp; - Bool is_isma_1, has_iod; - char *iod_str; - GF_X_Attribute *att; - - is_isma_1 = GF_FALSE; - iod_str = NULL; - sdp = gf_sdp_info_new(); - e = gf_sdp_info_parse(sdp, sdp_text, sdp_len); - - if (e == GF_OK) e = RP_SetupSDP(rtp, sdp, stream); - - /*root SDP, attach service*/ - if (!stream) { - /*look for IOD*/ - if (e==GF_OK) { - i=0; - while ((att = (GF_X_Attribute*)gf_list_enum(sdp->Attributes, &i))) { - if (!iod_str && !strcmp(att->Name, "mpeg4-iod") ) iod_str = att->Value; - if (!is_isma_1 && !strcmp(att->Name, "isma-compliance") ) { - if (!stricmp(att->Value, "1,1.0,1")) is_isma_1 = GF_TRUE; - } - } - - /*force iod reconstruction with ISMA to use proper clock dependencies*/ - if (is_isma_1) iod_str = NULL; - - /*some folks have weird notions of MPEG-4 systems, they use hardcoded IOD - with AAC ESD even when streaming AMR...*/ - if (iod_str) { - RTPStream *ch; - i=0; - while ((ch = (RTPStream *)gf_list_enum(rtp->channels, &i))) { - if ((ch->depacketizer->payt==GF_RTP_PAYT_AMR) || (ch->depacketizer->payt==GF_RTP_PAYT_AMR_WB) ) { - iod_str = NULL; - break; - } - } - } - if (!iod_str) { - RTPStream *ch; - Bool needs_iod = GF_FALSE; - i=0; - while ((ch = (RTPStream *)gf_list_enum(rtp->channels, &i))) { - if (ch->depacketizer && (ch->depacketizer->payt==GF_RTP_PAYT_MPEG4) && (ch->depacketizer->sl_map.StreamType==GF_STREAM_SCENE) -// || ((ch->depacketizer->payt==GF_RTP_PAYT_3GPP_DIMS) && (ch->depacketizer->sl_map.StreamType==GF_STREAM_SCENE)) - ) { - needs_iod = GF_TRUE; - break; - } - } - if (needs_iod) { - rtp->session_desc = (GF_Descriptor *)RP_GetChannelOD(ch, 0); - } - } - - if (iod_str) e = RP_SDPLoadIOD(rtp, iod_str); - } - /*attach service*/ - has_iod = rtp->session_desc ? GF_TRUE : GF_FALSE; - gf_service_connect_ack(rtp->service, NULL, e); - if (!e && !has_iod && !rtp->media_type) RP_SetupObjects(rtp); - rtp->media_type = 0; - } - /*channel SDP */ - else { - if (e) { - gf_service_connect_ack(rtp->service, stream->channel, e); - stream->status = RTP_Unavailable; - } else { - /*connect*/ - RP_SetupChannel(stream, NULL); - } - } - /*store SDP for later session migration*/ - if (sdp) { - char *buf=NULL; - gf_sdp_info_write(sdp, &buf); - if (buf) { - rtp->session_state_data = (char*)gf_malloc(sizeof(char) * (strlen("data:application/sdp,") + strlen(buf) + 1) ); - strcpy(rtp->session_state_data, "data:application/sdp,"); - strcat(rtp->session_state_data, buf); - gf_free(buf); - } - gf_sdp_info_del(sdp); - } -} - -void MigrateSDP_NetIO(void *cbk, GF_NETIO_Parameter *param) -{ - RTPClient *rtp = (RTPClient *)cbk; - - switch (param->msg_type) { - case GF_NETIO_GET_METHOD: - param->name = "POST"; - return; - case GF_NETIO_GET_HEADER: - if (!strcmp(param->name, "POST")) { - param->name = "Content-Type"; - param->value = "application/sdp"; - } - return; - - case GF_NETIO_GET_CONTENT: - param->data = rtp->session_state_data + strlen("data:application/sdp,"); - param->size = (u32) strlen(param->data); - return; - default: - return; - } -} - - -void RP_SaveSessionState(RTPClient *rtp) -{ - GF_Err e; - char *sdp_buf; - const char *opt; - GF_X_Attribute*att; - u32 i, j; - GF_SDPInfo *sdp; - RTSPSession *sess = NULL; - - if (!rtp->session_state_data) return; - - sdp_buf = rtp->session_state_data + strlen("data:application/sdp,"); - sdp = gf_sdp_info_new(); - e = gf_sdp_info_parse(sdp, sdp_buf, (u32) strlen(sdp_buf) ); - - for (i=0; i<gf_list_count(rtp->channels); i++) { - GF_SDPMedia *media = NULL; - RTPStream *ch = (RTPStream*)gf_list_get(rtp->channels, i); - if (!ch->control) continue; - - for (j=0; j<gf_list_count(sdp->media_desc); j++) { - u32 k; - GF_SDPMedia *med = (GF_SDPMedia*)gf_list_get(sdp->media_desc, j); - - for (k=0; k<gf_list_count(med->Attributes); k++) { - att = (GF_X_Attribute*)gf_list_get(med->Attributes, k); - if (!stricmp(att->Name, "control") && (strstr(att->Value, ch->control)!=NULL) ) { - media = med; - break; - } - } - if (media) - break; - } - if (!media) continue; - - if (ch->rtp_ch->net_info.IsUnicast) { - char szPorts[4096]; - u16 porta, portb; - media->PortNumber = ch->rtp_ch->net_info.client_port_first; - - /*remove x-server-port extension*/ - for (j=0; j<gf_list_count(media->Attributes); j++) { - att = (GF_X_Attribute*)gf_list_get(media->Attributes, j); - if (!stricmp(att->Name, "x-stream-state") ) { - gf_free(att->Name); - gf_free(att->Value); - gf_free(att); - gf_list_rem(media->Attributes, j); - } - } - ch->current_start += gf_rtp_get_current_time(ch->rtp_ch); - - GF_SAFEALLOC(att, GF_X_Attribute); - if (!att) { - GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTP] Failed to save stream state for channel\n")); - continue; - } - att->Name = gf_strdup("x-stream-state"); - porta = ch->rtp_ch->net_info.port_first ? ch->rtp_ch->net_info.port_first : ch->rtp_ch->net_info.client_port_first; - portb = ch->rtp_ch->net_info.port_last ? ch->rtp_ch->net_info.port_last : ch->rtp_ch->net_info.client_port_last; - - sprintf(szPorts, "server-port=%d-%d;ssrc=%X;npt=%g;seq=%d;rtptime=%d", - porta, - portb, - ch->rtp_ch->SenderSSRC, - ch->current_start, - ch->rtp_ch->rtp_first_SN, - ch->rtp_ch->rtp_time - ); - att->Value = gf_strdup(szPorts); - gf_list_add(media->Attributes, att); - - if (ch->rtsp) - sess = ch->rtsp; - } else { - media->PortNumber = ch->rtp_ch->net_info.port_first; - } - - } - /*remove x-server-port/x-session-id extension*/ - for (j=0; j<gf_list_count(sdp->Attributes); j++) { - att = (GF_X_Attribute*)gf_list_get(sdp->Attributes, j); - if (!stricmp(att->Name, "x-session-id") || !stricmp(att->Name, "x-session-name") - ) { - gf_free(att->Name); - gf_free(att->Value); - gf_free(att); - gf_list_rem(sdp->Attributes, j); - } - } - if (sess) { - char szURL[4096]; - - if (sess->session_id) { - GF_SAFEALLOC(att, GF_X_Attribute); - if (!att) { - GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTP] Failed to save session ID\n")); - } else { - att->Name = gf_strdup("x-session-id"); - att->Value = gf_strdup(sess->session_id); - gf_list_add(sdp->Attributes, att); - } - } - - GF_SAFEALLOC(att, GF_X_Attribute); - if (!att) { - GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTP] Failed to save session name\n")); - } else { - att->Name = gf_strdup("x-session-name"); - sprintf(szURL, "rtsp://%s:%d/%s", sess->session->Server, sess->session->Port, sess->session->Service); - att->Value = gf_strdup(szURL); - gf_list_add(sdp->Attributes, att); - } - } - - gf_free(rtp->session_state_data); - sdp_buf = NULL; - gf_sdp_info_write(sdp, &sdp_buf); - if (sdp_buf) { - rtp->session_state_data = (char*)gf_malloc(sizeof(char) * (strlen("data:application/sdp,") + strlen(sdp_buf) + 1) ); - strcpy(rtp->session_state_data, "data:application/sdp,"); - strcat(rtp->session_state_data, sdp_buf); - gf_free(sdp_buf); - } - - - gf_sdp_info_del(sdp); - - - opt = (char *) gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(rtp->service), "Streaming", "SessionMigrationServer"); - if (opt) { - if (rtp->dnload) gf_service_download_del(rtp->dnload); - rtp->dnload = NULL; - - if (strnicmp(opt, "http://", 7)) { - rtp->dnload = gf_service_download_new(rtp->service, opt, GF_NETIO_SESSION_NOT_THREADED, MigrateSDP_NetIO, rtp); - while (1) { - char buffer[100]; - u32 read; - e = gf_dm_sess_fetch_data(rtp->dnload, buffer, 100, &read); - if (e && (e!=GF_IP_NETWORK_EMPTY)) break; - } - gf_service_download_del(rtp->dnload); - rtp->dnload = NULL; - } else { - FILE *f = gf_fopen(opt, "wt"); - if (f) { - sdp_buf = rtp->session_state_data + strlen("data:application/sdp,"); - gf_fwrite(sdp_buf, 1, strlen(sdp_buf), f); - gf_fclose(f); - } else { - e = GF_IO_ERR; - } - } - if (sess && sess->owner && (e<0)) { - RP_SendMessage(sess->owner->service, e, "Error saving session state"); - } - } -} - -#endif /*GPAC_DISABLE_STREAMING*/ diff --git a/modules/saf_in/Makefile b/modules/saf_in/Makefile deleted file mode 100644 index 2a781f1..0000000 --- a/modules/saf_in/Makefile +++ /dev/null @@ -1,51 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/saf_in - -CFLAGS= $(OPTFLAGS) -w -I"$(SRC_PATH)/include" - - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -DDEBUG -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS=saf_in.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_saf_in$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols saf_in.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_dummy_in-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac_static $(LDFLAGS) -endif - - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/saf_in/saf_in.c b/modules/saf_in/saf_in.c deleted file mode 100644 index 02bc6cd..0000000 --- a/modules/saf_in/saf_in.c +++ /dev/null @@ -1,645 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2012 - * All rights reserved - * - * This file is part of GPAC / SAF reader module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include <gpac/modules/service.h> -#include <gpac/modules/codec.h> -#include <gpac/constants.h> -#include <gpac/thread.h> - -typedef struct -{ - LPNETCHANNEL ch; - u32 au_sn, stream_id, ts_res, buffer_min; - GF_ESD *esd; -} SAFChannel; - -enum -{ - SAF_FILE_LOCAL, - SAF_FILE_REMOTE, - SAF_LIVE_STREAM -}; - -typedef struct -{ - GF_ClientService *service; - GF_List *channels; - Bool needs_connection; - - u32 saf_type; - - /*file downloader*/ - GF_DownloadSession * dnload; - - /*SAF buffer for both lcoal, remote and live streams*/ - char *saf_data; - u32 saf_size, alloc_size; - - /*local file playing*/ - GF_Thread *th; - FILE *stream; - u32 run_state; - u32 start_range, end_range; - Double duration; - u32 nb_playing; -} SAFIn; - -static GFINLINE SAFChannel *saf_get_channel(SAFIn *saf, u32 stream_id, LPNETCHANNEL a_ch) -{ - SAFChannel *ch; - u32 i=0; - while ((ch = (SAFChannel *)gf_list_enum(saf->channels, &i))) { - if (ch->stream_id==stream_id) return ch; - if (a_ch && (ch->ch==a_ch)) return ch; - } - return NULL; -} - -static const char * SAF_MIME = "application/x-saf"; - -static const char * SAF_MIME_EXT = "saf lsr"; - -static const char * SAF_MIME_DESC = "SAF Rich Media"; - -static u32 SAF_RegisterMimeTypes(const GF_InputService *plug) -{ - if (!plug) - return 0; - gf_service_register_mime(plug, SAF_MIME, SAF_MIME_EXT, SAF_MIME_DESC); - return 1; -} - -static Bool SAF_CanHandleURL(GF_InputService *plug, const char *url) -{ - char *sExt; - if (!plug || !url) - return GF_FALSE; - sExt = strrchr(url, '.'); - if (gf_service_check_mime_register(plug, SAF_MIME, SAF_MIME_EXT, SAF_MIME_DESC, sExt)) return GF_TRUE; - return GF_FALSE; -} - -static void SAF_Regulate(SAFIn *read) -{ - GF_NetworkCommand com; - SAFChannel *ch; - - com.command_type = GF_NET_CHAN_BUFFER_QUERY; - /*sleep untill the buffer occupancy is too low - note that this work because all streams in this - demuxer are synchronized*/ - while (read->run_state) { - u32 min_occ = (u32) -1; - u32 i=0; - while ( (ch = (SAFChannel *)gf_list_enum(read->channels, &i))) { - com.base.on_channel = ch->ch; - gf_service_command(read->service, &com, GF_OK); - if (com.buffer.occupancy < ch->buffer_min) return; - if (com.buffer.occupancy) min_occ = MIN(min_occ, com.buffer.occupancy - ch->buffer_min); - } - if (min_occ == (u32) -1) break; - GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[SAF] Regulating SAF demux - sleeping for %d ms\n", min_occ)); - gf_sleep(min_occ); - } -} - -static void SAF_NetIO(void *cbk, GF_NETIO_Parameter *param) -{ - GF_Err e; - Bool is_rap, go; - SAFChannel *ch; - u32 cts, au_sn, au_size, type, i, stream_id; - u64 bs_pos; - GF_BitStream *bs; - GF_SLHeader sl_hdr; - - SAFIn *read = (SAFIn *) cbk; - - e = param->error; - /*done*/ - if (param->msg_type==GF_NETIO_DATA_TRANSFERED) { - if (read->stream && (read->saf_type==SAF_FILE_REMOTE)) read->saf_type = SAF_FILE_LOCAL; - return; - } else { - /*handle service message*/ - gf_service_download_update_stats(read->dnload); - if (param->msg_type!=GF_NETIO_DATA_EXCHANGE) { - if (e<0) { - if (read->needs_connection) { - read->needs_connection = GF_FALSE; - gf_service_connect_ack(read->service, NULL, e); - } - return; - } - if (read->needs_connection) { - u32 total_size; - gf_dm_sess_get_stats(read->dnload, NULL, NULL, &total_size, NULL, NULL, NULL); - if (!total_size) read->saf_type = SAF_LIVE_STREAM; - } - return; - } - } - if (!param->size) return; - - if (!read->run_state) return; - - if (read->alloc_size < read->saf_size + param->size) { - read->saf_data = (char*)gf_realloc(read->saf_data, sizeof(char)*(read->saf_size + param->size) ); - read->alloc_size = read->saf_size + param->size; - } - memcpy(read->saf_data + read->saf_size, param->data, sizeof(char)*param->size); - read->saf_size += param->size; - - /*first AU not complete yet*/ - if (read->saf_size<10) return; - - bs = gf_bs_new(read->saf_data, read->saf_size, GF_BITSTREAM_READ); - bs_pos = 0; - - go = GF_TRUE; - while (go) { - u64 avail = gf_bs_available(bs); - bs_pos = gf_bs_get_position(bs); - - if (avail<10) break; - - is_rap = (Bool)gf_bs_read_int(bs, 1); - au_sn = gf_bs_read_int(bs, 15); - gf_bs_read_int(bs, 2); - cts = gf_bs_read_int(bs, 30); - au_size = gf_bs_read_int(bs, 16); - avail-=8; - - if (au_size > avail) break; - assert(au_size>=2); - - type = gf_bs_read_int(bs, 4); - stream_id = gf_bs_read_int(bs, 12); - au_size -= 2; - - ch = saf_get_channel(read, stream_id, NULL); - switch (type) { - case 1: - case 2: - case 7: - if (ch) { - gf_bs_skip_bytes(bs, au_size); - } else { - SAFChannel *first = (SAFChannel *)gf_list_get(read->channels, 0); - GF_SAFEALLOC(ch, SAFChannel); - if (!ch) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[SAF] Failed to allocate SAF channel")); - gf_bs_del(bs); - return; - } - ch->stream_id = stream_id; - ch->esd = gf_odf_desc_esd_new(0); - ch->esd->ESID = stream_id; - ch->esd->OCRESID = first ? first->stream_id : stream_id; - ch->esd->slConfig->useRandomAccessPointFlag = 1; - ch->esd->slConfig->AUSeqNumLength = 0; - ch->esd->decoderConfig->objectTypeIndication = gf_bs_read_u8(bs); - ch->esd->decoderConfig->streamType = gf_bs_read_u8(bs); - ch->ts_res = ch->esd->slConfig->timestampResolution = gf_bs_read_u24(bs); - ch->esd->decoderConfig->bufferSizeDB = gf_bs_read_u16(bs); - au_size -= 7; - if ((ch->esd->decoderConfig->objectTypeIndication == 0xFF) && (ch->esd->decoderConfig->streamType == 0xFF) ) { - u16 mimeLen = gf_bs_read_u16(bs); - gf_bs_skip_bytes(bs, mimeLen); - au_size -= mimeLen+2; - } - if (type==7) { - u16 urlLen = gf_bs_read_u16(bs); - ch->esd->URLString = (char*)gf_malloc(sizeof(char)*(urlLen+1)); - gf_bs_read_data(bs, ch->esd->URLString, urlLen); - ch->esd->URLString[urlLen] = 0; - au_size -= urlLen+2; - } - if (au_size) { - ch->esd->decoderConfig->decoderSpecificInfo->dataLength = au_size; - ch->esd->decoderConfig->decoderSpecificInfo->data = (char*)gf_malloc(sizeof(char)*au_size); - gf_bs_read_data(bs, ch->esd->decoderConfig->decoderSpecificInfo->data, au_size); - } - if (ch->esd->decoderConfig->streamType==4) ch->buffer_min=100; - else if (ch->esd->decoderConfig->streamType==5) ch->buffer_min=400; - else ch->buffer_min=0; - - if (read->needs_connection && (ch->esd->decoderConfig->streamType==GF_STREAM_SCENE)) { - gf_list_add(read->channels, ch); - read->needs_connection = GF_FALSE; - gf_service_connect_ack(read->service, NULL, GF_OK); - } else if (read->needs_connection) { - gf_odf_desc_del((GF_Descriptor *) ch->esd); - gf_free(ch); - ch = NULL; - } else { - GF_ObjectDescriptor *od; - gf_list_add(read->channels, ch); - - od = (GF_ObjectDescriptor*)gf_odf_desc_new(GF_ODF_OD_TAG); - gf_list_add(od->ESDescriptors, ch->esd); - ch->esd = NULL; - od->objectDescriptorID = ch->stream_id; - gf_service_declare_media(read->service, (GF_Descriptor*)od, GF_FALSE); - - } - } - break; - case 4: - //don't dispatch anything until we have a play request - while (!read->nb_playing) { - if (read->run_state==0) { - gf_bs_del(bs); - return; - } - gf_sleep(1); - } - - if (ch) { - bs_pos = gf_bs_get_position(bs); - memset(&sl_hdr, 0, sizeof(GF_SLHeader)); - sl_hdr.accessUnitLength = au_size; - sl_hdr.AU_sequenceNumber = au_sn; - sl_hdr.compositionTimeStampFlag = 1; - sl_hdr.compositionTimeStamp = cts; - sl_hdr.randomAccessPointFlag = is_rap; - if (read->start_range && (read->start_range*ch->ts_res>cts*1000)) { - sl_hdr.compositionTimeStamp = read->start_range*ch->ts_res/1000; - } - gf_service_send_packet(read->service, ch->ch, read->saf_data+bs_pos, au_size, &sl_hdr, GF_OK); - } - gf_bs_skip_bytes(bs, au_size); - break; - case 3: - if (ch) gf_service_send_packet(read->service, ch->ch, NULL, 0, NULL, GF_EOS); - break; - case 5: - go = GF_FALSE; - read->run_state = 0; - i=0; - while ((ch = (SAFChannel *)gf_list_enum(read->channels, &i))) { - gf_service_send_packet(read->service, ch->ch, NULL, 0, NULL, GF_EOS); - } - break; - } - } - - gf_bs_del(bs); - if (bs_pos) { - u32 remain = (u32) (read->saf_size - bs_pos); - if (remain) memmove(read->saf_data, read->saf_data+bs_pos, sizeof(char)*remain); - read->saf_size = remain; - } - SAF_Regulate(read); -} - - -u32 SAF_Run(void *_p) -{ - GF_NETIO_Parameter par; - char data[1024]; - SAFIn *read = (SAFIn *)_p; - - par.msg_type = GF_NETIO_DATA_EXCHANGE; - par.data = data; - - gf_fseek(read->stream, 0, SEEK_SET); - read->saf_size=0; - read->run_state = 1; - while (read->run_state && !feof(read->stream) ) { - par.size = (u32) fread(data, 1, 1024, read->stream); - if ((s32) par.size <= 0) break; - SAF_NetIO(read, &par); - } - read->run_state = 2; - return 0; -} - -static void SAF_DownloadFile(GF_InputService *plug, char *url) -{ - SAFIn *read = (SAFIn*) plug->priv; - - read->dnload = gf_service_download_new(read->service, url, 0, SAF_NetIO, read); - if (!read->dnload) { - read->needs_connection = GF_FALSE; - gf_service_connect_ack(read->service, NULL, GF_NOT_SUPPORTED); - } else { - /*start our download (threaded)*/ - gf_dm_sess_process(read->dnload); - } - /*service confirm is done once fetched*/ -} - -typedef struct -{ - u32 stream_id; - u32 ts_res; -} StreamInfo; - -static void SAF_CheckFile(SAFIn *read) -{ - u32 nb_streams, i, cts, au_size, au_type, stream_id, ts_res; - GF_BitStream *bs; - StreamInfo si[1024]; - gf_fseek(read->stream, 0, SEEK_SET); - bs = gf_bs_from_file(read->stream, GF_BITSTREAM_READ); - - nb_streams=0; - while (gf_bs_available(bs)) { - gf_bs_read_u16(bs); - gf_bs_read_int(bs, 2); - cts = gf_bs_read_int(bs, 30); - au_size = gf_bs_read_int(bs, 16); - au_type = gf_bs_read_int(bs, 4); - stream_id = gf_bs_read_int(bs, 12); - au_size-=2; - ts_res = 0; - for (i=0; i<nb_streams; i++) { - if (si[i].stream_id==stream_id) ts_res = si[i].ts_res; - } - if (!ts_res) { - if ((au_type==1) || (au_type==2) || (au_type==7)) { - gf_bs_read_u16(bs); - ts_res = gf_bs_read_u24(bs); - au_size -= 5; - si[nb_streams].stream_id = stream_id; - si[nb_streams].ts_res = ts_res; - nb_streams++; - } - } - if (ts_res && (au_type==4)) { - Double ts = cts; - ts /= ts_res; - if (ts>read->duration) read->duration = ts; - } - gf_bs_skip_bytes(bs, au_size); - } - gf_bs_del(bs); - gf_fseek(read->stream, 0, SEEK_SET); -} - -static GF_Err SAF_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url) -{ - char szURL[2048]; - char *ext; - SAFIn *read = (SAFIn *)plug->priv; - read->service = serv; - - if (read->dnload) gf_service_download_del(read->dnload); - read->dnload = NULL; - - strcpy(szURL, url); - ext = strrchr(szURL, '#'); - if (ext) ext[0] = 0; - - read->needs_connection = GF_TRUE; - read->duration = 0; - - read->saf_type = SAF_FILE_LOCAL; - /*remote fetch*/ - if (strnicmp(url, "file://", 7) && strstr(url, "://")) { - read->saf_type = SAF_FILE_REMOTE; - SAF_DownloadFile(plug, (char *) szURL); - return GF_OK; - } - - read->stream = gf_fopen(szURL, "rb"); - if (!read->stream) { - gf_service_connect_ack(serv, NULL, GF_URL_ERROR); - return GF_OK; - } - SAF_CheckFile(read); - read->th = gf_th_new("SAFDemux"); - /*start playing for tune-in*/ - gf_th_run(read->th, SAF_Run, read); - return GF_OK; -} - -static GF_Err SAF_CloseService(GF_InputService *plug) -{ - SAFIn *read = (SAFIn *)plug->priv; - - if (read->th) { - if (read->run_state == 1) { - read->run_state=0; - while (read->run_state!=2) gf_sleep(2); - } - gf_th_del(read->th); - read->th = NULL; - } - - if (read->stream) gf_fclose(read->stream); - read->stream = NULL; - if (read->dnload) gf_service_download_del(read->dnload); - read->dnload = NULL; - gf_service_disconnect_ack(read->service, NULL, GF_OK); - return GF_OK; -} - -static GF_Descriptor *SAF_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) -{ - u32 i=0; - SAFChannel *root; - SAFIn *read = (SAFIn *)plug->priv; - GF_ObjectDescriptor *od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_IOD_TAG); - - od->objectDescriptorID = 1; - - while ( (root = (SAFChannel *)gf_list_enum(read->channels, &i))) { - if (root->esd && (root->esd->decoderConfig->streamType==GF_STREAM_SCENE)) break; - } - if (!root) return NULL; - - /*inline scene*/ - gf_list_add(od->ESDescriptors, root->esd); - root->esd = NULL; - return (GF_Descriptor *) od; -} - -static GF_Err SAF_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) -{ - u32 ES_ID; - SAFChannel *ch; - GF_Err e; - SAFIn *read = (SAFIn *)plug->priv; - - - ch = saf_get_channel(read, 0, channel); - if (ch) { - e = GF_SERVICE_ERROR; - } else { - e = GF_STREAM_NOT_FOUND; - if (strstr(url, "ES_ID")) { - sscanf(url, "ES_ID=%d", &ES_ID); - ch = saf_get_channel(read, ES_ID, NULL); - if (ch && !ch->ch) { - ch->ch = channel; - e = GF_OK; - } - } - } - - gf_service_connect_ack(read->service, channel, e); - return e; -} - -static GF_Err SAF_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) -{ - SAFChannel *ch; - SAFIn *read = (SAFIn *)plug->priv; - - GF_Err e = GF_STREAM_NOT_FOUND; - ch = saf_get_channel(read, 0, channel); - if (ch) { - gf_list_del_item(read->channels, ch); - if (ch->esd) gf_odf_desc_del((GF_Descriptor*)ch->esd); - gf_free(ch); - e = GF_OK; - } - gf_service_disconnect_ack(read->service, channel, e); - return GF_OK; -} - -static GF_Err SAF_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) -{ - SAFIn *read = (SAFIn *)plug->priv; - - if (!com->base.on_channel) return GF_NOT_SUPPORTED; - switch (com->command_type) { - case GF_NET_CHAN_SET_PULL: - return GF_NOT_SUPPORTED; - case GF_NET_CHAN_INTERACTIVE: - return GF_OK; - case GF_NET_CHAN_BUFFER: - return GF_OK; - case GF_NET_CHAN_DURATION: - com->duration.duration = read->duration; - return GF_OK; - case GF_NET_CHAN_PLAY: - if (!read->nb_playing) { - read->start_range = (u32) (com->play.start_range*1000); - read->end_range = (u32) (com->play.end_range*1000); - /*start demuxer*/ - if ((read->saf_type == SAF_FILE_LOCAL) && (read->run_state!=1)) { - gf_th_run(read->th, SAF_Run, read); - } - } - read->nb_playing++; - return GF_OK; - case GF_NET_CHAN_STOP: - assert(read->nb_playing); - read->nb_playing--; - /*stop demuxer*/ - if (!read->nb_playing && (read->run_state==1)) { - read->run_state=0; - while (read->run_state!=2) gf_sleep(2); - } - return GF_OK; - default: - return GF_OK; - } -} - -GF_InputService *NewSAFReader() -{ - SAFIn *reader; - GF_InputService *plug; - GF_SAFEALLOC(plug, GF_InputService); - if (!plug) return NULL; - GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC SAF Reader", "gpac distribution") - - GF_SAFEALLOC(reader, SAFIn); - if (!reader) { - gf_free(plug); - return NULL; - } - - plug->RegisterMimeTypes = SAF_RegisterMimeTypes; - plug->CanHandleURL = SAF_CanHandleURL; - plug->ConnectService = SAF_ConnectService; - plug->CloseService = SAF_CloseService; - plug->GetServiceDescriptor = SAF_GetServiceDesc; - plug->ConnectChannel = SAF_ConnectChannel; - plug->DisconnectChannel = SAF_DisconnectChannel; - plug->ServiceCommand = SAF_ServiceCommand; - - reader->channels = gf_list_new(); - plug->priv = reader; - return plug; -} - -void DeleteSAFReader(void *ifce) -{ - GF_InputService *plug = (GF_InputService *) ifce; - SAFIn *read = (SAFIn *)plug->priv; - if (!ifce) - return; - while (gf_list_count(read->channels)) { - SAFChannel *ch = (SAFChannel *)gf_list_last(read->channels); - gf_list_rem_last(read->channels); - if (ch->esd) gf_odf_desc_del((GF_Descriptor *) ch->esd); - gf_free(ch); - } - gf_list_del(read->channels); - if (read->saf_data) - gf_free(read->saf_data); - read->saf_data = NULL; - gf_free(read); - plug->priv = NULL; - gf_free(plug); -} - - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { - GF_NET_CLIENT_INTERFACE, - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - switch (InterfaceType) { - case GF_NET_CLIENT_INTERFACE: - return (GF_BaseInterface *) NewSAFReader(); - default: - return NULL; - } -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { - case GF_NET_CLIENT_INTERFACE: - DeleteSAFReader(ifce); - break; - } -} - -GPAC_MODULE_STATIC_DECLARATION( saf_in ) diff --git a/modules/sdl_out/Makefile b/modules/sdl_out/Makefile index 74b7584..ab04298 100644 --- a/modules/sdl_out/Makefile +++ b/modules/sdl_out/Makefile @@ -2,21 +2,21 @@ include ../../config.mak vpath %.c $(SRC_PATH)/modules/sdl_out -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" $(SDL_CFLAGS) +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) $(SDL_CFLAGS) -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif LINKFLAGS=-L../../bin/gcc -lgpac $(SDL_LIBS) -ifeq ($(CONFIG_DARWIN), yes) +ifeq ($(CONFIG_DARWIN),yes) LINKFLAGS+=-framework OpenGL endif diff --git a/modules/sdl_out/audio.c b/modules/sdl_out/audio.c index 4b355d6..630416c 100644 --- a/modules/sdl_out/audio.c +++ b/modules/sdl_out/audio.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2018 * All rights reserved * * This file is part of GPAC / SDL audio and video module @@ -43,11 +43,11 @@ void sdl_fill_audio(void *udata, Uint8 *stream, int len) ctx->alloc_size = len; } memset(stream, 0, len); - written = dr->FillBuffer(dr->audio_renderer, (char *) ctx->audioBuff, (u32) len); + written = dr->FillBuffer(dr->audio_renderer, ctx->audioBuff, (u32) len); if (written) SDL_MixAudio(stream, ctx->audioBuff, len, ctx->volume); } else { - dr->FillBuffer(dr->audio_renderer, (char *) stream, (u32) len); + dr->FillBuffer(dr->audio_renderer, stream, (u32) len); } } @@ -120,7 +120,7 @@ void SDL_DeleteAudio(void *ifce) { gf_free( dr ); } -static GF_Err SDLAud_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *nbBitsPerSample, u32 channel_cfg) +static GF_Err SDLAud_Configure(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *audioFormat, u64 channel_cfg) { s32 nb_samples; SDL_AudioSpec want_format, got_format; @@ -131,7 +131,29 @@ static GF_Err SDLAud_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *N memset(&want_format, 0, sizeof(SDL_AudioSpec)); want_format.freq = *SampleRate; - want_format.format = (*nbBitsPerSample==16) ? AUDIO_S16SYS : AUDIO_S8; + switch (*audioFormat) { + case GF_AUDIO_FMT_U8: + case GF_AUDIO_FMT_U8P: + want_format.format = AUDIO_U8; + break; + case GF_AUDIO_FMT_S16: + case GF_AUDIO_FMT_S16P: + want_format.format = AUDIO_S16; + break; +#if SDL_VERSION_ATLEAST(2,0,0) + case GF_AUDIO_FMT_S32: + case GF_AUDIO_FMT_S32P: + want_format.format = AUDIO_S32; + break; + case GF_AUDIO_FMT_FLT: + case GF_AUDIO_FMT_FLTP: + want_format.format = AUDIO_F32; + break; +#endif + default: + want_format.format = AUDIO_S16; + break; + } want_format.channels = *NbChannels; want_format.callback = sdl_fill_audio; want_format.userdata = dr; @@ -158,10 +180,21 @@ static GF_Err SDLAud_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *N switch (got_format.format) { case AUDIO_S8: case AUDIO_U8: - *nbBitsPerSample = 8; + *audioFormat = GF_AUDIO_FMT_U8; + break; + case AUDIO_S16: + *audioFormat = GF_AUDIO_FMT_S16; + break; +#if SDL_VERSION_ATLEAST(2,0,0) + case AUDIO_S32: + *audioFormat = GF_AUDIO_FMT_S32; + break; + case AUDIO_F32: + *audioFormat = GF_AUDIO_FMT_FLT; break; +#endif default: - *nbBitsPerSample = 16; + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[SDL] Error, unhandled audio format %s, requesting PCM s16\n", got_format.format)); break; } /*and play*/ @@ -229,7 +262,7 @@ void *SDL_NewAudio() dr->Setup = SDLAud_Setup; dr->Shutdown = SDLAud_Shutdown; - dr->ConfigureOutput = SDLAud_ConfigureOutput; + dr->Configure = SDLAud_Configure; dr->SetVolume = SDLAud_SetVolume; dr->SetPan = SDLAud_SetPan; dr->Play = SDLAud_Play; diff --git a/modules/sdl_out/sdl_out.h b/modules/sdl_out/sdl_out.h index 989a1d5..363c37c 100644 --- a/modules/sdl_out/sdl_out.h +++ b/modules/sdl_out/sdl_out.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / SDL audio and video module @@ -67,22 +67,21 @@ typedef struct Bool use_systems_memory; + Bool disable_vsync; + #if SDL_VERSION_ATLEAST(2,0,0) char szCaption[100]; Bool enable_defer_mode; Bool needs_clear; Bool needs_bb_flush; Bool needs_bb_grab; -#if defined(__APPLE__) && !defined(GPAC_IPHONE) - Bool disable_vsync; -#endif SDL_GLContext gl_context; SDL_Renderer *renderer; SDL_Window *screen; SDL_Texture *tx_back_buffer; - char *back_buffer_pixels; + u8 *back_buffer_pixels; SDL_Texture *pool_rgb, *pool_rgba, *pool_yuv; @@ -98,7 +97,7 @@ typedef struct SDL_Surface *offscreen_gl; - u32 output_3d_type; + Bool output_3d; void *os_handle; Bool force_alpha, hidden; @@ -120,7 +119,7 @@ typedef struct { u32 num_buffers, total_duration, delay_ms, total_size, volume, alloc_size; Bool is_init, is_running; - Uint8 * audioBuff; + u8 * audioBuff; } SDLAudCtx; void SDL_DeleteAudio(void *ifce); diff --git a/modules/sdl_out/video.c b/modules/sdl_out/video.c index 58bbac4..a570b8e 100644 --- a/modules/sdl_out/video.c +++ b/modules/sdl_out/video.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - Romain Bouqueau -* Copyright (c) Telecom ParisTech 2000-2012 +* Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / SDL audio and video module @@ -30,11 +30,15 @@ #include <windows.h> #endif -#ifdef GPAC_IPHONE +#ifdef GPAC_CONFIG_IOS #include <OpenGLES/ES1/gl.h> #include <OpenGLES/ES1/glext.h> #endif +#ifndef _WIN32_WCE +#include <locale.h> +#endif + /*cursors data*/ static char hand_data[] = { @@ -156,7 +160,7 @@ static u32 video_modes[] = 1152, 864, 1280, 1024 }; -static u32 nb_video_modes = 8; +static const u32 nb_video_modes = 8; void SDLVid_SetCaption() { @@ -203,373 +207,153 @@ SDL_Cursor *SDLVid_LoadCursor(char *maskdata) return SDL_CreateCursor(data, mask, 32, 32, 0, 0); } - -static void sdl_translate_key(u32 SDLkey, GF_EventKey *evt) +typedef struct { - evt->flags = 0; - evt->hw_code = SDLkey; - switch (SDLkey) { - case SDLK_BACKSPACE: - evt->key_code = GF_KEY_BACKSPACE; - break; - case SDLK_TAB: - evt->key_code = GF_KEY_TAB; - break; - case SDLK_CLEAR: - evt->key_code = GF_KEY_CLEAR; - break; - case SDLK_PAUSE: - evt->key_code = GF_KEY_PAUSE; - break; - case SDLK_ESCAPE: - evt->key_code = GF_KEY_ESCAPE; - break; - case SDLK_SPACE: - evt->key_code = GF_KEY_SPACE; - break; - - case SDLK_KP_ENTER: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_RETURN: - evt->key_code = GF_KEY_ENTER; - break; + u32 sdl_key; + u32 gf_key; + u32 gf_flags; +} SDLKeyToGPAC; - case SDLK_KP_MULTIPLY: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_ASTERISK: - evt->key_code = GF_KEY_STAR; - break; - case SDLK_KP_PLUS: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_PLUS: - evt->key_code = GF_KEY_PLUS; - break; - case SDLK_KP_MINUS: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_MINUS: - evt->key_code = GF_KEY_HYPHEN; - break; - case SDLK_KP_DIVIDE: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_SLASH: - evt->key_code = GF_KEY_SLASH; - break; - - case SDLK_KP_0: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_0: - evt->key_code = GF_KEY_0; - break; - case SDLK_KP_1: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_1: - evt->key_code = GF_KEY_1; - break; - case SDLK_KP_2: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_2: - evt->key_code = GF_KEY_2; - break; - case SDLK_KP_3: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_3: - evt->key_code = GF_KEY_3; - break; - case SDLK_KP_4: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_4: - evt->key_code = GF_KEY_4; - break; - case SDLK_KP_5: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_5: - evt->key_code = GF_KEY_5; - break; - case SDLK_KP_6: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_6: - evt->key_code = GF_KEY_6; - break; - case SDLK_KP_7: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_7: - evt->key_code = GF_KEY_7; - break; - case SDLK_KP_8: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_8: - evt->key_code = GF_KEY_8; - break; - case SDLK_KP_9: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_9: - evt->key_code = GF_KEY_9; - break; - case SDLK_KP_PERIOD: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_PERIOD: - evt->key_code = GF_KEY_FULLSTOP; - break; - case SDLK_KP_EQUALS: - evt->flags = GF_KEY_EXT_NUMPAD; - case SDLK_EQUALS: - evt->key_code = GF_KEY_EQUALS; - break; - - case SDLK_EXCLAIM: - evt->key_code = GF_KEY_EXCLAMATION; - break; - case SDLK_QUOTEDBL: - evt->key_code = GF_KEY_QUOTATION; - break; - case SDLK_HASH: - evt->key_code = GF_KEY_NUMBER; - break; - case SDLK_DOLLAR: - evt->key_code = GF_KEY_DOLLAR; - break; - case SDLK_AMPERSAND: - evt->key_code = GF_KEY_AMPERSAND; - break; - case SDLK_QUOTE: - evt->key_code = GF_KEY_APOSTROPHE; - break; - case SDLK_LEFTPAREN: - evt->key_code = GF_KEY_LEFTPARENTHESIS; - break; - case SDLK_RIGHTPAREN: - evt->key_code = GF_KEY_RIGHTPARENTHESIS; - break; - case SDLK_COMMA: - evt->key_code = GF_KEY_COMMA; - break; - case SDLK_COLON: - evt->key_code = GF_KEY_COLON; - break; - case SDLK_SEMICOLON: - evt->key_code = GF_KEY_SEMICOLON; - break; - case SDLK_LESS: - evt->key_code = GF_KEY_LESSTHAN; - break; - case SDLK_GREATER: - evt->key_code = GF_KEY_GREATERTHAN; - break; - case SDLK_QUESTION: - evt->key_code = GF_KEY_QUESTION; - break; - case SDLK_AT: - evt->key_code = GF_KEY_AT; - break; - case SDLK_LEFTBRACKET: - evt->key_code = GF_KEY_LEFTSQUAREBRACKET; - break; - case SDLK_RIGHTBRACKET: - evt->key_code = GF_KEY_RIGHTSQUAREBRACKET; - break; - case SDLK_BACKSLASH: - evt->key_code = GF_KEY_BACKSLASH; - break; - case SDLK_UNDERSCORE: - evt->key_code = GF_KEY_UNDERSCORE; - break; - case SDLK_BACKQUOTE: - evt->key_code = GF_KEY_GRAVEACCENT; - break; - case SDLK_DELETE: - evt->key_code = GF_KEY_DEL; - break; - case SDLK_UNDO: - evt->key_code = GF_KEY_UNDO; - break; - - case SDLK_UP: - evt->key_code = GF_KEY_UP; - break; - case SDLK_DOWN: - evt->key_code = GF_KEY_DOWN; - break; - case SDLK_RIGHT: - evt->key_code = GF_KEY_RIGHT; - break; - case SDLK_LEFT: - evt->key_code = GF_KEY_LEFT; - break; - case SDLK_INSERT: - evt->key_code = GF_KEY_INSERT; - break; - case SDLK_HOME: - evt->key_code = GF_KEY_HOME; - break; - case SDLK_END: - evt->key_code = GF_KEY_END; - break; - case SDLK_PAGEUP: - evt->key_code = GF_KEY_PAGEUP; - break; - case SDLK_PAGEDOWN: - evt->key_code = GF_KEY_PAGEDOWN; - break; - case SDLK_F1: - evt->key_code = GF_KEY_F1; - break; - case SDLK_F2: - evt->key_code = GF_KEY_F2; - break; - case SDLK_F3: - evt->key_code = GF_KEY_F3; - break; - case SDLK_F4: - evt->key_code = GF_KEY_F4; - break; - case SDLK_F5: - evt->key_code = GF_KEY_F5; - break; - case SDLK_F6: - evt->key_code = GF_KEY_F6; - break; - case SDLK_F7: - evt->key_code = GF_KEY_F7; - break; - case SDLK_F8: - evt->key_code = GF_KEY_F8; - break; - case SDLK_F9: - evt->key_code = GF_KEY_F9; - break; - case SDLK_F10: - evt->key_code = GF_KEY_F10; - break; - case SDLK_F11: - evt->key_code = GF_KEY_F11; - break; - case SDLK_F12: - evt->key_code = GF_KEY_F12; - break; - case SDLK_F13: - evt->key_code = GF_KEY_F13; - break; - case SDLK_F14: - evt->key_code = GF_KEY_F14; - break; - case SDLK_F15: - evt->key_code = GF_KEY_F15; - break; - case SDLK_NUMLOCKCLEAR: - evt->key_code = GF_KEY_NUMLOCK; - break; - case SDLK_CAPSLOCK: - evt->key_code = GF_KEY_CAPSLOCK; - break; - case SDLK_SCROLLLOCK: - evt->key_code = GF_KEY_SCROLL; - break; - - case SDLK_RSHIFT: - evt->key_code = GF_KEY_SHIFT; - evt->flags = GF_KEY_EXT_RIGHT; - break; - case SDLK_LSHIFT: - evt->key_code = GF_KEY_SHIFT; - evt->flags = GF_KEY_EXT_LEFT; - break; - case SDLK_LCTRL: - evt->key_code = GF_KEY_CONTROL; - evt->flags = GF_KEY_EXT_LEFT; - break; - case SDLK_RCTRL: - evt->key_code = GF_KEY_CONTROL; - evt->flags = GF_KEY_EXT_RIGHT; - break; - case SDLK_LALT: - evt->key_code = GF_KEY_ALT; - evt->flags = GF_KEY_EXT_LEFT; - break; - case SDLK_RALT: - evt->key_code = GF_KEY_ALT; - evt->flags = GF_KEY_EXT_RIGHT; - break; +static SDLKeyToGPAC SDLKeys[] = +{ + {SDLK_BACKSPACE, GF_KEY_BACKSPACE, 0}, + {SDLK_TAB, GF_KEY_TAB, 0}, + {SDLK_CLEAR, GF_KEY_CLEAR, 0}, + {SDLK_PAUSE, GF_KEY_PAUSE, 0}, + {SDLK_ESCAPE, GF_KEY_ESCAPE, 0}, + {SDLK_SPACE, GF_KEY_SPACE, 0}, + {SDLK_KP_ENTER, GF_KEY_EXT_NUMPAD, 0}, + {SDLK_RETURN, GF_KEY_ENTER, 0}, + {SDLK_KP_MULTIPLY, GF_KEY_EXT_NUMPAD, 0}, + {SDLK_ASTERISK, GF_KEY_STAR, 0}, + {SDLK_KP_PLUS, GF_KEY_EXT_NUMPAD, 0}, + {SDLK_PLUS, GF_KEY_PLUS, 0}, + {SDLK_KP_MINUS, GF_KEY_EXT_NUMPAD, 0}, + {SDLK_MINUS, GF_KEY_HYPHEN, 0}, + {SDLK_KP_DIVIDE, GF_KEY_EXT_NUMPAD, 0}, + {SDLK_SLASH, GF_KEY_SLASH, 0}, + {SDLK_KP_0, GF_KEY_0, GF_KEY_EXT_NUMPAD}, + {SDLK_0, GF_KEY_0, 0}, + {SDLK_KP_1, GF_KEY_1, GF_KEY_EXT_NUMPAD}, + {SDLK_1, GF_KEY_1, 0}, + {SDLK_KP_2, GF_KEY_2, GF_KEY_EXT_NUMPAD}, + {SDLK_2, GF_KEY_2, 0}, + {SDLK_KP_3, GF_KEY_3, GF_KEY_EXT_NUMPAD}, + {SDLK_3, GF_KEY_3, 0}, + {SDLK_KP_4, GF_KEY_4, GF_KEY_EXT_NUMPAD}, + {SDLK_4, GF_KEY_4, 0}, + {SDLK_KP_5, GF_KEY_5, GF_KEY_EXT_NUMPAD}, + {SDLK_5, GF_KEY_5, 0}, + {SDLK_KP_6, GF_KEY_6, GF_KEY_EXT_NUMPAD}, + {SDLK_6, GF_KEY_6, 0}, + {SDLK_KP_7, GF_KEY_7, GF_KEY_EXT_NUMPAD}, + {SDLK_7, GF_KEY_7, 0}, + {SDLK_KP_8, GF_KEY_8, GF_KEY_EXT_NUMPAD}, + {SDLK_8, GF_KEY_8, 0}, + {SDLK_KP_9, GF_KEY_9, GF_KEY_EXT_NUMPAD}, + {SDLK_9, GF_KEY_9, 0}, + {SDLK_KP_PERIOD, GF_KEY_FULLSTOP, GF_KEY_EXT_NUMPAD}, + {SDLK_PERIOD, GF_KEY_FULLSTOP, 0}, + {SDLK_KP_EQUALS, GF_KEY_EQUALS, GF_KEY_EXT_NUMPAD}, + {SDLK_EQUALS, GF_KEY_EQUALS, 0}, + {SDLK_EXCLAIM, GF_KEY_EXCLAMATION, 0}, + {SDLK_QUOTEDBL, GF_KEY_QUOTATION, 0}, + {SDLK_HASH, GF_KEY_NUMBER, 0}, + {SDLK_DOLLAR, GF_KEY_DOLLAR, 0}, + {SDLK_AMPERSAND, GF_KEY_AMPERSAND, 0}, + {SDLK_QUOTE, GF_KEY_APOSTROPHE, 0}, + {SDLK_LEFTPAREN, GF_KEY_LEFTPARENTHESIS, 0}, + {SDLK_RIGHTPAREN, GF_KEY_RIGHTPARENTHESIS, 0}, + {SDLK_COMMA, GF_KEY_COMMA, 0}, + {SDLK_COLON, GF_KEY_COLON, 0}, + {SDLK_SEMICOLON, GF_KEY_SEMICOLON, 0}, + {SDLK_LESS, GF_KEY_LESSTHAN, 0}, + {SDLK_GREATER, GF_KEY_GREATERTHAN, 0}, + {SDLK_QUESTION, GF_KEY_QUESTION, 0}, + {SDLK_AT, GF_KEY_AT, 0}, + {SDLK_LEFTBRACKET, GF_KEY_LEFTSQUAREBRACKET, 0}, + {SDLK_RIGHTBRACKET, GF_KEY_RIGHTSQUAREBRACKET, 0}, + {SDLK_BACKSLASH, GF_KEY_BACKSLASH, 0}, + {SDLK_UNDERSCORE, GF_KEY_UNDERSCORE, 0}, + {SDLK_BACKQUOTE, GF_KEY_GRAVEACCENT, 0}, + {SDLK_DELETE, GF_KEY_DEL, 0}, + {SDLK_UNDO, GF_KEY_UNDO, 0}, + {SDLK_UP, GF_KEY_UP, 0}, + {SDLK_DOWN, GF_KEY_DOWN, 0}, + {SDLK_RIGHT, GF_KEY_RIGHT, 0}, + {SDLK_LEFT, GF_KEY_LEFT, 0}, + {SDLK_INSERT, GF_KEY_INSERT, 0}, + {SDLK_HOME, GF_KEY_HOME, 0}, + {SDLK_END, GF_KEY_END, 0}, + {SDLK_PAGEUP, GF_KEY_PAGEUP, 0}, + {SDLK_PAGEDOWN, GF_KEY_PAGEDOWN, 0}, + {SDLK_F1, GF_KEY_F1, 0}, + {SDLK_F2, GF_KEY_F2, 0}, + {SDLK_F3, GF_KEY_F3, 0}, + {SDLK_F4, GF_KEY_F4, 0}, + {SDLK_F5, GF_KEY_F5, 0}, + {SDLK_F6, GF_KEY_F6, 0}, + {SDLK_F7, GF_KEY_F7, 0}, + {SDLK_F8, GF_KEY_F8, 0}, + {SDLK_F9, GF_KEY_F9, 0}, + {SDLK_F10, GF_KEY_F10, 0}, + {SDLK_F11, GF_KEY_F11, 0}, + {SDLK_F12, GF_KEY_F12, 0}, + {SDLK_F13, GF_KEY_F13, 0}, + {SDLK_F14, GF_KEY_F14, 0}, + {SDLK_F15, GF_KEY_F15, 0}, + {SDLK_NUMLOCKCLEAR, GF_KEY_NUMLOCK, 0}, + {SDLK_CAPSLOCK, GF_KEY_CAPSLOCK, 0}, + {SDLK_SCROLLLOCK, GF_KEY_SCROLL, 0}, + {SDLK_RSHIFT, GF_KEY_SHIFT, GF_KEY_EXT_RIGHT}, + {SDLK_LSHIFT, GF_KEY_SHIFT, GF_KEY_EXT_LEFT}, + {SDLK_LCTRL, GF_KEY_CONTROL, GF_KEY_EXT_LEFT}, + {SDLK_RCTRL, GF_KEY_CONTROL, GF_KEY_EXT_RIGHT}, + {SDLK_LALT, GF_KEY_ALT, GF_KEY_EXT_LEFT}, + {SDLK_RALT, GF_KEY_ALT, GF_KEY_EXT_RIGHT}, #if (SDL_MAJOR_VERSION<=1) && (SDL_MINOR_VERSION<3) - case SDLK_LMETA: - case SDLK_LSUPER: + {SDLK_LMETA, GF_KEY_META, GF_KEY_EXT_LEFT}, + {SDLK_LSUPER, GF_KEY_META, GF_KEY_EXT_LEFT}, #else - case SDLK_LGUI: + {SDLK_LGUI, GF_KEY_META, GF_KEY_EXT_LEFT}, #endif - evt->key_code = GF_KEY_META; - evt->flags = GF_KEY_EXT_LEFT; - break; + #if (SDL_MAJOR_VERSION<=1) && (SDL_MINOR_VERSION<3) - case SDLK_RMETA: - case SDLK_RSUPER: + {SDLK_RMETA, GF_KEY_META, GF_KEY_EXT_RIGHT}, + {SDLK_RSUPER, GF_KEY_META, GF_KEY_EXT_RIGHT}, #else - case SDLK_RGUI: + {SDLK_RGUI, GF_KEY_META, GF_KEY_EXT_RIGHT}, #endif - evt->key_code = GF_KEY_META; - evt->flags = GF_KEY_EXT_RIGHT; - break; - case SDLK_MODE: - evt->key_code = GF_KEY_MODECHANGE; - break; - case SDLK_APPLICATION: - evt->key_code = GF_KEY_COMPOSE; - break; - case SDLK_HELP: - evt->key_code = GF_KEY_HELP; - break; - case SDLK_PRINTSCREEN: - evt->key_code = GF_KEY_PRINTSCREEN; - break; - -#if (SDL_MAJOR_VERSION>=1) && (SDL_MINOR_VERSION>=3) - /* - SDLK_CARET = 94, - */ - case SDLK_a: - case SDLK_b: - case SDLK_c: - case SDLK_d: - case SDLK_e: - case SDLK_f: - case SDLK_g: - case SDLK_h: - case SDLK_i: - case SDLK_j: - case SDLK_k: - case SDLK_l: - case SDLK_m: - case SDLK_n: - case SDLK_o: - case SDLK_p: - case SDLK_q: - case SDLK_r: - case SDLK_s: - case SDLK_t: - case SDLK_u: - case SDLK_v: - case SDLK_w: - case SDLK_x: - case SDLK_y: - case SDLK_z: - evt->key_code = GF_KEY_A + SDLkey - SDLK_a; - break; - /* - SDLK_DELETE = 127, - SDLK_SYSREQ = 317, - SDLK_POWER = 320, + {SDLK_MODE, GF_KEY_MODECHANGE, 0}, + {SDLK_APPLICATION, GF_KEY_COMPOSE, 0}, + {SDLK_HELP, GF_KEY_HELP, 0}, + {SDLK_PRINTSCREEN, GF_KEY_PRINTSCREEN, 0} +}; - */ -#endif +static u32 num_sdl_keys = sizeof(SDLKeys) / sizeof(SDLKeyToGPAC); - default: - if ((SDLkey>=0x30) && (SDLkey<=0x39)) evt->key_code = GF_KEY_0 + SDLkey-0x30; - else if ((SDLkey>=0x41) && (SDLkey<=0x5A)) evt->key_code = GF_KEY_A + SDLkey-0x41; - else if ((SDLkey>=0x61) && (SDLkey<=0x7A)) evt->key_code = GF_KEY_A + SDLkey-0x61; - else - { - evt->key_code = GF_KEY_UNIDENTIFIED; +static void sdl_translate_key(u32 SDLkey, GF_EventKey *evt) +{ + u32 i; + evt->flags = 0; + evt->hw_code = SDLkey; + for (i=0; i<num_sdl_keys; i++) { + if (SDLKeys[i].sdl_key == SDLkey) { + evt->key_code = SDLKeys[i].gf_key; + evt->flags = SDLKeys[i].gf_flags; + return; } - break; + } + if ((SDLkey>=0x30) && (SDLkey<=0x39)) + evt->key_code = GF_KEY_0 + SDLkey-0x30; + else if ((SDLkey>=0x41) && (SDLkey<=0x5A)) + evt->key_code = GF_KEY_A + SDLkey-0x41; + else if ((SDLkey>=0x61) && (SDLkey<=0x7A)) + evt->key_code = GF_KEY_A + SDLkey-0x61; + else { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[SDL] Unrecognized key %X\n", SDLkey)); + evt->key_code = GF_KEY_UNIDENTIFIED; } } @@ -620,7 +404,7 @@ static void SDLVid_DestroyObjects(SDLVidCtx *ctx) } #if SDL_VERSION_ATLEAST(2,0,0) -#ifdef GPAC_IPHONE +#ifdef GPAC_CONFIG_IOS #define SDL_WINDOW_FLAGS SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE #define SDL_FULLSCREEN_FLAGS SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE #define SDL_GL_WINDOW_FLAGS SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE @@ -632,7 +416,7 @@ static void SDLVid_DestroyObjects(SDLVidCtx *ctx) #define SDL_GL_FULLSCREEN_FLAGS SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN #endif #else -#ifdef GPAC_IPHONE +#ifdef GPAC_CONFIG_IOS #define SDL_WINDOW_FLAGS SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL | SDL_RESIZABLE | SDL_NOFRAME #define SDL_FULLSCREEN_FLAGS SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL | SDL_FULLSCREEN | SDL_NOFRAME #define SDL_GL_WINDOW_FLAGS SDL_HWSURFACE | SDL_OPENGL | SDL_HWACCEL | SDL_RESIZABLE | SDL_NOFRAME @@ -645,10 +429,42 @@ static void SDLVid_DestroyObjects(SDLVidCtx *ctx) #endif #endif +#if SDL_VERSION_ATLEAST(2,0,0) && !defined(GPAC_CONFIG_IOS) +#include <gpac/media_tools.h> +void SDLVid_SetIcon(SDLVidCtx *ctx) +{ + u8 *buffer, *dec_buf; + u32 size, w, h, pf, Bpp, dst_size=0; + const char cfg[GF_MAX_PATH]; + if (!gf_opts_default_shared_directory((char *) cfg)) + return; + + strcat((char *) cfg, "/res/gpac.png"); + if (gf_file_load_data(cfg, &buffer, &size) != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDLOut] failed to load icon file %s\n", cfg )); + return; + } + gf_img_png_dec(buffer, size, &w, &h, &pf, NULL, &dst_size); + Bpp = gf_pixel_get_bytes_per_pixel(pf); + dec_buf = gf_malloc(sizeof(char)*dst_size); + gf_img_png_dec(buffer, size, &w, &h, &pf, dec_buf, &dst_size); + //RGBA only + SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(dec_buf, w, h, Bpp*8, w*Bpp, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000); + if (!surface) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDLOut] failed to create surface from icon: %s\n", SDL_GetError() )); + } else { + SDL_SetWindowIcon(ctx->screen, surface); + SDL_FreeSurface(surface); + } + gf_free(buffer); + gf_free(dec_buf); +} +#endif + GF_Err SDLVid_ResizeWindow(GF_VideoOutput *dr, u32 width, u32 height) { - Bool hw_reset = GF_FALSE; + Bool hw_reset; SDLVID(); GF_Event evt; @@ -657,7 +473,7 @@ GF_Err SDLVid_ResizeWindow(GF_VideoOutput *dr, u32 width, u32 height) GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[SDL] Resizing window %dx%d\n", width, height)); - if (ctx->output_3d_type) { + if (ctx->output_3d) { u32 flags, nb_bits; const char *opt; @@ -678,24 +494,24 @@ GF_Err SDLVid_ResizeWindow(GF_VideoOutput *dr, u32 width, u32 height) #endif SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "GLNbBitsDepth"); + opt = gf_opts_get_key("core", "gl-bits-depth"); nb_bits = opt ? atoi(opt) : 16; SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, nb_bits); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0); - opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "GLNbBitsPerComponent"); + opt = gf_opts_get_key("core", "gl-bits-comp"); nb_bits = opt ? atoi(opt) : 8; SDL_GL_SetAttribute(SDL_GL_RED_SIZE, nb_bits); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, nb_bits); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, nb_bits); -#if SDL_VERSION_ATLEAST(2,0,0) - if (ctx->hidden) - flags |= SDL_WINDOW_HIDDEN; -#endif - assert(width); assert(height); + #if SDL_VERSION_ATLEAST(2,0,0) + if (ctx->hidden) + flags |= SDL_WINDOW_HIDDEN; + + hw_reset = GF_FALSE; #ifdef GPAC_USE_GLES2 /* Set the correct attributes for MASK and MAJOR version */ @@ -703,15 +519,6 @@ GF_Err SDLVid_ResizeWindow(GF_VideoOutput *dr, u32 width, u32 height) SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); #endif -#if defined(__APPLE__) && !defined(GPAC_IPHONE) - { - const char *opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "DisableVSync"); - if (opt && !strcmp(opt, "yes")) { - ctx->disable_vsync = GF_TRUE; - } - } -#endif - if (!ctx->screen) { if (!(ctx->screen = SDL_CreateWindow("", 0, 0, width, height, flags))) { GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Cannot create window: %s\n", SDL_GetError())); @@ -719,10 +526,26 @@ GF_Err SDLVid_ResizeWindow(GF_VideoOutput *dr, u32 width, u32 height) return GF_IO_ERR; } GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[SDL] Window created\n")); + +#if SDL_VERSION_ATLEAST(2,0,0) && !defined(GPAC_CONFIG_IOS) + SDLVid_SetIcon(ctx); +#endif + + /*creating a window, at least on OSX, changes the locale and screws up float parsing !! + force setting the local back and pray that it will be changed before any other atof/strtod is called + + !! it also looks like the locale is not enforced synchronously on OSX: adding + assert(strtod(0.5)) in gf_malloc proto would lead to random crashes + */ +#ifndef _WIN32_WCE + setlocale( LC_NUMERIC, "C" ); +#endif } if ( !ctx->gl_context ) { - if (!(ctx->gl_context = SDL_GL_CreateContext(ctx->screen))) { + //in case we created a headless context before loading vout, create a shared context + SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); + if (!(ctx->gl_context = SDL_GL_CreateContext(ctx->screen))) { GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Cannot initialize gl context: %s\n", SDL_GetError())); gf_mx_v(ctx->evt_mx); return GF_IO_ERR; @@ -734,6 +557,17 @@ GF_Err SDLVid_ResizeWindow(GF_VideoOutput *dr, u32 width, u32 height) } hw_reset = GF_TRUE; } + + if (!ctx->disable_vsync) + ctx->disable_vsync = gf_opts_get_bool("core", "disable-vsync"); + + if (ctx->disable_vsync) { +#if defined(__APPLE__) && !defined(GPAC_CONFIG_IOS) +#else + SDL_GL_SetSwapInterval(0); +#endif + } + SDL_SetWindowSize(ctx->screen, width, height); #else @@ -755,21 +589,18 @@ GF_Err SDLVid_ResizeWindow(GF_VideoOutput *dr, u32 width, u32 height) } else { u32 flags; -#ifdef GPAC_IPHONE +#ifdef GPAC_CONFIG_IOS flags = SDL_FULLSCREEN_FLAGS; //SDL readme says it would make us faster SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); #else flags = SDL_WINDOW_FLAGS | SDL_WINDOW_RESIZABLE; - if (ctx->os_handle) flags &= ~SDL_WINDOW_RESIZABLE; - #endif - if (ctx->os_handle) flags &= ~SDL_WINDOW_RESIZABLE; #if SDL_VERSION_ATLEAST(2,0,0) -#ifdef GPAC_IPHONE +#ifdef GPAC_CONFIG_IOS //still some issues with render to tager and landscape orientation, we need to reset everything ... if (ctx->enable_defer_mode) { if (ctx->renderer) SDL_DestroyRenderer(ctx->renderer); @@ -792,13 +623,21 @@ GF_Err SDLVid_ResizeWindow(GF_VideoOutput *dr, u32 width, u32 height) gf_mx_v(ctx->evt_mx); return GF_IO_ERR; } + +#if SDL_VERSION_ATLEAST(2,0,0) && !defined(GPAC_CONFIG_IOS) + SDLVid_SetIcon(ctx); +#endif + + /*see above note*/ +#ifndef _WIN32_WCE + setlocale( LC_NUMERIC, "C" ); +#endif GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[SDL] Window created\n")); SDL_RaiseWindow(ctx->screen); } if ( !ctx->renderer ) { u32 flags = SDL_RENDERER_ACCELERATED; - const char *opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "DisableVSync"); - if (!opt || strcmp(opt, "yes")) { + if (! gf_opts_get_bool("core", "disable-vsync")) { flags |= SDL_RENDERER_PRESENTVSYNC; } @@ -809,7 +648,7 @@ GF_Err SDLVid_ResizeWindow(GF_VideoOutput *dr, u32 width, u32 height) return GF_IO_ERR; } } -#ifndef GPAC_IPHONE +#ifndef GPAC_CONFIG_IOS SDL_SetWindowSize(ctx->screen, width, height); #endif SDL_SetRenderDrawColor(ctx->renderer, 0, 0, 0, 255); @@ -824,6 +663,27 @@ GF_Err SDLVid_ResizeWindow(GF_VideoOutput *dr, u32 width, u32 height) return ctx->screen ? GF_OK : GF_IO_ERR; } +static void SDLVid_SetCursor(GF_VideoOutput *dr, u32 cursor_type) +{ +#ifndef GPAC_CONFIG_IOS + SDLVID(); + switch (cursor_type) { + case GF_CURSOR_ANCHOR: + case GF_CURSOR_TOUCH: + case GF_CURSOR_ROTATE: + case GF_CURSOR_PROXIMITY: + case GF_CURSOR_PLANE: + SDL_SetCursor(ctx->curs_hand); + break; + case GF_CURSOR_COLLIDE: + SDL_SetCursor(ctx->curs_collide); + break; + default: + SDL_SetCursor(ctx->curs_def); + break; + } +#endif +} static Bool SDLVid_InitializeWindow(SDLVidCtx *ctx, GF_VideoOutput *dr) { @@ -831,8 +691,10 @@ static Bool SDLVid_InitializeWindow(SDLVidCtx *ctx, GF_VideoOutput *dr) #if SDL_VERSION_ATLEAST(2,0,0) SDL_DisplayMode vinf; #else +#if SDL_VERSION_ATLEAST(1, 2, 10) const SDL_VideoInfo *vinf; #endif +#endif #ifdef WIN32 putenv("directx"); @@ -866,8 +728,9 @@ static Bool SDLVid_InitializeWindow(SDLVidCtx *ctx, GF_VideoOutput *dr) dr->max_screen_height = vinf.h; dr->max_screen_bpp = 8; #else - vinf = SDL_GetVideoInfo(); + #if SDL_VERSION_ATLEAST(1, 2, 10) + vinf = SDL_GetVideoInfo(); dr->max_screen_width = vinf->current_w; dr->max_screen_height = vinf->current_h; dr->max_screen_bpp = 8; @@ -903,6 +766,7 @@ static Bool SDLVid_InitializeWindow(SDLVidCtx *ctx, GF_VideoOutput *dr) SDLVid_SetCaption(); #endif GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[SDL] Video output initialized - screen resolution %d %d\n", dr->max_screen_width, dr->max_screen_height)); + return GF_TRUE; } @@ -920,7 +784,7 @@ static void SDLVid_ResetWindow(SDLVidCtx *ctx) } /*iOS SDL2 has a nasty bug that breaks switching between 2D and GL context if we don't re-init the video subsystem*/ -#ifdef GPAC_IPHONE +#ifdef GPAC_CONFIG_IOS if ( ctx->screen ) { SDL_DestroyWindow(ctx->screen); ctx->screen=NULL; @@ -939,8 +803,8 @@ static void SDLVid_ShutdownWindow(SDLVidCtx *ctx) SDL_QuitSubSystem(SDL_INIT_VIDEO); } -#if defined SDL_TEXTINPUTEVENT_TEXT_SIZE /*&& !defined GPAC_IPHONE*/ -#include <gpac/unicode.h> +#if defined SDL_TEXTINPUTEVENT_TEXT_SIZE /*&& !defined GPAC_CONFIG_IOS*/ +#include <gpac/utf.h> #endif @@ -949,7 +813,7 @@ Bool SDLVid_ProcessMessageQueue(SDLVidCtx *ctx, GF_VideoOutput *dr) SDL_Event sdl_evt; GF_Event gpac_evt; -#ifdef GPAC_IPHONE +#ifdef GPAC_CONFIG_IOS while (SDL_WaitEventTimeout(&sdl_evt, 0)) { #else while (SDL_PollEvent(&sdl_evt)) { @@ -967,10 +831,26 @@ Bool SDLVid_ProcessMessageQueue(SDLVidCtx *ctx, GF_VideoOutput *dr) break; case SDL_WINDOWEVENT_EXPOSED: case SDL_WINDOWEVENT_SHOWN: - case SDL_WINDOWEVENT_MOVED: gpac_evt.type = GF_EVENT_REFRESH; dr->on_event(dr->evt_cbk_hdl, &gpac_evt); break; + case SDL_WINDOWEVENT_MOVED: + gpac_evt.type = GF_EVENT_MOVE; + gpac_evt.move.x = sdl_evt.window.data1; + gpac_evt.move.y = sdl_evt.window.data2; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + break; + case SDL_WINDOWEVENT_CLOSE: + memset(&gpac_evt, 0, sizeof(GF_Event)); + gpac_evt.type = GF_EVENT_QUIT; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + return GF_FALSE; + case SDL_WINDOWEVENT_MINIMIZED: + memset(&gpac_evt, 0, sizeof(GF_Event)); + gpac_evt.type = GF_EVENT_SHOWHIDE; + gpac_evt.show.show_type = 0; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + return GF_FALSE; } break; #else @@ -1027,7 +907,7 @@ Bool SDLVid_ProcessMessageQueue(SDLVidCtx *ctx, GF_VideoOutput *dr) #endif -#if (SDL_MAJOR_VERSION>=1) && (SDL_MINOR_VERSION>=3) +#if SDL_VERSION_ATLEAST(2,0,0) if ((gpac_evt.type==GF_EVENT_KEYUP) && (gpac_evt.key.key_code==GF_KEY_V) #if defined(__DARWIN__) || defined(__APPLE__) @@ -1036,13 +916,10 @@ Bool SDLVid_ProcessMessageQueue(SDLVidCtx *ctx, GF_VideoOutput *dr) && ctx->ctrl_down #endif ) { -#if defined(__DARWIN__) || defined(__APPLE__) -#else gpac_evt.type = GF_EVENT_PASTE_TEXT; - gpac_evt.message.message = (const char *) SDL_GetClipboardText(); + gpac_evt.clipboard.text = (char *) SDL_GetClipboardText(); dr->on_event(dr->evt_cbk_hdl, &gpac_evt); - SDL_free((char *) gpac_evt.message.message); -#endif + SDL_free(gpac_evt.clipboard.text); } else if ((gpac_evt.type==GF_EVENT_KEYUP) && (gpac_evt.key.key_code==GF_KEY_C) #if defined(__DARWIN__) || defined(__APPLE__) @@ -1052,11 +929,10 @@ Bool SDLVid_ProcessMessageQueue(SDLVidCtx *ctx, GF_VideoOutput *dr) #endif ) { gpac_evt.type = GF_EVENT_COPY_TEXT; -#if defined(__DARWIN__) || defined(__APPLE__) -#else - if (dr->on_event(dr->evt_cbk_hdl, &gpac_evt)==GF_TRUE) - SDL_SetClipboardText((char *)gpac_evt.message.message ); -#endif + if (dr->on_event(dr->evt_cbk_hdl, &gpac_evt) && gpac_evt.clipboard.text) { + SDL_SetClipboardText(gpac_evt.clipboard.text ); + gf_free(gpac_evt.clipboard.text); + } } #endif @@ -1116,10 +992,28 @@ Bool SDLVid_ProcessMessageQueue(SDLVidCtx *ctx, GF_VideoOutput *dr) case SDL_MOUSEWHEEL: /*SDL handling is not perfect there, it just says up/down but no info on how much the wheel was rotated...*/ - gpac_evt.mouse.wheel_pos = INT2FIX(sdl_evt.wheel.x); + gpac_evt.mouse.wheel_pos = INT2FIX(sdl_evt.wheel.y); gpac_evt.type = GF_EVENT_MOUSEWHEEL; dr->on_event(dr->evt_cbk_hdl, &gpac_evt); break; + + case SDL_MULTIGESTURE: + gpac_evt.mtouch.x = FLT2FIX(sdl_evt.mgesture.x); + gpac_evt.mtouch.y = FLT2FIX(sdl_evt.mgesture.y); + gpac_evt.mtouch.num_fingers = sdl_evt.mgesture.numFingers; + gpac_evt.mtouch.rotation = sdl_evt.mgesture.dTheta; + gpac_evt.mtouch.pinch = sdl_evt.mgesture.dDist; + gpac_evt.type = GF_EVENT_MULTITOUCH; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + break; + + case SDL_DROPFILE: + gpac_evt.type = GF_EVENT_DROPFILE; + gpac_evt.open_file.nb_files = 1; + gpac_evt.open_file.files = &sdl_evt.drop.file; + dr->on_event(dr->evt_cbk_hdl, &gpac_evt); + + #endif } @@ -1139,6 +1033,7 @@ u32 SDLVid_EventProc(void *par) if (!SDLVid_InitializeWindow(ctx, dr)) { ctx->sdl_th_state = SDL_STATE_STOP_REQ; + return 1; } ctx->sdl_th_state = SDL_STATE_RUNNING; @@ -1191,16 +1086,27 @@ u32 SDLVid_EventProc(void *par) GF_Err SDLVid_Setup(struct _video_out *dr, void *os_handle, void *os_display, u32 init_flags) { + Bool show_window = GF_TRUE; SDLVID(); /*we don't allow SDL hack, not stable enough*/ //if (os_handle) SDLVid_SetHack(os_handle, 1); ctx->os_handle = os_handle; - ctx->is_init = GF_FALSE; - ctx->output_3d_type = 0; + if (!ctx->is_init) { + ctx->output_3d = GF_FALSE; + show_window = GF_TRUE; + } + ctx->force_alpha = (init_flags & GF_TERM_WINDOW_TRANSPARENT) ? GF_TRUE : GF_FALSE; ctx->hidden = (init_flags & GF_TERM_INIT_HIDE) ? GF_TRUE : GF_FALSE; + if (!ctx->hidden && show_window) { +#if SDL_VERSION_ATLEAST(2,0,0) + SDL_ShowWindow(ctx->screen); +#else +#endif + } + if (!SDLOUT_InitSDL()) return GF_IO_ERR; @@ -1225,6 +1131,15 @@ GF_Err SDLVid_Setup(struct _video_out *dr, void *os_handle, void *os_display, u3 } #endif + //coverage +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + GF_Event evt; + sdl_translate_key(SDLK_BACKSPACE, &evt.key); + SDLVid_SetCursor(dr, GF_CURSOR_NORMAL); + } +#endif + ctx->is_init = GF_TRUE; return GF_OK; } @@ -1282,11 +1197,9 @@ GF_Err SDLVid_SetFullScreen(GF_VideoOutput *dr, Bool bFullScreenOn, u32 *screen_ if (ctx->fullscreen) { #if ! ( SDL_VERSION_ATLEAST(2,0,0) ) - u32 flags = (ctx->output_3d_type==1) ? SDL_GL_FULLSCREEN_FLAGS : SDL_FULLSCREEN_FLAGS; + u32 flags = ctx->output_3d ? SDL_GL_FULLSCREEN_FLAGS : SDL_FULLSCREEN_FLAGS; #endif - Bool switch_res = GF_FALSE; - const char *sOpt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "SwitchResolution"); - if (sOpt && !stricmp(sOpt, "yes")) switch_res = GF_TRUE; + Bool switch_res = gf_opts_get_bool("core", "switch-vres"); if (!dr->max_screen_width || !dr->max_screen_height) switch_res = GF_TRUE; ctx->store_width = *screen_width; @@ -1338,11 +1251,11 @@ GF_Err SDLVid_SetFullScreen(GF_VideoOutput *dr, Bool bFullScreenOn, u32 *screen_ *screen_width = ctx->fs_width; *screen_height = ctx->fs_height; /*GL has changed*/ - if (ctx->output_3d_type==1) { + if (ctx->output_3d) { GF_Event evt; memset(&evt, 0, sizeof(GF_Event)); evt.type = GF_EVENT_VIDEO_SETUP; - evt.setup.opengl_mode = 3; + evt.setup.use_opengl = GF_TRUE; dr->on_event(dr->evt_cbk_hdl, &evt); } } else { @@ -1368,13 +1281,13 @@ GF_Err SDLVid_SetBackbufferSize(GF_VideoOutput *dr, u32 newWidth, u32 newHeight, u32 col; #endif - if (ctx->output_3d_type==1) return GF_BAD_PARAM; + if (ctx->output_3d) return GF_BAD_PARAM; - opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "HardwareMemory"); + opt = gf_opts_get_key("core", "hwvmem"); if (system_mem) { - if (opt && !strcmp(opt, "Always")) system_mem = GF_FALSE; + if (opt && !strcmp(opt, "always")) system_mem = GF_FALSE; } else { - if (opt && !strcmp(opt, "Never")) system_mem = GF_TRUE; + if (opt && !strcmp(opt, "never")) system_mem = GF_TRUE; } ctx->use_systems_memory = system_mem; @@ -1417,20 +1330,20 @@ GF_Err SDLVid_SetBackbufferSize(GF_VideoOutput *dr, u32 newWidth, u32 newHeight, u32 SDLVid_MapPixelFormat(SDL_PixelFormat *format, Bool force_alpha) { - if (format->palette) return 0; + if (!format || format->palette) return 0; switch (format->BitsPerPixel) { case 16: if ((format->Rmask==0x7c00) && (format->Gmask==0x03e0) && (format->Bmask==0x001f) ) return GF_PIXEL_RGB_555; if ((format->Rmask==0xf800) && (format->Gmask==0x07e0) && (format->Bmask==0x001f) ) return GF_PIXEL_RGB_565; return 0; case 24: - if (format->Rmask==0x00FF0000) return GF_PIXEL_RGB_24; - if (format->Rmask==0x000000FF) return GF_PIXEL_BGR_24; + if (format->Rmask==0x00FF0000) return GF_PIXEL_RGB; + if (format->Rmask==0x000000FF) return GF_PIXEL_BGR; return 0; case 32: if (format->Amask==0xFF000000) return GF_PIXEL_ARGB; - if (format->Rmask==0x00FF0000) return force_alpha ? GF_PIXEL_ARGB : GF_PIXEL_RGB_32; - if (format->Rmask==0x000000FF) return force_alpha ? GF_PIXEL_RGBA : GF_PIXEL_BGR_32; + if (format->Rmask==0x00FF0000) return force_alpha ? GF_PIXEL_ARGB : GF_PIXEL_RGBX; + if (format->Rmask==0x000000FF) return force_alpha ? GF_PIXEL_RGBA : GF_PIXEL_BGRX; return 0; default: return 0; @@ -1449,7 +1362,7 @@ static GF_Err SDLVid_LockBackBuffer(GF_VideoOutput *dr, GF_VideoSurface *video_i video_info->pitch_x = 0; video_info->pitch_y = ctx->width*3; video_info->video_buffer = ctx->back_buffer_pixels; - video_info->pixel_format = GF_PIXEL_RGB_24; + video_info->pixel_format = GF_PIXEL_RGB; video_info->is_hardware_memory = 0; if (ctx->needs_bb_grab) { SDL_RenderReadPixels(ctx->renderer, NULL, SDL_PIXELFORMAT_RGB24, video_info->video_buffer, video_info->pitch_y); @@ -1490,7 +1403,7 @@ static GF_Err SDLVid_LockBackBuffer(GF_VideoOutput *dr, GF_VideoSurface *video_i #if SDL_VERSION_ATLEAST(2,0,0) //for CGLSetParameter -#if defined(__APPLE__) && !defined(GPAC_IPHONE) +#if defined(__APPLE__) && !defined(GPAC_CONFIG_IOS) #include <OpenGL/OpenGL.h> #endif @@ -1500,18 +1413,17 @@ static GF_Err SDLVid_Flush(GF_VideoOutput *dr, GF_Window *dest) /*if resizing don't process otherwise we may deadlock*/ if (!ctx->screen) return GF_OK; - //GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Flush\n")); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[SDL] swapping video buffers\n")); - if (ctx->output_3d_type==1) { + if (ctx->output_3d) { //with SDL2 we have to disable vsync by overriding swap interval -#if defined(__APPLE__) && !defined(GPAC_IPHONE) +#if defined(__APPLE__) && !defined(GPAC_CONFIG_IOS) if (ctx->disable_vsync) { GLint sync = 0; CGLContextObj gl_ctx = CGLGetCurrentContext(); CGLSetParameter(gl_ctx, kCGLCPSwapInterval, &sync); } #endif - SDL_GL_SwapWindow(ctx->screen); return GF_OK; } @@ -1547,7 +1459,7 @@ static GF_Err SDLVid_Flush(GF_VideoOutput *dr, GF_Window *dest) //GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Flush\n")); - if (ctx->output_3d_type==1) { + if (ctx->output_3d) { SDL_GL_SwapBuffers(); return GF_OK; } @@ -1589,28 +1501,6 @@ static GF_Err SDLVid_Flush(GF_VideoOutput *dr, GF_Window *dest) } #endif -static void SDLVid_SetCursor(GF_VideoOutput *dr, u32 cursor_type) -{ -#ifndef GPAC_IPHONE - SDLVID(); - switch (cursor_type) { - case GF_CURSOR_ANCHOR: - case GF_CURSOR_TOUCH: - case GF_CURSOR_ROTATE: - case GF_CURSOR_PROXIMITY: - case GF_CURSOR_PLANE: - SDL_SetCursor(ctx->curs_hand); - break; - case GF_CURSOR_COLLIDE: - SDL_SetCursor(ctx->curs_collide); - break; - default: - SDL_SetCursor(ctx->curs_def); - break; - } -#endif -} - #ifdef WIN32 static u32 get_sys_col(int idx) @@ -1633,22 +1523,11 @@ static GF_Err SDLVid_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt) SDLVID(); SDLVid_ProcessMessageQueue(ctx, dr); #endif - -#if !defined(GPAC_IPHONE) && SDL_VERSION_ATLEAST(2,0,0) - { - SDLVID(); - if (ctx->szCaption[0]) { - SDL_SetWindowTitle(ctx->screen, ctx->szCaption); - ctx->szCaption[0]=0; - } - } -#endif - return GF_OK; } switch (evt->type) { case GF_EVENT_SET_CURSOR: -#ifndef GPAC_IPHONE +#ifndef GPAC_CONFIG_IOS SDLVid_SetCursor(dr, evt->cursor.cursor_type); #endif break; @@ -1662,9 +1541,11 @@ static GF_Err SDLVid_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt) #endif #if SDL_VERSION_ATLEAST(2,0,0) { +#if !defined(GPAC_CONFIG_IOS) SDLVID(); - strncpy(ctx->szCaption, evt->caption.caption, 99); - ctx->szCaption[99]=0; + SDL_SetWindowTitle(ctx->screen, evt->caption.caption); + SDLVid_ProcessEvent(dr, NULL); +#endif } #else SDL_WM_SetCaption(evt->caption.caption, NULL); @@ -1677,7 +1558,7 @@ static GF_Err SDLVid_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt) case GF_EVENT_SIZE: { SDLVID(); -#ifdef GPAC_IPHONE +#ifdef GPAC_CONFIG_IOS if (ctx->fullscreen) { } else { SDLVid_ResizeWindow(dr, evt->size.width, evt->size.height); @@ -1694,7 +1575,7 @@ static GF_Err SDLVid_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt) break; case GF_EVENT_MOVE: -#if !defined(GPAC_IPHONE) && SDL_VERSION_ATLEAST(2,0,0) +#if !defined(GPAC_CONFIG_IOS) && SDL_VERSION_ATLEAST(2,0,0) { SDLVID(); @@ -1716,12 +1597,12 @@ static GF_Err SDLVid_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt) case GF_EVENT_VIDEO_SETUP: { SDLVID(); - switch (evt->setup.opengl_mode) { - case 0: + ctx->disable_vsync=evt->setup.disable_vsync; + if (!evt->setup.use_opengl) { /*force a resetup of the window*/ - if (ctx->output_3d_type) { + if (ctx->output_3d) { ctx->width = ctx->height = 0; - ctx->output_3d_type = 0; + ctx->output_3d = GF_FALSE; SDLVid_ResetWindow(ctx); SDLVid_ResizeWindow(dr, evt->setup.width, evt->setup.height); } else { @@ -1729,32 +1610,24 @@ static GF_Err SDLVid_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt) SDLVid_ResizeWindow(dr, evt->setup.width, evt->setup.height); #endif } - ctx->output_3d_type = 0; + ctx->output_3d = GF_FALSE; return SDLVid_SetBackbufferSize(dr, evt->setup.width, evt->setup.height, evt->setup.system_memory); - case 1: + } else { /*force a resetup of the window*/ - if (!ctx->output_3d_type) { + if (!ctx->output_3d) { ctx->width = ctx->height = 0; SDLVid_ResetWindow(ctx); } - ctx->output_3d_type = 1; + ctx->output_3d = GF_TRUE; GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[SDL] Setting up 3D in SDL.\n")); -#ifdef GPAC_IPHONE +#ifdef GPAC_CONFIG_IOS // return SDLVid_ResizeWindow(dr, dr->max_screen_width, dr->max_screen_height); return SDLVid_ResizeWindow(dr, evt->setup.width, evt->setup.height); #else return SDLVid_ResizeWindow(dr, evt->setup.width, evt->setup.height); #endif - case 2: - /*find a way to do that in SDL*/ - ctx->output_3d_type = 2; - GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] 3D not supported with SDL.\n")); - return GF_NOT_SUPPORTED; - } - default: - GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Trying to set an Unknown Mode %d !\n", evt->setup.opengl_mode)); - return GF_NOT_SUPPORTED; } + } break; case GF_EVENT_SYS_COLORS: #ifdef WIN32 @@ -1793,7 +1666,7 @@ static GF_Err SDLVid_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt) case GF_EVENT_TEXT_EDITING_START: case GF_EVENT_TEXT_EDITING_END: -#if defined(GPAC_IPHONE) && SDL_VERSION_ATLEAST(2,0,0) +#if defined(GPAC_CONFIG_IOS) && SDL_VERSION_ATLEAST(2,0,0) if (evt->type==GF_EVENT_TEXT_EDITING_START) { SDL_StartTextInput(); } else { @@ -1804,296 +1677,19 @@ static GF_Err SDLVid_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt) return GF_NOT_SUPPORTED; #endif - } - return GF_OK; -} - - -static void copy_yuv(u8 *pYD, u8 *pVD, u8 *pUD, u32 pixel_format, u32 pitch_y, unsigned char *src, unsigned char *pU, unsigned char *pV, u32 src_stride, u32 src_pf, u32 src_width, u32 src_height, const GF_Window *src_wnd) -{ - unsigned char *pY; - pY = src; - if (src_pf == GF_PIXEL_YUV422) { - u32 i; - unsigned char * dst, * dst2, * src1, * src2, * dst3, * src3, * _src2, * _src3; - - if (!pU || !pV) { - pU = src + src_stride * src_height; - pV = src + 3 * src_stride * src_height / 2; - } - - if (src_wnd -> y || src_wnd -> x) { - pY = pY + src_stride * src_wnd -> y + src_wnd -> x; - pU = pU + (src_stride * src_wnd -> y + src_wnd -> x) / 2; - pV = pV + (src_stride * src_wnd -> y + src_wnd -> x) / 2; - - } - - src1 = pY; - dst = pYD; - _src2 = (pixel_format != GF_PIXEL_YV12) ? pU : pV; - _src3 = (pixel_format != GF_PIXEL_YV12) ? pV : pU; - - for (i = 0; i < src_wnd -> h; i++) { - memcpy(dst, src1, src_wnd -> w); - src1 += src_stride; - dst += pitch_y; - } - for (i = 0; i < src_wnd -> h / 2; i++) { - src2 = _src2 + i * src_stride; - dst2 = pVD + i * pitch_y / 2; - src3 = _src3 + i * src_stride; - dst3 = pUD + i * pitch_y / 2; - memcpy(dst2, src2, src_wnd -> w / 2); - memcpy(dst3, src3, src_wnd -> w / 2); - } - - } else if (src_pf == GF_PIXEL_YUV444) { - u32 i, j; - unsigned char * dst, * dst2, * src1, * src2, * dst3, * src3, * _src2, * _src3; - - if (!pU || !pV) { - pU = src + src_stride * src_height; - pV = src + 2 * src_stride * src_height; - } - - if (src_wnd -> y || src_wnd -> x) { - pY = pY + src_stride * src_wnd -> y + src_wnd -> x; - pU = pU + src_stride * src_wnd -> y + src_wnd -> x; - pV = pV + src_stride * src_wnd -> y + src_wnd -> x; - - } - src1 = pY; - dst = pYD; - _src2 = (pixel_format != GF_PIXEL_YV12) ? pU : pV; - _src3 = (pixel_format != GF_PIXEL_YV12) ? pV : pU; - - for (i = 0; i < src_wnd -> h; i++) { - memcpy(dst, src1, src_wnd -> w); - src1 += src_stride; - dst += pitch_y; - } - for (i = 0; i < src_wnd -> h / 2; i++) { - src2 = _src2 + 2 * i * src_stride; - dst2 = pVD + i * pitch_y / 2; - src3 = _src3 + 2 * i * src_stride; - dst3 = pUD + i * pitch_y / 2; - for (j = 0; j < src_wnd -> w / 2; j++) { - * dst2 = * src2; - dst2++; - src2 += 2; - * dst3 = * src3; - dst3++; - src3 += 2; - } - } - - } else if (src_pf == GF_PIXEL_YUV422_10) { - u32 i, j; - unsigned char * _src2, * _src3; - u16 * src_y, * src_u, * src_v; - if (!pU || !pV) { - pU = src + src_stride * src_height; - pV = src + 3 * src_stride * src_height / 2; - } - - if (src_wnd -> y || src_wnd -> x) { - src_y = (u16 * ) pY + src_wnd -> x; - src_u = (u16 * ) pU + src_wnd -> x / 2; - src_v = (u16 * ) pV + src_wnd -> x / 2; - pY = (u8 * ) src_y + src_stride * src_wnd -> y; - pU = (u8 * ) src_u + src_stride * src_wnd -> y / 2; - pV = (u8 * ) src_v + src_stride * src_wnd -> y / 2; - - } - - _src2 = (pixel_format != GF_PIXEL_YV12) ? pU : pV; - _src3 = (pixel_format != GF_PIXEL_YV12) ? pV : pU; - for (i = 0; i < src_wnd -> h; i++) { - u16 * src = (u16 * )(pY + i * src_stride); - u8 * dst = (u8 * )(pYD + i * pitch_y); - for (j = 0; j < src_wnd -> w; j++) { - * dst = ( * src) >> 2; - dst++; - src++; - } - } - for (i = 0; i < src_wnd -> h / 2; i++) { - u16 * src2 = (u16 * )(_src2 + i * src_stride); - u8 * dst2 = (u8 * )(pVD + i * pitch_y / 2); - for (j = 0; j < src_wnd -> w / 2; j++) { - * dst2 = ( * src2) >> 2; - dst2++; - src2++; - } - } - for (i = 0; i < src_wnd -> h / 2; i++) { - u16 * src3 = (u16 * )(_src3 + i * src_stride); - u8 * dst3 = (u8 * )(pUD + i * pitch_y / 2); - for (j = 0; j < src_wnd -> w / 2; j++) { - * dst3 = ( * src3) >> 2; - dst3++; - src3++; - } - } - - } else if (src_pf == GF_PIXEL_YUV444_10) { - u32 i, j; - unsigned char * _src2, * _src3; - u16 * src_y, * src_u, * src_v; - - if (!pU || !pV) { - pU = src + src_stride * src_height; - pV = src + 2 * src_stride * src_height; - } - - if (src_wnd -> y || src_wnd -> x) { - src_y = (u16 * ) pY + src_wnd -> x; - src_u = (u16 * ) pU + src_wnd -> x; - src_v = (u16 * ) pV + src_wnd -> x; - pY = (u8 * ) src_y + src_stride * src_wnd -> y; - pU = (u8 * ) src_u + src_stride * src_wnd -> y; - pV = (u8 * ) src_v + src_stride * src_wnd -> y; - - } - - _src2 = (pixel_format != GF_PIXEL_YV12) ? pU : pV; - _src3 = (pixel_format != GF_PIXEL_YV12) ? pV : pU; - for (i = 0; i < src_wnd -> h; i++) { - u16 * src = (u16 * )(pY + i * src_stride); - u8 * dst = (u8 * ) pYD + i * pitch_y; - for (j = 0; j < src_wnd -> w; j++) { - * dst = ( * src) >> 2; - dst++; - src++; - } - } - for (i = 0; i < src_wnd -> h / 2; i++) { - u16 * src2 = (u16 * )(_src2 + 2 * i * src_stride); - u8 * dst2 = (u8 * ) pVD + i * pitch_y / 2; - for (j = 0; j < src_wnd -> w / 2; j++) { - * dst2 = ( * src2) >> 2; - dst2++; - src2 += 2; - } - } - for (i = 0; i < src_wnd -> h / 2; i++) { - u16 * src3 = (u16 * )(_src3 + 2 * i * src_stride); - u8 * dst3 = (u8 * ) pUD + i * pitch_y / 2; - for (j = 0; j < src_wnd -> w / 2; j++) { - * dst3 = ( * src3) >> 2; - dst3++; - src3 += 2; - } - } - - } else { - - if (!pU || !pV) { - pU = src + src_stride * src_height; - pV = src + 5*src_stride * src_height/4; - } - - if (src_wnd->y || src_wnd->x) { - pY = pY + src_stride * src_wnd->y + src_wnd->x; - /*because of U and V downsampling by 2x2, working with odd Y offset will lead to a half-line shift between Y and UV components. We - therefore force an even Y offset for U and V planes.*/ - pU = pU + (src_stride * (src_wnd->y / 2) + src_wnd->x) / 2; - pV = pV + (src_stride * (src_wnd->y / 2) + src_wnd->x) / 2; - } - - /*complete source copy*/ - if ( (pitch_y == (s32) src_stride) && (src_wnd->w == src_width) && (src_wnd->h == src_height)) { - assert(!src_wnd->x); - assert(!src_wnd->y); - memcpy(pYD, pY, sizeof(unsigned char)*src_width*src_height); - memcpy(pVD, pV, sizeof(unsigned char)*src_width*src_height/4); - memcpy(pUD, pU, sizeof(unsigned char)*src_width*src_height/4); - } else if (src_pf==GF_PIXEL_YUY2) { - u32 i, j; - unsigned char *dst_y, *dst_u, *dst_v; - - pY = src + src_stride * src_wnd->y + src_wnd->x; - pU = src + src_stride * src_wnd->y + src_wnd->x + 1; - pV = src + src_stride * src_wnd->y + src_wnd->x + 3; - - - dst_y = pYD; - dst_v = pVD; - dst_u = pUD; - for (i=0; i<src_wnd->h; i++) { - for (j=0; j<src_wnd->w; j+=2) { - *dst_y = * pY; - *(dst_y+1) = * (pY+2); - dst_y += 2; - pY += 4; - if (i%2) continue; - - *dst_u = (*pU + *(pU + src_stride)) / 2; - *dst_v = (*pV + *(pV + src_stride)) / 2; - dst_u++; - dst_v++; - pU += 4; - pV += 4; - } - if (i%2) { - pU += src_stride; - pV += src_stride; - } - } - } else if (src_pf==GF_PIXEL_YV12_10) { - u32 i, j; - for (i=0; i<src_wnd->h; i++) { - u16 *py = (u16 *) (pY + i*src_stride); - for (j=0; j<src_wnd->w; j++) { - *pYD = (*py) >> 2; - pYD ++; - py++; - } - } - for (i=0; i<src_wnd->h/2; i++) { - u16 *pu = (u16 *) (pU + i*src_stride/2); - for (j=0; j<src_wnd->w/2; j++) { - *pUD = (*pu) >> 2; - pUD ++; - pu++; - } - } - for (i=0; i<src_wnd->h/2; i++) { - u16 *pv = (u16 *) (pV + i*src_stride/2); - for (j=0; j<src_wnd->w/2; j++) { - *pVD = (*pv) >> 2; - pVD ++; - pv++; - } - } - - } else { - u32 i; - unsigned char *dst, *src, *dst2, *src2, *dst3, *src3; - - src = pY; - dst = pYD; - - src2 = (pixel_format != GF_PIXEL_YV12) ? pU : pV; - dst2 = pVD; - src3 = (pixel_format != GF_PIXEL_YV12) ? pV : pU; - dst3 = pUD; - for (i=0; i<src_wnd->h; i++) { - memcpy(dst, src, src_wnd->w); - src += src_stride; - dst += pitch_y; - if (i<src_wnd->h/2) { - memcpy(dst2, src2, src_wnd->w/2); - src2 += src_stride/2; - dst2 += pitch_y/2; - memcpy(dst3, src3, src_wnd->w/2); - src3 += src_stride/2; - dst3 += pitch_y/2; - } + case GF_EVENT_SET_GL: + { +#if SDL_VERSION_ATLEAST(2,0,0) + SDLVID(); + if (SDL_GL_MakeCurrent(ctx->screen, ctx->gl_context)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[SDL] Cannot make context current: %s\n", SDL_GetError())); + return GF_IO_ERR; } +#endif } - } + return GF_OK; + } + return GF_OK; } #if SDL_VERSION_ATLEAST(2,0,0) @@ -2139,7 +1735,7 @@ static GF_Err SDL_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window //this is a clear (not very elegant ...) if ((video_src->width<=2) && (video_src->height<=2)) { u8 *pix =(u8 *) video_src->video_buffer; - if (video_src->pixel_format == GF_PIXEL_RGB_24) { + if (video_src->pixel_format == GF_PIXEL_RGB) { SDL_SetRenderDrawColor(ctx->renderer, pix[0], pix[1], pix[2], 0xFF); } else { SDL_SetRenderDrawColor(ctx->renderer, pix[0], pix[1], pix[2], pix[3]); @@ -2153,16 +1749,32 @@ static GF_Err SDL_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window } switch (video_src->pixel_format) { - case GF_PIXEL_RGB_24: + case GF_PIXEL_RGB: pool = &ctx->pool_rgb; format=SDL_PIXELFORMAT_RGB24; break; + case GF_PIXEL_XRGB: + pool = &ctx->pool_rgb; + format=SDL_PIXELFORMAT_BGRX8888; + break; + case GF_PIXEL_RGBX: + pool = &ctx->pool_rgb; + format=SDL_PIXELFORMAT_BGR888; + break; + case GF_PIXEL_XBGR: + pool = &ctx->pool_rgb; + format=SDL_PIXELFORMAT_RGBX8888; + break; + case GF_PIXEL_BGRX: + pool = &ctx->pool_rgb; + format=SDL_PIXELFORMAT_RGB888; + break; case GF_PIXEL_RGBA: pool = &ctx->pool_rgba; format=SDL_PIXELFORMAT_ABGR8888; set_blend=1; break; - case GF_PIXEL_YV12: + case GF_PIXEL_YUV: pool = &ctx->pool_yuv; format=SDL_PIXELFORMAT_YV12; format=SDL_PIXELFORMAT_IYUV; @@ -2171,11 +1783,30 @@ static GF_Err SDL_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window case GF_PIXEL_YUV444: case GF_PIXEL_YUV444_10: case GF_PIXEL_YUV422_10: - case GF_PIXEL_YV12_10: + case GF_PIXEL_YUV_10: need_copy=1; pool = &ctx->pool_yuv; format=SDL_PIXELFORMAT_YV12; break; + /*FIXME we need to upgrade our SDL build*/ +#if !defined(GPAC_CONFIG_IOS) && SDL_VERSION_ATLEAST(2,0,0) + case GF_PIXEL_NV12: + pool = &ctx->pool_yuv; + format=SDL_PIXELFORMAT_NV12; + break; + case GF_PIXEL_NV21: + pool = &ctx->pool_yuv; + format=SDL_PIXELFORMAT_NV21; + break; +#endif + case GF_PIXEL_UYVY: + pool = &ctx->pool_yuv; + format=SDL_PIXELFORMAT_UYVY; + break; + case GF_PIXEL_YUYV: + pool = &ctx->pool_yuv; + format=SDL_PIXELFORMAT_YUY2; + break; default: return GF_NOT_SUPPORTED; } @@ -2196,18 +1827,31 @@ static GF_Err SDL_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window SDL_QueryTexture((*pool), &format, &acc, &w, &h); if (need_copy) { - u8* pixels; + GF_VideoSurface dst_v; + u8 *pixels; int pitch; - u8 *pY, *pU, *pV; + GF_Window swnd; /*copy pixels*/ if (SDL_LockTexture(*pool, NULL, (void**)&pixels, &pitch) < 0) { return GF_NOT_SUPPORTED; } + if (!src_wnd) { + swnd.x = swnd.y=0; + swnd.w = video_src->width; + swnd.h = video_src->height; + src_wnd = &swnd; + } - pY = pixels; - pU = pixels + h*pitch; - pV = pixels + 5*h*pitch/4; - copy_yuv(pY, pU, pV, GF_PIXEL_YV12, pitch, (unsigned char *) video_src->video_buffer, (unsigned char *) video_src->u_ptr, (unsigned char *) video_src->v_ptr, video_src->pitch_y, video_src->pixel_format, video_src->width, video_src->height, src_wnd); + memset(&dst_v, 0, sizeof(GF_VideoSurface)); + dst_v.video_buffer = pixels; + dst_v.u_ptr = pixels + h*pitch; + dst_v.v_ptr = pixels + 5*h*pitch/4; + dst_v.pitch_y = pitch; + dst_v.pixel_format = GF_PIXEL_YUV; + dst_v.width = video_src->width; + dst_v.height = video_src->height; + + gf_stretch_bits(&dst_v, video_src, NULL, src_wnd, 0xFF, GF_FALSE, NULL, NULL); SDL_UnlockTexture(*pool); } else { @@ -2256,6 +1900,7 @@ static GF_Err SDL_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[SDL] Bliting surface (overlay type %d)\n", overlay_type)); if (overlay_type) { + GF_VideoSurface dst_v; if (!video_src) { if (ctx->yuv_overlay) { SDL_FreeYUVOverlay(ctx->yuv_overlay); @@ -2272,9 +1917,17 @@ static GF_Err SDL_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window /*copy pixels*/ SDL_LockYUVOverlay(ctx->yuv_overlay); - copy_yuv(ctx->yuv_overlay->pixels[0], ctx->yuv_overlay->pixels[1], ctx->yuv_overlay->pixels[2], GF_PIXEL_YV12, ctx->yuv_overlay->pitches[0], - (unsigned char *) video_src->video_buffer, (unsigned char *) video_src->u_ptr, (unsigned char *) video_src->v_ptr, video_src->pitch_y, video_src->pixel_format, - video_src->width, video_src->height, src_wnd); + + memset(&dst_v, 0, sizeof(GF_VideoSurface)); + dst_v.video_buffer = (char *) ctx->yuv_overlay->pixels[0]; + dst_v.u_ptr = (char *) ctx->yuv_overlay->pixels[1]; + dst_v.v_ptr = (char *) ctx->yuv_overlay->pixels[2]; + dst_v.pitch_y = ctx->yuv_overlay->pitches[0]; + dst_v.pixel_format = GF_PIXEL_YUV; + dst_v.width = video_src->width; + dst_v.height = video_src->height; + + gf_stretch_bits(&dst_v, video_src, NULL, src_wnd, 0xFF, GF_FALSE, NULL, NULL); SDL_UnlockYUVOverlay(ctx->yuv_overlay); @@ -2291,7 +1944,7 @@ static GF_Err SDL_Blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window return GF_NOT_SUPPORTED; switch (video_src->pixel_format) { - case GF_PIXEL_RGB_24: + case GF_PIXEL_RGB: pool = &ctx->pool_rgb; bpp = 3; break; @@ -2378,7 +2031,7 @@ void *SDL_NewVideo() driv->hw_caps |= GF_VIDEO_HW_HAS_YUV | GF_VIDEO_HW_HAS_STRETCH | GF_VIDEO_HW_HAS_RGBA; - opt = gf_modules_get_option((GF_BaseInterface *)driv, "Video", "SDL_DeferMode"); + opt = gf_opts_get_key("core", "sdl-defer"); ctx->enable_defer_mode = 0; if (opt && !strcmp(opt, "yes")) ctx->enable_defer_mode = 1; @@ -2390,6 +2043,9 @@ void *SDL_NewVideo() #endif +#ifdef GPAC_ENABLE_COVERAGE + SDLVid_MapPixelFormat(NULL, GF_FALSE); +#endif driv->Blit = SDL_Blit; driv->LockBackBuffer = SDLVid_LockBackBuffer; @@ -2399,6 +2055,7 @@ void *SDL_NewVideo() #else SDL_StartTextInput(); #endif /* SDL_TEXTINPUTEVENT_TEXT_SIZE */ + return driv; } diff --git a/modules/soft_raster/Makefile b/modules/soft_raster/Makefile deleted file mode 100644 index 1b7d52a..0000000 --- a/modules/soft_raster/Makefile +++ /dev/null @@ -1,55 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/soft_raster - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#big-endian config (needed for ARGB pixel format) -ifeq ($(IS_BIGENDIAN), yes) -CFLAGS+=-DEVG_BIG_ENDIAN -endif - - -#common obj -OBJS= ftgrays.o raster_load.o raster_565.o raster_argb.o raster_rgb.o stencil.o surface.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_soft_raster$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols rast_soft.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_soft_raster-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac_static $(LDFLAGS) -endif - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/soft_raster/rast_soft.h b/modules/soft_raster/rast_soft.h deleted file mode 100644 index 725a0e5..0000000 --- a/modules/soft_raster/rast_soft.h +++ /dev/null @@ -1,364 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / software 2D rasterizer module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - * - */ - - -#ifndef _RAST_SOFT_H_ -#define _RAST_SOFT_H_ - -#include <gpac/modules/raster2d.h> - -#ifdef __cplusplus -extern "C" { -#endif - - -/*RGB 555 support is disabled by default*/ -//#define GF_RGB_555_SUPORT - -/*for symbian enable 4k color depth (RGB444, 4096 colors) since a good amount of devices use that*/ -#ifdef __SYMBIAN32__ -#define GF_RGB_444_SUPORT -#endif - - -typedef struct _evg_surface EVGSurface; - -/*base stencil stack*/ -#define EVGBASESTENCIL \ - u32 type; \ - void (*fill_run)(struct _evg_base_stencil *p, EVGSurface *surf, s32 x, s32 y, u32 count); \ - GF_Matrix2D pmat; \ - GF_Matrix2D smat; \ - GF_Rect frame; \ - GF_ColorMatrix cmat; \ - -typedef struct _evg_base_stencil -{ - EVGBASESTENCIL -} EVGStencil; - -/*define this macro to convert points in the outline decomposition rather than when attaching path -to surface. When point conversion is inlined in the raster, memory usage is lower (no need for temp storage)*/ -#define INLINE_POINT_CONVERSION - -#ifdef INLINE_POINT_CONVERSION -typedef struct __vec2f EVG_Vector; -#else -typedef struct -{ - s32 x; - s32 y; -} EVG_Vector; -#endif - -typedef struct -{ - s32 n_contours; - s32 n_points; - EVG_Vector *points; - u8 *tags; - s32 *contours; - s32 flags; /*same as path flags*/ -} EVG_Outline; - -typedef struct TRaster_ *EVG_Raster; - -typedef struct EVG_Span_ -{ - short x; - unsigned short len; - unsigned char coverage; -} EVG_Span; - -typedef void (*EVG_SpanFunc)(int y, int count, EVG_Span *spans, void *user); -#define EVG_Raster_Span_Func EVG_SpanFunc - -typedef struct EVG_Raster_Params_ -{ - EVG_Outline *source; -#ifdef INLINE_POINT_CONVERSION - GF_Matrix2D *mx; -#endif - EVG_SpanFunc gray_spans; - - s32 clip_xMin, clip_yMin, clip_xMax, clip_yMax; - - void *user; -} EVG_Raster_Params; - - -EVG_Raster evg_raster_new(); -void evg_raster_del(EVG_Raster raster); -int evg_raster_render(EVG_Raster raster, EVG_Raster_Params *params); - -/*the surface object - currently only ARGB/RGB32, RGB/BGR and RGB555/RGB565 supported*/ -struct _evg_surface -{ - /*surface info*/ - char *pixels; - u32 pixelFormat, BPP; - u32 width, height; - s32 pitch_x, pitch_y; - Bool center_coords; - - /*color buffer for variable stencils - size of width*/ - u32 *stencil_pix_run; - - /*default texture filter level*/ - u32 texture_filter; - - u32 useClipper; - GF_IRect clipper; - - GF_Rect path_bounds; - - /*complete path transform (including page flipping/recenter)*/ - GF_Matrix2D mat; - - /*stencil currently used*/ - EVGStencil *sten; - - void *raster_cbk; - /*fills line pixels without any blending operation*/ - void (*raster_fill_run_no_alpha)(void *cbk, u32 x, u32 y, u32 run_h_len, GF_Color color); - /*fills line pixels with blending operation - alpha combines both fill color and anti-aliasing blending*/ - void (*raster_fill_run_alpha)(void *cbk, u32 x, u32 y, u32 run_h_len, GF_Color color, u8 alpha); - /*fills rectangle with or without blending operation - alpha combines both fill color and anti-aliasing blending*/ - void (*raster_fill_rectangle)(void *cbk, u32 x, u32 y, u32 width, u32 height, GF_Color color); - - - /*in solid color mode to speed things*/ - u32 fill_col; - u32 fill_565; - -#ifdef GF_RGB_444_SUPORT - u32 fill_444; -#endif - -#ifdef GF_RGB_555_SUPORT - u32 fill_555; -#endif - - /*FreeType raster*/ - EVG_Raster raster; - - /*FreeType outline (path converted to ft)*/ - EVG_Outline ftoutline; - EVG_Raster_Params ftparams; - -#ifndef INLINE_POINT_CONVERSION - /*transformed point list*/ - u32 pointlen; - EVG_Vector *points; -#endif -}; - -/*solid color brush*/ -typedef struct -{ - EVGBASESTENCIL - GF_Color color; -} EVG_Brush; - -/*max number of interpolation points*/ -#define EVGGRADIENTSLOTS 12 - -/*(2^EVGGRADIENTBITS)-1 colors in gradient. Below 6 bits the gradient is crappy */ -#define EVGGRADIENTBITS 10 - - -/*base gradient stencil*/ -#define EVGGRADIENT \ - s32 mod; \ - u32 pre[(1<<EVGGRADIENTBITS)]; \ - u32 col[EVGGRADIENTSLOTS]; \ - Fixed pos[EVGGRADIENTSLOTS]; \ - u8 alpha; \ - -typedef struct -{ - EVGBASESTENCIL - EVGGRADIENT -} EVG_BaseGradient; - -/*linear gradient*/ -typedef struct -{ - EVGBASESTENCIL - EVGGRADIENT - - GF_Point2D start, end; - GF_Matrix2D vecmat; - s32 curp; - Fixed pos_ft; -} EVG_LinearGradient; - -/*radial gradient*/ -typedef struct -{ - EVGBASESTENCIL - EVGGRADIENT - - GF_Point2D center, focus, radius; - /*drawing state vars*/ - GF_Point2D cur_p, d_f, d_i; - Fixed rad; -} EVG_RadialGradient; - -/*texture stencil - should be reworked, is very slow*/ -typedef struct -{ - EVGBASESTENCIL - u32 width, height, stride; - u32 pixel_format, Bpp; - char *pixels; - - GF_Point2D cur_pt; - Fixed cur_y, inc_x, inc_y; - - u32 mod, filter; - u32 replace_col; - Bool cmat_is_replace; - - u8 alpha; - - /*YUV->RGB local buffer*/ - unsigned char *conv_buf; - u32 conv_size; - unsigned char *orig_buf; - u32 orig_stride, orig_format; - Bool is_converted; - - Bool owns_texture; - - u32 (*tx_get_pixel)(char *pix); -} EVG_Texture; - -#define GF_TEXTURE_FILTER_DEFAULT GF_TEXTURE_FILTER_HIGH_SPEED - - -void evg_radial_init(EVG_RadialGradient *_this) ; -void evg_bmp_init(EVGStencil *p); -void evg_set_texture_active(EVGStencil *st); - - -GF_STENCIL evg_stencil_new(GF_Raster2D *, GF_StencilType type); -void evg_stencil_delete(GF_STENCIL st); -GF_Err evg_stencil_set_matrix(GF_STENCIL st, GF_Matrix2D *mx); -GF_Err evg_stencil_set_brush_color(GF_STENCIL st, GF_Color c); -GF_Err evg_stencil_set_linear_gradient(GF_STENCIL st, Fixed start_x, Fixed start_y, Fixed end_x, Fixed end_y); -GF_Err evg_stencil_set_radial_gradient(GF_STENCIL st, Fixed cx, Fixed cy, Fixed fx, Fixed fy, Fixed x_radius, Fixed y_radius); -GF_Err evg_stencil_set_gradient_interpolation(GF_STENCIL p, Fixed *pos, GF_Color *col, u32 count); -GF_Err evg_stencil_set_gradient_mode(GF_STENCIL p, GF_GradientMode mode); - -GF_Err evg_stencil_set_alpha(GF_STENCIL st, u8 alpha); - -/*texture only*/ -GF_Err evg_stencil_set_texture(GF_STENCIL st, char *pixels, u32 width, u32 height, u32 stride, GF_PixelFormat pixelFormat, GF_PixelFormat destination_format_hint, Bool no_copy); -GF_Err evg_stencil_set_tiling(GF_STENCIL st, GF_TextureTiling mode); -GF_Err evg_stencil_set_filter(GF_STENCIL st, GF_TextureFilter filter_mode); -GF_Err evg_stencil_set_color_matrix(GF_STENCIL st, GF_ColorMatrix *cmat); - - - -GF_SURFACE evg_surface_new(GF_Raster2D *, Bool center_coords); -void evg_surface_delete(GF_SURFACE _this); -void evg_surface_detach(GF_SURFACE _this); -GF_Err evg_surface_attach_to_buffer(GF_SURFACE _this, char *pixels, u32 width, u32 height, s32 pitch_x, s32 pitch_y, GF_PixelFormat pixelFormat); -GF_Err evg_surface_attach_to_texture(GF_SURFACE _this, GF_STENCIL sten); -GF_Err evg_surface_attach_to_callbacks(GF_SURFACE _this, GF_RasterCallback *callbacks, u32 width, u32 height); - -GF_Err evg_surface_set_matrix(GF_SURFACE surf, GF_Matrix2D *mat); -GF_Err evg_surface_set_raster_level(GF_SURFACE surf , GF_RasterLevel RasterSetting); -GF_Err evg_surface_set_clipper(GF_SURFACE surf, GF_IRect *rc); -GF_Err evg_surface_set_path(GF_SURFACE surf, GF_Path *gp); -GF_Err evg_surface_fill(GF_SURFACE surf, GF_STENCIL stencil); -GF_Err evg_surface_clear(GF_SURFACE surf, GF_IRect *rc, u32 color); - - -/*FT raster callbacks */ -void evg_bgra_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_bgra_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_bgra_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -GF_Err evg_surface_clear_bgra(GF_SURFACE surf, GF_IRect rc, GF_Color col); - -void evg_rgba_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_rgba_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_rgba_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_rgba_fill_erase(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -GF_Err evg_surface_clear_rgba(GF_SURFACE surf, GF_IRect rc, GF_Color col); - -void evg_bgrx_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_bgrx_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_bgrx_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); - -void evg_rgbx_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_rgbx_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_rgbx_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -GF_Err evg_surface_clear_rgbx(GF_SURFACE surf, GF_IRect rc, GF_Color col); - -void evg_rgb_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_rgb_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_rgb_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -GF_Err evg_surface_clear_rgb(GF_SURFACE surf, GF_IRect rc, GF_Color col); - -void evg_bgr_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_bgr_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_bgr_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -GF_Err evg_surface_clear_bgr(GF_SURFACE surf, GF_IRect rc, GF_Color col); - -void evg_565_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_565_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_565_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -GF_Err evg_surface_clear_565(GF_SURFACE _this, GF_IRect rc, GF_Color col); - -#ifdef GF_RGB_444_SUPORT -void evg_444_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_444_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_444_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -GF_Err evg_surface_clear_444(GF_SURFACE surf, GF_IRect rc, GF_Color col); -#endif - -#ifdef GF_RGB_555_SUPORT -void evg_555_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_555_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_555_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -GF_Err evg_surface_clear_555(GF_SURFACE surf, GF_IRect rc, GF_Color col); -#endif - -void evg_user_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_user_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); -void evg_user_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf); - - - - -#ifdef __cplusplus -} -#endif - -#endif - diff --git a/modules/soft_raster/raster_565.c b/modules/soft_raster/raster_565.c deleted file mode 100644 index c927327..0000000 --- a/modules/soft_raster/raster_565.c +++ /dev/null @@ -1,495 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / Scene Rendering sub-project - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - * - */ - - -#include "rast_soft.h" - - -/* - FIXME / WARNING - this code only works for little endian platfoms - not seen any big endian platform using RGB565/RGB555 output -*/ - - -static GFINLINE s32 -mul255(s32 a, s32 b) -{ - return ((a + 1) * b) >> 8; -} - - -/* - RGB 565 part -*/ - -static u16 overmask_565(u32 src, u16 dst, u32 alpha) -{ - u32 resr, resg, resb; - s32 srca = (src >> 24) & 0xff; - s32 srcr = (src >> 16) & 0xff; - s32 srcg = (src >> 8) & 0xff; - s32 srcb = (src >> 0) & 0xff; - - s32 dstr = (dst >> 8) & 0xf8; - s32 dstg = (dst >> 3) & 0xfc; - s32 dstb = (dst << 3) & 0xf8; - - srca = mul255(srca, alpha); - resr = mul255(srca, srcr - dstr) + dstr; - resg = mul255(srca, srcg - dstg) + dstg; - resb = mul255(srca, srcb - dstb) + dstb; - return GF_COL_565(resr, resg, resb); -} - -void overmask_565_const_run(u32 src, u16 *dst, s32 dst_pitch_x, u32 count) -{ - u32 resr, resg, resb; - u8 srca = (src >> 24) & 0xff; - u8 srcr = (src >> 16) & 0xff; - u8 srcg = (src >> 8) & 0xff; - u8 srcb = (src >> 0) & 0xff; - - while (count) { - register u16 val = *dst; - register u8 dstr = (val >> 8) & 0xf8; - register u8 dstg = (val >> 3) & 0xfc; - register u8 dstb = (val << 3) & 0xf8; - - resr = mul255(srca, srcr - dstr) + dstr; - resg = mul255(srca, srcg - dstg) + dstg; - resb = mul255(srca, srcb - dstb) + dstb; - *dst = GF_COL_565(resr, resg, resb); - dst = (u16*) (((u8*)dst)+dst_pitch_x); - count--; - } -} - -void evg_565_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u16 col565 = surf->fill_565; - u32 col = surf->fill_col; - register u32 a, fin, col_no_a; - u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; - register s32 i; - u32 len; - s32 x; - - col_no_a = col&0x00FFFFFF; - - for (i=0; i<count; i++) { - x = spans[i].x * surf->pitch_x; - len = spans[i].len; - if (spans[i].coverage != 0xFF) { - a = mul255(0xFF, spans[i].coverage); - fin = (a<<24) | (col_no_a); - overmask_565_const_run(fin, (u16*) (dst+x), surf->pitch_x, len); - } else { - while (len--) { - *(u16*) (dst + x) = col565; - x+=surf->pitch_x; - } - } - } -} - -void evg_565_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; - u32 col = surf->fill_col; - register u32 a, fin, col_no_a; - register s32 i; - - a = (col>>24)&0xFF; - col_no_a = col&0x00FFFFFF; - for (i=0; i<count; i++) { - fin = mul255(a, spans[i].coverage); - fin = (fin<<24) | col_no_a; - overmask_565_const_run(fin, (u16*) (dst + spans[i].x * surf->pitch_x), surf->pitch_x, spans[i].len); - } -} - - -void evg_565_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; - register u8 spanalpha, col_a; - register s32 i, x; - register u32 len; - register u32 *col; - - for (i=0; i<count; i++) { - len = spans[i].len; - spanalpha = spans[i].coverage; - surf->sten->fill_run(surf->sten, surf, spans[i].x, y, len); - col = surf->stencil_pix_run; - x = spans[i].x * surf->pitch_x; - while (len--) { - col_a = GF_COL_A(*col); - if (col_a) { - if ((spanalpha!=0xFF) || (col_a != 0xFF)) { - *(u16*) (dst+x) = overmask_565(*col, *(u16*) (dst+x), spanalpha); - } else { - *(u16*) (dst+x) = GF_COL_TO_565(*col); - } - } - col++; - x += surf->pitch_x; - } - } -} - -GF_Err evg_surface_clear_565(GF_SURFACE surf, GF_IRect rc, GF_Color col) -{ - register u32 x, y, w, h, sx, sy; - s32 st; - register u16 val; - EVGSurface *_this = (EVGSurface *)surf; - st = _this->pitch_y; - - h = rc.height; - w = rc.width; - sx = rc.x; - sy = rc.y; - - /*convert to 565*/ - val = GF_COL_TO_565(col); - - for (y=0; y<h; y++) { - u8 *data = (u8 *) _this->pixels + (sy+y) * st + _this->pitch_x*sx; - for (x=0; x<w; x++) { - *(u16*) data = val; - data += _this->pitch_x; - } - } - return GF_OK; -} - - - -/* - RGB 555 part -*/ -#ifdef GF_RGB_555_SUPORT - -static u16 overmask_555(u32 src, u16 dst, u32 alpha) -{ - u32 resr, resg, resb; - s32 srca = (src >> 24) & 0xff; - s32 srcr = (src >> 16) & 0xff; - s32 srcg = (src >> 8) & 0xff; - s32 srcb = (src >> 0) & 0xff; - - s32 dstr = (dst >> 7) & 0xf8; - s32 dstg = (dst >> 2) & 0xf8; - s32 dstb = (dst << 3) & 0xf8; - - srca = mul255(srca, alpha); - resr = mul255(srca, srcr - dstr) + dstr; - resg = mul255(srca, srcg - dstg) + dstg; - resb = mul255(srca, srcb - dstb) + dstb; - return GF_COL_555(resr, resg, resb); -} - -static void overmask_555_const_run(u32 src, u16 *dst, s32 dst_pitch_x, u32 count) -{ - u32 resr, resg, resb; - u8 srca = (src >> 24) & 0xff; - u8 srcr = (src >> 16) & 0xff; - u8 srcg = (src >> 8) & 0xff; - u8 srcb = (src >> 0) & 0xff; - - while (count) { - u16 val = *dst; - u8 dstr = (val >> 7) & 0xf8; - u8 dstg = (val >> 2) & 0xf8; - u8 dstb = (val << 3) & 0xf8; - - resr = mul255(srca, srcr - dstr) + dstr; - resg = mul255(srca, srcg - dstg) + dstg; - resb = mul255(srca, srcb - dstb) + dstb; - *dst = GF_COL_555(resr, resg, resb); - dst = (u16*) (((u8*)dst)+dst_pitch_x); - count--; - } -} - -void evg_555_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u16 col555 = surf->fill_555; - u32 col = surf->fill_col; - u32 a, fin, col_no_a; - u8 *dst = surf->pixels + y * surf->pitch_y; - s32 i, x; - u32 len; - u8 aa_lev = surf->AALevel; - - col_no_a = col&0x00FFFFFF; - for (i=0; i<count; i++) { - if (spans[i].coverage<aa_lev) continue; - x = spans[i].x * surf->pitch_x; - len = spans[i].len; - if (spans[i].coverage != 0xFF) { - a = mul255(0xFF, spans[i].coverage); - fin = (a<<24) | col_no_a; - overmask_555_const_run(fin, (u16*) (dst+x), surf->pitch_x, len); - } else { - while (len--) { - *(u16*) (dst+x) = col555; - x+=surf->pitch_x; - } - } - } -} - -void evg_555_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u8 *dst = surf->pixels + y * surf->pitch_x; - u32 col = surf->fill_col; - u32 a, fin, col_no_a; - s32 i; - u8 aa_lev = surf->AALevel; - - a = (col>>24)&0xFF; - col_no_a = col & 0x00FFFFFF; - for (i=0; i<count; i++) { - if (spans[i].coverage<aa_lev) continue; - fin = mul255(a, spans[i].coverage); - fin = (fin<<24) | col_no_a; - overmask_555_const_run(fin, (u16*) (dst + spans[i].x*surf->pitch_x), surf->pitch_x, spans[i].len); - } -} - - -void evg_555_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u8 *dst = surf->pixels + y * surf->pitch_y; - u8 spanalpha, col_a; - s32 i, x; - u32 len; - u32 *col; - u8 aa_lev = surf->AALevel; - - for (i=0; i<count; i++) { - if (spans[i].coverage<aa_lev) continue; - len = spans[i].len; - spanalpha = spans[i].coverage; - surf->sten->fill_run(surf->sten, surf, spans[i].x, y, len); - col = surf->stencil_pix_run; - x = spans[i].x * surf->pitch_x; - while (len--) { - col_a = GF_COL_A(*col); - if (col_a) { - if ((spanalpha!=0xFF) || (col_a != 0xFF)) { - *(u16*) (dst+x) = overmask_555(*col, *(u16*) (dst+x), spanalpha); - } else { - *(u16*) (dst+x) = GF_COL_TO_555(*col); - } - } - col++; - x += surf->pitch_x; - } - } -} - -GF_Err evg_surface_clear_555(GF_SURFACE surf, GF_IRect rc, GF_Color col) -{ - u32 x, y, w, h, sx, sy; - u16 val; - EVGSurface *_this = (EVGSurface *)surf; - - - h = rc.height; - w = rc.width; - sx = rc.x; - sy = rc.y; - - /*convert to 565*/ - val = GF_COL_TO_555(col); - - for (y=0; y<h; y++) { - u8 *data = _this->pixels + (sy+y) * _this->pitch_y + _this->pitch_x*sx; - for (x=0; x<w; x++) { - *(u16*)data = val; - data += _this->pitch_x; - } - } - return GF_OK; -} - -#endif - - -/* - RGB 444 part -*/ - -#ifdef GF_RGB_444_SUPORT - -static u16 overmask_444(u32 src, u16 dst, u32 alpha) -{ - u32 resr, resg, resb; - s32 srca = (src >> 24) & 0xff; - s32 srcr = (src >> 16) & 0xff; - s32 srcg = (src >> 8) & 0xff; - s32 srcb = (src >> 0) & 0xff; - - s32 dstr = (dst >> 4) & 0xf0; - s32 dstg = (dst ) & 0xf0; - s32 dstb = (dst << 4) & 0xf0; - - srca = mul255(srca, alpha); - resr = mul255(srca, srcr - dstr) + dstr; - resg = mul255(srca, srcg - dstg) + dstg; - resb = mul255(srca, srcb - dstb) + dstb; - return GF_COL_444(resr, resg, resb); -} - -static void overmask_444_const_run(u32 src, u16 *dst, s32 dst_pitch_x, u32 count) -{ - u32 resr, resg, resb; - u8 srca = (src >> 24) & 0xff; - u8 srcr = (src >> 16) & 0xff; - u8 srcg = (src >> 8) & 0xff; - u8 srcb = (src >> 0) & 0xff; - - while (count) { - u16 val = *dst; - u8 dstr = (val >> 4) & 0xf0; - u8 dstg = (val ) & 0xf0; - u8 dstb = (val << 4) & 0xf0; - - resr = mul255(srca, srcr - dstr) + dstr; - resg = mul255(srca, srcg - dstg) + dstg; - resb = mul255(srca, srcb - dstb) + dstb; - *dst = GF_COL_444(resr, resg, resb); - dst = (u16*) (((u8*)dst)+dst_pitch_x); - count--; - } -} - -void evg_444_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u16 col444 = surf->fill_444; - u32 col = surf->fill_col; - u32 a, fin, col_no_a; - u8 *dst = surf->pixels + y * surf->pitch_y; - s32 i, x; - u32 len; - u8 aa_lev = surf->AALevel; - - col_no_a = col&0x00FFFFFF; - for (i=0; i<count; i++) { - if (spans[i].coverage<aa_lev) continue; - x = spans[i].x * surf->pitch_x; - len = spans[i].len; - if (spans[i].coverage != 0xFF) { - a = mul255(0xFF, spans[i].coverage); - fin = (a<<24) | col_no_a; - overmask_444_const_run(fin, (u16*) (dst+x), surf->pitch_x, len); - } else { - while (len--) { - *(u16*) (dst+x) = col444; - x+=surf->pitch_x; - } - } - } -} - -void evg_444_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u8 *dst = surf->pixels + y * surf->pitch_y; - u32 col = surf->fill_col; - u32 a, fin, col_no_a; - s32 i; - u8 aa_lev = surf->AALevel; - - a = (col>>24)&0xFF; - col_no_a = col & 0x00FFFFFF; - for (i=0; i<count; i++) { - if (spans[i].coverage<aa_lev) continue; - fin = mul255(a, spans[i].coverage); - fin = (fin<<24) | col_no_a; - overmask_444_const_run(fin, (u16*) (dst + spans[i].x*surf->pitch_x), surf->pitch_x, spans[i].len); - } -} - - -void evg_444_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u8 *dst = surf->pixels + y * surf->pitch_x; - u8 spanalpha, col_a; - s32 i, x; - u32 len; - u32 *col; - u8 aa_lev = surf->AALevel; - - for (i=0; i<count; i++) { - if (spans[i].coverage<aa_lev) continue; - len = spans[i].len; - spanalpha = spans[i].coverage; - surf->sten->fill_run(surf->sten, surf, spans[i].x, y, len); - col = surf->stencil_pix_run; - x = spans[i].x + surf->pitch_x; - while (len--) { - col_a = GF_COL_A(*col); - if (col_a) { - if ((spanalpha!=0xFF) || (col_a != 0xFF)) { - *(u16*) (dst+x) = overmask_444(*col, *(u16*) (dst+x), spanalpha); - } else { - *(u16*) (dst+x) = GF_COL_TO_444(*col); - } - } - col++; - x += surf->pitch_x; - } - } -} - -GF_Err evg_surface_clear_444(GF_SURFACE surf, GF_IRect rc, GF_Color col) -{ - u32 x, y, w, h, sx, sy; - u16 val; - EVGSurface *_this = (EVGSurface *)surf; - - h = rc.height; - w = rc.width; - sx = rc.x; - sy = rc.y; - - val = GF_COL_TO_444(col); - - for (y=0; y<h; y++) { - u8 *data = _this->pixels + (sy+y) * _this->pitch_y + _this->pitch_x*sx; - for (x=0; x<w; x++) { - *(u16*)data = val; - data += _this->pitch_x; - } - } - return GF_OK; -} - -#endif - diff --git a/modules/soft_raster/raster_argb.c b/modules/soft_raster/raster_argb.c deleted file mode 100644 index 9bf359b..0000000 --- a/modules/soft_raster/raster_argb.c +++ /dev/null @@ -1,730 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / software 2D rasterizer module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - * - */ - -#include "rast_soft.h" - - -static GFINLINE s32 -mul255(s32 a, s32 b) -{ - return ((a+1) * b) >> 8; -} - -/* - 32 bit ARGB -*/ - -static void overmask_bgra(u32 src, u8 *dst, u32 alpha) -{ - s32 srca = (src >> 24) & 0xff; - s32 srcr = (src >> 16) & 0xff; - s32 srcg = (src >> 8) & 0xff; - s32 srcb = (src >> 0) & 0xff; - s32 dsta = dst[3]; - srca = mul255(srca, alpha); - if (dsta) { - s32 dstr = dst[1]; - s32 dstg = dst[1]; - s32 dstb = dst[0]; - dst[0] = mul255(srca, srcb - dstb) + dstb; - dst[1] = mul255(srca, srcg - dstg) + dstg; - dst[2] = mul255(srca, srcr - dstr) + dstr; - dst[3] = mul255(srca, srca) + mul255(255-srca, dsta); - } else { - dst[0] = srcb; - dst[1] = srcg; - dst[2] = srcr; - dst[3] = srca; - } -} - -static void overmask_bgra_const_run(u32 src, u8 *dst, s32 dst_pitch_x, u32 count) -{ - s32 srca = (src >> 24) & 0xff; - s32 srcr = (src >> 16) & 0xff; - s32 srcg = (src >> 8) & 0xff; - s32 srcb = (src >> 0) & 0xff; - - - while (count) { - s32 dsta = dst[3]; - /*special case for ARGB: if dst alpha is 0, consider the surface is empty and copy pixel*/ - if (dsta) { - s32 dstb = dst[0]; - s32 dstg = dst[1]; - s32 dstr = dst[2]; - - dst[0] = mul255(srca, srcb - dstb) + dstb; - dst[1] = mul255(srca, srcg - dstg) + dstg; - dst[2] = mul255(srca, srcr - dstr) + dstr; - dst[3] = mul255(srca, srca) + mul255(255-srca, dsta); - } else { - dst[0] = srcr; - dst[1] = srcg; - dst[2] = srcr; - dst[3] = srca; - } - dst += dst_pitch_x; - count--; - } -} - -void evg_bgra_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u32 col = surf->fill_col; - u32 a, fin, col_no_a; - u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; - s32 i, x; - u32 len; - u8 col_a, col_r, col_g, col_b; - - col_a = GF_COL_A(col); - col_r = GF_COL_R(col); - col_g = GF_COL_G(col); - col_b = GF_COL_B(col); - col_no_a = col & 0x00FFFFFF; - for (i=0; i<count; i++) { - x = spans[i].x * surf->pitch_x; - len = spans[i].len; - - if (spans[i].coverage != 0xFF) { - a = mul255(0xFF, spans[i].coverage); - fin = (a<<24) | col_no_a; - overmask_bgra_const_run(fin, dst + x, surf->pitch_x, len); - } else { - while (len--) { - dst[x] = col_b; - dst[x+1] = col_g; - dst[x+2] = col_r; - dst[x+3] = col_a; - x += surf->pitch_x; - } - } - } -} - -void evg_bgra_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; - u32 col = surf->fill_col; - u32 a, fin, col_no_a; - s32 i; - - a = (col>>24)&0xFF; - col_no_a = col & 0x00FFFFFF; - for (i=0; i<count; i++) { - fin = mul255(a, spans[i].coverage); - fin = (fin<<24) | col_no_a; - overmask_bgra_const_run(fin, dst + surf->pitch_x*spans[i].x, surf->pitch_x, spans[i].len); - } -} - - -void evg_bgra_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; - u8 spanalpha, col_a; - s32 i, x; - u32 len; - u32 *col, _col; - - for (i=0; i<count; i++) { - len = spans[i].len; - spanalpha = spans[i].coverage; - surf->sten->fill_run(surf->sten, surf, spans[i].x, y, len); - x = spans[i].x * surf->pitch_x; - col = surf->stencil_pix_run; - while (len--) { - _col = *col; - col_a = GF_COL_A(_col); - if (col_a) { - if ((spanalpha!=0xFF) || (col_a != 0xFF)) { - overmask_bgra(*col, (dst + x) , spanalpha); - } else { - dst[x] = GF_COL_B(_col); - dst[x+1] = GF_COL_G(_col); - dst[x+2] = GF_COL_R(_col); - dst[x+3] = col_a; - } - } - col++; - x += surf->pitch_x; - } - } -} - -GF_Err evg_surface_clear_bgra(GF_SURFACE surf, GF_IRect rc, GF_Color col) -{ - u8 *data; - u8 col_a, col_b, col_g, col_r; - u32 x, y, w, h, sx, sy; - s32 st; - Bool use_memset; - EVGSurface *_this = (EVGSurface *)surf; - st = _this->pitch_y; - - col_a = GF_COL_A(col); - col_r = GF_COL_R(col); - col_g = GF_COL_G(col); - col_b = GF_COL_B(col); - - h = rc.height; - w = rc.width; - sx = rc.x; - sy = rc.y; - - use_memset = GF_FALSE; - if (_this->pitch_x !=4) use_memset = GF_FALSE; - else if (!col_a) use_memset = GF_TRUE; - else if ((col_a==col_r) && (col_a==col_g) && (col_a==col_b)) use_memset = GF_TRUE; - - if (!use_memset) { - for (y = 0; y < h; y++) { - data = (u8 *) _this ->pixels + (sy+y)* st + _this->pitch_x*sx; - for (x = 0; x < w; x++) { - data[0] = col_b; - data[1] = col_g; - data[2] = col_r; - data[3] = col_a; - data += _this->pitch_x; - } - } - } else { - u32 sw = 4*w; - for (y = 0; y < h; y++) { - memset(_this ->pixels + (sy+y)* st + _this->pitch_x * sx, col_a, sizeof(char)*sw); - } - } - return GF_OK; -} - - -/* - 32 bit RGB -*/ - -static void overmask_bgrx(u32 src, u8 *dst, u32 alpha) -{ - s32 srca = (src >> 24) & 0xff; - s32 srcr = (src >> 16) & 0xff; - s32 srcg = (src >> 8) & 0xff; - s32 srcb = (src >> 0) & 0xff; - - s32 dstb = dst[0]; - s32 dstg = dst[1]; - s32 dstr = dst[2]; - - srca = mul255(srca, alpha); - dst[0] = mul255(srca, srcb - dstb) + dstb; - dst[1] = mul255(srca, srcg - dstg) + dstg; - dst[2] = mul255(srca, srcr - dstr) + dstr; - dst[3] = 0xFF; -} - -GFINLINE static void overmask_bgrx_const_run(u32 src, u8 *dst, s32 dst_pitch_x, u32 count) -{ - s32 srca = (src>>24) & 0xff; - u32 srcr = mul255(srca, ((src >> 16) & 0xff)) ; - u32 srcg = mul255(srca, ((src >> 8) & 0xff)) ; - u32 srcb = mul255(srca, ((src) & 0xff)) ; - u32 inva = 1 + 0xFF - srca; - - while (count) { - dst[0] = srcb + ((inva*dst[0])>>8); - dst[1] = srcg + ((inva*dst[1])>>8); - dst[2] = srcr + ((inva*dst[2])>>8); - dst[3] = 0xFF; - dst += dst_pitch_x; - count--; - } -} - -void evg_bgrx_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u32 col = surf->fill_col; - u32 fin, col_no_a, spana; - u8 col_r, col_g, col_b; - u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; - s32 i, x; - u32 len; - - col_no_a = col & 0x00FFFFFF; - col_r = GF_COL_R(col); - col_g = GF_COL_G(col); - col_b = GF_COL_B(col); - for (i=0; i<count; i++) { - spana = spans[i].coverage; - x = spans[i].x * surf->pitch_x; - len = spans[i].len; - - if (spana != 0xFF) { - fin = (spana<<24) | col_no_a; - overmask_bgrx_const_run(fin, dst + x, surf->pitch_x, len); - } else { - while (len--) { - dst[x] = col_b; - dst[x+1] = col_g; - dst[x+2] = col_r; - dst[x+3] = 0xFF; - x += surf->pitch_x; - } - } - } -} - -void evg_bgrx_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; - u32 col = surf->fill_col; - u32 a, fin, col_no_a; - s32 i; - - a = (col>>24)&0xFF; - col_no_a = col & 0x00FFFFFF; - for (i=0; i<count; i++) { - fin = mul255(a, spans[i].coverage); - fin = (fin<<24) | col_no_a; - overmask_bgrx_const_run(fin, dst + surf->pitch_x*spans[i].x, surf->pitch_x, spans[i].len); - } -} - - -void evg_bgrx_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; - u8 spanalpha, col_a; - s32 i, x; - u32 len; - u32 *col; - - for (i=0; i<count; i++) { - len = spans[i].len; - spanalpha = spans[i].coverage; - surf->sten->fill_run(surf->sten, surf, spans[i].x, y, len); - col = surf->stencil_pix_run; - x = spans[i].x * surf->pitch_x; - while (len--) { - u32 _col = *col; - col_a = GF_COL_A(_col); - if (col_a) { - if ((spanalpha!=0xFF) || (col_a != 0xFF)) { - overmask_bgrx(*col, dst+x, spanalpha); - } else { - dst[x] = GF_COL_B(_col); - dst[x+1] = GF_COL_G(_col); - dst[x+2] = GF_COL_R(_col); - dst[x+3] = 0xFF; - } - } - col++; - x += surf->pitch_x; - } - } -} - -/* - 32 bit BGR -*/ - -static void overmask_rgbx(u32 src, u8 *dst, u32 alpha) -{ - s32 srca = (src >> 24) & 0xff; - s32 srcr = (src >> 16) & 0xff; - s32 srcg = (src >> 8) & 0xff; - s32 srcb = (src >> 0) & 0xff; - - s32 dstr = dst[0]; - s32 dstg = dst[1]; - s32 dstb = dst[2]; - - srca = mul255(srca, alpha); - dst[0] = mul255(srca, srcr - dstr) + dstr; - dst[1] = mul255(srca, srcg - dstg) + dstg; - dst[2] = mul255(srca, srcb - dstb) + dstb; - dst[3] = 0xFF; -} - -GFINLINE static void overmask_rgbx_const_run(u32 src, u8 *dst, s32 dst_pitch_x, u32 count) -{ - s32 srca = (src>>24) & 0xff; - u32 srcr = mul255(srca, ((src >> 16) & 0xff)) ; - u32 srcg = mul255(srca, ((src >> 8) & 0xff)) ; - u32 srcb = mul255(srca, ((src) & 0xff)) ; - u32 inva = 1 + 0xFF - srca; - - while (count) { - dst[0] = srcr + ((inva*dst[0])>>8); - dst[1] = srcg + ((inva*dst[1])>>8); - dst[2] = srcb + ((inva*dst[2])>>8); - dst += dst_pitch_x; - count--; - } -} - -void evg_rgbx_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u32 col = surf->fill_col; - u32 fin, col_no_a, spana; - u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; - u8 r, g, b; - s32 i, x; - u32 len; - - col_no_a = col & 0x00FFFFFF; - r = GF_COL_R(col); - g = GF_COL_G(col); - b = GF_COL_B(col); - - for (i=0; i<count; i++) { - spana = spans[i].coverage; - x = spans[i].x * surf->pitch_x; - len = spans[i].len; - - if (spana != 0xFF) { - fin = (spana<<24) | col_no_a; - overmask_rgbx_const_run(fin, dst + x, surf->pitch_x, len); - } else { - while (len--) { - dst[x] = r; - dst[x+1] = g; - dst[x+2] = b; - dst[x+3] = 0xFF; - x += surf->pitch_x; - } - } - } -} - -void evg_rgbx_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; - u32 col = surf->fill_col; - u32 a, fin, col_no_a; - s32 i; - - a = (col>>24)&0xFF; - col_no_a = col & 0x00FFFFFF; - for (i=0; i<count; i++) { - fin = mul255(a, spans[i].coverage); - fin = (fin<<24) | col_no_a; - overmask_rgbx_const_run(fin, dst + surf->pitch_x*spans[i].x, surf->pitch_x, spans[i].len); - } -} - - -void evg_rgbx_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; - u8 spanalpha, col_a; - s32 i, x; - u32 len; - u32 *col, _col; - - for (i=0; i<count; i++) { - len = spans[i].len; - spanalpha = spans[i].coverage; - surf->sten->fill_run(surf->sten, surf, spans[i].x, y, len); - col = surf->stencil_pix_run; - x = spans[i].x * surf->pitch_x; - while (len--) { - _col = *col; - col_a = GF_COL_A(_col); - if (col_a) { - if ((spanalpha!=0xFF) || (col_a != 0xFF)) { - overmask_rgbx(*col, dst+x, spanalpha); - } else { - dst[x] = GF_COL_R(_col); - dst[x+1] = GF_COL_G(_col); - dst[x+2] = GF_COL_B(_col); - dst[x+3] = 0xFF; - } - } - col++; - x += surf->pitch_x; - } - } -} - -GF_Err evg_surface_clear_rgbx(GF_SURFACE surf, GF_IRect rc, GF_Color col) -{ - u32 x, y, w, h, sx, sy; - u8 r,g,b; - s32 st; - EVGSurface *_this = (EVGSurface *)surf; - st = _this->pitch_x; - - h = rc.height; - w = rc.width; - sx = rc.x; - sy = rc.y; - - r = GF_COL_R(col); - g = GF_COL_G(col); - b = GF_COL_B(col); - - for (y = 0; y < h; y++) { - u8 *data = (u8 *) _this ->pixels + (y + sy) * _this->pitch_y + st*sx; - for (x = 0; x < w; x++) { - data[0] = r; - data[1] = g; - data[2] = b; - data[3] = 0xFF; - data += st; - } - } - return GF_OK; -} - - - - -/* - 32 bit RGBA -*/ - -GFINLINE static void overmask_rgba(u32 src, u8 *dst, u32 alpha) -{ - u8 srca = GF_COL_A(src); - u8 srcr = GF_COL_R(src); - u8 srcg = GF_COL_G(src); - u8 srcb = GF_COL_B(src); - u8 dsta = dst[3]; - - srca = mul255(srca, alpha); - /*special case for RGBA: - if dst alpha is 0, consider the surface is empty and copy pixel - if source alpha is 0xFF erase the entire pixel - */ - if (dsta && (srca!=0xFF) ) { - u8 final_a; - u8 dstr = dst[0]; - u8 dstg = dst[1]; - u8 dstb = dst[2]; - - //do the maths , so that the result of the blend follows the sam DST = SRC*apha + DST(1-alpha) - //it gives a transform alpha of Fa = SRCa + DSTa - SRCa*DSTa - //and an RGB Fc = (SRCa*SRCc + DSTa*DSTc - DSTc*(DSTa-SRCa)) / Fa - final_a = dsta + srca - mul255(dsta, srca); - if (final_a) { - dst[0] = (u8) ((srcr*srca + dstr*(dsta-srca)) / final_a); - dst[1] = (u8) ((srcg*srca + dstg*(dsta-srca)) / final_a); - dst[2] = (u8) ((srcb*srca + dstb*(dsta-srca)) / final_a); - dst[3] = final_a; - } else { - assert(0); - } - } else { - dst[0] = srcr; - dst[1] = srcg; - dst[2] = srcb; - dst[3] = srca; - } -} - -GFINLINE static void overmask_rgba_const_run(u32 src, u8 *dst, s32 dst_pitch_x, u32 count) -{ - u8 srca = GF_COL_A(src); - u8 srcr = GF_COL_R(src); - u8 srcg = GF_COL_G(src); - u8 srcb = GF_COL_B(src); - - while (count) { - u8 dsta = dst[3]; - /*special case for RGBA: - if dst alpha is 0, consider the surface is empty and copy pixel - if source alpha is 0xFF erase the entire pixel - */ - if ((dsta != 0) && (srca != 0xFF)) { - u8 final_a; - u8 dstr = dst[0]; - u8 dstg = dst[1]; - u8 dstb = dst[2]; - - //do the maths , so that the result of the blend follows the sam DST = SRC*apha + DST(1-alpha) - //it gives a transform alpha of Fa = SRCa + DSTa - SRCa*DSTa - //and an RGB Fc = (SRCa*SRCc + DSTa*DSTc - DSTc*(DSTa-SRCa)) / Fa - final_a = dsta + srca - mul255(dsta, srca); - if (final_a) { - dst[0] = (u8) ((srcr*srca + dstr*(dsta-srca)) / final_a); - dst[1] = (u8) ((srcg*srca + dstg*(dsta-srca)) / final_a); - dst[2] = (u8) ((srcb*srca + dstb*(dsta-srca)) / final_a); - dst[3] = final_a; - } else { - assert(0); - } - } else { - dst[0] = srcr; - dst[1] = srcg; - dst[2] = srcb; - dst[3] = srca; - } - dst+=dst_pitch_x; - count--; - } -} - - -void evg_rgba_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u32 col = surf->fill_col; - u32 new_a, fin, col_no_a; - u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; - u8 *p; - s32 i; - u32 len; - - col_no_a = col & 0x00FFFFFF; - - for (i=0; i<count; i++) { - p = dst + spans[i].x*surf->pitch_x; - len = spans[i].len; - - new_a = spans[i].coverage; - fin = (new_a<<24) | col_no_a; - //we must blend in all cases since we have to merge with the dst alpha - overmask_rgba_const_run(fin, p, surf->pitch_x, len); - } -} - -void evg_rgba_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; - u32 a, fin, col_no_a; - s32 i; - - a = GF_COL_A(surf->fill_col); - col_no_a = surf->fill_col & 0x00FFFFFF; - - for (i=0; i<count; i++) { - fin = mul255(a, spans[i].coverage); - fin = (fin<<24) | col_no_a; - overmask_rgba_const_run(fin, dst + spans[i].x*surf->pitch_x, surf->pitch_x, spans[i].len); - } -} - -void evg_rgba_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; - u8 *p; - u8 spanalpha; - s32 i; - u32 len; - u32 *col; - - for (i=0; i<count; i++) { - p = dst + spans[i].x * surf->pitch_x; - len = spans[i].len; - spanalpha = spans[i].coverage; - surf->sten->fill_run(surf->sten, surf, spans[i].x, y, len); - col = surf->stencil_pix_run; - while (len--) { - //we must blend in all cases since we have to merge with the dst alpha - overmask_rgba(*col, p, spanalpha); - col++; - p += surf->pitch_x; - } - } -} - - -void evg_rgba_fill_erase(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; - u8 *p; - s32 i; - u32 len; - - for (i=0; i<count; i++) { - p = dst + spans[i].x*surf->pitch_x; - len = spans[i].len; - - if (spans[i].coverage != 0xFF) { - /* while (len--) { - *p = 0xFF-spans[i].coverage; - p += surf->pitch_x; - } - */ - } else { - while (len--) { - *(u32 *)p = 0; - p += surf->pitch_x; - } - } - } -} - -GF_Err evg_surface_clear_rgba(GF_SURFACE surf, GF_IRect rc, GF_Color col) -{ - u8 *data; - u8 a, r, g, b; - u32 x, y, w, h, sy; - s32 st; - Bool use_memset; - EVGSurface *_this = (EVGSurface *)surf; - st = _this->pitch_y; - - h = rc.height; - w = rc.width; - sy = rc.y; - - if (sy+h > _this->height) { - h = _this->height - sy; - } - if (rc.x + w > _this->width) { - w = _this->width - rc.x; - } - - a = GF_COL_A(col); - r = GF_COL_R(col); - g = GF_COL_G(col); - b = GF_COL_B(col); - - use_memset = GF_FALSE; - if (_this->pitch_x !=4) use_memset = GF_FALSE; - else if (!a) use_memset = GF_TRUE; - else if ((a==r) && (a==g) && (a==b)) use_memset = GF_TRUE; - - - if (!use_memset) { - for (y = 0; y < h; y++) { - data = (u8 *) _this ->pixels + (sy+y)* st + _this->pitch_x * rc.x; - for (x = 0; x < w; x++) { - *(data) = r; - *(data+1) = g; - *(data+2) = b; - *(data+3) = a; - data += 4; - } - } - } else { - u32 sw = 4*w; - for (y = 0; y < h; y++) { - memset(_this ->pixels + (sy+y)* st + _this->pitch_x * rc.x, a, sizeof(char)*sw); - } - } - return GF_OK; -} - diff --git a/modules/soft_raster/raster_load.c b/modules/soft_raster/raster_load.c deleted file mode 100644 index cea2b47..0000000 --- a/modules/soft_raster/raster_load.c +++ /dev/null @@ -1,106 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / software 2D rasterizer module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - * - */ - -#include "rast_soft.h" - -/*we don't need any private context*/ -GF_Raster2D *EVG_LoadRenderer() -{ - GF_Raster2D *dr; - GF_SAFEALLOC(dr, GF_Raster2D); - if (!dr) return NULL; - GF_REGISTER_MODULE_INTERFACE(dr, GF_RASTER_2D_INTERFACE, "GPAC 2D Raster", "gpac distribution") - - - dr->stencil_new = evg_stencil_new; - dr->stencil_delete = evg_stencil_delete; - dr->stencil_set_matrix = evg_stencil_set_matrix; - dr->stencil_set_brush_color = evg_stencil_set_brush_color; - dr->stencil_set_gradient_mode = evg_stencil_set_gradient_mode; - dr->stencil_set_linear_gradient = evg_stencil_set_linear_gradient; - dr->stencil_set_radial_gradient = evg_stencil_set_radial_gradient; - dr->stencil_set_gradient_interpolation = evg_stencil_set_gradient_interpolation; - dr->stencil_set_alpha = evg_stencil_set_alpha; - dr->stencil_set_texture = evg_stencil_set_texture; - dr->stencil_set_tiling = evg_stencil_set_tiling; - dr->stencil_set_filter = evg_stencil_set_filter; - dr->stencil_set_color_matrix = evg_stencil_set_color_matrix; - dr->stencil_texture_modified = NULL; - - dr->surface_new = evg_surface_new; - dr->surface_delete = evg_surface_delete; - dr->surface_attach_to_device = NULL; - dr->surface_attach_to_texture = evg_surface_attach_to_texture; - dr->surface_attach_to_buffer = evg_surface_attach_to_buffer; - dr->surface_detach = evg_surface_detach; - dr->surface_set_raster_level = evg_surface_set_raster_level; - dr->surface_set_matrix = evg_surface_set_matrix; - dr->surface_set_clipper = evg_surface_set_clipper; - dr->surface_set_path = evg_surface_set_path; - dr->surface_fill = evg_surface_fill; - dr->surface_attach_to_callbacks = evg_surface_attach_to_callbacks; - dr->surface_flush = NULL; - dr->surface_clear = evg_surface_clear; - return dr; -} - -void EVG_ShutdownRenderer(GF_Raster2D *dr) -{ - gf_free(dr); -} - -#ifndef GPAC_STANDALONE_RENDER_2D - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { - GF_RASTER_2D_INTERFACE, - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - if (InterfaceType==GF_RASTER_2D_INTERFACE) { - return (GF_BaseInterface *)EVG_LoadRenderer(); - } - return NULL; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - if (ifce->InterfaceType == GF_RASTER_2D_INTERFACE) { - EVG_ShutdownRenderer((GF_Raster2D *)ifce); - } -} - -GPAC_MODULE_STATIC_DECLARATION( soft_raster ) - -#endif diff --git a/modules/soft_raster/raster_rgb.c b/modules/soft_raster/raster_rgb.c deleted file mode 100644 index 4a76dc1..0000000 --- a/modules/soft_raster/raster_rgb.c +++ /dev/null @@ -1,402 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / software 2D rasterizer module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - * - * - */ - -#include "rast_soft.h" - -static s32 -mul255(s32 a, s32 b) -{ - return ((a + 1) * b) >> 8; -} - - -/* - RGB part -*/ - -static void overmask_rgb(u32 src, char *dst, u32 alpha) -{ - s32 srca = (src >> 24) & 0xff; - u32 srcr = (src >> 16) & 0xff; - s32 srcg = (src >> 8) & 0xff; - s32 srcb = (src) & 0xff; - - s32 dstr = (*dst) & 0xFF; - s32 dstg = *(dst+1) & 0xFF; - s32 dstb = *(dst+2) & 0xFF; - - srca = mul255(srca, alpha); - *dst = mul255(srca, srcr - dstr) + dstr; - *(dst+1) = mul255(srca, srcg - dstg) + dstg; - *(dst+2) = mul255(srca, srcb - dstb) + dstb; -} - -static void overmask_rgb_const_run(u32 src, char *dst, s32 dst_pitch_x, u32 count) -{ - u8 srca = (src >> 24) & 0xff; - u8 srcr = (src >> 16) & 0xff; - u8 srcg = (src >> 8) & 0xff; - u8 srcb = (src) & 0xff; - - while (count) { - u8 dstr = *(dst); - u8 dstg = *(dst+1); - u8 dstb = *(dst+2); - *dst = (u8) mul255(srca, srcr - dstr) + dstr; - *(dst+1) = (u8) mul255(srca, srcg - dstg) + dstg; - *(dst+2) = (u8) mul255(srca, srcb - dstb) + dstb; - dst += dst_pitch_x; - count--; - } -} - -void evg_rgb_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u32 col = surf->fill_col; - u32 a, fin, col_no_a; - char *dst = surf->pixels + y * surf->pitch_y; - char *p; - s32 i; - u32 len, r, g, b; - - r = GF_COL_R(col); - g = GF_COL_G(col); - b = GF_COL_B(col); - - col_no_a = col & 0x00FFFFFF; - for (i=0; i<count; i++) { - len = spans[i].len; - p = dst + spans[i].x * surf->pitch_x; - - if (spans[i].coverage != 0xFF) { - a = mul255(0xFF, spans[i].coverage); - fin = (a<<24) | col_no_a; - overmask_rgb_const_run(fin, p, surf->pitch_x, len); - } else { - while (len--) { - *(p) = r; - *(p + 1) = g; - *(p + 2) = b; - p += surf->pitch_x; - } - } - } -} - -void evg_rgb_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - char *dst = surf->pixels + y * surf->pitch_y; - u32 col = surf->fill_col; - u32 a, fin; - s32 i; - - a = (col>>24)&0xFF; - for (i=0; i<count; i++) { - fin = mul255(a, spans[i].coverage); - fin = (fin<<24) | (col&0x00FFFFFF); - overmask_rgb_const_run(fin, dst + surf->pitch_x * spans[i].x, surf->pitch_x, spans[i].len); - } -} - - -void evg_rgb_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - char *dst = surf->pixels + y * surf->pitch_y; - u8 spanalpha, col_a; - s32 i; - s32 x; - u32 len; - u32 *col; - - for (i=0; i<count; i++) { - len = spans[i].len; - spanalpha = spans[i].coverage; - surf->sten->fill_run(surf->sten, surf, spans[i].x, y, len); - col = surf->stencil_pix_run; - x = surf->pitch_x * spans[i].x; - while (len--) { - col_a = GF_COL_A(*col); - if (col_a) { - if ((spanalpha!=0xFF) || (col_a != 0xFF)) { - overmask_rgb(*col, dst + x, spanalpha); - } else { - *(dst + x) = GF_COL_R(*col); - *(dst + x + 1) = GF_COL_G(*col); - *(dst + x + 2) = GF_COL_B(*col); - } - } - col++; - x += surf->pitch_x; - } - } -} - -GF_Err evg_surface_clear_rgb(GF_SURFACE surf, GF_IRect rc, GF_Color col) -{ - u32 x, y, w, h, sx, sy; - s32 st; - u8 r, g, b; - EVGSurface *_this = (EVGSurface *)surf; - st = _this->pitch_y; - - h = rc.height; - w = rc.width; - sx = rc.x; - sy = rc.y; - - r = GF_COL_R(col); - g = GF_COL_G(col); - b = GF_COL_B(col); - - for (y = 0; y < h; y++) { - char *data = _this ->pixels + (y + sy) * st + _this->pitch_x*sx; - for (x = 0; x < w; x++) { - *(data) = r; - *(data+1) = g; - *(data+2) = b; - data += _this->pitch_x; - } - } - return GF_OK; -} - - -/* - - BGR part -*/ - -static void overmask_bgr(u32 src, char *dst, u32 alpha) -{ - s32 srca = (src >> 24) & 0xff; - s32 srcr = (src >> 16) & 0xff; - s32 srcg = (src >> 8) & 0xff; - s32 srcb = (src) & 0xff; - - s32 dstb = *dst & 0xFF; - s32 dstg = *(dst+1) & 0xFF; - s32 dstr = *(dst+2) & 0xFF; - - srca = mul255(srca, alpha); - *(dst) = mul255(srca, srcb - dstb) + dstb; - *(dst+1) = mul255(srca, srcg - dstg) + dstg; - *(dst+2) = mul255(srca, srcr - dstr) + dstr; -} - -static void overmask_bgr_const_run(u32 src, char *dst, s32 dst_pitch_x, u32 count) -{ - u8 srca = (src >> 24) & 0xff; - u8 srcr = (src >> 16) & 0xff; - u8 srcg = (src >> 8) & 0xff; - u8 srcb = (src) & 0xff; - - while (count) { - u8 dstb = *(dst); - u8 dstg = *(dst+1); - u8 dstr = *(dst+2); - *dst = (u8) mul255(srca, srcb - dstb) + dstb; - *(dst+1) = (u8) mul255(srca, srcg - dstg) + dstg; - *(dst+2) = (u8) mul255(srca, srcr - dstr) + dstr; - dst += dst_pitch_x; - count--; - } -} - -void evg_bgr_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u32 col = surf->fill_col; - u32 a, fin, col_no_a; - char *dst = surf->pixels + y * surf->pitch_y; - char *p; - s32 i; - u32 len, r, g, b; - - r = GF_COL_R(col); - g = GF_COL_G(col); - b = GF_COL_B(col); - - col_no_a = col & 0x00FFFFFF; - for (i=0; i<count; i++) { - len = spans[i].len; - p = dst + spans[i].x * surf->pitch_x; - - if (spans[i].coverage != 0xFF) { - a = mul255(0xFF, spans[i].coverage); - fin = (a<<24) | col_no_a; - overmask_bgr_const_run(fin, p, surf->pitch_x, len); - } else { - while (len--) { - *(p) = b; - *(p + 1) = g; - *(p + 2) = r; - p += surf->pitch_x; - } - } - } -} - -void evg_bgr_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - char *dst = surf->pixels + y * surf->pitch_y; - u32 col = surf->fill_col; - u32 a, fin, col_no_a; - s32 i; - - a = (col>>24)&0xFF; - col_no_a = col & 0x00FFFFFF; - for (i=0; i<count; i++) { - fin = mul255(a, spans[i].coverage); - fin = (fin<<24) | col_no_a; - overmask_bgr_const_run(fin, dst + surf->pitch_x * spans[i].x, surf->pitch_x, spans[i].len); - } -} - - -void evg_bgr_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - char *dst = surf->pixels + y * surf->pitch_y; - u8 spanalpha, col_a; - s32 i, x; - u32 len; - u32 *col, _col; - - for (i=0; i<count; i++) { - x = spans[i].x; - len = spans[i].len; - spanalpha = spans[i].coverage; - surf->sten->fill_run(surf->sten, surf, x, y, len); - x *= surf->pitch_x; - col = surf->stencil_pix_run; - while (len--) { - _col = *col; - col_a = GF_COL_A(_col); - if (col_a) { - if ((spanalpha!=0xFF) || (col_a != 0xFF)) { - overmask_bgr(_col, dst + x, spanalpha); - } else { - *(dst + x) = GF_COL_B(_col); - *(dst + x + 1) = GF_COL_G(_col); - *(dst + x + 2) = GF_COL_R(_col); - } - } - col++; - x += surf->pitch_x; - } - } -} - -GF_Err evg_surface_clear_bgr(GF_SURFACE surf, GF_IRect rc, GF_Color col) -{ - u32 x, y, w, h, sx, sy; - s32 st; - u8 r, g, b; - EVGSurface *_this = (EVGSurface *)surf; - st = _this->pitch_y; - - h = rc.height; - w = rc.width; - sx = rc.x; - sy = rc.y; - - r = GF_COL_R(col); - g = GF_COL_G(col); - b = GF_COL_B(col); - - for (y = 0; y < h; y++) { - char *data = _this ->pixels + (y+sy) * st + _this->pitch_x*sx; - for (x = 0; x < w; x++) { - *(data) = b; - *(data+1) = g; - *(data+2) = r; - data += 3; - } - } - return GF_OK; -} - - -/* - user-defined callbacks -*/ - -void evg_user_fill_const(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u32 col_no_a; - s32 i; - - col_no_a = surf->fill_col; - for (i=0; i<count; i++) { - if (spans[i].coverage != 0xFF) { - u32 a = mul255(0xFF, spans[i].coverage); - surf->raster_fill_run_alpha(surf->raster_cbk, spans[i].x, y, spans[i].len, col_no_a, a); - } else { - surf->raster_fill_run_no_alpha(surf->raster_cbk, spans[i].x, y, spans[i].len, col_no_a); - } - } -} - -void evg_user_fill_const_a(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u32 col, a, col_no_a; - s32 i; - a = (surf->fill_col>>24)&0xFF; - col_no_a = surf->fill_col | 0xFF000000; - for (i=0; i<count; i++) { - col = mul255(a, spans[i].coverage); - surf->raster_fill_run_alpha(surf->raster_cbk, spans[i].x, y, spans[i].len, col_no_a, col); - } -} - -void evg_user_fill_var(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf) -{ - u8 spanalpha, col_a, a; - s32 i; - u32 x, len; - u32 *col; - - for (i=0; i<count; i++) { - len = spans[i].len; - x = spans[i].x; - spanalpha = spans[i].coverage; - surf->sten->fill_run(surf->sten, surf, x, y, len); - col = surf->stencil_pix_run; - while (len--) { - col_a = GF_COL_A(*col); - if (col_a) { - if ((spanalpha!=0xFF) || (col_a != 0xFF)) { - a = mul255(col_a, spans[i].coverage); - surf->raster_fill_run_alpha(surf->raster_cbk, x, y, 1, *col, a); - } else { - surf->raster_fill_run_no_alpha(surf->raster_cbk, x, y, 1, *col); - } - } - col++; - x ++; - } - } -} - diff --git a/modules/soft_raster/stencil.c b/modules/soft_raster/stencil.c deleted file mode 100644 index f1f32b9..0000000 --- a/modules/soft_raster/stencil.c +++ /dev/null @@ -1,943 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / software 2D rasterizer module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include "rast_soft.h" - -EVGStencil *evg_solid_brush(); -EVGStencil *evg_texture_brush(); -EVGStencil *evg_linear_gradient_brush(); -EVGStencil *evg_radial_gradient_brush(); - - - -GF_Color color_interpolate(u32 a, u32 b, u8 pos) -{ - u32 ca = ((a>>24) )*(u32)(0xFF-pos)+((b>>24) )*(u32)pos; - u32 cr = ((a>>16)&0xFF)*(u32)(0xFF-pos)+((b>>16)&0xFF)*(u32)pos; - u32 cg = ((a>> 8)&0xFF)*(u32)(0xFF-pos)+((b>> 8)&0xFF)*(u32)pos; - u32 cb = ((a )&0xFF)*(u32)(0xFF-pos)+((b )&0xFF)*(u32)pos; - return (((ca+(ca>>8)+1)>>8)<<24)| - (((cr+(cr>>8)+1)>>8)<<16)| - (((cg+(cg>>8)+1)>>8)<< 8)| - (((cb+(cb>>8)+1)>>8) ); -} - - - - -/* - Generic gradient tools -*/ - -#define EVGGRADIENTBUFFERSIZE (1<<EVGGRADIENTBITS) -#define EVGGRADIENTMAXINTPOS EVGGRADIENTBUFFERSIZE - 1 - -static void gradient_update(EVG_BaseGradient *_this) -{ - s32 i, c; - s32 start, end, diff; - Fixed maxPos = INT2FIX(EVGGRADIENTMAXINTPOS); - - if (_this->pos[0]>=0) { - if(_this->pos[0]>0) { - end = FIX2INT(gf_mulfix(_this->pos[0], maxPos)); - for (i=0; i<= end; i++) { - _this->pre[i] = _this->col[0]; - } - } - for (c=0; c<EVGGRADIENTSLOTS; c++) { - if (_this->pos[c]<0) break; - if (_this->pos[c+1]>=0) { - start = FIX2INT(gf_mulfix(_this->pos[c], maxPos)); - end = FIX2INT(gf_mulfix(_this->pos[c+1], maxPos)); - diff = end-start; - - if (diff) { - for (i=start; i<=end; i++) { - _this->pre[i] = color_interpolate(_this->col[c], _this->col[c+1], - (u8) ( ( (i-start) * 255) / diff) ); - } - } - } else { - start = FIX2INT(gf_mulfix(_this->pos[c+0], maxPos)); - for(i=start; i<=EVGGRADIENTMAXINTPOS; i++) { - _this->pre[i] = _this->col[c]; - } - } - } - } -} - -static u32 gradient_get_color(EVG_BaseGradient *_this, s32 pos) -{ - s32 max_pos = 1 << EVGGRADIENTBITS; - - switch (_this->mod) { - case GF_GRADIENT_MODE_SPREAD: - if (pos<0) pos = -pos; - return _this->pre[(pos & max_pos) ? EVGGRADIENTMAXINTPOS - (pos % max_pos) : pos % max_pos]; - - case GF_GRADIENT_MODE_REPEAT: - while (pos < 0) pos += max_pos; - return _this->pre[pos % max_pos]; - - case GF_GRADIENT_MODE_PAD: - default: - return _this->pre[ MIN(EVGGRADIENTMAXINTPOS, MAX((s32) 0, pos))]; - } -} - -GF_Err evg_stencil_set_gradient_interpolation(GF_STENCIL p, Fixed *pos, GF_Color *col, u32 count) -{ - EVG_BaseGradient *_this = (EVG_BaseGradient *) p; - if ( (_this->type != GF_STENCIL_LINEAR_GRADIENT) && (_this->type != GF_STENCIL_RADIAL_GRADIENT) ) return GF_BAD_PARAM; - if (count>=EVGGRADIENTSLOTS-1) return GF_OUT_OF_MEM; - memcpy(_this->col, col, sizeof(GF_Color) * count); - memcpy(_this->pos, pos, sizeof(Fixed) * count); - _this->col[count] = 0; - _this->pos[count] = -FIX_ONE; - gradient_update(_this); - return GF_OK; -} - -GF_Err evg_stencil_set_gradient_mode(GF_STENCIL p, GF_GradientMode mode) -{ - EVG_BaseGradient *_this = (EVG_BaseGradient *) p; - if ( (_this->type != GF_STENCIL_LINEAR_GRADIENT) && (_this->type != GF_STENCIL_RADIAL_GRADIENT) ) return GF_BAD_PARAM; - _this->mod = mode; - return GF_OK; -} - -/* - Generic stencil -*/ - -GF_STENCIL evg_stencil_new(GF_Raster2D *_dr, GF_StencilType type) -{ - EVGStencil *st; - switch (type) { - case GF_STENCIL_SOLID: - st = evg_solid_brush(); - break; - case GF_STENCIL_LINEAR_GRADIENT: - st = evg_linear_gradient_brush(); - break; - case GF_STENCIL_RADIAL_GRADIENT: - st = evg_radial_gradient_brush(); - break; - case GF_STENCIL_TEXTURE: - st = evg_texture_brush(); - break; - default: - return 0L; - } - if (st) { - gf_mx2d_init(st->pmat); - gf_mx2d_init(st->smat); - gf_cmx_init(&st->cmat); - } - return st; -} - -void evg_stencil_delete(GF_STENCIL st) -{ - EVGStencil *_this = (EVGStencil *) st; - switch(_this->type) { - case GF_STENCIL_SOLID: - case GF_STENCIL_LINEAR_GRADIENT: - case GF_STENCIL_RADIAL_GRADIENT: - gf_free(_this); - return; - case GF_STENCIL_TEXTURE: - { - EVG_Texture *tx = (EVG_Texture *)_this; - /*destroy conversion buffer if any*/ - if ( tx->conv_buf) gf_free( tx->conv_buf ); - /*destroy local texture iof any*/ - if (tx->owns_texture && tx->pixels) gf_free(tx->pixels); - gf_free(_this); - } - return; - } -} - -GF_Err evg_stencil_set_matrix(GF_STENCIL st, GF_Matrix2D *mx) -{ - EVGStencil *_this = (EVGStencil *)st; - if (!_this || _this->type>GF_STENCIL_TEXTURE) return GF_BAD_PARAM; - if (mx) { - gf_mx2d_copy(_this->smat, *mx); - } else { - gf_mx2d_init(_this->smat); - } - return GF_OK; -} - - -/* - Solid color stencil -*/ - -EVGStencil *evg_solid_brush() -{ - EVG_Brush *tmp; - GF_SAFEALLOC(tmp, EVG_Brush); - if (!tmp) return 0L; - tmp->fill_run = NULL; - tmp->color = 0xFF000000; - tmp->type = GF_STENCIL_SOLID; - return (EVGStencil *) tmp; -} - -GF_Err evg_stencil_set_brush_color(GF_STENCIL st, GF_Color c) -{ - EVG_Brush *_this = (EVG_Brush *) st; - if (!_this || (_this ->type != GF_STENCIL_SOLID) ) return GF_BAD_PARAM; - _this->color = c; - return GF_OK; -} - - -/* - linear gradient stencil -*/ - -static void lgb_fill_run(EVGStencil *p, EVGSurface *surf, s32 x, s32 y, u32 count) -{ - Bool has_cmat, has_a; - Fixed _res; - s32 val; - u32 col, ca; - u32 *data = surf->stencil_pix_run; - EVG_LinearGradient *_this = (EVG_LinearGradient *) p; - - has_cmat = _this->cmat.identity ? GF_FALSE : GF_TRUE; - has_a = (_this->alpha==0xFF) ? GF_FALSE : GF_TRUE; - - /*no need to move x & y to fixed*/ - _res = (Fixed) (x * _this->smat.m[0] + y * _this->smat.m[1] + _this->smat.m[2]); - while (count) { - val = FIX2INT(_res); - _res += _this->smat.m[0]; - - col = gradient_get_color((EVG_BaseGradient *)_this, val ); - if (has_a) { - ca = ((GF_COL_A(col) + 1) * _this->alpha) >> 8; - col = ( ((ca<<24) & 0xFF000000) ) | (col & 0x00FFFFFF); - } - if (has_cmat) col = gf_cmx_apply(&p->cmat, col); - *data++ = col; - count--; - } -} - -GF_Err evg_stencil_set_linear_gradient(GF_STENCIL st, Fixed start_x, Fixed start_y, Fixed end_x, Fixed end_y) -{ - GF_Matrix2D mtx; - GF_Point2D s; - Fixed f; - EVG_LinearGradient *_this = (EVG_LinearGradient *) st; - if (_this->type != GF_STENCIL_LINEAR_GRADIENT) return GF_BAD_PARAM; - - _this->start.x = start_x; - _this->start.y = start_y; - _this->end.x = end_x; - _this->end.y = end_y; - s.x = end_x - start_x; - s.y = end_y - start_y; - f = gf_v2d_len(&s); - if (f) f = gf_invfix(f); - - gf_mx2d_init(mtx); - mtx.m[2] = - _this->start.x; - mtx.m[5] = - _this->start.y; - _this->vecmat = mtx; - - gf_mx2d_init(mtx); - gf_mx2d_add_rotation(&mtx, 0, 0, - gf_atan2(s.y, s.x)); - gf_mx2d_add_matrix(&_this->vecmat, &mtx); - - gf_mx2d_init(mtx); - gf_mx2d_add_scale(&mtx, f, f); - gf_mx2d_add_matrix(&_this->vecmat, &mtx); - return GF_OK; -} - -EVGStencil *evg_linear_gradient_brush() -{ - s32 i; - EVG_LinearGradient *tmp; - GF_SAFEALLOC(tmp, EVG_LinearGradient); - if (!tmp) return 0L; - gf_mx2d_init(tmp->vecmat); - tmp->fill_run = lgb_fill_run; - tmp->type = GF_STENCIL_LINEAR_GRADIENT; - for(i=0; i<EVGGRADIENTSLOTS; i++) tmp->pos[i]=-1; - - tmp->alpha = 0xFF; - evg_stencil_set_linear_gradient((EVGStencil *)tmp, 0, 0, FIX_ONE, 0); - return (EVGStencil *) tmp; -} - - -/* - radial gradient stencil -*/ - -static void rg_fill_run(EVGStencil *p, EVGSurface *surf, s32 _x, s32 _y, u32 count) -{ - Fixed x, y, dx, dy, b, val; - Bool has_cmat, has_a; - s32 pos; - u32 col, ca; - u32 *data = surf->stencil_pix_run; - EVG_RadialGradient *_this = (EVG_RadialGradient *) p; - - x = INT2FIX(_x); - y = INT2FIX(_y); - gf_mx2d_apply_coords(&_this->smat, &x, &y); - - has_cmat = _this->cmat.identity ? GF_FALSE : GF_TRUE; - has_a = (_this->alpha==0xFF) ? GF_FALSE : GF_TRUE; - - dx = x - _this->d_f.x; - dy = y - _this->d_f.y; - while (count) { - b = gf_mulfix(_this->rad, gf_mulfix(dx, _this->d_f.x) + gf_mulfix(dy, _this->d_f.y)); - val = gf_mulfix(b, b) + gf_mulfix(_this->rad, gf_mulfix(dx, dx)+gf_mulfix(dy, dy)); - b += gf_sqrt(val); - pos = FIX2INT(EVGGRADIENTBUFFERSIZE*b); - - col = gradient_get_color((EVG_BaseGradient *)_this, pos); - if (has_a) { - ca = ((GF_COL_A(col) + 1) * _this->alpha) >> 8; - col = ( ((ca<<24) & 0xFF000000) ) | (col & 0x00FFFFFF); - } - if (has_cmat) col = gf_cmx_apply(&p->cmat, col); - *data++ = col; - - dx += _this->d_i.x; - dy += _this->d_i.y; - count--; - } -} - -void evg_radial_init(EVG_RadialGradient *_this) -{ - GF_Point2D p0, p1; - p0.x = p0.y = p1.y = 0; - p1.x = FIX_ONE; - - gf_mx2d_apply_point(&_this->smat, &p0); - gf_mx2d_apply_point(&_this->smat, &p1); - _this->d_i.x = p1.x - p0.x; - _this->d_i.y = p1.y - p0.y; - - _this->rad = FIX_ONE - gf_mulfix(_this->d_f.x, _this->d_f.x) - gf_mulfix(_this->d_f.y, _this->d_f.y); - if (_this->rad) { - _this->rad = gf_invfix(_this->rad); - } else { - _this->rad = EVGGRADIENTBUFFERSIZE; - } -} - -EVGStencil *evg_radial_gradient_brush() -{ - s32 i; - EVG_RadialGradient *tmp; - GF_SAFEALLOC(tmp, EVG_RadialGradient); - if (!tmp) return 0L; - - tmp->fill_run = rg_fill_run; - tmp->type = GF_STENCIL_RADIAL_GRADIENT; - for(i=0; i<EVGGRADIENTSLOTS; i++) tmp->pos[i]=-1; - - tmp->center.x = tmp->center.y = FIX_ONE/2; - tmp->focus = tmp->center; - tmp->radius = tmp->center; - tmp->alpha = 0xFF; - return (EVGStencil *) tmp; -} - - -GF_Err evg_stencil_set_radial_gradient(GF_STENCIL st, Fixed cx, Fixed cy, Fixed fx, Fixed fy, Fixed x_radius, Fixed y_radius) -{ - EVG_RadialGradient *_this = (EVG_RadialGradient *) st; - if (_this->type != GF_STENCIL_RADIAL_GRADIENT) return GF_BAD_PARAM; - - _this->center.x = cx; - _this->center.y = cy; - _this->focus.x = fx; - _this->focus.y = fy; - _this->radius.x = x_radius; - _this->radius.y = y_radius; - return GF_OK; -} - -/* - Texture stencil - this is just a crude texture mapper, it lacks precision and filtering ... -*/ - -#define USE_BILINEAR 0 - -#if USE_BILINEAR -static GFINLINE s32 mul255(s32 a, s32 b) -{ - return ((a+1) * b) >> 8; -} -static u32 EVG_LERP(u32 c0, u32 c1, u8 t) -{ - s32 a0, r0, g0, b0; - s32 a1, r1, g1, b1; - s32 a2, r2, g2, b2; - - if (!t) return c0; - - a0 = GF_COL_A(c0); - r0 = GF_COL_R(c0); - g0 = GF_COL_G(c0); - b0 = GF_COL_B(c0); - a1 = GF_COL_A(c1); - r1 = GF_COL_R(c1); - g1 = GF_COL_G(c1); - b1 = GF_COL_B(c1); - - a2 = a0 + mul255(t, (a1 - a0)); - r2 = r0 + mul255(t, (r1 - r0)); - g2 = g0 + mul255(t, (g1 - g0)); - b2 = b0 + mul255(t, (b1 - b0)); - return (a2<<24) | (r2<<16) | (g2<<8) | b2; -} -#endif - -static void bmp_untransform_coord(EVG_Texture *_this, s32 _x, s32 _y, Fixed *outx, Fixed *outy) -{ - u32 checkx, checky; - Fixed x, y, dim; - - /* reverse to texture coords*/ - x = INT2FIX(_x); - y = INT2FIX(_y); - gf_mx2d_apply_coords(&_this->smat, &x, &y); - - checkx = checky = 0; - if (ABS(x)< FIX_ONE/20) checkx = 1; - if (ABS(y)< FIX_ONE/20) checky = 1; - - /*we may have a numerical stability issues, try to figure out whether we are close from 0 or width/height*/ - if ( checkx || checky) { - Fixed tx, ty; - tx = INT2FIX(_x+1); - ty = INT2FIX(_y+1); - gf_mx2d_apply_coords(&_this->smat, &tx, &ty); - - if (checkx) { - if (tx<0) x = INT2FIX(_this->width - 1); - else x = 0; - } - if (checky) { - if (ty<0) y = INT2FIX(_this->height - 1); - else y = 0; - } - } - - dim = INT2FIX(_this->width); - if (_this->mod & GF_TEXTURE_REPEAT_S) { - if (x<0) { - while (x<0) x += dim; - } else { - while (x>dim) x -= dim; - } - } else { - if (x<-dim) { - x = 0; - } else if (x>dim) { - x = dim; - } - while (x<0) x+=dim; - } - - dim = INT2FIX(_this->height); - if (_this->mod & GF_TEXTURE_REPEAT_T) { - if (y<0) { - while (y<0) y += dim; - } else { - while (y>dim) y -= dim; - } - } else { - if (y<-dim) { - y = 0; - } else if (y>dim) { - y = dim; - } - while (y<0) y+=dim; - } - - *outx=x; - *outy=y; -} - -static void bmp_fill_run(EVGStencil *p, EVGSurface *surf, s32 _x, s32 _y, u32 count) -{ - s32 cx, x0, y0; - u32 pix, replace_col; - Bool has_alpha, has_replace_cmat, has_cmat, repeat_s, repeat_t; - Fixed x, y, _fd; -#if USE_BILINEAR - s32 incx, incy; -#endif - u32 *data = surf->stencil_pix_run; - EVG_Texture *_this = (EVG_Texture *) p; - - bmp_untransform_coord(_this, _x, _y, &x, &y); - -#if USE_BILINEAR - incx = (_this->inc_x>0) ? 1 : -1; - incy = (_this->inc_y>0) ? 1 : -1; -#endif - - _fd = INT2FIX(_this->width); - repeat_s = _this->mod & GF_TEXTURE_REPEAT_S; - if (!repeat_s && (x < - _fd)) x = 0; - while (x<0) x += _fd; - - _fd = INT2FIX(_this->height); - repeat_t = _this->mod & GF_TEXTURE_REPEAT_T; - if (!repeat_t && (y < - _fd)) y = 0; - while (y<0) y += _fd; - - has_alpha = (_this->alpha != 255) ? GF_TRUE : GF_FALSE; - has_replace_cmat = _this->cmat_is_replace ? GF_TRUE : GF_FALSE; - has_cmat = _this->cmat.identity ? GF_FALSE : GF_TRUE; - replace_col = _this->replace_col; - - while (count) { - x0 = FIX2INT(x); - assert((s32)x0 >=0); - if (repeat_s) { - x0 = (x0) % _this->width; - } else { - x0 = MIN(x0, (s32) _this->width - 1); - } - - y0 = FIX2INT(y); - assert((s32)y0 >=0); - if (repeat_t) { - y0 = (y0) % _this->height; - } else if (y0 >= (s32) _this->height) { - y0 = _this->height-1; - } - - pix = _this->tx_get_pixel(_this->pixels + _this->stride*y0 + _this->Bpp*x0); - - _x++; - bmp_untransform_coord(_this, _x, _y, &x, &y); - - if (x<0) x+=INT2FIX(_this->width); - if (y<0) y+=INT2FIX(_this->height); - - /*bilinear filtering - disabled (too slow and not precise enough)*/ -#if USE_BILINEAR - if (_this->filter==GF_TEXTURE_FILTER_HIGH_QUALITY) { - u32 p00, p01, p10, p11; - s32 x1, y1; - u8 tx, ty; - - tx = FIX2INT(gf_muldiv(x, 255, _this->width) ); - ty = FIX2INT(gf_muldiv(y, 255, _this->height) ); - - if (tx>120 || ty>120) { - x1 = (x0+incx); - if (x1<0) { - while (x1<0) x1 += _this->width; - } else { - x1 = x1 % _this->width; - } - y1 = (y0+incy); - if (y1<0) { - while (y1<0) y1+=_this->height; - } else { - y1 = y1 % _this->height; - } - if (incx>0) { - if (x1<x0) tx = 255-tx; - } else { - if (x1>x0) tx = 255-tx; - } - if (incy>0) { - if (y1<y0) ty = 255-ty; - } else { - if (y1>y0) ty = 255-ty; - } - - p00 = pix; - p01 = _this->tx_get_pixel(_this->pixels + _this->stride*y0 + _this->Bpp*x1); - p10 = _this->tx_get_pixel(_this->pixels + _this->stride*y1 + _this->Bpp*x0); - p11 = _this->tx_get_pixel(_this->pixels + _this->stride*y1 + _this->Bpp*x1); - - p00 = EVG_LERP(p00, p01, tx); - p10 = EVG_LERP(p10, p11, tx); - pix = EVG_LERP(p00, p10, ty); - } - } -#endif - - if (has_alpha) { - cx = ((GF_COL_A(pix) + 1) * _this->alpha) >> 8; - pix = ( ((cx<<24) & 0xFF000000) ) | (pix & 0x00FFFFFF); - } - if (has_replace_cmat) { - u32 __a; - __a = GF_COL_A(pix); - __a = (u32) (_this->cmat.m[18] * __a); - pix = ((__a<<24) | (replace_col & 0x00FFFFFF)); - } else if (has_cmat) { - pix = gf_cmx_apply(&_this->cmat, pix); - } - *data++ = pix; - count--; - } -} - - -/*just a little faster...*/ -static void bmp_fill_run_straight(EVGStencil *p, EVGSurface *surf, s32 _x, s32 _y, u32 count) -{ - s32 x0, y0; - u32 pix; - u32 __a; - Bool repeat_s = GF_FALSE; - Fixed x, y, _fdim; - char *pix_line; - u32 *data = surf->stencil_pix_run; - EVG_Texture *_this = (EVG_Texture *) p; - - /*get texture coords in FIXED - offset*/ - x = _this->smat.m[0]*_x + _this->smat.m[2]; - y = _this->smat.m[4]*_y + _this->smat.m[5]; - - /*we may have a numerical stability issues, try to figure out whether we are close from 0 or width/height*/ - if (ABS(x)< FIX_ONE/10) { - Fixed test = _this->smat.m[0]*(_x+1) + _this->smat.m[2]; - if (test<0) x = INT2FIX(_this->width - 1); - else x = 0; - } - if (ABS(y)< FIX_ONE/10) { - Fixed test = _this->smat.m[4]*(_y+1) + _this->smat.m[5]; - if (test<0) y = INT2FIX(_this->height - 1); - else y = 0; - } - - /* and move in absolute coords*/ - _fdim = INT2FIX(_this->width); - repeat_s = (_this->mod & GF_TEXTURE_REPEAT_S); - if (!repeat_s && (x <- _fdim)) x=0; - while (x<0) x += _fdim; - - _fdim = INT2FIX(_this->height); - if (!(_this->mod & GF_TEXTURE_REPEAT_T) && (y <- _fdim)) y = 0; - while (y<0) y += _fdim; - - y0 = FIX2INT(y); - y0 = y0 % _this->height; - pix_line = _this->pixels + _this->stride*y0; - - while (count) { - x0 = FIX2INT(x); - if (repeat_s) { - x0 = (x0) % _this->width; - } else if (x0 >= (s32) _this->width) x0 = _this->width-1; - - x += _this->inc_x; - pix = _this->tx_get_pixel(pix_line + _this->Bpp*x0); - - if (_this->replace_col) { - __a = GF_COL_A(pix); - pix = ((__a<<24) | (_this->replace_col & 0x00FFFFFF)); - } - *data++ = pix; - count--; - } -} - -void evg_bmp_init(EVGStencil *p) -{ - GF_Point2D p0, p1; - EVG_Texture *_this = (EVG_Texture *) p; - - p0.x = p0.y = p1.y = 0; - p1.x = FIX_ONE; - gf_mx2d_apply_point(&_this->smat, &p0); - gf_mx2d_apply_point(&_this->smat, &p1); - _this->inc_x = p1.x - p0.x; - _this->inc_y = p1.y - p0.y; - - _this->replace_col = 0; - _this->cmat_is_replace = GF_FALSE; - if (!_this->cmat.identity - && !_this->cmat.m[0] && !_this->cmat.m[1] && !_this->cmat.m[2] && !_this->cmat.m[3] - && !_this->cmat.m[5] && !_this->cmat.m[6] && !_this->cmat.m[7] && !_this->cmat.m[8] - && !_this->cmat.m[10] && !_this->cmat.m[11] && !_this->cmat.m[12] && !_this->cmat.m[13] - && !_this->cmat.m[15] && !_this->cmat.m[16] && !_this->cmat.m[17] && !_this->cmat.m[19]) { - _this->cmat_is_replace = GF_TRUE; - _this->replace_col = GF_COL_ARGB(FIX2INT(_this->cmat.m[18]*255), FIX2INT(_this->cmat.m[4]*255), FIX2INT(_this->cmat.m[9]*255), FIX2INT(_this->cmat.m[14]*255)); - } - - if ((_this->alpha == 255) && !_this->smat.m[1] && !_this->smat.m[3] && (_this->cmat.identity || _this->cmat_is_replace)) { - _this->fill_run = bmp_fill_run_straight; - } else { - _this->fill_run = bmp_fill_run; - } -} - - -EVGStencil *evg_texture_brush() -{ - EVG_Texture *tmp; - GF_SAFEALLOC(tmp, EVG_Texture); - if (!tmp) return 0L; - - tmp->fill_run = bmp_fill_run; - tmp->type = GF_STENCIL_TEXTURE; - /*default is using the surface settings*/ - tmp->filter = GF_TEXTURE_FILTER_DEFAULT; - tmp->mod = 0; - gf_cmx_init(&tmp->cmat); - tmp->alpha = 255; - return (EVGStencil *) tmp; -} - - -u32 get_pix_argb(char *pix) { - return GF_COL_ARGB(*(pix+3) & 0xFF, *(pix+2) & 0xFF, *(pix+1) & 0xFF, *pix & 0xFF); -} -u32 get_pix_rgba(char *pix) { - return GF_COL_ARGB(*(pix+3) & 0xFF, *pix & 0xFF, *(pix+1) & 0xFF, *(pix+2) & 0xFF); -} -u32 get_pix_rgb_32(char *pix) { - return GF_COL_ARGB(0xFF, *(pix+2) & 0xFF, *(pix+1) & 0xFF, *pix & 0xFF); -} -u32 get_pix_rgb_24(char *pix) { - return GF_COL_ARGB(0xFF, *pix & 0xFF, *(pix+1) & 0xFF, *(pix+2) & 0xFF); -} -u32 get_pix_bgr_24(char *pix) { - return GF_COL_ARGB(0xFF, *(pix+2) & 0xFF, * (pix+1) & 0xFF, *pix & 0xFF); -} -u32 get_pix_444(char *pix) { - u16 val = *(u16*)pix; - return GF_COL_ARGB(0xFF, (u8) ( (val >> 4) & 0xf0), (u8) ( (val) & 0xf0), (u8) ( (val << 4) & 0xf0) ); -} -u32 get_pix_555(char *pix) { - u16 val = *(u16*)pix; - return GF_COL_ARGB(0xFF, (u8) ( (val >> 7) & 0xf8), (u8) ( (val >> 2) & 0xf8), (u8) ( (val << 3) & 0xf8) ); -} -u32 get_pix_565(char *pix) { - u16 val = *(u16*)pix; - return GF_COL_ARGB(0xFF, (u8) ( (val >> 8) & 0xf8), (u8) ( (val >> 3) & 0xfc), (u8) ( (val << 3) & 0xf8) ); -} -u32 get_pix_grey(char *pix) { - u8 val = *pix; - return GF_COL_ARGB(0xFF, val, val, val); -} -u32 get_pix_alphagrey(char *pix) { - return GF_COL_ARGB((u8) *(pix+1), (u8) *pix, (u8) *pix, (u8) *pix); -} - -static void texture_set_callback(EVG_Texture *_this) -{ - switch (_this->pixel_format) { - case GF_PIXEL_RGBA: - _this->tx_get_pixel = get_pix_rgba; - return; - case GF_PIXEL_ARGB: - _this->tx_get_pixel = get_pix_argb; - return; - case GF_PIXEL_RGB_32: - _this->tx_get_pixel = get_pix_rgb_32; - return; - case GF_PIXEL_RGB_24: - _this->tx_get_pixel = get_pix_rgb_24; - return; - case GF_PIXEL_BGR_24: - _this->tx_get_pixel = get_pix_bgr_24; - return; - case GF_PIXEL_RGB_444: - _this->tx_get_pixel = get_pix_444; - return; - case GF_PIXEL_RGB_555: - _this->tx_get_pixel = get_pix_555; - return; - case GF_PIXEL_RGB_565: - _this->tx_get_pixel = get_pix_565; - return; - case GF_PIXEL_GREYSCALE: - _this->tx_get_pixel = get_pix_grey; - return; - case GF_PIXEL_ALPHAGREY: - _this->tx_get_pixel = get_pix_alphagrey; - return; - } -} - -GF_Err evg_stencil_set_texture(GF_STENCIL st, char *pixels, u32 width, u32 height, u32 stride, GF_PixelFormat pixelFormat, GF_PixelFormat destination_format_hint, Bool no_copy) -{ - EVG_Texture *_this = (EVG_Texture *) st; - if (!_this || (_this->type != GF_STENCIL_TEXTURE) || !pixels || !width || !height || !stride || _this->owns_texture) - return GF_BAD_PARAM; - - _this->pixels = 0L; - _this->is_converted = GF_TRUE; - - switch (pixelFormat) { - case GF_PIXEL_ARGB: - case GF_PIXEL_RGBA: - case GF_PIXEL_RGB_32: - _this->Bpp = 4; - break; - case GF_PIXEL_RGB_24: - case GF_PIXEL_BGR_24: - _this->Bpp = 3; - break; - case GF_PIXEL_RGB_555: - case GF_PIXEL_RGB_565: - case GF_PIXEL_RGB_444: - case GF_PIXEL_ALPHAGREY: - _this->Bpp = 2; - break; - case GF_PIXEL_GREYSCALE: - _this->Bpp = 1; - break; - case GF_PIXEL_YV12: - case GF_PIXEL_IYUV: - case GF_PIXEL_I420: - _this->orig_format = GF_PIXEL_YV12; - _this->orig_buf = (u8 *) pixels; - _this->orig_stride = stride; - _this->is_converted = GF_FALSE; - break; - case GF_PIXEL_YUVA: - _this->orig_format = GF_PIXEL_YUVA; - _this->orig_buf = (u8 *) pixels; - _this->orig_stride = stride; - _this->is_converted = GF_FALSE; - break; - default: - /*the rest is not supported (eg BGR32)*/ - return GF_NOT_SUPPORTED; - } - _this->pixel_format = pixelFormat; - _this->width = width; - _this->height = height; - _this->stride = stride; - _this->pixels = (char *) pixels; - texture_set_callback(_this); - return GF_OK; -} - - -GF_Err evg_stencil_set_tiling(GF_STENCIL st, GF_TextureTiling mode) -{ - EVG_Texture *_this = (EVG_Texture *) st; - if (!_this || (_this->type != GF_STENCIL_TEXTURE)) return GF_BAD_PARAM; - _this->mod = mode; - return GF_OK; -} - -GF_Err evg_stencil_set_filter(GF_STENCIL st, GF_TextureFilter filter_mode) -{ - EVG_Texture *_this = (EVG_Texture *) st; - if (!_this || (_this->type != GF_STENCIL_TEXTURE)) return GF_BAD_PARAM; - _this->filter = filter_mode; - return GF_OK; -} - -GF_Err evg_stencil_set_color_matrix(GF_STENCIL st, GF_ColorMatrix *cmat) -{ - EVG_Texture *_this = (EVG_Texture *)st; - if (!_this) return GF_BAD_PARAM; - if (!cmat) gf_cmx_init(&_this->cmat); - else gf_cmx_copy(&_this->cmat, cmat); - return GF_OK; -} - -GF_Err evg_stencil_set_alpha(GF_STENCIL st, u8 alpha) -{ - EVG_Texture *_this = (EVG_Texture *)st; - if (!_this) return GF_BAD_PARAM; - if (_this->type==GF_STENCIL_SOLID) return GF_BAD_PARAM; - if (_this->type==GF_STENCIL_TEXTURE) - _this->alpha = alpha; - else - ((EVG_BaseGradient*)st)->alpha = alpha; - return GF_OK; -} - - -/*internal*/ -void evg_set_texture_active(EVGStencil *st) -{ - GF_VideoSurface src, dst; - EVG_Texture *_this = (EVG_Texture *)st; - if (_this->is_converted) return; - - /*perform YUV->RGB*/ - - if (_this->orig_format == GF_PIXEL_YV12) { - _this->Bpp = 3; - _this->pixel_format = GF_PIXEL_RGB_24; - } else { - _this->Bpp = 4; - _this->pixel_format = GF_PIXEL_ARGB; - } - if (_this->Bpp * _this->width * _this->height > _this->conv_size) { - if (_this->conv_buf) gf_free(_this->conv_buf); - _this->conv_size = _this->Bpp * _this->width * _this->height; - _this->conv_buf = (unsigned char *) gf_malloc(sizeof(unsigned char)*_this->conv_size); - } - - memset(&src, 0, sizeof(GF_VideoSurface)); - src.height = _this->height; - src.width = _this->width; - src.pitch_x = 0; - src.pitch_y = _this->orig_stride; - src.pixel_format = _this->orig_format; - src.video_buffer = (char *)_this->orig_buf; - - memset(&dst, 0, sizeof(GF_VideoSurface)); - dst.width = _this->width; - dst.height = _this->height; - dst.pitch_x = _this->Bpp; - dst.pitch_y = _this->Bpp * _this->width; - dst.pixel_format = _this->pixel_format; - dst.video_buffer = (char *) _this->conv_buf; - - gf_stretch_bits(&dst, &src, NULL, NULL, 0xFF, GF_FALSE, NULL, NULL); - - _this->is_converted = GF_TRUE; - _this->pixels = (char *) _this->conv_buf; - _this->stride = _this->Bpp * _this->width; - texture_set_callback(_this); -} - diff --git a/modules/soft_raster/surface.c b/modules/soft_raster/surface.c deleted file mode 100644 index 944d0ef..0000000 --- a/modules/soft_raster/surface.c +++ /dev/null @@ -1,662 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / software 2D rasterizer module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - * - */ - - -#include "rast_soft.h" - -static void get_surface_world_matrix(EVGSurface *_this, GF_Matrix2D *mat) -{ - gf_mx2d_init(*mat); - if (_this->center_coords) { - gf_mx2d_add_scale(mat, FIX_ONE, -FIX_ONE); - gf_mx2d_add_translation(mat, INT2FIX(_this->width / 2), INT2FIX(_this->height / 2)); - } -} - -GF_SURFACE evg_surface_new(GF_Raster2D *_dr, Bool center_coords) -{ - EVGSurface *_this; - GF_SAFEALLOC(_this, EVGSurface); - if (_this) { - _this->center_coords = center_coords; - _this->texture_filter = GF_TEXTURE_FILTER_DEFAULT; - _this->ftparams.source = &_this->ftoutline; - _this->ftparams.user = _this; - _this->raster = evg_raster_new(); - } - return _this; -} - -void evg_surface_delete(GF_SURFACE _this) -{ - EVGSurface *surf = (EVGSurface *)_this; - if (!surf) - return; -#ifndef INLINE_POINT_CONVERSION - if (surf->points) gf_free(surf->points); - surf->points = NULL; -#endif - if (surf->stencil_pix_run) gf_free(surf->stencil_pix_run); - surf->stencil_pix_run = NULL; - if (surf->raster) evg_raster_del(surf->raster); - surf->raster = NULL; - gf_free(surf); -} - -GF_Err evg_surface_set_matrix(GF_SURFACE _this, GF_Matrix2D *mat) -{ - GF_Matrix2D tmp; - EVGSurface *surf = (EVGSurface *)_this; - if (!surf) return GF_BAD_PARAM; - get_surface_world_matrix(surf, &surf->mat); - if (!mat) return GF_OK; - gf_mx2d_init(tmp); - gf_mx2d_add_matrix(&tmp, mat); - gf_mx2d_add_matrix(&tmp, &surf->mat); - gf_mx2d_copy(surf->mat, tmp); - return GF_OK; -} - - -GF_Err evg_surface_attach_to_callbacks(GF_SURFACE _this, GF_RasterCallback *callbacks, u32 width, u32 height) -{ - EVGSurface *surf = (EVGSurface *)_this; - if (!surf || !width || !height || !callbacks) return GF_BAD_PARAM; - if (!callbacks->cbk || !callbacks->fill_run_alpha || !callbacks->fill_run_no_alpha || !callbacks->fill_rect) return GF_BAD_PARAM; - - surf->width = width; - surf->height = height; - if (surf->stencil_pix_run) gf_free(surf->stencil_pix_run); - surf->stencil_pix_run = (u32 *) gf_malloc(sizeof(u32) * (width+2)); - - surf->raster_cbk = callbacks->cbk; - surf->raster_fill_run_alpha = callbacks->fill_run_alpha; - surf->raster_fill_run_no_alpha = callbacks->fill_run_no_alpha; - surf->raster_fill_rectangle = callbacks->fill_rect; - evg_surface_set_matrix(surf, NULL); - return GF_OK; -} - - -GF_Err evg_surface_attach_to_buffer(GF_SURFACE _this, char *pixels, u32 width, u32 height, s32 pitch_x, s32 pitch_y, GF_PixelFormat pixelFormat) -{ - u32 BPP; - EVGSurface *surf = (EVGSurface *)_this; - if (!surf || !pixels || (pixelFormat>GF_PIXEL_YUVA)) return GF_BAD_PARAM; - - switch (pixelFormat) { -#ifdef GF_RGB_444_SUPORT - case GF_PIXEL_RGB_444: -#endif -#ifdef GF_RGB_555_SUPORT - case GF_PIXEL_RGB_555: -#endif - case GF_PIXEL_RGB_565: - BPP = 2; - break; - case GF_PIXEL_BGR_24: - case GF_PIXEL_RGB_24: - BPP = 3; - break; - case GF_PIXEL_RGB_32: - case GF_PIXEL_ARGB: - case GF_PIXEL_RGBA: - case GF_PIXEL_BGR_32: - BPP = 4; - break; - default: - return GF_NOT_SUPPORTED; - } - if (!pitch_x) pitch_x = BPP; - surf->pitch_x = pitch_x; - surf->pitch_y = pitch_y; - if (!surf->stencil_pix_run || (surf->width != width)) { - if (surf->stencil_pix_run) gf_free(surf->stencil_pix_run); - surf->stencil_pix_run = (u32 *) gf_malloc(sizeof(u32) * (width+2)); - } - surf->width = width; - surf->height = height; - surf->pixels = (char*)pixels; - surf->pixelFormat = pixelFormat; - surf->BPP = BPP; - - surf->raster_cbk = NULL; - surf->raster_fill_run_alpha = NULL; - surf->raster_fill_run_no_alpha = NULL; - evg_surface_set_matrix(_this, NULL); - return GF_OK; -} - - -GF_Err evg_surface_attach_to_texture(GF_SURFACE _this, GF_STENCIL sten) -{ - u32 BPP; - EVGSurface *surf = (EVGSurface *)_this; - EVG_Texture *tx = (EVG_Texture *) sten;; - if (!surf || (tx->type != GF_STENCIL_TEXTURE)) return GF_BAD_PARAM; - - switch (tx->pixel_format) { - case GF_PIXEL_GREYSCALE: - BPP = 1; - break; - case GF_PIXEL_ALPHAGREY: -#ifdef GF_RGB_444_SUPORT - case GF_PIXEL_RGB_444: -#endif -#ifdef GF_RGB_555_SUPORT - case GF_PIXEL_RGB_555: -#endif - case GF_PIXEL_RGB_565: - BPP = 2; - break; - case GF_PIXEL_BGR_24: - case GF_PIXEL_RGB_24: - BPP = 3; - break; - case GF_PIXEL_RGB_32: - case GF_PIXEL_BGR_32: - case GF_PIXEL_ARGB: - case GF_PIXEL_RGBA: - BPP = 4; - break; - default: - return GF_NOT_SUPPORTED; - } - surf->pitch_x = BPP; - surf->pitch_y = tx->stride; - if (surf->stencil_pix_run) gf_free(surf->stencil_pix_run); - surf->stencil_pix_run = (u32 *) gf_malloc(sizeof(u32) * (tx->width+2)); - - surf->width = tx->width; - surf->height = tx->height; - surf->pixels = tx->pixels; - surf->pixelFormat = tx->pixel_format; - surf->BPP = BPP; - surf->raster_cbk = NULL; - surf->raster_fill_run_alpha = NULL; - surf->raster_fill_run_no_alpha = NULL; - evg_surface_set_matrix(surf, NULL); - return GF_OK; -} - -void evg_surface_detach(GF_SURFACE _this) -{ - EVGSurface *surf = (EVGSurface *)_this; - surf->raster_cbk = NULL; - surf->raster_fill_run_alpha = NULL; - surf->raster_fill_run_no_alpha = NULL; -} - -GF_Err evg_surface_clear(GF_SURFACE _this, GF_IRect *rc, u32 color) -{ - GF_IRect clear; - EVGSurface *surf = (EVGSurface *)_this; - if (!surf) return GF_BAD_PARAM; - - if (rc) { - s32 _x, _y; - if (surf->center_coords) { - _x = rc->x + surf->width / 2; - _y = surf->height / 2 - rc->y; - } else { - _x = rc->x; - _y = rc->y - rc->height; - } - - clear.width = (u32) rc->width; - if (_x>=0) { - clear.x = (u32) _x; - } else { - if ( (s32) clear.width + _x < 0) return GF_BAD_PARAM; - clear.width += _x; - clear.x = 0; - } - clear.height = (u32) rc->height; - if (_y>=0) { - clear.y = _y; - } else { - if ( (s32) clear.height + _y < 0) return GF_BAD_PARAM; - clear.height += _y; - clear.y = 0; - } - } else { - clear.x = clear.y = 0; - clear.width = surf->width; - clear.height = surf->height; - } - - if (surf->raster_cbk) { - surf->raster_fill_rectangle(surf->raster_cbk, clear.x, clear.y, clear.width, clear.height, color); - return GF_OK; - } - - switch (surf->pixelFormat) { - case GF_PIXEL_ARGB: - case GF_PIXEL_RGB_32: - return evg_surface_clear_bgra(surf, clear, color); - case GF_PIXEL_BGR_32: - return evg_surface_clear_rgbx(surf, clear, color); - case GF_PIXEL_RGBA: - return evg_surface_clear_rgba(surf, clear, color); - case GF_PIXEL_BGR_24: - return evg_surface_clear_bgr(surf, clear, color); - case GF_PIXEL_RGB_24: - return evg_surface_clear_rgb(surf, clear, color); - case GF_PIXEL_RGB_565: - return evg_surface_clear_565(surf, clear, color); -#ifdef GF_RGB_444_SUPORT - case GF_PIXEL_RGB_444: - return evg_surface_clear_444(surf, clear, color); -#endif -#ifdef GF_RGB_555_SUPORT - case GF_PIXEL_RGB_555: - return evg_surface_clear_555(surf, clear, color); -#endif - default: - return GF_BAD_PARAM; - } -} - -GF_Err evg_surface_set_raster_level(GF_SURFACE _this, GF_RasterLevel RasterSetting) -{ - EVGSurface *surf = (EVGSurface *)_this; - if (!surf) return GF_BAD_PARAM; - switch (RasterSetting) { - case GF_RASTER_HIGH_QUALITY: - surf->texture_filter = GF_TEXTURE_FILTER_HIGH_QUALITY; - break; - case GF_RASTER_MID: - surf->texture_filter = GF_TEXTURE_FILTER_HIGH_QUALITY; - break; - case GF_RASTER_HIGH_SPEED: - default: - surf->texture_filter = GF_TEXTURE_FILTER_HIGH_SPEED; - break; - } - return GF_OK; -} - - -GF_Err evg_surface_set_clipper(GF_SURFACE _this , GF_IRect *rc) -{ - EVGSurface *surf = (EVGSurface *)_this; - if (!surf) return GF_BAD_PARAM; - if (rc) { - surf->clipper = *rc; - surf->useClipper = 1; - /*clipper was given in BIFS like coords, we work with bottom-min for rect, (0,0) top-left of surface*/ - if (surf->center_coords) { - surf->clipper.x += surf->width / 2; - surf->clipper.y = surf->height / 2 - rc->y; - } else { - surf->clipper.y -= rc->height; - } - - if (surf->clipper.x <=0) { - if (surf->clipper.x + (s32) surf->clipper.width < 0) return GF_BAD_PARAM; - surf->clipper.width += surf->clipper.x; - surf->clipper.x = 0; - } - if (surf->clipper.y <=0) { - if (surf->clipper.y + (s32) surf->clipper.height < 0) return GF_BAD_PARAM; - surf->clipper.height += surf->clipper.y; - surf->clipper.y = 0; - } - if (surf->clipper.x + surf->clipper.width > (s32) surf->width) { - surf->clipper.width = surf->width - surf->clipper.x; - } - if (surf->clipper.y + surf->clipper.height > (s32) surf->height) { - surf->clipper.height = surf->height - surf->clipper.y; - } - } else { - surf->useClipper = 0; - } - return GF_OK; -} - - -static Bool setup_grey_callback(EVGSurface *surf) -{ - u32 col, a; - Bool use_const = GF_TRUE; - - if (surf->sten->type == GF_STENCIL_SOLID) { - col = surf->fill_col = ((EVG_Brush *)surf->sten)->color; - a = GF_COL_A(surf->fill_col); - } else { - col = a = 0; - use_const = GF_FALSE; - } - - if (surf->raster_cbk) { - if (use_const) { - if (!a) return GF_FALSE; - if (a!=0xFF) { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_user_fill_const_a; - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_user_fill_const; - } - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_user_fill_var; - } - return GF_TRUE; - } - - switch (surf->pixelFormat) { - case GF_PIXEL_ARGB: - if (use_const) { - if (!a) return GF_FALSE; - if (a!=0xFF) { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_bgra_fill_const_a; - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_bgra_fill_const; - } - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_bgra_fill_var; - } - break; - - case GF_PIXEL_RGBA: - if (use_const) { - if (!a) { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_rgba_fill_erase; - } else if (a!=0xFF) { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_rgba_fill_const_a; - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_rgba_fill_const; - } - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_rgba_fill_var; - } - break; - - case GF_PIXEL_RGB_32: - if (use_const) { - if (!a) return GF_FALSE; - if (a!=0xFF) { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_bgrx_fill_const_a; - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_bgrx_fill_const; - } - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_bgrx_fill_var; - } - break; - - case GF_PIXEL_BGR_32: - if (use_const) { - if (!a) return GF_FALSE; - if (a!=0xFF) { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_rgbx_fill_const_a; - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_rgbx_fill_const; - } - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_rgbx_fill_var; - } - break; - - case GF_PIXEL_RGB_24: - if (use_const) { - if (!a) return GF_FALSE; - if (a!=0xFF) { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_rgb_fill_const_a; - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_rgb_fill_const; - } - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_rgb_fill_var; - } - break; - case GF_PIXEL_BGR_24: - if (use_const) { - if (!a) return GF_FALSE; - if (a!=0xFF) { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_bgr_fill_const_a; - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_bgr_fill_const; - } - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_bgr_fill_var; - } - break; - case GF_PIXEL_RGB_565: - if (use_const) { - surf->fill_565 = GF_COL_TO_565(col); - if (!a) return GF_FALSE; - if (a!=0xFF) { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_565_fill_const_a; - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_565_fill_const; - } - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_565_fill_var; - assert(surf->sten->fill_run); - } - break; -#ifdef GF_RGB_444_SUPORT - if (use_const) { - surf->fill_444 = GF_COL_TO_444(col); - if (!a) return 0; - if (a!=0xFF) { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_444_fill_const_a; - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_444_fill_const; - } - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_444_fill_var; - } - break; -#endif -#ifdef GF_RGB_555_SUPORT - case GF_PIXEL_RGB_555: - if (use_const) { - surf->fill_555 = GF_COL_TO_555(col); - if (!a) return 0; - if (a!=0xFF) { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_555_fill_const_a; - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_555_fill_const; - } - } else { - surf->ftparams.gray_spans = (EVG_Raster_Span_Func) evg_555_fill_var; - } - break; -#endif - } - return GF_TRUE; -} - - -GF_Err evg_surface_set_path(GF_SURFACE _this, GF_Path *gp) -{ -#ifndef INLINE_POINT_CONVERSION - u32 i; - GF_Point2D pt; -#endif - EVGSurface *surf = (EVGSurface *)_this; - - if (!surf) return GF_BAD_PARAM; - if (!gp || !gp->n_points) { - surf->ftoutline.n_points = 0; - surf->ftoutline.n_contours = 0; - return GF_OK; - } - gf_path_flatten(gp); - surf->ftoutline.n_points = gp->n_points; - surf->ftoutline.n_contours = gp->n_contours; - - surf->ftoutline.tags = gp->tags; - surf->ftoutline.contours = (s32*) gp->contours; - - /*store path bounds for gradient/textures*/ - gf_path_get_bounds(gp, &surf->path_bounds); - /*invert Y (ft uses min Y)*/ - surf->path_bounds.y -= surf->path_bounds.height; - - surf->ftoutline.flags = 0; - if (gp->flags & GF_PATH_FILL_ZERO_NONZERO) surf->ftoutline.flags = GF_PATH_FILL_ZERO_NONZERO; - -#ifdef INLINE_POINT_CONVERSION - surf->ftoutline.n_points = gp->n_points; - surf->ftoutline.points = gp->points; - surf->ftparams.mx = &surf->mat; -#else - if (surf->pointlen < gp->n_points) { - surf->points = gf_realloc(surf->points, sizeof(EVG_Vector) * gp->n_points); - if (surf->points == NULL) { - surf->pointlen = 0; - return GF_OUT_OF_MEM; - } - surf->pointlen = gp->n_points; - } - surf->ftoutline.points = surf->points; - - for (i=0; i<gp->n_points; i++) { - pt = gp->points[i]; - gf_mx2d_apply_point(&surf->mat, &pt); -#ifdef GPAC_FIXED_POINT - surf->points[i].x = pt.x; - surf->points[i].y = pt.y; -#else - /*move to 16.16 representation*/ - surf->points[i].x = (u32) (pt.x * 0x10000L); - surf->points[i].y = (u32) (pt.y * 0x10000L); -#endif - } -#endif - return GF_OK; -} - -/* static void gray_spans_stub(s32 y, s32 count, EVG_Span *spans, EVGSurface *surf){} */ - -GF_Err evg_surface_fill(GF_SURFACE _this, GF_STENCIL stencil) -{ - GF_Rect rc; - GF_Matrix2D mat, st_mat; - Bool restore_filter; - EVGSurface *surf = (EVGSurface *)_this; - EVGStencil *sten = (EVGStencil *)stencil; - if (!surf || !stencil) return GF_BAD_PARAM; - if (!surf->ftoutline.n_points) return GF_OK; - surf->sten = sten; - - /*setup ft raster calllbacks*/ - if (!setup_grey_callback(surf)) return GF_OK; - - /* surf->ftparams.gray_spans = gray_spans_stub; */ - - get_surface_world_matrix(surf, &mat); - - restore_filter = GF_FALSE; - /*get path frame for texture convertion */ - if (sten->type != GF_STENCIL_SOLID) { - rc = surf->path_bounds; - gf_mx2d_apply_rect(&mat, &rc); - rc.x = rc.y = 0; - /*assign target frame and matrix*/ - sten->frame = rc; - gf_mx2d_copy(sten->pmat, surf->mat); - gf_mx2d_inverse(&sten->pmat); - - gf_mx2d_copy(st_mat, sten->smat); - gf_mx2d_init(sten->smat); - switch (sten->type) { - case GF_STENCIL_TEXTURE: - if (! ((EVG_Texture *)sten)->pixels) return GF_BAD_PARAM; - - if (((EVG_Texture *)sten)->mod & GF_TEXTURE_FLIP) { - if (!surf->center_coords) gf_mx2d_add_scale(&sten->smat, FIX_ONE, -FIX_ONE); - } else { - if (surf->center_coords) gf_mx2d_add_scale(&sten->smat, FIX_ONE, -FIX_ONE); - } - evg_set_texture_active(sten); - gf_mx2d_add_matrix(&sten->smat, &st_mat); - gf_mx2d_add_matrix(&sten->smat, &mat); - gf_mx2d_inverse(&sten->smat); - evg_bmp_init(sten); - if (((EVG_Texture *)sten)->filter == GF_TEXTURE_FILTER_DEFAULT) { - restore_filter = GF_TRUE; - ((EVG_Texture *)sten)->filter = surf->texture_filter; - } - - break; - case GF_STENCIL_LINEAR_GRADIENT: - { - EVG_LinearGradient *lin = (EVG_LinearGradient *)sten; - gf_mx2d_add_matrix(&sten->smat, &st_mat); - gf_mx2d_add_matrix(&sten->smat, &mat); - gf_mx2d_inverse(&sten->smat); - /*and finalize matrix in gradient coord system*/ - gf_mx2d_add_matrix(&sten->smat, &lin->vecmat); - gf_mx2d_add_scale(&sten->smat, INT2FIX(1<<EVGGRADIENTBITS), INT2FIX(1<<EVGGRADIENTBITS)); - - } - break; - case GF_STENCIL_RADIAL_GRADIENT: - { - EVG_RadialGradient *rad = (EVG_RadialGradient*)sten; - gf_mx2d_copy(sten->smat, st_mat); - gf_mx2d_add_matrix(&sten->smat, &mat); - gf_mx2d_inverse(&sten->smat); - gf_mx2d_add_translation(&sten->smat, -rad->center.x, -rad->center.y); - gf_mx2d_add_scale(&sten->smat, gf_invfix(rad->radius.x), gf_invfix(rad->radius.y)); - - rad->d_f.x = gf_divfix(rad->focus.x - rad->center.x, rad->radius.x); - rad->d_f.y = gf_divfix(rad->focus.y - rad->center.y, rad->radius.y); - /*init*/ - evg_radial_init(rad); - } - break; - } - } - - if (surf->useClipper) { - surf->ftparams.clip_xMin = surf->clipper.x; - surf->ftparams.clip_yMin = surf->clipper.y; - surf->ftparams.clip_xMax = (surf->clipper.x + surf->clipper.width); - surf->ftparams.clip_yMax = (surf->clipper.y + surf->clipper.height); - } else { - surf->ftparams.clip_xMin = 0; - surf->ftparams.clip_yMin = 0; - surf->ftparams.clip_xMax = (surf->width); - surf->ftparams.clip_yMax = (surf->height); - } - - /*and call the raster*/ - evg_raster_render(surf->raster, &surf->ftparams); - - /*restore stencil matrix*/ - if (sten->type != GF_STENCIL_SOLID) { - gf_mx2d_copy(sten->smat, st_mat); - if (restore_filter) ((EVG_Texture *)sten)->filter = GF_TEXTURE_FILTER_DEFAULT; - } - surf->sten = 0L; - return GF_OK; -} - - - diff --git a/modules/svg_in/Makefile b/modules/svg_in/Makefile deleted file mode 100644 index ded2911..0000000 --- a/modules/svg_in/Makefile +++ /dev/null @@ -1,57 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/svg_in - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -ifeq ($(CONFIG_ZLIB), local) -CFLAGS+= -I"$(LOCAL_INC_PATH)/zlib" -EXTRALIBS+=-L../../extra_lib/lib/gcc -endif -ifneq ($(CONFIG_ZLIB), no) -EXTRALIBS+=-lz -endif - -#common obj -OBJS= svg_in.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_svg_in$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols svg_in.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_svg_in-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac_static $(LDFLAGS) -endif - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/svg_in/svg_in.c b/modules/svg_in/svg_in.c deleted file mode 100644 index 0d61f97..0000000 --- a/modules/svg_in/svg_in.c +++ /dev/null @@ -1,466 +0,0 @@ -/* - * GPAC Multimedia Framework - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2012 - * All rights reserved - * - * This file is part of GPAC / SVG Loader module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include <gpac/internal/terminal_dev.h> -#include <gpac/scene_manager.h> -#include <gpac/constants.h> - -#if !defined(GPAC_DISABLE_SVG) && !defined(GPAC_DISABLE_ZLIB) - -#include <zlib.h> - -typedef struct -{ - GF_SceneLoader loader; - GF_Scene *scene; - u8 oti; - char *file_name; - u32 file_size; - u32 sax_max_duration; - u16 base_es_id; - u32 file_pos; - gzFile src; -} SVGIn; - -static Bool svg_check_download(SVGIn *svgin) -{ - u64 size; - FILE *f = gf_fopen(svgin->file_name, "rb"); - if (!f) return GF_FALSE; - gf_fseek(f, 0, SEEK_END); - size = gf_ftell(f); - gf_fclose(f); - if (size==svgin->file_size) return GF_TRUE; - return GF_FALSE; -} - -#define SVG_PROGRESSIVE_BUFFER_SIZE 4096 - -static GF_Err svgin_deflate(SVGIn *svgin, const char *buffer, u32 buffer_len) -{ - GF_Err e; - char svg_data[2049]; - int err; - u32 done = 0; - z_stream d_stream; - d_stream.zalloc = (alloc_func)0; - d_stream.zfree = (free_func)0; - d_stream.opaque = (voidpf)0; - d_stream.next_in = (Bytef*)buffer; - d_stream.avail_in = buffer_len; - d_stream.next_out = (Bytef*)svg_data; - d_stream.avail_out = 2048; - - err = inflateInit(&d_stream); - if (err == Z_OK) { - e = GF_OK; - while (d_stream.total_in < buffer_len) { - err = inflate(&d_stream, Z_NO_FLUSH); - if (err < Z_OK) { - e = GF_NON_COMPLIANT_BITSTREAM; - break; - } - svg_data[d_stream.total_out - done] = 0; - e = gf_sm_load_string(&svgin->loader, svg_data, GF_FALSE); - if (e || (err== Z_STREAM_END)) break; - done = (u32) d_stream.total_out; - d_stream.avail_out = 2048; - d_stream.next_out = (Bytef*)svg_data; - } - inflateEnd(&d_stream); - return e; - } - return GF_NON_COMPLIANT_BITSTREAM; -} - -static GF_Err SVG_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 stream_time, u32 mmlevel) -{ - GF_Err e = GF_OK; - SVGIn *svgin = (SVGIn *)plug->privateStack; - - if (stream_time==(u32)-1) { - if (svgin->src) gzclose(svgin->src); - svgin->src = NULL; - gf_sm_load_done(&svgin->loader); - svgin->loader.fileName = NULL; - svgin->file_pos = 0; - gf_sg_reset(svgin->scene->graph); - return GF_OK; - } - - switch (svgin->oti) { - /*!OTI for SVG dummy stream (dsi = file name) - GPAC internal*/ - case GPAC_OTI_PRIVATE_SCENE_SVG: - /*full doc parsing*/ - if ((svgin->sax_max_duration==(u32) -1) && svgin->file_size) { - /*init step*/ - if (!svgin->loader.fileName) { - /*not done yet*/ - if (!svg_check_download(svgin)) return GF_OK; - svgin->loader.fileName = svgin->file_name; - e = gf_sm_load_init(&svgin->loader); - } else { - e = gf_sm_load_run(&svgin->loader); - } - } - /*chunk parsing*/ - else { - u32 entry_time; - char file_buf[SVG_PROGRESSIVE_BUFFER_SIZE+2]; - /*initial load*/ - if (!svgin->src && !svgin->file_pos) { - svgin->src = gzopen(svgin->file_name, "rb"); - if (!svgin->src) return GF_URL_ERROR; - svgin->loader.fileName = svgin->file_name; - gf_sm_load_init(&svgin->loader); - } - e = GF_OK; - entry_time = gf_sys_clock(); - - while (1) { - u32 diff; - s32 nb_read; - nb_read = gzread(svgin->src, file_buf, SVG_PROGRESSIVE_BUFFER_SIZE); - /*we may have read nothing but we still need to call parse in case the parser got suspended*/ - if (nb_read<=0) { - if ((e==GF_EOS) && gzeof(svgin->src)) { - gf_set_progress("SVG Parsing", svgin->file_pos, svgin->file_size); - gzclose(svgin->src); - svgin->src = NULL; - gf_sm_load_done(&svgin->loader); - } - goto exit; - } - - file_buf[nb_read] = file_buf[nb_read+1] = 0; - - e = gf_sm_load_string(&svgin->loader, file_buf, GF_FALSE); - svgin->file_pos += nb_read; - - - - /*handle decompression*/ - if (svgin->file_pos > svgin->file_size) svgin->file_size = svgin->file_pos + 1; - if (e) break; - - gf_set_progress("SVG Parsing", svgin->file_pos, svgin->file_size); - diff = gf_sys_clock() - entry_time; - if (diff > svgin->sax_max_duration) { - break; - } - } - } - break; - - /*!OTI for streaming SVG - GPAC internal*/ - case GPAC_OTI_SCENE_SVG: - e = gf_sm_load_string(&svgin->loader, inBuffer, GF_FALSE); - break; - - /*!OTI for streaming SVG + gz - GPAC internal*/ - case GPAC_OTI_SCENE_SVG_GZ: - e = svgin_deflate(svgin, inBuffer, inBufferLength); - break; - - /*!OTI for DIMS (dsi = 3GPP DIMS configuration) - GPAC internal*/ - case GPAC_OTI_SCENE_DIMS: - { - u8 prev, dims_hdr; - u32 nb_bytes, size; - u64 pos; - char * buf2 = gf_malloc(inBufferLength); - GF_BitStream *bs = gf_bs_new(inBuffer, inBufferLength, GF_BITSTREAM_READ); - memcpy(buf2, inBuffer, inBufferLength); -// FILE *f = gf_fopen("dump.svg", "wb"); -// - while (gf_bs_available(bs)) { - pos = gf_bs_get_position(bs); - size = gf_bs_read_u16(bs); - nb_bytes = 2; - /*GPAC internal hack*/ - if (!size) { - size = gf_bs_read_u32(bs); - nb_bytes = 6; - } -// gf_fwrite( inBuffer + pos + nb_bytes + 1, 1, size - 1, f ); - - dims_hdr = gf_bs_read_u8(bs); - prev = buf2[pos + nb_bytes + size]; - - buf2[pos + nb_bytes + size] = 0; - if (dims_hdr & GF_DIMS_UNIT_C) { - e = svgin_deflate(svgin, buf2 + pos + nb_bytes + 1, size - 1); - } else { - e = gf_sm_load_string(&svgin->loader, buf2 + pos + nb_bytes + 1, GF_FALSE); - } - buf2[pos + nb_bytes + size] = prev; - gf_bs_skip_bytes(bs, size-1); - - } -// gf_fclose(f); - gf_bs_del(bs); - } - break; - - default: - return GF_BAD_PARAM; - } - -exit: - if ((e>=GF_OK) && (svgin->scene->graph_attached!=1) && (gf_sg_get_root_node(svgin->loader.scene_graph)!=NULL) ) { - gf_scene_attach_to_compositor(svgin->scene); - } - /*prepare for next playback*/ - if (e) { - gf_sm_load_done(&svgin->loader); - svgin->loader.fileName = NULL; - e = GF_EOS; - } - return e; -} - - - -static GF_Err SVG_AttachScene(GF_SceneDecoder *plug, GF_Scene *scene, Bool is_scene_decoder) -{ - SVGIn *svgin = (SVGIn *)plug->privateStack; - memset(&svgin->loader, 0, sizeof(GF_SceneLoader)); - svgin->loader.is = scene; - svgin->scene = scene; - svgin->loader.scene_graph = scene->graph; - svgin->loader.localPath = gf_modules_get_option((GF_BaseInterface *)plug, "General", "CacheDirectory"); - /*Warning: svgin->loader.type may be overriden in attach stream */ - svgin->loader.type = GF_SM_LOAD_SVG; - svgin->loader.flags = GF_SM_LOAD_FOR_PLAYBACK; - - if (svgin->oti!= GPAC_OTI_PRIVATE_SCENE_SVG) - gf_sm_load_init(&svgin->loader); - return GF_OK; -} - -static GF_Err SVG_ReleaseScene(GF_SceneDecoder *plug) -{ - return GF_OK; -} - -static GF_Err SVG_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd) -{ - const char *sOpt; - GF_BitStream *bs; - SVGIn *svgin = (SVGIn *)plug->privateStack; - if (esd->decoderConfig->upstream) return GF_NOT_SUPPORTED; - - svgin->loader.type = GF_SM_LOAD_SVG; - /* decSpecInfo is not null only when reading from an SVG file (local or distant, cached or not) */ - switch (esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_SCENE_SVG: - case GPAC_OTI_SCENE_SVG_GZ: - svgin->loader.flags |= GF_SM_LOAD_CONTEXT_STREAMING; - /*no decSpecInfo defined for streaming svg yet*/ - break; - case GPAC_OTI_SCENE_DIMS: - svgin->loader.type = GF_SM_LOAD_DIMS; - svgin->loader.flags |= GF_SM_LOAD_CONTEXT_STREAMING; - /*decSpecInfo not yet supported for DIMS svg - we need properties at the scene level to store the - various indications*/ - break; - case GPAC_OTI_PRIVATE_SCENE_SVG: - default: - if (!esd->decoderConfig->decoderSpecificInfo) return GF_NON_COMPLIANT_BITSTREAM; - bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ); - svgin->file_size = gf_bs_read_u32(bs); - svgin->file_pos = 0; - gf_bs_del(bs); - svgin->file_name = (char *) gf_malloc(sizeof(char)*(1 + esd->decoderConfig->decoderSpecificInfo->dataLength - sizeof(u32)) ); - memcpy(svgin->file_name, esd->decoderConfig->decoderSpecificInfo->data + sizeof(u32), esd->decoderConfig->decoderSpecificInfo->dataLength - sizeof(u32) ); - svgin->file_name[esd->decoderConfig->decoderSpecificInfo->dataLength - sizeof(u32) ] = 0; - break; - } - svgin->oti = esd->decoderConfig->objectTypeIndication; - - if (!esd->dependsOnESID) svgin->base_es_id = esd->ESID; - - sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "SAXLoader", "Progressive"); - if (sOpt && !strcmp(sOpt, "yes")) { - svgin->sax_max_duration = 30; - sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "SAXLoader", "MaxDuration"); - if (sOpt) { - svgin->sax_max_duration = atoi(sOpt); - } else { - svgin->sax_max_duration = 30; - gf_modules_set_option((GF_BaseInterface *)plug, "SAXLoader", "MaxDuration", "30"); - } - } else { - svgin->sax_max_duration = (u32) -1; - } - return GF_OK; -} - -static GF_Err SVG_DetachStream(GF_BaseDecoder *plug, u16 ES_ID) -{ - SVGIn *svgin = (SVGIn *)plug->privateStack; - if (svgin->file_name) gf_free(svgin->file_name); - svgin->file_name = NULL; - gf_sm_load_done(&svgin->loader); - return GF_OK; -} - -const char *SVG_GetName(struct _basedecoder *plug) -{ - SVGIn *svgin = (SVGIn *)plug->privateStack; - if (svgin->oti==GPAC_OTI_PRIVATE_SCENE_SVG) return ((svgin->sax_max_duration==(u32)-1) && svgin->file_size) ? "GPAC SVG SAX Parser" : "GPAC SVG Progressive Parser"; - if (svgin->oti==GPAC_OTI_SCENE_SVG) return "GPAC Streaming SVG Parser"; - if (svgin->oti==GPAC_OTI_SCENE_SVG_GZ) return "GPAC Streaming SVGZ Parser"; - if (svgin->oti==GPAC_OTI_SCENE_DIMS) return "GPAC DIMS Parser"; - return "INTERNAL ERROR"; -} - -static u32 SVG_CanHandleStream(GF_BaseDecoder *ifce, u32 StreamType, GF_ESD *esd, u8 PL) -{ - if (StreamType==GF_STREAM_PRIVATE_SCENE) { - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - - if (esd->decoderConfig->objectTypeIndication == GPAC_OTI_PRIVATE_SCENE_SVG) return GF_CODEC_SUPPORTED; - return GF_CODEC_NOT_SUPPORTED; - } else if (StreamType==GF_STREAM_SCENE) { - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - - switch (esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_SCENE_SVG: - case GPAC_OTI_SCENE_SVG_GZ: - case GPAC_OTI_SCENE_DIMS: - return GF_CODEC_SUPPORTED; - default: - return GF_CODEC_NOT_SUPPORTED; - } - } - return GF_CODEC_NOT_SUPPORTED; -} - -static GF_Err SVG_GetCapabilities(GF_BaseDecoder *plug, GF_CodecCapability *cap) -{ - cap->cap.valueInt = 0; - if (cap->CapCode==GF_CODEC_PADDING_BYTES) { - /* Adding one byte of padding for \r\n problems*/ - cap->cap.valueInt = 1; - return GF_OK; - } - return GF_NOT_SUPPORTED; -} - -static GF_Err SVG_SetCapabilities(GF_BaseDecoder *plug, GF_CodecCapability cap) -{ - if (cap.CapCode==GF_CODEC_ABORT) { - SVGIn *svgin = (SVGIn *)plug->privateStack; - gf_sm_load_suspend(&svgin->loader, GF_TRUE); - } - return GF_OK; -} - -/*interface create*/ -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - SVGIn *svgin; - GF_SceneDecoder *sdec; - - if (InterfaceType != GF_SCENE_DECODER_INTERFACE) return NULL; - - GF_SAFEALLOC(sdec, GF_SceneDecoder) - if (!sdec) return NULL; - GF_REGISTER_MODULE_INTERFACE(sdec, GF_SCENE_DECODER_INTERFACE, "GPAC SVG Parser", "gpac distribution"); - - GF_SAFEALLOC(svgin, SVGIn); - if (!svgin) { - gf_free(sdec); - return NULL; - } - sdec->privateStack = svgin; - sdec->AttachStream = SVG_AttachStream; - sdec->CanHandleStream = SVG_CanHandleStream; - sdec->DetachStream = SVG_DetachStream; - sdec->AttachScene = SVG_AttachScene; - sdec->ReleaseScene = SVG_ReleaseScene; - sdec->ProcessData = SVG_ProcessData; - sdec->GetName = SVG_GetName; - sdec->SetCapabilities = SVG_SetCapabilities; - sdec->GetCapabilities = SVG_GetCapabilities; - return (GF_BaseInterface *)sdec; -} - - -/*interface destroy*/ -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - SVGIn *svgin; - GF_SceneDecoder *sdec = (GF_SceneDecoder *)ifce; - if (!sdec) - return; - if (sdec->InterfaceType != GF_SCENE_DECODER_INTERFACE) return; - svgin = (SVGIn *) sdec->privateStack; - if (svgin) - gf_free(svgin); - sdec->privateStack = NULL; - gf_free(sdec); -} - -#else - - -/*interface create*/ -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - return NULL; -} - - -/*interface destroy*/ -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ -} - -#endif - -/*interface query*/ -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifndef GPAC_DISABLE_SVG - GF_SCENE_DECODER_INTERFACE, -#endif - 0 - }; - return si; -} - -GPAC_MODULE_STATIC_DECLARATION( svg_in ) diff --git a/modules/timedtext/Makefile b/modules/timedtext/Makefile deleted file mode 100644 index c3699a9..0000000 --- a/modules/timedtext/Makefile +++ /dev/null @@ -1,50 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/timedtext - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS=timedtext_dec.o timedtext_in.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_timedtext$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols timedtext.def -endif - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_timedtext-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac_static $(LDFLAGS) -endif - - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/timedtext/timedtext_in.c b/modules/timedtext/timedtext_in.c deleted file mode 100644 index 85aed75..0000000 --- a/modules/timedtext/timedtext_in.c +++ /dev/null @@ -1,470 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / 3GPP/MPEG4 timed text module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include <gpac/modules/service.h> -#include <gpac/modules/codec.h> -#include <gpac/constants.h> - -#if !defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_MEDIA_IMPORT) - -#include <gpac/media_tools.h> - -typedef struct -{ - GF_ClientService *service; - Bool od_done; - Bool needs_connection; - u32 status; - LPNETCHANNEL ch; - - GF_SLHeader sl_hdr; - - GF_ISOFile *mp4; - char *szFile; - u32 tt_track; - GF_ISOSample *samp; - u32 samp_num; - - u32 start_range; - /*file downloader*/ - GF_DownloadSession * dnload; -} TTIn; - - -const char * TTIN_MIME_TYPES[] = { - "x-subtitle/srt", "srt", "SRT SubTitles", - "x-subtitle/sub", "sub", "SUB SubTitles", - "x-subtitle/ttxt", "ttxt", "3GPP TimedText", - "text/vtt", "vtt", "VTT SubTitles", - NULL -}; - -static u32 TTIN_RegisterMimeTypes(const GF_InputService *plug) { - u32 i; - if (!plug) - return 0; - for (i = 0 ; TTIN_MIME_TYPES[i]; i+=3) { - gf_service_register_mime(plug, TTIN_MIME_TYPES[i], TTIN_MIME_TYPES[i+1], TTIN_MIME_TYPES[i+2]); - } - return i/3; -} - -static Bool TTIn_CanHandleURL(GF_InputService *plug, const char *url) -{ - char *sExt; - u32 i; - if (!plug || !url) - return GF_FALSE; - sExt = strrchr(url, '.'); - if (!sExt) - return GF_FALSE; - for (i = 0 ; TTIN_MIME_TYPES[i]; i+=3) { - if (gf_service_check_mime_register(plug, TTIN_MIME_TYPES[i], TTIN_MIME_TYPES[i+1], TTIN_MIME_TYPES[i+2], sExt)) - return GF_TRUE; - } - return GF_FALSE; -} - -static Bool TTIn_is_local(const char *url) -{ - if (!url) - return GF_FALSE; - if (!strnicmp(url, "file://", 7)) return GF_TRUE; - if (!strnicmp(url, "gmem://", 7)) return GF_TRUE; - if (strstr(url, "://")) return GF_FALSE; - return GF_TRUE; -} - -static GF_ESD *tti_get_esd(TTIn *tti) -{ - return gf_media_map_esd(tti->mp4, tti->tt_track); -} - -static void tti_setup_object(TTIn *tti, GF_InputService *plug) -{ - GF_ObjectDescriptor *od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - GF_ESD *esd = tti_get_esd(tti); - od->objectDescriptorID = 0; - od->service_ifce = plug; - gf_list_add(od->ESDescriptors, esd); - gf_service_declare_media(tti->service, (GF_Descriptor *)od, GF_FALSE); -} - - -GF_Err TTIn_LoadFile(GF_InputService *plug, const char *url, Bool is_cache) -{ - GF_Err e; - GF_MediaImporter import; - char szFILE[GF_MAX_PATH]; - TTIn *tti = (TTIn *)plug->priv; - const char *cache_dir; - if (!tti || !url) - return GF_BAD_PARAM; - cache_dir = gf_modules_get_option((GF_BaseInterface *)plug, "General", "CacheDirectory"); - if (cache_dir && strlen(cache_dir)) { - if (cache_dir[strlen(cache_dir)-1] != GF_PATH_SEPARATOR) { - sprintf(szFILE, "%s%csrt_%p_mp4", cache_dir, GF_PATH_SEPARATOR, tti); - } else { - sprintf(szFILE, "%ssrt_%p_mp4", cache_dir, tti); - } - } else { - sprintf(szFILE, "%p_temp_mp4", tti); - } - tti->mp4 = gf_isom_open(szFILE, GF_ISOM_OPEN_WRITE, NULL); - if (!tti->mp4) return gf_isom_last_error(NULL); - if (tti->szFile) - gf_free(tti->szFile); - tti->szFile = gf_strdup(szFILE); - - memset(&import, 0, sizeof(GF_MediaImporter)); - import.dest = tti->mp4; - /*override layout from sub file*/ - import.flags = GF_IMPORT_SKIP_TXT_BOX; - - if (!strnicmp(url, "gmem://", 7)) { - u8 *mem_address; - u32 size; - FILE *tmp_txt; - import.streamFormat = "TEXT"; - - if (sscanf(url, "gmem://%d@%p", &size, &mem_address) != 2) { - return GF_BAD_PARAM; - } - strcat(szFILE, "_tmptxt"); - tmp_txt = gf_fopen(szFILE, "w"); - if (!tmp_txt) return GF_IO_ERR; - fwrite(mem_address, size, 1, tmp_txt); - gf_fclose(tmp_txt); - - import.in_name = szFILE; - e = gf_media_import(&import); - - gf_delete_file(szFILE); - - } else { - import.in_name = (char *) url; - e = gf_media_import(&import); - } - if (!e) { - tti->tt_track = 1; - gf_isom_text_set_streaming_mode(tti->mp4, GF_TRUE); - } - return e; -} - -void TTIn_NetIO(void *cbk, GF_NETIO_Parameter *param) -{ - GF_Err e; - const char *szCache; - GF_InputService *plug = (GF_InputService *)cbk; - TTIn *tti = (TTIn *) plug->priv; - if (!tti) - return; - gf_service_download_update_stats(tti->dnload); - - e = param->error; - /*done*/ - if (param->msg_type==GF_NETIO_DATA_TRANSFERED) { - szCache = gf_dm_sess_get_cache_name(tti->dnload); - if (!szCache) e = GF_IO_ERR; - else { - e = TTIn_LoadFile(plug, szCache, GF_TRUE); - } - } - else if (!e || (param->msg_type==GF_NETIO_DATA_EXCHANGE)) - return; - - /*OK confirm*/ - if (tti->needs_connection) { - tti->needs_connection = GF_FALSE; - gf_service_connect_ack(tti->service, NULL, e); - if (!e && !tti->od_done) tti_setup_object(tti, plug); - } -} - -void TTIn_download_file(GF_InputService *plug, const char *url) -{ - TTIn *tti = (TTIn *) plug->priv; - if (!plug || !url) - return; - tti->needs_connection = GF_TRUE; - tti->dnload = gf_service_download_new(tti->service, url, 0, TTIn_NetIO, plug); - if (!tti->dnload) { - tti->needs_connection = GF_FALSE; - gf_service_connect_ack(tti->service, NULL, GF_NOT_SUPPORTED); - } else { - /*start our download (threaded)*/ - gf_dm_sess_process(tti->dnload); - } - /*service confirm is done once fetched*/ -} - -static GF_Err TTIn_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url) -{ - GF_Err e; - TTIn *tti = (TTIn *)plug->priv; - if (!plug || !url) - return GF_BAD_PARAM; - tti->service = serv; - - if (tti->dnload) gf_service_download_del(tti->dnload); - tti->dnload = NULL; - - /*remote fetch*/ - if (!TTIn_is_local(url)) { - TTIn_download_file(plug, url); - return GF_OK; - } - e = TTIn_LoadFile(plug, url, GF_FALSE); - gf_service_connect_ack(serv, NULL, e); - if (!e && !tti->od_done) tti_setup_object(tti, plug); - return GF_OK; -} - -static GF_Err TTIn_CloseService(GF_InputService *plug) -{ - TTIn *tti; - if (!plug) - return GF_BAD_PARAM; - tti = (TTIn *)plug->priv; - if (!tti) - return GF_BAD_PARAM; - if (tti->samp) - gf_isom_sample_del(&tti->samp); - tti->samp = NULL; - if (tti->mp4) - gf_isom_delete(tti->mp4); - tti->mp4 = NULL; - if (tti->szFile) { - gf_delete_file(tti->szFile); - gf_free(tti->szFile); - tti->szFile = NULL; - } - if (tti->dnload) - gf_service_download_del(tti->dnload); - tti->dnload = NULL; - if (tti->service) - gf_service_disconnect_ack(tti->service, NULL, GF_OK); - tti->service = NULL; - return GF_OK; -} - -static GF_Descriptor *TTIn_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) -{ - TTIn *tti; - if (!plug) - return NULL; - tti = (TTIn *)plug->priv; - if (!tti) - return NULL; - /*visual object*/ - switch (expect_type) { - case GF_MEDIA_OBJECT_UNDEF: - case GF_MEDIA_OBJECT_UPDATES: - case GF_MEDIA_OBJECT_TEXT: - { - GF_ObjectDescriptor *od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - GF_ESD *esd = tti_get_esd(tti); - od->objectDescriptorID = esd->ESID; - gf_list_add(od->ESDescriptors, esd); - tti->od_done = GF_TRUE; - return (GF_Descriptor *) od; - } - default: - return NULL; - } - return NULL; -} - -static GF_Err TTIn_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) -{ - u32 ES_ID; - GF_Err e; - TTIn *tti = (TTIn *)plug->priv; - if (!tti) - return GF_BAD_PARAM; - e = GF_SERVICE_ERROR; - if (!tti || tti->ch==channel) goto exit; - - e = GF_STREAM_NOT_FOUND; - ES_ID = 0; - if (strstr(url, "ES_ID")) sscanf(url, "ES_ID=%ud", &ES_ID); - - if (ES_ID==1) { - tti->ch = channel; - e = GF_OK; - } - -exit: - gf_service_connect_ack(tti->service, channel, e); - return e; -} - -static GF_Err TTIn_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) -{ - TTIn *tti = (TTIn *)plug->priv; - GF_Err e = GF_STREAM_NOT_FOUND; - - if (tti->ch == channel) { - tti->ch = NULL; - e = GF_OK; - } - gf_service_disconnect_ack(tti->service, channel, e); - return GF_OK; -} - -static GF_Err TTIn_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) -{ - TTIn *tti = (TTIn *)plug->priv; - if (!tti) - return GF_BAD_PARAM; - if (!com->base.on_channel) return GF_NOT_SUPPORTED; - switch (com->command_type) { - case GF_NET_CHAN_SET_PADDING: - gf_isom_set_sample_padding(tti->mp4, tti->tt_track, com->pad.padding_bytes); - return GF_OK; - case GF_NET_CHAN_DURATION: - com->duration.duration = (Double) (s64) gf_isom_get_media_duration(tti->mp4, tti->tt_track); - com->duration.duration /= gf_isom_get_media_timescale(tti->mp4, tti->tt_track); - return GF_OK; - case GF_NET_CHAN_PLAY: - tti->start_range = (com->play.start_range>0) ? (u32) (com->play.start_range * 1000) : 0; - if (tti->ch == com->base.on_channel) { - tti->samp_num = 0; - if (tti->samp) gf_isom_sample_del(&tti->samp); - } - return GF_OK; - case GF_NET_CHAN_STOP: - return GF_OK; - default: - return GF_OK; - } -} - - -static GF_Err TTIn_ChannelGetSLP(GF_InputService *plug, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, GF_SLHeader *out_sl_hdr, Bool *sl_compressed, GF_Err *out_reception_status, Bool *is_new_data) -{ - TTIn *tti = (TTIn *)plug->priv; - - *out_reception_status = GF_OK; - *sl_compressed = GF_FALSE; - *is_new_data = GF_FALSE; - - memset(&tti->sl_hdr, 0, sizeof(GF_SLHeader)); - tti->sl_hdr.randomAccessPointFlag = 1; - tti->sl_hdr.compositionTimeStampFlag = 1; - tti->sl_hdr.accessUnitStartFlag = tti->sl_hdr.accessUnitEndFlag = 1; - - /*fetching es data*/ - if (tti->ch == channel) { - if (tti->samp_num>=gf_isom_get_sample_count(tti->mp4, tti->tt_track)) { - *out_reception_status = GF_EOS; - return GF_OK; - } - - if (!tti->samp) { - u32 di; - if (tti->start_range) { - u32 di; - *out_reception_status = gf_isom_get_sample_for_movie_time(tti->mp4, tti->tt_track, tti->start_range, &di, GF_ISOM_SEARCH_SYNC_BACKWARD, &tti->samp, &tti->samp_num); - tti->start_range = 0; - } else { - tti->samp = gf_isom_get_sample(tti->mp4, tti->tt_track, tti->samp_num+1, &di); - } - if (!tti->samp) { - *out_reception_status = GF_CORRUPTED_DATA; - return GF_OK; - } - *is_new_data = GF_TRUE; - } - tti->sl_hdr.compositionTimeStamp = tti->sl_hdr.decodingTimeStamp = tti->samp->DTS; - *out_data_ptr = tti->samp->data; - *out_data_size = tti->samp->dataLength; - *out_sl_hdr = tti->sl_hdr; - return GF_OK; - } - return GF_STREAM_NOT_FOUND; -} - -static GF_Err TTIn_ChannelReleaseSLP(GF_InputService *plug, LPNETCHANNEL channel) -{ - TTIn *tti = (TTIn *)plug->priv; - - if (tti->ch == channel) { - if (!tti->samp) return GF_BAD_PARAM; - gf_isom_sample_del(&tti->samp); - tti->samp = NULL; - tti->samp_num++; - return GF_OK; - } - return GF_OK; -} - - -void *NewTTReader() -{ - TTIn *priv; - GF_InputService *plug; - GF_SAFEALLOC(plug, GF_InputService); - if (!plug) return NULL; - GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC TimedText Reader", "gpac distribution") - - GF_SAFEALLOC(priv, TTIn); - if (!priv) { - gf_free(plug); - return NULL; - } - plug->priv = priv; - - plug->RegisterMimeTypes = TTIN_RegisterMimeTypes; - plug->CanHandleURL = TTIn_CanHandleURL; - plug->CanHandleURLInService = NULL; - plug->ConnectService = TTIn_ConnectService; - plug->CloseService = TTIn_CloseService; - plug->GetServiceDescriptor = TTIn_GetServiceDesc; - plug->ConnectChannel = TTIn_ConnectChannel; - plug->DisconnectChannel = TTIn_DisconnectChannel; - plug->ChannelGetSLP = TTIn_ChannelGetSLP; - plug->ChannelReleaseSLP = TTIn_ChannelReleaseSLP; - plug->ServiceCommand = TTIn_ServiceCommand; - return plug; -} - -void DeleteTTReader(void *ifce) -{ - TTIn *tti; - GF_InputService *plug = (GF_InputService *) ifce; - if (!plug) - return; - tti = (TTIn *)plug->priv; - if (tti) { - TTIn_CloseService(plug); - gf_free(tti); - } - plug->priv = NULL; - gf_free(plug); -} - -#endif /* !defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_MEDIA_IMPORT)*/ diff --git a/modules/validator/Makefile b/modules/validator/Makefile index 704e41e..b9983d3 100644 --- a/modules/validator/Makefile +++ b/modules/validator/Makefile @@ -2,14 +2,14 @@ include ../../config.mak vpath %.c $(SRC_PATH)/modules/validator -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -DGPAC_HAVE_CONFIG_H -I../.. +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/modules/validator/validator.c b/modules/validator/validator.c index ab48ca1..f2c74d3 100644 --- a/modules/validator/validator.c +++ b/modules/validator/validator.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Cyril Concolato - * Copyright (c) Telecom ParisTech 2010-2012 + * Copyright (c) Telecom ParisTech 2010-2020 * All rights reserved * * This file is part of GPAC / Test Suite Validator Recorder sub-project @@ -23,8 +23,8 @@ * */ -#include <gpac/modules/term_ext.h> -#include <gpac/internal/terminal_dev.h> +#include <gpac/modules/compositor_ext.h> +#include <gpac/filters.h> #include <gpac/internal/compositor_dev.h> #include <gpac/internal/media_dev.h> #include <gpac/xml.h> @@ -32,15 +32,13 @@ typedef struct __validation_module { - GF_Terminal *term; + GF_Compositor *compositor; Bool is_recording; Bool trace_mode; - char *prev_fps; - char *prev_alias; /* Clock used to synchronize events in recording and playback*/ - GF_Clock *ck; + GF_ObjectManager *root_odm; /* Next event to process */ Bool next_event_snapshot; @@ -72,7 +70,7 @@ typedef struct __validation_module Bool snapshot_next_frame; u32 snapshot_number; - GF_TermEventFilter evt_filter; + GF_FSEventListener evt_filter; } GF_Validator; static void validator_xvs_close(GF_Validator *validator); @@ -92,7 +90,7 @@ static void validator_xvs_add_snapshot_node(GF_Validator *validator, const char snap_node->name = gf_strdup("snapshot"); snap_node->attributes = gf_list_new(); - GF_SAFEALLOC(att, GF_XMLAttribute); + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); if (!att) { GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[Validator] Failed to allocate snapshot\n")); return; @@ -102,7 +100,7 @@ static void validator_xvs_add_snapshot_node(GF_Validator *validator, const char sprintf(att->value, "%d", scene_time); gf_list_add(snap_node->attributes, att); - GF_SAFEALLOC(att, GF_XMLAttribute); + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); if (!att) { GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[Validator] Failed to allocate snapshot\n")); return; @@ -128,7 +126,7 @@ static char *validator_get_snapshot_name(GF_Validator *validator, Bool is_refere char *name = validator->test_filename ? validator->test_filename : validator->xvs_filename; char *dot; char dumpname[GF_MAX_PATH]; - dot = strrchr(name, '.'); + dot = gf_file_ext_start(name); dot[0] = 0; sprintf(dumpname, "%s-%s-%03d.png", name, (is_reference?"reference":"newest"), number); dot[0] = '.'; @@ -139,12 +137,12 @@ static char *validator_create_snapshot(GF_Validator *validator) { GF_Err e; GF_VideoSurface fb; - GF_Terminal *term = validator->term; + GF_Compositor *compositor = validator->compositor; char *dumpname; dumpname = validator_get_snapshot_name(validator, validator->is_recording, validator->snapshot_number); - e = gf_term_get_screen_buffer(term, &fb); + e = gf_sc_get_screen_buffer(compositor, &fb, 0); if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Error dumping screen buffer %s\n", gf_error_to_string(e))); } else { @@ -159,24 +157,65 @@ static char *validator_create_snapshot(GF_Validator *validator) if (!png) { GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Error writing file %s\n", dumpname)); } else { - gf_fwrite(dst, dst_size, 1, png); + gf_fwrite(dst, dst_size, png); gf_fclose(png); GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[Validator] Writing file %s\n", dumpname)); } } if (dst) gf_free(dst); - gf_term_release_screen_buffer(term, &fb); + gf_sc_release_screen_buffer(compositor, &fb); } validator->snapshot_number++; return dumpname; } +static GF_Err validator_file_dec(char *png_filename, u32 *hint_codecid, u32 *width, u32 *height, u32 *pixel_format, char **dst, u32 *dst_size) +{ + u32 fsize, codecid; + char *data; + GF_Err e; + + codecid = 0; + if (!hint_codecid || ! *hint_codecid) { + char *ext = gf_file_ext_start(png_filename); + if (!ext) return GF_NOT_SUPPORTED; + if (!stricmp(ext, ".png")) codecid = GF_CODECID_PNG; + else if (!stricmp(ext, ".jpg") || !stricmp(ext, ".jpeg")) codecid = GF_CODECID_JPEG; + } else { + codecid = *hint_codecid; + } + + e = gf_file_load_data(png_filename, (u8 **)&data, &fsize); + if (e) return e; + + e = GF_NOT_SUPPORTED; + *dst_size = 0; + if (codecid == GF_CODECID_JPEG) { +#ifdef GPAC_HAS_JPEG + e = gf_img_jpeg_dec(data, fsize, width, height, pixel_format, NULL, dst_size, 0); + if (*dst_size) { + *dst = gf_malloc(*dst_size); + return gf_img_jpeg_dec(data, fsize, width, height, pixel_format, *dst, dst_size, 0); + } +#endif + } else if (codecid == GF_CODECID_PNG) { +#ifdef GPAC_HAS_PNG + e = gf_img_png_dec(data, fsize, width, height, pixel_format, NULL, dst_size); + if (*dst_size) { + *dst = gf_malloc(*dst_size); + return gf_img_png_dec(data, fsize, width, height, pixel_format, *dst, dst_size); + } +#endif + } + return e; +} + static Bool validator_compare_snapshots(GF_Validator *validator) { char *ref_name, *new_name; u32 ref_width, ref_height, ref_pixel_format, ref_data_size; u32 new_width, new_height, new_pixel_format, new_data_size; - char *ref_data, *new_data; + char *ref_data=NULL, *new_data=NULL; Bool result = GF_FALSE; GF_Err e; u32 i; @@ -185,16 +224,19 @@ static Bool validator_compare_snapshots(GF_Validator *validator) ref_name = validator_get_snapshot_name(validator, GF_TRUE, snap_number); new_name = validator_get_snapshot_name(validator, GF_FALSE, snap_number); - e = gf_img_file_dec(ref_name, NULL, &ref_width, &ref_height, &ref_pixel_format, &ref_data, &ref_data_size); + e = validator_file_dec(ref_name, NULL, &ref_width, &ref_height, &ref_pixel_format, &ref_data, &ref_data_size); if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Cannot decode PNG file %s\n", ref_name)); goto end; } - e = gf_img_file_dec(new_name, NULL, &new_width, &new_height, &new_pixel_format, &new_data, &new_data_size); + e = validator_file_dec(new_name, NULL, &new_width, &new_height, &new_pixel_format, &new_data, &new_data_size); if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Cannot decode PNG file %s\n", new_name)); goto end; } + if (!ref_data) ref_data_size = 0; + if (!new_data) new_data_size = 0; + if (ref_width != new_width) { GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Snapshots have different widths: %d vs %d\n", ref_width, new_width)); goto end; @@ -207,6 +249,10 @@ static Bool validator_compare_snapshots(GF_Validator *validator) GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Snapshots have different pixel formats: %d vs %d\n", ref_pixel_format, new_pixel_format)); goto end; } + if (ref_data_size != new_data_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Snapshots have different pixel formats: %d vs %d\n", ref_pixel_format, new_pixel_format)); + goto end; + } for (i = 0; i<ref_data_size; i++) { if (ref_data[i] != new_data[i]) { @@ -220,6 +266,8 @@ end: GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[Validator] PNG Comparison result: %s\n", (result?"Same":"Different"))); if (ref_name) gf_free(ref_name); if (new_name) gf_free(new_name); + if (ref_data) gf_free(ref_data); + if (new_data) gf_free(new_data); return result; } #endif @@ -230,7 +278,7 @@ static void validator_on_video_frame(void *udta, u32 time) if (validator->snapshot_next_frame) { #ifndef GPAC_DISABLE_AV_PARSERS char *snap_name = validator_create_snapshot(validator); - validator_xvs_add_snapshot_node(validator, snap_name, gf_clock_time(validator->ck)); + validator_xvs_add_snapshot_node(validator, snap_name, gf_clock_time(validator->root_odm->ck)); gf_free(snap_name); #endif validator->snapshot_next_frame = GF_FALSE; @@ -248,12 +296,10 @@ Bool validator_on_event_play(void *udta, GF_Event *event, Bool consumed_by_compo case GF_EVENT_CONNECT: if (event->connect.is_connected) { if (!validator->trace_mode) { - gf_sc_add_video_listener(validator->term->compositor, &validator->video_listener); +//deprecated gf_sc_add_video_listener(validator->compositor, &validator->video_listener); } - validator->ck = validator->term->root_scene->scene_codec ? - validator->term->root_scene->scene_codec->ck : - validator->term->root_scene->dyn_ck; + validator->root_odm = validator->compositor->root_scene->root_od; } break; case GF_EVENT_CLICK: @@ -271,7 +317,7 @@ Bool validator_on_event_play(void *udta, GF_Event *event, Bool consumed_by_compo GF_Event evt; memset(&evt, 0, sizeof(GF_Event)); evt.type = GF_EVENT_QUIT; - validator->term->compositor->video_out->on_event(validator->term->compositor->video_out->evt_cbk_hdl, &evt); + gf_sc_on_event(validator->compositor, &evt); } return GF_TRUE; } @@ -313,14 +359,14 @@ static void validator_xvs_add_event_dom(GF_Validator *validator, GF_Event *event evt_node->attributes = gf_list_new(); - GF_SAFEALLOC(att, GF_XMLAttribute); + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); if (!att) { GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[Validator] Failed to allocate event time\n")); return; } att->name = gf_strdup("time"); att->value = (char*)gf_malloc(100); - sprintf(att->value, "%f", gf_scene_get_time(validator->term->root_scene)*1000); + sprintf(att->value, "%f", gf_scene_get_time(validator->compositor->root_scene)*1000); gf_list_add(evt_node->attributes, att); switch (event->type) { @@ -332,7 +378,7 @@ static void validator_xvs_add_event_dom(GF_Validator *validator, GF_Event *event case GF_EVENT_MOUSEMOVE: case GF_EVENT_MOUSEWHEEL: if (event->type == GF_EVENT_MOUSEDOWN || event->type == GF_EVENT_MOUSEUP) { - GF_SAFEALLOC(att, GF_XMLAttribute); + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); if (!att) { GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[Validator] Failed to allocate event info\n")); return; @@ -351,7 +397,7 @@ static void validator_xvs_add_event_dom(GF_Validator *validator, GF_Event *event } gf_list_add(evt_node->attributes, att); } - GF_SAFEALLOC(att, GF_XMLAttribute); + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); if (!att) { GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[Validator] Failed to allocate event info\n")); return; @@ -361,7 +407,7 @@ static void validator_xvs_add_event_dom(GF_Validator *validator, GF_Event *event sprintf(att->value, "%d", event->mouse.x); gf_list_add(evt_node->attributes, att); - GF_SAFEALLOC(att, GF_XMLAttribute); + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); if (!att) { GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[Validator] Failed to allocate event info\n")); return; @@ -371,7 +417,7 @@ static void validator_xvs_add_event_dom(GF_Validator *validator, GF_Event *event sprintf(att->value, "%d", event->mouse.y); gf_list_add(evt_node->attributes, att); if (event->type == GF_EVENT_MOUSEWHEEL) { - GF_SAFEALLOC(att, GF_XMLAttribute); + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); if (!att) { GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[Validator] Failed to allocate event info\n")); return; @@ -382,7 +428,7 @@ static void validator_xvs_add_event_dom(GF_Validator *validator, GF_Event *event gf_list_add(evt_node->attributes, att); } if (event->mouse.key_states & GF_KEY_MOD_SHIFT) { - GF_SAFEALLOC(att, GF_XMLAttribute); + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); if (!att) { GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[Validator] Failed to allocate event info\n")); return; @@ -392,7 +438,7 @@ static void validator_xvs_add_event_dom(GF_Validator *validator, GF_Event *event gf_list_add(evt_node->attributes, att); } if (event->mouse.key_states & GF_KEY_MOD_CTRL) { - GF_SAFEALLOC(att, GF_XMLAttribute); + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); if (!att) { GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[Validator] Failed to allocate event info\n")); return; @@ -402,7 +448,7 @@ static void validator_xvs_add_event_dom(GF_Validator *validator, GF_Event *event gf_list_add(evt_node->attributes, att); } if (event->mouse.key_states & GF_KEY_MOD_ALT) { - GF_SAFEALLOC(att, GF_XMLAttribute); + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); if (!att) { GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[Validator] Failed to allocate event info\n")); return; @@ -416,7 +462,7 @@ static void validator_xvs_add_event_dom(GF_Validator *validator, GF_Event *event case GF_EVENT_KEYUP: case GF_EVENT_KEYDOWN: case GF_EVENT_LONGKEYPRESS: - GF_SAFEALLOC(att, GF_XMLAttribute); + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); if (!att) { GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[Validator] Failed to allocate event info\n")); return; @@ -427,7 +473,7 @@ static void validator_xvs_add_event_dom(GF_Validator *validator, GF_Event *event #endif gf_list_add(evt_node->attributes, att); if (event->key.flags & GF_KEY_MOD_SHIFT) { - GF_SAFEALLOC(att, GF_XMLAttribute); + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); if (!att) { GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[Validator] Failed to allocate event info\n")); return; @@ -437,7 +483,7 @@ static void validator_xvs_add_event_dom(GF_Validator *validator, GF_Event *event gf_list_add(evt_node->attributes, att); } if (event->key.flags & GF_KEY_MOD_CTRL) { - GF_SAFEALLOC(att, GF_XMLAttribute); + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); if (!att) { GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[Validator] Failed to allocate event info\n")); return; @@ -447,7 +493,7 @@ static void validator_xvs_add_event_dom(GF_Validator *validator, GF_Event *event gf_list_add(evt_node->attributes, att); } if (event->key.flags & GF_KEY_MOD_ALT) { - GF_SAFEALLOC(att, GF_XMLAttribute); + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); if (!att) { GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[Validator] Failed to allocate event info\n")); return; @@ -458,7 +504,7 @@ static void validator_xvs_add_event_dom(GF_Validator *validator, GF_Event *event } break; case GF_EVENT_TEXTINPUT: - GF_SAFEALLOC(att, GF_XMLAttribute); + att = (GF_XMLAttribute *) gf_malloc(sizeof(GF_XMLAttribute)); if (!att) { GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[Validator] Failed to allocate event info\n")); return; @@ -489,9 +535,9 @@ Bool validator_on_event_record(void *udta, GF_Event *event, Bool consumed_by_com case GF_EVENT_CONNECT: if (event->connect.is_connected) { if (!validator->trace_mode) { - gf_sc_add_video_listener(validator->term->compositor, &validator->video_listener); +//deprecated gf_sc_add_video_listener(validator->compositor, &validator->video_listener); } - validator->ck = validator->term->root_scene->scene_codec ? validator->term->root_scene->scene_codec->ck : validator->term->root_scene->dyn_ck; + validator->root_odm = validator->compositor->root_scene->root_od; } break; case GF_EVENT_KEYDOWN: @@ -515,28 +561,28 @@ Bool validator_on_event_record(void *udta, GF_Event *event, Bool consumed_by_com if (event->key.key_code == GF_KEY_INSERT) { #ifndef GPAC_DISABLE_AV_PARSERS char *snap_name = validator_create_snapshot(validator); - validator_xvs_add_snapshot_node(validator, snap_name, gf_clock_time(validator->ck)); + validator_xvs_add_snapshot_node(validator, snap_name, gf_clock_time(validator->root_odm->ck)); gf_free(snap_name); #endif } else if (event->key.key_code == GF_KEY_END) { GF_Event evt; memset(&evt, 0, sizeof(GF_Event)); evt.type = GF_EVENT_QUIT; - validator->term->compositor->video_out->on_event(validator->term->compositor->video_out->evt_cbk_hdl, &evt); + gf_sc_on_event(validator->compositor, &evt); } else if (event->key.key_code == GF_KEY_F1) { validator->snapshot_next_frame = GF_TRUE; } } else if (event->key.key_code == GF_KEY_PAGEDOWN) { rec_event = GF_FALSE; validator_xvs_close(validator); - gf_term_disconnect(validator->term); - gf_sc_remove_video_listener(validator->term->compositor, &validator->video_listener); + gf_sc_disconnect(validator->compositor); +//deprecated gf_sc_remove_video_listener(validator->compositor, &validator->video_listener); validator_xvs_next(validator, GF_FALSE); } else if (event->key.key_code == GF_KEY_PAGEUP) { rec_event = GF_FALSE; validator_xvs_close(validator); - gf_term_disconnect(validator->term); - gf_sc_remove_video_listener(validator->term->compositor, &validator->video_listener); + gf_sc_disconnect(validator->compositor); +//deprecated gf_sc_remove_video_listener(validator->compositor, &validator->video_listener); validator_xvs_next(validator, GF_TRUE); } else if (event->key.key_code == GF_KEY_CONTROL) { rec_event = GF_FALSE; @@ -553,7 +599,6 @@ static void validator_xvl_open(GF_Validator *validator) { GF_Err e; u32 att_index; - GF_XMLAttribute *att; validator->xvl_parser = gf_xml_dom_new(); e = gf_xml_dom_parse(validator->xvl_parser, validator->xvl_filename, NULL, NULL); if (e != GF_OK) { @@ -569,7 +614,7 @@ static void validator_xvl_open(GF_Validator *validator) } att_index = 0; while (1) { - att = (GF_XMLAttribute*)gf_list_get(validator->xvl_node->attributes, att_index); + GF_XMLAttribute *att = (GF_XMLAttribute*)gf_list_get(validator->xvl_node->attributes, att_index); if (!att) break; if (!strcmp(att->name, "content-base")) { validator->test_base = gf_strdup(att->value); @@ -588,12 +633,12 @@ static void validator_xvl_close(GF_Validator *validator) char result_filename[GF_MAX_PATH]; char *dot; xvl_content = gf_xml_dom_serialize(validator->xvl_node, GF_FALSE); - dot = strrchr(validator->xvl_filename, '.'); + dot = gf_file_ext_start(validator->xvl_filename); dot[0] = 0; sprintf(result_filename, "%s-result.xml", validator->xvl_filename); dot[0] = '.'; xvl_fp = gf_fopen(result_filename, "wt"); - gf_fwrite(xvl_content, strlen(xvl_content), 1, xvl_fp); + gf_fwrite(xvl_content, strlen(xvl_content), xvl_fp); gf_fclose(xvl_fp); gf_free(xvl_content); } @@ -726,12 +771,11 @@ static void validator_xvs_close(GF_Validator *validator) if (validator->is_recording) { FILE *xvs_fp; char *xvs_content; - char filename[100]; - GF_XMLAttribute *att; GF_XMLAttribute *att_file = NULL; u32 att_index = 0; if (!validator->trace_mode) { + GF_XMLAttribute *att; while (1) { att = (GF_XMLAttribute*)gf_list_get(validator->xvs_node->attributes, att_index); if (!att) { @@ -755,6 +799,7 @@ static void validator_xvs_close(GF_Validator *validator) if (att->value) gf_free(att->value); } if (validator->test_base) { + char filename[100]; sprintf(filename, "%s%c%s", validator->test_base, GF_PATH_SEPARATOR, validator->test_filename); att->value = gf_strdup(filename); } else { @@ -763,7 +808,7 @@ static void validator_xvs_close(GF_Validator *validator) } xvs_content = gf_xml_dom_serialize(validator->xvs_node, GF_FALSE); xvs_fp = gf_fopen(validator->xvs_filename, "wt"); - gf_fwrite(xvs_content, strlen(xvs_content), 1, xvs_fp); + gf_fwrite(xvs_content, strlen(xvs_content), xvs_fp); gf_fclose(xvs_fp); gf_free(xvs_content); if (validator->owns_root) @@ -803,28 +848,27 @@ static void validator_xvs_close(GF_Validator *validator) validator->xvs_node_in_xvl = NULL; validator->xvs_filename = NULL; validator->test_filename = NULL; - validator->ck = NULL; + validator->root_odm = NULL; validator->xvs_event_index = 0; validator->snapshot_number = 0; } static void validator_test_open(GF_Validator *validator) { - char filename[100]; - if (!validator->trace_mode) { + char filename[100]; if (validator->test_base) sprintf(filename, "%s%c%s", validator->test_base, GF_PATH_SEPARATOR, validator->test_filename); else sprintf(filename, "%s", validator->test_filename); - gf_sc_add_video_listener(validator->term->compositor, &validator->video_listener); +//deprecated gf_sc_add_video_listener(validator->compositor, &validator->video_listener); if (validator->is_recording) validator->snapshot_next_frame = GF_TRUE; - gf_term_connect(validator->term, filename); + gf_sc_connect_from_time_ex(validator->compositor, filename, 0, 0, 0, NULL); } -// validator->ck = validator->term->root_scene->scene_codec ? validator->term->root_scene->scene_codec->ck : validator->term->root_scene->dyn_ck; +// validator->ck = validator->compositor->root_scene->scene_codec ? validator->compositor->root_scene->scene_codec->ck : validator->compositor->root_scene->dyn_ck; } static Bool validator_xvs_next(GF_Validator *validator, Bool reverse) @@ -854,14 +898,16 @@ static Bool validator_xvs_next(GF_Validator *validator, Bool reverse) static Bool validator_load_event(GF_Validator *validator) { GF_XMLNode *event_node; - GF_XMLAttribute *att; u32 att_index; memset(&validator->next_event, 0, sizeof(GF_Event)); validator->evt_loaded = GF_FALSE; validator->next_event_snapshot = GF_FALSE; - if (!validator->xvs_node) return GF_FALSE; + if (!validator->xvs_node) { + validator->compositor->validator_mode = GF_FALSE; + return GF_FALSE; + } while (1) { event_node = (GF_XMLNode*)gf_list_get(validator->xvs_node->content, validator->xvs_event_index); @@ -889,7 +935,7 @@ static Bool validator_load_event(GF_Validator *validator) att_index = 0; while (1) { - att = (GF_XMLAttribute*)gf_list_get(event_node->attributes, att_index); + GF_XMLAttribute *att = (GF_XMLAttribute*)gf_list_get(event_node->attributes, att_index); if (!att) break; if (!strcmp(att->name, "time")) { validator->next_time = atoi(att->value); @@ -923,10 +969,11 @@ static Bool validator_load_event(GF_Validator *validator) att_index++; } validator->evt_loaded = GF_TRUE; + validator->compositor->sys_frames_pending = GF_TRUE; return GF_TRUE; } -static Bool validator_process(GF_TermExt *termext, u32 action, void *param) +static Bool validator_process(GF_CompositorExt *termext, u32 action, void *param) { const char *opt; GF_Validator *validator = (GF_Validator*)termext->udta; @@ -934,24 +981,19 @@ static Bool validator_process(GF_TermExt *termext, u32 action, void *param) switch (action) { /* Upon starting of the terminal, we parse (possibly an XVL file), an XVS file, and start the first test sequence */ - case GF_TERM_EXT_START: - validator->term = (GF_Terminal *) param; - - /* if the validator is loaded, we switch off anti-aliasing for image comparison and we put a low framerate, - but we store the previous value to restore it upon termination of the validator */ - opt = (char *)gf_modules_get_option((GF_BaseInterface*)termext, "Compositor", "FrameRate"); - if (opt) validator->prev_fps = gf_strdup(opt); - opt = (char *)gf_modules_get_option((GF_BaseInterface*)termext, "Compositor", "AntiAlias"); - if (opt) validator->prev_alias = gf_strdup(opt); + case GF_COMPOSITOR_EXT_START: + validator->compositor = (GF_Compositor *) param; /* Check if the validator should be loaded and in which mode */ - opt = gf_modules_get_option((GF_BaseInterface*)termext, "Validator", "Mode"); + opt = gf_opts_get_key("Validator", "Mode"); if (!opt) { GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("Validator missing configuration, stopping.\n")); return GF_FALSE; - } else if (!strcmp(opt, "Play")) { + } else if (!strcmp(opt, "Play") ) { GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("Validator starting in playback mode.\n")); validator->is_recording = GF_FALSE; + //this will indicate to the compositor to increment scene time even though no new changes + validator->compositor->validator_mode = GF_TRUE; } else if (!strcmp(opt, "Record")) { GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("Validator starting in recording mode.\n")); validator->is_recording = GF_TRUE; @@ -963,13 +1005,15 @@ static Bool validator_process(GF_TermExt *termext, u32 action, void *param) return GF_FALSE; } + gf_opts_set_key("Validator", "Mode", "Disable"); + /* initializes the validator and starts */ validator->xvs_filename = NULL; - validator->xvl_filename = (char *)gf_modules_get_option((GF_BaseInterface*)termext, "Validator", "XVL"); + validator->xvl_filename = (char *)gf_opts_get_key("Validator", "XVL"); if (!validator->xvl_filename) { - validator->xvs_filename = (char *)gf_modules_get_option((GF_BaseInterface*)termext, "Validator", "XVS"); + validator->xvs_filename = (char *)gf_opts_get_key("Validator", "XVS"); if (!validator->xvs_filename) { - validator->xvs_filename = (char *)gf_modules_get_option((GF_BaseInterface*)termext, "Validator", "Trace"); + validator->xvs_filename = (char *)gf_opts_get_key("Validator", "Trace"); if (!validator->xvs_filename) { GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("Validator configuration without input, stopping.\n")); return GF_FALSE; @@ -984,11 +1028,6 @@ static Bool validator_process(GF_TermExt *termext, u32 action, void *param) GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("Validator using scenario playlist: %s\n", validator->xvl_filename)); } - /* since we changed parameters of the compositor, we need to trigger a reconfiguration */ -// gf_modules_set_option((GF_BaseInterface*)termext, "Compositor", "FrameRate", "5.0"); -// gf_modules_set_option((GF_BaseInterface*)termext, "Compositor", "AntiAlias", "None"); -// gf_term_set_option(validator->term, GF_OPT_RELOAD_CONFIG, 1); - /* TODO: if start returns 0, the module is not loaded, so the above init (filter registration) is not removed, should probably return 1 all the time, to make sure stop is called */ if (validator->xvl_filename) { @@ -1018,11 +1057,11 @@ static Bool validator_process(GF_TermExt *termext, u32 action, void *param) validator->evt_filter.udta = validator; if (!validator->is_recording) { validator->evt_filter.on_event = validator_on_event_play; - termext->caps |= GF_TERM_EXTENSION_NOT_THREADED; + termext->caps |= GF_COMPOSITOR_EXTENSION_NOT_THREADED; } else { validator->evt_filter.on_event = validator_on_event_record; } - gf_term_add_event_filter(validator->term, &validator->evt_filter); + gf_filter_add_event_listener(validator->compositor->filter, &validator->evt_filter); validator->video_listener.udta = validator; validator->video_listener.on_video_frame = validator_on_video_frame; validator->video_listener.on_video_reconfig = validator_on_video_reconfig; @@ -1036,11 +1075,11 @@ static Bool validator_process(GF_TermExt *termext, u32 action, void *param) /* when the terminal stops, we close the XVS parser and XVL parser if any, restore the config, and free all validator data (the validator will be destroyed when the module is unloaded) Note: we don't need to disconnect the terminal since it's already stopping */ - case GF_TERM_EXT_STOP: - gf_term_remove_event_filter(validator->term, &validator->evt_filter); + case GF_COMPOSITOR_EXT_STOP: + gf_filter_remove_event_listener(validator->compositor->filter, &validator->evt_filter); validator_xvs_close(validator); validator_xvl_close(validator); - validator->term = NULL; + validator->compositor = NULL; if (validator->test_base) { gf_free(validator->test_base); validator->test_base = NULL; @@ -1048,30 +1087,20 @@ static Bool validator_process(GF_TermExt *termext, u32 action, void *param) /*auto-disable the recording by default*/ if (!validator->trace_mode) { if (validator->is_recording ) { - gf_modules_set_option((GF_BaseInterface*)termext, "Validator", "Mode", "Play"); + gf_opts_set_key("Validator", "Mode", "Play"); } else { - gf_modules_set_option((GF_BaseInterface*)termext, "Validator", "Mode", "Disable"); + gf_opts_set_key("Validator", "Mode", "Disable"); } } GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("Stopping validator\n")); - if (validator->prev_fps) { - gf_modules_set_option((GF_BaseInterface*)termext, "Compositor", "FrameRate", validator->prev_fps); - gf_free(validator->prev_fps); - validator->prev_fps = NULL; - } - if (validator->prev_alias) { - gf_modules_set_option((GF_BaseInterface*)termext, "Compositor", "AntiAlias", validator->prev_alias); - gf_free(validator->prev_alias); - validator->prev_alias = NULL; - } break; /* When called in the main loop of the terminal, we don't do anything in the recording mode. In the playing/validating mode, we need to check if an event needs to be dispatched or if snapshots need to be made, until there is no more event, in which case we trigger either the load of the next XVS or the quit */ - case GF_TERM_EXT_PROCESS: + case GF_COMPOSITOR_EXT_PROCESS: /* if the time is right, dispatch the event and load the next one */ - while (!validator->is_recording && validator->evt_loaded && validator->ck && (validator->next_time <= gf_clock_time(validator->ck) )) { + while (!validator->is_recording && validator->evt_loaded && validator->root_odm && validator->root_odm->ck && (validator->next_time <= gf_clock_time(validator->root_odm->ck) )) { Bool has_more_events; //u32 diff = gf_clock_time(validator->ck) - validator->next_time; //GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[Validator] Time diff: evt_time=%d clock_time = %d, diff=%d\n", validator->next_time, gf_clock_time(validator->ck), diff)); @@ -1085,20 +1114,20 @@ static Bool validator_process(GF_TermExt *termext, u32 action, void *param) validator->next_event_snapshot = GF_FALSE; #endif } else { - validator->term->compositor->video_out->on_event(validator->term->compositor->video_out->evt_cbk_hdl, &validator->next_event); + gf_sc_on_event(validator->compositor, &validator->next_event); } has_more_events = validator_load_event(validator); if (!has_more_events) { validator_xvs_close(validator); if (! validator->trace_mode) { - gf_term_disconnect(validator->term); - gf_sc_remove_video_listener(validator->term->compositor, &validator->video_listener); + gf_sc_disconnect(validator->compositor); +//deprecated gf_sc_remove_video_listener(validator->compositor, &validator->video_listener); validator_xvs_next(validator, GF_FALSE); if (!validator->xvs_node) { GF_Event evt; memset(&evt, 0, sizeof(GF_Event)); evt.type = GF_EVENT_QUIT; - validator->term->compositor->video_out->on_event(validator->term->compositor->video_out->evt_cbk_hdl, &evt); + gf_sc_on_event(validator->compositor, &evt); } else { if (!validator->is_recording) { validator_load_event(validator); @@ -1113,15 +1142,19 @@ static Bool validator_process(GF_TermExt *termext, u32 action, void *param) } -GF_TermExt *validator_new() +GF_CompositorExt *validator_new() { - GF_TermExt *dr; + GF_CompositorExt *dr; GF_Validator *validator; - dr = (GF_TermExt*)gf_malloc(sizeof(GF_TermExt)); - memset(dr, 0, sizeof(GF_TermExt)); - GF_REGISTER_MODULE_INTERFACE(dr, GF_TERM_EXT_INTERFACE, "GPAC Test Validator", "gpac distribution"); + dr = (GF_CompositorExt*)gf_malloc(sizeof(GF_CompositorExt)); + memset(dr, 0, sizeof(GF_CompositorExt)); + GF_REGISTER_MODULE_INTERFACE(dr, GF_COMPOSITOR_EXT_INTERFACE, "GPAC Test Validator", "gpac distribution"); GF_SAFEALLOC(validator, GF_Validator); + if (!validator) { + gf_free(dr); + return NULL; + } dr->process = validator_process; dr->udta = validator; return dr; @@ -1130,10 +1163,8 @@ GF_TermExt *validator_new() void validator_delete(GF_BaseInterface *ifce) { - GF_TermExt *dr = (GF_TermExt *) ifce; + GF_CompositorExt *dr = (GF_CompositorExt *) ifce; GF_Validator *validator = (GF_Validator*)dr->udta; - if (validator->prev_fps) gf_free(validator->prev_fps); - if (validator->prev_alias) gf_free(validator->prev_alias); gf_free(validator); gf_free(dr); } @@ -1142,7 +1173,7 @@ GPAC_MODULE_EXPORT const u32 *QueryInterfaces() { static u32 si [] = { - GF_TERM_EXT_INTERFACE, + GF_COMPOSITOR_EXT_INTERFACE, 0 }; return si; @@ -1151,7 +1182,7 @@ const u32 *QueryInterfaces() GPAC_MODULE_EXPORT GF_BaseInterface *LoadInterface(u32 InterfaceType) { - if (InterfaceType == GF_TERM_EXT_INTERFACE) return (GF_BaseInterface *)validator_new(); + if (InterfaceType == GF_COMPOSITOR_EXT_INTERFACE) return (GF_BaseInterface *)validator_new(); return NULL; } @@ -1159,7 +1190,7 @@ GPAC_MODULE_EXPORT void ShutdownInterface(GF_BaseInterface *ifce) { switch (ifce->InterfaceType) { - case GF_TERM_EXT_INTERFACE: + case GF_COMPOSITOR_EXT_INTERFACE: validator_delete(ifce); break; } diff --git a/modules/vtb_decode/Makefile b/modules/vtb_decode/Makefile deleted file mode 100644 index e41ba7b..0000000 --- a/modules/vtb_decode/Makefile +++ /dev/null @@ -1,43 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/vtb_decode - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS=vtb_decode.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_vtb_decode$(DYN_LIB_SUFFIX) - - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) -L../../bin/gcc -lgpac $(LDFLAGS) -framework CoreFoundation -framework CoreVideo -framework CoreMedia -framework VideoToolbox - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/vtb_decode/vtb_decode.c b/modules/vtb_decode/vtb_decode.c deleted file mode 100644 index fdaf152..0000000 --- a/modules/vtb_decode/vtb_decode.c +++ /dev/null @@ -1,1341 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / codec pack module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - - -#include <stdint.h> - -#define Picture QuickdrawPicture -#include <VideoToolbox/VideoToolbox.h> -#undef Picture - -#ifndef kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder -# define kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder CFSTR("RequireHardwareAcceleratedVideoDecoder") -#endif - -//do not include math.h we would have a conflict with Fixed ... we're lucky we don't need maths routines here -#define _GF_MATH_H_ - -#include <gpac/modules/codec.h> -#include <gpac/internal/media_dev.h> -#include <gpac/constants.h> - -#include "../../src/compositor/gl_inc.h" - -#ifndef GPAC_DISABLE_AV_PARSERS - -typedef struct -{ - Bool is_hardware; - u32 width, height; - u32 pixel_ar, pix_fmt; - u32 out_size; - Bool raw_frame_dispatch; - - GF_ESD *esd; - GF_Err last_error; - - int vtb_type; - VTDecompressionSessionRef vtb_session; - CMFormatDescriptionRef fmt_desc; - CVPixelBufferRef frame; - u32 cts; - - u8 chroma_format, luma_bit_depth, chroma_bit_depth; - Bool frame_size_changed; - - u32 decoded_frames_pending; - Bool reconfig_needed; - - //MPEG-1/2 specific - Bool init_mpeg12; - - //MPEG-4 specific - Bool skip_mpeg4_vosh; - char *vosh; - u32 vosh_size; - - //NAL-based specific - Bool is_avc; - Bool is_annex_b; - char *cached_annex_b; - u32 cached_annex_b_size; - u32 nalu_size_length; - GF_List *SPSs, *PPSs; - s32 active_sps, active_pps; - AVCState avc; - Bool check_h264_isma; - - //openGL output -#ifdef GPAC_IPHONE - Bool use_gl_textures; - CVOpenGLESTextureCacheRef cache_texture; -#endif - void *gl_context; -} VTBDec; - - -static void VTBDec_on_frame(void *opaque, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags flags, CVImageBufferRef image, CMTime pts, CMTime duration) -{ - VTBDec *ctx = (VTBDec *)opaque; - if (ctx->frame) { - CVPixelBufferRelease(ctx->frame); - ctx->frame = NULL; - } - if (status != kCVReturnSuccess) { - ctx->last_error = GF_NON_COMPLIANT_BITSTREAM; - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Decode error - status %d\n", status)); - } - - if (!image) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[VTB] No output buffer - status %d\n", status)); - return; - } - ctx->frame = CVPixelBufferRetain(image); -} - -static CFDictionaryRef VTBDec_CreateBufferAttributes(VTBDec *ctx, OSType pix_fmt) -{ - CFMutableDictionaryRef buffer_attributes; - CFMutableDictionaryRef surf_props; - CFNumberRef w; - CFNumberRef h; - CFNumberRef pixel_fmt; - - w = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &ctx->width); - h = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &ctx->height); - pixel_fmt = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pix_fmt); - - buffer_attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - surf_props = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - - CFDictionarySetValue(buffer_attributes, kCVPixelBufferWidthKey, w); - CFRelease(w); - CFDictionarySetValue(buffer_attributes, kCVPixelBufferHeightKey, h); - CFRelease(h); - CFDictionarySetValue(buffer_attributes, kCVPixelBufferPixelFormatTypeKey, pixel_fmt); - CFRelease(pixel_fmt); - -#ifdef GPAC_IPHONE - if (ctx->use_gl_textures) - CFDictionarySetValue(buffer_attributes, kCVPixelBufferOpenGLESCompatibilityKey, kCFBooleanTrue); -#endif - - CFDictionarySetValue(buffer_attributes, kCVPixelBufferIOSurfacePropertiesKey, surf_props); - CFRelease(surf_props); - - return buffer_attributes; -} - -static GF_Err VTBDec_InitDecoder(VTBDec *ctx) -{ - CFMutableDictionaryRef dec_dsi, dec_type; - CFMutableDictionaryRef dsi; - VTDecompressionOutputCallbackRecord cbacks; - CFDictionaryRef buffer_attribs; - OSStatus status; - OSType kColorSpace; - - CFDataRef data = NULL; - char *dsi_data=NULL; - u32 dsi_data_size=0; - - dec_dsi = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - -// kColorSpace = kCVPixelFormatType_420YpCbCr8Planar; -// ctx->pix_fmt = GF_PIXEL_YV12; - - kColorSpace = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; - ctx->pix_fmt = GF_PIXEL_NV12; - - switch (ctx->esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_VIDEO_AVC : - if (gf_list_count(ctx->SPSs) && gf_list_count(ctx->PPSs)) { - s32 idx; - u32 i; - GF_AVCConfig *cfg; - GF_AVCConfigSlot *sps = NULL; - GF_AVCConfigSlot *pps = NULL; - - for (i=0; i<gf_list_count(ctx->SPSs); i++) { - sps = gf_list_get(ctx->SPSs, i); - if (ctx->active_sps<0) ctx->active_sps = sps->id; - - if (sps->id==ctx->active_sps) break; - sps = NULL; - } - if (!sps) return GF_NON_COMPLIANT_BITSTREAM; - for (i=0; i<gf_list_count(ctx->PPSs); i++) { - pps = gf_list_get(ctx->PPSs, i); - if (ctx->active_pps<0) ctx->active_pps = pps->id; - - if (pps->id==ctx->active_pps) break; - pps = NULL; - } - if (!pps) return GF_NON_COMPLIANT_BITSTREAM; - ctx->reconfig_needed = GF_FALSE; - - ctx->vtb_type = kCMVideoCodecType_H264; - - idx = ctx->active_sps; - ctx->width = ctx->avc.sps[idx].width; - ctx->height = ctx->avc.sps[idx].height; - if (ctx->avc.sps[idx].vui.par_num && ctx->avc.sps[idx].vui.par_den) { - ctx->pixel_ar = ctx->avc.sps[idx].vui.par_num; - ctx->pixel_ar <<= 16; - ctx->pixel_ar |= ctx->avc.sps[idx].vui.par_den; - } - ctx->chroma_format = ctx->avc.sps[idx].chroma_format; - ctx->luma_bit_depth = 8 + ctx->avc.sps[idx].luma_bit_depth_m8; - ctx->chroma_bit_depth = 8 + ctx->avc.sps[idx].chroma_bit_depth_m8; - - switch (ctx->chroma_format) { - case 2: - //422 decoding doesn't seem supported ... - if (ctx->luma_bit_depth>8) { - kColorSpace = kCVPixelFormatType_422YpCbCr10; - ctx->pix_fmt = GF_PIXEL_YUV422_10; - } else { - kColorSpace = kCVPixelFormatType_422YpCbCr8; - ctx->pix_fmt = GF_PIXEL_YUV422; - } - break; - case 3: - if (ctx->luma_bit_depth>8) { - kColorSpace = kCVPixelFormatType_444YpCbCr10; - ctx->pix_fmt = GF_PIXEL_YUV444_10; - } else { - kColorSpace = kCVPixelFormatType_444YpCbCr8; - ctx->pix_fmt = GF_PIXEL_YUV444; - } - break; - default: - if (ctx->luma_bit_depth>8) { - kColorSpace = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; - ctx->pix_fmt = GF_PIXEL_YV12_10; - } - break; - } - //always rewrite with cirrent sps and pps - cfg = gf_odf_avc_cfg_new(); - cfg->configurationVersion = 1; - cfg->profile_compatibility = ctx->avc.sps[idx].prof_compat; - cfg->AVCProfileIndication = ctx->avc.sps[idx].profile_idc; - cfg->AVCLevelIndication = ctx->avc.sps[idx].level_idc; - cfg->chroma_format = ctx->avc.sps[idx].chroma_format; - cfg->luma_bit_depth = 8 + ctx->avc.sps[idx].luma_bit_depth_m8; - cfg->chroma_bit_depth = 8 + ctx->avc.sps[idx].chroma_bit_depth_m8; - cfg->nal_unit_size = 4; - - gf_list_add(cfg->sequenceParameterSets, sps); - gf_list_add(cfg->pictureParameterSets, pps); - - gf_odf_avc_cfg_write(cfg, &dsi_data, &dsi_data_size); - gf_list_reset(cfg->sequenceParameterSets); - gf_list_reset(cfg->pictureParameterSets); - gf_odf_avc_cfg_del((cfg)); - - dsi = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)dsi_data, dsi_data_size); - if (data) { - CFDictionarySetValue(dsi, CFSTR("avcC"), data); - CFDictionarySetValue(dec_dsi, kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms, dsi); - CFRelease(data); - } - CFRelease(dsi); - - gf_free(dsi_data); - } - break; - case GPAC_OTI_VIDEO_MPEG2_SIMPLE: - case GPAC_OTI_VIDEO_MPEG2_MAIN: - case GPAC_OTI_VIDEO_MPEG2_SNR: - case GPAC_OTI_VIDEO_MPEG2_SPATIAL: - case GPAC_OTI_VIDEO_MPEG2_HIGH: - case GPAC_OTI_VIDEO_MPEG2_422: - ctx->vtb_type = kCMVideoCodecType_MPEG2Video; - if (!ctx->width || !ctx->height) { - ctx->init_mpeg12 = GF_TRUE; - return GF_OK; - } - ctx->init_mpeg12 = GF_FALSE; - break; - - case GPAC_OTI_VIDEO_MPEG1: - ctx->vtb_type = kCMVideoCodecType_MPEG1Video; - if (!ctx->width || !ctx->height) { - ctx->init_mpeg12 = GF_TRUE; - return GF_OK; - } - ctx->init_mpeg12 = GF_FALSE; - break; - case GPAC_OTI_VIDEO_MPEG4_PART2 : - { - Bool reset_dsi = GF_FALSE; - ctx->vtb_type = kCMVideoCodecType_MPEG4Video; - if (!ctx->esd->decoderConfig->decoderSpecificInfo) { - ctx->esd->decoderConfig->decoderSpecificInfo = (GF_DefaultDescriptor *) gf_odf_desc_new(GF_ODF_DSI_TAG); - } - - if (!ctx->esd->decoderConfig->decoderSpecificInfo->data) { - reset_dsi = GF_TRUE; - ctx->esd->decoderConfig->decoderSpecificInfo->data = ctx->vosh; - ctx->esd->decoderConfig->decoderSpecificInfo->dataLength = ctx->vosh_size; - } - - if (ctx->esd->decoderConfig->decoderSpecificInfo->data) { - GF_M4VDecSpecInfo vcfg; - GF_BitStream *bs; - - gf_m4v_get_config(ctx->esd->decoderConfig->decoderSpecificInfo->data, ctx->esd->decoderConfig->decoderSpecificInfo->dataLength, &vcfg); - ctx->width = vcfg.width; - ctx->height = vcfg.height; - if (ctx->esd->slConfig) { - ctx->esd->slConfig->predefined = 2; - } - bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - gf_bs_write_u32(bs, 0); - gf_odf_desc_write_bs((GF_Descriptor *) ctx->esd, bs); - gf_bs_get_content(bs, &dsi_data, &dsi_data_size); - gf_bs_del(bs); - - dsi = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - data = CFDataCreate(kCFAllocatorDefault, (const UInt8*) dsi_data, dsi_data_size); - gf_free(dsi_data); - - if (data) { - CFDictionarySetValue(dsi, CFSTR("esds"), data); - CFDictionarySetValue(dec_dsi, kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms, dsi); - CFRelease(data); - } - CFRelease(dsi); - - if (reset_dsi) { - ctx->esd->decoderConfig->decoderSpecificInfo->data = NULL; - ctx->esd->decoderConfig->decoderSpecificInfo->dataLength = 0; - } - ctx->skip_mpeg4_vosh = GF_FALSE; - } else { - ctx->skip_mpeg4_vosh = GF_TRUE; - return GF_OK; - } - break; - } - case GPAC_OTI_MEDIA_GENERIC: - if (ctx->esd->decoderConfig->decoderSpecificInfo && ctx->esd->decoderConfig->decoderSpecificInfo->dataLength) { - char *dsi = ctx->esd->decoderConfig->decoderSpecificInfo->data; - if (ctx->esd->decoderConfig->decoderSpecificInfo->dataLength<8) return GF_NON_COMPLIANT_BITSTREAM; - if (strnicmp(dsi, "s263", 4)) return GF_NOT_SUPPORTED; - - ctx->width = ((u8) dsi[4]); ctx->width<<=8; ctx->width |= ((u8) dsi[5]); - ctx->height = ((u8) dsi[6]); ctx->height<<=8; ctx->height |= ((u8) dsi[7]); - ctx->vtb_type = kCMVideoCodecType_H263; - } - break; - - default : - return GF_NOT_SUPPORTED; - } - - if (! ctx->width || !ctx->height) return GF_NOT_SUPPORTED; - - status = CMVideoFormatDescriptionCreate(kCFAllocatorDefault, ctx->vtb_type, ctx->width, ctx->height, dec_dsi, &ctx->fmt_desc); - - if (!ctx->fmt_desc) { - if (dec_dsi) CFRelease(dec_dsi); - return GF_NON_COMPLIANT_BITSTREAM; - } - buffer_attribs = VTBDec_CreateBufferAttributes(ctx, kColorSpace); - - cbacks.decompressionOutputCallback = VTBDec_on_frame; - cbacks.decompressionOutputRefCon = ctx; - - dec_type = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - CFDictionarySetValue(dec_type, kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder, kCFBooleanTrue); - ctx->is_hardware = GF_TRUE; - - status = VTDecompressionSessionCreate(NULL, ctx->fmt_desc, dec_type, buffer_attribs, &cbacks, &ctx->vtb_session); - //if HW decoder not available, try soft one - if (status) { - status = VTDecompressionSessionCreate(NULL, ctx->fmt_desc, NULL, buffer_attribs, &cbacks, &ctx->vtb_session); - ctx->is_hardware = GF_FALSE; - } - - if (dec_dsi) - CFRelease(dec_dsi); - if (dec_type) - CFRelease(dec_type); - if (buffer_attribs) - CFRelease(buffer_attribs); - - switch (status) { - case kVTVideoDecoderNotAvailableNowErr: - case kVTVideoDecoderUnsupportedDataFormatErr: - return GF_NOT_SUPPORTED; - case kVTVideoDecoderMalfunctionErr: - return GF_IO_ERR; - case kVTVideoDecoderBadDataErr : - return GF_BAD_PARAM; - - case kVTPixelTransferNotSupportedErr: - case kVTCouldNotFindVideoDecoderErr: - return GF_NOT_SUPPORTED; - case 0: - break; - default: - return GF_SERVICE_ERROR; - } - - //good to go ! - if (ctx->pix_fmt == GF_PIXEL_YUV422) { - ctx->out_size = ctx->width*ctx->height*2; - } else if (ctx->pix_fmt == GF_PIXEL_YUV444) { - ctx->out_size = ctx->width*ctx->height*3; - } else { - // (ctx->pix_fmt == GF_PIXEL_YV12) - ctx->out_size = ctx->width*ctx->height*3/2; - } - if (ctx->luma_bit_depth>8) { - ctx->out_size *= 2; - } - ctx->frame_size_changed = GF_TRUE; - return GF_OK; -} - -static void VTB_RegisterParameterSet(VTBDec *ctx, char *data, u32 size, Bool is_sps) -{ - Bool add = GF_TRUE; - u32 i, count; - s32 ps_id; - GF_List *dest = is_sps ? ctx->SPSs : ctx->PPSs; - - if (is_sps) { - ps_id = gf_media_avc_read_sps(data, size, &ctx->avc, 0, NULL); - if (ps_id<0) return; - } else { - ps_id = gf_media_avc_read_pps(data, size, &ctx->avc); - if (ps_id<0) return; - } - - count = gf_list_count(dest); - for (i=0; i<count; i++) { - GF_AVCConfigSlot *a_slc = gf_list_get(dest, i); - if (a_slc->id != ps_id) continue; - //not same size or different content but same ID, remove old xPS - if ((a_slc->size != size) || memcmp(a_slc->data, data, size) ) { - gf_free(a_slc->data); - gf_free(a_slc); - gf_list_rem(dest, i); - break; - } else { - add = GF_FALSE; - } - break; - } - if (add) { - GF_AVCConfigSlot *slc; - GF_SAFEALLOC(slc, GF_AVCConfigSlot); - slc->data = gf_malloc(size); - memcpy(slc->data, data, size); - slc->size = size; - slc->id = ps_id; - gf_list_add(dest, slc); - - //force re-activation of sps/pps - if (is_sps) ctx->active_sps = -1; - else ctx->active_pps = -1; - } -} - -static GF_Err VTBDec_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ - GF_Err e; - VTBDec *ctx = (VTBDec *)ifcg->privateStack; - ctx->esd = esd; - - //check AVC config - if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_AVC) { - ctx->SPSs = gf_list_new(); - ctx->PPSs = gf_list_new(); - ctx->is_avc = GF_TRUE; - ctx->check_h264_isma = GF_TRUE; - - ctx->avc.sps_active_idx = -1; - ctx->active_sps = ctx->active_pps = -1; - - if (!esd->decoderConfig->decoderSpecificInfo || !esd->decoderConfig->decoderSpecificInfo->data) { - ctx->is_annex_b = GF_TRUE; - ctx->width=ctx->height=128; - ctx->out_size = ctx->width*ctx->height*3/2; - ctx->pix_fmt = GF_PIXEL_YV12; - return GF_OK; - } else { - u32 i; - GF_AVCConfigSlot *slc; - GF_AVCConfig *cfg = gf_odf_avc_cfg_read(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength); - for (i=0; i<gf_list_count(cfg->sequenceParameterSets); i++) { - slc = gf_list_get(cfg->sequenceParameterSets, i); - slc->id = -1; - VTB_RegisterParameterSet(ctx, slc->data, slc->size, GF_TRUE); - } - - for (i=0; i<gf_list_count(cfg->pictureParameterSets); i++) { - slc = gf_list_get(cfg->pictureParameterSets, i); - slc->id = -1; - VTB_RegisterParameterSet(ctx, slc->data, slc->size, GF_FALSE); - } - - slc = gf_list_get(ctx->SPSs, 0); - if (slc) ctx->active_sps = slc->id; - - slc = gf_list_get(ctx->PPSs, 0); - if (slc) ctx->active_pps = slc->id; - - ctx->nalu_size_length = cfg->nal_unit_size; - if (gf_list_count(ctx->SPSs) && gf_list_count(ctx->PPSs) ) { - e = VTBDec_InitDecoder(ctx); - } else { - e = GF_OK; - } - gf_odf_avc_cfg_del(cfg); - return e; - } - } - - //check VOSH config - if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_MPEG4_PART2) { - if (!esd->decoderConfig->decoderSpecificInfo || !esd->decoderConfig->decoderSpecificInfo->data) { - ctx->width=ctx->height=128; - ctx->out_size = ctx->width*ctx->height*3/2; - ctx->pix_fmt = GF_PIXEL_YV12; - } else { - return VTBDec_InitDecoder(ctx); - } - } - - return VTBDec_InitDecoder(ctx); -} - -static void VTB_DelParamList(GF_List *list) -{ - while (gf_list_count(list)) { - GF_AVCConfigSlot *slc = gf_list_get(list, 0); - gf_free(slc->data); - gf_free(slc); - gf_list_rem(list, 0); - } - gf_list_del(list); -} - -static void VTBDec_DeleteDecoder(VTBDec *ctx) -{ - if (ctx->fmt_desc) { - CFRelease(ctx->fmt_desc); - ctx->fmt_desc = NULL; - } - if (ctx->vtb_session) { - VTDecompressionSessionInvalidate(ctx->vtb_session); - ctx->vtb_session=NULL; - } - VTB_DelParamList(ctx->SPSs); - ctx->SPSs = NULL; - VTB_DelParamList(ctx->PPSs); - ctx->PPSs = NULL; -} - -static GF_Err VTBDec_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - return GF_OK; -} - -static GF_Err VTBDec_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - VTBDec *ctx = (VTBDec *)ifcg->privateStack; - - switch (capability->CapCode) { - case GF_CODEC_RESILIENT: - capability->cap.valueInt = 1; - break; - case GF_CODEC_WIDTH: - capability->cap.valueInt = ctx->width; - break; - case GF_CODEC_HEIGHT: - capability->cap.valueInt = ctx->height; - break; - case GF_CODEC_STRIDE: - capability->cap.valueInt = ctx->width; - break; - case GF_CODEC_FPS: - capability->cap.valueFloat = 30.0; - break; - case GF_CODEC_PAR: - capability->cap.valueInt = ctx->pixel_ar; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = ctx->out_size; - break; - case GF_CODEC_PIXEL_FORMAT: - capability->cap.valueInt = ctx->pix_fmt; - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = 1; - break; - case GF_CODEC_BUFFER_MAX: - //since we do the temporal de-interleaving ask for more CUs to avoid displaying refs before reordered frames - capability->cap.valueInt = 6; - break; - /*by default we use 4 bytes padding (otherwise it happens that XviD crashes on some videos...)*/ - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 0; - break; - /*reorder is up to us*/ - case GF_CODEC_REORDER: - capability->cap.valueInt = 0; - break; - case GF_CODEC_WANTS_THREAD: - capability->cap.valueInt = 0; - break; - case GF_CODEC_FRAME_OUTPUT: - capability->cap.valueInt = 1; - break; - /*not known at our level...*/ - case GF_CODEC_CU_DURATION: - default: - capability->cap.valueInt = 0; - break; - } - return GF_OK; -} -static GF_Err VTBDec_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - VTBDec *ctx = (VTBDec *)ifcg->privateStack; - - switch (capability.CapCode) { - case GF_CODEC_FRAME_OUTPUT: - ctx->raw_frame_dispatch = capability.cap.valueInt ? GF_TRUE : GF_FALSE; -#ifdef GPAC_IPHONE - if (ctx->raw_frame_dispatch && (capability.cap.valueInt==2)) - ctx->use_gl_textures = GF_TRUE; -#endif - return GF_OK; - } - - /*return unsupported to avoid confusion by the player (like color space changing ...) */ - return GF_NOT_SUPPORTED; -} - - -static GF_Err VTB_ParseNALs(VTBDec *ctx, char *inBuffer, u32 inBufferLength, char **out_buffer, u32 *out_size) -{ - u32 i, sc_size; - char *ptr = inBuffer; - u32 nal_size; - GF_Err e = GF_OK; - GF_BitStream *bs = NULL; - - if (out_buffer) { - *out_buffer = NULL; - *out_size = 0; - } - - if (!ctx->nalu_size_length) { - sc_size=0; - nal_size = gf_media_nalu_next_start_code((u8 *) inBuffer, inBufferLength, &sc_size); - if (!sc_size) return GF_NON_COMPLIANT_BITSTREAM; - ptr += nal_size + sc_size; - assert(inBufferLength >= nal_size + sc_size); - inBufferLength -= nal_size + sc_size; - } - - while (inBufferLength) { - Bool add_nal = GF_TRUE; - u8 nal_type, nal_hdr; - GF_BitStream *nal_bs=NULL; - - if (ctx->nalu_size_length) { - nal_size = 0; - for (i=0; i<ctx->nalu_size_length; i++) { - nal_size = (nal_size<<8) + ((u8) ptr[i]); - } - ptr += ctx->nalu_size_length; - } else { - nal_size = gf_media_nalu_next_start_code((const u8 *) ptr, inBufferLength, &sc_size); - } - nal_bs = gf_bs_new(ptr, nal_size, GF_BITSTREAM_READ); - nal_hdr = gf_bs_read_u8(nal_bs); - nal_type = nal_hdr & 0x1F; - switch (nal_type) { - case GF_AVC_NALU_SEQ_PARAM: - VTB_RegisterParameterSet(ctx, ptr, nal_size, GF_TRUE); - add_nal = GF_FALSE; - break; - case GF_AVC_NALU_PIC_PARAM: - VTB_RegisterParameterSet(ctx, ptr, nal_size, GF_FALSE); - add_nal = GF_FALSE; - break; - case GF_AVC_NALU_ACCESS_UNIT: - case GF_AVC_NALU_END_OF_SEQ: - case GF_AVC_NALU_END_OF_STREAM: - case GF_AVC_NALU_FILLER_DATA: - add_nal = GF_FALSE; - break; - default: - break; - } - - gf_media_avc_parse_nalu(nal_bs, nal_hdr, &ctx->avc); - gf_bs_del(nal_bs); - - if ((nal_type<=GF_AVC_NALU_IDR_SLICE) && ctx->avc.s_info.sps) { - if (ctx->avc.sps_active_idx != ctx->active_sps) { - ctx->reconfig_needed = 1; - ctx->active_sps = ctx->avc.sps_active_idx; - ctx->active_pps = ctx->avc.s_info.pps->id; - return GF_OK; - } - } - - //if sps and pps are ready, init decoder - if (!ctx->vtb_session && gf_list_count(ctx->SPSs) && gf_list_count(ctx->PPSs) ) { - e = VTBDec_InitDecoder(ctx); - if (e) return e; - } - - if (!out_buffer) add_nal = GF_FALSE; - else if (add_nal && !ctx->vtb_session) add_nal = GF_FALSE; - - if (add_nal) { - if (!bs) bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - - gf_bs_write_u32(bs, nal_size); - gf_bs_write_data(bs, ptr, nal_size); - } - - ptr += nal_size; - if (ctx->nalu_size_length) { - if (inBufferLength < nal_size + ctx->nalu_size_length) break; - inBufferLength -= nal_size + ctx->nalu_size_length; - } else { - if (!sc_size || (inBufferLength < nal_size + sc_size)) break; - inBufferLength -= nal_size + sc_size; - ptr += sc_size; - } - } - - if (bs) { - gf_bs_get_content(bs, out_buffer, out_size); - gf_bs_del(bs); - } - return e; -} - -static GF_Err VTBDec_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ - OSStatus status; - CMSampleBufferRef sample = NULL; - CMBlockBufferRef block_buffer = NULL; - OSType type; - char *in_data; - u32 in_data_size; - Bool do_free=GF_FALSE; - - GF_Err e; - VTBDec *ctx = (VTBDec *)ifcg->privateStack; - - if (ctx->skip_mpeg4_vosh) { - GF_M4VDecSpecInfo dsi; - dsi.width = dsi.height = 0; - e = gf_m4v_get_config(inBuffer, inBufferLength, &dsi); - //found a vosh - remove it from payload, init decoder if needed - if ((e==GF_OK) && dsi.width && dsi.height) { - if (!ctx->vtb_session) { - ctx->vosh = inBuffer; - ctx->vosh_size = dsi.next_object_start; - e = VTBDec_InitDecoder(ctx); - if (e) return e; - - //enfoce removal for all frames - ctx->skip_mpeg4_vosh = GF_TRUE; - - if (!ctx->raw_frame_dispatch && (ctx->out_size != *outBufferLength)) { - *outBufferLength = ctx->out_size; - return GF_BUFFER_TOO_SMALL; - } - } - ctx->vosh_size = dsi.next_object_start; - } else if (!ctx->vtb_session) { - *outBufferLength=0; - return GF_OK; - } - } - - if (ctx->init_mpeg12) { - GF_M4VDecSpecInfo dsi; - dsi.width = dsi.height = 0; - - e = gf_mpegv12_get_config(inBuffer, inBufferLength, &dsi); - if ((e==GF_OK) && dsi.width && dsi.height) { - ctx->width = dsi.width; - ctx->height = dsi.height; - ctx->pixel_ar = dsi.par_num; - ctx->pixel_ar <<= 16; - ctx->pixel_ar |= dsi.par_den; - - e = VTBDec_InitDecoder(ctx); - if (e) return e; - - if (!ctx->raw_frame_dispatch && (ctx->out_size != *outBufferLength)) { - *outBufferLength = ctx->out_size; - return GF_BUFFER_TOO_SMALL; - } - } - - if (!ctx->vtb_session) { - *outBufferLength=0; - return GF_OK; - } - } - - if (ctx->check_h264_isma) { - if (inBuffer && !inBuffer[0] && !inBuffer[1] && !inBuffer[2] && (inBuffer[3]==0x01)) { - ctx->check_h264_isma=GF_FALSE; - ctx->nalu_size_length=0; - ctx->is_annex_b=GF_TRUE; - } - } - - //Always parse AVC data , remove SPS/PPS/... and reconfig if needed - if (ctx->is_annex_b || ctx->nalu_size_length) { - do_free=GF_TRUE; - if (ctx->vtb_session && ctx->cached_annex_b) { - in_data = ctx->cached_annex_b; - in_data_size = ctx->cached_annex_b_size; - ctx->cached_annex_b = NULL; - } else { - e = VTB_ParseNALs(ctx, inBuffer, inBufferLength, &in_data, &in_data_size); - if (e) return e; - } - - if (ctx->reconfig_needed) { - if (ctx->raw_frame_dispatch && ctx->decoded_frames_pending) { - *outBufferLength = 1; - gf_free(in_data); - ctx->cached_annex_b = NULL; - return GF_BUFFER_TOO_SMALL; - } - if (ctx->fmt_desc) { - CFRelease(ctx->fmt_desc); - ctx->fmt_desc = NULL; - } - if (ctx->vtb_session) { - VTDecompressionSessionInvalidate(ctx->vtb_session); - ctx->vtb_session=NULL; - } - VTBDec_InitDecoder(ctx); - if (ctx->out_size != *outBufferLength) { - *outBufferLength = ctx->out_size; - gf_free(in_data); - ctx->cached_annex_b = NULL; - return GF_BUFFER_TOO_SMALL; - } - } - - - if (!ctx->raw_frame_dispatch && (ctx->out_size != *outBufferLength)) { - *outBufferLength = ctx->out_size; - ctx->cached_annex_b = in_data; - ctx->cached_annex_b_size = in_data_size; - - return GF_BUFFER_TOO_SMALL; - } - } else if (ctx->vosh_size) { - in_data = inBuffer + ctx->vosh_size; - in_data_size = inBufferLength - ctx->vosh_size; - ctx->vosh_size = 0; - } else { - in_data = inBuffer; - in_data_size = inBufferLength; - } - - if (!ctx->vtb_session) { - *outBufferLength=0; - return GF_OK; - } - - - status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, in_data, in_data_size, kCFAllocatorNull, NULL, 0, in_data_size, 0, &block_buffer); - - if (status) { - return GF_IO_ERR; - } - - *outBufferLength=0; - if (block_buffer == NULL) - return GF_OK; - - - status = CMSampleBufferCreate(kCFAllocatorDefault, block_buffer, TRUE, NULL, NULL, ctx->fmt_desc, 1, 0, NULL, 0, NULL, &sample); - - if (status || (sample==NULL)) { - if (block_buffer) - CFRelease(block_buffer); - return GF_IO_ERR; - } - ctx->last_error = GF_OK; - status = VTDecompressionSessionDecodeFrame(ctx->vtb_session, sample, 0, NULL, 0); - if (!status) - status = VTDecompressionSessionWaitForAsynchronousFrames(ctx->vtb_session); - - - CFRelease(block_buffer); - CFRelease(sample); - if (do_free) - gf_free(in_data); - - if (ctx->last_error) return ctx->last_error; - if (status) - return GF_NON_COMPLIANT_BITSTREAM; - - if (!ctx->frame) { - *outBufferLength=0; - return ctx->last_error; - } - ctx->cts = *CTS; - *outBufferLength = ctx->out_size; - if (ctx->raw_frame_dispatch) return GF_OK; - - status = CVPixelBufferLockBaseAddress(ctx->frame, kCVPixelBufferLock_ReadOnly); - if (status != kCVReturnSuccess) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Error locking frame data\n")); - return GF_IO_ERR; - } - - type = CVPixelBufferGetPixelFormatType(ctx->frame); - - if ((type==kCVPixelFormatType_420YpCbCr8Planar) - || (type==kCVPixelFormatType_420YpCbCr8PlanarFullRange) - || (type==kCVPixelFormatType_422YpCbCr8_yuvs) - || (type==kCVPixelFormatType_444YpCbCr8) - || (type=='444v') - || (type==kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) - || (type==kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) - ) { - u32 i, j, nb_planes = (u32) CVPixelBufferGetPlaneCount(ctx->frame); - char *dst = outBuffer; - Bool needs_stride=GF_FALSE; - u32 stride = (u32) CVPixelBufferGetBytesPerRowOfPlane(ctx->frame, 0); - - //TOCHECK - for now the 3 planes are consecutive in VideoToolbox - if (stride==ctx->width) { - char *data = CVPixelBufferGetBaseAddressOfPlane(ctx->frame, 0); - memcpy(dst, data, sizeof(char)*ctx->out_size); - } else { - for (i=0; i<nb_planes; i++) { - char *data = CVPixelBufferGetBaseAddressOfPlane(ctx->frame, i); - u32 stride = (u32) CVPixelBufferGetBytesPerRowOfPlane(ctx->frame, i); - u32 w, h = (u32) CVPixelBufferGetHeightOfPlane(ctx->frame, i); - w = ctx->width; - if (i) { - switch (ctx->pix_fmt) { - case GF_PIXEL_YUV444: - break; - case GF_PIXEL_YUV422: - case GF_PIXEL_YV12: - w /= 2; - break; - } - } - if (stride != w) { - needs_stride=GF_TRUE; - for (j=0; j<h; j++) { - memcpy(dst, data, sizeof(char)*w); - dst += w; - data += stride; - } - } else { - memcpy(dst, data, sizeof(char)*h*stride); - dst += sizeof(char)*h*stride; - } - } - } - } - CVPixelBufferUnlockBaseAddress(ctx->frame, kCVPixelBufferLock_ReadOnly); - - return GF_OK; -} - -typedef struct -{ - Bool locked; - CVPixelBufferRef frame; - VTBDec *ctx; - u32 cts; - - //openGL mode -#ifdef GPAC_IPHONE - CVOpenGLESTextureRef y, u, v; -#endif -} VTB_Frame; - -void VTBFrame_Release(GF_MediaDecoderFrame *frame) -{ - VTB_Frame *f = (VTB_Frame *)frame->user_data; - if (f->locked) { - CVPixelBufferUnlockBaseAddress(f->frame, kCVPixelBufferLock_ReadOnly); - } -#ifdef GPAC_IPHONE - if (f->y) CVBufferRelease(f->y); - if (f->u) CVBufferRelease(f->u); - if (f->v) CVBufferRelease(f->v); - if (f->ctx->cache_texture) - CVOpenGLESTextureCacheFlush(f->ctx->cache_texture, 0); -#endif - - if (f->frame) { - CVPixelBufferRelease(f->frame); - } - f->ctx->decoded_frames_pending--; - - gf_free(f); - gf_free(frame); -} - -GF_Err VTBFrame_GetPlane(GF_MediaDecoderFrame *frame, u32 plane_idx, const char **outPlane, u32 *outStride) -{ - OSStatus status; - GF_Err e; - VTB_Frame *f = (VTB_Frame *)frame->user_data; - if (! outPlane || !outStride) return GF_BAD_PARAM; - *outPlane = NULL; - *outStride = 0; - -// if (!f->locked) { - status = CVPixelBufferLockBaseAddress(f->frame, kCVPixelBufferLock_ReadOnly); - if (status != kCVReturnSuccess) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Error locking frame data\n")); - return GF_IO_ERR; - } -// f->locked = GF_TRUE; -// } - e = GF_OK; - - if (CVPixelBufferIsPlanar(f->frame)) { - *outStride = (u32) CVPixelBufferGetBytesPerRowOfPlane(f->frame, plane_idx); - *outPlane = CVPixelBufferGetBaseAddressOfPlane(f->frame, plane_idx); - } else if (plane_idx==0) { - *outStride = (u32) CVPixelBufferGetBytesPerRow(f->frame); - *outPlane = CVPixelBufferGetBaseAddress(f->frame); - } else { - e = GF_BAD_PARAM; - } - CVPixelBufferUnlockBaseAddress(f->frame, kCVPixelBufferLock_ReadOnly); - - return e; -} - -#ifdef GPAC_IPHONE - -void *myGetGLContext(); - -GF_Err VTBFrame_GetGLTexture(GF_MediaDecoderFrame *frame, u32 plane_idx, u32 *gl_tex_format, u32 *gl_tex_id, GF_CodecMatrix * texcoordmatrix) -{ - OSStatus status; - GLenum target_fmt; - u32 w, h; - CVOpenGLESTextureRef *outTexture=NULL; - VTB_Frame *f = (VTB_Frame *)frame->user_data; - if (! gl_tex_format || !gl_tex_id) return GF_BAD_PARAM; - *gl_tex_format = 0; - *gl_tex_id = 0; - - if (!f->ctx->gl_context) { - f->ctx->gl_context = myGetGLContext(); - if (!f->ctx->gl_context) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Error locating current GL context\n")); - return GF_IO_ERR; - } - } - if (! f->ctx->decoded_frames_pending) return GF_IO_ERR; - - if (!f->ctx->cache_texture) { - status = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, f->ctx->gl_context, NULL, &f->ctx->cache_texture); - if (status != kCVReturnSuccess) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Error creating cache texture\n")); - return GF_IO_ERR; - } - } - - if (!f->locked) { - status = CVPixelBufferLockBaseAddress(f->frame, kCVPixelBufferLock_ReadOnly); - if (status != kCVReturnSuccess) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Error locking frame data\n")); - return GF_IO_ERR; - } - f->locked = GF_TRUE; - } - - if (plane_idx >= (u32) CVPixelBufferGetPlaneCount(f->frame)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Wrong plane index\n")); - return GF_BAD_PARAM; - } - - target_fmt = GL_LUMINANCE; - w = f->ctx->width; - h = f->ctx->height; - if (plane_idx) { - w /= 2; - h /= 2; - target_fmt = GL_LUMINANCE_ALPHA; - } - if (plane_idx==0) { - outTexture = &f->y; - } - else if (plane_idx==1) { - outTexture = &f->u; - } - //don't create texture if already done ! - if ( *outTexture == NULL) { - status = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, f->ctx->cache_texture, f->frame, NULL, GL_TEXTURE_2D, target_fmt, w, h, target_fmt, GL_UNSIGNED_BYTE, plane_idx, outTexture); - - if (status != kCVReturnSuccess) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Error creating cache texture for plane %d\n", plane_idx)); - return GF_IO_ERR; - } - } - *gl_tex_format = CVOpenGLESTextureGetTarget(*outTexture); - *gl_tex_id = CVOpenGLESTextureGetName(*outTexture); - - return GF_OK; -} -#endif - -GF_Err VTBDec_GetOutputFrame(GF_MediaDecoder *dec, u16 ES_ID, GF_MediaDecoderFrame **frame, Bool *needs_resize) -{ - GF_MediaDecoderFrame *a_frame; - VTB_Frame *vtb_frame; - VTBDec *ctx = (VTBDec *)dec->privateStack; - - *needs_resize = GF_FALSE; - - if (!ctx->frame) return GF_BAD_PARAM; - - GF_SAFEALLOC(a_frame, GF_MediaDecoderFrame); - if (!a_frame) return GF_OUT_OF_MEM; - GF_SAFEALLOC(vtb_frame, VTB_Frame); - if (!vtb_frame) { - gf_free(a_frame); - return GF_OUT_OF_MEM; - } - a_frame->user_data = vtb_frame; - vtb_frame->ctx = ctx; - vtb_frame->frame = ctx->frame; - ctx->frame = NULL; - a_frame->Release = VTBFrame_Release; - a_frame->GetPlane = VTBFrame_GetPlane; -#ifdef GPAC_IPHONE - if (ctx->use_gl_textures) - a_frame->GetGLTexture = VTBFrame_GetGLTexture; -#endif - - *frame = a_frame; - if (ctx->frame_size_changed) { - ctx->frame_size_changed = GF_FALSE; - *needs_resize = GF_TRUE; - } - vtb_frame->cts = ctx->cts; - if (ctx->cts==960) { - vtb_frame->cts = 960; - } - ctx->decoded_frames_pending++; - return GF_OK; -} - -static u32 VTBDec_CanHandleStream(GF_BaseDecoder *dec, u32 StreamType, GF_ESD *esd, u8 PL) -{ -#ifdef GPAC_IPHONE - u32 ret_val_OK = GF_CODEC_SUPPORTED * 2; -#else - u32 ret_val_OK = GF_CODEC_MAYBE_SUPPORTED; -#endif - if (StreamType != GF_STREAM_VISUAL) return GF_CODEC_NOT_SUPPORTED; - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - - switch (esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_VIDEO_AVC: - if (esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->data) { - GF_AVCConfig *cfg = gf_odf_avc_cfg_read(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength); - Bool cp_ok = GF_TRUE; - if (!cfg->chroma_format) { - GF_AVCConfigSlot *s = gf_list_get(cfg->sequenceParameterSets, 0); - if (s) { - AVCState avc; - s32 idx; - memset(&avc, 0, sizeof(AVCState)); - avc.sps_active_idx = -1; - idx = gf_media_avc_read_sps(s->data, s->size, &avc, 0, NULL); - cfg->chroma_format = avc.sps[idx].chroma_format; - cfg->luma_bit_depth = 8 + avc.sps[idx].luma_bit_depth_m8; - cfg->chroma_bit_depth = 8 + avc.sps[idx].chroma_bit_depth_m8; - } - } - if ((cfg->chroma_bit_depth>8) || (cfg->luma_bit_depth > 8) || (cfg->chroma_format>1)) { - cp_ok = GF_FALSE; - } - gf_odf_avc_cfg_del(cfg); - if (!cp_ok) return GF_CODEC_PROFILE_NOT_SUPPORTED; - } - return ret_val_OK; - - case GPAC_OTI_VIDEO_MPEG4_PART2: - return ret_val_OK; - - case GPAC_OTI_VIDEO_MPEG2_SIMPLE: - case GPAC_OTI_VIDEO_MPEG2_MAIN: - case GPAC_OTI_VIDEO_MPEG2_SNR: - case GPAC_OTI_VIDEO_MPEG2_SPATIAL: - case GPAC_OTI_VIDEO_MPEG2_HIGH: - case GPAC_OTI_VIDEO_MPEG2_422: - //not supported on iphone -#ifdef GPAC_IPHONE - return GF_CODEC_NOT_SUPPORTED; -#else - return ret_val_OK; -#endif - - //cannot make it work on ios and OSX version seems buggy (wrong frame output order) -// case GPAC_OTI_VIDEO_MPEG1: -// return GF_CODEC_SUPPORTED * 2; - - case GPAC_OTI_MEDIA_GENERIC: - if (esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->dataLength) { - char *dsi = esd->decoderConfig->decoderSpecificInfo->data; - if (!strnicmp(dsi, "s263", 4)) return ret_val_OK; - } - } - return GF_CODEC_NOT_SUPPORTED; -} - -static const char *VTBDec_GetCodecName(GF_BaseDecoder *dec) -{ - VTBDec *ctx = (VTBDec *)dec->privateStack; - switch (ctx->vtb_type) { - case kCMVideoCodecType_H264: - return ctx->is_hardware ? "VTB hardware AVC|H264" : "VTB software AVC|H264"; - case kCMVideoCodecType_MPEG2Video: - return ctx->is_hardware ? "VTB hardware MPEG-2" : "VTB software MPEG-2"; - case kCMVideoCodecType_MPEG4Video: - return ctx->is_hardware ? "VTB hardware MPEG-4 Part2" : "VTB software MPEG-4 Part2"; - case kCMVideoCodecType_H263: - return ctx->is_hardware ? "VTB hardware H263" : "VTB software H263"; - case kCMVideoCodecType_MPEG1Video: - return ctx->is_hardware ? "VTB hardware MPEG-1" : "VTB software MPEG-1"; - default: - return "VideoToolbox unknown Decoder"; - } -} - -GF_BaseDecoder *NewVTBDec() -{ - GF_MediaDecoder *ifcd; - VTBDec *dec; - - GF_SAFEALLOC(ifcd, GF_MediaDecoder); - if (!ifcd) return NULL; - GF_SAFEALLOC(dec, VTBDec); - if (!dec) { - gf_free(ifcd); - return NULL; - } - GF_REGISTER_MODULE_INTERFACE(ifcd, GF_MEDIA_DECODER_INTERFACE, "VideoToolbox Decoder", "gpac distribution") - - ifcd->privateStack = dec; - - /*setup our own interface*/ - ifcd->AttachStream = VTBDec_AttachStream; - ifcd->DetachStream = VTBDec_DetachStream; - ifcd->GetCapabilities = VTBDec_GetCapabilities; - ifcd->SetCapabilities = VTBDec_SetCapabilities; - ifcd->GetName = VTBDec_GetCodecName; - ifcd->CanHandleStream = VTBDec_CanHandleStream; - ifcd->ProcessData = VTBDec_ProcessData; - ifcd->GetOutputFrame = VTBDec_GetOutputFrame; - return (GF_BaseDecoder *) ifcd; -} - -void DeleteVTBDec(GF_BaseDecoder *ifcg) -{ - VTBDec *ctx = (VTBDec *)ifcg->privateStack; - VTBDec_DeleteDecoder(ctx); - -#ifdef GPAC_IPHONE - if (ctx->cache_texture) { - CFRelease(ctx->cache_texture); - } -#endif - gf_free(ctx); - gf_free(ifcg); -} -#endif - - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifndef GPAC_DISABLE_AV_PARSERS - GF_MEDIA_DECODER_INTERFACE, -#endif - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ -#ifndef GPAC_DISABLE_AV_PARSERS - if (InterfaceType == GF_MEDIA_DECODER_INTERFACE) return (GF_BaseInterface *)NewVTBDec(); -#endif - return NULL; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#ifndef GPAC_DISABLE_AV_PARSERS - case GF_MEDIA_DECODER_INTERFACE: - DeleteVTBDec((GF_BaseDecoder*)ifce); - break; -#endif - } -} - -GPAC_MODULE_STATIC_DECLARATION( vtb ) diff --git a/modules/vtt_in/Makefile b/modules/vtt_in/Makefile deleted file mode 100644 index bcb92b8..0000000 --- a/modules/vtt_in/Makefile +++ /dev/null @@ -1,51 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/vtt_in - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -LINKLIBS= -lgpac -LOCAL_LIB=-L../../bin/gcc - -#common obj -OBJS= vtt_in.o vtt_dec.o - -SRCS := $(OBJS:.o=.c) - -LIB=gm_vtt_in$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols vtt_in.def -endif - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) $(EXTRALIBS) $(LOCAL_LIB) $(LINKLIBS) $(LDFLAGS) -ifeq ($(STATICBUILD),yes) - $(CC) $(SHFLAGS) -o ../../bin/gcc/gm_vtt_in-static$(DYN_LIB_SUFFIX) $(OBJS) $(EXTRALIBS) $(LOCAL_LIB) -lgpac_static $(LDFLAGS) -endif - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/vtt_in/vtt_dec.c b/modules/vtt_in/vtt_dec.c deleted file mode 100644 index 7fe4e58..0000000 --- a/modules/vtt_in/vtt_dec.c +++ /dev/null @@ -1,490 +0,0 @@ -/* - * GPAC Multimedia Framework - * - * Authors: Cyril Concolato - * Copyright (c) Telecom ParisTech 2013- - * All rights reserved - * - * This file is part of GPAC / VTT Loader/Decoder module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include <gpac/constants.h> -#include <gpac/internal/terminal_dev.h> -#include <gpac/internal/isomedia_dev.h> -#include <gpac/internal/media_dev.h> -#include <gpac/internal/scenegraph_dev.h> -#include <gpac/internal/compositor_dev.h> -#include <gpac/nodes_svg.h> -#include <gpac/webvtt.h> - -#if !defined(GPAC_DISABLE_VTT) && !defined(GPAC_DISABLE_SVG) - -typedef struct { - GF_BaseInterface *module; - - /* Object Type Indication to distinguish between WebVTT content passsed to the decoder as: - - a single file (GPAC_OTI_PRIVATE_SCENE_VTT) - - character strings (GPAC_OTI_SCENE_VTT) - - ISO sample (GPAC_OTI_SCENE_VTT_MP4) - */ - u32 oti; - - /* Boolean indicating if there is already a WebVTT stream attached to this decoder */ - Bool is_stream_attached; - - /* ID of the Elementary Stream on which this stream depends (if any) - 0 if no dependency */ - u32 base_es_id; - - /* file_name of the VTT file when parsing a input text file - not used when parsing from MP4 or from a stream */ - char *file_name; - u64 file_size; - u64 file_pos; - - /* config of the VTT file when parsing an MP4 file - not used when parsing from text file or from a stream */ - char *config; - - /* Boolean indicating if a progressive download/parsing is required */ - Bool use_progressive; - - /* Scene to which this WebVTT stream is linked */ - GF_Scene *scene; - - /* Terminal to which this WebVTT stream is linked */ - GF_Terminal *terminal; - - GF_List *cues; - - /* Scene graph for the subtitle content */ - GF_SceneGraph *sg; - Bool has_rendering_script; -} VTTDec; - - -#define VTT_BUFFER_SIZE 4096 - -/* Checks that the file is fully downloaded - requires that the file_size is given in the DecoderSpecificInfo */ -static Bool vtt_check_download(VTTDec *vttdec) -{ - u64 size; - FILE *f = gf_fopen(vttdec->file_name, "rt"); - if (!f) return GF_FALSE; - gf_fseek(f, 0, SEEK_END); - size = gf_ftell(f); - gf_fclose(f); - if (size==vttdec->file_size) return GF_TRUE; - return GF_FALSE; -} - - -static GF_Err VTT_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 stream_time, u32 mmlevel) -{ - GF_Err e = GF_OK; - VTTDec *vttdec = (VTTDec *)plug->privateStack; - if (!vttdec->has_rendering_script) return GF_BAD_PARAM; - - if (stream_time==(u32)-1) { - /* TODO */ - return GF_OK; - } - - switch (vttdec->oti) { - case GPAC_OTI_PRIVATE_SCENE_VTT: - /*full parsing*/ - if (vttdec->file_size) { - /*init step*/ - if (!vttdec->use_progressive) { - /*not done yet*/ - if (!vtt_check_download(vttdec)) return GF_OK; - /* TODO: parse */ - } else { - /* TODO: parse what you can */ - } - } - break; - - case GPAC_OTI_SCENE_VTT: - /* TODO: try to parse a cue from the given string */ - break; - - case GPAC_OTI_SCENE_VTT_MP4: - { -#ifndef GPAC_DISABLE_MEDIA_IMPORT - char start[100], end[100]; - GF_List *cues; - cues = gf_webvtt_parse_cues_from_data(inBuffer, inBufferLength, 0); - gf_webvtt_js_removeCues(vttdec->sg->RootNode); - if (gf_list_count(cues)) { - while (gf_list_count(cues)) { - GF_WebVTTCue *cue = (GF_WebVTTCue *)gf_list_get(cues, 0); - gf_list_rem(cues, 0); - sprintf(start, "%02d:%02d:%02d.%03d", cue->start.hour, cue->start.min, cue->start.sec, cue->start.ms); - sprintf(end, "%02d:%02d:%02d.%03d", cue->end.hour, cue->end.min, cue->end.sec, cue->end.ms); - gf_webvtt_js_addCue(vttdec->sg->RootNode, cue->id, start, end, cue->settings, cue->text); - - gf_webvtt_cue_del(cue); - } - } - gf_list_del(cues); -#endif - } - break; - - default: - return GF_BAD_PARAM; - } - - return e; -} - -void VTT_UpdateSizeInfo(VTTDec *vttdec) -{ - u32 w, h; - GF_FieldInfo info; - char szVB[100]; - GF_Node *root = gf_sg_get_root_node(vttdec->sg); - if (!root) return; -#if 0 - w = vttdec->scene->root_od->term->compositor->display_width; - h = vttdec->scene->root_od->term->compositor->display_height; -#else - w=1280; - h=720; -#endif - - /*apply*/ - gf_sg_set_scene_size_info(vttdec->sg, w, h, GF_TRUE); - - sprintf(szVB, "0 0 %d %d", w, h); - gf_node_get_attribute_by_tag(root, TAG_SVG_ATT_viewBox, GF_TRUE, GF_FALSE, &info); - gf_svg_parse_attribute(root, &info, szVB, 0); - -} - - - -void VTT_load_script(VTTDec *vttdec, GF_SceneGraph *graph) -{ - GF_Node *n, *root; - GF_FieldInfo info; - const char *path; - FILE *jsfile; - - if (!graph) return; - gf_sg_add_namespace(graph, "http://www.w3.org/2000/svg", NULL); - gf_sg_add_namespace(graph, "http://www.w3.org/1999/xlink", "xlink"); - gf_sg_add_namespace(graph, "http://www.w3.org/2001/xml-events", "ev"); - gf_sg_set_scene_size_info(graph, 800, 600, GF_TRUE); - - /* modify the scene with an Inline/Animation pointing to the VTT Renderer */ - n = root = gf_node_new(graph, TAG_SVG_svg); - gf_node_register(root, NULL); - gf_sg_set_root_node(graph, root); - gf_node_get_attribute_by_name(n, "xmlns", 0, GF_TRUE, GF_FALSE, &info); - gf_svg_parse_attribute(n, &info, "http://www.w3.org/2000/svg", 0); - VTT_UpdateSizeInfo(vttdec); - gf_node_init(n); - - n = gf_node_new(graph, TAG_SVG_script); - gf_node_register(n, root); - gf_node_list_add_child(&((GF_ParentNode *)root)->children, n); - path = gf_modules_get_option((GF_BaseInterface *)vttdec->module, "WebVTT", "RenderingScript"); - if (!path) { - /* try to find the JS renderer in the default GPAC installation folder */ - const char *startuppath = gf_modules_get_option((GF_BaseInterface *)vttdec->module, "General", "StartupFile"); - char *jspath = gf_url_concatenate(startuppath, "webvtt-renderer.js"); - jsfile = gf_fopen(jspath, "rt"); - if (jsfile) { - gf_modules_set_option((GF_BaseInterface *)vttdec->module, "WebVTT", "RenderingScript", jspath); - gf_fclose(jsfile); - gf_free(jspath); - path = gf_modules_get_option((GF_BaseInterface *)vttdec->module, "WebVTT", "RenderingScript"); - } else { - gf_free(jspath); - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[WebVTT] Cannot find Rendering Script [WebVTT:RenderingScript] - check config file\n")); - return; - } - } - jsfile = gf_fopen(path, "rt"); - if (jsfile) { - gf_fclose(jsfile); - gf_node_get_attribute_by_tag(n, TAG_XLINK_ATT_href, GF_TRUE, GF_FALSE, &info); - if (strstr(path, ":\\")) { - gf_svg_parse_attribute(n, &info, (char *) path, 0); - } else { - char szPath[GF_MAX_PATH]; - strcpy(szPath, "file://"); - strcat(szPath, path); - gf_svg_parse_attribute(n, &info, (char *) szPath, 0); - } - - vttdec->has_rendering_script = GF_TRUE; - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[WebVTT] Cannot open Rendering Script - %s\n", path)); - return; - } - gf_node_init(n); - -} - -static GF_Err VTT_AttachScene(GF_SceneDecoder *plug, GF_Scene *scene, Bool is_scene_decoder) -{ - VTTDec *vttdec = (VTTDec *)plug->privateStack; - /*WebVTT cannot be a root scene object*/ - //if (is_scene_decoder) return GF_BAD_PARAM; - vttdec->scene = scene; - vttdec->terminal = scene->root_od->term; - - vttdec->sg = gf_sg_new_subscene(vttdec->scene->graph); - VTT_load_script(vttdec, vttdec->sg); - //vtt_create_text_node(vttdec, 2000, GF_TRUE); - return GF_OK; -} - -static void VTT_CleanExtraScene(VTTDec *vttdec) -{ - /* Cleaning scene graph related data */ - if (vttdec->sg) { - gf_scene_register_extra_graph(vttdec->scene, vttdec->sg, GF_TRUE); - gf_sg_del(vttdec->sg); - vttdec->sg = NULL; - } -} - -static GF_Err VTT_ReleaseScene(GF_SceneDecoder *plug) -{ - VTTDec *vttdec = (VTTDec *)plug->privateStack; - - VTT_CleanExtraScene(vttdec); - vttdec->scene = NULL; - vttdec->terminal = NULL; - - return GF_OK; -} - -static void VTT_ReadFileNameFromDSI(VTTDec *vttdec, GF_DefaultDescriptor *dsi) -{ - GF_BitStream *bs; - bs = gf_bs_new(dsi->data, dsi->dataLength, GF_BITSTREAM_READ); - vttdec->file_size = gf_bs_read_u32(bs); - vttdec->file_pos = 0; - gf_bs_del(bs); - vttdec->file_name = (char *) gf_malloc(sizeof(char)*(1 + dsi->dataLength - sizeof(u32)) ); - memcpy(vttdec->file_name, dsi->data + sizeof(u32), dsi->dataLength - sizeof(u32) ); - vttdec->file_name[dsi->dataLength - sizeof(u32) ] = 0; -} - -static void VTT_ReadConfigFromDSI(VTTDec *vttdec, GF_DefaultDescriptor *dsi) -{ - GF_BitStream *bs; - u32 entry_type; - - bs = gf_bs_new(dsi->data, dsi->dataLength, GF_BITSTREAM_READ); - entry_type = gf_bs_read_u32(bs); - if (entry_type == GF_ISOM_BOX_TYPE_WVTT) { - GF_Box *b; - gf_isom_box_parse(&b, bs); - vttdec->config = ((GF_StringBox *)b)->string; - ((GF_StringBox *)b)->string = NULL; - gf_isom_box_del(b); - } - gf_bs_del(bs); -} - -static GF_Err VTT_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd) -{ - VTTDec *vttdec = (VTTDec *)plug->privateStack; - /* This is a down stream */ - if (esd->decoderConfig->upstream) return GF_NOT_SUPPORTED; - /* Only process one stream at a time*/ - if (vttdec->is_stream_attached) return GF_NOT_SUPPORTED; - - /* decSpecInfo is not null only when reading from an SVG file (local or distant, cached or not) */ - switch (esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_SCENE_VTT: - /*no decSpecInfo defined for streaming VTT*/ - break; - case GPAC_OTI_SCENE_VTT_MP4: - /* decSpecInfo for VTT in MP4 contains WVTT config box */ - if (!esd->decoderConfig->decoderSpecificInfo) return GF_NON_COMPLIANT_BITSTREAM; - VTT_ReadConfigFromDSI(vttdec, esd->decoderConfig->decoderSpecificInfo); - break; - case GPAC_OTI_PRIVATE_SCENE_VTT: - default: - if (!esd->decoderConfig->decoderSpecificInfo) return GF_NON_COMPLIANT_BITSTREAM; - VTT_ReadFileNameFromDSI(vttdec, esd->decoderConfig->decoderSpecificInfo); - break; - } - vttdec->oti = esd->decoderConfig->objectTypeIndication; - vttdec->is_stream_attached = GF_TRUE; - - if (!esd->dependsOnESID) vttdec->base_es_id = esd->ESID; - - return GF_OK; -} - -static GF_Err VTT_DetachStream(GF_BaseDecoder *plug, u16 ES_ID) -{ - VTTDec *vttdec = (VTTDec *)plug->privateStack; - if (vttdec->file_name) gf_free(vttdec->file_name); - VTT_CleanExtraScene(vttdec); - vttdec->file_name = NULL; - vttdec->is_stream_attached = GF_FALSE; - if (vttdec->config) { - gf_free(vttdec->config); - vttdec->config = NULL; - } - return GF_OK; -} - -const char *VTT_GetName(struct _basedecoder *plug) -{ - VTTDec *vttdec = (VTTDec *)plug->privateStack; - if (vttdec->oti==GPAC_OTI_PRIVATE_SCENE_VTT) return "GPAC WebVTT Parser"; - if (vttdec->oti==GPAC_OTI_SCENE_VTT) return "GPAC WebVTT Streaming Parser"; - if (vttdec->oti==GPAC_OTI_SCENE_VTT_MP4) return "GPAC WebVTT/MP4 Parser"; - return "INTERNAL ERROR"; -} - -static u32 VTT_CanHandleStream(GF_BaseDecoder *ifce, u32 StreamType, GF_ESD *esd, u8 PL) -{ - if (StreamType==GF_STREAM_TEXT) { - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - switch (esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_SCENE_VTT: - case GPAC_OTI_SCENE_VTT_MP4: - return GF_CODEC_SUPPORTED; - default: - return GF_CODEC_NOT_SUPPORTED; - } - } - return GF_CODEC_NOT_SUPPORTED; -} - -static GF_Err VTT_GetCapabilities(GF_BaseDecoder *plug, GF_CodecCapability *cap) -{ - cap->cap.valueInt = 0; - /* TODO */ - return GF_NOT_SUPPORTED; -} - -static GF_Err VTT_SetCapabilities(GF_BaseDecoder *plug, GF_CodecCapability capability) -{ - VTTDec *vttdec = (VTTDec *)plug->privateStack; - if (capability.CapCode==GF_CODEC_ABORT) { - /* TODO */ - } else if (capability.CapCode==GF_CODEC_SHOW_SCENE) { - if (capability.cap.valueInt) { - VTT_UpdateSizeInfo(vttdec); - gf_scene_register_extra_graph(vttdec->scene, vttdec->sg, GF_FALSE); - } else { - gf_scene_register_extra_graph(vttdec->scene, vttdec->sg, GF_TRUE); - } - } - return GF_OK; -} - -GF_BaseInterface *NewVTTDec() -{ - VTTDec *vttdec; - GF_SceneDecoder *sdec; - - GF_SAFEALLOC(sdec, GF_SceneDecoder) - if (!sdec) return NULL; - GF_REGISTER_MODULE_INTERFACE(sdec, GF_SCENE_DECODER_INTERFACE, "GPAC WebVTT Parser", "gpac distribution"); - - GF_SAFEALLOC(vttdec, VTTDec); - if (!vttdec) { - gf_free(sdec); - return NULL; - } - vttdec->cues = gf_list_new(); - vttdec->module = (GF_BaseInterface *)sdec; - - sdec->privateStack = vttdec; - sdec->CanHandleStream = VTT_CanHandleStream; - sdec->AttachStream = VTT_AttachStream; - sdec->DetachStream = VTT_DetachStream; - sdec->AttachScene = VTT_AttachScene; - sdec->ReleaseScene = VTT_ReleaseScene; - sdec->ProcessData = VTT_ProcessData; - sdec->GetName = VTT_GetName; - sdec->SetCapabilities = VTT_SetCapabilities; - sdec->GetCapabilities = VTT_GetCapabilities; - return (GF_BaseInterface *)sdec; -} - -void DeleteVTTDec(GF_BaseDecoder *plug) -{ - VTTDec *dec= (VTTDec *)plug->privateStack; - gf_list_del(dec->cues); - gf_free(dec); - gf_free(plug); -} - -#endif // !defined(GPAC_DISABLE_VTT) && !defined(GPAC_DISABLE_SVG) - - -/*interface create*/ -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - switch (InterfaceType) { -#if !defined(GPAC_DISABLE_VTT) && !defined(GPAC_DISABLE_SVG) - case GF_SCENE_DECODER_INTERFACE: - return (GF_BaseInterface *)NewVTTDec(); -#endif - default: - return NULL; - } -} - - -/*interface destroy*/ -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#if !defined(GPAC_DISABLE_VTT) && !defined(GPAC_DISABLE_SVG) - case GF_SCENE_DECODER_INTERFACE: - DeleteVTTDec((GF_BaseDecoder *)ifce); - break; -#endif - } -} - -/*interface query*/ -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#if !defined(GPAC_DISABLE_VTT) && !defined(GPAC_DISABLE_SVG) - GF_SCENE_DECODER_INTERFACE, -#endif - 0 - }; - return si; -} - -GPAC_MODULE_STATIC_DECLARATION( vtt_dec ) diff --git a/modules/vtt_in/vtt_in.c b/modules/vtt_in/vtt_in.c deleted file mode 100644 index 795a9f9..0000000 --- a/modules/vtt_in/vtt_in.c +++ /dev/null @@ -1,374 +0,0 @@ -/* - * GPAC Multimedia Framework - * - * Authors: Cyril Concolato - * Copyright (c) Telecom ParisTech 2013- - * All rights reserved - * - * This file is part of GPAC / VTT Loader/Decoder module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include <gpac/internal/terminal_dev.h> -#include <gpac/scene_manager.h> -#include <gpac/constants.h> -#include <gpac/modules/js_usr.h> - -#if 0 - -typedef struct -{ - /* Counter-part object in the terminal */ - GF_ClientService *service; - /* Channel object associated to this object */ - LPNETCHANNEL channel; - - /*file downloader*/ - GF_DownloadSession * dnload; - - Bool needs_connection; - - /* Header for the SL packets created by this module and sent out to the terminal */ - GF_SLHeader sl_hdr; -} VTTIn; - -const char * VTT_MIME_TYPES[] = { - "text/vtt", "vtt", "VTT SubTitles", - NULL -}; - -static u32 VTT_RegisterMimeTypes(const GF_InputService *plug) { - u32 i; - if (!plug) return 0; - for (i = 0 ; VTT_MIME_TYPES[i]; i+=3) { - gf_service_register_mime(plug, VTT_MIME_TYPES[i], VTT_MIME_TYPES[i+1], VTT_MIME_TYPES[i+2]); - } - return i/3; -} - -static Bool VTT_CanHandleURL(GF_InputService *plug, const char *url) -{ - const char *sExt; - u32 i; - if (!plug || !url) return GF_FALSE; - sExt = strrchr(url, '.'); - if (!sExt) return GF_FALSE; - for (i = 0 ; VTT_MIME_TYPES[i]; i+=3) { - if (gf_service_check_mime_register(plug, VTT_MIME_TYPES[i], VTT_MIME_TYPES[i+1], VTT_MIME_TYPES[i+2], sExt)) return GF_TRUE; - } - return GF_FALSE; -} - -static Bool VTT_is_local(const char *url) -{ - if (!url) return GF_FALSE; - if (!strnicmp(url, "file://", 7)) return GF_TRUE; - if (strstr(url, "://")) return GF_FALSE; - return GF_TRUE; -} - -void VTT_NetIO(void *cbk, GF_NETIO_Parameter *param) -{ - GF_Err e; - const char *szCache; - GF_InputService *plug = (GF_InputService *)cbk; - VTTIn *vttin = (VTTIn *) plug->priv; - - if (!vttin) return; - gf_service_download_update_stats(vttin->dnload); - - e = param->error; - /*done*/ - if (param->msg_type==GF_NETIO_DATA_TRANSFERED) { - szCache = gf_dm_sess_get_cache_name(vttin->dnload); - if (!szCache) e = GF_IO_ERR; - else { - //e = TTIn_LoadFile(plug, szCache, 1); - } - } - else if (param->msg_type==GF_NETIO_DATA_EXCHANGE) { - return; - } - - /*OK confirm*/ - if (vttin->needs_connection) { - vttin->needs_connection = GF_FALSE; - gf_service_connect_ack(vttin->service, NULL, e); - //if (!e && !vttin->od_done) tti_setup_object(vttin); - } -} - -void VTT_download_file(GF_InputService *plug, const char *url) -{ - VTTIn *vttin = (VTTIn *) plug->priv; - if (!plug || !url) - return; - vttin->needs_connection = GF_TRUE; - vttin->dnload = gf_service_download_new(vttin->service, url, 0, VTT_NetIO, plug); - if (!vttin->dnload) { - vttin->needs_connection = GF_FALSE; - gf_service_connect_ack(vttin->service, NULL, GF_NOT_SUPPORTED); - } else { - /*start our download (threaded)*/ - gf_dm_sess_process(vttin->dnload); - } - /*service confirm is done once fetched*/ -} - -static GF_Err VTT_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url) -{ - GF_Err e; - VTTIn *vttin = (VTTIn *)plug->priv; - if (!plug || !url) - return GF_BAD_PARAM; - vttin->service = serv; - - if (vttin->dnload) gf_service_download_del(vttin->dnload); - vttin->dnload = NULL; - - /*remote fetch*/ - if (!VTT_is_local(url)) { - VTT_download_file(plug, url); - return GF_OK; - } else { - e = GF_OK; - //e = TTIn_LoadFile(plug, url, 0); - gf_service_connect_ack(serv, NULL, e); - //if (!e && !vttin->od_done) tti_setup_object(vttin); - } - return GF_OK; -} - -static GF_Err VTT_CloseService(GF_InputService *plug) -{ - VTTIn *vttin; - if (!plug) return GF_BAD_PARAM; - - vttin = (VTTIn *)plug->priv; - if (!vttin) return GF_BAD_PARAM; - - //if (vttin->szFile) { - // gf_delete_file(vttin->szFile); - // gf_free(vttin->szFile); - //} - //vttin->szFile = NULL; - - if (vttin->dnload) { - gf_service_download_del(vttin->dnload); - } - vttin->dnload = NULL; - - if (vttin->service) { - gf_service_disconnect_ack(vttin->service, NULL, GF_OK); - } - vttin->service = NULL; - - return GF_OK; -} - -static GF_Descriptor *VTT_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url) -{ - VTTIn *vttin; - if (!plug) return NULL; - vttin = (VTTIn *)plug->priv; - if (!vttin) return NULL; - /*visual object*/ - switch (expect_type) { - case GF_MEDIA_OBJECT_UNDEF: - case GF_MEDIA_OBJECT_UPDATES: - case GF_MEDIA_OBJECT_TEXT: - { - GF_ObjectDescriptor *od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - //GF_ESD *esd = tti_get_esd(vttin); - //od->objectDescriptorID = esd->ESID; - //gf_list_add(od->ESDescriptors, esd); - //vttin->od_done = 1; - return (GF_Descriptor *) od; - } - default: - return NULL; - } - return NULL; -} - -static GF_Err VTT_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream) -{ - u32 ES_ID; - GF_Err e; - VTTIn *vttin = (VTTIn *)plug->priv; - if (!vttin) return GF_BAD_PARAM; - e = GF_SERVICE_ERROR; - if (!vttin || vttin->channel==channel) goto exit; - - e = GF_STREAM_NOT_FOUND; - ES_ID = 0; - if (strstr(url, "ES_ID")) sscanf(url, "ES_ID=%ud", &ES_ID); - - if (ES_ID==1) { - vttin->channel = channel; - e = GF_OK; - } - -exit: - gf_service_connect_ack(vttin->service, channel, e); - return e; -} - -static GF_Err VTT_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel) -{ - VTTIn *vttin = (VTTIn *)plug->priv; - GF_Err e = GF_STREAM_NOT_FOUND; - - if (!vttin) return GF_BAD_PARAM; - - if (vttin->channel == channel) { - vttin->channel = NULL; - e = GF_OK; - } - gf_service_disconnect_ack(vttin->service, channel, e); - return GF_OK; -} - -static GF_Err VTT_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com) -{ - //VTTIn *vttin = (VTTIn *)plug->priv; - - //if (!vttin) return GF_BAD_PARAM; - //if (!com->base.on_channel) return GF_NOT_SUPPORTED; - //switch (com->command_type) { - //case GF_NET_CHAN_SET_PADDING: - // gf_isom_set_sample_padding(vttin->mp4, vttin->tt_track, com->pad.padding_bytes); - // return GF_OK; - //case GF_NET_CHAN_DURATION: - // com->duration.duration = (Double) (s64) gf_isom_get_media_duration(vttin->mp4, vttin->tt_track); - // com->duration.duration /= gf_isom_get_media_timescale(vttin->mp4, vttin->tt_track); - // return GF_OK; - //case GF_NET_CHAN_PLAY: - // vttin->start_range = (com->play.start_range>0) ? (u32) (com->play.start_range * 1000) : 0; - // if (vttin->channel == com->base.on_channel) { - // vttin->samp_num = 0; - // if (vttin->samp) gf_isom_sample_del(&vttin->samp); - // } - // return GF_OK; - //case GF_NET_CHAN_STOP: - // return GF_OK; - //default: - // return GF_OK; - //} - return GF_OK; -} - -static GF_Err VTT_ChannelGetSLP(GF_InputService *plug, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, GF_SLHeader *out_sl_hdr, Bool *sl_compressed, GF_Err *out_reception_status, Bool *is_new_data) -{ - //VTTIn *vttin = (VTTIn *)plug->priv; - - //*out_reception_status = GF_OK; - //*sl_compressed = GF_FALSE; - //*is_new_data = GF_FALSE; - - //memset(&vttin->sl_hdr, 0, sizeof(GF_SLHeader)); - //vttin->sl_hdr.randomAccessPointFlag = 1; - //vttin->sl_hdr.compositionTimeStampFlag = 1; - //vttin->sl_hdr.accessUnitStartFlag = vttin->sl_hdr.accessUnitEndFlag = 1; - - ///*fetching es data*/ - //if (vttin->channel == channel) { - // if (vttin->samp_num>=gf_isom_get_sample_count(vttin->mp4, vttin->tt_track)) { - // *out_reception_status = GF_EOS; - // return GF_OK; - // } - - // if (!vttin->samp) { - // u32 di; - // if (vttin->start_range) { - // u32 di; - // *out_reception_status = gf_isom_get_sample_for_movie_time(vttin->mp4, vttin->tt_track, vttin->start_range, &di, GF_ISOM_SEARCH_SYNC_BACKWARD, &vttin->samp, &vttin->samp_num); - // vttin->start_range = 0; - // } else { - // vttin->samp = gf_isom_get_sample(vttin->mp4, vttin->tt_track, vttin->samp_num+1, &di); - // } - // if (!vttin->samp) { - // *out_reception_status = GF_CORRUPTED_DATA; - // return GF_OK; - // } - // *is_new_data = 1; - // } - // vttin->sl_hdr.compositionTimeStamp = vttin->sl_hdr.decodingTimeStamp = vttin->samp->DTS; - // *out_data_ptr = vttin->samp->data; - // *out_data_size = vttin->samp->dataLength; - // *out_sl_hdr = vttin->sl_hdr; - // return GF_OK; - //} - return GF_STREAM_NOT_FOUND; -} - -static GF_Err VTT_ChannelReleaseSLP(GF_InputService *plug, LPNETCHANNEL channel) -{ - //VTTIn *vttin = (VTTIn *)plug->priv; - //if (vttin->channel == channel) { - // if (!vttin->samp) return GF_BAD_PARAM; - // gf_isom_sample_del(&vttin->samp); - // vttin->samp = NULL; - // vttin->samp_num++; - // return GF_OK; - //} - return GF_OK; -} - -static void *NewVTTInput() -{ - VTTIn *priv; - GF_InputService *plug; - GF_SAFEALLOC(plug, GF_InputService); - GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC VTT Reader", "gpac distribution") - - plug->RegisterMimeTypes = VTT_RegisterMimeTypes; - plug->CanHandleURL = VTT_CanHandleURL; - plug->CanHandleURLInService = NULL; - plug->ConnectService = VTT_ConnectService; - plug->CloseService = VTT_CloseService; - plug->GetServiceDescriptor = VTT_GetServiceDesc; - plug->ConnectChannel = VTT_ConnectChannel; - plug->DisconnectChannel = VTT_DisconnectChannel; - plug->ChannelGetSLP = VTT_ChannelGetSLP; - plug->ChannelReleaseSLP = VTT_ChannelReleaseSLP; - plug->ServiceCommand = VTT_ServiceCommand; - - GF_SAFEALLOC(priv, VTTIn); - plug->priv = priv; - return plug; -} - -void DeleteVTTInput(void *ifce) -{ - VTTIn *vttin; - GF_InputService *plug = (GF_InputService *)ifce; - if (!plug) - return; - vttin = (VTTIn *)plug->priv; - if (vttin) { - VTT_CloseService(plug); - gf_free(vttin); - } - plug->priv = NULL; - gf_free(plug); -} - - -#endif - diff --git a/modules/wav_out/Makefile b/modules/wav_out/Makefile index 334a6cb..20920bc 100644 --- a/modules/wav_out/Makefile +++ b/modules/wav_out/Makefile @@ -2,14 +2,14 @@ include ../../config.mak vpath %.c $(SRC_PATH)/modules/wav_out -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" -DDISABLE_WAVE_EX +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) -DDISABLE_WAVE_EX -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif diff --git a/modules/wav_out/wav_out.c b/modules/wav_out/wav_out.c index b10fc9f..7c5c1db 100644 --- a/modules/wav_out/wav_out.c +++ b/modules/wav_out/wav_out.c @@ -195,7 +195,7 @@ static void WAV_Shutdown(GF_AudioOutput *dr) /*we assume what was asked is what we got*/ -static GF_Err WAV_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *nbBitsPerSample, u32 channel_cfg) +static GF_Err WAV_Configure(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *audioFormat, u64 channel_cfg) { u32 i, retry; HRESULT hr; @@ -215,11 +215,24 @@ static GF_Err WAV_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbCh if (*NbChannels>2) *NbChannels=2; #endif + //only support for PCM 8/16/24/32 packet mode + switch (*audioFormat) { + case GF_AUDIO_FMT_U8: + case GF_AUDIO_FMT_S16: + case GF_AUDIO_FMT_S24: + case GF_AUDIO_FMT_S32: + break; + default: + //otherwise force PCM16 + *audioFormat = GF_AUDIO_FMT_S16; + break; + } + memset (&ctx->fmt, 0, sizeof(ctx->fmt)); ctx->fmt.cbSize = sizeof(WAVEFORMATEX); ctx->fmt.wFormatTag = WAVE_FORMAT_PCM; ctx->fmt.nChannels = *NbChannels; - ctx->fmt.wBitsPerSample = *nbBitsPerSample; + ctx->fmt.wBitsPerSample = gf_audio_fmt_bit_depth(*audioFormat); ctx->fmt.nSamplesPerSec = *SampleRate; ctx->fmt.nBlockAlign = ctx->fmt.wBitsPerSample * ctx->fmt.nChannels / 8; ctx->fmt.nAvgBytesPerSec = *SampleRate * ctx->fmt.nBlockAlign; @@ -238,11 +251,11 @@ static GF_Err WAV_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbCh if (channel_cfg & GF_AUDIO_CH_FRONT_RIGHT) format_ex.dwChannelMask |= SPEAKER_FRONT_RIGHT; if (channel_cfg & GF_AUDIO_CH_FRONT_CENTER) format_ex.dwChannelMask |= SPEAKER_FRONT_CENTER; if (channel_cfg & GF_AUDIO_CH_LFE) format_ex.dwChannelMask |= SPEAKER_LOW_FREQUENCY; - if (channel_cfg & GF_AUDIO_CH_BACK_LEFT) format_ex.dwChannelMask |= SPEAKER_BACK_LEFT; - if (channel_cfg & GF_AUDIO_CH_BACK_RIGHT) format_ex.dwChannelMask |= SPEAKER_BACK_RIGHT; - if (channel_cfg & GF_AUDIO_CH_BACK_CENTER) format_ex.dwChannelMask |= SPEAKER_BACK_CENTER; - if (channel_cfg & GF_AUDIO_CH_SIDE_LEFT) format_ex.dwChannelMask |= SPEAKER_SIDE_LEFT; - if (channel_cfg & GF_AUDIO_CH_SIDE_RIGHT) format_ex.dwChannelMask |= SPEAKER_SIDE_RIGHT; + if (channel_cfg & GF_AUDIO_CH_SURROUND_LEFT) format_ex.dwChannelMask |= SPEAKER_BACK_LEFT; + if (channel_cfg & GF_AUDIO_CH_SURROUND_RIGHT) format_ex.dwChannelMask |= SPEAKER_BACK_RIGHT; + if (channel_cfg & GF_AUDIO_CH_REAR_CENTER) format_ex.dwChannelMask |= SPEAKER_BACK_CENTER; + if (channel_cfg & GF_AUDIO_CH_SIDE_SURROUND_LEFT) format_ex.dwChannelMask |= SPEAKER_SIDE_LEFT; + if (channel_cfg & GF_AUDIO_CH_SIDE_SURROUND_RIGHT) format_ex.dwChannelMask |= SPEAKER_SIDE_RIGHT; fmt = (WAVEFORMATEX *) &format_ex; } #endif @@ -260,6 +273,8 @@ static GF_Err WAV_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbCh retry--; } if (hr != MMSYSERR_NOERROR) return GF_IO_ERR; + ctx->fmt.nBlockAlign = fmt->nBlockAlign; + ctx->fmt.nAvgBytesPerSec = fmt->nAvgBytesPerSec; if (!ctx->force_config) { /*one wave buffer size*/ @@ -297,7 +312,6 @@ static GF_Err WAV_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbCh static void WAV_WriteAudio(GF_AudioOutput *dr) { LPWAVEHDR hdr; - HRESULT hr; u32 i; WAVCTX(); @@ -320,7 +334,7 @@ static void WAV_WriteAudio(GF_AudioOutput *dr) /*fill it*/ hdr->dwBufferLength = dr->FillBuffer(dr->audio_renderer, hdr->lpData, ctx->buffer_size); hdr->dwFlags = 0; - hr = waveOutPrepareHeader(ctx->hwo, hdr, sizeof(WAVEHDR)); + waveOutPrepareHeader(ctx->hwo, hdr, sizeof(WAVEHDR)); /*write it*/ waveOutWrite(ctx->hwo, hdr, sizeof(WAVEHDR)); } @@ -444,7 +458,7 @@ void *NewWAVRender() driv->SelfThreaded = GF_FALSE; driv->Setup = WAV_Setup; driv->Shutdown = WAV_Shutdown; - driv->ConfigureOutput = WAV_ConfigureOutput; + driv->Configure= WAV_Configure; driv->GetAudioDelay = WAV_GetAudioDelay; driv->GetTotalBufferTime = WAV_GetTotalBufferTime; driv->SetVolume = WAV_SetVolume; diff --git a/modules/x11_out/Makefile b/modules/x11_out/Makefile index 22cc923..841e4e8 100644 --- a/modules/x11_out/Makefile +++ b/modules/x11_out/Makefile @@ -2,14 +2,14 @@ include ../../config.mak vpath %.c $(SRC_PATH)/modules/x11_out -CFLAGS= $(OPTFLAGS) -Wno-deprecated-declarations +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) -Wno-deprecated-declarations -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif @@ -17,28 +17,28 @@ endif CFLAGS+=-I"$(SRC_PATH)/include" -ifeq ($(X11_INC_PATH), ) +ifeq ($(X11_INC_PATH),) else CFLAGS+=-I$(X11_INC_PATH) endif -ifeq ($(X11_LIB_PATH), ) +ifeq ($(X11_LIB_PATH),) else EXTRALIBS+=-L$(X11_LIB_PATH) endif -ifeq ($(USE_X11_XV), yes) +ifeq ($(USE_X11_XV),yes) CFLAGS+=-DGPAC_HAS_X11_XV EXTRALIBS+=-lXv endif -ifeq ($(USE_X11_SHM), yes) +ifeq ($(USE_X11_SHM),yes) CFLAGS+=-DGPAC_HAS_X11_SHM EXTRALIBS+=-lXext endif -ifeq ($(HAS_OPENGL), yes) -ifeq ($(GPAC_USE_TINYGL), yes) +ifeq ($(HAS_OPENGL),yes) +ifeq ($(GPAC_USE_TINYGL),yes) else CFLAGS+=$(OGL_INCLS) EXTRALIBS+=$(OGL_LIBS) diff --git a/modules/x11_out/x11_out.c b/modules/x11_out/x11_out.c index af123dd..5f0c60d 100644 --- a/modules/x11_out/x11_out.c +++ b/modules/x11_out/x11_out.c @@ -2,7 +2,7 @@ * GPAC Multimedia Framework * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2012 + * Copyright (c) Telecom ParisTech 2005-2020 * All rights reserved * * This file is part of GPAC / X11 video output module @@ -25,15 +25,37 @@ #include "x11_out.h" #include <gpac/constants.h> -#include <gpac/unicode.h> +#include <gpac/utf.h> #include <gpac/user.h> #include <sys/time.h> #include <X11/XKBlib.h> #include <X11/Xatom.h> #include <errno.h> +enum +{ + GF_PIXEL_IYUV = GF_4CC('I', 'Y', 'U', 'V'), + GF_PIXEL_I420 = GF_4CC('I', '4', '2', '0'), + /*!YUV packed format*/ + GF_PIXEL_UYNV = GF_4CC('U', 'Y', 'N', 'V'), + /*!YUV packed format*/ + GF_PIXEL_YUNV = GF_4CC('Y', 'U', 'N', 'V'), + /*!YUV packed format*/ + GF_PIXEL_V422 = GF_4CC('V', '4', '2', '2'), + + GF_PIXEL_YV12 = GF_4CC('Y', 'V', '1', '2'), + GF_PIXEL_Y422 = GF_4CC('Y', '4', '2', '2'), + GF_PIXEL_YUY2 = GF_4CC('Y', 'U', 'Y', '2'), +}; + + void X11_SetupWindow (GF_VideoOutput * vout); +#ifdef GPAC_HAS_OPENGL +PFNGLXSWAPINTERVALEXTPROC my_glXSwapIntervalEXT; +PFNGLXSWAPINTERVALMESAPROC my_glXSwapIntervalMESA; +PFNGLXSWAPINTERVALSGIPROC my_glXSwapIntervalSGI; +#endif #ifdef GPAC_HAS_X11_XV static void X11_DestroyOverlay(XWindow *xwin) @@ -50,7 +72,7 @@ static void X11_DestroyOverlay(XWindow *xwin) static Bool is_same_yuv(u32 pf, u32 a_pf) { if (pf==a_pf) return 1; - return 0; +#if 0 switch (pf) { case GF_PIXEL_YV12: case GF_PIXEL_I420: @@ -84,6 +106,7 @@ static Bool is_same_yuv(u32 pf, u32 a_pf) return 0; } } +#endif return 0; } static u32 X11_GetPixelFormat(u32 pf) @@ -165,7 +188,7 @@ static GF_Err X11_InitOverlay(GF_VideoOutput *vout, u32 VideoWidth, u32 VideoHei X11_DestroyOverlay(xwin); - xwin->xvport = X11_GetXVideoPort(vout, GF_PIXEL_I420, 0); + xwin->xvport = X11_GetXVideoPort(vout, GF_PIXEL_YV12, 0); if (xwin->xvport<0) xwin->xvport = X11_GetXVideoPort(vout, GF_PIXEL_YUY2, 0); @@ -208,11 +231,12 @@ GF_Err X11_Blit(struct _video_out *vout, GF_VideoSurface *video_src, GF_Window * if ((xwin->xvport<0) || !xwin->overlay) { e = X11_InitOverlay(vout, video_src->width, video_src->height); if (e) return e; + if (!xwin->overlay) return GF_IO_ERR; } /*different size, recreate an image*/ if ((xwin->overlay->width != video_src->width) || (xwin->overlay->height != video_src->height)) { - if (xwin->overlay) XFree(xwin->overlay); + XFree(xwin->overlay); xwin->overlay = XvCreateImage(xwin->display, xwin->xvport, xwin->xv_pf_format, NULL, video_src->width, video_src->height); if (!xwin->overlay) return GF_IO_ERR; } @@ -261,7 +285,7 @@ GF_Err X11_Flush(struct _video_out *vout, GF_Window * dest) if (!xWindow->is_init) return GF_OK; cur_wnd = xWindow->fullscreen ? xWindow->full_wnd : xWindow->wnd; - if (xWindow->output_3d_mode==1) { + if (xWindow->output_3d) { #ifdef GPAC_HAS_OPENGL XSync(xWindow->display, False); glFlush(); @@ -289,404 +313,179 @@ GF_Err X11_Flush(struct _video_out *vout, GF_Window * dest) return GF_OK; } -//===================================== +typedef struct +{ + u32 x11_key; + u32 gf_key; + u32 gf_flags; +} X11KeyToGPAC; + +static X11KeyToGPAC X11Keys[] = +{ + {XK_BackSpace, GF_KEY_BACKSPACE, 0}, + {XK_Tab, GF_KEY_TAB, 0}, +// {XK_Linefeed, GF_KEY_LINEFEED, 0}, + {XK_Clear, GF_KEY_CLEAR, 0}, + {XK_KP_Enter, GF_KEY_ENTER, GF_KEY_EXT_NUMPAD}, + {XK_Return, GF_KEY_ENTER, 0}, + {XK_Pause, GF_KEY_PAUSE, 0}, + {XK_Caps_Lock, GF_KEY_CAPSLOCK, 0}, + {XK_Scroll_Lock, GF_KEY_SCROLL, 0}, + {XK_Escape, GF_KEY_ESCAPE, 0}, + {XK_Delete, GF_KEY_DEL, 0}, + {XK_Kanji, GF_KEY_KANJIMODE, 0}, + {XK_Katakana, GF_KEY_JAPANESEKATAKANA, 0}, + {XK_Romaji, GF_KEY_JAPANESEROMAJI, 0}, + {XK_Hiragana, GF_KEY_JAPANESEHIRAGANA, 0}, + {XK_Kana_Lock, GF_KEY_KANAMODE, 0}, + {XK_Home, GF_KEY_HOME, 0}, + {XK_Left, GF_KEY_LEFT, 0}, + {XK_Up, GF_KEY_UP, 0}, + {XK_Right, GF_KEY_RIGHT, 0}, + {XK_Down, GF_KEY_DOWN, 0}, + {XK_Page_Up, GF_KEY_PAGEUP, 0}, + {XK_Page_Down, GF_KEY_PAGEDOWN, 0}, + {XK_End, GF_KEY_END, 0}, + //{XK_Begin, GF_KEY_BEGIN, 0}, + {XK_Select, GF_KEY_SELECT, 0}, + {XK_Print, GF_KEY_PRINTSCREEN, 0}, + {XK_Execute, GF_KEY_EXECUTE, 0}, + {XK_Insert, GF_KEY_INSERT, 0}, + {XK_Undo, GF_KEY_UNDO, 0}, + //{XK_Redo, GF_KEY_BEGIN, 0}, + //{XK_Menu, GF_KEY_BEGIN, 0}, + {XK_Find, GF_KEY_FIND, 0}, + {XK_Cancel, GF_KEY_CANCEL, 0}, + {XK_Help, GF_KEY_HELP, 0}, + //{XK_Break, GF_KEY_BREAK, 0}, + //{XK_Mode_switch, GF_KEY_BEGIN, 0}, + {XK_Num_Lock, GF_KEY_NUMLOCK, 0}, + {XK_F1, GF_KEY_F1, 0}, + {XK_F2, GF_KEY_F2, 0}, + {XK_F3, GF_KEY_F3, 0}, + {XK_F4, GF_KEY_F4, 0}, + {XK_F5, GF_KEY_F5, 0}, + {XK_F6, GF_KEY_F6, 0}, + {XK_F7, GF_KEY_F7, 0}, + {XK_F8, GF_KEY_F8, 0}, + {XK_F9, GF_KEY_F9, 0}, + {XK_F10, GF_KEY_F10, 0}, + {XK_F11, GF_KEY_F11, 0}, + {XK_F12, GF_KEY_F12, 0}, + {XK_F13, GF_KEY_F13, 0}, + {XK_F14, GF_KEY_F14, 0}, + {XK_F15, GF_KEY_F15, 0}, + {XK_F16, GF_KEY_F16, 0}, + {XK_F17, GF_KEY_F17, 0}, + {XK_F18, GF_KEY_F18, 0}, + {XK_F19, GF_KEY_F19, 0}, + {XK_F20, GF_KEY_F20, 0}, + {XK_F21, GF_KEY_F21, 0}, + {XK_F22, GF_KEY_F22, 0}, + {XK_F23, GF_KEY_F23, 0}, + {XK_F24, GF_KEY_F24, 0}, + {XK_KP_Delete, GF_KEY_COMMA, GF_KEY_EXT_NUMPAD}, + {XK_KP_Decimal, GF_KEY_COMMA, GF_KEY_EXT_NUMPAD}, + {XK_KP_Insert, GF_KEY_0, GF_KEY_EXT_NUMPAD}, + {XK_KP_0, GF_KEY_0, GF_KEY_EXT_NUMPAD}, + {XK_KP_End, GF_KEY_1, GF_KEY_EXT_NUMPAD}, + {XK_KP_1, GF_KEY_1, GF_KEY_EXT_NUMPAD}, + {XK_KP_Down, GF_KEY_2, GF_KEY_EXT_NUMPAD}, + {XK_KP_2, GF_KEY_2, GF_KEY_EXT_NUMPAD}, + {XK_KP_Page_Down, GF_KEY_3, GF_KEY_EXT_NUMPAD}, + {XK_KP_3, GF_KEY_3, GF_KEY_EXT_NUMPAD}, + {XK_KP_Left, GF_KEY_4, GF_KEY_EXT_NUMPAD}, + {XK_KP_4, GF_KEY_4, GF_KEY_EXT_NUMPAD}, + {XK_KP_Begin, GF_KEY_5, GF_KEY_EXT_NUMPAD}, + {XK_KP_5, GF_KEY_5, GF_KEY_EXT_NUMPAD}, + {XK_KP_Right, GF_KEY_6, GF_KEY_EXT_NUMPAD}, + {XK_KP_6, GF_KEY_6, GF_KEY_EXT_NUMPAD}, + {XK_KP_Home, GF_KEY_7, GF_KEY_EXT_NUMPAD}, + {XK_KP_7, GF_KEY_7, GF_KEY_EXT_NUMPAD}, + {XK_KP_Up, GF_KEY_8, GF_KEY_EXT_NUMPAD}, + {XK_KP_8, GF_KEY_8, GF_KEY_EXT_NUMPAD}, + {XK_KP_Page_Up, GF_KEY_9, GF_KEY_EXT_NUMPAD}, + {XK_KP_9, GF_KEY_9, GF_KEY_EXT_NUMPAD}, + {XK_KP_Add, GF_KEY_PLUS, GF_KEY_EXT_NUMPAD}, + {XK_KP_Subtract, GF_KEY_HYPHEN, GF_KEY_EXT_NUMPAD}, + {XK_KP_Multiply, GF_KEY_STAR, GF_KEY_EXT_NUMPAD}, + {XK_KP_Divide, GF_KEY_SLASH, GF_KEY_EXT_NUMPAD}, + {XK_Shift_R, GF_KEY_EXT_RIGHT, 0}, + {XK_Shift_L, GF_KEY_SHIFT, 0}, + {XK_Control_R, GF_KEY_EXT_RIGHT, 0}, + {XK_Control_L, GF_KEY_CONTROL, 0}, + {XK_Alt_R, GF_KEY_EXT_RIGHT, 0}, + {XK_Alt_L, GF_KEY_ALT, 0}, + {XK_Super_R, GF_KEY_EXT_RIGHT, 0}, + {XK_Super_L, GF_KEY_WIN, 0}, + {XK_Menu, GF_KEY_META, 0}, +#ifdef XK_XKB_KEYS + {XK_ISO_Level3_Shift, GF_KEY_ALTGRAPH, 0}, +#endif + {'!', GF_KEY_EXCLAMATION, 0}, + {'"', GF_KEY_QUOTATION, 0}, + {'#', GF_KEY_NUMBER, 0}, + {'$', GF_KEY_DOLLAR, 0}, + {'&', GF_KEY_AMPERSAND, 0}, + {'\'', GF_KEY_APOSTROPHE, 0}, + {'(', GF_KEY_LEFTPARENTHESIS, 0}, + {')', GF_KEY_RIGHTPARENTHESIS, 0}, + {',', GF_KEY_COMMA, 0}, + {':', GF_KEY_COLON, 0}, + {';', GF_KEY_SEMICOLON, 0}, + {'<', GF_KEY_LESSTHAN, 0}, + {'>', GF_KEY_GREATERTHAN, 0}, + {'?', GF_KEY_QUESTION, 0}, + {'@', GF_KEY_AT, 0}, + {'[', GF_KEY_LEFTSQUAREBRACKET, 0}, + {']', GF_KEY_RIGHTSQUAREBRACKET, 0}, + {'\\', GF_KEY_BACKSLASH, 0}, + {'_', GF_KEY_UNDERSCORE, 0}, + {'`', GF_KEY_GRAVEACCENT, 0}, + {' ', GF_KEY_SPACE, 0}, + {'/', GF_KEY_SLASH, 0}, + {'*', GF_KEY_STAR, 0}, + {'-', GF_KEY_HYPHEN, 0}, + {'+', GF_KEY_PLUS, 0}, + {'=', GF_KEY_EQUALS, 0}, + {'^', GF_KEY_CIRCUM, 0}, + {'{', GF_KEY_LEFTCURLYBRACKET, 0}, + {'}', GF_KEY_RIGHTCURLYBRACKET, 0}, + {'|', GF_KEY_PIPE, 0}, +}; + +static u32 num_x11_keys = sizeof(X11Keys) / sizeof(X11KeyToGPAC); + /* * Translate X_Key to GF_Key */ //===================================== + static void x11_translate_key(u32 X11Key, GF_EventKey *evt) { + u32 i; + evt->flags = 0; evt->hw_code = X11Key & 0xFF; - switch (X11Key) { - - case XK_BackSpace: - evt->key_code = GF_KEY_BACKSPACE; - break; - case XK_Tab: - evt->key_code = GF_KEY_TAB; - break; - //case XK_Linefeed: evt->key_code = GF_KEY_LINEFEED; break; - case XK_Clear: - evt->key_code = GF_KEY_CLEAR; - break; - - case XK_KP_Enter: - evt->flags = GF_KEY_EXT_NUMPAD; - case XK_Return: - evt->key_code = GF_KEY_ENTER; - break; - case XK_Pause: - evt->key_code = GF_KEY_PAUSE; - break; - case XK_Caps_Lock: - evt->key_code = GF_KEY_CAPSLOCK; - break; - case XK_Scroll_Lock: - evt->key_code = GF_KEY_SCROLL; - break; - case XK_Escape: - evt->key_code = GF_KEY_ESCAPE; - break; - case XK_Delete: - evt->key_code = GF_KEY_DEL; - break; - - case XK_Kanji: - evt->key_code = GF_KEY_KANJIMODE; - break; - case XK_Katakana: - evt->key_code = GF_KEY_JAPANESEKATAKANA; - break; - case XK_Romaji: - evt->key_code = GF_KEY_JAPANESEROMAJI; - break; - case XK_Hiragana: - evt->key_code = GF_KEY_JAPANESEHIRAGANA; - break; - case XK_Kana_Lock: - evt->key_code = GF_KEY_KANAMODE; - break; - - case XK_Home: - evt->key_code = GF_KEY_HOME; - break; - case XK_Left: - evt->key_code = GF_KEY_LEFT; - break; - case XK_Up: - evt->key_code = GF_KEY_UP; - break; - case XK_Right: - evt->key_code = GF_KEY_RIGHT; - break; - case XK_Down: - evt->key_code = GF_KEY_DOWN; - break; - case XK_Page_Up: - evt->key_code = GF_KEY_PAGEUP; - break; - case XK_Page_Down: - evt->key_code = GF_KEY_PAGEDOWN; - break; - case XK_End: - evt->key_code = GF_KEY_END; - break; - //case XK_Begin: evt->key_code = GF_KEY_BEGIN; break; - - - case XK_Select: - evt->key_code = GF_KEY_SELECT; - break; - case XK_Print: - evt->key_code = GF_KEY_PRINTSCREEN; - break; - case XK_Execute: - evt->key_code = GF_KEY_EXECUTE; - break; - case XK_Insert: - evt->key_code = GF_KEY_INSERT; - break; - case XK_Undo: - evt->key_code = GF_KEY_UNDO; - break; - //case XK_Redo: evt->key_code = GF_KEY_BEGIN; break; - //case XK_Menu: evt->key_code = GF_KEY_BEGIN; break; - case XK_Find: - evt->key_code = GF_KEY_FIND; - break; - case XK_Cancel: - evt->key_code = GF_KEY_CANCEL; - break; - case XK_Help: - evt->key_code = GF_KEY_HELP; - break; - //case XK_Break: evt->key_code = GF_KEY_BREAK; break; - //case XK_Mode_switch: evt->key_code = GF_KEY_BEGIN; break; - case XK_Num_Lock: - evt->key_code = GF_KEY_NUMLOCK; - break; - - case XK_F1: - evt->key_code = GF_KEY_F1; - break; - case XK_F2: - evt->key_code = GF_KEY_F2; - break; - case XK_F3: - evt->key_code = GF_KEY_F3; - break; - case XK_F4: - evt->key_code = GF_KEY_F4; - break; - case XK_F5: - evt->key_code = GF_KEY_F5; - break; - case XK_F6: - evt->key_code = GF_KEY_F6; - break; - case XK_F7: - evt->key_code = GF_KEY_F7; - break; - case XK_F8: - evt->key_code = GF_KEY_F8; - break; - case XK_F9: - evt->key_code = GF_KEY_F9; - break; - case XK_F10: - evt->key_code = GF_KEY_F10; - break; - case XK_F11: - evt->key_code = GF_KEY_F11; - break; - case XK_F12: - evt->key_code = GF_KEY_F12; - break; - case XK_F13: - evt->key_code = GF_KEY_F13; - break; - case XK_F14: - evt->key_code = GF_KEY_F14; - break; - case XK_F15: - evt->key_code = GF_KEY_F15; - break; - case XK_F16: - evt->key_code = GF_KEY_F16; - break; - case XK_F17: - evt->key_code = GF_KEY_F17; - break; - case XK_F18: - evt->key_code = GF_KEY_F18; - break; - case XK_F19: - evt->key_code = GF_KEY_F19; - break; - case XK_F20: - evt->key_code = GF_KEY_F20; - break; - case XK_F21: - evt->key_code = GF_KEY_F21; - break; - case XK_F22: - evt->key_code = GF_KEY_F22; - break; - case XK_F23: - evt->key_code = GF_KEY_F23; - break; - case XK_F24: - evt->key_code = GF_KEY_F24; - break; - - case XK_KP_Delete: - case XK_KP_Decimal: - evt->flags = GF_KEY_EXT_NUMPAD; - evt->key_code = GF_KEY_COMMA; - break; - - case XK_KP_Insert: - case XK_KP_0: - evt->flags = GF_KEY_EXT_NUMPAD; - evt->key_code = GF_KEY_0; - break; - case XK_KP_End: - case XK_KP_1: - evt->flags = GF_KEY_EXT_NUMPAD; - evt->key_code = GF_KEY_1; - break; - case XK_KP_Down: - case XK_KP_2: - evt->flags = GF_KEY_EXT_NUMPAD; - evt->key_code = GF_KEY_2; - break; - case XK_KP_Page_Down: - case XK_KP_3: - evt->flags = GF_KEY_EXT_NUMPAD; - evt->key_code = GF_KEY_3; - break; - case XK_KP_Left: - case XK_KP_4: - evt->flags = GF_KEY_EXT_NUMPAD; - evt->key_code = GF_KEY_4; - break; - case XK_KP_Begin: - case XK_KP_5: - evt->flags = GF_KEY_EXT_NUMPAD; - evt->key_code = GF_KEY_5; - break; - case XK_KP_Right: - case XK_KP_6: - evt->flags = GF_KEY_EXT_NUMPAD; - evt->key_code = GF_KEY_6; - break; - case XK_KP_Home: - case XK_KP_7: - evt->flags = GF_KEY_EXT_NUMPAD; - evt->key_code = GF_KEY_7; - break; - case XK_KP_Up: - case XK_KP_8: - evt->flags = GF_KEY_EXT_NUMPAD; - evt->key_code = GF_KEY_8; - break; - case XK_KP_Page_Up: - case XK_KP_9: - evt->flags = GF_KEY_EXT_NUMPAD; - evt->key_code = GF_KEY_9; - break; - case XK_KP_Add: - evt->flags = GF_KEY_EXT_NUMPAD; - evt->key_code = GF_KEY_PLUS; - break; - case XK_KP_Subtract: - evt->flags = GF_KEY_EXT_NUMPAD; - evt->key_code = GF_KEY_HYPHEN; - break; - case XK_KP_Multiply: - evt->flags = GF_KEY_EXT_NUMPAD; - evt->key_code = GF_KEY_STAR; - break; - case XK_KP_Divide: - evt->flags = GF_KEY_EXT_NUMPAD; - evt->key_code = GF_KEY_SLASH; - break; - - - case XK_Shift_R: - evt->flags = GF_KEY_EXT_RIGHT; - case XK_Shift_L: - evt->key_code = GF_KEY_SHIFT; - break; - case XK_Control_R: - evt->flags = GF_KEY_EXT_RIGHT; - case XK_Control_L: - evt->key_code = GF_KEY_CONTROL; - break; - case XK_Alt_R: - evt->flags = GF_KEY_EXT_RIGHT; - case XK_Alt_L: - evt->key_code = GF_KEY_ALT; - break; - case XK_Super_R: - evt->flags = GF_KEY_EXT_RIGHT; - case XK_Super_L: - evt->key_code = GF_KEY_WIN; - break; - - case XK_Menu: - evt->key_code = GF_KEY_META; - break; -#ifdef XK_XKB_KEYS - case XK_ISO_Level3_Shift: - evt->key_code = GF_KEY_ALTGRAPH; - break; - -#endif - case '!': - evt->key_code = GF_KEY_EXCLAMATION; - break; - case '"': - evt->key_code = GF_KEY_QUOTATION; - break; - case '#': - evt->key_code = GF_KEY_NUMBER; - break; - case '$': - evt->key_code = GF_KEY_DOLLAR; - break; - case '&': - evt->key_code = GF_KEY_AMPERSAND; - break; - case '\'': - evt->key_code = GF_KEY_APOSTROPHE; - break; - case '(': - evt->key_code = GF_KEY_LEFTPARENTHESIS; - break; - case ')': - evt->key_code = GF_KEY_RIGHTPARENTHESIS; - break; - case ',': - evt->key_code = GF_KEY_COMMA; - break; - case ':': - evt->key_code = GF_KEY_COLON; - break; - case ';': - evt->key_code = GF_KEY_SEMICOLON; - break; - case '<': - evt->key_code = GF_KEY_LESSTHAN; - break; - case '>': - evt->key_code = GF_KEY_GREATERTHAN; - break; - case '?': - evt->key_code = GF_KEY_QUESTION; - break; - case '@': - evt->key_code = GF_KEY_AT; - break; - case '[': - evt->key_code = GF_KEY_LEFTSQUAREBRACKET; - break; - case ']': - evt->key_code = GF_KEY_RIGHTSQUAREBRACKET; - break; - case '\\': - evt->key_code = GF_KEY_BACKSLASH; - break; - case '_': - evt->key_code = GF_KEY_UNDERSCORE; - break; - case '`': - evt->key_code = GF_KEY_GRAVEACCENT; - break; - case ' ': - evt->key_code = GF_KEY_SPACE; - break; - case '/': - evt->key_code = GF_KEY_SLASH; - break; - case '*': - evt->key_code = GF_KEY_STAR; - break; - case '-': - evt->key_code = GF_KEY_HYPHEN; - break; - case '+': - evt->key_code = GF_KEY_PLUS; - break; - case '=': - evt->key_code = GF_KEY_EQUALS; - break; - case '^': - evt->key_code = GF_KEY_CIRCUM; - break; - case '{': - evt->key_code = GF_KEY_LEFTCURLYBRACKET; - break; - case '}': - evt->key_code = GF_KEY_RIGHTCURLYBRACKET; - break; - case '|': - evt->key_code = GF_KEY_PIPE; - break; - default: - if ((X11Key>='0') && (X11Key<='9')) evt->key_code = GF_KEY_0 + X11Key - '0'; - else if ((X11Key>='A') && (X11Key<='Z')) evt->key_code = GF_KEY_A + X11Key - 'A'; - /*DOM3: translate to A -> Z, not a -> z*/ - else if ((X11Key>='a') && (X11Key<='z')) { - evt->key_code = GF_KEY_A + X11Key - 'a'; - evt->hw_code = X11Key - 'a' + 'A'; + for (i=0; i<num_x11_keys; i++) { + if (X11Key == X11Keys[i].x11_key) { + evt->key_code = X11Keys[i].gf_key; + evt->flags = X11Keys[i].gf_flags; + return; } - else { - evt->key_code = 0; - GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[X11] Unrecognized key %X\n", X11Key)); - } - break; + } + + if ((X11Key>='0') && (X11Key<='9')) + evt->key_code = GF_KEY_0 + X11Key - '0'; + else if ((X11Key>='A') && (X11Key<='Z')) + evt->key_code = GF_KEY_A + X11Key - 'A'; + /*DOM3: translate to A -> Z, not a -> z*/ + else if ((X11Key>='a') && (X11Key<='z')) { + evt->key_code = GF_KEY_A + X11Key - 'a'; + evt->hw_code = X11Key - 'a' + 'A'; + } else { + evt->key_code = GF_KEY_UNIDENTIFIED; + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[X11] Unrecognized key %X\n", X11Key)); } } @@ -750,7 +549,7 @@ static void X11_HandleEvents(GF_VideoOutput *vout) xWindow->w_height = evt.size.height = xevent.xconfigure.height; vout->on_event(vout->evt_cbk_hdl, &evt); } else { - evt.type = GF_EVENT_MOVE_NOTIF; + evt.type = GF_EVENT_MOVE; evt.move.x = xevent.xconfigure.x; evt.move.y = xevent.xconfigure.y; vout->on_event(vout->evt_cbk_hdl, &evt); @@ -820,7 +619,7 @@ static void X11_HandleEvents(GF_VideoOutput *vout) memcpy(text, data, nb_bytes); text[nb_bytes] = 0; evt.type = GF_EVENT_PASTE_TEXT; - evt.message.message = (const char *) text; + evt.clipboard.text = text; vout->on_event(vout->evt_cbk_hdl, &evt); gf_free(text); } @@ -832,9 +631,9 @@ static void X11_HandleEvents(GF_VideoOutput *vout) Atom clipb_atom = XInternAtom(xWindow->display, "CLIPBOARD", 0); evt.type = GF_EVENT_COPY_TEXT; if (vout->on_event(vout->evt_cbk_hdl, &evt)==GF_TRUE) { - const char *txt = evt.message.message; - if (txt) { - XChangeProperty(xWindow->display, DefaultRootWindow(xWindow->display), XA_CUT_BUFFER0, XA_STRING, 8, PropModeReplace, (const unsigned char *)txt, strlen(txt)); + if (evt.clipboard.text) { + XChangeProperty(xWindow->display, DefaultRootWindow(xWindow->display), XA_CUT_BUFFER0, XA_STRING, 8, PropModeReplace, (const unsigned char *)evt.clipboard.text, strlen(evt.clipboard.text)); + gf_free(evt.clipboard.text); } if ((clipb_atom != None) && XGetSelectionOwner(xWindow->display, clipb_atom) != the_window) { @@ -939,11 +738,29 @@ static GF_Err X11_SetupGL(GF_VideoOutput *vout) xWin->glx_context = glXCreateContext(xWin->display,xWin->glx_visualinfo, NULL, True); XSync(xWin->display, False); if (!xWin->glx_context) return GF_IO_ERR; - if (xWin->output_3d_mode==2) return GF_IO_ERR; evt.setup.hw_reset = 1; } if ( ! glXMakeCurrent(xWin->display, xWin->fullscreen ? xWin->full_wnd : xWin->wnd, xWin->glx_context) ) return GF_IO_ERR; + +#ifdef GPAC_HAS_OPENGL + if (gf_opts_get_bool("core", "disable-vsync")) { + my_glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddress( (const GLubyte*)"glXSwapIntervalEXT"); + if (my_glXSwapIntervalEXT != NULL) { + my_glXSwapIntervalEXT(xWin->display, xWin->wnd, 0); + } else { + my_glXSwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC)glXGetProcAddress( (const GLubyte*)"glXSwapIntervalMESA"); + if (my_glXSwapIntervalMESA != NULL ) { + my_glXSwapIntervalMESA(0); + } else { + my_glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddress( (const GLubyte*)"glXSwapIntervalSGI"); + if (my_glXSwapIntervalSGI != NULL ) { + my_glXSwapIntervalSGI(0); + } + } + } + } +#endif XSync(xWin->display, False); evt.type = GF_EVENT_VIDEO_SETUP; @@ -952,61 +769,6 @@ static GF_Err X11_SetupGL(GF_VideoOutput *vout) return GF_OK; } -static GF_Err X11_SetupGLPixmap(GF_VideoOutput *vout, u32 width, u32 height) -{ - XWindow *xWin = (XWindow *)vout->opaque; - - if (xWin->glx_context) { - if (xWin->gl_offscreen) glXMakeCurrent(xWin->display, xWin->gl_offscreen, NULL); - else glXMakeCurrent(xWin->display, None, NULL); - - glXDestroyContext(xWin->display, xWin->glx_context); - xWin->glx_context = NULL; - } - if (xWin->gl_offscreen) glXDestroyGLXPixmap(xWin->display, xWin->gl_offscreen); - xWin->gl_offscreen = 0; - if (xWin->gl_pixmap) XFreePixmap(xWin->display, xWin->gl_pixmap); - xWin->gl_pixmap = 0; - - if (xWin->offscreen_type) { - GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[X11] Using offscreen GL context through XWindow\n")); - if (xWin->offscreen_type==2) { - XSync(xWin->display, False); - XMapWindow (xWin->display, (Window) xWin->gl_wnd); - XSync(xWin->display, False); -// XSetWMNormalHints (xWin->display, xWin->gl_wnd, Hints); - XStoreName (xWin->display, xWin->gl_wnd, "GPAC Offscreeen Window - debug mode"); - } - - XSync(xWin->display, False); - xWin->glx_context = glXCreateContext(xWin->display,xWin->glx_visualinfo, NULL, True); - XSync(xWin->display, False); - if (!xWin->glx_context) return GF_IO_ERR; - if ( ! glXMakeCurrent(xWin->display, xWin->gl_wnd, xWin->glx_context) ) return GF_IO_ERR; - XSync(xWin->display, False); - - } else { - GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[X11] Using offscreen GL context through glXPixmap\n")); - xWin->gl_pixmap = XCreatePixmap(xWin->display, xWin->gl_wnd, width, height, xWin->depth); - if (!xWin->gl_pixmap) return GF_IO_ERR; - - xWin->gl_offscreen = glXCreateGLXPixmap(xWin->display, xWin->glx_visualinfo, xWin->gl_pixmap); - if (!xWin->gl_offscreen) return GF_IO_ERR; - - XSync(xWin->display, False); - xWin->glx_context = glXCreateContext(xWin->display, xWin->glx_visualinfo, 0, GL_FALSE); - XSync(xWin->display, False); - if (!xWin->glx_context) return GF_IO_ERR; - - XSync(xWin->display, False); - GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[X11] Activating GLContext on GLPixmap - this may crash !!\n")); - glXMakeCurrent(xWin->display, xWin->gl_offscreen, xWin->glx_context); - } - - GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[X11] Offscreen GL context setup\n")); - return GF_OK; -} - static void X11_ReleaseGL(XWindow *xWin) { XSync(xWin->display, False); @@ -1064,7 +826,9 @@ static void X11_ReleaseBackBuffer (GF_VideoOutput * vout) */ GF_Err X11_InitBackBuffer (GF_VideoOutput * vout, u32 VideoWidth, u32 VideoHeight) { +#ifdef GPAC_HAS_X11_SHM Window cur_wnd; +#endif u32 size; VideoWidth = VideoWidth > 32 ? VideoWidth : 32; VideoWidth = VideoWidth < 4096 ? VideoWidth : 4096; @@ -1081,9 +845,9 @@ GF_Err X11_InitBackBuffer (GF_VideoOutput * vout, u32 VideoWidth, u32 VideoHeigh VideoWidth++; size = VideoWidth * VideoHeight * xWindow->bpp; - cur_wnd = xWindow->fullscreen ? xWindow->full_wnd : xWindow->wnd; #ifdef GPAC_HAS_X11_SHM + cur_wnd = xWindow->fullscreen ? xWindow->full_wnd : xWindow->wnd; /*if we're using YUV blit to offscreen, we must use a pixmap*/ if (vout->hw_caps & GF_VIDEO_HW_HAS_YUV) { GF_SAFEALLOC(xWindow->shmseginfo, XShmSegmentInfo); @@ -1196,21 +960,30 @@ GF_Err X11_ProcessEvent (struct _video_out * vout, GF_Event * evt) } break; case GF_EVENT_VIDEO_SETUP: - switch (evt->setup.opengl_mode) { + if (evt->setup.use_opengl) { #ifdef GPAC_HAS_OPENGL - case 1: - xWindow->output_3d_mode = 1; + xWindow->output_3d = GF_TRUE; return X11_SetupGL(vout); - case 2: - xWindow->output_3d_mode = 2; - return X11_SetupGLPixmap(vout, evt->setup.width, evt->setup.height); +#else + return GF_NOT_SUPPORTED; #endif - case 0: - xWindow->output_3d_mode = 0; + } else { + xWindow->output_3d = GF_FALSE; return X11_ResizeBackBuffer(vout, evt->setup.width, evt->setup.height); - default: - return GF_NOT_SUPPORTED; } + break; + case GF_EVENT_SET_GL: + if (!xWindow->output_3d) return GF_OK; + +#ifdef GPAC_HAS_OPENGL + if ( ! glXMakeCurrent(xWindow->display, xWindow->fullscreen ? xWindow->full_wnd : xWindow->wnd, xWindow->glx_context) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[X11] Cannot make context current\n")); + return GF_IO_ERR; + } +#else + return GF_NOT_SUPPORTED; +#endif + break; } } else { X11_HandleEvents(vout); @@ -1225,7 +998,7 @@ GF_Err X11_SetFullScreen (struct _video_out * vout, u32 bFullScreenOn, u32 * scr X11VID (); xWindow->fullscreen = bFullScreenOn; #ifdef GPAC_HAS_OPENGL -// if (xWindow->output_3d_mode==1) X11_ReleaseGL(xWindow); +// if (xWindow->output_3d) X11_ReleaseGL(xWindow); #endif if (bFullScreenOn) { @@ -1263,7 +1036,7 @@ GF_Err X11_SetFullScreen (struct _video_out * vout, u32 bFullScreenOn, u32 * scr /*backbuffer resize will be done right after this is called */ } #ifdef GPAC_HAS_OPENGL - if (xWindow->output_3d_mode==1) X11_SetupGL(vout); + if (xWindow->output_3d) X11_SetupGL(vout); #endif return GF_OK; } @@ -1309,6 +1082,8 @@ static int X11_BadAccess_ByPass(Display * display, XErrorEvent * event) { char msg[60]; + if (!display || !event) return 0; + if (event->error_code == BadAccess) { selectinput_err = 1; @@ -1327,7 +1102,6 @@ static int X11_BadAccess_ByPass(Display * display, static void X11_XScreenSaverState(XWindow *xwin, Bool turn_on) { - return; if (turn_on) { if (!xwin->ss_t) return; XSetScreenSaver(xwin->display, xwin->ss_t, xwin->ss_i, xwin->ss_b, xwin->ss_e); @@ -1346,7 +1120,10 @@ void X11_SetupWindow (GF_VideoOutput * vout) { X11VID (); +#if defined(GPAC_HAS_X11_SHM) || defined(GPAC_HAS_X11_XV) || defined(GPAC_HAS_OPENGL) const char *sOpt; +#endif + Bool autorepeat, supported; if (xWindow->setup_done) return; @@ -1381,7 +1158,17 @@ X11_SetupWindow (GF_VideoOutput * vout) xWindow->pixel_format = GF_PIXEL_RGB_565; break; case 24: - xWindow->pixel_format = GF_PIXEL_RGB_32; +#ifdef GPAC_BIG_ENDIAN + if (xWindow->visual->red_mask==0x00FF0000) + xWindow->pixel_format = GF_PIXEL_RGB; + else + xWindow->pixel_format = GF_PIXEL_BGR; +#else + if (xWindow->visual->red_mask==0x00FF0000) + xWindow->pixel_format = GF_PIXEL_BGR; + else + xWindow->pixel_format = GF_PIXEL_RGB; +#endif break; default: xWindow->pixel_format = GF_PIXEL_GREYSCALE; @@ -1502,8 +1289,8 @@ X11_SetupWindow (GF_VideoOutput * vout) xWindow->use_shared_memory = 0; #ifdef GPAC_HAS_X11_SHM - sOpt = gf_modules_get_option((GF_BaseInterface *)vout, "Video", "UseHardwareMemory"); - if (sOpt && !strcmp(sOpt, "yes")) { + sOpt = gf_opts_get_key("core", "hwvmem"); + if (!sOpt || strcmp(sOpt, "no")) { int XShmMajor, XShmMinor; Bool XShmPixmaps; if (XShmQueryVersion(xWindow->display, &XShmMajor, &XShmMinor, &XShmPixmaps)) { @@ -1518,15 +1305,15 @@ X11_SetupWindow (GF_VideoOutput * vout) #endif #ifdef GPAC_HAS_X11_XV - sOpt = gf_modules_get_option((GF_BaseInterface *)vout, "Video", "DisableColorKeying"); + sOpt = gf_opts_get_key("core", "no-colorkey"); if (sOpt && !strcmp(sOpt, "yes")) { - xWindow->xvport = X11_GetXVideoPort(vout, GF_PIXEL_I420, 0); + xWindow->xvport = X11_GetXVideoPort(vout, GF_PIXEL_YV12, 0); } else { - xWindow->xvport = X11_GetXVideoPort(vout, GF_PIXEL_I420, 1); + xWindow->xvport = X11_GetXVideoPort(vout, GF_PIXEL_YV12, 1); if (xWindow->xvport<0) { GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[X11] Hardware has no color keying\n")); vout->overlay_color_key = 0; - xWindow->xvport = X11_GetXVideoPort(vout, GF_PIXEL_I420, 0); + xWindow->xvport = X11_GetXVideoPort(vout, GF_PIXEL_YV12, 0); } else { GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[X11] Hardware uses color key %08x\n", vout->overlay_color_key)); } @@ -1541,8 +1328,7 @@ X11_SetupWindow (GF_VideoOutput * vout) #ifdef GPAC_HAS_X11_SHM /*if user asked for YUV->RGB on offscreen, do it (it may crash the system)*/ - sOpt = gf_modules_get_option((GF_BaseInterface *)vout, "Video", "EnableOffscreenYUV"); - if (sOpt && !strcmp(sOpt, "yes")) { + if (gf_opts_get_bool("core", "offscreen-yuv")) { vout->hw_caps |= GF_VIDEO_HW_HAS_YUV; GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[X11] Using XV Offscreen YUV2RGB acceleration\n")); } @@ -1588,11 +1374,11 @@ X11_SetupWindow (GF_VideoOutput * vout) int attribs[64]; int i, nb_bits, nb_depth_bits; - sOpt = gf_modules_get_option((GF_BaseInterface *)vout, "Video", "GLNbBitsPerComponent"); + sOpt = gf_opts_get_key("core", "gl-bits-comp"); /* Most outputs are 24/32 bits these days, use 8 bits per channel instead of 5, works better on MacOS X */ nb_bits = sOpt ? atoi(sOpt) : 8; if (!sOpt) { - gf_modules_set_option((GF_BaseInterface *)vout, "Video", "GLNbBitsPerComponent", "8"); + gf_opts_set_key("core", "gl-bits-comp", "8"); } retry_8bpp: i=0; @@ -1615,18 +1401,18 @@ retry_8bpp: attribs[i++] = 2; } - sOpt = gf_modules_get_option((GF_BaseInterface *)vout, "Video", "GLNbBitsDepth"); + sOpt = gf_opts_get_key("core", "gl-bits-depth"); nb_depth_bits = sOpt ? atoi(sOpt) : 16; if (!sOpt) { - gf_modules_set_option((GF_BaseInterface *)vout, "Video", "GLNbBitsDepth", "16"); + gf_opts_set_key("core", "gl-bits-depth", "16"); } if (nb_bits) { attribs[i++] = GLX_DEPTH_SIZE; attribs[i++] = nb_depth_bits; } - sOpt = gf_modules_get_option((GF_BaseInterface *)vout, "Video", "UseGLDoubleBuffering"); + sOpt = gf_opts_get_key("core", "gl-doublebuf"); if (!sOpt) { - gf_modules_set_option((GF_BaseInterface *)vout, "Video", "UseGLDoubleBuffering", "yes"); + gf_opts_set_key("core", "gl-doublebuf", "yes"); } if (!sOpt || !strcmp(sOpt, "yes")) { attribs[i++] = GLX_DOUBLEBUFFER; @@ -1655,13 +1441,14 @@ retry_8bpp: nb_bits = 8; goto retry_8bpp; } - xWindow->glx_visualinfo = my_glXGetVisualFromFBConfig(xWindow->display, fb[0]); + if (my_glXGetVisualFromFBConfig) + xWindow->glx_visualinfo = my_glXGetVisualFromFBConfig(xWindow->display, fb[0]); if (my_glXGetFBConfigAttrib && fb) { int r, g, b; - glXGetFBConfigAttrib(xWindow->display, fb[0], GLX_RED_SIZE, &r); - glXGetFBConfigAttrib(xWindow->display, fb[0], GLX_GREEN_SIZE, &g); - glXGetFBConfigAttrib(xWindow->display, fb[0], GLX_BLUE_SIZE, &b); + my_glXGetFBConfigAttrib(xWindow->display, fb[0], GLX_RED_SIZE, &r); + my_glXGetFBConfigAttrib(xWindow->display, fb[0], GLX_GREEN_SIZE, &g); + my_glXGetFBConfigAttrib(xWindow->display, fb[0], GLX_BLUE_SIZE, &b); GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[GLX] Configured display asked %d bits got r:%d g:%d b:%d bits\n", nb_bits, r, g, b)); } } else { @@ -1686,9 +1473,9 @@ retry_8bpp: XUnmapWindow(xWindow->display, (Window) xWindow->gl_wnd); XSync(xWindow->display, False); - sOpt = gf_modules_get_option((GF_BaseInterface *)vout, "Video", "X113DOffscreenMode"); + sOpt = gf_opts_get_key("core", "gl-offscreen"); if (!sOpt) - gf_modules_set_option((GF_BaseInterface *)vout, "Video", "X113DOffscreenMode", "Pixmap"); + gf_opts_set_key("core", "gl-offscreen", "Pixmap"); if (sOpt && !strcmp(sOpt, "Window")) { xWindow->offscreen_type = 1; } else if (sOpt && !strcmp(sOpt, "VisibleWindow")) { @@ -1715,6 +1502,13 @@ GF_Err X11_Setup(struct _video_out *vout, void *os_handle, void *os_display, u32 xWindow->par_wnd = (Window) os_handle; xWindow->init_flags = flags; + //window already setup and this restup asks for visible, show window + if (xWindow->wnd) { + if (!(xWindow->init_flags & GF_TERM_INIT_HIDE)) { + XMapWindow (xWindow->display, (Window) xWindow->wnd); + } + } + /*OSMOZILLA HACK*/ if (os_display) xWindow->no_select_input = 1; @@ -1782,6 +1576,12 @@ void *NewX11VideoOutput () driv->hw_caps = GF_VIDEO_HW_OPENGL; /*fixme - needs a better detection scheme*/ driv->hw_caps |= GF_VIDEO_HW_OPENGL_OFFSCREEN | GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA; + + if (gf_sys_is_test_mode()) { + GF_Event evt; + x11_translate_key(XK_BackSpace, &evt.key); + X11_BadAccess_ByPass(NULL, NULL); + } return (void *) driv; } @@ -1806,7 +1606,7 @@ const u32 *QueryInterfaces() }; return si; } - + /* * interface create diff --git a/modules/x11_out/x11_out.h b/modules/x11_out/x11_out.h index fd516f4..dfde0f8 100644 --- a/modules/x11_out/x11_out.h +++ b/modules/x11_out/x11_out.h @@ -2,7 +2,7 @@ * GPAC Multimedia Framework * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2012 + * Copyright (c) Telecom ParisTech 2005-2020 * All rights reserved * * This file is part of GPAC / X11 video output module @@ -111,7 +111,7 @@ typedef struct u32 w_width, w_height; u32 depth, bpp, pixel_format; - u32 output_3d_mode; + Bool output_3d; #ifdef GPAC_HAS_OPENGL XVisualInfo *glx_visualinfo; diff --git a/modules/xvid_dec/Makefile b/modules/xvid_dec/Makefile deleted file mode 100644 index 2b59689..0000000 --- a/modules/xvid_dec/Makefile +++ /dev/null @@ -1,61 +0,0 @@ -include ../../config.mak - -vpath %.c $(SRC_PATH)/modules/xvid_dec - -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" - -ifeq ($(DEBUGBUILD), yes) -CFLAGS+=-g -LDFLAGS+=-g -endif - -ifeq ($(GPROFBUILD), yes) -CFLAGS+=-pg -LDFLAGS+=-pg -endif - -#common obj -OBJS=xvid_dec.o - -#local xvid lib -ifeq ($(CONFIG_XVID), local) -CFLAGS+= -I"$(LOCAL_INC_PATH)/xvid" -EXTRALIBS+= -L../../extra_lib/lib/gcc -endif -EXTRALIBS+= -lxvidcore -lpthread - -SRCS := $(OBJS:.o=.c) - -LIB=gm_xvid_dec$(DYN_LIB_SUFFIX) -ifeq ($(CONFIG_WIN32),yes) -#LDFLAGS+=-export-symbols xvid_dec.def -endif - - -ifneq ($(prefix), /usr/local) -EXTRALIBS+=-L$(prefix)/lib -else -ifneq ($(prefix), /usr) -EXTRALIBS+=-L$(prefix)/lib -endif -endif - -all: $(LIB) - - -$(LIB): $(OBJS) - $(CC) $(SHFLAGS) -o ../../bin/gcc/$@ $(OBJS) -L../../bin/gcc -lgpac $(EXTRALIBS) $(LDFLAGS) - -clean: - rm -f $(OBJS) ../../bin/gcc/$(LIB) - -dep: depend - -depend: - rm -f .depend - $(CC) -MM $(CFLAGS) $(SRCS) 1>.depend - -distclean: clean - rm -f Makefile.bak .depend - --include .depend diff --git a/modules/xvid_dec/xvid_dec.c b/modules/xvid_dec/xvid_dec.c deleted file mode 100644 index 8ebc8bb..0000000 --- a/modules/xvid_dec/xvid_dec.c +++ /dev/null @@ -1,515 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / codec pack module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include <gpac/modules/codec.h> -#include <gpac/avparse.h> -#include <gpac/constants.h> - -/*if we don't have M4V (A)SP parser, we con't get width and height and xvid is then unusable ...*/ -#ifndef GPAC_DISABLE_AV_PARSERS - -#if !defined(__GNUC__) -# if defined(_WIN32_WCE) || defined (WIN32) -# pragma comment(lib, "libxvidcore") -# endif -#endif - - -#include <xvid.h> - -#ifndef XVID_DEC_FRAME -#define XVID_DEC_FRAME xvid_dec_frame_t -#define XVID_DEC_PARAM xvid_dec_create_t -#else -#define XVID_USE_OLD_API -#endif - -#undef XVID_USE_OLD_API - -static Bool xvid_is_init = GF_FALSE; - -typedef struct -{ - void *base_codec; - u16 base_ES_ID; - - u32 width, height, out_size, pixel_ar; - Bool first_frame; - s32 base_filters; - Float FPS; - u32 offset; - - /*demo for depth coding (auxiliary video data): the codec is pseudo-scalable with the depth data carried in - another stream - this is not very elegant ...*/ - void *depth_codec; - u16 depth_ES_ID; - char *temp_uv; - u32 yuv_size; -} XVIDDec; - -#define XVIDCTX() XVIDDec *ctx = (XVIDDec *) ifcg->privateStack - - -static GF_Err XVID_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ - GF_M4VDecSpecInfo dsi; - GF_Err e; - void **codec; -#ifdef XVID_USE_OLD_API - XVID_DEC_FRAME frame; - XVID_DEC_PARAM par; -#else - xvid_dec_frame_t frame; - xvid_dec_create_t par; -#endif - - XVIDCTX(); - - if (!esd->decoderConfig->decoderSpecificInfo || !esd->decoderConfig->decoderSpecificInfo->data) return GF_NON_COMPLIANT_BITSTREAM; - - /*locate any auxiliary video data descriptor on this stream*/ - if (esd->dependsOnESID) { - u32 i = 0; - GF_Descriptor *d = NULL; - if (esd->dependsOnESID != ctx->base_ES_ID) return GF_NOT_SUPPORTED; -#ifdef XVID_USE_OLD_API - return GF_NOT_SUPPORTED; -#endif - - while ((d = (GF_Descriptor*)gf_list_enum(esd->extensionDescriptors, &i))) { - if (d->tag == GF_ODF_AUX_VIDEO_DATA) break; - } - if (!d) return GF_NOT_SUPPORTED; - codec = &ctx->depth_codec; - ctx->depth_ES_ID = esd->ESID; - } else { - if (ctx->base_ES_ID && ctx->base_ES_ID!=esd->ESID) return GF_NOT_SUPPORTED; - codec = &ctx->base_codec; - ctx->base_ES_ID = esd->ESID; - } - if (*codec) xvid_decore(*codec, XVID_DEC_DESTROY, NULL, NULL); - - /*decode DSI*/ - e = gf_m4v_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi); - if (e) return e; - if (!dsi.width || !dsi.height) return GF_NON_COMPLIANT_BITSTREAM; - - memset(&par, 0, sizeof(par)); - par.width = dsi.width; - par.height = dsi.height; - /*note that this may be irrelevant when used through systems (FPS is driven by systems CTS)*/ - ctx->FPS = dsi.clock_rate; - ctx->FPS /= 1000; - if (!ctx->FPS) ctx->FPS = 30.0f; - ctx->pixel_ar = (dsi.par_num<<16) | dsi.par_den; - -#ifndef XVID_USE_OLD_API - par.version = XVID_VERSION; -#endif - - if (xvid_decore(NULL, XVID_DEC_CREATE, &par, NULL) < 0) return GF_NON_COMPLIANT_BITSTREAM; - - ctx->width = par.width; - ctx->height = par.height; - *codec = par.handle; - - /*init decoder*/ - memset(&frame, 0, sizeof(frame)); - frame.bitstream = (void *) esd->decoderConfig->decoderSpecificInfo->data; - frame.length = esd->decoderConfig->decoderSpecificInfo->dataLength; -#ifndef XVID_USE_OLD_API - frame.version = XVID_VERSION; - xvid_decore(*codec, XVID_DEC_DECODE, &frame, NULL); -#else - /*don't perform error check, XviD doesn't like DSI only frame ...*/ - xvid_decore(*codec, XVID_DEC_DECODE, &frame, NULL); -#endif - - ctx->first_frame = GF_TRUE; - /*output in YV12 only - let the player handle conversion*/ - if (ctx->depth_codec) { - ctx->out_size = ctx->width * ctx->height * 5 / 2; - ctx->temp_uv = (char*)gf_malloc(sizeof(char)*ctx->width * ctx->height / 2); - } else { - ctx->yuv_size = ctx->out_size = ctx->width * ctx->height * 3 / 2; - } - return GF_OK; -} -static GF_Err XVID_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - XVIDCTX(); - if (ctx->base_ES_ID == ES_ID) { - if (ctx->base_codec) xvid_decore(ctx->base_codec, XVID_DEC_DESTROY, NULL, NULL); - ctx->base_codec = NULL; - ctx->base_ES_ID = 0; - ctx->width = ctx->height = ctx->out_size = 0; - } - else if (ctx->depth_ES_ID == ES_ID) { - if (ctx->depth_codec) xvid_decore(ctx->depth_codec, XVID_DEC_DESTROY, NULL, NULL); - ctx->depth_codec = NULL; - ctx->depth_ES_ID = 0; - if (ctx->temp_uv) gf_free(ctx->temp_uv); - ctx->temp_uv = NULL; - } - return GF_OK; -} -static GF_Err XVID_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - XVIDCTX(); - - switch (capability->CapCode) { - case GF_CODEC_RESILIENT: - capability->cap.valueInt = 1; - break; - case GF_CODEC_WIDTH: - capability->cap.valueInt = ctx->width; - break; - case GF_CODEC_HEIGHT: - capability->cap.valueInt = ctx->height; - break; - case GF_CODEC_STRIDE: - capability->cap.valueInt = ctx->width; - break; - case GF_CODEC_FPS: - capability->cap.valueFloat = ctx->FPS; - break; - case GF_CODEC_PAR: - capability->cap.valueInt = ctx->pixel_ar; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = ctx->out_size; - break; - case GF_CODEC_PIXEL_FORMAT: - capability->cap.valueInt = ctx->depth_codec ? GF_PIXEL_YUVD : GF_PIXEL_YV12; - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = 1; - break; - case GF_CODEC_BUFFER_MAX: - capability->cap.valueInt = 4; - break; - /*by default we use 4 bytes padding (otherwise it happens that XviD crashes on some videos...)*/ - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 32; - break; - /*XviD performs frame reordering internally*/ - case GF_CODEC_REORDER: - capability->cap.valueInt = 1; - break; - case GF_CODEC_WANTS_THREAD: - { - const char *sOpt = gf_modules_get_option((GF_BaseInterface *)ifcg, "XviD", "Threaded"); - capability->cap.valueInt = (sOpt && stricmp(sOpt, "yes")) ? 1 : 0; - } - break; - /*not known at our level...*/ - case GF_CODEC_CU_DURATION: - default: - capability->cap.valueInt = 0; - break; - } - return GF_OK; -} -static GF_Err XVID_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - /*return unsupported to avoid confusion by the player (like color space changing ...) */ - return GF_NOT_SUPPORTED; -} -static GF_Err XVID_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ -#ifdef XVID_USE_OLD_API - XVID_DEC_FRAME frame; -#else - xvid_dec_frame_t frame; -#endif - void *codec; - s32 postproc, res; - XVIDCTX(); - - if (!ES_ID) { - *outBufferLength = 0; - return GF_OK; - } - - if (ES_ID == ctx->depth_ES_ID) { - codec = ctx->depth_codec; - } else { - codec = ctx->base_codec; - } - if (!codec) return GF_OK; - - if (*outBufferLength < ctx->out_size) { - *outBufferLength = ctx->out_size; - return GF_BUFFER_TOO_SMALL; - } - - memset(&frame, 0, sizeof(frame)); - frame.bitstream = (void *) (inBuffer + ctx->offset); - frame.length = inBufferLength - ctx->offset; - ctx->offset = 0; - - -#ifdef XVID_USE_OLD_API - frame.colorspace = XVID_CSP_I420; - frame.stride = ctx->width; - frame.image = (void *) outBuffer; -#else - frame.version = XVID_VERSION; - if (ES_ID == ctx->depth_ES_ID) { - frame.output.csp = XVID_CSP_PLANAR; - frame.output.stride[0] = ctx->width; - frame.output.plane[0] = (void *) (outBuffer + ctx->yuv_size); - frame.output.stride[1] = ctx->width/4; - frame.output.plane[1] = (void *) ctx->temp_uv; - frame.output.stride[2] = ctx->width/4; - frame.output.plane[2] = (void *) ctx->temp_uv; - } else { - frame.output.csp = XVID_CSP_I420; - frame.output.stride[0] = ctx->width; - frame.output.plane[0] = (void *) outBuffer; - } -#endif - - - - /*to check, not convinced yet by results...*/ - postproc = ctx->base_filters; - - switch (mmlevel) { - case GF_CODEC_LEVEL_SEEK: - case GF_CODEC_LEVEL_DROP: - /*turn off all post-proc*/ -#ifdef XVID_USE_OLD_API - postproc &= ~XVID_DEC_DEBLOCKY; - postproc &= ~XVID_DEC_DEBLOCKUV; -#else - postproc &= ~XVID_DEBLOCKY; - postproc &= ~XVID_DEBLOCKUV; - postproc &= ~XVID_FILMEFFECT; -#endif - break; - case GF_CODEC_LEVEL_VERY_LATE: - /*turn off post-proc*/ -#ifdef XVID_USE_OLD_API - postproc &= ~XVID_DEC_DEBLOCKY; -#else - postproc &= ~XVID_FILMEFFECT; - postproc &= ~XVID_DEBLOCKY; -#endif - break; - case GF_CODEC_LEVEL_LATE: -#ifdef XVID_USE_OLD_API - postproc &= ~XVID_DEC_DEBLOCKUV; -#else - postproc &= ~XVID_DEBLOCKUV; - postproc &= ~XVID_FILMEFFECT; -#endif - break; - } - postproc = 0; - - /*xvid may keep the first I frame and force a 1-frame delay, so we simply trick it*/ - if (ctx->first_frame) { - outBuffer[0] = 'v'; - outBuffer[1] = 'o'; - outBuffer[2] = 'i'; - outBuffer[3] = 'd'; - } - - res = xvid_decore(codec, XVID_DEC_DECODE, &frame, NULL); - if (res < 0) { - *outBufferLength = 0; - return GF_NON_COMPLIANT_BITSTREAM; - } - if (0 && res <= 6) { - *outBufferLength = 0; - return GF_OK; - } - - /*dispatch nothing if seeking or droping*/ - switch (mmlevel) { - case GF_CODEC_LEVEL_SEEK: - case GF_CODEC_LEVEL_DROP: - if (ES_ID == ctx->base_ES_ID) - *outBufferLength = 0; - break; - default: - *outBufferLength = ctx->out_size; - if (ctx->first_frame) { - ctx->first_frame = GF_FALSE; - if ((outBuffer[0] == 'v') && (outBuffer[1] == 'o') && (outBuffer[2] == 'i') && (outBuffer[3] == 'd')) { - *outBufferLength = 0; - return GF_OK; - } - } - if (res + 6 < frame.length) { - ctx->offset = res; - return GF_PACKED_FRAMES; - } - break; - } - - return GF_OK; -} - -static u32 XVID_CanHandleStream(GF_BaseDecoder *dec, u32 StreamType, GF_ESD *esd, u8 PL) -{ - if (StreamType != GF_STREAM_VISUAL) return GF_CODEC_NOT_SUPPORTED; - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - - if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_MPEG4_PART2) { - if (esd->decoderConfig->rvc_config || esd->decoderConfig->predefined_rvc_config) return GF_CODEC_MAYBE_SUPPORTED; - return GF_CODEC_SUPPORTED; - } - return GF_CODEC_NOT_SUPPORTED; -} - -static const char *XVID_GetCodecName(GF_BaseDecoder *dec) -{ -#ifdef XVID_USE_OLD_API - return "XviD Dev Version"; -#else - return "XviD 1.0"; -#endif -} - -GF_BaseDecoder *NewXVIDDec() -{ - const char *sOpt; - GF_MediaDecoder *ifcd; - XVIDDec *dec; - - GF_SAFEALLOC(ifcd, GF_MediaDecoder); - GF_SAFEALLOC(dec, XVIDDec); - GF_REGISTER_MODULE_INTERFACE(ifcd, GF_MEDIA_DECODER_INTERFACE, "XviD Decoder", "gpac distribution") - - ifcd->privateStack = dec; - - if (!xvid_is_init) { -#ifdef XVID_USE_OLD_API - XVID_INIT_PARAM init; - init.api_version = 0; - init.core_build = 0; - /*get info*/ - init.cpu_flags = XVID_CPU_CHKONLY; - xvid_init(NULL, 0, &init, NULL); - /*then init*/ - xvid_init(NULL, 0, &init, NULL); -#else - xvid_gbl_init_t init; - init.debug = 0; - init.version = XVID_VERSION; - init.cpu_flags = 0; /*autodetect*/ - xvid_global(NULL, 0, &init, NULL); -#endif - xvid_is_init = GF_TRUE; - } - - /*get config*/ - dec->base_filters = 0; - sOpt = gf_modules_get_option((GF_BaseInterface *)ifcd, "XviD", "PostProc"); - if (sOpt) { -#ifndef XVID_USE_OLD_API - if (strstr(sOpt, "FilmEffect")) dec->base_filters |= XVID_FILMEFFECT; -#endif - if (strstr(sOpt, "Deblock_Y")) { -#ifdef XVID_USE_OLD_API - dec->base_filters |= XVID_DEC_DEBLOCKY; -#else - dec->base_filters |= XVID_DEBLOCKY; -#endif - } - if (strstr(sOpt, "Deblock_UV")) { -#ifdef XVID_USE_OLD_API - dec->base_filters |= XVID_DEC_DEBLOCKUV; -#else - dec->base_filters |= XVID_DEBLOCKUV; -#endif - } - } - - /*setup our own interface*/ - ifcd->AttachStream = XVID_AttachStream; - ifcd->DetachStream = XVID_DetachStream; - ifcd->GetCapabilities = XVID_GetCapabilities; - ifcd->SetCapabilities = XVID_SetCapabilities; - ifcd->GetName = XVID_GetCodecName; - ifcd->CanHandleStream = XVID_CanHandleStream; - ifcd->ProcessData = XVID_ProcessData; - return (GF_BaseDecoder *) ifcd; -} - -void DeleteXVIDDec(GF_BaseDecoder *ifcg) -{ - XVIDCTX(); - if (ctx->base_codec) xvid_decore(ctx->base_codec, XVID_DEC_DESTROY, NULL, NULL); - if (ctx->depth_codec) xvid_decore(ctx->depth_codec, XVID_DEC_DESTROY, NULL, NULL); - gf_free(ctx); - gf_free(ifcg); -} - -#endif /*GPAC_DISABLE_AV_PARSERS*/ - - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifndef GPAC_DISABLE_AV_PARSERS - GF_MEDIA_DECODER_INTERFACE, -#endif - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ -#ifndef GPAC_DISABLE_AV_PARSERS - if (InterfaceType == GF_MEDIA_DECODER_INTERFACE) return (GF_BaseInterface *)NewXVIDDec(); -#endif - return NULL; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { -#ifndef GPAC_DISABLE_AV_PARSERS - case GF_MEDIA_DECODER_INTERFACE: - DeleteXVIDDec((GF_BaseDecoder*)ifce); - break; -#endif - } -} - -GPAC_MODULE_STATIC_DECLARATION( xvid ) diff --git a/modules/xvid_dec/xvid_dec_wce.cpp b/modules/xvid_dec/xvid_dec_wce.cpp deleted file mode 100644 index aa11716..0000000 --- a/modules/xvid_dec/xvid_dec_wce.cpp +++ /dev/null @@ -1,303 +0,0 @@ -/* - * GPAC - Multimedia Framework C SDK - * - * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 - * All rights reserved - * - * This file is part of GPAC / codec pack module - * - * GPAC is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * GPAC is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - -#include <gpac/modules/codec.h> -#include <gpac/avparse.h> -#include <gpac/constants.h> - -#include "xvid_wce/xvid.h" - -typedef struct -{ - void *codec; - /*no support for scalability in XVID yet*/ - u16 ES_ID; - u32 width, height, out_size, pixel_ar; - u32 cb_size, cb_trig; - Bool first_frame; - s32 base_filters; - Float FPS; -} XVIDDec; - -#define XVIDCTX() XVIDDec *ctx = (XVIDDec *) (ifcg->privateStack) - - -static GF_Err XVID_AttachStream(GF_BaseDecoder *ifcg, GF_ESD *esd) -{ - GF_M4VDecSpecInfo dsi; - GF_Err e; - unsigned char *ptr; - unsigned long pitch; - - XVIDCTX(); - - if (ctx->ES_ID && ctx->ES_ID!=esd->ESID) return GF_NOT_SUPPORTED; - if (!esd->decoderConfig->decoderSpecificInfo || !esd->decoderConfig->decoderSpecificInfo->data) return GF_NON_COMPLIANT_BITSTREAM; - - /*decode DSI*/ - e = gf_m4v_get_config((char *) esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi); - if (e) return e; - if (!dsi.width || !dsi.height) return GF_NON_COMPLIANT_BITSTREAM; - - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[XviD] Attaching Stream %d - framesize %d x %d\n", esd->ESID, dsi.width, dsi.height )); - - ctx->codec = InitCodec(dsi.width, dsi.height, GF_4CC('x', 'v', 'i', 'd')); - if (!ctx->codec) return GF_OUT_OF_MEM; - - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[XviD] Decoding DecoderSpecificInfo\n")); - - DecodeFrame(ctx->codec, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, ptr, ptr, ptr, pitch); - - /*note that this may be irrelevant when used through systems (FPS is driven by systems CTS)*/ - ctx->FPS = dsi.clock_rate; - ctx->FPS /= 1000; - if (!ctx->FPS) ctx->FPS = 30.0f; - ctx->width = dsi.width; - ctx->height = dsi.height; - ctx->pixel_ar = (dsi.par_num<<16) | dsi.par_den; - ctx->pixel_ar = 0; - ctx->ES_ID = esd->ESID; - ctx->first_frame = 1; - /*output in YV12 only - let the player handle conversion*/ - ctx->out_size = 3 * ctx->width * ctx->height / 2; - GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[XviD] Decoder setup - output size %d\n", ctx->out_size )); - return GF_OK; -} -static GF_Err XVID_DetachStream(GF_BaseDecoder *ifcg, u16 ES_ID) -{ - XVIDCTX(); - if (ctx->ES_ID != ES_ID) return GF_BAD_PARAM; - if (ctx->codec) CloseCodec(ctx->codec); - ctx->codec = NULL; - ctx->ES_ID = 0; - ctx->width = ctx->height = ctx->out_size = 0; - return GF_OK; -} -static GF_Err XVID_GetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability *capability) -{ - XVIDCTX(); - - switch (capability->CapCode) { - case GF_CODEC_WIDTH: - capability->cap.valueInt = ctx->width; - break; - case GF_CODEC_HEIGHT: - capability->cap.valueInt = ctx->height; - break; - case GF_CODEC_STRIDE: - capability->cap.valueInt = ctx->width; - break; - case GF_CODEC_FPS: - capability->cap.valueFloat = ctx->FPS; - break; - case GF_CODEC_PAR: - capability->cap.valueInt = ctx->pixel_ar; - break; - case GF_CODEC_OUTPUT_SIZE: - capability->cap.valueInt = ctx->out_size; - break; - case GF_CODEC_PIXEL_FORMAT: - capability->cap.valueInt = GF_PIXEL_YV12; - break; - case GF_CODEC_BUFFER_MIN: - capability->cap.valueInt = ctx->cb_trig; - break; - case GF_CODEC_BUFFER_MAX: - capability->cap.valueInt = ctx->cb_size; - break; - /*by default we use 4 bytes padding (otherwise it happens that XviD crashes on some videos...)*/ - case GF_CODEC_PADDING_BYTES: - capability->cap.valueInt = 32; - break; - /*XviD performs frame reordering internally*/ - case GF_CODEC_REORDER: - capability->cap.valueInt = 1; - break; - case GF_CODEC_WANTS_THREAD: - { - const char *sOpt = gf_modules_get_option((GF_BaseInterface *)ifcg, "XviD", "Threaded"); - capability->cap.valueInt = (sOpt && stricmp(sOpt, "yes")) ? 1 : 0; - } - break; - /*not known at our level...*/ - case GF_CODEC_CU_DURATION: - default: - capability->cap.valueInt = 0; - break; - } - return GF_OK; -} -static GF_Err XVID_SetCapabilities(GF_BaseDecoder *ifcg, GF_CodecCapability capability) -{ - /*return unsupported to avoid confusion by the player (like color space changing ...) */ - return GF_NOT_SUPPORTED; -} -static GF_Err XVID_ProcessData(GF_MediaDecoder *ifcg, - char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 *CTS, - char *outBuffer, u32 *outBufferLength, - u8 PaddingBits, u32 mmlevel) -{ - unsigned char *pY, *pU, *pV; - u32 i, uv_w, half_h; - unsigned long pitch; - XVIDCTX(); - - /*check not using scalabilty*/ - if (ES_ID != ctx->ES_ID) return GF_BAD_PARAM; - - if (*outBufferLength < ctx->out_size) { - *outBufferLength = ctx->out_size; - return GF_BUFFER_TOO_SMALL; - } - - if (!DecodeFrame(ctx->codec, inBuffer, inBufferLength, pY, pU, pV, pitch)) { - *outBufferLength = 0; - return GF_NON_COMPLIANT_BITSTREAM; - } - - /*dispatch nothing if seeking or droping*/ - switch (mmlevel) { - case GF_CODEC_LEVEL_SEEK: - case GF_CODEC_LEVEL_DROP: - *outBufferLength = 0; - return GF_OK; - default: - break; - } - *outBufferLength = ctx->out_size; - for (i=0; i<ctx->height; i++) { - unsigned char *src = pY + pitch*i; - char *dst = outBuffer + ctx->width*i; - memcpy(dst, src, sizeof(char) * ctx->width); - } - outBuffer += ctx->width * ctx->height; - half_h = ctx->height/2; - uv_w = ctx->width/2; - for (i=0; i<half_h; i++) { - unsigned char *src = pU + pitch/2*i; - char *dst = outBuffer + i*uv_w; - memcpy(dst, src, sizeof(char) * uv_w); - } - outBuffer += ctx->width * ctx->height / 4; - for (i=0; i<half_h; i++) { - unsigned char *src = pV + pitch/2*i; - char *dst = outBuffer + i*uv_w; - memcpy(dst, src, sizeof(char) * uv_w); - } - - return GF_OK; -} - -static const char *XVID_GetCodecName(GF_BaseDecoder *dec) -{ - return "XviD 1.0 for WinCE"; -} - -static u32 XVID_CanHandleStream(GF_BaseDecoder *dec, u32 StreamType, GF_ESD *esd, u8 PL) -{ - if (StreamType != GF_STREAM_VISUAL) return GF_CODEC_NOT_SUPPORTED; - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - if (esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_MPEG4_PART2) return GF_CODEC_STREAM_TYPE_SUPPORTED; - return GF_CODEC_NOT_SUPPORTED; -} - - - -GF_BaseDecoder *NewXVIDDec() -{ - GF_MediaDecoder *ifcd; - XVIDDec *dec; - - ifcd = (GF_MediaDecoder*) gf_malloc(sizeof(GF_MediaDecoder)); - memset(ifcd, 0, sizeof(GF_MediaDecoder)); - - dec = (XVIDDec*) gf_malloc(sizeof(XVIDDec)); - memset(dec, 0, sizeof(XVIDDec)); - - dec->cb_size = 4; - dec->cb_trig = 1; - /*setup our own interface*/ - ifcd->AttachStream = XVID_AttachStream; - ifcd->DetachStream = XVID_DetachStream; - ifcd->GetCapabilities = XVID_GetCapabilities; - ifcd->SetCapabilities = XVID_SetCapabilities; - ifcd->GetName = XVID_GetCodecName; - ifcd->ProcessData = XVID_ProcessData; - ifcd->CanHandleStream = XVID_CanHandleStream; - ifcd->privateStack = dec; - GF_REGISTER_MODULE_INTERFACE(ifcd, GF_MEDIA_DECODER_INTERFACE, "XviD for CE Decoder", "gpac distribution") - return (GF_BaseDecoder *) ifcd; -} - -void DeleteXVIDDec(GF_BaseDecoder *ifcg) -{ - XVIDCTX(); - if (ctx->codec) CloseCodec(ctx->codec); - gf_free(ctx); - gf_free(ifcg); -} - - -#ifdef __cplusplus -extern "C" { -#endif - -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() -{ - static u32 si [] = { -#ifndef GPAC_DISABLE_AV_PARSERS - GF_MEDIA_DECODER_INTERFACE, -#endif - 0 - }; - return si; -} - -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - if (InterfaceType == GF_MEDIA_DECODER_INTERFACE) return (GF_BaseInterface *) NewXVIDDec(); - return NULL; -} - -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) -{ - switch (ifce->InterfaceType) { - case GF_MEDIA_DECODER_INTERFACE: - DeleteXVIDDec((GF_BaseDecoder *)ifce); - break; - } -} - -GPAC_MODULE_STATIC_DECLARATION( xvid_dec_wce ) - -#ifdef __cplusplus -} -#endif diff --git a/packagers/osx/GPAC.app/Contents/Info.plist b/packagers/osx/GPAC.app/Contents/Info.plist new file mode 100644 index 0000000..c1371d9 --- /dev/null +++ b/packagers/osx/GPAC.app/Contents/Info.plist @@ -0,0 +1,577 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleDocumentTypes</key> + <array> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>bt</string> + <string>xmt</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-4 BIFS Text</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>wrl</string> + <string>x3d</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_model.icns</string> + <key>CFBundleTypeName</key> + <string>Web3D VRML/X3D File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>svg</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>W3C SVG File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>saf</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-4 SAF/LASeR File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>flv</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>Flash Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>pls</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>Shoutcast playlist</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>m3u</string> + <string>m3u8</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>Playlist file</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>aiff</string> + <string>aif</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeName</key> + <string>AIFF file</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>sdp</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>Session Description Protocol File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>wav</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeName</key> + <string>WAVE Audio File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>vob</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>DVD Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>a52</string> + <string>ac3</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeName</key> + <string>Digital Audio</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>aac</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeName</key> + <string>AAC file</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>ogm</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>Ogg MPEG-4 Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>ogg</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeMIMETypes</key> + <array> + <string>audio/ogg</string> + </array> + <key>CFBundleTypeName</key> + <string>Ogg Vorbis File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>ogv</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeMIMETypes</key> + <array> + <string>video/ogg</string> + </array> + <key>CFBundleTypeName</key> + <string>Ogg Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>avi</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>AVI container</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>mov</string> + <string>moov</string> + <string>qt</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeMIMETypes</key> + <array> + <string>video/quicktime</string> + </array> + <key>CFBundleTypeName</key> + <string>Apple QuickTime container</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>asf</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>Advanced Streaming Format</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>wma</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeName</key> + <string>Windows Media Audio</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>wmv</string> + <string>wm</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>Windows Media Video</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>mpg</string> + <string>mpeg</string> + <string>mpeg1</string> + <string>mpeg2</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>multiplexed MPEG-1/2</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>m1v</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-1 Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>m2a</string> + <string>mp1</string> + <string>mp2</string> + <string>mp3</string> + <string>mpa</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG Audio File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>m2p</string> + <string>ps</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-2 Program Stream</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>ts</string> + <string>m2ts</string> + <string>mts</string> + <string>mt2s</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeMIMETypes</key> + <array> + <string>video/mp2t</string> + </array> + <key>CFBundleTypeName</key> + <string>MPEG-2 Transport Stream</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>m2v</string> + <string>mpv</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-2 Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>m4i</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-4 Application</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>mp4</string> + <string>mpeg4</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_generic.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-4 File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>m4v</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-4 Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>m4a</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeName</key> + <string>MPEG-4 Audio File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>3gp</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>3GPP File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>mka</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_audio.icns</string> + <key>CFBundleTypeName</key> + <string>Matroska Audio File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>mkv</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>Matroska Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>webm</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_video.icns</string> + <key>CFBundleTypeName</key> + <string>WebM Video File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>srt</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_subs.icns</string> + <key>CFBundleTypeName</key> + <string>Subrip Subtitle File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + <key>LSTypeIsPackage</key> + <false/> + <key>NSPersistentStoreTypeKey</key> + <string>Binary</string> + </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>ttxt</string> + </array> + <key>CFBundleTypeIconFile</key> + <string>osmo_subs.icns</string> + <key>CFBundleTypeName</key> + <string>3GPP Subtitle File</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + <key>LSTypeIsPackage</key> + <false/> + <key>NSPersistentStoreTypeKey</key> + <string>Binary</string> + </dict> + </array> + <key>CFBundleExecutable</key> + <string>Osmo4</string> + <key>CFBundleGetInfoString</key> + <string>Copyright © 2003-2020 Telecom Paris</string> + <key>CFBundleIconFile</key> + <string>osmo.icns</string> + <key>CFBundleIdentifier</key> + <string>com.enst.gpac</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>Osmo4</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> +<!-- Please do not remove the comment in the next line + It allows mkdmg.sh to perform a Version remplacement --> + <string>9.9.9</string><!-- VERSION_REV_REPLACE --> + <key>CFBundleSignature</key> + <string>gpac</string> + <key>CFBundleURLTypes</key> + <array> + <dict> + <key>CFBundleURLIconFile</key> + <string>generic</string> + <key>CFBundleURLName</key> + <string>http url</string> + <key>CFBundleURLSchemes</key> + <array> + <string>http</string> + </array> + </dict> + <dict> + <key>CFBundleURLIconFile</key> + <string>generic</string> + <key>CFBundleURLName</key> + <string>Multimedia Stream URL</string> + <key>CFBundleURLSchemes</key> + <array> + <string>mms</string> + </array> + </dict> + <dict> + <key>CFBundleURLIconFile</key> + <string>generic</string> + <key>CFBundleURLName</key> + <string>RTSP</string> + <key>CFBundleURLSchemes</key> + <array> + <string>rtsp</string> + </array> + </dict> + <dict> + <key>CFBundleURLIconFile</key> + <string>generic</string> + <key>CFBundleURLName</key> + <string>udp url</string> + <key>CFBundleURLSchemes</key> + <array> + <string>udp</string> + </array> + </dict> + </array> + <key>CFBundleVersion</key> +<!-- Please do not remove the comment in the next line + It allows mkdmg.sh to perform a Version remplacement --> + <string>9999</string><!-- BUILD_REV_REPLACE --> + <key>LSMinimumSystemVersion</key> + <string>10.5.0</string> + <key>NSMainNibFile</key> + <string>MainMenu</string> + <key>NSPrincipalClass</key> + <string>NSApplication</string> + <key>SUEnableAutomaticChecks</key> + <string>NO</string> + <key>SUEnableSystemProfiling</key> + <string>YES</string> +</dict> +</plist> diff --git a/packagers/osx/GPAC.app/Contents/PkgInfo b/packagers/osx/GPAC.app/Contents/PkgInfo new file mode 100644 index 0000000..dd0975d --- /dev/null +++ b/packagers/osx/GPAC.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPLgpac \ No newline at end of file diff --git a/packagers/osx/GPAC.app/Contents/Resources/osmo.icns b/packagers/osx/GPAC.app/Contents/Resources/osmo.icns new file mode 100644 index 0000000000000000000000000000000000000000..da7226f64f8939dfd5fddf847699e6f2ba13250b GIT binary patch literal 147235 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 J|-(K2J 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 literal 0 HcmV?d00001 diff --git a/packagers/osx/GPAC.app/Contents/Resources/osmo_audio.icns b/packagers/osx/GPAC.app/Contents/Resources/osmo_audio.icns new file mode 100644 index 0000000000000000000000000000000000000000..d4a1d32fa20b9156aff490e73895d4c71408962f GIT binary patch literal 209406 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 F|Jj+&$WH(O literal 0 HcmV?d00001 diff --git a/packagers/osx/GPAC.app/Contents/Resources/osmo_generic.icns b/packagers/osx/GPAC.app/Contents/Resources/osmo_generic.icns new file mode 100644 index 0000000000000000000000000000000000000000..6d0417b4509c2ef92bb938aaa4c9a1573772f653 GIT binary patch literal 190916 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%0D|XRYL}7ZOds<PDXwav3 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 literal 0 HcmV?d00001 diff --git a/packagers/osx/GPAC.app/Contents/Resources/osmo_model.icns b/packagers/osx/GPAC.app/Contents/Resources/osmo_model.icns new file mode 100644 index 0000000000000000000000000000000000000000..18e61b3db349117c438408fe6bac9d7be2fabb95 GIT binary patch literal 236301 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 zΠ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%0DFgmiLCN&T)Li2JP735f4Q*`sj46C|6{ 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 CR&1F7 literal 0 HcmV?d00001 diff --git a/packagers/osx/GPAC.app/Contents/Resources/osmo_subs.icns b/packagers/osx/GPAC.app/Contents/Resources/osmo_subs.icns new file mode 100644 index 0000000000000000000000000000000000000000..3e8f02c94c1929d3877a6bd4bce1c38c8eabd3bf GIT binary patch literal 176846 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 )xmD%#LBw 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≪GwJil6xTPuu;#PcS8fUdi@OI; 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 literal 0 HcmV?d00001 diff --git a/packagers/osx/GPAC.app/Contents/Resources/osmo_video.icns b/packagers/osx/GPAC.app/Contents/Resources/osmo_video.icns new file mode 100644 index 0000000000000000000000000000000000000000..92316630eabc7f8f8a6e70337d7cb68dff8b190b GIT binary patch literal 221221 zcmce;2Ut{B*Y~|u+Vr6pu|<uECTe0&RMIuQD8wWhH5z+EKtNHZH+t_d%+P!9SP*O| zWtdKtX72^m=zMFRLGR?g@8o&D>w4dJ5=hRRv;Sx9wb!n{z1g{Cp9h+-Y39x?9@7y* zs}UN<GVhx*x}whI50^juU--Yk|8@Ce${*%;$Fbmv|KUF9EQMz*@RR@Jr>9e|@9cT` zSYM|uq_i|d*V$+MrgL{6dj1@t-JQgBj}AS5%65B_hx9~}&hF=PMxVKjjQ-J0JfJUq zGU_%m;^yX7s>cc7+RbfrWOQW2c-`5}o-=3E&F%4%klpxxkN!D+eDuk)tws|%JD)#$ z_Uw;55&H$ckNP_G-KC}7`cC5|PO@9C-)(%Dc;X*^?etjiE<)q&BGI_nv&G1?_}tlZ z=Suj9<bUei{-sOzUpnp2L6mLBk8eTFj%e5UhHY#_DQZ|(SQ{v9sq$zpfy<NSMIBX# zVjD_InoqINq}dG_Crc8mPbP;|)}CH~(Co&->ZHh$%EE@TvIZ9xqMT}sYrm8knb4hH zPz%Y6TMs3js7}cY+!7N7m#+04OTSpRZrMkhwz_c;*|YxKwxx@=pUwT+g@?>!r%oKN zkDDh#<CZUqL^3pvX2Y^ZOt6<~?Lene8FtoGE+SbwA~bC>LbldmJl!6Z9S@eZHy|4( z=v!MV)O9`yP37lEHf%(-o^YW&rS7ye;^gbrjx4w~KP8PWt1dp%`i8YF7;8<*QnqJD zC-)Un;c*Jna+mB>tt=~K&u6p1IveKvwHr2kyv$|CZn(0wwItE0WGlxHUAf5I+1}pP z&cSht2#ufYWQYK35rs`hWC0pav!b#n<E&^rM55UmO|_(fsT3<zSS3zsu0>25yo^Ru zw3K^99WQJ!p@XT635|tu<#mZ6HPac`RK|q5`0#?VyyNF)(y^&D6M4(|wD34xt~moz zQz+Y$>nfAd19yEi6O5yi=YIdoFCQ&gxosO<ku9laREjyxW~B=@)yj&=w6=d`s>jm3 z5PN@HJIu`f6j3JYOO(pe4JKn>*;Eeq84TS$RW`&IrZpGs-+J<RF14oG6o2u{E&Bxt z=kAt#Md{vvuWPvrAKqzrV0gA+&vCdk>-;pwfIlxZtGmc>t^2r^xHb%}>!HE}H3yCN z4XS{1<KTL|YVBj<f!-lcxI|_ra^jyE49_|Z28e6;I5c$k|M5qE--RD=a`=$;H4F}& z^JN;Zh~JjhSn9ugKL7X|Xl?$@59WOcx%`2U{|hwEU|>xcxpQ*Dm4TDy+*5swl+n8k zmlt>G{)PjS4F(_h8U6Pky1{Ulfq(oBcg;^2`X=<k&&ZuV(2`<JLL0y1?>^>z|IgpQ zbITrRElvJ+t>xd@{r~&>zTRHQ>fd}yLw4&{t<vodDgAE`ly<JtXtb+VX*HTvo&Whn z_bT|hr?;=Kx2FsKxBK5DLbRIBUUdBhd=TpG)M`Wi@<6v%i&Nm+_yII_{qmX8RT`t< z^Sr$0#FyRLRe!RfQ$zFvc~3{7)qnB~D|8vZA|5VXwW=4P=WdTj|M=tSoX3wxo<S48 zO8ZJ61V5WM@_5c4dC#7?J)Se?@l!0&_44(*H9hFX$m7Rz+@3snGCKO?>61CnP^pIK zYv7qaowgS}dpt6VKe!uu^2BWvUZCqU3iPg8)rZ`O#2^Cw4s=d)p7-gnQTSuLKjMZv ztvMrv%$z^pC-k4raeE5)iC5q(y!e+s!uCNAH!?B@UOzJG1{rXBQL24u9}0o2JR5xs zpHavF`w1j}a{W*F1ON2BcrpsI@RFx)@R-{(c<C$upiU6TgYFd`nKNgO8ykN%Z0#eg z$G=KfX?viHh8s_xK4s&s^p9sx!O5>aM0vi*V-xrC^78QgyjOSOf4a2Vu09BW=g(hU zM}1w|4@G|%3;N|lArMquM0}Ov_zHoC|N1H2jU!X1g8<e3_nx}6OONAQ-_`RUiN~b+ zUi9|9_`92beck`5tDSrBXu+Iv9htg%`TP61Mk30mVrj|oGiOee$^$<QMKob{+s$j5 zbLZ4oZ*}BLf{>+abkps2q02No+o>x>Eq5*?xJr<<Yn*;K<u^2*!eCIwqXSukJyKUO zqDJa_xK;>R+B-PdGFfOsl)g7!geC}^uJAc%!fS8OnFjw!7-$X>Am;jv+o=vP5{xa) zF0QGpNb`f|OYY>a=OcSjTiYgtM3tJFjGvEP>h5ZiyP!aYCJ=5V-%Lj+{A|xf)`9K6 zOSKo<x(b$}iaV)1<mg&_O^EzX_Nd#!L?OZXg#qm7)^sEvF8!E`91qrOJy3dIy-Z%1 zmr-34FAWl=p57RvI<*aM`ZrzLjB1-=MS-Cey3U5=!sN(YMWBD{C3k%DOv_euvLaCu z8<%;$q9Qv>mf4~_>e1S=4{rJ#*Kko<M}lueQnoBBFQ=kir91tlpR!>S+;lCumV(#{ zMOF3r%(9x=lIRv?Z>me=P{DE@vQNETjr`IzJt|eYIFP+vsOqbg*cRT7gggdxsG>KA z$j<ilw8eS2N2@yXWA=P}PJaMyt<Skri*`pzV+&jL%9h6D-M{_5)jd}9p#a(PFAU_P zg?^DcHoN;C{QW1_Z$4YK#%HGp*+leqmLZGxwrpMZ!?&L-owxk^FTVHr1-}0zrgxxm z85(Cf(aFjLy<4L{wchnhF|z(F=;9q++Um(n=A>1rDk%G}(Yyde^O9?B533p*RYSM+ z((R#$vdld^x1pu=bZ*G51(C>fvD?NC-!Hk2ZlDQfB$8P)B2j2eD;patOBy8%(MWV# zM@M^GTRTTbTLvi@nNqAB9hv5A3sZB3gQE>C5LwdT-gI*k-O`e7VKUjy(Vhm8MzM2r zp7%1O5OTJ0u&0TT2@DY@e#L$jQ`QbPBq3tZ9UbQ5kSfb9tFNxgfRGw*<-`;qdkb5~ z$p{73=#OXp%x=HjdoJAtQSIy~;8L2SgDVQZ(BH=K+O<R8)u!w%T#Bq58GPixbaX_1 zjs1FMq$Dh~s3e#hr^rNf2P+yBu9=nNM3gyjGCiZDFsrUUK^7`XYw|a<v$27jBpdsQ zsP0^RKyY|<cW;xlNE)55ibS?{w#J+G=u~xbV0=RMrRtiz-1HoUh6gusN;4}5Ta>O& z@{3BzOUusBt5)l}n|?-iwvIgHVCiUuI7)S0-No$k+Pbnhh4!+1jj4mpWVlCjv_}4! zy30CURzL`Emqa&E7x+HS(RKmE8syY`e^}c2fqrG;A<sB%cVXN?GQ-+Yfb1AfR%m~W zEWW6{Q{8qZWzUx1f8V`h#Z-tVij4yUEjkvx>$ii(c=~+xnq!_2Hw=4wCZfH+b=yyC zzx#CA0+;tcUhDfSd~XTW-g-PTx0&Q@OCAq?a-RFk*J2o)TG~3=(alX+CKl!p1a|h5 z&^!rZxY$|SJ383f*w{KiTufRJf+$lRY;5f8?d@#sC%zGZ%w1<pnmB3l8}D5sV#>q> z$CN3VLSry!RI(|=lo@2g+LFn%w6<oF&4Llbf^Ka?Gc&iaFgK%ETQe*ms4Oh4Z6T-_ zL{K?dSzD4JsNhS-KLyoNrj?}y1eF=x+Tl+@#js|WK~RycZE;XV6lN4umX*qIP|<Cu z5LBiNYX~Y)c}HdHkA4?)+J+PtWX80#fS|IlwsA!vjrwzb`+nb&q&#;~o4XXztRdbU zDAxAK@3>xZF_<41kew$w7T%l+(-b-pR5WV`B=4?Hk>+K|Dk@@<CA{QQ{)owBKxmjS ztZY$rL$p8~RH{-RPs~pU&uk4vkTc^=OH^MPFN}_nH<gxVWJuF4w6ozR5mYoQOO)Cc z>m3@Ok&=;<ReGsib;=E7Z6I){)(mv4t);xYDXpZcx+wBOYgh7j$cll3ifm0shviCr zd%H{^@&BFI-d!zNOtQAz20>-UWSZ{^k~VdBUW|6%AED6ZM(s5x(`_NBC^j^-Ga@NE zzeS_Ccsg<C=B-<IZhg-Qg35wnO+gEM!?$g6_c^!~8Yu`WzXK3F6e~*#qAcC~o7?x_ ze7xkHmCHU_<Mk5`Dw>rQ9l9(V=ZQ8DR3t0=+3Qw|k(o1{iOZa0VoqjSTUw8WlqJK` z+KS0wFs-aCEgd1GOs3jcGA&tF6QICfhLqFf=@3$Th$+(l;~_D1=HkUO$L2Br(Wg^> z%;|x-(Bqz*A1VKF6Cu}jVqWzE|895v_XkKZ@b$yze>{hOZ$CE}Vn}~^f>Z!=x(%O> z;>4papEMZ^`G0u;QkP6bC<Nb!!7?e(V2J*cz#4<02xfDL(PJ?55fekM;l=l_9-ycU zT9^jYH<!W-9yIdiz=}k%LHQ~dZiW|N@Tv~O)xBi{_z{r>dgH#~r<Ve`hE_B5<A}i! zvA;oi7u!%kLK%jVvHR3s0}IW(jlVa3o({toKN+rA8y|Rm#PB%+i~sd`^!zGw_Os{4 z86;Y4_+uV|EdS-R+~%$UR-y1`vEk_)boMWI4TjI{?i#)_-Ut2nQ2k%;zxZU{Gs6nw zeXwuKf299vgW=(8#``GSkcXsyO@Du0j-iE^QNuH94G&&h^7Jncl&^kZ;Ep9n;NNU? z<S+N{KQP>z_3}P)GZ+q_i+_2*U^w^cHX4^|_+#IK8!sQYRs9717uGXgeHf6v)ZHrM z1M2<qKMehC-mmV$|5z0o3^(dK?mc~aucID>TIZPmc?J%XOOLCN`suT0Pa#4dM;ccT z{;Yte9BYLlHW>csj2tVNzublkv)Nz&zGLkI+P{DQfAe~$Cms^Jt-<dw{Ov!<u%PC@ z&CAEfYn!Y8Eo8gb`-nfA!xabxLXk));Bnag{@!~B5Y^S!kIfeaM#Ls2r${+6iZnSX zE;3jw;Q0HxcB5%W{duCW_!L=2PC;>5MOAf8O?6dec}ZbjR+=<1B7o06sztMouy~T_ zBw1ErdCiH&v(1-U+Z7!hiuSgai|3n8)>Rhgrl-UO3RoT;XpSFS7?CK?DXBSmwnd@s zx;%L0`psLnZ{NCk<LXd<k51Wo{&Zb=etJr@nD5t$EDv);G0B-l)eRREy1py7?mrq~ zkD?LQV+8+YK7MffT7ReVQd50tjw~TS;Ma^CJXoR_X?EH1bM5+p8~0(6^WohaR|fn0 zdRaa8eU}G@uiv@<cy#pP?cr`^^Qp=_c|ri!_bhU9ImVAl&MrUMtm?h?U}W^+t>Iq1 zs{PW1b7#-6njFuZJAbiFsqGoWijQs$=vo@9@?~)nmP;dA=EVt4%qTr^QPY3>@skHP z`t==`&YnJ5UtLz5mzAELm0MI=S$F(Y(}i|z-?e)qBlm_o+fG;JNW=Ml4d{J;L5!@h z?!3DH&gjUUp-#nx(<iD66S@1=u6TRa)QJ<PzW(-#HTyUT1(o%u&bDd$Z$5f*e^__v zWNAiXfd2{P;KdG1%&KTq^xhg7x!te1)Ofr?dU(xSOcNHGK5qO36O#$!VWDZ93G=NV zd{av6PF+y;Tz@orZ%}={HcuMH_o_oS-kgY(g8F9t)kmXu`c)TBRc9Uf%#MXjCYZvo zghw*PLle^pEM#N%sc%}v@w1Aa8;?hB^|qZVO^X%y*C1zKUW~k?p}p_!=z}2*_%zjX zHMFDSadHw_K$%1)lPt{4OvgiQ`Pw6?wEnE}@}1F#L+Z1YS&3r*D&*|LkIkq!s~&zd zdb7K=p-Ot-qd9Mp#=-j`J%vS`Nu$x}G#Zskp^!<~7Rb@`PxmL39dFiNc{FmP^I~0Y za)5sYvN|G+%c{PhzcKP~NOiU@_pr<CH(7`bmVgcwL>L?<O#kRKtYiTev61DpPkqy? zPPg~nd2+X}^>}_tAiE4%`Uv8)Ynr=nkKXNVZ72)*_U$)dh1ZNkq0s0I7`t(-$S{N> zbjTK_Jj9&k8c+ltygu@vzx_m^G|0aMS^DwgvTHB)+<S7fv$;NR|DreN@{ol&nMz|Y zEv>A1HY95+OVFTF$mRmXSmK^pbw)ir@@PnLvQQe}SA>|}{Mf9Ti@o<ouW8TLB(I(O zx)7O>;r%c{VcD|mShnUiHr7~yEJBQF>to6qItCv-hV%s~BCi6(+{=l{sBZ4PKXO&w zR2B5WoEaiyVL^wnr7a9tISwQ|YPEr}D9u8G=&Qx0C)@fTJ|1d6k(VU&%13l|guLQH z_r1}ps>X8ed#?u|bA}D1bZ~U!I9WJ4IM~_RT3a##5$z*x(ec*)M~?^E>a*k6d5C^A zP+D?UfBVTbbz_<DqBnz(wY9yygVRK3=Sh>CohLduI>4mLDj3mL9xXWDHt_gi-=*q| zFuxo`_You(G^lSpxutC?_kCwhFfy}oaGdBoX)<Su`6NgQ2^k@XwwP6PLNPpguj^cy zT;h?1=-W6kS@nwHkvpAdD>;j2g(6c2XXhzXr?RJ+Pn|kt61>s|#@?R_N=~V+f%xgd zWS(m#qWK4>Ry6lLdf3}s6S(a4Fl6cEJay`{*It|c+H2FM;dBo4a76twsH{nMd-RH; zJ}csI2BPidClog7?v4(%)hB%jBQ%EHq{&lf%zS<3%o)?CO`T*<i$qknxXSZA4<Ge4 zSIEUX(-HMpWJaCh>XVzQhCJ6<5y;G+^ZECi_8edzG<V;(YqKj?D2PB5kM!D>!O=Uq z)A@=1X^7@0N-jO$`{+UU*|Pm_&kjZ7ghCeIMkoo2h>nX-h>wkq3J(<V_<VuTAEvgP z!jtNoqgUE$GJ^KX5tS2@eNugEbf~p1c;U=oM3o2yd~s+(PSxoajgH+(*J(A1GbfAF zV?+3Sp&%{@QNE6>IM@B~VfWdh6n`0_`iZ3F&HW>Hv<<o6&44u(kw74h1OvMUh6Z}H zT76d+tCQZ<-FLaaPkX*RHH<G1@B<OWS6<t8W%PPSU1rc;m?N;j49%U9{^ly5w*rwJ zUnmSps#f+7T<*S9k(L-8lPD`Wqg1jwXi7D#Zw>TbDv1jaiu?l*Ii#dffB#X>x#Hww zQbhF@q?Ca*w^gT-KbR^(j?jSxr#AKu_35=2GDE~X0sF`?o+wz}q~dju)t!BVy4u7* zaEusH_9oS|3_rQ9sLhb<OhFX?$jsyFTO<7!D)+xGK@$a{fP~ZigWZ}A<?(QlP{<2f zG<UI!o2MwaLJ3le4o%NccWtat$Q6T4#i#WT9(JE8NbpKVRDWJ#(YfBocQvP^AKQx& zLm&t(>lyA*b#$ms1PO)yY($z2@i1+pSW>1Ec9^SshqZYjLZJvz{8DOKuRgibT9w9{ zn*^6)d3F2sCqtL2J>C={bAO>Qq2<bD4XeXMc`*`fLL@tTJ1Zm+1vjch9cJ48p{5wV zNQB4{Wli0W?rBfu2LA>!{?R!N`Uej>8#6z*7h)N4R@ap-CA(vSx=1A8!UO&;Z_oe6 zYcEeI2OV>Tvipik3gZDpX3Oh4ZjJU|C`tB81ebA>N-hkH+)&gD=7UgxFra8)NUPv< zm@AX{K?v<V!1kQr<>l?o@|?g4ZBa@(Om)LO8Q?xdj;*{fFnUu_lg?S1067!MY7{p{ z`p=jBHcf!cw+RA@hX>T4L{_#$@pkV!=;7((%kpOXn0g;#1z!ML87j3}g@2R^%}`&K zSRg>;@B+x!ech>?09ULTmVHup|DpbL>L;)qV8#{a4iBmn+>W=DZPDz5K0b%M4<F_5 zg_3~4K#5ow)U51Ksuhjpx#^ktr6*g}O7(D`Tp;2jvM{AiadYJIxuUqO35d*zFFe~f za;xJwe=ex<g~|QHY6ZW;StaNDdinbE#UL0I6dV#79xjS(Q>xmk5+njPpCSl|DY&50 zT-C+%L6j6<**x^*T5F}uGaektPcFZ7<;n2H%3YKA$SjN>uDY&O2s++U)e5<6kywH? zK{6~nB8Z=_R-H-U^V!FIJr8;vh8%=fb?9$g2<3yR(Zx;OkMF4JGgyn`prH`UYC3L> z^qtLLOXDL`zNGqAw?f=8N5%(9$Tld3hDSsO^P^kTCxiJRu|iLeqpSnFckks0B?TRQ zH;M%iROGPq6PkMuI~#Ju-^L;Gk-&@-+ItW64M{GbDd8s$4M7cHDd%-`C@&@QMG`Cv zhDL-1@FUJ>&W8x2lBKEPKAr*bTxi7HMdAw0mF`%64i}Mm$+h6wzOw}p--EJ$M9%51 zM|V{x0_KCVNOJnNUcu^^qEx9nS}wIHps?Gr#l#DOLxLoHVd@2yQqGTJNv&mRalRg* zX|c!l?{epdp6|X@%@={Ze`4jOE2BfrC5gX+JS(=~?B$Ug?R7_A=OL3Ye!}1|tHVmE zZmY{o3=0X1OD#I1QlHKY6S4Wzgu$}o9ZKaHNsu&ECQnPxjQ94AlcxrI?D7#5Yp!-h z@}aWDm!BUPz1CWhvJ=!fiNzNNN3UF}+H2249}4*8cY72as`ip70bj`D38B>sNI9ud zUpiS?T2gVmSqar&UCob?$>r(k8JRiZ2l=v8dEDXsT)DFMRxS^!Avva~sdwa-qFU}r zi$!E^QW<1s@Iu)}DvTtAfvvaI9jf!-@bH8X&tn0sKr;znoTpH!*y?dAs36Yja(+S@ z7SGDg$>Dg#%2H*?98PkF_Rblh7|Z81bUnJGs!KoeMhqgm@>42WuaEYhEB=v>9Qo1x zS38s!BKcuSG8xB9L<C=mn4hi?fp8T+K7)|W%_~gskjPU(S&*tyUe|~6K{y)f-Glqu z<C#Ymfbce<w5t8)Xy2LKH9R!YpD({PplDCzN2bbTiQY$u5Dbru2;db#+vlum6o+PJ zfx&tC1%>%Xk4Zr~hF_>w3|)!kfplcran1b)x|3PQ7K5~xNLH=5HPYLZ@eL0-^SJr< zdzDrEFc~C@^5lj=5JpBu#YBq(&nZP6<5T!atXvx`TvRGK1o=vq1fEkVF5i-IKsi)a zuYw>ymCacW%05D<IkzA8G|9gJ<p6H!gC0eaFbS09VV>e}5RQtDiH(oo7pt+=O@Sh5 zK|x_*QE^E{glC*QDTrU9>QMCF&1NqJq8*l6ueysXkZ{Fcw7=pZa|y5PL2q+#5IdDA z5A_THgFzVg+rl(xd#zOUfr6Oq;?mNx^6Gex$XKx;ht*-K=)ITAUiBZ!WB%xA%J?3X zx!jURy%z#QuyUAZK(tXeAt_NPg`RXgR4ca1S*_Nbj^T+SlhgAH^TJs|eo$G55~gIA z?`5)Q!wX)yg}am}%hW2x&C%X7xo+?RJ~#K#z{Su&cIw;m;Df?gY<5CoQc9vAT?K7v zOI@B!DwSuKHz^eraWDcA3i%MNx#v{u4im-DofMX_K-Kpj>P}`KT?qQFd}(Fd^-*YQ z*YnV8{(R}3YaKE{0(LulKRXWe6O)otQloff8ui)iZ~>1ku;lZ^aiwj_#<KkMw4CDO zmsBba$X~x2?GN%1Stqm*iYKxT&yPhUUUFH>)zN|TWxM$3H9>g)9rbZxFoc-gd#7)7 zB9@lQl0;#bI*NmMfzc`HtW2}?<R~#OqK2h5HFh?<4ioi#U63a>J-4Cr(Oq?YhWFc` z4PD{Ip;2hTefVgezodDjQ<1?7OOd7d{^})62IW+Fa-g6_A?F7qWE(@Jw7k3^NhHkg zP>P{Qdqx|CJ3uzNu(9XyZDmc`zL`+#IdO&O`bV#~)e4~2Px25Hj1G6SrSJpe<lG<j zdq>M;a%qf6Sfotjg=Mk|=r{r)RABm@AHvI5Vcn|_)A-v#H@5UF7OIqPas*xf$Xwzz z$3y0E(Hq<La6*S3YG5~sm>(?MvdfzlA0Hv%g;Z+G`Qf<`*+oT!Y$dD8w5B9PbOP$C znL_utOX3eZHYjuBD=rR0-b)kL(n0%3P!`buoJ#zGi(cQp`xw9W*|0*XIxP<p2zY!v z6b;Y6pl%J9WI>e~Q|4ApsL2y1z_10zzs3B+cqfQ^Qgz$)k^XZ<k*lGF^%l!(Rkufa zn)3H^(acS|ctN4VBYJ3Ll^1IAGm=xX3Qx4C03q<>iwld3u@@>Uc~z#>HPPZG6(5E% zT~E3sAxD1)VV=BRefLp!V{U*82=C=fD_XCO4mMY^xyWMuPJSG>;OQL|tHU1Z2p*=v zAOl+Ke12L9co)m^tITREqFI6(7)Dws)b}4}@Dq+~1>vB4h>3fe<C%U7;-SY$EItpz zqxSlc85~4fy^}2q7PdaWrU>XDsnhtGWnd#VkyKq<nacGzBCJq@nKxdX=0zlXW3{N# z^ZlbY+N)&SXMozV@Z8f~5ASPFrTz}H%$>d|Nutn!=R*n!3>gb~nN^jQRorT7O>I>{ z0?*5HpNFUpTX*G!E-*kEd+-<7cC#}P*RH|llKAhfK+sc?UZ=bb-ESH0ZFU@tmdElF zZv8O`Lj)&WGh);7i_6N(ON;VkF`~nsUhcpD=pA%c$?YIrc|H^=Oppid`I?Q$qO5ut z4|JW*3vz)`*8-lj{L=8~)z;d`_c(~UWw%H!i{z%>d48osA?$Eewny_0dHNjo=Wvc4 z_VM)a^xU^~-FI90DbQn470T-`uEz3XWir;zw_w+hLqyS;KBx=z8NTl%BJ$DLeB&@S z{SXJyKH24)lA0RMmEJJi)_}CBx?05Za_4$d54!LEeZA`s-~Y-EJPoBtRp{;-24bME zmBo5)0XG~>!o@vwu{>qN43PB*$f#4|L2YFe7tyyL2!j4Bf)~?m7`fa5W3XAu93kuX zpEqpWxZ#(d+}5sH^MmUSj;K@(jbz8b6N55D06nXmzxzv24$8wL&aTG%kdLikeC*1V zmNvsES9x6K%Rx-n-9E|CltuFcPCql;?Norcd7g-S@Rv2;f6w{BVcl+jVL+*}L!s!p zZy2c+isC^x+G86PJh3IN6o$(;I%+dK<|czDqH|7Z?>+80TOQ6umfQC8a2Fge;idN* zUfk+Zz#w;~Qdt`*<Q_h-ee<T@c6xC{!njjPg`)e8!O)q^3&cH{^yqFVK1x7#z3R?L z-}&OiAEzcG%66fwvK7yRj;H!@knQSS2SbVeJ(L$%dDCFH*RO7GSFk(G)Gg(Sfg%Bm z?;sEbrBt;lH3RnzhM__cKT-+<rVPmea5dFEvF!Zi(c9`1*}~<{DTwSBpV!z4)3vh| zv0P-kVUI@)7Eg^0;D***fi>3K13D$E{avWkiZfM38M2Jxs<W!@!8@>T>T*f2Ff2JW zF<jv9{=-+x=D+jK{H?)-%16%!o6Dpd-;^TCZgE<5+c0?PL}nltnf$)@P@J5VY9ULB z4&Vmocid+gEJto%z1*#boeesDXLsMwjk}{P!?fEiS%EyiL;JU^{o<2PR(-X`?dM<I zzFD<$=_eb5E6$zH4|JKF3bToX{6^iq$CsOHlAvQT*}CsgjGUcHmPsRF<uR)0;_zdR z!Q{msuw{d5nDMaxTwb_<wcqulk5;eUxbLtqC_E-fo?BF$pB{N^^QTKb+ri)TmJCt$ ziDi|S24V7ix)@^5)Oqthk8l|$bw%32FSi}#@g*@ib(b{#*KXapbL-k=b#rxAq?pI{ z-R}Ce>!xkHc5mCfas7|3Yu$d{ynQb#rs!<j`CQIVA1s|GM-;EvTs*D6(q5k#!bN80 z%lGUTq{vvQ%Vnus7k#?b(~r%C{#YCk91;=~AQtj@Tz~If8`rJ-W&O`TuKDikuRdS( z*=MWZ=hII=`ShzFxBDlZ)G2>nn1-m|Lr5D}EUHtvJY@IAw*5Yla)>6GbjOkvYq##$ z<9^8JD2vUpW%>CWf{M6h)5c%d|NPUBu4}&k_UkV{7px|K@%_4Ax9;)`Nd96mta$7T z%czAyyQ@D_o*c|W^BmUhKfsO$MOmuniltxtx_!rvU3>QKKX73GzCF8k?%4MGmd%?s ztp9nP8&vQy!LQc*ym`m|Lq|PbK#=BgI58jAhDNWe8_MFndB}>iaO?hqoLHG$mL~pT z@$#>K-MNRgm$lD!FX(OGwsp&9sMw&l_J{Aj{rU@R;MZ$^*|PJ1H)qeXWw4Cn#*>zw z?YcX9rQ<|#+(r(XH*WlR+mE*IcRw1OB+E$g`Etp!Pk-3BWADCwd-v?#wPXA5TYua1 z>%S}{6u)s@zje2}4`<WTWw4yIB{;qEV&4N;2s>VwAmkvYaT83;CVlkVzWtu8z*u>r z|Avp2Ec@W|@7HbEyoI%u`rGDBze39c#q!1K)vQ&l)#!81YRc!|{rKzedpr(v*Djfz zg=p@vS#_|u_Nc%0cyUrN2TdGr0?VrQi`H!4xBq}QJ0Mhe;Fm9!FI&3oz2z<+vOk*e z@rNI*WUoNW*(*#|uKeKRPuWDdY}~%r^9cK^rP+wS;&5W_@%F*T5Bu9rl%zzmk>fa! zv>=galb3$CdDp&u``tYbd2ZY2w&t7F?9X2NWaXj-@64MsYx>klZ!BH1bjgb4ix<D= z^2w?%iL&uJcI=a-ux_{dNOJy3#n9tN1Bz4SvS<#n!kVz=N@dv3T=>ZkKX2K#V;5^T zedqSgZr`kUdy*BMN-{GU&qdbU@CecVA66_{_Q6-{w(L6K&GK5gEEh4pVI>z}!N;(D zqcS5tjE$H$<FG%24$H29h=H=Rr<hKjWY46-zE)FM7dAD4<y_m{5wg+-<-nDeh;=KL zto(Yj`(f_hWy|sqV=XVG@MQbI!;x#c^R<O3(a=H?E6f1r=|ErEjB!jRi@~CkU~w3) zSDQ`%IlHigoQg)}z{4lEyBiYrfA-$W)xREIzjRSPVy@w&6r5=5yZ7XFPwT0QjD#py zXvUs2$JwUQSadc6uUf;RHK<|Z#<7q?Ky+$J-38sXkw?R-rn0QyPv2d(^!<-Nde5Z* zS$^f8oL7IT`_|~gLG`)1Qh8h`3z_2x0!1>5!lja7(VC3c;$cmng&aepQwk~@@xt}} zenmsEocG~d2rYU4y_N43p~;{7CT3Tk*Is!%db6jkv8GrSCtm;<V1fx=l(!&p$;g6b zJ`qd7TKa;7*p&Q=lb3W?@B)0x@q*N2E8pax@$;50D@HaS9gNK=KdtD4CHpJ-meVz* zX;S$C*v2y6#MG2;Ho?^B_X8;jsYMkho7DrD0;tbb=O%eAn^TJ%7HtWY0bS5tc`$l^ zSl`-IS5Y8Kk8*XKz(wQ6jl<gj9M?q1q<N(^4FFK?j6A;4-PTZ=8TH$Ox9ZW#=|8Xm zZ=7!Hz7E{uYIjF-1Hh!LjJRX#m%lw3wq7_)nX~+tqp|53h2_;JfcOmF85zBOS$Vc5 zKUMhU+}S75`ycL&kmc8$?dZAw@CksMPDOLmiQ202qWtW1+`6S@<rkJ#RM($A-=^)m zet#50l;-0l8L|7{pEI`sInMvqFFw7f?yREw>b=p?2RDbhHHu5;n;K4@VAnexKhe;5 z_F|j5v;X?Nk<kY?`qa%Q%CeI=U(KJ>gdDAx{VGn%EUG=zs_O@A^yKlqn^y*UyL1{g zOXaB6b@p5yzJB*1Mo~juit{JRa#DkTT|DRYbI5+iM_WXR=>=7%E-3VuuibeFTOLOq zJ-C05b=Us>gNHz`MjqX}KG>zY*jQVVEe+c0GWX4k$mWd?H*(`JSZg@ns_N_;x_0aC z{Rf<fwh!*!zCPUFt!Zy=tS`&YNEB^a`SzQxt;lNn3b%tniD@}S74;2gn_D~7T38vj zhF-Lz?c%x9$E!;6GLl2RT;G3dwi3NQb>2rC4h6(Y0Rxs)RM#Cpd8(n|)X5WdHI-#W zu=67!`0)A<=Ffgpho()Rzv7$UeI*g`$+C1}jUqccGea&-hzjug{hJl<%z1rAH*&Up zZPtS2AAh@kkC(3>7gkq~`FibL|Lw=i-<|X39Opq~V>@}q8*j~<zhLpwCC~uPe|ygC z*|Qw)q4853r~DoAl>aRX#VKNtHQr<cL<#;Oep!&<dI-1<R0uLrY58;(%?v{(fI~fq z3Kf^YM$kN1TJo)^mcVNmP^Y09nv=DNVGcS#m0&lEl{M@MvgHGlq1sr(Mh<uiQj^R( zkfkZnXkp|7^u*2q@QJ{Q3HSuyi!E$4!~BLsGHpc+QwxF(Sy>a1#c5)oGjk$lB>-CB zIcW9B7UYYFYEGikVfPShI&yTH$Z|Hp&o~MkCcqV3(%=e|f$G5XB<eY2X8~;{Y%#F~ za037a3u5kL%eR4Fc7SsTE`!O6osAW=rDTgH<YZ>?GK~|F1QOU-V_d~#u<1^ifLU4N z?2Ku`UMQTFVqw;R97q;mAATKv8`uJj3J?>8WX>{Uo3hLtusayo8;}~SLt?;4VC+fc zWI@KJ+TrgZ8@BL9I&>2gpw)tu<IF+A37Q+YHYd>t2|~evu$)4wN6sKlXIk0XJ7NKF z1Cv2E9S<1P)C`(E9%&LrnYd*iH{O)Y1dwKY6$n^DDVPJwGzITk5?>RBF_;tIpdg(7 zuPAc@>=}FAiOCpKa0Ck&G?E#hObZefdM7*ZwUYz9pXogN-Np2OM47G&XHB$xX|%m9 z2$4yEGNGJd^OU_4cnHj6IL=<M@E=ho3t71=n&p6B4qln)U~9#sk<Ec+VvDS7iSO*J z8P+r3UGxtu6J{NTcV4r3^=4}(olF9l34XyMkSlEA#JLOp9%k|pbNSoY4qOV3c38m- zXr?hggaYhun(^*C|3owAO|fKJV<+PD;EzJYcxiy6y$!==_QJ(~3p2%t?lJ>1`YJi8 z0MRWdbi#blW7xgD@NZyd0HPaShs@i7nfOU631}vX3=sk4-~=yrn782XY33WYI3?kF zI)x0QB6ABG)5dNr<@^P~$a;)sPM$Jl^5j49%-MvwkP_mMY>|y<7T|5zL<(Op?B*^q z;>^iY2%I@(@)*vfzHW_ugzW+s!B7$MXbD9GMbET(``s{P?gR)F(@fTDX49s^`v{sj z-HM5G<7j6E*~>&!61EX%*NQoNVFWU<bDlhPI(w!Wu*@mW1j}@0jLCv=3>ev)QwZan zz(F$>L?A27GB@vahbg~@$ALY+xxo$-fSK5hkOO-tKMR1Frch&Tuybq}6W;-tNft^( zB7rzCBq}C>l|+e;jfo7F2n4V*#Xkg5r&vMOz(|NS3KeK3_=C8%v$UBzFBnach(*GH z@T9z&rZ!zycTZ1uSEpWe9)=C^VZhfU7-vpq!uuvp#Ll4r&NPEPy0-9sduztbg+XXK zyeJ?lv%bA|`08+9XJ>aWoXddS^8<rJ{rcugS%gq1#yHb*EUVZKm@Ja0aAXObWy^GY z2jfh!C?us;GdMhWxve@gIW{gOz2dx9%hJ$*kM<4>Uv4c+2oi~c0B6#{>l2+Fz#1yx zOx%8fHQ+*UI>wn|QK<aP<tqc-T`f6b0Rj=%k0p?VW}MgYG-Q3x;BaSsa<Eu}ai$Ff zG88%#ADIC-6RL$ZE>2s9^8&z`5^+#c)8LiMdX26jN&>QgGnahu;~_~{jTWTT+Rpx~ zy>)S7F~*rRoEaz)CXJ4jAP@)#G2d7QG*cuBuef{_gfzO-AtLcBn)zEmV5MHDG1vEB z)fa?`0)S@HXjZn^1WP&%(@Zj%j*A|gH1QowGsWV>)@y^EERBh_Em|~2Gc8ewB=oFK zq%rFpx^gBKwj_Z)#u9^iXALsugs*V`y*>}qOi4ga@3menTQfml8UPdsXy%7=-~G<_ zkWicsI_7HFU89jfV1TpfR)kM1X^bR9wg6vak6GFQ%rpxS2bBz8>EdZ@Qw11hdii?$ z_#E~<a>U2imk(P)B^uN2t9@BO4he$@SJSDpVFe5}Us&Owv|&tHidiP?X}vP6S93IE zZF`Jx_W>_&pQA_FM_7KQM-FpBFM*{DonEiQKU#I?)&6XW2v{bCX^q3unn7~_O$!PX zAC!T$<r`mN>KKrBby%n7YTnXz#B#m-{Ei$s%Hj#d011O(J|EHwD;#?DxvIh}7_>EZ z=(YN*{pn)NG8u$z4z{o(9ayFX6<0RMvi*Ee6^o@q*Ys+>W}+@#c+~eOK)_enF;Wtv z)asPA$$?xUIVi5UMb~+wGf{{osn~5$1!<Q4u&9V57GE+ZEx;sG6sft{r50%3(j6DW zN`1gf!SKka5Mi-icRmTYHT%dR4{txNNE}(G>A7_=LV#6qltK8?=`(<2!tPt_Q;5MC z9|Ic{2G-uWtQKqLq!A_t1&4-)MMOkJhYI5q`o>UUSiHo?hs8O#ci#c7B(PXBaH|vt z6d5*EgBP(=onhVA;^n2)o0wz@ldoKb;Ad&)!TGHA6d{02EE^dW6%izeKHqsU3^t6( zWKn)TA&CNy{d+ycl4||+o_HZ9nWUHY(I#PK3WKO4RyK37a!}LVZZ%7@7*?IMigtw> zin=2&0E!FKuaH2YSl*)7rVFD1iKS&E9QBUOh-bO)@eoE_>bp}b3<T6T=3^$!3Dlu= zF_xOcyBK5&5{Iv`G*()@@<dKbL|8;(TIqRR=h>VH30pW#9GZSgqt#vr3{92eWx4Fc zBS#X`<)L1C4vWgb`e*^Tg~}uv87rEND=gm|>jAEo?*qw{2rBONsWm!Pd5l;HlpQLL zFi3W~Q{UcLTTxzfszs~QX!Lc$xHM1)be9|HElQK8C-}MZ)3yC~@&$NpnKD+GG^<7M z0%M3kSx$v>vrgjRw!6>_T>^(kCWZU3i4YbB6smPPwtk!rs)w__N|*@15l<3w^Z33A zY4S8FkC&?Hx_1^@C@5BFpqwW{tT5+-{J%m4_HsLkVh68-dbBViB`r<h8-S5_cxZqy zQ>_((a-A?C%cz@Ql;j<h4o;Ga(sbIJo#8^POazRbHJv#d8~(D8XTS#5iT;B0+rw&A ziZEK9mX>_PAA%(uVlXO5P^trI-MN5p!e9&>3)pNRy=ifR621D$)p+a^+#usvq%&vz z#b}Ur=J5+3^lR&c5owSp)`$NRIK~77UxYTyNhVCmBS2<Rae2^T$e1)RxLKzjyq$(2 zDTzK7<;*t$OuaPP5~#Ox0Ke?vWwlhC0>bGLKL5gvk-{=)t(|q}gC(hjMHnNMRYv<H zq)UT^<uDWNzn9CyBolmVtU%c4e@RcrAd_DKt52aJY&kPM!aK-_8{-p_5=0qbvX$;+ zkSI2%1b9M4O`>;ne1I^Yr7>0aKgi=@koi)WPT2ga2&ZC>$uE1{e<>&&3rG3{$Ho{r zU`n!B23_cQs8np7vtHkMCQcxUmSz?d<wtVG0$^8gj!ixIAe)CdCV0VUxFr<iE5n(u z!CpH)KmYM?TX?XHC4W0T^pGTe3^PiTL|HnGP7fR9(q!_CysC3rbxoo~AQX#*5UT|j zb!?4^`pVr@phkZx%{id|%6`y)&0iq9dqa~ZO2$6tdhjqKC3s-8psG{foEIq)utk<a zK|n%<Qj6Q{jQsM3cAbs`>Nl^&VoLh5p`u#?Y9#6pw1rSX4Lr0+965MT-yjZ^L9O%M zdo&iaMyX7mB8h0zl!XX_VpB6SvvV?~F#-JO<ND6Aj)tc((Ld}A@y6mr_YSqk3X8w0 z{dRkKh`|oLG}^1l5=5k?WgOY;D`82UVJ##jSX8gh5CkUW0KOxTSVduSfT&2L6+@9; zesWrZK_>3Oa0^beT|gLZY-B8L;MmSQFHzBxYZ|3Y7@Uy7cRhF{7UEnMD-oA!GXxRY zm<qn+m^FoAf+8K(z40iW0HiM)6ROiPY%gwVCqm`7dJ}`p{k-tuM|!O)KR_5N-nQom zFEJr1P!LwrRVj?fGsZOrgH^0*)7sK7NdweWGj-QUH=I+$rD*KCEU8mJGfEpPlI3JB zngwg@!n!}M!Q#i6j1U-;!hR@$D6+6c-w_p<^U~hRswz(PgxY*@GK^T%H=dRV{k8)j zHK!51mo1Yva~lZ%t2cANBy*1-IQ;5pH#D-^minS>sVujoL7~&?PY4r>jcx$6$E!B2 zsf`IZuj9iIrtfKYV3_|7BCf$iXXw%BvvB7NgIC=7TG|>o$YRGHVS=FO**zUgW3PqL z1q{((hyks2kuV)^C;<3a#jiH2t&HP}j>8DjLalo+k||0$3MRiSJ!`t<JP?L4m$8em zoDagQcXQLi#BDEb1Zqh7Oks8z;Zj~Tsiv+fP2lY>uGWK<w_Y>}qNKhU<Qj(=Fhp>` zSQN&h#E{#X3E3y@Ig*+p2_JlZMJ<8RVzD5rx~i(0TSKj_t1e0s`uZF^BsrnuYDns9 zFZ98|@_4Teup|o|A~EiVl3EIiuN;aG43T$v#-_&!l5T^fSmUHW1^XE@3QEc=D#}Xo z)8hk<`S^P6_}Mq4SqrMyUJOTzlhQ-?VH8GGGZ<trXm0?@{FgVGvoOirD@jj_7Rc|u zxTaAHVd$=m6&&{QJH~;nSV#TXKIR9v|FUMAFcms1s#<%~a4lXKpO(hn{T3k7mmy2D zng_tq0{U;FF=E<aj=B2?bj^``*-c=AAZ)6ylL&mgxIR>G&;2_#u5(?xi5q+d%8;t= zx@Q=O#XW7pq2Iw=bDAYFezv4KF92CHn7a~VBuhGQ%$)~ApfigS#Pt}S3~FEmHcOW; z;_TS4={LX=>wo&u&DC|?Zl0t92JULj@Kb{-OcVz#h;T3Fn8wj2o)gVl4I|^1vk)t~ zCB~S04@;pbixmbnJ~Q0wQLA|xl2%hF;d}n-wstMY)!~=DY*A1J^cw130GV|XNg@cx zd2Poa6GoiGARERS^8sYS&;!RWOlaYZnB@+40q%ek0|XfZh8K50nyZ<j)gF(5E&d00 zZr!qVw+~M&No>@q)qQZ5rYl7dlAOkpkN4mEEn)On_9>GW0my_Uo|iKq`1=)Bt9N^b z6TN%5Ah;Sp=KVn(OU>4p>DsGd$|Pb59Ym6l)VdC>e)s`!%wh?2>v%vDcn~%WP{_mp z2C5lj_ENx@6ru#N+2HCgH|+C@!_xBjAbxl)oE3R?cc@#dhW%<99qgYk%}&oQt80c+ z33mZw_Lqf<BU96oBSmZv*RS7ucfrDiQ{m5XIKp(?k~;BSz?d}XJqRCJ(<wk2x9^8; zm?gJJON|NQhZZUcjCuF^U>{6oJG;Ak`UbAvy7vSy=AG7@VBs;JgTMdy)n}is{>F9P z`t|F+`~1UY3+KNzbD|ZU_Qrx#!r{aO6D9yKcHD8mCoY{WC#R)G`|%_(C6|UrxP}QY zA?CpO{74bU{l||#`ND1U{v*Pmh}fjG+#;w%(VQLMu2}e{)5LcHVp5Fu;u#neqN($i z174A79Qle2&#!m-3xt93dG#&Yf$O)85c6V9c66YCdt~R2-@0wu4iNJ<0%E%Uym{Lg z#LN@?_R$gyF{uz7xR%2l7KF{rUG^Om%b>iMr=@LM^4afRN4c=485m}0SQtSu1$>t8 z?oB_f`(?v_ftc%du#!*dRO^=j#I&LiQ%LASt?1?eG2h(bek3{_kF{hwm$|rZ*|F!K zx9?Fln`_HH>g(;k=f6VCpEhpWeJC*HtEI5i!7#4Fz!D3rePD>`=6;Zy0E%gHZ<qJJ z-1t|Bxt)NRKt=xzV*Uou(f`ng%Rq1n6(-$~cdSU|BOB79ZSJ1@__Xx2jDWRESAGKw zbMIdEKQT<#^;>qk`*QcazcK@k15+WxctHXd65x-}V1#M+@lJOS|FD#_Ea~BImc9Sk z+D-qAFu!%%fN>-5w`CY%+EQPxlUXsTjF$-W<E;nW4{?Iy)05d7KUwzvhhP5i)2|p| zGR6?*+V6k>f4+M4DuIz-ZUfrLcUw9eP9CoY8RJqGEHF{YV*u0Ke(~C!2iy<(a)ZLf z2RD4R^8NSTU%v9gk2#-=2Y@MXG5O$wk3Rm4V3$8_+P=^0826j^fL%_e<8>fdRU(!J zDX-Y21#QZ*@3-tZaKPQu%jeLJ&Ffsh`+~jtwa-3SvT(sWZ@)2P>g3t)Eq#Cadn;D1 zbor2Amqd}UKEv#C3LO`TQPh%7`7^t8n7QcFHNR{HP|ey)-@Ws<b>F(o{S&*SS~<V| z&eG*8T|WByX95Es`e0cOVi=rhMnxzVSQn;(7ypf2!usD-SOWYPyaW(8o`Y=g%D#>L zq#1J-EqD3on=J>A@b|wDG?ZzWXjFuPhUHf#jRvja{{mh@e{5WecC@v1dVSH#55Cys z_v?Ef=fNLHFbd+;SO+_6Shs;D{C}pGR0v+<;yU=>l1iBcv~k6!AHVOCkE}LMgR(I$ zGvi#tdOGFb@a3#Ad`YaoBWML^kXgLaic8ayMt|cSgjT-4{DT#R$l8Azu6e`?G8hPO z0Zst1ki{#0$tI5{k$_;DjKNT7QH_w}<+?i+*3~e<e1CZnnq-)61xusA72sX4evPpL zjS6Q;@%bpsFehQCfR9OGyvzgu1Fbf(><+7b)^irrBD>l4V_Ba#33CC=ZD@3gDSI4L zIPL`a#Tw@@!Ia1nPEG&}%r)o~r+M$zqlrtVfOjy2A&3X$Xzc6{8Ne`iyt9cZoC|`J zJ(fnUgUJbzYbt&EyYo+=i7u~O6MvWhR`%f@L3juSjHB2zXUwmSNM?-4;Bzwg<u-E` z&OL?f4fCfmfQtZf`4<BMqGQ9h9%p66wzda$^=A--c?^{?{hftPi1yx_PE-ou8XNG5 z5jSBXCA2pIkmWRy?M%bQ0O4U!Db8~jEI5Z4i)T8}FoeMv%MqU8I1Av1VdPR6WKpS( zvllLGhLCx8)<il5`~uGsEXm19IMLdPAXIR+2>X~qcb>Cgfolt#@LBZcWE&bBp0c#U zB#mvyvuD}S;W2m)5E|8b>RSsIcOa@^$vd;0t!NbNPA1EWZ_NU3M;x=FSx$O${=$V? zME!K}+}YC{;BP!YK+^<tL}O85cH}sH_WXqlfK8HJmcovQ*I$E<&yvfeSi%~_^f%_r ze|O=MrGv=aW%1%Aup?p~d#=U2dGEZt2ms}RJIK_3feZZecW6@fk7zO$j-s+m#{-#S zo55K%GnOeb83rzCim?^O^?WiSvEZ*xz!RnzOcFN;XbD;1)5cL@3xqlFM!Y$|R153~ zFh4#zXFT(UgM-Pjq#NO-aeITgnTZlHK?gsE879OYI96!G#P||B?BzBHK$CQ^12Ez% z)XB0H+p%qFQ0(v^@tnD-=|yA(Y9NNS2-XRF6GDt*#j=JIYZx{HM1(@c{1ozb9@(0j zVpVAKAiRusF=7-l;FvyvRN1y<3=bKQ0CrGdE(tIdDvD7btKma<#2yO@o9qPHh4RFe z!6=CD!^^N&fJ;Iaa2>-njNqe?L*qS>sl7RHL!%S0;ShQdQ52Gy*-2z$#J3=5Oa(84 zI?IA{mADMp&;WtQO;{7)IU@?jSs{QX9P2a1MAFQhJXRgxYlvuyxe2ry|Ak1Jn8Wcx zqN?Er50h9kf=Ci}L%TaBOLdq<{Tm`V)1HO_>Z?Z~2S6lEOvh+0Axoh;{cngQQHP<y zf;8ZMfUrO$Ctxg$p()WoknR3=MA8;Ym@o+wVv_j;Ad)6<9255>cF>p6Da`)`k(|ba zgOAW#08s@IGZRcCakj7swvg=jKM=_;-?qmsjrB_ff=C+k0}7TvcV7EPB1vB`8MkWS z5ZtbkAUWps#`nV)(A)eoketpOOHJ4hKoa7G2K^;|H`(T&futpol*qb;IRHtBD57J> zNp1fTNLs&433~_3a^a7U80j{V5cA0Ye!6lt@j@sZ@T(c7k>F{3Y!n<#wth(?|NU?! z^>y6E*kF<lr3^F@XAp`9RH#f$Bmeu^%IOT~U2HHUhTQ`|BMJTvP6kZ&CeX<LcC^wN z3KDc-t)Fa;aU_%j6bybbc{<=oI9j>-+h2F?V;?Zzw`a%3H7pK6BOz8G2T;%?GfX4F z6IcW~cqdFF9fdqDo5K?Zghs$=ikPT~@DQ<p&EdfINuZG~kToz8X3b=CGoX=#Kd{kM z=G>+DY$cD&f%UjdI6SUYv$b@MO4Zg_Unq+T<gmHiZ5T$vMmVD{NER4Inql^42f&On z1H(uTqz{u<UDUy0O1(;@(c#q`t-iabw_A0lBsmxiAz&n)-atm7G%<{PWd_A=0l-Lq zE>{v?(cXKxr}IKdYHWB!TykMUTN|sD)}~PDdiuI96vl|SJbwx7U#8$WG%gwv3DZdM zhS40_?DsH@<ZuI$Pj$hTA?2C0K=8vcANUIa66vXSb}Ox=rB$KnxvZ&<6Z3ckkc7O9 zIR^tt6C5nqITXi503_KwQQWCsI1=C5RvW_S@{Q*!T@UgDO51p?<d!yNXMbl^6px32 zBs9>*LLpH>$++hLQ$+$u^0>hzU9f+vwWYmQ!sWjLk{kHKqIO}cc}LfPIwz3J#XyoU z34(}3!a&j#yPGh{VF3n`TwZMRV2_g3YSMNtjK_O*ypk7ivR%|_rt0l)h`>6qDG25+ zxRfk_BO3h;UZYcHE)gONf1V&iKd5bMVYN<B<iSo`MA_)S{H^(4dF}P*rGSumOPhYM zBMA<P!Ac9ZfN(e!)?JMLwqa6jh~t$!Q9*yds)gH1O5hNuD-XIKU>&sZ^gQV9?jLla zP10(r8t9bsxmXAX4h{h_fg~YkmNw8dP$m&bk|Qb@ysT*9w36E{gtK?-*>}L*%Zug7 z@iOr^DrstCw=&ul3fSk3|1v|zKu<cK4MdVyP{Fw*VIpa24&{e)Nh63PTaYz)xgBhK ztF1Zw*nTfB4^MB{<j)g|#A1<vCpp{J+NNkZS)3)4XBX8qE80L77!H6wVBBL_wt+P_ z2v+bVk!8#|c?o@kiWYvWb9)Ns(7{7~aM}?BB>`|AErcI-v90}LSuC88<WRVx$lNpS z9mAR!jz5T!XfOeUib$ekCTa8|<bEP%lK$Kf#Z^^{p!KcxN*?PN97qPuG14jF<S5!3 zV&QPYkwfnL4<0(k<%N{DYOb9NW`ioW4H^d;nS#$_5I%*q3CeVgBss#$8~PS;>zrf` z78JcaUm3`WXjaq*Z~~)v?gtJZ-MeGQ?qfV*Zfp0oLN08e#!HvrMrf4C7)iq4{4si! zj*+B4CvIQ>!k^VPueG)9d>rInJO)sM#hlQFj;26vIQ%t^P%n3JEPLOs?fZFxl8&Ly zC=P)njgE!#!blRA7h$|5Mv`3Nshb*z$i;2#ina^qFSI~eU(68V7f1pEBti}^=}dcD z3MV2dIaw-;IdmXc7JX#T_I=!-CjE^n4j2ymw;;ms-WNx#PHdckVzVWXBqw%Ykkx9{ zrnp#@78e{C9G#rk(5^U<7R+aJrtu`nHLY!J4MGWlL1gK%9-c9>l)wYqy}0>`VSP9o z+=BNj5Cv%E4-4j|#KsQXR$wG4;FR3fx3sojDh%Utcx*P0%jJj?>pB$Y>&glXOKZ-w zwYRn^$~lozDRFo+D`Y=cnvxphy@!?3)^#JB<pYbvxM9Z?gv7*161VeE^BI&W7)kQP zmu_^lwl~Fc_@Qxu?nfXz#6pfByQRIItr*u1^}|_F3_Hs4$xzHZ{SQV<Q>2Oh{t2zB zTc^4B3?$)gSY#v9F_JWf3OMUEK$4D}h`!<0wliUz-~_3Zb&yZQUZ8*@YY~BP87F28 zrRL<v9T26afHF6^z3qxN2%l3lRy&9nIwq2)xLy-6NDxVu^v30uOL3fVKtS=HzGJZx zCg$Wr%jevFk{_IwkzqVunREC^GDt^p@)RutL(!OFK?h0HXfh2GNn>LOZU1zTX7JcK z_qy83Il)p$6yeTd1(PuDjEoS78{0&!PKlg^>@nI|Eb@SSB?`rj?Jd1GQ~W{M1n-T4 zwt`H<MDk_Dq0Imy$z>Pa*SEy+5<oaL_y8Zy4r7!b6%)!SP++4Q#QdaOBke2;bB|6< zlyC~#TU)yCWcp(wi8l=4W{6Ck@n>mFB)RO8`(0-PB<vI>D2u{gQB<B3nq8~*Y7sXw z6OM|Nlvc(b2#XSOGgz&rE!}ssj$tDCFJX*2U)oG1h$O4%L3fiVh@HYn4LKl=FrEgD zi;v?cvRlVPm14I$D-<0kBH8?~M0sw0cF0jaM^e<<*4o<Ab2r@|14$E%R0x+~Ao;Q! z33)y%`@!XNL1K2w+o^&3`B7shLKEY-sqN5;UZ~1SNlKEY7oTctDUF62#p7`}{E)0h z=ypw725u)F9V<(t$1#t56@ODOk7Os^8g5PI#$k{9?>QDjoHtEKN(pBdcPJV&Lb$N< zX~|&=qKhuJoh;6l0aL9#-`>sv^{ZDR{BRJyj1)RXhPZjfm51sGJn}7W$mQFL8eTv$ zR5`D0haw4@nj}r&2cK^(kg&y(Nx0U?6X1MIXeCQwYV2mX;CaP8t>hq9Cq^Bxr;~}* zU)6nE0Fv|kgclxbTV?FvB&p1M{XxKcaNsmGLCmdak#a<FnR)pbx0jTb=Ed{5IjwDC z(Cr*K$=eFLB%*79DsPQ}B+=7Amq@k3Kyp7fZ)B+TVgg4TBV&KR$1?)LJQ+@T=eJ4O z!RfiV1Z~GKuA(wGke%I*b+0@~<zQW+ae}7A!X5+3v8E9A6k#Cgy3;>s@Igo0r3?Wl zfcM*W&!e$1p+a_Gsj`?8l4T5Qg7L8`Osk3l`L$3{&018CIz@iJ5z@v+)?zXSk}&l% zb`~@MlFq;GIKruVI@r?IenKkYLMI3VQEqVd8AWr5FoTtER!{_fC@bSuOsLA?#lV22 zWq7oJ<Gls*NW9|^k7`LXw#6C417eXuwd0~0o3?W#K|>=NXkgpUR%XlN6VvnRF0{8P zsyMOuj5AnUTF$R9t*nX=G_><!_@W!_6b2p!U1&4LS`Z4Rk!H99!f{~*G;;d-?VK1^ z-qYJ{Ezp;>E1-#nUr-|yIUHFLQ47mJ)~vcL;wZPWo!4s7qPY7=&W-i?4TSMtLflJ| z7??)l)|wb3FbEpC?U*!xd+GU=z*dq%%1OtEG|S3)6{O1Q@)XtqA6_Z8^4jwTc37h4 zW)L%$7wk8*$21aVcSfgD;qM8OwjD}J;0N{p(cdCzHCN=a(<)%Y0=JS{Rb7!6$A*)I z2l!RkxS{7-u{b&Uz>l!RYC`Pagh;Y5!bq6W;MR^o1!_Uwx<4{CiXC?o6veGhxMD=7 z<`x#0mKGP}NTUSa2i*5<ar2U#hE9dj(mM2fFpL)`3)uMu=o-BV11|Fa$KG2&Rn>I= z<Aii~r*ue3iy%mYq=0~QN|$t(C@Bq60)i3(QX)txAdLuug3{gH{LcmN1$}sYeB=9D z>%Z1J3!OPLd-k3?XU@!?{W*Ki2^@K<#Q;hT8VHWmGYiT{kMc=fI-Kn84DKT8?uhnr zuyb<u@b+?dcCxdzvjbkSR50*M1{kAucF!D5$NI&kr+XSR0C*$<Xq$1e_<`_9pa)KS z7X^U#&~BPIr=+Dt`lQSqFMjF>?t=SR7U*kl?Qe%_V{LArsj8%??G^I2#|Plrw{$!j z;~Sfv9%rWyv={dDegpkL!V6-1(hs2R4*+AK8(TdBma-_{n4#nC@1215Mc<th;Asee zHGub(`fU|uWhGS;@4%P9xajN}**)$J^@{~&5I-|%fb;2$0BF=mbRazPM^6B&>j@rd z>huJd%A)-q)bAZH4Rm$}bRl(j<p%oNYAP!#`Y92rn*;Bb6?J!Yb^?x|TdxBH;{nF8 zwuT@)^3sVK1JV?+KzJm=>8=F0cK}R+Wn}3GT7Kh$eKSXo59bFuJN&z7ySpo*0(@Mo zjdgYOOdY%f0%B``A^m0X`1nh*&%-33C8=&^r?>B9hQ~lfAUnY$L65_!P7dIacv5CI z;U{Z*xNk_={PEGs_m3SNoxWZ0J*_3cmdW48kI+Bx;ghn~?%t8r<Ku~fK)<LokUA4= zr3XC0@TYSbIx@-$APE?zfLjPC0;ouUU*#Qh+gK29;A}LXu*xaGuX16ezuTvS6L_XO z>r3;WrezkCHuemTEdhY!Nb#e9h?MlCNPkZoMJXOub~bi0LL9(&3g9q^#8*LpBxs8c zYB?Yc03;3WImBo9q#>rKL_hF(l-IKg_*HH#On(3J`BN|8F#Y+<@Yw9qHps8C<5`HW ztE08Pij=6RxUACcJ9kv&#D%!oS?H<o(U6c&07>AqtkagG0l*oEcWj1l8ghC{l(To> z<HFWSz^xMQXn${S|Hu#Us~l~55#jG?r7SEWp`v5y8Wj4&uQER?#@k4ipPd$;7z9b4 zSWQkg&Y=E)Bh|66jmYpz6MAYZZRGCj7aR*5nAJZD_*E_~&WwC&t$6l0$k+3}v5Ks! zj*0oNew9t#%`XCUu5*JRNni_bvc(6w0$>b}AZTtGkdp3`b}c>4fJ@BK!Oa_ReGCW! z4nYY8-s1H4_3`m^GS|^iztFEz&BQagcA!^-3j|3bf#x*OBn%i207#PF(DMG{bYL*0 zr<!mJg8V9h_eR~le7*6!-0nNtg8V807++HZcyhpQkTOc@ItFGA50a$-H%Jszkl70` zm;*xsIS>k;K-tpDD?S5IQ`79P@kj%1mF6I~N*`Oizjv!tzM}_1=<I|*ewq|Wke`l< z7zl-p$Yo$@;}e^nk^VGT>8gP2-GBC~1iUn@?|Yl`0$!D<_@}=Ym>qy!^C=>Ucf-ii z+ATCGJ?qJRX>MLIrHh>^LBAL5R0(p^^wHq~5J^;=AGX31%b)+LPbH~{o`t2I=fk*+ zBu_07Zr<xsit4}%5I#o8CxD~|;8Q6l?Jo%<C3WIa2|%1i_v~DJmAOw4Npz6m4(P#? zCV~-3M1re|M!*3%j$R?*fmRw)0=zuDd;-@+{BFRAT)*Zc=p*1G2zO0LL{wbT2XLtb zIcZwhyLm}-1F$3=@ER}31O?)ViU<dRB>|U8-dj2V77pyg?d=S;fmVt8icyPR100&J zFws(zlhX5W^YHWV1K_8yn1rkf06kkey1U=t0l|_Wraugps7Q$CVMziSE-^)QJwszt zA2W0lBOMi4L6&o{BoaCyHS1M?sfetGff)dv+F#>7fh9q&PMrL30E9sXIQ{}l5|fdi zI3|$(<WmX2lE9-37-k5_={flXugmIMyZYYaIfW%bwgZ48Fom30iGdmY4`4}<*%jmT z24Lb-a|&FO&~~}Q!+iovp7tcjs0*@yBf<X(ED5r(17`0Zc4<UJdTw4`{u{!)C$J>N z$@B8VXbqr#h;V-cOa6cW0m~o&0)p)QCwCJG1q4e1W8oU#ODGC&D!|DC5Orb~KgAY6 zFd_t&Jm*#kf)g)+e6K)g1<2D2G&Il{Pe4gN?h{ZF9hiOrpbU@%IK-TwB1pg&P5uH( zUIJW?Kt8G<si(*S0_G_wi3dDuC+tr+V1V!pWCU1WXegLKSST1@XaZPJRRGukH36Uj zwE+o{2!u!y^O6BweIov}<OB`@xtCso4TK>9yiNh9Q!pk1fHR<j2GM{T)F*hP0v+I0 z1iFbQzzqmz0UfG%;_Hg+heQGbo<MNQ4<H5rYCsSj5&|~U4>*znSb0EL%n9&!iV6W7 zd;#w}A9R8rU?vDLIssaNae#nI4MHRF1sI8tKsd`OW1t+U3*c3OSOfre1#-bVL6NXP z(ohf)iBAAYOdeW%U|(|zUY)q<fsRgu!u3BnLJ|E0!~&r@h{%MDU|5m}P^5qSc#xYJ zZZID3-zhK#!o`3#5Hp>?k{Bl3^ki5_C%t^~gMEOrA)&FbeXxLE4??B@^a%-zg83Ah zyv9LCi~*=3Ajr)J03JbI2J#LD-d87KfIyQBl=v9HeK>*r&_FJVh)C!J)IYFEQenQU zY)o{N#K02=us;E<@JOiX{!@6;@#JV}5k#o}h5z-y|9aqmJ@CIC_+JnF|EmXJ0AnZ) z4ggsL=S=^<4G5G1nnC#uz{%lLQ{VgA8uEiQ1t<~z-?RaqK{KIi1^9Zx&*5;TGeMI9 z{{OfQFkC*Zz~NAT7uH&Rxc(1s29;IK=HH11=Wo_nQT^X^0`mjF{_-bb>H+X=iT`$B zj{y$+`qQu+1AI8<zZviYe{<I_YSmf*@U4yiR=A`-c#(|X=MAU>d#nFSpe?yj67XHV zec<bIJNrQ5LV^<7|B86k@xO9E*jd|LT|U@ZUtL|_IaprZSlc=HE7iE_f8GGJaL{Ks z&WEGjwYBx_{h#C=>~F8Ht?m5U54&M#|6vO#fBB@z@$Tl@=Kim8PMUYLzqz*k>%3S) z`5!farThFZKR({wTN?g3=jW?;b-zwfT}=N$1NeUyw!61nCd)ndv&i#TvrMS!Wq^(O zd>G>Uw;OPC_B^Bgot1){056yGnLnppfZ|JX1wYRfvts{d1NhI2Kib*;Aa}wDt>tH- zXIIUqqYYZ6`KQ4-`|tGt^ZCx--<i4({#1~}uTR8}7VJuZe<+s6<XNi#_a;Vg!M_kw zo*#NUdv#aN>ViI_bU}zeqk!URI8V?@e6HxvlIXSP7;JBZ0^hfS1ajcxyMGNMz<0*Z z0&djlCv#8f-u%n+K=_$}qutrNXN*B9{j<Wel>W~&(1rkz0|@~?7yjdw%Go_W+#Qqo zg&v^-!WfdM_>2ChbZ+9_{*}cq9Dli_nSk&;+8q%5r4%^84e)IHIjjMvz5N<Qzn;;I zQ2bi@n`^kzvx<AeS1+IpIwLqu1zkV{B{+IU_5$`Iii`7rPep;l&ZOu?G&rBZ1gG(5 z?2D+NB+n+xBZUhoF3QCF0%5+pq<j$t6qM4*XUf2I+qW*Jg3>s93O-@|4b5M&1Ho*M z54IitQhJhsNZSD+0utZVxo|Ef^f)8)xR~NExvY@Eb+r5Zq8$abUfRdypQcy<m>hlW zfAjn#@q;bmnew#qhf85Bq{(}IB!6igp-Bnk8(|Q_)5Ot0tOWRTWsrJQ-yl@yzZM56 z|4V`qqG%ti$p2c(8hXYK!uMR_;N87n6<<wAPyJa3gh1HsX9JkR`M?l;?gMiWvR@LP zZGV0iV9)Nrqw~!ECQS*P-g_DjM(BT{!EipmARq6H{Itk{YXNbm%1@g>4oHX2Q2)g6 z!V7B%qg~IR2>9PYN`OR<8xJ<!h3gQADxK_n)yI&EZs)TvOkb)26Cd{BpQC`bJg?Pk zxS@i|3_-Mjhx1><pvbwp^pKUP1FS=wEB!gi0hurke9x62MxU3lu5wxgnx~7P*uS4I z`;p!-JXbpbOtANlx|k1`^zaMi83)uC;Ovvw`?LfR^UnUEyAY`X@bbZ5!lC=w;N^yB z#nERxFH9k{ff*f!pAn$sfpbsdNnn7Z3*FE9II#rm1P&I)Ay3#(WPT6^FqPnf!JnTE z2n9Gn54p>$AYJz~z&!z1KaG2C1yu~4DF{Coo?RVUgBd_sf^!d~AbA%iG{J;N4ag9J z`{2wU@x?#)7b4(tOB&3A8t{`p4HvG{U@|DEL@?d{*~2Fc8Is__KaB^$Dj_sPp{V}t zt{kBY%zE`2xKZeTtM7wpe~FjcA$m^=cno@jX{JcQd@qWrPCo1jqV0jKr*`0~e;OZ+ zM@9UY8h60TixhavdL(&~TyUPiAy`NjIE(Q7HUwPyPjQp$kKIGX53P#?GIgJyRlqVJ zF=Y7dLB_8AKgqhVeD&q&oVWMmHePB5lRbq9yC4D4d>|c%@Bv)$UyOe|pUFR@Kqj#2 zi!>T=Q}V$=robjV{(pYX!F4W-$9{~XU~uU-$iM;%J&R!=yS853AGyxpdjDp8D16FD z4_tqW?o8gVDfo6URTEf`g7gTu<X??#PK8K<>#g$qD)sCdxerWL_~XHahfE0Il7BUR ze#-tjxZci<GkL$J(7uDI(tpS&`a2y5O!$xSThM{qP*B`^KPZnBf0cT6jRUbTgV#wa zi0<@{g8qi!Blaaw1GpgLSnEvQuc<Tn4}KI~{>7T~H$r}s{e=kde?2eX>{qF0*Ju47 z`XiI-4;0?t2nUKj5~3gilOUx3k4?|y{hB%(e@}iC5nQC){a)ZNrS-sTzW_o78Gn1~ zze+v3K70Q1eq>&P=tIA-{o~7gEZ{x)OHh#KZxf;voXG{J&gP%oALAb?`;P_v4WSDb zE+z^*6lDHc7XnMWAco8~bzo~s>ff;a<7`_T;NUVus0ZK*o4gmu1!qClpP?V~FLWcg z-oG1X5&*|gA;Um4*ii<sunS_y`aAz){lWP1@5%+&t04gnfJ8=tjHCYFX}}Qu<M4;} zO9GjZ!2JHzxRDGv(hZ4b9$c>$ys}*I^Pwmq`j`D_F&S_O(|;D4$VmyX;g!Jp*Kx*0 za>03L`sa(&2LS(Xx0rw0sycE~LL4~ArvAVKEbGD;vdtSjH6tK&{;8b5qbnsNBg96p z1sAW0Unm!xc^lkMdq4DsB5=ijHx42pBShzhEF<4Yz@jdUFGJS5z|(Tye^>60dPjPI zd}Mbpm!rzx?mwX5-h(MSPfgb17wv7qG=C7!@spF{<3VPTgR@QPg})yPve(-`nWv#K zekk&PAnHPrB1%dUOi75b@*i$L6ciK0_?HH1AT$)D>n>#fPnicg<P^l{Pr+nI9T%7g zf9eQW$X0!s1tcGuY7AWY--(+UsK{^yA%p5L<R@VlU6??`#DI!npxhuC|9WEIkereP zEeTR_<@!Y;f660)XdLsjKmjx?-oLv80;FaElRqlo0$BcGJ<?CIF1&!Ofu~a?ER-;0 zQU>$+)A*<^_j$V4TL}67kIle^a>1E&5S4ESG*>~x!QA^(g9k*=#fgJYL|`Gmiy?FH zaT~}60|Srn7qsJI{{NJ_@A^BjkTMp?BX}GPWI_X{r~D1#_fG<UDW9T*$oOp{0#a}P z7N`Mmm#F?@H2}mi`?pfh3Q5mkqp`~;4S<GhpCL{AHxf=~B7UDTJY(Jn8Zyw(wEwXl z0<iqe&hO{;Al9BWpbP`lfZw6+XSBah?EsDqe;D{6>-F(*Dk7*7#(yvi{b<(od3*Xl zGz78?0(EmvfU|25Bl_=u<=>aS|DEOMm-ZkOoa0k*GHqC%%lWsGiNDDB?IqQDfb#=r zHvkj%r&5a^#1wa&O(NiGcE1()YoW~fS$|p+DE`$94(9u7oC#PQpiLobE~NPM^^ITU z{M|LQ{x+oM@$q34pg16%A%o|NeoZFhAVA|p|5a_<0_hX~LV&j2JI}fkm<liXK<u;U zi_WKy0+6Ag6}o=PI=z_wjp6UYX2JS{mGKh>dpV3yP^xDy2AmfGNlyaS1*Y7ee*E@c zbTVj3t^2*^dqEQrLw;U(XCJdbUC#ZfQ4ql=iKAQ^D3-^wKV|Oh1j0bkI(|I<t>O0q z=|I*cxAS8FV7r*a4JQjxrhk$HTr|qU@g)5+P3&wqp6ui>4ga;_ciJFmi}OzbNZwBM zy{np~e;wS%%cYuEEovZ&_G#M=ca{w={%L=jt97;(p0YeXUMddsdD=C1ZglR=bv^Y7 zEQTmfV9Nd75v4yB^mlYz)nIPt;)Bu75B(ozR;MQ>rdMZxWasG5L*;~aXSezfA^*P< z4$8+2-8d)tT=L*>dv$qvb^GuF(E!=O_C_$u?->7S8T9Sm-;DWl4LYAZ+TQQ|%i9Bg zD)Co3;-ItlSk4Ri`}FSaybl;c_?6qgbIn%-fpGs`D5PX}cPsDDm5o1Y52Dgb2s8_k zb7A6WcYmc&<_GTeN4)=x2u||Z!r?y+grmLP{n>0$u=VuMIR7aXG_POt%HiL<Z4C6q z;r{O4O0(C0x#xds9m<X9&RO6s+k@kudgADK|6p%-d#*EF5Nx>psoH<@0+m%iy>n`1 z?RaPBWS!gF-ag*kTAS=l)n@#I?d^Z1DX3I@8lF$`E1P--dK$}Lq<E<EP@duS{}mB` z!UP653K0<w=1)lfNAtfP_+JnFuLu6u1OMxR^F3g0$PTziyak!YQ12;HQlcRTgBCJe z6GvCud$ydEAle_X0uY11dwg><BLJQQ={Uf@O9%)j+75=Hjivpm-h&C0P#YQ<n?Rjz zIiaCo!PI~~9kg7XXlDRB!()YGB`8iH2SNZ!gTfE?rxzeG(9n>-V`V5*5D8$JyaWY( ztPO=jsi>i$2maGgRMa>&g2IA=mese=H*#XV&wYjS3db?<5mFGlng>9g=&pK7%=QVC zdRG~^y?b{_E#&X0lVw)2<UU%HR}gYsmqh-kLP^6nc?1(5JIto`D!}rXoTmrjvJA}~ zp*s(5etALq8jeSfRKK#EySmPU#fPRI%Jgz&I#fT>B^r;umXQ&$V-)T_+Al<1bt~i! zLz$G=XcntU@fJe<b?7&sOb+Czg^tz^g=LC@T`h<n1{}gcd-Z4gVZbllu6gfjzHMqL zN3J{-lpWPo&yLwX&ObEbmVE12^-ZnPtv4NPv%Q)(ozsVSXWnzMbaHNIetItdB|#e& zDhaR22Bv?P&|?fWpo@MCLz3%JCEG@vs!cF@2jiGr_c}-Oh!cfxyD)5>CKUBhg2{nG z)g9COI&K?>qLgxUWga+6E37w%QxXsAgh-lh_66m)#-}Roe<yyhw0}5nSweVh;iALv zHq#+SH;w-i>pm=#uCl_iI%bsW-NV4LZkHvv+3S`vmkG(NdIxsWNDD{Wi%Rf_eekm# zZd_Znp+LMcaC;Tb>Heb?T9y$d)Tg|-HB?MitvXMYi6c|RUphR&Gix?`$$UevAn;3i z=3UC?4bvugB0Wg8ZP)6q<5N<gC?Yfr5C=9-)pEQ<-<lBYu#CXgWuBdvP78YTiITYY zP$7~9*|*D#=yhg0l-jFFaxxZP%A37}A2h0L-jXWkJns<F&TOzyEPtt{vl4sEpX}W{ z;!5E=A$$pns9nO+DR|G1zJ``bbGKLmEx8kY$kH5s7=tV+O#P}ZOiy~yIBa1YzBwj7 z|DXldt_^IiY_oEm(Q3E~<sMT*iwyx;LsZkGWFl2|e{jCK+*3U)$FiL7+^Wmpi_^-e zL_ftgh_l|)s$QC@M?oTREX-D`rL2N7CUQqxzI2leEAxZ?^B7_G+WNO6oJs+BH+=~e z?eIno7`~4`w2XDffJ1S%AXAVdm(s0yt4w?yx}ZaAuzrMd6uGMd-OEgAH`9lxUGJz@ zO@H7Ci$IPT7sAF|v!bY%%XPYqlzC=J@-f;~mw}p08{a1T5+B$rKB^DXb{~@O`!~N! zGn{kR{m8VnXjDfz$)*5&sb>N8bJh-e7Tpa*1fdqc&mW9#O1U+sr*9``rdzB#GnB() z8h9Gzmn5~T!Mxq#Pt-m3HH&=zilc_TT2MIs8>RRCV41^3A;#x;yNo8R)e$+SElVa; zE|#(uLxG;$_bwCYCoQxOZEfH5gfe56c}4b!-fumvq>?F=uPm3f`@xo>xlHq@g5;=D z<4O*cyD0kZNAIXvjWL5EzUTX}gNpAg;+(=lafx!rn$e&05hm+6i}O!z__yr7mP8q* zDev%kdbyVT`GC%lPi%ol>GIl9E8!<+)CrYWS=$IochKCfZx>u9z&N(-P3fpOq;<n; zB8rur!Zh3Q6#0C|X!~=Ok(IT6eAsOEuFIgFglq&__km$-#HS!#vHB<cCdaU;xOK16 zcKog#y-<5v=l9rhqqV|;=u6p?Uc5^$XNZX>9@mTIrrWY|NtBD-u;;!XlFj!7FWcKS z6K`O&Wrk%cOsx%{V!AWosp8bD?2QTTl$NCEp^9+pojOX-eit&c&A0&gCq$Tuef94t zG~(7Q6=p&c_3aWnb;)ggIbpsK#tT;)yue<^D?%@}X-6BIRGSfomgNzp36&0<SbSvc zy+MifDccUogz)m}qy1~do$itRigTjc5{_Rq>r_TQx2BK@8NV_A-jG+S!k4e`EWPS+ zHBO;vw1-X1!4=ErnVzF~A2nNabAsM$1`_3B<S`%BHN2rG7i12v^?8eJuEeN<(ssMW zgUpRukk1kC8U8AHlwn%lnE2{ik&(|+<mYr2o;MsBRANJrk-icaXhN--e;-qzN<=t# ze(<40b}}2Ge06RYSu<@_L^O;gr_LjW51}!ujsiVA5;hlkKH<vyI(Z7?0b)d9!H>Rd z44*pT?GNgi>7Im@L~waM;P?U?=qeP~%axRFK<L9-E^qiQa6vAH`?ABw+xMoTxssc` zHC#4tT(R;qetCR}GLwYP-#0+Zl59;E{c&!U!%K3Wb;fs|!9(#5F{^Ibspiq#>sm(q z-i-OB5~hsO+%N-o==mF9zpK*d=6Q2RIkMaim}DmSB+$jSD2_d8e$a+#Gb_TskfbS` zUdz{`Uf6@DPNs9`3FZ`v;cCsjagA{isucdOau1PM*&2@ZQ3RVuWt&HDHLg5^vSSbo zt|nPFM8Za+cyNhi3r7gPds1QDMxbF&fhW~5D||oDljUY=?;PUX_`98EPQ$JH-4@@Q z@g{{08{y`N#M6>&5*k(7nYcai$e+C?#l)luja_GkX+#nw_`K$`wblJp)Uja)R)Y|W z^zF<Sq^hAIyM{*cRTC*%?*14HErbB~pgKHByDDzB{Z+fkc8NfX&<Szg4_s4vaFXqo zgd6b2M#$o0e1eu0!zra~$1q9A-;@g;T1g!v@}V(#535jVVX;*+5|KP`$QW94idok| zwC#JlqnQ;Ub44&;wy*Uqzx@z`$Iu>`&Rol`C5A2Oq2cUW+t-0sX9D-ymAVP;pnmOb z1R0Z+(fYC->)a20tHto4>R16KgW1Ssl*yj?32jA}-0zM`^kr4B^NioOBT~(FnIiV$ zQom0(P3kbB>6w)f0oCAP$$!h;MpQ=BUG3%A<58G&ee3rq%?!H?s;ucSN<!#u7D~BS zIBfc!Ah%CiW4TG17FQi3If&yGcD?&Nxxd9NOn^PfEzRA7qqmzYZQ5%IEy!9v{-rH0 z^TxM!HTjN^ym7zR^*K6x4%=MS6Km?4B~&6Jqh?bxiOb@QNn87iO5R*4jLoB3z@OMk zp@?NfOWD;ZW)I2~vDXOVcNC|Gu8<ffd<dB2RV^89EN8|c(eFU_erVQ=?6vHp-AkK` z9`&&?`=DANxE7Ay%dr-BEt2U|ky<%-t^4@a9_}*AOzNYzL3D7gowA1nnZ(io%LY5r z>d)U1G5H6uqd7(5)!HN(h3v}2Q7aN)2V+`Tv*G7|+WuM?(tXdj79%fg$dna@%3<2? zy~k_0G6K!Y9(hIIx$XIMflJ;6;aSZl3nASNn@ZYO%gD9RKVn8PAeOyOL|%Al+H^_n zzFnkM3q=|XSp=hoC-Rf9_o2(2ofA1QB4c-^Cyrjhq*jm($94E>s>z2==~k>=?(}pt z#Hb9~MsG)VrLR5~;eXp)j>4$6InJ*Xy6|2LS9cwX>RPq=+u{eqliqok)d~7UpRO(9 z>CSk`=_Xx$>TUIGp2e&C`Xn@Sv<UuZN45^bsSe(OUO_XjM3F%aoj`gCtaopd#Xr4~ z9QjCB(dAe=O%r)PWX<amKjjDOTYSp5YcMM?4c>N2gc+4SeXkmIr*OSG;4ZmwYy86x zPOYnHck#k4_B~Dbm9=#3FG;*=e=2m?5f43L5dK`uVsL<H#v;sG7#3O?9xl^~$VI~a z<w8z+i}6)N?|#l+e?{tS6~0fa60b&9@hrqF?fPDNeNpBbIl$kt(t+c{oS4z=ms}8+ zV~eJrsTZTMBVwJPs=w=25zm)aW!1|;CsNdB;B=o%Jh`}sfRvS6rEzhn@_;v-B52vC z2~9e6*Zyfb8P{BCdigkdf>;Ny{5ZR}MaB(t=R;xo+bZ4Tjj?p%{JtH?w%^m?P|Adf zYt;>igb`_7BN?ev%i8C9S)VW+)CEVRO?KK=&<3OMT4|Cr*gj&ELu}K3bEv>D71J4S zvyt4!Apw&#b#Hgz)jWZRr)1s--(*o@!dpz71?$dQ$umoxL1ics!nkAhnuxQXABFj& zOuyY&;6o#qDZooeOsGNMVc4P|RE*Tim!dR(MUv}_+G))(6QlHENQ&BP%U+m3$f`{} z%MD&xH;*Kj&qBIiRj#A8$<ja>hVF=L)q|qhsbenA1;<hR(lIQG-z$C^0~r5sjbVls z3(<&W?7-)5bZ<eMi$;Wg<E^?)db?UXRf5$O$BoeQ_T4razZVUQMlj8HZ#{N4u-m&% zm9knW%9v9+h2?Oo+V)#(#3iml`8fx^@5RrBar+4BI85c<e<7^Q`}Rp=WVrLehkWX| zQXXFggj9*V?0pKd*7npS*tl_rBQgi{eB1HJVO%`oM7+h=O0wX$4`3#uil9S#YVB1g z-M(S$c&XVNzvBZ=okj>|$L_sTeauOajd|O^NPZZVBEyKShI|?A*hQsfbjmD);cFPv z(w9pd^`FqApf-h)WDt8@IcmJsavv~b>E+4h?x}r9_TYl)(sb7HRz65?S~R}1=e6v% zJx(`+YlF#slgf$A0NXRu+u0r^iE$BSh5fxxp{9?Z%%D~kkI)B^2|tbnT$g=Tv!oLF zhn|y91S}rhro(83t0&ZhQCHY~XLN{56VB4dg)(p_bH=w!!b03!;sq4XDy^B&VU)q% z8`JXpzKdR-%~sWdpS(t%f7F4&o*>L3RE%J}<ACY3#8X^%3nfvAYMLbxZ|&tRJ?Gmd zx<fD&E}iOew;yL~p`-0d<wxrm_t!m&4xgvTly^keuBx2mDflM$=$?RgxbC%iJwN9b zo~iP%kyX|W>UR2_F?uGh)!2s<uN(Ed;YuYZvt<K{taqiaM|H~<6ZFmNVJFXSev!|! zMT4pDZNMI%;P0g#zzxK`Huc^=)D3q`==tLF@{hjvldh;$3#(cgbCx^E+wg95$Y%|D zS?d!Hu78&0%r+(JuA5@~+9&yOyT2c0k!t%Dv_<v2VfO2WeI0k==44cXYAWF1a~LDc zdG#8MSOaaR-pN8v{-WtmJcvx=cejN_%DA0yLtOWUcRGO+D6MMXxo#Q14;|*Ra9y7H z#`<+&35MG6wd=7hgLHy`DkW|grQ-zE{v2!Feu+Gp{w?9)q=)TLE%eVw^0g5LvPyEB zTn0@%u)Im{(K_=6guV6ChNYeewOUadlF&`G(>B*bpoa4gH%z{9WAz=5T=%TWDA|F_ zLKqDr8f-#aE!)>;ZtsmEm-!Z*#7&-Ka$zmBh?ipy%o(73UECkK$594t{3xc7q^1kH z<m-}Pv22>r&F|s`ag^$wdxDP)pqvxPJw0f4Dn5{ZhDtQr-YPMb+kP5_ttJ^&|4C{n zjE(GSw{tPRf}h}Lnn*StQ-VtIYl!w79G^_3%Wyegr?^=R^Rf)tYC#hz@#?3<*E;zi z#Z{+2|1#qjc_mKMZu1gF);oqmH=l#npkBtX-pm|o*zfFEGbUu&`~)0@dfpAhK01SU zESRvHxx}VR;}A*e!tTC#<?^skkx$m}VIG_7D=t6L>y^;PIxeFfhj(cmB!jqUxz%EF zVGEAA(e<H<1jU595{#d|Xq4xgJ9H?_K`rSiUW&c`v2TLDw8=ip`Q^ZxWc*j$ueieg zX(-h~6R~&2Khc%Ym=()e=W6@ycQK^Af5P{Yq@u8+ovBDj$v*d(XF+eT9nKJi6A_~k zVfVeBQ^OZVMw<7-(J5Ny{ZMaTiV9-r<lXaDpc0qPsW5)7mR@(Ayxezv(FG&T)~8{V ze@WS_h*pQ0_}P-<$ff&-Uov4kEAwZc=pl+Uzp)NPkO|)4MHZ_YUcZ`6ec;p8wLf?* zhcY7PrWnU_nYz~&+;6(!!WyDmQ}pu7c$pbxM_)jt4zE1RpNoko+UaA9yiF7&MXr#; zxCi@!?}NS{S_#G~O9rKGN*p2rVJ)ncIvw6S`RfbRDwi-BsSdFp)S8(hz2%b{<f{7+ zq`b1vUli%y&SA4cvTGu4cOS)44F(a5?xx3QV(*3}_S}7HMJ1`>0;%i7VFjB))sMEn zx53s&nAa4u6&jN>hkxfch(NBq+v~|&A~0M0jXf1Tz%Zs**_U~!MC>c)BW!ymEvei= z>?=|EQ~OKq9~O1O;?ZjAl-*2*roZ`P_q==)3T^7M8B|UpL1j#wIfT*^UiDe<%}i!R zh-yh;wMgIYLf|cGYvB@!R4OcLqq1+NTYY`@)@s?(u-x-1k(0oe&=8FB?{+B6qsI)e zS4lW9-fX26?(ml?c!^)iO-y>fFvM_uy<aaj2Pbsy&YsQ}lh25vZWG(BEh%(zQ9FZ) z6&RMx%c!|prGg%P?^ts7kB#X{>+?E>{By2sZ3ev3c6U+4x@r+UFcW6zJoN1o?sG0* z-LV_?8n;DmmQ%PIU{pQOtR5m*e(;zov^RshHu;Hv70=EHl8ieU^Yz2XF}MRk?mQH$ z2`CLHG5x$cj+-Bia}JGF7295}>K-6HHdLgjufr&LWoPgzRLXpvG|@q;lH_Tn?A4aX zhPO6zmLrNp*`@Hc?_4o^IbyQ5t1;xf$<7>H<)!Hyge*261b=_UmdK^}NtwVUr5aZX zAuJ1emKTkS0~(%hE@L#BK)u3M?n=G8Ap)}#Gh2D=q_erH{pohst{&M{1s81^>O*@B z_NUd>_9Rql)>2FlrSjiuOpg`R*-9)GYVhQ)w7woIe`Ax#A>-*B(aQBETJDC=Yp(1! zyVTdSp*+7A>WFTIa}8`9d%`3o@NG-0H*>{gK61ZW7BsA9aW`kHvdkmK(|5XvrJ3B? zM*w>wIGHrNmqyS;nJ6=>O}6tR9j(IQ{B`PmLKRGdC?uBjWnrTGNqb(5iu<lHlZMJo z$c9;dh(oo^&y_rQMzxEh3tgtB?Yoo|Cj`hoY|@y-qFpv_n~3{<N#cRLn=po)fkufc z1s8voW!1xuV9Fx~Qn?cO9d_SxL4sCy3=FNAbhff%ZW_+Q0Ie`W^t>M75pDi@;uSmd z<|pqH4@_{VUT#RH^Uv0lSAVtlrW~fvJM6j>c30K)Xq%Tn&!R;wLSXq;{-;;!8B)y0 zdZ9?)@bPCNd29ySO2y)v%@C-=g^}cBWVw8acuDs&U8XS^ZZ~`v5q|bkCfNu_1Bdff zbz50=X6n8Cna&sq@d5*y-2n{)-uL~q2({c&;cOiX!4LC+-N%;Uh*tl+gn7lMUJoUc zp6@VKMVHC6j`+78i4oAjW|oc*U`=&-1mhX+jOz*bn;J3}<0_8?6kVS5c7-vhK%{$k zdz=xUh56o~;M9ZR%S7;~QLQf~2_KruHl@f4*3e&0w9NgYpit&^CG8MBht1pAt&*Vd zQs14y!6R!#o1&?SHe2NUS!Fz(%V;f|QXJupum@7;^VV@!7y|F_Iav_kR5&!xrcZ|4 zXU9?N*+^xCf4Zrs7{TAnbQl}1q&TXrSR&_mEoCX5UKGiw_iL(VMv1DcV&sw3h75UJ zXFL{?#-2dg*k{`?oqf{fqNvvoZ)kcwa7u5)3)sHq>7iiFj9|Fj^(|aMAcTu2xRME< zU)E$tFY#6cUpVV+wD=eOk#z7JaZ?35OP{sbVILFP)U!FzzKPlhorJqr@hT_8_W<U7 z=-r@n`6MIYTPF+a%5rbwEo|pW5@hFI6R8b$#cpjxS9<U+PA7iO3B{R$Zf}Dr-y3+^ zn$;ojp}b65>nXEbI+1pFrU?1b4F?#~jM}57BHwYi7s1hYnr5zjw$NDbc_MAc(yh~j zgX(baO3R$QY3BRs1d`a%x9R!~RM+hSGZrJTWQsP5U=c{-!<L6qFlXmorfyu$mUXt8 zPETE?hpz7|iB?rj=ow<OMZz>EFy3T)w5cDZMog#kv?*V)MC9{xVHIscGH>RpsxO?^ z7%FO)(sZ)ior1QAI<Z}KMp}+})DIs;pijB2-7&2baTOeRoT}}?{`><Tr9=$YiVHH7 zoMVMPL+fV>x@VsJI+4WTyu~u4g2>`G0<M$V6g8#P@H|P*D70o}Sg6pOluJ*b!X-dW ze^rl2d@o83T5o2Lkc+}y<)M}mx%cQxr_2~TL3g$UWFPqXIo*ekSoRb`$dOQIFn7~9 zKD)v^;9+Sf<?B))iym};%BA|Aj;hu&?wFG7suV105{0q?x;Y{0V(*j0H`UvB8bg{w z&C1in1nfkIUe&<V7-N1aEY+qh&9<Pb!c}=C-19W<G8OSsLGx&}(vEeOFxq%}D>hy~ ziL_Ac9kE(+1X9(@rZNfRk~q$@arn)e=<<jOOiksMO<!0As*h|fm|r$U*~sne;JQ-a z^0{A*y^>kV@HF#%X<*(Psfp<1Vv%oL&@^q?buc-^?4BH<Rs)RB1ielCVyu*4SZ;(a zuwS*GmhICD;!)Lz$KiO61xJAG-Wof`D(rnPEIQ*LM1$Vg@XO(wD|@=|CFm1xzK`#V zVkr1m9pzX$^_wS}D-}>PH8R_I7s>3!`zsPlYJK;vmvnKhysZ~b8Z1y54#mD9QuHdh zAT&8h_Aa0KrK1>X=k^{=v@{g4MZ}`6xObX?swt!&L<hywzYG_JXrz^;u<gr1*_4R| zzf}qwI7YW1EUQelfuTpf)0Pv+D3sfPHT#jxC`CUzP@amT!6iH)%;B)u{pKfhl^5=* zD|tv_LL!ow!z_unOiN*_IQ6O4n32#nu8oSOGa2#Jl^nQgeRLe|-i$kxFp<Z*g2ULu z0i7arucB0C6mz$jFht<C6UtgEj>`acNEtM(p!TuXlF&i$KE@aCn>&gAT2(9XI6Y|J z)NdZFesx;hPeLlfe@D#jE8xN24lUoWVm~<v!&Z0~$D-nPI0H)gF==MLB*Vw=M6K`i zqy!3J7cVvMEe%n|28!kZlVUN=3KUtg>y_S>(Hf4<R_CwJSv0a6+tk;w=r=KKQP(${ zH?Rq+M$?jr=7wHA@*|O7UogBnFCxLgkpGdXK^WG6)m4oTpVItki<e}VbO~CHM61u+ zH>KjMM~-si)MidY559iyre1~$^iK8Aq3#tZHoiXhA;x<&zsp}IbjRJ$MtT7*X8TT1 zFZ@b*XlP`=xMnS(;&)k(Z4YO!R{Mj|bmsVj#V~J!%eN!>cwUP>SG>h3Q&)LeB*RQe zG+6XbsQWJbe0cag(F4JHeS6`DzAQ|Mkzyq%PxXjvo=}-{VO7VuAI2itZyFBkxaHr} z;|<A4MR%ds>Bd7X2p#+ot2<c)<4MbiShU^9#hqnZH+FB|YGB(bRJZoip@n03<sx1o zUoYdDTSya;k8>5<{m|#!LN#e^JABec0-Yf{5#3EhgL<_tuOi4@FVoWiA8-*Ti6&@Z zFw|<nO0qXXtMV|d8abd<pyTr$rg-_vdC16+EX2d0)+u(=xnIH7QkPMc+}MW!9tT|x z)38eWb;NBwSJpDDW4QKUrl(r2_Y}t@FQbKiW_oa&UwVAu$U*ffjm34OZj|xVj>dr8 zeKK46$7pvdEy;)1rpgk6M)d37#6CVozORXQzhg9Ys-oP>j)*6_F+d|2tLnp~|LRDe z{pGQ8eb~t~g)LG?ntFMnuG!LM*XNT%_iBf*bdAT}V8E6QT0>1~Pcw*pA!c*4f>C3& zh#NwhHYtxKVW7D>EAzxKk#f|*`;|&^->aJw>(n2Sq+Rh4MeuCL74(fWoTEBPS?($} zRlQrTP`T^yUHz6+$y8KvA@<E>_1AgVt0|9$%0(lu;Exj&Z`|-}7h89;f}TLdo4bh{ zv_5YUXgXZz)IK6LT9!Wy6;<}JbRjDfn*iz7baE1h+uOph`M0K|-<cY2^D%I6KfBcu zR7q_e&lE#g;3|2WdSX6t%aDURT3|!rD+vy5q*C7lxgd3VVqD2yJmQXfUk0usw29Xu zA}DzJ7RJ1pN|d{suOZ%Gx(<V@RLC0?malaqKTmQ=M71@f6<LJmeodTt>><;L(AOOV zXEWEA*QTf3^Rc3xmz5$g`^YA~;0zJfWcUW>UbZ2{mwi?6-ZvMA$dMB3p!NgjcwPO3 zY>2NW%gkjyw|K6)@z#0M7Aq0sBgT!zy@(GzSvL9c)32;v4O>A~GwL11A8aOeU^Yk& z8s6cWFb<SV6+WV9ER8E|3lb>pWr??G^vP7^9z(V3Y#H)&+L6ZhQ6qZZxVVWH(bz8| zjG@ZDu!k|cHu2~&8K)arEFlLakMI37B;iELwngIgUPVgyiMwjJ4`6&Uc=R`qoW;Wp zd|i9tY$OX}5;`j|?eMDceJ&3=&w3<NT&>-z5YBw~YN|6s-v`}cDZ(xFW>JKhsaMQb z=@%ulj`ECQ-`*mSd5At=iz&q}gJFt(CmZsWYbx6#42oD!d-@uU35Q9q)Xekb{<_hM z$Md7)q!gld(Zk#|>as6V@Ev0$Xa&(6I3#`R;nBUh26ZX0r^0f-yiU=dA!*a2!+$v5 zPE7fU;`=&}Hp-|j<xH3u$-C`On=?I#kpaO<_^-Z_^m&&VcYc*Mk;PX0tllk7!HTHv z96BP2N{$-PuA58TA~8uD8Uxq89WzFlxmNB$tiUZ4GhfheX0CWFy*mYqajmbUrah05 zryGep3%`tH88Z(riKSG#Sb#I{Q*NRAwb%DC(Ba?oI%07bRnMyt3hqOR6e>D%h<Fs3 zQ$=O23!(NDrqqh>>o6Z!!=jNA<F^}ridmm@aJsD6+y;N|aHdMnBVEkwiPo|1%5{(L zGc7K(??3Kh$E7O<l@llDb<C)G9>eLO`@Hg~Nlw@+i_*}Fr)lP2X|*v{zx<Zoz^`#4 z=P@q*F-q*1<dM~Et#-5%Yjts2AAfD;7UJVon&O+`?qUWZe6z%r3t6S=h>RcN$F2=h za@?zq4bAkA$=*i9DOvGcP%I8NammcY51(Vk)?gtnBJ~s1Ue|VBXtqY7=b!F$O;CVl zcw6&4W>+gPEpI-xyxgNYGV#uUTN1ps{H5`Pp3#TO?2Uscc6%f?*vB_?wF-7`w(dzD z5nbtzQeX6586jWkgQ+78YT~$2;MKDF_WpfUA-yMEli?*dP;U(}vj;5Zr&d#zstF}r zzW)5xXinv)+rC+4C0FW#va8h%y3FSe-<Z6S*x^ZwOz1nr6IO;1@A9m0J(|AxQq#!v z@FV%u@I#pV+bF~G`Abl&<=V_qrZ=YP25mRgqVPUB^7b5~ga{wsFy)KJ3sspmW0To@ zGDYuS)SJG@(m3Ccr4sEpS<rm2j3xBGm{NpvlYh>{$CtKs|E}U~eO3pHF#n*jw1l-U zVWyGl4AtvdRk`=y@slspa1RhuVq?wR8kJm3)UMwPXSg3T?^Y*Hh->l9R*P^{Um(Qz zLl?AA=sV9ld(-T0z6p_6-t0USQlw^8AFIg5Y_EIPAex+uDt#n>)nSq2hQQ4`9K-o| zz9~a!f@4k@Z-%d3fyLUI&T>W*6Rsda(9scKeNLYGDGFVHnhTnzkw<-Y5{+kt#hu=7 z=-Fq)PIc_h2C8FMUTsbCTHG;gxAw8fxb%gtFIH{<RT{>VW#ibetur>O@!qz^oNO+f zfK__zn<Et!Bxjs$7xXeSF3Y=SV<X=1SE(A<{1&gIXd&tCgiwB`$r*aPn=xCN`;<2q z=j#jKr?AayO)7N3{;|U@Rle<2UBQ+}FI#;wTf6$3%xT%ZRVNHcpB18Ga3&-%8IRyh zxDkZbBM0=BqM`A)?7x@HVQ2sLD2!m*<gL^Sr_7F=P!Kvlk>&DMQ%t%-8v2B$z_+Mv z{?W%Q^<PaUb&=+u9KfM0Sig)Vf07|QC?h%fGRj7OF%pJFzBbRh62<6URFqvQVMt3c zrB1#Ha}Z5G*4R6_8kbs4F}!yIjXTCltBnCeDrz4a>oi6xsi{$C%_Wjjqh2JjW-W1! zO)|>H`bV=yGDNEw*VjUC7`+?rgvP)?5nK2c^1=T>lWF8SdG>YqCEx=-Y0*V8h!PWa zSjTrC-;*#=m3Xm$oG9+Veu&5*c(i6P{N^pMNemB-6O<hiQT`NiUy6;9!H5oxPSfta zx5Y`aLgoZ<VgbZ^M$G|w$I4MR^o3p)y~2P)r8f%4-an*~b^FdJw|gmvRbr`<8KXqg zbl`>=)RKpXCJDc?6)cq&CF@hy!Q_^^V&>x26x?KnlNHzCawMxF-^+^0TqUN_QXEyd znXtN$-4<`QyP7=S4uhAtT+aCJs{mTTJ#Fg`-WFMCzIcj!0?**D5PM&j-F9{StbsAa zAWD0XKxh9VsmpQuaD&jrL*)3Du{E0(RrglS<XpB>);?j?$4GoNmzd`nyn2#ddWubm z5s%iQL<m0m#Le2xPAY#mh8xl%YpM!I)@qC>p%!f<zpg}c*;n1Y_Hr))bVSl~qP1%O z8?W>u>@A)y%YBdSx6D0+9-vz=AT2Sn+LXyMv2_)E*IsC6ez)v<p`#Srs$SL>bLmEN z{(<;W$HWet2(AD!tI>3Dzqap#uU>RU8y}989x*Mx{a6W;PLtiiY_#e_qoSF3YtsvV zP$mX;vVT-kTrC8@T2V3<Er8Q|E6Y?3|K|5uOC)C<St=Hp1(tXNL(DdvRXy$nwYx6} zAIse3a;?51#NPQ~5IC~$7CC=<57CEZgGbVpxhdsYor!xn+qIe)Q_oAk6G-9-4JaO6 zA=thG17*?_;?ni3jn4FQ$8aZ<R)U3djP=;~uGdCRb)hm~0;DA-=?H$4@^$%BX^~M_ zkA_fjp=5~yC&jcgE4?6W->V)^<;~0Zl9B;hICn&^81IY}?QzQRH3Y-QmliM=*TnOb zGpeB*-ebzxB{tl<9saEbb+Lz_5n;T(4b@n?TKtJ!$RrQvi+w|zk%g8{W3lSo)Yoq? z2{#Gy+ZH?1)^y~Y4FU-o%OCY>K2i^d^MdolY%1Bt=fkkmx^6dSDkZhD-2#tW;kNym z<=*t=%kRG#P9hC<KA88`+iBS4ocFkz<P$in;4V4LPet8x{eC&DF^7NUGzO>WD762s zth2uI(2`OwNsBhIrP?BTZpYN~kX5qkpl{NsMO9*2t)6gmk8btgmJ=JlIj}oolp(Fw zeLwID+w!n1KajC`G5Nb_n<q!|U1^LKavF_Lhw3Z-(E*n^h<S@S3t*%X(`Mf4sn(&& zbGKt~zP9H+wsd5RNGAWPqHDlL)Z+F@AckBdGI*RP=JqN#R*AhbA>AvRLf7`X&tx{D zOWjlO2Vc5gEVvIM@h<g!MVTV(8pUGTGS)I1bg?!JX^SSW7J(ubFr&7je$D2d8vl+) z8lUmmZ262C6Yj_j`_=)rk5(|3OM{Z=Vla9(*t*KtL`K*88L7JZCU<XJ9;A#@$rh<u zKTW#k!d}$RDP3toqxS0hUE&bGQTgwb`2;z&e$pSR^M;@rvIrR8vyx(YiBDoYp|hx% zCYJniryV-gzD}+b=RKQJh<FRD`SWO6On<)ggkszm$#x=0T$F*(Y#+o(Ws1w=SCn@P zl1PV=)(^LXOnL@cXajWUEW^amm(rA~riPy{zLtNA>w--~{AqgS%U#9E@y4-M-ItdV zkz+%aZaDMNAxdALqrJtP`jD#0k)MROlBI0hj9w<eN)3B9D)S3Y1_4zbf0aP}j44J( zSaI*mJ*Pu*mSVjQtObGr_d|XuSA<pzulzQOj0L4*;T37efaotn_IkqYh9<1xvNV!g zaWO|8NM$cv1pOK+)-<)RzqeV}SX#mktM2*GG=0;<?`ZS(4vSB(U#2^A5$g8ao<p2! zb!6-bBb{q9$Rfn>gtR67c?n!k;>=#TQRqMMEgE1N)oyjT*@l(dp5^rUZZMO2OZinx zmY}b_wxrNYUrRFryY;asB37}SCtPyEgQh<T)!m^?)~dL>lfq_Y+l+N3+FL@#rb4r= zlTM1A^m9&KI{}g?N5cEZ3gzr0?}&+<49sCxn|nH<XTAeoXJcq?_Z}st;}hYfBnq)@ zxcQmT=m?~VvOVGs-@1XnyX1Sx$Jlqg@BR~-PPM!svt;+7vA)5))afPFwY1lMo2n0_ zrz%l$(Tcx1Urj16ntR_>!mCt|cZ;RPN}I>mcP$V{SM!4y)3uJcqEE~mWyC}Wq9&W~ zS41UE>N^|hwBFC^*<bD7n;$&TcA}FiydKl~@fb<U?&-*tHvP%e!Py~0;w+eumlNN{ zM>3$yq^|heYjRSlmPPjD9qb{~hk2z;G4Xa>T?=MWm2$3l{u-Ops8IV5%RgFBX<b@h z#ShKJ`+mlK3|aKAB137BZ9PM3b1S_j!tFv%=H6Y_k8y>s-Wy@&Ssd{qxDzEHZmzO{ z!*tuJ=5GHUXQ%w7`0KG8FVLX*{o+xNsk$>pm0Mn|Ig@<EE!JLmy#DG#BX`%(=F3N8 zB%zI6c_x}K8Scd@eiq>w{+3k0eGA_z8n5jhN}RQ#glNKoGq-68e9*``VR*L%o0e27 zj9EzqY8`P<rj{21|4p}stBlLA0R>jXMxM@g^w-UKztaN-I@9dc7gB7GL<nh_r;jbt zrQ5OK4RbqFI93Q_^<KBU7!8N!^)0(OmwKzhfmZslag!2RhlfqjB!8E1Q%_9&^>>A; zd(7QWSMBUG6Y1>{sanuu_M}qlQ2EuA&E<P+8$RUJYifDV<ByW?S;al`3R0)KY~XnP zDy$$Yuc=b_Y!g+ASjHVEK^k(GE1DMR@k5p~_pV=g5-RCJ=irD<<-hoFP%lkTE?J8% zZ(X)LPi4(ubco{dv7j=R=9-2thrI?S9d3?@{P(!qS_UXqb8rv8)?_zS6X7LWl8o5B zJ>pi?<xI=wx5{g+aN9CeDqg>@>));pH!5+|eT<4Rgw2fXCMdDz;}#xJ|HgL={(uW7 z=TTVe4csYdONCNaZGD}Wxg1$LxC?G<2Rf1lRia<+eSxLa+rHYUm!ZulxagGVklJ$p z@xIipX6Y;R4sz^~yZsXhpSNKOogS&IZF5gJ<U2(dbPkoIwavIlmJ72h%@{}QmY5}} z!1jNLuz5zPEo)S${-U#bN89$>(d5b%dKhnSVL|L(3UdC1sD(Y$#yYaGq(1wt1rmJ< zUiSv4+KNvW_Ra{=xU2Wju~iU17X~^Ozl@4oU)Fn>n%U4S+9Na_@V4*S6>8#)>Y#Da zxLqd|VbizrH?P)(jeE5#=WFlsDi0IKlxz!nvo8$=9|uL2B0l1Jz<^$PDdCfpf$SYj zArbU|tf`MdmpVq{=xh!v=|0Mb1bM{6I9X#Aqu=)$@|n_^%x02H3%H(Fjxpyvw?P;A z?i2OL-sPUpBYLn;lD|ERbT*{QeEEVuen<2MugxZh#oE0<oH``K?$R_%3JcPw?-Dg0 zQWQ=LuB>IiRX-1TTaGm=V*Wa+SIQ~b<?;U1yC!dOB-Q!Hz$ds%pgzFpJ3m>{>bKKh zeeL#ma({R<QwaIBCqkqkL97_(`^VqgpDBrN>%_)oIK8Lbvf_iMde$^Z#wgsDr13a; zS9I)}*t%^Gk(Qx??Kd92*PDhS2UyqkC1WeixBBSob#CovuRKU7aJ9~HhQ1o}=n5K< z$RaVP@M|qCp)9E{26rSlLhMb$MqiEMe#W~+oAtF}^Ba$=dZ#F1(KF`9>VAxsRt?KG zN;h8wY<!;2JK7L^o%*rzJ!_>{6#+kII7;2_A+gBrA@_|<$B|`bPfq5Qg3NbB#2i$; zb7WYzn!=Z~z7~A!`a)$pF%cl`@yL4CXV__0QN@LC@*_dbQ@9}7xirzQ$-0pg$=3U_ z`wtNM<egGbeNk<%1-sd?1SC?jGF33Nhv?PV$9;n(d)-W5zM@agDL7yS-Q;XA(iYbK zQX^<Fp)%V}>B#O&c=y#MFCE6ZhqhP?Ls79Q+D|;#+3dHH<eDai>t6G2-$d@&_sb@! zcdU{jj61;6MTe2&W#0+abcSwEqcMIvW|{7r@Rr>7n0+ow<ZH%w*qE?EJx{6{b}W?d z?Xs~nuVJo7eYCBfI5#y#x42(aM_(!YcGKRqDR7iF9(BWe>j`>x@n+{}ws?k!c9QU0 z|KcSnjdk_9+RnMw7t;Y4C06bpCIz_0>}EJH@f(u%;ahmJ6aAO4Wi=KqmsEvrT;A_8 zYUrPieQs9};LB!qfM4YKU`T^wYa+TK)9H;66V+sk;ND!{<fX8$Iz~AS-*lwN{2kuL zx6W2_8LDmx>6;wcdkYL5!%ubzsB5cwMiXYRsoM(M(nL(+&9#kMOFwm^Bo;`|EbzUX zYJ9J*pXcFXNYPyG<#o#Kyimys<!#4SM3zh4bHw`Z4njOg8$Q^MNhf6@kG;^%?4l8N zJp3X|aHYmUxw)h7mP?JF6*l7oXKNFTd}bo+gr^pzmh32Z;`fe;uG}^EH!#k4bgLyk zhr*pA7S~(VDoa;`;zi_9U*^YVE|EBDoQ{%f()1Mr47h3VDJ$z$3W-h(*{=61Kh_4Y zSl$XIJvf5@h)^?|`aGcjz<p??R$OboPwL5SXQ*Ry$-8UW-bgqh4dhYKlA-?4^Hy+D z6c3T6Xa+Ya>>q@2zN?xNz26e{ruBAT*lRJW{j0$`3u#SRpE+=-m!$1KCw3N+$0|q; zJUr~qo{9^uNkZPiD55>We9}BB&_QG7_-rHPn#lF;Dh8dk^r76M*!ZH1V@)!2{znm2 zdN1XgQrhvNWETj1x^gd5t9xh|r%6$8wo667&G3J_?%XO{dEi!n5`&OkuuG)Ra9dnl z*JHQ7{FrS8p?jf19ffN4!L`rNBw9Z|6O18?v3YPb|0LmATH4y}fkm5Vay(c=$)CTa zWRNbgLT#tP>_?lT>+2DD7d*Iby0llrZSV71?a_(`4;8`iZ6u6HE5@tH8uKqFkJ=wt zZPIWGqcF)f+}RiVY^PnHo@!>qv|P92iKIXMHJlsK8pZX|1n2kA?3=WKQ&yuT(vC)K zYQ%&-8)C$t9wZ@{`R!pxwp@eFoXh>BPhcWGq_>2;x@PUW`?+rRgP~+S)Jzp$LSRQ7 zG4T`=Ye7bHcCVM&@Z-I)qFZ&A2zkrR!uMyz@{Gn~To`s`L|*H!G+(XrQ^=NQU0HwE zzti0k35Clq7ulMkR%5Fvb8s7d`l)umS%Xd-EPG~99EDxtsEm6Y<zN$w87%cq&#_*z z_LzxASzx?jq%B4gI@jWNo(|&sW1KR|Y1Rj~;`!v$z8z{SO<)Wr%2KtgziMB9{7sK# z>fKX`xQ56#&n6I*LL%j_!efp^k>C#lFJXkf9<doa=FLSTj`1jCBt%(H4Ob2oNKXB9 zq>$e2(W~+l&VB$!;#%J^{CffXyEj+xc_@ttZ$~t)=D+7Ykm|0CuE?JLnsrEGK#fu< zFTeIUmg?{w&&$c%-<&3QCj_N)KdL7atTxCkz`i!1Yz};vip3j3-7|FkWp2O!^j%?v zU{urzv@4rkQVQ$K33eDIo4lAC4_=1Oj*I50J$8IpUFqV=%@*=-regk1MbKjmwwr{i z2wt_84z6VqgQ4W4ZbnW#VQU<H+t7p^ZHA(1)~(zyGk$|ZVYjF<ZPFawjr`vDAERg> zdry9xs2ydv8f)>o(a)Xrt12cu9&v=_(=W6+RruDWS2NbpMn?+?(P^Z$n`QFkd-R>7 zne>;}1aFYX3BBcM)T``Z=ocxIFZ9-=ihG{?GO08ouj9S#xKT0<hJ1K@ZCj-_%cHN! zv_7e?**<bwYK{8}zmL4S@&5rQK-j-?9Q*Qh;MMOJ04GYm?4B4cx;Vi<m1D8YMkEJI z#K@9sr>erc%M4YOXl~T6?@DhfT2XVaJH$1^1Q8{U^2n(UK~oCE{nz1BTkny7Ve5Jt zlBgpu;!T%ncaS<U7zx?oWlPs`XjLLkdU{WcbLz?GJsUQ*_-eW5;?{GyC+G?PDO&|{ zCeSl?|7M8{pN-#P$?l|%M9)=}uUiM&U9s4WS0(D|cCFZz+esS{8Y09d%wT%-HN_}! zR=yEADo7APFa9CTD>+p-xU_MMgOVqP+yN_Jc?>)`1S<JyyAF{P*|1cl0i>@|%M=1N zj*&PCw#s+%t{4QR%6r=xzj=NK3pc5Sun)!Xxon1niL{hE`lW+Deg5u3g4Z5465_Hd zJ85ad?eF3rZ=p1P9)ZB-t$dSjYJrE&NS;GhNCO=uT*(^^Rw{9^YN$E2D`h#kOcbM6 zEN5tk?<1dnZ=Y<PC62uhZ{q{gUPoU}`|eHSH;p@(0>e=NhiJ!bS5-N<#8^PI;5jhI zb+FWaOH!$45w&%_7He4jlv_QF>+${^lPWD`aiSm#78_V5*43NhtbvzMVD}Cj7u2!g zx7E%%B-q$-a_S;AR3E?ZNV8yORFTD-aam3uRO@eDISLp9_|D0Yr=_`zWL&eh^fDPD zuDm*GN@6zJnQ!hCCuUxWpL&@n3?KHxmZ+D@GPfO{COH7!nN%GmxNUr1K{N1~+^TUC zsr+HY(r^r^_UxsHZtp!SLz1S>UF9fF@dzxIyB_-Ha%A2pE*-a58d9VFOqc1NGSX7) z40PS?Kjcy0_{*3DrawLXm#h!BAXh8WwzJU#?CkM)C@1Sv8Nvw4=HToYr^PAO#nhOB zIi^W1GEYT6HV>PlOfqE*Mw~!hbsCJ&?R+oc-aa@EG<dR&R#}>F&+IYE)bGw8lfxrl z3Lnn_s*%lrb|@;MFgi!pN#zS4i{38oOevvaS)E$obqQ==IK?h<_=%Og`qr*ruSqaZ zZ~pSJ_^lC~@>8IIc`<xlJ9&7z2-#x*UnG5JzxRuQUf*}j&-SntHadn|f1<#S^+5!_ zvGUz2zBI#L7n&edbJ;0VlNMdmQymN*M;#?(G;9!jt2g@>1#y`qM^yX4M-f1F*TdpI zY*|*-CEHSi)^ARYfomDgP?}Z7IB3>g14Xt_>fH>f@L8a<51kh3mc0(SRj1!j-~$rp zAO)d>6+7+D{d`cM=Q9M%I8Qh&tt7<B^KVDXOrF?Jnd1Kd92E0&yQ;oIPNL^J$eq70 zDos<ZR*Eg#1y}j@{Q$2OXEDxLcbZ~-GC2_AZ*R%yU2aWJhut500A|oDMG7Uo!VVE@ zDZPCoZ&e(o;)*}81s-yUMV`G&+7(>KacoPIp_-2^hO+mzk*Rh_pRf0s>6us%H;=)? z;!|eT!XgM`%Ft=%OdbB;t6u5*Wna^nU&DfjPRFIDpFl=PS=<SBmBvCVB)iN{<boMX zx@y}Z+=NHp6G5jI$2j~OvxKbyA;MQb?hWCn%9_=*%YPr}2>jB0sQzA{gTh^HndR!w zs8F1eNB!diu;tE<Qy)m70~e$YOi>qclyqaw9}Mvi2AGF&czL&Li1qz{<FtHDW&cc% z^Tov4me@QAhRE%*x0QZ)5Wu7~@_*TxJM54rbPv~pF{__aC?)DT7)9$kC8D}8MKsHZ zq=QKwx{MOooQ{-zQzUxSTz&MQtPlC+2R$w$rV~e9UukcvA!rw>luZ!A4)KzEpOmj2 zBgmHyio^43-7d5gv&q3?h=4*->@x(sUzofp=2w5|*hoE98a!-~GS0vD`b08>Rvj5y zdLZn9bbhIb=p#JV`;&g?F|{GYHklgig{J%lUOZd@Q;k2^3CNfWkMVX0wd)}?0fyhZ zZV=ofbBx+<z2`$>;~V_0J6EsEe%j!lb6VN;kndw)&wBH?G+3&_a*z#@L91kPr=Wp$ zl&gzH*DK(*BQZ<2;)9+&v&%0dX=LG*jKE#DCq`;Zo`wGgyK3~9$FSIt5mQp3^85>7 zYPpvE;I~XJaZh}u_4YkQIwzDBwe0+KeJcEPw0Q`KQA_c_g-4jHE7a5S3I%gQgT!s9 zqC#FXnQQA!-5J?i2jP{Vy@RO=f&AkOxiOp&@b%_5fylHiMjV8(f<ABN*^9;&qYa5V z$3vgs3##co)1yF`!|r6x2kP701{T%ui4*!$8C_=wNc><RjuE2Y@g}uc7NL?g?VsN} z2FYxR@D>BuKN(0c5Ql6nOav||tlCCx8ubhs4S9Y^RY$lsS%AD{DP<Cw^k$jzGci8} zfjVKW=N38CMouac>605ReIhJZci^o7XgVll!337>(hbQ$@bL1mI=%OZJi2a;ANcNV z0YAahW*?cm12Zjw#l!HKeY+RT?=aZdh<y0TGukWSU(;$2CiF{C_~&nuPsl6FZp{QJ zdJq&JGspZm{krSG<m|$>Z~}-NLxA`MV&BLlK`(zzzUAo^s)nX){RsdGyDOfhydi1A zCFt$4`ffM<Z`Jpa0LMt0b^i<3fzR0$ZXh|ZXu*Dc_xvCPRj@Y5?(jI(T1a~o#&y!l zjR$=Nx`IgE;4NNj$VN_)A7IDmt#nB!R|I*)p>DO5UZjIP(d=vbch!^{MC>$4NPHfM z4@%B@WP<9QtCHXh8`xKp{ozm%VR_vfC5hnnqc4cXgzQ^?RW<983fG^AzIdq8zD?O+ z9ffNST`0)u@#Aic->jJ0y#i0@p1N8MZ1MBNAixKvplu+OsV;f*9VlEb8BiP^iOxU6 z3BqK<Mdu_5chWrQ&=@{0`e(@WfEDR=td^D1G!QcR&Cr(tXb9=(-sF-KkZAVeZ4ON* zBUq|b%Wx$Ucs)$^>qyll_XL6Y82cCU^@3xuw>S|kiVOMzxm0C}0*Fz%WZb$J>8|c} ziKn^iqHQR#&cTpOp)g{y_)IVK9#n@I?F|V`;>C|4O^PA5@~hm43rHgW7@;he?4`b? zWMQLCc&*GC_&bkatyixEX*5gjZgIUlUpe6zEUf|5!GTNU2>=;XOb$@QHQ<P2!Avry z9AFpmMD{+kXOjl#PEzRLan}tD+Q{2f?$&+*vO3ixVV+L-F3#oP5}CA}Igz*CIx_-9 za5U+pNHxH}HMDlNwd+Q&Up#FB*marsy5ZS4G2s9~X5r~FN1m5O-Y+*IGt=IvBpK>L zQKlb!C#8dM;Uh095w1gvBN?`%rO@xPF7>NpT_kZWNnG|qXw+RJiT$AOZW-{lD>Ox* zH`E?LpOc&=?@<VZG7`9we$X9bsh)ix4326)T!%sh8P#;H-`Ir})Kvp94qf{u_tV$d zOn<5^CnNi$#sFW3Mw%2fFWjlg=|>Z6K%&JHBjHc8CT1${C2jWjH7*-KlaNC4j00G- zaFWsp;K2Ut+ILy{F27z`+GG%zh8SL#%R&3~9Z+@cP2)f?GzC>n$lAlTQ)^QO?CD_c zlmm1ZEisJQGev4~`(zMFnPG7)+r@>9?XrLE{;eIAAi^;zBoJrwe}_8xz<<SZT!JXu z!FK~DsS~rcykJ>w_L3u008tN7le=K|CeY1+e~Z>AhC;FCvTDc^bq!frl%N<=-qtA+ zjJoPQxmL@sYjG6xY<A~VMbGIp@UFVKPBhh?)h>#6|7bC?)Bx51!L(p#N@0Zd>mBe% z=P}AS_(UgIZqwpGdQNOX7|E?8(vGOiMqlbG3_oh8#k;rpOr#MO?r3py^V$@Kg%?)i zx}q0#m(YCup9p4`%Cc?4@r(QKM7k@SMPgU3ilEy*W91Yz$+%n8x$;{D+P%-MWFC&e zA{ZZ3pi6xwv^~o^kO)ET4A@;`BI?#QjW}oK$kJF7V_#pqX+?038$8VUX~?qtOBr}b zz$vB_Wt3rYKyK45PcR@id&y{e=nsZY-7IJ%O1Ye-0LYv9%w)`>#0YoibVOfbg0d*U z7s{%ubQCjI<1vLE>08Wb77Fv7-Wk>$_Q#-xI!0gVsI8kb=}s$X{jXsRYwh7kIfJXn zg=a7ZIWiucn)4ED2q!|Bwbbdo{~rMPbY$s2b@xs_f&+M*60kBL*8+Z;z^t3agoE); z1HM09;X6C7zqN3cmcW7~IyP6%XEKe{#C#Kue(xCFN!tN5<v-NJuDf=KEVwt%^MS>} z2WfrR>OE_#GOFx1v#D#m`hO`=g)SOSh64oGbq1O%T*J44D^!S?+^hy}%(m#!c5Y`! zKPZqwV0mIktLq0||9!bxr?TvE$sFXEZG&aB!eJsm&lyJPH%mBILgFKyrV5^CsD*(J zvUY__ejgYgy83$*^N5kZnqI2Jss;!iGyfXYbRt3B0hW^RkLY?Z8xZX(U{L{&YC`7w z9tUe*RdU6c?2N)n74#D4*rKtDtJ3>pe)sdACeJBlbV(_81gxi?vAb0XlKc_Kgbs=O z6B}&_P5uqv4Mlu!d&af1^Gj(pX1b343ihknbT#SNpR_8-{lT>r(xBAkl(D-ZkxvP* z^wKfYKrI?QU#%U-;ot0j9sgtS@7U$;G~(iv=8>$(NDvD%Z!Z8?w+~8Fcs<Kyg40E5 zBg-Dx<}fjf{d{A0ggAG1B=Otk*6q&S{_!AIzt@?{qK#|Z+=YA(akV6ee+?~Vt(-p5 z3`IW^YcOeh%z9L&i61nG)i8YKT~_fC&?#bWJPCE6FnnWz-ZaFuZ7qNDpBx-(1Gr=0 z1?jeMlxmE|(EVM$*(0GwC875s_M2-FJ7h=R9m$9TSyd%7nvnUc4qJB9n-_~vu^N4< zp$ERb%83!qZ+mY0LEzIjVN@KoGK9ZhvQsm-n*_R*FSfuKy8Mb0e7)P|u{JRgdXJx5 zc9K4;EkO8u*bOB^AxOa6w2&cso1eo<#BnU4@LPFliI%J<ZxymAvo3O4p_8Y98HoOv zrHGl|D*@Zc%|TSat2~y=08{;~iXX9I9l<-MArZP0C+?z4#dIZ^cD1oMO6JBbV*}wk z0AW89rTNuG5Hwzh?~FuZULMN+7AuM4UuW*2z8@t^5lPSoze5TFqgvIi$Ky~8zprg| z81aD~lxVh!jaUo^Xm=;#Fjt&nH5ciImfhP%wQ{twjox+0KIB3Pc+@$ZRURjn(A{C$ z4|Sh*<4sh8t_>UGb{b(F;HOdvXwrQH`?+2Pg5&=>wL7Jr<ouM;)+Y)J>U+|^Hpys4 zKNRrBKL%)#-4SikG<sZ9@L?<;$UDU!`ZmMvc`E!C@On5mb3>vF)1p7icCS1iPEkuo zgYyo^aj`fQLl9kN2BvYmEIy1p58Vt<(=RWF51vYVKbP=LM1gpz|9XNPNt7H6dH)Q% zdl$(!f%Rf)@a&ZHR=Lc#es%0m4e3#V5esu=pZn0#{2zj4%$lJy0(%ST<wzazJj(wb zV*Tt#AMRY8jrI#&9_p7)eY{2S%<{tH$M|AsQ2HwQyhF&3hNkcRYmJnYqqPu<GE8Zt z*ZfKxs3)l`u~5YJ6j>fk<?Vp!PE}Dlz|9=oLX#(Uh?CPS$tjW*-lw0bvJgb=nJE&V z7CNbI2%vrjBxD5_qjyxUN7NjV`0R0dqtU7-d$b^Glb&E1W0S&-TFa9b{Y%p7{auF6 zvDtcf#@jOcT5(1)9tAIUK}>Q7VuMLkr(`Byau}{t&35JJY6{>1!Y+1pV@%Q$ABvGw zcMQ9jASKjLEb{Vf{d5Q@6J)4S+#?Z0jQJny&Y{Ai7-1{HHma}P&2L&e8b9UXp(b=k zMAqh*NMi2su5a`}fSo1II;0j7C~9rrI?j(w=5o4T>{L=rMb0GYhc*S5&EcDI7mlj< zRFy~X9`mEzvejs1ZWg()4WHlT*Y%&J2g{1=aazY|B>y0f60mj7iy;3&2hpe}xSq3~ zH@bSlQ!Qd6r*=UPB*?PWcZA?``UmdNMlAnuLTAbE51IYtKsE-PWx?x)z4lWkK|^Xh z5_bjr!Q@v&{y^7TYZxsUmJow2nr_g7gAg0o{>hoAvbQF!_i<wBFQsr1IFkTH%L$_0 zX3GaWKF<RIqKfyLmV1OuZj-DD-}}Bsi)QAKk0*UDnaw&}aiFr_^48xR#NGD=NAud@ zT)Y-mW0Vj!NL1xQn7H1Vvc^khkg0cfu}dj%CI1XH-xus|8pdbDaNgHM8&td2Ae0^a zJ3cNnB}H{Tu5m=5?*N;%4M%AlqzzJuw}T)TpMkkLe{(=o_JMsdf1dOKU;z;sii8Dn zwpq*2nnX|_uXY4l7Y?Sz*f|>5B(bV}vB%NODsa%$Xu3V>se!^ovZ7u?^+F5j^0~~t z<`-CVa-eTrzz1Bbs!g!(DpDW0G=}&x03Df3iat{}o&b_C;&VJ%!x&)b1Q?p*UB8il zXvBd%#VN{F>E3Ua5(qGMVc_*KaNW+y?G<&7Y?1y#%s)$kQ!FZ;Q_Ew7{Uv9aj0s~! z6n`$gQg4G!brgvz9>Qr##FsGRY`Y#n_P*nzf45=RL<_BI)e|FV`j48j?6bF==I?Wo zl*R9g*wxOzBw*2fXSWq9$yxes+~J)J7mqu9-zSHE`*QWb5CDr}AT%4E!l9@?cfG9~ z7{>v@hVrDc_0W?*xBN}F4<iWJy~!%_ov@g}vGt%=zt(7JYZ!44ME^{(dQp=+!nrr4 zG~v2dfYW2z)s+!o_a*ts)nL%YL~u8_fuAT3p~vMdG1jD#b70wFZEqL3YPfOw=7(T( zmuZ)r5T=hct6pe_lxgmGBM9Y}T$gttsIr7m?XsB%cnJIBJ{$}z6^ZuoM*7f`ir%#T zvA=XsC&Oj~6r`i&y(fWlz)T<`>-Hjg>4ijwZskV%(1X-XrO*_GRMw>h?9zPdIz_3= zZ^fGh!R`qJBBe7I$^4E%u|;09kMaulIz8!3*d)BE@(~CLaSM2|L6f|4Wk~iy7ZL9( z+KB{gDU>bNV+{+P?s0){wq~AknpBSh+(nyqQ+;ib(6$-^n8Q`1@VunAYX=_NOZ)7j zs*TX07lMq7$XR(@w`$Mpv8$EKRdH5%Upguri;@Cv-hb6-+h_+A?G2fFw3y_K<tT~8 z>tW^Z%APp6lRMq2yA}jB(@o*M*4LJhB_6v3ITgakU<;{ja07d+)ZIu-gL*U6qmi2; zrr)me)qw{h;(11oc{M>ht76mE{aho#iimAnfFw=s5Z(Rb_}jGvq2n=2_1`82#V9Zm zUfoK->0jOMuE2>;@B=_gZ4WjO??ow)pb**sXKRx&C0M35-51eE5;edwmI5^PDUTFZ z>yCRxl!HbRKSJy8ShK8(jND%Y2$-lT>ejS9{%9=2$Uj%fcsYMhVJkX0oVuk~;2ziZ zeCK44NrYRDo7^)cU1}!*ha56CUwLhDB2B&Ug5q2jZc7hWVaAr5{<uGq%(NhuCQ$a) zjT%>-_?Qm$YBNv%p`g|eck#*MBhb76Rbv)k!;vFk`Qwr%Z2%*W*#1&{MA%SkbDbP9 zQGa2Uw<C2=LITP{2&B{n7EF@acJIRtqclM52Isn6{LXmY_=Y*k;f9$0(y0sV?}z?d z6a?@B1Xt34zleKxFphw@drD2faE%3#d|#P$&3W10m{H_wfXF{DbONZ^hTI{YY<lT% zd~UIOriHdlQhd9WG!kYS36N!R;165dD8<~7oX~F;s?YcohyYDjlU*d?3e6jBtzCY& zgD@3cDAEMfO%qFa1}a^B`kW^L;Ip8jHH-+xC{IK3_)Pdht5BasnljE7*u~G*Pk{ne znN=jlqzMBH(w}~dsi^cG1~i{KP!(gIGnMJfr=(MAD10%YasuD%{V%FDNcAcy-U>aE zooXgV+E<r}(ReA;9;VYmxhi#9!`SnIXE^EMdZ%l?x7qX^^a1==t~W1Bf#WMOb92<S zk^Up-`L-P(J<QHo>ac|1=C@G)1AOtM=oT8%B@^*W0We&+aVQ6iG#g!-(Jy*55Zrl{ zNg}pREZ%KDW)KG-FOfGdehWH^T^1x5$$(Tcma+ONub6CFvT_$}gzcwEkxX#XYZw45 zt~s0Vy(>a>QFq4fZu${ay1(tPL?KZPKi&|>oK%-lVxMgR_A_<5Xx%mKc;x{<4!(hc zt?*vgwgVDGg^#zw&41J2;qeTYCauLqf0EuLn6K`*omtN?VM;Crk?=T0W*G|0bNMbk zul58xulIE9ZS|uM2+0N2@7JNf(c}~F>9Mgsx&uQ}@yohxb|lA%yz7Z;Xaq&=z7|?B zmg^SpBetk@s!EdJkXjY<TTcS=wX)<$XF+R^jB9T94$a7CD<XV%BA{5G=eT~W?_AJ= z4X^2?6*jU~f9pw#yQW|}W!s1&WDdvOdZwKD>PM<MS!9F)lvDJg-JDFZyZvP3zeGJN z6zr6;6e$z}uH5Ar@v)t`kn93sC$4o;L-=eE*}q+o^y=M07MbGB1sY?Xbnvvv2O5!t zEGgIDVe5CYe!)VPLYz^ioU;sTV+%;J>A;54c7Kf23dT%K&nO=p&sybrs{x0KcP#hS zJDaeCJ?D--j_^A1DwM0;=x7VO&mO{k;{rwZm9+ajccMbk?2~EV8DinC5cDsp<cx)^ zy=ro#Gf}`L(zw`C=kV-G`+F+>9mn5qYI%LVr2hwQAH%y@^>^cs;n$`1_7(g)PoGz0 zK=x|h?X+R>CRrGWiH{j1$Nhe=t7@fv5s6);?W74{Or|zPIYCfcGVjMdD<xKY?qBZ+ zEW_$uv}nm(?-JDf3dj3TT<|5{ULEj=V!xFL`3+|g2(j3u`IT+`F!og3P`e-I09*WT z-qVuAfs8#T;Od_Ek;slafp<X2CDk3JQ+C>fsLVF&_0yBy>m@ARgx7GAK6Zzfu*c0D zFP6853sC($R{CIvCll^X5VETOPK1-FHy+-nd0rQgm*(w2_ineYDi+m1B-@y*$qYd{ z5K~1l94TA)7c3ppL^Ap0Q9-8>tta|Nfn~noA+{FyQ0as=>MKR{N00=taC~NbM4_e4 z!HPn-(;ON*M)g3mD48G8A_Td6I{9i~+?$PON#8X`erU@x>N8VLjh=IFXQ;251gEAJ zu;z2&lDS^H`5UwI`45>9FJSglEjtR1<1U=4`9pddQ!rW!uQkF+c8cN5x_?wLZ~eHA z_D#?*Q9qyhA%!##k`@rJrU)V2*VB`#yx!0t&FEi2PpD_C{y?X$7D+lXR;PmkvxeCj zBd1u`7MCCH--NNlL6$>GGDU3CYx2Z6iNR)NYX*1*I<nclQ5HfuhF+hckK8Ds)CY^| zzd?X_I4_f%8^%3#3SWWBXb&!jF?D#)B+?9@Zjj4d6JhYlnFe7roBU3>S=YrS`PFZ6 zAs9k)CwI#UxN=2>$qfZ+Gxm*O3aeDbtYdXrryPr$v{3o58Xhkkax@g-rk*2cR-8@B z3KL%PdKmM7ObAiu<?)u<GxZaYxFw(_{*9An#LaBv%Y!LXU|dq4beAJ}+P@e5e#q^4 z9b8svNm@x2y7$f-cn#)QpIdNGwpkz*Xxht_Nx>rA!uc1n@T_Rps~L6lUT3V=V18et zewsam*43ua?7=uJ7FKMz>PlX9yIB~CO()SgESJ8NP*(qQ3E#tjxezsV@>UM=%sdp@ zS*z4d{GhAqce?ylK_3tbW2wRhR$=sEeHw=yg45pU-73o3fL7uhm=EwMFhOJDwc<V` zHnO-^?khR-(!=eYt0YIA&Ph=eS}ACA{0LrBOs;ybsU$p%h#<GUySg}`RF2$rwDE#$ zRwdu!{(^ju&KKTY@xo7EJnksrUL?Y{80zN71vcz$V3{p(JMsDBWFG(yYKK$0uzC-T z5?R}d4NMGtK&Ntm2vD`8!Q_4u(UP&1so_qeRmD+_uI==Z2O-Fbs7S~a%n9nG?<>}W zfzMo&ujjJ>DQdwBW!Ys34vNEL7n@0@vC{Zi3dzX|G(#78#}vM5Fsb7=Ht(%l9sC&w z<h{oy6qdK3xL)?sJgI4vFc83-!1<52Y6u6VRk`WEo_-g5FCctDZn2pfB$<)_eKXVP z3Np%pTn0g8QX$zf5?F{#lDta>;KJ!YVAmPvH4#aTND7x@^5=EQG0Rie0af$^L>S}` zoR>+%1ly88pY4zinXGz$LQ68BrFbks9w1QHQB>99AOS?wuqjQvtO})nI+5PL)GSye z_F?RvH)HZz+WXS${Lh}175nph^8i)NEm%-Q6+Mom>F5bmbWA!B)B4>A-)IzZ-)XK_ z*Mfb~cWpyKN|GnLm65QNE~9ajdFaa%B5*aFRA7x=zO-)W$}F;pap^!TMo6Mw(~sKW zBO2NKTT-Ng+bBFvxYz1Qh1M!7=qo2Js1wcfMq`K%ncy4jbBE>Uy5PJx8fzu=?IyGq zM8&j%rgc%kd`CRA=H9eGJr#(%b!}|(@Hv|P46B13KI1YpXZHav-oFAcyR=S)rIx7H zM%HCEDj?z{n-Emmy)&Y0d*0~t2RcpfY}dF;u*v8eV{OFP683wJw@ku(-%#3%yOiu0 zJFWz-^0;u-aj&qJvx^CFYB5?7lo3Q71WjhrKBkgXMDBItyFouXpavl*Oj|%_R{K(F z%Ka!O%zNponKB!*Y^NcD=Q%w<f%G{kYjvS*@=>z>KwMe%_W2N8eiW|thUN3;uUDK% ze@`gfB4+<5?lpMuBD+619E$2!D#AUn-0RK=Lr^?~B`O$5*V5Roge6nksQ9E}h+*)C zxmf(q=YTP|pMAi**wV%SBAtm=R{iA&YiHAm0<cr`XtuXCA@QK;nr`iwr%W9(8`Pe_ z=*<}!G+((ZHMO>6p5V#cP1C3@q~{!75migcheQek8C76@tXJ3@^&gEXd2@&kigPZP z(VHm3-mr&3-Fdi?m1yfwVUt&DJ}wv>#5<hu+MNfy=x4Y4!i?}PyoCIPzq2N{8JE5d z){pCeOZG*4A)02a$afb$7;$e4Ss9IU#?nIj@{mIj|1^$5{hNrW75_$DouF=~%q^sA z>}dkt*PC_#vxXB)@Y9+eOqmnF(1%5X>GSJ|bqSt2x~Qtz#I2;1I?q8(Ik-xfD?;qR zK-m9jO*w4rL<ysmQxP=-I}}{KG8%(U0D)M%+u#7ixD8v7I1;T49!Mwd(RI|xU{N;t z;fKhvw(18>L+}gNlO|N$DXT!Q*t!t5a^H835{7Tq4uxJ!V_4F!`~+`+7ZL0KEwY|h zJ9mu`)Iuh4fJcbXbbF1v31Z7UN0}U{u(&8+tK=^P8F)))$Vfn(YgmKwDs&tk=DP=d z`Oy~Yw+7WP>|4}4I$ODUzL45f31DM&_FIOfc97cGEXzeA&qQOXZ%+EuFirXC*<(k? zQwNJfz-0)DGovuj#hym(FzThz8%9TxD)RcW*bA{U{pSS4=-+(I%*ApBTO=MM4^Yr1 z{HxY28%MKhFgboM+i4zi;9re;V`j1Q0fFPL%r73rwu5p6wH?s}JUlRd{edR^tuPjy zC*etDf2q8jt@|D@=w}RqTB`Dv!w5JlM6}zaBa}=`&l5XVzsEqbgF9yuxaCHnq|#{1 z8EmsL{BopR#4N@^puxQ+KtYp;@9gMH{6U;AeQ5@tmKFlGl~}?ZJ2i|q8oq`dM`W^0 zQpPA;@G)!%8^ogw$Vxb8#kDq3JS?1zG(<;5vC-AqRaevn9aflgiQj1W<(2<1KM4@u zoc*r*p!I-Ij|-0*afb*6`UrA4br(!~_lg`rS6#ymXwF?Bf<B5F;2DFdGu&VP4P-we z?GkQTj5-lF*@~3T0l0*mQL5k~>~|m32Wb@<>VdqNL5LXXc_v_#3_fS*24IP{ib03o z1RfE`TP`$G7$9V)1dkRg>2r!%lOzo1Mm?cAUoC38N{^VCo5OUU`ZZgS;A0$FVEb#W zjA6VMvCZb(L!@?@ZvcY47x>ST_zmBR2Ej!uq=PPdFBS<?;Brp!Lg`>CuSoT~+%Y{E zJ2dj#G6TRl)7tFKjk*bG56b^M8HwS?Z_r<M2h4uQ@XyXlauWuS_f0S01*z{(CY%Wc z+9q+Qrq(8?vTwR26yxt#Cdwa6W49$`nq7MKou4XMb7;m_r}@Kn9_q|W-~8O>%g`1+ z*VUPAB9dR2UK_Xl<*5+NzH*xnieusMe7UTgVtMm9eu39F83fcgjhGs(%#^9x&Am}K z5vsksS)}v5=b6w622e#{Dk8}cX-^RdL`6u2p#;<CM9Ex>7>5jh4%UH6j;>n_6AO|p z$h&g2unf35G)vwN`_<vmM;K>M%XOBH?=~>=+kg3Y7qhVmUUO({aAO8X$!fQI1?s0L z{$QH(IruCY6o$pIj@5w6(I3Viw#;j6cV{bP2uM44?4DkwH^<eH)?M9@bZw=QYd1}@ zgtwwV;*akql_n*oR*YF8J~C&Xa?BiB`LH%a5t70I*f=Kjg-bcyK2ALtLFC3bZ3Q?z zbm0#x4w}IuWmbNYmFUghbnIRQ9ZQR7{>V<?8pj7TSR_87{Z(kwo)AHv_NmA>i&QB| z0&y2E-hlCz0EFFVu1~vWI-);VKvQV(vi?tEU;8J!Y2Hj4BC<o73S60`5%?Ud2Tk^~ z8}mmIhIAPaH8$Y@6e|$|nkBF{OG^NMA|})zBk}ZS0x@;*x(gh{r`|s$aNg$iL**vT z#0v%w;&kgzL(9B8X^eOkQPSTSRF9$eloV&`r5?)PPQqezGW)7=z%ccjgq?J{HET(n z7y5LE=z35^4RB`sg{yJ#AIIF)07<eRo6ar=!242HYLhKY)B{hXNqAFBKjrPI<UdH0 z5}4(538Vw&I`a7CjyPAYBMCS=ij<Vi2}Q<l23<_e`@alWv=bH~4<M^8xG7d`j&-a0 zE3UMi3nN0!@AsV5th)9>!<o~L(oCa8cDP8oez%4Y!Flv>8g_C5*l9pp%qg`uau3SK zXRX&85FW1}45#S9TCJU&zM(E&C}%n$++XAhzsz`WEzI^r%F7q0^F_7D7J+LX6fP^$ z9|+Qph%nnvYW+~S?#J3*_c^$L+JlmZDiS3dshdBzEfucxgDJlFhqpy|88H<|q8%w$ zI$W(zUfIzfk<lxr<Y}}g9i|gUT$Hq8=#&jMWPQ8yP71&8RWy<I9D74zmE_q5(|Qxm zzNpn&q-K!@xWJ^f)9TuF;`#tr$ha&Wp39j1s(TXPT!QjOpRJH%pgCunTr83aU~|n( zowuL*&PYCrje3H4d&*;w3rMNAVX%gxAe!I2PupELd!uFUM~#vIvjJ2LKULcn^%V4l zk=)<&Y0b7p=t->6dd{k<J`E+z$~Xw?#B<bZpB^^^7|`NuZPL`eL9gwFjc${3)lt}r zqcaFWr6^-jEW-#-#kT)STTQb#^j9fc1-BOJE(coHX0zQhbYeZ`55z9IKEM_^F&DLA zYiL{gGyz=Bc(DbgX<SZ99zo3EJBKSLAdnA40WpCkpcd1Zmf%$4D*G=5z#jHp_AJPs z+%XBkckK?~&+ni(NLSZ|9hDR-;P2UKCD5{|eKeP?B08R#qB8y-rz}VYb-vlWo(oBX zhKvJUx!V#>ny)JU=qX6$n`1}lHK0mK$hvrTb}rS0&cy!Cs*kOf-%25Ifz-cMub_Rg z-+!%BMmyIO2K9%)h?SF5JU-;de%?&3V$<G<AB$*1O?ARIblO4uO;f3*b95Xtx;k5W zQZ%Ho&Jzw2henWI0*ImY&4Cf~`FVtW>lBzAw|pIvqqyPE=}598ZD+TWugmS@zdHQ; ztrN@^npS)ej)4oGr5@Ld<qWaRVq`H+aE?>%YsLpy_wGv7AavleTX6`99PgftF`>ss zS0vRClGZjr=qgVUnAEeUmr2ZGF!<gUc=-Q%k>}CHe7p{!rLNi5XUQT099Q**#my9b z^39H<R9??}072}Bj_Nb?M?={&JX*CygwXBN*!}`P&=qJ2G62L|$Yn+H9?NT3{K<pv zEyglGZ2|B&lpA?k`xs@eOFar}D+D0$D00~g^r01r>P+~nwkKG48t|EP><6bW?udV$ z24#TnHPf}-@>F_}zCXS7^G5Y+>zS-FkM}kh%m)mb;=xrslc%z0Xm~eqVAv&U?GHOh zYrc|h{-!akk==CmOI+4m1Kaxx%dTJM$lfU_r1TJ(n1?Qpk*IfEvN_$j3A)9Y_IeiO z(Yhr>?o`GIjZNHv@^E?<ZS*aPsf_03#CBjj#0JsT7f4(!x0b*)^>lw&NP`@j2MZLd z>YadV48#$8c6i6HQCk>bWI@xbjt0RTWco&|*_kkPya@a$$RTbLu`uiVJ;6w7ug)u{ z5EW83X*vNuZ=6Hau>1c9V@%>0Wj|>63_u)HHZKbgUM_21mCI0;b)8a&9r>InW)b+g zQ(xkpvP_YuD|=am;gbUjW`l*&8)hg?>{`?RF&7zGYfC?9z_xGSVNwL)332-9HAp+r z*$n$nOv)gF0;L-6OW_2=WnTPZASq5LT!r$yqYqMriBTSwt&fI-;};0hDNuStUv+pY z4?3mCXNN}^=HPVpmKz9LQ~yGMPAyK??;lRx!+D2;ZWAci2(22)y~PfTXO2@>ZzNSn z9U_;jub<YBl4hMs17=D&a8X7YtEL(Z$#IDl)9qh8oI8)mUdBq!R$rZaFvqXRPXag> z&(Yv~Z;*B;s>E;hRrIfw%GNs(SyQrjqbQoFAr1r}TsWbuA_q9=C{qrmPIM{o?_%_! zT~ES3>g}Z4Cn)jPA()ol=9D@l^B5n>Tm&4IPd(YqtAPJh-uB|u&|>R(KwMk5hG2_` z&>>#!9Z-v!NES=N#KAm}EZ!o1((K>SvCEsp71Tuvg~k*nK}N{;(fj+3ZVv4%wYO|L z1iHJb*qK801_7{Zui2&;YFe`<TK7^~`zX~URVA~x8_!xQd4CvhYAZU#oBU-b9%Eol z>+a^~O@=bP*~?1HmFTClt|&F4nAgJ#I*)na6bFs;c?1XJ`YibbmLM`9Cb?YK=;L}$ zk{u(dCnj23u34TK&Akl0vjUGe_oSEhCh3?^k7w9NV5(EpAr8s#;sRkUgLve-3G*D? zM6CyJQI1h>1$q5l(XOSnYCJ2I-H^F&Q@|!5D?GH5=SmZRwPraau6&%9Y#wvxh)&~_ z@XqIy<GWgmPVC?yAcHgtbu7GmUzDiYuV~5FITOo{H2k5<yeUQ-M)%Rf>SjJ=m&ArO z{_=ax&~BvCK?b-Vgo2Ce9O&kJHTWhaEOK!~HMs&T%oH%NzJUjU!zpxtV<U__o=c0` zJ^3{GoYNjZRO1Q4KxIEIdA_}fTZN=&j5NmJ79(Y#>K<5T;JNdB<}a2EGtA=aNJHZ* zpM8M{<F+H&Oi=8tmn>~bxQ_d;Nj)WkuC|`o@*NdvrZLS0@N>!_yeyV*vuv<6PRZ!E z={BT2@|LGQ|4I^+g&j4k`^z@C<whw|nds|A<#GunnkO<wtjt^~!LwJ-Kuw5yy{8JD zx}CFQNQAderyvii<-}(?Mgi&*--?WLn+zmfACuN2XM`FUJC?tODN!Mm!_o^g3IAB8 zrlUkWXPidvzE#Lp^_yl<vOaNT3$Qr?-Y-StH#xi7cbTo|`GlKE+^d(xOoSByFmVcj zM)I!^YMYSt9s{bu?NW_&*vd}m158kB+0pWiH(e(iml!i-q!6?ZZZr}!uZl7Rmpwgs zAB1~(%+P-(kOQ{)yxiCZ5;KZiN`|HCP&dHi`pSs&cdR&wQI}5hd~tfhxWlWaqS}UX zWmoqR942(b-iSZCJ9OmQYQ=Oq*B=;R<F@SqZd0!6Lh5axq0$yt3ASbp`H34rnr`Wm zD02PlRVVMCrk{Y#IFmNVf6fl*(8e73?3~M*4M3JAFGnYqWL!%he>himnJFV+Dl%4k zV%>0^+T5Iil*SW?4)Xq$TJ_8eMfp9z7^ko|YJ(jCpHIgPpEfCPAE`Asyu}3rC=YRj zrjHp{aoJ{NkZvHw-a>Ly*`@-Vj=J|{9U7Z&)qLP<Fq4DV$t}j;qZPc?)$Kn)^71GK zK$Jk=31v!$DDbf5l0mU8Ut#UO<I~%1*v=QqnhbH^bt}o>+$j_mmD3WSt1F$UEYVKq zcM}A9rvg}v80Ng^>5`E2uu7XZm*Vuu1SmZ7YbRN{SQ-g1q8S^9gA@B!$E?-#Gs^KN zdpbIDNHL{cV$$~^c4pfXxI8zu$61g3GJ$&Vjv`b!f`TW3;oF^RrApwJDdx`(Pw9c= zf>Ua`LpZK-od@~z$WsCnn)MxaZD<WRkTJu86_lYIL{v->wp}Ou4Zp+6o?UGc1atr@ z(>LZQn49|zmn<9~Z<!}b^GyAUO{~9-JfXcsv<Cj5nyj5&vEUGfX>(n&EKw)2RCd6S zzTBQRu0Hv&M&bAzjyJ7KOpjO>!?ZC6lq6q;^-em*oVV!U=6V_mjbxRnp$}!4I(L{> z*Z1(i-kVH#J(w>c^WWhKs<IuVW4m=V&yHVHdjEnyvth8P>71<T%x5A*+Y9{_zk|?X z-kc3d#v;fL>?@AR>@*Zv2O=M}D6a$q0&c{V2ph&<jsmYPCmEScmN6E}rDe@1_~O-K z+CC8RIYH@A!dTTCSi-6IX;XZD)E#JvED_1a&ssulw??;I<|OC+zn0YP;~t>YedNre z9@jQuIB7u)JvqHwnB5a{0`MkGos9+_q&t{!Z>?ycKJA)YpzUL;1X#uev_n#(RVxTi z`5%`Qq0Zx%|2Wu>#W-H9zE07pgi8vsIE50-7G}i?y%C&E-D<Etba~#rYztUYl(K!# z1;0quu4<x!A18jGfErWRm3MA68-XN_4Tp@$J02UFbrs6bJ#;R1meX_ZPf189Vebe8 zFbrv)PZhvGX~TRVoTZF!26LA)AUv2PkOt~hO5}%4EpIoJc4fz#1Jk6J-B$Rk6;o|> zz|YC0z^8;uoymkJcEl2Qr0T+-cDw{*bCys_JUvXr>0>)K^6{wSD5O&&n|G;wx6{|^ ztGw|yqJvz2Lx9e|d5VS34UB@#>{%y?6_7vC=+LHivZ%<H9aWMlRwK=3HrFgobYyTR zJ46QW&(SyWC(?3sRgslh)m>JRI6rh;fy66Wi%Deor4o1)HLppciUQYgAhi=Qar>>j z>^ATeJMff<NALucjAcsb#G)xa9S`d=81R%x^H<7idZmvkPo%=Jf_s~JU+gH1qtrJr z%~}JmW~lwkNOn71Ed5wvRsf1+O)}h-cJb`d5iiYphE2>y=0vu_{h<}`czWytP`O}| zNmJarQD3RVPf#SdsT`7XlO<KA2-&hM*7uFLa;`XjO<s8};=#S35T_s$OQ5`4Lu)aM z04P4oW%FgEA(cclYTDSI+U0jTuoL+{;1?kxTPbQj-8ZlRDSQqp_JPC>><$vnxLMCZ zcIZSGJPo7EoaAar3Qf;^0(rANaSi7~;?<&?ouG<rD7S;GuJmk*8cuyPJG9NQpem`S zk78}HwB>v1!kq_MdALYqJ3P&34X-AA{yyxv=r4OdRBV-jaQ}pFVh*ZB3=CJkz<g60 zdb-C4$CVG`LyAF3<TYl2ut@4w2XU}DA?15*S_%vD&AACX9%3o^V?^Gq=M=Dnt75Oc z%Mzxy-CAx|f~xaUXh?=@K(~*~&f01j;{r>slSjZZ)}Srf>~WRE27QuAwIIvkNXV@h zr5A7OlT_Swqdo2LjJtQ*0_5{O<_^LuVTH4BO~=po$xOhs8R6VfR@cr>uH#qpv@<U2 z220FRx3@W{A!cc1i=zZg;(QC(aTm#?yC<9y_+_4?Fl+E+Li=8bUIBk=9Cu@O&E-?- ztz#CAg}X%c_8b;3TxYexK;hiaCrHCnAkPn;Ux2E8tKt(%P7EW6t2LuzUE%#AmxYWJ zHjNGf77JM4_IQ_k(f+k~y+ke|zoFDpl5~JyL1xngq|hS&C2EN~v+NBUn~91XF#{;J zzPGCBY?Ic^YId4(MG}Kns_gYy8<1@4mRP52d}+T{8Mi5I)LWdL)^569J}k$I*sBuN zLE!M0)O|MpLHvs;T<61E)APBI`(gIWCCDQrrN}BunUcliKoserXx}EYHdf7Jr;OKe zHtx=4)rs}9?`smNdXuXRt=AgU!YK&RGkLE3N=2rBs$~PwRVzoxs%h;2jamO6$3$Bt z5W*>9-<!Vp5gM(OK4p=Ki2!O>_>e2UMmec+!kgTCP;`uHdnHBt_Nd8iW~RM|?>=EE zY6s>Adg9Z1@MGm)&cNyspcVO@9FA;<pc4Gc><RIo4*Fy@^Vk&if?dLP2HvSuPL?<h z+L-Gc+UJ3&;$pvwdQ-g8kEWkUe`q94iFLCr0vnHG_j9`vxmkkCyd9s~VnWk5+Hg(i zNio&9OT7GqkQz|r;Bg{2qU7={m#$Rf{&0wcnZ@AD#*_Z<{IPz3f37t;tN(f0kJ^~P z+&|ply2^O>5p)Z5dzzZf5_2WgL|rK2{684_r)QT*1EH}e5%Jn}=;Y_^fLlBR>#bA? zmlN_<r@U`a?Wzj`PS!Ke4Y~A5j*ujE@02tCLO+&8^{&*=1bzUVyQ3sLG@=7ZE&;eu zdk3(hl%-IUt8$ObL~4sZoa-g{Lp5K0Tp7KPN$5OmO8jT0oOFRyBJx~H-gyMG455J$ z1Lw#fDbx_5l=_j!IdkY?)Zsrd@bO3>C1Jhx)d{i$P!_-y4aYZTanmw~Lix`dC<wGk zj(;MxmuQ{mudweTi#G=zgL;jsD?XmlEu88QWBv({n^>1U@oT>;kB=mV#D}iivi9k< z>~U)H0qBArcPR?-nwBAKL!f8_ZuYp<^$++^iZl7^4#)?`o7fS+picDy97r*{8P}P} zr(NlHng#WJ5&FEpOD(+p4A%V)ngkU(m`(*y8CgR54?}t|0l7Qg#Rin1j8ZqI=qgU* z$14?Jsp0s}s3_kjUaZ~d%c+Q@Z)D*#_v^078%pw)w`Bhq^_)=(aL3wG443kSTK&!p zqaw;|q`3<$S@Udx?2iG*gBQ~IafbhaU+`=yq@uX^hMN2IwgnGh#VJWqz?FWx1{IRo zvGsle7S7d~?{^C*g>o4O@jeW-ppr(GugFFf^d9QQTS7Pu;}r@8A*uoQFMaD?9zok~ zYZRI~s=pDmxR@vKmCTOrPCcV!YYw$xC`zyy2&zo+QlRhsW46M|i-dJFe(4>pIf_nH zaCE@$J`@QPXh-stQ0FTcR3@*WcSadn)jIvV`zb@*UhQ5Q)Mj!QRq`w`*DSF23{GUS zOLBLqYzgU7OE20O5o8o@Gp!yw{tQ|^#}N8s;nOXYHDVw>-)c(BB8C(@$O4lffD<F> z{c{oRSv=GvIc@^AyH_S(@VlPr`G?nLf$JFw;!UqiaQoq!IBaX92Re%>OyE!Uc-O4b zCy{Qw!eVez89Q5qz?PrE#(@2wCYkW{HI{)<Yhk31gdG@wY1EBHGR;QT{80Q?3JKZC zq!dlrLuGCmB-xt>aclmPZB>C`%HLOX?9H!x=99kj;5^+;LT0tTZBXyGgJw*@qBk`o z7MNUkr;!%u>VexRp0Akc-)zziT8h15&6+9@fQYzL;JO`Z&>5+he&8_z*j^Q^5Hs93 zo>5m5K}h3w8RKAH4KC$ndQ=R2QzwL<XreZT-|~r#J{wxoZ|}UB5acsn)-xquGt4Hu z>c3RZvncCod3>Ez{!e@Z&pV{fD*123PsIVJ^PdgY6e$M!?{@?Vo+b{~R?22;ba)R0 zqI%v42}q8G9B2Sf5IO!OUhkx7lo_x4XTTk+uLzWqN2Z+L%`S{F^)W7QiSc-VPE0J% z)Hn0KH&dQaoMy19DJ^+3*r#=<RH)k=k6UXi=<ArDRzv{9cF^KzgK!)S&?Gx#Mpxjl zv+>`I5idiqkxXxMeUSpHS<>fXiEZzReNO=3j@94anKuWg9;t!cYuET6)F-hM#1&au zaHwqqa}OIqtGW?%4eVG8PMnT*dQsoAI%zwMQ4{JuXXf;;TvU`ZshWfH?JIvE47JkH zdo*#5r|*|C_T;_!qPt-u+z1b<M@9P=5_VE&a`1)WJw?n&0zX;30_!!#>Nr&l)H#zK zFNH4|>Yc8$*!{x<aT)wswvtDwvrtb2faJTwTi&02m$56|HUmp0tgo`WbL&MMMycu> z{05;6em~#X-xFhNGLXpK*(3WLy(kf-M(3_9hyJ?_#qEUd=4`5o)4NN>)|h0#(h)c^ zwJ-DzI$!StA08Wy<F0lW<s@)nj4DLK*AGAjdYlcqB_{`M9pF=+SYmL&DLAKjt;9m= zuff1q#4z)1=K$pB!1M1$&mBXMdt0hj@bn(H6NFWM!Db|>YEnv-0-DCEllRWCIe{(w zk`H`bXYp5<DKg+N!GY4NBFF%p+`@Nt^VpA+)K*LS#_VcT+mF@LF0#mQ^WMp@n2~OY z@3`-DH_8tLN!}B1&qsy?>U+)P;1~~szn|#T9s@=8!kxcN_8FM7*ipCb51YXVe}I58 zvxOvMdgLEDc2*Tu$&ihDx{WlTPF9kRC(53k*>U2?6fsiC{^H^1Dq>(s9`l|(PFwMo zB_V|>4Xv}64?4Q#F+`)m$OO_biE${xsW*k;k_%(e_l{!OW%T2qcAls%6zY7PkrRB$ z$g1(S-}OnJ+m=C-!}1CyeJ}i6qun-NwkMbXI!>?>Wdy%JSu-6t5l+}%r!3hh%(u+s zK4Z!?qv!1L`3G0jyZY9uFJy%0aZ3wHFjUq9bOm!0F+){2+e&*K+oR%><j*%(CCM2= zL(*7qTC(Q4<tvxN1Yg@M_L$mlw=)bF@fBe?jb8<%ZwL@Fyl2zJdffG&r-_!?6;3Ug zLmj$Rhrojh2e0OU6V{O&CQ!SeJ%4IK69IR)OUb3lCcV+ayd{zmCN=$>H$C*kzeQXb z@QPutHhcCSfWR6}Ye@zV-nkwLx(hicQhO?XO>Ts*<}kV=q^0lVe!pKot20n>xx7-3 zctEg$0w`j{Z`Wos50fR8bKwxRgT((;DNHVAe6$I*nG3FNg3|Fn!#qvU4!)hnN{weB z(<kp1u4h8Gl0?iy<4Rf-;unP56utQuQ$<B4h-R*o@<7CS(VC-iTykZIo6Ah>CAXQu zu0h--LZETwrkg*Hmg69Xg0vQHEdt4@KP8U`Uw|C6ydIY~3}g@bGllSv5y1+KJaHOq ze~#M`a;+hLoCgnK_(<BAQ~7Q@wm<pOV_vAm$72$hQI*Dn<egFyDvF9iI<lS)L@Ke- z%NF$V&i^3Q<J*8gqZ8dsbW>L>`jBI6NI|rMry`u;YRcqI4#I9f`6*_o7#WQ)qd<RN zySUboQ}d@XitFnHkP*s7m(1Oc%3t<V6k3yyV>~#^O3L+4!sAFAx)@k$33~?o-aw-y zl;H4pkMP`|W@|+B@OhxPLT`Pythp)qbhf6M>SXs7#F=Qi775^Q>x4<`Hl8EW`YN%V z7^#W!m{p)?Kg(qSfS2m*GBAY_`s3cNK=*xO^v(r!c9?6wQvJG5VSz}GduhQick?;_ zJ&H1b3(Kw!t3*_!IlIt4=6&+W#$j@ke})~^Nk=~9*6AyNIm~mU&*nn#>%Lb$+wV!f zhZgM5a~-Ntjq~*v7J%8EiN>SO0RiFrD!DUpG#n?W&KyjJlaIB<8E~Z&I@cke6WpY> zn6v*J7*Hq%j3OdaM}SnI_L1HGr_uZysW{a3*R_DgR!VM%nJ_KdN`e!WEnYbRn!XeG zW8nap!}&$nqoW&`B~<`HK)%2FMgGa*Q)SDWku+&7V&sR9?+1P4Gnv0MQ7oZvUwjRN zJ{cN~RFjIvelf*U8|{6FLcjbd?S;E4bGOg<gEZ>{(Yh%CWX?|((k$(va1u5Lrezdu zK)OYZoXrR8A<#7YT8|YV=3)&eBED9rI9O)WF~vl#DZ`Y^Ozi?+T2zFci?eG)iK%im zkGL#<F~hXpdT)zWwJWcEJ3z@I(Sabk?9-2IB5=3Uvt6oX!3fGBAm>1!tedusf{EUu zIr@lL7#uO_B~3Y6kmREdl6Y;YKE*yknx{|}39(}OA$<fOkLgX#d4bz=_!~VSc}Mp! z;3b1s&%)gwY%V*{n?ri3D#P7F@@XL;;Ax_^4onoAx?{6tW6484JOHUk%)PRiYnilk zu0m(E;DSfdtDWVtS|g<c%12nUN{7DYW41hkB3G{KR)OUDwc0*@&3_HCgW>j<7uB-A zw`;#{=Euq1ET3-FN7c69&vjsYKHGrxTqFY$eG+QhVL!{&W>5G+K37`-Z9q)J%MY?H z(4|H_t>fv#ha@}As3g?oucS(<c{A<+hJN^dJ{KIB0n=bCsVO5}n^Uy_$3GOu75wN% z-9>Ab=}Z?0=v?$-KgSVH8N-6*=W$5fI?9sH4C8vy4a=?8Y2@g6QKHV%3kgzP5h&Dm z72dHfJ3<Qg<6uN)>*~YF*l~t#O6op8z2nwwZZeB#64B=NluH)_P>$ITVuu)8zD1!n zaOGeE*S5vi!~V-HHE#$H{RhADFq+-Os!=l|3ds4$AR!;!{_Len^<!x2QyffzAn4Uu z6v>Q$@ADTBU25ph^2g>K4J3-b=B`IKlLznE&vM^YkpP$!o4kMv#ON}5czlNMaGk$h zZ5h;<cn`~n2WAhNpA0{le9T89KA#eh$y%P;As&(wc@3y9B!G!WSf&O1wjZ;;N}aT? zxIoiA<KXkp7sbN)vzl0Be8xgJJQOO=iG#ARRjJaFSog?JV+R4mD-+i+5|zkeq3m*L zYPf^X9}%&I8mHrsl1^JgrfMN<R-jA~;YEsN6LK0{9O_jmUcR<HrtFzGT2snpH+dSQ z)I0^0VAB5(p=gTp1}VnTIx&x_5mygXwa>oj)Cf(Q1$R@8D`?YND`NywBO=Du8wwQr zdiMHf!+UTpuPKOb$4~Po`O^`!J&1gg#;ysMEcrSTPj^y9i2;Cw!XA!|7q3r1Jflt~ zCqwAW!Ujwf5G$5r0_jrKNRJNK=2Jw!^g%f?l;u#?0EjG{6YM?f6P#Is*@e3hp3KB6 zDpO&#oCIq<v^!y=f$P0gmWN?sw49&$ldeE_W4**naf4rm``oo5Z88+uJfyT}?>RPs zx<W59n9CB8SO<RJnXc9MmULdSQR;ChXusKFv9Pc1+0l8KaikR;nh^B;K3)flyS?be z63gqNqD~~FMKaaFSlVZ^P`;Uo1HXcIJV-4i{Q&=C$wJs&?3Tx0UuJLr7<CW=CNm2& zL?8%}ay>{Iv4uhobJ?pB;p}=z425a*cAXjJW$5RUXdJF(^pUy~XT-KRu8PO~ejKC& z4}y7v{{DaY>E(aCc>vpND^;7e^m54(t>}=?#}c%K?Wt}ZE*&XFW{}t$Q^ys#{Mz<O zXI>|k0f%r}+p=o5LVf3N(jSysSFA`kY(&Y};r1W5tvYJGAeU*_5-WU<z@Wp{H5<Sx z^-xeUiGn`{lF>HhB}dy-4K64~t$*%xMM;)D1rCbM?J)!aVdB$_X3=*Mk14BQStLtX zcq&X>-{LC@5_7%qx?dVg%@bz2y>v$N1>Zs;DTD0<OPnyA4sdNA#KUB8(ifv9I`q#r z^Bcl{RH9Xi<xjK@_WcJ%ERBUaR=7lvjv<qqrvNikz8cVEQbtCJ;lqt-=u3s5qzlb% zf+!>cdNHab`Sh?N+m>=BLUc?tna+ym1VS}xRY2hm>9jnwB8vIX4CVZD#*0FRaB)KV zw7kCB5L1g4H-w)!oX5Fy)0K<Dh%CM)nr$@S?BO5X!UTV#vZ^*AG%?0w{2E7?jC^ki zv3;@^HQ%_;tsi%u1s8eCHwMESs#+}W@(;tAwa8IDlHOz`e8T8tzD}HJl*}SU#On|V zY1a@o`sfO)OCvS&T+rJ`RI#SXXFdX6WAgnAQM11w@qt!x0A~1c&FQpvgDNN;n+hWh z?scI~W$dR<77&qfbQ^_f807-=yX+q7gwx{UD$)Mo0DirV6<PacF$|KIgbBt`p_p(3 zqg6AeGn`)aES9M(F(E};$~+xTEeY=vrK=t6N>O2x?k3=ZQfv%aD!pI97Is76&~VwG z5HUQ#*moQ1Np2vc>4iO?S?Semhq2x6koEh8G5!+O9|#p9vNAlTH}3RKAPnV#CQ1lT z+$zmli8RmhLD@hPF^l_%0ZwW@+r-BZfAH-TaW3tFFj|mpqO;e-vVe{cocaOo$*Cq# zCWW~ULUR+AESRE_zN&}bq<9OfLW(8O<^#kx$z;Vk3o1*i#IRIF^W9&d4!stBRDzLt zOwOW<L`X+0c!)+3uP_5cDO<3ytB9*EwsR5pe{i?~ej*eX$#3in6QY4_GSnVN4^y8k zLqJQk0rnL`Ch>Lg<@CEnn*(>h*?T~?NH!PW-&7zgrtP*?m4b)ix$3ETaIsAjFZEpj zo|y*l5@)DKc`aX4Qc1qks+qXuvN)s|XbMn#A;Dnvb0@9wZ-H&-q{=ye-JS@}!t&21 zRu__ej#kvV>Addc&)(S1Wf^tsF(dVu%FfZ2;StiHm$8C;4Y$xW>ilrgeAm@#cSXu6 z=V*H}FHR#n!h(6s(B$19-RdD9o5zTTxaGb;-w-}E!)_1xzR{YRX2aQ4B)7`!k#EpX zQp3PRBH7xXF9ZGa4hnYrN(?s5e(ybYXeknqp0S4|LCEZC6A6e|H^ay7fOzh&&Nm^e z#b5#p1uLngd@!cl%C#XW?7{SEy7NY(a?s+p2+D817&ijQ=^vpmN=hOWjpcf_>W0MC zMahy@p<?m+br6V3v4bBojf@y&LtrlWTNhQi_IoqZr#$eB?6nf#rL@}WHZRoQY9dzv zy*c0Sl#1Jko4{|PD=<5l>=Cx5@mQbIsqJ9{{|b#xEkMxP?804L4<%Et`m9R771(9( z&xtcsdd>dBo?6QrTxRD)N2(K)=7lvnI-P_e>pjP=%3eceqCNFUkRvycMZBJNqv4qB zuaLNbu~T@Bp~O05spxKBkhHGdsLfAYQW>uLgOhj-iYAf!d8X^^ebC=09fM{-d_%X; zc6`073N9P`6hA6gXte2cK^fwa8jiiz+$gDUmBD3Ogs2CpUviP|CHWm(GMzl_9T#AR z6m1Gv@K%QY<PyS0s+rxvB+qJIGiB4iaoOZV+8MH*St;2y%|0~O?%71pmGp5i0VhPi zd@{h!gOZ{+OEM_{&FbcJ1UW(R`(TqTwU}kDpNY?PULB+)23HjSb^4*6CKn@PY!&-Q z%YC%<An<`Ce-2_$vqNXs*pc`o6rqZ8w8a(!0r1Bm-zjbMJ8Rkg^MMH0a5fAah+vC| z6-dpmPYQ{j`bAsAVe04AUP4Lh>*Tbt4!_aj@I&K_YN*8y2y@2DO6`jmT|;)scy%;h z{Sl%ozCF7?;#=Q{FL1?#$a$yS;;tk<k;lJ=Fi_>g8vzj#ibHFAwSX_RK#iW?v>?Y= z!mQ;X2T(;@v=jl7YF8Nu*Uc2q0zwK68YaKhMc-9!C_Zd1iwWN@BZw0IMz7qh-dB*- z>7!Sn_IJt`I9&o6oQIfG>AD?j?zx<dauJmIG80Y&n}=_isW<+h&+PpwV}J6IFcAI& zbi~7i_+xXySb@I~7YQA~2;aT`fHzlgYTi_tH|PaqBhOWtWj1slU23@jfrYQDb^mU8 z^aoJt;Pj3|?Kf|msymlb6ZJ|cNarQ=o_~KtW0q)leK+WNybFbHt9P4#s?%$h7Sk|e zKR5MYA=FAVP>OSi;F>DSAXxb!v64}5KVjc;O^)V$lwk36IibxrTDDc8gr&6<)^(xD zq_%H6DJIHKlh>^aHByYg!<3T|5?MCBKph@jf8O^fEd2pj@`w!o93={3x#k=UO0=x{ z%MssHhQ_B*`$g5giVIAM06W+TaS0|103Tsk&1K&KO~8KB1vj0rFi_uvbr>t)@&{cd z>E(Fl171c)8TVm&w&nfVyr{g%{5;zV9ixZa1ZH6X<j;Q!Bnxxry8^St-g$o`Na;Tp zc|E94(6b)Lv?vVSA@d2mi*D4}g69vKW|)#2&W%t^)xBW-!$8E58hP4ZG-=UM{VT?1 z-D599d!NJgE-7dvJG$!8E7m7tBtSytGd69Q-|`m)aq_}kw!~)swxNCrQ^qcL1!hJP zTsPXlRhG@PJBCh3v%vi?FvW?SJ#^@oauLDsczbuLE*1vZ85xjV%BR{@=xb`!;024> z)e_RDr_mRVYX~>jcubm2o}K2A?5Guph<|FoNlZsYx4lHFo(6h81_st_TKh=_(>hpa z<s6ElWkg#us=7CPJ~f*^JWQuA!aWZO-lni)Xh!Z`OBbCgmc)ym-@J{!(v(f4i5S;C zPG22ayFDIHQ)|e`JJvaX=@?!sriIuVsl!)|XYkarE=$);&>TG5{IdgCGzLi#QWfV8 zN$LI=&#JU*+^1XqpVEKO|2y!pIhZ9L6fJJ26gA*d0n{vTRVVs|-OG75#_Ewokm@X{ zti8oVmhjWU#{Ptd^?s|n=~Zf>BoeY60l<06ffW3g$jb{edrDf4>Dx_tbtOjT+g+P_ zX(c@)y>_oSrWKW6qr?cPhZFRuOZ1oMN5ese<BvzH;8g<oJ9GrC(>_Ntq(9pU$N4>w z6FTeP{d^p!EQ@O0>s-NVA4nY#riqxC4*`1}lr8ZZMmi?2zzj?|`c}K=*0O87Y4rNX zRj{hU?Cmf;0TZ|z=Go3`DEov5vN<e-LwdV;3NtS$po^q*iWP!@LvD-pSaTvq9PpEw zWC0#zPJhH2fq)#k@9H0GCoI-4^`M)o8pk9(iwsf;!H7k?`i2=>6wW~ZDdhzDrS&2_ z@!%XT(Cxn1F4R_iF2zBx6GK`o2@F3m8>aoUn?4GV(JPcT=n>G0yW)=~88{09R!|&P ztz=ro12XdxY2AvW?Qo|WUrQB@CA%6}X=AWGH@V)^*dUZ*lxEh44c@Ppv3pv%Hg-G5 zj>dBdLyBJiDnwX11zc!U@y7n8Ij3cLue)iMIX@%njinwH9A(_bx$p~71)r_6(a4qt z<$J(<Fu8Zp(3oN3tmtC_aJyDT5vWhIu>3MbxO5v1iIJz=w{}^>*>O2NZwT+4O@lw2 z>AXl0`dUifaTT^c5S2HGcPNt=!B*NcJ{6GK%To7Jg61NOyC@1F%Ye;mhaYdL@7kZU zA3bMNTw;$!_oQ?V#Ksz{$6kD|7y+00SjfB%tyH^^!X8!<*%nyE5vW7rw~1r2UuC<d zCPX}4n~q&N$z<*)cO&5-#;1(%hZr#;M=m<Pwe-o0lXX%->Ms09{>;x3uVx{s0hVv| zL_2G$HWkOp`u!Vf;bhk)woDr1=BVxICu%hl(FLkaZq71DM%xJzQbhU}?&>PT8C4-Y zx6D-%#;HOIQkMKL&8HB&<UzUpF8(-OEyt{G;<*|fMe;P)8Uf$~lOE$I=$mvm*zvVP z2Q`q(q#7CI%Y+u?W>_yW(Q$*kw(JJCa*7Tkj4Le^H^Mp1k++Evk*zyXp?r{Id&7Y! zfFK^AVl@d5b`#7)8}~h6m$O47)$)m6hmk-E6GrQcUEa9u)m()ap#Mb+{(!T3Ahxw? z6hm+q)SvzX77;zAk(yCW=+u9J&k~8;S^Rg><wY4sOw<l<!`p#ZV$tHNGa*ZF;n52q ziTo!h&U~A>tX%yLps?us3^8S}6K;Qsy`oVi));w;+pI<$EFX#6K{BYu5|VOx$imO7 zONSj-FGyudgpQ>tn8FL3)#NcSf+0r&<!dwEED)eBy+{)Uj1RL2p(je<Hbigjl`c4* zb#cm+k;uHls;yxB>UVx^3CRu^#0?puUX#`-%55eWS9)fjcKm*8nuqu824KE@qD^dV z?gAo7isZMh7dFm`D{ii}edk_4sOh7=2<Ayb4gVTmHJJ7N07_Q+PX7@LO!HeUvJvtY zVVo_4CEiTRzKge^Q)}t6LNA>sT6{mtLyyxro0A|g18}ff7+q)MzrfxviB-c2dus7o zn^?7nZzWAfuhZM@-czvJpk~g}Qd-ZR!is-s6qNph%cyx_h?l^p62Xc!mUwY+jXXn_ zyzv0~3v#bAKHaEtlxhTC6lWS{(#PaJ2i*TXzSA4GQpNaK_8+8|5^EsEUPoinI^=dU z?!cqnxRD|Z69conplMeocgLG$h2peTo-q5m(W>?q+(OPE*BEg9<yZC+gS6cxHIG(i zx=Y{TCTpM5cw##kbKS)~d?^;}u479N_^d8c3WF!S4|d0J#c11GDnzfakFtXJCo_;C z7M^A;VlgPSlS|r2A?E-Pb>X9G{&lksft2wxtgqY<ZytR~$7cC_M7B)&G3tsw7H%Y5 z+CG)|Ao!xei6l+MC+v-^_?a6Tq~1CsEynbEMO=ZCbBgHNw=B=)DrvV`e;t{Zc^>KZ zeDQIa<XR|SN-DI~!oN^=eCn7@o%Mf(AQw>N$o3K+o(}A)KbyHB)t)x)=wWP^X-hh= z*5SgX1GT-7-QRPB3)dljn0V57&I6RpMO6_%v*Hy#-f-T4GDlGMgE1QH0OQLYFkgXY zwDDm`VUFvUw`&KOncWq<)_=ZmR`MyDIp14pb60II{vr>TU<p0_)=@5H;}zBeUH0eV z=*tNbMhicQE;}oPQSVLd0xbqW>~D6$L@kQgq*3GooK9qD+Ph~y(%D;~k!Wb@N_t_R z_arW!&Vs*zktr~ju(hdr_+Kn~K$N0u1|G_10dZ?gzz@`vecfL=*%`|?>U9Kb9xx<& zKjJ8FnT2G#sqP{AdvpObT2mAV!%b+8@esC^q^bLscB5ky@)BL-<WBUM3=!RyC^r5O z(-M&v%8?wiKbl}WU`ls3R77$_@TM~Nm|QKnGs3>n-V%EJet?TED}tCeKe2VZxkjn? zLjx;}RheS_bQ`@@+R%OQ_8{uF-CkK!Uh^VLD|SY06K%WsvmKV76eG7fAcjJ^!1T|e zL65*J&yG>vOyX%p{E1-h{|Eg9lU^3p-#WrEeQdcyz92pBQYdxED@g5S+SR6=c#!V* z2Jy!gnnYL<jy)-%zoi6hja!*^1ui{PBGU!mV*=S=m6eh}NyI66Fli!oMd$G92K#z| z{vAi(Z%dhdy-5EDR3F2tzv0w&e?FV<!>5nu)NK2DWAn@^41xz?XFwa)jKVyiiuO8m z*5L%&0IGn>t^rmpzC^7!WL}cnx-<F(q9)aSZC1WWl-oD99ykJH$}qt~8eX3*KdXag zA2fJP;l4qyR{P9*cQPzDgBsjd(wJ6O{~AWYJid>`EX^$THH2dV0Br70AGNT!(E}66 z9n6+6RU$z{`VC`oNRBy<z+vhHIZQ06YjNxL;F`E|(I!)*gC93ZL+~t$(62=-r8xKC zGA)}b*-(=(SQbN$KB~6F);oXCjW{o>z+0v#3+XnY$IjN}bMd`8NA{sG{co<rO`d`I zpFwuK+GxE=;!cS+^@k`iEE$Eo>)o;y+TzVY&DRA{PvvD$g5wNw<1opITe~|C@D=Hn zg~5t0u?tVdm<q2(3|x9h)gw6x>wn6egkxdJkrg))g+J{iU{ZU#{0Pk1iB(}CA`Oh8 zK9vVe!|T43{>oX`15r=6Cn<9mT>>C`DITEA5N}Z_o4yRE;@!-o42N&|$e-@?$fdXT zz1rCB80DOgoRtZF%HRtWLCNm>N`X!}!#!che8g6q!qLi7IW7I~RyG-^Kkr3UEQ4U4 z`du8tXKtpPp060pZ)xS``wXt4kJz6-&g3gwC1#UbZp8Xc0FiKqx4-5ez{EBDpKtFq z9^iUF7(NeC>BQA77chBc1`q+15xQGk<geTSb-f`f%nW;^r^<vDHaBsGyKXx`o;ud= zcrI41(cFNZkN|~IM{Mo{0o&WEqlU3G?CuYdMllNI-8u0sQM4f6gh8uDra8IRk1g2o zBAI&{%rgzF6EKm>?qSVNdry*zKp+xm9K{8Xc={jrSsT4BPoJO{<z_p1Hy#mUR{rxA zPe3F~{7f|D(^n{ni)Mw&NribsfhX}ypqg95m@kNAfq=``$rwcl3I7(x?Gm{^V_35w z2}xv+_WK6wzjE_xTN)TN&|=gpqMSlaa5<(Fl$aw*^F<`{=-2U{)0EN1q0F3>_i6@^ z7g9hU5f$_elIn-#JG^YDK8TGQb1X8H*QtZ3w!c$KvR!3YX?#8y{>OM%1Sr68*QV<3 z`_yFk>UB3TLxsAvT+bZ`aHmOT=dS-v@A@sxImtX9?EqS!({muE%@KTM=43L#U%K%i zFxj1hShj?cZC2DSEX|J<oJ^gSWiv4M*p_|tC9VCS^iJK?Fm!R8p_3>fCvskI2f@!l zOLtDvZ`25T{ywjn<j+Usb^C*BW&0|6F=P*gW7BDWdNe6q5wyOoj2l!~YY{BxTi`W3 z)r;dxYsf4(Nz0D2NPeWs%8<JPUcdC#jGM;z6X4AP)tnf82$;Ulo<UfBj(ENDErVVf z@zYI@`sIQQU<YLQ9{)LkEQ>{0ifx8m?9pBmy3C2fwue)5BvGNQw6eil2oB0>y^?+V zq@b)lQ=MB_|1Fl41;>X6D}-n;C5*Fxzy2N*k2_S?RDG$UCAYJE_-JTB(oth5W;$lv zqI?X;^dn7~<m4$LVT;yb!N9C1p0~DgIM8WtWO@q04Z!46F(OC8RBH*t_#Du|yRZiZ zGO?QOHYIkIe{p4xAPY)jBGA5DG6-jTyR&sXDoB*YQuP5?1Uj8hR(Ro&og|-!>>G*| zW?B)T89=8zRe&F!8>F~Tt~!BS??t=137=JH5@ny~Nrs~zkMT*Mgg8JqR_X{meaeBw z+~Q`NFRoYW6MG8m7yi=vV2*mFc^>iA$ZpEOd1+c_9YqkuP|YI7V0(FE3?;|v%A|(K zJQs*m{ijhbv*H!k(5y}6V1!Yc{RKuZXLmc=zv5KddV*R4<js^PPCfauG1{_{PX4X_ z*7>9iu-0{Rn=0Nz(1BmOqH#{2D>|n{cSgdp1^N2|V9p^&ZLR-oo9U&B7K6y_J>H*0 z0_<Yu31V0dbHX;=Lm|jX(zVN7*UzvC_JrVaWowM|JGE$@Dd2YABv>`)=Aqz^Z0)sr zk-ByIVMo`}i<}rN@6G>b)S6gGP5bG_2nf{Q|1EwV3i!C+duE0T6w7TSGh>VhqIwuG zX(pk$OjWP&To96<E;ZIAAzLiVy(XwvPLsu#$ZovneLGKVa?7+qO8T07L{Ob>7jl~- z=TiwyCcZIv`j$CxS|GaE^BQyu_z6pT36<RFsV8?}R^7+G1IF;+QCvKpgo;l)!^4T- zwuD9WTd2NCnBslp))W~Pj{&s<rMl-i1^0PANQZLk@HB4~Yw0Tr>odcmPKP(iWDC>? z9~(=VePJtfG~qAXgY{V!!;<qC(pWF#b8Em5lZjb3P*9_>7&C+l)fY~*DSOl2ck;p9 zdF)`M#n=)sL?VPu8HAO7?tSOA&TgYchM-tsX7<wHAo&sH-zfDA_=99oL9tE<_;{Kz ziW!0e7j8c>W@P(j086Lcrdm1>#j3bQ#vTSS3{+wu_l<A+3P>Hy5EnY*cb?}gn?fyY zrZ(|1K*IeLax=-ex#|e`Ub#1Y>t4Y7*|(o_bNrWb3(s-Ir9^(=w2S%0!c^07wy~OJ zpDzV+T|c(tkF`Z5?acY<5JI<v6QBJmXtZzgL@J87og2Mti1CQd%`R#Zhm|aMcm<hj z6%^Sg172+Q(DBg{Vkt(C(bgK$Crz5<rh4jAM`SOT(^l!*WaK}T+xPffF4RjnNS}AS z%3L2FFkXFan1vf~U^_J6;p7~y@!hYv!~@MU#V07rCCF6(7R^2bFmzmFfQYy{tit?4 zd%J0$<_Lkb{#{DK=Zy~~vrqatDi_zqG`IH&gd4bu(t0<|4UbCLB-XP?Zpe-l)41+C zb4WT)#+*SGNFxZ@R$Z7z?qG>Krbm`nGMLld8V)yQOU^um0fOc<MXE7Hwn1^{-waPo zQwO)xRsRB9y91y|W&5CIe-Rvb%-52+HN8Sy9h!iMv<znp>NK!YjoEM?D72VSZ~I0} zLjmt0{W0Xp{6EXQ#*}?6CMX(jAFkV!HbPXq{>p>OOsV`xctJS<HNJ3)hjgwnCF1h% zWjMl*60^J(dSab%BB-;T@jyONL()C|4jQPAb&<rp-hHb=VE6c1r_<Z@U(2;5-SYxE zR5CKQB*ajCggP=i4@2>7y|^eKKs^I;7Hq&o9Lan*g`R4uQl}TaAo$!B($XSW-KJ6c zEN&*c)1Lzt)N95($)DMsD{SSFEewofY*pdFl-!MznY?Z0rwQ&jSri-A?1_PRJ{&+> zc24Fui%kTNY*-v$YIBfna&(Xd>)5E)7n`^C8j*@ZNV}Kqjb;K^-)Kq9l*&j<m7VZF z{(>jp`Pl%yW69P9Q8Yp7(<x>dR-E@`m_U+!0d2Yc+3S-fJzp3Jop4+IW^jIvoYjlJ zki95r^_md?8uE-JXhRfoW}X^o;{1dIxZ;)zzcB>OzON**eq|9{SA+`S4M&9>&KpwV z?5s!JwL(+i=L}R+&Zswh;$7<yVM~O0%xzhVvS+hk0iX<K1N5*qpKsPk`VFalPRU@C zH2+H35*<&W3y+s_RB^1US^1tC;6C0xq$IG2x7Mwd(n3*)TK?7p^RI$pquRb&)v|vH z4hzDGS5=I~P-yFz1TGTssvF)aKsr6@Nk~;uqG#}f!|J+5UCTe&jxFGep#rnXSp1Kl z-4GKfrG%p%aTF@deu*WR#CMA^<9u@yqXgA(rVf1SelN(q>QpfAHwQ8qBn*r^HiWIR zBqHtJVeOF~*{7wN90gfK4?>c=44aLE{k035x~@P99CuT-DNmYv-*C#&_ebmw&4Kv+ zg^?D+?Lbk;H<Fl$<ctwzXKB>wxk^ch%D*6(Oui(lc;kuDxq|DF)D|e~j63o$9dcBU zn-}(d=^3Gm3Yo@WEVE`FL3@lZgE!RbV)={nv(|O!U7y7G9Zw_`aRVX02t59M3no>+ z2_@C9;yjmQwTDHf|6WR#>=VhlvI-cH`>%Hsb3BQ`(4160oGu26^(Ld@M|~0-ScP6N zLBq%TMRQLccU$85Q4Z->iqh5AfXPW7bbNQlHA=Jx*+EDMj#0j-JA$;Y@|`t=t}h>< zQ)64yBKV0<!D^>dwKXQB<rYr1Rg5K^TR1ix!stGZe5c-KJ35UKHSLTJPkc@%Yj<E6 z<DJRge)h`Wdo2R3HN7t>62lT+ziW-C3p#tjz!XC3eMl&4Aa^gQwCY?&A)a#@=4sSC zIL{txoJ>jtS~S#xCY!v3YfmyvXT@ncTcJ0hck98n|4sx;dYPFX!UEJU6{%lnqvxhI zjkO%u9f$RE($E#3z2zFxz|YXU*bH5!`L|4Ddjhe3rCg(c&6-By0U2nEoO{Gwd)jxl ze{-c~HzYtWokJ{Q1pSW0(y?6IK$#At#@E0EcOQ-iUMfv^odTGrKF&tN-Qy-9VC%;_ zfabk_?B9Q<4kOS!%^5UhX~!n0Rv(SDf6ojLCdxv_eruX%xGX+LH_g~8TXqs*8kKh| zbMElB;cw&?cf=`)l<3dbN7$S@D|h!8XLzZIn0tS2Fk(O9!g*7X8sTr#UU94t;60F_ zpz4xhA(;K{?h*y@hbvTH{~h&mLG}5LI1!TYOj%<{YCL=Lsz>1Oc4m`JXK0fRha=d) zTnjgSl#Yxn;e15tq{djd-R9vKNIq`!yV+b_t!GWrxc^_P!hl}9kGeQ>)>v}31vYN& z88Ou#4WbuJUWzb|B9w9w{ECUCo5hOt6m2Y3DfQ!oH>!Xv!LK<Sm3&!gY2oR7oU3Sn z5n$&xY=h^WasFr=o7_G08GH3M`j<iZHDv^AVK1?&`wfD-&Dg3-5<T<o;#*)1EszHH zLnP<4d4?uT#Wi^<#rH5Wx)YD*Ruqd^fU?q+B3-}e@!v|8)kFP&%_^nztlD^><irar z@#EhlKy?5?MVY8^Ll+r!T~);(<m(}DDrWy+kwoAyxWT7i(;{2`6plzM#*1l$5#E+q zciYET`guw%B~n>7b?LNlj}4r-zYp~of$d*>t|S+XVK3+v6^YZhoyJP|CPwc}ji&qE z8`<hn&a#-a7_FGl5l0;Tg||m<%khZ;ged5AQO;KfR9Lpj1&Cf_VDP9A?K@w>)6htz zWT@!XTo6tC`zkWhj!JpORmgM=JS?@!wn#71HOdaolN57gP{VD8m!jH@HsbArsw@>I zQZ?{%9Z#@kz<0k@ryX3f$ioJY6TB_fsaLVPr2liG+hGL8Q~9yZQNxc2<1mv7PiM+k zN!N#^E9vDOR}P9yG73rhFzIRMXyjw4ws<D3`rs$D+Ae2O1$?(|!M?$m$3d^EKgbph z$HNex9M=guviK7Kri@tvCpwl9LUEgM$Kk^zhw`#c-UmHU%)y*38Qrs^abX29W9`3$ z_`(J~h5ipWqY3lBQfzIJj&y?B{w-!B&8Zu}a_};#WV}ogNu6Zzf;2ahJ&gI46FbhB zlP=%t8pWi3ghSdnVt02QB7(I?Q%RKV`x7{#3oaxX`p8s7Vfry?*qQYPNbAdL8KCtA zFey;)f&j}HdT&yXny7rh(cCP(K~#{p{5sI)DPUJ<c=n@K8aoVx?gbAk%7F6Sep|=Z zjGIMB#CCd%D0>l2{1Wu{V6rcVA^W_?ZtBW2F9y)wF_$Qpi3Cb2U#y;YQLRNTCS>tu z)DLy{F2-=p=BucKb54^mX;xR4`T@c~HN%W2t)$-^7&?EmR6BKw=HYQE?BGa)tRh0X zmgXE=y2OH~ToNUniGx<{E!j4$TXjwg$j__|1PiPtiWEh&EFy2TU`3EEPD+yS5;HUd zA1KY5RwwS<0b6Y=Io~=Qk7y;Li+wFF(;;FC+=8>b9rzD+#&XX$wzvb3Fo7<Isi`Hd z%dTwDy`^0=8dx)hL|nS<Gcf>kFClN`B`SArH^6iW3vLp7HLJ?hoA_#Y$xNbkTZ_MI z&wgs={T!yHOIZ0cr;-_52qYC2b!4LrNP?=~yxzxt9u}U<1Gi?Qq)5!*0hoxRv(W`f zd6rJTq+@W>^c4!JO+h<+2(B9lH^cGgaV`eUo%E(pY(lG!jwpENQDNkkzLcR{*qg+U zVh20(7=jwgjq;CtkWOb-(W6fIfK+#LV;<Y!-;7ebfPlMpBBE#=1w!(pJT3&r-$L-F zhJyyRd`-qlH$kD@Iyl<*b;~YVfL)5!r*7*@m8Vl<WJrB*R9{Vs&98r0iE}{RRabKy zI~)xAJ?a;<U>6fQ%C-2U+f@HndeDfW${<_T-<XWIa{7y<6xe4%x$4=Zl_VbV(e>{w zpL>ZVAu*!QWd;#oBq4&#gh&nME75Iw#lkIG=ik7Ks5(Qf5AEV;C7P^@nSAY7EmM8e z@R(y{>H)qdM{kFJkFmRcSBMT|a5$A8(6Xu~m?Z)OdUZUP&FhX-1d-H_hq;{f{H^5p z64DVppUC(`4q3GiP@Es8Emol8DBJS9hmV+5<*TJdUJp=hm&Tl{$6={2+2ZWcUHO~w zBcA4>*n71w?}#UgA__NQW^vYP#6YO3{5aGKc>BwsYG+nfunTyn!yMc)jwmXcBPE0B z0~`vXAA$-Y+!3+ZqO)^Q94moEKerouyr@5yaHc)_Oxq}|Fx4>!82SoK+Xy`uH%=4B zL6Q>Y_bb=l$)p5u&L8o7Sf>g@Y^#gSwVGNhUcey0C^>`71o@pP=srFKS##`5T=Nq3 ztsEJ<Phr{Ijr`Sy3(`H~=(0CXeX<=GVsp!3u!2lK0x$?}C<bdMm|JT@?vnNqPi%l~ zJc(g49~rhtbeh!PzPN0r#F}Mo0;XM%{;TO2BdLhSwxL_7ma{wP9|#2Q@zbS3Rd1M4 z>f7q@IU{^fSHRnK*no_prazdRc7P!RGO2xA4m^Sy%&z=kYgRQVxmy$AX`%9(3{>cw z!Ni0(;JnA}WBpYZlM!`#Yq$9C6hVIRM^34XIe>L&%7=DavuX48w6l~)itGE@Z7NN1 z`)_hX0T4+Q_qd;i^sULAZKa0qL>9vUg)`%mSCK|HSeG%R>hGs^DNPQgYH~*3MSbM; znc>Sie$~7TM2}osfWEw9oLP`Pg4}ek(a2dy(0v{jz@-WH+E1f49t8h(a!-vJhVZWW zWmborq%CfhSko2g4mO?F^KQ(x{Bc1Ml8{p5w~Dy&hZge{j=qodI1&=c)YkV_e*XPV z8jO)OkhLUckZ-I4t=k`C#71YrHCIHFHtagF+Ar(udp1*BZ;^1B-IKAL8O}t^F)M~# zSJpkQeWUgF<>;Nfxs!g(?Eh8uII_)*71G!~9*dzh-u%y_(pt?*>nKh|J@+#i!Tnl> z4?s6v{L_rL+Zq2q1cn&k$~hy;Luvwp!Ggb<rgY|=^<QijpHYQ|_6<U#PV@Y$r)4?M z26&ELUO;0c(*Glr)&k=^$|F!TW73RLNdEPQ4gW^kqTutWMX6;NfCtrWRYRG^!~#yv zo6V4<=vpy%{K6oTk2C=^FrNIB5PtXUL<3)ZJnUp>M`Q&1@a!9z3B-_#>~BAc-7c%i z+>|gp?*5G#j7>2(?;1nCcav5fN`3~8zORgwD+3ue?;*_2u{t;7;?ym_>IjYI_$kw0 zF5xd3{^@~_r<6?oOYWSiOPZ-y@x&cu;qT0icJkmw7++%gz2!$OvgdXs*pn?_T>NXH zuo&SlSNFms;ww)cm_K6<XjGdIi->}Et^+=Ji~^`8W5s+Fi6!=*1Gw$QCYKIZK>}7# zS<p=Ggp=-U=fd0rVNK38u!^%?zC2}KCt?`E0b_Lc)F*T#@-6jrdc0&)=Q?t>BIOuF zUbEKVKZ%Pxy$oX(aRSqJU`Qj{AH@WU8%E4;zu!NEvY%2Fd3tv021j{v{8T;o(70Ii z3elk^r$e#w+P~}ZnR#V)9Jq`1Z^~S8LF@S=Mt)v8ifAl>c1=FDgbCdQu!0J0hPr?0 zn6O}T%`HbQcit8!2qm(bSV{7xI~#AFpot$o5#fq4{0~^_7{W$z<KsRjyq3E@U^+LI zz1zSq4KaYe>@AJ8qZ)?+)Qs~z5Th&}`d<tHP`_6nJl!_ORG9g^B(_AKAc2t<T0^I7 z)80a~Q%h{`I;K6YHNXs|p}_W{8;5?!Un7OIi7+L|vz3o80>na)6bb-OcP}wE1kYm} zOR$+<4Jd?RyFgI<f)>cU**H?e&r76b%xlNb6lfZ$xOF-jafq%^tQ#YA8?g9}OV7L^ zY9cy09HtS%tW`9X3JT&7aGB9{M`V?(a7Wg95dU*OT(bImoyv25%Xr)NPno*j_d_1w zM-g31yfLbG>Z5}nasHBv2@X;P9;SzM!<@6v|7dS=nPU(pre2^y2!m&<Vuw&z)%UbZ zH}%vE?WMFFp_WgGNE%iT6A5F~CK9?K^fdOuC0KLRBR!;nW!QzEn21oIGEIDBb$!?j zwiLK4(m?&-(p5*6q<RihqH}GeE`H5-yp`!FY^$@zLuV9xEq&K7<7EO*y5=M)#h+4T zb{F_&RIyi+%+xzuZNT$cdy7o}OEBj(I|HvZv>7J4HSJx&HIHk1?dj|V4ZvYBWgD7% z!^^d}g1(q)jvOxDK{JAZ;pWTAAoPpZnGhd!x-AU)60rQx#*+UlpnAiqts?Cx&kdnp z47>Q_DRVkUm;MVUxdT_IG)tr2B{@#wSUxosAFy_7%=sB#(Gh~hd$=-i;#0Kazv1$! zYh}z7m|p(2I^3DF3m>VX0S+wW0iN+T8BE#dr)lEBriAWhchF&im@oD%gE|q{^4$$W zl}YHy){A2p=#-&sc~C2fwM+#xyYP?g3@6OiBwsqy!sH|0bz@`QPonqRSR09e%T_Vj zpWkJWJ}v=Dx<i-mP8tq5VCGhm*b&IQkz+ffYni1!wS;FJS|@$tK>j_=|4pFK^UTkA z?KL10o)RQihX}vEGq7owYxU+ma~^enA$Hrc)*o1Yi({&4@22m+l7GKw%UspA-aJ>1 zWiUF+si1O%*ygJ(+gh8!QBLW1qaHqOh(fpb0Gugy6Jd^QM{$GJ{n0DhrZLi<BF9;7 zbo!vueFCu?`xZ@>zxS|yBl5Hh$C;qB8-=3BOl_(OYKVw`i+>HJvfEA!0+QFZ*>(L1 z;H(=jx5*J+2Hfd|vwV_d)9uJzn<Ws|&H$;1C)%GvmJy~}`e-#s+`k`Ft-<oR7uK^| zUr}M#N;*nHMo3%#e(rt;+et7&XT#y|ym)nTjkh7f-y&_>=mNG!+&7O@#)|+~iN*a| zP=`itoe)D+M%>^|qQtbz^j$egVH;K&YA)tFSb%CxS@~22IV7(>7I}ysM#~SXZ}jJ3 z=DaK>zdFa1Kp~xUu0YEvfO{}GILW~N{$r$!wdg56m`IvOZ|fkpxzBXF!R0i!`=h-3 zzAIi110TPJa;TDncIq}7;#ShEUC`8Mei3_fle!l~ra|2&yEhopmQ2|!qC@T4Rv(yg z1c?sAcP*#2L`U!M2_pa;PPYGUj<TEaip*eN75_b+ffiUPCxZjyI{L65yQ`P>2fQM~ zOrCkfDLUvk@Ih}lb)(F)PW+`x{8J=Ns%*r*T@IWi&vdqEoTTSK4|_nbu?%k3(tAk^ zc)Po??*N3+m+OPNfU&E<+bI}DXvJ9oQ(n5}Rx|Q@S*}@~xGWo4a2{IFgc2F1jts^E zSVgNGFL0#HPKBE>jM`(q@1!_<dR5i+T>a7CMNRNU%48XtK+AB@&XCK?jvM2P6guJ@ z__A1%L76R01cw%QW-?f1jB%_Ep<yqtCLauQ#l&O@86YXq<j0b3-+tfrGCA<G9f`Yg z*!@vLS2#c5^a`l&c#^-sfg3*qcfI8sO~BOejTl1Jgt6qmEOBv~CGRyJG%@ysN@+A& z4le1BTS(i^t;w<Z8x;0gIuxM`3DEdxHXp!MIM_10L{}V=dZ1hVh9t2l=e~a6EE)yv zBnDoBalH}$2qI&*a=BZo=<CDq&{N7f`B=1F6qpX56rx!BWXxWkm|)Bacxtambk5{H zw~=wlMY;c9DD!E?3QRdFy@5!)v}s&B^U>Z&(cCcd%!+^yfJ?V{7ct<`1P>rb0PwR! z9x|`|5$Fe8B0>?nLoWRo`A*PE^bNeM;OSiR7(gP<7hytpkIdTuHg=2Q@&$=dh=;OG z$3!9a#dz)%+svY|=xu{qRBm67o2X>BMQK+^!yGfg$4o93yNp2dWzQKlDsuL|<p4-A z1jZwNPo91q;t4noi$}IqSHD{*f+s0K{Cqk_6=>`yXA5e|Eh{TUwJrDO|2jzjfj;}H z&cOz@&{Gc*)c)i4^h^7C7yY}X`*!;;x1yWv=s);whw$5sKW_N`-QWGYJ^OYL@beBI z<8){k7=a(_KpCqR<vKE=x%N%pCU7}Iu+Y?>xJ8J=%*q~ic_DLa<It8wVOMTdf9Mh< z4*?Fz_Do*uYpY;R3($8-yy=!$JvgV&pjzp4^uj5vqxrzd;JH2AQ-A(Iq99zYJv)UE z+7PzG=Mt7J4kuvfTpok&GtOs}JNW36jU}l`=naM^GJ<LdhLN*@Ikf{c1Y8_l--MQr zAcOm~7rl>kiCbXXtBvb8EI_-s(qcJjk`BjUiWLtTr&LwjbT|h<U?Fmgl%a7tGeK;v z_94Xdur8F6Y%pP5nTXhKd8oI8)PCF+i<K;k9)7Oqz?+y?EuMOLsJ+PSBZ0qq$MS4I zr}lcM;U(ucgwh$6RC)8hmwf;$pk9vexRu%`*I#Qo^I5lWpDKP*$Y>6Dz?IGIhydsj zP8`E`G(z6fwLJ$CfZfDn_2!tLGc|TmmtoM;0F_tcL%P(KYirbQe-(q@zX(1EBLs3> z*_dJe{<X2s`)wV#JNwMmyjQ}p32;%2<?<@<yER4uj><j=$n?xvpAJn_(NuFmx?l^) zJ6uMa|6CFCIvb)kr#|teVr#$m--2`cn$N$CHV-XYoeb~-TA}2mkMdm@gM^+XNf%QT zG`8!RC1gr*c40A+@GvXE#ed1L)_tDZIObTGrD~jgvm*GEZmufvXEGcbEd|RRC`<-r z--bd1Tn5<}up2-v?gar5XGfbyTMq>^q3p@7jtrv@9pb(pSWT`SrS9>V5gAgVuYV*> zhEb=%wF8~XPg}H&cz~~tu_vH~+REv@9|cVCPV<$ZW&J{+JPXzy2-f==Hk&^<5^R$% z=7k=!Ixwrol$kBgb;B6LRHxrM>@edx?fWjVqEo+7PGi!LEK)~FD%uDmeFkwIz?1E3 zF1u&Z6;^~{>`V*crcb8-AMoZgFp9R()~{bg7hlp^DjaQQ^cRYu8-p7gU9;XUow}YB zRC^RBXJzbpg@Jn;Klsj0aou9gUu17caLfH$6<UVfXm#8NCt(xCC87I_y{Q#W;m(SX zWf%Vk_}&c1&ljg83bkRQfi#T2MT6s@4txdLQ;juUftChTy{hT!f_n5ie+bjr(@K?~ zWs2SlNKc${adk=B)CE#s<eV5EM)_NF3QU6n9yuD4oY6vN+PgG@2rCJ|<_h4to&X$u z6LP)j&^|~Mu-YOw78k-~bq1_-+wHTPMD?Ok6P2p5*}`jCNy!JjknIXpTq%P?U1OiB zkWxbX#UGXrka&Zq*-o2R1rFi%d7wa_`xO#o5~KL+Y|#KQCzc8ti`TB>?ZeM$^X0q+ z4g|D3f#^O)rTND?9_^&-4$MOR77UZGh!OqGROZ^^0lL;~`S_0EG*jCmYwaEv_l>hW z4>EKGPJ)U(_9AgjLFYPJvHKFGp&l=GIIRg`nVu1{*zOGbK{GhYSQ&9e3J8_RF=chh z{v09Ht-JQ`+1JMzk44CQEg1C7c3vhlvS!3IerpXvH;vzD7;pse<b<}yUeu`KR)hIH z*D7&%uH@+kO6fd%%Lt+0vhKT0jK-Qr7+$b)zE7>9-g!L0SCg{BUy4BJCg}L4p0=CT z+7P=sWJBwRT76%fH+P=5kwxjf3wwx+k5uPx<?u=tYMk)PRrYN*1gKL+839QP$2NMS z|5qavZ_1&FIPGQO4)VYy9oh{1os>TQk3hf`S!c<ACW?~7c%HeAx{_<>IG4o>SAsv5 zm=^{PF~5G=WK*<aMBTAs7A2S`X@0vVAP~A8&?4E3M5S40?5-8LbQQKVYWKn>oXtWb zHV|Z^W4(5eSz9#5@`At?MTakJEjr9LEq<imqBdp6wcnoD&@kjmP4xkfK}uMPYo~1i zJk=llHKF{u9g?Q|!b37bc1zX`R_V87yhpg4oOdNrO^>v-VB%3`Fy@(%VsgZg@vACw z2mO)pv!7W&*ypb-<`15wo0EEmZcsFtaD~Aa?t2qAk-W}Ma)_wPQ6mK-Zry;gW(z9> zHkia|X{a*YE_X+K-6;^r;e~)VZxikLG^=Q?d*Uc7m6iNT>Di5X;<1^Ebn78m58&1e z*+36HQ<yfjBGFhjSX&f%p1}jN;`PR4)lLWr9x@N@a4cVQ+V*C?op3Je6l`IvH%zYS zXK-C78K=7;{w!$2E!%dlTBZ-HXsjC|U|KmmLB-Rq4yy0YMswxLgxp8~a4Mb^RUHb; z$O8Ww@;9K*cSg$lg}$=2?+fY?BU*D2uw~B->B#4Q_X#@2eX5MO=x8G+?q4E?xan0S z<61qXID7?1<{wG!h{!*nr7UQA{M$<l3{-_!U$#?60Vykko^ErRdTR?*svb+QX*Tl^ zY?k0!DrdV$DQDeNNXpj7w_KK+OBaO+u`~&@OVmnC1S%SPv}xm$|9HYJyhTq3Ny9|^ zV}$j#8f>-fR(LRS(8Yxqprun)Z~+|uWS|<Qq7_gnseD#sU;(XpcR%Z9?t|Y{!>D$6 z{|*#(7F!wj2sWd3Wsepw!ha_J88wcm8eimIAf)Vh4@>wi*X*Pj4LvWOMw1oZ!$<1> zfyJ3iYnnR|hhYq-h$HJQ>e)aI4<19K<<~f~&q)Yif`Z=FUSUU};w|)%Ea1iGg^NVr zit+jO05XU!9cpyn2*AJn;-;%7Y))Zd<^vFymkE-Vl1?aTcC$+I`>TyVjRq^^{mOM| zJ(FI^$f)_yv9lhPR5=4<oVA9Lpk<Wgj(F;WKP8k&Py>pCibgTrU&u5<%K+Qo^^38% z4+A7_vV=Hm@IK`1EJieEXOl0@7wI*>pk5S8Ss3!C_Q&piWZ7cxu^L5QqFbkFATIiJ zi6gu7w{(Q!DuW;m-&gr9Ds@q(pz1vEwsDDkiv-oyJ#8n`J@H8i<{#WxRN?Fyy69!A zP&l{^lP;w_iTnVS%jkRjlQt_w*{D;09G%$&2FQ|rk6!V6zUqi6E)^k#RFTmbG%iA4 zVo-P3jH?Tp_zZv?Qh-|{<TCbz@yF!2*{8EaB|kNN!)2~C2~T43j8jY=rb#?-ME-C^ zwecNquoHU#TLhyoUiKpvAzZSa6sH4J_gvq-_tqhWPKv5Wo+fJ6fYPqnJwZN0El(;w zd2r>jIADQDf(=ik!{*Q8zb%Bkyat2hu6Nw7X3KTGxrkhXQI%aqn_QA^<Lf&fL5f?@ z3vTgnx=>-Fn0JrUuS)YnaR}Qx>HS_pB$UYHQ#4Nq8TDu=0q6Ft#kG2LGsq_<8Uo6o zJ{ih7`}1mN&=$f6*y+BF!cP0<9Es@kD)T>G$JDEX+1T+aZlIHIpU=<hEUnNT5UR!v zWAi7-LO@g60p@j`5ptx@z2i?vF|AkZ06itJEZqv@L{EpnqtZ^)9Jf<}_cnkoir#P8 zN{w5Mr2b3%)s3P9`W4qlmj!6X4Ork9+-ltnlxGmV|9e@+plx;_=a6B~AFr>IIL9^) zr9-{&OpB{XR_O{%;Fj1Kj1-nAE*mR@d8A$0s17#Gg_f0=DNXt;`fa5}I=Akw6@`$L zy2O$ZQELwh>;mmXO7fr*BL*9;MrQ#2+JmmR?q+WCn80$C5Co)wm4b_7yE2J6sjoax zp%;aaZOZS5iG+3almH2$yyp(qvluIE{EUkBx{o`yCuXs+RF84HXIgW18Y9D^rtiYQ zqK{-ZvR!`(#hVNjC%5?g>)9|Foyj-?@msm!fo+>NlBcD{qX|6m{;uHelLXt2S|$0` z*94jdS48cyfZ8{F33jH6E!f(dB&Ro!jU1Y-5gVI;&PwGrC#BC1tyw(d#%Q%21DQ6* z+1rG4K9*AGWGUKLelK5b3n|M(O0#4}>g-J~Z|0{u7{d_^&1%CnD2c>bDbFsrJ?W8r z+sZ<V%Jh`VB!Z#hSgA2}xJO=ZBwX}5<Wud}AW;hF_&&zBwXLE!dR3{bsf)7CQ!QU; ztbxFq^Ev!MJXSHb%qO_%HSX0fy((`nv<+kFjCtN$icv8e6|?*izRR{JfQKc%u&bfV zYo6alj(#;rh?Caft@i_CS8G}fl6`@0v4wn(-fYF+iEBv{j`atPTPuZoG}8TuU~!6C zSSrac^F~IyQ&y?XTDFD|(<L2*pzK2rU=c)J&=8$x`ZXj~TzzAZs6Wi_+O}<+wQbwB zy?fTSZQDF++qP}ny!(IWz4ya?CYdDjG@a>(w$rr9?;B7T49aWeWMdLllD81aua z2<?c|<SKRRc0I&FQdUzKwuREOTf8LB(k{bWFXC!tuM+mVbKFVg_aR+d)Vmlw({fEq z4Ev9AB|jfpE*)Y)x6_{YoGy->X*jVP0tp!hT~c&dB_5F(@lL8O5dxAaYdpzsK{WBl zx^wr?)$q2axYa+b%|!r&hA!&ojJ}50dn_)9dT5Z{myBsgVfDcx3gI?(3ce%+s0;rl z%GuUfzz_7vw$ljS16Vs!4QT~#gP_LHVP#|Ldy$iQkcSKhURE(rWAdwxv_EQq^*5}x zqJJN>`s|lyDO@4`(|7?s+O)8fDetnj-ES6AOSl?>)u@j)dX#)$_XmO1&9L(vti78P zb?HK%FTkW+u6M*C%Cx{{dPr#zKsP19?yUWIX%N0(y9ib>JK718bw#jwco^~xkfqd` z0Qs({!e2EdFps9yRVRWn5uuxTcY<#G=qN32hO$b+bq^Mn46PB=LLsRz5B_@~-@8z> zu{QolaTfsXjr3(<9bOLulvLJ3Z7S=JD_usiM^#AOk1WWRwOe~LW1vkOc&<NUU3%}= ze&t5r$y5ktIy_hrvFX(FkuA?;9JDf_)N@bXpxVI6^|vlteMACUY~KkgJ3tt>xaL%1 zOP#a$eWTFQsvK<X;lk5tzS9I?WJj~U;_zzu_V{U1f}T`EwfTp`Er23Yt_0vj^ot7^ z&?3g>9kaP+kB`UC;Xb9%;t;Z#%J|shufB{B%136}Y`064Rd|O_UH{~5MWEom4?_$j zQL!L_b(nZiu7y7oRbSTl$P%pe+Q*qx6Qyb#8ugMeiN9S8YoZ;Wd+8`KWY?*;F<Tz4 zBfvgtUqwjm+0715ydIbf@<eGvf<H<X%)&!72G84g$IVNK{Kf}L<`nNX$iz+Uc3i<| zES);fUPMh@mAML4WT`xmg*lcQRig&b<_f)IQ;Z&f#kU}TWN6KH?gdF|usyJ~KdP*Q zCdA5cOeic@vj<%V>)_CqHG8@14qPey!#^ee4fNm(&h-TfaKw$P76-k*73nfCrc$X# zN^d%<Tl6W?+D&E(CFl>}-kBqg50T4%ks5uB-A(;i@S_$3lk6%iaE(mCEuxGXR}B?< zAxp|r$U*iedsRY`ti!IcA9|xH_W7<B`8aGsVfqi~#^srzvBfFIDb&@NV>c+ASh)(1 z<EV*y0#4I>j&#_B8R|=tK~K&WvP#q|MNDAc>7=u?1I{0=FT2+IO9G>^EHi5m#G`F< zW<A6@(*>G&`y;(3F#~fu+W7uOK)XC-!<uV58OHyf(XpKS5KQmM{jurcNrP9^rwe=$ zL=FLhjVtyRl8LKcWJ<aeU~MTx$_F)aZIb9ZcZ?$VH00E94InCCF|R@T2HW1V4OiyY z@S-7n<+c&VoXw+`bL~8cmJr!};HlaN1aP_;doe&AGM&{MbOWD@tM&Myc=k4AYQuKP z9|@(vOv?shB+2d9Q!9iq7Ovqf8JW)Va*W&?(A%H`x~uOH-rMSlsz)3XBb)zmb9#}K zLAq+)0O3`#`RR8L8IMvYp%(<Z9E0Mhy`OoC9}HY9NzOB-=MHU_%RKoHR++3<)bK{M zo`aQ#@TIKvPjHhL69(9A*fB-Y@==6y&RqGu(%!*W`5~8vDHtr{T*tdy6do0h93d90 zdr|gr)x62-g6^?hK8`|nS?J&Qxme;f9dX|5<zpC?WPZ%N`d$s%D$Jk8*cZ5H9B^BX zk^?n;?Z`wfyl3PMED>TZd5RczlS)LHe%=IPGd`EUWqGEd!QwkQJE*7jv+W+<n+A+r zy*EJ$xrLHcO`3)uo&1AQO>~K7zJ~yU+Cs_Pb0Uc*3U`^=dgA`gm90d@gW`5w+1TH4 zflsNkK7;b2G{DZ<r=zLhI?ik<>PhRE%Eqx#C)9*pc%m|yQb^p>MvZ|jt}{S3vE)~e z;Kck(OeoVgAl<bw!_)q!wDGVZ1;@E=d@|$@pv}#{ez<$?*4EQ8fVLMv!E8F(+dlX2 zbZVW9t?!*`#k=u<R^+p06qa5}iOx11hWWvn)Cu~8{iDhl#f@tR&9wmAH%6kVyk|so zvCqr(C^_Y+tEZ2(+<knQAQ@DO=UxNX5z3y^!C8l_Qohu!>4(H0oDNvb8OU5z8Z?en z^Lea??3PE|@-PG9=`A-2dI`r%;yuFQO!t}Lqb8yu*^>hXosfdEvGIxJ4=o(3u}AL# zF+q>j*HwFxsWR91*T3QP8ZQWc8fek`@H<TQ>MYN_W>ya5iV%{aFxd{ZVL{Q9R>k(* z9Mn>bHH*+u+|oU&nMd4D`^2)_IXkz15fl<qlbTlHMd0dY#YoC2rU~Af?l{lQjB3JW zM5>K-ocHqATZa!)FN8)?lM5Vb=s-;#SOkRj`X+?gPMSt2tosk{sSV8)KFBce3N|9r zbB_71U6xaIZIpn|I^d*La$UJvezc47f*LsvFf|PY_<aq-$Cg4hV?Rc7N}}B&c#QKj zti+SQR=XUx+6!*5Ym{WPmABMPj^Om6b44JcAdxI*Q%A{@N%(*$FdL=%$v~j5W~1P< zm*8KjXfhUBUc)G-lpr4S&Lz+UNT_|(Nu;437I;%&W$DA-u`RlKJFg}!K;rzK;K700 zjgoj`E)YR5x99N;r60;it5&5a)}%2iE*D_jF2zc^00_?bNKQgwdGe&eoFlajm$v4q z9asj;7_8r&kaOKtNTRF7E`!?QcP%8bFkca4=r?7H9A4#dM*y&puaw~-6I_RvE=fY5 z8D}0CR!To8XFHK;QX(WDKO!dL_0PYQPeyx-)1$UBITYuF4CK5^h_ak4{1i9LBU;F4 z`O49l>iu0lufaY7+9zz1kwCDa6}|m|Jw5I_Z;OS>ksnw-hM#j4bMg!9>zOBq=!{X6 z$Jj#E2V&pxIMgQuBNBhO(lH2Q68R;Iz0v?DJAEX!JDy)oP^C_P6mr!LUIyKot71|i z(frwy>vnA0Rqp~)tii48ZLl;vA@TT$1SZa^00;l2Df%a%7WA~)1L`d|5<V>H!*=`y z=){G86XrP@dsw4Z>5HWf1WcSJ2Jo+>sZOiuULA}Y@ydl|r+0*3^-51sfY-8;;4zpU zIN6aFdI%Ek<J^JK-V^Wd7C`iOlt_<mbl1qcI7YAZJ2-0K_b=#2a=;K4_1df;gn?4s zkTVjivOnEQeFq*Fba7ciE8<yuR-74miJ55Sd(!o!qvwzdtP9RebPqrpnWBk5l`bkj z%@H>M2vykTLLxk*;<-y%LK5)J58j1eIlb*cEV+k|iRolOl@x>>P3!HrlEGocHF%Vu zpS@P`7aIpiDotvZU(=@6V|9H};dWdpAZ=q2(a)I2$`ZDspdZ<-BirM}%W`#<uD|Ck zeNjB&jDML%APInd%G9VNI|drcS(d(H1&@ROrvqE^4!t>;Iubi|poo0jtyRDMAV$7- zcO3&Foni1n_p#TA2McElEh#CVrsUOWgO#sx4EheNWd5^eVMNaX0QlSq0001EDNjHE z3l$3WtD~6yb+@;)XC(O50sdFY{Yvv+6V1Zh*bx8#=vVz^5O8q7{~Ey?*;zaOj|PD7 zYZ4h5o0tNC{Pza{5CHg>|C9fD{O1S!%l~z2^dtA90KoX4^}pTyr~c3P|JfkmfPnvx zeiQ*<ej5P-f`Whm0RCtLAQQ-|tLy(?QkR!k|1kzY1OSvWure@qrgdTY!}y2cCl27h zp#eV4{Q4x$^q(TiOIGrV#SVy*E=sE?sJ!K9msUMUTLzy+I6=lXHx$zz6i8w$jvn$} zx3^)Is+_UIeM6A(<j;U3{o+OZD^WJ18I!|IJ>xmKpSy#!A^NL-y5>Y65m|0+J@A8r z;Ax@zTM-1)2)Fk^^^4_pCd>e*aYKVqF!vst)<7occ@q^#O%~G)i&PJbv1B$LsKG|H z5&3miODOvScOBmQhasx^W7#qQ{_>tlKJ#qbEIv1~>&#x=)Q;%2VE|SCw9b55U94_R z*Ed6?I*?`~vc(<(M!3_{-}tuc>+A$l#ERq{%>3Y<c%Gs6jmppOJOrr%d#^<^ROHFl z!Gm7Gh{ti}JfQLNp9rplH#&)$lOuuPh0$SoKT5;l(T0uek0`_bY7|lV*g?fVR~)hH zSsr^9^Wj&uy(&IGyKZM4CV9G@Y;||XNXB)+Lojsp6|l3Y1UlS7xz89o+y+jXE<&vD zsM3hQ<PX(-HG$|;VceDK?AJf{-|g1VRa-&pmN+D9Gh-K45KZ9<K7?A>jY0B}M9$MG ze@2qRi&LiY_BZZ9JeNjG&ZeNfAh-Mo8Zj<(vv$EQq`ev(^=2&K(V8bUVC5fNk%zW& z84}(_L$=>26PZXe(#+%8rP6Vk)`v}j#(eW}^VdE0moXC%hjC^G`27&&Q6jM~;EDOW zQ!^0sA$CjWm;t@ZA{swjg`MMbT@ViZMe(3?4MeXl1N?}LE?<y<w#6n*NSEpuBFY9) zhR~C;7U8i}GJ>301j*TdkQY*|ir=oS`6p2&(~7sV`Qt;ts$lAAcEQv^km6iv>nr#6 zKMp;oY0uxL{QD!27JWpM?e(MuFM#I71sy0{lv@$#q8Dk9AVs2Gxlp8-^81@Qz4uP! zsX^*QPU?%*mz9FIj9)d{8PMOHeyqz%nX^{l0^mX34$avAoQS3%g7+U6sA4Ivn|4~w z!z(aMDf$)8DGT?sBwcY#WFUzAs7osV=vCo#1tM=wYdw^^AVaz(kL<~e>?sV~mI8PR z!oAJ=#-FM0>q1I%ajrHC2)0%4rDx8NA%dgdYTkDtN!^Zl(N|uu(j?LTEB7BQXkJxl zT11pXH-Y)eqDY_60vcK~r#LR$Wa=zDxVY)1cm?;HZvt<EkFK2A4O4_zBuFxishCqV z2)1Kzq@#oCYLGVmge?y3k6cmthoj){nsbH`aI-ZaAYBoPIf{XKgYfC-x91-{3LB7M z-F;7RQKwAy@!(OS_|^IMqp*ABJUU~#fK4UzcEXqD!^ldXLE|i?M_uD_Xbb#Fd&>4q z8I>#v@@jH4CL2xr$1Q9C%&NDa>7fMd*+3a2mvXfZtnz@r&$KO$b1Z!#z`y9qL~|i> zYwWbBV+QU0yc*x>l#H8`+=5ihzX0hV(wEWbW>fhO8Re3fq1}iyK?L1(2k=-BF|_=4 zHVO`UZ5;b7Gj*c4n-mPshb%9cSYY*q3_uA&**Pn|K6CucDcm?8ti}UBS&%Blyb=s^ z)g5QMZ++pv1o<|S>U|qR2dHn}=Q`M2p}9m6=)3PJNaEhG7_xHzrcIyHmj$zYDGN2x zeIwH;(_u=Cz*97P4%1{7@KL=?UmHDZJ@WlEC<oDKHY)eO-)3Gs`*;vbwmah7+L~sU z6J8(0VZFS#aDca`v+xNic8NHs49=P~O%$A#%(g8h=$3k&tjRk8(Tv#vo0z;5v%%x8 zcQ|z$uH8i>kZfej9~&xTE#5NQrCY6q+5!u@TK|lgGFO21+?O^*`821=2v1vY(#&M> zXJT!jlcDU-jK&N{o_3?duI`W^$WgOyirz~xpTS9*bQ1<T*r|hxJ8SsV{f~>)fl}8! z+lXiN66xkO<m-i&AmfQD_|HGBcIGG*#I9pHv0P@`bhylL0H<L2)}k7kX$OV0Xrs`s zV|Qn5<Y^81Nq;~CaboRs<KLdeM)o8}_M``Hi})QVfTJtKa9(1GJe!kTi20m?h(c#m z!dVPNPnk@P?eT^`Zw`|!vRDN<|8#QfTiT7DCBg;PNs_K`6Xb08?^ewC*ILt&KA(f^ zV&A9J)3=!o{Sardb#5D5)NQe_IhFLb8fFA69MJHRG)Cba&81sPf+`ay$U;bpZ^hMH zR&J;9)6~eA4p=j~O_P!tjeAIgw^9Z%9qWUNO<H3-f|(dYiLB$z^h9sUr8f@HK&6fP zXtBJR*6rFNZO#HQri)#x8oHLb3*H*_06>95wu`u=EO&^KfL&q=vN^qo1)@oPP7H~C za~{p+^^wsy=YHQaR%28D27r&8?t55u76$Fso{?Ywu!DVT$tnJ+etGbNU7abivlsnC zN_E!J7*SY!Qa%SKBvwDGmr8c!;&zM`nDq`aHjeh71XAXX%V9vepvkv0X+L%2I=qt| zFd|YAfP7&h0!*dU*p|JH{r_Z!JRlYaWsbp&q5{G}=h>o9z@jXn}tFZM+cZx5N4 zH#Nz|h$r&3&Y>6IY+J~9Qj|GWxS9Rw%4{nSeBV=p0$(uI%I8Nn<gRqbx#Em7zM|T0 z!MdacJQZ<U1UIs${YwM4H2~HL?O=6$vtxgCq*OwMCjUaxKc+&+tAV{bMnLGY&c+2~ zu<n$9eEj`emRXJ-=8B2RejTS3H~)L$BYeTaoXI^t&2lSTGW(+40-TEIq%#g{TCR*0 z)>KwM_Q;+~Sg17_!X|RQ5%-5+Wn?KH(7+{Sdk4J(QMCZC5b=eshZlCaKO#BA-C<1P z>RN2Klzu&&pu@>x65;4d#?y!B>ze99==qwV)D-*?w5bTaj}S{P{4njz7oiViZo<=o zn@5PJfM|`G7;#zgrOoDf7tJ9LmWY}lw2WAqD`8x^Dp`nIB`BXnR5lyPnt1gPEHpm$ zBzsK31CTjBs)x{4rN!Y{E_(7TNPFi%3G0y-Tj}Kj-^cAC#YZ#`t8yl{ZNnFln<0Tu z9MBZkuci$p5CRva6qyEI+W6Mna&&4=!eVduO#yz!>(FM$b;cI;0rlS+-Dbd$;<T6Y zWLZ9YW24gGz%OaRGahrnV%vCmkG$R3C5dwhzxM$F8bR7;t=9HV9{<`|u)`_@$BFUM zobNV1RyqH$EuWUk?;+V--A0p7=cYcjhTd8Wd^%G3xU6fTH^t*P@?7US09grA#QM?T zEulm}$>ug8%x{O4MEy6NHH?vcSJa5*_KQqbu1$&8h5K-!<tdD7#)WcZUVgS~<<A=) z_hnrw`d*=Ek2;bma#3g6;q`;tmr245a+J#69!#ME7)Mm(K>@){YVmGoTofn>Q{ObL zHgL6R{>fH@A94>cOq*>ujVq#gIHL3KjGwk*wMT${1|d+Vxu}0?@E8mqOx~XV%#`^x z3VOv`J(oH>mHqWk0+HOBeaKLvZj}%0j01k^g6L*`lv><Z1__h`?!bs^ZwwRK36?D* zf!rcmcRt*;(rm`TWh-zdpl?``!?v~hO4q&=0AKdZE%8m4J<C67xA?1bi4_kg0sn<^ zHX44BqzjV+qTXNNZZBou_H7z~{D)M)$Cst@zCsn0n0}{k*5uBUn{lH$bob};9&jHC zbKE#L7FYMwRE)Uz$zy|}Fv2)OukQsjmj&6K;@=_wlOV0}xai+mk`z7J6={PqDI$)c z;ptV8#9lO>9b2ml;N8M25gHKOa3N0lFYm-6z1J2OdsKL$1g%$;!)RdgOT^k$(_GUH z%3hQ4YjPL+c>}ldl>+7+e`4of^_LF&`3@fC`{XXwIh>YmXo{HI>S=o5ZWE6&nYp{s z%{W^g8Qz%+6<*KOW$}}Ak1h&fKIZ;FnBYb|pbG_xdTO~^2CzOqZ??ggVvoZ_zrd%> zh;Abh%~npX)0|+T)}XT$tn_aZW^!3rA?dx9AO=-Tdms)B5fx6om~N%~waG2@|0Lds zrn+7SVdz_g%LoMBzMGZA$Ulf>Hcu21YJ;FmC}RKS$mr5lZ6v89aw}8FHt9Wg%G`T@ z=YPysmn7sZ;HYV{i*$G2H*2Ul4J)r(Xd^)fF?K-p$%H`T)sT0BQ7kT7C3U&8+k)`) zn*L(GM-=e(aDP*82k46Y`-m<{d<@rnQguQ*Lwc7sJuZP0R~8O!N==37Th5)`Rj3lc z;6-d}y4*?IN@C!S)7~6C?Ug_to;DnnQKQg~w0t0)mRpHIzE<N<RA)=sw2^gkGhd^9 zrO&iI3y4oUnVmt_{`3mTuB_tzwe|dD<@W-dw4I=4T9N#4B5dx3{B=Kn{q8=`e*c5F zev7cCQ-GL~2VrWqbdR*HF^>WHPwF!Wav)xNQ)IX(0%*8Qj`Irvlh(|NRZfG>)0|-j zYgUtP7DP+Map+M^S5*eM#w33=IV~1p)Kfq-L-y~(G7@+5q$c4VJSWLg!jB*3$EI35 zjF<YVqPnkzNPfRJVzpEhjq%ra6<{xJt%D;>Cc*=cxD1_wONi5x;xuzB`=7>;)=?BY z_Vil8SoB9c9T94PCDN&!LtGj$fm@Pl>Xz6aJ;Y39>42N0hqr)yz+|@ll@JZyu9I#I z7l3$z=Vzr&@IMM-?k(}uZTd99c*isNY<-%|K5z%=a(MlcyNPAg(C~E2p*>WXv?1(8 z5lAlU1z+tdXuYQ$zxdP-a@hYQf=L9PR2F1=UThkBmUUm{?kb^7|2}fhBl#e`j+JyK zBK?~-T=m9y(;r`<esHFqXyCOBG2(j7n}o4C$kbST6$vzZ_L#lh^(<KswXNk768bCN zm7@eA!Z=#1^AlRT_Uy=JGvU4K6X?k^VkayL$Qd2sQP5=yUsR8QVr7NYkJT5d0i2(u zgPE0-Vv`e!e33L`S|UZAgcsR<=fY}G{j7ZdoJc2rJ^L{miv=^S*Sh$J=lll+>mbu- zb^F*DcUrsI%(o;hZwRq)iA7JF{B9Xo!EG_oeY5hQSfSd$$OEKI_L1LnUJ)-?WW2}~ zlAQ!n3klqQ3m0?VaQSnOLj-PnIEhJM!ekxH9md((qtjG5kr%^|8wvL`PUmbc=vYHF z+K9VP0s*lWmdgk*WN>G1&8f_K8w0k@2MMw#^`-QWa2l~Pbe3tQO_rI+1O|6#0=^Lq z&<Vh*G7VSOB42W6U&=c`<`4E@rusJ-H+7=V78-h%2m7j0Y}{)H3YpvNNsnY}43o5- zp02OSsQS&*GC}Ho8HyIowKvHM0+TAiAVF;H+h+Z2+{`SM048Mb+tVfZSUmx+cYkO3 z9qbN6TnEMZvw1a$mo+Igc=k^6=rsM0+8ARZ?{8)V?s#@`((~P_g|p~^&qbQSMvdrX z@qD>@#MvsN7u*bdS7jVUSul+|uJ+d1QH6JXPUxjUm5IpTgWW5e^8I{8UMStOOjsi! z!kEL+CCCToa3>ta@aEIS2Hk?Uqs@5cOD7ch;L(>%CuPS3110+`c)X_c^=5Q}*cw7Z zAUTh}@RU(!DUyOTH0Pqkb68ER#4Sb9=S4DNlIs<#drSO5Z)V&w%0nMU{4D%auo}Um z%G4<!A(D^GA!0nip`6=OJ%@KrT@FEx5479j{7#PMFmPL&ZJ%o&P<AMw{Nml226;OK z=xoP~$Ke2{z9wGlozw#IjZCROK++-}^~zkEVhNa^jGX`~s`<03A?y<u@lL-FI9dmh zb!)9z>d*`bPdl73(CG-N-)jZNEIbc(-)5?Ts9O<hK2LkTI@8YU?aG8*rc*k&Nr>J; z!UUv?V@owLujs5tFqh!8>;7GF^dt{-yx+jY+rUIkb0IWM`*y+*%g!y|K~>W%&7SOI zya`{iTksTEYPxMC)5b>G7pr%OZ^xSBPVHk(3`LQ^ACaJn@ML1-E!LXK>R+kNVD-hs zJO;CgANcx)R+Lj2{>B2dI|S{b#{xv?8}Rjw)09#kky}7dQ9JC_RIPlk)D7(2mP#I? zJj&NfhkcD=eKG;lS(L5^=NfTO;V$>WJ~^gxQVCS*-L3ayxSZx6c*}%!dVAM-1niJS zY<F=h)BRy%GFJb!1tw-qxK#&zSw)S)*WhAJb+^)%@CK%xAMl334qUCidUw#m518>c zps0Smc8GySmTZi91QBDoD1zYEklR~ywfUVx%_!U%WozmD>ob|>1UK!23?uE}4Cuw; zy;^>pm<j#^->Nl|RHRj^uL7s#qNofF2Xh5iV${>@cM~(i%lRDe17Mshn}E?UuM1Mu zy942kF?Rt*1-4z}p5cV?P17qXdSiCWhd%pzl*v{*YQ|CRIW0b-v%VBtja;QBn3;;y zF*vW#9A{=W6*zd@s>#TEyHyM?b5nLL5ZwD%QgA7F9P7d{!k>~We6%!ZJ%k3?QeT@g zYQqvhkm_1-**&7}qD`uC(#TzS_dDfp_54y<U!Z+s_!nm@`3D6LI1n0@UG!D9<5YPd z8NTD}EhQG6_#3HK9DR?h5Pbfr`)(^gxzkiZDOKb)s*8<kkXPW)D}`g462h^HRo0j) zKQ?(oVB1b5@5kD~&YMQk9kF_WNWqcREh0p%KE<&kjDB6plXsdE!=6~Flo4|=NEDju zp;Om+)6;H;d6Sm?aKzC{-LZ5=y`wCTwQx2{lYUueqVilFnXyUYF+}>MKS3Nd$eYxu zlgTHFzM%M?9qM_6ZZkA#Qc<m*+U_3&&MGQ*q$&r^>#*{WEgdIkfju+{!l*WPC_06V z2(C}7G_G+osC6`8XEc@&GLDdA6baVY`W`r<4!HlluGjD)3q6o7^%n;W2(4IXGjAx1 zFhf`!BeoPrNN$DQ^NPB=MAr9y6Z5I)V#Js#RvWlihii*LC=h1fr3AYurLtC&P)gKC zsJC^Gfu&cqPFcbv44@wOD9LW7?LYEi$8cEI;gsr^MBnT52V?9y#9T4+0sQ+SDv(`C z(wjf04r^dJSD8~K1IF04TouJt%&5=sDnhu#)(NNmp?djDyx8CHiOY^a!P|;Fb2+2X zb+VFsRUo%3a3bcDTi`bvM^amvrEjA!cq_am`r^I_TF2auo&GB`P$Im&^!wi153I(R zU@US)hL3g&H2Rw?06(@6=|Ak$lSg35vbHlUSj;=aM&sS=Y<v00{0ES9vZlb!v|D)= z*p5@Riv>KI?cyV|5RimZGW>w=XO}He+AF0|$PSG=cEUsmZyf63%7itws$t)D3sC}S zn2+Aybya+tzV%vHbFe~?P~m(%Jm;^n%@&Ojqq~VTn*Dh_%#O6yYVQnevNC7;;l$5r z=+}2HrC8#JVRq{C_*3xNgv4idAbEm$vvcOH?=^1!;Dh{Xs%X07mQ=8wapG8BuY@$( z`Abq`XLM>Y?T0UcgR@Np7mhk=hV-u$w)}vV(0P~Vuk@Q%cb}6u$jWqaI6+CLT4I2i zKZ-)+F{&@`b&dwhwz#MxjuVAj1CYhKRsdymV=wIN-PQ_gLNXUhr(2e^-3)8(soQ!E zDRmyz(!j099g)Is?n&#}c@7gVflTHk(m~X&W^Jtq4-_Yb+FOUxj;$RS4WDBr3Bgz| zCtqKUR}hIZewfn6Ey*Vr8Trb6-Gl0GEMb&AFX^Hg7g^S9tm&@UZ9L2A(5gjfYk}5Q zph?fMj?Da3O*NMG`92jui%1J}%cB#}i{tc_EHOh$VdzfuVq>=~xme{|Xf=Rr4ameP zZLd!JqBeLMMV6kudRwyp2y#Wn#1)L}bt=5t7WsH?HXs*SbNio%0K+I{rGTMFvml?! z3vY;@W;=s0Pa?Z(#LM1W>g}*U3!EtKiQhB-=sR9*W8^%Owd0<tPZ9u?YMN!O^RUG< zKYmjtn-_r6k%<r5scPw|8HmOWLd<WXBtOLIwtPhci6c^3NVh+e;&1hRKv1~S@h*gW zvh8c@-J{GYA}rB-_&QD-L2C$CmQ@h_=*Dr9>F3UQPKOLKm;MePKVKwHBDI8@j-)&+ z0b8m2%uilLz&s7H=bJw7@^(PJM&qu+s4=Cj0TE6+ahnu6;3g0~n7ZC5s)s5R!#u$^ z1v6uu{u@=TPThH^JvU)wgHo*eM|svpW^IGce^!%ev~)2|o0?us4t2E1Cm|@dX34Kh znnS0k!(ktQoDYJf^_m~mv;i15-Wo2azJn0ZO&eo_rcOXKxz4GTVQ^eQqsGhhRPL{< z2c0paRE<$PspKh009hVo*3M8*bj|a@jP6Y6wb{LDgseiqR)YY6EE>~Xcz_4@V%N5b zgE!eB9|=1#&6wI$W0ABE(6=KiH;wbcGW8nlod6)~;-?m#`G}#bM<<=!;x#l<#FfGH zmktc8B~(vE=eq6d;Cv(Mlj}=QT!4V;%jT3C+}l2LS33&gR{6*mcWE-@Ka$_&@gK=8 z0B-a&?H<-R5^n3I53SR*W9wU-zoR@~bRu+Vcwg?C+oJ0jh_-m@)Bp!3s-_H<6v;_~ zsZ!>oKgfDaOxnm%cQ1y{%5hB$EZ|lnX5p|`h=ts1X9>5dwe`rBK@I*I@zO}j`?Ve$ z6!brGUq2$n_Yik%ARvu^XF#WNrq`B)Yh7bRM5~N?8IRJ?-f@XF1Hl`G$$W&ePEHxo zR@5GWXqCW?)&w#Hq|Q7ibiE0gI*#y7!HdD?9R2%5F7e2{L%D30#UH~4DB%!rEc=vG ze>Y4AL9VPBc~O1rwY~j{^%nLV@<?b20<C~<@xKIhJQCM7#t=GA(UO5DWlYp-j8{IN zywB}q`A!!Iv#^tJhU-m~Pk)@|Kl^FvX5Xv1wuv0n3)co)zN2DxqK{R9yRSaR@G<9I zg(b#&3jpRfC(U`LP>L_<X*(lyP^)zB!z!}}hRc|EL(GHkVYH(IE71<xDfgfoZf?>Q zqaPn~p?Fi$G;p}uSNw_T0d+X|pi1-tWL4{9AiOxBHvV;;{N-#zP(kWfiO1YaRsKOk z*$4?5?(l;^A?gn)+;SJUy$<*>SIbtPQVGdxl9UgR+!6tsa;w%0(oU#$!*MN<W*t~` z+r51&RL(f$5}dja9XQ4;+crM<+caIC4y_U7-7DSeac=oz7K(YVY)Zx8<aAge{ea9y zXj#h{J&KCCZ|BH?B;!0H)EB}|2*}6LxwlDYD&Ab3!d~!;Q%l7tGBP@&C=r963_+Hm z)(!D7Ogk|_oTko+O~u(G2u(C@ua4BoMFzVGsABwTph_jzrRM3!6tXXUILg|0_(lyB zox&@-!y@~Nx~Wx~mQWxNa=feY{k7Eo!@6(ScHh;N0JXx4(%p9x%cKYYd+A?K`)2+q zZVoz*5U*|ZJRjYKN@4UpCtRL9<CXW$nHw!akl7Y^@rWuTJ?|cV<5Velj?CgjmFXX+ zh;fkee6j6(u1Uwi4!O-t3@Z+Od03+Ntdq#<-}d6ST$zrC{BFh(`TdWG5X=sDN9zKl z(%#$CSFd`HRR~`mt!na#f5*yBRPxieD^e{fLR^`Q+K}o5yhZM;I4o7{g~6Bo-bCGb zWNXv_RzDA@&VAT*k@vkg5yU(cXeSeotl>J<XrNrAMK+;KOP*6$x&2th@ao_vK-E$; z_Bk^Guc_>r==>IeViN#q-aUX~AdoR(R|9VMh2L40`E^m?PqDw1AQN9A$?RWf7-$Wh zDo17X9>V3W$jKj4Y)|G2j2=KThk>gn_}GDEBK*)w{wo-iSkSEtBLD%_r<&30d(Ij6 z6(ZN=l4!4H+Q41Y{IIYeAKgDg2)->%*)4pqTV4D+Nv4b#_Ukp(+!##Jm|wMDcm547 zGe?<(HleG1Op^Tr_Gu(cEqKEAaG?Xh>}pI0@L6+Nv8nDV&3;=y_}c_Cc(^0XbR%4W z2oc1|syUR_hERn$tAnX|T|p!aFxGgO1V2P{cD%!FEMtWYgxtP4Lo#ZL7PB1@uNUAw zOgE^(%<VBrz@m9-oq#(;n-1%;*FsEIMS?OT|L}r*uC*e!d8aoKwbB;Gz6RjSn5&^b zjco-2Sb`de(Cv_LJP=q6%AnR()+)Mtin&!tc-nCs{9YYs;@JBmo@q&+!Dt^WT(FMy zeb;I-JFX)o3GLg1Hq6^mK_`m-x^#{%d0Q{J={30a&J7w1anrj%(oBt3E-PU5o3q#i ze%?c5uaC7OEC6#*{o_(iXD8#68P-wlPJj&?33thux84k7`=;7xBx-8J%@#fk8!u## zIZoe&EXoKVq12atCM|F3D?YF!BOy0<7$S~%H8)xvmAZmaltr0gGN<DtR}(pwk7@F9 zzuWSO#aN~)nTO2oUBNA{yLaGhEsY9wl#R$(FMFd*pm>H<8>op#G<6~N12TgVLkuIj z=sfe{QmSx)(hIWEbx^^)x3He2qFL$(4=0a0cb9Pr7i;GID)_PHw+(ZHDnR8(<`u|a zx4u>m6x46w=qRz0jVIC_X}CxBLZ%mlArR}WQK4t_6f`<X)^(5tWX7PWL>o<FNGq6L ztS-Fvzm0_b%=(~^D1)3cQ8u@Q*|vJIs{y_2W#{~8tE}=yJdWDptiW$B#iA_fsq7#? zQ6va2?`hff0l4#}ug2OC#CUWQksPd!i1DUuz?8sMsZ~K&^aSO742w3FjeBkv>O3kl z_C@{x)4tq!TIlanTV;>s3be#N6T_zNsnWbmzw#)Saej5#j%XQUr@(g}NI?G6?Ld&V zY|xyQvg>-l8wVVQA~;<!Q`ycO>eA!`>1PFpB~nW`aE*;iN|TXz#jW=y@dy5Ztbb+7 zF=_navZ$Q=*cs=^aKioCi=?8PqQy0iLJoyO`O4^(LI;Fcb#QxN!7?SyMN5-Peb-3( zF?(lPcwF~hdncvcNZWhL)n{`W7!wqc-U`i%Ml)}vH9{i|G;qWKgN)_#e17D19kaZ9 ztm-V=qj0F+{}rM!&tt2BXK+S3+0;!QHnluk?!LFRFQ#^t6S$Ex;tI)HEJ(L^OpEkO zL@H$H6s9VNOp7zzqYof@jT^(azY<t(ZWVSmb9Z`zc^_}SjC0&B!bb&X!{qf|*1cKX zD{+jw(vvsim9z5IHBAi6T)a2gdZbVCV<zTljHYGE+!fsLXcjF_%f^20<H9&xdfE3o z(@*i$;7To7HK7)sZI|w^B`RP|SDTcj=7A~F<ShbF<DQi~N!Pm2{1({j^QqW{VBRKU z6s)fT>eus32F-=O0doNLiVWVdCF0fL@L)ol!NL9JfCua{7L1B4V!86pfh1!-x>n#q zxbRm8Lii^fhVvIZkQ_|e`VgT(ZkcW&D@#ndxJ`;~0iR5u4O&xlVzW?AL7DnE*W>SA zeX04m@&y}l>np;>)&!xg_<=_pFc0>RMm0xmn))wr+@Qq1OdU0xA8;o9i4{=z^d?m@ zKbOX=2G2d$Zf2m8bO@_hBGAU;P^?X`!-#Nm@S{HS!{}cjN6E31FyIGr5W94^SHz$J zR<Qg!wuMwsZ}ZnFYGsM#kbv|*E_>iMZG5mFd}pNEH^GidU7WjYdWv4*Y_<cJ<`_7^ zY2EV2Piu4srj}<7;|~E~W^NRny-o;b*WNpGBgf+{YK)c~i0=3j|Kh@X$o19rqv~qg zrM3X_i9G5^x>A_Zher4If{4hxG^pwtY~&V;(in2gu^I!f&V@7uD+M5>$#>f5=7g5A zg`-B)zZ}2|%jRo)b6o4lQ5zIg(H4|!Ib%1VL<biM*1J`z6MB{Mh~(#q#`L*N?C)as zwy4gBfM-y(Y=J)6HqvN`^05cMT|oDR57zAy!N6OSpkT-BU9N<5sFhdUk}29lV5jXg z>^*@$Re1!H9YsMEQE7*9c$MiIC~?)tBxCc~3SLsTD(Ankn|8I{3jI=oJlXQYJRd%M zb%K<&Qqk%_*m6^luNGqh#M6+0VOCW-Y6hdyHE(o39r`S}xLC8=|3o`<lQWG#$M_Y8 zD)v*%j^;q7ESIAe>x#)2Ypvp(-?sYYaXQi4$xIz6?V!A)kJl@L&1thsb;x$VzHsU> zJElPA_LS1sv)g9=fTo3lnPz9*uHCMxr#W>VTvJIL?_V!gi`9xU$_ftCIp+b!2&APi zW~eCoUZG#qEmKnHco0&b-8KDU6SfI2yUr!VM}eR~OE*GvEavp!rF$vYZ8hWLOqc){ z93?<>KVG#6N9IM2UgdmOcg&I}8dF7e<~3l&(xk0`^*bkxBRVC{6`gpqCdlz~7&Fj~ zQ-$Tw`i56tsE$4nimZI$;xknMsj7ZUhr7JBNgNKGH*Z9)G~9c>V0+yLJhl87N+Csr z2b7v@GpP=TSF%tI*+X6~U+!sC>UPxzXe;FPbvKOJ0zd@B_B2Jh0g_c{SlkCfZPUyn z=(Mv##DBUx^MXl|t1oD0A}=a!4EjPb@QkuksP4p{NP=&aZj#i9T}qHMXj2s8G8q0= z0qTZM<qcDbtM2|?!TQauPrQz$9#42D7qHOaHwEN4ziVSx<9j6}aB*gFjdCCcgtI7$ zb?qvhL$aT~51&=tUghyE>rYfO(m^VohWbKdJh*;v&7i)$(&)$=V}Q8slh>Ejn`SDd z)7Y@eXgWn6`Wa-(<ugK?JusQlAxFw_`(k96YU{lceKOBkvQX)cPy#((J3^h6>j?&C zT(r&@Pc1nd`5(Xg&}hGcEVt1U7l)1%Vx+gLm6|$Urn(aBjp^LuFo@9P2>MlI(yj!H z$w}9fFrk;XiYFVFN)p!nP=Nj}hX8qKgfsVhVYW{^U&CFH>?+;5?1>Fy(@Kr`UgO>3 zx^uEavb<T?F53lU5#y!s@1iceBI>;m@SN-sD?r2_yho<s=lU8%;hwWXK8zeV)BuO& zSK6>)rsx=<5R~9;Sau>$4R%X}La9reFh-sQF}1Q{6UopR-NE*WWZBgXvCAlWpiZ}M z8WOf5Uu38Wf-vIEy*uxOdBh(rwk=L--u9OOY6MapoJ>VfbTKmzl3*9r3LEUyOa|eA zuma^K2~U({X?{p<RV>0+;>ZBNTl)O~&CF(hb}<y2J`%?AXw~k(kUiQ~7yl!(1}h-u z;bX#^te9zzl?PAhI`q<<#%(!K573fZPg6rb_>dV(IWo2Jq#W*@A;rlC>Z^RgP3=bM z;heN^#PNv8JhC(*2W*{iL{N%G*(A20ezgH_s&egk${S~%YPQ|l;C9nLv{;-?YcfRa z?A?*jVQ7)1hNlhH5IIQ9q|Xx56v6R%2|-}u(RA4Y<92>L!D3ZY?tDGOrhan&6C%^l zK#6B=l4XYvER!-c`33mN0}DOAIsZohNBt-*I{xmo4U|51IA473Zs>XgMs6j3`D=;o zpaPh5c(w>}1&G#VD^`-{T1WgEtjm_))(2BWUlB1$>vmE#CuNwXQyB#$i%|x6cLjiN zDRkEXIxl!|Au}^tpOyU-i=(pKX5b6CDj**WCLl+jhnB)SIno8IQV!a5OV3Y?d%*t> zH75(b!CUgvxbWwvrrQD(B$x?}6z#Hl6B$~u58v7``VXr7$mjMqC(p_UO(g4LPx?}v z+g3P}-0h1lxIN=hrB#yIwQWFx(a2QPMTefrc95@*oU;~}P%m7wrO_gvuL-J#BrvvP zQJ;Dx&JPJYcS7w)pNdK3byBt_;s|kZkvFdEp&Ir>3?}1(N5vwR<KnTW1zsh=Gw4iJ zf*z?i@|m?W(9<x?zprUdm<zyAg40R{s6L`y%t@)NLC6a-&7DDa<=<lPGqSqoXNCB^ zcCDoC7#Q@cJi1GIo*_P9tA;rN)9z)v%ixt&p~L?1IiC)eXif8?Y^ip)1u&YEi0zO5 zblD*gD>y#)pdDgbw1Nj?hX?O}YYYwT0E0<F`JRlJ2tnU1I`(b0FSNrq#4_hvnbA*$ z%^**C`9?~(9o^Y7Z7=;5nHiP+H}6zuBK_B|ua9+R6_11fm1Nbd(E86`9=ubG!~^Gm z<0-1WMe7Wkv_9@+Aki&G8)*dVMP?)cr2qN3Z<cp;;=zp@1bj9R7pQAObLHnbm)h?w z`>q29W|G6UpM#i$Qw}Gjr3Qlw@(mzuxwT&xKf?~&MG*!UqIdzuXN6nmk;L7Qna#zW zqQ*K}-Gl5m)?{$?HAveftlD7Bw~l}o%woAwjfPxfY=!N>^O_b4l*C;iVgK3Kj%j0z zMLfNfURtYZFk+=`xq}m0U^8RVl({d6JmU{5j*jT=+j+)d){L5va-~SJuTt7I#4Bm# z6VfJ?UR`a<IU8H_{&(&WDN_n=b#z~<Nt>%r@Af=6s}ep$VDpVlAU)&?^8m>#+4EX0 zH66lGU7M0Engp!gx$5j2QVS0^LBozciou#{{;_(|C2OpwEIrW+x_B<ySSHZ2w`C!< zfeUz*Wwjf1FxLGDTpp)gHdd|hW@-@T#@X_jd}0x`sLi~#?Kd4`V}gLt<GL_|1_pgX z7L@L8&k29~={GT>+4qCuuhhfZn|D6FdRT3yUWB)p!v@LvEJmNj?K0@8>LhhB8<G@h zenzC)KP7GB8Qc0Be-LICUxuD?=9APMv76df$0GIhGkJ)(U*_{0ayM(otHZxO3baM@ zWW2Vula7dnrWrlps=gQ8k~ROm4h_xZYf0)_sqC>??CEGU#_8hG0tkG*P#%~4mmSqD zGbq>{;u!?3(5E$rLJSYZ@;F<cOPA-ukW>vyStx?L7ag|u3$ZDPy2Y@f<+BGZMo9HT z=;1$U%WItj5BK=)JPg(ZfswymSF>1)jppp0)n0nfo9m%+0gN)<@ib5m%uEHA5Tqr` z<@Lw3Fo)&a!(Vv9%TxHT;1#@LF$+&U6}sYu@CW+P+K;rnqti_<D6q1m=<h_JI%n<M zP4%xW^+eYtL}1s=zmy=vx1I)QA@l#FEimX;GBSg2J7Zsn%?^DC&AVi{I|!8~;*_JF zV5IXSK#J?N0pK@_!aQSycA{DTemU;72}tH3M1eQTpev>!Kfa%jEP3=!rQVE~_k@R@ znB}d1T~p7dFVWG~0eZTeSY3eu3{Ia3&h2p_SAxez0Kog#Ihr*k#V}$Y%Rk-rP0M9k z#>?op8maZ;>uT&r1JKk!H~ga^gV_CDIQUP~PoTq-r53%`<h=jWoT4Qe-X=6*ssseW z!S<T(H}DVa=8(1QlYm?KJ~U4u_ZHUo^fwUj-oxGh$r<e`m3AlK^B6$pm74#grv3J+ z3Pl8UNI7b^RarJZ_z`Ir)YP>5Z+a_Lte=j2T@Yxapk7jI-3&D$UkerN7{s7G>bPMq zsrd8$bC!EAN4-XCuf%aokRPuVEFp8rO@1wuE=Q3R!+RZ{r#g6DC|^<yHsFAOZIW6T zaaD_Hjg_zVBLKx5cv}k-_E8SYXBd(^;(iyVfX2#$+}acQGa9~$cmKVyHmFQRZH|Zk ziMmZ@i*E0d%T2}#N9;E8r9VAFTo?KZMSzftfj)Oz7I}7IR@Tp7@}-n5J7`92na@Ip z%jQ)r!cN8RsAEsn&NTXT|CqpW&y??uMb$NMnEZMGxjW24la{{{`aGB*9a3AD#4av7 zH1pF@#5|QF2m4cgq$|kXW3ez3=azVN;ez@g*}Jo7q9n}3q2Tq>uRL@B*42LpBB*$Z zGUq|E5*0yT9X1<D<qDh$6&x3$4yOx~eGrW1Y2~h6TD&x~aL4^{U-qBOec(Tl%g_w- z1oXKXsvTAA-2{m;s88FFo1zhh>F_#*unbs{_1MNk>0B<6ahtDqheI?82A)c_s~IYu z^r<2<;!C@)sLnD2p=eXIqiIq6s<!6)9AEb)w#O`yIa}qdbr$=YB2AvoY^y`_p1)<t zWx|DFy(a!3BCt}*Pw)acHRjr%yce9<d0HwL4cY`57eDyz86Eq7h*Yf48*V%oS)NL= z_2hDOy2)O1aNT}*)U+FCYTe)41A*LpKzHuz*9l*OJ=ZCzq4tvD92tgX(oB?XtpN8> zb_zwAwts%xm9am-mb-=1eM1kdEJ{MJ<T1=iW#S&=FUqz=eN(UkX{63b&`2bwP(vYn zMKdM5r+vCpx5&&|M@l*y#7C!;%5$y;tRy<Px%G}<R_()ojC{~*%HP;IY@?xi4PNG8 z_5Edh&e^^i*>ihzZ@=iUpo~8FYb{B3ZSq|okQ&!~U)Ia#jm7a7{%sjFTWdt`awy(l z?GOl~ZD<SKs+=ct!ffk6eqKca%~|!j+*;t!iof_R49CS0-UGQiNkIwNDW-={C8d8z zt(&WeX{#{(<_mZShJ7@bc!cCjLquZ1yVr3J`H&ofO_O&0R_6zjvPm%8QF9$gK400c z-?GbEiQs;05J_5Mpon$tQEW%{et7dp2RaHBxuBSv7T%Yk(06R`8lk5hv00^R!dFx; z+%^d#Iwg>B_FQ9&^mF2YA4dg$-MHj+E0D$<T#+;EBu2w-dIUIk&2ILU;a*dhaK<J_ zV?ZLGr31T@5pVQLN=qOxLiz0JM?vXET+Q{$7U$8{-cGb#RY*XJ@OSfBxKK4dnIBDw z5wYHr<bFl&^=^mvP}f&#d{ym^?GaRqUUw=e1S3uU;EgbCJZwXaVdf%b+?0Pc#4h<@ z#H{O$$#9?mtxOG1{sW%*M*XZShH!>3nzqryXZ_Tc!qJv@xO6u=7vu(vTDXr=?vWrf z3Y!7LBwp;|MoTjZor;_iH^A(Xa+Dzf=vMMW5dDF;d1rmc!E191gS`4=HPuGN29uDJ z6$jiBM!*1}@EFcVW;hE;z*lA)S^o)^(*^D0^fxCzeQ{r%ELoM!&~iK>R!PL>0^uqL z0ST(bzqezR;d(dxS>+WHYN5D9`ee9zECmPd6<0n|F`Fy8O#Fazc`nK2Rzhp8k^<|; zkv*5}6)v*W|F3#!>BNQH7DhFR46X}#B`}mxw)qudD-m|p{mkoH-V48!-a0a3i-Z>> ze4@Tkow9=t6Tg0zEq5h-@NRYrK5{TQ``xKUNRS3QUJMuvD`RgtGM4lkXs-6lH%CIm zz%m>=Lp3nEx>eSC&7|B5vO61Z##a&D=g1lxfPO1!iawj(8_hy}oF0ItN?>8446-RP z1=Qe&qWp(mT_8|Ca{gKYtx50ugnd&aded=Kk~*fZb4vFuAn0%03k8Yfec|Ox%j+0Y z#Xbfjj;=V$_fQy7NtZ&aK^CKBK!Ji($F20;6B2T>{klnrK_&Qf@7ZVGorNYi<gvj= zPx8}pRYzP-7(KhIGPE!xmZI=41HPUM?7C_WC)&#PkDgm8qRV`p!|6J^cWMfnPHGl8 zsi<zDd4w`vQhw@!F79%kpqlZd`KI1l`5f(JJZL-d<q_-wepujc@PJt?O&+hF%j$!~ z=`amEGX^#|^n$FA`#^zO1Kx(v@D@WSWoSZ@d)|E5&VIsaMZM)envm;o6kEUSUjE)h z9W1WlIk!E$uZM46cC;VUEgi}^rm0+lCWNqQej`r{=VHqQFaCJ&sav{&N>8-th{=8J zq-o7zRq)G-h!N#g1te9jw=;W;8A;E`w54@tv=T`fj_9cXXgowkfvkW42nM%!_MxSN z^e@?jTG(ZCZf<mngo=A1-qAt+EllyYh(6x(?1U|ECx1wGjvnU_WtWV~*TxgdV5d>N zZE=SoJ|dW>{Ovob38Vz1Q22yAiDIq3+JUP_(Aa_54E)f1j6?BS(BU#}hY1YjF~SK2 z&Gy&x{Q=30k1sxY-V=q+E^<|-BJ@K*56{v{CJITJFIowgt4N`*;HZks29sK_FA$t5 zq}WBb(!mm7nop!>^XT^QJ%R6~gJ`}mE48c^CJDS$Nh5;lr!wUqN-Le26Mc_$M%aRo zGWUsV$vqc);|)%-aU62%5cM>fPV1&yJzSl-e*^V#v3hAH?g_y%eZ{5N9M%Jhs*{d) z#$fcjr^eS}Y!fJQ{##}ds@({TfBNc7qf5(2N4zz}iJP;oJ8?Gtwwm`JrAH(jEEC@A zCnhR48kMBYNifONnsxNcV_=B;2O;C>wjF&4HL}oAXH|wHHIt>tmA?WF+IrGRMaEv* z=EtHA(tBU=4v|OjXKaG5AT21JE)j-xHjlxp;^pQ3LMLHEuP(!lhUjE#2K-66DVeT) zr=V@TH4Qt5inwpY2`n1^A$#AAem+S%@+I>xS|uCo(yGoAvv)X?ndBN=V>gzo6~r0z z03#N*=F>Qpk7^B0jW*NBq=noT+4u+u2q;`|e{w#SS6W0&7WC5;GU)nLu;;q{KL9yE z#=kn|@1DOq|4g<&)NcpBJTVrq%20bL75>(2Fd<@%0T3>Z5M^cY&sYG*ZR@2QZE+P{ z6Q{}j-Z(dV_LO?#c}IwRzs()fYN?N>s9`YSiEPBBu%Nw3ObczcS;kEfA8Cq06D@?t zzGUpon^9pcSYYt9ZO1RV<Yhjs_*$=ftA#>uTGk3NnoqNeBy6dNHGJJ&X*1Zo<AdIY z75=z}%32gB!@m=*)C_-ZTJ}i>t;I74*b7c}>82_86r0y;Zms0cw+<;!n^tgf9jj+a zgKa3thk!Bs#!e&Vvb3{MZf9JxA3LtD4_H;7R=A+yufd<<?05?><MBIXR@MM)Icw0M z>AamzGCp!3y3@V^Wynpsf8@NiatdFPZ}SToewEiC_2zY@BiGT+C*&nl4J=3Wi6{M? zm6HL+lYIUH%I|c)IxDtAjVqQfCbpw0nN9V_oyO5M2Ph_B$LYMhS6w+SfX4V>7(gb) zaW!JGyia91ZSkhC$}ibx$)m~3=9aO>xN3L%ws$?vQZrbGTP6-wNvKh%{V~OZjxRLM zCP2c%R4J+AP|y?d4d6j;M*SQCDD8(dTZU~H=yVB&^kv<8nHbCY{HJE6<^0k8$kQ=J zH7yh#o2b2eLk)WAuS!`K^|lhJSva+|cbUM=nM?;o@G&wW^+!~EanYqm2B!i^{S#J7 zf#AbMqmj8ZKFNX{%(!WfYKgV7EoZ(VYYIWG*?QOiEF=D%4(Z%P=+QeO1j5!4LpJYe zz+5;d@9o1#Aq%?V!xU4oFPEZg(aspm^=f;Xr2Gu_-t=kyjEq|bDVK!^8a%)|?=bAY zk2A&u*MX|MViqGHe}JWFF6m+I*Fma}asMht4Yl8@9=<U37f=;ohxX53Zkr8B7G!m# z1SS|>=L&#qPj<oYJYx)0Uy#4xpl=$JQ?83(5~cm$7n5eV^zDHMDXqN06`!~jjap{t zQm3SD<<*bz8`#GWE6kL-Li%0RB(7+28(r~GAj~(+7kyR!A+J?fFOqmL&A1!2$?6Lu zvQidA-n6qaaM<sp&xW<MOOAa6Uq;f2FuSQ?W48@?Gw=tL@2_UbS-QTinW428R_jHl zPAa(wM_~p{Wj5xi)j;-8L^@>71KQJ+%rSGuj6v<opG%5rX7~eYhg4L)B{goJte)ZL zH|%1;_EoNGlqbgkj8CA#WD`R`Ch3zo$9ESq_033XfSJBs0hI~27|860ltCgHn{0E0 z-~52GOC<n0P)_jE(;(&yzqtT}c#f_j^<@n=!@xq<Rv?bvh$S!G){&!yrS{|0o)O$j zzNv!@6NfUpQK-38QH}#hL<bt?uaofCk@Jdt<J8T)r0vEt8)RpB+Zr^c9D~t67AeWI z?uS=O-&@JI27!}wKkq7l(Enf%@Ph*kzanOf<gPzuuQ)xnFzr$LvpO;rT)@EL*0hc^ z-^PSMoUiSKX71J|v4}DfmUllYN<GF+mao5V7CVErbrJP8e+S7{ihH&ws%7iQt$Cjb zM3oNK&u|?RVQ1y-%E2CJ`eEtXmoai})=AQ{2<FmRn8`YeQorIvSVB*DMp~dmRa$f~ zoJ53=t(}nU9lT*p%h6LfHjZ#QCYjQ0GgD7)`jC7aDN(k5G=TFE5<$K@ExL5I!rAA} z&qjxfh#NW#U{~6iA4#_Hxu>{W5#YgQuB!6M9Gby~37G^6S0ah>0Ia!F{}8ANiAJq! zbISNg>NnPH=N@lsWEF;%ozRtD&YENQc!&6{UwTDgLP8Ql#}j7K@6+)Tq%8%+kg>+X z9{(#$0LO)X(c&2muphxewfqWe7ld-A;OCTBmGepEnACSQwt-O68k;PkJ7qwBW0lc3 z39P_M;LD5f33sEqFOLUhUQF%K3qEWsVXRV&dm9^-KAehxYnv*UXN1IE^<~NzAScJP zSV?caxwH28i6at>Mq|sLQQLO9f^zpV`~9Jr*5(H-UzDN~uK3mO^jnJOo4me)Q_;zh z5pa)j=H9p6<nHL1XnmDwc+U6u`5q-4?pEv57B&IF9x{Us&ZYpLqs=|CLtaYl0-B|^ z*;Z>v8c8*hn94p=w`;TY?PvVZMKJfU?tEb~8xQe-M^_R#o$yOBJ4@*~W^%u2TeCSA z>1YGf!s1!`Bs6|3#J?_QER*FnargjNE)^<dO+iZ&C@6+9LWzAu{wLn<?5-hMuSj5P zj6d7-=M88qypv@Bsg>m81M77hGc~QoSul?qt^78624BoDckFCC?IIB(7(*`i*6TUv zCLhtPXr*M?`8^nus>IREm`t1Eb1(&6GKh>bw}V3pgZ+&sfYZ^DP1zZAip0mnBJPeW zZ50}<7>uK830`88<+F^{NO~;m-dhiDTVG?Y7z0IErQpGrkdAY!h+(fu(bY{3BB}_l zT7J+4CAxZ$9<O|~=QH_Sio1`trpeO>hD>D5`ypA^!GWUe>=s|~#Kn=j#x4Y3-g&0P z1NwIZF7@H`VyOtQ;zngY&M~F2uMsfwKh{R-Qz-eQoy*1R*Pop>0lmY*3bGO#f} z-_kFD>=53Q#j?KXnExpcnvhKTMZ*d)kpf4?t}Z`42J!*~@b%5{Xew#F9wG>I29w1H zxvQ0*I}J}T!Zc&d%-i)ACZhxi`OCgBP`mG2{v?+j!kq6KGmz^jXPU}V+Sni1>Z$MJ zm?pxnIwQ>n8MqDfDvvm*vVkvcMMwUK$ohUWYJm_C!!&IMjnkfG|6j>(;m|b(Lf*Ee z<~gg+k#t_fjZ3U{hNGifSW!7TliTPafj*>B_#0b~9^m=ubpCgG+(D4HD}^pOV4Jax zT&Yd~`9sx1a7k^h;B789(26H-%MJw?0@>(55Jq$vkDK@@1~<2z@h!%+a(iA%E|%?w zI+Epqy4v9L0v6uYIb{V2kEzrdf5}3jSt)6Mns#0=tZFU#!87)=k$i|+1dw|5Mmnjq z{XjhuZVFEC5ycDq_KGQxEvULcq2Vd=4t<-I_(H%ttI!=oJ&o)w|0ge8SU2GEKgfRc zSWWiVOYH4B_Y{!7({uAaMG90C&TWSE$>p_1L<~!p(!rvxxPdwq#1AcJfBc&s)0?i> zT3_0yK4{+0kyhjx2jXqwpkZqUaLBD%0+3}WZ@yz9G{D@o{jg>7+9EM`U{LJ*{{60Y zPgaVx2}ZxghP|s!Ho`3IQG)*qp`C(FC2X-&A^!}OYtx>@92Rn%2G5a7im%OR-R$<c zKtx-i{kAl1;)+tR?0M&Tpg_WI%5GZNgvGOupx0Ld?nA_=C^~V2Tj@qJi;Obh$+a#| z7@v9+#3=000q7mDLe-0AXm{&ZLyglC5GH%qDbOUKL)y<jv%QxIueoo&p*#x_N^h&@ z1`0efv0L$YP=0-4avcc=&Lf9*O=JQWFp)umPI7HiiC=i?!CYW4;1>CCE5KPXlPF=! z^$KwxI)Tako=17szKzVg2C<A>`v-?mV2!D=`$H;?Iu<~lgD&yQiTV}AR?Dq1MJygc zF>B`c^3=^#F&lFDa<r$I$r5l+a~^kUpsX?9;<W8_TyNQfp>VAUgeQ;q7@wzAE+{`_ z<smIB7#{d^IRlf*+NHKZ1dDk%#MZ~*=zl*yLumZ{4}jqtXQ@{>kdloTpU+BcIWwdl zf*PlLlAe4zYl4beL~iwa7!dKxO+s;_kU=sl-bMC{^M#p!#JYs@-#S5ZVaJW`)Vw6T zs0^YpH?~Lb=g0dN>*lwF;2v8{;>rdhyjhb74R>Fzg=x0UgJom~O^pYR0EA^D8r1S$ z*%RUt=5M4z?@7Sj<u&Ygf{AQf5LcgDJ$;t#4fnZ3$n7HCcD2+ZqXw_<nOj2+sCcg~ zUQAh~Ga*Y!^_u{Y@&kjbXIgo*YxR7`Y(_@&-*%Bj9)QT_rR~3<^V+dXSn+a#WXsy? z(YqNEyd#Dpeg&<*W+a<lXSb9qGTz_~*7e7ewEIbKd!Soyd_+tQTWS>YJ~Nj0BjfWj z9XunGzJ44uV(MmKBE|%<9Oml!i3gryM?vTUDNL!(ag6u)0mk6#;+XjW=PD(%;y6*N zFf4W%$+(XR3aJ5%f5&tE)=KEn%>M!l^GI?YT`oIVVufF#PI8)W8;u~!9%yVEEM6d) ziq|L?3=ztO=7TG;_0hQhQ>dSbA;ft!8ggMQ<Cm3n8=j(6Y686gKo5SjWHA@d^_**M zH>2a9Y7YZadGfpuQj9cCrYF7|vx0$UOGl_qM0yUbTpW?59egxrQV+N~Uxvt43YDIW zpx#D(tS^<+Vjt><F^rF`#YHiFM&^O99}wM1q7h2JC@S`B6bajm3YwLEt?tC%3_@qZ z@bKj~55&FAmro~hBebwbB^<Ay!0&3a*<>O)gVLrkrlvr%FwkaXL(~=y9w}On;T0I4 zja4HmJj#K`1Zzp7A5ITTsSz31Dx_QR=eq7w>NuoF>-Zu%iQ!<hP+eAbjRokL>FRlQ z_12m{s*l8|`)?X?=T=jw2kv69jAmY?XAHFbO`<)6A~iLiYcnaaGLSn)fO|B;t~dBW z{nMon`tYy|@pJVnU<s~lj533GZ_#c8OCWO}B00MZFyhdjrPkeUPC@au0~d+SF>H=F zx=w$*Rb{u_E3W+A;6<K4Y;fl_2pp8lFA5hPM_*pYU;)7fu-49!0RMLnv|Zb}`Y*Z^ zvLB>?(4oZC-w2k3a2Sjcd}w~^MNT{}?bLQ5j52OJ7??q){XL};U))|U?f5V@GB`G? z+O!~Paw{2}t4_Y*&MOty^tB`Hm_z4>*gY&<-5|t~r8}Sb#>!-tODuils=lRnEMn?a zx{aF8O<ZU)d_un(UP3xgbn*zt&@yk<WX9-3$U}iV>+XOR2vul`p#8AG%)*hh*cu+Y z+ZIp$vTBtBk1w1HZ?w9nD6iQ*xx83*D*B)4jr@LPG-k9fu5u43CQeVp{0CC1SZ?S! z3bZ2om)u&1_0@r3EU`vwIRu7{8BNlv_oE{^T4Da`)}%s-3y3`{N@;>fNZ5EUndGQg zm5&cH3gbYS$5jpA?g~?Tt*E&V6S+Lv+PRQ8lJ+`YsonAoxO=Y7(HQLl;v3z~xQ*z) zJz5C@sbapjV%_7x0%`}xk<$kJo~hD);?iv3Hksw(s{Bbg+L#%X2pUB#&8r?{F2@As zaZk~A53fu>CRuwOT;$_JTh#ON>L4F#yEEed6Y_5(`5%Vi6y?C?2kx&}h_gHyUwY!y zc;Tj%TKFsD8Hygo(;49AnP(VB>ebJ17O<hU^YuL#EQXPLj60Cwy}PJ6u<Nd~X3D|! zWpk<v>YZ#ykCVb%(~7j0+euMxZS+c$%|4Vn)JxpXjxXlrwnq+a$+BU9r4`D0;jM2) zl+Z%RN}MYGZ<mmmM64xh$W#@umm8Sy>sdJG^she3+^%~z<Bn=2c-ASrdLFSxmWp9K zQdiA5KyLDJiPT)%t`l0ekG=aPrExdbXJwdxHe5BFRS(GoF+T9G^p}>~l4i;&D*`lQ z!8Qos<keoG?C(e_NTEbrNJ<p*hg;nMP2Gu&Awqg}&kP*X)i^D`2*^XXlj2XCDV!&I zA;KW6kT9nXuz=A}J^0Ur`Hd(au1IZN#2<pk>Um}(#n~YlUkVxEaps+EBXyD007MvH z-vffvlN;&JZJd@q)iNyoabX%cdYgs)qK<IH85M8hoom<b!me}un>radWPsur7I+AC zlG{G>Z!Ov!exRAc&l0b|T;gE8>w|^pZgg$4m`K$MS!}URaCX7=$DF-rY4ci;%=lnw zIVbc?pG~aOAtG%SH1G{1esnItm>S+E@eiWTTY4Zfz2d@<sQXVYWQh_?O8FB+-gx1h zFbq$$5E;ot`MpA3$Hk|0n)jzfBB^{y184grr7^MzN}FXzT*7fp?ad(y7O+$vIs+P% z?lS_Kr+$ROJ1f_BZ#U5z=m$c1$0!(jy3dbSR1JCduImEbtN7Ryohv3K9Qo&2l`Br2 z5f=;e1#ZEjVeHH*MEEnzF8FKnQ_JKx0#Ybc6Jgb22GLqyo1n~w=M|&mPMqPj=qtg8 zMkp2PWNCGS^&5$o58_N;!k;*47Q>uDU%<zj;S$YDprPTJziiR1;$DcOTm72OCM8cV zVeLi@L67K9EF(WCp5(Uur)f`Fv?pa%erelN>3)-5F-DM*zL;t=Ar3;Wap{@ee6Fv# zYx5UDe1sYc$ePIAv(yQGeYpxT^up1g2Wk5IKCzDFveNIx^(O+&-wpZ4Kbz6^u1JDb zvCGrHt2cO=`CLeGnsJS_g+zqAIV3)}j|}yi-{)#<OiI8$yO?KByS|NLxJ!s!&_at1 zZHl;T*D?BUN)p*y@~$3=`Q?x&6O_}a--qm@Xqg5HNTI6rd{<5e12N)oHO$NyoAq|l z#pvP?4e(UiO55HcIl*>vODGsVQrfhUE<}ez<&<Xo(Ge_YTd2|}&^QYU*FIb=_q+JK zi>wdp#`DMY1v*0-^E;~UyFa;Dr%>g7F>nL2u=B-29u06K-b6-nI&2L5*vupc1IIS$ z{{DUq%y1180Ce(uJ6X9LRiS?=JM%iA$d*vF2fn!*wLWg5`SyP`;~N=7dJO}?78JuV z<l>!1RzR67O217FUF!wkoPU;&Ts;gE<|kMhiYEvP8OunBcesL(1#X<XclU9f=B3Lj zv_Y{c(@|F@wdmjT=sbUy3`b|U9BUX{p#4d!8PEm%2_ePn_mi6+yVVgoOKX|jr+{!T zRtte!LYYu?8X`Z)WOpAGAe=%3{fQV`dBDf-2+0OHIdwk%>NTJx<&5|j7i%a97Gqgz zi30<(?Vb>7J^QGMFLJ!Othbv7CwOTMptjw6=ZN;BuIQ1)mYSGCaMuCf<{Rlnq_AY` z<YnMA1bNVIn=5fEbj^owLn7v%gS>P{-w`3S1_C-c{G&fy<1%3Pf@+urWRiUf^ykox zJmq9^kgnh#G`<LAPmFYCILrrXIvuhnOKzl2jL}xB82XUoC;0s$4xdsU{k2}5OBlu} z)`g%CXy4#=zM<_S{6=HnF6*^1SuK|HbwvsoozGbo%2VR})B26;N5vRLC3hieHGmsy zfRAQKd{J~)+i%(a@Bc&o+rxKX5pAw;b~r4LDp2q&i9Hg{-!yAkv9P<J-x8nS%5YOv z&u@glR)c;HtCD_LE4}x)<{{F*2g1(ZJSab=p|_qc+v4i#S^;SP5>rlCtp7vZB@MW~ zhIOpK1LLSBTU5<;&^RM;yQogqHYwR!8!W;-b^uzEmA@>0d7;{T*W(Y-?AEh6v^A(H zkF(|HEKUpj)coG;A4WU1q$y!G;=C^k1`R|8Y(v;8SPtpy+~_Whp}D=L-u$?vakT;e z3{NeIcfU=4a!$OqRBn{yZeimg^FU>mE?@S+e{1{=Z6ufAvkF8l|8r_@s8<Q=H7B)k z92kI`G>XRg6YFvV&DtF>jAL@G`Y-Y?E%8tOWJo>URZu@C{+3iGYUXE!Q*@zCM)n+l zec0j>zpIC2g2QXqE@>>{i{UlplQ{0uNG#y10i$h0o%QE!RzJ{N!gf}~XV1P7M?Gf2 z6n+A%sE6=7ZV%EsN!zGuo|s2enTU|sf-8|vVb{MzWe2vpK^tZ&`t{X(n6|2L#8i7! zRXk?~-ea(LrR@2aNZCN~|8K+yZX$lG+*B%-Jb;DO)2j)f9!S%1T-}@)CfQROd1uT{ zjS5oY=n~xv7`CrAID#%|Of3$$_;yon^ME+u(B#(z&-|G!mVHAc-nmV)Utb&YJK)3p z$o6{J<x-CG{`7%$6UqV0{<f(4vfn#c6aKj(?q`mpJ6x;l`;^^%2B1<rurl`&QLBYj z3u>nhxbTOl&rHM4s74(?t8&b-8dY1zLeFxI!F|MMa_ZrSu0!6_mm?r{+!gRb-@*z{ zcUq-u)xtjPn&JC<PyM~9{@(FFZ*~}d-r&D)bYHi=|84YN!}j*&`+I@?z3$&vcA)lY zN1raUW4oBq+VZZ=+_&Kk7i4|_j+HRQ9D8-W`dcPnx)SR}u(v*c|AT`$Ag67|JlV=! znXUN2lG%#a=u}jNf(Zr2#Ww4z1kIH=+0sfLjJEdP=<*z;#*V2f?^ONPBDAmLK;NT4 zZ=QN?@mj;h5n>6ox@0ZLxxm;6M$W~fW=J>i8xS?d%<+ew&-^4f$AnQhozzX@oG}h* z0;+Si0<#=$KIEBRA@#2RF%3dwsNKElU;1_mw_=GG>wFqGM|YtE)^chMHgOhIjhS2R z=A5hsmRsu{h$Y?n{XLUMAW1h6P40yUuKsuG6Vhl{qmbEly?rA`J*0z6{`7eEpV&}s z9dq)`Q%c>YaeBO?_<1YuxxmbY6Ysb<vDBi$3{oeW%4dlTzcbWeJ+6wMMJE`)n8z{l z9?nR_&x>m!%(fl#jf)Fp_pvjm@xaqxrC}XFr|^B6iRI^?afE)$cBR&J*v+kSYJDFR z9@w1d)U4LAU$xhli-6H<dwzc#K^+1+B0nj=VI-=3!v7;FHc43puw4?72eJI79oFM7 zJT2D!I%8wFdODd%eC|L*mAh3+8@XUc$gk@yXW;hGA*aaBfA0*dEuv4`F*F}qijX&j z65H7c-AqA(=l$wrrDTLQ^#L0e{ug4-V;eyIQwIL&<QalUz&6BL3|;x{Hqij%<CZEJ z{*;mH6bzW~C&}z(MYMUX3rU96MV?ED%TG5)lr`Z<^)L2p$gBDnzH?dO$^<?CD=_w| z2k=oacUB=rOE>H9;MuM_+5uT3BrEI^G&Vh~55c>{BXr%5fw)V#G)B!P8`)5K<opi% z)IZDLes2vx_CN?8DGR)Nu!ty}*3p*iMYINf8-#MCB~?8K3unYh$yPrjv^F3TV_I(A zIXe^gs?WrY3g=S6zCn{tL}~G&Gp0i>F--H9Aa}JePJD{kE0Y8Gm8X@@UWn%`KYCGo zsGa{kOBuXmq=m6w1|RwBI>tU1S2OQZ3hK`$RQ3NaaJK9RBVtxEhdRhIHo)rCjC`UB zK{TB)jYP(^_vhQ3z>C_O8{O-r7MP%i*mHQ3b`nZAi|{n`do4`OaKN*2us;Z0C;b_V z0GV7c&Uc)f`8_=8#P@HHJrXRhJ{DgAs?P*3NfrGSkd8wLab-At_|4xWPo$-0mK**l zF1PJxyj@}L8fU@_(ltY%n-3yF{7Xt13(<I>pSepe>u8Tzl<;W^le2k7sJk^YRL>Zk z7}oi(EYCfluo(_s0i@Eu{qmxedU^NUvAk~TWr8D-<0$xCpCQW(E1*;(GgHB<I`n^a zsZLbrE~T5Nu}wZBW}S=@Gz={4y7&TuU)l?{T(+vBqv^sxN_^WO0ia8~lRYMhmyuY1 zfo3ch#ffw9V?c6!Z}qi7P-nnQWNPCmV2SZjE|SxrR$GS;AOSfy5E_r_(*dhac#C}$ zXcaoozNuJN%o+=Wdh&e1lz;aweii-BuqaWRK6K0j|52u!gwOHIckdPnDl{krf?ih0 zF@Ld9FpdB}ub06>n6!r^Uc}dY%s=0Fp%C51_R2K_dCPOet{df7cS7A-FXsXxPeb)~ z+%v{dVy>>=hK9?9<*?{`2Qt59%A}eL7z)^R3gg4Sa7!K^z-@<qDYcUR)(MFEz!sa5 zVf8vi6)Kd#0lzEFP6t5c1m&P!XlzxFg@u?tHxmpJ&0Q+pi2Ektf^d}-yP_!HVD`%} zzr8&H!aqs1A=XYv86?Z*kw#lq;K5A(BWPEYM_FgXl#l6=ss@RBi)D)8?lDtKACxZ9 z)uiN96u+Ly@g7*;GyF_bASHGn78GH>@&%pauyqDspHg>kpZalh?Q@LN!Rq$buV5d? zbpAcW1W4--_1|^76WG7>)59BT<;T3xjZaSZ8>|3rv>gdhi$(^k0x{99)$pp$oa}4y zitqe$9o6<qwP#X$ot)uKf2RFi_VX?&B&-oKo~;2^iN1>rfx^jpZ!EFiz`kf4P>T!E zlgd}nCg$eJ!UQLNZ;kE<6x<q*Vmy1dwmksmUgvKadCqzRnI;&kv<0YiD|#36*^^eW zTxBTm9@UKvefRJ2ar910#HzYtSD3%coq}wDt39venvds~b<x(;F|n9lo_q_M_`UVl zpo-26a5)%rBpEL<DD?jNs5lNFDz<G(;YX>~UiGGf+Q-vRoZJq)P=u1p9#BG!s8`DU zdOGZd&=u^C8Zt)I{}~8hS1e=j5K>^DeeQC(U(UnbXe*AuNlE{41>Jui4l;)WR+i&} z{gvn)DGr>BcXiSh$ST=$r0R6cu95+Q&M}3}ZB;l_e6SCf6m8Y{f;6Tk%-4fWiiK>i zMVj7{5XnbEjvgXmCUAnAv%%zcxlRse8S@7;st6C)*rqu5vIx*6CBay^sl5{n6}>dT z*%Xu~AHX%Wwcr%Jpk$qRF^DGc!Z~NYpfcJj0{b4KiFwBdwzXPGoP%h4Ni9%c^(^(| z5l;XQ{V#qu5x9O)6P(ZNw0YdvFf4*$`&}`@tKFQbvNdezF(Nm3T`ZKr)YoRQ437pO zww%}5ih*Z>eR?p|=Xwbf*nS)sZg)@T-MD+a<g74H8)6*u!zTY8I%Y6;>e)RS{xQik zyopAv4!X_zUk`hPfK@>N^dI_R6-tc7hM)<U(1h)$b<~>lj&FuT;B8y}m9@(J(0q__ zm^!ypnf`Un+qUNt>=wV1zLDy~PNm*z;@@W@5B!798n8`ACn5B%lC=GhJ{slFi6+Qk z%vNq#Z>4;<msl!|r|9_2ytPs-=Hqck*PQ9ltRP<LQ}c={iAGeF>r@yAsm2NsW5Gwq zJh9ANe8%9fWyF&hi8q8aj9^&*NvY&+@30(2N)X&R#Q@PIKWmm~rDpDwfU#UqV>#D3 zP6g!Okz;v>!;FCAc4mJM@;d!FSP=yR?#WFUT<ZKoP88jFTiyc_N?J{LMgu}0YAU?f zQ$|)pLFH%0j0+7Kv|loT&r{D?lEDsemvhNRb~yK_Ir4AwaOYwt8llCpg3+wMTkFPe zVF0w<s)6FbxnBD8-uYn4;b4v9N=nytJR;6)IpU#XTr2T(uO!Nt+$fve=83A-Y;mj0 zg8}pYL%OQ5x(wwoW`Fd-QW+W5*j{=arlhF{x)#}01x^rC;y!tYEiYAVv3djFBaq-6 zbX&(E?WIj&`5oL(_@5kX&)Xa$wr6%V_Sx;kTvm}rwq?e^S~YAzOnEYddN6TJ3ao_g z<#{=gzzv@4j6+RwP2i(!=(Q(us1KQd_D0yEN{x$7#UA@gE#$i*+MkM(X17=ja;E}S zhoVc^TeW~z2naQzZrZ(4zt!NvxeK4Cio0LJI_lEU?8;xjaqOa}y%qFA5<{0;`)BSl zP6f32Y3*SFmNZ_n&P|LCTPmy%9O7W;UvgP@<$C!Szsd{57lhvV>*R*=2PjYXn~0px zD-{SZfB8c>m?;;^LsS@ERg)eQ)1p>X*<r8h@`8O99tkFmRo+6Yv8oZ*+dnF{_6UHD zO5Lec%bbm_7qhvk!Pdw|f|8TiWlbPIh(|JMtECW5D(;5LNl0Ye87}Q*AK3^Gq!O|Q zuI@DolIhIiN~wkuy*Ir&_lc!83CQh?hTjpS2<x24=jllf`S0dNPfwBYDQ_}02TfGy z;cp+!qggn)5I95h?T-Hu4_2)V{JCV@EUHHAnf9Vh{tdh?G!|4<j8Rl8JrgOZ6#_#v z*mc(q(gHgG7Ok2Tj;x72<~BF}tu~j->;4}`q{%tb#>FEA4_h?+mkNS-7Rxu1lmxJW z2wb*corVR>W~%1hwYp--i86TE!)b`{#O-Li2Zy*jA3I|}2F3pTtpR3$+Xi6^TO2=I zjB*Q$Hg(EccwfL%oM}clP?c=>YOsQ${V^Msk%5-9(QT&%ZdZ>33m1@Vv`qOk5DL<H z<?Flq;vn^}qiaFg;Ak(1oV2dxd{LOLw>{3@ld|JKt|hs2wTgKcc7-0CbeK0wo_^J* z99ISy)hrF>k8Cll=!(zRGDOiM<uczT56nsoBH)M2eogR7tAS!#<iO|ubL2W~o^ASQ z5%-;tFN52K3q7-xy~d)P%9j)+JFNDZAVI^D4=DT{cXm{RPup(L=M`jssj`M=vY0}k zN#PX=_K?ntJ=YRF0c}<=4R`mLikQvGm)sdd-3_Sw%E6t2c^{%?<NPa@Q|Jnv2ryS$ zCRoMAC^ZA_yGzEQsKnHmE{8%F4;$R<JO~2ACO?XqH<COp+<LcxPs25D1qr<G7a%k| zXVvYn8ugVK<u5&o$7~k{zbHVHwS=ysTG8`&-Y0R>T+5UywBNQ@C~R5AEx3Ku%Y!k* z)*Dm5gEh<FWv36tJ0_|2ZKh9eyFDCK60~?gR5<t?j8|&-d4YLv0Qrw0(@SiwR~;E} zaJN%!qFwY+?iP+_+XwjN1|Ul#)Xj~sN@>_Nx3yml$s=lul20vUTZ%QR+bGHuI#4?~ z>1_7g@BK~@pR&-0+H%hX$hJYJT|cdUU$^W{eU?GK8x-Uzsv^kUWiwgjaO=k(h?TXa zqzGsg+uLS)h0B4kOEn)Ekr4({=-HWi+4asF;fzH>h<C^zkghPnttb1nNKi02-tpXg z=^YAyut&H0XC11N__pxTmGAXGrdi5Au7(>B!?~`HP6ueF-tus*lZ*9-Pp%g0#V#O7 zR9OZ&^#?@VU@)XE1G)82Cq%mrcbS*V35ZwlZLtc_6AQ=KlxMJO{i2n@Q}l|}qZggn z1oWP;^WjmDJVOU=h9vV>hPiN}eLy4&S|M@bL|I%=FXA$hPju1hwnjCavd(8a=)uk= z$~%$>=g_S=yA-XQ3C$L5%%*T-0attgnJ#Wc81c%guBmidWQdyLk(=$ga7v@dKxNEC z!7U2rebbG<@`?j%OXqhD|3G4w-|XXvD$YF%UJYsr@G~#whMB;mBbU%929d*`0AP(x zwh-Mi?UW*dQj<*V!#J1dL#8XCv5lzl9}&>zDVc6NG%G}=k2J}|tSJzr?7M3pUvPo* zjboRsRz!lI7;188NU{R<NH)h@Ns@uZ6|~?O4x5y(yy@npp5sN!@>5b893odDyakWv z2jESR4MS&25U(|6)fBOiA&_0P{<;gHi;~FMf<XCFX>RDQA>e{``pBfRR?D*(dj=_p zGtO`5$Gni*Lf0i)rZbQ3ZWxXk?f32R%~JIfEZi_nz=8V8Ith~S+<%Ez!YRj)tBTf! zI-w;{KSu77>}fsdMUMutUO&l?cltPxQ~@)>li~EapB`+`v2D$NQ%%ntvqSiPE;iNW zU%+~D|8ZS-@G;Dw7}ZFs_}zVElq$fE<PV*zS8-2N#X%HDsT8XdVuBXONtKAJ>^X{K z$ca{rCTzJ`ve4!@+_>HFw%m(5qEP=tFB4%f02XQBpaneX0B=VO++^`fTf8zGN}@3p z8Y{~Yz0}P#g4?FC*r{G4tJrgV!#yBYV3Lp#7fuFC5LJ1-U9aR*iy+>m;e3-Tt)OrY zg<HbzUx!h_1A9ZU)!=v7SEBi-EAa%5%9sK$j@@ukMZ+V>9^KcVA3g&ONc>Mn5tx@P zzz+7s5O&2;iedAI89Wuj1aEV`GXpo&y0XRf*Nf|?VUoM+b_eXb(vjkMh2|WDy&fQ% zr1XZLr#1zuiQD*hoLYLE2yOgu_2T@i#zXa7mw-x>pr_47;Nm&H*@~=&C&Bcf0pohS zrE0m4Q}^&(;|e>?ltP@b8t2$^gkF9%QS3{AdJ7~-n-M)9FvuCXT6GKkHC@RL1{!+! zht&t{B*jJ*zZB&K&t~D-M*9Ob*$>>;2lzqICEqZxRdTgpTL>^XJ|^uk|5pYYiczg~ zd1lU=sYlYt!x__F@aRP^UE584mN_>H_f}~aD8Tb`00&o8T(@Wzy|>o=T^ysf*5pOg zE$$pt`FUX(RN40^A3&%$HjLt};A*V+<Sh={@^|Uc)uRFNt~%Ei;bTBWL1x;tAR537 zIXn*&iZ?)iMCuuqJr%b(!JJRKt-dV6fD{G1@X{v}e|1Rn@XAwtd+ba1rh6W<y~1E| zRk&Pe7Rg7s<e$e52?LqX>+YoS^OKGJR*O1U@#h-9R_Yx6T=v)qo@4mS8&);D9bG1a z71SMet#=Zu*6p3`UdEN5wuntTdpk*6^hLhHz!cUUB!4~Jl1x;5vW!fly?|y3JZM7{ zasXqeOE~NEJOUk3o0kyby2)0k4d;odc>c;i3P^)k@O8FtgB0aHozYs(H!L4r%C~gt z()?ZJScI>&@ej2|=03<dm1<%jW!}Hm$6Itae*i$*B|k10Y-zKA{ELdJY>IvU2{PYC zfG@&8T<q4*EY7m=bMBHTq2Vm+X6+I{BBWz&WH6X3;ZtJ9eVepJc*F&rC-bLi(H)p~ zg%PNJ9!;L;3pPNo+(K@NHdjE1&r!j@%W%%U|6cH}X@CJoc<^^3xoJC%)dpFfVCow< zTJCIi|3Q@n8DasED`iV`msZiM$^bp839Tn?ufbdHS3Z{3yoZ;FiyqpRT;}hpT)N1O zd(sh>AsVbBLhw<xhLl&poS!^cD*lYZ()^g{GMr71B{cSIns9IQS(P+9{j%lM^*)hc z&h7tKywmjmR^}ba^|L{P#`HwczUYvEqFd^Y$c*W^cb^08=`_9AzpxSAeeA?gQc0{U zk+Gu*yt$i}^}s1Q&nCZH8l>slB=OQ|iJAo_6Gd0ir@1XLzutv!z9)+y7p1bhnp&C% z-hmnRZbwzbK=#D<F@6RfvY&|x+L#=+f(J+jL~QQc-xhj7QuX|v%~qooMqs;nX(is* z+PTUp5lkYyBt2P`pdK^#d~8m^MsJ<Q>dTo471j|ivJS!W+_l~<!<wl@CyC7uO~p3H zpgp4n6i%z~K!&u%Qzcz#%)5<_!h5OSW+Y5uF?@dG=JMQRKPVo&3hj?AUIsa|U-hG$ zftZs%Y+ss0AV1Yj3&NBOT!q--2<zxaW$wj2Bx?R<oKp{FD~@@|EZHgd`1OA>mHw$# zSlO?ox*#d|CnpKm%U$`A7G=lUCh9)`2y<^|$BP+V<afltH6jBMXQr+TG&Ie44!><~ zS+d<+YaszHv6@Dp5<xjSJfJT%kT1ab^~+dk)jN*WKzh`X9A_Z(6P}#?#OaJ;gc2KB z;ttX$R?Z|Pll$4+g4ymdy~qxS#-Z^dd}Bl8ES1AE9HRRkq)M24g*0@wz(xG8s@e`n z;-c>cM$vrG*~*Q&VnqE7YXpg_Lk9jN252Jm_teGXme!@6FjD<KkSTC*hy}gqVs{@h z%=A^*IL|;lrOIIXKX5S=R$#Y|mUAl1wU|x$zb{ta3l6Y^aU@#bJzFDVIghJpCJB7k zV705QErpEq@9NJ=1E~yrs;Sh>y}ISrhf!DJtG&l6XhG>N+H>2n4!N+Y5n7N8QE_wW zvY6G~K$qWTt`^sBua-52wKv78#iAQ-J0nJOT6pZ2Ta4y`m-hXFPepvF5eX$`?g@w= zh%%x3Y%cJ147Ffpu~+F)gU7%)OU#CXyOAbV=gRW$JE1-Z(El%saFsFP(<Uyx?OyTt z4EAHwKlxm+1;JI&|9a^Xe-SO7GMXz7odg=;!2)qccASN8baWk~Fh^DHPak7S!{Ob+ z@B8D}cddW*Q1A%c7F*5BM00W#JC>kwG>?TdA*nNSYZjqvc^z3?P@L6=fT&%^EhF_t z5D;pR5`Yj6{|@X#+o186Z1Y)qgp5fCJQ#(eM)ha_iC{R=r3!q=d&k?sm*!Uzs)zO` zTO>8U+3N#o`3%&gY_f%wo}{+PD3S<A{0lr3o&p3((NG;8SK^xYK=KXo(N7N4R}=0Z ziKb3y5<`d#E9>axDBDRwbpAhN!R_HLK|vD_;cF|}O-qupO3%U^_oi#64U;{b{Vlci z&kSKH^VbuU94FU-$8ho8r+5lqbVczR2M~t*_J!(vY!W#)q6|YN?s%=UGCmn*&w(q~ zt337UBUBL`brQQUxitqmIv}F$C=`jaB4EOD+Q68df2vvCuFZODbST$Bv4FThgs=D~ z2ZVxkUmOHFrUw+spL|h{TvD2|8cw56M}Dijx-SSt?-dUJJe;-|b{R7+DDH|js!_fk zSK$q22%}ZF^{qEAA*>&~RL;rG!KKh)%AF$bn~Do^S%L?-@eKZjBF_a&fu5A2z;nfr z1vu*HR^y#=!F)$zF@Ex2jx!>@IsT)NUE|q+MwR@e7_oWi@|G8c(?Om&6F;Ee#U<ME zGi8c<n>)2VsJo0=kiaO(oiOJOq<DbRp<^qx$Wg)=;e;2PAcfNkl}L;Ybt<zhcXZ#t z-mvJcsESp5#vbX!5}aN}Ca))*mj_vfrMA_hfEE3m!)Acq)-|Rd14BR$D{leih<!cc z0qbpeTFa|`(9_)VzEQ&{(mwX9*EQco0lXKaQG9d@w1ZB{FDRrR^UaBA-5693hSK&G z^qEQG0!$V~+`*(YhaF1vlMI{Zli?3m+a6f$S+@GVUhVEv%Q51p_2_dK7qp{PL}g6Q z2}!_Lcp+BHWSs>!2G_}MFIrbf?fxNM$qgeeS@_<N^W}nwGUPo6Du~FU<>U<M3;F%J zYZ(*f7yNO|NE3HVV-VE-bppLO3!09*yuwk)IfYM4@PL=yj5V*U(cecr3CSNR)q;v8 z-xAjIAAC>&x5<Z8GQn7D4M;L{WTn{J=kqGfJWe6{QKRe{05wYbaWiM#oRhEamrbR~ zjUtY`<{l|fX408cN-U@-f(^JXD>dHeQMe1UODR6k5F^XkWSZ5D1rBl$k(!F71z@6U zZmKHTiII{(FWC&!2bnq8_~U1X{B@~ggX1cgXmF-1a+vuc`X&?*R-u=@;gT;{RTOi{ zpy`)7#>XjgwFt?g<gsdW0|sb65Qe9OC(SL}hAqVP4k|_r``J=b?)aIv)eYvEhm5Ew zpQs8BcdJ`CCJh51yFD4t3jbM&%IpW*6O2obojvIuYL8|(#2odfQV#iAptXiv4Pg^^ zaYkg5Ywj^Mn=AF^_xjtw>Q&bozP#+XP&@dZ<MjZIY|>5QLmmOZ6NUiM&)BBbXB1Lz zDoxx&q$5hD^2TQkj4q^=w<GOn9~u@DIC!^QPsakA9lcF%d(u$2oB`(sPexhjrFu{W zL%)31-Wl`457va>y}3<(({wwe0b)N-O-@4#4*qDG(B$tYoFt7zsjz~W!FTS8IhAn= z@oA~u`LnR1(twXgo|^PpKVn6E+s4GT*x3FXmow<3F9O(lg~|-u`BjgckY*;vdG3OE z;W!f5jJGNH##SRSW<I^9Q6~HoRF36`?F|JnP~LevLn3O%Z4}Ll3~=Qb-$V-5`$FGm z&SlkJwuoDrPz4SyA+PEK;JL6iJ$xx<*r>yr6g_eWHyaN*!l7olZ{pHc-M2L!u1nvS zcXxObjdj5FnteEfv9>X-?>C12b259J_K+QkpS@j>>tvjUmrio2H9Q{3OzY?+TZs2g z8J2l!D!e(V(IgJK<hR02on1z<pZ?X__^fS#&W|elI#+d(o$KO|>6j4e0RtazayKfm z@0z!rynVd~_$Kj7yY1GN5>W!hr6xg619tgYoHf7NbPnvZ|2EDuZBmktaH+rZ)A%42 zy|PbNlAKoapa!6aWZxrI!#b4$pzQhF1|{x)zYd(#c(e9eoWQ7g^1{EiTLKhrQfTXD zn!iY?T+cr=qG5?Lb3wGcdk8?|4DmlG*(a&jRWabE+qGng=+39Zsuss($>i2h6KE5l ze9sEHYdU&T)FHHLu^_!hmxaMz!!j{mIKHBN3u2^Rjy$CFtzcGAkwBQxiy^C0vP9@f zcYwW@#~~B&*n5$^Q9_<oVYK;jfo@#zq^_CU-E9iy|1AT97^yt`ZE3v0)67NLdU@~t zVPr#Jo<(l^1}$mugU(V>^WWEMC0Fa3#C0Z&Ik@>lT?j#6N%CmE?rno3M3z1{saxP& z>KMaX^AZC=^hySm@dV<o^HTPmJNRPf5%EW@tt^!I6VFqqz&u^zL5NI>O;FjA0IArd zW`Rn&bc;ND#u#FMo*+BT{cPxard?+z08fcn^@-el^+k!HW-%B+CJU3LyWoz+|5zu* zHA8MGE^ssk#>Ey+cUW9+=)yCfRj0!Ms|y`!;*CPs)zoBYp3dTn9j;P6ZO7LUEB!fH z(>hO$o}=V^vC@JvTG{NvVlz4^+Uz%JKK*g<@Bxe6986EY8tM{(M!nqqwy@K;MLVUw zWWy`41qLmwV0>;;HS~9REeAGOk8sZgJq@L|`Pq4oQRAw3uSP|r<!w$(?I|*)T?)k` z19a?gSbY>#L@PU43Gba^Qg-oZTg?i~-{U9(%y@)wyHlVQg@`%R>e<`;=ZuRxvKwf7 zcv5i0FMG6rx)tyM>BAcAznhszDJYbqX3f30dsA?4*-knJI8g5FkQE<wEKs1?d)4hn zY441YhI~eKf<D=O?}CRKI##@9UJ6UKXp<<RdPVnQb5h%iTW6#`njX}^7f$tf^fuNs z&4OL!uucWqF(>p0*sJjeS?$E*&H<x57!eRH(&dh91;*{jZ0sj<e%(E5oVWM$3<1s^ zToTN){D97b@ZOh7JLM_DgV>$%@Znzip5p;7o1$y{vmB){a~h6bo8velRo{X<?Q`CM z?3N5KFHN7zanqc>_mI_0W=eu$GkzMNqkr-Z867EpB-LLi%+oXYEw33Kq;O(gJnR_3 zil|&~M4w0zLgsC+4$Os|q+9@NCWEB@1C9)%4s4tBTZTMK22DvNzG>#0tD&2KP)*}H zsCPg6uO=yXr!`?aJ_ij}`4KCtqSRH=_1;em816RSywsG*^;_(OslDGGkHB;t%D)!A z_QV}2u4^S0B+!SwHe;3>Asf$zq|Kwqe^30iVLS_RG+lQ0AoZph7Q_hj7DHUd7a0%N z^3BB(V0(=b5s4yHbLhNUwJApzXkzCm<0_`$J+X^%-HBC%@#SiC`ur3RaI@lVno^B_ zm>YUBEm-YHMM3Ql5G}IgBy-Syh^@p!%?S*Y`bOIF`FXAz9<-u#gp|BE8X?L7E{1Yu z9vBwZak10mp7POW$3I9&?lA9{$L<||#HpQDHwi@biRs5XhaW~!?2tH@&#!e)V;oQt zDk-@!Vuq8!`<TPnDUtGpThyuzmI+pp{t%xRKbk`+R!dWQ8PN0|=gGW9c;pBe6Tp8Q z2qbe0bdTk)_nB)jdWOuwt=nxJL@ku__y^JaT(XeGb>Ci7<Za$?b}0<xdM)P4F-Hw; z)Qj5~sq2U=lI5H*%=n$x!r#wxgOud&DF@Gj`U8RsT(pW)u8Af2A%?=mKtthN@N5`z z?r0a&oO)XYnN2az@i}v`FbZQ}m2e%DYVVr@AHt~UIO(H)K~Xoj@FneeVEdrx4LvBe z9$JdSS(5um8pYK^^0OZ`aqtO~MY5_wYv2W1s3iFLbiZ-~*uGvnYFW-&qIuR~0^CoD z#1=#@SVzL!40yRsDv-xEKcymMQoi&aD|_`XvB0hSD(4OmrJ~zV0X^=~nF)B}a{N9v zZEFPF@nUK?#a_zKsB*5@D<AHC4|H5V;g!`HAZqXoINwU4Z2Gqx=0)&b6Y<=Yj`$r_ zI5`9%hIPpHZoGU_f}D5{HpX%UCn8|%Z>T8d=Iz-686co#O*^V|)=tl_b(@9mYI$qK zB{!m<>yId27w(%3-bu7LyWi7X%CNtJV{>Icr<Q=2f_vhJ8H(jtZEvtqQK~z{ci?_} zl5`99;gS{fX-Vr=%X*EH8?3`mFBFU@Ce1y|;AZfc^M~*Fs}GNI9pYBZL&?_qs>G8< zvroiOv(l8zktCk2)5g)MOOZ>2;t_vIYx2E|P^gKnDkY;P?C^7Qq?|=gA>&fTq^3qP zpf@d<5&CygPy2`+@43O1_Yr%ZV@pY37<g@6AikiHJ19FTW+Yhk?satjM4s^BSec7k zyKOv!aPEz2+o5-Y1Asmy5`&}~c$y!KQRhq-J2UwfbVH?x7TL~r>%NbjWhL-~`+;Gz zOY^AH6axywJflz`JXYkHr_lAz_vclwE0BYdq<Yi2t7BYdjvP~MBvby!Xpc#3Ym$Jl zQG)W%!&<5E#c_jFxQs0tNC`%BhHa(#O2mQ9Pyv2t4xME=baVu#$<OX5BwcN6J8(b> zYK9q?gGZmfKCd;m&58IL2?hFI=gp2~^G#Lwg`;J2Ls08I8sE_-f`*ejTbw6t1j2#m z98+1=`n1=hNj2p0%Fl>`_q=CT0)#{ZrDpv2RQFw4JRV8_m1eQP|9?;ZC*O^Nb0($N zqEFAQ%x!M^JIz_xtO%tZn#kR<XQXkhvmhYqFMPv{X09Yo=`wA*`s>4|WUzY#WSy@4 zFo<m!F57a>ssI{2Q$jjs|5pM{B2$}+&{vYxI)`G;QVX8X6O6HSo4?c`tEMx09#=}4 z#;bwxT+{Bn`&|UZ>flNFM~ZrhkIg}Obz`4B_r+fasi$>J^InvmqUrK{lr=<w172di zY-h?7a?#8cq4`BAcN&f2#b%WT;S!m!f$r=gHP)cK*Yxn8Wr|)a*>G{nKErwBL)3XK zvOhKqIjS0#1~GClfoBetuR{6~>+O|k5Nfweap@0ZF41tCi3-UeNN1X2O<^~6sX!gB z&!`|$3dL@b+<qJpq_(uPqEPg$V9)~0%x(_e&#!Um!AIb}XN0OjaL?Ro;gx@y{7in> zPAy*GW+&wG2hwYYIi<os(v3jkh{g9zJ>nnT<j-@9CUTreNuBASFt~fmw6ySfN1qa7 zhfzba-cR(9JM^{gmqW}RI=|R1OaDiM=@F7(N=<XcsFn4TvQU}api34{@U)I7kf|X+ zqlm60ixT-fy>r4CwhADC+f$<}9azPV4#z_Fqh*N%e@iiKNEGL`fe+np@L)vrOt(c` z@!1s)o`L>>*wGhFliVA|5Xzz@JfKFpnwza?EY(EB7_S02(lPI=si-ru-_SHYAjvCQ zQ`HOqSe&CS+p1pDLQJg!z+h79)@R4mdknVhaO1N+_P@?=2RvYO{nhdYX3f>scFh`& z_eT%NpV@9rV65T5TT3=qyXZWkeTd{pMj2W$=WsWNaCnVIjmI1px%JD%D{t<}bj7Rz z+VCt{>QZzu4L2^$aR;&>aLR7Gu<29L4JFhVg4O!8^QI@$4*?5$HO(9#=>J-5aTo(0 zw_UeL!~#K|!1|FaRhZgSU24{kvLG$_xL*wf#s-8QolDUboCL@LtvK`E$bf~|$D0>O zt;PnXXC933jddRz0NHxQs<($s+*0o2&+xCO@JPIrcj^ea?dFly;|hlr;#Vys>60pg zji_Yc#iG2kt7I&WAp;>VtdXR5*aaKo6tyyRRMh-?4g?Rdu4rDhWca9(Zz>1nDM;0L ze}yeY#AS0FBd^S}0!Ehg3`xo++)J)dhbl@)cL0#B5uz+%Oiviw{q;Y1X1A#>7o_Qb zu0g}=^!SMW!X999Vg>{jnNDJ<e2@CTwgY2X_+mcu(qL5BpEBY!?9iW3HOEj3<HclA zUpF+eeHSTMZ&TFxb&uIM=Y3q8a_MqQpnuxFyZRqEWZ#)248|TJ^A5Oq0va0OCI5Th zM4KYVDQvoS%I@Dj!870Ve@S$LL*!4^jHhp&JNjwh^&Chs{{@^2ph}gj@?hHBsn)gw zwG9{g>TM@qsFZ|IWn)8j9wlKYY=@|mj6{4GYh=lNrIv@W;SsYiW*h)5uD+T!9`z98 zl_`iucoqeu`DSVWOhB{0|7o@xH8O-)yY>Q}BZ(CPE`<D;m1;wwA6c)gDuMr7G6n2= z%7KoIAFA02Jm~O;OHz)4B-(`bGLcUfg?}kSE8<f^g(JscYB;x&F(3!GF#}O0OuGHG zXVXXR_pcN8(6C>$DE<Q8uce+Y6~%Z4F%ySi(wh}0Ko3&mtiPFwJ4$tPz*ny#2U;LQ zf?%|C0Lr^$2^n6MyMyG}C6ij6m=vExSCluQp{EHS86a@b3oUHpb|0~_fOD@wGY*L% zSd4hu+e*$q%^MslZimr)YBm`CV<F%EPZOHUXH=6I8G=iQlE{%z`=0vQxJH}#sEwa` zpdR#E%NmK1E$NL@b(Eo-s_#i&v5fPna<JYOqc<XRRFd;XXbJm(^4Y)27$O9=Cb{3Q zo#6JoXT>-dKN`i;^Dlf}J<Gvcck${%{~Z^hyv7p7PrNa^F89*@DMsNl;=v7j=OIOy zA2@1JI`ckU0kINajp~(x;Z<kig%ja`d<Y6L$I7f6lu`SB63BoB8PA-ANz4h8nzh)V zMc^Zw!#U}izD$h65kf(grp!zA4$FPhL_e84GjO!gS>m=u2W98O1nybzHmCUVRKf4a zX$TfPG!n)=SJ5igxvJ%CB}b-X2)Fp;YXl+a#>GR{U@tNeD~U4Q{t&KDEzZ@A?wu5( z<2X{yy;>bO39YYiF%xBxL$nU7<N0EwI?Gk1$L=n*fR3Ys8``$*5+x$ammHg)vm3hU z@ws17nGYk+c2zrrTaKp*DU91MnZ&S^p5FPXSCTF|OPF9Iu{m1I39<mdWRG5osG&N` zF<{~QF{9WSq@d(fCOOiH(~Q&;do@>Bnm2O@%P7O%;ueOcIAu@?7qf-3CK<m@<Y>6d z5Uix}Xxca3*H?x}VNnLYfm$nfBxx24!m)prm)8V4xWkrJ^3Bvj?_z)6!b7l9=-f7E zWeXH3x77cCVizXLi%N<CzVl-WESg`-+M|o{ZEc4W#=YGigJ8<z{|AtBBF@T1Fi&>_ z4DQoVBB(COQISOcTA(+14r=sYX_?V?;f|}u-)K)^qrcvw-R5x;;}R4TH6oTj{K>sw zNA0q_E?Cv5P)FGtVb+VSd7#bSZ<U<9jQwC$rVyejxYX&a|59CGPc3;LMR?)+cCPxi znja6hbLZ{uAGd9%&)cYcKIuov-i$wP?%!7NL*e(HeEs(Jwu?(>-D9`{bzZ&6nV^_n z=A3=ueeY<wQgMChn=NrRMWp{?&Il{`fBpThjacu3(abZW_-Wl>sCt1Rkk}~M(YwyK zsD`^?IWoe)#bkGcrQJkEU3A;NCIsV-pFPra)a;f^JwK^3Qco+Wa4^GjP1WmA(jS$; zpJ)D<O)Cv-AU+AEPct^htTZ4>*GOGWhwr*A)6&7P;6?$-+>#&yz~5TX*BLq-9Ibgb zcM&Q=X_ZA-I&F~>S_^sxqQHW-$_{j5LEC^7o81D17@x(!pxtjG$!|WUKt?v&xRO(~ z>xQ)L4Df7eFSlOzX~Ta!N5hl2K|!CQEMx^-kUBW(xQv&Ghw`!ypWuth_6_Wa#rw!W zmj-ce2f?QU%0Pi^Scz#o4fN$M3+1d5ZuTJR`-LwSrn(Fni_=TAG_CI~V?B)_1X;{p zDIm<j^t2`SpYa!#j=)py`I7^`McHq#!qGR}oW#rD>F+dZv-RyjM`bpqaH^j7ouJDn z2qrmrK&HRv$V)JE&~%qQlnedI0pnQAvNh6LQ0Pd6s*S^P`IyMD1?myqNWb&SrH5C< z!*H_#dM{!(IiA$!uaX~GhYTHueUxvbOBf3TuV9CB?yk4mAcl&curzJKlp5kT1dR3W z;(J3nLP)<#+qy;A-)*aNK_GNT+A|4uO5HhMe8v<730Ghh9(~wtEeWoC#qyl|!ufgt zeiDgqGfDxEmR%?_tim){Zw1*nQfXQlOS<UNN{u8C<)7G;C%pldQ71G(r7+nzRMrzd zoLQ^M9tKps%fa$&te}AlPyb89NQy7ykP3lQ5&1<g@teV$!drGb7&VNviLlV=8N~=U zrSl>JkS*O50VetyTk(fIr9ArZhJkU|aYqX&WU@+vSs8qPT96M=wA*z-IDHK>i@g{@ zrxES5q5oT)wD3DP?(7cna4LP(Tv0qvEdyt0diQ_Av!Tj%O>MWL!`Ajig)qel@k%c7 z4#D+A+@@H8K4@CacE)I+Y4B-fNa2Dl?;-^MQ|*0TD<zP6_AGj?olyl8=?<I`>pg*& z9Vgqk==$cEFtY6;=EnOGoF)r?#}DX@f28EZY`xY|G=i>f#LCundNw~0Y&I*_4L?Cs zk1b>(<jKwWX}TA;SQBJ?a_+6nT*>?9v#jI%Wve16FeG!A7%Vf@7?+o0{Yjiyitdq| z7Dyz6+_<EX&qMXW?wZhGYO<?wf_x=pP<z4KcO2Ljbn%tu&?FQMN|EHdgfs7dtKF2y zcLeA4wo#0tS>nqJ$7?87*$`dg$Vo@JfTO~1k9)l_G^1F?Gf`+;dx<QRx#t)-<dus4 z#fqfif0(n^DDrb0wG1f57*l918I$C0pn~v1|8&(uih@jo%Gt!Q5$>}&YrUj#qv4ZT zKR|k`sD&)Et4gT33T@Yi#<NA}iK*e^?`a&z{Sv>>MwYkC%zO_eXCUSrSnhj4x(&(8 zROmnW*uT7Cp}wrXjJgal`XqG1N`DOKnDH>^{q+f?a%w^D@rk>o986Dcol#V01Tc&7 z+z30%+DX)_EIqh_ov@!x&4!~(cOKtYPV815>2;?5I+q)m-b#|C@t%~l&R(MUr(Qyj zdUhd%YQxz)Fz!p|qyaL)8rXeDbMjhZjw@xj;H2E4V(@nUyD|SP&iz|sVw8s$M_URC z<Wj{LA+4g(SMteaBX;@zcLiJHr-EF2G4$&0%g!CqI`~A}S$hCIYN>saQDZfHrg5~q zH~hH$?yrDW>u8cu7Eu9Y!Ob|v)G=KluIWv@*SZ0+hK?oV03B5D6i0DH{7%hb4Ci^H z#HM=XGFbPxofC`Ua7(rGrni?g@M-PI!sQzlg;z<y4UhCEeSPk^ibpWs6Q4S+Oky17 zr3irf#eC@#_@95qekl0w@wqcuEIRmEtpYj)QX7=a^@R95nm10?K8_3U5mvHzRAN`s z2ETOOC;-d5Ru2HT?h(K3cYgC!y_>|v(VXDusvdo+eD+Kk?2u#fihQ_~``=EM6_>)K zR&zW3281gD8YteGu>B5$bxdCy^~h1NQ?Ds+7XB{$QjJB9HpU1!>#x?9uW4j57?qc! zj4qfZ)qQFfSR?Hop+j^WW^|eF2&)c~>zeA|x5RdK{t<L$#(V)Jp^pxc%E_V=2KpCG z2%swdFf+C_a;Tdz<7L#>%7#2elP!U!`W1j=A>7kQy5Yn_e=%E!e9FKDtnUQWW5i5P zXFn$@6|zNRECz(oyt`|RAPOtx!YM0`Yh+Q<Rq0%f$TLD)Q<%connu^-;HHcNQ0U6S zIQ@ik_OsoCn3B1|+)3RCo-EjEAI-GG4&7+KPXqSa%OtWBO;^Y7n4u+QP3y&PN4?w8 zXiT0s5yv$`j+<cK?{!Ct_~zAc5?hURYj2@OQF4><Ir@Da>oA>wj(bJx-#jhfK;s70 zr!C`2nx#hAUPdV%6CpHWgJV-Mo+v14v9oqQJOZdtd890vdYygAr*R37kQ5LKrTKN> z$T{^j`yUoJuUL8eFQdBT$9p4?cD!)%pU=j0a*Sh%`ZQH9`4*V+OpK0h!RF~sT^e*} zCLo`w|0$7`BY)5eT#xq70e14SoNgDAX?%!+uXN9n1jE6wBA^Pqa$aFo)sX>xR@SQJ zkH0+aqfWDdZ?6UhVzk*pC+TgX&rb~-oRp>JN+p2^ZI2pIf5IZ6hm?8V5?n7~i!P>$ z6Zk{!3Cb1>inr9EVxjv&T;#`hw{KumN%3{lx#^oq_#D=Cd9jsR1;Q6rkn=0gJ}Wos zu=Lly*D(@?E2BKsd=+jsqrD0Tg5(=a{8P5wH*0;C!REkGl6sLU$ly@C+$zw0O=f`E zzsbgmY$zz)m_YQ-L24o>X>OGyx^?_QfOz%P{5IJ_y2UQzI}%Sh!HxRzX_dy*diyG2 zcAKOaIOC4pJ$`q7wuI`7IW~-0IDd-~#GN2`36b_vf?60%gy$Z_VbH;>Mkmk6)bQIS zoyyEudGyYwo?;{6Hp3OIiUHNBF+iX8#i~hNC3n)F_-$Bh8YlUJyH|{W+yGgY4)Yu2 zakdcyEv&Ocs3DXS262i<(7`{Rp<ShAhZcn~+_&@1xu+{131EEVDg1vw>p&Rel%ar= zO{`?6!SzW>8=a^2mLFU}k81kHz_bFq*r$h#LP>77=B{H>TpDW_qm0)Y{xgs=u&*$? zr?VtGDASa%f%TuqgD#~T<i$S>Q_vc$zaaQQef;IyGtf&J#C1pCId!?MLWde~sQ-PI zuXjEsas3xWJ-Iu32D6ntzqCpzpl-tB#|}T~MZJpA#Iq%^Nd~pIycm<g+Fyn!nygCf z4PNLKmXI2HsC|`(L?wbVgu>NzfaB!}89)jAsv)9Y;!<IgILSCt5W2U}S1(S>304n~ z$qHmwYsi;wM@OxNud2dNj<q3?>%2y(eSle4B?sOuW&213rIXGor0xAM6C}T$N`DG) z#2TAkaMQt6eiCl64xxR^D33k7BT<nbV(i#0c*-LetF59sUb;@7W%hTIaOYS!cD;$9 ziqURML62Hq4KLaSdO_+uhtNQ|sHXGpWr!E)i@x@=3AngZA9V7Isnza43?GwpmY7ci zK~SVhksAq7)KVX4$>BYkJ+Ze5;i@`*a#m1uOej&_leZ1ZWL0W<*_{&&m*6g?2YO|= z$sV3}1&93?!JQGd_)D_>49%eH(MH{azmG&--e+%|2k>!2vNj>`Z`tIi4ezV}aZ^p_ zX=)PZ;L~#bfFC?}fLds}wQ@L6E{b<r#R}KdtZA2g6oM8~5<p_nk8b$On{7r%P|32g z*9Be$;L`8t2v2G5i0^*uts>Jo_6EIlUzT6ap$XzWRL;B>VJwkkGe5M6)jQ6ioFvGI zqXNSa!2HDKvqMU`I|78R$oPrS7bwrPSuDC#N8wTn1#4#*B5$r$E~A}wtB{b5)gG~X z?Zp2gNd!wo5#lyAWn0uXs+%i*cv%$GA)|y5@?&D((jDa}9vP9D((EZ+JM`V~(><_A zv``EqZp^5Zo-SL1qP!p_cgnM|*BF2JF;6$^e3!v>XL_+t?-o}&J*wtA^Sq;+a3Jyd zMI^xt!Im;tjn#bEt%Kp~_duVm+^y{20~1Tg6~Tg7s6ATOffDePUeX1-Rxsd(UA&j? zEwr<)u|9p-;jB6uKvp3M>Wn&MtTToBaXX@wt`67MA;ZB1g~ia(?<i5xnuvBQ;btmj zDcrc`Lb#iaO@}UMuY_h+p!;osoFc9tl`v#69NdQMkg0F(_kBqlR>yyMCiTOkz=a+g z;ss8B`Oxfomw=#nk#h=PBO2iAg)6U-(o3=pjpqfUXIODpEOuUUFyQr)n=)ULsQzsF z8Zs$YHWha*wjWbyNz?KK=$*OR0d;$ox{I=pMA_?U%j&VqxUcSEpq&KqO60LHd_8zJ z`1Np10dX^M-Q*DfT^K(-H38J(dj$V?M#%@wtEt24d!NaleWcKn#iI2}#kZQX5LIZI zMQZZJ>(I$-)FF@tQtWJ^B+Xr?erNvhHy_enTGCSq#dx@R`8evgcFC*P7jphRE8G;E z+Ur!h)830`U5lp0txoww7k@I5$4M#kv%xU7GI|-9t%zqGD+7E9km`#oQ80w$qP#G` zyLSNXA#-&pDpW|ZwDdJ}2&Mz6S}a&Lh+WeM^<Bc*p})XXk?<4)iAmu>)C;wj)Ii<R zvDOUT$yjO$R!kybcs(B|Dkk=`EG~NWekp`lLVxD@c;9me*Rbp<*HJOs`DoTHa`G!l zwDv}1+#je*)``4<KX3mJ8K{M6``HAXVGr&u=RG&pczIhhN6OpgF?y6PwnaA7RBViJ zUjhC+#LLo@2ZX8gnGqZd2T@|H9FCD^#r3hiA;?QY2o<3~;WB^milHsd4RWJ%QLrK~ zk-;iAufE_2+aNGs+#Lx}i}szS?MSJ8$h<B1*4~XD8Dt|dV9^cLXqb1jH}wsvm$kf; zxVe(hb|v@`Dh)fhV>qqLvR4Io0nI;YAjydWX#B!Rj^~Vi@am{BA=!ku-9fh;H!_Kn zwB4Ju&#dCGR}^I>Msskvr7zY$TsvvoG+|g+6$9CAo#-|k=LM!S8U<TBp{6_^8!Q_5 z*5*Xx(`$%K=|A@8qhe^G;8yCYU!6=njT-9wbrWZxhCdU4Ye1n#(bcCy8B49cv^`V% z$M-cpcesE77-{&b(@JJ(l4&>w&81kDIKYNPJyy|%K~65(iX23PbLtc+Hz6VJ2$r2H zOi=%D$}$9ypoRIi&J5z`FzmpaI(qnJ-K2bx)FaQZ5)I4Yo1*l3Gcgn7SN{jQ1-FCY zf~Zq*MoTG@z@m37tX7JYl8Pcv6D<$2hY=|A$5ET_dQNy`laV*qq78H$G-VFOGK}}~ z!?LW#M(eAjsv#7BSIL{77O%3))%eK+ka-JdV)=7}dc-TuNrzZ670Wwus>;cqS7(a< z0($}&66{Mq3}J6G&C|B=+k08netaPc+M%L`#bzIvi1rR$9uqmDyy7%!bO%pU)W7h6 zqZZ!U^3r6WA>w>yxgGC#IVV2$jbMI7G#e5Q;7Bp2{g+>f7g&jJ>0v}yIe%F4QN_{8 z!lqbgm-KuCZ<Vs4HeK0#s(lG|8ZXer(BY~N2XfpP+kMg)1>!@fj?#gezeYm66=;KH zH(_cSA_R}^>NB!wktZst0o^gpnu<Fh*HXy+m_y_aAdU9onF<bOfzd51w9&ua0={jv z_0dWAKJscFLB^ob@vh7mRc26miOKx<0*{eZaW><2O_(MmLU@73TnW;()B_$9Jlp`6 zRg@Ymsvw1O96vjTh4vL9FrKXMg{y)4K3}CMVcoWo!z_OIcFN_Xm8v)7^%Tjbb1dc| zEYcyi&p3sgj~t#NsJ*U>Gl#s5{}2@=I{c}5anU2B3kv^w>H_WTbYQu?V~aUsV?=NA zFfj?J;zvC0Y(u1>3`%AlMsm8#((S}oDHzBZxf}@ywwOz&EbAF5_QTt(6_3cQ2Sxce z3}lSf#bUWndI@Pf>~QcPj|;Qzd0wSNLg`-r7>y0i+O5Y3v9n1(p!?j_XYs2k-@gFL zwTm0`w2=YJ_-|rIo<L*Zj->;H^DV;!uFiD+x1?Tw+P`z`FD1e4C=CL7A0BvTV%j~& zOuJYde!L>azshR6gi_G>7;;V$RsQX8p2w41E+p-+!TC9nsGrGH0a;Q9Sz*YmztC@9 zsGSF{kQ@+*X6N>cU3qpAky;BM|7ldnqzNvB$R1M%9GPR+ND{!|RTXE%068X^MB{F@ zu@n%h9T=$v8WUEfXab*R+9;y;->_uWWLH^D$V7s4wO8Z|VT^~`+n0?zABa-6<AI`C z5C!b!uGTk`0>PY37WpHG`IUbFPb#=$@k0-*S)a;LKnQ7Dbw-RTLOL{m$UZfWYbE$K zv06jqziVF~6UtyRebvyPHSOMj?xL@#vQnu=CaKd}2o8#Byb~rUQrlnBAu^-n&ngp_ zbHw5B{bQ6{RAgb3INY8c^jQb_G3r~Q>fYM4EjoBILg<lFFaIT0ptZO3e2!JH?b;-e zSC6e#ogy*2*FdBGJLK+Bez>~PANQ^`=e;D3y4Sc78Y(k(Y<{kvC^46tbiW3^hP{;- z12ki0()0r$@%56Dfc7ju*y(%38Qp_?-J@y)?a?1XSfCgQnqYiCFt;2>9on#Vg<9){ zc<6$s;hJs0jO3B+!hr!RI_4+%taPz4@>MpC$N0eDi|F(be5ekFpMYdd8^m`P1J7EI znaMAT(!k;!S&i!>pC7(7ra(UrKYCxo8Uy(upH8I5Axhs(h<JffHssKqs<YOYn#;Cj zkK)U(`Aw<<kXpN9I~`7dHzfq@NMBmdpU6030wR>guvvb52)4iI<|S{SY=8`_!@Me? zYT5VB#q8y~Ht*ouRoIbvSGm`>XcbZ4^?VevyxvAGh<U}7a;^wvgL~9Fk?~vz6W8E$ zFojrxi7r<HH+pa3_ZBz<zGG-IfC0GtL;hA+u;Cy759TbEZkjF49$@ZZnEtW)*P<=6 zdeROyD}b4Fzf^sU^iq~W`1|Dv#HccQoYR9E$U~%5%0e}jU(?hw9i6^DjfAcc#+jBx zj-07mBjWr0zIHXo^p$l{uM1+}+@Uxf4*ToB<#tT!G@`Vo?sno!_yS!H`o3W}bu*4U zbC?GKrzqzMaE>minEV``<%-#KMWr@V+kmmIpt~J^Sr}_IT~QMV1hy^$d+ld9d!I81 zrM4`}539MCD0|!$=J)ezgGiT6EVmbxsW5?L9}JSY{p%xF7EePf`>f&ndanJwTYlcH zKW|aEe%`PDH>@Aq)<^B??-$$E#rE}m{5r?J-lf3uu3+9D2Ef`?N`><TmcV5>OXtqz z?4k8n#r(gZro>;2!E*m++a%&cIRKOjL1QGIcIrIVx%S$lTCChy7sd1(zd;Ik^Vgn| zBGH`)&F+U}7{Kj$P(2wKw2cN_z4=HgUDR^ii^I&j2N~x_gdbhj-s`{X5k_No1*f=t zHJa5N6%9_*cN3j?dmo#-=ebHs$kPVE0~$CK?~M_|;IC&GvQs}{#zXwS<gG^=k?@vh z$chJBI=kR#LLF!E0NSxR{i39bu7cyFa5gRPFtpRMVx36SiA=DcFiX1dNn?3ChreO< zVJh)Ms2vluDWNFTa{<eAXgN|C+29tuJKS$@=7?SL6ZbjI-q%B9_Cks7H4^%S7hGkq zO$b3;@t#ru-l3oinOKx;Nl=nmdKKCvNRk4B5nAaTO&t#)5_I(i;xBGB+pr1$g$x+X z==67+CS_Op|1d7501iR5%`^ZCJ+Q?3Q+FUj@By;ZsY%kb?+wSmW(cM`Q?85$2hA*q zCP;VV0??{90ncoX<5l}f9p})>A*m`Z_b!t_;=<|c=?|J<kU)r>Bz))taw`p35Ct@^ z*>hIT*5|5(l>}F{otj`>_r~H!pYnMG;hx<nqNo;Xw+N)6@C!YmuCJq?tIi5RSQ29- zQG8gQ3?78wE5Tpn6L<`j5cGZ2LUJt)Pz2gr<v<!_7EA*nuBiV2He_Rg{eIz=anL}E zJ3UP7Po<_tSR^4Dn{K#gD>jPZPCgEJ5E>&0jPO@5ieUussTJArQ0pqc?r{(e$7D9^ zt!VEm5S3WHD7BiGHqL-hMgpR=Frwq)_s@V{^!rc%hS6qL-icgIL6$wlQhb7blwr`% zeV>yg-77DuT0M`l<uN+z6O7x7mQ;@Ief^upQT-9Y?ND8eAGlICru0f$HOY2Q0ohM^ zCcEn_RNc$FL2KF@Z<H9k%a=x8;_^JvOpaeV!U_WRNQq$K^;Wz8f?|&aqcoMfK<&2# zyeQ4wvJ=`%#k?`g46_z<_HuZP7pnPt4|?*xg8}5zcaUiB_lhSDJxv9n!X`A&AStyT zm&eClFv0R~1w&`*Wp=T#P7I{KPOKfL-ZAt51wlMOHBn0f>jaFJb$3V*#A-sKwyC;+ z{IutO3%}Cry_64ky1pff<kSpDdev>Ex7uo*4wX3O-`#HT1kWQImPwV4;8-t#VVCBL zz~}xy`>I3J)xk7;;kO=QN0!=rP(eq*0@oB!`m$#jrf^tbMt(|K(PZUNR${i4CbLQK z(gYr{TY&TXMf8{kwW!XH%I-7?XSY2+vaHWKE-4CjQwx?!!-+67PmJpB=g^N4gJ3i+ zc(Fz3aWtw>#&se<5Si!XmO96@+|BjfW_Pe#Yg5K2x?nHmH&fxqP-YV@tP~wld&j~Z z8z$83g)Szj2ZXtPTSpm7dP*;d8CB#uyZDj-L&{rx2YBr@`V5;|KS1kM(5xug^(cF0 zUC2G+994J&DzR9TqvQ9ODLa}FC_Ub+@AXQg6ocv|Hy2O%DPpY_SgMDr<*~K7Z04>_ zwW9TB0KX*vHqP~IOBG!PF-&I{Sb!|e)Pc2)R0(qdS&Uh&rvpfXO1>B`Grg-8EQyGX zG%Ob~y&GwJQ(L8LCC@wEsf2H0wIX^6*Ftrl_NTK)|1H$cs2rUoGLvm)nr4&3huwt* zg@tI--q&*Y{Z4H!T{^ik7}FHf9KppQ)=u&;xW?fy^`L_UO9A=B@cVE}A4$!i<dV*& z2{FO3{>Md0n<q$JAkWC`5=!$Ptcog@IuUs|6<(9k3wf{zEKVx1r{PII8%Wj@y*x-y zP;3uwtQOo=i}V%`iM&K6lbGFU)M=MjrwD~V*pN;?laea`GNE8=O+_u3TL-9;!tE*e z)UuIjUc$f&?20c!MkU7TC0>~jGWJ~xPeo-4g>$vS_d^>f5wJ!0A=ylqLC2oDUkk57 zcmr<1jw)R`k8X~e+S|_V{|5XA;L*=Kk`B&_k_a%gfis=dX&bCvD5Ug=HVu~eEU*)h z#-EVP%>@$$YkS7LVc`^|9Ub_$Ud9OJA_YYgqdne3Ed&;aQ{2S%gM=gMV@EfQ8-JfX z9cm^wZ$aAr>Wyva7m*s9f&xSAl-uzZ15XtDzAt+o7F3^2tnlM%Z4;iVV>%jl8X30u z{*>vlJO#OK@xtXz=0*%TZ1waj3}mu+D=C@>VISHNVuqW>!IF43cMGAtO;+{=Z;EAl zBY-eb)DFPK049>qi(xm%UTX7FE@F-VHxA6=yb(6jSC|JY<(V~&PrBfs@g(M^MOt@v z#iRLP_CKxB`lE7Rl;TnqZMW(B6%T(kUB){&Y2Y{-7FKseTC!pX@Z3<MO^$<BRXD*T z49(h7VPD}3FFIiHF<U@H{U~HO-Xv~6n0VgO;upy!vmyq7D3*m)5Rq=Q$5K<3iFfAq z68W>@g`4WCeUhePNx36RW)Ez1qW9sS*!QTmVLaGF%zLx4QvZN>1Mo;3$4>kvoYRBK zgHNz%kFF@%L787SZ{mM2UM1=DV^qHC(1=F~hy>`D2~er5d@s+iR+cT{u{|7Y%3Y6_ zL{Ksc%TP1=24)WuTwyp3^9^DpDA3C;C9{vpKM1O4T<;GEV=e7xN*j?z6Cq7zV8isl zMR#$nrtU{CZbHTb1}$L^Iv(c{5Ro~3V-8%H?cY8vWQ!L++auQ<jG@me9|+R~DWwWg z(U)F}?k9Y652(y~Vg(^jgEn&Se|9jRB<g12A~u|}cv<&{qwk%9Ua&U3sEr3I3hSo{ z;|ZK|=oP`~I<{!P1)34VDr9NO<sLEn=GC}?BozgQszdTMzM}vLrfCkwAgk&G`B)J> zn|adD@_{B%@J2<?n5!0NqGzL>D1u5H^y-^uPwvP8B0JYi(pCUQr3cR~D=UDt%Tl&j zZ%*@j#NE2fI#hBK?W2^7c+HHC`HHhR?;y*pUCe>$Y;;hL{Q!=2Asl%}{*TLW;4F6J z(r>z_0)_SVN)0&BQ|Q8kVz(_^{+Rc`BWK^m4ADtvDGPHleN#^8ikRl6@piFlo;o0p zpe!}^(I9k5Ad4O*64vXZrB;V&6VNA%4}n07UxpnQ<Ug;g^D@?o2%oU!zLYxH%MO3D zPZ8)FS~BEWlNHP!1Klw1?umV$jWz_mLw926nCR#O^Iv1uaQ!I5Fx@)}6#aF2q-|SG zM(BhqmD$tOofBDto4`Llm18EGiL>Um?AtxQTYhJ%OU_Xa)}?E(oQ6>ol40rCsf!{! z_XJyWAPety-zAn`*+S2mc%*2Ap3M_nkCCE~)VGlawy2l-C_adwR)V>;sLU+4c=qE} zw*hk#H-|oTczMNy(cFqoh1`{yn`7c5V~l|8h>~X;{2UljEth}Gk$z^pR&!oyGlZam zwDVGM)*wamP;~tY=_dxBhLMUtwKZIfIw>IFdGiBg(VS$@26Oe8t5k?fVt2#{y`%bf zumK)OISgU=64Hyl#B30TIVXzw8=qxJ^nb4bW>O9F%y>;8-{|!2{f2zG4P)6QO2Qa5 z4<Y8uV;#%*`(m+yadew8RC)cs&g&ARis&z-(wh)Ple7VlSrEAhQ#fo&NHN!dNu4xA z;<%#;d_B_QhyjCUL`{Wkc!eAVK6SYDw{jLfnhYDW$!EK6;5(-5%gmE^!uIR!tg+7z z2}kudtSiSpn$)v6YCw@ZcaLVAU~=?(yU^2>$X8)W#(_}{+yR$2h}YT#5V*pJ5jw4+ z2vn$RNW{Z(ceie6f06T$vR)3FLB&b^z-#?f`QKxXloIH*?y!EvbURyO5fTci-<uXr zu5oB6TC5J5i^sN5txF><Dn#jmUT*<YI2K1DEF`&pQpqD(et@G5TCw0T1uHKIlk}_+ zx!doxt`vKl3qS~Jv5T6vHJXVZ|0gssTu}PXAIX=D(#MB!P(zIH`$Dj(-477m3Rxm_ zY;KN#bX$%;5JZ4$bg<vbjoude#@#Qsx_0Oa62ji8woH$(e{mLk2^3ScMtf&4hj<Cs zH$i)SKpQysRG(L8bF2eaN-<(HUQfdH1(R7-%iJ+%0F-Bn_yn=2=wP$@N(&|H)eE>E zmeYb2z?SoiUy)!#Uk~9r3v~8$IxeRA;;VT0eE~j2?5)XM5K3A#6N7|NX&pGm`~lnr zr^kayK%}s0*kDujyoMwdyl_0C1`O=m@(9MD+9+3o<y<Dt^>26pLtj%XMt*PUx?Sa{ zD!vq2V%F_br?(!ELGpp#mt6YnX$81~2Z|Ck7fB?&nu!@f!<)9~+nQ!1h#uR9y^}e$ zELHyD#L>cpGO5`r;5BAvCz3vfp))a<;Y`k#U_3u8&NJ7W@`oo_<uSmjZ|KhesVOW{ z##M4~Uy@p@FySzESp@9+=#^TL9Ub(jQo+C4*!XY|V=S9*ZXR4&IJk!U+fKeo>T%9W z=CrxE1`a8}B52@I6b@aw>qoAM_xjHZdBsQTfuk!E9H?KY?8{?AO^-7(sPUOnegT#8 z-b*d}8ejix*{&y@Z2eYBBYyB;J(m4m58ejdu2%er1s(w3C&NlF!^jGn99FP0?jRpc zt;jD3tGc2^wv<P>JlkiEPu9(<SMTmJ(h!69rbqApU-~Z}C1xjKwI0jzxU>?1!b_gm zTGOFiSo;cW28+N%#^#D-j&M$$4u&U%#DqYZDQV4kT3So>%D5UN`6u@2DyOtRWB7rQ zZLND`5CU-cDjPRw`tObAQYtUB0=EhPzdiWFy2IHo{6&4v42q?utnD<O;HM*q9h@pk zcp-e*G&%atvW}xnLuX0vnCh%yX(Tff1oScY)}c$?kV&uc5zNp3NW)42OWb$`50|A* zS2rkdT?&wfJ4^s2O+{KVC|A4gWlP6;IoSa7;?lb0&1WASGN&5nK!;Jem7O-syH?}o z&Jm-X12&Gt+$z%SEewe;8_XIT5*w?hA0;%u@6RXl?lC)bUk8MsSM9nVUd@jOD$o;J zC2!<M*Rr5ujeU|1B~WuvjXW;dxdwmKguyTOKOYv0w`5;730umEN<NzIb9)$He9`kb z`@p-8SRmuf&Gs^G6YIHIg)@4Q@a^GxV=}ld;5X2&aB~!J=~{;y{e?DW<+4b28y!UK za;|Oo`-CgPf=K!=wU#m>7Fa_1$TT2}6B>eqO4TC=N(MiwLh8XtqGnxml`HRfbn<OR z0xM4bNr#gQD1LI83C9<F3<7N6SK#VlPkBTMSb|K3+x@bHEKah%KB`BRjYDrQ=@ImC z&Hq2Ltv-Uy^EKg*-90|sZDOL&v;07wj}lwT_?LEz>7LGs!!0e10q43)?R1WA>x<sp zDL2m6RO$MGeY{*IR1fG`We?=IyHZ1V?(En(3zpKAhSZmpe|y1rBFlvbe8g(DnQ%$V zAVR?Rmb(4n%X3G^;4_w;SY3Bs)8JB8vxqx7wCD9&!GH81j#H1pn(@yXjCq5GuGw<J zEH<ET@Ulfj8KOksMzRBf)8WG$HK}@x=REJ)>WdBxbzPspdIA^*YfOTpN|XppSrdk( zo6af&SmRVKYrw42ARyt@H0ILR0p}wbH6`KP@5v&+Ja{6=y&9K4c3ayjx$#h^3Xz~F z1E)bFZ-znQOG<Q0HU@woVW8wk^c9=ysFYwx*4E(>KLgt;j;gMnXu$6a{R*?%cXk(^ zzhYHjw<g`k>s>r^skt$>y7?@g(#ug69mV=2Z>BGYA!!x5IFjF17`eWZSB>1R^X->) z0(g&NB&d)p8N0Ii+8lmRLf##e$_<0McUp4$ZuDLI8<R=cP_4%j&XfsNBL#|Fe7fLr zk7X*b3XD2_HzU7z<gdYBRN|R;87}#x7AW0;F!$OM)s|_cNXH(~1r@(r?WlajE}`vj z77IdDQp9O&XvU~ZAbfW8pa|J1-q}^9_qJ^t_!oA1m=LE+yjTO=n#GbCE_6o_6sgE| zFhn?|LCgAVOKgmyT?DBjSip_~k>YzP@Jq#BzJS#A4uY0XS3w_H8bLt}r3Khe)&qx4 z6Y2}}6g@IOss!?Iky>#!H90Qoo%O^CT>nQ%v=?r^&ouOjBo9Wx#&h1{v)iv@E_L*+ ze6WsSp>5#;Gf!lXK%_SGBcmQg$kes_^YlLm)gSVm^UsH`^4CM)b`}ByRHkh?kK|{c z(;!{%KT-$_5OU-KH_rPwDpSAtCjC0#bgv}t5$MIwS1J5Q;6(-+U%US)Z-gL;)P$4k zxIG^kC3s(IPxEwZ7k;$*K$O&ER4g%M-JtHQbzhO1_<hngCFXrn@P#wh#?pt3HB{oH z+o5|mILLZ$+&i1W(sJ70qJ{+H3v|33Q4t<{6M5vqIwfkdoMmpnvTy~*n<pMfJ}g#A z&r)QNWDH+tdpr$OJeaZ+Ra%{aTKSDnFL7V1xp40^;<?SVp}wuL>K&!bxUVw2uNq!C zngAaL2~lq|E>Zqzn1xy{je0FaRiIYu2`B=Z5LN!%9L|4*pYS9WLaSUZjW*kyl!K9f zR*U^*f_NH1m6)F;!R1-*<J$||{}CyPM~S<P1KyCDZD4B!;O5Xh^NV{deO+A<O(l{p ztxv7anzn--jZ1?sXjdHK>W4{2I6%q}g6*sMtLTVLK->Q0L?|0N-K~$#Zd$c}8P$`f z6X-!C%aaL}bPrycCjP!0aSV_1jNdfPhB#c(zoN?!@v)-91lF3<%5p{`yGMl~=~ghv zJ{3LMqCG}`&bvB~rMttd2yt*?{`k#SR!&r)7K@b_dn(uY{ZTrVPdQB1P~nuNcD5Xr z^sUB@c*v{o42kV`KXvJ;IL^Vo5Ky^fA{cf7@{%Nd{E#gZB~Y-9_M<3fC+tyPmX_Hx zXZE`OI(xvbD7$2wGa+0a2u))F2b-O!byGi2*d0T(8cM086$tOGeh6EuAR%?_b29Xl z7W2){mQd`xyxlGg!)iOsH)w7|fK4*<kqKI+IZd&9LUc?bUobWsOf+I^m<e<%sC#9w z4Wez6ZM4_3%4_~>$cp8N1t1#!!;iX<8SB`?ESPvASH^%rGplVqd7<_e$76|cNf(i3 zNW&d8P$+V%B2t^+J9U_#uX)T>S^HcP0?b4R>*OfD8qJs>dyV`4%YEoJR@>8)>=~|2 z&g5nCDc>m;bH61vFxv>!^1cIYveGP`VmbW(X#$B?c(PfJ*Lv@U7y}v?)p6MRbnhBr zt9odV{fMZSe9NdFe3s!+l3r?;8(=70*v+t(rI31F2w+0>ZLqUwh~Zx(Et~~Ki<<az zF?fiw)*uqiX9wtmQqjcH%^?B*Nx}wWMQ^D$y-{|lMv+FzdeLJ9Pb!uR@^6%jfKMob zjG!g~qaBvt4grwjmQpJ?47K3BYqNw&BsOLxdL-Z(AZa5{K#AyPrmZX;xqs|^&Rdn< z2RGKWTR^oFac$?+Qf98-LK6v>U_nU!LD{mp{UF-Mo@aD3qPP;iAZ|Dz{6qoX#wr?E zCb?Vf<E(!Yj1>F1x(SZos%eNQ*sbua6V135dkg0CuRzT-6!Glf7IIZvga_4sj%9~> zP8NN9^PV=X5sK6OHt*8kyHb<Npsj=U!=YwWO3|IiGH77cSolqC#YKFI>A^+y;Cjdx zIV8oFb6cn#i@IBKz2C;f;0TmBW4R$=iJ@E>m3e5<@<rS;65=X&-lIb{mK|h-<f~&g z9t!{{3FnogsQ6*KJhxLk%q-8n4sBeinVSkrRm*MGPFAJ8y|H~~3Sb9x-_ZHRn9n6W z<N)HsnDk9=a@>s&Esm(9)pR6sKcQ=uFC(!zV&-mrb}Xmgg3$rgIlrJRZE<-rU~Gjh zTl{c{`v6`?_5<|2Eh9uG;y$O47?G2;05Xq|nQ(fhUrpl}_#e~U9IWkI{(TvX%@Fdf zeTO=CUa?k<Ix|8i54mjSBFUh?EjwUGz<>bv*EV4+6HmukSMV7e;7}|9A!L4Jd|-V3 z6N*p0s#&_f*0*x%+$qa<_tWkF6Y$>Fexqg)I-9fZ9X`UF{(lCK$oUYXj4+p|36%8v zY`HXvLReJI*+B|%FUdglz4#&R7Q}_TK=$QWulRJ8@l<s-_tV1-y`T@!TBgF9tQ1s0 z)wDO(JLLap?B0w&JuDZOxyBSJj<^DF5{yJ#;hC)IaGy&4qQ0f)IpBALaVI~6AN{H_ zsFtS@Wt~pN>Bi>dus15oI0Yz9@R34rCUX!A`1-XucfCG{&M)2F;}<d+&jpmTND|+% zTd#bt1Gycy|95%Zez?Gg8|g7Iu@2fF%O7(MoMPtbtwXIZj2EW46c{ef{~~IBt}kSq z{HET^9-TwFMIA^}0BpFkz&R2Ht&t-pQ+}I?ZWw;l!O*OZ@6c)7RSPpNGMgwyT=D~b zXYpX`z_LV=&?Z52NaKI*<&2)4(?+)2&D|w1yBW87_WXpybUGKQ(bvc@R!R_T0!m4v z6f#=Sj}{5*e`JZmwToi||8mFyaz4#J8Ihj8+3NztT&oz@;0r}}Swl&(_?!U(s{JjI zzvE}Bx2@NgMiB953;y3|Hi9NRQbq?h5!Z<pll}m#zQp)FK3z+d%yoI2Y5<3&h0WF} zOS|ZD@B8@Vdadt5(CNL6r-xhqLH%_x%suX*NVm7<6?jlCvn{IuWg(zGku=;G=2N8+ zE@&+}%X$57=WUaFD8-}Lu@V&3umnUpe<>d8f4cbNISa)a`t+4Kp@g}kSq7R31G(x1 z@F2+Ht^^MnXRa(Nei_q><S#5W1L1VEglV0^HPH}oUl3MhAO5&t@C9>gDxbMbnI0fT zy08Te)S@CG25L~NxEG+FF>0P~HXxIg-2#Mi>tBq#3b}C_lauQGztx?u30ht#R85}U zLvOMoo7ER~UQCFcM}GCWg5sW)<MN`}nrZ99c@=_IO-Kd;yB}I-#?VmbL#W+&DEngx zLfKCF@)>eG>1~EI=f`4ugaFVos-)JU?DP2%{P+SMTbjNVJUnL*5v_xT<pyl%#=e<7 zJt_N+a-tA3OvAoHHd<7D00<+UbK{-!F2GxfeSEi{qqUb9$@{M(;H`r(dPHOau|dwD zYla>hOxky91Sj6NzYI{$gZq8V_+1GtnbI$>Z}gRNedlW>mT<JY7u3ml=Pmc|P|gAM zm_EJg)NG=eOm|m>h)LqQA%vgOL^XJwBhU68oVc0Jk{}*`l>i+Tc>#7AGpugkB<)a? zSbk;3Xz;49!2;fa(;@;Y<lZXV(>aFkq~H=fO6$b+f28%C`zYu6++n6bEx&^|+@0zj z03){E7yH0YMn*s9dYZXhO*qQd-ca;o(fI)XYt#qz)l7^gdEfB%ArRz+48^J;Qe57$ z1$)TB@d$5zHn48Req<m0|1@obcGW>!@@&Oh@~FI39Fy}$6urbmsIn6cNFe12X-+kO zpV?Fe%r)vcHM+jeqx&Lec=>VvRFv%APB1K^J;}a`Z_-?4#+Jsf%i~vxWR)YfvVe)B zyOBw}^-I<aG|zDp3j3Ss3NVEX$+)tF-`C>BbjVV|9gRBuWHE%2Zq&8~221bc*_*EW zsA)t)XaKXW19{aGXmd=Hu;Tt^9(q3-BIg9Bi~jt=lt<v;eZeR-K(q;`g_`;ujiOmD z$&}5qXA-DG(Enr1BQyEM1~~NSC4IA=ka!3gOX|*ZswP+}X$J-60;d(5?`c`2QF@kA zS51vTsQIFFJJ<Z=#e1?-VTK$pss|*@PC8_y{6l#a5EH=7w-~c+7kwOVqDmAycrzVf zY?ZyyRgIV8!P>RWF4^>exS#)pOD?GdM&2Uk$v|_CaYZw9Boorx)_!=fr}BpKS0~%B zvwE4?E%aE=x8<2FnM}S&@tpw!-4(qYuXa``n*y*5{jnX#4G?zTv7G&}0U3c<@{peJ zI=h-u0pz28$8=F*j=`1-5CjGR_+aCszvf;_4_Pf0y*Wf&&XbAkUQim+)Bh3cF1lHA zQR)pM1V5;UkQo$doV8ly@LUOE!<_90gGzUV0YZOGc|LTdy{a&>TnsyDz#Xh>^6Ryz zP!16Z=&)i5as_DZQ)q^U%s*=M**ykHX1k>!2c^F^(Wi?EE3}VN`erz!WjYqbd30BA z`B+80hM6B*^MR!MrGH+U2b#MfS2uZ>0-lxlwkcrU^iMAAuw12E<@<YwCw@LigbK5N zr6-0+<2vqn$pQaREr9V3Hl*djA9XYZKv+8gVsSLY$j|A)vYKPLN}^mc1UWcrbV6)2 zG;h|z+ilA0=Py^XQ9P2Dc%^GX^vWJDVmqDGfTeZrvbJcENjHeS;-pA=XMmTL3Hn;$ z%egaWat96>$+19uCkLzUi@a$1Z#h)W5ex!{=Mk1DZgsTk5z&jvIHGW3dCQ;CmYaO6 z+0DOFhOr1vEwO!Klh_sVS2?X0pI4ifJyBE8GW85n3kTk~>KK@gr(G+V4>7sDQ3b&k zmK;a4YO!(JA=VorCcUw9Dr_}u_4SCUX^XwWeC8~lo$;h-W`tChsR=BAH*bM<-4g-j zOCQ1u7NmiU*Mz1E#NF5U9estguc1`lU^6c!>_Al__}{=Yw~$wD0D_u&SCUJroG9Ei zbwzkT8^R})c>OhcUE-Zj*6Gx2t!I(pDMPj%R>H3W*B83C3No=RrddKYx<dt)>)W;? zyz|{U&H{GaZXLah4V|VQc@30dc*Rl=x3fk|{BT)l@3L@Ga7<sDKNKfse%mfeckrOm zj#O63?Nnkgvq;*-!Em|)Py?VHS0ql#+bns%^&i&RpYdX3&2ce6UUhA5vnZ6{*y?`= z#u@Z0<Gsm{luMdsr_IZ@#Lt~oa*?VaQxw%-V~wRaA;`m0%HSWWUQ2<r{qT<_&nFRZ zxnA3LR;*-%W+^B{KdTfue$`+Px8W%od|iCN+tj)ijfpN1JA<}Z3za1AcRU4XT>3w^ z1&r{d`=B{0uUu1dhF8)&LM~D4llTTg+dbGKW~UKWl-_N4FI94S76>A?=VYhd@~}ZK z4`AcglU0?22fr(^h9(Bo)LZkWNZ|L^o<s)Y_;l_3I#d1~3jYT|82%kI{|8Jz!=QiR zzc~IKCI1IVf5Uulw{(E;^L|mVK4CJa-)UlF!^zs|P>bbc_`oNwIP$)zK=Ol0+@#Na z9KWHH_dzwUdICgbM=$xmrP1&bZN7OAnno!ok0Q;(s_T~Q+A>?1a87f}B?h>-el%Oa z%Hh*Jr--zt3dIAj{5(f9Wzxir6eShOWw`7~e|8Vm{@INm<7Vt<M=W^NgEB5IivFmq zZXwt+iEK<<%`j##UHc+n(GS~Z1%va`jkGvB$<Ec&nuD%YT0G5_K<WU(?EAALGsn=^ z<NaU@zf{o#m1g~(bpAfnl)MKHNbx#y;&(y-YakOoKQG>qwY0ODp_Ha#a+I_8F+Kk^ zCKA*58;w^mUhcRSJyjeSv0^Ns4Zj0LwS&e$$#>ulOFCCc!vMS@xBT6bvEqP2T&<8C zOoM=5-sbIO?|b*XsYo-CV*PbV4U*j_5C1Px>7JN&K;r@zi5*u6dWkMzTIWH5=SR6O zv+tP?zx|WU2<|}HrI*`qfaQ3}=xO~0*E_`ak)n5{@JpIPfQDvC13%H4y}Y&E=BQzP zO9s92G34TUB;|-K0Bg}DITq%;p4T~7@Ah_?qZ#1fNJc`9&(ubdA=_6{-JsPT)4@4) zCBm3+)kk__MFc4H4Jf}M96X=t(gMPV*Ezh_Swm3S1ZY2cK6EOxY<WJpYg;<XT38cP zJ9qzj31TO^d&{pnjP{1a{%n?-Ri8JelkOAjPxk6ccP;%AoDw0=#R#K6hqSC20_C6~ zQx7M6=Cq~;A4kqe-i(sL3fqeD?DAfj^TW?8^nYuo?x~8&zU28@JjF#ZW3KDbxV<k~ zXH`g_;C89C=H`u?JQ}v4>I*b>PxfnpB`nX)4q=%F;v{svJJK2B5&)|pt%vn5s4Au9 zkY4drm{Hq~aDh~Gn6=bY-zfGgc6^=0l=y)TRuG~N<8It--I}=-kQ&mxrgf{!2~tXa zK!?eh6zl~*;cs`oK=bw2c=yL8&V`G1WD>4Wwa%*O&btYugJf2nK4U|{kJeeKL-l2A zXv5Dt-~1|?C?qwjI*vKfvq$kj_DOy#WUdD@HK*tT{7dAsT}cTGGD-wVxeYqPenPd& zT*?hRYIuPDz}Tl(#tb+~!tTG~fxl4rC<3TAuG;f(9$UUA!}{J_lo^Vn9Q%)2a3ck+ zwjo^O8$xJWFYrKTtWcG8OUuJ7th!y*%O`8VC4O6>3Ib(2dbiBFS3FuGyf@!~(C2Z| zq;EC}7tmU?{a~+Yx5nu$@IfSP5YCG?gT=!O^-hi$1Xy17ZX}PbK1s_;+7(6yo;u(^ z!d9j)zVM5lj?yT^yAxXg^35`>T%}(q<wPA%e>^_w*7vgFJk@3*jk5Ot7!3Y}(RC}X zj6_$BN|7Jd0Y36TGgE9Z`eeMwcf)hZ<cn_UtW#+49-y=?M*{jOFO3bGXlJDZdhuux z0W{QqC|7pa`+Hw|g3jD{SgpgLicPq(xSlSX$Dsr)UHiHk%f@^%e2gxlUBO#82Y+n4 zvrmbMjW*IA`*%5v6PzFY0R5;QzA)j>tOZHW*OGcLa7)*^j|JRquq^b2#LH2N-lVDg zW>B<d(lpn8W{p?Qf&F<$o9>H~Ff74?(Y6Olo6r;8(D-igtQTRt-k$uS=>>zkl`bxm zOl9jay68LFoI~^QcGm^G**<-=sqoD$oX%P{#e-LORTgLD9{T{s2#3t`E?;UJ*1I|Y zDG7qC@x;^otFv4!h7igl>7?+%Nz)#DTk<j@LPQM>v9>9&Ze@NtP*KWuYk%4Q64!_N z|8Ur)nvI^H9!(CN9=zG;hDiTZh90t3?62#(2AhTCyaYc%CELC;{Wq7IK7mggQd>1& za@K<Ve)v#ot*C(#Qr|Thc|K`08F;i8K^?=HfvW^?X&}X2uhad{MNZja4HnZpZcdLB zYozwQbqSX01<HErc#6HEEG-HWTP2?_bAa!q>-wt9t!#MP?brPn^Oa;~mH#@ThaOSl zEKsvYT!=?9x1Vw@ho*yCIIfl%1l_{JaHWt;-y#S2MhD`|@TM?OGh+J~^JIB|$46Ju zJqcFbWedc@BsCd-NP1;Ykk~=i355D$JOGHrH|!*C+og`yOCH@_h&9fTaD%rmuI6OP zn`gKNf=?bl(X-{q(szQMpbYVm?ZKdSvBk=7pv`={p5VJuvWQ4FL%=3008c=$zfj-% z1hl`mKv_@AHS>L=HrwP4%E?8Obbq5zGfJa|PobRrMVJjeDTX>OeRELEaSYKTv);`C z8X4RVMnR1qUzWCT=`XqLo++Da*7wL9cJ*j9+FA8;KAFG%>p~+BFTW0^V9FofP6?|F z1c>c{O&hgc7_yZY<8&M$+elhl9#dz<fO&OG+jMPlmPW2&K%&+h307cFa6FC#JNXtA z(x5Pcq&{$mnNIPo=~#q%6hB7L)zN2YlJv&YZ2|Y~0olk7K9>W1?5<@r3*leIR{)xk zf_!RX0JU&+A^$YiG*Us9+I0<%SfN|ZbN^Gt`XH58c9FK!q`flbNNDl8sxD$@4xAiq z>=&%`&e6D9nH&RpMXe*(MaV(kx$3z8XcH1z(<i<U>Qln^Axp_+%L%!+4kEv+P$Gh8 zdUdeuNlqvzj<z_X(k66lllh2}zQ*fg1xpXZO~t2leA_JaH2wVHazS|#5`+oMz{3-L zexb@3Fa29dK9<Ct_-`q(UB7dO&0DO__#qRy6Tykp9Nvba+ndcLZ~6U*uLC{&a&+Q2 zM6!ydPXtbJ{vij<BOOz4{4fvKmNin3efF~Mia=g5hME9L91S?<`3`=;5Y_-n{MU^4 zKaoQ`Vw))qAzUA+0Lkl{Ei-p?LuO)8RH3tn7)2|Gu?0aA4&%TJ*2kAl{~3(++(yJ9 zbxyl1h+xY19VT{9`q7t^T=B0b@uT-G$}yOgrdFSs@aaPp^-UD&5fdHs%bfLY*`z4W zm-+jua<CvCZsNHk$%_u+P5{QWt+Ljptbq;2t!9ALKyq=3CN}d!-M?92SqZ;E=MQFh z4@cGoIe|v{*j?sJ10ty`A^DRIgmmWpElBIYaPIf#v21E~HQTRQvrx)0@8ur-(^G{D z80BF*NtOx@2R~o-d*^grDGb0tFpKWLTRcw3O)0t0nqq&jig)2@cn<{po3{1$l%pf# zcbPMZv<$8gn4;ifBFhDiu~{UAR7k>_mAxoz-(m2p({MD;>L|mN&>&a%*1YpJR;SJy zRc=zl+5X`g61{*M(bN@ct>x|L{v=#R0$POPopHM^hr)+SYCn^)30t$HGXjD&=f&J* z>u4{L{`OHMHL^7o*9gKQbkMSqA-W8?!_iSeG)=#qo-LWcEFn5?qzJTBQRs_w40g>2 zH_$z2P{?mjT>^5Aq>p>nODFZGAFe`e=OtEf#2`Rrfljzxa`<&f{8fN^Cws~#6jN3H z%w5(Z>#>kl6r&97F}0%q1LPrsMCeio<Mk~YwtwBzW{$JqB3!5ZWdeiBp})RLybL^6 z{t}S07#$4Hrz*_C<;~~%opL)y2pD)UAP8a_sS9?%31ktCS5S?eEX(|^x0Ep+6|$pK z>>4|bl1&xXFStuc;@n@}m2mCakO9Y-TxF5w>lvLJWspnU4z_m^D$(>mjNL<|CN|id z35H50%s=wGNjRPi<Wg+N?AuFk%(W@Z2N#>Y?uD=sDNdp0lT{y0(_}di!-f_~$i)DG z2O*I8#&1fsocPg5L}F3IB{JYxR^=F@JJNO1QfGS!y#ujyI>0n%!lKa0WozI++8zn) zU-9*a*8w=@rKF_F$2bE4SPZm%Mfh~_D6MUhK3FbFtcnbW7(F>Ri@uT(A8XmQo#ldO zh~)w9zxn?MoYR7&-k&bjzN*cH-6Swt?(o7gJ$LVg;N)!GAJC$Mp*rD)E*~vHsD6PQ z@z&p@%0GYTyxeLwvpOqgrF5S8jV=)mDWxBL=wg<n1CcE>JDOs?3w34VHs{qY`Y7#% z_zTdxt<ptMvNZuCP)s8<MBEOWF7X0to%D=9@1ZyAH1Ydbg6)<dJ`BIOsmf_X5weE9 zW&c<Pa;u-5Q8Sby^Oe8wL3zuC1tirwdjf1{W2>yQ1klpZ*r5MI@ha6QB6OsGnQ<n0 z6^oawP}a*lW1Fs(C1WIJ>RI|R|1>)@z+O{k@cgbX-tw@8WqmOVg}v3Qh`5IgBjtzf zlt6J#N(bwguKOemTRU7G8ztyh+l<^Hhiwp?L{OBlHIkP^OrA5~NU%D`?AL`06K)O} zH=yiX{SAPQHrS0*8+!cJ9>;6E2uE(JPphZ6Uwk?CKT`k~gL~_Fj`D@0P6e;~kFF&4 z1D%_A#BB*(J`MNPB`Z<aZz*`r%Q_mC>4ki-f=jAJ&T-&RJ=_uF@)hgu^Wcus@v<9c z=0O5$3?g{_*t@KJZjlLU<Zue%5TU2Z&@NT(-QvF!uq%>Yj5DiS5>XcG&KWvnF{?%} zo;!nBGem1|pc=G>o1IW=XT?f1r<G~Sl164`uiqSicGAHih4Haq>Qj@_V*tk#eFZ|i z|3sHLWIfg-o3#HxYOggM2B)PI!=Y0S!4)+-=oYY>JjNecj@S$S!qjXDL-Na}03~?A zW+n()naOB^!exGkh(7*iCcV2(fgc+I$vpmPj$lhf=`4GH%60QFRRrbmVvkh@eaC^I z3}drxsU+vm1DhfyX!YD=W^jtT6Ezw^q~igLO1hCH_QsysDWm|_=9>Z1l|i__!9ZCc zs+pie1PD5_cYi~=ymtO>3Z}z@#!lD-or15IduYmL+J16Ku_*a<qS$23eF+y_DUC~I zv8+KP&)7Q###gm@v9Red%M1|Tol-BKe#lB<_tC}VfO{nPO(Wff=E63D7g>WNv?Y%F zST?MT0gqfpfU#`xAu?_b=w+qe(3&dAZGa-8hr=yWNEA_KD0$8nF#OmbjZF7@>}lVB z8z-F*D>3TOF<z-DYx@EN5^(x@_ltS><47ZZZOD2Ylwov;opJ^X(J}Y?mkPjbwa~#I z6d5uXxt*Vb@nwBAj-&Yz1#Y1VXMc_5>2WN@cWDb9;yM8yfKU8{abw?0Ua~L83f{)k zYeSh~4#gU3SA*<^KIgNykVOp_-!`o2;iiwAZsu?VJCJj`6TA8<Im82~f!f|<%1SIQ zV~4;&rYYkIM#I~hJ|qIw9_6zy!)N~o|1dpcYH?&bprp{=Nl;WK)TRZ?x=-|7iemPn z{*z)Bd1IU>UcgC7?UpCop!u8)ZV?sq?cJK}U-Qm5ZARwTC`T-Qa84~K`A_blI;-Vs zITVZHp5`<Ka=lpWopp5pr{`dEy-c+oo67Gzr}K9`uPYq^jq#J#i<3N+LElM!d?jsh zn4__7Ly=XW^r<D-jpEr*M{HghO|YfGT0!^)KTl05rH?OrSO)aeKF!u$vGiZJl|v85 z4{XmD)j;sC@R4QIo>9YN7npP^ub67S9$()GX^uoA`L5X8K+p}$Kc)`eVQ36N*_J61 zRhav^8j(0@Z9(3=5&%0IQHrobo?mH30QdBu5}S=~%3vDhzbibawPjCHbk$K!&^7x^ z_7KX;-`MP;851&RKwKf9QnU47O$0jQSj1j*Og-TfG#<B}K?E5PhRK0u1l-TN_2|>r zU~tP^6LamQ;dvB|l-G{xw8I{C5`}tAuYfE44!f2*AE;LMr)jN_%TT_<ri~sAmNV}t ztu@deNb1HI((rOGM2-AHF9U<MXuC84(s(it)G1(1b2l7!MI7i-qD|P|VawGs2%40( zB|aSA$YE;NGrW!|t6Ed6sAjb}q*bAge`aTt)+v%p=lfo5XEXNw>xynfiZGC<v>jN- zZnfH0<dZQ@ohI|p5TQtw?b|pd><-vzhZ<Hwb~x&?Ofyw;R>bqMjhKmrjkcGV!y_SI zu{V<{8!P??U4|z@6eW$ZSkuV2Kxy100WH&_u2!6DmI>y{X4)v5??hwT90kNNNX~ro zY)?W%hkjMo!Ni#Ta+`pYEQ{<vmniMipRQv1mR1oh59fz+{Y|dpOo*j_qUeop5d;MZ z2U!uES*>;lpHB%V94?l&23T{3$aVct$B4Bwe1>;Tc#1;95C+*Fjv18mGv2U2)4^Z- zTA5&Zsm!eoD^!@N6=OCCq(0AJoW27Po{1V-1&XQx;Y0IrFjf9LCbLPKAI<Ef**hdH zvs&h9ulX+Dv*doVp3kN(#SiSYo)EQqgE3uMm}=vq0P}^=^81&+oP*>&qBy)a#Ef3( znnvW?m5Hw<n$;ijf?4`2hzH5BG2%-rhAl)~cX39=jSItYjA82s5K}-vZ#_3NA4FbK z6ACAvb5k>+X^gtix;n$DRM5HcONB~{DAH-RyRBP+XJl8PHT$}Wxaf0&99c*yMw)nV zpBlYF&PB_0%y)%iBVIYN`j`!@o|)9b#gj^`nRbqW;Ev)&g@!$8qs;ygg=TN5=n!3B zv~ZjAZl?02TvI<Dd|_ImcbaFnGcU@oBDK-{QZ0dn@^Z$i^tfU)Gb@q_0^21tJCLAW zNc=ymS5L2Cc8wnV`DW#ZUHtTs#M&=o12Au*t5ImbZ>b*H>c>bEzR3^7v&ds~4_9qC zm2*Di6INpG22RU=U&W}5YcO5~oE`x(gB{{v-Mt|#`|A{V!seI7i%skI>uA8;FV1$= z>t!s6=e+%2BkhQA_WWfX`%*K6e~LWO)o^n3Pos7mpJ>E2L|uTf=AY`aHX8txKdhW% z$@FGrZFb9(Hn!A9IjMa1r*!H$aUA@>F_}lNgHz>(HHaJ(JM^k0^!_Ts?#ef6p5v7h zNm2NWVHW(wGggL&@83ZQZ7_gqNJNvo-A{&i>pt#EcrG!#i|1FG<pa&})A|N=hf{oc zv|mU;k`icSs_q4T3UrqEAmMrdTZZGr{m_jIaK{b>+Q692I@Nr&rV2k2%G<Ldgp7@( zx*zKJQa+sl_-?HKYqwFSwFlH@83|ZcH!2XZ(wvl+x@WMj;IRzSiD88PqBVSb(yCNg zoWL*<<5QQPsBzNw`p~}wi5q||E!MwI|6aaTN^kTu^O2i*pqBW|JTdzWlO0EeDa6VR zYq(2B_{MaDdepuYJu9~hnbOss4444anBavxG`Fo_;lMw*dS~B~5_u)C`Xz0#rE6Ga zl%GC`iRsW3_QHK1D1W^(WWKE1qT7C`Kii5l6gK=nFNuP^1ELPO7W#yd$Y7o~#iGt* zt<v?Z=Kc%@M^tTca`DLmhgO15o~2Z&GS-$?O8cKw*)f(JsAj!7dAfJP8uK2kE)=ec zHoe!7a{yeGI|z$0ITpFqPVGj(Z+CI|7)MCU$jyx{xh@!6_MG(74_OW>ot$8hV#Kn- zJOk6@=9Qh5QBZbF76GA13F)s!zaAP11oE8-p(<0H!JnXrCJ#24-Yc_q!8VY#&VeHQ zqf2DuhJOrSE#X6|g<<P+_bpsC9>O?E72j=-sMk8Ob82XKEk*6GotChN^x+BzRZhu# zd~YTW1N?2UG404Z1U<aGd>3nW3-UXlY~=JlbofC%%x?ND!k@?M#^v&L@YF<ZxCbY` zqu^!pkcZH@A=x!U!EMpS(;!X}fC_@*yf>O1{Z+Ew40TXmZ@By~S-YkH+oU*cL(niV zK`G&~B(@HR8Bby<50a4hU!#bSh}j6`m@3x<_q@z;(UK7U+Q;PfCHpLu2-RiFefHkf zw;JxIK0VaM{OoC#D)9v#OUUPV_i6tswAE$T)5di;Ts~QGXzPrfami@5xaP%x(*T(K zKag?mRfxwf)$*TTFWNrI>($38TwiRedbaj#dVjc<IlDf&>gQC%XsTeioYwi=eI8Xf zWWss<vhpsVoFC)T9QnT+gDV`z=Hy?lY}8_LswN6jZAcb+r_GLE^l6J1i+*9KLI5!$ z7$wd&B^}{KvDs~}MeE(jZnr!#AC8>k$C?6^-p2hTp0i%NC?tQ69<JTQ%?yp7toOn{ zVvh|@v@F>0kBV!&FH>VrQNVItBv!*Fb@AWjpzd@Cpm9CyPZ>bBiG!mFLoF|rn$9?3 zcNCS?_8ql}?ct;VI!z<7n*E8$%xQJEpSi=KoBtXc7hJ1x#d2h8f)5m(=2gChDelQ; zTq-(*A?{eKP{y6181B<e*G^{No$l4*bmMKjWSVV{xgD7(PeFu@R7-j;7oL6`CAQtw zQZHbdGs}{$JBh~{fBL0a&|o50BjuElp<NaciN3fx<>L3ffRnZp2j3MVQrn%eum!f{ zN2lYX{O)gHOD;qsf(gfDVRF4r<wjFHfFB{|J*WPl-&mfzB9^9+lFO1LtIZn6hhftg zjfh@c9SeBjI2*994`=giQ^o<{r{yJ$v@;0M$w1zisn=AB`t&x`SkZT_Tx{0GWy`DW z&?j0l%*G_Jpvxyzes}LKZP2_xp$80Vzpg|>EkJ+9b-x_=(RnA$jfojFuAG3?0O3Y6 zCtC};16`ni?(S1%aiDB59TjP2F1TjqW4KuzmGCKzl!4wWW$@2JX9ecSw?HTEamSx} zZG0y{tR@d>5V0^<ZBO&+vz6%>K|<0T)m||?_T<n_IB6B-%@6-g1v_dC5Uy~Xug~jt zv}ndIMmJlewjo=G&Q+*8Seo*|8h!#266_o9sAV1_Yw}CQmRmqf#+CB}t%jxH5|X{a znoTS7LC+IjEIu2XWfT?BSMq*=PWXT;vJUo5uzq2PH^3ltc8HI9DL0wW!Q+?}fyEvy zdI@x%&o8{{G(b=d<f(38hu^v|Dle(c%(yAwH=7>nrDd8>$9~8u2i27%kufe1Ls1=) zIM1VVL52^LbvfoC^u2%s5+cX(pF7#}_XnRpZfDN#_Vg9|d*S`QgrB#fANKSceO{RT z^>=~$dV9X5E9&T0eEx+ms+r?HdIb6X6nE9pbouCCw`{*}qL1zLXZ^dezgFLVuTgFK zy)l1RM%T|r{arbJuBJa#Im14BCG~H2eOui=cURTAKexCa+ueul-EI1}Gxd5Fch%cp z?ca~oo%*^TzOIRHs*LzUh%3ODwI}}*{KbY!Qp;mCzmcs|8z55;WWO$ZeU6Jcnv%hG z%J?1^VxzZ0<dr$ko=3zJm7Guqi-}P2odjYEBIwEdDj4(|l??kHuKYP<WkcCc0p-hi z!ORG|Np#%s^WhG$hEo)(N7%;6knLz%3q*LblRt$eK}JNb@dG@A(8%@t0x?mL*WXB- zS8q?^&29_5TK7VSz9F*Uo57!tYr2}Mi5%J5^LyA40H!Yw(3=mlsJF`_>q?mnxpv)c zJk@p~U*^$OMIy1-gsQ_xfx6tIx4T#t9WF=8(kM#EgaFP;BrA+;SYky(>8Jwyjp!6F z2!cx`>&-r<V_zQdBa-Qd-rjJS&K!CyiCbwO-tU&yF#jVi^Nl)MTH<feiOyvpEHmk5 z-4yy|*?G*9|1^+5c5bd%+lo#)Rx4$cRKUk`H)K^>X3i)4R@$Kb<(_Dx_HB!i(2hdd zORv}6C7meKacXB#|2`~)Sk1&mA`H-`Z2%M-u2SUn6H=3afw=t09o1bK*)eRA1v|-U zG%;vC!HcEu>UXGO0ih$ytF!LpBJUNUk3*=%Sl_h-c!5!sNO=`~(?*#l<nDtG<X4df zG`xO6@ph0PAPg8vw*0~EDjov4AF<Vsm}5AcJq0tu;YCt14o?I_E*NfSIcuu-W6aKo z&QedM_k!HeIOBmqCQBeoEC`VKg<wvEyGc{PsG^Nb3iB5SCr5?K#)H~BMOA>H=qBo< zH=O)QKe%?|wa%CZ%^3KGo%*bq`ek%DN@?}A>>~uGBIyQ+($dzzy?ok`9+F-pXnPmp z0jbUQ?pTUf>DMhrf9Q|l5Vq>T76X(@M+s4wP}Jl=URUF?!~T#uqRo=S?1e;yIVu=9 zbx5u>o`x?`0!iOQr}*k5%$XpVonGhceVGq~EI^(^g%NwCmE&=dv*(IEgVw&s|8fhh ze2;|+0tSndjrQyPgHSYBC+(Tuz>aMWS&3QX?PHUF8wsn+fL{^ni#D<5*sO&)WLV85 z{bO4=ZW{~M@X^G*X&sdty3bSYUA7)<xte}0)7Y&MGRiYvY%UC@d5Q<EDf57j4oHLZ z+Y@ltTJ^wFyI;>@Q}EtjlbaceFN|tWoWXj^)@xD=>hH;nzkq%>QN0wJW-t)JRmj*9 zvs5Mq8?Zu0`U_K0x(%01tiG;?;f9ol#C#YYQ>vkoGofrim?jmMEQn!YpQr^4o2k5) zZ7vF=0|I;A9e6gNXziHdCuJztE$pCslAZ>Hq4*BfI;Iydk8nL80_p}0c&F|e;sM1a zRqZ`&rzl_si-^0wbF2&ZZ9Hff4eR>tYDiv)BuNWGz3l-h_)XN+Zpd%99MGkv3o9Z} z6laOd#Lno#K<Yr*Ewi?Y@)4UTa@|r01Ok(zMdxS+%1##QQO6TbwyhmM+R5X5xEd%s zyn=OsmKFTdybsN913jV+F-VkoO^2lYkkl=yoFDaR&~K!)EyCtcD3+7%p<Bk^BhNqj z%e7%p7<A;$W?O%||5g@-nM8a5QK+LQTLA$8=^<3_PlxAT+pxuWOYLCwVT_6ZSJpr8 z04GM{CU(LHk3{B|#Vd}1su#jI(7Q!g<eO?vNUm+C-Zjcl_JL{Vl5Yoo&b{OfSyAY@ zwEV=7IV|=kqnzH~KAbU54<$bu9BX_!E$O0)>!t_B^^`7;7J!88sr-M<<$nekA?K*_ zX(|+^ahk2jF>kkacMoL_jGlBnGfii14;9pMJUxQ_nza}2TmN7u**R$=#JH1(RNu4_ z>X|UUy)u&vy0ah&ELt?BPCK41f8td(l7M9Mx2C_u9dx}7QEJ}iI)`)oJk9yUfktY0 z@O!I@uEB***`l`-i#cms_b}ealmrjj;y-=H0;j?)WrExorY}M2M5EI?Hln09bR$m6 z=AiUAEr_jHKB3t})EQmiWEKI=Ppu=>-D}2HJHizWR+c1=DPJ<PvtNBcXr6Ew4fS}e zk=N-g=Jlk~N-pv`{kwM@U!jg%Ax6AkYMK1-K{X>?SCyt=D7Niq)PzPWjitKo5g|nX z4xqgF2DHaCE`P2|o1fJ3aIc|+0QCZ3eGLZa>XW-KqgRbL)jfq%b%ihAxC&4!8bC1J z%#Wl7Fd9o|?>`;(xtlL}U-N!CBvH0Ff3|P>9Bzt&<$ysPibPAVE5T@uJBh7SfNVdm zl<x5!ZV6=z@uxnlsYg7kKDa5U+Fu1ZMq~jyj>AyldKT_NziiPC1dIB0YHB~l$Kl4E zoW<r*iU6_mIKrnFYCaK{A*v(_v$B?{7B4Y}etmPtUVXH;!UBXTYihCDXBqX)6zbHF z2rZt6J^yTezDW`2Fa-BrpOJ_T2U=FiC^S^{(kSrsJ#nS~LP%x=3lJbS3}c3@MBL$Z zLqq*MJ-J)h-LQG>c}t3PW_p4+nZf0%|A;;T%_WK2+qy>w-iI_hvy_hmQiYegXh{M_ zbAG!wa3{uzg=nDGvEW@|&oH2o8}UB|))o!7osZP?!2GSA5f0zD_8F)M@>`=Sw>3eP zIj!V6m;$2~2YM82t#I$-7vmQpiFCw!^Fe197;>dG?2aQD5aSBsdnRZO`y3BvtW4^f zF!$nLs}PqYG<DK3@x{4w{J^S()8F0IfN~SC+&h-wgteM_f$S;zHc>vey6S~}Wu_wu zU^O@$4ig0+?>uMg^MQy{*=i3aa%?_ve%<6aPr))pi%?CPBEd$js4P-)b69n#eqQ8V z+ja!girh|6lGr&}`X-4>BPuKo4+dGelkv`xBx&q-s_8-+{H0B>R(gJ|IwN&CvmL4C zGB|W}{}z{R>=xUi6>8``(fZ$-*n&^@iME^%*~EADgWvJZ60(CQS69c@Y~)4iO7JfX z8h02Feu?-%@RbQI8t>_;=tFRY(ts?o5?u6)-)i68iygY56xk@Ath5%R4YdV)DwV_{ z5^tQK`yB=}@cvli$X3xHbW*qw@?QGN_v#VELk1rwbcg>@+?>wS{VB<klKoIUt!*og zj*H)AR!D~tlz02Qm*``bho%w(zcV)S7Yr%C<50o>1IS@Mb6i53SV$UAYR+56sB7M6 zH=&j_{S$dZKo2V1#Q^n!e0*gDQ}cabcnO@08GwF?UyA+`UpZi<&JjpqqJZz}KRZo~ z^u3qb;4Aq&INW-K7BoeV2|49f4+p&L6ZedL=@#c4cZzqE@ATC_t2-1t*dUZSj-vVP z3H^{QfU^iB5O`3;rb}!E1yZV&=?IOLDP=nzqEB<sQlo(3%Ou5N60faWWe9R|IxpB@ z($tp*q&&nz_$7JVC_nCdq!tBi8%?A3z>PF`FHY1oq=;I2)<4p6(E*9&c6(XFR5@tF zCQ+*4c=qu3WUg)^G4@K74}Hm^(Lk%qO0KYOKF$}LQpwqcSMwm${c92WC=6-nAvz5= z*>T{y3}dvPX6u4(gGE_8IU6p7v(>DTqN(;!E+>GVz36!culGG#Vf2Qt{4to-rpoN= z#A3p{Nc!tnNaDc&8c#~lm;UJ?_HFTh=>CXia&?s5RZsLgjoZgXMpS6N8^tbwuduh@ z$@6{iyGddsN>~p>_Spq*OI9ew@7tY~OW5{`M=c|Jg8E$uJ}Lx&kMIL=B2vp|#Qf;h zWdRQggP6@}T;^)JDh19MJhFcw-??D4T0NiV)Eng>>!Po}PYwmh2LCj+zlYWvgy;W0 zt)4Bk5K-h*#mF8UE(4(}Tb>h0xLjDS{A`6tVH_WWoIH!aSQAR8L3dC`igvIcGGb;T z&kL`oro=Fs1NxPUCd?kYKimMo?~Lt;WPEBJ)n7cgw90)kM{^~ZV8XAUqF{duX(iRu zaL|H)X((^oRC8*xXUi~o^u~wMt|^qzk-GnHq8j`v2}GlibBQIE^*&l0b|_t@n7kFi z)W_wb7M!LripEuzmUIxKLAtZ33fS-W&qL)B&?QER2_@qor)#RU-aPF@L}5HzCK%fl zz9p<T38v392H49X3GN<}!p|av9fP@^B9r&#zZ{8DhKY&h`kkklkI)!0!59tilYQWg zt#D|vG8`SXps6!J)>3mG!nr)1Cz2*)T%vKnuqXQu1>HRrFNl_axw=+D@spdL3c};t zYE`4kT*xjx)LNhHmd#W4RRYB1s{x*MbEk0n>0`bxa|AvIcfLXT&f##lak3=W=Q4+V zAxrIb*IAcK3w#INso)$N>oA<`+xySAciNghSrq{6mWd<)f#1o~@$sP8CHxTZx$veB z;I5<_2PGo|j!XMfC03)Z-{%JpeVB1t-)*XJ3IK<mB||k)>19|n8`tz@Pf?*wBC&qO zYA{!Vt<-*NJ8;mZ$)@T8{VgvOD_)$uS~qiXiU})NfDR-#e!K_rqkzn9{HQRWGtJMj zj#T7zm1CcjrAEa(WMnbqT&Ur-cIauItn(mgE;0U4C>yDXrtOb-6kPoxsS01Fx(18J z{Wf4Yd|D4b2|zh7N4Nx;`wL}TeIDLSe@y%4N_q}B8t~DaVv!6SsYKd8uZ<n}ueOE+ zSQvoxa#AVE(GB_*HFq<Vfr=$Z%N!+j;+ePznr)ZDaLQq7D6XK5Yr?r136D472CcUc zp`lyyd2-PBJDWRc^d6AX$Ri(}SqW!ROe+k-qGLGDJjg$52T+_dgVR+iVN!`u(VX`G zeV~3%QA-^>rDzP*?t=u3AjK-18-cGIV02(WgJN5{1{DUN_x&VAxs96=5Qb9Iyrk)A zA8ys4S`+WirZutjp$gsGd`gF_+dN)tu>#*yjI|)D|7o_S*=M9zNea^)05{z>SO+%H zDvGjYNf3SpR|_R9)xjM|jgB`y7_1+4gajD~@#v4La_nh+S+4AhrpQl~??`QIIIaAp zYdlIYlYqeS4pt$V0Uw<i>Cs%Sr;Yo%QiJQWlVCy8FKBbet`^v|<0dwgWSoUj*hfBL zjE66HA)Nc8(#{@A@nmBv(W=xg!f}H0OdlR<beC&7MZkh{9|Am<BE1Pu$R_~3f$S8= zeP5GKQCAzr+v6@v6Hp0apsZ=hkf1iM(F%ZuX3@XjjQ(J2{scSfnOYPPe(;mdvWy1= zPL!4l!WD2dd_O&!Er;nkW^LbQkP~2cWyiks)-QA4qyT|V2aT|_IL;&GeV*|ux&R7F zZeJ}jF{8NHQU3*hG)G{H276`76F!G1ak-yGMFeRU`c*W^yYqu<2iAD9K{xJ#`I5n6 zNm|?D9N>uVTmX*ejgjD$le{L}y@cxDI9nE7#A_Jh{`z+)$YuXS6jpvNteZN@b6q{x zI?|1L^?$5@C&j$Vzc+OGm(?!Be@K4$s^&IGy<7*Zpeoh2L6AWbN?y2ftfay=ra~=} zULCL!wlmh`lsA%`AIZ5k(mLCtMAJPUH5z7AjY6tJe)qF<zeCe)@B+*Lf{Ms9Rg31b z*Jr3){x;-t9)};$tM6mR+_?~MUKed?p2vZD6HgI!_I~AVtTm+aas7!C)7D#YVm1qi z@%5$}QEA#L!#lZbQJix!VJvNdDROUv-ukpCYH114EZSrZf&wAhC37y%rF9ULeAebI z2@zR$mA57g(dTBK(RBzg9^=<8eh0$R2zpn=Nuuev6%z4$`GcDvFYek>QQwTF!>w6L zYs^P<76S#I*Iu8Mvw)MFL!ys}j1TBNRD+cdLQqq5>dd<Ui=}x3I2pEwz&mjWCykpe z1pIP^aZ6JAmcgY*a3}QlL!XS8q~lZ$6UpM`@Slefg7v{Qqnknq8v3;?$Xld*!vps{ z>k}DO_r(S{V9mvci^nwBv;*8j3QYO;4hPB^Ct<4XwA<dkz|u2qvVo7rNF|`4TXt}r ziCbD?OSMNWRRxMG_DwCWJiLa8)Jgn|dl^cnbB6WhMQ+><?v8&sM4bAoXn^!^EnV7! zh0HH1|4wJ49C<jow$yxr8HcPw9=56$MvJbY{d>M`@!Q;s3cg2DN5$3{0v%pq^O!7G zjDskZoyIj090c#lg?Q0`<;7T;)xB9msZPIFmZn>m2NqFbB>n``ht!eqyII|5?AvOs zKXH7W5NBzJSfs)tj9wY8!##Q#3hny``fD-L_7<F=Fdg#wit-HA{Oi_LhaL?FK~vRT z@?p2N-PVNoD!SO9R4jl#6;Au<to?c40>5TP^8Lsw8AYsS2;=q}jG>wU(hPaXqlD#K zophlKVJMs#FU06T9g~ZwkmpR=aI(J%atNnJyWbDeg1KHCvLxcuFG>FRUi`JiE&Wd7 zbA=EC)pkKv>b`JdxYhSbXJkD!q1uwGbPV{V@nYLw>5b2{0SFjVFmI)?%U<`9#(e2% za72(MnONcQN=8s&zsI;A5TT$At1e?h2O!hbKgpT~^d-tDe7OG5F^e%iq9h>P!XVC6 zy5E@kNy-&HdY`ON=g)!V0Nu~ko>0HRn`<t)hN&oP8t#mmhCF@<^zwzlz5CXcwWbU9 z<={J2pyt<0;-A`ysnw)dKC7`U?YR(|n!AFICl$aYDTw#_K^BzcrroP!V1}G)sM+zM z9mpu7xn#%i^Ur4tFx#PQUgb}}6V6Az0b`M|U=2P9$mg9X#$bA;znc{~OXc?OFA9wi z9_}(4x>H73z}(tR7G#$S%!gHe3=JEPEF2MbbCecD@WLUI>-4jC8n)lAAJc9E`5i}c zFW36_sAeTdJuXA-h5{;mxM#|qeGnf*7?JsBA)ed~2Z3cdj8H@%+;<2^?eW^AD5GcI z*9#vHKzw)Q$V7pDum5-gNXIHqzr)=@nu~Zmry;-We>3D&s5V0POH$Alk@j@YAA`V< ztJr!l?(4lu+F`~=WHl&Q()Dhw2*FlRDskC9UWlNQ1l^n0DNhdx5}Sfe;BC68mD6#Z zjkr?$)Aj3k)>B7Q{L6^AUDIC2AghmFfjK2R-^AB2#6AFToT!f4)3FhZDHMheBF|xg z#>&3tu7*ubiYYBXr(EHO1oAWXHZH1j>(sGjLu~W%=C}C?3?BG7jp^2w#V7@AD-KXc z@vW;wgB8#n(5FxE3|c*U6GU>OlGN17Up`rAIPkD=6I6V2bTL1Wgr3V3-($>gnpBQ{ zWdBkiTG=}fwd9G#WBr?IBftUJ(VDMg34#*h&6ar9f@YJIw$7`(2CGTj9JX9cYVmr4 z6g}&H*J50!td;fq5Aq9+ZN--PG*cMBt2^vX$+z_zBePV$$OP8&)S!KB-@NXeH^II4 zo#@*G#B~2x!BxImojK9?e>GP<B#q%YwTC;kyCJMvDmBNWLPl%2O-frlLNAnlM*}GE zWn~kwWV3{BC|LG^x+3>H>(^FmhDy5k<1Uq^6PlAv?Fbxat(v%#*OLjpjo(zp|5bOB z(p4M7vj0bg(VC%8-oQ#X<?BRK@3{8AA&yPnVT+tr=sc`WfDMuGzw;7I90|D<m{<55 zlWHO6VqHF{>$%2<n}2h>KG!UNwsUKJeP_=IK|h{AM|#9eCLA{77`}#Z%UQn_mzUB3 zonv`!Bwh3Tn#L2kH*<U7Bx$uIZq7y#=28p1S~2IsG!aE|6;J|L!|$iI5i3nx{Wk-K zY!br{<X~tSEBE04W!&ckND*om@E^(_SX>u)LWx&8b7|M152A9eCjMDo>I}OxB`;+@ z?oPM{DYDLH>kmsBg*wsbW!r_~7e4`Q`(dogX*(s3OcIJ!OcB=OWAkihnzgU3qA3?; z6L#w~>)hi694-U@R)Qp+2jZRk5hMpevo#dctefXZwC8zg8E3}wdI36rKuwc!bSZdd zjfXwoHg<1x--WCYr{@#L=aOH=1KkFn`r$IW@x%L7{k3NW4r{z}Ywml7;C=BU$Jg)8 z6@AbxNd&ZiUrQz!l8o%lp1)^-Cd@yj(I!H=$C!{-*d(6K_i?wg_K%aX*5POWX53M| zT4fAuz>EMn5M76@_o8q+B*aOli8wWn0)XxNHUs;BQBe-Kc$t<NeTM;?{S7%aO@O_4 zn6}p2jU8qIQEP5)kb_Zgy|uP%F{6;kYGa;mank7@d*Jo;9w!<iHW?z*uRy)$Sgj7z z_mTShUIqUeaEBV6F}uNMMLGmD?*~$`|9{Fzt91e2&QhoZT`-c>hmuKu1c`gZ?<b{p zSmjERfvZ&V>{g#s;G<L2_uG<t4n@`O;KH+u=?QvSFwK%PKIN!CaWlB}ksw$`_t;x| z&01wQ1kyr~Re6RiA$OCZ3l*r_qz45(0vjwk^|E!rGivTk<Qotwpi#neBG`M!9J%pQ za$DGPbl?8D<v&y0(70NND!C{KR_o1Jl9Xm?Lb^7wvo?!_HlSGz8(rc6ranGg1sCpg z%T5=pjLgQPD{6VQUSl5%=7sFqiRc3jU&6X=k4<;i*hLW4k>e2BK=U(NW!uDyihRx| z@I~18&<Ye7#TQElo+FJ$h%I=MMMVfz&J#1m!Ndc5P`?p9@N?StOvjoV2a|L;JdvKx zdK`OmYb7*K*;d7l^j*VzFg+Y>DW9Meaw{<>>ZS1AQ<vnzHP>y{?AW!I>?4akN`{D- z2k?T4<+w4^wnbq}TCFr4AnN<!=YLiejeZEzS<HrxL0^<kniYdzsPL@ZC=Dom6lRr6 zke!CoT>~0;NL;Fcnfb0EGU>qEVTE7km;VOLEc@F#z-%0<_fD9-L<)C05+iyA3WPX! zPacz-XC=YHOn*swGk)qK?2CkKEG-nK!@;ej1?q(#BTEtA$L|{(I?w~hX^l*tBPp(D z62oS9b3<i&6y@c%@PS7!-~DefPLQVonHr1AZ9UZn6mkA)M=^=iB8iYB9UyXBp*>7^ zjp8XpxD~oy!&u{4cdD0%w8O2)Tjb&TI7;(<BfU{}yIgg&TsgurDoLn#f@7tVzh&{o zrP+!wal3r|@b2)_cC<~xMOO*4HA?4lUgMG;*yZ6T@gmV*F5pWZy{1@+2cF(p(I@*F zP&c@{59=~Vp{xeQ*W6vHM><H2TTTIT1RsqK>>2jpWWRhotXd$hktdmv%0)Ng4S2HX z&r4-)K?ox5mhd+pKbIpRnOjTsr<kxFRU~+LNy}@J?Ui4~W3Q2Lv1s0e-6E$rdbYjD z0?KA0cr-LM!rCbb+%10Fv<`Rcfk+UJ{{tL8^!UI{EY~1odqp7$5#!9AaRwb`xS|#G z=DV^Qowk)UmNApyyT5ypbHNVPn?B@yl~&YgK%=QOp3=-ZS|ZCJtN%GNWlx^_K{6>` zv=4Te`b>|Rsx<??$`u`9gU!FG=vF$#h%4}y$z5&JjniUX)KXgyc^{QMC7GZgZL5(> zp$9BV)o~F|HV%9o4&~tvNL9{;+-fH9#zng8-Qn*6uhVM@(5i=kPk9<7y%t$S^?-*c z)?rlrY@-J*hOWrx4Qf1gZv#pfu1fqo{@=k$<u46{S(HY5>GCE9_g^@#W&t%sSq~tL zkv(<P=Ue=mUlQhH`B_nZSyw*KGwHaH1PW&iG+GS-AVaf6RDhdmzQ{<Ng^lOx)&!>% zVZV2V+Np}fMpAGo%22rkSrj$>*(*_K*##J|^ZQf`(ynkG?zQY#(dB1D`%d*`NrucN zgAK87+H1d-Ir<9-F|Ay$;!*QaXGE)4qt=5{a*MtT-Ja;LWd)q|WuR3{*)uv)$H+a5 zmsYl&R+6FNM4Ws(Q4{6faSUT;)}c30YL2YbvfHiZ@5W?!it0wG&LA_Swx%^<<*z5+ zY56!AjH}5ojH)aJ<O}CDM)3)zT=L#<1;bEq5yvDL&@#Xg7_^)pGycp*M8<NWcZOjI zMpla59lpXmvwaoA+{qT$<XLmV*-CO^9%YUoX<iKe0Y$J9XjN4+5=v0ggg=ht38YLn zF$Y&i;WA;lg9OoPg=(&T4i9v*Fn!ggE7Dg$Emo?iSpchml6WtJme&T!%@=!hVVPgb zSu2jb#v2$IDj^`WNj$is1fE;DMMom=SC=N{*y1llfa8iTt45MnG4B-}USmlH^22|7 zL~*reag%?3LvJx<eNIG8*`RjM(_hiN2?<FWa_BYLPDXv^$I_JavXh?cO$Ad>Gtp?j zG(wE8S4fW9Mg*&Ia~gl#d=U~~(VK#h9`S&^?(n6dt)zy(sLo$xhk^VLUqao``6ipm zA*GNS)W>=dVLS=Ve`R|3qB?}9LD*RZAwX|Jo2Rv!-WS|FKIavX*Z2pYP&(D{Sc&<9 z`%!vIhxgPb<}r$XgVHq)8XvDDaT}3--J)Q+mbR)V2beNW4QTsW7fk;!*di@3s8Vjx z!Ryg`23yo-e`v@WmYZe|8YiVm%R)*BTh@(vFovRQ)A{F^K9uwzQx^=0sH}d^2qH%1 z7PQr``=6jA(b{ySP?$7+n|SaMa^d!8_n-&eAbD$%NAZD%86AOT{?)3|dK5btZE2i< zlkF7s>gD2xd#Nx!8?_lyeb(D~BzI%ADXg(T`ELtBnpVpY+hR_=plrLbY5O20!@Yu4 z-#tD!T2@Q_q>J>3eCortYPmO_g<H(Ne-PblW)mAHfO`b0>HhDr%B`Hv#yQmNf*mT& zwgnIo0r!wxOp`-~zV4f%<XrPotd_urr8CI2#>Q=0;ec^DGljkb$LZ!K+F9D_;bT%! z4E1%rXrTs#y}2nK4w&`~GJh>n&eRM(blHwCi;b5yx<bOvTP5awTw0SD3{71v*M;x@ zWVBzz+5M5`?z(8lqoHj<#s6scsNlI~RfW33Q)&FNx5Sc>;eD+y%-`V=ARHKTy;<!H zyv5Px(SgVp-v=iH9_S1bNL0p)DV2swi`5{{Tc-R3%1p;c{mPL58Q-*L(PgU=?BvXw zi2C)CNs@@e>YrTwLY7y&+&&Xj_>-b40-{WO2O!`P4GE3`@GJPI4Q(%m1JD}k)c<i3 z{L?0mVJ`oD3NOx|dX=)jtB7xspmUpV35?dTY<%xj`uuB|WkQVlV>UJ6^|Sav^Q@(g zV=J+X#wSEq0*(zXi!>%J;>}>jP=RMJwu}wN+(Mvm)Ai21k4;qIZ3=C4S~h8#bj>Cp zBRpzfMY^!SM$!7Pz2N0rJ`>ja<0RdtAj-uSL?MPPx-#_*C1pg7zeB*+4Ux~G@&H2U zQAmBm{wSn??9nQW_Ia<Rn3xBZmxRNKR`KL?A~ubr=6ogMaE}ed(IUA%N4UJlI<ea_ zbl%FPcq~!}U6v7tHf*>j`+G9Sd3&p>(}sOXoMVz+(2fx<7JmqiZ!(z!m}Ma~ljw|U z&n>^m2!I?>_4O*;f%q5R#J%31(p6U+tLheb#H>C9T)ozn=Ti#z`SyFR7`$Q^0SbV= zk*Tr1g9AQlYwAxuR5QPLq&1!M4`GzWsa?%aGO}g#Fp4d&Wx|~;E}lnFDVYJ->d3~g zTq~x=%Qh41?ck3mlRtv`S9hHGPEoT5SaZ>l-x#RiRfxJg`3{K(#VW)(@0Hi$cIF2v zV4lE*&*8R_JpTy=?^i~A*=Sq;IW2LB&b^+42BYBssvqn+7*A-MFB2YJ@Ks~<rxHy^ z+-HT^>5wtzGqJcMtD4jG=qa!jtKdEgh_kka@|=oljXd4MHs>m#49Ld=2a`_!chie~ zi3;w)0O!qYI-0Gnhz{E|2r|UygW%`}$4n7stTeu1b~?N{e3ltpzpxS##=D*w!QYei z<QjWn{g*_f%H^wenY`CUCyn1i6XEVR<HAVk=NiTU>vb1DL|j~k9}o!nI*L|J1Zlk{ z?YKJ6a0E=BMi?3I9-fZMed{2DsDFyKp;5*;+8DP!@Uu)83!ojM*+%BgknE11^4FO} z(pAu7_8kW#njz{oq+HrOD9KjC<46Li&a&<_EVEOCm`&u5n+VNpqo}ln`~egs<0*nz zDG|vzt0F3*rueL9s)^^)tJLiWh%5eYK{Z7pTMr^F9Kjnv*^Lk-le2HvhOf?~ApTLi zmeHAsw<qvULWD0rX~|a`H!~ui%<GZJVuVASfu!N)Ubcr0I+Hk3`$LSdx!b?BHz~mn z6NA&Xrn2Si7}rqfo`=meA`P#MpSVl3&ycRqFbySEFnK<k`@9UWU<%O~(&t5Xh5tu` z+H*Eatf&u<W@4L~t$#Jfs$A1TNu%Y3jy9nY&Wc6&>#@>l;D;LClFnrW3>ta?Tf<K~ z+SK?9?`5mfHO(wHyS0xx8=-T*lSl`$@Ht`do3j)gNSh0*wQnaKbFF8Gf$>9XVRpjf zOtLxU8yje#&r@LKQ#Qzs{|NUsq_XuzFJB~caN1B~bs;@HVtq;ePBfzHgu>Z#k6n+j z5mu3RP>qAPpWv%HaxVa?-1Tz;OY9kr-mlPcs!hmuKL0Imd4;2%Q?O;doE3b%LhHD< zJP_UNOHg)dymoakXm`|vmlruGq_~2!`IS)|j)Z$|D-DxH+Hbq2U{yu1q1Zqh>R{l^ zSr|Dge2TXt18k*4c!ICC@AhRL1grQf&PrtutHU^D{YO|lgj@No_u?q*1*1;kwFlGm z*P=Gdt~?ZJI;GP+z*dBR3_=NfH@+>mrnzP9nFmU<S!md@=R@8QYU36<q8;BMV0TZr zX98)i=+z@Qu(R}|ZZ1F&+To*T);w0VKNgHm-mIl7KYV{8NqP;;KKQ6r02^^>b|j07 zjLkBO;8<;ROzW9MwA)7E9LN4VTAsIbo9J?R?1(sCsy;%UISQBvt~7b%3qT=YqsF0< zN^F&<?`<i=b_A`O^)p<|!xQDscKYn;kU*ce!V#z0N-~8ozG|X}Ak(~;{4Bf}3Ig#1 zw^v_%b@4unW->#VI2S*Ct+We9T#R-vas}wvv5{VdQ`Q2INC0a{!a$1xUrJ<K_dsJE zd+??r0*-Vko8YnmE1;@eY8w=tHLuF4BKB!=3!-vu^EG1~euGqB=TLV_!z@m}1>TdE zv(#hIVf>w!6OOj7am%GftewQIx4~V`vB1?^d9KS$7#XCFo|MeoE(SDiG<npY4M34( z*s+Zvq98V=zMK|T%D>VwNf(rMT2<xrcbN+xsD|2OA#iO+VH^_#Roe~YWMuiUWW!F# z3_HaksNbF_KXc@3sO4YqrPe<Kf?$sv&nBo6;_jRlG+i&l{^>XFO*Wz1K8ycs9sS4{ zd~D|I7}fuM#7opPd{d{y*OC(FjP8owEc7#R(QrqJqgo02fbK%u^dN{wF<;g}8WJ1w z$gw;>9DC((NJqvC`ML*mvUTNIdF{49ZNmRdg7)}8^B9|4Kx1xXd=C78+{TcsXVNpx zE}r)D2wi;^c_C1XTl*b3Pryw0M*C3ZOkd!$q-R6}A3p65<P+~<&t-x0Lr+DaNx=aM z=P$6@S`;Y+2pHEB`&P*lh(F*k!@f52OF4nhfIXV#Ebm7#Z7P(IZ2M`113+PmLY(PU zbUK~^FZ?75zbxuMAnZyly%xDY0}QZDwgH%KuW~FJ%r4(TNS$}___w@s-~*Qizdl<9 zaMvn4cF;?wizh`XhjyTp4F@;4y34Z(;koRD429&6rHYT}HYL?w>^Cv}Y`G&9M~;LJ zpDdvkru<Bj(vMTTf;i?PAJy|Wr5-P#P&gk+!sTX~So=-Xw8Cj`_-u+mVBIrPTh`UU znjZLh{~KJH*37&jyGF%mvC}0LO*sW5l_N2?TBZjq+r)(LHKZgoPBjxGd<aR#=+HKz zXMV1x`OV58xVRuks7R(p?u(HXyZFHehr+b*$^=qPJ5||m4NhM2f^=Q1a{%@o%taEO zl(z8fRy5uUQQ^(h{p@`lJDcONswl1AHfAC}_yLZ0fvb$EyzB7&VTQ|GT-i+X-~cw> z&+NrG1<YkzSc%fv!3nx2o=1szBIGzwxGrr>r}!>6lwahel~g#Rvwh-;|8<Es9h0D5 zP!+kfPM@9KfZ~XXH-yDAf@3o{dxGxNuunR;rZxZ>Rka0SQ3_#c7;Ed4(zlQw*2+o* z$l=<^vz((1)#d?~m_`m%*wSW#xn<WvtX^{%q#t&j<TL|gV1UPY-}kv(c(aN0g7}sV znI^E2!~<NDJ|7iYzU(Nh9&@|;EO8X7yHsTMAh;Z)1yJ^eU{K2><qI~%M#vo!LCoy( znyp~}e8i)VX7ADIa;JEy&yt;}Cj894DZF`61QMVQUgpGcN=8gln0s!{ewhKd3cUdj z`r1)b_bm5Kygy#Q3mWAeR;DAN>OF8{Dl2fuELtKCJhc{~Q~VlEP=@UO;HsJIS?jxe z3M*DYm!X3!1>1qI{{%u0ALAv1$X(ca2tBP*z#(T2#Hx&?q@OzN@Ws{Iw46f<dr*9G z3M0>MWqRw(<6G}R6T#yATNd8Q(6dtYpnQ)gDcVF)uG5VgedEGQH~qF8ynYsHEW2mG zLVDAYYzG;uK97zT!?i^GtjEvDZGa$*9O1)V*^()j@d_XC5W`soi8906t#G|hyj_z5 zu_kJ__WVT(&l_s{(Lt3;mD@SQCxaqjI^!-1spA_LJkDB1nQ<;I4I%q)H}pnCW%{@Z zVPtJ}Z8F0XircAX5AiQiehn$LPP5qjaI)+Y^!7ubiGvkKav#mBb$wXR&M8`Gv#=4< z)ydhBPy_fL&x5Cy65XI;8+yrHGmkU-?@fq;gF(|cvrct<b2GM^_gd(DPNB42;2>g5 zw0FtRmQ;!xUZvFB$Iex7q!buq!C8c^+cTw4FIXhg5%#c=Aa4GETj}gjWdJm~K%5t( zrB5@<J6Hbj3yQ3flKAr{a%3ff5e_he_HWxBJ@$M&Ecr_5qffyJ@YS=m<&e{-kryJ8 zz|%uVXvxPC8Z8WSh}?=*AW9j2*Vuthv#6HVyqaA46K;(H`0|7h>a$RK7uxkFK-GIt ze7IibrgdIJL4b}-D?x%_<;V7#)M5e|>33cv^9s$yu1c8qzSD*FD4pKSIi4f)8qMYT z>0f8D({<vhDDLgIT#}*mV89U>7r>=>7#PCd<=bG{d^A10#r=~5W#>a}gob6)CWPix z_>oJ+$M<DmPB?q7+^|0s4BeSqb9^bG{nU}506Rd$zl`;W99(t6u4eWG;%P`>vt+eD zR{1hhp+K2KQsxma(l0Ebj>%le3bwsD7>(V1D<>qTOpgq!A4<~UmmC|Z+#`^$O>y!4 zPEI5+y3=C62bi~}L;K)B9!Ho7;`i-1N<YBPb10KLLrxu<HN*JWGryFPM?oo!Z)n~) zOX@76$LVogIW!r*WKU_xq|=vo{get#7DkpeI3kMAa6443A3^x;H3dD2Czz^nT3*(q zfDh_J8-7&(5llU)_QvIjWQt?n3z~NrL^Tu4Ti>NaU0n<!RNP>J-X#O97cjifoaqG` z;Wh+tEy68I0!-jvn-)22B6O|Vta|Tll`*5>b}sOy4A)xH4M&Da11;HTJ5*=r%P@va zVGQ|<8kTvYNV0BQ|7DU2#->g1TB4Lm+RYoElm6I*B33TV`NJIH;<}<TF}O$4hZ7^z zi^fLlzkU@uW?^@cA4ci9o{Zf>s;<i@{x{Q<)*oBAztJk<q7nzVv0KA19{*wEkr1?0 z7n)8bV81Gw^9@g;|1*Ivjg%%(Vyv^Pm<B2TKi(!dI&9UD8d}-tvys-o+L{ebJBQGi z6OR&zG7@F)%*4>)WmeZI9+KzhgA}@*A<(77;`3%2X-+>dIN0L>b$sY%67n3Y=g#Bo zRUl^5gcYs%FRXoxl2GHeabsB6n%+T0Q0JP{jX{anjAWr)qGu@F(*s6~=tVRsk=wl+ zaa6oQz4+Sij@#botA&a#4HBe6Ttvh#M7#PQxZm=?)k7Cc<*d?j3i`tS2huTL9MwZP z(H<u$_)iwn0kVO;L`hvZXh#^T^xe+QBSN%n#^3CMpHw|I{3#u}62&ZVJ#9<jyU*l2 z;jT<^Fk9<_=XKqQVg|pnlRV-D{8I4NJzH0EdT0d$&*)#}w?0GaO|+}XLJXzP)IY=N zU||9Yn0rpLPz9TTM?)?DUCPRXA*O-&Ong!w!T7NARXvla;8Vw3%VziG_RVD1G;9v0 zJW^W6+ibr>eyBs*%o%V+eI9LkUl{rF&r*!Wk9G%`1l_qD@YMpgB4lgORU$30w8Te- z%HR&c_7)=7kdiF>GYU1g>tot<Z6ym3#GJ~*28NRz1WXD7hoXg^J8Ff82qY4G))?ha z{IwKYo3jjNqLb~s3i&@lqm31i1QRIZdC!cnMmt*cuc1{~jClYKVkM&u6MwNEbK8MQ zatK1xF8AD{7wFX*A-O|sC#Sc?P;tamXs2^jVjbMH;O_2I!-9MRnbc<NmvHRu>%f{p zF-&}x1VbeNy!>YO*)8_Up6#2;T$;NB<NH?qm}))f27^^9W4iW$!j(|%n8GSCaf#eT zTo96>Hb!XGAE^5Q23PLPaQR#Rf>H4iIw#frEbAo_Zt#3q3p1SGO}oKyppXzuW1B^$ zoL$VmHB=RECg(pK`xE!GTR36?by^GQd-&3-6~Epx@B{Cl<;1?9S4E(~E#T(Cj}Aw~ z9CuaFkcG5)tmR=`3rD+zW^_+pv=Z_ECLp{mY8U5B!=9xvPwf^fU^Dt|$CuRc<6^5I z1q-I=4pbbO+aUot97N`a?&we)g;RdrZp)<65!WJ6+UTLK<-bJ0^nVA+|1bIm(zFWg zaZ-|;r{j{l<qV-xNc|)0$2C$yZMQP3?OA$&VF{uyMal=X604MJ0zwl7q-pJyF1BXP zsTn}tfK}Xg+bTf9CvH~qY6I^n6e>zY_Mpu2SLNFM@v2v6!A)f|k)AEq`AFcKST_q= zcRj(a#)m+nON|e2+A$K|4bL+;M&yL2ZM-BQ$)U8MKn&>vzqE(G$<ss<Vup44!e7ZJ zWlB?nCg-9U_#yP2V*F1S7a@8r`EqHGrUN}2Cc&Im?+1q;&dF0r!Ylx+wdpp5QIz(C zjyp<Bpe%GdJQRgxzbw;SeQaX}6Upa~VP5NvayCA)p139*t-;&g&^2`#pMe?FM#VP- z&~XAfLKv+yojn5OCJ(mbm7t8>NyT1z^dR$SSPSLBs6D2QXll9{92)lapFsN};Aj&Z zJy!oytFx%vy87T00kAy<uA$?W4JxC_$D^Za<T`<D_?^n}6m(~zeBUxA%@swwMwfdO zb;KUZ_9RH70}`sTKL~O_K+dLo9qVDD+bZdn`0M;Ox&;c=u~Mh5R<JvzzyM1*Z1-2B zK|aEmLyrq=nNgh;v;l^<TfLbAOU7{oUh0$>2}e2dn0|kOav8^AyOr1v(uI8?A7p-! zC4U3WOoyG$eHZ1C*SC68Q)3tUih*&$e5xukMlD^dz?*Rnl~YCDy(R+uVnB^c&^@eC ztwgOQYG9bQtT;RM1R9>|+t^aP&q|q2skAjy0?fK=Y~pvRd1VyuhTlrkrEXIr^O&>@ zU71Xt!PmS08!Tmy6a6=3V68E~$R2`+FN97ku}WbXf9QZa=}VH-NYY!Qrt0y%n)5KU z>ISm8d&gs5c$!e@d-}|XUbTQ34K?a}7#He`Nk~6yVRpbR$DV2LDfhC+h2kc8r_+k9 zT+vT&v{Z;|HW^=~61S>OS-f9yL<5Ub#$Yv4lEAJFRZf$7`>aH+FMg1fz(V|n0LC}Y zJKS-%In`uO^mq29CwMfkq)xJ3y!AqC7GmMmAIjoXyKg=@!tC>eOId^mtz71~d9a8f zB6d_-V=)D;Dl6r6-8O|XwsUjQ%ZaKPAyj?<;(?X2!qjxKVv`d`l;AuzDHT1$L{hWU zo6xYa71rWk+tOMnE`>oB53M@MdzOg1#MUDIW^9$*szZaI(H_S<n(M^O3YA}6lH!Sn zTk5DCe&uG-D6OoVgSgbU1f?0h9+YW%ss9wN2;Hze_`33Lz~fzcFsZ+r#&m9oLNI^6 z>VZgxsQ5c+wJaJ`)^hCn>IY8(&T^GbXCOF7^d&D^Vg-vyv+s;dbj~g*D*o4%dVzc? z9_sO7I6>0*AZKkK=nok}U>+R4PtD}rF&AJlC(S@S$*pc0C=4v<fnl>PR=4iufKa>- zfyog_is5vX*kgOFFNm(Fc<c5X5o6dGu%tU?)uiR^<L<O4+Or*Jrm47E>-750U7QOV zTXaUTLV)&a;^{bm3!y*SMdXNJ(?llSU#_+WDkeNt4)US_1dxNHZ1G|F-~TUj<zRnw zmaq%QGhk5B^~i&^ZA~5xg)fY7nS|SLjd=4mQjgz*vU>r>f2`WBV0jEWYmu>=8pYU# zZJ+;V?b0|{is&j4yecpfMrbdS#f2|~Iw5SPux?-=`tv;~?P0tq3iyU&;-k=HVGe9t zP$HMBriimG0mqsKlU+Ec@xADXxlJ9c;?*7hV9D-T*&OqSQuRfl9!XvXWz0d1B$Vbn zVmEu9GbnMHgDWA5cuvx+hx@2IE1cy%vT;R1t55brI*-zNVTID5yN&dz0buI{Jc4-{ zFt|$H%SY5KIG{|^tq)|DgSfZ%3Z?&C!J1?~m<1h`8$2oU>`Bj}(y9nt1`l=EAtqs4 z6IzpbaG?VIJ3vu5|8+w?SY?$MN~hStqtH1+<qHh=zRSdqEgTOhg!a0CbhUnL4q^xt zw&Ybw!Sgh1TrShXwUfcEcf>5qloL6-fd@mH;|Em7abec?wu$GbqZ|YM>#|N7cy<c5 zo)~%#RL}@<jFTn{bxevjpg*>?I!caAXL3#ZQiLHW(xGcFnk2CvG~Xk8?bY6BhrhyK zbkW059nnFb25RK^Ri}0HHnqL*0@uN?h#!{E^GAXV_Bj;*Ta{TI!KlxVVu=!#ub9`} zy5sQi;pwR5o*e`Ug@ONOTyovA`MO|7e&v51Oy?c`Nq=#$I*N<$tw~Z!SNc~2^M9Pn z<_O?#bEO={vPG|MPhTHdH2ciBh4395kihg##@0rlVA$=?It?Gmj8XS}w7S_eeN-6c z!CJ6+OoO65jm^R!!M{O*26(3{l^q%MoaJz=<m<n38X_6I8lY4)Y_kp<+C2)0XVIOX zoxxbW$h5k@JEX4TPr_rT?ca}D^WCGccYGDa>K38GkE!_K(X-b<b!Oz7lIv=+Sf{ag z%ZlyXq#a@5GuZ5zKR+wQEImYJSc_~1Poe%8fXbXK`?0^ZlE&{etm6>7uy1XXg4mO7 z445V1Hl%gBe=^|Fc-Y%?@16A!Rh?SNTcc{{eHjp!v3qK72xIMy6AXVM`ehuJ)#1_< zr<T+VUgSSpfH!YE>uz%L3v1!omF#<;6+ZlR+1e`M*%>Tpc%%4o7R;T%0AU@R&8=mz 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 Qbj%LG{@#%Zu=bt**@pI7?*IS* literal 0 HcmV?d00001 diff --git a/packagers/osx/GPAC.app/Contents/Resources/osmo_widget.icns b/packagers/osx/GPAC.app/Contents/Resources/osmo_widget.icns new file mode 100644 index 0000000000000000000000000000000000000000..9f631118719736c5f6bf6cde67e71bf0eeae0ee3 GIT binary patch literal 227171 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 BN}~V( literal 0 HcmV?d00001 diff --git a/packagers/osx/SLA.r b/packagers/osx/SLA.r new file mode 100644 index 0000000..00977fe --- /dev/null +++ b/packagers/osx/SLA.r @@ -0,0 +1,446 @@ +data 'LPic' (5000) { + $"0002 0011 0003 0001 0000 0000 0002 0000" + $"0008 0003 0000 0001 0004 0000 0004 0005" + $"0000 000E 0006 0001 0005 0007 0000 0007" + $"0008 0000 0047 0009 0000 0034 000A 0001" + $"0035 000B 0001 0020 000C 0000 0011 000D" + $"0000 005B 0004 0000 0033 000F 0001 000C" + $"0010 0000 000B 000E 0000" +}; + + +data 'TEXT' (5002, "English") { + "Copyright (c) 2003-2020 Telecom Paris. All rights reserved.\n" + "For more information, visit <http://gpac.io>\n" + "\n" + "This software package includes many Multimedia Technologies. Some of these technologies may be covered by various patents hard to identify. These patents may or may not apply in your local jurisdiction.\n" + "\n" + "By installing this software, you acknowledge that you may have to pay royalty fees in order to legally use this software.\n" + "\n" + "DO NOT PROCEED with this installation if you do not understand or do not agree with these terms.\n" + "\n" + "Telecom Paris bears no liability for any infringing usage of this software, which is provided for educational or research purposes.\n" + "\n" + "This software is distributed under the GNU LESSER GENERAL PUBLIC LICENSE\n" + "See <http://www.gnu.org/licenses/lgpl-2.1.html> for more details\n" +}; + + +resource 'STR#' (5001, "German") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Deutsch", + /* [2] */ + "Akzeptieren", + /* [3] */ + "Ablehnen", + /* [4] */ + "Drucken", + /* [5] */ + "Sichern...", + /* [6] */ + "Klicken Sie auf ÒAkzeptierenÓ, wenn Sie mit den Bestimmungen des " + "Software-Lizenzvertrages einverstanden sind. Falls nicht, klicken " + "Sie bitte ÒAblehnenÓ an. Sie kšnnen die Software nur installieren, " + "wenn Sie ÒAkzeptierenÓ angeklickt haben.", + /* [7] */ + "Software-Lizenzvertrag", + /* [8] */ + "Dieser Text kann nicht gesichert werden. Diese Festplatte ist " + "mšglicherweise voll oder geschŸtzt oder der Ordner ist geschŸtzt.", + /* [9] */ + "Es kann nicht gedruckt werden. Bitte stellen Sie sicher, da§ ein " + "Drucker ausgewŠhlt ist." + } +}; + +resource 'STR#' (5002, "English") { + { /* array StringArray: 9 elements */ + /* [1] */ + "English", + /* [2] */ + "Agree", + /* [3] */ + "Disagree", + /* [4] */ + "Print", + /* [5] */ + "Save...", + /* [6] */ + "IMPORTANT - Read this License Agreement carefully before clicking on " + "the \"Agree\" button. By clicking on the \"Agree\" button, you agree " + "to be bound by the terms of the License Agreement.", + /* [7] */ + "Software License Agreement", + /* [8] */ + "This text cannot be saved. This disk may be full or locked, or the file " + "may be locked.", + /* [9] */ + "Unable to print. Make sure youÕve selected a printer." + } +}; + +resource 'STR#' (5003, "Spanish") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Espa–ol", + /* [2] */ + "Aceptar", + /* [3] */ + "No aceptar", + /* [4] */ + "Imprimir", + /* [5] */ + "Guardar...", + /* [6] */ + "Si est‡ de acuerdo con los tŽrminos de esta licencia, pulse \"Aceptar\" " + "para instalar el software. En el supuesto de que no estŽ de acuerdo con " + "los tŽrminos de esta licencia, pulse \"No aceptar.\"", + /* [7] */ + "Licencia de Software", + /* [8] */ + "Este texto no se puede guardar. El disco puede estar lleno o bloqueado, " + "o el archivo puede estar bloqueado.", + /* [9] */ + "No se puede imprimir. Compruebe que ha seleccionado una impresora." + } +}; + +resource 'STR#' (5004, "French") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Franais", + /* [2] */ + "Accepter", + /* [3] */ + "Refuser", + /* [4] */ + "Imprimer", + /* [5] */ + "Enregistrer...", + /* [6] */ + "Si vous acceptez les termes de la prŽsente licence, cliquez sur " + "\"Accepter\" afin d'installer le logiciel. Si vous n'tes pas d'accord " + "avec les termes de la licence, cliquez sur \"Refuser\".", + /* [7] */ + "Contrat de licence de logiciel", + /* [8] */ + "Ce texte ne peut tre sauvegardŽ. Le disque est peut-tre saturŽ ou " + "verrouillŽ, ou bien le fichier est peut-tre verrouillŽ.", + /* [9] */ + "Impression impossible. Assurez-vous dÕavoir sŽlectionnŽ une imprimante." + } +}; + +resource 'STR#' (5005, "Italian") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Italiano", + /* [2] */ + "Accetto", + /* [3] */ + "Rifiuto", + /* [4] */ + "Stampa", + /* [5] */ + "Registra...", + /* [6] */ + "Se accetti le condizioni di questa licenza, fai clic su \"Accetto\" per " + "installare il software. Altrimenti fai clic su \"Rifiuto\".", + /* [7] */ + "Licenza Software", + /* [8] */ + "Non posso registrare il testo. Il disco potrebbe essere pieno o protetto " + "oppure il documento potrebbe essere protetto.", + /* [9] */ + "Non posso stampare. Assicurati di aver selezionato una stampante." + } +}; + +resource 'STR#' (5006, "Japanese") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Japanese", + /* [2] */ + "“¯ˆÓ‚µ‚Ü‚·", + /* [3] */ + "“¯ˆÓ‚µ‚Ü‚¹‚ñ", + /* [4] */ + "ˆóü‚·‚é", + /* [5] */ + "•Û‘¶...", + /* [6] */ + "–{ƒ\\ƒtƒgƒEƒGƒAŽg—p‹–‘øŒ_–ñ‚ÌðŒ‚É“¯ˆÓ‚³" + "‚ê‚éê‡‚ɂ́Aƒ\\ƒtƒgƒEƒGƒA‚ðƒCƒ“ƒXƒg[ƒ‹" + "‚·‚邽‚߂Ɂu“¯ˆÓ‚µ‚Ü‚·v‚ð‰Ÿ‚µ‚Ä‚­‚¾‚³‚¢" + "B@“¯ˆÓ‚³‚ê‚È‚¢ê‡‚ɂ́Au“¯ˆÓ‚µ‚Ü‚¹‚ñ" + "v‚ð‰Ÿ‚µ‚Ä‚­‚¾‚³‚¢B", + /* [7] */ + "ƒ\\ƒtƒgƒEƒFƒAŽg—p‹–‘øŒ_–ñ", + /* [8] */ + "‚±‚̃eƒLƒXƒg‚́A•Û‘¶‚Å‚«‚Ü‚¹‚ñB‚±‚̃fƒB" + "ƒXƒN‚ɋ󂫂ª–³‚¢‚©ƒƒbƒN‚³‚ê‚Ä‚¢‚é‰Â”\\«" + "‚ª‚ ‚è‚Ü‚·B‚Ü‚½‚́A‚±‚̃tƒ@ƒCƒ‹‚ªƒƒbƒN" + "‚³‚ê‚Ä‚¢‚é‰Â”\\«‚ª‚ ‚è‚Ü‚·B", + /* [9] */ + "ˆóü‚Å‚«‚Ü‚¹‚ñBƒvƒŠƒ“ƒ^‚ª³‚µ‚­‘I‘ð‚³‚ê" + "‚Ä‚¢‚邱‚Æ‚ðŠm”F‚µ‚Ä‚­‚¾‚³‚¢B" + } +}; + +resource 'STR#' (5007, "Dutch") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Nederlands", + /* [2] */ + "Ja", + /* [3] */ + "Nee", + /* [4] */ + "Print", + /* [5] */ + "Bewaar...", + /* [6] */ + "Indien u akkoord gaat met de voorwaarden van deze licentie, kunt u op 'Ja' " + "klikken om de programmatuur te installeren. Indien u niet akkoord gaat, " + "klikt u op 'Nee'.", + /* [7] */ + "Softwarelicentie", + /* [8] */ + "De tekst kan niet worden bewaard. Het kan zijn dat uw schijf vol of " + "beveiligd is of dat het bestand beveiligd is.", + /* [9] */ + "Afdrukken niet mogelijk. Zorg dat er een printer is geselecteerd." + } +}; + +resource 'STR#' (5008, "Swedish") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Svensk", + /* [2] */ + "GodkŠnns", + /* [3] */ + "Avbšjs", + /* [4] */ + "Skriv ut", + /* [5] */ + "Spara...", + /* [6] */ + "Om Du godkŠnner licensvillkoren klicka pŒ \"GodkŠnns\" fšr att installera " + "programprodukten. Om Du inte godkŠnner licensvillkoren, klicka pŒ \"Avbšjs\".", + /* [7] */ + "Licensavtal fšr Programprodukt", + /* [8] */ + "Den hŠr texten kan inte sparas eftersom antingen skivan Šr full eller skivan " + "och/eller dokumentet Šr lŒst.", + /* [9] */ + "Kan inte skriva ut. Kontrollera att du har valt en skrivare. " + } +}; + +resource 'STR#' (5009, "Brazilian Portuguese") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Portugus, Brasil", + /* [2] */ + "Concordar", + /* [3] */ + "Discordar", + /* [4] */ + "Imprimir", + /* [5] */ + "Salvar...", + /* [6] */ + "Se est‡ de acordo com os termos desta licena, pressione \"Concordar\" para " + "instalar o software. Se n‹o est‡ de acordo, pressione \"Discordar\".", + /* [7] */ + "Licena de Uso de Software", + /* [8] */ + "Este texto n‹o pode ser salvo. O disco pode estar cheio ou\nbloqueado, ou o " + "arquivo pode estar bloqueado.", + /* [9] */ + "N‹o Ž poss’vel imprimir. Comprove que voc selecionou uma impressora." + } +}; + +resource 'STR#' (5010, "Simplified Chinese") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Simplified Chinese", + /* [2] */ + "ͬÒâ", + /* [3] */ + "²»Í¬Òâ", + /* [4] */ + "´òÓ¡", + /* [5] */ + "´æ´¢¡­", + /* [6] */ + "Èç¹ûÄúͬÒâ±¾Ðí¿ÉЭÒéµÄÌõ¿î£¬Çë°´¡°Í¬Ò⡱" + "À´°²×°´ËÈí¼þ¡£Èç¹ûÄú²»Í¬Ò⣬Çë°´¡°²»Í¬Òâ" + "¡±¡£", + /* [7] */ + "Èí¼þÐí¿ÉЭÒé", + /* [8] */ + "²»ÄÜ´æ´¢Õâ¸öÎļþ¡£´ÅÅÌ¿ÉÄܱ»Ëø¶¨»òÒÑÂú£¬" + "Ò²ÐíÊÇÎļþ±»Ëø¶¨ÁË¡£", + /* [9] */ + "ÎÞ·¨´òÓ¡¡£ÇëÈ·¶¨ÄúÒÑÑ¡ÔñÁËһ̨´òÓ¡»ú¡£" + } +}; + +resource 'STR#' (5011, "Traditional Chinese") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Traditional Chinese", + /* [2] */ + "¦P·N", + /* [3] */ + "¤£¦P·N", + /* [4] */ + "¦C¦L", + /* [5] */ + "Àx¦s¡K", + /* [6] */ + "¦pªG±z¦P·N¥»³\\¥iÃҸ̪º±ø´Ú¡A½Ð«ö¡§¦P·N¡¨" + "¥H¦w¸Ë³nÅé¡C¦pªG¤£¦P·N¡A½Ð«ö¡§¤£¦P·N¡¨¡C", + /* [7] */ + "³nÅé³\\¥i¨óij", + /* [8] */ + "¥»¤å¦rµLªkÀx¦s¡C³o­ÓºÏºÐ¥i¯à¤wº¡©Î¬OÂê©w" + "¡A©ÎÀɮפw¸gÂê©w¡C", + /* [9] */ + "µLªk¦C¦L¡C½Ð½T©w±z¤w¸g¿ï¾Ü¤F¦Lªí¾÷¡C" + } +}; + +resource 'STR#' (5012, "Danish") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Dansk", + /* [2] */ + "Enig", + /* [3] */ + "Uenig", + /* [4] */ + "Udskriv", + /* [5] */ + "Arkiver...", + /* [6] */ + "Hvis du accepterer betingelserne i licensaftalen, skal du klikke pŒ ÒEnigÓ " + "for at installere softwaren. Klik pŒ ÒUenigÓ for at annullere installeringen.", + /* [7] */ + "Licensaftale for software", + /* [8] */ + "Teksten kan ikke arkiveres. Disken er evt. fuld eller lŒst, eller ogsŒ er " + "arkivet lŒst.", + /* [9] */ + "Kan ikke udskrive. S¿rg for, at der er valgt en printer." + } +}; + +resource 'STR#' (5013, "Finnish") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Suomi", + /* [2] */ + "HyvŠksyn", + /* [3] */ + "En hyvŠksy", + /* [4] */ + "Tulosta", + /* [5] */ + "TallennaÉ", + /* [6] */ + "HyvŠksy lisenssisopimuksen ehdot osoittamalla ÕHyvŠksyÕ. Jos et hyvŠksy " + "sopimuksen ehtoja, osoita ÕEn hyvŠksyÕ.", + /* [7] */ + "Lisenssisopimus", + /* [8] */ + "TekstiŠ ei voida tallentaa. Levy voi olla tŠynnŠ tai lukittu.", + /* [9] */ + "TekstiŠ ei voida tulostaa. Varmista, ettŠ kirjoitin on valittu." + } +}; + +resource 'STR#' (5014, "French Canadian") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Franais canadien", + /* [2] */ + "Accepter", + /* [3] */ + "Refuser", + /* [4] */ + "Imprimer", + /* [5] */ + "Enregistrer...", + /* [6] */ + "Si vous acceptez les termes de la prŽsente licence, cliquez sur \"Accepter\" " + "afin d'installer le logiciel. Si vous n'tes pas d'accord avec les termes de " + "la licence, cliquez sur \"Refuser\".", + /* [7] */ + "Contrat de licence de logiciel", + /* [8] */ + "Ce texte ne peut tre sauvegardŽ. Le disque est peut-tre saturŽ ou verrouillŽ, " + "ou bien le fichier est peut-tre verrouillŽ.", + /* [9] */ + "Impression impossible. Assurez-vous dÕavoir sŽlectionnŽ une imprimante." + } +}; + +resource 'STR#' (5015, "Korean") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Korean", + /* [2] */ + "µ¿ÀÇ", + /* [3] */ + "µ¿ÀÇ ¾ÈÇÔ", + /* [4] */ + "ÇÁ¸°Æ®", + /* [5] */ + "ÀúÀå...", + /* [6] */ + "»ç¿ë °è¾à¼­ÀÇ ³»¿ë¿¡ µ¿ÀÇÇϸé, \"µ¿ÀÇ\" ´Ü" + "Ã߸¦ ´­·¯ ¼ÒÇÁÆ®¿þ¾î¸¦ ¼³Ä¡ÇϽʽÿÀ. µ¿À" + "ÇÇÏÁö ¾Ê´Â´Ù¸é, \"µ¿ÀÇ ¾ÈÇÔ\" ´ÜÃ߸¦ ´©¸£½" + "ʽÿÀ.", + /* [7] */ + "»ç¿ë °è¾à µ¿ÀǼ­", + /* [8] */ + "ÀÌ ÅØ½ºÆ®¸¦ ÀúÀåÇÒ ¼ö ¾ø½À´Ï´Ù. ÀÌ µð½ºÅ" + "©´Â ²Ë á°Å³ª Àá°Ü ÀÖ½À´Ï´Ù. ¶Ç´Â ÆÄÀÏÀÌ" + " Àá°Ü ÀÖÀ» ¼öµµ ÀÖ½À´Ï´Ù.", + /* [9] */ + "ÇÁ¸°Æ®ÇÒ ¼ö ¾ø½À´Ï´Ù. ÇÁ¸°Å͸¦ ¼±ÅÃÇß´ÂÁ" + "ö È®ÀÎÇϽʽÿÀ." + } +}; + +resource 'STR#' (5016, "Norwegian") { + { /* array StringArray: 9 elements */ + /* [1] */ + "Norsk", + /* [2] */ + "Enig", + /* [3] */ + "Ikke enig", + /* [4] */ + "Skriv ut", + /* [5] */ + "Arkiver...", + /* [6] */ + "Hvis De er enig i bestemmelsene i denne lisensavtalen, klikker De pŒ " + "\"Enig\"-knappen for Œ installere programvaren. Hvis De ikke er enig, " + "klikker De pŒ \"Ikke enig\".", + /* [7] */ + "Programvarelisensavtale", + /* [8] */ + "Denne teksten kan ikke arkiveres. Disken kan v¾re full eller lŒst, " + "eller filen kan v¾re lŒst. ", + /* [9] */ + "Kan ikke skrive ut. Forsikre deg om at du har valgt en skriver. " + } +}; diff --git a/packagers/osx/distribution.xml b/packagers/osx/distribution.xml new file mode 100644 index 0000000..be148ad --- /dev/null +++ b/packagers/osx/distribution.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<installer-gui-script minSpecVersion="1"> + <pkg-ref id="com.enst.gpac"/> + <options customize="never" require-scripts="false"/> + <choices-outline> + <line choice="default"> + <line choice="com.enst.gpac"/> + </line> + </choices-outline> + <choice id="default"/> + <choice id="com.enst.gpac" visible="false"> + <pkg-ref id="com.enst.gpac"/> + </choice> + <pkg-ref id="com.enst.gpac" version="0.7.2" onConclusion="none">tmppkg.pkg</pkg-ref> + + <title>GPAC Framework rev + + + diff --git a/packagers/osx/res/background.png b/packagers/osx/res/background.png new file mode 100755 index 0000000000000000000000000000000000000000..b544eed9fd21e4a791ec1f7b94045c7edfeea321 GIT binary patch literal 98776 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 Nv^4b8%T%pG{~s+Kk=6hJ literal 0 HcmV?d00001 diff --git a/packagers/osx/res/preamble.txt b/packagers/osx/res/preamble.txt new file mode 100644 index 0000000..7a70226 --- /dev/null +++ b/packagers/osx/res/preamble.txt @@ -0,0 +1,8 @@ +This software package includes many Multimedia Technologies. Some of these technologies may be covered by various patents hard to identify. These patents may or may not apply in your local jurisdiction. + +By installing this software, you acknowledge that you may have to pay royalty fees in order to legally use this software. + +DO NOT PROCEED with this installation if you do not understand or do not agree with these terms. + +Telecom Paris bears no liability for any infringing usage of this software, which is provided for educational or research purposes. + diff --git a/packagers/osx/scripts/postinstall b/packagers/osx/scripts/postinstall new file mode 100755 index 0000000..d821457 --- /dev/null +++ b/packagers/osx/scripts/postinstall @@ -0,0 +1,14 @@ +#!/bin/sh + +inst_dir="$2/GPAC.app" +echo "inst_dir: $inst_dir" + +inpath=`grep "$inst_dir" /etc/paths` + +if [ -n "$inpath" ] ; then +echo "Already in path" +else +echo "$inst_dir/Contents/MacOS/" >> /etc/paths +fi + +exit 0 \ No newline at end of file diff --git a/packagers/win32_64/nsis/gpac_installer.nsi b/packagers/win32_64/nsis/gpac_installer.nsi index 53f475a..37868f0 100644 --- a/packagers/win32_64/nsis/gpac_installer.nsi +++ b/packagers/win32_64/nsis/gpac_installer.nsi @@ -1,6 +1,6 @@ ;-------------------------------- ;General -!define GPAC_VERSION 0.7.1 +!define GPAC_VERSION 1.0.1 !include default.out !define GPAC_ROOT ..\..\.. @@ -39,8 +39,8 @@ FunctionEnd !include "MUI2.nsh" WindowIcon on -Icon "${GPAC_ROOT}\doc\osmo4.ico" -UninstallIcon "${GPAC_ROOT}\doc\osmo4.ico" +Icon "${GPAC_ROOT}\share\doc\osmo4.ico" +UninstallIcon "${GPAC_ROOT}\share\doc\osmo4.ico" ;-------------------------------- ;Interface Settings @@ -50,9 +50,10 @@ UninstallIcon "${GPAC_ROOT}\doc\osmo4.ico" Var DIALOG Var Label Var Confirm +Var VSRedistSetupError -LangString PAGE_TITLE ${LANG_ENGLISH} "Title" -LangString PAGE_SUBTITLE ${LANG_ENGLISH} "Subtitle" +;LangString PAGE_TITLE ${LANG_ENGLISH} "Title" +;LangString PAGE_SUBTITLE ${LANG_ENGLISH} "Subtitle" Function EnableNext Pop $R1 @@ -60,9 +61,9 @@ Function EnableNext GetDlgItem $0 $HWNDPARENT 1 ${If} $R1 == ${BST_CHECKED} EnableWindow $0 1 - ${Else} + ${Else} EnableWindow $0 0 - ${Endif} + ${Endif} FunctionEnd Function customPage @@ -79,8 +80,8 @@ Function customPage Pop $Confirm GetFunctionAddress $0 EnableNext nsDialogs::OnClick $Confirm $0 - - + + nsDialogs::Show FunctionEnd @@ -88,14 +89,14 @@ FunctionEnd ;Pages !insertmacro MUI_PAGE_WELCOME - !insertmacro MUI_PAGE_LICENSE "${GPAC_ROOT}\COPYING" + !insertmacro MUI_PAGE_LICENSE "${GPAC_ROOT}\COPYING" Page custom customPage !insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_DIRECTORY - + !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH - + !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES @@ -126,7 +127,7 @@ Function FctWriteRegStrAuth StrCmp $R0 "HKCR" +1 +3 WriteRegStr HKCR $R1 $R2 $R3 goto lbl_end - + #has current user admin privileges? userInfo::getAccountType Pop $0 @@ -154,7 +155,7 @@ FunctionEnd Push "${VALUESTR}" Call FctWriteRegStrAuth !macroend - + !define WriteRegStrAuth "!insertmacro WriteRegStrAuth" @@ -173,16 +174,16 @@ Function un.FctDeleteRegKeyAuth StrCmp $R0 "HKCR" +1 +3 DeleteRegKey HKCR $R1 goto lbl_end - + #has current user admin privileges? userInfo::getAccountType Pop $0 StrCmp $0 "Admin" lbl_admin lbl_not_admin - + lbl_admin: DeleteRegKey HKLM $R1 goto lbl_end - + lbl_not_admin: DeleteRegKey HKCU $R1 @@ -197,7 +198,7 @@ FunctionEnd Push "${SUBREG}" Call un.FctDeleteRegKeyAuth !macroend - + !define DeleteRegKeyAuth "!insertmacro DeleteRegKeyAuth" Function InsertGDIPLUS @@ -235,12 +236,8 @@ lbl_winnt: StrCmp $R1 '5.2' lbl_done ;.NET server lbl_add: - !ifndef IS_WIN64 - File "${GPAC_BIN}\Gdiplus.dll" - !endif lbl_xp: - File "${GPAC_BIN}\gm_gdip_raster.dll" lbl_done: FunctionEnd @@ -253,19 +250,27 @@ Section "GPAC Core" SecGPAC File /oname=ReadMe.txt "${GPAC_ROOT}\README.md" File /oname=License.txt "${GPAC_ROOT}\COPYING" File /oname=Changelog.txt "${GPAC_ROOT}\Changelog" - File "${GPAC_ROOT}\doc\configuration.html" - File "${GPAC_ROOT}\doc\gpac.mp4" - File "${GPAC_ROOT}\doc\osmo4.ico" + File "${GPAC_ROOT}\share\doc\osmo4.ico" File "${GPAC_BIN}\libgpac.dll" - File "${GPAC_BIN}\js.dll" - File "${GPAC_BIN}\libeay32.dll" - File "${GPAC_BIN}\ssleay32.dll" + File "${GPAC_BIN}\libcryptoMD.dll" + File "${GPAC_BIN}\libsslMD.dll" + + File "${GPAC_BIN}\avcodec-*.dll" + File "${GPAC_BIN}\avdevice-*.dll" + File "${GPAC_BIN}\avfilter-*.dll" + File "${GPAC_BIN}\avformat-*.dll" + File "${GPAC_BIN}\avutil-*.dll" + File "${GPAC_BIN}\swresample-*.dll" + File "${GPAC_BIN}\swscale-*.dll" + File "${GPAC_BIN}\postproc-*.dll" + File "${GPAC_BIN}\libx264-*.dll" + File "${GPAC_BIN}\OpenSVCDecoder.dll" ;create default cache SetOutPath $INSTDIR\cache SetOutPath $INSTDIR - + ${WriteRegStrAuth} HKCU "SOFTWARE\GPAC" "InstallDir" "$INSTDIR" ${WriteRegStrAuth} HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\GPAC" "DisplayName" "GPAC (remove only)" ${WriteRegStrAuth} HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\GPAC" "UninstallString" "$INSTDIR\uninstall.exe" @@ -277,148 +282,48 @@ SectionEnd Section "GPAC Player" SecOsmo4 SectionIn 1 - !ifndef IS_WIN64 - File "${GPAC_BIN}\Osmo4.exe" - !endif - File "${GPAC_BIN}\gm_dummy_in.dll" + File "${GPAC_BIN}\MP4Client.exe" + File "${GPAC_BIN}\gm_dx_hw.dll" - File "${GPAC_BIN}\gm_gpac_js.dll" - File "${GPAC_BIN}\gm_ismacryp.dll" ;copy GUI - SetOutPath $INSTDIR\gui - File "${GPAC_ROOT}\gui\gui.bt" - File "${GPAC_ROOT}\gui\gui.js" - File "${GPAC_ROOT}\gui\gwlib.js" - File "${GPAC_ROOT}\gui\mpegu-core.js" - File "${GPAC_ROOT}\gui\webvtt-renderer.js" - SetOutPath $INSTDIR\gui\icons - File /r /x .git ${GPAC_ROOT}\gui\icons\* - SetOutPath $INSTDIR\gui\extensions - File /r /x .git ${GPAC_ROOT}\gui\extensions\* + SetOutPath $INSTDIR\share + File "${GPAC_ROOT}\share\default.cfg" + SetOutPath $INSTDIR\share\res + File "${GPAC_ROOT}\share\res\gpac.mp4" + File "${GPAC_ROOT}\share\res\gpac_cfg_test.mp4" + File "${GPAC_ROOT}\share\res\gpac.png" + SetOutPath $INSTDIR\share\gui + File "${GPAC_ROOT}\share\gui\gui.bt" + File "${GPAC_ROOT}\share\gui\gui.js" + File "${GPAC_ROOT}\share\gui\gwlib.js" +; File "${GPAC_ROOT}\share\gui\mpegu-core.js" + SetOutPath $INSTDIR\share\gui\icons + File /r /x .git ${GPAC_ROOT}\share\gui\icons\* + SetOutPath $INSTDIR\share\gui\extensions + File /r /x .git ${GPAC_ROOT}\share\gui\extensions\* + + ;copy scripts + SetOutPath $INSTDIR\share\scripts + File /r /x .git ${GPAC_ROOT}\share\scripts\* ;copy shaders - SetOutPath $INSTDIR\shaders - File "${GPAC_ROOT}\shaders\vertex.glsl" - File "${GPAC_ROOT}\shaders\fragment.glsl" - - SetOutPath $INSTDIR -SectionEnd - -SubSection "GPAC Plugins" SecPlugins - - -; -; 2 install modes, normal one and full one - -Section "MPEG-4 BIFS Decoder" SecBIFS - SectionIn 1 - File "${GPAC_BIN}\gm_bifs_dec.dll" -SectionEnd - -Section "MPEG-4 ODF Decoder" SecODF - SectionIn 1 - File "${GPAC_BIN}\gm_odf_dec.dll" -SectionEnd - -Section "MPEG-4 LASeR Decoder" SecLASeR - SectionIn 1 - File "${GPAC_BIN}\gm_laser_dec.dll" -SectionEnd - -Section "MPEG-4 SAF Demultiplexer" SecSAF - SectionIn 1 - File "${GPAC_BIN}\gm_saf_in.dll" -SectionEnd - -Section "Textual MPEG-4 Loader" SecTextLoad - SectionIn 1 - File "${GPAC_BIN}\gm_ctx_load.dll" -SectionEnd + SetOutPath $INSTDIR\share\shaders + File /r /x .git ${GPAC_ROOT}\share\shaders\* -Section "Image Package (PNG, JPEG, BMP)" SecIMG - SectionIn 1 - File "${GPAC_BIN}\gm_img_in.dll" -SectionEnd - -Section "AAC Audio" SecAAC - SectionIn 1 - File "${GPAC_BIN}\gm_aac_in.dll" -SectionEnd - -Section "MP3 Audio" SecMP3 - SectionIn 1 - File "${GPAC_BIN}\gm_mp3_in.dll" -SectionEnd - -Section "AC3 Audio" SecAC3 - SectionIn 1 - File "${GPAC_BIN}\gm_ac3_in.dll" -SectionEnd - -Section "FFMPEG" SecFFMPEG - SectionIn 1 - File "${GPAC_BIN}\gm_ffmpeg_in.dll" - File "${GPAC_BIN}\avcodec-*.dll" - File "${GPAC_BIN}\avdevice-*.dll" - File "${GPAC_BIN}\avfilter-*.dll" - File "${GPAC_BIN}\avformat-*.dll" - File "${GPAC_BIN}\avutil-*.dll" - File "${GPAC_BIN}\avresample-*.dll" - File "${GPAC_BIN}\swresample-*.dll" - File "${GPAC_BIN}\swscale-*.dll" - File "${GPAC_BIN}\libx264-*.dll" -SectionEnd - -Section "XviD Video Decoder" SecXVID - SectionIn 1 - File "${GPAC_BIN}\gm_xvid_dec.dll" -SectionEnd - -;Section "AMR NB & WB" SecAMRFT -; SectionIn 1 -; File "..\gm_amr_float_dec.dll" -;SectionEnd - -Section "Subtitles" SecSUBS - SectionIn 1 - File "${GPAC_BIN}\gm_timedtext.dll" -SectionEnd - -Section "ISO File Format" SecISOFF - SectionIn 1 - File "${GPAC_BIN}\gm_isom_in.dll" -SectionEnd - -Section "MPEG-2 TS" SecM2TS - SectionIn 1 - File "${GPAC_BIN}\gm_mpegts_in.dll" -SectionEnd - -Section "RTP/RTSP" SecRTP - SectionIn 1 - File "${GPAC_BIN}\gm_rtp_in.dll" -SectionEnd + ;copy lang + SetOutPath $INSTDIR\share\lang + File /r /x .git ${GPAC_ROOT}\share\lang\* -Section "SVG" SecSVG - SectionIn 1 - File "${GPAC_BIN}\gm_svg_in.dll" -SectionEnd + ;copy vis + SetOutPath $INSTDIR\share\vis + File /r /x .git ${GPAC_ROOT}\share\vis\* -Section "WebVTT" SecWebVTT - SectionIn 1 - File "${GPAC_BIN}\gm_vtt_in.dll" + SetOutPath $INSTDIR SectionEnd -Section "GDI+" SecGDIP - SectionIn 1 - call InsertGDIPLUS -SectionEnd +SubSection "GPAC Plugins" SecPlugins -Section "GPAC 2D Raster" SecG2DS - SectionIn 1 - File "${GPAC_BIN}\gm_soft_raster.dll" -SectionEnd Section "FreeType" SecFT SectionIn 1 @@ -430,49 +335,23 @@ Section "Windows MME Audio" SecWAVE File "${GPAC_BIN}\gm_wav_out.dll" SectionEnd -Section "Xiph" SecXIPH - SectionIn 1 - File "${GPAC_BIN}\gm_ogg.dll" -SectionEnd - -Section "OpenSVC Decoder" SecOSVC +Section "SDL" SecSDL SectionIn 1 - File "${GPAC_BIN}\OpenSVCDecoder.dll" - File "${GPAC_BIN}\gm_opensvc_dec.dll" + File "${GPAC_BIN}\SDL.dll" + File "${GPAC_BIN}\gm_sdl_out.dll" SectionEnd Section "OpenHEVC Decoder" SecOHEVC SectionIn 1 - File "${GPAC_BIN}\libLibOpenHevcWrapper.dll" - File "${GPAC_BIN}\gm_openhevc_dec.dll" -SectionEnd - -Section "MPEG DASH Support" SecDASH - SectionIn 1 - File "${GPAC_BIN}\gm_mpd_in.dll" -SectionEnd - -Section "HTML 5 Media Source Extensions Support" SecMSE - SectionIn 1 - File "${GPAC_BIN}\gm_mse_in.dll" -SectionEnd - -Section "UPnP Support" SecUPnP - SectionIn 1 - File "${GPAC_BIN}\gm_platinum.dll" + File "${GPAC_BIN}\openhevc-1.dll" + File "${GPAC_BIN}\gf_ohevcdec.dll" SectionEnd -Section "Widget Manager" SecMPEGU +Section "Validator" SecValidator SectionIn 1 - File "${GPAC_BIN}\gm_widgetman.dll" + File "${GPAC_BIN}\gm_validator.dll" SectionEnd -;Section "MobileIP Framework" SecMobIP -; SectionIn 1 -; File "..\gm_mobile_ip.dll" -; File "..\MobileSession.dll" -;SectionEnd - SubSectionEnd @@ -484,18 +363,10 @@ Section "MP4Box" SecMP4B Call AddToPath SectionEnd -Section "MP42TS" SecMP42TS - SectionIn 1 - SetOutPath $INSTDIR - File "${GPAC_BIN}\MP42TS.exe" - Push $INSTDIR - Call AddToPath -SectionEnd - -Section "DashCast" SecDC +Section "gpac" SecGPACBIN SectionIn 1 SetOutPath $INSTDIR - File "${GPAC_BIN}\dashcast.exe" + File "${GPAC_BIN}\gpac.exe" Push $INSTDIR Call AddToPath SectionEnd @@ -507,53 +378,41 @@ Section "GPAC SDK" SecSDK File /r ${GPAC_ROOT}\include\*.h SetOutPath $INSTDIR\sdk\lib File ${GPAC_BIN}\libgpac.lib - File ${GPAC_EXTRA_LIB}\js.lib -SectionEnd - - -!ifndef IS_WIN64 -!define HK_MOZ "SOFTWARE\MozillaPlugins\@gpac/osmozilla,version=1.0" - -Section "Osmozilla" SecZILLA - SectionIn 1 - SetOutPath $INSTDIR - File "${GPAC_BIN}\nposmozilla.dll" - File "${GPAC_BIN}\nposmozilla.xpt" - - ${WriteRegStrAuth} HKCU "SOFTWARE\MozillaPlugins\@gpac/osmozilla,version=1.0" "Path" "$INSTDIR\nposmozilla.dll" - ${WriteRegStrAuth} HKCU "SOFTWARE\MozillaPlugins\@gpac/osmozilla,version=1.0" "XPTPath" "$INSTDIR\nposmozilla.xpt" - ${WriteRegStrAuth} HKCU "SOFTWARE\MozillaPlugins\@gpac/osmozilla,version=1.0" "Version" "${GPAC_VERSION}" - ${WriteRegStrAuth} HKCU "SOFTWARE\MozillaPlugins\@gpac/osmozilla,version=1.0" "Vendor" "GPAC" - ${WriteRegStrAuth} HKCU "SOFTWARE\MozillaPlugins\@gpac/osmozilla,version=1.0" "Description" "GPAC plugin" - ${WriteRegStrAuth} HKCU "SOFTWARE\MozillaPlugins\@gpac/osmozilla,version=1.0" "ProductName" "Osmozilla" SectionEnd -!endif +!if /FileExists "${GPAC_BIN}\GPAX.dll" Section "GPAX" SecGPAX SectionIn 1 SetOutPath $INSTDIR File "${GPAC_BIN}\GPAX.dll" RegDLL "$INSTDIR\GPAX.dll" SectionEnd +!endif - -Section "MP4Client" SecMP4C - SectionIn 1 - SetOutPath $INSTDIR - File "${GPAC_BIN}\MP4Client.exe" -SectionEnd - - - -Section "Windows Runtime Libraries" SecMSVCRT - SectionIn 1 +;Section "Windows Runtime Libraries" SecMSVCRT +; SectionIn 1 ; File "..\Microsoft.VC100.CRT.manifest" -; File "..\Microsoft.VC100.MFC.manifest" - File "${GPAC_BIN}\msvcr100.dll" - File "${GPAC_BIN}\mfc100.dll" -SectionEnd +; File "..\Microsoft.VC100.MFC.manifest" +; File "${GPAC_BIN}\msvcr100.dll" +; File "${GPAC_BIN}\mfc100.dll" +;SectionEnd +!ifdef IS_WIN64 +!define TARGET_ARCHITECTURE "x64" +!else +!define TARGET_ARCHITECTURE "x86" +!endif +Section "VS2015 C++ re-distributable Package (${TARGET_ARCHITECTURE})" SEC_VCREDIST1 +DetailPrint "Running VS2015 re-distributable setup..." + SectionIn 1 + SetOutPath "$TEMP\vc2015" + File "${GPAC_ROOT}\packagers\win32_64\nsis\vc_redist.${TARGET_ARCHITECTURE}.exe" + ExecWait '"$TEMP\vc2015\vc_redist.${TARGET_ARCHITECTURE}.exe" /install /quiet /norestart' $VSRedistSetupError + RMDir /r "$TEMP\vc2015" + DetailPrint "Finished VS2015 re-distributable setup" + SetOutPath "$INSTDIR" +SectionEnd SubSection "GPAC Shortcuts" @@ -567,60 +426,14 @@ Section "Add Start Menu Shortcuts" SetShellVarContext all CreateDirectory "$SMPROGRAMS\GPAC" CreateShortCut "$SMPROGRAMS\GPAC\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 - CreateShortCut "$SMPROGRAMS\GPAC\Osmo4.lnk" "$INSTDIR\MP4Client.exe" "" + CreateShortCut "$SMPROGRAMS\GPAC\Osmo4.lnk" "$INSTDIR\MP4Client.exe" "" CreateShortCut "$SMPROGRAMS\GPAC\Osmo4 (with Console).lnk" "$INSTDIR\MP4Client.exe" "-guid" - !ifndef IS_WIN64 - CreateShortCut "$SMPROGRAMS\GPAC\Osmo4 (Old UI).lnk" "$INSTDIR\Osmo4.exe" "" - !endif CreateShortCut "$SMPROGRAMS\GPAC\Readme.lnk" "$INSTDIR\ReadMe.txt" CreateShortCut "$SMPROGRAMS\GPAC\License.lnk" "$INSTDIR\License.txt" CreateShortCut "$SMPROGRAMS\GPAC\History.lnk" "$INSTDIR\changelog.txt" CreateShortCut "$SMPROGRAMS\GPAC\Configuration Info.lnk" "$INSTDIR\configuration.html" SectionEnd -!ifndef IS_WIN64 -Section "Add shortcut to QuickLaunch" - SectionIn 1 - CreateShortCut "$QUICKLAUNCH\GPAC.lnk" "$INSTDIR\Osmo4.exe" "" "$INSTDIR\Osmo4.exe" 0 -SectionEnd - -Section "Add shortcut to Desktop" - SectionIn 1 - CreateShortCut "$DESKTOP\Osmo4.lnk" "$INSTDIR\Osmo4.exe" "" "$INSTDIR\Osmo4.exe" 0 -SectionEnd - -!define SHCNE_ASSOCCHANGED 0x08000000 -!define SHCNF_IDLIST 0 - -Section "Make Osmo4 the default MPEG-4 Player" - SectionIn 1 - ;write file association - ${WriteRegStrAuth} HKCR GPAC\mp4\DefaultIcon "" "$INSTDIR\Osmo4.ico, 0" - ${WriteRegStrAuth} HKCR GPAC\mp4\Shell\open\command "" "$INSTDIR\Osmo4.exe %L" - ${WriteRegStrAuth} HKCR .mp4 "" "GPAC\mp4" - !system 'shell32.dll::SHChangeNotify(i, i, i, i) v (${SHCNE_ASSOCCHANGED}, ${SHCNF_IDLIST}, 0, 0)' - -SectionEnd - -Section "Associate 3GPP files (3GP) with Osmo4" - SectionIn 1 - ;write file association - ${WriteRegStrAuth} HKCR GPAC\3gp\DefaultIcon "" "$INSTDIR\Osmo4.ico, 0" - ${WriteRegStrAuth} HKCR GPAC\3gp\Shell\open\command "" "$INSTDIR\Osmo4.exe %L" - ${WriteRegStrAuth} HKCR .3gp "" "GPAC\3gp" - !system 'shell32.dll::SHChangeNotify(i, i, i, i) v (${SHCNE_ASSOCCHANGED}, ${SHCNF_IDLIST}, 0, 0)' -SectionEnd - -Section "Associate 3GPP2 files (3G2) with Osmo4" - SectionIn 1 - ;write file association - ${WriteRegStrAuth} HKCR GPAC\3g2\DefaultIcon "" "$INSTDIR\Osmo4.ico, 0" - ${WriteRegStrAuth} HKCR GPAC\3g2\Shell\open\command "" "$INSTDIR\Osmo4.exe %L" - ${WriteRegStrAuth} HKCR .3g2 "" "GPAC\3g2" - !system 'shell32.dll::SHChangeNotify(i, i, i, i) v (${SHCNE_ASSOCCHANGED}, ${SHCNF_IDLIST}, 0, 0)' -SectionEnd -!endif - SubSectionEnd @@ -629,45 +442,17 @@ SubSectionEnd !insertmacro MUI_DESCRIPTION_TEXT ${SecGPAC} "GPAC Core" !insertmacro MUI_DESCRIPTION_TEXT ${SecOsmo4} "GPAC Player" !insertmacro MUI_DESCRIPTION_TEXT ${SecPlugins} "GPAC Plugins" - !insertmacro MUI_DESCRIPTION_TEXT ${SecBIFS} "MPEG-4 BIFS Scene Decoder" - !insertmacro MUI_DESCRIPTION_TEXT ${SecODF} "MPEG-4 Object Descriptor Decoder" - !insertmacro MUI_DESCRIPTION_TEXT ${SecLASeR} "MPEG-4 LASeR Scene Decoder" - !insertmacro MUI_DESCRIPTION_TEXT ${SecTextLoad} "Support for uncompressed MPEG-4 (BT and XMT), VRML and X3D textual formats" - !insertmacro MUI_DESCRIPTION_TEXT ${SecSAF} "MPEG-4 SAF Demultiplexer" - !insertmacro MUI_DESCRIPTION_TEXT ${SecIMG} "Support for PNG, JPEG, BMP and JPEG2000 images" - !insertmacro MUI_DESCRIPTION_TEXT ${SecAAC} "Support for MPEG-4 Audio HE-AAC decoder and web radios" - !insertmacro MUI_DESCRIPTION_TEXT ${SecMP3} "Support for MPEG-1/2 Audio (inc. MP3) decoder and web radios" - !insertmacro MUI_DESCRIPTION_TEXT ${SecAC3} "Support for Dolby AC3 decoder and web radios" - !insertmacro MUI_DESCRIPTION_TEXT ${SecFFMPEG} "Support for FFMPEG libraries for various format decoding and demultiplexing" - !insertmacro MUI_DESCRIPTION_TEXT ${SecXVID} "Support for XVID library for MPEG-4 Video Part 2 decoding" - !insertmacro MUI_DESCRIPTION_TEXT ${SecAMRFT} "Support for AMR and AMR WideBand decoder and web radios" - !insertmacro MUI_DESCRIPTION_TEXT ${SecSUBS} "Subtitle support include SRT, SUB, 3GPP and MPEG-4 Text formats" - !insertmacro MUI_DESCRIPTION_TEXT ${SecISOFF} "Support for ISO-based file formats (3GP, MP4, MJ2K)" - !insertmacro MUI_DESCRIPTION_TEXT ${SecM2TS} "Support for MPEG-2 Transport Stream" - !insertmacro MUI_DESCRIPTION_TEXT ${SecRTP} "Support for RTP and RTSP IP streaming" - !insertmacro MUI_DESCRIPTION_TEXT ${SecSVG} "Support for SVG including progressive loading" - !insertmacro MUI_DESCRIPTION_TEXT ${SecWebVTT} "Support for WebVTT subtitles" - !insertmacro MUI_DESCRIPTION_TEXT ${SecGDIP} "GDIPlus-based rasterizer" - !insertmacro MUI_DESCRIPTION_TEXT ${SecG2DS} "GPAC software rasterizer" !insertmacro MUI_DESCRIPTION_TEXT ${SecFT} "FreeType font parsing" !insertmacro MUI_DESCRIPTION_TEXT ${SecWAVE} "Windows MME Audio output support" - !insertmacro MUI_DESCRIPTION_TEXT ${SecXIPH} "Support for XIPP OGG, Vorbis and Theora media" - !insertmacro MUI_DESCRIPTION_TEXT ${SecOSVC} "Support for SVC decoding through OpenSVC Decoder" !insertmacro MUI_DESCRIPTION_TEXT ${SecOHEVC} "Support for HEVC decoding through OpenHEVC Decoder" - !insertmacro MUI_DESCRIPTION_TEXT ${SecDASH} "HTTP Streaming using MPEG DASH" - !insertmacro MUI_DESCRIPTION_TEXT ${SecMSE} "HTTP Streaming using HTML 5 Media Source Extensions" - !insertmacro MUI_DESCRIPTION_TEXT ${SecUPnP} "Support for UPnP based on Platinum" - !insertmacro MUI_DESCRIPTION_TEXT ${SecMPEGU} "Support for W3C and MPEG-U Widgets" - !insertmacro MUI_DESCRIPTION_TEXT ${SecMobIP} "UNIGE Mobile IP Framework" - !insertmacro MUI_DESCRIPTION_TEXT ${SecOffisComp} "OFFIS Audio Compressor" - !insertmacro MUI_DESCRIPTION_TEXT ${SecMP4B} "MP4Box command-line tool for various multimedia operations" - !insertmacro MUI_DESCRIPTION_TEXT ${SecMP42TS} "MP42TS command-line tool for MPEG-2 TS multiplexing" - !insertmacro MUI_DESCRIPTION_TEXT ${SecDC} "DashCast offline and live MPEG-DASH Encoder" +; !insertmacro MUI_DESCRIPTION_TEXT ${SecUPnP} "Support for UPnP based on Platinum" +; !insertmacro MUI_DESCRIPTION_TEXT ${SecMPEGU} "Support for W3C and MPEG-U Widgets" + !insertmacro MUI_DESCRIPTION_TEXT ${SecMP4B} "MP4Box command-line tool for MP4 file manipulation" + !insertmacro MUI_DESCRIPTION_TEXT ${SecGPACBIN} "gpac command-line tool for various multimedia operations" !insertmacro MUI_DESCRIPTION_TEXT ${SecSDK} "GPAC SDK: headers and library files needed to develop modules for GPAC or appllication based on GPAC" - !insertmacro MUI_DESCRIPTION_TEXT ${SecZILLA} "GPAC playback support NPAPI-based browsers (FireFox/Gecko, Safari/WebKit)" - !insertmacro MUI_DESCRIPTION_TEXT ${SecGPAX} "GPAC playback support using ActiveX component (Internet Explorer)" - !insertmacro MUI_DESCRIPTION_TEXT ${SecMP4C} "GPAC command-line player and AVI dumper" - + !insertmacro MUI_DESCRIPTION_TEXT ${SecSDL} "GPAC SDL support" + !insertmacro MUI_DESCRIPTION_TEXT ${SecValidator} "GPAC Test Validator" + !insertmacro MUI_FUNCTION_DESCRIPTION_END @@ -735,77 +520,111 @@ SectionEnd !endif !endif -; AddToPath - Adds the given dir to the search path. -; Input - head of the stack -; Note - Win9x systems requires reboot +;-------------------------------------------------------------------- +; Path functions +; +; Based on example from: +; http://nsis.sourceforge.net/Path_Manipulation +; + + +!include "WinMessages.nsh" + +; Registry Entry for environment (NT4,2000,XP) +; All users: +;!define Environ 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' +; Current user only: +!define Environ 'HKCU "Environment"' + + +; AddToPath - Appends dir to PATH +; (does not work on Win9x/ME) +; +; Usage: +; Push "dir" +; Call AddToPath Function AddToPath Exch $0 Push $1 Push $2 Push $3 - - # don't add if the path doesn't exist - IfFileExists "$0\*.*" "" AddToPath_done - - ReadEnvStr $1 PATH + Push $4 + + ; NSIS ReadRegStr returns empty string on string overflow + ; Native calls are used here to check actual length of PATH + + ; $4 = RegOpenKey(HKEY_CURRENT_USER, "Environment", &$3) + System::Call "advapi32::RegOpenKey(i 0x80000001, t'Environment', *i.r3) i.r4" + IntCmp $4 0 0 done done + ; $4 = RegQueryValueEx($3, "PATH", (DWORD*)0, (DWORD*)0, &$1, ($2=NSIS_MAX_STRLEN, &$2)) + ; RegCloseKey($3) + System::Call "advapi32::RegQueryValueEx(i $3, t'PATH', i 0, i 0, t.r1, *i ${NSIS_MAX_STRLEN} r2) i.r4" + System::Call "advapi32::RegCloseKey(i $3)" + + ${If} $4 = 234 ; ERROR_MORE_DATA + DetailPrint "AddToPath: original length $2 > ${NSIS_MAX_STRLEN}" + MessageBox MB_OK "PATH not updated, original length $2 > ${NSIS_MAX_STRLEN}" /SD IDOK + Goto done + ${EndIf} + + ${If} $4 <> 0 ; NO_ERROR + ${If} $4 <> 2 ; ERROR_FILE_NOT_FOUND + DetailPrint "AddToPath: unexpected error code $4" + Goto done + ${EndIf} + StrCpy $1 "" + ${EndIf} + + ; Check if already in PATH Push "$1;" Push "$0;" Call StrStr Pop $2 - StrCmp $2 "" "" AddToPath_done + StrCmp $2 "" 0 done Push "$1;" Push "$0\;" Call StrStr Pop $2 - StrCmp $2 "" "" AddToPath_done - GetFullPathName /SHORT $3 $0 - Push "$1;" - Push "$3;" - Call StrStr - Pop $2 - StrCmp $2 "" "" AddToPath_done - Push "$1;" - Push "$3\;" - Call StrStr + StrCmp $2 "" 0 done + + ; Prevent NSIS string overflow + StrLen $2 $0 + StrLen $3 $1 + IntOp $2 $2 + $3 + IntOp $2 $2 + 2 ; $2 = strlen(dir) + strlen(PATH) + sizeof(";") + ${If} $2 > ${NSIS_MAX_STRLEN} + DetailPrint "AddToPath: new length $2 > ${NSIS_MAX_STRLEN}" + MessageBox MB_OK "PATH not updated, new length $2 > ${NSIS_MAX_STRLEN}." /SD IDOK + Goto done + ${EndIf} + + ; Append dir to PATH + DetailPrint "Add to PATH: $0" + StrCpy $2 $1 1 -1 + ${If} $2 == ";" + StrCpy $1 $1 -1 ; remove trailing ';' + ${EndIf} + ${If} $1 != "" ; no leading ';' + StrCpy $0 "$1;$0" + ${EndIf} + WriteRegExpandStr ${Environ} "PATH" $0 + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + +done: + Pop $4 + Pop $3 Pop $2 - StrCmp $2 "" "" AddToPath_done - - Call IsNT Pop $1 - StrCmp $1 1 AddToPath_NT - ; Not on NT - StrCpy $1 $WINDIR 2 - FileOpen $1 "$1\autoexec.bat" a - FileSeek $1 -1 END - FileReadByte $1 $2 - IntCmp $2 26 0 +2 +2 # DOS EOF - FileSeek $1 -1 END # write over EOF - FileWrite $1 "$\r$\nSET PATH=%PATH%;$3$\r$\n" - FileClose $1 - SetRebootFlag true - Goto AddToPath_done - - AddToPath_NT: - ReadRegStr $1 ${WriteEnvStr_RegKey} "PATH" - StrCpy $2 $1 1 -1 # copy last char - StrCmp $2 ";" 0 +2 # if last char == ; - StrCpy $1 $1 -1 # remove last char - StrCmp $1 "" AddToPath_NTdoIt - StrCpy $0 "$1;$0" - AddToPath_NTdoIt: - WriteRegExpandStr ${WriteEnvStr_RegKey} "PATH" $0 - SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 - - AddToPath_done: - Pop $3 - Pop $2 - Pop $1 - Pop $0 + Pop $0 FunctionEnd -; RemoveFromPath - Remove a given dir from the path -; Input: head of the stack + +; RemoveFromPath - Removes dir from PATH +; +; Usage: +; Push "dir" +; Call RemoveFromPath Function un.RemoveFromPath Exch $0 @@ -815,81 +634,42 @@ Function un.RemoveFromPath Push $4 Push $5 Push $6 - - IntFmt $6 "%c" 26 # DOS EOF - - Call un.IsNT + + ReadRegStr $1 ${Environ} "PATH" + StrCpy $5 $1 1 -1 + ${If} $5 != ";" + StrCpy $1 "$1;" ; ensure trailing ';' + ${EndIf} + Push $1 + Push "$0;" + Call un.StrStr + Pop $2 ; pos of our dir + StrCmp $2 "" done + + DetailPrint "Remove from PATH: $0" + StrLen $3 "$0;" + StrLen $4 $2 + StrCpy $5 $1 -$4 ; $5 is now the part before the path to remove + StrCpy $6 $2 "" $3 ; $6 is now the part after the path to remove + StrCpy $3 "$5$6" + StrCpy $5 $3 1 -1 + ${If} $5 == ";" + StrCpy $3 $3 -1 ; remove trailing ';' + ${EndIf} + WriteRegExpandStr ${Environ} "PATH" $3 + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + +done: + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 Pop $1 - StrCmp $1 1 unRemoveFromPath_NT - ; Not on NT - StrCpy $1 $WINDIR 2 - FileOpen $1 "$1\autoexec.bat" r - GetTempFileName $4 - FileOpen $2 $4 w - GetFullPathName /SHORT $0 $0 - StrCpy $0 "SET PATH=%PATH%;$0" - Goto unRemoveFromPath_dosLoop - - unRemoveFromPath_dosLoop: - FileRead $1 $3 - StrCpy $5 $3 1 -1 # read last char - StrCmp $5 $6 0 +2 # if DOS EOF - StrCpy $3 $3 -1 # remove DOS EOF so we can compare - StrCmp $3 "$0$\r$\n" unRemoveFromPath_dosLoopRemoveLine - StrCmp $3 "$0$\n" unRemoveFromPath_dosLoopRemoveLine - StrCmp $3 "$0" unRemoveFromPath_dosLoopRemoveLine - StrCmp $3 "" unRemoveFromPath_dosLoopEnd - FileWrite $2 $3 - Goto unRemoveFromPath_dosLoop - unRemoveFromPath_dosLoopRemoveLine: - SetRebootFlag true - Goto unRemoveFromPath_dosLoop - - unRemoveFromPath_dosLoopEnd: - FileClose $2 - FileClose $1 - StrCpy $1 $WINDIR 2 - Delete "$1\autoexec.bat" - CopyFiles /SILENT $4 "$1\autoexec.bat" - Delete $4 - Goto unRemoveFromPath_done - - unRemoveFromPath_NT: - ReadRegStr $1 ${WriteEnvStr_RegKey} "PATH" - StrCpy $5 $1 1 -1 # copy last char - StrCmp $5 ";" +2 # if last char != ; - StrCpy $1 "$1;" # append ; - Push $1 - Push "$0;" - Call un.StrStr ; Find `$0;` in $1 - Pop $2 ; pos of our dir - StrCmp $2 "" unRemoveFromPath_done - ; else, it is in path - # $0 - path to add - # $1 - path var - StrLen $3 "$0;" - StrLen $4 $2 - StrCpy $5 $1 -$4 # $5 is now the part before the path to remove - StrCpy $6 $2 "" $3 # $6 is now the part after the path to remove - StrCpy $3 $5$6 - - StrCpy $5 $3 1 -1 # copy last char - StrCmp $5 ";" 0 +2 # if last char == ; - StrCpy $3 $3 -1 # remove last char - - WriteRegExpandStr ${WriteEnvStr_RegKey} "PATH" $3 - SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 - - unRemoveFromPath_done: - Pop $6 - Pop $5 - Pop $4 - Pop $3 - Pop $2 - Pop $1 - Pop $0 + Pop $0 FunctionEnd + ########################################### # Utility Functions # ########################################### @@ -912,7 +692,7 @@ Function ${un}IsNT Pop $0 Push 0 Return - + IsNT_yes: ; NT!!! Pop $0 diff --git a/share/default.cfg b/share/default.cfg new file mode 100644 index 0000000..0f06378 --- /dev/null +++ b/share/default.cfg @@ -0,0 +1,16 @@ +[gpac.alias] +-play=-i @{1} aout vout +-plist=flist:srcs=@{-:N} vout aout +-info=src=@{+1:N} inspect +-mplay=src=@{+1:N} aout vout +-bench=reframer:raw @ -o null -stats -i @{1} +-vbench=vout:!vsync -i @{1} -stats +-gui=-noprog compositor:player=gui -xopt +[gpac.aliasdoc] +-play=play file. EX: gpac -play source.mp4 +-plist=build seamless playlist of files. EX: gpac -plist s1.mp4 [... sN.mp4] +-info=inspect files. EX: gpac -info s1.mp4 [... sN.mp4] +-mplay=play input files in parallel. EX: gpac -mplay s1.mp4 [... sN.mp4] +-bench=check decoding speed of source. EX: gpac -bench source.mp4 +-vbench=check decoding and display speed of source. EX: gpac -vbench source.mp4 +-gui=launch GUI (see `gpac -gui -h` for GUI options and `gpac -h[x] compositor` for compositor options) diff --git a/gui/iphone_wm_gui.js b/share/deprecated/iphone_wm_gui.js similarity index 97% rename from gui/iphone_wm_gui.js rename to share/deprecated/iphone_wm_gui.js index 651f59b..b0de49b 100644 --- a/gui/iphone_wm_gui.js +++ b/share/deprecated/iphone_wm_gui.js @@ -40,11 +40,11 @@ function adaptLayoutToSize() { if (l_deb < log_level) { alert("[UI] adaptLayoutToSize"); } - display_width = parseInt(gpac.get_option('General', 'LastWidth')); - display_height = parseInt(gpac.get_option('General', 'LastHeight')); + display_width = parseInt(scene.get_option('General', 'LastWidth')); + display_height = parseInt(scene.get_option('General', 'LastHeight')); alert("display "+display_width+" "+display_height); - if (!gpac.fullscreen && display_width && display_height) { - gpac.set_size(display_width, display_height); + if (!scene.fullscreen && display_width && display_height) { + scene.set_size(display_width, display_height); } var tmpObject, tmpObj2; // get size to adapt to @@ -485,10 +485,10 @@ function initialize() { alert("[UI] initialize"); } init = false; - var display_width = parseInt(gpac.get_option('Widgets', 'LastWMWidth')); - var display_height = parseInt(gpac.get_option('Widgets', 'LastWMHeight')); + var display_width = parseInt(scene.get_option('Widgets', 'LastWMWidth')); + var display_height = parseInt(scene.get_option('Widgets', 'LastWMHeight')); if (display_width && display_height) { - gpac.set_size(display_width, display_height); + scene.set_size(display_width, display_height); } root = document.documentElement; homepage = document.getElementById('homepage'); @@ -834,8 +834,8 @@ function resize() { adjustWhereWidgets(false); previousWidth = document.documentElement.viewport.width; previousHeight = document.documentElement.viewport.height; - gpac.set_option("Widgets", "LastWMWidth", '' + previousWidth); - gpac.set_option("Widgets", "LastWMHeight", '' + previousHeight); + scene.set_option("Widgets", "LastWMWidth", '' + previousWidth); + scene.set_option("Widgets", "LastWMHeight", '' + previousHeight); } // @@ -1313,7 +1313,7 @@ function refillWidgetAddList(flag) { removeAllChildren(widgetAddList); fllist = null; flstart = 0; - fllist = gpac.enum_directory(gpac.last_working_directory, "", false); + fllist = Sys.enum_directory(Sys.last_wdir, "", false); fillWidgetAddList(flag); } @@ -1321,21 +1321,21 @@ function refillWidgetAddList(flag) { // go to parent directory // function flUpDir(evt) { - var s = gpac.last_working_directory; + var s = Sys.last_wdir; if (l_inf <= log_level) { - alert("[UI] lwd:" + gpac.last_working_directory); + alert("[UI] lwd:" + Sys.last_wdir); } var index = s.lastIndexOf("\\"); if (index != -1) { - gpac.last_working_directory = s.substring(0, index); + Sys.last_wdir = s.substring(0, index); refillWidgetAddList(isThisAScan); } else { index = s.lastIndexOf("/"); if (index != -1) { - gpac.last_working_directory = s.substring(0, index); + Sys.last_wdir = s.substring(0, index); refillWidgetAddList(isThisAScan); } else { - gpac.last_working_directory = "/"; + Sys.last_wdir = "/"; refillWidgetAddList(isThisAScan); } } @@ -1346,17 +1346,17 @@ function flUpDir(evt) { // function flGoTo(newDir) { //alert("goto "+newDir); - var s = gpac.last_working_directory; + var s = Sys.last_wdir; if (s == "/") { - gpac.last_working_directory = newDir; + Sys.last_wdir = newDir; } else { var c = s.charAt(s.length - 1); if (c != '\\' && c != '/') { s += "/"; } - gpac.last_working_directory = s + newDir; + Sys.last_wdir = s + newDir; } - //alert(gpac.last_working_directory); + //alert(Sys.last_wdir); refillWidgetAddList(isThisAScan); } @@ -1390,7 +1390,7 @@ function flNextFiles(evt) { // scan the current directory recursively for widgets, clean up and return to home page // function flScanDir(evt) { - scan_directory(gpac.last_working_directory); + scan_directory(Sys.last_wdir); state = 'home'; var nbWidgets = getNbWidgets(); widgetContainer.setAttribute('display', 'none'); @@ -1410,7 +1410,7 @@ function flScanDir(evt) { // function scan_directory(dir) { var ii, j, count, list, w, uri, loadedWidgets = 0; - list = gpac.enum_directory(dir, '.xml;.wgt', 0); + list = Sys.enum_directory(dir, '.xml;.wgt', 0); for (ii = 0; ii < list.length; ii++) { uri = list[ii].path + list[ii].name; if (list[ii].directory) { @@ -1448,10 +1448,10 @@ function scan_directory(dir) { function fillWidgetAddList(flag) { if (flag) { widgetAddList.appendChild(use("cartoucheflag")); - document.getElementById("dirflag").textContent = gpac.last_working_directory; + document.getElementById("dirflag").textContent = Sys.last_wdir; } else { widgetAddList.appendChild(use("cartouche")); - document.getElementById("dir").textContent = gpac.last_working_directory; + document.getElementById("dir").textContent = Sys.last_wdir; } // next lines are file names var obj; @@ -1468,7 +1468,7 @@ function fillWidgetAddList(flag) { obj.listener = createGoto(escaping(fllist[i + flstart].name)); obj.addEventListener("click", obj.listener, false); } else if (isWidgetFileName(fllist[i + flstart].name)) { - obj.listener = createWidgetInstall(escaping(gpac.last_working_directory + '/' + fllist[i + flstart].name)); + obj.listener = createWidgetInstall(escaping(Sys.last_wdir + '/' + fllist[i + flstart].name)); obj.addEventListener("click", obj.listener, false); } } @@ -1561,7 +1561,7 @@ function media(etype, uri, w, h) { child.setAttribute('width', w); child.setAttribute('height', h); if (etype == 'animation') { - child.setAttributeNS('http://gpac.sourceforge.net/svg-extensions', 'use-as-primary', 'false'); + child.setAttributeNS('http://gpac.io/svg-extensions', 'use-as-primary', 'false'); } return child; } diff --git a/gui/iphone_wm_gui.svg b/share/deprecated/iphone_wm_gui.svg similarity index 99% rename from gui/iphone_wm_gui.svg rename to share/deprecated/iphone_wm_gui.svg index 63551da..12601a5 100644 --- a/gui/iphone_wm_gui.svg +++ b/share/deprecated/iphone_wm_gui.svg @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/gui/extensions/about/init.js b/share/gui/extensions/about/init.js new file mode 100644 index 0000000..a11314f --- /dev/null +++ b/share/gui/extensions/about/init.js @@ -0,0 +1,15 @@ + +my_ext = { + whoami: "GPAC-GUI-Extension", + name: "About", + icon: "info.svg", + author: "JeanLF", + description: "About GPAC", + url: "http://gpac.io/", + execjs: ["info.js"], + autostart: false, + config_id: "ABOUT", + requires_gl: false, + version_major: 1, + version_minor: 0 +}; diff --git a/gui/extensions/bifs_tests/applications-other.svg b/share/gui/extensions/bifs_tests/applications-other.svg similarity index 100% rename from gui/extensions/bifs_tests/applications-other.svg rename to share/gui/extensions/bifs_tests/applications-other.svg diff --git a/gui/extensions/bifs_tests/bifs_tests.js b/share/gui/extensions/bifs_tests/bifs_tests.js similarity index 100% rename from gui/extensions/bifs_tests/bifs_tests.js rename to share/gui/extensions/bifs_tests/bifs_tests.js diff --git a/gui/extensions/bifs_tests/init.js b/share/gui/extensions/bifs_tests/init.js similarity index 100% rename from gui/extensions/bifs_tests/init.js rename to share/gui/extensions/bifs_tests/init.js diff --git a/gui/extensions/player/applications-multimedia.svg b/share/gui/extensions/player/applications-multimedia.svg similarity index 100% rename from gui/extensions/player/applications-multimedia.svg rename to share/gui/extensions/player/applications-multimedia.svg diff --git a/gui/extensions/player/fileopen.js b/share/gui/extensions/player/fileopen.js similarity index 94% rename from gui/extensions/player/fileopen.js rename to share/gui/extensions/player/fileopen.js index 74bc06b..76105bb 100644 --- a/gui/extensions/player/fileopen.js +++ b/share/gui/extensions/player/fileopen.js @@ -1,7 +1,7 @@ extension.open_local_file = function () { var filebrowse = gw_new_file_dialog(null, 'Open file'); filebrowse.filter = '*'; - filebrowse.browse(gpac.last_working_directory); + filebrowse.browse(Sys.last_wdir); filebrowse.extension = this; this.file_open_dlg = true; @@ -25,7 +25,7 @@ extension.open_local_file = function () { if (value == null) { this.extension.controler.show(); } else { - if (directory) gpac.last_working_directory = directory; + if (directory) Sys.last_wdir = directory; this.extension.set_movie_url(value); } this.file_open_dlg = false; @@ -46,8 +46,11 @@ extension.open_local_file = function () { var f = history[history.length - i - 1]; var names = f.url.split('/'); if (names.length == 0) names = f.url.split('\\'); - - o.name = names.pop(); + if (names.length == 0) { + o.name = names.pop(); + } else { + o.name = f.url; + } o.directory = false; o.path = f.url; var delim = f.url.slice(-1); @@ -151,9 +154,9 @@ extension.open_local_file = function () { item.index = hist_idx; item.on_click = function () { var fb_dlg = this.dlg.dlg; - path += '#LIVE360TV'; + path += '#VR'; fb_dlg.extension.set_movie_url(path); - if (directory) gpac.last_working_directory = directory; + if (directory) Sys.last_wdir = directory; fb_dlg.close(); this.dlg.close(); } @@ -181,11 +184,12 @@ extension.open_local_file = function () { popup.edit = gw_new_text_edit(popup.area, ''); popup.edit.dlg = popup; - gpac.set_focus(popup.edit); + scene.set_focus(popup.edit); popup.edit.on_text = function (val) { if (val != '') { this.dlg.dlg.on_browse(val, null); this.dlg.dlg.close(); + this.dlg.dlg = null; } this.dlg.close(); } @@ -200,14 +204,15 @@ extension.open_local_file = function () { popup.set_alpha(1.0); popup.on_close = function () { - this.dlg.enable(); + if (this.dlg) { + this.dlg.enable(); + } } popup.show(); } - filebrowse.on_display_size(gw_display_width, gw_display_height); - if (gpac.hardware_rgba) filebrowse.set_alpha(0.8); + if (scene.hardware_rgba) filebrowse.set_alpha(0.8); this.controler.hide(); filebrowse.show(); diff --git a/share/gui/extensions/player/init.js b/share/gui/extensions/player/init.js new file mode 100644 index 0000000..99eec5b --- /dev/null +++ b/share/gui/extensions/player/init.js @@ -0,0 +1,24 @@ + +obj = { + whoami: "GPAC-GUI-Extension", + name: "Player", + icon: "applications-multimedia.svg", + author: "JeanLF", + description: "Nice test extension", + url: "http://foo.bar", + execjs: ["player.js", "fileopen.js", "playlist.js", "stats.js"], + autostart: true, + config_id: "Player", + requires_gl: false, + version_major: 1, + version_minor: 0, + clopts: [ + {name: "-stat", type: "", description: "show GUI stats"}, + {name: "-loop", type: "", description: "loop content"}, + {name: "-speed", type: "number", description: "set speed content"}, + {name: "-play-from", type: "number", description: "play from given time"}, + {name: "-service", type: "int", description: "set initial service ID for multi-service sources"}, + {name: "-addon", type: "URL", description: "inject specified URL as addon of main content"}, + ], + clusage: "[options] URL [options] [URL2]", +}; diff --git a/gui/extensions/player/player.js b/share/gui/extensions/player/player.js similarity index 89% rename from gui/extensions/player/player.js rename to share/gui/extensions/player/player.js index c5958ad..786b97a 100644 --- a/gui/extensions/player/player.js +++ b/share/gui/extensions/player/player.js @@ -41,7 +41,7 @@ extension = { initial_loop: false, initial_speed: 1, initial_start: 0, - show_stats_init: 0, + show_stats_init: false, def_width: 600, def_height: 600, disabled: false, @@ -57,6 +57,7 @@ extension = { stats_resources: [], nb_objs_at_last_scan: 0, stats_timer: null, + test_mode: false, do_show_controler: function () { if (this.file_open_dlg) { @@ -143,10 +144,6 @@ extension = { this.controler.fullscreen.on_click(); return true; } - if (evt.keycode == 'F8') { - gpac.reload(); - return true; - } */ return false; @@ -175,6 +172,13 @@ extension = { this.movie.children[0].extension = this; + if (gwskin.mobile_device) { + var size = scene.screen_width; + if (size > scene.screen_height) size = scene.screen_height; + this.def_width = size; + } + + this.movie.children[0].on_scene_size = function (evt) { var ext = this.extension; @@ -183,7 +187,11 @@ extension = { //process the error or connect service if (evt.error) { - var notif = gw_new_message(null, 'Error opening file!', 'Failed to open ' + this.url[0] + '\n\nReason: ' + gpac.error_string(evt.error)); + if (ext.test_mode) { + scene.exit(1); + } + + var notif = gw_new_message(null, 'Error opening file!', 'Failed to open ' + this.url[0] + '\n\nReason: ' + Sys.error_string(evt.error)); notif.show(); var url = ext.movie.children[0].url.length ? ext.movie.children[0].url[0] : ''; @@ -205,9 +213,10 @@ extension = { //switch back inline nodes and remove from dictionary gw_detach_child(this); - //force detach, we don't know when GC will be done - ext.movie.children[0].url[0] = ''; + let old_inline = ext.movie.children[0]; ext.movie.children[0] = this; + //force detach, we don't know when GC will be done + old_inline.url[0] = ''; if (evt.error) return; @@ -221,7 +230,8 @@ extension = { ext.movie_control.mediaStopTime = ext.initial_start; } ext.movie_control.loop = ext.initial_loop; - alert('mc.mST is ' + ext.movie_control.mediaStopTime); + ext.controler.loop.switch_icon(ext.movie_control.loop ? 1 : 0); + ext.movie_control.url[0] = ext.current_url; ext.movie_sensor.url[0] = ext.current_url; @@ -232,6 +242,7 @@ extension = { ext.initial_speed = 1; ext.initial_start = 0; + gwlog(l_deb, 'URL connected'); if ((ext.current_url.indexOf('gpac://') == 0) && ((ext.current_url.indexOf('://') < 0) || (ext.current_url.indexOf('file://') == 0))) { ext.local_url = true; @@ -239,23 +250,22 @@ extension = { ext.local_url = false; } - ext.root_odm = gpac.get_object_manager(ext.current_url); + ext.root_odm = scene.get_object_manager(ext.current_url); ext.reverse_playback_supported = ext.root_odm.reverse_playback_supported; ext.controler.hide(); var names = ext.current_url.split('/'); if (names.length == 0) names = ext.current_url.split('\\'); - gpac.caption = names.pop(); + scene.caption = names.pop(); ext.add_bookmark(ext.current_url, true); gwskin.enable_background(false); ext.declare_addons(); - if (ext.initial_service_id) { - var odm = gpac.get_object_manager(ext.current_url); - if (odm) odm.select_service(ext.initial_service_id); + if (ext.initial_service_id && ext.root_odm) { + ext.root_odm.select_service(ext.initial_service_id); ext.initial_service_id = 0; } @@ -268,41 +278,40 @@ extension = { if (ext.show_stats_init) { ext.view_stats(); } + if (ext.test_mode) { + ext.coverage_tests(); + } + ext.stats_timer.start(); } if (!ext.movie_connected) { ext.movie_connected = true; - gpac.set_3d(evt.type3d ? 1 : 0); + scene.set_3d(evt.type3d ? 1 : 0); ext.controler.play.switch_icon(ext.icon_pause); ext.dynamic_scene = evt.dynamic_scene; //force display size notif on controler to trigger resize of the window ext.controler.on_display_size(ext.controler.width, ext.controler.height); } - ext.root_odm = gpac.get_object_manager(ext.current_url); +// ext.root_odm = scene.get_object_manager(ext.current_url); ext.set_state(ext.GF_STATE_PLAY); - if (!gpac.fullscreen && evt.width && evt.height) { - var w, h, r_w, r_h; + //override scene size info + scene.set_size(evt.width, evt.height, true); + + if (!scene.fullscreen && evt.width && evt.height) { + var w, h; w = evt.width; h = evt.height; - gpac.set_size(w, h, true); + if (w > scene.screen_width) w = scene.screen_width; + if (h > scene.screen_height) h = scene.screen_height; - if (w > gpac.screen_width) w = gpac.screen_width; - if (h > gpac.screen_height) h = gpac.screen_height; + if (w= 100) percent_dload = 0; this.extension.controler.media_line.set_progress(percent_dload); + } else if (evt.bufferLevel && (evt.bufferRemainingTime>1000) ) { + var buf_lev = evt.bufferLevel; + if (evt.bufferLevel >= 100) buf_lev = 0; + this.extension.controler.media_line.set_progress(buf_lev); } } @@ -361,9 +374,6 @@ extension = { this.extension.controler.play.switch_icon(this.extension.icon_pause); - if (!this.extension.root_odm) { - this.extension.root_odm = gpac.get_object_manager(this.extension.current_url); - } if (this.extension.root_odm) { this.extension.declare_addons(); } @@ -645,20 +655,20 @@ extension = { wnd.snd_low.add_icon('audio_mute'); wnd.snd_low.on_click = function () { if (this.extension.muted) { - gpac.volume = this.extension.muted; + scene.volume = this.extension.muted; this.extension.muted = 0; this.switch_icon(0); } else { - this.extension.muted = gpac.volume ? gpac.volume : 1; - gpac.volume = 0; + this.extension.muted = scene.volume ? scene.volume : 1; + scene.volume = 0; this.switch_icon(1); } } wnd.snd_ctrl.on_slide = function (value, type) { if (this.extension.muted) this.extension.controler.snd_low.on_click(); - gpac.volume = value; + scene.volume = value; } - wnd.snd_ctrl.set_value(gpac.volume); + wnd.snd_ctrl.set_value(scene.volume); wnd.open = null; if (!gwskin.browser_mode) { @@ -719,13 +729,13 @@ extension = { wnd.fullscreen = gw_new_icon(wnd.infobar, 'fullscreen'); wnd.fullscreen.add_icon(gwskin.images.fullscreen_back); wnd.fullscreen.on_click = function () { - gpac.fullscreen = !gpac.fullscreen; + scene.fullscreen = !scene.fullscreen; } - wnd.fullscreen.switch_icon(gpac.fullscreen ? 1 : 0); + wnd.fullscreen.switch_icon(scene.fullscreen ? 1 : 0); if (!gwskin.browser_mode) { wnd.exit = gw_new_icon(wnd.infobar, 'exit'); - wnd.exit.on_click = function () { gpac.exit(); } + wnd.exit.on_click = function () { scene.exit(); } } else { wnd.exit = null; } @@ -741,7 +751,7 @@ extension = { if (this.time) this.time.set_size(4 * wnd.time.font_size(), control_icon_size); if (this.snd_ctrl) { this.snd_ctrl.set_size(2 * control_icon_size, control_icon_size / 2, control_icon_size / 3, control_icon_size / 2); - this.snd_ctrl.set_value(gpac.volume); + this.snd_ctrl.set_value(scene.volume); } var speed = this.extension.movie_control.mediaSpeed; @@ -761,7 +771,6 @@ extension = { var control_icon_size = gwskin.default_icon_height; var is_over = true; var show_navigate = false; - if (arguments.length == 0) { width = this.width; height = this.height; @@ -771,20 +780,19 @@ extension = { this.on_size(width, height); if (this.fullscreen) - this.fullscreen.switch_icon(gpac.fullscreen ? 1 : 0); + this.fullscreen.switch_icon(scene.fullscreen ? 1 : 0); width -= control_icon_size / 2; min_w = control_icon_size; if (this.time.visible) min_w += this.time.width; if (this.open) min_w += control_icon_size; if (this.home) min_w += control_icon_size; - if (this.exit && gpac.fullscreen) min_w += control_icon_size; + if (this.exit && scene.fullscreen) min_w += control_icon_size; full_w = 0; if (this.snd_low) full_w += control_icon_size; if (this.snd_ctrl) full_w += this.snd_ctrl.width; if (this.fullscreen) full_w += control_icon_size; - if (this.navigate) { this.navigate.hide(); @@ -793,12 +801,12 @@ extension = { full_w += control_icon_size; wnd.navigate.on_click = function () { -// var sensors_active = gpac.sensors_active; -// gpac.sensors_active = !sensors_active; +// var sensors_active = scene.sensors_active; +// scene.sensors_active = !sensors_active; // this.switch_icon(sensors_active ? 1 : 0); this.extension.select_navigation_vr(); } - } else if ( (this.extension.dynamic_scene != 1) && this.extension.movie_connected && (gpac.navigation_type != GF_NAVIGATE_TYPE_NONE)) { + } else if ( (this.extension.dynamic_scene != 1) && this.extension.movie_connected && (scene.navigation_type != GF_NAVIGATE_TYPE_NONE)) { show_navigate = true; full_w += control_icon_size; @@ -806,7 +814,6 @@ extension = { this.extension.select_navigation_type(); } } - } if (this.extension.movie_connected) { @@ -841,7 +848,7 @@ extension = { this.stop.hide(); this.play.hide(); } - + if (this.extension.duration) { if (this.forward) full_w += control_icon_size; if (this.extension.duration>1) { @@ -862,14 +869,13 @@ extension = { this.back_live.hide(); } - if (this.remote && UPnP.MediaRenderersCount && (this.extension.current_url != '')) { full_w += control_icon_size; } time_w = this.media_line.visible ? 2 * control_icon_size : 0; if (this.exit) { - if (gpac.fullscreen) { + if (scene.fullscreen) { this.exit.show(); } else { this.exit.hide(); @@ -932,9 +938,9 @@ extension = { wnd.on_display_size = function (width, height) { var h; - if (!gpac.fullscreen) { + if (!scene.fullscreen) { if (width < this.extension.def_width) { - gpac.set_size(this.extension.def_width, height); + scene.set_size(this.extension.def_width, height); return; } } else { @@ -953,9 +959,7 @@ extension = { } else h = 1.1 * gwskin.default_icon_height; - this.set_size(this.extension.def_width, h); - this.extension.safe_interact.set_size(gwskin.default_icon_height, gwskin.default_icon_height); this.extension.safe_interact.show(); this.extension.safe_interact.move((-gwskin.default_icon_height+width)/2, (gwskin.default_icon_height-height)/2); @@ -989,54 +993,63 @@ extension = { this.init_extension(); //check our args - var i, argc = gpac.argc; + var i; var url_arg = null; - - for (i = 1; i < argc; i++) { - var arg = gpac.get_arg(i); + var prog_name = Sys.args[0]; + var check_gpac_args = 1; + //check mp4client or MP4Client + if (prog_name && (prog_name.indexOf('lient')>=0)) + check_gpac_args = 0; + + for (i = 1; i < Sys.args.length; i++) { + var arg = Sys.args[i]; if (!arg) break; + if (check_gpac_args==1) { + if (arg=='-xopt') check_gpac_args=2; + continue; + } + //that's our file if (arg.charAt(0) != '-') { //do not reopen ourselves ! if (arg.indexOf('gui.bt') >= 0) continue; - url_arg if (arg.indexOf('://') < 0) url_arg = 'gpac://' + arg; else url_arg = arg; + continue; } //MP4Client options taking 2 args - else if ((arg == '-rti') || (arg == '-rtix') || (arg == '-c') || (arg == '-cfg') || (arg == '-size') || (arg == '-lf') || (arg == '-log-file') || (arg == '-logs') - || (arg == '-opt') || (arg == '-ifce') || (arg == '-views') || (arg == '-avi') || (arg == '-out') || (arg == '-ntp-shift') || (arg == '-fps') || (arg == '-scale') || (arg == '-run-for') - ) { - i++; - } else if (arg == '-service') { - this.initial_service_id = parseInt(gpac.get_arg(i + 1)); - i++; - } else if (arg == '-com') { - arg = gpac.get_arg(i + 1); - var idx = arg.indexOf('gpac add '); - - if (idx == 0) { - this.default_addon = arg.substring(9); + if (!check_gpac_args) { + if ((arg == '-rti') || (arg == '-rtix') || (arg == '-p') || (arg == '-size') || (arg == '-lf') || (arg == '-log-file') || (arg == '-logs') + || (arg == '-opt') || (arg == '-ifce') || (arg == '-views') || (arg == '-mosaic') || (arg == '-avi') || (arg == '-out') || (arg == '-ntp-shift') + || (arg == '-fps') || (arg == '-scale') || (arg == '-run-for') || (arg == '-bl') + ) { + i++; + continue; } + } - i++; - } else if (arg == '-addon') { - this.default_addon = gpac.get_arg(i + 1); - i++; + if (arg.startsWith('-service=')) { + this.initial_service_id = parseInt(arg.substring(9)); + } else if (arg.startsWith('-addon=')) { + this.default_addon = arg.substring(7); } else if (arg == '-loop') { this.initial_loop = true; - } else if (arg == '-stats') { + } else if ((arg == '-stats') || (arg == '-stat')) { + this.show_stats_init = true; + } else if (arg == '-gui-test') { + this.test_mode = true; this.show_stats_init = true; - } else if (arg == '-speed') { - this.initial_speed = Number(gpac.get_arg(i + 1)); - i++; - } else if (arg == '-play-from') { - this.initial_start = Number(gpac.get_arg(i + 1)); - i++; + } else if (arg.startsWith('-speed=')) { + this.initial_speed = Number(arg.substring(7)); + } else if (arg.startsWith('-play-from=')) { + this.initial_start = Number(arg.substring(11)); + } else { + continue; } + Sys.set_arg_used(i, true); } gwskin.media_url = ''; @@ -1054,20 +1067,41 @@ extension = { return; } this.set_playlist_mode(true); - - /* - var label = ''; - for (i = 1; i < argc; i++) { - label += '#'+i + ': ' + gpac.get_arg(i) + '\n'; - } - var notif = gw_new_message(null, 'GPAC Arguments', label); - notif.show(); - */ - } this.controler.show(); gw_hide_dock(); + + }, + + coverage_tests: function() { + scene.trigger_gc(); + scene.navigation_supported(GF_NAVIGATE_EXAMINE); + scene.move_window(0,0, true); + scene.show_keyboard(true); + scene.switch_quality(true); + var z = scene.zoom; + z = scene.text_selection; + + //test filtersession API + let nb_filters = session.nb_filters; + print("init ok, filter: " + nb_filters); + for (let idx=0; idx3)) { + scene.exit(3); + } h = Math.floor(value / 3600); value -= h * 3600; @@ -1276,7 +1313,6 @@ extension = { this.controler.play.switch_icon(this.icon_play); this.state = this.GF_STATE_STOP; this.set_speed(1); - this.root_odm = null; this.stats_resources = []; this.nb_objs_at_last_scan = 0; @@ -1285,6 +1321,7 @@ extension = { e.is_playing = false; e.index = this.playlist_idx; gwlib_filter_event(e); + this.root_odm = null; return; } if (state == this.GF_STATE_PAUSE) { @@ -1367,14 +1404,14 @@ extension = { gwskin.media_url = url; gwskin.pvr_url = ''; - if ((url == '') || (url == this.current_url)) { this.movie.children[0].url[0] = url; this.movie_control.url[0] = url; this.movie_sensor.url[0] = url; if (this.UPnP_Enabled) UPnP.MovieURL = url; this.movie_connected = (url == '') ? false : true; - gpac.caption = url; + scene.caption = url; + this.root_odm = null; this.controler.layout(); } else if (this.controlled_renderer == null) { @@ -1386,21 +1423,23 @@ extension = { this.movie_control.url[0] = url; this.movie_sensor.url[0] = url; this.movie.children[0].url[0] = url; + this.root_odm = null; return; } this.default_addon = null; this.root_odm = null; + this.stoped_url = null; /*create a temp inline holding the previous scene, use it as the main movie and use the old inline to test the resource. This avoids messing up with event targets already setup*/ var movie_inline = this.movie.children[0]; var temp_inline = new SFNode('Inline'); - if (typeof movie_inline.url[0] != 'undefined') - temp_inline.url[0] = movie_inline.url[0]; + if (movie_inline.url[0] && (typeof movie_inline.url[0] != 'undefined')) { + temp_inline.url[0] = movie_inline.url[0]; + } this.movie.children[0] = temp_inline; - movie_inline.callback_done = false; this.movie_connected = false; @@ -1410,7 +1449,6 @@ extension = { } else { movie_inline.url[0] = url; } - if (!movie_inline.callback_done) { gw_add_child(this.dictionary, movie_inline); } @@ -1470,9 +1508,9 @@ extension = { }, select_navigation_vr: function () { - var sensors_active = gpac.sensors_active; + var sensors_active = scene.sensors_active; var extension = this; -// gpac.sensors_active = !sensors_active; +// scene.sensors_active = !sensors_active; // this.switch_icon(sensors_active ? 1 : 0); var nb_items = 0; @@ -1487,34 +1525,33 @@ extension = { wnd.on_close = function () { this.extension.navigation_wnd = null; } - wnd.add_menu_item(sensors_active ? "Keyboard+mouse" : "Sensors", function () { gpac.sensors_active = !sensors_active; } ); + wnd.add_menu_item(sensors_active ? "Keyboard+mouse" : "Sensors", function () { scene.sensors_active = !sensors_active; } ); if (this.view_stereo==extension.GF_VIEW_MONO) { wnd.add_menu_item("Stereo", function () { extension.view_stereo = extension.GF_VIEW_STEREO; - gpac.set_option("Compositor", "FramePacking", "None"); - gpac.set_option("Compositor", "StereoType", "StereoHeadset"); + scene.set_option("Compositor", "stereo", "hmd"); } ); } else if (this.view_stereo==extension.GF_VIEW_STEREO) { wnd.add_menu_item("Stereo Side", function () { extension.view_stereo = extension.GF_VIEW_STEREO_SIDE; - gpac.set_option("Compositor", "Framepacking", "Side"); - gpac.set_option("Compositor", "StereoType", "StereoHeadset"); + scene.set_option("Compositor", "fpack", "side"); + scene.set_option("Compositor", "stereo", "hmd"); } ); } else if (this.view_stereo==extension.GF_VIEW_STEREO_SIDE) { wnd.add_menu_item("Stereo Top", function () { extension.view_stereo = extension.GF_VIEW_STEREO_TOP; - gpac.set_option("Compositor", "FramePacking", "Top"); - gpac.set_option("Compositor", "StereoType", "StereoHeadset"); + scene.set_option("Compositor", "fpack", "top"); + scene.set_option("Compositor", "stereo", "hmd"); } ); } else { wnd.add_menu_item("Mono", function () { extension.view_stereo = extension.GF_VIEW_MONO; - gpac.set_option("Compositor", "FramePacking", "None"); - gpac.set_option("Compositor", "StereoType", "None"); + scene.set_option("Compositor", "fpack", "none"); + scene.set_option("Compositor", "stereo", "none"); } ); } @@ -1526,7 +1563,7 @@ extension = { select_navigation_type: function () { var nb_items = 0; - var type = gpac.navigation; + var type = scene.navigation; if (this.navigation_wnd) { this.navigation_wnd.close(); return; @@ -1542,14 +1579,14 @@ extension = { wnd.select = function (type) { this.extension.navigation_wnd = null; if (type == 'reset') { - gpac.navigation_type = 0; + scene.navigation_type = 0; } else { - gpac.navigation = type; + scene.navigation = type; } } wnd.make_select_item = function (text, type, current_type) { if (current_type == type) text = '* ' + text; - if (gpac.navigation_supported(type)) { + if (scene.navigation_supported(type)) { wnd.add_menu_item(text, function () { this.select(type); }); } } @@ -1559,7 +1596,7 @@ extension = { wnd.make_select_item('Slide', GF_NAVIGATE_SLIDE, type); wnd.make_select_item('Examine', GF_NAVIGATE_EXAMINE, type); - if (gpac.navigation_type == GF_NAVIGATE_TYPE_3D) { + if (scene.navigation_type == GF_NAVIGATE_TYPE_3D) { wnd.make_select_item('Walk', GF_NAVIGATE_WALK, type); wnd.make_select_item('Fly', GF_NAVIGATE_FLY, type); wnd.make_select_item('Pan', GF_NAVIGATE_PAN, type); @@ -1619,6 +1656,8 @@ extension = { wnd.make_item = function (text, i) { wnd.add_menu_item(text, function () { + this.extension.stats_resources = []; + this.extension.nb_objs_at_last_scan = 0; this.extension.root_odm.select_service(this.extension.services[i].service_id); }); } @@ -1643,6 +1682,7 @@ extension = { if (root_odm && this.dynamic_scene) { for (var res_i = 0; res_i < root_odm.nb_resources; res_i++) { var m = root_odm.get_resource(res_i); + if (!m) continue; if (root_odm.selected_service != m.service_id) continue; if (m.get_srd() != null) continue; @@ -1711,6 +1751,7 @@ extension = { for (var res_i = 0; res_i < root.nb_resources; res_i++) { var m = root.get_resource(res_i); if (!m) continue; + if (m.disabled) continue; if (m.service_id && (m.service_id != selected_service)) continue; m.dependent_group_id=0; @@ -1734,15 +1775,13 @@ extension = { var ext = this.ext; var wnd = ext.stats_wnd; var nb_buff = 0; - if (!ext.root_odm) return; - + if (ext.nb_objs_at_last_scan != ext.root_odm.nb_resources) { ext.stats_resources = []; ext.gather_stats_resources(ext.root_odm, ext.root_odm.selected_service); ext.reload_stats(); } - if (ext.stats_data.length >= ext.stats_window) { ext.stats_data.splice(0, 1); } @@ -1752,10 +1791,10 @@ extension = { var t = new Date() stat_obj.time = Math.round(t.getTime() / 1000); - stat_obj.fps = Math.round(100 * gpac.fps) / 100; - stat_obj.cpu = gpac.cpu; - stat_obj.memory = Math.round(100 * gpac.memory / 1000 / 1000) / 100; - stat_obj.http_bandwidth = Math.round(gpac.http_bitrate); + stat_obj.fps = Math.round(100 * scene.fps) / 100; + stat_obj.cpu = Sys.process_cpu_usage; + stat_obj.memory = Math.round(100 * Sys.gpac_memory / 1000 / 1000) / 100; + stat_obj.http_bandwidth = Math.round(session.http_bitrate); stat_obj.bitrate = 0; stat_obj.buffer = 0; stat_obj.ntp_diff = 0; @@ -1779,7 +1818,7 @@ extension = { } } else bl = 100; - + if (m.dependent_group_id) { var dq_idx=0; while (1) { @@ -1801,9 +1840,7 @@ extension = { } if (stat_obj) { - if (m.ntp_diff > stat_obj.ntp_diff) { - stat_obj.ntp_diff = m.ntp_diff; - } + stat_obj.ntp_diff = m.ntp_diff >= 0 ? m.ntp_diff : m.ntp_diff/1000; } if (wnd) { diff --git a/gui/extensions/player/playlist.js b/share/gui/extensions/player/playlist.js similarity index 96% rename from gui/extensions/player/playlist.js rename to share/gui/extensions/player/playlist.js index 7cd6a95..7379d99 100644 --- a/gui/extensions/player/playlist.js +++ b/share/gui/extensions/player/playlist.js @@ -89,7 +89,7 @@ extension.view_playlist = function () { fb.on_browse = function (value, directory) { this.playlist.show(); plist.add_files(value, false, false); - if (directory) gpac.last_working_directory = directory; + if (directory) Sys.last_wdir = directory; } fb.on_close = function () { @@ -113,7 +113,7 @@ extension.view_playlist = function () { item.set_size(popup.width, gwskin.default_control_height); item.dlg = popup; item.on_click = function() { - var filelist = gpac.enum_directory(path, '*', false); + var filelist = Sys.enum_directory(path, '*', false); item.dlg.fb.playlist.add_files(filelist, false, false); item.dlg.fb.close(); item.dlg.close(); @@ -122,7 +122,7 @@ extension.view_playlist = function () { item.set_size(popup.width, gwskin.default_control_height); item.dlg = popup; item.on_click = function() { - var filelist = gpac.enum_directory(path, '*', false); + var filelist = Sys.enum_directory(path, '*', false); item.dlg.fb.playlist.add_files(filelist, true, false); item.dlg.fb.close(); item.dlg.close(); @@ -200,7 +200,7 @@ extension.view_playlist = function () { obj.name = list[i].name; ar.push(obj); } - if (list.length) gpac.last_working_directory = list[0].path; + if (list.length) Sys.last_wdir = list[0].path; //sort if (!this._sort_type) { @@ -290,8 +290,12 @@ extension.view_playlist = function () { } plist.on_display_size = function (width, height) { - var w = 2*width / 3; - this.set_size(w, 0.8 * height); + if (gwskin.mobile_device) { + this.set_size(width, height); + } else { + var w = 2*width / 3; + this.set_size(w, 0.8 * height); + } } plist.on_display_size(gw_display_width, gw_display_height); diff --git a/gui/extensions/player/stats.js b/share/gui/extensions/player/stats.js similarity index 92% rename from gui/extensions/player/stats.js rename to share/gui/extensions/player/stats.js index db2a756..5eab69b 100644 --- a/gui/extensions/player/stats.js +++ b/share/gui/extensions/player/stats.js @@ -80,7 +80,7 @@ extension.view_stats = function () { notif.show(); return; } - this.odm.select_quality(this.odm.gui.qualities[idx - 1].ID, this.odm.dependent_group_id); + this.odm.select_quality('' + (idx - 1), this.odm.dependent_group_id); } else { this.odm.select_quality('auto', this.odm.dependent_group_id); @@ -215,7 +215,12 @@ extension.view_stats = function () { iw.area = gw_new_text_area(iw, ''); iw.on_display_size = function (width, height) { - var w = 30 * gwskin.default_text_font_size; + var w; + if (gwskin.mobile_device) { + w = width; + } else { + w = 30 * gwskin.default_text_font_size; + } var h = 2 * gwskin.default_icon_height + 11 * gwskin.default_text_font_size; this.set_size(w, h); } @@ -239,7 +244,7 @@ extension.view_stats = function () { label += '' + m.samplerate + 'Hz ' + m.channels + ' channels'; } label += '\n' - label += 'Status: ' + m.status + ' - clock time: ' + m.clock_time + ' (drift ' + m.clock_drift + ')'; + label += 'Status: ' + m.status + ' - clock time: ' + m.clock_time; // + ' (drift ' + m.clock_drift + ')'; label += '\n' label += 'Composition Memory: ' + m.cb_unit_count + '/' + m.cb_capacity; @@ -268,11 +273,18 @@ extension.view_stats = function () { if (bw < 8000000) label += '' + Math.round(bw / 1000) + ' Kbps'; else label += '' + Math.round(bw / 1000 / 1000) + ' Mbps'; - label += '\n' - label += 'Download bandwidth: '; var bw = m.bandwidth_down; - if (bw < 8000) label += '' + bw + ' Kbps'; - else label += '' + Math.round(bw / 1000) + ' Mbps'; + if (bw) { + label += '\n' + label += 'Download bandwidth: '; + if (bw < 8000) label += '' + bw + ' Kbps'; + else label += '' + Math.round(bw / 1000) + ' Mbps'; + } + var sender_diff = odm.ntp_sender_diff; + if (sender_diff != null) { + label += '\n' + label += 'NTP transmission diff: ' + sender_diff + ' ms'; + } label += '\n' label += 'Codec: ' + m.codec; @@ -292,7 +304,7 @@ extension.view_stats = function () { this.wnd.area.set_content(label); } - + iw.timer.start(0); odm.gui.info_wnd.on_display_size(gw_display_width, gw_display_height); odm.gui.info_wnd.set_alpha(0.9); odm.gui.info_wnd.show(); @@ -307,7 +319,7 @@ extension.view_stats = function () { nb_buffering++; m.gui.buffer = gw_new_gauge(wnd.area, 'Buffer'); } - if (m.ntp_diff) nb_ntp_diff++; + if (m.ntp_diff || this.show_stats_init) nb_ntp_diff++; } else if (!m.is_addon) { m.gui.play = gw_new_icon(wnd.area, 'play'); m.gui.play.odm = m; @@ -329,8 +341,8 @@ extension.view_stats = function () { wnd.http_control = gw_new_slider(wnd.area); - if (gpac.http_max_bitrate) { - var br = Math.round(100 * gpac.http_max_bitrate / 1000 / 1000) / 100; + if (session.http_max_bitrate) { + var br = Math.round(100 * session.http_max_bitrate / 1000 / 1000) / 100; if (br > 200) br = 200; if (br <= 1) { v = 30 * br; @@ -343,7 +355,7 @@ extension.view_stats = function () { } wnd.http_control.set_value(v); - wnd.http_text.set_label('HTTP cap ' + Math.round(gpac.http_max_bitrate / 10000)/100 + ' Mbps'); + wnd.http_text.set_label('HTTP cap ' + Math.round(session.http_max_bitrate / 10000)/100 + ' Mbps'); } else { wnd.http_control.set_value(100); wnd.http_text.set_label('HTTP cap off'); @@ -363,13 +375,13 @@ extension.view_stats = function () { } if (br == 200) { this.text.set_label('HTTP cap off'); - gpac.http_max_bitrate = Math.round(0); + session.http_max_bitrate = Math.round(0); } else if (br == 0) { this.text.set_label('HTTP disable'); - gpac.http_max_bitrate = -1; + session.http_max_bitrate = -1; } else { this.text.set_label('HTTP cap ' + Math.round(100 * br) / 100 + ' Mbps'); - gpac.http_max_bitrate = Math.round(br * 1000000); + session.http_max_bitrate = Math.round(br * 1000000); } } @@ -427,8 +439,13 @@ extension.view_stats = function () { var w; var h = 2 * gwskin.default_icon_height; - w = 20 * gwskin.default_text_font_size; - w += 4 * gwskin.default_icon_height; + if (gwskin.mobile_device) { + w = width; + } else { + w = 20 * gwskin.default_text_font_size; + w += 4 * gwskin.default_icon_height; + } + if (this.has_select) w += 6 * gwskin.default_icon_height; @@ -496,9 +513,10 @@ extension.view_stats = function () { } } - var label = 'Statistics (' + gpac.nb_cores + ' cores - '; - if (gpac.system_memory > 1000000000) label += '' + Math.round(gpac.system_memory / 1000 / 1000 / 1000) + ' GB RAM)'; - else label += '' + Math.round(gpac.system_memory / 1000 / 1000) + ' MB RAM)'; + var label = 'Statistics (' + Sys.nb_cores + ' cores - '; + let mem = Sys.physical_memory; + if (mem > 1000000000) label += '' + Math.round(mem / 1000 / 1000 / 1000) + ' GB RAM)'; + else label += '' + Math.round(mem / 1000 / 1000) + ' MB RAM)'; wnd.set_label(label); diff --git a/gui/extensions/showroom/init.js b/share/gui/extensions/showroom/init.js similarity index 100% rename from gui/extensions/showroom/init.js rename to share/gui/extensions/showroom/init.js diff --git a/gui/extensions/showroom/osmo.bt b/share/gui/extensions/showroom/osmo.bt similarity index 100% rename from gui/extensions/showroom/osmo.bt rename to share/gui/extensions/showroom/osmo.bt diff --git a/gui/extensions/showroom/showroom.js b/share/gui/extensions/showroom/showroom.js similarity index 100% rename from gui/extensions/showroom/showroom.js rename to share/gui/extensions/showroom/showroom.js diff --git a/gui/extensions/widget_manager/applications-system.svg b/share/gui/extensions/widget_manager/applications-system.svg similarity index 100% rename from gui/extensions/widget_manager/applications-system.svg rename to share/gui/extensions/widget_manager/applications-system.svg diff --git a/gui/extensions/widget_manager/init.js b/share/gui/extensions/widget_manager/init.js similarity index 98% rename from gui/extensions/widget_manager/init.js rename to share/gui/extensions/widget_manager/init.js index 5dcf24c..1f00e36 100644 --- a/gui/extensions/widget_manager/init.js +++ b/share/gui/extensions/widget_manager/init.js @@ -35,7 +35,7 @@ function check_widget_display(width, height) function scan_directory(dir) { var i, j, count, list, new_wid, uri; - list = gpac.enum_directory(dir, '.xml;.wgt;.mgt', 0); + list = Sys.enum_directory(dir, '.xml;.wgt;.mgt', 0); for (i=0; i { + var s = "" + opt.name; + if (opt.type.length) + s += " (" + opt.type + ")"; + s += ": "; + let olen = s.length; + while (olen<=30) { + olen++; + s+= ' '; + } + s += opt.description; + print(-1, s); + }); + } + } + globalThis._gpac_log_name = null; +} + //performs layout on all contents function layout() { @@ -215,19 +273,19 @@ function layout() function on_resize(evt) { if ((gw_display_width == evt.width) && (gw_display_height == evt.height)) return; if (evt.width <=100) { - gpac.set_size(100, gw_display_height); + scene.set_size(100, gw_display_height); return; } if (evt.height <=80) { - gpac.set_size(gw_display_width, 40); + scene.set_size(gw_display_width, 40); return; } gw_display_width = evt.width; gw_display_height = evt.height; - if (!gpac.fullscreen) { - gpac.set_option('General', 'LastWidth', '' + gw_display_width); - gpac.set_option('General', 'LastHeight', '' + gw_display_height); + if (!scene.fullscreen) { + scene.set_option('General', 'LastWidth', '' + gw_display_width); + scene.set_option('General', 'LastHeight', '' + gw_display_height); } /* var v = 12; diff --git a/gui/gwlib.js b/share/gui/gwlib.js similarity index 97% rename from gui/gwlib.js rename to share/gui/gwlib.js index 151310e..4f705e0 100644 --- a/gui/gwlib.js +++ b/share/gui/gwlib.js @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////// // // Authors: -// Jean Le Feuvre, Telecom ParisTech +// Jean Le Feuvre, (c) 2010-2020 Telecom Paris // ///////////////////////////////////////////////////////////////////////////////// @@ -11,41 +11,41 @@ function gwlog(lev, str) { } /*log levels*/ -l_err = 1; -l_war = 2; -l_inf = 3; -l_deb = 4; +let l_err = 1; +let l_war = 2; +let l_inf = 3; +let l_deb = 4; /*default log level*/ -gw_log_level = l_inf; +let gw_log_level = l_inf; -GF_JS_EVENT_BASE = 20000; +let GF_JS_EVENT_BASE = 20000; //playback event //@param is_playing boolean giving the playback status; -GF_JS_EVENT_PLAYBACK = GF_JS_EVENT_BASE; +let GF_JS_EVENT_PLAYBACK = GF_JS_EVENT_BASE; //playlist insert event //@param url string giving URL to append -GF_JS_EVENT_PLAYLIST_ADD = GF_JS_EVENT_BASE + 1; +let GF_JS_EVENT_PLAYLIST_ADD = GF_JS_EVENT_BASE + 1; //playlist reset event //@param none -GF_JS_EVENT_PLAYLIST_RESET = GF_JS_EVENT_BASE + 2; +let GF_JS_EVENT_PLAYLIST_RESET = GF_JS_EVENT_BASE + 2; //playlist reset event //@param index item index in playlist -GF_JS_EVENT_PLAYLIST_PLAY = GF_JS_EVENT_BASE + 3; +let GF_JS_EVENT_PLAYLIST_PLAY = GF_JS_EVENT_BASE + 3; function gw_new_timer(progressive) { - var obj = new SFNode('TimeSensor'); + let obj = new SFNode('TimeSensor'); obj.set_timeout = function (time, loop) { this.cycleInterval = time ? time : 1; this.loop = loop; } - this.startTime = -1; + obj.startTime = -1; obj.start = function (when) { if (arguments.length==0) this.startTime = this.getTime(); else this.startTime = when + this.getTime(); @@ -252,7 +252,7 @@ function gw_new_fontstyle(size, align) { return fs; } -gw_resources_bank = []; +let gw_resources_bank = []; //static function gw_load_resource(url, is_icon) { @@ -447,12 +447,16 @@ gwskin.last_hit_y = 0; gwskin.enable_background = function(do_enable) { if (do_enable) { - gpac.set_back_color(gwskin.back_color.r, gwskin.back_color.g, gwskin.back_color.b, 1.0); + scene.set_back_color(gwskin.back_color.r, gwskin.back_color.g, gwskin.back_color.b, 1.0); } else { - gpac.set_back_color(0, 0, 0, 1.0); + scene.set_back_color(0, 0, 0, 1.0); } } +gwskin.enable_focus = function(do_enable) { + this.focus_on = do_enable; +} + //static function gw_get_abs_pos(child) { var pos = new SFVec2f(0, 0); @@ -552,6 +556,7 @@ gwskin.tooltip_exec = function (obj, show) { gwskin.tooltip_callback = function (obj, show) { + if (! obj._tooltip) return; if (!show) { gwskin.tooltip_timeout.obj = null; gwskin.tooltip_timeout.stop(0); @@ -561,12 +566,13 @@ gwskin.tooltip_callback = function (obj, show) { gwskin.tooltip_timeout.obj = obj; gwskin.tooltip_timeout.on_active = function (val) { if (!val && gwskin.tooltip_timeout.obj) { + //deactivate tooltip after first run + obj._tooltip = false; gwskin.tooltip_exec(gwskin.tooltip_timeout.obj, true); } } gwskin.tooltip_timeout.start(0); } - gwskin.default_label_font_size = 18; gwskin.default_text_font_size = 18; gwskin.default_font_family = 'SANS'; @@ -574,10 +580,9 @@ gwskin.default_icon_text_spacing = 8; gwskin.default_control_height = 48; gwskin.default_icon_height = 32; - //create styles gwskin.styles = []; -var s = { name: 'default' }; +let s = { name: 'default' }; gwskin.styles.push(s); s.normal = gw_new_appearance(0.4, 0.6, 1); @@ -600,9 +605,8 @@ s.text.skin = true; s.font = gw_new_fontstyle(gwskin.default_label_font_size, 1); s.font.skin = true; - //overrite style for button to have its own text color -var s = { name: 'button' }; +s = { name: 'button' }; gwskin.styles.push(s); s.text = gw_new_appearance(1, 1, 1); s.text.skin = true; @@ -659,7 +663,7 @@ s.font.skin = true; s.hide = gw_window_hide; s.show = gw_window_show; -tt_s = { name: 'tooltip' }; +let tt_s = { name: 'tooltip' }; gwskin.styles.push(tt_s); tt_s.normal = gw_new_appearance(0.6, 0.6, 0.6); tt_s.normal.texture = s.normal.texture; @@ -825,7 +829,7 @@ gwskin.labels.playlist_prev = 'Previous'; gwskin.images.channels = 'icons/tv.svg'; gwskin.labels.channels = 'TV Channels'; gwskin.images.view360 = 'icons/image.svg'; -gwskin.labels.view360 = '360°'; +gwskin.labels.view360 = 'VR'; gwskin.images.sensors = 'icons/compass.svg'; gwskin.labels.sensors = 'Orientation'; @@ -955,16 +959,16 @@ function gwlib_init(root_node) { gw_ui_root._name = 'Root Display'; gw_ui_root.has_popup = false; gw_add_child(root_node, gw_ui_root); - gw_ui_root.set_focus = function(wnd) { gw_ui_top_wnd = null; if (typeof wnd.on_event != 'undefined') { gw_ui_top_wnd = wnd; } if (typeof (wnd._no_focus) == 'boolean') return; - gpac.set_focus(gw_ui_top_wnd); + if (gwskin.focus_on) + scene.set_focus(gw_ui_top_wnd); } - + gw_ui_root.remove_focus = function(wnd) { gw_ui_top_wnd = null; for (var i = this.children.length; i > 0; i--) { @@ -978,7 +982,8 @@ function gwlib_init(root_node) { } } if (typeof (wnd._no_focus) == 'boolean') return; - gpac.set_focus(gw_ui_top_wnd); + if (gwskin.focus_on) + scene.set_focus(gw_ui_top_wnd); } gw_ui_root.add_child = function (child) { @@ -993,32 +998,31 @@ function gwlib_init(root_node) { gw_ui_top_wnd = null; gw_event_filters = []; - gpac.set_event_filter(gwlib_filter_event); - gwskin.has_opengl = (gpac.get_option('Compositor', 'OpenGLMode') != 'disable') ? true : false; + scene.set_event_filter(gwlib_filter_event); + gwskin.has_opengl = scene.has_opengl; - gwskin.browser_mode = (gpac.get_option('Temp', 'BrowserMode') == 'yes') ? true : false; + gwskin.browser_mode = (scene.get_option('Temp', 'BrowserMode') == 'yes') ? true : false; - gpac.focus_highlight = false; + scene.focus_highlight = false; gwskin.disable_transparency = false; //remove window gradients - if (!gwskin.has_opengl && !gpac.hardware_rgba) { + if (!gwskin.has_opengl && !scene.hardware_rgba) { s = gwskin.get_style('window', 'normal'); s.texture = null; gwskin.disable_transparency = true; gwskin.default_window_alpha = 1; } + var device = scene.get_option('core', 'devclass'); - var device = gpac.get_option('General', 'DeviceType'); - - if ((device == 'iOS') || (device == 'Android')) gwskin.mobile_device = true; + if ((device == 'ios') || (device == 'android')) gwskin.mobile_device = true; else gwskin.mobile_device = false; if (gwskin.mobile_device) { - var size = gw_display_width; - if (size> gw_display_height) size = gw_display_height; - gwskin_set_default_control_height(size/3); - gwskin_set_default_icon_height(size/3); + var size = scene.screen_width; + if (size > scene.screen_height) size = scene.screen_height; + gwskin_set_default_control_height(size/6); + gwskin_set_default_icon_height(size/12); } gwskin_set_white_blue(); @@ -1060,12 +1064,9 @@ function gwlib_init(root_node) { return this._to_string(obj); } gwskin.parse = function (serial_obj) { - if (serial_obj.charAt(0) != '(') { - return eval('(' + serial_obj + ')' ); - } + gwlog(l_deb, 'JSON eval ' + serial_obj); return eval(serial_obj); } - } function gwlib_add_event_filter(evt_filter) { @@ -1164,7 +1165,7 @@ function gw_close_child_list(list) { } } //trigger GC whenever we have closed a child list - gpac.trigger_gc(); + scene.trigger_gc(); } @@ -1353,12 +1354,12 @@ function gw_new_rectangle(class_name, style) { obj.set_size = function (w, h) { var hw, hh, rx_bl, ry_bl, rx_br, ry_br, rx_tl, ry_tl, rx_tr, ry_tr, rx, ry; - var temp; + let shape = this.children[0]; + var temp = shape.geometry.type; hw = w / 2; hh = h / 2; this.width = w; this.height = h; - temp = this.children[0].geometry.type; temp[0] = 7; temp[1] = 1; temp[2] = 7; @@ -1875,7 +1876,7 @@ function gw_new_icon_button(parent, icon_url, label, horizontal, class_name) { } if (this.on_over) this.on_over(value); - if (this._tooltip && gwskin.tooltip_callback) gwskin.tooltip_callback(this, value); + if (gwskin.tooltip_callback) gwskin.tooltip_callback(this, value); } }; Browser.addRoute(obj._touch, 'isOver', obj, obj._on_over); @@ -2114,7 +2115,7 @@ function gw_new_spincontrol(parent, horizontal) { return false; } this._idx = idx; - gpac.set_focus(this.children[idx]); + scene.set_focus(this.children[idx]); return true; } return false; @@ -2235,7 +2236,7 @@ function grid_event_navigate(dlg, children, type) { if (dlg.current_focus == -1) { if (type == 'Left') return false; dlg.current_focus = 0; - gpac.set_focus(children[0]); + scene.set_focus(children[0]); return true; } @@ -2245,7 +2246,7 @@ function grid_event_navigate(dlg, children, type) { var tr = children[dlg.current_focus].translation.y - children[dlg.current_focus].height / 2; if (dlg.current_focus + 1 == children.length) { dlg.current_focus = orig_focus; - gpac.set_focus(children[dlg.current_focus]); + scene.set_focus(children[dlg.current_focus]); return false; } /*goto right item*/ @@ -2256,7 +2257,7 @@ function grid_event_navigate(dlg, children, type) { break; if (dlg.current_focus + 1 == children.length) { dlg.current_focus = orig_focus; - gpac.set_focus(children[dlg.current_focus]); + scene.set_focus(children[dlg.current_focus]); return false; } dlg.current_focus++; @@ -2274,7 +2275,7 @@ function grid_event_navigate(dlg, children, type) { var tr = children[dlg.current_focus].translation.y + children[dlg.current_focus].height / 2; if (!dlg.current_focus) { dlg.current_focus = -1; - gpac.set_focus(null); + scene.set_focus(null); return false; } /*goto left item*/ @@ -2370,7 +2371,7 @@ function grid_event_navigate(dlg, children, type) { return grid_event_navigate(dlg, children, type); } - gpac.set_focus(children[dlg.current_focus]); + scene.set_focus(children[dlg.current_focus]); return true; } @@ -2430,7 +2431,7 @@ function gw_new_grid_container(parent) { if (this.current_focus >= 0) { if (this.current_focus < child) this.current_focus = child; else if (this.current_focus >= last_child) this.current_focus = last_child - 1; - gpac.set_focus(this._all_children[this.current_focus]); + scene.set_focus(this._all_children[this.current_focus]); } @@ -2677,7 +2678,7 @@ function gw_new_progress_bar(parent, vertical, with_progress, class_name) { this.children[1].move(0, (this.height - pheight) / 2, 0); } else { var pwidth = prog * this.width; - this.children[1].set_size(pwidth, this.height); + this.children[1].set_size(pwidth, this.height/3); this.children[1].move((pwidth - this.width) / 2, 0); } } @@ -2902,7 +2903,7 @@ function gw_new_text_edit(parent, text_data) { gw_add_child(obj, rect); gw_object_set_hitable(rect); rect.on_down = function (val) { - if (val) gpac.set_focus(this.parent.children[1]); + if (val) scene.set_focus(this.parent.children[1]); } edit = gw_new_text(obj, text_data, 'edit') @@ -3119,15 +3120,17 @@ function gw_new_message(container, label, content) { } notif.set_size(0.8 * gw_display_width, 120); - notif.timer = gw_new_timer(false); - notif.timer.wnd = notif; - notif.timer.set_timeout(gwskin.default_message_timeout, false); - notif.timer.start(0); - notif.timer.on_active = function (val) { - if (!val) this.wnd.close(); + if (arguments.length < 4) { + notif.timer = gw_new_timer(false); + notif.timer.wnd = notif; + notif.timer.set_timeout(gwskin.default_message_timeout, false); + notif.timer.start(0); + notif.timer.on_active = function (val) { + if (!val) this.wnd.close(); + } + notif.show_effect = 'notif'; + notif._no_focus = true; } - notif.show_effect = 'notif'; - notif._no_focus = true; return notif; } @@ -3182,10 +3185,10 @@ function gw_guess_mime_icon(name) var idx = 0; while (1) { - var mime = gpac.get_option('MimeTypes', idx); + var mime = scene.get_option('MimeTypes', idx); if (mime == null) break; idx++; - var mime_ext = gpac.get_option('MimeTypes', mime).split('"')[1]; + var mime_ext = scene.get_option('MimeTypes', mime).split('"')[1]; if (!mime_ext.match(reg)) continue; if (mime.indexOf('video') != -1) return gwskin.images.mime_video; @@ -3373,11 +3376,16 @@ function gw_new_file_dialog(container, label) { } else { if (dir) this.directory = dir; if (up && !this.path) up = false; - filelist = gpac.enum_directory(this.directory, this.filter, up); + filelist = Sys.enum_directory(this.directory, this.filter, up); if (filelist.length) { + let dir_name = filelist[0].path; + dir_name = dir_name.substr(0, dir_name.lastIndexOf("/")); + dir_name = dir_name.split('/').pop(); + if (dir_name.length == 0) dir_name = "/"; + this.directory = filelist[0].path; - this.set_label(this.directory); + this.set_label(dir_name); } else { this.set_label(''); } @@ -3407,7 +3415,8 @@ function gw_new_file_dialog(container, label) { if ( (!is_listing || (typeof filelist[i].directory == 'boolean')) && (filelist[i].directory || (filelist[i].name.indexOf('.') < 0))) { if (filelist[i].drive) icon_name = gwskin.images.drive; - else icon_name = gwskin.images.folder; + else if (filelist[i].directory) icon_name = gwskin.images.folder; + else icon_name = gwskin.images.mime_generic; } else { icon_name = gw_guess_mime_icon(is_listing ? f_path : f_name); } @@ -3454,7 +3463,6 @@ function gw_new_file_dialog(container, label) { } - function gw_new_plotter(parent) { var touch; var obj = new SFNode('Transform2D'); @@ -3626,4 +3634,3 @@ function gw_new_popup(anchor, type) return popup; } - diff --git a/gui/icons/add.svg b/share/gui/icons/add.svg similarity index 100% rename from gui/icons/add.svg rename to share/gui/icons/add.svg diff --git a/gui/icons/app.svg b/share/gui/icons/app.svg similarity index 100% rename from gui/icons/app.svg rename to share/gui/icons/app.svg diff --git a/gui/icons/audio.svg b/share/gui/icons/audio.svg similarity index 100% rename from gui/icons/audio.svg rename to share/gui/icons/audio.svg diff --git a/gui/icons/audio_full.svg b/share/gui/icons/audio_full.svg similarity index 100% rename from gui/icons/audio_full.svg rename to share/gui/icons/audio_full.svg diff --git a/gui/icons/audio_mute.svg b/share/gui/icons/audio_mute.svg similarity index 100% rename from gui/icons/audio_mute.svg rename to share/gui/icons/audio_mute.svg diff --git a/gui/icons/check.svg b/share/gui/icons/check.svg similarity index 100% rename from gui/icons/check.svg rename to share/gui/icons/check.svg diff --git a/gui/icons/close.svg b/share/gui/icons/close.svg similarity index 100% rename from gui/icons/close.svg rename to share/gui/icons/close.svg diff --git a/gui/icons/compass.svg b/share/gui/icons/compass.svg similarity index 100% rename from gui/icons/compass.svg rename to share/gui/icons/compass.svg diff --git a/gui/icons/cross.svg b/share/gui/icons/cross.svg similarity index 100% rename from gui/icons/cross.svg rename to share/gui/icons/cross.svg diff --git a/gui/icons/down.svg b/share/gui/icons/down.svg similarity index 100% rename from gui/icons/down.svg rename to share/gui/icons/down.svg diff --git a/gui/icons/expand.svg b/share/gui/icons/expand.svg similarity index 100% rename from gui/icons/expand.svg rename to share/gui/icons/expand.svg diff --git a/gui/icons/file.svg b/share/gui/icons/file.svg similarity index 100% rename from gui/icons/file.svg rename to share/gui/icons/file.svg diff --git a/gui/icons/film.svg b/share/gui/icons/film.svg similarity index 100% rename from gui/icons/film.svg rename to share/gui/icons/film.svg diff --git a/gui/icons/folder.svg b/share/gui/icons/folder.svg similarity index 100% rename from gui/icons/folder.svg rename to share/gui/icons/folder.svg diff --git a/gui/icons/harddrive.svg b/share/gui/icons/harddrive.svg similarity index 100% rename from gui/icons/harddrive.svg rename to share/gui/icons/harddrive.svg diff --git a/gui/icons/heart.svg b/share/gui/icons/heart.svg similarity index 100% rename from gui/icons/heart.svg rename to share/gui/icons/heart.svg diff --git a/gui/icons/home.svg b/share/gui/icons/home.svg similarity index 100% rename from gui/icons/home.svg rename to share/gui/icons/home.svg diff --git a/gui/icons/image.svg b/share/gui/icons/image.svg similarity index 100% rename from gui/icons/image.svg rename to share/gui/icons/image.svg diff --git a/gui/icons/info.svg b/share/gui/icons/info.svg similarity index 100% rename from gui/icons/info.svg rename to share/gui/icons/info.svg diff --git a/gui/icons/laptop.svg b/share/gui/icons/laptop.svg similarity index 100% rename from gui/icons/laptop.svg rename to share/gui/icons/laptop.svg diff --git a/gui/icons/left.svg b/share/gui/icons/left.svg similarity index 100% rename from gui/icons/left.svg rename to share/gui/icons/left.svg diff --git a/gui/icons/list.svg b/share/gui/icons/list.svg similarity index 100% rename from gui/icons/list.svg rename to share/gui/icons/list.svg diff --git a/gui/icons/live.svg b/share/gui/icons/live.svg similarity index 100% rename from gui/icons/live.svg rename to share/gui/icons/live.svg diff --git a/gui/icons/media_next.svg b/share/gui/icons/media_next.svg similarity index 100% rename from gui/icons/media_next.svg rename to share/gui/icons/media_next.svg diff --git a/gui/icons/media_prev.svg b/share/gui/icons/media_prev.svg similarity index 100% rename from gui/icons/media_prev.svg rename to share/gui/icons/media_prev.svg diff --git a/gui/icons/monitor.svg b/share/gui/icons/monitor.svg similarity index 100% rename from gui/icons/monitor.svg rename to share/gui/icons/monitor.svg diff --git a/gui/icons/more.svg b/share/gui/icons/more.svg similarity index 100% rename from gui/icons/more.svg rename to share/gui/icons/more.svg diff --git a/gui/icons/musical.svg b/share/gui/icons/musical.svg similarity index 100% rename from gui/icons/musical.svg rename to share/gui/icons/musical.svg diff --git a/gui/icons/navigation.svg b/share/gui/icons/navigation.svg similarity index 100% rename from gui/icons/navigation.svg rename to share/gui/icons/navigation.svg diff --git a/gui/icons/network.svg b/share/gui/icons/network.svg similarity index 100% rename from gui/icons/network.svg rename to share/gui/icons/network.svg diff --git a/gui/icons/next.svg b/share/gui/icons/next.svg similarity index 100% rename from gui/icons/next.svg rename to share/gui/icons/next.svg diff --git a/gui/icons/osmo.svg b/share/gui/icons/osmo.svg similarity index 100% rename from gui/icons/osmo.svg rename to share/gui/icons/osmo.svg diff --git a/gui/icons/overflowing.svg b/share/gui/icons/overflowing.svg similarity index 100% rename from gui/icons/overflowing.svg rename to share/gui/icons/overflowing.svg diff --git a/gui/icons/pause.svg b/share/gui/icons/pause.svg similarity index 100% rename from gui/icons/pause.svg rename to share/gui/icons/pause.svg diff --git a/gui/icons/pl_next.svg b/share/gui/icons/pl_next.svg similarity index 100% rename from gui/icons/pl_next.svg rename to share/gui/icons/pl_next.svg diff --git a/gui/icons/pl_prev.svg b/share/gui/icons/pl_prev.svg similarity index 100% rename from gui/icons/pl_prev.svg rename to share/gui/icons/pl_prev.svg diff --git a/gui/icons/play.svg b/share/gui/icons/play.svg similarity index 100% rename from gui/icons/play.svg rename to share/gui/icons/play.svg diff --git a/gui/icons/play_loop.svg b/share/gui/icons/play_loop.svg similarity index 100% rename from gui/icons/play_loop.svg rename to share/gui/icons/play_loop.svg diff --git a/gui/icons/play_shuffle.svg b/share/gui/icons/play_shuffle.svg similarity index 100% rename from gui/icons/play_shuffle.svg rename to share/gui/icons/play_shuffle.svg diff --git a/gui/icons/play_single.svg b/share/gui/icons/play_single.svg similarity index 100% rename from gui/icons/play_single.svg rename to share/gui/icons/play_single.svg diff --git a/gui/icons/power.svg b/share/gui/icons/power.svg similarity index 100% rename from gui/icons/power.svg rename to share/gui/icons/power.svg diff --git a/gui/icons/previous.svg b/share/gui/icons/previous.svg similarity index 100% rename from gui/icons/previous.svg rename to share/gui/icons/previous.svg diff --git a/gui/icons/remove.svg b/share/gui/icons/remove.svg similarity index 100% rename from gui/icons/remove.svg rename to share/gui/icons/remove.svg diff --git a/gui/icons/resize.svg b/share/gui/icons/resize.svg similarity index 100% rename from gui/icons/resize.svg rename to share/gui/icons/resize.svg diff --git a/gui/icons/rewind.svg b/share/gui/icons/rewind.svg similarity index 100% rename from gui/icons/rewind.svg rename to share/gui/icons/rewind.svg diff --git a/gui/icons/right.svg b/share/gui/icons/right.svg similarity index 100% rename from gui/icons/right.svg rename to share/gui/icons/right.svg diff --git a/gui/icons/seek_forward.svg b/share/gui/icons/seek_forward.svg similarity index 100% rename from gui/icons/seek_forward.svg rename to share/gui/icons/seek_forward.svg diff --git a/gui/icons/shrink.svg b/share/gui/icons/shrink.svg similarity index 100% rename from gui/icons/shrink.svg rename to share/gui/icons/shrink.svg diff --git a/gui/icons/sort.svg b/share/gui/icons/sort.svg similarity index 100% rename from gui/icons/sort.svg rename to share/gui/icons/sort.svg diff --git a/gui/icons/speed.svg b/share/gui/icons/speed.svg similarity index 100% rename from gui/icons/speed.svg rename to share/gui/icons/speed.svg diff --git a/gui/icons/star.svg b/share/gui/icons/star.svg similarity index 100% rename from gui/icons/star.svg rename to share/gui/icons/star.svg diff --git a/gui/icons/stop.svg b/share/gui/icons/stop.svg similarity index 100% rename from gui/icons/stop.svg rename to share/gui/icons/stop.svg diff --git a/gui/icons/stop2.svg b/share/gui/icons/stop2.svg similarity index 100% rename from gui/icons/stop2.svg rename to share/gui/icons/stop2.svg diff --git a/gui/icons/trash.svg b/share/gui/icons/trash.svg similarity index 100% rename from gui/icons/trash.svg rename to share/gui/icons/trash.svg diff --git a/gui/icons/tray.svg b/share/gui/icons/tray.svg similarity index 100% rename from gui/icons/tray.svg rename to share/gui/icons/tray.svg diff --git a/gui/icons/tv.svg b/share/gui/icons/tv.svg similarity index 100% rename from gui/icons/tv.svg rename to share/gui/icons/tv.svg diff --git a/gui/icons/up.svg b/share/gui/icons/up.svg similarity index 100% rename from gui/icons/up.svg rename to share/gui/icons/up.svg diff --git a/gui/icons/world.svg b/share/gui/icons/world.svg similarity index 100% rename from gui/icons/world.svg rename to share/gui/icons/world.svg diff --git a/share/lang/fr.txt b/share/lang/fr.txt new file mode 100644 index 0000000..ded274c --- /dev/null +++ b/share/lang/fr.txt @@ -0,0 +1,2440 @@ +[gpac] +mem-track=active le suivi de mémoire +mem-track_crc=3153737396 +mem-track-stack= active le suivi de mémoire avec analyse de la pile +mem-track-stack_crc=335838253 +p=utilise le profil indiqué pour la configuration globale de GPAC. Si non trouvé, un fichier de configuration est créé. Si un chemin de fichier est indiqué, le profil est charger à partir de ce fichier +p_crc=3615791768 +ltf=active les filtres unitaires de tests (utilisés uniquement pour les tests unitaires) +ltf_crc=2218501803 +loop=boucle l'exécution de la session, créant une session à chaque boucle (principalement utilisée pour les tests). Si aucune valeur n'est donnée, boucle pour toujours +loop_crc=2404405591 +wp=écrit toutes les options de filtres dans le fichier de configuration +wp_crc=2059005166 +wpx=écrit toutes les options de filtres et tous les arguments de méta-filtres dans le fichier de configuration (fichier de configuration volumineux!) +wpx_crc=2002959884 +stats=affiche les statistiques après exécution. Les statistiques peuvent être visualisées à l'exécution en tapant 's' dans l'invite. +stats_crc=247941264 +graph=affiche le graphe après exécution. Le graphique peut être visualisé à l'exécution en tapant 'g' dans l'invite +graph_crc=4042996408 +seps=@définit les jeux de caractères par défaut utilisés pour séparer différents arguments. + Le premier caractère est utilisé pour séparer les noms d'arguments + Le deuxième caractère, s'il est présent, est utilisé pour séparer les noms et les valeurs + Le troisième caractère, s'il est présent, est utilisé pour séparer les fragments des sources pid + Le quatrième caractère, s'il est présent, est utilisé pour les séparateurs de liste (sourceIDs, gfreg, ...) + Le cinquième caractère, s'il est présent, est utilisé pour la négation booléenne + Le sixième caractère, s'il existe, est utilisé pour les directives LINK (cf -h doc)@ +seps_crc=3475228674 +i=spécifie un fichier d'entrée - voir la documentation générale -h doc +i_crc=1595699293 +o=spécifie un fichier de sortie - voir la documentation générale -h doc +o_crc=3724167134 +h=@affiche l'aide. Par défaut, seules les options de base sont imprimées. -ha affiche les options avancées, -hx imprime les options expertes et -hh imprime toutes les options. Le paramètre de chaîne peut être: + vide: affiche l'aide de base de la ligne de commande. + doc: affiche les informations générales sur le filtre. + log: affiche l'aide du système de journalisation. + core: affiche les options de base de libgpac supportées. Utilisez -ha / -hx / -hh pour les options en mode avancé / expert / complet. + filtres: affiche le nom de tous les filtres disponibles. + filtres: *: affiche le nom de tous les filtres disponibles, y compris les méta-filtres. + codecs: affiche les codecs pris en charge. + props: affiche les propriétés de pid intégrées prises en charge. + links: affiche les connexions possibles entre chaque filtre. + FNAME: imprime les informations de filtre FNAME (plusieurs FNAME peuvent être spécifiés). Pour les méta-filtres, utilisez FNAME: INST, par exemple ffavin: avfoundation. Utilisez * pour imprimer des informations sur tous les filtres (avertissement, grande sortie!), *: * Pour imprimer des informations sur tous les filtres, y compris les instances de méta filtres (avertissement, grande sortie!). Par défaut, seules les options de filtre de base et la description sont affichées. Utilisez -ha pour afficher les options avancées et filtrer les capacités d'E / S, -hx pour les options expertes, -hh pour toutes les options@ +h_crc=4136223486 +doc=@Aide de l'outil en ligne de commande de GPAC pour la mise en place et l'execution de chaînes de filtres. +Voir -h pour les options disponibles. + +Général + +Les filtres dans GPAC sont des unités de traitement configurables consommant et produisant des paquets de données. Ces paquets sont acheminés entre les filtres via un canal de données appelé pid. +Un pid est responsable de l'allocation / suivi des paquets de données et du passage des paquets au(x) filtre(s) de destination. +Un pid de sortie de filtre peut être connecté à zéro ou plusieurs filtres. Une connexion multiple en sortie est gérée en interne par le moteur de filtres (pas besoin de filter spécifique pour cela dans GPAC). +Lorsqu'un pid ne peut être connecté à aucun filtre, un avertissement est émis et tous les paquets envoyés sur ce pid seront immédiatement détruits. La session peut toutefois encore être exécutée. +Chaque pid en sortie porte un ensemble de propriétés décrivant les données qu'il fournit (par exemple, largeur, hauteur, codec, ...). +Les propriétés peuvent être intégrées (identifiées par un code à 4 caractères dans GPAC) ou définies par l'utilisateur (identifiées par une chaîne). +Chaque pid suit ses modifications de propriétés et déclenche la reconfiguration du filtre lors du traitement des paquets. +Cela permet à la chaîne de filtres d'être reconfigurée au moment de l'exécution, ce qui permet de recharger une partie de la chaîne (par exemple, décharger un décodeur vidéo lors du passage de sources compressées à des sources non compressées). +Chaque filtre expose un ou plusieurs ensembles de fonctionnalités, appelés ensembles de fonctionnalités, qui sont un type de propriété et des valeurs qui doivent être mises en correspondance ou exclues dans les connexions pid. +Chaque filtre expose également un ensemble d'arguments à configurer, à l'aide de types de propriété et de chaîne de charactères. + +Cette aide est fournie avec les séparateurs par défaut :=#,@ pour spécifier les filtres, les propriétés et les options. Voir -seps pour les changer. + +Format des propriétés de PID + +Les propriétés booléennes sont au format yes/true/1 ou no/false/0 +Les propriétés d'énumération (pour les arguments) doivent utiliser la syntaxe donnée dans la description de l'argument, sinon la valeur 0 (première de l'énumération) est prise en compte. +Les propriétés à 1 dimension (nombres, flottants, entiers ...) sont formatées en valeur[unité], où unité peut être k/K (x1000) ou m/M (x1000000) ou g/G (x1000000000). +Pour de telles propriétés, valeur +I signifie valeur maximale possible, -I valeur minimale possible. +Les propriétés des fractions sont au format num/den ou num-den ou num, auquel cas le dénominateur est 1. +Les propriétés d'entier 32 bits non signées peuvent être spécifiées en hexadécimal au format 0xAABBCCDD. +Les propriétés de dimension N (vecteurs) sont au format DIM1xDIM2 [xDIM3 [xDIM4]], sans multiplicateur. +Les propriétés de données sont formatées comme suit: +"taille@adresse": bloc de données constant, non copié en interne; taille donne la taille du bloc, adresse le pointeur de données. +"0xBYTESTRING": bloc de données spécifié en hexadécimal, copié en interne. +Les propriétés du pointeur sont au format "adresse", adresse donnant l'adresse du pointeur (32 ou 64 bits selon les plateformes). +Les listes de chaînes et d'entiers sont formatées comme suit: val1, val2 [, ...] + +Remarque: Les caractères spéciaux dans les formats de propriété (0x, /, -, + I, -I, x) ne peuvent pas être configurés. + +Déclaration de filtre [FILTER_DECL] + +Les filtres sont répertoriés par nom, avec les options ajoutées sous forme de liste de paires Nom = Valeur séparées par des deux-points. +La valeur peut être omise pour les booléens, la valeur par défaut étant true (par exemple, noedit). Utiliser '!' Avant le nom, annule le résultat (par exemple: !moof_first) +Le nom peut être omis pour les énumérations (par exemple :disp=pbo est équivalent à :pbo), à condition que les développeurs de filtres fassent attention à ne pas réutiliser les noms d'énumérations dans un même filtre! + +Lorsque des paramètres textes sont utilisés (par exemple, des URL), il est recommandé d'utiliser le mot-clé "gpac" pour séparer les arguments. +EX: filtre: ARG=http://foo/bar?yes:gpac:opt=VAL +Cela permettra d'extraire correctement l'URL +EX: filtre: ARG=http://foo/bar?yes:opt=VAL +Cela ne parviendra pas à l'extraire l'option et conservera :opt=VAL dans l'URL +Remarque: le mécanisme d'échappement n'est pas nécessaire pour les sources locales, pour lesquelles l'existence du fichier est recherchée lors de l'analyse des arguments +Il n'est également pas nécessaire pour les procotoles intégrés (avin://, video://, audio://, pipe://) +Pour les protocoles tcp:// et udp://, l'échappement n'est pas nécessaire si un / final est ajouté après le port +EX: -i tcp: //127.0.0.1: 1234:OPT +Cela n'arrivera pas à extraire l'URL et les options +EX: -i tcp: //127.0.0.1: 1234/:OPT +Cela va extraire l'URL et les options +Remarque: une astuce pour éviter la séquence d'échappement consiste à déclarer l'option urls à la fin, par exemple, f1:opt1=foo:url=http:// bar, à condition que vous n'ayez qu'une seule URL. Voir les arguments héritant ci-dessous. + +Les filtres source et destination n'ont pas besoin d'être adressés par le nom du filtre, il suffit de spécifier src= ou dst=. +Vous pouvez également utiliser la syntaxe -src URL ou -i URL pour les sources et -dst URL ou -o URL pour les destinations. +Cela permet d'utiliser la complétion automatique de votre invite de commande. +EX: src=fichier.mp4 EX: -src fichier.mp4 EX: -i fichier.mp4 +Ceci trouvera un filtre (par exemple "fin") capable de charger fichier.mp4 +Le même résultat peut être obtenu en utilisant "fin:src=fichier.mp4" +EX: src=fichier.mp4 dst=dump.yuv +Cela décodera le contenu vidéo de file.mp4 et l'écrira dans dump.yuv +Des filtres source ou destination spécifiques peuvent également être spécifiés à l'aide de filterName:src=URL ou filterName:dst=URL. + +Il existe une option spéciale appelée "gfreg" qui permet de spécifier les filtres à utiliser lors de la gestion des URL. +EX: src=fichier.mp4:gfreg=ffdmx,ffdec +Cela utilisera ffdmx pour gérer le fichier et ffdec pour décoder +Cela peut être utilisé pour tester un filtre spécifique lorsque d'autres chaînes de filtres sont possibles. + +Par défaut, la chaîne de filtres sera résolue sans décodage / encodage si la destination accepte le format souhaité. Sinon, les décodeurs / encodeurs seront chargés dynamiquement pour effectuer la conversion, à moins que la résolution dynamique ne soit désactivée. Il existe un nom de filtre spécial pour les encodeurs, "enc", permettant de charger un filtre fournissant le codage souhaité. Les paramètres pour "enc" sont: +c=NAME identifie le codec souhaité. NOM peut être le nom du codec gpac ou l'instance du codeur pour ffmpeg ... +b=UINT ou débit=UINT ou débit=UINT indique le débit en bits par seconde +g=UINT ou gop=UINT indique la taille du groupe d'images dans les cadres +pfmt=4CC indique le format de pixel cible de la source, si supporté par le codec +all_intra=BOOL indique que toutes les images doivent être intra-images, si supporté par le codec +D'autres options peuvent être transmises aux filtres acceptant des arguments génériques (comme c'est le cas pour ffmpeg). +EX: src=dump.yuv:size=320x240:fps=25 enc:c=avc:b=150000:g=50:cgop=true:fast=true dst=raw.264 +Cela encode un ficher YUV 25 fps en AVC à 175 kbps avec une durée de GOP de 2 secondes, en utilisant des paramètres de codage fast et GOP fermé de ffmpeg. + +Note: l'opération inverse (forcer un décodage) est possible en utilisant le filtre reframer: +EX: src=fichier.mp4 reframer:raw @ -o null +Cela forcera le décodage du média à partir du fichier et jettera le résultat (par exemple, pour un benchmark du décodeur) + +Expliciter des liens entre les filtres [LINK] + +Le lien entre les filtres peut être spécifié manuellement. La syntaxe est un caractère '@' optionnellement suivi d'un entier (0 si omis). +Cela indique quels filtres spécifiés antérieurement (le précédent étant associé à 0) doivent être liés au prochain filtre indiqué. +Seule la dernière directive de liaison avant un filtre est utilisée pour configurer des liens pour ce filtre. +EX: fA fB @1 fC +Ceci indique de diriger les sorties fA vers fC +EX: fA fB @1 @0 fC +Cela indique de diriger les sorties du fB vers le fC, @1 est ignoré + +Si aucune directive de lien n'est donnée, les liens seront résolus dynamiquement pour générer autant de connexions que possible. +Par exemple, "fA fB fC" peut connecter fA à fB et fC si fB et fC acceptent les sorties fA + +La directive LINK est juste un raccourci rapide pour définir des arguments de filtre génériques: +- FID=nom, qui définit l'ID de filtre +- SID=nom1[,nom2...], qui définit les ID sources restreignant la liste des entrées possibles pour un filtre. + +EX: fA fB @1 fC +Ceci est équivalent à "fA fB:FID=1 fC:SID=1" +EX: fA: FID = 1 fB fC: SID = 1 +Ceci indique de diriger les sorties fA vers fC +EX: fA:FID=1 fB:FID=2 fC:SID=1 fD:SID=1,2 +Cela indique de diriger les sorties fA vers fC et les sorties fA et fB vers fD + +REMARQUE: Un filtre avec ID source défini ne peut pas être connecté en entrée de filtres sans ID. +Le nom peut être étendu à l'aide de l'identifiant de fragment (# par défaut): +name#PIDNAME: accepte uniquement les pids nommés PIDNAME. +name#TYPE: accepte uniquement les pids du type média correspondant. TYPE peut être 'audio' 'video' 'scene' 'text' 'font' +name#TYPEN: accepte uniquement le Nème pid du type média correspondant. +name#P4CC=VAL: accepte uniquement les pids dont la propriété prédéfinie avec le code donné vaut VAL. +name#PName=VAL: comme ci-dessus, en utilisant le nom d'une propriété prédéfinie. +name#AnyName=VAL: comme ci-dessus, en utilisant le nom d'une propriété non prédéfinie. + +Si la propriété n'est pas définie sur le pid, elle est considérée comme valide. Sinon, sa valeur est vérifiée par rapport à la valeur donnée. + +Les modificateurs suivants pour les comparaisons sont autorisés (pour P4CC=, PName= et AnyName=): +nom#P4CC=!VAL: accepte uniquement les pids dont la propriété ne correspond pas à VAL. +nom#P4CC-VAL: accepte uniquement les pids dont la propriété est strictement inférieure à VAL (uniquement pour les propriétés à 1 dimension). +nom#P4CC+​​VAL: accepte uniquement les pids avec une propriété strictement supérieure à VAL (uniquement pour les propriétés à 1 dimension). + +Un ID source peut aussi utiliser le charactère * pour faire correspondre une ou plusieurs propriétés, quel que soit le filtre source:\n" +EX: fA fB:SID=*#ServiceID=2 +Ceci indique fB n'accepte comme entrée que des pids avec une propriété ServiceID égale à 2, indépendemment des identifiant de filtres de ces pids. + +REMARQUE: Ces extensions fonctionnent également avec le raccourci LINK: +EX: fA fB @1#video fC +Ceci indique de diriger les sorties vidéo fA vers fC + +EX: src=img.heif @#ItemID=200 vout +Cela indique de connecter à vout uniquement les pids avec une propriété ItemID égale à 200. +EX: src=vid.mp4 @#PID=1 vout +Cela indique de connecter à vout uniquement les pids avec une propriété ID égale à 1. +EX: src=vid.mp4 @#Width=640 vout +Cela indique de connecter à vout uniquement les pids avec une propriété Width égale à 640. +EX: src=vid.mp4 @#Width-640 vout +Cela indique de connecter à vout uniquement les pids dont la propriété Width est inférieure à 640. + +Plusieurs fragments peuvent être spécifiés pour tester plusieurs propriétés de pid +EX: src=vid.mp4 @#Width=640#Height+380 vout +Cela indique de connecter à vout uniquement les pids avec une largeur égale à 640 et une hauteur supérieure à 380. + +Notez que si un pid est connecté à un filtre chargé explicitement, aucune résolution de lien dynamique supplémentaire ne sera effectuée pour le connecter à d'autres filtres. Les directives de liaison doivent être soigneusement établies +EX: src=fichier.mp4 @ reframer dst=dump.mp4 +Cela connectera le pid src (type fichier) à dst (type fichier) car dst n'a pas de sourceID et acceptera donc l'entrée de src. Comme le pid est connecté, le moteur de filtres n'essaiera pas de résoudre le lien entre src et reframer. Le résultat est une copie directe du fichier source, le reframer étant inutilisé +EX: src=fichier.mp4 reframer @ dst=dump.mp4 +Cela forcera dst à n'accepter que les pids de reframer, un multiplexeur sera chargé pour résoudre ce lien et src pid sera connecté à reframer (aucun ID de source), chargeant un démultiplexeur pour résoudre le lien. Le résultat est un remux complet du fichier source + + +Héritage d'arguments + +Sauf si explicitement désactivé (option -max-chain), le moteur de filtres résoudra les connexions implicites ou explicites (LINK) entre les filtres et allouera toute chaîne de filtres intermédiaires nécessaires à la connexion des filtres. Ce faisant, il charge de nouveaux filtres avec des arguments hérités à la fois de la source et de la destination. +EX: src=fichier.mp4:OPT dst=fichier.aac dst=fichier.264 +Ceci passera les arguments ":OPT" à tous les filtres chargés entre la source et les deux destinations +EX: src=fichier.mp4 dst=fichier.aac:OPT dst=fichier.264 +Cela passera les arguments ":OPT" à tous les filtres chargés entre la source et la destination file.aac +REMARQUE: les arguments de destination hérités sont les arguments placés APRÈS l'option dst= +EX: src=fichier.mp4 fout:OPTFOO:dst=fichier.aac:OPTBAR +Cela passera les arguments ":OPTBAR" à tous les filtres chargés entre la source et la destination file.aac, mais pas ":OPTFOO" + +Modèles d'URL de destination + +Les URL de destination peuvent être modélisées en utilisant le même mécanisme que MPEG-DASH (template ou modèles): +$KEYWORD$ est remplacé dans le modèle par la valeur résolue, +$KEYWORD%%0Nd $ est remplacé dans le modèle par l'entier résolu, complété avec N zéros si nécessaire, +$$ est un méchanisme d'échappement pour $ + +KEYWORD peut être présent plusieurs fois dans la chaîne. KEYWORD pris en charge (!! sensible à la casse !!): +num: remplacé par numéro de fichier si défini, 0 sinon +PID: ID du pid source +URL: URL du fichier source +File: chemin sur le disque pour le fichier source +p4cc=ABCD: utilise la propriété du pid avec le code 4CC ABCD +pname=VAL: utilise la propriété du pid avec le nom VAL +AUTRE: utilise la propriété du pid dont le nom correspond au nom donné (résolution de 4CC ou nom donné si aucune correspondance 4CC). + +L'utilisation de modèles d'URL peut être utile lors du codage de plusieurs qualités en une seule passe: +EX: src=dump.yuv:taille=640x360 vcrop:wnd=0x0x320x180 enc:c=avc:b=1M @2 enc:c=avc:b=750k dst=dump_$CropOrigin$x$Width$x$Height$.264 +Cela créera une version recadrée de la source, codée en AVC à 1M et une version non recadrée en AVC à 750 Ko. Les sorties sont dump_0x0x320x180.264 pour la version recadrée et dump_0x0x640x360.264 pour la version non recadrée. + +Duplication de filtres + +Lorsqu'un filtre accepte une seule connexion et possède une entrée connectée, la résolution dynamique n'est plus possible pour cette destination. Il peut y avoir des cas où ce comportement est indésirable. Soit un fichier HEIF avec N items: +EX: src=img.heif dst=dump_$ItemID$.jpg +Dans ce cas, un seul item (probablement le premier déclaré dans le fichier) se connectera à la destination. +Les autres items ne seront pas connectés car la destination n'accepte qu'un seul pid d'entrée. +Il existe une option spéciale "clone" permettant de cloner les filtres de destination avec les mêmes arguments: +EX: src=img.heif dst=dump_$ItemID$.jpg:clone +Dans ce cas, la destination sera clonée pour chaque item et tous seront exportés vers différents fichiers JPEG grâce au modèle d'URL. + +Modèles de chaînes de filtres + +Dans certains cas, le nombre de sorties désirées dépend de la source; par exemple, extraire N fichiers d'un multiplex de N services. +Il est possible d'utiliser un nom de propriété de pid dans l'ID source d'un filtre avec la valeur '*'. +Dans ce cas, chaque fois qu'un nouveau PID avec une nouvelle valeur pour la propriété est trouvé, le filtre avec un tel ID source sera automatiquement cloné. +EX: src=source.ts dst=fichier_$ServiceID$.mp4:SID=*#ServiceID=* +Dans cet example, chaque nouvelle valeur ServiceID trouvée lors de la connexion des PID à la destination créera un nouveau fichier de destination. +AVERTISSEMENT: cette fonctionnalité ne doit être utilisée qu'avec une seule propriétéà '*' par ID source, les résultats ne sont pas définis sinon. + +Assigner des propriétés aux PID + +Il est possible de définir des propriétés sur des pids de sortie déclarés par un filtre. Cela permet de marquer des parties du graphe avec des propriétés différentes de celles des autres parties (par exemple, ServiceID). La syntaxe utilise le séparateur de fragments pour identifier les propriétés, par exemple #Nom=Valeur, afin de définir la propriété voulue (4cc, nom intégré ou tout nom) avec une valeur donnée. +Si une propriété non prédéfinie est utilisée, la valeur sera définie sous forme de chaîne. +AVERTISSEMENT: les propriétés ne sont pas filtrées et remplacent les propriétés source. Veillez à ne pas corrompre la session en substituant des propriétés essentielles telles que Width, Height, SampleRate ! +EX: -i v1.mp4:# ServiceID=4 -i v2.mp4:# ServiceID=2 -o dump.ts +Cela multiplexera les flux dans dump.ts, en utilisant ServiceID 4 pour les pids de v1.mp4 et ServiceID 2 pour les pids de v2.mp4. + +Filtres externes + +GPAC est fourni avec un ensemble de filtres intégrés à libgpac. Il peut également charger des filtres externes dans des bibliothèques dynamiques, situées dans +les dossiers répertoriés par la clé mod-dirs" dans la section "core" du fichier de configuration GPAC. Les fichiers doivent être nommés gf_* et exporter une seule fonction +retournant un registre de filtre - voir la documentation de libgpac pour plus de détails. +@ +doc_crc=1035749621 +[core] +log-file=définit le fichier journal de sortie +log-file_crc=4024112903 +log-clock=insert le temps en microsecondes depuis l'heure de démarrage de GPAC avant chaque ligne du journal +log-clock_crc=1770718812 +log-utc=insert le temps UTC en ms avant chaque ligne du journal +log-utc_crc=2584821995 +logs=@définit les niveaux d'erreurs et les outils pour la journalisation. +Les niveux de journalisation peuvent être spécifiés indépendamment par outils impliqués dans une session. +Le format est une liste de toolX[:toolZ]@levelX, séparés par ':'. levelX peut être l'un des suivants: + quiet: ignore les messages + error: enregistre uniquement les messages d'erreurs + warning: enregistre les messages d'erreurs et d'avertissement + info: enregistre les messages d'erreur, d'avertissement et d'information + debug: enregistre tous les messages +toolX peut être l'un des suivants: + core: libgpac core + coding: bitstream formats (audio, video, scene) + container: container formats (ISO File, MPEG-2 TS, AVI, ...) + network: network data sauf trafic RTP + rtp: trafic rtp + author: outils de création (hint, import, export) + sync: messages de synchro + codec: messages des encodeurs/décodeurs + parser: scene parsers (svg, xmt, bt) + media: gestion des objets media dans le compositor + scene: arbre de scene et gestionnaire de scene + script: scripts + interact: interaction (événements, scripts, etc.) + smil: moteur de synchronisation SMIL + compose: moteur de composition (2D, 3D, etc.) + mmio: Gestion Entrées/Sorties matérielles audio et vidéo + rti: diverses statistiques d'exécution + cache: HTTP cache + audio: Rendu audio et mixeurs + mem: traqueur de mémoire GPAC + dash: HTTP streaming + module: GPAC modules (av out, font engine, 2D rasterizer) + filter: gestionnaire de filtres + sched: ordonnanceur des filtres + mutex: mutex calls + all: tous les outils enregistrés - d'autres outils peuvent être spécifiés ultérieurement. +Le mot clé spécial "ncl" peut être défini pour désactiver les couleurs. +Le mot clé spécial "strict" peut être défini pour quitter à la première erreur. + EX: -logs all@info:dash@debug:ncl +Cela passe tous les outils en niveau information, dash en niveau débogage et désactive les couleurs.@ +logs_crc=3235072836 +noprog=désactive les messages de progression +noprog_crc=2201226299 +quiet=désactive tous les messages, y compris les erreurs +quiet_crc=1639470913 +strict-error=quitte après la première erreur signalée +strict-error_crc=199866164 +store-dir=définit le répertoire de stockage +store-dir_crc=2166696791 +mod-dirs=définit les répertoires du module +mod-dirs_crc=1202590521 +ifce=définit l'interface de multicast par défaut via l'adresse IP de l'interface +ifce_crc=949588258 +lang=définit la langue préférée +lang_crc=231363827 +cfg=@définit la valeur du fichier de configuration. Le paramètre de chaîne peut être formaté comme suit: + section:key=val: définit la clé sur une nouvelle valeur + section:key=null or section:key: supprime la clé + section:*=null: supprime la section@ +cfg_crc=2886273405 +version=définit la version de GPAC, utilisée pour vérifier l'actualisation du fichier de configuration +version_crc=4248641509 +64bits=indique si la version GPAC est 64 bits, utilisée pour vérifier l'actualisation du fichier de configuration +64bits_crc=3330035881 +mod-reload=libère les bibliothèques partagées lors du déchargement d'un module plus utilisé +mod-reload_crc=1890630922 +mobile-ip=définit l'adresse IP pour mobileIP +mobile-ip_crc=2363993664 +cache=définit l'emplacement du répertoire de cache +cache_crc=3096764065 +proxy-on=active le proxy HTTP +proxy-on_crc=1445819246 +proxy-name=définit l'adresse proxy HTTP +proxy-name_crc=413963449 +proxy-port=définit le port du proxy HTTP +proxy-port_crc=4281085677 +maxrate=définit le débit de téléchargement HTTP maximal en bits par seconde. 0 signifie illimité +maxrate_crc=1922316623 +no-cache=désactive la mise en cache HTTP +no-cache_crc=3498973804 +offline-cache=active la mise en cache HTTP hors connexion (pas de revalidation des ressources existantes avec le serveur) +offline-cache_crc=2358998211 +clean-cache=indique si le cache HTTP doit être propre au lancement / à la sortie +clean-cache_crc=3957340416 +cache-size=spécifie la taille du cache en octets +cache-size_crc=3383563296 +head-timeout=définit le délai d'expiration de la requête HTTP en millisecondes +head-timeout_crc=160271390 +req-timeout=définit le délai d'expiration de la requête HTTP / RTSP en millisecondes +req-timeout_crc=1825497635 +broken-cert=accepte les certificats SSL cassés +broken-cert_crc=402885164 +user-agent=définit le nom de l'agent utilisateur pour HTTP / RTSP +user-agent_crc=4077155122 +user-profileid=définit l'ID du profil utilisateur (via l'entête de l'entité "X-UserProfileID") +user-profileid_crc=3934422946 +user-profile=définit le nom de fichier du profil utilisateur. Le contenu du fichier est ajouté en tant que corps aux demandes HEAD / GET +user-profile_crc=2635012117 +query-string=insère la chaîne de requête (sans '?'') dans l'URL des requêtes +query-string_crc=2502193860 +dbg-edges=enregistre l'état des arêtes dans le graphe de filtre avant la résolution de dijkstra (pour le débogage). Les arêtes sont affichées sous la forme source(status, weight, src_cap_idx, dst_cap_idx) +dbg-edges_crc=1993009596 +no-block=désactive le mode bloquant des filtres +no-block_crc=3173249070 +no-reg=désactiver la régulation de temps (pas de veille des threads) +no-reg_crc=4238392638 +sched=@définir le mode de l'ordonnanceur de filtres. Les modes possibles sont: + free: utilise des files d'attente sans mutex, sauf pour la liste des tâches (par défaut) + lock: utilise des mutex pour les files d'attente lorsque plusieurs threads + freex: utilise des files d'attente sans mutex, y compris pour les listes de tâches (expérimental) + flock: utilise des mutex pour les files d'attente même sans thread (mode débogage) + direct: pas de threads et éxecution directe des tâches autant que possible (mode débogage)@ +sched_crc=2961967838 +max-chain=définit la longueur maximale de la chaîne lors de la résolution des liens de filtre. La valeur par défaut est prévue pour ([in ->] demux -> reframer -> decoder -> encoder -> unframer -> mux [-> out]. Les chaînes de filtres chargées pour l'adaptation de capabilités (par exemple, changement de format de pixel) sont chargés après la résolution du lien. Définir la valeur sur 0 désactive la résolution du lien dynamique, toute la chaîne doit être spécifiée manuellement +max-chain_crc=3076294763 +threads=définir N thread supplémentaire pour la session. -1 signifie utiliser tous les cœurs disponibles +threads_crc=1234876614 +no-probe=désactive l'analyse des données source, l'extension de fichier doit alors être valide +no-probe_crc=1719555319 +blacklist=liste des filtres à ne pas charger (liste séparée par des virgules) +blacklist_crc=2069525485 +no-graph-cache=désactive la mise en cache des connexions du graphe de filtre. Si désactivé, le graphe sera recalculé à chaque résolution de lien (moins de mémoire, mais plus lent) +no-graph-cache_crc=111720336 +switch-vres=sélectionne la résolution d'écran vidéo la plus petite supérieure à la taille de la scène, sinon utilise la résolution vidéo actuelle +switch-vres_crc=2081476962 +hwvmem=spécifie (moteur de rendu 2D uniquement) si le backbuffer vidéo principal est toujours en mémoire matérielle, en mémoire système ou sélectionné par GPAC (mode par défaut). Selon le type de scène, cela peut modifier considérablement la vitesse d'affichage'. +hwvmem_crc=3676454290 +vert-shader=spécifie le fichier contenant le vertex shader +vert-shader_crc=3278130108 +frag-shader=spécifie le fichier contenant le fragment shader +frag-shader_crc=3278130108 +pref-yuv4cc=définit le YUV 4CC préféré pour les incrustations (utilisé par DirectX uniquement) +pref-yuv4cc_crc=3820182704 +yuv-overlay= indique que la superposition YUV est possible sur la carte vidéo. Toujours surchargé par le module de sortie vidéo +yuv-overlay_crc=1889387323 +offscreen-yuv=indique si yuv-> rgb hors écran est activé. peut être réglé sur false pour forcer la désactivation +offscreen-yuv_crc=3163794729 +overlay-color-key=indique la couleur à utiliser pour l'incrustation YUV, format hexadécimal +overlay-color-key_crc=1982790583 +gl-offscreen=@indique le mode openGL pour le rendu hors écran. + Window: Une fenêtre cachée est utilisée pour effectuer un rendu hors écran. Selon votre pilote vidéo et votre configuration X11, cela peut ne pas fonctionner. + VisibleWindow: une fenêtre visible est utilisée pour effectuer un rendu hors écran. Cela peut être utile lors du débogage + Pixmap: Un Pixmap X11 est utilisé pour effectuer un rendu hors écran. Selon votre pilote vidéo et votre configuration X11, cela peut ne pas fonctionner et même planter le lecteur. + PBuffer: utilise opengl PBuffers pour le dessin, pas toujours supporté@ +gl-offscreen_crc=2846403537 +gl-bits-comp=nombre de bits par composante de couleur dans openGL +gl-bits-comp_crc=1796229199 +gl-bits-depth=nombre de bits pour le tampon de profondeur dans openGL +gl-bits-depth_crc=2302848994 +gl-doublebuf=active le double buffer OpenGL +gl-doublebuf_crc=4108413565 +sdl-defer=utiliser le rendu différé pour SDL +sdl-defer_crc=3643921758 +no-colorkey=désactive la transparence par couleur au niveau de la sortie vidéo +no-colorkey_crc=2171123044 +video-output=indique le nom du module de sortie vidéo à utiliser +video-output_crc=475278293 +raster2d=indique le nom du module de rastériseur 2D à utiliser +raster2d_crc=333251869 +gapi-fbaccess=définit le mode d'accès GAPI sur le tampon de stockage +gapi-fbaccess_crc=379965248 +audio-output= indique le nom du module de sortie audio à utiliser +audio-output_crc=51792132 +alsa-devname=définit le nom du périhérique audio pour ALSA +alsa-devname_crc=3267261435 +force-alsarate=force la fréquence d'échantillonnage de sortie ALSA et OSS +force-alsarate_crc=3120558955 +ds-disable-notif=désactive les notifications de tampon audio DirectSound lorsqu'elles sont prises en charge +ds-disable-notif_crc=1023699628 +font-reader=indique le nom du module de chargement de polices de charactères +font-reader_crc=1884429091 +font-dirs=indique une liste de répertoires (séparés par des virgules) où trouver les polices de charactères +font-dirs_crc=111907503 +rescan-fonts=ndique que le(s) répertoire(s) des polices de charactères doit être réanalysé +rescan-fonts_crc=3797274707 +rmt=active le profilage via Remotery (https://github.com/Celtoys/Remotery). Une copie du visualiseur Remotery se trouve dans gpac/share/vis, généralement installée dans /usr/share/gpac/vis ou Program Files/GPAC/vis +rmt_crc=2323200474 +rmt-port=définit le port de Remotery +rmt-port_crc=3708528921 +rmt-reuse=autorise la réutilisation de port ouvert par Remotery +rmt-reuse_crc=3607580606 +rmt-localhost=force Remotery à n'autoriser que les connections depuis localhost +rmt-localhost_crc=1239978781 +rmt-sleep=temps de veille de Remotery en ms entre deux mise à jours serveur +rmt-sleep_crc=3230696886 +rmt-nmsg=définit le nombre de messages par mise à jour pour Remotery +rmt-nmsg_crc=4237510619 +rmt-qsize=définit la taille de la queue de message pour Remotery +rmt-qsize_crc=1971622359 +rmt-log=redirige les journaux vers remotery (expérimental, généralement mal géré par le navigateur) +rmt-log_crc=3311201781 +rmt-ogl=analyse aussi les performances OpenGL pour Remotery +rmt-ogl_crc=978980191 +[properties] +ID=ID du flux +ID_crc=131976233 +ESID=MPEG-4 ESID du flux - obligatoire si MPEG-4 Systems est utilisé +ESID_crc=3337700331 +ItemID=ID de l'item d'image dans HEIF, même valeur que l'ID +ItemID_crc=4166642518 +ServiceID=ID du service associé +ServiceID_crc=1785575902 +ClockID=ID du flux servant de référence d'horloge +ClockID_crc=3926148669 +DependencyID=ID du flux portant la couche de base pour ce pid +DependencyID_crc=3756022933 +SubLayer=indique si le flux est sublayer temporel du flux de base +SubLayer_crc=3086888640 +PlaybackMode=@Mode de lecture supporté: + 0: pas de contrôle du temps + 1: lecture, pause, navigation, vitesse=1 + 2: lecture, pause, navigation, vitesse>=0 + 3: lecture, pause, navigation, lecture inversée@ +PlaybackMode_crc=667732373 +Scalable=Flux scalable +Scalable_crc=2353017342 +TileBase=Flux de base de tuiles HEVC +TileBase_crc=2036085341 +Language=language du flux, code ISO639 2/3 ou RFC 4646 +Language_crc=1368763779 +ServiceName=Nom du service associé +ServiceName_crc=3726282591 +ServiceProvider=Fournisseur du service associé +ServiceProvider_crc=1262612664 +StreamType=type du flux (audio,vidéo,etc.) +StreamType_crc=2499440779 +StreamSubtype=sous-type (4CC) du flux (auxiliaire, séquence d'images, etc.) +StreamSubtype_crc=1313758233 +ISOMSubtype=sous-type (4CC) du flux ISOBMFF (avc1 avc2 ...) +ISOMSubtype_crc=554474985 +OrigStreamType=Type d'origine du flux avant chiffrement +OrigStreamType_crc=1422368035 +CodecID=Identifiant du format de codage +CodecID_crc=214443429 +InitialObjectDescriptor=Indique si le pid est déclaré dans l'IOD pour MPEG-4 Systems +InitialObjectDescriptor_crc=2512088943 +Unframed=indique les paquets média ne sont pas des trame / Access Units / images complète ou ne sont pas au format interne de libgpac (par exemple, annexeB pour avc / hevc, adts pour aac) +Unframed_crc=4245733010 +Duration=durée du flux +Duration_crc=275428770 +NumFrames=Nombre de trames dans le flux +NumFrames_crc=2432356885 +ConstantFrameSize=Taille des trames pour des flux de taille de trame constante +ConstantFrameSize_crc=3500637503 +TimeshiftDepth=Longueur de la mémoire de décallage temporel +TimeshiftDepth_crc=196570138 +Timescale=échelle de temps du flux (un delta d'horodatage de N vaut N / Timescale secondes) +Timescale_crc=3120776720 +ProfileLevel=MPEG-4 profile et niveaux +ProfileLevel_crc=2025902585 +DecoderConfig=Données de configuration du décodeur +DecoderConfig_crc=2434212694 +DecoderConfigEnhancement=Données de configuration du décodeur de la ou des couches d'amélioration +DecoderConfigEnhancement_crc=571289940 +SampleRate=Taux d'échantillonnage audio +SampleRate_crc=2503304202 +SamplesPerFrame=Nombre d'échantillons audio dans une trame compréssée +SamplesPerFrame_crc=1144622965 +NumChannels=Nombre de canaux audio +NumChannels_crc=58247240 +ChannelLayout=Disposition des canaux +ChannelLayout_crc=367517492 +AudioFormat=format des échantillons audio +AudioFormat_crc=94232868 +AudioPlaybackSpeed=Vitesse de lecture audio, utilisée uniquement pour la reconfiguration de la sortie audio +AudioPlaybackSpeed_crc=2194364450 +Delay=Retard de présentation comparé aux horodatages de composition, en échelle de temps du flux. Une valeur positive implique un retard du flux. Une valeur négative implique de passer/ne pas présenter le début du flux +Delay_crc=4030425587 +CTSShift=décalage CTS à appliquer en cas de CTS négatif +CTSShift_crc=1698165121 +Width=Largeur visuelle (video / text / graphics) +Width_crc=3627736268 +Height=Heuteur visuelle (video / text / graphics) +Height_crc=2645395397 +PixelFormat=Format de pixel des données décompréssées +PixelFormat_crc=1678811690 +Stride=Largeur de ligne en octet d'un plan Y/alpha ou de l'image (formats non planaire) +Stride_crc=76106405 +StrideUV=Largeur de ligne en octet d'un plan U/V +StrideUV_crc=1964825709 +BitDepthLuma=Nombre de bits pour les composants luma +BitDepthLuma_crc=4028264643 +BitDepthChroma=Nombre de bits pour les composants de chrominance +BitDepthChroma_crc=692587320 +FPS=Nombre de trames par secondes pour vidéo/animation +FPS_crc=1602187961 +Interlaced=indicateur de vidéo entrelacée +Interlaced_crc=587376242 +SAR=Ratio largeur/hauteur d'un pixel +SAR_crc=3890989426 +PAR=Ratio largeur/hauteur de l'image +PAR_crc=2532599547 +MaxWidth=Largeur maximale (vidéo / texte / graphiques) de toutes les couches d'amélioration +MaxWidth_crc=2141354912 +MaxHeight=Hauteur maximale (vidéo / texte / graphiques) de toutes les couches d'amélioration +MaxHeight_crc=1878599899 +ZOrder=ordre visuel de présentation du flux, de 0 (premier, devant) à max int (dernier, derrière) +ZOrder_crc=2087381540 +TransX=Translation horizontale du flux +TransX_crc=2257155539 +TransY=Translation verticale du flux +TransY_crc=2794883389 +CropOrigin=Position dans la fenêtre source, X, Y indiquent les coordonnées dans la source +CropOrigin_crc=1458354593 +OriginalSize=Résolution de la vidéo d'origine pour une vidéo croppée +OriginalSize_crc=1797949194 +Bitrate=Débit en bps +Bitrate_crc=2629756747 +Maxrate=Max bitrate en bps +Maxrate_crc=742329135 +DBSize=Taille de la mémoire de decodage en octets +DBSize_crc=3674125247 +MediaDataSize=Taille en octets des données média du flux +MediaDataSize_crc=246979115 +DataRef=Le référencement externe des données en ISOBMFF est possible (chaque trame compressée est un ensemble continu d'octets dans la source, sans transformation) +DataRef_crc=2333294061 +URL=URL de la source +URL_crc=110605863 +RemoteURL=URL distant de la source - utilisé pour MPEG-4 systems +RemoteURL_crc=348519343 +RedirectURL=URL de redirection de la source +RedirectURL_crc=1188029026 +SourcePath=Chemin du fichier source sur le système de fichiers +SourcePath_crc=499802723 +MIME Type=Type MIME de la source +MIME Type_crc=2372232615 +Extension=Extension du fichier source +Extension_crc=638625124 +Cached=Indique que le fichier est complètement mis en cache - les modifications sont signalées via pid info (pas de reconfiguration) +Cached_crc=3151091158 +DownloadRate=Vitesse de téléchargement en bits/s - les modifications sont signalées via pid info (pas de reconfiguration) +DownloadRate_crc=3036537463 +DownloadSize=Taille de la ressource en octets +DownloadSize_crc=3043116296 +DownBytes=Nombre d'octets téléchargés - les modifications sont signalées via pid info (pas de reconfiguration) +DownBytes_crc=4224364036 +ByteRange=Plage d'octets de la ressource associée en octets. Si non specifiée, les données représentent tout le fichier. +ByteRange_crc=589585796 +DisableProgressive=le flux/fichier ne peut pas être poussé/stocké progressivement car les premiers blocs doivent être ré-écrits à la fermeture +DisableProgressive_crc=1253734822 +IsoAltBrands=indique les marques ISOBMFF associées au PID/fichier +IsoAltBrands_crc=1745643599 +IsoBrand=indique la marque principale ISOBMFF associée à un PID/fichier +IsoBrand_crc=90889367 +ServiceWidth=Largeur d'affichage du service +ServiceWidth_crc=365048394 +ServiceHeight=Hauteur d'affichage du service +ServiceHeight_crc=3683000823 +CarouselRate=Taux de répétition en ms pour les données carrousel éventuelles +CarouselRate_crc=2407619266 +UTC=date et heure UTC du PID +UTC_crc=1412134201 +UTCTimestamp=Horodatage enéchelle de temps du flux correspondant à la date et à l'heure UTC +UTCTimestamp_crc=2648424488 +AudioVolume=Volume audio +AudioVolume_crc=2905216846 +AudioPan=Balance/Pan audio +AudioPan_crc=4102812374 +AudioPriority=Priorité du thread audio +AudioPriority_crc=1729537029 +ProtectionScheme=Type de schéma de protection (4CC) utilisé +ProtectionScheme_crc=2184009129 +SchemeVersion=Version du schéma de protection utilisée +SchemeVersion_crc=772159803 +SchemeURI=URI du schéma de protection +SchemeURI_crc=2450678833 +KMS_URI=URI du système de gestion de clés (ISMA, OMA) +KMS_URI_crc=1235676637 +SelectiveEncryption=Indique si le cryptage sélectif ISAM / OMA est utilisé +SelectiveEncryption_crc=432794341 +IVLength=Taille du vecteur d'initialisation ISMA +IVLength_crc=2455230602 +KILength=Taille de l'indicateur de clé ISMA +KILength_crc=9936747 +CryptType=Type d'encryption OMA +CryptType_crc=3417288242 +ContentID=Identifiant OMA du contenu +ContentID_crc=3951478426 +TextualHeaders=En-têtes textuelles OMA +TextualHeaders_crc=3209612233 +PlaintextLen=Taille des données en clair pour OMA +PlaintextLen_crc=1882107497 +CryptInfo=Chemin local vers le fichier d'information de crypto pour ce pid - voir l'aide du filtre cecrypt +CryptInfo_crc=3546178372 +DecryptInfo=Chemin local vers le fichier d'information de crypto pour ce pid - voir l'aide du filtre cdcrypt +DecryptInfo_crc=396626559 +SenderNTP=Temps d'envoi NTP +SenderNTP_crc=618552461 +Encrypted=Les paquets du flux sont cryptés par défaut (mais l'état de cryptage est indiqué par paquet) - les modifications sont signalées via pid_set_info (pas de reconfiguration) +Encrypted_crc=18696922 +OMAPreview=Plage d'aperçu en clair pour OMA +OMAPreview_crc=112468299 +CENC_PSSH=Blob binaire PSSH pour CENC, au format (u32)NbSystems [(bin128)SystemID(u32)version(u32)KID_count[(bin128)keyID](u32)priv_size(char*priv_size)priv_data] +CENC_PSSH_crc=297854741 +CENC_SAI=SAI CENC pour le paquet, au format (char(IV_Size))IV(u16)NbSubSamples [(u16)ClearBytes(u32)CryptedBytes] +CENC_SAI_crc=2119074303 +KID=ID de clé pour les paquets du PID - les modifications sont signalées par pid_set_info (pas de reconfiguration) +KID_crc=523421652 +IVSize=taille d'IV pour les paquets du PID +IVSize_crc=937084584 +ConstantIV=Constant IV pour les paquets du PID +ConstantIV_crc=1178937174 +CENCPattern=CENC crypt pattern, nb blocs skip dans frac.num et nb blocs crypt dans frac.den +CENCPattern_crc=2923640643 +CENCStore=4CC de l'emplacement de stockage ISOBMFF des données SAI +CENCStore_crc=988911565 +AMRModeSet=ModeSet pour AMR et AMR-WideBand +AMRModeSet_crc=688511568 +SubSampleInfo=blob binaire décrivant N plages du paquet, au format N [(u32)flags(u32)size(u32)reserved(u8)priority(u8) discardable] +SubSampleInfo_crc=4180379905 +NALUMaxSize=Taille maximale des unités NAL dans le flux - les modifications sont signalées via pid_set_info (pas de reconfiguration) +NALUMaxSize_crc=180584814 +FileNumber=Index du fichier utilisé lors de l'écriture de fichiers +FileNumber_crc=1736957882 +FileName=Nom du fichier de sortie lors de l'écriture. Doit être défini sur le premier paquet appartenant au nouveau fichier +FileName_crc=2609297906 +IDXName=Nom du fichier d'index pour la segmentation MPEG-2 TS. Doit être défini sur le premier paquet appartenant au nouveau fichier +IDXName_crc=3294858970 +EODS=Fin du segment DASH +EODS_crc=987469045 +MaxFrameSize=Taille maximale de la trame dans le flux - les modifications sont signalées par pid_set_info (pas de reconfiguration) +MaxFrameSize_crc=2398021276 +TrackTemplate=Track Box ISOBMFF sérialisée pour ce PID, sans aucune information de paquet (stbl et dref vide) - utilisé par le muxer ISOBMFF pour réinjecter les box d'entrées +TrackTemplate_crc=900009873 +MovieUserData=UDTA et autres box du moov sérialisées sous forme de liste pour ce PID - utilisé par le muxer ISOBMFF pour réinjecter les box d'entrées +MovieUserData_crc=319518667 +Period=ID de la période DASH +Period_crc=2945576102 +PStart=début de la période DASH - voir aide du filtre dasher +PStart_crc=1307682613 +PDur=Durée de la période DASH - voir aide du filtre dasher +PDur_crc=779454483 +Representation=ID de la représentation DASH +Representation_crc=239910461 +ASID=ID de l'adaptation set parent en DASH +ASID_crc=4060058902 +MuxSrc=Nom de la ou des sources de multiplexage, défini par le filtre dasher pour diriger ses sorties vers les multiplexeurs +MuxSrc_crc=2795310043 +DashMode=mode DASH à utiliser par le multiplexeur, le cas échéant. 0: le mux n'est pas un fichier DASH, 1: le mux est un fichier DASH normal, 2: le mux est un fichier DASH onDemand (un seul segment) +DashMode_crc=2881652700 +DashDur=durée du segment cible DASH en secondes pour ce PID +DashDur_crc=3066345473 +Role=Liste des rôles pour ce PID +Role_crc=180282755 +PDesc=Liste des descripteurs pour la période DASH contenant ce PID +PDesc_crc=468366391 +ASDesc=Liste des descripteurs conditionnels pour l'AdaptationSet DASH contenant ce PID. Si un PID avec le même nom de descripteur mais une valeur différente est trouvé, les PIDs seront dans différents AdaptationSets +ASDesc_crc=107869132 +ASCDesc=Liste des descripteurs communs pour l'AdaptationSet DASH contenant ce PID +ASCDesc_crc=2337131008 +RDesc=Liste des descripteurs pour la représentation DASH contenant ce PID +RDesc_crc=3509600818 +BUrl=Liste des URL de base pour ce PID +BUrl_crc=2685057823 +Template=Modèle (template) de noms de segment à utiliser pour la génération DASH pour ce PID +Template_crc=4158847963 +StartNumber=Numéro du premier segment généré pour ce PID - voir aide du filtre dasher +StartNumber_crc=3894646022 +xlink=URL de période distante pour DASH - voir aide du filtre dasher +xlink_crc=1236222573 +CDur=Durée maximale de média à traiter pour ce PID en mode DASH +CDur_crc=3648161947 +HLSPL=Nom de la liste de lecture variante HLS pour ce PID +HLSPL_crc=305561589 +DCue=Nom d'un fichier de cues pour ce PID - voir l'aide du filtre dasher +DCue_crc=2643542980 +RequireReorder=Indique que les paquets de ce PID proviennent d'une source avec pertes et réordonnances de paquets (UDP) +RequireReorder_crc=2282219477 +[inspect] +desc=Inspect packets on pids +desc_crc=4287839563 +help=@The inspector filter can be used to dump pid and packets. Te default options load only pid changes. +The packet inspector mode can be configured to dump specific properties of packets using the fmt option. +When the option is not present, all properties are dumped. Otherwise, only properties identified by $TOKEN$ are printed use $, @ or % for TOKEN separator. TOKEN can be: + pn: packet (frame in framed mode) number + dts: decoding time stamp in stream timescale, N/A if not available + cts: composition time stamp in stream timescale, N/A if not available + dur: duration in stream timescale + frame: framing status: frame_full (complete AU), frame_start, , frame_end, frame_cont + sap: SAP type of the frame + ilace: interlacing flag (0: progressive, 1: top field, 2: bottom field) + corr: corrupted packet flag + seek: seek flag + bo: byte offset in source, N/A if not available + roll: roll info + crypt: crypt flag + vers: carrousel version number + size: size of packet + crc: 32 bit CRC of packet + lf: insert linefeed + cr: insert carriage return + data: hex dump of packet - WARNING, THIS IS BIG !! + lp: leading picture flag + depo: depends on other packet flag + depf: is depended on other packet flag + red: redundant coding flag + ck: clock type (used for PCR discontinuities) + Property name or 4cc. + pid.P4CC: PID property 4CC + pid.PropName: PID property name + +EX: fmt="PID $pid.ID$ packet $pn$ DTS $dts$ CTS $cts$ $lf$" dumps packet number, cts and dts, eg "PID 1 packet 10 DTS 100 CTS 108 \n" + +An unrecognized keywork or missing property will resolve to an empty string + +Note: when dumping in interleaved mode, there is no guarantee that the packets will be dumped in their original sequence order +since the inspector fetches one packet at a time on each PID +@ +help_crc=1598221440 +log=Sets inspect log filename +log_crc=3563849341 +mode=Dump mode: au dumps full frame, blk dumps packets before AU reconstruction, raw dumps source packets without demuxing +mode_crc=4021149978 +interleave=Dumps packets as they are received on each pid. If false, report per pid is generated +interleave_crc=1103173539 +pck=Dumps packets along with PID state change - implied when fmt is set +pck_crc=1677973575 +props=Dumps packet properties - ignored when fmt is set, see filter help +props_crc=2778276055 +dump_data=Enables full data dump, WARNING heavy - ignored when fmt is set, see filter help +dump_data_crc=2188302190 +fmt=sets packet dump format - see filter help +fmt_crc=4290652087 +hdr=prints a header corresponding to fmt string without '$ 'or "pid." +hdr_crc=1641642031 +[probe] +desc=Inspect packets on demux pids (not file) +desc_crc=4187457874 +log=Sets inspect log filename +log_crc=3563849341 +mode=Dump mode: au dumps full frame, blk dumps packets before AU reconstruction, raw dumps source packets without demuxing +mode_crc=4021149978 +interleave=Dumps packets as they are received on each pid. If false, report per pid is generated +interleave_crc=1103173539 +pck=Dumps packets along with PID state change - implied when fmt is set +pck_crc=1677973575 +props=Dumps packet properties - ignored when fmt is set, see filter help +props_crc=2778276055 +dump_data=Enables full data dump, WARNING heavy - ignored when fmt is set, see filter help +dump_data_crc=2188302190 +fmt=sets packet dump format - see filter help +fmt_crc=4290652087 +hdr=prints a header corresponding to fmt string without '$ 'or "pid." +hdr_crc=1641642031 +[compositor] +desc=GPAC interactive media compositor +desc_crc=1475395797 +help=@The GPAC compositor allows mixing audio, video, text and graphics in a timed fashion. The compositor acts as a pseudo-sink for the video side and creates its own output window. +The video frames are however dispatched to an output video pid in the form of frame pointers requiring later GPU read if used. +The audio part acts as a regular filter.@ +help_crc=780203760 +aa=@Set anti-aliasing mode for raster graphics - whether the setting is applied or not depends on the graphics module / graphic card. + none: no anti-aliasing + text: anti-aliasing for text only + all: complete anti-aliasing@ +aa_crc=1071704873 +hlfill=Set highlight fill color (ARGB) +hlfill_crc=2517950793 +hlline=Set highlight stroke color (ARGB) +hlline_crc=1115719061 +hllinew=Set highlight stroke width +hllinew_crc=2662633524 +sz=specifies whether scalable zoom should be used or not. When scalable zoom is enabled, resizing the output window will also recompute all vectorial objects. Otherwise only the final buffer is stretched. +sz_crc=863905088 +bc=default background color to use when displaying transparent images or video with no scene composition instructions +bc_crc=1364931934 +yuvhw=enables YUV hardware for 2D blits +yuvhw_crc=286936651 +blitp=partial hardware blits (if not set, will force more redraw) +blitp_crc=2294562105 +stress=enables stress mode of compositor (rebuild all vector graphics and texture states at each frame) +stress_crc=3471029662 +fast=enables speed optimization - whether the setting is applied or not depends on the graphics module / graphic card +fast_crc=1107410525 +bvol=@draws bounding volume of objects. + no: disable bounding box + box: draws a rectangle (2D) or box (3D mode) + aabb: draws axis-aligned bounding-box tree (3D only)@ +bvol_crc=4290969438 +textxt=specifies whether text shall be drawn to a texture and then rendered or directly rendered. Using textured text can improve text rendering in 3D and also improve text-on-video like content. Default value will use texturing for OpenGL rendering. +textxt_crc=621473674 +out8b=converts 10-bit video to 8 bit texture before GPU upload. +out8b_crc=2444281991 +drop=drops late frame when drawing. By default frames are not droped until a heavy desync of 1 sec is observed +drop_crc=909051951 +sclock=forces synchronizing all streams on a single clock +sclock_crc=423984299 +sgaze=simulate gaze events through mouse +sgaze_crc=330294849 +ckey=color key to use in windowless mode (0xFFRRGGBB). GPAC currently doesn't support true alpha blitting to desktop due to limitations in most windowing toolkit, it therefore uses color keying mechanism. The alpha part of the key is used for global transparency of GPAC's output, if supported. +ckey_crc=423354452 +timeout=timeout in ms after which a source is considered dead +timeout_crc=1766072551 +fps=simulation frame rate when animation-only sources are played (ignored when video is present). +fps_crc=2750658324 +fsize=Forces the scene to resize to the biggest bitmap available if no size info is given in the BIFS configuration +fsize_crc=4245810103 +mode2d=specifies whether immediate drawing should be used or not. In immediate mode, the screen is completely redrawn at each frame. In defer mode object positioning is tracked from frame to frame and dirty rectangles info is collected in order to redraw the minimal amount of the screen buffer. Whether the setting is applied or not depends on the graphics module (currently all modules handle both mode). debug mode only renders changed areas. +mode2d_crc=384263271 +amc=audio multichannel support; if disabled always downmix to stereo. usefull if the multichannel output doesn't work properly +amc_crc=1601428466 +asr=forces output sample rate - 0 for auto +asr_crc=1603958301 +ach=forces output channels - 0 for auto +ach_crc=2643820771 +alayout=forces output channel layout - 0 for auto +alayout_crc=451080130 +afmt=forces output channel format - 0 for auto +afmt_crc=233586354 +asize=audio output packet size in samples +asize_crc=40294656 +abuf=audio output buffer duration in ms - the audio renderer fills the output pid up to this value. A too low value will lower latency but can have real-time playback issues +abuf_crc=1640494189 +avol=audio volume in percent +avol_crc=2361349938 +apan=audio pan in percent, 50 is no pan +apan_crc=485972319 +async=audio resynchronization; if disabled, audio data is never dropped but may get out of sync +async_crc=3135160244 +buf=playout buffer in ms. Overriden by BufferLenth property of input pid +buf_crc=2203983687 +rbuf=rebuffer trigger in ms. Overriden by RebufferLenth property of input pid +rbuf_crc=271064352 +mbuf=max buffer in ms (must be greater than playout buffer). Overriden by BufferMaxOccupancy property of input pid +mbuf_crc=4276787499 +ogl=specifies 2D rendering mode. When on, this will involve polygon tesselation which may not be supported on all platforms, and 2D graphics will not look as nice as 2D mode. In hybrid mode, the compositor performs software drawing of 2D graphics with no textures (better quality) and uses OpenGL for all textures. The raster mode only uses OpenGL for pixel IO but does not perform polygin fill (no tesselation) (slow, mainly for test purposes). +ogl_crc=3373719114 +pbo=enables PixelBufferObjects to push YUV textures to GPU in OpenGL Mode. This may slightly increase the performances of the playback. +pbo_crc=3490920568 +nav=overrides the default navigation mode of MPEG-4/VRML (Walk) and X3D (Examine) +nav_crc=4231320967 +linegl=specifies that outlining shall be done through OpenGL pen width rather than vectorial outlining +linegl_crc=4224418980 +epow2=@emulate power-of-2 textures for openGL (old hardware). Ignored if OpenGL rectangular texture extension is enabled + yes: video texture is not resized but emulated with padding. This usually speeds up video mapping on shapes but disables texture transformations + no: video is resized to a power of 2 texture when mapping to a shape@ +epow2_crc=2327000520 +paa=specifies whether polygon antialiasing should be used in full antialiasing mode. If not set, only lines and points antialiasing are used. +paa_crc=1786425518 +bcull=specifies whether backface culling shall be disable or not. +bcull_crc=3893702626 +wire=@enables wireframe mode. +"none": objects are drawn as solid +"only": objects are drawn as wireframe only +"solid": objects are drawn as solid and wireframe is then drawn@ +wire_crc=4252552551 +norms=enables normal drawing +norms_crc=363654300 +glus=use GLU scale, which may be slower but nicer than GPAC's software stretch routines. +glus_crc=2105210268 +rext=use non power of two (rectangular) texture GL extension +rext_crc=2372984472 +cull=use aabb culling: large objects are rendered in multiple calls when not fully in viewport +cull_crc=1939091489 +yuvgl=enables YUV open GL pixel format support +yuvgl_crc=2022018780 +depth_gl_scale=sets depth scaler +depth_gl_scale_crc=3877449784 +depth_gl_type=sets geometry type used to draw depth video +depth_gl_type_crc=47962644 +views=specifies the number of views to use in stereo mode +views_crc=2085138016 +stereo=@specifies the stereo output type (default "none"). If your graphic card does not support OpenGL shaders, only "top" and "side" modes will be available. +"side": images are displayed side by side from left to right. +"top": images are displayed from top (laft view) to bottom (right view). +"hmd": same as side except that view aspect ratio is not changed. +"ana": standard color anaglyph (red for left view, green and blue for right view) is used (forces views=2). +"cols": images are interleaved by columns, left view on even columns and left view on odd columns (forces views=2). +"rows": images are interleaved by columns, left view on even rows and left view on odd rows (forces views=2). +"spv5": images are interleaved by for SpatialView 19'' 5 views display, fullscreen mode (forces views=5). +"alio8": images are interleaved by for Alioscopy 8 views displays, fullscreen mode (forces views=8). +"custom": images are interleaved according to the shader file indicated in gpac config section Video, key InterleaverShader. The shader is exposed each view as uniform sampler2D gfViewX, where X is the view number starting from the left@ +stereo_crc=789458126 +fpack=indicates default frame packing of input video +fpack_crc=2619447659 +camlay=sets camera layout in multiview modes +camlay_crc=4152486677 +iod=specifies the eye separation in cm (distance between the cameras). +iod_crc=1720965022 +rview=reverse view order +rview_crc=591231777 +tvtn=number of point sampling for tile visibility algo +tvtn_crc=3890017384 +tvtt=number of points above which the tile is considered visible +tvtt_crc=1126892718 +tvtd=disables the tile having full coverage of the SRD, only displaying partial tiles +tvtd_crc=2657353927 +tvtf=force all tiles to be considered visible, regardless of viewpoint +tvtf_crc=387976198 +fov=default field of view for VR +fov_crc=214746862 +autocal=auto callibrates znear/zfar in depth rendering mode +autocal_crc=2157309113 +dispdepth=sets display depth, negative value uses default screen height +dispdepth_crc=4113138537 +dispdist=specifies the distance in cm between the camera and the zero-disparity plane. There is currently no automatic calibration of depth in GPAC +dispdist_crc=1518690589 +focdist=sets focus distance +focdist_crc=4011636888 +wfont=forces to wait for SVG fonts to be loaded before displaying frames +wfont_crc=3547111042 +defsz=default size +defsz_crc=2322452950 +dpi=default dpi if not indicated by video output +dpi_crc=2481033276 +dbgpvr=debugs scene used by PVR addon +dbgpvr_crc=4127459732 +[mp4dmx] +desc=ISOBMFF and QT demuxer +desc_crc=93151095 +help=@When scalable tracks are present in a file, the reader can operate in 3 modes using smode option: + smode=single: resolves all extractors to extract a single bitstream from a scalable set. The highest level is used +In this mode, there is no enhancement decoder config, only a base one resulting from the merge of the configs + smode=split: all extractors are removed and every track of the scalable set is declared. In this mode, each enhancement track has no base decoder config +and an enhancement decoder config. + smode=splitx: extractors are kept in the bitstream, and every track of the scalable set is declared. In this mode, each enhancement track has a base decoder config + (copied from base) and an enhancement decoder config. This is mostly used for DASHing content. + WARNING: smode=splitx will result in extractor NAL units still present in the output bitstream, which shall only be true if the output is ISOBMFF based +@ +help_crc=3155400072 +src=location of source content +src_crc=3972578009 +allt=@loads all tracks even if unknown + @ +allt_crc=1967417000 +noedit=do not use edit lists +noedit_crc=4288054093 +itt=(items-to-track) converts all items of root meta into a single PID +itt_crc=2291805652 +smode=@Load mode for scalable/tile tracks + split: each track is declared, extractors are removed + splitx: each track is declared, extractors are kept + single: a single track is declared (highest level for scalable, tile base for tiling)@ +smode_crc=1367399273 +stsd=only extract sample mapped to the given sample desciption index. 0 means no filter +stsd_crc=2429693299 +mov=pointer to a read/edit ISOBMF file used internally by importers and exporters +mov_crc=3090765908 +[bifsdec] +desc=MPEG-4 BIFS decoder +desc_crc=3597962957 +[odfdec] +desc=MPEG-4 OD decoder +desc_crc=2749565704 +[fin] +desc=Generic file input +desc_crc=1432646539 +src=location of source content +src_crc=3972578009 +block_size=block size used to read file +block_size_crc=29840651 +range=byte range +range_crc=2116497769 +ext=overrides file extension +ext_crc=1085665433 +mime=sets file mime type +mime_crc=1774517341 +[btplay] +desc=Textual BT/XMT/X3D loader for playback +desc_crc=3099725038 +progressive=enable progressive loading +progressive_crc=3818602383 +sax_dur=SAX loading duration +sax_dur_crc=1802111958 +[httpin] +desc=HTTP input +desc_crc=2945195665 +src=location of source content +src_crc=3972578009 +block_size=block size used to read file +block_size_crc=29840651 +cache=Sets cache mode: disk, disk without discarding, memory or none +cache_crc=1955261685 +range=Sets byte range, as fraction +range_crc=3506772796 +ext=overrides file extension +ext_crc=1085665433 +mime=sets file mime type +mime_crc=1774517341 +[svgplay] +desc=SVG loader for playback +desc_crc=1396251963 +sax_dur=SAX loading duration, 0 disables SAX parsing +sax_dur_crc=3513062436 +[rfimg] +desc=JPG/J2K/PNG/BMP Image reframer +desc_crc=1596853371 +timescale=timescale for media timestamps when loading from file not stream +timescale_crc=992061343 +dur=duration of image when loading from file not stream +dur_crc=1719404674 +[imgdec] +desc=PNG/JPG decoder +desc_crc=3552311705 +[rfadts] +desc=ADTS reframer +desc_crc=338359499 +frame_size=size of AAC frame in audio samples +frame_size_crc=1976232832 +index_dur=indexing window length +index_dur_crc=400520 +mpeg4=forces signaling as MPEG-4 AAC +mpeg4_crc=66846242 +ovsbr=forces oversampling SBR (does not multiply timescales by 2) +ovsbr_crc=1287852570 +sbr=@set SBR signaling: + no: no SBR signaling at all + imp: backward-compatible SBR signaling (audio signaled as AAC-LC) + exp: explicit SBR signaling (audio signaled as AAC-SBR)@ +sbr_crc=107785050 +ps=@set PS signaling: + no: no PS signaling at all + imp: backward-compatible PS signaling (audio signaled as AAC-LC) + exp: explicit PS signaling (audio signaled as AAC-PS) +@ +ps_crc=3200918403 +[rfmp3] +desc=MPEG-1/2 audio reframer +desc_crc=3201472508 +index_dur=indexing window length +index_dur_crc=400520 +[faad] +desc=FAAD decoder +desc_crc=2730188650 +[maddec] +desc=MAD decoder +desc_crc=2999506261 +[xviddec] +desc=XVid decoder +desc_crc=3424175961 +deblock_y=enable Y deblocking +deblock_y_crc=565202633 +deblock_uv=enable UV deblocking +deblock_uv_crc=4030753028 +film_effect=enable film effect +film_effect_crc=1374082604 +dering_y=enable Y deblocking +dering_y_crc=565202633 +dering_uv=enable UV deblocking +dering_uv_crc=4030753028 +[j2kdec] +desc=OpenJPEG2000 decoder +desc_crc=2046398867 +[rfac3] +desc=AC3 reframer +desc_crc=608396785 +index_dur=indexing window length +index_dur_crc=400520 +[a52] +desc=A52 decoder +desc_crc=546058210 +[rfamr] +desc=AMR/EVRC reframer +desc_crc=893249618 +index_dur=indexing window length +index_dur_crc=400520 +[oggdmx] +desc=OGG demuxer +desc_crc=209586963 +index_dur=indexing window length (unimplemented, only 0 disables stream probing for duration), +index_dur_crc=4269948406 +[vorbisdec] +desc=OGG/Vorbis decoder +desc_crc=1312885232 +[theoradec] +desc=OGG/Theora decoder +desc_crc=3220665371 +[m2tsdmx] +desc=MPEG-2 Transport Stream demuxer +desc_crc=526700568 +temi_url=force TEMI URL +temi_url_crc=3916788266 +dsmcc=enable DSMCC receiver +dsmcc_crc=279393923 +[sockin] +desc=UDP / TCP socket input +desc_crc=3624252474 +help=@This filter handles generic TCP and UDP input sockets. It can also probe for MPEG-2 TS over RTP input. Probing of MPEG-2 TS over UDP/RTP is enabled by default but can be turned off. + +Format of data can be specified by setting either ext or mime option. If not set, the format will be guessed by probing the first data packet +Your platform supports unix domain sockets, use tcpu://NAME and udpu://NAME +On OSX with VM packet replay you will need to force multicast routing, eg: route add -net 239.255.1.4/32 -interface vboxnet0@ +help_crc=830918544 +src=location of source content +src_crc=3972578009 +block_size=block size used to read socket +block_size_crc=1575913241 +sockbuf=socket max buffer size +sockbuf_crc=3655720801 +port=default port if not specified +port_crc=3919277725 +ifce=default multicast interface +ifce_crc=2634186680 +listen=indicate the input socket works in server mode +listen_crc=942997240 +ka=keep socket alive if no more connections +ka_crc=3219833474 +maxc=max number of concurrent connections +maxc_crc=2929003351 +np=disables TS probe on input data. Implied if mime or ext are given +np_crc=3094894051 +ext=indicates file extension of udp data +ext_crc=4011058385 +mime=indicates mime type of udp data +mime_crc=2546177900 +block=set blocking mode for socket(s) +block_crc=3267148328 +reorder_pck=number of packets delay for RTP reordering (M2TS over RTP) +reorder_pck_crc=2530740796 +reorder_delay=number of ms delay for RTP reordering (M2TS over RTP) +reorder_delay_crc=3603041699 +[osvcdec] +desc=OpenSVC decoder +desc_crc=3449928440 +[vtbdec] +desc=VideoToolBox decoder +desc_crc=3644050121 +reorder=number of frames to wait for temporal re-ordering +reorder_crc=761831010 +no_copy=dispatch VTB frames into filter chain (no copy) +no_copy_crc=2198326826 +pfmt=sets default pixel format for decoded video. If not matched default to nv12 +pfmt_crc=32157086 +disable_hw=Disables hardware decoding +disable_hw_crc=2484006419 +[lsrdec] +desc=MPEG-4 LASeR decoder +desc_crc=4044168197 +[safdmx] +desc=SAF demuxer +desc_crc=1779159991 +[dashin] +desc=MPEG-DASH & HLS access engine +desc_crc=2374307118 +max_buffer=max content buffer in ms. If 0 uses default player settings +max_buffer_crc=799920983 +auto_switch=switch quality every N segments, disabled if 0 +auto_switch_crc=2094101418 +store=enables file caching +store_crc=3848075024 +algo=@adaptation algorithm to use. The following algorithms are defined: + none: no adaptation logic + grate: GAPC legacy algo based on available rate + gbuf: GAPC legacy algo based on buffer occupancy + bba0: BBA-0 + bolaf: BOLA Finite + bolab: BOLA Basic + bolau: BOLA-U + bolao: BOLA-O@ +algo_crc=3083473991 +start_with=@specifies the initial representation criteria: + min_q: start with lowest quality + max_q: start with highest quality + min_bw: start with lowest bitrate + max_bw: start with highest bitrate; for tiles are used, all low priority tiles will have the lower (below max) bandwidth selected + max_bw_tiles: start with highest bitrate; for tiles all low priority tiles will have their lowest bandwidth selected@ +start_with_crc=4179083279 +max_res=use max media resolution to configure display +max_res_crc=1978124953 +immediate=when interactive switching is requested and immediate is set, the buffer segments are trashed +immediate_crc=3562300308 +abort=allow abort during a segment download +abort_crc=1958241568 +use_bmin=Uses the indicated min buffer time of the MPD if true, otherwise uses default player settings +use_bmin_crc=2864488242 +shift_utc=shifts DASH UTC clock +shift_utc_crc=3068346272 +atsc_shift=shifts ATSC requests time by given ms +atsc_shift_crc=4237691186 +server_utc=Uses ServerUTC: or Date: http headers instead of local UTC +server_utc_crc=2699367787 +screen_res=Uses screen resolution in selection phase +screen_res_crc=3526471735 +timeshift=Sets initial timshift in ms (if >0) or in %% of timeshift buffer (if <0) +timeshift_crc=3110845565 +tile_mode=@Sets tile adaptiaion mode: + none: bitrate is shared equaly accross all tiles + rows: bitrate decreases for each row of tiles starting from the top, same rate for each tile on the row + rrows: bitrate decreases for each row of tiles starting from the bottom, same rate for each tile on the row + mrows: bitrate decreased for top and bottom rows only, same rate for each tile on the row + cols: bitrate decreases for each columns of tiles starting from the left, same rate for each tile on the columns + rcols: bitrate decreases for each columns of tiles starting from the right, same rate for each tile on the columns + mcols: bitrate decreased for left and right columns only, same rate for each tile on the columns + center: bitrate decreased for all tiles on the edge of the picture + edges: bitrate decreased for all tiles on the center of the picture@ +tile_mode_crc=11481146 +tiles_rate=Indicates the amount of bandwidth to use at each quality level. The rate is recursively applied at each level, e.g. if 50%, Level1 gets 50%, level2 gets 25%, ... If 100, automatic rate allocation will be done by maximizing the quality in order of priority. If 0, bitstream will not be smoothed across tiles/qualities, and concurrency may happen between different media. +tiles_rate_crc=1471056937 +delay40X=Delay in millisconds to wait between two 40X on the same segment +delay40X_crc=3744917353 +exp_threshold=Delay in millisconds to wait after the segment AvailabilityEndDate before considering the segment lost +exp_threshold_crc=834945235 +switch_count=Indicates how many segments the client shall wait before switching up bandwidth. If 0, switch will happen as soon as the bandwidth is enough, but this is more prone to network variations +switch_count_crc=1861836865 +aggressive=If enabled, switching algo targets the closest bandwidth fitting the available download rate. If no, switching algo targets the lowest bitrate representation that is above the currently played (eg does not try to switch to max bandwidth) +aggressive_crc=685396106 +debug_as=Plays only the adaptation set indicated by its index in the MPD; if negative, all sets are used +debug_as_crc=2073744584 +speed=Enables adaptation based on playback speed +speed_crc=4202574190 +[cdcrypt] +desc=CENC, ISMA and adobe decryptor +desc_crc=3286015280 +help=@The CENC/ISMA decryptor uses a configuration file for retrieving keys. The syntax is available at: +https://gpac.wp.imt.fr/mp4box/encryption/common-encryption/ +The file can be set per PID using the property DecryptFile (highest priority), CryptFile (lower priority) +or set at the filter option level (lowest priority). +When the file set per PID, the first CrypTrack with the same ID is used, otherwise the first CrypTrack is used.@ +help_crc=3355643697 +cfile=Crypt file - see filter help +cfile_crc=3044696728 +[cecrypt] +desc=CENC and ISMA encryptor +desc_crc=2608147056 +help=@The CENC/ISMA encryptor uses a configuration file for declaring keys. The syntax is available at: +https://gpac.wp.imt.fr/mp4box/encryption/common-encryption/ +The file can be set per PID using the property CryptFile, or set at the filter option level. +When the file is set per PID, the first CrypTrack with the same ID is used, otherwise the first CrypTrack is used.@ +help_crc=1527605354 +cfile=Crypt file - see filter help +cfile_crc=3044696728 +[mxisom] +desc=ISOBMFF muxer +desc_crc=3174169042 +m4sys=force MPEG-4 Systems signaling of tracks +m4sys_crc=1831069778 +dref=only references data from source file - not compatible with all media sources +dref_crc=775055314 +ctmode=sets cts mode for video tracks. edit uses edit lists to shift first frame to presentation time 0. noedit doesn't shift timeline. negctts uses ctts v1 and no edit lists +ctmode_crc=3336851185 +idur=only imports the specified duration +idur_crc=2105903843 +pack3gp=packs a given number of 3GPP audio frames in one sample +pack3gp_crc=3970001969 +importer=compatibility with old importer, displays import progress +importer_crc=556485467 +pack_nal=repacks NALU size length to minimum possible size for AVC/HEVC/... +pack_nal_crc=2589428755 +xps_inband=Use inband param set for AVC/HEVC/.... If mix, creates non-standard files using single sample entry with first PSs found, and moves other PS inband +xps_inband_crc=131735851 +store=Write mode. inter uses cdur to interleave the file. flat writes a flat file, file at end. cap flushes to disk as soon as samples are added. tight uses per-sample interleaving of all tracks. frag framents the file using cdur duration. sfrag framents the file using cdur duration but adjusting to start with SAP1/3. +store_crc=809072118 +cdur=chunk duration for interleaving and fragmentation modes +cdur_crc=3889103669 +for_test=sets all dates and version info to 0 to enforce same binary result generation +for_test_crc=591187781 +timescale=timescale to use for movie edit lists +timescale_crc=1020713727 +moof_first=generates fragments starting with moof then mdat +moof_first_crc=2226525148 +abs_offset=uses absolute file offset in fragments rather than offsets from moof +abs_offset_crc=2126032520 +fsap=splits truns in video fragments at SAPs to reduce file size +fsap_crc=10664029 +file=pointer to a write/edit ISOBMF file used internally by importers and exporters +file_crc=725371499 +subs_sidx=number of subsegments per sidx. negative value disables sidx +subs_sidx_crc=2940657399 +m4cc=4 character code of empty box to appen at the end of a segment +m4cc_crc=2324258074 +chain_sidx=Uses daisy-chaining of SIDX +chain_sidx_crc=2945874345 +msn=Sets sequence number of first moof to N +msn_crc=1935169908 +msninc=Sets sequence number increase between moofs +msninc_crc=1182819374 +tfdt=sets TFDT of first traf +tfdt_crc=446145329 +tfdt_traf=sets TFDT in each traf +tfdt_traf_crc=1977654141 +nofragdef=disables default flags in fragments +nofragdef_crc=389153258 +straf=uses a single traf per moov (smooth streaming and co) +straf_crc=1179979078 +strun=uses a single traf per moov (smooth streaming and co) +strun_crc=1179979078 +psshs=@PSSH boxes store mode: + moof: in first moof of each segments + moov: in movie box + none: discarded@ +psshs_crc=1139148565 +sgpd_traf=stores sample group descriptions in traf (duplicated for each traf). If not used, sample group descriptions are stored in the movie box +sgpd_traf_crc=2972375000 +cache=Enables temp storage for VoD dash modes. When disabled, SIDX size will be estimated based on duration and DASH segment length, and padding will be used in the file before the final SIDX. When enabled, file data is stored to a cache and flushed upon completion +cache_crc=3264208109 +noinit=does not send initial moov, used for DASH bitstream switching mode +noinit_crc=1715651077 +tktpl=use track box from input if any as a template to create new track. no disables template, yes clones the track (except edits and decoder config), udta only loads udta +tktpl_crc=4009584019 +mudta=use udta and other moov extension boxes from input if any. no disables import, yes clones all extension boxes, udta only loads udta +mudta_crc=3373712603 +tmpd=sets temp dire for intermediate file(s) +tmpd_crc=3648307615 +mvex=sets mvex after tracks +mvex_crc=4110963461 +tkid=track ID of created track for single track. Default 0 uses next available trackID +tkid_crc=810495435 +fdur=fragments based on fragment duration rather than CTS. Mostly used for MP4Box -frag option +fdur_crc=1875532844 +btrt=sets btrt box in sample description +btrt_crc=3335833695 +block_size=target output block size, 0 for default internal value (10k) +block_size_crc=2554453340 +[rfqcp] +desc=QCP reframer +desc_crc=1012125759 +index_dur=indexing window length +index_dur_crc=400520 +[rfh263] +desc=H263 reframer +desc_crc=416498604 +fps=import frame rate +fps_crc=894530348 +index_dur=indexing window length +index_dur_crc=400520 +[rfmpgvid] +desc=MPEG-1 MPEG-2 MPEG-4(Part2) video reframer +desc_crc=2766981857 +help=The demux uses negative CTS offsets: CTS is corrrect, but some frames may have DTS > CTS +help_crc=3334498548 +fps=import frame rate +fps_crc=894530348 +index_dur=indexing window length +index_dur_crc=400520 +vfr=set variable frame rate import +vfr_crc=2959659910 +importer=compatibility with old importer, displays import results +importer_crc=2953354326 +[nhntr] +desc=NHNT reader +desc_crc=2986867744 +reframe=force reparsing of referenced content +reframe_crc=4149990424 +index_dur=indexing window length +index_dur_crc=400520 +[nhmlr] +desc=NHML parser +desc_crc=1087761555 +reframe=force reparsing of referenced content +reframe_crc=4149990424 +index_dur=indexing window length +index_dur_crc=400520 +[rfnalu] +desc=NALU Video (AVC & HEVC) reframer +desc_crc=2496497286 +help=@This demuxer only produces ISOBMFF-compatible output: start codes are removed, NALU length field added and avcC/hvcC config created. + The demux uses negative CTS offsets: CTS is corrrect, but some frames may have DTS > CTS.@ +help_crc=758087815 +fps=import frame rate +fps_crc=894530348 +autofps=detect FPS from bitstream, fallback to fps option if not possible +autofps_crc=2459069437 +index_dur=indexing window length. If 0, bitstream is not probed for duration +index_dur_crc=3784349922 +explicit=use explicit layered (SVC/LHVC) import +explicit_crc=868793531 +strict_poc=delay frame output of an entire GOP to ensure CTS info is correct when POC suddenly changes +strict_poc_crc=3867758617 +nosei=removes all sei messages +nosei_crc=1667863153 +nosvc=removes all SVC/MVC/LHVC data +nosvc_crc=3551677409 +novpsext=removes all VPS extensions +novpsext_crc=2586533869 +importer=compatibility with old importer, displays import results +importer_crc=2953354326 +nal_length=Sets number of bytes used to code length field: 1, 2 or 4 +nal_length_crc=3090395915 +subsamples=Import subsamples information +subsamples_crc=351036105 +[m2psdmx] +desc=MPEG Program Stream demuxer +desc_crc=1302440712 +[avidmx] +desc=AVI demuxer +desc_crc=3345334732 +fps=import frame rate, default is AVI one +fps_crc=2934746080 +importer=compatibility with old importer, displays import results +importer_crc=2953354326 +[txtin] +desc=Timed text SRT, SUB, TTXT, QT-TeXML, WebVTT, TTML and SWF2SVG loader +desc_crc=3978069602 +webvtt=force WebVTT import of SRT files +webvtt_crc=876324549 +nodefbox=skip default text box +nodefbox_crc=3177451349 +noflush=skip final sample flush for srt +noflush_crc=2679838919 +fontname=default font to use +fontname_crc=3544177182 +fontsize=default font size +fontsize_crc=2176459882 +lang=default language to use +lang_crc=3648271996 +width=default width of text area, set to 0 to resolve against visual PIDs +width_crc=3837674580 +height=default height of text area, set to 0 to resolve against visual PIDs +height_crc=3790348457 +x=default horizontal offset of text area: -1 (left), 0 (center) or 1 (right) +x_crc=2094383566 +y=default vertical offset of text area: -1 (bottom), 0 (center) or 1 (top) +y_crc=4101522951 +zorder=default z-order of the PID +zorder_crc=1497795429 +timescale=default timescale of the PID +timescale_crc=499577735 +[ttxtdec] +desc=TTXT/TX3G decoder +desc_crc=2081073929 +texture=Use texturing for output text +texture_crc=2371597645 +outline=Draw text outline +outline_crc=2990065825 +width=Default width when standalone rendering +width_crc=3801502871 +height=Default height when standalone rendering +height_crc=2770052284 +[vttdec] +desc=WebVTT decoder +desc_crc=1539593896 +script=Location of WebVTT SVG JS renderer +script_crc=4025437757 +[rtpin] +desc=RTP/RTSP/SDP input +desc_crc=2133681016 +src=RTSP location of source content +src_crc=851598752 +firstport=default first port number to use. 0 lets the filter decide +firstport_crc=3962131718 +ifce=IP of the default interface to use for multicast. If NULL, the default system interface will be used +ifce_crc=2720424345 +mcast_ttl=Sets multicast TTL +mcast_ttl_crc=3285186258 +reorder_len=RTP reorder length in packets +reorder_len_crc=3754139702 +reorder_delay=Max delay in RTP reorderer, packets will be dispatched after that +reorder_delay_crc=3227104775 +block_size=buffer size fur RTP/UDP or RTSP when interleaved +block_size_crc=4004462100 +disable_rtcp=Disables RTCP reporting +disable_rtcp_crc=65189633 +nat_keepalive=NAT keepalive delay in ms, disabled by default (except for SatIP, set to 30s by default) +nat_keepalive_crc=1499219508 +force_mcast=Force multicast on indicated IP in RTSP setup +force_mcast_crc=1763484740 +use_client_ports=Force using client ports (hack for some RTSP servers overriding client ports) +use_client_ports_crc=3888971245 +bandwidth=sets bandwidth param for RTSP requests +bandwidth_crc=3135130812 +default_port=Sets default RTSP port +default_port_crc=418455079 +satip_port=Sets default port for SATIP +satip_port_crc=2699168017 +interleave=Sets RTP over RTSP; critcial uses interleave only for MPEG-4 systems streams +interleave_crc=2052584898 +udp_timeout=Default timeout before considering UDP is down +udp_timeout_crc=2324324210 +rtsp_timeout=Default timeout before considering RTSP is down +rtsp_timeout_crc=2959531576 +rtcp_timeout=Default timeout for RTCP trafic in ms. After this timeout, playback will start unsync. If 0 always wait for RTCP +rtcp_timeout_crc=2760381520 +autortsp=Automatically reconfig RTSP interleaving if UDP timeout +autortsp_crc=682354717 +first_packet_drop=Sets number of first RTP packet to drop - 0 if no drop +first_packet_drop_crc=134810345 +frequency_drop=Drop 1 out of N packet - 0 disable droping +frequency_drop_crc=3638295141 +user_agent=User agent string, by default solved from GPAC preferences +user_agent_crc=4142140091 +languages=User languages, by default solved from GPAC preferences +languages_crc=2224109611 +stats=Updates statistics to the user every given MS, 0 disables reporting +stats_crc=1414847348 +[fout] +desc=Generic file output +desc_crc=320552344 +help=@The file output filter can work as a null sink when its destination is "null", dropping all input packets. In this case it accepts ANY type of input pid, not just file ones. +In regular mode, the filter will dump to file incomming packets (stream type file), starting a new file for each packet having a start block set, unless operating in cat mode. +The ouput file name can use gpac templating mechanism, see gpac help.@ +help_crc=606079466 +dst=location of destination file - see filter help +dst_crc=2944539646 +append=open in append mode +append_crc=3838208913 +dynext=indicates the file extension is set by filter chain, not dst +dynext_crc=1468217109 +start=sets playback start offset, [-1, 0] means percent of media dur, eg -1 == dur +start_crc=867548297 +speed=sets playback speed when vsync is on +speed_crc=1777817047 +fext=sets extension for graph resolution, regardless of file extension +fext_crc=3290862661 +cat=cats each file of input pid rather than creating one file per filename +cat_crc=522567284 +ow=always overwrite output if existing +ow_crc=2937308660 +[ufadts] +desc=Raw AAC to ADTS writer +desc_crc=2296886488 +mpeg2=Signals as MPEG2 AAC +mpeg2_crc=3374704675 +[reframer] +desc=Passthrough filter ensuring reframing, and optionnally decoding, of inputs +desc_crc=2516726260 +help=@This filter forces input pids to be properly framed (1 packet = 1 Access Unit). It is mostly used for file to file operations. +The filter can be used to filter out packets based on SAP types, for example to extract only the key frames (SAP 1,2,3) of a video +The filter can be used to add real-time regulation of input packets. For example to simulate a live DASH: + EX "src=m.mp4 reframer:rt=on dst=live.mpd:dynamic" +@ +help_crc=1592204113 +exporter=compatibility with old exporter, displays export results +exporter_crc=4138638672 +sap=Drops non-SAP packets, off by default. The string contains the list (whitespace or comma-separated) of SAP types (0,1,2,3,4) to forward. Note that forwarding only sap 0 will break the decoding ... +sap_crc=113824472 +rt=real-time regulation mode of input. off disables it, on enables it on each pid, sync enables it on all pid +rt_crc=1085635204 +speed=speed for real-time regulation mode - only positive value +speed_crc=2337596272 +raw=forces input streams to be in raw format (i.e. forces decoding of input) +raw_crc=1389623929 +[writegen] +desc=Generic single stream to file converter, used when extracting/converting PIDs +desc_crc=3830938964 +exporter=compatibility with old exporter, displays export results +exporter_crc=4138638672 +pfmt=Pixel format for raw extract. If not set, derived from extension +pfmt_crc=3535145677 +afmt=Audio format for raw extract. If not set, derived from extension +afmt_crc=4166772011 +decinfo=Decoder config insert mode: no means never, first means on first packet, sap means at each SAP, auto selects between no and first based on media type +decinfo_crc=588707814 +split=Force one file per decoded frame. +split_crc=2842677878 +frame=Force single frame dump with no rewrite. In this mode, all codecids are supported +frame_crc=1701807790 +sstart=Start number of frame to dump +sstart_crc=2458104567 +send=End number of frame to dump. If starttext dst=test.mpd +This will assign the specified XML descriptor to the adaptation set. + Note that this can be used to inject most DASH descriptors not natively handled by the dasher + The dasher handles the XML descriptor as a string and does not attempt to validate it. + Descriptors, as well as some dasher filter arguments, are string lists (comma-separated by default), so that multiple descriptors can be added: + EX: src=m1.mp4:#RDesc=,text dst=test.mpd +This will insert two descriptors in the representation(s) of m1.mp4 + EX: src=video.mp4:#Template=foo$Number$ src=audio.mp4:#Template=bar$Number$ dst=test.mpd +This will assign different templates to the audio and video sources. + EX: src=null:#xlink=http://foo/bar.xml:#PDur=4 src=m.mp4:#PStart=-1 +This will insert an create an MPD with first a remote period then a regular one + EX: src=null:#xlink=http://foo/bar.xml:#PStart=6 src=m.mp4 +This will insert an create an MPD with first a regular period, dashing ony 6s of content, then a remote one + +The dasher will request muxing filter chains for each representation and will reassign PID IDs +so that each media component (video, audio, ...) in an adaptation set has the same ID + +For HLS, the output pid will deliver the master playlist and the variant playlists +The default variant playlist are $NAME_$N.m3u8, where $NAME is the radical of the output file name and $N is the 1-based index of the variant + +The segmenter can take a list of cues to use for the segmentation process, in which case only these cues are used to derive segment boundaries. +Cues are given in an XML file with a root element called , with currently no attribute specified. The children are elements, with attributes: + "id": integer for stream/track/pid ID + "timescale":integer giving the units of following timestamps + "mode": if present and value is "edit", the timestamp are in presentation time (edit list applied). Otherwise they are in media time +The children of are elements, with attributes: + "sample": integer giving the sample/frame number of a sample at which spliting shall happen + "dts": long integer giving the decoding time stamp of a sample at which spliting shall happen + "cts": long integer giving the composition / presentation time stamp of a sample at which spliting shall happen +WARNING: cues shall be listed in decoding order. Cue files can be specified for the entire dasher, or per PID using DashCue property. + +Note to developpers: output muxers allowing segmented output must obey the following: +* add a "DashMode" capability to their input caps (value of the cap is ignored, only its presence is required) +* inspect packet properties + "FileNumber" gives the signal of a new DASH segment + "FileName" gives the optional file name (if not present, output shall be a single file) + "FileName" property is only set for packet carrying the "FileNumber" property + "FileName" property is only on one PID (usually the first) for multiplexed outputs + "IDXName" gives the optional index name (if not present, index shall be in the same file as dash segment). Only used for MPEG-2 TS for now + "EODS" property is set on packets with no DATA, no TS to signal the end of a DASH segment + This is only used when stoping/resuming the segmentation process, in order to flush segments without dispatching an EOS (see dasher "subdur" option) +* for each segment done, send a downstream event on the first connected PID signaling the size of the segment and the size of its index if any +* for muxers with init data, send a downstream event signaling the size of the init and the size of the global index if any +* the following filter options are passed to muxers, which should declare them as arguments: + noinit: disables output of init segment for the muxer (used to handle bitstream switching with single init in DASH) + frag: indicates muxer shall used fragmented format (used for ISOBMFF mostly) + subs_sidx=0: indicates an SIDX shall be generated - only added if not already specified by user + xps_inband=all|no: indicates AVC/HEVC/... parameter sets shall be sent inband or out of band + nofragdef: indicates fragment defaults should be set in each segment rather than in init segment + +The dasher will add the following properties to the output PIDs: + DashMode: identifies VoD (single file with global index) or regular DASH mode used by dasher + DashDur: identifies target DASH segment duration - this can be used to estimate the SIDX size for example +@ +help_crc=936572930 +dur=DASH target duration in seconds +dur_crc=1801857532 +tpl=use template mode (multiple segment, template URLs) +tpl_crc=2166577778 +stl=use segment timeline (ignored in on_demand mode) +stl_crc=2793559708 +dmode=MPD mode static: not live, dynamic: live generation, dynlast: last call for live, will turn the MPD into static) +dmode_crc=1937279415 +sseg=single segment is used +sseg_crc=133775808 +sfile=Segments are contained in a single file (default in on_demand) +sfile_crc=2818224601 +align=Enables segment time alignment between representations +align_crc=2358410793 +sap=Enables spliting segments at SAP boundaries +sap_crc=1678442801 +mix_codecs=Enables mixing different codecs in an adaptation set +mix_codecs_crc=3192116776 +ntp=Inserts/overrides NTP clock at the begining of each segment. rem removes NTP from all input packets. yes inserts NTP at each segment start. keep leaves input packet NTP untouched. +ntp_crc=1312122791 +no_sar=Does not check for identical sample aspect ratio for adaptation sets +no_sar_crc=2264793386 +m2ts=generates MPEG-2 TS output +m2ts_crc=2958227042 +for_test=sets all dates and version info to 0 to enforce same binary result generation +for_test_crc=591187781 +forcep=forces profile string for avc/hevc/aac +forcep_crc=4186129562 +bs_switch=@Bitstream switching mode (single init segment): + def: resolves to off for onDemand and inband for live + off: disables BS switching + on: enables it if same decoder configuration is possible + inband: moves decoder config inband if possible + force: enables it even if only one representation + multi: uses multiple stsd entries in ISOBMFF@ +bs_switch_crc=3434520549 +avcp=AVC|H264 profile to use if no profile could be found. If forcep is set, enforces this profile +avcp_crc=3693654408 +hvcp=HEVC profile to use if no profile could be found. If forcep is set, enforces this profile +hvcp_crc=299411363 +aacp=AAC profile to use if no profile could be found. If forcep is set, enforces this profile +aacp_crc=3461489333 +av1p=AV1 profile to use if no profile could be found. If forcep is set, enforces this profile +av1p_crc=2069855053 +vpxp=VP8/9 profile to use if no profile could be found. If forcep is set, enforces this profile +vpxp_crc=2561442118 +template=DASH template string to use to generate segment name - see filter help +template_crc=1225336935 +ext=File extension to use for segments +ext_crc=2247294486 +asto=availabilityStartTimeOffset to use. A negative value simply increases the AST, a positive value sets the ASToffset to representations +asto_crc=22785783 +profile=Specifies the target DASH profile. This will set default option values to ensure conformance to the desired profile. Auto turns profile to live for dynamic and full for non-dynamic. For MPEG-2 TS, only main and live are used, others default to main. +profile_crc=1613542548 +profX=specifies a list of profile extensions, as used by DASH-IF and DVB. The string will be colon-concatenated with the profile used +profX_crc=3638857297 +cp=Specifies the content protection element location +cp_crc=2541146739 +pssh=@sets PSSH storage mode: + f: stores in movie fragment only + v: stores in movie only + m: stores in mpd only + mf: stores in mpd and movie fragment + mv: stores in mpd and movie + n: discard pssh from mpd and segments@ +pssh_crc=867436926 +buf=DASH min buffer duration in ms. negative value means percent of segment duration (eg -150 = 1.5*seg_dur) +buf_crc=2337722010 +timescale=sets timescales for timeline and segment list/template. A value of 0 picks up the first timescale of the first stream in an adaptation set. A negative value forces using stream timescales for each timed element (multiplication of segment list/template/timelines). A positive value enforces the MPD timescale +timescale_crc=2194275231 +check_dur=checks duration of sources in period, trying to have roughly equal duration. Enforced whenever period start times are used +check_dur_crc=2701873344 +skip_seg=increments segment number whenever an empty segment would be produced - NOT DASH COMPLIANT +skip_seg_crc=2300060293 +title=sets MPD title +title_crc=2316929427 +source=sets MPD Source +source_crc=1362922919 +info=sets MPD info url +info_crc=3848134995 +cprt=adds copyright string to MPD +cprt_crc=1310849082 +lang=sets lang of MPD Info +lang_crc=4119305264 +location=sets MPD locations to given URL +location_crc=2493686601 +base=sets base URLs of MPD +base_crc=824861713 +refresh=MPD refresh rate for dynamic, in seconds. A negative value sets the MPD duration. If 0, uses dash duration +refresh_crc=4029772805 +tsb=Sets time-shift buffer depth in seconds. A negative value means infinity +tsb_crc=3643999879 +subdur=specifies maximum duration of the input file to be segmentated. This does not change the segment duration, segmentation stops once segments produced exceeded the duration. +subdur_crc=881872903 +ast=for live mode, sets start date (as xs:date, eg YYYY-MM-DDTHH:MM:SSZ. Default is now. !! Do not use with multiple periods, nor when DASH duration is not a multiple of GOP size !! +ast_crc=3116941388 +state=path to file used to store/reload state info when simulating live. This is stored as a valid MPD with GPAC XML extensions +state_crc=230281675 +loop=loops source file when dashing with subdur and state. If not set, a new period is created once the source is over +loop_crc=3713770963 +split=enables cloning samples for text/metadata/scene description streams, marking further clones as redundant +split_crc=1586303016 +hlsc=inserts clock reference in variant playlist in live HLS +hlsc_crc=2539913156 +cues=sets cue file - see filter help +cues_crc=3959109360 +strict_cues=strict mode for cues, complains if spliting is not on SAP type 1/2/3 or if unused cue is found +strict_cues_crc=4206252436 +subs_sidx=number of subsegments per sidx. negative value disables sidx. Only used to inherit sidx option of destination +subs_sidx_crc=462930136 +cmpd=skips line feed and spaces in MPD XML for more compacity +cmpd_crc=3957816220 +_p_gentime=pointer to u64 holding the ntp clock in ms of next DASH generation in live mode +_p_gentime_crc=2500516562 +_p_mpdtime=pointer to u64 holding the mpd time in ms of the last generated segment +_p_mpdtime_crc=2221099631 +[tileagg] +desc=Video tile aggregator for HEVC +desc_crc=2683596396 +drop=Specify indexes of tiles to drop +drop_crc=4121476755 +[pin] +desc=pipe input +desc_crc=896381415 +help=@This filter handles generic input pipes (mono-directionnal) in blocking or non blocking mode. +Input pipes cannot seek +The associated protocol scheme is pipe:// when loaded as a generic input (eg, -i pipe://URL where URL is a relative or absolute pipe name) +It can be set to run forever (until the session is closed), ignoring any potential pipe close on the writing side +Data format of the pipe should be specified using extension (either in file name or through ext option) or mime type. +If neither is set, data format probing will be done + +On Windows hosts, the default pipe prefix is "\\.\pipe\gpac\" if no prefix is set + EX dst=mypipe resolves in \\.\pipe\gpac\mypipe + EX dst=\\.\pipe\myapp\mypipe resolves in \\.\pipe\myapp\mypipe +Any destination name starting with \\ is used as is, with \ translated in / + +Input pipes are created by default in non-blocking mode + +The pipe input can create the pipe if not found using mkp option. On windows hosts, this will create a pipe server. +On non windows hosts, the created pipe will delete the pipe file upon filter destruction + +Input pipes can be setup to run forever using the ka option. In this case, end of stream will never be triggered +This can be usefull to pipe raw streams from different process into gpac: +Receiver side: gpac -i pipe://mypipe:ext=.264:mkp:ka +Sender side: cat raw1.264 > mypipe && gpac -i raw2.264 -o pipe://mypipe:ext=.264@ +help_crc=2923617023 +src=location of source content +src_crc=3972578009 +block_size=buffer size used to read pipe +block_size_crc=3061152364 +ext=indicates file extension of pipe data +ext_crc=700124000 +mime=indicates mime type of pipe data +mime_crc=1064584767 +blk=opens pipe in block mode - see filter help +blk_crc=2775489198 +ka=keep-alive pipe when end of input is detected - see filter help +ka_crc=423639628 +mkp=create pipe if not found - see filter help +mkp_crc=627091973 +[pout] +desc=pipe output +desc_crc=3061618447 +help=@This filter handles generic output pipes (mono-directionnal) in blocking mode only. +Output pipes don't currently support non blocking mode +The assoicated protocol scheme is pipe:// when loaded as a generic output (eg, -o pipe://URL where URL is a relative or absolute pipe name) +Data format of the pipe must currently be specified using extension (either in filename or through ext option) or mime type +The pipe name indicated in dst can use template mechanisms from gpac, e.g. dst=pipe_$ServiceID$ + +On Windows hosts, the default pipe prefix is "\\.\pipe\gpac\" if no prefix is set + EX dst=mypipe resolves in \\.\pipe\gpac\mypipe + EX dst=\\.\pipe\myapp\mypipe resolves in \\.\pipe\myapp\mypipe +Any destination name starting with \\ is used as is, with \ translated in / + +The pipe input can create the pipe if not found using mkp option. On windows hosts, this will create a pipe server. +On non windows hosts, the created pipe will delete the pipe file upon filter destruction@ +help_crc=2537976292 +dst=location of destination file +dst_crc=821593475 +ext=indicates file extension of pipe data +ext_crc=700124000 +mime=indicates mime type of pipe data +mime_crc=1064584767 +dynext=indicates the file extension is set by filter chain, not dst +dynext_crc=1468217109 +start=sets playback start offset, [-1, 0] means percent of media dur, eg -1 == dur +start_crc=867548297 +speed=sets playback speed +speed_crc=1335286326 +mkp=create pipe if not found - see filter help +mkp_crc=627091973 +block_size=buffer size used to write to pipe, windows only +block_size_crc=1196031025 +[gsfm] +desc=GPAC Super/Simple/Serialized/Stream/State Format multiplexer +desc_crc=2320300515 +help=@This filter serializes the streams states (config/reconfig/info update/remove/eos) and packets of input PIDs. This allows either saving to file a session, or forwarding the state/data of streams to another instance of GPAC using either pipes or sockets. Upstream events are not serialized. + +The default behaviour does not insert sequence numbers. When running over general protocols not ensuring packet order, this should be inserted. +The serializer sends tune-in packets (global and per pid) at the requested carousel rate - if 0, no carousel. These packets are marked as redundant so that they can be discarded by output filters if needed + +The stream format can be encrypted in AES 128 CBC mode. For all packets, the packet header (header, size, frame size/block offset and optionnal seq num) are in the clear and the followings byte until the last byte of the last multiple of block size (16) fitting in the payload are encrypted. +For data packets, each fragment is encrypted individually to avoid error propagation in case of losses. +For other packets, the entire packet is encrypted before fragmentation (fragments cannot be processed individually). +For header/tunein packets, the first 25 bytes after the header are in the clear (signature,version,IV and pattern). +The IV is constant to avoid packet overhead, randomly generated if not set and sent in the initial stream header. Pattern mode can be used (cf CENC cbcs) to encrypt K block and leave N blocks in the clear + +The header/tunein packet may get quite big when all pid properties are kept. In order to help reduce its size, the minp option can be used: this will remove all built-in properties marked as dropable (cf property help) as well as all non built-in properties. +The skp option may also be used to specify which property to drop: + EX: skp="4CC1,Name2 +This will remove properties of type 4CC1 and properties (built-in or not) of name Name2 + +@ +help_crc=674252074 +sigsn=signal packet sequence number after header field and before size field. Sequence number is per PID, encoded on 16 bits. Header packet does not have a SN +sigsn_crc=2881341237 +sigdur=signal duration +sigdur_crc=2956144634 +sigbo=signal byte offset +sigbo_crc=712939432 +sigdts=signal decoding timestamp +sigdts_crc=2272876547 +dbg=debug mode, skips packet or force packet size to 0 +dbg_crc=276458543 +key=encrypts packets using given key - see filter helps +key_crc=761663705 +IV=sets IV for encryption - a constant IV is used to keep packet overhead small (cbcs-like) +IV_crc=4150683076 +pattern=sets nb crypt / nb_skip block pattern. default is all encrypted +pattern_crc=1443111083 +mpck=sets max packet size. 0 means no fragmentation (each AU is sent in one packet) +mpck_crc=3469220762 +magic=magic string to append in setup packet +magic_crc=950225201 +skp=comma separated list of pid property names to skip - see filter help +skp_crc=649594567 +minp=includes only the minimum set of properties required for stream processing - see filter help +minp_crc=3288650696 +crate=carousel period for tune-in info in seconds - see filter help +crate_crc=233595902 +[gsfd] +desc=GPAC Super/Simple/Serialized/Stream/State Format demultiplexer +desc_crc=3195342797 +help=@This filter deserialize the stream states (config/reconfig/info update/remove/eos) and packets of input PIDs. +This allows either reading a session saved to file, or receiving the state/data of streams from another instance of GPAC +using either pipes or sockets + +The stream format can be encrypted in AES 128 CBC mode, in which case the demux filters has to be given a 128 bit key.@ +help_crc=2170380418 +key=key for decrypting packets +key_crc=3137419262 +magic=magic string to check in setup packet +magic_crc=1086934258 +mq=sets max packet queue length for loss detection. 0 will flush incomplete packet when a new one starts +mq_crc=3948056643 +pad=byte value used to pad lost packets +pad_crc=567022909 +[sockout] +desc=UDP / TCP socket output +desc_crc=706603685 +help=@This filter handles generic output sockets (mono-directionnal) in blocking mode only. +The filter can work in server mode, waiting for source connections, or can directly connect +In server mode, the filter can be instructed to keep running at the end of the stream +In server mode, the default behaviour is to keep input packets when no more clients are connected; this can be adjusted though the kp option, however there is no realtime regulation of how fast packets are droped. +If your sources are not real time, consider adding a real-time scheduler in the chain (cf reframer filter), or set the send rate option + +Your platform supports unix domain sockets, use tcpu://NAME and udpu://NAME +The socket output can be configured to drop or revert packet order for test purposes. In both cases, a window size in packets is specified +as the drop/revert fraction denominator, and the index of the packet to drop/revert is given as the numerator +If the numerator is 0, a packet is randomly chosen in that window + EX: :pckd=4/10 +This drops every 4th packet of each 10 packet window + EX: :pckr=0/100 +This reverts the send order of one random packet in each 100 packet window + +@ +help_crc=2884219652 +dst=location of destination file +dst_crc=821593475 +sockbuf=block size used to read file +sockbuf_crc=29840651 +port=default port if not specified +port_crc=3919277725 +ifce=default multicast interface +ifce_crc=2634186680 +ext=indicates file extension of pipe data +ext_crc=700124000 +mime=indicates mime type of pipe data +mime_crc=1064584767 +listen=indicates the output socket works in server mode +listen_crc=1400617944 +maxc=max number of concurrent connections +maxc_crc=2929003351 +ka=keep socket alive if no more connections +ka_crc=3219833474 +kp=keep packets in queue if no more clients +kp_crc=989374280 +start=sets playback start offset, [-1, 0] means percent of media dur, eg -1 == dur +start_crc=867548297 +speed=sets playback speed +speed_crc=1335286326 +rate=sets send rate, disabled by default (as fast as possible) +rate_crc=1201178208 +pckr=reverse packet every N - see filter help +pckr_crc=1266362871 +pckd=drop packet every N - see filter help +pckd_crc=141742775 +[rfav1] +desc=AV1 reframer +desc_crc=2440643930 +fps=import frame rate +fps_crc=894530348 +autofps=detect FPS from bitstream, fallback to fps option if not possible +autofps_crc=2459069437 +index_dur=indexing window length +index_dur_crc=400520 +[ufobu] +desc=OBU to IVF/OBU/annexB writer +desc_crc=1909868685 +help=The OBU rewrite filter is used to rewrite the OBU bitstream into IVF, annex B or OBU sequence, reinserting the temporal delimiter OBU +help_crc=3160818170 +rcfg=Force repeating decoder config at each I-frame +rcfg_crc=1063479090 +[atscin] +desc=ATSC input +desc_crc=2310180652 +help=@This filter is a receiver for ATSC 3.0 ROUTE sessions. Source is identified using the string atsc://. +The default behaviour is to populate GPAC HTTP cache with the recieved files, using "http://gpatsc/serviceN/" as service root, N being the ATSC service ID. +In cache mode, repeated files are always send. +The cached MPD is assigned the following headers: + "x-dash-atsc: N" indicates the ATSC service ID + "x-dash-first-seg: NAME" indicates the name of the first segment completely retrieved from the broadcast + "x-atsc-loop: yes" indicates a loop in the service has been detected +The cached files are assigned the following headers: + "x-atsc: yes" indicates the file comes from an ATSC session + +This behaviour can be changed by disabling the cache, in which case the filter will output files on a single output pid of type "file". The files are dispatched once fully received, the output pid carries a sequence of complete files. Repeated files are not repeated unless requested. +If needed, one pid per TSI can be used rather than a single pid. This avoids mixing files of different mime types on the same pid (e.g. mpd and isobmff). + +On OSX with VM packet replay you will need to force multicast routing, eg: "route add -net 239.255.1.4/32 -interface vboxnet0"@ +help_crc=2438370667 +src=location of source content - see filter help +src_crc=2057671206 +ifce=IP of the default interface to use for multicast. If NULL, the default system interface will be used +ifce_crc=2720424345 +cache=indicates the files should populate GPAC HTTP cache - see filter help +cache_crc=3991612955 +service=ID of service to bootstrap on. 0 means tune to no service, -1 tune all services -2 means tune on first service found +service_crc=2153960510 +buffer=UDP socket receive buffer size to use in bytes +buffer_crc=3107696634 +timeout=timeout in ms after which tunein fails +timeout_crc=52156333 +kc=keeps corrupted file +kc_crc=2656289289 +sr=skips repeated files - ignored in cache mode +sr_crc=3811532847 +stsi=defines one output pid per tsi/serviceID - ignored in cache mode, see filter help +stsi_crc=1919500824 +[ffdmx] +desc=FFMPEG demuxer Lavf58.19.100 +desc_crc=3623408448 +help=See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more detailed info on demuxers options +help_crc=2773937765 +src=location of source content +src_crc=3972578009 +*=Any possible args defined for AVFormatContext and sub-classes +*_crc=3038444400 +[ffdec] +desc=FFMPEG decoder Lavc58.33.100 +desc_crc=1676202269 +help=See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more detailed info on decoder options +help_crc=3645840873 +*=Any possible args defined for AVCodecContext and sub-classes +*_crc=3590061128 +[ffavin] +desc=FFMPEG Audio Video Capture Lavd58.4.105 +desc_crc=1713481961 +help=@Typical classes ar 'dshow' on windows, 'avfoundation' on OSX, 'video4linux2' or 'x11grab' on linux + +Typical device name can be the webcam name: +'FaceTime HD Camera' on OSX, device name on windows, '/dev/video0' on linux +'screen-capture-recorder', see http://screencapturer.sf.net/ on windows +'Capture screen 0' on OSX (0=first screen) +X display name (eg ':0.0') on linux +See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more detailed info on capture devices options@ +help_crc=3558347629 +src=url of device, video:// audio:// or av:// +src_crc=498597052 +fmt=name of device class - see filter help. If not set, defaults to first device class +fmt_crc=984977400 +dev=name of device or index of device - see filter help +dev_crc=1788567366 +copy=Copy raw frames rather instead of sharing them +copy_crc=2380977802 +sclock=Use system clock (us) instead of device timestamp (for buggy devices) +sclock_crc=4121739073 +probes=Probes a given number of video frames before emitting - this usually helps with bad timing of the first frames +probes_crc=2991822157 +*=Any possible args defined for AVInputFormat and AVFormatContext +*_crc=2282268849 +[ffsws] +desc=FFMPEG video rescaler SwS5.2.100 +desc_crc=2667761521 +help=@For bicubic, to tune the shape of the basis function, p1 tunes f(1) and p2 f´(1) +For gauss p1 tunes the exponent and thus cutoff frequency +For lanczos p1 tunes the width of the window function +See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more detailed info on rescaler options@ +help_crc=1152263406 +size=size of output video. When not set, input size is used +size_crc=3013168504 +pfmt=pixel format for output video. When not set, input format is used +pfmt_crc=3385868644 +scale=scaling mode +scale_crc=1787905575 +p1=scaling algo param1 - see filter info +p1_crc=3248942273 +p2=scaling algo param2 - see filter info +p2_crc=3750392874 +[ffenc] +desc=FFMPEG encoder Lavc58.33.100 +desc_crc=1585704748 +help=See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more detailed info on encoder options +help_crc=4200915646 +c=codec identifier. Can be any supported GPAC ID or ffmpeg ID or filter subclass name +c_crc=555030123 +pfmt=pixel format for input video. When not set, input format is used +pfmt_crc=1510696702 +all_intra=only produces intra frames +all_intra_crc=95202502 +ls=outputs log +ls_crc=2313182371 +*=Any possible args defined for AVCodecContext and sub-classes +*_crc=3590061128 +[ohevcdec] +desc=OpenHEVC decoder +desc_crc=946975516 +threading=Set threading mode +threading_crc=3260668799 +nb_threads=Set number of threads. If 0, uses number of cores minus one +nb_threads_crc=3233565253 +no_copy=Directly dispatch internal decoded frame without copy +no_copy_crc=1587072150 +pack_hfr=Packs 4 consecutive frames in a single output +pack_hfr_crc=3457532627 +seek_reset=Resets decoder when seeking +seek_reset_crc=2431770610 +force_stereo=Forces stereo output for multiview (top-bottom only) +force_stereo_crc=1504896698 diff --git a/share/res/gpac.mp4 b/share/res/gpac.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..0030ef4b24aa9bb662024bf6252aa0ebeaa39886 GIT binary patch literal 1660 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 z%0Asi2`xfa-^ga>-G)Y@;*{|j`U+!~s9;6U?nwFgm3Tkz>~9wOGf9_1b^4}uvnzsK zxXw)g4}v5mVWyr;=qnYx^l)!^YOcL1RfQYn=hgX{@JDz5@+;tb2EuP=HF-^*e=i$? z1HAkrYcUNFJow$d1rj?TZE_pG=CSk!tYRPVIR?Or@ed?+jTat3omBLoK|3yM^LV@! z5QD@_6`xG0JQ*O?-srp16aoJHSlirA!;F0ixNEw$ZFF+so3_|Y_a2zsnhmkecd{LO zpS#%&D+uM{+bwV1iBiMQsoE}`cuY5yxFAM;&ScENg<2=bA5MuBh1zJE#-zz7wT6F1 zP83Ku$9_dZjX=8)0BV2(Cffo?!tRuu&qQyN_#c#&&$h=Mj<=E)^X-cFdd(eO+Id51 zPQjE}r8l4%m*b|nk!3=TZ!~X~rd^tNVIg^$7@_QkKY#z8I6Hm&5zwficvZagN$lnc z^9e^tcU>9a%@VDreBfFc-fujUhXz`Z?;}{sOo|06^L| zBPGr$0HH%NqOho2Ob;1N{)3kB569O+`C&N|=c{H{#@%UKu(58RrsSXPj$UgX{EwGo zyo~EiJwI3v(2MUjzA(tyCoWR@fhRtGv0#`z!dK@t<2Yhh^dKFs4C%qSHOpzyd@D2j zp;_!yc|8N!z35}r7Z+a9*M69^Kaa z-r`IKxV8Vi+WUKZ(_PqoxJKC3XgTxu&+fl7s{6BtOeS!+J6iW|8lQcmCRNCwAH zAHP;EQj)siuGEhU@rw>?EBr+;5ohcHHL{HTK^Z9fA#AtGzp{tA;e;%p^_e2qNwmVW zgouQvc36$`?_FMTN9^afdkUyp&W&e(lIBsoY_NIRbun5)#gGr<#phjoA@=s#;If8MEzDlKaOrUq0sVSnbMF>5w_zg%W=@z^F}4eB6m3MsF?oD6elBKGKBtHNmU55OzpL7l>oKv zr_alq&A{G?t^S`5YQzaYi=KaLcH>u3DPr5T;CX{$C-{GOd+(sAx^G)_cauS& z$x3X3fPjDs5+q9!kSr(}QAC2|AhAgT0+JLEm8cR0l$;w>Fc1XEsmWP#4&84xe&4yj z_o`0Sd3Ebv|7hAOn!WexwdbC5jycBoTAfUVGQ|8)G?e}^Y#himaPAA=bv41zQWy5% z6*@jnZlnBcO#NVg@3wv4W!{rL`2CwEg-B<77Nv>JIH(mhxo~y~*kx|z-_@9Ox|~^8 z!NGoW_Gp~rguO+pXrj^7W9fz1YIvmva9kEiNoM|&VCkLF*0S1@s;%X>{>S)0czaHYx) zC{Y8m7qU*m+>Odo!F`8rrc$GJ_f4=6GSAG9S8%I;~K6^#<` zdWM+9K&s&_R0O}|U3%jsNtrXvy*CG)F{J>?@8#$kj)csQ8x&n!@V$If09?3e95*$K z;M?;dS%lT~4M^r&0AFvA zZFN#1#z3wy4noA4=YA5sWz*IZi7&Wr7F_&cs&Vq>g<^)w9nf??1+4x;RX}3-AJVDD ztQ&Z!1J&ZcP>d-630yQznngs`H6~K_2%ApXEvGEilK=etzsSoe1?kj;^2MH0&ws&V zW0t2!`1|@7_5t!+c;eAt_~O)q(geU<)fYXmXET=_&0uJkKyn&NBs(o%d3_^ROzPMH zZCz66>}`Kxjv4!5tbXq12=_@d9S%2q`3@y1g+eEPbB&)WesdA0r++lVu3%# zQ)_<8XJHA063XusKBEi>;2))3(JNl$UJlT@F4cQIMS2=ZDG|YDShTpCh>PYy5THY0 z$EnVOqnr|S|6|ckiMy76FX(g$^sH1)TB^pFCoNq*;P8Z@KT~v^Hvc^$bBcTEt*e}p ze9L`EXrs8Bc>>LEJD#QV`_Yr)t!PB<)YHLHG1b!ACvdXMH*J#~A=mx&p-#KslT*Hx z&4hx+H3Bp<>`#E9Iw}5oj*j+ACYlce)bl89#T)y8q&{n9o2a9ywqF=awG(bs0*9-1 z+5_Y1h9)+n^Z+CL3!%Q-`TO(l=JNCeD+EBkztAFZ+5Gp9fL=rE+DJi)}*%!2gH7AQ=f;N2cBT~~(fUeRC#ZuMw2RCCEg+bT4eb8K z-ebi92ruqHvYHWs+fs<%4VEsmjB5zih~oJ0wodg)+!rItEZgo8t?Hi&IcFS}jR+U| z`usnizrizof6uN+qF+0oG=`nk$0@_0^+1ZJxP+{_9b?&S9$?>T8#7cRdE=W;91RLD z2IT5nf0u@Zi#=ACAYhIA-UIY$yF{qMjfbM3Cc|=t?PAH`&HHV2=8qg*eGs2m=HhHW zy>=dmQ(Nnkk7YnER(6?_9H&cPGd64VeU~=HdUq(Illk#G@g$)>`kLLRh$kkMM`ztT z+i$+DRKEY33xi{rGJ#mjWSyA)dsxZ=GgB!2|MH`YDqjxnF_RH;Q{qEMK{i zGX>_{8hV!`Z*i6>*Fp1pQJ*+%%KO|`WPA^bFW_|e`Wsi@?3>sBtZTY6y~mcpJ_K2j zTEl`Va>dcPdcSX6zK-fgGVsNu4<={ssx#y-?Et2jDw5+TSmZuMpf(2@CEvTlfQdHm z4S)9|9G53K@2f@w12YrSFB!fa)UIb=cu_L2>teMuMS%6%B*WbgpALxWo<(FhktS)e4dyw|-1Ua-Do$MEA%lIKx~lJg zF~=50MJB2BJWHRbaKJ)7VadiAHnEhaI(4FVcy8W^kma%sQ8{0z5fAnAP4gE$0grC< zdsdmQ7BA058oDPsTvM0*D3+(Pzg8vM&sws-;!ybH%yA#dPe1v0sy^Of=aS-Qt|{CD zw%IqZm|5V?4d%^hcfiA)m~`=pyT(_{#nIKYYH`odq_zwE*u0a1_QT}5S}^&#uN9E+ zqTuxbZr)!tHkJ8VaH+*ZVL*%_U-!9j$j)Eaf)JB~t)z+#epe>*a8eP)McMgxK-bXWA2~7T`Fm5rh?RnKgX8DI| zYP7&nbdWB0nA|13>-K=>vObyhV8VsyE7=puv7bX&Uza3-e?vc(`%<2IGGqF^=_Br7 zf9~GV_fsNe%U1B0k0;rx-TaK^wAZdvjg3f{M#D1o>GERf~qd zfar;u1pvb$QwXRC;k~-zPv2N0Q+0uOQW*Ta`*;8Qybdy$#l7*`(ZZ&~&h3UDg4dIA zOerOwPX0aF4vdnRQ=mRTB2lITjF7u7@Z&jX9Gz9s5 zOYXohNM77Lz%4|0M7=GK9yJ8UbTGw-kU9lnOD3=;qXDU%0QE%>hKpLr;cckWUjo2V z4zO_ebp?Q`tty=EidR>O!SbJ?DdV`2F>v&v!2Q`4;q* znI*&x#gnlYR?zE{^gXm}l=pApxWp{jM1N3H81L^-B*SXzIl`XU;c;MpZ|;wc&-^O} z7s@hN$Cvo?iV8$42#6OBbAeO9ctfnvsp^ar;iSz9z@Cj8?^s9!0kbp&y%J2uXMpv@ zy{>k;v1Wg^r2w;MnAI-+wrRG>Vfc5f@s7jD;W1O6(&K8T=pm192Q6e&!;KB+hH^D-6+vWj>x<4 zL%vhZ^|hVQA1}@pzen6MQIS1T7JM2>2v^JVQ$wIPI=j8(d42Y@_5{O z^61jdcUSU`vDfR(G^+^r-^*G zJT}Y0_;Yc?6{hfSc8?Rp=o#)D9ovl?TU5%pk;p7>=D%1mGB0F~yYVe433?8{BVDue zSb=2hvSw0nJdnci{%RA0GS60nA*HB-CzZpl3dB@c`0ik*+C?lKA_ko151_)1k9(@k z8r?L8-fc8}HV-ABPo7)MEe}wBj8l_j$oX34bBP#IadjxPk7BZ|c6ru~az;SHl5kGV2OHMkO`{=}$6*+UM zSdF^@^V~Wcf{67jZrkWc7F~_qHU2z%EY+{XWJbPa)F#Rx?dkSsz`3)QZG60~b7;D+ zLzInxTPXsyOAWDHL^%u;-dd=3WTN!lSyFU?Pb7b0s8PF-_*_7>#9(cDZ@&<~T3*-in(3w}H@bpuyyc2?TQwXl(0>b8FAM z-g4+nS$uq9y!W-i7A9@v*5<`u5B5~;K3VsON`L+S%})gytTQkJgivS@whl~x{|+kq z;Y+>)G$=w3Cctf}g^eNntOjZv!dgsK5K#Dr>hEQjFptjPOqD&k4YQ|pOpKy=MthX| zk@{x8In6^VZ|56%v0~3z%x(^cXN%l-%rCqfwZXG#c*E*?Gz#`b>0BZdm+43N9Vtl# zW=FOT9C>>w_o{b-1c3)YfG}+&A(Z|0I|T!`adAVFPRaPpf=ktjX~u`)$2K3zMb22- zBs`Q5DVO&Crdyi!bD-~KEro(wdgC=2rh$tjGtnQWWR2XD^l4JhNT=Er{~VA$k`9Qw zz#&*zh?{4){mrOa{Gx1)u6I95JOB~UGLseDLInp7RWtVX!N+)(fHK}^0s(w3fgC27 z3nfK?iBu?v6AhLDML3E#-k_)KFY794ICWGwJ z4&=($2D4!N56StR`e@G;JwIa{?jy;SMMY(r-GaIvQXeQJ=zn9*dc?{G0Gd9<)72Zg z_9|u8$9qZQ!Jjw_=<|i6G{Y%V!634}cpJm~z`z!F7TRpY#^{x0|Buz{wKQJcx^A)i z@Ru?V>DjlX5rhRwxeq*@ClwS22q{#UOS7N4oK+<$RPZqlMH%XlPiTpmIlXa7y6LET zRpOkrPNq5qgQhWRg(a!njh#C_-nsGU6&FY{i zODbXSTfVVT+a=Z7P_?!!iTksYP@b=dNeS2(ay}aCk`*rrj5<_rdKG8xHrF6MRe6)@ z%F~>$X*`=uyMTvme9+ytU(9gvdHsqkP4+L(tua3229#jZuE39=K*Jy;A@)nXMdTWK@A>DaWD!^pI^{@X+_CJqymS?P?^H?nS|jgWQ6$j7XpoAVr7L$EuJf+sc>#sq-CH_ z+7C?nw0san{^8!1VBAS@v5^RIM%a3h<=*1O`#=0yyP{W@@+X;j%#mGw?;gz8vh2y< z$chaT&eAwk@cuyX@IrgC!Mg9SXE^fn&xOQ3>D12fB}mADN5m(sKMc0*>03aj9tPj1I4>zjv=T zh~!8%sU7?89Lc<|f7K<@s;$^YTl21BbFkd2@UcP2f%`N!19=Vi)q^b_+qnaWSY5*G z3$9#@Fa?kykOk`~S?As6Wdz>izye!6gpL%LQsc$$IY|jsVTh(pY~6CxGw-kKnEtAY z0O!{!xb&HCXS~=ng#&8sM@1~6acZpPO;fff6&*||BkA(~iFJie0hJ5CWY?)}ECZgk ze4_WhetvqxOqnzoVxWa1p$z;@Peha=WS-AFHP4F0_E?~%vV@dc;hzKTEqZ!6)YRjH zOXOpR12euHJ2n#%LAM?siY}ehK%)DvWju~zP-5&4C)z~eH;57IQvR_Pg(FL4jaPG# zaL7zEOmpZ5Up-_Wk`XB)s{Hy}ZOB$3X=c+X6gMMLFnA|#CAZt@mBop4Vfhu4E&YN$@#CfC zPX$rEk7C1&HKoQBZuu8EX;_Y2{G()oFy^rOHMp4c#Y$ljly48f_&owVanSN9XmG7Z zp|`>7C=Up=*H92d=ViMBzeg$oAPHCNW;C}-@2`#*0D z9os9)dW;VIiOYLcMHTp2>il-bT5uCebLd+4_h5fB*Jk~UzSW&+dPS2j8Wp5OM9or! zXj<4^1%9YH5{{-Ngn+pNm~w}h0%(asm8`RG^Is*WQ4)kAUo@8aF4yH3{q{U>_`vdD zY4m>Yc<_nkdTD^jr|^x8xExB%g~OEIV@&F6Y~lOr{^g&Ii{G9)SH59w3jN?|NeP@k zr+)55ioK}czv=wIJ25ECWu)T#qgOAh!Bo1`s>8_ks=`sq1yXphUON$y3IYxh4M0R+ z&`3hKe@h6yKw<#v5rRwwM&XFC7Vt(zfs@W`BPHG6HKo1(wnKnfZvM-7HuGUr{?aAX@SNe)+cM&w3p(~_~giOm)kW= z>)u#Xf8p<#G``$YF>|f?zQ)p5tbU6H#m%k&sb+TEGGGZ??m#&O1de|W^2>=a8?EZ> z(2kavRH0puWT)%#Dsj4OYSjIZMOE9Cix@R1^I6%xS0yh<#Hi{`9-u@W=Uf@{07@v! z8V*Sa;P@J@o+*-3Vi`2o`l<29nYy7XyVUnx1DtQhsqZ~lG!(eoOPJ8uG_Y9mW}^$2 z+`mt~8t5_GL{ME{FZgNT$AOudlw`3qEme}Av)|mSwU}Jx z^XGM(pC-T1izOVUd;Opo*~O3 z->{UZ1>@T=gyUJ*=0Sk?!3jRuguBajd2gZR=%612_}mtK!j{Z`JIhdf=kv(G0ASa! z$5fi0E7zsD_aaT%J1N0{b#3wB)x_C)7}fh(le#R&EP2eu234eVj{vEQ>NO3oYiz-C zm4fq#H=k+bJQ^3;Yz17U){>EwalO~9zM*IVv+D}p1r_TJzSas9o>786Wxe6kYb}Uu zAK1=%h>~k82U%{tB|GzZqRHdLB`vgDh#YYz;PAL6a5mxB^JMOYC*?0GS4Sc41BqM6`V^9C2#CUqL@sG}PbEPfRb}skGV90)nxY<&y z3-cVyD#uSS@}&!Nv5tIG&809iZSevBhXsq5 z_dfgkz8b*#o1Zr@yy%jLM1nRc_slJiU7Y}toKZH<(5Ih<}lD{V>Qw`=q zs)K|oER2hjG=}RJnZIXBis?(In{87~4qHJdv*tIy6flv8f~|s}^=+c5IHYQ<4BZ{W zEFa|b?ys*c#kti84z7$eWP}G4b38nQySMg9y~Cx(v?(b4*U1ExAVH*hHquSVkg(YW zj--RpqRGO6m^wN2D0Cio;!Yg$S&3g=ofa7u!ifalCfua2;rN7xi*w+X#qd@KmDIAk zTS*}1VDMEq5zg#Hez+uzT6RxkGjRFi*SCSYD{&{SBqI+?l59h>!kPH(QYfD=-N0aQ=1f;1MGuZQJ_fj6HF44qW@);D7SVPvTAC{*DQ6-Z=50 z-Mw{-D2q1_4P{?)9bYyi%Oit$ph$8A3BIXqxUK>e$2=a~3#vB4{S|i$*aOmJKt2s9 zm7S`Tf&YWl4hMSSr)b12EBq(GZ@YZx2BeUjAbhj^RP62)k-&?=ftLSY*ynF^9vG4S z?`f3byZQf!y8W-_`6@-w-?KV}JAifR|Co05x1Ddae*J$;yZQ?m0PoV%PkyZpD;!Jj0|_9m<%1Ss3uDw(-F?j%#w^X+KV`&FKvfqy{~@Rk1~9Q%*I zC*~ih1bChPL%TXv@xzWg{X@F?hpu%Bn*7Ti`-{QdS-<{a-tm=exf;;?*1I_M@F91| zfc8YyKlGiGdT~0r)9>`(Z}zX`+`kCT>EZ$V)BhWkD^qZ7>t9Js@3(zzkGj7|a^sSQ z>yoF+_fJDfceKYv`!9^!dLQvQC32rK&PHj$8vdC5?YpsmOm!t%L}ZH~j5Jm@gI!|YG=u%J{TnG6-2CO6?{3Y7wQr`^tywbsYSdA~1hJJc zR<@lS1dyy3+*tj5etT}J<(~=`XLODsbcsR+zl;E@|po&9E_IY@@zjpeM)1!qZ%s@ju?oi)IgYj_R zdDhI^c>?z!)4wq@{gvB0%;*LpuypnJZo5&B=5ejYas)1P)$sEfPFBI24>5)L`;bzV z7%DB(P=H7eqNNB%b6)7Gg-CFRc@#v%S-=5~$B^cXGH@MI&ZHqFRe~c>;9>EyMCRz; z^Y8$j*PTsUy+3pv_FQ*y%hj9m(R%*NB~NEls0L(xAEZ@04hR{$C0;PnrQ`THu9J}U zIC0q~e8zpCar$NpMWhlD`T~N9p91yHDl>+kftEkyH2DJswr-8!!SQlaNxDS#`>Gu| z=h&dtml6VoA?7%~ql|@ertA4do?Y?h<~B5BDO$%07RWVlL3vwsGX4eFj9?pc2?B$S zco(VNTVeS#kFzFL5l^fd?8L&LIe|>_U|kplD4$3}{9xYaSv%k|_%uQ1kziNT+6H`6 z9$LG2X*%^Mx`r10Dwo+)U(!Hp(87ssS!OL&vv@C+ne;c6dm(dv*%D@RM}M;Ok-eT7 zJ3~MY!(|o4^S_;q2qKkuDm_V2)MUVG^CQHgjRPX}^vT>j zgTAF>yo8@$CEd1|!?ZH`c+i}m)LxDfqeZyrz%ILQojD0$S7@a%u9Gvq6KQd!+x@3% zc(+rv%5~r?I^a}kikd*FxbKPg2h8Q_p5%P-Q;v;B0hI?$Y{I!{VXNB2nP2Ze49G9e z4HG0UkY)0R#s(PL96y?T5gnu77az3PF%=X(LrDRd^jf|4_&xd!mJ!j1jc|k!R0RNL zHpDcxy#pqD>^#@u3d98UtuKKk`M9t4HwePzFUo?-jpOGe0vMCkTZ<5-F(@fkH~9PF zIIde5O~~pmhl>Z&E{V8U&IaC}zt^-Zp?tTKzlNIM25Fxoyr#qWso17H^B1QW$uxIv zm6wKoBURes^FgmmEsvDxHjFJ%gi)~TeKFJ7q(q#GL9->@x%kZO2>9lxr{**7)=f9f zFY`>8vi80+ZvR$dR%pev&@xEgEBW?YPD>2c>!y!s&s@kLiEvMn-;JArWhE&SLC4aW z0@s!n-Wpc;l*{5S;kfbZeGutx1WX{9CU#!708qTx131GUKT+; zdlm&eP;?=9CU+P)Fw4myddkFa82fmtdTr-uNBFBtLDJKMR3_O>naAgp?}*(E&Ux;} zdRrpz%^zk1m0grT#b@h)?lpsT&zYd7*jL@?6d(|2SnA<<#erOw3Ac#*jZ0Zhv)hL} z6bI!!l6aB5vP#tYBJ+7LFo^?LT9cwAoAIz?6fpu8K?-pr;BXuKQk0z#G1}qT*31hev;g7EH~_GDfV$^2q_aJTPA+DxUT5Io2=XS5K*J@V8-$dl@%(ILrN1mUCbRoBpr7I_v`N{-4TQX*6| zSG3Bs5&-!eaTaG@7%%O)Ftfh3wX~wcjeZ9Pk&=?Ccb>SQr>m}A`S+GTYcc-p?Pv zNqjypoE_Tydv9}!`O2#`-=okmuj}@_qq=#FN~_-zsY7IBsM|0X6cmd#*1a7@f8nf>5+YUoE(#HbPnXR- zr`L+yXCFx|H(thN{H`yS-xDNKMUmUECN6#r4_t<5z1 zxV&W)^7jCI1go6G6fkgBcFJw8cWf8|Yk#tW`84(*`nKCQ9e1;1&5ex0lMMeJ2^@X7 zm{j)}xciiMQ&}obTQu|);ql5kG$9rG*g8gmEeEZzz)EC=^{om)0!mk>85j&~O3A|q zHCr?USwH*!Qz#*yk!ZhW9&@lIbd|$Y1J~c|#PC$Wp5)7LK4(|9d0j{+O~?FOiV zlfMPK9#`N*o^*lWVuJbEXNMvM8yjDWU;Hb_@%7mI@E~zpdZ^>*s$m2piU17NQuQw~%Y< z_$I9yViQ3>i}C2jc72A^|CDzQZkY;!U^L{#EE3=Er(}Ce?=Hs`RwBZXF6@0~>bD8} zFim>%t2+;#xx~lAGOy`1C(b}uZ)f1?b4Yct)(7}P*dY~y zz-&4LL{aMl#ucU3Q9_v zGBv(}oU3$vEP$RFr*E8>YAFb#&2F?y6x4c_@aHV;C4d7%cnO*-2aXWlb>0QQUWAIr z$G}ApHoq0707X*EKrQ?eZ-M>V-m^wbQ{T^Dzg|{7AW7vQGQYChvPbYfsMg^2&rJdO zejWd}bbD%fPrNM=NA~5KKLW?iy}!3mF2$BxKEGD|y?)Vmlpls5q!r^M(fU3mqZ~zh zjgWdi)C>)j!9^>R zih79WJbJ)S%bE$&KS|456z?p^32JI;kZoG)>#rgYL+l*#pBS(D>wFwM;Ba01Dy)1h zK(scC_3Ll9yGG%!9tXS^52||8E0N*EY!xP3{3{@IA9gpYy)-gDm9Qlk2}6_u4=;sV zz{ZN4qJ0}GI|(3$w>(3(^voY9^s%#Qri?V&JXmAK0T_b#x{PtAMfI2@X>e}C?p zj4OJLc|iC!ft;aQNr{wRfFCdp1Rz~qDAR>gKpVRP@bXIHC{BFag&Fj`MWYAazb`)C zpMJ&XpIzXIJ?y_>pr0^9F+a3-XnU-t*yp#$KQ1Hd^1d!te6Hfzi|2~h*vxn9<>Vx} z8(Louez%?8mc4D|(q-}{J&F{U?ntr^*AtS{y9h%FU%aGe1#{uLaF-LJ14`q15JM>w zIi*q8s$rPT1R#(d;POznF%vmufqk4lJ+if4A}WAQWTR%6DLT*%HdWN zA&?Lg9IgPf;=u>=#S@}a-NmCab6lK5N*1HJ5nG`~pHS1T(WP6ZHC%K4<{^NNWf|G7 zZhf1w`o;5p-?qWa1w7M5=cW7YF>l^(VWi0^GP659KHN|)tnU#nN+We>P>CTaRZizd zxSWgU_f6pFoNE_VL=m zme;Q?lw)}W;+Yr;A=_s*+ZQ!U$#DS@8zyzrj=b7j;o8GJ2DzDb@?8h)44GO-e8SNa zGcU}FgTot8H|V`nRr(U*DPBgUl0ZqlKwk=-h0@~aI>`7T4#GMRw~|%H=ZvumAjqNt zl1`F6fgkw)b3)0ep?87#`2LYU4#;d>PZQb_7oV{ID5<7!eX6DT+V(8>rE|R@%r6Ub z3!Kmt9hvr~Zvu2!TlVxW%{H5-k1a3e2})blzYHWlu}Y`zNpK4Kz-XyC>~=QfiCW|z z)(9kU+gySj56{VG)M7JX^L1vQLCHl(X|=V60@91CCo`PTsO@Ir>f=t~WJl^(S z?{u{t)QCyh-6P^TSbgBstweA~EnCGFr?4D6qH#H=IqTk|s5`$TZw-p2jWl+8^VPW2 zD z%)dhs*nSJ9U5BnZ0=_uJ4Ja4uK->u?uS&~c1J%CvT-(DjSy^`&?(2D$E>Lx8|1ujU zQ0O9&n^=i=4gYaogWTSird&JRr5bKwnQ{_gIqKk5DW$UBixHHx44)O@*<74_v$;6m z{zCKnh;KU8`iwY9shtaZGbEq!dZ45a0k$Ef@BDE#M5(avgMpw+fE|w4S3fyjiG1dx zeuoUqPVz*+zD11BBYG9XYtiyNbFFa?^UC5s_~v&M*e{5d30{MTy!(TgsH56Z{m3=4 zR9r$r@$=o(?h7&kn&K?o*B1({1n!uzgx_v9c^QvhWR2j=%9A01{n-O8Z+0K>aPe#V ze47+JUOZb%!StvZz#9C5MFG&beJ5Y(t;2|H1!j8fVP?6EX_2c5N>(hdPYroO$LE2g zd;8L-ES!h-$8=8+a^Y!Kst@HrR4EU$JPB}~b@gl1GBhNUZx+(QJf222p+s6wnkGK9 zXeSKw*qO1zLH2k6R2N-9PWAJEdd>`w?0}88gGY89B4G-6O4RW`%WzmQ-PE*oe&H_l zCgIXM52WzDQJP=Z%WD!i?`g)hudf8!5m{bYy$0*qS~CbKe(G`KYQN$~gpXzTx=WtZ zkAw3?s|7ak+fVhRWj3>gCu$2x*~_U2NL7Q4l54;8eFhq7;O7 zWH9t-C^^4@3&utN;X9x9ra}I`Qk+f2GCS{6FlpzJm4w=C8nx9bfenfw6?UoM_*A;E z7Y1Q1it%$b0NDmHLDJg93OZeYDTJ4+vG3zmM!gZf5nFrg?^x;cs`r|+M12DTBOBP{koUzM#$#{x6-QsHIs`U1Y%QL5MFY6?U4z8IKA3i`Ud7zB-lOOQqwcN`^QU6>bZijIx zKO{o`C>kGPGl`uTC{|F$1_ntogx;1NfQYkQfeJXe=)F39@ps35xVW#g3kFd< z(@vt#qYYRzw5*!Ocl0<|zse{)sCj?VA!<*f5yO+WmBDi;GIGZ(UYwiFvio>~K)a!? z7lQb^fZ$2?+Iu5yl6-QDg;P(O^@7vl!gW{?S+z0^i z4@QGkz?`t2?t`BFwdLcbx$v9U3#9j|?;BYz-Pqjp{rJr#&J%}?6YKqMcsPH(O&B8* z_UTTt+ylY8wfCDM^m|^VTvLrT4i6OmDRsUj;Wl51T=pN4tJF95baZ2*9)GwBZq##0 z&(piV`8@0XD3M=wk7O(OgQ(*m!NHQlt6qqhz>s;T6A6(j7F$6uJy;PD8?Q#|YfDoc z2a|}BnH-r%sw|IQR;6;H^DD`DqM!CjInV6p=4O7BPW3EhZr(gfvEOJkYCg9$@7=_5 zPK$^=hw6?0+Q$70v^dt2ksW`|8n~5vCu2WfazLpYia=LkC9(B`=?M3S`CcGESyN_n zXTA?2&U7UHpg>&k1g!wM!w?K8h!w(1b*-!Xf5pA}Fa5=CfJx0iCNnKnpp$>fl{oFI zn_7X-{D-<%0O;leq~||>-K7M40FMHkB0j)D^#6vom&HT{4sSXG5EH-{<-FryRTrED zcfD>A2Q;hUh4}s#6Xd_Zk!Ha{{OYWop3CXxq&%x-2&VDAq;yRmKa^(EyPT8t{jRgs z71OXuolfdCMmt&`C84WvN}tc%aA(SZD~d#xWmc9m^G3r=0zG7d6nWQ+zZC+h0w0-k z7dAQ)7)M;Aj>_&8n2pWYNqv~-QW$;n$tTW{jZ1-KGwOKqYHOZ-FUL28tOC|I-BSUM z>?6*D;K=+xAao(m5>Wz;(ky*Z^24d4*7SR(%QjH|O8ii2UjoPu9%O2>o?z zh8WvyG~Q*;*$ku>Iu=IF%A@uNX2n)_a)Z1Jyui7vuo0kw(TZjFe>n~Uisn!$-pOOf z9)1aic)Vi|!bpZL5SxK5fQ&Yg0kmN~{T+gWzJAX#_44Nx-)LvdRQc+kw(@)=XF#q7 zhd8UE#la%qzphSBT)cBFqIF!pAjo}qW+s8$Cu0WNX<$_sSoJ8e?4?{yg*ic~ zFQ|}3FC+1aNsS$0AYOGBjy05l(4Ap$vm5lf8Nz|BJUER^d`=6j%1;B>lCgz6$J8G* z=DvPxB(bWXz^b^JWT@z3-F^MI{P#HpkD}1`X51GxFTMWNqwnecE>Z8v&&4UA!!BPS_FG;&9|?#I6Nj zRdAMv#Uc6fy0Z{bOblfLa!0%=V~Y|Myu=$MKL}x9g^S;WuOoCiG_^ETTvhl7UWGHKthm-~TUM@I@?hUrf* zL0o1~-YwnRc?=Xp8<#XjHEW>m4)WReFC*iT>+6_A-o0x?3^d0}STv~@A;;OeZn#{Im^PO!^;v$~i zb=M;MZvBu5rT~Z&z(qv6pb?A1<^yN&%;)?nz!6Z9RdyQG%=f$@nbrUOHE=Y(Ao(aV(2*clmOybEKcWDVj zf<@z%l{qB|m0>iXXU=fT)o*v>(UlR|1{+I;jvKg?ujj}@$H#Vl_dY9AYm~ieUK|M9 z&uhwby(4}wd-8j0=lhdf&hI$3g3jt~kBjokM9_G2=v9u#u2__2R+d6_rid=7P&K50 zB1Z|pIPrPRL;xG3eQV>3fw+zRshi|zi@7JHjdJFxh0S#H?IcNtjSdo&QEqP)SZrmD zhFa5LiDw|>(PeG76fuwC%|)AAaZG&C^eSgd4*qOA$YO~1wyJIC8y!ArUmqbP1k@~q*mZ)aB91*M2+cf%tTemccy5fX|XjZ{d@sHant_H}yg4H`r^ zJ;B&|6)iG)IK9?HF3NOH?+JQu^@3R~eBuTSEI>Jv4H19}HxD96l?BI#e5L>vLyZHP z94DsuIdS~PrwMz;r4X;oOXDu-P@u78RAecA?0wKTkMmRMrq$i@3~Dm*I?mNxX%f+i z58Pa-+RZR==E2VQ#X8^^(p|a(0!m}kwTzoojR>yxX~&9)zokXXAazK|l1US37=v|b zU;w^VhFK9cbA2NGte6-7)|!ma0*1)0Pxlt_2i7y##wHyH49-miPPAsQ*|Fhwt z%%I)Ewf)Ey%={qnJ0-oty?(dYa^>*wU3IR%C3ouFpKpuuEIDnvo|-x9$j#j19h-em zy&uo&L}yti2#ZofE|_qAeM1Xz(a=Do$@Dyz@RLKS>Z)My9`vrx;!*fBs#B7kW%|Vq z(R0otdwYAu90$w(68z`3A6ZmkAEi#Ev6y;tjJ2g4saXuA44Cg;`E_Hi{^V6&8g1M! z!k|fO-*VObwgi&wLBfOzYlT5&eI!FBlu2fWZ`P6PV8;&lu*HPUz^Vxl)e!hOOhaB8 z^2iV{B8tA?AApnY_j|zNAXPto9ME_x2&*4C`j(o*dM(R1V#{*5Asp`5-0zKQMHNgBy0HE~og>0=<+~K$0%>z5&0H|v7uB9NI&!F- zmkm5qQd?<4n4e+1`iy3eKn!|IDUJws5!4Ua;|J0#Q&y4iZ%_?_L6?`7w53@Qp#?_l zY2b=9NEJXoBUG(zpBBWum{-|W@rlC`=KXMg#&nOi`op`jyQWq``C5On z{R-AoXrN@Ilv+iM>f+h+7vsd;Eq;<+yj0cM*gOAtddJ}`rmbr786y%9PDUV)XyTK( znpbPbrJ)C-;>(oV0CU-MCu^5Tg2u_TgkjfjBm#vJ$tlpDJnU)xq=RW>ql|mD-*(s? z6kl;)Z&aP}Wpmac=TvJ97gxv)s`ZAXy`T5R`e9N($`e@z2>Kq+OrXSG_?s9c%ciXPnB_xCf5+w(~eGpFkt-%fj(U_i5z_+Jx z$A9of0Q%xH(-{LB-eO|5(ljhT-c+;-Ne@>(Nvr7!I2^7*duccdTzvE!^O;P${(%&E z)>A4^+2_WNA9x~}Z8zMSn4EOjj7i9&`3sCHu1m3dJbtg)WA`eC=(0Zbc?wol63$V4Xd#Bn4^33lzY_&Vs1=8YDA}yec7{&dx#GM`KTK2kw7R zDnP3y&o67fh`sZR)vryguX30e&7E?q+WO172LgfKN^{2>Kh1pl3jg+~FVQ9Wxk+i>Qu6ENiAjw~vn!L=yrPYZcGBbC_N*P%-*xQbaUuBe}B0%kumB)QQUT5BUC#aH^ETG(wG=kYXighU(>UzYduSdz;pD&$z zLGXuHN4ntx^Vx-uL$+PoqKp=0&!;`&D=0XhP1fIkZ!-1Ln0zvWY|$(I!*d7aH-vAI z+0le!0=gO|7oR_%R>;Z%ji>!M)sp#RbW7?4V4^lBWp`*Y2n!q^p0trpwH|c5_F8wc z>x?k0d*Ll1x0-Tb-=}|Ze#{ByMhjxuhtPPu73m>8w}o7S&OaRnr0W)(bu1O zw)ypT66AH?U*rqTCX}V>bU5UP2uhA0WfcBdS={$lE!)^d zLB0{3o1x+!kjEQ?DIi`&++cl-XM=+N+@2Y}Kj#B-I{7!>^MsNq2#*$bcCmq*Au9SF z?=H+Uue`b8Xh{e0Av^D+Y>+6NLqxKQ-|{dDgZ(ZjsWSU0!KJ#Ew2&VUN&K$N-Sv2M zR6!5Y!&vHj3FjwZQY&$QYYHP6xS+f{wWkZWA_%I$=guhL`T;r{ZsNUfe2%b{Z>Vcy z_76*`BMtn8Io#eT}mxQ3l(cV^4K|Yky#i~F}iz8bR$QKSR!L; z<)+iLPwKm42GNXffmMC3mfg4%o%PpsX@R=Wb_lGZuEz3TRh(l1aW0{Ng@p?UyNl>< zHPcx^*xT22L3L1X=@h5A~^7?AV;Fz0IJakf)o{XKh z$)j{ov@eNXN~^VV*-@s0l;SDBL@1MJw#7t>8y8dOe2^bI^XdGC!h25zYo%cx z|5lj_ZVFulCd~ER5&IS6!L9;0WNdIB7*D8PMWPBV&_<|=U9T`>D%znX}7gTVN4(r}rZWJhf!9EjCnDSi-`jVeKcRM1-ljFL|P0zMz4U_mGXxg|k_qD?L> z-(za>4h}dma$vjU?YQ6VdkmQDZYgo>^q}pfj8R)QcJe2t920nMJzo84MGw1t<|z>z zeY*Ycn%u4NM_axoPkx2a-nl~4ZtxBz(+d5SR(56Xx>(FmUkIePl>cPAUY%!i&6QqH z4TTR^g@3du)C06;c6?=h$`KBrOn`I4h%HVAYs+O%*HdS;b@$Kx;zEu^44G5?tIwJK zc0XNP-$pa!HQGk*pm}!gpSMyprI(pH@bW{0UQM6=R&}qXw$k5F(QV_$vquP75<{R- z!~hx6K{3Gh0X$)Z0>&3S;oNI{%D@LPROB^62?4~U?1uYeb>hiJ%R7=6=#$$Cmxql# z`b1rlT2`~tfn{go4nM}5UH-lIyRjh6y2X-U5n!Fs?kO*`cbP*mn}=!D#YX(Wl)^@W zC}L872Sv*?Myjxh0$)TJWx>y&$D87}Ukxm49hYZ5#{{%!n?6P7V6FUkF=x+K$9H;n zZ=9ahc&-W0(*50r{58hnMW14YY@4RqHcLuH+I}=am6&}3QPlVk>t`Q&% z5u6^lL0HlyZ7Rs1WLyV!1`1v4AB$v+GfJ(gHd#G?VHmeGU&+3BYCW(t4^ieSI-j7+ zdhSjkA)8_^iwWnwKalF9Z`)`D3Evw&7KW?VCl^+)ijy{2eu*GRQ{E6v&87Hb%oVD; z25c}kVBbOUP)LGtFmUL0#mS1Vu<`Tt*e}PVaG+{dKR6y>#6480zi!5S*>2GrJe%?h zy?k2~$>g#$$Y3VZHs<)(i3CrV-uqHlo-Tq7x*_S<_jjw3LzPo{wcPE(rTu9RM5!kS zAK54?mQ9&`3%!g(bd~hpQ2WJ(M=Ub|FSQAptohABFfcz6V!4ZgU=?$%^RQ+rVGrDR z8~`PHXB&_`)n$bZX&L)sM`NqeCH3V~ON@O?ZvAYp|Ajl&qj*wn?ZF%VOCuAK!lL%v zo1!K1uKD%EM*4DjTR;C4%gYTZ=kO_LEYcD%)73A&;1zl4am_*On+IR}#~~0xzm;ZV zL3k00t_6yB3BY=*05VhviUQ+m2+u}#9)h%q4rr^>+3oV3&s+S?F?GU9Rkup}X<`qz zTW?YZi;6rpQf+ygtwObjyiV}Ei^!OKd$;H>CE+JTYuVQhuJqn;!#kHJ+HIL3kMA9c zD{QCh{gc%RyYhoBO-X>gfik<6yuFe$ahvuo+bFrWLU#QaU?qSM`m;D2yt7?$8_-I` zfMb_p1#TmRhXnTo3v$~2t3<}NR`*Ze%|BLLjl4bLRUunHyH~*YvaqNybc!#Zmora{ z$No&Nz6j941!-0>;cHmNC;ztF^3>F3PS{h&qh=T>G#+dMJJUtj{=_apC(qu zvgF38@xcKFl8K#*!#&pM%d1^)V;!%u=Qx$>N?jviq603wVCxROYeh5#pV zOTYnt=H7wcsQSU~nJ^|GZER`G%tPGD+C8N|VDa5a17SGpv@wBXujPhZuUzu+PoHPC z`^%P5^A`7y|DspoF`wQuyDsS6WU}_Mog;Zn@;qG2or!rAb{ zQQ$x%2f8clr79^L98CRu^du30rv|)R*%A0ai_sh;dGfPrNz?oN>PEAXFD7Jz$FQ*8 z6h?#uH#2N=X7ofY`{pLk*eSl(rIaK_Vm2|mKI;{qhOAwBPBay|nK&q^CFFcY5MvUMPAqI!p z!<(N>4BwB-y9BymfA@L4sc0&he=nuey?k+TuF$^uV5daAJMQvSyszest2z-|L0E*A z2j6^qhT(Hgf6)|+Ic$~PiKV^~u|5YK{0$5NLs0TTrseDc+96mB1h0Yv69^kY22%Wv z4#h%k@Cfj~;bg&^Dg77E7aLnEy&XGOq%Y1!ijwc!2aS`Ws39uKeWK;Pf7rT^*)78I zUlR7y3Pv{%@?QU(scv4O%32jy|7pcytF`G@n^BWp_xlERlONimGnTS7kJqIb+FqM$ zfqgw8d>?AbdBUg~nREm(;N;pcTRjGt9vvmj2(Ch@r=mhatOTr+0FTUr;OOmk|0(*+ z`0Mny(m$Ik_k-odi@ub+dGJ?~0|9Z#1u+M27Tj?co_ntLcE>+}A)oSYcQY5SILZ_jYY>xG}ksLYj2o#(YsX~f`+2r7M z&9*<6X@Z%RU?6x}24Y28R+YbIiFPv3{wTD6^|pn;MA5UIwPJcvTYe zg>2-whGc{J^;l`vgNN>%s*SEU1pW>*PP@4)H4o6Ab;>c-b(#zIzQud1_&osWn9Ph) z(3?nN!$Wz1BB8{%X7&e&ln(`>VVfvmH*4bz;aNBk=D_3#qTdN*#=Y?U5O9h4H$~;2 z)H`_kBp}IiL6wuGO-Vn7E$BhhXN&TTIh8}nrg5v@^;Ye-T)WR~2uwVL84}Dz@kc%m z=KB2|%=%io>M0bEY9on_zCi#%s6b^K!2g%)3$v=kaR_dp2rHaN>Y*DuEK-2QL1Bo3 zfTADlmWNwBz~P&*wfJBcBcvhCYLva1TQT@={Y7dMcGH}t!6>IB!%cqVcx%}A>FhSw zS)by0%;Q^RcfRunKM(U9x2$kem?N~5SlCt`IwnuNs^3@DCn!Lk0l_G_?hvn!UPdN{ zMnP#ZC}%JiRX>^qq$0p5iQEGv0|o&qoMXVMgq0IRG(Hd6AA~zGWZf>ty1Zji6G%VK z+32wB$|0?GXS_-2K1Z8fTw8EK#6_ab7+ny5Fk@sVLN7P{xLdT7F3s(xD`VRq4FK zvsY2vd*+LPSn`a(x+q4%P>eOk4%D3>4QewU1QzNA|C8nR`D;UEl5;ow@YgS{Gwh11R;#J=Pab2=xWpE57#Gi#Lg9ebcqppY0loU zOV@0pZ#zshsMEZ8w0*-S6wg>^;_1tFY?F&2LbVD^UiW{Z$d}l_mkx-5BtXzG7tGia z6c!cQ7SZu;Motw`jp~|8w0rR-Z+{DJS&>W685tS=^Y9mooO|Cj^=41^yYZDD5_w&u z;S&Yi@=Wqm&;E(Ot3+D=i#Qo>@HkG;+op!&$3g2-#h(^1!L$E8R72aw2e-JIaaS4` zmu9|lp7-HFdpsWI+1z(CQ>;qR+L$78%P1t}grW0`UG(;#r0Y$u2=B}=hRz0qdtO&G zOKyE2J1$S|xr#ScO7GRZ8!W+qVoJOsvH9=vIJI>@HC`_H+3S~(pXOn_UO+8NkXoUt z4@vppNRf$L=IYW^d&NTe}%0i?rSkL^YqegJ3 zy8Gjna^-MmN>YbOvD1dl6XegEnddvh<*85BF)JZR!c(mmTuk;@{WM7c{S}F3Ktw@3 z&i)U(;?!xktnq2AW^nWTko}8djZNI6`WasmFE>TrHwvLd(-#E6NnenKvhMri1SeKn zKV9~H9e8sXe7ll{l!ZWO6Yl(Li z-Y-X)sOlJM=njVy5RoTI4l(o;qBhPR%?j@v!q)uRJDQ;p(fwC#%?^ws3c6L0Eh3tO zkcxBtaKi*Vy6l7h;+D)y$3I}&etx;B8^o=2!>pOHMy07SN0x+8|D6kS&CzZf!|mOy zjNm=azv1UYs4C<6QrO?o8H{pRSo?&_-!>9T8$7785BH{ z=0Pm0;w%DqW-lo8odH;;(S~8UoPA9Q4D5j+5lMtnrwXv@pz^?U+nj#-zm$dK1Cm;yljp~+t2T!SogzTh%xK;VpjwMv%W!>FsI|$r#k0Y(v&dqw{L)3}YnxcZ$X|~y5DZ2MiG&=yd)-)! zUHzW@_eu7#(~j!QlctN}53Tlds!vq{nCfD)6e1~D&mw%fQ)9ZFIP)v3XOuD-L@TL% zcUd9IP$MnWCjum*@eLdakf9zal*6L}(_ z&%AP7IipV;Vd0vkubAdsuDjh98dSt+pCG&tx~}?RE^S0s{$dZql70!hok*~xXF+C# z5Qu`10+2u=9Kl9s$U{vDrrKg-hzL}3C_AMJK^LS43DM$cF>N^4G#mlMs~N;h1F1z^b24i$ ze~e7wvpZt4zRsy{#eWUEaM$}yB1*HKu6^L|rY29Hlw|d*ew-SV!69(Jw_jv3C`2JL{I=yb`J@; zn39eVpij+ z&d^V)rV8>C>u*?!4R@E2AREt;wJy({wgVem^Bt;Z@+?LxI|KdlBiULVzj^gm>h{2= zKV^JQhx7IZE)uOWiB4bjWaHA95O?01pfE-ej7%t!wr?KUX5@B}eHKt{-WMM{=xW~$ z^?2Io>z*p~gL&GxwRPF4?hA@HNc>rW}{69u7XuW`i95WayzJxs`eM~EOBbT9^~vHjoX&DU;^3ua6i zOc*~I8Z7>Pax57WwZZE6UR~30<#Bs~(&eSns`EvD-<;0(q5Z#W9bQ$tWfbZMk%Kzk zqj%ISeU0n!o*H#nknXxLiQGZa-Cy6k>ky^deey6%G42B|o!I>d_7S{NHZRkbi657m&X? z__%cXYRc4GmISrE04$h~;)X*XzdqngE;u+}Gx}Bk%C_LOXGzWE{LSJ0fO^d&$!)b! z#f74iPZuUqbT8NDcJV~kNcl^)pDhF^Byjz+A3JPqe)dpYbuPJfP=~cLSfqFG$^pUl z4t=i5(+r23++n zhGH`twPxM6V`E?pNw6FdC?&hspMPF8`cQHZm2*cY`k~>&te&+6+Tw!%dxC6ROv%Aq ztM~4Q%U2XADOc$$!@2Mqn87C-dvCZoQ3PbnJl)eS&Ncdqlg})8MqAjac202P;5L%9 z*f$xp4SsFoM~Q4pS2J5XDGtJRhS5Kp5++D*wcq)u%-qVWB)BGVeX+H^m+K2*6q#N~ zUb1Ku#7z@z-ZOpjrI@tJun>PkI&A;N5$DaJQVpe|@H4_^=DNlhW*_@~+1HeK-w=DO zR}*>I-hZgUmTV(G>cRHi;wEwOKoA=Nk<07?uYGYLRk)|i+`+|!uW0|c2Yj%i_|L3# zem3?PZ>$0)b@lGZ*wcIXHl9Y;4hD zZH)iR@zLWImBA&nUHnKk-8-(-)9W{B;{msZX)+*8r(H70_#`kF>F|T*@T!LC7MlF4 zs;rsL8(*ZrEi-O7l$lDWDK3BbXf^2d!QSu8-0JnrPQJw4rFa@@N0Og@0k>0XnHHTA zuaUcNm(R5Fy<{)9YFpg_m+vyJ-~r-`Xh7INtn=nzLfgeuHsM#9TrZr(`|vR zzPyFvAN8Vd4Ri2o(l$~`Tru}w2Y%v9I4)Q7`c*vE%?Lr#jhC4LE;_VAG|S?d>c3R) z>P|N?1=>x9n!d5@N5SnkSSSODNWg}IY3($wYDM0`vua3i&xOcBrARNTkeCJ=EV{tXDp1^{d)V#f*G@X1BaWTw=C9K1C}!@oXAEoJ9p+}eg8t) zg_}POYJ#PFtjWpVZpm;!WW-{D}AmC&Sa-8 z6GQ=ptOh!=VZ*{T{^Ucc82%%wiWB2RabSwo?yzL=xje*r-EOSUeDbTz({n$=>Ntnn z4{bvZ$bd}Wa18mh?#-iCsz-`?aK3c!(ug(!H&OO-S( z#X`G2de~DvE>b@CO&cWjT_nmc%<5|}cD1mI)8ezl%+3mxBbd^})el>}z44s;ArU zd)`#c$ooG0Xgc;s1YmK7skGFBxAt*YdjuM&xvnixdtTkR)ouIW*VpTRKWy2rFm*tW zcoNg@KJ!f?{MoZtTf+|g0!!5fm#ZGchzlAVdl%1i{ zT`74Q*J(1S>yv83N_k36#tS^Wi%SI(j|Kh|-3>Z=cxF;VtNi&wD=+BIxZ_M!)e)t= z08&Dgv`#KRLSh4mXV^q!@HNHwLgeiNX3LFBjf4e-udx^6rs8|cY`wDX!et+N5{_U;jCD1}-JJjN@KfG`i89-2L#fgb&{5+l) za!?+ZJ{g`(p1#%gq}{dE@pfoPyjX~N*6o7UHjh94n&Hz6Wt%&}UeB-UzDj!*7RI4N zOMeF+&$!gA=B_I*+w&bE>oU#&fJ`WUKVY(^)02Qn4~PzBn21KLw>bpq zFJ;5^fB&qL4_P{?h%R?~!CIp#?)erbxX3g4dUmPM?IV&5Y7!(Mc%4w4crWst(5&8JxTv zoI8T3f0sL2^?5+KThgu8uf{Ji!Fg{-v8!j9S;|0>9eScIY6F|vxE%Oe3P>=H2L~H- zZT=9f#2^R=Z=Fp*D;j3X@ZSJBDuVf)vuQfxf=(SqzG@}(b1bz%!n^C=^>>xOv{}E1 z`qDYps?*pU5@(_+%toXCZ@r>?CY5$T5dT-{c~S4Ddn+H9#bQ1DWrQrU1luWsLa8kt z30$|vQ}*P4%XEuZfsTGEJ|((Pq3JH3p~5@r?F$s%SwLQR=uSPP@b24>8z|^hn*aq} zse~k=C0#8l#l+osupz4^FGyrrg+6q@m2L{XIx|`OY`8S$J%~eF(=MX+aH6kqn|{>w zj)eInX0eE?nm!;TF58Z>klGuHtC=2mU48oWlTrkY3uReL_*QM>Gway7>kg|-EkG@@z!tj!veRB(CuaH~|h z`JxSTND<+(TT8P#cXQQ?&4g)EYfoyR2|u~;zN`l4uJ`(chG!@owJ;WH*|i6;$nPz(YL?{R|4m;p=Kw%7~5H(a!0!KyBKxr^2iU%Hw2eV~+!)HWF5FRc9rpcVd_3#UM z2{U28#)5d;wk zE*h9xXsC#upb8BEe!Eg3;MY-w>%hTCn~i>_?5yv2r*X$6?)Oh`M(mu)6GNi??(?;i z%gK<*__q5B&Zmoojq>#i^g9@)HvUWF<5^#S;cGjAQ3CE0MhEqy!pU$mEcK?}du}Dq z$Ge_?Ymjfp(bi>@<=?zRz1|2(A(57fdI)$90{&5t2?wq~o{?$00f9z0op4CVe#uL8 zhuYb+%gx}u5`{-+$CeMuH8noAH{3oZH}Nb&Q(@zGRcZIE`kHEvACHxWDVak`#6=au zJ*^ob>LITZMx8U({VP;s&%fs-Sx5K}wX?$Jq7ITo`95&VO3=Iak}FO~Y7WWblgF90 zRg=dp+|nCyhGtzjo}>35vTW%29t3&d-+Wjo_@|hjpj7z;ZE=HTy+}G+NRPc(KqpnV z#YX%n6%W0-R^H`ubv;Ob>*j;f3@x%;v981M+8PDmxHm zZXX^W-qIGoy=IZK%-vlZ)I#$sLodU%>DH$)^CD-Dh&LV6W|6kBB6)8<&Q@5B^5V^ux!%EC1H=;6&b-(SEaCH0})g{IrDH>C=1Y$d?nZ)r*M!voDRoxA!|D zN>50;xz8y=eF@)3$?)HL^n|~ezP8j$hB>2g2oBvpG(6bEO5{D1>Mx)sO&O6-VuLtY z0o!r4KaHroJ3K^g4`;)xH4d~^nCJMN2;f7&O-2s41@r?4S}Ob-W2aIXs^8Qgr`nO8-U}eY|ZI5Epq1|O;9cHuX>KCc55PsB) z`9z7nCM$33>wy4h}K^h>IQ1h|D1Kc>T}|2q|_^V2ASE=M$uhC z!bA#jW(e32aQT{GhrDJZ$OV$|6m4?e;CzHs2!K$7O_aK=WV(EMz__^F=z;smV08m_ z_PB&a4n6yi)2YocvJY!JDZGtUE^i|ocYoL3?Oqf)Hq}+1b#nSZ&ejk^q~reW{%VdK zR;)oz#%$q|NxD&1b2s++%8zJKBD*n|4WyDoBS1?=PS~!9(7Q)VLIPLRO9MO?KyTp! z9+-BNG$C#0m^P`gPXUW&nhB%F=GXx3ZxjhbtTPG+o4?T^8)ktSPqtCRug?3KFXRlC zr7L!l4$qE`K4xF(BHi9_V%{-(l7AXgD8nI@a&*5WoF>$3#8iW%O9iF^LG`r%1={rf z3geD>tMp*?IvNAhOOtNQ=+0SGThiwFZP+J$4tP`M_)J$gk9`o5{Oi zc#oIp6T0pRr03@M{6QIBnAsC8O@hgSqD z?sb-=R}P?2e8JSVbKX}v7!lx8KPauAPXt{x(b&%?4pWX+A7(g$NJQQIA{NC;fdd zxH;LCZy&h_buoAQ{_&a>y9yo~X8B_!j(aP_P;ma7O15#X^3NK2t*L}8g#23e&?Im+ z)RQf-E*dBENl?-s-(7>r#w^?2U%a`fE ze!TOiqJ2qJXuGZ*crzTKXm2C`3VV^>b7j41pQmhgv_%3*E_FeTWG8x_Aq)NnxXkTYCa!S_sDv7k(YDjqXt@YwF(eeDTET9g>Q-%FHp)EQ3QGF+obj z8?_26)eGCnN`}wlCL*}$EXJGzH!23-h>F>8&FSx#K&%CXk{A8JLvU&vWg{nbEs}XT z1g+L}J6CO3Bs_Fl?OV^#Cksk4(bQ*NagnN|-ihn6`CUlU#Lka&9jELjJqxL)+2@|< zONg`T2}!Uo1%dM0A43yEn=FzdlF+F_CIA>na>oOJFRXz~%L9yyMF6*j4+#-NN7_(s zcuy}Kr@`yfqwx#PfEy35r=G1Z3fZ&R89N*#9parho4YgXFsZJo5sNwo+{A2j4C;yA z!jcPr4va_wN}&5O4Mn8C%VlQI%a7c|Jz8XTw_WaiyPna8c;G50A6l}qu4CTs;@_lrrCfSP@s}KlkY#IraImcQQBikOT zDFGhRzw+&=T92wVRBa1)1#(1*U`ay?RFFiCv%)({g-jvmQLvF9M1XqD85w|N zvmwkeg)c$KD{PQlClbJrbbL`DT?~WSveSZa3;Z{tZl`17V=X}jyzyM+^2Z@{w3*(L zJtohjY`fNdcTNq8#M>06WoE3MC=6Q8Clx}{f6SdJ{wyLJ&3s~TU*tC*hW%kxF(StA ztKa^Cp$(FP_KH6IBm65~tR|w60FlK8dQd#@96owF0#}lkp|)(MPD-TsF9E$342uG% znorBWzd-AwXk{QM&Q1$&u6$dlg#X#16Y zm-j*YdE#ASTh3kB)>kr{dO$Uo5|cb3a}+Y_?oF{sX+VrnbM#27P@#h%h?5~&D2)zE zx8VT2EmE!Z@`_7Ld<4uU=Z6Cq5VI))wyP2i3BEmg^X#mJ`=D#LoXN;#qt$*K({b-D z+t8ybHv$TyHl3bdjTD29s_3>Kso$4}*X?$#v)+yP&V0C)q0pf$9BmgN#rQ9=YtNRYfjeYPx zMq_B&gjVb>)_~Dn`OL>A zr)T>DA)6BZeGE?z>g$-tnst{;`$GN%d2_dym4AJ_h<>#m{_pOgzg4**(V=|6t>}9` zuZBKbI3>O$%364tN}2-(l@JFF5Vq-JR{`d%0+PG_VDJYTgaFXKnAuGDW{ri)O@hxlxJJXs24HR$Hma1jhDcWjk$yK?yP zV{6rL?6q7ps2m$ej3iPjFYRp0uIro}?|u<98!x8^=8nf#dbV+A3trH|_?Q2@4EX?q(i)iD zKp=b=qS{D}Tu9-i zU7)=l_M_l@{yrJ^m3im24^363%i)cw#hKsqX6fV?J~kU|mXBvb%#?wI_LYgSnwsiU z8RmN4ThQ>+ZqL(*{W*{SD=@dYvMsOS~il3SqCZ6OWCP_3p_( zF^^X7EH58k@>;4f%+0>}?RjD{t)b!9^s<6Q!|C^X%EVW&M2={v>F%h80ecCbyzb-a zx=-|ZpB+&eA0LIY);s7@KF2xfh=hoMv@EcvQUH%5s}kI(2AuSIEJa9&#^WKsQlXy2 z2OYT(g|cvi*2^J@T?Z<6ysig44{uFu#(lk4)*y-3%Um{!N2W1;T_|nm%cH>I;JAjn zGr#psijoT#c^v98-fwhU&;2~-Sfy$Se@!Au0ZUrAt3Fr=xT%dirDlPC8G#vEhItk8YelCGV9*rTx|18v0B^|Mb-%Rz-0p0YWG!Qw*+ z=G9-pw5yU6`6uzG-`fUKN-MdR>D7CpJSXLOj=w%rFZ!xZTE^(s8C_I&o^5L5Z)bzN z5lw<1goJ1qU>*!SVE<--sPrmgwP~*tcPJr}VmUyD0kED0Wg9z9%Fh0Hkc)(5QPMt$ zgu#|q^QhjxN>bc5hAB|oL3?+i(b?=@%R4(;a(<_*c{XwXQlmW?`8jX*FMq9vAd~$0 zm1}(%B7`imn$)mXQPhLFk`GBjiX@1DI7mZ*^{Z~nJhS^M&zB@)#mEI~#SoRmkHYg_o;mBm-Z+=}yRgID+*sD;QGe2;_5gE;y}kK% z=eW-0OcbjL2)nn={Lm#auV!T*w$|U_ood?&t`9=hd*g+ZelxXz@=@AQB9xOzicEwl zLAn)syI9lh@&|u}QpgF!l&nPFfXgxAPUQ2&(p#xE@ z=s`~a!Arn(Yh(7&%kMNE@-v>-dhej6 z_c^sgO+Wco4sRmsO19Q!jd}z2A~oWEqq@K3dl(ENnY)jEIi<2jXitj57f3U9GF)i~ zqgCij5_zy8M?PV8rB|D2lnkXg#t z2js&7UH{@AF&pJq=Jw&eHON29GaITPk69x735t%NrR()|GlJpr$yL&_WbDRG}(i`FCO3X z&Bjev#_8uj;#oDN*$rpKe*`#T&NSK??k#YV1Pzoue*9PBIpfCP6J2*Zi)7jB()Ex0 zTU2(_nSOe;uB!W~Tv+nku7#lj2@x8J5K$wxkK!NTU4u9#oQNNK0od?>|9)f&I@+S> za5TLLP67MfBr9Zk*ArDuad~5>oECY(e+%Q0lTvqFfU1>??T8K~9UBlb{{*pl;6IWn z&TKT2NPqkj$t&OzVPX>V{cZbuapIw)adcl!2G{ih$~1^7)*?bqd5TK|IL+Ys1HW0o zIS1`0s~k*lAQHk@;sLrRFC~rvehRGsXo9m%WPZngB`zq?eRF)XNLY#f;4w>DL~!j3 zrX_Y0$y!#vKplg8iMfp%J@453uwk7yw=%eP^1r67Ue2m863YaLxJvpE3&yVZ#+H2LKKNkcz}Ppn?zdJop*-=y$ksNrY^n zd9Dq8R?jra=@wjIHr3LV<3&bFveMunV96-05lEXjLiieM-d1PmT-OlK*)99| ziMidvCsTP3jt(Q0erPy_@=lZ!l6{Lg*@+PUN;L3Wz3l#X3jP!NT>W3)7w}L*UztLL z7vcY{b?hZ`s)pf5G4T+_I^?mf~6k?gUCPi<~6F)!=e<}`b&dLOc8huHic-l!Y6 zo-X7Ps^CrS9hSR%SXW+0))f6W;c*Sg%WCq=vZ*Yp$(+8x`IZJ-)r-#j!>q0iCt%J8 zj;{=e!sGu}NdLM2;#L99rVbuJCeEcDgMZM??0<>LzoL+90O#`TnkHTl?K1pgwnr(x z8L;PN-%rp*>#M2}RpuNo7E`e;KU}-Cw3PZ9TL)X5LC<9ievp?=(DjU1HJh|0o)XVuaN${Alm)!64EWpK|-y*!vq2~)K7}HVC`Ps(`YUlJ{jvPZK|CKw4LADo_+K`3q z()tCQEqYVoWy5QU?#BJhSJ6ANdQ`v#l66pDq$5PwPz5NParHO$Qt+LdUOMAJnd97U zR}Tt<1-WFqY3Q3THf2hKvGP7xGL0jG(9mCu`c7lC!fbD)Vp%zK3NGSI`ht28zS7>+ z@0or&-tJ$a3a$9rKtDPtBS`F^X;|jR!kKg2DgeIYKG+)$5suX3d=d^{ z{qHK7-?9&^WS5t;V-aLbl zIM@aHh~Ppa*}z+b^Y#1h8X<)@nzein+3)b(n6^&GgfKaI_MwDsiCeUia2L1DjAX6e zz5iW$w`Tv#MmirG;C3S+iLv8b-7Rv<4H))Hr|g94@~9vatsHN8IV-n6DzV z?aXdmVA5_bz%($H?!eTZ>xh{WEdc+}0g9|2jvx=P$ODMC=vi@r}#j zNq1h!+aTuCbv}{Yg%`Xtay$u3AR`Yf-!$k1Ct6(sy^Lk_XWS>~C;-F>59*^6G`h}s z+`2n>{yb)={i)H@p(k^x>mi-LxU}I=BE8nn** zU>xcM?=8I>=%O45>>iybdk8=`ZYNoPPd#;9fNkb%DZk+B7kiOr##E zQOFx%$agan4OFJN^nYwbRlNUhB+%{6{Gvi*Gp+k}zZDP(5>L_ls<3{Fch zJ&S_jz{xoQ^Z)L}-OZy*xBDc0#f$|#FAs_CM|T9j5`z77lpu!o zNAsSiHW~N{P5#zisHvg&;;b+Cj)ka9SjCZ!$5HE{F$y#2V!-cu!(vzF!_Q%lbLIWE zT!;I9GY)HWPX!7tWxlJbSbk@S1#6EJFdx&v%l3{69FQ<{D-5@ZM*R=}L-YSF33B=< z?Koc}>C4N~06OKCC`l!+i89nP3T@7io5LU4$4jT=UIQS6lM>e zg>FOK1 z_3%X4NI6L(DPNmMjKJx3+wtiHlH+T>Jr_`AvtcPSRriaOQg8fsd-cWMCm$_3tE&qaD3ljMK`yq*1IsDMX^DNUyhzyC-bz(2+ z=)tK5x~(#c)(J(tcz-jSnHM=m&QX4ea;`!aEcpvc*Xfd`wcCeho>rcu> z;LCbB6Cn9Wlk3;>%%6Du6D!_#IBjN8Laak1J${cL{yXNXs|XeIy2cDNIUtzi`pex_ z^a^XA4Ok0UNmw|z7ySf)v1N#RO*5gKmtAD}0&1|J)0KL3kX9I8Gf~6m9sw!Ks=Vg? zx98^Hs6y{VDr#JYH9kszCWoFuLDs@`|B!^^#VhM~6f}FU4>5F87Y^LD3Ol~~z(1Dc zB|eBFxrze4L}0OtYw?0q5~4gqh^30{8iZ>qVlP2}aSmWh z{#BF{;@MT6XPi_KI>c48D*_vlABUg(vDN?MOzQ|?XE3vPJ=`Ej|$xubQ|lSF(c%{l6;(bcX^eheJIP_%y)AtUdzn)j!nL~ z1A>NK>sInVfxA&}y6mQ{xp!OcB-In-oW>(b_&J%fb1v`qd&4&T4snf0r)fvAO? zkIuZX6vIpIps7GD*OCIkh68jl+c}`@4iS_ueUCc{=m(O3uJ;D$b3;*+)R8f71pdT5 zZ$22@J(6yLQ&1lL!q>jWA-Mkd7;pH#)%De^?7R{5j5Pi2>bNrnG;5roylkaRRyKut z_j)3p-|zF4ahW@BwOc-%wPpElC}FJmso~$^@|?DF@P5jTPktS(0N6s7{Qa#eaQ5CE z0Kecr7ea{pw!IFx_&=br>;~k#r)@MLi<>5QSQGk4%t8jzzR<`AGYTo`sLHj^?;9Ru zG>u^in_QHpco@<@IL(A=3D{qn==TH$9#m(L$C_L0#Em;Bn`p*f4eH|2x6y&6UG*3x zX7a9=QfW}^ps>&6O5zN@)Egz1)32A-qt#GiGB*k~x-G5&*EDp^um2Ld8~?Uy5a4(uGx0+0tt^7pKL_J~mk z&lX*Q42NJFQFwX)O2>nnaeR3Tpxa6>@gvzMr)Y~0ME9N)glOG-R?!T1pc-_IAFBFDA0aYX4W-V(F~at8*tobC|l@)3u# z@taZ8@pl=ru{fRIEKW!jmn$f9uqC=U&L~7vGgr)8=A4uQ4^U zTxrpDR_fA5rI^wZ7ZG68HytO9^Ro39QZjHL5wq?1OX{0FkE1OjE@f%zqDe6pKqS*2j3jL zlNoZt?O7j#eC4s7bM7{>Q|B&n>9ylqSNCiQT`|A2|D>j!OD2|BS0ieFDJur@0>8nbd~OXcoyX~lou=< zALWnG!$)YFYQyJ&ocI0(HgpS9r3|si$|EME^cLDEs2?JGWouSTZ}K zX}w$SRmH-T8^OFLpZnlbaN3HLtl9V}O9SGIz=#sDp*NclJ}p@UBF7Mjy9D6H@OH&S zVDUzc-=S^;NKv9Tz&6o+-|T63@OX;f&&l)66_f1CrzVL)q198DGqq*Evg5{2*PeO1 zlpAklcZ7JkJ#U*CIDVpjNm;a%DQpvb^R5^E(9`OMd(-G9JiQjB#zMrW4d|hW5CO^* zN+gBp>*)c#j8+@AvpX1M>DhcV6r=?ipzVtFrhEp()o^z8x3ey*3%jjBRo?GjSs$#d zbG2-2B3M$|KF{r8>OLLIdoKG*>8tSTvI{kych z`shnMq`zuE(t2RG0xZ|` zxM(?|!a)WxxDEaU1q@yHRtHv^j5o3jH!m@%VxKIHVX_%)`M;z(AmsLkDu`aHs)|zD(1$0Nt0a70-)Hi%eoRJKkogz$LTlF3$x5>&GL6 zpC10dlCC?D%C`-_=h!5B6Cp`*shG09OHHI$r02q^9Dc{qD7Kq-JxB&Em7tOb;@IU#jo?E0NJtVL16kkN~xB+ii zs}K7`GJF@)Di=rhf6vCxxQ&gBbC<;r(DJ9E%;8U;(q$#y8587|D`A6ki@m?udYaqr z(xB}`pxj3BU@ryyYl!Qg2pZ^XNVBkQ>Zg}auqE}?1s*-`KoR3&9Ja0bu=zau-1>=a zNtUtXUmLTG3x|{!$sKAjm0jC{MDLqf(s>mpRIBU^d9`I4tqBmGy1&bPgwM=s z>z`@HNjB$uC(BXK?OWY-&B|fk2M92&)x$Byc9KL0 za1by-a2znnTZyzhk+1+QE31AZw&P8Yf{49dS61UrbkE$}Rw+^CTU-CYzSW6)RB>+6 zo|44me!aK;!rw>UH?R5v3*W&odoVFeTLwM181(XdlT38SqkXi4{c*Y?Aq-238!0)t z5@u&71tc`AI{%FzkT}BfQ~tl!i~+ztw{<=6DzNb1uxhF9gr7T2Xb-JD$F(+0rF}>d z-%Qrp_@%lrdPf$m#fsh)yK1XT{#ahMj*(!0qkp)Hc^4EB&~bh6h^2&@O%eaCl~?eo zfmwQm5RE20B^fr(>EhR;?a)Q4paU+#kvWb&w!t($0;MC?a}6R*!xj`^rU7@;;B=C& zwmJ&VnhOl!8$cfHc6WWdH0;t%Ui+D#9SA z!~a^;p`8T7$-s+(@m+=mxuJ>M^~?EPYT|?i_p%8{E+;PUWM(A!nM{OeKlz!_hnbn0 z@~UYz`7`a0kq$OVBaR(!E>m2&A;dpeAMpc0&eLo94v3c4iENBud3c17GssTo2#}$H z0wIL>w?&>B_G2iSU0&kj^4TQ;!@cR;YvH>Rwx2V;JYrz@t--cw8T!E-8mm&D?=84S zEpifM6alfIYM_^PNDE&T*WO_qI-@dqCq|rV(1%5llay#LpNGW-2ZKkvJU^i41z+Nl zvxOf(#mg;U^F@DBx)E(H)60_6d6PYnT{B3Jwo7um$2YR85vyscIIfv;QQMkJjLWE4nM3tD{{A5Ze z%#K7sxrEZb{yzyzda?>ga*+!DO{hRA)PzvpSW@NpT?Q|@;i~U??4yD2USE{I`u#VZ zf6=tkr=e*y{OhQQw<^xOD;Day0AUPB69|ixxfyMoPtQcEGX+)gDLm1OsHH)156OzxQc6c2ye z?crqmE8`@R4o|~7m|onk+^pe13_5{|amd##I&R2tGZjNWqbdmrI`z<;FlCWd(KmFkCiRNXIMtGqx0z9?#) zhvyZWA(9Bj>`Oft*$gxkek}=Ddg19RHu7K_qu6UeoEy0BEF5)QvG$c4pG5tAIER$x ztFyh~xiPCan5d9yzU&(-ZQR%Vb3(G-mPVJYQZrN&Hhe~Lx24_}^T)5L%qTqE?X49q zNdXQf<;Ev8r_;S4je$AXojXH52r>rg3Pn~T*~Fm74?c&HzFVdPu?(>150rM&m9aVn zAf$j}fF>exzTmx0=6Mw3Sl$#;(Ej7EAbcbHSNlPqIT^j=a2>17;+r}iHg;i>TB%z& z#%fyn^P+XMYlK5yqK!;{$DMR@hA4_I_BN;HfCWi8-6ILt=a1<5-e~!!#|B+#n??UQ zzcAmcOGQK1amuwFcEcUyl>Ym#q+~r!4ia&4PCIcKhj4U6!A}rfcp-=awpx#k_E~tK zVFF?nA|sZyrx)8=I0-$U8T;>+p52wiPhNO6i4kD@{oFW@e_vC%`J{CR>C+o@w^&N# zH^Hly6IDL8zmiNQQ+j?MREMWVM;>lu#JX$yvyI>D>6GMsq^8KS7;M4Zx7XX z&@Pe*s?{C@DwC>8i#oF$ntZci&)0_4SiaoCt#fzXYWiC3<;CQ(1o%?~t-17PCjea0CLyy$+>sL|JdEp!V93~1%}q?psY?&=?T~{5!9idzOc1a`u&%cS{xzRUkYkuF-T=-J-2W0sylTDM^JI=tDEpa! zqd~rQUedmnm-&hkTbbP=t0P&TJzU-Xdd{gwuCe3 zY-6LZ4>jDO`(q_sJ>G3A9I>4+#l^PC$nwt@HUcN;{4__{8pkmIV$0wWM5+W#rQqc5 zx7S*2Uv=klP%VmBEp}T*Ccbt}X?(g@il?twUH0wH5nc`l%D~fH$@!t*2zua)VJp?R zCk>CvRo(1q2vN>yfWnzk>YE5Q3ugaY=}g=C0R^2R6CaO!KkHEv-UuDy4f=*l&BDp* z_2LOuDhZI<$X1KtXo$?W4s=(m;9$Uh9!RFR7uc6bSfqWwJ@_iXGE&%ZL%>O})k&9c zZx2UCXiywB%72qXw0OV%=HlIda_niKjWj}G5vKi z?#Bl=VQbxpIG$0$p^>j$-wbWmukkNjb7wfOjX7`ufM^P-E4aE>U`O%M8`;cLY`=$r zpbF9l5X5zR4mc{4#3RhTEuLX^J;{1yy>fG&Pf>iB`NCQ&P*Za_@*+cJ>_xK0{ORJy zYm6V9?pLDt?B`fr>kns(FQ+#1&-|D_*{=t?JJ86RZxn4_B53y0uD-sW15^WtSewsM zZ72S*X=GAxknU>aNf8L>phFNEhWO~;!x8_k$iLb`2dr)_r$577-oMc}>S9TV{u*1l z6f+{f(Cv+Xs<~b;WQ-@^VSBdgS!vq!+qE{hY82ruRcFO>=(_%c+vVx1C+qY5md?cs z$@mNHAPV_liyrAkHa6KjzcSjkx^2PF2P%fSPtm=CFE1D)dl4eNkkbeRrNV<_sI|bX zXQO0Hw%@RsfaT$y+b|Oi?AuF!<5kA>@Y-zi3N1gLK#C~UfTXK>x6F6K$Kwv{A;+?f z!-N@!_f6d;o#f~Q{;P-Ms@EPF7}o=ta#gHc6vE(^#NwypZu?p9 zOnW`#>4vW1@rUb;jrX_Le@^fGTr^s_U+lHmaL0pgYh^^*rssH;`My<$(v3Oaqg^`^ z)ThpKOV8UO6Pv3iU#c^&+mm?M5MSkuy}yL)5e&Gt_&5Ei-*Br!b$Ac0$%zX|UcHTE z%LdZnz%Lze?15NP*xkj#E0czVIJ=h9lSy@h_LbLO-e=dOGOoy0R0iv1EzUa! z-MzHPa(!a|`e4NN?9M{cRwpdQF4=TzS_P)!(jmp86iqloa0uHe4&MkRbqaNA07Q=f z=DcVG1Ps$PmC&l!ZxUZSnWaHp=;OT&(*X-nA^>ezkSABnzDY=^2n-P(`|@a>Jw*=3 z^ct4vU5#pv$OSxHZP^zsJ{ha@_T_jHY?Yyt7IE>?Qj-bUKAX6#*u-?2;+w4Nba=xC zUQgzuP@q1N`62UF`jySSXY#@!zoFvl_r;9)Ja;_w0rg1{@g=m-SQ)rL$YZ6%!9EtC ziLgaO@^ob)mVq+0nbYf-@0gR!E$6GWRfAx$&+z|>TiOm)Lv;5$3yxW{EUg~1zAhNp z7BX1KDDFQ8>sPJQ+V9I}G>T<{IkqnMWh#;b;+V#}%xu#E9SXt* zQdnd8A_$XTae>?Azg@r2Y%Cndz|5QWR?u|D^2Sz)D=Zy1=T8vU2pkkoi+TlybI!mSOYg!7aZ>7>nk`L*xh=^TRx? zv5~PEYn}t7&G8P>7Okv$>i7hQb(08mh*|!AVBKz(5*_)(J{#z!w+#ck-MW(B{=4A1 z+vTi~Aw~JK)9LY7X`i*N8DApJ*f1+bn}8NWx1I2uz_>s+@4~mC0EL$zsPK6Ipj^ZW zM55R^#%e%{DvUh;>N;^z+vDpWUf9QDqUT&-uGkNmu+qVlV@3K=(z1EHr6(3Le zLv5DiU2oRFdCOhx$J2nw`YVAYi*3@mV@=S=${j(Z&-P;E5Kt;`3x_CKyRHA#5G}Y% zU?Nc@%u5x$D|5Bj+qxdoGI=ji?vv0H#t$;?rkccJyq#OJX2DrBOFQpByn2Phe@UTs zn>6P?x|bWBgzBc(cQ;wbHn} zAeZGL^P}5|F1=$QB}7nEDhC_fZfbYzjy0@I)Fy$XjFcnKa+T`0DTK@l_CNFku=V1rVeWyuBN`Z&zwRD&OV-$U;56Wyc;UY+{#{l_}-n0iT=@2E*)3;BS+K6i4)o-=U>l$l661(yT{*Iy3AMu`@ zj9Z=udGv918h02PCp-74^IO7pQ3TiXq)yuX&)hLGXN^v~VH(0Pm@#!^M7G!~p$Y!u z9W{CJo&)nSYGmqEPpL-6&ywY%zXr77Pn=0|J-5#a^1eGE{*l}v9Q*KpgFra?GT;K# zLSUT_7NX~Qe zame^yB%ep+UD-4b^|YZFCixlsBG$7`Pu*|0;?spC8Y9MJ`i&chRdYM?RySHyJm&qP zcEY6CzW1dG=KoZ*EOpIdH%7%RX6|Ni$=q#_p?U)_q%%YyVYnf1xb_2s(Dm{im^G<^ zIf(ummyIXJkE|9#QIo{OHio<%dIPXrhO95C&07Z zkH1OM*<4Sk%5bW>J3bV-pPxWCr(&KnDXq57MOMV+FEwo;P5sR~u4kkVDP+Mouw75~ zs(RYi#9qYc>6jk^o0n6uxkpJLWNsxu<> zqFV~$l^0MZO^(SwweGkiOc+QwA6%3D-gwQv>JJxV$K^$f(6Pm+#KJO$#Gc@rb=ls# z@N9MM)>9M-?;H*8vxG8%x8*e-T;D&!nUmJFcDwc?6o*51D}FrOFxSqTX`2=wa7DWi z@W?XL_t*{7Mj}zLatqdcvb7FSK-8SAi>YNh;AK1L6C6g371A+imihy^$+SGFHs8k= z_sBkXrlshdZM~}ZTkA-QWd7wZO8fQ3VLEMR>SWneQ>ALOYZRf2yt)mpdKj~dU7luu z;{DkDvy7(POS+pcs-PSG@*2910am?~YXtlr`N@&ZsvmdY*NfM3>t>(UX(5P$l zY#DK#WP}HLq&$nyYk0qzuxFkt@11qKG|XH5VX>9K!Y(9k_t$Cx@wI&7zuB5M%6T8E zn~k~cC_f)&#GOg!>e%_b2_t|lfRb<+<~5JRz6c1Y&KWr1&Vj2i1YUuYDfUKi|p zyI)_`$Q0Aax+SY|f!cNW@_A>%nl9ES!Ou%8Ko@DCh`u%V)w7*AUe3a~HcNJ;^C-m2y*GB!p<%!+wmizf zqDX`Ex_u1B*k*qA6%chqex!G>4MQp#YsvIY%% zT552JRWxhqvyrtV%Tkx_ON-9(F{;*n?!~yBxX$(nNpYysMiS3hB4b-kpTj%*Q@Qh> zLyFYM3z7Bw8HM+&pcqI{KtPvI zMk-Jz7@U6Sz-640k?kG^Ueob=aB(Z^i6MCOpZ?`x3B($bhVDWvkT$)>LZj} zD9O;-;7*Zr5IxBWT{ICy%6W?Iv+InUq&Jhg$U`a~t{0ALfo5Ih7v!P~N#I)hg@xKW14Jo=h%EZrEv9H2kG+*CUqG&UW0yWwRs-FmEPvYE__4oAK3 z^FhB#ri1qx~VeshYPa9EGTC`rUe#>5vXMWm>Z{O(8cTi!|Iad;yk8NWIp_W z!(VicT#PgZ@GW?-^W-W#nk0Y`dzUWCh6rQiAPA1m6aM(8A2YoevfVa`qxzeb$hp7u zjAPTclz*vdKg(-cewLaFFY&Lm8NcmUZOM%4>=hR?2UyNkkQwH}e8YEb>?bPYi6<1g zAe~#Gx)p^WYDwM$cNUOZDqaHw1Xv`1<#b>&NRZVBfzAq;A=J1Tyv281X<=RX_SoMT z<<$9Wzoef@?iw0rL?Afi<-IsMgcwl`y|=IOJ$U^=Aor-#gP9_;TZ9U}3vGk2n?V*N ztAC;ohISFt8*&T@-vg}QGvf(tyeS0u%FNp4oCw_d(ywXNQctM7?oS14m9uP{t6c;sQnF7F;-arvE$!y<#AkaDS_q`zuZNz!QVWe& zklVfDTL7C9RhHt;vl`PL?1b@23+6*JR3Ccy^R$j%K%H3ohOwo-vlWzWIGpJ{vYeWFIpfQn z>&-OXNYYj5J*d1fSCGkIZI4Tah?11Qj)S%Xl83;+yw3$hgKjM455gJw%Ao_ng)8=l zAxaMu3Hdx7u3}6`|IGJ=7xR9XH)$w)6SV{NK17t6&yY;xk83E?&VKXRu3wTetQ1Jz zl;~cDm?aK3RH2Zc>~D}q&hIWTUL8L#6NawllhKN8lR}iCXb^)Ik3D8J!f>qA=I0~N z+T}p?ip^^Dt+~YA@yfcvSIi`hO<8NR97i^&r3&6(o+Psd9Qq~AE0&A+E9~tvh=paT z%(*%a8(7Ulr2qE1CNYyPdn*@i%T$$Cb3yhkl7&H3Kf-%CEUgQ+*iH>kGWxRqno-%A z&4E#DjfMasVPX0;DQ!B5w}f+wk*!? z;>P<}U@+y9OfDJ5FDJimki-ho>$l^#9wgSUO|0cn|0i*?gc2k9xjg3GIp$t|tSdKJ z^677sdy!N7&F>2~tZhe{6uX%T&%8tNcX#K3`;A~Q-H|yUPVX?HA0%VvLL_tS3F)^c|WnYWAk-<;lQS%^H70JkYAi%kWW#`kk8ZJ=boUr5Z^Pg`yxX3ZJvsX@I7^O Kd13GA1^o|WjTtQf literal 0 HcmV?d00001 diff --git a/share/scripts/ttml-renderer.js b/share/scripts/ttml-renderer.js new file mode 100644 index 0000000..816968a --- /dev/null +++ b/share/scripts/ttml-renderer.js @@ -0,0 +1,277 @@ +/* JavaScript Renderer for TTML tracks + Generates GPAC Scene Graph (SVG) to display TTML docs + +The renderer is currently extremely basic: +- only displays text samples in a single view area +- no backcolor, no highlight, limited font support +- no image attachments + +The SVG scenegraph uses uDOM for scripting + +The native code will look for the following functions: + +function on_ttml_sample(ttmldoc, scene_time, pck_dur) + @ttmldoc: DOM document representing the TTML doc + @scene_time: scene time in milliseconds as int + @pck_dur: packet duration in seconds as double, if known, or 0 otherwise + + The function may return a positive value in milliseconds indicating to call back the on_ttml_clock in the given amount of time (for animations and text off toggle) +If the return value is <=0, on_ttml_clock will not be called + +The ttmldoc may be kept alive in the script for later usage (it is not destroyed by the native code after the on_ttml_sample function is called). + +A TTML sample will contain a valid TTML document containing typically a single TTML

+ +function on_ttml_clock(scene_time) + @scene_time: scene time in milliseconds as int + +The function may return a positive value in milliseconds indicating to call back the on_ttml_clock in the given amount of time (for animations and text off toggle) +If the return value is <=0, on_ttml_clock will not be called + */ + +var ttml_dbg = false; +var ttml_view_area = false; + +function ttml_dbg_msg(msg) { + if (ttml_dbg) { + alert(msg); + } +} + +var rect = document.documentElement.getRectPresentationTrait("viewBox"); +var width = rect.width; +var height = rect.height; + +ttml_dbg_msg("TTML Rendered Loaded size: " + width + "x" + height); + +//filter-assignable variables - if changing names, also do it in filters/dec_ttml.c +var xOffset = 5; +var yOffset = 5; +var fontSize = 20; +var textColor = "white"; +var fontFamily = "SANS"; +var lineSpaceFactor = 1.0; +var v_align = 0; +//end of filter-assignable variables + +//for now only one +var display_area = document.createElement("g"); +document.documentElement.appendChild(display_area); + +function create_text_area(settings) { + var t = document.createElement("textArea"); + t.setAttribute("x", xOffset + settings.x_offset); + t.setAttribute("y", yOffset); + var w, h; + if (settings.align_top==2) { + t.setAttribute("display-align", "before"); + h=height - 2*yOffset; + } else if (settings.align_top==1) { + t.setAttribute("display-align", "center"); + h=height - 2*yOffset; + } else { + t.setAttribute("display-align", "after"); + h = height - 2*yOffset - settings.linePosition; + } + w = settings.size - 2*(xOffset + settings.x_offset); + t.setAttribute("height", h); + t.setAttribute("width", w); + ttml_dbg_msg("Creating textArea size " + w + " x " + h + " - xoffset: " + xOffset); + + t.setAttribute("fill", textColor); + + if (height>=720) { + fontSize = height * 0.05; + } + + t.setAttribute("font-size", fontSize); + t.setAttribute("font-family", fontFamily); + t.setAttribute("text-align", settings.align); + t.setAttribute("line-increment", lineSpaceFactor*fontSize); + + if (ttml_view_area) { + var r = document.createElement("rect"); + r.setAttribute("x", xOffset+settings.x_offset); + r.setAttribute("y", yOffset); + r.setAttribute("fill", "cyan"); + r.setAttribute("width", w); + r.setAttribute("height", h); + display_area.appendChild(r); + } + + display_area.appendChild(t); + ttml_dbg_msg("textArea created: "+t); + return t; +} + +function add_text_span(parent, text, styles) { + var i; + var span = document.createElement('tspan'); + + if (styles != undefined) { + if (styles.italic) span.setAttribute("font-style", "italic"); + if (styles.underlined) span.setAttribute("text-decoration", "underline"); + if (styles.bold) span.setAttribute("font-weight", "bold"); + } + span.textContent = text; + parent.appendChild(span); + ttml_dbg_msg("tspan created "+span + " in " + parent.tagName); + return span; +} + +function parse_sample_settings(ttmldoc, tsample, reg_id) +{ + ttml_dbg_msg("Parsing sample settings"); + var obj = {}; + obj.linePosition = lineSpaceFactor*fontSize; + obj.align_top = v_align; + obj.x_offset = 0; + obj.size = 100; + obj.size *= width/100; + + obj.align = "center"; + + //todo, parse style and region attributes... +/* + if (reg_id==="") + return obj; + + var region = ttmldoc.getElementById(reg_id); + if (region==null) { + ttml_dbg_msg("region not found: "+reg_id); + return obj; + } + var styles = region.getElementsByTagName("style"); + if (styles.length==0) { + ttml_dbg_msg("region found but no styles"); + return obj; + } + var style = styles.item(0); + var offset = style.getAttribute('offset'); + var extend = style.getAttribute('extent'); + var disp_align = style.getAttribute('displayAlign'); +*/ + + return obj; +} + +function parse_time(timestamp) +{ + var result = 0; + if (timestamp.indexOf(':')>0) { + var nums = timestamp.split(':'); + result = parseInt(nums[0]) * 3600; + result += parseInt(nums[1]) * 60; + result*= 1000; + result += 1000 * Number.parseFloat(nums[2]); + } + else if (timestamp.indexOf('s')>0) { + result = 1000 * Number.parseFloat(timestamp); + } + return result; +} + +function push_ttml_text_node(textArea, node, styles) +{ + var children = node.childNodes; + ttml_dbg_msg("child " + node.nodeName + " children " + children.length); + + for (var i=0; i1) { + alert("TTML sample has " + samples.length + " defined, only the last sample will be displayed"); + } + ttml_dbg_msg(samples.length + " text samples"); + var body_region = ""; + if (body.length) { + var b = body.item(0); + body_region = b.getAttribute('region'); + } + for (var i=0; i scene_time) return sample_end - scene_time; + return 0; +} + +function on_ttml_clock(scene_time) +{ + if (!sample_end) return 0; + + if (sample_end > scene_time) { + //ttml_dbg_msg("not over yet: " + scene_time + " vs last end " + sample_end); + return sample_end - scene_time; + } + ttml_dbg_msg("sample is over (end: " + sample_end + " - now: " + scene_time + ")" ); + display_area.textContent = ''; + sample_end = 0; + return 0; +} diff --git a/gui/webvtt-renderer.js b/share/scripts/webvtt-renderer.js similarity index 95% rename from gui/webvtt-renderer.js rename to share/scripts/webvtt-renderer.js index d433ec0..8e0f32d 100644 --- a/gui/webvtt-renderer.js +++ b/share/scripts/webvtt-renderer.js @@ -7,12 +7,16 @@ var DEBUG = false; var rect = document.documentElement.getRectPresentationTrait("viewBox"); var width = rect.width; var height = rect.height; + +//filter-assignable variables - if changing names, also do it in filters/dec_webvtt.c var xOffset = 5; var yOffset = 5; var fontSize = 20; var textColor = "white"; var fontFamily = "SANS"; -var lineIncrement = fontSize; +var lineSpaceFactor = 1.0; +//end of filter-assignable variables + var nbCues = 0; var cueArea = document.createElement("g"); @@ -45,7 +49,7 @@ function createTextArea(settingsObj) { t.setAttribute("font-size", fontSize); t.setAttribute("font-family", fontFamily); t.setAttribute("text-align", settingsObj.align); - t.setAttribute("line-increment", lineIncrement); + t.setAttribute("line-increment", lineSpaceFactor*fontSize); cueArea.appendChild(t); reportMessage("textArea created: "+t); return t; @@ -101,14 +105,14 @@ function parseCueSettings(cueSettings){ if (compositeCueSettings.line.match(/\%/)) { obj.linePosition = parseFloat(compositeCueSettings.line.replace(/\%/ig,"")); if (isNaN(obj.linePosition)) { - obj.linePosition = nbCues*lineIncrement; + obj.linePosition = nbCues*lineSpaceFactor*fontSize; } else { obj.linePosition *= height/100; } } else { obj.linePosition = parseFloat(compositeCueSettings.line)*lineIncrement; if (isNaN(obj.linePosition)) { - obj.linePosition = nbCues*lineIncrement; + obj.linePosition = nbCues*lineSpaceFactor*fontSize; } else { if (obj.linePosition > 0) { obj.fromTop = true; @@ -119,7 +123,7 @@ function parseCueSettings(cueSettings){ } } } else { - obj.linePosition = nbCues*lineIncrement; + obj.linePosition = nbCues*lineSpaceFactor*fontSize; } reportMessage("linePosition: "+obj.linePosition); if (compositeCueSettings.position !== undefined) { diff --git a/shaders/fragment.glsl b/share/shaders/fragment.glsl similarity index 82% rename from shaders/fragment.glsl rename to share/shaders/fragment.glsl index 0d5a4c7..22d6312 100644 --- a/shaders/fragment.glsl +++ b/share/shaders/fragment.glsl @@ -97,32 +97,6 @@ uniform float gfKeyLow; uniform float gfKeyHigh; uniform bool hasColorKey; -#ifdef GF_GL_HAS_TEXTURE - -uniform int gfNumTextures; - -//Texture samplers -#if defined(GF_GL_IS_YUV) -uniform int yuvPixelFormat; -uniform sampler2D y_plane; -uniform sampler2D u_plane; -uniform sampler2D v_plane; -#elif defined(GF_GL_IS_ExternalOES) -uniform samplerExternalOES imgOES; -#else -uniform sampler2D img; -#endif - -//Texture other -uniform float alpha; -const vec3 offset = vec3(-0.0625, -0.5, -0.5); -const vec3 R_mul = vec3(1.164, 0.000, 1.596); -const vec3 G_mul = vec3(1.164, -0.391, -0.813); -const vec3 B_mul = vec3(1.164, 2.018, 0.000); - -#endif - - //Varyings #ifdef GF_GL_HAS_LIGHT varying vec3 m_normal; @@ -297,57 +271,32 @@ void main() #ifdef GF_GL_HAS_TEXTURE //currently supporting 1 texture - if (gfNumTextures>0) { -#if defined(GF_GL_IS_YUV) - texc = TexCoord.st; - yuv.x = texture2D(y_plane, texc).r; - if (yuvPixelFormat==2) { - yuv.y = texture2D(u_plane, texc).r; - yuv.z = texture2D(u_plane, texc).a; - } - else if (yuvPixelFormat==1) { - yuv.y = texture2D(u_plane, texc).a; - yuv.z = texture2D(u_plane, texc).r; - } - else { - yuv.y = texture2D(u_plane, texc).r; - yuv.z = texture2D(v_plane, texc).r; - } - yuv += offset; - rgb.r = dot(yuv, R_mul); - rgb.g = dot(yuv, G_mul); - rgb.b = dot(yuv, B_mul); - - rgba = vec4(rgb, alpha); - -#elif defined(GF_GL_IS_ExternalOES) - rgba = texture2D(imgOES, TexCoord); -#else - rgba = texture2D(img, TexCoord); -#endif + rgba = maintx_sample(TexCoord); #ifdef GF_GL_HAS_LIGHT - if (gfNumLights>0) { //RGB texture - fragColor *= rgba; - } - //RGB texture with material 2D [TODO: check] - else if(gfNumLights==0) + if (gfNumLights>0) { //RGB texture + fragColor *= rgba; + } + //RGB texture with material 2D [TODO: check] + else if(gfNumLights==0) #endif - { - fragColor = rgba; - } + { + fragColor = rgba; + } //we have mat 2D + texture #ifndef GF_GL_IS_ExternalOES - if (hasMaterial2D) { - if(gfEmissionColor.a > 0.0 && gfEmissionColor.a <1.0) { - fragColor *= gfEmissionColor; - } else if(fragColor.rgb == vec3(0.0, 0.0, 0.0)){ - fragColor.rgb = gfEmissionColor.rgb; - } + if (hasMaterial2D) { + if (gfEmissionColor.a > 0.0) { + fragColor *= gfEmissionColor; + } + //hack - if full transparency on texture with material2D, use material color + else if (fragColor.rgb == vec3(0.0, 0.0, 0.0)){ + fragColor.rgb = gfEmissionColor.rgb; } -#endif // GF_GL_IS_ExternalOES } +#endif // GF_GL_IS_ExternalOES + #endif // GF_GL_HAS_TEXTURE diff --git a/shaders/vertex.glsl b/share/shaders/vertex.glsl similarity index 100% rename from shaders/vertex.glsl rename to share/shaders/vertex.glsl diff --git a/share/vis/Code/Console.js b/share/vis/Code/Console.js new file mode 100644 index 0000000..1332cd4 --- /dev/null +++ b/share/vis/Code/Console.js @@ -0,0 +1,208 @@ + +Console = (function() +{ + var BORDER = 10; + var HEIGHT = 200; + + + function Console(wm, server) + { + // Create the window and its controls + this.Window = wm.AddWindow("Console", 10, 10, 100, 100); + this.PageContainer = this.Window.AddControlNew(new WM.Container(10, 10, 400, 160)); + DOM.Node.AddClass(this.PageContainer.Node, "ConsoleText"); + this.AppContainer = this.Window.AddControlNew(new WM.Container(10, 10, 400, 160)); + DOM.Node.AddClass(this.AppContainer.Node, "ConsoleText"); + this.UserInput = this.Window.AddControlNew(new WM.EditBox(10, 5, 400, 30, "Input", "")); + this.UserInput.SetChangeHandler(Bind(ProcessInput, this)); + this.Window.ShowNoAnim(); + + // This accumulates log text as fast as is required + this.PageTextBuffer = ""; + this.PageTextUpdatePending = false; + this.AppTextBuffer = ""; + this.AppTextUpdatePending = false; + + // Setup command history control + this.CommandHistory = LocalStore.Get("App", "Global", "CommandHistory", [ ]); + this.CommandIndex = 0; + this.MaxNbCommands = 200; + DOM.Event.AddHandler(this.UserInput.EditNode, "keydown", Bind(OnKeyPress, this)); + DOM.Event.AddHandler(this.UserInput.EditNode, "focus", Bind(OnFocus, this)); + + // At a much lower frequency this will update the console window + window.setInterval(Bind(UpdateHTML, this), 500); + + // Setup log requests from the server + this.Server = server; + server.SetConsole(this); + server.AddMessageHandler("LOGM", Bind(OnLog, this)); + + this.Window.SetOnResize(Bind(OnUserResize, this)); + } + + + Console.prototype.Log = function(text) + { + this.PageTextBuffer = LogText(this.PageTextBuffer, text); + this.PageTextUpdatePending = true; + } + + + Console.prototype.WindowResized = function(width, height) + { + // Place window + this.Window.SetPosition(BORDER, height - BORDER - 200); + this.Window.SetSize(width - 2 * BORDER, HEIGHT); + + ResizeInternals(this); + } + + + function OnLog(self, socket, data_view) + { + var data_view_reader = new DataViewReader(data_view, 4); + var text = data_view_reader.GetString(); + self.AppTextBuffer = LogText(self.AppTextBuffer, text); + self.AppTextUpdatePending = true; + } + + + function LogText(existing_text, new_text) + { + // Filter the text a little to make it safer + if (new_text == null) + new_text = "NULL"; + + // Find and convert any HTML entities, ensuring the browser doesn't parse any embedded HTML code + // This also allows the log to contain arbitrary C++ code (e.g. assert comparison operators) + new_text = Convert.string_to_html_entities(new_text); + + // Prefix date and end with new line + var d = new Date(); + new_text = "[" + d.toLocaleTimeString() + "] " + new_text + "
"; + + // Append to local text buffer and ensure clip the oldest text to ensure a max size + existing_text = existing_text + new_text; + var max_len = 10 * 1024; + var len = existing_text.length; + if (len > max_len) + existing_text = existing_text.substr(len - max_len, max_len); + + return existing_text; + } + + function OnUserResize(self, evt) + { + ResizeInternals(self); + } + + function ResizeInternals(self) + { + // Place controls + var parent_size = self.Window.Size; + var mid_w = parent_size[0] / 3; + self.UserInput.SetPosition(BORDER, parent_size[1] - 2 * BORDER - 30); + self.UserInput.SetSize(parent_size[0] - 100, 18); + var output_height = self.UserInput.Position[1] - 2 * BORDER; + self.PageContainer.SetPosition(BORDER, BORDER); + self.PageContainer.SetSize(mid_w - 2 * BORDER, output_height); + self.AppContainer.SetPosition(mid_w, BORDER); + self.AppContainer.SetSize(parent_size[0] - mid_w - BORDER, output_height); + } + + + function UpdateHTML(self) + { + // Reset the current text buffer as html + + if (self.PageTextUpdatePending) + { + var page_node = self.PageContainer.Node; + page_node.innerHTML = self.PageTextBuffer; + page_node.scrollTop = page_node.scrollHeight; + self.PageTextUpdatePending = false; + } + + if (self.AppTextUpdatePending) + { + var app_node = self.AppContainer.Node; + app_node.innerHTML = self.AppTextBuffer; + app_node.scrollTop = app_node.scrollHeight; + self.AppTextUpdatePending = false; + } + } + + + function ProcessInput(self, node) + { + // Send the message exactly + var msg = node.value; + self.Server.Send("CONI" + msg); + + // Emit to console and clear + self.Log("> " + msg); + self.UserInput.SetValue(""); + + // Keep track of recently issued commands, with an upper bound + self.CommandHistory.push(msg); + var extra_commands = self.CommandHistory.length - self.MaxNbCommands; + if (extra_commands > 0) + self.CommandHistory.splice(0, extra_commands); + + // Set command history index to the most recent command + self.CommandIndex = self.CommandHistory.length; + + // Backup to local store + LocalStore.Set("App", "Global", "CommandHistory", self.CommandHistory); + + // Keep focus with the edit box + return true; + } + + + function OnKeyPress(self, evt) + { + evt = DOM.Event.Get(evt); + + if (evt.keyCode == Keyboard.Codes.UP) + { + if (self.CommandHistory.length > 0) + { + // Cycle backwards through the command history + self.CommandIndex--; + if (self.CommandIndex < 0) + self.CommandIndex = self.CommandHistory.length - 1; + var command = self.CommandHistory[self.CommandIndex]; + self.UserInput.SetValue(command); + } + + // Stops default behaviour of moving cursor to the beginning + DOM.Event.StopDefaultAction(evt); + } + + else if (evt.keyCode == Keyboard.Codes.DOWN) + { + if (self.CommandHistory.length > 0) + { + // Cycle fowards through the command history + self.CommandIndex = (self.CommandIndex + 1) % self.CommandHistory.length; + var command = self.CommandHistory[self.CommandIndex]; + self.UserInput.SetValue(command); + } + + // Stops default behaviour of moving cursor to the end + DOM.Event.StopDefaultAction(evt); + } + } + + + function OnFocus(self) + { + // Reset command index on focus + self.CommandIndex = self.CommandHistory.length; + } + + + return Console; +})(); diff --git a/share/vis/Code/DataViewReader.js b/share/vis/Code/DataViewReader.js new file mode 100644 index 0000000..ba3fc8e --- /dev/null +++ b/share/vis/Code/DataViewReader.js @@ -0,0 +1,47 @@ + +// +// Simple wrapper around DataView that auto-advances the read offset and provides +// a few common data type conversions specific to this app +// +DataViewReader = (function () +{ + function DataViewReader(data_view, offset) + { + this.DataView = data_view; + this.Offset = offset; + } + + DataViewReader.prototype.GetUInt32 = function () + { + var v = this.DataView.getUint32(this.Offset, true); + this.Offset += 4; + return v; + } + + DataViewReader.prototype.GetUInt64 = function () + { + var v = this.DataView.getFloat64(this.Offset, true); + this.Offset += 8; + return v; + } + + DataViewReader.prototype.GetStringOfLength = function (string_length) + { + var string = ""; + for (var i = 0; i < string_length; i++) + { + string += String.fromCharCode(this.DataView.getInt8(this.Offset)); + this.Offset++; + } + + return string; + } + + DataViewReader.prototype.GetString = function () + { + var string_length = this.GetUInt32(); + return this.GetStringOfLength(string_length); + } + + return DataViewReader; +})(); diff --git a/share/vis/Code/PixelTimeRange.js b/share/vis/Code/PixelTimeRange.js new file mode 100644 index 0000000..14a0668 --- /dev/null +++ b/share/vis/Code/PixelTimeRange.js @@ -0,0 +1,61 @@ + + +PixelTimeRange = (function() +{ + function PixelTimeRange(start_us, span_us, span_px) + { + this.Span_px = span_px; + this.Set(start_us, span_us); + } + + + PixelTimeRange.prototype.Set = function(start_us, span_us) + { + this.Start_us = start_us; + this.Span_us = span_us; + this.End_us = this.Start_us + span_us; + this.usPerPixel = this.Span_px / this.Span_us; + } + + + PixelTimeRange.prototype.SetStart = function(start_us) + { + this.Start_us = start_us; + this.End_us = start_us + this.Span_us; + } + + + PixelTimeRange.prototype.SetEnd = function(end_us) + { + this.End_us = end_us; + this.Start_us = end_us - this.Span_us; + } + + + PixelTimeRange.prototype.SetPixelSpan = function(span_px) + { + this.Span_px = span_px; + this.usPerPixel = this.Span_px / this.Span_us; + } + + + PixelTimeRange.prototype.PixelOffset = function(time_us) + { + return Math.floor((time_us - this.Start_us) * this.usPerPixel); + } + + + PixelTimeRange.prototype.PixelSize = function(time_us) + { + return Math.floor(time_us * this.usPerPixel); + } + + + PixelTimeRange.prototype.Clone = function() + { + return new PixelTimeRange(this.Start_us, this.Span_us, this.Span_px); + } + + + return PixelTimeRange; +})(); diff --git a/share/vis/Code/Remotery.js b/share/vis/Code/Remotery.js new file mode 100644 index 0000000..ca03709 --- /dev/null +++ b/share/vis/Code/Remotery.js @@ -0,0 +1,338 @@ + +// +// TODO: Window resizing needs finer-grain control +// TODO: Take into account where user has moved the windows +// TODO: Controls need automatic resizing within their parent windows +// + + +Settings = (function() +{ + function Settings() + { + this.IsPaused = false; + } + + return Settings; + +})(); + + +Remotery = (function() +{ + // crack the url and get the parameter we want + var getUrlParameter = function getUrlParameter( search_param) + { + var page_url = decodeURIComponent( window.location.search.substring(1) ), + url_vars = page_url.split('&'), + param_name, + i; + + for (i = 0; i < url_vars.length; i++) + { + param_name = url_vars[i].split('='); + + if (param_name[0] === search_param) + { + return param_name[1] === undefined ? true : param_name[1]; + } + } + }; + + function Remotery() + { + this.WindowManager = new WM.WindowManager(); + this.Settings = new Settings(); + + // "addr" param is ip:port and will override the local store version if passed in the URL + var addr = getUrlParameter( "addr" ); + if ( addr != null ) + this.ConnectionAddress = "ws://" + addr + "/rmt"; + else + this.ConnectionAddress = LocalStore.Get("App", "Global", "ConnectionAddress", "ws://127.0.0.1:17815/rmt"); + + this.Server = new WebSocketConnection(); + this.Server.AddConnectHandler(Bind(OnConnect, this)); + + // Create the console up front as everything reports to it + this.Console = new Console(this.WindowManager, this.Server); + + // Create required windows + this.TitleWindow = new TitleWindow(this.WindowManager, this.Settings, this.Server, this.ConnectionAddress); + this.TitleWindow.SetConnectionAddressChanged(Bind(OnAddressChanged, this)); + this.TimelineWindow = new TimelineWindow(this.WindowManager, this.Settings, this.Server, Bind(OnTimelineCheck, this)); + this.TimelineWindow.SetOnHover(Bind(OnSampleHover, this)); + this.TimelineWindow.SetOnSelected(Bind(OnSampleSelected, this)); + + this.NbSampleWindows = 0; + this.SampleWindows = { }; + this.FrameHistory = { }; + this.SelectedFrames = { }; + this.NameMap = { }; + + this.Server.AddMessageHandler("SMPL", Bind(OnSamples, this)); + this.Server.AddMessageHandler("SSMP", Bind(OnSampleName, this)); + + // Kick-off the auto-connect loop + AutoConnect(this); + + // Hook up resize event handler + DOM.Event.AddHandler(window, "resize", Bind(OnResizeWindow, this)); + OnResizeWindow(this); + + // Hook up browser-native canvas refresh + this.DisplayFrame = 0; + this.LastKnownPause = this.Settings.IsPaused; + var self = this; + (function display_loop() + { + window.requestAnimationFrame(display_loop); + DrawTimeline(self); + })(); + } + + + function AutoConnect(self) + { + // Only attempt to connect if there isn't already a connection or an attempt to connect + if (!self.Server.Connected()) + self.Server.Connect(self.ConnectionAddress); + + // Always schedule another check + window.setTimeout(Bind(AutoConnect, self), 2000); + } + + + function OnConnect(self) + { + // Connection address has been validated + LocalStore.Set("App", "Global", "ConnectionAddress", self.ConnectionAddress); + } + + + function OnAddressChanged(self, node) + { + // Update and disconnect, relying on auto-connect to reconnect + self.ConnectionAddress = node.value; + self.Server.Disconnect(); + + // Give input focus away + return false; + } + + + function DrawTimeline(self) + { + // Has pause state changed? + if (self.Settings.IsPaused != self.LastKnownPaused) + { + // When switching TO paused, draw one last frame to ensure the sample text gets drawn + self.LastKnownPaused = self.Settings.IsPaused; + self.TimelineWindow.DrawAllRows(); + return; + } + + // Don't waste time drawing the timeline when paused + if (self.Settings.IsPaused) + return; + + // requestAnimationFrame can run up to 60hz which is way too much for drawing the timeline + // Assume it's running at 60hz and skip frames to achieve 10hz instead + // Doing this instead of using setTimeout because it's better for browser rendering (or; will be once WebGL is in use) + // TODO: Expose as config variable because high refresh rate is great when using a separate viewiing machine + if ((self.DisplayFrame % 10) == 0) + self.TimelineWindow.DrawAllRows(); + + self.DisplayFrame++; + } + + + function DecodeSample(self, data_view_reader) + { + var sample = {}; + + // Get name hash and lookup name it map + sample.name_hash = data_view_reader.GetUInt32(); + sample.name = self.NameMap[sample.name_hash]; + + // If the name doesn't exist in the map yet, request it from the server + if (sample.name == undefined) + { + // Meanwhile, store the hash as the name + sample.name = { "string": sample.name_hash }; + self.NameMap[sample.name_hash] = sample.name; + self.Server.Send("GSMP" + sample.name_hash); + } + + // Get the rest of the sample data + sample.id = data_view_reader.GetUInt32(); + sample.colour = data_view_reader.GetStringOfLength(7); + sample.us_start = data_view_reader.GetUInt64(); + sample.us_length = data_view_reader.GetUInt64(); + sample.us_self = data_view_reader.GetUInt64(); + sample.call_count = data_view_reader.GetUInt32(); + sample.recurse_depth = data_view_reader.GetUInt32(); + + // Calculate dependent properties + sample.ms_length = (sample.us_length / 1000.0).toFixed(3); + sample.ms_self = (sample.us_self / 1000.0).toFixed(3); + + // Recurse into children + sample.children = []; + DecodeSampleArray(self, data_view_reader, sample.children); + + return sample; + } + + + function DecodeSampleArray(self, data_view_reader, samples) + { + var nb_samples = data_view_reader.GetUInt32(); + for (var i = 0; i < nb_samples; i++) + { + var sample = DecodeSample(self, data_view_reader); + samples.push(sample) + } + } + + + function DecodeSamples(self, data_view_reader) + { + // Message-specific header + var message = { }; + message.thread_name = data_view_reader.GetString(); + message.nb_samples = data_view_reader.GetUInt32(); + message.sample_digest = data_view_reader.GetUInt32(); + + // Read samples + message.samples = []; + message.samples.push(DecodeSample(self, data_view_reader)); + + return message; + } + + + function OnSamples(self, socket, data_view) + { + // Discard any new samples while paused + if (self.Settings.IsPaused) + return; + + // Binary decode incoming sample data + var message = DecodeSamples(self, new DataViewReader(data_view, 8)); + var name = message.thread_name; + + // Add to frame history for this thread + var thread_frame = new ThreadFrame(message); + if (!(name in self.FrameHistory)) + self.FrameHistory[name] = [ ]; + var frame_history = self.FrameHistory[name]; + frame_history.push(thread_frame); + + // Discard old frames to keep memory-use constant + var max_nb_frames = 10000; + var extra_frames = frame_history.length - max_nb_frames; + if (extra_frames > 0) + frame_history.splice(0, extra_frames); + + // Create sample windows on-demand + if (!(name in self.SampleWindows)) + { + self.SampleWindows[name] = new SampleWindow(self.WindowManager, name, self.NbSampleWindows); + self.SampleWindows[name].WindowResized(self.TimelineWindow.Window, self.Console.Window); + self.NbSampleWindows++; + MoveSampleWindows(this); + } + + // Set on the window and timeline + self.SampleWindows[name].OnSamples(message.nb_samples, message.sample_digest, message.samples); + self.TimelineWindow.OnSamples(name, frame_history); + } + + + function OnSampleName(self, socket, data_view) + { + // Add any names sent by the server to the local map + var data_view_reader = new DataViewReader(data_view, 4); + var name_hash = data_view_reader.GetUInt32(); + var name = data_view_reader.GetString(); + self.NameMap[name_hash].string = name; + } + + + function OnTimelineCheck(self, name, evt) + { + // Show/hide the equivalent sample window and move all the others to occupy any left-over space + var target = DOM.Event.GetNode(evt); + self.SampleWindows[name].SetVisible(target.checked); + MoveSampleWindows(self); + } + + + function MoveSampleWindows(self) + { + // Stack all windows next to each other + var xpos = 0; + for (var i in self.SampleWindows) + { + var sample_window = self.SampleWindows[i]; + if (sample_window.Visible) + sample_window.SetXPos(xpos++, self.TimelineWindow.Window, self.Console.Window); + } + } + + + function OnSampleHover(self, thread_name, hover) + { + // Hover only changes sample window contents when paused + var sample_window = self.SampleWindows[thread_name]; + if (sample_window && self.Settings.IsPaused) + { + if (hover == null) + { + // When there's no hover, go back to the selected frame + if (self.SelectedFrames[thread_name]) + { + var frame = self.SelectedFrames[thread_name]; + sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples); + } + } + + else + { + // Populate with sample under hover + var frame = hover[0]; + sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples); + } + } + } + + + function OnSampleSelected(self, thread_name, select) + { + // Lookup sample window set the frame samples on it + if (select && thread_name in self.SampleWindows) + { + var sample_window = self.SampleWindows[thread_name]; + var frame = select[0]; + self.SelectedFrames[thread_name] = frame; + sample_window.OnSamples(frame.NbSamples, frame.SampleDigest, frame.Samples); + } + } + + + function OnResizeWindow(self) + { + // Resize windows + var w = window.innerWidth; + var h = window.innerHeight; + self.Console.WindowResized(w, h); + self.TitleWindow.WindowResized(w, h); + self.TimelineWindow.WindowResized(w, h, self.TitleWindow.Window); + for (var i in self.SampleWindows) + self.SampleWindows[i].WindowResized(self.TimelineWindow.Window, self.Console.Window); + } + + + return Remotery; +})(); \ No newline at end of file diff --git a/share/vis/Code/SampleWindow.js b/share/vis/Code/SampleWindow.js new file mode 100644 index 0000000..2b0b5d9 --- /dev/null +++ b/share/vis/Code/SampleWindow.js @@ -0,0 +1,215 @@ + +SampleWindow = (function() +{ + function SampleWindow(wm, name, offset) + { + // Sample digest for checking if grid needs to be repopulated + this.NbSamples = 0; + this.SampleDigest = null; + + // Source sample reference to reduce repopulation + this.Samples = null; + + this.XPos = 10 + offset * 410; + this.Window = wm.AddWindow(name, 100, 100, 100, 100); + this.Window.ShowNoAnim(); + this.Visible = true; + + // Create a grid that's indexed by the unique sample ID + this.Grid = this.Window.AddControlNew(new WM.Grid()); + var cell_data = + { + Name: "Samples", + Length: "Time (ms)", + Self: "Self (ms)", + Calls: "Calls", + Recurse: "Recurse", + }; + var cell_classes = + { + Name: "SampleTitleNameCell", + Length: "SampleTitleTimeCell", + Self: "SampleTitleTimeCell", + Calls: "SampleTitleCountCell", + Recurse: "SampleTitleCountCell", + }; + this.RootRow = this.Grid.Rows.Add(cell_data, "GridGroup", cell_classes); + this.RootRow.Rows.AddIndex("_ID"); + } + + + SampleWindow.prototype.SetXPos = function(xpos, top_window, bottom_window) + { + Anim.Animate( + Bind(AnimatedMove, this, top_window, bottom_window), + this.XPos, 10 + xpos * 410, 0.25); + } + + + function AnimatedMove(self, top_window, bottom_window, val) + { + self.XPos = val; + self.WindowResized(top_window, bottom_window); + } + + + SampleWindow.prototype.SetVisible = function(visible) + { + if (visible != this.Visible) + { + if (visible == true) + this.Window.Show(); + else + this.Window.Hide(); + + this.Visible = visible; + } + } + + + SampleWindow.prototype.WindowResized = function(top_window, bottom_window) + { + var top = top_window.Position[1] + top_window.Size[1] + 10; + this.Window.SetPosition(this.XPos, top_window.Position[1] + top_window.Size[1] + 10); + this.Window.SetSize(400, bottom_window.Position[1] - 10 - top); + } + + + SampleWindow.prototype.OnSamples = function(nb_samples, sample_digest, samples) + { + if (!this.Visible) + return; + + // If the source hasn't changed, don't repopulate + if (this.Samples == samples) + return; + this.Samples = samples; + + // Recreate all the HTML if the number of samples gets bigger + if (nb_samples > this.NbSamples) + { + GrowGrid(this.RootRow, nb_samples); + this.NbSamples = nb_samples; + } + + // If the content of the samples changes from previous update, update them all + if (this.SampleDigest != sample_digest) + { + this.RootRow.Rows.ClearIndex("_ID"); + var index = UpdateAllSampleFields(this.RootRow, samples, 0, ""); + this.SampleDigest = sample_digest; + + // Clear out any left-over rows + for (var i = index; i < this.RootRow.Rows.Rows.length; i++) + { + var row = this.RootRow.Rows.Rows[i]; + DOM.Node.Hide(row.Node); + } + } + + else if (this.Visible) + { + // Otherwise just update the existing sample fields + UpdateChangedSampleFields(this.RootRow, samples, ""); + } + } + + + function GrowGrid(parent_row, nb_samples) + { + parent_row.Rows.Clear(); + + for (var i = 0; i < nb_samples; i++) + { + var cell_data = + { + _ID: i, + Name: "", + Length: "", + Self: "", + Calls: "", + Recurse: "", + }; + + var cell_classes = + { + Name: "SampleNameCell", + Length: "SampleTimeCell", + Self: "SampleTimeCell", + Calls: "SampleCountCell", + Recurse: "SampleCountCell", + }; + + parent_row.Rows.Add(cell_data, null, cell_classes); + } + } + + + function UpdateAllSampleFields(parent_row, samples, index, indent) + { + for (var i in samples) + { + var sample = samples[i]; + + // Match row allocation in GrowGrid + var row = parent_row.Rows.Rows[index++]; + + // Sample row may have been hidden previously + DOM.Node.Show(row.Node); + + // Assign unique ID so that the common fast path of updating sample times only + // can lookup target samples in the grid + row.CellData._ID = sample.id; + parent_row.Rows.AddRowToIndex("_ID", sample.id, row); + + // Record sample name for later comparison + row.CellData.Name = sample.name.string; + + // Set sample name and colour + var name_node = row.CellNodes["Name"]; + name_node.innerHTML = indent + sample.name.string; + DOM.Node.SetColour(name_node, sample.colour); + + row.CellNodes["Length"].innerHTML = sample.ms_length; + row.CellNodes["Self"].innerHTML = sample.ms_self; + row.CellNodes["Calls"].innerHTML = sample.call_count; + row.CellNodes["Recurse"].innerHTML = sample.recurse_depth; + + index = UpdateAllSampleFields(parent_row, sample.children, index, indent + "     "); + } + + return index; + } + + + function UpdateChangedSampleFields(parent_row, samples, indent) + { + for (var i in samples) + { + var sample = samples[i]; + + var row = parent_row.Rows.GetBy("_ID", sample.id); + if (row) + { + row.CellNodes["Length"].innerHTML = sample.ms_length; + row.CellNodes["Self"].innerHTML = sample.ms_self; + row.CellNodes["Calls"].innerHTML = sample.call_count; + row.CellNodes["Recurse"].innerHTML = sample.recurse_depth; + + // Sample name will change when it switches from hash ID to network-retrieved + // name. Quickly check that before re-applying the HTML for the name. + if (row.CellData.Name != sample.name.string) + { + var name_node = row.CellNodes["Name"]; + row.CellData.Name = sample.name.string; + name_node.innerHTML = indent + sample.name.string; + } + } + + UpdateChangedSampleFields(parent_row, sample.children, indent + "     "); + } + } + + + return SampleWindow; +})(); diff --git a/share/vis/Code/ThreadFrame.js b/share/vis/Code/ThreadFrame.js new file mode 100644 index 0000000..a631590 --- /dev/null +++ b/share/vis/Code/ThreadFrame.js @@ -0,0 +1,28 @@ + + +ThreadFrame = (function() +{ + function ThreadFrame(message) + { + // Persist the required message data + this.NbSamples = message.nb_samples; + this.SampleDigest = message.sample_digest; + this.Samples = message.samples; + + // Calculate the frame start/end times + this.StartTime_us = 0; + this.EndTime_us = 0; + var nb_root_samples = this.Samples.length; + if (nb_root_samples > 0) + { + var last_sample = this.Samples[nb_root_samples - 1]; + this.StartTime_us = this.Samples[0].us_start; + this.EndTime_us = last_sample.us_start + last_sample.us_length; + } + + this.Length_us = this.EndTime_us - this.StartTime_us; + } + + + return ThreadFrame; +})(); diff --git a/share/vis/Code/TimelineRow.js b/share/vis/Code/TimelineRow.js new file mode 100644 index 0000000..8fcc5ed --- /dev/null +++ b/share/vis/Code/TimelineRow.js @@ -0,0 +1,379 @@ + + +TimelineRow = (function() +{ + var row_template = function(){/* +

+
+ +
+
+
+
+
+
+
-
+
+
+ +
+
+*/}.toString().split(/\n/).slice(1, -1).join("\n"); + + + var CANVAS_Y_OFFSET = 0; + var CANVAS_BORDER = 1; + var SAMPLE_HEIGHT = 16; + var SAMPLE_BORDER = 1; + var SAMPLE_Y_SPACING = SAMPLE_HEIGHT + SAMPLE_BORDER * 2; + var SAMPLE_Y_OFFSET = CANVAS_Y_OFFSET + CANVAS_BORDER + 1; + + + function TimelineRow(name, width, parent_node, frame_history, check_handler) + { + this.Name = name; + + // Create the row HTML and add to the parent + this.ContainerNode = DOM.Node.CreateHTML(row_template); + this.Node = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowData"); + this.LabelNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowLabel"); + this.LabelNode.innerHTML = name; + this.CheckboxNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowCheckbox"); + var expand_node_0 = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowExpand", 0); + var expand_node_1 = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowExpand", 1); + this.IncNode = DOM.Node.FindWithClass(expand_node_0, "TimelineRowExpandButton"); + this.DecNode = DOM.Node.FindWithClass(expand_node_1, "TimelineRowExpandButton"); + this.CanvasNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowCanvas"); + parent_node.appendChild(this.ContainerNode); + + // All sample view windows visible by default + this.CheckboxNode.checked = true; + DOM.Event.AddHandler(this.CheckboxNode, "change", function(evt) { check_handler(name, evt); }); + + // Manually hook-up events to simulate div:active + // I can't get the equivalent CSS to work in Firefox, so... + DOM.Event.AddHandler(this.IncNode, "mousedown", ExpandButtonDown); + DOM.Event.AddHandler(this.IncNode, "mouseup", ExpandButtonUp); + DOM.Event.AddHandler(this.IncNode, "mouseleave", ExpandButtonUp); + DOM.Event.AddHandler(this.DecNode, "mousedown", ExpandButtonDown); + DOM.Event.AddHandler(this.DecNode, "mouseup", ExpandButtonUp); + DOM.Event.AddHandler(this.DecNode, "mouseleave", ExpandButtonUp); + + // Pressing +/i increases/decreases depth + DOM.Event.AddHandler(this.IncNode, "click", Bind(IncDepth, this)); + DOM.Event.AddHandler(this.DecNode, "click", Bind(DecDepth, this)); + + // Setup the canvas + this.Depth = 1; + this.Ctx = this.CanvasNode.getContext("2d"); + this.SetSize(width); + this.Clear(); + + // Frame index to start at when looking for first visible sample + this.StartFrameIndex = 0; + + this.FrameHistory = frame_history; + this.VisibleFrames = [ ]; + this.VisibleTimeRange = null; + + // Sample the mouse is currently hovering over + this.HoverSample = null; + this.HoverSampleDepth = 0; + + // Currently selected sample + this.SelectedSample = null; + this.SelectedSampleDepth = 0; + } + + + TimelineRow.prototype.SetSize = function(width) + { + // Must ALWAYS set the width/height properties together. Setting one on its own has weird side-effects. + this.CanvasNode.width = width; + this.CanvasNode.height = CANVAS_BORDER + SAMPLE_BORDER + SAMPLE_Y_SPACING * this.Depth; + this.Draw(true); + } + + + TimelineRow.prototype.Clear = function() + { + // Fill box that shows the boundary between thread rows + this.Ctx.fillStyle = "#666" + var b = CANVAS_BORDER; + this.Ctx.fillRect(b, b, this.CanvasNode.width - b * 2, this.CanvasNode.height - b * 2); + } + + + TimelineRow.prototype.SetVisibleFrames = function(time_range) + { + // Clear previous visible list + this.VisibleFrames = [ ]; + if (this.FrameHistory.length == 0) + return; + + // Store a copy of the visible time range rather than referencing it + // This prevents external modifications to the time range from affecting rendering/selection + time_range = time_range.Clone(); + this.VisibleTimeRange = time_range; + + // The frame history can be reset outside this class + // This also catches the overflow to the end of the frame list below when a thread stops sending samples + var max_frame = Math.max(this.FrameHistory.length - 1, 0); + var start_frame_index = Math.min(this.StartFrameIndex, max_frame); + + // First do a back-track in case the time range moves negatively + while (start_frame_index > 0) + { + var frame = this.FrameHistory[start_frame_index]; + if (time_range.Start_us > frame.StartTime_us) + break; + start_frame_index--; + } + + // Then search from this point for the first visible frame + while (start_frame_index < this.FrameHistory.length) + { + var frame = this.FrameHistory[start_frame_index]; + if (frame.EndTime_us > time_range.Start_us) + break; + start_frame_index++; + } + + // Gather all frames up to the end point + this.StartFrameIndex = start_frame_index; + for (var i = start_frame_index; i < this.FrameHistory.length; i++) + { + var frame = this.FrameHistory[i]; + if (frame.StartTime_us > time_range.End_us) + break; + this.VisibleFrames.push(frame); + } + } + + + TimelineRow.prototype.Draw = function(draw_text) + { + this.Clear(); + + // Draw all root samples in the visible frame set + for (var i in this.VisibleFrames) + { + var frame = this.VisibleFrames[i]; + DrawSamples(this, frame.Samples, 1, draw_text); + } + } + + + function DrawSamples(self, samples, depth, draw_text) + { + for (var i in samples) + { + var sample = samples[i]; + DrawSample(self, sample, depth, draw_text); + + if (depth < self.Depth && sample.children != null) + DrawSamples(self, sample.children, depth + 1, draw_text); + } + } + + + TimelineRow.prototype.UpdateHoverSample = function(mouse_state, x_offset) + { + var hover = GetSampleAtPosition(this, mouse_state, x_offset); + if (hover) + this.SetHoverSample(hover[1], hover[2]); + return hover; + } + + + TimelineRow.prototype.UpdateSelectedSample = function(mouse_state, x_offset) + { + var select = GetSampleAtPosition(this, mouse_state, x_offset); + if (select) + this.SetSelectedSample(select[1], select[2]); + return select; + } + + + TimelineRow.prototype.SetHoverSample = function(sample, sample_depth) + { + if (sample != this.HoverSample) + { + // Discard old highlight + // TODO: When zoomed right out, tiny samples are anti-aliased and this becomes inaccurate + var old_sample = this.HoverSample; + var old_sample_depth = this.HoverSampleDepth; + this.HoverSample = null; + this.HoverSampleDepth = 0; + DrawSample(this, old_sample, old_sample_depth, true); + + // Add new highlight + this.HoverSample = sample; + this.HoverSampleDepth = sample_depth; + DrawSample(this, sample, sample_depth, true); + } + } + + + TimelineRow.prototype.SetSelectedSample = function(sample, sample_depth) + { + if (sample != this.SelectedSample) + { + // Discard old highlight + // TODO: When zoomed right out, tiny samples are anti-aliased and this becomes inaccurate + var old_sample = this.SelectedSample; + var old_sample_depth = this.SelectedSampleDepth; + this.SelectedSample = null; + this.SelectedSampleDepth = 0; + DrawSample(this, old_sample, old_sample_depth, true); + + // Add new highlight + this.SelectedSample = sample; + this.SelectedSampleDepth = sample_depth; + DrawSample(this, sample, sample_depth, true); + } + } + + + function ExpandButtonDown(evt) + { + var node = DOM.Event.GetNode(evt); + DOM.Node.AddClass(node, "TimelineRowExpandButtonActive"); + } + + + function ExpandButtonUp(evt) + { + var node = DOM.Event.GetNode(evt); + DOM.Node.RemoveClass(node, "TimelineRowExpandButtonActive"); + } + + + function IncDepth(self) + { + self.Depth++; + self.SetSize(self.CanvasNode.width); + } + + + function DecDepth(self) + { + if (self.Depth > 1) + { + self.Depth--; + self.SetSize(self.CanvasNode.width); + } + } + + + function GetSampleAtPosition(self, mouse_state, x_offset) + { + // Mouse movement can occur before any data is sent to a timeline row + var time_range = self.VisibleTimeRange; + if (time_range == null) + return; + + // Get the time the mouse is over + var x = mouse_state.Position[0] - x_offset; + var time_us = time_range.Start_us + x / time_range.usPerPixel; + + var canvas_y_offset = DOM.Node.GetPosition(self.CanvasNode)[1]; + var mouse_y_offset = mouse_state.Position[1] - canvas_y_offset; + mouse_y_offset = Math.min(Math.max(mouse_y_offset, 0), self.CanvasNode.height); + var depth = Math.floor(mouse_y_offset / SAMPLE_Y_SPACING) + 1; + + // Search for the first frame to intersect this time + for (var i in self.VisibleFrames) + { + var frame = self.VisibleFrames[i]; + if (time_us >= frame.StartTime_us && time_us < frame.EndTime_us) + { + var found_sample = FindSample(self, frame.Samples, time_us, depth, 1); + if (found_sample != null) + return [ frame, found_sample[0], found_sample[1] ]; + } + } + + return null; + } + + + function FindSample(self, samples, time_us, target_depth, depth) + { + for (var i in samples) + { + var sample = samples[i]; + if (depth == target_depth) + { + if (time_us >= sample.us_start && time_us < sample.us_start + sample.us_length) + return [ sample, depth ]; + } + + else if (depth < target_depth && sample.children != null) + { + var found_sample = FindSample(self, sample.children, time_us, target_depth, depth + 1); + if (found_sample != null) + return found_sample; + } + } + + return null; + } + + + function DrawSample(self, sample, depth, draw_text) + { + if (sample == null) + return; + + // Determine pixel range of the sample + var time_range = self.VisibleTimeRange; + var x0 = time_range.PixelOffset(sample.us_start); + var x1 = x0 + time_range.PixelSize(sample.us_length); + + // Clip to padded timeline row + var min_x = 3; + var max_x = self.CanvasNode.width - 5; + x0 = Math.min(Math.max(x0, min_x), max_x); + x1 = Math.min(Math.max(x1, min_x), max_x); + + var offset_x = x0; + var offset_y = SAMPLE_Y_OFFSET + (depth - 1) * SAMPLE_Y_SPACING; + var size_x = x1 - x0; + var size_y = SAMPLE_HEIGHT; + + // Normal rendering + var ctx = self.Ctx; + ctx.fillStyle = sample.colour; + ctx.fillRect(offset_x, offset_y, size_x, size_y); + + // Highlight rendering + var b = (sample == self.HoverSample) ? 255 : 0; + var r = (sample == self.SelectedSample) ? 255 : 0; + if (b + r > 0) + { + ctx.lineWidth = 1; + ctx.strokeStyle = "rgb(" + r + ", 0, " + b + ")"; + ctx.strokeRect(offset_x + 0.5, offset_y + 0.5, size_x - 1, size_y - 1); + } + + // Draw sample names clipped to the bounds of the sample + // Also reject tiny samples with no space to render text + if (draw_text && size_x > 8) + { + ctx.save(); + ctx.beginPath(); + ctx.rect(offset_x + 2.5, offset_y + 1.5, size_x - 5, size_y - 3); + ctx.clip(); + ctx.font = "9px verdana"; + ctx.fillStyle = "black"; + var text = sample.name.string + text += " (" + sample.ms_length + "ms"; + text += ", " + sample.call_count + "c)"; + ctx.fillText(text, offset_x + 5.5, offset_y + 1.5 + 9); + ctx.restore(); + } + } + + + return TimelineRow; +})(); diff --git a/share/vis/Code/TimelineWindow.js b/share/vis/Code/TimelineWindow.js new file mode 100644 index 0000000..127c308 --- /dev/null +++ b/share/vis/Code/TimelineWindow.js @@ -0,0 +1,284 @@ + +// +// TODO: Use WebGL and instancing for quicker renders +// + + +TimelineWindow = (function() +{ + var BORDER = 10; + + var ROW_START_SIZE = 210; + + var ROW_END_SIZE = 20; // make room for scrollbar + + var box_template = "
"; + + function TimelineWindow(wm, settings, server, check_handler) + { + this.Settings = settings; + + // Ordered list of thread rows on the timeline + this.ThreadRows = [ ]; + + // Create window and containers + this.Window = wm.AddWindow("Timeline", 10, 20, 100, 100); + this.Window.ShowNoAnim(); + this.TimelineContainer = this.Window.AddControlNew(new WM.Container(10, 10, 800, 160)); + DOM.Node.AddClass(this.TimelineContainer.Node, "TimelineContainer"); + + var mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel"; + DOM.Event.AddHandler(this.TimelineContainer.Node, mouse_wheel_event, Bind(OnMouseScroll, this)); + + // Setup timeline manipulation + this.MouseDown = false; + this.LastMouseState = null; + this.TimelineMoved = false; + this.OnHoverHandler = null; + this.OnSelectedHandler = null; + DOM.Event.AddHandler(this.TimelineContainer.Node, "mousedown", Bind(OnMouseDown, this)); + DOM.Event.AddHandler(this.TimelineContainer.Node, "mouseup", Bind(OnMouseUp, this)); + DOM.Event.AddHandler(this.TimelineContainer.Node, "mousemove", Bind(OnMouseMove, this)); + + // Set time range AFTER the window has been created, as it uses the window to determine pixel coverage + this.TimeRange = new PixelTimeRange(0, 200 * 1000, RowWidth(this)); + + this.CheckHandler = check_handler; + + this.Window.SetOnResize(Bind(OnUserResize, this)); + } + + + TimelineWindow.prototype.SetOnHover = function(handler) + { + this.OnHoverHandler = handler; + } + + + TimelineWindow.prototype.SetOnSelected = function(handler) + { + this.OnSelectedHandler = handler; + } + + TimelineWindow.prototype.WindowResized = function(width, height, top_window) + { + // Resize window + var top = top_window.Position[1] + top_window.Size[1] + 10; + this.Window.SetPosition(10, top); + this.Window.SetSize(width - 2 * 10, 260); + + ResizeInternals(this); + } + + + TimelineWindow.prototype.ResetTimeRange = function() + { + this.TimeRange.SetStart(0); + } + + + TimelineWindow.prototype.OnSamples = function(thread_name, frame_history) + { + // Shift the timeline to the last entry on this thread + // As multiple threads come through here with different end frames, only do this for the latest + var last_frame = frame_history[frame_history.length - 1]; + if (last_frame.EndTime_us > this.TimeRange.End_us) + this.TimeRange.SetEnd(last_frame.EndTime_us); + + // Search for the index of this thread + var thread_index = -1; + for (var i in this.ThreadRows) + { + if (this.ThreadRows[i].Name == thread_name) + { + thread_index = i; + break; + } + } + + // If this thread has not been seen before, add a new row to the list and re-sort + if (thread_index == -1) + { + var row = new TimelineRow(thread_name, RowWidth(this), this.TimelineContainer.Node, frame_history, this.CheckHandler); + this.ThreadRows.push(row); + this.ThreadRows.sort(function(a, b) { return b.Name.localeCompare(a.Name); }); + } + } + + + TimelineWindow.prototype.DrawAllRows = function() + { + var time_range = this.TimeRange; + var draw_text = this.Settings.IsPaused; + for (var i in this.ThreadRows) + { + var thread_row = this.ThreadRows[i]; + thread_row.SetVisibleFrames(time_range); + thread_row.Draw(draw_text); + } + } + + + function RowXOffset(self) + { + // Add sizing of the label + // TODO: Use computed size + return DOM.Node.GetPosition(self.TimelineContainer.Node)[0] + ROW_START_SIZE; + } + + + function RowWidth(self) + { + // Subtract sizing of the label + // TODO: Use computed size + return self.TimelineContainer.Size[0] - (ROW_START_SIZE + ROW_END_SIZE); + } + + function OnUserResize(self, evt) + { + ResizeInternals(self); + } + + function ResizeInternals(self) + { + // Resize controls + var parent_size = self.Window.Size; + self.TimelineContainer.SetPosition(BORDER, 10); + self.TimelineContainer.SetSize(parent_size[0] - 2 * BORDER, parent_size[1] - 40); + + // Resize rows + var row_width = RowWidth(self); + for (var i in self.ThreadRows) + { + var row = self.ThreadRows[i]; + row.SetSize(row_width); + } + + // Adjust time range to new width + self.TimeRange.SetPixelSpan(row_width); + self.DrawAllRows(); + } + + + function OnMouseScroll(self, evt) + { + var mouse_state = new Mouse.State(evt); + var scale = 1.11; + if (mouse_state.WheelDelta > 0) + scale = 1 / scale; + + // What time is the mouse hovering over? + var x = mouse_state.Position[0] - RowXOffset(self); + var time_us = self.TimeRange.Start_us + x / self.TimeRange.usPerPixel; + + // Calculate start time relative to the mouse hover position + var time_start_us = self.TimeRange.Start_us - time_us; + + // Scale and offset back to the hover time + self.TimeRange.Set(time_start_us * scale + time_us, self.TimeRange.Span_us * scale); + self.DrawAllRows(); + + // Prevent vertical scrolling on mouse-wheel + DOM.Event.StopDefaultAction(evt); + } + + + function OnMouseDown(self, evt) + { + // Only manipulate the timelime when paused + if (!self.Settings.IsPaused) + return; + + self.MouseDown = true; + self.LastMouseState = new Mouse.State(evt); + self.TimelineMoved = false; + DOM.Event.StopDefaultAction(evt); + } + + + function OnMouseUp(self, evt) + { + // Only manipulate the timelime when paused + if (!self.Settings.IsPaused) + return; + + var mouse_state = new Mouse.State(evt); + + self.MouseDown = false; + + if (!self.TimelineMoved) + { + // Search for the row being clicked and update its selection + var row_node = DOM.Event.GetNode(evt); + for (var i in self.ThreadRows) + { + var thread_row = self.ThreadRows[i]; + if (thread_row.CanvasNode == row_node) + { + var select = thread_row.UpdateSelectedSample(mouse_state, RowXOffset(self)); + + // Call any selection handlers + if (self.OnSelectedHandler) + self.OnSelectedHandler(thread_row.Name, select); + + break; + } + } + } + } + + + function OnMouseMove(self, evt) + { + // Only manipulate the timelime when paused + if (!self.Settings.IsPaused) + return; + + var mouse_state = new Mouse.State(evt); + + if (self.MouseDown) + { + // Get the time the mouse is over + var x = mouse_state.Position[0] - RowXOffset(self); + var time_us = self.TimeRange.Start_us + x / self.TimeRange.usPerPixel; + + // Shift the visible time range with mouse movement + var time_offset_us = (mouse_state.Position[0] - self.LastMouseState.Position[0]) / self.TimeRange.usPerPixel; + if (time_offset_us) + { + self.TimeRange.SetStart(self.TimeRange.Start_us - time_offset_us); + self.DrawAllRows(); + self.TimelineMoved = true; + } + } + + else + { + // Highlight any samples the mouse moves over + var row_node = DOM.Event.GetNode(evt); + for (var i in self.ThreadRows) + { + var thread_row = self.ThreadRows[i]; + if (thread_row.CanvasNode == row_node) + { + var hover = thread_row.UpdateHoverSample(mouse_state, RowXOffset(self)); + + if (self.OnHoverHandler) + self.OnHoverHandler(thread_row.Name, hover); + } + else + { + thread_row.SetHoverSample(null, 0); + if (self.OnHoverHandler) + self.OnHoverHandler(thread_row.Name, null); + } + } + } + + self.LastMouseState = mouse_state; + } + + + return TimelineWindow; +})(); + diff --git a/share/vis/Code/TitleWindow.js b/share/vis/Code/TitleWindow.js new file mode 100644 index 0000000..459267b --- /dev/null +++ b/share/vis/Code/TitleWindow.js @@ -0,0 +1,71 @@ + +TitleWindow = (function() +{ + function TitleWindow(wm, settings, server, connection_address) + { + this.Settings = settings; + + this.Window = wm.AddWindow("     Remotery", 10, 10, 100, 100); + this.Window.ShowNoAnim(); + + this.PingContainer = this.Window.AddControlNew(new WM.Container(4, -13, 10, 10)); + DOM.Node.AddClass(this.PingContainer.Node, "PingContainer"); + + this.EditBox = this.Window.AddControlNew(new WM.EditBox(10, 5, 300, 18, "Connection Address", connection_address)); + + // Setup pause button + this.PauseButton = this.Window.AddControlNew(new WM.Button("Pause", 5, 5, { toggle: true })); + this.PauseButton.SetOnClick(Bind(OnPausePressed, this)); + + server.AddMessageHandler("PING", Bind(OnPing, this)); + + this.Window.SetOnResize(Bind(OnUserResize, this)); + } + + + TitleWindow.prototype.SetConnectionAddressChanged = function(handler) + { + this.EditBox.SetChangeHandler(handler); + } + + + TitleWindow.prototype.WindowResized = function(width, height) + { + this.Window.SetSize(width - 2 * 10, 50); + ResizeInternals(this); + } + + function OnUserResize(self, evt) + { + ResizeInternals(self); + } + + function ResizeInternals(self) + { + self.PauseButton.SetPosition(self.Window.Size[0] - 60, 5); + } + + + function OnPausePressed(self) + { + self.Settings.IsPaused = self.PauseButton.IsPressed(); + if (self.Settings.IsPaused) + self.PauseButton.SetText("Paused"); + else + self.PauseButton.SetText("Pause"); + } + + + function OnPing(self, server) + { + // Set the ping container as active and take it off half a second later + DOM.Node.AddClass(self.PingContainer.Node, "PingContainerActive"); + window.setTimeout(Bind(function(self) + { + DOM.Node.RemoveClass(self.PingContainer.Node, "PingContainerActive"); + }, self), 500); + } + + + return TitleWindow; +})(); \ No newline at end of file diff --git a/share/vis/Code/WebSocketConnection.js b/share/vis/Code/WebSocketConnection.js new file mode 100644 index 0000000..95b1208 --- /dev/null +++ b/share/vis/Code/WebSocketConnection.js @@ -0,0 +1,137 @@ + +WebSocketConnection = (function() +{ + function WebSocketConnection() + { + this.MessageHandlers = { }; + this.Socket = null; + this.Console = null; + } + + + WebSocketConnection.prototype.SetConsole = function(console) + { + this.Console = console; + } + + + WebSocketConnection.prototype.Connected = function() + { + // Will return true if the socket is also in the process of connecting + return this.Socket != null; + } + + + WebSocketConnection.prototype.AddConnectHandler = function(handler) + { + this.AddMessageHandler("__OnConnect__", handler); + } + + + WebSocketConnection.prototype.AddDisconnectHandler = function(handler) + { + this.AddMessageHandler("__OnDisconnect__", handler); + } + + + WebSocketConnection.prototype.AddMessageHandler = function(message_name, handler) + { + // Create the message handler array on-demand + if (!(message_name in this.MessageHandlers)) + this.MessageHandlers[message_name] = [ ]; + this.MessageHandlers[message_name].push(handler); + } + + + WebSocketConnection.prototype.Connect = function(address) + { + // Disconnect if already connected + if (this.Connected()) + this.Disconnect(); + + Log(this, "Connecting to " + address); + + this.Socket = new WebSocket(address); + this.Socket.binaryType = "arraybuffer"; + this.Socket.onopen = Bind(OnOpen, this); + this.Socket.onmessage = Bind(OnMessage, this); + this.Socket.onclose = Bind(OnClose, this); + this.Socket.onerror = Bind(OnError, this); + } + + + WebSocketConnection.prototype.Disconnect = function() + { + Log(this, "Disconnecting"); + if (this.Connected()) + this.Socket.close(); + } + + + WebSocketConnection.prototype.Send = function(msg) + { + if (this.Connected()) + this.Socket.send(msg); + } + + + function Log(self, message) + { + self.Console.Log(message); + } + + + function CallMessageHandlers(self, message_name, data_view) + { + if (message_name in self.MessageHandlers) + { + var handlers = self.MessageHandlers[message_name]; + for (var i in handlers) + handlers[i](self, data_view); + } + } + + + function OnOpen(self, event) + { + Log(self, "Connected"); + CallMessageHandlers(self, "__OnConnect__"); + } + + + function OnClose(self, event) + { + // Clear all references + self.Socket.onopen = null; + self.Socket.onmessage = null; + self.Socket.onclose = null; + self.Socket.onerror = null; + self.Socket = null; + + Log(self, "Disconnected"); + CallMessageHandlers(self, "__OnDisconnect__"); + } + + + function OnError(self, event) + { + Log(self, "Connection Error "); + } + + + function OnMessage(self, event) + { + var data_view = new DataView(event.data); + + var id = String.fromCharCode( + data_view.getInt8(0), + data_view.getInt8(1), + data_view.getInt8(2), + data_view.getInt8(3)); + + CallMessageHandlers(self, id, data_view); + } + + + return WebSocketConnection; +})(); diff --git a/share/vis/Styles/Remotery.css b/share/vis/Styles/Remotery.css new file mode 100644 index 0000000..5fd0e48 --- /dev/null +++ b/share/vis/Styles/Remotery.css @@ -0,0 +1,234 @@ + +body +{ + /* Take up the full page */ + width: 100%; + height: 100%; + margin: 0px; + + background-color: #999; + + touch-action: none; +} + + +/* Override default container style to remove 3D effect */ +.Container +{ + border: none; + box-shadow: none; +} + + +/* Override default edit box style to remove 3D effect */ +.EditBox +{ + border: none; + box-shadow: none; + width:200; +} + + + +.ConsoleText +{ + overflow:auto; + color: #BBB; + font: 9px Verdana; + margin: 2px; + white-space: pre; +} + + +.PingContainer +{ + background-color: #F55; + border-radius: 2px; + + /* Transition from green is gradual */ + transition: background-color 0.25s ease-in; +} + + +.PingContainerActive +{ + background-color: #5F5; + + /* Transition to green is instant */ + transition: none; +} + + +.SampleNameCell +{ + width:243px; +} +.SampleTimeCell +{ + width:52px; +} +.SampleCountCell +{ + width:43px; +} +.SampleTitleNameCell +{ + width:238px; + + padding: 1px 1px 1px 2px; + border: 1px solid; + border-radius: 2px; + + border-top-color:#555; + border-left-color:#555; + border-bottom-color:#111; + border-right-color:#111; + + background: #222; +} +.SampleTitleTimeCell +{ + width:47px; + + padding: 1px 1px 1px 2px; + border: 1px solid; + border-radius: 2px; + + border-top-color:#555; + border-left-color:#555; + border-bottom-color:#111; + border-right-color:#111; + + background: #222; +} +.SampleTitleCountCell +{ + width:38px; + + padding: 1px 1px 1px 2px; + border: 1px solid; + border-radius: 2px; + + border-top-color:#555; + border-left-color:#555; + border-bottom-color:#111; + border-right-color:#111; + + background: #222; +} + + +.TimelineBox +{ + /* Following style generally copies GridRowCell.GridGroup from BrowserLib */ + + padding: 1px 1px 1px 2px; + margin: 1px; + + border: 1px solid; + border-radius: 2px; + border-top-color:#555; + border-left-color:#555; + border-bottom-color:#111; + border-right-color:#111; + + background: #222; + + font: 9px Verdana; + color: #BBB; +} +.TimelineRow +{ + width: 100%; +} +.TimelineRowCheckbox +{ + width: 12px; + height: 12px; + margin: 0px; +} +.TimelineRowCheck +{ + /* Pull .TimelineRowExpand to the right of the checkbox */ + float:left; + + width: 14px; + height: 14px; +} +.TimelineRowExpand +{ + /* Pull .TimelineRowLabel to the right of +/- buttons */ + float:left; + + width: 14px; + height: 14px; +} +.TimelineRowExpandButton +{ + width: 11px; + height: 12px; + + color: #333; + + border: 1px solid; + + border-top-color:#F4F4F4; + border-left-color:#F4F4F4; + border-bottom-color:#8E8F8F; + border-right-color:#8E8F8F; + + /* Top-right to bottom-left grey background gradient */ + background: #f6f6f6; /* Old browsers */ + background: -moz-linear-gradient(-45deg, #f6f6f6 0%, #abaeb2 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,#f6f6f6), color-stop(100%,#abaeb2)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(-45deg, #f6f6f6 0%,#abaeb2 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(-45deg, #f6f6f6 0%,#abaeb2 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(-45deg, #f6f6f6 0%,#abaeb2 100%); /* IE10+ */ + background: linear-gradient(135deg, #f6f6f6 0%,#abaeb2 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f6f6f6', endColorstr='#abaeb2',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */ + + text-align: center; + vertical-align: center; +} +.TimelineRowExpandButton:hover +{ + border-top-color:#79C6F9; + border-left-color:#79C6F9; + border-bottom-color:#385D72; + border-right-color:#385D72; + + /* Top-right to bottom-left blue background gradient, matching border */ + background: #f3f3f3; /* Old browsers */ + background: -moz-linear-gradient(-45deg, #f3f3f3 0%, #79c6f9 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,#f3f3f3), color-stop(100%,#79c6f9)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(-45deg, #f3f3f3 0%,#79c6f9 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(-45deg, #f3f3f3 0%,#79c6f9 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(-45deg, #f3f3f3 0%,#79c6f9 100%); /* IE10+ */ + background: linear-gradient(135deg, #f3f3f3 0%,#79c6f9 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f3f3f3', endColorstr='#79c6f9',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */ +} +.TimelineRowExpandButtonActive +{ + /* Simple means of shifting text within a div to the bottom-right */ + padding-left:1px; + padding-top:1px; + width:10px; + height:11px; +} +.TimelineRowLabel +{ + /* Pull .TimelineRowCanvas to the right of the label */ + float:left; + + width: 140px; + height: 14px; +} +.TimelineRowCanvas +{ +} + +/* enable vertical scrollbar in TimelineContainer (useful for many threads) */ +.TimelineContainer +{ + overflow-y: auto; +} diff --git a/share/vis/extern/BrowserLib/Core/Code/Animation.js b/share/vis/extern/BrowserLib/Core/Code/Animation.js new file mode 100644 index 0000000..516aa9c --- /dev/null +++ b/share/vis/extern/BrowserLib/Core/Code/Animation.js @@ -0,0 +1,65 @@ + +// +// Very basic linear value animation system, for now. +// + + +namespace("Anim"); + + +Anim.Animation = (function() +{ + var anim_hz = 60; + + + function Animation(anim_func, start_value, end_value, time, end_callback) + { + // Setup initial parameters + this.StartValue = start_value; + this.EndValue = end_value; + this.ValueInc = (end_value - start_value) / (time * anim_hz); + this.Value = start_value; + this.Complete = false; + this.EndCallback = end_callback; + + // Cache the update function to prevent recreating the closure + var self = this; + this.AnimFunc = anim_func; + this.AnimUpdate = function() { Update(self); } + + // Call for the start value + this.AnimUpdate(); + } + + + function Update(self) + { + // Queue up the next frame immediately + var id = window.setTimeout(self.AnimUpdate, 1000 / anim_hz); + + // Linear step the value and check for completion + self.Value += self.ValueInc; + if (Math.abs(self.Value - self.EndValue) < 0.01) + { + self.Value = self.EndValue; + self.Complete = true; + + if (self.EndCallback) + self.EndCallback(); + + window.clearTimeout(id); + } + + // Pass to the animation function + self.AnimFunc(self.Value); + } + + + return Animation; +})(); + + +Anim.Animate = function(anim_func, start_value, end_value, time, end_callback) +{ + return new Anim.Animation(anim_func, start_value, end_value, time, end_callback); +} diff --git a/share/vis/extern/BrowserLib/Core/Code/Bind.js b/share/vis/extern/BrowserLib/Core/Code/Bind.js new file mode 100644 index 0000000..102ee26 --- /dev/null +++ b/share/vis/extern/BrowserLib/Core/Code/Bind.js @@ -0,0 +1,92 @@ +// +// This will generate a closure for the given function and optionally bind an arbitrary number of +// its initial arguments to specific values. +// +// Parameters: +// +// 0: Either the function scope or the function. +// 1: If 0 is the function scope, this is the function. +// Otherwise it's the start of the optional bound argument list. +// 2: Start of the optional bound argument list if 1 is the function. +// +// Examples: +// +// function GlobalFunction(p0, p1, p2) { } +// function ThisFunction(p0, p1, p2) { } +// +// var a = Bind("GlobalFunction"); +// var b = Bind(this, "ThisFunction"); +// var c = Bind("GlobalFunction", BoundParam0, BoundParam1); +// var d = Bind(this, "ThisFunction", BoundParam0, BoundParam1); +// var e = Bind(GlobalFunction); +// var f = Bind(this, ThisFunction); +// var g = Bind(GlobalFunction, BoundParam0, BoundParam1); +// var h = Bind(this, ThisFunction, BoundParam0, BoundParam1); +// +// a(0, 1, 2); +// b(0, 1, 2); +// c(2); +// d(2); +// e(0, 1, 2); +// f(0, 1, 2); +// g(2); +// h(2); +// +function Bind() +{ + // No closure to define? + if (arguments.length == 0) + return null; + + // Figure out which of the 4 call types is being used to bind + // Locate scope, function and bound parameter start index + + if (typeof(arguments[0]) == "string") + { + var scope = window; + var func = window[arguments[0]]; + var start = 1; + } + + else if (typeof(arguments[0]) == "function") + { + var scope = window; + var func = arguments[0]; + var start = 1; + } + + else if (typeof(arguments[1]) == "string") + { + var scope = arguments[0]; + var func = scope[arguments[1]]; + var start = 2; + } + + else if (typeof(arguments[1]) == "function") + { + var scope = arguments[0]; + var func = arguments[1]; + var start = 2; + } + + else + { + // unknown + console.log("Bind() ERROR: Unknown bind parameter configuration"); + return; + } + + // Convert the arguments list to an array + var arg_array = Array.prototype.slice.call(arguments, start); + start = arg_array.length; + + return function() + { + // Concatenate incoming arguments + for (var i = 0; i < arguments.length; i++) + arg_array[start + i] = arguments[i]; + + // Call the function in the given scope with the new arguments + return func.apply(scope, arg_array); + } +} diff --git a/share/vis/extern/BrowserLib/Core/Code/Convert.js b/share/vis/extern/BrowserLib/Core/Code/Convert.js new file mode 100644 index 0000000..b1f5461 --- /dev/null +++ b/share/vis/extern/BrowserLib/Core/Code/Convert.js @@ -0,0 +1,218 @@ + +namespace("Convert"); + + +// +// Convert between utf8 and b64 without raising character out of range exceptions with unicode strings +// Technique described here: http://monsur.hossa.in/2012/07/20/utf-8-in-javascript.html +// +Convert.utf8string_to_b64string = function(str) +{ + return btoa(unescape(encodeURIComponent(str))); +} +Convert.b64string_to_utf8string = function(str) +{ + return decodeURIComponent(escape(atob(str))); +} + + +// +// More general approach, converting between byte arrays and b64 +// Info here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding +// +Convert.b64string_to_Uint8Array = function(sBase64, nBlocksSize) +{ + function b64ToUint6 (nChr) + { + return nChr > 64 && nChr < 91 ? + nChr - 65 + : nChr > 96 && nChr < 123 ? + nChr - 71 + : nChr > 47 && nChr < 58 ? + nChr + 4 + : nChr === 43 ? + 62 + : nChr === 47 ? + 63 + : + 0; + } + + var + sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), + nInLen = sB64Enc.length, + nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2, + taBytes = new Uint8Array(nOutLen); + + for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) + { + nMod4 = nInIdx & 3; + nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4; + if (nMod4 === 3 || nInLen - nInIdx === 1) + { + for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) + taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; + nUint24 = 0; + } + } + + return taBytes; +} +Convert.Uint8Array_to_b64string = function(aBytes) +{ + function uint6ToB64 (nUint6) + { + return nUint6 < 26 ? + nUint6 + 65 + : nUint6 < 52 ? + nUint6 + 71 + : nUint6 < 62 ? + nUint6 - 4 + : nUint6 === 62 ? + 43 + : nUint6 === 63 ? + 47 + : + 65; + } + + var nMod3, sB64Enc = ""; + + for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) + { + nMod3 = nIdx % 3; + if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) + sB64Enc += "\r\n"; + nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24); + if (nMod3 === 2 || aBytes.length - nIdx === 1) + { + sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63)); + nUint24 = 0; + } + } + + return sB64Enc.replace(/A(?=A$|$)/g, "="); +} + + +// +// Unicode and arbitrary value safe conversion between strings and Uint8Arrays +// +Convert.Uint8Array_to_string = function(aBytes) +{ + var sView = ""; + + for (var nPart, nLen = aBytes.length, nIdx = 0; nIdx < nLen; nIdx++) + { + nPart = aBytes[nIdx]; + sView += String.fromCharCode( + nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? /* six bytes */ + /* (nPart - 252 << 32) is not possible in ECMAScript! So...: */ + (nPart - 252) * 1073741824 + (aBytes[++nIdx] - 128 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 + : nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? /* five bytes */ + (nPart - 248 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 + : nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? /* four bytes */ + (nPart - 240 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 + : nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? /* three bytes */ + (nPart - 224 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 + : nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? /* two bytes */ + (nPart - 192 << 6) + aBytes[++nIdx] - 128 + : /* nPart < 127 ? */ /* one byte */ + nPart + ); + } + + return sView; +} +Convert.string_to_Uint8Array = function(sDOMStr) +{ + var aBytes, nChr, nStrLen = sDOMStr.length, nArrLen = 0; + + /* mapping... */ + + for (var nMapIdx = 0; nMapIdx < nStrLen; nMapIdx++) + { + nChr = sDOMStr.charCodeAt(nMapIdx); + nArrLen += nChr < 0x80 ? 1 : nChr < 0x800 ? 2 : nChr < 0x10000 ? 3 : nChr < 0x200000 ? 4 : nChr < 0x4000000 ? 5 : 6; + } + + aBytes = new Uint8Array(nArrLen); + + /* transcription... */ + + for (var nIdx = 0, nChrIdx = 0; nIdx < nArrLen; nChrIdx++) + { + nChr = sDOMStr.charCodeAt(nChrIdx); + if (nChr < 128) + { + /* one byte */ + aBytes[nIdx++] = nChr; + } + else if (nChr < 0x800) + { + /* two bytes */ + aBytes[nIdx++] = 192 + (nChr >>> 6); + aBytes[nIdx++] = 128 + (nChr & 63); + } + else if (nChr < 0x10000) + { + /* three bytes */ + aBytes[nIdx++] = 224 + (nChr >>> 12); + aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); + aBytes[nIdx++] = 128 + (nChr & 63); + } + else if (nChr < 0x200000) + { + /* four bytes */ + aBytes[nIdx++] = 240 + (nChr >>> 18); + aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); + aBytes[nIdx++] = 128 + (nChr & 63); + } + else if (nChr < 0x4000000) + { + /* five bytes */ + aBytes[nIdx++] = 248 + (nChr >>> 24); + aBytes[nIdx++] = 128 + (nChr >>> 18 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); + aBytes[nIdx++] = 128 + (nChr & 63); + } + else /* if (nChr <= 0x7fffffff) */ + { + /* six bytes */ + aBytes[nIdx++] = 252 + /* (nChr >>> 32) is not possible in ECMAScript! So...: */ (nChr / 1073741824); + aBytes[nIdx++] = 128 + (nChr >>> 24 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 18 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); + aBytes[nIdx++] = 128 + (nChr & 63); + } + } + + return aBytes; +} + + +// +// Converts all characters in a string that have equivalent entities to their ampersand/entity names. +// Based on https://gist.github.com/jonathantneal/6093551 +// +Convert.string_to_html_entities = (function() +{ + 'use strict'; + + var data = '34quot38amp39apos60lt62gt160nbsp161iexcl162cent163pound164curren165yen166brvbar167sect168uml169copy170ordf171laquo172not173shy174reg175macr176deg177plusmn178sup2179sup3180acute181micro182para183middot184cedil185sup1186ordm187raquo188frac14189frac12190frac34191iquest192Agrave193Aacute194Acirc195Atilde196Auml197Aring198AElig199Ccedil200Egrave201Eacute202Ecirc203Euml204Igrave205Iacute206Icirc207Iuml208ETH209Ntilde210Ograve211Oacute212Ocirc213Otilde214Ouml215times216Oslash217Ugrave218Uacute219Ucirc220Uuml221Yacute222THORN223szlig224agrave225aacute226acirc227atilde228auml229aring230aelig231ccedil232egrave233eacute234ecirc235euml236igrave237iacute238icirc239iuml240eth241ntilde242ograve243oacute244ocirc245otilde246ouml247divide248oslash249ugrave250uacute251ucirc252uuml253yacute254thorn255yuml402fnof913Alpha914Beta915Gamma916Delta917Epsilon918Zeta919Eta920Theta921Iota922Kappa923Lambda924Mu925Nu926Xi927Omicron928Pi929Rho931Sigma932Tau933Upsilon934Phi935Chi936Psi937Omega945alpha946beta947gamma948delta949epsilon950zeta951eta952theta953iota954kappa955lambda956mu957nu958xi959omicron960pi961rho962sigmaf963sigma964tau965upsilon966phi967chi968psi969omega977thetasym978upsih982piv8226bull8230hellip8242prime8243Prime8254oline8260frasl8472weierp8465image8476real8482trade8501alefsym8592larr8593uarr8594rarr8595darr8596harr8629crarr8656lArr8657uArr8658rArr8659dArr8660hArr8704forall8706part8707exist8709empty8711nabla8712isin8713notin8715ni8719prod8721sum8722minus8727lowast8730radic8733prop8734infin8736ang8743and8744or8745cap8746cup8747int8756there48764sim8773cong8776asymp8800ne8801equiv8804le8805ge8834sub8835sup8836nsub8838sube8839supe8853oplus8855otimes8869perp8901sdot8968lceil8969rceil8970lfloor8971rfloor9001lang9002rang9674loz9824spades9827clubs9829hearts9830diams338OElig339oelig352Scaron353scaron376Yuml710circ732tilde8194ensp8195emsp8201thinsp8204zwnj8205zwj8206lrm8207rlm8211ndash8212mdash8216lsquo8217rsquo8218sbquo8220ldquo8221rdquo8222bdquo8224dagger8225Dagger8240permil8249lsaquo8250rsaquo8364euro'; + var charCodes = data.split(/[A-z]+/); + var entities = data.split(/\d+/).slice(1); + + return function encodeHTMLEntities(text) + { + return text.replace(/[\u00A0-\u2666<>"'&]/g, function (match) + { + var charCode = String(match.charCodeAt(0)); + var index = charCodes.indexOf(charCode); + return '&' + (entities[index] ? entities[index] : '#' + charCode) + ';'; + }); + }; +})(); diff --git a/share/vis/extern/BrowserLib/Core/Code/Core.js b/share/vis/extern/BrowserLib/Core/Code/Core.js new file mode 100644 index 0000000..542f54f --- /dev/null +++ b/share/vis/extern/BrowserLib/Core/Code/Core.js @@ -0,0 +1,26 @@ + +// TODO: requires function for checking existence of dependencies + + +function namespace(name) +{ + // Ensure all nested namespaces are created only once + + var ns_list = name.split("."); + var parent_ns = window; + + for (var i in ns_list) + { + var ns_name = ns_list[i]; + if (!(ns_name in parent_ns)) + parent_ns[ns_name] = { }; + + parent_ns = parent_ns[ns_name]; + } +} + + +function multiline(fn) +{ + return fn.toString().split(/\n/).slice(1, -1).join("\n"); +} diff --git a/share/vis/extern/BrowserLib/Core/Code/DOM.js b/share/vis/extern/BrowserLib/Core/Code/DOM.js new file mode 100644 index 0000000..f11fb19 --- /dev/null +++ b/share/vis/extern/BrowserLib/Core/Code/DOM.js @@ -0,0 +1,499 @@ + +namespace("DOM.Node"); +namespace("DOM.Event"); +namespace("DOM.Applet"); + + + +// +// ===================================================================================================================== +// ----- DOCUMENT NODE/ELEMENT EXTENSIONS ------------------------------------------------------------------------------ +// ===================================================================================================================== +// + + + +DOM.Node.Get = function(id) +{ + return document.getElementById(id); +} + + +// +// Set node position +// +DOM.Node.SetPosition = function(node, position) +{ + node.style.left = position[0]; + node.style.top = position[1]; +} +DOM.Node.SetX = function(node, x) +{ + node.style.left = x; +} +DOM.Node.SetY = function(node, y) +{ + node.style.top = y; +} + + +// +// Get the absolute position of a HTML element on the page +// +DOM.Node.GetPosition = function(element, account_for_scroll) +{ + // Recurse up through parents, summing offsets from their parent + var x = 0, y = 0; + for (var node = element; node != null; node = node.offsetParent) + { + x += node.offsetLeft; + y += node.offsetTop; + } + + if (account_for_scroll) + { + // Walk up the hierarchy subtracting away any scrolling + for (var node = element; node != document.body; node = node.parentNode) + { + x -= node.scrollLeft; + y -= node.scrollTop; + } + } + + return [x, y]; +} + + +// +// Set node size +// +DOM.Node.SetSize = function(node, size) +{ + node.style.width = size[0]; + node.style.height = size[1]; +} +DOM.Node.SetWidth = function(node, width) +{ + node.style.width = width; +} +DOM.Node.SetHeight = function(node, height) +{ + node.style.height = height; +} + + +// +// Get node OFFSET size: +// clientX includes padding +// offsetX includes padding and borders +// scrollX includes padding, borders and size of contained node +// +DOM.Node.GetSize = function(node) +{ + return [ node.offsetWidth, node.offsetHeight ]; +} +DOM.Node.GetWidth = function(node) +{ + return node.offsetWidth; +} +DOM.Node.GetHeight = function(node) +{ + return node.offsetHeight; +} + + +// +// Set node opacity +// +DOM.Node.SetOpacity = function(node, value) +{ + node.style.opacity = value; +} + + +DOM.Node.SetColour = function(node, colour) +{ + node.style.color = colour; +} + + +// +// Hide a node by completely disabling its rendering (it no longer contributes to document layout) +// +DOM.Node.Hide = function(node) +{ + node.style.display = "none"; +} + + +// +// Show a node by restoring its influcen in document layout +// +DOM.Node.Show = function(node) +{ + node.style.display = "block"; +} + + +// +// Add a CSS class to a HTML element, specified last +// +DOM.Node.AddClass = function(node, class_name) +{ + // Ensure the class hasn't already been added + DOM.Node.RemoveClass(node, class_name); + node.className += " " + class_name; +} + + +// +// Remove a CSS class from a HTML element +// +DOM.Node.RemoveClass = function(node, class_name) +{ + // Remove all variations of where the class name can be in the string list + var regexp = new RegExp("\\b" + class_name + "\\b"); + node.className = node.className.replace(regexp, ""); +} + + + +// +// Check to see if a HTML element contains a class +// +DOM.Node.HasClass = function(node, class_name) +{ + var regexp = new RegExp("\\b" + class_name + "\\b"); + return regexp.test(node.className); +} + + +// +// Recursively search for a node with the given class name +// +DOM.Node.FindWithClass = function(parent_node, class_name, index) +{ + // Search the children looking for a node with the given class name + for (var i in parent_node.childNodes) + { + var node = parent_node.childNodes[i]; + if (DOM.Node.HasClass(node, class_name)) + { + if (index === undefined || index-- == 0) + return node; + } + + // Recurse into children + node = DOM.Node.FindWithClass(node, class_name); + if (node != null) + return node; + } + + return null; +} + + +// +// Check to see if one node logically contains another +// +DOM.Node.Contains = function(node, container_node) +{ + while (node != null && node != container_node) + node = node.parentNode; + return node != null; +} + + +// +// Create the HTML nodes specified in the text passed in +// Assumes there is only one root node in the text +// +DOM.Node.CreateHTML = function(html) +{ + var div = document.createElement("div"); + div.innerHTML = html; + + // First child may be a text node, followed by the created HTML + var child = div.firstChild; + if (child != null && child.nodeType == 3) + child = child.nextSibling; + return child; +} + + +// +// Make a copy of a HTML element, making it visible and clearing its ID to ensure it's not a duplicate +// +DOM.Node.Clone = function(name) +{ + // Get the template element and clone it, making sure it's renderable + var node = DOM.Node.Get(name); + node = node.cloneNode(true); + node.id = null; + node.style.display = "block"; + return node; +} + + +// +// Append an arbitrary block of HTML to an existing node +// +DOM.Node.AppendHTML = function(node, html) +{ + var child = DOM.Node.CreateHTML(html); + node.appendChild(child); + return child; +} + + +// +// Append a div that clears the float style +// +DOM.Node.AppendClearFloat = function(node) +{ + var div = document.createElement("div"); + div.style.clear = "both"; + node.appendChild(div); +} + + +// +// Check to see that the object passed in is an instance of a DOM node +// +DOM.Node.IsNode = function(object) +{ + return object instanceof Element; +} + + +// +// Create an "iframe shim" so that elements within it render over a Java Applet +// http://web.archive.org/web/20110707212850/http://www.oratransplant.nl/2007/10/26/using-iframe-shim-to-partly-cover-a-java-applet/ +// +DOM.Node.CreateShim = function(parent) +{ + var shimmer = document.createElement("iframe"); + + // Position the shimmer so that it's the same location/size as its parent + shimmer.style.position = "fixed"; + shimmer.style.left = parent.style.left; + shimmer.style.top = parent.style.top; + shimmer.style.width = parent.offsetWidth; + shimmer.style.height = parent.offsetHeight; + + // We want the shimmer to be one level below its contents + shimmer.style.zIndex = parent.style.zIndex - 1; + + // Ensure its empty + shimmer.setAttribute("frameborder", "0"); + shimmer.setAttribute("src", ""); + + // Add to the document and the parent + document.body.appendChild(shimmer); + parent.Shimmer = shimmer; + return shimmer; +} + + + +// +// ===================================================================================================================== +// ----- EVENT HANDLING EXTENSIONS ------------------------------------------------------------------------------------- +// ===================================================================================================================== +// + + + +// +// Retrieves the event from the first parameter passed into an HTML event +// +DOM.Event.Get = function(evt) +{ + // Internet explorer doesn't pass the event + return window.event || evt; +} + + +// +// Retrieves the element that triggered an event from the event object +// +DOM.Event.GetNode = function(evt) +{ + evt = DOM.Event.Get(evt); + + // Get the target element + var element; + if (evt.target) + element = evt.target; + else if (e.srcElement) + element = evt.srcElement; + + // Default Safari bug + if (element.nodeType == 3) + element = element.parentNode; + + return element; +} + + +// +// Stop default action for an event +// +DOM.Event.StopDefaultAction = function(evt) +{ + if (evt && evt.preventDefault) + evt.preventDefault(); + else if (window.event && window.event.returnValue) + window.event.returnValue = false; +} + + +// +// Stops events bubbling up to parent event handlers +// +DOM.Event.StopPropagation = function(evt) +{ + evt = DOM.Event.Get(evt); + if (evt) + { + evt.cancelBubble = true; + if (evt.stopPropagation) + evt.stopPropagation(); + } +} + + +// +// Stop both event default action and propagation +// +DOM.Event.StopAll = function(evt) +{ + DOM.Event.StopDefaultAction(evt); + DOM.Event.StopPropagation(evt); +} + + +// +// Adds an event handler to an event +// +DOM.Event.AddHandler = function(obj, evt, func) +{ + if (obj) + { + if (obj.addEventListener) + obj.addEventListener(evt, func, false); + else if (obj.attachEvent) + obj.attachEvent("on" + evt, func); + } +} + + +// +// Removes an event handler from an event +// +DOM.Event.RemoveHandler = function(obj, evt, func) +{ + if (obj) + { + if (obj.removeEventListener) + obj.removeEventListener(evt, func, false); + else if (obj.detachEvent) + obj.detachEvent("on" + evt, func); + } +} + + +// +// Get the position of the mouse cursor, page relative +// +DOM.Event.GetMousePosition = function(evt) +{ + evt = DOM.Event.Get(evt); + + var px = 0; + var py = 0; + if (evt.pageX || evt.pageY) + { + px = evt.pageX; + py = evt.pageY; + } + else if (evt.clientX || evt.clientY) + { + px = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + py = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; + } + + return [px, py]; +} + + + +// +// ===================================================================================================================== +// ----- JAVA APPLET EXTENSIONS ---------------------------------------------------------------------------------------- +// ===================================================================================================================== +// + + + +// +// Create an applet element for loading a Java applet, attaching it to the specified node +// +DOM.Applet.Load = function(dest_id, id, code, archive) +{ + // Lookup the applet destination + var dest = DOM.Node.Get(dest_id); + if (!dest) + return; + + // Construct the applet element and add it to the destination + Debug.Log("Injecting applet DOM code"); + var applet = ""; + applet += ""; + dest.innerHTML = applet; +} + + +// +// Moves and resizes a named applet so that it fits in the destination div element. +// The applet must be contained by a div element itself. This container div is moved along +// with the applet. +// +DOM.Applet.Move = function(dest_div, applet, z_index, hide) +{ + if (!applet || !dest_div) + return; + + // Before modifying any location information, hide the applet so that it doesn't render over + // any newly visible elements that appear while the location information is being modified. + if (hide) + applet.style.visibility = "hidden"; + + // Get its view rect + var pos = DOM.Node.GetPosition(dest_div); + var w = dest_div.offsetWidth; + var h = dest_div.offsetHeight; + + // It needs to be embedded in a
for correct scale/position adjustment + var container = applet.parentNode; + if (!container || container.localName != "div") + { + Debug.Log("ERROR: Couldn't find source applet's div container"); + return; + } + + // Reposition and resize the containing div element + container.style.left = pos[0]; + container.style.top = pos[1]; + container.style.width = w; + container.style.height = h; + container.style.zIndex = z_index; + + // Resize the applet itself + applet.style.width = w; + applet.style.height = h; + + // Everything modified, safe to show + applet.style.visibility = "visible"; +} diff --git a/share/vis/extern/BrowserLib/Core/Code/Keyboard.js b/share/vis/extern/BrowserLib/Core/Code/Keyboard.js new file mode 100644 index 0000000..f70f4ea --- /dev/null +++ b/share/vis/extern/BrowserLib/Core/Code/Keyboard.js @@ -0,0 +1,149 @@ + +namespace("Keyboard") + + +// ===================================================================================================================== +// Key codes copied from closure-library +// https://code.google.com/p/closure-library/source/browse/closure/goog/events/keycodes.js +// --------------------------------------------------------------------------------------------------------------------- +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +Keyboard.Codes = { + WIN_KEY_FF_LINUX : 0, + MAC_ENTER : 3, + BACKSPACE : 8, + TAB : 9, + NUM_CENTER : 12, // NUMLOCK on FF/Safari Mac + ENTER : 13, + SHIFT : 16, + CTRL : 17, + ALT : 18, + PAUSE : 19, + CAPS_LOCK : 20, + ESC : 27, + SPACE : 32, + PAGE_UP : 33, // also NUM_NORTH_EAST + PAGE_DOWN : 34, // also NUM_SOUTH_EAST + END : 35, // also NUM_SOUTH_WEST + HOME : 36, // also NUM_NORTH_WEST + LEFT : 37, // also NUM_WEST + UP : 38, // also NUM_NORTH + RIGHT : 39, // also NUM_EAST + DOWN : 40, // also NUM_SOUTH + PRINT_SCREEN : 44, + INSERT : 45, // also NUM_INSERT + DELETE : 46, // also NUM_DELETE + ZERO : 48, + ONE : 49, + TWO : 50, + THREE : 51, + FOUR : 52, + FIVE : 53, + SIX : 54, + SEVEN : 55, + EIGHT : 56, + NINE : 57, + FF_SEMICOLON : 59, // Firefox (Gecko) fires this for semicolon instead of 186 + FF_EQUALS : 61, // Firefox (Gecko) fires this for equals instead of 187 + FF_DASH : 173, // Firefox (Gecko) fires this for dash instead of 189 + QUESTION_MARK : 63, // needs localization + A : 65, + B : 66, + C : 67, + D : 68, + E : 69, + F : 70, + G : 71, + H : 72, + I : 73, + J : 74, + K : 75, + L : 76, + M : 77, + N : 78, + O : 79, + P : 80, + Q : 81, + R : 82, + S : 83, + T : 84, + U : 85, + V : 86, + W : 87, + X : 88, + Y : 89, + Z : 90, + META : 91, // WIN_KEY_LEFT + WIN_KEY_RIGHT : 92, + CONTEXT_MENU : 93, + NUM_ZERO : 96, + NUM_ONE : 97, + NUM_TWO : 98, + NUM_THREE : 99, + NUM_FOUR : 100, + NUM_FIVE : 101, + NUM_SIX : 102, + NUM_SEVEN : 103, + NUM_EIGHT : 104, + NUM_NINE : 105, + NUM_MULTIPLY : 106, + NUM_PLUS : 107, + NUM_MINUS : 109, + NUM_PERIOD : 110, + NUM_DIVISION : 111, + F1 : 112, + F2 : 113, + F3 : 114, + F4 : 115, + F5 : 116, + F6 : 117, + F7 : 118, + F8 : 119, + F9 : 120, + F10 : 121, + F11 : 122, + F12 : 123, + NUMLOCK : 144, + SCROLL_LOCK : 145, + + // OS-specific media keys like volume controls and browser controls. + FIRST_MEDIA_KEY : 166, + LAST_MEDIA_KEY : 183, + + SEMICOLON : 186, // needs localization + DASH : 189, // needs localization + EQUALS : 187, // needs localization + COMMA : 188, // needs localization + PERIOD : 190, // needs localization + SLASH : 191, // needs localization + APOSTROPHE : 192, // needs localization + TILDE : 192, // needs localization + SINGLE_QUOTE : 222, // needs localization + OPEN_SQUARE_BRACKET : 219, // needs localization + BACKSLASH : 220, // needs localization + CLOSE_SQUARE_BRACKET: 221, // needs localization + WIN_KEY : 224, + MAC_FF_META : 224, // Firefox (Gecko) fires this for the meta key instead of 91 + MAC_WK_CMD_LEFT : 91, // WebKit Left Command key fired, same as META + MAC_WK_CMD_RIGHT : 93, // WebKit Right Command key fired, different from META + WIN_IME : 229, + + // We've seen users whose machines fire this keycode at regular one + // second intervals. The common thread among these users is that + // they're all using Dell Inspiron laptops, so we suspect that this + // indicates a hardware/bios problem. + // http://en.community.dell.com/support-forums/laptop/f/3518/p/19285957/19523128.aspx + PHANTOM : 255 +}; +// ===================================================================================================================== diff --git a/share/vis/extern/BrowserLib/Core/Code/LocalStore.js b/share/vis/extern/BrowserLib/Core/Code/LocalStore.js new file mode 100644 index 0000000..7bb8481 --- /dev/null +++ b/share/vis/extern/BrowserLib/Core/Code/LocalStore.js @@ -0,0 +1,40 @@ + +namespace("LocalStore"); + + +LocalStore.Set = function(class_name, class_id, variable_id, data) +{ + try + { + if (typeof(Storage) != "undefined") + { + var name = class_name + "_" + class_id + "_" + variable_id; + localStorage[name] = JSON.stringify(data); + } + } + catch (e) + { + console.log("Local Storage Set Error: " + e.message); + } +} + + +LocalStore.Get = function(class_name, class_id, variable_id, default_data) +{ + try + { + if (typeof(Storage) != "undefined") + { + var name = class_name + "_" + class_id + "_" + variable_id; + var data = localStorage[name] + if (data) + return JSON.parse(data); + } + } + catch (e) + { + console.log("Local Storage Get Error: " + e.message); + } + + return default_data; +} \ No newline at end of file diff --git a/share/vis/extern/BrowserLib/Core/Code/Mouse.js b/share/vis/extern/BrowserLib/Core/Code/Mouse.js new file mode 100644 index 0000000..a694b80 --- /dev/null +++ b/share/vis/extern/BrowserLib/Core/Code/Mouse.js @@ -0,0 +1,83 @@ + +namespace("Mouse"); + + +Mouse.State =(function() +{ + function State(event) + { + // Get button press states + if (typeof event.buttons != "undefined") + { + // Firefox + this.Left = (event.buttons & 1) != 0; + this.Right = (event.buttons & 2) != 0; + this.Middle = (event.buttons & 4) != 0; + } + else + { + // Chrome + this.Left = (event.button == 0); + this.Middle = (event.button == 1); + this.Right = (event.button == 2); + } + + // Get page-relative mouse position + this.Position = DOM.Event.GetMousePosition(event); + + // Get wheel delta + var delta = 0; + if (event.wheelDelta) + delta = event.wheelDelta / 120; // IE/Opera + else if (event.detail) + delta = -event.detail / 3; // Mozilla + this.WheelDelta = delta; + + // Get the mouse position delta + // Requires Pointer Lock API support + this.PositionDelta = [ + event.movementX || event.mozMovementX || event.webkitMovementX || 0, + event.movementY || event.mozMovementY || event.webkitMovementY || 0 + ]; + } + + return State; +})(); + + +// +// Basic Pointer Lock API support +// https://developer.mozilla.org/en-US/docs/WebAPI/Pointer_Lock +// http://www.chromium.org/developers/design-documents/mouse-lock +// +// Note that API has not been standardised yet so browsers can implement functions with prefixes +// + + +Mouse.PointerLockSupported = function() +{ + return 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document; +} + + +Mouse.RequestPointerLock = function(element) +{ + element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock; + if (element.requestPointerLock) + element.requestPointerLock(); +} + + +Mouse.ExitPointerLock = function() +{ + document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock || document.webkitExitPointerLock; + if (document.exitPointerLock) + document.exitPointerLock(); +} + + +// Can use this element to detect whether pointer lock is enabled (returns non-null) +Mouse.PointerLockElement = function() +{ + return document.pointerLockElement || document.mozPointerLockElement || document.webkitPointerLockElement; +} diff --git a/share/vis/extern/BrowserLib/Core/Code/MurmurHash3.js b/share/vis/extern/BrowserLib/Core/Code/MurmurHash3.js new file mode 100644 index 0000000..c423d49 --- /dev/null +++ b/share/vis/extern/BrowserLib/Core/Code/MurmurHash3.js @@ -0,0 +1,68 @@ + +namespace("Hash"); + +/** + * JS Implementation of MurmurHash3 (r136) (as of May 20, 2011) + * + * @author Gary Court + * @see http://github.com/garycourt/murmurhash-js + * @author Austin Appleby + * @see http://sites.google.com/site/murmurhash/ + * + * @param {string} key ASCII only + * @param {number} seed Positive integer only + * @return {number} 32-bit positive integer hash + */ + +Hash.Murmur3 = function(key, seed) +{ + var remainder, bytes, h1, h1b, c1, c1b, c2, c2b, k1, i; + + remainder = key.length & 3; // key.length % 4 + bytes = key.length - remainder; + h1 = seed; + c1 = 0xcc9e2d51; + c2 = 0x1b873593; + i = 0; + + while (i < bytes) { + k1 = + ((key.charCodeAt(i) & 0xff)) | + ((key.charCodeAt(++i) & 0xff) << 8) | + ((key.charCodeAt(++i) & 0xff) << 16) | + ((key.charCodeAt(++i) & 0xff) << 24); + ++i; + + k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff; + k1 = (k1 << 15) | (k1 >>> 17); + k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff; + + h1 ^= k1; + h1 = (h1 << 13) | (h1 >>> 19); + h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff; + h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16)); + } + + k1 = 0; + + switch (remainder) { + case 3: k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16; + case 2: k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8; + case 1: k1 ^= (key.charCodeAt(i) & 0xff); + + k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff; + k1 = (k1 << 15) | (k1 >>> 17); + k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff; + h1 ^= k1; + } + + h1 ^= key.length; + + h1 ^= h1 >>> 16; + h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff; + h1 ^= h1 >>> 13; + h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff; + h1 ^= h1 >>> 16; + + return h1 >>> 0; +} \ No newline at end of file diff --git a/share/vis/extern/BrowserLib/WindowManager/Code/Button.js b/share/vis/extern/BrowserLib/WindowManager/Code/Button.js new file mode 100644 index 0000000..12e0981 --- /dev/null +++ b/share/vis/extern/BrowserLib/WindowManager/Code/Button.js @@ -0,0 +1,131 @@ + +namespace("WM"); + + +WM.Button = (function() +{ + var template_html = "
"; + + + function Button(text, x, y, opts) + { + this.OnClick = null; + this.Toggle = opts && opts.toggle; + + this.Node = DOM.Node.CreateHTML(template_html); + + // Set node dimensions + this.SetPosition(x, y); + if (opts && opts.w && opts.h) + this.SetSize(opts.w, opts.h); + + // Override the default class name + if (opts && opts.class) + this.Node.className = opts.class; + + this.SetText(text); + + // Create the mouse press event handlers + DOM.Event.AddHandler(this.Node, "mousedown", Bind(OnMouseDown, this)); + this.OnMouseOutDelegate = Bind(OnMouseUp, this, false); + this.OnMouseUpDelegate = Bind(OnMouseUp, this, true); + } + + + Button.prototype.SetPosition = function(x, y) + { + this.Position = [ x, y ]; + DOM.Node.SetPosition(this.Node, this.Position); + } + + + Button.prototype.SetSize = function(w, h) + { + this.Size = [ w, h ]; + DOM.Node.SetSize(this.Node, this.Size); + } + + + Button.prototype.SetText = function(text) + { + this.Node.innerHTML = text; + } + + + Button.prototype.SetOnClick = function(on_click) + { + this.OnClick = on_click; + } + + + Button.prototype.SetState = function(pressed) + { + if (pressed) + DOM.Node.AddClass(this.Node, "ButtonHeld"); + else + DOM.Node.RemoveClass(this.Node, "ButtonHeld"); + } + + + Button.prototype.ToggleState = function() + { + if (DOM.Node.HasClass(this.Node, "ButtonHeld")) + this.SetState(false); + else + this.SetState(true); + } + + + Button.prototype.IsPressed = function() + { + return DOM.Node.HasClass(this.Node, "ButtonHeld"); + } + + + function OnMouseDown(self, evt) + { + // Decide how to set the button state + if (self.Toggle) + self.ToggleState(); + else + self.SetState(true); + + // Activate release handlers + DOM.Event.AddHandler(self.Node, "mouseout", self.OnMouseOutDelegate); + DOM.Event.AddHandler(self.Node, "mouseup", self.OnMouseUpDelegate); + + DOM.Event.StopAll(evt); + } + + + function OnMouseUp(self, confirm, evt) + { + if (confirm) + { + // Only release for non-toggles + if (!self.Toggle) + self.SetState(false); + } + else + { + // Decide how to set the button state + if (self.Toggle) + self.ToggleState(); + else + self.SetState(false); + } + + // Remove release handlers + DOM.Event.RemoveHandler(self.Node, "mouseout", self.OnMouseOutDelegate); + DOM.Event.RemoveHandler(self.Node, "mouseup", self.OnMouseUpDelegate); + + // Call the click handler if this is a button press + if (confirm && self.OnClick) + self.OnClick(self); + + DOM.Event.StopAll(evt); + } + + + return Button; +})(); \ No newline at end of file diff --git a/share/vis/extern/BrowserLib/WindowManager/Code/ComboBox.js b/share/vis/extern/BrowserLib/WindowManager/Code/ComboBox.js new file mode 100644 index 0000000..d199b3a --- /dev/null +++ b/share/vis/extern/BrowserLib/WindowManager/Code/ComboBox.js @@ -0,0 +1,237 @@ + +namespace("WM"); + + +WM.ComboBoxPopup = (function() +{ + var body_template_html = "
"; + + var item_template_html = " \ +
\ +
\ +
\ +
\ +
"; + + + function ComboBoxPopup(combo_box) + { + this.ComboBox = combo_box; + this.ParentNode = combo_box.Node; + this.ValueNodes = [ ]; + + // Create the template node + this.Node = DOM.Node.CreateHTML(body_template_html); + + DOM.Event.AddHandler(this.Node, "mousedown", Bind(SelectItem, this)); + this.CancelDelegate = Bind(this, "Cancel"); + } + + + ComboBoxPopup.prototype.SetValues = function(values) + { + // Clear existing values + this.Node.innerHTML = ""; + + // Generate HTML nodes for each value + this.ValueNodes = [ ]; + for (var i in values) + { + var item_node = DOM.Node.CreateHTML(item_template_html); + var text_node = DOM.Node.FindWithClass(item_node, "ComboBoxPopupItemText"); + + item_node.Value = values[i]; + text_node.innerHTML = values[i]; + + this.Node.appendChild(item_node); + this.ValueNodes.push(item_node); + } + } + + + ComboBoxPopup.prototype.Show = function(selection_index) + { + // Initially match the position of the parent node + var pos = DOM.Node.GetPosition(this.ParentNode); + DOM.Node.SetPosition(this.Node, pos); + + // Take the width/z-index from the parent node + this.Node.style.width = this.ParentNode.offsetWidth; + this.Node.style.zIndex = this.ParentNode.style.zIndex + 1; + + // Setup event handlers + DOM.Event.AddHandler(document.body, "mousedown", this.CancelDelegate); + + // Show the popup so that the HTML layout engine kicks in before + // the layout info is used below + this.ParentNode.appendChild(this.Node); + + // Show/hide the tick image based on which node is selected + for (var i in this.ValueNodes) + { + var node = this.ValueNodes[i]; + var icon_node = DOM.Node.FindWithClass(node, "ComboBoxPopupItemIcon"); + + if (i == selection_index) + { + icon_node.style.display = "block"; + + // Also, shift the popup up so that the mouse is over the selected item and is highlighted + var item_pos = DOM.Node.GetPosition(this.ValueNodes[selection_index]); + var diff_pos = [ item_pos[0] - pos[0], item_pos[1] - pos[1] ]; + pos = [ pos[0] - diff_pos[0], pos[1] - diff_pos[1] ]; + } + else + { + icon_node.style.display = "none"; + } + } + + DOM.Node.SetPosition(this.Node, pos); + } + + + ComboBoxPopup.prototype.Hide = function() + { + DOM.Event.RemoveHandler(document.body, "mousedown", this.CancelDelegate); + this.ParentNode.removeChild(this.Node); + } + + + function SelectItem(self, evt) + { + // Search for which item node is being clicked on + var node = DOM.Event.GetNode(evt); + for (var i in self.ValueNodes) + { + var value_node = self.ValueNodes[i]; + if (DOM.Node.Contains(node, value_node)) + { + // Set the value on the combo box + self.ComboBox.SetValue(value_node.Value); + self.Hide(); + break; + } + } + } + + + function Cancel(self, evt) + { + // Don't cancel if the mouse up is anywhere on the popup or combo box + var node = DOM.Event.GetNode(evt); + if (!DOM.Node.Contains(node, self.Node) && + !DOM.Node.Contains(node, self.ParentNode)) + { + self.Hide(); + } + + + DOM.Event.StopAll(evt); + } + + + return ComboBoxPopup; +})(); + + +WM.ComboBox = (function() +{ + var template_html = " \ +
\ +
\ +
\ +
\ +
"; + + + function ComboBox() + { + this.OnChange = null; + + // Create the template node and locate key nodes + this.Node = DOM.Node.CreateHTML(template_html); + this.TextNode = DOM.Node.FindWithClass(this.Node, "ComboBoxText"); + + // Create a reusable popup + this.Popup = new WM.ComboBoxPopup(this); + + // Set an empty set of values + this.SetValues([]); + this.SetValue("<empty>"); + + // Create the mouse press event handlers + DOM.Event.AddHandler(this.Node, "mousedown", Bind(OnMouseDown, this)); + this.OnMouseOutDelegate = Bind(OnMouseUp, this, false); + this.OnMouseUpDelegate = Bind(OnMouseUp, this, true); + } + + + ComboBox.prototype.SetOnChange = function(on_change) + { + this.OnChange = on_change; + } + + + ComboBox.prototype.SetValues = function(values) + { + this.Values = values; + this.Popup.SetValues(values); + } + + + ComboBox.prototype.SetValue = function(value) + { + // Set the value and its HTML rep + var old_value = this.Value; + this.Value = value; + this.TextNode.innerHTML = value; + + // Call change handler + if (this.OnChange) + this.OnChange(value, old_value); + } + + + ComboBox.prototype.GetValue = function() + { + return this.Value; + } + + + function OnMouseDown(self, evt) + { + // If this check isn't made, the click will trigger from the popup, too + var node = DOM.Event.GetNode(evt); + if (DOM.Node.Contains(node, self.Node)) + { + // Add the depression class and activate release handlers + DOM.Node.AddClass(self.Node, "ComboBoxPressed"); + DOM.Event.AddHandler(self.Node, "mouseout", self.OnMouseOutDelegate); + DOM.Event.AddHandler(self.Node, "mouseup", self.OnMouseUpDelegate); + + DOM.Event.StopAll(evt); + } + } + + + function OnMouseUp(self, confirm, evt) + { + // Remove depression class and remove release handlers + DOM.Node.RemoveClass(self.Node, "ComboBoxPressed"); + DOM.Event.RemoveHandler(self.Node, "mouseout", self.OnMouseOutDelegate); + DOM.Event.RemoveHandler(self.Node, "mouseup", self.OnMouseUpDelegate); + + // If this is a confirmed press and there are some values in the list, show the popup + if (confirm && self.Values.length > 0) + { + var selection_index = self.Values.indexOf(self.Value); + self.Popup.Show(selection_index); + } + + DOM.Event.StopAll(evt); + } + + + return ComboBox; +})(); diff --git a/share/vis/extern/BrowserLib/WindowManager/Code/Container.js b/share/vis/extern/BrowserLib/WindowManager/Code/Container.js new file mode 100644 index 0000000..2ba1388 --- /dev/null +++ b/share/vis/extern/BrowserLib/WindowManager/Code/Container.js @@ -0,0 +1,34 @@ + +namespace("WM"); + + +WM.Container = (function() +{ + var template_html = "
"; + + + function Container(x, y, w, h) + { + // Create a simple container node + this.Node = DOM.Node.CreateHTML(template_html); + this.SetPosition(x, y); + this.SetSize(w, h); + } + + + Container.prototype.SetPosition = function(x, y) + { + this.Position = [ x, y ]; + DOM.Node.SetPosition(this.Node, this.Position); + } + + + Container.prototype.SetSize = function(w, h) + { + this.Size = [ w, h ]; + DOM.Node.SetSize(this.Node, this.Size); + } + + + return Container; +})(); \ No newline at end of file diff --git a/share/vis/extern/BrowserLib/WindowManager/Code/EditBox.js b/share/vis/extern/BrowserLib/WindowManager/Code/EditBox.js new file mode 100644 index 0000000..fd0a039 --- /dev/null +++ b/share/vis/extern/BrowserLib/WindowManager/Code/EditBox.js @@ -0,0 +1,119 @@ + +namespace("WM"); + + +WM.EditBox = (function() +{ + var template_html = " \ +
\ +
Label
\ + \ +
"; + + + function EditBox(x, y, w, h, label, text) + { + this.ChangeHandler = null; + + // Create node and locate its internal nodes + this.Node = DOM.Node.CreateHTML(template_html); + this.LabelNode = DOM.Node.FindWithClass(this.Node, "EditBoxLabel"); + this.EditNode = DOM.Node.FindWithClass(this.Node, "EditBox"); + + // Set label and value + this.LabelNode.innerHTML = label; + this.SetValue(text); + + this.SetPosition(x, y); + this.SetSize(w, h); + + this.PreviousValue = ""; + + // Hook up the event handlers + DOM.Event.AddHandler(this.EditNode, "focus", Bind(OnFocus, this)); + DOM.Event.AddHandler(this.EditNode, "keypress", Bind(OnKeyPress, this)); + DOM.Event.AddHandler(this.EditNode, "keydown", Bind(OnKeyDown, this)); + } + + + EditBox.prototype.SetPosition = function(x, y) + { + this.Position = [ x, y ]; + DOM.Node.SetPosition(this.Node, this.Position); + } + + + EditBox.prototype.SetSize = function(w, h) + { + this.Size = [ w, h ]; + DOM.Node.SetSize(this.EditNode, this.Size); + } + + + EditBox.prototype.SetChangeHandler = function(handler) + { + this.ChangeHandler = handler; + } + + + EditBox.prototype.SetValue = function(value) + { + if (this.EditNode) + this.EditNode.value = value; + } + + + EditBox.prototype.GetValue = function() + { + if (this.EditNode) + return this.EditNode.value; + + return null; + } + + + EditBox.prototype.LoseFocus = function() + { + if (this.EditNode) + this.EditNode.blur(); + } + + + function OnFocus(self, evt) + { + // Backup on focus + self.PreviousValue = self.EditNode.value; + } + + + function OnKeyPress(self, evt) + { + // Allow enter to confirm the text only when there's data + if (evt.keyCode == 13 && self.EditNode.value != "" && self.ChangeHandler) + { + var focus = self.ChangeHandler(self.EditNode); + if (!focus) + self.EditNode.blur(); + self.PreviousValue = ""; + } + } + + + function OnKeyDown(self, evt) + { + // Allow escape to cancel any text changes + if (evt.keyCode == 27) + { + // On initial edit of the input, escape should NOT replace with the empty string + if (self.PreviousValue != "") + { + self.EditNode.value = self.PreviousValue; + } + + self.EditNode.blur(); + } + } + + + return EditBox; +})(); diff --git a/share/vis/extern/BrowserLib/WindowManager/Code/Grid.js b/share/vis/extern/BrowserLib/WindowManager/Code/Grid.js new file mode 100644 index 0000000..bb72858 --- /dev/null +++ b/share/vis/extern/BrowserLib/WindowManager/Code/Grid.js @@ -0,0 +1,248 @@ + +namespace("WM"); + + +WM.GridRows = (function() +{ + function GridRows(parent_object) + { + this.ParentObject = parent_object; + + // Array of rows in the order they were added + this.Rows = [ ]; + + // Collection of custom row indexes for fast lookup + this.Indexes = { }; + } + + + GridRows.prototype.AddIndex = function(cell_field_name) + { + var index = { }; + + // Go through existing rows and add to the index + for (var i in this.Rows) + { + var row = this.Rows[i]; + if (cell_field_name in row.CellData) + { + var cell_field = row.CellData[cell_field_name]; + index[cell_field] = row; + } + } + + this.Indexes[cell_field_name] = index; + } + + + GridRows.prototype.ClearIndex = function(index_name) + { + this.Indexes[index_name] = { }; + } + + GridRows.prototype.AddRowToIndex = function(index_name, cell_data, row) + { + this.Indexes[index_name][cell_data] = row; + } + + + GridRows.prototype.Add = function(cell_data, row_classes, cell_classes) + { + var row = new WM.GridRow(this.ParentObject, cell_data, row_classes, cell_classes); + this.Rows.push(row); + return row; + } + + + GridRows.prototype.GetBy = function(cell_field_name, cell_data) + { + var index = this.Indexes[cell_field_name]; + return index[cell_data]; + } + + + GridRows.prototype.Clear = function() + { + // Remove all node references from the parent + for (var i in this.Rows) + { + var row = this.Rows[i]; + row.Parent.BodyNode.removeChild(row.Node); + } + + // Clear all indexes + for (var i in this.Indexes) + this.Indexes[i] = { }; + + this.Rows = [ ]; + } + + + return GridRows; +})(); + + +WM.GridRow = (function() +{ + var template_html = "
"; + + + // + // 'cell_data' is an object with a variable number of fields. + // Any fields prefixed with an underscore are hidden. + // + function GridRow(parent, cell_data, row_classes, cell_classes) + { + // Setup data + this.Parent = parent; + this.IsOpen = true; + this.AnimHandle = null; + this.Rows = new WM.GridRows(this); + this.CellData = cell_data; + this.CellNodes = { } + + // Create the main row node + this.Node = DOM.Node.CreateHTML(template_html); + if (row_classes) + DOM.Node.AddClass(this.Node, row_classes); + + // Embed a pointer to the row in the root node so that it can be clicked + this.Node.GridRow = this; + + // Create nodes for each required cell + for (var attr in this.CellData) + { + if (this.CellData.hasOwnProperty(attr)) + { + var data = this.CellData[attr]; + + // Update any grid row index references + if (attr in parent.Rows.Indexes) + parent.Rows.AddRowToIndex(attr, data, this); + + // Hide any cells with underscore prefixes + if (attr[0] == "_") + continue; + + // Create a node for the cell and add any custom classes + var node = DOM.Node.AppendHTML(this.Node, "
"); + if (cell_classes && attr in cell_classes) + DOM.Node.AddClass(node, cell_classes[attr]); + this.CellNodes[attr] = node; + + // If this is a Window Control, add its node to the cell + if (data instanceof Object && "Node" in data && DOM.Node.IsNode(data.Node)) + { + data.ParentNode = node; + node.appendChild(data.Node); + } + + else + { + // Otherwise just assign the data as text + node.innerHTML = data; + } + } + } + + // Add the body node for any children + if (!this.Parent.BodyNode) + this.Parent.BodyNode = DOM.Node.AppendHTML(this.Parent.Node, "
"); + + // Add the row to the parent + this.Parent.BodyNode.appendChild(this.Node); + } + + + GridRow.prototype.Open = function() + { + // Don't allow open while animating + if (this.AnimHandle == null || this.AnimHandle.Complete) + { + this.IsOpen = true; + + // Kick off open animation + var node = this.BodyNode; + this.AnimHandle = Anim.Animate( + function (val) { DOM.Node.SetHeight(node, val) }, + 0, this.Height, 0.2); + } + } + + + GridRow.prototype.Close = function() + { + // Don't allow close while animating + if (this.AnimHandle == null || this.AnimHandle.Complete) + { + this.IsOpen = false; + + // Record height for the next open request + this.Height = this.BodyNode.offsetHeight; + + // Kick off close animation + var node = this.BodyNode; + this.AnimHandle = Anim.Animate( + function (val) { DOM.Node.SetHeight(node, val) }, + this.Height, 0, 0.2); + } + } + + + GridRow.prototype.Toggle = function() + { + if (this.IsOpen) + this.Close(); + else + this.Open(); + } + + + return GridRow; +})(); + + +WM.Grid = (function() +{ + var template_html = " \ +
\ +
\ +
"; + + + function Grid() + { + this.Rows = new WM.GridRows(this); + + this.Node = DOM.Node.CreateHTML(template_html); + this.BodyNode = DOM.Node.FindWithClass(this.Node, "GridBody"); + + DOM.Event.AddHandler(this.Node, "dblclick", OnDblClick); + + var mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel"; + DOM.Event.AddHandler(this.Node, mouse_wheel_event, Bind(OnMouseScroll, this)); + } + + function OnDblClick(evt) + { + // Clicked on a header? + var node = DOM.Event.GetNode(evt); + if (DOM.Node.HasClass(node, "GridRowName")) + { + // Toggle rows open/close + var row = node.parentNode.GridRow; + if (row) + row.Toggle(); + } + } + + + function OnMouseScroll(self, evt) + { + var mouse_state = new Mouse.State(evt); + self.Node.scrollTop -= mouse_state.WheelDelta * 20; + } + + + return Grid; +})(); diff --git a/share/vis/extern/BrowserLib/WindowManager/Code/Label.js b/share/vis/extern/BrowserLib/WindowManager/Code/Label.js new file mode 100644 index 0000000..9b1d852 --- /dev/null +++ b/share/vis/extern/BrowserLib/WindowManager/Code/Label.js @@ -0,0 +1,31 @@ + +namespace("WM"); + + +WM.Label = (function() +{ + var template_html = "
"; + + + function Label(x, y, text) + { + // Create the node + this.Node = DOM.Node.CreateHTML(template_html); + + // Allow position to be optional + if (x != null && y != null) + DOM.Node.SetPosition(this.Node, [x, y]); + + this.SetText(text); + } + + + Label.prototype.SetText = function(text) + { + if (text != null) + this.Node.innerHTML = text; + } + + + return Label; +})(); \ No newline at end of file diff --git a/share/vis/extern/BrowserLib/WindowManager/Code/Treeview.js b/share/vis/extern/BrowserLib/WindowManager/Code/Treeview.js new file mode 100644 index 0000000..66ef80e --- /dev/null +++ b/share/vis/extern/BrowserLib/WindowManager/Code/Treeview.js @@ -0,0 +1,352 @@ + +namespace("WM"); + + +WM.Treeview = (function() +{ + var Margin = 10; + + + var tree_template_html = " \ +
\ +
\ +
\ +
\ +
\ +
\ +
"; + + + var item_template_html = " \ +
\ + \ +
\ +
\ +
\ +
\ +
"; + + + // TODO: Remove parent_node (required for stuff that doesn't use the WM yet) + function Treeview(x, y, width, height, parent_node) + { + // Cache initialisation options + this.ParentNode = parent_node; + this.Position = [ x, y ]; + this.Size = [ width, height ]; + + this.Node = null; + this.ScrollbarNode = null; + this.SelectedItem = null; + this.ContentsNode = null; + + // Setup options + this.HighlightOnHover = false; + this.EnableScrollbar = true; + this.HorizontalLayoutDepth = 1; + + // Generate an empty tree + this.Clear(); + } + + + Treeview.prototype.SetHighlightOnHover = function(highlight) + { + this.HighlightOnHover = highlight; + } + + + Treeview.prototype.SetEnableScrollbar = function(enable) + { + this.EnableScrollbar = enable; + } + + + Treeview.prototype.SetHorizontalLayoutDepth = function(depth) + { + this.HorizontalLayoutDepth = depth; + } + + + Treeview.prototype.SetNodeSelectedHandler = function(handler) + { + this.NodeSelectedHandler = handler; + } + + + Treeview.prototype.Clear = function() + { + this.RootItem = new WM.TreeviewItem(this, null, null, null, null); + this.GenerateHTML(); + } + + + Treeview.prototype.Root = function() + { + return this.RootItem; + } + + + Treeview.prototype.ClearSelection = function() + { + if (this.SelectedItem != null) + { + DOM.Node.RemoveClass(this.SelectedItem.Node, "TreeviewItemSelected"); + this.SelectedItem = null; + } + } + + + Treeview.prototype.SelectItem = function(item, mouse_pos) + { + // Notify the select handler + if (this.NodeSelectedHandler) + this.NodeSelectedHandler(item.Node, this.SelectedItem, item, mouse_pos); + + // Remove highlight from the old selection + this.ClearSelection(); + + // Swap in new selection and apply highlight + this.SelectedItem = item; + DOM.Node.AddClass(this.SelectedItem.Node, "TreeviewItemSelected"); + } + + + Treeview.prototype.GenerateHTML = function() + { + // Clone the template and locate important nodes + var old_node = this.Node; + this.Node = DOM.Node.CreateHTML(tree_template_html); + this.ChildrenNode = DOM.Node.FindWithClass(this.Node, "TreeviewItemChildren"); + this.ScrollbarNode = DOM.Node.FindWithClass(this.Node, "TreeviewScrollbar"); + + DOM.Node.SetPosition(this.Node, this.Position); + DOM.Node.SetSize(this.Node, this.Size); + + // Generate the contents of the treeview + GenerateTree(this, this.ChildrenNode, this.RootItem.Children, 0); + + // Cross-browser (?) means of adding a mouse wheel handler + var mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel"; + DOM.Event.AddHandler(this.Node, mouse_wheel_event, Bind(OnMouseScroll, this)); + + DOM.Event.AddHandler(this.Node, "dblclick", Bind(OnMouseDoubleClick, this)); + DOM.Event.AddHandler(this.Node, "mousedown", Bind(OnMouseDown, this)); + DOM.Event.AddHandler(this.Node, "mouseup", OnMouseUp); + + // Swap in the newly generated control node if it's already been attached to a parent + if (old_node && old_node.parentNode) + { + old_node.parentNode.removeChild(old_node); + this.ParentNode.appendChild(this.Node); + } + + if (this.EnableScrollbar) + { + this.UpdateScrollbar(); + DOM.Event.AddHandler(this.ScrollbarNode, "mousedown", Bind(OnMouseDown_Scrollbar, this)); + DOM.Event.AddHandler(this.ScrollbarNode, "mouseup", Bind(OnMouseUp_Scrollbar, this)); + DOM.Event.AddHandler(this.ScrollbarNode, "mouseout", Bind(OnMouseUp_Scrollbar, this)); + DOM.Event.AddHandler(this.ScrollbarNode, "mousemove", Bind(OnMouseMove_Scrollbar, this)); + } + + else + { + DOM.Node.Hide(DOM.Node.FindWithClass(this.Node, "TreeviewScrollbarInset")); + } + } + + + Treeview.prototype.UpdateScrollbar = function() + { + if (!this.EnableScrollbar) + return; + + var scrollbar_scale = Math.min((this.Node.offsetHeight - Margin * 2) / this.ChildrenNode.offsetHeight, 1); + this.ScrollbarNode.style.height = parseInt(scrollbar_scale * 100) + "%"; + + // Shift the scrollbar container along with the parent window + this.ScrollbarNode.parentNode.style.top = this.Node.scrollTop; + + var scroll_fraction = this.Node.scrollTop / (this.Node.scrollHeight - this.Node.offsetHeight); + var max_height = this.Node.offsetHeight - Margin; + var max_scrollbar_offset = max_height - this.ScrollbarNode.offsetHeight; + var scrollbar_offset = scroll_fraction * max_scrollbar_offset; + this.ScrollbarNode.style.top = scrollbar_offset; + } + + + function GenerateTree(self, parent_node, items, depth) + { + if (items.length == 0) + return null; + + for (var i in items) + { + var item = items[i]; + + // Create the node for this item and locate important nodes + var node = DOM.Node.CreateHTML(item_template_html); + var img = DOM.Node.FindWithClass(node, "TreeviewItemImage"); + var text = DOM.Node.FindWithClass(node, "TreeviewItemText"); + var children = DOM.Node.FindWithClass(node, "TreeviewItemChildren"); + + // Attach the item to the node + node.TreeviewItem = item; + item.Node = node; + + // Add the class which highlights selection on hover + if (self.HighlightOnHover) + DOM.Node.AddClass(node, "TreeviewItemHover"); + + // Instruct the children to wrap around + if (depth >= self.HorizontalLayoutDepth) + node.style.cssFloat = "left"; + + if (item.OpenImage == null || item.CloseImage == null) + { + // If there no images, remove the image node + node.removeChild(img); + } + else + { + // Set the image source to open + img.src = item.OpenImage.src; + img.style.width = item.OpenImage.width; + img.style.height = item.OpenImage.height; + item.ImageNode = img; + } + + // Setup the text to display + text.innerHTML = item.Label; + + // Add the div to the parent and recurse into children + parent_node.appendChild(node); + GenerateTree(self, children, item.Children, depth + 1); + item.ChildrenNode = children; + } + + // Clear the wrap-around + if (depth >= self.HorizontalLayoutDepth) + DOM.Node.AppendClearFloat(parent_node.parentNode); + } + + + function OnMouseScroll(self, evt) + { + // Get mouse wheel movement + var delta = evt.detail ? evt.detail * -1 : evt.wheelDelta; + delta *= 8; + + // Scroll the main window with wheel movement and clamp + self.Node.scrollTop -= delta; + self.Node.scrollTop = Math.min(self.Node.scrollTop, (self.ChildrenNode.offsetHeight - self.Node.offsetHeight) + Margin * 2); + + self.UpdateScrollbar(); + } + + + function OnMouseDoubleClick(self, evt) + { + DOM.Event.StopDefaultAction(evt); + + // Get the tree view item being clicked, if any + var node = DOM.Event.GetNode(evt); + var tvitem = GetTreeviewItemFromNode(self, node); + if (tvitem == null) + return; + + if (tvitem.Children.length) + tvitem.Toggle(); + } + + + function OnMouseDown(self, evt) + { + DOM.Event.StopDefaultAction(evt); + + // Get the tree view item being clicked, if any + var node = DOM.Event.GetNode(evt); + var tvitem = GetTreeviewItemFromNode(self, node); + if (tvitem == null) + return; + + // If clicking on the image, expand any children + if (node.tagName == "IMG" && tvitem.Children.length) + { + tvitem.Toggle(); + } + + else + { + var mouse_pos = DOM.Event.GetMousePosition(evt); + self.SelectItem(tvitem, mouse_pos); + } + } + + + function OnMouseUp(evt) + { + // Event handler used merely to stop events bubbling up to containers + DOM.Event.StopPropagation(evt); + } + + + function OnMouseDown_Scrollbar(self, evt) + { + self.ScrollbarHeld = true; + + // Cache the mouse height relative to the scrollbar + self.LastY = evt.clientY; + self.ScrollY = self.Node.scrollTop; + + DOM.Node.AddClass(self.ScrollbarNode, "TreeviewScrollbarHeld"); + DOM.Event.StopDefaultAction(evt); + } + + + function OnMouseUp_Scrollbar(self, evt) + { + self.ScrollbarHeld = false; + DOM.Node.RemoveClass(self.ScrollbarNode, "TreeviewScrollbarHeld"); + } + + + function OnMouseMove_Scrollbar(self, evt) + { + if (self.ScrollbarHeld) + { + var delta_y = evt.clientY - self.LastY; + self.LastY = evt.clientY; + + var max_height = self.Node.offsetHeight - Margin; + var max_scrollbar_offset = max_height - self.ScrollbarNode.offsetHeight; + var max_contents_scroll = self.Node.scrollHeight - self.Node.offsetHeight; + var scale = max_contents_scroll / max_scrollbar_offset; + + // Increment the local float variable and assign, as scrollTop is of type int + self.ScrollY += delta_y * scale; + self.Node.scrollTop = self.ScrollY; + self.Node.scrollTop = Math.min(self.Node.scrollTop, (self.ChildrenNode.offsetHeight - self.Node.offsetHeight) + Margin * 2); + + self.UpdateScrollbar(); + } + } + + + function GetTreeviewItemFromNode(self, node) + { + // Walk up toward the tree view node looking for this first item + while (node && node != self.Node) + { + if ("TreeviewItem" in node) + return node.TreeviewItem; + + node = node.parentNode; + } + + return null; + } + + return Treeview; +})(); diff --git a/share/vis/extern/BrowserLib/WindowManager/Code/TreeviewItem.js b/share/vis/extern/BrowserLib/WindowManager/Code/TreeviewItem.js new file mode 100644 index 0000000..ac6133e --- /dev/null +++ b/share/vis/extern/BrowserLib/WindowManager/Code/TreeviewItem.js @@ -0,0 +1,109 @@ + +namespace("WM"); + + +WM.TreeviewItem = (function() +{ + function TreeviewItem(treeview, name, data, open_image, close_image) + { + // Assign members + this.Treeview = treeview; + this.Label = name; + this.Data = data; + this.OpenImage = open_image; + this.CloseImage = close_image; + + this.Children = [ ]; + + // The HTML node wrapping the item and its children + this.Node = null; + + // The HTML node storing the image for the open/close state feedback + this.ImageNode = null; + + // The HTML node storing just the children + this.ChildrenNode = null; + + // Animation handle for opening and closing the child nodes, only used + // if the tree view item as children + this.AnimHandle = null; + + // Open state of the item + this.IsOpen = true; + } + + + TreeviewItem.prototype.AddItem = function(name, data, open_image, close_image) + { + var item = new WM.TreeviewItem(this.Treeview, name, data, open_image, close_image); + this.Children.push(item); + return item; + } + + + TreeviewItem.prototype.Open = function() + { + if (this.AnimHandle == null || this.AnimHandle.Complete) + { + // Swap to the open state + this.IsOpen = true; + if (this.ImageNode != null && this.OpenImage != null) + this.ImageNode.src = this.OpenImage.src; + + // Cache for closure binding + var child_node = this.ChildrenNode; + var end_height = this.StartHeight; + var treeview = this.Treeview; + + // Reveal the children and animate their height to max + this.ChildrenNode.style.display = "block"; + this.AnimHandle = Anim.Animate( + function (val) { DOM.Node.SetHeight(child_node, val) }, + 0, end_height, 0.2, + function() { treeview.UpdateScrollbar(); }); + + // Fade the children in + Anim.Animate(function(val) { DOM.Node.SetOpacity(child_node, val) }, 0, 1, 0.2); + } + } + + + TreeviewItem.prototype.Close = function() + { + if (this.AnimHandle == null || this.AnimHandle.Complete) + { + // Swap to the close state + this.IsOpen = false; + if (this.ImageNode != null && this.CloseImage != null) + this.ImageNode.src = this.CloseImage.src; + + // Cache for closure binding + var child_node = this.ChildrenNode; + var treeview = this.Treeview; + + // Mark the height of the item for reload later + this.StartHeight = child_node.offsetHeight; + + // Shrink the height of the children and hide them upon completion + this.AnimHandle = Anim.Animate( + function (val) { DOM.Node.SetHeight(child_node, val) }, + this.ChildrenNode.offsetHeight, 0, 0.2, + function() { child_node.style.display = "none"; treeview.UpdateScrollbar(); }); + + // Fade the children out + Anim.Animate(function(val) { DOM.Node.SetOpacity(child_node, val) }, 1, 0, 0.2); + } + } + + + TreeviewItem.prototype.Toggle = function() + { + if (this.IsOpen) + this.Close(); + else + this.Open(); + } + + + return TreeviewItem; +})(); diff --git a/share/vis/extern/BrowserLib/WindowManager/Code/Window.js b/share/vis/extern/BrowserLib/WindowManager/Code/Window.js new file mode 100644 index 0000000..810ac4d --- /dev/null +++ b/share/vis/extern/BrowserLib/WindowManager/Code/Window.js @@ -0,0 +1,295 @@ + +namespace("WM"); + + +WM.Window = (function() +{ + var template_html = multiline(function(){/* \ +
+
+
Window Title Bar
+
+
+
+
+
+
+ */}); + + + function Window(manager, title, x, y, width, height, parent_node) + { + this.Manager = manager; + this.ParentNode = parent_node || document.body; + this.OnMove = null; + this.OnResize = null; + this.Visible = false; + this.AnimatedShow = false; + + // Clone the window template and locate key nodes within it + this.Node = DOM.Node.CreateHTML(template_html); + this.TitleBarNode = DOM.Node.FindWithClass(this.Node, "WindowTitleBar"); + this.TitleBarTextNode = DOM.Node.FindWithClass(this.Node, "WindowTitleBarText"); + this.TitleBarCloseNode = DOM.Node.FindWithClass(this.Node, "WindowTitleBarClose"); + this.ResizeHandleNode = DOM.Node.FindWithClass(this.Node, "WindowResizeHandle"); + this.BodyNode = DOM.Node.FindWithClass(this.Node, "WindowBody"); + + // Setup the position and dimensions of the window + this.SetPosition(x, y); + this.SetSize(width, height); + + // Set the title text + this.TitleBarTextNode.innerHTML = title; + + // Hook up event handlers + DOM.Event.AddHandler(this.Node, "mousedown", Bind(this, "SetTop")); + DOM.Event.AddHandler(this.TitleBarNode, "mousedown", Bind(this, "BeginMove")); + DOM.Event.AddHandler(this.ResizeHandleNode, "mousedown", Bind(this, "BeginResize")); + DOM.Event.AddHandler(this.TitleBarCloseNode, "mouseup", Bind(this, "Hide")); + + // Create delegates for removable handlers + this.MoveDelegate = Bind(this, "Move"); + this.EndMoveDelegate = Bind(this, "EndMove") + this.ResizeDelegate = Bind(this, "Resize"); + this.EndResizeDelegate = Bind(this, "EndResize"); + } + + Window.prototype.SetOnMove = function(on_move) + { + this.OnMove = on_move; + } + + Window.prototype.SetOnResize = function(on_resize) + { + this.OnResize = on_resize; + } + + + Window.prototype.Show = function() + { + if (this.Node.parentNode != this.ParentNode) + { + this.ShowNoAnim(); + Anim.Animate(Bind(this, "OpenAnimation"), 0, 1, 1); + } + } + + + Window.prototype.ShowNoAnim = function() + { + // Add to the document + this.ParentNode.appendChild(this.Node); + this.AnimatedShow = false; + this.Visible = true; + } + + + Window.prototype.Hide = function(evt) + { + if (this.Node.parentNode == this.ParentNode && evt.button == 0) + { + if (this.AnimatedShow) + { + // Trigger animation that ends with removing the window from the document + Anim.Animate( + Bind(this, "CloseAnimation"), + 0, 1, 0.25, + Bind(this, "HideNoAnim")); + } + else + { + this.HideNoAnim(); + } + } + } + + + Window.prototype.HideNoAnim = function() + { + // Remove node + this.ParentNode.removeChild(this.Node); + this.Visible = false; + } + + + Window.prototype.SetTop = function() + { + this.Manager.SetTopWindow(this); + } + + + + Window.prototype.SetTitle = function(title) + { + this.TitleBarTextNode.innerHTML = title; + } + + + // TODO: Update this + Window.prototype.AddControl = function(control) + { + // Get all arguments to this function and replace the first with this window node + var args = [].slice.call(arguments); + args[0] = this.BodyNode; + + // Create the control and call its Init method with the modified arguments + var instance = new control(); + instance.Init.apply(instance, args); + + return instance; + } + + + Window.prototype.AddControlNew = function(control) + { + control.ParentNode = this.BodyNode; + this.BodyNode.appendChild(control.Node); + return control; + } + + + Window.prototype.Scale = function(t) + { + // Calculate window bounds centre/extents + var ext_x = this.Size[0] / 2; + var ext_y = this.Size[1] / 2; + var mid_x = this.Position[0] + ext_x; + var mid_y = this.Position[1] + ext_y; + + // Scale from the mid-point + DOM.Node.SetPosition(this.Node, [ mid_x - ext_x * t, mid_y - ext_y * t ]); + DOM.Node.SetSize(this.Node, [ this.Size[0] * t, this.Size[1] * t ]); + } + + + Window.prototype.OpenAnimation = function(val) + { + // Power ease in + var t = 1 - Math.pow(1 - val, 8); + this.Scale(t); + DOM.Node.SetOpacity(this.Node, 1 - Math.pow(1 - val, 8)); + this.AnimatedShow = true; + } + + + Window.prototype.CloseAnimation = function(val) + { + // Power ease out + var t = 1 - Math.pow(val, 4); + this.Scale(t); + DOM.Node.SetOpacity(this.Node, t); + } + + + Window.prototype.NotifyChange = function() + { + if (this.OnMove) + { + var pos = DOM.Node.GetPosition(this.Node); + this.OnMove(this, pos); + } + } + + + Window.prototype.BeginMove = function(evt) + { + // Calculate offset of the window from the mouse down position + var mouse_pos = DOM.Event.GetMousePosition(evt); + this.Offset = [ mouse_pos[0] - this.Position[0], mouse_pos[1] - this.Position[1] ]; + + // Dynamically add handlers for movement and release + DOM.Event.AddHandler(document, "mousemove", this.MoveDelegate); + DOM.Event.AddHandler(document, "mouseup", this.EndMoveDelegate); + + DOM.Event.StopDefaultAction(evt); + } + + + Window.prototype.Move = function(evt) + { + // Use the offset at the beginning of movement to drag the window around + var mouse_pos = DOM.Event.GetMousePosition(evt); + var offset = this.Offset; + var pos = [ mouse_pos[0] - offset[0], mouse_pos[1] - offset[1] ]; + this.SetPosition(pos[0], pos[1]); + + if (this.OnMove) + this.OnMove(this, pos); + + DOM.Event.StopDefaultAction(evt); + } + + + Window.prototype.EndMove = function(evt) + { + // Remove handlers added during mouse down + DOM.Event.RemoveHandler(document, "mousemove", this.MoveDelegate); + DOM.Event.RemoveHandler(document, "mouseup", this.EndMoveDelegate); + + DOM.Event.StopDefaultAction(evt); + } + + + Window.prototype.BeginResize = function(evt) + { + // Calculate offset of the window from the mouse down position + var mouse_pos = DOM.Event.GetMousePosition(evt); + this.MousePosBeforeResize = [ mouse_pos[0], mouse_pos[1] ]; + this.SizeBeforeResize = this.Size; + + // Dynamically add handlers for movement and release + DOM.Event.AddHandler(document, "mousemove", this.ResizeDelegate); + DOM.Event.AddHandler(document, "mouseup", this.EndResizeDelegate); + + DOM.Event.StopDefaultAction(evt); + } + + + Window.prototype.Resize = function(evt) + { + // Use the offset at the beginning of movement to drag the window around + var mouse_pos = DOM.Event.GetMousePosition(evt); + var offset = [ mouse_pos[0] - this.MousePosBeforeResize[0], mouse_pos[1] - this.MousePosBeforeResize[1] ]; + this.SetSize(this.SizeBeforeResize[0] + offset[0], this.SizeBeforeResize[1] + offset[1]); + + if (this.OnResize) + this.OnResize(this, this.Size); + + DOM.Event.StopDefaultAction(evt); + } + + + Window.prototype.EndResize = function(evt) + { + // Remove handlers added during mouse down + DOM.Event.RemoveHandler(document, "mousemove", this.ResizeDelegate); + DOM.Event.RemoveHandler(document, "mouseup", this.EndResizeDelegate); + + DOM.Event.StopDefaultAction(evt); + } + + + Window.prototype.SetPosition = function(x, y) + { + this.Position = [ x, y ]; + DOM.Node.SetPosition(this.Node, this.Position); + } + + + Window.prototype.SetSize = function(w, h) + { + w = Math.max(80, w); + h = Math.max(15, h); + this.Size = [ w, h ]; + DOM.Node.SetSize(this.Node, this.Size); + } + + + Window.prototype.GetZIndex = function() + { + return parseInt(this.Node.style.zIndex); + } + + + return Window; +})(); \ No newline at end of file diff --git a/share/vis/extern/BrowserLib/WindowManager/Code/WindowManager.js b/share/vis/extern/BrowserLib/WindowManager/Code/WindowManager.js new file mode 100644 index 0000000..bef33c2 --- /dev/null +++ b/share/vis/extern/BrowserLib/WindowManager/Code/WindowManager.js @@ -0,0 +1,54 @@ + +namespace("WM"); + + +WM.WindowManager = (function() +{ + function WindowManager() + { + // An empty list of windows under window manager control + this.Windows = [ ]; + } + + + WindowManager.prototype.AddWindow = function(title, x, y, width, height, parent_node) + { + // Create the window and add it to the list of windows + var wnd = new WM.Window(this, title, x, y, width, height, parent_node); + this.Windows.push(wnd); + + // Always bring to the top on creation + wnd.SetTop(); + + return wnd; + } + + + WindowManager.prototype.SetTopWindow = function(top_wnd) + { + // Bring the window to the top of the window list + var top_wnd_index = this.Windows.indexOf(top_wnd); + if (top_wnd_index != -1) + this.Windows.splice(top_wnd_index, 1); + this.Windows.push(top_wnd); + + // Set a CSS z-index for each visible window from the bottom up + for (var i in this.Windows) + { + var wnd = this.Windows[i]; + if (!wnd.Visible) + continue; + + // Ensure there's space between each window for the elements inside to be sorted + var z = (parseInt(i) + 1) * 10; + wnd.Node.style.zIndex = z; + + // Notify window that its z-order has changed + wnd.NotifyChange(); + } + } + + + return WindowManager; + +})(); \ No newline at end of file diff --git a/share/vis/extern/BrowserLib/WindowManager/Styles/WindowManager.css b/share/vis/extern/BrowserLib/WindowManager/Styles/WindowManager.css new file mode 100644 index 0000000..e367bc0 --- /dev/null +++ b/share/vis/extern/BrowserLib/WindowManager/Styles/WindowManager.css @@ -0,0 +1,652 @@ + + +.notextsel +{ + /* Disable text selection so that it doesn't interfere with button-clicking */ + user-select: none; + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer */ + -khtml-user-select: none; /* KHTML browsers (e.g. Konqueror) */ + -webkit-user-select: none; /* Chrome, Safari, and Opera */ + -webkit-touch-callout: none; /* Disable Android and iOS callouts*/ + + /* Stops the text cursor over the label */ + cursor:default; +} + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Window Styles */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +body +{ + /* Clip contents to browser window without adding scrollbars */ + overflow: hidden; +} + +.Window +{ + position:absolute; + + /* Clip all contents to the window border */ + overflow: hidden; + + background: #555; + + /*padding: 0px !important;*/ + + border-radius: 3px; + -moz-border-radius: 5px; + + -webkit-box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; + box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; +} + +/*:root +{ + --SideBarSize: 5px; +} + +.WindowBodyDebug +{ + color: #BBB; + font: 9px Verdana; + white-space: nowrap; +} + +.WindowSizeLeft +{ + position: absolute; + left: 0px; + top: 0px; + width: var(--SideBarSize); + height: 100%; +} +.WindowSizeRight +{ + position: absolute; + left: calc(100% - var(--SideBarSize)); + top:0px; + width: var(--SideBarSize); + height:100%; +} +.WindowSizeTop +{ + position: absolute; + left: 0px; + top: 0px; + width: 100%; + height: var(--SideBarSize); +} +.WindowSizeBottom +{ + position: absolute; + left: 0px; + top: calc(100% - var(--SideBarSize)); + width: 100%; + height: var(--SideBarSize); +}*/ + + +.Window_Transparent +{ + /* Set transparency changes to fade in/out */ + opacity: 0.5; + transition: opacity 0.5s ease-out; + -moz-transition: opacity 0.5s ease-out; + -webkit-transition: opacity 0.5s ease-out; +} + +.Window_Transparent:hover +{ + opacity: 1; +} + +.WindowTitleBar +{ + height: 17px; + cursor: move; + /*overflow: hidden;*/ + + border-bottom: 1px solid #303030; + border-radius: 5px; +} + +.WindowTitleBarText +{ + color: #BBB; + font: 9px Verdana; + /*white-space: nowrap;*/ + + padding: 3px; + cursor: move; +} + +.WindowTitleBarClose +{ + color: #999999; + font: 9px Verdana; + + padding: 3px; + cursor: default; +} + +.WindowTitleBarClose:hover { + color: #bbb; +} + +.WindowResizeHandle +{ + color: #999999; + font: 17px Verdana; + padding: 3px; + cursor: se-resize; + position: absolute; + bottom: -7px; + right: -3px; +} + +.WindowBody { + position: absolute; + /* overflow: hidden; */ + display: block; + padding: 10px; + border-top: 1px solid #606060; + top: 18px; + left: 0; + right: 0; + bottom: 0; + height: auto; +} + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Container Styles */ +/* ------------------------------------------------------------------------------------------------------------------ */ + + + +.Container +{ + /* Position relative to the parent window */ + position: absolute; + + /* Clip contents */ + /*overflow: hidden;*/ + + background:#2C2C2C; + + border: 1px black solid; + + /* Two inset box shadows to simulate depressing */ + -webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; + box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; +} + +/*.Panel +{*/ + /* Position relative to the parent window */ + /*position: absolute;*/ + + /* Clip contents */ + /*overflow: hidden; + + background:#2C2C2C; + + border: 1px black solid;*/ + + /* Two inset box shadows to simulate depressing */ + /*-webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; + box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset;*/ +/*}*/ + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Ruler Styles */ +/* ------------------------------------------------------------------------------------------------------------------ */ + + + +/*.Ruler +{ + position: absolute; + + border: dashed 1px; + + opacity: 0.35; +}*/ + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Treeview Styles */ +/* ------------------------------------------------------------------------------------------------------------------ */ + + + +.Treeview +{ + position: absolute; + + background:#2C2C2C; + border: 1px solid black; + overflow:hidden; + + /* Two inset box shadows to simulate depressing */ + -webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; + box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; +} + +.TreeviewItem +{ + margin:1px; + padding:2px; + border:solid 1px #2C2C2C; + background-color:#2C2C2C; +} + +.TreeviewItemImage +{ + float: left; +} + +.TreeviewItemText +{ + float: left; + margin-left:4px; +} + +.TreeviewItemChildren +{ + overflow: hidden; +} + +.TreeviewItemSelected +{ + background-color:#444; + border-color:#FFF; + + -webkit-transition: background-color 0.2s ease-in-out; + -moz-transition: background-color 0.2s ease-in-out; + -webkit-transition: border-color 0.2s ease-in-out; + -moz-transition: border-color 0.2s ease-in-out; +} + +/* Used to populate treeviews that want highlight on hover behaviour */ +.TreeviewItemHover +{ +} + +.TreeviewItemHover:hover +{ + background-color:#111; + border-color:#444; + + -webkit-transition: background-color 0.2s ease-in-out; + -moz-transition: background-color 0.2s ease-in-out; + -webkit-transition: border-color 0.2s ease-in-out; + -moz-transition: border-color 0.2s ease-in-out; +} + +.TreeviewScrollbarInset +{ + float: right; + + position:relative; + + height: 100%; + + /* CRAZINESS PART A: Trying to get the inset and scrollbar to have 100% height match its container */ + margin: -8px -8px 0 0; + padding: 0 1px 14px 1px; + + width:20px; + background:#2C2C2C; + border: 1px solid black; + + /* Two inset box shadows to simulate depressing */ + -webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; + box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; +} + +.TreeviewScrollbar +{ + position:relative; + + background:#2C2C2C; + border: 1px solid black; + + /* CRAZINESS PART B: Trying to get the inset and scrollbar to have 100% height match its container */ + padding: 0 0 10px 0; + margin: 1px 0 0 0; + + width: 18px; + height: 100%; + + border-radius:6px; + border-color:#000; + border-width:1px; + border-style:solid; + + /* The gradient for the button background */ + background-color:#666; + background: -webkit-gradient(linear, left top, left bottom, from(#666), to(#383838)); + background: -moz-linear-gradient(top, #666, #383838); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#666666', endColorstr='#383838'); + + /* A box shadow and inset box highlight */ + -webkit-box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; + box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; +} + +.TreeviewScrollbarHeld +{ + /* Reset the gradient to a full-colour background */ + background:#383838; + + /* Two inset box shadows to simulate depressing */ + -webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; + box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; +} + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Edit Box Styles */ +/* ------------------------------------------------------------------------------------------------------------------ */ + + + +.EditBoxContainer +{ + position: absolute; + padding:2px 10px 2px 10px; +} + +.EditBoxLabel +{ + float:left; + padding: 3px 4px 4px 4px; + font: 9px Verdana; +} + +.EditBox +{ + float:left; + + background:#666; + border: 1px solid; + border-radius: 6px; + padding: 3px 4px 3px 4px; + height: 20px; + + box-shadow: 1px 1px 1px #222 inset; + + transition: all 0.3s ease-in-out; +} + +.EditBox:focus +{ + background:#FFF; + outline:0; +} + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Label Styles */ +/* ------------------------------------------------------------------------------------------------------------------ */ + + + +.Label +{ + /* Position relative to the parent window */ + position:absolute; + + color: #BBB; + font: 9px Verdana; +} + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Combo Box Styles */ +/* ------------------------------------------------------------------------------------------------------------------ */ + + + +.ComboBox +{ + position:absolute; + + /* TEMP! */ + width:90px; + + /* Height is fixed to match the font */ + height:14px; + + /* Align the text within the combo box */ + padding: 1px 0 0 5px; + + /* Solid, rounded border */ + border: 1px solid #111; + border-radius: 5px; + + /* http://www.colorzilla.com/gradient-editor/#e3e3e3+0,c6c6c6+22,b7b7b7+33,afafaf+50,a7a7a7+67,797979+82,414141+100;Custom */ + background: #e3e3e3; + background: -moz-linear-gradient(top, #e3e3e3 0%, #c6c6c6 22%, #b7b7b7 33%, #afafaf 50%, #a7a7a7 67%, #797979 82%, #414141 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#e3e3e3), color-stop(22%,#c6c6c6), color-stop(33%,#b7b7b7), color-stop(50%,#afafaf), color-stop(67%,#a7a7a7), color-stop(82%,#797979), color-stop(100%,#414141)); + background: -webkit-linear-gradient(top, #e3e3e3 0%,#c6c6c6 22%,#b7b7b7 33%,#afafaf 50%,#a7a7a7 67%,#797979 82%,#414141 100%); + background: -o-linear-gradient(top, #e3e3e3 0%,#c6c6c6 22%,#b7b7b7 33%,#afafaf 50%,#a7a7a7 67%,#797979 82%,#414141 100%); + background: -ms-linear-gradient(top, #e3e3e3 0%,#c6c6c6 22%,#b7b7b7 33%,#afafaf 50%,#a7a7a7 67%,#797979 82%,#414141 100%); + background: linear-gradient(top, #e3e3e3 0%,#c6c6c6 22%,#b7b7b7 33%,#afafaf 50%,#a7a7a7 67%,#797979 82%,#414141 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e3e3e3', endColorstr='#414141',GradientType=0 ); +} + +.ComboBoxPressed +{ + /* The reverse of the default background, simulating depression */ + background: #414141; + background: -moz-linear-gradient(top, #414141 0%, #797979 18%, #a7a7a7 33%, #afafaf 50%, #b7b7b7 67%, #c6c6c6 78%, #e3e3e3 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#414141), color-stop(18%,#797979), color-stop(33%,#a7a7a7), color-stop(50%,#afafaf), color-stop(67%,#b7b7b7), color-stop(78%,#c6c6c6), color-stop(100%,#e3e3e3)); + background: -webkit-linear-gradient(top, #414141 0%,#797979 18%,#a7a7a7 33%,#afafaf 50%,#b7b7b7 67%,#c6c6c6 78%,#e3e3e3 100%); + background: -o-linear-gradient(top, #414141 0%,#797979 18%,#a7a7a7 33%,#afafaf 50%,#b7b7b7 67%,#c6c6c6 78%,#e3e3e3 100%); + background: -ms-linear-gradient(top, #414141 0%,#797979 18%,#a7a7a7 33%,#afafaf 50%,#b7b7b7 67%,#c6c6c6 78%,#e3e3e3 100%); + background: linear-gradient(top, #414141 0%,#797979 18%,#a7a7a7 33%,#afafaf 50%,#b7b7b7 67%,#c6c6c6 78%,#e3e3e3 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#414141', endColorstr='#e3e3e3',GradientType=0 ); +} + +.ComboBoxText +{ + /* Text info */ + color: #000; + font: 9px Verdana; + + float:left; +} + +.ComboBoxIcon +{ + /* Push the image to the far right */ + float:right; + + /* Align the image with the combo box */ + padding: 2px 5px 0 0; +} + +.ComboBoxPopup +{ + position: fixed; + + background: #CCC; + + border-radius: 5px; + + padding: 1px 0 1px 0; +} + +.ComboBoxPopupItem +{ + /* Text info */ + color: #000; + font: 9px Verdana; + + padding: 1px 1px 1px 5px; + + border-bottom: 1px solid #AAA; + border-top: 1px solid #FFF; +} + +.ComboBoxPopupItemText +{ + float:left; +} + +.ComboBoxPopupItemIcon +{ + /* Push the image to the far right */ + float:right; + + /* Align the image with the combo box */ + padding: 2px 5px 0 0; +} + +.ComboBoxPopupItem:first-child +{ + border-top: 0px; +} + +.ComboBoxPopupItem:last-child +{ + border-bottom: 0px; +} + +.ComboBoxPopupItem:hover +{ + color:#FFF; + background: #2036E1; +} + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Grid Styles */ +/* ------------------------------------------------------------------------------------------------------------------ */ + + +.Grid { + overflow: auto; + background: #333; + height: 100%; + border-radius: 2px; +} + +.GridBody +{ + overflow-x: auto; + overflow-y: auto; + height: inherit; +} + +.GridRow +{ + display: inline-block; + white-space: nowrap; + + background:#303030; + + color: #BBB; + font: 9px Verdana; + + padding: 2px; +} + +.GridRow.GridGroup +{ + padding: 0px; +} + +.GridRow:nth-child(odd) +{ + background:#333; +} + +.GridRowCell +{ + display: inline-block; +} +.GridRowCell.GridGroup +{ + color: #BBB; + + /* Override default from name */ + width: 100%; + + padding: 1px 1px 1px 2px; + border: 1px solid; + border-radius: 2px; + + border-top-color:#555; + border-left-color:#555; + border-bottom-color:#111; + border-right-color:#111; + + background: #222; +} + +.GridRowBody +{ + /* Clip all contents for show/hide group*/ + overflow: hidden; + + /* Crazy CSS rules: controls for properties don't clip if this isn't set on this parent */ + position: relative; +} + + + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Button Styles */ +/* ------------------------------------------------------------------------------------------------------------------ */ + + + +.Button +{ + /* Position relative to the parent window */ + position:absolute; + + border-radius:4px; + + /* Padding at the top includes 2px for the text drop-shadow */ + padding: 2px 5px 3px 5px; + + color: #BBB; + font: 9px Verdana; + text-shadow: 1px 1px 1px black; + text-align: center; + + background-color:#555; + + /* A box shadow and inset box highlight */ + -webkit-box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; + box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; +} + +.Button:hover { + background-color: #616161; +} + +.Button.ButtonHeld +{ + /* Reset the gradient to a full-colour background */ + background:#383838; + + /* Two inset box shadows to simulate depressing */ + -webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; + box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; +} diff --git a/share/vis/index.html b/share/vis/index.html new file mode 100644 index 0000000..3e57256 --- /dev/null +++ b/share/vis/index.html @@ -0,0 +1,55 @@ + + + + + + Remotery Viewer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Makefile b/src/Makefile index 9825b6a..c483c8d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -2,209 +2,212 @@ include ../config.mak vpath %.c $(SRC_PATH)/src -CFLAGS= $(OPTFLAGS) -I"$(SRC_PATH)/include" +CFLAGS=-I"$(SRC_PATH)/include" $(OPTFLAGS) -ifeq ($(DEBUGBUILD), yes) +ifeq ($(DEBUGBUILD),yes) CFLAGS+=-g LDFLAGS+=-g endif -ifeq ($(GPROFBUILD), yes) +ifeq ($(GPROFBUILD),yes) CFLAGS+=-pg LDFLAGS+=-pg endif ## libgpac objects gathering: src/utils -LIBGPAC_UTILS=utils/os_divers.o utils/os_file.o utils/list.o utils/bitstream.o utils/error.o utils/alloc.o utils/url.o utils/configfile.o -ifeq ($(DISABLE_CORE_TOOLS), no) -LIBGPAC_UTILS+=utils/sha1.o utils/base_encoding.o utils/os_net.o utils/os_thread.o utils/os_config_init.o utils/cache.o utils/downloader.o utils/xml_parser.o utils/utf.o utils/token.o utils/color.o +LIBGPAC_UTILS=utils/os_divers.o utils/os_file.o utils/list.o utils/bitstream.o utils/constants.o utils/error.o utils/alloc.o utils/url.o utils/configfile.o utils/gltools.o utils/gzio.o +ifeq ($(DISABLE_CORE_TOOLS),no) +LIBGPAC_UTILS+=utils/sha1.o utils/base_encoding.o utils/math.o utils/os_net.o utils/os_thread.o utils/os_config_init.o utils/cache.o utils/downloader.o utils/xml_parser.o utils/utf.o utils/token.o utils/color.o utils/Remotery.o endif -ifeq ($(DISABLE_PLAYER), no) -LIBGPAC_UTILS+=utils/os_module.o utils/math.o utils/path2d.o utils/path2d_stroker.o utils/module.o utils/uni_bidi.o utils/ringbuffer.o utils/unicode.o utils/map.o +ifeq ($(DISABLE_PLAYER),no) +LIBGPAC_UTILS+=utils/uni_bidi.o utils/unicode.o endif +LIBGPAC_UTILS+=utils/os_module.o utils/module.o utils/path2d.o utils/path2d_stroker.o ## libgpac objects gathering: src/ietf LIBGPAC_IETF= -ifeq ($(DISABLE_STREAMING), no) +ifeq ($(DISABLE_STREAMING),no) LIBGPAC_IETF=ietf/rtcp.o ietf/rtp.o ietf/rtp_packetizer.o ietf/rtp_pck_3gpp.o ietf/rtp_pck_mpeg12.o ietf/rtp_pck_mpeg4.o ietf/rtsp_command.o ietf/rtsp_common.o ietf/rtsp_response.o ietf/rtsp_session.o ietf/sdp.o ietf/rtp_depacketizer.o ietf/rtp_streamer.o endif ## libgpac objects gathering: src/bifs LIBGPAC_BIFS= -ifeq ($(DISABLE_BIFS), no) +ifeq ($(DISABLE_BIFS),no) LIBGPAC_BIFS=bifs/arith_decoder.o bifs/bifs_codec.o bifs/bifs_node_tables.o bifs/com_dec.o bifs/com_enc.o bifs/conditional.o bifs/field_decode.o bifs/field_encode.o bifs/memory_decoder.o bifs/predictive_mffield.o bifs/quantize.o bifs/script_dec.o bifs/script_enc.o bifs/unquantize.o endif ## libgpac objects gathering: src/isomedia -LIBGPAC_ISOM=isomedia/avc_ext.o isomedia/box_code_3gpp.o isomedia/box_code_apple.o isomedia/box_code_base.o isomedia/box_code_drm.o isomedia/box_code_meta.o isomedia/box_dump.o isomedia/box_funcs.o isomedia/data_map.o isomedia/drm_sample.o isomedia/isom_intern.o isomedia/isom_read.o isomedia/isom_store.o isomedia/isom_write.o isomedia/media.o isomedia/media_odf.o isomedia/meta.o isomedia/movie_fragments.o isomedia/sample_descs.o isomedia/stbl_read.o isomedia/stbl_write.o isomedia/track.o isomedia/tx3g.o isomedia/iff.o -ifeq ($(DISABLE_ISOFF_HINT), no) +LIBGPAC_ISOM=isomedia/avc_ext.o isomedia/box_code_3gpp.o isomedia/box_code_apple.o isomedia/box_code_base.o isomedia/box_code_drm.o isomedia/box_code_meta.o isomedia/box_dump.o isomedia/box_funcs.o isomedia/data_map.o isomedia/drm_sample.o isomedia/isom_intern.o isomedia/isom_read.o isomedia/isom_store.o isomedia/isom_write.o isomedia/media.o isomedia/media_odf.o isomedia/meta.o isomedia/movie_fragments.o isomedia/sample_descs.o isomedia/stbl_read.o isomedia/stbl_write.o isomedia/track.o isomedia/tx3g.o isomedia/iff.o +ifeq ($(DISABLE_ISOFF_HINT),no) LIBGPAC_ISOM+=isomedia/hint_track.o isomedia/hinting.o endif -ifeq ($(DISABLE_ISOM_ADOBE), no) +ifeq ($(DISABLE_ISOM_ADOBE),no) LIBGPAC_ISOM+=isomedia/box_code_adobe.o endif -ifeq ($(DISABLE_TTML), no) -LIBGPAC_ISOM+=isomedia/ttml.o -endif ## libgpac objects gathering: src/odf -LIBGPAC_ODF=odf/desc_private.o odf/descriptors.o odf/odf_code.o odf/odf_codec.o odf/odf_command.o odf/qos.o odf/slc.o -ifeq ($(MINIMAL_OD), no) -LIBGPAC_ODF+=odf/ipmpx_code.o odf/oci_codec.o +LIBGPAC_ODF=odf/desc_private.o odf/descriptors.o odf/odf_code.o odf/odf_codec.o odf/odf_command.o odf/qos.o odf/slc.o +ifeq ($(MINIMAL_OD),no) +LIBGPAC_ODF+=odf/ipmpx_code.o odf/oci_codec.o -ifeq ($(DISABLE_OD_DUMP), no) +ifeq ($(DISABLE_OD_DUMP),no) LIBGPAC_ODF+=odf/ipmpx_dump.o endif -ifeq ($(DISABLE_OD_PARSE), no) +ifeq ($(DISABLE_OD_PARSE),no) LIBGPAC_ODF+=odf/ipmpx_parse.o endif endif -ifeq ($(DISABLE_OD_DUMP), no) +ifeq ($(DISABLE_OD_DUMP),no) LIBGPAC_ODF+=odf/odf_dump.o endif -ifeq ($(DISABLE_OD_PARSE), no) +ifeq ($(DISABLE_OD_PARSE),no) LIBGPAC_ODF+=odf/odf_parse.o endif ## libgpac objects gathering: src/scenegraph -LIBGPAC_SCENE=scenegraph/base_scenegraph.o scenegraph/mpeg4_animators.o scenegraph/commands.o scenegraph/mpeg4_nodes.o scenegraph/mpeg4_valuator.o scenegraph/vrml_interpolators.o scenegraph/vrml_proto.o scenegraph/vrml_route.o scenegraph/vrml_script.o scenegraph/vrml_smjs.o scenegraph/vrml_tools.o scenegraph/x3d_nodes.o scenegraph/svg_attributes.o scenegraph/svg_types.o scenegraph/svg_smjs.o scenegraph/smil_anim.o scenegraph/smil_timing.o scenegraph/svg_properties.o scenegraph/dom_events.o scenegraph/dom_smjs.o scenegraph/xbl_process.o scenegraph/xml_ns.o scenegraph/html5_media_smjs.o scenegraph/html5_mse_smjs.o scenegraph/webvtt_smjs.o +LIBGPAC_SCENE=scenegraph/base_scenegraph.o scenegraph/mpeg4_animators.o scenegraph/commands.o scenegraph/mpeg4_nodes.o scenegraph/mpeg4_valuator.o scenegraph/vrml_interpolators.o scenegraph/vrml_proto.o scenegraph/vrml_route.o scenegraph/vrml_script.o scenegraph/vrml_js.o scenegraph/vrml_tools.o scenegraph/x3d_nodes.o scenegraph/svg_attributes.o scenegraph/svg_types.o scenegraph/svg_js.o scenegraph/smil_anim.o scenegraph/smil_timing.o scenegraph/svg_properties.o scenegraph/dom_events.o scenegraph/dom_js.o scenegraph/xml_ns.o -## libgpac objects gathering: src/mcrypt -LIBGPAC_MCRYPT= -ifeq ($(DISABLE_MCRYPT), no) -LIBGPAC_MCRYPT+=mcrypt/cbc.o mcrypt/cfb.o mcrypt/ctr.o mcrypt/des.o mcrypt/ecb.o mcrypt/g_crypt.o mcrypt/ncfb.o mcrypt/nofb.o mcrypt/ofb.o mcrypt/rijndael-128.o mcrypt/rijndael-192.o mcrypt/rijndael-256.o mcrypt/stream.o mcrypt/tripledes.o +## libgpac objects gathering: src/crypto +LIBGPAC_CRYPTO= +ifeq ($(DISABLE_CRYPTO),no) +LIBGPAC_CRYPTO+=crypto/g_crypt.o crypto/g_crypt_openssl.o crypto/g_crypt_tinyaes.o crypto/tiny_aes.o endif +LIBGPAC_EVG=evg/ftgrays.o evg/raster3d.o evg/raster_565.o evg/raster_argb.o evg/raster_rgb.o evg/raster_yuv.o evg/stencil.o evg/surface.o + ## libgpac objects gathering: src/media tools -LIBGPAC_MEDIATOOLS=media_tools/isom_tools.o media_tools/dash_segmenter.o media_tools/av_parsers.o -ifeq ($(DISABLE_AV_PARSERS), no) +LIBGPAC_MEDIATOOLS=media_tools/isom_tools.o media_tools/dash_segmenter.o media_tools/av_parsers.o media_tools/atsc_dmx.o + +ifeq ($(DISABLE_AV_PARSERS),no) LIBGPAC_MEDIATOOLS+=media_tools/img.o endif -ifeq ($(DISABLE_MEDIA_IMPORT), no) +ifeq ($(DISABLE_MEDIA_IMPORT),no) LIBGPAC_MEDIATOOLS+=media_tools/media_import.o endif -ifeq ($(DISABLE_M2TS), no) +ifeq ($(DISABLE_M2TS),no) LIBGPAC_MEDIATOOLS+=media_tools/mpegts.o endif -ifeq ($(DISABLE_MPD), no) +ifeq ($(DISABLE_MPD),no) LIBGPAC_MEDIATOOLS+=media_tools/m3u8.o media_tools/mpd.o endif -ifeq ($(DISABLE_DASH_CLIENT), no) -LIBGPAC_MEDIATOOLS+=media_tools/dash_client.o +ifeq ($(DISABLE_DASH_CLIENT),no) +LIBGPAC_MEDIATOOLS+=media_tools/dash_client.o endif -ifeq ($(DISABLE_MEDIA_EXPORT), no) +ifeq ($(DISABLE_MEDIA_EXPORT),no) LIBGPAC_MEDIATOOLS+=media_tools/media_export.o endif -ifeq ($(DISABLE_M2TS_MUX), no) +ifeq ($(DISABLE_M2TS_MUX),no) LIBGPAC_MEDIATOOLS+=media_tools/m2ts_mux.o endif -ifeq ($(DISABLE_STREAMING), no) -LIBGPAC_MEDIATOOLS+=media_tools/filestreamer.o -endif -ifeq ($(DISABLE_DVBX), no) +ifeq ($(DISABLE_DVBX),no) LIBGPAC_MEDIATOOLS+=media_tools/ait.o media_tools/dsmcc.o media_tools/dvb_mpe.o media_tools/reedsolomon.o endif -ifeq ($(DISABLE_AVILIB), no) +ifeq ($(DISABLE_AVILIB),no) LIBGPAC_MEDIATOOLS+=media_tools/avilib.o endif -ifeq ($(DISABLE_M2PS), no) +ifeq ($(DISABLE_M2PS),no) LIBGPAC_MEDIATOOLS+=media_tools/mpeg2_ps.o endif -ifeq ($(DISABLE_OGG), no) +ifeq ($(DISABLE_OGG),no) LIBGPAC_MEDIATOOLS+=media_tools/gpac_ogg.o endif -ifeq ($(DISABLE_MCRYPT), no) -LIBGPAC_MEDIATOOLS+=media_tools/ismacryp.o +ifeq ($(DISABLE_CRYPTO),no) +LIBGPAC_MEDIATOOLS+=media_tools/crypt_tools.o endif -ifeq ($(DISABLE_ISOFF_HINT), no) +ifeq ($(DISABLE_ISOFF_HINT),no) LIBGPAC_MEDIATOOLS+=media_tools/isom_hinter.o else -ifeq ($(DISABLE_STREAMING), no) +ifeq ($(DISABLE_STREAMING),no) LIBGPAC_MEDIATOOLS+=media_tools/isom_hinter.o endif endif -ifeq ($(DISABLE_SAF), no) +ifeq ($(DISABLE_SAF),no) LIBGPAC_MEDIATOOLS+=media_tools/saf.o endif -ifeq ($(DISABLE_VOBSUB), no) +ifeq ($(DISABLE_VOBSUB),no) LIBGPAC_MEDIATOOLS+=media_tools/vobsub.o endif -ifeq ($(DISABLE_TTXT), no) -LIBGPAC_MEDIATOOLS+=media_tools/text_import.o -endif - -ifeq ($(DISABLE_PLAYER), no) -LIBGPAC_MEDIATOOLS+=media_tools/html5_media.o media_tools/html5_mse.o -endif LIBGPAC_MEDIATOOLS+=media_tools/webvtt.o +LIBGPAC_FILTERS=filter_core/filter_pck.o filter_core/filter_pid.o filter_core/filter_props.o filter_core/filter_queue.o filter_core/filter_session.o filter_core/filter_register.o filter_core/filter.o filter_core/filter_session_js.o + +LIBGPAC_QUICKJS= +ifeq ($(CONFIG_JS),yes) +LIBGPAC_QUICKJS=quickjs/cutils.o quickjs/libbf.o quickjs/libregexp.o quickjs/libunicode.o quickjs/quickjs.o +LIBGPAC_QUICKJS+=jsmods/core.o jsmods/evg.o jsmods/scene_js.o jsmods/storage.o jsmods/webgl.o jsmods/xhr.o +endif + ## libgpac objects gathering: src/scene_manager LIBGPAC_SCENEMANAGER= -ifeq ($(DISABLE_SMGR), no) +ifeq ($(DISABLE_SMGR),no) LIBGPAC_SCENEMANAGER+=scene_manager/scene_manager.o scene_manager/text_to_bifs.o endif -ifeq ($(DISABLE_LOADER_BT), no) +ifeq ($(DISABLE_LOADER_BT),no) LIBGPAC_SCENEMANAGER+=scene_manager/loader_bt.o endif -ifeq ($(DISABLE_LOADER_XMT), no) +ifeq ($(DISABLE_LOADER_XMT),no) LIBGPAC_SCENEMANAGER+=scene_manager/loader_xmt.o endif -ifeq ($(DISABLE_LOADER_ISOFF), no) +ifeq ($(DISABLE_LOADER_ISOFF),no) LIBGPAC_SCENEMANAGER+=scene_manager/loader_isom.o endif -ifeq ($(DISABLE_LOADER_QTVR), no) +ifeq ($(DISABLE_LOADER_QTVR),no) LIBGPAC_SCENEMANAGER+=scene_manager/loader_qt.o endif -ifeq ($(DISABLE_SVG), no) -LIBGPAC_SCENEMANAGER+=scene_manager/loader_svg.o +ifeq ($(DISABLE_SVG),no) +LIBGPAC_SCENEMANAGER+=scene_manager/loader_svg.o endif -ifeq ($(DISABLE_LOADER_SWF), no) -LIBGPAC_SCENEMANAGER+=scene_manager/swf_parse.o scene_manager/swf_bifs.o scene_manager/swf_svg.o +ifeq ($(DISABLE_LOADER_SWF),no) +LIBGPAC_SCENEMANAGER+=scene_manager/swf_parse.o scene_manager/swf_bifs.o scene_manager/swf_svg.o endif -ifeq ($(DISABLE_SCENE_DUMP), no) +ifeq ($(DISABLE_SCENE_DUMP),no) LIBGPAC_SCENEMANAGER+=scene_manager/scene_dump.o endif -ifeq ($(DISABLE_SCENE_STATS), no) +ifeq ($(DISABLE_SCENE_STATS),no) LIBGPAC_SCENEMANAGER+=scene_manager/scene_stats.o endif -ifeq ($(DISABLE_SENG), no) -LIBGPAC_SCENEMANAGER+=scene_manager/scene_engine.o +ifeq ($(DISABLE_SENG),no) +LIBGPAC_SCENEMANAGER+=scene_manager/scene_engine.o endif -ifeq ($(DISABLE_SCENE_ENCODE), no) +ifeq ($(DISABLE_SCENE_ENCODE),no) LIBGPAC_SCENEMANAGER+=scene_manager/encode_isom.o endif ## libgpac objects gathering: src/terminal -LIBGPAC_TERMINAL=terminal/channel.o terminal/clock.o terminal/decoder.o terminal/term_node_init.o terminal/input_sensor.o terminal/media_control.o terminal/media_manager.o terminal/media_memory.o terminal/media_object.o terminal/media_sensor.o terminal/mpeg4_inline.o terminal/network_service.o terminal/object_browser.o terminal/object_manager.o terminal/scene.o terminal/terminal.o terminal/svg_external.o +LIBGPAC_TERMINAL=terminal/terminal.o + ## libgpac objects gathering: src/compositor -LIBGPAC_COMPOSITOR=compositor/audio_input.o compositor/audio_mixer.o compositor/audio_render.o compositor/bindable.o compositor/camera.o compositor/compositor.o compositor/compositor_2d.o compositor/compositor_3d.o compositor/compositor_node_init.o compositor/drawable.o compositor/events.o compositor/font_engine.o compositor/hc_flash_shape.o compositor/hardcoded_protos.o compositor/mesh.o compositor/mesh_collide.o compositor/mesh_tesselate.o compositor/mpeg4_animstream.o compositor/mpeg4_audio.o compositor/mpeg4_background.o compositor/mpeg4_background2d.o compositor/mpeg4_bitmap.o compositor/mpeg4_composite.o compositor/mpeg4_form.o compositor/mpeg4_geometry_2d.o compositor/mpeg4_geometry_3d.o compositor/mpeg4_geometry_ifs2d.o compositor/mpeg4_geometry_ils2d.o compositor/mpeg4_gradients.o compositor/mpeg4_grouping.o compositor/mpeg4_grouping_2d.o compositor/mpeg4_grouping_3d.o compositor/mpeg4_layer_2d.o compositor/mpeg4_layer_3d.o compositor/mpeg4_layout.o compositor/mpeg4_lighting.o compositor/mpeg4_path_layout.o compositor/mpeg4_sensors.o compositor/mpeg4_sound.o compositor/mpeg4_text.o compositor/mpeg4_textures.o compositor/mpeg4_timesensor.o compositor/mpeg4_viewport.o compositor/navigate.o compositor/offscreen_cache.o compositor/svg_base.o compositor/svg_filters.o compositor/svg_font.o compositor/svg_geometry.o compositor/svg_grouping.o compositor/svg_media.o compositor/svg_paint_servers.o compositor/svg_text.o compositor/texturing.o compositor/texturing_gl.o compositor/visual_manager.o compositor/visual_manager_2d.o compositor/visual_manager_2d_draw.o compositor/visual_manager_3d.o compositor/visual_manager_3d_gl.o compositor/x3d_geometry.o +LIBGPAC_COMPOSITOR=compositor/audio_input.o compositor/audio_mixer.o compositor/audio_render.o compositor/bindable.o compositor/camera.o compositor/compositor.o compositor/compositor_2d.o compositor/compositor_3d.o compositor/compositor_node_init.o compositor/drawable.o compositor/events.o compositor/font_engine.o compositor/hc_flash_shape.o compositor/hardcoded_protos.o compositor/mesh.o compositor/mesh_collide.o compositor/mesh_tesselate.o compositor/mpeg4_animstream.o compositor/mpeg4_audio.o compositor/mpeg4_background.o compositor/mpeg4_background2d.o compositor/mpeg4_bitmap.o compositor/mpeg4_composite.o compositor/mpeg4_form.o compositor/mpeg4_geometry_2d.o compositor/mpeg4_geometry_3d.o compositor/mpeg4_geometry_ifs2d.o compositor/mpeg4_geometry_ils2d.o compositor/mpeg4_gradients.o compositor/mpeg4_grouping.o compositor/mpeg4_grouping_2d.o compositor/mpeg4_grouping_3d.o compositor/mpeg4_layer_2d.o compositor/mpeg4_layer_3d.o compositor/mpeg4_layout.o compositor/mpeg4_lighting.o compositor/mpeg4_path_layout.o compositor/mpeg4_sensors.o compositor/mpeg4_sound.o compositor/mpeg4_text.o compositor/mpeg4_textures.o compositor/mpeg4_timesensor.o compositor/mpeg4_viewport.o compositor/navigate.o compositor/offscreen_cache.o compositor/svg_base.o compositor/svg_filters.o compositor/svg_font.o compositor/svg_geometry.o compositor/svg_grouping.o compositor/svg_media.o compositor/svg_paint_servers.o compositor/svg_text.o compositor/texturing.o compositor/texturing_gl.o compositor/visual_manager.o compositor/visual_manager_2d.o compositor/visual_manager_2d_draw.o compositor/visual_manager_3d.o compositor/visual_manager_3d_gl.o compositor/x3d_geometry.o compositor/clock.o compositor/mpeg4_inputsensor.o compositor/mpeg4_mediacontrol.o compositor/media_object.o compositor/mpeg4_mediasensor.o compositor/mpeg4_inline.o compositor/scene_ns.o compositor/object_manager.o compositor/scene.o compositor/svg_external.o compositor/scene_node_init.o -ifeq ($(DISABLE_PLAYER), yes) -LIBGPAC_COMPOSITOR= +ifeq ($(DISABLE_PLAYER),yes) +LIBGPAC_COMPOSITOR=compositor/font_engine.o LIBGPAC_TERMINAL= endif -ifeq ($(DISABLE_SCENEGRAPH), yes) -LIBGPAC_SCENE= +ifeq ($(DISABLE_SCENEGRAPH),yes) +LIBGPAC_SCENE=scenegraph/vrml_js.o endif +ifeq ($(DISABLE_EVG),yes) +LIBGPAC_EVG= +endif ## libgpac objects gathering: src/laser LIBGPAC_LASER= -ifeq ($(DISABLE_LASER), no) +ifeq ($(DISABLE_LASER),no) LIBGPAC_LASER=laser/lsr_enc.o laser/lsr_dec.o laser/lsr_tables.o endif -## libgpac objects gathering: -OBJS=$(LIBGPAC_UTILS) $(LIBGPAC_MCRYPT) $(LIBGPAC_SCENE) $(LIBGPAC_IETF) $(LIBGPAC_BIFS) $(LIBGPAC_ISOM) $(LIBGPAC_ODF) $(LIBGPAC_MEDIATOOLS) $(LIBGPAC_SCENEMANAGER) $(LIBGPAC_TERMINAL) $(LIBGPAC_COMPOSITOR) $(LIBGPAC_LASER) +## libgpac objects gathering: +OBJS=$(LIBGPAC_UTILS) $(LIBGPAC_CRYPTO) $(LIBGPAC_SCENE) $(LIBGPAC_IETF) $(LIBGPAC_BIFS) $(LIBGPAC_ISOM) $(LIBGPAC_ODF) $(LIBGPAC_MEDIATOOLS) $(LIBGPAC_SCENEMANAGER) $(LIBGPAC_TERMINAL) $(LIBGPAC_COMPOSITOR) $(LIBGPAC_LASER) $(LIBGPAC_EVG) $(LIBGPAC_FILTERS) $(LIBGPAC_FILTERS_SVC) $(LIBGPAC_QUICKJS) SCENEGRAPH_CFLAGS= MEDIATOOLS_CFLAGS=$(JS_FLAGS) @@ -212,6 +215,70 @@ MEDIATOOLS_CFLAGS=$(JS_FLAGS) ##include static modules and other deps for libgpac include ../static.mak +LIBGPAC_FILTERS+=filters/bsrw.o filters/compose.o filters/dasher.o filters/dec_ac52.o filters/dec_bifs.o filters/dec_faad.o filters/dec_img.o filters/dec_j2k.o filters/dec_laser.o filters/dec_mad.o filters/dec_mediacodec.o filters/dec_nvdec.o filters/dec_nvdec_sdk.o filters/dec_odf.o filters/dec_theora.o filters/dec_ttml.o filters/dec_ttxt.o filters/dec_vorbis.o filters/dec_vtb.o filters/dec_webvtt.o filters/dec_xvid.o filters/decrypt_cenc_isma.o filters/dmx_avi.o filters/dmx_dash.o filters/dmx_gsf.o filters/dmx_m2ts.o filters/dmx_mpegps.o filters/dmx_nhml.o filters/dmx_nhnt.o filters/dmx_ogg.o filters/dmx_saf.o filters/dmx_vobsub.o filters/enc_jpg.o filters/enc_png.o filters/encrypt_cenc_isma.o filters/ff_common.o filters/ff_avf.o filters/ff_dec.o filters/ff_dmx.o filters/ff_enc.o filters/ff_rescale.o filters/ff_mx.o filters/filelist.o filters/hevcmerge.o filters/hevcsplit.o filters/in_atsc.o filters/in_dvb4linux.o filters/in_file.o filters/in_http.o filters/in_pipe.o filters/in_rtp.o filters/in_rtp_rtsp.o filters/in_rtp_sdp.o filters/in_rtp_signaling.o filters/in_rtp_stream.o filters/in_sock.o filters/inspect.o filters/isoffin_load.o filters/isoffin_read.o filters/isoffin_read_ch.o filters/jsfilter.o filters/load_bt_xmt.o filters/load_svg.o filters/load_text.o filters/mux_avi.o filters/mux_gsf.o filters/mux_isom.o filters/mux_ts.o filters/out_audio.o filters/out_file.o filters/out_http.o filters/out_pipe.o filters/out_rtp.o filters/out_rtsp.o filters/out_sock.o filters/out_video.o filters/reframer.o filters/reframe_ac3.o filters/reframe_adts.o filters/reframe_latm.o filters/reframe_amr.o filters/reframe_av1.o filters/reframe_flac.o filters/reframe_h263.o filters/reframe_img.o filters/reframe_mp3.o filters/reframe_mpgvid.o filters/reframe_nalu.o filters/reframe_prores.o filters/reframe_qcp.o filters/reframe_rawvid.o filters/reframe_rawpcm.o filters/resample_audio.o filters/tileagg.o filters/tssplit.o filters/unit_test_filter.o filters/rewind.o filters/rewrite_adts.o filters/rewrite_mp4v.o filters/rewrite_nalu.o filters/rewrite_obu.o filters/vflip.o filters/vcrop.o filters/write_generic.o filters/write_nhml.o filters/write_nhnt.o filters/write_qcp.o filters/write_vtt.o ../modules/dektec_out/dektec_video_decl.o + +FILTERS_CFLAGS+=$(JS_FLAGS) + +LIBGPAC_FILTERS_OHEVC+=filters/dec_openhevc.o + +FILTERS_OHEVC_CFLAGS=$(OHEVC_CFLAGS) + +LIBGPAC_FILTERS_SVC+=filters/dec_opensvc.o + +FILTERS_SVC_CFLAGS=$(OSVC_CFLAGS) + +ifneq ($(CONFIG_FFMPEG),no) +FILTERS_CFLAGS+=$(ffmpeg_cflags) +EXTRALIBS+=$(ffmpeg_lflags) +endif + +ifneq ($(CONFIG_FAAD),no) +EXTRALIBS+=-lfaad +endif + +ifneq ($(CONFIG_MAD),no) +EXTRALIBS+=-lmad +endif + +ifneq ($(CONFIG_XVID),no) +EXTRALIBS+=-lxvidcore +endif + +ifneq ($(CONFIG_A52),no) +EXTRALIBS+=-la52 +endif + +ifneq ($(CONFIG_VORBIS),no) +EXTRALIBS+=-lvorbis +endif + +ifneq ($(CONFIG_THEORA),no) +EXTRALIBS+=-ltheora +endif + +ifneq ($(CONFIG_JP2),no) +EXTRALIBS+=$(JP2_LDFLAGS) +ifeq ($(CONFIG_JP2),local) +CFLAGS+= -DOPJ_STATIC -I"$(LOCAL_INC_PATH)/openjpeg" +else +CFLAGS+=$(JP2_CFLAGS) +endif +endif + +ifeq ($(CONFIG_OPENHEVC),yes) +#EXTRALIBS+=-lopenhevc +EXTRALIBS+=$(OHEVC_LDFLAGS) +endif + +ifeq ($(CONFIG_OPENSVC),yes) +#EXTRALIBS+=-lOpenSVCDec +EXTRALIBS+=$(OSVC_LDFLAGS) +endif + +ifeq ($(CONFIG_VTB),yes) +EXTRALIBS+=$(vtb_ldflags) +endif + ##libgpac library output @@ -226,24 +293,25 @@ EXTRALIBS+=$(GPAC_SH_FLAGS) endif LD_SONAME="-Wl,-soname,libgpac.so.$(VERSION_MAJOR)" -ifeq ($(CONFIG_DARWIN), yes) +ifeq ($(CONFIG_DARWIN),yes) LD_SONAME= endif -ifeq ($(CONFIG_SUNOS), yes) +ifeq ($(CONFIG_SUNOS),yes) LD_SONAME="-Wl,-h,$(LIB)" EXTRALIBS+= -lrt endif -SRCS := $(OBJS:.o=.c) + +SRCS := $(OBJS:.o=.c) all: lib lib: ../bin/gcc/$(LIB) #there's a bunch of warnings in there, get rid of them -mcrypt: CFLAGS= $(OPTFLAGS) -w -I"$(SRC_PATH)/include" -mcrypt: $(LIBGPAC_MCRYPT) +crypto: CFLAGS= $(OPTFLAGS) -w -I"$(SRC_PATH)/include" +crypto: $(LIBGPAC_CRYPTO) scenegraph: CFLAGS += $(SCENEGRAPH_CFLAGS) scenegraph: $(LIBGPAC_SCENE) @@ -260,8 +328,21 @@ compositor: $(LIBGPAC_COMPOSITOR) .deps/compositor/%: CFLAGS += $(COMPOSITOR_CFLAGS) .deps/compositor/%: CFLAGS += $(COMPOSITOR_CFLAGS) +filters: CFLAGS += $(FILTERS_CFLAGS) +filters: $(LIBGPAC_FILTERS) +.deps/filters/%: CFLAGS += $(FILTERS_CFLAGS) +.deps/filters/%: CFLAGS += $(FILTERS_CFLAGS) + +filters_ohevc: CFLAGS += $(FILTERS_OHEVC_CFLAGS) +filters_ohevc: $(LIBGPAC_FILTERS_OHEVC) +.deps/filters/dec_openhevc.dep: CFLAGS += $(FILTERS_OHEVC_CFLAGS) + +filters_svc: CFLAGS += $(FILTERS_SVC_CFLAGS) +filters_svc: $(LIBGPAC_FILTERS_SVC) +.deps/filters/dec_opensvc.dep: CFLAGS += $(FILTERS_SVC_CFLAGS) + -../bin/gcc/$(LIB): $(LIBGPAC_UTILS) $(LIBGPAC_IETF) $(LIBGPAC_BIFS) $(LIBGPAC_ODF) $(LIBGPAC_LASER) $(LIBGPAC_ISOM) $(LIBGPAC_SCENEMANAGER) $(LIBGPAC_TERMINAL) compositor scenegraph media_tools mcrypt $(OBJS) +../bin/gcc/$(LIB): $(LIBGPAC_UTILS) $(LIBGPAC_IETF) $(LIBGPAC_BIFS) $(LIBGPAC_ODF) $(LIBGPAC_LASER) $(LIBGPAC_ISOM) $(LIBGPAC_SCENEMANAGER) $(LIBGPAC_TERMINAL) compositor scenegraph media_tools crypto filters filters_ohevc filters_svc $(OBJS) @echo "OBJS $(OBJS)" @echo "LIBS $(EXTRALIBS)" @@ -286,7 +367,7 @@ endif endif -ifeq ($(shell fgrep "Libs.private:" ../gpac.pc 1>&2 2> /dev/null ; echo $$?), 1) +ifeq ($(shell fgrep "Libs.private:" ../gpac.pc 1>&2 2> /dev/null ; echo $$?),1) @echo "Libs.private: -lgpac_static $(EXTRALIBS)" >> ../gpac.pc endif @@ -303,7 +384,7 @@ DEPS := $(SRCS:%.c=.deps/%.dep) @mkdir -p $(dir $@) $(CC) -MM $(CFLAGS) "$<" -MT "$*.o" -MF "$@" -clean: +clean: rm -f $(OBJS) ../bin/gcc/$(LIB) rm -rf ../bin/gcc/libgpac_static.a ../bin/gcc/libgpac.so \ ../bin/gcc/libgpac.so.$(VERSION_MAJOR) \ diff --git a/src/bifs/arith_decoder.c b/src/bifs/arith_decoder.c index cb8371f..1b57da8 100644 --- a/src/bifs/arith_decoder.c +++ b/src/bifs/arith_decoder.c @@ -138,7 +138,8 @@ GF_AADecoder *gp_bifs_aa_dec_new(GF_BitStream *bs) { GF_AADecoder *tmp; GF_SAFEALLOC(tmp, GF_AADecoder); - tmp->bs = bs; + if (tmp) + tmp->bs = bs; return tmp; } void gp_bifs_aa_dec_del(GF_AADecoder *dec) @@ -179,9 +180,9 @@ void gp_bifs_aa_dec_resync(GF_AADecoder *dec) { u32 rewind; if (!dec->needs_flush) return; - rewind = dec->read_bits - 1 - (dec->used_bits+1); /*magic number from IM1 (spec is wrong there)*/ rewind = 14; + //spec: rewind = dec->read_bits - 1 - (dec->used_bits+1); if (dec->skip_bits < rewind) gf_bs_rewind_bits(dec->bs, rewind - dec->skip_bits); dec->needs_flush = 0; diff --git a/src/bifs/bifs_codec.c b/src/bifs/bifs_codec.c index 224c765..57224bf 100644 --- a/src/bifs/bifs_codec.c +++ b/src/bifs/bifs_codec.c @@ -123,10 +123,11 @@ BIFSStreamInfo *gf_bifs_dec_get_stream(GF_BifsDecoder * codec, u16 ESID) } GF_EXPORT -GF_Err gf_bifs_decoder_configure_stream(GF_BifsDecoder * codec, u16 ESID, char *DecoderSpecificInfo, u32 DecoderSpecificInfoLength, u32 objectTypeIndication) +GF_Err gf_bifs_decoder_configure_stream(GF_BifsDecoder * codec, u16 ESID, u8 *DecoderSpecificInfo, u32 DecoderSpecificInfoLength, u32 objectTypeIndication) { GF_BitStream *bs; BIFSStreamInfo *pInfo; + Bool new_cfg = GF_FALSE; GF_Err e; if (!DecoderSpecificInfo) { @@ -140,15 +141,16 @@ GF_Err gf_bifs_decoder_configure_stream(GF_BifsDecoder * codec, u16 ESID, char * assert( codec->streamInfo ); return gf_list_add(codec->streamInfo, pInfo); } -// gf_mx_p(codec->mx); + assert( codec ); - if (gf_bifs_dec_get_stream(codec, ESID) != NULL) { -// gf_mx_v(codec->mx); - return GF_BAD_PARAM; + pInfo = gf_bifs_dec_get_stream(codec, ESID); + //we allow reconfigure of the BIFS stream + if (pInfo == NULL) { + GF_SAFEALLOC(pInfo, BIFSStreamInfo); + if (!pInfo) return GF_OUT_OF_MEM; + new_cfg = GF_TRUE; } bs = gf_bs_new(DecoderSpecificInfo, DecoderSpecificInfoLength, GF_BITSTREAM_READ); - GF_SAFEALLOC(pInfo, BIFSStreamInfo); - if (!pInfo) return GF_OUT_OF_MEM; pInfo->ESID = ESID; pInfo->config.version = objectTypeIndication; @@ -176,18 +178,20 @@ GF_Err gf_bifs_decoder_configure_stream(GF_BifsDecoder * codec, u16 ESID, char * gf_sg_set_scene_size_info(codec->scenegraph, pInfo->config.Width, pInfo->config.Height, pInfo->config.PixelMetrics); } - gf_list_add(codec->streamInfo, pInfo); -// gf_mx_v(codec->mx); + if (new_cfg) + gf_list_add(codec->streamInfo, pInfo); return GF_OK; } + + +#if 0 //deprecated GF_EXPORT void gf_bifs_decoder_ignore_size_info(GF_BifsDecoder *codec) { if (codec) codec->ignore_size = GF_TRUE; } - GF_EXPORT GF_Err gf_bifs_decoder_remove_stream(GF_BifsDecoder *codec, u16 ESID) { @@ -204,6 +208,8 @@ GF_Err gf_bifs_decoder_remove_stream(GF_BifsDecoder *codec, u16 ESID) } return GF_BAD_PARAM; } +#endif + GF_EXPORT void gf_bifs_decoder_del(GF_BifsDecoder *codec) @@ -225,10 +231,6 @@ void gf_bifs_decoder_del(GF_BifsDecoder *codec) gf_list_rem(codec->command_buffers, 0); } gf_list_del(codec->command_buffers); - - if (codec->extraction_path) gf_free(codec->extraction_path); - if (codec->service_url) gf_free(codec->service_url); -// gf_mx_del(codec->mx); gf_free(codec); } @@ -241,24 +243,30 @@ void BD_EndOfStream(void *co) void gf_bs_set_eos_callback(GF_BitStream *bs, void (*EndOfStream)(void *par), void *par); GF_EXPORT -GF_Err gf_bifs_decode_au(GF_BifsDecoder *codec, u16 ESID, const char *data, u32 data_length, Double ts_offset) +Bool gf_bifs_decode_has_conditionnals(GF_BifsDecoder *codec) +{ + return codec && codec->has_conditionnals ? GF_TRUE : GF_FALSE; +} + +GF_EXPORT +GF_Err gf_bifs_decode_au(GF_BifsDecoder *codec, u16 ESID, const u8 *data, u32 data_length, Double ts_offset) { GF_BitStream *bs; GF_Err e; if (!codec || !data || codec->dec_memory_mode) return GF_BAD_PARAM; + if (!data_length) return GF_OK; -// gf_mx_p(codec->mx); codec->info = gf_bifs_dec_get_stream(codec, ESID); if (!codec->info) { -// gf_mx_v(codec->mx); return GF_BAD_PARAM; } /*setup current scene graph*/ codec->current_graph = codec->scenegraph; codec->cts_offset = ts_offset; - bs = gf_bs_new(data, data_length, GF_BITSTREAM_READ); + bs = gf_bs_new((u8 *)data, data_length, GF_BITSTREAM_READ); + if (!bs) return GF_OUT_OF_MEM; gf_bs_set_eos_callback(bs, BD_EndOfStream, codec); if (codec->info->config.elementaryMasks) { @@ -270,17 +278,10 @@ GF_Err gf_bifs_decode_au(GF_BifsDecoder *codec, u16 ESID, const char *data, u32 /*reset current config*/ codec->info = NULL; codec->current_graph = NULL; -// gf_mx_v(codec->mx); return e; } -void gf_bifs_decoder_set_time_offset(GF_BifsDecoder *codec, Double ts) -{ - codec->cts_offset = ts; -} - - #ifndef GPAC_DISABLE_BIFS_ENC GF_Node *gf_bifs_enc_find_node(GF_BifsEncoder *codec, u32 nodeID) @@ -384,7 +385,7 @@ GF_Err gf_bifs_encoder_new_stream(GF_BifsEncoder *codec, u16 ESID, GF_BIFSConfig } GF_EXPORT -GF_Err gf_bifs_encode_au(GF_BifsEncoder *codec, u16 ESID, GF_List *command_list, char **out_data, u32 *out_data_length) +GF_Err gf_bifs_encode_au(GF_BifsEncoder *codec, u16 ESID, GF_List *command_list, u8 **out_data, u32 *out_data_length) { GF_BitStream *bs; GF_Err e; @@ -413,7 +414,7 @@ GF_Err gf_bifs_encode_au(GF_BifsEncoder *codec, u16 ESID, GF_List *command_list, } GF_EXPORT -GF_Err gf_bifs_encoder_get_config(GF_BifsEncoder *codec, u16 ESID, char **out_data, u32 *out_data_length) +GF_Err gf_bifs_encoder_get_config(GF_BifsEncoder *codec, u16 ESID, u8 **out_data, u32 *out_data_length) { GF_BitStream *bs; @@ -490,7 +491,7 @@ GF_Err gf_bifs_encoder_set_source_url(GF_BifsEncoder *codec, const char *src_url #endif /*GPAC_DISABLE_BIFS_ENC*/ - +GF_EXPORT u32 gf_bifs_get_child_table(GF_Node *Node) { assert(Node); @@ -527,16 +528,5 @@ Bool gf_bifs_get_aq_info(GF_Node *Node, u32 FieldIndex, u8 *QType, u8 *AType, Fi } } -GF_EXPORT -void gf_bifs_decoder_set_extraction_path(GF_BifsDecoder *codec, const char *path, const char *service_url) -{ - if (!codec) return; - if (codec->extraction_path) gf_free(codec->extraction_path); - codec->extraction_path = path ? gf_strdup(path) : NULL; - if (codec->service_url) gf_free(codec->service_url); - codec->service_url = service_url ? gf_strdup(service_url) : NULL; - -} - #endif /*GPAC_DISABLE_BIFS*/ diff --git a/src/bifs/com_dec.c b/src/bifs/com_dec.c index cb28551..ea63580 100644 --- a/src/bifs/com_dec.c +++ b/src/bifs/com_dec.c @@ -285,7 +285,7 @@ static GF_Err BD_DecProtoDelete(GF_BifsDecoder * codec, GF_BitStream *bs) static GF_Err BD_DecMultipleIndexReplace(GF_BifsDecoder * codec, GF_BitStream *bs) { u32 ID, ind, field_ind, NumBits, lenpos, lennum, count, pos; - GF_Node *node, *new_node; + GF_Node *node; GF_Err e; GF_FieldInfo field, sffield; @@ -307,6 +307,7 @@ static GF_Err BD_DecMultipleIndexReplace(GF_BifsDecoder * codec, GF_BitStream *b /*cf index value replace */ if (field.fieldType == GF_SG_VRML_MFNODE) { while (count) { + GF_Node *new_node; pos = gf_bs_read_int(bs, lenpos); /*first decode*/ new_node = gf_bifs_dec_node(codec, bs, field.NDTtype); @@ -584,7 +585,7 @@ static GF_Err BD_DecIndexInsert(GF_BifsDecoder * codec, GF_BitStream *bs) u32 NumBits, ind, field_ind; u8 type; s32 pos; - GF_Node *def, *node; + GF_Node *def; GF_FieldInfo field, sffield; NodeID = 1 + gf_bs_read_int(bs, codec->info->config.NodeIDBits); @@ -621,7 +622,7 @@ static GF_Err BD_DecIndexInsert(GF_BifsDecoder * codec, GF_BitStream *bs) /*rescale the MFField and parse the SFField*/ if (field.fieldType==GF_SG_VRML_MFNODE) { - node = gf_bifs_dec_node(codec, bs, field.NDTtype); + GF_Node *node = gf_bifs_dec_node(codec, bs, field.NDTtype); if (!node) return codec->LastError; e = gf_node_register(node, def); @@ -1023,10 +1024,14 @@ GF_Err gf_bifs_dec_proto_list(GF_BifsDecoder * codec, GF_BitStream *bs, GF_List if (codec->UseName) { gf_bifs_dec_name(bs, name); } else { - sprintf(name, "Proto%d", numProtos); + sprintf(name, "Proto%d", gf_list_count(codec->current_graph->protos) ); } /*create a proto in the current graph*/ proto = gf_sg_proto_new(codec->current_graph, ID, name, proto_list ? GF_TRUE : GF_FALSE); + if (!proto) { + e = GF_NON_COMPLIANT_BITSTREAM; + goto exit; + } if (proto_list) gf_list_add(proto_list, proto); /*during parsing, this proto is the current active one - all nodes/proto defined/declared @@ -1233,7 +1238,10 @@ GF_Err gf_bifs_dec_proto_list(GF_BifsDecoder * codec, GF_BitStream *bs, GF_List exit: if (e) { - if (proto) gf_sg_proto_del(proto); + if (proto) { + if (proto_list) gf_list_del_item(proto_list, proto); + gf_sg_proto_del(proto); + } codec->current_graph = rootSG; } /*restore original parent proto at codec level*/ diff --git a/src/bifs/com_enc.c b/src/bifs/com_enc.c index 0b0000e..1cc0b71 100644 --- a/src/bifs/com_enc.c +++ b/src/bifs/com_enc.c @@ -408,11 +408,13 @@ GF_Err BE_IndexDelete(GF_BifsEncoder *codec, GF_Command *com, GF_BitStream *bs) GF_Err BE_NodeReplace(GF_BifsEncoder *codec, GF_Command *com, GF_BitStream *bs) { - GF_CommandField *inf; - if (!gf_list_count(com->command_fields)) return GF_OK; - inf = (GF_CommandField *)gf_list_get(com->command_fields, 0); + GF_Node *new_node = NULL; + if (gf_list_count(com->command_fields)) { + GF_CommandField *inf = (GF_CommandField *)gf_list_get(com->command_fields, 0); + if (inf) new_node = inf->new_node; + } GF_BIFS_WRITE_INT(codec, bs, gf_node_get_id(com->node) - 1, codec->info->config.NodeIDBits, "NodeID", NULL); - return gf_bifs_enc_node(codec, inf->new_node, NDT_SFWorldNode, bs, NULL); + return gf_bifs_enc_node(codec, new_node, NDT_SFWorldNode, bs, NULL); } GF_Err BE_FieldReplace(GF_BifsEncoder *codec, GF_Command *com, GF_BitStream *bs) @@ -955,7 +957,7 @@ GF_Err gf_bifs_enc_commands(GF_BifsEncoder *codec, GF_List *comList, GF_BitStrea GF_EXPORT -GF_Err gf_bifs_encoder_get_rap(GF_BifsEncoder *codec, char **out_data, u32 *out_data_length) +GF_Err gf_bifs_encoder_get_rap(GF_BifsEncoder *codec, u8 **out_data, u32 *out_data_length) { GF_BitStream *bs; GF_Err e; diff --git a/src/bifs/conditional.c b/src/bifs/conditional.c index 723ed47..9b1853d 100644 --- a/src/bifs/conditional.c +++ b/src/bifs/conditional.c @@ -141,6 +141,7 @@ void SetupConditional(GF_BifsDecoder *codec, GF_Node *node) priv->info = codec->info; priv->codec = codec; + codec->has_conditionnals = GF_TRUE; gf_node_set_callback_function(node, Conditional_PreDestroy); gf_node_set_private(node, priv); ((M_Conditional *)node)->on_activate = Conditional_OnActivate; diff --git a/src/bifs/field_decode.c b/src/bifs/field_decode.c index b7282b9..b06a872 100644 --- a/src/bifs/field_decode.c +++ b/src/bifs/field_decode.c @@ -26,6 +26,7 @@ #include +#include #include "quant.h" #include "script.h" @@ -89,7 +90,7 @@ GF_Err gf_bifs_dec_sf_field(GF_BifsDecoder * codec, GF_BitStream *bs, GF_Node *n * ((SFBool *) field->far_ptr) = (SFBool) gf_bs_read_int(bs, 1); break; case GF_SG_VRML_SFCOLOR: - ((SFColor *)field->far_ptr)->red = BD_ReadSFFloat(codec, bs);; + ((SFColor *)field->far_ptr)->red = BD_ReadSFFloat(codec, bs); ((SFColor *)field->far_ptr)->green = BD_ReadSFFloat(codec, bs); ((SFColor *)field->far_ptr)->blue = BD_ReadSFFloat(codec, bs); break; @@ -247,7 +248,7 @@ GF_Err gf_bifs_dec_sf_field(GF_BifsDecoder * codec, GF_BitStream *bs, GF_Node *n *((GF_Node **) field->far_ptr) = new_node; break; case GF_SG_VRML_SFSCRIPT: -#ifdef GPAC_HAS_SPIDERMONKEY +#ifdef GPAC_HAS_QJS codec->LastError = SFScript_Parse(codec, (SFScript*)field->far_ptr, bs, node); #else return GF_NOT_SUPPORTED; @@ -370,7 +371,6 @@ GF_Err BD_DecMFFieldVec(GF_BifsDecoder * codec, GF_BitStream *bs, GF_Node *node, u32 i; GF_ChildNodeItem *last; u8 qp_local, qp_on, initial_qp; - GF_Node *new_node; GF_FieldInfo sffield; memset(&sffield, 0, sizeof(GF_FieldInfo)); @@ -403,7 +403,7 @@ GF_Err BD_DecMFFieldVec(GF_BifsDecoder * codec, GF_BitStream *bs, GF_Node *node, } else { last = NULL; for (i=0; iNDTtype); + GF_Node *new_node = gf_bifs_dec_node(codec, bs, field->NDTtype); if (new_node) { e = gf_node_register(new_node, is_mem_com ? NULL : node); if (e) return e; @@ -506,10 +506,10 @@ GF_Err gf_bifs_dec_field(GF_BifsDecoder * codec, GF_BitStream *bs, GF_Node *node if (codec->info->config.UsePredictiveMFField) { flag = gf_bs_read_int(bs, 1); if (flag) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("[BIFS] Stream uses Predictive Field Coding!\n")); #ifdef GPAC_ENABLE_BIFS_PMF return gf_bifs_dec_pred_mf_field(codec, bs, node, field); #else + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("[BIFS] Stream uses Predictive Field Coding, disabled in this build!\n")); return GF_NOT_SUPPORTED; #endif } @@ -721,9 +721,16 @@ GF_Node *gf_bifs_dec_node(GF_BifsDecoder * codec, GF_BitStream *bs, u32 NDT_Tag) /*NULL node is encoded as USE with ID = all bits to 1*/ if (nodeID == (u32) (1<info->config.NodeIDBits)) return NULL; - //find node and return it + //find node new_node = gf_sg_find_node(codec->current_graph, nodeID); + //check node is allowed for the given NDT + if (new_node && !gf_node_in_table(new_node, NDT_Tag)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("[BIFS] Node %s not allowed as field/child of NDT type %d\n", gf_node_get_class_name(new_node), NDT_Tag)); + codec->LastError = GF_SG_UNKNOWN_NODE; + return NULL; + } + if (!new_node) { codec->LastError = GF_SG_UNKNOWN_NODE; } else { @@ -845,9 +852,10 @@ GF_Node *gf_bifs_dec_node(GF_BifsDecoder * codec, GF_BitStream *bs, u32 NDT_Tag) } } } + if (!new_node) #endif - if (!new_node) { + { if (proto) { /*create proto interface*/ new_node = gf_sg_proto_create_instance(codec->current_graph, proto); diff --git a/src/bifs/field_encode.c b/src/bifs/field_encode.c index ab96858..ac1be4b 100644 --- a/src/bifs/field_encode.c +++ b/src/bifs/field_encode.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / BIFS codec sub-project @@ -106,31 +106,36 @@ GF_Err gf_bifs_enc_sf_field(GF_BifsEncoder *codec, GF_BitStream *bs, GF_Node *no return GF_URL_ERROR; } if (res_src) gf_free(res_src); - gf_fseek(f, 0, SEEK_END); - size = (u32) gf_ftell(f); + size = (u32) gf_fsize(f); val = gf_get_bit_size(size); GF_BIFS_WRITE_INT(codec, bs, val, 5, "nbBits", NULL); GF_BIFS_WRITE_INT(codec, bs, size, val, "length", NULL); - gf_fseek(f, 0, SEEK_SET); + while (size) { - u32 read = (u32) fread(buf, 1, 4096, f); + u32 read = (u32) gf_fread(buf, 4096, f); gf_bs_write_data(bs, buf, read); size -= read; } gf_fclose(f); } else { u32 i, val, len; + char *dump_str = NULL; char *str = (char *) ((SFString*)field->far_ptr)->buffer; if (node && (node->sgprivate->tag==TAG_MPEG4_BitWrapper) ) { len = ((M_BitWrapper*)node)->buffer_len; } else { len = str ? (u32) strlen(str) : 0; + dump_str = str; } val = gf_get_bit_size(len); GF_BIFS_WRITE_INT(codec, bs, val, 5, "nbBits", NULL); GF_BIFS_WRITE_INT(codec, bs, len, val, "length", NULL); for (i=0; icommandList, bs_cond); - if (!e) gf_bs_get_content(bs_cond, (char**)&cb->buffer, &cb->bufferSize); + if (!e) gf_bs_get_content(bs_cond, &cb->buffer, &cb->bufferSize); gf_bs_del(bs_cond); if (e) return e; GF_LOG(GF_LOG_DEBUG, GF_LOG_CODING, ("[BIFS] /*End SFCommandBuffer*/\n")); @@ -210,7 +215,7 @@ GF_Err gf_bifs_enc_sf_field(GF_BifsEncoder *codec, GF_BitStream *bs, GF_Node *no return gf_bifs_enc_node(codec, *((GF_Node **)field->far_ptr), field->NDTtype, bs, node); case GF_SG_VRML_SFSCRIPT: -#ifdef GPAC_HAS_SPIDERMONKEY +#ifdef GPAC_HAS_QJS codec->LastError = SFScript_Encode(codec, (SFScript *)field->far_ptr, bs, node); #else return GF_NOT_SUPPORTED; @@ -399,7 +404,7 @@ GF_Err EncNodeFields(GF_BifsEncoder * codec, GF_BitStream *bs, GF_Node *node) } if (node->sgprivate->tag == TAG_ProtoNode) { - clone = gf_sg_proto_create_instance(node->sgprivate->scenegraph, ((GF_ProtoInstance *)node)->proto_interface);; + clone = gf_sg_proto_create_instance(node->sgprivate->scenegraph, ((GF_ProtoInstance *)node)->proto_interface); } else { clone = gf_node_new(node->sgprivate->scenegraph, node->sgprivate->tag); } @@ -561,7 +566,6 @@ GF_Err gf_bifs_enc_node(GF_BifsEncoder * codec, GF_Node *node, u32 NDT_Tag, GF_B u32 NDTBits, node_type, node_tag, BVersion, node_id; const char *node_name; Bool flag, reset_qp14; - GF_Node *new_node; GF_Err e; assert(codec->info); @@ -578,6 +582,7 @@ GF_Err gf_bifs_enc_node(GF_BifsEncoder * codec, GF_Node *node, u32 NDT_Tag, GF_B GF_BIFS_WRITE_INT(codec, bs, flag ? 1 : 0, 1, "USE", (char*)gf_node_get_class_name(node)); if (flag) { + GF_Node *new_node; gf_bs_write_int(bs, gf_node_get_id(node) - 1, codec->info->config.NodeIDBits); new_node = gf_bifs_enc_find_node(codec, gf_node_get_id(node) ); if (!new_node) diff --git a/src/bifs/memory_decoder.c b/src/bifs/memory_decoder.c index 5842061..4053445 100644 --- a/src/bifs/memory_decoder.c +++ b/src/bifs/memory_decoder.c @@ -177,7 +177,7 @@ static GF_Err BM_ParseGlobalQuantizer(GF_BifsDecoder *codec, GF_BitStream *bs, G codec->ActiveQP = NULL; codec->scenegraph->global_qp = NULL; - if (node && (gf_node_get_tag(node) != TAG_MPEG4_QuantizationParameter)) { + if (gf_node_get_tag(node) != TAG_MPEG4_QuantizationParameter) { gf_node_unregister(node, NULL); return GF_NON_COMPLIANT_BITSTREAM; } @@ -186,10 +186,10 @@ static GF_Err BM_ParseGlobalQuantizer(GF_BifsDecoder *codec, GF_BitStream *bs, G codec->ActiveQP = (M_QuantizationParameter *) node; codec->ActiveQP->isLocal = 0; codec->scenegraph->global_qp = node; - if (node) { - /*register TWICE: once for the command, and for the scenegraph globalQP*/ - node->sgprivate->num_instances = 2; - } + + /*register TWICE: once for the command, and for the scenegraph globalQP*/ + node->sgprivate->num_instances = 2; + com = gf_sg_command_new(codec->current_graph, GF_SG_GLOBAL_QUANTIZER); inf = gf_sg_command_field_new(com); inf->new_node = node; @@ -230,7 +230,7 @@ static GF_Err BM_ParseProtoDelete(GF_BifsDecoder *codec, GF_BitStream *bs, GF_Li static GF_Err BM_XReplace(GF_BifsDecoder *codec, GF_BitStream *bs, GF_List *com_list) { GF_FieldInfo targetField, fromField, decfield; - GF_Node *target, *n, *fromNode; + GF_Node *target, *fromNode; s32 pos = -2; u32 id, nbBits, ind, aind; GF_Err e; @@ -243,6 +243,7 @@ static GF_Err BM_XReplace(GF_BifsDecoder *codec, GF_BitStream *bs, GF_List *com_ com = gf_sg_command_new(codec->current_graph, GF_SG_XREPLACE); BM_SetCommandNode(com, target); + gf_list_add(com_list, com); nbBits = gf_get_bit_size(gf_node_get_num_fields_in_mode(target, GF_SG_FIELD_CODING_IN)-1); ind = gf_bs_read_int(bs, nbBits); @@ -259,6 +260,7 @@ static GF_Err BM_XReplace(GF_BifsDecoder *codec, GF_BitStream *bs, GF_List *com_ if (gf_bs_read_int(bs, 1)) { /*index is dynamic*/ if (gf_bs_read_int(bs, 1)) { + GF_Node *n; id = 1 + gf_bs_read_int(bs, codec->info->config.NodeIDBits); n = gf_sg_find_node(codec->current_graph, id); if (!n) return GF_SG_UNKNOWN_NODE; @@ -344,11 +346,7 @@ static GF_Err BM_XReplace(GF_BifsDecoder *codec, GF_BitStream *bs, GF_List *com_ decfield.far_ptr = inf->field_ptr = gf_sg_vrml_field_pointer_new(inf->fieldType); } e = gf_bifs_dec_sf_field(codec, bs, target, &decfield, GF_TRUE); - if (e) return e; - - gf_list_add(com_list, com); - - return GF_OK; + return e; } static GF_Err BM_ParseExtendedUpdates(GF_BifsDecoder *codec, GF_BitStream *bs, GF_List *com_list) @@ -401,7 +399,6 @@ static GF_Err BM_ParseExtendedUpdates(GF_BifsDecoder *codec, GF_BitStream *bs, G GF_Err BM_ParseNodeInsert(GF_BifsDecoder *codec, GF_BitStream *bs, GF_List *com_list) { u32 NodeID, NDT; - GF_Command *com; GF_CommandField *inf; s32 type, pos; GF_Node *node, *def; @@ -429,7 +426,7 @@ GF_Err BM_ParseNodeInsert(GF_BifsDecoder *codec, GF_BitStream *bs, GF_List *com_ } node = gf_bifs_dec_node(codec, bs, NDT); if (!codec->LastError) { - com = gf_sg_command_new(codec->current_graph, GF_SG_NODE_INSERT); + GF_Command *com = gf_sg_command_new(codec->current_graph, GF_SG_NODE_INSERT); BM_SetCommandNode(com, def); inf = gf_sg_command_field_new(com); inf->pos = pos; @@ -890,14 +887,11 @@ GF_Err BM_SceneReplace(GF_BifsDecoder *codec, GF_BitStream *bs, GF_List *com_lis GF_Err BM_ParseCommand(GF_BifsDecoder *codec, GF_BitStream *bs, GF_List *com_list) { u8 go, type; - u32 count; GF_Err e; go = 1; e = GF_OK; codec->LastError = GF_OK; - count = 0; - while (go) { type = gf_bs_read_int(bs, 2); switch (type) { @@ -915,10 +909,7 @@ GF_Err BM_ParseCommand(GF_BifsDecoder *codec, GF_BitStream *bs, GF_List *com_lis break; } if (e) return e; - go = gf_bs_read_int(bs, 1); - count++; - } while (gf_list_count(codec->QPs)) { gf_bifs_dec_qp_remove(codec, GF_TRUE); @@ -994,7 +985,7 @@ GF_Err gf_bifs_flush_command_list(GF_BifsDecoder *codec) } GF_EXPORT -GF_Err gf_bifs_decode_command_list(GF_BifsDecoder *codec, u16 ESID, char *data, u32 data_length, GF_List *com_list) +GF_Err gf_bifs_decode_command_list(GF_BifsDecoder *codec, u16 ESID, u8 *data, u32 data_length, GF_List *com_list) { GF_BitStream *bs; GF_Err e; diff --git a/src/bifs/predictive_mffield.c b/src/bifs/predictive_mffield.c index a4397cd..57146c4 100644 --- a/src/bifs/predictive_mffield.c +++ b/src/bifs/predictive_mffield.c @@ -226,7 +226,7 @@ GF_Err PMF_ParsePValue(PredMF *pmf, GF_BitStream *bs, GF_FieldInfo *field) if (diff_ori != 0) { s32 k=0; for (i=0; i< pmf->num_comp - diff_ori; i++) { - k = (i + diff_ori) % pmf->num_comp; + //k = (i + diff_ori) % pmf->num_comp; temp_val[i] = inv * ( pmf->previous_val[i] + pmf->current_val[i]); } k = diff_ori - 1; diff --git a/src/bifs/quantize.c b/src/bifs/quantize.c index 5034a08..033cde2 100644 --- a/src/bifs/quantize.c +++ b/src/bifs/quantize.c @@ -58,7 +58,7 @@ GF_Err gf_bifs_enc_qp_remove(GF_BifsEncoder *codec, Bool ActivatePrev) u32 gf_bifs_enc_qp14_get_bits(GF_BifsEncoder *codec) { if (!codec->ActiveQP || !codec->coord_stored) return 0; - return (u32) ceil(log(codec->NumCoord+1) / log(2) ); + return (u32) ceil(log1p(codec->NumCoord) / log(2) ); } void gf_bifs_enc_qp14_enter(GF_BifsEncoder *codec, Bool Enter) @@ -211,7 +211,7 @@ GF_Err Q_EncCoordOnUnitSphere(GF_BifsEncoder *codec, GF_BitStream *bs, u32 NbBit { u32 i; u32 len = NbComp+1; - s32 orientation =-1; + u32 orientation=0; Fixed maxTmp = - FIX_MAX; for (i=0; i maxTmp) { diff --git a/src/bifs/script.h b/src/bifs/script.h index 5a43241..969c213 100644 --- a/src/bifs/script.h +++ b/src/bifs/script.h @@ -28,7 +28,7 @@ #include -#if !defined(GPAC_DISABLE_BIFS) && defined(GPAC_HAS_SPIDERMONKEY) +#if !defined(GPAC_DISABLE_BIFS) && defined(GPAC_HAS_QJS) #define NUMBITS_STATEMENT 3 #define NUMBITS_EXPR_TYPE 6 @@ -107,6 +107,6 @@ GF_Err SFScript_Parse(GF_BifsDecoder *codec, SFScript *script_field, GF_BitStrea GF_Err SFScript_Encode(GF_BifsEncoder *codec, SFScript *script_field, GF_BitStream *bs, GF_Node *n); #endif -#endif /* !defined(GPAC_DISABLE_BIFS) && defined(GPAC_HAS_SPIDERMONKEY) */ +#endif /* !defined(GPAC_DISABLE_BIFS) && defined(GPAC_HAS_QJS) */ #endif diff --git a/src/bifs/script_dec.c b/src/bifs/script_dec.c index d82c7f3..0edb501 100644 --- a/src/bifs/script_dec.c +++ b/src/bifs/script_dec.c @@ -26,7 +26,7 @@ #include "script.h" -#if !defined(GPAC_DISABLE_BIFS) && defined(GPAC_HAS_SPIDERMONKEY) +#if !defined(GPAC_DISABLE_BIFS) && defined(GPAC_HAS_QJS) #define BINOP_MINVAL ET_EQ @@ -783,4 +783,4 @@ void SFS_GetBoolean(ScriptParser *parser) } } -#endif /*!defined(GPAC_DISABLE_BIFS) && defined(GPAC_HAS_SPIDERMONKEY)*/ +#endif /*!defined(GPAC_DISABLE_BIFS) && defined(GPAC_HAS_QJS)*/ diff --git a/src/bifs/script_enc.c b/src/bifs/script_enc.c index daa9c97..b326f7e 100644 --- a/src/bifs/script_enc.c +++ b/src/bifs/script_enc.c @@ -31,7 +31,7 @@ #include -#if !defined(GPAC_DISABLE_BIFS) && !defined(GPAC_DISABLE_BIFS_ENC) && defined(GPAC_HAS_SPIDERMONKEY) +#if !defined(GPAC_DISABLE_BIFS) && !defined(GPAC_DISABLE_BIFS_ENC) && defined(GPAC_HAS_QJS) typedef struct { @@ -57,7 +57,7 @@ typedef struct #define SFE_WRITE_INT(sc_enc, val, nbBits, str1, str2) \ if (!sc_enc->emul) GF_BIFS_WRITE_INT(sc_enc->codec, sc_enc->bs, val, nbBits, str1, str2); \ - + static GF_Err EncScriptFields(ScriptEnc *sc_enc) { @@ -1175,7 +1175,7 @@ void SFE_PutBoolean(ScriptEnc *sc_enc, char *str) GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("[bifs] Script encoding: Token %s read, %s expected\n", tok_names[curTok], tok_names[x])); \ sc_enc->err = GF_BAD_PARAM; \ } \ - + u32 TOK_To_ET(u32 tok) { @@ -1262,6 +1262,7 @@ u32 TOK_To_ET(u32 tok) } } +#ifndef GPAC_DISABLE_LOG static const char *expr_name[] = { "ET_CURVED_EXPR", "ET_NEGATIVE", @@ -1315,6 +1316,8 @@ static const char *expr_name[] = { "ET_VAR", "NUMBER_OF_EXPR_TYPE" }; +#endif + #define NUMBER_OF_RANK 15 static s32 ET_Rank[NUMBER_OF_EXPR_TYPE] = @@ -1708,7 +1711,7 @@ skip_token: SFE_CheckToken(sc_enc, TOK_SEMICOLON); break; default: - GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("[bifs] Script encoding: illegal expression type %s\n", expr_name[expr])); + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("[bifs] Script encoding: illegal expression type %s\n", expr < GF_ARRAY_LENGTH(expr_name) ? expr_name[expr] : "")); sc_enc->err = GF_BAD_PARAM; break; } @@ -1830,4 +1833,4 @@ void SFE_Params(ScriptEnc *sc_enc, u32 start, u32 end) } } -#endif /* !defined(GPAC_DISABLE_BIFS) && !defined(GPAC_DISABLE_BIFS_ENC) && defined(GPAC_HAS_SPIDERMONKEY) */ +#endif /* !defined(GPAC_DISABLE_BIFS) && !defined(GPAC_DISABLE_BIFS_ENC) && defined(GPAC_HAS_QJS) */ diff --git a/src/bifs/unquantize.c b/src/bifs/unquantize.c index 1d493b9..a4bfcbf 100644 --- a/src/bifs/unquantize.c +++ b/src/bifs/unquantize.c @@ -32,7 +32,7 @@ u32 gf_bifs_dec_qp14_get_bits(GF_BifsDecoder *codec) { if (!codec->ActiveQP || !codec->coord_stored) return 0; - return (u32) ceil(log(codec->NumCoord+1) / log(2) ); + return (u32) ceil(log1p(codec->NumCoord) / log(2) ); } void gf_bifs_dec_qp14_enter(GF_BifsDecoder * codec, Bool Enter) diff --git a/src/compositor/audio_input.c b/src/compositor/audio_input.c index 245ebd4..62fbff3 100644 --- a/src/compositor/audio_input.c +++ b/src/compositor/audio_input.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2018 * All rights reserved * * This file is part of GPAC / Scene Compositor sub-project @@ -34,25 +34,8 @@ since the drift may be high on TS for example, where PTS-PCR>500ms is quite comm //introduce oscillations in the clock and non-smooth playback #define MIN_DRIFT_ADJUST 75 -struct __audiofilteritem -{ - GF_AudioInterface input; - GF_AudioInterface *src; - - u32 out_chan, out_ch_cfg; - - u32 nb_used, nb_filled; - - GF_AudioFilterChain filter_chain; -}; - - -GF_AudioFilterItem *gf_af_new(GF_Compositor *compositor, GF_AudioInterface *src, char *filter_name); -void gf_af_del(GF_AudioFilterItem *af); -void gf_af_reset(GF_AudioFilterItem *af); - -static char *gf_audio_input_fetch_frame(void *callback, u32 *size, u32 audio_delay_ms) +static u8 *gf_audio_input_fetch_frame(void *callback, u32 *size, u32 *planar_size, u32 audio_delay_ms) { char *frame; u32 obj_time, ts; @@ -64,7 +47,9 @@ static char *gf_audio_input_fetch_frame(void *callback, u32 *size, u32 audio_del if (!ai->stream) return NULL; done = ai->stream_finished; - frame = gf_mo_fetch_data(ai->stream, ai->compositor->audio_renderer->step_mode ? GF_MO_FETCH_PAUSED : GF_MO_FETCH, 0, &ai->stream_finished, &ts, size, NULL, NULL, NULL); + ai->input_ifce.is_buffering = GF_FALSE; + + frame = gf_mo_fetch_data(ai->stream, ai->compositor->audio_renderer->non_rt_output ? GF_MO_FETCH_PAUSED : GF_MO_FETCH, 0, &ai->stream_finished, &ts, size, NULL, NULL, NULL, planar_size); /*invalidate scene on end of stream to refresh audio graph*/ if (done != ai->stream_finished) { gf_sc_invalidate(ai->compositor, NULL); @@ -72,17 +57,18 @@ static char *gf_audio_input_fetch_frame(void *callback, u32 *size, u32 audio_del /*no more data or not enough data, reset syncro drift*/ if (!frame) { - if (!ai->stream_finished) { - GF_LOG(GF_LOG_INFO, GF_LOG_AUDIO, ("[Audio Input] No data in audio object\n")); + if (!ai->stream_finished && gf_mo_is_started(ai->stream) && (ai->stream->odm->ck->speed == FIX_ONE)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[Audio Input] No data in audio object\n")); } gf_mo_adjust_clock(ai->stream, 0); + ai->input_ifce.is_buffering = gf_mo_is_buffering(ai->stream); *size = 0; return NULL; } ai->need_release = GF_TRUE; //step mode, return the frame without sync check - if (ai->compositor->audio_renderer->step_mode) { + if (ai->compositor->audio_renderer->non_rt_output) { GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[Audio Input] audio frame CTS %u %d bytes fetched\n", ts, *size)); return frame; } @@ -97,6 +83,25 @@ static char *gf_audio_input_fetch_frame(void *callback, u32 *size, u32 audio_del drift = (s32)obj_time; drift -= (s32)ts; } + if (ai->stream->odm->prev_clock_at_discontinuity_plus_one) { + s32 drift_old = drift; + s32 diff; + drift_old -= (s32) ai->stream->odm->ck->init_timestamp; + drift_old += (s32) ai->stream->odm->prev_clock_at_discontinuity_plus_one - 1; + diff = ABS(drift_old); + diff -= ABS(drift); + if (diff < 0) { + GF_LOG(GF_LOG_INFO, GF_LOG_SYNC, ("[Audio Input] in clock discontinuity: drift old clock %d new clock %d - disabling clock adjustment\n", drift_old, drift)); + drift = 0; + audio_delay_ms = 0; + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_SYNC, ("[Audio Input] end of clock discontinuity: drift old clock %d new clock %d\n", drift_old, drift)); + ai->stream->odm->prev_clock_at_discontinuity_plus_one = 0; + if (drift<0) { + drift = 0; + } + } + } #ifdef ENABLE_EARLY_FRAME_DETECTION /*too early (silence insertions), skip*/ @@ -116,9 +121,13 @@ static char *gf_audio_input_fetch_frame(void *callback, u32 *size, u32 audio_del GF_LOG(GF_LOG_INFO, GF_LOG_AUDIO, ("[Audio Input] Audio data too late obj time %d - CTS %d - drift %d ms - resync forced\n", obj_time - audio_delay_ms, ts, drift)); gf_mo_release_data(ai->stream, *size, 2); ai->need_release = GF_FALSE; - return gf_audio_input_fetch_frame(callback, size, audio_delay_ms); + return gf_audio_input_fetch_frame(callback, size, planar_size, audio_delay_ms); } - resync_delay = gf_mo_get_clock_drift(ai->stream) - drift; + if (ai->stream->odm && ai->stream->odm->ck) + resync_delay = ai->stream->odm->ck->audio_delay - drift; + else + resync_delay = -drift; + if (resync_delay < 0) resync_delay = -resync_delay; if (resync_delay > MIN_DRIFT_ADJUST) { @@ -149,7 +158,10 @@ static Bool gf_audio_input_get_volume(void *callback, Fixed *vol) if (ai->snd && ai->snd->GetChannelVolume) { return ai->snd->GetChannelVolume(ai->snd->owner, vol); } else { - vol[0] = vol[1] = vol[2] = vol[3] = vol[4] = vol[5] = ai->intensity; + u32 i; + for (i=0; iintensity; + return (ai->intensity==FIX_ONE) ? GF_FALSE : GF_TRUE; } } @@ -158,6 +170,9 @@ static Bool gf_audio_input_is_muted(void *callback) { GF_AudioInput *ai = (GF_AudioInput *) callback; if (!ai->stream) return GF_TRUE; + + if (ai->stream->odm->nb_buffering) + gf_odm_check_buffering(ai->stream->odm, NULL); if (ai->is_muted) return GF_TRUE; return gf_mo_is_muted(ai->stream); @@ -168,18 +183,19 @@ static Bool gf_audio_input_get_config(GF_AudioInterface *aifc, Bool for_recf) GF_AudioInput *ai = (GF_AudioInput *) aifc->callback; if (!ai->stream) return GF_FALSE; /*watchout for object reuse*/ - if (aifc->samplerate && (gf_mo_get_flags(ai->stream) & GF_MO_IS_INIT)) return GF_TRUE; + if (aifc->samplerate && !ai->stream->config_changed) return GF_TRUE; - gf_mo_get_audio_info(ai->stream, &aifc->samplerate, &aifc->bps , &aifc->chan, &aifc->ch_cfg); + gf_mo_get_audio_info(ai->stream, &aifc->samplerate, &aifc->afmt , &aifc->chan, &aifc->ch_layout, &aifc->forced_layout); if (!for_recf) return aifc->samplerate ? GF_TRUE : GF_FALSE; - if (aifc->samplerate * aifc->chan * aifc->bps && ((aifc->chan<=2) || aifc->ch_cfg)) { - gf_mo_set_flag(ai->stream, GF_MO_IS_INIT, GF_TRUE); + if (aifc->samplerate && aifc->chan && aifc->afmt && ((aifc->chan<=2) || aifc->ch_layout)) { + ai->stream->config_changed = GF_FALSE; return GF_TRUE; } - gf_mo_set_flag(ai->stream, GF_MO_IS_INIT, GF_FALSE); + //still not ready ! + ai->stream->config_changed=GF_TRUE; return GF_FALSE; } @@ -208,14 +224,11 @@ void gf_sc_audio_predestroy(GF_AudioInput *ai) { gf_sc_audio_stop(ai); gf_sc_audio_unregister(ai); - - if (ai->filter) gf_af_del(ai->filter); } GF_EXPORT GF_Err gf_sc_audio_open(GF_AudioInput *ai, MFURL *url, Double clipBegin, Double clipEnd, Bool lock_timeline) { - u32 i; if (ai->is_open) return GF_BAD_PARAM; /*get media object*/ @@ -228,18 +241,9 @@ GF_Err gf_sc_audio_open(GF_AudioInput *ai, MFURL *url, Double clipBegin, Double ai->stream_finished = GF_FALSE; ai->is_open = 1; - gf_mo_set_flag(ai->stream, GF_MO_IS_INIT, GF_FALSE); - - if (ai->filter) gf_af_del(ai->filter); - ai->filter = NULL; + //force reload of audio props + ai->stream->config_changed = GF_TRUE; - for (i=0; icount; i++) { - if (url->vals[i].url && !strnicmp(url->vals[i].url, "#filter=", 8)) { - ai->filter = gf_af_new(ai->compositor, &ai->input_ifce, url->vals[i].url+8); - if (ai->filter) - break; - } - } return GF_OK; } @@ -253,14 +257,11 @@ void gf_sc_audio_stop(GF_AudioInput *ai) assert(!ai->need_release); - gf_mo_stop(ai->stream); + gf_mo_stop(&ai->stream); ai->is_open = 0; gf_mo_unregister(ai->owner, ai->stream); ai->stream = NULL; - if (ai->filter) gf_af_del(ai->filter); - ai->filter = NULL; - gf_mixer_lock(ai->compositor->audio_renderer->mixer, GF_FALSE); } @@ -272,7 +273,7 @@ void gf_sc_audio_restart(GF_AudioInput *ai) if (ai->need_release) gf_mo_release_data(ai->stream, 0xFFFFFFFF, 2); ai->need_release = GF_FALSE; ai->stream_finished = GF_FALSE; - if (ai->filter) gf_af_reset(ai->filter); + gf_mo_restart(ai->stream); } @@ -287,7 +288,6 @@ GF_EXPORT void gf_sc_audio_register(GF_AudioInput *ai, GF_TraverseState *tr_state) { GF_AudioInterface *aifce; - /*check interface is valid*/ if (!ai->input_ifce.FetchFrame || !ai->input_ifce.GetChannelVolume @@ -298,7 +298,6 @@ void gf_sc_audio_register(GF_AudioInput *ai, GF_TraverseState *tr_state) ) return; aifce = &ai->input_ifce; - if (ai->filter) aifce = &ai->filter->input; if (tr_state->audio_parent) { /*this assume only one parent may use an audio node*/ @@ -328,7 +327,6 @@ GF_EXPORT void gf_sc_audio_unregister(GF_AudioInput *ai) { GF_AudioInterface *aifce = &ai->input_ifce; - if (ai->filter) aifce = &ai->filter->input; if (ai->register_with_renderer) { ai->register_with_renderer = GF_FALSE; @@ -339,114 +337,3 @@ void gf_sc_audio_unregister(GF_AudioInput *ai) } } - -static char *gf_af_fetch_frame(void *callback, u32 *size, u32 audio_delay_ms) -{ - GF_AudioFilterItem *af = (GF_AudioFilterItem *)callback; - - *size = 0; - if (!af->nb_used) { - /*force filling the filter chain output until no data is available, otherwise we may end up - with data in input and no data as output because of block framing of the filter chain*/ - while (!af->nb_filled) { - u32 nb_bytes; - char *data = af->src->FetchFrame(af->src->callback, &nb_bytes, audio_delay_ms + af->filter_chain.delay_ms); - /*no input data*/ - if (!data || !nb_bytes) - return NULL; - - if (nb_bytes > af->filter_chain.min_block_size) nb_bytes = af->filter_chain.min_block_size; - memcpy(af->filter_chain.tmp_block1, data, nb_bytes); - af->src->ReleaseFrame(af->src->callback, nb_bytes); - - af->nb_filled = gf_afc_process(&af->filter_chain, nb_bytes); - } - } - - *size = af->nb_filled - af->nb_used; - return af->filter_chain.tmp_block1 + af->nb_used; -} -static void gf_af_release_frame(void *callback, u32 nb_bytes) -{ - GF_AudioFilterItem *af = (GF_AudioFilterItem *)callback; - /*mark used bytes of filter output*/ - af->nb_used += nb_bytes; - if (af->nb_used==af->nb_filled) { - af->nb_used = 0; - af->nb_filled = 0; - } -} - -static Fixed gf_af_get_speed(void *callback) -{ - GF_AudioFilterItem *af = (GF_AudioFilterItem *)callback; - return af->src->GetSpeed(af->src->callback); -} -static Bool gf_af_get_channel_volume(void *callback, Fixed *vol) -{ - GF_AudioFilterItem *af = (GF_AudioFilterItem *)callback; - return af->src->GetChannelVolume(af->src->callback, vol); -} - -static Bool gf_af_is_muted(void *callback) -{ - GF_AudioFilterItem *af = (GF_AudioFilterItem *)callback; - return af->src->IsMuted(af->src->callback); -} - -static Bool gf_af_get_config(GF_AudioInterface *ai, Bool for_reconf) -{ - GF_AudioFilterItem *af = (GF_AudioFilterItem *)ai->callback; - - Bool res = af->src->GetConfig(af->src, for_reconf); - if (!res) return GF_FALSE; - if (!for_reconf) return GF_TRUE; - - - af->input.bps = af->src->bps; - af->input.samplerate = af->src->samplerate; - - af->input.ch_cfg = af->src->ch_cfg; - af->input.chan = af->src->chan; - - if (gf_afc_setup(&af->filter_chain, af->input.bps, af->input.samplerate, af->src->chan, af->src->ch_cfg, &af->input.chan, &af->input.ch_cfg)!=GF_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_AUDIO, ("[Audio Input] Failed to configure audio filter chain\n")); - - return GF_FALSE; - } - return GF_TRUE; -} - -GF_AudioFilterItem *gf_af_new(GF_Compositor *compositor, GF_AudioInterface *src, char *filter_name) -{ - GF_AudioFilterItem *filter; - if (!src || !filter_name) return NULL; - - GF_SAFEALLOC(filter, GF_AudioFilterItem); - if (!filter) return NULL; - - filter->src = src; - filter->input.FetchFrame = gf_af_fetch_frame; - filter->input.ReleaseFrame = gf_af_release_frame; - filter->input.GetSpeed = gf_af_get_speed; - filter->input.GetChannelVolume = gf_af_get_channel_volume; - filter->input.IsMuted = gf_af_is_muted; - filter->input.GetConfig = gf_af_get_config; - filter->input.callback = filter; - - gf_afc_load(&filter->filter_chain, compositor->user, filter_name); - return filter; -} - -void gf_af_del(GF_AudioFilterItem *af) -{ - gf_afc_unload(&af->filter_chain); - gf_free(af); -} - -void gf_af_reset(GF_AudioFilterItem *af) -{ - gf_afc_reset(&af->filter_chain); - - af->nb_filled = af->nb_used = 0; -} diff --git a/src/compositor/audio_mixer.c b/src/compositor/audio_mixer.c index e0687d1..e39164c 100644 --- a/src/compositor/audio_mixer.c +++ b/src/compositor/audio_mixer.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2018 * All rights reserved * * This file is part of GPAC / Scene Compositor sub-project @@ -25,9 +25,6 @@ #include -/*max number of channels we support in mixer*/ -#define GF_SR_MAX_CHANNELS 16 - /* Notes about the mixer: 1- spatialization is out of scope for the mixer (eg that's the sound node responsability) @@ -39,20 +36,22 @@ typedef struct { GF_AudioInterface *src; - s32 *ch_buf[GF_SR_MAX_CHANNELS]; + s32 *ch_buf[GF_AUDIO_MIXER_MAX_CHANNELS]; /*resampled buffer*/ u32 buffer_size; - u32 bytes_per_sec; + u32 bytes_per_sec, bit_depth; Bool has_prev; - s32 last_channels[GF_SR_MAX_CHANNELS]; + s32 last_channels[GF_AUDIO_MIXER_MAX_CHANNELS]; u32 in_bytes_used, out_samples_written, out_samples_to_write; Fixed speed; - Fixed pan[6]; + Fixed pan[GF_AUDIO_MIXER_MAX_CHANNELS]; + s32 (*get_sample)(u8 *data, u32 nb_ch, u32 sample_offset, u32 channel, u32 planar_stride); + Bool is_planar; Bool muted; } MixerInput; @@ -63,21 +62,28 @@ struct __audiomix /*output config*/ u32 sample_rate; u32 nb_channels; - u32 bits_per_sample; - u32 channel_cfg; + u32 afmt, bit_depth; + u64 channel_layout; GF_Mutex *mx; /*if set forces stereo/mono*/ Bool force_channel_out; /*set to true by mixer when detecting an audio config change*/ Bool must_reconfig; Bool isEmpty; + Bool source_buffering; /*set to non null if this outputs directly to the driver, in which case audio formats have to be checked*/ struct _audio_render *ar; + Fixed max_speed; + s32 *output; u32 output_size; }; +#define GF_S24_MAX 8388607 +#define GF_S24_MIN -8388608 + + GF_EXPORT GF_AudioMixer *gf_mixer_new(struct _audio_render *ar) { @@ -90,10 +96,12 @@ GF_AudioMixer *gf_mixer_new(struct _audio_render *ar) am->isEmpty = GF_TRUE; am->ar = ar; am->sample_rate = 44100; - am->bits_per_sample = 16; + am->afmt = GF_AUDIO_FMT_S16; + am->bit_depth = gf_audio_fmt_bit_depth(am->afmt); am->nb_channels = 2; am->output = NULL; am->output_size = 0; + am->max_speed = FIX_MAX; return am; } @@ -102,6 +110,12 @@ Bool gf_mixer_must_reconfig(GF_AudioMixer *am) return am->must_reconfig; } +void gf_mixer_set_max_speed(GF_AudioMixer *am, Double max_speed) +{ + am->max_speed = FLT2FIX(max_speed); +} + +GF_EXPORT void gf_mixer_del(GF_AudioMixer *am) { gf_mixer_remove_all(am); @@ -118,7 +132,7 @@ void gf_mixer_remove_all(GF_AudioMixer *am) while (gf_list_count(am->sources)) { MixerInput *in = (MixerInput *)gf_list_get(am->sources, 0); gf_list_rem(am->sources, 0); - for (j=0; jch_buf[j]) gf_free(in->ch_buf[j]); } gf_free(in); @@ -149,7 +163,7 @@ void gf_mixer_force_chanel_out(GF_AudioMixer *am, u32 num_channels) u32 gf_mixer_get_block_align(GF_AudioMixer *am) { - return am->nb_channels*am->bits_per_sample/8; + return am->nb_channels * am->bit_depth/8; } GF_EXPORT @@ -169,6 +183,12 @@ Bool gf_mixer_empty(GF_AudioMixer *am) return am->isEmpty; } +GF_EXPORT +Bool gf_mixer_buffering(GF_AudioMixer *am) +{ + return am->source_buffering; +} + GF_EXPORT void gf_mixer_add_input(GF_AudioMixer *am, GF_AudioInterface *src) { @@ -198,7 +218,7 @@ void gf_mixer_remove_input(GF_AudioMixer *am, GF_AudioInterface *src) MixerInput *in = (MixerInput *)gf_list_get(am->sources, i); if (in->src != src) continue; gf_list_rem(am->sources, i); - for (j=0; jch_buf[j]) gf_free(in->ch_buf[j]); } gf_free(in); @@ -210,46 +230,184 @@ void gf_mixer_remove_input(GF_AudioMixer *am, GF_AudioInterface *src) } -static GF_Err get_best_samplerate(GF_AudioMixer *am, u32 *out_sr, u32 *out_ch, u32 *out_bps) +static GF_Err get_best_samplerate(GF_AudioMixer *am, u32 *out_sr, u32 *out_ch, u32 *out_fmt) { if (!am->ar) return GF_OK; +#ifdef ENABLE_AOUT if (!am->ar->audio_out || !am->ar->audio_out->QueryOutputSampleRate) return GF_OK; - return am->ar->audio_out->QueryOutputSampleRate(am->ar->audio_out, out_sr, out_ch, out_bps); + return am->ar->audio_out->QueryOutputSampleRate(am->ar->audio_out, out_sr, out_ch, out_fmt); +#endif + return GF_OK; } GF_EXPORT -void gf_mixer_get_config(GF_AudioMixer *am, u32 *outSR, u32 *outCH, u32 *outBPS, u32 *outChCfg) +void gf_mixer_get_config(GF_AudioMixer *am, u32 *outSR, u32 *outCH, u32 *outFMT, u64 *outChCfg) { - (*outBPS) = am->bits_per_sample; + (*outFMT) = am->afmt; (*outCH) = am->nb_channels; (*outSR) = am->sample_rate; - (*outChCfg) = am->channel_cfg; + (*outChCfg) = am->channel_layout; } GF_EXPORT -void gf_mixer_set_config(GF_AudioMixer *am, u32 outSR, u32 outCH, u32 outBPS, u32 outChCfg) +void gf_mixer_set_config(GF_AudioMixer *am, u32 outSR, u32 outCH, u32 outFMT, u64 outChCfg) { - if ((am->bits_per_sample == outBPS) && (am->nb_channels == outCH) - && (am->sample_rate==outSR) && (am->channel_cfg == outChCfg)) return; + if ((am->afmt == outFMT) && (am->nb_channels == outCH) + && (am->sample_rate==outSR) && (am->channel_layout == outChCfg)) return; gf_mixer_lock(am, GF_TRUE); - am->bits_per_sample = outBPS; + am->afmt = outFMT; + am->bit_depth = gf_audio_fmt_bit_depth(am->afmt); if (!am->force_channel_out) am->nb_channels = outCH; - if (get_best_samplerate(am, &outSR, &outCH, &outBPS) == GF_OK) { + if (get_best_samplerate(am, &outSR, &outCH, &outFMT) == GF_OK) { am->sample_rate = outSR; - if (outCH>2) am->channel_cfg = outChCfg; - else if (outCH==2) am->channel_cfg = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT; - else am->channel_cfg = GF_AUDIO_CH_FRONT_LEFT; + if (outCH>2) am->channel_layout = outChCfg; + else if (outCH==2) am->channel_layout = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT; + else am->channel_layout = GF_AUDIO_CH_FRONT_LEFT; } /*if main mixer recfg output*/ if (am->ar) am->ar->need_reconfig = GF_TRUE; + am->must_reconfig = GF_FALSE; gf_mixer_lock(am, GF_FALSE); } +static GFINLINE s32 make_s24_int(u8 *ptr) +{ + s32 val; + s16 res; + memcpy(&res, ptr+1, 2); + val = res * 256; + val |= ptr[0]; + return val; +} + +#define MIX_S16_SCALE 65535 +#define MIX_S24_SCALE 255 +#define MIX_U8_SCALE 16777215 + +s32 input_sample_s32(u8 *data, u32 nb_ch, u32 sample_offset, u32 channel, u32 planar_stride) +{ + s32 *src = (s32 *)data; + return src[sample_offset*nb_ch + channel]; +} +s32 input_sample_s32p(u8 *data, u32 nb_ch, u32 sample_offset, u32 channel, u32 planar_stride) +{ + s32 *src = (s32 *)data; + return src[sample_offset + planar_stride*channel/4]; +} +s32 input_sample_s24(u8 *data, u32 nb_ch, u32 sample_offset, u32 channel, u32 planar_stride) +{ + return make_s24_int(&data[sample_offset*nb_ch*3 + 3*channel]) * MIX_S24_SCALE; +} +s32 input_sample_s24p(u8 *data, u32 nb_ch, u32 sample_offset, u32 channel, u32 planar_stride) +{ + return make_s24_int(&data[sample_offset*3 + channel*planar_stride]) * MIX_S24_SCALE; +} + +#define TRUNC_FLT_DBL(_a) \ + if (_a<-1.0) return GF_INT_MIN;\ + else if (_a>1.0) return GF_INT_MAX;\ + return (s32) (_a * GF_INT_MAX);\ + +s32 input_sample_flt(u8 *data, u32 nb_ch, u32 sample_offset, u32 channel, u32 planar_stride) +{ + Float *src = (Float *)data; + Float samp = src[sample_offset*nb_ch + channel]; + TRUNC_FLT_DBL(samp); +} +s32 input_sample_fltp(u8 *data, u32 nb_ch, u32 sample_offset, u32 channel, u32 planar_stride) +{ + Float *src = (Float *)data; + Float samp = src[sample_offset + planar_stride*channel/4]; + TRUNC_FLT_DBL(samp); +} +s32 input_sample_dbl(u8 *data, u32 nb_ch, u32 sample_offset, u32 channel, u32 planar_stride) +{ + Double *src = (Double *)data; + Double samp = src[sample_offset*nb_ch + channel]; + TRUNC_FLT_DBL(samp); +} +s32 input_sample_dblp(u8 *data, u32 nb_ch, u32 sample_offset, u32 channel, u32 planar_stride) +{ + Double *src = (Double *)data; + Double samp = src[sample_offset + planar_stride * channel / 8]; + TRUNC_FLT_DBL(samp); +} +s32 input_sample_s16(u8 *data, u32 nb_ch, u32 sample_offset, u32 channel, u32 planar_stride) +{ + s16 *src = (s16 *)data; + s32 res = src[sample_offset*nb_ch + channel]; + return res * MIX_S16_SCALE; +} +s32 input_sample_s16p(u8 *data, u32 nb_ch, u32 sample_offset, u32 channel, u32 planar_stride) +{ + s16 *src = (s16 *)data; + s32 res = src[sample_offset + planar_stride*channel / 2]; + return res * MIX_S16_SCALE; +} +s32 input_sample_u8(u8 *data, u32 nb_ch, u32 sample_offset, u32 channel, u32 planar_stride) +{ + s32 res = data[sample_offset*nb_ch + channel]; + res -= 128; + return res * MIX_U8_SCALE; +} +s32 input_sample_u8p(u8 *data, u32 nb_ch, u32 sample_offset, u32 channel, u32 planar_stride) +{ + s32 res = data[sample_offset + planar_stride*channel]; + res -= 128; + return res * MIX_U8_SCALE; +} + +static void gf_am_configure_source(MixerInput *in) +{ + in->bit_depth = gf_audio_fmt_bit_depth(in->src->afmt); + in->bytes_per_sec = in->src->samplerate * in->src->chan * in->bit_depth / 8; + in->is_planar = gf_audio_fmt_is_planar(in->src->afmt); + switch (in->src->afmt) { + case GF_AUDIO_FMT_S32: + in->get_sample = input_sample_s32; + break; + case GF_AUDIO_FMT_S32P: + in->get_sample = input_sample_s32p; + break; + case GF_AUDIO_FMT_S24: + in->get_sample = input_sample_s24; + break; + case GF_AUDIO_FMT_S24P: + in->get_sample = input_sample_s24p; + break; + case GF_AUDIO_FMT_FLT: + in->get_sample = input_sample_flt; + break; + case GF_AUDIO_FMT_FLTP: + in->get_sample = input_sample_fltp; + break; + case GF_AUDIO_FMT_DBL: + in->get_sample = input_sample_dbl; + break; + case GF_AUDIO_FMT_DBLP: + in->get_sample = input_sample_dblp; + break; + case GF_AUDIO_FMT_S16: + in->get_sample = input_sample_s16; + break; + case GF_AUDIO_FMT_S16P: + in->get_sample = input_sample_s16p; + break; + case GF_AUDIO_FMT_U8: + in->get_sample = input_sample_u8; + break; + case GF_AUDIO_FMT_U8P: + in->get_sample = input_sample_u8p; + break; + } +} + GF_EXPORT Bool gf_mixer_reconfig(GF_AudioMixer *am) { - u32 i, count, numInit, max_sample_rate, max_channels, max_bps, cfg_changed, ch_cfg; + u32 i, count, numInit, max_sample_rate, max_channels, max_afmt, cfg_changed; + u64 ch_layout; gf_mixer_lock(am, GF_TRUE); if (am->isEmpty || !am->must_reconfig) { gf_mixer_lock(am, GF_FALSE); @@ -264,12 +422,12 @@ Bool gf_mixer_reconfig(GF_AudioMixer *am) numInit = 0; max_channels = am->nb_channels; - max_bps = am->bits_per_sample; + max_afmt = am->afmt; cfg_changed = 0; // max_sample_rate = am->sample_rate; - max_sample_rate = 0, + max_sample_rate = 0; - ch_cfg = 0; + ch_layout = 0; count = gf_list_count(am->sources); assert(count); @@ -279,7 +437,7 @@ Bool gf_mixer_reconfig(GF_AudioMixer *am) has_cfg = in->src->GetConfig(in->src, GF_TRUE); if (has_cfg) { /*check same cfg...*/ - if (in->src->samplerate * in->src->chan * in->src->bps == 8*in->bytes_per_sec) { + if (in->src->samplerate * in->src->chan * in->bit_depth == 8*in->bytes_per_sec) { numInit++; continue; } @@ -292,54 +450,71 @@ Bool gf_mixer_reconfig(GF_AudioMixer *am) // cfg_changed = 1; max_sample_rate = in->src->samplerate; } - if ((count==1) && (max_bps!=in->src->bps)) { + if ((count==1) && (max_afmt != in->src->afmt)) { cfg_changed = 1; - max_bps = in->src->bps; - } else if (max_bpssrc->bps) { + max_afmt = in->src->afmt; + } else if (max_afmtsrc->afmt) { cfg_changed = 1; - max_bps = in->src->bps; + max_afmt = in->src->afmt; } if (!am->force_channel_out) { if ((count==1) && (max_channels!=in->src->chan)) { cfg_changed = 1; max_channels = in->src->chan; - if (in->src->chan>2) ch_cfg |= in->src->ch_cfg; - } else if (max_channels < in->src->chan) { - cfg_changed = 1; - max_channels = in->src->chan; - if (in->src->chan>2) ch_cfg |= in->src->ch_cfg; +// if (in->src->forced_layout) + ch_layout |= in->src->ch_layout; + } + else { + u32 nb_ch = in->src->chan; + if (in->src->forced_layout) { + u64 cfg = in->src->ch_layout; + nb_ch = 0; + while (cfg) { + nb_ch++; + cfg >>= 1; + } + ch_layout |= in->src->ch_layout; + } + if (max_channels < nb_ch) { + cfg_changed = 1; + max_channels = nb_ch; + if (nb_ch > 2) ch_layout |= in->src->ch_layout; + } } } numInit++; - in->bytes_per_sec = in->src->samplerate * in->src->chan * in->src->bps / 8; + gf_am_configure_source(in); + /*cfg has changed, we must reconfig everything*/ if (cfg_changed || (max_sample_rate != am->sample_rate) ) { in->has_prev = GF_FALSE; - memset(&in->last_channels, 0, sizeof(s16)*GF_SR_MAX_CHANNELS); + memset(&in->last_channels, 0, sizeof(s32)*GF_AUDIO_MIXER_MAX_CHANNELS); } } if (cfg_changed || (max_sample_rate && (max_sample_rate != am->sample_rate)) ) { if (max_channels>2) { - if (ch_cfg != am->channel_cfg) { + if (!ch_layout) { + //TODO pickup default layout ? + } else if (ch_layout != am->channel_layout) { /*recompute num channel based on all input channels*/ max_channels = 0; - if (ch_cfg & GF_AUDIO_CH_FRONT_LEFT) max_channels ++; - if (ch_cfg & GF_AUDIO_CH_FRONT_RIGHT) max_channels ++; - if (ch_cfg & GF_AUDIO_CH_FRONT_CENTER) max_channels ++; - if (ch_cfg & GF_AUDIO_CH_LFE) max_channels ++; - if (ch_cfg & GF_AUDIO_CH_BACK_LEFT) max_channels ++; - if (ch_cfg & GF_AUDIO_CH_BACK_RIGHT) max_channels ++; - if (ch_cfg & GF_AUDIO_CH_BACK_CENTER) max_channels ++; - if (ch_cfg & GF_AUDIO_CH_SIDE_LEFT) max_channels ++; - if (ch_cfg & GF_AUDIO_CH_SIDE_RIGHT) max_channels ++; + if (ch_layout & GF_AUDIO_CH_FRONT_LEFT) max_channels ++; + if (ch_layout & GF_AUDIO_CH_FRONT_RIGHT) max_channels ++; + if (ch_layout & GF_AUDIO_CH_FRONT_CENTER) max_channels ++; + if (ch_layout & GF_AUDIO_CH_LFE) max_channels ++; + if (ch_layout & GF_AUDIO_CH_SURROUND_LEFT) max_channels ++; + if (ch_layout & GF_AUDIO_CH_SURROUND_RIGHT) max_channels ++; + if (ch_layout & GF_AUDIO_CH_REAR_CENTER) max_channels ++; + if (ch_layout & GF_AUDIO_CH_REAR_SURROUND_LEFT) max_channels ++; + if (ch_layout & GF_AUDIO_CH_REAR_SURROUND_RIGHT) max_channels ++; } } else { - ch_cfg = GF_AUDIO_CH_FRONT_LEFT; - if (max_channels==2) ch_cfg |= GF_AUDIO_CH_FRONT_RIGHT; + ch_layout = GF_AUDIO_CH_FRONT_LEFT; + if (max_channels==2) ch_layout |= GF_AUDIO_CH_FRONT_RIGHT; } - gf_mixer_set_config(am, max_sample_rate, max_channels, max_bps, ch_cfg); + gf_mixer_set_config(am, max_sample_rate, max_channels, max_afmt, ch_layout); } if (numInit == count) am->must_reconfig = GF_FALSE; @@ -349,32 +524,49 @@ Bool gf_mixer_reconfig(GF_AudioMixer *am) return cfg_changed; } -static GFINLINE u32 get_channel_out_pos(u32 in_ch, u32 out_cfg) +static GFINLINE u32 get_channel_out_pos(u32 in_ch, u64 out_ch_layout) { u32 i, cfg, pos; pos = 0; for (i=0; i<9; i++) { cfg = 1<<(i); - if (out_cfg & cfg) { + if (out_ch_layout & cfg) { if (cfg == in_ch) return pos; pos++; } } - return GF_SR_MAX_CHANNELS; + return GF_AUDIO_MIXER_MAX_CHANNELS; } /*this is crude, we'd need a matrix or something*/ -static GFINLINE void gf_mixer_map_channels(s32 *inChan, u32 nb_in, u32 in_cfg, u32 nb_out, u32 out_cfg) +static GFINLINE void gf_mixer_map_channels(s32 *inChan, u32 nb_in, u64 in_ch_layout, Bool forced_layout, u32 nb_out, u64 out_ch_layout) { u32 i; if (nb_in==1) { /*mono to stereo*/ if (nb_out==2) { - inChan[1] = inChan[0]; + //layout forced, don't copy + if (in_ch_layout && forced_layout) { + u32 idx = 0; + while (1) { + in_ch_layout >>= 1; + if (!in_ch_layout) break; + idx++; + } + if (idx) { +// inChan[idx] = inChan[0]; +// inChan[0] = 0; + inChan[1] = inChan[0]; + } else { + inChan[1] = inChan[0]; + } + } else { + inChan[1] = inChan[0]; + } } else if (nb_out>2) { /*if center channel use it (we assume we always have stereo channels)*/ - if (out_cfg & GF_AUDIO_CH_FRONT_CENTER) { + if (out_ch_layout & GF_AUDIO_CH_FRONT_CENTER) { inChan[2] = inChan[0]; inChan[0] = 0; for (i=3; i>=1; @@ -418,9 +610,9 @@ static GFINLINE void gf_mixer_map_channels(s32 *inChan, u32 nb_in, u32 in_cfg, u } /*less output than input channels (eg sound card doesn't support requested format*/ else if (nb_in>nb_out) { - s32 bckup[GF_SR_MAX_CHANNELS]; + s32 bckup[GF_AUDIO_MIXER_MAX_CHANNELS]; u32 pos; - u32 cfg = in_cfg; + u64 cfg = in_ch_layout; u32 ch = 0; memcpy(bckup, inChan, sizeof(s32)*nb_in); for (i=0; isrc->FetchFrame(in->src->callback, &src_size, audio_delay); - if (!in_s8 || !src_size) { + in_data = (s8 *) in->src->FetchFrame(in->src->callback, &src_size, &planar_stride, audio_delay); + if (!in_data || !src_size) { in->has_prev = GF_FALSE; /*done, stop fill*/ in->out_samples_to_write = 0; return; } - + ratio = (u32) (in->src->samplerate * FIX2INT(255*in->speed) / am->sample_rate); - src_samp = (u32) (src_size * 8 / in->src->bps / in->src->chan); + src_samp = (u32) (src_size * 8 / in->bit_depth / in->src->chan); in_ch = in->src->chan; out_ch = am->nb_channels; - if (in->src->bps == 8) { - in_s16 = NULL; - } else { - in_s16 = (s16 *) in_s8; - in_s8 = NULL; - } /*just in case, if only 1 sample available in src, copy over and discard frame since we cannot interpolate audio*/ if (src_samp==1) { in->has_prev = GF_TRUE; - for (j=0; jlast_channels[j] = in_s16 ? in_s16[j] : in_s8[j]; + for (j=0; jlast_channels[j] = in->get_sample(in_data, in_ch, 0, j, planar_stride); + } in->in_bytes_used = src_size; return; } /*while space to fill and input data, convert*/ use_prev = in->has_prev; - memset(inChanNext, 0, sizeof(s32)*GF_SR_MAX_CHANNELS); + memset(inChan, 0, sizeof(s32)*GF_AUDIO_MIXER_MAX_CHANNELS); + memset(inChanNext, 0, sizeof(s32)*GF_AUDIO_MIXER_MAX_CHANNELS); i = 0; next = prev = 0; while (1) { @@ -512,26 +700,32 @@ static void gf_mixer_fetch_input(GF_AudioMixer *am, MixerInput *in, u32 audio_de if (use_prev && prev) use_prev = GF_FALSE; - if (in_s16) { - for (j=0; jlast_channels[j] : in_s16[in_ch*prev + j]; - inChanNext[j] = in_s16[in_ch*next + j]; - inChan[j] = (frac*inChanNext[j] + (255-frac)*inChan[j]) / 255; + for (j = 0; j < in_ch; j++) { + inChan[j] = use_prev ? in->last_channels[j] : in->get_sample(in_data, in_ch, prev, j, planar_stride); + if (frac) { + inChanNext[j] = in->get_sample(in_data, in_ch, next, j, planar_stride); + inChan[j] = (s32) ( ( ((s64) inChanNext[j])*frac + ((s64)inChan[j])*(255-frac)) / 255 ); } - } else { - for (j=0; jlast_channels[j] : in_s8[in_ch*prev + j]; - inChanNext[j] = in_s8[in_ch*next + j]; - inChan[j] = (frac*inChanNext[j] + (255-frac)*inChan[j]) / 255; + //don't apply pan when forced layout is used + if (!in->src->forced_layout && (in->pan[j]!=FIX_ONE) ) { + inChan[j] = (s32) ( ((s64) inChan[j]) * FIX2INT(100 * in->pan[j]) / 100); } } - gf_mixer_map_channels(inChan, in_ch, in->src->ch_cfg, out_ch, am->channel_cfg); - //Fixe me - we nee to uderstand how to map pan from N channels to K channels ... - for (j=0; jch_buf[j] + in->out_samples_written) = (s32) (inChan[j] * FIX2INT(100*in->pan[j]) / 100 ); - } + if (in->speed <= am->max_speed) { + //map inChannel to the output channel config + gf_mixer_map_channels(inChan, in_ch, in->src->ch_layout, in->src->forced_layout, out_ch, am->channel_layout); + + for (j=0; jch_buf[j] + in->out_samples_written) = (s32) inChan[j]; + } + } else { + for (j=0; jch_buf[j] + in->out_samples_written) = 0; + } + } + in->out_samples_written ++; if (in->out_samples_written == in->out_samples_to_write) break; i++; @@ -542,7 +736,7 @@ static void gf_mixer_fetch_input(GF_AudioMixer *am, MixerInput *in, u32 audio_de if (next==src_samp) { in->in_bytes_used = src_size; } else { - in->in_bytes_used = MIN(src_size, prev*in->src->bps * in->src->chan / 8); + in->in_bytes_used = MIN(src_size, prev * in->bit_depth * in->src->chan / 8); } } else { in->has_prev = GF_TRUE; @@ -550,16 +744,15 @@ static void gf_mixer_fetch_input(GF_AudioMixer *am, MixerInput *in, u32 audio_de for (j=0; jlast_channels[j] = inChanNext[j]; in->in_bytes_used = src_size; } else { - in->in_bytes_used = prev*in->src->bps * in->src->chan / 8; + in->in_bytes_used = prev * in->bit_depth * in->src->chan / 8; if (in->in_bytes_used>src_size) { in->in_bytes_used = src_size; for (j=0; jlast_channels[j] = inChanNext[j]; } else { u32 idx; - idx = (prev>=src_samp) ? in_ch*(src_samp-1) : in_ch*prev; + idx = (prev>=src_samp) ? (src_samp-1) : prev; for (j=0; jlast_channels[j] = in_s16 ? in_s16[idx + j] : in_s8[idx + j]; + in->last_channels[j] = in->get_sample(in_data, in_ch, idx, j, planar_stride); } } } @@ -572,12 +765,13 @@ GF_EXPORT u32 gf_mixer_get_output(GF_AudioMixer *am, void *buffer, u32 buffer_size, u32 delay) { MixerInput *in, *single_source; - Fixed pan[6]; + Fixed pan[GF_AUDIO_MIXER_MAX_CHANNELS]; Bool is_muted, force_mix; u32 i, j, count, size, in_size, nb_samples, nb_written; s32 *out_mix, nb_act_src; char *data, *ptr; + am->source_buffering = GF_FALSE; //reset buffer whatever the state of the mixer is memset(buffer, 0, buffer_size); @@ -614,6 +808,7 @@ u32 gf_mixer_get_output(GF_AudioMixer *am, void *buffer, u32 buffer_size, u32 de if (single_source->src->chan != am->nb_channels) goto do_mix; if (single_source->src->GetSpeed(single_source->src->callback)!=FIX_ONE) goto do_mix; if (single_source->src->GetChannelVolume(single_source->src->callback, pan)) goto do_mix; + if (single_source->src->afmt != am->afmt) goto do_mix; single_source_mix: @@ -622,9 +817,13 @@ single_source_mix: is_muted = single_source->muted; while (buffer_size) { + u32 planar_stride; size = 0; - data = single_source->src->FetchFrame(single_source->src->callback, &size, delay); - if (!data || !size) break; + data = single_source->src->FetchFrame(single_source->src->callback, &size, &planar_stride, delay); + if (!data || !size) { + am->source_buffering = single_source->src->is_buffering; + break; + } /*don't copy more than possible*/ if (size > buffer_size) size = buffer_size; if (!is_muted) { @@ -633,7 +832,7 @@ single_source_mix: buffer_size -= size; ptr += size; single_source->src->ReleaseFrame(single_source->src->callback, size); - delay += size * 8000 / am->bits_per_sample / am->sample_rate / am->nb_channels; + delay += size * 8000 / am->bit_depth / am->sample_rate / am->nb_channels; } /*not completely filled*/ @@ -653,11 +852,10 @@ single_source_mix: do_mix: nb_act_src = 0; - nb_samples = buffer_size / (am->nb_channels * am->bits_per_sample / 8); + nb_samples = buffer_size / (am->nb_channels * am->bit_depth / 8); /*step 1, cfg*/ if (am->output_sizeoutput) gf_free(am->output); - am->output = (s32*)gf_malloc(sizeof(s32) * buffer_size); + am->output = (s32*)gf_realloc(am->output, sizeof(s32) * buffer_size); am->output_size = buffer_size; } @@ -667,17 +865,16 @@ do_mix: in = (MixerInput *)gf_list_get(am->sources, i); in->muted = in->src->IsMuted(in->src->callback); if (in->muted) continue; + if (!in->bit_depth) gf_am_configure_source(in); if (in->buffer_size < nb_samples) { - for (j=0; jch_buf[j]) gf_free(in->ch_buf[j]); - in->ch_buf[j] = (s32 *) gf_malloc(sizeof(s32) * nb_samples); + for (j=0; jch_buf[j] = (s32 *) gf_realloc(in->ch_buf[j], sizeof(s32) * nb_samples); } in->buffer_size = nb_samples; } in->speed = in->src->GetSpeed(in->src->callback); if (in->speed<0) in->speed *= -1; - in->out_samples_written = 0; in->in_bytes_used = 0; @@ -692,7 +889,7 @@ do_mix: in->muted = GF_TRUE; continue; } - //otherwise fallthrou, mixer keeps the same config + //otherwise fallthrough, mixer keeps the same config force_mix = GF_TRUE; } @@ -702,12 +899,14 @@ do_mix: assert(in->src->samplerate); in->out_samples_to_write = nb_samples; if (in->src->IsMuted(in->src->callback)) { - memset(in->pan, 0, sizeof(Fixed)*6); + memset(in->pan, 0, sizeof(Fixed)*GF_AUDIO_MIXER_MAX_CHANNELS); } else { if (!force_mix && !in->src->GetChannelVolume(in->src->callback, in->pan)) { /*track first active source with same cfg as mixer*/ if (!single_source && (in->src->samplerate == am->sample_rate) - && (in->src->chan == am->nb_channels) && (in->speed == FIX_ONE) + && (in->src->chan == am->nb_channels) + && (in->speed == FIX_ONE) + && (in->src->afmt == am->afmt) ) single_source = in; } @@ -727,7 +926,7 @@ do_mix: if ((nb_act_src==1) && single_source) goto single_source_mix; /*step 2, fill all buffers*/ - while (nb_act_src) { + while (1) { u32 nb_to_fill = 0; /*fill*/ for (i=0; ioutput; in = (MixerInput *)gf_list_get(am->sources, i); - if (!in->out_samples_to_write) continue; + if (!in->out_samples_written) continue; /*only write what has been filled in the source buffer (may be less than output size)*/ - for (j=0; jout_samples_written; j++) { - u32 k; - for (k=0; knb_channels; k++) { - (*out_mix) += * (in->ch_buf[k] + j); + for (j = 0; j < in->out_samples_written; j++) { + for (k = 0; k < am->nb_channels; k++) { + (*out_mix) += *(in->ch_buf[k] + j); out_mix += 1; } } @@ -776,13 +975,123 @@ do_mix: return 0; } - //we do not re-normalize based on the numbner of input, this is the author's responsability + //TODO big-endian support (output is assumed to be little endian PCM) + + //we do not re-normalize based on the number of input, this is the author's responsability out_mix = am->output; - if (am->bits_per_sample==16) { - s16 *out_s16 = (s16 *) buffer; - for (i=0; inb_channels; j++) { - s32 samp = (*out_mix ); + if (am->afmt == GF_AUDIO_FMT_S32) { + s32 *out_s32 = (s32 *)buffer; + for (i = 0; i < nb_written; i++) { + for (j = 0; j < am->nb_channels; j++) { + s32 samp = (*out_mix); + (*out_s32) = samp; + out_s32 += 1; + out_mix += 1; + } + } + } + else if (am->afmt == GF_AUDIO_FMT_S32P) { + s32 *out_s32 = (s32 *) buffer; + for (j = 0; j < am->nb_channels; j++) { + out_mix = am->output + j; + for (i = 0; i < nb_written; i++) { + s32 samp = (*out_mix); + (*out_s32) = samp; + out_s32 += 1; + out_mix += am->nb_channels; + } + } + } + else if (am->afmt == GF_AUDIO_FMT_FLT) { + Float *out_flt = (Float *)buffer; + for (i = 0; i < nb_written; i++) { + for (j = 0; j < am->nb_channels; j++) { + s32 samp = (*out_mix); + (*out_flt) = ((Float)samp) / GF_INT_MAX; + out_flt += 1; + out_mix += 1; + } + } + } + else if (am->afmt == GF_AUDIO_FMT_FLTP) { + Float *out_flt = (Float *)buffer; + for (j = 0; j < am->nb_channels; j++) { + out_mix = am->output + j; + for (i = 0; i < nb_written; i++) { + s32 samp = (*out_mix); + (*out_flt) = ((Float)samp) / GF_INT_MAX; + out_flt += 1; + out_mix += am->nb_channels; + } + } + } + else if (am->afmt == GF_AUDIO_FMT_DBL) { + Double *out_dbl = (Double *)buffer; + for (i = 0; i < nb_written; i++) { + for (j = 0; j < am->nb_channels; j++) { + s32 samp = (*out_mix); + (*out_dbl) = ((Double)samp) / GF_INT_MAX; + out_dbl += 1; + out_mix += 1; + } + } + } + else if (am->afmt == GF_AUDIO_FMT_DBLP) { + Double *out_dbl = (Double *)buffer; + for (j = 0; j < am->nb_channels; j++) { + out_mix = am->output + j; + for (i = 0; i < nb_written; i++) { + s32 samp = (*out_mix); + (*out_dbl) = ((Double)samp) / GF_INT_MAX; + out_dbl += 1; + out_mix += am->nb_channels; + } + } + } + else if (am->afmt == GF_AUDIO_FMT_S24) { + s8 *out_s24 = (s8 *)buffer; + for (i = 0; inb_channels; j++) { + s32 samp = (*out_mix); +// char samp_c[4]; + samp /= MIX_S24_SCALE; + if (samp > GF_S24_MAX) samp = GF_S24_MAX; + else if (samp < GF_S24_MIN) samp = GF_S24_MIN; +// memcpy(samp_c, &samp, 3); + out_s24[2] = (samp>>16) & 0xFF; + out_s24[1] = (samp>>8) & 0xFF; + out_s24[0] = samp & 0xFF; + out_s24 += 3; + out_mix += 1; + } + } + } else if (am->afmt == GF_AUDIO_FMT_S24P) { + s8 *out_s24 = (s8 *)buffer; + for (j = 0; jnb_channels; j++) { + out_mix = am->output + j; + for (i = 0; i GF_S24_MAX) samp = GF_S24_MAX; + else if (samp < GF_S24_MIN) samp = GF_S24_MIN; + lsb = (samp) & 0xFF; + samp >>= 8; +// *((s16 *) (out_s24 +1) ) = (s16)samp; + *((s16 *) s16tmp) = (s16)samp; + memcpy(out_s24 +1, s16tmp, 2); + out_s24[0] = lsb; + out_s24 += 3; + out_mix += am->nb_channels; + } + } + } else if (am->afmt == GF_AUDIO_FMT_S16) { + s16 *out_s16 = (s16 *)buffer; + for (i = 0; inb_channels; j++) { + s32 samp = (*out_mix); + samp /= MIX_S16_SCALE; if (samp > GF_SHORT_MAX) samp = GF_SHORT_MAX; else if (samp < GF_SHORT_MIN) samp = GF_SHORT_MIN; (*out_s16) = samp; @@ -790,21 +1099,50 @@ do_mix: out_mix += 1; } } - } else { - s8 *out_s8 = (s8 *) buffer; + } else if (am->afmt == GF_AUDIO_FMT_S16P) { + s16 *out_s16 = (s16 *)buffer; + for (j = 0; jnb_channels; j++) { + out_mix = am->output + j; + for (i = 0; i GF_SHORT_MAX) samp = GF_SHORT_MAX; + else if (samp < GF_SHORT_MIN) samp = GF_SHORT_MIN; + (*out_s16) = samp; + out_s16 += 1; + out_mix += am->nb_channels; + } + } + } else if (am->afmt == GF_AUDIO_FMT_U8) { + u8 *out_u8 = (u8 *) buffer; for (i=0; inb_channels; j++) { - s32 samp = (*out_mix ) / 255; + s32 samp = (*out_mix ) / MIX_U8_SCALE; + if (samp > 127) samp = 127; + else if (samp < -128) samp = -128; + samp += 128; + (*out_u8) = (u8) samp; + out_u8 += 1; + out_mix += 1; + } + } + } else if (am->afmt == GF_AUDIO_FMT_U8P) { + s8 *out_s8 = (s8 *) buffer; + for (j=0; jnb_channels; j++) { + out_mix = am->output + j; + for (i=0; i 127) samp = 127; else if (samp < -128) samp = -128; + samp += 128; (*out_s8) = samp; out_s8 += 1; - out_mix += 1; + out_mix += am->nb_channels; } } } - nb_written *= am->nb_channels*am->bits_per_sample/8; + nb_written *= am->nb_channels * am->bit_depth / 8; gf_mixer_lock(am, GF_FALSE); return nb_written; diff --git a/src/compositor/audio_render.c b/src/compositor/audio_render.c index 8b94acd..7316231 100644 --- a/src/compositor/audio_render.c +++ b/src/compositor/audio_render.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2018 * All rights reserved * * This file is part of GPAC / Scene Compositor sub-project @@ -25,538 +25,137 @@ #include -GF_Err gf_afc_load(GF_AudioFilterChain *afc, GF_User *user, char *filterstring) +void gf_ar_rcfg_done(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck) { - struct _audiofilterentry *prev_filter = NULL; - - while (filterstring) { - u32 i, count; - GF_AudioFilter *filter; - char *sep = strstr(filterstring, ";;"); - if (sep) sep[0] = 0; - - count = gf_modules_get_count(user->modules); - filter = NULL; - for (i=0; imodules, i, GF_AUDIO_FILTER_INTERFACE); - if (filter) { - if (filter->SetFilter - && filter->Configure - && filter->Process - && filter->Reset - && filter->SetOption - && filter->GetOption - && filter->SetFilter(filter, filterstring) - ) - break; - - gf_modules_close_interface((GF_BaseInterface *)filter); - } - filter = NULL; - } - if (filter) { - struct _audiofilterentry *entry; - GF_SAFEALLOC(entry, struct _audiofilterentry); - if (!entry) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate audio filter entry\n")); - } else { - entry->filter = filter; - if (prev_filter) prev_filter->next = entry; - else afc->filters = entry; - prev_filter = entry; - } - } - if (sep) { - sep[0] = ';'; - filterstring = sep + 2; - } else { - break; - } - } - return GF_OK; -} - -GF_Err gf_afc_setup(GF_AudioFilterChain *afc, u32 bps, u32 sr, u32 chan, u32 ch_cfg, u32 *ch_out, u32 *ch_cfg_out) -{ - struct _audiofilterentry *entry; - u32 block_len; - u32 och, ocfg, in_ch; - Bool not_in_place; - - if (afc->tmp_block1) gf_free(afc->tmp_block1); - afc->tmp_block1 = NULL; - if (afc->tmp_block2) gf_free(afc->tmp_block2); - afc->tmp_block2 = NULL; - - *ch_out = *ch_cfg_out = 0; - in_ch = chan; - afc->min_block_size = 0; - afc->max_block_size = 0; - afc->delay_ms = 0; - - not_in_place = GF_FALSE; - - entry = afc->filters; - while (entry) { - if (entry->in_block) { - gf_free(entry->in_block); - entry->in_block = NULL; - } - - if (entry->filter->Configure(entry->filter, sr, bps, chan, ch_cfg, &och, &ocfg, &block_len, &entry->delay_ms, &entry->in_place)==GF_OK) { - u32 out_block_size; - entry->in_block_size = chan * bps * block_len / 8; - if (!afc->min_block_size || (afc->min_block_size > entry->in_block_size)) - afc->min_block_size = entry->in_block_size; - - out_block_size = och * bps * block_len / 8; - if (afc->max_block_size < out_block_size) afc->max_block_size = out_block_size; - - entry->enable = GF_TRUE; - chan = och; - ch_cfg = ocfg; - - if (!entry->in_place) not_in_place = GF_TRUE; - - afc->delay_ms += entry->delay_ms; - } else { - entry->enable = GF_FALSE; - } - entry = entry->next; - } - - if (!afc->max_block_size) afc->max_block_size = 1000; - if (!afc->min_block_size) afc->min_block_size = afc->max_block_size * in_ch / chan; - afc->tmp_block1 = (char*)gf_malloc(sizeof(char) * afc->max_block_size * 2); - if (!afc->tmp_block1) return GF_OUT_OF_MEM; - if (not_in_place) { - afc->tmp_block2 = (char*)gf_malloc(sizeof(char) * afc->max_block_size * 2); - if (!afc->tmp_block2) return GF_OUT_OF_MEM; - } - - /*alloc buffers*/ - entry = afc->filters; - while (entry) { - if (entry->enable && entry->in_block_size) { - entry->in_block = (char*)gf_malloc(sizeof(char) * (entry->in_block_size + afc->max_block_size) ); - if (!entry->in_block) return GF_OUT_OF_MEM; - } - entry = entry->next; + u32 size; + GF_AudioRenderer *ar = (GF_AudioRenderer *) gf_filter_pck_get_data(pck, &size); + assert(!size); + assert(ar->wait_for_rcfg); + ar->wait_for_rcfg --; + if (ar->wait_for_rcfg) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[Compositor] Reconfigure negociation %d still pending\n", ar->wait_for_rcfg)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[Compositor] Reconfigure negociation done\n")); } - *ch_out = chan; - *ch_cfg_out = ch_cfg; - afc->enable_filters = GF_TRUE; - return GF_OK; } -u32 gf_afc_process(GF_AudioFilterChain *afc, u32 nb_bytes) +static GF_Err gf_ar_setup_output_format(GF_AudioRenderer *ar) { - struct _audiofilterentry *entry = afc->filters; - - while (entry) { - char *inptr, *outptr; - if (!nb_bytes || !entry->enable) { - entry = entry->next; - continue; - } - inptr = afc->tmp_block1; - outptr = entry->in_place ? afc->tmp_block1 : afc->tmp_block2; - - /*sample-based input, process directly the data*/ - if (!entry->in_block) { - entry->filter->Process(entry->filter, inptr, nb_bytes, outptr, &nb_bytes); - } else { - u32 processed = 0; - u32 nb_bytes_out = 0; - - assert(nb_bytes + entry->nb_bytes <= entry->in_block_size + afc->max_block_size); - /*copy bytes in input*/ - memcpy(entry->in_block + entry->nb_bytes, inptr, nb_bytes); - entry->nb_bytes += nb_bytes; - - /*and process*/ - while (entry->nb_bytes >= entry->in_block_size) { - u32 done; - entry->filter->Process(entry->filter, entry->in_block + processed, entry->in_block_size, outptr + nb_bytes_out, &done); - done = entry->in_block_size; - nb_bytes_out += done; - entry->nb_bytes -= entry->in_block_size; - processed += entry->in_block_size; - } - /*move remaining data at the beginning of the buffer*/ - if (processed && entry->nb_bytes) - memmove(entry->in_block, entry->in_block+processed, entry->nb_bytes); - - nb_bytes = nb_bytes_out; - } - - /*swap ptr so that input data of next step (filter, output write) is always in tmp_block1*/ - if (inptr != outptr) { - afc->tmp_block1 = outptr; - afc->tmp_block2 = inptr; - } - entry = entry->next; + u32 freq, a_fmt, nb_chan; + u64 ch_cfg; + u32 bsize; + + freq = ar->compositor->asr; + a_fmt = ar->compositor->afmt; + nb_chan = ar->compositor->ach; + ch_cfg = ar->compositor->alayout; + bsize = ar->compositor->asize; + if (!bsize) bsize = 1024; + + if (!freq || !a_fmt || !nb_chan || !ch_cfg) { + gf_mixer_get_config(ar->mixer, &freq, &nb_chan, &a_fmt, &ch_cfg); + + /*user disabled multichannel audio*/ + if (!ar->compositor->amc && (nb_chan>2) ) nb_chan = 2; + } else { + if (!ar->config_forced) ar->config_forced++; } - return nb_bytes; -} -void gf_afc_unload(GF_AudioFilterChain *afc) -{ - while (afc->filters) { - struct _audiofilterentry *tmp = afc->filters; - afc->filters = tmp->next; - gf_modules_close_interface((GF_BaseInterface *)tmp->filter); - if (tmp->in_block) gf_free(tmp->in_block); - gf_free(tmp); - } - if (afc->tmp_block1) gf_free(afc->tmp_block1); - if (afc->tmp_block2) gf_free(afc->tmp_block2); - memset(afc, 0, sizeof(GF_AudioFilterChain)); -} -void gf_afc_reset(GF_AudioFilterChain *afc) -{ - struct _audiofilterentry *filter = afc->filters; - while (filter) { - filter->filter->Reset(filter->filter); - filter->nb_bytes = 0; + gf_mixer_set_config(ar->mixer, freq, nb_chan, a_fmt, ch_cfg); - filter = filter->next; + if (ar->samplerate) { + ar->time_at_last_config_sr = ar->current_time_sr * freq / ar->samplerate; } -} - - -static GF_Err gf_ar_setup_output_format(GF_AudioRenderer *ar) -{ - GF_Err e; - u32 freq, nb_bits, nb_chan, ch_cfg; - u32 in_ch, in_cfg, in_bps, in_freq; - - gf_mixer_get_config(ar->mixer, &freq, &nb_chan, &nb_bits, &ch_cfg); - - /*user disabled multichannel audio*/ - if (ar->disable_multichannel && (nb_chan>2) ) nb_chan = 2; - - in_ch = nb_chan; - in_cfg = ch_cfg; - in_bps = nb_bits; - in_freq = freq; - - if (ar->filter_chain.filters) { - u32 osr, obps, och, ocfg; - - e = gf_afc_setup(&ar->filter_chain, nb_bits, freq, nb_chan, ch_cfg, &och, &ocfg); - osr = freq; - obps = nb_bits; - nb_chan = och; - - /*try to reconfigure audio output*/ - if (!e) - e = ar->audio_out->ConfigureOutput(ar->audio_out, &osr, &och, &obps, ocfg); - - /*output module cannot support filter output, disable it ...*/ - if (e || (osr != freq) || (och != nb_chan) || (obps != nb_bits)) { - nb_bits = in_bps; - freq = in_freq; - nb_chan = in_ch; - ar->filter_chain.enable_filters = GF_FALSE; - e = ar->audio_out->ConfigureOutput(ar->audio_out, &freq, &nb_chan, &nb_bits, ch_cfg); - } - } else { - e = ar->audio_out->ConfigureOutput(ar->audio_out, &freq, &nb_chan, &nb_bits, ch_cfg); + if (!ar->compositor->abuf) { +#ifdef GPAC_CONFIG_ANDROID + ar->compositor->abuf = 200; +#else + ar->compositor->abuf = 100; +#endif } - - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[AudioRender] reconfigure error %d\n", e)); - if (nb_chan>2) { - nb_chan=2; - in_ch=2; - ch_cfg=0; - e = ar->audio_out->ConfigureOutput(ar->audio_out, &freq, &nb_chan, &nb_bits, ch_cfg); - } - if (e) return e; + ar->samplerate = freq; + ar->bytes_per_samp = nb_chan * gf_audio_fmt_bit_depth(a_fmt) / 8; + ar->bytes_per_second = freq * ar->bytes_per_samp; + ar->max_bytes_out = ar->bytes_per_second * ar->compositor->abuf / 1000; + while (ar->max_bytes_out % (2*ar->bytes_per_samp) ) ar->max_bytes_out++; + ar->buffer_size = ar->bytes_per_samp * bsize; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[Compositor] Reconfigure audio to %d Hz %d channels %s\n", freq, nb_chan, gf_audio_fmt_name(a_fmt) )); + + if (ar->aout) { + gf_filter_pid_set_property(ar->aout, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(freq) ); + gf_filter_pid_set_property(ar->aout, GF_PROP_PID_TIMESCALE, &PROP_UINT(freq) ); + gf_filter_pid_set_property(ar->aout, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(nb_chan) ); + gf_filter_pid_set_property(ar->aout, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(a_fmt) ); + gf_filter_pid_set_property(ar->aout, GF_PROP_PID_CHANNEL_LAYOUT, &PROP_LONGUINT(ch_cfg) ); + gf_filter_pid_set_property(ar->aout, GF_PROP_PID_AUDIO_VOLUME, &PROP_UINT(ar->volume) ); + gf_filter_pid_set_property(ar->aout, GF_PROP_PID_AUDIO_PAN, &PROP_UINT(ar->pan) ); + + gf_filter_pid_set_max_buffer(ar->aout, 1000*ar->compositor->abuf); } - gf_mixer_set_config(ar->mixer, freq, nb_chan, nb_bits, in_cfg); - ar->audio_delay = ar->audio_out->GetAudioDelay(ar->audio_out); - - ar->audio_out->SetVolume(ar->audio_out, ar->volume); - ar->audio_out->SetPan(ar->audio_out, ar->pan); ar->time_at_last_config = ar->current_time; ar->bytes_requested = 0; - ar->bytes_per_second = freq * nb_chan * nb_bits / 8; - if (ar->audio_listeners) { - u32 k=0; - GF_AudioListener *l; - while ((l = (GF_AudioListener*)gf_list_enum(ar->audio_listeners, &k))) { - l->on_audio_reconfig(l->udta, in_freq, in_bps, in_ch, in_cfg); - } + if (ar->aout) { + GF_FilterPacket *pck; + //issue a dummy packet to tag the point at which we reconfigured + pck = gf_filter_pck_new_shared(ar->aout, (u8 *) ar, 0, gf_ar_rcfg_done); + ar->wait_for_rcfg ++; + gf_filter_pck_set_readonly(pck); + gf_filter_pck_send(pck); } return GF_OK; } static void gf_ar_pause(GF_AudioRenderer *ar, Bool DoFreeze, Bool for_reconfig, Bool reset_hw_buffer) { + GF_FilterEvent evt; gf_mixer_lock(ar->mixer, GF_TRUE); if (DoFreeze) { if (!ar->Frozen) { ar->freeze_time = gf_sys_clock_high_res(); - if (!for_reconfig && ar->audio_out && ar->audio_out->Play) ar->audio_out->Play(ar->audio_out, 0); - ar->Frozen = GF_TRUE; + if (!for_reconfig && ar->aout) { + GF_FEVT_INIT(evt, GF_FEVT_STOP, ar->aout); + gf_filter_pid_send_event(ar->aout, &evt); + } GF_LOG(GF_LOG_DEBUG, GF_LOG_SYNC, ("[Audio] pausing master clock - time "LLD" (sys time "LLD")\n", ar->freeze_time, gf_sys_clock_high_res())); + ar->Frozen = GF_TRUE; } } else { if (ar->Frozen) { - if (!for_reconfig && ar->audio_out && ar->audio_out->Play) ar->audio_out->Play(ar->audio_out, reset_hw_buffer ? 2 : 1); - ar->Frozen = GF_FALSE; - ar->start_time += gf_sys_clock_high_res() - ar->freeze_time; - GF_LOG(GF_LOG_DEBUG, GF_LOG_SYNC, ("[Audio] resuming master clock - new time "LLD" (sys time "LLD") \n", ar->start_time, gf_sys_clock_high_res())); - } - } - gf_mixer_lock(ar->mixer, GF_FALSE); -} - - -static u32 gf_ar_fill_output(void *ptr, char *buffer, u32 buffer_size) -{ - u32 written; - GF_AudioRenderer *ar = (GF_AudioRenderer *) ptr; - if (!ar->need_reconfig) { - u32 delay_ms = ar->disable_resync ? 0 : ar->audio_delay; - - if (ar->Frozen) { - memset(buffer, 0, buffer_size); - return buffer_size; - } - - gf_mixer_lock(ar->mixer, GF_TRUE); - - if (ar->filter_chain.enable_filters) { - char *ptr = buffer; - written = 0; - delay_ms += ar->filter_chain.delay_ms; - - while (buffer_size) { - u32 to_copy; - if (!ar->nb_used) { - u32 nb_bytes; - - /*fill input block*/ - nb_bytes = gf_mixer_get_output(ar->mixer, ar->filter_chain.tmp_block1, ar->filter_chain.min_block_size, delay_ms); - if (!nb_bytes) - return written; - - /*delay used to check for late frames - we only use it on the first call to gf_mixer_get_output()*/ - delay_ms = 0; - - ar->nb_filled = gf_afc_process(&ar->filter_chain, nb_bytes); - if (!ar->nb_filled) continue; - } - to_copy = ar->nb_filled - ar->nb_used; - if (to_copy>buffer_size) to_copy = buffer_size; - memcpy(ptr, ar->filter_chain.tmp_block1 + ar->nb_used, to_copy); - ptr += to_copy; - buffer_size -= to_copy; - written += to_copy; - ar->nb_used += to_copy; - if (ar->nb_used==ar->nb_filled) ar->nb_used = 0; - } - } else { - /*written = */gf_mixer_get_output(ar->mixer, buffer, buffer_size, delay_ms); - } - gf_mixer_lock(ar->mixer, GF_FALSE); - - //done with one sim step, go back in pause - if (ar->step_mode) { - ar->step_mode = GF_FALSE; - gf_ar_pause(ar, GF_TRUE, GF_FALSE, GF_FALSE); - } - - if (!ar->need_reconfig) { - if (ar->audio_listeners) { - u32 k=0; - GF_AudioListener *l; - while ((l = (GF_AudioListener*)gf_list_enum(ar->audio_listeners, &k))) { - l->on_audio_frame(l->udta, buffer, buffer_size, gf_sc_ar_get_clock(ar), delay_ms); - } + if (!for_reconfig && ar->aout) { + GF_FEVT_INIT(evt, GF_FEVT_PLAY, ar->aout); + evt.play.hw_buffer_reset = reset_hw_buffer ? 1 : 0; + gf_filter_pid_send_event(ar->aout, &evt); } - ar->bytes_requested += buffer_size; - ar->current_time = ar->time_at_last_config + (u32) (ar->bytes_requested * 1000 / ar->bytes_per_second); + ar->start_time += gf_sys_clock_high_res() - ar->freeze_time; + GF_LOG(GF_LOG_DEBUG, GF_LOG_SYNC, ("[Audio] resuming master clock - new time "LLD" (sys time "LLD") \n", ar->start_time, gf_sys_clock_high_res())); + ar->Frozen = GF_FALSE; } - //always return buffer size (eg requested input size to be filled), since the clock is always increased by buffer_size (cf above line) - return buffer_size; } - return 0; -} - -void gf_sc_flush_next_audio(GF_Compositor *compositor) -{ - if (!compositor->audio_renderer->audio_out) - return; - - gf_mixer_lock(compositor->audio_renderer->mixer, GF_TRUE); - compositor->audio_renderer->step_mode = GF_TRUE; - //resume for one frame - if (compositor->audio_renderer->Frozen) { - gf_ar_pause(compositor->audio_renderer, GF_FALSE, GF_FALSE, GF_FALSE); - } - gf_mixer_lock(compositor->audio_renderer->mixer, GF_FALSE); -} - -Bool gf_sc_check_audio_pending(GF_Compositor *compositor) -{ - Bool res = GF_FALSE; - gf_mixer_lock(compositor->audio_renderer->mixer, GF_TRUE); - if (compositor->audio_renderer->step_mode) - res = GF_FALSE; - gf_mixer_lock(compositor->audio_renderer->mixer, GF_FALSE); - - return res; -} - -u32 gf_ar_proc(void *p) -{ - GF_AudioRenderer *ar = (GF_AudioRenderer *) p; - - ar->audio_th_state = 1; - - GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[AudioRender] Entering audio thread ID %d\n", gf_th_id() )); - - gf_mixer_lock(ar->mixer, GF_TRUE); - ar->need_reconfig = GF_TRUE; - gf_sc_ar_reconfig(ar); gf_mixer_lock(ar->mixer, GF_FALSE); - - while (ar->audio_th_state == 1) { - //do mix even if mixer is empty, otherwise we will push the same buffer over and over to the sound card - /* - if (ar->Frozen ) { - gf_sleep(0); - } else - */ { - if (ar->need_reconfig) gf_sc_ar_reconfig(ar); - ar->audio_out->WriteAudio(ar->audio_out); - } - } - GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[AudioRender] Exiting audio thread\n")); - ar->audio_out->Shutdown(ar->audio_out); - ar->audio_th_state = 3; - return 0; } -GF_AudioRenderer *gf_sc_ar_load(GF_User *user) +GF_AudioRenderer *gf_sc_ar_load(GF_Compositor *compositor, u32 init_flags) { - const char *sOpt; - u32 i, count; - u32 num_buffers, total_duration; - GF_Err e; GF_AudioRenderer *ar; ar = (GF_AudioRenderer *) gf_malloc(sizeof(GF_AudioRenderer)); memset(ar, 0, sizeof(GF_AudioRenderer)); - num_buffers = total_duration = 0; - sOpt = gf_cfg_get_key(user->config, "Audio", "ForceConfig"); - if (sOpt && !stricmp(sOpt, "yes")) { - sOpt = gf_cfg_get_key(user->config, "Audio", "NumBuffers"); - num_buffers = sOpt ? atoi(sOpt) : 6; - sOpt = gf_cfg_get_key(user->config, "Audio", "TotalDuration"); - total_duration = sOpt ? atoi(sOpt) : 400; - } - - sOpt = gf_cfg_get_key(user->config, "Audio", "NoResync"); - ar->disable_resync = (sOpt && !stricmp(sOpt, "yes")) ? GF_TRUE : GF_FALSE; - sOpt = gf_cfg_get_key(user->config, "Audio", "DisableMultiChannel"); - ar->disable_multichannel = (sOpt && !stricmp(sOpt, "yes")) ? GF_TRUE : GF_FALSE; + ar->compositor = compositor; ar->mixer = gf_mixer_new(ar); - ar->user = user; - - ar->volume = 100; - sOpt = gf_cfg_get_key(user->config, "Audio", "Volume"); - if (!sOpt) gf_cfg_set_key(user->config, "Audio", "Volume", "100"); - else ar->volume = atoi(sOpt); - - sOpt = gf_cfg_get_key(user->config, "Audio", "Pan"); - ar->pan = sOpt ? atoi(sOpt) : 50; - - if (! (user->init_flags & GF_TERM_NO_AUDIO) ) { - - /*get a prefered compositor*/ - sOpt = gf_cfg_get_key(user->config, "Audio", "DriverName"); - if (sOpt) { - ar->audio_out = (GF_AudioOutput *) gf_modules_load_interface_by_name(user->modules, sOpt, GF_AUDIO_OUTPUT_INTERFACE); - if (!ar->audio_out) { - ar->audio_out = NULL; - sOpt = NULL; - } - } - if (!ar->audio_out) { - GF_AudioOutput *raw_out = NULL; - count = gf_modules_get_count(ar->user->modules); - for (i=0; iaudio_out = (GF_AudioOutput *) gf_modules_load_interface(ar->user->modules, i, GF_AUDIO_OUTPUT_INTERFACE); - if (!ar->audio_out) continue; - - //in enum mode, only use raw out if everything else failed ... - if (!stricmp(ar->audio_out->module_name, "Raw Audio Output")) { - raw_out = ar->audio_out; - ar->audio_out = NULL; - continue; - } - GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[AudioRender] Audio output module %s loaded\n", ar->audio_out->module_name)); - /*check that's a valid audio compositor*/ - if ((ar->audio_out->SelfThreaded && ar->audio_out->SetPriority) || ar->audio_out->WriteAudio) { - /*remember the module we use*/ - gf_cfg_set_key(user->config, "Audio", "DriverName", ar->audio_out->module_name); - break; - } - gf_modules_close_interface((GF_BaseInterface *)ar->audio_out); - ar->audio_out = NULL; - } - if (raw_out) { - if (ar->audio_out) gf_modules_close_interface((GF_BaseInterface *)raw_out); - else ar->audio_out = raw_out; - } - } - - /*if not init we run with a NULL audio compositor*/ - if (ar->audio_out) { - ar->audio_out->FillBuffer = gf_ar_fill_output; - ar->audio_out->audio_renderer = ar; - GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[AudioRender] Setting up audio module %s\n", ar->audio_out->module_name)); - e = ar->audio_out->Setup(ar->audio_out, ar->user->os_window_handler, num_buffers, total_duration); - - - /*load main audio filter*/ - gf_afc_load(&ar->filter_chain, user, (char*)gf_cfg_get_key(user->config, "Audio", "Filter")); - - - if (e != GF_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("Could not setup audio out %s\n", ar->audio_out->module_name)); - gf_modules_close_interface((GF_BaseInterface *)ar->audio_out); - ar->audio_out = NULL; - } else { - if (!ar->audio_out->SelfThreaded) { - ar->th = gf_th_new("AudioRenderer"); - gf_th_run(ar->th, gf_ar_proc, ar); - } else { - gf_ar_setup_output_format(ar); - if (ar->audio_out->SetPriority) ar->audio_out->SetPriority(ar->audio_out, GF_THREAD_PRIORITY_REALTIME); - } - } - } - if (!ar->audio_out) { - gf_cfg_set_key(user->config, "Audio", "DriverName", "No Audio Output Available"); - } else { - if (user->init_flags & GF_TERM_USE_AUDIO_HW_CLOCK) - ar->clock_use_audio_out = GF_TRUE; - } + ar->non_rt_output = GF_TRUE; + ar->volume = MIN(100, compositor->avol); + ar->pan = MIN(100, compositor->apan); + if (! (init_flags & GF_TERM_NO_AUDIO) ) { + gf_ar_setup_output_format(ar); } - /*init compositor timer*/ - ar->start_time = gf_sys_clock_high_res(); + gf_mixer_set_max_speed(ar->mixer, compositor->max_aspeed); ar->current_time = 0; return ar; } @@ -565,42 +164,28 @@ void gf_sc_ar_del(GF_AudioRenderer *ar) { if (!ar) return; - GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[AudioRender] Destroying compositor\n")); + GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[Compositor] Destroying compositor\n")); /*resume if paused (might cause deadlock otherwise)*/ if (ar->Frozen) gf_sc_ar_control(ar, GF_SC_AR_RESUME); - /*stop and shutdown*/ - if (ar->audio_out) { - /*kill audio thread*/ - if (!ar->audio_out->SelfThreaded) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[AudioRender] stopping audio thread\n")); - ar->audio_th_state = 2; - while (ar->audio_th_state != 3) { - gf_sleep(33); - } - GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[AudioRender] audio thread stopped\n")); - gf_th_del(ar->th); - GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[AudioRender] audio thread destroyed\n")); - } - /*lock access before shutdown and emulate a reconfig (avoids mixer lock from self-threaded modules)*/ - ar->need_reconfig = GF_TRUE; - gf_mixer_lock(ar->mixer, GF_TRUE); - if (ar->audio_out->SelfThreaded) ar->audio_out->Shutdown(ar->audio_out); - gf_modules_close_interface((GF_BaseInterface *)ar->audio_out); - ar->audio_out = NULL; - gf_mixer_lock(ar->mixer, GF_FALSE); - } - gf_mixer_del(ar->mixer); - if (ar->audio_listeners) gf_list_del(ar->audio_listeners); - gf_afc_unload(&ar->filter_chain); + gf_mixer_del(ar->mixer); gf_free(ar); - GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[AudioRender] Renderer destroyed\n")); + GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[Compositor] Renderer destroyed\n")); } void gf_sc_ar_reset(GF_AudioRenderer *ar) { + if (!ar) return; + gf_mixer_remove_all(ar->mixer); + if (ar->scene_ready) { + ar->scene_ready = 0; + ar->current_time = 0; + ar->current_time_sr = 0; + ar->bytes_requested = 0; + ar->nb_audio_objects = 0; + } } void gf_sc_ar_control(GF_AudioRenderer *ar, u32 PauseType) @@ -610,37 +195,35 @@ void gf_sc_ar_control(GF_AudioRenderer *ar, u32 PauseType) void gf_sc_ar_set_volume(GF_AudioRenderer *ar, u32 Volume) { - char sOpt[10]; - gf_mixer_lock(ar->mixer, GF_TRUE); - ar->volume = MIN(Volume, 100); - if (ar->audio_out) ar->audio_out->SetVolume(ar->audio_out, ar->volume); - sprintf(sOpt, "%d", ar->volume); - gf_cfg_set_key(ar->user->config, "Audio", "Volume", sOpt); - - gf_mixer_lock(ar->mixer, GF_FALSE); + if (Volume>100) Volume=100; + if (ar->volume==Volume) return; + ar->volume = Volume; + if (ar->aout) gf_filter_pid_set_property(ar->aout, GF_PROP_PID_AUDIO_VOLUME, &PROP_UINT(ar->volume) ); } void gf_sc_ar_mute(GF_AudioRenderer *ar, Bool mute) { - gf_mixer_lock(ar->mixer, GF_TRUE); ar->mute = mute; - if (ar->audio_out) ar->audio_out->SetVolume(ar->audio_out, mute ? 0 : ar->volume); - gf_mixer_lock(ar->mixer, GF_FALSE); + if (ar->aout) gf_filter_pid_set_property(ar->aout, GF_PROP_PID_AUDIO_VOLUME, &PROP_UINT(mute ? 0 : ar->volume) ); } void gf_sc_ar_set_pan(GF_AudioRenderer *ar, u32 Balance) { - gf_mixer_lock(ar->mixer, GF_TRUE); - ar->pan = MIN(Balance, 100); - if (ar->audio_out) ar->audio_out->SetPan(ar->audio_out, ar->pan); - gf_mixer_lock(ar->mixer, GF_FALSE); + if (Balance>100) Balance = 100; + if (ar->pan == Balance) return; + ar->pan = Balance; + if (ar->aout) gf_filter_pid_set_property(ar->aout, GF_PROP_PID_AUDIO_PAN, &PROP_UINT(ar->pan) ); } +void compositor_setup_aout(GF_Compositor *ctx); void gf_sc_ar_add_src(GF_AudioRenderer *ar, GF_AudioInterface *source) { Bool recfg; if (!ar) return; + + compositor_setup_aout(ar->compositor); + /*lock mixer*/ gf_mixer_lock(ar->mixer, GF_TRUE); gf_mixer_add_input(ar->mixer, source); @@ -648,8 +231,12 @@ void gf_sc_ar_add_src(GF_AudioRenderer *ar, GF_AudioInterface *source) recfg = gf_mixer_reconfig(ar->mixer); if (!ar->need_reconfig) ar->need_reconfig = recfg; - if (!gf_mixer_empty(ar->mixer) && ar->audio_out && ar->audio_out->Play) - ar->audio_out->Play(ar->audio_out, 1); + if (!gf_mixer_empty(ar->mixer) && ar->aout) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_PLAY, ar->aout); + gf_filter_pid_send_event(ar->aout, &evt); + } + /*unlock mixer*/ gf_mixer_lock(ar->mixer, GF_FALSE); } @@ -658,112 +245,177 @@ void gf_sc_ar_remove_src(GF_AudioRenderer *ar, GF_AudioInterface *source) { if (ar) { gf_mixer_remove_input(ar->mixer, source); - if (gf_mixer_empty(ar->mixer) && ar->audio_out && ar->audio_out->Play) - ar->audio_out->Play(ar->audio_out, 0); + if (gf_mixer_empty(ar->mixer) && ar->aout) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_STOP, ar->aout); + gf_filter_pid_send_event(ar->aout, &evt); + } } } +#if 0 //unused void gf_sc_ar_set_priority(GF_AudioRenderer *ar, u32 priority) { - if (ar->audio_out && ar->audio_out->SelfThreaded) { - ar->audio_out->SetPriority(ar->audio_out, priority); - } else { - gf_th_set_priority(ar->th, priority); - } + if (ar->aout) + gf_filter_pid_set_property(ar->aout, GF_PROP_PID_AUDIO_PRIORITY, &PROP_UINT(priority) ); } +#endif -void gf_sc_ar_reconfig(GF_AudioRenderer *ar) +void gf_sc_ar_update_video_clock(GF_AudioRenderer *ar, u32 video_ts) { - Bool frozen; - if (!ar->need_reconfig || !ar->audio_out) return; - /*lock mixer*/ - gf_mixer_lock(ar->mixer, GF_TRUE); - - frozen = ar->Frozen; - if (!frozen ) - gf_ar_pause(ar, GF_TRUE, GF_TRUE, GF_FALSE); - - ar->need_reconfig = GF_FALSE; - gf_ar_setup_output_format(ar); - - if (!frozen) - gf_ar_pause(ar, GF_FALSE, GF_TRUE, GF_FALSE); - - /*unlock mixer*/ - gf_mixer_lock(ar->mixer, GF_FALSE); + ar->video_ts = video_ts; } -u32 gf_sc_ar_get_delay(GF_AudioRenderer *ar) +static void gf_ar_pck_done(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck) { - return ar->audio_out->GetAudioDelay(ar->audio_out); + u32 size; + GF_Compositor *compositor = gf_filter_pid_get_udta(pid); + /*data = */gf_filter_pck_get_data(pck, &size); + if (!size) return; + + assert(size <= compositor->audio_renderer->nb_bytes_out); + compositor->audio_renderer->nb_bytes_out -= size; } - -u32 gf_sc_ar_get_clock(GF_AudioRenderer *ar) +void gf_ar_send_packets(GF_AudioRenderer *ar) { - if (ar->clock_use_audio_out) return ar->current_time; + u32 written, max_send=100; + u64 now = gf_sys_clock_high_res(); - if (ar->Frozen) { - return (u32) ((ar->freeze_time - ar->start_time) / 1000); + if (!ar->aout) { + if (ar->compositor->player) { + ar->current_time = (u32) ( (now - ar->start_time)/1000); + } + return; } + if (!ar->scene_ready) return; + if (ar->need_reconfig) return; + if (ar->Frozen) return; - return (u32) ((gf_sys_clock_high_res() - ar->start_time) / 1000); -} + //reconfiguration is pending, wait for the packet issued at reconfig to be consummed before issuing any new frame + if (ar->wait_for_rcfg) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[Compositor] Waiting for audio output reconfiguration\n")); + return; + } + if (ar->scene_ready) { + if (!ar->start_time) { + ar->start_time = now; + } + if (!ar->nb_audio_objects && !ar->non_rt_output) { + ar->current_time = (u32) ( (now - ar->start_time)/1000); + return; + } + } -GF_EXPORT -void gf_sc_reload_audio_filters(GF_Compositor *compositor) -{ - GF_AudioRenderer *ar = compositor->audio_renderer; - if (!ar) return; + while (max_send) { + u32 delay_ms = 0; + u8 *data; + u32 dur; + GF_FilterPacket *pck; - gf_mixer_lock(ar->mixer, GF_TRUE); + if (gf_filter_pid_would_block(ar->aout)) + break; - gf_afc_unload(&ar->filter_chain); - gf_afc_load(&ar->filter_chain, ar->user, (char*)gf_cfg_get_key(ar->user->config, "Audio", "Filter")); + pck = gf_filter_pck_new_alloc_destructor(ar->aout, ar->buffer_size, &data, gf_ar_pck_done); + if (!pck) break; + + if (ar->compositor->async) { + GF_Fraction64 ref_ts; + gf_filter_get_clock_hint(ar->compositor->filter, NULL, &ref_ts); + //valid clock hint, compute delay between last known playback point and current time + if (ref_ts.den) { + if (ref_ts.den != ar->samplerate) { + delay_ms = (u32) (1000 * ar->current_time_sr / ar->samplerate); + delay_ms -= (u32) (1000 * ref_ts.num / ref_ts.den); + } else { + delay_ms = (u32) (1000 * (ar->current_time_sr - ref_ts.num) / ar->samplerate); + } + } + //unknown clock hint, use number of bytes out as delay + else { + delay_ms = (1000*ar->nb_bytes_out) / ar->bytes_per_second; + } + } - gf_ar_pause(ar, GF_TRUE, GF_TRUE, GF_FALSE); - ar->need_reconfig = GF_FALSE; - gf_ar_setup_output_format(ar); - gf_ar_pause(ar, GF_FALSE, GF_TRUE, GF_FALSE); + gf_mixer_lock(ar->mixer, GF_TRUE); + written = gf_mixer_get_output(ar->mixer, data, ar->buffer_size, delay_ms); + gf_mixer_lock(ar->mixer, GF_FALSE); - gf_mixer_lock(ar->mixer, GF_FALSE); + if (!written) { + if (!ar->non_rt_output) written = ar->buffer_size; + else if (ar->scene_ready && ar->nb_audio_objects && !gf_mixer_buffering(ar->mixer) ) written = ar->buffer_size; + else { + gf_filter_pck_truncate(pck, 0); + gf_filter_pck_discard(pck); + break; + } + } + + if (writtenbuffer_size) { + gf_filter_pck_truncate(pck, written); + } + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + gf_filter_pck_set_cts(pck, ar->current_time_sr); + dur = written / ar->bytes_per_samp; + gf_filter_pck_set_duration(pck, dur); + GF_LOG(GF_LOG_INFO, GF_LOG_AUDIO, ("[Compositor] Send audio frame TS "LLU" nb samples %d - AR clock %u - delay %d ms\n", ar->current_time_sr, dur, ar->current_time, delay_ms)); + + ar->nb_bytes_out += written; + gf_filter_pck_send(pck); + + ar->bytes_requested += written; + ar->current_time_sr = ar->time_at_last_config_sr + (u32) (ar->bytes_requested / ar->bytes_per_samp); + ar->current_time = ar->time_at_last_config + (u32) (ar->bytes_requested * 1000 / ar->bytes_per_second); + + max_send--; + + //this is a safety for non blocking mode, otherwise the pid_would_block is enough + if (ar->nb_bytes_out > ar->max_bytes_out) + break; + } } -GF_EXPORT -GF_Err gf_sc_add_audio_listener(GF_Compositor *compositor, GF_AudioListener *al) + +void gf_sc_ar_send_or_reconfig(GF_AudioRenderer *ar) { - GF_AudioMixer *mixer; - u32 sr, ch, bps, ch_cfg; - if (!compositor || !al || !al->on_audio_frame || !al->on_audio_reconfig) return GF_BAD_PARAM; - if (!compositor->audio_renderer) return GF_NOT_SUPPORTED; + Bool frozen; + if (ar->need_reconfig) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[Compositor] Reconfiguring audio mixer\n")); + /*lock mixer*/ + gf_mixer_lock(ar->mixer, GF_TRUE); - mixer = compositor->audio_renderer->mixer; - gf_mixer_lock(mixer, GF_TRUE); + frozen = ar->Frozen; + if (!frozen ) + gf_ar_pause(ar, GF_TRUE, GF_TRUE, GF_FALSE); - if (!compositor->audio_renderer->audio_listeners) compositor->audio_renderer->audio_listeners = gf_list_new(); - gf_list_add(compositor->audio_renderer->audio_listeners, al); + ar->need_reconfig = GF_FALSE; + gf_ar_setup_output_format(ar); - gf_mixer_get_config(mixer, &sr, &ch, &bps, &ch_cfg); + if (!frozen) + gf_ar_pause(ar, GF_FALSE, GF_TRUE, GF_FALSE); - al->on_audio_reconfig(al->udta, sr, bps, ch, ch_cfg); + /*unlock mixer*/ + gf_mixer_lock(ar->mixer, GF_FALSE); + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_AUDIO, ("[Compositor] sending audio packets\n")); + gf_ar_send_packets(ar); +} - gf_mixer_lock(mixer, GF_FALSE); - return GF_OK; +#if 0 //unused +u32 gf_sc_ar_get_delay(GF_AudioRenderer *ar) +{ + if (!ar->bytes_per_second) return 0; + //try to FIXME, this is not as precise as what we have before using ar->audio_out->GetAudioDelay(ar->audio_out) + // since we don't know how much of the first packet data out there has been consumed + return 1000 * ar->nb_bytes_out / ar->bytes_per_second; } +#endif -GF_EXPORT -GF_Err gf_sc_remove_audio_listener(GF_Compositor *compositor, GF_AudioListener *al) +u32 gf_sc_ar_get_clock(GF_AudioRenderer *ar) { - if (!compositor || !al) return GF_BAD_PARAM; - if (!compositor->audio_renderer) return GF_NOT_SUPPORTED; - - gf_mixer_lock(compositor->audio_renderer->mixer, GF_TRUE); - gf_list_del_item(compositor->audio_renderer->audio_listeners, al); - if (!gf_list_count(compositor->audio_renderer->audio_listeners)) { - gf_list_del(compositor->audio_renderer->audio_listeners); - compositor->audio_renderer->audio_listeners = NULL; - } - gf_mixer_lock(compositor->audio_renderer->mixer, GF_FALSE); - return GF_OK; + return ar->current_time; } + + + diff --git a/src/compositor/bindable.c b/src/compositor/bindable.c index 94355dc..dc24788 100644 --- a/src/compositor/bindable.c +++ b/src/compositor/bindable.c @@ -311,12 +311,11 @@ void PreDestroyBindable(GF_Node *bindable, GF_List *stack_list) Bindable_SetIsBound(bindable, GF_FALSE); while (gf_list_count(stack_list)) { - GF_Node *stack_top; GF_List *stack = (GF_List*)gf_list_get(stack_list, 0); gf_list_rem(stack_list, 0); gf_list_del_item(stack, bindable); if (is_bound) { - stack_top = (GF_Node*)gf_list_get(stack, 0); + GF_Node *stack_top = (GF_Node*)gf_list_get(stack, 0); if (stack_top) Bindable_SetSetBind(stack_top, GF_TRUE); } } diff --git a/src/compositor/camera.c b/src/compositor/camera.c index 8634897..cdc6c6f 100644 --- a/src/compositor/camera.c +++ b/src/compositor/camera.c @@ -30,20 +30,6 @@ #define FORCE_CAMERA_3D -GF_Camera *new_camera() -{ - GF_Camera *tmp; - GF_SAFEALLOC(tmp, GF_Camera); - if (!tmp) return NULL; - tmp->speed = 1; - - return tmp; -} -void delete_camera(GF_Camera *cam) -{ - if (cam) gf_free(cam); -} - void camera_invalidate(GF_Camera *cam) { /*forces recompute of default viewpoint*/ @@ -210,7 +196,7 @@ void camera_set_2d(GF_Camera *cam) #endif } -void camera_update_stereo(GF_Camera *cam, GF_Matrix2D *user_transform, Bool center_coords, Fixed horizontal_shift, Fixed nominal_view_distance, Fixed view_distance_offset, u32 camera_layout) +void camera_update_stereo(GF_Camera *cam, GF_Matrix2D *user_transform, Bool center_coords, Fixed horizontal_shift, Fixed nominal_view_distance, Fixed view_distance_offset, u32 camlay) { Fixed vlen, h, w, ar; SFVec3f corner, center; @@ -222,8 +208,12 @@ void camera_update_stereo(GF_Camera *cam, GF_Matrix2D *user_transform, Bool cent gf_mx_init(post_model_view); if (cam->is_3D) { + if (!cam->z_far) { + cam->z_near = FIX_ONE / 100; + cam->z_far = FIX_ONE * 100; + } /*setup perspective*/ - if (camera_layout==GF_3D_CAMERA_OFFAXIS) { + if (camlay==GF_3D_CAMERA_OFFAXIS) { Fixed left, right, top, bottom, shift, wd2, ndfl, viewing_distance; SFVec3f eye, pos, tar, disp; @@ -338,11 +328,11 @@ void camera_update_stereo(GF_Camera *cam, GF_Matrix2D *user_transform, Bool cent cam->center = b.center; cam->radius = b.radius; - if (camera_layout==GF_3D_CAMERA_OFFAXIS) - camera_layout=GF_3D_CAMERA_LINEAR; + if (camlay==GF_3D_CAMERA_OFFAXIS) + camlay=GF_3D_CAMERA_LINEAR; } - if (camera_layout == GF_3D_CAMERA_CIRCULAR) { + if (camlay == GF_3D_CAMERA_CIRCULAR) { GF_Matrix mx; Fixed viewing_distance = nominal_view_distance; SFVec3f pos, target; @@ -367,7 +357,7 @@ void camera_update_stereo(GF_Camera *cam, GF_Matrix2D *user_transform, Bool cent gf_mx_apply_vec(&mx, &pos); gf_mx_lookat(&cam->modelview, pos, target, cam->up); - } else if (camera_layout == GF_3D_CAMERA_LINEAR) { + } else if (camlay == GF_3D_CAMERA_LINEAR) { Fixed viewing_distance = nominal_view_distance + view_distance_offset; GF_Vec eye, disp, pos, tar; @@ -496,22 +486,25 @@ void camera_jump(GF_Camera *cam) } -Bool camera_animate(GF_Camera *cam) +Bool camera_animate(GF_Camera *cam, void *_compositor) { u32 now; Fixed frac; + GF_Compositor *compositor = (GF_Compositor *) _compositor; if (!cam->anim_len) return GF_FALSE; + now = gf_sc_get_clock(compositor); + if (cam->jumping) { if (!cam->anim_start) { - cam->anim_start = gf_sys_clock(); + cam->anim_start = now; cam->dheight = 0; return GF_TRUE; } cam->position.y -= cam->dheight; cam->target.y -= cam->dheight; - now = gf_sys_clock() - cam->anim_start; + now -= cam->anim_start; if (now > cam->anim_len) { cam->anim_len = 0; cam->jumping = GF_FALSE; @@ -528,10 +521,10 @@ Bool camera_animate(GF_Camera *cam) } if (!cam->anim_start) { - cam->anim_start = gf_sys_clock(); + cam->anim_start = now; frac = 0; } else { - now = gf_sys_clock() - cam->anim_start; + now -= cam->anim_start; if (now > cam->anim_len) { cam->anim_len = 0; #ifndef FORCE_CAMERA_3D diff --git a/src/compositor/clock.c b/src/compositor/clock.c new file mode 100644 index 0000000..25f4138 --- /dev/null +++ b/src/compositor/clock.c @@ -0,0 +1,298 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file scene part of GPAC / Scene Compositor sub-project + * + * GPAC scene free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC scene distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include + +static GF_Clock *gf_clock_new(GF_Compositor *compositor) +{ + GF_Clock *tmp; + GF_SAFEALLOC(tmp, GF_Clock); + if (!tmp) return NULL; + tmp->mx = gf_mx_new("Clock"); + tmp->compositor = compositor; + tmp->speed = FIX_ONE; + tmp->timeline_id = 1; + return tmp; +} + +void gf_clock_del(GF_Clock *ck) +{ + gf_mx_del(ck->mx); + gf_free(ck); +} + +GF_Clock *gf_clock_find(GF_List *Clocks, u16 clock_id, u16 ES_ID) +{ + u32 i; + GF_Clock *tmp; + i=0; + while ((tmp = (GF_Clock *)gf_list_enum(Clocks, &i))) { + //first check the clock ID + if (tmp->clock_id == clock_id) return tmp; + //then check the ES ID + if (ES_ID && (tmp->clock_id == ES_ID)) return tmp; + } + //no clocks found... + return NULL; +} + +static GF_Clock *gf_ck_look_for_clock_dep(GF_Scene *scene, u16 clock_id) +{ + u32 i, j; + GF_ODMExtraPid *xpid; + GF_ObjectManager *odm; + + /*check in top OD*/ + if (scene->root_od->pid_id == clock_id) return scene->root_od->ck; + i=0; + while ((xpid = (GF_ODMExtraPid*)gf_list_enum(scene->root_od->extra_pids, &i))) { + if (xpid->pid_id == clock_id) return scene->root_od->ck; + } + /*check in sub ODs*/ + j=0; + while ((odm = (GF_ObjectManager*)gf_list_enum(scene->resources, &j))) { + if (odm->pid_id == clock_id) return odm->ck; + i=0; + while ((xpid = (GF_ODMExtraPid*)gf_list_enum(odm->extra_pids, &i))) { + if (xpid->pid_id == clock_id) return odm->ck; + } + } + return NULL; +} + +/*remove clocks created due to out-of-order OCR dependencies*/ +static void gf_ck_resolve_clock_dep(GF_List *clocks, GF_Scene *scene, GF_Clock *new_ck, u16 Clock_ESID) +{ + u32 i; + GF_Clock *clock; + GF_ObjectManager *odm; + + /*check all objects - if any uses a clock which ID == the clock_ESID then + this clock shall be removed*/ + if (scene->root_od->ck && (scene->root_od->ck->clock_id == Clock_ESID)) { + scene->root_od->ck = new_ck; + } + i=0; + while ((odm = (GF_ObjectManager*)gf_list_enum(scene->resources, &i))) { + if (odm->ck && (odm->ck->clock_id == Clock_ESID)) { + odm->ck = new_ck; + } + } + /*destroy clock*/ + i=0; + while ((clock = (GF_Clock*)gf_list_enum(clocks, &i))) { + if (clock->clock_id == Clock_ESID) { + gf_list_rem(clocks, i-1); + gf_clock_del(clock); + return; + } + } +} + +GF_Clock *gf_clock_attach(GF_List *clocks, GF_Scene *scene, u16 clock_id, u16 ES_ID, s32 hasOCR) +{ + Bool check_dep; + GF_Clock *tmp = gf_clock_find(clocks, clock_id, ES_ID); + /*ck dep can only be solved if in the main service*/ + check_dep = (scene->root_od->scene_ns && scene->root_od->scene_ns->clocks==clocks) ? GF_TRUE : GF_FALSE; + + /*this partly solves a->b->c*/ + if (!tmp && check_dep) tmp = gf_ck_look_for_clock_dep(scene, clock_id); + if (!tmp) { + tmp = gf_clock_new(scene->compositor); + tmp->clock_id = clock_id; + gf_list_add(clocks, tmp); + } else { + if (tmp->clock_id == ES_ID) tmp->clock_id = clock_id; + /*this finally solves a->b->c*/ + if (check_dep && (tmp->clock_id != ES_ID)) gf_ck_resolve_clock_dep(clocks, scene, tmp, ES_ID); + } + return tmp; +} + +void gf_clock_reset(GF_Clock *ck) +{ + ck->clock_init = 0; + ck->audio_delay = 0; + ck->speed_set_time = 0; + //do NOT reset buffering flag, because RESET scene called only + //for the stream owning the clock, and other streams may + //have signaled buffering on this clock + ck->init_timestamp = 0; + ck->start_time = 0; + ck->has_seen_eos = 0; + ck->media_time_at_init = 0; + ck->has_media_time_shift = 0; + ck->timeline_id++; +} + +void gf_clock_set_time(GF_Clock *ck, u32 TS) +{ + if (!ck->clock_init) { + ck->init_timestamp = TS; + ck->clock_init = 1; + ck->audio_delay = 0; + /*update starttime and pausetime even in pause mode*/ + ck->pause_time = ck->start_time = gf_sc_get_clock(ck->compositor); + } +} + + + +void gf_clock_pause(GF_Clock *ck) +{ + gf_mx_p(ck->mx); + if (!ck->nb_paused) + ck->pause_time = gf_sc_get_clock(ck->compositor); + ck->nb_paused += 1; + gf_mx_v(ck->mx); +} + +void gf_clock_resume(GF_Clock *ck) +{ + gf_mx_p(ck->mx); + assert(ck->nb_paused); + if (!ck->nb_paused) { + assert(!ck->nb_buffering); + } + ck->nb_paused -= 1; + //in player mode, increment the start time to reflect how long we have been buffering + //in non-player mode, since we don't care about real-time, don't update the clock start time + //this avoids cases where the first composed frame is dispatched while the object(s) are buffering + //updating the clock would rewind the timebase in the past and won't trigger next frame fetch on these objects + if (!ck->nb_paused && ck->compositor->player) + ck->start_time += gf_sc_get_clock(ck->compositor) - ck->pause_time; + gf_mx_v(ck->mx); +} + + +u32 gf_clock_real_time(GF_Clock *ck) +{ + u32 time; + assert(ck); + if (!ck->clock_init) return ck->start_time; + time = ck->nb_paused > 0 ? ck->pause_time : gf_sc_get_clock(ck->compositor); + +#ifdef GPAC_FIXED_POINT + + if ((ck->speed < 0) && ((s32) ck->init_timestamp < FIX2INT( (-ck->speed * 100) * (time - ck->start_time)) / 100 ) ) { + time = 0; + } else { + time = ck->speed_set_time + ck->init_timestamp + (time - ck->start_time) * FIX2INT(100*ck->speed) / 100; + } + +#else + + if ((ck->speed < 0) && ((s32) ck->init_timestamp < (-ck->speed) * (time - ck->start_time))) { + time = 0; + } else { + //DO NOT CHANGE the position of cast float->u32, otherwise we have precision issues when ck->init_timestamp + //is >= 0x40000000. We know for sure that ck->speed * (time - ck->start_time) is positive + time = ck->speed_set_time + ck->init_timestamp + (u32) (ck->speed * (time - ck->start_time) ); + } + +#endif + + return time; +} + +GF_EXPORT +u32 gf_clock_time(GF_Clock *ck) +{ + u32 time = gf_clock_real_time(ck); + if ((ck->audio_delay>0) && (time < (u32) ck->audio_delay)) return 0; + return time - ck->audio_delay; +} + +u32 gf_clock_to_media_time(GF_Clock *ck, u32 clock_val) +{ + u32 t = clock_val; + if (ck && ck->has_media_time_shift) { + if (t>ck->init_timestamp) t -= ck->init_timestamp; + else t=0; + t += ck->media_time_at_init; + } + return t; +} + +u32 gf_clock_media_time(GF_Clock *ck) +{ + u32 t; + if (!ck) return 0; + if (!ck->has_seen_eos && ck->last_ts_rendered) t = ck->last_ts_rendered; + else t = gf_clock_time(ck); + return gf_clock_to_media_time(ck, t); +} + +u32 gf_clock_elapsed_time(GF_Clock *ck) +{ + if (!ck || ck->nb_buffering || ck->nb_paused) return 0; + return gf_sys_clock() - ck->start_time; +} + +Bool gf_clock_is_started(GF_Clock *ck) +{ + if (!ck || !ck->clock_init || ck->nb_buffering || ck->nb_paused) return 0; + return 1; +} + +/*buffering scene protected by a mutex because it may be triggered by composition memory (audio or visual threads)*/ +void gf_clock_buffer_on(GF_Clock *ck) +{ + gf_mx_p(ck->mx); + if (!ck->nb_buffering) gf_clock_pause(ck); + ck->nb_buffering += 1; + gf_mx_v(ck->mx); +} + +void gf_clock_buffer_off(GF_Clock *ck) +{ + gf_mx_p(ck->mx); + //assert(ck->nb_buffering); + if (ck->nb_buffering) { + ck->nb_buffering -= 1; + if (!ck->nb_buffering) + gf_clock_resume(ck); + } + gf_mx_v(ck->mx); +} + + +void gf_clock_set_speed(GF_Clock *ck, Fixed speed) +{ + u32 time; + if (speed==ck->speed) return; + time = gf_sc_get_clock(ck->compositor); + /*adjust start time*/ + ck->speed_set_time = gf_clock_time(ck) - ck->init_timestamp; + ck->pause_time = ck->start_time = time; + ck->speed = speed; +} + +void gf_clock_set_audio_delay(GF_Clock *ck, s32 ms_delay) +{ + if (ck) ck->audio_delay = ms_delay; +} + diff --git a/src/compositor/compositor.c b/src/compositor/compositor.c index 39eb31d..7eaf474 100644 --- a/src/compositor/compositor.c +++ b/src/compositor/compositor.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / Scene Compositor sub-project @@ -24,27 +24,36 @@ */ #include -/*for user and terminal API (options and InputSensor)*/ -#include #include #include #include +#include #include "nodes_stacks.h" #include "visual_manager.h" #include "texturing.h" +static void gf_sc_recompute_ar(GF_Compositor *compositor, GF_Node *top_node); + #define SC_DEF_WIDTH 320 #define SC_DEF_HEIGHT 240 + +GF_EXPORT +Bool gf_sc_send_event(GF_Compositor *compositor, GF_Event *evt) +{ + return gf_filter_forward_gf_event(compositor->filter, evt, GF_FALSE, GF_FALSE); +} + + void gf_sc_next_frame_state(GF_Compositor *compositor, u32 state) { -// GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Forcing frame redraw state: %d\n", state)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Forcing frame redraw state: %d\n", state)); if (state==GF_SC_DRAW_FLUSH) { if (!compositor->skip_flush) compositor->skip_flush = 2; - + //if in openGL mode ignore refresh events (content of the window is still OK). This is only used for overlays in 2d if (!compositor->frame_draw_type #ifndef GPAC_DISABLE_3D @@ -93,7 +102,7 @@ static void gf_sc_set_fullscreen(GF_Compositor *compositor) evt.type = GF_EVENT_MESSAGE; evt.message.message = "Cannot switch to fullscreen"; evt.message.error = e; - gf_term_send_event(compositor->term, &evt); + gf_sc_send_event(compositor, &evt); compositor->fullscreen = 0; compositor->video_out->SetFullScreen(compositor->video_out, 0, &compositor->display_width, &compositor->display_height); } @@ -117,43 +126,41 @@ static void gf_sc_reconfig_task(GF_Compositor *compositor) u32 width,height; /*reconfig audio if needed (non-threaded compositors)*/ - if (compositor->audio_renderer && !compositor->audio_renderer->th) gf_sc_ar_reconfig(compositor->audio_renderer); - + if (compositor->audio_renderer +#ifdef ENABLE_AOUT + && !compositor->audio_renderer->th +#endif + ) if (compositor->msg_type) { compositor->msg_type |= GF_SR_IN_RECONFIG; if (compositor->msg_type & GF_SR_CFG_INITIAL_RESIZE) { - GF_Event evt; memset(&evt, 0, sizeof(GF_Event)); evt.type = GF_EVENT_VIDEO_SETUP; evt.setup.width = compositor->new_width; evt.setup.height = compositor->new_height; evt.setup.system_memory = compositor->video_memory ? GF_FALSE : GF_TRUE; - -#ifdef OPENGL_RASTER - if (compositor->opengl_raster) { - evt.setup.opengl_mode = 1; - evt.setup.system_memory = GF_FALSE; - evt.setup.back_buffer = GF_TRUE; - } -#endif - + evt.setup.disable_vsync = compositor->bench_mode ? GF_TRUE : GF_FALSE; + #ifndef GPAC_DISABLE_3D if (compositor->hybrid_opengl || compositor->force_opengl_2d) { - evt.setup.opengl_mode = 1; + evt.setup.use_opengl = GF_TRUE; evt.setup.system_memory = GF_FALSE; evt.setup.back_buffer = GF_TRUE; } - + #endif - compositor->video_out->ProcessEvent(compositor->video_out, &evt); + compositor->video_out->ProcessEvent(compositor->video_out, &evt); + + if (evt.setup.use_opengl) { + gf_opengl_init(); + } + compositor->msg_type &= ~GF_SR_CFG_INITIAL_RESIZE; } - /*scene size has been overriden*/ + /*scene size has been overridden*/ if (compositor->msg_type & GF_SR_CFG_OVERRIDE_SIZE) { - GF_Event evt; - assert(!(compositor->override_size_flags & 2)); compositor->msg_type &= ~GF_SR_CFG_OVERRIDE_SIZE; compositor->override_size_flags |= 2; @@ -165,7 +172,7 @@ static void gf_sc_reconfig_task(GF_Compositor *compositor) evt.type = GF_EVENT_SIZE; evt.size.width = width; evt.size.height = height; - gf_term_send_event(compositor->term, &evt); + gf_sc_send_event(compositor, &evt); } /*size changed from scene cfg: resize window first*/ if (compositor->msg_type & GF_SR_CFG_SET_SIZE) { @@ -175,12 +182,14 @@ static void gf_sc_reconfig_task(GF_Compositor *compositor) GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Changing display size to %d x %d\n", compositor->new_width, compositor->new_height)); fs_width = fs_height = 0; if (restore_fs) { +#if defined(GPAC_CONFIG_ANDROID) || defined(GPAC_CONFIG_IOS) if ((compositor->new_width>compositor->display_width) || (compositor->new_height>compositor->display_height)) { u32 w = compositor->display_width; compositor->display_width = compositor->display_height; compositor->display_height = w; compositor->recompute_ar = 1; } +#endif fs_width = compositor->display_width; fs_height = compositor->display_height; } @@ -192,7 +201,7 @@ static void gf_sc_reconfig_task(GF_Compositor *compositor) if ( !(compositor->msg_type & GF_SR_CFG_WINDOWSIZE_NOTIF)) { compositor->video_out->ProcessEvent(compositor->video_out, &evt); } - + compositor->msg_type &= ~GF_SR_CFG_WINDOWSIZE_NOTIF; if (restore_fs) { @@ -231,15 +240,9 @@ static void gf_sc_reconfig_task(GF_Compositor *compositor) } compositor->msg_type &= ~GF_SR_IN_RECONFIG; } - if (notif_size && compositor->video_listeners) { - u32 k=0; - GF_VideoListener *l; - while ((l = gf_list_enum(compositor->video_listeners, &k))) { - if ( compositor->user && (compositor->user->init_flags & GF_TERM_WINDOW_TRANSPARENT) ) - l->on_video_reconfig(l->udta, compositor->display_width, compositor->display_height, 4); - else - l->on_video_reconfig(l->udta, compositor->display_width, compositor->display_height, 3); - } + if (notif_size) { + gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_WIDTH, &PROP_UINT(compositor->display_width)); + gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_HEIGHT, &PROP_UINT(compositor->display_height)); } /*3D driver changed message, recheck extensions*/ @@ -259,13 +262,82 @@ static void gf_sc_reconfig_task(GF_Compositor *compositor) } } + +static void gf_sc_frame_ifce_done(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck) +{ + GF_FilterFrameInterface *frame_ifce = gf_filter_pck_get_frame_interface(pck); + GF_Compositor *compositor = gf_filter_get_udta(filter); + if (frame_ifce) { + if (compositor->fb.video_buffer) { + gf_sc_release_screen_buffer(compositor, &compositor->fb); + compositor->fb.video_buffer = NULL; + } + } + compositor->frame_ifce.user_data = NULL; + compositor->flush_pending = (compositor->skip_flush!=1) ? GF_TRUE : GF_FALSE; + compositor->skip_flush = 0; +} + +GF_Err gf_sc_frame_ifce_get_plane(GF_FilterFrameInterface *frame_ifce, u32 plane_idx, const u8 **outPlane, u32 *outStride) +{ + GF_Err e = GF_BAD_PARAM; + GF_Compositor *compositor = frame_ifce->user_data; + + if (plane_idx==0) { + e = GF_OK; + if (!compositor->fb.video_buffer) + e = gf_sc_get_screen_buffer(compositor, &compositor->fb, 0); + } + *outPlane = compositor->fb.video_buffer; + *outStride = compositor->fb.pitch_y; + return e; +} +#ifndef GPAC_DISABLE_3D +GF_Err gf_sc_frame_ifce_get_gl_texture(GF_FilterFrameInterface *frame_ifce, u32 plane_idx, u32 *gl_tex_format, u32 *gl_tex_id, GF_Matrix_unexposed * texcoordmatrix) +{ + GF_Compositor *compositor = frame_ifce->user_data; + if (!compositor->fbo_tx_id) return GF_BAD_PARAM; + if (plane_idx) return GF_BAD_PARAM; + if (gl_tex_id) *gl_tex_id = compositor->fbo_tx_id; + if (gl_tex_format) *gl_tex_format = compositor_3d_get_fbo_pixfmt(); + //framebuffer is already oriented as a GL texture not as an image + if (texcoordmatrix) + gf_mx_add_scale(texcoordmatrix, FIX_ONE, -FIX_ONE, FIX_ONE); + return GF_OK; +} +#endif + +static void gf_sc_flush_video(GF_Compositor *compositor, Bool locked) +{ + GF_Window rc; + + //release compositor in case we have vsync + if (locked) gf_sc_lock(compositor, 0); + rc.x = rc.y = 0; + rc.w = compositor->display_width; + rc.h = compositor->display_height; + compositor->video_out->Flush(compositor->video_out, &rc); + compositor->flush_pending = GF_FALSE; + if (locked) gf_sc_lock(compositor, 1); +} + +void gf_sc_render_frame(GF_Compositor *compositor); + GF_EXPORT Bool gf_sc_draw_frame(GF_Compositor *compositor, Bool no_flush, s32 *ms_till_next) { Bool ret = GF_FALSE; - if (no_flush) - compositor->skip_flush=1; + gf_sc_ar_send_or_reconfig(compositor->audio_renderer); + + //frame still pending + if (compositor->frame_ifce.user_data) + return GF_TRUE; + + if (compositor->flush_pending) { + gf_sc_flush_video(compositor, GF_FALSE); + } + compositor->skip_flush = no_flush ? 1 : 0; gf_sc_render_frame(compositor); @@ -278,7 +350,7 @@ Bool gf_sc_draw_frame(GF_Compositor *compositor, Bool no_flush, s32 *ms_till_nex //next frame is late, we should redraw if (compositor->ms_until_next_frame < 0) ret = GF_TRUE; else if (compositor->frame_draw_type) ret = GF_TRUE; - else if (compositor->fonts_pending) ret = GF_TRUE; + else if (compositor->fonts_pending>0) ret = GF_TRUE; return ret; } @@ -302,56 +374,451 @@ static void gf_sc_reset_framerate(GF_Compositor *compositor) compositor->current_frame = 0; } -static Bool gf_sc_on_event(void *cbck, GF_Event *event); -static Bool gf_sc_set_check_raster2d(GF_Compositor *compositor, GF_Raster2D *ifce) +enum { - /*check base*/ - if (!ifce->stencil_new || !ifce->surface_new) return 0; - /*if these are not set we cannot draw*/ - if (!ifce->surface_clear || !ifce->surface_set_path || !ifce->surface_fill) return 0; - /*check we can init a surface with the current driver (the rest is optional)*/ - if (ifce->surface_attach_to_buffer) return 1; - return GF_FALSE; + GF_COMPOSITOR_THREAD_START = 0, + GF_COMPOSITOR_THREAD_RUN, + GF_COMPOSITOR_THREAD_ABORTING, + GF_COMPOSITOR_THREAD_DONE, + GF_COMPOSITOR_THREAD_INIT_FAILED, +}; + +static GF_Err nullvout_setup(struct _video_out *vout, void *os_handle, void *os_display, u32 init_flags) +{ + return GF_OK; +} +static void nullvout_shutdown(struct _video_out *vout) +{ + return; +} +static GF_Err nullvout_flush(struct _video_out *vout, GF_Window *dest) +{ + return GF_OK; +} +static GF_Err nullvout_fullscreen(struct _video_out *vout, Bool fs_on, u32 *new_disp_width, u32 *new_disp_height) +{ + return GF_OK; +} +static GF_Err nullvout_evt(struct _video_out *vout, GF_Event *event) +{ + return GF_OK; +} +static GF_Err nullvout_lock(struct _video_out *vout, GF_VideoSurface *video_info, Bool do_lock) +{ + return GF_IO_ERR; +} +static GF_Err nullvout_blit(struct _video_out *vout, GF_VideoSurface *video_src, GF_Window *src_wnd, GF_Window *dst_wnd, u32 overlay_type) +{ + return GF_OK; +} + +GF_VideoOutput null_vout = { + .Setup = nullvout_setup, + .Shutdown = nullvout_shutdown, + .Flush = nullvout_flush, + .SetFullScreen = nullvout_fullscreen, + .ProcessEvent = nullvout_evt, + .LockBackBuffer = nullvout_lock, + .Blit = nullvout_blit, + .hw_caps = GF_VIDEO_HW_HAS_RGB | GF_VIDEO_HW_HAS_RGBA | GF_VIDEO_HW_HAS_YUV | GF_VIDEO_HW_HAS_YUV_OVERLAY | GF_VIDEO_HW_HAS_STRETCH +}; + +static GF_Err gl_vout_evt(struct _video_out *vout, GF_Event *evt) +{ + GF_Compositor *compositor = (GF_Compositor *)vout->opaque; + if (!evt || (evt->type != GF_EVENT_VIDEO_SETUP)) return GF_OK; + + if (!compositor->player && (compositor->passthrough_pfmt != GF_PIXEL_RGB)) { + u32 pfmt = compositor->dyn_filter_mode ? GF_PIXEL_RGBA : GF_PIXEL_RGB; + compositor->passthrough_pfmt = pfmt; + compositor->opfmt = pfmt; + if (compositor->vout) { + u32 stride = compositor->output_width * ( (pfmt == GF_PIXEL_RGBA) ? 4 : 3 ); + gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_PIXFMT, &PROP_UINT(pfmt)); + gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_STRIDE, &PROP_UINT(stride)); + } + } + + +#ifndef GPAC_DISABLE_3D + return compositor_3d_setup_fbo(evt->setup.width, evt->setup.height, &compositor->fbo_id, &compositor->fbo_tx_id, &compositor->fbo_depth_id); +#else + return GF_NOT_SUPPORTED; +#endif } -static GF_Err gf_sc_load(GF_Compositor *compositor) +GF_VideoOutput gl_vout = { + .Setup = nullvout_setup, + .Shutdown = nullvout_shutdown, + .Flush = nullvout_flush, + .SetFullScreen = nullvout_fullscreen, + .ProcessEvent = gl_vout_evt, + .LockBackBuffer = NULL, + .Blit = NULL, + .hw_caps = GF_VIDEO_HW_OPENGL +}; + +static GF_Err rawvout_lock(struct _video_out *vout, GF_VideoSurface *vi, Bool do_lock) { + GF_Compositor *compositor = (GF_Compositor *)vout->opaque; + + if (do_lock) { + u32 pfmt; + if (!vi) return GF_BAD_PARAM; + pfmt = compositor->opfmt; + if (!pfmt && compositor->passthrough_txh) pfmt = compositor->passthrough_txh->pixelformat; + + if (!pfmt) { + pfmt = compositor->dyn_filter_mode ? GF_PIXEL_RGBA : GF_PIXEL_RGB; + } + + memset(vi, 0, sizeof(GF_VideoSurface)); + vi->width = compositor->display_width; + vi->height = compositor->display_height; + gf_pixel_get_size_info(pfmt, compositor->display_width, compositor->display_height, NULL, &vi->pitch_y, NULL, NULL, NULL); + if (compositor->passthrough_txh && !compositor->passthrough_txh->frame_ifce && (pfmt == compositor->passthrough_txh->pixelformat)) { + if (!compositor->passthrough_pck) + compositor->passthrough_pck = gf_filter_pck_new_clone(compositor->vout, compositor->passthrough_txh->stream->pck, &compositor->passthrough_data); + + vi->video_buffer = compositor->passthrough_data; + vi->pitch_y = compositor->passthrough_txh->stride; + } else { + vi->video_buffer = compositor->framebuffer; + } + vi->pitch_x = gf_pixel_get_bytes_per_pixel(pfmt); + vi->pixel_format = pfmt; + compositor->passthrough_pfmt = pfmt; + } + return GF_OK; +} + +static GF_Err rawvout_evt(struct _video_out *vout, GF_Event *evt) +{ + u32 pfmt, stride, stride_uv; + GF_Compositor *compositor = (GF_Compositor *)vout->opaque; + if (!evt || (evt->type != GF_EVENT_VIDEO_SETUP)) return GF_OK; + + pfmt = compositor->opfmt; + if (!pfmt) { + pfmt = compositor->dyn_filter_mode ? GF_PIXEL_RGBA : GF_PIXEL_RGB; + } + + compositor->passthrough_pfmt = pfmt; + stride=0; + stride_uv = 0; + gf_pixel_get_size_info(pfmt, evt->setup.width, evt->setup.height, &compositor->framebuffer_size, &stride, &stride_uv, NULL, NULL); + + if (compositor->vout) { + gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_PIXFMT, &PROP_UINT(pfmt)); + gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_STRIDE, &PROP_UINT(stride)); + gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_STRIDE_UV, stride_uv ? &PROP_UINT(stride_uv) : NULL); + } + + if (compositor->framebuffer_size > compositor->framebuffer_alloc) { + compositor->framebuffer_alloc = compositor->framebuffer_size; + compositor->framebuffer = gf_realloc(compositor->framebuffer, sizeof(char)*compositor->framebuffer_size); + } + if (!compositor->framebuffer) return GF_OUT_OF_MEM; + memset(compositor->framebuffer, 0, sizeof(char)*compositor->framebuffer_size); + +#ifndef GPAC_DISABLE_3D + if (compositor->needs_offscreen_gl && (compositor->ogl != GF_SC_GLMODE_OFF)) + return compositor_3d_setup_fbo(evt->setup.width, evt->setup.height, &compositor->fbo_id, &compositor->fbo_tx_id, &compositor->fbo_depth_id); +#endif + + evt->setup.use_opengl = GF_FALSE; + return GF_OK; +} + +GF_VideoOutput raw_vout = { + .Setup = nullvout_setup, + .Shutdown = nullvout_shutdown, + .Flush = nullvout_flush, + .SetFullScreen = nullvout_fullscreen, + .ProcessEvent = rawvout_evt, + .LockBackBuffer = rawvout_lock, + .Blit = NULL, + .hw_caps = GF_VIDEO_HW_HAS_RGB | GF_VIDEO_HW_HAS_RGBA +}; + +void gf_sc_setup_passthrough(GF_Compositor *compositor) +{ + u32 timescale; + Bool is_raw_out = GF_FALSE; + u32 update_pfmt = 0; + compositor->passthrough_inplace = GF_FALSE; + + if (!compositor->passthrough_txh) { + compositor->passthrough_timescale = 0; + return; + } + + timescale = gf_filter_pck_get_timescale(compositor->passthrough_txh->stream->pck); + if (compositor->passthrough_timescale != timescale) { + + compositor->passthrough_timescale = timescale; + gf_filter_pid_copy_properties(compositor->vout, compositor->passthrough_txh->stream->odm->pid); + if (compositor->video_out==&raw_vout) { + update_pfmt = compositor->opfmt ? compositor->opfmt : compositor->passthrough_txh->pixelformat; + } else { + update_pfmt = GF_PIXEL_RGB; + } + } + if (compositor->video_out == &raw_vout) { + is_raw_out = GF_TRUE; + if (!compositor->opfmt || (compositor->opfmt == compositor->passthrough_txh->pixelformat) ) { + if (!compositor->passthrough_txh->frame_ifce) { + compositor->passthrough_inplace = GF_TRUE; + update_pfmt = GF_FALSE; + } + if (!compositor->opfmt && (compositor->passthrough_pfmt != compositor->passthrough_txh->pixelformat)) { + compositor->passthrough_pfmt = compositor->passthrough_txh->pixelformat; + gf_filter_pid_copy_properties(compositor->vout, compositor->passthrough_txh->stream->odm->pid); + } + } + } + + if (update_pfmt) { + u32 stride=0, stride_uv=0, out_size=0; + gf_pixel_get_size_info(update_pfmt, compositor->display_width, compositor->display_width, &out_size, &stride, &stride_uv, NULL, NULL); + gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_PIXFMT, &PROP_UINT(update_pfmt)); + gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_STRIDE, &PROP_UINT(stride)); + gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_STRIDE_UV, stride_uv ? &PROP_UINT(stride_uv) : NULL); + + if (is_raw_out && !compositor->passthrough_inplace && (compositor->framebuffer_size != out_size) ) { + compositor->framebuffer_size = out_size; + if (out_size > compositor->framebuffer_alloc) { + compositor->framebuffer_alloc = out_size; + compositor->framebuffer = gf_realloc(compositor->framebuffer, out_size); + } + } + } +} + +static GF_Err gf_sc_load_driver(GF_Compositor *compositor) +{ + GF_Err e; + const char *sOpt; + void *os_disp=NULL; + + //filter mode + if (!compositor->player) { + compositor->video_out = &null_vout; + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + nullvout_setup(NULL, NULL, NULL, 0); + nullvout_fullscreen(NULL, GF_FALSE, NULL, NULL); + nullvout_evt(NULL, NULL); + nullvout_lock(NULL, NULL, GF_FALSE); + nullvout_blit(NULL, NULL, NULL, NULL, 0); + } +#endif + + e = gf_filter_request_opengl(compositor->filter); + if (e) return e; +#ifndef GPAC_DISABLE_3D + gf_sc_load_opengl_extensions(compositor, GF_TRUE); + if (!compositor->gl_caps.fbo) +#endif + { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Compositor] No support for OpenGL framebuffer object, cannot run in GL mode.\n")); + compositor->drv = GF_SC_DRV_OFF; + return GF_NOT_SUPPORTED; + } + compositor->video_out = &gl_vout; + compositor->video_out->opaque = compositor; + return GF_OK; + } + + //player mode +#ifndef GPAC_DISABLE_3D + sOpt = gf_opts_get_key("core", "video-output"); + if (sOpt && !strcmp(sOpt, "glfbo")) { + compositor->video_out = &gl_vout; + compositor->video_out->opaque = compositor; + sOpt = gf_opts_get_key("core", "glfbo-txid"); + if (sOpt) { + compositor->fbo_tx_id = atoi(sOpt); + compositor->external_tx_id = GF_TRUE; + } + if (!compositor->fbo_tx_id) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Compositor] glfbo driver specified but no target texture ID found, cannot initialize\n")); + compositor->drv = GF_SC_DRV_OFF; + return GF_BAD_PARAM; + } + gf_sc_load_opengl_extensions(compositor, GF_TRUE); + if (!compositor->gl_caps.fbo) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Compositor] No support for OpenGL framebuffer object, cannot run in glfbo mode.\n")); + compositor->drv = GF_SC_DRV_OFF; + return GF_NOT_SUPPORTED; + } + return GF_OK; + } +#endif + + compositor->video_out = (GF_VideoOutput *) gf_module_load(GF_VIDEO_OUTPUT_INTERFACE, NULL); + + if (!compositor->video_out ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Compositor] Failed to load a video output module.\n")); + return GF_IO_ERR; + } + + sOpt = gf_opts_get_key("Temp", "OSDisp"); + if (sOpt) sscanf(sOpt, "%p", &os_disp); + + compositor->video_out->evt_cbk_hdl = compositor; + compositor->video_out->on_event = gf_sc_on_event; + /*init hw*/ + e = compositor->video_out->Setup(compositor->video_out, compositor->os_wnd, os_disp, compositor->init_flags); + if (e != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Compositor] Error setuing up video output: %s\n", gf_error_to_string(e) )); + return e; + } + if (!gf_opts_get_key("core", "video-output")) { + gf_opts_set_key("core", "video-output", compositor->video_out->module_name); + } + gf_filter_register_opengl_provider(compositor->filter, GF_TRUE); + return GF_OK; +} + +#ifndef GPAC_DISABLE_3D +static void gf_sc_check_video_driver(GF_Compositor *compositor) +{ + Bool force_gl_out = GF_FALSE; + if (compositor->player) return; + if (compositor->video_out == &null_vout) return; + + if (compositor->visual->type_3d || compositor->hybrid_opengl) { + force_gl_out = GF_TRUE; + } else if (compositor->needs_offscreen_gl && (compositor->ogl!=GF_SC_GLMODE_OFF) ) { + force_gl_out = GF_TRUE; + } + + if (force_gl_out) { + GF_Err e; + if (compositor->video_out != &raw_vout) return; + e = gf_sc_load_driver(compositor); + if (e) { + compositor->video_out = &raw_vout; + compositor->video_memory = 2; + } + return; + } + + if (compositor->video_out == &raw_vout) { + if (compositor->needs_offscreen_gl) { + gf_filter_request_opengl(compositor->filter); + } + return; + } + compositor->video_out = &raw_vout; + compositor->video_memory = 2; +} +#endif + +GF_Err gf_sc_load(GF_Compositor *compositor) +{ + u32 i; + const char *sOpt; + + compositor->mx = gf_mx_new("Compositor"); + if (!compositor->mx) return GF_OUT_OF_MEM; + + /*load proto modules*/ + compositor->proto_modules = gf_list_new(); + if (!compositor->proto_modules) return GF_OUT_OF_MEM; + for (i=0; i< gf_modules_count(); i++) { + GF_HardcodedProto *ifce = (GF_HardcodedProto *) gf_modules_load(i, GF_HARDCODED_PROTO_INTERFACE); + if (ifce) gf_list_add(compositor->proto_modules, ifce); + } + + compositor->init_flags = 0; + compositor->os_wnd = NULL; + sOpt = gf_opts_get_key("Temp", "OSWnd"); + if (sOpt) sscanf(sOpt, "%p", &compositor->os_wnd); + sOpt = gf_opts_get_key("Temp", "InitFlags"); + if (sOpt) compositor->init_flags = atoi(sOpt); + + if (compositor->noaudio) + compositor->init_flags |= GF_TERM_NO_AUDIO; + + /*force initial for 2D/3D setup*/ + compositor->msg_type |= GF_SR_CFG_INITIAL_RESIZE; + /*set default size if owning output*/ + if (!compositor->os_wnd) { + compositor->new_width = compositor->osize.x>0 ? compositor->osize.x : SC_DEF_WIDTH; + compositor->new_height = compositor->osize.y>0 ? compositor->osize.y : SC_DEF_HEIGHT; + compositor->msg_type |= GF_SR_CFG_SET_SIZE; + } + + if (!compositor->player) + compositor->init_flags = GF_TERM_INIT_HIDE; + + if (gf_opts_get_bool("temp", "gpac-help")) { + compositor->init_flags |= GF_TERM_NO_VIDEO | GF_TERM_NO_AUDIO; + } + + if (compositor->init_flags & GF_TERM_NO_VIDEO) { + compositor->video_out = &null_vout; + compositor->ogl = GF_SC_GLMODE_OFF; + } else if (compositor->player || (compositor->drv==GF_SC_DRV_ON) || (compositor->ogl==GF_SC_GLMODE_HYBRID) ){ + GF_Err e; + + e = gf_sc_load_driver(compositor); + if (e) return e; + } else { + raw_vout.opaque = compositor; + compositor->video_out = &raw_vout; + compositor->video_memory = 2; + } + compositor->strike_bank = gf_list_new(); + if (!compositor->strike_bank) return GF_OUT_OF_MEM; compositor->visuals = gf_list_new(); + if (!compositor->visuals) return GF_OUT_OF_MEM; GF_SAFEALLOC(compositor->traverse_state, GF_TraverseState); - if (!compositor->traverse_state) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to initilaize compositor\n")); - return GF_OUT_OF_MEM; - } + if (!compositor->traverse_state) return GF_OUT_OF_MEM; compositor->traverse_state->vrml_sensors = gf_list_new(); + if (!compositor->traverse_state->vrml_sensors) return GF_OUT_OF_MEM; compositor->traverse_state->use_stack = gf_list_new(); + if (!compositor->traverse_state->use_stack) return GF_OUT_OF_MEM; #ifndef GPAC_DISABLE_3D compositor->traverse_state->local_lights = gf_list_new(); + if (!compositor->traverse_state->local_lights) return GF_OUT_OF_MEM; #endif compositor->sensors = gf_list_new(); + if (!compositor->sensors) return GF_OUT_OF_MEM; compositor->previous_sensors = gf_list_new(); + if (!compositor->previous_sensors) return GF_OUT_OF_MEM; compositor->hit_use_stack = gf_list_new(); + if (!compositor->hit_use_stack) return GF_OUT_OF_MEM; compositor->prev_hit_use_stack = gf_list_new(); + if (!compositor->prev_hit_use_stack) return GF_OUT_OF_MEM; compositor->focus_ancestors = gf_list_new(); + if (!compositor->focus_ancestors) return GF_OUT_OF_MEM; compositor->focus_use_stack = gf_list_new(); + if (!compositor->focus_use_stack) return GF_OUT_OF_MEM; compositor->env_tests = gf_list_new(); + if (!compositor->env_tests) return GF_OUT_OF_MEM; /*setup main visual*/ compositor->visual = visual_new(compositor); + if (!compositor->visual) return GF_OUT_OF_MEM; compositor->visual->GetSurfaceAccess = compositor_2d_get_video_access; compositor->visual->ReleaseSurfaceAccess = compositor_2d_release_video_access; compositor->visual->CheckAttached = compositor_2d_check_attached; compositor->visual->ClearSurface = compositor_2d_clear_surface; + compositor_2d_init_callbacks(compositor); + if (compositor->video_out->FlushRectangles) compositor->visual->direct_flush = GF_TRUE; - compositor_2d_init_callbacks(compositor); - // default value for depth gain is not zero #ifdef GF_SR_USE_DEPTH compositor->traverse_state->depth_gain = FIX_ONE; @@ -363,8 +830,10 @@ static GF_Err gf_sc_load(GF_Compositor *compositor) /*create a drawable for focus highlight*/ compositor->focus_highlight = drawable_new(); + if (!compositor->focus_highlight) return GF_OUT_OF_MEM; /*associate a dummy node for traversing*/ compositor->focus_highlight->node = gf_node_new(NULL, TAG_UndefinedNode); + if (!compositor->focus_highlight->node) return GF_OUT_OF_MEM; gf_node_register(compositor->focus_highlight->node, NULL); gf_node_set_callback_function(compositor->focus_highlight->node, drawable_traverse_focus); @@ -373,295 +842,116 @@ static GF_Err gf_sc_load(GF_Compositor *compositor) /*default collision mode*/ compositor->collide_mode = GF_COLLISION_DISPLACEMENT; //GF_COLLISION_NORMAL compositor->gravity_on = GF_TRUE; - + /*create default unit sphere and box for bounds*/ compositor->unit_bbox = new_mesh(); + if (!compositor->unit_bbox) return GF_OUT_OF_MEM; mesh_new_unit_bbox(compositor->unit_bbox); gf_mx_init(compositor->traverse_state->model_matrix); #endif compositor->was_system_memory=1; - return GF_OK; -} - - -static GF_Err gf_sc_create(GF_Compositor *compositor) -{ - const char *sOpt; - - /*load video out*/ - sOpt = gf_cfg_get_key(compositor->user->config, "Video", "DriverName"); - if (sOpt) { - compositor->video_out = (GF_VideoOutput *) gf_modules_load_interface_by_name(compositor->user->modules, sOpt, GF_VIDEO_OUTPUT_INTERFACE); - if (compositor->video_out) { - compositor->video_out->evt_cbk_hdl = compositor; - compositor->video_out->on_event = gf_sc_on_event; - /*init hw*/ - if (!compositor->video_out->Setup || compositor->video_out->Setup(compositor->video_out, compositor->user->os_window_handler, compositor->user->os_display, compositor->user->init_flags) != GF_OK) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Failed to Setup Video Driver %s!\n", sOpt)); - gf_modules_close_interface((GF_BaseInterface *)compositor->video_out); - compositor->video_out = NULL; - } - } - } - - if (!compositor->video_out) { - GF_VideoOutput *raw_out = NULL; - u32 i, count = gf_modules_get_count(compositor->user->modules); - GF_LOG(GF_LOG_INFO, GF_LOG_CORE, ("Trying to find a suitable video driver amongst %d modules...\n", count)); - for (i=0; ivideo_out = (GF_VideoOutput *) gf_modules_load_interface(compositor->user->modules, i, GF_VIDEO_OUTPUT_INTERFACE); - if (!compositor->video_out) continue; - compositor->video_out->evt_cbk_hdl = compositor; - compositor->video_out->on_event = gf_sc_on_event; - //in enum mode, only use raw out if everything else failed ... - if (!stricmp(compositor->video_out->module_name, "Raw Video Output")) { - raw_out = compositor->video_out; - compositor->video_out = NULL; - continue; - } - - /*init hw*/ - if (compositor->video_out->Setup && compositor->video_out->Setup(compositor->video_out, compositor->user->os_window_handler, compositor->user->os_display, compositor->user->init_flags)==GF_OK) { - gf_cfg_set_key(compositor->user->config, "Video", "DriverName", compositor->video_out->module_name); - break; - } - gf_modules_close_interface((GF_BaseInterface *)compositor->video_out); - compositor->video_out = NULL; - } - if (raw_out) { - if (compositor->video_out) gf_modules_close_interface((GF_BaseInterface *)raw_out); - else { - compositor->video_out = raw_out; - compositor->video_out ->Setup(compositor->video_out, compositor->user->os_window_handler, compositor->user->os_display, compositor->user->init_flags); - } - } - } - if (!compositor->video_out ) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Failed to create compositor->video_out, did not find any suitable driver.\n")); - return GF_IO_ERR; - } - - sOpt = gf_cfg_get_key(compositor->user->config, "Video", "DPI"); - if (sOpt) { - compositor->video_out->dpi_x = compositor->video_out->dpi_y = atoi(sOpt); - } - - /*try to load a raster driver*/ - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "Raster2D"); - if (sOpt) { - compositor->rasterizer = (GF_Raster2D *) gf_modules_load_interface_by_name(compositor->user->modules, sOpt, GF_RASTER_2D_INTERFACE); - if (!compositor->rasterizer) { - sOpt = NULL; - } else if (!gf_sc_set_check_raster2d(compositor, compositor->rasterizer)) { - gf_modules_close_interface((GF_BaseInterface *)compositor->rasterizer); - compositor->rasterizer = NULL; - sOpt = NULL; - } - } - if (!compositor->rasterizer) { - u32 i, count; - count = gf_modules_get_count(compositor->user->modules); - for (i=0; irasterizer = (GF_Raster2D *) gf_modules_load_interface(compositor->user->modules, i, GF_RASTER_2D_INTERFACE); - if (!compositor->rasterizer) continue; - if (gf_sc_set_check_raster2d(compositor, compositor->rasterizer)) break; - gf_modules_close_interface((GF_BaseInterface *)compositor->rasterizer); - compositor->rasterizer = NULL; - } - if (compositor->rasterizer) gf_cfg_set_key(compositor->user->config, "Compositor", "Raster2D", compositor->rasterizer->module_name); - } - if (!compositor->rasterizer) { - if (compositor->video_out->Shutdown) compositor->video_out->Shutdown(compositor->video_out); - gf_modules_close_interface((GF_BaseInterface *)compositor->video_out); - compositor->video_out = NULL; - return GF_IO_ERR; - } - - /*and init*/ - if (gf_sc_load(compositor) != GF_OK) { - gf_modules_close_interface((GF_BaseInterface *)compositor->rasterizer); - compositor->rasterizer = NULL; - compositor->video_out->Shutdown(compositor->video_out); - gf_modules_close_interface((GF_BaseInterface *)compositor->video_out); - compositor->video_out = NULL; - return GF_IO_ERR; - } + compositor->systems_pids = gf_list_new(); + if (!compositor->systems_pids) return GF_OUT_OF_MEM; compositor->textures = gf_list_new(); + if (!compositor->textures) return GF_OUT_OF_MEM; compositor->textures_gc = gf_list_new(); - compositor->frame_rate = 30.0; - compositor->frame_duration = 33; + if (!compositor->textures_gc) return GF_OUT_OF_MEM; + if (!compositor->fps.num || !compositor->fps.den) { + compositor->fps.num = 30; + compositor->fps.den = 1; + } + compositor->frame_duration = compositor->fps.num * 1000; + compositor->frame_duration /= compositor->fps.den; + compositor->time_nodes = gf_list_new(); + if (!compositor->time_nodes) return GF_OUT_OF_MEM; compositor->event_queue = gf_list_new(); + if (!compositor->event_queue) return GF_OUT_OF_MEM; compositor->event_queue_back = gf_list_new(); + if (!compositor->event_queue_back) return GF_OUT_OF_MEM; compositor->evq_mx = gf_mx_new("EventQueue"); + if (!compositor->evq_mx) return GF_OUT_OF_MEM; #ifdef GF_SR_USE_VIDEO_CACHE compositor->cached_groups = gf_list_new(); + if (!compositor->cached_groups) return GF_OUT_OF_MEM; compositor->cached_groups_queue = gf_list_new(); + if (!compositor->cached_groups_queue) return GF_OUT_OF_MEM; #endif /*load audio renderer*/ if (!compositor->audio_renderer) - compositor->audio_renderer = gf_sc_ar_load(compositor->user); + compositor->audio_renderer = gf_sc_ar_load(compositor, compositor->init_flags); gf_sc_reset_framerate(compositor); - compositor->font_manager = gf_font_manager_new(compositor->user); + compositor->font_manager = gf_filter_get_font_manager(compositor->filter); + if (!compositor->font_manager) return GF_OUT_OF_MEM; compositor->extra_scenes = gf_list_new(); + if (!compositor->extra_scenes) return GF_OUT_OF_MEM; compositor->interaction_level = GF_INTERACT_NORMAL | GF_INTERACT_INPUT_SENSOR | GF_INTERACT_NAVIGATION; compositor->scene_sampled_clock = 0; compositor->video_th_id = gf_th_id(); - return GF_OK; -} - -enum -{ - GF_COMPOSITOR_THREAD_START = 0, - GF_COMPOSITOR_THREAD_RUN, - GF_COMPOSITOR_THREAD_ABORTING, - GF_COMPOSITOR_THREAD_DONE, - GF_COMPOSITOR_THREAD_INIT_FAILED, -}; - -static u32 gf_sc_proc(void *par) -{ - GF_Err e; - GF_Compositor *compositor = (GF_Compositor *) par; - - GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[Compositor] Entering thread ID %d\n", gf_th_id() )); - - compositor->video_th_state = GF_COMPOSITOR_THREAD_START; - e = gf_sc_create(compositor); - if (e != GF_OK) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Compositor] Failed to initialize compositor: %s\n", gf_error_to_string(e) )); - compositor->video_th_state = GF_COMPOSITOR_THREAD_INIT_FAILED; - return 1; - } - - compositor->video_th_state = GF_COMPOSITOR_THREAD_RUN; - while (compositor->video_th_state == GF_COMPOSITOR_THREAD_RUN) { - //simulation tick is self-regulating. Call it regardless of the visibility status - gf_sc_render_frame(compositor); - } -#ifndef GPAC_DISABLE_3D - visual_3d_reset_graphics(compositor->visual); - compositor_2d_reset_gl_auto(compositor); -#endif - gf_sc_texture_cleanup_hw(compositor); - - - /*destroy video out here if we're using openGL, to avoid threading issues*/ - compositor->video_out->Shutdown(compositor->video_out); - gf_modules_close_interface((GF_BaseInterface *)compositor->video_out); - compositor->video_out = NULL; - compositor->video_th_state = GF_COMPOSITOR_THREAD_DONE; - return 0; -} - -GF_Compositor *gf_sc_new(GF_User *user, Bool self_threaded, GF_Terminal *term) -{ - GF_Err e; - GF_Compositor *tmp; - - GF_SAFEALLOC(tmp, GF_Compositor); - if (!tmp) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Failed to allocate compositor : OUT OF MEMORY!\n")); - return NULL; - } - tmp->user = user; - tmp->term = term; - tmp->mx = gf_mx_new("Compositor"); - - /*load proto modules*/ - if (user) { - u32 i; - tmp->proto_modules = gf_list_new(); - for (i=0; i< gf_modules_get_count(user->modules); i++) { - GF_HardcodedProto *ifce = (GF_HardcodedProto *) gf_modules_load_interface(user->modules, i, GF_HARDCODED_PROTO_INTERFACE); - if (ifce) gf_list_add(tmp->proto_modules, ifce); + /*load extensions*/ + compositor->extensions = gf_list_new(); + if (!compositor->extensions) return GF_OUT_OF_MEM; + compositor->unthreaded_extensions = gf_list_new(); + if (!compositor->unthreaded_extensions) return GF_OUT_OF_MEM; + for (i=0; i< gf_modules_count(); i++) { + GF_CompositorExt *ifce = (GF_CompositorExt *) gf_modules_load(i, GF_COMPOSITOR_EXT_INTERFACE); + if (ifce) { + + if (!ifce->process(ifce, GF_COMPOSITOR_EXT_START, compositor)) { + gf_modules_close_interface((GF_BaseInterface *) ifce); + continue; + } + gf_list_add(compositor->extensions, ifce); + if (ifce->caps & GF_COMPOSITOR_EXTENSION_NOT_THREADED) + gf_list_add(compositor->unthreaded_extensions, ifce); } } - /*force initial for 2D/3D setup*/ - tmp->msg_type |= GF_SR_CFG_INITIAL_RESIZE; - /*set default size if owning output*/ - if (tmp->user && !tmp->user->os_window_handler) { - const char *opt; - tmp->new_width = SC_DEF_WIDTH; - tmp->new_height = SC_DEF_HEIGHT; - opt = gf_cfg_get_key(user->config, "Compositor", "DefaultWidth"); - if (opt) tmp->new_width = atoi(opt); - opt = gf_cfg_get_key(user->config, "Compositor", "DefaultHeight"); - if (opt) tmp->new_height = atoi(opt); - - tmp->msg_type |= GF_SR_CFG_SET_SIZE; + if (!gf_list_count(compositor->unthreaded_extensions)) { + gf_list_del(compositor->unthreaded_extensions); + compositor->unthreaded_extensions = NULL; } + compositor->display_width = 320; + compositor->display_height = 240; + compositor->recompute_ar = GF_TRUE; + compositor->scene_sampled_clock = 0; + if (compositor->autoconfig_opengl || compositor->hybrid_opengl) + gf_sc_recompute_ar(compositor, NULL); - if (self_threaded) { - - tmp->VisualThread = gf_th_new("Compositor"); - gf_th_run(tmp->VisualThread, gf_sc_proc, tmp); - - /*wait until init is done*/ - while (tmp->video_th_state < GF_COMPOSITOR_THREAD_RUN) { - gf_sleep(1); - } - /*init failure*/ - if (tmp->video_th_state == GF_COMPOSITOR_THREAD_INIT_FAILED) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("GF_COMPOSITOR_THREAD_INIT_FAILED : Deleting compositor.\n")); - gf_sc_del(tmp); - return NULL; - } - } else { - e = gf_sc_create(tmp); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Error while calling gf_sc_create() : %s, deleting compositor.\n", gf_error_to_string(e))); - gf_sc_del(tmp); - return NULL; - } - } - - if ((tmp->user->init_flags & GF_TERM_NO_REGULATION) || !tmp->VisualThread) - tmp->no_regulation = GF_TRUE; - /*try to load GL extensions*/ #ifndef GPAC_DISABLE_3D - gf_sc_load_opengl_extensions(tmp, GF_FALSE); + gf_sc_load_opengl_extensions(compositor, GF_FALSE); #endif + gf_sc_reload_config(compositor); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RTI, ("[RTI]\tCompositor Cycle Log\tNetworks\tDecoders\tFrame\tDirect Draw\tVisual Config\tEvent\tRoute\tSMIL Timing\tTime node\tTexture\tSMIL Anim\tTraverse setup\tTraverse (and direct Draw)\tTraverse (and direct Draw) without anim\tIndirect Draw\tTraverse And Draw (Indirect or Not)\tFlush\tCycle\n")); - return tmp; + return GF_OK; } - -void gf_sc_del(GF_Compositor *compositor) +void gf_sc_unload(GF_Compositor *compositor) { + u32 i; if (!compositor) return; GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Destroying\n")); + compositor->discard_input_events = GF_TRUE; gf_sc_lock(compositor, GF_TRUE); - if (compositor->VisualThread) { - if (compositor->video_th_state == GF_COMPOSITOR_THREAD_RUN) { - compositor->video_th_state = GF_COMPOSITOR_THREAD_ABORTING; - while (compositor->video_th_state != GF_COMPOSITOR_THREAD_DONE) { - gf_sc_lock(compositor, GF_FALSE); - gf_sleep(1); - gf_sc_lock(compositor, GF_TRUE); - } - } - gf_th_del(compositor->VisualThread); - } else { #ifndef GPAC_DISABLE_3D - compositor_2d_reset_gl_auto(compositor); + compositor_2d_reset_gl_auto(compositor); #endif - gf_sc_texture_cleanup_hw(compositor); - } + gf_sc_texture_cleanup_hw(compositor); if (compositor->video_out) { GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Closing video output\n")); @@ -687,6 +977,7 @@ void gf_sc_del(GF_Compositor *compositor) if (compositor->focus_ancestors) gf_list_del(compositor->focus_ancestors); if (compositor->focus_use_stack) gf_list_del(compositor->focus_use_stack); if (compositor->env_tests) gf_list_del(compositor->env_tests); + if (compositor->systems_pids) gf_list_del(compositor->systems_pids); if (compositor->traverse_state) { gf_list_del(compositor->traverse_state->vrml_sensors); @@ -701,7 +992,9 @@ void gf_sc_del(GF_Compositor *compositor) if (compositor->unit_bbox) mesh_free(compositor->unit_bbox); if (compositor->screen_buffer) gf_free(compositor->screen_buffer); + if (compositor->line_buffer) gf_free(compositor->line_buffer); #endif + if (compositor->framebuffer) gf_free(compositor->framebuffer); GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Unloading visual compositor module\n")); @@ -710,7 +1003,6 @@ void gf_sc_del(GF_Compositor *compositor) /*unload proto modules*/ if (compositor->proto_modules) { - u32 i; for (i=0; i< gf_list_count(compositor->proto_modules); i++) { GF_HardcodedProto *ifce = gf_list_get(compositor->proto_modules, i); gf_modules_close_interface((GF_BaseInterface *) ifce); @@ -728,13 +1020,13 @@ void gf_sc_del(GF_Compositor *compositor) gf_list_rem(compositor->event_queue, 0); gf_free(qev); } - if (compositor->evq_mx) gf_mx_v(compositor->evq_mx); - if (compositor->evq_mx) gf_mx_del(compositor->evq_mx); + if (compositor->evq_mx) { + gf_mx_v(compositor->evq_mx); + gf_mx_del(compositor->evq_mx); + } gf_list_del(compositor->event_queue); gf_list_del(compositor->event_queue_back); - if (compositor->font_manager) gf_font_manager_del(compositor->font_manager); - #ifdef GF_SR_USE_VIDEO_CACHE gf_list_del(compositor->cached_groups); gf_list_del(compositor->cached_groups_queue); @@ -744,29 +1036,50 @@ void gf_sc_del(GF_Compositor *compositor) if (compositor->textures_gc) gf_list_del(compositor->textures_gc); if (compositor->time_nodes) gf_list_del(compositor->time_nodes); if (compositor->extra_scenes) gf_list_del(compositor->extra_scenes); - if (compositor->video_listeners) gf_list_del(compositor->video_listeners); + if (compositor->input_streams) gf_list_del(compositor->input_streams); + if (compositor->x3d_sensors) gf_list_del(compositor->x3d_sensors); + + + /*unload extensions*/ + for (i=0; i< gf_list_count(compositor->extensions); i++) { + GF_CompositorExt *ifce = gf_list_get(compositor->extensions, i); + ifce->process(ifce, GF_COMPOSITOR_EXT_STOP, NULL); + } + + /*remove all event filters*/ +// gf_list_reset(term->event_filters); + + /*unload extensions*/ + for (i=0; i< gf_list_count(compositor->extensions); i++) { + GF_CompositorExt *ifce = gf_list_get(compositor->extensions, i); + gf_modules_close_interface((GF_BaseInterface *) ifce); + } + gf_list_del(compositor->extensions); + gf_list_del(compositor->unthreaded_extensions); gf_sc_lock(compositor, GF_FALSE); gf_mx_del(compositor->mx); - gf_free(compositor); GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Destroyed\n")); } -void gf_sc_set_fps(GF_Compositor *compositor, Double fps) +void gf_sc_set_fps(GF_Compositor *compositor, GF_Fraction fps) { - if (fps) { - compositor->frame_rate = fps; - compositor->frame_duration = (u32) (1000 / fps); + if (fps.den) { + u64 dur; + compositor->fps = fps; + dur = fps.den; + dur *= 1000; + dur /= fps.num; + + compositor->frame_duration = (u32) dur; gf_sc_reset_framerate(compositor); - } -} -u32 gf_sc_get_audio_buffer_length(GF_Compositor *compositor) -{ - if (!compositor || !compositor->audio_renderer || !compositor->audio_renderer->audio_out) return 0; - return compositor->audio_renderer->audio_out->GetTotalBufferTime(compositor->audio_renderer->audio_out); -} + if (compositor->vout && !compositor->timescale) { + gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_TIMESCALE, &PROP_UINT(fps.num) ); + } + } +} static void gf_sc_set_play_state(GF_Compositor *compositor, u32 PlayState) { @@ -777,7 +1090,6 @@ static void gf_sc_set_play_state(GF_Compositor *compositor, u32 PlayState) /*step mode*/ if (PlayState==GF_STATE_STEP_PAUSE) { compositor->step_mode = GF_TRUE; - gf_sc_flush_next_audio(compositor); compositor->paused = 1; } else { compositor->step_mode = GF_FALSE; @@ -786,14 +1098,16 @@ static void gf_sc_set_play_state(GF_Compositor *compositor, u32 PlayState) } } +GF_EXPORT u32 gf_sc_get_clock(GF_Compositor *compositor) { - if (!compositor->bench_mode) { + if (!compositor->bench_mode && compositor->player) { return gf_sc_ar_get_clock(compositor->audio_renderer); } return compositor->scene_sampled_clock; } +GF_EXPORT GF_Err gf_sc_set_scene_size(GF_Compositor *compositor, u32 Width, u32 Height, Bool force_size) { if (!Width || !Height) { @@ -815,13 +1129,6 @@ GF_Err gf_sc_set_scene_size(GF_Compositor *compositor, u32 Width, u32 Height, Bo return GF_OK; } -Bool gf_sc_get_size(GF_Compositor *compositor, u32 *Width, u32 *Height) -{ - *Height = compositor->scene_height; - *Width = compositor->scene_width; - return 1; -} - #ifndef GPAC_DISABLE_SVG GF_EXPORT Fixed gf_sc_svg_convert_length_to_display(GF_Compositor *compositor, SVG_Length *length) @@ -873,10 +1180,14 @@ void compositor_set_ar_scale(GF_Compositor *compositor, Fixed scaleX, Fixed scal static void gf_sc_reset(GF_Compositor *compositor, Bool has_scene) { - Bool draw_mode; - + Bool mode2d; GF_VisualManager *visual; u32 i=0; + + //init failed + if (!compositor->traverse_state) + return; + while ((visual = (GF_VisualManager *)gf_list_enum(compositor->visuals, &i))) { /*reset display list*/ visual->cur_context = visual->context; @@ -890,7 +1201,7 @@ static void gf_sc_reset(GF_Compositor *compositor, Bool has_scene) visual->to_redraw.count = 0; /*reset the surface as well*/ - if (visual->raster_surface) compositor->rasterizer->surface_delete(visual->raster_surface); + if (visual->raster_surface) gf_evg_surface_delete(visual->raster_surface); visual->raster_surface = NULL; } @@ -898,24 +1209,26 @@ static void gf_sc_reset(GF_Compositor *compositor, Bool has_scene) gf_list_reset(compositor->previous_sensors); /*reset traverse state*/ - draw_mode = compositor->traverse_state->immediate_draw; + mode2d = compositor->traverse_state->immediate_draw; gf_list_del(compositor->traverse_state->vrml_sensors); gf_list_del(compositor->traverse_state->use_stack); #ifndef GPAC_DISABLE_3D gf_list_del(compositor->traverse_state->local_lights); + + compositor_3d_delete_fbo(&compositor->fbo_id, &compositor->fbo_tx_id, &compositor->fbo_depth_id, compositor->external_tx_id); #endif - + memset(compositor->traverse_state, 0, sizeof(GF_TraverseState)); - + compositor->traverse_state->vrml_sensors = gf_list_new(); compositor->traverse_state->use_stack = gf_list_new(); #ifndef GPAC_DISABLE_3D compositor->traverse_state->local_lights = gf_list_new(); #endif - + gf_mx2d_init(compositor->traverse_state->transform); gf_cmx_init(&compositor->traverse_state->color_mat); - compositor->traverse_state->immediate_draw = draw_mode; + compositor->traverse_state->immediate_draw = mode2d; #ifdef GF_SR_USE_DEPTH compositor->traverse_state->depth_gain = FIX_ONE; @@ -939,7 +1252,8 @@ static void gf_sc_reset(GF_Compositor *compositor, Bool has_scene) compositor->focus_node = NULL; compositor->focus_text_type = 0; compositor->frame_number = 0; - compositor->video_memory = compositor->was_system_memory ? 0 : 1; + if (compositor->video_memory!=2) + compositor->video_memory = compositor->was_system_memory ? 0 : 1; compositor->rotation = 0; gf_list_reset(compositor->focus_ancestors); @@ -964,7 +1278,11 @@ GF_Err gf_sc_set_scene(GF_Compositor *compositor, GF_SceneGraph *scene_graph) if (!compositor) return GF_BAD_PARAM; gf_sc_lock(compositor, 1); - GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, (scene_graph ? "[Compositor] Attaching new scene\n" : "[Compositor] Detaching scene\n")); + if (scene_graph && !compositor->scene) { + GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Attaching new scene\n")); + } else if (!scene_graph && compositor->scene) { + GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Detaching scene\n")); + } if (compositor->audio_renderer && (compositor->scene != scene_graph)) { GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Reseting audio compositor\n")); @@ -988,6 +1306,7 @@ GF_Err gf_sc_set_scene(GF_Compositor *compositor, GF_SceneGraph *scene_graph) compositor->scene = scene_graph; do_notif = GF_FALSE; if (scene_graph) { + GF_Scene *scene_ctx = gf_sg_get_private(scene_graph); #ifndef GPAC_DISABLE_SVG SVG_Length *w, *h; SVG_ViewBox *vb; @@ -995,23 +1314,28 @@ GF_Err gf_sc_set_scene(GF_Compositor *compositor, GF_SceneGraph *scene_graph) u32 tag; GF_Node *top_node; #endif - const char *opt; Bool had_size_info = compositor->has_size_info; + + compositor->timed_nodes_valid = GF_TRUE; + if (scene_ctx && scene_ctx->is_dynamic_scene) + compositor->timed_nodes_valid = GF_FALSE; + /*get pixel size if any*/ gf_sg_get_scene_size_info(compositor->scene, &width, &height); compositor->has_size_info = (width && height) ? 1 : 0; if (compositor->has_size_info != had_size_info) compositor->scene_width = compositor->scene_height = 0; - compositor->video_memory = gf_scene_is_dynamic_scene(scene_graph); + if (compositor->video_memory!=2) + compositor->video_memory = gf_scene_is_dynamic_scene(scene_graph); #ifndef GPAC_DISABLE_3D compositor->visual->camera.world_bbox.is_set = 0; #endif /*default back color is black*/ - if (! (compositor->user->init_flags & GF_TERM_WINDOWLESS)) { - if (compositor->default_back_color) { - compositor->back_color = compositor->default_back_color; + if (! (compositor->init_flags & GF_TERM_WINDOWLESS)) { + if (compositor->bc) { + compositor->back_color = compositor->bc; } else { compositor->back_color = 0xFF000000; } @@ -1035,7 +1359,7 @@ GF_Err gf_sc_set_scene(GF_Compositor *compositor, GF_SceneGraph *scene_graph) vb = info.far_ptr; } /*default back color is white*/ - if (is_svg && ! (compositor->user->init_flags & GF_TERM_WINDOWLESS)) compositor->back_color = 0xFFFFFFFF; + if (is_svg && ! (compositor->init_flags & GF_TERM_WINDOWLESS)) compositor->back_color = 0xFFFFFFFF; /*hack for SVG where size is set in % - negotiate a canvas size*/ if (!compositor->has_size_info && w && h && vb) { @@ -1069,13 +1393,8 @@ GF_Err gf_sc_set_scene(GF_Compositor *compositor, GF_SceneGraph *scene_graph) } /*default back color is key color*/ - if (compositor->user->init_flags & GF_TERM_WINDOWLESS) { - opt = gf_cfg_get_key(compositor->user->config, "Compositor", "ColorKey"); - if (opt) { - u32 r, g, b, a; - sscanf(opt, "%02X%02X%02X%02X", &a, &r, &g, &b); - compositor->back_color = GF_COL_ARGB(0xFF, r, g, b); - } + if (compositor->init_flags & GF_TERM_WINDOWLESS) { + if (compositor->ckey) compositor->back_color = compositor->ckey; } /*set scene size only if different, otherwise keep scaling/FS*/ @@ -1087,12 +1406,7 @@ GF_Err gf_sc_set_scene(GF_Compositor *compositor, GF_SceneGraph *scene_graph) width = compositor->scene_width; height = compositor->scene_height; - opt = gf_cfg_get_key(compositor->user->config, "Compositor", "ScreenWidth"); - if (opt) width = atoi(opt); - opt = gf_cfg_get_key(compositor->user->config, "Compositor", "ScreenHeight"); - if (opt) height = atoi(opt); - - if (!compositor->user->os_window_handler) { + if (!compositor->os_wnd) { /*only notify user if we are attached to a window*/ //do_notif = 0; if (compositor->video_out->max_screen_width && (width > compositor->video_out->max_screen_width)) @@ -1103,6 +1417,9 @@ GF_Err gf_sc_set_scene(GF_Compositor *compositor, GF_SceneGraph *scene_graph) gf_sc_set_size(compositor,width, height); } } + } else { + gf_sc_ar_reset(compositor->audio_renderer); + compositor->needs_offscreen_gl = GF_FALSE; } gf_sc_reset_framerate(compositor); @@ -1122,18 +1439,11 @@ GF_Err gf_sc_set_scene(GF_Compositor *compositor, GF_SceneGraph *scene_graph) evt.type = GF_EVENT_SCENE_SIZE; evt.size.width = width; evt.size.height = height; - gf_term_send_event(compositor->term, &evt); + gf_sc_send_event(compositor, &evt); } return GF_OK; } -void gf_sc_lock_audio(GF_Compositor *compositor, Bool doLock) -{ - if (compositor->audio_renderer) { - gf_mixer_lock(compositor->audio_renderer->mixer, doLock); - } -} - GF_EXPORT void gf_sc_lock(GF_Compositor *compositor, Bool doLock) { @@ -1151,6 +1461,10 @@ GF_Err gf_sc_set_size(GF_Compositor *compositor, u32 NewWidth, u32 NewHeight) Bool lock_ok; GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("sc_set_size %dx%d\n", NewWidth, NewHeight)); + if (compositor->osize.x && compositor->osize.y) { + NewWidth = compositor->osize.x; + NewHeight = compositor->osize.y; + } if ((compositor->display_width == NewWidth) && (compositor->display_height == NewHeight)) return GF_OK; @@ -1183,7 +1497,7 @@ GF_Err gf_sc_set_size(GF_Compositor *compositor, u32 NewWidth, u32 NewHeight) evt.type = GF_EVENT_SCENE_SIZE; evt.size.width = NewWidth; evt.size.height = NewHeight; - gf_term_send_event(compositor->term, &evt); + gf_sc_send_event(compositor, &evt); } return GF_OK; @@ -1192,261 +1506,60 @@ GF_Err gf_sc_set_size(GF_Compositor *compositor, u32 NewWidth, u32 NewHeight) GF_EXPORT void gf_sc_reload_config(GF_Compositor *compositor) { - const char *sOpt; - - /*changing drivers needs exclusive access*/ gf_sc_lock(compositor, 1); + gf_sc_set_fps(compositor, compositor->fps); - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "FrameRate"); - if (!sOpt) { - sOpt = "30.0"; - gf_cfg_set_key(compositor->user->config, "Compositor", "FrameRate", "30.0"); - } - gf_sc_set_fps(compositor, atof(sOpt)); - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "ForceSceneSize"); - if (sOpt && ! stricmp(sOpt, "yes")) { - compositor->override_size_flags = 1; - } else { - compositor->override_size_flags = 0; - } - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "AntiAlias"); - if (sOpt) { - if (! stricmp(sOpt, "None")) gf_sc_set_option(compositor, GF_OPT_ANTIALIAS, GF_ANTIALIAS_NONE); - else if (! stricmp(sOpt, "Text")) gf_sc_set_option(compositor, GF_OPT_ANTIALIAS, GF_ANTIALIAS_TEXT); - else gf_sc_set_option(compositor, GF_OPT_ANTIALIAS, GF_ANTIALIAS_FULL); - } else { - gf_cfg_set_key(compositor->user->config, "Compositor", "AntiAlias", "All"); - gf_sc_set_option(compositor, GF_OPT_ANTIALIAS, GF_ANTIALIAS_FULL); - } - - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "FocusHighlightFill"); - if (sOpt) sscanf(sOpt, "%x", &compositor->highlight_fill); - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "FocusHighlightStroke"); - if (sOpt) sscanf(sOpt, "%x", &compositor->highlight_stroke); - else compositor->highlight_stroke = 0xFF000000; - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "FocusHighlightStrokeWidth"); - if (sOpt) { - Float v; - sscanf(sOpt, "%f", &v); - compositor->highlight_stroke_width = FLT2FIX(v); - } - else compositor->highlight_stroke_width = FIX_ONE; - - - compositor->text_sel_color = 0xFFAAAAFF; - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "TextSelectHighlight"); - if (sOpt) sscanf(sOpt, "%x", &compositor->text_sel_color); - if (!compositor->text_sel_color) compositor->text_sel_color = 0xFFAAAAFF; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "ScalableZoom"); - compositor->scalable_zoom = (!sOpt || !stricmp(sOpt, "yes") ) ? 1 : 0; - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "DisableYUV"); - compositor->enable_yuv_hw = (sOpt && !stricmp(sOpt, "yes") ) ? 0 : 1; - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "DisablePartialHardwareBlit"); - compositor->disable_partial_hw_blit = (sOpt && !stricmp(sOpt, "yes") ) ? 1 : 0; - - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "StressMode"); - gf_sc_set_option(compositor, GF_OPT_STRESS_MODE, (sOpt && !stricmp(sOpt, "yes") ) ? 1 : 0); - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "HighSpeed"); - gf_sc_set_option(compositor, GF_OPT_HIGHSPEED, (sOpt && !stricmp(sOpt, "yes") ) ? 1 : 0); - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "BoundingVolume"); - if (sOpt) { - if (! stricmp(sOpt, "Box")) gf_sc_set_option(compositor, GF_OPT_DRAW_BOUNDS, GF_BOUNDS_BOX); - else if (! stricmp(sOpt, "AABB")) gf_sc_set_option(compositor, GF_OPT_DRAW_BOUNDS, GF_BOUNDS_AABB); - else gf_sc_set_option(compositor, GF_OPT_DRAW_BOUNDS, GF_BOUNDS_NONE); - } else { - gf_cfg_set_key(compositor->user->config, "Compositor", "BoundingVolume", "None"); - gf_sc_set_option(compositor, GF_OPT_DRAW_BOUNDS, GF_BOUNDS_NONE); - } - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "TextureTextMode"); - if (sOpt && !stricmp(sOpt, "Always")) compositor->texture_text_mode = GF_TEXTURE_TEXT_ALWAYS; - else if (sOpt && !stricmp(sOpt, "Never")) compositor->texture_text_mode = GF_TEXTURE_TEXT_NEVER; - else compositor->texture_text_mode = GF_TEXTURE_TEXT_DEFAULT; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "Output8bit"); - if (!sOpt) { - sOpt = (compositor->video_out->max_screen_bpp > 8) ? "no" : "yes"; - gf_cfg_set_key(compositor->user->config, "Compositor", "Output8bit", sOpt); - } - if (sOpt && !strcmp(sOpt, "yes")) compositor->output_as_8bit = GF_TRUE; - - if (compositor->audio_renderer) { - sOpt = gf_cfg_get_key(compositor->user->config, "Audio", "NoResync"); - compositor->audio_renderer->disable_resync = (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0; - - sOpt = gf_cfg_get_key(compositor->user->config, "Audio", "DisableMultiChannel"); - compositor->audio_renderer->disable_multichannel = (sOpt && !stricmp(sOpt, "yes")) ? 1 : 0; - } - -#ifdef GF_SR_USE_VIDEO_CACHE - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "VideoCacheSize"); - compositor->video_cache_max_size = sOpt ? atoi(sOpt) : 0; - compositor->video_cache_max_size *= 1024; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "CacheScale"); - compositor->cache_scale = sOpt ? atoi(sOpt) : 100; - if (!compositor->cache_scale) compositor->cache_scale = 100; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "CacheTolerance"); - compositor->cache_tolerance = sOpt ? atoi(sOpt) : 30; -#endif + if (compositor->fsize) + compositor->override_size_flags |= 1; + else + compositor->override_size_flags &= ~1; - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "TextureFromDecoderMemory"); - compositor->texture_from_decoder_memory = (sOpt && !strcmp(sOpt, "yes")) ? GF_TRUE : GF_FALSE; - if (!sOpt) - gf_cfg_set_key(compositor->user->config, "Compositor", "TextureFromDecoderMemory", "no"); #ifndef GPAC_DISABLE_3D - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "OpenGLMode"); - if (! (compositor->video_out->hw_caps & GF_VIDEO_HW_OPENGL)) { - if (sOpt && strcmp(sOpt, "disable")) { - GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] OpenGL mode %s requested but no opengl-capable output - disabling openGL\n", sOpt)); + if (compositor->player && (compositor->ogl > GF_SC_GLMODE_OFF)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] OpenGL mode requested but no opengl-capable output - disabling openGL\n")); } compositor->force_opengl_2d = 0; compositor->autoconfig_opengl = 0; compositor->hybrid_opengl = 0; } else { - compositor->force_opengl_2d = (sOpt && !strcmp(sOpt, "always")) ? 1 : 0; - if (!sOpt) { + compositor->force_opengl_2d = (compositor->ogl==GF_SC_GLMODE_ON) ? 1 : 0; + if (compositor->ogl == GF_SC_GLMODE_AUTO) { compositor->recompute_ar = 1; compositor->autoconfig_opengl = 1; } else { - compositor->hybrid_opengl = !strcmp(sOpt, "hybrid") ? 1 : 0; -#ifdef OPENGL_RASTER - compositor->opengl_raster = !strcmp(sOpt, "raster") ? 1 : 0; - if (compositor->opengl_raster) compositor->traverse_state->immediate_draw = GF_TRUE; -#endif + compositor->hybrid_opengl = (compositor->ogl==GF_SC_GLMODE_HYBRID) ? 1 : 0; } } - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "EnablePBO"); - if (!sOpt) gf_cfg_set_key(compositor->user->config, "Compositor", "EnablePBO", "no"); - compositor->enable_pbo = (sOpt && !strcmp(sOpt, "yes")) ? 1 : 0; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "DefaultNavigationMode"); - if (sOpt && !strcmp(sOpt, "Walk")) compositor->default_navigation_mode = GF_NAVIGATE_WALK; - else if (sOpt && !strcmp(sOpt, "Examine")) compositor->default_navigation_mode = GF_NAVIGATE_EXAMINE; - else if (sOpt && !strcmp(sOpt, "Fly")) compositor->default_navigation_mode = GF_NAVIGATE_FLY; - -#ifdef GPAC_HAS_GLU - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "RasterOutlines"); - compositor->raster_outlines = (sOpt && !stricmp(sOpt, "yes") ) ? 1 : 0; -#endif - /*currently: - - no support for npow2 textures, and no support for DrawPixels - */ -#ifndef GPAC_USE_GLES1X - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "EmulatePOW2"); - compositor->emul_pow2 = (sOpt && !stricmp(sOpt, "yes") ) ? 1 : 0; -#else - compositor->raster_outlines = 1; - compositor->emul_pow2 = 1; +#ifdef GPAC_USE_GLES1X + compositor->linegl = 1; + compositor->epow2 = 1; #endif - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "PolygonAA"); - compositor->poly_aa = (sOpt && !stricmp(sOpt, "yes") ) ? 1 : 0; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "BackFaceCulling"); - if (sOpt && !stricmp(sOpt, "Off")) compositor->backcull = GF_BACK_CULL_OFF; - else if (sOpt && !stricmp(sOpt, "Alpha")) compositor->backcull = GF_BACK_CULL_ALPHA; - else compositor->backcull = GF_BACK_CULL_ON; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "Wireframe"); - if (sOpt && !stricmp(sOpt, "WireOnly")) compositor->wiremode = GF_WIREFRAME_ONLY; - else if (sOpt && !stricmp(sOpt, "WireOnSolid")) compositor->wiremode = GF_WIREFRAME_SOLID; - else compositor->wiremode = GF_WIREFRAME_NONE; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "DrawNormals"); - if (sOpt && !stricmp(sOpt, "PerFace")) compositor->draw_normals = GF_NORMALS_FACE; - else if (sOpt && !stricmp(sOpt, "PerVertex")) compositor->draw_normals = GF_NORMALS_VERTEX; - else compositor->draw_normals = GF_NORMALS_NONE; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "DisableGLUScale"); - compositor->disable_glu_scale = (sOpt && !stricmp(sOpt, "yes") ) ? 1 : 0; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "DisableRectExt"); - compositor->disable_rect_ext = (sOpt && !stricmp(sOpt, "yes") ) ? 1 : 0; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "DisableGLCulling"); - compositor->disable_gl_cull = (sOpt && !stricmp(sOpt, "yes") ) ? 1 : 0; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "DisableYUVGL"); - compositor->disable_yuvgl = (sOpt && !stricmp(sOpt, "yes") ) ? 1 : 0; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "DepthScale"); - if (!sOpt) { - sOpt = "100"; - gf_cfg_set_key(compositor->user->config, "Compositor", "DepthScale", sOpt); - } - compositor->depth_gl_scale = FLT2FIX( (Float) atof(sOpt) ); - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "DepthType"); - if (!sOpt) { - sOpt = "VertexArray"; - gf_cfg_set_key(compositor->user->config, "Compositor", "DepthType", sOpt); - } - if (sOpt && !strcmp(sOpt, "Points")) compositor->depth_gl_type = GF_SC_DEPTH_GL_POINTS; - else if (sOpt && !strnicmp(sOpt, "Strips", 6)) { - compositor->depth_gl_type = GF_SC_DEPTH_GL_STRIPS; - compositor->depth_gl_strips_filter = 0; - if (strlen(sOpt)>7) compositor->depth_gl_strips_filter = FLT2FIX( (Float) atof(sOpt+7) ); - } - else if (sOpt && !strcmp(sOpt, "VertexArray")) compositor->depth_gl_type = GF_SC_DEPTH_GL_VBO; - else compositor->depth_gl_type = GF_SC_DEPTH_GL_NONE; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "NumViews"); - if (!sOpt) { - sOpt = "1"; - gf_cfg_set_key(compositor->user->config, "Compositor", "NumViews", "1"); - } - compositor->visual->nb_views = atoi(sOpt); - if (!compositor->visual->nb_views) compositor->visual->nb_views = 1; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "StereoType"); - if (!sOpt) { - sOpt = "none"; - gf_cfg_set_key(compositor->user->config, "Compositor", "StereoType", "none"); - } - if (!strcmp(sOpt, "SideBySide")) compositor->visual->autostereo_type = GF_3D_STEREO_SIDE; - else if (!strcmp(sOpt, "TopToBottom")) compositor->visual->autostereo_type = GF_3D_STEREO_TOP; - else if (!strcmp(sOpt, "Custom")) compositor->visual->autostereo_type = GF_3D_STEREO_CUSTOM; - /*built-in interleavers*/ - else if (!strcmp(sOpt, "Anaglyph")) compositor->visual->autostereo_type = GF_3D_STEREO_ANAGLYPH; - else if (!strcmp(sOpt, "Columns")) compositor->visual->autostereo_type = GF_3D_STEREO_COLUMNS; - else if (!strcmp(sOpt, "Rows")) compositor->visual->autostereo_type = GF_3D_STEREO_ROWS; - else if (!strcmp(sOpt, "SPV19")) { - compositor->visual->autostereo_type = GF_3D_STEREO_5VSP19; + compositor->visual->nb_views = compositor->nbviews; + compositor->visual->camlay = compositor->camlay; + compositor->visual->autostereo_type = compositor->stereo; + if (compositor->visual->autostereo_type == GF_3D_STEREO_5VSP19) { if (compositor->visual->nb_views != 5) { - GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] SPV19 interleaving used but only %d views indicated - adjusting to 5 view\n", compositor->visual->nb_views )); + if (compositor->visual->nb_views) { + GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] SPV19 interleaving used but only %d views indicated - adjusting to 5 view\n", compositor->visual->nb_views )); + } compositor->visual->nb_views = 5; - gf_cfg_set_key(compositor->user->config, "Compositor", "NumViews", "5"); } } - else if (!strcmp(sOpt, "ALIO")) { - compositor->visual->autostereo_type = GF_3D_STEREO_8VALIO; + else if (compositor->visual->autostereo_type == GF_3D_STEREO_8VALIO) { if (compositor->visual->nb_views != 8) { - GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] ALIO interleaving used but only %d views indicated - adjusting to 8 view\n", compositor->visual->nb_views )); - compositor->visual->nb_views = 8; - gf_cfg_set_key(compositor->user->config, "Compositor", "NumViews", "8"); + if (compositor->visual->nb_views) { + GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] ALIO interleaving used but only %d views indicated - adjusting to 8 view\n", compositor->visual->nb_views )); + compositor->visual->nb_views = 8; + } } } - else if (!strcmp(sOpt, "StereoHeadset")) compositor->visual->autostereo_type = GF_3D_STEREO_HEADSET; - else { - compositor->visual->autostereo_type = GF_3D_STEREO_NONE; - compositor->visual->nb_views = 1; - gf_cfg_set_key(compositor->user->config, "Compositor", "NumViews", "1"); - } if ((compositor->visual->autostereo_type!=GF_3D_STEREO_NONE) && (compositor->visual->autostereo_type <= GF_3D_STEREO_HEADSET)) { compositor->visual->nb_views = 2; @@ -1460,96 +1573,60 @@ void gf_sc_reload_config(GF_Compositor *compositor) case GF_3D_STEREO_COLUMNS: case GF_3D_STEREO_ROWS: if (compositor->visual->nb_views != 2) { - GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] Stereo interleaving used but %d views indicated - adjusting to 2 view\n", compositor->visual->nb_views )); + if (compositor->visual->nb_views) { + GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] Stereo interleaving used but %d views indicated - adjusting to 2 view\n", compositor->visual->nb_views )); + } compositor->visual->nb_views = 2; - gf_cfg_set_key(compositor->user->config, "Compositor", "NumViews", "2"); } break; } - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "FramePacking"); - if (!sOpt) { - sOpt = "None"; - gf_cfg_set_key(compositor->user->config, "Compositor", "FramePacking", "None"); - } - if (!strcmp(sOpt, "Side")) compositor->frame_packing = GF_3D_STEREO_SIDE; - else if (!strcmp(sOpt, "Top")) compositor->frame_packing = GF_3D_STEREO_TOP; - else compositor->frame_packing = GF_3D_STEREO_NONE; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "CameraLayout"); - if (!sOpt) { - sOpt = "Straight"; - gf_cfg_set_key(compositor->user->config, "Compositor", "CameraLayout", "Straight"); - } - if (!strcmp(sOpt, "Linear")) compositor->visual->camera_layout = GF_3D_CAMERA_LINEAR; - else if (!strcmp(sOpt, "Circular")) compositor->visual->camera_layout = GF_3D_CAMERA_CIRCULAR; - else if (!strcmp(sOpt, "OffAxis")) compositor->visual->camera_layout = GF_3D_CAMERA_OFFAXIS; - else compositor->visual->camera_layout = GF_3D_CAMERA_STRAIGHT; - - compositor->interoccular_distance = FLT2FIX(6.3f); - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "EyeSeparation"); - if (sOpt) compositor->interoccular_distance = FLT2FIX( atof(sOpt)) ; - else gf_cfg_set_key(compositor->user->config, "Compositor", "EyeSeparation", "6.3"); - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "ReverseViews"); - if (sOpt && !strcmp(sOpt, "yes")) compositor->visual->reverse_views = 1; + if (!compositor->visual->nb_views) compositor->visual->nb_views = 1; #endif //GPAC_DISABLE_3D - /*load defer mode only once hybrid_opengl is known. If no hybrid openGL and no backbuffer 2D, disable defer rendering*/ if (!compositor->hybrid_opengl && compositor->video_out->hw_caps & GF_VIDEO_HW_DIRECT_ONLY) { compositor->traverse_state->immediate_draw = 1; } else { - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "DrawMode"); - if (!sOpt) { - sOpt = "defer"; - gf_cfg_set_key(compositor->user->config, "Compositor", "DrawMode", sOpt); - } - - if (!strcmp(sOpt, "immediate")) compositor->traverse_state->immediate_draw = 1; - else if (!strcmp(sOpt, "defer-debug")) { + if (compositor->mode2d==GF_DRAW_MODE_IMMEDIATE) + compositor->traverse_state->immediate_draw = 1; + else if (compositor->mode2d==GF_DRAW_MODE_DEFER_DEBUG) { compositor->traverse_state->immediate_draw = 0; compositor->debug_defer = 1; + } else { + compositor->traverse_state->immediate_draw = 0; } - else compositor->traverse_state->immediate_draw = 0; } #ifdef GF_SR_USE_DEPTH - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "AutoStereoCalibration"); - compositor->auto_calibration = (sOpt && !strcmp(sOpt, "yes")) ? 1 : 0; - - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "DisplayDepth"); - compositor->display_depth = sOpt ? (!strcmp(sOpt, "auto") ? -1 : atoi(sOpt)) : 0; #ifndef GPAC_DISABLE_3D /*if auto-stereo mode, turn on display depth by default*/ - if (compositor->visual->autostereo_type && !compositor->display_depth) { - compositor->display_depth = -1; + if (compositor->visual->autostereo_type && !compositor->dispdepth) { + compositor->dispdepth = -1; } #endif - if (!compositor->video_out->view_distance) { - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "ViewDistance"); - compositor->video_out->view_distance = FLT2FIX( sOpt ? (Float) atof(sOpt) : 50.0f ); - } - -#ifndef GPAC_DISABLE_3D - sOpt = gf_cfg_get_key(compositor->user->config, "Compositor", "FocusDistance"); - compositor->focus_distance = 0; - if (sOpt) - compositor->focus_distance = FLT2FIX( atof(sOpt) ); - else - gf_cfg_set_key(compositor->user->config, "Compositor", "FocusDistance", "0"); #endif -#endif + if (!compositor->video_out->dispdist) { + compositor->video_out->dispdist = compositor->dispdist; + } + if (compositor->sgaze) compositor->gazer_enabled = GF_TRUE; + if (!compositor->video_out->dpi_x) { + compositor->video_out->dpi_x = compositor->dpi.x; + compositor->video_out->dpi_y = compositor->dpi.y; + } gf_sc_reset_graphics(compositor); gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); + if (compositor->audio_renderer) { + gf_sc_ar_set_volume(compositor->audio_renderer, compositor->avol); + gf_sc_ar_set_pan(compositor->audio_renderer, compositor->apan); + } gf_sc_lock(compositor, 0); } @@ -1578,22 +1655,22 @@ GF_Err gf_sc_set_option(GF_Compositor *compositor, u32 type, u32 value) gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); break; case GF_OPT_STRESS_MODE: - compositor->stress_mode = value; + compositor->stress = value; break; case GF_OPT_ANTIALIAS: - compositor->antiAlias = value; + compositor->aa = value; gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); break; case GF_OPT_HIGHSPEED: - compositor->high_speed = value; + compositor->fast = value; gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); break; case GF_OPT_DRAW_BOUNDS: - compositor->draw_bvol = value; + compositor->bvol = value; gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); break; case GF_OPT_TEXTURE_TEXT: - compositor->texture_text_mode = value; + compositor->textxt = value; gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); break; case GF_OPT_ASPECT_RATIO: @@ -1651,7 +1728,7 @@ GF_Err gf_sc_set_option(GF_Compositor *compositor, u32 type, u32 value) } break; case GF_OPT_SCALABLE_ZOOM: - compositor->scalable_zoom = value; + compositor->sz = value; /*emulate size message to force AR recompute*/ compositor->msg_type |= GF_SR_CFG_AR; break; @@ -1671,7 +1748,7 @@ GF_Err gf_sc_set_option(GF_Compositor *compositor, u32 type, u32 value) break; case GF_OPT_YUV_HARDWARE: - compositor->enable_yuv_hw = value; + compositor->yuvhw = value; break; case GF_OPT_NAVIGATION_TYPE: compositor->rotation = 0; @@ -1715,29 +1792,29 @@ GF_Err gf_sc_set_option(GF_Compositor *compositor, u32 type, u32 value) #ifndef GPAC_DISABLE_3D case GF_OPT_EMULATE_POW2: - compositor->emul_pow2 = value; + compositor->epow2 = value; break; case GF_OPT_POLYGON_ANTIALIAS: - compositor->poly_aa = value; + compositor->paa = value; break; case GF_OPT_BACK_CULL: - compositor->backcull = value; + compositor->bcull = value; break; case GF_OPT_WIREFRAME: - compositor->wiremode = value; + compositor->wire = value; break; case GF_OPT_NORMALS: - compositor->draw_normals = value; + compositor->norms = value; break; #ifdef GPAC_HAS_GLU case GF_OPT_RASTER_OUTLINES: - compositor->raster_outlines = value; + compositor->linegl = value; break; #endif case GF_OPT_NO_RECT_TEXTURE: - if (value != compositor->disable_rect_ext) { - compositor->disable_rect_ext = value; + if (value != compositor->rext) { + compositor->rext = value; /*RECT texture support - we must reload HW*/ gf_sc_reset_graphics(compositor); } @@ -1763,6 +1840,10 @@ GF_Err gf_sc_set_option(GF_Compositor *compositor, u32 type, u32 value) e = GF_NOT_SUPPORTED; #endif break; + case GF_OPT_MULTIVIEW_MODE: + compositor->multiview_mode = value; + break; + default: e = GF_BAD_PARAM; @@ -1816,6 +1897,7 @@ Bool gf_sc_is_over(GF_Compositor *compositor, GF_SceneGraph *scene_graph) return 1; } +GF_EXPORT u32 gf_sc_get_option(GF_Compositor *compositor, u32 type) { switch (type) { @@ -1828,7 +1910,7 @@ u32 gf_sc_get_option(GF_Compositor *compositor, u32 type) case GF_OPT_IS_OVER: return gf_sc_is_over(compositor, NULL); case GF_OPT_STRESS_MODE: - return compositor->stress_mode; + return compositor->stress; case GF_OPT_AUDIO_VOLUME: return compositor->audio_renderer->volume; case GF_OPT_AUDIO_PAN: @@ -1837,9 +1919,9 @@ u32 gf_sc_get_option(GF_Compositor *compositor, u32 type) return compositor->audio_renderer->mute; case GF_OPT_ANTIALIAS: - return compositor->antiAlias; + return compositor->aa; case GF_OPT_HIGHSPEED: - return compositor->high_speed; + return compositor->fast; case GF_OPT_ASPECT_RATIO: return compositor->aspect_ratio; case GF_OPT_FULLSCREEN: @@ -1851,7 +1933,7 @@ u32 gf_sc_get_option(GF_Compositor *compositor, u32 type) case GF_OPT_FREEZE_DISPLAY: return compositor->freeze_display; case GF_OPT_TEXTURE_TEXT: - return compositor->texture_text_mode; + return compositor->textxt; case GF_OPT_USE_OPENGL: return compositor->force_opengl_2d; @@ -1860,11 +1942,11 @@ u32 gf_sc_get_option(GF_Compositor *compositor, u32 type) if (compositor->debug_defer) return GF_DRAW_MODE_DEFER_DEBUG; return GF_DRAW_MODE_DEFER; case GF_OPT_SCALABLE_ZOOM: - return compositor->scalable_zoom; + return compositor->sz; case GF_OPT_YUV_HARDWARE: - return compositor->enable_yuv_hw; + return compositor->yuvhw; case GF_OPT_YUV_FORMAT: - return compositor->enable_yuv_hw ? compositor->video_out->yuv_pixel_format : 0; + return compositor->yuvhw ? compositor->video_out->yuv_pixel_format : 0; case GF_OPT_NAVIGATION_TYPE: #ifndef GPAC_DISABLE_3D if (compositor->navigation_disabled) return GF_NAVIGATE_TYPE_NONE; @@ -1897,7 +1979,7 @@ u32 gf_sc_get_option(GF_Compositor *compositor, u32 type) case GF_OPT_VIDEO_CACHE_SIZE: #ifdef GF_SR_USE_VIDEO_CACHE - return compositor->video_cache_max_size/1024; + return compositor->vcsize/1024; #else return 0; #endif @@ -1916,6 +1998,7 @@ u32 gf_sc_get_option(GF_Compositor *compositor, u32 type) } } +GF_EXPORT void gf_sc_map_point(GF_Compositor *compositor, s32 X, s32 Y, Fixed *bifsX, Fixed *bifsY) { /*coordinates are in user-like OS....*/ @@ -1946,7 +2029,7 @@ GF_Err gf_sc_get_screen_buffer(GF_Compositor *compositor, GF_VideoSurface *frame return e; } -GF_Err gf_sc_get_offscreen_buffer(GF_Compositor *compositor, GF_VideoSurface *framebuffer, u32 view_idx, u32 depth_dump_mode) +GF_Err gf_sc_get_offscreen_buffer(GF_Compositor *compositor, GF_VideoSurface *framebuffer, u32 view_idx, GF_CompositorGrabMode depth_dump_mode) { if (!compositor || !framebuffer) return GF_BAD_PARAM; #ifndef GPAC_DISABLE_3D @@ -2015,12 +2098,13 @@ Double gf_sc_get_fps(GF_Compositor *compositor, Bool absoluteFPS) gf_mx_v(compositor->mx); - if (!run_time) return (Double) compositor->frame_rate; + if (!run_time) return ((Double) compositor->fps.num)/compositor->fps.den; fps = 1000*frames; fps /= run_time; return fps; } +GF_EXPORT void gf_sc_register_time_node(GF_Compositor *compositor, GF_TimeNode *tn) { /*may happen with DEF/USE */ @@ -2029,22 +2113,17 @@ void gf_sc_register_time_node(GF_Compositor *compositor, GF_TimeNode *tn) gf_list_add(compositor->time_nodes, tn); tn->is_registered = 1; } + +GF_EXPORT void gf_sc_unregister_time_node(GF_Compositor *compositor, GF_TimeNode *tn) { gf_list_del_item(compositor->time_nodes, tn); } - - -GF_Node *gf_sc_pick_node(GF_Compositor *compositor, s32 X, s32 Y) -{ - return NULL; -} - static void gf_sc_recompute_ar(GF_Compositor *compositor, GF_Node *top_node) { Bool force_pause; - + // force_pause = compositor->audio_renderer->Frozen ? 0 : 1; force_pause = GF_FALSE; @@ -2071,24 +2150,36 @@ static void gf_sc_recompute_ar(GF_Compositor *compositor, GF_Node *top_node) } if (compositor->visual->type_3d) { compositor_3d_set_aspect_ratio(compositor); - gf_sc_load_opengl_extensions(compositor, compositor->visual->type_3d); + gf_sc_load_opengl_extensions(compositor, compositor->visual->type_3d ? GF_TRUE : GF_FALSE); #ifndef GPAC_USE_GLES1X visual_3d_init_shaders(compositor->visual); #endif if (compositor->autoconfig_opengl) { + u32 mode = compositor->ogl; compositor->autoconfig_opengl = 0; compositor->force_opengl_2d = 0; + compositor->hybrid_opengl = GF_FALSE; compositor->visual->type_3d = prev_type_3d; #if !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) //enable hybrid mode by default - if (compositor->visual->compositor->shader_only_mode) { - gf_cfg_set_key(compositor->user->config, "Compositor", "OpenGLMode", "hybrid"); - compositor->hybrid_opengl = 1; - } else { - gf_cfg_set_key(compositor->user->config, "Compositor", "OpenGLMode", "disable"); + if (compositor->visual->compositor->shader_mode_disabled && (mode==GF_SC_GLMODE_HYBRID) ) { + mode = GF_SC_GLMODE_OFF; } #endif + switch (mode) { + case GF_SC_GLMODE_OFF: + break; + case GF_SC_GLMODE_ON: + compositor->force_opengl_2d = 1; + if (!compositor->visual->type_3d) + compositor->visual->type_3d = 1; + break; + case GF_SC_GLMODE_AUTO: + case GF_SC_GLMODE_HYBRID: + compositor->hybrid_opengl = GF_TRUE; + break; + } } } @@ -2127,6 +2218,11 @@ static void gf_sc_recompute_ar(GF_Compositor *compositor, GF_Node *top_node) compositor->msg_type |= GF_SR_CFG_FULLSCREEN; } + if (compositor->vout) { + gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_WIDTH, &PROP_UINT(compositor->output_width) ); + gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_HEIGHT, &PROP_UINT(compositor->output_height) ); + gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_FPS, &PROP_FRAC(compositor->fps) ); + } } } @@ -2156,7 +2252,7 @@ static void gf_sc_setup_root_visual(GF_Compositor *compositor, GF_Node *top_node #ifndef GPAC_DISABLE_3D /*move to perspective 3D when simulating depth*/ #ifdef GF_SR_USE_DEPTH - if (compositor->display_depth) { + if (compositor->dispdepth) { compositor->visual->type_3d = 0; compositor->visual->camera.is_3D = 0; } else @@ -2193,7 +2289,7 @@ static void gf_sc_setup_root_visual(GF_Compositor *compositor, GF_Node *top_node case TAG_SVG_svg: #ifndef GPAC_DISABLE_3D #ifdef GF_SR_USE_DEPTH - if (compositor->display_depth) { + if (compositor->dispdepth>=0) { compositor->visual->type_3d = 2; compositor->visual->camera.is_3D = 1; } else @@ -2227,6 +2323,9 @@ static void gf_sc_setup_root_visual(GF_Compositor *compositor, GF_Node *top_node if (compositor->force_opengl_2d==2) force_navigate=1; } + if (compositor->drv==GF_SC_DRV_AUTO) + gf_sc_check_video_driver(compositor); + if (! (compositor->video_out->hw_caps & GF_VIDEO_HW_OPENGL)) { compositor->visual->type_3d = 0; compositor->visual->camera.is_3D = 0; @@ -2253,7 +2352,7 @@ static void gf_sc_setup_root_visual(GF_Compositor *compositor, GF_Node *top_node } -static void gf_sc_draw_scene(GF_Compositor *compositor) +static Bool gf_sc_draw_scene(GF_Compositor *compositor) { u32 flags; @@ -2263,11 +2362,11 @@ static void gf_sc_draw_scene(GF_Compositor *compositor) //GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Scene has no root node, nothing to draw\n")); //nothing to draw, skip flush compositor->skip_flush = 1; - return; + return GF_FALSE; } #ifdef GF_SR_USE_VIDEO_CACHE - if (!compositor->video_cache_max_size) + if (!compositor->vcsize) compositor->traverse_state->in_group_cache = 1; #endif @@ -2275,17 +2374,38 @@ static void gf_sc_draw_scene(GF_Compositor *compositor) if (compositor->video_setup_failed) { compositor->skip_flush = 1; } - else if (! visual_draw_frame(compositor->visual, top_node, compositor->traverse_state, 1)) { - /*android backend uses opengl without telling it to us, we need an ugly hack here ...*/ -#ifdef GPAC_ANDROID - compositor->skip_flush = 0; -#else - if (compositor->skip_flush==2) { - compositor->skip_flush = 0; - } else { - compositor->skip_flush = 1; + else { + if (compositor->nodes_pending) { + u32 i, count, n_count; + i=0; + count = gf_list_count(compositor->nodes_pending); + while (inodes_pending, i); + gf_node_traverse(n, NULL); + if (!compositor->nodes_pending) break; + n_count = gf_list_count(compositor->nodes_pending); + if (n_count==count) i++; + else count=n_count; + } + } + if (compositor->passthrough_txh) { + gf_sc_setup_passthrough(compositor); + compositor->traverse_state->immediate_draw = 1; } + + if (! visual_draw_frame(compositor->visual, top_node, compositor->traverse_state, 1)) { + /*android backend uses opengl without telling it to us, we need an ugly hack here ...*/ +#ifdef GPAC_CONFIG_ANDROID + compositor->skip_flush = 0; +#else + if (compositor->skip_flush==2) { + compositor->skip_flush = 0; + } else { + compositor->skip_flush = 1; + } #endif + } + } @@ -2301,10 +2421,13 @@ static void gf_sc_draw_scene(GF_Compositor *compositor) compositor->recompute_ar = 0; } compositor->zoom_changed = 0; + compositor->audio_renderer->scene_ready = GF_TRUE; + return GF_TRUE; } #ifndef GPAC_DISABLE_LOG +//defined in smil_anim ... extern u32 time_spent_in_anim; #endif @@ -2323,18 +2446,6 @@ static void compositor_release_textures(GF_Compositor *compositor, Bool frame_dr } } -void gf_sc_flush_video(GF_Compositor *compositor) -{ - GF_Window rc; - - //release compositor in case we have vsync - gf_sc_lock(compositor, 0); - rc.x = rc.y = 0; - rc.w = compositor->display_width; - rc.h = compositor->display_height; - compositor->video_out->Flush(compositor->video_out, &rc); - gf_sc_lock(compositor, 1); -} void gf_sc_render_frame(GF_Compositor *compositor) { @@ -2342,8 +2453,9 @@ void gf_sc_render_frame(GF_Compositor *compositor) GF_SceneGraph *sg; #endif GF_List *temp_queue; - u32 in_time, end_time, i, count, frame_duration; - Bool frame_drawn, has_timed_nodes=GF_FALSE, all_tx_done=GF_TRUE; + u32 in_time, end_time, i, count, frame_duration, frame_ts, frame_draw_type_bck; + Bool frame_drawn, has_timed_nodes=GF_FALSE, has_tx_streams=GF_FALSE, all_tx_done=GF_TRUE; + #ifndef GPAC_DISABLE_LOG s32 event_time, route_time, smil_timing_time=0, time_node_time, texture_time, traverse_time, flush_time, txtime; #endif @@ -2359,12 +2471,20 @@ void gf_sc_render_frame(GF_Compositor *compositor) /*first thing to do, let the video output handle user event if it is not threaded*/ compositor->video_out->ProcessEvent(compositor->video_out, NULL); + //handle all unthreaded extensions + if (compositor->unthreaded_extensions) { + count = gf_list_count(compositor->unthreaded_extensions); + for (i=0; iunthreaded_extensions, i); + ifce->process(ifce, GF_COMPOSITOR_EXT_PROCESS, NULL); + } + } + if (compositor->freeze_display) { gf_sc_lock(compositor, 0); - if (!compositor->bench_mode) { + if (!compositor->bench_mode && compositor->player) { compositor->scene_sampled_clock = gf_sc_ar_get_clock(compositor->audio_renderer); } - if (!compositor->no_regulation) gf_sleep(compositor->frame_duration); return; } @@ -2373,22 +2493,32 @@ void gf_sc_render_frame(GF_Compositor *compositor) /* if there is no scene*/ if (!compositor->scene && !gf_list_count(compositor->extra_scenes) ) { gf_sc_draw_scene(compositor); - //increase clock in bench mode before releasing mutex - if (compositor->bench_mode && (compositor->force_bench_frame==1)) { - compositor->scene_sampled_clock += compositor->frame_duration; + if (compositor->player) { + //increase clock in bench mode before releasing mutex + if (compositor->bench_mode && (compositor->force_bench_frame==1)) { + compositor->scene_sampled_clock += compositor->frame_duration; + } + } else { + if (compositor->root_scene && compositor->root_scene->graph_attached) { + compositor->check_eos_state = 2; + } } gf_sc_lock(compositor, 0); - if (!compositor->no_regulation) { - gf_sleep(compositor->bench_mode ? 2 : compositor->frame_duration); - } compositor->force_bench_frame=0; compositor->frame_draw_type = 0; + compositor->recompute_ar = 0; + compositor->ms_until_next_frame = compositor->frame_duration; return; } if (compositor->reset_graphics) { gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); visual_reset_graphics(compositor->visual); + +#ifndef GPAC_DISABLE_3D + compositor_3d_delete_fbo(&compositor->fbo_id, &compositor->fbo_tx_id, &compositor->fbo_depth_id, compositor->external_tx_id); +#endif + } /*process pending user events*/ @@ -2422,15 +2552,16 @@ void gf_sc_render_frame(GF_Compositor *compositor) event_time = gf_sys_clock() - event_time; #endif - - if (!compositor->bench_mode) { - compositor->scene_sampled_clock = gf_sc_ar_get_clock(compositor->audio_renderer); - } else { - if (compositor->force_bench_frame==1) { - //a system frame is pending on a future frame - we must increase our time - compositor->scene_sampled_clock += compositor->frame_duration; + if (compositor->player) { + if (!compositor->bench_mode) { + compositor->scene_sampled_clock = gf_sc_ar_get_clock(compositor->audio_renderer); + } else { + if (compositor->force_bench_frame==1) { + //a system frame is pending on a future frame - we must increase our time + compositor->scene_sampled_clock += compositor->frame_duration; + } + compositor->force_bench_frame = 0; } - compositor->force_bench_frame = 0; } @@ -2439,41 +2570,12 @@ void gf_sc_render_frame(GF_Compositor *compositor) compositor->ms_until_next_frame = GF_INT_MAX; frame_duration = compositor->frame_duration; -#ifndef GPAC_DISABLE_LOG - texture_time = gf_sys_clock(); -#endif - /*update all video textures*/ - count = gf_list_count(compositor->textures); - for (i=0; itextures, i); - if (!txh) break; - //this is not a natural (video) texture - if (! txh->stream) continue; - - /*signal graphics reset before updating*/ - if (compositor->reset_graphics && txh->tx_io) gf_sc_texture_reset(txh); - txh->update_texture_fcnt(txh); - - if (!txh->stream_finished) { - u32 d = gf_mo_get_min_frame_dur(txh->stream); - if (d && (d < frame_duration)) frame_duration = d; - - all_tx_done=0; - } - } - - //it may happen that we have a reconfigure request at this stage, especially if updating one of the textures - //forced a relayout - do it right away - if (compositor->msg_type) { - gf_sc_lock(compositor, 0); - return; - } - + if (compositor->auto_rotate) + compositor_handle_auto_navigation(compositor); -#ifndef GPAC_DISABLE_LOG - texture_time = gf_sys_clock() - texture_time; -#endif + /*first update time nodes since they may trigger play/stop request*/ + compositor->force_late_frame_draw = GF_FALSE; #ifndef GPAC_DISABLE_SVG @@ -2515,19 +2617,92 @@ void gf_sc_render_frame(GF_Compositor *compositor) count--; continue; } - has_timed_nodes = GF_TRUE; + has_timed_nodes = compositor->timed_nodes_valid; } #ifndef GPAC_DISABLE_LOG time_node_time = gf_sys_clock() - time_node_time; #endif + + compositor->passthrough_txh = NULL; +#ifndef GPAC_DISABLE_LOG + texture_time = gf_sys_clock(); +#endif + //compute earliest frame TS in all textures that need refresh (skip textures with same timing) + frame_ts = (u32) -1; + + /*update all video textures*/ + frame_draw_type_bck = compositor->frame_draw_type; + compositor->frame_draw_type = 0; + has_tx_streams = GF_FALSE; + count = gf_list_count(compositor->textures); + for (i=0; itextures, i); + if (!txh) break; + //this is not a natural (video) texture + if (! txh->stream) continue; + has_tx_streams = GF_TRUE; + + /*signal graphics reset before updating*/ + if (compositor->reset_graphics && txh->tx_io) gf_sc_texture_reset(txh); + txh->update_texture_fcnt(txh); + + if (!txh->stream_finished) { + u32 d = gf_mo_get_min_frame_dur(txh->stream); + if (d && (d < frame_duration)) frame_duration = d; + //if the texture needs update (new frame), compute its timestamp in system timebase + if (txh->needs_refresh) { + u32 ts = gf_mo_map_timestamp_to_sys_clock(txh->stream, txh->stream->timestamp); + if (tsplayer) { + if (frame_draw_type_bck) + compositor->frame_draw_type = frame_draw_type_bck; + } + //in non player mode, don't redraw frame if due to end of streams on textures + else if (!frame_draw_type_bck && compositor->frame_draw_type && all_tx_done) + compositor->frame_draw_type = 0; + + + if (!compositor->player) { + if (compositor->passthrough_txh && compositor->passthrough_txh->frame_ifce && !compositor->passthrough_txh->frame_ifce->get_plane) { + compositor->passthrough_txh = NULL; + } + if (!compositor->passthrough_txh && frame_draw_type_bck) + compositor->frame_draw_type = frame_draw_type_bck; + + if (compositor->passthrough_txh && compositor->passthrough_txh->width && compositor->passthrough_txh->needs_refresh) { + compositor->scene_sampled_clock = compositor->passthrough_txh->last_frame_time/* + dur*/; + } + //we were buffering and are now no longer, update scene clock + if (compositor->passthrough_txh && compositor->passthrough_check_buffer && !gf_mo_is_buffering(compositor->passthrough_txh->stream)) { + u32 dur = gf_mo_get_min_frame_dur(compositor->passthrough_txh->stream); + compositor->passthrough_check_buffer = GF_FALSE; + compositor->scene_sampled_clock = compositor->passthrough_txh->last_frame_time + dur; + } + } + + //it may happen that we have a reconfigure request at this stage, especially if updating one of the textures + //forced a relayout - do it right away + if (compositor->msg_type) { + gf_sc_lock(compositor, 0); + return; + } + + if (compositor->focus_text_type) { if (!compositor->caret_next_draw_time) { compositor->caret_next_draw_time = gf_sys_clock(); compositor->show_caret = 1; } if (compositor->caret_next_draw_time <= compositor->last_frame_time) { - compositor->frame_draw_type=GF_SC_DRAW_FRAME; + compositor->frame_draw_type = GF_SC_DRAW_FRAME; compositor->caret_next_draw_time+=500; compositor->show_caret = !compositor->show_caret; compositor->text_edit_changed = 1; @@ -2613,18 +2788,59 @@ void gf_sc_render_frame(GF_Compositor *compositor) if (compositor->force_next_frame_redraw) { compositor->force_next_frame_redraw=0; - compositor->frame_draw_type=GF_SC_DRAW_FRAME; + compositor->frame_draw_type = GF_SC_DRAW_FRAME; } - //if hidden and no listener, do not draw the scene - if (compositor->is_hidden && !compositor->video_listeners) { + //if hidden and player mode, do not draw the scene + if (compositor->is_hidden && compositor->player) { compositor->frame_draw_type = 0; } + if (!compositor->player) { + if (compositor->check_eos_state<=1) { + compositor->check_eos_state = 0; + /*check if we have to force a frame dispatch */ + + //no passthrough texture + if (!compositor->passthrough_txh) { + if (!compositor->vfr) { + //in CFR and no texture associated, always force a redraw + if (!has_tx_streams && has_timed_nodes) + compositor->frame_draw_type = GF_SC_DRAW_FRAME; + //otherwise if texture(s) but not all done, force a redraw + else if (!all_tx_done) + compositor->frame_draw_type = GF_SC_DRAW_FRAME; + //we still have active system pids (BIFS/LASeR/DIMS commands), force a redraw + if (gf_list_count(compositor->systems_pids)) + compositor->frame_draw_type = GF_SC_DRAW_FRAME; + //validator always triggers redraw to make sure the scene clock is incremented so that events can be fired + if (compositor->validator_mode) + compositor->frame_draw_type = GF_SC_DRAW_FRAME; + } + } + //we have a passthrough texture, only generate an output frame when we have a new input on the passthrough + else { + //still waiting for initialization either from the passthrough stream or a texture used in the scene + if (!compositor->passthrough_txh->width || !compositor->passthrough_txh->stream->pck || (compositor->ms_until_next_frame<0) ) { + compositor->frame_draw_type = GF_SC_DRAW_NONE; + //prevent release of frame + if (compositor->passthrough_txh->needs_release) + compositor->passthrough_txh->needs_release = 2; + } + //done + else if ((compositor->passthrough_txh->stream_finished) && (compositor->ms_until_next_frame >= 0)) { + compositor->check_eos_state = 2; + } + } + } + + } + frame_drawn = (compositor->frame_draw_type==GF_SC_DRAW_FRAME) ? 1 : 0; /*if invalidated, draw*/ if (compositor->frame_draw_type) { + Bool emit_frame = GF_FALSE; Bool textures_released = 0; #ifndef GPAC_DISABLE_LOG @@ -2639,25 +2855,33 @@ void gf_sc_render_frame(GF_Compositor *compositor) /*video flush only*/ if (compositor->frame_draw_type==GF_SC_DRAW_FLUSH) { compositor->frame_draw_type = 0; - } /*full render*/ else { + Bool scene_drawn; compositor->frame_draw_type = 0; GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Redrawing scene - STB %d\n", compositor->scene_sampled_clock)); - gf_sc_draw_scene(compositor); + scene_drawn = gf_sc_draw_scene(compositor); + #ifndef GPAC_DISABLE_LOG traverse_time = gf_sys_clock() - traverse_time; #endif - if (compositor->video_listeners && compositor->skip_flush!=1) { - u32 k=0; - GF_VideoListener *l; - while ((l = gf_list_enum(compositor->video_listeners, &k))) { - l->on_video_frame(l->udta, gf_sc_ar_get_clock(compositor->audio_renderer) ); - } + if (compositor->ms_until_next_frame < 0) { + emit_frame = GF_FALSE; + compositor->last_error = GF_OK; } + else if (!scene_drawn) emit_frame = GF_FALSE; + else if (compositor->frame_draw_type) emit_frame = GF_FALSE; + else if (compositor->fonts_pending>0) emit_frame = GF_FALSE; + else emit_frame = GF_TRUE; + +#ifdef GPAC_CONFIG_ANDROID + if (!emit_frame && scene_drawn) { + compositor->frame_was_produced = GF_TRUE; + } +#endif } /*and flush*/ @@ -2665,19 +2889,115 @@ void gf_sc_render_frame(GF_Compositor *compositor) flush_time = gf_sys_clock(); #endif - if(compositor->user->init_flags & GF_TERM_INIT_HIDE) + if (compositor->init_flags & GF_TERM_INIT_HIDE) compositor->skip_flush = 1; + //new frame generated, emit packet + if (emit_frame) { + u64 pck_frame_ts=0; + GF_FilterPacket *pck; + frame_ts = 0; + if (compositor->passthrough_pck) { + pck = compositor->passthrough_pck; + compositor->passthrough_pck = NULL; + pck_frame_ts = gf_filter_pck_get_cts(pck); + } else { + //assign udta of frame interface event when using shared packet, as it is used to test when frame is released + compositor->frame_ifce.user_data = compositor; + if (compositor->video_out==&raw_vout) { + pck = gf_filter_pck_new_shared(compositor->vout, compositor->framebuffer, compositor->framebuffer_size, gf_sc_frame_ifce_done); + } else { + compositor->frame_ifce.get_plane = gf_sc_frame_ifce_get_plane; + compositor->frame_ifce.get_gl_texture = NULL; +#ifndef GPAC_DISABLE_3D + if (compositor->fbo_tx_id) { + compositor->frame_ifce.get_gl_texture = gf_sc_frame_ifce_get_gl_texture; + } +#endif + compositor->frame_ifce.flags = GF_FRAME_IFCE_BLOCKING; + pck = gf_filter_pck_new_frame_interface(compositor->vout, &compositor->frame_ifce, gf_sc_frame_ifce_done); + } + + if (compositor->passthrough_txh) { + gf_filter_pck_merge_properties(compositor->passthrough_txh->stream->pck, pck); + pck_frame_ts = gf_filter_pck_get_cts(compositor->passthrough_txh->stream->pck); + } else { + //no texture found/updated, use scene sampled clock + if (compositor->timescale) { + frame_ts = compositor->frame_number * compositor->fps.den * compositor->timescale; + frame_ts /= compositor->fps.num; + } else { + frame_ts = compositor->frame_number * compositor->fps.den; + } + gf_filter_pck_set_cts(pck, frame_ts); + } + } + if (pck_frame_ts) { + u64 ts = pck_frame_ts; + ts *= 1000; + ts /= compositor->passthrough_timescale; + frame_ts = (u32) ts; + } + gf_filter_pck_send(pck); + gf_sc_ar_update_video_clock(compositor->audio_renderer, frame_ts); + + if (!compositor->player) { + if (compositor->passthrough_txh) { + // update clock if not buffering + if (!gf_mo_is_buffering(compositor->passthrough_txh->stream)) + { + u64 next_frame; + next_frame = pck_frame_ts + gf_filter_pck_get_duration(pck); + next_frame *= 1000; + next_frame /= compositor->passthrough_timescale; + assert(next_frame>=compositor->scene_sampled_clock); + compositor->scene_sampled_clock = (u32) next_frame; + } + // if buffering, remember to update clock at next frame + else { + compositor->passthrough_check_buffer = GF_TRUE; + } + if (compositor->passthrough_txh->stream_finished) + compositor->check_eos_state = 2; + } else { + u64 res; + compositor->last_frame_ts = frame_ts; + res = (compositor->frame_number+1); + res *= compositor->fps.den; + res *= 1000; + res /= compositor->fps.num; + assert(res>=compositor->scene_sampled_clock); + compositor->scene_sampled_clock = (u32) res; + } + GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Send video frame TS %u (%u ms) - next frame scene clock %d ms - passthrough %p\n", frame_ts, (frame_ts*1000)/compositor->fps.num, compositor->scene_sampled_clock, compositor->passthrough_txh)); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Send video frame TS %u - AR clock %d\n", frame_ts, compositor->audio_renderer->current_time)); + if (compositor->bench_mode) { + compositor->force_bench_frame = 1; + } + compositor->frame_was_produced = GF_TRUE; + } + } else if (!compositor->player) { + frame_drawn = GF_FALSE; + //prevent release of frame + if (compositor->passthrough_txh && compositor->passthrough_txh->needs_release) + compositor->passthrough_txh->needs_release = 2; + } + + if (compositor->passthrough_pck) { + gf_filter_pck_discard(compositor->passthrough_pck); + compositor->passthrough_pck = NULL; + } + //if no overlays, release textures before flushing, otherwise we might loose time waiting for vsync if (!compositor->visual->has_overlays) { compositor_release_textures(compositor, frame_drawn); textures_released = 1; } - if (compositor->skip_flush!=1) { - gf_sc_flush_video(compositor); - } else { - compositor->skip_flush = 0; + //no flush pending + if (!compositor->flush_pending && !compositor->skip_flush) { + gf_sc_flush_video(compositor, GF_TRUE); } #ifndef GPAC_DISABLE_LOG @@ -2693,7 +3013,7 @@ void gf_sc_render_frame(GF_Compositor *compositor) compositor_release_textures(compositor, frame_drawn); } - if (compositor->stress_mode) { + if (compositor->stress) { gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); gf_sc_reset_graphics(compositor); } @@ -2712,6 +3032,25 @@ void gf_sc_render_frame(GF_Compositor *compositor) compositor->traverse_and_direct_draw_time = 0; compositor->indirect_draw_time = 0; #endif + + if (!compositor->player) { + //if systems frames are pending (not executed because too early), increase clock + if ((compositor->sys_frames_pending && (compositor->ms_until_next_frame>=0) ) + //if timed nodes or validator mode (which acts as a timed node firing events), increase scene clock + //in vfr mode + || (compositor->vfr && (has_timed_nodes || compositor->validator_mode) ) + ) { + u64 res; + compositor->sys_frames_pending = GF_FALSE; + compositor->frame_number++; + res = compositor->frame_number; + res *= compositor->fps.den; + res *= 1000; + res /= compositor->fps.num; + assert(res >= compositor->scene_sampled_clock); + compositor->scene_sampled_clock = (u32) res; + } + } } compositor->reset_graphics = 0; @@ -2748,11 +3087,12 @@ void gf_sc_render_frame(GF_Compositor *compositor) //in bench mode we always increase the clock of the fixed target simulation rate - this needs refinement if video is used ... compositor->scene_sampled_clock += frame_duration; } - compositor->video_frame_pending=0; gf_sc_lock(compositor, 0); if (frame_drawn) compositor->step_mode = GF_FALSE; + /*old arch code kept for reference*/ +#if 0 /*let the owner decide*/ if (compositor->no_regulation) return; @@ -2779,13 +3119,13 @@ void gf_sc_render_frame(GF_Compositor *compositor) u64 start = gf_sys_clock_high_res(); u64 diff=0; u64 wait_for = 1000 * (u64) compositor->ms_until_next_frame; - while (! compositor->msg_type && ! compositor->video_frame_pending) { + while (! compositor->msg_type) { gf_sleep(1); diff = gf_sys_clock_high_res() - start; if (diff >= wait_for) break; } - GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Compositor slept %d ms until next frame (msg type %d - frame pending %d)\n", diff/1000, compositor->msg_type, compositor->video_frame_pending)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Compositor slept %d ms until next frame (msg type %d)\n", diff/1000, compositor->msg_type)); } return; } @@ -2799,6 +3139,8 @@ void gf_sc_render_frame(GF_Compositor *compositor) end_time %= frame_duration; gf_sleep(frame_duration - end_time); GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Compositor slept for %d ms\n", frame_duration - end_time)); +#endif + } Bool gf_sc_visual_is_registered(GF_Compositor *compositor, GF_VisualManager *visual) @@ -2988,9 +3330,9 @@ static Bool gf_sc_handle_event_intern(GF_Compositor *compositor, GF_Event *event { Bool ret; - if (compositor->term && (compositor->interaction_level & GF_INTERACT_INPUT_SENSOR) && (event->type<=GF_EVENT_MOUSEWHEEL)) { + if ( (compositor->interaction_level & GF_INTERACT_INPUT_SENSOR) && (event->type<=GF_EVENT_MOUSEWHEEL)) { GF_Event evt = *event; - gf_term_mouse_input(compositor->term, &evt.mouse); + gf_sc_input_sensor_mouse_input(compositor, &evt.mouse); } /* if (!compositor->interaction_level || (compositor->interaction_level==GF_INTERACT_INPUT_SENSOR) ) { @@ -3023,7 +3365,7 @@ void gf_sc_traverse_subscene(GF_Compositor *compositor, GF_Node *inline_parent, static Bool gf_sc_on_event_ex(GF_Compositor *compositor , GF_Event *event, Bool from_user) { /*not assigned yet*/ - if (!compositor || !compositor->visual) return GF_FALSE; + if (!compositor || !compositor->visual || compositor->discard_input_events) return GF_FALSE; /*we're reconfiguring the video output, cancel all messages except GL reconfig (context lost)*/ if (compositor->msg_type & GF_SR_IN_RECONFIG) { if (event->type==GF_EVENT_VIDEO_SETUP) { @@ -3037,15 +3379,22 @@ static Bool gf_sc_on_event_ex(GF_Compositor *compositor , GF_Event *event, Bool } switch (event->type) { case GF_EVENT_SHOWHIDE: + if (!from_user) { + /*switch fullscreen off!!!*/ + compositor->is_hidden = event->show.show_type ? GF_FALSE : GF_TRUE; + break; + } case GF_EVENT_SET_CAPTION: case GF_EVENT_MOVE: + if (!from_user) { + if (compositor->last_had_overlays) { + gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); + } + break; + } compositor->video_out->ProcessEvent(compositor->video_out, event); break; - case GF_EVENT_MOVE_NOTIF: - if (compositor->last_had_overlays) { - gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); - } break; case GF_EVENT_REFRESH: /*when refreshing a window with overlays we redraw the scene */ @@ -3066,11 +3415,11 @@ static Bool gf_sc_on_event_ex(GF_Compositor *compositor , GF_Event *event, Bool break; case GF_EVENT_SIZE: /*user consummed the resize event, do nothing*/ - if ( gf_term_send_event(compositor->term, event) ) + if ( gf_sc_send_event(compositor, event) ) return GF_TRUE; /*not consummed and compositor "owns" the output window (created by the output module), resize*/ - if (!compositor->user->os_window_handler) { + if (!compositor->os_wnd) { /*EXTRA CARE HERE: the caller (video output) is likely a different thread than the compositor one, and the compositor may be locked on the video output (flush or whatever)!! */ @@ -3082,7 +3431,7 @@ static Bool gf_sc_on_event_ex(GF_Compositor *compositor , GF_Event *event, Bool ) { //OSX bug with SDL when requesting 4k window we get max screen height but 4k width ... -#if defined(GPAC_CONFIG_DARWIN) && !defined(GPAC_IPHONE) +#if defined(GPAC_CONFIG_DARWIN) && !defined(GPAC_CONFIG_IOS) if (compositor->display_width==event->size.width) { if (compositor->display_height > 2*event->size.height) { event->size.width = compositor->display_width * event->size.height / compositor->display_height; @@ -3134,22 +3483,18 @@ static Bool gf_sc_on_event_ex(GF_Compositor *compositor , GF_Event *event, Bool ret = GF_FALSE; event->key.flags |= compositor->key_states; /*key sensor*/ - if (compositor->term && (compositor->interaction_level & GF_INTERACT_INPUT_SENSOR) ) { - ret = gf_term_keyboard_input(compositor->term, event->key.key_code, event->key.hw_code, (event->type==GF_EVENT_KEYUP) ? GF_TRUE : GF_FALSE); + if ((compositor->interaction_level & GF_INTERACT_INPUT_SENSOR) ) { + ret = gf_sc_input_sensor_keyboard_input(compositor, event->key.key_code, event->key.hw_code, (event->type==GF_EVENT_KEYUP) ? GF_TRUE : GF_FALSE); } ret += gf_sc_handle_event_intern(compositor, event, from_user); return ret; } case GF_EVENT_TEXTINPUT: - if (compositor->term && (compositor->interaction_level & GF_INTERACT_INPUT_SENSOR) ) - gf_term_string_input(compositor->term , event->character.unicode_char); + if (compositor->interaction_level & GF_INTERACT_INPUT_SENSOR) + gf_sc_input_sensor_string_input(compositor , event->character.unicode_char); return gf_sc_handle_event_intern(compositor, event, from_user); - /*switch fullscreen off!!!*/ - case GF_EVENT_SHOWHIDE_NOTIF: - compositor->is_hidden = event->show.show_type ? GF_FALSE : GF_TRUE; - break; case GF_EVENT_MOUSEMOVE: event->mouse.button = 0; @@ -3158,31 +3503,35 @@ static Bool gf_sc_on_event_ex(GF_Compositor *compositor , GF_Event *event, Bool case GF_EVENT_MOUSEWHEEL: event->mouse.key_states = compositor->key_states; case GF_EVENT_SENSOR_ORIENTATION: + case GF_EVENT_MULTITOUCH: return gf_sc_handle_event_intern(compositor, event, from_user); case GF_EVENT_PASTE_TEXT: - gf_sc_paste_text(compositor, event->message.message); + gf_sc_paste_text(compositor, event->clipboard.text); break; case GF_EVENT_COPY_TEXT: if (gf_sc_has_text_selection(compositor)) { - event->message.message = gf_sc_get_selected_text(compositor); + const char *str = gf_sc_get_selected_text(compositor); + event->clipboard.text = str ? gf_strdup(event->clipboard.text) : NULL; } else { - event->message.message = NULL; + event->clipboard.text = NULL; } break; /*when we process events we don't forward them to the user*/ default: - return gf_term_send_event(compositor->term, event); + return gf_sc_send_event(compositor, event); } /*if we get here, event has been consumed*/ return GF_TRUE; } -static Bool gf_sc_on_event(void *cbck, GF_Event *event) +GF_EXPORT +Bool gf_sc_on_event(void *cbck, GF_Event *event) { return gf_sc_on_event_ex((GF_Compositor *)cbck, event, GF_FALSE); } +GF_EXPORT Bool gf_sc_user_event(GF_Compositor *compositor, GF_Event *event) { switch (event->type) { @@ -3191,10 +3540,6 @@ Bool gf_sc_user_event(GF_Compositor *compositor, GF_Event *event) case GF_EVENT_SET_CAPTION: compositor->video_out->ProcessEvent(compositor->video_out, event); return GF_FALSE; - -// case GF_EVENT_SET_CAPTION: -// gf_sc_queue_event(compositor, event); -// return GF_FALSE; default: return gf_sc_on_event_ex(compositor, event, GF_TRUE); } @@ -3209,14 +3554,7 @@ void gf_sc_register_extra_graph(GF_Compositor *compositor, GF_SceneGraph *extra_ gf_sc_lock(compositor, GF_FALSE); } - -u32 gf_sc_get_audio_delay(GF_Compositor *compositor) -{ - return compositor->audio_renderer ? compositor->audio_renderer->audio_delay : 0; -} - - -Bool gf_sc_script_action(GF_Compositor *compositor, u32 type, GF_Node *n, GF_JSAPIParam *param) +Bool gf_sc_script_action(GF_Compositor *compositor, GF_JSAPIActionType type, GF_Node *n, GF_JSAPIParam *param) { switch (type) { case GF_JSAPI_OP_GET_SCALE: @@ -3284,11 +3622,13 @@ Bool gf_sc_script_action(GF_Compositor *compositor, u32 type, GF_Node *n, GF_JSA if (type==GF_JSAPI_OP_GET_LOCAL_BBOX) { #ifndef GPAC_DISABLE_SVG GF_SAFEALLOC(tr_state.svg_props, SVGPropertiesPointers); - gf_svg_properties_init_pointers(tr_state.svg_props); - tr_state.abort_bounds_traverse = GF_TRUE; - gf_node_traverse(n, &tr_state); - gf_svg_properties_reset_pointers(tr_state.svg_props); - gf_free(tr_state.svg_props); + if (tr_state.svg_props) { + gf_svg_properties_init_pointers(tr_state.svg_props); + tr_state.abort_bounds_traverse = GF_TRUE; + gf_node_traverse(n, &tr_state); + gf_svg_properties_reset_pointers(tr_state.svg_props); + gf_free(tr_state.svg_props); + } #endif } else { gf_node_traverse(gf_sg_get_root_node(compositor->scene), &tr_state); @@ -3379,6 +3719,8 @@ Bool gf_sc_script_action(GF_Compositor *compositor, u32 type, GF_Node *n, GF_JSA case GF_JSAPI_OP_GET_DPI_Y: param->opt = compositor->video_out->dpi_y; return GF_TRUE; + default: + return GF_FALSE; } return GF_FALSE; } @@ -3419,6 +3761,7 @@ Bool gf_sc_has_text_selection(GF_Compositor *compositor) return (compositor->store_text_state==GF_SC_TSEL_FROZEN) ? GF_TRUE : GF_FALSE; } +GF_EXPORT const char *gf_sc_get_selected_text(GF_Compositor *compositor) { const u16 *srcp; @@ -3438,7 +3781,7 @@ const char *gf_sc_get_selected_text(GF_Compositor *compositor) compositor->traverse_state->traversing_mode = 0; if (compositor->sel_buffer) compositor->sel_buffer[compositor->sel_buffer_len]=0; srcp = compositor->sel_buffer; - + if (compositor->selected_text) gf_free(compositor->selected_text); compositor->selected_text = gf_malloc(sizeof(char)*2*compositor->sel_buffer_len); len = gf_utf8_wcstombs((char *) compositor->selected_text, 2*compositor->sel_buffer_len, &srcp); @@ -3466,51 +3809,7 @@ void gf_sc_check_focus_upon_destroy(GF_Node *n) if (compositor->hit_text==n) compositor->hit_text = NULL; } -GF_EXPORT -GF_Err gf_sc_add_video_listener(GF_Compositor *sc, GF_VideoListener *vl) -{ - if (!sc|| !vl || !vl->on_video_frame || !vl->on_video_reconfig) return GF_BAD_PARAM; - - gf_sc_lock(sc, GF_TRUE); - if (!sc->video_listeners) sc->video_listeners = gf_list_new(); - gf_list_add(sc->video_listeners, vl); - gf_sc_lock(sc, GF_FALSE); - return GF_OK; -} - -GF_EXPORT -GF_Err gf_sc_remove_video_listener(GF_Compositor *sc, GF_VideoListener *vl) -{ - if (!sc|| !vl) return GF_BAD_PARAM; - - gf_sc_lock(sc, GF_TRUE); - gf_list_del_item(sc->video_listeners, vl); - if (!gf_list_count(sc->video_listeners)) { - gf_list_del(sc->video_listeners); - sc->video_listeners = NULL; - } - gf_sc_lock(sc, GF_FALSE); - return GF_OK; -} - -Bool gf_sc_use_raw_texture(GF_Compositor *compositor) -{ - if (!compositor) return 0; -// if (!compositor->visual->type_3d && !compositor->force_opengl_2d) return 0; - return compositor->texture_from_decoder_memory; -} - -void gf_sc_get_av_caps(GF_Compositor *compositor, u32 *width, u32 *height, u32 *display_bit_depth, u32 *audio_bpp, u32 *channels, u32 *sample_rate) -{ - if (width) *width = compositor->video_out->max_screen_width; - if (height) *height = compositor->video_out->max_screen_height; - if (display_bit_depth) *display_bit_depth = compositor->video_out->max_screen_bpp ? compositor->video_out->max_screen_bpp : 8; - //to do - if (audio_bpp) *audio_bpp = 8; - if (channels) *channels = 0; - if (sample_rate) *sample_rate = 48000; -} - +#if 0 //unused void gf_sc_set_system_pending_frame(GF_Compositor *compositor, Bool frame_pending) { if (frame_pending) { @@ -3522,11 +3821,6 @@ void gf_sc_set_system_pending_frame(GF_Compositor *compositor, Bool frame_pendin } } -void gf_sc_set_video_pending_frame(GF_Compositor *compositor) -{ - compositor->video_frame_pending = GF_TRUE; -} - void gf_sc_queue_event(GF_Compositor *compositor, GF_Event *evt) { u32 i, count; @@ -3551,6 +3845,8 @@ void gf_sc_queue_event(GF_Compositor *compositor, GF_Event *evt) } gf_mx_v(compositor->evq_mx); } +#endif + void gf_sc_queue_dom_event(GF_Compositor *compositor, GF_Node *node, GF_DOM_Event *evt) { @@ -3588,7 +3884,10 @@ void gf_sc_queue_dom_event_on_target(GF_Compositor *compositor, GF_DOM_Event *ev for (i=0; ievent_queue, i); if ((qev->target==target) && (qev->dom_evt.type==evt->type) && (qev->sg==sg) ) { - qev->dom_evt = *evt; + //do not override any pending dowload progress event by new buffer state events + if ((evt->type!=GF_EVENT_MEDIA_PROGRESS) || !qev->dom_evt.media_event.loaded_size) { + qev->dom_evt = *evt; + } gf_mx_v(compositor->evq_mx); return; } @@ -3680,12 +3979,85 @@ Bool gf_sc_navigation_supported(GF_Compositor *compositor, u32 type) return GF_TRUE; } -Bool gf_sc_use_3d(GF_Compositor *compositor) + +u32 gf_sc_check_end_of_scene(GF_Compositor *compositor, Bool skip_interactions) +{ + if (!compositor->root_scene || !compositor->root_scene->root_od || !compositor->root_scene->root_od->scene_ns) return 1; + + if (!skip_interactions) { + /*if input sensors consider the scene runs forever*/ + if (gf_list_count(compositor->input_streams)) return 0; + if (gf_list_count(compositor->x3d_sensors)) return 0; + } + + /*check no clocks are still running*/ + if (!gf_scene_check_clocks(compositor->root_scene->root_od->scene_ns, compositor->root_scene, 0)) return 0; + if (compositor->root_scene->is_dynamic_scene) return 1; + + /*ask compositor if there are sensors*/ + return gf_sc_get_option(compositor, skip_interactions ? GF_OPT_IS_OVER : GF_OPT_IS_FINISHED); +} + +void gf_sc_queue_node_traverse(GF_Compositor *compositor, GF_Node *node) { -#ifndef GPAC_DISABLE_3D - return (compositor->visual->type_3d || compositor->hybrid_opengl) ? 1 : 0; + gf_sc_lock(compositor, GF_TRUE); + if (!compositor->nodes_pending) compositor->nodes_pending = gf_list_new(); + gf_list_add(compositor->nodes_pending, node); + gf_sc_lock(compositor, GF_FALSE); +} +void gf_sc_unqueue_node_traverse(GF_Compositor *compositor, GF_Node *node) +{ + gf_sc_lock(compositor, GF_TRUE); + if (compositor->nodes_pending) { + gf_list_del_item(compositor->nodes_pending, node); + if (!gf_list_count(compositor->nodes_pending)) { + gf_list_del(compositor->nodes_pending); + compositor->nodes_pending = NULL; + } + } + gf_sc_lock(compositor, GF_FALSE); +} + +GF_EXPORT +GF_DownloadManager *gf_sc_get_downloader(GF_Compositor *compositor) +{ + return gf_filter_get_download_manager(compositor->filter); +} + +void gf_sc_sys_frame_pending(GF_Compositor *compositor, Double ts_offset, u32 obj_time, GF_Filter *from_filter) +{ + if (!compositor->player) { + compositor->sys_frames_pending = GF_TRUE; + if (from_filter) + gf_filter_ask_rt_reschedule(from_filter, 0); + } else { + u32 wait_ms = (u32) (ts_offset * 1000 - obj_time); + + if (!compositor->ms_until_next_frame || ((s32) wait_ms < compositor->ms_until_next_frame)) { + compositor->ms_until_next_frame = (s32) wait_ms; + } + if (from_filter) { + gf_filter_ask_rt_reschedule(from_filter, wait_ms*500); + } + } +} + +Bool gf_sc_check_gl_support(GF_Compositor *compositor) +{ +#ifdef GPAC_DISABLE_3D + return GF_FALSE; #else - return 0; + if (!compositor->player && !compositor->is_opengl) { + if (compositor->drv==GF_SC_DRV_OFF) { + return GF_FALSE; + } + compositor->needs_offscreen_gl = GF_TRUE; + compositor->autoconfig_opengl = 1; + compositor->recompute_ar = 1; + return GF_TRUE; + } + return GF_TRUE; #endif + } diff --git a/src/compositor/compositor_2d.c b/src/compositor/compositor_2d.c index 060317a..9e44d07 100644 --- a/src/compositor/compositor_2d.c +++ b/src/compositor/compositor_2d.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / Scene Compositor sub-project @@ -28,113 +28,10 @@ #include "visual_manager.h" #include "nodes_stacks.h" #include -#include #include "texturing.h" #include "gl_inc.h" -#ifdef OPENGL_RASTER - -static void c2d_gl_fill_no_alpha(void *cbk, u32 x, u32 y, u32 run_h_len, GF_Color color) -{ -#if defined(GPAC_USE_GLES1X) - GLfloat line[4]; - - line[0] = FIX2FLT(x); - line[1] = FIX2FLT(y); - line[2] = FIX2FLT(x+run_h_len); - line[3] = line[1]; - - glColor4ub(GF_COL_R(color), GF_COL_G(color), GF_COL_B(color), 0xFF); - glVertexPointer(2, GL_FLOAT, 0, line); - glEnableClientState(GL_VERTEX_ARRAY); - glDrawArrays(GL_LINES, 0, 2); - glDisableClientState(GL_VERTEX_ARRAY); - -#else - glBegin(GL_LINES); - glColor3ub(GF_COL_R(color), GF_COL_G(color), GF_COL_B(color)); -// glColor3f(GF_COL_R(color)/255, GF_COL_G(color)/255, GF_COL_B(color)/255); - glVertex2i(x,y); - glVertex2i(x+run_h_len,y); - glEnd(); -#endif -} - - -static void c2d_gl_fill_alpha(void *cbk, u32 x, u32 y, u32 run_h_len, GF_Color color, u8 alpha) -{ -#if defined(GPAC_USE_GLES1X) - GLfloat line[4]; - - line[0] = FIX2FLT(x); - line[1] = FIX2FLT(y); - line[2] = FIX2FLT(x+run_h_len); - line[3] = line[1]; - - glEnable(GL_BLEND); - glColor4ub(GF_COL_R(color), GF_COL_G(color), GF_COL_B(color), (u8) alpha); - - glVertexPointer(2, GL_FLOAT, 0, line); - glEnableClientState(GL_VERTEX_ARRAY); - glDrawArrays(GL_LINES, 0, 2); - glDisableClientState(GL_VERTEX_ARRAY); - - glDisable(GL_BLEND); -#else - glEnable(GL_BLEND); - glColor4ub(GF_COL_R(color), GF_COL_G(color), GF_COL_B(color), (u8) alpha); - glBegin(GL_LINES); - glVertex2i(x,y); - glVertex2i(x+run_h_len,y); - glEnd(); - glDisable(GL_BLEND); -#endif -} - -static void c2d_gl_fill_rect(void *cbk, u32 x, u32 y, u32 width, u32 height, GF_Color color) -{ - return; -#if defined(GPAC_USE_GLES1X) - GLfloat line[8]; - - line[0] = FIX2FLT(x); - line[1] = FIX2FLT(y); - line[2] = FIX2FLT(x+width); - line[3] = FIX2FLT(y); - line[4] = FIX2FLT(x+width); - line[5] = FIX2FLT(y+height); - line[6] = FIX2FLT(x); - line[7] = FIX2FLT(y+height); - - glEnable(GL_BLEND); - glColor4ub(GF_COL_R(color), GF_COL_G(color), GF_COL_B(color), GF_COL_A(color)); - - glVertexPointer(4, GL_FLOAT, 0, line); - glEnableClientState(GL_VERTEX_ARRAY); - glDrawArrays(GL_TRIANGLE_FAN, 0, 2); - glDisableClientState(GL_VERTEX_ARRAY); - - glDisable(GL_BLEND); -#else - glEnable(GL_BLEND); - glColor4ub(GF_COL_R(color), GF_COL_G(color), GF_COL_B(color), GF_COL_A(color)); - glBegin(GL_TRIANGLES); - glVertex2i(x,y); - glVertex2i(x+width,y); - glVertex2i(x+width,y+height); - glEnd(); - glBegin(GL_TRIANGLES); - glVertex2i(x,y); - glVertex2i(x+width,y+height); - glVertex2i(x,y+height); - glEnd(); - glDisable(GL_BLEND); -#endif -} - -#endif - #ifndef GPAC_DISABLE_3D void compositor_2d_hybgl_clear_surface(GF_VisualManager *visual, GF_IRect *rc, u32 BackColor, u32 is_offscreen_clear) @@ -144,12 +41,12 @@ void compositor_2d_hybgl_clear_surface(GF_VisualManager *visual, GF_IRect *rc, u if (!visual->is_attached) return; if (!BackColor && !visual->offscreen && !is_offscreen_clear) { - if (!visual->compositor->user || !(visual->compositor->user->init_flags & GF_TERM_WINDOW_TRANSPARENT)) { + if ( !(visual->compositor->init_flags & GF_TERM_WINDOW_TRANSPARENT)) { BackColor = visual->compositor->back_color & 0x00FFFFFF; } } if (is_offscreen_clear) { - visual->compositor->rasterizer->surface_clear(visual->raster_surface, rc, BackColor); + gf_evg_surface_clear(visual->raster_surface, rc, BackColor); //if we clear the canvas with non-0 alpha, remember the area cleared in case we have to erase it later (overlapping bitmap) //if we clear dirty area of the canvas, remember the area to force gl flush if (GF_COL_A(BackColor) || (is_offscreen_clear==2)) @@ -198,7 +95,7 @@ void compositor_2d_hybgl_flush_video(GF_Compositor *compositor, GF_IRect *area) visual_3d_enable_antialias(compositor->visual, GF_FALSE); gf_sc_texture_set_blend_mode(compositor->hybgl_txh, TX_MODULATE); //visual_3d_set_material_2d_argb(compositor->visual, 0xFFFFFFFF); - compositor->visual->has_material_2d = 0; + compositor->visual->has_material_2d = GF_FALSE; a_tr_state.mesh_num_textures = gf_sc_texture_enable(compositor->hybgl_txh, NULL); if (a_tr_state.mesh_num_textures ) { if (area) { @@ -260,48 +157,6 @@ exit: compositor->visual->nb_objects_on_canvas_since_last_ogl_flush = 0; } -Bool c2d_gl_draw_bitmap(GF_VisualManager *visual, GF_TraverseState *tr_state, DrawableContext *ctx) -{ - u8 alpha = GF_COL_A(ctx->aspect.fill_color); - - if (ctx->transform.m[1] || ctx->transform.m[3]) return GF_FALSE; - - visual_3d_set_state(visual, V3D_STATE_LIGHT, GF_FALSE); - visual_3d_enable_antialias(visual, GF_FALSE); - if (alpha && (alpha != 0xFF)) { - visual_3d_set_material_2d_argb(visual, ctx->aspect.fill_color); - gf_sc_texture_set_blend_mode(ctx->aspect.fill_texture, TX_MODULATE); - } else if (gf_sc_texture_is_transparent(ctx->aspect.fill_texture)) { - gf_sc_texture_set_blend_mode(ctx->aspect.fill_texture, TX_REPLACE); - } else { - visual_3d_set_state(visual, V3D_STATE_BLEND, GF_FALSE); - } -#ifndef GPAC_DISABLE_VRML - /*ignore texture transform for bitmap*/ - tr_state->mesh_num_textures = gf_sc_texture_enable(ctx->aspect.fill_texture, tr_state->appear ? ((M_Appearance *)tr_state->appear)->textureTransform : NULL); - if (tr_state->mesh_num_textures) { - SFVec2f size, orig; - GF_Mesh *mesh; - size.x = ctx->bi->unclip.width; - size.y = ctx->bi->unclip.height; -# ifdef OPENGL_RASTER - if (visual->compositor->opengl_raster) { - orig.x = ctx->bi->unclip.x + INT2FIX(visual->compositor->vp_width)/2; - orig.y = INT2FIX(visual->compositor->vp_height)/2 - ctx->bi->unclip.y + ctx->bi->unclip.height; - } -#endif - mesh = new_mesh(); - mesh_new_rectangle(mesh, size, &orig, GF_TRUE); - visual_3d_mesh_paint(tr_state, mesh); - mesh_free(mesh); - gf_sc_texture_disable(ctx->aspect.fill_texture); - tr_state->mesh_num_textures = 0; - return GF_TRUE; - } -#endif - return GF_FALSE; -} - Bool compositor_2d_hybgl_draw_bitmap(GF_VisualManager *visual, GF_TraverseState *tr_state, DrawableContext *ctx) { GF_Node *txtrans = NULL; @@ -377,38 +232,10 @@ static GF_Err compositor_2d_setup_opengl(GF_VisualManager *visual) camera_update(&visual->camera, NULL, visual->compositor->hybrid_opengl ? GF_TRUE : visual->center_coords); visual_3d_projection_matrix_modified(visual); - -#ifdef OPENGL_RASTER - if (compositor->opengl_raster) { - gf_mx_add_scale(&visual->camera.modelview, FIX_ONE, -FIX_ONE, FIX_ONE); - gf_mx_add_translation(&visual->camera.modelview, -visual->camera.width/2, -visual->camera.height/2, 0); - } -#endif return GF_OK; } #endif -#ifdef OPENGL_RASTER -static GF_Err c2d_video_access_opengl_raster(GF_VisualManager *visual) -{ - GF_Err e; - GF_Compositor *compositor = visual->compositor; - GF_RasterCallback callbacks; - - callbacks.cbk = visual; - callbacks.fill_run_alpha = c2d_gl_fill_alpha; - callbacks.fill_run_no_alpha = c2d_gl_fill_no_alpha; - callbacks.fill_rect = c2d_gl_fill_rect; - - visual->DrawBitmap = c2d_gl_draw_bitmap; - - e = compositor->rasterizer->surface_attach_to_callbacks(visual->raster_surface, &callbacks, compositor->vp_width, compositor->vp_height); - if (e) return e; - - return compositor_2d_setup_opengl(visual); -} -#endif - #ifndef GPAC_DISABLE_3D static GF_Err c2d_video_access_hybrid_opengl(GF_VisualManager *visual) @@ -456,7 +283,7 @@ static GF_Err c2d_video_access_hybrid_opengl(GF_VisualManager *visual) if (visual->compositor->traverse_state->immediate_draw) memset(compositor->hybgl_txh->data, 0, 4*compositor->hybgl_txh->width*compositor->hybgl_txh->height); - e = compositor->rasterizer->surface_attach_to_buffer(visual->raster_surface, compositor->hybgl_txh->data, + e = gf_evg_surface_attach_to_buffer(visual->raster_surface, compositor->hybgl_txh->data, compositor->hybgl_txh->width, compositor->hybgl_txh->height, 0, @@ -478,39 +305,11 @@ static GF_Err c2d_get_video_access_normal(GF_VisualManager *visual) compositor->hw_locked = GF_FALSE; - /*try from video memory handle (WIN32) if supported*/ - if ((compositor->video_out->hw_caps & GF_VIDEO_HW_HAS_HWND_HDC) - && compositor->rasterizer->surface_attach_to_device - && compositor->video_out->LockOSContext - ) { - compositor->hw_context = compositor->video_out->LockOSContext(compositor->video_out, GF_TRUE); - if (compositor->hw_context) { - e = compositor->rasterizer->surface_attach_to_device(visual->raster_surface, compositor->hw_context, compositor->vp_width, compositor->vp_height); - if (!e) { - visual->is_attached = GF_TRUE; - GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Video surface handle attached to raster\n")); - return GF_OK; - } - compositor->video_out->LockOSContext(compositor->video_out, GF_FALSE); - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor2D] Cannot attach video surface handle to raster: %s\n", gf_error_to_string(e) )); - } - } - - if (compositor->video_out->hw_caps & GF_VIDEO_HW_HAS_LINE_BLIT) { - e = compositor->rasterizer->surface_attach_to_callbacks(visual->raster_surface, &compositor->raster_callbacks, compositor->vp_width, compositor->vp_height); - if (!e) { - visual->is_attached = GF_TRUE; - GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Video surface callbacks attached to raster\n")); - return GF_OK; - } - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor2D] Failed to attach video surface callbacks to raster\n")); - } - e = compositor->video_out->LockBackBuffer(compositor->video_out, &compositor->hw_surface, GF_TRUE); if (e==GF_OK) { compositor->hw_locked = GF_TRUE; - e = compositor->rasterizer->surface_attach_to_buffer(visual->raster_surface, compositor->hw_surface.video_buffer, + e = gf_evg_surface_attach_to_buffer(visual->raster_surface, compositor->hw_surface.video_buffer, compositor->hw_surface.width, compositor->hw_surface.height, compositor->hw_surface.pitch_x, @@ -521,7 +320,8 @@ static GF_Err c2d_get_video_access_normal(GF_VisualManager *visual) GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Video surface memory attached to raster - w=%d h=%d pitch_x=%d pitch_y=%d\n", compositor->hw_surface.width, compositor->hw_surface.height, compositor->hw_surface.pitch_x, compositor->hw_surface.pitch_y)); return GF_OK; } - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor2D] Cannot attach video surface memory to raster: %s\n", gf_error_to_string(e) )); + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor2D] Cannot attach video surface memory to raster for pixel format %s: %s\n", gf_pixel_fmt_name(compositor->hw_surface.pixel_format), gf_error_to_string(e) )); + compositor->last_error = e; compositor->video_out->LockBackBuffer(compositor->video_out, &compositor->hw_surface, GF_FALSE); } compositor->hw_locked = GF_FALSE; @@ -535,12 +335,6 @@ GF_Err compositor_2d_get_video_access(GF_VisualManager *visual) { if (!visual->raster_surface) return GF_BAD_PARAM; -#ifdef OPENGL_RASTER - if (visual->compositor->opengl_raster && visual->compositor->rasterizer->surface_attach_to_callbacks) { - return c2d_video_access_opengl_raster(visual); - } -#endif - #ifndef GPAC_DISABLE_3D if (visual->compositor->hybrid_opengl) { return c2d_video_access_hybrid_opengl(visual); @@ -569,7 +363,7 @@ void compositor_2d_clear_surface(GF_VisualManager *visual, GF_IRect *rc, u32 Bac GF_Window src_wnd, dst_wnd; if (!BackColor && !visual->offscreen) { - if (!visual->compositor->user || !(visual->compositor->user->init_flags & GF_TERM_WINDOW_TRANSPARENT)) { + if ( !(visual->compositor->init_flags & GF_TERM_WINDOW_TRANSPARENT)) { BackColor = visual->compositor->back_color; } } @@ -583,7 +377,7 @@ void compositor_2d_clear_surface(GF_VisualManager *visual, GF_IRect *rc, u32 Bac video_src.width = 2; video_src.pitch_x = 0; video_src.pitch_y = 6; - video_src.pixel_format = GF_PIXEL_RGB_24; + video_src.pixel_format = GF_PIXEL_RGB; video_src.video_buffer = data; src_wnd.x = src_wnd.y = 0; @@ -617,7 +411,6 @@ void compositor_2d_release_video_access(GF_VisualManager *visual) { GF_Compositor *compositor = visual->compositor; if (visual->is_attached) { - compositor->rasterizer->surface_detach(visual->raster_surface); visual->is_attached = GF_FALSE; } @@ -639,7 +432,7 @@ void compositor_2d_release_video_access(GF_VisualManager *visual) static void store_blit_times(GF_TextureHandler *txh, u32 push_time) { -#ifndef GPAC_DISABLE_LOGS +#ifndef GPAC_DISABLE_LOG u32 ck; #endif @@ -647,7 +440,7 @@ static void store_blit_times(GF_TextureHandler *txh, u32 push_time) txh->nb_frames ++; txh->upload_time += push_time; -#ifndef GPAC_DISABLE_LOGS +#ifndef GPAC_DISABLE_LOG gf_mo_get_object_time(txh->stream, &ck); if (ck>txh->last_frame_time) { GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Bliting frame (CTS %d) %d ms too late\n", txh->last_frame_time, ck - txh->last_frame_time )); @@ -673,7 +466,6 @@ Bool compositor_texture_rectangles(GF_VisualManager *visual, GF_TextureHandler * if (final.width<=0 || final.height <=0) return GF_FALSE; if (txh->width==0 || txh->height==0) return GF_FALSE; - w_scale = final.width / txh->width; h_scale = final.height / txh->height; @@ -820,6 +612,9 @@ static Bool compositor_2d_draw_bitmap_ex(GF_VisualManager *visual, GF_TextureHan if (!compositor_texture_rectangles(visual, txh, clip, unclip, &src_wnd, &dst_wnd, &use_blit, &has_scale)) return GF_TRUE; + //blitter is disabled + if (txh->flags & GF_SR_TEXTURE_DISABLE_BLIT) return GF_FALSE; + /*can we use hardware blitter ?*/ hw_caps = visual->compositor->video_out->hw_caps; overlay_type = 0; @@ -839,8 +634,8 @@ static Bool compositor_2d_draw_bitmap_ex(GF_VisualManager *visual, GF_TextureHan // return 0; switch (txh->pixelformat) { - case GF_PIXEL_RGB_24: - case GF_PIXEL_BGR_24: + case GF_PIXEL_RGB: + case GF_PIXEL_BGR: case GF_PIXEL_RGBS: case GF_PIXEL_RGBD: // case GF_PIXEL_RGB_555: @@ -859,18 +654,17 @@ static Bool compositor_2d_draw_bitmap_ex(GF_VisualManager *visual, GF_TextureHan if (hw_caps & GF_VIDEO_HW_HAS_RGBA) use_soft_stretch = GF_FALSE; break; - case GF_PIXEL_YV12: - case GF_PIXEL_IYUV: - case GF_PIXEL_I420: + case GF_PIXEL_YUV: case GF_PIXEL_YVYU: - case GF_PIXEL_YUY2: + case GF_PIXEL_YUYV: case GF_PIXEL_YUVD: case GF_PIXEL_YUV422: case GF_PIXEL_YUV444: case GF_PIXEL_YUV444_10: case GF_PIXEL_YUV422_10: - case GF_PIXEL_YV12_10: + case GF_PIXEL_YUV_10: case GF_PIXEL_NV12: + case GF_PIXEL_NV12_10: case GF_PIXEL_NV21: if (hw_caps & GF_VIDEO_HW_HAS_YUV) use_soft_stretch = GF_FALSE; else if (hw_caps & GF_VIDEO_HW_HAS_YUV_OVERLAY) overlay_type = 1; @@ -879,13 +673,13 @@ static Bool compositor_2d_draw_bitmap_ex(GF_VisualManager *visual, GF_TextureHan break; } /*disable based on settings*/ - if (!visual->compositor->enable_yuv_hw + if (!visual->compositor->yuvhw || (ctx->col_mat || !visual->compositor->video_out->Blit) ) { use_soft_stretch = GF_TRUE; overlay_type = 0; } - if (visual->compositor->disable_partial_hw_blit && ((src_wnd.w!=txh->width) || (src_wnd.h!=txh->height) )) { + if (!visual->compositor->blitp && ((src_wnd.w!=txh->width) || (src_wnd.h!=txh->height) )) { use_soft_stretch = GF_TRUE; } @@ -929,12 +723,17 @@ static Bool compositor_2d_draw_bitmap_ex(GF_VisualManager *visual, GF_TextureHan video_src.pitch_y = txh->stride; video_src.pixel_format = txh->pixelformat; #ifdef GF_SR_USE_DEPTH - if (txh->pixelformat==GF_PIXEL_YUVD) video_src.pixel_format = GF_PIXEL_YV12; + if (txh->pixelformat==GF_PIXEL_YUVD) video_src.pixel_format = GF_PIXEL_YUV; #endif video_src.video_buffer = txh->data; - if (txh->raw_memory) { - video_src.u_ptr = (char *) txh->pU; - video_src.v_ptr = (char *) txh->pV; + if (txh->frame_ifce) { + u32 stride; + if (!txh->frame_ifce->get_plane) return GF_FALSE; + + e = txh->frame_ifce->get_plane(txh->frame_ifce, 0, (const u8 **) &video_src.video_buffer, &video_src.pitch_y); + if (e) return GF_FALSE; + txh->frame_ifce->get_plane(txh->frame_ifce, 1, (const u8 **) video_src.u_ptr, &stride); + txh->frame_ifce->get_plane(txh->frame_ifce, 2, (const u8 **) video_src.v_ptr, &stride); } video_src.global_alpha = alpha; @@ -983,7 +782,7 @@ static Bool compositor_2d_draw_bitmap_ex(GF_VisualManager *visual, GF_TextureHan } //will pause clock if first HW load - gf_sc_texture_check_pause_on_first_load(txh); + gf_sc_texture_check_pause_on_first_load(txh, GF_TRUE); if (overlay_type) { u32 push_time; @@ -1011,7 +810,7 @@ static Bool compositor_2d_draw_bitmap_ex(GF_VisualManager *visual, GF_TextureHan visual->has_overlays = GF_TRUE; //will resume clock if first HW load - gf_sc_texture_check_pause_on_first_load(txh); + gf_sc_texture_check_pause_on_first_load(txh, GF_FALSE); return GF_TRUE; } GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor2D] Error during overlay blit - trying with soft one\n")); @@ -1046,25 +845,42 @@ static Bool compositor_2d_draw_bitmap_ex(GF_VisualManager *visual, GF_TextureHan } //will resume clock if first HW load - gf_sc_texture_check_pause_on_first_load(txh); + gf_sc_texture_check_pause_on_first_load(txh, GF_FALSE); if (use_soft_stretch) { GF_VideoSurface backbuffer; + if (!visual->compositor->softblt) { + if (is_attached) visual_2d_init_raster(visual); + txh->flags |= GF_SR_TEXTURE_DISABLE_BLIT; + return GF_FALSE; + } + e = visual->compositor->video_out->LockBackBuffer(visual->compositor->video_out, &backbuffer, GF_TRUE); if (!e) { u32 push_time = gf_sys_clock(); - gf_stretch_bits(&backbuffer, &video_src, &dst_wnd, &src_wnd, alpha, GF_FALSE, tr_state->col_key, ctx->col_mat); + e = gf_stretch_bits(&backbuffer, &video_src, &dst_wnd, &src_wnd, alpha, GF_FALSE, tr_state->col_key, ctx->col_mat); store_blit_times(txh, push_time); visual->compositor->video_out->LockBackBuffer(visual->compositor->video_out, &backbuffer, GF_FALSE); + if (e) { + if (e != GF_NOT_SUPPORTED) { + GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor2D] Cannot soft blit surface (error %s) - will try using software rasterizer\n", gf_error_to_string(e) )); + visual->compositor->last_error = e; + } + if (is_attached) visual_2d_init_raster(visual); + txh->flags |= GF_SR_TEXTURE_DISABLE_BLIT; + return GF_FALSE; + } } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor2D] Cannot lock back buffer - Error %s\n", gf_error_to_string(e) )); + if (e != GF_NOT_SUPPORTED) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor2D] Cannot lock back buffer - Error %s\n", gf_error_to_string(e) )); + visual->compositor->last_error = e; + } if (is_attached) visual_2d_init_raster(visual); - return GF_FALSE; } if (!visual->compositor->video_memory) { GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor2D] Reconfiguring video output to use video memory\n")); - visual->compositor->video_memory = GF_TRUE; + visual->compositor->video_memory = 1; visual->compositor->root_visual_setup = GF_FALSE; gf_sc_next_frame_state(visual->compositor, GF_SC_DRAW_FRAME); } @@ -1081,6 +897,8 @@ Bool compositor_2d_draw_bitmap(GF_VisualManager *visual, GF_TraverseState *tr_st u8 alpha = 0xFF; if (!ctx->aspect.fill_texture) return GF_TRUE; + if ((ctx->aspect.fill_texture == visual->compositor->passthrough_txh) && visual->compositor->passthrough_inplace) + return GF_TRUE; /*check if texture is ready - if not pretend we drew it*/ if (!ctx->aspect.fill_texture->data) return GF_TRUE; if (ctx->transform.m[0]<0) return GF_FALSE; @@ -1104,30 +922,6 @@ Bool compositor_2d_draw_bitmap(GF_VisualManager *visual, GF_TraverseState *tr_st if (!alpha) return GF_TRUE; switch (ctx->aspect.fill_texture->pixelformat) { - case GF_PIXEL_ALPHAGREY: - case GF_PIXEL_GREYSCALE: - case GF_PIXEL_RGB_24: - case GF_PIXEL_BGR_24: - case GF_PIXEL_RGB_555: - case GF_PIXEL_RGB_565: - case GF_PIXEL_ARGB: - case GF_PIXEL_RGBA: - case GF_PIXEL_YV12: - case GF_PIXEL_IYUV: - case GF_PIXEL_YVYU: - case GF_PIXEL_YUY2: - case GF_PIXEL_I420: - case GF_PIXEL_NV21: - case GF_PIXEL_NV12: - case GF_PIXEL_YUVA: - case GF_PIXEL_RGBS: - case GF_PIXEL_RGBAS: - case GF_PIXEL_YUV422: - case GF_PIXEL_YUV444: - case GF_PIXEL_YUV444_10: - case GF_PIXEL_YUV422_10: - case GF_PIXEL_YV12_10: - break; case GF_PIXEL_YUVD: case GF_PIXEL_RGBD: case GF_PIXEL_RGBDS: @@ -1138,10 +932,9 @@ Bool compositor_2d_draw_bitmap(GF_VisualManager *visual, GF_TraverseState *tr_st return GF_TRUE; } #endif - break; - /*the rest has to be displayed through brush for now, we only use YUV and RGB pool*/ + //fallthrought default: - return GF_FALSE; + break; } /*direct drawing, no clippers */ @@ -1266,7 +1059,7 @@ GF_Err compositor_2d_set_aspect_ratio(GF_Compositor *compositor) scaleY = gf_divfix(INT2FIX(compositor->vp_height), INT2FIX(compositor->scene_height)); if (!scaleY) scaleY = FIX_ONE; - if (!compositor->scalable_zoom) { + if (!compositor->sz) { compositor->output_width = compositor->scene_width; compositor->output_height = compositor->scene_height; compositor->vp_width = FIX2INT(gf_divfix(INT2FIX(compositor->display_width), scaleX)); @@ -1291,23 +1084,16 @@ GF_Err compositor_2d_set_aspect_ratio(GF_Compositor *compositor) evt.type = GF_EVENT_VIDEO_SETUP; evt.setup.width = compositor->vp_width; evt.setup.height = compositor->vp_height; - evt.setup.opengl_mode = 0; + evt.setup.use_opengl = GF_FALSE; + evt.setup.disable_vsync = compositor->bench_mode ? GF_TRUE : GF_FALSE; /*copy over settings*/ evt.setup.system_memory = compositor->video_memory ? GF_FALSE : GF_TRUE; if (compositor->request_video_memory) evt.setup.system_memory = GF_FALSE; compositor->request_video_memory = GF_FALSE; -#ifdef OPENGL_RASTER - if (compositor->opengl_raster) { - evt.setup.opengl_mode = 1; - evt.setup.system_memory = GF_FALSE; - evt.setup.back_buffer = GF_TRUE; - } -#endif - #ifndef GPAC_DISABLE_3D if (compositor->hybrid_opengl) { - evt.setup.opengl_mode = 1; + evt.setup.use_opengl = GF_TRUE; evt.setup.system_memory = GF_FALSE; evt.setup.back_buffer = GF_TRUE; } @@ -1316,12 +1102,12 @@ GF_Err compositor_2d_set_aspect_ratio(GF_Compositor *compositor) if (compositor->was_system_memory != evt.setup.system_memory) changed = GF_TRUE; else if (old_vp_width != compositor->vp_width) changed = GF_TRUE; else if (old_vp_height != compositor->vp_height) changed = GF_TRUE; - else if (compositor->is_opengl != evt.setup.opengl_mode) changed = GF_TRUE; + else if (compositor->is_opengl != evt.setup.use_opengl) changed = GF_TRUE; if (changed) { GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor2D] Reconfiguring display size %d x %d - opengl %s - use %s memory\n", evt.setup.width, evt.setup.height, - (evt.setup.opengl_mode==2) ? "Offscreen" : (evt.setup.opengl_mode==1) ? "yes" : "no", evt.setup.system_memory ? "systems" : "video" + evt.setup.use_opengl ? "yes" : "no", evt.setup.system_memory ? "systems" : "video" )); e = compositor->video_out->ProcessEvent(compositor->video_out, &evt); @@ -1338,12 +1124,16 @@ GF_Err compositor_2d_set_aspect_ratio(GF_Compositor *compositor) evt.type = GF_EVENT_QUIT; evt.message.error = e; evt.message.message = "Cannot setup video output"; - gf_term_send_event(compositor->term, &evt); + gf_sc_send_event(compositor, &evt); return e; } - compositor->is_opengl = evt.setup.opengl_mode; + compositor->is_opengl = evt.setup.use_opengl; compositor->was_system_memory = evt.setup.system_memory; + + if (evt.setup.use_opengl) { + gf_opengl_init(); + } } if (compositor->has_size_info) { compositor->traverse_state->vp_size.x = INT2FIX(compositor->scene_width); @@ -1675,14 +1465,7 @@ void visual_2d_draw_overlays(GF_VisualManager *visual) } } - void compositor_2d_init_callbacks(GF_Compositor *compositor) { compositor->visual->DrawBitmap = compositor_2d_draw_bitmap; - if (compositor->video_out->hw_caps & GF_VIDEO_HW_HAS_LINE_BLIT) { - compositor->raster_callbacks.cbk = compositor->video_out; - compositor->raster_callbacks.fill_run_alpha = (raster_cbk_fill_run_alpha) compositor->video_out->DrawHLineAlpha; - compositor->raster_callbacks.fill_run_no_alpha = (raster_cbk_fill_run_no_alpha) compositor->video_out->DrawHLine; - compositor->raster_callbacks.fill_rect = (raster_cbk_fill_rect) compositor->video_out->DrawRectangle; - } } diff --git a/src/compositor/compositor_3d.c b/src/compositor/compositor_3d.c index cfaa891..d83f02b 100644 --- a/src/compositor/compositor_3d.c +++ b/src/compositor/compositor_3d.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / Scene Compositor sub-project @@ -52,11 +52,10 @@ GF_Err compositor_3d_set_aspect_ratio(GF_Compositor *compositor) compositor->vp_y = 0; scaleX = scaleY = FIX_ONE; - if (!compositor->has_size_info) { + if (!compositor->has_size_info || compositor->inherit_type_3d) { compositor->visual->width = compositor->vp_width; compositor->visual->height = compositor->vp_height; } else { - switch (compositor->aspect_ratio) { case GF_ASPECT_RATIO_FILL_SCREEN: break; @@ -106,10 +105,11 @@ GF_Err compositor_3d_set_aspect_ratio(GF_Compositor *compositor) evt.setup.width = compositor->display_width; evt.setup.height = compositor->display_height; evt.setup.back_buffer = GF_TRUE; + evt.setup.disable_vsync = compositor->bench_mode ? GF_TRUE : GF_FALSE; #ifdef GPAC_USE_TINYGL - evt.setup.opengl_mode = 0; + evt.setup.use_opengl = GF_FALSE; #else - evt.setup.opengl_mode = 1; + evt.setup.use_opengl = GF_TRUE; compositor->is_opengl = GF_TRUE; #endif @@ -117,6 +117,9 @@ GF_Err compositor_3d_set_aspect_ratio(GF_Compositor *compositor) gf_sc_reset_graphics(compositor); return GF_OK; } + if (evt.setup.use_opengl) { + gf_opengl_init(); + } #if defined(GPAC_USE_TINYGL) { @@ -125,12 +128,12 @@ GF_Err compositor_3d_set_aspect_ratio(GF_Compositor *compositor) GF_Err e = compositor->video_out->LockBackBuffer(compositor->video_out, &bb, 1); if (e==GF_OK) { switch (bb.pixel_format) { - case GF_PIXEL_RGB_32: + case GF_PIXEL_RGBX: case GF_PIXEL_ARGB: bpp = 32; break; - case GF_PIXEL_RGB_24: - case GF_PIXEL_BGR_24: + case GF_PIXEL_RGB: + case GF_PIXEL_BGR: bpp = 24; break; case GF_PIXEL_RGB_565: @@ -205,7 +208,7 @@ void compositor_3d_draw_bitmap(Drawable *stack, DrawAspect2D *asp, GF_TraverseSt visual_3d_enable_antialias(tr_state->visual, GF_FALSE); visual_3d_set_material_2d_argb(tr_state->visual, GF_COL_ARGB(alpha, 0xFF, 0xFF, 0xFF)); - + if (alpha && (alpha != 0xFF)) { gf_sc_texture_set_blend_mode(txh, TX_MODULATE); } else if (gf_sc_texture_is_transparent(txh)) { @@ -232,7 +235,7 @@ void compositor_3d_draw_bitmap(Drawable *stack, DrawAspect2D *asp, GF_TraverseSt if (tr_state->depth_offset) { GF_Matrix mx; Fixed offset; - Fixed disp_depth = (compositor->display_depth<0) ? INT2FIX(tr_state->visual->height) : INT2FIX(compositor->display_depth); + Fixed disp_depth = (compositor->dispdepth<0) ? INT2FIX(tr_state->visual->height) : INT2FIX(compositor->dispdepth); if (disp_depth) { GF_Matrix bck_mx; if (!tr_state->pixel_metrics) disp_depth = gf_divfix(disp_depth, tr_state->min_hsize); @@ -260,6 +263,5 @@ void compositor_3d_draw_bitmap(Drawable *stack, DrawAspect2D *asp, GF_TraverseSt } } - #endif diff --git a/src/compositor/compositor_node_init.c b/src/compositor/compositor_node_init.c index ade70a9..702f0d6 100644 --- a/src/compositor/compositor_node_init.c +++ b/src/compositor/compositor_node_init.c @@ -43,7 +43,6 @@ void compositor_init_afx_node(GF_Compositor *compositor, GF_Node *node, MFURL *u #endif } - void gf_sc_on_node_init(GF_Compositor *compositor, GF_Node *node) { switch (gf_node_get_tag(node)) { @@ -558,7 +557,9 @@ void gf_sc_on_node_init(GF_Compositor *compositor, GF_Node *node) break; case TAG_SVG_filter: +#ifdef GPAC_ENABLE_SVG_FILTERS compositor_init_svg_filter(compositor, node); +#endif break; case TAG_LSR_updates: diff --git a/src/compositor/drawable.c b/src/compositor/drawable.c index 7aaf169..080eec6 100644 --- a/src/compositor/drawable.c +++ b/src/compositor/drawable.c @@ -167,15 +167,15 @@ void drawable_reset_bounds(Drawable *dr, GF_VisualManager *visual) void drawable_del_ex(Drawable *dr, GF_Compositor *compositor) { StrikeInfo2D *si; - Bool is_reg = 0; - DRInfo *dri, *cur; + DRInfo *dri; BoundInfo *bi, *_cur; /*remove node from all visuals it's on*/ dri = dr->dri; while (dri) { - is_reg = compositor ? gf_sc_visual_is_registered(compositor, dri->visual) : 0; + DRInfo *cur; + Bool is_reg = compositor ? gf_sc_visual_is_registered(compositor, dri->visual) : 0; bi = dri->current_bounds; while (bi) { @@ -320,7 +320,7 @@ void drawable_mark_modified(Drawable *drawable, GF_TraverseState *tr_state) } /*move current bounds to previous bounds*/ -Bool drawable_flush_bounds(Drawable *drawable, GF_VisualManager *on_visual, u32 draw_mode) +Bool drawable_flush_bounds(Drawable *drawable, GF_VisualManager *on_visual, u32 mode2d) { Bool was_drawn; DRInfo *dri; @@ -342,9 +342,9 @@ Bool drawable_flush_bounds(Drawable *drawable, GF_VisualManager *on_visual, u32 was_drawn = (dri->current_bounds && dri->current_bounds->clip.width) ? 1 : 0; - if (draw_mode) { + if (mode2d) { /*permanent direct drawing mode, destroy previous bounds*/ - if (draw_mode==1) { + if (mode2d==1) { if (dri->previous_bounds) { GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Destroying previous bounds info for drawable %s\n", gf_node_get_class_name(drawable->node))); while (dri->previous_bounds) { @@ -584,7 +584,7 @@ check_default: asp->fill_color &= 0x00FFFFFF; break; case TAG_MPEG4_PointSet2D: - asp->fill_color |= FIX2INT(255 * (m ? (FIX_ONE - m->transparency) : FIX_ONE)) << 24; + asp->fill_color |= ((u32 )(FIX2INT(255 * (m ? (FIX_ONE - m->transparency) : FIX_ONE))) ) << 24; asp->pen_props.width = 0; break; default: @@ -760,7 +760,8 @@ DrawableContext *drawable_init_context_mpeg4(Drawable *drawable, GF_TraverseStat /*FIXME - only needed for texture*/ if (!tr_state->color_mat.identity) { GF_SAFEALLOC(ctx->col_mat, GF_ColorMatrix); - gf_cmx_copy(ctx->col_mat, &tr_state->color_mat); + if (ctx->col_mat) + gf_cmx_copy(ctx->col_mat, &tr_state->color_mat); } /*IndexedLineSet2D and PointSet2D ignores fill flag and texturing*/ @@ -907,7 +908,6 @@ void drawable_finalize_sort_ex(DrawableContext *ctx, GF_TraverseState *tr_state, } else { gf_path_get_bounds(ctx->drawable->path, &store_orig_bounds); } - //if (store_orig_bounds) fprintf(stderr, "store_orig_bounds: %d\n", (int) store_orig_bounds.width ); ctx->bi->unclip = store_orig_bounds; gf_mx2d_apply_rect(&tr_state->transform, &ctx->bi->unclip); @@ -984,7 +984,6 @@ void drawable_check_focus_highlight(GF_Node *node, GF_TraverseState *tr_state, G Drawable *hlight; GF_Node *prev_node; u32 prev_mode; - GF_Rect *bounds; GF_Matrix2D cur; GF_Compositor *compositor = tr_state->visual->compositor; @@ -1006,6 +1005,7 @@ void drawable_check_focus_highlight(GF_Node *node, GF_TraverseState *tr_state, G /*check if focus node has changed*/ prev_node = gf_node_get_private(hlight->node); if (prev_node != node) { + GF_Rect *bounds; /*this is a grouping node, get its bounds*/ if (!orig_bounds) { gf_mx2d_copy(cur, tr_state->transform); @@ -1031,10 +1031,10 @@ void drawable_check_focus_highlight(GF_Node *node, GF_TraverseState *tr_state, G } hl_ctx = visual_2d_get_drawable_context(tr_state->visual); hl_ctx->drawable = hlight; - hl_ctx->aspect.fill_color = compositor->highlight_fill; - hl_ctx->aspect.line_color = compositor->highlight_stroke; + hl_ctx->aspect.fill_color = compositor->hlfill; + hl_ctx->aspect.line_color = compositor->hlline; hl_ctx->aspect.line_scale = 0; - hl_ctx->aspect.pen_props.width = compositor->highlight_stroke_width; + hl_ctx->aspect.pen_props.width = compositor->hllinew; hl_ctx->aspect.pen_props.join = GF_LINE_JOIN_BEVEL; hl_ctx->aspect.pen_props.dash = GF_DASH_STYLE_DOT; @@ -1042,7 +1042,7 @@ void drawable_check_focus_highlight(GF_Node *node, GF_TraverseState *tr_state, G if (compositor->edited_text) { hl_ctx->aspect.pen_props.width = 2*FIX_ONE; hl_ctx->aspect.pen_props.dash = 1; - hl_ctx->aspect.line_color = compositor->highlight_stroke; + hl_ctx->aspect.line_color = compositor->hlline; } @@ -1081,7 +1081,9 @@ StrikeInfo2D *drawable_get_strikeinfo(GF_Compositor *compositor, Drawable *drawa { StrikeInfo2D *si, *prev; GF_Node *lp; +#ifndef GPAC_DISABLE_VRML Bool dirty; +#endif if (!asp->pen_props.width) return NULL; if (path && !path->n_points) return NULL; @@ -1145,11 +1147,13 @@ StrikeInfo2D *drawable_get_strikeinfo(GF_Compositor *compositor, Drawable *drawa /*node changed or outline not build*/ #ifndef GPAC_DISABLE_VRML dirty = lp ? drawable_lineprops_dirty(lp) : 0; -#else - dirty = 0; #endif - if (!si->outline || dirty || (si->line_scale != asp->line_scale) || (si->path_length != asp->pen_props.path_length) || (svg_flags & CTX_SVG_OUTLINE_GEOMETRY_DIRTY)) { + if (!si->outline +#ifndef GPAC_DISABLE_VRML + || dirty +#endif + || (si->line_scale != asp->line_scale) || (si->path_length != asp->pen_props.path_length) || (svg_flags & CTX_SVG_OUTLINE_GEOMETRY_DIRTY)) { u32 i; Fixed w = asp->pen_props.width; Fixed dash_o = asp->pen_props.dash_offset; @@ -1518,7 +1522,8 @@ DrawableContext *drawable_init_context_svg(Drawable *drawable, GF_TraverseState /*FIXME - only needed for texture*/ if (!tr_state->color_mat.identity) { GF_SAFEALLOC(ctx->col_mat, GF_ColorMatrix); - gf_cmx_copy(ctx->col_mat, &tr_state->color_mat); + if (ctx->col_mat) + gf_cmx_copy(ctx->col_mat, &tr_state->color_mat); } switch (gf_node_get_tag(ctx->drawable->node) ) { diff --git a/src/compositor/drawable.h b/src/compositor/drawable.h index 747c843..f0653c1 100644 --- a/src/compositor/drawable.h +++ b/src/compositor/drawable.h @@ -155,7 +155,7 @@ void drawable_reset_group_highlight(GF_TraverseState *tr_state, GF_Node *n); /*move current bounds to previous bounds for given target visual manager - called BEFORE updating the visual manager returns 1 if nod was draw last frame on this visual manager, 0 otherwise*/ -Bool drawable_flush_bounds(Drawable *node, GF_VisualManager *on_visual, u32 draw_mode); +Bool drawable_flush_bounds(Drawable *node, GF_VisualManager *on_visual, u32 mode2d); /* return 1 if same bound is found in previous list (and remove it from the list) diff --git a/src/compositor/events.c b/src/compositor/events.c index ca14f73..4a6c350 100644 --- a/src/compositor/events.c +++ b/src/compositor/events.c @@ -27,7 +27,6 @@ #include "nodes_stacks.h" #include "mpeg4_grouping.h" #include -#include #include static GF_Node *browse_parent_for_focus(GF_Compositor *compositor, GF_Node *elt, Bool prev_focus); @@ -70,8 +69,6 @@ static Bool exec_text_selection(GF_Compositor *compositor, GF_Event *event) static void flush_text_node_edit(GF_Compositor *compositor, Bool final_flush) { Bool signal; - char *txt; - size_t len; if (!compositor->edited_text) return; /* if this is the final editing and there is text, @@ -88,6 +85,8 @@ static void flush_text_node_edit(GF_Compositor *compositor, Bool final_flush) *compositor->edited_text = NULL; } if (compositor->sel_buffer_len) { + char *txt; + size_t len; const u16 *lptr; txt = (char*)gf_malloc(sizeof(char)*2*compositor->sel_buffer_len); lptr = compositor->sel_buffer; @@ -267,8 +266,7 @@ static Bool load_text_node(GF_Compositor *compositor, u32 cmd_type) if (append) { u16 end; const u16 *srcp; - size_t len; - GF_DOMText *cur, *ntext; + GF_DOMText *ntext; GF_ChildNodeItem *children = ((GF_ParentNode *) compositor->focus_node)->children; GF_Node *t = gf_node_new(gf_node_get_graph(child->node), TAG_SVG_tbreak); @@ -282,6 +280,9 @@ static Bool load_text_node(GF_Compositor *compositor, u32 cmd_type) gf_node_list_insert_child(&children, t, pos); res = &((GF_DOMText *)child->node)->textContent; } else { + size_t len; + GF_DOMText *cur; + gf_node_list_insert_child(&children, t, pos+1); ntext = (GF_DOMText*) gf_node_new(gf_node_get_graph(child->node), TAG_DOMText); gf_node_init(t); @@ -340,18 +341,6 @@ static Bool load_text_node(GF_Compositor *compositor, u32 cmd_type) } res = &((GF_DOMText *)child->node)->textContent; } - /* if (1) { - GF_ChildNodeItem *child = ((GF_ParentNode *) compositor->focus_node)->children; - fprintf(stderr, "Dumping text tree:\n"); - while (child) { - switch (gf_node_get_tag(child->node)) { - case TAG_SVG_tbreak: fprintf(stderr, "\ttbreak\n"); break; - case TAG_DOMText: fprintf(stderr, "\ttext: %s\n", ((GF_DOMText *)child->node)->textContent); break; - } - child = child->next; - } - } - */ break; } child = child->next; @@ -551,9 +540,7 @@ static void toggle_keyboard(GF_Compositor * compositor, Bool do_show) GF_Err e = compositor->video_out->ProcessEvent(compositor->video_out, &evt); if (e == GF_OK) return; } - if (compositor->term) { - gf_term_user_event(compositor->term, &evt); - } + gf_sc_user_event(compositor, &evt); } static Bool hit_node_editable(GF_Compositor *compositor, Bool check_focus_node) @@ -662,9 +649,7 @@ static Bool exec_event_dom(GF_Compositor *compositor, GF_Event *event) cursor_type = GF_CURSOR_NORMAL; /*all mouse events*/ - if ((event->type<=GF_EVENT_MOUSEWHEEL) - // && (gf_node_get_dom_event_filter(gf_sg_get_root_node(compositor->scene)) & GF_DOM_EVENT_MOUSE) - ) { + if (event->type<=GF_EVENT_MOUSEWHEEL) { Fixed X = compositor->hit_world_point.x; Fixed Y = compositor->hit_world_point.y; /*flip back to origin at top-left*/ @@ -738,7 +723,7 @@ static Bool exec_event_dom(GF_Compositor *compositor, GF_Event *event) TODO quick- fix for iPhone as well TODO clean: figure out whether we use a mouse or a touch device - if touch device, remove this test */ -#if !defined(_WIN32_WCE) || !defined(GPAC_ANDROID) +#if !defined(_WIN32_WCE) || !defined(GPAC_CONFIG_ANDROID) if ((compositor->grab_x == X) && (compositor->grab_y == Y)) #endif { @@ -1003,7 +988,6 @@ Bool gf_sc_exec_event_vrml(GF_Compositor *compositor, GF_Event *ev) gf_sc_reset_collide_cursor(compositor); } if (res) { -#if 1 GF_SceneGraph *sg; /*apply event cascade - this is needed for cases where several events are processed inbetween 2 simulation tick. If we don't flush the routes stack, the result will likely be wrong @@ -1013,7 +997,6 @@ Bool gf_sc_exec_event_vrml(GF_Compositor *compositor, GF_Event *ev) while ((sg = (GF_SceneGraph*)gf_list_enum(compositor->extra_scenes, &i))) { gf_sg_activate_routes(sg); } -#endif return 1; } return GF_FALSE; @@ -1376,7 +1359,6 @@ test_grouping: #endif { s32 i, wc; - GF_ChildNodeItem *child; #ifndef GPAC_DISABLE_X3D if (tag==TAG_X3D_Switch) { child = ((X_Switch*)elt)->children; @@ -1915,7 +1897,7 @@ Bool gf_sc_execute_event(GF_Compositor *compositor, GF_TraverseState *tr_state, if (ev->type==GF_EVENT_KEYDOWN) { switch (ev->key.key_code) { case GF_KEY_ENTER: - if (0&&compositor->focus_text_type) { + if ((0) && compositor->focus_text_type) { exec_text_input(compositor, NULL); ret = GF_TRUE; #ifndef GPAC_DISABLE_VRML @@ -1934,9 +1916,10 @@ Bool gf_sc_execute_event(GF_Compositor *compositor, GF_TraverseState *tr_state, case GF_KEY_RIGHT: if (compositor->focus_uses_dom_events) { ret += gf_sc_svg_focus_navigate(compositor, ev->key.key_code); - } else if (compositor->keynav_node) { - GF_Node *next_nav = NULL; + } #ifndef GPAC_DISABLE_VRML + else if (compositor->keynav_node) { + GF_Node *next_nav = NULL; switch (ev->key.key_code) { case GF_KEY_UP: next_nav = ((M_KeyNavigator*)compositor->keynav_node)->up; @@ -1951,12 +1934,12 @@ Bool gf_sc_execute_event(GF_Compositor *compositor, GF_TraverseState *tr_state, next_nav = ((M_KeyNavigator*)compositor->keynav_node)->right; break; } -#endif if (next_nav) { gf_sc_change_key_navigator(compositor, next_nav); ret = GF_TRUE; } } +#endif break; } } @@ -1968,7 +1951,8 @@ Bool gf_sc_execute_event(GF_Compositor *compositor, GF_TraverseState *tr_state, static Bool forward_event(GF_Compositor *compositor, GF_Event *ev, Bool consumed) { - Bool ret = gf_term_forward_event(compositor->term, ev, consumed, GF_FALSE); + Bool ret = gf_filter_forward_gf_event(compositor->filter, ev, consumed, GF_FALSE); + if (consumed) return GF_FALSE; if ((ev->type==GF_EVENT_MOUSEUP) && (ev->mouse.button==GF_MOUSE_LEFT)) { @@ -1981,7 +1965,7 @@ static Bool forward_event(GF_Compositor *compositor, GF_Event *ev, Bool consumed event.mouse.key_states = compositor->key_states; event.mouse.x = ev->mouse.x; event.mouse.y = ev->mouse.y; - ret += gf_term_send_event(compositor->term, &event); + ret += gf_sc_send_event(compositor, &event); } compositor->last_click_time = now; } @@ -1995,6 +1979,23 @@ Bool gf_sc_exec_event(GF_Compositor *compositor, GF_Event *evt) Bool switch_coords = GF_FALSE; Bool ret = GF_FALSE; if (evt->type<=GF_EVENT_MOUSEWHEEL) { + if (compositor->sgaze) { + compositor->gaze_x = evt->mouse.x; + compositor->gaze_y = evt->mouse.y; + } +#ifndef GPAC_DISABLE_3D + else if ((compositor->visual->autostereo_type==GF_3D_STEREO_SIDE) || (compositor->visual->autostereo_type==GF_3D_STEREO_HEADSET)) { + if (evt->mouse.x > compositor->visual->camera.proj_vp.width) + evt->mouse.x -= FIX2INT(compositor->visual->camera.proj_vp.width); + evt->mouse.x *= 2; + } + else if (compositor->visual->autostereo_type==GF_3D_STEREO_TOP) { + if (evt->mouse.y > compositor->visual->camera.proj_vp.height) + evt->mouse.y -= FIX2INT(compositor->visual->camera.proj_vp.height); + evt->mouse.y *= 2; + } +#endif + if (compositor->visual->center_coords) { x = evt->mouse.x; y = evt->mouse.y; @@ -2016,16 +2017,16 @@ Bool gf_sc_exec_event(GF_Compositor *compositor, GF_Event *evt) if (switch_coords) { evt->mouse.x = x; evt->mouse.y = y; + } if (!ret) { #ifndef GPAC_DISABLE_3D /*remember active layer on mouse click - may be NULL*/ - if ((evt->type==GF_EVENT_MOUSEDOWN) && (evt->mouse.button==GF_MOUSE_LEFT)) compositor->active_layer = compositor->traverse_state->layer3d; + if ((evt->type==GF_EVENT_MOUSEDOWN) && (evt->mouse.button==GF_MOUSE_LEFT)) + compositor->active_layer = compositor->traverse_state->layer3d; #endif - } - if (!ret) { ret = forward_event(compositor, evt, ret); } //if event is consumed before forwarding don't apply navigation diff --git a/src/compositor/font_engine.c b/src/compositor/font_engine.c index 30e81f0..a746172 100644 --- a/src/compositor/font_engine.c +++ b/src/compositor/font_engine.c @@ -43,41 +43,18 @@ struct _gf_ft_mgr Bool wait_font_load; }; - -GF_FontManager *gf_font_manager_new(GF_User *user) +GF_EXPORT +GF_FontManager *gf_font_manager_new() { char *def_font = "SERIF"; - u32 i, count; GF_FontManager *font_mgr; GF_FontReader *ifce; - const char *opt; - - ifce = NULL; - opt = gf_cfg_get_key(user->config, "FontEngine", "FontReader"); - if (opt) { - ifce = (GF_FontReader *) gf_modules_load_interface_by_name(user->modules, opt, GF_FONT_READER_INTERFACE); - if (ifce && ifce->init_font_engine(ifce) != GF_OK) { - gf_modules_close_interface((GF_BaseInterface *)ifce); - ifce = NULL; - } - } + Bool wait_for_fonts = gf_opts_get_bool("core", "wait-fonts"); - if (!ifce) { - count = gf_modules_get_count(user->modules); - for (i=0; imodules, i, GF_FONT_READER_INTERFACE); - if (!ifce) continue; + ifce = (GF_FontReader *) gf_module_load(GF_FONT_READER_INTERFACE, NULL); + if (ifce) ifce->init_font_engine(ifce); - if (ifce->init_font_engine(ifce) != GF_OK) { - gf_modules_close_interface((GF_BaseInterface *)ifce); - ifce = NULL; - continue; - } - gf_cfg_set_key(user->config, "FontEngine", "FontReader", ifce->module_name); - break; - } - } GF_SAFEALLOC(font_mgr, GF_FontManager); if (!font_mgr) { GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate font manager\n")); @@ -96,9 +73,8 @@ GF_FontManager *gf_font_manager_new(GF_User *user) gf_path_add_line_to(font_mgr->line_path, -FIX_ONE/2, -FIX_ONE/2); gf_path_close(font_mgr->line_path); - opt = gf_cfg_get_key(user->config, "FontEngine", "WaitForFontLoad"); - if (!opt) gf_cfg_set_key(user->config, "FontEngine", "WaitForFontLoad", "no"); - if (opt && !strcmp(opt, "yes")) font_mgr->wait_font_load = 1; + + font_mgr->wait_font_load = wait_for_fonts; return font_mgr; } @@ -109,7 +85,9 @@ void gf_font_predestroy(GF_Font *font) while (gf_list_count(font->spans)) { GF_TextSpan *ts = gf_list_get(font->spans, 0); gf_list_rem(font->spans, 0); +#ifndef GPAC_DISABLE_PLAYER gf_node_dirty_set(ts->user, 0, 0); +#endif ts->user=NULL; } gf_list_del(font->spans); @@ -189,7 +167,7 @@ GF_Err gf_font_manager_unregister_font(GF_FontManager *fm, GF_Font *font) } - +GF_EXPORT GF_Font *gf_font_manager_set_font_ex(GF_FontManager *fm, char **alt_fonts, u32 nb_fonts, u32 styles, Bool check_only) { u32 i; @@ -207,15 +185,15 @@ GF_Font *gf_font_manager_set_font_ex(GF_FontManager *fm, char **alt_fonts, u32 n font_name = alt_fonts[i]; if (!stricmp(font_name, "SERIF")) { - opt = gf_modules_get_option((GF_BaseInterface *)fm->reader, "FontEngine", "FontSerif"); + opt = gf_opts_get_key("FontCache", "FontSerif"); if (opt) font_name = (char*)opt; } else if (!stricmp(font_name, "SANS") || !stricmp(font_name, "sans-serif")) { - opt = gf_modules_get_option((GF_BaseInterface *)fm->reader, "FontEngine", "FontSans"); + opt = gf_opts_get_key("FontCache", "FontSans"); if (opt) font_name = (char*)opt; } else if (!stricmp(font_name, "TYPEWRITER") || !stricmp(font_name, "monospace")) { - opt = gf_modules_get_option((GF_BaseInterface *)fm->reader, "FontEngine", "FontFixed"); + opt = gf_opts_get_key("FontCache", "FontFixed"); if (opt) font_name = (char*)opt; } @@ -394,7 +372,7 @@ static GF_Glyph *gf_font_get_glyph(GF_FontManager *fm, GF_Font *font, u32 name) return glyph; } - +GF_EXPORT GF_TextSpan *gf_font_manager_create_span(GF_FontManager *fm, GF_Font *font, char *text, Fixed font_size, Bool needs_x_offset, Bool needs_y_offset, Bool needs_rotate, const char *xml_lang, Bool fliped_text, u32 styles, GF_Node *user) { GF_Err e; @@ -458,6 +436,7 @@ GF_TextSpan *gf_font_manager_create_span(GF_FontManager *fm, GF_Font *font, char } +#ifndef GPAC_DISABLE_PLAYER typedef struct _span_internal { @@ -478,6 +457,8 @@ typedef struct _span_internal #endif } GF_SpanExtensions; +#endif + void gf_font_manager_delete_span(GF_FontManager *fm, GF_TextSpan *span) { @@ -488,6 +469,7 @@ void gf_font_manager_delete_span(GF_FontManager *fm, GF_TextSpan *span) if (span->dy) gf_free(span->dy); if (span->rot) gf_free(span->rot); +#ifndef GPAC_DISABLE_PLAYER if (span->ext) { if (span->ext->path) gf_path_del(span->ext->path); #ifndef GPAC_DISABLE_3D @@ -502,9 +484,12 @@ void gf_font_manager_delete_span(GF_FontManager *fm, GF_TextSpan *span) } gf_free(span->ext); } +#endif + gf_free(span); } +GF_EXPORT void gf_font_manager_refresh_span_bounds(GF_TextSpan *span) { u32 i; @@ -636,6 +621,8 @@ GF_Path *gf_font_span_create_path(GF_TextSpan *span) return path; } +#ifndef GPAC_DISABLE_PLAYER + static void span_alloc_extensions(GF_TextSpan *span) { if (span->ext) return; @@ -671,12 +658,10 @@ static Bool span_setup_texture(GF_Compositor *compositor, GF_TextSpan *span, Boo Fixed cx, cy, sx, sy, max, min; u32 tw, th; GF_Matrix2D mx; - GF_STENCIL stencil, brush; - GF_SURFACE surface; + GF_EVGStencil *stencil, *brush; + GF_EVGSurface *surface; u32 width, height; Fixed scale; - GF_Raster2D *raster = compositor->rasterizer; - span_alloc_extensions(span); /*something failed*/ @@ -746,7 +731,7 @@ static Bool span_setup_texture(GF_Compositor *compositor, GF_TextSpan *span, Boo gf_sc_texture_setup(span->ext->txh, compositor, NULL); gf_sc_texture_allocate(span->ext->txh); stencil = gf_sc_texture_get_stencil(span->ext->txh); - if (!stencil) stencil = raster->stencil_new(raster, GF_STENCIL_TEXTURE); + if (!stencil) stencil = gf_evg_stencil_new(GF_STENCIL_TEXTURE); /*FIXME - make it work with alphagrey...*/ span->ext->txh->width = width; @@ -756,7 +741,7 @@ static Bool span_setup_texture(GF_Compositor *compositor, GF_TextSpan *span, Boo span->ext->txh->transparent = 1; span->ext->txh->flags |= GF_SR_TEXTURE_NO_GL_FLIP; - surface = raster->surface_new(raster, 1); + surface = gf_evg_surface_new(1); if (!surface) { gf_sc_texture_release(span->ext->txh); return 0; @@ -764,11 +749,11 @@ static Bool span_setup_texture(GF_Compositor *compositor, GF_TextSpan *span, Boo span->ext->txh->data = (char *) gf_malloc(sizeof(char)*span->ext->txh->stride*span->ext->txh->height); memset(span->ext->txh->data, 0, sizeof(char)*span->ext->txh->stride*span->ext->txh->height); - raster->stencil_set_texture(stencil, span->ext->txh->data, span->ext->txh->width, span->ext->txh->height, span->ext->txh->stride, span->ext->txh->pixelformat, span->ext->txh->pixelformat, 1); - raster->surface_attach_to_texture(surface, stencil); + gf_evg_stencil_set_texture(stencil, span->ext->txh->data, span->ext->txh->width, span->ext->txh->height, span->ext->txh->stride, span->ext->txh->pixelformat); + gf_evg_surface_attach_to_texture(surface, stencil); - brush = raster->stencil_new(raster, GF_STENCIL_SOLID); - raster->stencil_set_brush_color(brush, 0xFF000000); + brush = gf_evg_stencil_new(GF_STENCIL_SOLID); + gf_evg_stencil_set_brush_color(brush, for_3d ? 0xFFFFFFFF : tr_state->ctx->aspect.fill_color); cx = bounds.x + bounds.width/2; cy = bounds.y - bounds.height/2; @@ -778,14 +763,14 @@ static Bool span_setup_texture(GF_Compositor *compositor, GF_TextSpan *span, Boo gf_mx2d_add_scale(&mx, sx, sy); // gf_mx2d_add_scale(&mx, 99*FIX_ONE/100, 99*FIX_ONE/100); - raster->surface_set_matrix(surface, &mx); - raster->surface_set_raster_level(surface, GF_RASTER_HIGH_QUALITY); + gf_evg_surface_set_matrix(surface, &mx); + gf_evg_surface_set_raster_level(surface, GF_RASTER_HIGH_QUALITY); span_path = gf_font_span_create_path(span); - raster->surface_set_path(surface, span_path); + gf_evg_surface_set_path(surface, span_path); - raster->surface_fill(surface, brush); - raster->stencil_delete(brush); - raster->surface_delete(surface); + gf_evg_surface_fill(surface, brush); + gf_evg_stencil_delete(brush); + gf_evg_surface_delete(surface); gf_path_del(span_path); if (span->font->baseline) { @@ -862,7 +847,7 @@ void gf_font_spans_draw_3d(GF_List *spans, GF_TraverseState *tr_state, DrawAspec Bool fill_2d, vect_outline, can_texture_text; GF_Compositor *compositor = (GF_Compositor*)tr_state->visual->compositor; - vect_outline = !compositor->raster_outlines; + vect_outline = !compositor->linegl; visual_3d_set_state(tr_state->visual, V3D_STATE_BLEND, 0); @@ -926,7 +911,7 @@ void gf_font_spans_draw_3d(GF_List *spans, GF_TraverseState *tr_state, DrawAspec can_texture_text = 0; if (fill_2d || !asp) { /*check if we can use text texturing*/ - if (force_texturing || (compositor->texture_text_mode != GF_TEXTURE_TEXT_NEVER) ) { + if (force_texturing || (compositor->textxt != GF_TEXTURE_TEXT_NEVER) ) { if (fill_2d && asp->pen_props.width) { can_texture_text = 0; } else { @@ -935,18 +920,19 @@ void gf_font_spans_draw_3d(GF_List *spans, GF_TraverseState *tr_state, DrawAspec } } - visual_3d_enable_antialias(tr_state->visual, compositor->antiAlias); + visual_3d_enable_antialias(tr_state->visual, compositor->aa); if (fill_2d || !asp || tr_state->mesh_num_textures) { - if (fill_2d) visual_3d_set_material_2d_argb(tr_state->visual, asp->fill_color); + if (fill_2d && asp) visual_3d_set_material_2d_argb(tr_state->visual, asp->fill_color); i = tr_state->text_split_idx ? tr_state->text_split_idx-1 : 0; while ((span = (GF_TextSpan *)gf_list_enum(spans, &i))) { if (text_hl) { visual_3d_fill_rect(tr_state->visual, span->bounds, hl_color); - if (fill_2d) - visual_3d_set_material_2d_argb(tr_state->visual, asp->fill_color); - else + if (fill_2d) { + if (asp) + visual_3d_set_material_2d_argb(tr_state->visual, asp->fill_color); + } else visual_3d_setup_appearance(tr_state); } @@ -965,7 +951,7 @@ void gf_font_spans_draw_3d(GF_List *spans, GF_TraverseState *tr_state, DrawAspec if (tr_state->text_split_idx) break; } - tr_state->visual->has_material_2d = 0; + tr_state->visual->has_material_2d = GF_FALSE; /*reset texturing in case of line texture*/ if (!asp) visual_3d_disable_texture(tr_state); @@ -1284,12 +1270,11 @@ static void gf_font_spans_select(GF_TextSpan *span, GF_TraverseState *tr_state, void gf_font_spans_get_selection(GF_Node *node, GF_List *spans, GF_TraverseState *tr_state) { - GF_TextSpan *span; u32 i, count; GF_Rect rc; count = gf_list_count(spans); for (i=0; itexture_text_mode==GF_TEXTURE_TEXT_ALWAYS) ) { + if (force_texture_text || (compositor->textxt==GF_TEXTURE_TEXT_ALWAYS) ) { use_texture_text = !ctx->aspect.fill_texture && !ctx->aspect.pen_props.width; } @@ -1331,6 +1316,7 @@ void gf_font_spans_draw_2d(GF_List *spans, GF_TraverseState *tr_state, u32 hl_co if (GF_COL_A(hl_color) == 0) hl_color = 0; } + memset(&rc, 0, sizeof(GF_Rect)); count = gf_list_count(spans); i=ctx->sub_path_index ? ctx->sub_path_index-1 : 0; for(; iglyphs[j]->path, &rc); rc.height = INT2FIX(span->font->ascent + span->font->descent); - if (0&&span->rot) { - GF_Matrix2D r; - gf_mx2d_init(r); - gf_mx2d_add_rotation(&r, 0, 0, span->rot[i]); - gf_mx2d_apply_coords(&r, &loc_x, &loc_y); - } - if (use_dom_events && !compositor->sel_buffer) { if (svg_drawable_is_over(drawable, loc_x, loc_y, &asp, tr_state, &rc)) goto picked; @@ -1520,3 +1499,5 @@ picked: gf_list_add(tr_state->visual->compositor->sensors, gf_list_get(tr_state->vrml_sensors, i)); } } + +#endif // GPAC_DISABLE_PLAYER diff --git a/src/compositor/gl_inc.h b/src/compositor/gl_inc.h index 1dd4286..9facb29 100644 --- a/src/compositor/gl_inc.h +++ b/src/compositor/gl_inc.h @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / Scene Compositor sub-project @@ -27,89 +27,102 @@ #define _GL_INC_H_ #ifdef WIN32 -#include +# include #endif #ifndef _GF_SETUP_H_ -#error "Missing gpac/setup.h include" +# error "Missing gpac/setup.h include" #endif #ifndef GPAC_DISABLE_3D #ifdef GPAC_USE_GLES1X +# ifdef GPAC_CONFIG_ANDROID +# include "GLES/gl.h" +# elif defined(GPAC_CONFIG_IOS) +# include "OpenGLES/ES1/gl.h" +# include "OpenGLES/ES1/glext.h" +# include "glues.h" +# else +/*non standard include on linux/windows, usually not used*/ +# include "GLES/egl.h" +# endif + +# if defined(GPAC_HAS_GLU) && !defined (GPAC_CONFIG_IOS) +/*WARNING - this is NOT a standard include, GLU is not supported by GLES*/ +# include +# endif -#ifdef GPAC_ANDROID -#include "GLES/gl.h" -#elif defined(GPAC_IPHONE) -#include "OpenGLES/ES1/gl.h" -#include "OpenGLES/ES1/glext.h" -#include "glues.h" -#else -#include "GLES/egl.h" -#endif +# ifndef EGL_VERSION_1_0 +# define EGL_VERSION_1_0 1 +# endif -#if defined(GPAC_HAS_GLU) && !defined (GPAC_IPHONE) -/*WARNING - this is NOT a standard include, GLU is not supported by GLES*/ -#include -#endif +# ifndef EGL_VERSION_1_1 +# ifdef GL_OES_VERSION_1_1 +# define EGL_VERSION_1_1 1 +# endif +# endif +//end GPAC_USE_GLES1X -#ifndef EGL_VERSION_1_0 -#define EGL_VERSION_1_0 1 -#endif +# elif defined(GPAC_USE_GLES2) +# ifdef GPAC_CONFIG_IOS +# include "OpenGLES/ES2/gl.h" +# include "glues.h" +# else +# include +# include +# endif -#ifndef EGL_VERSION_1_1 -#ifdef GL_OES_VERSION_1_1 -#define EGL_VERSION_1_1 1 -#endif -#endif +//end GPAC_USE_GLES2 #elif defined (CONFIG_DARWIN_GL) #include #include -#else +//end CONFIG_DARWIN_GL - -#define GL_GLEXT_PROTOTYPES - -#ifdef GPAC_USE_OGLES1X -#include -#include - -#elif defined(GPAC_USE_GLES2) - -#ifdef GPAC_IPHONE -#include "OpenGLES/ES2/gl.h" -#include "glues.h" #else +//generic windows/linux config -#include -#include -#endif - -#else +//in case the versions are defined, get the prototypes +# define GL_GLEXT_PROTOTYPES +# include +# ifdef GPAC_HAS_GLU +# include +# endif -#include +//end generic windows/linux config -#ifdef GPAC_HAS_GLU -#include #endif +#if 0 +#define GL_CHECK_ERR() {s32 res = glGetError(); if (res) GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("GL Error %d file %s line %d\n", res, __FILE__, __LINE__)); } +#else +#define GL_CHECK_ERR() #endif +#ifdef GPAC_USE_TINYGL +# define GLTEXENV glTexEnvi +# define GLTEXPARAM glTexParameteri +# define TexEnvType u32 +#elif defined (GPAC_USE_GLES1X) +# define GLTEXENV glTexEnvx +# define GLTEXPARAM glTexParameterx +# define TexEnvType Fixed +#else +# define GLTEXENV glTexEnvf +# define GLTEXPARAM glTexParameteri +# define TexEnvType Float #endif -#define GL_CHECK_ERR {s32 res = glGetError(); if (res) GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("GL Error %d file %s line %d\n", res, __FILE__, __LINE__)); } - /*macros for GL proto and fun declaration*/ #ifdef _WIN32_WCE #define GLAPICAST * #elif defined(WIN32) -#include #define GLAPICAST APIENTRY * #else #define GLAPICAST * @@ -118,8 +131,11 @@ #define GLDECL(ret, funname, args) \ typedef ret (GLAPICAST proc_ ## funname)args; \ extern proc_ ## funname funname; \ - -#define GLDECL_STATIC(funname) proc_ ## funname funname = NULL + + +#define GLDECL_FUNC(funname) proc_ ## funname funname = NULL +#define GLDECL_FUNC_STATIC(funname) static proc_ ## funname funname = NULL +#define GLDECL_EXTERN(funname) extern proc_ ## funname funname; #if defined GPAC_USE_TINYGL //no extensions with TinyGL @@ -129,8 +145,7 @@ extern proc_ ## funname funname; \ #define LOAD_GL_FUNCS #define GET_GLFUN(funname) funname = (proc_ ## funname) wglGetProcAddress(#funname) #elif defined(CONFIG_DARWIN_GL) -extern void (*glutGetProcAddress(const GLubyte *procname))( void ); -#define GET_GLFUN(funname) funname = (proc_ ## funname) glutGetProcAddress(#funname) +//no extensions with OpenGL on OSX/IOS, glext.h is present #else #define LOAD_GL_FUNCS extern void (*glXGetProcAddress(const GLubyte *procname))( void ); @@ -152,6 +167,10 @@ extern void (*glXGetProcAddress(const GLubyte *procname))( void ); #endif +#if defined(GPAC_USE_GLES1X) || defined(GPAC_USE_GLES2) +# define GPAC_GL_NO_STRIDE +#endif + #if !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_GLES2) /*redefine all ext needed*/ @@ -316,7 +335,15 @@ GLDECL(void, glActiveTexture, (GLenum texture) ) GLDECL(void, glClientActiveTexture, (GLenum texture) ) -GLDECL(void, glBlendEquation, (GLint mode) ) +GLDECL(void, glBlendColor, (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)) +GLDECL(void, glBlendEquation, (GLenum mode)) +GLDECL(void, glBlendEquationSeparate, (GLenum modeRGB, GLenum modeAlpha)) +GLDECL(void, glBlendFuncSeparate, (GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha) ) + +GLDECL(void, glCompressedTexImage2D, (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data) ) +GLDECL(void, glCompressedTexSubImage2D, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data) ) +GLDECL(void, glGenerateMipmap, (GLenum target ) ) + #endif //GL_VERSION_1_3 #ifndef GL_VERSION_1_4 @@ -335,7 +362,7 @@ GLDECL(void, glBlendEquation, (GLint mode) ) GLDECL(void, glPointParameterf, (GLenum , GLfloat) ) GLDECL(void, glPointParameterfv, (GLenum, const GLfloat *) ) -#endif +#endif //GL_VERSION_1_4 @@ -355,7 +382,7 @@ GLDECL(void, glGenBuffers, (GLsizei , GLuint *) ) GLDECL(void, glDeleteBuffers, (GLsizei , GLuint *) ) GLDECL(void, glBindBuffer, (GLenum, GLuint ) ) GLDECL(void, glBufferData, (GLenum, int, void *, GLenum) ) -GLDECL(void, glBufferSubData, (GLenum, int, int, void *) ) +GLDECL(void, glBufferSubData, (GLenum, size_t, int, void *) ) GLDECL(void *, glMapBuffer, (GLenum, GLenum) ) GLDECL(void *, glUnmapBuffer, (GLenum) ) @@ -456,6 +483,113 @@ GLDECL(void *, glUnmapBuffer, (GLenum) ) #define GL_WRITE_ONLY_ARB 0x88B9 #define GL_DYNAMIC_DRAW_ARB 0x88E8 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_TEXTURE_DEPTH_SIZE 0x884A +#define GL_DEPTH_TEXTURE_MODE 0x884B + +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#define GL_COMPARE_R_TO_TEXTURE 0x884E + +#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 +#define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210 +#define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211 +#define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212 +#define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213 +#define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214 +#define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215 +#define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216 +#define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217 +#define GL_FRAMEBUFFER_DEFAULT 0x8218 +#define GL_FRAMEBUFFER_UNDEFINED 0x8219 +#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A +#define GL_MAX_RENDERBUFFER_SIZE 0x84E8 +#define GL_DEPTH_STENCIL 0x84F9 +#define GL_UNSIGNED_INT_24_8 0x84FA +#define GL_DEPTH24_STENCIL8 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE 0x88F1 +#define GL_TEXTURE_RED_TYPE 0x8C10 +#define GL_TEXTURE_GREEN_TYPE 0x8C11 +#define GL_TEXTURE_BLUE_TYPE 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE 0x8C13 +#define GL_TEXTURE_DEPTH_TYPE 0x8C16 +#define GL_UNSIGNED_NORMALIZED 0x8C17 +#define GL_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_DRAW_FRAMEBUFFER_BINDING GL_FRAMEBUFFER_BINDING +#define GL_RENDERBUFFER_BINDING 0x8CA7 +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA +#define GL_RENDERBUFFER_SAMPLES 0x8CAB +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS 0x8CDF +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_COLOR_ATTACHMENT1 0x8CE1 +#define GL_COLOR_ATTACHMENT2 0x8CE2 +#define GL_COLOR_ATTACHMENT3 0x8CE3 +#define GL_COLOR_ATTACHMENT4 0x8CE4 +#define GL_COLOR_ATTACHMENT5 0x8CE5 +#define GL_COLOR_ATTACHMENT6 0x8CE6 +#define GL_COLOR_ATTACHMENT7 0x8CE7 +#define GL_COLOR_ATTACHMENT8 0x8CE8 +#define GL_COLOR_ATTACHMENT9 0x8CE9 +#define GL_COLOR_ATTACHMENT10 0x8CEA +#define GL_COLOR_ATTACHMENT11 0x8CEB +#define GL_COLOR_ATTACHMENT12 0x8CEC +#define GL_COLOR_ATTACHMENT13 0x8CED +#define GL_COLOR_ATTACHMENT14 0x8CEE +#define GL_COLOR_ATTACHMENT15 0x8CEF +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_STENCIL_ATTACHMENT 0x8D20 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_RENDERBUFFER 0x8D41 +#define GL_RENDERBUFFER_WIDTH 0x8D42 +#define GL_RENDERBUFFER_HEIGHT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44 +#define GL_STENCIL_INDEX1 0x8D46 +#define GL_STENCIL_INDEX4 0x8D47 +#define GL_STENCIL_INDEX8 0x8D48 +#define GL_STENCIL_INDEX16 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55 +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 +#define GL_MAX_SAMPLES 0x8D57 + +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_BLEND_COLOR 0x8005 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 + +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB GLDECL(GLuint, glCreateProgram, (void) ) GLDECL(void, glDeleteProgram, (GLuint ) ) @@ -470,6 +604,9 @@ GLDECL(void, glDetachShader, (GLuint program, GLuint shader) ) GLDECL(void, glGetShaderiv, (GLuint shader, GLenum type, GLint *res) ) GLDECL(void, glGetInfoLogARB, (GLuint shader, GLint size, GLsizei *rsize, const char *logs) ) GLDECL(GLint, glGetUniformLocation, (GLuint prog, const char *name) ) +GLDECL(GLint, glGetUniformfv, (GLuint program, GLint location, GLfloat *params)) +GLDECL(GLint, glGetUniformiv, (GLuint program, GLint location, GLint *params)) + GLDECL(void, glUniform1f, (GLint location, GLfloat v0) ) GLDECL(void, glUniform2f, (GLint location, GLfloat v0, GLfloat v1) ) GLDECL(void, glUniform3f, (GLint location, GLfloat v0, GLfloat v1, GLfloat v2) ) @@ -497,14 +634,63 @@ GLDECL(void, glUniformMatrix3x4fv, (GLint location, GLsizei count, GLboolean tra GLDECL(void, glUniformMatrix4x3fv, (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) ) GLDECL(void, glGetProgramiv, (GLuint program, GLenum pname, GLint *params) ) GLDECL(void, glGetProgramInfoLog, (GLuint program, GLsizei maxLength, GLsizei *length, char *infoLog) ) - -#ifndef GPAC_ANDROID +GLDECL(void, glGetShaderInfoLog, (GLuint shader, GLsizei bufSize, GLsizei *length, char *infoLog) ) +GLDECL(void, glGetShaderSource, (GLuint shader, GLsizei bufSize, GLsizei *length, char *source) ) + +GLDECL(void, glGetActiveAttrib, (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, const char *name)) +GLDECL(void, glGetActiveUniform, (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, const char *name)) +GLDECL(void, glGetAttachedShaders, (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *shaders)) +GLDECL(void, glBindAttribLocation, (GLuint program, GLuint index, const char *name)) + +GLDECL(GLboolean, glIsBuffer, (GLuint buffer) ) +GLDECL(GLboolean, glIsFramebuffer, (GLuint framebuffer)) +GLDECL(GLboolean, glIsProgram, (GLuint program)) +GLDECL(GLboolean, glIsRenderbuffer, (GLuint rbuffer)) +GLDECL(GLboolean, glIsShader, (GLuint shader)) +GLDECL(void, glSampleCoverage, (GLclampf value, GLboolean invert) ) +GLDECL(void, glStencilFuncSeparate, (GLenum face, GLenum func, GLint ref, GLuint mask) ) +GLDECL(void, glStencilOpSeparate, (GLenum face, GLenum fail, GLenum zfail, GLenum zpass) ) +GLDECL(void, glStencilMaskSeparate, (GLenum face, GLuint mask) ) +GLDECL(void, glValidateProgram, (GLuint program) ) + +GLDECL(void, glVertexAttrib1fv, (GLuint index, const GLfloat *v) ) +GLDECL(void, glVertexAttrib1f, (GLuint index, GLfloat x)) +GLDECL(void, glVertexAttrib2f, (GLuint index, GLfloat x, GLfloat y)) +GLDECL(void, glVertexAttrib2fv, (GLuint index, const GLfloat *v)) +GLDECL(void, glVertexAttrib3f, (GLuint index, GLfloat x, GLfloat y, GLfloat z)) +GLDECL(void, glVertexAttrib3fv, (GLuint index, const GLfloat *v)) +GLDECL(void, glVertexAttrib4f, (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)) +GLDECL(void, glVertexAttrib4fv, (GLuint index, const GLfloat *v)) + +GLDECL(void, glGetBufferParameteriv, (GLenum target, GLenum pname, GLint *params) ) +GLDECL(void, glGetFramebufferAttachmentParameteriv, (GLenum target, GLenum attachment, GLenum pname, GLint *params) ) + +GLDECL(void, glGetVertexAttribiv, (GLuint index, GLenum pname, GLint *params) ) +GLDECL(void, glGetVertexAttribfv, (GLuint index, GLenum pname, GLfloat *params) ) +GLDECL(void, glGetVertexAttribPointerv, (GLuint index, GLenum pname, GLvoid **pointer) ) + +#ifndef GPAC_CONFIG_ANDROID GLDECL(void, glEnableVertexAttribArray, (GLuint index) ) GLDECL(void, glDisableVertexAttribArray, (GLuint index) ) GLDECL(void, glVertexAttribPointer, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer) ) GLDECL(void, glVertexAttribIPointer, (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid * pointer) ) GLDECL(GLint, glGetAttribLocation, (GLuint prog, const char *name) ) -#endif + +GLDECL(void, glBindFramebuffer, (GLenum target, GLuint framebuffer)) +GLDECL(void, glFramebufferTexture2D, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) +GLDECL(void, glGenFramebuffers, (GLsizei n, GLuint *ids)) +GLDECL(void, glGenRenderbuffers, (GLsizei n, GLuint *renderbuffers)) +GLDECL(void, glBindRenderbuffer, (GLenum target, GLuint renderbuffer)) +GLDECL(void, glRenderbufferStorage, (GLenum target, GLenum internalformat, GLsizei width, GLsizei height)) +GLDECL(void, glFramebufferRenderbuffer, (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)) +GLDECL(void, glDeleteFramebuffers, (GLsizei n, const GLuint * framebuffers)) +GLDECL(void, glDeleteRenderbuffers, (GLsizei n, const GLuint * renderbuffers)) +GLDECL(void, glGetRenderbufferParameteriv, (GLenum target, GLenum pname, GLint *params) ) + +GLDECL(GLenum, glCheckFramebufferStatus, (GLenum target)) + + +#endif //GPAC_CONFIG_ANDROID #endif //GL_VERSION_2_0 diff --git a/src/compositor/hardcoded_protos.c b/src/compositor/hardcoded_protos.c index a21a262..14ad0ef 100644 --- a/src/compositor/hardcoded_protos.c +++ b/src/compositor/hardcoded_protos.c @@ -30,7 +30,6 @@ #include "mpeg4_grouping.h" #include -#include #include "texturing.h" @@ -40,7 +39,7 @@ return GF_FALSE; \ }\ if (field.fieldType != __type) {\ - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[HardcodedProtos] %s field idx %d (%s) is not of type %s\n", __name, field.fieldIndex, field.name, gf_sg_vrml_get_field_type_by_name(__type) ));\ + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[HardcodedProtos] %s field idx %d (%s) is not of type %s\n", __name, field.fieldIndex, field.name, gf_sg_vrml_get_field_type_name(__type) ));\ return GF_FALSE;\ } @@ -99,7 +98,6 @@ static Bool PathExtrusion_GetNode(GF_Node *node, PathExtrusion *path_ext) static void TraversePathExtrusion(GF_Node *node, void *rs, Bool is_destroy) { PathExtrusion path_ext; - Drawable *stack_2d; GF_TraverseState *tr_state = (GF_TraverseState *)rs; Drawable3D *stack = (Drawable3D *)gf_node_get_private(node); @@ -112,6 +110,7 @@ static void TraversePathExtrusion(GF_Node *node, void *rs, Bool is_destroy) if (gf_node_dirty_get(node)) { + Drawable *stack_2d; u32 mode = tr_state->traversing_mode; tr_state->traversing_mode = TRAVERSE_GET_BOUNDS; gf_node_traverse(path_ext.geometry, tr_state); @@ -207,13 +206,6 @@ static Bool PlanarExtrusion_GetNode(GF_Node *node, PlanarExtrusion *path_ext) static void TraversePlanarExtrusion(GF_Node *node, void *rs, Bool is_destroy) { PlanarExtrusion plane_ext; - Drawable *stack_2d; - u32 i, j, k; - MFVec3f spine_vec; - SFVec3f d; - Fixed spine_len; - GF_Rect bounds; - GF_Path *geo, *spine; GF_TraverseState *tr_state = (GF_TraverseState *)rs; Drawable3D *stack = (Drawable3D *)gf_node_get_private(node); @@ -227,8 +219,15 @@ static void TraversePlanarExtrusion(GF_Node *node, void *rs, Bool is_destroy) if (gf_node_dirty_get(node)) { + Drawable *stack_2d; + u32 i, j, k; + MFVec3f spine_vec; + SFVec3f d; + Fixed spine_len; + GF_Rect bounds; u32 cur, nb_pts; u32 mode = tr_state->traversing_mode; + GF_Path *geo, *spine; geo = spine = NULL; tr_state->traversing_mode = TRAVERSE_GET_BOUNDS; @@ -424,7 +423,7 @@ static void TraversePlaneClipper(GF_Node *node, void *rs, Bool is_destroy) if (gf_node_dirty_get(node)) { PlaneClipper_GetNode(node, &stack->pc); - gf_node_dirty_clear(node, 0); + gf_node_dirty_clear(node, GF_SG_NODE_DIRTY); } if (tr_state->num_clip_planes==MAX_USER_CLIP_PLANES) { @@ -635,12 +634,10 @@ static Bool DepthGroup_GetNode(GF_Node *node, DepthGroup *dg) CHECK_FIELD("DepthGroup", 0, GF_SG_VRML_MFNODE); dg->children = *(GF_ChildNodeItem **) field.far_ptr; - CHECK_FIELD("DepthGroup", 1, GF_SG_VRML_SFINT32); - - CHECK_FIELD("DepthGroup", 2, GF_SG_VRML_SFFLOAT); + CHECK_FIELD("DepthGroup", 1, GF_SG_VRML_SFFLOAT); dg->depth_gain = * (SFFloat *) field.far_ptr; - CHECK_FIELD("DepthGroup", 3, GF_SG_VRML_SFFLOAT); + CHECK_FIELD("DepthGroup", 2, GF_SG_VRML_SFFLOAT); dg->depth_offset = * (SFFloat *) field.far_ptr; return GF_TRUE; @@ -912,7 +909,6 @@ static void TraverseUntransform(GF_Node *node, void *rs, Bool is_destroy) memcpy(&backup_cam, tr_state->camera, sizeof(GF_Camera)); - camera_invalidate(tr_state->camera); tr_state->camera->is_3D = GF_FALSE; tr_state->camera->flags |= CAM_NO_LOOKAT; @@ -921,7 +917,7 @@ static void TraverseUntransform(GF_Node *node, void *rs, Bool is_destroy) if (tr_state->traversing_mode == TRAVERSE_SORT) { - visual_3d_set_viewport(tr_state->visual, tr_state->camera->vp); + visual_3d_set_viewport(tr_state->visual, tr_state->camera->proj_vp); visual_3d_projection_matrix_modified(tr_state->visual); gf_node_traverse_children((GF_Node *)&stack->untr, tr_state); @@ -931,7 +927,7 @@ static void TraverseUntransform(GF_Node *node, void *rs, Bool is_destroy) visual_3d_projection_matrix_modified(tr_state->visual); - visual_3d_set_viewport(tr_state->visual, tr_state->camera->vp); + visual_3d_set_viewport(tr_state->visual, tr_state->camera->proj_vp); } else if (tr_state->traversing_mode == TRAVERSE_PICK) { Fixed prev_dist = tr_state->visual->compositor->hit_square_dist; GF_Ray r = tr_state->ray; @@ -1179,6 +1175,7 @@ typedef struct CustomTexture tx; GF_TextureHandler txh; u32 gl_id; + Bool disabled; } CustomTextureStack; static Bool CustomTexture_GetNode(GF_Node *node, CustomTexture *tx) @@ -1213,7 +1210,7 @@ static void TraverseCustomTexture(GF_Node *node, void *rs, Bool is_destroy) static void CustomTexture_update(GF_TextureHandler *txh) { #ifndef GPAC_DISABLE_3D - char data[12]; + u8 data[12]; #endif CustomTextureStack *stack = gf_node_get_private(txh->owner); //alloc texture @@ -1222,6 +1219,8 @@ static void CustomTexture_update(GF_TextureHandler *txh) gf_sc_texture_allocate(txh); if (!txh->tx_io) return; } + if (stack->disabled) return; + #ifndef GPAC_DISABLE_3D //texture not setup, do it if (! gf_sc_texture_get_gl_id(txh)) { @@ -1230,7 +1229,7 @@ static void CustomTexture_update(GF_TextureHandler *txh) //in our case we only want to test openGL so no need to fill in the texture width/height stride //since we will upload ourselves the texture txh->transparent = 0; - txh->pixelformat = GF_PIXEL_RGB_24; + txh->pixelformat = GF_PIXEL_RGB; //signaling we modified associated data (even if no data in our case) to mark texture as dirty gf_sc_texture_set_data(txh); @@ -1250,9 +1249,9 @@ static void CustomTexture_update(GF_TextureHandler *txh) #ifndef GPAC_DISABLE_3D //setup our texture data memset(data, 0, sizeof(char)*12); - data[0] = (char) (0xFF * FIX2FLT(stack->tx.intensity)); //first pixel red modulated by intensity - data[4] = (char) (0xFF * FIX2FLT(stack->tx.intensity)); //second pixel green - data[8] = (char) (0xFF * FIX2FLT(stack->tx.intensity)); //third pixel blue + data[0] = (u8) (0xFF * FIX2FLT(stack->tx.intensity)); //first pixel red modulated by intensity + data[4] = (u8) (0xFF * FIX2FLT(stack->tx.intensity)); //second pixel green + data[8] = (u8) (0xFF * FIX2FLT(stack->tx.intensity)); //third pixel blue //last pixel black glBindTexture( GL_TEXTURE_2D, stack->gl_id); @@ -1276,6 +1275,11 @@ void compositor_init_custom_texture(GF_Compositor *compositor, GF_Node *node) gf_node_set_callback_function(node, TraverseCustomTexture); stack->tx = tx; + if (!gf_sc_check_gl_support(compositor)) { + stack->disabled = GF_TRUE; + GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] Driver disabled, cannot render custom texture test\n")); + return; + } //register texture object gf_sc_texture_setup(&stack->txh, compositor, node); stack->txh.update_texture_fcnt = CustomTexture_update; @@ -1292,7 +1296,7 @@ static void TraverseVRGeometry(GF_Node *node, void *rs, Bool is_destroy) GF_TextureHandler *txh; GF_MediaObjectVRInfo vrinfo; GF_MeshSphereAngles sphere_angles; - + Bool mesh_was_reset = GF_FALSE; GF_TraverseState *tr_state = (GF_TraverseState *)rs; Drawable3D *stack = (Drawable3D *)gf_node_get_private(node); @@ -1310,7 +1314,12 @@ static void TraverseVRGeometry(GF_Node *node, void *rs, Bool is_destroy) if (gf_node_dirty_get(node) || (tr_state->traversing_mode==TRAVERSE_DRAW_3D)) { if (! gf_mo_get_srd_info(txh->stream, &vrinfo)) return; - + + if (vrinfo.has_full_coverage && tr_state->disable_partial_sphere) { + if ((vrinfo.srd_w!=vrinfo.srd_max_x) || (vrinfo.srd_h!=vrinfo.srd_max_y)) + return; + } + sphere_angles.min_phi = -GF_PI2 + GF_PI * vrinfo.srd_y / vrinfo.srd_max_y; sphere_angles.max_phi = -GF_PI2 + GF_PI * (vrinfo.srd_h + vrinfo.srd_y) / vrinfo.srd_max_y; @@ -1322,7 +1331,7 @@ static void TraverseVRGeometry(GF_Node *node, void *rs, Bool is_destroy) mesh_reset(stack->mesh); radius = MAX(vrinfo.scene_width, vrinfo.scene_height) / 4; - //may happen that we don't have an scene width/height, use hardcoded 100 units radius (size actually doesn't matter + //may happen that we don't have a scene width/height, use hardcoded 100 units radius (size actually doesn't matter //since our VP/camera is at the center of the sphere if (!radius) { radius = 100; @@ -1334,67 +1343,120 @@ static void TraverseVRGeometry(GF_Node *node, void *rs, Bool is_destroy) txh->flags &= ~GF_SR_TEXTURE_REPEAT_S; txh->flags &= ~GF_SR_TEXTURE_REPEAT_T; } + mesh_was_reset = GF_TRUE; gf_node_dirty_clear(node, GF_SG_NODE_DIRTY); } if (tr_state->traversing_mode==TRAVERSE_DRAW_3D) { - GF_Vec center, target, ref; - Fixed r, theta_angle, phi_angle; - DrawAspect2D asp; Bool visible = GF_FALSE; - Fixed center_phi = sphere_angles.min_phi + (sphere_angles.max_phi - sphere_angles.min_phi) / 2; - Fixed center_theta = sphere_angles.min_theta + (sphere_angles.max_theta - sphere_angles.min_theta) / 2; - visual_3d_enable_depth_buffer(tr_state->visual, GF_FALSE); + if (! tr_state->camera_was_dirty && !mesh_was_reset) { + visible = (stack->mesh->flags & MESH_WAS_VISIBLE) ? GF_TRUE : GF_FALSE; + } else if ((vrinfo.srd_w==vrinfo.srd_max_x) && (vrinfo.srd_h==vrinfo.srd_max_y)) { + visible = GF_TRUE; + } + else if (txh->compositor->tvtf) { + visible = GF_TRUE; + //estimate visibility asap, even if texture not yet ready (we have SRD info): + //this allows sending stop commands which will free inactive decoder HW context + } else { + u32 i, j; + u32 nb_visible=0; + u32 nb_tests = tr_state->visual->compositor->tvtn; + u32 min_visible_threshold = tr_state->visual->compositor->tvtt; + u32 stride; + + //pick nb_tests vertices spaced every stride in the mesh + stride = stack->mesh->v_count; + stride /= nb_tests; + for (i=0; imesh->vertices[i*stride].pos; + //check the point is in our frustum - don't test far plane + for (j=1; j<6; j++) { + Fixed d = gf_plane_get_distance(&tr_state->camera->planes[j], &pt); + if (d<0) { + vis = GF_FALSE; + break; + } + } + if (vis) { + nb_visible++; + //abort test if more visible points than our threshold + if (nb_visible > min_visible_threshold) + break; + } + } + if (nb_visible > min_visible_threshold) + visible = GF_TRUE; + GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Texture %d Partial sphere is %s - %d sample points visible out of %d\n", txh->stream->OD_ID, visible ? "visible" : "hidden", nb_visible, i)); + } - visual_3d_enable_antialias(tr_state->visual, GF_FALSE); - visual_3d_draw(tr_state, stack->mesh); + if (visible) { + stack->mesh->flags |= MESH_WAS_VISIBLE; + } else { + stack->mesh->flags &= ~MESH_WAS_VISIBLE; + } - visual_3d_enable_depth_buffer(tr_state->visual, GF_TRUE); + if (visible && (vrinfo.srd_w != vrinfo.srd_max_x) && tr_state->visual->compositor->gazer_enabled) { + s32 gx, gy; + tr_state->visual->compositor->hit_node = NULL; + tr_state->visual->compositor->hit_square_dist = 0; + + //gaze coords are 0,0 in top-left + gx = (s32)( tr_state->visual->compositor->gaze_x - tr_state->camera->width/2 ); + gy = (s32)( tr_state->camera->height/2 - tr_state->visual->compositor->gaze_y ); + + visual_3d_setup_ray(tr_state->visual, tr_state, gx, gy); + visual_3d_vrml_drawable_pick(node, tr_state, stack->mesh, NULL); + if (tr_state->visual->compositor->hit_node) { + GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Texture %d Partial sphere is under gaze coord %d %d\n", txh->stream->OD_ID, tr_state->visual->compositor->gaze_x, tr_state->visual->compositor->gaze_y)); + + tr_state->visual->compositor->hit_node = NULL; + } else { + visible = GF_FALSE; + } - /*notify decoder/network stack on whether the geometry was visible or not (maybe a % of what is visible would be nicer)*/ - memset(&asp, 0, sizeof(DrawAspect2D)); - drawable_get_aspect_2d_mpeg4(node, &asp, tr_state); - - center.y = gf_sin(center_phi); - r = gf_sqrt(FIX_ONE - gf_mulfix(center.y, center.y) ); - center.x = gf_mulfix(r, gf_cos(center_theta) ); - center.z = gf_mulfix(r, gf_sin(center_theta) ); - gf_vec_norm(¢er); - - ref = center; - ref.y = 0; - gf_vec_norm(&ref); - target = camera_get_target_dir(tr_state->camera); - target.y=0; - gf_vec_norm(&target); - theta_angle = gf_acos( gf_vec_dot(target, ref) ); - theta_angle-=GF_PI; - if (theta_angle<0) theta_angle = -theta_angle; - - ref = center; - ref.x = 0; - gf_vec_norm(&ref); - target = camera_get_target_dir(tr_state->camera); - target.x=0; - gf_vec_norm(&target); - phi_angle = gf_acos( gf_vec_dot(target, ref) ); - phi_angle-=GF_PI; - if (phi_angle<-GF_PI2) phi_angle += GF_PI; - - if ((theta_angle < tr_state->camera->fieldOfView/2 + (sphere_angles.max_theta-sphere_angles.min_theta)/2) - && (phi_angle < tr_state->camera->fieldOfView/2 + (sphere_angles.max_phi-sphere_angles.min_phi) /2) - ) { - visible = GF_TRUE; } - - GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Texure %d Partial sphere is %s - Angle center-cam is %.02f h %.02f v\n", txh->stream->OD_ID, visible ? "visible" : "hidden", theta_angle, phi_angle)); - if (visible) { - gf_mo_hint_quality_degradation(asp.fill_texture->stream, 0); + if (vrinfo.has_full_coverage) { + if (visible) { + if (!txh->is_open) { + GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Texture %d stoped on visible partial sphere - starting it\n", txh->stream->OD_ID)); + assert(txh->stream && txh->stream->odm); + txh->stream->odm->disable_buffer_at_next_play = GF_TRUE; + + gf_sc_texture_play(txh, NULL); + } + if (txh->data) { + visual_3d_enable_depth_buffer(tr_state->visual, GF_FALSE); + visual_3d_enable_antialias(tr_state->visual, GF_FALSE); + if (!tr_state->visual->compositor->tvtd || (vrinfo.srd_w != vrinfo.srd_max_x)) { + visual_3d_draw(tr_state, stack->mesh); + } + visual_3d_enable_depth_buffer(tr_state->visual, GF_TRUE); + } + } else { + if (txh->is_open) { + GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Texture %d playing on hidden partial sphere - stoping it\n", txh->stream->OD_ID)); + gf_sc_texture_stop_no_unregister(txh); + } + } } else { - gf_mo_hint_quality_degradation(asp.fill_texture->stream, 100); + if (txh->data) { + visual_3d_enable_depth_buffer(tr_state->visual, GF_FALSE); + visual_3d_enable_antialias(tr_state->visual, GF_FALSE); + visual_3d_draw(tr_state, stack->mesh); + visual_3d_enable_depth_buffer(tr_state->visual, GF_TRUE); + } + if (!tr_state->disable_partial_sphere) { + if (visible) { + gf_mo_hint_quality_degradation(txh->stream, 0); + } else { + gf_mo_hint_quality_degradation(txh->stream, 100); + } + } } } } @@ -1410,6 +1472,186 @@ static void compositor_init_vr_geometry(GF_Compositor *compositor, GF_Node *node gf_node_set_callback_function(node, TraverseVRGeometry); } +#define VRHUD_SCALE 6 +static void TraverseVRHUD(GF_Node *node, void *rs, Bool is_destroy) +{ + GF_TraverseState *tr_state = (GF_TraverseState *) rs; + GF_Matrix mv_bck, proj_bck, cam_bck; + /*SFVec3f target;*/ + GF_Rect vp, orig_vp; + u32 mode, i, cull_bck; + Fixed angle_yaw/*, angle_pitch*/; + SFVec3f axis; + GF_Node *subtree = gf_node_get_private(node); + if (is_destroy) return; + + if (!tr_state->camera) return; + mode = tr_state->visual->compositor->vrhud_mode; + if (!mode) return; + + gf_mx_copy(mv_bck, tr_state->model_matrix); + gf_mx_copy(cam_bck, tr_state->camera->modelview); + + tr_state->disable_partial_sphere = GF_TRUE; + /*target = tr_state->camera->target;*/ + orig_vp = tr_state->camera->proj_vp; + +/* + //compute pitch (elevation) + dlen = tr_state->camera->target.x*tr_state->camera->target.x + tr_state->camera->target.z*tr_state->camera->target.z; + dlen = gf_sqrt(dlen); +*/ + + /*angle_pitch = gf_atan2(tr_state->camera->target.y, dlen);*/ + + //compute yaw (rotation Y) + angle_yaw = gf_atan2(tr_state->camera->target.z, tr_state->camera->target.x); + + //compute axis for the pitch + axis = tr_state->camera->target; + axis.y=0; + gf_vec_norm(&axis); + + visual_3d_enable_depth_buffer(tr_state->visual, GF_FALSE); + + if (mode==2) { + //rear mirror, reverse x-axis on projection + tr_state->camera->projection.m[0] *= -1; + visual_3d_projection_matrix_modified(tr_state->visual); + //inverse backface culling + tr_state->reverse_backface = GF_TRUE; + vp = orig_vp; + vp.width/=VRHUD_SCALE; + vp.height/=VRHUD_SCALE; + vp.x = orig_vp.x + (orig_vp.width-vp.width)/2; + vp.y = orig_vp.y + orig_vp.height-vp.height; + + visual_3d_set_viewport(tr_state->visual, vp); + + gf_mx_add_rotation(&tr_state->model_matrix, GF_PI, tr_state->camera->up.x, tr_state->camera->up.y, tr_state->camera->up.z); + gf_node_traverse(subtree, rs); + tr_state->camera->projection.m[0] *= -1; + visual_3d_projection_matrix_modified(tr_state->visual); + } else if (mode==1) { + gf_mx_copy(proj_bck, tr_state->camera->projection); + gf_mx_init(tr_state->camera->modelview); + + //force projection with PI/2 fov and AR 1:1 + tr_state->camera->projection.m[0] = -1; + tr_state->camera->projection.m[5] = 1; + visual_3d_projection_matrix_modified(tr_state->visual); + //force cull inside + cull_bck = tr_state->cull_flag; + tr_state->cull_flag = CULL_INSIDE; + //inverse backface culling + tr_state->reverse_backface = GF_TRUE; + + //draw 3 viewports, each separated by PI/2 rotation + for (i=0; i<3; i++) { + vp = orig_vp; + vp.height/=VRHUD_SCALE; + vp.width=vp.height; + //we reverse X in the projection, so reverse the viewports + vp.x = orig_vp.x + orig_vp.width/2 - 3*vp.width/2 + (3-i-1)*vp.width; + vp.y = orig_vp.y + orig_vp.height - vp.height; + visual_3d_set_viewport(tr_state->visual, vp); + tr_state->disable_cull = GF_TRUE; + + gf_mx_init(tr_state->model_matrix); + gf_mx_add_rotation(&tr_state->model_matrix, angle_yaw-GF_PI, 0, 1, 0); + gf_mx_add_rotation(&tr_state->model_matrix, i*GF_PI2, 0, 1, 0); + + gf_node_traverse(subtree, rs); + } + gf_mx_copy(tr_state->camera->projection, proj_bck); + visual_3d_projection_matrix_modified(tr_state->visual); + tr_state->cull_flag = cull_bck; + gf_mx_copy(tr_state->camera->modelview, cam_bck); + } + else if ((mode==4) || (mode==3)) { + // mirror, reverse x-axis on projection + tr_state->camera->projection.m[0] *= -1; + visual_3d_projection_matrix_modified(tr_state->visual); + //inverse backface culling + tr_state->reverse_backface = GF_TRUE; + + //side left view + vp = orig_vp; + vp.width/=VRHUD_SCALE; + vp.height/=VRHUD_SCALE; + if (mode==3) { + vp.x = orig_vp.x; + } else { + vp.x = orig_vp.x + orig_vp.width/2 - 2*vp.width; + } + vp.y = orig_vp.y + orig_vp.height - vp.height; + visual_3d_set_viewport(tr_state->visual, vp); + + gf_mx_add_rotation(&tr_state->model_matrix, -2*GF_PI/3, 0, 1, 0); + + gf_node_traverse(subtree, rs); + + //side right view + if (mode==3) { + vp.x = orig_vp.x + orig_vp.width - vp.width; + } else { + vp.x = orig_vp.x + orig_vp.width/2+vp.width; + } + visual_3d_set_viewport(tr_state->visual, vp); + + gf_mx_copy(tr_state->model_matrix, mv_bck); + gf_mx_add_rotation(&tr_state->model_matrix, 2*GF_PI/3, 0, 1, 0); + + gf_node_traverse(subtree, rs); + + if (mode==4) { + //upper view + vp.x = orig_vp.x + orig_vp.width/2 - vp.width; + visual_3d_set_viewport(tr_state->visual, vp); + + gf_mx_copy(tr_state->model_matrix, mv_bck); + gf_mx_add_rotation(&tr_state->model_matrix, - GF_PI2, -axis.z, 0, axis.x); + gf_node_traverse(subtree, rs); + + //down view + vp.x = orig_vp.x + orig_vp.width/2; + visual_3d_set_viewport(tr_state->visual, vp); + + gf_mx_copy(tr_state->model_matrix, mv_bck); + gf_mx_add_rotation(&tr_state->model_matrix, GF_PI2, -axis.z, 0, axis.x); + + gf_node_traverse(subtree, rs); + } + + tr_state->camera->projection.m[0] *= -1; + visual_3d_projection_matrix_modified(tr_state->visual); + } + + //restore camera and VP + gf_mx_copy(tr_state->model_matrix, mv_bck); + visual_3d_set_viewport(tr_state->visual, orig_vp); + visual_3d_enable_depth_buffer(tr_state->visual, GF_TRUE); + tr_state->disable_partial_sphere = GF_FALSE; + tr_state->reverse_backface = GF_FALSE; +} + +void compositor_init_vrhud(GF_Compositor *compositor, GF_Node *node) +{ + GF_Node *n; + GF_SceneGraph *sg = gf_node_get_graph(node); + sg = gf_sg_get_parent(sg); + + n = gf_sg_find_node_by_name(sg, "DYN_TRANS"); + if (!n) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Unable to initialize VRHUD group, no main scene\n")); + } else { + gf_node_set_callback_function(node, TraverseVRHUD); + gf_node_proto_set_grouping(node); + gf_node_set_private(node, n); + } +} + + #endif //GPAC_DISABLE_3D /*hardcoded proto loading - this is mainly used for module development and testing...*/ @@ -1429,27 +1671,27 @@ void gf_sc_init_hardcoded_proto(GF_Compositor *compositor, GF_Node *node) if (!url) continue; #ifndef GPAC_DISABLE_3D - if (!strcmp(url, "urn:inet:gpac:builtin:PathExtrusion")) { - compositor_init_path_extrusion(compositor, node); - return; - } - if (!strcmp(url, "urn:inet:gpac:builtin:PlanarExtrusion")) { - compositor_init_planar_extrusion(compositor, node); - return; - } - if (!strcmp(url, "urn:inet:gpac:builtin:PlaneClipper")) { - compositor_init_plane_clipper(compositor, node); - return; - } + if (!strcmp(url, "urn:inet:gpac:builtin:PathExtrusion")) { + compositor_init_path_extrusion(compositor, node); + return; + } + if (!strcmp(url, "urn:inet:gpac:builtin:PlanarExtrusion")) { + compositor_init_planar_extrusion(compositor, node); + return; + } + if (!strcmp(url, "urn:inet:gpac:builtin:PlaneClipper")) { + compositor_init_plane_clipper(compositor, node); + return; + } if (!strcmp(url, "urn:inet:gpac:builtin:VRGeometry")) { compositor_init_vr_geometry(compositor, node); return; } + if (!strcmp(url, "urn:inet:gpac:builtin:VRHUD")) { + compositor_init_vrhud(compositor, node); + return; + } #endif - if (!strcmp(url, "urn:inet:gpac:builtin:TextureText")) { - compositor_init_texture_text(compositor, node); - return; - } if (!strcmp(url, "urn:inet:gpac:builtin:OffscreenGroup")) { compositor_init_offscreen_group(compositor, node); return; @@ -1471,7 +1713,9 @@ void gf_sc_init_hardcoded_proto(GF_Compositor *compositor, GF_Node *node) return; } if (!strcmp(url, "urn:inet:gpac:builtin:FlashShape")) { +#ifdef GPAC_ENABLE_FLASHSHAPE compositor_init_hc_flashshape(compositor, node); +#endif return; } if (!strcmp(url, "urn:inet:gpac:builtin:StyleGroup")) { @@ -1486,7 +1730,7 @@ void gf_sc_init_hardcoded_proto(GF_Compositor *compositor, GF_Node *node) compositor_init_custom_texture(compositor, node); return; } - + /*check proto modules*/ if (compositor->proto_modules) { diff --git a/src/compositor/hc_flash_shape.c b/src/compositor/hc_flash_shape.c index 4e51f05..c65d35e 100644 --- a/src/compositor/hc_flash_shape.c +++ b/src/compositor/hc_flash_shape.c @@ -31,6 +31,8 @@ #include #endif +#ifdef GPAC_ENABLE_FLASHSHAPE + typedef struct { GF_Path *path; @@ -121,7 +123,6 @@ static void build_shape(FSStack *st, GF_Node *node) cur.x = cur.y = 0; fill_item = line_item = NULL; need_line = need_fill = GF_FALSE; - cur.x = cur.y = 0; has_ci = coordIndex->count ? 1 : 0; pts = coords->vals; line_col = fill_col = 0; @@ -441,9 +442,6 @@ static void fs_traverse(GF_Node *node, void *rs, Bool is_destroy) /*finalize*/ #ifndef GPAC_DISABLE_VRML ctx = drawable_init_context_mpeg4(st->drawable, tr_state); -#else - ctx = NULL; -#endif if (!ctx) return; /*force width to max width used for clipper compute*/ @@ -452,6 +450,9 @@ static void fs_traverse(GF_Node *node, void *rs, Bool is_destroy) } drawable_finalize_sort(ctx, tr_state, &st->bounds); break; +#else + return; +#endif } } @@ -474,3 +475,4 @@ void compositor_init_hc_flashshape(GF_Compositor *compositor, GF_Node *node) gf_node_set_callback_function(node, fs_traverse); } +#endif // GPAC_ENABLE_FLASHSHAPE diff --git a/src/terminal/media_object.c b/src/compositor/media_object.c similarity index 51% rename from src/terminal/media_object.c rename to src/compositor/media_object.c index 2a8e5e6..c899e38 100644 --- a/src/terminal/media_object.c +++ b/src/compositor/media_object.c @@ -2,10 +2,10 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2018 * All rights reserved * - * This file is part of GPAC / Media terminal sub-project + * This file is part of GPAC / Scene Compositor sub-project * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -23,14 +23,9 @@ * */ - -#include #include #include -#include #include -#include "media_memory.h" -#include "media_control.h" #include #include @@ -192,7 +187,7 @@ GF_MediaObject *gf_mo_register(GF_Node *node, MFURL *url, Bool lock_timelines, B return res; } - +GF_EXPORT void gf_mo_unregister(GF_Node *node, GF_MediaObject *mo) { if (mo && node) { @@ -217,6 +212,9 @@ Bool gf_mo_get_visual_info(GF_MediaObject *mo, u32 *width, u32 *height, u32 *str { if ((mo->type != GF_MEDIA_OBJECT_VIDEO) && (mo->type!=GF_MEDIA_OBJECT_TEXT)) return GF_FALSE; + if (mo->config_changed) { + gf_mo_update_caps(mo); + } if (width) *width = mo->width; if (height) *height = mo->height; if (stride) *stride = mo->stride; @@ -227,220 +225,189 @@ Bool gf_mo_get_visual_info(GF_MediaObject *mo, u32 *width, u32 *height, u32 *str } GF_EXPORT -void gf_mo_get_nb_views(GF_MediaObject *mo, int * nb_views) +void gf_mo_get_nb_views(GF_MediaObject *mo, u32 *nb_views) { if (mo) *nb_views = mo->nb_views; } GF_EXPORT -void gf_mo_get_nb_layers(GF_MediaObject *mo, int * nb_layers) +void gf_mo_get_nb_layers(GF_MediaObject *mo, u32 *nb_layers) { if (mo) *nb_layers = mo->nb_layers; } GF_EXPORT -Bool gf_mo_get_audio_info(GF_MediaObject *mo, u32 *sample_rate, u32 *bits_per_sample, u32 *num_channels, u32 *channel_config) +Bool gf_mo_get_audio_info(GF_MediaObject *mo, u32 *sample_rate, u32 *bits_per_sample, u32 *num_channels, u64 *channel_config, Bool *forced_layout) { - if (!mo->odm || !mo->odm->codec || (mo->type != GF_MEDIA_OBJECT_AUDIO)) return GF_FALSE; + if (!mo->odm || (mo->type != GF_MEDIA_OBJECT_AUDIO)) return GF_FALSE; + + if (mo->odm->pid && (!mo->sample_rate || !mo->num_channels)) + gf_filter_pid_get_packet(mo->odm->pid); + + if (mo->config_changed) { + gf_mo_update_caps(mo); + } if (sample_rate) *sample_rate = mo->sample_rate; - if (bits_per_sample) *bits_per_sample = mo->bits_per_sample; + if (bits_per_sample) *bits_per_sample = mo->afmt; if (num_channels) *num_channels = mo->num_channels; if (channel_config) *channel_config = mo->channel_config; - return GF_TRUE; -} + if (forced_layout) *forced_layout = GF_FALSE; -static void gf_mo_update_visual_info(GF_MediaObject *mo) -{ - GF_Channel *ch; - GF_NetworkCommand com; - - GF_CodecCapability cap; - if ((mo->type != GF_MEDIA_OBJECT_VIDEO) && (mo->type!=GF_MEDIA_OBJECT_TEXT)) return; - - cap.CapCode = GF_CODEC_WIDTH; - gf_codec_get_capability(mo->odm->codec, &cap); - mo->width = cap.cap.valueInt; - - cap.CapCode = GF_CODEC_HEIGHT; - gf_codec_get_capability(mo->odm->codec, &cap); - mo->height = cap.cap.valueInt; - - if (mo->type==GF_MEDIA_OBJECT_TEXT) return; - - cap.CapCode = GF_CODEC_FLIP; - cap.cap.valueInt = 0; - gf_codec_get_capability(mo->odm->codec, &cap); - mo->is_flipped = cap.cap.valueInt ? GF_TRUE : GF_FALSE; + if (mo->odm->ambi_ch_id) { + if (mo->num_channels>1) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[ODM%d]: tagged as ambisonic channel %d but has %d channels, ignoring ambisonic tag\n", mo->odm->ID, mo->odm->ambi_ch_id, mo->num_channels )); + } else { + if (num_channels) *num_channels = 1; + if (channel_config) *channel_config = (u64) ( 1 << (mo->odm->ambi_ch_id - 1) ); + if (forced_layout) *forced_layout = GF_TRUE; - cap.CapCode = GF_CODEC_STRIDE; - gf_codec_get_capability(mo->odm->codec, &cap); - mo->stride = cap.cap.valueInt; + } + } - cap.CapCode = GF_CODEC_PIXEL_FORMAT; - gf_codec_get_capability(mo->odm->codec, &cap); - mo->pixelformat = cap.cap.valueInt; + return GF_TRUE; +} - cap.CapCode = GF_CODEC_FPS; - gf_codec_get_capability(mo->odm->codec, &cap); - mo->odm->codec->fps = cap.cap.valueFloat; - cap.CapCode = GF_CODEC_NBVIEWS; - gf_codec_get_capability(mo->odm->codec, &cap); - mo->nb_views = cap.cap.valueInt; +void gf_mo_update_caps(GF_MediaObject *mo) +{ + Bool changed = GF_FALSE; + const GF_PropertyValue *v, *v2; + if (!mo->odm || !mo->odm->pid) return; + + mo->planar_audio = GF_FALSE; + +#define UPDATE_CAP(_code, _field) \ + v = gf_filter_pid_get_property(mo->odm->pid, _code);\ + if (v) {\ + if (mo->_field && (mo->_field != v->value.uint)) changed=GF_TRUE;\ + mo->_field = v->value.uint;\ + }\ + + if (mo->odm->type==GF_STREAM_VISUAL) { + + UPDATE_CAP(GF_PROP_PID_WIDTH, width) + UPDATE_CAP(GF_PROP_PID_HEIGHT, height) + UPDATE_CAP(GF_PROP_PID_STRIDE, stride) + UPDATE_CAP(GF_PROP_PID_PIXFMT, pixelformat) + UPDATE_CAP(GF_PROP_PID_BITRATE, bitrate) + + v = gf_filter_pid_get_property(mo->odm->pid, GF_PROP_PID_SAR); + if (v) { + u32 n_par = (v->value.frac.num) << 16 | (v->value.frac.den); + if (mo->pixel_ar && (mo->pixel_ar!=n_par)) changed = GF_TRUE; + mo->pixel_ar = n_par; + } - cap.CapCode = GF_CODEC_NBLAYERS; - gf_codec_get_capability(mo->odm->codec, &cap); - mo->nb_layers = cap.cap.valueInt; + v = gf_filter_pid_get_property(mo->odm->pid, GF_PROP_PID_SRD); + v2 = gf_filter_pid_get_property(mo->odm->pid, GF_PROP_PID_SRD_REF); + if (v && v->value.vec4i.w && v->value.vec4i.z) { + mo->srd_x = v->value.vec4i.x; + mo->srd_y = v->value.vec4i.y; + mo->srd_w = v->value.vec4i.z; + mo->srd_h = v->value.vec4i.w; + if (v2) { + mo->srd_full_w = v2->value.vec2i.x; + mo->srd_full_h = v2->value.vec2i.y; + } - if (mo->odm && mo->odm->parentscene->is_dynamic_scene) { -#ifndef GPAC_DISABLE_VRML - const char *name = gf_node_get_name(gf_event_target_get_node(gf_mo_event_target_get(mo, 0))); - if (name && !strcmp(name, "DYN_VIDEO1")) { - const char *opt; - u32 r, g, b, a; - M_Background2D *back = (M_Background2D *) gf_sg_find_node_by_name(mo->odm->parentscene->graph, "DYN_BACK"); - if (back) { - switch (cap.cap.valueInt) { - case GF_PIXEL_ARGB: - case GF_PIXEL_RGBA: - case GF_PIXEL_YUVA: - opt = gf_cfg_get_key(mo->odm->term->user->config, "Compositor", "BackColor"); - if (!opt) { - gf_cfg_set_key(mo->odm->term->user->config, "Compositor", "BackColor", "FF999999"); - opt = "FF999999"; + if (mo->odm->parentscene->is_dynamic_scene) { + u32 old_type = mo->odm->parentscene->srd_type; + if ((mo->srd_w == mo->srd_full_w) && (mo->srd_h == mo->srd_full_h)) { + mo->odm->parentscene->srd_type = 2; + } else if (!mo->odm->parentscene->srd_type) { + mo->odm->parentscene->srd_type = 1; + } + if (old_type != mo->odm->parentscene->srd_type) { + //reset scene graph but prevent object stop/start + u32 i, count = gf_list_count(mo->odm->parentscene->scene_objects); + for (i=0; iodm->parentscene->scene_objects, i); + an_mo->num_open++; } - sscanf(opt, "%02X%02X%02X%02X", &a, &r, &g, &b); - back->backColor.red = INT2FIX(r)/255; - back->backColor.green = INT2FIX(g)/255; - back->backColor.blue = INT2FIX(b)/255; - break; - default: - back->backColor.red = back->backColor.green = back->backColor.blue = 0; - break; + gf_sg_reset(mo->odm->parentscene->graph); + for (i=0; iodm->parentscene->scene_objects, i); + an_mo->num_open--; + } + gf_scene_regenerate(mo->odm->parentscene); } - gf_node_dirty_set((GF_Node *)back, 0, GF_TRUE); - } - } -#endif - } - /*get PAR settings*/ - cap.CapCode = GF_CODEC_PAR; - gf_codec_get_capability(mo->odm->codec, &cap); - mo->pixel_ar = cap.cap.valueInt; - if (! (mo->pixel_ar & 0x0000FFFF)) mo->pixel_ar = 0; - if (! (mo->pixel_ar & 0xFFFF0000)) mo->pixel_ar = 0; - - memset(&com, 0, sizeof(GF_NetworkCommand)); - ch = (GF_Channel *)gf_list_get(mo->odm->channels, 0); - if (!ch) return; - com.base.on_channel = ch; - - /**/ - if (! mo->pixel_ar) { - com.base.command_type = GF_NET_CHAN_GET_PIXEL_AR; - if (gf_term_service_command(ch->service, &com) == GF_OK) { - if ((com.par.hSpacing>65535) || (com.par.vSpacing>65535)) { - com.par.hSpacing>>=16; - com.par.vSpacing>>=16; - } - if (com.par.hSpacing|| com.par.vSpacing) - mo->pixel_ar = (com.par.hSpacing<<16) | com.par.vSpacing; - } - } - - com.base.command_type = GF_NET_CHAN_GET_SRD; - if ((gf_term_service_command(ch->service, &com) == GF_OK)) { - //regular SRD object - if (com.srd.w && com.srd.h) { - mo->srd_x = com.srd.x; - mo->srd_y = com.srd.y; - mo->srd_w = com.srd.w; - mo->srd_h = com.srd.h; - mo->srd_full_w = com.srd.width; - mo->srd_full_h = com.srd.height; - - if (mo->odm->parentscene->is_dynamic_scene && !mo->odm->parentscene->is_srd) { - mo->odm->parentscene->is_srd = GF_TRUE; } } - // SRD object with no size but global scene size: HEVC tiled bas object - else if (com.srd.width && com.srd.height) { - if (mo->odm->parentscene->is_dynamic_scene && !mo->odm->parentscene->is_srd) { + // SRD object with no size but global scene size: HEVC tiled based object + else if (v2 && v2->value.vec2i.x && v2->value.vec2i.y) { + if (mo->odm->parentscene->is_dynamic_scene && !mo->odm->parentscene->srd_type) { mo->odm->parentscene->is_tiled_srd = GF_TRUE; + mo->srd_full_w = v2->value.vec2i.x; + mo->srd_full_h = v2->value.vec2i.y; } } - } -} + } else if (mo->odm->type==GF_STREAM_AUDIO) { + UPDATE_CAP(GF_PROP_PID_SAMPLE_RATE, sample_rate) + UPDATE_CAP(GF_PROP_PID_NUM_CHANNELS, num_channels) + UPDATE_CAP(GF_PROP_PID_AUDIO_FORMAT, afmt) + else mo->afmt = GF_AUDIO_FMT_S16; + + v = gf_filter_pid_get_property(mo->odm->pid, GF_PROP_PID_CHANNEL_LAYOUT); + if (v) { + if (mo->channel_config && (mo->channel_config!=v->value.longuint)) changed = GF_TRUE; + mo->channel_config = v->value.longuint; + } -static void gf_mo_update_audio_info(GF_MediaObject *mo) -{ - GF_CodecCapability cap; - if (!mo->odm || !mo->odm->codec || (mo->type != GF_MEDIA_OBJECT_AUDIO)) return; - - if (mo->odm->term->bench_mode==2) { - mo->sample_rate = 44100; - mo->bits_per_sample = 16; - mo->num_channels = 2; - mo->channel_config = 0; - return; + mo->bytes_per_sec = gf_audio_fmt_bit_depth(mo->afmt) * mo->num_channels * mo->sample_rate / 8; + mo->planar_audio = gf_audio_fmt_is_planar(mo->afmt); + } else if (mo->odm->type==GF_STREAM_OD) { + //nothing to do + } else if (mo->odm->type==GF_STREAM_OCR) { + //nothing to do + } else if (mo->odm->type==GF_STREAM_SCENE) { + //nothing to do + } else if (mo->odm->type==GF_STREAM_TEXT) { + //nothing to do + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("Unknwon scene object type %d\n", mo->odm->type)); } - cap.CapCode = GF_CODEC_SAMPLERATE; - gf_codec_get_capability(mo->odm->codec, &cap); - mo->sample_rate = cap.cap.valueInt; - - cap.CapCode = GF_CODEC_NB_CHAN; - gf_codec_get_capability(mo->odm->codec, &cap); - mo->num_channels = cap.cap.valueInt; - - cap.CapCode = GF_CODEC_BITS_PER_SAMPLE; - gf_codec_get_capability(mo->odm->codec, &cap); - mo->bits_per_sample = cap.cap.valueInt; - cap.CapCode = GF_CODEC_CHANNEL_CONFIG; - gf_codec_get_capability(mo->odm->codec, &cap); - mo->channel_config = cap.cap.valueInt; - - mo->odm->codec->bytes_per_sec = mo->sample_rate * mo->num_channels * mo->bits_per_sample / 8; -} - -void gf_mo_update_caps(GF_MediaObject *mo) -{ - mo->flags &= ~GF_MO_IS_INIT; - - if (mo->type == GF_MEDIA_OBJECT_VIDEO) { - gf_mo_update_visual_info(mo); - } else if (mo->type == GF_MEDIA_OBJECT_AUDIO) { - gf_mo_update_audio_info(mo); + if (changed) { + GF_Event evt; + GF_Scene *scene = mo->odm->subscene ? mo->odm->subscene : mo->odm->parentscene; + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_QUALITY_SWITCHED; + gf_sc_send_event(scene->compositor, &evt); } } -static Bool gf_odm_check_cb_resize(GF_MediaObject *mo, GF_Codec *codec) +static u64 convert_ts_to_ms(GF_MediaObject *mo, u64 ts, u32 timescale, Bool *discard) { - /*resize requested - if last frame destroy CB and force a decode*/ - if (codec->force_cb_resize && (codec->CB->UnitCount<=1)) { - GF_CMUnit *CU = gf_cm_get_output(codec->CB); - if (!CU || (CU->TS==mo->timestamp)) { - GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[%s] ODM%d: Resizing output buffer %d -> %d\n", codec->decio->module_name, codec->odm->OD->objectDescriptorID, codec->CB->UnitSize, codec->force_cb_resize)); - gf_codec_resize_composition_buffer(codec, codec->force_cb_resize); - codec->force_cb_resize=0; - //decode and resize CB - return GF_TRUE; + if (mo->odm->delay) { + if (mo->odm->delay >= 0) { + ts += mo->odm->delay; + } else if (ts < (u64) -mo->odm->delay) { + *discard = GF_TRUE; + return 0; + } else { + ts -= -mo->odm->delay; } } - return GF_FALSE; + ts *= 1000; + ts /= timescale; + return ts; } GF_EXPORT -char *gf_mo_fetch_data(GF_MediaObject *mo, GF_MOFetchMode resync, u32 upload_time_ms, Bool *eos, u32 *timestamp, u32 *size, s32 *ms_until_pres, s32 *ms_until_next, GF_MediaDecoderFrame **outFrame) +u8 *gf_mo_fetch_data(GF_MediaObject *mo, GF_MOFetchMode resync, u32 upload_time_ms, Bool *eos, u32 *timestamp, u32 *size, s32 *ms_until_pres, s32 *ms_until_next, GF_FilterFrameInterface **outFrame, u32 *planar_size) { - GF_Codec *codec; + Bool discard=GF_FALSE; u32 force_decode_mode = 0; - u32 obj_time; - GF_CMUnit *CU; - s32 diff; - Bool bench_mode, skip_resync; + u32 obj_time, obj_time_orig; + s64 diff; + Bool skip_resync; + u32 timescale=0; + u64 pck_ts=0, next_ts=0; + u32 retry_pull; + Bool is_first = GF_FALSE; + Bool move_to_next_only = GF_FALSE; *eos = GF_FALSE; *timestamp = mo->timestamp; @@ -449,228 +416,342 @@ char *gf_mo_fetch_data(GF_MediaObject *mo, GF_MOFetchMode resync, u32 upload_tim if (ms_until_next) *ms_until_next = mo->ms_until_next; if (outFrame) *outFrame = NULL; - if (!gf_odm_lock_mo(mo)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] ODM %d: Failed to lock MO, returning\n", mo->odm ? mo->odm->OD->objectDescriptorID : 0)); + if (!mo->odm || !mo->odm->pid) return NULL; - } - - if (!mo->odm->codec || !mo->odm->codec->CB) { - gf_odm_lock(mo->odm, 0); - return NULL; - } /*if frame locked return it*/ if (mo->nb_fetch) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] ODM %d: CU already fetched, returning\n", mo->odm->OD->objectDescriptorID)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] ODM %d: CU already fetched, returning\n", mo->odm->ID)); mo->nb_fetch ++; - gf_odm_lock(mo->odm, 0); + if (planar_size) *planar_size = mo->framesize / mo->num_channels; return mo->frame; } - codec = mo->odm->codec; - - /*end of stream */ - *eos = gf_cm_is_eos(codec->CB); - /*not running and no resync (ie audio)*/ - if (!resync && !gf_cm_is_running(codec->CB)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] ODM %d: CB not running, returning\n", mo->odm->OD->objectDescriptorID)); - gf_odm_lock(mo->odm, 0); - return NULL; + if (mo->pck && mo->frame_ifce && (mo->frame_ifce->flags & GF_FRAME_IFCE_BLOCKING) ) { + gf_filter_pck_unref(mo->pck); + mo->pck = NULL; } - if (resync==GF_MO_FETCH_PAUSED) - resync=GF_MO_FETCH; - bench_mode = mo->odm->term->bench_mode; - - /*resize requested - if last frame destroy CB and force a decode*/ - if (gf_odm_check_cb_resize(mo, codec)) { - force_decode_mode = 1; + if ( gf_odm_check_buffering(mo->odm, NULL) ) { + //if buffering, first frame fetched and still buffering return + if (mo->first_frame_fetched && mo->odm->nb_buffering) + return NULL; } - /*fast forward, bench mode with composition memory: force one decode if no data is available*/ - if (! *eos && ((codec->ck->speed > FIX_ONE) || (codec->odm->term->bench_mode && !codec->CB->no_allocation) || (codec->type==GF_STREAM_AUDIO) ) ) { - force_decode_mode = 2; +retry: + discard = GF_FALSE; + if (!mo->pck) { + mo->pck = gf_filter_pid_get_packet(mo->odm->pid); + if (!mo->pck) { + if (gf_filter_pid_is_eos(mo->odm->pid)) { + if (!mo->is_eos) { + mo->is_eos = GF_TRUE; + mediasensor_update_timing(mo->odm, GF_TRUE); + gf_odm_on_eos(mo->odm, mo->odm->pid); + gf_odm_signal_eos_reached(mo->odm); + } + } else { + mo->odm->ck->has_seen_eos = GF_FALSE; + } + *eos = mo->is_eos; + return NULL; + } else { + gf_filter_pck_ref(&mo->pck); + gf_filter_pid_drop_packet(mo->odm->pid); + } + is_first = GF_TRUE; } + assert(mo->pck); + mo->first_frame_fetched = GF_TRUE; + mo->is_eos = GF_FALSE; - if (force_decode_mode) { - u32 retry = 100; - gf_odm_lock(mo->odm, 0); - while (retry) { - gf_odm_check_cb_resize(mo, codec); - - if (gf_term_lock_codec(codec, GF_TRUE, GF_TRUE)) { - gf_codec_process(codec, 1); - gf_term_lock_codec(codec, GF_FALSE, GF_TRUE); - } -// if (force_decode_mode==2) break; - CU = gf_cm_get_output(codec->CB); - if (CU) - break; - - retry--; - //we will wait max 100 ms for the CB to be re-fill - gf_sleep(1); - } - if (!retry && (force_decode_mode==1)) { - GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[ODM%d] At %d could not resize, decode and fetch next frame in 100 ms - blank frame after TS %u\n", mo->odm->OD->objectDescriptorID, gf_clock_time(codec->ck), mo->timestamp)); - } - if (!gf_odm_lock_mo(mo)) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] ODM %d: Failed to lock ODM\n", mo->odm->OD->objectDescriptorID)); + /*not running and no resync (ie audio)*/ + if (!gf_clock_is_started(mo->odm->ck)) { + if (!resync) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] ODM %d: CB not running, returning\n", mo->odm->ID)); + return NULL; + } else if (mo->odm->ck->nb_buffering && mo->odm->type==GF_STREAM_AUDIO) { return NULL; } } - /*new frame to fetch, lock*/ - CU = gf_cm_get_output(codec->CB); - /*no output*/ - if (!CU || (CU->RenderedLength == CU->dataLength)) { - gf_odm_lock(mo->odm, 0); - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] No CU available\n", mo->odm->OD->objectDescriptorID)); - return NULL; + /*data = */gf_filter_pck_get_data(mo->pck, size); + timescale = gf_filter_pck_get_timescale(mo->pck); + + pck_ts = convert_ts_to_ms(mo, gf_filter_pck_get_cts(mo->pck), timescale, &discard); + if (discard) { + gf_filter_pck_unref(mo->pck); + mo->pck = NULL; + goto retry; } - /*note this assert is NOT true when recomputing DTS from CTS on the fly (MPEG1/2 RTP and H264/AVC RTP)*/ - //assert(CU->TS >= codec->CB->LastRenderedTS); + if (resync==GF_MO_FETCH_PAUSED) + resync=GF_MO_FETCH; - if (codec->CB->UnitCount<=1) resync = GF_MO_FETCH; + retry_pull = 1; + /*fast forward, bench mode with composition memory: force one decode if no data is available*/ + if (! *eos && ((mo->odm->ck->speed > FIX_ONE) || mo->odm->parentscene->compositor->bench_mode || (mo->odm->type==GF_STREAM_AUDIO) ) ) { + retry_pull = 10; + force_decode_mode=1; + } - if (bench_mode && resync) { - resync = GF_MO_FETCH; - if (mo->timestamp == CU->TS) { - if (CU->next->dataLength) { - gf_cm_drop_output(codec->CB); - CU = gf_cm_get_output(codec->CB); + while (retry_pull) { + retry_pull--; + next_ts = 0; + if (gf_filter_pid_get_first_packet_cts(mo->odm->pid, &next_ts) ) { + next_ts = 1 + convert_ts_to_ms(mo, next_ts, timescale, &discard); + break; + } else { + if (gf_filter_pid_is_eos(mo->odm->pid)) { + if (!mo->is_eos) { + mo->is_eos = GF_TRUE; + *eos = mo->is_eos; + mediasensor_update_timing(mo->odm, GF_TRUE); + gf_odm_on_eos(mo->odm, mo->odm->pid); + force_decode_mode=0; + } + break; } } - } + *eos = mo->is_eos; + if (!retry_pull) break; + gf_filter_pid_try_pull(mo->odm->pid); + } + if (!retry_pull && (force_decode_mode==1)) { + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[ODM%d] At %d could not force a pull from pid - POTENTIAL blank frame after TS %u\n", mo->odm->ID, gf_clock_time(mo->odm->ck), mo->timestamp)); + } /*resync*/ - obj_time = gf_clock_time(codec->ck); - - skip_resync = GF_FALSE; - //no drop mode, only for speed = 1: all frames are presented, we discard the current output only if already presented and next frame time is mature - if ((codec->ck->speed == FIX_ONE) && (mo->type==GF_MEDIA_OBJECT_VIDEO) && !(codec->flags & GF_ESM_CODEC_IS_LOW_LATENCY) ) { + obj_time = obj_time_orig = gf_clock_time(mo->odm->ck); + + if (mo->odm->prev_clock_at_discontinuity_plus_one) { + s32 diff_new, diff_old, diff_pck_old, diff_pck_new; + s32 old_timebase_time = (s32) obj_time; + old_timebase_time -= (s32) mo->odm->ck->init_timestamp; + old_timebase_time += (s32) mo->odm->prev_clock_at_discontinuity_plus_one; + diff_new = (s32) obj_time; + diff_new -= mo->last_fetch_time; + if (diff_new < 0) diff_new = -diff_new; + diff_old = (s32) old_timebase_time; + diff_old -= mo->last_fetch_time; + if (diff_old < 0) diff_old = -diff_old; + + diff_pck_old = (s32) pck_ts - (s32) old_timebase_time; + diff_pck_new = (s32) pck_ts - (s32) obj_time; + if (ABS(diff_pck_old) > ABS(diff_pck_new)) { + //don't reset discontinuity flag for audio + if (resync>GF_MO_FETCH) { + GF_LOG(GF_LOG_INFO, GF_LOG_SYNC, ("[ODM%d] end of clock discontinuity: diff pck TS to old clock %d to new clock %d\n", mo->odm->ID, diff_pck_old, diff_pck_new)); + mo->odm->prev_clock_at_discontinuity_plus_one = 0; + } + } else if (diff_old < diff_new) { + GF_LOG(GF_LOG_INFO, GF_LOG_SYNC, ("[ODM%d] in clock discontinuity: time since fetch old clock %d new clock %d\n", mo->odm->ID, diff_old, diff_new)); + obj_time = old_timebase_time; + } + } - if (!(mo->odm->term->flags & GF_TERM_DROP_LATE_FRAMES)) { - //if the next AU is at most 1 sec from the current clock use no drop mode -// GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] At %d frame TS %u next frame TS %d next data length %d (%d in CB)\n", mo->odm->OD->objectDescriptorID, obj_time, CU->TS, CU->next->TS, CU->next->dataLength, codec->CB->UnitCount)); - if (CU->next->dataLength && (CU->next->TS + 1000 >= obj_time)) { + skip_resync = mo->odm->parentscene->compositor->bench_mode ? GF_TRUE : GF_FALSE; + //no drop mode, only for speed = 1: all frames are presented, we discard the current output only if already presented and next frame time is mature + if ((mo->odm->ck->speed == FIX_ONE) + && (mo->type==GF_MEDIA_OBJECT_VIDEO) + //if no buffer playout we are in low latency configuration, don"t skip resync + && mo->odm->buffer_playout_us + ) { + assert(mo->odm->parentscene); + if (! mo->odm->parentscene->compositor->drop) { + if (mo->odm->parentscene->compositor->force_late_frame_draw) { + mo->flags |= GF_MO_IN_RESYNC; + } + else if (mo->flags & GF_MO_IN_RESYNC) { + if (next_ts >= 1 + obj_time) { + skip_resync = GF_TRUE; + mo->flags &= ~GF_MO_IN_RESYNC; + } + } + else if (next_ts && (next_ts < pck_ts) ) { skip_resync = GF_TRUE; - } else { - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] At %u frame TS %u next frame TS %d too late in no-drop mode, enabling drop - resync mode %d\n", mo->odm->OD->objectDescriptorID, obj_time, CU->TS, CU->next->TS, resync)); + } + //if the next AU is at most 300 ms from the current clock use no drop mode + else if (next_ts + 300 >= obj_time) { + skip_resync = GF_TRUE; + } else if (next_ts) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_SYNC, ("[ODM%d] At %u frame TS %u next frame TS %d too late in no-drop mode, enabling drop - resync mode %d\n", mo->odm->ID, obj_time, pck_ts, next_ts, resync)); + mo->flags |= GF_MO_IN_RESYNC; } } } if (skip_resync) { - resync=GF_MO_FETCH; - if (mo->odm->term->use_step_mode) upload_time_ms=0; + resync=GF_MO_FETCH; //prevent resync code below + if (mo->odm->parentscene->compositor->use_step_mode) upload_time_ms=0; + //we are in no resync mode, drop current frame once played and object time just matured //do it only if clock is started or if compositor step mode is set - //the time threshold for fecthing is given by the caller - if ( (gf_clock_is_started(codec->ck) || mo->odm->term->use_step_mode) - - && (mo->timestamp==CU->TS) && CU->next->dataLength && (CU->next->TS <= obj_time + upload_time_ms) ) { - - gf_cm_drop_output(codec->CB); - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] Switching to next CU CTS %u now %u\n", mo->odm->OD->objectDescriptorID, CU->next->TS, obj_time)); - CU = gf_cm_get_output(codec->CB); - if (!CU) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] No next CU available, skipping\n", mo->odm->OD->objectDescriptorID)); - gf_odm_lock(mo->odm, 0); - return NULL; - } + //the time threshold for fetching is given by the caller + if ( (gf_clock_is_started(mo->odm->ck) || mo->odm->parentscene->compositor->use_step_mode) + && (mo->timestamp==pck_ts) && next_ts && ( (next_ts <= 1 + obj_time + upload_time_ms) || (next_ts <= 1 + obj_time_orig + upload_time_ms) ) ) + { + //drop current and go to next - we use the same loop as regular resync below + resync = GF_MO_FETCH_RESYNC; + move_to_next_only = GF_TRUE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] Switching to CU CTS %u (next %d) now %u\n", mo->odm->ID, pck_ts, next_ts, obj_time)); } } - - if (resync) { + if (resync!=GF_MO_FETCH) { u32 nb_dropped = 0; - while (1) { - if (codec->ck->speed > 0 ? CU->TS >= obj_time : CU->TS <= obj_time ) - break; + while (next_ts) { + if (!move_to_next_only) { + if (mo->odm->ck->speed > 0 ? pck_ts >= obj_time : pck_ts <= obj_time ) + break; - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] Try to drop frame TS %u next frame TS %u length %d obj time %u\n", mo->odm->OD->objectDescriptorID, CU->TS, CU->next->TS, CU->next->dataLength, obj_time)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] Try to drop frame TS %u next frame TS %u obj time %u\n", mo->odm->ID, pck_ts, next_ts, obj_time)); - if (!CU->next->dataLength) { - if (force_decode_mode) { - obj_time = gf_clock_time(codec->ck); - gf_odm_lock(mo->odm, 0); - if (gf_term_lock_codec(codec, GF_TRUE, GF_TRUE)) { - gf_codec_process(codec, 1); - gf_term_lock_codec(codec, GF_FALSE, GF_TRUE); - } - gf_odm_lock(mo->odm, 1); - if (!CU->next->dataLength) { - break; - } - } else { + //nothing ready yet + if ( gf_filter_pid_first_packet_is_empty(mo->odm->pid) ) { break; } - } - /*figure out closest time*/ - if (codec->ck->speed > 0 ? CU->next->TS > obj_time : CU->next->TS < obj_time) { - *eos = GF_FALSE; - break; + /*figure out closest time*/ + if (mo->odm->ck->speed > 0 ? next_ts > obj_time : next_ts < obj_time) { + *eos = GF_FALSE; + break; + } + + nb_dropped ++; + if (nb_dropped>=1) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] At OTB %u dropped frame TS %u\n", mo->odm->ID, obj_time, pck_ts)); + + mo->odm->nb_dropped++; + } } - nb_dropped ++; - if (nb_dropped>=1) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] At OTB %u dropped frame TS %u\n", mo->odm->OD->objectDescriptorID, obj_time, CU->TS)); - codec->nb_dropped++; + //delete our packet + gf_filter_pck_unref(mo->pck); + mo->pck = gf_filter_pid_get_packet(mo->odm->pid); + gf_filter_pck_ref( &mo->pck); + + pck_ts = convert_ts_to_ms(mo, gf_filter_pck_get_cts(mo->pck), timescale, &discard); + //drop next packet from pid + gf_filter_pid_drop_packet(mo->odm->pid); + + if (obj_time != obj_time_orig) { + s32 diff_pck_old = (s32) pck_ts - (s32) obj_time; + s32 diff_pck_new = (s32) pck_ts - (s32) obj_time_orig; + + if (ABS(diff_pck_old) > ABS(diff_pck_new)) { + GF_LOG(GF_LOG_INFO, GF_LOG_SYNC, ("[ODM%d] end of clock discontinuity, moving from old time base %d to new %d\n", mo->odm->ID, obj_time, obj_time_orig)); + obj_time = obj_time_orig; + mo->odm->prev_clock_at_discontinuity_plus_one = 0; + } } - /*discard*/ - CU->RenderedLength = CU->dataLength = 0; - gf_cm_drop_output(codec->CB); - /*get next*/ - CU = gf_cm_get_output(codec->CB); - *eos = gf_cm_is_eos(codec->CB); + next_ts = 0; + if (gf_filter_pid_get_first_packet_cts(mo->odm->pid, &next_ts)) { + next_ts = convert_ts_to_ms(mo, next_ts, timescale, &discard); + } + if (move_to_next_only) + break; } } - mo->framesize = CU->dataLength - CU->RenderedLength; - mo->frame = CU->data + CU->RenderedLength; - mo->media_frame = CU->frame; - if (CU->next->dataLength) { - diff = (s32) (CU->next->TS) - (s32) obj_time; - } else { - diff = mo->odm->codec->min_frame_dur; + mo->frame = (char *) gf_filter_pck_get_data(mo->pck, &mo->size); + mo->framesize = mo->size - mo->RenderedLength; + + //planar mode, RenderedLength correspond to all channels, so move frame pointer + //to first sample non consumed = RenderedLength/nb_channels + if (mo->planar_audio) { + mo->frame += mo->RenderedLength / mo->num_channels; + } else { + mo->frame += mo->RenderedLength; } -// fprintf(stderr, "diff is %d ms\n", diff); - mo->ms_until_next = FIX2INT(diff * mo->speed); + mo->frame_ifce = gf_filter_pck_get_frame_interface(mo->pck); +// mo->media_frame = CU->frame; - diff = (mo->speed >= 0) ? (s32) (CU->TS) - (s32) obj_time : (s32) obj_time - (s32) (CU->TS); + diff = (s32) ( (mo->speed >= 0) ? ( (s64) pck_ts - (s64) obj_time) : ( (s64) obj_time - (s64) pck_ts) ); mo->ms_until_pres = FIX2INT(diff * mo->speed); + if (mo->is_eos) { + diff = 1000*gf_filter_pck_get_duration(mo->pck) / timescale; + if (!diff) diff = 100; + } else { + diff = next_ts ? next_ts : (pck_ts + 1000*gf_filter_pck_get_duration(mo->pck) / timescale); + diff = (s32) ( (mo->speed >= 0) ? ( (s64) diff - (s64) obj_time) : ( (s64) obj_time - (s64) diff) ); - if (mo->timestamp != CU->TS) { -#ifndef GPAC_DISABLE_VRML - if (! gf_cm_is_eos(codec->CB)) - mediasensor_update_timing(mo->odm, 0); -#endif + mo->odm->ck->has_seen_eos = GF_FALSE; + } + mo->ms_until_next = FIX2INT(diff * mo->speed); + if (mo->ms_until_next < 0) + mo->ms_until_next = 0; + + //safe guard + if (mo->ms_until_next>500) + mo->ms_until_next=500; + + if ((mo->timestamp != pck_ts) || is_first) { + const GF_PropertyValue *v; + u32 media_time; + u64 dur = gf_filter_pck_get_duration(mo->pck); + dur *= 1000; + dur /= timescale; + mo->frame_dur = (u32) dur; + mo->last_fetch_time = obj_time; + + mo->timestamp = (u32) pck_ts; + media_time = gf_clock_to_media_time(mo->odm->ck, mo->timestamp); + + if (mo->odm->media_current_time <= media_time) + mo->odm->media_current_time = media_time; if (mo->odm->parentscene->is_dynamic_scene) { GF_Scene *s = mo->odm->parentscene; while (s && s->root_od->addon) { s = s->root_od->parentscene; } - if (s && s->root_od) + if (s && (s->root_od->media_current_time < mo->odm->media_current_time) ) s->root_od->media_current_time = mo->odm->media_current_time; } - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d (%s)] At OTB %u fetch frame TS %u size %d (previous TS %u) - %d unit in CB - UTC "LLU" ms - %d ms until CTS is due - %d ms until next frame\n", mo->odm->OD->objectDescriptorID, mo->odm->net_service->url, gf_clock_time(codec->ck), CU->TS, mo->framesize, mo->timestamp, codec->CB->UnitCount, gf_net_get_utc(), mo->ms_until_pres, mo->ms_until_next )); +#ifndef GPAC_DISABLE_VRML + if (! *eos ) + mediasensor_update_timing(mo->odm, GF_FALSE); +#endif + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d (%s)] At OTB %u fetch frame TS %u size %d (previous TS %u) - %d unit in CB - UTC "LLU" ms - %d ms until CTS is due - %d ms until next frame\n", mo->odm->ID, mo->odm->scene_ns->url, gf_clock_time(mo->odm->ck), pck_ts, mo->framesize, mo->timestamp, gf_filter_pid_get_packet_count(mo->odm->pid), gf_net_get_utc(), mo->ms_until_pres, mo->ms_until_next )); + + v = gf_filter_pck_get_property(mo->pck, GF_PROP_PCK_SENDER_NTP); + if (v) { + GF_PropertyEntry *pe = NULL; - if (CU->sender_ntp) { - s32 ntp_diff = gf_net_get_ntp_diff_ms(CU->sender_ntp); - GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[ODM%d (%s)] Frame TS %u NTP diff with sender %d ms\n", mo->odm->OD->objectDescriptorID, mo->odm->net_service->url, CU->TS, ntp_diff)); + mo->odm->last_drawn_frame_ntp_sender = v->value.longuint; + + v = gf_filter_pck_get_property(mo->pck, GF_PROP_PCK_RECEIVER_NTP); + if (v) { + mo->odm->last_drawn_frame_ntp_receive = v->value.longuint; + } + + mo->odm->last_drawn_frame_ntp_diff = gf_net_get_ntp_diff_ms(mo->odm->last_drawn_frame_ntp_sender); + v = gf_filter_pid_get_info_str(mo->odm->pid, "ntpdiff", &pe); + if (v) { + mo->odm->last_drawn_frame_ntp_diff -= v->value.sint; + } + gf_filter_release_property(pe); + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[ODM%d (%s)] Frame TS %u NTP diff with sender %d ms\n", mo->odm->ID, mo->odm->scene_ns->url, pck_ts, mo->odm->last_drawn_frame_ntp_diff)); + + if (mo->odm->parentscene->compositor->ntpsync + && (mo->odm->last_drawn_frame_ntp_diff > (s32) mo->odm->parentscene->compositor->ntpsync) +// && first_ntp + ) { +// first_ntp = GF_FALSE; + u32 ntp_diff = mo->odm->last_drawn_frame_ntp_diff - mo->odm->parentscene->compositor->ntpsync; + mo->odm->ck->init_timestamp += ntp_diff; + mo->flags |= GF_MO_IN_RESYNC; + } } - mo->timestamp = CU->TS; /*signal EOS after rendering last frame, not while rendering it*/ *eos = GF_FALSE; @@ -678,160 +759,105 @@ char *gf_mo_fetch_data(GF_MediaObject *mo, GF_MOFetchMode resync, u32 upload_tim //already rendered the last frame, consider we no longer have pending late frame on this stream mo->ms_until_pres = 0; } else { -// GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d (%s)] At OTB %u same frame fetch TS %u\n", mo->odm->OD->objectDescriptorID, mo->odm->net_service->url, obj_time, CU->TS )); +// GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d (%s)] At OTB %u same frame fetch TS %u\n", mo->odm->ID, mo->odm->net_service->url, obj_time, CU->TS )); + + //if paused force a high value for next frame + if (!gf_clock_is_started(mo->odm->ck)) { + mo->ms_until_next = 100; + } } /*also adjust CU time based on consummed bytes in input, since some codecs output very large audio chunks*/ - if (codec->bytes_per_sec) mo->timestamp += CU->RenderedLength * 1000 / codec->bytes_per_sec; + if (mo->bytes_per_sec) mo->timestamp += mo->RenderedLength * 1000 / mo->bytes_per_sec; - if (bench_mode) { + if (mo->odm->parentscene->compositor->bench_mode) { mo->ms_until_pres = -1; mo->ms_until_next = 1; } + //TODO fixme, hack for clock signaling + if (!mo->frame && !mo->frame_ifce) + return NULL; mo->nb_fetch ++; *timestamp = mo->timestamp; *size = mo->framesize; if (ms_until_pres) *ms_until_pres = mo->ms_until_pres; if (ms_until_next) *ms_until_next = mo->ms_until_next; - if (outFrame) *outFrame = mo->media_frame; + if (outFrame) *outFrame = mo->frame_ifce; + if (planar_size) *planar_size = mo->framesize / mo->num_channels; - gf_term_service_media_event(mo->odm, GF_EVENT_MEDIA_TIME_UPDATE); +// gf_odm_service_media_event(mo->odm, GF_EVENT_MEDIA_TIME_UPDATE); - gf_odm_lock(mo->odm, 0); - if (mo->media_frame) - return (char *) mo->media_frame; + if (mo->frame_ifce) + return (char *) mo->frame_ifce; - if (codec->direct_vout) - return (char *) codec->CB->pY; return mo->frame; } -GF_EXPORT -GF_Err gf_mo_get_raw_image_planes(GF_MediaObject *mo, u8 **pY_or_RGB, u8 **pU, u8 **pV, u32 *stride_luma_rgb, u32 *stride_chroma) -{ - u32 stride; - if (!mo || !mo->odm || !mo->odm->codec) return GF_BAD_PARAM; - if (mo->odm->codec->direct_vout) { - *pY_or_RGB = mo->odm->codec->CB->pY; - *pU = mo->odm->codec->CB->pU; - *pV = mo->odm->codec->CB->pV; - return GF_OK; - } - if (!mo->odm->codec->direct_frame_output || !mo->media_frame) - return GF_BAD_PARAM; - - mo->media_frame->GetPlane(mo->media_frame, 0, (const char **) pY_or_RGB, stride_luma_rgb); - mo->media_frame->GetPlane(mo->media_frame, 1, (const char **) pU, &stride); - mo->media_frame->GetPlane(mo->media_frame, 2, (const char **) pV, &stride); - *stride_chroma = stride; - return GF_OK; -} GF_EXPORT void gf_mo_release_data(GF_MediaObject *mo, u32 nb_bytes, s32 drop_mode) { -#if 0 - u32 obj_time; -#endif - if (!gf_odm_lock_mo(mo)) - return; + if (!mo || !mo->odm || !mo->odm->pid || !mo->nb_fetch) return; - if (!mo->nb_fetch || !mo->odm->codec) { - gf_odm_lock(mo->odm, 0); - return; - } mo->nb_fetch--; if (mo->nb_fetch) { - gf_odm_lock(mo->odm, 0); return; } - /* if ((drop_mode==0) && !(mo->odm->term->flags & GF_TERM_DROP_LATE_FRAMES) && (mo->type==GF_MEDIA_OBJECT_VIDEO)) - drop_mode=1; - else - */ - if (mo->odm->codec->CB->no_allocation && (mo->odm->codec->CB->Capacity==1) ) - drop_mode = 1; - - - /*perform a sanity check on TS since the CB may have changed status - this may happen in - temporal scalability only*/ - if (mo->odm->codec->CB->output->dataLength ) { - if (nb_bytes==0xFFFFFFFF) { - mo->odm->codec->CB->output->RenderedLength = mo->odm->codec->CB->output->dataLength; - } else { - assert(mo->odm->codec->CB->output->RenderedLength + nb_bytes <= mo->odm->codec->CB->output->dataLength); - mo->odm->codec->CB->output->RenderedLength += nb_bytes; - } + if (nb_bytes==0xFFFFFFFF) { + mo->RenderedLength = mo->size; + } else { + assert(mo->RenderedLength + nb_bytes <= mo->size); + mo->RenderedLength += nb_bytes; + } - if (drop_mode<0) { - /*only allow for explicit last frame keeping if only one node is using the resource + if (drop_mode<0) { + /*only allow for explicit last frame keeping if only one node is using the resource otherwise this would block the composition memory*/ - if (mo->num_open>1) drop_mode=0; - else { - gf_odm_lock(mo->odm, 0); - return; - } + if (mo->num_open>1) { + drop_mode=0; + } else { + return; } + } - /*discard frame*/ - if (mo->odm->codec->CB->output->RenderedLength == mo->odm->codec->CB->output->dataLength) { - if (drop_mode) { - gf_cm_drop_output(mo->odm->codec->CB); - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] At OTB %u released frame TS %u\n", mo->odm->OD->objectDescriptorID, gf_clock_time(mo->odm->codec->ck), mo->timestamp)); - } else { - /*we cannot drop since we don't know the speed of the playback (which can even be frame by frame)*/ + /*discard frame*/ + if (mo->RenderedLength >= mo->size) { + mo->RenderedLength = 0; - //notif CB we kept the output - gf_cm_output_kept(mo->odm->codec->CB); - } + if (!mo->pck) return; + + if (drop_mode==3) + drop_mode=0; + else if (gf_filter_pck_is_blocking_ref(mo->pck) ) + drop_mode = 1; + + if (drop_mode) { + gf_filter_pck_unref(mo->pck); + mo->pck = NULL; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] At OTB %u released frame TS %u\n", mo->odm->ID,gf_clock_time(mo->odm->ck), mo->timestamp)); + } else { + /*we cannot drop since we don't know the speed of the playback (which can even be frame by frame)*/ } } - gf_odm_lock(mo->odm, 0); } GF_EXPORT void gf_mo_get_object_time(GF_MediaObject *mo, u32 *obj_time) { - if (!gf_odm_lock_mo(mo)) return; - - /*regular media codec...*/ - if (mo->odm->codec) { - /*get absolute clock (without drift) for audio*/ - if (mo->odm->codec->type==GF_STREAM_AUDIO) - *obj_time = gf_clock_real_time(mo->odm->codec->ck); + /*get absolute clock (without drift) for audio*/ + if (mo && mo->odm && mo->odm->ck) { + if (mo->odm->type==GF_STREAM_AUDIO) + *obj_time = gf_clock_real_time(mo->odm->ck); else - *obj_time = gf_clock_time(mo->odm->codec->ck); - } - /*BIFS object */ - else if (mo->odm->subscene && mo->odm->subscene->scene_codec) { - *obj_time = gf_clock_time(mo->odm->subscene->scene_codec->ck); + *obj_time = gf_clock_time(mo->odm->ck); } /*unknown / unsupported object*/ else { *obj_time = 0; } - gf_odm_lock(mo->odm, 0); -} - -GF_EXPORT -s32 gf_mo_get_clock_drift(GF_MediaObject *mo) -{ - s32 res = 0; - if (!gf_odm_lock_mo(mo)) return 0; - - /*regular media codec...*/ - if (mo->odm->codec) { - res = mo->odm->codec->ck->drift; - } - /*BIFS object */ - else if (mo->odm->subscene && mo->odm->subscene->scene_codec) { - res = mo->odm->subscene->scene_codec->ck->drift; - } - gf_odm_lock(mo->odm, 0); - return res; } GF_EXPORT @@ -840,20 +866,14 @@ void gf_mo_play(GF_MediaObject *mo, Double clipBegin, Double clipEnd, Bool can_l if (!mo) return; if (!mo->num_open && mo->odm) { - s32 res; - Bool is_restart = GF_FALSE; - - /*remove object from media queue*/ - gf_term_lock_media_queue(mo->odm->term, GF_TRUE); - res = gf_list_del_item(mo->odm->term->media_queue, mo->odm); - gf_term_lock_media_queue(mo->odm->term, GF_FALSE); - - if (mo->odm->action_type!=GF_ODM_ACTION_PLAY) { - mo->odm->action_type = GF_ODM_ACTION_PLAY; - is_restart = GF_FALSE; - res = -1; + mo->is_eos = GF_FALSE; + if (mo->odm->state == GF_ODM_STATE_PLAY) { + if (mo->odm->flags & GF_ODM_PREFETCH) { + mo->odm->flags &= ~GF_ODM_PREFETCH; + mo->num_open++; + return; + } } - if (mo->odm->flags & GF_ODM_NO_TIME_CTRL) { mo->odm->media_start_time = 0; } else { @@ -875,13 +895,9 @@ void gf_mo_play(GF_MediaObject *mo, Double clipBegin, Double clipEnd, Bool can_l } } /*done prefetching*/ - mo->odm->flags &= ~GF_ODM_PREFETCH; + assert(! (mo->odm->flags & GF_ODM_PREFETCH) ); - if (is_restart) { - mediacontrol_restart(mo->odm); - } else { - gf_odm_start(mo->odm, (res>=0) ? 1 : 0); - } + gf_odm_start(mo->odm); } else if (mo->odm) { if (mo->num_to_restart) mo->num_restart--; if (!mo->num_restart && (mo->num_to_restart==mo->num_open+1) ) { @@ -893,59 +909,51 @@ void gf_mo_play(GF_MediaObject *mo, Double clipBegin, Double clipEnd, Bool can_l } GF_EXPORT -Bool gf_mo_stop(GF_MediaObject *mo) +void gf_mo_stop(GF_MediaObject **_mo) { - Bool ret = GF_FALSE; - if (!mo || !mo->num_open) return GF_FALSE; + GF_MediaObject *mo = _mo ? *_mo : NULL; + if (!mo || !mo->num_open) return; mo->num_open--; if (!mo->num_open && mo->odm) { - if (mo->odm->flags & GF_ODM_DESTROYED) return GF_TRUE; - - /*do not stop directly, this can delete channel data currently being decoded (BIFS anim & co)*/ - gf_term_lock_media_queue(mo->odm->term, GF_TRUE); - /*if object not in media queue, add it*/ - if (gf_list_find(mo->odm->term->media_queue, mo->odm)<0) { - gf_list_add(mo->odm->term->media_queue, mo->odm); + mo->first_frame_fetched = GF_FALSE; + if (mo->odm->flags & GF_ODM_DESTROYED) { + *_mo = NULL; + return; } /*signal STOP request*/ - if ((mo->OD_ID==GF_MEDIA_EXTERNAL_ID) || (mo->odm && mo->odm->OD && (mo->odm->OD->objectDescriptorID==GF_MEDIA_EXTERNAL_ID))) { - mo->odm->action_type = GF_ODM_ACTION_DELETE; - ret = GF_TRUE; + if ((mo->OD_ID==GF_MEDIA_EXTERNAL_ID) || (mo->odm && mo->odm->ID && (mo->odm->ID==GF_MEDIA_EXTERNAL_ID))) { + gf_odm_disconnect(mo->odm, 2); + *_mo = NULL; + } else { + if ( gf_odm_stop_or_destroy(mo->odm) ) { + *_mo = NULL; + } } - else - mo->odm->action_type = GF_ODM_ACTION_STOP; - - gf_term_lock_media_queue(mo->odm->term, GF_FALSE); } else { if (!mo->num_to_restart) { mo->num_restart = mo->num_to_restart = mo->num_open + 1; } } - return ret; } GF_EXPORT void gf_mo_restart(GF_MediaObject *mo) { - void *mediactrl_stack = NULL; - if (!gf_odm_lock_mo(mo)) return; - + /*if no control and not root of a scene, check timelines are unlocked*/ + if (!mo->odm->subscene #ifndef GPAC_DISABLE_VRML - mediactrl_stack = gf_odm_get_mediacontrol(mo->odm); + && !gf_odm_get_mediacontrol(mo->odm) #endif - /*if no control and not root of a scene, check timelines are unlocked*/ - if (!mediactrl_stack && !mo->odm->subscene) { + ) { /*don't restart if sharing parent scene clock*/ if (gf_odm_shares_clock(mo->odm, gf_odm_get_media_clock(mo->odm->parentscene->root_od))) { - gf_odm_lock(mo->odm, 0); return; } } /*all other cases, call restart to take into account clock references*/ mediacontrol_restart(mo->odm); - gf_odm_lock(mo->odm, 0); } u32 gf_mo_get_od_id(MFURL *url) @@ -1013,7 +1021,7 @@ Bool gf_mo_is_same_url(GF_MediaObject *obj, MFURL *an_url, Bool *keep_fragment, if (!obj->URLs.count) { if (!obj->odm) return GF_FALSE; - strcpy(szURL1, obj->odm->net_service->url); + strcpy(szURL1, obj->odm->scene_ns->url); } else { strcpy(szURL1, obj->URLs.vals[0].url); } @@ -1023,10 +1031,11 @@ Bool gf_mo_is_same_url(GF_MediaObject *obj, MFURL *an_url, Bool *keep_fragment, if (keep_fragment) *keep_fragment = GF_FALSE; include_sub_url = GF_TRUE; } else if ((obj->type==GF_MEDIA_OBJECT_SCENE) && keep_fragment && obj->odm) { - GF_ClientService *ns; u32 j; /*for remoteODs/dynamic ODs, check if one of the running service cannot be used*/ for (i=0; icount; i++) { + GF_Scene *scene; + GF_SceneNamespace *sns; char *frag = strrchr(an_url->vals[i].url, '#'); j=0; /*this is the same object (may need some refinement)*/ @@ -1034,7 +1043,7 @@ Bool gf_mo_is_same_url(GF_MediaObject *obj, MFURL *an_url, Bool *keep_fragment, /*fragment is a media segment, same URL*/ if (frag ) { - Bool same_res = GF_FALSE; + Bool same_res; frag[0] = 0; same_res = !strncmp(an_url->vals[i].url, szURL1, strlen(an_url->vals[i].url)) ? GF_TRUE : GF_FALSE; frag[0] = '#'; @@ -1051,16 +1060,16 @@ Bool gf_mo_is_same_url(GF_MediaObject *obj, MFURL *an_url, Bool *keep_fragment, } } - gf_term_lock_media_queue(obj->odm->term, 1); - while ( (ns = (GF_ClientService*)gf_list_enum(obj->odm->term->net_services, &j)) ) { + scene = gf_scene_get_root_scene(obj->odm->parentscene ? obj->odm->parentscene : obj->odm->subscene); + while ( (sns = (GF_SceneNamespace*) gf_list_enum(scene->namespaces, &j) ) ) { /*sub-service of an existing service - don't touch any fragment*/ - if (gf_term_service_can_handle_url(ns, an_url->vals[i].url)) { +#ifdef FILTER_FIXME + if (gf_term_service_can_handle_url(sns, an_url->vals[i].url)) { *keep_fragment = GF_TRUE; - gf_term_lock_media_queue(obj->odm->term, 0); return GF_FALSE; } +#endif } - gf_term_lock_media_queue(obj->odm->term, 0); } } @@ -1069,8 +1078,11 @@ Bool gf_mo_is_same_url(GF_MediaObject *obj, MFURL *an_url, Bool *keep_fragment, for (i=0; icount; i++) { if (an_url->vals[i].url && !stricmp(szURL1, an_url->vals[i].url)) return GF_TRUE; } + if (obj->odm && (obj->odm->flags & GF_ODM_PASSTHROUGH) && an_url->count && an_url->vals[0].url && !strncmp(an_url->vals[0].url, "gpid://", 7)) + return GF_TRUE; /*not same resource, we will have to check fragment as URL might point to a sub-service or single stream of a mux*/ if (keep_fragment) *keep_fragment = GF_TRUE; + return GF_FALSE; } ext = strrchr(szURL1, '#'); @@ -1100,7 +1112,6 @@ Bool gf_mo_url_changed(GF_MediaObject *mo, MFURL *url) /*special case for 3GPP text: if not playing and user node changed, force removing it*/ if (ret && mo->odm && !mo->num_open && (mo->type == GF_MEDIA_OBJECT_TEXT)) { mo->flags |= GF_MO_DISPLAY_REMOVE; - gf_term_stop_codec(mo->odm->codec, GF_FALSE); } return ret; } @@ -1145,39 +1156,48 @@ void gf_mo_set_speed(GF_MediaObject *mo, Fixed speed) ctrl = gf_odm_get_mediacontrol(mo->odm); if (ctrl) return; #endif - if (mo->odm->net_service && mo->odm->net_service->owner && (mo->odm->net_service->owner->flags & GF_ODM_INHERIT_TIMELINE)) + + if (mo->odm->scene_ns && mo->odm->scene_ns->owner && (mo->odm->scene_ns->owner->flags & GF_ODM_INHERIT_TIMELINE)) return; + gf_odm_set_speed(mo->odm, speed, GF_TRUE); } GF_EXPORT Fixed gf_mo_get_current_speed(GF_MediaObject *mo) { - return (mo && mo->odm && mo->odm->codec && mo->odm->codec->ck) ? mo->odm->codec->ck->speed : FIX_ONE; + return (mo && mo->odm && mo->odm->ck) ? mo->odm->ck->speed : FIX_ONE; } GF_EXPORT u32 gf_mo_get_min_frame_dur(GF_MediaObject *mo) { - return (mo && mo->odm && mo->odm->codec) ? mo->odm->codec->min_frame_dur : 0; + return mo ? mo->frame_dur : 0; +} +GF_EXPORT +u32 gf_mo_map_timestamp_to_sys_clock(GF_MediaObject *mo, u32 ts) +{ + return (mo && mo->odm)? mo->odm->ck->start_time + ts : 0; } +Bool gf_mo_is_buffering(GF_MediaObject *mo) +{ + return (mo && mo->odm && mo->odm->ck->nb_buffering) ? GF_TRUE : GF_FALSE; +} GF_EXPORT Fixed gf_mo_get_speed(GF_MediaObject *mo, Fixed in_speed) { Fixed res = in_speed; + if (!mo || !mo->odm) return in_speed; #ifndef GPAC_DISABLE_VRML MediaControlStack *ctrl; - if (!gf_odm_lock_mo(mo)) return in_speed; - /*get control*/ ctrl = gf_odm_get_mediacontrol(mo->odm); if (ctrl) res = ctrl->control->mediaSpeed; - gf_odm_lock(mo->odm, 0); #endif return res; @@ -1190,7 +1210,7 @@ Bool gf_mo_get_loop(GF_MediaObject *mo, Bool in_loop) #ifndef GPAC_DISABLE_VRML MediaControlStack *ctrl; #endif - if (!gf_odm_lock_mo(mo)) return in_loop; + if (!mo || !mo->odm) return in_loop; /*get control*/ #ifndef GPAC_DISABLE_VRML @@ -1209,7 +1229,6 @@ Bool gf_mo_get_loop(GF_MediaObject *mo, Bool in_loop) */ #endif } - gf_odm_lock(mo->odm, 0); return in_loop; } @@ -1217,9 +1236,7 @@ GF_EXPORT Double gf_mo_get_duration(GF_MediaObject *mo) { Double dur; - if (!gf_odm_lock_mo(mo)) return -1.0; dur = ((Double) (s64)mo->odm->duration)/1000.0; - gf_odm_lock(mo->odm, 0); return dur; } @@ -1231,12 +1248,11 @@ Bool gf_mo_should_deactivate(GF_MediaObject *mo) MediaControlStack *ctrl; #endif - if (!gf_odm_lock_mo(mo)) return GF_FALSE; - - - if (!mo->odm->state || (mo->odm->parentscene && mo->odm->parentscene->is_dynamic_scene)) { - gf_odm_lock(mo->odm, 0); - return GF_FALSE; + if (!mo || !mo->odm) return GF_TRUE; + if (!mo->odm->state) return GF_FALSE; + //if dynamic scene we can deactivate + if (mo->odm->parentscene && mo->odm->parentscene->is_dynamic_scene) { + return GF_TRUE; } #ifndef GPAC_DISABLE_VRML @@ -1252,52 +1268,48 @@ Bool gf_mo_should_deactivate(GF_MediaObject *mo) #endif res = GF_TRUE; - gf_odm_lock(mo->odm, 0); return res; } GF_EXPORT Bool gf_mo_is_muted(GF_MediaObject *mo) { - Bool res = GF_FALSE; #ifndef GPAC_DISABLE_VRML - if (!gf_odm_lock_mo(mo)) return GF_FALSE; - res = mo->odm->media_ctrl ? mo->odm->media_ctrl->control->mute : GF_FALSE; - gf_odm_lock(mo->odm, 0); + return mo->odm->media_ctrl ? mo->odm->media_ctrl->control->mute : GF_FALSE; +#else + return GF_FALSE; #endif - return res; +} + +GF_EXPORT +Bool gf_mo_is_started(GF_MediaObject *mo) +{ + if (mo && mo->odm && gf_clock_is_started(mo->odm->ck)) return GF_TRUE; + return GF_FALSE; } GF_EXPORT Bool gf_mo_is_done(GF_MediaObject *mo) { - Bool res = GF_FALSE; - GF_Codec *codec; + GF_Clock *ck; u64 dur; - if (!gf_odm_lock_mo(mo)) return GF_FALSE; + if (!mo || !mo->odm) return GF_FALSE; - if (mo->odm->codec && mo->odm->codec->CB) { - /*for natural media use composition buffer*/ - res = (mo->odm->codec->CB->Status==CB_STOP) ? GF_TRUE : GF_FALSE; - } else { - /*otherwise check EOS and time*/ - codec = mo->odm->codec; - dur = mo->odm->duration; - if (!mo->odm->codec) { - if (!mo->odm->subscene) res = GF_FALSE; - else { - codec = mo->odm->subscene->scene_codec; - dur = mo->odm->subscene->duration; - } - } - if (codec && (codec->Status==GF_ESM_CODEC_STOP)) { - /*codec is done, check by duration*/ - GF_Clock *ck = gf_odm_get_media_clock(mo->odm); - if (gf_clock_time(ck) > dur) res = GF_TRUE; - } + if (! mo->odm->has_seen_eos) return GF_FALSE; + + if ((mo->odm->type==GF_STREAM_AUDIO) || (mo->odm->type==GF_STREAM_VISUAL)) { + return GF_TRUE; } - gf_odm_lock(mo->odm, 0); - return res; + + /*check time - technically this should also apply to video streams since we could extend the duration + of the last frame - to further test*/ + dur = (mo->odm->subscene && mo->odm->subscene->duration) ? mo->odm->subscene->duration : mo->odm->duration; + /*codec is done, check by duration*/ + ck = gf_odm_get_media_clock(mo->odm); + if (gf_clock_time(ck) > dur) + return GF_TRUE; + + return GF_FALSE; } /*resyncs clock - only audio objects are allowed to use this*/ @@ -1305,18 +1317,12 @@ GF_EXPORT void gf_mo_adjust_clock(GF_MediaObject *mo, s32 ms_drift) { if (!mo || !mo->odm) return; - if (!mo->odm->codec || (mo->odm->codec->type != GF_STREAM_AUDIO) ) return; - gf_clock_adjust_drift(mo->odm->codec->ck, ms_drift); + if (mo->odm->type != GF_STREAM_AUDIO) return; + gf_clock_set_audio_delay(mo->odm->ck, ms_drift); } GF_EXPORT -u32 gf_mo_get_flags(GF_MediaObject *mo) -{ - return mo ? mo->flags : 0; -} - -GF_EXPORT -void gf_mo_set_flag(GF_MediaObject *mo, u32 flag, Bool set_on) +void gf_mo_set_flag(GF_MediaObject *mo, GF_MOUserFlags flag, Bool set_on) { if (mo) { if (set_on) @@ -1326,72 +1332,40 @@ void gf_mo_set_flag(GF_MediaObject *mo, u32 flag, Bool set_on) } } -GF_EXPORT -u32 gf_mo_get_last_frame_time(GF_MediaObject *mo) -{ - return mo ? mo->timestamp : 0; -} - -GF_EXPORT -Bool gf_mo_is_private_media(GF_MediaObject *mo) -{ - if (mo && mo->odm && mo->odm->codec && mo->odm->codec->decio && (mo->odm->codec->decio->InterfaceType==GF_PRIVATE_MEDIA_DECODER_INTERFACE)) return GF_TRUE; - return GF_FALSE; -} - -GF_EXPORT -Bool gf_mo_set_position(GF_MediaObject *mo, GF_Window *src, GF_Window *dst) -{ - GF_Err e; - GF_PrivateMediaDecoder *dec; - if (!mo->odm || !mo->odm->codec || !mo->odm->codec->decio || (mo->odm->codec->decio->InterfaceType!=GF_PRIVATE_MEDIA_DECODER_INTERFACE)) return GF_FALSE; - - dec = (GF_PrivateMediaDecoder*)mo->odm->codec->decio; - e = dec->Control(dec, GF_FALSE, src, dst); - if (e==GF_BUFFER_TOO_SMALL) return GF_TRUE; - return GF_FALSE; -} - -GF_EXPORT -Bool gf_mo_is_raw_memory(GF_MediaObject *mo) -{ - if (!mo->odm || !mo->odm->codec) return GF_FALSE; - return mo->odm->codec->direct_frame_output || mo->odm->codec->direct_vout ; -} - GF_EXPORT u32 gf_mo_has_audio(GF_MediaObject *mo) { - char *sub_url, *ext; +#ifdef FILTER_FIXME + char *sub_url; +#endif u32 i; - GF_NetworkCommand com; - GF_ClientService *ns; + GF_SceneNamespace *ns; GF_Scene *scene; if (!mo || !mo->odm) return 0; if (mo->type != GF_MEDIA_OBJECT_VIDEO) return 0; - if (!mo->odm->net_service) return 2; + if (!mo->odm->scene_ns) return 2; - ns = mo->odm->net_service; + ns = mo->odm->scene_ns; scene = mo->odm->parentscene; +#ifdef FILTER_FIXME sub_url = strchr(ns->url, '#'); +#endif for (i=0; iresources); i++) { GF_ObjectManager *odm = (GF_ObjectManager *)gf_list_get(scene->resources, i); - if (odm->net_service != ns) continue; - if (!odm->mo) continue; + if (odm->scene_ns != ns) continue; + //object already associated + if (odm->mo) continue; +#ifdef FILTER_FIXME if (sub_url) { - ext = odm->mo->URLs.count ? odm->mo->URLs.vals[0].url : NULL; + char *ext = mo->URLs.count ? mo->URLs.vals[0].url : NULL; if (ext) ext = strchr(ext, '#'); if (!ext || strcmp(sub_url, ext)) continue; } - /*there is already an audio object in this service, do not recreate one*/ - if (odm->mo->type == GF_MEDIA_OBJECT_AUDIO) return 0; +#endif + /*we have one audio object not bound with the scene from the same service, let's use it*/ + if (odm->type == GF_STREAM_AUDIO) return 1; } - memset(&com, 0, sizeof(GF_NetworkCommand) ); - com.command_type = GF_NET_SERVICE_HAS_AUDIO; - com.audio.base_url = mo->URLs.count ? mo->URLs.vals[0].url : NULL; - if (!com.audio.base_url) com.audio.base_url = ns->url; - if (gf_term_service_command(ns, &com) == GF_OK) return 1; return 0; } @@ -1417,13 +1391,6 @@ GF_DOMEventTarget *gf_mo_event_target_add_node(GF_MediaObject *mo, GF_Node *n) #endif } -GF_Err gf_mo_event_target_remove(GF_MediaObject *mo, GF_DOMEventTarget *target) -{ - if (!mo || !target) return GF_BAD_PARAM; - gf_list_del_item(mo->evt_targets, target); - return GF_OK; -} - GF_Err gf_mo_event_target_remove_by_index(GF_MediaObject *mo, u32 i) { if (!mo) return GF_BAD_PARAM; @@ -1502,6 +1469,8 @@ void gf_mo_del(GF_MediaObject *mo) { assert(gf_list_count(mo->evt_targets) == 0); gf_list_del(mo->evt_targets); + if (mo->pck) gf_filter_pck_unref(mo->pck); + gf_sg_mfurl_del(mo->URLs); gf_free(mo); } @@ -1509,9 +1478,8 @@ void gf_mo_del(GF_MediaObject *mo) Bool gf_mo_get_srd_info(GF_MediaObject *mo, GF_MediaObjectVRInfo *vr_info) { GF_Scene *scene; - if (!vr_info) return GF_FALSE; - if (!gf_odm_lock_mo(mo)) return GF_FALSE; - + if (!vr_info || !mo->odm) return GF_FALSE; + scene = mo->odm->subscene ? mo->odm->subscene : mo->odm->parentscene; memset(vr_info, 0, sizeof(GF_MediaObjectVRInfo)); @@ -1524,61 +1492,72 @@ Bool gf_mo_get_srd_info(GF_MediaObject *mo, GF_MediaObjectVRInfo *vr_info) vr_info->srd_max_x = scene->srd_max_x; vr_info->srd_max_y = scene->srd_max_y; vr_info->is_tiled_srd = scene->is_tiled_srd; + vr_info->has_full_coverage = (scene->srd_type==2) ? GF_TRUE : GF_FALSE; gf_sg_get_scene_size_info(scene->graph, &vr_info->scene_width, &vr_info->scene_height); - gf_odm_lock(mo->odm, 0); - return (!scene->vr_type && !scene->is_srd) ? GF_FALSE : GF_TRUE; + if (mo->srd_w && mo->srd_h) return GF_TRUE; + if (mo->srd_full_w && mo->srd_full_h) return GF_TRUE; + return GF_FALSE; } /*sets quality hint for this media object - quality_rank is between 0 (min quality) and 100 (max quality)*/ void gf_mo_hint_quality_degradation(GF_MediaObject *mo, u32 quality_degradation) { - if (!gf_odm_lock_mo(mo)) return; - if (!mo->odm || !mo->odm->codec) { - gf_odm_lock(mo->odm, 0); + if (!mo || !mo->odm || !mo->odm->pid) { return; } if (mo->quality_degradation_hint != quality_degradation) { - GF_NetworkCommand com; - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.command_type = GF_NET_SERVICE_QUALITY_SWITCH; - com.base.on_channel = gf_list_get(mo->odm->codec->inChannels, 0); - com.switch_quality.quality_degradation = quality_degradation; - gf_term_service_command(mo->odm->net_service, &com); - + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_QUALITY_SWITCH, mo->odm->pid); + evt.quality_switch.quality_degradation = quality_degradation; + gf_filter_pid_send_event(mo->odm->pid, &evt); + mo->quality_degradation_hint = quality_degradation; } - - gf_odm_lock(mo->odm, 0); } void gf_mo_hint_visible_rect(GF_MediaObject *mo, u32 min_x, u32 max_x, u32 min_y, u32 max_y) { - - if (!gf_odm_lock_mo(mo)) return; - if (!mo->odm || !mo->odm->codec) { - gf_odm_lock(mo->odm, 0); + if (!mo || !mo->odm || !mo->odm->pid) { return; } + if ((mo->view_min_x!=min_x) || (mo->view_max_x!=max_x) || (mo->view_min_y!=min_y) || (mo->view_max_y!=max_y)) { - GF_NetworkCommand com; + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_VISIBILITY_HINT, mo->odm->pid); mo->view_min_x = min_x; mo->view_max_x = max_x; mo->view_min_y = min_y; mo->view_max_y = max_y; - memset(&com, 0, sizeof(GF_NetworkCommand)); - com.base.command_type = GF_NET_CHAN_VISIBILITY_HINT; - com.base.on_channel = gf_list_get(mo->odm->codec->inChannels, 0); - com.visibility_hint.min_x = min_x; - com.visibility_hint.max_x = max_x; - com.visibility_hint.min_y = min_y; - com.visibility_hint.max_y = max_y; - gf_term_service_command(mo->odm->net_service, &com); + evt.visibility_hint.min_x = min_x; + evt.visibility_hint.max_x = max_x; + evt.visibility_hint.min_y = min_y; + evt.visibility_hint.max_y = max_y; + + gf_filter_pid_send_event(mo->odm->pid, &evt); + } +} + +void gf_mo_hint_gaze(GF_MediaObject *mo, u32 gaze_x, u32 gaze_y) +{ + if (!mo || !mo->odm || !mo->odm->pid) { + return; + } + + if ((mo->view_min_x!=gaze_x) || (mo->view_min_y!=gaze_y) ) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_VISIBILITY_HINT, mo->odm->pid); + mo->view_min_x = gaze_x; + mo->view_min_y = gaze_y; + + evt.visibility_hint.min_x = gaze_x; + evt.visibility_hint.min_y = gaze_y; + evt.visibility_hint.is_gaze = GF_TRUE; + + gf_filter_pid_send_event(mo->odm->pid, &evt); } - - gf_odm_lock(mo->odm, 0); } diff --git a/src/compositor/mesh.c b/src/compositor/mesh.c index 41f8a05..dea429f 100644 --- a/src/compositor/mesh.c +++ b/src/compositor/mesh.c @@ -646,6 +646,15 @@ void mesh_new_sphere(GF_Mesh *mesh, Fixed radius, Bool low_res, GF_MeshSphereAng if (radius<0) num_steps = 72; if (low_res) num_steps /= 2; + if (sphere_angles) { + Fixed min_subd1, min_subd2; + min_subd1 = gf_divfix(sphere_angles->max_phi - sphere_angles->min_phi, GF_PI); + min_subd2 = gf_divfix(sphere_angles->max_theta - sphere_angles->min_theta, GF_2PI); + if (min_subd1<0) min_subd1 = -min_subd1; + if (min_subd2<0) min_subd2 = -min_subd2; + if (min_subd2flags |= MESH_IS_SOLID; } + if (radius<0) radius = -radius; mesh->bounds.min_edge.x = mesh->bounds.min_edge.y = mesh->bounds.min_edge.z = -radius; mesh->bounds.max_edge.x = mesh->bounds.max_edge.y = mesh->bounds.max_edge.z = radius; + gf_bbox_refresh(&mesh->bounds); if (radius != FIX_ONE) gf_mesh_build_aabbtree(mesh); @@ -1134,7 +1145,7 @@ void mesh_new_ifs_intern(GF_Mesh *mesh, GF_Node *__coord, MFInt32 *coordIndex, GF_Node *__texCoords, MFInt32 *texCoordIndex, Fixed creaseAngle) { - u32 i, n, count, c_count, nor_count; + u32 i, idx, count, c_count, nor_count; u32 index; Bool smooth_normals; SFVec2f tx; @@ -1238,7 +1249,7 @@ void mesh_new_ifs_intern(GF_Mesh *mesh, GF_Node *__coord, MFInt32 *coordIndex, has_color = (colorRGB->color.count) ? 1 : 0; } } - n = 0; + idx = 0; if (has_color) { if (!colorPerVertex) { index = colorIndex->count ? colorIndex->vals[0] : 0; @@ -1292,15 +1303,15 @@ void mesh_new_ifs_intern(GF_Mesh *mesh, GF_Node *__coord, MFInt32 *coordIndex, cur_face = 0; for (i=0; ivals[i] == -1) { - n++; + idx++; if (has_color && !colorPerVertex) { - GET_IDX(n, colorIndex); + GET_IDX(idx, colorIndex); #ifndef GPAC_DISABLE_X3D MESH_GET_COL(colRGBA, index); #endif } if (has_normal && !normalPerVertex) { - GET_IDX(n, normalIndex); + GET_IDX(idx, normalIndex); if (index < nor_count) nor = normal->vector.vals[index]; } if (faces[cur_face]->v_count<3) faces[cur_face]->v_count=0; @@ -1547,11 +1558,15 @@ void mesh_new_elevation_grid(GF_Mesh *mesh, GF_Node *node) /*alloc face & normals tables*/ if (smooth_normals) { faces = (GF_Mesh **)gf_malloc(sizeof(GF_Mesh *)*face_count); + if (!faces) return; faces_info = (struct face_info*)gf_malloc(sizeof(struct face_info)*face_count); + if (!faces_info) return; memset(faces_info, 0, sizeof(struct face_info)*face_count); pts_info = (struct pt_info*)gf_malloc(sizeof(struct pt_info)*pt_count); + if (!pts_info) return; memset(pts_info, 0, sizeof(struct pt_info)*pt_count); faces[cur_face] = new_mesh(); + if (!faces[cur_face]) return; } for (j=0; jnormalPerVertex) { idx = i + j * (xDimension-1); if (idxvector.count) { - SFVec3f n = norm->vector.vals[idx]; + n = norm->vector.vals[idx]; gf_vec_norm(&n); MESH_SET_NORMAL(vx, n); } @@ -1602,7 +1617,7 @@ void mesh_new_elevation_grid(GF_Mesh *mesh, GF_Node *node) if (has_normal && eg->normalPerVertex) { idx = (i+l) + (j+k) * xDimension; if (idxvector.count) { - SFVec3f n = norm->vector.vals[idx]; + n = norm->vector.vals[idx]; gf_vec_norm(&n); MESH_SET_NORMAL(vx, n); } @@ -1629,7 +1644,10 @@ void mesh_new_elevation_grid(GF_Mesh *mesh, GF_Node *node) gf_vec_norm(&faces_info[cur_face].nor); /*done with face*/ cur_face++; - if (cur_facev_count; j++) { - SFVec3f n = smooth_face_normals(pts_info, pt_count, faces_info, face_count, j, i, cosCrease); + n = smooth_face_normals(pts_info, pt_count, faces_info, face_count, j, i, cosCrease); MESH_SET_NORMAL(faces[i]->vertices[j], n); } } @@ -2120,7 +2138,6 @@ static void mesh_extrude_path_intern(GF_Mesh *mesh, GF_Path *path, MFVec3f *thes /*generate begin cap*/ if (begin_face) { - SFVec3f n; scale.x = scale.y = FIX_ONE; if (spine_scale && spine_scale->count) scale = spine_scale->vals[0]; @@ -2182,7 +2199,6 @@ static void mesh_extrude_path_intern(GF_Mesh *mesh, GF_Path *path, MFVec3f *thes /*generate end cap*/ if (end_face) { - SFVec3f n; scale.x = scale.y = FIX_ONE; if (spine_scale && (nb_spine-1count)) scale = spine_scale->vals[nb_spine-1]; /*get last SCP after rotation*/ @@ -2248,7 +2264,7 @@ static void mesh_extrude_path_intern(GF_Mesh *mesh, GF_Path *path, MFVec3f *thes for (i=0; iv_count; j++) { - SFVec3f n = smooth_face_normals(pts_info, pt_count, faces_info, face_count, j, i, cosCrease); + n = smooth_face_normals(pts_info, pt_count, faces_info, face_count, j, i, cosCrease); MESH_SET_NORMAL(faces[i]->vertices[j], n); } } diff --git a/src/compositor/mesh_collide.c b/src/compositor/mesh_collide.c index 829e49d..d8814b8 100644 --- a/src/compositor/mesh_collide.c +++ b/src/compositor/mesh_collide.c @@ -156,7 +156,7 @@ if (!mesh || !node) return; res[1] = gf_mulfix(res[1], res[1]); num_pos = aabb_split(mesh, node, 2); res[2] = gf_divfix(INT2FIX(num_pos), INT2FIX(node->nb_idx)) - FIX_ONE/2; - res[2] = gf_mulfix(res[2], res[2]);; + res[2] = gf_mulfix(res[2], res[2]); axis = 0; if (res[1] < res[axis]) axis = 1; @@ -341,7 +341,7 @@ Bool gf_mesh_aabb_ray_hit(GF_Mesh *mesh, AABBNode *n, GF_Ray *ray, Fixed *closes gf_vec_add(*outPoint, ray->orig, *outPoint); } if (outNormal) { - IDX_TYPE *idx = &mesh->indices[3*n->indices[inters_idx]];; + IDX_TYPE *idx = &mesh->indices[3*n->indices[inters_idx]]; if (mesh->flags & MESH_IS_SMOOTHED) { gf_vec_diff(v1, mesh->vertices[idx[1]].pos, mesh->vertices[idx[0]].pos); gf_vec_diff(v2, mesh->vertices[idx[2]].pos, mesh->vertices[idx[0]].pos); @@ -352,7 +352,7 @@ Bool gf_mesh_aabb_ray_hit(GF_Mesh *mesh, AABBNode *n, GF_Ray *ray, Fixed *closes } } if (outTexCoords) { - IDX_TYPE *idx = &mesh->indices[3*n->indices[inters_idx]];; + IDX_TYPE *idx = &mesh->indices[3*n->indices[inters_idx]]; ray_hit_triangle_get_u_v(ray, &mesh->vertices[idx[0]].pos, &mesh->vertices[idx[1]].pos, &mesh->vertices[idx[2]].pos, &outTexCoords->x, &outTexCoords->y); @@ -503,7 +503,7 @@ Bool gf_mesh_closest_face_aabb(GF_Mesh *mesh, AABBNode *node, SFVec3f pos, Fixed return gf_mesh_closest_face_aabb(mesh, node->neg, pos, min_dist, min_sq_dist, min_col_dist, outPoint); } - need_norm = (mesh->flags & MESH_IS_SMOOTHED) ? GF_TRUE : GF_FALSE, + need_norm = (mesh->flags & MESH_IS_SMOOTHED) ? GF_TRUE : GF_FALSE; r.orig = pos; has_inter = GF_FALSE; for (i=0; inb_idx; i++) { @@ -562,7 +562,7 @@ Bool gf_mesh_closest_face(GF_Mesh *mesh, SFVec3f pos, Fixed min_dist, SFVec3f *o return gf_mesh_closest_face_aabb(mesh, mesh->aabb_root, pos, min_dist, d, &dmax, outPoint); } - need_norm = (mesh->flags & MESH_IS_SMOOTHED) ? GF_TRUE : GF_FALSE, + need_norm = (mesh->flags & MESH_IS_SMOOTHED) ? GF_TRUE : GF_FALSE; r.orig = pos; has_inter = GF_FALSE; diff --git a/src/compositor/mesh_tesselate.c b/src/compositor/mesh_tesselate.c index e30e90c..101fbf4 100644 --- a/src/compositor/mesh_tesselate.c +++ b/src/compositor/mesh_tesselate.c @@ -39,7 +39,7 @@ #ifdef GPAC_HAS_GLU -#ifdef GPAC_IPHONE +#ifdef GPAC_CONFIG_IOS #define GLdouble GLfloat #endif @@ -58,10 +58,14 @@ typedef struct static void CALLBACK mesh_tess_begin(GLenum which) { assert(which==GL_TRIANGLES); } -static void CALLBACK mesh_tess_end(void) { } -static void CALLBACK mesh_tess_error(GLenum error_code) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Mesh] Tesselate error %s\n", gluErrorString(error_code))); +static void CALLBACK mesh_tess_end(void) { } +static void CALLBACK mesh_tess_error(GLenum error_code) +{ + if (error_code) + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Mesh] Tesselate error %s\n", gluErrorString(error_code))); +} + /*only needed to force GL_TRIANGLES*/ static void CALLBACK mesh_tess_edgeflag(GLenum flag) { } @@ -219,6 +223,13 @@ void gf_mesh_tesselate_path(GF_Mesh *mesh, GF_Path *path, u32 outline_style) mesh->bounds.max_edge.y = rc.y; mesh->bounds.min_edge.z = mesh->bounds.max_edge.z = 0; gf_bbox_refresh(&mesh->bounds); + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + mesh_tess_error(0); + } +#endif + } #else @@ -284,8 +295,6 @@ u32 polygon_check_convexity(GF_Vertex *pts, u32 len, u32 direction) pThird = pSecond; GetPoint2D(pThird, pts[0]); - /* Get different point, return if less than 3 diff points. */ - if (len < 3 ) return GF_POLYGON_CONVEX_LINE; iread = 1; ConvGetPointDelta(dprev, pThird, pSecond); pSaveSecond = pSecond; @@ -304,8 +313,9 @@ u32 polygon_check_convexity(GF_Vertex *pts, u32 len, u32 direction) GetPoint2D(pThird, pts[0]); dcur.x = pThird.x - pSecond.x; dcur.y = pThird.y - pSecond.y; - if ( ConvCompare(dcur) ) ConvCheckTriple; - + if ( ConvCompare(dcur) ) { + ConvCheckTriple; + } /* and check for direction changes back to second vertex */ dcur.x = pSaveSecond.x - pSecond.x; dcur.y = pSaveSecond.y - pSecond.y; diff --git a/src/compositor/mpeg4_animstream.c b/src/compositor/mpeg4_animstream.c index 5bbdf46..2db6e35 100644 --- a/src/compositor/mpeg4_animstream.c +++ b/src/compositor/mpeg4_animstream.c @@ -49,7 +49,7 @@ static void animationstream_destroy(GF_Node *node, void *rs, Bool is_destroy) } if (st->stream && as->isActive) { gf_mo_set_flag(st->stream, GF_MO_DISPLAY_REMOVE, 1); - gf_mo_stop(st->stream); + gf_mo_stop(&st->stream); } gf_sg_vrml_mf_reset(&st->current_url, GF_SG_VRML_MFURL); gf_free(st); @@ -79,7 +79,7 @@ static void animationstream_check_url(AnimationStreamStack *stack, M_AnimationSt /*if changed while playing stop old source*/ if (as->isActive) { gf_mo_set_flag(stack->stream, GF_MO_DISPLAY_REMOVE, 1); - gf_mo_stop(stack->stream); + gf_mo_stop(&stack->stream); } gf_mo_unregister((GF_Node *)as, stack->stream); @@ -121,7 +121,7 @@ static void animationstream_deactivate(AnimationStreamStack *stack, M_AnimationS if (stack->stream) { if (gf_mo_url_changed(stack->stream, &as->url)) gf_mo_set_flag(stack->stream, GF_MO_DISPLAY_REMOVE, 1); - gf_mo_stop(stack->stream); + gf_mo_stop(&stack->stream); } stack->time_handle.needs_unregister = 1; gf_sc_invalidate(stack->compositor, NULL); diff --git a/src/compositor/mpeg4_audio.c b/src/compositor/mpeg4_audio.c index 938eaa8..edd469a 100644 --- a/src/compositor/mpeg4_audio.c +++ b/src/compositor/mpeg4_audio.c @@ -28,14 +28,6 @@ #ifndef GPAC_DISABLE_VRML -typedef struct -{ - GF_AudioInput input; - GF_TimeNode time_handle; - Double start_time; - Bool set_duration, failure; -} AudioClipStack; - static void audioclip_activate(AudioClipStack *st, M_AudioClip *ac) { @@ -88,7 +80,7 @@ static void audioclip_traverse(GF_Node *node, void *rs, Bool is_destroy) if (ac->isActive) { gf_sc_audio_register(&st->input, (GF_TraverseState*)rs); } - if (st->set_duration && st->input.stream) { + if (st->set_duration && st->input.stream && st->input.stream->odm) { ac->duration_changed = gf_mo_get_duration(st->input.stream); gf_node_event_out(node, 6/*"duration_changed"*/); st->set_duration = GF_FALSE; @@ -104,7 +96,10 @@ static void audioclip_update_time(GF_TimeNode *tn) M_AudioClip *ac = (M_AudioClip *)tn->udta; AudioClipStack *st = (AudioClipStack *)gf_node_get_private((GF_Node*)tn->udta); - if (st->failure) return; + if (st->failure) { + st->time_handle.needs_unregister = GF_TRUE; + return; + } if (! ac->isActive) { st->start_time = ac->startTime; st->input.speed = ac->pitch; @@ -176,14 +171,6 @@ void compositor_audioclip_modified(GF_Node *node) } -typedef struct -{ - GF_AudioInput input; - GF_TimeNode time_handle; - Bool is_active; - Double start_time; -} AudioSourceStack; - static void audiosource_activate(AudioSourceStack *st, M_AudioSource *as) { if (gf_sc_audio_open(&st->input, &as->url, 0, -1, GF_FALSE) != GF_OK) @@ -449,7 +436,7 @@ static void audiobuffer_update_time(GF_TimeNode *tn) -static char *audiobuffer_fetch_frame(void *callback, u32 *size, u32 audio_delay_ms) +static u8 *audiobuffer_fetch_frame(void *callback, u32 *size, u32 *planar_stride, u32 audio_delay_ms) { u32 blockAlign; AudioBufferStack *st = (AudioBufferStack *) gf_node_get_private( ((GF_AudioInput *) callback)->owner); @@ -458,7 +445,7 @@ static char *audiobuffer_fetch_frame(void *callback, u32 *size, u32 audio_delay_ if (!st->is_init) return NULL; if (!st->buffer) { st->done = GF_FALSE; - st->buffer_size = (u32) ceil(FIX2FLT(ab->length) * st->output.input_ifce.bps*st->output.input_ifce.samplerate*st->output.input_ifce.chan/8); + st->buffer_size = (u32) ceil(FIX2FLT(ab->length) * gf_audio_fmt_bit_depth(st->output.input_ifce.afmt) * st->output.input_ifce.samplerate*st->output.input_ifce.chan/8); blockAlign = gf_mixer_get_block_align(st->am); /*BLOCK ALIGN*/ while (st->buffer_size%blockAlign) st->buffer_size++; @@ -518,7 +505,8 @@ static Bool audiobuffer_get_volume(void *callback, Fixed *vol) if (ai->snd->GetChannelVolume) { return ai->snd->GetChannelVolume(ai->snd->owner, vol); } else { - vol[0] = vol[1] = vol[2] = vol[3] = vol[4] = vol[5] = FIX_ONE; +// u32 i; +// for (i=0; icallback)->owner); if (gf_mixer_must_reconfig(st->am)) { + Bool force_config = GF_TRUE; if (gf_mixer_reconfig(st->am)) { if (st->buffer) gf_free(st->buffer); st->buffer = NULL; st->buffer_size = 0; } - gf_mixer_get_config(st->am, &aifc->samplerate, &aifc->chan, &aifc->bps, &aifc->ch_cfg); - st->is_init = (aifc->samplerate && aifc->chan && aifc->bps) ? GF_TRUE : GF_FALSE; + gf_mixer_get_config(st->am, &aifc->samplerate, &aifc->chan, &aifc->afmt, &aifc->ch_layout); + //we only work with packed formats + switch (aifc->afmt) { + case GF_AUDIO_FMT_U8P: + aifc->afmt = GF_AUDIO_FMT_U8; + break; + case GF_AUDIO_FMT_S16P: + aifc->afmt = GF_AUDIO_FMT_S16; + break; + case GF_AUDIO_FMT_S24P: + aifc->afmt = GF_AUDIO_FMT_S24; + break; + case GF_AUDIO_FMT_S32P: + aifc->afmt = GF_AUDIO_FMT_S32; + break; + case GF_AUDIO_FMT_FLTP: + aifc->afmt = GF_AUDIO_FMT_FLT; + break; + case GF_AUDIO_FMT_DBLP: + aifc->afmt = GF_AUDIO_FMT_DBL; + break; + default: + force_config = GF_FALSE; + break; + } + if (force_config) { + gf_mixer_set_config(st->am, aifc->samplerate, aifc->chan, aifc->afmt, aifc->ch_layout); + } + st->is_init = (aifc->samplerate && aifc->chan && aifc->afmt) ? GF_TRUE : GF_FALSE; assert(st->is_init); - if (!st->is_init) aifc->samplerate = aifc->chan = aifc->bps = aifc->ch_cfg = 0; + if (!st->is_init) { + aifc->samplerate = aifc->chan = aifc->afmt = 0; + aifc->ch_layout = 0; + } /*this will force invalidation*/ return (for_reconf && st->is_init) ? GF_TRUE : GF_FALSE; } diff --git a/src/compositor/mpeg4_background.c b/src/compositor/mpeg4_background.c index 6f55843..07eb718 100644 --- a/src/compositor/mpeg4_background.c +++ b/src/compositor/mpeg4_background.c @@ -132,14 +132,15 @@ static void back_build_dome(GF_Mesh *mesh, MFFloat *angles, MFColor *color, Bool mesh_reset(mesh); start_col.red = start_col.green = start_col.blue = 0; + start_col.alpha = FIX_ONE; end_col = start_col; + if (color->count) { COL_TO_RGBA(start_col, color->vals[0]); end_col = start_col; if (color->count>1) COL_TO_RGBA(end_col, color->vals[1]); } - start_col.alpha = end_col.alpha = FIX_ONE; vx.texcoords.x = vx.texcoords.y = 0; vx.color = MESH_MAKE_COL(start_col); vx.pos.x = vx.pos.z = 0; @@ -278,6 +279,8 @@ static void TraverseBackground(GF_Node *node, void *rs, Bool is_destroy) DestroyBackground(node); return; } + if (tr_state->visual->compositor->noback) + return; gf_node_dirty_clear(node, 0); bck = (M_Background *)node; @@ -316,8 +319,8 @@ static void TraverseBackground(GF_Node *node, void *rs, Bool is_destroy) if (tr_state->traversing_mode == TRAVERSE_SORT) { gf_mx_copy(st->current_mx, tr_state->model_matrix); if (!tr_state->pixel_metrics && tr_state->visual->compositor->inherit_type_3d) { - Fixed scale = gf_divfix(FIX_ONE, tr_state->min_hsize); - gf_mx_add_scale(&st->current_mx, scale, scale, scale); + Fixed pix_scale = gf_divfix(FIX_ONE, tr_state->min_hsize); + gf_mx_add_scale(&st->current_mx, pix_scale, pix_scale, pix_scale); } } diff --git a/src/compositor/mpeg4_background2d.c b/src/compositor/mpeg4_background2d.c index 417f9cf..a343d94 100644 --- a/src/compositor/mpeg4_background2d.c +++ b/src/compositor/mpeg4_background2d.c @@ -126,7 +126,7 @@ static void DrawBackground2D_2D(DrawableContext *ctx, GF_TraverseState *tr_state stack->txh.width = 2; stack->txh.height = 2; stack->txh.stride = 6; - stack->txh.pixelformat = GF_PIXEL_RGB_24; + stack->txh.pixelformat = GF_PIXEL_RGB; } if (use_texture) { @@ -322,6 +322,8 @@ static void TraverseBackground2D(GF_Node *node, void *rs, Bool is_destroy) DestroyBackground2D(node); return; } + if (tr_state->visual->compositor->noback) + return; bck = (M_Background2D *)node; @@ -338,7 +340,8 @@ static void TraverseBackground2D(GF_Node *node, void *rs, Bool is_destroy) /*first traverse, bound if needed*/ if (gf_list_find(tr_state->backgrounds, node) < 0) { - M_Background2D *top_bck = (M_Background2D *)node; + M_Background2D *top_bck; + gf_list_add(tr_state->backgrounds, node); assert(gf_list_find(stack->reg_stacks, tr_state->backgrounds)==-1); gf_list_add(stack->reg_stacks, tr_state->backgrounds); @@ -444,6 +447,14 @@ DrawableContext *b2d_get_context(M_Background2D *node, GF_List *from_stack) static void UpdateBackgroundTexture(GF_TextureHandler *txh) { gf_sc_texture_update_frame(txh, 0); + + if (!txh->compositor->player && !txh->compositor->passthrough_txh && txh->stream && txh->stream->odm && (txh->stream->odm->flags & GF_ODM_PASSTHROUGH)) { + if (!txh->width || ((txh->width==txh->compositor->display_width) && (txh->height==txh->compositor->display_height))) + txh->compositor->passthrough_txh = txh; + else + txh->compositor->passthrough_txh = NULL; + } + /*restart texture if needed (movie background controled by MediaControl)*/ if (txh->stream_finished && gf_mo_get_loop(txh->stream, 0)) gf_sc_texture_restart(txh); @@ -497,6 +508,7 @@ void compositor_background2d_modified(GF_Node *node) gf_sc_invalidate(st->txh.compositor, NULL); } +#if 0 //unused Bool compositor_background_transparent(GF_Node *node) { if (node && (gf_node_get_tag(node) == TAG_MPEG4_Background2D)) { @@ -510,5 +522,7 @@ Bool compositor_background_transparent(GF_Node *node) /*consider all other background nodes transparent*/ return 1; } +#endif + #endif /*GPAC_DISABLE_VRML*/ diff --git a/src/compositor/mpeg4_bitmap.c b/src/compositor/mpeg4_bitmap.c index b05ccaa..d9f92ef 100644 --- a/src/compositor/mpeg4_bitmap.c +++ b/src/compositor/mpeg4_bitmap.c @@ -37,7 +37,7 @@ typedef struct _bitmap_stack /*cached size for 3D mode*/ SFVec2f size, scale; u32 prev_tx_w, prev_tx_h; - GF_Rect rc; + GF_Rect rc, unclip_rc; } BitmapStack; @@ -185,9 +185,11 @@ static void draw_bitmap_2d(GF_Node *node, GF_TraverseState *tr_state) gf_mx2d_copy(_mat, ctx->transform); gf_mx2d_inverse(&_mat); gf_mx2d_apply_rect(&_mat, &rc); - - drawable_reset_path(st->graph); - gf_path_add_rect_center(st->graph->path, 0, 0, rc.width, rc.height); + if ((st->unclip_rc.width != rc.width) || (st->unclip_rc.height != rc.height)) { + drawable_reset_path(st->graph); + gf_path_add_rect_center(st->graph->path, 0, 0, rc.width, rc.height); + st->unclip_rc = rc; + } ctx->flags |= CTX_NO_ANTIALIAS; visual_2d_texture_path(tr_state->visual, st->graph->path, ctx, tr_state); } @@ -197,6 +199,7 @@ static void draw_bitmap_2d(GF_Node *node, GF_TraverseState *tr_state) static void TraverseBitmap(GF_Node *node, void *rs, Bool is_destroy) { GF_Rect rc; + Bool rectangle_check_adaptation(GF_Node *node, Drawable *stack, GF_TraverseState *tr_state); DrawableContext *ctx; BitmapStack *st = (BitmapStack *)gf_node_get_private(node); GF_TraverseState *tr_state = (GF_TraverseState *)rs; @@ -206,6 +209,8 @@ static void TraverseBitmap(GF_Node *node, void *rs, Bool is_destroy) gf_free(st); return; } + if (! rectangle_check_adaptation(node, st->graph, tr_state)) + return; switch (tr_state->traversing_mode) { case TRAVERSE_DRAW_2D: diff --git a/src/compositor/mpeg4_composite.c b/src/compositor/mpeg4_composite.c index f21a587..a0d92f2 100644 --- a/src/compositor/mpeg4_composite.c +++ b/src/compositor/mpeg4_composite.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / Scene Compositor sub-project @@ -47,8 +47,12 @@ typedef struct GF_List *sensors, *previous_sensors, *temp_sensors, *temp_previous_sensors; GF_Node *prev_hit_appear; + GF_TraverseState *tr_state; + #ifdef GPAC_USE_TINYGL ostgl_context *tgl_ctx; +#else + Bool use_fbo; #endif } CompositeTextureStack; @@ -92,7 +96,7 @@ static Bool composite2d_draw_bitmap(GF_VisualManager *visual, GF_TraverseState * video_src.pitch_y = ctx->aspect.fill_texture->stride; video_src.pixel_format = ctx->aspect.fill_texture->pixelformat; #ifdef GF_SR_USE_DEPTH - if (ctx->aspect.fill_texture->pixelformat==GF_PIXEL_YUVD) video_src.pixel_format = GF_PIXEL_YV12; + if (ctx->aspect.fill_texture->pixelformat==GF_PIXEL_YUVD) video_src.pixel_format = GF_PIXEL_YUV; #endif video_src.video_buffer = ctx->aspect.fill_texture->data; @@ -143,6 +147,9 @@ static void composite_traverse(GF_Node *node, void *rs, Bool is_destroy) gf_list_del(st->sensors); gf_list_del(st->previous_sensors); + gf_list_del(st->tr_state->vrml_sensors); + gf_free(st->tr_state); + gf_free(st); } else { gf_node_traverse_children(node, rs); @@ -249,18 +256,18 @@ static Bool composite_do_bindable(GF_Node *n, GF_TraverseState *tr_state, Bool f static void composite_update(GF_TextureHandler *txh) { s32 w, h; - GF_STENCIL stencil; + GF_EVGStencil *stencil; +#ifndef GPAC_USE_GLES1X M_Background2D *back; - GF_TraverseState *tr_state; +#endif + GF_List *sensor_bck; Bool invalidate_all; u32 new_pixel_format; GF_Compositor *compositor = (GF_Compositor *)txh->compositor; CompositeTextureStack *st = (CompositeTextureStack *) gf_node_get_private(txh->owner); - GF_Raster2D *raster = st->visual->compositor->rasterizer; if (st->unsupported) return; - /* if (compositor->recompute_ar) { gf_node_dirty_set(txh->owner, 0, 0); @@ -273,28 +280,27 @@ static void composite_update(GF_TextureHandler *txh) } gf_node_dirty_clear(st->txh.owner, 0); + /*in OpenGL_ES, only RGBA can be safelly used with glReadPixels*/ +#if defined(GPAC_USE_GLES1X) + new_pixel_format = GF_PIXEL_RGBA; + +#else + back = gf_list_get(st->visual->back_stack, 0); - if (back && back->isBound) new_pixel_format = GF_PIXEL_RGB_24; + if (back && back->isBound) new_pixel_format = GF_PIXEL_RGB; else new_pixel_format = GF_PIXEL_RGBA; - #ifdef GPAC_USE_TINYGL /*TinyGL pixel format is fixed at compile time, we cannot override it !*/ if (st->visual->type_3d) new_pixel_format = GF_PIXEL_RGBA; -#else -#ifndef GPAC_DISABLE_3D +#elif !defined(GPAC_DISABLE_3D) /*no alpha support in offscreen rendering*/ - if (!compositor->visual->type_3d && !compositor->hybrid_opengl && (st->visual->type_3d) && !(compositor->video_out->hw_caps & GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA)) - new_pixel_format = GF_PIXEL_RGB_24; -#endif - - /*in OpenGL_ES, only RGBA can be safelly used with glReadPixels*/ -#ifdef GPAC_USE_GLES1X - new_pixel_format = GF_PIXEL_RGBA; + if (!compositor->visual->type_3d && !compositor->hybrid_opengl && !compositor->fbo_id && (st->visual->type_3d) && !(compositor->video_out->hw_caps & GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA)) + new_pixel_format = GF_PIXEL_RGB; #endif -#endif +#endif //GPAC_USE_GLES1X /*FIXME - we assume RGB+Depth+bitshape, we should check with the video out module*/ #if defined(GF_SR_USE_DEPTH) && !defined(GPAC_DISABLE_3D) @@ -321,18 +327,18 @@ static void composite_update(GF_TextureHandler *txh) if (new_pixel_format==GF_PIXEL_RGBA) { new_pixel_format=GF_PIXEL_ARGB; } else { - new_pixel_format=GF_PIXEL_BGR_24; + new_pixel_format=GF_PIXEL_BGR; } } else { - if (new_pixel_format==GF_PIXEL_RGB_24) { - new_pixel_format=GF_PIXEL_RGB_32; + if (new_pixel_format==GF_PIXEL_RGB) { + new_pixel_format=GF_PIXEL_RGBX; } } } else if (h<-1) { h = -h; - if (new_pixel_format==GF_PIXEL_RGB_24) { - new_pixel_format=GF_PIXEL_RGB_32; + if (new_pixel_format==GF_PIXEL_RGB) { + new_pixel_format=GF_PIXEL_RGBX; } } @@ -372,7 +378,7 @@ static void composite_update(GF_TextureHandler *txh) } /*we don't use rect ext because of no support for texture transforms*/ - if (1 + if ((1) #ifndef GPAC_DISABLE_3D || compositor->gl_caps.npot_texture #endif @@ -406,7 +412,7 @@ static void composite_update(GF_TextureHandler *txh) txh->stride = txh->width * 4; txh->transparent = 1; break; - case GF_PIXEL_RGB_24: + case GF_PIXEL_RGB: txh->stride = txh->width * 3; txh->transparent = 0; break; @@ -414,36 +420,21 @@ static void composite_update(GF_TextureHandler *txh) st->visual->width = txh->width; st->visual->height = txh->height; + st->use_fbo = GF_FALSE; - stencil = raster->stencil_new(raster, GF_STENCIL_TEXTURE); + stencil = gf_evg_stencil_new(GF_STENCIL_TEXTURE); #ifndef GPAC_DISABLE_3D if (st->visual->type_3d) { - GF_Compositor *compositor = st->visual->compositor; /*figure out what to do if main visual (eg video out) is not in OpenGL ...*/ if (!compositor->visual->type_3d && !compositor->hybrid_opengl) { /*create an offscreen window for OpenGL rendering*/ - if ((compositor->offscreen_width < st->txh.width) || (compositor->offscreen_height < st->txh.height)) { -#ifndef GPAC_USE_TINYGL - GF_Err e; - GF_Event evt; - compositor->offscreen_width = MAX(compositor->offscreen_width, st->txh.width); - compositor->offscreen_height = MAX(compositor->offscreen_height, st->txh.height); - - evt.type = GF_EVENT_VIDEO_SETUP; - evt.setup.width = compositor->offscreen_width; - evt.setup.height = compositor->offscreen_height; - evt.setup.back_buffer = 0; - evt.setup.opengl_mode = 2; - e = compositor->video_out->ProcessEvent(compositor->video_out, &evt); - if (e) { - gf_sc_texture_release(txh); - st->unsupported = 1; - return; - } - /*reload openGL ext*/ - gf_sc_load_opengl_extensions(compositor, 1); -#endif + if (!compositor->fbo_id + && ((compositor->offscreen_width < st->txh.width) || (compositor->offscreen_height < st->txh.height)) + ) { + + GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[CompositeTexture] Offscreen OpenGL is not possible if no openGL context is created - use hybridGL mode for compositor\n")); + st->unsupported = GF_TRUE; } } else { needs_stencil = 0; @@ -457,7 +448,7 @@ static void composite_update(GF_TextureHandler *txh) /*set stencil texture - we don't check error as an image could not be supported by the rasterizer but still supported by the blitter (case of RGBD/RGBDS)*/ - raster->stencil_set_texture(stencil, txh->data, txh->width, txh->height, txh->stride, txh->pixelformat, txh->pixelformat, 0); + gf_evg_stencil_set_texture(stencil, txh->data, txh->width, txh->height, txh->stride, txh->pixelformat); #ifdef GPAC_USE_TINYGL if (st->visual->type_3d && !compositor->visual->type_3d) { @@ -465,8 +456,14 @@ static void composite_update(GF_TextureHandler *txh) GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[CompositeTexture] Creating TinyGL Offscreen context %p (%d %d - pf %s)\n", st->tgl_ctx, txh->width, txh->width, gf_4cc_to_str(txh->pixelformat))); } #endif - } +#if !defined(GPAC_USE_TINYGL) && !defined(GPAC_DISABLE_3D) + else if (compositor->gl_caps.fbo) { + if (gf_sc_texture_setup_fbo(&st->txh)==GF_OK) + st->use_fbo = GF_TRUE; + } +#endif + invalidate_all = 1; gf_sc_texture_set_stencil(txh, stencil); } @@ -479,423 +476,442 @@ static void composite_update(GF_TextureHandler *txh) #else if (compositor->visual->type_3d) { st->visual->type_3d = 2; - } else if (compositor->hybrid_opengl) { + } else if (compositor->hybrid_opengl || compositor->fbo_id) { st->visual->type_3d = 2; } else if (! (compositor->video_out->hw_caps & GF_VIDEO_HW_OPENGL_OFFSCREEN)) { st->visual->type_3d = 0; } else { st->visual->type_3d = 2; } - } #endif - + } #endif - stencil = gf_sc_texture_get_stencil(txh); - if (!stencil) return; + stencil = gf_sc_texture_get_stencil(txh); + if (!stencil) return; #ifdef GPAC_USE_TINYGL - if (st->tgl_ctx) ostgl_make_current(st->tgl_ctx, 0); + if (st->tgl_ctx) + ostgl_make_current(st->tgl_ctx, 0); +#elif !defined(GPAC_DISABLE_3D) + if (st->use_fbo) { + gf_sc_texture_enable_fbo(&st->txh, GF_TRUE); + } #endif - GF_SAFEALLOC(tr_state, GF_TraverseState); - if (!tr_state) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate traverse state for composite texture\n")); - return; - } - tr_state->vrml_sensors = gf_list_new(); - tr_state->visual = st->visual; - tr_state->invalidate_all = invalidate_all; + sensor_bck = st->tr_state->vrml_sensors; + memset(st->tr_state, 0, sizeof(GF_TraverseState)); + st->tr_state->vrml_sensors = sensor_bck; + st->tr_state->visual = st->visual; + st->tr_state->invalidate_all = invalidate_all; - tr_state->immediate_draw = st->visual->compositor->traverse_state->immediate_draw; + st->tr_state->immediate_draw = st->visual->compositor->traverse_state->immediate_draw; - gf_mx2d_init(tr_state->transform); - gf_cmx_init(&tr_state->color_mat); + gf_mx2d_init(st->tr_state->transform); + gf_cmx_init(&st->tr_state->color_mat); - tr_state->backgrounds = st->visual->back_stack; - tr_state->viewpoints = st->visual->view_stack; - tr_state->pixel_metrics = gf_sg_use_pixel_metrics(gf_node_get_graph(st->txh.owner)); - tr_state->min_hsize = INT2FIX( MIN(txh->width, txh->height) ) / 2; - tr_state->vp_size.x = INT2FIX(txh->width); - tr_state->vp_size.y = INT2FIX(txh->height); + st->tr_state->backgrounds = st->visual->back_stack; + st->tr_state->viewpoints = st->visual->view_stack; + st->tr_state->pixel_metrics = gf_sg_use_pixel_metrics(gf_node_get_graph(st->txh.owner)); + st->tr_state->min_hsize = INT2FIX( MIN(txh->width, txh->height) ) / 2; + st->tr_state->vp_size.x = INT2FIX(txh->width); + st->tr_state->vp_size.y = INT2FIX(txh->height); - composite_do_bindable(st->txh.owner, tr_state, st->first); - st->first = 0; + composite_do_bindable(st->txh.owner, st->tr_state, st->first); + st->first = 0; - GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[CompositeTexture] Entering draw cycle\n")); + GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[CompositeTexture] Entering draw cycle\n")); - txh->needs_refresh = visual_draw_frame(st->visual, st->txh.owner, tr_state, 0); - txh->transparent = (st->visual->last_had_back==2) ? 0 : 1; + txh->needs_refresh = visual_draw_frame(st->visual, st->txh.owner, st->tr_state, 0); + txh->transparent = (st->visual->last_had_back==2) ? 0 : 1; - if (!compositor->edited_text && st->visual->has_text_edit) - st->visual->has_text_edit = 0; + if (!compositor->edited_text && st->visual->has_text_edit) + st->visual->has_text_edit = 0; - /*set active viewport in image coordinates top-left=(0, 0), not in BIFS*/ - if (0 && gf_list_count(st->visual->view_stack)) { - M_Viewport *vp = (M_Viewport *)gf_list_get(st->visual->view_stack, 0); +#if 0 + /*set active viewport in image coordinates top-left=(0, 0), not in BIFS*/ + if (gf_list_count(st->visual->view_stack)) { + M_Viewport *vp = (M_Viewport *)gf_list_get(st->visual->view_stack, 0); - if (vp->isBound) { - SFVec2f size = vp->size; - if (size.x >=0 && size.y>=0) { - /*FIXME - we need tracking of VP changes*/ - txh->needs_refresh = 1; - } + if (vp->isBound) { + SFVec2f size = vp->size; + if (size.x >=0 && size.y>=0) { + /*FIXME - we need tracking of VP changes*/ + txh->needs_refresh = 1; } } + } +#endif - if (txh->needs_refresh) { + if (txh->needs_refresh) { #ifndef GPAC_DISABLE_3D - //for composite 3D, store current buffer to texture - TODO: use FBOs to avoid the copy ... - if (st->visual->camera.is_3D && (st->visual->compositor->visual->type_3d || st->visual->compositor->hybrid_opengl)) { + #ifndef GPAC_USE_TINYGL - gf_sc_copy_to_texture(&st->txh); + if (st->use_fbo) { + gf_sc_texture_enable_fbo(&st->txh, GF_FALSE); + } else #endif - } - else if (st->visual->camera.is_3D) { - if (st->visual->compositor->visual->type_3d) { + //no FBO, for composite 3D, store current buffer to texture + if (st->visual->camera.is_3D && (st->visual->compositor->visual->type_3d || st->visual->compositor->hybrid_opengl)) { #ifndef GPAC_USE_TINYGL - gf_sc_copy_to_texture(&st->txh); -#else - /*in TinyGL we only need to push associated bitmap to the texture*/ - gf_sc_texture_push_image(&st->txh, 0, 0); + gf_sc_copy_to_texture(&st->txh); #endif - } else { - + } + else if (st->visual->camera.is_3D) { + if (st->visual->compositor->visual->type_3d) { #ifndef GPAC_USE_TINYGL - gf_sc_copy_to_stencil(&st->txh); - + gf_sc_copy_to_texture(&st->txh); #else - - if (txh->pixelformat==GF_PIXEL_RGBDS) gf_get_tinygl_depth(&st->txh); + /*in TinyGL we only need to push associated bitmap to the texture*/ + gf_sc_texture_push_image(&st->txh, 0, 0); #endif - } - } else + } else { +#ifndef GPAC_USE_TINYGL + gf_sc_copy_to_stencil(&st->txh); +#else + if (txh->pixelformat==GF_PIXEL_RGBDS) + gf_get_tinygl_depth(&st->txh); #endif - { - if (raster->stencil_texture_modified) raster->stencil_texture_modified(stencil); - gf_sc_texture_set_stencil(txh, stencil); } - gf_sc_invalidate(st->txh.compositor, NULL); + } else +#endif + { + gf_sc_texture_set_stencil(txh, stencil); } - gf_list_del(tr_state->vrml_sensors); - gf_free(tr_state); - GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[CompositeTexture] Leaving draw cycle\n")); + gf_sc_invalidate(st->txh.compositor, NULL); } + GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[CompositeTexture] Leaving draw cycle\n")); +} - GF_Err composite_get_video_access(GF_VisualManager *visual) - { - GF_STENCIL stencil; - GF_Err e; - CompositeTextureStack *st = (CompositeTextureStack *) gf_node_get_private(visual->offscreen); - - if (!st->txh.tx_io || !visual->raster_surface) return GF_BAD_PARAM; - stencil = gf_sc_texture_get_stencil(&st->txh); - if (!stencil) return GF_BAD_PARAM; - e = visual->compositor->rasterizer->surface_attach_to_texture(visual->raster_surface, stencil); - if (!e) visual->is_attached = 1; - return e; - } +GF_Err composite_get_video_access(GF_VisualManager *visual) +{ + GF_EVGStencil *stencil; + GF_Err e; + CompositeTextureStack *st = (CompositeTextureStack *) gf_node_get_private(visual->offscreen); + + if (!st->txh.tx_io || !visual->raster_surface) return GF_BAD_PARAM; + stencil = gf_sc_texture_get_stencil(&st->txh); + if (!stencil) return GF_BAD_PARAM; + e = gf_evg_surface_attach_to_texture(visual->raster_surface, stencil); + if (!e) visual->is_attached = 1; + return e; +} - void composite_release_video_access(GF_VisualManager *visual) - { - visual->compositor->rasterizer->surface_detach(visual->raster_surface); +void composite_release_video_access(GF_VisualManager *visual) +{ +} + +Bool composite_check_visual_attached(GF_VisualManager *visual) +{ + return visual->is_attached; +} + +void compositor_init_compositetexture2d(GF_Compositor *compositor, GF_Node *node) +{ + M_CompositeTexture2D *c2d = (M_CompositeTexture2D *)node; + CompositeTextureStack *st; + GF_SAFEALLOC(st, CompositeTextureStack); + + if (!st) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate composite texture stack\n")); + return; } - Bool composite_check_visual_attached(GF_VisualManager *visual) - { - return visual->is_attached; + GF_SAFEALLOC(st->tr_state, GF_TraverseState); + if (!st->tr_state) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate composite texture state\n")); + return; } + st->tr_state->vrml_sensors = gf_list_new(); - void compositor_init_compositetexture2d(GF_Compositor *compositor, GF_Node *node) - { - M_CompositeTexture2D *c2d = (M_CompositeTexture2D *)node; - CompositeTextureStack *st; - GF_SAFEALLOC(st, CompositeTextureStack); + st->sensors = gf_list_new(); + st->previous_sensors = gf_list_new(); + gf_sc_texture_setup(&st->txh, compositor, node); + /*remove texture from compositor and add it at the end, so that any sub-textures are handled before*/ + gf_list_del_item(compositor->textures, &st->txh); + gf_list_add(compositor->textures, &st->txh); - if (!st) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate composite texture stack\n")); - return; - } - st->sensors = gf_list_new(); - st->previous_sensors = gf_list_new(); - gf_sc_texture_setup(&st->txh, compositor, node); - /*remove texture from compositor and add it at the end, so that any sub-textures are handled before*/ - gf_list_del_item(compositor->textures, &st->txh); - gf_list_add(compositor->textures, &st->txh); - - st->txh.update_texture_fcnt = composite_update; - - if ((c2d->repeatSandT==1) || (c2d->repeatSandT==3) ) st->txh.flags |= GF_SR_TEXTURE_REPEAT_S; - if (c2d->repeatSandT>1) st->txh.flags |= GF_SR_TEXTURE_REPEAT_T; - - /*create composite visual*/ - st->visual = visual_new(compositor); - st->visual->offscreen = node; - st->visual->GetSurfaceAccess = composite_get_video_access; - st->visual->ReleaseSurfaceAccess = composite_release_video_access; - st->visual->DrawBitmap = composite2d_draw_bitmap; - st->visual->CheckAttached = composite_check_visual_attached; - - st->visual->raster_surface = compositor->rasterizer->surface_new(compositor->rasterizer, 1); - - - st->first = 1; - st->visual->compositor = compositor; - gf_node_set_private(node, st); - gf_node_set_callback_function(node, composite_traverse); - gf_sc_visual_register(compositor, st->visual); - } + st->txh.update_texture_fcnt = composite_update; + + if ((c2d->repeatSandT==1) || (c2d->repeatSandT==3) ) st->txh.flags |= GF_SR_TEXTURE_REPEAT_S; + if (c2d->repeatSandT>1) st->txh.flags |= GF_SR_TEXTURE_REPEAT_T; + + /*create composite visual*/ + st->visual = visual_new(compositor); + st->visual->offscreen = node; + st->visual->GetSurfaceAccess = composite_get_video_access; + st->visual->ReleaseSurfaceAccess = composite_release_video_access; + st->visual->DrawBitmap = composite2d_draw_bitmap; + st->visual->CheckAttached = composite_check_visual_attached; + + st->visual->raster_surface = gf_evg_surface_new(1); + + + st->first = 1; + st->visual->compositor = compositor; + gf_node_set_private(node, st); + gf_node_set_callback_function(node, composite_traverse); + gf_sc_visual_register(compositor, st->visual); +} #ifndef GPAC_DISABLE_3D - void compositor_init_compositetexture3d(GF_Compositor *compositor, GF_Node *node) - { - M_CompositeTexture3D *c3d = (M_CompositeTexture3D *)node; - CompositeTextureStack *st; - GF_SAFEALLOC(st, CompositeTextureStack); - if (!st) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate composite texture stack\n")); - return; - } - st->sensors = gf_list_new(); - st->previous_sensors = gf_list_new(); - gf_sc_texture_setup(&st->txh, compositor, node); - /*remove texture from compositor and add it at the end, so that any sub-textures are handled before*/ - gf_list_del_item(compositor->textures, &st->txh); - gf_list_add(compositor->textures, &st->txh); - - st->txh.update_texture_fcnt = composite_update; - - if (c3d->repeatS) st->txh.flags |= GF_SR_TEXTURE_REPEAT_S; - if (c3d->repeatT) st->txh.flags |= GF_SR_TEXTURE_REPEAT_T; - - /*create composite visual*/ - st->visual = visual_new(compositor); - st->visual->offscreen = node; - st->visual->GetSurfaceAccess = composite_get_video_access; - st->visual->ReleaseSurfaceAccess = composite_release_video_access; - st->visual->CheckAttached = composite_check_visual_attached; - - st->visual->camera.is_3D = 1; - st->first = 1; - st->visual->compositor = compositor; - gf_node_set_private(node, st); - gf_node_set_callback_function(node, composite_traverse); - gf_sc_visual_register(compositor, st->visual); - - camera_invalidate(&st->visual->camera); +void compositor_init_compositetexture3d(GF_Compositor *compositor, GF_Node *node) +{ + M_CompositeTexture3D *c3d = (M_CompositeTexture3D *)node; + CompositeTextureStack *st; + + if (!gf_sc_check_gl_support(compositor)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] Driver disabled, cannot render 3D composite textures\n")); + return; } -#endif - GF_TextureHandler *compositor_get_composite_texture(GF_Node *node) - { - CompositeTextureStack *st = (CompositeTextureStack*) gf_node_get_private(node); - return &st->txh; + GF_SAFEALLOC(st, CompositeTextureStack); + if (!st) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate composite texture stack\n")); + return; + } + GF_SAFEALLOC(st->tr_state, GF_TraverseState); + if (!st->tr_state) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate composite texture state\n")); + return; } - Bool compositor_compositetexture_handle_event(GF_Compositor *compositor, GF_Node *composite_appear, GF_Event *ev, Bool is_flush) - { - GF_Ray ray; - Fixed dist; - Bool had_text_sel=0; - GF_Matrix mx; - GF_TraverseState *tr_state; - GF_ChildNodeItem *children, *l; - Bool res; - SFVec3f txcoord, loc_pt, world_pt; - GF_Matrix l2w_mx, w2l_mx; - CompositeTextureStack *stack; - GF_Node *appear, *prev_appear; - M_Appearance *ap = (M_Appearance *)composite_appear; - assert(ap && ap->texture); - - if (ev->type > GF_EVENT_MOUSEMOVE) return 0; - stack = gf_node_get_private(ap->texture); - if (!stack->txh.tx_io) return 0; - - tr_state = NULL; - children = NULL; - - - if (!is_flush) { - txcoord.x = compositor->hit_texcoords.x; - txcoord.y = compositor->hit_texcoords.y; - txcoord.z = 0; - if (gf_sc_texture_get_transform(&stack->txh, ap->textureTransform, &mx, 1)) { - /*tx coords are inverted when mapping, thus applying directly the matrix will give us the - untransformed coords*/ - gf_mx_apply_vec(&mx, &txcoord); - while (txcoord.x<0) txcoord.x += FIX_ONE; - while (txcoord.x>FIX_ONE) txcoord.x -= FIX_ONE; - while (txcoord.y<0) txcoord.y += FIX_ONE; - while (txcoord.y>FIX_ONE) txcoord.y -= FIX_ONE; - } + st->tr_state->vrml_sensors = gf_list_new(); - /*convert to tx space*/ - ev->mouse.x = FIX2INT( (txcoord.x - FIX_ONE/2) * stack->visual->width + FIX_ONE/2); - ev->mouse.y = FIX2INT( (txcoord.y - FIX_ONE/2) * stack->visual->height + FIX_ONE/2); + st->sensors = gf_list_new(); + st->previous_sensors = gf_list_new(); + gf_sc_texture_setup(&st->txh, compositor, node); + /*remove texture from compositor and add it at the end, so that any sub-textures are handled before*/ + gf_list_del_item(compositor->textures, &st->txh); + gf_list_add(compositor->textures, &st->txh); + st->txh.update_texture_fcnt = composite_update; - GF_SAFEALLOC(tr_state, GF_TraverseState); - if (!tr_state) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate composite texture state\n")); - return 0; - } - tr_state->vrml_sensors = gf_list_new(); - tr_state->visual = stack->visual; - tr_state->traversing_mode = TRAVERSE_PICK; - tr_state->pixel_metrics = gf_sg_use_pixel_metrics(gf_node_get_graph(ap->texture)); - tr_state->vp_size.x = INT2FIX(stack->txh.width); - tr_state->vp_size.y = INT2FIX(stack->txh.height); - tr_state->color_mat.identity = 1; - - gf_mx2d_init(tr_state->transform); + if (c3d->repeatS) st->txh.flags |= GF_SR_TEXTURE_REPEAT_S; + if (c3d->repeatT) st->txh.flags |= GF_SR_TEXTURE_REPEAT_T; + + /*create composite visual*/ + st->visual = visual_new(compositor); + st->visual->offscreen = node; + st->visual->GetSurfaceAccess = composite_get_video_access; + st->visual->ReleaseSurfaceAccess = composite_release_video_access; + st->visual->CheckAttached = composite_check_visual_attached; + + st->visual->camera.is_3D = 1; + st->first = 1; + st->visual->compositor = compositor; + gf_node_set_private(node, st); + gf_node_set_callback_function(node, composite_traverse); + gf_sc_visual_register(compositor, st->visual); + + camera_invalidate(&st->visual->camera); +} +#endif + +GF_TextureHandler *compositor_get_composite_texture(GF_Node *node) +{ + CompositeTextureStack *st = (CompositeTextureStack*) gf_node_get_private(node); + return &st->txh; +} + +Bool compositor_compositetexture_handle_event(GF_Compositor *compositor, GF_Node *composite_appear, GF_Event *ev, Bool is_flush) +{ + GF_Ray ray; + Fixed dist; + Bool had_text_sel=0; + GF_Matrix mx; + GF_ChildNodeItem *children, *l; + Bool res; + SFVec3f txcoord, loc_pt, world_pt; + GF_Matrix l2w_mx, w2l_mx; + CompositeTextureStack *stack; + GF_Node *appear, *prev_appear; + GF_List *sensor_bck; + M_Appearance *ap = (M_Appearance *)composite_appear; + assert(ap && ap->texture); + + if (ev->type > GF_EVENT_MOUSEMOVE) return 0; + stack = gf_node_get_private(ap->texture); + if (!stack->txh.tx_io) return 0; + + children = NULL; + + + if (!is_flush) { + txcoord.x = compositor->hit_texcoords.x; + txcoord.y = compositor->hit_texcoords.y; + txcoord.z = 0; + if (gf_sc_texture_get_transform(&stack->txh, ap->textureTransform, &mx, 1)) { + /*tx coords are inverted when mapping, thus applying directly the matrix will give us the + untransformed coords*/ + gf_mx_apply_vec(&mx, &txcoord); + while (txcoord.x<0) txcoord.x += FIX_ONE; + while (txcoord.x>FIX_ONE) txcoord.x -= FIX_ONE; + while (txcoord.y<0) txcoord.y += FIX_ONE; + while (txcoord.y>FIX_ONE) txcoord.y -= FIX_ONE; + } + + /*convert to tx space*/ + ev->mouse.x = FIX2INT( (txcoord.x - FIX_ONE/2) * stack->visual->width + FIX_ONE/2); + ev->mouse.y = FIX2INT( (txcoord.y - FIX_ONE/2) * stack->visual->height + FIX_ONE/2); + + sensor_bck = stack->tr_state->vrml_sensors; + memset(stack->tr_state, 0, sizeof(GF_TraverseState)); + stack->tr_state->vrml_sensors = sensor_bck; + + stack->tr_state->visual = stack->visual; + stack->tr_state->traversing_mode = TRAVERSE_PICK; + stack->tr_state->pixel_metrics = gf_sg_use_pixel_metrics(gf_node_get_graph(ap->texture)); + stack->tr_state->vp_size.x = INT2FIX(stack->txh.width); + stack->tr_state->vp_size.y = INT2FIX(stack->txh.height); + stack->tr_state->color_mat.identity = 1; + + gf_mx2d_init(stack->tr_state->transform); #ifndef GPAC_DISABLE_3D - gf_mx_init(tr_state->model_matrix); + gf_mx_init(stack->tr_state->model_matrix); #endif - /*collect sensors but not anchors*/ - l = children = ((M_CompositeTexture2D*)ap->texture)->children; - while (l) { - GF_SensorHandler *hsens = compositor_mpeg4_get_sensor_handler_ex(l->node, GF_TRUE); - if (hsens) gf_list_add(tr_state->vrml_sensors, hsens); - l = l->next; - } + /*collect sensors but not anchors*/ + l = children = ((M_CompositeTexture2D*)ap->texture)->children; + while (l) { + GF_SensorHandler *hsens = compositor_mpeg4_get_sensor_handler_ex(l->node, GF_TRUE); + if (hsens) gf_list_add(stack->tr_state->vrml_sensors, hsens); + l = l->next; } + } - stack->temp_sensors = compositor->sensors; - stack->temp_previous_sensors = compositor->previous_sensors; - compositor->sensors = stack->sensors; - compositor->previous_sensors = stack->previous_sensors; + stack->temp_sensors = compositor->sensors; + stack->temp_previous_sensors = compositor->previous_sensors; + compositor->sensors = stack->sensors; + compositor->previous_sensors = stack->previous_sensors; - ray = compositor->hit_world_ray; - dist = compositor->hit_square_dist; - prev_appear = compositor->prev_hit_appear; + ray = compositor->hit_world_ray; + dist = compositor->hit_square_dist; + prev_appear = compositor->prev_hit_appear; - /*protect against destrucion in case of self-destroy*/ - if (prev_appear) { - gf_node_register(prev_appear, NULL); - } - compositor->prev_hit_appear = stack->prev_hit_appear; - appear = compositor->hit_appear; - compositor->hit_appear = NULL; - - /*also backup current hit state in case we hit a node in the texture but don't consume the event*/ - loc_pt = compositor->hit_local_point; - world_pt = compositor->hit_world_point; - gf_mx_copy(l2w_mx, compositor->hit_local_to_world); - gf_mx_copy(w2l_mx, compositor->hit_world_to_local); - - if (compositor->text_selection) had_text_sel=1; - - if (is_flush) { - res = 0; - gf_list_reset(stack->sensors); - gf_sc_exec_event_vrml(compositor, ev); - } else { - res = visual_execute_event(stack->visual, tr_state, ev, children); - } + /*protect against destrucion in case of self-destroy*/ + if (prev_appear) { + gf_node_register(prev_appear, NULL); + } + compositor->prev_hit_appear = stack->prev_hit_appear; + appear = compositor->hit_appear; + compositor->hit_appear = NULL; + + /*also backup current hit state in case we hit a node in the texture but don't consume the event*/ + loc_pt = compositor->hit_local_point; + world_pt = compositor->hit_world_point; + gf_mx_copy(l2w_mx, compositor->hit_local_to_world); + gf_mx_copy(w2l_mx, compositor->hit_world_to_local); + + if (compositor->text_selection) had_text_sel=1; + + if (is_flush) { + res = 0; + gf_list_reset(stack->sensors); + gf_sc_exec_event_vrml(compositor, ev); + } else { + res = visual_execute_event(stack->visual, stack->tr_state, ev, children); + } - if (!had_text_sel && compositor->edited_text) { - stack->visual->has_text_edit = 1; - } else if (!compositor->text_selection) { - stack->visual->has_text_edit = 0; - } + if (!had_text_sel && compositor->edited_text) { + stack->visual->has_text_edit = 1; + } else if (!compositor->text_selection) { + stack->visual->has_text_edit = 0; + } - if (!res) { - compositor->hit_local_point = loc_pt; - gf_mx_copy(compositor->hit_local_to_world, l2w_mx); - gf_mx_copy(compositor->hit_world_to_local, w2l_mx); - } + if (!res) { + compositor->hit_local_point = loc_pt; + gf_mx_copy(compositor->hit_local_to_world, l2w_mx); + gf_mx_copy(compositor->hit_world_to_local, w2l_mx); + } - compositor->hit_world_point = world_pt; - compositor->hit_world_ray = ray; - compositor->hit_square_dist = dist; + compositor->hit_world_point = world_pt; + compositor->hit_world_ray = ray; + compositor->hit_square_dist = dist; - stack->sensors = compositor->sensors; - stack->previous_sensors = compositor->previous_sensors; - compositor->sensors = stack->temp_sensors; - stack->temp_sensors = NULL; - compositor->previous_sensors = stack->temp_previous_sensors; - stack->temp_previous_sensors = NULL; + stack->sensors = compositor->sensors; + stack->previous_sensors = compositor->previous_sensors; + compositor->sensors = stack->temp_sensors; + stack->temp_sensors = NULL; + compositor->previous_sensors = stack->temp_previous_sensors; + stack->temp_previous_sensors = NULL; - if (!is_flush) { - gf_list_del(tr_state->vrml_sensors); + if (!is_flush) { #ifndef GPAC_DISABLE_3D - if (tr_state->layer3d) compositor->traverse_state->layer3d = tr_state->layer3d; + if (stack->tr_state->layer3d) compositor->traverse_state->layer3d = stack->tr_state->layer3d; #endif - gf_free(tr_state); - } + } - stack->prev_hit_appear = compositor->prev_hit_appear; + stack->prev_hit_appear = compositor->prev_hit_appear; - //finally unregister the node, this may destroy the stack ! - if (prev_appear) { - if (prev_appear->sgprivate->num_instances>1) { - compositor->prev_hit_appear = prev_appear; - compositor->hit_appear = appear; - } else { - compositor->prev_hit_appear = NULL; - compositor->hit_appear = NULL; - } - gf_node_unregister(prev_appear, NULL); - } else { + //finally unregister the node, this may destroy the stack ! + if (prev_appear) { + if (prev_appear->sgprivate->num_instances>1) { compositor->prev_hit_appear = prev_appear; compositor->hit_appear = appear; + } else { + compositor->prev_hit_appear = NULL; + compositor->hit_appear = NULL; } - - return res; + gf_node_unregister(prev_appear, NULL); + } else { + compositor->prev_hit_appear = prev_appear; + compositor->hit_appear = appear; } - void compositor_compositetexture_sensor_delete(GF_Node *composite_appear, GF_SensorHandler *hdl) - { - CompositeTextureStack *stack = gf_node_get_private(composite_appear); - gf_list_del_item(stack->previous_sensors, hdl); - gf_list_del_item(stack->sensors, hdl); - if (stack->temp_sensors) - gf_list_del_item(stack->temp_sensors, hdl); - if (stack->temp_previous_sensors) - gf_list_del_item(stack->temp_previous_sensors, hdl); - } + return res; +} + +void compositor_compositetexture_sensor_delete(GF_Node *composite_appear, GF_SensorHandler *hdl) +{ + CompositeTextureStack *stack = gf_node_get_private(composite_appear); + gf_list_del_item(stack->previous_sensors, hdl); + gf_list_del_item(stack->sensors, hdl); + if (stack->temp_sensors) + gf_list_del_item(stack->temp_sensors, hdl); + if (stack->temp_previous_sensors) + gf_list_del_item(stack->temp_previous_sensors, hdl); +} - void compositor_adjust_scale(GF_Node *node, Fixed *sx, Fixed *sy) +void compositor_adjust_scale(GF_Node *node, Fixed *sx, Fixed *sy) +{ + switch (gf_node_get_tag(node)) { + case TAG_MPEG4_CompositeTexture2D: + case TAG_MPEG4_CompositeTexture3D: { - switch (gf_node_get_tag(node)) { - case TAG_MPEG4_CompositeTexture2D: - case TAG_MPEG4_CompositeTexture3D: - { - CompositeTextureStack *st = (CompositeTextureStack *) gf_node_get_private(node); - (*sx) = gf_divfix(*sx, st->sx); - (*sy) = gf_divfix(*sy, st->sy); - break; - } - default: - return; - } + CompositeTextureStack *st = (CompositeTextureStack *) gf_node_get_private(node); + (*sx) = gf_divfix(*sx, st->sx); + (*sy) = gf_divfix(*sy, st->sy); + break; + } + default: + return; } +} - Bool compositor_is_composite_texture(GF_Node *appear) - { - M_Appearance *ap = NULL; - u32 tag; - if (!appear) return 0; +Bool compositor_is_composite_texture(GF_Node *appear) +{ + M_Appearance *ap = NULL; + u32 tag; + if (!appear) return 0; - tag = gf_node_get_tag(appear); - if (tag==TAG_MPEG4_Appearance) ap = (M_Appearance *)appear; + tag = gf_node_get_tag(appear); + if (tag==TAG_MPEG4_Appearance) ap = (M_Appearance *)appear; #ifndef GPAC_DISABLE_X3D - else if (tag==TAG_X3D_Appearance) ap = (M_Appearance *)appear; + else if (tag==TAG_X3D_Appearance) ap = (M_Appearance *)appear; #endif - if (!ap) return 0; - if (!ap->texture) return 0; - switch (gf_node_get_tag(((M_Appearance *)appear)->texture)) { - case TAG_MPEG4_CompositeTexture2D: - case TAG_MPEG4_CompositeTexture3D: - return 1; - } - return 0; + if (!ap) return 0; + if (!ap->texture) return 0; + switch (gf_node_get_tag(((M_Appearance *)appear)->texture)) { + case TAG_MPEG4_CompositeTexture2D: + case TAG_MPEG4_CompositeTexture3D: + return 1; } + return 0; +} #endif /*GPAC_DISABLE_VRML*/ diff --git a/src/compositor/mpeg4_form.c b/src/compositor/mpeg4_form.c index aa6455f..b06a7d5 100644 --- a/src/compositor/mpeg4_form.c +++ b/src/compositor/mpeg4_form.c @@ -301,7 +301,7 @@ static void TraverseForm(GF_Node *n, void *rs, Bool is_destroy) while (1) { if (last_ind+index > fm->groupsIndex.count) goto err_exit; if (fm->groupsIndex.vals[last_ind+index]==-1) break; - if (index>MAX_FORM_GROUP_INDEX) goto err_exit; + if (index>=MAX_FORM_GROUP_INDEX) goto err_exit; idx[index] = fm->groupsIndex.vals[last_ind+index]; index++; } @@ -496,14 +496,13 @@ static void sv_apply(FormStack *st, Fixed space, u32 *group_idx, u32 count) { u32 i, k; Fixed tot_len, inter_space; - GF_Rect *t, *b; tot_len = 0; if(space > -FIX_ONE) { inter_space = space; } else { - t = &form_get_group(st, group_idx[count-1])->final; - b = &form_get_group(st, group_idx[0])->final; + GF_Rect *t = &form_get_group(st, group_idx[count-1])->final; + GF_Rect *b = &form_get_group(st, group_idx[0])->final; inter_space = b->y - t->y; if (group_idx[0]!=0) inter_space -= t->height; for (i=1; ifinal.height; diff --git a/src/compositor/mpeg4_geometry_2d.c b/src/compositor/mpeg4_geometry_2d.c index b5161f3..e2289fe 100644 --- a/src/compositor/mpeg4_geometry_2d.c +++ b/src/compositor/mpeg4_geometry_2d.c @@ -75,7 +75,6 @@ static void TraverseShape(GF_Node *node, void *rs, Bool is_destroy) /*check traverse mode, and take care of switch-off flag*/ if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) { - GF_Node *m; tr_state->appear = (GF_Node *) shape->appearance; /*this is done regardless of switch flag*/ @@ -83,7 +82,7 @@ static void TraverseShape(GF_Node *node, void *rs, Bool is_destroy) if (tr_state->appear) { /*apply line width*/ - m = ((M_Appearance *)tr_state->appear)->material; + GF_Node *m = ((M_Appearance *)tr_state->appear)->material; if (m && (gf_node_get_tag(m)==TAG_MPEG4_Material2D) ) { DrawAspect2D asp; Fixed width = 0; @@ -181,7 +180,7 @@ static void TraverseCircle(GF_Node *node, void *rs, Bool is_destroy) if (!stack->mesh) { Fixed a = ((M_Circle *) node)->radius * 2; stack->mesh = new_mesh(); - mesh_new_ellipse(stack->mesh, a, a, tr_state->visual->compositor->high_speed); + mesh_new_ellipse(stack->mesh, a, a, tr_state->visual->compositor->fast); } visual_3d_draw_2d(stack, tr_state); return; @@ -236,7 +235,7 @@ static void TraverseEllipse(GF_Node *node, void *rs, Bool is_destroy) case TRAVERSE_DRAW_3D: if (!stack->mesh) { stack->mesh = new_mesh(); - mesh_new_ellipse(stack->mesh, ((M_Ellipse *) node)->radius.x * 2, ((M_Ellipse *) node)->radius.y * 2, tr_state->visual->compositor->high_speed); + mesh_new_ellipse(stack->mesh, ((M_Ellipse *) node)->radius.x * 2, ((M_Ellipse *) node)->radius.y * 2, tr_state->visual->compositor->fast); } visual_3d_draw_2d(stack, tr_state); return; @@ -318,6 +317,85 @@ static void rectangle_check_changes(GF_Node *node, Drawable *stack, GF_TraverseS drawable_mark_modified(stack, tr_state); } } + +Bool rectangle_check_adaptation(GF_Node *node, Drawable *stack, GF_TraverseState *tr_state) +{ + GF_TextureHandler *txh; + GF_MediaObjectVRInfo vrinfo; + s32 gaze_x, gaze_y; + Bool is_visible = GF_FALSE; + if (! tr_state->visual->compositor->gazer_enabled) + return GF_TRUE; + + if (!tr_state->appear || ! ((M_Appearance *)tr_state->appear)->texture) + return GF_TRUE; + + txh = gf_sc_texture_get_handler( ((M_Appearance *) tr_state->appear)->texture ); + if (!txh->stream) return GF_TRUE; + + if (! gf_mo_get_srd_info(txh->stream, &vrinfo)) + return GF_TRUE; + + if (!vrinfo.srd_w && !vrinfo.srd_h && vrinfo.is_tiled_srd) { + if (txh->stream->srd_full_w && txh->stream->srd_full_h) { + gaze_x = tr_state->visual->compositor->gaze_x; + gaze_x *= txh->stream->srd_full_w; + gaze_x /= tr_state->visual->width; + + gaze_y = tr_state->visual->compositor->gaze_y; + gaze_y *= txh->stream->srd_full_h; + gaze_y /= tr_state->visual->height; + + gf_mo_hint_gaze(txh->stream, gaze_x, gaze_y); + } + + return GF_TRUE; + } + + gaze_x = tr_state->visual->compositor->gaze_x; + gaze_x *= vrinfo.srd_max_x; + gaze_x /= tr_state->visual->width; + + gaze_y = tr_state->visual->compositor->gaze_y; + gaze_y *= vrinfo.srd_max_y; + gaze_y /= tr_state->visual->height; + + //simple test condition: only keep the first row + if ((gaze_x>=vrinfo.srd_x) && (gaze_x<=vrinfo.srd_x+vrinfo.srd_w) && (gaze_y>=vrinfo.srd_y) && (gaze_y<=vrinfo.srd_y+vrinfo.srd_h)) { + + GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Texture %d Partial plane is under gaze coord %d %d\n", txh->stream->OD_ID, tr_state->visual->compositor->gaze_x, tr_state->visual->compositor->gaze_y)); + is_visible = GF_TRUE; + } + + if (vrinfo.has_full_coverage) { + if (is_visible) { + if (!txh->is_open) { + GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Texture %d stoped on visible partial plane - starting it\n", txh->stream->OD_ID)); + assert(txh->stream && txh->stream->odm); + txh->stream->odm->disable_buffer_at_next_play = GF_TRUE; + + gf_sc_texture_play(txh, NULL); + } + if (! txh->data) return GF_FALSE; + return GF_TRUE; + } else { + if (txh->is_open) { + GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Texure %d playing on hidden partial plane - stoping it\n", txh->stream->OD_ID)); + gf_sc_texture_stop_no_unregister(txh); + } + return GF_FALSE; + } + } else { + if (is_visible) { + gf_mo_hint_quality_degradation(txh->stream, 0); + if (! txh->data) return GF_FALSE; + } else { + gf_mo_hint_quality_degradation(txh->stream, 100); + } + } + return GF_TRUE; +} + static void TraverseRectangle(GF_Node *node, void *rs, Bool is_destroy) { DrawableContext *ctx; @@ -331,6 +409,9 @@ static void TraverseRectangle(GF_Node *node, void *rs, Bool is_destroy) rectangle_check_changes(node, stack, tr_state); + if (! rectangle_check_adaptation(node, stack, tr_state)) + return; + switch (tr_state->traversing_mode) { case TRAVERSE_DRAW_2D: compositor_2d_draw_rectangle(tr_state); @@ -412,7 +493,7 @@ void curve2d_check_changes(GF_Node *node, Drawable *stack, GF_TraverseState *tr_ if (!coord) return; stack->path->fineness = c2D->fineness; - if (tr_state->visual->compositor->high_speed) stack->path->fineness /= 2; + if (tr_state->visual->compositor->fast) stack->path->fineness /= 2; pts = coord->point.vals; @@ -709,6 +790,9 @@ static void TraverseBitWrapper(GF_Node *node, void *rs, Bool is_destroy) gf_node_set_private(node, NULL); return; } +#ifdef GPAC_ENABLE_COVERAGE + if (!rs) return; +#endif tr_state = (GF_TraverseState *)rs; // Traverse the node here @@ -720,8 +804,15 @@ void compositor_init_bitwrapper(GF_Compositor *compositor, GF_Node *node) { M_BitWrapper *bit; bit = (M_BitWrapper *)node; + if (!bit->node) return; gf_node_set_private(node, gf_node_get_private(bit->node)); gf_node_set_callback_function(node, TraverseBitWrapper); + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + TraverseBitWrapper(node, NULL, GF_FALSE); + } +#endif } diff --git a/src/compositor/mpeg4_geometry_3d.c b/src/compositor/mpeg4_geometry_3d.c index 1a1d36f..d989690 100644 --- a/src/compositor/mpeg4_geometry_3d.c +++ b/src/compositor/mpeg4_geometry_3d.c @@ -89,7 +89,7 @@ void compositor_init_box(GF_Compositor *compositor, GF_Node *node) static void build_shape_cone(GF_Node *n, Drawable3D *stack, GF_TraverseState *tr_state) { M_Cone *co = (M_Cone *)n; - mesh_new_cone(stack->mesh, co->height, co->bottomRadius, co->bottom, co->side, tr_state->visual->compositor->high_speed); + mesh_new_cone(stack->mesh, co->height, co->bottomRadius, co->bottom, co->side, tr_state->visual->compositor->fast); } static void TraverseCone(GF_Node *n, void *rs, Bool is_destroy) @@ -106,7 +106,7 @@ void compositor_init_cone(GF_Compositor *compositor, GF_Node *node) static void build_shape_cylinder(GF_Node *n, Drawable3D *stack, GF_TraverseState *tr_state) { M_Cylinder *cy = (M_Cylinder *)n; - mesh_new_cylinder(stack->mesh, cy->height, cy->radius, cy->bottom, cy->side, cy->top, tr_state->visual->compositor->high_speed); + mesh_new_cylinder(stack->mesh, cy->height, cy->radius, cy->bottom, cy->side, cy->top, tr_state->visual->compositor->fast); } static void TraverseCylinder(GF_Node *n, void *rs, Bool is_destroy) @@ -123,7 +123,7 @@ void compositor_init_cylinder(GF_Compositor *compositor, GF_Node *node) static void build_shape_sphere(GF_Node *n, Drawable3D *stack, GF_TraverseState *tr_state) { M_Sphere *sp = (M_Sphere *)n; - mesh_new_sphere(stack->mesh, sp->radius, tr_state->visual->compositor->high_speed, NULL); + mesh_new_sphere(stack->mesh, sp->radius, tr_state->visual->compositor->fast, NULL); } static void get_tx_coords_from_angle(GF_TraverseState *tr_state, GF_TextureHandler *txh, Bool horizontal, u32 *min_coord, u32 *max_coord) @@ -187,8 +187,6 @@ static void get_tx_coords_from_angle(GF_TraverseState *tr_state, GF_TextureHandl } *min_coord= (u32) (min_tx*dim); *max_coord= (u32) (max_tx*dim); - -// fprintf(stderr, "%s Angle is %g (%g <-> %g) - min tx %g (%u) - max tx %g (%u)\n", horizontal ? "X" : "Y", theta_angle, angle_start, angle_end, min_tx, *min_coord, max_tx, *max_coord); } static void TraverseSphere(GF_Node *n, void *rs, Bool is_destroy) @@ -250,29 +248,37 @@ static void TraverseIFS(GF_Node *n, void *rs, Bool is_destroy) static void IFS_SetColorIndex(GF_Node *node, GF_Route *route) { M_IndexedFaceSet *ifs = (M_IndexedFaceSet *)node; - gf_sg_vrml_field_copy(&ifs->colorIndex, &ifs->set_colorIndex, GF_SG_VRML_MFINT32); - gf_sg_vrml_mf_reset(&ifs->set_colorIndex, GF_SG_VRML_MFINT32); + if (node) { + gf_sg_vrml_field_copy(&ifs->colorIndex, &ifs->set_colorIndex, GF_SG_VRML_MFINT32); + gf_sg_vrml_mf_reset(&ifs->set_colorIndex, GF_SG_VRML_MFINT32); + } } static void IFS_SetCoordIndex(GF_Node *node, GF_Route *route) { M_IndexedFaceSet *ifs = (M_IndexedFaceSet *)node; - gf_sg_vrml_field_copy(&ifs->coordIndex, &ifs->set_coordIndex, GF_SG_VRML_MFINT32); - gf_sg_vrml_mf_reset(&ifs->set_coordIndex, GF_SG_VRML_MFINT32); + if (node) { + gf_sg_vrml_field_copy(&ifs->coordIndex, &ifs->set_coordIndex, GF_SG_VRML_MFINT32); + gf_sg_vrml_mf_reset(&ifs->set_coordIndex, GF_SG_VRML_MFINT32); + } } static void IFS_SetNormalIndex(GF_Node *node, GF_Route *route) { M_IndexedFaceSet *ifs = (M_IndexedFaceSet *)node; - gf_sg_vrml_field_copy(&ifs->normalIndex, &ifs->set_normalIndex, GF_SG_VRML_MFINT32); - gf_sg_vrml_mf_reset(&ifs->set_normalIndex, GF_SG_VRML_MFINT32); + if (node) { + gf_sg_vrml_field_copy(&ifs->normalIndex, &ifs->set_normalIndex, GF_SG_VRML_MFINT32); + gf_sg_vrml_mf_reset(&ifs->set_normalIndex, GF_SG_VRML_MFINT32); + } } static void IFS_SetTexCoordIndex(GF_Node *node, GF_Route *route) { M_IndexedFaceSet *ifs = (M_IndexedFaceSet *)node; - gf_sg_vrml_field_copy(&ifs->texCoordIndex, &ifs->set_texCoordIndex, GF_SG_VRML_MFINT32); - gf_sg_vrml_mf_reset(&ifs->set_texCoordIndex, GF_SG_VRML_MFINT32); + if (node) { + gf_sg_vrml_field_copy(&ifs->texCoordIndex, &ifs->set_texCoordIndex, GF_SG_VRML_MFINT32); + gf_sg_vrml_mf_reset(&ifs->set_texCoordIndex, GF_SG_VRML_MFINT32); + } } void compositor_init_ifs(GF_Compositor *compositor, GF_Node *node) @@ -284,6 +290,16 @@ void compositor_init_ifs(GF_Compositor *compositor, GF_Node *node) ifs->on_set_coordIndex = IFS_SetCoordIndex; ifs->on_set_normalIndex = IFS_SetNormalIndex; ifs->on_set_texCoordIndex = IFS_SetTexCoordIndex; + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + IFS_SetCoordIndex(NULL, NULL); + IFS_SetColorIndex(NULL, NULL); + IFS_SetNormalIndex(NULL, NULL); + IFS_SetTexCoordIndex(NULL, NULL); + } +#endif + } static void build_shape_ils(GF_Node *n, Drawable3D *stack, GF_TraverseState *tr_state) @@ -300,15 +316,19 @@ static void TraverseILS(GF_Node *n, void *rs, Bool is_destroy) static void ILS_SetColorIndex(GF_Node *node, GF_Route *route) { M_IndexedLineSet *ils = (M_IndexedLineSet *)node; - gf_sg_vrml_field_copy(&ils->colorIndex, &ils->set_colorIndex, GF_SG_VRML_MFINT32); - gf_sg_vrml_mf_reset(&ils->set_colorIndex, GF_SG_VRML_MFINT32); + if (node) { + gf_sg_vrml_field_copy(&ils->colorIndex, &ils->set_colorIndex, GF_SG_VRML_MFINT32); + gf_sg_vrml_mf_reset(&ils->set_colorIndex, GF_SG_VRML_MFINT32); + } } static void ILS_SetCoordIndex(GF_Node *node, GF_Route *route) { M_IndexedLineSet *ils = (M_IndexedLineSet *)node; - gf_sg_vrml_field_copy(&ils->coordIndex, &ils->set_coordIndex, GF_SG_VRML_MFINT32); - gf_sg_vrml_mf_reset(&ils->set_coordIndex, GF_SG_VRML_MFINT32); + if (node) { + gf_sg_vrml_field_copy(&ils->coordIndex, &ils->set_coordIndex, GF_SG_VRML_MFINT32); + gf_sg_vrml_mf_reset(&ils->set_coordIndex, GF_SG_VRML_MFINT32); + } } void compositor_init_ils(GF_Compositor *compositor, GF_Node *node) @@ -318,6 +338,14 @@ void compositor_init_ils(GF_Compositor *compositor, GF_Node *node) gf_node_set_callback_function(node, TraverseILS); ils->on_set_colorIndex = ILS_SetColorIndex; ils->on_set_coordIndex = ILS_SetCoordIndex; + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + ILS_SetCoordIndex(NULL, NULL); + ILS_SetColorIndex(NULL, NULL); + } +#endif + } @@ -334,8 +362,10 @@ static void TraverseElevationGrid(GF_Node *n, void *rs, Bool is_destroy) static void ElevationGrid_SetHeight(GF_Node *node, GF_Route *route) { M_ElevationGrid *eg = (M_ElevationGrid *)node; - gf_sg_vrml_field_copy(&eg->height, &eg->set_height, GF_SG_VRML_MFFLOAT); - gf_sg_vrml_mf_reset(&eg->set_height, GF_SG_VRML_MFFLOAT); + if (node) { + gf_sg_vrml_field_copy(&eg->height, &eg->set_height, GF_SG_VRML_MFFLOAT); + gf_sg_vrml_mf_reset(&eg->set_height, GF_SG_VRML_MFFLOAT); + } } void compositor_init_elevation_grid(GF_Compositor *compositor, GF_Node *node) @@ -344,6 +374,11 @@ void compositor_init_elevation_grid(GF_Compositor *compositor, GF_Node *node) drawable_3d_new(node); gf_node_set_callback_function(node, TraverseElevationGrid); eg->on_set_height = ElevationGrid_SetHeight; +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + ElevationGrid_SetHeight(NULL, NULL); + } +#endif } static void build_shape_extrusion(GF_Node *n, Drawable3D *stack, GF_TraverseState *tr_state) @@ -359,26 +394,34 @@ static void TraverseExtrusion(GF_Node *n, void *rs, Bool is_destroy) static void Extrusion_SetCrossSection(GF_Node *node, GF_Route *route) { M_Extrusion *eg = (M_Extrusion *)node; - gf_sg_vrml_field_copy(&eg->crossSection, &eg->set_crossSection, GF_SG_VRML_MFVEC2F); - gf_sg_vrml_mf_reset(&eg->set_crossSection, GF_SG_VRML_MFVEC2F); + if (node) { + gf_sg_vrml_field_copy(&eg->crossSection, &eg->set_crossSection, GF_SG_VRML_MFVEC2F); + gf_sg_vrml_mf_reset(&eg->set_crossSection, GF_SG_VRML_MFVEC2F); + } } static void Extrusion_SetOrientation(GF_Node *node, GF_Route *route) { M_Extrusion *eg = (M_Extrusion *)node; - gf_sg_vrml_field_copy(&eg->orientation, &eg->set_orientation, GF_SG_VRML_MFROTATION); - gf_sg_vrml_mf_reset(&eg->set_orientation, GF_SG_VRML_MFROTATION); + if (node) { + gf_sg_vrml_field_copy(&eg->orientation, &eg->set_orientation, GF_SG_VRML_MFROTATION); + gf_sg_vrml_mf_reset(&eg->set_orientation, GF_SG_VRML_MFROTATION); + } } static void Extrusion_SetScale(GF_Node *node, GF_Route *route) { M_Extrusion *eg = (M_Extrusion *)node; - gf_sg_vrml_field_copy(&eg->scale, &eg->set_scale, GF_SG_VRML_MFVEC2F); - gf_sg_vrml_mf_reset(&eg->set_scale, GF_SG_VRML_MFVEC2F); + if (node) { + gf_sg_vrml_field_copy(&eg->scale, &eg->set_scale, GF_SG_VRML_MFVEC2F); + gf_sg_vrml_mf_reset(&eg->set_scale, GF_SG_VRML_MFVEC2F); + } } static void Extrusion_SetSpine(GF_Node *node, GF_Route *route) { M_Extrusion *eg = (M_Extrusion *)node; - gf_sg_vrml_field_copy(&eg->spine, &eg->set_spine, GF_SG_VRML_MFVEC3F); - gf_sg_vrml_mf_reset(&eg->set_spine, GF_SG_VRML_MFVEC3F); + if (node) { + gf_sg_vrml_field_copy(&eg->spine, &eg->set_spine, GF_SG_VRML_MFVEC3F); + gf_sg_vrml_mf_reset(&eg->set_spine, GF_SG_VRML_MFVEC3F); + } } void compositor_init_extrusion(GF_Compositor *compositor, GF_Node *node) { @@ -389,6 +432,16 @@ void compositor_init_extrusion(GF_Compositor *compositor, GF_Node *node) ext->on_set_orientation = Extrusion_SetOrientation; ext->on_set_scale = Extrusion_SetScale; ext->on_set_spine = Extrusion_SetSpine; + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + Extrusion_SetCrossSection(NULL, NULL); + Extrusion_SetOrientation(NULL, NULL); + Extrusion_SetScale(NULL, NULL); + Extrusion_SetSpine(NULL, NULL); + } +#endif + } diff --git a/src/compositor/mpeg4_geometry_ifs2d.c b/src/compositor/mpeg4_geometry_ifs2d.c index 58dd9e7..76e8612 100644 --- a/src/compositor/mpeg4_geometry_ifs2d.c +++ b/src/compositor/mpeg4_geometry_ifs2d.c @@ -79,17 +79,17 @@ static void ifs2d_check_changes(GF_Node *node, Drawable *stack, GF_TraverseState static void IFS2D_Draw(GF_Node *node, GF_TraverseState *tr_state) { u32 i, count, ci_count; +#if 0 //unused u32 j, ind_col, num_col; SFVec2f center, end; SFColor col_cen; - GF_STENCIL grad; + GF_EVGStencil *grad; u32 *colors; - GF_Path *path; +#endif SFVec2f start; SFVec2f *pts; SFColor col; Fixed alpha; - GF_Raster2D *raster; DrawableContext *ctx = tr_state->ctx; M_IndexedFaceSet2D *ifs2D = (M_IndexedFaceSet2D *)node; M_Coordinate2D *coord = (M_Coordinate2D*) ifs2D->coord; @@ -124,7 +124,7 @@ static void IFS2D_Draw(GF_Node *node, GF_TraverseState *tr_state) /*we have color per faces so we need N path :(*/ if (! ifs2D->colorPerVertex) { - path = gf_path_new(); + GF_Path *path = gf_path_new(); count = 0; i = 0; @@ -166,16 +166,13 @@ static void IFS2D_Draw(GF_Node *node, GF_TraverseState *tr_state) } /*final case, color per vertex means gradient fill/strike*/ - raster = tr_state->visual->compositor->rasterizer; - grad = raster->stencil_new(raster, GF_STENCIL_VERTEX_GRADIENT); /*not supported, fill default*/ - if (!grad) { - visual_2d_texture_path(tr_state->visual, ctx->drawable->path, ctx, tr_state); - visual_2d_draw_path(tr_state->visual, ctx->drawable->path, ctx, NULL, NULL, tr_state); - return; - } + visual_2d_texture_path(tr_state->visual, ctx->drawable->path, ctx, tr_state); + visual_2d_draw_path(tr_state->visual, ctx->drawable->path, ctx, NULL, NULL, tr_state); + return; +#if 0 //deprecated path = gf_path_new(); ind_col = 0; @@ -225,8 +222,8 @@ static void IFS2D_Draw(GF_Node *node, GF_TraverseState *tr_state) col_cen.green += col.green; col_cen.red += col.red; - raster->stencil_set_vertex_path(grad, path); - raster->stencil_set_vertex_colors(grad, colors, num_col); + gf_evg_stencil_set_vertex_path(grad, path); + gf_evg_stencil_set_vertex_colors(grad, colors, num_col); gf_free(colors); @@ -235,24 +232,27 @@ static void IFS2D_Draw(GF_Node *node, GF_TraverseState *tr_state) col_cen.red /= num_col; center.x /= num_col; center.y /= num_col; - raster->stencil_set_vertex_center(grad, center.x, center.y, GF_COL_ARGB_FIXED(alpha, col_cen.red, col_cen.green, col_cen.blue) ); + gf_evg_stencil_set_vertex_center(grad, center.x, center.y, GF_COL_ARGB_FIXED(alpha, col_cen.red, col_cen.green, col_cen.blue) ); - raster->stencil_set_matrix(grad, &ctx->transform); + gf_evg_stencil_set_matrix(grad, &ctx->transform); /*draw*/ visual_2d_draw_path(tr_state->visual, ctx->drawable->path, ctx, grad, grad, tr_state); - raster->stencil_delete(grad); + gf_evg_stencil_delete(grad); //goto next point i++; ind_col += num_col + 1; if (i >= ci_count) break; - grad = raster->stencil_new(raster, GF_STENCIL_VERTEX_GRADIENT); + grad = gf_evg_stencil_new(GF_STENCIL_VERTEX_GRADIENT); ctx->flags &= ~CTX_PATH_FILLED; ctx->flags &= ~CTX_PATH_STROKE; } gf_path_del(path); +#endif + + } static void TraverseIFS2D(GF_Node *node, void *rs, Bool is_destroy) @@ -321,15 +321,19 @@ static void TraverseIFS2D(GF_Node *node, void *rs, Bool is_destroy) static void IFS2D_SetColorIndex(GF_Node *node, GF_Route *route) { M_IndexedFaceSet2D *ifs2D = (M_IndexedFaceSet2D *)node; - gf_sg_vrml_field_copy(&ifs2D->colorIndex, &ifs2D->set_colorIndex, GF_SG_VRML_MFINT32); - gf_sg_vrml_mf_reset(&ifs2D->set_colorIndex, GF_SG_VRML_MFINT32); + if (node) { + gf_sg_vrml_field_copy(&ifs2D->colorIndex, &ifs2D->set_colorIndex, GF_SG_VRML_MFINT32); + gf_sg_vrml_mf_reset(&ifs2D->set_colorIndex, GF_SG_VRML_MFINT32); + } } static void IFS2D_SetCoordIndex(GF_Node *node, GF_Route *route) { M_IndexedFaceSet2D *ifs2D = (M_IndexedFaceSet2D *)node; - gf_sg_vrml_field_copy(&ifs2D->coordIndex, &ifs2D->set_coordIndex, GF_SG_VRML_MFINT32); - gf_sg_vrml_mf_reset(&ifs2D->set_coordIndex, GF_SG_VRML_MFINT32); + if (node) { + gf_sg_vrml_field_copy(&ifs2D->coordIndex, &ifs2D->set_coordIndex, GF_SG_VRML_MFINT32); + gf_sg_vrml_mf_reset(&ifs2D->set_coordIndex, GF_SG_VRML_MFINT32); + } } void compositor_init_indexed_face_set2d(GF_Compositor *compositor, GF_Node *node) @@ -340,6 +344,14 @@ void compositor_init_indexed_face_set2d(GF_Compositor *compositor, GF_Node *node gf_node_set_callback_function(node, TraverseIFS2D); ifs2D->on_set_colorIndex = IFS2D_SetColorIndex; ifs2D->on_set_coordIndex = IFS2D_SetCoordIndex; + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + IFS2D_SetCoordIndex(NULL, NULL); + IFS2D_SetColorIndex(NULL, NULL); + } +#endif + } #endif /*GPAC_DISABLE_VRML*/ diff --git a/src/compositor/mpeg4_geometry_ils2d.c b/src/compositor/mpeg4_geometry_ils2d.c index 99e7194..518c4f6 100644 --- a/src/compositor/mpeg4_geometry_ils2d.c +++ b/src/compositor/mpeg4_geometry_ils2d.c @@ -77,11 +77,13 @@ static void ILS2D_Draw(GF_Node *node, GF_TraverseState *tr_state) SFColor col; Fixed alpha; u32 i, count, col_ind, ind, end_at; - u32 linear[2], *colors; + u32 linear[2]; +#if 0 //unused + u32 *colors, j; +#endif SFVec2f start, end; - u32 j, num_col; - GF_STENCIL grad; - GF_Raster2D *raster; + u32 num_col; + GF_EVGStencil *grad; DrawableContext *ctx = tr_state->ctx; M_IndexedLineSet2D *ils2D = (M_IndexedLineSet2D *)node; M_Coordinate2D *coord = (M_Coordinate2D*) ils2D->coord; @@ -137,7 +139,6 @@ static void ILS2D_Draw(GF_Node *node, GF_TraverseState *tr_state) return; } - raster = NULL; end_at = ils2D->coordIndex.count; if (!end_at) end_at = coord->point.count; @@ -169,11 +170,10 @@ static void ILS2D_Draw(GF_Node *node, GF_TraverseState *tr_state) } } - raster = tr_state->visual->compositor->rasterizer; /*use linear gradient*/ if (num_col==2) { Fixed pos[2]; - grad = raster->stencil_new(raster, GF_STENCIL_LINEAR_GRADIENT); + grad = gf_evg_stencil_new(GF_STENCIL_LINEAR_GRADIENT); if (ils2D->colorIndex.count) { col = color->color.vals[ils2D->colorIndex.vals[col_ind]]; linear[0] = GF_COL_ARGB_FIXED(alpha, col.red, col.green, col.blue); @@ -192,12 +192,14 @@ static void ILS2D_Draw(GF_Node *node, GF_TraverseState *tr_state) } pos[0] = 0; pos[1] = FIX_ONE; - raster->stencil_set_linear_gradient(grad, start.x, start.y, end.x, end.y); - raster->stencil_set_gradient_interpolation(grad, pos, linear, 2); + gf_evg_stencil_set_linear_gradient(grad, start.x, start.y, end.x, end.y); + gf_evg_stencil_set_gradient_interpolation(grad, pos, linear, 2); } else { - grad = raster->stencil_new(raster, GF_STENCIL_VERTEX_GRADIENT); + grad = NULL; +#if 0 //unused + grad = gf_evg_stencil_new(GF_STENCIL_VERTEX_GRADIENT); if (grad) { - raster->stencil_set_vertex_path(grad, path); + gf_evg_stencil_set_vertex_path(grad, path); colors = (u32*)gf_malloc(sizeof(u32) * num_col); for (j=0; jstencil_set_vertex_colors(grad, colors, num_col); + gf_evg_stencil_set_vertex_colors(grad, colors, num_col); gf_free(colors); } +#endif + } - raster->stencil_set_matrix(grad, &ctx->transform); + gf_evg_stencil_set_matrix(grad, &ctx->transform); visual_2d_draw_path(tr_state->visual, path, ctx, NULL, grad, tr_state); - if (grad) raster->stencil_delete(grad); + if (grad) gf_evg_stencil_delete(grad); i ++; col_ind += num_col + 1; @@ -289,15 +293,19 @@ static void TraverseILS2D(GF_Node *node, void *rs, Bool is_destroy) static void ILS2D_SetColorIndex(GF_Node *node, GF_Route *route) { M_IndexedLineSet2D *ils2D = (M_IndexedLineSet2D *)node; - gf_sg_vrml_field_copy(&ils2D->colorIndex, &ils2D->set_colorIndex, GF_SG_VRML_MFINT32); - gf_sg_vrml_mf_reset(&ils2D->set_colorIndex, GF_SG_VRML_MFINT32); + if (node) { + gf_sg_vrml_field_copy(&ils2D->colorIndex, &ils2D->set_colorIndex, GF_SG_VRML_MFINT32); + gf_sg_vrml_mf_reset(&ils2D->set_colorIndex, GF_SG_VRML_MFINT32); + } } static void ILS2D_SetCoordIndex(GF_Node *node, GF_Route *route) { M_IndexedLineSet2D *ils2D = (M_IndexedLineSet2D *)node; - gf_sg_vrml_field_copy(&ils2D->coordIndex, &ils2D->set_coordIndex, GF_SG_VRML_MFINT32); - gf_sg_vrml_mf_reset(&ils2D->set_coordIndex, GF_SG_VRML_MFINT32); + if (node) { + gf_sg_vrml_field_copy(&ils2D->coordIndex, &ils2D->set_coordIndex, GF_SG_VRML_MFINT32); + gf_sg_vrml_mf_reset(&ils2D->set_coordIndex, GF_SG_VRML_MFINT32); + } } void compositor_init_indexed_line_set2d(GF_Compositor *compositor, GF_Node *node) @@ -308,6 +316,14 @@ void compositor_init_indexed_line_set2d(GF_Compositor *compositor, GF_Node *node gf_node_set_callback_function(node, TraverseILS2D); ils2D->on_set_colorIndex = ILS2D_SetColorIndex; ils2D->on_set_coordIndex = ILS2D_SetCoordIndex; + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + ILS2D_SetCoordIndex(NULL, NULL); + ILS2D_SetColorIndex(NULL, NULL); + } +#endif + } #endif /*GPAC_DISABLE_VRML*/ diff --git a/src/compositor/mpeg4_gradients.c b/src/compositor/mpeg4_gradients.c index ad35ad6..8158a01 100644 --- a/src/compositor/mpeg4_gradients.c +++ b/src/compositor/mpeg4_gradients.c @@ -92,7 +92,7 @@ static void UpdateLinearGradient(GF_TextureHandler *txh) u32 i, *cols; Fixed a; Bool const_a; - GF_STENCIL stencil; + GF_EVGStencil *stencil; M_LinearGradient *lg = (M_LinearGradient *) txh->owner; GradientStack *st = (GradientStack *) gf_node_get_private(txh->owner); @@ -109,7 +109,7 @@ static void UpdateLinearGradient(GF_TextureHandler *txh) } stencil = gf_sc_texture_get_stencil(txh); - if (!stencil) stencil = txh->compositor->rasterizer->stencil_new(txh->compositor->rasterizer, GF_STENCIL_LINEAR_GRADIENT); + if (!stencil) stencil = gf_evg_stencil_new(GF_STENCIL_LINEAR_GRADIENT); /*set stencil even if assigned, this invalidates the associated bitmap state in 3D*/ gf_sc_texture_set_stencil(txh, stencil); @@ -124,15 +124,15 @@ static void UpdateLinearGradient(GF_TextureHandler *txh) cols[i] = GF_COL_ARGB_FIXED(a, lg->keyValue.vals[i].red, lg->keyValue.vals[i].green, lg->keyValue.vals[i].blue); if (a != FIX_ONE) txh->transparent = 1; } - txh->compositor->rasterizer->stencil_set_gradient_interpolation(stencil, lg->key.vals, cols, lg->key.count); + gf_evg_stencil_set_gradient_interpolation(stencil, lg->key.vals, cols, lg->key.count); gf_free(cols); - txh->compositor->rasterizer->stencil_set_gradient_mode(stencil, (GF_GradientMode) lg->spreadMethod); + gf_evg_stencil_set_gradient_mode(stencil, (GF_GradientMode) lg->spreadMethod); } static void LG_ComputeMatrix(GF_TextureHandler *txh, GF_Rect *bounds, GF_Matrix2D *mat, Bool for_3d) { - GF_STENCIL stencil; + GF_EVGStencil *stencil; M_LinearGradient *lg = (M_LinearGradient *) txh->owner; stencil = gf_sc_texture_get_stencil(txh); @@ -152,7 +152,7 @@ static void LG_ComputeMatrix(GF_TextureHandler *txh, GF_Rect *bounds, GF_Matrix2 i order to avoid overflows in fixed point*/ gf_mx2d_add_scale(mat, bounds->width, bounds->height); - txh->compositor->rasterizer->stencil_set_linear_gradient(stencil, lg->startPoint.x, lg->startPoint.y, lg->endPoint.x, lg->endPoint.y); + gf_evg_stencil_set_linear_gradient(stencil, lg->startPoint.x, lg->startPoint.y, lg->endPoint.x, lg->endPoint.y); } //TODO - replace this with shader-based code ... @@ -165,16 +165,15 @@ static void BuildLinearGradientTexture(GF_TextureHandler *txh) Fixed a; Bool const_a; GF_Matrix2D mat; - GF_STENCIL stenc; - GF_SURFACE surface; - GF_STENCIL texture2D; + GF_EVGStencil * stenc; + GF_EVGSurface *surface; + GF_EVGStencil * texture2D; GF_Path *path; GF_Err e; Bool transparent; u32 pix_fmt = 0; M_LinearGradient *lg = (M_LinearGradient *) txh->owner; GradientStack *st = (GradientStack *) gf_node_get_private(txh->owner); - GF_Raster2D *raster = txh->compositor->rasterizer; if (!txh->tx_io) return; @@ -193,11 +192,11 @@ static void BuildLinearGradientTexture(GF_TextureHandler *txh) transparent = (lg->opacity.count==1) ? (lg->opacity.vals[0]!=FIX_ONE) : 1; /*init our 2D graphics stuff*/ - texture2D = raster->stencil_new(raster, GF_STENCIL_TEXTURE); + texture2D = gf_evg_stencil_new(GF_STENCIL_TEXTURE); if (!texture2D) return; - surface = raster->surface_new(raster, 1); + surface = gf_evg_surface_new(1); if (!surface) { - raster->stencil_delete(texture2D); + gf_evg_stencil_delete(texture2D); return; } @@ -215,16 +214,16 @@ static void BuildLinearGradientTexture(GF_TextureHandler *txh) memset(st->tx_data, 0, sizeof(char)*txh->stride*txh->height); pix_fmt = GF_PIXEL_RGBA; - e = raster->stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, pix_fmt, pix_fmt, 1); + e = gf_evg_stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, pix_fmt); if (e) { pix_fmt = GF_PIXEL_ARGB; - e = raster->stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, pix_fmt, pix_fmt, 1); + e = gf_evg_stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, pix_fmt); } } else { if (!st->tx_data) { st->tx_data = (char *) gf_malloc(sizeof(char)*GRAD_TEXTURE_SIZE*GRAD_TEXTURE_SIZE*3); } - e = raster->stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 3*GRAD_TEXTURE_SIZE, GF_PIXEL_RGB_24, GF_PIXEL_RGB_24, 1); + e = gf_evg_stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 3*GRAD_TEXTURE_SIZE, GF_PIXEL_RGB); /*try with ARGB (it actually is needed for GDIplus module since GDIplus cannot handle native RGB texture (it works in BGR)*/ if (e) { /*remember for later use*/ @@ -232,29 +231,29 @@ static void BuildLinearGradientTexture(GF_TextureHandler *txh) transparent = 1; gf_free(st->tx_data); st->tx_data = (char *) gf_malloc(sizeof(char)*GRAD_TEXTURE_SIZE*GRAD_TEXTURE_SIZE*4); - e = raster->stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, GF_PIXEL_ARGB, GF_PIXEL_ARGB, 1); + e = gf_evg_stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, GF_PIXEL_ARGB); } } st->txh.transparent = transparent; if (e) { gf_free(st->tx_data); - raster->stencil_delete(texture2D); - raster->surface_delete(surface); + gf_evg_stencil_delete(texture2D); + gf_evg_surface_delete(surface); return; } - e = raster->surface_attach_to_texture(surface, texture2D); + e = gf_evg_surface_attach_to_texture(surface, texture2D); if (e) { - raster->stencil_delete(texture2D); - raster->surface_delete(surface); + gf_evg_stencil_delete(texture2D); + gf_evg_surface_delete(surface); return; } /*create & setup gradient*/ - stenc = raster->stencil_new(raster, GF_STENCIL_LINEAR_GRADIENT); + stenc = gf_evg_stencil_new(GF_STENCIL_LINEAR_GRADIENT); if (!stenc) { - raster->stencil_delete(texture2D); - raster->surface_delete(surface); + gf_evg_stencil_delete(texture2D); + gf_evg_surface_delete(surface); return; } /*move line to object space*/ @@ -262,16 +261,16 @@ static void BuildLinearGradientTexture(GF_TextureHandler *txh) end.x *= GRAD_TEXTURE_SIZE; start.y *= GRAD_TEXTURE_SIZE; end.y *= GRAD_TEXTURE_SIZE; - raster->stencil_set_linear_gradient(stenc, start.x, start.y, end.x, end.y); + gf_evg_stencil_set_linear_gradient(stenc, start.x, start.y, end.x, end.y); const_a = (lg->opacity.count == 1) ? 1 : 0; cols = (u32*)gf_malloc(sizeof(u32) * lg->key.count); for (i=0; ikey.count; i++) { a = (const_a ? lg->opacity.vals[0] : lg->opacity.vals[i]); cols[i] = GF_COL_ARGB_FIXED(a, lg->keyValue.vals[i].red, lg->keyValue.vals[i].green, lg->keyValue.vals[i].blue); } - raster->stencil_set_gradient_interpolation(stenc, lg->key.vals, cols, lg->key.count); + gf_evg_stencil_set_gradient_interpolation(stenc, lg->key.vals, cols, lg->key.count); gf_free(cols); - raster->stencil_set_gradient_mode(stenc, (GF_GradientMode)lg->spreadMethod); + gf_evg_stencil_set_gradient_mode(stenc, (GF_GradientMode)lg->spreadMethod); /*fill surface*/ path = gf_path_new(); @@ -291,14 +290,14 @@ static void BuildLinearGradientTexture(GF_TextureHandler *txh) gf_mx2d_add_translation(&mat, -INT2FIX(GRAD_TEXTURE_HSIZE), -INT2FIX(GRAD_TEXTURE_HSIZE)); /*back to GL bottom->up order*/ gf_mx2d_add_scale(&mat, FIX_ONE, -FIX_ONE); - raster->stencil_set_matrix(stenc, &mat); - - raster->surface_set_raster_level(surface, GF_RASTER_HIGH_QUALITY); - raster->surface_set_path(surface, path); - raster->surface_fill(surface, stenc); - raster->stencil_delete(stenc); - raster->surface_delete(surface); - raster->stencil_delete(texture2D); + gf_evg_stencil_set_matrix(stenc, &mat); + + gf_evg_surface_set_raster_level(surface, GF_RASTER_HIGH_QUALITY); + gf_evg_surface_set_path(surface, path); + gf_evg_surface_fill(surface, stenc); + gf_evg_stencil_delete(stenc); + gf_evg_surface_delete(surface); + gf_evg_stencil_delete(texture2D); gf_path_del(path); txh->data = st->tx_data; @@ -325,7 +324,7 @@ static void BuildLinearGradientTexture(GF_TextureHandler *txh) } } else { txh->stride = GRAD_TEXTURE_SIZE*3; - txh->pixelformat = GF_PIXEL_RGB_24; + txh->pixelformat = GF_PIXEL_RGB; } txh->flags |= GF_SR_TEXTURE_NO_GL_FLIP; gf_sc_texture_set_data(txh); @@ -361,17 +360,15 @@ static void BuildRadialGradientTexture(GF_TextureHandler *txh) Fixed a, radius; Bool const_a; GF_Matrix2D mat; - GF_STENCIL stenc; - GF_SURFACE surface; - GF_STENCIL texture2D; + GF_EVGStencil * stenc; + GF_EVGSurface *surface; + GF_EVGStencil * texture2D; GF_Path *path; GF_Err e; u32 pix_fmt = 0; Bool transparent; M_RadialGradient *rg = (M_RadialGradient*) txh->owner; GradientStack *st = (GradientStack *) gf_node_get_private(txh->owner); - GF_Raster2D *raster = txh->compositor->rasterizer; - if (!txh->tx_io) return; @@ -387,11 +384,11 @@ static void BuildRadialGradientTexture(GF_TextureHandler *txh) transparent = (rg->opacity.count==1) ? ((rg->opacity.vals[0]!=FIX_ONE) ? 1 : 0) : 1; /*init our 2D graphics stuff*/ - texture2D = raster->stencil_new(raster, GF_STENCIL_TEXTURE); + texture2D = gf_evg_stencil_new(GF_STENCIL_TEXTURE); if (!texture2D) return; - surface = raster->surface_new(raster, 1); + surface = gf_evg_surface_new(1); if (!surface) { - raster->stencil_delete(texture2D); + gf_evg_stencil_delete(texture2D); return; } @@ -410,17 +407,17 @@ static void BuildRadialGradientTexture(GF_TextureHandler *txh) pix_fmt = GF_PIXEL_RGBA; - e = raster->stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, pix_fmt, pix_fmt, 1); + e = gf_evg_stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, pix_fmt); if (e) { pix_fmt = GF_PIXEL_ARGB; - e = raster->stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, pix_fmt, pix_fmt, 1); + e = gf_evg_stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, pix_fmt); } } else { if (!st->tx_data) { st->tx_data = (char *) gf_malloc(sizeof(char)*GRAD_TEXTURE_SIZE*GRAD_TEXTURE_SIZE*3); } - e = raster->stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 3*GRAD_TEXTURE_SIZE, GF_PIXEL_RGB_24, GF_PIXEL_RGB_24, 1); + e = gf_evg_stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 3*GRAD_TEXTURE_SIZE, GF_PIXEL_RGB); /*try with ARGB (it actually is needed for GDIplus module since GDIplus cannot handle native RGB texture (it works in BGR)*/ if (e) { /*remember for later use*/ @@ -428,29 +425,29 @@ static void BuildRadialGradientTexture(GF_TextureHandler *txh) transparent = 1; gf_free(st->tx_data); st->tx_data = (char *) gf_malloc(sizeof(char)*GRAD_TEXTURE_SIZE*GRAD_TEXTURE_SIZE*4); - e = raster->stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, GF_PIXEL_ARGB, GF_PIXEL_ARGB, 1); + e = gf_evg_stencil_set_texture(texture2D, st->tx_data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, GF_PIXEL_ARGB); } } st->txh.transparent = transparent; if (e) { gf_free(st->tx_data); - raster->stencil_delete(texture2D); - raster->surface_delete(surface); + gf_evg_stencil_delete(texture2D); + gf_evg_surface_delete(surface); return; } - e = raster->surface_attach_to_texture(surface, texture2D); + e = gf_evg_surface_attach_to_texture(surface, texture2D); if (e) { - raster->stencil_delete(texture2D); - raster->surface_delete(surface); + gf_evg_stencil_delete(texture2D); + gf_evg_surface_delete(surface); return; } /*create & setup gradient*/ - stenc = raster->stencil_new(raster, GF_STENCIL_RADIAL_GRADIENT); + stenc = gf_evg_stencil_new(GF_STENCIL_RADIAL_GRADIENT); if (!stenc) { - raster->stencil_delete(texture2D); - raster->surface_delete(surface); + gf_evg_stencil_delete(texture2D); + gf_evg_surface_delete(surface); } center = rg->center; @@ -464,7 +461,7 @@ static void BuildRadialGradientTexture(GF_TextureHandler *txh) focal.y *= GRAD_TEXTURE_SIZE; radius *= GRAD_TEXTURE_SIZE; - raster->stencil_set_radial_gradient(stenc, center.x, center.y, focal.x, focal.y, radius, radius); + gf_evg_stencil_set_radial_gradient(stenc, center.x, center.y, focal.x, focal.y, radius, radius); const_a = (rg->opacity.count == 1) ? 1 : 0; cols = (u32*) gf_malloc(sizeof(u32) * rg->key.count); @@ -472,9 +469,9 @@ static void BuildRadialGradientTexture(GF_TextureHandler *txh) a = (const_a ? rg->opacity.vals[0] : rg->opacity.vals[i]); cols[i] = GF_COL_ARGB_FIXED(a, rg->keyValue.vals[i].red, rg->keyValue.vals[i].green, rg->keyValue.vals[i].blue); } - raster->stencil_set_gradient_interpolation(stenc, rg->key.vals, cols, rg->key.count); + gf_evg_stencil_set_gradient_interpolation(stenc, rg->key.vals, cols, rg->key.count); gf_free(cols); - raster->stencil_set_gradient_mode(stenc, (GF_GradientMode)rg->spreadMethod); + gf_evg_stencil_set_gradient_mode(stenc, (GF_GradientMode)rg->spreadMethod); /*fill surface*/ path = gf_path_new(); @@ -493,14 +490,14 @@ static void BuildRadialGradientTexture(GF_TextureHandler *txh) gf_mx2d_add_translation(&mat, -INT2FIX(GRAD_TEXTURE_HSIZE), -INT2FIX(GRAD_TEXTURE_HSIZE)); /*back to GL bottom->up order*/ gf_mx2d_add_scale(&mat, FIX_ONE, -FIX_ONE); - raster->stencil_set_matrix(stenc, &mat); - - raster->surface_set_raster_level(surface, GF_RASTER_HIGH_QUALITY); - raster->surface_set_path(surface, path); - raster->surface_fill(surface, stenc); - raster->stencil_delete(stenc); - raster->surface_delete(surface); - raster->stencil_delete(texture2D); + gf_evg_stencil_set_matrix(stenc, &mat); + + gf_evg_surface_set_raster_level(surface, GF_RASTER_HIGH_QUALITY); + gf_evg_surface_set_path(surface, path); + gf_evg_surface_fill(surface, stenc); + gf_evg_stencil_delete(stenc); + gf_evg_surface_delete(surface); + gf_evg_stencil_delete(texture2D); gf_path_del(path); txh->data = st->tx_data; @@ -517,17 +514,21 @@ static void BuildRadialGradientTexture(GF_TextureHandler *txh) for (i=0; iheight; i++) { char *data = txh->data + i*txh->stride; for (j=0; jwidth; j++) { - u32 val = *(u32 *) &data[4*j]; - data[4*j] = (val>>16) & 0xFF; - data[4*j+1] = (val>>8) & 0xFF; - data[4*j+2] = (val) & 0xFF; - data[4*j+3] = (val>>24) & 0xFF; + u8 pa, pr, pg, pb; + pa = data[4*j]; + pr = data[4*j+1]; + pg = data[4*j+2]; + pb = data[4*j+3]; + data[4*j] = pr; + data[4*j+1] = pg; + data[4*j+2] = pb; + data[4*j+3] = pa; } } } } else { txh->stride = GRAD_TEXTURE_SIZE*3; - txh->pixelformat = GF_PIXEL_RGB_24; + txh->pixelformat = GF_PIXEL_RGB; } // tx_set_blend_enable(txh, 1); txh->flags |= GF_SR_TEXTURE_NO_GL_FLIP; @@ -538,7 +539,7 @@ static void BuildRadialGradientTexture(GF_TextureHandler *txh) static void UpdateRadialGradient(GF_TextureHandler *txh) { Bool const_a; - GF_STENCIL stencil; + GF_EVGStencil * stencil; u32 i, *cols; Fixed a; M_RadialGradient *rg = (M_RadialGradient*) txh->owner; @@ -553,7 +554,7 @@ static void UpdateRadialGradient(GF_TextureHandler *txh) if (!txh->tx_io) gf_sc_texture_allocate(txh); stencil = gf_sc_texture_get_stencil(txh); - if (!stencil)stencil = txh->compositor->rasterizer->stencil_new(txh->compositor->rasterizer, GF_STENCIL_RADIAL_GRADIENT); + if (!stencil) stencil = gf_evg_stencil_new(GF_STENCIL_RADIAL_GRADIENT); /*set stencil even if assigned, this invalidates the associated bitmap state in 3D*/ gf_sc_texture_set_stencil(txh, stencil); @@ -574,16 +575,16 @@ static void UpdateRadialGradient(GF_TextureHandler *txh) a = (const_a ? rg->opacity.vals[0] : rg->opacity.vals[i]); cols[i] = GF_COL_ARGB_FIXED(a, rg->keyValue.vals[i].red, rg->keyValue.vals[i].green, rg->keyValue.vals[i].blue); } - txh->compositor->rasterizer->stencil_set_gradient_interpolation(stencil, rg->key.vals, cols, rg->key.count); + gf_evg_stencil_set_gradient_interpolation(stencil, rg->key.vals, cols, rg->key.count); gf_free(cols); - txh->compositor->rasterizer->stencil_set_gradient_mode(stencil, (GF_GradientMode) rg->spreadMethod); + gf_evg_stencil_set_gradient_mode(stencil, (GF_GradientMode) rg->spreadMethod); } static void RG_ComputeMatrix(GF_TextureHandler *txh, GF_Rect *bounds, GF_Matrix2D *mat, Bool for_3d) { - GF_STENCIL stencil; + GF_EVGStencil * stencil; M_RadialGradient *rg = (M_RadialGradient *) txh->owner; if (rg->key.count<2) return; @@ -596,7 +597,7 @@ static void RG_ComputeMatrix(GF_TextureHandler *txh, GF_Rect *bounds, GF_Matrix2 GradientGetMatrix((GF_Node *) rg->transform, mat); - txh->compositor->rasterizer->stencil_set_radial_gradient(stencil, rg->center.x, rg->center.y, rg->focalPoint.x, rg->focalPoint.y, rg->radius, rg->radius); + gf_evg_stencil_set_radial_gradient(stencil, rg->center.x, rg->center.y, rg->focalPoint.x, rg->focalPoint.y, rg->radius, rg->radius); /*move to center of bounds*/ gf_mx2d_add_translation(mat, gf_divfix(bounds->x, bounds->width), gf_divfix(bounds->y - bounds->height, bounds->height)); /*scale back to object coordinates - the gradient is still specified in texture coordinates diff --git a/src/compositor/mpeg4_grouping.c b/src/compositor/mpeg4_grouping.c index 55ca4bf..6c6dbd5 100644 --- a/src/compositor/mpeg4_grouping.c +++ b/src/compositor/mpeg4_grouping.c @@ -379,11 +379,6 @@ void group_2d_traverse_with_order(GF_Node *node, GroupingNode2D *group, GF_Trave #ifndef GPAC_DISABLE_3D -void group_3d_setup(GroupingNode *group, GF_Node *node) -{ - memset(group, 0, sizeof(GroupingNode)); -} - void group_3d_delete(GF_Node *node) { GroupingNode *group = (GroupingNode *)gf_node_get_private(node); @@ -723,7 +718,6 @@ void parent_node_traverse(GF_Node *node, ParentNode2D *group, GF_TraverseState * /*picking: collect sensors*/ sensor_backup = NULL; if ((tr_state->traversing_mode==TRAVERSE_PICK) && (group->flags & GROUP_HAS_SENSORS) ) { - GF_SensorHandler *hsens; /*reset sensor stack if any sensors at this level*/ sensor_backup = tr_state->vrml_sensors; tr_state->vrml_sensors = gf_list_new(); @@ -732,7 +726,7 @@ void parent_node_traverse(GF_Node *node, ParentNode2D *group, GF_TraverseState * /*add sensor(s) to traversing state*/ l = ((GF_ParentNode *)node)->children; while (l) { - hsens = compositor_mpeg4_get_sensor_handler_ex(l->node, GF_TRUE); + GF_SensorHandler *hsens = compositor_mpeg4_get_sensor_handler_ex(l->node, GF_TRUE); if (hsens) gf_list_add(tr_state->vrml_sensors, hsens); l = l->next; } diff --git a/src/compositor/mpeg4_grouping_2d.c b/src/compositor/mpeg4_grouping_2d.c index dce9873..2110c4c 100644 --- a/src/compositor/mpeg4_grouping_2d.c +++ b/src/compositor/mpeg4_grouping_2d.c @@ -265,6 +265,7 @@ void compositor_init_transformmatrix2d(GF_Compositor *compositor, GF_Node *node) { Transform2DStack *stack; GF_SAFEALLOC(stack, Transform2DStack); + if (!stack) return; gf_mx2d_init(stack->mat); gf_node_set_private(node, stack); @@ -337,6 +338,7 @@ void compositor_init_colortransform(GF_Compositor *compositor, GF_Node *node) { ColorTransformStack *stack; GF_SAFEALLOC(stack, ColorTransformStack); + if (!stack) return; gf_cmx_init(&stack->cmat); gf_node_set_private(node, stack); @@ -417,6 +419,8 @@ void compositor_init_orderedgroup(GF_Compositor *compositor, GF_Node *node) { OrderedGroupStack *ptr; GF_SAFEALLOC(ptr, OrderedGroupStack); + if (!ptr) return; + gf_node_set_private(node, ptr); gf_node_set_callback_function(node, TraverseOrderedGroup); } diff --git a/src/compositor/mpeg4_grouping_3d.c b/src/compositor/mpeg4_grouping_3d.c index 1ebf79c..2c87a0b 100644 --- a/src/compositor/mpeg4_grouping_3d.c +++ b/src/compositor/mpeg4_grouping_3d.c @@ -50,6 +50,7 @@ void compositor_init_group(GF_Compositor *compositor, GF_Node *node) { GroupingNode2D *ptr; GF_SAFEALLOC(ptr, GroupingNode2D); + if (!ptr) return; gf_node_set_private(node, ptr); gf_node_set_callback_function(node, TraverseGroup); } @@ -168,6 +169,7 @@ static void NewTransformStack(GF_Compositor *compositor, GF_Node *node, GF_Child { TransformStack *st; GF_SAFEALLOC(st, TransformStack); + if (!st) return; gf_mx_init(st->mx); gf_node_set_private(node, st); diff --git a/src/terminal/mpeg4_inline.c b/src/compositor/mpeg4_inline.c similarity index 82% rename from src/terminal/mpeg4_inline.c rename to src/compositor/mpeg4_inline.c index 0739fbf..58ad056 100644 --- a/src/terminal/mpeg4_inline.c +++ b/src/compositor/mpeg4_inline.c @@ -2,10 +2,10 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2017 * All rights reserved * - * This file is part of GPAC / Media terminal sub-project + * This file is part of GPAC / Scene Compositor sub-project * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -29,8 +29,7 @@ #include /*for URL concatenation*/ #include -#include -#include "media_control.h" +#include #include #include #include @@ -47,7 +46,7 @@ void gf_inline_restart(GF_Scene *scene) { scene->needs_restart = 1; - gf_term_invalidate_compositor(scene->root_od->term); + gf_sc_invalidate(scene->compositor, NULL); } @@ -62,17 +61,19 @@ static Bool gf_inline_set_scene(M_Inline *root) mo = gf_scene_get_media_object_ex(parent, &root->url, GF_MEDIA_OBJECT_SCENE, GF_FALSE, NULL, GF_FALSE, (GF_Node*)root); if (!mo) return GF_FALSE; //invalidate as soon as we have an mo (eg something is attached to the scene) - gf_term_invalidate_compositor(parent->root_od->term); + gf_sc_invalidate(parent->compositor, NULL); if (!mo->odm) return GF_FALSE; if (!mo->odm->subscene) { - gf_term_invalidate_compositor(parent->root_od->term); + gf_sc_invalidate(parent->compositor, NULL); return GF_FALSE; } /*assign inline scene as private stack of inline node, and remember inline node for event propagation*/ gf_node_set_private((GF_Node *)root, mo->odm->subscene); mo->odm->subscene->object_attached = GF_TRUE; + if (gf_list_find(mo->odm->subscene->attached_inlines, root)<0) + gf_list_add(mo->odm->subscene->attached_inlines, root); /*play*/ gf_mo_play(mo, 0, -1, GF_FALSE); @@ -95,7 +96,7 @@ void gf_inline_on_modified(GF_Node *node) if (mo) { Bool changed = GF_TRUE; if (ODID != GF_MEDIA_EXTERNAL_ID) { - if (ODID && (ODID==scene->root_od->OD->objectDescriptorID)) changed = GF_FALSE; + if (ODID && (ODID==scene->root_od->ID)) changed = GF_FALSE; } else { if (gf_mo_is_same_url(mo, &pInline->url, NULL, 0) ) changed = GF_FALSE; } @@ -131,18 +132,13 @@ void gf_inline_on_modified(GF_Node *node) } } } else { - gf_term_lock_media_queue(scene->root_od->term, GF_TRUE); /*external media are completely unloaded, except addons which are only declared once */ - if (!scene->root_od->addon && (scene->root_od->OD->objectDescriptorID==GF_MEDIA_EXTERNAL_ID)) { - scene->root_od->action_type = GF_ODM_ACTION_DELETE; + if (!scene->root_od->addon && (scene->root_od->ID==GF_MEDIA_EXTERNAL_ID)) { + gf_odm_disconnect(scene->root_od, 2); } else { - scene->root_od->action_type = GF_ODM_ACTION_STOP; + gf_odm_stop_or_destroy(scene->root_od); } - if (gf_list_find(scene->root_od->term->media_queue, scene->root_od)<0) - gf_list_add(scene->root_od->term->media_queue, scene->root_od); - - gf_term_lock_media_queue(scene->root_od->term, GF_FALSE); } } } @@ -175,7 +171,7 @@ static void gf_inline_check_restart(GF_Scene *scene) if (scene->root_od->media_ctrl && scene->root_od->media_ctrl->control->loop) { GF_Clock *ck = gf_odm_get_media_clock(scene->root_od); - if (ck->has_seen_eos) { + if (ck->has_seen_eos && !ck->nb_paused) { u32 now = gf_clock_time(ck); u64 dur = scene->duration; if (scene->root_od->media_ctrl->current_seg) { @@ -197,7 +193,7 @@ static void gf_inline_check_restart(GF_Scene *scene) scene->root_od->media_ctrl->current_seg = 0; } else { /*trigger render until to watch for restart...*/ - gf_term_invalidate_compositor(scene->root_od->term); + gf_sc_invalidate(scene->compositor, NULL); } } } @@ -209,7 +205,7 @@ void gf_scene_mpeg4_inline_check_restart(GF_Scene *scene) gf_inline_check_restart(scene); if (scene->needs_restart) { - gf_term_invalidate_compositor(scene->root_od->term); + gf_sc_invalidate(scene->compositor, NULL); return; } } @@ -244,7 +240,7 @@ void gf_scene_mpeg4_inline_restart(GF_Scene *scene) gf_scene_disconnect(scene, GF_FALSE); if (scene->root_od->media_ctrl) scene->root_od->media_ctrl->current_seg = current_seg; /*3- restart the scene*/ - gf_odm_start(scene->root_od, 0); + gf_odm_start(scene->root_od); } } @@ -258,6 +254,8 @@ static void gf_inline_traverse(GF_Node *n, void *rs, Bool is_destroy) if (!scene) return; mo = scene->root_od ? scene->root_od->mo : NULL; + gf_list_del_item(scene->attached_inlines, n); + gf_scene_notify_event(scene, GF_EVENT_UNLOAD, n, NULL, GF_OK, GF_TRUE); if (!mo) return; gf_mo_event_target_remove_by_node(mo, n); @@ -266,8 +264,6 @@ static void gf_inline_traverse(GF_Node *n, void *rs, Bool is_destroy) if (mo->num_open) { mo->num_open --; if (!mo->num_open) { - gf_term_lock_media_queue(scene->root_od->term, GF_TRUE); - /*this is unspecified in the spec: whenever an inline not using the OD framework is destroyed, destroy the associated resource*/ if (mo->OD_ID == GF_MEDIA_EXTERNAL_ID) { @@ -282,13 +278,11 @@ static void gf_inline_traverse(GF_Node *n, void *rs, Bool is_destroy) } gf_mo_del(mo); } - scene->root_od->action_type = GF_ODM_ACTION_DELETE; - gf_list_add(scene->root_od->term->media_queue, scene->root_od); + gf_odm_disconnect(scene->root_od, 2); } else { - scene->root_od->action_type = GF_ODM_ACTION_SCENE_DISCONNECT; - gf_list_add(scene->root_od->term->media_queue, scene->root_od); + gf_odm_stop(scene->root_od, 1); + gf_scene_disconnect(scene->root_od->subscene, GF_TRUE); } - gf_term_lock_media_queue(scene->root_od->term, GF_FALSE); } } return; @@ -319,7 +313,7 @@ static void gf_inline_traverse(GF_Node *n, void *rs, Bool is_destroy) gf_node_dirty_set(n, 0, GF_TRUE); //and request bew anim frame until attached if (scene->object_attached) - gf_term_invalidate_compositor(scene->root_od->term); + gf_sc_invalidate(scene->compositor, NULL); return; } /*clear dirty flags for any sub-inlines, bitmaps or protos*/ @@ -327,7 +321,7 @@ static void gf_inline_traverse(GF_Node *n, void *rs, Bool is_destroy) current_url = scene->current_url; scene->current_url = & ((M_Inline*)n)->url; - gf_sc_traverse_subscene(scene->root_od->term->compositor, n, scene->graph, rs); + gf_sc_traverse_subscene(scene->compositor, n, scene->graph, rs); scene->current_url = current_url; //do we have to restart for next frame ? If so let's do it @@ -343,11 +337,7 @@ static void gf_inline_traverse(GF_Node *n, void *rs, Bool is_destroy) } scene->needs_restart = 0; - gf_term_lock_media_queue(scene->root_od->term, GF_TRUE); - scene->root_od->action_type = GF_ODM_ACTION_SCENE_INLINE_RESTART; - gf_list_add(scene->root_od->term->media_queue, scene->root_od); - gf_term_lock_media_queue(scene->root_od->term, GF_FALSE); - + gf_scene_mpeg4_inline_restart(scene); gf_node_dirty_set(n, 0, GF_TRUE); return; } @@ -355,14 +345,14 @@ static void gf_inline_traverse(GF_Node *n, void *rs, Bool is_destroy) } -static Bool gf_inline_is_hardcoded_proto(GF_Terminal *term, MFURL *url) +static Bool gf_inline_is_hardcoded_proto(GF_Compositor *compositor, MFURL *url) { u32 i; for (i=0; icount; i++) { if (!url->vals[i].url) continue; if (strstr(url->vals[i].url, "urn:inet:gpac:builtin")) return GF_TRUE; - if (gf_sc_uri_is_hardcoded_proto(term->compositor, url->vals[i].url)) + if (gf_sc_uri_is_hardcoded_proto(compositor, url->vals[i].url)) return GF_TRUE; } return GF_FALSE; @@ -373,14 +363,33 @@ GF_SceneGraph *gf_inline_get_proto_lib(void *_is, MFURL *lib_url) GF_ProtoLink *pl; u32 i; GF_Scene *scene = (GF_Scene *) _is; - if (!scene || !lib_url->count) return NULL; + if (!scene) return NULL; + + //this is a scene reset, destroy all proto links + if (!lib_url) { + while (gf_list_count(scene->extern_protos)) { + pl = gf_list_pop_back(scene->extern_protos); + if (pl->mo) { + //proto link was not attached, manual discard + if (!pl->mo->odm) { + gf_sg_vrml_mf_reset(&pl->mo->URLs, GF_SG_VRML_MFURL); + gf_list_del_item(scene->scene_objects, pl->mo); + gf_mo_del(pl->mo); + } + } + gf_free(pl); + } + return NULL; + } + if (!lib_url->count) + return NULL; - if (gf_inline_is_hardcoded_proto(scene->root_od->term, lib_url)) return (void *) GF_SG_INTERNAL_PROTO; + if (gf_inline_is_hardcoded_proto(scene->compositor, lib_url)) return (void *) GF_SG_INTERNAL_PROTO; i=0; while ((pl = (GF_ProtoLink*)gf_list_enum(scene->extern_protos, &i))) { /*not ready yet*/ - if (!pl->mo || !pl->mo->odm || ! pl->mo->odm->net_service) continue; + if (!pl->mo || !pl->mo->odm) continue; if (gf_mo_get_od_id(pl->url) != GF_MEDIA_EXTERNAL_ID) { if (gf_mo_get_od_id(pl->url) == gf_mo_get_od_id(lib_url)) { @@ -399,17 +408,19 @@ GF_SceneGraph *gf_inline_get_proto_lib(void *_is, MFURL *lib_url) char *url1, *url2; Bool ok; if (!pl->mo || !pl->mo->odm) continue; - if (! pl->mo->odm->net_service) continue; + if (gf_mo_get_od_id(pl->url) != GF_MEDIA_EXTERNAL_ID) continue; /*not the same url*/ if (!gf_mo_is_same_url(pl->mo, lib_url, NULL, 0)) continue; - /*check the url path is the same*/ - url1 = gf_url_concatenate(pl->mo->odm->net_service->url, lib_url->vals[0].url); - url2 = gf_url_concatenate(scene->root_od->net_service->url, lib_url->vals[0].url); ok = GF_FALSE; + + /*check the url path is the same*/ + url1 = gf_url_concatenate(pl->mo->odm->scene_ns->url, lib_url->vals[0].url); + url2 = gf_url_concatenate(scene->root_od->scene_ns->url, lib_url->vals[0].url); if (url1 && url2 && !strcmp(url1, url2)) ok=GF_TRUE; if (url1) gf_free(url1); if (url2) gf_free(url2); + if (!ok) continue; if (!pl->mo->odm || !pl->mo->odm->subscene) return NULL; return pl->mo->odm->subscene->graph; @@ -423,7 +434,7 @@ GF_SceneGraph *gf_inline_get_proto_lib(void *_is, MFURL *lib_url) if (!lib_url || !lib_url->count) return NULL; /*internal, don't waste ressources*/ - if (gf_inline_is_hardcoded_proto(scene->root_od->term, lib_url)) return NULL; + if (gf_inline_is_hardcoded_proto(scene->compositor, lib_url)) return NULL; i=0; while ((pl = (GF_ProtoLink*)gf_list_enum(scene->extern_protos, &i)) ) { @@ -442,16 +453,6 @@ GF_SceneGraph *gf_inline_get_proto_lib(void *_is, MFURL *lib_url) return NULL; } -Bool gf_inline_is_protolib_object(GF_Scene *scene, GF_ObjectManager *odm) -{ - u32 i; - GF_ProtoLink *pl; - i=0; - while ((pl = (GF_ProtoLink*)gf_list_enum(scene->extern_protos, &i))) { - if (pl->mo->odm == odm) return GF_TRUE; - } - return GF_FALSE; -} GF_EXPORT Bool gf_inline_is_default_viewpoint(GF_Node *node) @@ -465,7 +466,7 @@ Bool gf_inline_is_default_viewpoint(GF_Node *node) if (!nname) return GF_FALSE; /*check any viewpoint*/ - seg_name = strrchr(scene->root_od->net_service->url, '#'); + seg_name = strrchr(scene->root_od->scene_ns->url, '#'); /*check the URL of the parent*/ if (!seg_name && scene->current_url) { @@ -488,13 +489,6 @@ void gf_init_inline(GF_Scene *scene, GF_Node *node) gf_node_set_callback_function(node, gf_inline_traverse); } -static GF_Config *storage_get_cfg(M_Storage *storage) -{ - GF_Scene *scene; - scene = (GF_Scene *)gf_node_get_private((GF_Node*)storage); - return scene->root_od->term->user->config; -} - static char *storage_get_section(M_Storage *storage) { GF_Scene *scene; @@ -506,9 +500,9 @@ static char *storage_get_section(M_Storage *storage) scene = (GF_Scene *)gf_node_get_private((GF_Node*)storage); - len = strlen(scene->root_od->net_service->url)+strlen(storage->name.buffer)+2; + len = strlen(scene->root_od->scene_ns->url)+strlen(storage->name.buffer)+2; szPath = (char *)gf_malloc(sizeof(char)* len); - strcpy(szPath, scene->root_od->net_service->url); + strcpy(szPath, scene->root_od->scene_ns->url); strcat(szPath, "@"); strcat(szPath, storage->name.buffer); gf_sha1_csum((u8*)szPath, (u32) strlen(szPath), hash); @@ -567,26 +561,25 @@ static void gf_storage_load(M_Storage *storage) char szID[20]; u32 i, count; u32 sec, exp, frac; - GF_Config *cfg = storage_get_cfg(storage); char *section = storage_get_section(storage); - if (!cfg || !section) return; + if (!section) return; - if (!gf_cfg_get_key_count(cfg, section)) { + if (!gf_opts_get_key_count(section)) { gf_free(section); return; } - opt = gf_cfg_get_key(cfg, section, "expireAfterNTP"); + opt = gf_opts_get_key(section, "expireAfterNTP"); gf_net_get_ntp(&sec, &frac); sscanf(opt, "%u", &exp); if (exp && (exp<=sec)) { - gf_cfg_del_section(cfg, section); + gf_opts_del_section(section); gf_free(section); return; } - count = gf_cfg_get_key_count(cfg, section)-1; + count = gf_opts_get_key_count(section)-1; if (!count || (count!=storage->storageList.count)) { - gf_cfg_del_section(cfg, section); + gf_opts_del_section(section); gf_free(section); return; } @@ -594,7 +587,7 @@ static void gf_storage_load(M_Storage *storage) for (i=0; istorageList.vals[i].node) break; if (gf_node_get_field(storage->storageList.vals[i].node, storage->storageList.vals[i].fieldIndex, &info) != GF_OK) break; @@ -658,11 +651,10 @@ void gf_storage_save(M_Storage *storage) { char szID[20]; u32 i, j; - GF_Config *cfg = storage_get_cfg(storage); char *section = storage_get_section(storage); - if (!cfg || !section) return; + if (!section) return; - gf_cfg_del_section(cfg, section); + gf_opts_del_section(section); if (storage->expireAfter) { u32 sec, frac; @@ -670,9 +662,9 @@ void gf_storage_save(M_Storage *storage) gf_net_get_ntp(&sec, &frac); sec += storage->expireAfter; sprintf(szNTP, "%u", sec); - gf_cfg_set_key(cfg, section, "expireAfterNTP", szNTP); + gf_opts_set_key(section, "expireAfterNTP", szNTP); } else { - gf_cfg_set_key(cfg, section, "expireAfterNTP", "0"); + gf_opts_set_key(section, "expireAfterNTP", "0"); } for (i=0; istorageList.count; i++) { @@ -687,10 +679,10 @@ void gf_storage_save(M_Storage *storage) val = storage_serialize_sf(info.far_ptr, info.fieldType); } else { //u32 sftype = gf_sg_vrml_get_sf_type(info.fieldType); - char *slotval; - void *slot; val = NULL; for (j=0; j<((GenMFField *)info.far_ptr)->count; j++) { + char *slotval; + void *slot; if (gf_sg_vrml_mf_get_item(info.far_ptr, info.fieldType, &slot, j) != GF_OK) break; slotval = storage_serialize_sf(info.far_ptr, info.fieldType); if (!slotval) break; @@ -707,7 +699,7 @@ void gf_storage_save(M_Storage *storage) } } if (val) { - gf_cfg_set_key(cfg, section, szID, val); + gf_opts_set_key(section, szID, val); gf_free(val); } } @@ -719,9 +711,9 @@ static void gf_storage_traverse(GF_Node *n, void *rs, Bool is_destroy) { if (is_destroy) { GF_Scene *scene = (GF_Scene *)gf_node_get_private(n); - GF_ClientService *net_service = scene->root_od->net_service; + GF_SceneNamespace *scene_ns = scene->root_od->scene_ns; while (scene->root_od->parentscene) { - if (scene->root_od->parentscene->root_od->net_service != net_service) + if (scene->root_od->parentscene->root_od->scene_ns != scene_ns) break; scene = scene->root_od->parentscene; } @@ -731,16 +723,16 @@ static void gf_storage_traverse(GF_Node *n, void *rs, Bool is_destroy) static void on_force_restore(GF_Node *n, struct _route *_route) { - gf_storage_load((M_Storage *)n); + if (n) gf_storage_load((M_Storage *)n); } static void on_force_save(GF_Node *n, struct _route *_route) { - gf_storage_save((M_Storage *)n); + if (n) gf_storage_save((M_Storage *)n); } void gf_scene_init_storage(GF_Scene *scene, GF_Node *node) { - GF_ClientService *net_service; + GF_SceneNamespace *scene_ns; M_Storage *storage = (M_Storage *) node; if (!storage->name.buffer || !strlen(storage->name.buffer) ) return; @@ -751,17 +743,28 @@ void gf_scene_init_storage(GF_Scene *scene, GF_Node *node) gf_node_set_callback_function(node, gf_storage_traverse); gf_node_set_private(node, scene); - net_service = scene->root_od->net_service; + scene_ns = scene->root_od->scene_ns; while (scene->root_od->parentscene) { - if (scene->root_od->parentscene->root_od->net_service != net_service) + if (scene->root_od->parentscene->root_od->scene_ns != scene_ns) break; scene = scene->root_od->parentscene; } + gf_list_add(scene->storages, node); if (storage->_auto) gf_storage_load(storage); -} +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + Bool aptr; + on_force_save(NULL, NULL); + on_force_restore(NULL, NULL); + storage_parse_sf(&aptr, GF_SG_VRML_SFBOOL, "true"); + + } #endif +} + +#endif // GPAC_DISABLE_VRML GF_Node *gf_scene_get_keynav(GF_SceneGraph *sg, GF_Node *sensor) { diff --git a/src/terminal/input_sensor.c b/src/compositor/mpeg4_inputsensor.c similarity index 62% rename from src/terminal/input_sensor.c rename to src/compositor/mpeg4_inputsensor.c index 3b20d5d..6460bf1 100644 --- a/src/terminal/input_sensor.c +++ b/src/compositor/mpeg4_inputsensor.c @@ -2,10 +2,10 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2017 * All rights reserved * - * This file is part of GPAC / Media terminal sub-project + * This file is part of GPAC / Scene Compositor sub-project * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -23,47 +23,76 @@ * */ -#include #include +#include #include #include #include #ifndef GPAC_DISABLE_VRML -#include "input_sensor.h" +enum +{ + IS_KeySensor = 1, + IS_StringSensor, + IS_Mouse, + IS_HTKSensor, +}; -#if GPAC_HTK_DEMO +typedef struct +{ + /*object for this input stream*/ + GF_ObjectManager *odm; -void HTK_Init(const char *htk_path); -void HTK_Close(void); -void HTK_SetDictionary(char *dicofile); -void HTK_DoDetection(void); -const char *HTK_GetWord(); -int HTK_GetWordIndex(); -float HTK_GetWordScore(); -void StartHTK(ISPriv *is_dec); + /*list of attached nodes*/ + GF_List *is_nodes; + /*stream ID*/ + u16 ES_ID; + /*uncompressed data frame*/ + GF_List *ddf; + + GF_InputSensorDevice *io_dev; + + u32 type; + + /*string sensor sep char */ + s16 termChar, delChar; + /*current typed text in UTF-8*/ + unsigned short enteredText[5000]; + u32 text_len; +} GF_InputSensorCtx; + + +void gf_isdec_del(GF_BaseDecoder *plug); + +static GF_Err IS_ProcessData(GF_InputSensorCtx *is_ctx, const char *inBuffer, u32 inBufferLength); + +typedef struct +{ + /*stream context*/ + u16 ES_ID; + Bool registered; + GF_MediaObject *mo; + M_InputSensor *is; +} ISStack; + + + +typedef struct +{ + u16 enteredText[5000]; + u32 text_len; + GF_Compositor *compositor; +} StringSensorStack; -static u32 htk_num_users = 0; -#endif /* input sensor decoder(s) handling */ -GF_Err gf_isdec_configure(GF_BaseDecoder *plug, GF_Scene *scene, Bool is_remote) -{ - ISPriv *is = (ISPriv *)plug->privateStack; - /*we can only deal with encoded content (for now)*/ - if (!scene->scene_codec) return GF_NOT_SUPPORTED; - is->scene = scene; - is->is_local = !is_remote; - return GF_OK; -} - -static void add_field(ISPriv *priv, u32 fieldType, const char *fieldName) +static void add_field(GF_InputSensorCtx *priv, u32 fieldType, const char *fieldName) { GF_FieldInfo *field = (GF_FieldInfo *) gf_malloc(sizeof(GF_FieldInfo)); memset(field, 0, sizeof(GF_FieldInfo)); @@ -76,151 +105,180 @@ static void add_field(ISPriv *priv, u32 fieldType, const char *fieldName) static void isdev_add_field(GF_InputSensorDevice *dev, u32 fieldType, const char *fieldName) { - ISPriv *is = (ISPriv *)dev->input_decoder->privateStack; - add_field(is, fieldType, fieldName); + if (dev) { + GF_InputSensorCtx *is = (GF_InputSensorCtx *)dev->input_stream_context; + add_field(is, fieldType, fieldName); + } } -static void isdev_dispatch_frame(struct __input_device *dev, u8 *data, u32 data_len) +static void isdev_dispatch_frame(struct __input_device *dev, const u8 *data, u32 data_len) { - GF_SLHeader slh; - GF_Codec*cod; u32 i; - ISPriv *priv = (ISPriv *)dev->input_decoder->privateStack; + GF_InputSensorCtx *is_ctx; + GF_InputSensorCtx *priv; + if (!dev || !data) return; - memset(&slh, 0, sizeof(GF_SLHeader)); - slh.accessUnitStartFlag = slh.accessUnitEndFlag = 1; - slh.compositionTimeStampFlag = 1; - /*note we could use an exact TS but it's not needed: since the input is generated locally - we want it to be decoded as soon as possible, thus using 0 emulates permanent seeking on - InputSensor stream, hence forces input frame resync*/ - slh.compositionTimeStamp = 0; + priv = (GF_InputSensorCtx *)dev->input_stream_context; /*get all decoders and send frame*/ i=0; - while ((cod = (GF_Codec*)gf_list_enum(priv->scene->root_od->term->input_streams, &i))) { - ISPriv *is = (ISPriv *)cod->decio->privateStack; - if (is->type==priv->type) { - GF_Channel *ch = (GF_Channel *)gf_list_get(cod->inChannels, 0); - gf_es_receive_sl_packet(ch->service, ch, (char *)data, data_len, &slh, GF_OK); + while ((is_ctx = gf_list_enum(priv->odm->parentscene->compositor->input_streams, &i))) { + if (is_ctx->type==priv->type) { + IS_ProcessData(is_ctx, data, data_len); } } } -static GF_Err IS_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd) +static GF_InputSensorCtx *locate_is_ctx_for_odm(GF_Scene *scene, GF_ObjectManager *for_odm) { + u32 i, count; + count = gf_list_count(scene->compositor->input_streams); + for (i=0; icompositor->input_streams, i); + if (is_ctx->odm == for_odm) return is_ctx; + } + return NULL; +} + +GF_Err gf_input_sensor_setup_object(GF_ObjectManager *odm, GF_ESD *esd) +{ + u32 i; + GF_InputSensorCtx *is_ctx; GF_BitStream *bs; - u32 len, size, i; + u32 len, size; char devName[255]; u16 termSeq[20]; + GF_Scene *scene = odm->parentscene; + + if (esd->URLString) return GF_NOT_SUPPORTED; - ISPriv *is = (ISPriv *)plug->privateStack; - if (esd->decoderConfig->upstream) return GF_NOT_SUPPORTED; if (!esd->decoderConfig->decoderSpecificInfo || !esd->decoderConfig->decoderSpecificInfo->dataLength) return GF_NON_COMPLIANT_BITSTREAM; - /*no more than one UI stream per object*/ - if (is->ES_ID) return GF_NOT_SUPPORTED; - is->ES_ID = esd->ESID; + if (!scene->compositor->input_streams) { + scene->compositor->input_streams = gf_list_new(); + if (!scene->compositor->input_streams) return GF_OUT_OF_MEM; + } + is_ctx = locate_is_ctx_for_odm(scene, odm); + if (is_ctx) return GF_OK; + + GF_SAFEALLOC(is_ctx, GF_InputSensorCtx); + if (!is_ctx) return GF_OUT_OF_MEM; + + is_ctx->odm = odm; + is_ctx->is_nodes = gf_list_new(); + is_ctx->ddf = gf_list_new(); + + is_ctx->ES_ID = esd->ESID; /*parse config*/ bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ); len = gf_bs_read_int(bs, 8); for (i=0; itype = gf_crc_32(devName, len); + is_ctx->type = gf_crc_32(devName, len); size = len + 1; if (!stricmp(devName, "KeySensor")) { - is->type = IS_KeySensor; - add_field(is, GF_SG_VRML_SFINT32, "keyPressed"); - add_field(is, GF_SG_VRML_SFINT32, "keyReleased"); - add_field(is, GF_SG_VRML_SFINT32, "actionKeyPressed"); - add_field(is, GF_SG_VRML_SFINT32, "actionKeyReleased"); - add_field(is, GF_SG_VRML_SFBOOL, "shiftKeyPressed"); - add_field(is, GF_SG_VRML_SFBOOL, "controlKeyPressed"); - add_field(is, GF_SG_VRML_SFBOOL, "altKeyPressed"); + is_ctx->type = IS_KeySensor; + add_field(is_ctx, GF_SG_VRML_SFINT32, "keyPressed"); + add_field(is_ctx, GF_SG_VRML_SFINT32, "keyReleased"); + add_field(is_ctx, GF_SG_VRML_SFINT32, "actionKeyPressed"); + add_field(is_ctx, GF_SG_VRML_SFINT32, "actionKeyReleased"); + add_field(is_ctx, GF_SG_VRML_SFBOOL, "shiftKeyPressed"); + add_field(is_ctx, GF_SG_VRML_SFBOOL, "controlKeyPressed"); + add_field(is_ctx, GF_SG_VRML_SFBOOL, "altKeyPressed"); } else if (!stricmp(devName, "StringSensor")) { - is->type = IS_StringSensor; - add_field(is, GF_SG_VRML_SFSTRING, "enteredText"); - add_field(is, GF_SG_VRML_SFSTRING, "finalText"); + is_ctx->type = IS_StringSensor; + add_field(is_ctx, GF_SG_VRML_SFSTRING, "enteredText"); + add_field(is_ctx, GF_SG_VRML_SFSTRING, "finalText"); - is->termChar = '\r'; - is->delChar = '\b'; + is_ctx->termChar = '\r'; + is_ctx->delChar = '\b'; /*get escape chars if any specified*/ if (sizedecoderConfig->decoderSpecificInfo->dataLength) { const char *src = esd->decoderConfig->decoderSpecificInfo->data + size; gf_utf8_mbstowcs(termSeq, esd->decoderConfig->decoderSpecificInfo->dataLength - size, &src); - is->termChar = termSeq[0]; - is->delChar = termSeq[1]; + is_ctx->termChar = termSeq[0]; + is_ctx->delChar = termSeq[1]; } } else if (!stricmp(devName, "Mouse")) { - is->type = IS_Mouse; - add_field(is, GF_SG_VRML_SFVEC2F, "position"); - add_field(is, GF_SG_VRML_SFBOOL, "leftButtonDown"); - add_field(is, GF_SG_VRML_SFBOOL, "middleButtonDown"); - add_field(is, GF_SG_VRML_SFBOOL, "rightButtonDown"); - add_field(is, GF_SG_VRML_SFFLOAT, "wheel"); + is_ctx->type = IS_Mouse; + add_field(is_ctx, GF_SG_VRML_SFVEC2F, "position"); + add_field(is_ctx, GF_SG_VRML_SFBOOL, "leftButtonDown"); + add_field(is_ctx, GF_SG_VRML_SFBOOL, "middleButtonDown"); + add_field(is_ctx, GF_SG_VRML_SFBOOL, "rightButtonDown"); + add_field(is_ctx, GF_SG_VRML_SFFLOAT, "wheel"); } else { GF_InputSensorDevice *ifce; /*not found, check all modules*/ - u32 plugCount = gf_modules_get_count(is->scene->root_od->term->user->modules); + u32 plugCount = gf_modules_count(); for (i = 0; i < plugCount ; i++) { - ifce = (GF_InputSensorDevice *) gf_modules_load_interface(is->scene->root_od->term->user->modules, i, GF_INPUT_DEVICE_INTERFACE); + ifce = (GF_InputSensorDevice *) gf_modules_load(i, GF_INPUT_DEVICE_INTERFACE); if (!ifce) continue; - ifce->input_decoder = plug; - if (ifce->RegisterDevice && ifce->RegisterDevice(ifce, devName, bs, isdev_add_field) ) { - is->io_dev = ifce; + ifce->input_stream_context = is_ctx; + if (ifce->RegisterDevice && ifce->RegisterDevice(ifce, devName, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, isdev_add_field) ) { + is_ctx->io_dev = ifce; break; } gf_modules_close_interface((GF_BaseInterface *) ifce); } - if (!is->io_dev) return GF_NOT_SUPPORTED; + if (!is_ctx->io_dev) { + gf_free(is_ctx); + return GF_NOT_SUPPORTED; + } + is_ctx->io_dev->DispatchFrame = isdev_dispatch_frame; + } - is->io_dev->DispatchFrame = isdev_dispatch_frame; - plug->module_name = is->io_dev->module_name; - plug->author_name = is->io_dev->author_name; +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + isdev_add_field(NULL, 0, NULL); + isdev_dispatch_frame(NULL, NULL, 0); } +#endif - gf_bs_del(bs); + gf_list_add(is_ctx->odm->parentscene->compositor->input_streams, is_ctx); return GF_OK; } -static GF_Err IS_DetachStream(GF_BaseDecoder *plug, u16 ES_ID) +void gf_input_sensor_delete(GF_ObjectManager *odm) { - ISPriv *is = (ISPriv *)plug->privateStack; - is->ES_ID = 0; - return GF_OK; -} + /*get IS dec*/ + GF_InputSensorCtx *is_ctx = locate_is_ctx_for_odm(odm->parentscene, odm); + if (!is_ctx) return; -static GF_Err IS_GetCapabilities(GF_BaseDecoder *plug, GF_CodecCapability *capability) -{ - capability->cap.valueInt = 0; - return GF_OK; -} + gf_list_del(is_ctx->is_nodes); -static GF_Err IS_SetCapabilities(GF_BaseDecoder *plug, const GF_CodecCapability capability) -{ - return GF_OK; + while (gf_list_count(is_ctx->ddf)) { + GF_FieldInfo *fi = (GF_FieldInfo *)gf_list_get(is_ctx->ddf, 0); + gf_list_rem(is_ctx->ddf, 0); + gf_sg_vrml_field_pointer_del(fi->far_ptr, fi->fieldType); + gf_free(fi); + } + gf_list_del(is_ctx->ddf); + gf_list_del_item(odm->parentscene->compositor->input_streams, is_ctx); + gf_free(is_ctx); } -static GF_Err IS_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 AU_time, u32 mmlevel) + + +static GF_Err IS_ProcessData(GF_InputSensorCtx *is_ctx, const char *inBuffer, u32 inBufferLength) { u32 i, j, count; Double scene_time; GF_BitStream *bs; GF_FieldInfo *field; ISStack *st; - ISPriv *priv = (ISPriv *)plug->privateStack; GF_Err e = GF_OK; /*decode data frame except if local stringSensor*/ - bs = gf_bs_new(inBuffer, inBufferLength, GF_BITSTREAM_READ); + bs = gf_bs_new((u8 *)inBuffer, inBufferLength, GF_BITSTREAM_READ); i=0; - while ((field = (GF_FieldInfo *)gf_list_enum(priv->ddf, &i))) { + while ((field = (GF_FieldInfo *)gf_list_enum(is_ctx->ddf, &i))) { /*store present flag in eventIn for command skip - this is an ugly hack but it works since DDF don't have event types*/ field->eventType = gf_bs_read_int(bs, 1); /*parse val ourselves (we don't want to depend on bifs codec)*/ @@ -269,9 +327,14 @@ static GF_Err IS_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u32 in if ( ((SFString *)field->far_ptr)->buffer ) gf_free( ((SFString *)field->far_ptr)->buffer); ((SFString *)field->far_ptr)->buffer = (char*)gf_malloc(sizeof(char)*(length+1)); - memset(((SFString *)field->far_ptr)->buffer , 0, length+1); - for (j=0; jfar_ptr)->buffer[j] = gf_bs_read_int(bs, 8); + if ( ((SFString *)field->far_ptr)->buffer) { + for (j=0; jfar_ptr)->buffer[j] = gf_bs_read_int(bs, 8); + } + ((SFString *)field->far_ptr)->buffer[length] = 0; + } else { + gf_bs_del(bs); + return GF_OUT_OF_MEM; } } break; @@ -281,21 +344,21 @@ static GF_Err IS_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u32 in gf_bs_del(bs); /*special case for StringSensor in local mode: lookup for special chars*/ - if ((priv->type == IS_StringSensor) && priv->is_local) { + if (is_ctx->type == IS_StringSensor) { char tmp_utf8[5000]; const unsigned short *ptr; u32 len; - GF_FieldInfo *field1 = (GF_FieldInfo *)gf_list_get(priv->ddf, 0); - GF_FieldInfo *field2 = (GF_FieldInfo *)gf_list_get(priv->ddf, 1); + GF_FieldInfo *field1 = (GF_FieldInfo *)gf_list_get(is_ctx->ddf, 0); + GF_FieldInfo *field2 = (GF_FieldInfo *)gf_list_get(is_ctx->ddf, 1); SFString *inText = (SFString *) field1->far_ptr; SFString *outText = (SFString *) field2->far_ptr; field1->eventType = field2->eventType = 0; - priv->enteredText[priv->text_len] = (short) '\0'; + is_ctx->enteredText[is_ctx->text_len] = (short) '\0'; - len = (u32) gf_utf8_wcslen(priv->enteredText); - if (len && (priv->enteredText[len-1] == priv->termChar)) { - ptr = priv->enteredText; + len = (u32) gf_utf8_wcslen(is_ctx->enteredText); + if (len && (is_ctx->enteredText[len-1] == is_ctx->termChar)) { + ptr = is_ctx->enteredText; len = (u32) gf_utf8_wcstombs(tmp_utf8, 5000, &ptr); if (outText->buffer) gf_free(outText->buffer); outText->buffer = (char*)gf_malloc(sizeof(char) * (len)); @@ -303,23 +366,21 @@ static GF_Err IS_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u32 in outText->buffer[len-1] = 0; if (inText->buffer) gf_free(inText->buffer); inText->buffer = NULL; - priv->text_len = 0; + is_ctx->text_len = 0; field1->eventType = field2->eventType = 1; } else { - if (priv->delChar) { + if (is_ctx->delChar) { /*remove chars*/ - if ((len>1) && (priv->enteredText[len-1] == priv->delChar)) { - priv->enteredText[len-1] = (short) '\0'; + if ((len>1) && (is_ctx->enteredText[len-1] == is_ctx->delChar)) { + is_ctx->enteredText[len-1] = (short) '\0'; + len--; + is_ctx->enteredText[len-1] = (short) '\0'; len--; - if (len) { - priv->enteredText[len-1] = (short) '\0'; - len--; - } } } - priv->text_len = len; - ptr = priv->enteredText; + is_ctx->text_len = len; + ptr = is_ctx->enteredText; len = (u32) gf_utf8_wcstombs(tmp_utf8, 5000, &ptr); if (inText->buffer) gf_free(inText->buffer); inText->buffer = (char*)gf_malloc(sizeof(char) * (len+1)); @@ -329,142 +390,97 @@ static GF_Err IS_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u32 in } } - gf_term_lock_compositor(priv->scene->root_od->term, 1); + //we still need this since we have no clue when the device sensor is calling us + //TO CLEANUP (define PIDs and filters for input sensors and put them in the main thread ?) + gf_sc_lock(is_ctx->odm->parentscene->compositor, GF_TRUE); /*apply it*/ i=0; - while ((st = (ISStack*)gf_list_enum(priv->is_nodes, &i))) { + while ((st = (ISStack*)gf_list_enum(is_ctx->is_nodes, &i))) { assert(st->is); assert(st->mo); if (!st->is->enabled) continue; count = gf_list_count(st->is->buffer.commandList); - scene_time = gf_scene_get_time(priv->scene); + scene_time = gf_scene_get_time(is_ctx->odm->parentscene); for (j=0; jis->buffer.commandList, j); - GF_FieldInfo *field = (GF_FieldInfo *)gf_list_get(priv->ddf, j); GF_CommandField *info = (GF_CommandField *)gf_list_get(com->command_fields, 0); + field = (GF_FieldInfo *)gf_list_get(is_ctx->ddf, j); if (info && field && field->eventType) { gf_sg_vrml_field_copy(info->field_ptr, field->far_ptr, field->fieldType); - gf_sg_command_apply(priv->scene->graph, com, scene_time); + gf_sg_command_apply(is_ctx->odm->parentscene->graph, com, scene_time); } } } - gf_term_lock_compositor(priv->scene->root_od->term, 0); + gf_sc_lock(is_ctx->odm->parentscene->compositor, GF_FALSE); return e; } -void gf_isdec_del(GF_BaseDecoder *plug) -{ - ISPriv *priv = (ISPriv *)plug->privateStack; - gf_list_del(priv->is_nodes); - - while (gf_list_count(priv->ddf)) { - GF_FieldInfo *fi = (GF_FieldInfo *)gf_list_get(priv->ddf, 0); - gf_list_rem(priv->ddf, 0); - gf_sg_vrml_field_pointer_del(fi->far_ptr, fi->fieldType); - gf_free(fi); - } - gf_list_del(priv->ddf); - gf_free(priv); - gf_free(plug); -} - - -GF_BaseDecoder *gf_isdec_new(GF_ESD *esd, u32 PL) -{ - ISPriv *priv; - GF_SceneDecoder *tmp; - - tmp = (GF_SceneDecoder*) gf_malloc(sizeof(GF_SceneDecoder)); - if (!tmp) return NULL; - memset(tmp, 0, sizeof(GF_SceneDecoder)); - - priv = (ISPriv *) gf_malloc(sizeof(ISPriv)); - memset(priv, 0, sizeof(ISPriv)); - - priv->is_nodes = gf_list_new(); - priv->ddf = gf_list_new(); - - tmp->privateStack = priv; - - tmp->AttachStream = IS_AttachStream; - tmp->DetachStream = IS_DetachStream; - tmp->GetCapabilities = IS_GetCapabilities; - tmp->SetCapabilities = IS_SetCapabilities; - tmp->ProcessData = IS_ProcessData; - /*we don't use this...*/ - tmp->AttachScene = NULL; - - GF_REGISTER_MODULE_INTERFACE(tmp, GF_SCENE_DECODER_INTERFACE, "GPAC InputSensor Decoder", "gpac distribution") - return (GF_BaseDecoder *) tmp; -} /* input sensor node handling */ -static void IS_Unregister(GF_Node *node, ISStack *st) +static void InputSensorUnregister(GF_Node *node, ISStack *st) { - u32 i; GF_ObjectManager *odm; - ISPriv *is_dec; + GF_InputSensorCtx *is_ctx; gf_mo_unregister(node, st->mo); odm = st->mo->odm; if (!odm) return; - assert(odm->codec && (odm->codec->type == GF_STREAM_INTERACT)); + assert(odm->type == GF_STREAM_INTERACT); /*get IS dec*/ - is_dec = (ISPriv*)odm->codec->decio->privateStack; - for (i=0; iis_nodes); i++) { - ISStack *tmp = (ISStack *)gf_list_get(is_dec->is_nodes, i); - if (tmp == st) { - gf_list_rem(is_dec->is_nodes, i); - i--; - } - } + is_ctx = locate_is_ctx_for_odm(odm->parentscene, odm); + if (!is_ctx) return; + + gf_list_del_item(is_ctx->is_nodes, st); + + /*stop stream*/ - if (st->mo->num_open) gf_mo_stop(st->mo); + if (st->mo->num_open) gf_mo_stop(&st->mo); st->mo = NULL; if (st->registered) { st->registered = 0; - if (is_dec->io_dev && is_dec->io_dev->Stop) is_dec->io_dev->Stop(is_dec->io_dev); + if (is_ctx->io_dev && is_ctx->io_dev->Stop) is_ctx->io_dev->Stop(is_ctx->io_dev); } } -static void IS_Register(GF_Node *n) +static void InputSensorRegister(GF_Node *n) { GF_ObjectManager *odm; - ISPriv *is_dec; + GF_InputSensorCtx *is_ctx; u32 i; ISStack *st = (ISStack *)gf_node_get_private(n); odm = st->mo->odm; - if (!odm) return; + if (!odm || (odm->type != GF_STREAM_INTERACT)) return; - assert(odm->codec && (odm->codec->type == GF_STREAM_INTERACT)); + assert(odm->type == GF_STREAM_INTERACT); /*get IS dec*/ - is_dec = (ISPriv*)odm->codec->decio->privateStack; - if ( gf_list_find(is_dec->is_nodes, st) == -1 ) - gf_list_add(is_dec->is_nodes, st); + is_ctx = locate_is_ctx_for_odm(odm->parentscene, odm); + if (!is_ctx) return; + + if ( gf_list_find(is_ctx->is_nodes, st) == -1 ) + gf_list_add(is_ctx->is_nodes, st); /*start stream*/ gf_mo_play(st->mo, 0, -1, 0); - gf_term_unqueue_node_traverse(odm->term, n); + gf_sc_unqueue_node_traverse(is_ctx->odm->parentscene->compositor, n); /*we want at least one sensor enabled*/ i=0; - while ((st = gf_list_enum(is_dec->is_nodes, &i))) { + while ((st = gf_list_enum(is_ctx->is_nodes, &i))) { if (st->is->enabled) { st->registered = 1; - if (is_dec->io_dev && is_dec->io_dev->Start) is_dec->io_dev->Start(is_dec->io_dev); + if (is_ctx->io_dev && is_ctx->io_dev->Start) is_ctx->io_dev->Start(is_ctx->io_dev); break; } } - } static void TraverseInputSensor(GF_Node *node, void *rs, Bool is_destroy) @@ -474,15 +490,15 @@ static void TraverseInputSensor(GF_Node *node, void *rs, Bool is_destroy) if (is_destroy) { GF_Scene *scene; - if (st->registered) IS_Unregister(node, st); + if (st->registered) InputSensorUnregister(node, st); scene = (GF_Scene*)gf_sg_get_private(gf_node_get_graph(node)); - gf_term_unqueue_node_traverse(scene->root_od->term, node); + gf_sc_unqueue_node_traverse(scene->compositor, node); gf_free(st); - } else { + } else if (!st->registered) { /*get decoder object */ if (!st->mo) st->mo = gf_mo_register(node, &is->url, 0, 0); /*register with decoder*/ - if (st->mo && !st->registered) IS_Register(node); + if (st->mo) InputSensorRegister(node); } } @@ -498,7 +514,8 @@ void InitInputSensor(GF_Scene *scene, GF_Node *node) stack->is = (M_InputSensor *) node; gf_node_set_private(node, stack); gf_node_set_callback_function(node, TraverseInputSensor); - gf_term_queue_node_traverse(scene->root_od->term, node); + + gf_sc_queue_node_traverse(scene->compositor, node); } /*check only URL changes*/ @@ -510,15 +527,15 @@ void InputSensorModified(GF_Node *node) mo = gf_mo_register(node, &st->is->url, 0, 0); if ((mo!=st->mo) || !st->registered) { if (mo!=st->mo) { - if (st->mo) IS_Unregister(node, st); + if (st->mo) InputSensorUnregister(node, st); st->mo = mo; } if (st->is->enabled) - IS_Register(node); + InputSensorRegister(node); else return; } else if (!st->is->enabled) { - IS_Unregister(node, st); + InputSensorUnregister(node, st); return; } } @@ -528,21 +545,20 @@ void InputSensorModified(GF_Node *node) /* input sensor DDF generations (user interface) */ - -void gf_term_mouse_input(GF_Terminal *term, GF_EventMouse *event) +GF_EXPORT +void gf_sc_input_sensor_mouse_input(GF_Compositor *compositor, GF_EventMouse *event) { s32 X, Y; u32 left_but_down, middle_but_down, right_but_down; SFFloat wheel_pos; u32 i; - GF_Codec *cod; GF_BitStream *bs; - GF_SLHeader slh; - char *buf; + u8 *buf; u32 buf_size; Fixed bX, bY; + GF_InputSensorCtx *is_ctx; - if (!term || !gf_list_count(term->input_streams)) return; + if ( !gf_list_count(compositor->input_streams)) return; X = event->x; Y = event->y; @@ -569,9 +585,9 @@ void gf_term_mouse_input(GF_Terminal *term, GF_EventMouse *event) } /*get BIFS coordinates*/ - gf_sc_map_point(term->compositor, X, Y, &bX, &bY); - bX = gf_divfix(bX, term->compositor->scale_x); - bY = gf_divfix(bY, term->compositor->scale_y); + gf_sc_map_point(compositor, X, Y, &bX, &bY); + bX = gf_divfix(bX, compositor->scale_x); + bY = gf_divfix(bY, compositor->scale_y); bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); @@ -600,42 +616,33 @@ void gf_term_mouse_input(GF_Terminal *term, GF_EventMouse *event) gf_bs_get_content(bs, &buf, &buf_size); gf_bs_del(bs); - memset(&slh, 0, sizeof(GF_SLHeader)); - slh.accessUnitStartFlag = slh.accessUnitEndFlag = 1; - slh.compositionTimeStampFlag = 1; - /*note we could use an exact TS but it's not needed: since the input is generated locally - we want it to be decoded as soon as possible, thus using 0 emulates permanent seeking on - InputSensor stream, hence forces input frame resync*/ - slh.compositionTimeStamp = 0; - /*get all IS Mouse decoders and send frame*/ i=0; - while ((cod = (GF_Codec*)gf_list_enum(term->input_streams, &i))) { - ISPriv *is = (ISPriv *)cod->decio->privateStack; - if (is->type==IS_Mouse) { - GF_Channel *ch = (GF_Channel *)gf_list_get(cod->inChannels, 0); - gf_es_receive_sl_packet(ch->service, ch, buf, buf_size, &slh, GF_OK); + while ((is_ctx = gf_list_enum(compositor->input_streams, &i))) { + if (is_ctx->type==IS_Mouse) { + IS_ProcessData(is_ctx, buf, buf_size); } } gf_free(buf); } -Bool gf_term_keyboard_input(GF_Terminal *term, u32 key_code, u32 hw_code, Bool isKeyUp) +GF_EXPORT +Bool gf_sc_input_sensor_keyboard_input(GF_Compositor *compositor, u32 key_code, u32 hw_code, Bool isKeyUp) { u32 i; GF_BitStream *bs; GF_SLHeader slh; - char *buf; + u8 *buf; #ifndef GPAC_DISABLE_X3D X_KeySensor *n; #endif + GF_InputSensorCtx *is_ctx; u32 buf_size; u32 actionKey = 0; u32 shiftKeyDown, controlKeyDown, altKeyDown; - GF_Codec *cod; s32 keyPressed, keyReleased, actionKeyPressed, actionKeyReleased; - if (!term || (!gf_list_count(term->input_streams) && !gf_list_count(term->x3d_sensors)) ) return 0; + if (!gf_list_count(compositor->input_streams) && !gf_list_count(compositor->x3d_sensors)) return 0; memset(&slh, 0, sizeof(GF_SLHeader)); slh.accessUnitStartFlag = slh.accessUnitEndFlag = 1; @@ -764,20 +771,16 @@ Bool gf_term_keyboard_input(GF_Terminal *term, u32 key_code, u32 hw_code, Bool i /*get all IS keySensor decoders and send frame*/ i=0; - while ((cod = (GF_Codec*)gf_list_enum(term->input_streams, &i))) { - ISPriv *is = (ISPriv *)cod->decio->privateStack; - if (is->type==IS_KeySensor) { -// GF_Channel *ch = gf_list_get(cod->inChannels, 0); -// gf_es_receive_sl_packet(ch->service, ch, buf, buf_size, &slh, GF_OK); - - IS_ProcessData((GF_SceneDecoder*)cod->decio, buf, buf_size, 0, 0, 0); + while ((is_ctx = gf_list_enum(compositor->input_streams, &i))) { + if (is_ctx->type==IS_KeySensor) { + IS_ProcessData(is_ctx, buf, buf_size); } } gf_free(buf); #ifndef GPAC_DISABLE_X3D i=0; - while ((n = (X_KeySensor*)gf_list_enum(term->x3d_sensors, &i))) { + while ((n = (X_KeySensor*)gf_list_enum(compositor->x3d_sensors, &i))) { u16 tc[2]; u32 len; char szStr[10]; @@ -842,36 +845,27 @@ Bool gf_term_keyboard_input(GF_Terminal *term, u32 key_code, u32 hw_code, Bool i return 1; } -void gf_term_string_input(GF_Terminal *term, u32 character) +GF_EXPORT +void gf_sc_input_sensor_string_input(GF_Compositor *compositor, u32 character) { u32 i; GF_BitStream *bs; - GF_SLHeader slh; #ifndef GPAC_DISABLE_X3D X_StringSensor *n; #endif - GF_Codec *cod; - char *buf; + GF_InputSensorCtx *is_ctx; + u8 *buf; u32 buf_size; - if (!character || !term) return; - if (!gf_list_count(term->input_streams) && !gf_list_count(term->x3d_sensors)) return; - - memset(&slh, 0, sizeof(GF_SLHeader)); - slh.accessUnitStartFlag = slh.accessUnitEndFlag = 1; - slh.compositionTimeStampFlag = 1; - /*cf above*/ - slh.compositionTimeStamp = 0; + if (!character) return; + if (!gf_list_count(compositor->input_streams) && !gf_list_count(compositor->x3d_sensors)) return; /*get all IS StringSensor decoders and send frame*/ i=0; - while ((cod = (GF_Codec*)gf_list_enum(term->input_streams, &i))) { - ISPriv *is = (ISPriv *)cod->decio->privateStack; - if (is->type==IS_StringSensor) { - -// GF_Channel *ch = (GF_Channel *)gf_list_get(cod->inChannels, 0); - is->enteredText[is->text_len] = character; - is->text_len += 1; + while ((is_ctx = gf_list_enum(compositor->input_streams, &i))) { + if (is_ctx->type==IS_StringSensor) { + is_ctx->enteredText[is_ctx->text_len] = character; + is_ctx->text_len += 1; /*write empty DDF*/ bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); @@ -881,8 +875,7 @@ void gf_term_string_input(GF_Terminal *term, u32 character) gf_bs_get_content(bs, &buf, &buf_size); gf_bs_del(bs); -// gf_es_receive_sl_packet(ch->service, ch, buf, buf_size, &slh, GF_OK); - IS_ProcessData((GF_SceneDecoder*)cod->decio, buf, buf_size, 0, 0, 0); + IS_ProcessData(is_ctx, buf, buf_size); gf_free(buf); } @@ -892,7 +885,7 @@ void gf_term_string_input(GF_Terminal *term, u32 character) #ifndef GPAC_DISABLE_X3D /*get all X3D StringSensors*/ i=0; - while ((n = (X_StringSensor*)gf_list_enum(term->x3d_sensors, &i))) { + while ((n = (X_StringSensor*)gf_list_enum(compositor->x3d_sensors, &i))) { StringSensorStack *st; char szStr[5000]; const unsigned short *ptr; @@ -940,22 +933,25 @@ void gf_term_string_input(GF_Terminal *term, u32 character) void DestroyKeySensor(GF_Node *node, void *rs, Bool is_destroy) { if (is_destroy) { - GF_Terminal *term = (GF_Terminal *) gf_node_get_private(node); - gf_list_del_item(term->x3d_sensors, node); + GF_Compositor *compositor = (GF_Compositor *) gf_node_get_private(node); + gf_list_del_item(compositor->x3d_sensors, node); } } void InitKeySensor(GF_Scene *scene, GF_Node *node) { - gf_node_set_private(node, scene->root_od->term); + gf_node_set_private(node, scene->compositor); gf_node_set_callback_function(node, DestroyKeySensor); - gf_list_add(scene->root_od->term->x3d_sensors, node); + if (!scene->compositor->x3d_sensors) + scene->compositor->x3d_sensors = gf_list_new(); + + gf_list_add(scene->compositor->x3d_sensors, node); } void DestroyStringSensor(GF_Node *node, void *rs, Bool is_destroy) { if (is_destroy) { StringSensorStack *st = (StringSensorStack *) gf_node_get_private(node); - gf_list_del_item(st->term->x3d_sensors, node); + gf_list_del_item(st->compositor->x3d_sensors, node); gf_free(st); } } @@ -967,23 +963,30 @@ void InitStringSensor(GF_Scene *scene, GF_Node *node) GF_LOG(GF_LOG_ERROR, GF_LOG_INTERACT, ("[Terminal] Failed to allocate string sensor stack\n")); return; } - st->term = scene->root_od->term; + st->compositor = scene->compositor; gf_node_set_private(node, st); gf_node_set_callback_function(node, DestroyStringSensor); - gf_list_add(scene->root_od->term->x3d_sensors, node); + + if (!scene->compositor->x3d_sensors) + scene->compositor->x3d_sensors = gf_list_new(); + + gf_list_add(scene->compositor->x3d_sensors, node); } #endif /*GPAC_DISABLE_X3D*/ #else -void gf_term_mouse_input(GF_Terminal *term, GF_EventMouse *event) +GF_EXPORT +void gf_sc_input_sensor_mouse_input(GF_Compositor *compositor, GF_EventMouse *event) { } -Bool gf_term_keyboard_input(GF_Terminal *term, u32 key_code, u32 hw_code, Bool isKeyUp) +GF_EXPORT +Bool gf_sc_input_sensor_keyboard_input(GF_Compositor *compositor, u32 key_code, u32 hw_code, Bool isKeyUp) { return GF_TRUE; } -void gf_term_string_input(GF_Terminal *term, u32 character) +GF_EXPORT +void gf_sc_input_sensor_string_input(GF_Compositor *compositor, u32 character) { } diff --git a/src/compositor/mpeg4_layer_3d.c b/src/compositor/mpeg4_layer_3d.c index 923a886..2d493a2 100644 --- a/src/compositor/mpeg4_layer_3d.c +++ b/src/compositor/mpeg4_layer_3d.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / Scene Compositor sub-project @@ -131,157 +131,6 @@ static void l3d_CheckBindables(GF_Node *n, GF_TraverseState *tr_state, Bool forc } -u32 layer3d_setup_offscreen(GF_Node *node, Layer3DStack *st, GF_TraverseState *tr_state, Fixed width, Fixed height) -{ - GF_STENCIL stencil; - u32 new_pixel_format, w, h; - GF_Compositor *compositor = (GF_Compositor *)st->visual->compositor; - - if (st->unsupported) return 0; - -#ifndef GPAC_USE_TINYGL - /*no support for offscreen rendering*/ - if (!(compositor->video_out->hw_caps & GF_VIDEO_HW_OPENGL_OFFSCREEN)) { - st->unsupported = 1; - return 0; - } -#endif - - /* - if (tr_state->visual->compositor->recompute_ar) { - gf_node_dirty_set(node, 0, 0); - return 0; - } - */ - - new_pixel_format = GF_PIXEL_RGBA; -#ifndef GPAC_USE_TINYGL -// if (!compositor_background_transparent(gf_list_get(tr_state->backgrounds, 0)) ) -// new_pixel_format = GF_PIXEL_RGB_24; - - /*in OpenGL_ES, only RGBA can be safelly used with glReadPixels*/ -#ifdef GPAC_USE_GLES1X - new_pixel_format = GF_PIXEL_RGBA; -#else - /*no support for alpha in offscreen rendering*/ - if (!(compositor->video_out->hw_caps & GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA)) - new_pixel_format = GF_PIXEL_RGB_24; -#endif - -#endif - - /*FIXME - we assume RGB+Depth+bitshape, we should check with the video out module*/ -#if defined(GF_SR_USE_DEPTH) && !defined(GPAC_DISABLE_3D) - if (st->visual->type_3d && (compositor->video_out->hw_caps & GF_VIDEO_HW_HAS_DEPTH) ) new_pixel_format = GF_PIXEL_RGBDS; -#endif - - w = (u32) FIX2INT(gf_ceil(width)); - h = (u32) FIX2INT(gf_ceil(height)); - - /*1- some implementation of glReadPixel crash if w||h are not multiple of 4*/ - /*2- some implementation of glReadPixel don't behave properly here when texture is not a power of 2*/ - w = gf_get_next_pow2(w); - if (w>1024) w = 1024; - h = gf_get_next_pow2(h); - if (h>1024) h = 1024; - - - if (!w || !h) return 0; - - if (st->txh.tx_io - && (new_pixel_format == st->txh.pixelformat) - && (w == st->txh.width) - && (h == st->txh.height) - && (compositor->offscreen_width >= w) - && (compositor->offscreen_height >= h) - ) - return 2; - - if (st->txh.tx_io) { -#ifdef GPAC_USE_TINYGL - if (st->tgl_ctx) ostgl_delete_context(st->tgl_ctx); -#endif - gf_sc_texture_release(&st->txh); - if (st->txh.data) gf_free(st->txh.data); - st->txh.data = NULL; - } - - st->vp = gf_rect_center(INT2FIX(w), INT2FIX(h) ); - - st->txh.width = w; - st->txh.height = h; - - gf_sc_texture_allocate(&st->txh); - st->txh.pixelformat = new_pixel_format; - - if (new_pixel_format==GF_PIXEL_RGBA) { - st->txh.stride = w * 4; - st->txh.transparent = 1; - } - else if (new_pixel_format==GF_PIXEL_RGBDS) { - st->txh.stride = w * 4; - st->txh.transparent = 1; - } - else { - st->txh.stride = w * 3; - st->txh.transparent = 0; - } - - st->visual->width = w; - st->visual->height = h; - - - stencil = compositor->rasterizer->stencil_new(compositor->rasterizer, GF_STENCIL_TEXTURE); - -#ifndef GPAC_USE_TINYGL - /*create an offscreen window for OpenGL rendering*/ - if ((compositor->offscreen_width < w) || (compositor->offscreen_height < h)) { - GF_Err e; - GF_Event evt; - compositor->offscreen_width = MAX(compositor->offscreen_width, w); - compositor->offscreen_height = MAX(compositor->offscreen_height, h); - - evt.type = GF_EVENT_VIDEO_SETUP; - evt.setup.width = tr_state->visual->compositor->offscreen_width; - evt.setup.height = tr_state->visual->compositor->offscreen_height; - evt.setup.back_buffer = 0; - evt.setup.opengl_mode = 2; - e = compositor->video_out->ProcessEvent(compositor->video_out, &evt); - if (e) { - gf_sc_texture_release(&st->txh); - st->unsupported = 1; - return 0; - } - visual_3d_reset_graphics(compositor->visual); - /*reload openGL ext*/ - gf_sc_load_opengl_extensions(compositor, 1); - /*load openGL shaders*/ - visual_3d_init_shaders(compositor->visual); - } -#endif - st->txh.data = (char*)gf_malloc(sizeof(unsigned char) * st->txh.stride * st->txh.height); - memset(st->txh.data, 0, sizeof(unsigned char) * st->txh.stride * st->txh.height); - - /*set stencil texture - we don't check error as an image could not be supported by the rasterizer - but still supported by the blitter (case of RGBD/RGBDS)*/ - compositor->rasterizer->stencil_set_texture(stencil, st->txh.data, st->txh.width, st->txh.height, st->txh.stride, st->txh.pixelformat, st->txh.pixelformat, 0); - -#ifdef GPAC_USE_TINYGL - /*create TinyGL offscreen context*/ - st->tgl_ctx = ostgl_create_context(st->txh.width, st->txh.height, st->txh.transparent ? 32 : 24, &st->txh.data, 1); - - st->scale_x = st->scale_y = FIX_ONE; -#else - st->scale_x = INT2FIX(w) / tr_state->visual->compositor->offscreen_width; - st->scale_y = INT2FIX(h) / tr_state->visual->compositor->offscreen_height; -#endif - - gf_sc_texture_set_stencil(&st->txh, stencil); - drawable_reset_path(st->drawable); - gf_path_add_rect_center(st->drawable->path, 0, 0, st->clip.width, st->clip.height); - return 1; -} - static void layer3d_draw_2d(GF_Node *node, GF_TraverseState *tr_state) { DrawableContext *ctx = tr_state->ctx; @@ -338,7 +187,7 @@ static void layer3d_setup_clip(Layer3DStack *st, GF_TraverseState *tr_state, Boo static void TraverseLayer3D(GF_Node *node, void *rs, Bool is_destroy) { - Bool prev_layer, changed = 0; + Bool prev_layer;//, changed; GF_List *oldb, *oldv, *oldf, *oldn; GF_Rect rc; u32 cur_lights; @@ -377,7 +226,7 @@ static void TraverseLayer3D(GF_Node *node, void *rs, Bool is_destroy) if (l->size.y>=0) st->clip.height = l->size.y; st->clip = gf_rect_center(st->clip.width, st->clip.height); - changed = 1; +// changed = 1; } transform = &tr_state->transform; @@ -476,29 +325,13 @@ static void TraverseLayer3D(GF_Node *node, void *rs, Bool is_destroy) if ((tr_state->traversing_mode==TRAVERSE_SORT) || (tr_state->traversing_mode==TRAVERSE_DRAW_2D)) { u32 trav_mode = tr_state->traversing_mode; - if (gf_node_dirty_get(node)) changed = 1; +// if (gf_node_dirty_get(node)) changed = 1; gf_node_dirty_clear(node, GF_SG_NODE_DIRTY|GF_SG_VRML_BINDABLE_DIRTY); - /*!! we were in a 2D mode, setup associated texture !!*/ + /*!! we were in a 2D mode without hybridGL, not supported !*/ if (!prev_cam) { - switch (layer3d_setup_offscreen(node, st, tr_state, rc.width, rc.height)) { - case 0: - goto l3d_exit; - case 2: - if (!changed && !(st->visual->camera.flags & CAM_IS_DIRTY) && !st->visual->camera.anim_len) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Layer3D] No changes found , skipping 3D draw\n")); - goto layer3d_unchanged_2d; - } - default: - break; - } -#ifdef GPAC_USE_TINYGL - if (st->tgl_ctx) ostgl_make_current(st->tgl_ctx, 0); -#endif - - rc = st->vp; - /*setup GL*/ - visual_3d_setup(tr_state->visual); + st->unsupported = GF_TRUE; + goto l3d_exit; } /*note that we don't backup the state as a layer3D cannot be declared in a layer3D*/ tr_state->layer3d = node; @@ -506,7 +339,7 @@ static void TraverseLayer3D(GF_Node *node, void *rs, Bool is_destroy) GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Layer3D] Redrawing\n")); - layer3d_setup_clip(st, tr_state, prev_cam ? 1 : 0, rc); + layer3d_setup_clip(st, tr_state, 1, rc); //this only happens in hybridGL mode if (trav_mode==TRAVERSE_DRAW_2D) { @@ -522,7 +355,7 @@ static void TraverseLayer3D(GF_Node *node, void *rs, Bool is_destroy) /*this will init projection. Note that we're binding the viewpoint in the current pixelMetrics context even if the viewpoint was declared in an inline below if no previous camera, we're using offscreen rendering, force clear */ - visual_3d_init_draw(tr_state, prev_cam ? 1 : 2); + visual_3d_init_draw(tr_state, 1); visual_3d_check_collisions(tr_state, NULL, l->children); tr_state->traversing_mode = TRAVERSE_SORT; @@ -548,6 +381,7 @@ static void TraverseLayer3D(GF_Node *node, void *rs, Bool is_destroy) tr_state->visual->type_3d = old_type_3d; tr_state->layer3d = NULL; +#if 0 /*!! we were in a 2D mode, create drawable context!!*/ if (!prev_cam ) { DrawableContext *ctx; @@ -559,13 +393,9 @@ static void TraverseLayer3D(GF_Node *node, void *rs, Bool is_destroy) gf_get_tinygl_depth(&st->txh); #endif - if (tr_state->visual->compositor->rasterizer->stencil_texture_modified) - tr_state->visual->compositor->rasterizer->stencil_texture_modified(gf_sc_texture_get_stencil(&st->txh) ); gf_sc_texture_set_stencil(&st->txh, gf_sc_texture_get_stencil(&st->txh) ); changed = 1; -layer3d_unchanged_2d: - /*restore visual*/ tr_state->visual = old_visual; tr_state->appear = NULL; @@ -579,6 +409,8 @@ layer3d_unchanged_2d: if (st->txh.transparent) ctx->flags |= CTX_IS_TRANSPARENT; drawable_finalize_sort(ctx, tr_state, NULL); } +#endif + } /*check picking - we must fall in our 2D clipper except when mvt is grabbed on layer*/ else if (!gf_node_dirty_get(node) && (tr_state->traversing_mode==TRAVERSE_PICK)) { @@ -709,12 +541,17 @@ l3d_exit: void compositor_init_layer3d(GF_Compositor *compositor, GF_Node *node) { Layer3DStack *stack; + + if (!gf_sc_check_gl_support(compositor)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] Driver disabled, cannot render Layer 3D\n")); + return; + } + GF_SAFEALLOC(stack, Layer3DStack); if (!stack) { GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate layer 3d stack\n")); return; } - stack->visual = visual_new(compositor); stack->visual->type_3d = 2; stack->visual->camera.is_3D = 1; diff --git a/src/compositor/mpeg4_layout.c b/src/compositor/mpeg4_layout.c index 6c80406..e1209e4 100644 --- a/src/compositor/mpeg4_layout.c +++ b/src/compositor/mpeg4_layout.c @@ -66,8 +66,8 @@ static LineInfo *new_line_info(LayoutStack *st) { LineInfo *li; GF_SAFEALLOC(li, LineInfo); - - gf_list_add(st->lines, li); + if (li) + gf_list_add(st->lines, li); return li; } @@ -599,7 +599,7 @@ static void layout_scroll(GF_TraverseState *tr_state, LayoutStack *st, M_Layout } /*draw next frame*/ if (!stop_anim) { - tr_state->visual->compositor->force_next_frame_redraw = 1; + tr_state->visual->compositor->force_next_frame_redraw = GF_TRUE; return; } @@ -747,8 +747,12 @@ layout_exit: static Bool OnLayout(GF_SensorHandler *sh, Bool is_over, Bool is_cancel, GF_Event *ev, GF_Compositor *compositor) { - Bool vertical = ((M_Layout *)sh->sensor)->scrollVertical; - LayoutStack *st = (LayoutStack *) gf_node_get_private(sh->sensor); + Bool vertical; + LayoutStack *st; + if (!sh || !ev) return GF_FALSE; + + st = (LayoutStack *) gf_node_get_private(sh->sensor); + vertical = ((M_Layout *)sh->sensor)->scrollVertical; if (!is_over) { st->is_scrolling = 0; @@ -800,7 +804,7 @@ static Bool OnLayout(GF_SensorHandler *sh, Bool is_over, Bool is_cancel, GF_Even static Bool layout_is_enabled(GF_Node *node) { M_Layout *l = (M_Layout *)node; - if (l->scrollRate != 0) return 0; + if (node && (l->scrollRate != 0)) return 0; return 1; } @@ -820,6 +824,14 @@ void compositor_init_layout(GF_Compositor *compositor, GF_Node *node) stack->hdl.sensor = node; stack->hdl.IsEnabled = layout_is_enabled; stack->hdl.OnUserEvent = OnLayout; +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + OnLayout(NULL, GF_FALSE, GF_FALSE, NULL, compositor); + layout_is_enabled(node); + compositor_mpeg4_layout_get_sensor_handler(node); + } +#endif + } void compositor_layout_modified(GF_Compositor *compositor, GF_Node *node) @@ -839,7 +851,9 @@ void compositor_layout_modified(GF_Compositor *compositor, GF_Node *node) GF_SensorHandler *compositor_mpeg4_layout_get_sensor_handler(GF_Node *node) { - LayoutStack *st = (LayoutStack *) gf_node_get_private(node); + LayoutStack *st; + if (!node) return NULL; + st = (LayoutStack *) gf_node_get_private(node); return &st->hdl; } diff --git a/src/terminal/media_control.c b/src/compositor/mpeg4_mediacontrol.c similarity index 93% rename from src/terminal/media_control.c rename to src/compositor/mpeg4_mediacontrol.c index aa64ffe..0ff4eaf 100644 --- a/src/terminal/media_control.c +++ b/src/compositor/mpeg4_mediacontrol.c @@ -4,10 +4,10 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2017 * All rights reserved * - * This file is part of GPAC / Media terminal sub-project + * This file is part of GPAC / Scene Compositor sub-project * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -26,7 +26,6 @@ */ -#include "media_control.h" #include #include @@ -106,7 +105,7 @@ void mediacontrol_restart(GF_ObjectManager *odm) i=0; while ((ctrl_od = (GF_ObjectManager*)gf_list_enum(to_restart, &i))) { //we want to make sure restart is clled right away with the current media control settings - gf_odm_start(ctrl_od, 2); + gf_odm_start(ctrl_od); } gf_list_del(to_restart); } @@ -280,9 +279,9 @@ void MC_GetRange(MediaControlStack *ctrl, Double *start_range, Double *end_range { u32 i; Double duration; - GF_Segment *last_seg, *prev_seg; if (gf_list_count(ctrl->seg)) { + GF_Segment *last_seg, *prev_seg; GF_Segment *desc = (GF_Segment *)gf_list_get(ctrl->seg, ctrl->current_seg); if (!desc) { *start_range = 0; @@ -323,15 +322,11 @@ void MC_GetRange(MediaControlStack *ctrl, Double *start_range, Double *end_range void RenderMediaControl(GF_Node *node, void *rs, Bool is_destroy) { Bool shall_restart, need_restart; - GF_MediaObject *prev; GF_ObjectManager *odm; GF_TraverseState *tr_state = (GF_TraverseState *)rs; MediaControlStack *stack =(MediaControlStack *) gf_node_get_private(node); if (is_destroy) { - GF_ObjectManager *odm; - MediaControlStack *stack = (MediaControlStack *) gf_node_get_private(node); - /*reset ODM using this control*/ if (stack->stream) { if (stack->stream->odm) { @@ -358,6 +353,7 @@ void RenderMediaControl(GF_Node *node, void *rs, Bool is_destroy) /*check url target*/ if (stack->stream) { if (MC_URLChanged(&stack->url, &stack->control->url)) { + GF_MediaObject *prev; gf_sg_vrml_mf_reset(&stack->url, GF_SG_VRML_MFURL); prev = stack->stream; @@ -369,7 +365,7 @@ void RenderMediaControl(GF_Node *node, void *rs, Bool is_destroy) if (!stack->stream->odm) return; /*MediaControl on inline: if dynamic scene, make sure it is connected before attaching...*/ if (stack->stream->odm->subscene) { - if (stack->stream->odm->subscene->is_dynamic_scene && !stack->stream->odm->subscene->dyn_ck) return; + if (stack->stream->odm->subscene->is_dynamic_scene && !stack->stream->odm->ck) return; } gf_sg_vrml_field_copy(&stack->url, &stack->control->url, GF_SG_VRML_MFURL); @@ -411,7 +407,7 @@ void RenderMediaControl(GF_Node *node, void *rs, Bool is_destroy) } else { stack->stream = gf_scene_get_media_object(stack->parent, &stack->control->url, GF_MEDIA_OBJECT_UNDEF, 0); if (!stack->stream || !stack->stream->odm) { - if (stack->control->url.count) gf_term_invalidate_compositor(stack->parent->root_od->term); + if (stack->control->url.count) gf_sc_invalidate(stack->parent->compositor, NULL); stack->stream = NULL; stack->changed = 0; return; @@ -422,7 +418,7 @@ void RenderMediaControl(GF_Node *node, void *rs, Bool is_destroy) stack->stream = NULL; if (stack->control->url.count) { stack->is_init = 0; - gf_term_invalidate_compositor(stack->parent->root_od->term); + gf_sc_invalidate(stack->parent->compositor, NULL); } return; } @@ -566,41 +562,35 @@ void MC_Modified(GF_Node *node) gf_node_dirty_set( gf_sg_get_root_node(gf_node_get_graph(node)), 0, 1); /*invalidate scene, we recompute MC state in render*/ - gf_term_invalidate_compositor(stack->parent->root_od->term); + gf_sc_invalidate(stack->parent->compositor, NULL); } void gf_odm_set_mediacontrol(GF_ObjectManager *odm, MediaControlStack *ctrl) { - u32 i; - GF_Channel *ch; - /*keep track of it*/ if (ctrl && (gf_list_find(odm->mc_stack, ctrl) < 0)) gf_list_add(odm->mc_stack, ctrl); if (ctrl && !ctrl->control->enabled) return; if (odm->subscene && odm->subscene->is_dynamic_scene) { - if (odm->subscene->dyn_ck) { + if (odm->ck) { /*deactivate current control*/ - if (ctrl && odm->subscene->dyn_ck->mc) { - odm->subscene->dyn_ck->mc->control->enabled = 0; - gf_node_event_out((GF_Node *)odm->subscene->dyn_ck->mc->control, 7/*"enabled"*/); + if (ctrl && odm->ck->mc) { + odm->ck->mc->control->enabled = 0; + gf_node_event_out((GF_Node *)odm->ck->mc->control, 7/*"enabled"*/); } - odm->subscene->dyn_ck->mc = ctrl; + odm->ck->mc = ctrl; } } else { /*for each clock in the controled OD*/ - i=0; - while ((ch = (GF_Channel*)gf_list_enum(odm->channels, &i))) { - if (ch->clock->mc != ctrl) { - /*deactivate current control*/ - if (ctrl && ch->clock->mc) { - ch->clock->mc->control->enabled = 0; - gf_node_event_out((GF_Node *)ch->clock->mc->control, 7/*"enabled"*/); - } - /*and attach this control to the clock*/ - ch->clock->mc = ctrl; + if (odm->ck && (odm->ck->mc != ctrl)) { + /*deactivate current control*/ + if (ctrl && odm->ck->mc) { + odm->ck->mc->control->enabled = 0; + gf_node_event_out((GF_Node *)odm->ck->mc->control, 7/*"enabled"*/); } + /*and attach this control to the clock*/ + odm->ck->mc = ctrl; } } /*store active control on media*/ @@ -665,7 +655,7 @@ Bool gf_odm_check_segment_switch(GF_ObjectManager *odm) if (ctrl->current_seg>=count) return 0; /*synth media, trigger if end of segment run-time*/ - if (!odm->codec || ((odm->codec->type!=GF_STREAM_VISUAL) && (odm->codec->type!=GF_STREAM_AUDIO))) { + if (!odm->type || ((odm->type!=GF_STREAM_VISUAL) && (odm->type!=GF_STREAM_AUDIO))) { GF_Clock *ck = gf_odm_get_media_clock(odm); u32 now = gf_clock_time(ck); u64 dur = odm->subscene ? odm->subscene->duration : odm->duration; @@ -673,7 +663,7 @@ Bool gf_odm_check_segment_switch(GF_ObjectManager *odm) if (odm->subscene && odm->subscene->needs_restart) return 0; if (cur) dur = (u32) ((cur->Duration+cur->startTime)*1000); //if next frame is after current segment trigger switch now - if (now + odm->term->compositor->frame_duration < dur) + if (now + odm->parentscene->compositor->frame_duration < dur) return 0; } else { /*FIXME - for natural media with scalability, we should only process when all streams of the object are done*/ diff --git a/src/terminal/media_sensor.c b/src/compositor/mpeg4_mediasensor.c similarity index 79% rename from src/terminal/media_sensor.c rename to src/compositor/mpeg4_mediasensor.c index 93c9013..3aad8f4 100644 --- a/src/terminal/media_sensor.c +++ b/src/compositor/mpeg4_mediasensor.c @@ -2,10 +2,10 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2017 * All rights reserved * - * This file is part of GPAC / Media terminal sub-project + * This file is part of GPAC / Scene Compositor sub-project * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -24,7 +24,6 @@ */ -#include "media_control.h" #include #include @@ -50,16 +49,21 @@ void RenderMediaSensor(GF_Node *node, void *rs, Bool is_destroy) //we need to disable culling otherwise we may never be called back again ... tr_state->disable_cull = 1; - if (!st->stream) st->stream = gf_mo_register(node, &st->sensor->url, 0, 0); - if (!st->stream || !st->stream->odm) return; - if (!st->is_init) { + if (!st->stream) { + st->stream = gf_mo_register(node, &st->sensor->url, 0, 0); + if (!st->stream) return; + } + if (!st->stream->odm) return; + gf_list_add(st->stream->odm->ms_stack, st); gf_odm_init_segments(st->stream->odm, st->seg, &st->sensor->url); st->is_init = 1; st->active_seg = 0; - } + //st->stream may be NULL when destroying the ODM + if (!st->stream || !st->stream->odm) return; + /*media sensor bound to natural media (audio, video) is updated when fetching the stream data for rendering.*/ @@ -67,27 +71,28 @@ void RenderMediaSensor(GF_Node *node, void *rs, Bool is_destroy) /*check inline scenes - if the scene is set to restart DON'T MODIFY SENSOR: since we need a 2 render passes to restart inline, scene is considered as not running*/ if (st->stream->odm->subscene && !st->stream->odm->subscene->needs_restart) { - if (st->stream->odm->subscene->scene_codec) ck = st->stream->odm->subscene->scene_codec->ck; + if (! st->stream->odm->subscene->is_dynamic_scene) ck = st->stream->odm->subscene->root_od->ck; /*dynamic scene*/ - else ck = st->stream->odm->subscene->dyn_ck; + else ck = st->stream->odm->ck; + if (st->stream->odm->subscene->is_dynamic_scene) do_update_clock = 0; } - /*check anim streams*/ - else if (st->stream->odm->codec && (st->stream->odm->codec->type==GF_STREAM_SCENE)) ck = st->stream->odm->codec->ck; + /*check anim or OCR streams*/ + else if (st->stream->odm->type==GF_STREAM_SCENE) ck = st->stream->odm->ck; /*check OCR streams*/ - else if (st->stream->odm->ocr_codec) ck = st->stream->odm->ocr_codec->ck; + else if (st->stream->odm->ck) ck = st->stream->odm->ck; if (ck && ck->clock_init ) { if (do_update_clock) st->stream->odm->media_current_time = gf_clock_media_time(ck); - mediasensor_update_timing(st->stream->odm, 0); + mediasensor_update_timing(st->stream->odm, GF_FALSE); } //if main addon is VoD , fire a timeshift update else if (st->stream->odm->subscene && st->stream->odm->subscene->sys_clock_at_main_activation) { GF_Event evt; memset(&evt, 0, sizeof(evt)); evt.type = GF_EVENT_TIMESHIFT_UPDATE; - gf_term_send_event(st->stream->odm->term, &evt); + gf_sc_send_event(st->stream->odm->parentscene->compositor, &evt); } } @@ -129,25 +134,7 @@ void MS_Modified(GF_Node *node) } st->stream = NULL; st->is_init = 0; - gf_term_invalidate_compositor(st->parent->root_od->term); -} - - -static void media_sensor_activate_segment(MediaSensorStack *media_sens, GF_Segment *desc) -{ - media_sens->sensor->isActive = 1; - gf_node_event_out((GF_Node *) media_sens->sensor, 4/*"isActive"*/); - /*set info*/ - gf_sg_vrml_mf_reset(& media_sens->sensor->info, GF_SG_VRML_MFSTRING); - gf_sg_vrml_mf_alloc(& media_sens->sensor->info, GF_SG_VRML_MFSTRING, 1); - media_sens->sensor->info.vals[0] = desc->SegmentName ? gf_strdup(desc->SegmentName) : NULL; - gf_node_event_out((GF_Node *) media_sens->sensor, 5/*"info"*/); - /*set duration*/ - media_sens->sensor->mediaDuration = desc->Duration; - gf_node_event_out((GF_Node *) media_sens->sensor, 3/*"mediaDuration"*/); - /*set seg start time*/ - media_sens->sensor->streamObjectStartTime = desc->startTime; - gf_node_event_out((GF_Node *) media_sens->sensor, 2/*"streamObjectStartTime"*/); + gf_sc_invalidate(st->parent->compositor, NULL); } void mediasensor_update_timing(GF_ObjectManager *odm, Bool is_eos) @@ -160,8 +147,8 @@ void mediasensor_update_timing(GF_ObjectManager *odm, Bool is_eos) time = odm->media_current_time / 1000.0; //dirty hack to get timing of frame when very late (openhevc debug) - if (odm->subscene && odm->subscene->dyn_ck && odm->subscene->dyn_ck->last_TS_rendered) - time = odm->subscene->dyn_ck->last_TS_rendered / 1000.0; + if (odm->subscene && odm->ck && odm->ck->last_ts_rendered) + time = odm->ck->last_ts_rendered / 1000.0; for (j=0; jms_stack, j); @@ -182,7 +169,7 @@ void mediasensor_update_timing(GF_ObjectManager *odm, Bool is_eos) media_sens->sensor->isActive = 0; gf_node_event_out((GF_Node *) media_sens->sensor, 4/*"isActive"*/); - GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[ODM%d] Deactivating media sensor\n", odm->OD->objectDescriptorID)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[ODM%d] Deactivating media sensor\n", odm->ID)); } continue; } @@ -233,7 +220,7 @@ void mediasensor_update_timing(GF_ObjectManager *odm, Bool is_eos) media_sens->sensor->isActive = 0; gf_node_event_out((GF_Node *) media_sens->sensor, 4/*"isActive"*/); - GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[ODM%d] Deactivating media sensor at time %g - segment %s\n", odm->OD->objectDescriptorID, time, desc->SegmentName)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[ODM%d] Deactivating media sensor at time %g - segment %s\n", odm->ID, time, desc->SegmentName)); } continue; } @@ -248,9 +235,23 @@ void mediasensor_update_timing(GF_ObjectManager *odm, Bool is_eos) } if (!media_sens->sensor->isActive) { - media_sensor_activate_segment(media_sens, desc); - GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[ODM%d] Activating media sensor time %g - segment %s\n", odm->OD->objectDescriptorID, time, desc->SegmentName)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[ODM%d] Activating media sensor time %g - segment %s\n", odm->ID, time, desc->SegmentName)); + + media_sens->sensor->isActive = 1; + gf_node_event_out((GF_Node *) media_sens->sensor, 4/*"isActive"*/); + /*set info*/ + gf_sg_vrml_mf_reset(& media_sens->sensor->info, GF_SG_VRML_MFSTRING); + gf_sg_vrml_mf_alloc(& media_sens->sensor->info, GF_SG_VRML_MFSTRING, 1); + media_sens->sensor->info.vals[0] = desc->SegmentName ? gf_strdup(desc->SegmentName) : NULL; + gf_node_event_out((GF_Node *) media_sens->sensor, 5/*"info"*/); + /*set duration*/ + media_sens->sensor->mediaDuration = desc->Duration; + gf_node_event_out((GF_Node *) media_sens->sensor, 3/*"mediaDuration"*/); + /*set seg start time*/ + media_sens->sensor->streamObjectStartTime = desc->startTime; + gf_node_event_out((GF_Node *) media_sens->sensor, 2/*"streamObjectStartTime"*/); + } /*set media time - relative to segment start time*/ @@ -267,7 +268,7 @@ void mediasensor_update_timing(GF_ObjectManager *odm, Bool is_eos) media_sens->sensor->isActive = 0; gf_node_event_out((GF_Node *) media_sens->sensor, 4/*"isActive"*/); media_sens->active_seg = count; - GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[ODM%d] Deactivating media sensor at time %g: no more segments\n", odm->OD->objectDescriptorID, time)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[ODM%d] Deactivating media sensor at time %g: no more segments\n", odm->ID, time)); } } } @@ -279,7 +280,7 @@ void MS_Stop(MediaSensorStack *st) st->sensor->isActive = 0; gf_node_event_out((GF_Node *) st->sensor, 4/*"isActive"*/); - GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[ODM%d] Deactivating media sensor\n", st->stream->odm->OD->objectDescriptorID)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_INTERACT, ("[ODM%d] Deactivating media sensor\n", st->stream->odm->ID)); } st->active_seg = 0; } diff --git a/src/compositor/mpeg4_path_layout.c b/src/compositor/mpeg4_path_layout.c index efb9a99..0ce5b1a 100644 --- a/src/compositor/mpeg4_path_layout.c +++ b/src/compositor/mpeg4_path_layout.c @@ -272,7 +272,8 @@ void compositor_init_path_layout(GF_Compositor *compositor, GF_Node *node) { PathLayoutStack *stack; GF_SAFEALLOC(stack, PathLayoutStack); - + if (!stack) return; + parent_node_setup((ParentNode2D*)stack); gf_node_set_private(node, stack); gf_node_set_callback_function(node, TraversePathLayout); diff --git a/src/compositor/mpeg4_sensors.c b/src/compositor/mpeg4_sensors.c index c56d3f8..720d686 100644 --- a/src/compositor/mpeg4_sensors.c +++ b/src/compositor/mpeg4_sensors.c @@ -29,8 +29,6 @@ #include "visual_manager.h" /*for anchor*/ #include "mpeg4_grouping.h" -/*for anchor processing, which needs to be filtered at the inline scene level*/ -#include #ifndef GPAC_DISABLE_VRML @@ -105,9 +103,6 @@ static void TraverseAnchor(GF_Node *node, void *rs, Bool is_destroy) if (url && url->count && url->vals[0].url && strlen(url->vals[0].url) ) st->enabled = 1; - if (!tr_state->visual->compositor->user->EventProc) { - st->enabled = 0; - } gf_node_dirty_clear(node, GF_SG_NODE_DIRTY); } @@ -147,11 +142,9 @@ static void anchor_activation(GF_Node *node, AnchorStack *st, GF_Compositor *com Bindable_SetSetBind(bindable, 1); break; } - } else if (compositor->term) { + } else { if (gf_scene_process_anchor(node, &evt)) break; - } else if (gf_term_send_event(compositor->term, &evt)) { - break; } i++; } @@ -172,23 +165,22 @@ static Bool OnAnchor(GF_SensorHandler *sh, Bool is_over, Bool is_cancel, GF_Even if (!is_cancel) anchor_activation(sh->sensor, st, compositor); } else if (is_over && !st->over) { st->over = 1; - if (compositor->user->EventProc) { - evt.type = GF_EVENT_NAVIGATE_INFO; - switch (gf_node_get_tag(sh->sensor)) { - case TAG_MPEG4_Anchor: - evt.navigate.to_url = ((M_Anchor *)sh->sensor)->description.buffer; - url = & ((M_Anchor *)sh->sensor)->url; - break; + evt.type = GF_EVENT_NAVIGATE_INFO; + switch (gf_node_get_tag(sh->sensor)) { + case TAG_MPEG4_Anchor: + evt.navigate.to_url = ((M_Anchor *)sh->sensor)->description.buffer; + url = & ((M_Anchor *)sh->sensor)->url; + break; #ifndef GPAC_DISABLE_X3D - case TAG_X3D_Anchor: - evt.navigate.to_url = ((X_Anchor *)sh->sensor)->description.buffer; - url = & ((X_Anchor *)sh->sensor)->url; - break; + case TAG_X3D_Anchor: + evt.navigate.to_url = ((X_Anchor *)sh->sensor)->description.buffer; + url = & ((X_Anchor *)sh->sensor)->url; + break; #endif - } - if (url && (!evt.navigate.to_url || !strlen(evt.navigate.to_url))) evt.navigate.to_url = url->vals[0].url; - gf_term_send_event(compositor->term, &evt); } + if (url && (!evt.navigate.to_url || !strlen(evt.navigate.to_url))) evt.navigate.to_url = url->vals[0].url; + gf_sc_send_event(compositor, &evt); + } else if (!is_over) { st->over = 0; } @@ -1233,13 +1225,12 @@ static Bool OnSphereSensor(GF_SensorHandler *sh, Bool is_over, Bool is_cancel, G sphere->trackPoint_changed = compositor->hit_local_point; gf_node_event_out(sh->sensor, 5/*"trackPoint_changed"*/); } else { - GF_Ray r; - r = compositor->hit_world_ray; - gf_mx_apply_ray(&compositor->hit_world_to_local, &r); - if (!gf_ray_hit_sphere(&r, NULL, st->radius, &compositor->hit_local_point)) { + GF_Ray ray = compositor->hit_world_ray; + gf_mx_apply_ray(&compositor->hit_world_to_local, &ray); + if (!gf_ray_hit_sphere(&ray, NULL, st->radius, &compositor->hit_local_point)) { vec.x = vec.y = vec.z = 0; /*doesn't work properly...*/ - compositor->hit_local_point = gf_closest_point_to_line(r.orig, r.dir, vec); + compositor->hit_local_point = gf_closest_point_to_line(ray.orig, ray.dir, vec); } } @@ -1550,25 +1541,25 @@ void envtest_evaluate(GF_Node *node, GF_Route *_route) break; /*automotive situation - fixme we should use a profile doc ?*/ case 6: - opt = gf_cfg_get_key(compositor->user->config, "Profile", "Automotive"); + opt = gf_opts_get_key("Profile", "Automotive"); equal = (opt && !strcmp(opt, "yes")) ? 1 : 2; strcpy(par_value, (equal==1) ? "TRUE" : "FALSE"); break; /*visually challenged - fixme we should use a profile doc ?*/ case 7: - opt = gf_cfg_get_key(compositor->user->config, "Profile", "VisuallyChallenged"); + opt = gf_opts_get_key("Profile", "VisuallyChallenged"); equal = (opt && !strcmp(opt, "yes")) ? 1 : 2; strcpy(par_value, (equal==1) ? "TRUE" : "FALSE"); break; /*has touch - fixme we should find out by ourselves*/ case 8: - opt = gf_cfg_get_key(compositor->user->config, "Profile", "HasTouchScreen"); + opt = gf_opts_get_key("Profile", "HasTouchScreen"); equal = (!opt || !strcmp(opt, "yes")) ? 1 : 2; strcpy(par_value, (equal==1) ? "TRUE" : "FALSE"); break; /*has key - fixme we should find out by ourselves*/ case 9: - opt = gf_cfg_get_key(compositor->user->config, "Profile", "HasKeyPad"); + opt = gf_opts_get_key("Profile", "HasKeyPad"); equal = (!opt || !strcmp(opt, "yes")) ? 1 : 2; strcpy(par_value, (equal==1) ? "TRUE" : "FALSE"); break; @@ -1620,6 +1611,11 @@ void compositor_evaluate_envtests(GF_Compositor *compositor, u32 param_type) } } +void compositor_envtest_modified(GF_Node *node) +{ + envtest_evaluate(node, NULL); +} + void compositor_init_envtest(GF_Compositor *compositor, GF_Node *node) { M_EnvironmentTest *envtest = (M_EnvironmentTest *)node; @@ -1628,12 +1624,14 @@ void compositor_init_envtest(GF_Compositor *compositor, GF_Node *node) gf_node_set_callback_function(node, traverse_envtest); envtest->on_evaluate = envtest_evaluate; -} -void compositor_envtest_modified(GF_Node *node) -{ - envtest_evaluate(node, NULL); +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + compositor_envtest_modified(node); + } +#endif } + #endif /*GPAC_DISABLE_VRML*/ diff --git a/src/compositor/mpeg4_sound.c b/src/compositor/mpeg4_sound.c index 650937a..7c81aae 100644 --- a/src/compositor/mpeg4_sound.c +++ b/src/compositor/mpeg4_sound.c @@ -69,15 +69,14 @@ static void TraverseSound2D(GF_Node *node, void *rs, Bool is_destroy) } static Bool SND2D_GetChannelVolume(GF_Node *node, Fixed *vol) { + u32 i; Fixed volume = ((M_Sound2D *)node)->intensity; - vol[0] = vol[1] = vol[2] = vol[3] = vol[4] = vol[5] = volume; + for (i=0; isnd_ifce.GetPriority = SND2D_GetPriority; snd->snd_ifce.GetChannelVolume = SND2D_GetChannelVolume; snd->snd_ifce.owner = node; gf_node_set_private(node, snd); @@ -226,7 +224,7 @@ static void TraverseSound(GF_Node *node, void *rs, Bool is_destroy) st->identity = (st->intensity==FIX_ONE) ? 1 : 0; if (snd->spatialize) { - Fixed ang, sign; + Fixed sign; SFVec3f cross; pos = snd->location; gf_mx_apply_vec(&tr_state->model_matrix, &pos); @@ -258,10 +256,12 @@ static void TraverseSound(GF_Node *node, void *rs, Bool is_destroy) } static Bool SND_GetChannelVolume(GF_Node *node, Fixed *vol) { + u32 i; M_Sound *snd = (M_Sound *)node; SoundStack *st = (SoundStack *)gf_node_get_private(node); + for (i=2; iintensity; - vol[2] = vol[3] = vol[4] = vol[5] = st->intensity; if (snd->spatialize) { vol[0] = st->lgain; vol[1] = st->rgain; @@ -270,10 +270,6 @@ static Bool SND_GetChannelVolume(GF_Node *node, Fixed *vol) } return !st->identity; } -static u8 SND_GetPriority(GF_Node *node) -{ - return (u8) ((M_Sound *)node)->priority*255; -} void compositor_init_sound(GF_Compositor *compositor, GF_Node *node) { @@ -284,7 +280,6 @@ void compositor_init_sound(GF_Compositor *compositor, GF_Node *node) return; } snd->snd_ifce.GetChannelVolume = SND_GetChannelVolume; - snd->snd_ifce.GetPriority = SND_GetPriority; snd->snd_ifce.owner = node; gf_node_set_private(node, snd); gf_node_set_callback_function(node, TraverseSound); diff --git a/src/compositor/mpeg4_text.c b/src/compositor/mpeg4_text.c index 545e491..cc28a5c 100644 --- a/src/compositor/mpeg4_text.c +++ b/src/compositor/mpeg4_text.c @@ -61,10 +61,9 @@ typedef struct void text_clean_paths(GF_Compositor *compositor, TextStack *stack) { - GF_TextSpan *span; /*delete all path objects*/ while (gf_list_count(stack->spans)) { - span = (GF_TextSpan*) gf_list_get(stack->spans, 0); + GF_TextSpan *span = (GF_TextSpan*) gf_list_get(stack->spans, 0); gf_list_rem(stack->spans, 0); gf_font_manager_delete_span(compositor->font_manager, span); } @@ -155,6 +154,8 @@ static void build_text_split(TextStack *st, M_Text *txt, GF_TraverseState *tr_st span->bounds.y = start_y; span->bounds.x = 0; span->bounds.width = 0; + span->bounds.y += 2; + span->bounds.height += 4; if (split_words) { for (k=0; knb_glyphs; k++) { @@ -162,7 +163,7 @@ static void build_text_split(TextStack *st, M_Text *txt, GF_TraverseState *tr_st span->bounds.width += tspan->font_scale * (span->glyphs[k] ? span->glyphs[k]->horiz_advance : tspan->font->max_advance_h); } } else { - span->glyphs[0] = tspan->glyphs[FSLTR ? j : (len - j - 1) ]; + //span->glyphs[0] = tspan->glyphs[FSLTR ? j : (len - j - 1) ]; span->glyphs[0] = tspan->glyphs[j]; span->bounds.width = tspan->font_scale * (span->glyphs[0] ? span->glyphs[0]->horiz_advance : tspan->font->max_advance_h); } @@ -185,6 +186,8 @@ static void build_text_split(TextStack *st, M_Text *txt, GF_TraverseState *tr_st span->bounds.height = st->ascent + st->descent; span->bounds.y = start_y; span->bounds.x = 0; + span->bounds.y += 2; + span->bounds.height += 4; k = (j - first_char); span->glyphs[0] = tspan->glyphs[FSLTR ? (first_char+k) : (len - first_char - k - 1)]; span->bounds.width = tspan->font_scale * (span->glyphs[0] ? span->glyphs[0]->horiz_advance : tspan->font->max_advance_h); @@ -268,7 +271,6 @@ static void build_text(TextStack *st, M_Text *txt, GF_TraverseState *tr_state) } /*word is bigger than allowed extent, rewrite 3 previous chars*/ if ((s32)size*tspan->font_scale >= -maxExtent) { - u32 k; u32 nb_chars = (j<2) ? j : 3; for (k=0; ktexture_text_flag = *(SFBool*)field.far_ptr ? 1 : 0; -} - - -void compositor_init_texture_text(GF_Compositor *compositor, GF_Node *node) -{ - gf_node_set_callback_function(node, TraverseTextureText); -} - #ifndef GPAC_DISABLE_3D void compositor_extrude_text(GF_Node *node, GF_TraverseState *tr_state, GF_Mesh *mesh, MFVec3f *thespine, Fixed creaseAngle, Bool begin_cap, Bool end_cap, MFRotation *spine_ori, MFVec2f *spine_scale, Bool txAlongSpine) { diff --git a/src/compositor/mpeg4_textures.c b/src/compositor/mpeg4_textures.c index 0b07d41..dfc6193 100644 --- a/src/compositor/mpeg4_textures.c +++ b/src/compositor/mpeg4_textures.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / Scene Compositor sub-project @@ -38,6 +38,7 @@ extern "C" { /*for cache texture decode and hash*/ #include #include +#include "nodes_stacks.h" typedef struct @@ -85,7 +86,7 @@ static void movietexture_deactivate(MovieTextureStack *stack, M_MovieTexture *mt stack->time_handle.needs_unregister = 1; if (stack->txh.is_open) { - gf_sc_texture_stop(&stack->txh); + gf_sc_texture_stop_no_unregister(&stack->txh); } } static void movietexture_update(GF_TextureHandler *txh) @@ -170,6 +171,8 @@ static void movietexture_update_time(GF_TimeNode *st) if (! mt->isActive) movietexture_activate(stack, mt, time); stack->txh.stream_finished = GF_FALSE; + if (!mt->url.count) + stack->txh.stream_finished = GF_TRUE; } void compositor_init_movietexture(GF_Compositor *compositor, GF_Node *node) @@ -235,22 +238,22 @@ static void imagetexture_destroy(GF_Node *node, void *rs, Bool is_destroy) if (gf_node_get_tag(node)==TAG_MPEG4_CacheTexture) { char section[64]; const char *opt, *file; - Bool delete_file = 1; + Bool delete_file = GF_TRUE; M_CacheTexture *ct = (M_CacheTexture*)node; sprintf(section, "@cache=%p", ct); - file = gf_cfg_get_key(txh->compositor->user->config, section, "cacheFile"); - opt = gf_cfg_get_key(txh->compositor->user->config, section, "expireAfterNTP"); + file = gf_opts_get_key(section, "cacheFile"); + opt = gf_opts_get_key(section, "expireAfterNTP"); if (opt) { u32 sec, frac, exp; sscanf(opt, "%u", &exp); gf_net_get_ntp(&sec, &frac); - if (!exp || (exp>sec)) delete_file=0; + if (!exp || (exp>sec)) delete_file=GF_FALSE; } if (delete_file) { - gf_delete_file((char*)file); - gf_cfg_del_section(txh->compositor->user->config, section); + if (file) gf_file_delete((char*)file); + gf_opts_del_section(section); } if (txh->data) gf_free(txh->data); @@ -297,38 +300,32 @@ static void imagetexture_update(GF_TextureHandler *txh) if (ct->image.buffer) { char *par = (char *) gf_scene_get_service_url( gf_node_get_graph(txh->owner ) ); char *src_url = gf_url_concatenate(par, ct->image.buffer); - FILE *test = gf_fopen( src_url ? src_url : ct->image.buffer, "rb"); - if (test) { - fseek(test, 0, SEEK_END); - ct->data_len = (u32) gf_ftell(test); - ct->data = gf_malloc(sizeof(char)*ct->data_len); - fseek(test, 0, SEEK_SET); - if (ct->data_len != fread(ct->data, 1, ct->data_len, test)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to load CacheTexture data from file %s: IO err\n", src_url ? src_url : ct->image.buffer ) ); - gf_free(ct->data); - ct->data = NULL; - ct->data_len = 0; - } - gf_fclose(test); - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to load CacheTexture data from file %s: not found\n", src_url ? src_url : ct->image.buffer ) ); + + if (ct->data) gf_free(ct->data); + ct->data = NULL; + ct->data_len = 0; + + e = gf_file_load_data(src_url ? src_url : ct->image.buffer, &ct->data, &ct->data_len); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to load CacheTexture data from file %s: %s\n", src_url ? src_url : ct->image.buffer, gf_error_to_string(e) ) ); } + if (ct->image.buffer) gf_free(ct->image.buffer); ct->image.buffer = NULL; if (src_url) gf_free(src_url); } /*BIFS decoded playback*/ switch (ct->objectTypeIndication) { - case GPAC_OTI_IMAGE_JPEG: + case GF_CODECID_JPEG: out_size = 0; - e = gf_img_jpeg_dec((char *) ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, NULL, &out_size, 3); + e = gf_img_jpeg_dec(ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, NULL, &out_size, 3); if (e==GF_BUFFER_TOO_SMALL) { u32 BPP; txh->data = gf_malloc(sizeof(char) * out_size); if (txh->pixelformat==GF_PIXEL_GREYSCALE) BPP = 1; else BPP = 3; - e = gf_img_jpeg_dec((char *) ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, txh->data, &out_size, BPP); + e = gf_img_jpeg_dec(ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, txh->data, &out_size, BPP); if (e==GF_OK) { gf_sc_texture_allocate(txh); gf_sc_texture_set_data(txh); @@ -337,12 +334,12 @@ static void imagetexture_update(GF_TextureHandler *txh) } } break; - case GPAC_OTI_IMAGE_PNG: + case GF_CODECID_PNG: out_size = 0; - e = gf_img_png_dec((char *) ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, NULL, &out_size); + e = gf_img_png_dec(ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, NULL, &out_size); if (e==GF_BUFFER_TOO_SMALL) { txh->data = gf_malloc(sizeof(char) * out_size); - e = gf_img_png_dec((char *) ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, txh->data, &out_size); + e = gf_img_png_dec(ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, txh->data, &out_size); if (e==GF_OK) { gf_sc_texture_allocate(txh); gf_sc_texture_set_data(txh); @@ -360,14 +357,12 @@ static void imagetexture_update(GF_TextureHandler *txh) u32 i; u8 hash[20]; FILE *cached_texture; - char szExtractName[GF_MAX_PATH], section[64], *opt, *src_url; - opt = (char *) gf_cfg_get_key(txh->compositor->user->config, "General", "CacheDirectory"); + char szExtractName[GF_MAX_PATH], *opt, *src_url; + opt = (char *) gf_opts_get_key("core", "cache"); if (opt) { strcpy(szExtractName, opt); } else { - opt = gf_get_default_cache_directory(); - strcpy(szExtractName, opt); - gf_free(opt); + strcpy(szExtractName, gf_get_default_cache_directory()); } strcat(szExtractName, "/"); src_url = (char *) gf_scene_get_service_url( gf_node_get_graph(txh->owner ) ); @@ -384,16 +379,17 @@ static void imagetexture_update(GF_TextureHandler *txh) strcat(szExtractName, ct->cacheURL.buffer); cached_texture = gf_fopen(szExtractName, "wb"); if (cached_texture) { - gf_fwrite(ct->data, 1, ct->data_len, cached_texture); + gf_fwrite(ct->data, ct->data_len, cached_texture); gf_fclose(cached_texture); } /*and write cache info*/ if (ct->expirationDate!=0) { + char section[64]; sprintf(section, "@cache=%p", ct); - gf_cfg_set_key(txh->compositor->user->config, section, "serviceURL", src_url); - gf_cfg_set_key(txh->compositor->user->config, section, "cacheFile", szExtractName); - gf_cfg_set_key(txh->compositor->user->config, section, "cacheName", ct->cacheURL.buffer); + gf_opts_set_key(section, "serviceURL", src_url); + gf_opts_set_key(section, "cacheFile", szExtractName); + gf_opts_set_key(section, "cacheName", ct->cacheURL.buffer); if (ct->expirationDate>0) { char exp[50]; @@ -401,9 +397,9 @@ static void imagetexture_update(GF_TextureHandler *txh) gf_net_get_ntp(&sec, &frac); sec += ct->expirationDate; sprintf(exp, "%u", sec); - gf_cfg_set_key(txh->compositor->user->config, section, "expireAfterNTP", exp); + gf_opts_set_key(section, "expireAfterNTP", exp); } else { - gf_cfg_set_key(txh->compositor->user->config, section, "expireAfterNTP", "0"); + gf_opts_set_key(section, "expireAfterNTP", "0"); } } } @@ -444,18 +440,18 @@ void compositor_init_imagetexture(GF_Compositor *compositor, GF_Node *node) /*locate existing cache*/ url = gf_scene_get_service_url( gf_node_get_graph(node) ); - count = gf_cfg_get_section_count(compositor->user->config); + count = gf_opts_get_section_count(); for (i=0; iuser->config, i); + const char *name = gf_opts_get_section_name(i); if (strncmp(name, "@cache=", 7)) continue; - opt = gf_cfg_get_key(compositor->user->config, name, "serviceURL"); + opt = gf_opts_get_key(name, "serviceURL"); if (!opt || stricmp(opt, url)) continue; - opt = gf_cfg_get_key(compositor->user->config, name, "cacheName"); + opt = gf_opts_get_key(name, "cacheName"); if (opt && ct->cacheURL.buffer && !stricmp(opt, ct->cacheURL.buffer)) { - opt = gf_cfg_get_key(compositor->user->config, name, "cacheFile"); - if (opt) gf_delete_file((char*)opt); - gf_cfg_del_section(compositor->user->config, name); + opt = gf_opts_get_key(name, "cacheFile"); + if (opt) gf_file_delete((char*)opt); + gf_opts_del_section(name); break; } } @@ -537,7 +533,7 @@ static void pixeltexture_update(GF_TextureHandler *txh) stride *= 2; break; case 3: - pix_format = GF_PIXEL_RGB_24; + pix_format = GF_PIXEL_RGB; txh->transparent = 0; stride *= 3; break; @@ -558,17 +554,16 @@ static void pixeltexture_update(GF_TextureHandler *txh) if (st->pixels) gf_free(st->pixels); st->pixels = (char*)gf_malloc(sizeof(char) * stride * pt->image.height); /*FIXME FOR OPENGL !!*/ - if (0) { - for (i=0; iimage.height; i++) { - memcpy(st->pixels + i * stride, pt->image.pixels + i * stride, stride); - } +#if 0 + for (i=0; iimage.height; i++) { + memcpy(st->pixels + i * stride, pt->image.pixels + i * stride, stride); } +#else /*revert pixel ordering...*/ - else { - for (i=0; iimage.height; i++) { - memcpy(st->pixels + i * stride, pt->image.pixels + (pt->image.height - 1 - i) * stride, stride); - } + for (i=0; iimage.height; i++) { + memcpy(st->pixels + i * stride, pt->image.pixels + (pt->image.height - 1 - i) * stride, stride); } +#endif txh->width = pt->image.width; txh->height = pt->image.height; @@ -623,6 +618,62 @@ void compositor_init_mattetexture(GF_Compositor *compositor, GF_Node *node) gf_node_set_private(node, txh); gf_node_set_callback_function(node, imagetexture_destroy); } + +void gf_sc_mo_destroyed(GF_Node *n) +{ + void *st = gf_node_get_private(n); + if (!st) return; + + switch (gf_node_get_tag(n)) { + case TAG_MPEG4_Background2D: + ((Background2DStack *)st)->txh.stream = NULL; + break; + case TAG_MPEG4_Background: + case TAG_X3D_Background: +#ifndef GPAC_DISABLE_3D + ((BackgroundStack *)st)->txh_back.stream = NULL; + ((BackgroundStack *)st)->txh_front.stream = NULL; + ((BackgroundStack *)st)->txh_left.stream = NULL; + ((BackgroundStack *)st)->txh_right.stream = NULL; + ((BackgroundStack *)st)->txh_top.stream = NULL; + ((BackgroundStack *)st)->txh_bottom.stream = NULL; +#endif + break; + case TAG_MPEG4_ImageTexture: + case TAG_X3D_ImageTexture: + ((GF_TextureHandler *)st)->stream = NULL; + break; + case TAG_MPEG4_MovieTexture: + case TAG_X3D_MovieTexture: + ((MovieTextureStack *)st)->txh.stream = NULL; + break; + case TAG_MPEG4_MediaSensor: + ((MediaSensorStack *)st)->stream = NULL; + break; + case TAG_MPEG4_MediaControl: + ((MediaControlStack *)st)->stream = NULL; + break; + case TAG_MPEG4_AudioSource: + ((AudioSourceStack *)st)->input.stream = NULL; + break; + case TAG_MPEG4_AudioClip: + case TAG_X3D_AudioClip: + ((AudioClipStack *)st)->input.stream = NULL; + break; +#ifndef GPAC_DISABLE_SVG + case TAG_SVG_video: + case TAG_SVG_image: + ((SVG_video_stack *)st)->txh.stream = NULL; + break; + case TAG_SVG_audio: + ((SVG_audio_stack *)st)->input.stream = NULL; + break; +#endif + default: + break; + } +} + #ifdef __cplusplus } #endif diff --git a/src/compositor/mpeg4_timesensor.c b/src/compositor/mpeg4_timesensor.c index 93a2833..a8fa471 100644 --- a/src/compositor/mpeg4_timesensor.c +++ b/src/compositor/mpeg4_timesensor.c @@ -99,7 +99,7 @@ void timesensor_update_time(GF_TimeNode *st) } #ifndef GPAC_DISABLE_X3D if (stack->is_x3d && !TS->loop) { - if (!stack->start_time) return; + if (!stack->start_time && !gf_sys_old_arch_compat() ) return; if (currentTime >= TS->startTime+stack->cycle_interval) return; } #endif diff --git a/src/compositor/mpeg4_viewport.c b/src/compositor/mpeg4_viewport.c index 00eaef8..e1d734c 100644 --- a/src/compositor/mpeg4_viewport.c +++ b/src/compositor/mpeg4_viewport.c @@ -25,11 +25,11 @@ #include "nodes_stacks.h" #include "visual_manager.h" -/*for default scene view*/ -#include + #include +GF_EXPORT GF_Err gf_sc_get_viewpoint(GF_Compositor *compositor, u32 viewpoint_idx, const char **outName, Bool *is_bound) { #ifndef GPAC_DISABLE_VRML @@ -43,18 +43,25 @@ GF_Err gf_sc_get_viewpoint(GF_Compositor *compositor, u32 viewpoint_idx, const c n = (GF_Node*)gf_list_get(compositor->visual->view_stack, viewpoint_idx-1); switch (gf_node_get_tag(n)) { case TAG_MPEG4_Viewport: - *outName = ((M_Viewport*)n)->description.buffer; - *is_bound = ((M_Viewport*)n)->isBound; + if (outName) + *outName = ((M_Viewport*)n)->description.buffer; + if (is_bound) + *is_bound = ((M_Viewport*)n)->isBound; return GF_OK; case TAG_MPEG4_Viewpoint: #ifndef GPAC_DISABLE_X3D case TAG_X3D_Viewpoint: #endif - *outName = ((M_Viewpoint*)n)->description.buffer; - *is_bound = ((M_Viewpoint*)n)->isBound; + if (outName) + *outName = ((M_Viewpoint*)n)->description.buffer; + if (is_bound) + *is_bound = ((M_Viewpoint*)n)->isBound; return GF_OK; default: - *outName = NULL; + if (outName) + *outName = NULL; + if (is_bound) + *is_bound = GF_FALSE; return GF_OK; } #else @@ -62,6 +69,7 @@ GF_Err gf_sc_get_viewpoint(GF_Compositor *compositor, u32 viewpoint_idx, const c #endif } +GF_EXPORT GF_Err gf_sc_set_viewpoint(GF_Compositor *compositor, u32 viewpoint_idx, const char *viewpoint_name) { #ifndef GPAC_DISABLE_VRML @@ -112,7 +120,7 @@ GF_Err gf_sc_set_viewpoint(GF_Compositor *compositor, u32 viewpoint_idx, const c #ifndef GPAC_DISABLE_VRML -#define VPCHANGED(__rend) { GF_Event evt; evt.type = GF_EVENT_VIEWPOINTS; gf_term_send_event(__rend->term, &evt); } +#define VPCHANGED(__comp) { GF_Event evt; evt.type = GF_EVENT_VIEWPOINTS; gf_sc_send_event(__comp, &evt); } static void DestroyViewStack(GF_Node *node) @@ -381,9 +389,14 @@ static void TraverseViewpoint(GF_Node *node, void *rs, Bool is_destroy) /*not bound or in 2D visual*/ if (!vp->isBound || !tr_state->navigations) return; - if (!gf_node_dirty_get(node)) return; + if (!gf_node_dirty_get(node)) { + if ((tr_state->vp_size.x == st->last_vp_size.x) && (tr_state->vp_size.y == st->last_vp_size.y)) return; + } gf_node_dirty_clear(node, 0); + st->last_vp_size.x = tr_state->vp_size.x; + st->last_vp_size.y = tr_state->vp_size.y; + /*move to local system*/ gf_mx_copy(mx, st->world_view_mx); gf_mx_add_translation(&mx, vp->position.x, vp->position.y, vp->position.z); @@ -421,9 +434,11 @@ void compositor_init_viewpoint(GF_Compositor *compositor, GF_Node *node) static void navinfo_set_bind(GF_Node *node, GF_Route *route) { - ViewStack *st = (ViewStack *) gf_node_get_private(node); - Bindable_OnSetBind(node, st->reg_stacks, NULL); - gf_sc_invalidate( gf_sc_get_compositor(node), NULL); + if (node) { + ViewStack *st = (ViewStack *) gf_node_get_private(node); + Bindable_OnSetBind(node, st->reg_stacks, NULL); + gf_sc_invalidate( gf_sc_get_compositor(node), NULL); + } } static void TraverseNavigationInfo(GF_Node *node, void *rs, Bool is_destroy) @@ -518,7 +533,8 @@ static void TraverseNavigationInfo(GF_Node *node, void *rs, Bool is_destroy) if (ni->avatarSize.count>1) tr_state->camera->avatar_size.y = gf_mulfix(scale, ni->avatarSize.vals[1]); if (ni->avatarSize.count>2) tr_state->camera->avatar_size.z = gf_mulfix(scale, ni->avatarSize.vals[2]); - if (0 && tr_state->pixel_metrics) { +#if 0 + if (tr_state->pixel_metrics) { u32 s = MAX(tr_state->visual->width, tr_state->visual->height); s /= 2; // tr_state->camera->speed = ni->speed; @@ -529,6 +545,8 @@ static void TraverseNavigationInfo(GF_Node *node, void *rs, Bool is_destroy) } #endif +#endif + } void compositor_init_navigation_info(GF_Compositor *compositor, GF_Node *node) @@ -544,6 +562,12 @@ void compositor_init_navigation_info(GF_Compositor *compositor, GF_Node *node) gf_node_set_private(node, st); gf_node_set_callback_function(node, TraverseNavigationInfo); ((M_NavigationInfo*)node)->on_set_bind = navinfo_set_bind; + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + navinfo_set_bind(NULL, NULL); + } +#endif } diff --git a/src/compositor/navigate.c b/src/compositor/navigate.c index c5e23de..ad25486 100644 --- a/src/compositor/navigate.c +++ b/src/compositor/navigate.c @@ -26,7 +26,6 @@ #include "nodes_stacks.h" #include "visual_manager.h" #include -#include #ifndef GPAC_DISABLE_3D @@ -274,6 +273,11 @@ Bool gf_sc_fit_world_to_screen(GF_Compositor *compositor) camera_set_vectors(cam, pos, cam->vp_orientation, cam->fieldOfView); cam->position = diff; camera_move_to(cam, pos, cam->target, cam->up); + if (!compositor->player) { + camera_stop_anim(cam); + camera_set_vectors(cam, cam->end_pos, cam->end_ori, cam->end_fov); + } + cam->examine_center = tr_state.bbox.center; cam->flags |= CF_STORE_VP; if (cam->z_far < dist) cam->z_far = 10*dist; @@ -282,7 +286,61 @@ Bool gf_sc_fit_world_to_screen(GF_Compositor *compositor) return 1; } -//#define SCALE_NAV +static void handle_mouse_move_3d(GF_Compositor *compositor, GF_Camera *cam, u32 keys, Fixed dx, Fixed dy) +{ + Fixed trans_scale = cam->width/20; + //if default VP is quite far from center use larger dz/dy moves + if (cam->vp_dist>100) trans_scale *= 10; + + switch (cam->navigate_mode) { + /*FIXME- we'll likely need a "step" value for walk at some point*/ + case GF_NAVIGATE_WALK: + case GF_NAVIGATE_FLY: + view_pan_x(compositor, cam, -dx); + if (keys & GF_KEY_MOD_CTRL) view_pan_y(compositor, cam, dy); + else view_translate_z(compositor, cam, gf_mulfix(dy, trans_scale)); + break; + case GF_NAVIGATE_VR: + view_pan_x(compositor, cam, -dx); + if (keys & GF_KEY_MOD_CTRL) view_zoom(compositor, cam, dy); + else view_pan_y(compositor, cam, dy); + break; + case GF_NAVIGATE_PAN: + view_pan_x(compositor, cam, -dx); + if (keys & GF_KEY_MOD_CTRL) view_translate_z(compositor, cam, gf_mulfix(dy, trans_scale)); + else view_pan_y(compositor, cam, dy); + break; + case GF_NAVIGATE_SLIDE: + view_translate_x(compositor, cam, gf_mulfix(dx, trans_scale)); + if (keys & GF_KEY_MOD_CTRL) view_translate_z(compositor, cam, gf_mulfix(dy, trans_scale)); + else view_translate_y(compositor, cam, gf_mulfix(dy, trans_scale)); + break; + case GF_NAVIGATE_EXAMINE: + if (keys & GF_KEY_MOD_CTRL) { + view_translate_z(compositor, cam, gf_mulfix(dy, trans_scale)); + view_roll(compositor, cam, gf_mulfix(dx, trans_scale)); + } else { + if (ABS(dx) > ABS(dy)) { + view_exam_x(compositor, cam, -gf_mulfix(GF_PI, dx)); + } else { + view_exam_y(compositor, cam, gf_mulfix(GF_PI, dy)); + } + } + break; + case GF_NAVIGATE_ORBIT: + if (keys & GF_KEY_MOD_CTRL) { + view_translate_z(compositor, cam, gf_mulfix(dy, trans_scale)); + } else { + view_orbit_x(compositor, cam, -gf_mulfix(GF_PI, dx)); + view_orbit_y(compositor, cam, gf_mulfix(GF_PI, dy)); + } + break; + case GF_NAVIGATE_GAME: + view_pan_x(compositor, cam, -dx); + view_pan_y(compositor, cam, dy); + break; + } +} static Bool compositor_handle_navigation_3d(GF_Compositor *compositor, GF_Event *ev) { @@ -290,9 +348,6 @@ static Bool compositor_handle_navigation_3d(GF_Compositor *compositor, GF_Event Fixed dx, dy, key_trans, key_pan, key_exam; s32 key_inv; u32 keys; -#ifdef SCALE_NAV - Bool is_pixel_metrics; -#endif GF_Camera *cam; Fixed zoom = compositor->zoom; @@ -300,9 +355,6 @@ static Bool compositor_handle_navigation_3d(GF_Compositor *compositor, GF_Event #ifndef GPAC_DISABLE_VRML if (compositor->active_layer) { cam = compositor_layer3d_get_camera(compositor->active_layer); -#ifdef SCALE_NAV - is_pixel_metrics = gf_sg_use_pixel_metrics(gf_node_get_graph(compositor->active_layer)); -#endif } #endif @@ -310,9 +362,6 @@ static Bool compositor_handle_navigation_3d(GF_Compositor *compositor, GF_Event cam = &compositor->visual->camera; assert(compositor); assert(compositor->scene); -#ifdef SCALE_NAV - is_pixel_metrics = compositor->traverse_state->pixel_metrics; -#endif } if (!cam || (cam->navigate_mode==GF_NAVIGATE_NONE)) return 0; @@ -328,21 +377,16 @@ static Bool compositor_handle_navigation_3d(GF_Compositor *compositor, GF_Event dx = (x - compositor->grab_x); dy = (compositor->grab_y - y); -#ifdef SCALE_NAV - trans_scale = is_pixel_metrics ? cam->width/2 : INT2FIX(10); - key_trans = is_pixel_metrics ? INT2FIX(10) : cam->avatar_size.x; -#else trans_scale = cam->width/20; key_trans = cam->avatar_size.x/2; //if default VP is quite far from center use larger dz/dy moves if (cam->vp_dist>100) trans_scale *= 10; -#endif if (cam->world_bbox.is_set && (key_trans*5 > cam->world_bbox.radius)) { key_trans = cam->world_bbox.radius / 100; } - key_pan = FIX_ONE/25; + key_pan = FIX_ONE/20; key_exam = FIX_ONE/20; key_inv = 1; @@ -354,6 +398,14 @@ static Bool compositor_handle_navigation_3d(GF_Compositor *compositor, GF_Event key_trans*=4; } + if (! compositor->orientation_sensors_active) { + Fixed yaw, pitch, roll; + gf_mx_get_yaw_pitch_roll(&compositor->visual->camera.modelview, &yaw, &pitch, &roll); + compositor->audio_renderer->yaw = yaw; + compositor->audio_renderer->pitch = pitch; + compositor->audio_renderer->roll = roll; + } + switch (ev->type) { case GF_EVENT_MOUSEDOWN: /*left*/ @@ -383,9 +435,9 @@ static Bool compositor_handle_navigation_3d(GF_Compositor *compositor, GF_Event } break; - /* note: shortcuts are mostly the same as blaxxun contact, I don't feel like remembering 2 sets...*/ case GF_EVENT_MOUSEMOVE: - if (gf_term_get_option(compositor->term, GF_OPT_ORIENTATION_SENSORS_ACTIVE)) return 0; + if (compositor->orientation_sensors_active) return 0; + if (!compositor->navigation_state) { if (cam->navigate_mode==GF_NAVIGATE_GAME) { /*init mode*/ @@ -393,58 +445,19 @@ static Bool compositor_handle_navigation_3d(GF_Compositor *compositor, GF_Event compositor->grab_y = y; compositor->navigation_state = 1; } + compositor->auto_rotate=0; return 0; } compositor->navigation_state++; - switch (cam->navigate_mode) { - /*FIXME- we'll likely need a "step" value for walk at some point*/ - case GF_NAVIGATE_WALK: - case GF_NAVIGATE_FLY: - view_pan_x(compositor, cam, -dx); - if (keys & GF_KEY_MOD_CTRL) view_pan_y(compositor, cam, dy); - else view_translate_z(compositor, cam, gf_mulfix(dy, trans_scale)); - break; - case GF_NAVIGATE_VR: - view_pan_x(compositor, cam, -dx); - if (keys & GF_KEY_MOD_CTRL) view_zoom(compositor, cam, dy); - else view_pan_y(compositor, cam, dy); - break; - case GF_NAVIGATE_PAN: - view_pan_x(compositor, cam, -dx); - if (keys & GF_KEY_MOD_CTRL) view_translate_z(compositor, cam, gf_mulfix(dy, trans_scale)); - else view_pan_y(compositor, cam, dy); - break; - case GF_NAVIGATE_SLIDE: - view_translate_x(compositor, cam, gf_mulfix(dx, trans_scale)); - if (keys & GF_KEY_MOD_CTRL) view_translate_z(compositor, cam, gf_mulfix(dy, trans_scale)); - else view_translate_y(compositor, cam, gf_mulfix(dy, trans_scale)); - break; - case GF_NAVIGATE_EXAMINE: - if (keys & GF_KEY_MOD_CTRL) { - view_translate_z(compositor, cam, gf_mulfix(dy, trans_scale)); - view_roll(compositor, cam, gf_mulfix(dx, trans_scale)); - } else { - if (ABS(dx) > ABS(dy)) { - view_exam_x(compositor, cam, -gf_mulfix(GF_PI, dx)); - } else { - view_exam_y(compositor, cam, gf_mulfix(GF_PI, dy)); - } - } - break; - case GF_NAVIGATE_ORBIT: - if (keys & GF_KEY_MOD_CTRL) { - view_translate_z(compositor, cam, gf_mulfix(dy, trans_scale)); - } else { - view_orbit_x(compositor, cam, -gf_mulfix(GF_PI, dx)); - view_orbit_y(compositor, cam, gf_mulfix(GF_PI, dy)); - } - break; - case GF_NAVIGATE_GAME: - view_pan_x(compositor, cam, -dx); - view_pan_y(compositor, cam, dy); - break; - } + if (x <= -0.49) compositor->auto_rotate = 1; + else if (x >= 0.49) compositor->auto_rotate = 2; + else if (y <= -0.49) compositor->auto_rotate = 3; + else if (y >= 0.49) compositor->auto_rotate = 4; + else compositor->auto_rotate = 0; + + handle_mouse_move_3d(compositor, cam, keys, dx, dy); + compositor->grab_x = x; compositor->grab_y = y; return 1; @@ -471,7 +484,36 @@ static Bool compositor_handle_navigation_3d(GF_Compositor *compositor, GF_Event } return 1; + case GF_EVENT_MULTITOUCH: + compositor->auto_rotate=0; + compositor->navigation_state = 0; + if (ev->mtouch.num_fingers==2) { + if( ABS(ev->mtouch.pinch) * 100 > 2 ) { + if (cam->is_3D) { + view_translate_z(compositor, cam, gf_mulfix(cam->width, compositor->visual->width* ev->mtouch.pinch)); + } else { + nav_set_zoom_trans_2d(compositor->visual, zoom + gf_mulfix(trans_scale, ev->mtouch.pinch), 0, 0); + } + return 1; + } + if( ABS(ev->mtouch.rotation) > GF_PI/40 ) { + view_roll(compositor, cam, gf_mulfix(ev->mtouch.rotation, trans_scale)); + return 1; + } + } else if (ev->mtouch.num_fingers==3) { + compositor->visual->camera.start_zoom = compositor->zoom; + compositor->zoom = FIX_ONE; + compositor->interoccular_offset = 0; + compositor->focdist = 0; + compositor->interoccular_offset = 0; + compositor->focdist = 0; + compositor_3d_reset_camera(compositor); + return 1; + } + return 0; + case GF_EVENT_MOUSEUP: + compositor->auto_rotate=0; if (ev->mouse.button==GF_MOUSE_LEFT) compositor->navigation_state = 0; break; @@ -495,9 +537,9 @@ static Bool compositor_handle_navigation_3d(GF_Compositor *compositor, GF_Event compositor->visual->camera.start_zoom = compositor->zoom; compositor->zoom = FIX_ONE; compositor->interoccular_offset = 0; - compositor->focus_distance = 0; + compositor->focdist = 0; compositor->interoccular_offset = 0; - compositor->focus_distance = 0; + compositor->focdist = 0; compositor_3d_reset_camera(compositor); } break; @@ -514,9 +556,9 @@ static Bool compositor_handle_navigation_3d(GF_Compositor *compositor, GF_Event if (keys & GF_KEY_MOD_ALT) { if ( (keys & GF_KEY_MOD_SHIFT) && (compositor->visual->nb_views > 1) ) { /*+ or - 10 cm*/ - compositor->focus_distance += INT2FIX(key_inv); + compositor->focdist += INT2FIX(key_inv); cam->flags |= CAM_IS_DIRTY; - fprintf(stderr, "AutoStereo view distance %f - focus %f\n", FIX2FLT(compositor->video_out->view_distance)/100, FIX2FLT(compositor->focus_distance)/100); + fprintf(stderr, "AutoStereo view distance %f - focus %f\n", FIX2FLT(compositor->video_out->dispdist)/100, FIX2FLT(compositor->focdist)/100); gf_sc_invalidate(compositor, NULL); return 1; } @@ -556,7 +598,7 @@ static Bool compositor_handle_navigation_3d(GF_Compositor *compositor, GF_Event if (keys & GF_KEY_MOD_ALT) { if ( (keys & GF_KEY_MOD_SHIFT) && (compositor->visual->nb_views > 1) ) { compositor->interoccular_offset += FLT2FIX(0.5) * key_inv; - fprintf(stderr, "AutoStereo interoccular distance %f\n", FIX2FLT(compositor->interoccular_distance + compositor->interoccular_offset)); + fprintf(stderr, "AutoStereo interoccular distance %f\n", FIX2FLT(compositor->iod + compositor->interoccular_offset)); cam->flags |= CAM_IS_DIRTY; gf_sc_invalidate(compositor, NULL); return 1; @@ -607,35 +649,55 @@ static Bool compositor_handle_navigation_3d(GF_Compositor *compositor, GF_Event return 1; } break; + case GF_KEY_D: + if (keys & GF_KEY_MOD_CTRL) { + compositor->tvtd = !compositor->tvtd; + gf_sc_invalidate(compositor, NULL); + return 1; + } + break; + case GF_KEY_G: + if (keys & GF_KEY_MOD_CTRL) { + compositor->vrhud_mode++; + if (compositor->vrhud_mode==5) compositor->vrhud_mode=0; + gf_sc_invalidate(compositor, NULL); + return 1; + } + break; + case GF_KEY_A: + if (keys & GF_KEY_MOD_CTRL) { + compositor->tvtf = !compositor->tvtf; + gf_sc_invalidate(compositor, NULL); + return 1; + } + break; } break; case GF_EVENT_SENSOR_ORIENTATION: { - Fixed x, y, z, w, yaw, /*pitch, */roll; + Fixed z, w, yaw, /*pitch, */roll; GF_Vec target; GF_Matrix mx; -#ifndef GPAC_ANDROID - /* - * In iOS we get x, y, z in quaternions (first measurement is the frame of reference) - */ - x = ev->sensor.x; - y = ev->sensor.y; - z = ev->sensor.z; - w = ev->sensor.w; + /* In iOS we get x, y, z in quaternions (first measurement is the frame of reference) */ + if (ev->sensor.w) { + x = ev->sensor.x; + y = ev->sensor.y; + z = ev->sensor.z; + w = ev->sensor.w; - yaw = gf_atan2(2*gf_mulfix(z,w) - 2*gf_mulfix(y,x) , 1 - 2*gf_mulfix(z,z) - 2*gf_mulfix(x,x)); - //pitch = asin(2*y*z + 2*x*w); - roll = gf_atan2(2*gf_mulfix(y,w) - 2*gf_mulfix(z,x) , 1 - 2*gf_mulfix(y,y) - 2*gf_mulfix(x,x)); -#else - /* - * In Android we get yaw, pitch, roll values (in rad) - * The frame of reference is absolute - */ - yaw = ev->sensor.x; - //pitch = ev->sensor.y; - roll = ev->sensor.z; -#endif + yaw = gf_atan2(2*gf_mulfix(z,w) - 2*gf_mulfix(y,x) , 1 - 2*gf_mulfix(z,z) - 2*gf_mulfix(x,x)); + //pitch = asin(2*y*z + 2*x*w); + roll = gf_atan2(2*gf_mulfix(y,w) - 2*gf_mulfix(z,x) , 1 - 2*gf_mulfix(y,y) - 2*gf_mulfix(x,x)); + } else { + /* + * In Android we get yaw, pitch, roll values (in rad) + * The frame of reference is absolute + */ + yaw = ev->sensor.x; + //pitch = ev->sensor.y; + roll = ev->sensor.z; + } target.x = 0; target.y = -FIX_ONE; target.z = 0; @@ -729,6 +791,26 @@ static Bool compositor_handle_navigation_2d(GF_VisualManager *visual, GF_Event * return 0; } break; + case GF_EVENT_MULTITOUCH: + if (ev->mtouch.num_fingers==2) { + if( ABS(ev->mtouch.pinch) * 100 > 2 ) { + new_zoom = zoom + ev->mtouch.pinch * MIN(visual->width, visual->height)/100; + nav_set_zoom_trans_2d(visual, new_zoom, 0, 0); + return 1; + } + if( ABS(ev->mtouch.rotation) > GF_PI/40 ) { + visual->compositor->rotation -= ev->mtouch.rotation; + nav_set_zoom_trans_2d(visual, zoom, 0, 0); + return 1; + } + } else if (ev->mtouch.num_fingers==3) { + visual->compositor->trans_x = visual->compositor->trans_y = 0; + visual->compositor->rotation = 0; + visual->compositor->zoom = FIX_ONE; + nav_set_zoom_trans_2d(visual, FIX_ONE, 0, 0); + return 1; + } + return 0; case GF_EVENT_MOUSEWHEEL: switch (navigation_mode) { @@ -807,7 +889,7 @@ static Bool compositor_handle_navigation_2d(GF_VisualManager *visual, GF_Event * case GF_KEY_UP: if (navigation_mode == GF_NAVIGATE_SLIDE) { if (keys & GF_KEY_MOD_CTRL) { - Fixed new_zoom = zoom; + new_zoom = zoom; if (new_zoom > FIX_ONE) new_zoom += key_inv*FIX_ONE/10; else new_zoom += key_inv*FIX_ONE/20; nav_set_zoom_trans_2d(visual, new_zoom, 0, 0); @@ -826,6 +908,40 @@ static Bool compositor_handle_navigation_2d(GF_VisualManager *visual, GF_Event * return 0; } +void compositor_handle_auto_navigation(GF_Compositor *compositor) +{ +#ifndef GPAC_DISABLE_3D + GF_Camera *cam = NULL; + Fixed dx, dy; +#ifndef GPAC_DISABLE_VRML + if (compositor->active_layer) { + cam = compositor_layer3d_get_camera(compositor->active_layer); + } + if (!cam) + cam = &compositor->visual->camera; +#endif + + dx = dy = 0; + switch (compositor->auto_rotate) { + case 1: + dx = -FIX_ONE/100; + break; + case 2: + dx = FIX_ONE/100; + break; + case 3: + dy = FIX_ONE/100; + break; + case 4: + dy = -FIX_ONE/100; + break; + default: + return; + } + handle_mouse_move_3d(compositor, cam, 0, dx, dy); +#endif +} + Bool compositor_handle_navigation(GF_Compositor *compositor, GF_Event *ev) { if (!compositor->scene) return 0; diff --git a/src/compositor/nodes_stacks.h b/src/compositor/nodes_stacks.h index a19b0c2..94965b1 100644 --- a/src/compositor/nodes_stacks.h +++ b/src/compositor/nodes_stacks.h @@ -91,6 +91,7 @@ typedef struct GF_List *reg_stacks; Bool prev_was_bound; GF_Matrix world_view_mx; + SFVec2f last_vp_size; u32 last_sim_time; } ViewStack; @@ -130,6 +131,27 @@ typedef struct } BackgroundStack; #endif +#ifndef GPAC_DISABLE_SVG +typedef struct +{ + GF_TextureHandler txh; + Drawable *drawable; + MFURL txurl; + Bool first_frame_fetched; + GF_Node *audio; + Bool audio_dirty; + Bool stop_requested; +} SVG_video_stack; + +typedef struct +{ + GF_AudioInput input; + Bool is_active, is_error; + MFURL aurl; +} SVG_audio_stack; + +#endif + typedef struct { @@ -269,13 +291,13 @@ GF_TextureHandler *gf_sc_hardcoded_proto_get_texture_handler(GF_Node *n); void compositor_extrude_text(GF_Node *node, GF_TraverseState *tr_state, GF_Mesh *mesh, MFVec3f *thespine, Fixed creaseAngle, Bool begin_cap, Bool end_cap, MFRotation *spine_ori, MFVec2f *spine_scale, Bool txAlongSpine); #endif -void compositor_init_texture_text(GF_Compositor *compositor, GF_Node *node); - void compositor_init_envtest(GF_Compositor *compositor, GF_Node *node); void compositor_envtest_modified(GF_Node *node); void compositor_evaluate_envtests(GF_Compositor *compositor, u32 param_type); +#ifdef GPAC_ENABLE_FLASHSHAPE void compositor_init_hc_flashshape(GF_Compositor *compositor, GF_Node *node); +#endif #endif /*GPAC_DISABLE_VRML*/ @@ -317,8 +339,11 @@ void compositor_init_svg_font_face_uri(GF_Compositor *compositor, GF_Node *node) void compositor_init_svg_updates(GF_Compositor *compositor, GF_Node *node); +#ifdef GPAC_ENABLE_SVG_FILTERS void compositor_init_svg_filter(GF_Compositor *compositor, GF_Node *node); void svg_draw_filter(GF_Node *filter, GF_Node *node, GF_TraverseState *tr_state); +#endif + GF_TextureHandler *compositor_svg_get_gradient_texture(GF_Node *node); GF_TextureHandler *compositor_svg_get_image_texture(GF_Node *node); diff --git a/src/compositor/object_manager.c b/src/compositor/object_manager.c new file mode 100644 index 0000000..d63bbdd --- /dev/null +++ b/src/compositor/object_manager.c @@ -0,0 +1,1967 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2018 + * All rights reserved + * + * This file is part of GPAC / Scene Compositor sub-project + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include + +GF_EXPORT +GF_ObjectManager *gf_odm_new() +{ + GF_ObjectManager *tmp; + GF_SAFEALLOC(tmp, GF_ObjectManager); + if (!tmp) return NULL; + +#ifndef GPAC_DISABLE_VRML + tmp->ms_stack = gf_list_new(); + tmp->mc_stack = gf_list_new(); +#endif + return tmp; +} + +void gf_odm_reset_media_control(GF_ObjectManager *odm, Bool signal_reset) +{ +#ifndef GPAC_DISABLE_VRML + MediaSensorStack *media_sens; + MediaControlStack *media_ctrl; + + while ((media_sens = (MediaSensorStack *)gf_list_last(odm->ms_stack))) { + MS_Stop(media_sens); + /*and detach from stream object*/ + media_sens->stream = NULL; + gf_list_rem_last(odm->ms_stack); + } + + while ((media_ctrl = (MediaControlStack *)gf_list_last(odm->mc_stack))) { + if (signal_reset) + gf_odm_remove_mediacontrol(odm, media_ctrl); + media_ctrl->stream = NULL; + media_ctrl->ck = NULL; + gf_list_rem_last(odm->mc_stack); + } +#endif +} + + +void gf_odm_del(GF_ObjectManager *odm) +{ + if (odm->addon && (odm->addon->root_od==odm)) { + odm->addon->root_od = NULL; + odm->addon->started = 0; + } + if (odm->upper_layer_odm) { + odm->upper_layer_odm->lower_layer_odm = NULL; + } + if (odm->lower_layer_odm) { + odm->lower_layer_odm->upper_layer_odm = NULL; + } + + /*detach media object as referenced by the scene - this should ensures that any attempt to lock the ODM from the + compositor will fail as the media object is no longer linked to object manager*/ + if (odm->mo) odm->mo->odm = NULL; + +#ifndef GPAC_DISABLE_VRML + gf_odm_reset_media_control(odm, 0); + gf_list_del(odm->ms_stack); + gf_list_del(odm->mc_stack); +#endif + + if (odm->type == GF_STREAM_INTERACT) + gf_input_sensor_delete(odm); + + if (odm->raw_frame_sema) gf_sema_del(odm->raw_frame_sema); + + if (odm->pid) gf_filter_pid_set_udta(odm->pid, NULL); + if (odm->extra_pids) { + while (gf_list_count(odm->extra_pids)) { + GF_ODMExtraPid *xpid = gf_list_pop_back(odm->extra_pids); + if (xpid->pid) gf_filter_pid_set_udta(xpid->pid, NULL); + gf_free(xpid); + } + gf_list_del(odm->extra_pids); + } + gf_free(odm); +} + + +void gf_odm_register_pid(GF_ObjectManager *odm, GF_FilterPid *pid, Bool register_only) +{ + u32 es_id=0; + const GF_PropertyValue *prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ESID); + if (!prop) prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + if (prop) es_id = prop->value.uint; + + if (! odm->pid) { + odm->pid = pid; + odm->pid_id = es_id; + } else { + GF_ODMExtraPid *xpid; + if (!odm->extra_pids) odm->extra_pids = gf_list_new(); + GF_SAFEALLOC(xpid, GF_ODMExtraPid); + if (xpid) { + xpid->pid = pid; + xpid->pid_id = es_id; + gf_list_add(odm->extra_pids, xpid); + } + } + + if (register_only) return; + + gf_odm_setup_object(odm, odm->subscene ? odm->scene_ns : odm->parentscene->root_od->scene_ns, pid); +} + +//this function is not exposed and shall only be used for the reset scene event +//it sends the event synchronously, as needed for gf_term_disconnect() +void gf_filter_pid_exec_event(GF_FilterPid *pid, GF_FilterEvent *evt); + +GF_EXPORT +void gf_odm_disconnect(GF_ObjectManager *odm, u32 do_remove) +{ + GF_Compositor *compositor = odm->parentscene ? odm->parentscene->compositor : odm->subscene->compositor; + + if (odm->skip_disconnect_state) { + if (do_remove) odm->skip_disconnect_state = 2; + return; + } + gf_odm_stop(odm, GF_TRUE); + + /*disconnect sub-scene*/ + if (odm->subscene) { + //send a scene reset + if (odm->pid) { + GF_FilterEvent fevt; + + GF_FEVT_INIT(fevt, GF_FEVT_RESET_SCENE, odm->pid); + fevt.attach_scene.object_manager = odm; + gf_filter_pid_exec_event(odm->pid, &fevt); + } + gf_scene_disconnect(odm->subscene, do_remove ? GF_TRUE : GF_FALSE); + } + else if (odm->pid) { + GF_FilterEvent fevt; + switch (odm->type) { + case GF_STREAM_SCENE: + case GF_STREAM_OD: + case GF_STREAM_PRIVATE_SCENE: + case GF_STREAM_TEXT: + GF_FEVT_INIT(fevt, GF_FEVT_RESET_SCENE, odm->pid); + fevt.attach_scene.object_manager = odm; + fevt.attach_scene.on_pid = odm->pid; + gf_filter_pid_exec_event(odm->pid, &fevt); + break; + } + } + /*no destroy*/ + if (!do_remove) return; + + /*unload the decoders before deleting the channels to prevent any access fault*/ + if (odm->type==GF_STREAM_INTERACT) { + u32 i, count; + // Remove all Registered InputSensor nodes -> shut down the InputSensor threads -> prevent illegal access on deleted pointers + GF_MediaObject *obj = odm->mo; + count = gf_mo_event_target_count(obj); + for (i=0; ienabled = 0; + InputSensorModified(n); + break; +#endif + default: + break; + } + } + } + + /*detach from network service */ + if (odm->scene_ns) { + GF_Scene *scene = odm->parentscene; + GF_SceneNamespace *ns = odm->scene_ns; + if (ns->nb_odm_users) ns->nb_odm_users--; + if (ns->owner == odm) { + /*detach it!!*/ + ns->owner = NULL; + /*try to assign a new root in case this is not scene shutdown*/ + if (ns->nb_odm_users && odm->parentscene) { + GF_ObjectManager *new_root; + u32 i = 0; + while ((new_root = (GF_ObjectManager *)gf_list_enum(odm->parentscene->resources, &i)) ) { + if (new_root == odm) continue; + if (new_root->scene_ns != ns) continue; + + ns->owner = new_root; + break; + } + } + if (!ns->owner) ns->nb_odm_users=0; + } + scene = scene ? gf_scene_get_root_scene(scene) : NULL; + odm->scene_ns = NULL; + if (!ns->nb_odm_users && scene) gf_scene_ns_del(ns, scene); + } + + + /*delete from the parent scene.*/ + if (odm->parentscene) { + GF_Event evt; + if ((odm->type == GF_STREAM_AUDIO) && odm->parentscene->compositor->audio_renderer->nb_audio_objects) + odm->parentscene->compositor->audio_renderer->nb_audio_objects--; + + if (odm->addon) { + gf_list_del_item(odm->parentscene->declared_addons, odm->addon); + gf_scene_reset_addon(odm->addon, GF_FALSE); + odm->addon = NULL; + } + + evt.type = GF_EVENT_CONNECT; + evt.connect.is_connected = GF_FALSE; + gf_filter_forward_gf_event(odm->parentscene->compositor->filter, &evt, GF_FALSE, GF_TRUE); + + gf_scene_remove_object(odm->parentscene, odm, do_remove); + if (odm->subscene) gf_scene_del(odm->subscene); + gf_odm_del(odm); + return; + } + + /*this is the scene root OD (may be a remote OD ..) */ + if (odm->subscene) { + GF_Event evt; + + evt.type = GF_EVENT_CONNECT; + evt.connect.is_connected = GF_FALSE; + gf_sc_send_event(compositor, &evt); + gf_scene_del(odm->subscene); + } + + /*delete the ODMan*/ + gf_odm_del(odm); +} + +static Bool gf_odm_should_auto_select(GF_ObjectManager *odm) +{ + u32 i, count; + if (odm->type == GF_STREAM_SCENE) return GF_TRUE; + //TODO- detect image media to start playback right away + //if (odm->type == GF_STREAM_VISUAL) return GF_TRUE; + + if (odm->parentscene && !odm->parentscene->is_dynamic_scene) { + return GF_TRUE; + } + + if (odm->parentscene && odm->parentscene->root_od->addon) { + if (odm->parentscene->root_od->addon->addon_type == GF_ADDON_TYPE_MAIN) + return GF_FALSE; + } + + count = gf_list_count(odm->parentscene->resources); + for (i=0; iparentscene->resources, i); + if (an_odm==odm) continue; + if (an_odm->type != odm->type) continue; + //same type - if the first one has been autumatically activated, do not activate this one + if (an_odm->state == GF_ODM_STATE_PLAY) return GF_FALSE; + } + return GF_TRUE; +} + + +void gf_odm_setup_remote_object(GF_ObjectManager *odm, GF_SceneNamespace *parent_ns, char *remote_url) +{ + char *parent_url = NULL; + if (!remote_url) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[ODM%d] No URL specified for remote object - ignoring object setup\n", odm->ID)); + return; + } + + if (!odm->scene_ns) { + if (odm->flags & GF_ODM_DESTROYED) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[ODM%d] Object has been scheduled for destruction - ignoring object setup\n", odm->ID)); + return; + } + + odm->scene_ns = parent_ns ? parent_ns : odm->parentscene->root_od->scene_ns; + if (odm->scene_ns) + odm->scene_ns->nb_odm_users++; + } + + /*store original OD ID */ + if (!odm->media_current_time) + odm->media_current_time = odm->ID; + + //detach it + odm->scene_ns = NULL; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] Object redirection to %s (MO %08x)\n", odm->ID, remote_url, odm->mo)); + + /*if object is a scene, create the inline before connecting the object. + This is needed in order to register the nodes using the resource for event + propagation (stored at the inline level) + */ + if (odm->mo && (odm->mo->type==GF_MEDIA_OBJECT_SCENE)) { + odm->subscene = gf_scene_new(NULL, odm->parentscene); + odm->subscene->root_od = odm; + //scenes are by default dynamic + odm->subscene->is_dynamic_scene = GF_TRUE; + } + parent_url = parent_ns ? parent_ns->url : NULL; + if (parent_url && !strnicmp(parent_url, "views://", 8)) + parent_url = NULL; + + //make sure we don't have an ID before attempting to connect + if (odm->ID == GF_MEDIA_EXTERNAL_ID) { + odm->ID = 0; + } + odm->flags |= GF_ODM_NOT_IN_OD_STREAM; + odm->ServiceID = 0; + gf_scene_ns_connect_object(odm->subscene ? odm->subscene : odm->parentscene, odm, remote_url, parent_url); +} + +GF_EXPORT +void gf_odm_setup_object(GF_ObjectManager *odm, GF_SceneNamespace *parent_ns, GF_FilterPid *for_pid) +{ + GF_Err e; + + if (!odm->scene_ns) { + if (odm->flags & GF_ODM_DESTROYED) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[ODM%d] Object has been scheduled for destruction - ignoring object setup\n", odm->ID)); + return; + } + odm->scene_ns = parent_ns; + odm->scene_ns->nb_odm_users++; + } + + /*restore OD ID */ + if (odm->media_current_time) { + odm->ID = odm->media_current_time; + odm->media_current_time = 0; + odm->flags |= GF_ODM_REMOTE_OD; + } + + if (odm->scene_ns->owner && (odm->scene_ns->owner->flags & GF_ODM_INHERIT_TIMELINE)) { + odm->flags |= GF_ODM_INHERIT_TIMELINE; + } + + /*empty object, use a dynamic scene*/ + if (! odm->pid && odm->subscene) { + assert(odm->subscene->root_od==odm); + odm->subscene->is_dynamic_scene = GF_TRUE; + } else if (odm->pid) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[Terminal] Setting up object streams\n")); + + e = gf_odm_setup_pid(odm, for_pid); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("Service %s PID %s Setup Failure: %s", odm->scene_ns->url, gf_filter_pid_get_name(for_pid ? for_pid : odm->pid), gf_error_to_string(e) )); + } + } + + if (odm->pid && !odm->buffer_playout_us) { + GF_FilterEvent evt; + const GF_PropertyValue *prop; + u32 tsdepth=0; + GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; + + odm->buffer_playout_us = scene->compositor->buf * 1000; + odm->buffer_min_us = scene->compositor->rbuf * 1000; + odm->buffer_max_us = scene->compositor->mbuf * 1000; + + //check the same on the pid + prop = gf_filter_pid_get_property_str(for_pid ? for_pid : odm->pid, "BufferLength"); + if (prop) odm->buffer_playout_us = prop->value.uint; + prop = gf_filter_pid_get_property_str(for_pid ? for_pid : odm->pid, "RebufferLength"); + if (prop) odm->buffer_min_us = prop->value.uint; + prop = gf_filter_pid_get_property_str(for_pid ? for_pid : odm->pid, "BufferMaxOccupancy"); + if (prop) odm->buffer_max_us = prop->value.uint; + + prop = gf_filter_pid_get_property(for_pid ? for_pid : odm->pid, GF_PROP_PID_TIMESHIFT_DEPTH); + if (prop && prop->value.frac.den) { + tsdepth = (u32) ( ((u64)prop->value.frac.num) * 1000 / prop->value.frac.den); + } + gf_odm_set_timeshift_depth(odm, tsdepth); + + if (odm->buffer_playout_us > odm->buffer_max_us) odm->buffer_max_us = odm->buffer_playout_us; + + prop = gf_filter_pid_get_property(for_pid ? for_pid : odm->pid, GF_PROP_PID_FILE_CACHED); + if (prop) { + odm->buffer_playout_us = odm->buffer_max_us = 1000; + odm->buffer_min_us = 0; + } + GF_FEVT_INIT(evt, GF_FEVT_BUFFER_REQ, for_pid ? for_pid : odm->pid); + evt.buffer_req.max_buffer_us = odm->buffer_max_us; + evt.buffer_req.min_playout_us = odm->buffer_min_us; + evt.buffer_req.max_playout_us = odm->buffer_playout_us; + gf_filter_pid_send_event(NULL, &evt); + + if (odm->buffer_min_us>evt.buffer_req.max_playout_us) + odm->buffer_min_us = 0; + } + + + /*setup mediaobject info except for top-level OD*/ + if (odm->parentscene) { + GF_Event evt; + + //this may result in an attempt to lock the compositor, so release the net MX before + if (!odm->scalable_addon) { + gf_scene_setup_object(odm->parentscene, odm); + } + + /*setup node decoder*/ +#if FILTER_FIXME + if (odm->mo && odm->codec && odm->codec->decio && (odm->codec->decio->InterfaceType==GF_NODE_DECODER_INTERFACE) ) { + GF_NodeDecoder *ndec = (GF_NodeDecoder *) odm->codec->decio; + GF_Node *n = gf_event_target_get_node(gf_mo_event_target_get(odm->mo, 0)); + if (n) ndec->AttachNode(ndec, n); + + /*not clear in the spec how the streams attached to AFX are started - default to "right now"*/ + gf_odm_start(odm); + } +#endif + if (odm->pid && (odm->pid==for_pid)) { + evt.type = GF_EVENT_CONNECT; + evt.connect.is_connected = GF_TRUE; + gf_filter_forward_gf_event(odm->parentscene->compositor->filter, &evt, GF_FALSE, GF_TRUE); + } + } else if (odm->pid==for_pid) { + /*othewise send a connect ack for top level*/ + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM] Root object connected (%s) !\n", odm->scene_ns->url)); + if (odm->subscene) { + GF_Event evt; + evt.type = GF_EVENT_CONNECT; + evt.connect.is_connected = GF_TRUE; + gf_sc_send_event(odm->subscene->compositor, &evt); + } + } + + + /* start object*/ + /*object is already started (new PID was inserted for this object)*/ + if (odm->state==GF_ODM_STATE_PLAY) { + if (odm->pid==for_pid) + odm->state = GF_ODM_STATE_STOP; + gf_odm_start(odm); + } + /*object is the root, always start*/ + else if (!odm->parentscene) { + assert(odm->subscene && (odm->subscene->root_od==odm)); + odm->flags &= ~GF_ODM_NOT_SETUP; + gf_odm_start(odm); + } + /*object is a pure OCR object - connect*/ + else if (odm->type==GF_STREAM_OCR) { + odm->flags &= ~GF_ODM_NOT_SETUP; + gf_odm_start(odm); + } + /*if the object is inserted from a broadcast, start it if not already done. This covers cases where the scene (BIFS, LASeR) and + the media (images) are both carrouseled and the carrousels are interleaved. If we wait for the scene to trigger a PLAY, we will likely + have to wait for an entire image carousel period to start filling the buffers, which is sub-optimal + we also force a prefetch for object declared outside the OD stream to make sure we don't loose any data before object declaration and play + as can be the case with MPEG2 TS (first video packet right after the PMT) - this should be refined*/ + else if ( ((odm->flags & GF_ODM_NO_TIME_CTRL) || (odm->flags & GF_ODM_NOT_IN_OD_STREAM)) && gf_odm_should_auto_select(odm) && (odm->parentscene->selected_service_id == odm->ServiceID)) { + Bool force_play = GF_FALSE; + + if (odm->state==GF_ODM_STATE_STOP) { + odm->flags |= GF_ODM_PREFETCH; + force_play = GF_TRUE; + } + + if (force_play) { + odm->flags |= GF_ODM_INITIAL_BROADCAST_PLAY; + odm->parentscene->selected_service_id = odm->ServiceID; + + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[ODM%d] Inserted from input service %s - forcing play\n", odm->ID, odm->scene_ns->url)); + odm->flags &= ~GF_ODM_NOT_SETUP; + gf_odm_start(odm); + } + } + + odm->flags &= ~GF_ODM_NOT_SETUP; + + /*for objects inserted by user (subs & co), auto select*/ + if (odm->parentscene && odm->parentscene->is_dynamic_scene + && (odm->ID==GF_MEDIA_EXTERNAL_ID) + && (odm->flags & GF_ODM_REMOTE_OD) + ) { + GF_Event evt; + + if (odm->addon) { + Bool role_set = 0; + + if (odm->addon->addon_type >= GF_ADDON_TYPE_MAIN) return; + + //check role - for now look into URL, we need to inspect DASH roles + if (odm->mo && odm->mo->URLs.count && odm->mo->URLs.vals[0].url) { + char *sep = strchr(odm->mo->URLs.vals[0].url, '?'); + if (sep && strstr(sep, "role=main")) { + odm->addon->addon_type = GF_ADDON_TYPE_MAIN; + role_set = 1; + } + } + + if (!role_set) { + const GF_PropertyValue *prop = gf_filter_pid_get_property(for_pid ? for_pid : odm->pid, GF_PROP_PID_ROLE); + if (prop && prop->value.string && !strcmp(prop->value.string, "main")) { + odm->addon->addon_type = GF_ADDON_TYPE_MAIN; + } + } + + + if (odm->addon->addon_type == GF_ADDON_TYPE_ADDITIONAL) { + gf_scene_select_object(odm->parentscene, odm); + } + return; + } + + if (!odm->parentscene) { + evt.type = GF_EVENT_STREAMLIST; + gf_sc_send_event(odm->parentscene->compositor, &evt); + return; + } + } else if (odm->parentscene) { + const GF_PropertyValue *prop = gf_filter_pid_get_property(for_pid ? for_pid : odm->pid, GF_PROP_PID_ROLE); + if (prop && prop->value.string && !strncmp(prop->value.string, "ambi", 4)) { + odm->ambi_ch_id = atoi(prop->value.string + 4); + if (odm->ambi_ch_id > odm->parentscene->ambisonic_type) + odm->parentscene->ambisonic_type = odm->ambi_ch_id; + } + } +} + +/*setup channel, clock and query caps*/ +GF_EXPORT +GF_Err gf_odm_setup_pid(GF_ObjectManager *odm, GF_FilterPid *pid) +{ + GF_Clock *ck; + GF_List *ck_namespace; + s8 flag; + u32 clockID; + GF_Scene *scene; + Bool clock_inherited = GF_TRUE; + const GF_PropertyValue *prop; + u32 OD_OCR_ID=0; + u32 es_id=0; + + /*find the clock for this new channel*/ + ck = NULL; + flag = (s8) -1; + if (!pid) pid = odm->pid; + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode() && pid) { + GF_FilterPidStatistics stats; + gf_filter_pid_get_statistics(pid, &stats, GF_STATS_DECODER_SOURCE); + gf_filter_pid_get_packet_count(pid); + } +#endif + + if (odm->ck) { + ck = odm->ck; + goto clock_setup; + } + + /*sync reference*/ + if (odm->sync_ref && odm->sync_ref->odm) { + ck = odm->sync_ref->odm->ck; + goto clock_setup; + } + /*timeline override*/ + if (odm->flags & GF_ODM_INHERIT_TIMELINE) { + ck = odm->parentscene->root_od->ck; + + /**/ + if (!ck) { + GF_ObjectManager *odm_par = odm->parentscene->root_od->parentscene->root_od; + while (odm_par) { + ck = odm_par->ck; + + if (ck) break; + + odm_par = (odm_par->parentscene && odm_par->parentscene->root_od->parentscene ) ? odm_par->parentscene->root_od->parentscene->root_od : NULL; + } + } + if (ck) + goto clock_setup; + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[ODM] Cannot inherit timeline from parent scene for scene %s - new clock will be created\n", odm->scene_ns->url)); + } + + /*get clocks namespace (eg, parent scene)*/ + scene = odm->subscene ? odm->subscene : odm->parentscene; + if (!scene) return GF_BAD_PARAM; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CLOCK_ID); + if (prop) OD_OCR_ID = prop->value.uint; + + ck_namespace = odm->scene_ns->clocks; + odm->set_speed = odm->scene_ns->set_speed; + + /*little trick for non-OD addressing: if object is a remote one, and service owner already has clocks, + override OCR. This will solve addressing like file.avi#audio and file.avi#video*/ + if (!OD_OCR_ID && (odm->flags & GF_ODM_REMOTE_OD) && (gf_list_count(ck_namespace)==1) ) { + ck = (GF_Clock*)gf_list_get(ck_namespace, 0); + OD_OCR_ID = ck->clock_id; + } + /*for dynamic scene, force all streams to be sync on main OD stream (one timeline, no need to reload ressources)*/ + else if (odm->parentscene && odm->parentscene->is_dynamic_scene && !odm->subscene) { + GF_ObjectManager *parent_od = odm->parentscene->root_od; + if (parent_od->scene_ns && parent_od->scene_ns->clocks && (gf_list_count(parent_od->scene_ns->clocks)==1)) { + ck = (GF_Clock*)gf_list_get(parent_od->scene_ns->clocks, 0); + if (!odm->ServiceID || (odm->ServiceID==ck->service_id)) { + goto clock_setup; + } + } + } + + /*do we have an OCR specified*/ + clockID = OD_OCR_ID; + /*if OCR stream force self-synchro !!*/ + if (odm->type == GF_STREAM_OCR) clockID = odm->ID; + if (!clockID) { + if (odm->ID == GF_MEDIA_EXTERNAL_ID) { + clockID = (u32) (intptr_t) odm->scene_ns; + } else { + clockID = odm->ID; + } + } + + /*override clock dependencies if specified*/ + if (scene->compositor->sclock) { + GF_Scene *parent = gf_scene_get_root_scene(scene); + clockID = scene->root_od->ck->clock_id; + ck_namespace = parent->root_od->scene_ns->clocks; + assert(ck_namespace); + } + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ESID); + if (!prop) prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + if (prop) es_id = prop->value.uint; + + ck = gf_clock_attach(ck_namespace, scene, clockID, es_id, flag); + if (!ck) return GF_OUT_OF_MEM; + + ck->service_id = odm->ServiceID; + clock_inherited = GF_FALSE; + + if (es_id==ck->clock_id) + odm->owns_clock = GF_TRUE; + + if (scene->root_od->subscene && scene->root_od->subscene->is_dynamic_scene && !scene->root_od->ck) + scene->root_od->ck = ck; + +clock_setup: + assert(ck); + odm->ck = ck; + odm->clock_inherited = clock_inherited; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_DELAY); + if (prop) { + odm->delay = prop->value.sint; + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (prop) { + odm->delay *= 1000; + odm->delay /= (s32) prop->value.uint; + } + } + + gf_odm_update_duration(odm, pid); + /*regular setup*/ + return GF_OK; +} + +void gf_odm_update_duration(GF_ObjectManager *odm, GF_FilterPid *pid) +{ + u64 dur=0; + GF_PropertyEntry *pe = NULL; + const GF_PropertyValue *prop; + prop = gf_filter_pid_get_info(pid, GF_PROP_PID_DURATION, &pe); + if (prop) { + dur = prop->value.lfrac.num; + dur *= 1000; + if (prop->value.lfrac.den) dur /= prop->value.lfrac.den; + } + gf_filter_release_property(pe); + + if ((u32) dur > odm->duration) { + odm->duration = (u32) dur; + /*update scene duration*/ + gf_scene_set_duration(odm->subscene ? odm->subscene : odm->parentscene); + } +} + +/*this is the tricky part: make sure the net is locked before doing anything since an async service +reply could destroy the object we're queuing for play*/ +void gf_odm_start(GF_ObjectManager *odm) +{ + /*object is not started - issue channel setup requests*/ + if (!odm->state) { + /*look for a given segment name to play*/ + if (odm->subscene) { + char *url, *frag=NULL; + assert(odm->subscene->root_od==odm); + + url = (odm->mo && odm->mo->URLs.count) ? odm->mo->URLs.vals[0].url : odm->scene_ns->url; + frag = strrchr(url, '#'); + + if (frag) { + GF_Segment *seg = gf_odm_find_segment(odm, frag+1); + if (seg) { + odm->media_start_time = (u64) ((s64) seg->startTime*1000); + odm->media_stop_time = (u64) ((s64) (seg->startTime + seg->Duration)*1000); + } + } + } + } + gf_odm_play(odm); +} + +void gf_odm_play(GF_ObjectManager *odm) +{ + u64 range_end; + Bool media_control_paused = 0; + Bool start_range_is_clock = 0; + Double ck_time; + GF_Clock *clock = odm->ck; + GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; +#ifndef GPAC_DISABLE_VRML + MediaControlStack *ctrl; +#endif + GF_FilterEvent com; + GF_Clock *parent_ck = NULL; + + if (!scene) return; + + if (odm->mo && odm->mo->pck && !(odm->flags & GF_ODM_PREFETCH)) { + /*reset*/ + gf_filter_pck_unref(odm->mo->pck); + odm->mo->pck = NULL; + } + + if (odm->parentscene) { + parent_ck = gf_odm_get_media_clock(odm->parentscene->root_od); + if (!gf_odm_shares_clock(odm, parent_ck)) parent_ck = NULL; + } + + //PID not yet attached, mark as state_play and wait for error or OK + if (!odm->pid ) { + odm->state = GF_ODM_STATE_PLAY; + return; + } + + if ( !(odm->flags & GF_ODM_INHERIT_TIMELINE) && odm->owns_clock ) { + gf_clock_reset(clock); + } + + range_end = odm->media_stop_time; + + /*send play command*/ + GF_FEVT_INIT(com, GF_FEVT_PLAY, odm->pid) + + if (odm->flags & GF_ODM_INITIAL_BROADCAST_PLAY) { + odm->flags &= ~GF_ODM_INITIAL_BROADCAST_PLAY; + com.play.initial_broadcast_play = 1; + } + + /*play from requested time (seeking or non-mpeg4 media control)*/ + if (odm->media_start_time && !clock->clock_init) { + ck_time = (Double) (s64) odm->media_start_time; + ck_time /= 1000; + } + else if (odm->parentscene && odm->parentscene->root_od->media_start_time && !clock->clock_init) { + ck_time = (Double) (s64) odm->parentscene->root_od->media_start_time; + ck_time /= 1000; + } + /*play from current time*/ + else { + ck_time = gf_clock_media_time(clock); + ck_time /= 1000; + start_range_is_clock = 1; + } + + /*handle initial start - MPEG-4 is a bit annoying here, streams are not started through OD but through + scene nodes. If the stream runs on the BIFS/OD clock, the clock is already started at this point and we're + sure to get at least a one-frame delay in PLAY, so just remove it - note we're generous but this shouldn't hurt*/ + if (ck_time<=0.5) ck_time = 0; + + + /*adjust time for addons*/ + if (odm->parentscene && odm->parentscene->root_od->addon) { + com.play.initial_broadcast_play = 0; + //addon timing is resolved against timestamps, not media time + if (start_range_is_clock) { + if (!gf_clock_is_started(clock)) { + ck_time = (Double) odm->parentscene->root_od->addon->media_pts; + ck_time /= 90000; + } else { + ck_time = gf_clock_time(clock); + ck_time /= 1000; + } + } + ck_time = gf_scene_adjust_time_for_addon(odm->parentscene->root_od->addon, ck_time, &com.play.timestamp_based); + //we are having a play request for an addon without the main content being active - we no longer have timestamp info from the main content + if (!clock->clock_init && com.play.timestamp_based) + com.play.timestamp_based = 2; + + if (ck_time<0) + ck_time=0; + + if (odm->scalable_addon) { + //this is a scalable extension to an object in the parent scene + gf_scene_select_scalable_addon(odm->parentscene->root_od->parentscene, odm); + } + } + + com.play.start_range = ck_time; + + if (range_end) { + com.play.end_range = (s64) range_end / 1000.0; + } else { + if (!odm->subscene && odm->parentscene && gf_odm_shares_clock(odm->parentscene->root_od, clock) + && (odm->parentscene->root_od->media_stop_time != odm->parentscene->root_od->duration) + ) { + com.play.end_range = (s64) odm->parentscene->root_od->media_stop_time / 1000.0; + } else { + com.play.end_range = 0; + } + } + + com.play.speed = clock->speed; + if (ABS(com.play.speed)>scene->compositor->max_vspeed) + com.play.drop_non_ref = GF_TRUE; + +#ifndef GPAC_DISABLE_VRML + ctrl = parent_ck ? parent_ck->mc : gf_odm_get_mediacontrol(odm); + /*override range and speed with MC*/ + if (ctrl && !odm->disable_buffer_at_next_play) { + //for addon, use current clock settings (media control is ignored) + if (!odm->parentscene || !odm->parentscene->root_od->addon) { + //this is fake timeshift, eg we are playing a VoD as a timeshift service: stop and start times have already been adjusted + if (ctrl->control->mediaStopTime<0 && !odm->timeshift_depth) { + } else { + MC_GetRange(ctrl, &com.play.start_range, &com.play.end_range); + } + } + + com.play.speed = FIX2FLT(ctrl->control->mediaSpeed); + if (ABS(com.play.speed)>scene->compositor->max_vspeed) + com.play.drop_non_ref = GF_TRUE; + /*if the channel doesn't control the clock, jump to current time in the controled range, not just the beginning*/ + if ((ctrl->stream != odm->mo) && (ck_time>com.play.start_range) && (com.play.end_range>com.play.start_range) + && (ck_timepaused) media_control_paused = 1; + + gf_clock_set_speed(clock, ctrl->control->mediaSpeed); + if (odm->mo) odm->mo->speed = ctrl->control->mediaSpeed; + +#if 0 + /*if requested seek time AND media control, adjust start range to current play time*/ + if ((com.play.speed>=0) && odm->media_start_time) { + if ((com.play.start_range>=0) && (com.play.end_range>com.play.start_range)) { + if (ctrl->control->loop) { + Double active_dur = com.play.end_range - com.play.start_range; + while (ck_time>active_dur) ck_time -= active_dur; + } else { + ck_time = 0; + //com.play.start_range = com.play.end_range; + } + } + com.play.start_range += ck_time; + } +#endif + + } +#endif //GPAC_DISABLE_VRML + + /*full object playback*/ + if (com.play.end_range<=0) { + if (com.play.speed<0) { + odm->media_stop_time = 0; + } else { + odm->media_stop_time = odm->subscene ? 0 : odm->duration; + } + } else { + odm->media_stop_time = (u64) -1; + if (com.play.end_range < FLT_MAX) { + /*segment playback - since our timing is in ms whereas segment ranges are double precision, + make sure we have a LARGER range in ms, otherwise media sensors won't deactivate properly*/ + odm->media_stop_time = (u64) ceil(1000 * com.play.end_range); + } + } + + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[ODM%d %s] PID %s: At OTB %u requesting PLAY from %g to %g (clock init %d) - speed %g\n", odm->ID, odm->scene_ns->url, gf_filter_pid_get_name(odm->pid), gf_clock_time(clock), com.play.start_range, com.play.end_range, clock->clock_init, com.play.speed)); + + + if (odm->state != GF_ODM_STATE_PLAY) { + odm->state = GF_ODM_STATE_PLAY; + odm->nb_buffering ++; + scene->nb_buffering++; + //start buffering + gf_clock_buffer_on(odm->ck); + odm->has_seen_eos = GF_FALSE; + + if ((odm->type!=GF_STREAM_VISUAL) && (odm->type!=GF_STREAM_AUDIO)) { + if (gf_list_find(scene->compositor->systems_pids, odm->pid)<0) + gf_list_add(scene->compositor->systems_pids, odm->pid); + } + + gf_filter_pid_send_event(odm->pid, &com); + } + if (odm->extra_pids) { + u32 k, count = gf_list_count(odm->extra_pids); + for (k=0; kextra_pids, k); + if (xpid->state != GF_ODM_STATE_PLAY) { + xpid->state = GF_ODM_STATE_PLAY; + com.base.on_pid = xpid->pid; + odm->has_seen_eos = GF_FALSE; + odm->nb_buffering ++; + scene->nb_buffering++; + //start buffering + gf_clock_buffer_on(odm->ck); + + if ((odm->type!=GF_STREAM_VISUAL) && (odm->type!=GF_STREAM_AUDIO)) { + if (gf_list_find(scene->compositor->systems_pids, xpid->pid)<0) + gf_list_add(scene->compositor->systems_pids, xpid->pid); + } + + gf_filter_pid_send_event(xpid->pid, &com); + } + } + } + + if (odm->parentscene) { + if (odm->parentscene->root_od->addon) { + odm->parentscene->root_od->addon->started = 1; + } + if (odm->parentscene->first_frame_pause_type) { + media_control_paused = GF_TRUE; + } + } else if (odm->subscene && odm->subscene->first_frame_pause_type) { + media_control_paused = GF_TRUE; + } + + gf_odm_service_media_event(odm, GF_EVENT_MEDIA_LOAD_START); + gf_odm_service_media_event(odm, GF_EVENT_MEDIA_TIME_UPDATE); + + odm->disable_buffer_at_next_play = GF_FALSE; + + if (odm->flags & GF_ODM_PAUSE_QUEUED) { + odm->flags &= ~GF_ODM_PAUSE_QUEUED; + media_control_paused = 1; + } + + if (media_control_paused) { + gf_odm_pause(odm); + } +} + +void gf_odm_stop(GF_ObjectManager *odm, Bool force_close) +{ + u32 i; + GF_ODMExtraPid *xpid; +#ifndef GPAC_DISABLE_VRML + MediaControlStack *ctrl; + MediaSensorStack *media_sens; +#endif + GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; + GF_FilterEvent com; + + odm->flags &= ~GF_ODM_PREFETCH; + + //root ODs of dynamic scene may not have seen play/pause request + if (!odm->state && !odm->scalable_addon && (!odm->subscene || !odm->subscene->is_dynamic_scene) ) return; + + //PID not yet attached, mark as state_stop and wait for error or OK + if (!odm->pid ) { + odm->state = GF_ODM_STATE_STOP; + return; + } + + + if (force_close && odm->mo) odm->mo->flags |= GF_MO_DISPLAY_REMOVE; + /*stop codecs*/ + if (odm->subscene) { + GF_ObjectManager *sub_odm; + + /*stops all resources of the subscene as well*/ + i=0; + while ((sub_odm=(GF_ObjectManager *)gf_list_enum(odm->subscene->resources, &i))) { + gf_odm_stop(sub_odm, force_close); + } + } + + /*send stop command*/ + odm->has_seen_eos = GF_FALSE; + odm->state = GF_ODM_STATE_STOP; + GF_FEVT_INIT(com, GF_FEVT_STOP, odm->pid) + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[ODM%d %s] PID %s At OTB %u requesting STOP\n", odm->ID, odm->scene_ns->url, gf_filter_pid_get_name(odm->pid), odm->ck ? gf_clock_time(odm->ck) : 0 )); + + gf_filter_pid_send_event(odm->pid, &com); + gf_list_del_item(scene->compositor->systems_pids, odm->pid); + i=0; + while ((xpid = (GF_ODMExtraPid*)gf_list_enum(odm->extra_pids, &i)) ) { + xpid->has_seen_eos = GF_FALSE; + xpid->state = GF_ODM_STATE_STOP; + com.base.on_pid = xpid->pid; + gf_list_del_item(scene->compositor->systems_pids, xpid->pid); + gf_filter_pid_send_event(xpid->pid, &com); + + } + + if (odm->parentscene && odm->parentscene->root_od->addon) { + odm->parentscene->root_od->addon->started = 0; + } + if (odm->nb_buffering) { + assert(scene->nb_buffering>=odm->nb_buffering); + scene->nb_buffering -= odm->nb_buffering; + while (odm->nb_buffering && odm->ck) { + gf_clock_buffer_off(odm->ck); + odm->nb_buffering --; + } + if (!scene->nb_buffering) { + gf_scene_buffering_info(scene); + } + } + + gf_odm_service_media_event(odm, GF_EVENT_ABORT); + + /*stops clock if this is a scene stop*/ + if (!(odm->flags & GF_ODM_INHERIT_TIMELINE) && odm->subscene && odm->ck && odm->owns_clock) { + gf_clock_reset(odm->ck); + } + odm->media_current_time = 0; + +#ifndef GPAC_DISABLE_VRML + /*reset media sensor(s)*/ + i = 0; + while ((media_sens = (MediaSensorStack *)gf_list_enum(odm->ms_stack, &i))) { + MS_Stop(media_sens); + } + /*reset media control state*/ + ctrl = gf_odm_get_mediacontrol(odm); + if (ctrl) ctrl->current_seg = 0; +#endif + +} + +void gf_odm_on_eos(GF_ObjectManager *odm, GF_FilterPid *pid) +{ + u32 i, count; + Bool all_done = GF_TRUE; +#ifndef GPAC_DISABLE_VRML + if (gf_odm_check_segment_switch(odm)) return; +#endif + + if (odm->pid==pid) { + odm->has_seen_eos = GF_TRUE; + } + if (!odm->has_seen_eos) all_done = GF_FALSE; + + count = gf_list_count(odm->extra_pids); + for (i=0; iextra_pids, i); + if (xpid->pid == pid) { + xpid->has_seen_eos = GF_TRUE; + } + if (!xpid->has_seen_eos) all_done = GF_FALSE; + } + if (!all_done) return; + + if (odm->addon && odm->addon->is_splicing) + odm->addon->is_over = 1; + if (odm->parentscene && odm->parentscene->root_od->addon && odm->parentscene->root_od->addon->is_splicing) + odm->parentscene->root_od->addon->is_over = 1; + + if (odm->ck->has_seen_eos) return; + + odm->ck->has_seen_eos = 1; + gf_odm_check_buffering(odm, pid); + +#ifndef GPAC_DISABLE_VRML + //check for scene restart upon end of stream + if (odm->subscene) { + gf_scene_mpeg4_inline_check_restart(odm->subscene); + } +#endif + + gf_odm_service_media_event(odm, GF_EVENT_MEDIA_LOAD_DONE); + //a little optimization here: for scene with no associated resources (no audio, video, images), unload + //the filter chain once the scene is loaded + //TODO: further optimize to disconnect scenes with static resources (images, logo, ...) + if (odm->subscene && !gf_list_count(odm->subscene->resources)) { + Bool skip = GF_FALSE; + GF_PropertyEntry *pe=NULL; + const GF_PropertyValue *p = gf_filter_pid_get_info(odm->pid, GF_PROP_PID_KEEP_AFTER_EOS, &pe); + if (p && p->value.boolean) skip = GF_TRUE; + gf_filter_release_property(pe); + + //if PID disabled auto-remove, do not destroy filter chain + if (!skip) { + GF_FilterEvent fevt; + + GF_FEVT_INIT(fevt, GF_FEVT_RESET_SCENE, odm->pid); + fevt.attach_scene.object_manager = odm; + gf_filter_pid_exec_event(odm->pid, &fevt); + + gf_filter_pid_set_udta(odm->pid, NULL); + odm->pid = NULL; + for (i=0; iextra_pids, i); + gf_filter_pid_set_udta(xpid->pid, NULL); + xpid->pid = NULL; + } + gf_filter_remove_src(odm->subscene->compositor->filter, odm->scene_ns->source_filter); + odm->scene_ns->source_filter = NULL; + } + } + + gf_odm_signal_eos_reached(odm); + +} + +void gf_odm_signal_eos_reached(GF_ObjectManager *odm) +{ + if (odm->parentscene && !gf_scene_is_root(odm->parentscene) ) { + GF_ObjectManager *root = odm->parentscene->root_od; + Bool is_over = 0; + + if (!gf_scene_check_clocks(root->scene_ns, root->subscene, 0)) return; + if (root->subscene->is_dynamic_scene) + is_over = 1; + else + is_over = gf_sc_is_over(odm->parentscene->compositor, root->subscene->graph); + + if (is_over) { + gf_odm_service_media_event(root, GF_EVENT_MEDIA_ENDED); + } + } else { + GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; + if (scene && odm->parentscene && gf_sc_check_end_of_scene(scene->compositor, 0)) { + GF_Event evt; + evt.type = GF_EVENT_EOS; + gf_sc_send_event(odm->parentscene->compositor, &evt); + } + } +} + + +void gf_odm_set_timeshift_depth(GF_ObjectManager *odm, u32 stream_timeshift) +{ + GF_Scene *scene; + if (odm->timeshift_depth == stream_timeshift) + return; + + odm->timeshift_depth = stream_timeshift; + scene = odm->subscene ? odm->subscene : odm->parentscene; + + /*update scene duration*/ + gf_scene_set_timeshift_depth(scene); +} + + +GF_Clock *gf_odm_get_media_clock(GF_ObjectManager *odm) +{ + while (odm->lower_layer_odm) { + odm = odm->lower_layer_odm; + } + if (odm->ck) return odm->ck; + return NULL; +} + + +Bool gf_odm_shares_clock(GF_ObjectManager *odm, GF_Clock *ck) +{ + if (odm->ck == ck) return GF_TRUE; + return GF_FALSE; +} + + +void gf_odm_pause(GF_ObjectManager *odm) +{ + u32 i; +#ifndef GPAC_DISABLE_VRML + MediaSensorStack *media_sens; +#endif + GF_ODMExtraPid *xpid; + GF_FilterEvent com; + GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; + //postpone until the PLAY request + if (odm->state != GF_ODM_STATE_PLAY) { + odm->flags |= GF_ODM_PAUSE_QUEUED; + return; + } + + if (odm->flags & GF_ODM_PAUSED) return; + odm->flags |= GF_ODM_PAUSED; + + //cleanup - we need to enter in stop state for broadcast modes + if (odm->flags & GF_ODM_NO_TIME_CTRL) return; + + scene = gf_scene_get_root_scene(scene); + + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[ODM%d %s] PID %s: At OTB %u requesting PAUSE (clock init %d)\n", odm->ID, odm->scene_ns->url, gf_filter_pid_get_name(odm->pid), gf_clock_time(odm->ck), odm->ck->clock_init )); + + GF_FEVT_INIT(com, GF_FEVT_PAUSE, odm->pid); + gf_clock_pause(odm->ck); + + if ((odm->state == GF_ODM_STATE_PLAY) && (scene->first_frame_pause_type!=2)) { + gf_filter_pid_send_event(odm->pid, &com); + } + + i=0; + while ((xpid = (GF_ODMExtraPid*)gf_list_enum(odm->extra_pids, &i))) { + gf_clock_pause(odm->ck); + if (xpid->state != GF_ODM_STATE_PLAY) continue; + + //if we are in dump mode, the clocks are paused (step-by-step render), but we don't send the pause commands to + //the network ! + if (scene->first_frame_pause_type!=2) { + com.base.on_pid = xpid->pid; + gf_filter_pid_send_event(xpid->pid, &com); + } + } + + //if we are in dump mode, only the clocks are paused (step-by-step render), the media object is still in play state + if (scene->first_frame_pause_type==2) { + return; + } + +#ifndef GPAC_DISABLE_VRML + /*mediaSensor shall generate isActive false when paused*/ + i=0; + while ((media_sens = (MediaSensorStack *)gf_list_enum(odm->ms_stack, &i)) ) { + if (media_sens->sensor->isActive) { + media_sens->sensor->isActive = 0; + gf_node_event_out((GF_Node *) media_sens->sensor, 4/*"isActive"*/); + } + } +#endif + +} + +void gf_odm_resume(GF_ObjectManager *odm) +{ + u32 i; + +#ifndef GPAC_DISABLE_VRML + MediaSensorStack *media_sens; + MediaControlStack *ctrl; +#endif + GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; + GF_ODMExtraPid *xpid; + GF_FilterEvent com; + + if (odm->flags & GF_ODM_PAUSE_QUEUED) { + odm->flags &= ~GF_ODM_PAUSE_QUEUED; + return; + } + + if (!(odm->flags & GF_ODM_PAUSED)) return; + odm->flags &= ~GF_ODM_PAUSED; + + if (odm->flags & GF_ODM_NO_TIME_CTRL) return; + +#ifndef GPAC_DISABLE_VRML + ctrl = gf_odm_get_mediacontrol(odm); +#endif + + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[ODM%d %s] CH%d: At OTB %u requesting RESUME (clock init %d)\n", odm->ID, odm->scene_ns->url, gf_filter_pid_get_name(odm->pid), gf_clock_time(odm->ck), odm->ck->clock_init )); + + GF_FEVT_INIT(com, GF_FEVT_RESUME, odm->pid); + com.play.speed = odm->ck->speed; + if (ctrl) com.play.speed = ctrl->control->mediaSpeed; + if (ABS(com.play.speed)>scene->compositor->max_vspeed) + com.play.drop_non_ref = GF_TRUE; + + gf_clock_resume(odm->ck); + if (odm->state == GF_ODM_STATE_PLAY) + gf_filter_pid_send_event(odm->pid, &com); + + i=0; + while ((xpid = (GF_ODMExtraPid*)gf_list_enum(odm->extra_pids, &i)) ) { + gf_clock_resume(odm->ck); + + if (odm->state != GF_ODM_STATE_PLAY) continue; + + com.base.on_pid = xpid->pid; + gf_filter_pid_send_event(odm->pid, &com); + } + +#ifndef GPAC_DISABLE_VRML + /*override speed with MC*/ + if (ctrl) { + gf_clock_set_speed(odm->ck, ctrl->control->mediaSpeed); + } + + /*mediaSensor shall generate isActive TRUE when resumed*/ + i=0; + while ((media_sens = (MediaSensorStack *)gf_list_enum(odm->ms_stack, &i)) ) { + if (!media_sens->sensor->isActive) { + media_sens->sensor->isActive = 1; + gf_node_event_out((GF_Node *) media_sens->sensor, 4/*"isActive"*/); + } + } +#endif +} + +void gf_odm_set_speed(GF_ObjectManager *odm, Fixed speed, Bool adjust_clock_speed) +{ + u32 i; + GF_ODMExtraPid *xpid; + GF_FilterEvent com; + GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; + + if (odm->flags & GF_ODM_NO_TIME_CTRL) return; + if (!odm->pid) return; + + if (adjust_clock_speed) + gf_clock_set_speed(odm->ck, speed); + + GF_FEVT_INIT(com, GF_FEVT_SET_SPEED, odm->pid); + + com.play.speed = FIX2FLT(speed); + if (ABS(com.play.speed)>scene->compositor->max_vspeed) + com.play.drop_non_ref = GF_TRUE; + + gf_filter_pid_send_event(odm->pid, &com); + + i=0; + while ((xpid = (GF_ODMExtraPid*)gf_list_enum(odm->extra_pids, &i)) ) { + + com.play.on_pid = xpid->pid; + gf_filter_pid_send_event(xpid->pid, &com); + } +} + +GF_Segment *gf_odm_find_segment(GF_ObjectManager *odm, char *descName) +{ +#if FILTER_FIXME + GF_Segment *desc; + u32 i = 0; + if (!odm->OD) return NULL; + while ( (desc = (GF_Segment *)gf_list_enum(odm->OD->OCIDescriptors, &i)) ) { + if (desc->tag != GF_ODF_SEGMENT_TAG) continue; + if (!stricmp(desc->SegmentName, descName)) return desc; + } +#endif + return NULL; +} + +#if FILTER_FIXME +static void gf_odm_insert_segment(GF_ObjectManager *odm, GF_Segment *seg, GF_List *list) +{ + /*this reorders segments when inserting into list - I believe this is not compliant*/ +#if 0 + GF_Segment *desc; + u32 i = 0; + while ((desc = gf_list_enum(list, &i))) { + if (desc == seg) return; + if (seg->startTime + seg->Duration <= desc->startTime) { + gf_list_insert(list, seg, i); + return; + } + } +#endif + gf_list_add(list, seg); +} +#endif + +/*add segment descriptor and sort them*/ +void gf_odm_init_segments(GF_ObjectManager *odm, GF_List *list, MFURL *url) +{ +#if FILTER_FIXME + char *str, *sep; + char seg1[1024], seg2[1024], seg_url[4096]; + GF_Segment *first_seg, *last_seg, *seg; + u32 i, j; + + /*browse all URLs*/ + for (i=0; icount; i++) { + if (!url->vals[i].url) continue; + str = strstr(url->vals[i].url, "#"); + if (!str) continue; + str++; + strcpy(seg_url, str); + /*segment closed range*/ + if ((sep = strstr(seg_url, "-")) ) { + strcpy(seg2, sep+1); + sep[0] = 0; + strcpy(seg1, seg_url); + first_seg = gf_odm_find_segment(odm, seg1); + if (!first_seg) continue; + last_seg = gf_odm_find_segment(odm, seg2); + } + /*segment open range*/ + else if ((sep = strstr(seg_url, "+")) ) { + sep[0] = 0; + strcpy(seg1, seg_url); + first_seg = gf_odm_find_segment(odm, seg_url); + if (!first_seg) continue; + last_seg = NULL; + } + /*single segment*/ + else { + first_seg = gf_odm_find_segment(odm, seg_url); + if (!first_seg) continue; + gf_odm_insert_segment(odm, first_seg, list); + continue; + } + /*segment range process*/ + gf_odm_insert_segment(odm, first_seg, list); + j=0; + while ( (seg = (GF_Segment *)gf_list_enum(odm->OD->OCIDescriptors, &j)) ) { + if (seg->tag != GF_ODF_SEGMENT_TAG) continue; + if (seg==first_seg) continue; + if (seg->startTime + seg->Duration <= first_seg->startTime) continue; + /*this also includes last_seg insertion !!*/ + if (last_seg && (seg->startTime + seg->Duration > last_seg->startTime + last_seg->Duration) ) continue; + gf_odm_insert_segment(odm, seg, list); + } + } +#endif +} + +static Bool odm_update_buffer(GF_Scene *scene, GF_ObjectManager *odm, GF_FilterPid *pid, Bool *signal_eob) +{ + u32 timescale; + u64 buffer_duration = gf_filter_pid_query_buffer_duration(pid, GF_TRUE); + if (odm->ck && ! odm->ck->clock_init) { + u64 time; + GF_FilterPacket *pck = gf_filter_pid_get_packet(pid); + if (!pck) return GF_TRUE; + timescale = gf_filter_pck_get_timescale(pck); + + time = gf_filter_pck_get_cts(pck); + if (time==GF_FILTER_NO_TS) time = gf_filter_pck_get_dts(pck); + if (time==GF_FILTER_NO_TS) { + //this usually happens with BT/XMT playback + GF_LOG(GF_LOG_INFO, GF_LOG_SYNC, ("No timestamp on first packet, using 0\n")); + time = 0; + } + if (odm->delay<0) { + if (time < (u64) -odm->delay) { + gf_filter_pid_drop_packet(pid); + return GF_TRUE; + } + time-= -odm->delay; + } + + time *= 1000; + time /= timescale; + gf_clock_set_time(odm->ck, (u32) time); + odm->media_current_time = 0; + if (odm->parentscene) { + odm->parentscene->root_od->media_start_time = 0; + odm->parentscene->root_od->media_current_time = 0; + } + gf_odm_check_clock_mediatime(odm); + + if (pck && gf_filter_pck_is_blocking_ref(pck)) + odm->blocking_media = GF_TRUE; + } + + //TODO abort buffering when errors are found on the input chain !! + if (odm->blocking_media || (buffer_duration >= odm->buffer_playout_us)) { + odm->nb_buffering --; + assert(scene->nb_buffering); + scene->nb_buffering--; + if (!scene->nb_buffering) { + *signal_eob = GF_TRUE; + } + if (odm->ck) + gf_clock_buffer_off(odm->ck); + } else if (gf_filter_pid_has_seen_eos(pid) ) { + odm->nb_buffering --; + assert(scene->nb_buffering); + scene->nb_buffering--; + //if eos while buffering, consider the last rebuffer an error + //fixeme, we need a way to probe for eos being "close" but not yet detected + if (odm->nb_rebuffer) + odm->nb_rebuffer --; + if (!scene->nb_buffering) { + *signal_eob = GF_TRUE; + if (scene->nb_rebuffer) { + scene->nb_rebuffer--; + } + } + if (odm->ck) + gf_clock_buffer_off(odm->ck); + } + return GF_FALSE; +} + +Bool gf_odm_check_buffering(GF_ObjectManager *odm, GF_FilterPid *pid) +{ + u32 timescale; + Bool signal_eob = GF_FALSE; + GF_Scene *scene; + GF_FilterClockType ck_type; + u64 clock_reference; + GF_FilterPacket *pck; + + assert(odm); + + if (!pid) + pid = odm->pid; + + scene = odm->subscene ? odm->subscene : odm->parentscene; + if (!scene) return GF_FALSE; + pck = gf_filter_pid_get_packet(pid); + ck_type = gf_filter_pid_get_clock_info(pid, &clock_reference, ×cale); + + if (!odm->ck->clock_init && ck_type) { + clock_reference *= 1000; + clock_reference /= timescale; + gf_clock_set_time(odm->ck, (u32) clock_reference); + if (odm->parentscene) + odm->parentscene->root_od->media_start_time = 0; + } + + if (odm->nb_buffering) { + GF_ODMExtraPid *xpid; + u32 i=0; + Bool ret = odm_update_buffer(scene, odm, pid, &signal_eob); + if (ret) return GF_TRUE; + while (odm->nb_buffering && (xpid = gf_list_enum(odm->extra_pids, &i))) { + ret = odm_update_buffer(scene, odm, xpid->pid, &signal_eob); + if (ret) return GF_TRUE; + } + } + + if (scene->nb_buffering) { + GF_ObjectManager *an_odm; + u32 i=0; + while ((an_odm = gf_list_enum(scene->resources,&i))) { + if (odm==an_odm) continue; + if (!an_odm->pid) continue; + + if (an_odm->nb_buffering) + odm_update_buffer(scene, an_odm, an_odm->pid, &signal_eob); + } + } else if (!odm->blocking_media && odm->buffer_min_us && odm->pid && odm->ck && odm->ck->clock_init && !gf_filter_pid_has_seen_eos(odm->pid) ) { + u64 buffer_duration = gf_filter_pid_query_buffer_duration(odm->pid, GF_TRUE); + if (buffer_duration < odm->buffer_min_us) { + gf_clock_buffer_on(odm->ck); + odm->nb_buffering++; + odm->nb_rebuffer++; + if (!scene->nb_buffering) scene->nb_rebuffer++; + scene->nb_buffering++; + } + } + + if (scene->nb_buffering || signal_eob) + gf_scene_buffering_info(scene); + + //handle both PCR discontinuities or TS looping when no PCR disc is present/signaled + if (pck) { + s32 diff=0; + u64 pck_time = 0; + u32 clock_time = gf_clock_time(odm->ck); + s32 diff_to = 0; + if (ck_type) { + clock_reference *= 1000; + clock_reference /= timescale; + diff = (s32) clock_time + odm->buffer_playout_us/1000; + diff -= (s32) clock_reference; + GF_LOG(GF_LOG_INFO, GF_LOG_SYNC, ("Clock %d (ODM %d) reference found "LLU" ms clock time %d ms - diff %d - type %d\n", odm->ck->clock_id, odm->ID, clock_reference, clock_time, diff, ck_type)); + + //if explicit clock discontinuity, mark clock + if (ck_type==GF_FILTER_CLOCK_PCR_DISC) + odm->ck->ocr_discontinuity_time = (u32) (1+clock_reference); + } + pck_time = gf_filter_pck_get_cts(pck); + timescale = gf_filter_pck_get_timescale(pck); + if (pck_time != GF_FILTER_NO_TS) { + pck_time *= 1000; + pck_time /= timescale; + pck_time += 1; + diff = (u32) ((u64) clock_time - pck_time); + diff_to = odm->ck->ocr_discontinuity_time ? 500 : 8000; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_SYNC, ("Clock %d (ODM %d) pck time %d - clock ref "LLU" clock time %d - diff %d vs %d\n", odm->ck->clock_id, odm->ID, pck_time, clock_reference, clock_time, diff, diff_to)); + + //we have a valid TS for the packet, and the CTS diff to the current clock is larget than 8 sec, check for discontinuities + //it may happen that video is sent up to 4 or 5 seconds ahead of the PCR in some systems, 8 sec should be enough + if (diff_to && (ABS(diff) > diff_to) ) { + s64 diff_pck_old_clock, diff_pck_new_clock; + //compute diff to old clock and new clock + diff_pck_new_clock = pck_time-1 - (s64) clock_reference; + if (diff_pck_new_clock<0) diff_pck_new_clock = -diff_pck_new_clock; + diff_pck_old_clock = pck_time-1 - (s64) clock_time; + if (diff_pck_old_clock<0) diff_pck_old_clock = -diff_pck_old_clock; + + //if the packet time is closer to the new clock than the old, switch to new clock + if (diff_pck_old_clock > diff_pck_new_clock) { + u32 i, count; + GF_Scene *in_scene = odm->subscene ? odm->subscene : odm->parentscene; + GF_LOG(GF_LOG_INFO, GF_LOG_SYNC, ("Clock %d (ODM %d) discontinuity detected "LLU" clock time %d - diff %d - type %d - pck time "LLU"\n", odm->ck->clock_id, odm->ID, clock_reference, clock_time, diff, ck_type, pck_time-1)); + + count = gf_list_count(in_scene->resources); + for (i=0; iresources, i); + if (an_odm->ck != odm->ck) continue; + an_odm->prev_clock_at_discontinuity_plus_one = 1 + clock_time; + } + odm->ck->clock_init = GF_FALSE; + gf_clock_set_time(odm->ck, odm->ck->ocr_discontinuity_time ? odm->ck->ocr_discontinuity_time - 1 : (u32) clock_reference); + odm->ck->ocr_discontinuity_time = 0; + } + } + } else if (ck_type) { + clock_reference *= 1000; + clock_reference /= timescale; + if (ck_type==GF_FILTER_CLOCK_PCR_DISC) + odm->ck->ocr_discontinuity_time = (u32) (1 + clock_reference); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_SYNC, ("Clock %d (ODM %d) received "LLU" type %d clock time %d no pending packets\n", odm->ck->clock_id, odm->ID, clock_reference, ck_type, gf_clock_time(odm->ck))); + } + if (!odm->ck->nb_buffering) { + gf_odm_service_media_event(odm, GF_EVENT_MEDIA_PROGRESS); + } + return odm->ck->nb_buffering ? GF_TRUE : GF_FALSE; +} + +#ifndef GPAC_DISABLE_SVG +void gf_odm_collect_buffer_info(GF_SceneNamespace *scene_ns, GF_ObjectManager *odm, GF_DOMMediaEvent *media_event, u32 *min_time, u32 *min_buffer) +{ + GF_ODMExtraPid *xpid; + u32 i, val; + u64 buf_val; + + if (!odm->pid) return; + if (odm->scene_ns != scene_ns) return; + if (! odm->buffer_playout_us) { + media_event->bufferValid = GF_FALSE; + return; + } + + if (odm->nb_buffering) + media_event->bufferValid = GF_TRUE; + + buf_val = gf_filter_pid_query_buffer_duration(odm->pid, GF_FALSE); + if (buf_val > odm->buffer_max_us) buf_val = odm->buffer_max_us; + val = (u32) ((buf_val * 100) / odm->buffer_max_us); + if (*min_buffer > val) (*min_buffer) = val; + + if (*min_time > (u32) buf_val / 1000) + *min_time = (u32) buf_val / 1000; + + i=0; + while ((xpid = gf_list_enum(odm->extra_pids, &i))) { + + buf_val = gf_filter_pid_query_buffer_duration(odm->pid, GF_FALSE); + if (buf_val > odm->buffer_max_us) buf_val = odm->buffer_max_us; + val = (u32) ((buf_val * 100) / odm->buffer_playout_us); + if (*min_buffer > val) (*min_buffer) = val; + + if (*min_time > (u32) buf_val / 1000) + *min_time = (u32) buf_val / 1000; + } +} +#endif + +void gf_odm_service_media_event_with_download(GF_ObjectManager *odm, GF_EventType event_type, u64 loaded_size, u64 total_size, u32 bytes_per_sec) +{ +#ifndef GPAC_DISABLE_SVG + u32 i, count, min_buffer, min_time; + GF_DOM_Event evt; + GF_ObjectManager *an_od; + GF_Scene *scene; + + if (!odm || !odm->scene_ns) return; + if (odm->mo) { + count = gf_mo_event_target_count(odm->mo); + + //for dynamic scenes, check if we have listeners on the root object of the scene containing this media + if (odm->parentscene + && odm->parentscene->is_dynamic_scene + && odm->parentscene->root_od->mo + && (odm->parentscene->root_od->scene_ns==odm->scene_ns) + ) { + odm = odm->parentscene->root_od; + count = gf_mo_event_target_count(odm->mo); + } + if (!count) return; + } else { + count = 0; + } + + + memset(&evt, 0, sizeof(GF_DOM_Event)); + + evt.media_event.bufferValid = GF_FALSE; + evt.media_event.session_name = odm->scene_ns->url; + + min_time = min_buffer = (u32) -1; + scene = odm->subscene ? odm->subscene : odm->parentscene; + if (!scene) return; + + /*get buffering on root OD*/ + gf_odm_collect_buffer_info(odm->scene_ns, scene->root_od, &evt.media_event, &min_time, &min_buffer); + + /*get buffering on all ODs*/ + i=0; + while ((an_od = (GF_ObjectManager*)gf_list_enum(scene->resources, &i))) { + if (odm->scene_ns == an_od->scene_ns) + gf_odm_collect_buffer_info(odm->scene_ns, an_od, &evt.media_event, &min_time, &min_buffer); + } + + if (min_buffer != (u32) -1) { + evt.media_event.level = min_buffer; + } + if (min_time != (u32) -1) + evt.media_event.remaining_time = INT2FIX(min_time) / 60; + evt.media_event.status = 0; + evt.media_event.loaded_size = loaded_size; + evt.media_event.total_size = total_size; + + evt.type = event_type; + evt.bubbles = 0; /*the spec says yes but we force it to NO*/ + + //these events may be triggered from any input or decoding threads. Sync processing cannot be + //achieved in most cases, because we may run into deadlocks, especially if the event + //was triggered by a service opened by JS + for (i=0; imo->evt_targets, i); + if (target) + gf_sc_queue_dom_event_on_target(scene->compositor, &evt, target, scene->graph); + } + if (!count) { + GF_Node *root = gf_sg_get_root_node(scene->graph); + if (root) gf_sc_queue_dom_event(scene->compositor, root, &evt); + } +#endif +} + +void gf_odm_service_media_event(GF_ObjectManager *odm, GF_EventType event_type) +{ + gf_odm_service_media_event_with_download(odm, event_type, 0, 0, 0); +} + +Bool gf_odm_stop_or_destroy(GF_ObjectManager *odm) +{ + Bool destroy = GF_FALSE; + if (odm->mo ) { + if (odm->addon) odm->flags |= GF_ODM_REGENERATE_SCENE; + else if (odm->mo->OD_ID==GF_MEDIA_EXTERNAL_ID) destroy = GF_TRUE; + else if (odm->ID==GF_MEDIA_EXTERNAL_ID) destroy = GF_TRUE; + } + if (destroy) { + gf_odm_disconnect(odm, 2); + return GF_TRUE; + } + gf_odm_stop(odm, 0); + return GF_FALSE; +} + + +static void get_codec_stats(GF_FilterPid *pid, GF_MediaInfo *info) +{ + GF_FilterPidStatistics stats; + gf_filter_pid_get_statistics(pid, &stats, GF_STATS_LOCAL_INPUTS); + + info->avg_bitrate = stats.avgerage_bitrate; + info->max_bitrate = stats.max_bitrate; + info->nb_dec_frames = stats.nb_processed; + info->max_dec_time = stats.max_process_time; + info->total_dec_time = stats.total_process_time; + info->first_frame_time = (u32) stats.first_process_time/1000; + info->last_frame_time = (u32) stats.last_process_time/1000; + info->au_duration = (u32) stats.min_frame_dur/1000; + info->nb_iraps = stats.nb_saps; + info->irap_max_dec_time = stats.max_sap_process_time; + info->irap_total_dec_time = stats.total_sap_process_time; + info->avg_process_bitrate = stats.average_process_rate; + info->max_process_bitrate = stats.max_process_rate; + info->db_unit_count = stats.nb_buffer_units; +} + +GF_EXPORT +GF_Err gf_odm_get_object_info(GF_ObjectManager *odm, GF_MediaInfo *info) +{ + const GF_PropertyValue *prop; + GF_ObjectManager *an_odm; + GF_FilterPid *pid; + + if (!odm || !info) return GF_BAD_PARAM; + memset(info, 0, sizeof(GF_MediaInfo)); + + info->ODID = odm->ID; + info->ServiceID = odm->ServiceID; + info->pid_id = odm->pid_id; + info->ocr_id = odm->ck ? odm->ck->clock_id : 0; + info->od_type = odm->type; + + info->duration = (Double) (s64)odm->duration; + info->duration /= 1000; + + pid = odm->pid; + an_odm = odm; + while (an_odm->lower_layer_odm) { + an_odm = an_odm->lower_layer_odm; + pid = an_odm->pid; + } + + if (pid) { + /*since we don't remove ODs that failed setup, check for clock*/ + if (odm->ck) { + info->current_time = odm->media_current_time; + info->ntp_diff = odm->last_drawn_frame_ntp_diff; + info->current_time = gf_clock_media_time(odm->ck); + + } + info->current_time /= 1000; + info->nb_dropped = odm->nb_dropped; + } else if (odm->subscene) { + if (odm->subscene->root_od && odm->subscene->root_od->ck) { + info->current_time = gf_clock_media_time(odm->subscene->root_od->ck); + info->current_time /= 1000; + } + info->duration = (Double) (s64)odm->subscene->duration; + info->duration /= 1000; + info->nb_dropped = odm->subscene->root_od ? odm->subscene->root_od->nb_dropped : 0; + info->generated_scene = odm->subscene->is_dynamic_scene; + } + if (info->duration && info->current_time>info->duration) + info->current_time = info->duration; + + info->buffer = -2; + info->db_unit_count = 0; + + if (odm->state) { + GF_Clock *ck; + + ck = gf_odm_get_media_clock(odm); + /*no clock means setup failed*/ + if (!ck) { + info->status = 4; + } else { + info->status = gf_clock_is_started(ck) ? 1 : 2; + info->clock_drift = ck->audio_delay; + + info->buffer = -1; + info->min_buffer = -1; + info->max_buffer = 0; + + if (pid) + info->buffer = (u32) gf_filter_pid_query_buffer_duration(pid, GF_FALSE) / 1000; + info->max_buffer = odm->buffer_max_us / 1000; + info->min_buffer = odm->buffer_min_us / 1000; + +#ifdef FILTER_FIXME + info->protection = ch->ipmp_tool ? 1 : 2; +#endif + } + } + + if (odm->scene_ns) { + info->service_handler = odm->scene_ns->source_filter ? gf_filter_get_name(odm->scene_ns->source_filter) : "unloaded"; + + info->service_url = odm->scene_ns->url; + if (odm->scene_ns->owner == odm) info->owns_service = 1; + } else if ((odm->subscene && odm->subscene->graph_attached) || (odm->ID)) { + info->service_url = "No associated network Service"; + } else { + info->service_url = "Service not found or error"; + } + + if (pid) { + info->codec_name = gf_filter_pid_get_filter_name(pid); + info->od_type = odm->type; + + gf_filter_pid_get_buffer_occupancy(pid, &info->cb_max_count, &info->cb_unit_count, NULL, NULL); + + get_codec_stats(pid, info); + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_LANGUAGE); + if (prop) info->lang_code = prop->value.string; + } + + if (odm->subscene) { + gf_sg_get_scene_size_info(odm->subscene->graph, &info->width, &info->height); + } else if (odm->mo) { + switch (info->od_type) { + case GF_STREAM_VISUAL: + gf_mo_get_visual_info(odm->mo, &info->width, &info->height, NULL, &info->par, &info->pixelFormat, NULL); + break; + case GF_STREAM_AUDIO: + gf_mo_get_audio_info(odm->mo, &info->sample_rate, &info->afmt, &info->num_channels, NULL, NULL); + info->clock_drift = 0; + break; + case GF_STREAM_TEXT: + gf_mo_get_visual_info(odm->mo, &info->width, &info->height, NULL, NULL, NULL, NULL); + break; + } + } + + if (odm->mo && odm->mo->URLs.count) + info->media_url = odm->mo->URLs.vals[0].url; + return GF_OK; +} + +//adjust media time info in case the timestamp found at init is not media time 0 +void gf_odm_check_clock_mediatime(GF_ObjectManager *odm) +{ + u64 timestamp; + u32 timescale; + u32 i; + Double media_time, shift; + GF_Scene *scene; + const GF_PropertyValue *p; + GF_PropertyEntry *pe=NULL; + if (!odm->owns_clock) return; + + if (odm->ck->has_media_time_shift) return; + + timescale = gf_filter_pid_get_timescale(odm->pid); + if (!timescale) return; + + p = gf_filter_pid_get_info_str(odm->pid, "time:timestamp", &pe); + if (!p) return; + timestamp = p->value.longuint; + p = gf_filter_pid_get_info_str(odm->pid, "time:media", &pe); + if (!p) return; + gf_filter_release_property(pe); + media_time = p->value.number; + + shift = (Double) timestamp; + shift /= timescale; + shift -= ((Double)odm->ck->init_timestamp)/1000; + media_time += shift; + odm->ck->media_time_at_init = (u32) (media_time * 1000); + odm->ck->has_media_time_shift = GF_TRUE; + + scene = odm->subscene ? odm->subscene : odm->parentscene; + if (!scene) return; + if (scene->root_od) + scene->root_od->media_current_time = 0; + + for (i=0; iresources); i++) { + GF_ObjectManager *anodm = gf_list_get(scene->resources, i); + anodm->media_current_time = 0; + } +} + diff --git a/src/compositor/offscreen_cache.c b/src/compositor/offscreen_cache.c index 7f19350..edbfb42 100644 --- a/src/compositor/offscreen_cache.c +++ b/src/compositor/offscreen_cache.c @@ -102,7 +102,7 @@ void group_cache_setup(GroupCache *cache, GF_Rect *path_bounds, GF_IRect *pix_bo gf_free(cache->txh.data); #ifdef CACHE_DEBUG_ALPHA cache->txh.stride = pix_bounds->width * 3; - cache->txh.pixelformat = GF_PIXEL_RGB_24; + cache->txh.pixelformat = GF_PIXEL_RGB; cache->txh.transparent = 0; #endif @@ -139,13 +139,11 @@ Bool group_cache_traverse(GF_Node *node, GroupCache *cache, GF_TraverseState *tr /*we need to redraw the group in an offscreen visual*/ if (force_recompute) { - GF_Matrix2D backup; GF_IRect rc1, rc2; u32 prev_flags; Bool prev_hybgl, visual_attached, for_3d=GF_FALSE; GF_Rect cache_bounds; - GF_SURFACE offscreen_surface, old_surf; - GF_Raster2D *r2d = tr_state->visual->compositor->rasterizer; + GF_EVGSurface *offscreen_surface, *old_surf; DrawableContext *child_ctx; Fixed temp_x, temp_y, scale_x, scale_y; #ifndef GPAC_DISABLE_3D @@ -214,7 +212,7 @@ Bool group_cache_traverse(GF_Node *node, GroupCache *cache, GF_TraverseState *tr setup top clipers */ old_surf = tr_state->visual->raster_surface; - offscreen_surface = r2d->surface_new(r2d, tr_state->visual->center_coords); /*a new temp raster visual*/ + offscreen_surface = gf_evg_surface_new(tr_state->visual->center_coords); /*a new temp raster visual*/ tr_state->visual->raster_surface = offscreen_surface; #ifndef GPAC_DISABLE_3D if (type_3d) { @@ -230,8 +228,8 @@ Bool group_cache_traverse(GF_Node *node, GroupCache *cache, GF_TraverseState *tr /*use current surface coordinate scaling to compute the cache*/ #ifdef GF_SR_USE_VIDEO_CACHE - scale_x = tr_state->visual->compositor->cache_scale * scale_x / 100; - scale_y = tr_state->visual->compositor->cache_scale * scale_y / 100; + scale_x = tr_state->visual->compositor->vcscale * scale_x / 100; + scale_y = tr_state->visual->compositor->vcscale * scale_y / 100; #endif if (scale_x<0) scale_x = -scale_x; @@ -256,7 +254,7 @@ Bool group_cache_traverse(GF_Node *node, GroupCache *cache, GF_TraverseState *tr /*attach the buffer to visual*/ - r2d->surface_attach_to_buffer(offscreen_surface, cache->txh.data, + gf_evg_surface_attach_to_buffer(offscreen_surface, cache->txh.data, cache->txh.width, cache->txh.height, 0, @@ -327,7 +325,7 @@ Bool group_cache_traverse(GF_Node *node, GroupCache *cache, GF_TraverseState *tr tr_state->visual->compositor->hybrid_opengl = prev_hybgl; tr_state->visual->is_attached = visual_attached; - r2d->surface_delete(offscreen_surface); + gf_evg_surface_delete(offscreen_surface); tr_state->visual->raster_surface = old_surf; tr_state->traversing_mode = TRAVERSE_SORT; @@ -448,7 +446,7 @@ static void group_cache_insert_entry(GF_Node *node, GroupingNode2D *group, GF_Tr group->traverse_time)); GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Status (KB): Max: %d\tUsed: %d\tNb Groups: %d\n", - tr_state->visual->compositor->video_cache_max_size, + tr_state->visual->compositor->vcsize, tr_state->visual->compositor->video_cache_current_size, gf_list_count(tr_state->visual->compositor->cached_groups) )); @@ -500,7 +498,7 @@ static Bool gf_cache_remove_entry(GF_Compositor *compositor, GF_Node *node, Grou FIX2FLT(group->traverse_time))); GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Status (B): Max: %d\tUsed: %d\tNb Groups: %d\n", - compositor->video_cache_max_size, + compositor->vcsize, compositor->video_cache_current_size, gf_list_count(compositor->cached_groups) )); @@ -605,9 +603,9 @@ Bool group_2d_cache_traverse(GF_Node *node, GroupingNode2D *group, GF_TraverseSt else if (group->cache) { Fixed scale = MAX(tr_state->transform.m[0], tr_state->transform.m[4]); - if (100*scale >= group->cache->scale*(100 + tr_state->visual->compositor->cache_tolerance)) + if (100*scale >= group->cache->scale*(100 + tr_state->visual->compositor->vctol)) zoom_changed = 1; - else if ((100+tr_state->visual->compositor->cache_tolerance)*scale <= 100*group->cache->scale) + else if ((100+tr_state->visual->compositor->vctol)*scale <= 100*group->cache->scale) zoom_changed = 1; else zoom_changed = 0; @@ -648,7 +646,7 @@ Bool group_cache_compute_stats(GF_Node *node, GroupingNode2D *group, GF_Traverse DrawableContext *ctx; u32 nb_segments, nb_objects; u32 alpha_pixels, opaque_pixels, area_world; - u32 video_cache_max_size, cache_size, prev_cache_size; + u32 vcsize, cache_size, prev_cache_size; u32 i; GF_RectArray ra; @@ -659,7 +657,7 @@ Bool group_cache_compute_stats(GF_Node *node, GroupingNode2D *group, GF_Traverse prev_cache_size = group->cached_size; /*reset bounds*/ group_bounds.width = group_bounds.height = 0; - video_cache_max_size = tr_state->visual->compositor->video_cache_max_size; + vcsize = tr_state->visual->compositor->vcsize; /*never cache root node - this should be refined*/ if (gf_node_get_parent(node, 0) == NULL) goto group_reject; @@ -726,14 +724,14 @@ Bool group_cache_compute_stats(GF_Node *node, GroupingNode2D *group, GF_Traverse if (10*opaque_pixels < 7*area_world) goto group_reject; /*the memory size allocated for the cache - cache is drawn in final coordinate system !!*/ - group_bounds.width = tr_state->visual->compositor->cache_scale * group_bounds.width / 100; - group_bounds.height = tr_state->visual->compositor->cache_scale * group_bounds.height / 100; + group_bounds.width = tr_state->visual->compositor->vcscale * group_bounds.width / 100; + group_bounds.height = tr_state->visual->compositor->vcscale * group_bounds.height / 100; cache_size = FIX2INT(group_bounds.width) * FIX2INT(group_bounds.height) * 4 /* pixelFormat is ARGB*/; /*TEST 5: cache is less than 10x10 pixels: discard*/ if (cache_size < 400) goto group_reject; /*TEST 6: cache is larger than our allowed memory: discard*/ - if (cache_size>=video_cache_max_size) { + if (cache_size>=vcsize) { tr_state->cache_too_small = 1; goto group_reject; } @@ -795,7 +793,7 @@ group_reject: )); GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Status (B): Max: %d\tUsed: %d\tNb Groups: %d\n", - tr_state->visual->compositor->video_cache_max_size, + tr_state->visual->compositor->vcsize, tr_state->visual->compositor->video_cache_current_size, gf_list_count(tr_state->visual->compositor->cached_groups) )); @@ -807,12 +805,11 @@ group_reject: void group_2d_cache_evaluate(GF_Node *node, GroupingNode2D *group, GF_TraverseState *tr_state, DrawableContext *first_child, Bool skip_first_child, u32 last_cache_idx) { u32 nb_cache_added, i; - Fixed priority; GF_Compositor *compositor = tr_state->visual->compositor; /*first frame is unusable for stats because lot of time is being spent building the path and allocating the drawable contexts*/ - if (!compositor->video_cache_max_size || !compositor->frame_number || group->changed || tr_state->in_group_cache) { + if (!compositor->vcsize || !compositor->frame_number || group->changed || tr_state->in_group_cache) { group->traverse_time = 0; return; } @@ -852,10 +849,10 @@ void group_2d_cache_evaluate(GF_Node *node, GroupingNode2D *group, GF_TraverseSt cache->traverse_time = 0; gf_list_rem(compositor->cached_groups_queue, last_cache_idx); } - priority = INT2FIX (group->nb_objects*1024*1024*avg_time) / group->cached_size; + group->priority = INT2FIX (group->nb_objects*1024*1024*avg_time) / group->cached_size; /*when the memory exceeds the constraint, remove the subgroups that have the lowest deltas*/ - while (compositor->video_cache_current_size > compositor->video_cache_max_size) { + while (compositor->video_cache_current_size > compositor->vcsize) { gf_cache_remove_entry(compositor, node, NULL); GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Removing low priority cache - current total size %d\n", compositor->video_cache_current_size)); } @@ -868,7 +865,7 @@ void compositor_set_cache_memory(GF_Compositor *compositor, u32 memory) while (compositor->video_cache_current_size) { gf_cache_remove_entry(compositor, NULL, NULL); } - compositor->video_cache_max_size = memory; + compositor->vcsize = memory; /*and force recompute*/ compositor->zoom_changed = 1; } diff --git a/src/terminal/scene.c b/src/compositor/scene.c similarity index 69% rename from src/terminal/scene.c rename to src/compositor/scene.c index 9dca7d2..a84f17d 100644 --- a/src/terminal/scene.c +++ b/src/compositor/scene.c @@ -2,10 +2,10 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2018 * All rights reserved * - * This file is part of GPAC / Media terminal sub-project + * This file is part of GPAC / Scene Compositor sub-project * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -29,9 +29,7 @@ #include /*for URL concatenation*/ #include -#include #include -#include "media_control.h" #include #include @@ -40,9 +38,6 @@ #include #endif -#include "input_sensor.h" -#include "media_memory.h" - GF_EXPORT Double gf_scene_get_time(void *_is) { @@ -51,7 +46,8 @@ Double gf_scene_get_time(void *_is) u32 ret; GF_Clock *ck; assert(scene); - ck = scene->scene_codec ? scene->scene_codec->ck : scene->dyn_ck; + assert(scene->root_od); + ck = scene->root_od->ck; if (!ck) return 0.0; ret = gf_clock_time(ck); if (scene->root_od->media_stop_time && (scene->root_od->media_stop_timeroot_od->media_stop_time; @@ -68,17 +64,234 @@ void gf_storage_save(M_Storage *storage); static void inline_on_media_event(GF_Scene *scene, u32 type) { - gf_term_service_media_event(scene->scene_codec->odm, type); + gf_odm_service_media_event(scene->root_od, type); +} + + +void gf_scene_message_ex(GF_Scene *scene, const char *service, const char *message, GF_Err error, Bool no_filtering) +{ + GF_Event evt; + if (!scene || !scene->compositor) return; + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_MESSAGE; + evt.message.service = service; + evt.message.message = message; + evt.message.error = error; + + if (no_filtering) { + gf_filter_ui_event(scene->compositor->filter, &evt); + } else { + gf_filter_send_gf_event(scene->compositor->filter, &evt); + } +} + +void gf_scene_message(GF_Scene *scene, const char *service, const char *message, GF_Err error) +{ + gf_scene_message_ex(scene, service, message, error, 0); +} + + +char *gf_scene_resolve_xlink(GF_Node *node, char *the_url) +{ + char *url; + GF_Scene *scene = gf_sg_get_private(gf_node_get_graph(node)); + if (!scene) return gf_strdup(the_url); + + url = gf_strdup(the_url); +#ifndef GPAC_DISABLE_SVG + /*apply XML:base*/ + while (node) { + GF_FieldInfo info; + if (gf_node_get_attribute_by_tag(node, TAG_XML_ATT_base, 0, 0, &info)==GF_OK) { + char *new_url = gf_url_concatenate( ((XMLRI*)info.far_ptr)->string, url); + if (new_url) { + gf_free(url); + url = new_url; + } + } + node = gf_node_get_parent(node, 0); + } +#endif + + /*if this is a fragment and no XML:BASE was found, this is a fragment of the current document*/ + if (url[0]=='#') return url; + + if (scene->redirect_xml_base) { + the_url = gf_url_concatenate(scene->redirect_xml_base, url); + } else { +// the_url = gf_url_concatenate(is->root_od->net_service->url, url); + /*the root url of a document should be "." if not specified, so that the final URL resolve happens only once + at the service level*/ + the_url = gf_strdup(url); + } + gf_free(url); + return the_url; +} + +static Bool gf_scene_script_action(void *opaque, GF_JSAPIActionType type, GF_Node *n, GF_JSAPIParam *param) +{ + Bool ret; + GF_Scene *root_scene; + GF_Scene *scene = (GF_Scene *) opaque; + if (!scene) return GF_FALSE; + root_scene = gf_scene_get_root_scene(scene); + + if (type==GF_JSAPI_OP_MESSAGE) { + gf_scene_message_ex(scene, scene->root_od->scene_ns->url, param->info.msg, param->info.e, 1); + return 1; + } + if (type==GF_JSAPI_OP_GET_COMPOSITOR) { + param->compositor = scene->compositor; + return 1; + } + if (type==GF_JSAPI_OP_RESOLVE_XLINK) { +#ifndef GPAC_DISABLE_SVG + param->uri.url = (char *) gf_scene_resolve_xlink(n, (char *) param->uri.url); + return 1; +#else + return 0; +#endif + } + if (type==GF_JSAPI_OP_GET_OPT) { + param->gpac_cfg.key_val = gf_opts_get_key(param->gpac_cfg.section, param->gpac_cfg.key); + return 1; + } + if (type==GF_JSAPI_OP_SET_OPT) { + gf_opts_set_key(param->gpac_cfg.section, param->gpac_cfg.key, param->gpac_cfg.key_val); + return 1; + } + if (type==GF_JSAPI_OP_GET_DOWNLOAD_MANAGER) { + param->dnld_man = gf_filter_get_download_manager(scene->compositor->filter); + return 1; + } + if (type==GF_JSAPI_OP_SET_TITLE) { + GF_Event evt; + evt.type = GF_EVENT_SET_CAPTION; + evt.caption.caption = param->uri.url; + gf_filter_send_gf_event(scene->compositor->filter, &evt); + return 1; + } + if (type==GF_JSAPI_OP_GET_SUBSCENE) { + GF_Scene *a_scene = (GF_Scene *)gf_node_get_private(n); + param->scene = a_scene->graph; + return 1; + } + + if (type==GF_JSAPI_OP_RESOLVE_URI) { + char *url; + char new_url[GF_MAX_PATH]; +#ifdef FILTER_FIXME + char localized_url[GF_MAX_PATH]; +#endif + Bool result=GF_FALSE; + GF_Scene *a_scene = (GF_Scene *)gf_sg_get_private(gf_node_get_graph(n)); + url = (char *)param->uri.url; + if (!url) { + param->uri.url = gf_strdup(a_scene->root_od->scene_ns->url); + param->uri.nb_params = 0; + return 1; + } + + new_url[0]=0; +#ifdef FILTER_FIXME + result = gf_term_relocate_url(term, url, a_scene->root_od->net_service->url, new_url, localized_url); +#endif + if (result) param->uri.url = gf_strdup(new_url); + else param->uri.url = gf_url_concatenate(a_scene->root_od->scene_ns->url, url); + return 1; + } + + /*special case for pause/stop/resume*/ + if (type==GF_JSAPI_OP_PAUSE_SVG) { + GF_SceneGraph *graph = gf_node_get_graph(n); + if (n == gf_sg_get_root_node(graph)) { + GF_Scene *a_scene = (GF_Scene *)gf_sg_get_private(graph); + if (a_scene->root_od->ck) gf_clock_pause(a_scene->root_od->ck); + return 1; + } + } + if (type==GF_JSAPI_OP_RESUME_SVG) { + GF_SceneGraph *graph = gf_node_get_graph(n); + if (n == gf_sg_get_root_node(graph)) { + GF_Scene *a_scene = (GF_Scene *)gf_sg_get_private(graph); + if (a_scene->root_od->ck) gf_clock_resume(a_scene->root_od->ck); + return 1; + } + } + if (type==GF_JSAPI_OP_RESTART_SVG) { + GF_SceneGraph *graph = gf_node_get_graph(n); + if (n == gf_sg_get_root_node(graph)) { + GF_Scene *a_scene = (GF_Scene *)gf_sg_get_private(graph); + GF_Clock *ck = a_scene->root_od->ck; + if (ck) { + Bool is_paused = ck->nb_paused ? GF_TRUE : GF_FALSE; + if (is_paused) gf_clock_resume(ck); + gf_scene_restart_dynamic(a_scene, 0, 0, 0); + if (is_paused) gf_clock_pause(ck); + } + return 1; + } + return 0; + } + if (type==GF_JSAPI_OP_SET_SCENE_SPEED) { + GF_SceneGraph *graph = gf_node_get_graph(n); + if (n == gf_sg_get_root_node(graph)) { + GF_Scene *a_scene = (GF_Scene *)gf_sg_get_private(graph); + GF_Clock *ck = a_scene->root_od->ck; + if (ck) { + gf_clock_set_speed(ck, param->val); + } + return 1; + } + return 0; + } + + + ret = gf_sc_script_action(scene->compositor, type, n, param); + if (ret) return ret; + + if (type==GF_JSAPI_OP_LOAD_URL) { + if (gf_sg_get_private(gf_node_get_graph(n)) == root_scene) { + GF_Event evt; + evt.type = GF_EVENT_NAVIGATE; + evt.navigate.to_url = param->uri.url; + evt.navigate.parameters = param->uri.params; + evt.navigate.param_count = param->uri.nb_params; + return gf_filter_send_gf_event(scene->compositor->filter, &evt); + } else { + /*TODO*/ + return 0; + } + } + return 0; +} + + +Bool gf_scene_is_root(GF_Scene *scene) +{ + GF_Scene *s = scene; + while (s->root_od->parentscene) s = s->root_od->parentscene; + return (s==scene) ? GF_TRUE : GF_FALSE; +} + +GF_Scene *gf_scene_get_root_scene(GF_Scene *scene) +{ + while (scene && scene->root_od && scene->root_od->parentscene) + scene = scene->root_od->parentscene; + return scene; } + + GF_EXPORT -GF_Scene *gf_scene_new(GF_Scene *parentScene) +GF_Scene *gf_scene_new(GF_Compositor *compositor, GF_Scene *parentScene) { GF_Scene *tmp; + if (!compositor && !parentScene) return NULL; + GF_SAFEALLOC(tmp, GF_Scene); if (! tmp) return NULL; - tmp->mx_resources = gf_mx_new("SceneResources"); tmp->resources = gf_list_new(); tmp->scene_objects = gf_list_new(); tmp->extra_scenes = gf_list_new(); @@ -86,13 +299,20 @@ GF_Scene *gf_scene_new(GF_Scene *parentScene) /*init inline scene*/ if (parentScene) { tmp->graph = gf_sg_new_subscene(parentScene->graph); + assert(!compositor || (compositor==parentScene->compositor)); + tmp->compositor = parentScene->compositor; } else { tmp->graph = gf_sg_new(); + tmp->compositor = compositor; + //only for the top scene + tmp->namespaces = gf_list_new(); } gf_sg_set_private(tmp->graph, tmp); - gf_sg_set_node_callback(tmp->graph, gf_term_node_callback); + gf_sg_set_node_callback(tmp->graph, gf_scene_node_callback); gf_sg_set_scene_time_callback(tmp->graph, gf_scene_get_time); + if (tmp->compositor && !tmp->compositor->nojs) + gf_sg_set_script_action(tmp->graph, gf_scene_script_action, tmp); //copy over pause_at_first_frame flag so that new subscene is not paused right away if (parentScene) @@ -104,8 +324,9 @@ GF_Scene *gf_scene_new(GF_Scene *parentScene) tmp->storages = gf_list_new(); tmp->keynavigators = gf_list_new(); - + tmp->attached_inlines = gf_list_new(); #endif + tmp->on_media_event = inline_on_media_event; return tmp; } @@ -121,10 +342,9 @@ static void gf_scene_reset_urls(GF_Scene *scene) SFURL_RESET(scene->dims_url); } +GF_EXPORT void gf_scene_del(GF_Scene *scene) { - gf_mx_p(scene->root_od->term->net_mx); - gf_list_del(scene->resources); assert(!gf_list_count(scene->extra_scenes) ); gf_list_del(scene->extra_scenes); @@ -138,26 +358,9 @@ void gf_scene_del(GF_Scene *scene) gf_list_del(scene->extern_protos); #endif - /*delete scene decoder */ - if (scene->scene_codec) { - GF_SceneDecoder *dec = (GF_SceneDecoder *)scene->scene_codec->decio; - /*make sure the scene codec doesn't have anything left in the scene graph*/ - if (dec->ReleaseScene) dec->ReleaseScene(dec); - - gf_term_remove_codec(scene->root_od->term, scene->scene_codec); - gf_codec_del(scene->scene_codec); - /*reset pointer to NULL in case nodes try to access scene time*/ - scene->scene_codec = NULL; - } - /*delete the scene graph*/ gf_sg_del(scene->graph); - if (scene->od_codec) { - gf_term_remove_codec(scene->root_od->term, scene->od_codec); - gf_codec_del(scene->od_codec); - scene->od_codec = NULL; - } /*don't touch the root_od, will be deleted by the parent scene*/ /*clean all remaining associations*/ @@ -181,8 +384,25 @@ void gf_scene_del(GF_Scene *scene) if (scene->fragment_uri) gf_free(scene->fragment_uri); if (scene->redirect_xml_base) gf_free(scene->redirect_xml_base); - gf_mx_v(scene->root_od->term->net_mx); - gf_mx_del(scene->mx_resources); + if (scene->namespaces) { + while (gf_list_count(scene->namespaces)) { + GF_SceneNamespace *sns = gf_list_pop_back(scene->namespaces); + gf_scene_ns_del(sns, scene); + } + gf_list_del(scene->namespaces); + } + +#ifndef GPAC_DISABLE_VRML + while (gf_list_count(scene->attached_inlines)) { + GF_Node *n_inline = gf_list_pop_back(scene->attached_inlines); + gf_node_set_private(n_inline, NULL); + } + gf_list_del(scene->attached_inlines); +#endif + + if (scene->compositor->root_scene == scene) + scene->compositor->root_scene = NULL; + gf_free(scene); } @@ -192,7 +412,7 @@ GF_ObjectManager *gf_scene_find_odm(GF_Scene *scene, u16 OD_ID) GF_ObjectManager *odm; u32 i=0; while ((odm = (GF_ObjectManager *)gf_list_enum(scene->resources, &i))) { - if (odm->OD && odm->OD->objectDescriptorID == OD_ID) return odm; + if (odm->ID == OD_ID) return odm; } return NULL; } @@ -203,27 +423,26 @@ void gf_scene_disconnect(GF_Scene *scene, Bool for_shutdown) u32 i; GF_MediaObject *obj; GF_ObjectManager *odm; - GF_SceneDecoder *dec = NULL; - if (scene->scene_codec) dec = (GF_SceneDecoder *)scene->scene_codec->decio; GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[Scene] disconnecting\n")); - gf_term_lock_compositor(scene->root_od->term, GF_TRUE); /*force unregistering of inline nodes (for safety)*/ if (for_shutdown && scene->root_od->mo) { /*reset private stack of all inline nodes still registered*/ while (gf_mo_event_target_count(scene->root_od->mo)) { - GF_Node *n = (GF_Node *)gf_event_target_get_node(gf_mo_event_target_get(scene->root_od->mo, 0)); gf_mo_event_target_remove_by_index(scene->root_od->mo, 0); #ifndef GPAC_DISABLE_VRML - switch (gf_node_get_tag(n)) { - case TAG_MPEG4_Inline: + GF_Node *n = (GF_Node *)gf_event_target_get_node(gf_mo_event_target_get(scene->root_od->mo, 0)); + if (n) { + switch (gf_node_get_tag(n)) { + case TAG_MPEG4_Inline: #ifndef GPAC_DISABLE_X3D - case TAG_X3D_Inline: + case TAG_X3D_Inline: #endif - gf_node_set_private(n, NULL); - break; + gf_node_set_private(n, NULL); + break; + } } #endif } @@ -234,7 +453,8 @@ void gf_scene_disconnect(GF_Scene *scene, Bool for_shutdown) if (for_shutdown) { i = 0; while ((odm = (GF_ObjectManager *)gf_list_enum(scene->resources, &i))) { - if (for_shutdown && odm->mo) { + if (odm->mo) { + odm->ck = NULL; obj = odm->mo; while (gf_mo_event_target_count(obj)) { GF_Node *n = (GF_Node *)gf_event_target_get_node(gf_mo_event_target_get(obj, 0)); @@ -270,28 +490,27 @@ void gf_scene_disconnect(GF_Scene *scene, Bool for_shutdown) } #endif - if (scene->root_od->term->root_scene == scene) { - gf_sc_set_scene(scene->root_od->term->compositor, NULL); + if (!scene->root_od->parentscene) { + gf_sc_set_scene(scene->compositor, NULL); } gf_scene_reset_addons(scene); /*release the scene - at this stage, we no longer have any node stack refering to our media objects */ - if (dec && dec->ReleaseScene) dec->ReleaseScene(dec); - gf_sc_node_destroy(scene->root_od->term->compositor, NULL, scene->graph); + + gf_sc_node_destroy(scene->compositor, NULL, scene->graph); gf_sg_reset(scene->graph); scene->graph_attached = 0; - /*reset statc ressource flag since we destroyed scene objects*/ - scene->static_media_ressources = GF_FALSE; - - /*disconnect and kill all objects*/ - if (!for_shutdown && scene->static_media_ressources) { + /*disconnect all objects*/ + if (!for_shutdown) { i=0; /*stop all objects but DON'T DESTROY THEM*/ while ((odm = (GF_ObjectManager *)gf_list_enum(scene->resources, &i))) { if (odm->state) gf_odm_disconnect(odm, GF_FALSE); + //reset OD ID until we get a new OD re-attaching this object + odm->ID = 0; } /*reset all stream associations*/ i=0; @@ -299,10 +518,16 @@ void gf_scene_disconnect(GF_Scene *scene, Bool for_shutdown) gf_sg_vrml_mf_reset(&obj->URLs, GF_SG_VRML_MFURL); gf_mo_event_target_reset(obj); } - } else { + } + /*disconnect and remove all objects*/ + else { while (gf_list_count(scene->resources)) { odm = (GF_ObjectManager *)gf_list_get(scene->resources, 0); - gf_odm_disconnect(odm, (for_shutdown || !scene->static_media_ressources) ? 2 : 0); + if (odm->skip_disconnect_state) { + gf_list_rem(scene->resources, 0); + } else { + gf_odm_disconnect(odm, for_shutdown ? 2 : 0); + } } #ifndef GPAC_DISABLE_VRML while (gf_list_count(scene->extern_protos)) { @@ -326,14 +551,12 @@ void gf_scene_disconnect(GF_Scene *scene, Bool for_shutdown) gf_scene_reset_urls(scene); scene->object_attached = 0; - gf_term_lock_compositor(scene->root_od->term, GF_FALSE); } static void gf_scene_insert_object(GF_Scene *scene, GF_MediaObject *mo, Bool lock_timelines, GF_MediaObject *sync_ref, Bool keep_fragment, GF_Scene *original_parent_scene) { - GF_ObjectManager *root_od; GF_ObjectManager *odm; - char *url; + char *url, *final_url; if (!mo || !scene) return; odm = gf_odm_new(); @@ -341,70 +564,31 @@ static void gf_scene_insert_object(GF_Scene *scene, GF_MediaObject *mo, Bool loc odm->mo = mo; mo->odm = odm; odm->parentscene = scene; - odm->OD = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); - odm->OD->objectDescriptorID = GF_MEDIA_EXTERNAL_ID; - odm->parentscene = scene; - odm->term = scene->root_od->term; - root_od = scene->root_od; + odm->ID = GF_MEDIA_EXTERNAL_ID; if (scene->force_single_timeline) lock_timelines = GF_TRUE; url = mo->URLs.vals[0].url; - if (url) { - if (!stricmp(url, "KeySensor")) { - GF_ESD *esd = gf_odf_desc_esd_new(0); - esd->decoderConfig->streamType = GF_STREAM_INTERACT; - esd->decoderConfig->objectTypeIndication = 1; - gf_free(esd->decoderConfig->decoderSpecificInfo->data); - esd->decoderConfig->decoderSpecificInfo->data = gf_strdup(" KeySensor"); - esd->decoderConfig->decoderSpecificInfo->data[0] = 9; - esd->decoderConfig->decoderSpecificInfo->dataLength = 10; - esd->ESID = esd->OCRESID = 65534; - gf_list_add(odm->OD->ESDescriptors, esd); - } else if (!stricmp(url, "StringSensor")) { - GF_ESD *esd = gf_odf_desc_esd_new(0); - esd->decoderConfig->streamType = GF_STREAM_INTERACT; - esd->decoderConfig->objectTypeIndication = 1; - gf_free(esd->decoderConfig->decoderSpecificInfo->data); - esd->decoderConfig->decoderSpecificInfo->data = gf_strdup(" StringSensor"); - esd->decoderConfig->decoderSpecificInfo->data[0] = 12; - esd->decoderConfig->decoderSpecificInfo->dataLength = 13; - esd->ESID = esd->OCRESID = 65534; - gf_list_add(odm->OD->ESDescriptors, esd); - } else if (!stricmp(url, "Mouse")) { - GF_ESD *esd = gf_odf_desc_esd_new(0); - esd->decoderConfig->streamType = GF_STREAM_INTERACT; - esd->decoderConfig->objectTypeIndication = 1; - gf_free(esd->decoderConfig->decoderSpecificInfo->data); - esd->decoderConfig->decoderSpecificInfo->data = gf_strdup(" Mouse"); - esd->decoderConfig->decoderSpecificInfo->data[0] = 5; - esd->decoderConfig->decoderSpecificInfo->dataLength = 6; - esd->ESID = esd->OCRESID = 65534; - gf_list_add(odm->OD->ESDescriptors, esd); - } else { - if (!keep_fragment) { - char *frag = strrchr(mo->URLs.vals[0].url, '#'); - if (frag) frag[0] = 0; - odm->OD->URLString = gf_strdup(mo->URLs.vals[0].url); - if (frag) frag[0] = '#'; - } else { - odm->OD->URLString = gf_strdup(mo->URLs.vals[0].url); - } - if (lock_timelines) odm->flags |= GF_ODM_INHERIT_TIMELINE; - } + + if (!url) return; + + if (!stricmp(url, "KeySensor")) { + final_url = "gpac://KeySensor"; + } else if (!stricmp(url, "StringSensor")) { + final_url = "gpac://StringSensor"; + } else if (!stricmp(url, "Mouse")) { + final_url = "gpac://Mouse"; + } else { + final_url = mo->URLs.vals[0].url; + if (lock_timelines) odm->flags |= GF_ODM_INHERIT_TIMELINE; } /*HACK - temp storage of sync ref*/ - if (sync_ref) odm->ocr_codec = (struct _generic_codec *)sync_ref; + if (sync_ref) odm->sync_ref = sync_ref; GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[Scene] Inserting new MediaObject %08x for resource %s\n", odm->mo, url)); - gf_mx_p(scene->mx_resources); gf_list_add(scene->resources, odm); - gf_mx_v(scene->mx_resources); - if (original_parent_scene) { - gf_odm_setup_object(odm, original_parent_scene->root_od->net_service); - } else { - gf_odm_setup_object(odm, root_od->net_service); - } + + gf_odm_setup_remote_object(odm, original_parent_scene ? original_parent_scene->root_od->scene_ns : NULL, final_url); } static void gf_scene_reinsert_object(GF_Scene *scene, GF_MediaObject *mo) @@ -425,11 +609,9 @@ void gf_scene_remove_object(GF_Scene *scene, GF_ObjectManager *odm, u32 for_shut u32 i; GF_MediaObject *obj; - gf_term_lock_net(odm->term, GF_TRUE); gf_list_del_item(scene->resources, odm); - gf_term_lock_net(odm->term, GF_FALSE); - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[Scene] removing ODM %d\n", odm->OD ? odm->OD->objectDescriptorID : GF_MEDIA_EXTERNAL_ID)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[Scene] removing ODM %d\n", odm->ID )); i=0; @@ -438,21 +620,25 @@ void gf_scene_remove_object(GF_Scene *scene, GF_ObjectManager *odm, u32 for_shut /*assigned object*/ (obj->odm==odm) || /*remote OD*/ - ((obj->OD_ID!=GF_MEDIA_EXTERNAL_ID) && odm->OD && (obj->OD_ID == odm->OD->objectDescriptorID) ) || + ((obj->OD_ID!=GF_MEDIA_EXTERNAL_ID) && (obj->OD_ID == odm->ID) ) /*dynamic OD*/ - (obj->URLs.count && odm->OD && odm->OD->URLString && !stricmp(obj->URLs.vals[0].url, odm->OD->URLString)) + || (obj->URLs.count && odm->scene_ns && (odm->scene_ns!=scene->root_od->scene_ns) && !stricmp(obj->URLs.vals[0].url, odm->scene_ns->url)) ) { u32 discard_obj = 0; - gf_odm_lock(odm, 1); + obj->flags = 0; if (obj->odm) obj->odm->mo = NULL; odm->mo = NULL; obj->odm = NULL; + if (obj->pck) { + gf_filter_pck_unref(obj->pck); + obj->pck = NULL; + } obj->frame = NULL; obj->framesize = obj->timestamp = 0; + obj->config_changed = GF_TRUE; - gf_odm_lock(odm, 0); /*if graph not attached we can remove the link (this is likely scene shutdown for some error)*/ if (!scene->graph_attached) { @@ -483,16 +669,22 @@ void gf_scene_remove_object(GF_Scene *scene, GF_ObjectManager *odm, u32 for_shut /*reset private stack of all inline nodes still registered*/ if (discard_obj) { while (gf_mo_event_target_count(obj)) { - GF_Node *n = (GF_Node *)gf_event_target_get_node(gf_mo_event_target_get(obj, 0)); gf_mo_event_target_remove_by_index(obj, 0); #ifndef GPAC_DISABLE_VRML - switch (gf_node_get_tag(n)) { - case TAG_MPEG4_Inline: + GF_Node *n = (GF_Node *)gf_event_target_get_node(gf_mo_event_target_get(obj, 0)); + if (n) { + switch (gf_node_get_tag(n)) { + case TAG_MPEG4_Inline: #ifndef GPAC_DISABLE_X3D - case TAG_X3D_Inline: + case TAG_X3D_Inline: #endif - gf_node_set_private(n, NULL); - break; + if (obj->num_open) gf_mo_stop(&obj); + gf_node_set_private(n, NULL); + break; + default: + gf_sc_mo_destroyed(n); + break; + } } #endif } @@ -512,8 +704,10 @@ void gf_scene_remove_object(GF_Scene *scene, GF_ObjectManager *odm, u32 for_shut //browse all channels and update buffering info void gf_scene_buffering_info(GF_Scene *scene) { - u32 i, j, max_buffer, cur_buffer, max_buff_val=0; - GF_Channel *ch; + GF_ODMExtraPid *xpid; + u32 i, j; + u64 max_buffer, cur_buffer, max_buff_val=0; + u64 buf_val; GF_Event evt; GF_ObjectManager *odm; if (!scene) return; @@ -521,43 +715,66 @@ void gf_scene_buffering_info(GF_Scene *scene) max_buffer = cur_buffer = 0; /*get buffering on root OD*/ - j=0; - while ((ch = (GF_Channel*)gf_list_enum(scene->root_od->channels, &j))) { - /*count only re-buffering channels*/ - if (!ch->BufferOn) continue; - - max_buffer += ch->MaxBuffer; - cur_buffer += (ch->BufferTime>0) ? ch->BufferTime : 1; + odm = scene->root_od; + if (odm->pid && odm->buffer_playout_us) { + if (max_buff_val < odm->buffer_playout_us) + max_buff_val = odm->buffer_playout_us; + + if (odm->nb_buffering) { + max_buffer += odm->buffer_playout_us; + buf_val = gf_filter_pid_query_buffer_duration(odm->pid, GF_FALSE); + if (buf_val > max_buffer) buf_val = max_buffer; + cur_buffer += (buf_val>0) ? buf_val : 1; + i=0; + while ((xpid = gf_list_enum(odm->extra_pids, &i))) { + max_buffer += odm->buffer_playout_us; + buf_val = gf_filter_pid_query_buffer_duration(xpid->pid, GF_FALSE); + if (buf_val > max_buffer) buf_val = max_buffer; + cur_buffer += (buf_val>0) ? buf_val : 1; + } + } } /*get buffering on all ODs*/ i=0; while ((odm = (GF_ObjectManager*)gf_list_enum(scene->resources, &i))) { - if (!odm->codec) continue; + if (!odm->buffer_playout_us) continue; + if (max_buff_val < odm->buffer_playout_us) + max_buff_val = odm->buffer_playout_us; + + if (!odm->nb_buffering) continue; + + max_buffer += odm->buffer_playout_us; + buf_val = gf_filter_pid_query_buffer_duration(odm->pid, GF_FALSE); + if (buf_val > max_buffer) buf_val = max_buffer; + cur_buffer += (buf_val>0) ? buf_val : 1; j=0; - while ((ch = (GF_Channel*)gf_list_enum(odm->channels, &j))) { - if (max_buff_val < ch->MaxBuffer) max_buff_val = ch->MaxBuffer; - - /*count only re-buffering channels*/ - if (!ch->BufferOn) continue; - if (ch->MaxBuffer) { - max_buffer += ch->MaxBuffer; - cur_buffer += (ch->BufferTime>0) ? ch->BufferTime : 0; - } + while ((xpid = gf_list_enum(odm->extra_pids, &j))) { + max_buffer += odm->buffer_playout_us; + buf_val = gf_filter_pid_query_buffer_duration(xpid->pid, GF_FALSE); + if (buf_val > max_buffer) buf_val = max_buffer; + cur_buffer += (buf_val>0) ? buf_val : 1; } } + //likely local file playback with buffer disabled + if (!max_buff_val) + return; + //destruction + if (!scene->root_od->scene_ns) + return; evt.type = GF_EVENT_PROGRESS; evt.progress.progress_type = 0; - evt.progress.service = scene->root_od->net_service->url; + evt.progress.service = scene->root_od->scene_ns->url; + if (!max_buffer || !cur_buffer || (max_buffer <= cur_buffer)) { if (!max_buffer) max_buffer=max_buff_val; - evt.progress.done = evt.progress.total = max_buffer; + evt.progress.done = evt.progress.total = (u32) (max_buffer / 1000); } else { - evt.progress.done = cur_buffer; - evt.progress.total = max_buffer; + evt.progress.done = (u32) (cur_buffer / 1000); + evt.progress.total = (u32) (max_buffer / 1000); } - gf_term_send_event(scene->root_od->term, &evt); + gf_sc_send_event(scene->compositor, &evt); } @@ -606,14 +823,14 @@ void gf_scene_notify_event(GF_Scene *scene, u32 event_type, GF_Node *n, void *_e if (no_queueing) { gf_dom_event_fire(n, dom_event); } else { - gf_sc_queue_dom_event(scene->root_od->term->compositor, n, dom_event); + gf_sc_queue_dom_event(scene->compositor, n, dom_event); } } else { if (root) { if (no_queueing) { gf_dom_event_fire(root, dom_event); } else { - gf_sc_queue_dom_event(scene->root_od->term->compositor, root, dom_event); + gf_sc_queue_dom_event(scene->compositor, root, dom_event); } } @@ -623,7 +840,7 @@ void gf_scene_notify_event(GF_Scene *scene, u32 event_type, GF_Node *n, void *_e if (no_queueing) { gf_dom_event_fire(an, dom_event); } else { - gf_sc_queue_dom_event(scene->root_od->term->compositor, an, dom_event); + gf_sc_queue_dom_event(scene->compositor, an, dom_event); } } } @@ -634,28 +851,29 @@ void gf_scene_notify_event(GF_Scene *scene, u32 event_type, GF_Node *n, void *_e GF_EXPORT void gf_scene_attach_to_compositor(GF_Scene *scene) { - char *url; if (!scene->root_od) return; + if (scene->graph_attached==1) return; - if ((scene->graph_attached==1) || (gf_sg_get_root_node(scene->graph)==NULL) ) { - gf_term_invalidate_compositor(scene->root_od->term); + scene->graph_attached = 1; + if (gf_sg_get_root_node(scene->graph)==NULL) { + gf_sc_invalidate(scene->compositor, NULL); return; } - scene->graph_attached = 1; /*locate fragment IRI*/ - if (scene->root_od->net_service && scene->root_od->net_service->url) { + if (scene->root_od->scene_ns && scene->root_od->scene_ns->url) { + char *url; if (scene->fragment_uri) { gf_free(scene->fragment_uri); scene->fragment_uri = NULL; } - url = strchr(scene->root_od->net_service->url, '#'); + url = strchr(scene->root_od->scene_ns->url, '#'); if (url) scene->fragment_uri = gf_strdup(url+1); } /*main display scene, setup compositor*/ - if (scene->root_od->term->root_scene == scene) { - gf_sc_set_scene(scene->root_od->term->compositor, scene->graph); + if (!scene->root_od->parentscene) { + gf_sc_set_scene(scene->compositor, scene->graph); } else { u32 i, count; @@ -663,12 +881,12 @@ void gf_scene_attach_to_compositor(GF_Scene *scene) for (i=0; iroot_od->mo, i))); } - gf_term_invalidate_compositor(scene->root_od->term); + gf_sc_invalidate(scene->compositor, NULL); if (scene->root_od->parentscene->is_dynamic_scene) { u32 w, h; gf_sg_get_scene_size_info(scene->graph, &w, &h); - gf_sc_set_size(scene->root_od->term->compositor, w, h); + gf_sc_set_size(scene->compositor, w, h); } /*trigger a scene attach event*/ gf_scene_notify_event(scene, GF_EVENT_SCENE_ATTACHED, NULL, NULL, GF_OK, GF_FALSE); @@ -696,6 +914,7 @@ static GFINLINE Bool is_match_obj_type(u32 type, u32 hint_type) return GF_FALSE; } +GF_EXPORT GF_MediaObject *gf_scene_get_media_object_ex(GF_Scene *scene, MFURL *url, u32 obj_type_hint, Bool lock_timelines, GF_MediaObject *sync_ref, Bool force_new_if_not_attached, GF_Node *node) { GF_MediaObject *obj; @@ -707,9 +926,7 @@ GF_MediaObject *gf_scene_get_media_object_ex(GF_Scene *scene, MFURL *url, u32 ob OD_ID = gf_mo_get_od_id(url); if (!OD_ID) return NULL; - gf_mx_p(scene->mx_resources); - - /*we may have overriden the time lines in parent scene, thus all objects in this scene have the same clock*/ + /*we may have overridden the time lines in parent scene, thus all objects in this scene have the same clock*/ if (scene->root_od->parentscene && scene->root_od->parentscene->force_single_timeline) lock_timelines = GF_TRUE; @@ -751,19 +968,8 @@ restart: if (timeline_locked != lock_timelines) continue; - gf_term_lock_media_queue(scene->root_od->term, GF_TRUE); if (obj->odm->flags & GF_ODM_DESTROYED) can_reuse = GF_FALSE; - else if (obj->odm->action_type == GF_ODM_ACTION_DELETE) { - /*check if object is being destroyed (no longer in the queue)*/ - if (gf_list_del_item(scene->root_od->term->media_queue, obj->odm)<0) { - can_reuse = GF_FALSE; - } - /*otherwise reuse object, discard current destroy command*/ - else { - obj->odm->action_type = GF_ODM_ACTION_PLAY; - } - } - gf_term_lock_media_queue(scene->root_od->term, GF_FALSE); + if (!can_reuse) continue; } @@ -771,12 +977,10 @@ restart: if (!first_pass && !force_new_if_not_attached) { if (node && (gf_mo_event_target_find_by_node(obj, node)<0)) gf_mo_event_target_add_node(obj, node); - gf_mx_v(scene->mx_resources); return obj; } /*special case where the URL is requested twice for the same node: use the existing resource*/ else if (node && (gf_mo_event_target_find_by_node(obj, node)>=0)) { - gf_mx_v(scene->mx_resources); return obj; } } @@ -787,7 +991,6 @@ restart: /*we cannot create an OD manager at this point*/ if (obj_type_hint==GF_MEDIA_OBJECT_UNDEF) { - gf_mx_v(scene->mx_resources); return NULL; } @@ -815,19 +1018,25 @@ restart: gf_scene_insert_object(scene, obj, lock_timelines, sync_ref, keep_fragment, original_parent_scene); /*safety check!!!*/ if (gf_list_find(scene->scene_objects, obj)<0) { - gf_mx_v(scene->mx_resources); return NULL; } if (obj->odm==NULL) { gf_list_del_item(scene->scene_objects, obj); + gf_mo_event_target_reset(obj); gf_mo_del(obj); - gf_mx_v(scene->mx_resources); return NULL; } + } else { + u32 j; + for (j=0; jresources); j++) { + GF_ObjectManager *odm = gf_list_get(scene->resources, j); + if (odm->ID == obj->OD_ID) { + obj->odm = odm; + break; + } + } } - - gf_mx_v(scene->mx_resources); return obj; } @@ -836,13 +1045,33 @@ GF_MediaObject *gf_scene_get_media_object(GF_Scene *scene, MFURL *url, u32 obj_t return gf_scene_get_media_object_ex(scene, url, obj_type_hint, lock_timelines, NULL, GF_FALSE, NULL); } + +static void gf_scene_get_video_size(GF_MediaObject *mo, u32 *w, u32 *h) +{ + u32 pixel_ar; + if (!gf_mo_get_visual_info(mo, w, h, NULL, &pixel_ar, NULL, NULL)) return; + if (pixel_ar) { + u32 n, d; + n = (pixel_ar>>16) & 0x0000FFFF; + d = (pixel_ar) & 0x0000FFFF; + *w = (*w * n) / d; + } +#ifndef GPAC_DISABLE_3D + if (mo->odm) { + if (mo->odm->parentscene->compositor->fpack==GF_3D_STEREO_TOP) *h /= 2; + else if (mo->odm->parentscene->compositor->fpack==GF_3D_STEREO_SIDE) *w /= 2; + } +#endif +} + + GF_EXPORT void gf_scene_setup_object(GF_Scene *scene, GF_ObjectManager *odm) { GF_MediaObject *obj; u32 i; - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[Scene] Setup object manager %d (MO %p)\n", odm->OD->objectDescriptorID, odm->mo)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[Scene] Setup object manager %d (MO %p)\n", odm->ID, odm->mo)); /*an object may already be assigned (when using ESD URLs, setup is performed twice)*/ if (odm->mo != NULL) goto existing; @@ -850,7 +1079,7 @@ void gf_scene_setup_object(GF_Scene *scene, GF_ObjectManager *odm) i=0; while ((obj = (GF_MediaObject*)gf_list_enum(scene->scene_objects, &i))) { /*make sure services are different*/ - if (obj->odm && (odm->net_service != obj->odm->net_service)) continue; + if (obj->odm && (odm->source_id != obj->odm->source_id)) continue; if (obj->OD_ID==GF_MEDIA_EXTERNAL_ID) { //assert(obj->odm); @@ -861,9 +1090,18 @@ void gf_scene_setup_object(GF_Scene *scene, GF_ObjectManager *odm) goto existing; } } - else if (obj->OD_ID == odm->OD->objectDescriptorID) { - assert(obj->odm==NULL); + else if (obj->OD_ID == odm->ID) { + GF_ObjectManager *old_odm = NULL; + //OD update may trigger reassignment of ODM + if (obj->odm && (obj->odm != odm)) { + old_odm = obj->odm; + } obj->odm = odm; + if (old_odm) { + if (old_odm->mo) old_odm->mo->odm = NULL; + old_odm->mo = NULL; + gf_odm_disconnect(old_odm, GF_TRUE); + } odm->mo = obj; goto existing; } @@ -872,28 +1110,33 @@ void gf_scene_setup_object(GF_Scene *scene, GF_ObjectManager *odm) odm->mo = gf_mo_new(); gf_list_add(scene->scene_objects, odm->mo); odm->mo->odm = odm; - odm->mo->OD_ID = odm->OD->objectDescriptorID; + odm->mo->OD_ID = odm->ID; existing: /*setup object type*/ - if (!odm->codec) odm->mo->type = GF_MEDIA_OBJECT_SCENE; - else if (odm->codec->type == GF_STREAM_VISUAL) odm->mo->type = GF_MEDIA_OBJECT_VIDEO; - else if (odm->codec->type == GF_STREAM_AUDIO) odm->mo->type = GF_MEDIA_OBJECT_AUDIO; - else if (odm->codec->type == GF_STREAM_TEXT) odm->mo->type = GF_MEDIA_OBJECT_TEXT; - else if (odm->codec->type == GF_STREAM_SCENE) odm->mo->type = GF_MEDIA_OBJECT_UPDATES; + if (!odm->type) odm->type = GF_MEDIA_OBJECT_SCENE; + else if (odm->type == GF_STREAM_VISUAL) odm->mo->type = GF_MEDIA_OBJECT_VIDEO; + else if (odm->type == GF_STREAM_AUDIO) odm->mo->type = GF_MEDIA_OBJECT_AUDIO; + else if (odm->type == GF_STREAM_TEXT) odm->mo->type = GF_MEDIA_OBJECT_TEXT; + else if (odm->type == GF_STREAM_SCENE) odm->mo->type = GF_MEDIA_OBJECT_UPDATES; /*update info*/ gf_mo_update_caps(odm->mo); /*media object playback has already been requested by the scene, trigger media start*/ if (odm->mo->num_open && !odm->state) { - gf_odm_start(odm, 0); + gf_odm_start(odm); if (odm->mo->speed != FIX_ONE) gf_odm_set_speed(odm, odm->mo->speed, GF_TRUE); } if ((odm->mo->type==GF_MEDIA_OBJECT_VIDEO) && scene->is_dynamic_scene && !odm->parentscene->root_od->addon) { gf_scene_force_size_to_video(scene, odm->mo); } + else if (!odm->scene_ns->source_filter && (odm->flags & GF_ODM_PASSTHROUGH)) { + u32 w, h; + gf_scene_get_video_size(odm->mo, &w, &h); + gf_sc_set_size(scene->compositor, w, h); + } /*invalidate scene for all nodes using the OD*/ - gf_term_invalidate_compositor(odm->term); + gf_sc_invalidate(odm->parentscene->compositor, NULL); } GF_EXPORT @@ -914,7 +1157,6 @@ void gf_scene_set_duration(GF_Scene *scene) max_dur = scene->root_od->duration; i=0; while ((odm = (GF_ObjectManager*)gf_list_enum(scene->resources, &i))) { - if (!odm->codec) continue; if (!ck || gf_odm_shares_clock(odm, ck)) { if (odm->duration>max_dur) max_dur = odm->duration; } @@ -937,15 +1179,14 @@ void gf_scene_set_duration(GF_Scene *scene) } #endif - if ((scene == scene->root_od->term->root_scene) && scene->root_od->term->user->EventProc) { + if (!scene->root_od->parentscene) { GF_Event evt; evt.type = GF_EVENT_DURATION; evt.duration.duration = dur; evt.duration.can_seek = (scene->root_od->flags & GF_ODM_NO_TIME_CTRL) ? GF_FALSE : GF_TRUE; if (dur<1.0) evt.duration.can_seek = 0; - gf_term_send_event(scene->root_od->term,&evt); + gf_sc_send_event(scene->compositor, &evt); } - } GF_EXPORT @@ -960,7 +1201,6 @@ void gf_scene_set_timeshift_depth(GF_Scene *scene) max_timeshift = scene->root_od->timeshift_depth; i=0; while ((odm = (GF_ObjectManager*)gf_list_enum(scene->resources, &i))) { - if (!odm->codec) continue; if (!ck || gf_odm_shares_clock(odm, ck)) { if (odm->timeshift_depth > max_timeshift) max_timeshift = odm->timeshift_depth; } @@ -1005,38 +1245,19 @@ void gf_scene_register_extra_graph(GF_Scene *scene, GF_SceneGraph *extra_scene, if (gf_list_find(scene->extra_scenes, extra_scene)<0) return; gf_list_del_item(scene->extra_scenes, extra_scene); /*for root scene*/ - if (scene->root_od->term->root_scene == scene) { - gf_sc_register_extra_graph(scene->root_od->term->compositor, extra_scene, 1); + if (! scene->root_od->parentscene) { + gf_sc_register_extra_graph(scene->compositor, extra_scene, 1); } } else { if (gf_list_find(scene->extra_scenes, extra_scene)>=0) return; gf_list_add(scene->extra_scenes, extra_scene); /*for root scene*/ - if (scene->root_od->term->root_scene == scene) { - gf_sc_register_extra_graph(scene->root_od->term->compositor, extra_scene, 0); + if (!scene->root_od->parentscene) { + gf_sc_register_extra_graph(scene->compositor, extra_scene, 0); } } } - -static void gf_scene_get_video_size(GF_MediaObject *mo, u32 *w, u32 *h) -{ - u32 pixel_ar; - if (!gf_mo_get_visual_info(mo, w, h, NULL, &pixel_ar, NULL, NULL)) return; - if (pixel_ar) { - u32 n, d; - n = (pixel_ar>>16) & 0x0000FFFF; - d = (pixel_ar) & 0x0000FFFF; - *w = (*w * n) / d; - } -#ifndef GPAC_DISABLE_3D - if (mo->odm) { - if (mo->odm->term->compositor->frame_packing==GF_3D_STEREO_TOP) *h /= 2; - else if (mo->odm->term->compositor->frame_packing==GF_3D_STEREO_SIDE) *w /= 2; - } -#endif -} - void gf_scene_force_size_to_video(GF_Scene *scene, GF_MediaObject *mo) { u32 w, h; @@ -1081,13 +1302,8 @@ static void IS_UpdateVideoPos(GF_Scene *scene) } gf_node_dirty_set((GF_Node *)tr, 0, 0); - gf_scene_set_addon_layout_info(scene, scene->addon_position, scene->addon_size_factor); - if (scene->root_od->term->root_scene == scene) { - //if (scene->graph_attached) gf_sc_set_scene(scene->root_od->term->compositor, NULL); - //gf_sc_set_scene(scene->root_od->term->compositor, scene->graph); - } } static GF_Node *is_create_node(GF_SceneGraph *sg, u32 tag, const char *def_name) @@ -1103,9 +1319,10 @@ static GF_Node *is_create_node(GF_SceneGraph *sg, u32 tag, const char *def_name) static Bool is_odm_url(SFURL *url, GF_ObjectManager *odm) { if (!url->OD_ID && !url->url) return 0; - if (odm->OD->objectDescriptorID != GF_MEDIA_EXTERNAL_ID) return (url->OD_ID==odm->OD->objectDescriptorID) ? 1 : 0; - if (!url->url || !odm->OD->URLString) return 0; - return !stricmp(url->url, odm->OD->URLString); + if (odm->ID != GF_MEDIA_EXTERNAL_ID) return (url->OD_ID==odm->ID) ? 1 : 0; + + if (!url->url || !odm->scene_ns || !odm->scene_ns->url) return 0; + return !stricmp(url->url, odm->scene_ns->url); } static void set_media_url(GF_Scene *scene, SFURL *media_url, GF_Node *node, MFURL *node_url, u32 type) @@ -1121,61 +1338,41 @@ static void set_media_url(GF_Scene *scene, SFURL *media_url, GF_Node *node, MFU count = gf_list_count(scene->resources); for (i=0; iresources, i); - if (odm->scalable_addon || !odm->OD) + if (odm->scalable_addon || !odm->ID) continue; if (type==GF_STREAM_TEXT) { - if (!odm->codec || ((odm->codec->type!=type) && (odm->codec->type!=GF_STREAM_ND_SUBPIC))) continue; + if (odm->type!=type) continue; } else if (type==GF_STREAM_SCENE) { - if (!odm->subscene || (!odm->subscene->scene_codec && !odm->subscene->is_dynamic_scene) ) continue; + if (!odm->subscene || !odm->subscene->is_dynamic_scene) continue; if (odm->subscene->root_od->addon) continue; } else { - if (!odm->codec || (odm->codec->type!=type)) continue; - - //browse from this ODM on untill we find an object with the desired OTI (if any) - if ((type==GF_STREAM_AUDIO) && odm->term->prefered_audio_codec_oti) { - u32 j; - for (j=i; jresources, j); - if (an_odm->codec && (an_odm->codec->oti==odm->term->prefered_audio_codec_oti)) { - odm = an_odm; - break; - } - } - } + if (odm->type!=type) continue; + //todo: select preferred media format ? } - if (scene->selected_service_id && (scene->selected_service_id != odm->OD->ServiceID)) { + if (scene->selected_service_id && (scene->selected_service_id != odm->ServiceID)) { //objects inserted from broadcast may have been played but not yet registered with the scene, we need to force a stop if ((odm->mo && !odm->mo->num_open) || !odm->mo) { if (odm->state==GF_ODM_STATE_PLAY) { - /*do not stop directly*/ - gf_term_lock_media_queue(odm->term, GF_TRUE); - /*if object not in media queue, add it*/ - if (gf_list_find(odm->term->media_queue, odm)<0) { - gf_list_add(odm->term->media_queue, odm); - } - odm->action_type = GF_ODM_ACTION_STOP; - gf_term_lock_media_queue(odm->term, GF_FALSE); + + gf_odm_stop(odm, GF_FALSE); } } continue; } - media_url->OD_ID = odm->OD->objectDescriptorID; - if (media_url->OD_ID==GF_MEDIA_EXTERNAL_ID) media_url->url = gf_strdup(odm->net_service->url); + media_url->OD_ID = odm->ID; + if (media_url->OD_ID==GF_MEDIA_EXTERNAL_ID) media_url->url = gf_strdup(odm->scene_ns->url); - if (!scene->dyn_ck) { - if (odm->subscene && odm->subscene->scene_codec) { - scene->dyn_ck = odm->subscene->scene_codec->ck; - } else if (odm->codec) { - scene->dyn_ck = odm->codec->ck; - } + //happens when switching service in a TS multiplex + if (!scene->root_od->ck) { + scene->root_od->ck = odm->ck; } if (odm->mo && (type==GF_STREAM_VISUAL)) { @@ -1222,45 +1419,48 @@ static void set_media_url(GF_Scene *scene, SFURL *media_url, GF_Node *node, MFU static void scene_video_mouse_move(void *param, GF_FieldInfo *field) { u32 i, count; + Bool supported = GF_FALSE; GF_Scene *scene = (GF_Scene *) param; SFVec2f tx_coord = * ((SFVec2f *) field->far_ptr); + GF_Node *n = gf_sg_find_node_by_name(scene->graph, "DYN_TOUCH"); if (!scene->visual_url.OD_ID) return; count = gf_list_count(scene->resources); for (i=0; iresources, i); if (!odm->mo) continue; - if (odm->codec && odm->mo->OD_ID && (odm->mo->OD_ID != GF_MEDIA_EXTERNAL_ID) && (odm->mo->OD_ID==scene->visual_url.OD_ID)) { - GF_Err e; - u32 w, h; - GF_CodecCapability cap; - cap.CapCode = GF_CODEC_INTERACT_COORDS; - w = FIX2INT( tx_coord.x * 0xFFFF); - h = FIX2INT( tx_coord.y * 0xFFFF); - cap.cap.valueInt = w<<16 | h; - - e = gf_codec_set_capability(odm->codec, cap); - if (e==GF_NOT_SUPPORTED) { - GF_Node *n = gf_sg_find_node_by_name(scene->graph, "DYN_TOUCH"); - if (n) ((M_TouchSensor *)n)->enabled = GF_FALSE; - } - return; + prop = gf_filter_pid_get_property_str(odm->pid, "MouseEvents"); + if (prop && prop->value.boolean) { + GF_FilterEvent evt; + supported = GF_TRUE; + GF_FEVT_INIT(evt, GF_FEVT_USER, odm->pid); + + evt.user_event.event.type = ((M_TouchSensor *)n)->isActive ? GF_EVENT_MOUSEDOWN : GF_EVENT_MOUSEUP; + evt.user_event.event.mouse.x = FIX2INT( tx_coord.x * odm->mo->width); + evt.user_event.event.mouse.y = FIX2INT( tx_coord.y * odm->mo->height); + + gf_filter_pid_send_event(odm->pid, &evt); + } } + if (!supported) { + if (n) ((M_TouchSensor *)n)->enabled = GF_FALSE; + } } -static GF_Node *load_vr_proto_node(GF_SceneGraph *sg, const char *def_name) +static GF_Node *load_vr_proto_node(GF_SceneGraph *sg, const char *name, const char *def_name) { GF_Proto *proto; GF_Node *node; - char *name = "urn:inet:gpac:builtin:VRGeometry"; - - proto = gf_sg_find_proto(sg, 0, name); + if (!name) name = "urn:inet:gpac:builtin:VRGeometry"; + + proto = gf_sg_find_proto(sg, 0, (char *) name); if (!proto) { MFURL *url; - proto = gf_sg_proto_new(sg, 0, name, GF_FALSE); + proto = gf_sg_proto_new(sg, 0, (char *) name, GF_FALSE); url = gf_sg_proto_get_extern_url(proto); if (url) url->vals = gf_malloc(sizeof(SFURL)); @@ -1269,7 +1469,6 @@ static GF_Node *load_vr_proto_node(GF_SceneGraph *sg, const char *def_name) return NULL; } url->count=1; - url->vals = gf_malloc(sizeof(SFURL)); url->vals[0].url = gf_strdup(name); } node = gf_sg_proto_create_instance(sg, proto); @@ -1305,15 +1504,15 @@ static void create_movie(GF_Scene *scene, GF_Node *root, const char *tr_name, co ((M_Appearance *)n2)->texture = (GF_Node *)mt; gf_node_register((GF_Node *)mt, n2); - if (scene->is_srd) { + if (scene->srd_type) { GF_Node *app = n2; - + if (scene->vr_type) { - n2 = load_vr_proto_node(scene->graph, name_geo); + n2 = load_vr_proto_node(scene->graph, NULL, name_geo); } else { n2 = is_create_node(scene->graph, TAG_MPEG4_Rectangle, name_geo); } - + ((M_Shape *)n1)->geometry = n2; gf_node_register(n2, n1); //force appearance material2D.filled = TRUE @@ -1343,27 +1542,23 @@ void gf_scene_regenerate(GF_Scene *scene) M_MovieTexture *mt; M_AnimationStream *as; M_Inline *dims; - M_Transform2D *addon_tr; - M_Layer2D *addon_layer; - M_Inline *addon_scene; if (scene->is_dynamic_scene != 1) return; - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[Inline] Regenerating scene graph for service %s\n", scene->root_od->net_service->url)); - - gf_sc_lock(scene->root_od->term->compositor, 1); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[Inline] Regenerating scene graph for service %s\n", scene->root_od->scene_ns->url)); - ac = (M_AudioClip *) gf_sg_find_node_by_name(scene->graph, "DYN_AUDIO"); + ac = (M_AudioClip *) gf_sg_find_node_by_name(scene->graph, "DYN_AUDIO1"); /*this is the first time, generate a scene graph*/ if (!ac) { - GF_Event evt; - + GF_Node *root; + /*create an OrderedGroup*/ n1 = is_create_node(scene->graph, scene->vr_type ? TAG_MPEG4_Group : TAG_MPEG4_OrderedGroup, NULL); gf_sg_set_root_node(scene->graph, n1); gf_node_register(n1, NULL); + root = n1; - if (! scene->root_od->parentscene) { + if (! scene->root_od->parentscene && !scene->compositor->dyn_filter_mode) { n2 = is_create_node(scene->graph, TAG_MPEG4_Background2D, "DYN_BACK"); gf_node_list_add_child( &((GF_ParentNode *)n1)->children, n2); gf_node_register(n2, n1); @@ -1371,17 +1566,14 @@ void gf_scene_regenerate(GF_Scene *scene) //create VP info regardless of VR type if (scene->vr_type) { - const char *opt; n2 = is_create_node(scene->graph, TAG_MPEG4_Viewpoint, "DYN_VP"); ((M_Viewpoint *)n2)->position.z = 0; - ((M_Viewpoint *)n2)->fieldOfView = GF_PI/2; - opt = gf_cfg_get_key(scene->root_od->term->user->config, "Compositor", "VRDefaultFOV"); - if (!opt) { - opt="1.570796326794897"; - gf_cfg_set_key(scene->root_od->term->user->config, "Compositor", "VRDefaultFOV", opt); - } - ((M_Viewpoint *)n2)->fieldOfView = FLT2FIX( atof(opt) ); +#ifndef GPAC_DISABLE_3D + ((M_Viewpoint *)n2)->fieldOfView = scene->compositor->fov; +#else + ((M_Viewpoint *)n2)->fieldOfView = GF_PI/2; +#endif gf_node_list_add_child( &((GF_ParentNode *)n1)->children, n2); gf_node_register(n2, n1); @@ -1403,7 +1595,7 @@ void gf_scene_regenerate(GF_Scene *scene) gf_node_list_add_child( &((GF_ParentNode *)n1)->children, n2); gf_node_register(n2, n1); - ac = (M_AudioClip *) is_create_node(scene->graph, TAG_MPEG4_AudioClip, "DYN_AUDIO"); + ac = (M_AudioClip *) is_create_node(scene->graph, TAG_MPEG4_AudioClip, "DYN_AUDIO1"); ac->startTime = gf_scene_get_time(scene); ((M_Sound2D *)n2)->source = (GF_Node *)ac; gf_node_register((GF_Node *)ac, n2); @@ -1424,6 +1616,10 @@ void gf_scene_regenerate(GF_Scene *scene) create_movie(scene, n1, "TR1", "DYN_VIDEO1", "DYN_GEOM1"); if (! scene->vr_type) { + M_Transform2D *addon_tr; + M_Layer2D *addon_layer; + M_Inline *addon_scene; + /*text streams controlled through AnimationStream*/ n1 = gf_sg_get_root_node(scene->graph); as = (M_AnimationStream *) is_create_node(scene->graph, TAG_MPEG4_AnimationStream, "DYN_TEXT"); @@ -1457,25 +1653,63 @@ void gf_scene_regenerate(GF_Scene *scene) gf_node_list_add_child( &((GF_ParentNode *)addon_layer)->children, (GF_Node*)addon_scene); gf_node_register((GF_Node *)addon_scene, (GF_Node *)addon_layer); } - + //VR mode, add VR headup + else { + GF_Node *vrhud = load_vr_proto_node(scene->graph, "urn:inet:gpac:builtin:VRHUD", NULL); + gf_node_list_add_child( &((GF_ParentNode *)root)->children, (GF_Node*)vrhud); + gf_node_register(vrhud, root); + } //send activation for sensors memset(&evt, 0, sizeof(GF_Event)); evt.type = GF_EVENT_SENSOR_REQUEST; evt.activate_sensor.activate = scene->vr_type; evt.activate_sensor.sensor_type = GF_EVENT_SENSOR_ORIENTATION; - if (gf_term_send_event(scene->root_od->term, &evt)==GF_TRUE) { - scene->root_od->term->orientation_sensors_active = scene->vr_type; + if (gf_sc_send_event(scene->compositor, &evt)==GF_TRUE) { + scene->compositor->orientation_sensors_active = scene->vr_type; } else { - scene->root_od->term->orientation_sensors_active = GF_FALSE; + scene->compositor->orientation_sensors_active = GF_FALSE; } } + if (scene->ambisonic_type) { + char szName[20]; + SFURL url; + u32 i, count; + GF_Node *an, *root = gf_sg_get_root_node(scene->graph); + url.url = NULL; + url.OD_ID = 0; - ac = (M_AudioClip *) gf_sg_find_node_by_name(scene->graph, "DYN_AUDIO"); - set_media_url(scene, &scene->audio_url, (GF_Node*)ac, &ac->url, GF_STREAM_AUDIO); + count = gf_list_count(scene->resources); + for (i=0; iresources, i); + if (!odm->ambi_ch_id) continue; + + sprintf(szName, "DYN_AUDIO%d", odm->ambi_ch_id); + an = gf_sg_find_node_by_name(scene->graph, szName); + if (!an) { + /*create an sound2D and an audioClip node*/ + an = is_create_node(scene->graph, TAG_MPEG4_Sound2D, NULL); + gf_node_list_add_child( &((GF_ParentNode *)root)->children, an); + gf_node_register(an, root); + + ac = (M_AudioClip *) is_create_node(scene->graph, TAG_MPEG4_AudioClip, szName); + ac->startTime = gf_scene_get_time(scene); + ((M_Sound2D *)an)->source = (GF_Node *)ac; + gf_node_register((GF_Node *)ac, an); + } + ac = (M_AudioClip *) gf_sg_find_node_by_name(scene->graph, szName); - if (scene->is_srd) { + url.OD_ID = odm->ID; + set_media_url(scene, &url, (GF_Node*)ac, &ac->url, GF_STREAM_AUDIO); + } + } else { + ac = (M_AudioClip *) gf_sg_find_node_by_name(scene->graph, "DYN_AUDIO1"); + set_media_url(scene, &scene->audio_url, (GF_Node*)ac, &ac->url, GF_STREAM_AUDIO); + } + + + if (scene->srd_type) { char szName[20], szTex[20], szGeom[20]; u32 i, nb_srd = 0, srd_missing = 0; GF_ObjectManager *a_odm; @@ -1527,7 +1761,7 @@ void gf_scene_regenerate(GF_Scene *scene) scene->srd_min_y = min_y; scene->srd_max_x = max_x; scene->srd_max_y = max_y; - + url.url = NULL; gf_sg_get_scene_size_info(scene->graph, &sw, &sh); i=0; @@ -1538,21 +1772,23 @@ void gf_scene_regenerate(GF_Scene *scene) sprintf(szName, "TR%d", i); sprintf(szTex, "DYN_VIDEO%d", i); sprintf(szGeom, "DYN_GEOM%d", i); - url.OD_ID = a_odm->OD->objectDescriptorID; + url.OD_ID = a_odm->ID; mt = (M_MovieTexture *) gf_sg_find_node_by_name(scene->graph, szTex); if (!mt) continue; set_media_url(scene, &url, (GF_Node*)mt, &mt->url, GF_STREAM_VISUAL); - if (!scene->dyn_ck && a_odm->codec) { - scene->dyn_ck = a_odm->codec->ck; + if (!scene->root_od->ck && a_odm->ck) { + scene->root_od->ck = a_odm->ck; } - + if (scene->vr_type) { n2 = gf_sg_find_node_by_name(scene->graph, szGeom); gf_node_changed(n2, NULL); } else { + M_Transform2D *addon_tr; + tw = INT2FIX( sw * a_odm->mo->srd_w) / (max_x - min_x); th = INT2FIX(sh * a_odm->mo->srd_h) / (max_y - min_y); @@ -1588,19 +1824,19 @@ void gf_scene_regenerate(GF_Scene *scene) set_media_url(scene, &scene->dims_url, (GF_Node*)dims, &dims->url, GF_STREAM_SCENE); } - gf_sc_lock(scene->root_od->term->compositor, 0); - /*disconnect to force resize*/ - if (scene->root_od->term->root_scene == scene) { - gf_sc_set_scene(scene->root_od->term->compositor, scene->graph); + if (!scene->root_od->parentscene) { + gf_sc_set_scene(scene->compositor, scene->graph); scene->graph_attached = 1; + evt.type = GF_EVENT_STREAMLIST; - gf_term_send_event(scene->root_od->term, &evt); + gf_sc_send_event(scene->compositor, &evt); + IS_UpdateVideoPos(scene); } else { gf_scene_notify_event(scene, scene->graph_attached ? GF_EVENT_STREAMLIST : GF_EVENT_SCENE_ATTACHED, NULL, NULL, GF_OK, GF_FALSE); scene->graph_attached = 1; - gf_term_invalidate_compositor(scene->root_od->term); + gf_sc_invalidate(scene->compositor, NULL); } } @@ -1609,10 +1845,12 @@ void gf_scene_toggle_addons(GF_Scene *scene, Bool show_addons) M_Inline *dscene = (M_Inline *) gf_sg_find_node_by_name(scene->graph, "ADDON_SCENE"); if (show_addons) { +#ifdef FILTER_FIXME GF_AssociatedContentLocation addon_info; memset(&addon_info, 0, sizeof(GF_AssociatedContentLocation)); addon_info.timeline_id = -100; gf_scene_register_associated_media(scene, &addon_info); +#endif } else { gf_sg_vrml_mf_reset(&dscene->url, GF_SG_VRML_MFURL); } @@ -1643,13 +1881,18 @@ void gf_scene_select_main_addon(GF_Scene *scene, GF_ObjectManager *odm, Bool set static Bool check_odm_deactivate(SFURL *url, GF_ObjectManager *odm, GF_Node *n) { GF_FieldInfo info; + MFURL *mfurl; if (!is_odm_url(url, odm) || !n) return 0; + gf_node_get_field_by_name(n, "url", &info); + mfurl = (MFURL *)info.far_ptr; + if ((url->OD_ID!=GF_MEDIA_EXTERNAL_ID) && mfurl->count && (mfurl->vals[0].OD_ID==url->OD_ID)) + return 1; + if (url->url) gf_free(url->url); url->url = NULL; url->OD_ID = 0; - gf_node_get_field_by_name(n, "url", &info); gf_sg_vrml_mf_reset(info.far_ptr, GF_SG_VRML_MFURL); gf_node_get_field_by_name(n, "stopTime", &info); *((SFTime *)info.far_ptr) = gf_node_get_scene_time(n); @@ -1689,12 +1932,34 @@ static void odm_activate(SFURL *url, GF_Node *n) gf_node_changed(n, NULL); } +GF_EXPORT void gf_scene_set_service_id(GF_Scene *scene, u32 service_id) { if (!scene->is_dynamic_scene) return; - gf_sc_lock(scene->root_od->term->compositor, 1); + gf_sc_lock(scene->compositor, 1); if (scene->selected_service_id != service_id) { + u32 i; + GF_ObjectManager *odm, *remote_odm = NULL; + //delete all objects with given service ID + i=0; + while ((odm = gf_list_enum(scene->resources, &i))) { + if (odm->ServiceID != scene->selected_service_id) continue; + if (odm->redirect_url) { + remote_odm = odm; + assert(remote_odm->scene_ns->nb_odm_users); + remote_odm->scene_ns->nb_odm_users--; + remote_odm->scene_ns = scene->root_od->scene_ns; + remote_odm->scene_ns->nb_odm_users++; + } + //delete all objects from this service + else if (remote_odm) { + if (odm->scene_ns==remote_odm->scene_ns) odm->scene_ns->owner = odm; + gf_odm_disconnect(odm, 2); + } + } + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[Scene] Switching %s from service %d to service %d (media time %g)\n", scene->root_od->scene_ns->url, scene->selected_service_id, service_id, (Double)scene->root_od->media_start_time/1000.0)); + scene->selected_service_id = service_id; scene->audio_url.OD_ID = 0; scene->visual_url.OD_ID = 0; @@ -1702,14 +1967,28 @@ void gf_scene_set_service_id(GF_Scene *scene, u32 service_id) scene->dims_url.OD_ID = 0; scene->force_size_set = 0; //reset clock since we change service IDs, but request a PLAY from the current time - if (scene->dyn_ck) { - scene->root_od->media_start_time = gf_clock_media_time(scene->dyn_ck); - scene->dyn_ck = NULL; + if (scene->root_od->ck) { + scene->root_od->media_start_time = gf_clock_media_time(scene->root_od->ck); + scene->root_od->ck = NULL; + } + + if (remote_odm) { + i=0; + while ((odm = gf_list_enum(scene->resources, &i))) { + if (odm->ServiceID!=scene->selected_service_id) continue; + if (odm->redirect_url) { + //gf_odm_setup_object will increment the number of odms in net service (it's supposed to + //be called only upon startup, but we reuse the function). Since we are already registered + //with the service, decrement before calling + odm->scene_ns->nb_odm_users--; + gf_odm_setup_object(odm, odm->scene_ns, odm->pid); + } + break; + } } - GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[Scene] Switching %s from service %d to service %d (media time %g)\n", scene->root_od->net_service->url, scene->selected_service_id, service_id, (Double)scene->root_od->media_start_time/1000.0)); gf_scene_regenerate(scene); } - gf_sc_lock(scene->root_od->term->compositor, 0); + gf_sc_lock(scene->compositor, 0); } GF_EXPORT @@ -1718,26 +1997,29 @@ void gf_scene_select_object(GF_Scene *scene, GF_ObjectManager *odm) char *url; if (!scene->is_dynamic_scene || !scene->graph_attached || !odm) return; - if (!odm->codec) { + if (!odm->ID) { if (!odm->addon) return; } - if (odm->OD->ServiceID && scene->selected_service_id && (scene->selected_service_id != odm->OD->ServiceID)) { - gf_scene_set_service_id(scene, odm->OD->ServiceID); + if (odm->ServiceID && scene->selected_service_id && (scene->selected_service_id != odm->ServiceID)) { + gf_scene_set_service_id(scene, odm->ServiceID); return; } if (odm->state) { - if (check_odm_deactivate(&scene->audio_url, odm, gf_sg_find_node_by_name(scene->graph, "DYN_AUDIO")) ) return; + if (check_odm_deactivate(&scene->audio_url, odm, gf_sg_find_node_by_name(scene->graph, "DYN_AUDIO1")) ) return; if (check_odm_deactivate(&scene->visual_url, odm, gf_sg_find_node_by_name(scene->graph, "DYN_VIDEO1") )) return; if (check_odm_deactivate(&scene->text_url, odm, gf_sg_find_node_by_name(scene->graph, "DYN_TEXT") )) return; } - if (!odm->codec && odm->subscene) { + if (!odm->ID && odm->subscene) { M_Inline *dscene = (M_Inline *) gf_sg_find_node_by_name(scene->graph, "ADDON_SCENE"); + if (!dscene) + return; + if (odm->addon && odm->addon->addon_type==GF_ADDON_TYPE_MAIN) { return; } @@ -1748,14 +2030,14 @@ void gf_scene_select_object(GF_Scene *scene, GF_ObjectManager *odm) return; } - if (odm->codec->type == GF_STREAM_AUDIO) { - M_AudioClip *ac = (M_AudioClip *) gf_sg_find_node_by_name(scene->graph, "DYN_AUDIO"); + if (odm->type == GF_STREAM_AUDIO) { + M_AudioClip *ac = (M_AudioClip *) gf_sg_find_node_by_name(scene->graph, "DYN_AUDIO1"); if (!ac) return; if (scene->audio_url.url) gf_free(scene->audio_url.url); scene->audio_url.url = NULL; - scene->audio_url.OD_ID = odm->OD->objectDescriptorID; + scene->audio_url.OD_ID = odm->ID; if (!ac->url.count) gf_sg_vrml_mf_alloc(&ac->url, GF_SG_VRML_MFURL, 1); - ac->url.vals[0].OD_ID = odm->OD->objectDescriptorID; + ac->url.vals[0].OD_ID = odm->ID; if (ac->url.vals[0].url) { gf_free(ac->url.vals[0].url); ac->url.vals[0].url = NULL; @@ -1770,14 +2052,14 @@ void gf_scene_select_object(GF_Scene *scene, GF_ObjectManager *odm) return; } - if (odm->codec->type == GF_STREAM_VISUAL) { + if (odm->type == GF_STREAM_VISUAL) { M_MovieTexture *mt = (M_MovieTexture*) gf_sg_find_node_by_name(scene->graph, "DYN_VIDEO1"); if (!mt) return; if (scene->visual_url.url) gf_free(scene->visual_url.url); scene->visual_url.url = NULL; - scene->visual_url.OD_ID = odm->OD->objectDescriptorID; + scene->visual_url.OD_ID = odm->ID; if (!mt->url.count) gf_sg_vrml_mf_alloc(&mt->url, GF_SG_VRML_MFURL, 1); - mt->url.vals[0].OD_ID = odm->OD->objectDescriptorID; + mt->url.vals[0].OD_ID = odm->ID; if (mt->url.vals[0].url) gf_free(mt->url.vals[0].url); url = odm->mo->URLs.count ? odm->mo->URLs.vals[0].url : NULL; if (url) { @@ -1787,19 +2069,19 @@ void gf_scene_select_object(GF_Scene *scene, GF_ObjectManager *odm) mt->startTime = gf_scene_get_time(scene); gf_node_changed((GF_Node *)mt, NULL); if (odm->mo) gf_scene_force_size_to_video(scene, odm->mo); - scene->selected_service_id = odm->OD->ServiceID; + scene->selected_service_id = odm->ServiceID; return; } - if (odm->codec->type == GF_STREAM_TEXT) { + if (odm->type == GF_STREAM_TEXT) { M_AnimationStream *as = (M_AnimationStream*) gf_sg_find_node_by_name(scene->graph, "DYN_TEXT"); if (!as) return; if (scene->text_url.url) gf_free(scene->text_url.url); scene->text_url.url = NULL; - scene->text_url.OD_ID = odm->OD->objectDescriptorID; + scene->text_url.OD_ID = odm->ID; if (!as->url.count) gf_sg_vrml_mf_alloc(&as->url, GF_SG_VRML_MFURL, 1); - as->url.vals[0].OD_ID = odm->OD->objectDescriptorID; + as->url.vals[0].OD_ID = odm->ID; if (as->url.vals[0].url) gf_free(as->url.vals[0].url); url = odm->mo->URLs.count ? odm->mo->URLs.vals[0].url : NULL; if (url) { @@ -1815,14 +2097,13 @@ void gf_scene_select_object(GF_Scene *scene, GF_ObjectManager *odm) void gf_scene_select_main_addon(GF_Scene *scene, GF_ObjectManager *odm, Bool set_on, u32 current_clock_time) { GF_DOM_Event devt; - const char *opt = gf_cfg_get_key(scene->root_od->term->user->config, "Systems", "DebugPVRScene"); - M_Inline *dscene = (M_Inline *) gf_sg_find_node_by_name(scene->graph, (opt && !strcmp(opt, "yes")) ? "ADDON_SCENE" : "PVR_SCENE"); + M_Inline *dscene = (M_Inline *) gf_sg_find_node_by_name(scene->graph, scene->compositor->dbgpvr ? "ADDON_SCENE" : "PVR_SCENE"); if (scene->main_addon_selected==set_on) return; scene->main_addon_selected = set_on; if (set_on) { - odm_deactivate(gf_sg_find_node_by_name(scene->graph, "DYN_AUDIO")); + odm_deactivate(gf_sg_find_node_by_name(scene->graph, "DYN_AUDIO1")); odm_deactivate(gf_sg_find_node_by_name(scene->graph, "DYN_VIDEO1")); odm_deactivate(gf_sg_find_node_by_name(scene->graph, "DYN_TEXT")); @@ -1844,14 +2125,14 @@ void gf_scene_select_main_addon(GF_Scene *scene, GF_ObjectManager *odm, Bool set gf_sg_vrml_field_copy(&dscene->url, &odm->mo->URLs, GF_SG_VRML_MFURL); gf_node_changed((GF_Node *)dscene, NULL); } else { - GF_Clock *ck = scene->scene_codec ? scene->scene_codec->ck : scene->dyn_ck; + GF_Clock *ck = scene->root_od->ck; //reactivating the main content will trigger a reset on the clock - remember where we are and resume from this point scene->root_od->media_start_time = gf_clock_media_time(ck); scene->sys_clock_at_main_activation = 0; scene->obj_clock_at_main_activation = 0; - odm_activate(&scene->audio_url, gf_sg_find_node_by_name(scene->graph, "DYN_AUDIO")); + odm_activate(&scene->audio_url, gf_sg_find_node_by_name(scene->graph, "DYN_AUDIO1")); odm_activate(&scene->visual_url, gf_sg_find_node_by_name(scene->graph, "DYN_VIDEO1")); odm_activate(&scene->text_url, gf_sg_find_node_by_name(scene->graph, "DYN_TEXT")); @@ -1949,8 +2230,8 @@ void gf_scene_restart_dynamic(GF_Scene *scene, s64 from_time, Bool restart_only, from_time = 0; } - ck = scene->dyn_ck; - if (scene->scene_codec) ck = scene->scene_codec->ck; + ck = scene->root_od->ck; + if (!scene->is_dynamic_scene) ck = scene->root_od->ck; if (!ck) return; //first pass to check if we need to enable the addon acting as time shifting @@ -1960,8 +2241,8 @@ void gf_scene_restart_dynamic(GF_Scene *scene, s64 from_time, Bool restart_only, if (odm->addon && (odm->addon->addon_type==GF_ADDON_TYPE_MAIN)) { //assign clock if not yet available - if (odm->addon->root_od->subscene && !odm->addon->root_od->subscene->dyn_ck) - odm->addon->root_od->subscene->dyn_ck = scene->dyn_ck; + if (odm->addon->root_od->subscene && !odm->addon->root_od->ck) + odm->addon->root_od->ck = scene->root_od->ck; //we're timeshifting through the main addon, activate it if (from_time < -1) { @@ -1985,34 +2266,32 @@ void gf_scene_restart_dynamic(GF_Scene *scene, s64 from_time, Bool restart_only, to_restart = gf_list_new(); i=0; while ((odm = (GF_ObjectManager*)gf_list_enum(scene->resources, &i))) { - if (gf_odm_shares_clock(odm, ck)) { - if (odm->state != GF_ODM_STATE_BLOCKED) { + //do not restart not selected objects + if (odm->state != GF_ODM_STATE_PLAY) + continue; - //object is not an addon and main addon is selected, do not add - if (!odm->addon && scene->main_addon_selected) { - } - //object is an addon and enabled, restart if main and main is enabled, or if not main - else if (odm->addon && odm->addon->enabled) { - if (odm->addon->addon_type==GF_ADDON_TYPE_MAIN) { - if (scene->main_addon_selected) { - gf_list_add(to_restart, odm); - } - } else { + if (gf_odm_shares_clock(odm, ck)) { + //object is not an addon and main addon is selected, do not add + if (!odm->addon && scene->main_addon_selected) { + } + //object is an addon and enabled, restart if main and main is enabled, or if not main + else if (odm->addon && odm->addon->enabled) { + if (odm->addon->addon_type==GF_ADDON_TYPE_MAIN) { + if (scene->main_addon_selected) { gf_list_add(to_restart, odm); } - } else if (!scene->selected_service_id || (scene->selected_service_id==odm->OD->ServiceID)) { + } else { gf_list_add(to_restart, odm); } - - if (odm->state == GF_ODM_STATE_PLAY) { - gf_odm_stop(odm, 1); - } + } else if (!scene->selected_service_id || (scene->selected_service_id==odm->ServiceID) ) { + gf_odm_stop(odm, 1); + gf_list_add(to_restart, odm); } } } if (!restart_only) { - GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[Scene] Restarting from "LLD"\n", LLD_CAST from_time)); + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[Scene] Restarting from "LLD"\n", from_time)); /*reset clock*/ gf_clock_reset(ck); @@ -2035,14 +2314,14 @@ void gf_scene_restart_dynamic(GF_Scene *scene, s64 from_time, Bool restart_only, if (odm->subscene && odm->subscene->is_dynamic_scene) { gf_scene_restart_dynamic(odm->subscene, from_time, 0, 0); } else { - gf_odm_start(odm, 0); + gf_odm_start(odm); } } gf_list_del(to_restart); /*also check nodes since they may be deactivated (end of stream)*/ if (scene->is_dynamic_scene) { - M_AudioClip *ac = (M_AudioClip *) gf_sg_find_node_by_name(scene->graph, "DYN_AUDIO"); + M_AudioClip *ac = (M_AudioClip *) gf_sg_find_node_by_name(scene->graph, "DYN_AUDIO1"); M_MovieTexture *mt = (M_MovieTexture *) gf_sg_find_node_by_name(scene->graph, "DYN_VIDEO1"); M_AnimationStream *as = (M_AnimationStream *) gf_sg_find_node_by_name(scene->graph, "DYN_TEXT"); if (ac) { @@ -2078,15 +2357,15 @@ void gf_scene_force_size(GF_Scene *scene, u32 width, u32 height) width /= 2; height /= 2; /*if we already processed a force size in 360, don't do it again*/ - if (scene->force_size_set) + if (scene->force_size_set) return; #ifndef GPAC_DISABLE_VRML scene->force_size_set = GF_TRUE; - if (! scene->is_srd) { + if (! scene->srd_type) { GF_Node *node = gf_sg_find_node_by_name(scene->graph, "DYN_GEOM1"); if (node && (((M_Sphere *)node)->radius == FIX_ONE)) { - u32 radius = MAX(width, height) / 2; + u32 radius = MAX(width, height)/4; ((M_Sphere *)node)->radius = - INT2FIX(radius); gf_node_changed(node, NULL); @@ -2096,34 +2375,40 @@ void gf_scene_force_size(GF_Scene *scene, u32 width, u32 height) } if (scene->is_dynamic_scene) { - GF_NetworkCommand com; - - memset(&com, 0, sizeof(GF_NetworkCommand)); - if (!scene->vr_type) { - com.base.command_type = GF_NET_SERVICE_HAS_FORCED_VIDEO_SIZE; - gf_term_service_command(scene->root_od->net_service, &com); - } - - if (scene->root_od->term->root_scene == scene) { - if (com.par.width && com.par.height) { - gf_sc_set_scene_size(scene->root_od->term->compositor, width, height, 1); + u32 serv_w=0, serv_h=0; + GF_FilterPid *pid = gf_filter_get_ipid(scene->compositor->filter, 0); + const GF_PropertyValue *prop; + prop = gf_filter_pid_get_property(pid, GF_PROP_SERVICE_WIDTH); + if (prop) serv_w = prop->value.uint; + prop = gf_filter_pid_get_property(pid, GF_PROP_SERVICE_HEIGHT); + if (prop) serv_h = prop->value.uint; + + + if (!scene->root_od->parentscene) { + if (serv_w && serv_h) { + gf_sc_set_scene_size(scene->compositor, width, height, 1); if (!scene->force_size_set) { - gf_sc_set_size(scene->root_od->term->compositor, com.par.width, com.par.height); + gf_sc_set_size(scene->compositor, serv_w, serv_h); scene->force_size_set = 1; } else { - gf_sc_set_size(scene->root_od->term->compositor, 0, 0); + gf_sc_set_size(scene->compositor, 0, 0); } } else { - /*need output resize*/ - gf_sg_set_scene_size_info(scene->graph, width, height, 1); - gf_sc_set_scene(scene->root_od->term->compositor, scene->graph); - gf_sc_set_size(scene->root_od->term->compositor, width, height); + if (scene->vr_type) { + width = MAX(width, height) / 2; + gf_sg_set_scene_size_info(scene->graph, 0, 0, 1); + } else { + /*need output resize*/ + gf_sg_set_scene_size_info(scene->graph, width, height, 1); + gf_sc_set_scene(scene->compositor, scene->graph); + gf_sc_set_size(scene->compositor, width, height); + } } } else if (!scene->force_size_set) { - if (com.par.width && com.par.height) { - width = com.par.width; - height = com.par.height; + if (serv_w && serv_h) { + width = serv_w; + height = serv_h; } if (scene->vr_type) { gf_sg_set_scene_size_info(scene->graph, 0, 0, 1); @@ -2134,7 +2419,7 @@ void gf_scene_force_size(GF_Scene *scene, u32 width, u32 height) } else { u32 w, h; gf_sg_get_scene_size_info(scene->graph, &w, &h); - if (!com.par.width && !com.par.height && ((wgraph, width, height, 1); } else { GF_DOM_Event devt; @@ -2156,10 +2441,10 @@ void gf_scene_force_size(GF_Scene *scene, u32 width, u32 height) } else if (scene->root_od->parentscene && scene->root_od->parentscene->is_dynamic_scene) { gf_sg_set_scene_size_info(scene->root_od->parentscene->graph, width, height, gf_sg_use_pixel_metrics(scene->root_od->parentscene->graph)); - if (scene->root_od->term->root_scene == scene->root_od->parentscene) { + if (!scene->root_od->parentscene) { if (width && height) { - gf_sc_set_scene_size(scene->root_od->term->compositor, width, height, GF_TRUE); - gf_sc_set_size(scene->root_od->term->compositor, width, height); + gf_sc_set_scene_size(scene->compositor, width, height, GF_TRUE); + gf_sc_set_size(scene->compositor, width, height); } } } @@ -2169,7 +2454,7 @@ void gf_scene_force_size(GF_Scene *scene, u32 width, u32 height) } else { gf_sg_set_scene_size_info(scene->graph, width, height, GF_TRUE); } - if (scene->is_srd) + if (scene->srd_type) gf_scene_regenerate(scene); #ifndef GPAC_DISABLE_VRML @@ -2185,7 +2470,6 @@ void gf_scene_force_size(GF_Scene *scene, u32 width, u32 height) GF_EXPORT Bool gf_scene_process_anchor(GF_Node *caller, GF_Event *evt) { - GF_Terminal *term; #ifndef GPAC_DISABLE_VRML u32 i; M_Inline *inl; @@ -2195,12 +2479,11 @@ Bool gf_scene_process_anchor(GF_Node *caller, GF_Event *evt) if (!sg) return 1; scene = (GF_Scene *)gf_sg_get_private(sg); if (!scene) return 1; - term = scene->root_od->term; /*if main scene forward to user. If no params or first one not "self" forward to user*/ - if ((term->root_scene==scene) || !evt->navigate.parameters || !evt->navigate.param_count || (stricmp(evt->navigate.parameters[0], "self") && stricmp(evt->navigate.parameters[0], "_self"))) { - if (term->user->EventProc) return gf_term_send_event(term, evt); - return 1; + if (! scene->root_od->parentscene || !evt->navigate.parameters || !evt->navigate.param_count || (stricmp(evt->navigate.parameters[0], "self") && stricmp(evt->navigate.parameters[0], "_self"))) { + + return gf_filter_ui_event(scene->compositor->filter, evt); } if (!scene->root_od->mo) return 1; @@ -2236,7 +2519,7 @@ GF_Compositor *gf_sc_get_compositor(GF_Node *node) if (!sg) return NULL; scene = (GF_Scene *)gf_sg_get_private(sg); if (!scene) return NULL; - return scene->root_od->term->compositor; + return scene->compositor; } const char *gf_scene_get_fragment_uri(GF_Node *node) @@ -2281,60 +2564,75 @@ GF_Node *gf_scene_get_subscene_root(GF_Node *node) } /*returns 0 if any of the clock still hasn't seen EOS*/ -Bool gf_scene_check_clocks(GF_ClientService *ns, GF_Scene *scene, Bool check_buffering) +Bool gf_scene_check_clocks(GF_SceneNamespace *ns, GF_Scene *scene, Bool check_buffering) { GF_Clock *ck; Bool initialized = GF_FALSE; u32 i; + if (scene) { GF_ObjectManager *odm; - if (scene->root_od->net_service != ns) { - if (!gf_scene_check_clocks(scene->root_od->net_service, scene, check_buffering)) return 0; + if (scene->root_od->scene_ns != ns) { + if (!gf_scene_check_clocks(scene->root_od->scene_ns, scene, check_buffering)) return 0; } i=0; while ( (odm = (GF_ObjectManager*)gf_list_enum(scene->resources, &i)) ) { - if (odm->net_service && (odm->net_service != ns)) { - if (!gf_scene_check_clocks(odm->net_service, NULL, check_buffering)) return 0; - } else if (odm->codec && odm->codec->CB) { + switch (odm->type) { + case GF_STREAM_OCR: + case GF_STREAM_INTERACT: + continue; + default: + break; + } + + if (odm->scene_ns && (odm->scene_ns != ns)) { + if (!gf_scene_check_clocks(odm->scene_ns, NULL, check_buffering)) return 0; + } else if (odm->ck) { initialized = GF_TRUE; if (!check_buffering) { - if (!gf_cm_is_eos(odm->codec->CB) ) { + if (! odm->has_seen_eos && (odm->state != GF_ODM_STATE_STOP) ) { return 0; } } else { - if (odm->codec->ck->Buffering) { + if (odm->ck->nb_buffering) { return 0; } } } } } + i=0; - while ( (ck = (GF_Clock *)gf_list_enum(ns->Clocks, &i) ) ) { + while (ns->clocks && (ck = (GF_Clock *)gf_list_enum(ns->clocks, &i) ) ) { initialized = GF_TRUE; if (!check_buffering) { - if (!ck->has_seen_eos) return 0; + Bool is_eos = ck->has_seen_eos; + //object not active consider it done + if (!is_eos && ns->owner->mo && !ns->owner->mo->num_open) + is_eos = GF_TRUE; + + if (!is_eos) return 0; } else { - if (ck->Buffering) return 0; + if (ck->nb_buffering) return 0; } } if (!check_buffering && scene) { - if (scene->scene_codec) { + if (scene->root_od->ID) { initialized = GF_TRUE; - if (scene->scene_codec->Status != GF_ESM_CODEC_STOP) return 0; + if (scene->root_od->parentscene && (scene->root_od->state != GF_ODM_STATE_STOP)) return 0; } - if (scene->od_codec && (scene->od_codec->Status != GF_ESM_CODEC_STOP)) return 0; } if (!initialized) return 0; + return 1; } const char *gf_scene_get_service_url(GF_SceneGraph *sg) { GF_Scene *scene = gf_sg_get_private(sg); - if (scene) return scene->root_od->net_service->url; + if (scene) return scene->root_od->scene_ns->url; return NULL; } @@ -2342,17 +2640,17 @@ Bool gf_scene_is_over(GF_SceneGraph *sg) { u32 i, count; GF_Scene *scene = gf_sg_get_private(sg); - if (!scene) return 0; - if (scene->scene_codec) - return (scene->scene_codec->Status==GF_ESM_CODEC_EOS) ? 1 : 0; + if (!scene) return GF_FALSE; + if (scene->root_od->has_seen_eos) + return scene->root_od->ck->has_seen_eos; count = gf_list_count(scene->resources); for (i=0; iresources, i); - if (odm->codec && (odm->codec->Status != GF_ESM_CODEC_EOS) && (odm->codec->Status!=GF_ESM_CODEC_STOP)) return 0; - if (odm->subscene && !gf_scene_is_over(odm->subscene->graph) ) return 0; + if (!odm->has_seen_eos || !odm->ck->has_seen_eos) return GF_FALSE; + if (odm->subscene && !gf_scene_is_over(odm->subscene->graph) ) return GF_FALSE; } - return 1; + return GF_TRUE; } GF_SceneGraph *gf_scene_enum_extra_scene(GF_SceneGraph *sg, u32 *i) @@ -2384,7 +2682,7 @@ void gf_scene_generate_views(GF_Scene *scene, char *url, char *parent_path) M_Inline *inl; #endif GF_Event evt; - gf_sc_node_destroy(scene->root_od->term->compositor, NULL, scene->graph); + gf_sc_node_destroy(scene->compositor, NULL, scene->graph); gf_sg_reset(scene->graph); scene->force_single_timeline = 1; @@ -2404,12 +2702,7 @@ void gf_scene_generate_views(GF_Scene *scene, char *url, char *parent_path) char *sep; if (use_old_syntax) { - sep = strchr(url_search, ':'); - /*if :// or :\ is found, skip it*/ - if (sep && ( ((sep[1] == '/') && (sep[2] == '/')) || (sep[1] == '\\') ) ) { - url_search = sep+1; - continue; - } + sep = gf_url_colon_suffix(url_search); } else { sep = strstr(url_search, "::"); } @@ -2458,26 +2751,129 @@ void gf_scene_generate_views(GF_Scene *scene, char *url, char *parent_path) url_search = url; } - gf_sc_set_option(scene->root_od->term->compositor, GF_OPT_USE_OPENGL, 1); + gf_sc_set_option(scene->compositor, GF_OPT_USE_OPENGL, 1); - gf_sg_set_scene_size_info(scene->graph, 0, 0, 1); - gf_sc_set_scene(scene->root_od->term->compositor, scene->graph); - scene->graph_attached = 1; scene->is_dynamic_scene = 2; + gf_sg_set_scene_size_info(scene->graph, 0, 0, 1); + + gf_scene_attach_to_compositor(scene); evt.type = GF_EVENT_CONNECT; evt.connect.is_connected = 1; - gf_term_send_event(scene->root_od->term, &evt); + gf_sc_send_event(scene->compositor, &evt); #endif } -void scene_reset_addon(GF_AddonMedia *addon, Bool disconnect) +void gf_scene_generate_mosaic(GF_Scene *scene, char *url, char *parent_path) { - if (disconnect && addon->root_od) { - gf_odm_disconnect(addon->root_od, 1); +#ifndef GPAC_DISABLE_VRML + char *url_search, *cur_url; + Bool use_old_syntax = 1; + GF_Node *n1; + M_Inline *inl; + Bool first_pass = GF_TRUE; + u32 nb_items=0, nb_rows=0, nb_cols=0; + s32 width=1920, height=1080, x=0, y=0, tw=1920, th=1080; + + GF_Event evt; + gf_sc_node_destroy(scene->compositor, NULL, scene->graph); + gf_sg_reset(scene->graph); + + scene->force_single_timeline = GF_FALSE; + n1 = is_create_node(scene->graph, TAG_MPEG4_OrderedGroup, NULL); + gf_sg_set_root_node(scene->graph, n1); + gf_node_register(n1, NULL); + + if (strstr(url, "::")) use_old_syntax = 0; + +restart: + url_search = cur_url = url; + x = y = 0; + while (1) { + char *sep; + + if (use_old_syntax) { + sep = strchr(url_search, ':'); + /*if :// or :\ is found, skip it*/ + if (sep && ( ((sep[1] == '/') && (sep[2] == '/')) || (sep[1] == '\\') ) ) { + url_search = sep+1; + continue; + } + } else { + sep = strstr(url_search, "::"); + } + if (sep) sep[0] = 0; + + if (first_pass) { + nb_items ++; + } else { + GF_Node *tr = is_create_node(scene->graph, TAG_MPEG4_Transform2D, NULL); + GF_Node *layer = is_create_node(scene->graph, TAG_MPEG4_Layer2D, NULL); + gf_node_register(tr, n1); + gf_node_list_add_child( &((GF_ParentNode *)n1)->children, tr); + + ((M_Transform2D *)tr)->translation.x = INT2FIX( -width/2 + tw/2 + x*tw); + ((M_Transform2D *)tr)->translation.y = INT2FIX( height/2 - th/2 - y*th); + + x++; + if (x==nb_cols) { + y++; + x=0; + } + + gf_node_register(layer, tr); + gf_node_list_add_child( &((M_Transform2D *)tr)->children, layer); + ((M_Layer2D *)layer)->size.x = INT2FIX(tw); + ((M_Layer2D *)layer)->size.y = INT2FIX(th); + + inl = (M_Inline *) is_create_node(scene->graph, TAG_MPEG4_Inline, NULL); + gf_node_list_add_child( &((M_Layer2D *)layer)->children, (GF_Node *)inl); + gf_node_register((GF_Node*) inl, layer); + + gf_sg_vrml_mf_reset(&inl->url, GF_SG_VRML_MFURL); + gf_sg_vrml_mf_append(&inl->url, GF_SG_VRML_MFURL, NULL); + inl->url.vals[0].url = gf_url_concatenate(parent_path, cur_url); + } + + if (!sep) break; + sep[0] = ':'; + if (use_old_syntax) { + cur_url = sep+1; + } else { + cur_url = sep+2; + } + url_search = cur_url; } + if (first_pass) { + first_pass = GF_FALSE; + nb_cols=(u32) gf_ceil( gf_sqrt(nb_items) ); + nb_rows=nb_items/nb_cols; + if (nb_cols * nb_rows < nb_items) nb_rows++; + tw = width/nb_cols; + th = height/nb_rows; + goto restart; + } + + scene->is_dynamic_scene = 2; + gf_sg_set_scene_size_info(scene->graph, width, height, 1); + + gf_scene_attach_to_compositor(scene); + + evt.type = GF_EVENT_CONNECT; + evt.connect.is_connected = 1; + gf_sc_send_event(scene->compositor, &evt); +#endif +} + + +void gf_scene_reset_addon(GF_AddonMedia *addon, Bool disconnect) +{ if (addon->root_od) { addon->root_od->addon = NULL; + if (disconnect) { + gf_scene_remove_object(addon->root_od->parentscene, addon->root_od, 2); + gf_odm_disconnect(addon->root_od, 1); + } } if (addon->url) gf_free(addon->url); @@ -2490,9 +2886,10 @@ void gf_scene_reset_addons(GF_Scene *scene) GF_AddonMedia *addon = gf_list_last(scene->declared_addons); gf_list_rem_last(scene->declared_addons); - scene_reset_addon(addon, 0); + gf_scene_reset_addon(addon, GF_FALSE); } } +#ifdef FILTER_FIXME static void load_associated_media(GF_Scene *scene, GF_AddonMedia *addon) { @@ -2512,12 +2909,16 @@ static void load_associated_media(GF_Scene *scene, GF_AddonMedia *addon) //we force the timeline of the addon to be locked with the main scene mo = gf_scene_get_media_object(scene, &url, GF_MEDIA_OBJECT_SCENE, GF_TRUE); - if (!mo || !mo->odm) return; + if (!mo || !mo->odm) { + assert(0); + return; + } addon->root_od = mo->odm; mo->odm->addon = addon; } + GF_EXPORT void gf_scene_register_associated_media(GF_Scene *scene, GF_AssociatedContentLocation *addon_info) { @@ -2547,8 +2948,7 @@ void gf_scene_register_associated_media(GF_Scene *scene, GF_AssociatedContentLoc gf_scene_toggle_addons(scene, GF_FALSE); gf_scene_remove_object(addon->root_od->parentscene, addon->root_od, 2); gf_odm_disconnect(addon->root_od, 1); - } - if (addon->root_od) { + addon->root_od->addon = NULL; } return; @@ -2557,13 +2957,30 @@ void gf_scene_register_associated_media(GF_Scene *scene, GF_AssociatedContentLoc if (addon_info->enable_if_defined) addon->enabled = GF_TRUE; + //declaration of start time + if (addon->is_splicing && (addon->splice_start<0) && addon_info->is_splicing) { + addon->splice_in_pts = addon_info->splice_time_pts; + + if (addon->splice_in_pts) { + addon->media_pts = (u64) (addon_info->splice_start_time); + addon->splice_start = addon_info->splice_start_time / 90; + addon->splice_end = addon_info->splice_end_time / 90; + } else { + addon->media_pts = (u64)(addon_info->splice_start_time * 90000); + addon->splice_start = addon_info->splice_start_time * 1000; + addon->splice_end = addon_info->splice_end_time * 1000; + } + } + //restart addon if (!addon->root_od && addon->timeline_ready && addon->enabled) { load_associated_media(scene, addon); } //nothing associated, deactivate addon - if (!addon_info->external_URL) { - scene_reset_addon(addon, 1); + if (!addon_info->external_URL || !strlen(addon_info->external_URL) ) { + gf_list_rem(scene->declared_addons, i); + gf_scene_reset_addon(addon, GF_TRUE); + gf_scene_toggle_addons(scene, GF_FALSE); } else if (strcmp(addon_info->external_URL, addon->url)) { //reconfigure addon gf_free(addon->url); @@ -2572,9 +2989,10 @@ void gf_scene_register_associated_media(GF_Scene *scene, GF_AssociatedContentLoc } return; } + addon = NULL; } - if (!addon_info->external_URL) { + if (!addon_info->external_URL || !strlen(addon_info->external_URL) ) { return; } @@ -2594,6 +3012,25 @@ void gf_scene_register_associated_media(GF_Scene *scene, GF_AssociatedContentLoc addon->url = gf_strdup(addon_info->external_URL); addon->media_timescale = 1; addon->timeline_ready = (addon_info->timeline_id<0) ? 1 : 0; + addon->splice_in_pts = addon_info->splice_time_pts; + if (addon_info->is_splicing) { + addon->addon_type = GF_ADDON_TYPE_SPLICED; + scene->has_splicing_addons = GF_TRUE; + + if (addon->splice_in_pts) { + addon->media_pts = (u64) (addon_info->splice_start_time); + addon->splice_start = addon_info->splice_start_time / 90; + addon->splice_end = addon_info->splice_end_time / 90; + } else { + addon->media_pts = (u64)(addon_info->splice_start_time * 90000); + + addon->splice_start = addon_info->splice_start_time * 1000; + addon->splice_end = addon_info->splice_end_time * 1000; + } + } else { + addon->splice_start = addon_info->splice_start_time; + addon->splice_end = addon_info->splice_end_time; + } if (!new_addon) return; @@ -2602,7 +3039,7 @@ void gf_scene_register_associated_media(GF_Scene *scene, GF_AssociatedContentLoc if (! scene->root_od->parentscene) { evt.type = GF_EVENT_ADDON_DETECTED; evt.addon_connect.addon_url = addon->url; - addon->enabled = gf_term_send_event(scene->root_od->term,&evt); + addon->enabled = gf_sc_send_event(scene->compositor, &evt); if (addon->timeline_ready) load_associated_media(scene, addon); @@ -2640,7 +3077,7 @@ void gf_scene_notify_associated_media_timeline(GF_Scene *scene, GF_AssociatedCon //this is a splicing point, discard all previsously declared splicing addons || prev_addon->is_splicing ) { - scene_reset_addon(prev_addon, GF_TRUE); + gf_scene_reset_addon(prev_addon, GF_TRUE); gf_list_rem(scene->declared_addons, i); i--; count--; @@ -2694,6 +3131,8 @@ void gf_scene_notify_associated_media_timeline(GF_Scene *scene, GF_AssociatedCon } } +#endif + Bool gf_scene_check_addon_restart(GF_AddonMedia *addon, u64 cts, u64 dts) { u32 i; @@ -2709,8 +3148,6 @@ Bool gf_scene_check_addon_restart(GF_AddonMedia *addon, u64 cts, u64 dts) return GF_FALSE; } - gf_mx_p(addon->root_od->mx); - addon->loop_detected = 0; addon->media_pts = addon->past_media_pts; addon->media_timestamp = addon->past_media_timestamp; @@ -2724,8 +3161,6 @@ Bool gf_scene_check_addon_restart(GF_AddonMedia *addon, u64 cts, u64 dts) GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("Looping addon - CTS "LLD" - addon media TS "LLD" (CTS "LLD") addon media time "LLD"\n", cts, addon->media_pts, addon->media_pts/90, addon->media_timestamp)); - gf_mx_v(addon->root_od->mx); - to_restart = gf_list_new(); i=0; @@ -2738,13 +3173,13 @@ Bool gf_scene_check_addon_restart(GF_AddonMedia *addon, u64 cts, u64 dts) i=0; while ((odm = (GF_ObjectManager*)gf_list_enum(to_restart, &i))) { - gf_odm_start(odm, 2); + gf_odm_start(odm); } gf_list_del(to_restart); return GF_TRUE; } -Double gf_scene_adjust_time_for_addon(GF_AddonMedia *addon, Double clock_time, u32 *timestamp_based) +Double gf_scene_adjust_time_for_addon(GF_AddonMedia *addon, Double clock_time, u8 *timestamp_based) { Double media_time; if (!addon->timeline_ready) @@ -2753,6 +3188,10 @@ Double gf_scene_adjust_time_for_addon(GF_AddonMedia *addon, Double clock_time, u if (timestamp_based) *timestamp_based = (addon->timeline_id>=0) ? 0 : 1; + if (addon->is_splicing) { + return ((Double)addon->media_timestamp) / addon->media_timescale; + } + //get PTS diff (clock is in ms, pt is in 90k) media_time = clock_time; media_time -= addon->media_pts/90000.0; @@ -2766,6 +3205,13 @@ s64 gf_scene_adjust_timestamp_for_addon(GF_AddonMedia *addon, u64 orig_ts) { s64 media_ts_ms; assert(addon->timeline_ready); + if (addon->is_splicing) { + if (!addon->min_dts_set || (orig_tssplice_min_dts)) { + addon->splice_min_dts = orig_ts; + addon->min_dts_set = GF_TRUE; + } + orig_ts -= addon->splice_min_dts; + } media_ts_ms = orig_ts; media_ts_ms -= (addon->media_timestamp*1000) / addon->media_timescale; media_ts_ms += (addon->media_pts/90); @@ -2774,20 +3220,17 @@ s64 gf_scene_adjust_timestamp_for_addon(GF_AddonMedia *addon, u64 orig_ts) void gf_scene_select_scalable_addon(GF_Scene *scene, GF_ObjectManager *odm) { - GF_NetworkCommand com; - GF_CodecCapability caps; +#ifdef FILTER_FIXME Bool nalu_annex_b; - GF_Channel *ch, *base_ch; - GF_ObjectManager *odm_base = NULL; - u32 i, count, mtype; Bool force_attach=GF_FALSE; - ch = gf_list_get(odm->channels, 0); - if (!ch->esd) return; - mtype = ch->esd->decoderConfig->streamType; +#endif + GF_ObjectManager *odm_base = NULL; + u32 i, count; + count = gf_list_count(scene->resources); for (i=0; iresources, i); - if ((mtype==odm_base->codec->type) && odm_base->codec) + if ((odm->type==odm_base->type) && odm_base->ID) break; odm_base=NULL; //todo @@ -2796,25 +3239,35 @@ void gf_scene_select_scalable_addon(GF_Scene *scene, GF_ObjectManager *odm) } if (!odm_base) return; - base_ch = gf_list_get(odm_base->channels, 0); - - switch (base_ch->esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_VIDEO_AVC: - case GPAC_OTI_VIDEO_SVC: - case GPAC_OTI_VIDEO_MVC: - switch (ch->esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_VIDEO_LHVC: - if (!odm_base->codec->hybrid_layered_coded) { + odm_base->upper_layer_odm = odm; + odm->lower_layer_odm = odm_base; + + switch (odm_base->original_oti) { + case GF_CODECID_AVC: + case GF_CODECID_SVC: + case GF_CODECID_MVC: + switch (odm->original_oti) { + case GF_CODECID_LHVC: + if (!odm_base->hybrid_layered_coded) { +#ifdef FILTER_FIXME force_attach=GF_TRUE; +#endif + } - odm_base->codec->hybrid_layered_coded=GF_TRUE; + odm_base->hybrid_layered_coded=GF_TRUE; break; } break; - case GPAC_OTI_VIDEO_HEVC: + case GF_CODECID_HEVC: +#ifdef FILTER_FIXME force_attach=GF_TRUE; +#endif break; } + odm->lower_layer_odm = odm_base; + +#ifdef FILTER_FIXME + GF_NetworkCommand com; if (odm_base->upper_layer_odm) { force_attach=GF_FALSE; @@ -2826,8 +3279,9 @@ void gf_scene_select_scalable_addon(GF_Scene *scene, GF_ObjectManager *odm) nalu_annex_b = 1; if (base_ch->esd->decoderConfig->decoderSpecificInfo && base_ch->esd->decoderConfig->decoderSpecificInfo->dataLength) nalu_annex_b = 0; - - if (0 && odm_base->codec->hybrid_layered_coded && ch->esd->decoderConfig->decoderSpecificInfo && ch->esd->decoderConfig->decoderSpecificInfo->dataLength) { + + if (odm_base->hybrid_layered_coded && ch->esd->decoderConfig->decoderSpecificInfo && ch->esd->decoderConfig->decoderSpecificInfo->dataLength) { + nalu_annex_b = 0; if (force_attach) { odm_base->codec->decio->AttachStream(odm_base->codec->decio, ch->esd); @@ -2840,6 +3294,7 @@ void gf_scene_select_scalable_addon(GF_Scene *scene, GF_ObjectManager *odm) } odm_base->codec->decio->AttachStream(odm_base->codec->decio, ch->esd); } + memset(&com, 0, sizeof(GF_NetworkCommand)); com.command_type = GF_NET_CHAN_NALU_MODE; //force AnnexB mode and no sync sample seeking @@ -2851,11 +3306,60 @@ void gf_scene_select_scalable_addon(GF_Scene *scene, GF_ObjectManager *odm) ch->stream_state = 1; ch->media_padding_bytes = base_ch->media_padding_bytes; gf_term_service_command(ch->service, &com); - } - //signal to the base decoder that we will want full quality caps.CapCode = GF_CODEC_MEDIA_SWITCH_QUALITY; - caps.cap.valueInt = 2; + // splicing, signal to the base decoder that we will want low quality and wait for splice activation + if (odm->parentscene->root_od->addon->is_splicing) { + caps.cap.valueInt = 0; + } + //not splicing, signal to the base decoder that we will want full quality right now + else { + caps.cap.valueInt = 2; + } odm_base->codec->decio->SetCapabilities(odm_base->codec->decio, caps); +#endif +} + + +GF_EXPORT +void gf_scene_switch_quality(GF_Scene *scene, Bool up) +{ + u32 i; + GF_ObjectManager *odm; + GF_FilterEvent evt; + + if (!scene) return; + + GF_FEVT_INIT(evt, GF_FEVT_QUALITY_SWITCH, NULL); + evt.quality_switch.up = up; + + if (scene->root_od->pid) { + gf_filter_pid_send_event(scene->root_od->pid, &evt); + if (scene->root_od->extra_pids) { + GF_ODMExtraPid *xpid; + i=0; + while ( (xpid = gf_list_enum(scene->root_od->extra_pids, &i) ) ) { + gf_filter_pid_send_event(xpid->pid, &evt); + } + } + } + + i=0; + while (NULL != (odm = gf_list_enum(scene->resources, &i))) { + if (odm->pid) { + gf_filter_pid_send_event(odm->pid, &evt); + } + if (odm->subscene) + gf_scene_switch_quality(odm->subscene, up); + +#ifdef FILTER_FIXME + if (odm->scalable_addon) { + if (up) + gf_odm_start(odm, 0); + else + gf_odm_stop(odm, GF_FALSE); + } +#endif + } } diff --git a/src/terminal/term_node_init.c b/src/compositor/scene_node_init.c similarity index 82% rename from src/terminal/term_node_init.c rename to src/compositor/scene_node_init.c index a8aa102..060c03b 100644 --- a/src/terminal/term_node_init.c +++ b/src/compositor/scene_node_init.c @@ -2,10 +2,10 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2017 * All rights reserved * - * This file is part of GPAC / Media terminal sub-project + * This file is part of GPAC / Scene Compositor sub-project * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -23,11 +23,7 @@ * */ - -#include #include -/*includes MPEG4 nodes + input sensor stack*/ -#include "input_sensor.h" /*includes X3D nodes for WorldInfo, Inline and Key/String sensors*/ #include #include @@ -57,17 +53,19 @@ void TraverseKeyNavigator(GF_Node *node, void *rs, Bool is_destroy) if (is_destroy) { GF_Scene *scene = (GF_Scene *)gf_node_get_private(node); gf_list_del_item(scene->keynavigators, node); - gf_sc_key_navigator_del(scene->root_od->term->compositor, node); + gf_sc_key_navigator_del(scene->compositor, node); } } void on_kn_set_focus(GF_Node*node, GF_Route *_route) { - GF_Scene *scene = (GF_Scene *)gf_node_get_private(node); - gf_sc_change_key_navigator(scene->root_od->term->compositor, node); + if (node) { + GF_Scene *scene = (GF_Scene *)gf_node_get_private(node); + gf_sc_change_key_navigator(scene->compositor, node); + } } -void evaluate_term_cap(GF_Node *node, GF_Route *route) +void evaluate_scene_cap(GF_Node *node, GF_Route *route) { GF_SystemRTInfo rti; Double fps; @@ -80,7 +78,7 @@ void evaluate_term_cap(GF_Node *node, GF_Route *route) tc->value = 0; switch (tc->capability) { case 0: /*framerate*/ - fps = gf_sc_get_fps(scene->root_od->term->compositor, 1); + fps = gf_sc_get_fps(scene->compositor, 1); if (fps<=5.0) tc->value = 1; else if (fps<=10.0) tc->value = 2; else if (fps<=20.0) tc->value = 3; @@ -90,7 +88,7 @@ void evaluate_term_cap(GF_Node *node, GF_Route *route) case 1: /*colordepth*/ return; case 2: /*screensize*/ - height = scene->root_od->term->compositor->display_height; + height = scene->compositor->display_height; if (height<200) tc->value = 1; else if (height<400) tc->value = 2; else if (height<800) tc->value = 3; @@ -127,13 +125,13 @@ void evaluate_term_cap(GF_Node *node, GF_Route *route) /*GPAC extensions*/ case 100: /*display width*/ - tc->value = scene->root_od->term->compositor->display_width; + tc->value = scene->compositor->display_width; break; case 101: /*display height*/ - tc->value = scene->root_od->term->compositor->display_height; + tc->value = scene->compositor->display_height; break; case 102: /*frame rate*/ - tc->value = (u32) gf_sc_get_fps(scene->root_od->term->compositor, 1); + tc->value = (u32) gf_sc_get_fps(scene->compositor, 1); break; case 103: /*total CPU*/ if (!gf_sys_get_rti(200, &rti, 0) ) return; @@ -169,10 +167,10 @@ void evaluate_term_cap(GF_Node *node, GF_Route *route) break; case 112: /*audio vol*/ - tc->value = gf_sc_get_option(scene->root_od->term->compositor, GF_OPT_AUDIO_VOLUME); + tc->value = gf_sc_get_option(scene->compositor, GF_OPT_AUDIO_VOLUME); break; case 113: /*audio pan*/ - tc->value = gf_sc_get_option(scene->root_od->term->compositor, GF_OPT_AUDIO_PAN); + tc->value = gf_sc_get_option(scene->compositor, GF_OPT_AUDIO_PAN); break; default: return; @@ -183,10 +181,10 @@ void evaluate_term_cap(GF_Node *node, GF_Route *route) static void InitTermCap(GF_Scene *scene, GF_Node *node) { M_TermCap *tc = (M_TermCap *)node; - tc->on_evaluate = evaluate_term_cap; + tc->on_evaluate = evaluate_scene_cap; gf_node_set_private(node, scene); /*evaluate upon init (cf BIFS spec)*/ - evaluate_term_cap(node, NULL); + evaluate_scene_cap(node, NULL); } #endif /*GPAC_DISABLE_VRML*/ @@ -200,8 +198,9 @@ static void svg_traverse_title(GF_Node *node, void *rs, Bool is_destroy) } #endif +void gf_sc_on_node_init(GF_Compositor *sc, GF_Node *node); -void gf_term_on_node_init(void *_scene, GF_Node *node) +void gf_scene_on_node_init(void *_scene, GF_Node *node) { GF_Scene *scene = (GF_Scene *)_scene; if (!node || !scene) return; @@ -263,6 +262,11 @@ void gf_term_on_node_init(void *_scene, GF_Node *node) gf_node_set_private(node, scene); gf_list_add(scene->keynavigators, node); ((M_KeyNavigator*)node)->on_setFocus = on_kn_set_focus; +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + on_kn_set_focus(NULL, NULL); + } +#endif break; #endif @@ -276,17 +280,17 @@ void gf_term_on_node_init(void *_scene, GF_Node *node) #endif default: - gf_sc_on_node_init(scene->root_od->term->compositor, node); + gf_sc_on_node_init(scene->compositor, node); break; } } -void gf_term_on_node_modified(void *_is, GF_Node *node) +void gf_scene_on_node_modified(void *_is, GF_Node *node) { GF_Scene *scene = (GF_Scene *)_is; if (!scene) return; if (!node) { - gf_sc_invalidate(scene->root_od->term->compositor, NULL); + gf_sc_invalidate(scene->compositor, NULL); return; } @@ -315,38 +319,38 @@ void gf_term_on_node_modified(void *_is, GF_Node *node) break; #endif default: - gf_sc_invalidate(scene->root_od->term->compositor, node); + gf_sc_invalidate(scene->compositor, node); break; } } -static void gf_term_on_node_destroyed(void *_is, GF_Node *node) +static void gf_scene_on_node_destroyed(void *_is, GF_Node *node) { GF_Scene *scene = (GF_Scene *)_is; if (!scene) return; - gf_sc_node_destroy(scene->root_od->term->compositor, node, NULL); + gf_sc_node_destroy(scene->compositor, node, NULL); } GF_EXPORT -void gf_term_node_callback(void *_is, u32 type, GF_Node *n, void *param) +void gf_scene_node_callback(void *_is, GF_SGNodeCbkType type, GF_Node *n, void *param) { switch (type) { case GF_SG_CALLBACK_MODIFIED: - gf_term_on_node_modified(_is, n); + gf_scene_on_node_modified(_is, n); break; case GF_SG_CALLBACK_NODE_DESTROY: - gf_term_on_node_destroyed(_is, n); + gf_scene_on_node_destroyed(_is, n); break; case GF_SG_CALLBACK_INIT: - gf_term_on_node_init(_is, n); + gf_scene_on_node_init(_is, n); break; /*get all inline nodes using this subscene and bubble up...*/ case GF_SG_CALLBACK_GRAPH_DIRTY: { u32 i=0; - GF_Node *root; GF_Scene *scene = (GF_Scene *)_is; if (scene->root_od->mo) { + GF_Node *root; while ((root=(GF_Node*)gf_mo_event_target_enum_node(scene->root_od->mo, &i))) { gf_node_dirty_set(root, GF_SG_CHILD_DIRTY, GF_TRUE); } diff --git a/src/compositor/scene_ns.c b/src/compositor/scene_ns.c new file mode 100644 index 0000000..76fdf4c --- /dev/null +++ b/src/compositor/scene_ns.c @@ -0,0 +1,612 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2018 + * All rights reserved + * + * This file is part of GPAC / Scene Compositor sub-project + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include + + +void scene_ns_on_setup_error(GF_Filter *failed_filter, void *udta, GF_Err err) +{ + GF_SceneNamespace *scene_ns; + GF_Scene *scene; + GF_ObjectManager *root = (GF_ObjectManager *)udta; + assert(root); + scene_ns = root->scene_ns; + assert(scene_ns); + scene = root->subscene ? root->subscene : root->parentscene; + assert(scene); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM] Service connection failure received from %s - %s\n", scene_ns->url, gf_error_to_string(err) )); + + if (root->scene_ns->owner != root) { + gf_scene_message(scene, scene_ns->url, "Incompatible module type", GF_SERVICE_ERROR); + return; + } + /*this is service connection*/ + gf_odm_service_media_event(root, GF_EVENT_MEDIA_SETUP_DONE); + + scene_ns->connect_ack = GF_TRUE; + if (err) { + char msg[5000]; + snprintf(msg, sizeof(msg), "Cannot open %s", scene_ns->url); + gf_scene_message(scene, scene_ns->url, msg, err); + + if (root->mo) root->mo->connect_failure = GF_TRUE; + gf_odm_service_media_event(root, GF_EVENT_ERROR); + + /*destroy service only if attached*/ + if (root) { + GF_Scene *top_scene; + //notify before disconnecting + if (root->subscene) gf_scene_notify_event(root->subscene, GF_EVENT_SCENE_ATTACHED, NULL, NULL, err, GF_FALSE); + + //detach clocks before destroying namspace + root->ck = NULL; + if (root->subscene) { + u32 i, count = gf_list_count(root->subscene->resources); + for (i=0; isubscene->resources, i); + anodm->ck = NULL; + if (anodm->scene_ns==root->scene_ns) + anodm->scene_ns = NULL; + } + } + + root->scene_ns = NULL; + if (scene_ns->owner && scene_ns->nb_odm_users) scene_ns->nb_odm_users--; + scene_ns->owner = NULL; + + top_scene = gf_scene_get_root_scene(scene); + + //source filter no longer exists + scene_ns->source_filter = NULL; + gf_scene_ns_del(scene_ns, top_scene); + + if (!root->parentscene) { + GF_Event evt; + evt.type = GF_EVENT_CONNECT; + evt.connect.is_connected = 0; + gf_filter_send_gf_event(scene->compositor->filter, &evt); + } else { + /*try to reinsert OD for VRML/X3D with multiple URLs: + 1- first remove from parent scene without destroying object, this will trigger a re-setup + if other URLs are present + 2- then destroy object*/ + gf_scene_remove_object(root->parentscene, root, 0); + gf_odm_disconnect(root, 1); + } + return; + } + } +} + + + +#ifdef FILTER_FIXME +static void term_on_disconnect(GF_ClientService *service, LPNETCHANNEL netch, GF_Err response) +{ + GF_ObjectManager *root; + GF_Channel *ch; + GF_Terminal *term = service->term; + + /*may be null upon destroy*/ + root = service->owner; + if (root && (root->net_service != service)) { + if (root->net_service) gf_term_message(term, service->url, "Incompatible module type", GF_SERVICE_ERROR); + return; + } + //reset global seek time + if (term->root_scene && term->root_scene->root_od) + term->root_scene->root_od->media_start_time = 0; + + /*this is service disconnect*/ + if (!netch) { + if (service->subservice_disconnect) { + if (service->owner && service->subservice_disconnect==1) { + GF_Scene *scene = service->owner->subscene ? service->owner->subscene : service->owner->parentscene; + /*destroy all media*/ + gf_scene_disconnect(scene, GF_TRUE); + } + return; + } + /*unregister from valid services*/ + if (gf_list_del_item(term->net_services, service)>=0) { + /*and queue for destroy*/ + gf_list_add(term->net_services_to_remove, service); + } + return; + } + /*this is channel disconnect*/ + + /*no notif in case of failure for disconnection*/ + ch = gf_term_get_channel(service, netch); + if (!ch) return; + /*signal channel state*/ + ch->es_state = GF_ESM_ES_DISCONNECTED; +} +#endif + +void gf_scene_insert_pid(GF_Scene *scene, GF_SceneNamespace *sns, GF_FilterPid *pid, Bool is_in_iod) +{ + u32 i, min_od_id; + GF_MediaObject *the_mo; + GF_ObjectManager *odm, *root; + const GF_PropertyValue *v; + u32 mtype=0; + u32 pid_odid=0; + u32 pid_id=0; + u32 ServiceID=0; + + root = sns->owner; + if (!root) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Service %s] has no root, aborting !\n", sns->url)); + return; + } + if (root->flags & GF_ODM_DESTROYED) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Service %s] root has been scheduled for destruction - aborting !\n", sns->url)); + return; + } + scene = root->subscene ? root->subscene : root->parentscene; + if (scene->root_od->addon && (scene->root_od->addon->addon_type == GF_ADDON_TYPE_MAIN)) { + scene->root_od->flags |= GF_ODM_REGENERATE_SCENE; + } + + if (scene->is_dynamic_scene) { + if (!root->ID) + root->ID = 1; + } + + v = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (!v) return; + mtype = v->value.uint; + + v = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_ID); + if (v) ServiceID = v->value.uint; + + v = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + if (v) pid_id = v->value.uint; + + if (scene->compositor->autofps && (mtype==GF_STREAM_VISUAL) ) { + v = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (v && v->value.frac.den && v->value.frac.num && (v->value.frac.den!=v->value.frac.num)) { + scene->compositor->autofps = GF_FALSE; + gf_sc_set_fps(scene->compositor, v->value.frac); + } + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[Service %s] Adding new media\n", sns->url)); + + /*object declared this way are not part of an OD stream and are considered as dynamic*/ + /* od->objectDescriptorID = GF_MEDIA_EXTERNAL_ID; */ + + /*check if we have a mediaObject in the scene not attached and matching this object*/ + the_mo = NULL; + odm = NULL; + min_od_id = 0; + for (i=0; iscene_objects); i++) { +#if FILTER_FIXME + char *frag = NULL; +#endif + char *ext, *url; + u32 match_esid = 0; + Bool type_matched = GF_FALSE; + GF_MediaObject *mo = gf_list_get(scene->scene_objects, i); + + if ((mo->OD_ID != GF_MEDIA_EXTERNAL_ID) && (min_od_idOD_ID)) + min_od_id = mo->OD_ID; + + //got the service, check the type + /*match type*/ + switch (mtype) { + case GF_STREAM_VISUAL: + if (mo->type == GF_MEDIA_OBJECT_VIDEO) type_matched = GF_TRUE; + break; + case GF_STREAM_AUDIO: + if (mo->type == GF_MEDIA_OBJECT_AUDIO) type_matched = GF_TRUE; + break; + case GF_STREAM_SCENE: + if (mo->type == GF_MEDIA_OBJECT_UPDATES) type_matched = GF_TRUE; + break; + default: + break; + } + if (!mo->odm) { + u32 j; + //if not external OD, look for ODM with same ID + if (mo->OD_ID != GF_MEDIA_EXTERNAL_ID) { + for (j=0; jresources); j++) { + GF_ObjectManager *an_odm = gf_list_get(scene->resources, j); + if (an_odm->ID == mo->OD_ID) { + mo->odm = an_odm; + break; + } + } + } + if (!mo->odm) { + continue; + } + } + /*if object is attached to a service, don't bother looking in a different one*/ + if (mo->odm->scene_ns && (mo->odm->scene_ns != sns)) { + Bool mine = 0; + if (mo->odm->scene_ns->source_filter) { + mine = gf_filter_pid_is_filter_in_parents(pid, mo->odm->scene_ns->source_filter); + } + //passthrough ODM not yet assigned, consider it ours + else if (!mo->odm->pid && (mo->odm->flags & GF_ODM_PASSTHROUGH)) { + mine = GF_TRUE; + } + if (!mine) continue; + + if (type_matched) { + the_mo = mo; + odm = mo->odm; + pid_odid = odm->ID = mo->OD_ID; + break; + } + } + + /*already assigned object*/ + if (mo->odm->ID) continue; + + /*we cannot match yet the object, we need the OD command for that*/ + if (mo->OD_ID != GF_MEDIA_EXTERNAL_ID) { + continue; + } + if (!mo->URLs.count || !mo->URLs.vals[0].url) continue; + + ext = strrchr(mo->URLs.vals[0].url, '#'); + if (ext) { +#if FILTER_FIXME + frag = strchr(ext, '='); +#endif + ext[0] = 0; + } + url = mo->URLs.vals[0].url; + if (!strnicmp(url, "file://localhost", 16)) url += 16; + else if (!strnicmp(url, "file://", 7)) url += 7; + else if (!strnicmp(url, "gpac://", 7)) url += 7; + else if (!strnicmp(url, "pid://", 6)) match_esid = atoi(url+6); + + if (!match_esid && mo->odm->scene_ns && !strstr(mo->odm->scene_ns->url, url)) { + if (ext) ext[0] = '#'; + continue; + } + if (ext) ext[0] = '#'; + + if (match_esid && (pid_id != match_esid)) + continue; + + /*match type*/ + switch (mtype) { + case GF_STREAM_VISUAL: + if (mo->type != GF_MEDIA_OBJECT_VIDEO) continue; + break; + case GF_STREAM_AUDIO: + if (mo->type != GF_MEDIA_OBJECT_AUDIO) continue; + break; + case GF_STREAM_SCENE: + if (mo->type != GF_MEDIA_OBJECT_UPDATES) continue; + break; + default: + continue; + } +#if FILTER_FIXME + if (frag) { + u32 frag_id = 0; + u32 ID = od->objectDescriptorID; + if (ID==GF_MEDIA_EXTERNAL_ID) ID = esd->ESID; + frag++; + frag_id = atoi(frag); + if (ID!=frag_id) continue; + } +#endif + the_mo = mo; + odm = mo->odm; + pid_odid = odm->ID = mo->OD_ID; + if (mo->OD_ID == GF_MEDIA_EXTERNAL_ID) + odm->flags |= GF_ODM_NOT_IN_OD_STREAM; + break; + } + + /*add a pass on scene->resource to check for min_od_id, + otherwise we may have another modules declaring an object with ID 0 from + another thread, which will assert (only one object with a givne OD ID)*/ + for (i=0; iresources); i++) { + GF_ObjectManager *an_odm = gf_list_get(scene->resources, i); + + if (an_odm->ID == GF_MEDIA_EXTERNAL_ID) continue; + + if (min_od_id < an_odm->ID) + min_od_id = an_odm->ID; + + if (odm) continue; + //check for OD replacement, look for ODMs with no PID assigned and a source filter origin for this pid + if (an_odm->pid) continue; + + if (!an_odm->scene_ns->source_filter) continue; + if (! gf_filter_pid_is_filter_in_parents(pid, an_odm->scene_ns->source_filter)) + continue; + + odm = an_odm; + pid_odid = an_odm->ID; + break; + } + + if (is_in_iod) { + odm = scene->root_od; + //for inline cases, the media object may already be setup + the_mo = odm->mo; + if (the_mo) pid_odid = the_mo->OD_ID; + } + + if (!odm) { + odm = gf_odm_new(); + odm->parentscene = scene; + gf_list_add(scene->resources, odm); + } + odm->flags |= GF_ODM_NOT_SETUP; + if (!pid_odid) { + pid_odid = min_od_id + 1; + } + odm->ID = pid_odid; + odm->mo = the_mo; + odm->ServiceID = ServiceID; + if (!odm->pid) { + odm->type = mtype; + if (mtype == GF_STREAM_AUDIO) { + scene->compositor->audio_renderer->nb_audio_objects++; + scene->compositor->audio_renderer->scene_ready = GF_FALSE; + } + } + + //register PID with ODM, but don't call setup object + gf_odm_register_pid(odm, pid, GF_TRUE); + + gf_filter_pid_set_udta(pid, odm); + + v = gf_filter_pid_get_property(pid, GF_PROP_PID_PLAYBACK_MODE); + if (!v || !v->value.uint) odm->flags |= GF_ODM_NO_TIME_CTRL; + + if (the_mo) the_mo->OD_ID = odm->ID; + if (!scene->selected_service_id) + scene->selected_service_id = ServiceID; + + if (odm->parentscene && odm->parentscene->is_dynamic_scene) + odm->flags |= GF_ODM_NOT_IN_OD_STREAM; + + //we insert right away the PID as a new object if the scene is dynamic + //if the scene is not dynamic, we wait for the corresponding OD update + //FILTER_FIXME: needs rework to enable attaching subtitle to a non-dynamic scene + //otherwise if subscene, this is an IOD + if (odm->subscene || (odm->flags & GF_ODM_NOT_IN_OD_STREAM) ) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM%d] setup object - MO %08x\n", odm->ID, odm->mo)); + gf_odm_setup_object(odm, sns, pid); + } else { + //cannot setup until we get the associated OD_Update + odm->ID = 0; + } + + if (scene->is_dynamic_scene && !root->scene_ns->connect_ack) { + /*othewise send a connect ack for top level*/ + GF_Event evt; + root->scene_ns->connect_ack = GF_TRUE; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ODM] Root object connected (%s) !\n", root->scene_ns->url)); + + evt.type = GF_EVENT_CONNECT; + evt.connect.is_connected = GF_TRUE; + gf_sc_send_event(scene->compositor, &evt); + } +} + +GF_SceneNamespace *gf_scene_ns_new(GF_Scene *scene, GF_ObjectManager *owner, const char *url, const char *parent_url) +{ + char *frag; + GF_SceneNamespace *sns; + + GF_SAFEALLOC(sns, GF_SceneNamespace); + if (!sns) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Compose] Failed to allocate namespace\n")); + return NULL; + } + sns->owner = owner; + sns->url = gf_url_concatenate(parent_url, url); + sns->clocks = gf_list_new(); + frag = strchr(sns->url, '#'); + if (frag) { + sns->url_frag = gf_strdup(frag+1); + frag[0] = 0; + } + + //move to top scene + scene = gf_scene_get_root_scene(scene); + gf_list_add(scene->namespaces, sns); + + return sns; +} + + + +void gf_scene_ns_del(GF_SceneNamespace *sns, GF_Scene *root_scene) +{ + gf_list_del_item(root_scene->namespaces, sns); + + if (sns->source_filter) { + gf_filter_remove_src(root_scene->compositor->filter, sns->source_filter); + } + if (sns->clocks) { + while (gf_list_count(sns->clocks)) { + GF_Clock *ck = gf_list_pop_back(sns->clocks); + gf_clock_del(ck); + } + gf_list_del(sns->clocks); + } + if (sns->url) gf_free(sns->url); + if (sns->url_frag) gf_free(sns->url_frag); + gf_free(sns); +} + + +Bool scene_ns_remove_object(GF_Filter *filter, void *callback, u32 *reschedule_ms) +{ + GF_ObjectManager *odm = (GF_ObjectManager *)callback; + gf_odm_disconnect(odm, 2); + return GF_FALSE; +} + +/*connects given OD manager to its URL*/ +void gf_scene_ns_connect_object(GF_Scene *scene, GF_ObjectManager *odm, char *serviceURL, char *parent_url) +{ + GF_Err e; + char *frag; + Bool reloc_result=GF_FALSE; + + /*check cache*/ + if (parent_url) { + u32 i, count; + count = gf_opts_get_section_count(); + for (i=0; icompositor->root_scene->namespaces); + for (i=0; icompositor->root_scene->namespaces, i); + if (sns->url && !strcmp(sns->url, serviceURL)) { + frag[0] = '#'; + odm->scene_ns = sns; + sns->nb_odm_users++; + return; + } + } + } + + odm->scene_ns = gf_scene_ns_new(scene->compositor->root_scene, odm, serviceURL, (odm->addon || reloc_result) ? NULL : parent_url); + + if (!odm->scene_ns) { + gf_scene_message(scene, serviceURL, "Cannot create scene service", GF_OUT_OF_MEM); + gf_odm_disconnect(odm, 1); + if (frag) frag[0] = '#'; + return; + } + odm->scene_ns->nb_odm_users++; + assert(odm->scene_ns->owner == odm); + + if (frag && !odm->scene_ns->url_frag) { + odm->scene_ns->url_frag = gf_strdup(frag+1); + } + + if (!strncmp(serviceURL, "gpid://", 7)) { + gf_odm_service_media_event(odm, GF_EVENT_MEDIA_SETUP_BEGIN); + gf_odm_service_media_event(odm, GF_EVENT_MEDIA_SETUP_DONE); + odm->scene_ns->connect_ack = GF_TRUE; + odm->flags |= GF_ODM_NOT_IN_OD_STREAM | GF_ODM_PASSTHROUGH; + if (frag) frag[0] = '#'; + return; + } + if (!parent_url && odm->parentscene && odm->parentscene->root_od->scene_ns) + parent_url = odm->parentscene->root_od->scene_ns->url; + + odm->scene_ns->source_filter = gf_filter_connect_source(scene->compositor->filter, serviceURL, parent_url, GF_FALSE, &e); + + if (frag) frag[0] = '#'; + if (!odm->scene_ns->source_filter) { + Bool remove_scene=GF_FALSE; + GF_Scene *target_scene = odm->subscene ? NULL : odm->parentscene; + if (odm->mo) odm->mo->connect_failure = GF_TRUE; + odm->skip_disconnect_state = 1; + //prevent scene from being disconnected - this can happen if a script catches the event and triggers a disonnection of the parent scene + if (target_scene) target_scene->root_od->skip_disconnect_state = 1; + gf_scene_notify_event(scene, GF_EVENT_SCENE_ATTACHED, NULL, NULL, e, GF_TRUE); + gf_scene_message(scene, serviceURL, "Cannot find filter for service", e); + odm->skip_disconnect_state = 0; + + if (target_scene) { + if (target_scene->root_od->skip_disconnect_state==2) remove_scene = GF_TRUE; + target_scene->root_od->skip_disconnect_state = 0; + } + if (remove_scene) { + gf_filter_post_task(scene->compositor->filter, scene_ns_remove_object, target_scene->root_od, "remove_odm"); + } else { + gf_filter_post_task(scene->compositor->filter, scene_ns_remove_object, odm, "remove_odm"); + } + return; + } + //make sure we only connect this filter to ourselves + gf_filter_set_source(scene->compositor->filter, odm->scene_ns->source_filter, NULL); + + gf_filter_set_setup_failure_callback(scene->compositor->filter, odm->scene_ns->source_filter, scene_ns_on_setup_error, odm); + + /*OK connect*/ + gf_odm_service_media_event(odm, GF_EVENT_MEDIA_SETUP_BEGIN); +} + + +GF_EXPORT +Bool gf_term_is_supported_url(GF_Terminal *term, const char *fileName, Bool use_parent_url, Bool no_mime_check) +{ +#ifdef FILTER_FIXME + GF_InputService *ifce; + GF_Err e; + char *sURL; + char *mime=NULL; + char *parent_url = NULL; + if (use_parent_url && term->root_scene) parent_url = term->root_scene->root_od->net_service->url; + + ifce = gf_term_can_handle_service(term, fileName, parent_url, no_mime_check, &sURL, &e, NULL, &mime); + if (!ifce) return 0; + gf_modules_close_interface((GF_BaseInterface *) ifce); + gf_free(sURL); + if (mime) gf_free(mime); +#endif + return 1; +} diff --git a/src/compositor/svg_base.c b/src/compositor/svg_base.c index 675fdcc..852f022 100644 --- a/src/compositor/svg_base.c +++ b/src/compositor/svg_base.c @@ -27,6 +27,7 @@ #ifndef GPAC_DISABLE_SVG #include "nodes_stacks.h" +#include /* @@ -229,7 +230,7 @@ Bool compositor_svg_evaluate_conditional(GF_Compositor *compositor, SVGAllAttrib { u32 i, count; Bool found; - const char *lang_3cc, *lang_2cc; + s32 lang_idx; /*process required features*/ count = atts->requiredFeatures ? gf_list_count(*atts->requiredFeatures) : 0; @@ -250,8 +251,8 @@ Bool compositor_svg_evaluate_conditional(GF_Compositor *compositor, SVGAllAttrib else if (!strnicmp(iri->string, "http://www.w3.org/TR/SVG11/feature", 34)) { feat = iri->string+35; if (feat) { - Bool found = GF_FALSE; u32 j, nbf; + found = GF_FALSE; nbf = sizeof(svg11_features) / sizeof(struct svg_11_feature); for (j=0; jstring, "http://www.w3.org/Graphics/SVG/feature/1.2/", 43)) { feat = iri->string+44; if (feat) { - Bool found = GF_FALSE; u32 j, nbf; + found = GF_FALSE; nbf = sizeof(svg12_features) / sizeof(struct svg_12_feature); for (j=0; jsystemLanguage ? gf_list_count(*atts->systemLanguage) : 0; if (count) { found = GF_FALSE; - lang_3cc = gf_cfg_get_key(compositor->user->config, "Systems", "Language3CC"); - if (!lang_3cc) lang_3cc = "und"; - lang_2cc = gf_cfg_get_key(compositor->user->config, "Systems", "Language2CC"); - if (!lang_2cc) lang_2cc = "un"; + const char *lang = gf_opts_get_key("core", "lang"); + lang_idx = lang ? gf_lang_find(lang) : -1; } else { - lang_3cc = "und"; - lang_2cc = "un"; + lang_idx = -1; found = GF_TRUE; } for (i=0; isystemLanguage, i); - /*3 char-code*/ - if (strlen(lang)==3) { - if (!stricmp(lang, lang_3cc)) { - found = GF_TRUE; - break; - } - } - /*2 char-code, only check first 2 chars - TODO FIXME*/ - else if (!strnicmp(lang, lang_2cc, 2)) { + s32 pref_lang_idx = lang ? gf_lang_find(lang) : -1; + if (pref_lang_idx==lang_idx) { found = GF_TRUE; break; } @@ -324,13 +315,13 @@ Bool compositor_svg_evaluate_conditional(GF_Compositor *compositor, SVGAllAttrib count = atts->requiredFormats ? gf_list_count(*atts->requiredFormats) : 0; if (count) { for (i=0; irequiredFormats, i); char *sep = strchr(mime, ';'); if (sep) sep[0] = 0; - opt = gf_cfg_get_key(compositor->user->config, "MimeTypes", mime); + mime_ok = gf_filter_is_supported_mime(compositor->filter, mime); if (sep) sep[0] = ';'; - if (!opt) return GF_FALSE; + if (!mime_ok) return GF_FALSE; } } diff --git a/src/terminal/svg_external.c b/src/compositor/svg_external.c similarity index 74% rename from src/terminal/svg_external.c rename to src/compositor/svg_external.c index d8492e9..6d9ef63 100644 --- a/src/terminal/svg_external.c +++ b/src/compositor/svg_external.c @@ -2,10 +2,10 @@ * GPAC - Multimedia Framework C SDK * * Authors: Cyril Concolato - Jean le Feuvre - * Copyright (c) Telecom ParisTech 2005-2012 + * Copyright (c) Telecom ParisTech 2005-2017 * All rights reserved * - * This file is part of GPAC / SVG Rendering sub-project + * This file is part of GPAC / Scene Compositor sub-project * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -24,58 +24,18 @@ */ #include -#include #include #ifndef GPAC_DISABLE_SVG #include +#include #include #include #include - -char *gf_term_resolve_xlink(GF_Node *node, char *the_url) -{ - char *url; - GF_Scene *scene = gf_sg_get_private(gf_node_get_graph(node)); - if (!scene) return NULL; - - url = gf_strdup(the_url); - /*apply XML:base*/ - while (node) { - GF_FieldInfo info; - if (gf_node_get_attribute_by_tag(node, TAG_XML_ATT_base, 0, 0, &info)==GF_OK) { - char *new_url = gf_url_concatenate( ((XMLRI*)info.far_ptr)->string, url); - if (new_url) { - gf_free(url); - url = new_url; - } - } - node = gf_node_get_parent(node, 0); - } - - /*if this is a fragment and no XML:BASE was found, this is a fragment of the current document*/ - if (url[0]=='#') return url; - - if (scene) { - char *the_url; - if (scene->redirect_xml_base) { - the_url = gf_url_concatenate(scene->redirect_xml_base, url); - } else { -// the_url = gf_url_concatenate(is->root_od->net_service->url, url); - /*the root url of a document should be "." if not specified, so that the final URL resolve happens only once - at the service level*/ - the_url = gf_strdup(url); - } - gf_free(url); - return the_url; - } - return url; -} - GF_EXPORT -GF_Err gf_term_get_mfurl_from_xlink(GF_Node *node, MFURL *mfurl) +GF_Err gf_sc_get_mfurl_from_xlink(GF_Node *node, MFURL *mfurl) { u32 stream_id = 0; GF_Err e = GF_OK; @@ -98,19 +58,19 @@ GF_Err gf_term_get_mfurl_from_xlink(GF_Node *node, MFURL *mfurl) mfurl->count = 1; GF_SAFEALLOC(mfurl->vals, SFURL) + if (!mfurl->vals) return GF_BAD_PARAM; sfurl = mfurl->vals; - if (!sfurl) return GF_BAD_PARAM; sfurl->OD_ID = stream_id; if (stream_id) return GF_OK; if (!strncmp(iri->string, "data:", 5)) { - const char *cache_dir = gf_cfg_get_key(scene->root_od->term->user->config, "General", "CacheDirectory"); + const char *cache_dir = gf_opts_get_key("core", "cache"); e = gf_node_store_embedded_data(iri, cache_dir, "embedded_"); if (e) return e; sfurl->url = gf_strdup(iri->string); return GF_OK; } - sfurl->url = gf_term_resolve_xlink(node, iri->string); + sfurl->url = gf_scene_resolve_xlink(node, iri->string); return e; } @@ -153,7 +113,7 @@ static GF_Scene *gf_svg_get_subscene(GF_Node *elt, XLinkAttributesPointers *xlin memset(&url, 0, sizeof(MFURL)); if (!xlinkp->href) return NULL; - gf_term_get_mfurl_from_xlink(elt, &url); + gf_sc_get_mfurl_from_xlink(elt, &url); while (scene->secondary_resource && scene->root_od->parentscene) scene = scene->root_od->parentscene; @@ -166,7 +126,7 @@ static GF_Scene *gf_svg_get_subscene(GF_Node *elt, XLinkAttributesPointers *xlin return mo->odm->subscene; } - +GF_EXPORT GF_MediaObject *gf_mo_load_xlink_resource(GF_Node *node, Bool primary_resource, Double clipBegin, Double clipEnd) { GF_Scene *new_resource; @@ -205,26 +165,22 @@ GF_MediaObject *gf_mo_load_xlink_resource(GF_Node *node, Bool primary_resource, return new_resource->root_od->mo; } +GF_EXPORT void gf_mo_unload_xlink_resource(GF_Node *node, GF_MediaObject *mo) { - if (!mo) return; - if (!gf_odm_lock_mo(mo)) return; - if (!mo->odm->subscene) { - gf_odm_lock(mo->odm, 0); + if (!mo || !mo->odm || !mo->odm->subscene) { return; } + if (mo->num_open) { mo->num_open--; if (!mo->num_open) { /*do we simply stop the associated document or unload it??? to check*/ -// gf_mo_stop(mo); +// gf_mo_stop(&mo); gf_odm_disconnect(mo->odm, 2); return; } } - - /*ODM may be destroyed at this point !!*/ - if (mo->odm) gf_odm_lock(mo->odm, 0); } #endif //GPAC_DISABLE_SVG diff --git a/src/compositor/svg_filters.c b/src/compositor/svg_filters.c index 0fab04d..f32044b 100644 --- a/src/compositor/svg_filters.c +++ b/src/compositor/svg_filters.c @@ -29,6 +29,8 @@ #include "nodes_stacks.h" #include "texturing.h" +#ifdef GPAC_ENABLE_SVG_FILTERS + typedef struct { GF_TextureHandler txh; @@ -195,7 +197,7 @@ void svg_draw_filter(GF_Node *filter, GF_Node *node, GF_TraverseState *tr_state) GF_IRect txrc; Fixed scale_x, scale_y, temp_x, temp_y; DrawableContext *ctx, *child_ctx; - GF_SURFACE offscreen_surface, old_surf; + GF_EVGSurface *offscreen_surface, *old_surf; GF_Rect bounds, local_bounds, rc; GF_Matrix2D backup; SVGAllAttributes all_atts; @@ -260,14 +262,14 @@ void svg_draw_filter(GF_Node *filter, GF_Node *node, GF_TraverseState *tr_state) old_surf = tr_state->visual->raster_surface; - offscreen_surface = tr_state->visual->compositor->rasterizer->surface_new(tr_state->visual->compositor->rasterizer, tr_state->visual->center_coords); + offscreen_surface = gf_evg_surface_new(tr_state->visual->center_coords); tr_state->visual->raster_surface = offscreen_surface; gf_mx2d_copy(backup, tr_state->transform); gf_mx2d_init(tr_state->transform); /*attach the buffer to visual*/ - tr_state->visual->compositor->rasterizer->surface_attach_to_buffer(offscreen_surface, st->txh.data, + gf_evg_surface_attach_to_buffer(offscreen_surface, st->txh.data, st->txh.width, st->txh.height, 0, @@ -333,7 +335,7 @@ void svg_draw_filter(GF_Node *filter, GF_Node *node, GF_TraverseState *tr_state) /*restore state and destroy whatever needs to be cleaned*/ tr_state->in_svg_filter = 0; tr_state->immediate_draw = prev_flags; - tr_state->visual->compositor->rasterizer->surface_delete(offscreen_surface); + gf_evg_surface_delete(offscreen_surface); tr_state->visual->raster_surface = old_surf; tr_state->traversing_mode = TRAVERSE_SORT; tr_state->visual->surf_rect = rc1; @@ -442,6 +444,8 @@ void compositor_init_svg_filter(GF_Compositor *compositor, GF_Node *node) gf_sc_texture_allocate(&stack->txh); } +#endif //GPAC_ENABLE_SVG_FILTERS + #endif diff --git a/src/compositor/svg_font.c b/src/compositor/svg_font.c index 7842be5..2cfe56c 100644 --- a/src/compositor/svg_font.c +++ b/src/compositor/svg_font.c @@ -194,18 +194,16 @@ static void svg_traverse_font(GF_Node *node, void *rs, Bool is_destroy) static void svg_font_on_load(GF_Node *handler, GF_DOM_Event *event, GF_Node *observer) { - GF_Compositor *compositor; GF_Font *font; assert(event->currentTarget->ptr_type==GF_DOM_EVENT_TARGET_NODE); assert(gf_node_get_tag((GF_Node*)event->currentTarget->ptr)==TAG_SVG_font); font = gf_node_get_private((GF_Node*)event->currentTarget->ptr); font->not_loaded = 0; - compositor = (GF_Compositor *)gf_node_get_private((GF_Node *)handler); /*brute-force signaling that all fonts have changed and texts must be recomputed*/ - compositor->reset_fonts = 1; - gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); - compositor->fonts_pending--; + font->compositor->reset_fonts = 1; + gf_sc_next_frame_state(font->compositor, GF_SC_DRAW_FRAME); + font->compositor->fonts_pending--; } void compositor_init_svg_font(GF_Compositor *compositor, GF_Node *node) @@ -234,6 +232,7 @@ void compositor_init_svg_font(GF_Compositor *compositor, GF_Node *node) return; } font->ft_mgr = compositor->font_manager; + font->compositor = compositor; font->get_glyphs = svg_font_get_glyphs; font->load_glyph = svg_font_load_glyph; @@ -318,7 +317,6 @@ void compositor_init_svg_font(GF_Compositor *compositor, GF_Node *node) /*wait for onLoad event before activating the font, otherwise we may not have all the glyphs*/ handler = gf_dom_listener_build(node_font, GF_EVENT_LOAD, 0); handler->handle_event = svg_font_on_load; - gf_node_set_private((GF_Node *)handler, compositor); } @@ -443,17 +441,33 @@ static Bool svg_font_uri_check(GF_Node *node, FontURIStack *st) if (!font_name) return 0; if (!st->mo) { st->mo = gf_mo_load_xlink_resource(node, 0, 0, -1); - if (!st->mo) return 0; + if (!st->mo) { + st->compositor->fonts_pending--; + return 0; + } } ext_sg = gf_mo_get_scenegraph(st->mo); - if (!ext_sg) return 0; + if (!ext_sg) { + st->compositor->fonts_pending--; + return 0; + } atts.xlink_href->target = gf_sg_find_node_by_name(ext_sg, font_name+1); - if (!atts.xlink_href->target) return 0; + if (!atts.xlink_href->target) { + st->compositor->fonts_pending--; + return 0; + } } font_elt = atts.xlink_href->target; - if (gf_node_get_tag(font_elt) != TAG_SVG_font) return 0; + if (gf_node_get_tag(font_elt) != TAG_SVG_font) { + st->compositor->fonts_pending--; + return 0; + } font = gf_node_get_private(font_elt); - if (!font) return 0; + if (!font) { + st->compositor->fonts_pending--; + return 0; + } + st->alias = font; gf_mo_is_done(st->mo); diff --git a/src/compositor/svg_geometry.c b/src/compositor/svg_geometry.c index 8c026e4..69e0bbc 100644 --- a/src/compositor/svg_geometry.c +++ b/src/compositor/svg_geometry.c @@ -139,9 +139,9 @@ Bool svg_drawable_is_over(Drawable *drawable, Fixed x, Fixed y, DrawAspect2D *as } } if (check_stroke) { - StrikeInfo2D *si; /*not painted or don't care about stroke*/ if ((check_stroke!=2) || asp->line_texture || asp->line_color) { + StrikeInfo2D *si; if (glyph_rc) return 1; si = drawable_get_strikeinfo(tr_state->visual->compositor, drawable, asp, tr_state->appear, NULL, 0, NULL); /*point is over outline*/ diff --git a/src/compositor/svg_grouping.c b/src/compositor/svg_grouping.c index 72fd245..fba2781 100644 --- a/src/compositor/svg_grouping.c +++ b/src/compositor/svg_grouping.c @@ -29,7 +29,6 @@ #include "nodes_stacks.h" #include "offscreen_cache.h" -#include #include #include @@ -116,7 +115,7 @@ static void svg_recompute_viewport_transformation(GF_Node *node, SVGsvgStack *st else { GF_Node *target = gf_sg_find_node_by_name(gf_node_get_graph(node), (char *) frag_uri); if (target) { - GF_Matrix2D mx; + GF_Matrix2D vp_mx; GF_TraverseState bounds_state; memset(&bounds_state, 0, sizeof(bounds_state)); bounds_state.traversing_mode = TRAVERSE_GET_BOUNDS; @@ -127,8 +126,8 @@ static void svg_recompute_viewport_transformation(GF_Node *node, SVGsvgStack *st gf_mx2d_init(bounds_state.mx_at_node); gf_mx_init(tr_state->visual->compositor->hit_world_to_local); gf_sc_get_nodes_bounds(node, ((GF_ParentNode *)node)->children, &bounds_state, NULL); - gf_mx2d_from_mx(&mx, &tr_state->visual->compositor->hit_world_to_local); - gf_mx2d_apply_rect(&mx, &bounds_state.bounds); + gf_mx2d_from_mx(&vp_mx, &tr_state->visual->compositor->hit_world_to_local); + gf_mx2d_apply_rect(&vp_mx, &bounds_state.bounds); ext_vb.x = bounds_state.bounds.x; ext_vb.y = bounds_state.bounds.y-bounds_state.bounds.height; ext_vb.width = bounds_state.bounds.width; @@ -254,21 +253,18 @@ static void svg_recompute_viewport_transformation(GF_Node *node, SVGsvgStack *st stack->vpw = vp_w; stack->vph = vp_h; +#if 0 /*we need a clipper*/ if (stack->root_svg && !tr_state->parent_anim_atts && (par.meetOrSlice==SVG_MEETORSLICE_SLICE)) { GF_Rect rc; rc.width = parent_width; rc.height = parent_height; - if (!stack->root_svg) { - rc.x = 0; - rc.y = parent_height; - gf_mx2d_apply_rect(&stack->viewbox_mx, &rc); - } else { - rc.x = dx; - rc.y = dy + parent_height; - } -// tr_state->visual->top_clipper = gf_rect_pixelize(&rc); + rc.x = dx; + rc.y = dy + parent_height; + tr_state->visual->top_clipper = gf_rect_pixelize(&rc); } +#endif + } gf_mx2d_add_matrix(&stack->viewbox_mx, &mx); } @@ -368,6 +364,10 @@ static void svg_traverse_svg(GF_Node *node, void *rs, Bool is_destroy) vp_fill = tr_state->svg_props->viewport_fill; vp_opacity = tr_state->svg_props->viewport_fill_opacity ? tr_state->svg_props->viewport_fill_opacity->value : FIX_ONE; } + if (tr_state->visual->compositor->noback) { + vp_fill = NULL; + vp_opacity = 0; + } if (vp_fill && (vp_fill->type != SVG_PAINT_NONE) && vp_opacity) { Bool col_dirty = 0; @@ -503,7 +503,7 @@ void compositor_init_svg_svg(GF_Compositor *compositor, GF_Node *node) Bool compositor_svg_get_viewport(GF_Node *n, GF_Rect *rc) { SVGsvgStack *stack; - if (gf_node_get_tag(n) != TAG_SVG_svg) return 0; + if (!n || (gf_node_get_tag(n) != TAG_SVG_svg)) return 0; stack = gf_node_get_private(n); rc->width = stack->parent_vp.x; rc->height = stack->parent_vp.y; @@ -545,9 +545,9 @@ static void svg_traverse_g(GF_Node *node, void *rs, Bool is_destroy) return; } /*group cache traverse routine*/ - else if ((tr_state->traversing_mode == TRAVERSE_DRAW_2D) + else if (tr_state->traversing_mode == TRAVERSE_DRAW_2D #ifndef GPAC_DISABLE_3D - || (tr_state->traversing_mode == TRAVERSE_DRAW_3D) + || tr_state->traversing_mode == TRAVERSE_DRAW_3D #endif ) { SVGgStack *group = gf_node_get_private(node); @@ -583,7 +583,9 @@ static void svg_traverse_g(GF_Node *node, void *rs, Bool is_destroy) SVGgStack *group; if (!tr_state->in_svg_filter && all_atts.filter && all_atts.filter->iri.target) { +#ifdef GPAC_ENABLE_SVG_FILTERS svg_draw_filter(all_atts.filter->iri.target, node, tr_state); +#endif return; } group = gf_node_get_private(node); @@ -686,12 +688,14 @@ void compositor_init_svg_g(GF_Compositor *compositor, GF_Node *node) { SVGgStack *stack; GF_SAFEALLOC(stack, SVGgStack); + if (!stack) return; gf_node_set_private(node, stack); gf_node_set_callback_function(node, svg_traverse_g); } +#if 0 //unused static void svg_traverse_defs(GF_Node *node, void *rs, Bool is_destroy) { SVGPropertiesPointers backup_props; @@ -725,6 +729,7 @@ void compositor_init_svg_defs(GF_Compositor *compositor, GF_Node *node) { gf_node_set_callback_function(node, svg_traverse_defs); } +#endif @@ -778,7 +783,7 @@ static void svg_traverse_switch(GF_Node *node, void *rs, Bool is_destroy) compositor_svg_apply_local_transformation(tr_state, &all_atts, &backup_matrix, &mx_3d); if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) { gf_sc_get_nodes_bounds(node, ((SVG_Element *)node)->children, tr_state, selected_idx); - } else if (*selected_idx >= 0) { + } else { GF_Node *child = gf_node_list_get_child(((SVG_Element *)node)->children, *selected_idx); gf_node_traverse(child, tr_state); @@ -848,24 +853,6 @@ static void svg_traverse_a(GF_Node *node, void *rs, Bool is_destroy) tr_state->svg_flags = backup_flags; } -static Bool is_timing_target(GF_Node *n) -{ - if (!n) return 0; - switch (gf_node_get_tag(n)) { - case TAG_SVG_set: - case TAG_SVG_animate: - case TAG_SVG_animateColor: - case TAG_SVG_animateTransform: - case TAG_SVG_animateMotion: - case TAG_SVG_discard: - case TAG_SVG_animation: - case TAG_SVG_video: - case TAG_SVG_audio: - return 1; - } - return 0; -} - static void svg_a_set_view(GF_Node *handler, GF_Compositor *compositor, const char *url) { gf_scene_set_fragment_uri(handler, url); @@ -892,9 +879,8 @@ static void svg_a_handle_event(GF_Node *handler, GF_DOM_Event *event, GF_Node *o a = (SVG_Element *) event->currentTarget->ptr; gf_svg_flatten_attributes(a, &all_atts); - compositor = (GF_Compositor *)gf_node_get_private((GF_Node *)handler); + compositor = (GF_Compositor *)gf_node_get_private((GF_Node *)a); - if (!compositor->user->EventProc) return; if (!all_atts.xlink_href) return; if (event->type==GF_EVENT_MOUSEOVER) { @@ -907,14 +893,14 @@ static void svg_a_handle_event(GF_Node *handler, GF_DOM_Event *event, GF_Node *o if (!evt.navigate.to_url) evt.navigate.to_url = "document internal link"; } - gf_term_send_event(compositor->term, &evt); + gf_sc_send_event(compositor, &evt); return; } evt.type = GF_EVENT_NAVIGATE; if (all_atts.xlink_href->type == XMLRI_STRING) { - evt.navigate.to_url = gf_term_resolve_xlink(handler, all_atts.xlink_href->string); + evt.navigate.to_url = gf_scene_resolve_xlink(handler, all_atts.xlink_href->string); if (evt.navigate.to_url) { if (all_atts.target) { evt.navigate.parameters = (const char **) &all_atts.target; @@ -925,11 +911,7 @@ static void svg_a_handle_event(GF_Node *handler, GF_DOM_Event *event, GF_Node *o } if (evt.navigate.to_url[0] != '#') { - if (compositor->term) { - gf_scene_process_anchor(handler, &evt); - } else { - gf_term_send_event(compositor->term, &evt); - } + gf_scene_process_anchor(handler, &evt); gf_free((char *)evt.navigate.to_url); return; } @@ -948,12 +930,22 @@ static void svg_a_handle_event(GF_Node *handler, GF_DOM_Event *event, GF_Node *o return; } /*this is a time event*/ - if (is_timing_target(all_atts.xlink_href->target)) { + switch (gf_node_get_tag(all_atts.xlink_href->target)) { + case TAG_SVG_set: + case TAG_SVG_animate: + case TAG_SVG_animateColor: + case TAG_SVG_animateTransform: + case TAG_SVG_animateMotion: + case TAG_SVG_discard: + case TAG_SVG_animation: + case TAG_SVG_video: + case TAG_SVG_audio: gf_smil_timing_insert_clock(all_atts.xlink_href->target, 0, gf_node_get_scene_time((GF_Node *)handler) ); - } - /*this is an implicit SVGView event*/ - else { + break; + default: + /*this is an implicit SVGView event*/ svg_a_set_view(handler, compositor, gf_node_get_name(all_atts.xlink_href->target)); + break; } } @@ -961,24 +953,22 @@ void compositor_init_svg_a(GF_Compositor *compositor, GF_Node *node) { SVG_handlerElement *handler; gf_node_set_callback_function(node, svg_traverse_a); + gf_node_set_private((GF_Node *)node, compositor); /*listener for onClick event*/ handler = gf_dom_listener_build(node, GF_EVENT_CLICK, 0); /*and overwrite handler*/ handler->handle_event = svg_a_handle_event; - gf_node_set_private((GF_Node *)handler, compositor); /*listener for activate event*/ handler = gf_dom_listener_build(node, GF_EVENT_ACTIVATE, 0); /*and overwrite handler*/ handler->handle_event = svg_a_handle_event; - gf_node_set_private((GF_Node *)handler, compositor); /*listener for mousemove event*/ handler = gf_dom_listener_build(node, GF_EVENT_MOUSEOVER, 0); /*and overwrite handler*/ handler->handle_event = svg_a_handle_event; - gf_node_set_private((GF_Node *)handler, compositor); } @@ -1049,7 +1039,7 @@ static void svg_traverse_resource(GF_Node *node, void *rs, Bool is_destroy, Bool is_fragment = 1; } else if (stack->resource) { stack->inline_sg = gf_mo_get_scenegraph(stack->resource); - if (!is_foreign_object) { + if (!is_foreign_object && all_atts.xlink_href->string) { stack->fragment_id = strchr(all_atts.xlink_href->string, '#'); } } @@ -1136,6 +1126,7 @@ void compositor_init_svg_use(GF_Compositor *compositor, GF_Node *node) { SVGlinkStack *stack; GF_SAFEALLOC(stack, SVGlinkStack); + if (!stack) return; gf_node_set_private(node, stack); gf_node_set_callback_function(node, svg_traverse_use); /*force first processing of xlink-href*/ @@ -1179,15 +1170,9 @@ static void svg_animation_smil_update(GF_Node *node, SVGlinkStack *stack, Fixed } } -static void svg_reset_xlink_target(GF_Node *node) -{ - SVGAllAttributes all_atts; - gf_svg_flatten_attributes((SVG_Element *)node, &all_atts); - if (all_atts.xlink_href) all_atts.xlink_href->target=NULL; -} - -static void svg_animation_smil_evaluate(SMIL_Timing_RTI *rti, Fixed normalized_scene_time, u32 status) +static void svg_animation_smil_evaluate(SMIL_Timing_RTI *rti, Fixed normalized_scene_time, GF_SGSMILTimingEvalState status) { + Bool reset_target = GF_FALSE; GF_Node *node = gf_smil_get_element(rti); SVGlinkStack *stack = gf_node_get_private(node); switch (status) { @@ -1196,13 +1181,13 @@ static void svg_animation_smil_evaluate(SMIL_Timing_RTI *rti, Fixed normalized_s break; case SMIL_TIMING_EVAL_FREEZE: if (stack->resource) { - gf_mo_stop(stack->resource); + gf_mo_stop(&stack->resource); stack->needs_play = 1; } break; case SMIL_TIMING_EVAL_REMOVE: if (stack->resource) { - svg_reset_xlink_target(node); + reset_target = GF_TRUE; gf_mo_unload_xlink_resource(node, stack->resource); stack->resource = NULL; stack->fragment_id = NULL; @@ -1212,12 +1197,19 @@ static void svg_animation_smil_evaluate(SMIL_Timing_RTI *rti, Fixed normalized_s break; case SMIL_TIMING_EVAL_REPEAT: if (stack->resource) { - svg_reset_xlink_target(node); + reset_target = GF_TRUE; stack->fragment_id = NULL; stack->inline_sg = NULL; gf_mo_restart(stack->resource); } break; + default: + break; + } + if (reset_target) { + SVGAllAttributes all_atts; + gf_svg_flatten_attributes((SVG_Element *)node, &all_atts); + if (all_atts.xlink_href) all_atts.xlink_href->target=NULL; } } @@ -1316,7 +1308,7 @@ static void svg_traverse_animation(GF_Node *node, void *rs, Bool is_destroy) if (subroot) tr_state->visual->compositor->focus_node = subroot; } - if (stack->inline_sg) { + if (stack->inline_sg && stack->resource->odm) { gf_sc_traverse_subscene(tr_state->visual->compositor, node, stack->inline_sg, tr_state); } @@ -1343,6 +1335,7 @@ void compositor_init_svg_animation(GF_Compositor *compositor, GF_Node *node) SVGlinkStack *stack; GF_SAFEALLOC(stack, SVGlinkStack); + if (!stack) return; gf_node_set_private(node, stack); gf_node_set_callback_function(node, svg_traverse_animation); @@ -1369,6 +1362,7 @@ void compositor_init_svg_foreign_object(GF_Compositor *compositor, GF_Node *node { SVGlinkStack *stack; GF_SAFEALLOC(stack, SVGlinkStack); + if (!stack) return; gf_node_set_private(node, stack); gf_node_set_callback_function(node, svg_traverse_foreign_object); /*force first processing of xlink-href*/ @@ -1391,7 +1385,7 @@ GF_Node *compositor_svg_get_xlink_resource_node(GF_Node *node, XMLRI *xlink) return NULL; } -GF_EXPORT +#if 0 //unused GF_SceneGraph *gf_sc_animation_get_scenegraph(GF_Node *node) { SVGlinkStack *stack; @@ -1399,6 +1393,7 @@ GF_SceneGraph *gf_sc_animation_get_scenegraph(GF_Node *node) stack = gf_node_get_private(node); return stack->inline_sg; } +#endif #endif diff --git a/src/compositor/svg_media.c b/src/compositor/svg_media.c index 20e16de..76bee74 100644 --- a/src/compositor/svg_media.c +++ b/src/compositor/svg_media.c @@ -28,24 +28,10 @@ #ifndef GPAC_DISABLE_SVG #include "nodes_stacks.h" -#include - static void svg_audio_smil_evaluate_ex(SMIL_Timing_RTI *rti, Fixed normalized_scene_time, u32 status, GF_Node *audio, GF_Node *video); static void svg_traverse_audio_ex(GF_Node *node, void *rs, Bool is_destroy, SVGPropertiesPointers *props); -typedef struct -{ - GF_TextureHandler txh; - Drawable *drawable; - MFURL txurl; - Bool first_frame_fetched; - GF_Node *audio; - Bool audio_dirty; - Bool stop_requested; -} SVG_video_stack; - - static Bool svg_video_get_transform_behavior(GF_TraverseState *tr_state, SVGAllAttributes *atts, Fixed *cx, Fixed *cy, Fixed *angle) { @@ -206,7 +192,7 @@ static void SVG_Build_Bitmap_Graph(SVG_video_stack *stack, GF_TraverseState *tr_ drawable_reset_path(stack->drawable); gf_path_add_rect_center(stack->drawable->path, rectx, recty, rectwidth, rectheight); gf_path_get_bounds(stack->drawable->path, &new_rc); - if (!gf_rect_equal(rc, new_rc)) + if (!gf_rect_equal(&rc, &new_rc)) drawable_mark_modified(stack->drawable, tr_state); else if (stack->txh.flags & GF_SR_TEXTURE_PRIVATE_MEDIA) drawable_mark_modified(stack->drawable, tr_state); @@ -223,7 +209,7 @@ static void svg_play_texture(SVG_video_stack *stack, SVGAllAttributes *atts) { SVGAllAttributes all_atts; Bool lock_scene = GF_FALSE; - if (stack->txh.is_open) gf_sc_texture_stop(&stack->txh); + if (stack->txh.is_open) gf_sc_texture_stop_no_unregister(&stack->txh); if (!atts) { gf_svg_flatten_attributes((SVG_Element*)stack->txh.owner, &all_atts); @@ -290,7 +276,7 @@ static void svg_traverse_bitmap(GF_Node *node, void *rs, Bool is_destroy) if (gf_node_dirty_get(node) & GF_SG_SVG_XLINK_HREF_DIRTY) { if (!stack->txh.stream || gf_mo_url_changed(stack->txh.stream, &stack->txurl)) { - gf_term_get_mfurl_from_xlink(node, &stack->txurl); + gf_sc_get_mfurl_from_xlink(node, &stack->txurl); stack->txh.width = stack->txh.height = 0; /*remove associated audio if any*/ @@ -312,7 +298,6 @@ static void svg_traverse_bitmap(GF_Node *node, void *rs, Bool is_destroy) gf_node_dirty_clear(node, 0); SVG_Build_Bitmap_Graph((SVG_video_stack*)gf_node_get_private(node), tr_state); } - } if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) { @@ -486,7 +471,7 @@ static void SVG_Update_video(GF_TextureHandler *txh) stack->first_frame_fetched = GF_TRUE; /*stop stream if needed*/ if (!gf_smil_timing_is_active(txh->owner)) { - gf_sc_texture_stop(txh); + gf_sc_texture_stop_no_unregister(txh); //make sure the refresh flag is not cleared txh->needs_refresh = GF_TRUE; } @@ -519,11 +504,11 @@ static void SVG_Update_video(GF_TextureHandler *txh) if (stack->stop_requested) { stack->stop_requested = GF_FALSE; - gf_sc_texture_stop(&stack->txh); + gf_sc_texture_stop_no_unregister(&stack->txh); } } -static void svg_video_smil_evaluate(SMIL_Timing_RTI *rti, Fixed normalized_scene_time, u32 status) +static void svg_video_smil_evaluate(SMIL_Timing_RTI *rti, Fixed normalized_scene_time, GF_SGSMILTimingEvalState status) { SVG_video_stack *stack = (SVG_video_stack *)gf_node_get_private(gf_smil_get_element(rti)); @@ -550,6 +535,8 @@ static void svg_video_smil_evaluate(SMIL_Timing_RTI *rti, Fixed normalized_scene case SMIL_TIMING_EVAL_REPEAT: gf_sc_texture_restart(&stack->txh); break; + default: + break; } if (stack->audio) svg_audio_smil_evaluate_ex(rti, normalized_scene_time, status, stack->audio, stack->txh.owner); } @@ -619,12 +606,6 @@ void compositor_svg_video_modified(GF_Compositor *compositor, GF_Node *node) /*********************/ /* SVG audio element */ /*********************/ -typedef struct -{ - GF_AudioInput input; - Bool is_active, is_error; - MFURL aurl; -} SVG_audio_stack; static void svg_audio_smil_evaluate_ex(SMIL_Timing_RTI *rti, Fixed normalized_scene_time, u32 status, GF_Node *slave_audio, GF_Node *video) { @@ -661,7 +642,7 @@ static void svg_audio_smil_evaluate_ex(SMIL_Timing_RTI *rti, Fixed normalized_sc else if (!slave_audio && stack->input.stream_finished && (gf_smil_get_media_duration(rti) < 0) ) { Double dur = gf_mo_get_duration(stack->input.stream); if (dur <= 0) { - dur = gf_mo_get_last_frame_time(stack->input.stream); + dur = stack->input.stream ? stack->input.stream->timestamp : 0; dur /= 1000; } gf_smil_set_media_duration(rti, dur); @@ -689,7 +670,7 @@ static void svg_audio_smil_evaluate_ex(SMIL_Timing_RTI *rti, Fixed normalized_sc } } -static void svg_audio_smil_evaluate(SMIL_Timing_RTI *rti, Fixed normalized_scene_time, u32 status) +static void svg_audio_smil_evaluate(SMIL_Timing_RTI *rti, Fixed normalized_scene_time, GF_SGSMILTimingEvalState status) { svg_audio_smil_evaluate_ex(rti, normalized_scene_time, status, NULL, NULL); } @@ -731,7 +712,7 @@ static void svg_traverse_audio_ex(GF_Node *node, void *rs, Bool is_destroy, SVGP stack->is_error = GF_FALSE; gf_node_dirty_clear(node, GF_SG_SVG_XLINK_HREF_DIRTY); - gf_term_get_mfurl_from_xlink(node, &(stack->aurl)); + gf_sc_get_mfurl_from_xlink(node, &(stack->aurl)); gf_svg_flatten_attributes((SVG_Element*) node, &atts); if (atts.syncBehavior) lock_timeline = (*atts.syncBehavior == SMIL_SYNCBEHAVIOR_LOCKED) ? GF_TRUE : GF_FALSE; @@ -775,7 +756,7 @@ void compositor_init_svg_audio(GF_Compositor *compositor, GF_Node *node, Bool sl { SVG_audio_stack *stack; GF_SAFEALLOC(stack, SVG_audio_stack) - + if (!stack) return; gf_sc_audio_setup(&stack->input, compositor, node); /*force first processing of xlink-href*/ @@ -813,7 +794,7 @@ typedef struct Double clipBegin, clipEnd; } SVG_updates_stack; -static void svg_updates_smil_evaluate(SMIL_Timing_RTI *rti, Fixed normalized_scene_time, u32 status) +static void svg_updates_smil_evaluate(SMIL_Timing_RTI *rti, Fixed normalized_scene_time, GF_SGSMILTimingEvalState status) { SVG_updates_stack *stack = (SVG_updates_stack *)gf_node_get_private(gf_smil_get_element(rti)); @@ -832,11 +813,13 @@ static void svg_updates_smil_evaluate(SMIL_Timing_RTI *rti, Fixed normalized_sce case SMIL_TIMING_EVAL_REMOVE: stack->is_open = GF_FALSE; gf_mo_set_flag(stack->resource, GF_MO_DISPLAY_REMOVE, GF_TRUE); - gf_mo_stop(stack->resource); + gf_mo_stop(&stack->resource); break; case SMIL_TIMING_EVAL_REPEAT: gf_mo_restart(stack->resource); break; + default: + break; } } @@ -853,7 +836,7 @@ static void svg_traverse_updates(GF_Node *node, void *rs, Bool is_destroy) if (stack->resource) { if (stack->is_open) { gf_mo_set_flag(stack->resource, GF_MO_DISPLAY_REMOVE, GF_TRUE); - gf_mo_stop(stack->resource); + gf_mo_stop(&stack->resource); } gf_mo_unregister(node, stack->resource); } @@ -881,14 +864,14 @@ static void svg_traverse_updates(GF_Node *node, void *rs, Bool is_destroy) if (all_atts.syncBehavior) lock_timeline = (*all_atts.syncBehavior == SMIL_SYNCBEHAVIOR_LOCKED) ? GF_TRUE : GF_FALSE; - gf_term_get_mfurl_from_xlink(node, &url); + gf_sc_get_mfurl_from_xlink(node, &url); new_res = gf_mo_register(node, &url, lock_timeline, GF_FALSE); gf_sg_mfurl_del(url); if (stack->resource!=new_res) { if (stack->resource) { - gf_mo_stop(stack->resource); + gf_mo_stop(&stack->resource); gf_mo_unregister(node, stack->resource); } stack->resource = new_res; diff --git a/src/compositor/svg_paint_servers.c b/src/compositor/svg_paint_servers.c index 804eede..821e1da 100644 --- a/src/compositor/svg_paint_servers.c +++ b/src/compositor/svg_paint_servers.c @@ -109,7 +109,6 @@ static GF_Node *svg_copy_gradient_attributes_from(GF_Node *node, SVGAllAttribute static void svg_gradient_traverse(GF_Node *node, GF_TraverseState *tr_state, Bool real_traverse) { - GF_STENCIL stencil; u32 count, nb_col; Bool is_dirty, all_dirty; Fixed alpha, max_offset; @@ -214,10 +213,11 @@ static void svg_gradient_traverse(GF_Node *node, GF_TraverseState *tr_state, Boo if (is_dirty) { u32 i; + GF_EVGStencil * stencil; if (!st->txh.tx_io) gf_sc_texture_allocate(&st->txh); stencil = gf_sc_texture_get_stencil(&st->txh); - if (!stencil) stencil = st->txh.compositor->rasterizer->stencil_new(st->txh.compositor->rasterizer, st->linear ? GF_STENCIL_LINEAR_GRADIENT : GF_STENCIL_RADIAL_GRADIENT); + if (!stencil) stencil = gf_evg_stencil_new(st->linear ? GF_STENCIL_LINEAR_GRADIENT : GF_STENCIL_RADIAL_GRADIENT); /*set stencil even if assigned, this invalidates the associated bitmap state in 3D*/ gf_sc_texture_set_stencil(&st->txh, stencil); @@ -229,8 +229,8 @@ static void svg_gradient_traverse(GF_Node *node, GF_TraverseState *tr_state, Boo } } - st->txh.compositor->rasterizer->stencil_set_gradient_interpolation(stencil, st->keys, st->cols, nb_col); - st->txh.compositor->rasterizer->stencil_set_gradient_mode(stencil, /*lg->spreadMethod*/ GF_GRADIENT_MODE_PAD); + gf_evg_stencil_set_gradient_interpolation(stencil, st->keys, st->cols, nb_col); + gf_evg_stencil_set_gradient_mode(stencil, /*lg->spreadMethod*/ GF_GRADIENT_MODE_PAD); st->txh.needs_refresh = GF_TRUE; } else { @@ -257,13 +257,15 @@ static void svg_update_gradient(SVG_GradientStack *st, GF_ChildNodeItem *childre if (!tr_state->svg_props) { GF_SAFEALLOC(svgp, SVGPropertiesPointers); - gf_svg_properties_init_pointers(svgp); - tr_state->svg_props = svgp; + if (svgp) { + gf_svg_properties_init_pointers(svgp); + tr_state->svg_props = svgp; - svg_gradient_traverse(node, tr_state, GF_FALSE); + svg_gradient_traverse(node, tr_state, GF_FALSE); - gf_svg_properties_reset_pointers(svgp); - gf_free(svgp); + gf_svg_properties_reset_pointers(svgp); + gf_free(svgp); + } tr_state->svg_props = NULL; } else { svg_gradient_traverse(node, tr_state, GF_FALSE); @@ -341,16 +343,14 @@ void compositor_svg_build_gradient_texture(GF_TextureHandler *txh) u32 i; Fixed size; GF_Matrix2D mat; - GF_STENCIL stencil; - GF_SURFACE surface; - GF_STENCIL texture2D; + GF_EVGStencil * stencil; + GF_EVGSurface *surface; + GF_EVGStencil * texture2D; GF_Path *path; GF_Err e; Bool transparent; SVGAllAttributes all_atts; SVG_GradientStack *st = (SVG_GradientStack *) gf_node_get_private(txh->owner); - GF_Raster2D *raster = txh->compositor->rasterizer; - if (!txh->tx_io) return; @@ -368,11 +368,11 @@ void compositor_svg_build_gradient_texture(GF_TextureHandler *txh) if (!stencil) return; /*init our 2D graphics stuff*/ - texture2D = raster->stencil_new(raster, GF_STENCIL_TEXTURE); + texture2D = gf_evg_stencil_new(GF_STENCIL_TEXTURE); if (!texture2D) return; - surface = raster->surface_new(raster, GF_TRUE); + surface = gf_evg_surface_new(GF_TRUE); if (!surface) { - raster->stencil_delete(texture2D); + gf_evg_stencil_delete(texture2D); return; } @@ -385,12 +385,12 @@ void compositor_svg_build_gradient_texture(GF_TextureHandler *txh) } else { memset(txh->data, 0, sizeof(char)*txh->stride*txh->height); } - e = raster->stencil_set_texture(texture2D, txh->data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, GF_PIXEL_ARGB, GF_PIXEL_ARGB, GF_TRUE); + e = gf_evg_stencil_set_texture(texture2D, txh->data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, GF_PIXEL_ARGB); } else { if (!txh->data) { txh->data = (char *) gf_malloc(sizeof(char)*GRAD_TEXTURE_SIZE*GRAD_TEXTURE_SIZE*3); } - e = raster->stencil_set_texture(texture2D, txh->data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 3*GRAD_TEXTURE_SIZE, GF_PIXEL_RGB_24, GF_PIXEL_RGB_24, GF_TRUE); + e = gf_evg_stencil_set_texture(texture2D, txh->data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 3*GRAD_TEXTURE_SIZE, GF_PIXEL_RGB); /*try with ARGB (it actually is needed for GDIplus module since GDIplus cannot handle native RGB texture (it works in BGR)*/ if (e) { /*remember for later use*/ @@ -398,21 +398,21 @@ void compositor_svg_build_gradient_texture(GF_TextureHandler *txh) transparent = GF_TRUE; gf_free(txh->data); txh->data = (char *) gf_malloc(sizeof(char)*GRAD_TEXTURE_SIZE*GRAD_TEXTURE_SIZE*4); - e = raster->stencil_set_texture(texture2D, txh->data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, GF_PIXEL_ARGB, GF_PIXEL_ARGB, GF_TRUE); + e = gf_evg_stencil_set_texture(texture2D, txh->data, GRAD_TEXTURE_SIZE, GRAD_TEXTURE_SIZE, 4*GRAD_TEXTURE_SIZE, GF_PIXEL_ARGB); } } if (e) { gf_free(txh->data); txh->data = NULL; - raster->stencil_delete(texture2D); - raster->surface_delete(surface); + gf_evg_stencil_delete(texture2D); + gf_evg_surface_delete(surface); return; } - e = raster->surface_attach_to_texture(surface, texture2D); + e = gf_evg_surface_attach_to_texture(surface, texture2D); if (e) { - raster->stencil_delete(texture2D); - raster->surface_delete(surface); + gf_evg_stencil_delete(texture2D); + gf_evg_surface_delete(surface); return; } @@ -444,12 +444,12 @@ void compositor_svg_build_gradient_texture(GF_TextureHandler *txh) gf_mx2d_add_translation(&mat, -size, -size); } - raster->stencil_set_matrix(stencil, &mat); - raster->surface_set_raster_level(surface, GF_RASTER_HIGH_QUALITY); - raster->surface_set_path(surface, path); - raster->surface_fill(surface, stencil); - raster->surface_delete(surface); - raster->stencil_delete(texture2D); + gf_evg_stencil_set_matrix(stencil, &mat); + gf_evg_surface_set_raster_level(surface, GF_RASTER_HIGH_QUALITY); + gf_evg_surface_set_path(surface, path); + gf_evg_surface_fill(surface, stencil); + gf_evg_surface_delete(surface); + gf_evg_stencil_delete(texture2D); gf_path_del(path); txh->width = GRAD_TEXTURE_SIZE; @@ -475,7 +475,7 @@ void compositor_svg_build_gradient_texture(GF_TextureHandler *txh) } } else { txh->stride = GRAD_TEXTURE_SIZE*3; - txh->pixelformat = GF_PIXEL_RGB_24; + txh->pixelformat = GF_PIXEL_RGB; } gf_sc_texture_set_data(txh); return; @@ -495,7 +495,7 @@ static void SVG_UpdateLinearGradient(GF_TextureHandler *txh) static void SVG_LG_ComputeMatrix(GF_TextureHandler *txh, GF_Rect *bounds, GF_Matrix2D *mat, Bool for_3d) { - GF_STENCIL stencil; + GF_EVGStencil * stencil; SFVec2f start, end; SVGAllAttributes all_atts; SVG_Element *lg = (SVG_Element *) txh->owner; @@ -555,7 +555,7 @@ static void SVG_LG_ComputeMatrix(GF_TextureHandler *txh, GF_Rect *bounds, GF_Mat end.y = 0; } - txh->compositor->rasterizer->stencil_set_gradient_mode(stencil, (GF_GradientMode) all_atts.spreadMethod ? *(SVG_SpreadMethod*)all_atts.spreadMethod : 0); + gf_evg_stencil_set_gradient_mode(stencil, (GF_GradientMode) all_atts.spreadMethod ? *(SVG_SpreadMethod*)all_atts.spreadMethod : 0); if (bounds && (!all_atts.gradientUnits || (*(SVG_GradientUnit*)all_atts.gradientUnits==SVG_GRADIENTUNITS_OBJECT)) ) { @@ -563,7 +563,7 @@ static void SVG_LG_ComputeMatrix(GF_TextureHandler *txh, GF_Rect *bounds, GF_Mat gf_mx2d_add_scale(mat, bounds->width, bounds->height); gf_mx2d_add_translation(mat, bounds->x, bounds->y - bounds->height); } - txh->compositor->rasterizer->stencil_set_linear_gradient(stencil, start.x, start.y, end.x, end.y); + gf_evg_stencil_set_linear_gradient(stencil, start.x, start.y, end.x, end.y); } void compositor_init_svg_linearGradient(GF_Compositor *compositor, GF_Node *node) @@ -601,7 +601,7 @@ static void SVG_UpdateRadialGradient(GF_TextureHandler *txh) static void SVG_RG_ComputeMatrix(GF_TextureHandler *txh, GF_Rect *bounds, GF_Matrix2D *mat, Bool for_3d) { - GF_STENCIL stencil; + GF_EVGStencil * stencil; SFVec2f center, focal; Fixed radius; SVGAllAttributes all_atts; @@ -618,7 +618,7 @@ static void SVG_RG_ComputeMatrix(GF_TextureHandler *txh, GF_Rect *bounds, GF_Mat gf_svg_flatten_attributes((SVG_Element*)txh->owner, &all_atts); - /*get "transfered" attributed from xlink:href if any*/ + /*get transfered attributed from xlink:href if any*/ svg_copy_gradient_attributes_from(txh->owner, &all_atts); gf_mx2d_init(*mat); @@ -660,7 +660,7 @@ static void SVG_RG_ComputeMatrix(GF_TextureHandler *txh, GF_Rect *bounds, GF_Mat center.y = FIX_ONE/2; } - txh->compositor->rasterizer->stencil_set_gradient_mode(stencil, (GF_GradientMode) all_atts.spreadMethod ? *(SVG_SpreadMethod*)all_atts.spreadMethod : 0); + gf_evg_stencil_set_gradient_mode(stencil, (GF_GradientMode) all_atts.spreadMethod ? *(SVG_SpreadMethod*)all_atts.spreadMethod : 0); if (all_atts.fx) { focal.x = all_atts.fx->value; @@ -677,9 +677,9 @@ static void SVG_RG_ComputeMatrix(GF_TextureHandler *txh, GF_Rect *bounds, GF_Mat /* clamping fx/fy according to: http://www.w3.org/TR/SVG11/pservers.html#RadialGradients - If the point defined by ‘fx’ and ‘fy’ lies outside the circle defined by ‘cx’, ‘cy’ and ‘r’, - then the user agent shall set the focal point to the intersection of the line from (‘cx’, ‘cy’) - to (‘fx’, ‘fy’) with the circle defined by ‘cx’, ‘cy’ and ‘r’.*/ + If the point defined by fx and fy lies outside the circle defined by cx, cy and r, + then the user agent shall set the focal point to the intersection of the line from (cx, cy) + to (fx, fy) with the circle defined by cx, cy and r.*/ { Fixed norm = gf_v2d_distance(&focal, ¢er); if (norm > radius) { @@ -695,7 +695,7 @@ static void SVG_RG_ComputeMatrix(GF_TextureHandler *txh, GF_Rect *bounds, GF_Mat gf_mx2d_add_scale(mat, bounds->width, bounds->height); gf_mx2d_add_translation(mat, bounds->x, bounds->y - bounds->height); } - txh->compositor->rasterizer->stencil_set_radial_gradient(stencil, center.x, center.y, focal.x, focal.y, radius, radius); + gf_evg_stencil_set_radial_gradient(stencil, center.x, center.y, focal.x, focal.y, radius, radius); } void compositor_init_svg_radialGradient(GF_Compositor *compositor, GF_Node *node) @@ -779,6 +779,7 @@ void compositor_init_svg_solidColor(GF_Compositor *compositor, GF_Node *node) { GF_SolidColorStack *st; GF_SAFEALLOC(st, GF_SolidColorStack); + if (!st) return; gf_node_set_private(node, st); gf_node_set_callback_function(node, svg_traverse_solidColor); } diff --git a/src/compositor/svg_text.c b/src/compositor/svg_text.c index 08a9583..c212711 100644 --- a/src/compositor/svg_text.c +++ b/src/compositor/svg_text.c @@ -360,7 +360,7 @@ static void svg_text_area_queue_state(GF_TraverseState *tr_state, GF_TextSpan *s u32 i, count; count = gf_list_count(tr_state->x_anchors); for (i=0; ix_anchors, i); + st = (textArea_state*)gf_list_get(tr_state->x_anchors, i); if (st->span==span) { st->last_glyph = last_glyph; return; @@ -393,7 +393,7 @@ static void svg_text_area_apply_diff_baselines(GF_TraverseState *tr_state, Fixed static void svg_traverse_dom_text_area(GF_Node *node, SVGAllAttributes *atts, GF_TraverseState *tr_state, GF_List *spans) { GF_DOMText *dom_text = (GF_DOMText *)node; - u32 word_start, word_end; + u32 word_start; u32 i, j; Fixed line_spacing; GF_Font *font; @@ -424,7 +424,7 @@ static void svg_traverse_dom_text_area(GF_Node *node, SVGAllAttributes *atts, GF line_spacing = gf_mulfix(span->font_size, FLT2FIX(1.0) ); - word_start = word_end = 0; + word_start = 0; i = 0; /* boucle principale: mot par mot */ while (inb_glyphs) { @@ -556,7 +556,6 @@ static void get_domtext_width(GF_Node *node, SVGAllAttributes *atts, GF_Traverse if ((tr_state->count_x==1)||(tr_state->count_y==1) || !gf_list_count(tr_state->x_anchors) ) { entry = (Fixed*)gf_malloc(sizeof(Fixed)); - *entry = block_width; if (span->flags & GF_TEXT_SPAN_RIGHT_TO_LEFT) *entry = -block_width; else *entry = block_width; @@ -843,7 +842,6 @@ static void svg_traverse_text(GF_Node *node, void *rs, Bool is_destroy) SVG_Element *text = (SVG_Element *)node; SVGAllAttributes atts; u32 i,imax; - Fixed * lw; if (is_destroy) { drawable_del(st->drawable); @@ -949,7 +947,7 @@ static void svg_traverse_text(GF_Node *node, void *rs, Bool is_destroy) /*apply justification of all blocks*/ imax=gf_list_count(tr_state->x_anchors); for (i=0; ix_anchors, i); + Fixed *lw = gf_list_get(tr_state->x_anchors, i); svg_apply_text_anchor(tr_state, lw); } @@ -1344,11 +1342,11 @@ static void svg_traverse_textArea(GF_Node *node, void *rs, Bool is_destroy) tr_state->text_end_x = 0; tr_state->text_end_y = (tr_state->svg_props->line_increment->type == SVG_NUMBER_AUTO ? 0 : tr_state->svg_props->line_increment->value); - tr_state->x_anchors = gf_list_new(); - if (tr_state->svg_props->font_size && (tr_state->svg_props->font_size->value <= tr_state->max_height)) { Fixed remain; u32 c, refresh_to_idx, prev_refresh; + tr_state->x_anchors = gf_list_new(); + /*switch to bounds mode, and recompute children*/ mode = tr_state->traversing_mode; tr_state->traversing_mode = TRAVERSE_GET_BOUNDS; diff --git a/src/compositor/texturing.c b/src/compositor/texturing.c index 51588c9..8197fa7 100644 --- a/src/compositor/texturing.c +++ b/src/compositor/texturing.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2018 * All rights reserved * * This file is part of GPAC / Scene Compositor sub-project @@ -24,7 +24,6 @@ */ #include "texturing.h" -#include #include #include "nodes_stacks.h" @@ -78,6 +77,9 @@ GF_Err gf_sc_texture_open(GF_TextureHandler *txh, MFURL *url, Bool lock_scene_ti /*get media object*/ txh->stream = gf_mo_register(txh->owner, url, lock_scene_timeline, 0); + //consider the texture open to avoid repeatingly calling for open on bad URLs/OD + txh->is_open = 1; + /*bad/Empty URL*/ if (!txh->stream) return GF_NOT_SUPPORTED; @@ -89,19 +91,22 @@ GF_Err gf_sc_texture_play_from_to(GF_TextureHandler *txh, MFURL *url, Double sta { if (!txh->stream) { GF_Err e; + if (!url) return GF_BAD_PARAM; e = gf_sc_texture_open(txh, url, lock_scene_timeline); if (e != GF_OK) return e; } + txh->is_open = 1; /*request play*/ gf_mo_play(txh->stream, start_offset, end_offset, can_loop); txh->last_frame_time = (u32) (-1); - //gf_sc_invalidate(txh->compositor, NULL); - txh->is_open = 1; + //we need to rework the raw memory stuff to be transparent +#ifdef FILTER_FIXME + txh->raw_memory = GF_FALSE; +#endif /*request play*/ - txh->raw_memory = gf_mo_is_raw_memory(txh->stream); return GF_OK; } @@ -110,14 +115,34 @@ GF_Err gf_sc_texture_play(GF_TextureHandler *txh, MFURL *url) { Double offset = 0; Bool loop = 0; - if (txh->compositor->term && (txh->compositor->term->play_state!=GF_STATE_PLAYING)) { + + if (txh->compositor->play_state != GF_STATE_PLAYING) { offset = gf_node_get_scene_time(txh->owner); loop = /*gf_mo_get_loop(gf_mo_register(txh->owner, url, 0, 0), 0)*/ 1; } + return gf_sc_texture_play_from_to(txh, url, offset, -1, loop, 0); } +GF_EXPORT +void gf_sc_texture_stop_no_unregister(GF_TextureHandler *txh) +{ + if (!txh->is_open) return; + /*release texture WITHOUT droping frame*/ + if (txh->needs_release) { + gf_mo_release_data(txh->stream, 0xFFFFFFFF, 1); + txh->needs_release = 0; + txh->frame_ifce = NULL; + } + gf_sc_invalidate(txh->compositor, NULL); + gf_mo_stop(&txh->stream); + txh->data = NULL; + txh->frame_ifce = NULL; + + txh->is_open = 0; +} + GF_EXPORT void gf_sc_texture_stop(GF_TextureHandler *txh) { @@ -126,14 +151,15 @@ void gf_sc_texture_stop(GF_TextureHandler *txh) if (txh->needs_release) { gf_mo_release_data(txh->stream, 0xFFFFFFFF, -1); txh->needs_release = 0; - txh->frame = NULL; + txh->frame_ifce = NULL; } gf_sc_invalidate(txh->compositor, NULL); - if (gf_mo_stop(txh->stream)) { + gf_mo_stop(&txh->stream); + if (!txh->stream) { txh->data = NULL; } txh->is_open = 0; - + /*and deassociate object*/ gf_mo_unregister(txh->owner, txh->stream); txh->stream = NULL; @@ -158,6 +184,8 @@ static void setup_texture_object(GF_TextureHandler *txh, Bool private_media) gf_mo_get_visual_info(txh->stream, &txh->width, &txh->height, &txh->stride, &txh->pixel_ar, &txh->pixelformat, &txh->is_flipped); gf_sc_texture_configure_conversion(txh); + gf_pixel_get_size_info(txh->pixelformat, txh->width, txh->height, NULL, NULL, NULL, &txh->nb_planes, NULL); + if (private_media) { txh->transparent = 1; txh->pixelformat = GF_PIXEL_ARGB; @@ -166,6 +194,7 @@ static void setup_texture_object(GF_TextureHandler *txh, Bool private_media) txh->transparent = 0; switch (txh->pixelformat) { case GF_PIXEL_ALPHAGREY: + case GF_PIXEL_GREYALPHA: case GF_PIXEL_ARGB: case GF_PIXEL_RGBA: case GF_PIXEL_YUVA: @@ -174,7 +203,6 @@ static void setup_texture_object(GF_TextureHandler *txh, Bool private_media) break; } } - gf_mo_set_flag(txh->stream, GF_MO_IS_INIT, GF_TRUE); } } @@ -186,22 +214,26 @@ void gf_sc_texture_update_frame(GF_TextureHandler *txh, Bool disable_resync) u32 size, ts, push_time; s32 ms_until_pres, ms_until_next; - /*already refreshed*/ - if ((txh->stream_finished && txh->tx_io) || txh->needs_refresh) return; - if (!txh->stream) { txh->data = NULL; return; } + if (txh->stream->config_changed) { + txh->data = NULL; + } + + /*already refreshed*/ + if ((txh->stream_finished && txh->tx_io) || txh->needs_refresh) + return; /*should never happen!!*/ if (txh->needs_release) { gf_mo_release_data(txh->stream, 0xFFFFFFFF, 0); - txh->frame=NULL; + txh->frame_ifce=NULL; } /*check init flag*/ - if (!(gf_mo_get_flags(txh->stream) & GF_MO_IS_INIT)) { + if (txh->stream->config_changed) { needs_reload = 1; txh->data = NULL; if (txh->tx_io) { @@ -211,11 +243,11 @@ void gf_sc_texture_update_frame(GF_TextureHandler *txh, Bool disable_resync) //if first frame use 20ms as upload time push_time = txh->nb_frames ? txh->upload_time/txh->nb_frames : 20; - txh->data = gf_mo_fetch_data(txh->stream, disable_resync ? GF_MO_FETCH : GF_MO_FETCH_RESYNC, push_time, &txh->stream_finished, &ts, &size, &ms_until_pres, &ms_until_next, &txh->frame); + txh->data = gf_mo_fetch_data(txh->stream, disable_resync ? GF_MO_FETCH : GF_MO_FETCH_RESYNC, push_time, &txh->stream_finished, &ts, &size, &ms_until_pres, &ms_until_next, &txh->frame_ifce, NULL); - if (!(gf_mo_get_flags(txh->stream) & GF_MO_IS_INIT)) { + if (txh->stream->config_changed) { needs_reload = 1; - } else if (size && txh->size && (size != txh->size)) { + } else if (txh->data && size && txh->size && (size != txh->size)) { needs_reload = 1; } @@ -226,19 +258,27 @@ void gf_sc_texture_update_frame(GF_TextureHandler *txh, Bool disable_resync) gf_sc_texture_release(txh); txh->needs_refresh = 1; } - if (gf_mo_is_private_media(txh->stream)) { - setup_texture_object(txh, 1); - gf_node_dirty_set(txh->owner, 0, 0); - } + txh->flags &= ~GF_SR_TEXTURE_DISABLE_BLIT; + } /*if no frame or muted don't draw*/ - if (!txh->data || !size) { - GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Visual Texture] No output frame available \n")); - assert(!txh->frame); - + if (!txh->data && !txh->frame_ifce) { + GF_LOG(txh->stream->connect_failure ? GF_LOG_DEBUG : GF_LOG_INFO, GF_LOG_COMPOSE, ("[Texture %p] No output frame available \n", txh)); + + if (txh->compositor->use_step_mode || !txh->compositor->player) { + if (!txh->stream->connect_failure && ((s32)txh->last_frame_time<0) ) { + if (!txh->probe_time_ms) txh->probe_time_ms = gf_sys_clock(); + else if (gf_sys_clock() - txh->probe_time_ms > txh->compositor->timeout / 2) { + GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Texture %p] No output frame in %d ms, considering stream not available\n", txh, txh->compositor->timeout / 2)); + txh->stream->connect_failure = GF_TRUE; + gf_sc_texture_stop_no_unregister(txh); + } + txh->compositor->ms_until_next_frame = -1; + } + } /*TODO - check if this is needed */ - if (txh->flags & GF_SR_TEXTURE_PRIVATE_MEDIA) { + else if (txh->flags & GF_SR_TEXTURE_PRIVATE_MEDIA) { //txh->needs_refresh = 1; gf_sc_invalidate(txh->compositor, NULL); } @@ -249,23 +289,21 @@ void gf_sc_texture_update_frame(GF_TextureHandler *txh, Bool disable_resync) txh->compositor->frame_delay = ms_until_pres; /*if setup and same frame return*/ - if (txh->tx_io && (txh->stream_finished || (txh->last_frame_time==ts)) ) { + if (txh->tx_io && ((s32) txh->last_frame_time>=0) && (txh->stream_finished || (txh->last_frame_time==ts)) ) { gf_mo_release_data(txh->stream, 0xFFFFFFFF, 0); txh->needs_release = 0; if (!txh->stream_finished) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Visual Texture] Same frame fetched (TS %u)\n", ts)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Texture %p] Same frame fetched (TS %u)\n", txh, ts)); if (txh->compositor->ms_until_next_frame > ms_until_next) txh->compositor->ms_until_next_frame = ms_until_next; } return; } + GF_LOG(GF_LOG_INFO, GF_LOG_SYNC, ("[Texture %p] Updated new frame at clock time %d (%d ms) time %u ms\n", txh, gf_clock_time(txh->stream->odm->ck), gf_sys_clock(), ts)); txh->stream_finished = 0; txh->needs_release = 1; txh->last_frame_time = ts; txh->size = size; - if (txh->raw_memory && (!txh->frame || !txh->frame->GetGLTexture) ) { - gf_mo_get_raw_image_planes(txh->stream, (u8 **) &txh->data, (u8 **) &txh->pU, (u8 **) &txh->pV, &txh->stride, &txh->stride_chroma); - } if (gf_mo_is_muted(txh->stream)) return; @@ -294,14 +332,18 @@ void gf_sc_texture_update_frame(GF_TextureHandler *txh, Bool disable_resync) GF_EXPORT void gf_sc_texture_release_stream(GF_TextureHandler *txh) { + txh->needs_refresh = 0; if (txh->needs_release) { assert(txh->stream); - gf_mo_release_data(txh->stream, 0xFFFFFFFF, 0); + gf_mo_release_data(txh->stream, 0xFFFFFFFF, (txh->needs_release==2) ? 3 :0); + if (txh->needs_release==2) { + txh->last_frame_time = -1; + } txh->needs_release = 0; - txh->frame = NULL; + txh->frame_ifce = NULL; } - txh->needs_refresh = 0; + if (txh->stream) txh->stream->config_changed = GF_FALSE; } @@ -353,8 +395,10 @@ GF_TextureHandler *gf_sc_texture_get_handler(GF_Node *n) return compositor_svg_get_image_texture(n); #endif +#ifndef GPAC_DISABLE_VRML case TAG_ProtoNode: return gf_sc_hardcoded_proto_get_texture_handler(n); +#endif default: return NULL; } diff --git a/src/compositor/texturing.h b/src/compositor/texturing.h index 8da6775..54ab070 100644 --- a/src/compositor/texturing.h +++ b/src/compositor/texturing.h @@ -42,20 +42,18 @@ void gf_sc_texture_reset(GF_TextureHandler *hdl); /*push data to hardware if needed, creating the hardware handle(s)*/ Bool gf_sc_texture_push_image(GF_TextureHandler *txh, Bool generate_mipmaps, Bool for2d); -/*refreshes hardware data for given rect (eg glTexSubImage)*/ -void gf_sc_texture_refresh_area(GF_TextureHandler *, GF_IRect *rc, void *mem); /*gets texture transform matrix - returns 1 if not identity @tx_transform: texture transform node from appearance*/ Bool gf_sc_texture_get_transform(GF_TextureHandler *txh, GF_Node *tx_transform, GF_Matrix *mx, Bool for_picking); /*gets the associated raster2D stencil handler*/ -GF_STENCIL gf_sc_texture_get_stencil(GF_TextureHandler *hdl); +GF_EVGStencil * gf_sc_texture_get_stencil(GF_TextureHandler *hdl); /*sets the associated raster2D stencil handler (used by gradients)*/ -void gf_sc_texture_set_stencil(GF_TextureHandler *hdl, GF_STENCIL stencil); +void gf_sc_texture_set_stencil(GF_TextureHandler *hdl, GF_EVGStencil * stencil); Bool gf_sc_texture_is_transparent(GF_TextureHandler *txh); -void gf_sc_texture_check_pause_on_first_load(GF_TextureHandler *txh); +void gf_sc_texture_check_pause_on_first_load(GF_TextureHandler *txh, Bool do_freeze); /* configures conversion parameters and allocates the conv data buffer*/ GF_Err gf_sc_texture_configure_conversion(GF_TextureHandler *txh); @@ -79,6 +77,9 @@ Bool gf_sc_texture_needs_reload(GF_TextureHandler *hdl); #ifndef GPAC_USE_TINYGL /*copy current GL window to the texture - the viewport used is the texture one (0,0,W,H) */ void gf_sc_copy_to_texture(GF_TextureHandler *txh); + +GF_Err gf_sc_texture_setup_fbo(GF_TextureHandler *txh); +void gf_sc_texture_enable_fbo(GF_TextureHandler *txh, Bool enable); #endif Bool gf_sc_texture_convert(GF_TextureHandler *txh); diff --git a/src/compositor/texturing_gl.c b/src/compositor/texturing_gl.c index fde0da5..5d088c6 100644 --- a/src/compositor/texturing_gl.c +++ b/src/compositor/texturing_gl.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / Scene Compositor sub-project @@ -29,20 +29,6 @@ #include "nodes_stacks.h" #include "gl_inc.h" -#ifdef GPAC_USE_TINYGL -# define GLTEXENV glTexEnvi -# define GLTEXPARAM glTexParameteri -# define TexEnvType u32 -#elif defined (GPAC_USE_GLES1X) -# define GLTEXENV glTexEnvx -# define GLTEXPARAM glTexParameterx -# define TexEnvType Fixed -#else -# define GLTEXENV glTexEnvf -# define GLTEXPARAM glTexParameteri -# define TexEnvType Float -#endif - /*tx flags*/ @@ -55,14 +41,17 @@ enum /*OpenGL texturing flags*/ - /*these 4 are exclusives*/ - TX_MUST_SCALE = (1<<10), - TX_IS_POW2 = (1<<11), - TX_IS_RECT = (1<<12), - TX_EMULE_POW2 = (1<<13), - TX_EMULE_FIRST_LOAD = (1<<14), - - TX_IS_FLIPPED = (1<<15), + /*texture is rect*/ + TX_IS_RECT = (1<<10), + /*texture emulates power of two through larger buffer and upscaling*/ + TX_EMULE_POW2 = (1<<11), + /*texture is flipped*/ + TX_IS_FLIPPED = (1<<12), + /*texture must be converted*/ + TX_CONV_8BITS = (1<<13), + + TX_FIRST_UPLOAD_FREEZE = (1<<14), + TX_FIRST_UPLOAD_FREEZE_DONE = (1<<15), }; @@ -71,36 +60,41 @@ struct __texture_wrapper u32 flags; /*2D texturing*/ - GF_STENCIL tx_raster; - //0: not paused, 1: paused, 2: initial pause has been done - u32 init_pause_status; - Bool conv_to_8bit; - char *conv_data; + GF_EVGStencil * tx_raster; + u8 *conv_data; GF_Matrix texcoordmatrix; - /*3D texturing*/ + //format of converted texture for 10->8 bit or non RGB->RGB if no shader support + u32 conv_format; + //size info of converted data + u32 conv_w, conv_h, conv_stride; + + /*3D OpenGL texturing*/ #ifndef GPAC_DISABLE_3D /*opengl texture id*/ - u32 id; u32 blend_mode; - u32 rescale_width, rescale_height; - char *scale_data; + //scaling factor when emulating pow2 Fixed conv_wscale, conv_hscale; - u32 conv_format, conv_w, conv_h; - Bool use_external_textures; - - /*gl textures vars (gl_type: 2D texture or rectangle (NV ext) )*/ - u32 nb_comp, gl_format, gl_type, gl_dtype; - Bool yuv_shader; - u32 v_id, u_id; - u32 pbo_id, u_pbo_id, v_pbo_id; - Bool pbo_pushed; + //texture wrapper object + GF_GLTextureWrapper tx; + + /* gl_format: GL_RGB, GL_RGBA, GL_LUMINANCE or GL_LUMINANCE_ALPHA*/ + u32 gl_format; + /* gl_type: 2D texture or rectangle*/ + u32 gl_type; + + /*FBO framebuffer ID*/ + u32 fbo_id; + /*FBO texture attachment ID*/ + u32 fbo_txid; + /*FBO depth buffer attacghment ID*/ + u32 depth_id; #endif + #ifdef GF_SR_USE_DEPTH char *depth_data; #endif - }; GF_Err gf_sc_texture_allocate(GF_TextureHandler *txh) @@ -113,30 +107,31 @@ GF_Err gf_sc_texture_allocate(GF_TextureHandler *txh) GF_Err gf_sc_texture_configure_conversion(GF_TextureHandler *txh) { - if (txh->compositor->output_as_8bit) { - - if (txh->pixelformat == GF_PIXEL_YV12_10) { - txh->stride /= 2; - txh->tx_io->conv_to_8bit = GF_TRUE; - txh->pixelformat = GF_PIXEL_YV12; - if(txh->raw_memory) - txh->tx_io->conv_data = (char*)gf_realloc(txh->tx_io->conv_data, 3 * sizeof(char)* txh->stride * txh->height / 2); + txh->flags &= ~TX_CONV_8BITS; + if (txh->compositor->out8b) { + u32 osize; + if (txh->pixelformat == GF_PIXEL_YUV_10) { + txh->tx_io->conv_format = GF_PIXEL_YUV; + } + else if (txh->pixelformat == GF_PIXEL_NV12_10) { + txh->tx_io->conv_format = GF_PIXEL_NV12; + } + else if (txh->pixelformat == GF_PIXEL_NV21_10) { + txh->tx_io->conv_format = GF_PIXEL_NV21; } else if (txh->pixelformat == GF_PIXEL_YUV422_10) { - txh->stride /= 2; - txh->tx_io->conv_to_8bit = GF_TRUE; - txh->pixelformat = GF_PIXEL_YUV422; - - if (txh->raw_memory) - txh->tx_io->conv_data = (char*)gf_realloc(txh->tx_io->conv_data, 2 * sizeof(char)* txh->stride * txh->height); + txh->tx_io->conv_format = GF_PIXEL_YUV422; } else if (txh->pixelformat == GF_PIXEL_YUV444_10) { - txh->stride /= 2; - txh->tx_io->conv_to_8bit = GF_TRUE; - txh->pixelformat = GF_PIXEL_YUV444; - if (txh->raw_memory) - txh->tx_io->conv_data = (char*)gf_realloc(txh->tx_io->conv_data, 3 * sizeof(char)* txh->stride * txh->height); + txh->tx_io->conv_format = GF_PIXEL_YUV444; + } else { + return GF_OK; } + osize = 0; + txh->tx_io->conv_stride = 0; + gf_pixel_get_size_info(txh->tx_io->conv_format, txh->width, txh->height, &osize, &txh->tx_io->conv_stride, NULL, NULL, NULL); + txh->tx_io->conv_data = (char*)gf_realloc(txh->tx_io->conv_data, osize); + txh->flags |= TX_CONV_8BITS; } return GF_OK; } @@ -147,16 +142,11 @@ static void release_txio(struct __texture_wrapper *tx_io) { #ifndef GPAC_DISABLE_3D - if (!tx_io->use_external_textures) { - if (tx_io->id) glDeleteTextures(1, &tx_io->id); - if (tx_io->u_id) glDeleteTextures(1, &tx_io->u_id); - if (tx_io->v_id) glDeleteTextures(1, &tx_io->v_id); + if (tx_io->fbo_id) { + compositor_3d_delete_fbo(&tx_io->fbo_id, &tx_io->fbo_txid, &tx_io->depth_id, GF_FALSE); + } else { + gf_gl_txw_reset(&tx_io->tx); } - if (tx_io->pbo_id) glDeleteBuffers(1, &tx_io->pbo_id); - if (tx_io->u_pbo_id) glDeleteBuffers(1, &tx_io->u_pbo_id); - if (tx_io->v_pbo_id) glDeleteBuffers(1, &tx_io->v_pbo_id); - - if (tx_io->scale_data) gf_free(tx_io->scale_data); #endif if (tx_io->conv_data) gf_free(tx_io->conv_data); @@ -179,7 +169,7 @@ void gf_sc_texture_release(GF_TextureHandler *txh) gf_sc_lock(txh->compositor, 1); if (txh->tx_io->tx_raster) { - txh->compositor->rasterizer->stencil_delete(txh->tx_io->tx_raster); + gf_evg_stencil_delete(txh->tx_io->tx_raster); txh->tx_io->tx_raster = NULL; } @@ -208,123 +198,75 @@ void gf_sc_texture_cleanup_hw(GF_Compositor *compositor) GF_Err gf_sc_texture_set_data(GF_TextureHandler *txh) { +#ifndef GPAC_DISABLE_3D + u8 *data=NULL; + GF_FilterFrameInterface *fifce = txh->frame_ifce; +#endif txh->tx_io->flags |= TX_NEEDS_RASTER_LOAD | TX_NEEDS_HW_LOAD; - if (txh->tx_io->conv_to_8bit) { - GF_VideoSurface dst; - u8 *p_y; - u32 src_stride = txh->stride * 2; +#ifndef GPAC_DISABLE_3D + data = txh->data; + //10->8 bit conversion + if (txh->flags & TX_CONV_8BITS) { + GF_VideoSurface dst, src; + GF_Err e; memset(&dst, 0, sizeof(GF_VideoSurface)); + memset(&src, 0, sizeof(GF_VideoSurface)); dst.width = txh->width; dst.height = txh->height; - dst.pitch_y = txh->stride; - dst.video_buffer = txh->raw_memory ? txh->tx_io->conv_data : txh->data; - p_y = (u8 *)txh->data; - - if (txh->pixelformat == GF_PIXEL_YV12) { + dst.pitch_y = txh->tx_io->conv_stride; + dst.video_buffer = txh->tx_io->conv_data; + dst.pixel_format = txh->tx_io->conv_format; - gf_color_write_yv12_10_to_yuv(&dst, (u8 *)p_y, (u8 *)txh->pU, (u8 *)txh->pV, src_stride, txh->width, txh->height, NULL, GF_FALSE); + src.width = txh->width; + src.height = txh->height; + src.pitch_y = txh->stride; + src.video_buffer = txh->data; + src.pixel_format = txh->pixelformat; - if (txh->raw_memory) { - txh->data = dst.video_buffer; - txh->pU = (u8*) dst.video_buffer + dst.pitch_y * txh->height; - txh->pV = (u8*) dst.video_buffer + 5 * dst.pitch_y * txh->height / 4; - } + if (txh->frame_ifce) { + u32 st_y, st_u, st_v; + txh->frame_ifce->get_plane(txh->frame_ifce, 0, (const u8 **) &src.video_buffer, &st_y); + txh->frame_ifce->get_plane(txh->frame_ifce, 1, (const u8 **) &src.u_ptr, &st_u); + txh->frame_ifce->get_plane(txh->frame_ifce, 2, (const u8 **) &src.v_ptr, &st_v); + if (src.video_buffer) src.pitch_y = st_y; } - else if (txh->pixelformat == GF_PIXEL_YUV422) { - gf_color_write_yuv422_10_to_yuv422(&dst, (u8 *)p_y, (u8 *)txh->pU, (u8 *)txh->pV, src_stride, txh->width, txh->height, NULL, GF_FALSE); - - if (txh->raw_memory) { - txh->data = dst.video_buffer; - txh->pU = (u8*) dst.video_buffer + dst.pitch_y * txh->height; - txh->pV = (u8*) dst.video_buffer + 3 * dst.pitch_y * txh->height / 2; - } - - } - else if (txh->pixelformat == GF_PIXEL_YUV444) { - - gf_color_write_yuv444_10_to_yuv444(&dst, (u8 *)p_y, (u8 *)txh->pU, (u8 *)txh->pV, src_stride, txh->width, txh->height, NULL, GF_FALSE); - - if (txh->raw_memory) { - txh->data = dst.video_buffer; - txh->pU = dst.video_buffer + dst.pitch_y * txh->height; - txh->pV = dst.video_buffer + 2 * dst.pitch_y * txh->height; - } + e = gf_stretch_bits(&dst, &src, NULL, NULL, 0xFF, GF_FALSE, NULL, NULL); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[V3D] Failed to convert 10-bits to 8-bit texture: %s\n", gf_error_to_string(e) )); + return e; } + fifce = NULL; + data = dst.video_buffer; } +#endif + #if !defined(GPAC_DISABLE_3D) && !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_GLES2) //PBO mode: start pushing the texture - if (txh->data && txh->tx_io->pbo_id) { - u8 *ptr; - u32 size = txh->stride*txh->height; - - glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, txh->tx_io->pbo_id); - ptr =(u8 *)glMapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY_ARB); - if (ptr) memcpy(ptr, txh->data, size); - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); - - if (txh->tx_io->u_pbo_id) { - u8 *pU = txh->pU; - u8 *pV = txh->pV; - if (!pU) pU = (u8 *) txh->data + size; - if (!pV) pV = (u8 *) txh->data + 5*size/4; - - glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, txh->tx_io->u_pbo_id); - ptr =(u8 *)glMapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY_ARB); - if (ptr) memcpy(ptr, pU, size/4); - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); - - glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, txh->tx_io->v_pbo_id); - ptr =(u8 *)glMapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY_ARB); - if (ptr) memcpy(ptr, pV, size/4); - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); - } - - txh->tx_io->pbo_pushed = 1; + if (data && !fifce && (txh->tx_io->tx.pbo_state!=GF_GL_PBO_NONE)) { + txh->tx_io->tx.pbo_state = GF_GL_PBO_PUSH; + gf_gl_txw_upload(&txh->tx_io->tx, data, fifce); //we just pushed our texture to the GPU, release - if (txh->raw_memory) { + if (txh->frame_ifce) { gf_sc_texture_release_stream(txh); } } - return GF_OK; - - //We do not have PBOs in ES2.0 -#elif !defined(GPAC_DISABLE_3D) && defined(GPAC_USE_GLES2) - //PBO mode: start pushing the texture - if (txh->tx_io->pbo_id) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[V3D:GLSL] PBOs are not implemented in GL ES 2.0\n")); - } - return GF_NOT_SUPPORTED; -#else - return GF_NOT_SUPPORTED; #endif + return GF_OK; } void gf_sc_texture_reset(GF_TextureHandler *txh) { #ifndef GPAC_DISABLE_3D - if (txh->tx_io->id) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Texturing] Releasing OpenGL texture %d\n", txh->tx_io->id)); - - if (txh->tx_io->use_external_textures) { - glDeleteTextures(1, &txh->tx_io->id); - if (txh->tx_io->u_id) { - glDeleteTextures(1, &txh->tx_io->u_id); - glDeleteTextures(1, &txh->tx_io->v_id); - } - } - txh->tx_io->id = txh->tx_io->u_id = txh->tx_io->v_id = 0; - - if (txh->tx_io->pbo_id) glDeleteBuffers(1, &txh->tx_io->pbo_id); - if (txh->tx_io->u_pbo_id) glDeleteBuffers(1, &txh->tx_io->u_pbo_id); - if (txh->tx_io->v_pbo_id) glDeleteBuffers(1, &txh->tx_io->v_pbo_id); - txh->tx_io->pbo_id = txh->tx_io->u_pbo_id = txh->tx_io->v_pbo_id = 0; + if (txh->tx_io->tx.nb_textures) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Texturing] Releasing OpenGL texture\n")); + gf_gl_txw_reset(&txh->tx_io->tx); + } + if (txh->tx_io->fbo_id) { + compositor_3d_delete_fbo(&txh->tx_io->fbo_id, &txh->tx_io->fbo_txid, &txh->tx_io->depth_id, GF_FALSE); } txh->tx_io->flags |= TX_NEEDS_HW_LOAD; #endif @@ -349,10 +291,10 @@ void gf_sc_texture_set_blend_mode(GF_TextureHandler *txh, u32 mode) if (txh->tx_io) txh->tx_io->blend_mode = mode; } -void tx_bind_with_mode(GF_TextureHandler *txh, Bool transparent, u32 blend_mode, Bool no_bind) +void tx_bind_with_mode(GF_TextureHandler *txh, Bool transparent, u32 blend_mode, Bool no_bind, u32 glsl_prog_id) { - if (!txh->tx_io || !txh->tx_io->id || !txh->tx_io->gl_type) return; - + if (!txh->tx_io->gl_type) + return; #ifndef GPAC_USE_GLES2 if (!no_bind) glEnable(txh->tx_io->gl_type); @@ -388,39 +330,35 @@ void tx_bind_with_mode(GF_TextureHandler *txh, Bool transparent, u32 blend_mode, //blend_mode for ES2.0 can be implemented inside the fragment shader if desired #endif - if (!no_bind) - glBindTexture(txh->tx_io->gl_type, txh->tx_io->id); + if (!no_bind) { + gf_gl_txw_bind(&txh->tx_io->tx, "maintx", glsl_prog_id, 0); + } } void tx_bind(GF_TextureHandler *txh) { if (txh->tx_io ) - tx_bind_with_mode(txh, txh->transparent, txh->tx_io->blend_mode, 0); + tx_bind_with_mode(txh, txh->transparent, txh->tx_io->blend_mode, 0, 0); } void gf_sc_texture_disable(GF_TextureHandler *txh) { - if (txh && txh->tx_io) { + if (!txh || !txh->tx_io) + return; #ifndef GPAC_USE_GLES1X - if (txh->tx_io->yuv_shader) { -// glUseProgram(0); - txh->compositor->visual->current_texture_glsl_program = 0; -// glActiveTexture(GL_TEXTURE0); - glBindTexture(txh->tx_io->gl_type, 0); - - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[GL Texture] Texture drawn (CTS %u)\n", txh->last_frame_time)); - - } + glBindTexture(txh->tx_io->gl_type, 0); #endif + #if !defined(GPAC_USE_GLES2) - glDisable(txh->tx_io->gl_type); + glDisable(txh->tx_io->gl_type); #endif - if (txh->transparent) glDisable(GL_BLEND); - gf_sc_texture_check_pause_on_first_load(txh); - txh->compositor->visual->glsl_flags &= ~(GF_GL_HAS_TEXTURE | GF_GL_IS_YUV | GF_GL_IS_ExternalOES); - } + if (txh->transparent) + glDisable(GL_BLEND); + + gf_sc_texture_check_pause_on_first_load(txh, GF_FALSE); + txh->compositor->visual->bound_tx_pix_fmt = 0; } @@ -432,7 +370,7 @@ Bool tx_can_use_rect_ext(GF_Compositor *compositor, GF_TextureHandler *txh) // compositor->gl_caps.yuv_texture = 0; if (!compositor->gl_caps.rect_texture) return 0; - if (!compositor->disable_rect_ext) return 1; + if (compositor->rext) return 1; /*this happens ONLY with text texturing*/ if (!txh->owner) return 0; @@ -459,22 +397,21 @@ Bool tx_can_use_rect_ext(GF_Compositor *compositor, GF_TextureHandler *txh) static Bool tx_setup_format(GF_TextureHandler *txh) { - GLint tx_id[3]; - u32 i, nb_tx = 1; - Bool is_pow2, use_rect, flip, use_yuv_shaders; + u32 npow_w, npow_h; + Bool is_pow2, use_rect, flip; GF_Compositor *compositor = (GF_Compositor *)txh->compositor; /*first setup, this will force recompute bounds in case used with bitmap - we could refine and only invalidate for bitmaps only*/ - if (txh->owner && (!txh->tx_io->rescale_width || !txh->tx_io->rescale_height)) + if (txh->owner) gf_node_dirty_set(txh->owner, 0, 1); - txh->tx_io->rescale_width = gf_get_next_pow2(txh->width); - txh->tx_io->rescale_height = gf_get_next_pow2(txh->height); + npow_w = gf_get_next_pow2(txh->width); + npow_h = gf_get_next_pow2(txh->height); flip = (txh->tx_io->flags & TX_IS_FLIPPED); - is_pow2 = ((txh->tx_io->rescale_width==txh->width) && (txh->tx_io->rescale_height==txh->height)) ? 1 : 0; - txh->tx_io->flags = TX_IS_POW2; + is_pow2 = ((npow_w==txh->width) && (npow_h==txh->height)) ? 1 : 0; + txh->tx_io->flags = 0; txh->tx_io->gl_type = GL_TEXTURE_2D; /* all textures can be used in GLES2 */ @@ -483,7 +420,7 @@ static Bool tx_setup_format(GF_TextureHandler *txh) #else #if !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) - if (compositor->shader_only_mode) { + if (!compositor->shader_mode_disabled || compositor->gl_caps.npot || compositor->gl_caps.has_shaders) { use_rect = GF_TRUE; } else #endif @@ -496,235 +433,96 @@ static Bool tx_setup_format(GF_TextureHandler *txh) #endif txh->tx_io->flags = TX_IS_RECT; } - if (!use_rect && !compositor->gl_caps.npot_texture && !is_pow2) txh->tx_io->flags = TX_MUST_SCALE; } #endif - use_yuv_shaders = 0; - txh->tx_io->nb_comp = txh->tx_io->gl_format = 0; - txh->tx_io->gl_dtype = GL_UNSIGNED_BYTE; + txh->tx_io->gl_format = 0; switch (txh->pixelformat) { case GF_PIXEL_GREYSCALE: txh->tx_io->gl_format = GL_LUMINANCE; - txh->tx_io->nb_comp = 1; - txh->tx_io->gl_type = GL_TEXTURE_2D; - if (!is_pow2) txh->tx_io->flags = TX_MUST_SCALE; break; case GF_PIXEL_ALPHAGREY: + case GF_PIXEL_GREYALPHA: txh->tx_io->gl_format = GL_LUMINANCE_ALPHA; - txh->tx_io->nb_comp = 2; - txh->tx_io->gl_type = GL_TEXTURE_2D; - if (!is_pow2) txh->tx_io->flags = TX_MUST_SCALE; break; - case GF_PIXEL_RGB_24: + case GF_PIXEL_RGB: txh->tx_io->gl_format = GL_RGB; - txh->tx_io->nb_comp = 3; break; - case GF_PIXEL_BGR_24: + case GF_PIXEL_BGR: txh->tx_io->gl_format = GL_RGB; - txh->tx_io->nb_comp = 3; break; - case GF_PIXEL_BGR_32: + case GF_PIXEL_BGRX: txh->tx_io->gl_format = GL_RGBA; - txh->tx_io->nb_comp = 4; break; - case GF_PIXEL_RGB_32: + case GF_PIXEL_RGBX: case GF_PIXEL_RGBA: txh->tx_io->gl_format = GL_RGBA; - txh->tx_io->nb_comp = 4; break; #ifndef GPAC_USE_GLES1X case GF_PIXEL_ARGB: if (!compositor->gl_caps.bgra_texture) return 0; txh->tx_io->gl_format = GL_BGRA_EXT; - txh->tx_io->nb_comp = 4; break; #endif - case GF_PIXEL_YV12: - case GF_PIXEL_YV12_10: + case GF_PIXEL_YUV: + case GF_PIXEL_YUV_10: case GF_PIXEL_YUV422: case GF_PIXEL_YUV422_10: case GF_PIXEL_YUV444: case GF_PIXEL_YUV444_10: case GF_PIXEL_NV21: + case GF_PIXEL_NV21_10: case GF_PIXEL_NV12: -#ifndef GPAC_USE_GLES1X - if (compositor->gl_caps.has_shaders && (is_pow2 || compositor->visual->compositor->shader_only_mode) ) { - use_yuv_shaders = 1; - break; - } -#ifndef GPAC_USE_GLES2 - else if (!compositor->disable_yuvgl && compositor->gl_caps.yuv_texture && !(txh->tx_io->flags & TX_MUST_SCALE) ) { - txh->tx_io->gl_format = compositor->gl_caps.yuv_texture; - txh->tx_io->nb_comp = 3; - txh->tx_io->gl_dtype = UNSIGNED_SHORT_8_8_MESA; + case GF_PIXEL_NV12_10: + case GF_PIXEL_YUYV: + case GF_PIXEL_YVYU: + case GF_PIXEL_VYUY: + case GF_PIXEL_UYVY: + case GF_PIXEL_GL_EXTERNAL: +#if !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) + if (!compositor->visual->compositor->shader_mode_disabled) { break; } #endif -#endif + if (!use_rect && compositor->epow2) txh->tx_io->flags = TX_EMULE_POW2; + txh->tx_io->gl_format = GL_RGB; + break; //fallthrough - case GF_PIXEL_YUY2: + case GF_PIXEL_YUVA: + case GF_PIXEL_YUVA444: case GF_PIXEL_YUVD: #if !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) - if (compositor->gl_caps.has_shaders && (is_pow2 || compositor->visual->compositor->shader_only_mode) ) { - use_yuv_shaders = 1; - } else -#endif - { - if (!use_rect && compositor->emul_pow2) txh->tx_io->flags = TX_EMULE_POW2; - txh->tx_io->gl_format = GL_RGB; - txh->tx_io->nb_comp = 3; + if (!compositor->visual->compositor->shader_mode_disabled) { + break; } +#endif + if (!use_rect && compositor->epow2) txh->tx_io->flags = TX_EMULE_POW2; + txh->tx_io->gl_format = GL_RGBA; break; case GF_PIXEL_RGBD: - case GF_PIXEL_RGB_24_DEPTH: - if (!use_rect && compositor->emul_pow2) txh->tx_io->flags = TX_EMULE_POW2; + case GF_PIXEL_RGB_DEPTH: + if (!use_rect && compositor->epow2) txh->tx_io->flags = TX_EMULE_POW2; txh->tx_io->gl_format = GL_RGB; - txh->tx_io->nb_comp = 3; break; default: + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[V3D:GLSL] Unknown pixel format %s\n", gf_4cc_to_str(txh->pixelformat) )); return 0; } if (flip) txh->tx_io->flags |= TX_IS_FLIPPED; /*in case the ID has been lost, resetup*/ - if (!txh->tx_io->id) { - glGenTextures(1, &txh->tx_io->id); - txh->tx_io->flags |= TX_EMULE_FIRST_LOAD; - txh->tx_io->u_id = 0; - txh->tx_io->v_id = 0; - } - tx_id[0] = txh->tx_io->id; - -#ifndef GPAC_USE_GLES1X - if (use_yuv_shaders && !txh->tx_io->u_id) { - glGenTextures(1, &txh->tx_io->u_id); - glGenTextures(1, &txh->tx_io->v_id); - tx_id[1] = txh->tx_io->u_id; - tx_id[2] = txh->tx_io->v_id; - nb_tx = 3; - } -#endif - -#if !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_GLES2) - if (txh->compositor->gl_caps.pbo && txh->compositor->enable_pbo) { - u32 size = txh->stride*txh->height; - - if (!txh->tx_io->pbo_id && txh->tx_io->id) { - glGenBuffers(1, &txh->tx_io->pbo_id); - if (txh->tx_io->pbo_id) { - glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, txh->tx_io->pbo_id); - glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, size, NULL, GL_DYNAMIC_DRAW_ARB); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); - } - } - if (!txh->tx_io->u_pbo_id && txh->tx_io->u_id) { - glGenBuffers(1, &txh->tx_io->u_pbo_id); - if (txh->tx_io->u_pbo_id) { - glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, txh->tx_io->u_pbo_id); - glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, size/4, NULL, GL_DYNAMIC_DRAW_ARB); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); - } - } - if (!txh->tx_io->v_pbo_id && txh->tx_io->v_id) { - glGenBuffers(1, &txh->tx_io->v_pbo_id); - if (txh->tx_io->v_pbo_id) { - glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, txh->tx_io->v_pbo_id); - glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, size/4, NULL, GL_DYNAMIC_DRAW_ARB); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); - } + if (!txh->tx_io->tx.nb_textures) { + u32 pfmt = txh->pixelformat; + u32 stride = txh->stride; + txh->tx_io->tx.pbo_state = (txh->compositor->gl_caps.pbo && txh->compositor->pbo) ? GF_GL_PBO_BOTH : GF_GL_PBO_NONE; + if (txh->tx_io->conv_format) { + stride = txh->tx_io->conv_stride; + pfmt = txh->tx_io->conv_format; } - } -#endif - - if (use_yuv_shaders) { - //we use LUMINANCE because GL_RED is not defined on android ... - txh->tx_io->gl_format = GL_LUMINANCE; - txh->tx_io->nb_comp = 1; - txh->tx_io->yuv_shader = 1; - if (txh->pixelformat==GF_PIXEL_YV12_10 || txh->pixelformat==GF_PIXEL_YUV422_10 ||txh->pixelformat==GF_PIXEL_YUV444_10 ) { - - txh->tx_io->gl_dtype = GL_UNSIGNED_SHORT; - } - txh->compositor->visual->yuv_pixelformat_type = txh->pixelformat; - } - - /*note we don't free the data if existing, since this only happen when re-setting up after context loss (same size)*/ - if ((txh->tx_io->flags == TX_MUST_SCALE) & !txh->tx_io->scale_data) { - txh->tx_io->scale_data = (char*)gf_malloc(sizeof(char) * txh->tx_io->nb_comp*txh->tx_io->rescale_width*txh->tx_io->rescale_height); - memset(txh->tx_io->scale_data , 0, sizeof(char) * txh->tx_io->nb_comp*txh->tx_io->rescale_width*txh->tx_io->rescale_height); - } - - //setup all textures - for (i=0; itx_io->gl_type); -#endif - glBindTexture(txh->tx_io->gl_type, tx_id[i] ); - -#if defined(GPAC_USE_GLES1X) || defined(GPAC_USE_GLES2) - -#ifdef GPAC_USE_GLES2 - if (!is_pow2) { - GLTEXPARAM(txh->tx_io->gl_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - GLTEXPARAM(txh->tx_io->gl_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } else -#endif - { - GLTEXPARAM(txh->tx_io->gl_type, GL_TEXTURE_WRAP_S, (txh->flags & GF_SR_TEXTURE_REPEAT_S) ? GL_REPEAT : GL_CLAMP_TO_EDGE); - GLTEXPARAM(txh->tx_io->gl_type, GL_TEXTURE_WRAP_T, (txh->flags & GF_SR_TEXTURE_REPEAT_T) ? GL_REPEAT : GL_CLAMP_TO_EDGE); - } - - if (is_pow2 && txh->tx_io->gl_type == GL_TEXTURE_2D) { - GLTEXPARAM(txh->tx_io->gl_type, GL_TEXTURE_MAG_FILTER, txh->compositor->high_speed ? GL_NEAREST : GL_LINEAR); - GLTEXPARAM(txh->tx_io->gl_type, GL_TEXTURE_MIN_FILTER, txh->compositor->high_speed ? GL_NEAREST : GL_LINEAR); - } else { - GLTEXPARAM(txh->tx_io->gl_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - GLTEXPARAM(txh->tx_io->gl_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - } -#else - -#ifndef GPAC_USE_TINYGL - if (txh->tx_io->gl_type == GL_TEXTURE_2D) { - GLTEXPARAM(txh->tx_io->gl_type, GL_TEXTURE_WRAP_S, (txh->flags & GF_SR_TEXTURE_REPEAT_S) ? GL_REPEAT : GL_CLAMP_TO_EDGE); - GLTEXPARAM(txh->tx_io->gl_type, GL_TEXTURE_WRAP_T, (txh->flags & GF_SR_TEXTURE_REPEAT_T) ? GL_REPEAT : GL_CLAMP_TO_EDGE); - } else -#endif - //clamp to edge for NPOT textures - { - GLTEXPARAM(txh->tx_io->gl_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - GLTEXPARAM(txh->tx_io->gl_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } - - if (txh->tx_io->gl_type == GL_TEXTURE_2D) { - GLTEXPARAM(txh->tx_io->gl_type, GL_TEXTURE_MAG_FILTER, txh->compositor->high_speed ? GL_NEAREST : GL_LINEAR); - GLTEXPARAM(txh->tx_io->gl_type, GL_TEXTURE_MIN_FILTER, txh->compositor->high_speed ? GL_NEAREST : GL_LINEAR); - } else { - GLTEXPARAM(txh->tx_io->gl_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - GLTEXPARAM(txh->tx_io->gl_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - } -#endif - - if (txh->tx_io->yuv_shader && (txh->pixelformat==GF_PIXEL_YV12_10 || txh->pixelformat==GF_PIXEL_YUV422_10 ||txh->pixelformat==GF_PIXEL_YUV444_10)) { - //will never happen on GLES for now since we don't have GLES2 support yet ... - //FIXME - allow 10bit support in GLES2 -#if !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_GLES2) - glPixelStorei(GL_UNPACK_ALIGNMENT, 2); - //we use 10 bits but GL will normalise using 16 bits, so we need to multiply the nomralized result by 2^6 - glPixelTransferi(GL_RED_SCALE, 64); -#endif - } else { -#if !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_GLES2) - glPixelTransferi(GL_RED_SCALE, 1); -#endif - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - } -#if !defined(GPAC_USE_GLES2) - glDisable(txh->tx_io->gl_type); -#endif + gf_gl_txw_setup(&txh->tx_io->tx, pfmt, txh->width, txh->height, stride, 0, GF_TRUE, NULL); } return 1; } @@ -739,44 +537,6 @@ char *gf_sc_texture_get_data(GF_TextureHandler *txh, u32 *pix_format) return txh->data; } -void txh_unpack_yuv(GF_TextureHandler *txh) -{ - u32 i, j; - u8 *dst, *y, *u, *v, *p_y, *p_u, *p_v; - if (!txh->tx_io->conv_data) { - txh->tx_io->conv_data = (char*)gf_malloc(sizeof(char) * 2 * txh->width * txh->height); - } - p_y = (u8 *) txh->data; - p_u = (u8 *) txh->data + txh->stride*txh->height; - p_v = (u8 *) txh->data + 5*txh->stride*txh->height/4; - - /*convert to UYVY and flip texture*/ - for (i=0; iheight; i++) { - u32 idx = txh->height-i-1; - y = p_y + idx*txh->stride; - u = p_u + (idx/2) * txh->stride/2; - v = p_v + (idx/2) * txh->stride/2; - dst = (u8 *) txh->tx_io->conv_data + 2*i*txh->stride; - - for (j=0; jwidth/2; j++) { - *dst = *u; - dst++; - u++; - *dst = *y; - dst++; - y++; - *dst = *v; - dst++; - v++; - *dst = *y; - dst++; - y++; - } - } - /*remove GL texture flip*/ - txh->tx_io->flags |= TX_IS_FLIPPED; -} - /*note about conversion: we consider that a texture without a stream attached is generated by the compositor hence is never flipped. Otherwise all textures attached to stream are flipped in order to match uv coords*/ Bool gf_sc_texture_convert(GF_TextureHandler *txh) @@ -787,26 +547,34 @@ Bool gf_sc_texture_convert(GF_TextureHandler *txh) if (!txh->needs_refresh) return 1; +#if !defined(GPAC_DISABLE_3D) && !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) + if (!txh->compositor->shader_mode_disabled) { + txh->tx_io->flags |= TX_NEEDS_HW_LOAD; + return 1; + } +#endif + switch (txh->pixelformat) { case GF_PIXEL_ARGB: if (!compositor->gl_caps.bgra_texture) return 0; goto common; case GF_PIXEL_RGBD: - if ((txh->compositor->depth_gl_type==GF_SC_DEPTH_GL_NONE) || (txh->compositor->depth_gl_type==GF_SC_DEPTH_GL_VBO)) { + if (txh->compositor->depth_gl_type==GF_SC_DEPTH_GL_NONE) { bpp = 4; break; } - case GF_PIXEL_BGR_24: + case GF_PIXEL_BGR: bpp = 3; break; - case GF_PIXEL_BGR_32: + case GF_PIXEL_BGRX: bpp = 4; break; case GF_PIXEL_GREYSCALE: case GF_PIXEL_ALPHAGREY: - case GF_PIXEL_RGB_24: - case GF_PIXEL_RGB_32: + case GF_PIXEL_GREYALPHA: + case GF_PIXEL_RGB: + case GF_PIXEL_RGBX: case GF_PIXEL_RGBA: common: txh->tx_io->conv_format = txh->pixelformat; @@ -830,29 +598,25 @@ common: txh->tx_io->flags |= TX_IS_FLIPPED; return 1; - case GF_PIXEL_YV12: - case GF_PIXEL_YV12_10: + case GF_PIXEL_YUV: + case GF_PIXEL_YUV_10: case GF_PIXEL_YUV422: case GF_PIXEL_YUV422_10: case GF_PIXEL_YUV444: case GF_PIXEL_YUV444_10: - case GF_PIXEL_NV21: case GF_PIXEL_NV12: - case GF_PIXEL_I420: - if (txh->tx_io->gl_format == compositor->gl_caps.yuv_texture) { - txh->tx_io->conv_format = GF_PIXEL_YVYU; - txh->tx_io->flags |= TX_NEEDS_HW_LOAD; - txh_unpack_yuv(txh); - return 1; - } - if (txh->tx_io->yuv_shader) { - txh->tx_io->flags |= TX_NEEDS_HW_LOAD; - return 1; - } - case GF_PIXEL_YUY2: + case GF_PIXEL_NV12_10: + case GF_PIXEL_NV21: + case GF_PIXEL_NV21_10: + case GF_PIXEL_YUYV: + case GF_PIXEL_YVYU: + case GF_PIXEL_UYVY: + case GF_PIXEL_VYUY: bpp = 3; break; case GF_PIXEL_YUVD: + case GF_PIXEL_YUVA: + case GF_PIXEL_YUVA444: bpp = 4; break; default: @@ -874,6 +638,8 @@ common: } } out_stride = bpp * ((txh->tx_io->flags & TX_EMULE_POW2) ? txh->tx_io->conv_w : txh->width); + txh->tx_io->conv_stride = out_stride; + memset(&src, 0, sizeof(GF_VideoSurface)); memset(&dst, 0, sizeof(GF_VideoSurface)); @@ -891,29 +657,29 @@ common: dst.video_buffer = txh->tx_io->conv_data; switch (txh->pixelformat) { - case GF_PIXEL_YUY2: - case GF_PIXEL_YV12: - case GF_PIXEL_YV12_10: + case GF_PIXEL_YUYV: + case GF_PIXEL_YUV: + case GF_PIXEL_YUV_10: case GF_PIXEL_YUV422: case GF_PIXEL_YUV422_10: case GF_PIXEL_YUV444: case GF_PIXEL_YUV444_10: case GF_PIXEL_NV21: case GF_PIXEL_NV12: - case GF_PIXEL_I420: - case GF_PIXEL_BGR_24: - case GF_PIXEL_BGR_32: - txh->tx_io->conv_format = dst.pixel_format = GF_PIXEL_RGB_24; + case GF_PIXEL_NV12_10: + case GF_PIXEL_BGR: + case GF_PIXEL_BGRX: + txh->tx_io->conv_format = dst.pixel_format = GF_PIXEL_RGB; /*stretch and flip*/ gf_stretch_bits(&dst, &src, NULL, NULL, 0xFF, !txh->is_flipped, NULL, NULL); if ( !txh->is_flipped) txh->flags |= GF_SR_TEXTURE_NO_GL_FLIP; break; case GF_PIXEL_YUVD: - if ((txh->compositor->depth_gl_type==GF_SC_DEPTH_GL_NONE) || (txh->compositor->depth_gl_type==GF_SC_DEPTH_GL_VBO)) { - src.pixel_format = GF_PIXEL_YV12; - txh->tx_io->conv_format = GF_PIXEL_RGB_24_DEPTH; - dst.pixel_format = GF_PIXEL_RGB_24; + if (txh->compositor->depth_gl_type==GF_SC_DEPTH_GL_NONE) { + src.pixel_format = GF_PIXEL_YUV; + txh->tx_io->conv_format = GF_PIXEL_RGB_DEPTH; + dst.pixel_format = GF_PIXEL_RGB; dst.pitch_y = 3*txh->width; /*stretch YUV->RGB*/ gf_stretch_bits(&dst, &src, NULL, NULL, 0xFF, 1, NULL, NULL); @@ -928,21 +694,21 @@ common: txh->flags |= GF_SR_TEXTURE_NO_GL_FLIP; break; case GF_PIXEL_RGBD: - if ((txh->compositor->depth_gl_type==GF_SC_DEPTH_GL_NONE) || (txh->compositor->depth_gl_type==GF_SC_DEPTH_GL_VBO)) { + if (txh->compositor->depth_gl_type==GF_SC_DEPTH_GL_NONE) { dst.pitch_y = 3*txh->width; - txh->tx_io->conv_format = GF_PIXEL_RGB_24_DEPTH; - dst.pixel_format = GF_PIXEL_RGB_24; + txh->tx_io->conv_format = GF_PIXEL_RGB_DEPTH; + dst.pixel_format = GF_PIXEL_RGB; for (j=0; jheight; j++) { - u8 *src = (u8 *) txh->data + (txh->height-j-1)*txh->stride; + u8 *src_p = (u8 *) txh->data + (txh->height-j-1)*txh->stride; u8 *dst_p = (u8 *) txh->tx_io->conv_data + j*3*txh->width; u8 *dst_d = (u8 *) txh->tx_io->conv_data + txh->height*3*txh->width + j*txh->width; for (i=0; iwidth; i++) { - *dst_p++ = src[i*4]; - *dst_p++ = src[i*4 + 1]; - *dst_p++ = src[i*4 + 2]; - *dst_d++ = src[i*4 + 3]; + *dst_p++ = src_p[i*4]; + *dst_p++ = src_p[i*4 + 1]; + *dst_p++ = src_p[i*4 + 2]; + *dst_d++ = src_p[i*4 + 3]; } } txh->flags |= GF_SR_TEXTURE_NO_GL_FLIP; @@ -955,102 +721,32 @@ common: #endif - -#ifndef GPAC_DISABLE_3D -static void do_tex_image_2d(GF_TextureHandler *txh, GLint tx_mode, Bool first_load, u8 *data, u32 stride, u32 w, u32 h, u32 pbo_id) -{ - Bool needs_stride; - GL_CHECK_ERR - - if (txh->tx_io->gl_dtype==GL_UNSIGNED_SHORT) { - needs_stride = (stride != 2*w*txh->tx_io->nb_comp) ? GF_TRUE : GF_FALSE; - if (needs_stride) stride /= 2; - } else { - needs_stride = (stride!=w*txh->tx_io->nb_comp) ? GF_TRUE : GF_FALSE; - } - -#if !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_GLES2) - if (needs_stride) - glPixelStorei(GL_UNPACK_ROW_LENGTH, stride); -#else - if (needs_stride) { - if (txh->compositor->gl_caps.gles2_unpack) { - glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride); - } else { - GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[V3D:GLSL] Texture with stride - OpenGL ES2.0 extension \"EXT_unpack_subimage\" is required\n")); - } - } -#endif - -#if !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_GLES2) - if (txh->tx_io->pbo_pushed) { - glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo_id); - glTexImage2D(txh->tx_io->gl_type, 0, tx_mode, w, h, 0, txh->tx_io->gl_format, txh->tx_io->gl_dtype, NULL); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); - } - else -#elif defined(GPAC_USE_GLES2) - if (txh->tx_io->pbo_pushed) { - glTexImage2D(txh->tx_io->gl_type, 0, tx_mode, w, h, 0, txh->tx_io->gl_format, txh->tx_io->gl_dtype, NULL); - } else -#endif - if (first_load) { - glTexImage2D(txh->tx_io->gl_type, 0, tx_mode, w, h, 0, txh->tx_io->gl_format, txh->tx_io->gl_dtype, data); - } else { - glTexSubImage2D(txh->tx_io->gl_type, 0, 0, 0, w, h, txh->tx_io->gl_format, txh->tx_io->gl_dtype, data); - } - -#if !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_GLES2) - if (needs_stride) - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - return; -#else - - - if (!needs_stride) - return; - - //no GL_UNPACK_ROW_LENGTH on GLES, push line by line ... - if (first_load) { - glTexImage2D(txh->tx_io->gl_type, 0, tx_mode, w, h, 0, txh->tx_io->gl_format, txh->tx_io->gl_dtype, NULL); - } - { - u32 i; - for (i=0; itx_io->gl_type, 0, 0, 0, w, 1, txh->tx_io->gl_format, txh->tx_io->gl_dtype, ptr); - } - } -#endif -} - -#endif - Bool gf_sc_texture_push_image(GF_TextureHandler *txh, Bool generate_mipmaps, Bool for2d) { #ifndef GPAC_DISABLE_3D - u32 ck; char *data; - Bool first_load = 0; - GLint tx_mode; - u32 pixel_format, w, h, nb_views=1, nb_layers=1, nb_frames=1; + u32 pixel_format; + int nb_views = 1, nb_layers = 1; u32 push_time; if (txh->stream) { gf_mo_get_nb_views(txh->stream, &nb_views); gf_mo_get_nb_layers(txh->stream, &nb_layers); } - if (txh->raw_memory || nb_views == 1) nb_frames = 1; - else if (nb_layers) nb_frames = nb_layers; +// if (txh->frame_ifce || nb_views == 1) nb_frames = 1; +// else if (nb_layers) nb_frames = nb_layers; #endif - if (for2d) { Bool load_tx = 0; if (!txh->data) return 0; + if (!txh->tx_io) { + GF_SAFEALLOC(txh->tx_io, struct __texture_wrapper); + if (!txh->tx_io) return 0; + } if (!txh->tx_io->tx_raster) { - txh->tx_io->tx_raster = txh->compositor->rasterizer->stencil_new(txh->compositor->rasterizer, GF_STENCIL_TEXTURE); + txh->tx_io->tx_raster = gf_evg_stencil_new(GF_STENCIL_TEXTURE); if (!txh->tx_io->tx_raster) return 0; load_tx = 1; } @@ -1059,8 +755,39 @@ Bool gf_sc_texture_push_image(GF_TextureHandler *txh, Bool generate_mipmaps, Boo txh->tx_io->flags &= ~TX_NEEDS_RASTER_LOAD; } if (load_tx) { - if (txh->compositor->rasterizer->stencil_set_texture(txh->tx_io->tx_raster, txh->data, txh->width, txh->height, txh->stride, (GF_PixelFormat) txh->pixelformat, (GF_PixelFormat) txh->compositor->video_out->pixel_format, 0) != GF_OK) + const u8 *pData = txh->data; + const u8 *pU=NULL, *pV=NULL, *pA=NULL; + u32 stride = txh->stride; + u32 stride_uv=0; + u32 stride_alpha=0; + GF_Err e=GF_OK; + if (txh->frame_ifce) { + pData=NULL; + if (txh->frame_ifce->get_plane) { + e = txh->frame_ifce->get_plane(txh->frame_ifce, 0, &pData, &stride); + if (!e && txh->nb_planes>1) + e = txh->frame_ifce->get_plane(txh->frame_ifce, 1, &pU, &stride_uv); + if (!e && txh->nb_planes>2) + e = txh->frame_ifce->get_plane(txh->frame_ifce, 2, &pV, &stride_uv); + if (!e && txh->nb_planes>3) + e = txh->frame_ifce->get_plane(txh->frame_ifce, 3, &pA, &stride_alpha); + } + } + if (!pData) { + if (!txh->compositor->last_error) + txh->compositor->last_error = GF_NOT_SUPPORTED; return 0; + } + + if (!e) + e = gf_evg_stencil_set_texture_planes(txh->tx_io->tx_raster, txh->width, txh->height, (GF_PixelFormat) txh->pixelformat, pData, txh->stride, pU, pV, stride_uv, pA, stride_alpha); + + if (e != GF_OK) { + if (!txh->compositor->last_error) + txh->compositor->last_error = e; + return 0; + } + txh->compositor->last_error = GF_OK; } return 1; } @@ -1069,265 +796,84 @@ Bool gf_sc_texture_push_image(GF_TextureHandler *txh, Bool generate_mipmaps, Boo if (! (txh->tx_io->flags & TX_NEEDS_HW_LOAD) ) return 1; - /*in case the ID has been lost, resetup*/ - if (!txh->tx_io->id) { - glGenTextures(1, &txh->tx_io->id); - /*force setup of image*/ - txh->needs_refresh = 1; - tx_setup_format(txh); - txh->tx_io->flags |= TX_EMULE_FIRST_LOAD; - GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Texturing] Allocating OpenGL texture %d\n", txh->tx_io->id)); + if (txh->tx_io->fbo_id) { + return 1; } - if (!txh->tx_io->gl_type) return 0; + GL_CHECK_ERR() - /*if data not yet ready don't push the texture*/ + if (txh->data) { + //reconfig from GL textures to non-GL output + if (!txh->frame_ifce && !txh->tx_io->tx.internal_textures) { + gf_gl_txw_reset(&txh->tx_io->tx); + } + //reconfig from non-GL output to GL textures + else if (txh->tx_io->tx.internal_textures + && txh->frame_ifce + && txh->frame_ifce->get_gl_texture + ) { + gf_gl_txw_reset(&txh->tx_io->tx); + } + } if (txh->data) { /*convert image*/ gf_sc_texture_convert(txh); } + txh->tx_io->tx.frame_ifce = txh->frame_ifce; + + /*in case the ID has been lost, resetup*/ + if (!txh->tx_io->tx.nb_textures) { + /*force setup of image*/ + txh->needs_refresh = 1; + tx_setup_format(txh); + GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Texturing] Allocating OpenGL texture\n")); + } + if (!txh->tx_io->gl_type) + return 0; + + GL_CHECK_ERR() tx_bind(txh); + GL_CHECK_ERR() txh->tx_io->flags &= ~TX_NEEDS_HW_LOAD; data = gf_sc_texture_get_data(txh, &pixel_format); if (!data) return 0; - if (txh->tx_io->flags & TX_EMULE_FIRST_LOAD) { - txh->tx_io->flags &= ~TX_EMULE_FIRST_LOAD; - first_load = 1; - } - - if (txh->tx_io->flags & TX_EMULE_POW2) { - w = txh->tx_io->conv_w; - h = txh->tx_io->conv_h; - } else { - w = txh->width; - h = txh->height * nb_frames; - } -#if defined(GPAC_USE_GLES1X) || defined(GPAC_USE_GLES2) - tx_mode = txh->tx_io->gl_format; -#else - tx_mode = txh->tx_io->nb_comp; - if (txh->tx_io->conv_format==GF_PIXEL_YVYU) { - tx_mode = txh->tx_io->gl_format; - } -#endif - - push_time = gf_sys_clock(); + gf_sc_texture_check_pause_on_first_load(txh, GF_TRUE); #ifdef GPAC_USE_TINYGL - glTexImage2D(txh->tx_io->gl_type, 0, tx_mode, w, h, 0, txh->tx_io->gl_format, txh->tx_io->gl_dtype, (unsigned char *) data); + glTexImage2D(txh->tx_io->gl_type, 0, txh->tx_io->gl_format, w, h, 0, txh->tx_io->gl_format, GL_UNSIGNED_BYTE, (unsigned char *) data); #else - gf_sc_texture_check_pause_on_first_load(txh); - - /*pow2 texture or hardware support*/ - if (! (txh->tx_io->flags & TX_MUST_SCALE) ) { - if (txh->tx_io->yuv_shader) { - u32 stride_luma = txh->stride; - u32 stride_chroma = txh->stride_chroma; - u8 *pY, *pU, *pV; - - if (txh->frame && txh->frame->GetGLTexture) { - u32 gl_format; - - if (!txh->tx_io->use_external_textures) { - glDeleteTextures(1, &txh->tx_io->id); - glDeleteTextures(1, &txh->tx_io->u_id); - glDeleteTextures(1, &txh->tx_io->v_id); - txh->tx_io->id = txh->tx_io->u_id = txh->tx_io->v_id = 0; - txh->tx_io->use_external_textures = GF_TRUE; - } - - if (txh->frame->GetGLTexture(txh->frame, 0, &gl_format, &txh->tx_io->id, &txh->tx_io->texcoordmatrix) == GF_OK) { - - glBindTexture(gl_format, txh->tx_io->id); - GLTEXPARAM(gl_format, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - GLTEXPARAM(gl_format, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - GLTEXPARAM(gl_format, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - GLTEXPARAM(gl_format, GL_TEXTURE_MIN_FILTER, GL_NEAREST); -#ifdef GPAC_ANDROID - if ( gl_format == GL_TEXTURE_EXTERNAL_OES) { - txh->tx_io->flags |= TX_IS_FLIPPED; - txh->tx_io->gl_type = GL_TEXTURE_EXTERNAL_OES; - goto push_exit; - } -#endif // GPAC_ANDROID - - if (txh->frame->GetGLTexture(txh->frame, 1, &gl_format, &txh->tx_io->u_id, &txh->tx_io->texcoordmatrix) == GF_OK) { - glBindTexture(gl_format, txh->tx_io->u_id); - GLTEXPARAM(gl_format, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - GLTEXPARAM(gl_format, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - GLTEXPARAM(gl_format, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - GLTEXPARAM(gl_format, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - } - } - goto push_exit; - } - - - pY = (u8 *) data; - pU = pV = NULL; - - if (txh->raw_memory) { - assert(txh->pU); - pU = (u8 *) txh->pU; - pV = (u8 *) txh->pV; - } else { - pU = (u8 *) pY + nb_frames * txh->height * txh->stride; - } - - switch (txh->pixelformat) { - case GF_PIXEL_YUV444_10: - case GF_PIXEL_YUV444: - if (!stride_chroma) - stride_chroma = stride_luma; - if (!pV) - pV = (u8 *) pU + txh->height * stride_chroma; - break; - case GF_PIXEL_YUV422_10: - case GF_PIXEL_YUV422: - if (!stride_chroma) - stride_chroma = stride_luma/2; - if (!pV) - pV = (u8 *) pU + txh->height * stride_chroma; - break; - case GF_PIXEL_YV12_10: - case GF_PIXEL_YV12: - if (!stride_chroma) - stride_chroma = stride_luma/2; - if (!pV) - pV = (u8 *) pU + txh->height * nb_frames * stride_chroma / 2; - break; - case GF_PIXEL_NV21: - case GF_PIXEL_NV12: - if (!stride_chroma) - stride_chroma = stride_luma/2; - break; - default: - if (!stride_chroma) - stride_chroma = stride_luma/2; - pV = NULL; - break; - } - -#if !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_GLES2) - - if (txh->pixelformat==GF_PIXEL_YV12_10 || txh->pixelformat==GF_PIXEL_YUV422_10 ||txh->pixelformat==GF_PIXEL_YUV444_10) { - glPixelStorei(GL_UNPACK_ALIGNMENT, 2); - //we use 10 bits but GL will normalise using 16 bits, so we need to multiply the nomralized result by 2^6 - glPixelTransferi(GL_RED_SCALE, 64); - } -#endif - - do_tex_image_2d(txh, tx_mode, first_load, pY, stride_luma, w, h, txh->tx_io->pbo_id); - GL_CHECK_ERR - - /* - * Note: GF_PIXEL_NV21 is default for Android camera review. First wxh bytes is the Y channel, - * the following (wxh)/2 bytes is UV plane. - * Reference: http://stackoverflow.com/questions/22456884/how-to-render-androids-yuv-nv21-camera-image-on-the-background-in-libgdx-with-o - */ - if ((txh->pixelformat == GF_PIXEL_NV21) || (txh->pixelformat == GF_PIXEL_NV12)) { - u32 fmt = txh->tx_io->gl_format; - txh->tx_io->gl_format = GL_LUMINANCE_ALPHA; - glBindTexture(txh->tx_io->gl_type, txh->tx_io->u_id); - do_tex_image_2d(txh, GL_LUMINANCE_ALPHA, first_load, pU, stride_chroma, w/2, h/2, txh->tx_io->u_pbo_id); - txh->tx_io->gl_format = fmt; - GL_CHECK_ERR - } - else if (txh->pixelformat == GF_PIXEL_YV12_10 || txh->pixelformat == GF_PIXEL_YV12 ) { - glBindTexture(txh->tx_io->gl_type, txh->tx_io->u_id); - do_tex_image_2d(txh, tx_mode, first_load, pU, stride_chroma, w/2, h/2, txh->tx_io->u_pbo_id); - GL_CHECK_ERR - - glBindTexture(txh->tx_io->gl_type, txh->tx_io->v_id); - do_tex_image_2d(txh, tx_mode, first_load, pV, stride_chroma, w/2, h/2, txh->tx_io->v_pbo_id); - GL_CHECK_ERR - } - else if (txh->pixelformat == GF_PIXEL_YUV422_10 || txh->pixelformat == GF_PIXEL_YUV422) { - - glBindTexture(txh->tx_io->gl_type, txh->tx_io->u_id); - do_tex_image_2d(txh, tx_mode, first_load, pU, stride_chroma, w/2 , h , txh->tx_io->u_pbo_id); - GL_CHECK_ERR - - glBindTexture(txh->tx_io->gl_type, txh->tx_io->v_id); - do_tex_image_2d(txh, tx_mode, first_load, pV, stride_chroma, w/2 , h, txh->tx_io->v_pbo_id); - GL_CHECK_ERR - } - else if (txh->pixelformat == GF_PIXEL_YUV444_10 || txh->pixelformat == GF_PIXEL_YUV444) { - - glBindTexture(txh->tx_io->gl_type, txh->tx_io->u_id); - do_tex_image_2d(txh, tx_mode, first_load, pU, stride_chroma, w, h, txh->tx_io->u_pbo_id); - GL_CHECK_ERR - - glBindTexture(txh->tx_io->gl_type, txh->tx_io->v_id); - do_tex_image_2d(txh, tx_mode, first_load, pV, stride_chroma, w, h, txh->tx_io->v_pbo_id); - GL_CHECK_ERR - } - -#if !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_GLES2) - if (txh->pixelformat==GF_PIXEL_YV12_10 || txh->pixelformat==GF_PIXEL_YUV444_10 || txh->pixelformat==GF_PIXEL_YUV422_10 ) { - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glPixelTransferi(GL_RED_SCALE, 1); - } -#endif - - txh->tx_io->pbo_pushed = 0; - } else { - do_tex_image_2d(txh, tx_mode, first_load, (u8 *) data, txh->stride, w, h, txh->tx_io->pbo_id); - txh->tx_io->pbo_pushed = 0; - } - } else { - -#ifdef GPAC_HAS_GLU - if (!txh->compositor->disable_glu_scale) { - gluScaleImage(txh->tx_io->gl_format, txh->width, txh->height, txh->tx_io->gl_dtype, data, txh->tx_io->rescale_width, txh->tx_io->rescale_height, txh->tx_io->gl_dtype, txh->tx_io->scale_data); - } else -#endif - { - /*it appears gluScaleImage is quite slow - use ourt own resampler which is not as nice but a but faster*/ - GF_VideoSurface src, dst; - memset(&src, 0, sizeof(GF_VideoSurface)); - src.width = txh->width; - src.height = txh->height; - src.pitch_x = 0; - src.pitch_y = txh->stride; - src.pixel_format = txh->pixelformat; - src.video_buffer = txh->data; - - memset(&dst, 0, sizeof(GF_VideoSurface)); - dst.width = txh->tx_io->rescale_width; - dst.height = txh->tx_io->rescale_height; - dst.pitch_x = 0; - dst.pitch_y = txh->tx_io->rescale_width*txh->tx_io->nb_comp; - dst.pixel_format = txh->pixelformat; - dst.video_buffer = txh->tx_io->scale_data; - - gf_stretch_bits(&dst, &src, NULL, NULL, 0xFF, 0, NULL, NULL); - } - - if (first_load) { - glTexImage2D(txh->tx_io->gl_type, 0, tx_mode, txh->tx_io->rescale_width, txh->tx_io->rescale_height, 0, txh->tx_io->gl_format, txh->tx_io->gl_dtype, txh->tx_io->scale_data); - } else { - glTexSubImage2D(txh->tx_io->gl_type, 0, 0, 0, txh->tx_io->rescale_width, txh->tx_io->rescale_height, txh->tx_io->gl_format, txh->tx_io->gl_dtype, txh->tx_io->scale_data); + if (txh->frame_ifce && txh->frame_ifce->get_gl_texture ) { + if (txh->tx_io->tx.internal_textures) { + glDeleteTextures(txh->tx_io->tx.nb_textures, txh->tx_io->tx.textures); + txh->tx_io->tx.internal_textures = GF_FALSE; } } -#endif + if (txh->tx_io->tx.pbo_state == GF_GL_PBO_PUSH) + txh->tx_io->tx.pbo_state = GF_GL_PBO_TEXIMG; -push_exit: + GL_CHECK_ERR() + gf_gl_txw_upload(&txh->tx_io->tx, data, txh->frame_ifce); + GL_CHECK_ERR() + +#endif push_time = gf_sys_clock() - push_time; txh->nb_frames ++; txh->upload_time += push_time; -#ifndef GPAC_DISABLE_LOGS - gf_mo_get_object_time(txh->stream, &ck); - GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[GL Texture] Texture (CTS %u) %d ms after due date - Pushed %s in %d ms - average push time %d ms (PBO enabled %s)\n", txh->last_frame_time, ck - txh->last_frame_time, txh->tx_io->yuv_shader ? "YUV textures" : "texture", push_time, txh->upload_time / txh->nb_frames, txh->tx_io->pbo_pushed ? "yes" : "no")); +#ifndef GPAC_DISABLE_LOG + if (txh->stream) { + u32 ck; + gf_mo_get_object_time(txh->stream, &ck); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[GL Texture] Texture (CTS %u) %d ms after due date - Pushed %s in %d ms - average push time %d ms (PBO enabled %s)\n", txh->last_frame_time, ck - txh->last_frame_time, txh->tx_io->tx.is_yuv ? "YUV textures" : "texture", push_time, txh->upload_time / txh->nb_frames, txh->tx_io->tx.pbo_state ? "yes" : "no")); + } #endif return 1; @@ -1338,40 +884,25 @@ push_exit: #ifndef GPAC_DISABLE_3D -void gf_sc_texture_refresh_area(GF_TextureHandler *txh, GF_IRect *rc, void *mem) -{ - if (!txh->tx_io) return; - - if (txh->tx_io->flags & TX_NEEDS_HW_LOAD) { - gf_sc_texture_push_image(txh, 0, 0); - } - - glTexSubImage2D(txh->tx_io->gl_type, 0, rc->x, rc->y, rc->width, rc->height, txh->tx_io->gl_format, txh->tx_io->gl_dtype, mem); -} - -static Bool tx_set_image(GF_TextureHandler *txh, Bool generate_mipmaps) -{ - return gf_sc_texture_push_image(txh, generate_mipmaps, 0); -} - u32 gf_sc_texture_get_gl_id(GF_TextureHandler *txh) { - return txh->tx_io ? txh->tx_io->id : 0; + return (txh->tx_io && txh->tx_io->tx.nb_textures) ? txh->tx_io->tx.textures[0] : 0; } - #ifndef GPAC_USE_TINYGL void gf_sc_copy_to_texture(GF_TextureHandler *txh) { /*in case the ID has been lost, resetup*/ - if (!txh->tx_io->id) { - glGenTextures(1, &txh->tx_io->id); + if (!txh->tx_io->tx.nb_textures) { tx_setup_format(txh); } - GL_CHECK_ERR + GL_CHECK_ERR() + tx_bind(txh); - GL_CHECK_ERR + + GL_CHECK_ERR() + #ifdef GPAC_USE_GLES2 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); @@ -1379,14 +910,36 @@ void gf_sc_copy_to_texture(GF_TextureHandler *txh) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); #endif - GL_CHECK_ERR + GL_CHECK_ERR() + if (txh->compositor->fbo_id) compositor_3d_enable_fbo(txh->compositor, GF_TRUE); glCopyTexImage2D(txh->tx_io->gl_type, 0, txh->tx_io->gl_format, 0, 0, txh->width, txh->height, 0); + if (txh->compositor->fbo_id) compositor_3d_enable_fbo(txh->compositor, GF_FALSE); + #ifndef GPAC_USE_GLES2 glDisable(txh->tx_io->gl_type); #endif - GL_CHECK_ERR + GL_CHECK_ERR() +} + +GF_Err gf_sc_texture_setup_fbo(GF_TextureHandler *txh) +{ + txh->tx_io->gl_type = GL_TEXTURE_2D; + return compositor_3d_setup_fbo(txh->width, txh->height, &txh->tx_io->fbo_id, &txh->tx_io->fbo_txid, &txh->tx_io->depth_id); +} + +void gf_sc_texture_enable_fbo(GF_TextureHandler *txh, Bool enable) +{ +#ifndef GPAC_USE_GLES1X + if (txh->tx_io && txh->tx_io->fbo_id) + glBindFramebuffer(GL_FRAMEBUFFER, enable ? txh->tx_io->fbo_id : 0); + GL_CHECK_ERR() + if (!enable) + glBindTexture(GL_TEXTURE_2D, 0); +#endif } + + #endif #ifndef GPAC_USE_TINYGL @@ -1397,13 +950,16 @@ void gf_sc_copy_to_stencil(GF_TextureHandler *txh) char *tmp=NULL; /*in case the ID has been lost, resetup*/ - if (!txh->data || !txh->tx_io->tx_raster) return; + if (!txh->data || !txh->tx_io->tx_raster) + return; GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[GL Texture] Copying GL backbuffer %dx%d@PF=%s to systems memory\n", txh->width, txh->height, gf_4cc_to_str(txh->pixelformat) )); + if (txh->compositor->fbo_id) compositor_3d_enable_fbo(txh->compositor, GF_TRUE); + if (txh->pixelformat==GF_PIXEL_RGBA) { glReadPixels(0, 0, txh->width, txh->height, GL_RGBA, GL_UNSIGNED_BYTE, txh->data); - } else if (txh->pixelformat==GF_PIXEL_RGB_24) { + } else if (txh->pixelformat==GF_PIXEL_RGB) { glReadPixels(0, 0, txh->width, txh->height, GL_RGB, GL_UNSIGNED_BYTE, txh->data); } #ifdef GF_SR_USE_DEPTH @@ -1449,6 +1005,8 @@ void gf_sc_copy_to_stencil(GF_TextureHandler *txh) } #endif /*GF_SR_USE_DEPTH*/ + if (txh->compositor->fbo_id) compositor_3d_enable_fbo(txh->compositor, GF_FALSE); + /*flip image because of openGL*/ tmp = (char*)gf_malloc(sizeof(char)*txh->stride); hy = txh->height/2; @@ -1474,7 +1032,7 @@ void gf_get_tinygl_depth(GF_TextureHandler *txh) Bool gf_sc_texture_get_transform(GF_TextureHandler *txh, GF_Node *tx_transform, GF_Matrix *mx, Bool for_picking) { #ifndef GPAC_DISABLE_3D - u32 nb_views=1; + int nb_views=1; #endif Bool ret = 0; gf_mx_init(*mx); @@ -1482,13 +1040,13 @@ Bool gf_sc_texture_get_transform(GF_TextureHandler *txh, GF_Node *tx_transform, #ifndef GPAC_DISABLE_3D gf_mo_get_nb_views(txh->stream, &nb_views); -#ifdef GPAC_ANDROID - if(txh->stream && txh->tx_io->gl_type == GL_TEXTURE_EXTERNAL_OES) { +#ifdef GPAC_CONFIG_ANDROID + if (txh->stream && txh->tx_io->gl_type == GL_TEXTURE_EXTERNAL_OES) { gf_mx_copy(*mx, txh->tx_io->texcoordmatrix); ret = 1; } -#endif // GPAC_ANDROID - if (nb_views>1 && !txh->raw_memory){ +#endif // GPAC_CONFIG_ANDROID + if (nb_views>1 && !txh->frame_ifce) { if (txh->compositor->visual->current_view%2 != 0 && !txh->compositor->multiview_mode){ gf_mx_add_translation(mx, 0, 0.5f, 0); } @@ -1496,7 +1054,7 @@ Bool gf_sc_texture_get_transform(GF_TextureHandler *txh, GF_Node *tx_transform, ret = 1; } - if (txh->stream && (txh->compositor->frame_packing==GF_3D_STEREO_TOP)) { + if (txh->stream && (txh->compositor->fpack==GF_3D_STEREO_TOP)) { if ((txh->compositor->visual->current_view % 2 != 0) && !txh->compositor->multiview_mode) { gf_mx_add_translation(mx, 0, 0.5f, 0); } @@ -1517,7 +1075,7 @@ Bool gf_sc_texture_get_transform(GF_TextureHandler *txh, GF_Node *tx_transform, /*WATCHOUT: GL_TEXTURE_RECTANGLE coords are w, h not 1.0, 1.0 (but not with shaders, we do the txcoord conversion in the fragment shader*/ if (txh->tx_io->flags & TX_IS_RECT) { #ifndef GPAC_DISABLE_3D - if (!for_picking && !txh->tx_io->yuv_shader) { + if (!for_picking && !txh->tx_io->tx.is_yuv) { gf_mx_add_scale(mx, INT2FIX(txh->width), INT2FIX(txh->height), FIX_ONE); ret = 1; } @@ -1584,348 +1142,9 @@ Bool gf_sc_texture_get_transform(GF_TextureHandler *txh, GF_Node *tx_transform, } } #endif /*GPAC_DISABLE_X3D*/ - return ret; -} - -#if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_3D) -static Bool gf_sc_texture_enable_matte_texture(GF_Node *n) -{ - return GF_FALSE; -#if 0 - - GF_TextureHandler *b_surf; -#if !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) - GF_TextureHandler *matte_hdl; - GF_TextureHandler *a_surf; - GF_TextureHandler *alpha_surf; - char * action ; - int tmp; - u8 texture[4]; - MFFloat coefficients; -#endif - M_MatteTexture *matte = (M_MatteTexture *)n; - - - b_surf = gf_sc_texture_get_handler(matte->surfaceB); - - if (!b_surf || !b_surf->tx_io) return 0; - glEnable(GL_BLEND); - tx_set_image(b_surf, 0); - -#if defined(GPAC_USE_TINYGL) || defined(GPAC_USE_GLES1X) - tx_bind(b_surf); - return 1; -#else - - -#ifdef LOAD_GL_1_3 - /*To remove: gcc 4.6 introduces this warning*/ -#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Waddress" -#endif - if (glActiveTexture==NULL) { - tx_bind(b_surf); - return 1; - } - /*To remove: gcc 4.6 introduces this warning*/ -#if __GNUC__ == 4 && __GNUC_MINOR__ == 6 -#pragma GCC diagnostic pop -#endif - -#endif - - matte_hdl = gf_node_get_private(n); - if (!matte_hdl->tx_io) { - gf_sc_texture_allocate(matte_hdl); - } - a_surf = gf_sc_texture_get_handler(matte->surfaceA); - if (!a_surf->tx_io) a_surf = NULL; - alpha_surf = gf_sc_texture_get_handler(matte->alphaSurface); - if (!alpha_surf->tx_io) alpha_surf = NULL; - - action = (matte->operation).buffer; - glDisable(GL_TEXTURE_2D); - - /* SCALE */ - if (! strcmp(action,"SCALE") || !strcmp(action,"BIAS") ) { - TexEnvType operand; - coefficients = (matte->parameter); - if (coefficients.count < 3) { - tx_bind(b_surf); - return 1; - } - texture[0] = (u8) FIX2INT( 255 * coefficients.vals[0]); - texture[1] = (u8) FIX2INT( 255 * coefficients.vals[1]); - texture[2] = (u8) FIX2INT( 255 * coefficients.vals[2]); - texture[3] = 255; - if (coefficients.count >= 4) - texture[3] = (u8) FIX2INT( 255 * coefficients.vals[3]); - - glActiveTexture(GL_TEXTURE0); - if (!matte_hdl->tx_io->id) { - glGenTextures(1, &matte_hdl->tx_io->id); - } - glBindTexture( GL_TEXTURE_2D, matte_hdl->tx_io->id); - glEnable( GL_DEPTH_TEST ); - glEnable( GL_TEXTURE_2D ); - glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); - GLTEXPARAM( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR ); - GLTEXPARAM( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR ); - GLTEXPARAM( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_REPEAT ); - GLTEXPARAM( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_REPEAT ); - glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,1,1,0,GL_RGBA,GL_UNSIGNED_BYTE,texture); - - operand = GL_MODULATE; - if (!strcmp(action,"BIAS")) operand = GL_ADD_SIGNED; - - glActiveTexture(GL_TEXTURE1); - tx_bind(b_surf); - GLTEXENV(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_RGB, operand); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE0_RGB , GL_TEXTURE1); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE0); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE ); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_TEXTURE1 ); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); - return 2; - } - /* end SCALE */ - - /* CROSS_FADE */ - if (! strcmp(action,"CROSS_FADE")) { - tmp = FIX2INT(255 * matte->fraction); - texture[0] = (unsigned char) tmp; - texture[1] = (unsigned char) tmp; - texture[2] = (unsigned char) tmp; - texture[3] = (unsigned char) 255; // donne l'alpha de l'image de sortie - - glActiveTexture(GL_TEXTURE0); - if (!matte_hdl->tx_io->id) { - glGenTextures(1, &matte_hdl->tx_io->id); - } - glBindTexture( GL_TEXTURE_2D, matte_hdl->tx_io->id); - glEnable( GL_TEXTURE_2D ); - glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); - GLTEXPARAM( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR ); - GLTEXPARAM( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR ); - GLTEXPARAM( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_REPEAT ); - GLTEXPARAM( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_REPEAT ); - glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,1,1,0,GL_RGBA,GL_UNSIGNED_BYTE,texture); - - /* fin de la g\E9n\E9ration de la texture donn\E9e par la fraction ! */ - - /* m\E9lange effectif des textures ! } */ - glActiveTexture(GL_TEXTURE1); - tx_bind(b_surf); - - if (a_surf) { - glActiveTexture(GL_TEXTURE2); - tx_set_image(a_surf, 0); - tx_bind(a_surf); - GLTEXENV(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE ); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE1 ); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE2 ); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_TEXTURE0 ); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_COLOR); - } - return 3; - } - /* end CROSS_FADE */ - - /*REVEAL */ - if (!strcmp(action,"REVEAL")) { - glActiveTexture(GL_TEXTURE0); - tx_bind(b_surf); - - if (alpha_surf) { - tx_set_image(alpha_surf, 0); - glActiveTexture(GL_TEXTURE1); - tx_bind(alpha_surf); - } - if (a_surf) { - glActiveTexture(GL_TEXTURE2); - tx_set_image(a_surf, 0); - tx_bind(a_surf); - - GLTEXENV(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE0_RGB , GL_TEXTURE0); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE2); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_TEXTURE1); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA); - - } - return 3; - } - /* end REVEAL */ - - /*INVERT */ - if (! strcmp(action,"INVERT")) { - glActiveTexture(GL_TEXTURE0); - tx_bind(b_surf); - GLTEXENV(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_ONE_MINUS_SRC_COLOR); - - if (matte->parameter.count && matte->parameter.vals[0]) { - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE ); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE0); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - } - return 1; - } - /* end INVERT */ - - /* op\E9ration REPLACE_ALPHA */ - if (!strcmp(action,"REPLACE_ALPHA")) { - glActiveTexture(GL_TEXTURE0); - tx_bind(b_surf); - - if (alpha_surf) { - glEnable(GL_BLEND); - glActiveTexture(GL_TEXTURE1); - tx_set_image(alpha_surf, 0); - tx_bind(alpha_surf); - GLTEXENV(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); - } - return 2; - } - /* end REPLACE_ALPHA */ - - /* MULTIPLY_ALPHA */ - if (!strcmp(action,"MULTIPLY_ALPHA")) { - glActiveTexture(GL_TEXTURE0); - tx_bind(b_surf); - GLTEXENV(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); - - if (alpha_surf) { - glActiveTexture(GL_TEXTURE1); - tx_set_image(alpha_surf, 0); - tx_bind(alpha_surf); - GLTEXENV(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_TEXTURE1); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE0); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); - } - return 2; - } - /* end MULTIPLY_ALPHA */ - - /*ADD */ - if (! strcmp(action,"ADD")) { - glActiveTexture(GL_TEXTURE0); - tx_bind(b_surf); - if (a_surf) { - glActiveTexture(GL_TEXTURE1); - tx_set_image(a_surf, 0); - tx_bind(a_surf); - GLTEXENV(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD_SIGNED); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE1); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE0); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); - } - return 2; - } - /*end ADD*/ - - /* ADD_SIGNED*/ - if (! strcmp(action,"ADD_SIGNED")) { - glActiveTexture(GL_TEXTURE0); - tx_bind(b_surf); - if (a_surf) { - glActiveTexture(GL_TEXTURE1); - tx_set_image(a_surf, 0); - tx_bind(a_surf); - GLTEXENV(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE1); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE0); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); - } - return 2; - } - /* end ADD_SIGNED*/ - - /*SUBSTRACT*/ - if (! strcmp(action,"SUBSTRACT")) { - glActiveTexture(GL_TEXTURE0); - tx_bind(b_surf); - if (a_surf) { - glActiveTexture(GL_TEXTURE1); - tx_set_image(a_surf, 0); - tx_bind(a_surf); - GLTEXENV(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_SUBTRACT); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE1); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE0); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); - } - return 2; - } - /* end SUBTRACT*/ - - /*BLEND*/ - if (! strcmp(action,"BLEND")) { - glActiveTexture(GL_TEXTURE0); - tx_bind(b_surf); - if (a_surf) { - GLTEXENV(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); - glActiveTexture(GL_TEXTURE1); - tx_set_image(a_surf, 0); - tx_bind(a_surf); - GLTEXENV(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); - GLTEXENV(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - GLTEXENV(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); - GLTEXENV(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE1); - GLTEXENV(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); - } - return 2; - } - /*end BLEND */ - - tx_bind(b_surf); - return 1; - -#endif - -#endif + return ret; } -#endif //GPAC_DISABLE_VRML Bool gf_sc_texture_is_transparent(GF_TextureHandler *txh) { @@ -1947,39 +1166,53 @@ Bool gf_sc_texture_is_transparent(GF_TextureHandler *txh) u32 gf_sc_texture_enable_ex(GF_TextureHandler *txh, GF_Node *tx_transform, GF_Rect *bounds) { GF_Matrix mx; + Bool res; #if !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) - GF_Compositor *compositor = (GF_Compositor *)txh->compositor; + GF_GLProgInstance *prog; #endif - GF_VisualManager *root_visual = (GF_VisualManager *) txh->compositor->visual; + GF_VisualManager *root_visual; + + if (!txh || !txh->tx_io) return 0; + root_visual = (GF_VisualManager *) txh->compositor->visual; + if (txh->stream && !txh->stream->pck) { + if (txh->tx_io->tx.nb_textures) { + txh->tx_io->tx.frame_ifce = NULL; + goto skip_push; + } + return 0; + } if (root_visual->has_material_2d) { // mat2d (hence no lights) - root_visual->glsl_flags &= ~GF_GL_HAS_LIGHT; + root_visual->active_glsl_flags &= ~GF_GL_HAS_LIGHT; } #ifndef GPAC_DISABLE_VRML if (txh->matteTexture) { - u32 ret = gf_sc_texture_enable_matte_texture(txh->matteTexture); //Removed - will always return GF_FALSE - if (!ret) return 0; - - if (gf_sc_texture_get_transform(txh, tx_transform, &mx, 0)) - visual_3d_set_texture_matrix(root_visual, &mx); - else - visual_3d_set_texture_matrix(root_visual, NULL); - - return ret; + //not supported yet + return 0; } #endif - if (!txh || !txh->tx_io) - return 0; if (txh->compute_gradient_matrix && gf_sc_texture_needs_reload(txh) ) { compositor_gradient_update(txh); } - - if (! tx_set_image(txh, 0) ) { + if (!txh->pixelformat) return 0; + + if (!txh->stream || txh->data || txh->frame_ifce) { + gf_rmt_begin_gl(gf_sc_texture_push_image); + glGetError(); + res = gf_sc_texture_push_image(txh, 0, 0); + gf_rmt_end_gl(); + glGetError(); + if (!res) return 0; } +skip_push: + + gf_rmt_begin_gl(gf_sc_texture_enable); + glGetError(); + if (bounds && txh->compute_gradient_matrix) { GF_Matrix2D mx2d; txh->compute_gradient_matrix(txh, bounds, &mx2d, 1); @@ -1993,63 +1226,24 @@ u32 gf_sc_texture_enable_ex(GF_TextureHandler *txh, GF_Node *tx_transform, GF_Re } txh->flags |= GF_SR_TEXTURE_USED; - root_visual->glsl_flags |= GF_GL_HAS_TEXTURE; - root_visual->glsl_flags &= ~(GF_GL_IS_YUV | GF_GL_IS_ExternalOES); + root_visual->active_glsl_flags |= GF_GL_HAS_TEXTURE; + root_visual->bound_tx_pix_fmt = txh->pixelformat; #if !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) + prog = visual_3d_check_program_exists(root_visual, root_visual->active_glsl_flags, txh->pixelformat); - if (txh->tx_io->yuv_shader) { - u32 active_shader; //stores current shader (GLES2.0 or the old stuff) -#ifdef GPAC_ANDROID - root_visual->glsl_flags |= (txh->tx_io->gl_type == GL_TEXTURE_EXTERNAL_OES ) ? GF_GL_IS_ExternalOES : GF_GL_IS_YUV; -#else - root_visual->glsl_flags |= GF_GL_IS_YUV; -#endif // GPAC_ANDROID - active_shader = root_visual->glsl_programs[root_visual->glsl_flags]; //Set active - - GL_CHECK_ERR - - glUseProgram(active_shader); - GL_CHECK_ERR - -#ifdef GPAC_ANDROID - if (txh->tx_io->gl_type != GL_TEXTURE_EXTERNAL_OES) { -#endif // GPAC_ANDROID - glActiveTexture(GL_TEXTURE2); - glBindTexture(txh->tx_io->gl_type, txh->tx_io->v_id); - - glActiveTexture(GL_TEXTURE1); - glBindTexture(txh->tx_io->gl_type, txh->tx_io->u_id); -#ifdef GPAC_ANDROID - } -#endif // GPAC_ANDROID - glActiveTexture(GL_TEXTURE0); - glBindTexture(txh->tx_io->gl_type, txh->tx_io->id); - - GL_CHECK_ERR - tx_bind_with_mode(txh, txh->transparent, txh->tx_io->blend_mode, 1); -#ifndef GPAC_USE_GLES2 - glClientActiveTexture(GL_TEXTURE0); + if (prog) { + glUseProgram(prog->prog); + GL_CHECK_ERR() + tx_bind_with_mode(txh, txh->transparent, txh->tx_io->blend_mode, 0, prog->prog); + } else #endif - - return 1; - } - - if (compositor->shader_only_mode) { - glUseProgram(root_visual->glsl_programs[root_visual->glsl_flags]); - GL_CHECK_ERR - - glActiveTexture(GL_TEXTURE0); - GL_CHECK_ERR - glBindTexture(txh->tx_io->gl_type, txh->tx_io->id); - GL_CHECK_ERR - + { tx_bind(txh); - return 1; } -#endif - tx_bind(txh); + gf_rmt_end_gl(); + glGetError(); return 1; } @@ -2068,31 +1262,31 @@ Bool gf_sc_texture_needs_reload(GF_TextureHandler *txh) } -GF_STENCIL gf_sc_texture_get_stencil(GF_TextureHandler *txh) +GF_EVGStencil * gf_sc_texture_get_stencil(GF_TextureHandler *txh) { return txh->tx_io->tx_raster; } -void gf_sc_texture_set_stencil(GF_TextureHandler *txh, GF_STENCIL stencil) +void gf_sc_texture_set_stencil(GF_TextureHandler *txh, GF_EVGStencil * stencil) { txh->tx_io->tx_raster = stencil; txh->tx_io->flags |= TX_NEEDS_HW_LOAD; } -void gf_sc_texture_check_pause_on_first_load(GF_TextureHandler *txh) +void gf_sc_texture_check_pause_on_first_load(GF_TextureHandler *txh, Bool do_freeze) { - if (txh->stream && txh->tx_io) { - switch (txh->tx_io->init_pause_status) { - case 0: + if (!txh->stream || !txh->tx_io) + return; + + if (do_freeze) { + if (! (txh->tx_io->flags & TX_FIRST_UPLOAD_FREEZE) ) { + txh->tx_io->flags |= TX_FIRST_UPLOAD_FREEZE; gf_sc_ar_control(txh->compositor->audio_renderer, GF_SC_AR_PAUSE); - txh->tx_io->init_pause_status = 1; - break; - case 1: + } + } else { + if (!(txh->tx_io->flags & TX_FIRST_UPLOAD_FREEZE_DONE)) { + txh->tx_io->flags |= TX_FIRST_UPLOAD_FREEZE_DONE; gf_sc_ar_control(txh->compositor->audio_renderer, GF_SC_AR_RESUME); - txh->tx_io->init_pause_status = 2; - break; - default: - break; } } } diff --git a/src/compositor/visual_manager.c b/src/compositor/visual_manager.c index 7756abb..1e89cc3 100644 --- a/src/compositor/visual_manager.c +++ b/src/compositor/visual_manager.c @@ -53,11 +53,18 @@ GF_VisualManager *visual_new(GF_Compositor *compositor) tmp->view_stack = gf_list_new(); #endif - tmp->raster_brush = compositor->rasterizer->stencil_new(compositor->rasterizer, GF_STENCIL_SOLID); + tmp->raster_brush = gf_evg_stencil_new(GF_STENCIL_SOLID); tmp->DrawBitmap = visual_draw_bitmap_stub; tmp->ClearSurface = visual_2d_clear_surface; +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + visual_draw_bitmap_stub(tmp, NULL, NULL); + visual_reset_graphics(NULL); + } +#endif + #ifndef GPAC_DISABLE_3D #ifndef GPAC_DISABLE_VRML @@ -65,6 +72,7 @@ GF_VisualManager *visual_new(GF_Compositor *compositor) tmp->fog_stack = gf_list_new(); #endif /*GPAC_DISABLE_VRML*/ tmp->alpha_nodes_to_draw = gf_list_new(); + tmp->compiled_programs = gf_list_new(); #endif return tmp; @@ -74,9 +82,9 @@ void visual_del(GF_VisualManager *visual) { ra_del(&visual->to_redraw); - if (visual->raster_surface) visual->compositor->rasterizer->surface_delete(visual->raster_surface); + if (visual->raster_surface) gf_evg_surface_delete(visual->raster_surface); visual->raster_surface = NULL; - if (visual->raster_brush) visual->compositor->rasterizer->stencil_delete(visual->raster_brush); + if (visual->raster_brush) gf_evg_stencil_delete(visual->raster_brush); visual->raster_brush = NULL; while (visual->context) { @@ -105,6 +113,7 @@ void visual_del(GF_VisualManager *visual) #endif /*GPAC_DISABLE_VRML*/ gf_list_del(visual->alpha_nodes_to_draw); + gf_list_del(visual->compiled_programs); #endif gf_free(visual); } @@ -187,6 +196,8 @@ Bool visual_draw_frame(GF_VisualManager *visual, GF_Node *root, GF_TraverseState visual_clean_contexts(visual); return res; } + if (visual->compositor->hybrid_opengl) + visual_3d_clean_state(visual); #endif return visual_2d_draw_frame(visual, root, tr_state, is_root_visual); } @@ -294,6 +305,7 @@ void gf_sc_get_nodes_bounds(GF_Node *self, GF_ChildNodeItem *children, GF_Traver void visual_reset_graphics(GF_VisualManager *visual) { + if (!visual) return; #ifndef GPAC_DISABLE_3D if (visual->type_3d) { visual_3d_reset_graphics(visual); diff --git a/src/compositor/visual_manager.h b/src/compositor/visual_manager.h index 556cfae..dd0f09a 100644 --- a/src/compositor/visual_manager.h +++ b/src/compositor/visual_manager.h @@ -39,11 +39,11 @@ /* number of preprocessor flags for GL3/ES2.0 */ #define GF_GL_NUM_OF_FLAGS 6 -#ifdef GPAC_ANDROID +#ifdef GPAC_CONFIG_ANDROID #define GF_GL_NB_FRAG_SHADERS (1<<(GF_GL_NUM_OF_FLAGS) ) //=2^GF_GL_NUM_OF_FLAGS #else #define GF_GL_NB_FRAG_SHADERS (1<<(GF_GL_NUM_OF_FLAGS-1) ) //=2^GF_GL_NUM_OF_FLAGS-1 ( ExternalOES ignored in fragment shader when the platform is not Android) -#endif // GPAC_ANDROID +#endif // GPAC_CONFIG_ANDROID #define GF_GL_NB_VERT_SHADERS (1<<(GF_GL_NUM_OF_FLAGS-2) ) //=2^GF_GL_NUM_OF_FLAGS-2 (YUV and ExternalOES ignored in vertex shader) /* setting preprocessor flags for GL3/ES2.0 shaders */ @@ -66,6 +66,26 @@ enum GF_3D_CAMERA_CIRCULAR, }; + +#ifndef GPAC_DISABLE_3D + +#if defined( _LP64 ) && defined(CONFIG_DARWIN_GL) +#define GF_SHADERID u64 +#else +#define GF_SHADERID u32 +#endif + +typedef struct _gl_prog +{ + GF_SHADERID vertex; + GF_SHADERID fragment; + GF_SHADERID prog; + u32 flags; + u32 pix_fmt; +} GF_GLProgInstance; +#endif + + struct _visual_manager { GF_Compositor *compositor; @@ -140,9 +160,9 @@ struct _visual_manager Bool (*CheckAttached)(GF_VisualManager *visual); /*raster surface interface*/ - GF_SURFACE raster_surface; + GF_EVGSurface *raster_surface; /*raster brush interface*/ - GF_STENCIL raster_brush; + GF_EVGStencil *raster_brush; /*node owning this visual manager (composite textures) - NULL for root visual*/ GF_Node *offscreen; @@ -160,12 +180,6 @@ struct _visual_manager * Visual Manager part for 3D drawing */ -#if defined( _LP64 ) && defined(CONFIG_DARWIN_GL) -#define GF_SHADERID u64 -#else -#define GF_SHADERID u32 -#endif - #ifndef GPAC_DISABLE_VRML /*navigation stack*/ GF_List *navigation_stack; @@ -195,8 +209,7 @@ struct _visual_manager GF_RectArray hybgl_drawn; u32 nb_objects_on_canvas_since_last_ogl_flush; - u32 nb_views, current_view, autostereo_type, camera_layout; - Bool reverse_views; + u32 nb_views, current_view, autostereo_type, camlay; GF_SHADERID base_glsl_vertex; @@ -206,9 +219,6 @@ struct _visual_manager GF_SHADERID autostereo_glsl_program; GF_SHADERID autostereo_glsl_fragment; - GF_SHADERID current_texture_glsl_program; - - Bool needs_projection_matrix_reload; /*GL state to emulate with GLSL [ES2.0]*/ @@ -236,22 +246,12 @@ struct _visual_manager /*end of GL state to emulate with GLSL*/ -//startof GL3/ES2.0 elements /* shaders used for shader-only drawing */ GF_SHADERID glsl_program; + GF_List *compiled_programs; + u32 bound_tx_pix_fmt; //only one texture currently supported - /* Storing Compiled Shaders */ - GF_SHADERID glsl_programs[GF_GL_NB_FRAG_SHADERS]; - GF_SHADERID glsl_vertex_shaders[GF_GL_NB_VERT_SHADERS]; - GF_SHADERID glsl_fragment_shaders[GF_GL_NB_FRAG_SHADERS]; - - /* If GF_TRUE the Array of Shaders is built */ - Bool glsl_has_shaders; - - /* Compilation/Features Flags for dynamic shader */ - u32 glsl_flags; -//endof - + u32 active_glsl_flags; #endif //!GPAC_DISABLE_3D #ifdef GF_SR_USE_DEPTH @@ -279,5 +279,9 @@ void visual_clean_contexts(GF_VisualManager *visual); void visual_reset_graphics(GF_VisualManager *visual); +#ifndef GPAC_DISABLE_3D +GF_GLProgInstance *visual_3d_check_program_exists(GF_VisualManager *root_visual, u32 flags, u32 pix_fmt); +#endif + #endif /*_VISUAL_MANAGER_H_*/ diff --git a/src/compositor/visual_manager_2d.c b/src/compositor/visual_manager_2d.c index e698d09..3cbfb48 100644 --- a/src/compositor/visual_manager_2d.c +++ b/src/compositor/visual_manager_2d.c @@ -169,6 +169,7 @@ void visual_2d_drawable_delete(GF_VisualManager *visual, struct _drawable *drawa } } +#if 0 //unused Bool visual_2d_node_cull(GF_TraverseState *tr_state, GF_Rect *bounds) { GF_Rect rc; @@ -179,6 +180,7 @@ Bool visual_2d_node_cull(GF_TraverseState *tr_state, GF_Rect *bounds) if (gf_irect_overlaps(&tr_state->visual->top_clipper, &i_rc)) return 1; return 0; } +#endif void visual_2d_setup_projection(GF_VisualManager *visual, GF_TraverseState *tr_state) { @@ -193,7 +195,7 @@ void visual_2d_setup_projection(GF_VisualManager *visual, GF_TraverseState *tr_s /*setup clipper*/ if (visual->center_coords) { if (!visual->offscreen) { - if (visual->compositor->scalable_zoom) + if (visual->compositor->sz) rc = gf_rect_center(INT2FIX(visual->compositor->display_width), INT2FIX(visual->compositor->display_height)); else rc = gf_rect_center(INT2FIX(visual->compositor->output_width + 2*visual->compositor->vp_x), INT2FIX(visual->compositor->output_height + 2*visual->compositor->vp_y)); @@ -257,7 +259,7 @@ GF_Err visual_2d_init_draw(GF_VisualManager *visual, GF_TraverseState *tr_state) DrawableContext *ctx; M_Background2D *bck; #endif - u32 draw_mode; + u32 mode2d; /*reset display list*/ visual->cur_context = visual->context; @@ -278,15 +280,15 @@ GF_Err visual_2d_init_draw(GF_VisualManager *visual, GF_TraverseState *tr_state) return e; tr_state->immediate_for_defer = GF_FALSE; - draw_mode = 0; + mode2d = 0; if (tr_state->immediate_draw) { - draw_mode = 1; + mode2d = 1; } /*if we're requested to invalidate everything, switch to direct drawing but don't reset bounds*/ else if (tr_state->invalidate_all) { tr_state->immediate_draw = 1; tr_state->immediate_for_defer = GF_TRUE; - draw_mode = 2; + mode2d = 2; } tr_state->invalidate_all = 0; @@ -296,7 +298,7 @@ GF_Err visual_2d_init_draw(GF_VisualManager *visual, GF_TraverseState *tr_state) it = visual->prev_nodes; while (it) { /*node was not drawn on this visual*/ - if (!drawable_flush_bounds(it->drawable, visual, draw_mode)) { + if (!drawable_flush_bounds(it->drawable, visual, mode2d)) { GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Visual2D] Unregistering previously drawn node %s from visual\n", gf_node_get_class_name(it->drawable->node))); /*remove all bounds info related to this visual and unreg node */ @@ -316,8 +318,8 @@ GF_Err visual_2d_init_draw(GF_VisualManager *visual, GF_TraverseState *tr_state) count++; } } - GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Visual2D] Top visual initialized - %d nodes registered and %d removed - using %s rendering\n", count, rem, draw_mode ? "direct" : "dirty-rect")); - if (!draw_mode) return GF_OK; + GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Visual2D] Top visual initialized - %d nodes registered and %d removed - using %s rendering\n", count, rem, mode2d ? "direct" : "dirty-rect")); + if (!mode2d) return GF_OK; #ifndef GPAC_DISABLE_VRML /*direct mode, draw background*/ @@ -735,7 +737,7 @@ Bool visual_2d_terminate_draw(GF_VisualManager *visual, GF_TraverseState *tr_sta tr_state->traversing_mode = TRAVERSE_DRAW_2D; //if only one opaque object has changed and not moved, skip background unless hybgl mode - if (!visual->compositor->hybrid_opengl && !hyb_force_redraw && !hyb_force_background && first_opaque && (visual->to_redraw.count==1) && gf_rect_equal(first_opaque->bi->clip, visual->to_redraw.list[0].rect)) { + if (!visual->compositor->hybrid_opengl && !hyb_force_redraw && !hyb_force_background && first_opaque && (visual->to_redraw.count==1) && irect_rect_equal(&first_opaque->bi->clip, &visual->to_redraw.list[0].rect)) { visual->has_modif=0; goto skip_background; } @@ -804,12 +806,12 @@ Bool visual_2d_terminate_draw(GF_VisualManager *visual, GF_TraverseState *tr_sta skip_background: #ifndef GPAC_DISABLE_LOG - if (gf_log_tool_level_on(GF_LOG_COMPOSE, GF_LOG_INFO)) { - GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Visual2D] Redraw %d / %d nodes (all: %s - %d dirty rects\n)", num_changed, num_nodes, redraw_all ? "yes" : "no", visual->to_redraw.count)); + if (gf_log_tool_level_on(GF_LOG_COMPOSE, GF_LOG_DEBUG)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Visual2D] Redraw %d / %d nodes (all: %s - %d dirty rects\n)", num_changed, num_nodes, redraw_all ? "yes" : "no", visual->to_redraw.count)); if (visual->to_redraw.count>1) GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("\n")); for (i=0; ito_redraw.count; i++) { - GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("\tDirtyRect #%d: %d:%d@%dx%d\n", i+1, visual->to_redraw.list[i].rect.x, visual->to_redraw.list[i].rect.y, visual->to_redraw.list[i].rect.width, visual->to_redraw.list[i].rect.height)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("\tDirtyRect #%d: %d:%d@%dx%d\n", i+1, visual->to_redraw.list[i].rect.x, visual->to_redraw.list[i].rect.y, visual->to_redraw.list[i].rect.width, visual->to_redraw.list[i].rect.height)); assert(visual->to_redraw.list[i].rect.width); } } @@ -842,14 +844,13 @@ skip_background: if (visual->direct_flush) { GF_DirtyRectangles dr; - u32 i; dr.count = visual->to_redraw.count; dr.list = gf_malloc(sizeof(GF_IRect)*dr.count); for (i=0; ito_redraw.list[i].rect; } visual->compositor->video_out->FlushRectangles(visual->compositor->video_out, &dr); - visual->compositor->skip_flush=1; + visual->compositor->skip_flush = 1; gf_free(dr.list); } @@ -864,7 +865,6 @@ exit: Bool visual_2d_draw_frame(GF_VisualManager *visual, GF_Node *root, GF_TraverseState *tr_state, Bool is_root_visual) { - GF_SceneGraph *sg; GF_Matrix2D backup; u32 i; Bool res; @@ -876,10 +876,21 @@ Bool visual_2d_draw_frame(GF_VisualManager *visual, GF_Node *root, GF_TraverseSt gf_mx2d_copy(backup, tr_state->transform); visual->bounds_tracker_modif_flag = DRAWABLE_HAS_CHANGED; +#ifndef GPAC_DISABLE_3D + if (visual->compositor->hybrid_opengl && visual->compositor->fbo_id) { + compositor_3d_enable_fbo(visual->compositor, GF_TRUE); + } +#endif + e = visual_2d_init_draw(visual, tr_state); if (e) { gf_mx2d_copy(tr_state->transform, backup); GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Visual2D] Cannot init draw phase: %s\n", gf_error_to_string(e))); +#ifndef GPAC_DISABLE_3D + if (visual->compositor->hybrid_opengl && visual->compositor->fbo_id) { + compositor_3d_enable_fbo(visual->compositor, GF_FALSE); + } +#endif return 0; } @@ -892,6 +903,7 @@ Bool visual_2d_draw_frame(GF_VisualManager *visual, GF_Node *root, GF_TraverseSt GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Visual2D] Traversing scene subtree (root node %s)\n", root ? gf_node_get_class_name(root) : "none")); if (is_root_visual) { + GF_SceneGraph *sg; gf_node_traverse(root, tr_state); i=0; @@ -917,6 +929,11 @@ Bool visual_2d_draw_frame(GF_VisualManager *visual, GF_Node *root, GF_TraverseSt } #endif +#ifndef GPAC_DISABLE_3D + if (visual->compositor->hybrid_opengl && visual->compositor->fbo_id) { + compositor_3d_enable_fbo(visual->compositor, GF_FALSE); + } +#endif return res; } diff --git a/src/compositor/visual_manager_2d.h b/src/compositor/visual_manager_2d.h index 79b3566..0c530b8 100644 --- a/src/compositor/visual_manager_2d.h +++ b/src/compositor/visual_manager_2d.h @@ -34,7 +34,7 @@ GF_Rect gf_rect_ft(GF_IRect *rc); Bool gf_irect_inside(GF_IRect *rc1, GF_IRect *rc2); /*@rc1 equales @rc2*/ -#define gf_rect_equal(rc1, rc2) ((rc1.width == rc2.width) && (rc1.height == rc2.height) && (rc1.x == rc2.x) && (rc1.y == rc2.y)) +#define irect_rect_equal(rc1, rc2) ((rc1.width == rc2.width) && (rc1.height == rc2.height) && (rc1.x == rc2.x) && (rc1.y == rc2.y)) //#define TRACK_OPAQUE_REGIONS @@ -91,8 +91,6 @@ struct _drawable_store */ Bool visual_2d_draw_frame(GF_VisualManager *visual, GF_Node *root, GF_TraverseState *tr_state, Bool is_root_visual); -Bool visual_2d_node_cull(GF_TraverseState *tr_state, GF_Rect *bounds); - void visual_2d_pick_node(GF_VisualManager *visual, GF_TraverseState *tr_state, GF_Event *ev, GF_ChildNodeItem *children); void visual_2d_clear_surface(GF_VisualManager *visual, GF_IRect *rc, u32 BackColor, u32 is_offscreen); @@ -113,7 +111,7 @@ void visual_2d_release_raster(GF_VisualManager *visual); void visual_2d_texture_path(GF_VisualManager *visual, GF_Path *path, DrawableContext *ctx, GF_TraverseState *tr_state); /*draw the path (fill and strike) - if brushes are NULL they are created if needed based on the context aspect DrawPath shall always be called after TexturePath*/ -void visual_2d_draw_path(GF_VisualManager *visual, GF_Path *path, DrawableContext *ctx, GF_STENCIL brush, GF_STENCIL pen, GF_TraverseState *tr_state); +void visual_2d_draw_path(GF_VisualManager *visual, GF_Path *path, DrawableContext *ctx, GF_EVGStencil * brush, GF_EVGStencil * pen, GF_TraverseState *tr_state); /*special texturing extension for text, using a given path (text rectangle) and texture*/ void visual_2d_texture_path_text(GF_VisualManager *visual, DrawableContext *txt_ctx, GF_Path *path, GF_Rect *object_bounds, GF_TextureHandler *txh, GF_TraverseState *tr_state); /*fill given rect with given color with given ctx transform and clipper (used for text hilighting only) @@ -123,7 +121,7 @@ void visual_2d_fill_rect(GF_VisualManager *visual, DrawableContext *ctx, GF_Rect /*extended version of above function to override texture transforms - needed for proper texturing of glyphs*/ void visual_2d_texture_path_extended(GF_VisualManager *visual, GF_Path *path, GF_TextureHandler *txh, struct _drawable_context *ctx, GF_Rect *orig_bounds, GF_Matrix2D *ext_mx, GF_TraverseState *tr_state); -void visual_2d_draw_path_extended(GF_VisualManager *visual, GF_Path *path, DrawableContext *ctx, GF_STENCIL brush, GF_STENCIL pen, GF_TraverseState *tr_state, GF_Rect *orig_bounds, GF_Matrix2D *ext_mx, Bool is_erase); +void visual_2d_draw_path_extended(GF_VisualManager *visual, GF_Path *path, DrawableContext *ctx, GF_EVGStencil * brush, GF_EVGStencil * pen, GF_TraverseState *tr_state, GF_Rect *orig_bounds, GF_Matrix2D *ext_mx, Bool is_erase); /*video overlay context*/ diff --git a/src/compositor/visual_manager_2d_draw.c b/src/compositor/visual_manager_2d_draw.c index ea6e351..0900afd 100644 --- a/src/compositor/visual_manager_2d_draw.c +++ b/src/compositor/visual_manager_2d_draw.c @@ -33,9 +33,8 @@ GF_Err visual_2d_init_raster(GF_VisualManager *visual) { - GF_Raster2D *raster = visual->compositor->rasterizer; if (!visual->raster_surface) { - visual->raster_surface = raster->surface_new(raster, visual->center_coords); + visual->raster_surface = gf_evg_surface_new(visual->center_coords); if (!visual->raster_surface) return GF_IO_ERR; } return visual->GetSurfaceAccess(visual); @@ -44,9 +43,6 @@ GF_Err visual_2d_init_raster(GF_VisualManager *visual) void visual_2d_release_raster(GF_VisualManager *visual) { if (visual->raster_surface) { - if (visual->compositor->rasterizer->surface_flush) - visual->compositor->rasterizer->surface_flush(visual->raster_surface); - visual->ReleaseSurfaceAccess(visual); } } @@ -59,20 +55,19 @@ void visual_2d_clear_surface(GF_VisualManager *visual, GF_IRect *rc, u32 BackCol #endif if (! visual->CheckAttached(visual) ) return; - if (!BackColor && !visual->offscreen) { - if (!visual->compositor->user || !(visual->compositor->user->init_flags & GF_TERM_WINDOW_TRANSPARENT)) { + if (!BackColor && !visual->offscreen && !visual->compositor->dyn_filter_mode) { + if ( !(visual->compositor->init_flags & GF_TERM_WINDOW_TRANSPARENT)) { BackColor = visual->compositor->back_color; } } - visual->compositor->rasterizer->surface_clear(visual->raster_surface, rc, BackColor); + gf_evg_surface_clear(visual->raster_surface, rc, BackColor); } static void draw_clipper(GF_VisualManager *visual, struct _drawable_context *ctx) { GF_PenSettings clipset; GF_Path *clippath, *cliper; - GF_Raster2D *raster = visual->compositor->rasterizer; if (ctx->flags & CTX_IS_BACKGROUND) return; @@ -83,30 +78,29 @@ static void draw_clipper(GF_VisualManager *visual, struct _drawable_context *ctx gf_path_add_rect_center(clippath, ctx->bi->unclip.x + ctx->bi->unclip.width/2, ctx->bi->unclip.y - ctx->bi->unclip.height/2, ctx->bi->unclip.width, ctx->bi->unclip.height); cliper = gf_path_get_outline(clippath, clipset); gf_path_del(clippath); - raster->surface_set_matrix(visual->raster_surface, NULL); - raster->surface_set_clipper(visual->raster_surface, NULL); - raster->surface_set_path(visual->raster_surface, cliper); - raster->stencil_set_brush_color(visual->raster_brush, 0xFF000000); - raster->surface_fill(visual->raster_surface, visual->raster_brush); + gf_evg_surface_set_matrix(visual->raster_surface, NULL); + gf_evg_surface_set_clipper(visual->raster_surface, NULL); + gf_evg_surface_set_path(visual->raster_surface, cliper); + gf_evg_stencil_set_brush_color(visual->raster_brush, 0xFF000000); + gf_evg_surface_fill(visual->raster_surface, visual->raster_brush); gf_path_del(cliper); } -static void visual_2d_fill_path(GF_VisualManager *visual, DrawableContext *ctx, GF_STENCIL stencil, GF_TraverseState *tr_state, Bool is_erase) +static void visual_2d_fill_path(GF_VisualManager *visual, DrawableContext *ctx, GF_EVGStencil * stencil, GF_TraverseState *tr_state, Bool is_erase) { Bool has_modif = GF_FALSE; GF_IRect clip; - GF_Raster2D *raster = visual->compositor->rasterizer; - /*background & direct drawing : use ctx clip*/ - if ((ctx->flags & CTX_IS_BACKGROUND) || tr_state->immediate_draw) { + /*direct drawing : use ctx clip*/ + if (tr_state->immediate_draw) { if (ctx->bi->clip.width && ctx->bi->clip.height) { GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Visual2D] Redrawing node %s [%s] (direct draw)\n", gf_node_get_log_name(ctx->drawable->node), gf_node_get_class_name(ctx->drawable->node) )); if (stencil) { - raster->surface_set_clipper(visual->raster_surface, &ctx->bi->clip); - raster->surface_fill(visual->raster_surface, stencil); + gf_evg_surface_set_clipper(visual->raster_surface, &ctx->bi->clip); + gf_evg_surface_fill(visual->raster_surface, stencil); } else { - raster->surface_clear(visual->raster_surface, &ctx->bi->clip, 0); + gf_evg_surface_clear(visual->raster_surface, &ctx->bi->clip, 0); } has_modif = GF_TRUE; @@ -125,10 +119,10 @@ static void visual_2d_fill_path(GF_VisualManager *visual, DrawableContext *ctx, if (clip.width && clip.height) { GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Visual2D] Redrawing node %s [%s] (indirect draw @ dirty rect idx %d)\n", gf_node_get_log_name(ctx->drawable->node), gf_node_get_class_name(ctx->drawable->node), i)); if (stencil) { - raster->surface_set_clipper(visual->raster_surface, &clip); - raster->surface_fill(visual->raster_surface, stencil); + gf_evg_surface_set_clipper(visual->raster_surface, &clip); + gf_evg_surface_fill(visual->raster_surface, stencil); } else { - raster->surface_clear(visual->raster_surface, &clip, 0); + gf_evg_surface_clear(visual->raster_surface, &clip, 0); } has_modif = 1; } @@ -147,26 +141,25 @@ static void visual_2d_fill_path(GF_VisualManager *visual, DrawableContext *ctx, } } -static void visual_2d_set_options(GF_Compositor *compositor, GF_SURFACE rend, Bool forText, Bool no_antialias) +static void visual_2d_set_options(GF_Compositor *compositor, GF_EVGSurface *rend, Bool forText, Bool no_antialias) { - GF_Raster2D *raster = compositor->rasterizer; if (no_antialias) { - raster->surface_set_raster_level(rend, GF_RASTER_HIGH_SPEED); + gf_evg_surface_set_raster_level(rend, GF_RASTER_HIGH_SPEED); } else { - switch (compositor->antiAlias) { + switch (compositor->aa) { case GF_ANTIALIAS_NONE: - raster->surface_set_raster_level(rend, GF_RASTER_HIGH_SPEED); + gf_evg_surface_set_raster_level(rend, GF_RASTER_HIGH_SPEED); break; case GF_ANTIALIAS_TEXT: if (forText) { - raster->surface_set_raster_level(rend, GF_RASTER_HIGH_QUALITY); + gf_evg_surface_set_raster_level(rend, GF_RASTER_HIGH_QUALITY); } else { - raster->surface_set_raster_level(rend, compositor->high_speed ? GF_RASTER_HIGH_QUALITY : GF_RASTER_MID); + gf_evg_surface_set_raster_level(rend, compositor->fast ? GF_RASTER_HIGH_QUALITY : GF_RASTER_MID); } break; case GF_ANTIALIAS_FULL: default: - raster->surface_set_raster_level(rend, GF_RASTER_HIGH_QUALITY); + gf_evg_surface_set_raster_level(rend, GF_RASTER_HIGH_QUALITY); break; } } @@ -228,9 +221,8 @@ static void visual_2d_get_texture_transform(GF_Node *__appear, GF_TextureHandler static void visual_2d_draw_gradient(GF_VisualManager *visual, GF_Path *path, GF_TextureHandler *txh, struct _drawable_context *ctx, GF_TraverseState *tr_state, GF_Matrix2D *ext_mx, GF_Rect *orig_bounds) { GF_Rect rc; - GF_STENCIL stencil; + GF_EVGStencil * stencil; GF_Matrix2D g_mat; - GF_Raster2D *raster = visual->compositor->rasterizer; if (!txh) txh = ctx->aspect.fill_texture; @@ -259,21 +251,21 @@ static void visual_2d_draw_gradient(GF_VisualManager *visual, GF_Path *path, GF_ gf_mx2d_add_matrix(&g_mat, &ctx->transform); - raster->stencil_set_matrix(stencil, &g_mat); - raster->stencil_set_color_matrix(stencil, ctx->col_mat); + gf_evg_stencil_set_matrix(stencil, &g_mat); + gf_evg_stencil_set_color_matrix(stencil, ctx->col_mat); /*MPEG-4/VRML context or no fill info*/ if (ctx->flags & CTX_HAS_APPEARANCE || !ctx->aspect.fill_color) - raster->stencil_set_alpha(stencil, 0xFF); + gf_evg_stencil_set_alpha(stencil, 0xFF); else - raster->stencil_set_alpha(stencil, GF_COL_A(ctx->aspect.fill_color) ); + gf_evg_stencil_set_alpha(stencil, GF_COL_A(ctx->aspect.fill_color) ); - raster->surface_set_matrix(visual->raster_surface, &ctx->transform); + gf_evg_surface_set_matrix(visual->raster_surface, &ctx->transform); txh->flags |= GF_SR_TEXTURE_USED; - raster->surface_set_path(visual->raster_surface, path); + gf_evg_surface_set_path(visual->raster_surface, path); visual_2d_fill_path(visual, ctx, stencil, tr_state, 0); - raster->surface_set_path(visual->raster_surface, NULL); + gf_evg_surface_set_path(visual->raster_surface, NULL); ctx->flags |= CTX_PATH_FILLED; } @@ -282,17 +274,15 @@ static void visual_2d_draw_gradient(GF_VisualManager *visual, GF_Path *path, GF_ void visual_2d_texture_path_text(GF_VisualManager *visual, DrawableContext *txt_ctx, GF_Path *path, GF_Rect *object_bounds, GF_TextureHandler *txh, GF_TraverseState *tr_state) { - GF_STENCIL stencil; + GF_EVGStencil * stencil; Fixed sS, sT; GF_Matrix2D gf_mx2d_txt; GF_Rect orig_rc; u8 alpha, r, g, b; GF_ColorMatrix cmat; - GF_Raster2D *raster; if (! visual->CheckAttached(visual) ) return; - raster = visual->compositor->rasterizer; stencil = gf_sc_texture_get_stencil(txh); if (!stencil) return; @@ -316,7 +306,7 @@ void visual_2d_texture_path_text(GF_VisualManager *visual, DrawableContext *txt_ gf_mx2d_add_matrix(&gf_mx2d_txt, &txt_ctx->transform); /*set path transform, except for background2D node which is directly build in the final coord system*/ - raster->stencil_set_matrix(stencil, &gf_mx2d_txt); + gf_evg_stencil_set_matrix(stencil, &gf_mx2d_txt); alpha = GF_COL_A(txt_ctx->aspect.fill_color); r = GF_COL_R(txt_ctx->aspect.fill_color); @@ -325,26 +315,26 @@ void visual_2d_texture_path_text(GF_VisualManager *visual, DrawableContext *txt_ /*if col do a cxmatrix*/ if (!r && !g && !b) { - raster->stencil_set_alpha(stencil, alpha); + gf_evg_stencil_set_alpha(stencil, alpha); } else { - raster->stencil_set_alpha(stencil, 0xFF); + gf_evg_stencil_set_alpha(stencil, 0xFF); memset(cmat.m, 0, sizeof(Fixed) * 20); cmat.m[4] = INT2FIX(r)/255; cmat.m[9] = INT2FIX(g)/255; cmat.m[14] = INT2FIX(b)/255; cmat.m[18] = INT2FIX(alpha)/255; cmat.identity = 0; - raster->stencil_set_color_matrix(stencil, &cmat); + gf_evg_stencil_set_color_matrix(stencil, &cmat); } - raster->surface_set_matrix(visual->raster_surface, &txt_ctx->transform); + gf_evg_surface_set_matrix(visual->raster_surface, &txt_ctx->transform); txh->flags |= GF_SR_TEXTURE_USED; /*push path*/ - raster->surface_set_path(visual->raster_surface, path); + gf_evg_surface_set_path(visual->raster_surface, path); visual_2d_fill_path(visual, txt_ctx, stencil, tr_state, 0); - raster->surface_set_path(visual->raster_surface, NULL); + gf_evg_surface_set_path(visual->raster_surface, NULL); txt_ctx->flags |= CTX_PATH_FILLED; } @@ -401,13 +391,13 @@ void visual_2d_flush_hybgl_canvas(GF_VisualManager *visual, GF_TextureHandler *t } //immediate mode flush, erase all canvas (we just completely flusged it) if (tr_state->immediate_draw && had_flush && !tr_state->immediate_for_defer) { - visual->compositor->rasterizer->surface_clear(visual->raster_surface, NULL, 0); + gf_evg_surface_clear(visual->raster_surface, NULL, 0); } //defer mode, erase all part of the canvas below us else if (txh) { visual_2d_draw_path_extended(visual, ctx->drawable->path, ctx, NULL, NULL, tr_state, NULL, NULL, GF_TRUE); } else { - visual->compositor->rasterizer->surface_clear(visual->raster_surface, &ctx->bi->clip, 0); + gf_evg_surface_clear(visual->raster_surface, &ctx->bi->clip, 0); } } ctx->bi->clip = rc; @@ -428,7 +418,7 @@ void visual_2d_flush_hybgl_canvas(GF_VisualManager *visual, GF_TextureHandler *t void visual_2d_texture_path_opengl_auto(GF_VisualManager *visual, GF_Path *path, GF_TextureHandler *txh, struct _drawable_context *ctx, GF_Rect *orig_bounds, GF_Matrix2D *ext_mx, GF_TraverseState *tr_state) { GF_Rect clipper; - GF_Matrix mx; + GF_Matrix mx, bck_mx; u32 prev_mode = tr_state->traversing_mode; u32 prev_type_3d = tr_state->visual->type_3d; @@ -437,6 +427,7 @@ void visual_2d_texture_path_opengl_auto(GF_VisualManager *visual, GF_Path *path, tr_state->visual->type_3d = 4; tr_state->appear = ctx->appear; if (ctx->col_mat) gf_cmx_copy(&tr_state->color_mat, ctx->col_mat);// + gf_mx_copy(bck_mx, tr_state->model_matrix); tr_state->traversing_mode=TRAVERSE_DRAW_3D; //in hybridGL the 2D camera is always setup as centered-coords, we have to insert flip+translation in case of top-left origin @@ -467,6 +458,7 @@ void visual_2d_texture_path_opengl_auto(GF_VisualManager *visual, GF_Path *path, ctx->flags |= CTX_PATH_FILLED; visual_3d_reset_clipper_2d(tr_state->visual); + gf_mx_copy(tr_state->model_matrix, bck_mx); } #endif @@ -475,18 +467,15 @@ void visual_2d_texture_path_extended(GF_VisualManager *visual, GF_Path *path, GF { Fixed sS, sT; u32 tx_tile; - GF_STENCIL tx_raster; + GF_EVGStencil * tx_raster; GF_Matrix2D mx_texture; GF_Rect orig_rc; - GF_Raster2D *raster; if (! visual->CheckAttached(visual) ) return; - raster = visual->compositor->rasterizer; - if (!txh) txh = ctx->aspect.fill_texture; if (!txh) return; - if (!txh->tx_io) { + if (!txh->tx_io && !txh->data) { gf_node_dirty_set(txh->owner, 0, 1); txh->needs_refresh=1; @@ -519,17 +508,7 @@ void visual_2d_texture_path_extended(GF_VisualManager *visual, GF_Path *path, GF gf_node_dirty_set(ctx->drawable->node, GF_SG_NODE_DIRTY, 1); } - if (compositor_texture_rectangles(visual, txh, &ctx->bi->clip, &ctx->bi->unclip, &src, &dst, NULL, NULL)) { - if (txh->stream && gf_mo_set_position(txh->stream, &src, &dst)) { - gf_mo_get_visual_info(txh->stream, &txh->width, &txh->height, &txh->stride, &txh->pixel_ar, &txh->pixelformat, &txh->is_flipped); - /*force dirty flag to get called again*/ - gf_node_dirty_set(ctx->drawable->node, GF_SG_NODE_DIRTY, 1); - gf_sc_next_frame_state(visual->compositor, GF_SC_DRAW_FRAME); - } - } - - - + compositor_texture_rectangles(visual, txh, &ctx->bi->clip, &ctx->bi->unclip, &src, &dst, NULL, NULL); return; } @@ -571,33 +550,33 @@ void visual_2d_texture_path_extended(GF_VisualManager *visual, GF_Path *path, GF if (!(ctx->flags & CTX_IS_BACKGROUND) ) gf_mx2d_add_matrix(&mx_texture, &ctx->transform); /*set path transform*/ - raster->stencil_set_matrix(tx_raster, &mx_texture); + gf_evg_stencil_set_matrix(tx_raster, &mx_texture); tx_tile = 0; if (txh->flags & GF_SR_TEXTURE_REPEAT_S) tx_tile |= GF_TEXTURE_REPEAT_S; if (txh->flags & GF_SR_TEXTURE_REPEAT_T) tx_tile |= GF_TEXTURE_REPEAT_T; if (ctx->flags & CTX_FLIPED_COORDS) - tx_tile |= GF_TEXTURE_FLIP; - raster->stencil_set_tiling(tx_raster, (GF_TextureTiling) tx_tile); + tx_tile |= GF_TEXTURE_FLIP_Y; + gf_evg_stencil_set_mapping(tx_raster, (GF_TextureMapFlags) tx_tile); if (!(ctx->flags & CTX_IS_BACKGROUND) ) { u8 a = GF_COL_A(ctx->aspect.fill_color); if (!a) a = GF_COL_A(ctx->aspect.line_color); /*texture alpha scale is the original material transparency, NOT the one after color transform*/ - raster->stencil_set_alpha(tx_raster, a ); - raster->stencil_set_color_matrix(tx_raster, ctx->col_mat); + gf_evg_stencil_set_alpha(tx_raster, a ); + gf_evg_stencil_set_color_matrix(tx_raster, ctx->col_mat); - raster->surface_set_matrix(visual->raster_surface, &ctx->transform); + gf_evg_surface_set_matrix(visual->raster_surface, &ctx->transform); } else { - raster->surface_set_matrix(visual->raster_surface, NULL); + gf_evg_surface_set_matrix(visual->raster_surface, NULL); } txh->flags |= GF_SR_TEXTURE_USED; /*push path & draw*/ - raster->surface_set_path(visual->raster_surface, path); + gf_evg_surface_set_path(visual->raster_surface, path); visual_2d_fill_path(visual, ctx, tx_raster, tr_state, 0); - raster->surface_set_path(visual->raster_surface, NULL); + gf_evg_surface_set_path(visual->raster_surface, NULL); @@ -628,17 +607,16 @@ void visual_2d_texture_path(GF_VisualManager *visual, GF_Path *path, struct _dra #define ADAPTATION_SIZE 0 -void visual_2d_draw_path_extended(GF_VisualManager *visual, GF_Path *path, DrawableContext *ctx, GF_STENCIL brush, GF_STENCIL pen, GF_TraverseState *tr_state, GF_Rect *orig_bounds, GF_Matrix2D *ext_mx, Bool is_erase) +void visual_2d_draw_path_extended(GF_VisualManager *visual, GF_Path *path, DrawableContext *ctx, GF_EVGStencil * brush, GF_EVGStencil * pen, GF_TraverseState *tr_state, GF_Rect *orig_bounds, GF_Matrix2D *ext_mx, Bool is_erase) { Bool dofill, dostrike; - GF_Raster2D *raster = visual->compositor->rasterizer; #ifdef SKIP_DRAW return; #endif if (! visual->CheckAttached(visual) ) return; if ((ctx->flags & CTX_PATH_FILLED) && (ctx->flags & CTX_PATH_STROKE) ) { - if (visual->compositor->draw_bvol) draw_clipper(visual, ctx); + if (visual->compositor->bvol) draw_clipper(visual, ctx); return; } @@ -650,7 +628,7 @@ void visual_2d_draw_path_extended(GF_VisualManager *visual, GF_Path *path, Drawa dofill = 1; if (!brush) { brush = visual->raster_brush; - raster->stencil_set_brush_color(brush, ctx->aspect.fill_color); + gf_evg_stencil_set_brush_color(brush, ctx->aspect.fill_color); } } @@ -663,20 +641,20 @@ void visual_2d_draw_path_extended(GF_VisualManager *visual, GF_Path *path, Drawa } /*set path transform, except for background2D node which is directly build in the final coord system*/ - raster->surface_set_matrix(visual->raster_surface, (ctx->flags & CTX_IS_BACKGROUND) ? NULL : &ctx->transform); + gf_evg_surface_set_matrix(visual->raster_surface, (ctx->flags & CTX_IS_BACKGROUND) ? NULL : &ctx->transform); /*fill path*/ if (dofill) { #if ADAPTATION_SIZE if ((ctx->bi->clip.widthbi->clip.heightsurface_clear(visual->raster_surface, &ctx->bi->clip, ctx->aspect.fill_color); + gf_evg_surface_clear(visual->raster_surface, &ctx->bi->clip, ctx->aspect.fill_color); } else #endif { /*push path*/ - raster->surface_set_path(visual->raster_surface, path); + gf_evg_surface_set_path(visual->raster_surface, path); visual_2d_fill_path(visual, ctx, brush, tr_state, is_erase); - raster->surface_set_path(visual->raster_surface, NULL); + gf_evg_surface_set_path(visual->raster_surface, NULL); } } @@ -690,7 +668,7 @@ void visual_2d_draw_path_extended(GF_VisualManager *visual, GF_Path *path, Drawa if (!pen) { pen = visual->raster_brush; - raster->stencil_set_brush_color(pen, ctx->aspect.line_color); + gf_evg_stencil_set_brush_color(pen, ctx->aspect.line_color); } si = drawable_get_strikeinfo(visual->compositor, ctx->drawable, &ctx->aspect, ctx->appear, path, ctx->flags, NULL); @@ -698,7 +676,7 @@ void visual_2d_draw_path_extended(GF_VisualManager *visual, GF_Path *path, Drawa if (ctx->aspect.line_texture) { visual_2d_texture_path_extended(visual, si->outline, ctx->aspect.line_texture, ctx, orig_bounds, ext_mx, tr_state); } else { - raster->surface_set_path(visual->raster_surface, si->outline); + gf_evg_surface_set_path(visual->raster_surface, si->outline); visual_2d_fill_path(visual, ctx, pen, tr_state, 0); } /*that's ugly, but we cannot cache path outline for IFS2D/ILS2D*/ @@ -711,10 +689,10 @@ void visual_2d_draw_path_extended(GF_VisualManager *visual, GF_Path *path, Drawa } } - if (visual->compositor->draw_bvol) draw_clipper(visual, ctx); + if (visual->compositor->bvol) draw_clipper(visual, ctx); } -void visual_2d_draw_path(GF_VisualManager *visual, GF_Path *path, DrawableContext *ctx, GF_STENCIL brush, GF_STENCIL pen, GF_TraverseState *tr_state) +void visual_2d_draw_path(GF_VisualManager *visual, GF_Path *path, DrawableContext *ctx, GF_EVGStencil * brush, GF_EVGStencil * pen, GF_TraverseState *tr_state) { visual_2d_draw_path_extended(visual, path, ctx, brush, pen, tr_state, NULL, NULL, GF_FALSE); } @@ -723,7 +701,6 @@ void visual_2d_fill_rect(GF_VisualManager *visual, DrawableContext *ctx, GF_Rect { GF_Path *path; GF_Rect *rc; - GF_Raster2D *raster = visual->compositor->rasterizer; #ifdef SKIP_DRAW return; #endif @@ -733,7 +710,7 @@ void visual_2d_fill_rect(GF_VisualManager *visual, DrawableContext *ctx, GF_Rect if (!color && !strike_color) return; if ((ctx->flags & CTX_PATH_FILLED) && (ctx->flags & CTX_PATH_STROKE) ) { - if (visual->compositor->draw_bvol) draw_clipper(visual, ctx); + if (visual->compositor->bvol) draw_clipper(visual, ctx); return; } @@ -741,11 +718,11 @@ void visual_2d_fill_rect(GF_VisualManager *visual, DrawableContext *ctx, GF_Rect visual_2d_set_options(visual->compositor, visual->raster_surface, 0, 1); if (_rc) { rc = _rc; - raster->surface_set_matrix(visual->raster_surface, &ctx->transform); + gf_evg_surface_set_matrix(visual->raster_surface, &ctx->transform); } else { rc = &ctx->bi->unclip; - raster->surface_set_matrix(visual->raster_surface, NULL); + gf_evg_surface_set_matrix(visual->raster_surface, NULL); } path = gf_path_new(); @@ -758,10 +735,10 @@ void visual_2d_fill_rect(GF_VisualManager *visual, DrawableContext *ctx, GF_Rect if (color) { /*push path*/ - raster->surface_set_path(visual->raster_surface, path); - raster->stencil_set_brush_color(visual->raster_brush, color); + gf_evg_surface_set_path(visual->raster_surface, path); + gf_evg_stencil_set_brush_color(visual->raster_brush, color); visual_2d_fill_path(visual, ctx, visual->raster_brush, tr_state, 0); - raster->surface_set_path(visual->raster_surface, NULL); + gf_evg_surface_set_path(visual->raster_surface, NULL); } if (strike_color) { GF_Path *outline; @@ -770,24 +747,24 @@ void visual_2d_fill_rect(GF_VisualManager *visual, DrawableContext *ctx, GF_Rect pen.width = 1; pen.join = GF_LINE_JOIN_BEVEL; pen.dash = GF_DASH_STYLE_DOT; - raster->stencil_set_brush_color(visual->raster_brush, strike_color); + gf_evg_stencil_set_brush_color(visual->raster_brush, strike_color); outline = gf_path_get_outline(path, pen); outline->flags &= ~GF_PATH_FILL_ZERO_NONZERO; - raster->surface_set_path(visual->raster_surface, outline); + gf_evg_surface_set_path(visual->raster_surface, outline); visual_2d_fill_path(visual, ctx, visual->raster_brush, tr_state, 0); - raster->surface_set_path(visual->raster_surface, NULL); + gf_evg_surface_set_path(visual->raster_surface, NULL); gf_path_del(outline); } gf_path_del(path); } +#if 0 //unused void visual_2d_fill_irect(GF_VisualManager *visual, GF_IRect *rc, u32 fill, u32 strike) { GF_Path *path; GF_Path *outline; GF_PenSettings pen; - GF_Raster2D *raster = visual->compositor->rasterizer; #ifdef SKIP_DRAW return; #endif @@ -800,10 +777,10 @@ void visual_2d_fill_irect(GF_VisualManager *visual, GF_IRect *rc, u32 fill, u32 /*no aa*/ visual_2d_set_options(visual->compositor, visual->raster_surface, 0, 1); - raster->surface_set_matrix(visual->raster_surface, NULL); + gf_evg_surface_set_matrix(visual->raster_surface, NULL); - raster->surface_set_raster_level(visual->raster_surface, GF_RASTER_HIGH_SPEED); - raster->surface_set_matrix(visual->raster_surface, NULL); + gf_evg_surface_set_raster_level(visual->raster_surface, GF_RASTER_HIGH_SPEED); + gf_evg_surface_set_matrix(visual->raster_surface, NULL); path = gf_path_new(); gf_path_add_move_to(path, INT2FIX(rc->x-1), INT2FIX(rc->y+2-rc->height)); @@ -813,13 +790,13 @@ void visual_2d_fill_irect(GF_VisualManager *visual, GF_IRect *rc, u32 fill, u32 gf_path_close(path); if (fill) { - raster->surface_set_path(visual->raster_surface, path); - raster->stencil_set_brush_color(visual->raster_brush, fill); + gf_evg_surface_set_path(visual->raster_surface, path); + gf_evg_stencil_set_brush_color(visual->raster_brush, fill); - raster->surface_set_clipper(visual->raster_surface, rc); - raster->surface_fill(visual->raster_surface, visual->raster_brush); + gf_evg_surface_set_clipper(visual->raster_surface, rc); + gf_evg_surface_fill(visual->raster_surface, visual->raster_brush); - raster->surface_set_path(visual->raster_surface, NULL); + gf_evg_surface_set_path(visual->raster_surface, NULL); } if (strike) { @@ -830,13 +807,13 @@ void visual_2d_fill_irect(GF_VisualManager *visual, GF_IRect *rc, u32 fill, u32 outline = gf_path_get_outline(path, pen); outline->flags &= ~GF_PATH_FILL_ZERO_NONZERO; - raster->surface_set_path(visual->raster_surface, outline); - raster->stencil_set_brush_color(visual->raster_brush, strike); + gf_evg_surface_set_path(visual->raster_surface, outline); + gf_evg_stencil_set_brush_color(visual->raster_brush, strike); - raster->surface_set_clipper(visual->raster_surface, rc); - raster->surface_fill(visual->raster_surface, visual->raster_brush); + gf_evg_surface_set_clipper(visual->raster_surface, rc); + gf_evg_surface_fill(visual->raster_surface, visual->raster_brush); - raster->surface_set_path(visual->raster_surface, NULL); + gf_evg_surface_set_path(visual->raster_surface, NULL); gf_path_del(outline); } gf_path_del(path); @@ -845,3 +822,5 @@ void visual_2d_fill_irect(GF_VisualManager *visual, GF_IRect *rc, u32 fill, u32 ra_union_rect(&visual->hybgl_drawn, rc); #endif } +#endif + diff --git a/src/compositor/visual_manager_3d.c b/src/compositor/visual_manager_3d.c index fc3045b..1c4df18 100644 --- a/src/compositor/visual_manager_3d.c +++ b/src/compositor/visual_manager_3d.c @@ -99,7 +99,7 @@ void drawable3d_check_focus_highlight(GF_Node *node, GF_TraverseState *tr_state, } else { bounds = orig_bounds; } - visual_3d_draw_bbox(tr_state, bounds); + visual_3d_draw_bbox(tr_state, bounds, GF_FALSE); } @@ -218,16 +218,18 @@ void visual_3d_viewpoint_change(GF_TraverseState *tr_state, GF_Node *vp, Bool an } } if (vp) { +#if 0 /*now check if vp is in pixel metrics. If not then: - either it's in the main scene, there's nothing to do - or it's in an inline, and the inline has been scaled if main scene is in pm: nothing to do*/ - if (0 && gf_sg_use_pixel_metrics(gf_node_get_graph(vp))) { + if ( gf_sg_use_pixel_metrics(gf_node_get_graph(vp))) { GF_Matrix mx; gf_mx_init(mx); gf_mx_add_scale(&mx, tr_state->min_hsize, tr_state->min_hsize, tr_state->min_hsize); gf_mx_apply_vec(&mx, &position); gf_mx_apply_vec(&mx, &local_center); } +#endif } /*default VP setup - this is undocumented in the spec. Default VP pos is (0, 0, 10) but not really nice in pixel metrics. We set z so that we see just the whole visual*/ @@ -240,46 +242,46 @@ void visual_3d_viewpoint_change(GF_TraverseState *tr_state, GF_Node *vp, Bool an } #ifdef GF_SR_USE_DEPTH /* 3D world calibration for stereoscopic screen */ - if (tr_state->visual->compositor->auto_calibration && tr_state->visual->compositor->video_out->view_distance) { - Fixed view_distance, disparity; + if (tr_state->visual->compositor->autocal && tr_state->visual->compositor->video_out->dispdist) { + Fixed dispdist, disparity; /*get view distance in pixels*/ - view_distance = tr_state->visual->compositor->video_out->view_distance * tr_state->visual->compositor->video_out->dpi_x; - view_distance = gf_divfix(view_distance , FLT2FIX(2.54f) ); + dispdist = tr_state->visual->compositor->video_out->dispdist * tr_state->visual->compositor->video_out->dpi_x; + dispdist = gf_divfix(dispdist , FLT2FIX(2.54f) ); disparity = INT2FIX(tr_state->visual->compositor->video_out->disparity); if (tr_state->visual->depth_vp_range) { - position.z = view_distance; - tr_state->camera->z_near = view_distance - tr_state->visual->depth_vp_position + tr_state->visual->depth_vp_range/2; - tr_state->camera->z_far = view_distance - tr_state->visual->depth_vp_position - tr_state->visual->depth_vp_range/2; + position.z = dispdist; + tr_state->camera->z_near = dispdist - tr_state->visual->depth_vp_position + tr_state->visual->depth_vp_range/2; + tr_state->camera->z_far = dispdist - tr_state->visual->depth_vp_position - tr_state->visual->depth_vp_range/2; } else if (disparity) { /*3,4 cm = 1,3386 inch -> pixels*/ Fixed half_interocular_dist_pixel = FLT2FIX(1.3386) * tr_state->visual->compositor->video_out->dpi_x; //frustum placed to match user's real viewpoint - position.z = view_distance; + position.z = dispdist; //near plane will match front side of the display's stereoscopic box //-> n=D- (dD)/(e+d) - tr_state->camera->z_near = view_distance - - gf_divfix( gf_mulfix(disparity,view_distance), (half_interocular_dist_pixel + disparity)); + tr_state->camera->z_near = dispdist - + gf_divfix( gf_mulfix(disparity,dispdist), (half_interocular_dist_pixel + disparity)); } - else if (tr_state->visual->compositor->display_depth) { - Fixed dist = INT2FIX(tr_state->visual->compositor->display_depth); + else if (tr_state->visual->compositor->dispdepth) { + dist = INT2FIX(tr_state->visual->compositor->dispdepth); if (dist<0) dist = INT2FIX(tr_state->visual->height); #if 1 - view_distance = gf_divfix(tr_state->visual->height, 2*gf_tan(fieldOfView/2) ); + dispdist = gf_divfix(tr_state->visual->height, 2*gf_tan(fieldOfView/2) ); #else - view_distance = gf_muldiv(view_distance, tr_state->visual->height, tr_state->visual->compositor->video_out->max_screen_height); - fieldOfView = 2*gf_atan2(tr_state->visual->height/2, view_distance); + dispdist = gf_muldiv(dispdist, tr_state->visual->height, tr_state->visual->compositor->video_out->max_screen_height); + fieldOfView = 2*gf_atan2(tr_state->visual->height/2, dispdist); #endif //frustum placed to match user's real viewpoint - position.z = view_distance; - tr_state->camera->z_near = view_distance - 2*dist/3; - tr_state->camera->z_far = view_distance + dist/2; + position.z = dispdist; + tr_state->camera->z_near = dispdist - 2*dist/3; + tr_state->camera->z_far = dispdist + dist/2; } } #endif @@ -331,12 +333,12 @@ void visual_3d_setup_projection(GF_TraverseState *tr_state, Bool is_layer) Fixed fov = GF_PI/4; #ifdef GF_SR_USE_DEPTH /* 3D world calibration for stereoscopic screen */ - if (tr_state->visual->compositor->auto_calibration && tr_state->visual->compositor->video_out->view_distance) { + if (tr_state->visual->compositor->autocal && tr_state->visual->compositor->video_out->dispdist) { /*get view distance in pixels*/ - Fixed view_distance = tr_state->visual->compositor->video_out->view_distance * tr_state->visual->compositor->video_out->dpi_x; - view_distance = gf_divfix(view_distance , FLT2FIX(2.54f) ); + Fixed dispdist = tr_state->visual->compositor->video_out->dispdist * tr_state->visual->compositor->video_out->dpi_x; + dispdist = gf_divfix(dispdist , FLT2FIX(2.54f) ); - fov = 2*gf_atan2( INT2FIX(tr_state->visual->compositor->video_out->max_screen_width)/2, view_distance); + fov = 2*gf_atan2( INT2FIX(tr_state->visual->compositor->video_out->max_screen_width)/2, dispdist); } #endif @@ -355,6 +357,7 @@ void visual_3d_setup_projection(GF_TraverseState *tr_state, Bool is_layer) /*scene not yet ready, force a recompute of world bounds at next frame*/ if (!is_layer && gf_sc_fit_world_to_screen(tr_state->visual->compositor) == 0) { tr_state->camera->had_viewpoint = 2; + gf_sc_invalidate(tr_state->visual->compositor, NULL); } } } else { @@ -363,12 +366,15 @@ void visual_3d_setup_projection(GF_TraverseState *tr_state, Bool is_layer) } } + + tr_state->camera_was_dirty = GF_FALSE; + if (tr_state->visual->nb_views>1) { s32 view_idx; Fixed interocular_dist_pixel; Fixed delta = 0; - interocular_dist_pixel = tr_state->visual->compositor->interoccular_distance + tr_state->visual->compositor->interoccular_offset; + interocular_dist_pixel = tr_state->visual->compositor->iod + tr_state->visual->compositor->interoccular_offset; view_idx = tr_state->visual->current_view; view_idx -= tr_state->visual->nb_views/2; @@ -376,11 +382,13 @@ void visual_3d_setup_projection(GF_TraverseState *tr_state, Bool is_layer) if (! (tr_state->visual->nb_views % 2)) { delta += interocular_dist_pixel/2; } - if (tr_state->visual->reverse_views) delta = - delta; + if (tr_state->visual->compositor->rview) delta = - delta; tr_state->camera->flags |= CAM_IS_DIRTY; - camera_update_stereo(tr_state->camera, &tr_state->transform, tr_state->visual->center_coords, delta, tr_state->visual->compositor->video_out->view_distance, tr_state->visual->compositor->focus_distance, tr_state->visual->camera_layout); + tr_state->camera_was_dirty = GF_TRUE; + camera_update_stereo(tr_state->camera, &tr_state->transform, tr_state->visual->center_coords, delta, tr_state->visual->compositor->video_out->dispdist, tr_state->visual->compositor->focdist, tr_state->visual->camlay); } else { + if (tr_state->camera->flags & CAM_IS_DIRTY) tr_state->camera_was_dirty = GF_TRUE; camera_update(tr_state->camera, &tr_state->transform, tr_state->visual->center_coords); } @@ -437,7 +445,9 @@ static void visual_3d_draw_background(GF_TraverseState *tr_state, u32 layer_type /*if composite visual, clear with alpha = 0*/ if (tr_state->visual==tr_state->visual->compositor->visual) { alpha = FIX_ONE; - if (tr_state->visual->compositor->user && (tr_state->visual->compositor->user->init_flags & GF_TERM_WINDOW_TRANSPARENT) ) { + if (tr_state->visual->compositor->init_flags & GF_TERM_WINDOW_TRANSPARENT) { + alpha = 0; + } else if (tr_state->visual->compositor->dyn_filter_mode) { alpha = 0; } } @@ -456,7 +466,7 @@ static void visual_3d_draw_background_on_axis(GF_TraverseState *tr_state, u32 la GF_Camera *cam = &tr_state->visual->camera; Fixed ar = gf_divfix(cam->width, cam->height); - tr_state->visual->camera_layout = 0; + tr_state->visual->camlay = 0; gf_mx_copy(proj, cam->projection); gf_mx_copy(model, cam->modelview); @@ -468,7 +478,7 @@ static void visual_3d_draw_background_on_axis(GF_TraverseState *tr_state, u32 la visual_3d_draw_background(tr_state, layer_type); - tr_state->visual->camera_layout = GF_3D_CAMERA_OFFAXIS; + tr_state->visual->camlay = GF_3D_CAMERA_OFFAXIS; gf_mx_copy(cam->projection, proj); gf_mx_copy(cam->modelview, model); @@ -480,7 +490,7 @@ void visual_3d_init_draw(GF_TraverseState *tr_state, u32 layer_type) #ifndef GPAC_DISABLE_VRML GF_Node *bindable; #endif - Bool off_axis_background = (tr_state->camera->is_3D && (tr_state->visual->camera_layout==GF_3D_CAMERA_OFFAXIS)) ? 1 : 0; + Bool off_axis_background = (tr_state->camera->is_3D && (tr_state->visual->camlay==GF_3D_CAMERA_OFFAXIS)) ? 1 : 0; /*if not in layer, traverse navigation node FIXME: we should update the nav info according to the world transform at the current viewpoint (vrml)*/ @@ -506,15 +516,15 @@ void visual_3d_init_draw(GF_TraverseState *tr_state, u32 layer_type) } else { tr_state->camera->navigation_flags = NAV_ANY | NAV_HEADLIGHT; if (tr_state->camera->is_3D) { - if (tr_state->visual->compositor->default_navigation_mode != GF_NAVIGATE_NONE) { - tr_state->camera->navigate_mode = tr_state->visual->compositor->default_navigation_mode; + if (tr_state->visual->compositor->nav != GF_NAVIGATE_NONE) { + tr_state->camera->navigate_mode = tr_state->visual->compositor->nav; } else { /*X3D is by default examine, VRML/MPEG4 is WALK*/ tr_state->camera->navigate_mode = (tr_state->visual->type_3d==3) ? GF_NAVIGATE_EXAMINE : GF_NAVIGATE_WALK; } #ifdef GF_SR_USE_DEPTH - /* if (tr_state->visual->compositor->display_depth) + /* if (tr_state->visual->compositor->dispdepth) tr_state->camera->navigate_mode = GF_NAVIGATE_NONE; */ #endif @@ -533,15 +543,18 @@ void visual_3d_init_draw(GF_TraverseState *tr_state, u32 layer_type) } /*animate current camera - if returns TRUE draw next frame*/ - if (camera_animate(tr_state->camera)) { + if (camera_animate(tr_state->camera, tr_state->visual->compositor)) { if (tr_state->visual->compositor->active_layer) gf_node_dirty_set(tr_state->visual->compositor->active_layer, 0, 1); - gf_sc_invalidate(tr_state->visual->compositor, NULL); + + tr_state->visual->compositor->force_next_frame_redraw = GF_TRUE; } /*turn off depth buffer in 2D*/ visual_3d_enable_depth_buffer(tr_state->visual, tr_state->camera->is_3D); + tr_state->camera->proj_vp = tr_state->camera->vp; + if ((tr_state->visual->autostereo_type==GF_3D_STEREO_SIDE) || (tr_state->visual->autostereo_type==GF_3D_STEREO_HEADSET)) { GF_Rect orig_vp = tr_state->camera->vp; Fixed vp_width = orig_vp.width; @@ -599,6 +612,7 @@ void visual_3d_init_draw(GF_TraverseState *tr_state, u32 layer_type) /*setup projection*/ visual_3d_setup_projection(tr_state, layer_type); + tr_state->camera->proj_vp = tr_state->camera->vp; tr_state->camera->vp = orig_vp; tr_state->camera->width = old_w; tr_state->camera->height = old_h; @@ -627,6 +641,7 @@ void visual_3d_init_draw(GF_TraverseState *tr_state, u32 layer_type) /*setup projection*/ visual_3d_setup_projection(tr_state, layer_type); + tr_state->camera->proj_vp = tr_state->camera->vp; tr_state->camera->vp = orig_vp; } else { visual_3d_set_viewport(tr_state->visual, tr_state->camera->vp); @@ -644,7 +659,7 @@ void visual_3d_init_draw(GF_TraverseState *tr_state, u32 layer_type) } /*regular background draw*/ - if (!tr_state->camera->is_3D || tr_state->visual->camera_layout != GF_3D_CAMERA_OFFAXIS) { + if (!tr_state->camera->is_3D || tr_state->visual->camlay != GF_3D_CAMERA_OFFAXIS) { visual_3d_draw_background(tr_state, layer_type); } @@ -660,12 +675,10 @@ void visual_3d_init_draw(GF_TraverseState *tr_state, u32 layer_type) static GFINLINE Bool visual_3d_has_alpha(GF_TraverseState *tr_state, GF_Node *geom) { - Bool is_mat3D; Drawable3D *stack; - - is_mat3D = 0; #ifndef GPAC_DISABLE_VRML + Bool is_mat3D = GF_FALSE; if (tr_state->appear) { GF_Node *mat = ((M_Appearance *)tr_state->appear)->material; if (mat) { @@ -679,7 +692,7 @@ static GFINLINE Bool visual_3d_has_alpha(GF_TraverseState *tr_state, GF_Node *ge #ifndef GPAC_DISABLE_X3D case TAG_X3D_Material: #endif - is_mat3D = 1; + is_mat3D = GF_TRUE; if ( ((M_Material *)mat)->transparency) return 1; break; case TAG_MPEG4_MaterialKey: @@ -711,7 +724,7 @@ static GFINLINE Bool visual_3d_has_alpha(GF_TraverseState *tr_state, GF_Node *ge void visual_3d_register_context(GF_TraverseState *tr_state, GF_Node *geometry) { u32 i, count; - DirectionalLightContext *nl, *ol; + DirectionalLightContext *ol; Drawable3DContext *ctx; Drawable3D *drawable; @@ -762,7 +775,7 @@ void visual_3d_register_context(GF_TraverseState *tr_state, GF_Node *geometry) i=0; while ((ol = (DirectionalLightContext*)gf_list_enum(tr_state->local_lights, &i))) { - nl = (DirectionalLightContext*)gf_malloc(sizeof(DirectionalLightContext)); + DirectionalLightContext *nl = (DirectionalLightContext*)gf_malloc(sizeof(DirectionalLightContext)); memcpy(nl, ol, sizeof(DirectionalLightContext)); gf_list_add(ctx->directional_lights, nl); } @@ -856,7 +869,7 @@ void visual_3d_flush_contexts(GF_VisualManager *visual, GF_TraverseState *tr_sta /*reset directional lights*/ tr_state->local_light_on = 0; for (i=gf_list_count(ctx->directional_lights); i>0; i--) { - DirectionalLightContext *dl = (DirectionalLightContext*)gf_list_get(ctx->directional_lights, i-1); + dl = (DirectionalLightContext*)gf_list_get(ctx->directional_lights, i-1); gf_node_traverse(dl->dlight, tr_state); gf_free(dl); } @@ -945,8 +958,11 @@ Bool visual_3d_draw_frame(GF_VisualManager *visual, GF_Node *root, GF_TraverseSt u32 time = gf_sys_clock(); #endif + if (visual->compositor->fbo_id) + compositor_3d_enable_fbo(visual->compositor, GF_TRUE); + visual_3d_setup(visual); - visual->glsl_flags = 0; + visual->active_glsl_flags = 0; /*setup our traversing state*/ visual_3d_setup_traversing_state(visual, tr_state); @@ -981,7 +997,7 @@ Bool visual_3d_draw_frame(GF_VisualManager *visual, GF_Node *root, GF_TraverseSt visual_3d_end_auto_stereo_pass(visual); } - if (is_root_visual) visual->compositor->reset_graphics = 0; + visual->compositor->reset_graphics = 0; } } else { @@ -989,6 +1005,9 @@ Bool visual_3d_draw_frame(GF_VisualManager *visual, GF_Node *root, GF_TraverseSt } GF_LOG(GF_LOG_DEBUG, GF_LOG_RTI, ("[RTI] Frame\t%d\t3D drawn in \t%d\tms\n", visual->compositor->frame_number, gf_sys_clock() - time)); + if (visual->compositor->fbo_id) + compositor_3d_enable_fbo(visual->compositor, GF_FALSE); + return 1; } @@ -1102,12 +1121,13 @@ void visual_3d_check_collisions(GF_TraverseState *tr_state, GF_Node *on_node, GF } else { /*camera displacement collision*/ if (tr_state->camera->collide_dist) { - if (tr_state->camera->collide_dist>=tr_state->camera->avatar_size.x) + if (tr_state->camera->collide_dist>=tr_state->camera->avatar_size.x) { GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Collision] Collision distance %g greater than avatar collide size %g\n", FIX2FLT(tr_state->camera->collide_dist), FIX2FLT(tr_state->camera->avatar_size.x))); - /*safety check due to precision, always stay below collide dist*/ - if (tr_state->camera->collide_dist>=tr_state->camera->avatar_size.x) tr_state->camera->collide_dist = tr_state->camera->avatar_size.x; - + /*safety check due to precision, always stay below collide dist*/ + tr_state->camera->collide_dist = tr_state->camera->avatar_size.x; + } + gf_vec_diff(n, tr_state->camera->position, tr_state->camera->collide_point); gf_vec_norm(&n); n = gf_vec_scale(n, tr_state->camera->avatar_size.x - tr_state->camera->collide_dist); @@ -1243,17 +1263,14 @@ Bool visual_3d_node_cull(GF_TraverseState *tr_state, GF_BBox *bbox, Bool skip_ne #endif } -void visual_3d_pick_node(GF_VisualManager *visual, GF_TraverseState *tr_state, GF_Event *ev, GF_ChildNodeItem *children) +Bool visual_3d_setup_ray(GF_VisualManager *visual, GF_TraverseState *tr_state, s32 ix, s32 iy) { Fixed in_x, in_y, x, y; SFVec3f start, end; SFVec4f res; - visual_3d_setup_traversing_state(visual, tr_state); - visual_3d_setup_projection(tr_state, 0); - - x = INT2FIX(ev->mouse.x); - y = INT2FIX(ev->mouse.y); + x = INT2FIX(ix); + y = INT2FIX(iy); /*if coordinate system is not centered, move to centered coord before applying camera transform because the (un)projection matrices include this transform*/ @@ -1271,6 +1288,7 @@ void visual_3d_pick_node(GF_VisualManager *visual, GF_TraverseState *tr_state, G y = gf_mulfix(y, scale); } +#if 0 start.z = visual->camera.z_near; end.z = visual->camera.z_far; if (!tr_state->camera->is_3D && !tr_state->pixel_metrics) { @@ -1280,6 +1298,7 @@ void visual_3d_pick_node(GF_VisualManager *visual, GF_TraverseState *tr_state, G start.x = end.x = x; start.y = end.y = y; } +#endif /*unproject to world coords*/ in_x = 2*x/ (s32) visual->width; @@ -1287,20 +1306,20 @@ void visual_3d_pick_node(GF_VisualManager *visual, GF_TraverseState *tr_state, G res.x = in_x; res.y = in_y; - res.z = -FIX_ONE; + res.z = -FIX_ONE/2; res.q = FIX_ONE; gf_mx_apply_vec_4x4(&visual->camera.unprojection, &res); - if (!res.q) return; + if (!res.q) return GF_FALSE; start.x = gf_divfix(res.x, res.q); start.y = gf_divfix(res.y, res.q); start.z = gf_divfix(res.z, res.q); res.x = in_x; res.y = in_y; - res.z = FIX_ONE; + res.z = FIX_ONE/2; res.q = FIX_ONE; gf_mx_apply_vec_4x4(&visual->camera.unprojection, &res); - if (!res.q) return; + if (!res.q) return GF_FALSE; end.x = gf_divfix(res.x, res.q); end.y = gf_divfix(res.y, res.q); end.z = gf_divfix(res.z, res.q); @@ -1309,13 +1328,24 @@ void visual_3d_pick_node(GF_VisualManager *visual, GF_TraverseState *tr_state, G /*also update hit info world ray in case we have a grabbed sensor with mouse off*/ visual->compositor->hit_world_ray = tr_state->ray; - GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Picking] cast ray\n\tOrigin %.4f %.4f %.4f - End %.4f %.4f %.4f\n\tDir %.4f %.4f %.4f\n", + return GF_TRUE; +} + +void visual_3d_pick_node(GF_VisualManager *visual, GF_TraverseState *tr_state, GF_Event *ev, GF_ChildNodeItem *children) +{ + + visual_3d_setup_traversing_state(visual, tr_state); + visual_3d_setup_projection(tr_state, 0); + + + if (!visual_3d_setup_ray(visual, tr_state, ev->mouse.x, ev->mouse.y)) + return; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Picking] cast ray Origin %.4f %.4f %.4f Direction %.4f %.4f %.4f\n", FIX2FLT(tr_state->ray.orig.x), FIX2FLT(tr_state->ray.orig.y), FIX2FLT(tr_state->ray.orig.z), - FIX2FLT(end.x), FIX2FLT(end.y), FIX2FLT(end.z), FIX2FLT(tr_state->ray.dir.x), FIX2FLT(tr_state->ray.dir.y), FIX2FLT(tr_state->ray.dir.z))); - visual->compositor->hit_square_dist = 0; visual->compositor->hit_node = NULL; gf_list_reset(visual->compositor->sensors); @@ -1664,7 +1694,7 @@ void visual_3d_draw_2d_with_aspect(Drawable *st, GF_TraverseState *tr_state, Dra if (!si) return; if (!si->mesh_outline) { - si->is_vectorial = asp->line_texture ? GF_TRUE : !tr_state->visual->compositor->raster_outlines; + si->is_vectorial = asp->line_texture ? GF_TRUE : !tr_state->visual->compositor->linegl; si->mesh_outline = new_mesh(); #ifdef GPAC_HAS_GLU if (si->is_vectorial) { @@ -1868,47 +1898,46 @@ Bool visual_3d_setup_texture(GF_TraverseState *tr_state, Fixed diffuse_alpha) #ifndef GPAC_DISABLE_VRML GF_TextureHandler *txh; tr_state->mesh_num_textures = 0; - if (!tr_state->appear) return 0; + if (!tr_state->appear) return GF_TRUE; gf_node_dirty_reset(tr_state->appear, 0); txh = gf_sc_texture_get_handler(((M_Appearance *)tr_state->appear)->texture); - if (txh) { - gf_sc_texture_set_blend_mode(txh, gf_sc_texture_is_transparent(txh) ? TX_MODULATE : TX_REPLACE); - tr_state->mesh_num_textures = gf_sc_texture_enable(txh, ((M_Appearance *)tr_state->appear)->textureTransform); - if (tr_state->mesh_num_textures) { - Fixed v[4]; - switch (txh->pixelformat) { - /*override diffuse color with full intensity, but keep material alpha (cf VRML lighting)*/ - case GF_PIXEL_RGB_24: - if (tr_state->visual->has_material_2d) { - SFColor c; - c.red = c.green = c.blue = FIX_ONE; - visual_3d_set_material_2d(tr_state->visual, c, diffuse_alpha); - } else { - v[0] = v[1] = v[2] = FIX_ONE; - v[3] = diffuse_alpha; - visual_3d_set_material(tr_state->visual, V3D_MATERIAL_DIFFUSE, v); - } - break; - /*override diffuse color AND material alpha (cf VRML lighting)*/ - case GF_PIXEL_RGBA: - if (!tr_state->visual->has_material_2d) { - v[0] = v[1] = v[2] = v[3] = FIX_ONE; - visual_3d_set_material(tr_state->visual, V3D_MATERIAL_DIFFUSE, v); - } - tr_state->mesh_is_transparent = 1; - break; - /* case GF_PIXEL_GREYSCALE: - tr_state->mesh_num_textures = 2; - break; - */ + //no texture, return TRUE (eg draw) + if (!txh) + return GF_TRUE; + + gf_sc_texture_set_blend_mode(txh, gf_sc_texture_is_transparent(txh) ? TX_MODULATE : TX_REPLACE); + tr_state->mesh_num_textures = gf_sc_texture_enable(txh, ((M_Appearance *)tr_state->appear)->textureTransform); + if (tr_state->mesh_num_textures) { + Fixed v[4]; + switch (txh->pixelformat) { + /*override diffuse color with full intensity, but keep material alpha (cf VRML lighting)*/ + case GF_PIXEL_RGB: + if (tr_state->visual->has_material_2d) { + SFColor c; + c.red = c.green = c.blue = FIX_ONE; + visual_3d_set_material_2d(tr_state->visual, c, diffuse_alpha); + } else { + v[0] = v[1] = v[2] = FIX_ONE; + v[3] = diffuse_alpha; + visual_3d_set_material(tr_state->visual, V3D_MATERIAL_DIFFUSE, v); } + break; + /*override diffuse color AND material alpha (cf VRML lighting)*/ + case GF_PIXEL_RGBA: + if (!tr_state->visual->has_material_2d) { + v[0] = v[1] = v[2] = v[3] = FIX_ONE; + visual_3d_set_material(tr_state->visual, V3D_MATERIAL_DIFFUSE, v); + } + tr_state->mesh_is_transparent = 1; + break; } - return tr_state->mesh_num_textures; } + return tr_state->mesh_num_textures ? GF_TRUE : GF_FALSE; +#else + return GF_TRUE; #endif /*GPAC_DISABLE_VRML*/ - return 0; } void visual_3d_disable_texture(GF_TraverseState *tr_state) @@ -1927,7 +1956,7 @@ Bool visual_3d_setup_appearance(GF_TraverseState *tr_state) /*setup material and check if 100% transparent - in which case don't draw*/ if (!visual_3d_setup_material(tr_state, 0, &diff_a)) return 0; /*setup texture*/ - visual_3d_setup_texture(tr_state, diff_a); + if (! visual_3d_setup_texture(tr_state, diff_a)) return 0; return 1; } @@ -1977,8 +2006,8 @@ void visual_3d_enable_headlight(GF_VisualManager *visual, Bool bOn, GF_Camera *c void visual_3d_set_material_2d(GF_VisualManager *visual, SFColor col, Fixed alpha) { - visual->has_material_2d = alpha ? 1 : 0; - visual->has_material=0; + visual->has_material_2d = alpha ? GF_TRUE : GF_FALSE; + visual->has_material = 0; if (visual->has_material_2d) { visual->mat_2d.red = col.red; visual->mat_2d.green = col.green; @@ -1991,13 +2020,13 @@ void visual_3d_set_material_2d(GF_VisualManager *visual, SFColor col, Fixed alph void visual_3d_set_material_2d_argb(GF_VisualManager *visual, u32 col) { u32 a = GF_COL_A(col); - visual->has_material_2d = a ? 1 : 0; - visual->has_material=0; + visual->has_material_2d = a ? GF_TRUE : GF_FALSE; + visual->has_material = 0; if (visual->has_material_2d) { visual->mat_2d.red = INT2FIX( GF_COL_R(col) ) / 255; visual->mat_2d.green = INT2FIX( GF_COL_G(col) ) / 255; visual->mat_2d.blue = INT2FIX( GF_COL_B(col) ) / 255; - visual->mat_2d.alpha = INT2FIX( a ) / 255;; + visual->mat_2d.alpha = INT2FIX( a ) / 255; } } @@ -2060,6 +2089,7 @@ void visual_3d_set_texture_matrix(GF_VisualManager *visual, GF_Matrix *mx) if (mx) gf_mx_copy(visual->tx_matrix, *mx); } + void visual_3d_has_inactive_light(GF_VisualManager *visual) { visual->has_inactive_lights = GF_TRUE; diff --git a/src/compositor/visual_manager_3d.h b/src/compositor/visual_manager_3d.h index a909cfc..7d35593 100644 --- a/src/compositor/visual_manager_3d.h +++ b/src/compositor/visual_manager_3d.h @@ -41,6 +41,8 @@ /*draw frame, performing collisions, camera displacement and drawing*/ Bool visual_3d_draw_frame(GF_VisualManager *visual, GF_Node *root, GF_TraverseState *tr_state, Bool is_root_visual); +Bool visual_3d_setup_ray(GF_VisualManager *visual, GF_TraverseState *tr_state, s32 ix, s32 iy); + /*traverse the scene and picks the node under the current ray, if any*/ void visual_3d_pick_node(GF_VisualManager *visual, GF_TraverseState *tr_state, GF_Event *ev, GF_ChildNodeItem *children); @@ -84,7 +86,7 @@ void visual_3d_set_2d_strike(GF_TraverseState *tr_state, DrawAspect2D *asp); /*sets 3D material. Returns false is object is not visible due to appearance - exported for text drawing*/ Bool visual_3d_setup_appearance(GF_TraverseState *tr_state); -/*sets 3D texture. Returns true if a texture is found and successfully bound +/*sets 3D texture. Returns true if a texture is found and successfully bound or no texture found, FALSE otherwise (texture failure) - exported for text drawing*/ Bool visual_3d_setup_texture(GF_TraverseState *tr_state, Fixed diffuse_alpha); /*disables texture @@ -224,7 +226,6 @@ void visual_3d_set_texture_matrix(GF_VisualManager *visual, GF_Matrix *mx); void visual_3d_projection_matrix_modified(GF_VisualManager *visual); - /*setup viewport (vp: top-left, width, height)*/ void visual_3d_set_viewport(GF_VisualManager *visual, GF_Rect vp); @@ -301,7 +302,7 @@ void visual_3d_mesh_hatch(GF_TraverseState *tr_state, GF_Mesh *mesh, u32 hatchSt #endif -void visual_3d_draw_bbox(GF_TraverseState *tr_state, GF_BBox *box); +void visual_3d_draw_bbox(GF_TraverseState *tr_state, GF_BBox *box, Bool is_debug); GF_Err visual_3d_init_autostereo(GF_VisualManager *visual); @@ -310,6 +311,8 @@ void visual_3d_end_auto_stereo_pass(GF_VisualManager *visual); void visual_3d_init_shaders(GF_VisualManager *visual); void visual_3d_reset_graphics(GF_VisualManager *visual); +void visual_3d_clean_state(GF_VisualManager *visual); + #endif /*GPAC_DISABLE_3D*/ diff --git a/src/compositor/visual_manager_3d_gl.c b/src/compositor/visual_manager_3d_gl.c index 2b964e1..435cfbf 100644 --- a/src/compositor/visual_manager_3d_gl.c +++ b/src/compositor/visual_manager_3d_gl.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / Scene Compositor sub-project @@ -65,89 +65,9 @@ #undef GL_MAX_CLIP_PLANES #endif - #define CHECK_GL_EXT(name) ((strstr(ext, name) != NULL) ? 1 : 0) -#ifdef LOAD_GL_1_3 - -GLDECL_STATIC(glActiveTexture); -GLDECL_STATIC(glClientActiveTexture); -GLDECL_STATIC(glBlendEquation); - -#endif //LOAD_GL_1_3 - -#ifdef LOAD_GL_1_4 - -GLDECL_STATIC(glPointParameterf); -GLDECL_STATIC(glPointParameterfv); - -#endif //LOAD_GL_1_4 - - -#ifdef LOAD_GL_1_5 -GLDECL_STATIC(glGenBuffers); -GLDECL_STATIC(glDeleteBuffers); -GLDECL_STATIC(glBindBuffer); -GLDECL_STATIC(glBufferData); -GLDECL_STATIC(glBufferSubData); -GLDECL_STATIC(glMapBuffer); -GLDECL_STATIC(glUnmapBuffer); -#endif //LOAD_GL_1_5 - -#ifdef LOAD_GL_2_0 - -GLDECL_STATIC(glCreateProgram); -GLDECL_STATIC(glDeleteProgram); -GLDECL_STATIC(glLinkProgram); -GLDECL_STATIC(glUseProgram); -GLDECL_STATIC(glCreateShader); -GLDECL_STATIC(glDeleteShader); -GLDECL_STATIC(glShaderSource); -GLDECL_STATIC(glCompileShader); -GLDECL_STATIC(glAttachShader); -GLDECL_STATIC(glDetachShader); -GLDECL_STATIC(glGetShaderiv); -GLDECL_STATIC(glGetInfoLogARB); -GLDECL_STATIC(glGetUniformLocation); -GLDECL_STATIC(glUniform1f); -GLDECL_STATIC(glUniform2f); -GLDECL_STATIC(glUniform3f); -GLDECL_STATIC(glUniform4f); -GLDECL_STATIC(glUniform1i); -GLDECL_STATIC(glUniform2i); -GLDECL_STATIC(glUniform3i); -GLDECL_STATIC(glUniform4i); -GLDECL_STATIC(glUniform1fv); -GLDECL_STATIC(glUniform2fv); -GLDECL_STATIC(glUniform3fv); -GLDECL_STATIC(glUniform4fv); -GLDECL_STATIC(glUniform1iv); -GLDECL_STATIC(glUniform2iv); -GLDECL_STATIC(glUniform3iv); -GLDECL_STATIC(glUniform4iv); -GLDECL_STATIC(glUniformMatrix2fv); -GLDECL_STATIC(glUniformMatrix3fv); -GLDECL_STATIC(glUniformMatrix4fv); -GLDECL_STATIC(glUniformMatrix2x3fv); -GLDECL_STATIC(glUniformMatrix3x2fv); -GLDECL_STATIC(glUniformMatrix2x4fv); -GLDECL_STATIC(glUniformMatrix4x2fv); -GLDECL_STATIC(glUniformMatrix3x4fv); -GLDECL_STATIC(glUniformMatrix4x3fv); -GLDECL_STATIC(glGetProgramiv); -GLDECL_STATIC(glGetProgramInfoLog); -GLDECL_STATIC(glGetAttribLocation); - -#ifndef GPAC_ANDROID -GLDECL_STATIC(glEnableVertexAttribArray); -GLDECL_STATIC(glDisableVertexAttribArray); -GLDECL_STATIC(glVertexAttribPointer); -GLDECL_STATIC(glVertexAttribIPointer); -#endif - -#endif //LOAD_GL_2_0 - void gf_sc_load_opengl_extensions(GF_Compositor *compositor, Bool has_gl_context) { #ifdef GPAC_USE_TINYGL @@ -157,13 +77,13 @@ void gf_sc_load_opengl_extensions(GF_Compositor *compositor, Bool has_gl_context #else const char *ext = NULL; - if (compositor->visual->type_3d || compositor->hybrid_opengl) + if (has_gl_context) ext = (const char *) glGetString(GL_EXTENSIONS); - if (!ext) ext = gf_cfg_get_key(compositor->user->config, "Compositor", "OpenGLExtensions"); + if (!ext) ext = gf_opts_get_key("core", "glext"); /*store OGL extension to config for app usage*/ - else if (gf_cfg_get_key(compositor->user->config, "Compositor", "OpenGLExtensions")==NULL) - gf_cfg_set_key(compositor->user->config, "Compositor", "OpenGLExtensions", ext ? ext : "None"); + else if (gf_opts_get_key("core", "glext")==NULL) + gf_opts_set_key("core", "glext", ext ? ext : "None"); if (!ext) return; @@ -177,6 +97,11 @@ void gf_sc_load_opengl_extensions(GF_Compositor *compositor, Bool has_gl_context compositor->gl_caps.abgr_texture = 1; if (CHECK_GL_EXT("GL_EXT_bgra")) compositor->gl_caps.bgra_texture = 1; + if (CHECK_GL_EXT("GL_EXT_framebuffer_object") || CHECK_GL_EXT("GL_ARB_framebuffer_object")) + compositor->gl_caps.fbo = 1; + if (CHECK_GL_EXT("GL_ARB_texture_non_power_of_two")) + compositor->gl_caps.npot = 1; + if (CHECK_GL_EXT("GL_ARB_point_parameters")) { compositor->gl_caps.point_sprite = 1; @@ -197,8 +122,6 @@ void gf_sc_load_opengl_extensions(GF_Compositor *compositor, Bool has_gl_context #ifndef GPAC_USE_GLES1X if (CHECK_GL_EXT("GL_EXT_texture_rectangle") || CHECK_GL_EXT("GL_NV_texture_rectangle")) { compositor->gl_caps.rect_texture = 1; - if (CHECK_GL_EXT("GL_MESA_ycbcr_texture")) compositor->gl_caps.yuv_texture = YCBCR_MESA; - else if (CHECK_GL_EXT("GL_APPLE_ycbcr_422")) compositor->gl_caps.yuv_texture = YCBCR_422_APPLE; } #endif @@ -212,138 +135,55 @@ void gf_sc_load_opengl_extensions(GF_Compositor *compositor, Bool has_gl_context /*we have a GL context, init the rest (proc addresses & co)*/ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &compositor->gl_caps.max_texture_size); -#ifdef LOAD_GL_1_3 - if (CHECK_GL_EXT("GL_ARB_multitexture")) { - GET_GLFUN(glActiveTexture); - GET_GLFUN(glClientActiveTexture); - } - GET_GLFUN(glBlendEquation); -#endif - -#ifdef LOAD_GL_1_4 - if (compositor->gl_caps.point_sprite) { - GET_GLFUN(glPointParameterf); - GET_GLFUN(glPointParameterfv); - } -#endif - -#ifdef LOAD_GL_1_5 - if (compositor->gl_caps.vbo) { - GET_GLFUN(glGenBuffers); - GET_GLFUN(glDeleteBuffers); - GET_GLFUN(glBindBuffer); - GET_GLFUN(glBufferData); - GET_GLFUN(glBufferSubData); - } if (CHECK_GL_EXT("GL_ARB_pixel_buffer_object")) { - GET_GLFUN(glMapBuffer); - GET_GLFUN(glUnmapBuffer); - compositor->gl_caps.pbo=1; } -#endif - - - #ifdef LOAD_GL_2_0 - GET_GLFUN(glCreateProgram); - if (glCreateProgram != NULL) { - GET_GLFUN(glDeleteProgram); - GET_GLFUN(glLinkProgram); - GET_GLFUN(glUseProgram); - GET_GLFUN(glCreateShader); - GET_GLFUN(glDeleteShader); - GET_GLFUN(glShaderSource); - GET_GLFUN(glCompileShader); - GET_GLFUN(glAttachShader); - GET_GLFUN(glDetachShader); - GET_GLFUN(glGetShaderiv); - GET_GLFUN(glGetInfoLogARB); - GET_GLFUN(glGetUniformLocation); - GET_GLFUN(glUniform1f); - GET_GLFUN(glUniform2f); - GET_GLFUN(glUniform3f); - GET_GLFUN(glUniform4f); - GET_GLFUN(glUniform1i); - GET_GLFUN(glUniform2i); - GET_GLFUN(glUniform3i); - GET_GLFUN(glUniform4i); - GET_GLFUN(glUniform1fv); - GET_GLFUN(glUniform2fv); - GET_GLFUN(glUniform3fv); - GET_GLFUN(glUniform4fv); - GET_GLFUN(glUniform1iv); - GET_GLFUN(glUniform2iv); - GET_GLFUN(glUniform3iv); - GET_GLFUN(glUniform4iv); - GET_GLFUN(glUniformMatrix2fv); - GET_GLFUN(glUniformMatrix3fv); - GET_GLFUN(glUniformMatrix4fv); - GET_GLFUN(glUniformMatrix2x3fv); - GET_GLFUN(glUniformMatrix3x2fv); - GET_GLFUN(glUniformMatrix2x4fv); - GET_GLFUN(glUniformMatrix4x2fv); - GET_GLFUN(glUniformMatrix3x4fv); - GET_GLFUN(glUniformMatrix4x3fv); - GET_GLFUN(glGetProgramiv); - GET_GLFUN(glGetProgramInfoLog); - compositor->gl_caps.has_shaders = 1; -#ifndef GPAC_ANDROID - GET_GLFUN(glEnableVertexAttribArray); - GET_GLFUN(glDisableVertexAttribArray); - GET_GLFUN(glVertexAttribPointer); - GET_GLFUN(glVertexAttribIPointer); - GET_GLFUN(glGetAttribLocation); - - if (glGetAttribLocation != NULL) { - compositor->shader_only_mode = GF_TRUE; +#ifndef GPAC_CONFIG_ANDROID + if (glGetAttribLocation == NULL) { + compositor->shader_mode_disabled = GF_TRUE; } #endif - } else { GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] OpenGL shaders not supported\n")); } -#endif //LOAD_GL_FUNCS +#endif //LOAD_GL_2_0 #endif //GPAC_USE_TINYGL #ifdef GPAC_USE_GLES2 compositor->gl_caps.has_shaders = GF_TRUE; - compositor->shader_only_mode = GF_TRUE; #elif defined (GL_VERSION_2_0) compositor->gl_caps.has_shaders = GF_TRUE; - compositor->shader_only_mode = GF_TRUE; #endif - - if (!compositor->gl_caps.has_shaders && (compositor->visual->autostereo_type > GF_3D_STEREO_LAST_SINGLE_BUFFER)) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] OpenGL shaders not supported - disabling auto-stereo output\n")); - compositor->visual->nb_views=1; - compositor->visual->autostereo_type = GF_3D_STEREO_NONE; - compositor->visual->camera_layout = GF_3D_CAMERA_STRAIGHT; + if (!compositor->gl_caps.has_shaders) { +#if !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) + compositor->shader_mode_disabled = GF_TRUE; +#endif + if (compositor->visual->autostereo_type > GF_3D_STEREO_LAST_SINGLE_BUFFER) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] OpenGL shaders not supported - disabling auto-stereo output\n")); + compositor->visual->nb_views=1; + compositor->visual->autostereo_type = GF_3D_STEREO_NONE; + compositor->visual->camlay = GF_3D_CAMERA_STRAIGHT; + } } #if !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) - if (compositor->shader_only_mode) { - const char *shader = gf_cfg_get_key(compositor->user->config, "Compositor", "VertexShader"); - FILE *t = shader ? gf_fopen(shader, "rt") : NULL; - if (!t) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] GLES Vertex shader not found, disabling shaders\n")); - compositor->shader_only_mode = GF_FALSE; + if (!compositor->shader_mode_disabled && compositor->vertshader && compositor->fragshader) { + if (!gf_file_exists(compositor->vertshader)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] GLES Vertex shader %s not found, disabling shaders\n", compositor->vertshader)); + compositor->shader_mode_disabled = GF_TRUE; } - if (t) gf_fclose(t); - shader = gf_cfg_get_key(compositor->user->config, "Compositor", "FragmentShader"); - t = shader ? gf_fopen(shader, "rt") : NULL; - if (!t) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] GLES Fragment shader not found, disabling shaders\n")); - compositor->shader_only_mode = GF_FALSE; + if (!gf_file_exists(compositor->fragshader)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] GLES Fragment shader %s not found, disabling shaders\n", compositor->fragshader)); + compositor->shader_mode_disabled = GF_TRUE; } - if (t) gf_fclose(t); } #endif } @@ -512,7 +352,7 @@ static char *glsl_view_8VAlio = GLSL_PREFIX "\ /** parses (glShaderSource) and compiles (glCompileShader) shader source - \return GF_TRUE if successful +\return GF_TRUE if successful */ Bool visual_3d_compile_shader(GF_SHADERID shader_id, const char *name, const char *source) { @@ -546,44 +386,34 @@ Bool visual_3d_compile_shader(GF_SHADERID shader_id, const char *name, const cha } static GF_SHADERID visual_3d_shader_from_source_file(const char *src_path, u32 shader_type) { - FILE *src = gf_fopen(src_path, "rt"); GF_SHADERID shader = 0; - if (src) { - size_t size; - char *shader_src; - gf_fseek(src, 0, SEEK_END); - size = (size_t) gf_ftell(src); - gf_fseek(src, 0, SEEK_SET); - shader_src = gf_malloc(sizeof(char)*(size+1)); - size = fread(shader_src, 1, size, src); - gf_fclose(src); - if (size != (size_t) -1) { - shader_src[size]=0; - shader = glCreateShader(shader_type); - if (visual_3d_compile_shader(shader, (shader_type == GL_FRAGMENT_SHADER) ? "fragment" : "vertex", shader_src)==GF_FALSE) { - glDeleteShader(shader); - shader = 0; - } + u32 size; + char *shader_src; + + GF_Err e = gf_file_load_data(src_path, (u8 **) &shader_src, &size); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to open shader file %s: %s\n", src_path, gf_error_to_string(e) )); + } else { + shader = glCreateShader(shader_type); + if (visual_3d_compile_shader(shader, (shader_type == GL_FRAGMENT_SHADER) ? "fragment" : "vertex", shader_src)==GF_FALSE) { + glDeleteShader(shader); + shader = 0; } gf_free(shader_src); - } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to open shader file %s\n", src_path)); } return shader; } -#ifdef GPAC_IPHONE +#ifdef GPAC_CONFIG_IOS #include #include #endif -static GF_SHADERID visual_3d_shader_with_flags(const char *src_path, u32 shader_type, u32 flags) { +static GF_SHADERID visual_3d_shader_with_flags(const char *src_path, u32 shader_type, u32 flags, u32 pixfmt) { - FILE *src = gf_fopen(src_path, "rt"); GF_SHADERID shader = 0; - char *shader_src; - char *defs, *tmp, szKey[100]; + char *defs, szKey[100]; size_t str_size; defs = (char *) gf_strdup(GLES_VERSION_STRING); @@ -629,68 +459,45 @@ static GF_SHADERID visual_3d_shader_with_flags(const char *src_path, u32 shader_ } } - if (src) { - size_t size; - gf_fseek(src, 0, SEEK_END); - size = (size_t) gf_ftell(src); - gf_fseek(src, 0, SEEK_SET); - shader_src = gf_malloc(sizeof(char)*(size+1)); - size = fread(shader_src, 1, size, src); - tmp = (char *) gf_malloc(sizeof(char)*(size+str_size+2)); - strcpy(tmp, defs); - strncat(tmp, shader_src, (size)); - gf_fclose(src); - if (size != (size_t) -1) { - tmp[size+str_size]=0; - shader = glCreateShader(shader_type); - if (visual_3d_compile_shader(shader, (shader_type == GL_FRAGMENT_SHADER) ? "fragment" : "vertex", tmp)==GF_FALSE) { - glDeleteShader(shader); - shader = 0; - } - } - gf_free(shader_src); - gf_free(tmp); - gf_free(defs); + char *shader_src; + u32 size; + GF_Err e = gf_file_load_data(src_path ,(u8 **) &shader_src, &size); + + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to open shader file %s: %s\n", src_path, gf_error_to_string(e))); } else { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to open shader file %s\n", src_path)); - } - return shader; -} + char *final_src = NULL; -static void visual_3d_set_tx_planes(GF_VisualManager *visual) -{ - GLint i,j, loc; + gf_dynstrcat(&final_src, defs, NULL); - for(i=0; iglsl_programs[i]); - if (i & GF_GL_IS_YUV) { - for (j=0; j<3; j++) { - const char *txname = (j==0) ? "y_plane" : (j==1) ? "u_plane" : "v_plane"; - loc = glGetUniformLocation(visual->glsl_programs[i], txname); - if (loc == -1) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to locate texture %s in YUV shader\n", txname)); - continue; - } - glUniform1i(loc, j); - } - } else if (i & GF_GL_IS_ExternalOES) { - loc = glGetUniformLocation(visual->glsl_programs[i], "imgOES"); - if (loc == -1) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to locate texture imgOES in ExternalOES shader\n")); + //add texture code + gf_gl_txw_insert_fragment_shader(pixfmt, "maintx", &final_src); + + //append the rest + if (sep) { + sep[0] = 'v'; + gf_dynstrcat(&final_src, sep, "\n"); } - glUniform1i(loc, 0); } else { - loc = glGetUniformLocation(visual->glsl_programs[i], "img"); - if (loc == -1) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to locate texture img in RGB shader\n")); - } - glUniform1i(loc, 0); + gf_dynstrcat(&final_src, shader_src, "\n"); + } + shader = glCreateShader(shader_type); + if (visual_3d_compile_shader(shader, (shader_type == GL_FRAGMENT_SHADER) ? "fragment" : "vertex", final_src)==GF_FALSE) { + glDeleteShader(shader); + shader = 0; } + + gf_free(shader_src); + gf_free(final_src); + gf_free(defs); } + return shader; } void visual_3d_init_stereo_shaders(GF_VisualManager *visual) @@ -732,14 +539,9 @@ void visual_3d_init_stereo_shaders(GF_VisualManager *visual) break; case GF_3D_STEREO_CUSTOM: - { - const char *sOpt = gf_cfg_get_key(visual->compositor->user->config, "Compositor", "InterleaverShader"); - if (sOpt) { - visual->autostereo_glsl_fragment = visual_3d_shader_from_source_file(sOpt, GL_FRAGMENT_SHADER); - if (visual->autostereo_glsl_fragment) res = GF_TRUE; - } - } - break; + visual->autostereo_glsl_fragment = visual_3d_shader_from_source_file(visual->compositor->mvshader, GL_FRAGMENT_SHADER); + if (visual->autostereo_glsl_fragment) res = GF_TRUE; + break; } } @@ -757,7 +559,7 @@ void visual_3d_init_stereo_shaders(GF_VisualManager *visual) GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, (pszInfoLog)); res = GF_FALSE; } - GL_CHECK_ERR + GL_CHECK_ERR() } if (!res) { @@ -772,6 +574,7 @@ void visual_3d_init_stereo_shaders(GF_VisualManager *visual) +#if 0 static GLint gf_glGetUniformLocation(GF_SHADERID glsl_program, const char *uniform_name) { GLint loc = glGetUniformLocation(glsl_program, uniform_name); @@ -780,7 +583,10 @@ static GLint gf_glGetUniformLocation(GF_SHADERID glsl_program, const char *unifo } return loc; } +#endif + +#if 0 static GLint gf_glGetAttribLocation(GF_SHADERID glsl_program, const char *attrib_name) { GLint loc = glGetAttribLocation(glsl_program, attrib_name); @@ -789,15 +595,16 @@ static GLint gf_glGetAttribLocation(GF_SHADERID glsl_program, const char *attrib } return loc; } +#endif //The following functions were used for shader testing [in ES2.0] #if 0 static void gf_glQueryProgram(GF_SHADERID progObj) { GLint err_log = -10; - GL_CHECK_ERR + GL_CHECK_ERR() glValidateProgram(progObj); - GL_CHECK_ERR + GL_CHECK_ERR() glGetProgramiv(progObj, GL_VALIDATE_STATUS, &err_log); printf("GL_VALIDATE_STATUS: %d \n ",err_log); glGetProgramiv(progObj, GL_LINK_STATUS, &err_log); @@ -813,7 +620,7 @@ static void gf_glQueryUniform(GF_SHADERID progObj, const char *name, int index) GLint loc, i; GLfloat res[16]; - loc = gf_glGetUniformLocation(progObj, name); + loc = glGetUniformLocation(progObj, name); if(loc<0) { GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("failed to locate uniform. exiting\n")); return; @@ -830,7 +637,7 @@ static void gf_glQueryAttrib(GF_SHADERID progObj, const char *name, int index, G GLint loc, i; GLfloat res[16]; - loc = gf_glGetAttribLocation(progObj, name); + loc = glGetAttribLocation(progObj, name); if (loc<0) { GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("failed to locate attribute. exiting\n")); return; @@ -850,7 +657,7 @@ static void gf_glQueryUniforms(GF_SHADERID progObj) glGetProgramiv(progObj, GL_ACTIVE_UNIFORMS, &numUniforms); glGetProgramiv(progObj, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxUniformLen); - uniformName = malloc(sizeof(char) * maxUniformLen); + uniformName = gf_malloc(sizeof(char) * maxUniformLen); for(index = 0; index < numUniforms; index++) { GLint size; GLenum type; @@ -897,6 +704,7 @@ static void gf_glQueryUniforms(GF_SHADERID progObj) break; } } + gf_free(uniformName); } static void gf_glQueryAttributes(GF_SHADERID progObj) @@ -909,7 +717,7 @@ static void gf_glQueryAttributes(GF_SHADERID progObj) printf("Listing Attribs... \n"); glGetProgramiv(progObj, GL_ACTIVE_ATTRIBUTES, &numAttributes); glGetProgramiv(progObj, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxAttributeLen); - attributeName = malloc(sizeof(char) * maxAttributeLen); + attributeName = gf_malloc(sizeof(char) * maxAttributeLen); for(index = 0; index < numAttributes; index++) { GLint size; GLenum type; @@ -956,122 +764,88 @@ static void gf_glQueryAttributes(GF_SHADERID progObj) break; } } + gf_free(attributeName); } #endif -static Bool visual_3d_init_generic_shaders(GF_VisualManager *visual) +static GF_GLProgInstance *visual_3d_build_program(GF_VisualManager *visual, u32 flags, u32 pix_fmt) { - u32 i; - GLint err_log = -10; - const char *shader_file; + s32 linked; + GF_GLProgInstance *pi; - GF_Config *cfg = visual->compositor->user->config; + GF_SAFEALLOC(pi, GF_GLProgInstance); + if (!pi) return NULL; + pi->flags = flags; + pi->pix_fmt = pix_fmt; - //FIXME: Clear error log - for Android we will always have an GL_INVALID_VALUE error glGetError(); + pi->prog = glCreateProgram(); + if (!pi->prog) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to create program\n")); + gf_free(pi); + return NULL; + } + pi->vertex = visual_3d_shader_with_flags(visual->compositor->vertshader , GL_VERTEX_SHADER, flags, 0); + if (!pi->vertex) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to compile vertex shader\n")); + DEL_PROGRAM(pi->prog) + gf_free(pi); + return NULL; + } + if (pix_fmt == GF_PIXEL_GL_EXTERNAL) { + flags |= GF_GL_IS_ExternalOES; + } + pi->fragment = visual_3d_shader_with_flags(visual->compositor->fragshader , GL_FRAGMENT_SHADER, flags, pix_fmt); + if (!pi->fragment) { + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to compile fragment shader\n")); + DEL_PROGRAM(pi->prog) + DEL_SHADER(pi->vertex); + gf_free(pi); + return NULL; + } + glAttachShader(pi->prog, pi->vertex); + GL_CHECK_ERR(); + glAttachShader(pi->prog, pi->fragment); + GL_CHECK_ERR(); + + glLinkProgram(pi->prog); + GL_CHECK_ERR(); + + glGetProgramiv(pi->prog, GL_LINK_STATUS, &linked); + if (!linked) { + int i32CharsWritten, i32InfoLogLength; + char pszInfoLog[2048]; + glGetProgramiv(pi->prog, GL_INFO_LOG_LENGTH, &i32InfoLogLength); + glGetProgramInfoLog(pi->prog, i32InfoLogLength, &i32CharsWritten, pszInfoLog); + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, (pszInfoLog)); + DEL_PROGRAM(pi->prog) + DEL_SHADER(pi->vertex); + DEL_SHADER(pi->fragment); + gf_free(pi); + return NULL; + } + GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] fragment shader compiled fine\n")); + return pi; +} - /* test if programs exist (by using flags 0) - * this check was introduced due to losing program objects when switching rasterization modes - * (hit '3' in runtime to recreate issue) - * the program objects were not deleted though - */ - if (visual->glsl_programs[0]) { - glGetProgramiv(visual->glsl_programs[0], GL_VALIDATE_STATUS, &err_log); - if(err_log==-10) { - for (i=0; iglsl_vertex_shaders[i]); - } - for (i=0; iglsl_fragment_shaders[i]); - DEL_PROGRAM(visual->glsl_programs[i]); - } - visual->glsl_has_shaders=0; - //Clear error log (if the program is lost we will always have an GL_INVALID_VALUE error) - glGetError(); - } - } else { - visual->glsl_has_shaders=0; - GL_CHECK_ERR; - } - - //nothing to do - if (visual->glsl_has_shaders) { +static Bool visual_3d_init_generic_shaders(GF_VisualManager *visual) +{ + GF_GLProgInstance *pi; + if (gf_list_count(visual->compiled_programs)) return GF_TRUE; - } - - GL_CHECK_ERR - //Creating Program for the shaders - for(i=0; iglsl_programs[i] = glCreateProgram(); - } - visual->glsl_has_shaders = GF_TRUE; - GL_CHECK_ERR - - shader_file =(char *) gf_cfg_get_key(cfg, "Compositor", "VertexShader"); - if (!shader_file) return GF_FALSE; - - for (i=0; iglsl_vertex_shaders[i] = visual_3d_shader_with_flags(shader_file , GL_VERTEX_SHADER, i); - if (!visual->glsl_vertex_shaders[i]) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to compile vertex shader\n")); - return GF_FALSE; - } - } + if (!gf_file_exists(visual->compositor->vertshader)) + return GF_FALSE; + if (!gf_file_exists(visual->compositor->fragshader)) + return GF_FALSE; - shader_file =(char *) gf_cfg_get_key(cfg, "Compositor", "FragmentShader"); - if (!shader_file) return GF_FALSE; - for (i=0; iglsl_programs[i]); - visual->glsl_programs[i]=0; - visual->glsl_fragment_shaders[i] = 0; - continue; - } + pi = visual_3d_build_program(visual, 0, 0); + if (!pi) return GF_FALSE; - visual->glsl_fragment_shaders[i] = visual_3d_shader_with_flags(shader_file , GL_FRAGMENT_SHADER, i); - if (!visual->glsl_fragment_shaders[i]) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to compile fragment shader\n")); - continue; - } - GL_CHECK_ERR; - - //compute vertex shader for this fragment: all YUV frag shaders are texture vert shaders - vert_id = i; - if (i & (GF_GL_IS_YUV | GF_GL_IS_ExternalOES)) { - vert_id &= ~(GF_GL_IS_YUV | GF_GL_IS_ExternalOES); - vert_id |= GF_GL_HAS_TEXTURE; - assert(vert_idglsl_programs[i], visual->glsl_vertex_shaders[vert_id]); - GL_CHECK_ERR; - - glAttachShader(visual->glsl_programs[i], visual->glsl_fragment_shaders[i]); - GL_CHECK_ERR; - - glLinkProgram(visual->glsl_programs[i]); - GL_CHECK_ERR; - - glGetProgramiv(visual->glsl_programs[i], GL_LINK_STATUS, &linked); - if (!linked) { - int i32CharsWritten, i32InfoLogLength; - char pszInfoLog[2048]; - glGetProgramiv(visual->glsl_programs[i], GL_INFO_LOG_LENGTH, &i32InfoLogLength); - glGetProgramInfoLog(visual->glsl_programs[i], i32InfoLogLength, &i32CharsWritten, pszInfoLog); - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, (pszInfoLog)); - } - - glUseProgram(visual->glsl_programs[i]); - GL_CHECK_ERR; - } - - /* Set texture planes*/ - visual_3d_set_tx_planes(visual); + glDeleteShader(pi->vertex); + glDeleteShader(pi->fragment); + glDeleteProgram(pi->prog); + gf_free(pi); return GF_TRUE; } @@ -1083,7 +857,7 @@ void visual_3d_init_shaders(GF_VisualManager *visual) if (!visual->compositor->gl_caps.has_shaders) return; - if (visual->compositor->shader_only_mode) { + if (!visual->compositor->shader_mode_disabled) { //If we fail to configure shaders, force 2D mode if (! visual_3d_init_generic_shaders(visual)) { visual->compositor->hybrid_opengl = GF_FALSE; @@ -1104,9 +878,6 @@ void visual_3d_init_shaders(GF_VisualManager *visual) void visual_3d_reset_graphics(GF_VisualManager *visual) { #if !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) - - u32 i; - if (visual->compositor->visual != visual) return; @@ -1124,15 +895,13 @@ void visual_3d_reset_graphics(GF_VisualManager *visual) visual->autostereo_mesh = NULL; } - for (i=0; iglsl_vertex_shaders[i]); - } - for (i=0; iglsl_fragment_shaders[i]); - DEL_PROGRAM(visual->glsl_programs[i]); + while (gf_list_count(visual->compiled_programs)) { + GF_GLProgInstance *gi = gf_list_pop_back(visual->compiled_programs); + DEL_SHADER(gi->vertex); + DEL_SHADER(gi->fragment); + DEL_PROGRAM(gi->prog); + gf_free(gi); } - visual->glsl_has_shaders=0; - #endif } @@ -1177,12 +946,12 @@ static void visual_3d_load_matrix_shaders(GF_SHADERID program, Fixed *mat, const u32 i; #endif - loc = gf_glGetUniformLocation(program, name); + loc = glGetUniformLocation(program, name); if(loc<0) { GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("GL Error (file %s line %d): Invalid matrix name", __FILE__, __LINE__)); return; } - GL_CHECK_ERR + GL_CHECK_ERR() #ifdef GPAC_FIXED_POINT for (i=0; i<16; i++) _mat[i] = FIX2FLT(mat[i]); @@ -1190,7 +959,7 @@ static void visual_3d_load_matrix_shaders(GF_SHADERID program, Fixed *mat, const #else glUniformMatrix4fv(loc, 1, GL_FALSE, mat); #endif - GL_CHECK_ERR + GL_CHECK_ERR() } #endif @@ -1260,14 +1029,13 @@ void visual_3d_end_auto_stereo_pass(GF_VisualManager *visual) #if !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) u32 i; GLint loc, loc_vertex_attrib, loc_texcoord_attrib; - char szTex[100]; Fixed hw, hh; GF_Matrix mx; glFlush(); - GL_CHECK_ERR + GL_CHECK_ERR() #ifndef GPAC_USE_GLES2 glEnable(GL_TEXTURE_2D); @@ -1291,14 +1059,14 @@ void visual_3d_end_auto_stereo_pass(GF_VisualManager *visual) #endif glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, visual->auto_stereo_width, visual->auto_stereo_height, 0); - GL_CHECK_ERR + GL_CHECK_ERR() #ifndef GPAC_USE_GLES2 glDisable(GL_TEXTURE_2D); #endif glClear(GL_DEPTH_BUFFER_BIT); - GL_CHECK_ERR + GL_CHECK_ERR() if (visual->current_view+1nb_views) return; hw = INT2FIX(visual->width); @@ -1329,19 +1097,21 @@ void visual_3d_end_auto_stereo_pass(GF_VisualManager *visual) loc_texcoord_attrib = -1; //setup vertex attrib - loc_vertex_attrib = gf_glGetAttribLocation(visual->autostereo_glsl_program, "gfVertex"); + loc_vertex_attrib = glGetAttribLocation(visual->autostereo_glsl_program, "gfVertex"); if (loc_vertex_attrib>=0) { glVertexAttribPointer(loc_vertex_attrib, 3, GL_FLOAT, GL_FALSE, sizeof(GF_Vertex), &visual->autostereo_mesh->vertices[0].pos); glEnableVertexAttribArray(loc_vertex_attrib); - GL_CHECK_ERR + GL_CHECK_ERR() + //setup texcoord location - loc_texcoord_attrib = gf_glGetAttribLocation(visual->autostereo_glsl_program, "gfTextureCoordinates"); + loc_texcoord_attrib = glGetAttribLocation(visual->autostereo_glsl_program, "gfTextureCoordinates"); if (loc_texcoord_attrib>=0) { + char szTex[100]; glVertexAttribPointer(loc_texcoord_attrib, 2, GL_FLOAT, GL_FALSE, sizeof(GF_Vertex), &visual->autostereo_mesh->vertices[0].texcoords); glEnableVertexAttribArray(loc_texcoord_attrib); - GL_CHECK_ERR + GL_CHECK_ERR() /*bind all our textures*/ for (i=0; inb_views; i++) { @@ -1359,15 +1129,15 @@ void visual_3d_end_auto_stereo_pass(GF_VisualManager *visual) glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); #endif - GL_CHECK_ERR + GL_CHECK_ERR() glBindTexture(GL_TEXTURE_2D, visual->gl_textures[i]); - GL_CHECK_ERR + GL_CHECK_ERR() glUniform1i(loc, i); - GL_CHECK_ERR + GL_CHECK_ERR() } //draw @@ -1377,27 +1147,27 @@ void visual_3d_end_auto_stereo_pass(GF_VisualManager *visual) glDrawElements(GL_TRIANGLES, visual->autostereo_mesh->i_count, GL_UNSIGNED_INT, visual->autostereo_mesh->indices); #endif - GL_CHECK_ERR + GL_CHECK_ERR() } } if (loc_vertex_attrib>=0) glDisableVertexAttribArray(loc_vertex_attrib); if (loc_texcoord_attrib>=0) glDisableVertexAttribArray(loc_texcoord_attrib); - GL_CHECK_ERR + GL_CHECK_ERR() glUseProgram(0); #ifndef GPAC_USE_GLES2 /*not sure why this is needed but it prevents a texturing bug on XP on parallels*/ glActiveTexture(GL_TEXTURE0); - GL_CHECK_ERR + GL_CHECK_ERR() glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); #endif - GL_CHECK_ERR + GL_CHECK_ERR() #endif // !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) } @@ -1407,7 +1177,7 @@ static void visual_3d_setup_quality(GF_VisualManager *visual) { #ifndef GPAC_USE_GLES2 - if (visual->compositor->high_speed) { + if (visual->compositor->fast) { glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); glHint(GL_LINE_SMOOTH_HINT, GL_FASTEST); @@ -1423,10 +1193,10 @@ static void visual_3d_setup_quality(GF_VisualManager *visual) #endif } - if (visual->compositor->antiAlias == GF_ANTIALIAS_FULL) { + if (visual->compositor->aa == GF_ANTIALIAS_FULL) { glEnable(GL_LINE_SMOOTH); #ifndef GPAC_USE_GLES1X - if (visual->compositor->poly_aa) + if (visual->compositor->paa) glEnable(GL_POLYGON_SMOOTH); else glDisable(GL_POLYGON_SMOOTH); @@ -1444,8 +1214,8 @@ static void visual_3d_setup_quality(GF_VisualManager *visual) void visual_3d_setup(GF_VisualManager *visual) { - - if (visual->gl_setup) { + //in non-player mode, we might not be the only ones using the gl context !! + if (visual->compositor->player && visual->gl_setup) { visual->has_fog = GF_FALSE; glClear(GL_DEPTH_BUFFER_BIT); return; @@ -1544,7 +1314,7 @@ void visual_3d_enable_antialias(GF_VisualManager *visual, Bool bOn) if (bOn) { glEnable(GL_LINE_SMOOTH); #ifndef GPAC_USE_GLES1X - if (visual->compositor->poly_aa) + if (visual->compositor->paa) glEnable(GL_POLYGON_SMOOTH); else glDisable(GL_POLYGON_SMOOTH); @@ -1570,8 +1340,11 @@ void visual_3d_enable_antialias(GF_VisualManager *visual, Bool bOn) void visual_3d_enable_depth_buffer(GF_VisualManager *visual, Bool on) { - if (on) glEnable(GL_DEPTH_TEST); - else glDisable(GL_DEPTH_TEST); + if (on) { + glEnable(GL_DEPTH_TEST); + } else { + glDisable(GL_DEPTH_TEST); + } } void visual_3d_set_viewport(GF_VisualManager *visual, GF_Rect vp) @@ -1694,6 +1467,9 @@ static void visual_3d_set_clippers(GF_VisualManager *visual, GF_TraverseState *t u32 i; GF_Matrix inv_mx; + if (!visual->num_clips) + return; + gf_mx_copy(inv_mx, tr_state->model_matrix); gf_mx_inverse(&inv_mx); @@ -1795,7 +1571,7 @@ static void visual_3d_set_lights(GF_VisualManager *visual) glEnable(iLight); switch (li->type) { - //directionnal light + //directional light case 0: case 3: #if defined(GPAC_USE_GLES1X) && defined(GPAC_FIXED_POINT) @@ -2010,7 +1786,7 @@ void visual_3d_enable_fog(GF_VisualManager *visual) vals[2] = visual->fog_color.blue; vals[3] = FIX_ONE; glFogxv(GL_FOG_COLOR, vals); - glHint(GL_FOG_HINT, visual->compositor->high_speed ? GL_FASTEST : GL_NICEST); + glHint(GL_FOG_HINT, visual->compositor->fast ? GL_FASTEST : GL_NICEST); #else Float vals[4]; glEnable(GL_FOG); @@ -2033,7 +1809,7 @@ void visual_3d_enable_fog(GF_VisualManager *visual) vals[2] = FIX2FLT(visual->fog_color.blue); vals[3] = 1; glFogfv(GL_FOG_COLOR, vals); - glHint(GL_FOG_HINT, visual->compositor->high_speed ? GL_FASTEST : GL_NICEST); + glHint(GL_FOG_HINT, visual->compositor->fast ? GL_FASTEST : GL_NICEST); #endif #endif @@ -2072,7 +1848,7 @@ static void visual_3d_do_draw_mesh(GF_TraverseState *tr_state, GF_Mesh *mesh) /*if inside or no aabb for the mesh draw vertex array*/ - if (tr_state->visual->compositor->disable_gl_cull || (tr_state->cull_flag==CULL_INSIDE) || !mesh->aabb_root || !mesh->aabb_root->pos) { + if (!tr_state->visual->compositor->cull || (tr_state->cull_flag==CULL_INSIDE) || !mesh->aabb_root || !mesh->aabb_root->pos) { #if defined(GPAC_USE_GLES1X) || defined(GPAC_USE_GLES2) glDrawElements(prim_type, mesh->i_count, GL_UNSIGNED_SHORT, idx_addr); #else @@ -2186,18 +1962,18 @@ static void visual_3d_set_lights_shaders(GF_TraverseState *tr_state) GF_VisualManager *visual = tr_state->visual; char szName[100]; - loc = gf_glGetUniformLocation(visual->glsl_program, "gfNumLights"); + loc = glGetUniformLocation(visual->glsl_program, "gfNumLights"); if (loc>=0) glUniform1i(loc, visual->num_lights); - GL_CHECK_ERR + GL_CHECK_ERR() /* * Equivalent to glLightModel(GL_LIGHTMODEL_TWO_SIDE, GL_TRUE); */ - loc = gf_glGetUniformLocation(visual->glsl_program, "gfLightTwoSide"); + loc = glGetUniformLocation(visual->glsl_program, "gfLightTwoSide"); if (loc>=0) glUniform1i(loc, 1); - GL_CHECK_ERR + GL_CHECK_ERR() li = &visual->lights[0]; @@ -2224,7 +2000,7 @@ static void visual_3d_set_lights_shaders(GF_TraverseState *tr_state) } sprintf(szName, "%s%d%s", "lights[", i, "].type"); - loc = gf_glGetUniformLocation(visual->glsl_program, szName); //Uniform name lights[i].type + loc = glGetUniformLocation(visual->glsl_program, szName); //Uniform name lights[i].type if (loc>=0) { if (li->type==3) { glUniform1i(loc, 0); //headlight; Set type 0-directional @@ -2246,7 +2022,7 @@ static void visual_3d_set_lights_shaders(GF_TraverseState *tr_state) vals[2] = -FIX2FLT(pt.z); vals[3] = 0; sprintf(szName, "%s%d%s", "lights[", i, "].direction"); - loc = gf_glGetUniformLocation(visual->glsl_program, szName); + loc = glGetUniformLocation(visual->glsl_program, szName); if (loc>=0) glUniform4fv(loc, 1, vals); //Set direction @@ -2256,7 +2032,6 @@ static void visual_3d_set_lights_shaders(GF_TraverseState *tr_state) vals[1] = -FIX2FLT(pt.y); vals[2] = -FIX2FLT(pt.z); vals[3] = 0; - pt = li->position; } else { //we have a spot or point light pt = li->position; gf_mx_apply_vec(&mx, &pt); @@ -2266,7 +2041,7 @@ static void visual_3d_set_lights_shaders(GF_TraverseState *tr_state) vals[3] = 1.0; } sprintf(szName, "%s%d%s", "lights[", i, "].position"); - loc = gf_glGetUniformLocation(visual->glsl_program, szName); + loc = glGetUniformLocation(visual->glsl_program, szName); if (loc>=0) glUniform4fv(loc, 1, vals); @@ -2279,7 +2054,7 @@ static void visual_3d_set_lights_shaders(GF_TraverseState *tr_state) vals[1] = FIX2FLT(pt.y); vals[2] = FIX2FLT(pt.z); sprintf(szName, "%s%d%s", "lights[", i, "].attenuation"); - loc = gf_glGetUniformLocation(visual->glsl_program, szName); + loc = glGetUniformLocation(visual->glsl_program, szName); if (loc>=0) glUniform3fv(loc, 1, vals); @@ -2288,17 +2063,17 @@ static void visual_3d_set_lights_shaders(GF_TraverseState *tr_state) vals[2] = FIX2FLT(li->color.blue); vals[3] = 0; sprintf(szName, "%s%d%s", "lights[", i, "].color"); - loc = gf_glGetUniformLocation(visual->glsl_program, szName); + loc = glGetUniformLocation(visual->glsl_program, szName); if (loc>=0) glUniform4fv(loc, 1, vals); sprintf(szName, "%s%d%s", "lights[", i, "].intensity"); - loc = gf_glGetUniformLocation(visual->glsl_program, szName); + loc = glGetUniformLocation(visual->glsl_program, szName); if (loc>=0) glUniform1f(loc, li->intensity); sprintf(szName, "%s%d%s", "lights[", i, "].cutOffAngle"); - loc = gf_glGetUniformLocation(visual->glsl_program, szName); + loc = glGetUniformLocation(visual->glsl_program, szName); if (loc>=0) glUniform1f(loc, li->cutOffAngle); @@ -2309,43 +2084,43 @@ static void visual_3d_set_lights_shaders(GF_TraverseState *tr_state) vals[1] = FIX2FLT(li->color.green)*intensity; vals[2] = FIX2FLT(li->color.blue)*intensity; vals[3] = 1; - loc = gf_glGetUniformLocation(visual->glsl_program, "gfLightDiffuse"); + loc = glGetUniformLocation(visual->glsl_program, "gfLightDiffuse"); if (loc>=0) glUniform4fv(loc, 1, vals); - loc = gf_glGetUniformLocation(visual->glsl_program, "gfLightSpecular"); + loc = glGetUniformLocation(visual->glsl_program, "gfLightSpecular"); if (loc>=0) glUniform4fv(loc, 1, vals); vals[0] = FIX2FLT(li->color.red)*ambientIntensity; vals[1] = FIX2FLT(li->color.green)*ambientIntensity; vals[2] = FIX2FLT(li->color.blue)*ambientIntensity; vals[3] = 1; - loc = gf_glGetUniformLocation(visual->glsl_program, "gfLightAmbient"); + loc = glGetUniformLocation(visual->glsl_program, "gfLightAmbient"); if (loc>=0) glUniform4fv(loc, 1, vals); - GL_CHECK_ERR + GL_CHECK_ERR() } static void visual_3d_set_fog_shaders(GF_VisualManager *visual) { GLint loc; if (visual->has_fog) { - loc = gf_glGetUniformLocation(visual->glsl_program, "gfFogEnabled"); + loc = glGetUniformLocation(visual->glsl_program, "gfFogEnabled"); if(loc>=0) glUniform1i(loc, GL_TRUE); - loc = gf_glGetUniformLocation(visual->glsl_program, "gfFogColor"); + loc = glGetUniformLocation(visual->glsl_program, "gfFogColor"); if(loc>=0) glUniform3fv(loc, 1, (GLfloat *) &visual->fog_color); - loc = gf_glGetUniformLocation(visual->glsl_program, "gfFogDensity"); + loc = glGetUniformLocation(visual->glsl_program, "gfFogDensity"); if(loc>=0) glUniform1f(loc, visual->fog_density ); - loc = gf_glGetUniformLocation(visual->glsl_program, "gfFogType"); + loc = glGetUniformLocation(visual->glsl_program, "gfFogType"); if(loc>=0) glUniform1i(loc, (GLuint) visual->fog_type ); - loc = gf_glGetUniformLocation(visual->glsl_program, "gfFogVisibility"); + loc = glGetUniformLocation(visual->glsl_program, "gfFogVisibility"); if(loc>=0) glUniform1f(loc, visual->fog_visibility); } else { @@ -2354,7 +2129,7 @@ static void visual_3d_set_fog_shaders(GF_VisualManager *visual) glUniform1i(loc, GL_FALSE); } - GL_CHECK_ERR + GL_CHECK_ERR() } static void visual_3d_set_clippers_shaders(GF_VisualManager *visual, GF_TraverseState *tr_state) @@ -2373,7 +2148,7 @@ static void visual_3d_set_clippers_shaders(GF_VisualManager *visual, GF_Traverse gf_mx_copy(eye_mx, tr_state->camera->modelview); gf_mx_add_matrix(&eye_mx, &tr_state->model_matrix); - loc = gf_glGetUniformLocation(visual->glsl_program, "gfNumClippers"); + loc = glGetUniformLocation(visual->glsl_program, "gfNumClippers"); if (loc>=0) glUniform1i(loc, visual->num_clips); @@ -2395,7 +2170,7 @@ static void visual_3d_set_clippers_shaders(GF_VisualManager *visual, GF_Traverse } sprintf(szName, "%s%d%s", "clipPlane[", i, "]"); //parse plane values - loc = gf_glGetUniformLocation(visual->glsl_program, szName); + loc = glGetUniformLocation(visual->glsl_program, szName); if (loc>=0) { vals[0] = p.normal.x; vals[1] = p.normal.y; @@ -2406,6 +2181,28 @@ static void visual_3d_set_clippers_shaders(GF_VisualManager *visual, GF_Traverse } } +GF_GLProgInstance *visual_3d_check_program_exists(GF_VisualManager *root_visual, u32 flags, u32 pix_fmt) +{ + u32 i, count; + GF_GLProgInstance *prog = NULL; + if (root_visual->compositor->shader_mode_disabled) + return NULL; + + count = gf_list_count(root_visual->compiled_programs); + for (i=0; icompiled_programs, i); + if ((prog->flags==flags) && (prog->pix_fmt==pix_fmt)) break; + prog = NULL; + } + + if (!prog) { + prog = visual_3d_build_program(root_visual, flags, pix_fmt); + if (!prog) return NULL; + gf_list_add(root_visual->compiled_programs, prog); + } + return prog; +} + static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh *mesh) { void *vertex_buffer_address; @@ -2414,8 +2211,16 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh GLint loc, loc_vertex_array, loc_color_array, loc_normal_array, loc_textcoord_array; u32 flags; u32 num_lights = visual->num_lights; - - flags = root_visual->glsl_flags; + Bool is_debug_bounds = GF_FALSE; + GF_GLProgInstance *prog = NULL; + + flags = root_visual->active_glsl_flags; + + if (!mesh) { + is_debug_bounds = GF_TRUE; + mesh = tr_state->visual->compositor->unit_bbox; + flags = 0; + } if (visual->has_material_2d) { num_lights = 0; @@ -2434,7 +2239,7 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh } - if (num_lights) { + if (num_lights && !is_debug_bounds) { flags |= GF_GL_HAS_LIGHT; } else { flags &= ~GF_GL_HAS_LIGHT; @@ -2446,18 +2251,21 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh flags &= ~GF_GL_HAS_CLIP; } - root_visual->glsl_flags = visual->glsl_flags = flags; + root_visual->active_glsl_flags = visual->active_glsl_flags = flags; + + prog = visual_3d_check_program_exists(root_visual, flags, root_visual->bound_tx_pix_fmt); + if (!prog) return; //check if we are using a different program than last time, if so force matrices updates - if ((visual->glsl_program != root_visual->glsl_programs[visual->glsl_flags]) - || !root_visual->glsl_programs[visual->glsl_flags]) { + if (visual->glsl_program != prog->prog) { tr_state->visual->needs_projection_matrix_reload = GF_TRUE; } - GL_CHECK_ERR - visual->glsl_program = root_visual->glsl_programs[visual->glsl_flags]; + GL_CHECK_ERR() + visual->glsl_program = prog->prog; + glUseProgram(visual->glsl_program); - GL_CHECK_ERR + GL_CHECK_ERR() if (! visual_3d_bind_buffer(visual->compositor, mesh, &vertex_buffer_address)) { glUseProgram(0); @@ -2472,12 +2280,12 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh loc_color_array = loc_normal_array = loc_textcoord_array = -1; //setup vertext array location (always true) - loc_vertex_array = gf_glGetAttribLocation(visual->glsl_program, "gfVertex"); + loc_vertex_array = glGetAttribLocation(visual->glsl_program, "gfVertex"); if (loc_vertex_array<0) return; glEnableVertexAttribArray(loc_vertex_array); - GL_CHECK_ERR + GL_CHECK_ERR() #if defined(GPAC_FIXED_POINT) glVertexAttribPointer(loc_vertex_array, 3, GL_FIXED, GL_TRUE, sizeof(GF_Vertex), vertex_buffer_address); #else @@ -2490,11 +2298,27 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh //setup clippers visual_3d_set_clippers_shaders(visual, tr_state); + if (is_debug_bounds) { + Float cols[4]; + loc = glGetUniformLocation(visual->glsl_program, "gfEmissionColor"); + cols[0] = 1.0f - FIX2FLT(visual->mat_2d.red); + cols[1] = 1.0f - FIX2FLT(visual->mat_2d.green); + cols[2] = 1.0f - FIX2FLT(visual->mat_2d.blue); + cols[3] = 1.0f; + if (loc>=0) + glUniform4fv(loc, 1, (GLfloat *) cols); + GL_CHECK_ERR() + + loc = glGetUniformLocation(visual->glsl_program, "hasMaterial2D"); + if (loc>=0) + glUniform1i(loc, 1); + GL_CHECK_ERR() + } /* Material2D does not have any lights, color used is "gfEmissionColor" uniform */ - if (visual->has_material_2d) { + else if (visual->has_material_2d) { //for YUV manually set alpha if (flags & GF_GL_IS_YUV) { - loc = gf_glGetUniformLocation(visual->glsl_program, "alpha"); + loc = glGetUniformLocation(visual->glsl_program, "alpha"); if(loc>=0) glUniform1f(loc, FIX2FLT(visual->mat_2d.alpha)); } @@ -2507,29 +2331,29 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh } } - loc = gf_glGetUniformLocation(visual->glsl_program, "gfEmissionColor"); + loc = glGetUniformLocation(visual->glsl_program, "gfEmissionColor"); if (loc>=0) glUniform4fv(loc, 1, (GLfloat *) & visual->mat_2d); - GL_CHECK_ERR + GL_CHECK_ERR() - loc = gf_glGetUniformLocation(visual->glsl_program, "hasMaterial2D"); + loc = glGetUniformLocation(visual->glsl_program, "hasMaterial2D"); if (loc>=0) glUniform1i(loc, 1); - GL_CHECK_ERR + GL_CHECK_ERR() } else { - loc = gf_glGetUniformLocation(visual->glsl_program, "hasMaterial2D"); + loc = glGetUniformLocation(visual->glsl_program, "hasMaterial2D"); if (loc>=0) glUniform1i(loc, 0); - GL_CHECK_ERR + GL_CHECK_ERR() //for YUV manually set alpha if (flags & GF_GL_IS_YUV) { - loc = gf_glGetUniformLocation(visual->glsl_program, "alpha"); + loc = glGetUniformLocation(visual->glsl_program, "alpha"); if(loc>=0) glUniform1f(loc, 1.0); - GL_CHECK_ERR + GL_CHECK_ERR() } } @@ -2551,16 +2375,16 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh #endif switch (i) { case 0: - loc = gf_glGetUniformLocation(visual->glsl_program, "gfAmbientColor"); + loc = glGetUniformLocation(visual->glsl_program, "gfAmbientColor"); break; case 1: - loc = gf_glGetUniformLocation(visual->glsl_program, "gfDiffuseColor"); + loc = glGetUniformLocation(visual->glsl_program, "gfDiffuseColor"); break; case 2: - loc = gf_glGetUniformLocation(visual->glsl_program, "gfSpecularColor"); + loc = glGetUniformLocation(visual->glsl_program, "gfSpecularColor"); break; case 3: - loc = gf_glGetUniformLocation(visual->glsl_program, "gfEmissionColor"); + loc = glGetUniformLocation(visual->glsl_program, "gfEmissionColor"); break; } @@ -2568,7 +2392,7 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh glUniform4fv(loc, 1, _rgba); } //TO CHECK: if this does not work as it is supposed to, try: visual->shininess * 128 - loc = gf_glGetUniformLocation(visual->glsl_program, "gfShininess"); + loc = glGetUniformLocation(visual->glsl_program, "gfShininess"); if (loc>=0) glUniform1f(loc, FIX2FLT(visual->shininess)); @@ -2576,8 +2400,8 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh } //setup mesh color vertex attribute - only available for some shaders - if (!tr_state->mesh_num_textures && (mesh->flags & MESH_HAS_COLOR)) { - loc_color_array = gf_glGetAttribLocation(visual->glsl_program, "gfMeshColor"); + if (!tr_state->mesh_num_textures && (mesh->flags & MESH_HAS_COLOR) && !is_debug_bounds) { + loc_color_array = glGetAttribLocation(visual->glsl_program, "gfMeshColor"); if (loc_color_array >= 0) { //for now colors are 8bit/comp RGB(A), so used GL_UNSIGNED_BYTE and GL_TRUE for normalizing values @@ -2590,7 +2414,7 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh } glEnableVertexAttribArray(loc_color_array); } - GL_CHECK_ERR + GL_CHECK_ERR() } if (flags & GF_GL_HAS_LIGHT) { @@ -2612,7 +2436,7 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh visual_3d_load_matrix_shaders(tr_state->visual->glsl_program, (Fixed *) &normal_mx.m, "gfNormalMatrix"); - loc_normal_array = gf_glGetAttribLocation(visual->glsl_program, "gfNormal"); + loc_normal_array = glGetAttribLocation(visual->glsl_program, "gfNormal"); if (loc_normal_array>=0) { #ifdef MESH_USE_FIXED_NORMAL glVertexAttribPointer(loc_normal_array, 3, GL_FLOAT, GL_FALSE, sizeof(GF_Vertex), ((char *)vertex_buffer_address + MESH_NORMAL_OFFSET) ); @@ -2621,55 +2445,55 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh #endif glEnableVertexAttribArray(loc_normal_array); } - GL_CHECK_ERR + GL_CHECK_ERR() - loc = gf_glGetUniformLocation(visual->glsl_program, "gfNumLights"); + loc = glGetUniformLocation(visual->glsl_program, "gfNumLights"); if (loc>=0) glUniform1i(loc, num_lights); visual_3d_set_lights_shaders(tr_state); - GL_CHECK_ERR + GL_CHECK_ERR() } //setup mesh normal vertex attribute - only available for some shaders if (tr_state->mesh_num_textures && (mesh->mesh_type==MESH_TRIANGLES) && !(mesh->flags & MESH_NO_TEXTURE)) { - loc = gf_glGetUniformLocation(visual->glsl_program, "gfNumTextures"); - if (loc>=0) { - glUniform1i(loc, tr_state->mesh_num_textures); - } - GL_CHECK_ERR - if (visual->has_tx_matrix) { //parsing texture matrix - loc = gf_glGetUniformLocation(visual->glsl_program, "gfTextureMatrix"); + loc = glGetUniformLocation(visual->glsl_program, "gfTextureMatrix"); if (loc>=0) glUniformMatrix4fv(loc, 1, GL_FALSE, visual->tx_matrix.m); - GL_CHECK_ERR + GL_CHECK_ERR() - loc = gf_glGetUniformLocation(visual->glsl_program, "hasTextureMatrix"); + loc = glGetUniformLocation(visual->glsl_program, "hasTextureMatrix"); if (loc>=0) glUniform1i(loc, 1); } else { - loc = gf_glGetUniformLocation(visual->glsl_program, "hasTextureMatrix"); + loc = glGetUniformLocation(visual->glsl_program, "hasTextureMatrix"); if (loc>=0) glUniform1i(loc, 0); } //parsing texture coordinates - loc_textcoord_array = gf_glGetAttribLocation(visual->glsl_program, "gfMultiTexCoord"); + loc_textcoord_array = glGetAttribLocation(visual->glsl_program, "gfMultiTexCoord"); if (loc_textcoord_array>=0) { glVertexAttribPointer(loc_textcoord_array, 2, GL_FLOAT, GL_FALSE, sizeof(GF_Vertex), ((char *)vertex_buffer_address + MESH_TEX_OFFSET)); glEnableVertexAttribArray(loc_textcoord_array); - GL_CHECK_ERR + GL_CHECK_ERR() } if (flags & GF_GL_IS_YUV) { - loc = gf_glGetUniformLocation(visual->glsl_program, "yuvPixelFormat"); + loc = glGetUniformLocation(visual->glsl_program, "yuvPixelFormat"); if (loc>=0) { int yuv_mode = 0; - if (visual->yuv_pixelformat_type == GF_PIXEL_NV21) yuv_mode = 1; - else if (visual->yuv_pixelformat_type == GF_PIXEL_NV12) yuv_mode = 2; + switch (visual->yuv_pixelformat_type) { + case GF_PIXEL_NV21: + yuv_mode = 1; + break; + case GF_PIXEL_NV12: + yuv_mode = 2; + break; + } glUniform1i(loc, yuv_mode); } - GL_CHECK_ERR + GL_CHECK_ERR() } } @@ -2677,7 +2501,7 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh if (mesh->mesh_type != MESH_TRIANGLES) { //According to the spec we should pass a 0,0,1 Normal and disable lights. we just disable lights if(flags & GF_GL_HAS_LIGHT) { - loc = gf_glGetUniformLocation(visual->glsl_program, "gfNumLights"); + loc = glGetUniformLocation(visual->glsl_program, "gfNumLights"); if (loc>=0) glUniform1i(loc, 0); } @@ -2688,17 +2512,21 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh #endif } else { - if (visual->compositor->backcull - && (!tr_state->mesh_is_transparent || (visual->compositor->backcull ==GF_BACK_CULL_ALPHA) ) + if (visual->compositor->bcull + && (!tr_state->mesh_is_transparent || (visual->compositor->bcull ==GF_BACK_CULL_ALPHA) ) && (mesh->flags & MESH_IS_SOLID)) { glEnable(GL_CULL_FACE); - glFrontFace((mesh->flags & MESH_IS_CW) ? GL_CW : GL_CCW); + if (tr_state->reverse_backface) { + glFrontFace((mesh->flags & MESH_IS_CW) ? GL_CCW : GL_CW); + } else { + glFrontFace((mesh->flags & MESH_IS_CW) ? GL_CW : GL_CCW); + } } else { glDisable(GL_CULL_FACE); } } - GL_CHECK_ERR + GL_CHECK_ERR() //We have a Colour Matrix to be applied if(!tr_state->color_mat.identity) { @@ -2717,18 +2545,18 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh } //the rest of the values form the translation vector - loc = gf_glGetUniformLocation(visual->glsl_program, "gfTranslationVector"); + loc = glGetUniformLocation(visual->glsl_program, "gfTranslationVector"); if (loc>=0) glUniform4fv(loc, 1, (GLfloat *) &translateV); - GL_CHECK_ERR + GL_CHECK_ERR() loc = glGetUniformLocation(visual->glsl_program, "hasColorMatrix"); if(loc>=0) glUniform1i(loc, 1); - loc = gf_glGetUniformLocation(visual->glsl_program, "gfColorMatrix"); + loc = glGetUniformLocation(visual->glsl_program, "gfColorMatrix"); if (loc>=0) glUniformMatrix4fv(loc, 1, GL_FALSE, toBeParsed.m); - GL_CHECK_ERR + GL_CHECK_ERR() } else { loc = glGetUniformLocation(visual->glsl_program, "hasColorMatrix"); if(loc>=0) glUniform1i(loc, 0); @@ -2772,12 +2600,9 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh visual_3d_do_draw_mesh(tr_state, mesh); - GL_CHECK_ERR + GL_CHECK_ERR() //We drawn, now we Reset - if (mesh->vbo) - glBindBuffer(GL_ARRAY_BUFFER, 0); - if (mesh->vbo) glBindBuffer(GL_ARRAY_BUFFER, 0); @@ -2787,26 +2612,26 @@ static void visual_3d_draw_mesh_shader_only(GF_TraverseState *tr_state, GF_Mesh if (loc_textcoord_array>=0) glDisableVertexAttribArray(loc_textcoord_array); //instead of visual_3d_reset_lights(visual); - if(visual->compositor->visual->glsl_flags & GF_GL_HAS_LIGHT) { - loc = gf_glGetUniformLocation(visual->glsl_program, "gfNumLights"); + if(root_visual->active_glsl_flags & GF_GL_HAS_LIGHT) { + loc = glGetUniformLocation(visual->glsl_program, "gfNumLights"); if (loc>=0) glUniform1i(loc, 0); - GL_CHECK_ERR + GL_CHECK_ERR() } if (visual->has_clipper_2d) { glDisable(GL_SCISSOR_TEST); } - visual->has_material_2d = 0; - visual->glsl_flags = visual->compositor->visual->glsl_flags; - root_visual->glsl_flags &= ~ (GF_GL_IS_ExternalOES | GF_GL_IS_YUV | GF_GL_HAS_COLOR); + visual->has_material_2d = GF_FALSE; + visual->active_glsl_flags = root_visual->active_glsl_flags; + root_visual->active_glsl_flags &= ~ GF_GL_HAS_COLOR; visual->has_material = 0; visual->state_color_on = 0; if (tr_state->mesh_is_transparent) glDisable(GL_BLEND); tr_state->mesh_is_transparent = 0; - GL_CHECK_ERR + GL_CHECK_ERR() glUseProgram(0); - GL_CHECK_ERR + GL_CHECK_ERR() } #endif // !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) @@ -2821,6 +2646,7 @@ static void visual_3d_draw_mesh(GF_TraverseState *tr_state, GF_Mesh *mesh) GF_VisualManager *visual = tr_state->visual; Bool has_col, has_tx, has_norm; void *base_address = NULL; + Bool is_debug_bounds = GF_FALSE; #if defined(GPAC_FIXED_POINT) && !defined(GPAC_USE_GLES1X) Float *color_array = NULL; @@ -2830,23 +2656,31 @@ static void visual_3d_draw_mesh(GF_TraverseState *tr_state, GF_Mesh *mesh) #endif - GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[V3D] Drawing mesh %p\n", mesh)); + if (mesh) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[V3D] Drawing mesh %p\n", mesh)); + } + //clear error glGetError(); - GL_CHECK_ERR + GL_CHECK_ERR() #ifdef GPAC_USE_GLES2 visual_3d_draw_mesh_shader_only(tr_state, mesh); return; #else -#if !defined(GPAC_ANDROID) && !defined(GPAC_IPHONE) && !defined(GPAC_FIXED_POINT) - if (visual->compositor->shader_only_mode) { +#if !defined(GPAC_CONFIG_ANDROID) && !defined(GPAC_CONFIG_IOS) && !defined(GPAC_FIXED_POINT) + if (!visual->compositor->shader_mode_disabled) { visual_3d_draw_mesh_shader_only(tr_state, mesh); return; } #endif + if (!mesh) { + mesh = tr_state->visual->compositor->unit_bbox; + is_debug_bounds = GF_TRUE; + } + if (! visual_3d_bind_buffer(compositor, mesh, &base_address)) { #if! defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_TINYGL) glUseProgram(0); @@ -2855,24 +2689,28 @@ static void visual_3d_draw_mesh(GF_TraverseState *tr_state, GF_Mesh *mesh) } has_col = has_tx = has_norm = 0; - //set lights before pushing modelview matrix - visual_3d_set_lights(visual); + if (!is_debug_bounds) { + //set lights before pushing modelview matrix + visual_3d_set_lights(visual); + } visual_3d_update_matrices(tr_state); /*enable states*/ - if (visual->has_fog) visual_3d_enable_fog(visual); + if (!is_debug_bounds) { + if (visual->has_fog) visual_3d_enable_fog(visual); - if (visual->state_color_on) glEnable(GL_COLOR_MATERIAL); - else glDisable(GL_COLOR_MATERIAL); + if (visual->state_color_on) glEnable(GL_COLOR_MATERIAL); + else glDisable(GL_COLOR_MATERIAL); + + if (visual->state_blend_on) glEnable(GL_BLEND); + } - if (visual->state_blend_on) glEnable(GL_BLEND); //setup scissor visual_3d_set_clipper_scissor(visual, tr_state); - if (visual->num_clips) - visual_3d_set_clippers(visual, tr_state); + visual_3d_set_clippers(visual, tr_state); glEnableClientState(GL_VERTEX_ARRAY); #if defined(GPAC_USE_GLES1X) @@ -2893,53 +2731,28 @@ static void visual_3d_draw_mesh(GF_TraverseState *tr_state, GF_Mesh *mesh) */ if (visual->has_material_2d) { glDisable(GL_LIGHTING); - -#if !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_TINYGL) - if (visual->compositor->visual->current_texture_glsl_program) { - int loc = glGetUniformLocation(visual->compositor->visual->current_texture_glsl_program, "alpha"); - if (loc == -1) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to locate uniform \"alpha\" in YUV shader\n")); - } else { - glUniform1f(loc, FIX2FLT(visual->mat_2d.alpha) ); - } - } else -#endif - { - if (visual->mat_2d.alpha != FIX_ONE) { - glEnable(GL_BLEND); - visual_3d_enable_antialias(visual, 0); - } else { - //disable blending only if no texture ! - if (!tr_state->mesh_num_textures) - glDisable(GL_BLEND); - visual_3d_enable_antialias(visual, visual->compositor->antiAlias ? 1 : 0); - } + if (visual->mat_2d.alpha != FIX_ONE) { + glEnable(GL_BLEND); + visual_3d_enable_antialias(visual, 0); + } else { + //disable blending only if no texture ! + if (!tr_state->mesh_num_textures) + glDisable(GL_BLEND); + visual_3d_enable_antialias(visual, visual->compositor->aa ? 1 : 0); + } #ifdef GPAC_USE_GLES1X - glColor4x( FIX2INT(visual->mat_2d.red * 255), FIX2INT(visual->mat_2d.green * 255), FIX2INT(visual->mat_2d.blue * 255), FIX2INT(visual->mat_2d.alpha * 255)); + glColor4x( FIX2INT(visual->mat_2d.red * 255), FIX2INT(visual->mat_2d.green * 255), FIX2INT(visual->mat_2d.blue * 255), FIX2INT(visual->mat_2d.alpha * 255)); #elif defined(GPAC_FIXED_POINT) - glColor4f(FIX2FLT(visual->mat_2d.red), FIX2FLT(visual->mat_2d.green), FIX2FLT(visual->mat_2d.blue), FIX2FLT(visual->mat_2d.alpha)); + glColor4f(FIX2FLT(visual->mat_2d.red), FIX2FLT(visual->mat_2d.green), FIX2FLT(visual->mat_2d.blue), FIX2FLT(visual->mat_2d.alpha)); #else - glColor4f(visual->mat_2d.red, visual->mat_2d.green, visual->mat_2d.blue, visual->mat_2d.alpha); + glColor4f(visual->mat_2d.red, visual->mat_2d.green, visual->mat_2d.blue, visual->mat_2d.alpha); #endif - } - } - -#if !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_TINYGL) - else if (visual->compositor->visual->current_texture_glsl_program) { - int loc = glGetUniformLocation(visual->compositor->visual->current_texture_glsl_program, "alpha"); - if (loc == -1) { - GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to locate uniform \"alpha\" in YUV shader\n")); - } else { - glUniform1f(loc, 1.0 ); - } } -#endif - //setup material color if (visual->has_material) { u32 i; - GL_CHECK_ERR + GL_CHECK_ERR() for (i=0; i<4; i++) { GLenum mode; Fixed *rgba = (Fixed *) & visual->materials[i]; @@ -2975,14 +2788,14 @@ static void visual_3d_draw_mesh(GF_TraverseState *tr_state, GF_Mesh *mesh) #else glMaterialfv(GL_FRONT_AND_BACK, mode, _rgba); #endif - GL_CHECK_ERR + GL_CHECK_ERR() } #ifdef GPAC_USE_GLES1X glMaterialx(GL_FRONT_AND_BACK, GL_SHININESS, visual->shininess * 128); #else glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, FIX2FLT(visual->shininess) * 128); #endif - GL_CHECK_ERR + GL_CHECK_ERR() } //otherwise setup mesh color @@ -3139,8 +2952,8 @@ static void visual_3d_draw_mesh(GF_TraverseState *tr_state, GF_Mesh *mesh) glNormalPointer(normal_type, sizeof(GF_Vertex), ((char *)base_address + MESH_NORMAL_OFFSET)); if (mesh->mesh_type==MESH_TRIANGLES) { - if (compositor->backcull - && (!tr_state->mesh_is_transparent || (compositor->backcull ==GF_BACK_CULL_ALPHA) ) + if (compositor->bcull + && (!tr_state->mesh_is_transparent || (compositor->bcull ==GF_BACK_CULL_ALPHA) ) && (mesh->flags & MESH_IS_SOLID)) { glEnable(GL_CULL_FACE); glFrontFace((mesh->flags & MESH_IS_CW) ? GL_CW : GL_CCW); @@ -3150,9 +2963,9 @@ static void visual_3d_draw_mesh(GF_TraverseState *tr_state, GF_Mesh *mesh) } } - GL_CHECK_ERR + GL_CHECK_ERR() visual_3d_do_draw_mesh(tr_state, mesh); - GL_CHECK_ERR + GL_CHECK_ERR() glDisableClientState(GL_VERTEX_ARRAY); if (has_col) glDisableClientState(GL_COLOR_ARRAY); @@ -3183,15 +2996,14 @@ static void visual_3d_draw_mesh(GF_TraverseState *tr_state, GF_Mesh *mesh) glDisable(GL_COLOR_MATERIAL); //reset all our states - if (visual->num_clips) - visual_3d_reset_clippers(visual); - visual->has_material_2d = 0; + visual_3d_reset_clippers(visual); + visual->has_material_2d = GF_FALSE; visual->has_material = 0; visual->state_color_on = 0; if (tr_state->mesh_is_transparent) glDisable(GL_BLEND); tr_state->mesh_is_transparent = 0; - GL_CHECK_ERR + GL_CHECK_ERR() #endif } @@ -3227,7 +3039,15 @@ static void visual_3d_draw_normals(GF_TraverseState *tr_state, GF_Mesh *mesh) visual_3d_set_debug_color(0); - if (tr_state->visual->compositor->draw_normals==GF_NORMALS_VERTEX) { +#if !defined(GPAC_DISABLE_3D) && !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) + //in shader mode force pushing projection and modelview using fixed pipeline API + if (!tr_state->visual->compositor->shader_mode_disabled) { + tr_state->visual->needs_projection_matrix_reload=GF_TRUE; + visual_3d_update_matrices(tr_state); + } +#endif + + if (tr_state->visual->compositor->norms==GF_NORMALS_VERTEX) { IDX_TYPE *idx = mesh->indices; for (i=0; ii_count; i+=3) { for (j=0; j<3; j++) { @@ -3301,19 +3121,19 @@ void visual_3d_draw_aabb_nodeBounds(GF_TraverseState *tr_state, AABBNode *node) gf_mx_add_translation(&tr_state->model_matrix, c.x, c.y, c.z); gf_mx_add_scale(&tr_state->model_matrix, s.x, s.y, s.z); - visual_3d_draw_mesh(tr_state, tr_state->visual->compositor->unit_bbox); + visual_3d_draw_mesh(tr_state, NULL); gf_mx_copy(tr_state->model_matrix, mx); } } -void visual_3d_draw_bbox_ex(GF_TraverseState *tr_state, GF_BBox *box, Bool is_debug) +void visual_3d_draw_bbox(GF_TraverseState *tr_state, GF_BBox *box, Bool is_debug) { GF_Matrix mx; SFVec3f c, s; if (! is_debug) { - visual_3d_set_debug_color(tr_state->visual->compositor->highlight_stroke); + visual_3d_set_debug_color(tr_state->visual->compositor->hlline); } gf_vec_diff(s, box->max_edge, box->min_edge); @@ -3325,23 +3145,18 @@ void visual_3d_draw_bbox_ex(GF_TraverseState *tr_state, GF_BBox *box, Bool is_de gf_mx_add_translation(&tr_state->model_matrix, c.x, c.y, c.z); gf_mx_add_scale(&tr_state->model_matrix, s.x, s.y, s.z); - visual_3d_draw_mesh(tr_state, tr_state->visual->compositor->unit_bbox); + visual_3d_draw_mesh(tr_state, NULL); gf_mx_copy(tr_state->model_matrix, mx); } -void visual_3d_draw_bbox(GF_TraverseState *tr_state, GF_BBox *box) -{ - visual_3d_draw_bbox_ex(tr_state, box, 0); -} - static void visual_3d_draw_bounds(GF_TraverseState *tr_state, GF_Mesh *mesh) { visual_3d_set_debug_color(0); - if (mesh->aabb_root && (tr_state->visual->compositor->draw_bvol==GF_BOUNDS_AABB)) { + if (mesh->aabb_root && (tr_state->visual->compositor->bvol==GF_BOUNDS_AABB)) { visual_3d_draw_aabb_nodeBounds(tr_state, mesh->aabb_root); } else { - visual_3d_draw_bbox_ex(tr_state, &mesh->bounds, 1); + visual_3d_draw_bbox(tr_state, &mesh->bounds, GF_TRUE); } } @@ -3350,8 +3165,14 @@ void visual_3d_mesh_paint(GF_TraverseState *tr_state, GF_Mesh *mesh) #if !defined(GPAC_USE_GLES2) Bool mesh_drawn = 0; #endif + + GL_CHECK_ERR() + + gf_rmt_begin_gl(visual_3d_mesh_paint); + glGetError(); + GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[V3D] Drawing mesh %p\n", mesh)); - if (tr_state->visual->compositor->wiremode != GF_WIREFRAME_ONLY) { + if (tr_state->visual->compositor->wire != GF_WIREFRAME_ONLY) { visual_3d_draw_mesh(tr_state, mesh); #if !defined(GPAC_USE_GLES2) mesh_drawn = 1; @@ -3359,7 +3180,7 @@ void visual_3d_mesh_paint(GF_TraverseState *tr_state, GF_Mesh *mesh) } #if !defined(GPAC_USE_GLES2) - if (tr_state->visual->compositor->draw_normals) { + if (tr_state->visual->compositor->norms) { if (!mesh_drawn) { visual_3d_update_matrices(tr_state); mesh_drawn=1; @@ -3367,7 +3188,7 @@ void visual_3d_mesh_paint(GF_TraverseState *tr_state, GF_Mesh *mesh) visual_3d_draw_normals(tr_state, mesh); } - if ((mesh->mesh_type==MESH_TRIANGLES) && (tr_state->visual->compositor->wiremode != GF_WIREFRAME_NONE)) { + if ((mesh->mesh_type==MESH_TRIANGLES) && (tr_state->visual->compositor->wire != GF_WIREFRAME_NONE)) { glDisable(GL_LIGHTING); visual_3d_set_debug_color(0xFFFFFFFF); @@ -3388,10 +3209,13 @@ void visual_3d_mesh_paint(GF_TraverseState *tr_state, GF_Mesh *mesh) #endif - if (tr_state->visual->compositor->draw_bvol) + if (tr_state->visual->compositor->bvol) visual_3d_draw_bounds(tr_state, mesh); GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[V3D] Done drawing mesh %p\n", mesh)); + + gf_rmt_end_gl(); + glGetError(); } #if !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES2) @@ -3663,14 +3487,11 @@ void visual_3d_fill_rect(GF_VisualManager *visual, GF_Rect rc, SFColorRGBA color #endif } +//note that we always use glReadPixel even if the actual rendering target is a texture +//this is the recommended way by khronos any way GF_Err compositor_3d_get_screen_buffer(GF_Compositor *compositor, GF_VideoSurface *fb, u32 depth_dump_mode) { - /*FIXME*/ u32 i; -#ifndef GPAC_USE_TINYGL - char*tmp; - u32 hy; -#endif //GPAC_USE_TINYGL fb->width = compositor->display_width; fb->height = compositor->display_height; @@ -3753,7 +3574,6 @@ GF_Err compositor_3d_get_screen_buffer(GF_Compositor *compositor, GF_VideoSurfac glReadPixels(0, 0, fb->width, fb->height, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, depth_data); if (depth_dump_mode==2) { - u32 i; fb->pixel_format = GF_PIXEL_RGBDS; /*this corresponds to the RGBDS ordering*/ @@ -3769,7 +3589,6 @@ GF_Err compositor_3d_get_screen_buffer(GF_Compositor *compositor, GF_VideoSurfac } /*this corresponds to RGBD ordering*/ } else if (depth_dump_mode==3) { - u32 i; fb->pixel_format = GF_PIXEL_RGBD; for (i=0; iheight*fb->width; i++) fb->video_buffer[i*4+3] = depth_data[i]; @@ -3782,23 +3601,37 @@ GF_Err compositor_3d_get_screen_buffer(GF_Compositor *compositor, GF_VideoSurfac #endif /*GPAC_USE_GLES1X*/ } else { /*if (compositor->user && (compositor->user->init_flags & GF_TERM_WINDOW_TRANSPARENT))*/ u32 size; - fb->pitch_x = 4; - fb->pitch_y = 4*compositor->vp_width; + fb->pitch_x = 3; + if (!compositor->opfmt && compositor->dyn_filter_mode) { + fb->pitch_x = 4; + } else if (compositor->opfmt==GF_PIXEL_RGBA) { + fb->pitch_x = 4; + } + + fb->pitch_y = fb->pitch_x * compositor->vp_width; size = fb->pitch_y * fb->height; if (compositor->screen_buffer_alloc_size < size) { compositor->screen_buffer_alloc_size = size; compositor->screen_buffer = gf_realloc(compositor->screen_buffer, compositor->screen_buffer_alloc_size); + compositor->line_buffer = gf_realloc(compositor->line_buffer, fb->pitch_y); + } fb->video_buffer = compositor->screen_buffer; - fb->pixel_format = GF_PIXEL_RGBA; - glReadPixels(0, 0, fb->width, fb->height, GL_RGBA, GL_UNSIGNED_BYTE, fb->video_buffer); + fb->pixel_format = (fb->pitch_x == 4) ? GF_PIXEL_RGBA : GF_PIXEL_RGB; + + if (compositor->fbo_id) compositor_3d_enable_fbo(compositor, GF_TRUE); + + glReadPixels(0, 0, fb->width, fb->height, (fb->pitch_x == 4) ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, fb->video_buffer); + + if (compositor->fbo_id) compositor_3d_enable_fbo(compositor, GF_FALSE); + /* } else { fb->pitch_x = 3; fb->pitch_y = 3*compositor->vp_width; fb->video_buffer = (char*)gf_malloc(sizeof(char) * fb->pitch_y * fb->height); - fb->pixel_format = GF_PIXEL_RGB_24; + fb->pixel_format = GF_PIXEL_RGB; glReadPixels(compositor->vp_x, compositor->vp_y, fb->width, fb->height, GL_RGB, GL_UNSIGNED_BYTE, fb->video_buffer); */ @@ -3806,14 +3639,12 @@ GF_Err compositor_3d_get_screen_buffer(GF_Compositor *compositor, GF_VideoSurfac #ifndef GPAC_USE_TINYGL /*flip image (openGL always handle image data bottom to top) */ - tmp = (char*)gf_malloc(sizeof(char)*fb->pitch_y); - hy = fb->height/2; + u32 hy = fb->height/2; for (i=0; ivideo_buffer+ i*fb->pitch_y, fb->pitch_y); + memcpy(compositor->line_buffer, fb->video_buffer+ i*fb->pitch_y, fb->pitch_y); memcpy(fb->video_buffer + i*fb->pitch_y, fb->video_buffer + (fb->height - 1 - i) * fb->pitch_y, fb->pitch_y); - memcpy(fb->video_buffer + (fb->height - 1 - i) * fb->pitch_y, tmp, fb->pitch_y); + memcpy(fb->video_buffer + (fb->height - 1 - i) * fb->pitch_y, compositor->line_buffer, fb->pitch_y); } - gf_free(tmp); #endif return GF_OK; } @@ -3836,7 +3667,7 @@ GF_Err compositor_3d_get_offscreen_buffer(GF_Compositor *compositor, GF_VideoSur if (view_idx>=compositor->visual->nb_views) return GF_BAD_PARAM; fb->width = compositor->visual->auto_stereo_width; fb->height = compositor->visual->auto_stereo_height; - fb->pixel_format = GF_PIXEL_RGB_24; + fb->pixel_format = GF_PIXEL_RGB; fb->pitch_y = 3*fb->width; fb->video_buffer = gf_malloc(sizeof(char)*3*fb->width*fb->height); if (!fb->video_buffer) return GF_OUT_OF_MEM; @@ -3872,8 +3703,6 @@ void visual_3d_point_sprite(GF_VisualManager *visual, Drawable *stack, GF_Textur Fixed x, y; Float inc, scale; Bool in_strip; - Float delta = 0; - Bool first_pass = 2; GF_Node *txtrans = NULL; if ((visual->compositor->depth_gl_type==GF_SC_DEPTH_GL_POINTS) && visual->compositor->gl_caps.point_sprite) { @@ -3931,7 +3760,8 @@ void visual_3d_point_sprite(GF_VisualManager *visual, Drawable *stack, GF_Textur } if (visual->compositor->depth_gl_type==GF_SC_DEPTH_GL_STRIPS) { - delta = FIX2FLT(visual->compositor->depth_gl_strips_filter); + u32 first_pass; + Float delta = FIX2FLT(visual->compositor->depth_gl_strips_filter); if (!delta) first_pass = 2; else first_pass = 1; @@ -3955,7 +3785,7 @@ restart: if (!tr_state->pixel_metrics) inc /= FIX2FLT(tr_state->min_hsize); // x = 0; y = 1; - y = gf_mulfix(y, INT2FIX(txh->height/2));; + y = gf_mulfix(y, INT2FIX(txh->height/2)); if (!tr_state->pixel_metrics) y = gf_divfix(y, tr_state->min_hsize); in_strip = 0; @@ -3973,10 +3803,12 @@ restart: if (first_pass==1) { if ((z1>delta) || (z2>delta)) { - if (0 && in_strip) { +#if 0 + if (in_strip) { glEnd(); in_strip = 0; } +#endif x += FLT2FIX(inc); continue; } @@ -4079,7 +3911,7 @@ restart: data = (u8 *) gf_sc_texture_get_data(txh, &pixel_format); if (!data) return; - if (pixel_format!=GF_PIXEL_RGB_24_DEPTH) return; + if (pixel_format!=GF_PIXEL_RGB_DEPTH) return; data += txh->height*txh->width*3; for (h=0; hheight; h++) { @@ -4104,4 +3936,93 @@ restart: } + + +GF_Err compositor_3d_setup_fbo(u32 width, u32 height, u32 *fbo_id, u32 *tx_id, u32 *depth_id) +{ +#if defined(GPAC_USE_GLES1X) + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to setup FBO object: not supported on OpenGL ES 1.x\n")); + return GF_NOT_SUPPORTED; +#else + if (! *tx_id) { + glGenTextures(1, tx_id); + } + glBindTexture(GL_TEXTURE_2D, *tx_id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + + if (! *fbo_id) + glGenFramebuffers(1, fbo_id); + + glBindFramebuffer(GL_FRAMEBUFFER, *fbo_id); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *tx_id, 0); + + if (! *depth_id) + glGenRenderbuffers(1, depth_id); + + glBindRenderbuffer(GL_RENDERBUFFER, *depth_id); +#if defined(GPAC_CONFIG_IOS) || defined(GPAC_CONFIG_ANDROID) + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height); +#else + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); +#endif + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, *depth_id); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + switch (status) { + case GL_FRAMEBUFFER_COMPLETE: + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to setup FBO object: FBO status %08x\n", status)); + return GF_NOT_SUPPORTED; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return GF_OK; +#endif + +} + +void compositor_3d_delete_fbo(u32 *fbo_id, u32 *fbo_tx_id, u32 *fbo_depth_id, Bool keep_tx_id) +{ +#ifndef GPAC_USE_GLES1X + if (*fbo_id) { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, fbo_id); + *fbo_id = 0; + } + if (*fbo_depth_id) { + glDeleteRenderbuffers(1, fbo_depth_id); + *fbo_depth_id = 0; + } + if (*fbo_tx_id && !keep_tx_id) { + glDeleteTextures(1, fbo_tx_id); + *fbo_tx_id = 0; + } +#endif +} + +u32 compositor_3d_get_fbo_pixfmt() +{ + return GL_TEXTURE_2D; +} + +void compositor_3d_enable_fbo(GF_Compositor *compositor, Bool enable) +{ +#ifndef GPAC_USE_GLES1X + glBindFramebuffer(GL_FRAMEBUFFER, enable ? compositor->fbo_id : 0); + if (!enable) + glBindTexture(GL_TEXTURE_2D, 0); +#endif + +} + +void visual_3d_clean_state(GF_VisualManager *visual) +{ + glGetError(); +} + #endif // GPAC_DISABLE_3D diff --git a/src/compositor/x3d_geometry.c b/src/compositor/x3d_geometry.c index 76e15f2..74919dc 100644 --- a/src/compositor/x3d_geometry.c +++ b/src/compositor/x3d_geometry.c @@ -551,9 +551,11 @@ static void TraverseIndexedTriangleSet(GF_Node *node, void *rs, Bool is_destroy) static void ITS_SetIndex(GF_Node *node, GF_Route *route) { - X_IndexedTriangleSet *its = (X_IndexedTriangleSet*)node; - gf_sg_vrml_field_copy(&its->index, &its->set_index, GF_SG_VRML_MFINT32); - gf_sg_vrml_mf_reset(&its->set_index, GF_SG_VRML_MFINT32); + if (node) { + X_IndexedTriangleSet *its = (X_IndexedTriangleSet*)node; + gf_sg_vrml_field_copy(&its->index, &its->set_index, GF_SG_VRML_MFINT32); + gf_sg_vrml_mf_reset(&its->set_index, GF_SG_VRML_MFINT32); + } } void compositor_init_indexed_triangle_set(GF_Compositor *compositor, GF_Node *node) @@ -562,6 +564,13 @@ void compositor_init_indexed_triangle_set(GF_Compositor *compositor, GF_Node *no drawable_3d_new(node); gf_node_set_callback_function(node, TraverseIndexedTriangleSet); its->on_set_index = ITS_SetIndex; + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + ITS_SetIndex(NULL, NULL); + } +#endif + } @@ -808,9 +817,11 @@ static void TraverseIndexedTriangleStripSet(GF_Node *node, void *rs, Bool is_des static void ITSS_SetIndex(GF_Node *node, GF_Route *route) { - X_IndexedTriangleStripSet *itss = (X_IndexedTriangleStripSet*)node; - gf_sg_vrml_field_copy(&itss->index, &itss->set_index, GF_SG_VRML_MFINT32); - gf_sg_vrml_mf_reset(&itss->set_index, GF_SG_VRML_MFINT32); + if (node) { + X_IndexedTriangleStripSet *itss = (X_IndexedTriangleStripSet*)node; + gf_sg_vrml_field_copy(&itss->index, &itss->set_index, GF_SG_VRML_MFINT32); + gf_sg_vrml_mf_reset(&itss->set_index, GF_SG_VRML_MFINT32); + } } void compositor_init_indexed_triangle_strip_set(GF_Compositor *compositor, GF_Node *node) @@ -819,6 +830,12 @@ void compositor_init_indexed_triangle_strip_set(GF_Compositor *compositor, GF_No drawable_3d_new(node); gf_node_set_callback_function(node, TraverseIndexedTriangleStripSet); itss->on_set_index = ITSS_SetIndex; + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + ITSS_SetIndex(NULL, NULL); + } +#endif } @@ -1037,9 +1054,11 @@ static void TraverseIndexedTriangleFanSet(GF_Node *node, void *rs, Bool is_destr static void ITFS_SetIndex(GF_Node *node, GF_Route *route) { - X_IndexedTriangleFanSet *itfs = (X_IndexedTriangleFanSet *)node; - gf_sg_vrml_field_copy(&itfs->index, &itfs->set_index, GF_SG_VRML_MFINT32); - gf_sg_vrml_mf_reset(&itfs->set_index, GF_SG_VRML_MFINT32); + if (node) { + X_IndexedTriangleFanSet *itfs = (X_IndexedTriangleFanSet *)node; + gf_sg_vrml_field_copy(&itfs->index, &itfs->set_index, GF_SG_VRML_MFINT32); + gf_sg_vrml_mf_reset(&itfs->set_index, GF_SG_VRML_MFINT32); + } } void compositor_init_indexed_triangle_fan_set(GF_Compositor *compositor, GF_Node *node) @@ -1048,6 +1067,12 @@ void compositor_init_indexed_triangle_fan_set(GF_Compositor *compositor, GF_Node drawable_3d_new(node); gf_node_set_callback_function(node, TraverseIndexedTriangleFanSet); itfs->on_set_index = ITFS_SetIndex; + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + ITFS_SetIndex(NULL, NULL); + } +#endif } #endif /*GPAC_DISABLE_3D*/ diff --git a/src/crypto/g_crypt.c b/src/crypto/g_crypt.c new file mode 100644 index 0000000..c7dd19e --- /dev/null +++ b/src/crypto/g_crypt.c @@ -0,0 +1,105 @@ +/* +* GPAC - Multimedia Framework C SDK +* +* Authors: Jean Le Feuvre +* Copyright (c) Telecom ParisTech 2000-2018 +* All rights reserved +* +* This file is part of GPAC / crypto lib sub-project +* +* GPAC is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* GPAC is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; see the file COPYING. If not, write to +* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +* +*/ + +#include + +GF_EXPORT +GF_Crypt *gf_crypt_open(GF_CRYPTO_ALGO algorithm, GF_CRYPTO_MODE mode) +{ + GF_Crypt *td; + GF_Err e; + + GF_SAFEALLOC(td, GF_Crypt); + if (td == NULL) return NULL; + +#ifdef GPAC_HAS_SSL + e = gf_crypt_open_open_openssl(td, mode); +#else + e = gf_crypt_open_open_tinyaes(td, mode); +#endif + + if (e != GF_OK) { + gf_free(td); + return NULL; + } + return td; +} + +GF_EXPORT +void gf_crypt_close(GF_Crypt *td) +{ + if (!td) return; + td->_deinit_crypt(td); + gf_free(td->context); + gf_free(td); +} + +GF_EXPORT +GF_Err gf_crypt_set_key(GF_Crypt *td, void *key) +{ + td->_set_key(td, key); + return GF_OK; +} + +GF_EXPORT +GF_Err gf_crypt_set_IV(GF_Crypt *td, const void *iv, u32 size) +{ + if (!td) return GF_BAD_PARAM; + return td->_set_state(td, (void *)iv, size); +} + +GF_Err gf_crypt_get_IV(GF_Crypt *td, void *iv, u32 *size) +{ + if (!td) return GF_BAD_PARAM; + return td->_get_state(td, iv, size); +} + + +GF_EXPORT +GF_Err gf_crypt_init(GF_Crypt *td, void *key, const void *IV) +{ + GF_Err e; + + e = td->_init_crypt(td, key, IV); + if (e != GF_OK) gf_crypt_close(td); + //need for openssl we have 2 passes init + td->_set_key(td, key); + return e; +} + +GF_EXPORT +GF_Err gf_crypt_encrypt(GF_Crypt *td, void *plaintext, u32 len) +{ + if (!td) return GF_BAD_PARAM; + return td->_crypt(td, plaintext, len); +} + +GF_EXPORT +GF_Err gf_crypt_decrypt(GF_Crypt *td, void *ciphertext, u32 len) +{ + if (!td) return GF_BAD_PARAM; + if (!len) return GF_OK; + return td->_decrypt(td, ciphertext, len); +} diff --git a/src/crypto/g_crypt_openssl.c b/src/crypto/g_crypt_openssl.c new file mode 100644 index 0000000..420a73b --- /dev/null +++ b/src/crypto/g_crypt_openssl.c @@ -0,0 +1,226 @@ +/* +* GPAC - Multimedia Framework C SDK +* +* Authors: Rodolphe Fouquet +* Copyright (c) Motion Spell 2016 +* All rights reserved +* +* This file is part of GPAC / crypto lib sub-project +* +* GPAC is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* GPAC is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; see the file COPYING. If not, write to +* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +* +*/ + +#include + +#ifdef GPAC_HAS_SSL +#include +#include + +#include + +typedef struct { + AES_KEY enc_key, dec_key; + + u8 block[AES_BLOCK_SIZE]; + u8 padded_input[AES_BLOCK_SIZE]; // use only when the input length is inferior to the algo block size + u8 previous_ciphertext[AES_BLOCK_SIZE]; +} Openssl_ctx_cbc; + +/** CBC STUFF **/ + +GF_Err gf_crypt_init_openssl_cbc(GF_Crypt* td, void *key, const void *iv) +{ + Openssl_ctx_cbc *ctx = (Openssl_ctx_cbc*)td->context; + if (!ctx) { + GF_SAFEALLOC(ctx, Openssl_ctx_cbc); + if (ctx == NULL) return GF_OUT_OF_MEM; + td->context = ctx; + } + + if (iv != NULL) { + memcpy(ctx->previous_ciphertext, iv, AES_BLOCK_SIZE); + } + return GF_OK; +} + +void gf_crypt_deinit_openssl_cbc(GF_Crypt* td) +{ +} + +void gf_set_key_openssl_cbc(GF_Crypt* td, void *key) +{ + Openssl_ctx_cbc* ctx = (Openssl_ctx_cbc*)td->context; + AES_set_encrypt_key(key, 128, &(ctx->enc_key)); + AES_set_decrypt_key(key, 128, &(ctx->dec_key)); +} + +GF_Err gf_crypt_set_IV_openssl_cbc(GF_Crypt* td, const u8 *iv, u32 iv_size) +{ + Openssl_ctx_cbc* ctx = (Openssl_ctx_cbc*)td->context; + memcpy(ctx->previous_ciphertext, iv, iv_size); + return GF_OK; +} + +GF_Err gf_crypt_get_IV_openssl_cbc(GF_Crypt* td, u8 *iv, u32 *iv_size) +{ + Openssl_ctx_cbc* ctx = (Openssl_ctx_cbc*)td->context; + *iv_size = AES_BLOCK_SIZE; + memcpy(iv, ctx->previous_ciphertext, AES_BLOCK_SIZE); + return GF_OK; +} + +GF_Err gf_crypt_crypt_openssl_cbc(GF_Crypt* td, u8 *plaintext, u32 len, u32 aes_crypt_type) { + Openssl_ctx_cbc* ctx = (Openssl_ctx_cbc*)td->context; + u32 iteration; + AES_KEY *key = aes_crypt_type ? &ctx->enc_key : &ctx->dec_key; + u32 numberOfIterations = len / AES_BLOCK_SIZE; + if (numberOfIterations * AES_BLOCK_SIZE < len) numberOfIterations++; + + for (iteration = 0; iteration < numberOfIterations; ++iteration) { + if (len - iteration*AES_BLOCK_SIZE < AES_BLOCK_SIZE) { + memset(ctx->padded_input, 0, AES_BLOCK_SIZE); + memcpy(ctx->padded_input, plaintext, len - iteration*AES_BLOCK_SIZE); + AES_cbc_encrypt(plaintext + iteration*AES_BLOCK_SIZE, ctx->block, AES_BLOCK_SIZE, key, ctx->previous_ciphertext, aes_crypt_type); + memcpy(plaintext + iteration*AES_BLOCK_SIZE, ctx->block, len - iteration*AES_BLOCK_SIZE); + } else { + AES_cbc_encrypt(plaintext + iteration*AES_BLOCK_SIZE, plaintext + iteration*AES_BLOCK_SIZE, AES_BLOCK_SIZE, key, ctx->previous_ciphertext, aes_crypt_type); + } + } + return GF_OK; +} + +GF_Err gf_crypt_encrypt_openssl_cbc(GF_Crypt* td, u8 *plaintext, u32 len) +{ + return gf_crypt_crypt_openssl_cbc(td, plaintext, len, AES_ENCRYPT); +} + +GF_Err gf_crypt_decrypt_openssl_cbc(GF_Crypt* td, u8 *ciphertext, u32 len) +{ + return gf_crypt_crypt_openssl_cbc(td, ciphertext, len, AES_DECRYPT); +} + +typedef struct { + AES_KEY key; + + u8 cyphered_iv[16]; + u8 iv[16]; + unsigned int c_counter_pos; +} Openssl_ctx_ctr; + + +/** CTR STUFF **/ + +void gf_set_key_openssl_ctr(GF_Crypt* td, void *key) +{ + Openssl_ctx_ctr* ctx = (Openssl_ctx_ctr*)td->context; + AES_set_encrypt_key(key, 128, &(ctx->key)); +} + +GF_Err gf_crypt_set_IV_openssl_ctr(GF_Crypt* td, const u8 *iv, u32 iv_size) +{ + Openssl_ctx_ctr* ctx = (Openssl_ctx_ctr*)td->context; + + ctx->c_counter_pos = ((u8*)iv)[0]; + memcpy(ctx->iv, &((u8*)iv)[1], iv_size - 1); + memset(ctx->cyphered_iv, 0, 16); + return GF_OK; +} + +GF_Err gf_crypt_get_IV_openssl_ctr(GF_Crypt* td, u8 *iv, u32 *iv_size) +{ + Openssl_ctx_ctr* ctx = (Openssl_ctx_ctr*)td->context; + + *iv_size = AES_BLOCK_SIZE + 1; + + ((u8 *)iv)[0] = ctx->c_counter_pos; + memcpy(&((u8 *)iv)[1], ctx->iv, AES_BLOCK_SIZE); + return GF_OK; +} + +GF_Err gf_crypt_init_openssl_ctr(GF_Crypt* td, void *key, const void *iv) +{ + Openssl_ctx_ctr *ctx = (Openssl_ctx_ctr*)td->context; + if (!ctx) { + GF_SAFEALLOC(ctx, Openssl_ctx_ctr); + if (!ctx) return GF_OUT_OF_MEM; + + td->context = ctx; + } + ctx->c_counter_pos = 0; + if (iv != NULL) { + memcpy(ctx->iv, &((u8*)iv)[0], AES_BLOCK_SIZE); + } + + return GF_OK; +} + +void gf_crypt_deinit_openssl_ctr(GF_Crypt* td) +{ +} + +GF_Err gf_crypt_crypt_openssl_ctr(GF_Crypt* td, u8 *plaintext, u32 len) +{ + Openssl_ctx_ctr* ctx = (Openssl_ctx_ctr*)td->context; + + CRYPTO_ctr128_encrypt(plaintext, plaintext, len, &ctx->key, ctx->iv, ctx->cyphered_iv, &ctx->c_counter_pos, (block128_f)AES_encrypt); + + return GF_OK; +} + +GF_Err gf_crypt_encrypt_openssl_ctr(GF_Crypt* td, u8 *plaintext, u32 len) +{ + return gf_crypt_crypt_openssl_ctr(td, plaintext, len); +} + +GF_Err gf_crypt_decrypt_openssl_ctr(GF_Crypt* td, u8 *ciphertext, u32 len) +{ + return gf_crypt_crypt_openssl_ctr(td, ciphertext, len); +} + + +GF_Err gf_crypt_open_open_openssl(GF_Crypt* td, GF_CRYPTO_MODE mode) +{ + td->mode = mode; + switch (td->mode) { + case GF_CBC: + td->_init_crypt = gf_crypt_init_openssl_cbc; + td->_deinit_crypt = gf_crypt_deinit_openssl_cbc; + td->_set_key = gf_set_key_openssl_cbc; + td->_crypt = gf_crypt_encrypt_openssl_cbc; + td->_decrypt = gf_crypt_decrypt_openssl_cbc; + td->_get_state = gf_crypt_get_IV_openssl_cbc; + td->_set_state = gf_crypt_set_IV_openssl_cbc; + break; + case GF_CTR: + td->_init_crypt = gf_crypt_init_openssl_ctr; + td->_deinit_crypt = gf_crypt_deinit_openssl_ctr; + td->_set_key = gf_set_key_openssl_ctr; + td->_crypt = gf_crypt_encrypt_openssl_ctr; + td->_decrypt = gf_crypt_decrypt_openssl_ctr; + td->_get_state = gf_crypt_get_IV_openssl_ctr; + td->_set_state = gf_crypt_set_IV_openssl_ctr; + break; + default: + return GF_BAD_PARAM; + break; + } + + td->algo = GF_AES_128; + return GF_OK; +} + +#endif + diff --git a/src/crypto/g_crypt_tinyaes.c b/src/crypto/g_crypt_tinyaes.c new file mode 100644 index 0000000..c81f497 --- /dev/null +++ b/src/crypto/g_crypt_tinyaes.c @@ -0,0 +1,201 @@ +/* +* GPAC - Multimedia Framework C SDK +* +* Authors: Jean Le Feuvre +* Copyright (c) Telecom ParisTech 2018 +* All rights reserved +* +* This file is part of GPAC / crypto lib sub-project +* +* GPAC is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* GPAC is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; see the file COPYING. If not, write to +* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +* +*/ + +#include + +#ifndef GPAC_HAS_SSL +#include "tiny_aes.h" + +#include + +/** CBC mode **/ + +GF_Err gf_crypt_init_tinyaes_cbc(GF_Crypt* td, void *key, const void *iv) +{ + struct AES_ctx *ctx = (struct AES_ctx *)td->context; + if (!ctx) { + GF_SAFEALLOC(ctx, struct AES_ctx); + if (ctx == NULL) return GF_OUT_OF_MEM; + + td->context = ctx; + } + + + if (iv != NULL) { + AES_init_ctx_iv(ctx, key, iv); + } else { + AES_init_ctx(ctx, key); + } + return GF_OK; +} + +void gf_crypt_deinit_tinyaes_cbc(GF_Crypt* td) +{ +} + +void gf_set_key_tinyaes_cbc(GF_Crypt* td, void *key) +{ + struct AES_ctx* ctx = (struct AES_ctx *)td->context; + AES_init_ctx(ctx, key); +} + +GF_Err gf_crypt_set_IV_tinyaes_cbc(GF_Crypt* td, const u8 *iv, u32 iv_size) +{ + struct AES_ctx* ctx = (struct AES_ctx *)td->context; + if (iv_size>AES_BLOCKLEN) return GF_BAD_PARAM; + + AES_ctx_set_iv(ctx, iv); + return GF_OK; +} + +GF_Err gf_crypt_get_IV_tinyaes_cbc(GF_Crypt* td, u8 *iv, u32 *iv_size) +{ + struct AES_ctx* ctx = (struct AES_ctx *)td->context; + AES_ctx_get_iv(ctx, iv); + *iv_size = AES_BLOCKLEN; + + return GF_OK; +} + + +GF_Err gf_crypt_encrypt_tinyaes_cbc(GF_Crypt* td, u8 *plaintext, u32 len) +{ + struct AES_ctx* ctx = (struct AES_ctx *)td->context; + AES_CBC_encrypt_buffer(ctx, plaintext, len); + return GF_OK; +} + +GF_Err gf_crypt_decrypt_tinyaes_cbc(GF_Crypt* td, u8 *ciphertext, u32 len) +{ + struct AES_ctx* ctx = (struct AES_ctx *)td->context; + if (lencontext; + AES_init_ctx(ctx, key); +} + +GF_Err gf_crypt_set_IV_tinyaes_ctr(GF_Crypt* td, const u8 *iv, u32 iv_size) +{ + struct AES_ctx* ctx = (struct AES_ctx *)td->context; + + if (iv_size>AES_BLOCKLEN) { + ctx->counter_pos = iv[0]; + AES_ctx_set_iv(ctx, &((u8*)iv)[1]); + } else { + AES_ctx_set_iv(ctx, iv); + } + return GF_OK; +} + +GF_Err gf_crypt_get_IV_tinyaes_ctr(GF_Crypt* td, u8 *iv, u32 *iv_size) +{ + struct AES_ctx* ctx = (struct AES_ctx *)td->context; + *iv_size = AES_BLOCKLEN + 1; + iv[0] = ctx->counter_pos; + AES_ctx_get_iv(ctx, iv + 1); + return GF_OK; +} + +GF_Err gf_crypt_init_tinyaes_ctr(GF_Crypt* td, void *key, const void *iv) +{ + struct AES_ctx* ctx = (struct AES_ctx* ) td->context; + if (!ctx) { + GF_SAFEALLOC(ctx, struct AES_ctx); + if (ctx == NULL) return GF_OUT_OF_MEM; + td->context = ctx; + } + + /* For ctr */ + if (iv) { + AES_init_ctx_iv(ctx, key, iv); + } else { + AES_init_ctx(ctx, key); + } + return GF_OK; +} + +void gf_crypt_deinit_tinyaes_ctr(GF_Crypt* td) +{ +} + + +GF_Err gf_crypt_encrypt_tinyaes_ctr(GF_Crypt* td, u8 *plaintext, u32 len) +{ + struct AES_ctx* ctx = (struct AES_ctx *)td->context; + AES_CTR_xcrypt_buffer(ctx, plaintext, len); + return GF_OK; +} + +GF_Err gf_crypt_decrypt_tinyaes_ctr(GF_Crypt* td, u8 *ciphertext, u32 len) +{ + struct AES_ctx* ctx = (struct AES_ctx *)td->context; + AES_CTR_xcrypt_buffer(ctx, ciphertext, len); + return GF_OK; +} + + +GF_Err gf_crypt_open_open_tinyaes(GF_Crypt* td, GF_CRYPTO_MODE mode) +{ + td->mode = mode; + switch (td->mode) { + case GF_CBC: + td->_init_crypt = gf_crypt_init_tinyaes_cbc; + td->_deinit_crypt = gf_crypt_deinit_tinyaes_cbc; + td->_set_key = gf_set_key_tinyaes_cbc; + td->_crypt = gf_crypt_encrypt_tinyaes_cbc; + td->_decrypt = gf_crypt_decrypt_tinyaes_cbc; + td->_get_state = gf_crypt_get_IV_tinyaes_cbc; + td->_set_state = gf_crypt_set_IV_tinyaes_cbc; + break; + case GF_CTR: + td->_init_crypt = gf_crypt_init_tinyaes_ctr; + td->_deinit_crypt = gf_crypt_deinit_tinyaes_ctr; + td->_set_key = gf_set_key_tinyaes_ctr; + td->_crypt = gf_crypt_encrypt_tinyaes_ctr; + td->_decrypt = gf_crypt_decrypt_tinyaes_ctr; + td->_get_state = gf_crypt_get_IV_tinyaes_ctr; + td->_set_state = gf_crypt_set_IV_tinyaes_ctr; + break; + default: + return GF_BAD_PARAM; + break; + + } + td->algo = GF_AES_128; + return GF_OK; +} + +#endif + diff --git a/src/crypto/tiny_aes.c b/src/crypto/tiny_aes.c new file mode 100644 index 0000000..9d17072 --- /dev/null +++ b/src/crypto/tiny_aes.c @@ -0,0 +1,587 @@ +/* + +This is an implementation of the AES algorithm, specifically ECB, CTR and CBC mode. +Block size can be chosen in aes.h - available choices are AES128, AES192, AES256. + +The implementation is verified against the test vectors in: + National Institute of Standards and Technology Special Publication 800-38A 2001 ED + +ECB-AES128 +---------- + + plain-text: + 6bc1bee22e409f96e93d7e117393172a + ae2d8a571e03ac9c9eb76fac45af8e51 + 30c81c46a35ce411e5fbc1191a0a52ef + f69f2445df4f9b17ad2b417be66c3710 + + key: + 2b7e151628aed2a6abf7158809cf4f3c + + resulting cipher + 3ad77bb40d7a3660a89ecaf32466ef97 + f5d3d58503b9699de785895a96fdbaaf + 43b1cd7f598ece23881b00e3ed030688 + 7b0c785e27e8ad3f8223207104725dd4 + + +NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0) + You should pad the end of the string with zeros if this is not the case. + For AES192/256 the key size is proportionally larger. + +*/ + + +/*****************************************************************************/ +/* Includes: */ +/*****************************************************************************/ + +#include "tiny_aes.h" + +#ifndef GPAC_HAS_SSL + +/*****************************************************************************/ +/* Defines: */ +/*****************************************************************************/ +// The number of columns comprising a state in AES. This is a constant in AES. Value=4 +#define Nb 4 + +#if defined(AES256) && (AES256 == 1) + #define Nk 8 + #define Nr 14 +#elif defined(AES192) && (AES192 == 1) + #define Nk 6 + #define Nr 12 +#else + #define Nk 4 // The number of 32 bit words in a key. + #define Nr 10 // The number of rounds in AES Cipher. +#endif + +// jcallan@github points out that declaring Multiply as a function +// reduces code size considerably with the Keil ARM compiler. +// See this link for more information: https://github.com/kokke/tiny-AES-C/pull/3 +#ifndef MULTIPLY_AS_A_FUNCTION + #define MULTIPLY_AS_A_FUNCTION 0 +#endif + + + + +/*****************************************************************************/ +/* Private variables: */ +/*****************************************************************************/ +// state - array holding the intermediate results during decryption. +typedef u8 state_t[4][4]; + + + +// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM +// The numbers below can be computed dynamically trading ROM for RAM - +// This can be useful in (embedded) bootloader applications, where ROM is often limited. +static const u8 sbox[256] = { + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; + +static const u8 rsbox[256] = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }; + +// The round constant word array, Rcon[i], contains the values given by +// x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) +static const u8 Rcon[11] = { + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; + +/* + * Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES-C/pull/12), + * that you can remove most of the elements in the Rcon array, because they are unused. + * + * From Wikipedia's article on the Rijndael key schedule @ https://en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon + * + * "Only the first some of these constants are actually used – up to rcon[10] for AES-128 (as 11 round keys are needed), + * up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm." + */ + + +/*****************************************************************************/ +/* Private functions: */ +/*****************************************************************************/ +/* +static u8 getSBoxValue(u8 num) +{ + return sbox[num]; +} +*/ +#define getSBoxValue(num) (sbox[(num)]) +/* +static u8 getSBoxInvert(u8 num) +{ + return rsbox[num]; +} +*/ +#define getSBoxInvert(num) (rsbox[(num)]) + +// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. +static void KeyExpansion(u8* RoundKey, const u8* Key) +{ + unsigned i; + u8 tempa[4]; // Used for the column/row operations + + // The first round key is the key itself. + for (i = 0; i < Nk; ++i) + { + RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; + RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; + RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; + RoundKey[(i * 4) + 3] = Key[(i * 4) + 3]; + } + + // All other round keys are found from the previous round keys. + for (i = Nk; i < Nb * (Nr + 1); ++i) + { + unsigned j, k; + { + k = (i - 1) * 4; + tempa[0]=RoundKey[k + 0]; + tempa[1]=RoundKey[k + 1]; + tempa[2]=RoundKey[k + 2]; + tempa[3]=RoundKey[k + 3]; + + } + + if (i % Nk == 0) + { + // This function shifts the 4 bytes in a word to the left once. + // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] + + // Function RotWord() + { + k = tempa[0]; + tempa[0] = tempa[1]; + tempa[1] = tempa[2]; + tempa[2] = tempa[3]; + tempa[3] = k; + } + + // SubWord() is a function that takes a four-byte input word and + // applies the S-box to each of the four bytes to produce an output word. + + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + + tempa[0] = tempa[0] ^ Rcon[i/Nk]; + } +#if defined(AES256) && (AES256 == 1) + if (i % Nk == 4) + { + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + } +#endif + j = i * 4; k=(i - Nk) * 4; + RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0]; + RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1]; + RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2]; + RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3]; + } +} + +void AES_init_ctx(struct AES_ctx* ctx, const u8* key) +{ + KeyExpansion(ctx->RoundKey, key); +#if (defined(CTR) && (CTR == 1)) + ctx->counter_pos = 0; +#endif +} + +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) +void AES_init_ctx_iv(struct AES_ctx* ctx, const u8* key, const u8* iv) +{ + KeyExpansion(ctx->RoundKey, key); +#if (defined(CTR) && (CTR == 1)) + ctx->counter_pos = 0; +#endif + memcpy (ctx->Iv, iv, AES_BLOCKLEN); +} +void AES_ctx_set_iv(struct AES_ctx* ctx, const u8* iv) +{ + memcpy (ctx->Iv, iv, AES_BLOCKLEN); +} +void AES_ctx_get_iv(struct AES_ctx* ctx, u8* iv) +{ + memcpy(iv, ctx->Iv, AES_BLOCKLEN); +} +#endif + +// This function adds the round key to state. +// The round key is added to the state by an XOR function. +static void AddRoundKey(u8 round,state_t* state,u8* RoundKey) +{ + u8 i,j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j]; + } + } +} + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void SubBytes(state_t* state) +{ + u8 i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxValue((*state)[j][i]); + } + } +} + +// The ShiftRows() function shifts the rows in the state to the left. +// Each row is shifted with different offset. +// Offset = Row number. So the first row is not shifted. +static void ShiftRows(state_t* state) +{ + u8 temp; + + // Rotate first row 1 columns to left + temp = (*state)[0][1]; + (*state)[0][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[3][1]; + (*state)[3][1] = temp; + + // Rotate second row 2 columns to left + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to left + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[3][3]; + (*state)[3][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[1][3]; + (*state)[1][3] = temp; +} + +static u8 xtime(u8 x) +{ + return ((x<<1) ^ (((x>>7) & 1) * 0x1b)); +} + +// MixColumns function mixes the columns of the state matrix +static void MixColumns(state_t* state) +{ + u8 i; + u8 Tmp, Tm, t; + for (i = 0; i < 4; ++i) + { + t = (*state)[i][0]; + Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ; + Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ; + Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ; + Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ; + Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ; + } +} + +// Multiply is used to multiply numbers in the field GF(2^8) +// Note: The last call to xtime() is unneeded, but often ends up generating a smaller binary +// The compiler seems to be able to vectorize the operation better this way. +// See https://github.com/kokke/tiny-AES-c/pull/34 +#if MULTIPLY_AS_A_FUNCTION +static u8 Multiply(u8 x, u8 y) +{ + return (((y & 1) * x) ^ + ((y>>1 & 1) * xtime(x)) ^ + ((y>>2 & 1) * xtime(xtime(x))) ^ + ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ + ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))); /* this last call to xtime() can be omitted */ + } +#else +#define Multiply(x, y) \ + ( ((y & 1) * x) ^ \ + ((y>>1 & 1) * xtime(x)) ^ \ + ((y>>2 & 1) * xtime(xtime(x))) ^ \ + ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \ + ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \ + +#endif + +// MixColumns function mixes the columns of the state matrix. +// The method used to multiply may be difficult to understand for the inexperienced. +// Please use the references to gain more information. +static void InvMixColumns(state_t* state) +{ + int i; + u8 a, b, c, d; + for (i = 0; i < 4; ++i) + { + a = (*state)[i][0]; + b = (*state)[i][1]; + c = (*state)[i][2]; + d = (*state)[i][3]; + + (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); + (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); + (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); + (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); + } +} + + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void InvSubBytes(state_t* state) +{ + u8 i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxInvert((*state)[j][i]); + } + } +} + +static void InvShiftRows(state_t* state) +{ + u8 temp; + + // Rotate first row 1 columns to right + temp = (*state)[3][1]; + (*state)[3][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[0][1]; + (*state)[0][1] = temp; + + // Rotate second row 2 columns to right + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to right + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[1][3]; + (*state)[1][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[3][3]; + (*state)[3][3] = temp; +} + + +// Cipher is the main function that encrypts the PlainText. +static void Cipher(state_t* state, u8* RoundKey) +{ + u8 round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(0, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr-1 rounds are executed in the loop below. + for (round = 1; round < Nr; ++round) + { + SubBytes(state); + ShiftRows(state); + MixColumns(state); + AddRoundKey(round, state, RoundKey); + } + + // The last round is given below. + // The MixColumns function is not here in the last round. + SubBytes(state); + ShiftRows(state); + AddRoundKey(Nr, state, RoundKey); +} + +static void InvCipher(state_t* state,u8* RoundKey) +{ + u8 round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(Nr, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr-1 rounds are executed in the loop below. + for (round = (Nr - 1); round > 0; --round) + { + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(round, state, RoundKey); + InvMixColumns(state); + } + + // The last round is given below. + // The MixColumns function is not here in the last round. + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(0, state, RoundKey); +} + + +/*****************************************************************************/ +/* Public functions: */ +/*****************************************************************************/ +#if defined(ECB) && (ECB == 1) + + +void AES_ECB_encrypt(struct AES_ctx *ctx,const u8* buf) +{ + // The next function call encrypts the PlainText with the Key using AES algorithm. + Cipher((state_t*)buf, ctx->RoundKey); +} + +void AES_ECB_decrypt(struct AES_ctx* ctx,const u8* buf) +{ + // The next function call decrypts the PlainText with the Key using AES algorithm. + InvCipher((state_t*)buf, ctx->RoundKey); +} + + +#endif // #if defined(ECB) && (ECB == 1) + + + + + +#if defined(CBC) && (CBC == 1) + + +static void XorWithIv(u8* buf, u8* Iv) +{ + u8 i; + for (i = 0; i < AES_BLOCKLEN; ++i) // The block in AES is always 128bit no matter the key size + { + buf[i] ^= Iv[i]; + } +} + +void AES_CBC_encrypt_buffer(struct AES_ctx *ctx,u8* buf, u32 length) +{ + uintptr_t i; + u8 *Iv = ctx->Iv; + for (i = 0; i < length; i += AES_BLOCKLEN) + { + XorWithIv(buf, Iv); + Cipher((state_t*)buf, ctx->RoundKey); + Iv = buf; + buf += AES_BLOCKLEN; + //printf("Step %d - %d", i/16, i); + } + /* store Iv in ctx for next call */ + memcpy(ctx->Iv, Iv, AES_BLOCKLEN); +} + +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, u8* buf, u32 length) +{ + uintptr_t i; + u8 storeNextIv[AES_BLOCKLEN]; + for (i = 0; i < length; i += AES_BLOCKLEN) + { + memcpy(storeNextIv, buf, AES_BLOCKLEN); + InvCipher((state_t*)buf, ctx->RoundKey); + XorWithIv(buf, ctx->Iv); + memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN); + buf += AES_BLOCKLEN; + } + +} + +#endif // #if defined(CBC) && (CBC == 1) + + + +#if defined(CTR) && (CTR == 1) + +/* Symmetrical operation: same function for encrypting as for decrypting. Note any IV/nonce should never be reused with the same key */ +void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, u8* buf, u32 length) +{ + unsigned i; + int bi = (AES_BLOCKLEN - ctx->counter_pos); + assert(ctx->counter_posbuffer, ctx->Iv, AES_BLOCKLEN); + Cipher((state_t*)ctx->buffer,ctx->RoundKey); + + /* Increment Iv and handle overflow */ + for (bi = (AES_BLOCKLEN - 1); bi >= 0; --bi) + { + /* inc will owerflow */ + if (ctx->Iv[bi] == 255) + { + ctx->Iv[bi] = 0; + continue; + } + ctx->Iv[bi] += 1; + break; + } + bi = 0; + } + + buf[i] = (buf[i] ^ ctx->buffer[bi]); + } + ctx->counter_pos = (AES_BLOCKLEN - bi); +} + +#endif // #if defined(CTR) && (CTR == 1) + +#endif //GPAC_HAS_SSL + + diff --git a/src/crypto/tiny_aes.h b/src/crypto/tiny_aes.h new file mode 100644 index 0000000..4d7f6b2 --- /dev/null +++ b/src/crypto/tiny_aes.h @@ -0,0 +1,93 @@ +#ifndef _AES_H_ +#define _AES_H_ + +#include + +// #define the macros below to 1/0 to enable/disable the mode of operation. +// +// CBC enables AES encryption in CBC-mode of operation. +// CTR enables encryption in counter-mode. +// ECB enables the basic ECB 16-byte block algorithm. All can be enabled simultaneously. + +// The #ifndef-guard allows it to be configured before #include'ing or at compile time. +#ifndef CBC + #define CBC 1 +#endif + +#ifndef ECB + #define ECB 1 +#endif + +#ifndef CTR + #define CTR 1 +#endif + + +#define AES128 1 +//#define AES192 1 +//#define AES256 1 + +#define AES_BLOCKLEN 16 //Block length in bytes AES is 128b block only + +#if defined(AES256) && (AES256 == 1) + #define AES_KEYLEN 32 + #define AES_keyExpSize 240 +#elif defined(AES192) && (AES192 == 1) + #define AES_KEYLEN 24 + #define AES_keyExpSize 208 +#else + #define AES_KEYLEN 16 // Key length in bytes + #define AES_keyExpSize 176 +#endif + +struct AES_ctx +{ + u8 RoundKey[AES_keyExpSize]; +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) + u8 Iv[AES_BLOCKLEN]; + u8 counter_pos; + u8 buffer[AES_BLOCKLEN]; +#endif +}; + +void AES_init_ctx(struct AES_ctx* ctx, const u8* key); +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) +void AES_init_ctx_iv(struct AES_ctx* ctx, const u8* key, const u8* iv); +void AES_ctx_set_iv(struct AES_ctx* ctx, const u8* iv); +void AES_ctx_get_iv(struct AES_ctx* ctx, u8* iv); +#endif + +#if defined(ECB) && (ECB == 1) +// buffer size is exactly AES_BLOCKLEN bytes; +// you need only AES_init_ctx as IV is not used in ECB +// NB: ECB is considered insecure for most uses +void AES_ECB_encrypt(struct AES_ctx* ctx, const u8* buf); +void AES_ECB_decrypt(struct AES_ctx* ctx, const u8* buf); + +#endif // #if defined(ECB) && (ECB == !) + + +#if defined(CBC) && (CBC == 1) +// buffer size MUST be mutile of AES_BLOCKLEN; +// Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme +// NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv() +// no IV should ever be reused with the same key +void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, u8* buf, u32 length); +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, u8* buf, u32 length); + +#endif // #if defined(CBC) && (CBC == 1) + + +#if defined(CTR) && (CTR == 1) + +// Same function for encrypting as for decrypting. +// IV is incremented for every block, and used after encryption as XOR-compliment for output +// Suggesting https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme +// NOTES: you need to set IV in ctx with AES_init_ctx_iv() or AES_ctx_set_iv() +// no IV should ever be reused with the same key +void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, u8* buf, u32 length); + +#endif // #if defined(CTR) && (CTR == 1) + + +#endif //_AES_H_ diff --git a/src/dir_info b/src/dir_info deleted file mode 100644 index fed8895..0000000 --- a/src/dir_info +++ /dev/null @@ -1,47 +0,0 @@ -This is a short overview of the gpac library source repository. - -gpac/src/bifs - BInary Format for Scene coding (decoder and encoder) (BIFS tables are with MPEG4Gen application) - -gpac/src/ietf - Small RTP/RTSP/SDP library, plus media packetizers. - -gpac/src/isomedia - IsoMedia File Format - features file reading/writing/editing, precise interleaving, hint track creation -and supports movie fragments (read/write). Includes 3GPP/3GPP2 features and simple AVC support - JPEG2000 support to be done. - -gpac/src/mcrypt - Port of libmcrypt into gpac for cryptographic tool. Currently only AES-128 CTR mode is used, but the other tools will likely be usefull for IPMPX - -gpac/src/media_tools - media tools for authoring: ISMA & 3GPP tools, media importing and exporting, hinting - -gpac/src/odf - MPEG-4 Object Descriptor Framework: encoding/decoding of all descriptors, OD codec and OCI codec - -gpac/src/compositor - Compositor for 2D & 3D drawing - handles MPEG-4, X3D/VRML and SVG. - -gpac/src/scene_manager - memory representation of the scene, importers (BT/XMT/SWF/QT), dumpers and encoding - -gpac/src/scenegraph - Scene Graph API (MPEG4/VRML/X3D/SVG) - -gpac/src/terminal - Client application engine. The guts of the gpac client performing stream synchronization and -media object setup, control and decoding. All decoders / network services are abstracted through plugins - -gpac/src/utils - All generic objects used throughout the lib (list, bitstream, thread, mutex...) -The OS specific files are in dedicated sub_folders (eg ./Tools/w32 for all windows code, ...) -porting the lib means barely porting these files and updating the makefile - -gpac/include/gpac - all exported files of the lib (high level APIs). Development headers are - -gpac/include/gpac/internal - all development files of the lib (low level access). - -gpac/include/gpac/modules - all module APIs defined in GPAC. diff --git a/modules/soft_raster/ftgrays.c b/src/evg/ftgrays.c similarity index 76% rename from modules/soft_raster/ftgrays.c rename to src/evg/ftgrays.c index 3d920d1..54e4d7e 100644 --- a/modules/soft_raster/ftgrays.c +++ b/src/evg/ftgrays.c @@ -59,105 +59,23 @@ /* GPAC version modifications: * removed all cubic/quadratic support (present in GPAC path objects) - * moved span data memoru to dynamic allocation + * moved span data memory to dynamic allocation * bypassed Y-sorting of cells by using an array of scanlines: a bit more consuming in memory, but faster cell sorting (X-sorting only) + * deferred coverage push for 3D */ #include "rast_soft.h" -#include -#define ErrRaster_MemoryOverflow -4 -#define ErrRaster_Invalid_Mode -2 -#define ErrRaster_Invalid_Outline -1 - - -#define GPAC_FIX_BITS 16 -#define PIXEL_BITS 8 -#define PIXEL_BITS_DIFF 8 /*GPAC_FIX_BITS - PIXEL_BITS*/ - -#define ONE_PIXEL ( 1L << PIXEL_BITS ) -#define PIXEL_MASK ( -1L << PIXEL_BITS ) -#define TRUNC( x ) ( (TCoord)((x) >> PIXEL_BITS) ) -#define SUBPIXELS( x ) ( (TPos)(x) << PIXEL_BITS ) -#define FLOOR( x ) ( (x) & -ONE_PIXEL ) -#define CEILING( x ) ( ( (x) + ONE_PIXEL - 1 ) & -ONE_PIXEL ) -#define ROUND( x ) ( ( (x) + ONE_PIXEL / 2 ) & -ONE_PIXEL ) -#define UPSCALE( x ) ( (x) >> ( PIXEL_BITS_DIFF) ) -#define DOWNSCALE( x ) ( (x) << ( PIXEL_BITS_DIFF) ) - - -/*************************************************************************/ -/* */ -/* TYPE DEFINITIONS */ -/* */ - -/* don't change the following types to FT_Int or FT_Pos, since we might */ -/* need to define them to "float" or "double" when experimenting with */ -/* new algorithms */ - -typedef int TCoord; /* integer scanline/pixel coordinate */ -typedef long TPos; /* sub-pixel coordinate */ - -/* determine the type used to store cell areas. This normally takes at */ -/* least PIXEL_BYTES*2 + 1. On 16-bit systems, we need to use `long' */ -/* instead of `int', otherwise bad things happen */ - -/* approximately determine the size of integers using an ANSI-C header */ -#if UINT_MAX == 0xFFFFU -typedef long TArea; -#else -typedef int TArea; -#endif - - -/* maximal number of gray spans in a call to the span callback */ -#define FT_MAX_GRAY_SPANS 64 - -typedef struct TCell_ -{ - TCoord x; - int cover; - TArea area; -} AACell; - -typedef struct -{ - AACell *cells; - int alloc, num; -} AAScanline; - - -typedef struct TRaster_ -{ - AAScanline *scanlines; - int max_lines; - TPos min_ex, max_ex, min_ey, max_ey; - TCoord ex, ey; - TPos x, y, last_ey; - TArea area; - int cover; - - EVG_Span gray_spans[FT_MAX_GRAY_SPANS]; - int num_gray_spans; - EVG_Raster_Span_Func render_span; - void *render_span_data; - -#ifdef INLINE_POINT_CONVERSION - GF_Matrix2D *mx; -#endif -} TRaster; - -#define AA_CELL_STEP_ALLOC 8 - -static GFINLINE void gray_record_cell( TRaster *raster ) +void gray_record_cell( TRaster *raster ) { if (( raster->area | raster->cover) && (raster->eymax_ey)) { - AACell *cell; long y = raster->ey - raster->min_ey; if (y>=0) { + AACell *cell; AAScanline *sl = &raster->scanlines[y]; + if (sl->num >= sl->alloc) { sl->cells = (AACell*)gf_realloc(sl->cells, sizeof(AACell)* (sl->alloc + AA_CELL_STEP_ALLOC)); sl->alloc += AA_CELL_STEP_ALLOC; @@ -170,11 +88,16 @@ static GFINLINE void gray_record_cell( TRaster *raster ) else cell->x = (TCoord)(raster->ex - raster->min_ex); cell->area = raster->area; cell->cover = raster->cover; + cell->idx1 = raster->idx1; + cell->idx2 = raster->idx2; + + if (raster->first_scanline > (u32) y) + raster->first_scanline = y; } } } -static GFINLINE void gray_set_cell( TRaster *raster, TCoord ex, TCoord ey ) +void gray_set_cell( TRaster *raster, TCoord ex, TCoord ey ) { if ((raster->ex != ex) || (raster->ey != ey)) { gray_record_cell(raster); @@ -186,8 +109,6 @@ static GFINLINE void gray_set_cell( TRaster *raster, TCoord ex, TCoord ey ) } -#ifdef INLINE_POINT_CONVERSION - static GFINLINE void evg_translate_point(GF_Matrix2D *mx, EVG_Vector *pt, TPos *x, TPos *y) { Fixed _x, _y; @@ -202,7 +123,6 @@ static GFINLINE void evg_translate_point(GF_Matrix2D *mx, EVG_Vector *pt, TPos * *y = (s32) (_y * ONE_PIXEL); #endif } -#endif static GFINLINE int gray_move_to( EVG_Vector* to, EVG_Raster raster) { @@ -212,18 +132,15 @@ static GFINLINE int gray_move_to( EVG_Vector* to, EVG_Raster raster) /* record current cell, if any */ gray_record_cell(raster); -#ifdef INLINE_POINT_CONVERSION evg_translate_point(raster->mx, to, &x, &y); -#else - x = UPSCALE( to->x ); - y = UPSCALE( to->y ); -#endif + ex = TRUNC(x); ey = TRUNC(y); if ( ex < raster->min_ex ) ex = (TCoord)(raster->min_ex - 1); raster->area = 0; raster->cover = 0; gray_set_cell( raster, ex, ey ); + if (ey<0) ey=0; raster->last_ey = SUBPIXELS( ey ); raster->x = x; @@ -240,11 +157,13 @@ static void gray_render_scanline( TRaster *raster, TCoord ey, TPos x1, TCoord TCoord ex1, ex2, fx1, fx2, delta; long p, first; long dx; - int incr, lift, mod, rem; + int incr, mod; dx = x2 - x1; ex1 = TRUNC( x1 ); /* if (ex1 >= raster->max_ex) ex1 = raster->max_ex-1; */ ex2 = TRUNC( x2 ); /* if (ex2 >= raster->max_ex) ex2 = raster->max_ex-1; */ + if (ex1<0) ex1=0; + if (ex2<0) ex2=0; fx1 = (TCoord)( x1 - SUBPIXELS( ex1 ) ); fx2 = (TCoord)( x2 - SUBPIXELS( ex2 ) ); @@ -288,6 +207,7 @@ static void gray_render_scanline( TRaster *raster, TCoord ey, TPos x1, TCoord y1 += delta; if ( ex1 != ex2 ) { + int lift, rem; p = ONE_PIXEL * ( y2 - y1 + delta ); lift = (TCoord)( p / dx ); rem = (TCoord)( p % dx ); @@ -321,7 +241,7 @@ static void gray_render_scanline( TRaster *raster, TCoord ey, TPos x1, TCoord /*************************************************************************/ /* */ /* Render a given line as a series of scanlines. */ -static GFINLINE void gray_render_line(TRaster *raster, TPos to_x, TPos to_y, int is_line) +void gray_render_line(TRaster *raster, TPos to_x, TPos to_y) { TCoord min, max; TCoord ey1, ey2, fy1, fy2; @@ -330,18 +250,10 @@ static GFINLINE void gray_render_line(TRaster *raster, TPos to_x, TPos to_y, i long p, first; int delta, rem, mod, lift, incr; - /*point filtering*/ -#if 0 - if (is_line) { - dx = (to_x - raster->x)>>PIXEL_BITS; - dy = (to_y - raster->y)>>PIXEL_BITS; - if ( dx*dx + dy*dy < 1) return; - } -#endif - ey1 = TRUNC( raster->last_ey ); ey2 = TRUNC( to_y ); /* if (ey2 >= raster->max_ey) ey2 = raster->max_ey-1; */ + if (ey2<0) ey2=0; fy1 = (TCoord)( raster->y - raster->last_ey ); fy2 = (TCoord)( to_y - SUBPIXELS( ey2 ) ); @@ -459,41 +371,28 @@ End: static int EVG_Outline_Decompose(EVG_Outline *outline, TRaster *user) { EVG_Vector v_start; - EVG_Vector* point; - EVG_Vector* limit; - char* tags; int n; /* index of contour in outline */ int first; /* index of first point in contour */ -#ifdef INLINE_POINT_CONVERSION TPos _x, _y; -#endif first = 0; for ( n = 0; n < outline->n_contours; n++ ) { + EVG_Vector *point; + EVG_Vector *limit; int last; /* index of last point in contour */ last = outline->contours[n]; limit = outline->points + last; v_start = outline->points[first]; point = outline->points + first; - tags = (char*) outline->tags + first; gray_move_to(&v_start, user); while ( point < limit ) { point++; - tags++; -#ifdef INLINE_POINT_CONVERSION evg_translate_point(user->mx, point, &_x, &_y); - gray_render_line(user, _x, _y, 1); -#else - gray_render_line(user, UPSCALE(point->x), UPSCALE( point->y), 1); -#endif + gray_render_line(user, _x, _y); } -#ifdef INLINE_POINT_CONVERSION - evg_translate_point(user->mx, &v_start, &_x, &_y); - gray_render_line(user, _x, _y, 0); -#else /* close the contour with a line segment */ - gray_render_line(user, UPSCALE(v_start.x), UPSCALE( v_start.y), 0); -#endif + evg_translate_point(user->mx, &v_start, &_x, &_y); + gray_render_line(user, _x, _y); first = last + 1; } return 0; @@ -515,7 +414,7 @@ static int EVG_Outline_Decompose(EVG_Outline *outline, TRaster *user) #define QSORT_THRESHOLD 9 /* below this size, a sub-array will be sorted */ /* through a normal insertion sort */ -static void gray_quick_sort( AACell *cells, int count ) +void gray_quick_sort( AACell *cells, int count ) { AACell *stack[80]; /* should be enough ;-) */ AACell **top; /* top of stack */ @@ -528,8 +427,9 @@ static void gray_quick_sort( AACell *cells, int count ) for (;;) { int len = (int)( limit - base ); - AACell *i, *j, *pivot; + AACell *i, *j; if ( len > QSORT_THRESHOLD ) { + AACell *pivot; /* we use base + len/2 as the pivot */ pivot = base + len / 2; SWAP_CELLS( base, pivot, temp ); @@ -595,12 +495,11 @@ static void gray_quick_sort( AACell *cells, int count ) } } - -static void gray_hline( TRaster *raster, TCoord x, TCoord y, TPos area, int acount, Bool zero_non_zero_rule) +static void gray_hline( TRaster *raster, TCoord x, TCoord y, TPos area, int acount, Bool zero_non_zero_rule, u32 idx1, u32 idx2) { + int coverage; EVG_Span* span; int count; - int coverage; x += (TCoord)raster->min_ex; if (x>=raster->max_ex) return; @@ -629,38 +528,46 @@ static void gray_hline( TRaster *raster, TCoord x, TCoord y, TPos area, int coverage = 255; } - if ( coverage ) { - /* see if we can add this span to the current list */ - count = raster->num_gray_spans; - span = raster->gray_spans + count - 1; - if ( count > 0 && - (int)span->x + span->len == (int)x && - span->coverage == coverage ) - { - span->len = (unsigned short)( span->len + acount ); - return; - } + if (!coverage) + return; - if (count >= FT_MAX_GRAY_SPANS ) { - raster->render_span(y, count, raster->gray_spans, raster->render_span_data ); - raster->num_gray_spans = 0; + /* see if we can add this span to the current list */ + count = raster->num_gray_spans; + span = raster->gray_spans + count - 1; + if ( count > 0 && + (int)span->x + span->len == (int)x && + span->coverage == coverage ) + { + span->len = (unsigned short)( span->len + acount ); + return; + } - span = raster->gray_spans; - } else - span++; + if ((u32) count >= raster->max_gray_spans) { + raster->render_span(y, count, raster->gray_spans, raster->render_span_data ); + raster->num_gray_spans = 0; - /* add a gray span to the current list */ - span->x = (short)x; - span->len = (unsigned short)acount; - span->coverage = (unsigned char)coverage; - raster->num_gray_spans++; + span = raster->gray_spans; + } else { + if (count==raster->alloc_gray_spans) { + raster->alloc_gray_spans*=2; + raster->gray_spans = gf_realloc(raster->gray_spans, sizeof(EVG_Span)*raster->alloc_gray_spans); + span = raster->gray_spans + count - 1; + } + span++; } + + /* add a gray span to the current list */ + span->x = (short)x; + span->len = (unsigned short)acount; + span->coverage = (unsigned char)coverage; + span->idx1 = idx1; + span->idx2 = idx2; + raster->num_gray_spans++; } -static void gray_sweep_line( TRaster *raster, AAScanline *sl, int y, Bool zero_non_zero_rule) +void gray_sweep_line( TRaster *raster, AAScanline *sl, int y, Bool zero_non_zero_rule) { - TCoord x, cover; - TArea area; + TCoord cover; AACell *start, *cur; cur = sl->cells; @@ -668,6 +575,8 @@ static void gray_sweep_line( TRaster *raster, AAScanline *sl, int y, Bool zero_n raster->num_gray_spans = 0; while (sl->num) { + TCoord x; + TArea area; start = cur; x = start->x; area = start->area; @@ -685,41 +594,41 @@ static void gray_sweep_line( TRaster *raster, AAScanline *sl, int y, Bool zero_n /* if the start cell has a non-null area, we must draw an */ /* individual gray pixel there */ if ( area && x >= 0 ) { - gray_hline( raster, x, y, cover * ( ONE_PIXEL * 2 ) - area, 1, zero_non_zero_rule); + gray_hline( raster, x, y, cover * ( ONE_PIXEL * 2 ) - area, 1, zero_non_zero_rule, start->idx1, start->idx2); x++; } if ( x < 0 ) x = 0; /* draw a gray span between the start cell and the current one */ if ( cur->x > x ) - gray_hline( raster, x, y, cover * ( ONE_PIXEL * 2 ), cur->x - x, zero_non_zero_rule); + gray_hline( raster, x, y, cover * ( ONE_PIXEL * 2 ), cur->x - x, zero_non_zero_rule, cur->idx1, cur->idx2); } raster->render_span((int) (y + raster->min_ey), raster->num_gray_spans, raster->gray_spans, raster->render_span_data ); } -int evg_raster_render(EVG_Raster raster, EVG_Raster_Params* params) +int evg_raster_render(GF_EVGSurface *surf) { Bool zero_non_zero_rule; - int i, size_y; - EVG_Outline* outline = (EVG_Outline*)params->source; + u32 i, size_y; + EVG_Raster raster = surf->raster; + EVG_Outline* outline = (EVG_Outline*)&surf->ftoutline; + /* return immediately if the outline is empty */ if ( outline->n_points == 0 || outline->n_contours <= 0 ) return 0; - raster->render_span = (EVG_Raster_Span_Func) params->gray_spans; - raster->render_span_data = params->user; + raster->render_span = (EVG_Raster_Span_Func) surf->gray_spans; + raster->render_span_data = surf; /* Set up state in the raster object */ - raster->min_ex = params->clip_xMin; - raster->min_ey = params->clip_yMin; - raster->max_ex = params->clip_xMax; - raster->max_ey = params->clip_yMax; + raster->min_ex = surf->clip_xMin; + raster->min_ey = surf->clip_yMin; + raster->max_ex = surf->clip_xMax; + raster->max_ey = surf->clip_yMax; -#ifdef INLINE_POINT_CONVERSION - raster->mx = params->mx; -#endif + raster->mx = surf->mx; - size_y = (int) (raster->max_ey - raster->min_ey); + size_y = (u32) (raster->max_ey - raster->min_ey); if (raster->max_lines < size_y) { raster->scanlines = (AAScanline*)gf_realloc(raster->scanlines, sizeof(AAScanline)*size_y); memset(&raster->scanlines[raster->max_lines], 0, sizeof(AAScanline)*(size_y-raster->max_lines) ); @@ -730,6 +639,8 @@ int evg_raster_render(EVG_Raster raster, EVG_Raster_Params* params) raster->ey = (int) (raster->max_ey+1); raster->cover = 0; raster->area = 0; + raster->first_scanline = raster->max_ey; + EVG_Outline_Decompose(outline, raster); gray_record_cell( raster ); @@ -737,7 +648,7 @@ int evg_raster_render(EVG_Raster raster, EVG_Raster_Params* params) zero_non_zero_rule = (outline->flags & GF_PATH_FILL_ZERO_NONZERO) ? GF_TRUE : GF_FALSE; /* sort each scanline and render it*/ - for (i=0; ifirst_scanline; iscanlines[i]; if (sl->num) { if (sl->num>1) gray_quick_sort(sl->cells, sl->num); @@ -746,31 +657,36 @@ int evg_raster_render(EVG_Raster raster, EVG_Raster_Params* params) } } -#if 0 - for (i=0; imax_lines; i++) { - gf_free(raster->scanlines[i].cells); - } - gf_free(raster->scanlines); - raster->scanlines = 0; - raster->max_lines = 0; -#endif - return 0; } + + + EVG_Raster evg_raster_new() { TRaster *raster; GF_SAFEALLOC(raster , TRaster); + if (!raster) return NULL; + raster->max_gray_spans = raster->alloc_gray_spans = FT_MAX_GRAY_SPANS; + raster->gray_spans = gf_malloc(sizeof(EVG_Span)* raster->max_gray_spans); + if (!raster->gray_spans) { + gf_free(raster); + return NULL; + } return raster; } void evg_raster_del(EVG_Raster raster) { - int i; + u32 i; for (i=0; imax_lines; i++) { gf_free(raster->scanlines[i].cells); + if (raster->scanlines[i].pixels) + gf_free(raster->scanlines[i].pixels); } + gf_free(raster->gray_spans); + gf_free(raster->scanlines); gf_free(raster); } diff --git a/src/evg/rast_soft.h b/src/evg/rast_soft.h new file mode 100644 index 0000000..4b5c3ff --- /dev/null +++ b/src/evg/rast_soft.h @@ -0,0 +1,524 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / software 2D rasterizer + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + */ + + +#ifndef _GF_EVG_DEV_H_ +#define _GF_EVG_DEV_H_ + +#include + +/*base stencil stack*/ +#define EVGBASESTENCIL \ + u32 type; \ + void (*fill_run)(GF_EVGStencil *p, GF_EVGSurface *surf, s32 x, s32 y, u32 count); \ + GF_Matrix2D pmat; \ + GF_Matrix2D smat; \ + GF_Rect frame; \ + GF_ColorMatrix cmat; + + + +struct _gf_evg_base_stencil +{ + EVGBASESTENCIL +}; + +typedef struct __vec2f EVG_Vector; + +typedef struct +{ + s32 n_contours; + s32 n_points; + EVG_Vector *points; + u8 *tags; + s32 *contours; + s32 flags; /*same as path flags*/ +} EVG_Outline; + +typedef struct TRaster_ *EVG_Raster; + +typedef struct EVG_Span_ +{ + unsigned short x; + unsigned short len; + unsigned char coverage; + u32 idx1, idx2; +} EVG_Span; + +typedef void (*EVG_SpanFunc)(int y, int count, EVG_Span *spans, void *user); +#define EVG_Raster_Span_Func EVG_SpanFunc + + +EVG_Raster evg_raster_new(); +void evg_raster_del(EVG_Raster raster); +int evg_raster_render(GF_EVGSurface *surf); + +GF_Err evg_raster_render_path_3d(GF_EVGSurface *surf); +GF_Err evg_raster_render3d(GF_EVGSurface *surf, u32 *indices, u32 nb_idx, Float *vertices, u32 nb_vertices, u32 nb_comp, GF_EVGPrimitiveType prim_type); + +typedef struct +{ + GF_Matrix proj; + GF_Matrix modelview; + //depth buffer + Float *depth_buffer; + + gf_evg_fragment_shader frag_shader; + void *frag_shader_udta; + gf_evg_vertex_shader vert_shader; + void *vert_shader_udta; + /*render state*/ + u32 vp_x, vp_y, vp_w, vp_h; + Bool is_ccw; + Bool backface_cull; + Float max_depth; + Float min_depth; + Float depth_range; + Float point_size, line_size; + Bool smooth_points; + Bool disable_aa, write_depth, early_depth_test, run_write_depth; + Bool (*depth_test)(Float depth_buf_value, Float frag_value); + Bool mode2d; + Bool clip_zero; + Float zw_factor, zw_offset; + /*internal variables for triangle rasterization*/ + GF_EVGPrimitiveType prim_type; + GF_Vec4 s_v1, s_v2, s_v3; + Float area; + + Float s3_m_s2_x, s3_m_s2_y; + Float s1_m_s3_x, s1_m_s3_y; + Float s2_m_s1_x, s2_m_s1_y; + Float pt_radius; + Float v1v2_length, v1v3_length, v2v3_length; + GF_Vec v1v2, v1v3, v2v3; + + struct _gf_evg_base_stencil yuv_sten; + + u32 *pix_vals; +} EVG_Surface3DExt; + +typedef enum +{ + EVG_YUV_NONE=0, + EVG_YUV_444, + EVG_YUV +} EVG_YUVType; + +/*the surface object - currently only ARGB/RGB32, RGB/BGR and RGB555/RGB565 supported*/ +struct _gf_evg_surface +{ + /*surface info*/ + char *pixels; + u32 pixelFormat, BPP; + u32 width, height; + s32 pitch_x, pitch_y; + Bool center_coords; + Bool is_transparent; + + /*color buffer for variable stencils - size of width * 32 bit for 8 bit surface or width * 64 otherwise*/ + void *stencil_pix_run; + + /*default texture filter level*/ + u32 texture_filter; + + u32 useClipper; + GF_IRect clipper; + + GF_Rect path_bounds; + + /*complete path transform (including page flipping/recenter)*/ + GF_Matrix2D mat; + + /*stencil currently used*/ + GF_EVGStencil *sten; + + void (*yuv_flush_uv)(GF_EVGSurface *surf, u8 *surf_uv_alpha, s32 cu, s32 cv, s32 y); + + GF_EVGCompositeMode comp_mode; + + /*in solid color mode to speed things*/ + u32 fill_col; + u64 fill_col_wide; + u32 grey_type; + + /*FreeType raster*/ + EVG_Raster raster; + + /*FreeType outline (path converted to ft)*/ + EVG_Outline ftoutline; + + GF_Matrix2D *mx; + EVG_SpanFunc gray_spans; + + void (*fill_single)(s32 y, s32 x, u32 col, GF_EVGSurface *surf); + void (*fill_single_a)(s32 y, s32 x, u8 coverage, u32 col, GF_EVGSurface *surf); + + s32 clip_xMin, clip_yMin, clip_xMax, clip_yMax; + + + u8 *uv_alpha; + u32 uv_alpha_alloc; + + Bool swap_uv, not_8bits, is_422; + EVG_YUVType yuv_type; + u32 yuv_prof; + + u32 idx_y1, idx_u, idx_v, idx_a, idx_g, idx_r, idx_b; + + u8 (*get_alpha)(void *udta, u8 src_alpha, s32 x, s32 y); + void *get_alpha_udta; + + Bool is_3d_matrix; + GF_Matrix mx3d; + EVG_Surface3DExt *ext3d; +}; + +/*solid color brush*/ +typedef struct +{ + EVGBASESTENCIL + GF_Color color; +} EVG_Brush; + +/*max number of interpolation points*/ +#define EVGGRADIENTSLOTS 12 + +/*(2^EVGGRADIENTBITS)-1 colors in gradient. Below 6 bits the gradient is crappy */ +#define EVGGRADIENTBITS 10 + + +/*base gradient stencil*/ +#define EVGGRADIENT \ + s32 mod; \ + u32 precomputed_argb[(1< + +#define ErrRaster_MemoryOverflow -4 +#define ErrRaster_Invalid_Mode -2 +#define ErrRaster_Invalid_Outline -1 + + +#define GPAC_FIX_BITS 16 +#define PIXEL_BITS 8 +#define PIXEL_BITS_DIFF 8 /*GPAC_FIX_BITS - PIXEL_BITS*/ + +#define ONE_PIXEL ( 1L << PIXEL_BITS ) +#define PIXEL_MASK ( -1L << PIXEL_BITS ) +#define TRUNC( x ) ( (TCoord)((x) >> PIXEL_BITS) ) +#define SUBPIXELS( x ) ( (TPos)(x) << PIXEL_BITS ) +#define FLOOR( x ) ( (x) & -ONE_PIXEL ) +#define CEILING( x ) ( ( (x) + ONE_PIXEL - 1 ) & -ONE_PIXEL ) +#define ROUND( x ) ( ( (x) + ONE_PIXEL / 2 ) & -ONE_PIXEL ) +#define UPSCALE( x ) ( (x) >> ( PIXEL_BITS_DIFF) ) +#define DOWNSCALE( x ) ( (x) << ( PIXEL_BITS_DIFF) ) + + +/*************************************************************************/ +/* */ +/* TYPE DEFINITIONS */ +/* */ + +/* don't change the following types to FT_Int or FT_Pos, since we might */ +/* need to define them to "float" or "double" when experimenting with */ +/* new algorithms */ + +typedef int TCoord; /* integer scanline/pixel coordinate */ +typedef long TPos; /* sub-pixel coordinate */ + +/* determine the type used to store cell areas. This normally takes at */ +/* least PIXEL_BYTES*2 + 1. On 16-bit systems, we need to use `long' */ +/* instead of `int', otherwise bad things happen */ + +/* approximately determine the size of integers using an ANSI-C header */ +#if UINT_MAX == 0xFFFFU +typedef long TArea; +#else +typedef int TArea; +#endif + + +/* maximal number of gray spans in a call to the span callback */ +#define FT_MAX_GRAY_SPANS 64 + +#define AA_CELL_STEP_ALLOC 8 + +typedef struct TCell_ +{ + TCoord x; + int cover; + TArea area; + u32 idx1, idx2; +} AACell; + +typedef struct +{ + TCoord x; + u32 color; + int cover; + Float depth; + Float write_depth; + u32 idx1, idx2; +} PatchPixel; + +typedef struct +{ + //current cells in sweep + AACell *cells; + int alloc, num; + + /*list of candidates for border pixels (visible edges), flushed at end of shape + this ensures that a pixel is draw once and only once + */ + PatchPixel *pixels; + u32 palloc, pnum; +} AAScanline; + + +typedef struct TRaster_ +{ + AAScanline *scanlines; + u32 max_lines; + TPos min_ex, max_ex, min_ey, max_ey; + TCoord ex, ey; + TPos x, y, last_ey; + TArea area; + int cover; + u32 idx1, idx2; + + EVG_Span *gray_spans; + int num_gray_spans; + u32 max_gray_spans, alloc_gray_spans; + EVG_Raster_Span_Func render_span; + void *render_span_data; + + u32 first_scanline; + + GF_Matrix2D *mx; +} TRaster; + +void gray_record_cell( TRaster *raster ); +void gray_set_cell( TRaster *raster, TCoord ex, TCoord ey ); +void gray_render_line(TRaster *raster, TPos to_x, TPos to_y); +void gray_quick_sort( AACell *cells, int count); +void gray_sweep_line( TRaster *raster, AAScanline *sl, int y, Bool zero_non_zero_rule); + +#endif + diff --git a/src/evg/raster3d.c b/src/evg/raster3d.c new file mode 100644 index 0000000..43dd58c --- /dev/null +++ b/src/evg/raster3d.c @@ -0,0 +1,1480 @@ +/* +* GPAC - Multimedia Framework C SDK +* +* Authors: Jean Le Feuvre +* Copyright (c) Telecom ParisTech 2019 +* All rights reserved +* +* This file is part of GPAC / software 3D rasterizer module +* +* GPAC is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* GPAC is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; see the file COPYING. If not, write to +* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +* +* +*/ + +#include "rast_soft.h" + + + +#if defined(WIN32) && !defined(__GNUC__) +# include +# define GPAC_HAS_SSE2 +#else +# ifdef __SSE2__ +# include +# define GPAC_HAS_SSE2 +# endif +#endif + +#ifdef GPAC_HAS_SSE2 + +static Float float_clamp(Float val, Float minval, Float maxval) +{ + _mm_store_ss( &val, _mm_min_ss( _mm_max_ss(_mm_set_ss(val),_mm_set_ss(minval)), _mm_set_ss(maxval) ) ); + return val; +} +#else + +#define float_clamp(_val, _minval, _maxval)\ + (_val<_minval) ? _minval : (_val>_maxval) ? _maxval : _val; + +#endif + + + +static GFINLINE Bool evg3d_persp_divide(GF_Vec4 *pt) +{ + //perspective divide + if (!pt->q) return GF_FALSE; + pt->q = gf_divfix(FIX_ONE, pt->q); + pt->x = gf_mulfix(pt->x, pt->q); + pt->y = gf_mulfix(pt->y, pt->q); + pt->z = gf_mulfix(pt->z, pt->q); + return GF_TRUE; +} + +static GFINLINE void evg_ndc_to_raster(GF_EVGSurface *surf, GF_Vec4 *pt, TPos *x, TPos *y) +{ + //from [-1, 1] to [0,2] to [0,1] to [0,vp_width] to [vp left, vp right] + pt->x = (pt->x + FIX_ONE) / 2 * surf->width; + //idem but flip Y + pt->y = (FIX_ONE - (pt->y + FIX_ONE)/2) * surf->height; + + //move Z from [-1, 1] to [min_depth, max_depth] + pt->z += FIX_ONE; + pt->z /= 2; + +#ifdef GPAC_FIXED_POINT + *x = UPSCALE(pt->x); + *y = UPSCALE(pt->y); +#else + *x = (s32) (pt->x * ONE_PIXEL); + *y = (s32) (pt->y * ONE_PIXEL); +#endif +} +static GFINLINE void evg3d_ndc_to_raster(GF_EVGSurface *surf, GF_Vec4 *pt, TPos *x, TPos *y) +{ + //from [-1, 1] to [0,2] to [0,1] to [0,vp_width] to [vp left, vp right] + pt->x = (pt->x + FIX_ONE) / 2 * surf->ext3d->vp_w + surf->ext3d->vp_x; + //idem but flip Y + pt->y = (FIX_ONE - (pt->y + FIX_ONE)/2) * surf->ext3d->vp_h + surf->ext3d->vp_y; + /*compute depth*/ + if (!surf->ext3d->clip_zero) { + //move Z from [-1, 1] to [min_depth, max_depth] + pt->z += FIX_ONE; + pt->z /= 2; + } + pt->z = surf->ext3d->min_depth + pt->z * surf->ext3d->depth_range; + +#ifdef GPAC_FIXED_POINT + *x = UPSCALE(pt->x); + *y = UPSCALE(pt->y); +#else + *x = (s32) (pt->x * ONE_PIXEL); + *y = (s32) (pt->y * ONE_PIXEL); +#endif +} + +static GFINLINE int gray3d_move_to(EVG_Raster raster, TPos x, TPos y) +{ + TCoord ex, ey; + + /* record current cell, if any */ + gray_record_cell(raster); + + ex = TRUNC(x); + ey = TRUNC(y); + if ( ex < raster->min_ex ) ex = (TCoord)(raster->min_ex - 1); + raster->area = 0; + raster->cover = 0; + gray_set_cell( raster, ex, ey ); + if (ey<0) ey=0; + raster->last_ey = SUBPIXELS( ey ); + + raster->x = x; + raster->y = y; + return 0; +} + +GF_Err evg_raster_render_path_3d(GF_EVGSurface *surf) +{ + EVG_Vector v_start; + int n; /* index of contour in outline */ + int first; /* index of first point in contour */ + TPos _x, _y, _sx, _sy; + GF_Vec dir; + u32 li, size_y; + EVG_Raster raster = surf->raster; + EVG_Outline *outline = &surf->ftoutline; + + raster->render_span = (EVG_Raster_Span_Func) surf->gray_spans; + raster->render_span_data = surf; + + /* Set up state in the raster object */ + raster->min_ex = surf->clip_xMin; + raster->min_ey = surf->clip_yMin; + raster->max_ex = surf->clip_xMax; + raster->max_ey = surf->clip_yMax; + + + size_y = (u32) (raster->max_ey - raster->min_ey); + if ((u32) raster->max_lines < size_y) { + raster->scanlines = (AAScanline*)gf_realloc(raster->scanlines, sizeof(AAScanline)*size_y); + memset(&raster->scanlines[raster->max_lines], 0, sizeof(AAScanline)*(size_y-raster->max_lines) ); + raster->max_lines = size_y; + } + + raster->ex = (int) (raster->max_ex+1); + raster->ey = (int) (raster->max_ey+1); + raster->cover = 0; + raster->area = 0; + raster->first_scanline = raster->max_ey; + + dir.x = dir.y = 0; + dir.z = -FIX_ONE; + + first = 0; + for ( n = 0; n < outline->n_contours; n++ ) { + GF_Vec4 pt; + EVG_Vector *point; + EVG_Vector *limit; + int last; /* index of last point in contour */ + last = outline->contours[n]; + limit = outline->points + last; + v_start = outline->points[first]; + point = outline->points + first; + + pt.x = v_start.x; + pt.y = v_start.y; + pt.z = 0; + pt.q = 1; + gf_mx_apply_vec_4x4(&surf->mx3d, &pt); + if (!evg3d_persp_divide(&pt)) { + continue; + } + evg_ndc_to_raster(surf, &pt, &_sx, &_sy); + gray3d_move_to(raster, _sx, _sy); + while ( point < limit ) { + point++; + + pt.x = point->x; + pt.y = point->y; + pt.z = 0; + pt.q = 1; + gf_mx_apply_vec_4x4(&surf->mx3d, &pt); + if (!evg3d_persp_divide(&pt)) { + break; + } + evg_ndc_to_raster(surf, &pt, &_x, &_y); + gray_render_line(raster, _x, _y); + } + gray_render_line(raster, _sx, _sy); + + first = last + 1; + } + + gray_record_cell( raster ); + /* sort each scanline and render it*/ + for (li=raster->first_scanline; liscanlines[li]; + if (sl->num) { + if (sl->num>1) gray_quick_sort(sl->cells, sl->num); + gray_sweep_line(raster, sl, li, GF_TRUE); + } + } + return GF_OK; +} + + +Bool evg3d_get_fragment(GF_EVGSurface *surf, GF_EVGFragmentParam *frag_param, Bool *is_transparent) +{ + surf->fill_col = 0; + surf->fill_col_wide = 0; + + if (!surf->ext3d->frag_shader(surf->ext3d->frag_shader_udta, frag_param)) + return GF_FALSE; + +#if 0 + frag_param->color.x = float_clamp(frag_param->color.x, 0.0, 1.0); + frag_param->color.y = float_clamp(frag_param->color.y, 0.0, 1.0); + frag_param->color.z = float_clamp(frag_param->color.z, 0.0, 1.0); + frag_param->color.q = float_clamp(frag_param->color.q, 0.0, 1.0); +#endif + + if (frag_param->color.q<1.0) *is_transparent = GF_TRUE; + + if (surf->not_8bits) { + surf->fill_col_wide = evg_make_col_wide((u16) frag_param->color.q*0xFFFF, (u16) frag_param->color.x*0xFFFF, (u16) frag_param->color.y*0xFFFF, (u16) frag_param->color.z*0xFFFF); + } else { + u8 a = (u8) (frag_param->color.q*255); + u8 r = (u8) (frag_param->color.x*255); + u8 g = (u8) (frag_param->color.y*255); + u8 b = (u8) (frag_param->color.z*255); + surf->fill_col = GF_COL_ARGB(a, r, g, b); + } + + if (surf->not_8bits) { + if (surf->yuv_type) { + if (frag_param->frag_valid==GF_EVG_FRAG_RGB) { + surf->fill_col_wide = gf_evg_argb_to_ayuv_wide(surf, surf->fill_col_wide); + } + } else { + if (frag_param->frag_valid==GF_EVG_FRAG_YUV) { + surf->fill_col_wide = gf_evg_ayuv_to_argb_wide(surf, surf->fill_col_wide); + } + } + } else { + if (surf->yuv_type) { + /*RGB frag*/ + if (frag_param->frag_valid==GF_EVG_FRAG_RGB) { + surf->fill_col = gf_evg_argb_to_ayuv(surf, surf->fill_col); + } + } else { + if (frag_param->frag_valid==GF_EVG_FRAG_YUV) { + surf->fill_col = gf_evg_ayuv_to_argb(surf, surf->fill_col); + } + } + } + return GF_TRUE; +} + +static float edgeFunction(const GF_Vec4 *a, const GF_Vec4 *b, const GF_Vec4 *c) +{ + return (c->x - a->x) * (b->y - a->y) - (c->y - a->y) * (b->x - a->x); +} + +static float edgeFunction_pre(const GF_Vec4 *a, const Float b_minus_a_x, const Float b_minus_a_y, const GF_Vec4 *c) +{ + return (c->x - a->x) * (b_minus_a_y) - (c->y - a->y) * (b_minus_a_x); +} + +typedef struct +{ + GF_EVGSurface *surf; + GF_EVGFragmentParam frag_param; +} EVGFragCallback; + +static void push_patch_pixel(AAScanline *sl, s32 x, u32 col, u8 coverage, Float depth, Float write_depth, u32 idx1, u32 idx2) +{ + u32 i; + PatchPixel *pp; + for (i=0; ipnum; i++) { + if (sl->pixels[i].x>x) break; + if (sl->pixels[i].xpixels[i].color = col; + sl->pixels[i].cover = coverage; + sl->pixels[i].depth = depth; + sl->pixels[i].write_depth = write_depth; + sl->pixels[i].idx1 = idx1; + sl->pixels[i].idx2 = idx2; + return; + } + if (coverage==0xFF) return; + + if (sl->pnum == sl->palloc) { + sl->palloc += AA_CELL_STEP_ALLOC; + sl->pixels = gf_realloc(sl->pixels, sizeof(PatchPixel) * sl->palloc); + } + if (i==sl->pnum) { + pp = &sl->pixels[sl->pnum]; + } else { + memmove(&sl->pixels[i+1], &sl->pixels[i], sizeof(PatchPixel)*(sl->pnum - i)); + pp = &sl->pixels[i]; + } + sl->pnum++; + pp->color = col; + pp->cover = coverage; + pp->x = x; + pp->depth = depth; + pp->write_depth = write_depth; + pp->idx1 = idx1; + pp->idx2 = idx2; +} + +static PatchPixel *get_patch_pixel(AAScanline *sl, s32 x) +{ + u32 i; + for (i=0; ipnum; i++) { + if (sl->pixels[i].x>x) break; + if (sl->pixels[i].xpixels[i]; + } + return NULL; +} + + +static void remove_patch_pixel(AAScanline *sl, s32 x) +{ + u32 i; + for (i=0; ipnum; i++) { + if (sl->pixels[i].x>x) break; + if (sl->pixels[i].xpnum) + memmove(&sl->pixels[i], &sl->pixels[i+1], sizeof(PatchPixel)*(sl->pnum-i-1)); + sl->pnum--; + return; + } +} + +void EVG3D_SpanFunc(int y, int count, EVG_Span *spans, void *user) +{ + int i; + GF_Vec4 pix; + EVGFragCallback *fcbck = user; + GF_EVGSurface *surf = fcbck->surf; + EVG_Surface3DExt *s3d = surf->ext3d; + GF_EVGFragmentParam *frag_param = &fcbck->frag_param; + AAScanline *sl = &surf->raster->scanlines[y]; + Float *depth_line = s3d->depth_buffer ? &s3d->depth_buffer[y*surf->width] : NULL; + Float depth_buf_val; + + pix.z=0; + pix.q=1; + + for (i=0; idisable_aa) + coverage = 0xFF; + else if (!s3d->mode2d) + prev_partial = get_patch_pixel(sl, x); + + pix.x = (Float)x + 0.5f; + pix.y = (Float)y + 0.5f; + + if (s3d->prim_type==GF_EVG_POINTS) { + if (s3d->smooth_points) { + Float dx, dy; + dx = (Float)x - s3d->s_v1.x; + dy = (Float)y - s3d->s_v1.y; + dx *=dx; + dy *=dy; + if (dx+dy > s3d->pt_radius) { + spans[i].coverage=0; + continue; + } + } + depth = s3d->s_v1.z; + + bc1 = bc2 = bc3 = 0; + if (coverage!=0xFF) { + full_cover = GF_FALSE; + } + } else if (s3d->prim_type==GF_EVG_LINES) { + GF_Vec pt; + + gf_vec_diff(pt, pix, s3d->s_v1); + bc1 = gf_vec_len(pt); + bc1 /= s3d->v1v2_length; + bc1 = float_clamp(bc1, 0, 1); + bc2 = FIX_ONE - bc1; + depth = s3d->s_v1.z * bc1 + s3d->s_v2.z * bc2; + + bc3 = 0; + if (coverage!=0xFF) { + full_cover = GF_FALSE; + } + } else { + bc1 = edgeFunction_pre(&s3d->s_v2, s3d->s3_m_s2_x, s3d->s3_m_s2_y, &pix); + bc1 /= s3d->area; + bc2 = edgeFunction_pre(&s3d->s_v3, s3d->s1_m_s3_x, s3d->s1_m_s3_y, &pix); + bc2 /= s3d->area; + bc3 = edgeFunction_pre(&s3d->s_v1, s3d->s2_m_s1_x, s3d->s2_m_s1_y, &pix); + bc3 /= s3d->area; + //bc3 = 1.0 - bc1 - bc2; + + /* in antialiased mode, we don't need to test for bc1>=0 && bc2>=0 && bc3>=0 (ie, is the pixel in the triangle), + because we already know the point is in the triangle since we are called back + in non-AA mode, coverage is forced to full pixel, and we check if we are or not in the triangle*/ + if (s3d->disable_aa) + { + if ((bc1<0) || (bc2<0) || (bc3<0)) { + spans[i].coverage=0; + continue; + } + } + /*if coverage is not full, we are on a line - recompute the weights assuming the pixel is exactly on the line*/ + else if (coverage!=0xFF) { + + //results are not precise enough to give better results, we always clamp for now +#if 0 + if (!s3d->backface_cull) { + +#if 0 + //method 1, find a subpixel in a 5x5 grid belonging to the triangle + GF_Vec4 subp; + u32 k, l; + subp.q = 1; + subp.z = 0; + Bool subp_found=GF_FALSE; + for (k=0; k<=4; k++) { + for (l=0; l<=4; l++) { + subp.x = (Float) x + 0.25*k; + subp.y = (Float) x + 0.25*l; + bc1 = edgeFunction_pre(&s3d->s_v2, s3d->s3_m_s2_x, s3d->s3_m_s2_y, &subp); + bc1 /= s3d->area; + bc2 = edgeFunction_pre(&s3d->s_v3, s3d->s1_m_s3_x, s3d->s1_m_s3_y, &subp); + bc2 /= s3d->area; + bc3 = 1.0 - bc1 - bc2; + if ((bc1<0) || (bc2<0) || (bc3<0)) continue; + subp_found = GF_TRUE; + break; + } + } +// assert(subp_found); + +#else + //method 2, reproject point on triangle edges + GF_Vec pt; + u32 on_line = 0; + + if ((bc1<0) || (bc2<0) || (bc3<0)) { + if ((bc1<0) && (bc2<0)) { bc3 = 1.0; bc1 = bc2 = 0; } + else if ((bc1<0) && (bc3<0)) { bc2 = 1.0; bc1 = bc3 = 0; } + else if ((bc2<0) && (bc3<0)) { bc1 = 1.0; bc2 = bc3 = 0; } + else if ((bc1<=bc2) && (bc1<=bc3)) on_line = 3; + else if ((bc2<=bc1) && (bc2<=bc3)) on_line = 2; + else if ((bc3<=bc1) && (bc3<=bc2)) on_line = 1; + } + //project pixel center on line v2v3 + if (on_line==3) { + bc1 = 0; + gf_vec_diff(pt, pix, s3d->s_v2); + pt.z=0; + bc2 = gf_vec_dot(pt, s3d->v2v3); + bc2 /= s3d->v2v3_length; + bc3 = float_clamp(bc2, 0, 1); + bc2 = FIX_ONE - bc3; + } + //project pixel center on line v1v3 + else if (on_line==2) { + bc2 = 0; + gf_vec_diff(pt, pix, s3d->s_v1); + pt.z=0; + bc1 = gf_vec_dot(pt, s3d->v1v3); + bc1 /= s3d->v1v3_length; + bc3 = float_clamp(bc1, 0, 1); + bc1 = FIX_ONE - bc3; + } + //project pixel center on line v1v2 + else if (on_line==1) { + bc3 = 0; + gf_vec_diff(pt, pix, s3d->s_v1); + pt.z=0; + bc1 = gf_vec_dot(pt, s3d->v1v2); + bc1 /= s3d->v1v2_length; + bc2 = float_clamp(bc1, 0, 1); + bc1 = FIX_ONE - bc2; + } +#endif + + } else +#endif + if (!s3d->mode2d) { + bc1 = float_clamp(bc1, 0, 1); + bc2 = float_clamp(bc2, 0, 1); + bc3 = float_clamp(bc3, 0, 1); + } + + full_cover = GF_FALSE; + transparent = GF_TRUE; + } + depth = s3d->s_v1.z * bc1 + s3d->s_v2.z * bc2 + s3d->s_v3.z * bc3; + } + + //clip by depth + if ((depthmin_depth) || (depth>s3d->max_depth)) { + spans[i].coverage=0; + continue; + } + + depth_buf_val = depth_line ? depth_line[x] : s3d->max_depth; + if (prev_partial) { + if ((spans[i].idx1==prev_partial->idx1) + || (spans[i].idx1==prev_partial->idx2) + || (spans[i].idx2==prev_partial->idx1) + || (spans[i].idx2==prev_partial->idx2) + ) { + depth = prev_partial->depth; + edge_merge = GF_TRUE; + } else { + edge_merge = GF_FALSE; + if (!s3d->depth_test(prev_partial->write_depth, depth_buf_val)) + depth_buf_val = prev_partial->write_depth; + } + } + + + //do depth test except for edges + if (! edge_merge && s3d->early_depth_test) { + if (! s3d->depth_test(depth_buf_val, depth)) { + spans[i].coverage=0; + continue; + } + } + + frag_param->screen_x = pix.x; + frag_param->screen_y = pix.y; + //compute Z window coord + frag_param->screen_z = s3d->zw_factor * depth + s3d->zw_offset; + frag_param->depth = depth; + frag_param->color.q = 1.0; + frag_param->frag_valid = 0; + /*perspective corrected barycentric, eg bc1/q1, bc2/q2, bc3/q3 - we already have store 1/q in the perspective divide step*/ + frag_param->pbc1 = bc1*s3d->s_v1.q; + frag_param->pbc2 = bc2*s3d->s_v2.q; + frag_param->pbc3 = bc3*s3d->s_v3.q; + + frag_param->persp_denum = frag_param->pbc1 + frag_param->pbc2 + frag_param->pbc3; + + if (!evg3d_get_fragment(surf, frag_param, &transparent)) { + spans[i].coverage=0; + continue; + } + if (!s3d->early_depth_test) { + if (! s3d->depth_test(depth_buf_val, frag_param->depth)) { + spans[i].coverage=0; + continue; + } + } + //we overwrite a partial + if (prev_partial) { + if (edge_merge) { + u32 ncov = coverage; + + ncov += prev_partial->cover; + if (!s3d->depth_test(depth_buf_val, depth)) { + remove_patch_pixel(sl, prev_partial->x); + spans[i].coverage=0; + continue; + } + if (ncov>=0xFF) { + if (prev_partial->cover==0xFF) { + spans[i].coverage=0; + continue; + } + remove_patch_pixel(sl, prev_partial->x); + full_cover = GF_TRUE; + coverage = 0xFF; + } else { + prev_partial->cover = ncov; + prev_partial->color = surf->fill_col; + spans[i].coverage=0; + continue; + } + } + else if (s3d->depth_test(prev_partial->write_depth, depth)) { + prev_partial->write_depth = depth; + } + } + + //partial coverage, store + if (!full_cover && !s3d->mode2d) { + push_patch_pixel(sl, x, surf->fill_col, coverage, depth, depth_buf_val, spans[i].idx1, spans[i].idx2); + spans[i].coverage=0; + continue; + } + //full opacity, write + else { + if (surf->fill_single) { + if (transparent) { + surf->fill_single_a(y, x, coverage, surf->fill_col, surf); + } else { + surf->fill_single(y, x, surf->fill_col, surf); + } + } else { + spans[i].coverage = 0xFF; + s3d->pix_vals[x] = surf->fill_col; + } + } + + //write depth + if (s3d->run_write_depth) + depth_line[x] = frag_param->depth; + + + } //end span.len loop + } //end spans count loop + + if (!surf->fill_single) { + surf->gray_spans(y, count, spans, surf); + } +} + +static GFINLINE Bool precompute_tri(EVG_Surface3DExt *s3d, EVGFragCallback *frag_ckck, TPos xmin, TPos xmax, TPos ymin, TPos ymax, + TPos _x1, TPos _y1, TPos _x2, TPos _y2, TPos _x3, TPos _y3, + GF_Vec4 *s_pt1, GF_Vec4 *s_pt2, GF_Vec4 *s_pt3, + u32 vidx1, u32 vidx2, u32 vidx3 +) +{ + /*completely outside viewport*/ + if ((_x1=xmax) && (_x2>=xmax) && (_x3>=xmax)) + return GF_FALSE; + if ((_y1>=ymax) && (_y2>=ymax) && (_y3>=ymax)) + return GF_FALSE; + + s3d->area = edgeFunction(s_pt1, s_pt2, s_pt3); + + s3d->s_v1 = *s_pt1; + s3d->s_v2 = *s_pt2; + s3d->s_v3 = *s_pt3; + frag_ckck->frag_param.idx1 = vidx1; + frag_ckck->frag_param.idx2 = vidx2; + frag_ckck->frag_param.idx3 = vidx3; + + + //precompute a few things for this run + s3d->s3_m_s2_x = s3d->s_v3.x - s3d->s_v2.x; + s3d->s3_m_s2_y = s3d->s_v3.y - s3d->s_v2.y; + s3d->s1_m_s3_x = s3d->s_v1.x - s3d->s_v3.x; + s3d->s1_m_s3_y = s3d->s_v1.y - s3d->s_v3.y; + s3d->s2_m_s1_x = s3d->s_v2.x - s3d->s_v1.x; + s3d->s2_m_s1_y = s3d->s_v2.y - s3d->s_v1.y; + + GF_Vec lv; + lv.z=0; + gf_vec_diff(lv, *s_pt2, *s_pt1); + s3d->v1v2_length = gf_vec_len(lv); + gf_vec_norm(&lv); + s3d->v1v2 = lv; + gf_vec_diff(lv, *s_pt3, *s_pt2); + s3d->v2v3_length = gf_vec_len(lv); + gf_vec_norm(&lv); + s3d->v2v3 = lv; + gf_vec_diff(lv, *s_pt3, *s_pt1); + s3d->v1v3_length = gf_vec_len(lv); + gf_vec_norm(&lv); + s3d->v1v3 = lv; + + return GF_TRUE; +} + +GF_Err evg_raster_render3d(GF_EVGSurface *surf, u32 *indices, u32 nb_idx, Float *vertices, u32 nb_vertices, u32 nb_comp, GF_EVGPrimitiveType prim_type) +{ + u32 i, li, size_y, nb_comp_1, idx_inc; + GF_Matrix projModeView; + EVG_Span span; + u32 is_strip_fan=0; + EVG_Raster raster = surf->raster; + EVG_Surface3DExt *s3d = surf->ext3d; + EVGFragCallback frag_ckck; + u32 first_patch, last_patch; + TPos xmin, xmax, ymin, ymax; + Bool glob_quad_done = GF_TRUE; + u32 prim_index=0; + GF_EVGVertexParam vparam; + int hpw=0; + int hlw=0; + if (!surf->ext3d->frag_shader) return GF_BAD_PARAM; + + raster->render_span = (EVG_Raster_Span_Func) EVG3D_SpanFunc; + raster->render_span_data = &frag_ckck; + + /* Set up state in the raster object */ + raster->min_ex = surf->clip_xMin; + raster->min_ey = surf->clip_yMin; + raster->max_ex = surf->clip_xMax; + raster->max_ey = surf->clip_yMax; + + s3d->run_write_depth = s3d->depth_buffer ? s3d->write_depth : GF_FALSE; + + size_y = (u32) (raster->max_ey - raster->min_ey); + if (raster->max_lines < size_y) { + raster->scanlines = (AAScanline*)gf_realloc(raster->scanlines, sizeof(AAScanline)*size_y); + memset(&raster->scanlines[raster->max_lines], 0, sizeof(AAScanline)*(size_y-raster->max_lines) ); + raster->max_lines = size_y; + } + for (li=0; liscanlines[li].pnum = 0; + } + first_patch = 0xFFFFFFFF; + last_patch = 0; + + gf_mx_copy(projModeView, s3d->proj); + gf_mx_add_matrix_4x4(&projModeView, &s3d->modelview); + + switch (prim_type) { + case GF_EVG_LINE_STRIP: + is_strip_fan = 1; + case GF_EVG_LINES: + idx_inc = 2; + hlw = (int) (s3d->line_size*ONE_PIXEL/2); + prim_type = GF_EVG_LINES; + break; + case GF_EVG_TRIANGLES: + idx_inc = 3; + break; + case GF_EVG_TRIANGLE_STRIP: + is_strip_fan = 1; + idx_inc = 3; + prim_type = GF_EVG_TRIANGLES; + break; + case GF_EVG_POLYGON: + case GF_EVG_TRIANGLE_FAN: + is_strip_fan = 2; + idx_inc = 3; + prim_type = GF_EVG_TRIANGLES; + break; + case GF_EVG_QUADS: + idx_inc = 4; + glob_quad_done = GF_FALSE; + break; + case GF_EVG_QUAD_STRIP: + idx_inc = 4; + glob_quad_done = GF_FALSE; + is_strip_fan = 1; + prim_type = GF_EVG_QUADS; + break; + default: + idx_inc = 1; + hpw = (int) (s3d->point_size*ONE_PIXEL/2); + s3d->pt_radius = (Float) (s3d->point_size*s3d->point_size) / 4; + + break; + } + if (!is_strip_fan && (nb_idx % idx_inc)) + return GF_BAD_PARAM; + + s3d->prim_type = prim_type; + memset(&frag_ckck, 0, sizeof(EVGFragCallback)); + frag_ckck.surf = surf; + frag_ckck.frag_param.ptype = prim_type; + frag_ckck.frag_param.prim_index = 0; + + xmin = raster->min_ex * ONE_PIXEL; + xmax = raster->max_ex * ONE_PIXEL; + ymin = raster->min_ey * ONE_PIXEL; + ymax = raster->max_ey * ONE_PIXEL; + + //raster coordinates of points + TPos _x1=0, _y1=0, _x2=0, _y2=0, _x3=0, _y3=0, _x4=0, _y4=0, _prev_x2=0, _prev_y2=0; + /*vertices in raster/window space: x and y are in pixel space, z is [min_depth, max_depth]*/ + GF_Vec4 s_pt1, s_pt2, s_pt3, s_pt4, prev_s_pt2; + u32 idx1=0, idx2=0, idx3=0, idx4=0; + u32 vidx1=0, vidx2=0, vidx3=0, vidx4=0, prev_vidx2=0; + + memset(&vparam, 0, sizeof(GF_EVGVertexParam)); + vparam.ptype = prim_type; + + nb_comp_1 = nb_comp-1; + for (i=0; ivert_shader) { + vparam.prim_index = prim_index; + } + prim_index++; + +#define GETVEC(_name, _idx)\ + vx->x = vertices[_idx];\ + vx->y = vertices[_idx+1];\ + if (_idx+nb_comp_1>=nb_vertices) return GF_BAD_PARAM;\ + vx->z = (nb_comp>2) ? vertices[_idx+2] : 0.0f;\ + vx->q = 1.0;\ + if (s3d->vert_shader) {\ + s3d->vert_shader(s3d->vert_shader_udta, &vparam); \ + s_##_name = vparam.out_vertex;\ + } else {\ + s_##_name.x = vx->x;\ + s_##_name.y = vx->y;\ + s_##_name.z = vx->z;\ + s_##_name.q = 1;\ + gf_mx_apply_vec_4x4(&projModeView, &s_##_name);\ + }\ + if (!evg3d_persp_divide(&s_##_name)) continue;\ + + /*get vertice, apply transform and perspective divide and translate to raster*/ + if (is_strip_fan && i) { + idx_inc=1; + if (prim_type==GF_EVG_LINES) { + _x1 = _x2; + _y1 = _y2; + s_pt1 = s_pt2; + vidx1 = vidx2; + vidx2 = indices[i]; + idx2 = vidx2 * nb_comp; + vparam.vertex_idx = vidx2; + vparam.vertex_idx_in_prim = 1; + GETVEC(pt2, idx2) + evg3d_ndc_to_raster(surf, &s_pt2, &_x2, &_y2); + } + //triangle strip + else if (is_strip_fan==1) { + /*triangle strip*/ + if (prim_type==GF_EVG_TRIANGLES) { + /*swap v2 to v1 and v3 to v2*/ + _x1 = _x2; + _y1 = _y2; + s_pt1 = s_pt2; + vidx1 = vidx2; + + _x2 = _x3; + _y2 = _y3; + s_pt2 = s_pt3; + vidx2 = vidx3; + + vidx3 = indices[i]; + idx3 = vidx3 * nb_comp; + vparam.vertex_idx = vidx3; + vparam.vertex_idx_in_prim = 2; + GETVEC(pt3, idx3) + evg3d_ndc_to_raster(surf, &s_pt3, &_x3, &_y3); + } + //quad strip - since we draw [v1, v2, v3, v4] as 2 triangles [v1, v2, v3] and [v1, v3, v4] + // we only need to restore prev v2 as v1 + else { + _x1 = _prev_x2; + _y1 = _prev_y2; + s_pt1 = prev_s_pt2; + vidx1 = prev_vidx2; + + vidx4 = indices[i]; + idx4 = vidx4 * nb_comp; + vparam.vertex_idx = vidx4; + vparam.vertex_idx_in_prim = 3; + GETVEC(pt4, idx4) + evg3d_ndc_to_raster(surf, &s_pt4, &_x4, &_y4); + } + } + //triangle fan + else { + //keep center and move 3rd vertex as second + _x2 = _x3; + _y2 = _y3; + s_pt2 = s_pt3; + vidx2 = vidx3; + + vidx3 = indices[i]; + idx3 = vidx3 * nb_comp; + vparam.vertex_idx = vidx3; + vparam.vertex_idx_in_prim = 2; + GETVEC(pt3, idx3) + evg3d_ndc_to_raster(surf, &s_pt3, &_x3, &_y3); + } + } else { + vidx1 = indices[i]; + idx1 = vidx1 * nb_comp; + vparam.vertex_idx = vidx1; + vparam.vertex_idx_in_prim = 0; + GETVEC(pt1, idx1) + evg3d_ndc_to_raster(surf, &s_pt1, &_x1, &_y1); + if (prim_type>=GF_EVG_LINES) { + vidx2 = indices[i+1]; + idx2 = vidx2 * nb_comp; + vparam.vertex_idx = vidx2; + vparam.vertex_idx_in_prim = 1; + GETVEC(pt2, idx2) + evg3d_ndc_to_raster(surf, &s_pt2, &_x2, &_y2); + if (prim_type>=GF_EVG_TRIANGLES) { + vidx3 = indices[i+2]; + idx3 = vidx3 * nb_comp; + vparam.vertex_idx = vidx3; + vparam.vertex_idx_in_prim = 2; + GETVEC(pt3, idx3) + evg3d_ndc_to_raster(surf, &s_pt3, &_x3, &_y3); + if (prim_type==GF_EVG_QUADS) { + vidx4 = indices[i+3]; + idx4 = vidx4 * nb_comp; + vparam.vertex_idx = vidx4; + vparam.vertex_idx_in_prim = 3; + GETVEC(pt4, idx4) + evg3d_ndc_to_raster(surf, &s_pt4, &_x4, &_y4); + } + } + } + } +#undef GETVEC + +restart_quad: + raster->first_scanline = surf->height; + raster->ex = (int) (raster->max_ex+1); + raster->ey = (int) (raster->max_ey+1); + raster->cover = 0; + raster->area = 0; + + + if (prim_type>=GF_EVG_TRIANGLES) { + + if (!precompute_tri(s3d, &frag_ckck, xmin, xmax, ymin, ymax, _x1, _y1, _x2, _y2, _x3, _y3, &s_pt1, &s_pt2, &s_pt3, vidx1, vidx2, vidx3)) + continue; + + //check backcull + if (s3d->is_ccw) { + if (s3d->area<0) { + if (s3d->backface_cull) + continue; + } + } else { + if (s3d->area>0) { + if (s3d->backface_cull) + continue; + } + } + } else { + s3d->s_v1 = s_pt1; + frag_ckck.frag_param.idx1 = vidx1; + if (prim_type==GF_EVG_LINES) { + GF_Vec lv; + s3d->s_v2 = s_pt2; + gf_vec_diff(lv, s_pt2, s_pt1); + lv.z=0; + s3d->v1v2_length = gf_vec_len(lv); + frag_ckck.frag_param.idx2 = vidx2; + } + } + frag_ckck.frag_param.prim_index = prim_index-1; + + if (prim_type==GF_EVG_POINTS) { + raster->idx1 = raster->idx2 = idx1; + //draw square + gray3d_move_to(raster, _x1-hpw, _y1-hpw); + gray_render_line(raster, _x1+hpw, _y1-hpw); + gray_render_line(raster, _x1+hpw, _y1+hpw); + gray_render_line(raster, _x1-hpw, _y1+hpw); + //and close + gray_render_line(raster, _x1-hpw, _y1-hpw); + gray_record_cell( raster ); + } else if (prim_type==GF_EVG_LINES) { + raster->idx1 = idx1; + raster->idx2 = idx2; + gray3d_move_to(raster, _x1+hlw, _y1+hlw); + gray_render_line(raster, _x1-hlw, _y1-hlw); + gray_render_line(raster, _x2-hlw, _y2-hlw); + gray_render_line(raster, _x2+hlw, _y2+hlw); + //and close + gray_render_line(raster, _x1+hlw, _y1+hlw); + gray_record_cell( raster ); + } else { + raster->idx1 = idx1; + raster->idx2 = idx2; + gray3d_move_to(raster, _x1, _y1); + gray_render_line(raster, _x2, _y2); + raster->idx1 = idx2; + raster->idx2 = idx3; + gray_render_line(raster, _x3, _y3); + + //and close + raster->idx1 = idx3; + raster->idx2 = idx1; + gray_render_line(raster, _x1, _y1); + gray_record_cell( raster ); + } + + /* sort each scanline and render it*/ + for (li=raster->first_scanline; liscanlines[li]; + //if nothing on this line, we are done for this primitive + if (!sl->num) break; + + if (sl->num>1) gray_quick_sort(sl->cells, sl->num); + gray_sweep_line(raster, sl, li, GF_FALSE); + + if (sl->pnum) { + if (first_patch>li) first_patch = li; + if (last_patchs_v1 = s3d->s_v2; + s3d->s_v2 = s3d->s_v3; + goto restart_quad; + } + } + + /*flush all partial fragments*/ + span.len = 0; + for (li=first_patch; li<=last_patch; li++) { + AAScanline *sl = &raster->scanlines[li]; + Float *depth_line = &surf->ext3d->depth_buffer[li]; + for (i=0; ipnum; i++) { + PatchPixel *pi = &sl->pixels[i]; + + if (pi->cover == 0xFF) continue; + if (!surf->ext3d->depth_test(pi->write_depth, pi->depth)) continue; + + if (surf->fill_single) { + surf->fill_single_a(li, pi->x, pi->cover, pi->color, surf); + } else { + span.coverage = pi->cover; + span.x = pi->x; + surf->gray_spans(li, 1, &span, surf); + } + if (surf->ext3d->run_write_depth) + depth_line[pi->x] = pi->depth; + } + } + return GF_OK; +} + +GF_Err evg_raster_render3d_path(GF_EVGSurface *surf, GF_Path *path, Float z) +{ + + EVG_Vector v_start; + int n; /* index of contour in outline */ + int first; /* index of first point in contour */ + TPos _x, _y, _sx, _sy; + u32 li, size_y; + EVG_Raster raster = surf->raster; + EVG_Outline *outline; + GF_Matrix projModeView; + EVG_Surface3DExt *s3d = surf->ext3d; + EVGFragCallback frag_ckck; + u32 xmin, xmax, ymin, ymax; + GF_Err e; + GF_Rect rc; + + if (!surf->ext3d->frag_shader) return GF_BAD_PARAM; + + e = gf_evg_surface_set_path(surf, path); + if (e) return e; + + outline = &surf->ftoutline; + + raster->render_span = (EVG_Raster_Span_Func) EVG3D_SpanFunc; + raster->render_span_data = &frag_ckck; + + /* Set up state in the raster object */ + raster->min_ex = surf->clip_xMin; + raster->min_ey = surf->clip_yMin; + raster->max_ex = surf->clip_xMax; + raster->max_ey = surf->clip_yMax; + s3d->run_write_depth = s3d->depth_buffer ? s3d->write_depth : GF_FALSE; + + size_y = (u32) (raster->max_ey - raster->min_ey); + if (raster->max_lines < size_y) { + raster->scanlines = (AAScanline*)gf_realloc(raster->scanlines, sizeof(AAScanline)*size_y); + memset(&raster->scanlines[raster->max_lines], 0, sizeof(AAScanline)*(size_y-raster->max_lines) ); + raster->max_lines = size_y; + } + + gf_mx_copy(projModeView, s3d->proj); + gf_mx_add_matrix_4x4(&projModeView, &s3d->modelview); + + s3d->prim_type = GF_EVG_TRIANGLES; + memset(&frag_ckck, 0, sizeof(EVGFragCallback)); + frag_ckck.surf = surf; + frag_ckck.frag_param.ptype = GF_EVG_TRIANGLES; + frag_ckck.frag_param.prim_index = 0; + + xmin = raster->min_ex * ONE_PIXEL; + xmax = raster->max_ex * ONE_PIXEL; + ymin = raster->min_ey * ONE_PIXEL; + ymax = raster->max_ey * ONE_PIXEL; + + //raster coordinates of points + TPos _x1, _y1, _x2, _y2, _x3, _y3; + /*vertices in raster/window space: x and y are in pixel space, z is [min_depth, max_depth]*/ + GF_Vec4 s_pt1, s_pt2, s_pt3; + + /*get bounds, and compute interpolation from (top-left, top-right, bottom-right) vertices*/ + gf_path_get_bounds(path, &rc); + s_pt1.x = rc.x; s_pt1.y = rc.y; s_pt1.z = z; s_pt1.q = 1; + s_pt2.x = rc.x + rc.width; s_pt2.y = rc.y; s_pt2.z = z; s_pt2.q = 1; + s_pt3.x = rc.x + rc.width; s_pt3.y = rc.y - rc.height ; s_pt3.z = z; s_pt3.q = 1; + + gf_mx_apply_vec_4x4(&projModeView, &s_pt1); + evg3d_persp_divide(&s_pt1); + evg3d_ndc_to_raster(surf, &s_pt1, &_x1, &_y1); + gf_mx_apply_vec_4x4(&projModeView, &s_pt2); + evg3d_persp_divide(&s_pt2); + evg3d_ndc_to_raster(surf, &s_pt2, &_x2, &_y2); + gf_mx_apply_vec_4x4(&projModeView, &s_pt3); + evg3d_persp_divide(&s_pt3); + evg3d_ndc_to_raster(surf, &s_pt3, &_x3, &_y3); + + if (!precompute_tri(s3d, &frag_ckck, xmin, xmax, ymin, ymax, _x1, _y1, _x2, _y2, _x3, _y3, &s_pt1, &s_pt2, &s_pt3, 0, 1, 2)) + return GF_OK; + + s3d->mode2d = GF_TRUE; + first = 0; + for ( n = 0; n < outline->n_contours; n++ ) { + GF_Vec4 pt; + EVG_Vector *point; + EVG_Vector *limit; + int last; /* index of last point in contour */ + last = outline->contours[n]; + limit = outline->points + last; + v_start = outline->points[first]; + point = outline->points + first; + + pt.x = v_start.x; + pt.y = v_start.y; + pt.z = z; + pt.q = 1; + gf_mx_apply_vec_4x4(&projModeView, &pt); + if (!evg3d_persp_divide(&pt)) { + continue; + } + evg3d_ndc_to_raster(surf, &pt, &_sx, &_sy); + gray3d_move_to(raster, _sx, _sy); + while ( point < limit ) { + point++; + + pt.x = point->x; + pt.y = point->y; + pt.z = z; + pt.q = 1; + gf_mx_apply_vec_4x4(&projModeView, &pt); + if (!evg3d_persp_divide(&pt)) { + break; + } + evg3d_ndc_to_raster(surf, &pt, &_x, &_y); + gray_render_line(raster, _x, _y); + } + gray_render_line(raster, _sx, _sy); + + first = last + 1; + } + + gray_record_cell( raster ); + /* sort each scanline and render it*/ + for (li=raster->first_scanline; liscanlines[li]; + if (sl->num) { + if (sl->num>1) gray_quick_sort(sl->cells, sl->num); + gray_sweep_line(raster, sl, li, GF_TRUE); + } + } + surf->ext3d->mode2d = GF_FALSE; + return GF_OK; + +} +GF_Err evg_3d_resize(GF_EVGSurface *surf) +{ +/* surf->ext3d->depth_buffer = gf_realloc(surf->ext3d->depth_buffer, sizeof(Float)*surf->width*surf->height); + if (!surf->ext3d->depth_buffer) return GF_OUT_OF_MEM; +*/ + surf->ext3d->depth_buffer = NULL; + + if (!surf->ext3d->vp_w || !surf->ext3d->vp_h) { + surf->ext3d->vp_x = 0; + surf->ext3d->vp_y = 0; + surf->ext3d->vp_w = surf->width; + surf->ext3d->vp_h = surf->height; + } + surf->ext3d->pix_vals = gf_realloc(surf->ext3d->pix_vals, sizeof(u32)*surf->width); + return GF_OK; +} + +GF_EXPORT +GF_Err gf_evg_surface_clear_depth(GF_EVGSurface *surf, Float depth) +{ + u32 i, lsize; + Float *depths; + u8 *depth_p; + if (!surf->ext3d) return GF_BAD_PARAM; + + depths = surf->ext3d->depth_buffer; + if (!depths) return GF_OK; //GF_BAD_PARAM; + //copy first line + for (i=0; iwidth; i++) { + depths[i] = depth; + } + //copy first line + depth_p = (u8 *) surf->ext3d->depth_buffer; + lsize = sizeof(Float) * surf->width; + for (i=1; iheight; i++) { + u8 *depth_l = depth_p + i*lsize; + memcpy(depth_l, depth_p, lsize); + } + return GF_OK; +} + + +GF_EXPORT +GF_Err gf_evg_surface_viewport(GF_EVGSurface *surf, u32 x, u32 y, u32 w, u32 h) +{ + if (!surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->vp_x = x; + surf->ext3d->vp_y = y; + surf->ext3d->vp_w = w; + surf->ext3d->vp_h = h; + return GF_OK; +} + +static Bool depth_test_never(Float depth_buf_value, Float frag_value) +{ + return GF_FALSE; +} +static Bool depth_test_always(Float depth_buf_value, Float frag_value) +{ + return GF_TRUE; +} +static Bool depth_test_less(Float depth_buf_value, Float frag_value) +{ + if (frag_valuedepth_buf_value) return GF_TRUE; + return GF_FALSE; +} +static Bool depth_test_greater_equal(Float depth_buf_value, Float frag_value) +{ + if (frag_value>=depth_buf_value) return GF_TRUE; + return GF_FALSE; +} +static Bool depth_test_not_equal(Float depth_buf_value, Float frag_value) +{ + if (frag_value==depth_buf_value) return GF_FALSE; + return GF_TRUE; +} + +GF_EXPORT +GF_Err gf_evg_set_depth_test(GF_EVGSurface *surf, GF_EVGDepthTest mode) +{ + if (!surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->write_depth = GF_TRUE; + switch (mode) { + case GF_EVGDEPTH_NEVER: + surf->ext3d->depth_test = depth_test_never; + return GF_OK; + case GF_EVGDEPTH_ALWAYS: + surf->ext3d->depth_test = depth_test_always; + return GF_OK; + case GF_EVGDEPTH_EQUAL: + surf->ext3d->depth_test = depth_test_equal; + return GF_OK; + case GF_EVGDEPTH_NEQUAL: + surf->ext3d->depth_test = depth_test_not_equal; + return GF_OK; + case GF_EVGDEPTH_LESS: + surf->ext3d->depth_test = depth_test_less; + return GF_OK; + case GF_EVGDEPTH_LESS_EQUAL: + surf->ext3d->depth_test = depth_test_less_equal; + return GF_OK; + case GF_EVGDEPTH_GREATER: + surf->ext3d->depth_test = depth_test_greater; + return GF_OK; + case GF_EVGDEPTH_GREATER_EQUAL: + surf->ext3d->depth_test = depth_test_greater_equal; + return GF_OK; + case GF_EVGDEPTH_DISABLE: + surf->ext3d->depth_test = depth_test_always; + surf->ext3d->write_depth = GF_FALSE; + return GF_OK; + } + return GF_BAD_PARAM; + +} + +GF_Err evg_3d_update_depth_range(GF_EVGSurface *surf) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->depth_range = surf->ext3d->max_depth - surf->ext3d->min_depth; + if (surf->ext3d->clip_zero) { + surf->ext3d->zw_factor = surf->ext3d->depth_range; + surf->ext3d->zw_offset = surf->ext3d->min_depth; + } else { + surf->ext3d->zw_factor = surf->ext3d->depth_range / 2; + surf->ext3d->zw_offset = (surf->ext3d->min_depth+surf->ext3d->max_depth)/2; + + } + return GF_OK; +} + +static void yuv_3d_fill_run(GF_EVGStencil *p, GF_EVGSurface *surf, s32 _x, s32 _y, u32 count) +{ + u32 *data = surf->stencil_pix_run; + u32 *src = surf->ext3d->pix_vals + _x; + + memcpy(data, src, sizeof(u32)*count); +} + +EVG_Surface3DExt *evg_init_3d_surface(GF_EVGSurface *surf) +{ + EVG_Surface3DExt *ext3d; + GF_SAFEALLOC(ext3d, EVG_Surface3DExt); + if (!ext3d) return NULL; + gf_mx_init(ext3d->proj); + gf_mx_init(ext3d->modelview); + ext3d->backface_cull = GF_TRUE; + ext3d->is_ccw = GF_TRUE; + ext3d->min_depth = 0; + ext3d->max_depth = FIX_ONE; + ext3d->depth_range = FIX_ONE; + ext3d->point_size = FIX_ONE; + ext3d->line_size = FIX_ONE; + ext3d->clip_zero = GF_FALSE; + ext3d->disable_aa = GF_FALSE; + ext3d->write_depth = GF_TRUE; + ext3d->early_depth_test = GF_TRUE; + ext3d->depth_test = depth_test_less; + ext3d->yuv_sten.fill_run = yuv_3d_fill_run; + evg_3d_update_depth_range(surf); + return ext3d; +} + + + +GF_Err gf_evg_surface_set_fragment_shader(GF_EVGSurface *surf, gf_evg_fragment_shader shader, void *shader_udta) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->frag_shader = shader; + surf->ext3d->frag_shader_udta = shader_udta; + return GF_OK; +} + +GF_Err gf_evg_surface_disable_early_depth(GF_EVGSurface *surf, Bool disable) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->early_depth_test = !disable; + return GF_OK; +} + +GF_Err gf_evg_surface_set_vertex_shader(GF_EVGSurface *surf, gf_evg_vertex_shader shader, void *shader_udta) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->vert_shader = shader; + surf->ext3d->vert_shader_udta = shader_udta; + return GF_OK; +} + +GF_Err gf_evg_surface_set_ccw(GF_EVGSurface *surf, Bool is_ccw) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->is_ccw = is_ccw; + return GF_OK; +} +GF_Err gf_evg_surface_set_backcull(GF_EVGSurface *surf, Bool backcull) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->backface_cull = backcull; + return GF_OK; +} +GF_Err gf_evg_surface_set_antialias(GF_EVGSurface *surf, Bool antialias) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->disable_aa = antialias ? GF_FALSE : GF_TRUE; + return GF_OK; +} +GF_Err gf_evg_surface_set_min_depth(GF_EVGSurface *surf, Float min_depth) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->min_depth = min_depth; + evg_3d_update_depth_range(surf); + return GF_OK; +} +GF_Err gf_evg_surface_set_max_depth(GF_EVGSurface *surf, Float max_depth) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->max_depth = max_depth; + evg_3d_update_depth_range(surf); + return GF_OK; +} + +GF_Err gf_evg_surface_set_clip_zero(GF_EVGSurface *surf, Bool clip_zero) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->clip_zero = clip_zero; + evg_3d_update_depth_range(surf); + return GF_OK; +} + +GF_Err gf_evg_surface_set_point_size(GF_EVGSurface *surf, Float size) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->point_size = size; + return GF_OK; +} +GF_Err gf_evg_surface_set_line_size(GF_EVGSurface *surf, Float size) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->line_size = size; + return GF_OK; +} +GF_Err gf_evg_surface_set_point_smooth(GF_EVGSurface *surf, Bool smooth) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->smooth_points = smooth; + return GF_OK; +} + +GF_Err gf_evg_surface_write_depth(GF_EVGSurface *surf, Bool do_write) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->write_depth = do_write; + return GF_OK; +} +GF_Err gf_evg_surface_set_depth_buffer(GF_EVGSurface *surf, Float *depth) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + surf->ext3d->depth_buffer = depth; + return GF_OK; +} diff --git a/src/evg/raster_565.c b/src/evg/raster_565.c new file mode 100644 index 0000000..82d2404 --- /dev/null +++ b/src/evg/raster_565.c @@ -0,0 +1,662 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / software 2D rasterizer + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + */ + + +#include "rast_soft.h" + + +/* + FIXME / WARNING - this code only works for little endian platfoms + not seen any big endian platform using RGB565/RGB555 output +*/ + + +static GFINLINE s32 +mul255(s32 a, s32 b) +{ + return ((a + 1) * b) >> 8; +} + + +/* + RGB 565 part +*/ + +static void write_565(u8 *dst, u8 r, u8 g, u8 b) +{ + r>>=3; + g>>=2; + b>>=3; + + dst[0] = r; + dst[0] <<= 3; + dst[0] |= ((g >> 3) & 0x7); + dst[1] = (g & 0x7); + dst[1] <<= 5; + dst[1] |= b; +} + +static void overmask_565(u32 src, u8 *dst, u32 alpha) +{ + u32 resr, resg, resb; + s32 srca = (src >> 24) & 0xff; + s32 srcr = (src >> 16) & 0xff; + s32 srcg = (src >> 8) & 0xff; + s32 srcb = (src >> 0) & 0xff; + + s32 dstr = (dst[0] >> 3) & 0x1f; + s32 dstg = (dst[0]) & 0x7; + dstg <<= 3; + dstg |= (dst[1]>>3) & 0x7; + s32 dstb = (dst[1]) & 0x1f; + + dstr<<=3; + dstg<<=2; + dstb<<=3; + + srca = mul255(srca, alpha); + resr = mul255(srca, srcr - dstr) + dstr; + resg = mul255(srca, srcg - dstg) + dstg; + resb = mul255(srca, srcb - dstb) + dstb; + write_565(dst, resr, resg, resb); +} + +void overmask_565_const_run(u32 src, u8 *dst, s32 dst_pitch_x, u32 count) +{ + u32 resr, resg, resb; + u8 srca = (src >> 24) & 0xff; + u8 srcr = (src >> 16) & 0xff; + u8 srcg = (src >> 8) & 0xff; + u8 srcb = (src >> 0) & 0xff; + + while (count) { + s32 dstr = (dst[0] >> 3) & 0x1f; + s32 dstg = (dst[0]) & 0x7; + dstg <<= 3; + dstg |= (dst[1]>>3) & 0x7; + s32 dstb = (dst[1]) & 0x1f; + + dstr<<=3; + dstg<<=2; + dstb<<=3; + + resr = mul255(srca, srcr - dstr) + dstr; + resg = mul255(srca, srcg - dstg) + dstg; + resb = mul255(srca, srcb - dstb) + dstb; + write_565(dst, resr, resg, resb); + dst += dst_pitch_x; + count--; + } +} + + +void evg_565_fill_single(s32 y, s32 x, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + u8 r = GF_COL_R(col); + u8 g = GF_COL_G(col); + u8 b = GF_COL_B(col); + write_565(dst, r, g, b); +} + +void evg_565_fill_single_a(s32 y, s32 x, u8 coverage, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + overmask_565(col, dst, coverage); +} + + +void evg_565_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u32 col = surf->fill_col; + u32 a, fin, col_no_a; + u8 r, g, b; + u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; + register s32 i; + u32 len; + s32 x; + + col_no_a = col&0x00FFFFFF; + r = GF_COL_R(col); + g = GF_COL_G(col); + b = GF_COL_B(col); + + for (i=0; ipitch_x; + len = spans[i].len; + if (spans[i].coverage != 0xFF) { + a = mul255(0xFF, spans[i].coverage); + fin = (a<<24) | (col_no_a); + overmask_565_const_run(fin, dst+x, surf->pitch_x, len); + } else { + while (len--) { + write_565(dst+x, r, g, b); + x+=surf->pitch_x; + } + } + } +} + +void evg_565_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; + u32 col = surf->fill_col; + register u32 a, fin, col_no_a; + register s32 i; + + a = (col>>24)&0xFF; + col_no_a = col&0x00FFFFFF; + if (surf->get_alpha) { + for (i=0; iget_alpha(surf->get_alpha_udta, a, x, y); + fin = mul255(aa, spans[i].coverage); + fin = (fin<<24) | col_no_a; + overmask_565_const_run(fin, dst + x * surf->pitch_x, surf->pitch_x, 1); + } + } + } else { + for (i=0; ipitch_x, surf->pitch_x, spans[i].len); + } + } +} + + +void evg_565_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; + register s32 i; + + for (i=0; isten, surf, spans[i].x, y, len); + col = surf->stencil_pix_run; + x = spans[i].x * surf->pitch_x; + while (len--) { + col_a = GF_COL_A(*col); + if (col_a) { + u32 _col = *col; + if ((spanalpha!=0xFF) || (col_a != 0xFF)) { + overmask_565(_col, dst+x, spanalpha); + } else { + write_565(dst+x, GF_COL_R(_col), GF_COL_G(_col), GF_COL_B(_col)); + } + } + col++; + x += surf->pitch_x; + } + } +} + +GF_Err evg_surface_clear_565(GF_EVGSurface *surf, GF_IRect rc, GF_Color col) +{ + register u32 x, y, w, h, sx, sy; + register u8 r, g, b; + u8 *data_o; + + h = rc.height; + w = rc.width; + sx = rc.x; + sy = rc.y; + + /*convert to 565*/ + r = GF_COL_R(col); + g = GF_COL_R(col); + b = GF_COL_R(col); + + data_o = NULL; + for (y=0; ypixels + (sy+y) * surf->pitch_y + surf->pitch_x*sx; + if (!y) { + data_o = data; + for (x=0; xpitch_x; + } + } else { + memcpy(data, data_o, w*surf->pitch_x); + } + } + return GF_OK; +} + + + +/* + RGB 555 part +*/ + +static void write_555(u8 *dst, u8 r, u8 g, u8 b) +{ + r>>=3; + g>>=3; + b>>=3; + + dst[0] = r; + dst[0] <<= 2; + dst[0] |= ((g >> 3) & 0x3); + dst[1] = (g & 0x7); + dst[1] <<= 5; + dst[1] |= b; +} + +static void overmask_555(u32 src, u8 *dst, u32 alpha) +{ + u32 resr, resg, resb; + s32 srca = (src >> 24) & 0xff; + s32 srcr = (src >> 16) & 0xff; + s32 srcg = (src >> 8) & 0xff; + s32 srcb = (src >> 0) & 0xff; + + s32 dstr = (dst[0] >> 2) & 0x1f; + s32 dstg = (dst[0] ) & 0x3; + dstg <<= 3; + dstg |= (dst[1]>>5 ) & 0x7; + s32 dstb = (dst[1] ) & 0x1f; + dstr<<=3; + dstg<<=3; + dstb<<=3; + + srca = mul255(srca, alpha); + resr = mul255(srca, srcr - dstr) + dstr; + resg = mul255(srca, srcg - dstg) + dstg; + resb = mul255(srca, srcb - dstb) + dstb; + write_555(dst, resr, resg, resb); +} + +static void overmask_555_const_run(u32 src, u8 *dst, s32 dst_pitch_x, u32 count) +{ + u32 resr, resg, resb; + u8 srca = (src >> 24) & 0xff; + u8 srcr = (src >> 16) & 0xff; + u8 srcg = (src >> 8) & 0xff; + u8 srcb = (src >> 0) & 0xff; + + while (count) { + s32 dstr = (dst[0] >> 2) & 0x1f; + s32 dstg = (dst[0] ) & 0x3; + dstg <<= 3; + dstg |= (dst[1]>>5 ) & 0x7; + s32 dstb = (dst[1] ) & 0x1f; + dstr<<=3; + dstg<<=3; + dstb<<=3; + + resr = mul255(srca, srcr - dstr) + dstr; + resg = mul255(srca, srcg - dstg) + dstg; + resb = mul255(srca, srcb - dstb) + dstb; + write_555(dst, resr, resg, resb); + dst += dst_pitch_x; + count--; + } +} + + +void evg_555_fill_single(s32 y, s32 x, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + u8 r = GF_COL_R(col); + u8 g = GF_COL_G(col); + u8 b = GF_COL_B(col); + write_555(dst, r, g, b); +} + +void evg_555_fill_single_a(s32 y, s32 x, u8 coverage, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + overmask_555(col, dst, coverage); +} + +void evg_555_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u32 col = surf->fill_col; + u32 a, fin, col_no_a; + u8 *dst = surf->pixels + y * surf->pitch_y; + u8 r, g, b; + s32 i, x; + u32 len; + + r = GF_COL_R(col); + g = GF_COL_G(col); + b = GF_COL_B(col); + + col_no_a = col&0x00FFFFFF; + for (i=0; ipitch_x; + len = spans[i].len; + if (spans[i].coverage != 0xFF) { + a = mul255(0xFF, spans[i].coverage); + fin = (a<<24) | col_no_a; + overmask_555_const_run(fin, dst+x, surf->pitch_x, len); + } else { + while (len--) { + write_555(dst+x, r, g, b); + x+=surf->pitch_x; + } + } + } +} + +void evg_555_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y; + u32 col = surf->fill_col; + u32 a, fin, col_no_a; + s32 i; + + a = (col>>24)&0xFF; + col_no_a = col & 0x00FFFFFF; + if (surf->get_alpha) { + for (i=0; iget_alpha(surf->get_alpha_udta, a, x, y); + fin = mul255(aa, spans[i].coverage); + fin = (fin<<24) | col_no_a; + overmask_555_const_run(fin, dst + x*surf->pitch_x, surf->pitch_x, 1); + } + } + } else { + for (i=0; ipitch_x, surf->pitch_x, spans[i].len); + } + } +} + + +void evg_555_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y; + s32 i; + + for (i=0; isten, surf, spans[i].x, y, len); + col = surf->stencil_pix_run; + x = spans[i].x * surf->pitch_x; + while (len--) { + col_a = GF_COL_A(*col); + if (col_a) { + u32 _col = *col; + if ((spanalpha!=0xFF) || (col_a != 0xFF)) { + overmask_555(*col, dst+x, spanalpha); + } else { + write_555(dst+x, GF_COL_R(_col), GF_COL_G(_col), GF_COL_B(_col) ); + } + } + col++; + x += surf->pitch_x; + } + } +} + +GF_Err evg_surface_clear_555(GF_EVGSurface *surf, GF_IRect rc, GF_Color col) +{ + u32 x, y, w, h, sx, sy; + u8 r, g, b; + u8 *data_o; + + h = rc.height; + w = rc.width; + sx = rc.x; + sy = rc.y; + r = GF_COL_R(col); + g = GF_COL_G(col); + b = GF_COL_B(col); + + data_o = NULL; + for (y=0; ypixels + (sy+y) * surf->pitch_y + surf->pitch_x*sx; + if (!y) { + data_o = data; + for (x=0; xpitch_x; + } + } else { + memcpy(data, data_o, w*surf->pitch_x); + } + } + return GF_OK; +} + + +/* + RGB 444 part +*/ + +static void write_444(u8 *dst, u8 r, u8 g, u8 b) +{ + dst[0] = r >> 4; + dst[1] = g >> 4; + dst[1]<<=4; + dst[1] |= b >> 4; +} + +static void overmask_444(u8 *dst, u32 src, u32 alpha) +{ + u32 resr, resg, resb; + s32 srca = (src >> 24) & 0xff; + s32 srcr = (src >> 16) & 0xff; + s32 srcg = (src >> 8) & 0xff; + s32 srcb = (src >> 0) & 0xff; + + s32 dstr = (dst[0] & 0x0f) << 4; + s32 dstg = ((dst[1] >> 4) & 0x0f) << 4; + s32 dstb = (dst[1] & 0x0f) << 4; + + srca = mul255(srca, alpha); + resr = mul255(srca, srcr - dstr) + dstr; + resg = mul255(srca, srcg - dstg) + dstg; + resb = mul255(srca, srcb - dstb) + dstb; + write_444(dst, resr, resg, resb); +} + +static void overmask_444_const_run(u32 src, u8 *dst, s32 dst_pitch_x, u32 count) +{ + u32 resr, resg, resb; + s32 srca = (src >> 24) & 0xff; + s32 srcr = (src >> 16) & 0xff; + s32 srcg = (src >> 8) & 0xff; + s32 srcb = (src >> 0) & 0xff; + + while (count) { + s32 dstr = dst[0] & 0x0f; + s32 dstg = (dst[1]>>4) & 0x0f; + s32 dstb = (dst[1]) & 0x0f; + + dstr<<=4; + dstg<<=4; + dstb<<=4; + + resr = mul255(srca, srcr - dstr) + dstr; + resg = mul255(srca, srcg - dstg) + dstg; + resb = mul255(srca, srcb - dstb) + dstb; + write_444(dst, resr, resg, resb); + + dst += dst_pitch_x; + count--; + } +} + + +void evg_444_fill_single(s32 y, s32 x, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + u8 r = GF_COL_R(col); + u8 g = GF_COL_G(col); + u8 b = GF_COL_B(col); + write_444(dst, r, g, b); +} + +void evg_444_fill_single_a(s32 y, s32 x, u8 coverage, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + overmask_444(dst, col, coverage); +} + +void evg_444_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u32 col = surf->fill_col; + u32 a, fin, col_no_a; + u8 *dst = surf->pixels + y * surf->pitch_y; + u8 r, g, b; + s32 i, x; + u32 len; + + r = GF_COL_R(col); + g = GF_COL_G(col); + b = GF_COL_B(col); + + col_no_a = col&0x00FFFFFF; + for (i=0; ipitch_x; + len = spans[i].len; + if (spans[i].coverage != 0xFF) { + a = mul255(0xFF, spans[i].coverage); + fin = (a<<24) | col_no_a; + overmask_444_const_run(fin, dst+x, surf->pitch_x, len); + } else { + while (len--) { + write_444(dst+x, r, g, b); + x+=surf->pitch_x; + } + } + } +} + +void evg_444_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y; + u32 col = surf->fill_col; + u32 a, fin, col_no_a; + s32 i; + + a = (col>>24)&0xFF; + col_no_a = col & 0x00FFFFFF; + + + if (surf->get_alpha) { + for (i=0; iget_alpha(surf->get_alpha_udta, a, x, y); + fin = mul255(aa, spans[i].coverage); + fin = (fin<<24) | col_no_a; + overmask_444_const_run(fin, dst + x*surf->pitch_x, surf->pitch_x, 1); + } + } + } else { + for (i=0; ipitch_x, surf->pitch_x, spans[i].len); + } + } +} + + +void evg_444_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y; + s32 i; + + for (i=0; isten, surf, spans[i].x, y, len); + col = surf->stencil_pix_run; + x = spans[i].x * surf->pitch_x; + while (len--) { + col_a = GF_COL_A(*col); + if (col_a) { + u32 _col = *col; + if ((spanalpha!=0xFF) || (col_a != 0xFF)) { + overmask_444(dst+x, _col, spanalpha); + } else { + write_444(dst+x, GF_COL_R(_col), GF_COL_G(_col), GF_COL_B(_col) ); + } + } + col++; + x += surf->pitch_x; + } + } +} + +GF_Err evg_surface_clear_444(GF_EVGSurface *surf, GF_IRect rc, GF_Color col) +{ + u32 x, y, w, h, sx, sy; + u8 r, g, b; + u8 *data_o; + + h = rc.height; + w = rc.width; + sx = rc.x; + sy = rc.y; + + r = GF_COL_R(col); + g = GF_COL_G(col); + b = GF_COL_B(col); + + data_o = NULL; + for (y=0; ypixels + (sy+y) * surf->pitch_y + surf->pitch_x*sx; + if (!y) { + data_o = data; + for (x=0; xpitch_x; + } + } else { + memcpy(data, data_o, w * surf->pitch_x); + } + } + return GF_OK; +} + diff --git a/src/evg/raster_argb.c b/src/evg/raster_argb.c new file mode 100644 index 0000000..162df11 --- /dev/null +++ b/src/evg/raster_argb.c @@ -0,0 +1,758 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / software 2D rasterizer + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + */ + +#include "rast_soft.h" + + +static GFINLINE s32 +mul255(s32 a, s32 b) +{ + return ((a+1) * b) >> 8; +} + + +/* + 32 bit ARGB/BGRA/RGBA +*/ + +u32 do_composite_mode(GF_EVGCompositeMode comp_mode, s32 *srca, s32 *dsta) +{ + switch (comp_mode) { + case GF_EVG_SRC_ATOP: + if (*srca && *dsta) {} + else if (*dsta) { *srca = 0; } + else { *dsta = *srca = 0; } + break; + case GF_EVG_SRC_IN: + if (*srca && *dsta) {} + else { *dsta = *srca = 0; } + break; + case GF_EVG_SRC_OUT: + if (*srca && !*dsta) {} + else { *dsta = *srca = 0; } + break; + case GF_EVG_DST_ATOP: + if (*srca && *dsta) { *srca = 0;} + else if (*srca) { *dsta = 0; } + else { *dsta = *srca = 0; } + break; + case GF_EVG_DST_IN: + if (*srca && *dsta) { *srca = 0; } + else { *dsta = *srca = 0; } + break; + case GF_EVG_DST_OUT: + if (!*srca && *dsta) {*srca = 0;} + else { *dsta = *srca = 0; } + break; + case GF_EVG_DST_OVER: + if (*dsta) {*srca = 0;} + else { *dsta = *srca = 0; } + break; + case GF_EVG_LIGHTER: + return 1; + case GF_EVG_COPY: + *dsta = 0; + break; + case GF_EVG_XOR: + return 2; + case GF_EVG_SRC_OVER: + return 0; + } + return 0; +} + +GFINLINE static void overmask_argb(u32 src, u8 *dst, u32 alpha, GF_EVGSurface *surf) +{ + u32 cmode; + s32 srca = GF_COL_A(src); + s32 srcr = GF_COL_R(src); + s32 srcg = GF_COL_G(src); + s32 srcb = GF_COL_B(src); + s32 dsta = dst[surf->idx_a]; + + srca = mul255(srca, alpha); + + cmode = do_composite_mode(surf->comp_mode, &srca, &dsta); + if (cmode==1) { + u8 dstr = dst[surf->idx_r]; + u8 dstg = dst[surf->idx_g]; + u8 dstb = dst[surf->idx_b]; +// dsta += srca; +// if (srca>0xFF) srca = 0xFF; + dstr += srcr; + if (dstr>0xFF) dstr = 0xFF; + dstg += srcg; + if (dstg>0xFF) dstg = 0xFF; + dstb += srcb; + if (dstb>0xFF) dstb = 0xFF; + dst[surf->idx_a] = (u8) srca; + dst[surf->idx_r] = (u8) dstr; + dst[surf->idx_g] = (u8) dstg; + dst[surf->idx_b] = (u8) dstb; + return; + } + if (cmode==2) { + u8 dstr = dst[surf->idx_r]; + u8 dstg = dst[surf->idx_g]; + u8 dstb = dst[surf->idx_b]; + dst[surf->idx_a] = (u8) srca; + dst[surf->idx_r] = (u8) (dstr^srcr); + dst[surf->idx_g] = (u8) (dstg^srcg); + dst[surf->idx_b] = (u8) (dstb^srcb); + return; + } + + /*special case for RGBA: + if dst alpha is 0, consider the surface is empty and copy pixel + if source alpha is 0xFF erase the entire pixel + */ + if (dsta && (srca!=0xFF) ) { + s32 final_a; + s32 dstr = dst[surf->idx_r]; + s32 dstg = dst[surf->idx_g]; + s32 dstb = dst[surf->idx_b]; + + //do the maths , so that the result of the blend follows the same DST = SRC*apha + DST(1-alpha) + //it gives a transform alpha of Fa = SRCa + DSTa - SRCa*DSTa + //and an RGB Fc = (SRCa*SRCc + DSTa*DSTc - DSTc*(DSTa-SRCa)) / Fa + final_a = dsta + srca - mul255(dsta, srca); + if (final_a) { + s32 res; + dst[surf->idx_a] = final_a; + res = (srcr*srca + dstr*(dsta-srca)) / final_a; + if (res<0) res=0; + dst[surf->idx_r] = (u8) (res); + res = (srcg*srca + dstg*(dsta-srca)) / final_a; + if (res<0) res=0; + dst[surf->idx_g] = (u8) (res); + res = (srcb*srca + dstb*(dsta-srca)) / final_a; + if (res<0) res=0; + dst[surf->idx_b] = (u8) (res); + } + } else { + dst[surf->idx_a] = (u8) srca; + dst[surf->idx_r] = (u8) srcr; + dst[surf->idx_g] = (u8) srcg; + dst[surf->idx_b] = (u8) srcb; + } +} + +GFINLINE static void overmask_argb_const_run(u32 src, u8 *dst, s32 dst_pitch_x, u32 count, GF_EVGSurface *surf) +{ + u8 const_srca = GF_COL_A(src); + s32 srcr = GF_COL_R(src); + s32 srcg = GF_COL_G(src); + s32 srcb = GF_COL_B(src); + + while (count) { + s32 srca = const_srca; + s32 dsta = dst[surf->idx_a]; + + do_composite_mode(surf->comp_mode, &srca, &dsta); + + /*special case for RGBA: + if dst alpha is 0, consider the surface is empty and copy pixel + if source alpha is 0xFF erase the entire pixel + */ + if ((dsta != 0) && (srca != 0xFF)) { + s32 final_a; + s32 dstr = dst[surf->idx_r]; + s32 dstg = dst[surf->idx_g]; + s32 dstb = dst[surf->idx_b]; + + //do the maths , so that the result of the blend follows the same DST = SRC*apha + DST(1-alpha) + //it gives a transform alpha of Fa = SRCa + DSTa - SRCa*DSTa + //and an RGB Fc = (SRCa*SRCc + DSTa*DSTc - DSTc*(DSTa-SRCa)) / Fa + final_a = dsta + srca - mul255(dsta, srca); + if (final_a) { + s32 res; + dst[surf->idx_a] = final_a; + res = (srcr*srca + dstr*(dsta-srca)) / final_a; + if (res<0) res=0; + dst[surf->idx_r] = (u8) (res); + res = (srcg*srca + dstg*(dsta-srca)) / final_a; + if (res<0) res=0; + dst[surf->idx_g] = (u8) (res); + res = (srcb*srca + dstb*(dsta-srca)) / final_a; + if (res<0) res=0; + dst[surf->idx_b] = (u8) (res); + } + } else { + dst[surf->idx_a] = (u8) srca; + dst[surf->idx_r] = (u8) srcr; + dst[surf->idx_g] = (u8) srcg; + dst[surf->idx_b] = (u8) srcb; + } + dst+=dst_pitch_x; + count--; + } +} + +void evg_argb_fill_single(s32 y, s32 x, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + overmask_argb(col, dst, 0xFF, surf); +} + +void evg_argb_fill_single_a(s32 y, s32 x, u8 coverage, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + overmask_argb(col, dst, coverage, surf); +} + + +void evg_argb_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u32 col = surf->fill_col; + u32 col_no_a; + u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; + s32 i; + + col_no_a = col & 0x00FFFFFF; + + for (i=0; ipitch_x; + len = spans[i].len; + + new_a = spans[i].coverage; + fin = (new_a<<24) | col_no_a; + //we must blend in all cases since we have to merge with the dst alpha + overmask_argb_const_run(fin, p, surf->pitch_x, len, surf); + } +} + +void evg_argb_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; + u32 a, fin, col_no_a; + s32 i; + + a = GF_COL_A(surf->fill_col); + col_no_a = surf->fill_col & 0x00FFFFFF; + if (surf->get_alpha) { + u32 j; + for (i=0; iget_alpha(surf->get_alpha_udta, a, x, y); + fin = mul255(aa, spans[i].coverage); + fin = (fin<<24) | col_no_a; + overmask_argb_const_run(fin, dst + x * surf->pitch_x, surf->pitch_x, 1, surf); + } + } + } else { + for (i=0; ipitch_x, surf->pitch_x, spans[i].len, surf); + } + } +} + +void evg_argb_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; + s32 i; + + for (i=0; ipitch_x; + len = spans[i].len; + spanalpha = spans[i].coverage; + evg_fill_run(surf->sten, surf, spans[i].x, y, len); + col = surf->stencil_pix_run; + while (len--) { + //we must blend in all cases since we have to merge with the dst alpha + overmask_argb(*col, p, spanalpha, surf); + col++; + p += surf->pitch_x; + } + } +} + + +void evg_argb_fill_erase(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; + s32 i; + + for (i=0; ipitch_x; + len = spans[i].len; + if (spans[i].coverage != 0xFF) { + } else { + while (len--) { + *(u32 *)p = 0; + p += surf->pitch_x; + } + } + } +} + +GF_Err evg_surface_clear_argb(GF_EVGSurface *surf, GF_IRect rc, GF_Color col) +{ + u8 *data, *o_data; + u8 a, r, g, b; + u32 x, y, w, h, sy; + s32 st; + GF_EVGSurface *_this = (GF_EVGSurface *)surf; + st = _this->pitch_y; + + h = rc.height; + w = rc.width; + sy = rc.y; + + if (sy+h > _this->height) { + h = _this->height - sy; + } + if (rc.x + w > _this->width) { + w = _this->width - rc.x; + } + + a = GF_COL_A(col); + r = GF_COL_R(col); + g = GF_COL_G(col); + b = GF_COL_B(col); + + o_data = NULL; + for (y = 0; y < h; y++) { + data = (u8 *) _this ->pixels + (sy+y)* st + _this->pitch_x * rc.x; + if (!y) { + o_data = data; + for (x = 0; x < w; x++) { + data[surf->idx_a] = a; + data[surf->idx_r] = r; + data[surf->idx_g] = g; + data[surf->idx_b] = b; + data += 4; + } + } else { + memcpy(data, o_data, w*4); + } + } + return GF_OK; +} + +/* + 32 bit RGBX/XRGB/BGRX/XBGR +*/ + +static void overmask_rgbx(u32 src, u8 *dst, u32 alpha, GF_EVGSurface *surf) +{ + s32 srca = (src >> 24) & 0xff; + s32 srcr = (src >> 16) & 0xff; + s32 srcg = (src >> 8) & 0xff; + s32 srcb = (src >> 0) & 0xff; + + s32 dstr = dst[surf->idx_r]; + s32 dstg = dst[surf->idx_g]; + s32 dstb = dst[surf->idx_b]; + + srca = mul255(srca, alpha); + dst[surf->idx_r] = mul255(srca, srcr - dstr) + dstr; + dst[surf->idx_g] = mul255(srca, srcg - dstg) + dstg; + dst[surf->idx_b] = mul255(srca, srcb - dstb) + dstb; +} + +GFINLINE static void overmask_rgbx_const_run(u32 src, u8 *dst, s32 dst_pitch_x, u32 count, GF_EVGSurface *surf) +{ + s32 srca = (src>>24) & 0xff; + u32 srcr = mul255(srca, ((src >> 16) & 0xff)) ; + u32 srcg = mul255(srca, ((src >> 8) & 0xff)) ; + u32 srcb = mul255(srca, ((src) & 0xff)) ; + while (count) { + u32 dstc; + dstc = dst[surf->idx_r]; + dst[surf->idx_r] = mul255(srca, srcr - dstc) + dstc; + dstc = dst[surf->idx_g]; + dst[surf->idx_g] = mul255(srca, srcg - dstc) + dstc; + dstc = dst[surf->idx_b]; + dst[surf->idx_b] = mul255(srca, srcb - dstc) + dstc; + dst += dst_pitch_x; + count--; + } +} + + +void evg_rgbx_fill_single(s32 y, s32 x, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + u8 r = GF_COL_R(col); + u8 g = GF_COL_G(col); + u8 b = GF_COL_B(col); + dst[surf->idx_r] = r; + dst[surf->idx_g] = g; + dst[surf->idx_b] = b; +} + +void evg_rgbx_fill_single_a(s32 y, s32 x, u8 coverage, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + overmask_rgbx(col, dst, coverage, surf); +} + +void evg_rgbx_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u32 col = surf->fill_col; + u32 fin, col_no_a, spana; + u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; + u8 r, g, b; + s32 i, x; + u32 len; + + col_no_a = col & 0x00FFFFFF; + r = GF_COL_R(col); + g = GF_COL_G(col); + b = GF_COL_B(col); + + for (i=0; ipitch_x; + len = spans[i].len; + + if (spana != 0xFF) { + fin = (spana<<24) | col_no_a; + overmask_rgbx_const_run(fin, dst + x, surf->pitch_x, len, surf); + } else { + while (len--) { + dst[x+surf->idx_r] = r; + dst[x+surf->idx_g] = g; + dst[x+surf->idx_b] = b; + x += surf->pitch_x; + } + } + } +} + +void evg_rgbx_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; + u32 col = surf->fill_col; + u32 a, fin, col_no_a; + s32 i; + + a = (col>>24)&0xFF; + col_no_a = col & 0x00FFFFFF; + if (surf->get_alpha) { + for (i=0; iget_alpha(surf->get_alpha_udta, a, x, y); + fin = mul255(aa, spans[i].coverage); + fin = (fin<<24) | col_no_a; + overmask_rgbx_const_run(fin, dst + surf->pitch_x * x, surf->pitch_x, 1, surf); + } + } + } else { + for (i=0; ipitch_x*spans[i].x, surf->pitch_x, spans[i].len, surf); + } + } +} + + +void evg_rgbx_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; + s32 i; + + for (i=0; isten, surf, spans[i].x, y, len); + col = surf->stencil_pix_run; + x = spans[i].x * surf->pitch_x; + + while (len--) { + _col = *col; + col_a = GF_COL_A(_col); + if (col_a) { + if ((spanalpha!=0xFF) || (col_a != 0xFF)) { + overmask_rgbx(*col, dst+x, spanalpha, surf); + } else { + dst[x+surf->idx_r] = GF_COL_R(_col); + dst[x+surf->idx_g] = GF_COL_G(_col); + dst[x+surf->idx_b] = GF_COL_B(_col); + } + } + col++; + x += surf->pitch_x; + } + } +} + +GF_Err evg_surface_clear_rgbx(GF_EVGSurface *surf, GF_IRect rc, GF_Color col) +{ + u32 x, y, w, h, sx, sy; + u8 r,g,b; + s32 st; + char *o_data; + GF_EVGSurface *_this = (GF_EVGSurface *)surf; + st = _this->pitch_x; + + h = rc.height; + w = rc.width; + sx = rc.x; + sy = rc.y; + + r = GF_COL_R(col); + g = GF_COL_G(col); + b = GF_COL_B(col); + + o_data = NULL; + for (y = 0; y < h; y++) { + u8 *data = (u8 *) _this ->pixels + (y + sy) * _this->pitch_y + st*sx; + if (!y) { + o_data = data; + for (x = 0; x < w; x++) { + data[surf->idx_r] = r; + data[surf->idx_g] = g; + data[surf->idx_b] = b; + data[surf->idx_a] = 0xFF; + data += st; + } + } else { + memcpy(data, o_data, w*4); + } + } + return GF_OK; +} + + + + +/* + alpha grey +*/ + +static void overmask_alphagrey(u32 src, u8 *dst, u32 alpha, u32 grey_type, u32 idx_g, u32 idx_a) +{ + s32 srca = (src >> 24) & 0xff; + s32 srcc; + s32 dsta = dst[idx_a]; + + if (grey_type==0) srcc = (src >> 16) & 0xff; + else if (grey_type==1) srcc = (src >> 8) & 0xff; + else srcc = (src >> 0) & 0xff; + srca = mul255(srca, alpha); + + if (dsta) { + s32 dstc = dst[idx_g]; + dst[idx_g] = mul255(srca, srcc - dstc) + dstc; + dst[idx_a] = mul255(srca, srca) + mul255(255-srca, dsta); + } else { + dst[idx_g] = srcc; + dst[idx_a] = srca; + } +} + +static void overmask_alphagrey_const_run(u32 src_a, u32 src_c, u8 *dst, s32 dst_pitch_x, u32 count, u32 idx_g, u32 idx_a) +{ + while (count) { + s32 dsta = dst[idx_a]; + /*special case : if dst alpha is 0, consider the surface is empty and copy pixel*/ + if (dsta) { + s32 dstc = dst[idx_g]; + dst[idx_g] = mul255(src_a, src_c - dstc) + dstc; + dst[idx_a] = mul255(src_a, src_a) + mul255(255-src_a, dsta); + } else { + dst[idx_g] = src_c; + dst[idx_a] = src_a; + } + dst += dst_pitch_x; + count--; + } +} + + +void evg_alphagrey_fill_single(s32 y, s32 x, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + u8 col_c; + + if (surf->grey_type==0) col_c = GF_COL_R(col); + else if (surf->grey_type==1) col_c = GF_COL_G(col); + else col_c = GF_COL_B(col); + dst[surf->idx_g] = col_c; +} + +void evg_alphagrey_fill_single_a(s32 y, s32 x, u8 coverage, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + overmask_alphagrey(col, dst, coverage, surf->grey_type, surf->idx_g, surf->idx_a); +} + +void evg_alphagrey_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u32 col = surf->fill_col; + u32 a; + u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; + s32 i, x; + u32 len; + u8 col_a, col_c; + + col_a = GF_COL_A(col); + if (surf->grey_type==0) col_c = GF_COL_R(col); + else if (surf->grey_type==1) col_c = GF_COL_G(col); + else col_c = GF_COL_B(col); + + for (i=0; ipitch_x; + len = spans[i].len; + + if (spans[i].coverage != 0xFF) { + a = mul255(0xFF, spans[i].coverage); + overmask_alphagrey_const_run(a, col_c, dst + x, surf->pitch_x, len, surf->idx_g, surf->idx_a); + } else { + while (len--) { + dst[x+surf->idx_g] = col_c; + dst[x+surf->idx_a] = col_a; + x += surf->pitch_x; + } + } + } +} + +void evg_alphagrey_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; + u32 col = surf->fill_col; + u32 a, fin, col_c; + s32 i; + + a = GF_COL_A(col); + if (surf->grey_type==0) col_c = GF_COL_R(col); + else if (surf->grey_type==1) col_c = GF_COL_G(col); + else col_c = GF_COL_B(col); + + if (surf->get_alpha) { + for (i=0; iget_alpha(surf->get_alpha_udta, a, x, y); + fin = mul255(aa, spans[i].coverage); + overmask_alphagrey_const_run(fin, col_c, dst + surf->pitch_x * x, surf->pitch_x, 1, surf->idx_g, surf->idx_a); + } + } + } else { + for (i=0; ipitch_x*spans[i].x, surf->pitch_x, spans[i].len, surf->idx_g, surf->idx_a); + } + } +} + + +void evg_alphagrey_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = (u8 *) surf->pixels + y * surf->pitch_y; + s32 i; + + for (i=0; isten, surf, spans[i].x, y, len); + x = spans[i].x * surf->pitch_x; + col = surf->stencil_pix_run; + while (len--) { + _col = *col; + col_a = GF_COL_A(_col); + if (col_a) { + if ((spanalpha!=0xFF) || (col_a != 0xFF)) { + overmask_alphagrey(*col, (dst + x) , spanalpha, surf->grey_type, surf->idx_g, surf->idx_a); + } else { + u8 dstc; + + if (surf->grey_type==0) dstc = GF_COL_R(_col); + else if (surf->grey_type==1) dstc = GF_COL_G(_col); + else dstc = GF_COL_B(_col); + + dst[x+surf->idx_g] = dstc; + dst[x+surf->idx_a] = col_a; + } + } + col++; + x += surf->pitch_x; + } + } +} + +GF_Err evg_surface_clear_alphagrey(GF_EVGSurface *surf, GF_IRect rc, GF_Color col) +{ + u8 *data, *data_o; + u8 col_a, col_c; + u32 x, y, w, h, sx, sy; + s32 st; + GF_EVGSurface *_this = (GF_EVGSurface *)surf; + st = _this->pitch_y; + + col_a = GF_COL_A(col); + if (surf->grey_type==0) col_c = GF_COL_R(col); + else if (surf->grey_type==1) col_c = GF_COL_G(col); + else col_c = GF_COL_B(col); + + h = rc.height; + w = rc.width; + sx = rc.x; + sy = rc.y; + + data_o = NULL; + + for (y = 0; y < h; y++) { + data = (u8 *) _this ->pixels + (sy+y)* st + _this->pitch_x*sx; + if (!y) { + data_o = data; + for (x = 0; x < w; x++) { + data[surf->idx_a] = col_c; + data[surf->idx_g] = col_a; + data += _this->pitch_x; + } + } else { + memcpy(data, data_o, w*_this->BPP); + } + } + return GF_OK; +} diff --git a/src/evg/raster_rgb.c b/src/evg/raster_rgb.c new file mode 100644 index 0000000..8b80016 --- /dev/null +++ b/src/evg/raster_rgb.c @@ -0,0 +1,389 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / software 2D rasterizer module + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + * + */ + +#include "rast_soft.h" + +static s32 +mul255(s32 a, s32 b) +{ + return ((a + 1) * b) >> 8; +} + + +/* + RGB part +*/ + +void overmask_rgb(u32 src, u8 *dst, u32 alpha, GF_EVGSurface *surf) +{ + s32 srca = (src >> 24) & 0xff; + s32 srcr = (src >> 16) & 0xff; + s32 srcg = (src >> 8) & 0xff; + s32 srcb = (src) & 0xff; + + s32 dstr = dst[surf->idx_r] & 0xFF; + s32 dstg = dst[surf->idx_g] & 0xFF; + s32 dstb = dst[surf->idx_b] & 0xFF; + + srca = mul255(srca, alpha); + dst[surf->idx_r] = mul255(srca, srcr - dstr) + dstr; + dst[surf->idx_g] = mul255(srca, srcg - dstg) + dstg; + dst[surf->idx_b] = mul255(srca, srcb - dstb) + dstb; +} + +static void overmask_rgb_const_run(u32 src, u8 *dst, s32 dst_pitch_x, u32 count, GF_EVGSurface *surf) +{ + s32 srca = (src >> 24) & 0xff; + s32 srcr = (src >> 16) & 0xff; + s32 srcg = (src >> 8) & 0xff; + s32 srcb = (src) & 0xff; + + while (count) { + s32 dstr = dst[surf->idx_r]; + s32 dstg = dst[surf->idx_g]; + s32 dstb = dst[surf->idx_b]; + dst[surf->idx_r] = (u8) mul255(srca, srcr - dstr) + dstr; + dst[surf->idx_g] = (u8) mul255(srca, srcg - dstg) + dstg; + dst[surf->idx_b] = (u8) mul255(srca, srcb - dstb) + dstb; + dst += dst_pitch_x; + count--; + } +} + +void evg_rgb_fill_single(s32 y, s32 x, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + u8 r = GF_COL_R(col); + u8 g = GF_COL_G(col); + u8 b = GF_COL_B(col); + dst[surf->idx_r] = r; + dst[surf->idx_g] = g; + dst[surf->idx_b] = b; +} + +void evg_rgb_fill_single_a(s32 y, s32 x, u8 coverage, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + overmask_rgb(col, dst, coverage, surf); +} + +void evg_rgb_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u32 col = surf->fill_col; + u8 *dst = surf->pixels + y * surf->pitch_y; + s32 i; + u32 col_no_a, r, g, b; + + r = GF_COL_R(col); + g = GF_COL_G(col); + b = GF_COL_B(col); + + col_no_a = col & 0x00FFFFFF; + for (i=0; ipitch_x; + + if (spans[i].coverage != 0xFF) { + a = mul255(0xFF, spans[i].coverage); + fin = (a<<24) | col_no_a; + overmask_rgb_const_run(fin, p, surf->pitch_x, len, surf); + } else { + while (len--) { + p[surf->idx_r] = r; + p[surf->idx_g] = g; + p[surf->idx_b] = b; + p += surf->pitch_x; + } + } + } +} + +void evg_rgb_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y; + u32 col = surf->fill_col; + u32 a, fin; + s32 i; + + a = (col>>24)&0xFF; + if (surf->get_alpha) { + for (i=0; iget_alpha(surf->get_alpha_udta, a, x, y); + fin = mul255(aa, spans[i].coverage); + fin = (fin<<24) | (col&0x00FFFFFF); + overmask_rgb_const_run(fin, dst + surf->pitch_x * x, surf->pitch_x, 1, surf); + } + } + } else { + for (i=0; ipitch_x * spans[i].x, surf->pitch_x, spans[i].len, surf); + } + } +} + + +void evg_rgb_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y; + s32 i; + + for (i=0; isten, surf, spans[i].x, y, len); + col = surf->stencil_pix_run; + x = surf->pitch_x * spans[i].x; + while (len--) { + col_a = GF_COL_A(*col); + if (col_a) { + if ((spanalpha!=0xFF) || (col_a != 0xFF)) { + overmask_rgb(*col, dst + x, spanalpha, surf); + } else { + dst[x + surf->idx_r] = GF_COL_R(*col); + dst[x + surf->idx_g] = GF_COL_G(*col); + dst[x + surf->idx_b] = GF_COL_B(*col); + } + } + col++; + x += surf->pitch_x; + } + } +} + +GF_Err evg_surface_clear_rgb(GF_EVGSurface *surf, GF_IRect rc, GF_Color col) +{ + u32 x, y, w, h, sx, sy; + s32 st; + u8 r, g, b; + u8 *o_data; + GF_EVGSurface *_this = (GF_EVGSurface *)surf; + st = _this->pitch_y; + + h = rc.height; + w = rc.width; + sx = rc.x; + sy = rc.y; + + r = GF_COL_R(col); + g = GF_COL_G(col); + b = GF_COL_B(col); + o_data = NULL; + for (y = 0; y < h; y++) { + u8 *data = _this ->pixels + (y + sy) * st + _this->pitch_x*sx; + if (!y) { + o_data = data; + for (x = 0; x < w; x++) { + data[surf->idx_r] = r; + data[surf->idx_g] = g; + data[surf->idx_b] = b; + data += _this->pitch_x; + } + } else { + memcpy(data, o_data, w*3); + } + } + return GF_OK; +} + + + + +/* + grey part +*/ + +static void overmask_grey(u32 src, char *dst, u32 alpha, u32 grey_type) +{ + s32 srca = (src >> 24) & 0xff; + u32 srcc; + s32 dstc = *dst; + + if (grey_type==0) srcc = (src >> 16) & 0xff; + else if (grey_type==1) srcc = (src >> 8) & 0xff; + else srcc = (src) & 0xff; + + + srca = mul255(srca, alpha); + *dst = mul255(srca, srcc - dstc) + dstc; +} + +static void overmask_grey_const_run(u8 srca, u8 srcc, char *dst, s32 dst_pitch_x, u32 count) +{ + while (count) { + u8 dstc = *(dst); + *dst = (u8) mul255(srca, srcc - dstc) + dstc; + dst += dst_pitch_x; + count--; + } +} + +void evg_grey_fill_single(s32 y, s32 x, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + u8 c; + + if (surf->grey_type==0) c = GF_COL_R(col); + else if (surf->grey_type==1) c = GF_COL_G(col); + else c = GF_COL_B(col); + *dst = c; +} + +void evg_grey_fill_single_a(s32 y, s32 x, u8 coverage, u32 col, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y + x * surf->pitch_x; + overmask_grey(col, dst, coverage, surf->grey_type); +} + +void evg_grey_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u32 col = surf->fill_col; + u32 c; + u8 *dst = surf->pixels + y * surf->pitch_y; + s32 i; + + if (surf->grey_type==0) c = GF_COL_R(col); + else if (surf->grey_type==1) c = GF_COL_G(col); + else c = GF_COL_B(col); + + for (i=0; ipitch_x; + + if (spans[i].coverage != 0xFF) { + a = mul255(0xFF, spans[i].coverage); + overmask_grey_const_run(a, c, p, surf->pitch_x, len); + } else { + while (len--) { + *(p) = c; + p += surf->pitch_x; + } + } + } +} + +void evg_grey_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y; + u32 a, fin; + s32 i; + u8 c; + + if (surf->grey_type==0) c = GF_COL_R(surf->fill_col); + else if (surf->grey_type==1) c = GF_COL_G(surf->fill_col); + else c = GF_COL_B(surf->fill_col); + + a = GF_COL_A(surf->fill_col); + if (surf->get_alpha) { + for (i=0; iget_alpha(surf->get_alpha_udta, a, x, y); + fin = mul255(aa, spans[i].coverage); + overmask_grey_const_run(fin, c, dst + surf->pitch_x * x, surf->pitch_x, 1); + } + } + } else { + for (i=0; ipitch_x * spans[i].x, surf->pitch_x, spans[i].len); + } + } +} + + +void evg_grey_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u8 *dst = surf->pixels + y * surf->pitch_y; + s32 i; + + for (i=0; isten, surf, spans[i].x, y, len); + col = surf->stencil_pix_run; + x = surf->pitch_x * spans[i].x; + while (len--) { + col_a = GF_COL_A(*col); + if (col_a) { + if ((spanalpha!=0xFF) || (col_a != 0xFF)) { + overmask_grey(*col, dst + x, spanalpha, surf->grey_type); + } else { + u8 c; + + if (surf->grey_type==0) c = GF_COL_R(*col); + else if (surf->grey_type==1) c = GF_COL_G(*col); + else c = GF_COL_B(*col); + + *(dst + x) = c; + } + } + col++; + x += surf->pitch_x; + } + } +} + +GF_Err evg_surface_clear_grey(GF_EVGSurface *surf, GF_IRect rc, GF_Color col) +{ + u32 y, w, h, sx, sy; + s32 st; + u8 r; + st = surf->pitch_y; + + h = rc.height; + w = rc.width; + sx = rc.x; + sy = rc.y; + + if (surf->grey_type==0) r = GF_COL_R(col); + else if (surf->grey_type==1) r = GF_COL_G(col); + else r = GF_COL_B(col); + + for (y = 0; y < h; y++) { + char *data = surf ->pixels + (y + sy) * st + surf->pitch_x*sx; + memset(data, r, w*surf->pitch_x); + } + return GF_OK; +} diff --git a/src/evg/raster_yuv.c b/src/evg/raster_yuv.c new file mode 100644 index 0000000..dba1636 --- /dev/null +++ b/src/evg/raster_yuv.c @@ -0,0 +1,2228 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2019 + * All rights reserved + * + * This file is part of GPAC / software 2D rasterizer module + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + * + */ + +#include "rast_soft.h" + +//TODO: support for color spaces, support for more than 10 bits and BigEndian format ? + +#if 1 + +#define mul255(_a, _b) ( (((u32)(_a) + 1) * (u32)(_b) ) >> 8) + +#else +static s32 +mul255(s32 a, s32 b) +{ + return ((a + 1) * b) >> 8; +} +#endif + +static s32 +mul255_zero(s32 a, s32 b) +{ + if (!a) return 0; + return ((a + 1) * b) >> 8; +} + +//RGB <-> YUV full range conversion, using integer (1024 factor) +#define YUV_USE_INT + +void gf_evg_rgb_to_yuv(GF_EVGSurface *surf, GF_Color col, u8*y, u8*cb, u8*cr) +{ + u32 r = GF_COL_R(col); + u32 g = GF_COL_G(col); + u32 b = GF_COL_B(col); + +#ifndef YUV_USE_INT + *y = (u8) (0.299*r + 0.587 * g + 0.114 * b); + *cb = (u8) (-0.169*(s32)r - 0.331*(s32)g + 0.499*b + 128); + *cr = (u8) (0.499 * r - 0.418*(s32)g - 0.0813*(s32)b + 128); +#else + u32 _v = 306*r + 601 * g + 117 * b; + *y = (u8) (_v >> 10); + _v = (-173*(s32)r - 339*(s32)g + 511*b + 131072); + *cb = (u8) (_v >> 10); + _v = (511 * r - 428*(s32)g - 83*(s32)b + 131072); + *cr = (u8) (_v >> 10); +#endif +} +GF_Color gf_evg_argb_to_ayuv(GF_EVGSurface *surf, GF_Color col) +{ + u8 a, y, cb, cr; + a = GF_COL_A(col); + u32 r = GF_COL_R(col); + u32 g = GF_COL_G(col); + u32 b = GF_COL_B(col); + +#ifndef YUV_USE_INT + y = (u8) (0.299*r + 0.587 * g + 0.114 * b); + cb = (u8) (-0.169*(s32)r - 0.331*(s32)g + 0.499*b + 128); + cr = (u8) (0.499 * r - 0.418*(s32)g - 0.0813*(s32)b + 128); +#else + u32 _v = 306*r + 601 * g + 117 * b; + y = (u8) (_v >> 10); + _v = (-173*(s32)r - 339*(s32)g + 511*b + 131072); + cb = (u8) (_v >> 10); + _v = (511 * r - 428*(s32)g - 83*(s32)b + 131072); + cr = (u8) (_v >> 10); +#endif + return GF_COL_ARGB(a, y, cb, cr); +} + +GF_Err gf_gf_evg_rgb_to_yuv_f(GF_EVGSurface *surf, Float r, Float g, Float b, Float *y, Float *cb, Float *cr) +{ + *y = (0.299f * r + 0.587f * g + 0.114f * b); + *cb = (-0.169f * (s32)r - 0.331f * (s32)g + 0.499f * b + 128.0f); + *cr = (0.499f * r - 0.418f * (s32)g - 0.0813f * (s32)b + 128.0f); + return GF_OK; +} +GF_Err gf_evg_yuv_to_rgb_f(GF_EVGSurface *surf, Float y, Float cb, Float cr, Float *r, Float *g, Float *b) +{ + *r = (y + 1.402f * (cr - 128.0f)); + *g = (y - 0.344136f * (cb - 128.0f) - 0.714136f * (cr-128.0f) ); + *b = (y + 1.772f * (cb - 128.0f) ); + return GF_OK; +} + +GF_Color gf_evg_ayuv_to_argb(GF_EVGSurface *surf, GF_Color col) +{ + u32 a; + s32 y, cb, cr; + s32 r, g, b; + a = GF_COL_A(col); + y = GF_COL_R(col); + cb = GF_COL_G(col); + cr = GF_COL_B(col); + +#ifndef YUV_USE_INT + r = (s32) (y + 1.402 * (cr - 128)); + g = (s32) (y - 0.344136 * (cb - 128) - 0.714136*(cr-128) ); + b = (s32) (y + 1.772 * (cb - 128) ); + +#define TRUNC_8BIT(_a) if (_a<0) {_a = 0;} else if (_a>255) {_a=255;} + TRUNC_8BIT(r) + TRUNC_8BIT(g) + TRUNC_8BIT(b) + +#else + y *= 1024; + r = (s32) (y + 1436 * (cr - 128)); + g = (s32) (y - 352 * (cb - 128) - 731*(cr-128) ); + b = (s32) (y + 1814 * (cb - 128) ); + +#define TRUNC_8BIT(_a) if (_a<0) {_a = 0;} else { u32 __a = (u32) _a; __a>>=10; if (__a>255) {__a=255;} _a = __a; } + TRUNC_8BIT(r) + TRUNC_8BIT(g) + TRUNC_8BIT(b) + +#endif + + + return GF_COL_ARGB(a, r, g, b); +} + +u64 gf_evg_argb_to_ayuv_wide(GF_EVGSurface *surf, u64 col) +{ + u16 a, y, cb, cr; + u32 r, g, b; + + a = (col>>48)&0xFFFF; + r = (col>>32)&0xFFFF; + g = (col>>16)&0xFFFF; + b = (col)&0xFFFF; + +#ifndef YUV_USE_INT + y = (u16) (0.299*r + 0.587 * g + 0.114 * b); + cb = (u16) (-0.169*(s32)r - 0.331*(s32)g + 0.499*b + 32768); + cr = (u16) (0.499 * r - 0.418*(s32)g - 0.0813*(s32)b + 32768); +#else + u32 _v = 306*r + 601 * g + 117 * b; + y = (u16) (_v >> 10); + _v = (-173*(s32)r - 339*(s32)g + 511*b + 33554432); + cb = (u16) (_v >> 10); + _v = (511 * r - 428*(s32)g - 83*(s32)b + 33554432); + cr = (u16) (_v >> 10); +#endif + + + return evg_make_col_wide(a, y, cb, cr); +} +u64 gf_evg_ayuv_to_argb_wide(GF_EVGSurface *surf, u64 col) +{ + u32 a; + s64 y, cb, cr; + s32 r, g, b; + a = (col>>48)&0xFFFF; + y = (col>>32)&0xFFFF; + cb = (col>>16)&0xFFFF; + cr = (col)&0xFFFF; + +#ifndef YUV_USE_INT + r = (s32) (y + 1.402 * (cr - 32768)); + g = (s32) (y - 0.344136 * (cb - 32768) - 0.714136*(cr-32768) ); + b = (s32) (y + 1.772 * (cb - 32768) ); +#define TRUNC_16BIT(_a) if (_a<0) {_a = 0;} else if (_a>32768) {_a=32768;} + TRUNC_16BIT(r) + TRUNC_16BIT(g) + TRUNC_16BIT(b) + +#else + y *= 1024; + r = (s32) (y + 1436 * (cr - 32768)); + g = (s32) (y - 352 * (cb - 32768) - 731*(cr-32768) ); + b = (s32) (y + 1814 * (cb - 32768) ); + +#define TRUNC_16BIT(_a) if (_a<0) {_a = 0;} else { u32 __a = (u32) _a; __a>>=10; if (__a>32768) {__a=32768;} _a = __a; } + TRUNC_16BIT(r) + TRUNC_16BIT(g) + TRUNC_16BIT(b) + +#endif + + return evg_make_col_wide(a, r, g, b); +} + +void evg_make_ayuv_color_mx(GF_ColorMatrix *cmat, GF_ColorMatrix *yuv_cmat) +{ + GF_ColorMatrix cmx_y2r, cmx_r2y; + gf_cmx_init(&cmx_r2y); + gf_cmx_init(&cmx_y2r); + cmx_r2y.identity = cmx_y2r.identity = GF_FALSE; + + //r = (s32) (y + 1.402 * (cr - 128)); + cmx_y2r.m[0] = FIX_ONE; //y + cmx_y2r.m[2] = FLT2FIX(1.402); //cr + cmx_y2r.m[4] = FLT2FIX(-1.402 * 0.504); //tr + + //g = (s32) (y - 0.344136 * (cb - 128) - 0.714136*(cr-128) ); + cmx_y2r.m[5] = FIX_ONE; //y + cmx_y2r.m[6] = FLT2FIX(- 0.344136); //cb + cmx_y2r.m[7] = FLT2FIX(- 0.714136); //cr + cmx_y2r.m[9] = FLT2FIX(0.344136 * 0.504 + 0.714136*0.504); //tr + + //b = (s32) (y + 1.772 * (cb - 128) ); + cmx_y2r.m[10] = FIX_ONE; //y + cmx_y2r.m[11] = FLT2FIX(1.772); //cb + cmx_y2r.m[14] = FLT2FIX(-1.772 * 0.504); //tr + + + //y = (u8) (0.299*r + 0.587 * g + 0.114 * b); + cmx_r2y.m[0] = FLT2FIX(0.299); //r + cmx_r2y.m[1] = FLT2FIX(0.587); //g + cmx_r2y.m[2] = FLT2FIX(0.114); //b + + + //cb = (u8) (-0.169*(s32)r - 0.331*(s32)g + 0.499*b + 128); + cmx_r2y.m[5] = FLT2FIX(-0.169); //r + cmx_r2y.m[6] = FLT2FIX(-0.331); //g + cmx_r2y.m[7] = FLT2FIX(0.499); //b + cmx_r2y.m[9] = FLT2FIX(0.504); //tr + + //cr = (u8) (0.499 * r - 0.418*(s32)g - 0.0813*(s32)b + 128); + cmx_r2y.m[10] = FLT2FIX(0.499); //r + cmx_r2y.m[11] = FLT2FIX(-0.418); //g + cmx_r2y.m[12] = FLT2FIX(-0.0813); //b + cmx_r2y.m[14] = FLT2FIX(0.504); //tr + + gf_cmx_copy(yuv_cmat, &cmx_r2y); + gf_cmx_multiply(yuv_cmat, cmat); + gf_cmx_multiply(yuv_cmat, &cmx_y2r); +} + + +/* + YUV420p part +*/ + +static void overmask_yuv420p(u8 col_a, u8 cy, char *dst, u32 alpha) +{ + s32 srca = col_a; + u32 srcc = cy; + s32 dstc = (*dst) & 0xFF; + + srca = mul255(srca, alpha); + *dst = mul255(srca, srcc - dstc) + dstc; +} + +static void overmask_yuv420p_const_run(u8 a, u8 val, u8 *ptr, u32 count, short x) +{ + while (count) { + u8 dst = *(ptr); + *ptr = (u8) mul255(a, val - dst) + dst; + ptr ++; + count--; + } +} +void evg_yuv420p_flush_uv_const(GF_EVGSurface *surf, u8 *surf_uv_alpha, s32 cu, s32 cv, s32 y) +{ + u32 i, a; + char *pU = surf->pixels + surf->height *surf->pitch_y; + char *pV; + pU += y/2 * surf->pitch_y/2; + pV = pU + surf->height/2 * surf->pitch_y/2; + + //we are at an odd line, write uv + for (i=0; iwidth; i+=2) { + u8 dst; + + //even line + a = surf->uv_alpha[i] + surf->uv_alpha[i+1]; + //odd line + a += surf_uv_alpha[i] + surf_uv_alpha[i+1]; + + if (a) { + char *s_ptr_u, *s_ptr_v; + + a /= 4; + + s_ptr_u = pU + i / 2; + s_ptr_v = pV + i / 2; + if (a==0xFF) { + *s_ptr_u = (u8) cu; + *s_ptr_v = (u8) cv; + } else { + dst = *(s_ptr_u); + *s_ptr_u = (u8) mul255(a, cu - dst) + dst; + + dst = *(s_ptr_v); + *s_ptr_v = (u8) mul255(a, cv - dst) + dst; + } + } + } + memset(surf->uv_alpha, 0, surf->uv_alpha_alloc); +} + +void evg_yuv420p_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + char *pY = surf->pixels; + u8 *surf_uv_alpha; + s32 i; + u8 cy, cu, cv; + Bool write_uv = (y%2) ? GF_TRUE : GF_FALSE; + + if (surf->is_422) { + write_uv = GF_TRUE; + surf_uv_alpha = surf->uv_alpha; + } + else if (write_uv) { + surf_uv_alpha = surf->uv_alpha + surf->width; + } else { + surf_uv_alpha = surf->uv_alpha; + } + + pY += y * surf->pitch_y; + + cy = GF_COL_R(surf->fill_col); + cu = GF_COL_G(surf->fill_col); + cv = GF_COL_B(surf->fill_col); + + for (i=0; iyuv_flush_uv(surf, surf_uv_alpha, cu, cv, y); + } + +} + +void evg_yuv420p_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u32 a; + char *pY = surf->pixels; + u8 *surf_uv_alpha; + s32 i; + u8 cy, cu, cv; + Bool write_uv = (y%2) ? GF_TRUE : GF_FALSE; + + if (surf->is_422) { + write_uv = GF_TRUE; + surf_uv_alpha = surf->uv_alpha; + } else if (write_uv) { + surf_uv_alpha = surf->uv_alpha + surf->width; + } else { + surf_uv_alpha = surf->uv_alpha; + } + + pY += y * surf->pitch_y; + + cy = GF_COL_R(surf->fill_col); + cu = GF_COL_G(surf->fill_col); + cv = GF_COL_B(surf->fill_col); + a = GF_COL_A(surf->fill_col); + + if (surf->get_alpha) { + for (i=0; iget_alpha(surf->get_alpha_udta, a, x, y); + fin = mul255(aa, spans[i].coverage); + + overmask_yuv420p_const_run((u8)fin, cy, s_pY, 1, 0); + + memset(surf_uv_alpha + x, (u8)fin, 1); + } + } + } else { + for (i=0; iyuv_flush_uv(surf, surf_uv_alpha, cu, cv, y); +} + + +void evg_yuv420p_flush_uv_var(GF_EVGSurface *surf, u8 *surf_uv_alpha, s32 _cu, s32 _cv, s32 y) +{ + u32 i; + char *pU, *pV; + pU = surf->pixels + surf->height *surf->pitch_y; + pU += y/2 * surf->pitch_y/2; + pV = pU + surf->height/2 * surf->pitch_y/2; + + for (i=0; iwidth; i+=2) { + u32 a, a11, a12, a21, a22; + u32 idx1=3*i; + u32 idx2=3*i + 3; + //get alpha + a11 = (u32)surf->uv_alpha[idx1]; + a12 = (u32)surf->uv_alpha[idx2]; + a21 = (u32)surf_uv_alpha[idx1]; + a22 = (u32)surf_uv_alpha[idx2]; + + a = a11 + a12 + a21 + a22; + if (a) { + u8 cdst=0; + u32 chroma_u, chroma_v, c11, c12, c21, c22; + + a /= 4; + //get cb + idx1 += 1; + idx2 += 1; + + if (a!=0xFF) { + cdst = *pU; + } + c11 = (u32)surf->uv_alpha[idx1]; + if (a11!=0xFF) c11 = mul255_zero(a11, c11 - cdst) + cdst; + c12 = (u32)surf->uv_alpha[idx2]; + if (a12!=0xFF) c12 = mul255_zero(a12, c12 - cdst) + cdst; + c21 = (u32)surf_uv_alpha[idx1]; + if (a21!=0xFF) c21 = mul255_zero(a21, c21 - cdst) + cdst; + c22 = (u32)surf_uv_alpha[idx2]; + if (a22!=0xFF) c22 = mul255_zero(a22, c22 - cdst) + cdst; + + chroma_u = c11 + c12 + c21 + c22; + chroma_u /= 4; + + //get cr + idx1 += 1; + idx2 += 1; + + if (a!=0xFF) { + cdst = *pV; + } + c11 = (u32)surf->uv_alpha[idx1]; + if (a11!=0xFF) c11 = mul255_zero(a11, c11 - cdst) + cdst; + c12 = (u32)surf->uv_alpha[idx2]; + if (a12!=0xFF) c12 = mul255_zero(a12, c12 - cdst) + cdst; + c21 = (u32)surf_uv_alpha[idx1]; + if (a21!=0xFF) c21 = mul255_zero(a21, c21 - cdst) + cdst; + c22 = (u32)surf_uv_alpha[idx2]; + if (a22!=0xFF) c22 = mul255_zero(a22, c22 - cdst) + cdst; + + chroma_v = c11 + c12 + c21 + c22; + chroma_v /= 4; + + *pU = chroma_u; + *pV = chroma_v; + } + pU++; + pV++; + } + //reset for next pass + memset(surf->uv_alpha, 0, surf->uv_alpha_alloc); + +} + +void evg_yuv420p_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + s32 i; + char *pY = surf->pixels; + u8 *surf_uv_alpha; + Bool write_uv = (y%2) ? GF_TRUE : GF_FALSE; + + if (surf->is_422) { + write_uv = GF_TRUE; + surf_uv_alpha = surf->uv_alpha; + } else if (write_uv) { + //second line of storage (we store alpha, cr, cb for each pixel) + surf_uv_alpha = surf->uv_alpha + 3*surf->width; + } else { + //first line of storage + surf_uv_alpha = surf->uv_alpha; + } + + pY += y * surf->pitch_y; + + for (i=0; isten, surf, spans[i].x, y, len); + p_col = surf->stencil_pix_run; + + s_pY = pY + spans[i].x; + x = spans[i].x; + + while (len--) { + u32 col = *p_col; + col_a = GF_COL_A(col); + if (col_a) { + u8 cy, cb, cr; + u32 idx=3*x; + //col is directly packed as AYCbCr + cy = GF_COL_R(col); + cb = GF_COL_G(col); + cr = GF_COL_B(col); + + if ((spanalpha!=0xFF) || (col_a != 0xFF)) { + overmask_yuv420p(col_a, cy, s_pY, spanalpha); + + u8 a = mul255(col_a, spanalpha); + surf_uv_alpha[idx] = a; + } else { + *s_pY = cy; + surf_uv_alpha[idx] = 0xFF; + } + surf_uv_alpha[idx+1] = cb; + surf_uv_alpha[idx+2] = cr; + } + s_pY++; + p_col++; + x++; + } + } + //compute final u,v for both lines + if (write_uv) { + surf->yuv_flush_uv(surf, surf_uv_alpha, 0, 0, y); + } +} + +GF_Err evg_surface_clear_yuv420p(GF_EVGSurface *_surf, GF_IRect rc, GF_Color col) +{ + s32 i; + u8 cy, cb, cr; + GF_EVGSurface *surf = (GF_EVGSurface *)_surf; + char *pY, *pU, *pV; + + pY = surf->pixels + rc.y * surf->pitch_y + rc.x; + pU = surf->pixels + surf->height * surf->pitch_y + rc.y/2 * surf->pitch_y/2 + rc.x/2; + pV = surf->pixels + 5*surf->height * surf->pitch_y/4 + rc.y/2 * surf->pitch_y/2 + rc.x/2; + + gf_evg_rgb_to_yuv(surf, col, &cy, &cb, &cr); + + for (i = 0; i < rc.height; i++) { + memset(pY, cy, rc.width); + pY += surf->pitch_y; + if (i%2) { + memset(pU, cb, rc.width/2); + pU += surf->pitch_y/2; + memset(pV, cr, rc.width/2); + pV += surf->pitch_y/2; + } + } + return GF_OK; +} + + +/* + NV12 / NV21 part +*/ + + +void evg_nv12_flush_uv_const(GF_EVGSurface *surf, u8 *surf_uv_alpha, s32 cu, s32 cv, s32 y) +{ + u32 i, a; + char *pU = surf->pixels + surf->height *surf->pitch_y; + pU += y/2 * surf->pitch_y; + + for (i=0; iwidth; i+=2) { + u8 dst; + + //even line + a = surf->uv_alpha[i] + surf->uv_alpha[i+1]; + //odd line + a += surf_uv_alpha[i] + surf_uv_alpha[i+1]; + + if (a) { + char *s_ptr; + a /= 4; + + s_ptr = pU + i; + if (a==0xFF) { + *s_ptr = cu; + + s_ptr++; + *s_ptr = cv; + } else { + dst = *(s_ptr); + *s_ptr = (u8) mul255(a, cu - dst) + dst; + + s_ptr++; + dst = *(s_ptr); + *s_ptr = (u8) mul255(a, cv - dst) + dst; + } + } + } + memset(surf->uv_alpha, 0, surf->uv_alpha_alloc); +} + +void evg_nv12_flush_uv_var(GF_EVGSurface *surf, u8 *surf_uv_alpha, s32 cu, s32 cv, s32 y) +{ + u32 i; + char *pU; + pU = surf->pixels + surf->height *surf->pitch_y; + pU += y/2 * surf->pitch_y; + + for (i=0; iwidth; i+=2) { + u32 a, a11, a12, a21, a22; + + u32 idx1=3*i; + u32 idx2=3*i + 3; + + //get alpha + a11 = (u32)surf->uv_alpha[idx1]; + a12 = (u32)surf->uv_alpha[idx2]; + a21 = (u32)surf_uv_alpha[idx1]; + a22 = (u32)surf_uv_alpha[idx2]; + a = a11+a12+a21+a22; + + if (a) { + u8 cdst=0; + u32 chroma_u, chroma_v, c11, c12, c21, c22; + + a /= 4; + + //get cb + idx1 += 1; + idx2 += 1; + if (a!=0xFF) + cdst = pU[surf->idx_u]; + + c11 = (u32)surf->uv_alpha[idx1]; + if (a11!=0xFF) c11 = mul255_zero(a11, c11 - cdst) + cdst; + c12 = (u32)surf->uv_alpha[idx2]; + if (a12!=0xFF) c12 = mul255_zero(a12, c12 - cdst) + cdst; + c21 = (u32)surf_uv_alpha[idx1]; + if (a21!=0xFF) c21 = mul255_zero(a21, c21 - cdst) + cdst; + c22 = (u32)surf_uv_alpha[idx2]; + if (a22!=0xFF) c22 = mul255_zero(a22, c22 - cdst) + cdst; + + chroma_u = c11 + c12 + c21 + c22; + chroma_u /= 4; + + //get cr + idx1 += 1; + idx2 += 1; + + if (a!=0xFF) + cdst = pU[surf->idx_v]; + + c11 = (u32)surf->uv_alpha[idx1]; + if (a11!=0xFF) c11 = mul255_zero(a11, c11 - cdst) + cdst; + c12 = (u32)surf->uv_alpha[idx2]; + if (a12!=0xFF) c12 = mul255_zero(a12, c12 - cdst) + cdst; + c21 = (u32)surf_uv_alpha[idx1]; + if (a21!=0xFF) c21 = mul255_zero(a21, c21 - cdst) + cdst; + c22 = (u32)surf_uv_alpha[idx2]; + if (a22!=0xFF) c22 = mul255_zero(a22, c22 - cdst) + cdst; + + chroma_v = c11 + c12 + c21 + c22; + chroma_v /= 4; + + pU[surf->idx_u] = chroma_u; + pU[surf->idx_v] = chroma_v; + } + pU+=2; + } + //reset for next pass + memset(surf->uv_alpha, 0, surf->uv_alpha_alloc); +} + + +GF_Err evg_surface_clear_nv12(GF_EVGSurface *_surf, GF_IRect rc, GF_Color col, Bool swap_uv) +{ + s32 i; + u8 cy, cb, cr; + GF_EVGSurface *surf = (GF_EVGSurface *)_surf; + char *pY = surf->pixels; + char *pU = surf->pixels + surf->height *surf->pitch_y; + char *pU_first; + + pY += rc.y * surf->pitch_y; + pU += rc.y/2 * surf->pitch_y; + + pY += rc.x; + pU += rc.x/2; + pU_first = pU; + + gf_evg_rgb_to_yuv(surf, col, &cy, &cb, &cr); + + if (swap_uv) { + u8 t = cb; + cb = cr; + cr = t; + } + for (i = 0; i < rc.height; i++) { + memset(pY, cy, rc.width); + pY += surf->pitch_y; + if (i%2) { + //first uv line, build it + if (i==1) { + s32 j; + for (j=0; jpitch_y; + } + } + } + return GF_OK; +} + + +/* + YUV422 part +*/ + +void evg_yuv422p_flush_uv_const(GF_EVGSurface *surf, u8 *surf_uv_alpha, s32 cu, s32 cv, s32 y) +{ + u32 i, a; + char *pU = surf->pixels + surf->height *surf->pitch_y; + char *pV; + pU += y * surf->pitch_y/2; + pV = pU + surf->height * surf->pitch_y/2; + + for (i=0; iwidth; i+=2) { + u8 dst; + + a = surf->uv_alpha[i] + surf->uv_alpha[i+1]; + + if (a) { + char *s_ptr_u, *s_ptr_v; + + a /= 2; + s_ptr_u = pU + i / 2; + s_ptr_v = pV + i / 2; + if (a==0xFF) { + *s_ptr_u = (u8) cu; + *s_ptr_v = (u8) cv; + } else { + dst = *(s_ptr_u); + *s_ptr_u = (u8) mul255(a, cu - dst) + dst; + + dst = *(s_ptr_v); + *s_ptr_v = (u8) mul255(a, cv - dst) + dst; + } + } + } + memset(surf->uv_alpha, 0, surf->uv_alpha_alloc); +} + +void evg_yuv422p_flush_uv_var(GF_EVGSurface *surf, u8 *surf_uv_alpha, s32 _cu, s32 _cv, s32 y) +{ + u32 i; + char *pU, *pV; + pU = surf->pixels + surf->height *surf->pitch_y; + pU += y * surf->pitch_y/2; + pV = pU + surf->height * surf->pitch_y/2; + + for (i=0; iwidth; i+=2) { + u32 a, a1, a2; + u32 idx1=3*i; + u32 idx2=3*i + 3; + //get alpha + a1 = (u32)surf->uv_alpha[idx1]; + a2 = (u32)surf->uv_alpha[idx2]; + a = a1+a2; + if (a) { + u8 cdst=0; + u32 chroma_u, chroma_v, c1, c2; + + a /= 2; + + //get cb + if (a != 0xFF) + cdst = *pU; + + c1 = (u32)surf->uv_alpha[idx1+1]; + if (a1!=0xFF) c1 = mul255_zero(a1, c1 - cdst) + cdst; + c2 = (u32)surf->uv_alpha[idx2+1]; + if (a2!=0xFF) c2 = mul255_zero(a2, c1 - cdst) + cdst; + chroma_u = c1 + c2; + chroma_u /= 2; + + //get cb + if (a != 0xFF) + cdst = *pV; + + c1 = (u32)surf->uv_alpha[idx1+2]; + if (a1!=0xFF) c1 = mul255_zero(a1, c1 - cdst) + cdst; + c2 = (u32)surf->uv_alpha[idx2+2]; + if (a2!=0xFF) c2 = mul255_zero(a2, c1 - cdst) + cdst; + chroma_v = c1 + c2; + chroma_v /= 2; + + + *pU = chroma_u; + *pV = chroma_v; + } + pU++; + pV++; + } + //reset for next pass + memset(surf->uv_alpha, 0, surf->uv_alpha_alloc); +} + + +GF_Err evg_surface_clear_yuv422p(GF_EVGSurface *_surf, GF_IRect rc, GF_Color col) +{ + s32 i; + u8 cy, cb, cr; + GF_EVGSurface *surf = (GF_EVGSurface *)_surf; + char *pY = surf->pixels; + char *pU = surf->pixels + surf->height *surf->pitch_y; + char *pV; + + pY += rc.y * surf->pitch_y; + pU += rc.y/2 * surf->pitch_y/2; + pV = pU + surf->height/2 * surf->pitch_y/2; + + pY += rc.x; + pU += rc.x/2; + pV += rc.x/2; + + gf_evg_rgb_to_yuv(surf, col, &cy, &cb, &cr); + + for (i = 0; i < rc.height; i++) { + memset(pY, cy, rc.width); + pY += surf->pitch_y; + memset(pU, cb, rc.width/2); + pU += surf->pitch_y/2; + memset(pV, cr, rc.width/2); + pV += surf->pitch_y/2; + } + return GF_OK; +} + +/* + YUV444 part +*/ + +void evg_yuv444p_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + char *pY, *pU, *pV; + s32 i; + u8 cy, cu, cv; + + pY = surf->pixels + y * surf->pitch_y; + pU = surf->pixels + surf->height*surf->pitch_y + y * surf->pitch_y; + pV = surf->pixels + 2*surf->height*surf->pitch_y + y * surf->pitch_y; + + cy = GF_COL_R(surf->fill_col); + cu = GF_COL_G(surf->fill_col); + cv = GF_COL_B(surf->fill_col); + + for (i=0; ipixels + y * surf->pitch_y; + pU = surf->pixels + surf->height*surf->pitch_y + y * surf->pitch_y; + pV = surf->pixels + 2*surf->height*surf->pitch_y + y * surf->pitch_y; + + cy = GF_COL_R(surf->fill_col); + cu = GF_COL_G(surf->fill_col); + cv = GF_COL_B(surf->fill_col); + a = GF_COL_A(surf->fill_col); + + if (surf->get_alpha) { + for (i=0; iget_alpha(surf->get_alpha_udta, a, x, y); + fin = mul255(aa, spans[i].coverage); + + overmask_yuv420p_const_run((u8)fin, cy, s_pY, 1, 0); + overmask_yuv420p_const_run((u8)fin, cu, s_pU, 1, 0); + overmask_yuv420p_const_run((u8)fin, cv, s_pV, 1, 0); + } + } + } else { + for (i=0; ipixels + y * surf->pitch_y; + pU = pY + surf->height*surf->pitch_y; + pV = pU + surf->height*surf->pitch_y; + + for (i=0; isten, surf, spans[i].x, y, len); + p_col = surf->stencil_pix_run; + + s_pY = pY + spans[i].x; + s_pU = pU + spans[i].x; + s_pV = pV + spans[i].x; + + while (len--) { + u32 col = *p_col; + col_a = GF_COL_A(col); + if (col_a) { + u8 cy, cb, cr; + cy = GF_COL_R(col); + cb = GF_COL_G(col); + cr = GF_COL_B(col); + + if ((spanalpha!=0xFF) || (col_a != 0xFF)) { + overmask_yuv420p(col_a, cy, s_pY, spanalpha); + overmask_yuv420p(col_a, cb, s_pU, spanalpha); + overmask_yuv420p(col_a, cr, s_pV, spanalpha); + } else { + *s_pY = cy; + *s_pU = cb; + *s_pV = cr; + } + } + s_pY++; + s_pU++; + s_pV++; + p_col++; + } + } +} + +GF_Err evg_surface_clear_yuv444p(GF_EVGSurface *_surf, GF_IRect rc, GF_Color col) +{ + s32 i; + u8 cy, cb, cr; + GF_EVGSurface *surf = (GF_EVGSurface *)_surf; + char *pY = surf->pixels; + char *pU = surf->pixels + surf->height *surf->pitch_y; + char *pV; + + pY += rc.y * surf->pitch_y; + pU += rc.y * surf->pitch_y; + pV = pU + surf->height * surf->pitch_y; + + pY += rc.x; + pU += rc.x; + pV += rc.x; + + gf_evg_rgb_to_yuv(surf, col, &cy, &cb, &cr); + + for (i = 0; i < rc.height; i++) { + memset(pY, cy, rc.width); + pY += surf->pitch_y; + memset(pU, cb, rc.width); + pU += surf->pitch_y; + memset(pV, cr, rc.width); + pV += surf->pitch_y; + } + return GF_OK; +} + + +/* + YUYV part +*/ + +static void overmask_yuvy(char *dst, u8 c, u32 alpha) +{ + s32 srca = alpha; + u32 srcc = c; + s32 dstc = (*dst) & 0xFF; + *dst = mul255(srca, srcc - dstc) + dstc; +} +void evg_yuyv_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + char *pY; + s32 i; + u8 cy, cu, cv; + + if (!count) return; + + pY = surf->pixels + y * surf->pitch_y; + cy = GF_COL_R(surf->fill_col); + cu = GF_COL_G(surf->fill_col); + cv = GF_COL_B(surf->fill_col); + + for (i=0; iidx_y1; + //if odd pixel move to next one + if (spans[i].x%2) s_pY += 2; + + if (spans[i].coverage != 0xFF) { + memset(surf->uv_alpha + spans[i].x, spans[i].coverage, len); + while (len--) { + overmask_yuvy(s_pY, cy, spans[i].coverage); + s_pY += 2; + } + } else { + memset(surf->uv_alpha + spans[i].x, 0xFF, len); + + while (len--) { + *s_pY = cy; + s_pY += 2; + } + } + } + + for (i=0; i<(s32) surf->width; i+=2) { + u32 a = (u32)surf->uv_alpha[i]; + a += (u32)surf->uv_alpha[i + 1]; + if (a) { + a /=2; + if (a==0xFF) { + pY[surf->idx_u] = cu; + pY[surf->idx_v] = cv; + } else if (a) { + overmask_yuvy(pY + surf->idx_u, cu, a); + overmask_yuvy(pY + surf->idx_v, cv, a); + } + } + pY+=4; + } + memset(surf->uv_alpha, 0, surf->uv_alpha_alloc); +} + +void evg_yuyv_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + char *pY; + s32 i; + u8 cy, cu, cv, a; + + pY = surf->pixels + y * surf->pitch_y; + cy = GF_COL_R(surf->fill_col); + cu = GF_COL_G(surf->fill_col); + cv = GF_COL_B(surf->fill_col); + a = GF_COL_A(surf->fill_col); + + for (i=0; iuv_alpha + spans[i].x, (u8)fin, len); + while (len--) { + overmask_yuvy(s_pY + surf->idx_y1, cy, fin); + s_pY += 2; + } + } + pY = surf->pixels + y * surf->pitch_y; + for (i=0; i<(s32) surf->width; i+=2) { + u32 p_a = surf->uv_alpha[i]; + p_a += surf->uv_alpha[i + 1]; + p_a /=2; + if (p_a==0xFF) { + pY[surf->idx_u] = cu; + pY[surf->idx_v] = cv; + } else if (p_a) { + overmask_yuvy(pY + surf->idx_u, cu, p_a); + overmask_yuvy(pY + surf->idx_v, cv, p_a); + } + pY+=4; + } + memset(surf->uv_alpha, 0, surf->uv_alpha_alloc); + +} + +void evg_yuyv_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + s32 i; + char *pY; + + pY = surf->pixels + y * surf->pitch_y; + + for (i=0; isten, surf, spans[i].x, y, len); + p_col = surf->stencil_pix_run; + + x = spans[i].x; + + while (len--) { + u32 col = *p_col; + col_a = GF_COL_A(col); + if (col_a) { + u32 idx=3*x; + u8 cy, cb, cr; + cy = GF_COL_R(col); + cb = GF_COL_G(col); + cr = GF_COL_B(col); + + if ((spanalpha!=0xFF) || (col_a != 0xFF)) { + overmask_yuv420p(col_a, cy, s_pY + surf->idx_y1, spanalpha); + + u8 a = mul255(col_a, spanalpha); + surf->uv_alpha[idx] = a; + } else { + s_pY[surf->idx_y1] = cy; + surf->uv_alpha[idx] = 0xFF; + } + surf->uv_alpha[idx+1] = cb; + surf->uv_alpha[idx+2] = cr; + } + s_pY+=2; + p_col++; + x++; + } + } + pY = surf->pixels + y * surf->pitch_y; + for (i=0; i<(s32)surf->width; i+=2) { + u32 a; + u32 idx1=3*i; + u32 idx2=3*i + 3; + //get alpha + a = (u32)surf->uv_alpha[idx1] + (u32)surf->uv_alpha[idx2]; + if (a) { + u32 chroma; + + a /= 2; + + //get cb + chroma = (u32)surf->uv_alpha[idx1+1] + (u32)surf->uv_alpha[idx2+1]; + chroma /= 2; + if (a==0xFF) { + pY[surf->idx_u] = chroma; + } else { + overmask_yuvy(pY + surf->idx_u, chroma, a); + } + //get cr + chroma = (u32)surf->uv_alpha[idx1+2] + (u32)surf->uv_alpha[idx2+2]; + chroma /= 2; + if (a==0xFF) { + pY[surf->idx_v] = chroma; + } else { + overmask_yuvy(pY + surf->idx_v, chroma, a); + } + } + pY+=4; + } + + memset(surf->uv_alpha, 0, surf->uv_alpha_alloc); + +} + +GF_Err evg_surface_clear_yuyv(GF_EVGSurface *_surf, GF_IRect rc, GF_Color col) +{ + u32 i; + s32 j; + u8 cy, cb, cr; + GF_EVGSurface *surf = (GF_EVGSurface *)_surf; + char *o_pY; + char *pY = surf->pixels; + pY += rc.y * surf->pitch_y; + pY += (rc.x/2) * 4; + + gf_evg_rgb_to_yuv(surf, col, &cy, &cb, &cr); + o_pY = pY; + for (i = 0; i <(u32)rc.height; i++) { + if (!i) { + char *dst = pY; + for (j = 0; j < rc.width/2; j++) { + dst[surf->idx_y1] = cy; + dst[surf->idx_u] = cb; + dst[surf->idx_y1 + 2] = cy; + dst[surf->idx_v] = cr; + dst += 4; + } + } else { + memcpy(pY, o_pY, rc.width*2); + } + pY += surf->pitch_y; + } + return GF_OK; +} + + +#ifdef GPAC_BIG_ENDIAN + +#define set_u16_le(_ptr, val) { ((u8 *)_ptr)[0] = (val>>8)&0xFF; ((u8 *)_ptr)[1] = (val&0xFF); } +#define set_u16_be(_ptr, val) { *(u16 *) _ptr = (u16) val; } + +#define get_u16_le(val, _ptr) { val = ((u32) (*(u8 *) _ptr+1)<< 8) | *(u8 *) _ptr; } +#define get_u16_be(val, _ptr) { val = *(u16 *) (_ptr); } + +#else + +#define set_u16_le(_ptr, val) { (*(u16 *) _ptr) = (u16) val; } +#define set_u16_be(_ptr, val) { ((u8 *)_ptr)[0] = (val>>8)&0xFF; ((u8 *)_ptr)[1] = (val&0xFF); } + +#define get_u16_le(val, _ptr) { val = *(u16 *) (_ptr); } +#define get_u16_be(val, _ptr) { val = ((u32) (*(u8 *) _ptr)<< 8) | *(u8 *) _ptr+1; } + +#endif + + +/* + YUV420p-10 part +*/ +static s32 +mul_10(s64 a, s64 b) +{ + return (s32) ( ((a + 1) * b) >> 16); +} + +static s32 +mul_10_zero(s64 a, s64 b) +{ + if (!a) return 0; + return (s32) ( ((a + 1) * b) >> 16); +} + +static void overmask_yuv420p_10(u16 col_a, u16 cy, u16 *dst, u32 alpha) +{ + s32 srca = col_a; + u32 srcc = cy; + s32 dstc; + get_u16_le(dstc, dst); + + srca = mul_10(srca, alpha); + set_u16_le(dst, (mul_10(srca, srcc - dstc) + dstc) ); +} + +static void overmask_yuv420p_10_const_run(u16 a, u16 val, u16 *ptr, u32 count, short x) +{ + while (count) { + u16 dst; + get_u16_le(dst, ptr); + set_u16_le(ptr, (u16) (mul_10(a, val - dst) + dst)); + ptr ++; + count--; + } +} + +void evg_yuv420p_10_flush_uv_const(GF_EVGSurface *surf, u8 *_surf_uv_alpha, s32 cu, s32 cv, s32 y) +{ + u32 i, a; + u16 *surf_uv_alpha_even = (u16 *) surf->uv_alpha; + u16 *surf_uv_alpha_odd = (u16 *) _surf_uv_alpha; + char *pU = surf->pixels + surf->height *surf->pitch_y; + char *pV; + pU += y/2 * surf->pitch_y/2; + pV = pU + surf->height/2 * surf->pitch_y/2; + + //we are at an odd line, write uv + for (i=0; iwidth; i+=2) { + u16 dst; + + //even line + a = surf_uv_alpha_even[i] + surf_uv_alpha_even[i+1]; + //odd line + a += surf_uv_alpha_odd[i] + surf_uv_alpha_odd[i+1]; + + if (a) { + u16 *s_ptr_u, *s_ptr_v; + + a /= 4; + + s_ptr_u = ((u16 *) pU) + i/2; + s_ptr_v = ((u16 *) pV) + i/2; + if (a==0xFFFF) { + set_u16_le(s_ptr_u, cu); + set_u16_le(s_ptr_v, cv); + } else { + get_u16_le(dst, s_ptr_u); + set_u16_le(s_ptr_u, (u16) (mul_10(a, cu - dst) + dst) ); + get_u16_le(dst, s_ptr_v); + set_u16_le(s_ptr_v, (u16) (mul_10(a, cv - dst) + dst) ); + } + } + } + memset(surf->uv_alpha, 0, surf->uv_alpha_alloc); +} + +void evg_yuv420p_10_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u16 *pY; + u16 *surf_uv_alpha; + s32 i; + u16 cy, cu, cv; + Bool write_uv = (y%2) ? GF_TRUE : GF_FALSE; + + if (surf->is_422) { + write_uv = GF_TRUE; + surf_uv_alpha = (u16 *)surf->uv_alpha; + } + else if (write_uv) { + surf_uv_alpha = ((u16 *)surf->uv_alpha) + surf->width; + } else { + surf_uv_alpha = (u16 *)surf->uv_alpha; + } + + pY = (u16 *) (surf->pixels + y * surf->pitch_y); + + cy = (surf->fill_col_wide>>32) & 0xFFFF; + cy >>=6; + cu = (surf->fill_col_wide>>16) & 0xFFFF; + cu >>=6; + cv = (surf->fill_col_wide) & 0xFFFF; + cv >>=6; + + for (i=0; iyuv_flush_uv(surf, (u8 *)surf_uv_alpha, cu, cv, y); + } + +} + +void evg_yuv420p_10_fill_const_a(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u32 a; + u16 *pY; + u16 *surf_uv_alpha; + s32 i; + u16 cy, cu, cv; + Bool write_uv = (y%2) ? GF_TRUE : GF_FALSE; + + if (surf->is_422) { + write_uv = GF_TRUE; + surf_uv_alpha = (u16 *)surf->uv_alpha; + } else if (write_uv) { + surf_uv_alpha = (u16 *)surf->uv_alpha + surf->width; + } else { + surf_uv_alpha = (u16 *)surf->uv_alpha; + } + + pY = (u16 *) (surf->pixels + y * surf->pitch_y); + + + a = (surf->fill_col_wide>>48) & 0xFFFF; + cy = (surf->fill_col_wide>>32) & 0xFFFF; + cy >>=6; + cu = (surf->fill_col_wide>>16) & 0xFFFF; + cu >>=6; + cv = (surf->fill_col_wide) & 0xFFFF; + cv >>=6; + + if (surf->get_alpha) { + for (i=0; iget_alpha(surf->get_alpha_udta, a, x, y) * spans[i].coverage; + fin /= 0xFF; + + overmask_yuv420p_10_const_run(fin, cy, s_pY, 1, 0); + surf_uv_alpha[x] = fin; + } + } + } else { + for (i=0; iyuv_flush_uv(surf, (u8*)surf_uv_alpha, cu, cv, y); +} + +void evg_yuv420p_10_flush_uv_var(GF_EVGSurface *surf, u8 *_surf_uv_alpha, s32 _cu, s32 _cv, s32 y) +{ + u32 i; + u16 *surf_uv_alpha_even = (u16 *) surf->uv_alpha; + u16 *surf_uv_alpha_odd = (u16 *) _surf_uv_alpha; + u16 *pU, *pV; + pU = (u16 *) (surf->pixels + surf->height *surf->pitch_y + y/2 * surf->pitch_y/2); + pV = (u16 *) (surf->pixels + 5*surf->height *surf->pitch_y/4 + y/2 * surf->pitch_y/2); + + for (i=0; iwidth; i+=2) { + u32 a, a11, a12, a21, a22; + u32 idx1=3*i; + u32 idx2=3*i + 3; + + //get alpha + a11 = (u32)surf_uv_alpha_even[idx1]; + a12 = (u32)surf_uv_alpha_even[idx2]; + a21 = (u32)surf_uv_alpha_odd[idx1]; + a22 = (u32)surf_uv_alpha_odd[idx2]; + + a = a11+a12+a21+a22; + if (a) { + s32 cdst=0; + s32 chroma_u, chroma_v, c11, c12, c21, c22; + + a /= 4; + + //get cb + idx1 += 1; + idx2 += 1; + + if (a!=0xFFFF) { + get_u16_le(cdst, pU); + } + c11 = (u32)surf_uv_alpha_even[idx1]; + if (a11!=0xFFFF) c11 = mul_10_zero(a11, c11 - cdst) + cdst; + c12 = (u32)surf_uv_alpha_even[idx2]; + if (a12!=0xFFFF) c12 = mul_10_zero(a12, c12 - cdst) + cdst; + c21 = (u32)surf_uv_alpha_odd[idx1]; + if (a21!=0xFFFF) c21 = mul_10_zero(a21, c21 - cdst) + cdst; + c22 = (u32)surf_uv_alpha_odd[idx2]; + if (a22!=0xFFFF) c22 = mul_10_zero(a22, c22 - cdst) + cdst; + + chroma_u = c11 + c12 + c21 + c22; + chroma_u /= 4; + set_u16_le(pU, (u16) chroma_u); + + //get cr + idx1 += 1; + idx2 += 1; + + if (a!=0xFFFF) { + get_u16_le(cdst, pV); + } + c11 = (u32)surf_uv_alpha_even[idx1]; + if (a11!=0xFFFF) c11 = mul_10_zero(a11, c11 - cdst) + cdst; + c12 = (u32)surf_uv_alpha_even[idx2]; + if (a12!=0xFFFF) c12 = mul_10_zero(a12, c12 - cdst) + cdst; + c21 = (u32)surf_uv_alpha_odd[idx1]; + if (a21!=0xFFFF) c21 = mul_10_zero(a21, c21 - cdst) + cdst; + c22 = (u32)surf_uv_alpha_odd[idx2]; + if (a22!=0xFFFF) c22 = mul_10_zero(a22, c22 - cdst) + cdst; + + chroma_v = c11 + c12 + c21 + c22; + chroma_v /= 4; + set_u16_le(pV, (u16) chroma_v); + } + pU++; + pV++; + } + //reset for next pass + memset(surf->uv_alpha, 0, surf->uv_alpha_alloc); + +} + +void evg_yuv420p_10_fill_var(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + s32 i; + u16 *pY; + u16 *surf_uv_alpha; + Bool write_uv = (y%2) ? GF_TRUE : GF_FALSE; + + if (surf->is_422) { + write_uv = GF_TRUE; + surf_uv_alpha = (u16 *) surf->uv_alpha; + } else if (write_uv) { + //second line of storage (we store alpha, cr, cb for each pixel) + surf_uv_alpha = ((u16 *) surf->uv_alpha) + 3*surf->width; + } else { + //first line of storage + surf_uv_alpha = (u16 *) surf->uv_alpha; + } + + pY = (u16 *) (surf->pixels + y * surf->pitch_y); + + for (i=0; isten, surf, spans[i].x, y, len); + p_col = surf->stencil_pix_run; + + s_pY = pY + spans[i].x; + x = spans[i].x; + + while (len--) { + u64 col = *p_col; + u32 col_a = (col>>48)&0xFFFF; + if (col_a) { + u16 cy, cb, cr; + u32 idx=3*x; + //col is directly packed as AYCbCr + cy = ((col>>32) & 0xFFFF) >> 6; + cb = ((col>>16) & 0xFFFF) >> 6; + cr = ((col) & 0xFFFF) >> 6; + + if ((spanalpha!=0xFF) || (col_a != 0xFFFF)) { + u16 spana = spanalpha; + spana <<= 8; + overmask_yuv420p_10(col_a, cy, s_pY, spana); + surf_uv_alpha[idx] = mul_10(col_a, spana); + } else { + set_u16_le(s_pY, cy); + surf_uv_alpha[idx] = 0xFFFF; + } + surf_uv_alpha[idx+1] = cb; + surf_uv_alpha[idx+2] = cr; + } + s_pY++; + p_col++; + x++; + } + } + //compute final u,v for both lines + if (write_uv) { + surf->yuv_flush_uv(surf, (u8 *)surf_uv_alpha, 0, 0, y); + } +} + +GF_Err evg_surface_clear_yuv420p_10(GF_EVGSurface *_surf, GF_IRect rc, GF_Color col) +{ + s32 i, j; + u8 _cy, _cb, _cr; + u16 cy, cb, cr; + GF_EVGSurface *surf = (GF_EVGSurface *)_surf; + u16 *pY, *pU, *pV, *o_pY, *o_pU, *o_pV; + + pY = (u16 *) (surf->pixels + rc.y * surf->pitch_y + 2*rc.x); + pU = (u16 *) (surf->pixels + surf->height * surf->pitch_y + rc.y/2 * surf->pitch_y/2 + rc.x); + pV = (u16 *) (surf->pixels + 5*surf->height * surf->pitch_y/4 + rc.y/2 * surf->pitch_y/2 + rc.x); + + gf_evg_rgb_to_yuv(surf, col, &_cy, &_cb, &_cr); + cy = ((u16)_cy) << 2; + cb = ((u16)_cb) << 2; + cr = ((u16)_cr) << 2; + o_pY = pY; + o_pU = pU; + o_pV = pV; + for (i = 0; i pitch_y/2; + + if (i%2) continue; + + if (!i) { + for (j=0; jpitch_y/4; + pV += surf->pitch_y/4; + } + return GF_OK; +} + +/* + NV12 / NV21 10 bit part +*/ + + +void evg_nv12_10_flush_uv_const(GF_EVGSurface *surf, u8 *_surf_uv_alpha, s32 cu, s32 cv, s32 y) +{ + u32 i, a; + u16 *surf_uv_alpha_even = (u16 *) surf->uv_alpha; + u16 *surf_uv_alpha_odd = (u16 *)_surf_uv_alpha; + u8 *pUV = surf->pixels + surf->height * surf->pitch_y + y/2 * surf->pitch_y; + u8 *pU = pUV + 2*surf->idx_u; + u8 *pV = pUV + 2*surf->idx_v; + + for (i=0; iwidth; i+=2) { + //even line + a = surf_uv_alpha_even[i] + surf_uv_alpha_even[i+1]; + //odd line + a += surf_uv_alpha_odd[i] + surf_uv_alpha_odd[i+1]; + + if (a) { + s32 dst; + a /= 4; + + if (a==0xFFFF) { + set_u16_le(pU, cu); + set_u16_le(pV, cv); + } else { + get_u16_le(dst, pU); + set_u16_le(pU, (u16) (mul_10(a, cu - dst) + dst) ); + get_u16_le(dst, pV); + set_u16_le(pV, (u16) (mul_10(a, cv - dst) + dst) ); + } + } + pU += 4; + pV += 4; + } + memset(surf->uv_alpha, 0, surf->uv_alpha_alloc); +} + + +void evg_nv12_10_flush_uv_var(GF_EVGSurface *surf, u8 *_surf_uv_alpha, s32 cu, s32 cv, s32 y) +{ + u32 i; + u16 *surf_uv_alpha_even = (u16 *) surf->uv_alpha; + u16 *surf_uv_alpha_odd = (u16 *) _surf_uv_alpha; + u8 *pUV = surf->pixels + surf->height *surf->pitch_y + y/2 * surf->pitch_y; + u8 *pU = pUV + 2*surf->idx_u; + u8 *pV = pUV + 2*surf->idx_v; + + for (i=0; iwidth; i+=2) { + u32 a, a11, a12, a21, a22; + u32 idx1=3*i; + u32 idx2=3*i + 3; + //get alpha + a11 = (u32)surf_uv_alpha_even[idx1]; + a12 = (u32)surf_uv_alpha_even[idx2]; + a21 = (u32)surf_uv_alpha_odd[idx1]; + a22 = (u32)surf_uv_alpha_odd[idx2]; + a = a11+a12+a21+a22; + + if (a) { + s32 cdst=0, chroma_u, chroma_v, c11, c12, c21, c22; + + a /= 4; + + //get cb + idx1 += 1; + idx2 += 1; + if (a!=0xFFFF) + get_u16_le(cdst, pU); + + c11 = (u32)surf_uv_alpha_even[idx1]; + if (a11!=0xFFFF) c11 = mul_10_zero(a11, c11 - cdst) + cdst; + c12 = (u32)surf_uv_alpha_even[idx2]; + if (a12!=0xFFFF) c12 = mul_10_zero(a12, c12 - cdst) + cdst; + c21 = (u32)surf_uv_alpha_odd[idx1]; + if (a21!=0xFFFF) c21 = mul_10_zero(a21, c21 - cdst) + cdst; + c22 = (u32)surf_uv_alpha_odd[idx2]; + if (a22!=0xFFFF) c22 = mul_10_zero(a22, c22 - cdst) + cdst; + + chroma_u = c11+c12+c21+c22; + chroma_u /= 4; + + //get cr + idx1 += 1; + idx2 += 1; + if (a!=0xFFFF) + get_u16_le(cdst, pV); + + c11 = (u32)surf_uv_alpha_even[idx1]; + if (a11!=0xFFFF) c11 = mul_10_zero(a11, c11 - cdst) + cdst; + c12 = (u32)surf_uv_alpha_even[idx2]; + if (a12!=0xFFFF) c12 = mul_10_zero(a12, c12 - cdst) + cdst; + c21 = (u32)surf_uv_alpha_odd[idx1]; + if (a21!=0xFFFF) c21 = mul_10_zero(a21, c21 - cdst) + cdst; + c22 = (u32)surf_uv_alpha_odd[idx2]; + if (a22!=0xFFFF) c22 = mul_10_zero(a22, c22 - cdst) + cdst; + + chroma_v = c11+c12+c21+c22; + chroma_v /= 4; + + + set_u16_le(pU, chroma_u); + set_u16_le(pV, chroma_v); + } + pU += 4; + pV += 4; + } + //reset for next pass + memset(surf->uv_alpha, 0, surf->uv_alpha_alloc); +} + + +GF_Err evg_surface_clear_nv12_10(GF_EVGSurface *_surf, GF_IRect rc, GF_Color col, Bool swap_uv) +{ + s32 i, j; + u8 _cy, _cb, _cr; + u16 cy, cb, cr; + GF_EVGSurface *surf = (GF_EVGSurface *)_surf; + u16 *s_pY, *s_pU; + char *pY = surf->pixels; + char *pU = surf->pixels + surf->height *surf->pitch_y; + char *pU_first, *pY_first; + + pY += rc.y * surf->pitch_y; + pU += rc.y/2 * surf->pitch_y; + + pY += 2*rc.x; + pU += 2*rc.x; + pU_first = pU; + pY_first = pY; + + gf_evg_rgb_to_yuv(surf, col, &_cy, &_cb, &_cr); + + if (swap_uv) { + u8 t = _cb; + _cb = _cr; + _cr = t; + } + cy = ((u16)_cy) << 2; + cb = ((u16)_cb) << 2; + cr = ((u16)_cr) << 2; + + s_pY = (u16 *) pY; + s_pU = (u16 *) pU; + + //init first lines + if (cy) { + for (j=0; jpitch_y/2; + + for (j=0; jpitch_y/2; + + for (i = 1; i < rc.height; i++) { + + memcpy(s_pY, pY_first, 2*rc.width); + s_pY += surf->pitch_y/2; + + if (i%2) continue; + + //non-first uv line recopy from first (half width, U and V mix, 2 bytes = width*2) + memcpy(s_pU, pU_first, 2*rc.width); + s_pU += surf->pitch_y/2; + } + return GF_OK; +} + + + +/* + YUV422 10 part +*/ + +void evg_yuv422p_10_flush_uv_const(GF_EVGSurface *surf, u8 *_surf_uv_alpha, s32 cu, s32 cv, s32 y) +{ + u32 i, a; + char *pU = surf->pixels + surf->height *surf->pitch_y; + char *pV; + u16 *surf_uv_alpha = (u16 *) surf->uv_alpha; + pU += y * surf->pitch_y/2; + pV = pU + surf->height * surf->pitch_y/2; + + for (i=0; iwidth; i+=2) { + a = surf_uv_alpha[i] + surf_uv_alpha[i+1]; + + if (a) { + s32 dst; + u16 *s_ptr_u, *s_ptr_v; + + a /= 2; + + s_ptr_u = (u16*)pU + i / 2; + s_ptr_v = (u16*)pV + i / 2; + if (a==0xFFFF) { + set_u16_le(s_ptr_u, (u16) cu); + set_u16_le(s_ptr_v, (u16) cv); + + } else { + get_u16_le(dst, s_ptr_u); + set_u16_le(s_ptr_u, (u16) (mul_10(a, cu - dst) + dst) ); + + get_u16_le(dst, s_ptr_v); + set_u16_le(s_ptr_v, (u16) (mul_10(a, cv - dst) + dst)); + } + } + } + memset(surf->uv_alpha, 0, surf->uv_alpha_alloc); +} + +void evg_yuv422p_10_flush_uv_var(GF_EVGSurface *surf, u8 *_surf_uv_alpha, s32 _cu, s32 _cv, s32 y) +{ + u32 i; + u16 *surf_uv_alpha = (u16 *) surf->uv_alpha; + u16 *pU = (u16 *) (surf->pixels + surf->height *surf->pitch_y + y * surf->pitch_y/2); + u16 *pV = (u16 *) (surf->pixels + surf->height *surf->pitch_y + surf->height * surf->pitch_y/2 + y * surf->pitch_y/2); + + for (i=0; iwidth; i+=2) { + u32 a, a11, a12; + u32 idx1=3*i; + u32 idx2=3*i + 3; + //get alpha + a11 = surf_uv_alpha[idx1]; + a12 = surf_uv_alpha[idx2]; + a=a11+a12; + + if (a) { + s32 cdst=0, chroma_u, chroma_v, c11, c12; + + a /= 2; + + //get cb + if (a!=0xFFFF) + get_u16_le(cdst, pU); + + c11 = (u32)surf_uv_alpha[idx1+1]; + if (a11!=0xFFFF) c11 = mul_10_zero(a11, c11 - cdst) + cdst; + c12 = (u32)surf_uv_alpha[idx2+1]; + if (a12!=0xFFFF) c12 = mul_10_zero(a12, c12 - cdst) + cdst; + + chroma_u = c11 + c12; + chroma_u /= 2; + set_u16_le(pU, (u16) chroma_u); + + //get cr + if (a!=0xFFFF) + get_u16_le(cdst, pV); + + c11 = (u32)surf_uv_alpha[idx1+2]; + if (a11!=0xFFFF) c11 = mul_10_zero(a11, c11 - cdst) + cdst; + c12 = (u32)surf_uv_alpha[idx2+2]; + if (a12!=0xFFFF) c12 = mul_10_zero(a12, c12 - cdst) + cdst; + + chroma_v = c11 + c12; + chroma_v /= 2; + set_u16_le(pV, (u16) chroma_v); + } + pU++; + pV++; + } + //reset for next pass + memset(surf->uv_alpha, 0, surf->uv_alpha_alloc); +} + + +GF_Err evg_surface_clear_yuv422p_10(GF_EVGSurface *_surf, GF_IRect rc, GF_Color col) +{ + s32 i, j; + u8 _cy, _cb, _cr; + u16 cy, cb, cr; + GF_EVGSurface *surf = (GF_EVGSurface *)_surf; + char *pY = surf->pixels; + char *pU = surf->pixels + surf->height *surf->pitch_y; + char *pV; + char *o_pY, *o_pU, *o_pV; + + pY += rc.y * surf->pitch_y; + pU += rc.y/2 * surf->pitch_y/2; + pV = pU + surf->height/2 * surf->pitch_y/2; + + pY += 2*rc.x; + pU += rc.x; + pV += rc.x; + + o_pY = pY; + o_pU = pU; + o_pV = pV; + gf_evg_rgb_to_yuv(surf, col, &_cy, &_cb, &_cr); + + cy = ((u16)_cy) << 2; + cb = ((u16)_cb) << 2; + cr = ((u16)_cr) << 2; + + for (i = 0; i < rc.height; i++) { + if (!i) { + for (j=0; jpitch_y; + pU += surf->pitch_y/2; + pV += surf->pitch_y/2; + } + return GF_OK; +} + + +/* + YUV444 10bits part +*/ + +void evg_yuv444p_10_fill_const(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf) +{ + u16 *pY, *pU, *pV; + s32 i; + u32 cy, cu, cv; + + pY = (u16 *) (surf->pixels + y * surf->pitch_y); + pU = (u16 *) (surf->pixels + surf->height*surf->pitch_y + y * surf->pitch_y); + pV = (u16 *) (surf->pixels + 2*surf->height*surf->pitch_y + y * surf->pitch_y); + + cy = (surf->fill_col_wide>>32) & 0xFFFF; + cy >>=6; + cu = (surf->fill_col_wide>>16) & 0xFFFF; + cu >>=6; + cv = (surf->fill_col_wide) & 0xFFFF; + cv >>=6; + + for (i=0; ipixels + y * surf->pitch_y); + pU = (u16 *) (surf->pixels + surf->height*surf->pitch_y + y * surf->pitch_y); + pV = (u16 *) (surf->pixels + 2*surf->height*surf->pitch_y + y * surf->pitch_y); + + a = (surf->fill_col_wide>>48) & 0xFFFF; + cy = (surf->fill_col_wide>>32) & 0xFFFF; + cy >>=6; + cu = (surf->fill_col_wide>>16) & 0xFFFF; + cu >>=6; + cv = (surf->fill_col_wide) & 0xFFFF; + cv >>=6; + + if (surf->get_alpha) { + for (i=0; iget_alpha(surf->get_alpha_udta, a, x, y) * spans[i].coverage; + fin /= 0xFF; + + overmask_yuv420p_10_const_run(fin, cy, s_pY, 1, 0); + overmask_yuv420p_10_const_run(fin, cu, s_pU, 1, 0); + overmask_yuv420p_10_const_run(fin, cv, s_pV, 1, 0); + } + } + } else { + for (i=0; ipixels + y * surf->pitch_y); + pU = (u16 *) (surf->pixels + surf->height*surf->pitch_y + y * surf->pitch_y); + pV = (u16 *) (surf->pixels + 2*surf->height*surf->pitch_y + y * surf->pitch_y); + + for (i=0; isten, surf, spans[i].x, y, len); + p_col = surf->stencil_pix_run; + + s_pY = pY + spans[i].x; + s_pU = pU + spans[i].x; + s_pV = pV + spans[i].x; + + while (len--) { + u64 col = *p_col; + u32 col_a = (col>>48)&0xFFFF; + if (col_a) { + u16 cy, cb, cr; + //col is directly packed as AYCbCr + cy = ((col>>32) & 0xFFFF) >> 6; + cb = ((col>>16) & 0xFFFF) >> 6; + cr = ((col) & 0xFFFF) >> 6; + + if ((spanalpha!=0xFF) || (col_a != 0xFFFF)) { + u16 spana = spanalpha; + spana <<= 8; + overmask_yuv420p_10(col_a, cy, s_pY, spana); + overmask_yuv420p_10(col_a, cb, s_pU, spana); + overmask_yuv420p_10(col_a, cr, s_pV, spana); + } else { + set_u16_le(s_pY, cy); + set_u16_le(s_pU, cb); + set_u16_le(s_pV, cr); + } + } + s_pY++; + s_pU++; + s_pV++; + p_col++; + } + } +} + +GF_Err evg_surface_clear_yuv444p_10(GF_EVGSurface *_surf, GF_IRect rc, GF_Color col) +{ + s32 i; + u8 _cy, _cb, _cr; + u16 cy, cb, cr; + GF_EVGSurface *surf = (GF_EVGSurface *)_surf; + char *pY = surf->pixels; + char *pU = surf->pixels + surf->height *surf->pitch_y; + char *pV, *o_pY, *o_pU, *o_pV; + + pY += rc.y * surf->pitch_y; + pU += rc.y * surf->pitch_y; + pV = pU + surf->height * surf->pitch_y; + + pY += 2*rc.x; + pU += 2*rc.x; + pV += 2*rc.x; + + gf_evg_rgb_to_yuv(surf, col, &_cy, &_cb, &_cr); + + cy = ((u16)_cy) << 2; + cb = ((u16)_cb) << 2; + cr = ((u16)_cr) << 2; + + o_pY = pY; + o_pU = pU; + o_pV = pV; + + for (i = 0; i < rc.height; i++) { + if (!i) { + s32 j; + for (j=0; jpitch_y; + pU += surf->pitch_y; + pV += surf->pitch_y; + } + return GF_OK; +} diff --git a/src/evg/stencil.c b/src/evg/stencil.c new file mode 100644 index 0000000..c0e0632 --- /dev/null +++ b/src/evg/stencil.c @@ -0,0 +1,2021 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / software 2D rasterizer module + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "rast_soft.h" + +static GF_EVGStencil *evg_solid_brush(); +static GF_EVGStencil *evg_texture_brush(); +static GF_EVGStencil *evg_linear_gradient_brush(); +static GF_EVGStencil *evg_radial_gradient_brush(); + + + +GF_Color color_interpolate(u32 a, u32 b, u8 pos) +{ + u32 ca = ((a>>24) )*(u32)(0xFF-pos)+((b>>24) )*(u32)pos; + u32 cr = ((a>>16)&0xFF)*(u32)(0xFF-pos)+((b>>16)&0xFF)*(u32)pos; + u32 cg = ((a>> 8)&0xFF)*(u32)(0xFF-pos)+((b>> 8)&0xFF)*(u32)pos; + u32 cb = ((a )&0xFF)*(u32)(0xFF-pos)+((b )&0xFF)*(u32)pos; + return (((ca+(ca>>8)+1)>>8)<<24)| + (((cr+(cr>>8)+1)>>8)<<16)| + (((cg+(cg>>8)+1)>>8)<< 8)| + (((cb+(cb>>8)+1)>>8) ); +} + + + + +/* + Generic gradient tools +*/ + +#define EVGGRADIENTBUFFERSIZE (1<updated = 1; + + if (_this->pos[0]>=0) { + if(_this->pos[0]>0) { + end = FIX2INT(gf_mulfix(_this->pos[0], maxPos)); + for (i=0; i<= end; i++) { + _this->precomputed_argb[i] = _this->col[0]; + } + } + for (c=0; cpos[c]<0) break; + if (_this->pos[c+1]>=0) { + start = FIX2INT(gf_mulfix(_this->pos[c], maxPos)); + end = FIX2INT(gf_mulfix(_this->pos[c+1], maxPos)); + diff = end-start; + + if (diff) { + for (i=start; i<=end; i++) { + _this->precomputed_argb[i] = color_interpolate(_this->col[c], _this->col[c+1], + (u8) ( ( (i-start) * 255) / diff) ); + } + } + } else { + start = FIX2INT(gf_mulfix(_this->pos[c+0], maxPos)); + for(i=start; i<=EVGGRADIENTMAXINTPOS; i++) { + _this->precomputed_argb[i] = _this->col[c]; + } + } + } + } +} + +static u32 gradient_get_color(EVG_BaseGradient *_this, s32 pos) +{ + s32 max_pos = 1 << EVGGRADIENTBITS; + + switch (_this->mod) { + case GF_GRADIENT_MODE_SPREAD: + if (pos<0) pos = -pos; + return _this->precomputed_dest[(pos & max_pos) ? EVGGRADIENTMAXINTPOS - (pos % max_pos) : pos % max_pos]; + + case GF_GRADIENT_MODE_REPEAT: + while (pos < 0) pos += max_pos; + return _this->precomputed_dest[pos % max_pos]; + + case GF_GRADIENT_MODE_PAD: + default: + return _this->precomputed_dest[ MIN(EVGGRADIENTMAXINTPOS, MAX((s32) 0, pos))]; + } +} + +GF_EXPORT +GF_Err gf_evg_stencil_set_gradient_interpolation(GF_EVGStencil * p, Fixed *pos, GF_Color *col, u32 count) +{ + EVG_BaseGradient *_this = (EVG_BaseGradient *) p; + if ( (_this->type != GF_STENCIL_LINEAR_GRADIENT) && (_this->type != GF_STENCIL_RADIAL_GRADIENT) ) return GF_BAD_PARAM; + if (count>=EVGGRADIENTSLOTS-1) return GF_OUT_OF_MEM; + if (count) { + memcpy(_this->col, col, sizeof(GF_Color) * count); + memcpy(_this->pos, pos, sizeof(Fixed) * count); + } + _this->col[count] = 0; + _this->pos[count] = -FIX_ONE; + gradient_update(_this); + return GF_OK; +} + + +GF_EXPORT +GF_Err gf_evg_stencil_push_gradient_interpolation(GF_EVGStencil * p, Fixed pos, GF_Color col) +{ + u32 count=0; + EVG_BaseGradient *_this = (EVG_BaseGradient *) p; + if ( (_this->type != GF_STENCIL_LINEAR_GRADIENT) && (_this->type != GF_STENCIL_RADIAL_GRADIENT) ) return GF_BAD_PARAM; + while (countpos[count]==-FIX_ONE) break; + count++; + } + if (count>=EVGGRADIENTSLOTS-1) return GF_OUT_OF_MEM; + _this->col[count] = col; + _this->pos[count] = pos; + count++; + _this->col[count] = 0; + _this->pos[count] = -FIX_ONE; + gradient_update(_this); + return GF_OK; +} + +GF_EXPORT +GF_Err gf_evg_stencil_set_gradient_mode(GF_EVGStencil * p, GF_GradientMode mode) +{ + EVG_BaseGradient *_this = (EVG_BaseGradient *) p; + if ( (_this->type != GF_STENCIL_LINEAR_GRADIENT) && (_this->type != GF_STENCIL_RADIAL_GRADIENT) ) return GF_BAD_PARAM; + _this->mod = mode; + return GF_OK; +} + +/* + Generic stencil +*/ + +GF_EXPORT +GF_EVGStencil * gf_evg_stencil_new(GF_StencilType type) +{ + GF_EVGStencil *st; + switch (type) { + case GF_STENCIL_SOLID: + st = evg_solid_brush(); + break; + case GF_STENCIL_LINEAR_GRADIENT: + st = evg_linear_gradient_brush(); + break; + case GF_STENCIL_RADIAL_GRADIENT: + st = evg_radial_gradient_brush(); + break; + case GF_STENCIL_TEXTURE: + st = evg_texture_brush(); + break; + default: + return 0L; + } + if (st) { + gf_mx2d_init(st->pmat); + gf_mx2d_init(st->smat); + gf_cmx_init(&st->cmat); + } + return st; +} + +GF_EXPORT +void gf_evg_stencil_delete(GF_EVGStencil * st) +{ + GF_EVGStencil *_this = (GF_EVGStencil *) st; + switch(_this->type) { + case GF_STENCIL_SOLID: + case GF_STENCIL_LINEAR_GRADIENT: + case GF_STENCIL_RADIAL_GRADIENT: + gf_free(_this); + return; + case GF_STENCIL_TEXTURE: + { + EVG_Texture *tx = (EVG_Texture *)_this; + /*destroy local texture iof any*/ + if (tx->owns_texture && tx->pixels) gf_free(tx->pixels); + gf_free(_this); + } + return; + } +} + +GF_EXPORT +GF_Err gf_evg_stencil_set_matrix(GF_EVGStencil * st, GF_Matrix2D *mx) +{ + GF_EVGStencil *_this = (GF_EVGStencil *)st; + if (!_this || _this->type>GF_STENCIL_TEXTURE) return GF_BAD_PARAM; + if (mx) { + gf_mx2d_copy(_this->smat, *mx); + } else { + gf_mx2d_init(_this->smat); + } + return GF_OK; +} + + +GF_EXPORT +GF_StencilType gf_evg_stencil_type(GF_EVGStencil *sten) +{ + return sten ? sten->type : 0; +} + +/* + Solid color stencil +*/ + +static GF_EVGStencil *evg_solid_brush() +{ + EVG_Brush *tmp; + GF_SAFEALLOC(tmp, EVG_Brush); + if (!tmp) return 0L; + tmp->fill_run = NULL; + tmp->color = 0xFF000000; + tmp->type = GF_STENCIL_SOLID; + return (GF_EVGStencil *) tmp; +} + +GF_EXPORT +GF_Err gf_evg_stencil_set_brush_color(GF_EVGStencil * st, GF_Color c) +{ + EVG_Brush *_this = (EVG_Brush *) st; + if (!_this || (_this ->type != GF_STENCIL_SOLID) ) return GF_BAD_PARAM; + _this->color = c; + return GF_OK; +} + + +/* + linear gradient stencil +*/ + +static void lg_fill_run(GF_EVGStencil *p, GF_EVGSurface *surf, s32 x, s32 y, u32 count) +{ + Fixed _res; + s32 val; + u32 col; + u32 *data = surf->stencil_pix_run; + u64 *data_wide = surf->not_8bits ? surf->stencil_pix_run : NULL; + EVG_LinearGradient *_this = (EVG_LinearGradient *) p; + + assert(data); + + /*no need to move x & y to fixed*/ + _res = (Fixed) (x * _this->smat.m[0] + y * _this->smat.m[1] + _this->smat.m[2]); + while (count) { + val = FIX2INT(_res); + _res += _this->smat.m[0]; + + col = gradient_get_color((EVG_BaseGradient *)_this, val ); + if (data_wide) *data_wide++ = evg_col_to_wide(col); + else *data++ = col; + + count--; + } +} + +GF_EXPORT +GF_Err gf_evg_stencil_set_linear_gradient(GF_EVGStencil * st, Fixed start_x, Fixed start_y, Fixed end_x, Fixed end_y) +{ + GF_Matrix2D mtx; + GF_Point2D s; + Fixed f; + EVG_LinearGradient *_this = (EVG_LinearGradient *) st; + if (_this->type != GF_STENCIL_LINEAR_GRADIENT) return GF_BAD_PARAM; + + _this->start.x = start_x; + _this->start.y = start_y; + _this->end.x = end_x; + _this->end.y = end_y; + s.x = end_x - start_x; + s.y = end_y - start_y; + f = gf_v2d_len(&s); + if (f) f = gf_invfix(f); + + gf_mx2d_init(mtx); + mtx.m[2] = - _this->start.x; + mtx.m[5] = - _this->start.y; + _this->vecmat = mtx; + + gf_mx2d_init(mtx); + gf_mx2d_add_rotation(&mtx, 0, 0, - gf_atan2(s.y, s.x)); + gf_mx2d_add_matrix(&_this->vecmat, &mtx); + + gf_mx2d_init(mtx); + gf_mx2d_add_scale(&mtx, f, f); + gf_mx2d_add_matrix(&_this->vecmat, &mtx); + return GF_OK; +} + +static GF_EVGStencil *evg_linear_gradient_brush() +{ + s32 i; + EVG_LinearGradient *tmp; + GF_SAFEALLOC(tmp, EVG_LinearGradient); + if (!tmp) return 0L; + gf_mx2d_init(tmp->vecmat); + tmp->fill_run = lg_fill_run; + tmp->type = GF_STENCIL_LINEAR_GRADIENT; + for(i=0; ipos[i]=-1; + + tmp->alpha = 0xFF; + gf_evg_stencil_set_linear_gradient((GF_EVGStencil *)tmp, 0, 0, FIX_ONE, 0); + return (GF_EVGStencil *) tmp; +} + + +/* + radial gradient stencil +*/ + +static void rg_fill_run(GF_EVGStencil *p, GF_EVGSurface *surf, s32 _x, s32 _y, u32 count) +{ + Fixed x, y, dx, dy, b, val; + s32 pos; + u32 col; + u32 *data = surf->stencil_pix_run; + u64 *data_wide = surf->not_8bits ? surf->stencil_pix_run : NULL; + EVG_RadialGradient *_this = (EVG_RadialGradient *) p; + + assert(data); + + x = INT2FIX(_x); + y = INT2FIX(_y); + gf_mx2d_apply_coords(&_this->smat, &x, &y); + + dx = x - _this->d_f.x; + dy = y - _this->d_f.y; + while (count) { + b = gf_mulfix(_this->rad, gf_mulfix(dx, _this->d_f.x) + gf_mulfix(dy, _this->d_f.y)); + val = gf_mulfix(b, b) + gf_mulfix(_this->rad, gf_mulfix(dx, dx)+gf_mulfix(dy, dy)); + b += gf_sqrt(val); + pos = FIX2INT(EVGGRADIENTBUFFERSIZE*b); + + col = gradient_get_color((EVG_BaseGradient *)_this, pos); + if (data_wide) *data_wide++ = evg_col_to_wide(col); + else *data++ = col; + + dx += _this->d_i.x; + dy += _this->d_i.y; + count--; + } +} + +void evg_radial_init(EVG_RadialGradient *_this) +{ + GF_Point2D p0, p1; + p0.x = p0.y = p1.y = 0; + p1.x = FIX_ONE; + + gf_mx2d_apply_point(&_this->smat, &p0); + gf_mx2d_apply_point(&_this->smat, &p1); + _this->d_i.x = p1.x - p0.x; + _this->d_i.y = p1.y - p0.y; + + _this->rad = FIX_ONE - gf_mulfix(_this->d_f.x, _this->d_f.x) - gf_mulfix(_this->d_f.y, _this->d_f.y); + if (_this->rad) { + _this->rad = gf_invfix(_this->rad); + } else { + _this->rad = EVGGRADIENTBUFFERSIZE; + } +} + +static GF_EVGStencil *evg_radial_gradient_brush() +{ + s32 i; + EVG_RadialGradient *tmp; + GF_SAFEALLOC(tmp, EVG_RadialGradient); + if (!tmp) return 0L; + + tmp->fill_run = rg_fill_run; + tmp->type = GF_STENCIL_RADIAL_GRADIENT; + for(i=0; ipos[i]=-1; + + tmp->center.x = tmp->center.y = FIX_ONE/2; + tmp->focus = tmp->center; + tmp->radius = tmp->center; + tmp->alpha = 0xFF; + return (GF_EVGStencil *) tmp; +} + + +GF_EXPORT +GF_Err gf_evg_stencil_set_radial_gradient(GF_EVGStencil * st, Fixed cx, Fixed cy, Fixed fx, Fixed fy, Fixed x_radius, Fixed y_radius) +{ + EVG_RadialGradient *_this = (EVG_RadialGradient *) st; + if (_this->type != GF_STENCIL_RADIAL_GRADIENT) return GF_BAD_PARAM; + + _this->center.x = cx; + _this->center.y = cy; + _this->focus.x = fx; + _this->focus.y = fy; + _this->radius.x = x_radius; + _this->radius.y = y_radius; + return GF_OK; +} + + +void evg_gradient_precompute(EVG_BaseGradient *grad, GF_EVGSurface *surf) +{ + Bool do_cmat, do_yuv, has_a, has_changed; + u32 i, nb_col; + + has_changed = grad->updated; + + if (surf->yuv_type) { + if (grad->yuv_prof != surf->yuv_prof) { + grad->yuv_prof = surf->yuv_prof; + has_changed = GF_TRUE; + } + } else { + if (grad->yuv_prof) { + grad->yuv_prof = 0; + has_changed = GF_TRUE; + } + } + if (!has_changed) { + return; + } + grad->updated = 0; + + do_yuv = (surf->yuv_type==EVG_YUV_NONE) ? GF_FALSE : GF_TRUE; + + do_cmat = (grad->cmat.identity) ? GF_FALSE : GF_TRUE; + has_a = (grad->alpha==0xFF) ? GF_FALSE : GF_TRUE; + nb_col = (1<precomputed_argb[i]; + if (has_a) { + u32 ca = ((u32) (GF_COL_A(argb) + 1) * grad->alpha) >> 8; + argb = ( ((ca<<24) & 0xFF000000) ) | (argb & 0x00FFFFFF); + } + if (do_cmat) + argb = gf_cmx_apply(&grad->cmat, argb); + + if (do_yuv) + argb = gf_evg_argb_to_ayuv(surf, argb); + + grad->precomputed_dest[i] = argb; + } +} + + +/* + Texture stencil + FIXME: add filtering , check bilinear +*/ + +static GFINLINE s32 mul255(s32 a, s32 b) +{ + return ((a+1) * b) >> 8; +} +static u32 EVG_LERP(u32 c0, u32 c1, u8 t) +{ + s32 a0, r0, g0, b0; + s32 a1, r1, g1, b1; + s32 a2, r2, g2, b2; + + if (!t) return c0; + + a0 = GF_COL_A(c0); + r0 = GF_COL_R(c0); + g0 = GF_COL_G(c0); + b0 = GF_COL_B(c0); + a1 = GF_COL_A(c1); + r1 = GF_COL_R(c1); + g1 = GF_COL_G(c1); + b1 = GF_COL_B(c1); + + a2 = a0 + mul255(t, (a1 - a0)); + r2 = r0 + mul255(t, (r1 - r0)); + g2 = g0 + mul255(t, (g1 - g0)); + b2 = b0 + mul255(t, (b1 - b0)); + return GF_COL_ARGB(a2, r2, g2, b2); +} + +static GFINLINE s64 mul_wide(s64 a, s64 b) +{ + return ((a+1) * b) >> 16; +} +static u64 EVG_LERP_WIDE(u64 c0, u64 c1, u8 t) +{ + s64 a0, r0, g0, b0; + s64 a1, r1, g1, b1; + s64 a2, r2, g2, b2; + + if (!t) return c0; + + a0 = (c0>>48) & 0xFFFF; + r0 = (c0>>32) & 0xFFFF; + g0 = (c0>>16) & 0xFFFF; + b0 = (c0) & 0xFFFF; + + a1 = (c1>>48) & 0xFFFF; + r1 = (c1>>32) & 0xFFFF; + g1 = (c1>>16) & 0xFFFF; + b1 = (c1) & 0xFFFF; + + a2 = a0 + mul_wide(t, (a1 - a0)); + r2 = r0 + mul_wide(t, (r1 - r0)); + g2 = g0 + mul_wide(t, (g1 - g0)); + b2 = b0 + mul_wide(t, (b1 - b0)); + + return evg_make_col_wide(a2, r2, g2, b2); +} + +static void tex_untransform_coord(EVG_Texture *_this, s32 _x, s32 _y, Fixed *outx, Fixed *outy) +{ + u32 checkx, checky; + Fixed x, y, dim; + + /* reverse to texture coords*/ + x = INT2FIX(_x); + y = INT2FIX(_y); + gf_mx2d_apply_coords(&_this->smat, &x, &y); + + checkx = checky = 0; + if (ABS(x)< FIX_ONE/20) checkx = 1; + if (ABS(y)< FIX_ONE/20) checky = 1; + + /*we may have a numerical stability issues, try to figure out whether we are close from 0 or width/height*/ + if ( checkx || checky) { + Fixed tx, ty; + tx = INT2FIX(_x+1); + ty = INT2FIX(_y+1); + gf_mx2d_apply_coords(&_this->smat, &tx, &ty); + + if (checkx) { + if (tx<0) x = INT2FIX(_this->width - 1); + else x = 0; + } + if (checky) { + if (ty<0) y = INT2FIX(_this->height - 1); + else y = 0; + } + } + + dim = INT2FIX(_this->width); + if (_this->mod & GF_TEXTURE_REPEAT_S) { + if (x<0) { + while (x<0) x += dim; + } else { + while (x>dim) x -= dim; + } + } else { + if (x<-dim) { + x = 0; + } else if (x>dim) { + x = dim; + } + while (x<0) x+=dim; + } + + dim = INT2FIX(_this->height); + if (_this->mod & GF_TEXTURE_REPEAT_T) { + if (y<0) { + while (y<0) y += dim; + } else { + while (y>dim) y -= dim; + } + } else { + if (y<-dim) { + y = 0; + } else if (y>dim) { + y = dim; + } + while (y<0) y+=dim; + } + + *outx=x; + *outy=y; +} + + +static u32 evg_paramtx_get_pixel(struct __evg_texture *_this, u32 x, u32 y) +{ + Float a, r, g, b; + _this->tx_callback(_this->tx_callback_udta, x, y, &r, &g, &b, &a); + r*=255; + g*=255; + b*=255; + a*=255; + return GF_COL_ARGB(a, r, g, b); +} +u64 evg_paramtx_get_pixel_wide(struct __evg_texture *_this, u32 x, u32 y) +{ + Float a, r, g, b; + _this->tx_callback(_this->tx_callback_udta, x, y, &r, &g, &b, &a); + r*=0xFFFF; + g*=0xFFFF; + b*=0xFFFF; + a*=0xFFFF; + return evg_make_col_wide(a, r, g, b); +} + +static void tex_fill_run_callback(EVG_Texture *p, GF_EVGSurface *surf, s32 _x, s32 _y, u32 count) +{ + u32 *data = surf->stencil_pix_run; + while (count) { + *data = evg_paramtx_get_pixel(p, _x, _y); + data++; + count--; + _x++; + } +} + +static void tex_fill_run_callback_wide(EVG_Texture *p, GF_EVGSurface *surf, s32 _x, s32 _y, u32 count) +{ + u64 *data = surf->stencil_pix_run; + while (count) { + *data = evg_paramtx_get_pixel_wide(p, _x, _y); + data++; + count--; + } +} + +//bilinear used fo 2D graphics ? +#define USE_BILINEAR 0 + +static void tex_fill_run(GF_EVGStencil *p, GF_EVGSurface *surf, s32 _x, s32 _y, u32 count) +{ + s32 cx, x0, y0; + u32 pix, replace_col; + Bool has_alpha, has_replace_cmat, has_cmat, repeat_s, repeat_t; + Fixed x, y, _fd; +#if USE_BILINEAR + s32 incx, incy; +#endif + u32 *data = surf->stencil_pix_run; + EVG_Texture *_this = (EVG_Texture *) p; + + if (_this->tx_callback && _this->tx_callback_screen_coords) { + tex_fill_run_callback(_this, surf, _x, _y, count); + return; + } + + tex_untransform_coord(_this, _x, _y, &x, &y); + +#if USE_BILINEAR + incx = (_this->inc_x>0) ? 1 : -1; + incy = (_this->inc_y>0) ? 1 : -1; +#endif + + _fd = INT2FIX(_this->width); + repeat_s = _this->mod & GF_TEXTURE_REPEAT_S; + if (!repeat_s && (x < - _fd)) x = 0; + while (x<0) x += _fd; + + _fd = INT2FIX(_this->height); + repeat_t = _this->mod & GF_TEXTURE_REPEAT_T; + if (!repeat_t && (y < - _fd)) y = 0; + while (y<0) y += _fd; + + has_alpha = (_this->alpha != 255) ? GF_TRUE : GF_FALSE; + has_replace_cmat = _this->cmat_is_replace ? GF_TRUE : GF_FALSE; + if (has_replace_cmat) has_cmat = GF_FALSE; + else has_cmat = _this->cmat.identity ? GF_FALSE : GF_TRUE; + + replace_col = _this->replace_col; + + while (count) { + x0 = FIX2INT(x); + assert((s32)x0 >=0); + if (repeat_s) { + x0 = (x0) % _this->width; + } else { + x0 = MIN(x0, (s32) _this->width - 1); + } + + y0 = FIX2INT(y); + assert((s32)y0 >=0); + if (repeat_t) { + y0 = (y0) % _this->height; + } else if (y0 >= (s32) _this->height) { + y0 = _this->height-1; + } + + pix = _this->tx_get_pixel(_this, x0, y0); + + _x++; + tex_untransform_coord(_this, _x, _y, &x, &y); + + if (x<0) x+=INT2FIX(_this->width); + if (y<0) y+=INT2FIX(_this->height); + + /*bilinear filtering - disabled (too slow and not precise enough)*/ +#if USE_BILINEAR + if (_this->filter==GF_TEXTURE_FILTER_HIGH_QUALITY) { + u32 p00, p01, p10, p11; + s32 x1, y1; + u8 tx, ty; + + tx = FIX2INT(gf_muldiv(x, 255, _this->width) ); + ty = FIX2INT(gf_muldiv(y, 255, _this->height) ); + + x1 = (x0+incx); + if (x1<0) { + while (x1<0) x1 += _this->width; + } else { + x1 = x1 % _this->width; + } + y1 = (y0+incy); + if (y1<0) { + while (y1<0) y1+=_this->height; + } else { + y1 = y1 % _this->height; + } + if (incx>0) { + if (x1x0) tx = 255-tx; + } + if (incy>0) { + if (y1y0) ty = 255-ty; + } + + p00 = pix; + p01 = _this->tx_get_pixel(_this, x1, y0); + p10 = _this->tx_get_pixel(_this, x0, y1); + p11 = _this->tx_get_pixel(_this, x1, y1); + + p00 = EVG_LERP(p00, p01, tx); + p10 = EVG_LERP(p10, p11, tx); + pix = EVG_LERP(p00, p10, ty); + + } +#endif + + if (has_alpha) { + cx = ((GF_COL_A(pix) + 1) * _this->alpha) >> 8; + pix = ( ((cx<<24) & 0xFF000000) ) | (pix & 0x00FFFFFF); + } + if (has_replace_cmat) { + u32 __a; + __a = GF_COL_A(pix); + __a = (u32) (_this->cmat.m[18] * __a); + //replace col is in target pixel format + pix = ((__a<<24) | (replace_col & 0x00FFFFFF)); + } + //move pixel to target pixel format, applying color transform matrix + else if (_this->is_yuv) { + //if surf is rgb, transform + if (!surf->yuv_type) { + pix = gf_evg_ayuv_to_argb(surf, pix); + //apply cmat + if (has_cmat) + pix = gf_cmx_apply(&_this->cmat, pix); + } else if (has_cmat) { + //yuv->yuv , use color matrix in yuv domain + //this is equivalent to gf_evg_ayuv_to_argb, gf_cmx_apply(&_this->cmat, pix), gf_evg_argb_to_ayuv + pix = gf_cmx_apply(&_this->yuv_cmat, pix); + } + } + //texture is RGB + else { + //apply cmat + if (has_cmat) + pix = gf_cmx_apply(&_this->cmat, pix); + + //dest is yuv, transform + if (surf->yuv_type) + pix = gf_evg_argb_to_ayuv(surf, pix); + } + + *data++ = pix; + count--; + } +} + + +/*just a little faster...*/ +static void tex_fill_run_straight(GF_EVGStencil *p, GF_EVGSurface *surf, s32 _x, s32 _y, u32 count) +{ + s32 x0, y0; + u32 pix; + u32 __a; + Bool repeat_s = GF_FALSE; + Fixed x, y, _fdim; + u32 *data = surf->stencil_pix_run; + EVG_Texture *_this = (EVG_Texture *) p; + + if (_this->tx_callback && _this->tx_callback_screen_coords) { + tex_fill_run_callback(_this, surf, _x, _y, count); + return; + } + + /*get texture coords in FIXED - offset*/ + x = _this->smat.m[0]*_x + _this->smat.m[2]; + y = _this->smat.m[4]*_y + _this->smat.m[5]; + + /*we may have a numerical stability issues, try to figure out whether we are close from 0 or width/height*/ + if (ABS(x)< FIX_ONE/10) { + Fixed test = _this->smat.m[0]*(_x+1) + _this->smat.m[2]; + if (test<0) x = INT2FIX(_this->width - 1); + else x = 0; + } + if (ABS(y)< FIX_ONE/10) { + Fixed test = _this->smat.m[4]*(_y+1) + _this->smat.m[5]; + if (test<0) y = INT2FIX(_this->height - 1); + else y = 0; + } + + /* and move in absolute coords*/ + _fdim = INT2FIX(_this->width); + repeat_s = (_this->mod & GF_TEXTURE_REPEAT_S); + if (!repeat_s && (x <- _fdim)) x=0; + while (x<0) x += _fdim; + + _fdim = INT2FIX(_this->height); + if (!(_this->mod & GF_TEXTURE_REPEAT_T) && (y <- _fdim)) y = 0; + while (y<0) y += _fdim; + + y0 = FIX2INT(y); + y0 = y0 % _this->height; + + while (count) { + x0 = FIX2INT(x); + if (repeat_s) { + x0 = (x0) % _this->width; + } else if (x0 >= (s32) _this->width) x0 = _this->width-1; + + x += _this->inc_x; + pix = _this->tx_get_pixel(_this, x0, y0); + + //replace_col is in destination format + if (_this->replace_col) { + __a = GF_COL_A(pix); + pix = ((__a<<24) | (_this->replace_col & 0x00FFFFFF)); + } + //move pixel to target pixel format + else if (_this->is_yuv && !surf->yuv_type) { + pix = gf_evg_ayuv_to_argb(surf, pix); + } + else if (!_this->is_yuv && surf->yuv_type) { + pix = gf_evg_argb_to_ayuv(surf, pix); + } + + *data++ = pix; + count--; + } +} + +u64 evg_col_to_wide( u32 col) +{ + u32 a = GF_COL_A(col) << 8 | 0xFF; + u32 r = GF_COL_R(col) << 8 | 0xFF; + u32 g = GF_COL_G(col) << 8 | 0xFF; + u32 b = GF_COL_B(col) << 8 | 0xFF; + return evg_make_col_wide(a, r, g, b); +} + +static void tex_fill_run_wide(GF_EVGStencil *p, GF_EVGSurface *surf, s32 _x, s32 _y, u32 count) +{ + s32 x0, y0; + u64 pix, replace_col; + Bool has_alpha, has_replace_cmat, has_cmat, repeat_s, repeat_t; + Fixed x, y, _fd; +#if USE_BILINEAR + s32 incx, incy; +#endif + u64 *data = surf->stencil_pix_run; + EVG_Texture *_this = (EVG_Texture *) p; + + if (_this->tx_callback && _this->tx_callback_screen_coords) { + tex_fill_run_callback_wide(_this, surf, _x, _y, count); + return; + } + + tex_untransform_coord(_this, _x, _y, &x, &y); + +#if USE_BILINEAR + incx = (_this->inc_x>0) ? 1 : -1; + incy = (_this->inc_y>0) ? 1 : -1; +#endif + + _fd = INT2FIX(_this->width); + repeat_s = _this->mod & GF_TEXTURE_REPEAT_S; + if (!repeat_s && (x < - _fd)) x = 0; + while (x<0) x += _fd; + + _fd = INT2FIX(_this->height); + repeat_t = _this->mod & GF_TEXTURE_REPEAT_T; + if (!repeat_t && (y < - _fd)) y = 0; + while (y<0) y += _fd; + + has_alpha = (_this->alpha != 255) ? GF_TRUE : GF_FALSE; + has_replace_cmat = _this->cmat_is_replace ? GF_TRUE : GF_FALSE; + if (has_replace_cmat) has_cmat = GF_FALSE; + else has_cmat = _this->cmat.identity ? GF_FALSE : GF_TRUE; + + replace_col = evg_col_to_wide(_this->replace_col); + + while (count) { + x0 = FIX2INT(x); + assert((s32)x0 >=0); + if (repeat_s) { + x0 = (x0) % _this->width; + } else { + x0 = MIN(x0, (s32) _this->width - 1); + } + + y0 = FIX2INT(y); + assert((s32)y0 >=0); + if (repeat_t) { + y0 = (y0) % _this->height; + } else if (y0 >= (s32) _this->height) { + y0 = _this->height-1; + } + + if (_this->tx_get_pixel_wide) { + pix = _this->tx_get_pixel_wide(_this, x0, y0); + } else { + pix = evg_col_to_wide( _this->tx_get_pixel(_this, x0, y0) ); + } + _x++; + tex_untransform_coord(_this, _x, _y, &x, &y); + + if (x<0) x+=INT2FIX(_this->width); + if (y<0) y+=INT2FIX(_this->height); + + /*bilinear filtering - disabled (too slow and not precise enough)*/ +#if USE_BILINEAR + if (_this->filter==GF_TEXTURE_FILTER_HIGH_QUALITY) { + u64 p00, p01, p10, p11; + s32 x1, y1; + u8 tx, ty; + + tx = FIX2INT(gf_muldiv(x, 255, _this->width) ); + ty = FIX2INT(gf_muldiv(y, 255, _this->height) ); + + if (tx>120 || ty>120) { + x1 = (x0+incx); + if (x1<0) { + while (x1<0) x1 += _this->width; + } else { + x1 = x1 % _this->width; + } + y1 = (y0+incy); + if (y1<0) { + while (y1<0) y1+=_this->height; + } else { + y1 = y1 % _this->height; + } + if (incx>0) { + if (x1x0) tx = 255-tx; + } + if (incy>0) { + if (y1y0) ty = 255-ty; + } + + p00 = pix; + if (_this->tx_get_pixel_wide) { + p01 = _this->tx_get_pixel_wide(_this, x1, y0); + p10 = _this->tx_get_pixel_wide(_this, x0, y1); + p11 = _this->tx_get_pixel_wide(_this, x1, y1); + } else { + p01 = evg_col_to_wide(_this->tx_get_pixel(_this, x1, y0) ); + p10 = evg_col_to_wide(_this->tx_get_pixel(_this, x0, y1) ); + p11 = evg_col_to_wide(_this->tx_get_pixel(_this, x1, y1) ); + } + p00 = EVG_LERP_WIDE(p00, p01, tx); + p10 = EVG_LERP_WIDE(p10, p11, tx); + pix = EVG_LERP_WIDE(p00, p10, ty); + } + } +#endif + + if (has_alpha) { + u64 _a = (pix>>48)&0xFF; + _a = (_a * _this->alpha) >> 8; + _a<<=48; + pix = ( (_a & 0xFFFF000000000000UL) ) | (pix & 0x0000FFFFFFFFFFFFUL); + } + if (has_replace_cmat) { + u64 _a = (pix>>48)&0xFF; + _a = (_a * _this->alpha) >> 8; + _a = (u64) (_this->cmat.m[18] * _a); + _a<<=48; + pix = ( (_a & 0xFFFF000000000000UL) ) | (replace_col & 0x0000FFFFFFFFFFFFUL); + } + //move pixel to target pixel format, applying color transform matrix + else if (_this->is_yuv) { + //if surf is rgb, transform + if (!surf->yuv_type) { + pix = gf_evg_ayuv_to_argb_wide(surf, pix); + //apply cmat + if (has_cmat) + pix = gf_cmx_apply_wide(&_this->cmat, pix); + } else if (has_cmat) { + //yuv->yuv , use color matrix in yuv domain + //this is equivalent to gf_evg_ayuv_to_argb, gf_cmx_apply(&_this->cmat, pix), gf_evg_argb_to_ayuv + pix = gf_cmx_apply_wide(&_this->yuv_cmat, pix); + } + } + //texture is RGB + else { + //apply cmat + if (has_cmat) + pix = gf_cmx_apply_wide(&_this->cmat, pix); + + //dest is yuv, transform + if (surf->yuv_type) + pix = gf_evg_argb_to_ayuv_wide(surf, pix); + } + + *data++ = pix; + count--; + } +} + + +/*just a little faster...*/ +static void tex_fill_run_straight_wide(GF_EVGStencil *p, GF_EVGSurface *surf, s32 _x, s32 _y, u32 count) +{ + s32 x0, y0; + u64 pix; + Bool repeat_s = GF_FALSE; + Fixed x, y, _fdim; + u64 *data = surf->stencil_pix_run; + EVG_Texture *_this = (EVG_Texture *) p; + + if (_this->tx_callback && _this->tx_callback_screen_coords) { + tex_fill_run_callback_wide(_this, surf, _x, _y, count); + return; + } + + /*get texture coords in FIXED - offset*/ + x = _this->smat.m[0]*_x + _this->smat.m[2]; + y = _this->smat.m[4]*_y + _this->smat.m[5]; + + /*we may have a numerical stability issues, try to figure out whether we are close from 0 or width/height*/ + if (ABS(x)< FIX_ONE/10) { + Fixed test = _this->smat.m[0]*(_x+1) + _this->smat.m[2]; + if (test<0) x = INT2FIX(_this->width - 1); + else x = 0; + } + if (ABS(y)< FIX_ONE/10) { + Fixed test = _this->smat.m[4]*(_y+1) + _this->smat.m[5]; + if (test<0) y = INT2FIX(_this->height - 1); + else y = 0; + } + + /* and move in absolute coords*/ + _fdim = INT2FIX(_this->width); + repeat_s = (_this->mod & GF_TEXTURE_REPEAT_S); + if (!repeat_s && (x <- _fdim)) x=0; + while (x<0) x += _fdim; + + _fdim = INT2FIX(_this->height); + if (!(_this->mod & GF_TEXTURE_REPEAT_T) && (y <- _fdim)) y = 0; + while (y<0) y += _fdim; + + y0 = FIX2INT(y); + y0 = y0 % _this->height; + + while (count) { + x0 = FIX2INT(x); + if (repeat_s) { + x0 = (x0) % _this->width; + } else if (x0 >= (s32) _this->width) x0 = _this->width-1; + + x += _this->inc_x; + + if (_this->tx_get_pixel_wide) { + pix = _this->tx_get_pixel_wide(_this, x0, y0); + } else { + pix = evg_col_to_wide( _this->tx_get_pixel(_this, x0, y0) ); + } + + //replace_col is in destination format + if (_this->replace_col) { + u64 _a = (pix>>48)&0xFF; + _a = (_a * _this->alpha) >> 8; + _a<<=48; + pix = ( (_a & 0xFFFF000000000000UL) ) | (_this->replace_col & 0x0000FFFFFFFFFFFFUL); + } + //move pixel to target pixel format + else if (_this->is_yuv && !surf->yuv_type) { + pix = gf_evg_ayuv_to_argb_wide(surf, pix); + } + else if (!_this->is_yuv && surf->yuv_type) { + pix = gf_evg_argb_to_ayuv_wide(surf, pix); + } + + *data++ = pix; + count--; + } +} + +GF_EVGStencil *evg_texture_brush() +{ + EVG_Texture *tmp; + GF_SAFEALLOC(tmp, EVG_Texture); + if (!tmp) return 0L; + + tmp->fill_run = tex_fill_run; + tmp->type = GF_STENCIL_TEXTURE; + /*default is using the surface settings*/ + gf_evg_stencil_set_filter( (GF_EVGStencil *) tmp, GF_TEXTURE_FILTER_DEFAULT); + tmp->mod = 0; + gf_cmx_init(&tmp->cmat); + tmp->alpha = 255; + return (GF_EVGStencil *) tmp; +} + + +u32 get_pix_argb(EVG_Texture *_this, u32 x, u32 y) +{ + char *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + return GF_COL_ARGB(*(pix) & 0xFF, *(pix+1) & 0xFF, *(pix+2) & 0xFF, *(pix+3) & 0xFF); +} +u32 get_pix_rgba(EVG_Texture *_this, u32 x, u32 y) +{ + char *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + return GF_COL_ARGB(*(pix+3) & 0xFF, *(pix) & 0xFF, *(pix+1) & 0xFF, *(pix+2) & 0xFF); +} +u32 get_pix_abgr(EVG_Texture *_this, u32 x, u32 y) +{ + char *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + return GF_COL_ARGB(*(pix) & 0xFF, *(pix+3) & 0xFF, *(pix+2) & 0xFF, *(pix+1) & 0xFF); +} +u32 get_pix_bgra(EVG_Texture *_this, u32 x, u32 y) +{ + char *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + return GF_COL_ARGB(*(pix+3) & 0xFF, *(pix+2) & 0xFF, *(pix+1) & 0xFF, *(pix) & 0xFF); +} +u32 get_pix_rgbx(EVG_Texture *_this, u32 x, u32 y) +{ + char *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + return GF_COL_ARGB(0xFF, *(pix) & 0xFF, *(pix+1) & 0xFF, *(pix+2) & 0xFF); +} +u32 get_pix_xrgb(EVG_Texture *_this, u32 x, u32 y) +{ + char *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + return GF_COL_ARGB(0xFF, *(pix+1) & 0xFF, *(pix+2) & 0xFF, *(pix+3) & 0xFF); +} +u32 get_pix_xbgr(EVG_Texture *_this, u32 x, u32 y) +{ + char *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + return GF_COL_ARGB(0xFF, *(pix+3) & 0xFF, *(pix+2) & 0xFF, *(pix+1) & 0xFF); +} +u32 get_pix_bgrx(EVG_Texture *_this, u32 x, u32 y) +{ + char *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + return GF_COL_ARGB(0xFF, *(pix+2) & 0xFF, *(pix+1) & 0xFF, *(pix) & 0xFF); +} +u32 get_pix_rgb_24(EVG_Texture *_this, u32 x, u32 y) +{ + char *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + return GF_COL_ARGB(0xFF, *pix & 0xFF, *(pix+1) & 0xFF, *(pix+2) & 0xFF); +} +u32 get_pix_bgr_24(EVG_Texture *_this, u32 x, u32 y) +{ + char *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + return GF_COL_ARGB(0xFF, *(pix+2) & 0xFF, * (pix+1) & 0xFF, *pix & 0xFF); +} +u32 get_pix_444(EVG_Texture *_this, u32 x, u32 y) +{ + u8 *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + u32 r = pix[0]&0x0f; + u32 g = (pix[1]>>4)&0x0f; + u32 b = pix[1]&0x0f; + return GF_COL_ARGB(0xFF, (r << 4), (g << 4), (b << 4)); +} +u32 get_pix_555(EVG_Texture *_this, u32 x, u32 y) +{ + u8 *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + u32 r = (pix[0]>>2) & 0x1f; + u32 g = (pix[0])&0x3; + g<<=3; + g |= (pix[1]>>5) & 0x7; + u32 b = pix[1] & 0x1f; + return GF_COL_ARGB(0xFF, (r << 3), (g << 3), (b << 3)); +} +u32 get_pix_565(EVG_Texture *_this, u32 x, u32 y) +{ + char *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + u32 r = (pix[0]>>3) & 0x1f; + u32 g = (pix[0])&0x7; + g<<=3; + g |= (pix[1]>>5) & 0x7; + u32 b = pix[1] & 0x1f; + return GF_COL_ARGB(0xFF, (r << 3), (g << 2), (b << 3)); +} +u32 get_pix_grey(EVG_Texture *_this, u32 x, u32 y) +{ + char *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + u8 val = *pix; + return GF_COL_ARGB(0xFF, val, val, val); +} +u32 get_pix_alphagrey(EVG_Texture *_this, u32 x, u32 y) +{ + u8 a, g; + char *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + a = *pix; + g = *(pix+1); + return GF_COL_ARGB(a, g, g, g); +} +u32 get_pix_greyalpha(EVG_Texture *_this, u32 x, u32 y) +{ + u8 a, g; + char *pix = _this->pixels + y * _this->stride + _this->Bpp*x; + g = *pix; + a = *(pix+1); + return GF_COL_ARGB(a, g, g, g); +} +u32 get_pix_yuv420p(EVG_Texture *_this, u32 x, u32 y) +{ + u8 *pY = _this->pixels + y * _this->stride + x; + u8 *pU = _this->pix_u + y/2 * _this->stride/2 + x/2; + u8 *pV = _this->pix_v + y/2 * _this->stride/2 + x/2; + return GF_COL_ARGB(0xFF, *pY, *pU, *pV); +} + +#ifdef GPAC_BIG_ENDIAN + +#define GET_LE_10BIT_AS_8(_ptr) ( (((u16)(_ptr)[1])<<8 | (u16)(_ptr)[0] ) >> 2 ) +#define GET_LE_10BIT_AS_16(_ptr) ( (((u16)(_ptr)[1])<<8 | (u16)(_ptr)[0] ) << 6 ) + +#define GET_BE_10BIT_AS_8(_ptr) ( (*(u16 *)(_ptr)) >> 2 ) +#define GET_BE_10BIT_AS_16(_ptr) ( (*(u16 *)(_ptr)) << 6 ) + +#else + +#define GET_LE_10BIT_AS_8(_ptr) ( (*(u16 *)(_ptr)) >> 2 ) +#define GET_LE_10BIT_AS_16(_ptr) ( (*(u16 *)(_ptr)) << 6 ) + +#define GET_BE_10BIT_AS_8(_ptr) ( (((u16)(_ptr)[0])<<8 | (u16)(_ptr)[1] ) >> 2 ) +#define GET_BE_10BIT_AS_16(_ptr) ( (((u16)(_ptr)[0])<<8 | (u16)(_ptr)[1] ) << 6 ) + +#endif + + +u32 get_pix_yuv420p_10(EVG_Texture *_this, u32 x, u32 y) +{ + u8 vy, vu, vv; + u8 *pY = _this->pixels + y * _this->stride + x*2; + u8 *pU = _this->pix_u + y/2 * _this->stride/2 + (x/2)*2; + u8 *pV = _this->pix_v + y/2 * _this->stride/2 + (x/2)*2; + + vy = GET_LE_10BIT_AS_8(pY); + vu = GET_LE_10BIT_AS_8(pU); + vv = GET_LE_10BIT_AS_8(pV); + + return GF_COL_ARGB(0xFF, vy, vu, vv); +} + +u64 get_pix_yuv420p_10_wide(EVG_Texture *_this, u32 x, u32 y) +{ + u16 vy, vu, vv; + u8 *pY = _this->pixels + y * _this->stride + x*2; + u8 *pU = _this->pix_u + y/2 * _this->stride/2 + (x/2)*2; + u8 *pV = _this->pix_v + y/2 * _this->stride/2 + (x/2)*2; + + vy = GET_LE_10BIT_AS_16(pY); + vu = GET_LE_10BIT_AS_16(pU); + vv = GET_LE_10BIT_AS_16(pV); + + return evg_make_col_wide(0xFFFF, vy, vu, vv); +} +u32 get_pix_yuv420p_a(EVG_Texture *_this, u32 x, u32 y) +{ + u8 *pY = _this->pixels + y * _this->stride + x; + u8 *pU = _this->pix_u + y/2 * _this->stride/2 + x/2; + u8 *pV = _this->pix_v + y/2 * _this->stride/2 + x/2; + u8 *pA = _this->pix_a + y * _this->stride + x; + + return GF_COL_ARGB(*pA, *pY, *pU, *pV); +} +u32 get_pix_yuv422p(EVG_Texture *_this, u32 x, u32 y) +{ + u8 *pY = _this->pixels + y * _this->stride + x; + u8 *pU = _this->pix_u + y * _this->stride/2 + x/2; + u8 *pV = _this->pix_v + y * _this->stride/2 + x/2; + return GF_COL_ARGB(0xFF, *pY, *pU, *pV); +} +u32 get_pix_yuv422p_10(EVG_Texture *_this, u32 x, u32 y) +{ + u8 vy, vu, vv; + u8 *pY = _this->pixels + y * _this->stride + x*2; + u8 *pU = _this->pix_u + y * _this->stride/2 + (x/2)*2; + u8 *pV = _this->pix_v + y * _this->stride/2 + (x/2)*2; + + vy = GET_LE_10BIT_AS_8(pY); + vu = GET_LE_10BIT_AS_8(pU); + vv = GET_LE_10BIT_AS_8(pV); + return GF_COL_ARGB(0xFF, vy, vu, vv); +} + +u64 get_pix_yuv422p_10_wide(EVG_Texture *_this, u32 x, u32 y) +{ + u16 vy, vu, vv; + u8 *pY = _this->pixels + y * _this->stride + x*2; + u8 *pU = _this->pix_u + y * _this->stride/2 + (x/2)*2; + u8 *pV = _this->pix_v + y * _this->stride/2 + (x/2)*2; + + vy = GET_LE_10BIT_AS_16(pY); + vu = GET_LE_10BIT_AS_16(pU); + vv = GET_LE_10BIT_AS_16(pV); + return evg_make_col_wide(0xFFFF, vy, vu, vv); +} + +u32 get_pix_yuv444p(EVG_Texture *_this, u32 x, u32 y) +{ + u8 *pY = _this->pixels + y * _this->stride + x; + u8 *pU = _this->pix_u + y * _this->stride + x; + u8 *pV = _this->pix_v + y * _this->stride + x; + return GF_COL_ARGB(0xFF, *pY, *pU, *pV); +} +u32 get_pix_yuv444p_10(EVG_Texture *_this, u32 x, u32 y) +{ + u8 vy, vu, vv; + u8 *pY = _this->pixels + y * _this->stride + x*2; + u8 *pU = _this->pix_u + y * _this->stride + x*2; + u8 *pV = _this->pix_v + y * _this->stride + x*2; + + vy = GET_LE_10BIT_AS_8(pY); + vu = GET_LE_10BIT_AS_8(pU); + vv = GET_LE_10BIT_AS_8(pV); + return GF_COL_ARGB(0xFF, vy, vu, vv); +} +u64 get_pix_yuv444p_10_wide(EVG_Texture *_this, u32 x, u32 y) +{ + u16 vy, vu, vv; + u8 *pY = _this->pixels + y * _this->stride + x*2; + u8 *pU = _this->pix_u + y * _this->stride + x*2; + u8 *pV = _this->pix_v + y * _this->stride + x*2; + + vy = GET_LE_10BIT_AS_16(pY); + vu = GET_LE_10BIT_AS_16(pU); + vv = GET_LE_10BIT_AS_16(pV); + return evg_make_col_wide(0xFFFF, vy, vu, vv); +} +u32 get_pix_yuv444p_a(EVG_Texture *_this, u32 x, u32 y) +{ + u8 *pY = _this->pixels + y * _this->stride + x; + u8 *pU = _this->pix_u + y * _this->stride + x; + u8 *pV = _this->pix_v + y * _this->stride + x; + u8 *pA = _this->pix_a + y * _this->stride + x; + return GF_COL_ARGB(*pA, *pY, *pU, *pV); +} +u32 get_pix_yuv_nv12(EVG_Texture *_this, u32 x, u32 y) +{ + u8 *pY = _this->pixels + y * _this->stride + x; + u8 *pU = _this->pix_u + y/2 * _this->stride + (x/2)*2; + return GF_COL_ARGB(0xFF, *pY, *pU, *(pU+1)); +} + +u32 get_pix_yuv_nv12_10(EVG_Texture *_this, u32 x, u32 y) +{ + u16 vy, vu, vv; + u8 *pY = _this->pixels + y * _this->stride + x*2; + u8 *pU = _this->pix_u + y/2 * _this->stride + (x/2)*4; + vy = GET_LE_10BIT_AS_8(pY); + vu = GET_LE_10BIT_AS_8(pU); + vv = GET_LE_10BIT_AS_8(pU+2); + + return GF_COL_ARGB(0xFF, vy, vu, vv); +} +u64 get_pix_yuv_nv12_10_wide(EVG_Texture *_this, u32 x, u32 y) +{ + u16 vy, vu, vv; + u8 *pY = _this->pixels + y * _this->stride + x*2; + u8 *pU = _this->pix_u + y/2 * _this->stride + (x/2)*4; + + vy = GET_LE_10BIT_AS_16(pY); + vu = GET_LE_10BIT_AS_16(pU); + vv = GET_LE_10BIT_AS_16(pU+2); + + return evg_make_col_wide(0xFFFF, vy, vu, vv); +} +u32 get_pix_yuv_nv21(EVG_Texture *_this, u32 x, u32 y) +{ + u8 *pY = _this->pixels + y * _this->stride + x; + u8 *pU = _this->pix_u + y/2 * _this->stride + (x/2)*2; + return GF_COL_ARGB(0xFF, *pY, *(pU+1), *pU); +} +u32 get_pix_yuv_nv21_10(EVG_Texture *_this, u32 x, u32 y) +{ + u8 vy, vu, vv; + u8 *pY = _this->pixels + y * _this->stride + x*2; + u8 *pU = _this->pix_u + y/2 * _this->stride + (x/2)*4; + + vy = GET_LE_10BIT_AS_8(pY); + vu = GET_LE_10BIT_AS_8(pU); + vv = GET_LE_10BIT_AS_8(pU+2); + + return GF_COL_ARGB(0xFF, vy, vv, vu); +} +u64 get_pix_yuv_nv21_10_wide(EVG_Texture *_this, u32 x, u32 y) +{ + u16 vy, vu, vv; + u8 *pY = _this->pixels + y * _this->stride + x*2; + u8 *pU = _this->pix_u + y/2 * _this->stride + (x/2)*4; + + vy = GET_LE_10BIT_AS_16(pY); + vu = GET_LE_10BIT_AS_16(pU); + vv = GET_LE_10BIT_AS_16(pU+2); + return evg_make_col_wide(0xFFFF, vy, vv, vu); +} +u32 get_pix_yuyv(EVG_Texture *_this, u32 x, u32 y) +{ + u8 *pY = _this->pixels + y * _this->stride + (x/2)*4; + u8 u = pY[1]; + u8 v = pY[3]; + u8 luma = (x%2) ? pY[2] : pY[0]; + return GF_COL_ARGB(0xFF, luma, u, v); +} +u32 get_pix_yvyu(EVG_Texture *_this, u32 x, u32 y) +{ + u8 *pY = _this->pixels + y * _this->stride + (x/2)*4; + u8 u = pY[3]; + u8 v = pY[1]; + u8 luma = (x%2) ? pY[2] : pY[0]; + return GF_COL_ARGB(0xFF, luma, u, v); +} +u32 get_pix_uyvy(EVG_Texture *_this, u32 x, u32 y) +{ + u8 *pY = _this->pixels + y * _this->stride + (x/2)*4; + u8 u = pY[0]; + u8 v = pY[2]; + u8 luma = (x%2) ? pY[3] : pY[1]; + return GF_COL_ARGB(0xFF, luma, u, v); +} +u32 get_pix_vyuy(EVG_Texture *_this, u32 x, u32 y) +{ + u8 *pY = _this->pixels + y * _this->stride + (x/2)*4; + u8 u = pY[2]; + u8 v = pY[0]; + u8 luma = (x%2) ? pY[3] : pY[1]; + return GF_COL_ARGB(0xFF, luma, u, v); +} + +static void texture_set_callbacks(EVG_Texture *_this) +{ + if (_this->tx_callback) + return; + switch (_this->pixel_format) { + case GF_PIXEL_RGBA: + _this->tx_get_pixel = get_pix_rgba; + return; + case GF_PIXEL_ARGB: + _this->tx_get_pixel = get_pix_argb; + return; + case GF_PIXEL_ABGR: + _this->tx_get_pixel = get_pix_abgr; + return; + case GF_PIXEL_BGRA: + _this->tx_get_pixel = get_pix_bgra; + return; + case GF_PIXEL_RGBX: + _this->tx_get_pixel = get_pix_rgbx; + return; + case GF_PIXEL_BGRX: + _this->tx_get_pixel = get_pix_bgrx; + return; + case GF_PIXEL_XRGB: + _this->tx_get_pixel = get_pix_xrgb; + return; + case GF_PIXEL_XBGR: + _this->tx_get_pixel = get_pix_xbgr; + return; + case GF_PIXEL_RGB: + _this->tx_get_pixel = get_pix_rgb_24; + return; + case GF_PIXEL_BGR: + _this->tx_get_pixel = get_pix_bgr_24; + return; + case GF_PIXEL_RGB_444: + _this->tx_get_pixel = get_pix_444; + return; + case GF_PIXEL_RGB_555: + _this->tx_get_pixel = get_pix_555; + return; + case GF_PIXEL_RGB_565: + _this->tx_get_pixel = get_pix_565; + return; + case GF_PIXEL_GREYSCALE: + _this->tx_get_pixel = get_pix_grey; + return; + case GF_PIXEL_ALPHAGREY: + _this->tx_get_pixel = get_pix_alphagrey; + return; + case GF_PIXEL_GREYALPHA: + _this->tx_get_pixel = get_pix_greyalpha; + return; + case GF_PIXEL_YUV: + _this->tx_get_pixel = get_pix_yuv420p; + break; + case GF_PIXEL_YUVA: + _this->tx_get_pixel = get_pix_yuv420p_a; + break; + case GF_PIXEL_YUV422: + _this->tx_get_pixel = get_pix_yuv422p; + break; + case GF_PIXEL_YUV444: + _this->tx_get_pixel = get_pix_yuv444p; + break; + case GF_PIXEL_YUVA444: + _this->tx_get_pixel = get_pix_yuv444p_a; + break; + case GF_PIXEL_NV12: + _this->tx_get_pixel = get_pix_yuv_nv12; + break; + case GF_PIXEL_NV21: + _this->tx_get_pixel = get_pix_yuv_nv21; + break; + case GF_PIXEL_YUYV: + _this->tx_get_pixel = get_pix_yuyv; + return; + case GF_PIXEL_YVYU: + _this->tx_get_pixel = get_pix_yvyu; + return; + case GF_PIXEL_UYVY: + _this->tx_get_pixel = get_pix_uyvy; + return; + case GF_PIXEL_VYUY: + _this->tx_get_pixel = get_pix_vyuy; + return; + case GF_PIXEL_YUV_10: + _this->tx_get_pixel = get_pix_yuv420p_10; + _this->tx_get_pixel_wide = get_pix_yuv420p_10_wide; + break; + case GF_PIXEL_YUV422_10: + _this->tx_get_pixel = get_pix_yuv422p_10; + _this->tx_get_pixel_wide = get_pix_yuv422p_10_wide; + break; + case GF_PIXEL_YUV444_10: + _this->tx_get_pixel = get_pix_yuv444p_10; + _this->tx_get_pixel_wide = get_pix_yuv444p_10_wide; + break; + case GF_PIXEL_NV12_10: + _this->tx_get_pixel = get_pix_yuv_nv12_10; + _this->tx_get_pixel_wide = get_pix_yuv_nv12_10_wide; + break; + case GF_PIXEL_NV21_10: + _this->tx_get_pixel = get_pix_yuv_nv21_10; + _this->tx_get_pixel_wide = get_pix_yuv_nv21_10_wide; + break; + default: + return; + } + //assign image planes + if (_this->pix_u) return; + + switch (_this->pixel_format) { + case GF_PIXEL_YUV_10: + case GF_PIXEL_YUV: + if (!_this->stride_uv) _this->stride_uv = _this->stride/2; + _this->pix_u = _this->pixels + _this->stride*_this->height; + _this->pix_v = _this->pix_u + _this->stride_uv * _this->height/2; + return; + case GF_PIXEL_YUVA: + if (!_this->stride_uv) _this->stride_uv = _this->stride/2; + _this->pix_u = _this->pixels + _this->stride*_this->height; + _this->pix_v = _this->pix_u + _this->stride_uv * _this->height/2; + _this->pix_a = _this->pix_v + _this->stride_uv * _this->height/2; + return; + case GF_PIXEL_YUV422_10: + case GF_PIXEL_YUV422: + if (!_this->stride_uv) _this->stride_uv = _this->stride/2; + _this->pix_u = _this->pixels + _this->stride*_this->height; + _this->pix_v = _this->pix_u + _this->stride_uv * _this->height; + return; + case GF_PIXEL_YUV444_10: + case GF_PIXEL_YUV444: + if (!_this->stride_uv) _this->stride_uv = _this->stride; + _this->pix_u = _this->pixels + _this->stride*_this->height; + _this->pix_v = _this->pix_u + _this->stride_uv * _this->height; + return; + case GF_PIXEL_YUVA444: + if (!_this->stride_uv) _this->stride_uv = _this->stride; + _this->pix_u = _this->pixels + _this->stride*_this->height; + _this->pix_v = _this->pix_u + _this->stride_uv * _this->height; + _this->pix_a = _this->pix_v + _this->stride_uv * _this->height; + return; + case GF_PIXEL_NV12: + case GF_PIXEL_NV21: + case GF_PIXEL_NV12_10: + case GF_PIXEL_NV21_10: + if (!_this->stride_uv) _this->stride_uv = _this->stride; + _this->pix_u = _this->pixels + _this->stride*_this->height; + return; + } +} + +static GF_Err gf_evg_stencil_set_texture_internal(GF_EVGStencil * st, u32 width, u32 height, GF_PixelFormat pixelFormat, const char *pixels, u32 stride, const char *u_plane, const char *v_plane, u32 uv_stride, const char *alpha_plane, u32 alpha_stride) +{ + EVG_Texture *_this = (EVG_Texture *) st; + if (!_this || (_this->type != GF_STENCIL_TEXTURE) || !pixels || !width || !height || _this->owns_texture) + return GF_BAD_PARAM; + + _this->pixels = NULL; + _this->is_yuv = GF_FALSE; + + switch (pixelFormat) { + case GF_PIXEL_ARGB: + case GF_PIXEL_RGBA: + case GF_PIXEL_ABGR: + case GF_PIXEL_BGRA: + case GF_PIXEL_RGBX: + case GF_PIXEL_XRGB: + case GF_PIXEL_XBGR: + case GF_PIXEL_BGRX: + _this->Bpp = 4; + break; + case GF_PIXEL_RGB: + case GF_PIXEL_BGR: + _this->Bpp = 3; + break; + case GF_PIXEL_RGB_555: + case GF_PIXEL_RGB_565: + case GF_PIXEL_RGB_444: + case GF_PIXEL_ALPHAGREY: + case GF_PIXEL_GREYALPHA: + _this->Bpp = 2; + break; + case GF_PIXEL_GREYSCALE: + _this->Bpp = 1; + break; + case GF_PIXEL_YUV: + case GF_PIXEL_NV12: + case GF_PIXEL_NV21: + case GF_PIXEL_YUV422: + case GF_PIXEL_YUV444: + case GF_PIXEL_YUVA: + case GF_PIXEL_YUVA444: + _this->is_yuv = GF_TRUE; + _this->Bpp = 1; + break; + case GF_PIXEL_YUV_10: + case GF_PIXEL_YUV422_10: + case GF_PIXEL_YUV444_10: + case GF_PIXEL_NV12_10: + case GF_PIXEL_NV21_10: + _this->is_yuv = GF_TRUE; + _this->Bpp = 2; + break; + case GF_PIXEL_YUYV: + case GF_PIXEL_YVYU: + case GF_PIXEL_UYVY: + case GF_PIXEL_VYUY: + _this->is_yuv = GF_TRUE; + _this->Bpp = 1; + if (!stride) + stride = 4 * width; + break; + default: + return GF_NOT_SUPPORTED; + } + if (!stride) + stride = _this->Bpp * width; + + _this->pixel_format = pixelFormat; + _this->width = width; + _this->height = height; + _this->stride = stride; + _this->stride_uv = uv_stride; + _this->stride_alpha = alpha_stride ? alpha_stride : stride; + _this->pixels = (char *) pixels; + _this->pix_u = (char *) u_plane; + _this->pix_v = (char *) v_plane; + texture_set_callbacks(_this); + return GF_OK; +} + +GF_EXPORT +GF_Err gf_evg_stencil_set_texture_planes(GF_EVGStencil *stencil, u32 width, u32 height, GF_PixelFormat pixelFormat, const u8 *y_or_rgb, u32 stride, const u8 *u_plane, const u8 *v_plane, u32 uv_stride, const u8 *alpha_plane, u32 stride_alpha) +{ + return gf_evg_stencil_set_texture_internal(stencil, width, height, pixelFormat, y_or_rgb, stride, u_plane, v_plane, uv_stride, alpha_plane, stride_alpha); +} +GF_EXPORT +GF_Err gf_evg_stencil_set_texture(GF_EVGStencil *stencil, u8 *pixels, u32 width, u32 height, u32 stride, GF_PixelFormat pixelFormat) +{ + return gf_evg_stencil_set_texture_internal(stencil, width, height, pixelFormat, pixels, stride, NULL, NULL, 0, NULL, 0); +} + +GF_EXPORT +GF_Err gf_evg_stencil_set_texture_parametric(GF_EVGStencil *stencil, u32 width, u32 height, GF_PixelFormat pixelFormat, gf_evg_texture_callback callback, void *cbk_data, Bool use_screen_coords) +{ + EVG_Texture *_this = (EVG_Texture *) stencil; + u8 data=0; + GF_Err e; + if (!callback) return GF_BAD_PARAM; + e = gf_evg_stencil_set_texture_internal(stencil, width, height, pixelFormat, &data, width, NULL, NULL, 0, NULL, 0); + if (e) return e; + _this->pixels = NULL; + _this->tx_get_pixel = evg_paramtx_get_pixel; + _this->tx_get_pixel_wide = evg_paramtx_get_pixel_wide; + + _this->tx_callback = callback; + _this->tx_callback_udta = cbk_data; + _this->tx_callback_screen_coords = use_screen_coords; + return GF_OK; +} + +void evg_texture_init(GF_EVGStencil *p, GF_EVGSurface *surf) +{ + GF_Point2D p0, p1; + EVG_Texture *_this = (EVG_Texture *) p; + + p0.x = p0.y = p1.y = 0; + p1.x = FIX_ONE; + gf_mx2d_apply_point(&_this->smat, &p0); + gf_mx2d_apply_point(&_this->smat, &p1); + _this->inc_x = p1.x - p0.x; + _this->inc_y = p1.y - p0.y; + + _this->replace_col = 0; + _this->cmat_is_replace = GF_FALSE; + if (!_this->cmat.identity + && !_this->cmat.m[0] && !_this->cmat.m[1] && !_this->cmat.m[2] && !_this->cmat.m[3] + && !_this->cmat.m[5] && !_this->cmat.m[6] && !_this->cmat.m[7] && !_this->cmat.m[8] + && !_this->cmat.m[10] && !_this->cmat.m[11] && !_this->cmat.m[12] && !_this->cmat.m[13] + && !_this->cmat.m[15] && !_this->cmat.m[16] && !_this->cmat.m[17] && !_this->cmat.m[19]) { + _this->cmat_is_replace = GF_TRUE; + _this->replace_col = GF_COL_ARGB(FIX2INT(_this->cmat.m[18]*255), FIX2INT(_this->cmat.m[4]*255), FIX2INT(_this->cmat.m[9]*255), FIX2INT(_this->cmat.m[14]*255)); + + if (surf->yuv_type) { + _this->replace_col = gf_evg_argb_to_ayuv(surf, _this->replace_col); + } + } + + if ((_this->alpha == 255) && !_this->smat.m[1] && !_this->smat.m[3] && (_this->cmat.identity || _this->cmat_is_replace)) { + if (surf->not_8bits) { + _this->fill_run = tex_fill_run_straight_wide; + } else { + _this->fill_run = tex_fill_run_straight; + } + } else { + if (!_this->cmat.identity && _this->is_yuv && surf->yuv_type) { + evg_make_ayuv_color_mx(&_this->cmat, &_this->yuv_cmat); + } + if (surf->not_8bits) { + _this->fill_run = tex_fill_run_wide; + } else { + _this->fill_run = tex_fill_run; + } + } + + texture_set_callbacks(_this); +} + + + +GF_EXPORT +GF_Err gf_evg_stencil_set_mapping(GF_EVGStencil * st, GF_TextureMapFlags mode) +{ + EVG_Texture *_this = (EVG_Texture *) st; + if (!_this || (_this->type != GF_STENCIL_TEXTURE)) return GF_BAD_PARAM; + _this->mod = mode; + return GF_OK; +} + +GF_EXPORT +GF_Err gf_evg_stencil_set_filter(GF_EVGStencil * st, GF_TextureFilter filter_mode) +{ + EVG_Texture *_this = (EVG_Texture *) st; + if (!_this || (_this->type != GF_STENCIL_TEXTURE)) return GF_BAD_PARAM; + _this->filter = filter_mode; + return GF_OK; +} + +GF_EXPORT +GF_Err gf_evg_stencil_set_color_matrix(GF_EVGStencil * st, GF_ColorMatrix *cmat) +{ + Bool is_grad; + GF_EVGStencil *_this = (GF_EVGStencil *)st; + if (!_this) return GF_BAD_PARAM; + is_grad = ((_this->type==GF_STENCIL_LINEAR_GRADIENT) || (_this->type==GF_STENCIL_RADIAL_GRADIENT)) ? GF_TRUE : GF_FALSE; + + + if (!cmat) { + if (is_grad && !_this->cmat.identity) + ((EVG_BaseGradient *) _this)->updated = 1; + gf_cmx_init(&_this->cmat); + } else { + if (is_grad && memcmp(&_this->cmat.m, &cmat->m, sizeof(Fixed)*20)) + ((EVG_BaseGradient *) _this)->updated = 1; + gf_cmx_copy(&_this->cmat, cmat); + } + return GF_OK; +} + +static u32 gf_evg_stencil_get_pixel_intern(GF_EVGStencil *st, s32 x, s32 y, Bool want_yuv) +{ + u32 col; + EVG_Texture *_this = (EVG_Texture *) st; + if (!_this || (_this->type != GF_STENCIL_TEXTURE) || !_this->tx_get_pixel) return 0; + if (x<0) x=0; + else if ((u32) x>=_this->width) x = _this->width-1; + + if (y<0) y=0; + else if ((u32) y>=_this->height) y = _this->height-1; + + col = _this->tx_get_pixel(_this, x, y); + if (_this->is_yuv) { + if (!want_yuv) return gf_evg_ayuv_to_argb(NULL, col); + } else { + if (want_yuv) return gf_evg_argb_to_ayuv(NULL, col); + } + return col; +} + +GF_EXPORT +u32 gf_evg_stencil_get_pixel(GF_EVGStencil *st, s32 x, s32 y) +{ + return gf_evg_stencil_get_pixel_intern(st, x, y, GF_FALSE); +} + +#if 0 //unused +GF_EXPORT +u32 gf_evg_stencil_get_pixel_yuv(GF_EVGStencil *st, s32 x, s32 y) +{ + return gf_evg_stencil_get_pixel_intern(st, x, y, GF_TRUE); +} +#endif + +static GF_Err gf_evg_stencil_get_pixel_f_intern(EVG_Texture *_this, Float x, Float y, Float *r, Float *g, Float *b, Float *a, Bool want_yuv) +{ + u32 col; + if (!_this->tx_get_pixel) return GF_BAD_PARAM; + + if (_this->mod & GF_TEXTURE_FLIP_X) x = -x; + if (_this->mod & GF_TEXTURE_FLIP_Y) y = -y; + + x*=_this->width; + y*=_this->height; + if (_this->mod & GF_TEXTURE_REPEAT_S) { + while (x<0) x += _this->width; + while (x>=_this->width) x -= _this->width; + } else { + if (x<0) x=0; + else if (x>=_this->width) x = (Float)_this->width-1; + } + + if (_this->mod & GF_TEXTURE_REPEAT_T) { + while (y<0) y += _this->height; + while (y>=_this->height) y -= _this->height; + } else { + if (y<0) y=0; + else if (y>=_this->height) y = (Float)_this->height-1; + } + + //10-bit or more texture, use wide and convert to float + if (_this->tx_get_pixel_wide) { + u64 colw; + if (_this->filter==GF_TEXTURE_FILTER_HIGH_SPEED) { + colw = _this->tx_get_pixel_wide(_this, (s32) x, (s32) y); + } else { + u32 _x = (u32) floor(x); + u32 _y = (u32) floor(y); + if (_this->filter==GF_TEXTURE_FILTER_MID) { + if ((x - _x > 0.5) && _x+1<_this->width) _x++; + if ((y - _y > 0.5) && _y+1<_this->height) _y++; + colw = _this->tx_get_pixel_wide(_this, _x, _y); + } else { + u64 col01, col11, col10; + s32 _x1 = _x+1; + s32 _y1 = _y+1; + u8 diff_x = (u8) (255 * (x - _x)); + u8 diff_y = (u8) (255 * (y - _y)); + + if ((u32)_x1>=_this->width) _x1 = _this->width-1; + if ((u32)_y1>=_this->height) _y1 = _this->height-1; + colw = _this->tx_get_pixel_wide(_this, _x, _y); + col10 = _this->tx_get_pixel_wide(_this, _x1, _y); + col01 = _this->tx_get_pixel_wide(_this, _x, _y1); + col11 = _this->tx_get_pixel_wide(_this, _x1, _y1); + colw = EVG_LERP_WIDE(colw, col10, diff_x); + col11 = EVG_LERP_WIDE(col01, col11, diff_x); + colw = EVG_LERP_WIDE(colw, col11, diff_y); + } + } + if (_this->is_yuv) { + if (!want_yuv) colw = gf_evg_ayuv_to_argb_wide(NULL, colw); + } else { + if (want_yuv) colw = gf_evg_argb_to_ayuv_wide(NULL, colw); + } + + *r = ((Float) GF_COLW_R(colw) ) / 0xFFFF; + *g = ((Float) GF_COLW_G(colw) ) / 0xFFFF; + *b = ((Float) GF_COLW_B(colw) ) / 0xFFFF; + *a = ((Float) GF_COLW_A(colw) ) / 0xFFFF; + return GF_OK; + } + + //8-bit texture, use regular and convert to float + if (_this->filter==GF_TEXTURE_FILTER_HIGH_SPEED) { + col = _this->tx_get_pixel(_this, (s32) x, (s32) y); + } else { + u32 _x = (u32) floor(x); + u32 _y = (u32) floor(y); + if (_this->filter==GF_TEXTURE_FILTER_MID) { + if ((x - _x > 0.5) && _x+1<_this->width) _x++; + if ((y - _y > 0.5) && _y+1<_this->height) _y++; + col = _this->tx_get_pixel(_this, _x, _y); + } else { + u32 col01, col11, col10; + s32 _x1 = _x+1; + s32 _y1 = _y+1; + u8 diff_x = (u8) (255 * (x - _x)); + u8 diff_y = (u8) (255 * (y - _y)); + + if ((u32)_x1>=_this->width) _x1 = _this->width-1; + if ((u32)_y1>=_this->height) _y1 = _this->height-1; + col = _this->tx_get_pixel(_this, _x, _y); + col10 = _this->tx_get_pixel(_this, _x1, _y); + col01 = _this->tx_get_pixel(_this, _x, _y1); + col11 = _this->tx_get_pixel(_this, _x1, _y1); + col = EVG_LERP(col, col10, diff_x); + col11 = EVG_LERP(col01, col11, diff_x); + col = EVG_LERP(col, col11, diff_y); + } + } + if (_this->is_yuv) { + if (!want_yuv) col = gf_evg_ayuv_to_argb(NULL, col); + } else { + if (want_yuv) col = gf_evg_argb_to_ayuv(NULL, col); + } + *r = ((Float) GF_COL_R(col) ) / 255.0f; + *g = ((Float) GF_COL_G(col) ) / 255.0f; + *b = ((Float) GF_COL_B(col) ) / 255.0f; + *a = ((Float) GF_COL_A(col) ) / 255.0f; + return GF_OK; +} + +GF_EXPORT +GF_Err gf_evg_stencil_get_pixel_f(GF_EVGStencil *st, Float x, Float y, Float *r, Float *g, Float *b, Float *a) +{ + EVG_Texture *_this = (EVG_Texture *) st; + if (!_this || (_this->type != GF_STENCIL_TEXTURE)) return GF_BAD_PARAM; + return gf_evg_stencil_get_pixel_f_intern(_this, x, y, r, g, b, a, GF_FALSE); +} + +GF_EXPORT +GF_Err gf_evg_stencil_get_pixel_yuv_f(GF_EVGStencil *st, Float x, Float y, Float *r, Float *g, Float *b, Float *a) +{ + EVG_Texture *_this = (EVG_Texture *) st; + if (!_this || (_this->type != GF_STENCIL_TEXTURE)) return GF_BAD_PARAM; + return gf_evg_stencil_get_pixel_f_intern(_this, x, y, r, g, b, a, GF_TRUE); +} + +GF_EXPORT +GF_Err gf_evg_stencil_set_alpha(GF_EVGStencil * st, u8 alpha) +{ + EVG_Texture *_this = (EVG_Texture *)st; + if (!_this) return GF_BAD_PARAM; + if (_this->type==GF_STENCIL_SOLID) return GF_BAD_PARAM; + if (_this->type==GF_STENCIL_TEXTURE) + _this->alpha = alpha; + else { + if ( ((EVG_BaseGradient*)st)->alpha != alpha) { + ((EVG_BaseGradient*)st)->updated = 1; + } + ((EVG_BaseGradient*)st)->alpha = alpha; + } + return GF_OK; +} + +void evg_fill_run(GF_EVGStencil *p, GF_EVGSurface *surf, s32 x, s32 y, u32 count) +{ + p->fill_run(p, surf, x, y, count); + if (surf->get_alpha) { + u32 i; + EVG_Texture *_p = (EVG_Texture *)p; + if (_p->Bpp>8) { + u64 *coll = (u64 *)surf->stencil_pix_run; + for (i=0; i>48)&0xFFFF; + a = 0xFF * surf->get_alpha(surf->get_alpha_udta, (u8) (a/0xFF), x+i, y); + *coll = (a<<48) | ((*coll) & 0x0000FFFFFFFFFFFFUL); + coll ++; + } + } else { + u32 *col = (u32 *)surf->stencil_pix_run; + for (i=0; iget_alpha(surf->get_alpha_udta, a, x+i, y); + *col = (a<<24) | ((*col) & 0x00FFFFFF); + col ++; + } + } + } +} diff --git a/src/evg/surface.c b/src/evg/surface.c new file mode 100644 index 0000000..2c777e3 --- /dev/null +++ b/src/evg/surface.c @@ -0,0 +1,1111 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / software 2D rasterizer module + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + */ + + +#include "rast_soft.h" + +static void get_surface_world_matrix(GF_EVGSurface *surf, GF_Matrix2D *mat) +{ + gf_mx2d_init(*mat); + if (surf->center_coords) { + gf_mx2d_add_scale(mat, FIX_ONE, -FIX_ONE); + gf_mx2d_add_translation(mat, INT2FIX(surf->width / 2), INT2FIX(surf->height / 2)); + } +} + +GF_EXPORT +GF_EVGSurface *gf_evg_surface_new(Bool center_coords) +{ + GF_EVGSurface *surf; + GF_SAFEALLOC(surf, GF_EVGSurface); + if (surf) { + surf->center_coords = center_coords; + surf->texture_filter = GF_TEXTURE_FILTER_DEFAULT; + surf->raster = evg_raster_new(); + surf->yuv_prof=1; + } + return surf; +} + +EVG_Surface3DExt *evg_init_3d_surface(GF_EVGSurface *surf); + +GF_EXPORT +GF_EVGSurface *gf_evg_surface3d_new() +{ + GF_EVGSurface *surf; + GF_SAFEALLOC(surf, GF_EVGSurface); + if (surf) { + surf->texture_filter = GF_TEXTURE_FILTER_DEFAULT; + surf->raster = evg_raster_new(); + surf->yuv_prof=1; + + surf->ext3d = evg_init_3d_surface(surf); + if (!surf->ext3d) { + gf_free(surf); + return NULL; + } + } + return surf; +} + +GF_EXPORT +void gf_evg_surface_set_center_coords(GF_EVGSurface *surf, Bool center_coords) +{ + if (surf) surf->center_coords = center_coords; +} + +GF_EXPORT +void gf_evg_surface_delete(GF_EVGSurface *surf) +{ + if (!surf) + return; + if (surf->stencil_pix_run) gf_free(surf->stencil_pix_run); + surf->stencil_pix_run = NULL; + if (surf->raster) evg_raster_del(surf->raster); + surf->raster = NULL; + if (surf->uv_alpha) gf_free(surf->uv_alpha); + + if (surf->ext3d) { + if (surf->ext3d->pix_vals) + gf_free(surf->ext3d->pix_vals); + gf_free(surf->ext3d); + } + gf_free(surf); +} + +GF_EXPORT +GF_Err gf_evg_surface_set_matrix(GF_EVGSurface *surf, GF_Matrix2D *mat) +{ + GF_Matrix2D tmp; + if (!surf) return GF_BAD_PARAM; + get_surface_world_matrix(surf, &surf->mat); + surf->is_3d_matrix = GF_FALSE; + if (!mat) return GF_OK; + + gf_mx2d_init(tmp); + gf_mx2d_add_matrix(&tmp, mat); + gf_mx2d_add_matrix(&tmp, &surf->mat); + gf_mx2d_copy(surf->mat, tmp); + return GF_OK; +} + +GF_EXPORT +GF_Err gf_evg_surface_set_matrix_3d(GF_EVGSurface *surf, GF_Matrix *mat) +{ + if (!surf) return GF_BAD_PARAM; + get_surface_world_matrix(surf, &surf->mat); + surf->is_3d_matrix = GF_FALSE; + if (!mat) return GF_OK; + gf_mx_copy(surf->mx3d, *mat); + surf->is_3d_matrix = GF_TRUE; + return GF_OK; +} + +static void evg_surface_set_components_idx(GF_EVGSurface *surf) +{ + surf->idx_y1=surf->idx_u=surf->idx_v=0; + surf->idx_a=surf->idx_r=surf->idx_g=surf->idx_b=0; + switch (surf->pixelFormat) { + case GF_PIXEL_YUYV: + surf->idx_y1=0; + surf->idx_u=1; + surf->idx_v=3; + break; + case GF_PIXEL_YVYU: + surf->idx_y1=0; + surf->idx_u=3; + surf->idx_v=1; + break; + case GF_PIXEL_UYVY: + surf->idx_y1=1; + surf->idx_u=0; + surf->idx_v=2; + break; + case GF_PIXEL_VYUY: + surf->idx_y1=1; + surf->idx_u=2; + surf->idx_v=0; + break; + case GF_PIXEL_ALPHAGREY: + surf->idx_a = 0; + surf->idx_g = 1; + break; + case GF_PIXEL_GREYALPHA: + surf->idx_a = 1; + surf->idx_g = 0; + break; + case GF_PIXEL_ARGB: + surf->idx_a=0; + surf->idx_r=1; + surf->idx_g=2; + surf->idx_b=3; + break; + case GF_PIXEL_RGBA: + surf->idx_a=3; + surf->idx_r=0; + surf->idx_g=1; + surf->idx_b=2; + break; + case GF_PIXEL_BGRA: + surf->idx_a=3; + surf->idx_r=2; + surf->idx_g=1; + surf->idx_b=0; + break; + case GF_PIXEL_ABGR: + surf->idx_a=0; + surf->idx_r=3; + surf->idx_g=2; + surf->idx_b=1; + break; + case GF_PIXEL_RGBX: + surf->idx_r=0; + surf->idx_g=1; + surf->idx_b=2; + surf->idx_a=3; + break; + case GF_PIXEL_BGRX: + surf->idx_r=2; + surf->idx_g=1; + surf->idx_b=0; + surf->idx_a=3; + break; + case GF_PIXEL_XRGB: + surf->idx_a=0; + surf->idx_r=1; + surf->idx_g=2; + surf->idx_b=3; + break; + case GF_PIXEL_XBGR: + surf->idx_a=0; + surf->idx_r=3; + surf->idx_g=2; + surf->idx_b=1; + break; + case GF_PIXEL_RGB: + surf->idx_r=0; + surf->idx_g=1; + surf->idx_b=2; + break; + case GF_PIXEL_BGR: + surf->idx_r=2; + surf->idx_g=1; + surf->idx_b=0; + break; + } +} + +GF_Err evg_3d_resize(GF_EVGSurface *surf); + + +GF_EXPORT +GF_Err gf_evg_surface_attach_to_buffer(GF_EVGSurface *surf, u8 *pixels, u32 width, u32 height, s32 pitch_x, s32 pitch_y, GF_PixelFormat pixelFormat) +{ + u32 BPP; + Bool size_changed=GF_FALSE; + if (!surf || !pixels) return GF_BAD_PARAM; + + surf->is_transparent = GF_FALSE; + surf->not_8bits = GF_FALSE; + switch (pixelFormat) { + case GF_PIXEL_GREYSCALE: + BPP = 1; + break; + case GF_PIXEL_ALPHAGREY: + case GF_PIXEL_GREYALPHA: + BPP = 2; + surf->is_transparent = GF_TRUE; + break; + case GF_PIXEL_RGB_444: + case GF_PIXEL_RGB_555: + case GF_PIXEL_RGB_565: + BPP = 2; + break; + case GF_PIXEL_ARGB: + case GF_PIXEL_BGRA: + case GF_PIXEL_RGBA: + case GF_PIXEL_ABGR: + surf->is_transparent = GF_TRUE; + case GF_PIXEL_RGBX: + case GF_PIXEL_BGRX: + case GF_PIXEL_XRGB: + case GF_PIXEL_XBGR: + BPP = 4; + break; + case GF_PIXEL_BGR: + case GF_PIXEL_RGB: + BPP = 3; + break; + case GF_PIXEL_YUV: + case GF_PIXEL_NV12: + case GF_PIXEL_NV21: + case GF_PIXEL_YUV422: + case GF_PIXEL_YUV444: + case GF_PIXEL_YUYV: + case GF_PIXEL_YVYU: + case GF_PIXEL_UYVY: + case GF_PIXEL_VYUY: + BPP = 1; + break; + case GF_PIXEL_YUV_10: + case GF_PIXEL_NV12_10: + case GF_PIXEL_NV21_10: + case GF_PIXEL_YUV422_10: + case GF_PIXEL_YUV444_10: + BPP = 2; + surf->not_8bits = GF_TRUE; + break; + default: + return GF_NOT_SUPPORTED; + } + if (!pitch_x) pitch_x = BPP; + surf->pitch_x = pitch_x; + surf->pitch_y = pitch_y; + if (!surf->stencil_pix_run || (surf->width != width)) { + u32 run_size = sizeof(u32) * (width+2); + if (surf->not_8bits) run_size *= 2; + if (surf->stencil_pix_run) gf_free(surf->stencil_pix_run); + surf->stencil_pix_run = (u32 *) gf_malloc(run_size); + } + if (surf->width != width) { + surf->width = width; + size_changed = GF_TRUE; + } + if (surf->height != height) { + surf->height = height; + size_changed = GF_TRUE; + } + surf->pixels = (char*)pixels; + surf->pixelFormat = pixelFormat; + surf->BPP = BPP; + evg_surface_set_components_idx(surf); + gf_evg_surface_set_matrix(surf, NULL); + if (surf->ext3d && size_changed) + evg_3d_resize(surf); + return GF_OK; +} + + +GF_EXPORT +GF_Err gf_evg_surface_attach_to_texture(GF_EVGSurface *surf, GF_EVGStencil * sten) +{ + EVG_Texture *tx = (EVG_Texture *) sten; + if (!surf || (tx->type != GF_STENCIL_TEXTURE)) return GF_BAD_PARAM; + + return gf_evg_surface_attach_to_buffer(surf, tx->pixels, tx->width, tx->height, 0, tx->stride, tx->pixel_format); +} + + +GF_EXPORT +GF_Err gf_evg_surface_clear(GF_EVGSurface *surf, GF_IRect *rc, u32 color) +{ + GF_IRect clear; + if (!surf) return GF_BAD_PARAM; + + if (rc) { + s32 _x, _y; + if (surf->center_coords) { + _x = rc->x + surf->width / 2; + _y = surf->height / 2 - rc->y; + } else { + _x = rc->x; + _y = rc->y - rc->height; + } + /*clip is outside our bounds, ignore*/ + if ((_x>=(s32) surf->width) || (_x + rc->width < 0)) + return GF_OK; + if ((_y>= (s32) surf->height) || (_y + rc->height < 0)) + return GF_OK; + + clear.width = (u32) rc->width; + if (_x>=0) { + clear.x = (u32) _x; + } else { + if ( (s32) clear.width + _x < 0) return GF_BAD_PARAM; + clear.width += _x; + clear.x = 0; + } + if (clear.x + clear.width > (s32) surf->width) + clear.width = surf->width - clear.x; + + if (!clear.width) + return GF_OK; + + clear.height = (u32) rc->height; + if (_y>=0) { + clear.y = _y; + } else { + if ( (s32) clear.height + _y < 0) return GF_BAD_PARAM; + clear.height += _y; + clear.y = 0; + } + if (clear.y + clear.height > (s32) surf->height) + clear.height = surf->height - clear.y; + + if (!clear.height) + return GF_OK; + } else { + clear.x = clear.y = 0; + clear.width = surf->width; + clear.height = surf->height; + } + + switch (surf->pixelFormat) { + /*RGB formats*/ + case GF_PIXEL_GREYSCALE: + return evg_surface_clear_grey(surf, clear, color); + case GF_PIXEL_ALPHAGREY: + case GF_PIXEL_GREYALPHA: + return evg_surface_clear_alphagrey(surf, clear, color); + case GF_PIXEL_ARGB: + case GF_PIXEL_BGRA: + case GF_PIXEL_RGBA: + case GF_PIXEL_ABGR: + return evg_surface_clear_argb(surf, clear, color); + + case GF_PIXEL_RGBX: + case GF_PIXEL_BGRX: + case GF_PIXEL_XRGB: + case GF_PIXEL_XBGR: + return evg_surface_clear_rgbx(surf, clear, color); + + case GF_PIXEL_RGB: + case GF_PIXEL_BGR: + return evg_surface_clear_rgb(surf, clear, color); + case GF_PIXEL_RGB_444: + return evg_surface_clear_444(surf, clear, color); + case GF_PIXEL_RGB_555: + return evg_surface_clear_555(surf, clear, color); + case GF_PIXEL_RGB_565: + return evg_surface_clear_565(surf, clear, color); + + /*YUV formats*/ + case GF_PIXEL_YUV: + return evg_surface_clear_yuv420p(surf, clear, color); + case GF_PIXEL_YUV422: + return evg_surface_clear_yuv422p(surf, clear, color); + case GF_PIXEL_NV12: + return evg_surface_clear_nv12(surf, clear, color, GF_FALSE); + case GF_PIXEL_NV21: + return evg_surface_clear_nv12(surf, clear, color, GF_TRUE); + case GF_PIXEL_YUV444: + return evg_surface_clear_yuv444p(surf, clear, color); + case GF_PIXEL_YUYV: + case GF_PIXEL_YVYU: + case GF_PIXEL_UYVY: + case GF_PIXEL_VYUY: + return evg_surface_clear_yuyv(surf, clear, color); + case GF_PIXEL_YUV_10: + return evg_surface_clear_yuv420p_10(surf, clear, color); + case GF_PIXEL_NV12_10: + return evg_surface_clear_nv12_10(surf, clear, color, GF_FALSE); + case GF_PIXEL_NV21_10: + return evg_surface_clear_nv12_10(surf, clear, color, GF_TRUE); + case GF_PIXEL_YUV422_10: + return evg_surface_clear_yuv422p_10(surf, clear, color); + case GF_PIXEL_YUV444_10: + return evg_surface_clear_yuv444p_10(surf, clear, color); + default: + return GF_BAD_PARAM; + } +} + +GF_EXPORT +GF_Err gf_evg_surface_set_raster_level(GF_EVGSurface *surf, GF_RasterQuality RasterSetting) +{ + if (!surf) return GF_BAD_PARAM; + switch (RasterSetting) { + case GF_RASTER_HIGH_QUALITY: + surf->texture_filter = GF_TEXTURE_FILTER_HIGH_QUALITY; + break; + case GF_RASTER_MID: + surf->texture_filter = GF_TEXTURE_FILTER_HIGH_QUALITY; + break; + case GF_RASTER_HIGH_SPEED: + default: + surf->texture_filter = GF_TEXTURE_FILTER_HIGH_SPEED; + break; + } + return GF_OK; +} + + +GF_EXPORT +GF_Err gf_evg_surface_set_clipper(GF_EVGSurface *surf , GF_IRect *rc) +{ + if (!surf) return GF_BAD_PARAM; + if (rc) { + surf->clipper = *rc; + surf->useClipper = 1; + /*clipper was given in BIFS like coords, we work with bottom-min for rect, (0,0) top-left of surface*/ + if (surf->center_coords) { + surf->clipper.x += surf->width / 2; + surf->clipper.y = surf->height / 2 - rc->y; + } else { + surf->clipper.y -= rc->height; + } + + if (surf->clipper.x <=0) { + if (surf->clipper.x + (s32) surf->clipper.width < 0) return GF_BAD_PARAM; + surf->clipper.width += surf->clipper.x; + surf->clipper.x = 0; + } + if (surf->clipper.y <=0) { + if (surf->clipper.y + (s32) surf->clipper.height < 0) return GF_BAD_PARAM; + surf->clipper.height += surf->clipper.y; + surf->clipper.y = 0; + } + if (surf->clipper.x + surf->clipper.width > (s32) surf->width) { + surf->clipper.width = surf->width - surf->clipper.x; + } + if (surf->clipper.y + surf->clipper.height > (s32) surf->height) { + surf->clipper.height = surf->height - surf->clipper.y; + } + } else { + surf->useClipper = 0; + } + return GF_OK; +} + + +static Bool setup_grey_callback(GF_EVGSurface *surf, Bool for_3d) +{ + u32 a, uv_alpha_size=0; + Bool use_const = GF_TRUE; + + //in 3D mode, we only write one pixel at a time except in YUV modes + if (for_3d) { + a = 1; + use_const = GF_TRUE; + } + else if (surf->sten->type == GF_STENCIL_SOLID) { + surf->fill_col = ((EVG_Brush *)surf->sten)->color; + a = GF_COL_A(surf->fill_col); + } else { + a = 0; + use_const = GF_FALSE; + } + + if (use_const && !a && !surf->is_transparent) return GF_FALSE; + + surf->is_422 = GF_FALSE; + surf->yuv_type = 0; + surf->swap_uv = GF_FALSE; + surf->yuv_flush_uv = NULL; + + if (surf->comp_mode) a=100; + else if (surf->get_alpha) a=100; + surf->fill_single = NULL; + surf->fill_single_a = NULL; + + switch (surf->pixelFormat) { + case GF_PIXEL_GREYSCALE: + if (use_const) { + if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_grey_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_grey_fill_const; + } + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_grey_fill_var; + } + if (surf->ext3d) { + surf->fill_single = evg_grey_fill_single; + surf->fill_single_a = evg_grey_fill_single_a; + } + break; + case GF_PIXEL_ALPHAGREY: + case GF_PIXEL_GREYALPHA: + if (use_const) { + if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_alphagrey_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_alphagrey_fill_const; + } + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_alphagrey_fill_var; + } + if (surf->ext3d) { + surf->fill_single = evg_alphagrey_fill_single; + surf->fill_single_a = evg_alphagrey_fill_single_a; + } + break; + case GF_PIXEL_ARGB: + case GF_PIXEL_RGBA: + case GF_PIXEL_BGRA: + case GF_PIXEL_ABGR: + if (use_const) { + if (!a) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_argb_fill_erase; + } else if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_argb_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_argb_fill_const; + } + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_argb_fill_var; + } + if (surf->ext3d) { + surf->fill_single = evg_argb_fill_single; + surf->fill_single_a = evg_argb_fill_single_a; + } + break; + + case GF_PIXEL_RGBX: + case GF_PIXEL_BGRX: + case GF_PIXEL_XRGB: + case GF_PIXEL_XBGR: + if (use_const) { + if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_rgbx_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_rgbx_fill_const; + } + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_rgbx_fill_var; + } + if (surf->ext3d) { + surf->fill_single = evg_rgbx_fill_single; + surf->fill_single_a = evg_rgbx_fill_single_a; + } + break; + + case GF_PIXEL_RGB: + case GF_PIXEL_BGR: + if (use_const) { + if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_rgb_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_rgb_fill_const; + } + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_rgb_fill_var; + } + if (surf->ext3d) { + surf->fill_single = evg_rgb_fill_single; + surf->fill_single_a = evg_rgb_fill_single_a; + } + break; + + case GF_PIXEL_RGB_565: + if (use_const) { + if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_565_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_565_fill_const; + } + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_565_fill_var; + assert(surf->sten->fill_run); + } + if (surf->ext3d) { + surf->fill_single = evg_565_fill_single; + surf->fill_single_a = evg_565_fill_single_a; + } + break; + case GF_PIXEL_RGB_444: + if (use_const) { + if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_444_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_444_fill_const; + } + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_444_fill_var; + } + if (surf->ext3d) { + surf->fill_single = evg_444_fill_single; + surf->fill_single_a = evg_444_fill_single_a; + } + break; + case GF_PIXEL_RGB_555: + if (use_const) { + if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_555_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_555_fill_const; + } + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_555_fill_var; + } + if (surf->ext3d) { + surf->fill_single = evg_555_fill_single; + surf->fill_single_a = evg_555_fill_single_a; + } + break; + case GF_PIXEL_YUV: + surf->yuv_type = EVG_YUV; + if (use_const && !for_3d) { + surf->yuv_flush_uv = evg_yuv420p_flush_uv_const; + + if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_fill_const; + } + uv_alpha_size=2*surf->width; + } else { + surf->yuv_flush_uv = evg_yuv420p_flush_uv_var; + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_fill_var; + uv_alpha_size = 2 * 3*surf->width; + if (for_3d) + surf->sten = &surf->ext3d->yuv_sten; + } + break; + case GF_PIXEL_NV21: + surf->swap_uv = GF_TRUE; + case GF_PIXEL_NV12: + surf->yuv_type = EVG_YUV; + + if (surf->swap_uv) { + surf->idx_u = 1; + surf->idx_v = 0; + } else { + surf->idx_u = 0; + surf->idx_v = 1; + } + + if (use_const && !for_3d) { + surf->yuv_flush_uv = evg_nv12_flush_uv_const; + + if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_fill_const; + } + uv_alpha_size = 2*surf->width; + } else { + surf->yuv_flush_uv = evg_nv12_flush_uv_var; + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_fill_var; + uv_alpha_size = 2 * 3*surf->width; + } + break; + case GF_PIXEL_YUV422: + surf->yuv_type = EVG_YUV; + surf->is_422 = GF_TRUE; + if (use_const && !for_3d) { + surf->yuv_flush_uv = evg_yuv422p_flush_uv_const; + + if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_fill_const; + } + uv_alpha_size = 2*surf->width; + } else { + surf->yuv_flush_uv = evg_yuv422p_flush_uv_var; + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_fill_var; + uv_alpha_size = 2 * 3*surf->width; + } + break; + case GF_PIXEL_YUV444: + surf->yuv_type = EVG_YUV_444; + if (use_const) { + if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv444p_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv444p_fill_const; + } + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv444p_fill_var; + } + break; + case GF_PIXEL_YUYV: + case GF_PIXEL_YVYU: + case GF_PIXEL_UYVY: + case GF_PIXEL_VYUY: + surf->yuv_type = EVG_YUV; + if (use_const && !for_3d) { + if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuyv_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuyv_fill_const; + } + uv_alpha_size = 2*surf->width; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuyv_fill_var; + uv_alpha_size = 2 * 3*surf->width; + } + break; + + case GF_PIXEL_YUV_10: + surf->yuv_type = EVG_YUV; + if (use_const && !for_3d) { + surf->yuv_flush_uv = evg_yuv420p_10_flush_uv_const; + + if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_10_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_10_fill_const; + } + uv_alpha_size = 4*surf->width; + } else { + surf->yuv_flush_uv = evg_yuv420p_10_flush_uv_var; + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_10_fill_var; + uv_alpha_size = 4*3*surf->width; + } + break; + case GF_PIXEL_NV21_10: + surf->swap_uv = GF_TRUE; + case GF_PIXEL_NV12_10: + surf->yuv_type = EVG_YUV; + + if (surf->swap_uv) { + surf->idx_u = 1; + surf->idx_v = 0; + } else { + surf->idx_u = 0; + surf->idx_v = 1; + } + + if (use_const && !for_3d) { + surf->yuv_flush_uv = evg_nv12_10_flush_uv_const; + + if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_10_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_10_fill_const; + } + uv_alpha_size = 4*surf->width; + } else { + surf->yuv_flush_uv = evg_nv12_10_flush_uv_var; + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_10_fill_var; + uv_alpha_size = 4 * 3*surf->width; + } + break; + case GF_PIXEL_YUV422_10: + surf->yuv_type = EVG_YUV; + surf->is_422 = GF_TRUE; + if (use_const && !for_3d) { + surf->yuv_flush_uv = evg_yuv422p_10_flush_uv_const; + + if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_10_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_10_fill_const; + } + uv_alpha_size = 4*surf->width; + } else { + surf->yuv_flush_uv = evg_yuv422p_10_flush_uv_var; + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv420p_10_fill_var; + uv_alpha_size = 4 * 3*surf->width; + } + break; + case GF_PIXEL_YUV444_10: + surf->yuv_type = EVG_YUV_444; + if (use_const) { + if (a!=0xFF) { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv444p_10_fill_const_a; + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv444p_10_fill_const; + } + } else { + surf->gray_spans = (EVG_Raster_Span_Func) evg_yuv444p_10_fill_var; + } + break; + default: + return GF_FALSE; + } + + if (uv_alpha_size) { + if (surf->uv_alpha_alloc < uv_alpha_size) { + surf->uv_alpha_alloc = uv_alpha_size; + surf->uv_alpha = gf_realloc(surf->uv_alpha, uv_alpha_size); + memset(surf->uv_alpha, 0, uv_alpha_size); + } + memset(surf->uv_alpha, 0, sizeof(char)*surf->uv_alpha_alloc); + } + if (surf->yuv_type && for_3d) + surf->sten = &surf->ext3d->yuv_sten; + + if (use_const && surf->yuv_type) { + u8 y, cb, cr; + gf_evg_rgb_to_yuv(surf, surf->fill_col, &y, &cb, &cr); + if (surf->swap_uv) { + u8 t = cb; + cb = cr; + cr = (u8) t; + surf->swap_uv = GF_FALSE; + } + + surf->fill_col = GF_COL_ARGB(a, y, cb, cr); + surf->fill_col_wide = evg_col_to_wide(surf->fill_col); + } + + return GF_TRUE; +} + + +GF_EXPORT +GF_Err gf_evg_surface_set_path(GF_EVGSurface *surf, GF_Path *gp) +{ + if (!surf) return GF_BAD_PARAM; + if (!gp || !gp->n_points) { + surf->ftoutline.n_points = 0; + surf->ftoutline.n_contours = 0; + return GF_OK; + } + gf_path_flatten(gp); + surf->ftoutline.n_points = gp->n_points; + surf->ftoutline.n_contours = gp->n_contours; + + surf->ftoutline.tags = gp->tags; + surf->ftoutline.contours = (s32*) gp->contours; + + /*store path bounds for gradient/textures*/ + gf_path_get_bounds(gp, &surf->path_bounds); + /*invert Y (ft uses min Y)*/ + surf->path_bounds.y -= surf->path_bounds.height; + + surf->ftoutline.flags = 0; + if (gp->flags & GF_PATH_FILL_ZERO_NONZERO) surf->ftoutline.flags = GF_PATH_FILL_ZERO_NONZERO; + + surf->ftoutline.n_points = gp->n_points; + surf->ftoutline.points = gp->points; + surf->mx = &surf->mat; + return GF_OK; +} + +/* static void gray_spans_stub(s32 y, s32 count, EVG_Span *spans, GF_EVGSurface *surf){} */ + +GF_EXPORT +GF_Err gf_evg_surface_fill(GF_EVGSurface *surf, GF_EVGStencil *sten) +{ + GF_Err e; + GF_Rect rc; + u32 max_gray; + GF_Matrix2D mat, st_mat; + Bool restore_filter; + if (!surf || !sten) return GF_BAD_PARAM; + if (!surf->ftoutline.n_points) return GF_OK; + surf->sten = sten; + + /*setup ft raster calllbacks*/ + if (!setup_grey_callback(surf, GF_FALSE)) return GF_OK; + + /* surf->gray_spans = gray_spans_stub; */ + + /*TODO, check matrix with 3D transform*/ + + get_surface_world_matrix(surf, &mat); + restore_filter = GF_FALSE; + /*get path frame for texture convertion */ + if (sten->type != GF_STENCIL_SOLID) { + rc = surf->path_bounds; + gf_mx2d_apply_rect(&mat, &rc); + rc.x = rc.y = 0; + /*assign target frame and matrix*/ + sten->frame = rc; + gf_mx2d_copy(sten->pmat, surf->mat); + gf_mx2d_inverse(&sten->pmat); + + gf_mx2d_copy(st_mat, sten->smat); + gf_mx2d_init(sten->smat); + + switch (sten->type) { + case GF_STENCIL_TEXTURE: + if ( ((EVG_Texture *)sten)->tx_callback) { + + } else { + if (! ((EVG_Texture *)sten)->pixels) return GF_BAD_PARAM; + } + + if (((EVG_Texture *)sten)->mod & GF_TEXTURE_FLIP_Y) { + if (!surf->center_coords) gf_mx2d_add_scale(&sten->smat, FIX_ONE, -FIX_ONE); + } else { + if (surf->center_coords) gf_mx2d_add_scale(&sten->smat, FIX_ONE, -FIX_ONE); + } + gf_mx2d_add_matrix(&sten->smat, &st_mat); + gf_mx2d_add_matrix(&sten->smat, &mat); + gf_mx2d_inverse(&sten->smat); + + evg_texture_init(sten, surf); + if (((EVG_Texture *)sten)->filter == GF_TEXTURE_FILTER_DEFAULT) { + restore_filter = GF_TRUE; + ((EVG_Texture *)sten)->filter = surf->texture_filter; + } + + break; + case GF_STENCIL_LINEAR_GRADIENT: + { + EVG_LinearGradient *lin = (EVG_LinearGradient *)sten; + gf_mx2d_add_matrix(&sten->smat, &st_mat); + gf_mx2d_add_matrix(&sten->smat, &mat); + gf_mx2d_inverse(&sten->smat); + /*and finalize matrix in gradient coord system*/ + gf_mx2d_add_matrix(&sten->smat, &lin->vecmat); + gf_mx2d_add_scale(&sten->smat, INT2FIX(1<smat, st_mat); + gf_mx2d_add_matrix(&sten->smat, &mat); + gf_mx2d_inverse(&sten->smat); + gf_mx2d_add_translation(&sten->smat, -rad->center.x, -rad->center.y); + gf_mx2d_add_scale(&sten->smat, gf_invfix(rad->radius.x), gf_invfix(rad->radius.y)); + + rad->d_f.x = gf_divfix(rad->focus.x - rad->center.x, rad->radius.x); + rad->d_f.y = gf_divfix(rad->focus.y - rad->center.y, rad->radius.y); + /*init*/ + evg_radial_init(rad); + evg_gradient_precompute((EVG_BaseGradient *)rad, surf); + } + break; + } + } + max_gray = surf->raster->max_gray_spans; + /*force complete line callback for YUV 420/422*/ + if (surf->yuv_type==EVG_YUV) + surf->raster->max_gray_spans = 0xFFFFFFFF; + + if (surf->useClipper) { + surf->clip_xMin = surf->clipper.x; + surf->clip_yMin = surf->clipper.y; + surf->clip_xMax = (surf->clipper.x + surf->clipper.width); + surf->clip_yMax = (surf->clipper.y + surf->clipper.height); + } else { + surf->clip_xMin = 0; + surf->clip_yMin = 0; + surf->clip_xMax = (surf->width); + surf->clip_yMax = (surf->height); + } + + /*and call the raster*/ + if (surf->is_3d_matrix) + e = evg_raster_render_path_3d(surf); + else + e = evg_raster_render(surf); + + surf->raster->max_gray_spans = max_gray; + + /*restore stencil matrix*/ + if (sten->type != GF_STENCIL_SOLID) { + gf_mx2d_copy(sten->smat, st_mat); + if (restore_filter) ((EVG_Texture *)sten)->filter = GF_TEXTURE_FILTER_DEFAULT; + } + surf->sten = 0L; + return e; +} + + + +void gf_evg_surface_set_composite_mode(GF_EVGSurface *surf, GF_EVGCompositeMode comp_mode) +{ + if (surf) surf->comp_mode = comp_mode; +} + +void gf_evg_surface_set_alpha_callback(GF_EVGSurface *surf, u8 (*get_alpha)(void *udta, u8 src_alpha, s32 x, s32 y), void *cbk) +{ + if (surf) { + surf->get_alpha = get_alpha; + surf->get_alpha_udta = cbk; + } +} + +GF_Err gf_evg_surface_set_projection(GF_EVGSurface *surf, GF_Matrix *mx) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + gf_mx_copy(surf->ext3d->proj, *mx); + return GF_OK; +} +GF_Err gf_evg_surface_set_modelview(GF_EVGSurface *surf, GF_Matrix *mx) +{ + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + gf_mx_copy(surf->ext3d->modelview, *mx); + return GF_OK; +} + +GF_Err evg_raster_render3d(GF_EVGSurface *surf, u32 *indices, u32 nb_idx, Float *vertices, u32 nb_vertices, u32 nb_comp, GF_EVGPrimitiveType prim_type); + +GF_Err gf_evg_surface_draw_array(GF_EVGSurface *surf, u32 *indices, u32 nb_idx, Float *vertices, u32 nb_vertices, u32 nb_comp, GF_EVGPrimitiveType prim_type) +{ + GF_Err e; + u32 max_gray; + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + + /*setup ft raster calllbacks*/ + if (!setup_grey_callback(surf, GF_TRUE)) return GF_OK; + + if (surf->useClipper) { + surf->clip_xMin = surf->clipper.x; + surf->clip_yMin = surf->clipper.y; + surf->clip_xMax = (surf->clipper.x + surf->clipper.width); + surf->clip_yMax = (surf->clipper.y + surf->clipper.height); + } else { + surf->clip_xMin = 0; + surf->clip_yMin = 0; + surf->clip_xMax = (surf->width); + surf->clip_yMax = (surf->height); + } + max_gray = surf->raster->max_gray_spans; + /*force complete line callback for YUV 420/422*/ + if (surf->yuv_type==EVG_YUV) + surf->raster->max_gray_spans = 0xFFFFFFFF; + + /*and call the raster*/ + e = evg_raster_render3d(surf, indices, nb_idx, vertices, nb_vertices, nb_comp, prim_type); + surf->raster->max_gray_spans = max_gray; + return e; +} + +GF_Err evg_raster_render3d_path(GF_EVGSurface *surf, GF_Path *path, Float z); + +GF_Err gf_evg_surface_draw_path(GF_EVGSurface *surf, GF_Path *path, Float z) +{ + GF_Err e; + u32 max_gray; + if (!surf || !surf->ext3d) return GF_BAD_PARAM; + + /*setup ft raster calllbacks*/ + if (!setup_grey_callback(surf, GF_TRUE)) return GF_OK; + + if (surf->useClipper) { + surf->clip_xMin = surf->clipper.x; + surf->clip_yMin = surf->clipper.y; + surf->clip_xMax = (surf->clipper.x + surf->clipper.width); + surf->clip_yMax = (surf->clipper.y + surf->clipper.height); + } else { + surf->clip_xMin = 0; + surf->clip_yMin = 0; + surf->clip_xMax = (surf->width); + surf->clip_yMax = (surf->height); + } + max_gray = surf->raster->max_gray_spans; + /*force complete line callback for YUV 420/422*/ + if (surf->yuv_type==EVG_YUV) + surf->raster->max_gray_spans = 0xFFFFFFFF; + + /*and call the raster*/ + e = evg_raster_render3d_path(surf, path, z); + surf->raster->max_gray_spans = max_gray; + return e; +} diff --git a/src/export.cpp b/src/export.cpp index 6b1b987..4000265 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC @@ -38,12 +38,27 @@ #endif -#pragma comment (linker, EXPORT_SYMBOL(gpac_features) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sys_init) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sys_close) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sys_set_args) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sys_get_argc) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_get_argv) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sys_get_arg) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_is_arg_used) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_mark_arg_used) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_is_test_mode) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_old_arch_compat) ) + +#ifdef GPAC_ENABLE_COVERAGE +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_is_cov_mode) ) +#endif + +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_is_quiet) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_gpac_version) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_gpac_copyright) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_gpac_copyright_cite) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_features) ) + #pragma comment (linker, EXPORT_SYMBOL(gf_sleep) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mkdir) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rmdir) ) @@ -53,6 +68,23 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_sys_clock_high_res) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sys_get_rti) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sys_get_battery_state) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_get_options) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_is_gpac_arg) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_print_arg) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_print_core_help) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_set_cfg_option) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_localized) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_get_process_id) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_set_console_code) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_log_use_color) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_log_use_file) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_format_help) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_word_match) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_profiler_set_callback) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_profiler_send) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_profiler_enable_sampling) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sys_profiler_sampling_enabled) ) + #pragma comment (linker, EXPORT_SYMBOL(gf_get_default_cache_directory) ) #pragma comment (linker, EXPORT_SYMBOL(gf_4cc_to_str) ) #pragma comment (linker, EXPORT_SYMBOL(gf_error_to_string) ) @@ -60,44 +92,62 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_rand) ) #pragma comment (linker, EXPORT_SYMBOL(gf_get_user_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_enum_directory) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_file_load_data) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dynstrcat) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_gmtime) ) #pragma comment (linker, EXPORT_SYMBOL(gf_log_modify_tools_levels) ) #pragma comment (linker, EXPORT_SYMBOL(gf_log_set_tools_levels) ) #pragma comment (linker, EXPORT_SYMBOL(gf_log_set_tool_level) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_log_get_tool_level) ) #pragma comment (linker, EXPORT_SYMBOL(gf_log_set_strict_error) ) #pragma comment (linker, EXPORT_SYMBOL(gf_log_set_callback) ) #pragma comment (linker, EXPORT_SYMBOL(gf_log_get_tools_levels) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_log_reset_file) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_log_tool_level_on) ) #ifndef GPAC_DISABLE_LOG -#pragma comment (linker, EXPORT_SYMBOL(gf_log_tool_level_on) ) #pragma comment (linker, EXPORT_SYMBOL(gf_log) ) #pragma comment (linker, EXPORT_SYMBOL(gf_log_va_list) ) #pragma comment (linker, EXPORT_SYMBOL(gf_log_lt) ) #endif +#pragma comment (linker, EXPORT_SYMBOL(gf_fsize) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fileio_new) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fileio_del) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fileio_get_udta) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fileio_url) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fileio_set_stats) ) + #pragma comment (linker, EXPORT_SYMBOL(gf_set_progress) ) #pragma comment (linker, EXPORT_SYMBOL(gf_set_progress_callback) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_delete_file) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_move_file) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_temp_file_new) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_file_delete) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_file_move) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_file_temp) ) #pragma comment (linker, EXPORT_SYMBOL(gf_file_modification_time) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_fread) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fwrite) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fopen) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fclose) ) #pragma comment (linker, EXPORT_SYMBOL(gf_fseek) ) #pragma comment (linker, EXPORT_SYMBOL(gf_ftell) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fread) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fgets) ) + #pragma comment (linker, EXPORT_SYMBOL(gf_file_exists) ) #pragma comment (linker, EXPORT_SYMBOL(gf_file_basename) ) #pragma comment (linker, EXPORT_SYMBOL(gf_file_ext_start) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_url_colon_suffix) ) #pragma comment (linker, EXPORT_SYMBOL(gf_prompt_has_input) ) #pragma comment (linker, EXPORT_SYMBOL(gf_prompt_get_char) ) #pragma comment (linker, EXPORT_SYMBOL(gf_prompt_set_echo_off) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_prompt_get_size) ) + #pragma comment (linker, EXPORT_SYMBOL(gf_crc_32) ) -#ifndef GPAC_DISABLE_ZLIB #pragma comment (linker, EXPORT_SYMBOL(gf_gz_compress_payload) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_gz_compress_payload_ex) ) #pragma comment (linker, EXPORT_SYMBOL(gf_gz_decompress_payload) ) -#endif +#pragma comment (linker, EXPORT_SYMBOL(gf_lz_compress_payload) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_lz_decompress_payload) ) #pragma comment (linker, EXPORT_SYMBOL(gf_file_handles_count) ) /* Memory */ @@ -109,6 +159,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_mem_strdup) ) #pragma comment (linker, EXPORT_SYMBOL(gf_memory_print) ) #pragma comment (linker, EXPORT_SYMBOL(gf_memory_size) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_mem_enable_tracker) ) #else #pragma comment (linker, EXPORT_SYMBOL(gf_malloc) ) #pragma comment (linker, EXPORT_SYMBOL(gf_calloc) ) @@ -117,19 +168,6 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_strdup) ) #endif /*GPAC_MEMORY_TRACKING*/ -/* Print */ -#pragma comment (linker, EXPORT_SYMBOL(gf_asprintf) ) - -/* Ring Buffer */ -#ifndef GPAC_DISABLE_PLAYER -#pragma comment (linker, EXPORT_SYMBOL(gf_ringbuffer_new) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_ringbuffer_read) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_ringbuffer_write) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_ringbuffer_available_for_read ) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_ringbuffer_del) ) -#endif - - /* Sound */ #ifndef GPAC_DISABLE_PLAYER #pragma comment (linker, EXPORT_SYMBOL(gf_mixer_set_config) ) @@ -160,28 +198,11 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_list_rem_last) ) #pragma comment (linker, EXPORT_SYMBOL(gf_list_swap) ) #pragma comment (linker, EXPORT_SYMBOL(gf_list_transfer) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_list_reverse) ) #pragma comment (linker, EXPORT_SYMBOL(gf_list_clone) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_list_rev_enum) ) #pragma comment (linker, EXPORT_SYMBOL(gf_list_pop_front) ) #pragma comment (linker, EXPORT_SYMBOL(gf_list_pop_back) ) -/* Map */ -#ifndef GPAC_DISABLE_PLAYER -#pragma comment (linker, EXPORT_SYMBOL(gf_map_new) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_map_del) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_map_reset) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_map_find) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_map_insert) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_map_rem) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_map_count) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_map_has_key) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_map_iter_set) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_map_iter_has_next) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_map_iter_reset) ) -#endif - /* Bitstream */ #pragma comment (linker, EXPORT_SYMBOL(gf_bs_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bs_from_file) ) @@ -190,7 +211,6 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_bs_read_int) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bs_read_long_int) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bs_read_u8) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_bs_read_u8_until_delimiter) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bs_read_u16) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bs_read_u24) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bs_read_u32) ) @@ -218,9 +238,20 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_bs_get_position) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bs_get_size) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bs_get_refreshed_size) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_bs_set_output_buffering) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bs_transfer) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bs_flush) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_bs_bits_available) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_bs_get_bit_offset) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_bs_get_bit_position) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_bs_get_content_no_truncate) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_bs_read_vluimsbf5) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_bs_read_u16_le) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_bs_read_u32_le) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_bs_truncate) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_bs_write_byte) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_bs_write_u16_le) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_bs_write_u32_le) ) /* Thread */ @@ -238,13 +269,12 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_mx_v) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mx_p) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mx_try_lock) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_mx_get_num_locks) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sema_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sema_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sema_notify) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sema_wait) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sema_wait_for) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_global_resource_lock) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_global_resource_unlock) ) /* Socket */ #pragma comment (linker, EXPORT_SYMBOL(gf_sk_new) ) @@ -264,24 +294,30 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_sk_get_local_ip) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sk_get_local_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sk_get_remote_address) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sk_send_to) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sk_setup_multicast) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sk_is_multicast_address) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sk_send_wait) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sk_receive_wait) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sk_receive_no_select) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sk_set_usec_wait) ) + #pragma comment (linker, EXPORT_SYMBOL(gf_url_is_local) ) #pragma comment (linker, EXPORT_SYMBOL(gf_url_get_absolute_path) ) #pragma comment (linker, EXPORT_SYMBOL(gf_url_concatenate) ) #pragma comment (linker, EXPORT_SYMBOL(gf_url_get_resource_path) ) #pragma comment (linker, EXPORT_SYMBOL(gf_url_get_resource_name) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_url_remove_last_delimiter) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_url_get_ressource_extension) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_url_to_fs_path) ) #pragma comment (linker, EXPORT_SYMBOL(gf_utc_time_since_1970) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_gettimeofday) ) #pragma comment (linker, EXPORT_SYMBOL(gf_net_get_ntp) ) #pragma comment (linker, EXPORT_SYMBOL(gf_net_get_ntp_ts) ) #pragma comment (linker, EXPORT_SYMBOL(gf_net_get_ntp_diff_ms) ) #pragma comment (linker, EXPORT_SYMBOL(gf_net_set_ntp_shift) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_net_get_utc_ts) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_net_ntp_to_utc) ) + + +#pragma comment (linker, EXPORT_SYMBOL(gf_errno_str) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_blob_get_data) ) #pragma comment (linker, EXPORT_SYMBOL(gf_net_get_utc) ) @@ -289,7 +325,6 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_net_get_timezone) ) #pragma comment (linker, EXPORT_SYMBOL(gf_net_has_ipv6) ) #pragma comment (linker, EXPORT_SYMBOL(gf_net_is_ipv6) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_net_mobileip_set_callback) ) #ifndef GPAC_DISABLE_CORE_TOOLS #pragma comment (linker, EXPORT_SYMBOL(gf_base64_encode) ) @@ -303,46 +338,54 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_token_get) ) #pragma comment (linker, EXPORT_SYMBOL(gf_token_get_line) ) #pragma comment (linker, EXPORT_SYMBOL(gf_token_find) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_token_get_strip) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cfg_init) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cfg_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cfg_force_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cfg_del) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cfg_remove) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cfg_save) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cfg_get_key) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cfg_get_sub_key) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cfg_set_key) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cfg_get_section_count) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cfg_get_section_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cfg_get_key_count) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cfg_get_key_name) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cfg_insert_key) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cfg_del_section) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cfg_get_filename) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cfg_set_filename) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cfg_discard_changes) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_opts_get_key) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_opts_set_key) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_opts_get_key_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_opts_del_section) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_opts_get_section_count) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_opts_get_section_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_opts_get_key_count) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_opts_discard_changes) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_opts_get_bool) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_opts_get_int) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_opts_get_filename) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_opts_default_shared_directory) ) + + #ifndef GPAC_DISABLE_PLAYER #pragma comment (linker, EXPORT_SYMBOL(gf_modules_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_modules_del) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_modules_refresh) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_modules_get_count) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_modules_count) ) #pragma comment (linker, EXPORT_SYMBOL(gf_modules_get_module_directories) ) #pragma comment (linker, EXPORT_SYMBOL(gf_modules_get_file_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_module_get_file_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_module_load_static) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_modules_load_interface) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_modules_load_interface_by_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_modules_load) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_modules_load_by_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_modules_close_interface) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_modules_get_option) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_modules_set_option) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_modules_get_config) ) #endif #pragma comment (linker, EXPORT_SYMBOL(gf_utf8_wcstombs) ) #pragma comment (linker, EXPORT_SYMBOL(gf_utf8_mbstowcs) ) #pragma comment (linker, EXPORT_SYMBOL(gf_utf8_wcslen) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_utf8_to_wcs) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_wcs_to_utf8) ) #ifndef GPAC_DISABLE_PLAYER #pragma comment (linker, EXPORT_SYMBOL(gf_utf8_is_right_to_left) ) #pragma comment (linker, EXPORT_SYMBOL(gf_utf8_reorder_bidi) ) @@ -369,40 +412,23 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_fetch_data) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_last_error) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_get_resource_name) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_reset) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_mime_type) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_get_header) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_get_header_sizes_and_times) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_enum_headers) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_set_data_rate) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_get_data_rate) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_reassign) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cache_get_url) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cache_get_cache_filename) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cache_create_entry) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cache_get_mime_type) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cache_append_http_headers) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cache_open_write_cache) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cache_set_last_modified_on_server) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cache_set_etag_on_server) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cache_set_content_length) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cache_set_mime_type) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cache_get_cache_filename_range) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dm_set_auth_callback) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_delete_cached_file_entry_session) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_url_info_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_get_url_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_url_info_init) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_dm_is_thread_dead) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_abort) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_set_range) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_setup_from_url) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_dm_get_file_memory) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_get_global_rate) ) - - - -#pragma comment (linker, EXPORT_SYMBOL(gf_dm_sess_can_be_cached_on_disk) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dm_wget) ) #pragma comment (linker, EXPORT_SYMBOL(gf_xml_sax_new) ) @@ -412,8 +438,6 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_xml_sax_suspend) ) #pragma comment (linker, EXPORT_SYMBOL(gf_xml_sax_parse_file) ) #pragma comment (linker, EXPORT_SYMBOL(gf_xml_sax_get_line) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_xml_sax_get_file_size) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_xml_sax_get_file_pos) ) #pragma comment (linker, EXPORT_SYMBOL(gf_xml_sax_peek_node) ) #pragma comment (linker, EXPORT_SYMBOL(gf_xml_sax_binary_file) ) #pragma comment (linker, EXPORT_SYMBOL(gf_xml_sax_get_error) ) @@ -423,12 +447,9 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_xml_dom_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_xml_dom_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_xml_dom_parse) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_xml_dom_create_root) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_xml_dom_get_attribute) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_xml_dom_set_attribute) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_xml_dom_create_attribute) ) #pragma comment (linker, EXPORT_SYMBOL(gf_xml_dom_append_child) ) #pragma comment (linker, EXPORT_SYMBOL(gf_xml_dom_rem_child) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_xml_dom_node_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_xml_dom_get_root) ) #pragma comment (linker, EXPORT_SYMBOL(gf_xml_dom_get_error) ) #pragma comment (linker, EXPORT_SYMBOL(gf_xml_dom_get_line) ) @@ -438,13 +459,15 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_xml_dom_get_root_nodes_count) ) #pragma comment (linker, EXPORT_SYMBOL(gf_xml_dom_get_root_idx) ) #pragma comment (linker, EXPORT_SYMBOL(gf_xml_parse_bit_sequence) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_xml_dom_detach_root) ) #endif #ifndef GPAC_DISABLE_SVG #pragma comment (linker, EXPORT_SYMBOL(gf_dom_get_key_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dom_get_key_type) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dom_event_get_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dom_event_fire_ex) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dom_listener_count) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dom_listener_get) ) #endif #ifdef GPAC_FIXED_POINT @@ -486,6 +509,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_mx2d_apply_coords) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mx2d_apply_point) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mx2d_apply_rect) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_mx2d_decompose) ) + #pragma comment (linker, EXPORT_SYMBOL(gf_get_bit_size) ) /*exported for 3D tools*/ @@ -495,6 +520,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_vec_norm) ) #pragma comment (linker, EXPORT_SYMBOL(gf_vec_scale) ) #pragma comment (linker, EXPORT_SYMBOL(gf_vec_cross) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_vec_scale_p) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_vec_cross_p) ) #pragma comment (linker, EXPORT_SYMBOL(gf_quat_to_rotation) ) #pragma comment (linker, EXPORT_SYMBOL(gf_quat_from_matrix) ) #pragma comment (linker, EXPORT_SYMBOL(gf_quat_from_rotation) ) @@ -531,6 +558,9 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_mx_decompose) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mx_rotate_vector) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mx_rotation_matrix_from_vectors) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_mx_apply_bbox_sphere) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_mx_get_yaw_pitch_roll) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_mx_transpose) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mx2d_from_mx) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mx_apply_plane) ) #pragma comment (linker, EXPORT_SYMBOL(gf_plane_get_distance) ) @@ -543,24 +573,19 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_ray_hit_box) ) #pragma comment (linker, EXPORT_SYMBOL(gf_ray_hit_sphere) ) #pragma comment (linker, EXPORT_SYMBOL(gf_ray_hit_triangle) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_ray_hit_triangle_backcull) ) /*color.h exports*/ #pragma comment (linker, EXPORT_SYMBOL(gf_stretch_bits) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_color_write_yv12_10_to_yuv) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_color_write_yuv422_10_to_yuv422) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_color_write_yuv444_10_to_yuv444) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_color_write_yuv422_10_to_yuv) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_color_write_yuv444_10_to_yuv) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cmx_init) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cmx_set) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_cmx_set_all) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cmx_copy) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cmx_multiply) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cmx_apply) ) #pragma comment (linker, EXPORT_SYMBOL(gf_cmx_apply_fixed) ) #pragma comment (linker, EXPORT_SYMBOL(gf_color_parse) ) #pragma comment (linker, EXPORT_SYMBOL(gf_color_get_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_color_enum) ) + /*path2d.h exports*/ @@ -594,6 +619,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_polygone2d_get_convexity) ) #pragma comment (linker, EXPORT_SYMBOL(gf_path_get_outline) ) #pragma comment (linker, EXPORT_SYMBOL(gf_path_point_over) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_path_clone) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_path_is_empty) ) #endif //GPAC_DISABLE_PLAYER @@ -615,29 +642,41 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_odf_codec_get_com) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_com_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_com_del) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_afx_get_type_description) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_esd_get_textual_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_slc_set_pref) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_get_bifs_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_get_text_config) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_odf_get_ui_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_get_laser_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_encode_ui_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sl_depacketize) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_desc_write) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_desc_size) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_odf_stream_type_name) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_odf_stream_type_by_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_avc_cfg_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_avc_cfg_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_avc_cfg_read) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_avc_cfg_write) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_avc_cfg_write_bs) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_hevc_cfg_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_hevc_cfg_read) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_hevc_cfg_del) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_av1_cfg_del) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_av1_cfg_new) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_av1_cfg_write) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_av1_cfg_write_bs) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_desc_write_bs) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_hevc_cfg_read_bs) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_hevc_cfg_write) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_hevc_cfg_write_bs) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_vp_cfg_del) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_vp_cfg_new) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_vp_cfg_read) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_vp_cfg_read_bs) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_vp_cfg_write) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_vp_cfg_write_bs) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_dump_txtcfg) ) + #ifndef GPAC_MINIMAL_ODF #pragma comment (linker, EXPORT_SYMBOL(gf_odf_desc_add_desc) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_desc_list_read) ) @@ -676,24 +715,22 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_probe_file) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_open) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_close) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_write) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_delete) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_mode) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_file_size) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_moov_first) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_parse) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_clone_box) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_xml_metadata_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_dims_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_stxt_get_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_new_xml_subtitle_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_xml_subtitle_get_description) ) -#ifndef GPAC_DISABLE_ISOM_WRITE -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_write) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_size) ) -#endif #pragma comment (linker, EXPORT_SYMBOL(gf_isom_open_progressive) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_missing_bytes) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_freeze_order) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_is_fragmented) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_fragmented_duration) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_fragments_count) ) @@ -713,6 +750,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_track_id) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_track_by_id) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_is_track_enabled) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_track_flags) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_is_track_encrypted) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_track_duration) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_media_type) ) @@ -742,17 +780,16 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_sample_size) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_sample_sync) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_edit_list_type) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_edit_segment_count) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_edit_segment) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_edits_count) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_edit) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_copyright_count) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_copyright) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_watermark) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_has_sync_points) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_has_time_offset) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_cts_to_dts_shift) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_sync_point_count) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_has_root_od) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_root_od) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_disable_odf_conversion) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_is_track_in_root_od) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_esd) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_decoder_config) ) @@ -765,6 +802,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_alternate_brand) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_has_padding_bits) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_visual_info) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_visual_info) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_visual_bit_depth) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_pixel_aspect_ratio) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_audio_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_chapter_count) ) @@ -773,8 +812,6 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_user_data) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_generic_sample_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_fragment_defaults) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_sample_fragment_count) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_sample_fragment_size) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_default_sync_track) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_3gp_config_get) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_ac3_config_get) ) @@ -812,6 +849,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_has_meta_xml) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_extract_meta_xml) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_extract_meta_item) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_extract_meta_item_mem) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_meta_image_props) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_has_movie) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_has_segment) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_segment_get_fragment_count) ) @@ -820,10 +859,9 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_is_single_av) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_guess_specification) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_keep_utc_times) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_no_version_date_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_has_sync_shadows) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_has_sample_dependency) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_find_od_for_track) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_find_od_id_for_track) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_apple_get_tag) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_media_data_size) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_omadrm_info) ) @@ -836,6 +874,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_rvc_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_sample_rap_roll_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_reset_fragment_info) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_reset_sample_count) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_reset_seq_num) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_track_original_id) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_nalu_extract_mode) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_nalu_extract_mode) ) @@ -843,18 +883,26 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_needs_layer_reconstruction) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_last_producer_time_box) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_current_tfdt) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_is_smooth_streaming_moov) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_is_adobe_protection_media) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_adobe_protection_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_sample_group_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_tile_info) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_drop_date_version_info_enabled) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_oinf_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_udta_count) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_udta_type) ) - -#ifndef GPAC_DISABLE_ISOM_WRITE -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_sync_table) ) -#endif +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_is_video_handler_type) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_sample_is_fragment_start) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_constant_sample_size) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_constant_sample_duration) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_enable_raw_pack) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_track_magic) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_sample_from_dts) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_sample_padding_bits) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_sample_get_subsample) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_sample_has_subsamples) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_sample_get_subsamples_buffer) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_max_sample_delta) ) # ifndef GPAC_DISABLE_ISOM_DUMP #pragma comment (linker, EXPORT_SYMBOL(gf_isom_dump) ) @@ -870,13 +918,24 @@ #ifndef GPAC_DISABLE_ISOM_HINTING #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_payt_count) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_payt_info) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_reset_hint_reader) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_next_hint_packet) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_sdp_get) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_sdp_track_get) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_hinter_track_force_no_offsets) ) + # ifndef GPAC_DISABLE_ISOM_DUMP #pragma comment (linker, EXPORT_SYMBOL(gf_isom_dump_hint_sample) ) #endif + +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_hint_pck_read) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_hint_pck_del) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_hint_pck_new) ) + +#ifndef GPAC_DISABLE_ISOM_WRITE +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_hint_pck_size) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_hint_pck_write) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_hint_pck_length) ) +#endif + #endif #ifndef GPAC_DISABLE_ISOM_WRITE @@ -887,6 +946,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_creation_time) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_update_duration) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_enabled) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_flags) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_id) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_rewrite_track_dependencies) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_add_sample) ) @@ -895,31 +955,29 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_refresh_size_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_add_sample_reference) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_last_sample_duration) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_last_sample_duration_ex) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_reference) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_track_reference) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_track_references) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_update_sample) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_update_sample_reference) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_sample) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_final_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_storage_mode) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_enable_compression) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_force_64bit_chunk_offset) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_storage_mode) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_interleave_time) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_interleave_time) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_copyright) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_media_type) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_media_subtype) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_copyright) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_add_track_kind) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_track_kind) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_track_kind) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_track_kind_count) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_watermark) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_edit_segment) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_modify_edit_segment) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_append_edit_segment) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_edit_segments) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_edit_segment) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_edit) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_modify_edit) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_append_edit) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_edit) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_edits) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_update_edit_list_duration) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_media_language) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_handler_name) ) @@ -932,17 +990,16 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_brand_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_modify_alternate_brand) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_reset_alt_brands) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_sample_padding_bits) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_visual_info) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_visual_color_info) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_visual_bit_depth) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_dolby_vision_profile) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_pixel_aspect_ratio) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_layout_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_matrix) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_audio_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_cts_packing) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_modify_cts_offset) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_cts_info) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_name) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_track_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_shift_cts_offset) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_pl_indication) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_root_od_id) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_root_od_url) ) @@ -954,27 +1011,17 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_change_mpeg4_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_add_desc_to_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_new_generic_sample_description) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_change_generic_sample_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_update_bitrate) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_sample_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_clone_sample_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_clone_track) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_clone_pl_indications) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_clone_root_od) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_is_same_sample_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_add_chapter) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_chapter) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_sync_shadows) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_sync_shadow) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_interleaving_group) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_priority_in_group) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_max_samples_per_chunk) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_hint_max_chunk_size) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_extraction_slc) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_extraction_slc) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_track_group) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_track_priority_in_group) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_store_movie_config) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_load_movie_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_make_interleave) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_3gp_config_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_3gp_config_update) ) @@ -992,9 +1039,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_text_set_box) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_text_add_blink) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_text_set_wrap) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_text_to_sample) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_text_set_display_flags) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_track_protection) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_text_to_sample) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_ismacryp_protection) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_change_ismacryp_protection) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_avc_config_new) ) @@ -1004,6 +1050,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_hevc_config_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_hevc_config_update) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_hevc_set_inband_config) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_lhvc_force_inband_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_lhvc_config_update) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_3gp_config_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_3gp_config_update) ) @@ -1015,7 +1062,6 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_meta_item) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_meta_primary_item) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_meta_xml) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_meta_xml_memory) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_meta_add_item_ref) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_iff_create_image_item_from_track) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_meta_xml) ) @@ -1027,18 +1073,24 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_switch_parameter) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_alternate_group_id) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_new_dims_description) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_update_dims_description) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_rvc_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_sample_rap_group) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_sample_roll_group) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_sample_cenc_group) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_composition_offset_mode) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_output_buffering) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_add_sample_group_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_add_sample_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_copy_sample_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_sample_group) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_group) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_progress_callback) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_apply_box_patch) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_ac3_config_new) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_write) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_size) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_tmcd_config_new) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_tmcd_config) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_track_magic) ) #ifndef GPAC_DISABLE_ISOM_HINTING @@ -1063,8 +1115,6 @@ #endif /*GPAC_DISABLE_ISOM_HINTING*/ #ifndef GPAC_DISABLE_ISOM_FRAGMENTS -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_add_sample_fragment) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_remove_sample_fragment) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_setup_track_fragment) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_finalize_for_fragment) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_start_fragment) ) @@ -1075,7 +1125,6 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_single_moof_mode) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_fragment_add_sample) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_fragment_append_data) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_fragment_add_sai) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_clone_pssh) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_start_segment) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_close_segment) ) @@ -1087,16 +1136,14 @@ #endif /*GPAC_DISABLE_ISOM_WRITE*/ /*isomedia_dev.h exports*/ -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_parse_texte_sample) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_parse_text_sample) ) #pragma comment (linker, EXPORT_SYMBOL(gf_isom_delete_text_sample) ) #ifndef GPAC_DISABLE_STREAMING #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_streamer_new) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_rtp_streamer_new_extended) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_streamer_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_streamer_append_sdp) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_streamer_append_sdp_extended) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_rtp_streamer_append_sdp_decoding_dependency) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_streamer_send_au) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_streamer_send_au_with_sn) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_streamer_send_data) ) @@ -1104,39 +1151,41 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_streamer_disable_auto_rtcp) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_streamer_send_rtcp) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_streamer_get_payload_type) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_rtp_streamer_set_interleave_callbacks) ) - -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_streamer_new) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_streamer_del) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_streamer_write_sdp) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_streamer_get_sdp) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_streamer_send_next_packet) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_isom_streamer_reset) ) #endif #pragma comment (linker, EXPORT_SYMBOL(gf_media_map_esd) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_media_map_item_esd) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_get_file_hash) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_reduce_aspect_ratio) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_get_reduced_frame_rate) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_av1_config_get) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_av1_config_new) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_equal) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_box_write_header) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_cenc_is_pattern_mode) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_close_fragments) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_bitrate) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_get_sample_cenc_info_ex) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_hevc_set_tile_config) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_mvc_config_get) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_mvc_config_new) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_high_dynamic_range_info) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_clean_aperture) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_image_sequence_alpha) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_set_image_sequence_coding_constraints) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_svc_config_new) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_vp_config_get) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_vp_config_new) ) + #endif /*GPAC_DISABLE_ISOM*/ /*service.h exports*/ #ifndef GPAC_DISABLE_PLAYER -#pragma comment (linker, EXPORT_SYMBOL(gf_service_connect_ack) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_service_disconnect_ack) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_service_command) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_service_send_packet) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_service_get_url) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_service_declare_media) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_service_get_interface) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_service_register_mime) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_service_check_mime_register) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_service_download_new) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_service_download_del) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_service_download_update_stats) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_service_command) ) /*mediaobject.h exports*/ #pragma comment (linker, EXPORT_SYMBOL(gf_mo_register) ) @@ -1158,18 +1207,16 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_mo_is_muted) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mo_is_done) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mo_adjust_clock) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_mo_get_last_frame_time) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mo_get_visual_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mo_get_audio_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mo_get_current_speed) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mo_has_audio) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_mo_is_private_media) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_mo_set_position) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_mo_get_flags) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mo_set_flag) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mo_get_min_frame_dur) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_mo_get_clock_drift) ) - +#pragma comment (linker, EXPORT_SYMBOL(gf_mo_event_target_add_node) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_mo_get_nb_layers) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_mo_get_nb_views) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_mo_is_started) ) #ifndef GPAC_DISABLE_SVG #pragma comment (linker, EXPORT_SYMBOL(gf_mo_load_xlink_resource) ) @@ -1195,16 +1242,12 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_response_reset) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_session_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_session_del) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_set_mobile_ip) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_set_buffer_size) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_session_reset) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_is_my_session) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_get_last_session_id) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_get_server_name) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_get_service_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_get_response) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_get_session_state) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_get_last_request) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_reset_aggregation) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_send_command) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_set_interleave_callback) ) @@ -1213,13 +1256,13 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_unregister_interleave) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_session_new_server) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_get_command) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_load_service_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_generate_session_id) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_send_response) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_get_session_ip) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_get_next_interleave_id) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_get_remote_address) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_get_session_port) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_session_write_interleaved) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_rtsp_http_tunnel_start) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_setup_transport) ) @@ -1238,15 +1281,12 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_send_rtcp_report) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_send_bye) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_send_packet) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_rtp_set_info_rtcp) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_is_unicast) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_is_interleaved) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_get_clockrate) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_rtp_is_active) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_get_low_interleave_id) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_get_hight_interleave_id) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_get_transport) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_rtp_get_local_ssrc) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_get_loss) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_get_tcp_bytes_sent) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_get_ports) ) @@ -1254,8 +1294,6 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_sdp_info_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sdp_info_reset) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sdp_info_parse) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sdp_info_check) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sdp_info_write) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sdp_media_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sdp_media_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sdp_conn_new) ) @@ -1268,17 +1306,20 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_builder_process) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_builder_format_sdp) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_builder_get_payload_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_rtp_builder_set_cryp_info) ) + #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_depacketizer_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_depacketizer_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_depacketizer_reset) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_depacketizer_process) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_rtp_depacketizer_get_slconfig) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_reorderer_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_reorderer_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_reorderer_reset) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_reorderer_add) ) #pragma comment (linker, EXPORT_SYMBOL(gf_rtp_reorderer_get) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_rtp_reset_ssrc) ) + #endif /*GPAC_DISABLE_STREAMING*/ @@ -1311,13 +1352,13 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_media_make_isma) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_make_3gpp) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_make_psp) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_media_check_qt_prores) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_import) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_import_chapters) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_media_import_chapters_file) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_change_pl) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_nalu_next_start_code_bs) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_media_nalu_payload_end_bs) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_media_avc_rewrite_samples) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_split_svc) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_merge_svc) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_split_hevc_tiles) ) @@ -1326,17 +1367,26 @@ #endif /*GPAC_DISABLE_MEDIA_IMPORT*/ +#pragma comment (linker, EXPORT_SYMBOL(gf_inspect_dump_nalu) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_inspect_dump_obu) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_inspect_dump_prores) ) + + #ifndef GPAC_DISABLE_ISOM_WRITE #pragma comment (linker, EXPORT_SYMBOL(gf_media_change_par) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_remove_non_rap) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_media_update_bitrate) ) #endif #ifndef GPAC_DISABLE_AV_PARSERS #pragma comment (linker, EXPORT_SYMBOL(gf_media_nalu_next_start_code) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_media_nalu_remove_emulation_bytes) ) #pragma comment (linker, EXPORT_SYMBOL(gf_avc_get_sps_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_avc_get_pps_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_avc_get_profile_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_avc_hevc_get_chroma_format_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_avc_is_rext_profile) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_avc_parse_nalu) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_avc_read_sps) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_avc_read_pps) ) @@ -1344,35 +1394,41 @@ #ifndef GPAC_DISABLE_HEVC #pragma comment (linker, EXPORT_SYMBOL(gf_media_hevc_read_vps) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_hevc_read_vps_ex) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_media_hevc_read_vps_bs) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_hevc_read_sps) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_media_hevc_read_sps_ex) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_media_hevc_read_sps_bs) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_hevc_read_pps) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_hevc_get_sps_info) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_media_hevc_read_pps_bs) ) #pragma comment (linker, EXPORT_SYMBOL(gf_hevc_get_profile_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_hevc_get_sps_info_with_state) ) #pragma comment (linker, EXPORT_SYMBOL(gf_media_hevc_parse_nalu) ) - #endif //GPAC_DISABLE_HEVC + +#pragma comment (linker, EXPORT_SYMBOL(gf_media_aom_av1_parse_obu) ) + #endif /*GPAC_DISABLE_AV_PARSERS*/ +#pragma comment (linker, EXPORT_SYMBOL(gf_mpegh_get_mhas_pl) ) + #ifndef GPAC_DISABLE_MEDIA_EXPORT #pragma comment (linker, EXPORT_SYMBOL(gf_media_export) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_media_export_nhml) ) #endif /*GPAC_DISABLE_MEDIA_EXPORT*/ -/*mcrypt exports*/ -#ifndef GPAC_DISABLE_MCRYPT +/*crypto exports*/ +#ifndef GPAC_DISABLE_CRYPTO #pragma comment (linker, EXPORT_SYMBOL(gf_crypt_open) ) #pragma comment (linker, EXPORT_SYMBOL(gf_crypt_close) ) #pragma comment (linker, EXPORT_SYMBOL(gf_crypt_init) ) #pragma comment (linker, EXPORT_SYMBOL(gf_crypt_decrypt) ) #pragma comment (linker, EXPORT_SYMBOL(gf_crypt_encrypt) ) #pragma comment (linker, EXPORT_SYMBOL(gf_crypt_set_key) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_crypt_set_state) ) -#endif GPAC_DISABLE_MCRYPT +#pragma comment (linker, EXPORT_SYMBOL(gf_crypt_set_IV) ) +#endif GPAC_DISABLE_CRYPTO + #pragma comment (linker, EXPORT_SYMBOL(gf_sha1_csum) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sha1_csum_hexa) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sha1_file) ) #ifndef GPAC_DISABLE_AV_PARSERS @@ -1381,7 +1437,6 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_m4v_parse_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m4v_parse_frame) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m4v_get_object_start) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_m4v_is_valid_object_type) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m4v_get_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m4v_rewrite_pl) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mp3_num_channels) ) @@ -1396,6 +1451,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_m4a_get_channel_cfg) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m4a_write_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m4a_write_config_bs) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_m4a_parse_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_ac3_parser_bs) ) #pragma comment (linker, EXPORT_SYMBOL(gf_eac3_parser_bs) ) @@ -1412,8 +1468,6 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_img_jpeg_dec) ) #pragma comment (linker, EXPORT_SYMBOL(gf_img_png_dec) ) #pragma comment (linker, EXPORT_SYMBOL(gf_img_png_enc) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_img_png_enc_file) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_img_file_dec) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m4v_get_profile_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mp3_version) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mp3_version_name) ) @@ -1424,15 +1478,14 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_m4a_get_profile) ) #endif -#if !defined(GPAC_DISABLE_MCRYPT) && !defined(GPAC_DISABLE_ISOM_WRITE) -/*ismacryp.h exports*/ +#if !defined(GPAC_DISABLE_CRYPTO) && !defined(GPAC_DISABLE_ISOM_WRITE) +/*crypt_tools.h exports*/ #pragma comment (linker, EXPORT_SYMBOL(gf_crypt_file) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_crypt_fragment) ) #pragma comment (linker, EXPORT_SYMBOL(gf_decrypt_file) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_ismacryp_encrypt_track) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_ismacryp_decrypt_track) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_ismacryp_gpac_get_info) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_ismacryp_mpeg4ip_get_info) ) - +#pragma comment (linker, EXPORT_SYMBOL(gf_decrypt_fragment) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_crypt_info_load) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_crypt_info_del) ) #endif @@ -1452,16 +1505,14 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_sm_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sm_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sm_stream_new) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sm_stream_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sm_stream_au_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sm_aggregate) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sm_reset) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sm_load_init) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sm_load_done) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sm_load_string) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sm_load_run) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sm_load_suspend) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sm_import_bifs_subtitle) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sm_get_mux_info) ) #endif #ifndef GPAC_DISABLE_LOADER_BT @@ -1478,8 +1529,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_sm_dump) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sm_dump_command_list) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sm_dump_graph) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sm_dumper_set_extra_graph) ) #ifndef GPAC_DISABLE_OD_DUMP -#pragma comment (linker, EXPORT_SYMBOL(gf_odf_dump_au) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_dump_com) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_dump_desc) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odf_dump_com_list) ) @@ -1493,14 +1544,11 @@ #ifndef GPAC_DISABLE_SENG /*bifsengine exports*/ #pragma comment (linker, EXPORT_SYMBOL(gf_seng_init) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_seng_init_from_string) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_seng_init_from_context) ) #pragma comment (linker, EXPORT_SYMBOL(gf_seng_get_stream_count) ) #pragma comment (linker, EXPORT_SYMBOL(gf_seng_get_stream_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_seng_encode_context) ) #pragma comment (linker, EXPORT_SYMBOL(gf_seng_encode_from_file) ) #pragma comment (linker, EXPORT_SYMBOL(gf_seng_encode_from_string) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_seng_encode_from_commands) ) #pragma comment (linker, EXPORT_SYMBOL(gf_seng_save_context) ) #pragma comment (linker, EXPORT_SYMBOL(gf_seng_enable_aggregation) ) #pragma comment (linker, EXPORT_SYMBOL(gf_seng_aggregate_context) ) @@ -1508,22 +1556,16 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_seng_get_iod) ) #pragma comment (linker, EXPORT_SYMBOL(gf_seng_terminate) ) #pragma comment (linker, EXPORT_SYMBOL(gf_seng_get_stream_carousel_info) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_seng_dump_rap_on) ) #endif /*bifs.h exports*/ #ifndef GPAC_DISABLE_BIFS #pragma comment (linker, EXPORT_SYMBOL(gf_bifs_decoder_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bifs_decoder_del) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_bifs_decoder_set_extraction_path) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_bifs_decoder_ignore_size_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bifs_decoder_configure_stream) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_bifs_decoder_remove_stream) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bifs_decode_au) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bifs_decode_command_list) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bifs_get_aq_info) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_bifs_get_node_type) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_bifs_proto_field_set_aq_info) ) #ifndef GPAC_DISABLE_BIFS_ENC #pragma comment (linker, EXPORT_SYMBOL(gf_bifs_encoder_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bifs_encoder_del) ) @@ -1533,6 +1575,9 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_bifs_encoder_get_version) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bifs_encoder_get_rap) ) #pragma comment (linker, EXPORT_SYMBOL(gf_bifs_encoder_set_source_url) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_bifs_get_child_table) ) + + #endif #endif /*GPAC_DISABLE_BIFS*/ @@ -1603,8 +1648,17 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_node_list_get_child) ) #pragma comment (linker, EXPORT_SYMBOL(gf_node_list_get_count) ) #pragma comment (linker, EXPORT_SYMBOL(gf_node_list_del_child_idx) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sg_script_get_field_index) ) + -#pragma comment (linker, EXPORT_SYMBOL(gf_node_dump_attribute) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_node_dirty_parent_graph) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_node_dirty_parents) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_node_get_proto_root) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_node_proto_is_grouping) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_node_proto_set_grouping) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_node_remove_id) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_node_set_cyclic_traverse_flag) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_node_store_embedded_data) ) #ifndef GPAC_DISABLE_SVG @@ -1635,6 +1689,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_node_unregister_iri) ) #pragma comment (linker, EXPORT_SYMBOL(gf_svg_parse_element_id) ) #pragma comment (linker, EXPORT_SYMBOL(gf_svg_is_animation_tag) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_svg_apply_animations) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_svg_attributes_copy) ) #pragma comment (linker, EXPORT_SYMBOL(gf_xml_get_element_tag) ) @@ -1658,11 +1714,9 @@ #ifndef GPAC_DISABLE_VRML #pragma comment (linker, EXPORT_SYMBOL(gf_node_get_num_fields_in_mode) ) #pragma comment (linker, EXPORT_SYMBOL(gf_node_insert_child) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_node_remove_child) ) #pragma comment (linker, EXPORT_SYMBOL(gf_node_event_out) ) #pragma comment (linker, EXPORT_SYMBOL(gf_node_event_out_str) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_vrml_get_event_type_name) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sg_vrml_get_field_type_by_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_vrml_field_pointer_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_vrml_field_pointer_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_vrml_is_sf_field) ) @@ -1675,6 +1729,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_sg_vrml_mf_reset) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_vrml_field_copy) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_vrml_field_equal) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sg_vrml_field_clone) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_route_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_route_new_to_callback) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_route_del) ) @@ -1682,16 +1737,12 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_sg_route_find) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_route_find_by_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_route_set_id) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sg_route_get_id) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_route_set_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_route_get_name) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_activate_routes) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_del) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_set_in_graph) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_get_graph) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_set_private) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_get_private) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_get_extern_url) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_add_node_code) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_get_field_count) ) @@ -1699,8 +1750,6 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_field_find) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_field_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_field_set_ised) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_field_set_private) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_field_get_private) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_field_get_field) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_create_instance) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_proto_load_code) ) @@ -1723,6 +1772,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_sg_set_proto_loader) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sg_sfcolor_to_rgba) ) #pragma comment (linker, EXPORT_SYMBOL(gf_node_in_table_by_tag) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sg_vrml_get_field_type_name) ) + #ifdef GPAC_HAS_SPIDERMONKEY #pragma comment (linker, EXPORT_SYMBOL(gf_js_vrml_flush_event_out) ) #endif @@ -1775,6 +1826,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_term_object_subscene_type) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_select_object) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_select_service) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_term_find_service) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_toggle_addons) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_get_object_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_get_download_info) ) @@ -1783,30 +1835,19 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_term_dump_scene) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_set_size) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_user_event) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_mouse_input) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_keyboard_input) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_string_input) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_connect_from_time) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_add_object) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_is_supported_url) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_attach_service) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_set_simulation_frame_rate) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_get_simulation_frame_rate) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_step_clocks) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_process_flush) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_process_flush_video) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_service_get_interface) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_get_service_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_get_text_selection) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_paste_text) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_get_url) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_relocate_url) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_connect_with_path) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mo_register) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mo_unregister) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mo_get_scenegraph) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_send_event) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_lock_compositor) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_switch_quality) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_get_visual_output_size) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_scene_update) ) @@ -1814,14 +1855,10 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_term_get_screen_buffer) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_release_screen_buffer) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_get_offscreen_buffer) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_process_shortcut) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_set_speed) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_relocate_url) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_add_event_filter) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_remove_event_filter) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_lock_media_queue) ) #pragma comment (linker, EXPORT_SYMBOL(gf_term_get_current_service_id) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_get_clock) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_term_print_graph) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_term_is_type_supported) ) /*terminal_dev exports*/ #pragma comment (linker, EXPORT_SYMBOL(gf_scene_new) ) @@ -1835,30 +1872,25 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_scene_force_size) ) #pragma comment (linker, EXPORT_SYMBOL(gf_scene_process_anchor) ) #pragma comment (linker, EXPORT_SYMBOL(gf_scene_disconnect) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_scene_register_associated_media) ) #pragma comment (linker, EXPORT_SYMBOL(gf_scene_set_addon_layout_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_scene_select_object) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odm_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odm_setup_object) ) #pragma comment (linker, EXPORT_SYMBOL(gf_odm_disconnect) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_odm_setup_es) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_odm_remove_es) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_node_callback) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_term_message) ) #pragma comment (linker, EXPORT_SYMBOL(gf_clock_time) ) #pragma comment (linker, EXPORT_SYMBOL(gf_scene_get_media_object_ex) ) #pragma comment (linker, EXPORT_SYMBOL(gf_scene_resume_live) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_scene_set_timeshift_depth) ) #endif #ifndef GPAC_DISABLE_PLAYER /*compositor.h exports*/ -#pragma comment (linker, EXPORT_SYMBOL(gf_sc_new) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sc_del) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sc_load) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sc_unload) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_set_fps) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_set_scene) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_draw_frame) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sc_on_node_init) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_invalidate) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_get_clock) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_lock) ) @@ -1869,25 +1901,20 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_sc_get_fps) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_get_screen_buffer) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_release_screen_buffer) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sc_render_frame) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_reset_graphics) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sc_pick_node) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_get_viewpoint) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_set_viewpoint) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_set_size) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_register_extra_graph) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_get_compositor) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sc_reload_audio_filters) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sc_add_video_listener) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sc_remove_video_listener) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sc_add_audio_listener) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_sc_remove_audio_listener) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_focus_switch_ring) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_is_over) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_navigation_supported) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sc_on_event) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sc_get_selected_text) ) + #ifndef GPAC_DISABLE_SVG -#pragma comment (linker, EXPORT_SYMBOL(gf_sc_animation_get_scenegraph) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_svg_convert_length_to_display) ) #endif @@ -1912,6 +1939,10 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_sc_audio_restart) ) #pragma comment (linker, EXPORT_SYMBOL(gf_sc_reload_config) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sc_audio_check_url) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sc_texture_open) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sc_texture_stop_no_unregister) ) + #pragma comment (linker, EXPORT_SYMBOL(gf_font_manager_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_font_manager_set_font) ) #pragma comment (linker, EXPORT_SYMBOL(gf_font_manager_create_span) ) @@ -1921,22 +1952,15 @@ /*mpegts.h exports*/ #ifndef GPAC_DISABLE_MPEG2TS -#pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_get_esd) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_demux_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_demux_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_process_data) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_reset_parsers) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_reset_parsers_for_program) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_es_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_set_pes_framing) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_get_stream_name) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_crc32_check) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_restamp) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_demux_file) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_pes_get_framing_mode) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_get_sdt_info) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_abort_parsing) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_pause_demux) ) /* carousel.h */ @@ -1947,6 +1971,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_get_dmscc_overlord) ) #endif #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_demux_dmscc_init) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_probe_file) ) + #ifndef GPAC_DISABLE_MPEG2TS_MUX @@ -1955,7 +1981,6 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_mux_set_pcr_max_interval) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_mux_program_add) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_program_stream_add) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_program_stream_update_ts_scale) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_mux_update_config) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_mux_process) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_get_sys_clock) ) @@ -1973,22 +1998,22 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_mpd_init_from_dom) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mpd_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m3u8_to_mpd) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_mpd_smooth_to_mpd) ) + #pragma comment (linker, EXPORT_SYMBOL(gf_m3u8_solve_representation_xlink) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mpd_solve_segment_list_xlink) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mpd_delete_segment_list) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m3u8_parse_master_playlist) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_mpd_write) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mpd_write_file) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mpd_get_base_url_count) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mpd_resolve_url) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_mpd_getter_del_session) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mpd_get_duration) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mpd_resolve_segment_duration) ) #pragma comment (linker, EXPORT_SYMBOL(gf_mpd_init_smooth_from_dom) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_mpd_complete_from_dom) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_mpd_get_segment_start_time_with_timescale) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_demuxer_setup)) -#pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_demuxer_play) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_demuxer_close) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_get_socket) ) #endif /*GPAC_DISABLE_MPEG2TS*/ @@ -2056,6 +2081,12 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_dasher_add_input) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dasher_process) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dasher_set_start_date) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dasher_enable_cached_inputs) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dasher_enable_loop_inputs) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dasher_set_split_mode) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dasher_set_cues) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dasher_print_session_info) ) + #pragma comment (linker, EXPORT_SYMBOL(gf_dasher_next_update_time) ) @@ -2067,6 +2098,7 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_dvb_mpe_init) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dvb_mpe_section_new) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dvb_mpe_shutdown) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dvb_get_freq_from_url) ) #pragma comment (linker, EXPORT_SYMBOL(gf_m2ts_print_mpe_info) ) #endif @@ -2087,16 +2119,17 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_dash_switch_quality) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_get_duration) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_time_shift_buffer_depth) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_dash_check_mpd_root_type) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_segment_mime) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_segment_init_url) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_segment_init_keys) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_select) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_seek) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_groups_set_language) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_in_last_period) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_get_period_switch_status) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_request_period_switch) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_is_running) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dash_is_in_setup) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_num_segments_ready) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_discard_segment) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_next_segment_location) ) @@ -2107,17 +2140,12 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_dash_seek) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_start_range) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_segment_switch_forced) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_current_segment_start_time) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_presentation_time_offset) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_video_info) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_representation_info) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_dash_get_buffer_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_check_bandwidth) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_dash_resync_to_segment) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_loop_detected) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_is_dynamic_mpd) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_get_min_buffer_time) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_dash_set_idle_interval) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_set_utc_shift) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_enable_utc_drift_compensation) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_set_max_resolution) ) @@ -2152,30 +2180,329 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_dash_get_tile_adaptation_mode) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_srd_max_size_info) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_srd_info) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_dash_set_threaded_download) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_set_quality_degradation_hint) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_set_visible_rect) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_get_utc_drift_estimate) ) #pragma comment (linker, EXPORT_SYMBOL(gf_dash_set_algo) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dash_set_atsc_ast_shift) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dash_ignore_xlink) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dash_group_get_num_components) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dash_all_groups_done) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dash_set_period_xlink_query_string) ) #endif #if !defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_VTT) -#pragma comment (linker, EXPORT_SYMBOL(gf_webvtt_js_addCue) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_webvtt_js_removeCues) ) #pragma comment (linker, EXPORT_SYMBOL(gf_webvtt_parse_cues_from_data) ) #pragma comment (linker, EXPORT_SYMBOL(gf_webvtt_parse_iso_cues) ) #pragma comment (linker, EXPORT_SYMBOL(gf_webvtt_cue_del) ) #pragma comment (linker, EXPORT_SYMBOL(gf_webvtt_dump_iso_track) ) #endif -#ifndef GPAC_DISABLE_MSE -#pragma comment (linker, EXPORT_SYMBOL(gf_mse_track_buffer_get_next_packet) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_mse_track_buffer_release_packet) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_mse_mediasource_del) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_mse_mediasource_open) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_mse_mediasource_close) ) -#pragma comment (linker, EXPORT_SYMBOL(gf_mse_mediasource_end) ) -#endif - #pragma comment (linker, EXPORT_SYMBOL(utf8_to_ucs4) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_dmx_new) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_dmx_del) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_tune_in) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_set_callback) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_dmx_process) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_dmx_get_object_count) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_dmx_remove_object_by_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_dmx_remove_first_object) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_dmx_find_service) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_dmx_purge_objects) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_dmx_get_first_packet_time) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_dmx_get_last_packet_time) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_dmx_get_nb_packets) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_dmx_get_recv_bytes) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_dmx_set_service_udta) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_dmx_get_service_udta) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_dmx_debug_tsi) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_set_max_objects_store) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_dm_add_cache_entry) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dm_force_headers) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dm_set_localcache_provider) ) + +/*filter session exports*/ +#pragma comment (linker, EXPORT_SYMBOL(gf_sc_connect_from_time_ex) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sc_disconnect) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sc_send_event) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_sc_get_downloader) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_scene_switch_quality) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_scene_set_service_id) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odm_get_object_info) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_bit_depth) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_get_cicp_layout) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_get_layout_from_cicp) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_get_layout_name_from_cicp) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_get_cicp_from_layout) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_stream_type_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_stream_type_all_names) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_stream_types_enum) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_term_print_stats) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_stream_type_afx_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_mvc_config_get) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_avc_hevc_get_chroma_format_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_media_nal_rewrite_samples) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_set_max_objects_store) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_atsc3_dmx_debug_tsi) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_itags_find_by_id3tag) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_itags_find_by_itag) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_itags_find_by_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_itags_get_type) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_itags_get_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_itags_get_itag) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_itags_get_id3tag) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_itags_enum_tags) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_id3_get_genre) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_id3_get_genre_tag) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_pixel_fmt_all_names) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_pixel_fmt_all_shortnames) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_pixel_get_size_info) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_pixel_fmt_enum) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_pixel_get_bytes_per_pixel) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_codecid_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_codecid_enum) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_codecid_mime) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_codecid_alt) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_codecid_type) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_codecid_4cc_type) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_all_names) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_all_shortnames) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_audio_fmt_enum) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_new) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_new_defaults) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_del) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_load_filter) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_run) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_print_stats) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_print_connections) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_set_separators) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_props_get_type_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_props_get_type_desc) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_props_parse_type) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_props_dump_val) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_props_get_description) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_filters_registers_count) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_get_filter_register) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_lock_filters) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_get_filters_count) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_get_filter_stats) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_register_test_filters) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_load_source) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_load_destination) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_props_4cc_get_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_post_user_task ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_abort ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_is_last_task ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_set_source ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_print_all_connections ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_check_filter_register_cap ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_send_update ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_arg ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_arg_str ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_ui_event ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_is_alias ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_stats ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_in_parent_chain ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_set_max_resolution_chain_length ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_get_max_resolution_chain_length ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_get_last_connect_error ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_get_last_process_error ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_filter_exists ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_enable_reporting) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_set_ui_callback) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_enum_unmapped_options) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_send_update) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_fs_load_script) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_reconnect_output) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_add_event_listener ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_remove_event_listener ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_description) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_version) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_author) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_help) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_args) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_info) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_release_property) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_udta ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_max_extra_input_pids ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_discard ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_ref ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_ref_props ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_unref ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_data ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_property ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_property_str ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_property_dyn ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_property ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_property_str ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_enum_properties ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_framing ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_framing ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_dts ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_dts ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_cts ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_cts ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_timescale ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_sap ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_sap ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_roll_info ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_roll_info ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_interlaced ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_interlaced ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_corrupted ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_corrupted ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_duration ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_duration ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_seek_flag ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_seek_flag ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_dependency_flags ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_dependency_flags ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_carousel_version ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_carousel_version ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_byte_offset ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_byte_offset ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_crypt_flags ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_crypt_flags ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_seq_num ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_seq_num ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_clock_type ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_clock_type ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_frame_interface ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_expand ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_truncate ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_merge_properties_filter ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_merge_properties ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_new_alloc ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_new_alloc_destructor ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_new_shared_internal ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_new_shared ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_new_ref ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_new_frame_interface) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_forward ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_send ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_check_caps ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_copy_properties ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_drop_packet ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_set_name ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_name ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_filter_name ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_orig_src_args ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_buffer_occupancy ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_set_udta ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_udta ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_packet_count ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_packet ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_first_packet_cts ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_first_packet_is_empty ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_is_eos ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_set_eos ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_enum_properties ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_would_block ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_query_buffer_duration ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_has_seen_eos ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_event_name ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_send_event ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_send_event ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_exec_event ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_is_filter_in_parents ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_statistics ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_remove ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_try_pull ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_clock_info ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_timescale ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_clear_eos ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_set_clock_mode ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_args ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_set_max_buffer ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_max_buffer ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_set_loose_connect ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_caps_query ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_caps_query_str ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_resolve_file_template ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_set_discard ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_destination ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_source ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_property ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_property_str ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_info) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_info_str) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_set_property ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_set_property_str ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_set_info) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_set_info_str) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_set_framing_mode ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_new ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_allow_direct_dispatch ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_get_alias_udta ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_post_process_task ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_ipid_count ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_ipid ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_opid_count ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_opid ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_ask_rt_reschedule ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_set_setup_failure_callback ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_notification_failure ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_setup_failure ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_post_task ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_remove_src ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_remove ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_connect_source ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_connect_destination ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_load_filter ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_is_supported_source ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_output_buffer_max ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_make_sticky ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_hint_single_clock ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_clock_hint ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_override_caps ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_init_play_event ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_name ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_set_name ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_reset_source ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_negociate_property ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_register_opengl_provider) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_request_opengl) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_count_source_by_protocol) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_disable_probe) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_disable_inputs) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_get_num_events_queued) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_all_sinks_done) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_has_pid_connection_pending) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_reporting_enabled) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_negociate_property_str) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pid_negociate_property_dyn) ) + + +#pragma comment (linker, EXPORT_SYMBOL(gf_bs_enable_emulation_byte_removal ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_odf_av1_cfg_del ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_isom_av1_config_get ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_media_avc_read_sps_bs ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_media_avc_read_pps_bs ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_dasher_set_hls_clock ) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_av1_get_obu_name) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_av1_reset_state) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_av1_init_state) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_get_next_pow2) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_htonl) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_htons) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_ntohl) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_ntohs) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_inline_is_default_viewpoint) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_mpegv12_get_config) ) + +#pragma comment (linker, EXPORT_SYMBOL(gf_smil_set_media_duration) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_smil_timing_init_runtime_info) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_smil_timing_insert_clock) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_smil_timing_is_active) ) diff --git a/src/filter_core/filter.c b/src/filter_core/filter.c new file mode 100644 index 0000000..0cef62d --- /dev/null +++ b/src/filter_core/filter.c @@ -0,0 +1,3854 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / filters sub-project + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terfsess of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "filter_session.h" +#include + +//helper functions +void gf_void_del(void *p) +{ + gf_free(p); +} +void gf_filterpacket_del(void *p) +{ + GF_FilterPacket *pck=(GF_FilterPacket *)p; + if (pck->data) gf_free(pck->data); + gf_free(p); +} + +static void gf_filter_parse_args(GF_Filter *filter, const char *args, GF_FilterArgType arg_type, Bool for_script); + +const char *gf_fs_path_escape_colon(GF_FilterSession *sess, const char *path) +{ + const char *res, *arg; + if (!path) return NULL; + if (sess->sep_args != ':') + return strchr(path, sess->sep_args); + + arg = strchr(path, sess->sep_name); + res = gf_url_colon_suffix(path); + if (arg && res && (res > arg)) + res = gf_url_colon_suffix(arg+1); + return res; +} + +static const char *gf_filter_get_args_stripped(GF_FilterSession *fsess, const char *in_args, Bool is_dst) +{ + char szEscape[7]; + char *args_striped = NULL; + if (in_args) { + char szDst[6]; + const char *key; + if (is_dst) { + key = "dst"; + } else { + key = "src"; + } + if (!strncmp(in_args, key, 3) && (in_args[3]==fsess->sep_name)) { + args_striped = (char *) in_args; + } else { + sprintf(szDst, "%c%s%c", fsess->sep_name, key, fsess->sep_name); + args_striped = strstr(in_args, szDst); + } + + if (args_striped) { + args_striped = (char *)gf_fs_path_escape_colon(fsess, args_striped+4); + if (args_striped) args_striped ++; + } else { + args_striped = (char *)in_args; + } + } + sprintf(szEscape, "gpac%c", fsess->sep_args); + if (args_striped && !strncmp(args_striped, szEscape, 5)) + return args_striped + 5; + + return args_striped; +} + +const char *gf_filter_get_dst_args(GF_Filter *filter) +{ + return gf_filter_get_args_stripped(filter->session, filter->dst_args, GF_TRUE); +} + +char *gf_filter_get_dst_name(GF_Filter *filter) +{ + char szDst[5]; + char *dst, *arg_sep, *res, *dst_args; + sprintf(szDst, "dst%c", filter->session->sep_name); + + dst_args = filter->dst_args; + if (!dst_args) { + GF_FilterPid *outpid = gf_list_get(filter->output_pids, 0); + if (outpid) dst_args = outpid->filter->dst_args; + + if (!dst_args) { + GF_Filter *outf = gf_list_get(filter->destination_links, 0); + if (!outf || !outf->dst_args) + outf = gf_list_get(filter->destination_filters, 0); + if (outf) + dst_args = filter->dst_args; + } + } + dst = dst_args ? strstr(dst_args, szDst) : NULL; + if (!dst) return NULL; + + arg_sep = (char*) gf_fs_path_escape_colon(filter->session, dst+4); + + if (arg_sep) { + arg_sep[0] = 0; + res = gf_strdup(dst+4); + arg_sep[0] = filter->session->sep_args; + } else { + res = gf_strdup(dst+4); + } + return res; +} + +GF_Filter *gf_filter_new(GF_FilterSession *fsess, const GF_FilterRegister *freg, const char *src_args, const char *dst_args, GF_FilterArgType arg_type, GF_Err *err, GF_Filter *multi_sink_target, Bool is_dynamic_filter) +{ + char szName[200]; + const char *dst_striped = NULL; + const char *src_striped = NULL; + GF_Filter *filter; + GF_Err e; + u32 i; + assert(fsess); + + GF_SAFEALLOC(filter, GF_Filter); + if (!filter) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to alloc filter for %s\n", freg->name)); + return NULL; + } + filter->freg = freg; + filter->session = fsess; + filter->max_extra_pids = freg->max_extra_pids; + filter->dynamic_filter = is_dynamic_filter; + + if (fsess->use_locks) { + snprintf(szName, 200, "Filter%sPackets", filter->freg->name); + filter->pcks_mx = gf_mx_new(szName); + } + + //for now we always use a lock on the filter task lists + //this mutex protects the task list and the number of process virtual tasks + //we cannot remove it in non-threaded mode since we have no garantee that a filter won't use threading on its own + snprintf(szName, 200, "Filter%sTasks", filter->freg->name); + filter->tasks_mx = gf_mx_new(szName); + + filter->tasks = gf_fq_new(filter->tasks_mx); + + if (! (filter->session->flags & GF_FS_FLAG_NO_RESERVOIR)) { + filter->pcks_shared_reservoir = gf_fq_new(filter->pcks_mx); + filter->pcks_alloc_reservoir = gf_fq_new(filter->pcks_mx); + filter->pcks_inst_reservoir = gf_fq_new(filter->pcks_mx); + } + + filter->pending_pids = gf_fq_new(NULL); + + filter->blacklisted = gf_list_new(); + filter->destination_filters = gf_list_new(); + filter->destination_links = gf_list_new(); + + filter->bundle_idx_at_resolution = -1; + filter->cap_idx_at_resolution = -1; + + if (fsess->filters_mx) gf_mx_p(fsess->filters_mx); + gf_list_add(fsess->filters, filter); + if (fsess->filters_mx) gf_mx_v(fsess->filters_mx); + + filter->multi_sink_target = multi_sink_target; + + src_striped = src_args; + dst_striped = NULL; + switch (arg_type) { + case GF_FILTER_ARG_EXPLICIT_SOURCE_NO_DST_INHERIT: + filter->arg_type = arg_type = GF_FILTER_ARG_EXPLICIT_SOURCE; + filter->no_dst_arg_inherit = GF_TRUE; + break; + case GF_FILTER_ARG_INHERIT_SOURCE_ONLY: + filter->arg_type = arg_type = GF_FILTER_ARG_INHERIT; + filter->no_dst_arg_inherit = GF_TRUE; + break; + default: + filter->arg_type = arg_type; + dst_striped = gf_filter_get_args_stripped(fsess, dst_args, GF_TRUE); + break; + } + + if ((arg_type!=GF_FILTER_ARG_EXPLICIT_SOURCE) && (arg_type!=GF_FILTER_ARG_EXPLICIT)) { + src_striped = gf_filter_get_args_stripped(fsess, src_args, GF_FALSE); + } + + //if we already concatenated our dst args to this source filter (eg this is an intermediate dynamically loaded one) + //don't reappend the args + if (dst_striped && src_striped && strstr(src_striped, dst_striped) != NULL) { + dst_striped = NULL; + } + + if (src_striped && dst_striped) { + char *all_args; + const char *dbsep; + char *localarg_marker; + u32 nb_db_sep=0; + char szDBSep[3]; + Bool insert_escape = GF_FALSE; + u32 len = 2 + (u32) strlen(src_striped) + (u32) strlen(dst_striped); + //source has a URL (locat or not), escape it to make sure we don't pass dst args as params to the URL + if ((strstr(src_striped, "src=") || strstr(src_striped, "dst=")) && strstr(src_striped, "://")){ + char szEscape[10]; + sprintf(szEscape, "%cgpac", fsess->sep_args); + if (strstr(src_striped, szEscape)==NULL) { + insert_escape = GF_TRUE; + len += 5; + } + } + + szDBSep[0] = szDBSep[1] = filter->session->sep_args; + szDBSep[2] = 0; + dbsep = src_striped; + while (dbsep) { + char *next_dbsep = strstr(dbsep, szDBSep); + if (!next_dbsep) break; + nb_db_sep++; + dbsep = next_dbsep+2; + } + if (nb_db_sep % 2) nb_db_sep=1; + else nb_db_sep=0; + + all_args = gf_malloc(sizeof(char)*(len+nb_db_sep)); + if (!nb_db_sep) { + szDBSep[1] = 0; + } + + if (insert_escape) { + sprintf(all_args, "%s%sgpac%c%s", src_striped, szDBSep, fsess->sep_args, dst_striped); + } else { + sprintf(all_args, "%s%s%s", src_striped, szDBSep, dst_striped); + } + localarg_marker = strstr(all_args, "gfloc"); + if (localarg_marker) { + localarg_marker[0]=0; + if (strlen(all_args) && (localarg_marker[-1]==fsess->sep_args)) + localarg_marker[-1]=0; + } + e = gf_filter_new_finalize(filter, all_args, arg_type); + filter->orig_args = all_args; + src_striped = NULL; + } else if (dst_striped) { + e = gf_filter_new_finalize(filter, dst_striped, arg_type); + filter->orig_args = gf_strdup(dst_striped); + src_striped = NULL; + } else { + e = gf_filter_new_finalize(filter, src_striped, arg_type); + } + filter->dst_args = dst_args ? gf_strdup(dst_args) : NULL; + + if (e) { + if (!filter->setup_notified && (e<0) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Error %s while instantiating filter %s\n", gf_error_to_string(e),freg->name)); + gf_filter_setup_failure(filter, e); + } + if (err) *err = e; + //filter requested cancelation of filter session upon init + if (e==GF_EOS) { + fsess->run_status = GF_EOS; + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s requested cancelation of filter session\n", freg->name)); + } + return NULL; + } + if (filter && src_striped) filter->orig_args = gf_strdup(src_striped); + + for (i=0; inb_caps; i++) { + if (freg->caps[i].flags & GF_CAPFLAG_OUTPUT) { + filter->has_out_caps = GF_TRUE; + break; + } + } + if (filter) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Created filter register %s args %s\n", freg->name, filter->orig_args ? filter->orig_args : "none")); + } + return filter; +} + +GF_Err gf_filter_new_finalize(GF_Filter *filter, const char *args, GF_FilterArgType arg_type) +{ + gf_filter_set_name(filter, NULL); + + gf_filter_parse_args(filter, args, arg_type, GF_FALSE); + + if (filter->freg->initialize) { + GF_Err e; + FSESS_CHECK_THREAD(filter) + e = filter->freg->initialize(filter); + if (e) return e; + } + if ((filter->freg->flags & GF_FS_REG_SCRIPT) && filter->freg->update_arg) { + GF_Err e; + gf_filter_parse_args(filter, args, arg_type, GF_TRUE); + e = filter->freg->update_arg(filter, NULL, NULL); + if (e) return e; + } + + //flush all pending pid init requests + if (filter->has_pending_pids) { + filter->has_pending_pids=GF_FALSE; + while (gf_fq_count(filter->pending_pids)) { + GF_FilterPid *pid=gf_fq_pop(filter->pending_pids); + gf_filter_pid_post_init_task(filter, pid); + } + } + +#ifdef GPAC_HAS_QJS + jsfs_on_filter_created(filter); +#endif + + return GF_OK; +} + +void gf_filter_reset_pending_packets(GF_Filter *filter) +{ + //may happen when a filter is removed from the chain + if (filter->postponed_packets) { + while (gf_list_count(filter->postponed_packets)) { + GF_FilterPacket *pck = gf_list_pop_front(filter->postponed_packets); + gf_filter_packet_destroy(pck); + } + gf_list_del(filter->postponed_packets); + filter->postponed_packets = NULL; + } +} + +static void reset_filter_args(GF_Filter *filter); + +//when destroying the filter queue we have to skip tasks marked as notified, since they are also present in the +//session task list +void task_del(void *_task) +{ + GF_FSTask *task = _task; + if (!task->notified) gf_free(task); +} + +void gf_filter_del(GF_Filter *filter) +{ + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s destruction\n", filter->name)); + assert(filter); +// assert(!filter->detached_pid_inst); + assert(!filter->detach_pid_tasks_pending); +// assert(!filter->swap_pidinst_dst); + assert(!filter->swap_pidinst_src); + + +#ifdef GPAC_HAS_QJS + jsfs_on_filter_destroyed(filter); +#endif + +#ifndef GPAC_DISABLE_3D + gf_list_del_item(filter->session->gl_providers, filter); + gf_fs_check_gl_provider(filter->session); +#endif + +#ifdef GPAC_MEMORY_TRACKING + if (filter->session->check_allocs) { + if (filter->max_nb_process>10 && (filter->max_nb_consecutive_process * 10 < filter->max_nb_process)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("\nFilter %s extensively uses memory alloc/free in process(): \n", filter->name)); + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("\tmax stats of over %d calls (%d consecutive calls with no alloc/free):\n", filter->max_nb_process, filter->max_nb_consecutive_process)); + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("\t\t%d allocs %d callocs %d reallocs %d free\n", filter->max_stats_nb_alloc, filter->max_stats_nb_calloc, filter->max_stats_nb_realloc, filter->max_stats_nb_free)); + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("\tPlease consider rewriting the code\n")); + } + } +#endif + + //may happen when a filter is removed from the chain + if (filter->postponed_packets) { + while (gf_list_count(filter->postponed_packets)) { + GF_FilterPacket *pck = gf_list_pop_front(filter->postponed_packets); + gf_filter_packet_destroy(pck); + } + gf_list_del(filter->postponed_packets); + filter->postponed_packets = NULL; + } + + //delete output pids before the packet reservoir + while (gf_list_count(filter->output_pids)) { + gf_filter_pid_del(gf_list_pop_back(filter->output_pids)); + } + gf_list_del(filter->output_pids); + + gf_list_del(filter->blacklisted); + gf_list_del(filter->destination_filters); + gf_list_del(filter->destination_links); + gf_list_del(filter->source_filters); + gf_list_del(filter->input_pids); + gf_fq_del(filter->tasks, task_del); + gf_fq_del(filter->pending_pids, NULL); + + reset_filter_args(filter); + if (filter->src_args) gf_free(filter->src_args); + + if (filter->pcks_shared_reservoir) + gf_fq_del(filter->pcks_shared_reservoir, gf_void_del); + if (filter->pcks_inst_reservoir) + gf_fq_del(filter->pcks_inst_reservoir, gf_void_del); + if (filter->pcks_alloc_reservoir) + gf_fq_del(filter->pcks_alloc_reservoir, gf_filterpacket_del); + + gf_mx_del(filter->pcks_mx); + if (filter->tasks_mx) + gf_mx_del(filter->tasks_mx); + + if (filter->id) gf_free(filter->id); + if (filter->source_ids) gf_free(filter->source_ids); + if (filter->dynamic_source_ids) gf_free(filter->dynamic_source_ids); + if (filter->filter_udta) gf_free(filter->filter_udta); + if (filter->orig_args) gf_free(filter->orig_args); + if (filter->dst_args) gf_free(filter->dst_args); + if (filter->name) gf_free(filter->name); + if (filter->status_str) gf_free(filter->status_str); + + if (!filter->session->in_final_flush && !filter->session->run_status) { + u32 i, count; + gf_mx_p(filter->session->filters_mx); + count = gf_list_count(filter->session->filters); + for (i=0; isession->filters, i); + gf_list_del_item(a_filter->destination_filters, filter); + gf_list_del_item(a_filter->destination_links, filter); + gf_list_del_item(a_filter->source_filters, filter); + if (a_filter->cap_dst_filter==filter) + a_filter->cap_dst_filter = NULL; + if (a_filter->cloned_from == filter) + a_filter->cloned_from = NULL; + if (a_filter->on_setup_error_filter == filter) + a_filter->on_setup_error_filter = NULL; + if (a_filter->target_filter == filter) + a_filter->target_filter = NULL; + if (a_filter->dst_filter == filter) + a_filter->dst_filter = NULL; + } + gf_mx_v(filter->session->filters_mx); + } + if (filter->instance_description) + gf_free(filter->instance_description); + if (filter->instance_version) + gf_free(filter->instance_version); + if (filter->instance_author) + gf_free(filter->instance_author); + if (filter->instance_help) + gf_free(filter->instance_help); + +#ifdef GPAC_HAS_QJS + if (filter->iname) + gf_free(filter->iname); +#endif + + gf_free(filter); +} + +GF_EXPORT +void *gf_filter_get_udta(GF_Filter *filter) +{ + assert(filter); + + return filter->filter_udta; +} + +GF_EXPORT +const char * gf_filter_get_name(GF_Filter *filter) +{ + assert(filter); + if (filter->name) + return (const char *)filter->name; + return (const char *)filter->freg->name; +} + +GF_EXPORT +void gf_filter_set_name(GF_Filter *filter, const char *name) +{ + assert(filter); + + if (filter->name) gf_free(filter->name); + filter->name = gf_strdup(name ? name : filter->freg->name); +} + +static void gf_filter_set_id(GF_Filter *filter, const char *ID) +{ + assert(filter); + + if (filter->id) gf_free(filter->id); + filter->id = ID ? gf_strdup(ID) : NULL; +} + +GF_EXPORT +void gf_filter_reset_source(GF_Filter *filter) +{ + if (filter && filter->source_ids) { + gf_free(filter->source_ids); + filter->source_ids = NULL; + } +} + +static void gf_filter_set_sources(GF_Filter *filter, const char *sources_ID) +{ + assert(filter); + if (!sources_ID) { + if (filter->source_ids) gf_free(filter->source_ids); + filter->source_ids = NULL; + return; + } + if (!filter->source_ids) { + filter->source_ids = gf_strdup(sources_ID); + return; + } + gf_dynstrcat(&filter->source_ids, sources_ID, ","); +} + +static void gf_filter_set_arg(GF_Filter *filter, const GF_FilterArgs *a, GF_PropertyValue *argv) +{ +#ifdef WIN32 + void *ptr = (void *) (((char*) filter->filter_udta) + a->offset_in_private); +#else + void *ptr = filter->filter_udta + a->offset_in_private; +#endif + Bool res = GF_FALSE; + if (a->offset_in_private<0) return; + + switch (argv->type) { + case GF_PROP_BOOL: + if (a->offset_in_private + sizeof(Bool) <= filter->freg->private_size) { + *(Bool *)ptr = argv->value.boolean; + res = GF_TRUE; + } + break; + case GF_PROP_SINT: + if (a->offset_in_private + sizeof(s32) <= filter->freg->private_size) { + *(s32 *)ptr = argv->value.sint; + res = GF_TRUE; + } + break; + case GF_PROP_UINT: + case GF_PROP_PIXFMT: + case GF_PROP_PCMFMT: + if (a->offset_in_private + sizeof(u32) <= filter->freg->private_size) { + *(u32 *)ptr = argv->value.uint; + res = GF_TRUE; + } + break; + case GF_PROP_LSINT: + if (a->offset_in_private + sizeof(s64) <= filter->freg->private_size) { + *(s64 *)ptr = argv->value.longsint; + res = GF_TRUE; + } + break; + case GF_PROP_LUINT: + if (a->offset_in_private + sizeof(u64) <= filter->freg->private_size) { + *(u64 *)ptr = argv->value.longuint; + res = GF_TRUE; + } + break; + case GF_PROP_FLOAT: + if (a->offset_in_private + sizeof(Fixed) <= filter->freg->private_size) { + *(Fixed *)ptr = argv->value.fnumber; + res = GF_TRUE; + } + break; + case GF_PROP_DOUBLE: + if (a->offset_in_private + sizeof(Double) <= filter->freg->private_size) { + *(Double *)ptr = argv->value.number; + res = GF_TRUE; + } + break; + case GF_PROP_FRACTION: + if (a->offset_in_private + sizeof(GF_Fraction) <= filter->freg->private_size) { + *(GF_Fraction *)ptr = argv->value.frac; + res = GF_TRUE; + } + break; + case GF_PROP_FRACTION64: + if (a->offset_in_private + sizeof(GF_Fraction64) <= filter->freg->private_size) { + *(GF_Fraction64 *)ptr = argv->value.lfrac; + res = GF_TRUE; + } + break; + case GF_PROP_VEC2I: + if (a->offset_in_private + sizeof(GF_PropVec2i) <= filter->freg->private_size) { + *(GF_PropVec2i *)ptr = argv->value.vec2i; + res = GF_TRUE; + } + break; + case GF_PROP_VEC2: + if (a->offset_in_private + sizeof(GF_PropVec2) <= filter->freg->private_size) { + *(GF_PropVec2 *)ptr = argv->value.vec2; + res = GF_TRUE; + } + break; + case GF_PROP_VEC3I: + if (a->offset_in_private + sizeof(GF_PropVec3i) <= filter->freg->private_size) { + *(GF_PropVec3i *)ptr = argv->value.vec3i; + res = GF_TRUE; + } + break; + case GF_PROP_VEC3: + if (a->offset_in_private + sizeof(GF_PropVec3) <= filter->freg->private_size) { + *(GF_PropVec3 *)ptr = argv->value.vec3; + res = GF_TRUE; + } + break; + case GF_PROP_VEC4I: + if (a->offset_in_private + sizeof(GF_PropVec4i) <= filter->freg->private_size) { + *(GF_PropVec4i *)ptr = argv->value.vec4i; + res = GF_TRUE; + } + break; + case GF_PROP_VEC4: + if (a->offset_in_private + sizeof(GF_PropVec4) <= filter->freg->private_size) { + *(GF_PropVec4 *)ptr = argv->value.vec4; + res = GF_TRUE; + } + break; + + case GF_PROP_NAME: + case GF_PROP_STRING: + if (a->offset_in_private + sizeof(char *) <= filter->freg->private_size) { + if (*(char **)ptr) gf_free( * (char **)ptr); + //we don't strdup since we don't free the string at the caller site + *(char **)ptr = argv->value.string; + res = GF_TRUE; + } + break; + case GF_PROP_DATA: + case GF_PROP_DATA_NO_COPY: + case GF_PROP_CONST_DATA: + if (a->offset_in_private + sizeof(GF_PropData) <= filter->freg->private_size) { + GF_PropData *pd = (GF_PropData *) ptr; + if ((argv->type!=GF_PROP_CONST_DATA) && pd->ptr) gf_free(pd->ptr); + //we don't free/alloc since we don't free the string at the caller site + pd->size = argv->value.data.size; + pd->ptr = argv->value.data.ptr; + res = GF_TRUE; + } + break; + case GF_PROP_POINTER: + if (a->offset_in_private + sizeof(void *) <= filter->freg->private_size) { + *(void **)ptr = argv->value.ptr; + res = GF_TRUE; + } + break; + case GF_PROP_STRING_LIST: + if (a->offset_in_private + sizeof(void *) <= filter->freg->private_size) { + GF_List *l = *(GF_List **)ptr; + if (l) { + while (gf_list_count(l)) { + char *s = gf_list_pop_back(l); + gf_free(s); + } + gf_list_del(l); + } + //we don't clone since we don't free the string at the caller site + *(GF_List **)ptr = argv->value.string_list; + res = GF_TRUE; + } + break; + case GF_PROP_UINT_LIST: + if (a->offset_in_private + sizeof(void *) <= filter->freg->private_size) { + GF_PropUIntList *l = (GF_PropUIntList *)ptr; + if (l->vals) gf_free(l->vals); + *l = argv->value.uint_list; + res = GF_TRUE; + } + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Property type %s not supported for filter argument\n", gf_props_get_type_name(argv->type) )); + return; + break; + } + if (!res) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to set argument %s: memory offset %d overwrite structure size %f\n", a->arg_name, a->offset_in_private, filter->freg->private_size)); + } +} + + +static void filter_translate_autoinc(GF_Filter *filter, char *value) +{ + u32 ainc_crc, i; + GF_FSAutoIncNum *auto_int=NULL; + u32 inc_count; + Bool assigned=GF_FALSE; + s32 max_int = 0; + s32 increment=1; + char szInt[100]; + + while (1) { + char *step_sep; + char *inc_end, *inc_sep; + inc_sep = strstr(value, "$GINC("); + if (!inc_sep) return; + inc_end = strstr(inc_sep, ")"); + if (!inc_end) return; + + inc_sep[0] = 0; + inc_end[0] = 0; + inc_end+=1; + strcpy(szInt, inc_sep+6); + ainc_crc = (u32) gf_crc_32(szInt, (u32) strlen(szInt) ); + step_sep = strchr(szInt, ','); + if (step_sep) { + step_sep[0] = 0; + sscanf(step_sep+1, "%d", &increment); + } + + inc_count = gf_list_count(filter->session->auto_inc_nums); + for (i=0; isession->auto_inc_nums, i); + if (auto_int->crc != ainc_crc) { + auto_int=NULL; + continue; + } + if (auto_int->filter == filter) { + sprintf(szInt, "%d", auto_int->inc_val); + break; + } + if (!assigned) + max_int = auto_int->inc_val; + else if ((increment>0) && (max_int < auto_int->inc_val)) + max_int = auto_int->inc_val; + else if ((increment<0) && (max_int > auto_int->inc_val)) + max_int = auto_int->inc_val; + + assigned = GF_TRUE; + auto_int = NULL; + } + if (!auto_int) { + GF_SAFEALLOC(auto_int, GF_FSAutoIncNum); + if (auto_int) { + auto_int->filter = filter; + auto_int->crc = ainc_crc; + if (assigned) auto_int->inc_val = max_int + increment; + else sscanf(szInt, "%d", &auto_int->inc_val); + gf_list_add(filter->session->auto_inc_nums, auto_int); + } + } + if (auto_int) { + sprintf(szInt, "%d", auto_int->inc_val); + strcat(value, szInt); + strcat(value, inc_end); + } + } +} + +static GF_PropertyValue gf_filter_parse_prop_solve_env_var(GF_Filter *filter, u32 type, const char *name, const char *value, const char *enum_values) +{ + char szPath[GF_MAX_PATH]; + GF_PropertyValue argv; + + if (!value) return gf_props_parse_value(type, name, NULL, enum_values, filter->session->sep_list); + + + if (!strnicmp(value, "$GSHARE", 7)) { + if (gf_opts_default_shared_directory(szPath)) { + strcat(szPath, value+7); + value = szPath; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to query GPAC shared resource directory location\n")); + } + } + else if (!strnicmp(value, "$GJS", 4)) { + Bool found = GF_FALSE; + const char *all_dirs = gf_opts_get_key("core", "js-dirs"); + if (all_dirs) { + const char *dirs = all_dirs; + while (dirs && dirs[0]) { + char *sep = strchr(dirs, ','); + if (sep) { + u32 cplen = (u32) (sep-dirs); + if (cplen>=GF_MAX_PATH) cplen = GF_MAX_PATH-1; + strncpy(szPath, dirs, cplen); + szPath[cplen]=0; + dirs = sep+1; + } else { + strcpy(szPath, dirs); + } + if (!strcmp(szPath, "$GJS")) { + gf_opts_default_shared_directory(szPath); + strcat(szPath, "/scripts/jsf"); + } + strcat(szPath, value+4); + if (gf_file_exists(szPath)) { + value = szPath; + found = GF_TRUE; + break; + } + if (!sep) break; + } + if (!found) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed solve to %s in GPAC script directories %s, file not found\n", value, all_dirs)); + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to query GPAC shared resource directory location\n")); + } + } + else if (!strnicmp(value, "$GLANG", 6)) { + value = gf_opts_get_key("core", "lang"); + if (!value) value = "en"; + } + else if (!strnicmp(value, "$GUA", 4)) { + value = gf_opts_get_key("core", "user-agent"); + if (!value) value = "GPAC " GPAC_VERSION; + } else if (strstr(value, "$GINC(")) { + char *a_value = gf_strdup(value); + filter_translate_autoinc(filter, a_value); + argv = gf_props_parse_value(type, name, a_value, enum_values, filter->session->sep_list); + gf_free(a_value); + return argv; + } + argv = gf_props_parse_value(type, name, value, enum_values, filter->session->sep_list); + return argv; +} + +void gf_filter_update_arg_task(GF_FSTask *task) +{ + u32 i=0; + Bool found = GF_FALSE; + GF_FilterUpdate *arg=task->udta; + //find arg + i=0; + while (task->filter->freg->args) { + GF_PropertyValue argv; + const GF_FilterArgs *a = &task->filter->freg->args[i]; + i++; + Bool is_meta = GF_FALSE; + if (!a || !a->arg_name) break; + + if ((a->flags & GF_FS_ARG_META) && !strcmp(a->arg_name, "*")) { + if (!task->filter->freg->update_arg) + continue; + is_meta = GF_TRUE; + } else if (strcmp(a->arg_name, arg->name)) { + continue; + } + + found = GF_TRUE; + if (!is_meta && ! (a->flags & GF_FS_ARG_UPDATE) ) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Argument %s of filter %s is not updatable - ignoring\n", a->arg_name, task->filter->name)); + break; + } + + argv = gf_filter_parse_prop_solve_env_var(task->filter, a->arg_type, a->arg_name, arg->val, a->min_max_enum); + + if (argv.type != GF_PROP_FORBIDEN) { + GF_Err e = GF_OK; + FSESS_CHECK_THREAD(task->filter) + //if no update function consider the arg OK + if (task->filter->freg->update_arg) { + e = task->filter->freg->update_arg(task->filter, arg->name, &argv); + } + if (e==GF_OK) { + if (!is_meta) + gf_filter_set_arg(task->filter, a, &argv); + } else if (e!=GF_NOT_FOUND) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Filter %s did not accept update of arg %s to value %s: %s\n", task->filter->name, arg->name, arg->val, gf_error_to_string(e) )); + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to parse argument %s value %s\n", a->arg_name, a->arg_default_val)); + } + break; + } + + if (!found) { + if (arg->recursive) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Failed to locate argument %s in filter %s, propagating %s the filter chain\n", arg->name, task->filter->freg->name, + (arg->recursive & (GF_FILTER_UPDATE_UPSTREAM|GF_FILTER_UPDATE_DOWNSTREAM)) ? "up and down" : ((arg->recursive & GF_FILTER_UPDATE_UPSTREAM) ? "up" : "down") )); + + GF_List *flist = gf_list_new(); + if (arg->recursive & GF_FILTER_UPDATE_UPSTREAM) { + for (i=0; ifilter->num_output_pids; i++) { + GF_FilterPid *pid = gf_list_get(task->filter->output_pids, i); + if (gf_list_find(flist, pid->filter)<0) gf_list_add(flist, pid->filter); + } + for (i=0; ifilter->session, NULL, a_f, arg->name, arg->val, GF_FILTER_UPDATE_UPSTREAM); + } + gf_list_reset(flist); + + } + if (arg->recursive & GF_FILTER_UPDATE_DOWNSTREAM) { + for (i=0; ifilter->num_input_pids; i++) { + GF_FilterPidInst *pidi = gf_list_get(task->filter->input_pids, i); + if (gf_list_find(flist, pidi->pid->filter)<0) gf_list_add(flist, pidi->pid->filter); + } + + for (i=0; ifilter->session, NULL, a_f, arg->name, arg->val, GF_FILTER_UPDATE_DOWNSTREAM); + } + } + + gf_list_del(flist); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Failed to locate argument %s in filter %s\n", arg->name, task->filter->freg->name)); + } + } + gf_free(arg->name); + gf_free(arg->val); + gf_free(arg); +} + +static const char *gf_filter_load_arg_config(GF_Filter *filter, const char *sec_name, const char *arg_name, const char *arg_val) +{ + Bool gf_sys_has_filter_global_args(); + const char *opt; + GF_FilterSession *session = filter->session; + + //look in global args + if (gf_sys_has_filter_global_args()) { + u32 alen = (u32) strlen(arg_name); + u32 i, nb_args = gf_sys_get_argc(); + for (i=0; ifreg->name, arg, len)) + continue; + arg += len+1; + } + + if (!strncmp(arg, arg_name, alen)) { + u32 len=0; + char *sep = strchr(arg, '='); + if (sep) { + len = (u32) (sep - (arg)); + } else { + len = (u32) strlen(arg); + } + if (len != alen) continue; + gf_fs_push_arg(session, arg_name, GF_TRUE, 0); + + if (sep) return sep+1; + //no arg value means boolean true + else return "true"; + } + } + } + + //look in config file + opt = gf_opts_get_key(sec_name, arg_name); + if (opt) + return opt; + + //ifce (used by socket and keep MP4Client behaviour: some options are set in MP4Client main apply them + if (!strcmp(arg_name, "ifce")) { + opt = gf_opts_get_key("core", "ifce"); + if (opt) + return opt; + } + + return arg_val; +} + +static void gf_filter_load_meta_args_config(const char *sec_name, GF_Filter *filter) +{ + GF_PropertyValue argv; + Bool gf_sys_has_filter_global_meta_args(); + u32 i, key_count = gf_opts_get_key_count(sec_name); + + FSESS_CHECK_THREAD(filter) + + for (i=0; ifreg->args) { + const GF_FilterArgs *a = &filter->freg->args[k]; + if (!a || !a->arg_name) break; + k++; + if (!strcmp(a->arg_name, arg_name)) { + arg_found = GF_TRUE; + break; + } + } + if (arg_found) continue; + + arg_val = gf_opts_get_key(sec_name, arg_name); + if (!arg_val) continue; + + memset(&argv, 0, sizeof(GF_PropertyValue)); + argv.type = GF_PROP_STRING; + argv.value.string = (char *) arg_val; + filter->freg->update_arg(filter, arg_name, &argv); + } + if (!gf_sys_has_filter_global_meta_args()) return; + + key_count = gf_sys_get_argc(); + for (i=0; ifreg->name, arg, len)) + continue; + arg += len+1; + } + + sep = strchr(arg, '='); + memset(&argv, 0, sizeof(GF_PropertyValue)); + argv.type = GF_PROP_STRING; + if (sep) { + u32 cplen = (u32) (sep - arg); + if (cplen>=META_MAX_ARG) cplen=META_MAX_ARG; + strncpy(szArg, arg, cplen); + szArg[META_MAX_ARG] = 0; + argv.value.string = (char *) sep+1; + } else { + u32 cplen = (u32) strlen(arg); + if (cplen>=META_MAX_ARG) cplen=META_MAX_ARG; + strncpy(szArg, arg, cplen); + szArg[META_MAX_ARG] = 0; + } +#undef META_MAX_ARG + + e = filter->freg->update_arg(filter, szArg, &argv); + gf_fs_push_arg(filter->session, szArg, (e==GF_OK) ? GF_TRUE : GF_FALSE, 2); + } +} + +static void filter_parse_dyn_args(GF_Filter *filter, const char *args, GF_FilterArgType arg_type, Bool for_script, char *szSrc, char *szDst, char *szEscape, char *szSecName, Bool has_meta_args, u32 argfile_level) +{ + char *szArg=NULL; + u32 i=0; + u32 alloc_len=1024; + const GF_FilterArgs *f_args = NULL; + Bool opts_optional = GF_FALSE; + + if (args) + szArg = gf_malloc(sizeof(char)*1024); + + //parse each arg + while (args) { + char *value; + u32 len; + Bool found=GF_FALSE; + char *escaped = NULL; + Bool opaque_arg = GF_FALSE; + Bool absolute_url = GF_FALSE; + Bool internal_url = GF_FALSE; + Bool internal_arg = GF_FALSE; + char *sep = NULL; + + //look for our arg separator - if arg[0] is also a separator, consider the entire string until next double sep as the parameter + if (args[0] != filter->session->sep_args) + sep = strchr(args, filter->session->sep_args); + else { + while (args[0] == filter->session->sep_args) { + args++; + } + sep = (char *) args + 1; + while (1) { + sep = strchr(sep, filter->session->sep_args); + if (!sep) break; + if (sep[1]==filter->session->sep_args) { + break; + } + sep = sep+1; + } + opaque_arg = GF_TRUE; + } + + if (!opaque_arg) { + //we don't use gf_fs_path_escape_colon here because we also analyse whether the URL is internal or not, and we don't want to do that on each arg + + if (filter->session->sep_args == ':') { + while (sep && !strncmp(sep, "://", 3)) { + absolute_url = GF_TRUE; + //filter internal url schemes + if ((!strncmp(args, szSrc, 4) || !strncmp(args, szDst, 4) ) && + (!strncmp(args+4, "video://", 8) + || !strncmp(args+4, "audio://", 8) + || !strncmp(args+4, "av://", 5) + || !strncmp(args+4, "gmem://", 7) + || !strncmp(args+4, "gpac://", 7) + || !strncmp(args+4, "pipe://", 7) + || !strncmp(args+4, "tcp://", 6) + || !strncmp(args+4, "udp://", 6) + || !strncmp(args+4, "tcpu://", 7) + || !strncmp(args+4, "udpu://", 7) + || !strncmp(args+4, "rtp://", 6) + || !strncmp(args+4, "atsc://", 7) + || !strncmp(args+4, "gfio://", 7) + ) + ) { + internal_url = GF_TRUE; + sep = strchr(sep+3, ':'); + if (!strncmp(args+4, "tcp://", 6) + || !strncmp(args+4, "udp://", 6) + || !strncmp(args+4, "tcpu://", 7) + || !strncmp(args+4, "udpu://", 7) + || !strncmp(args+4, "rtp://", 6) + ) { + char *sep2 = sep ? strchr(sep+1, ':') : NULL; + char *sep3 = sep ? strchr(sep+1, '/') : NULL; + if (sep2 && sep3 && (sep2>sep3)) { + sep2 = strchr(sep3, ':'); + } + if (sep2 || sep3 || sep) { + u32 port = 0; + if (sep2) { + sep2[0] = 0; + if (sep3) sep3[0] = 0; + } + else if (sep3) sep3[0] = 0; + if (sscanf(sep+1, "%d", &port)==1) { + char szPort[20]; + snprintf(szPort, 20, "%d", port); + if (strcmp(sep+1, szPort)) + port = 0; + } + if (sep2) sep2[0] = ':'; + if (sep3) sep3[0] = '/'; + + if (port) sep = sep2; + } + } + + } else { + char *sep2 = strstr(sep+3, "::"); + //escape sequence present after this argument, use it + if (sep2) { + sep = sep2; + } else { + //get root / + sep = strchr(sep+3, '/'); + //get first : after root + if (sep) sep = strchr(sep+1, ':'); + } + } + } + + //watchout for "C:\\" or "C:/" + while (sep && (sep[1]=='\\' || sep[1]=='/')) { + sep = strchr(sep+1, ':'); + } + //escape data/time + if (sep) { + char *prev_date = sep-3; + if (prev_date[0]=='T') {} + else if (prev_date[1]=='T') { prev_date ++; } + else { prev_date = NULL; } + +skip_date: + if (prev_date) { + char *search_after = NULL; + //assume date Th:m:s or Th:mm:s or Thh:m:s or Thh:mm:s + if ((prev_date[2] == ':') && (prev_date[4] == ':')) search_after = prev_date+5; + else if ((prev_date[2] == ':') && (prev_date[5] == ':')) search_after = prev_date+6; + else if ((prev_date[3] == ':') && (prev_date[5] == ':')) search_after = prev_date+6; + else if ((prev_date[3] == ':') && (prev_date[6] == ':')) search_after = prev_date+7; + + if (search_after) { + //take care of lists + char *next_date = strchr(search_after, 'T'); + char *next_sep = strchr(search_after, ':'); + if (next_date && next_sep && (next_datesession->sep_list) { + prev_date = next_date; + goto skip_date; + } + } + sep = strchr(search_after, ':'); + } + } + } + } + if (sep) { + escaped = strstr(sep, szEscape); + if (escaped) sep = escaped; + } + + if (sep && !strncmp(args, szSrc, 4) && !escaped && absolute_url && !internal_url) { + Bool file_exists; + sep[0]=0; + if (!strcmp(args+4, "null")) file_exists = GF_TRUE; + else if (!strncmp(args+4, "tcp://", 6)) file_exists = GF_TRUE; + else if (!strncmp(args+4, "udp://", 6)) file_exists = GF_TRUE; + else file_exists = gf_file_exists(args+4); + + if (!file_exists) { + char *fsep = strchr(args+4, filter->session->sep_frag); + if (fsep) { + fsep[0] = 0; + file_exists = gf_file_exists(args+4); + fsep[0] = filter->session->sep_frag; + } + } + sep[0]= filter->session->sep_args; + if (!file_exists) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Non-escaped argument pattern \"%s\" in src %s, assuming arguments are part of source URL. Use src=PATH:gpac:ARGS to differentiate, or change separators\n", sep, args)); + sep = NULL; + } + } + } + + + //escape some XML inputs + if (sep) { + char *xml_start = strchr(args, '<'); + len = (u32) (sep-args); + if (xml_start) { + u32 xlen = (u32) (xml_start-args); + if ((xlen < len) && (args[len-1] != '>')) { + while (1) { + sep = strchr(sep+1, filter->session->sep_args); + if (!sep) { + len = (u32) strlen(args); + break; + } + len = (u32) (sep-args); + if (args[len-1] != '>') break; + } + } + + } + } + else len = (u32) strlen(args); + + if (len>=alloc_len) { + alloc_len = len+1; + szArg = gf_realloc(szArg, sizeof(char)*len); + } + strncpy(szArg, args, len); + szArg[len]=0; + + value = strchr(szArg, filter->session->sep_name); + if (value) { + value[0] = 0; + value++; + } + + if (szArg[0] == filter->session->sep_frag) { + filter->user_pid_props = GF_TRUE; + goto skip_arg; + } + + if ((arg_type == GF_FILTER_ARG_INHERIT) && (!strcmp(szArg, "src") || !strcmp(szArg, "dst")) ) + goto skip_arg; + + i=0; + f_args = filter->freg->args; + if (for_script) + f_args = filter->instance_args; + + while (filter->filter_udta && f_args) { + Bool is_my_arg = GF_FALSE; + Bool reverse_bool = GF_FALSE; + const char *restricted=NULL; + const GF_FilterArgs *a = &f_args[i]; + i++; + if (!a || !a->arg_name) break; + + if (!strcmp(a->arg_name, szArg)) + is_my_arg = GF_TRUE; + else if ( (szArg[0]==filter->session->sep_neg) && !strcmp(a->arg_name, szArg+1)) { + is_my_arg = GF_TRUE; + reverse_bool = GF_TRUE; + } + //little optim here, if no value check for enums + else if (!value && a->min_max_enum && strchr(a->min_max_enum, '|') ) { + char *arg_found = strstr(a->min_max_enum, szArg); + if (arg_found) { + char c = arg_found[strlen(szArg)]; + if (!c || (c=='|')) { + is_my_arg = GF_TRUE; + value = szArg; + } + } + } + + if (is_my_arg) restricted = gf_opts_get_key_restricted(szSecName, a->arg_name); + if (restricted) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Argument %s of filter %s is restricted to %s by system-wide configuration, ignoring\n", szArg, filter->freg->name, restricted)); + found=GF_TRUE; + break; + } + + if (is_my_arg) { + GF_PropertyValue argv; + found=GF_TRUE; + + argv = gf_filter_parse_prop_solve_env_var(filter, (a->flags & GF_FS_ARG_META) ? GF_PROP_STRING : a->arg_type, a->arg_name, value, a->min_max_enum); + + if (reverse_bool && (argv.type==GF_PROP_BOOL)) + argv.value.boolean = !argv.value.boolean; + + if (argv.type != GF_PROP_FORBIDEN) { + if (!for_script && (a->offset_in_private>=0)) { + gf_filter_set_arg(filter, a, &argv); + } else if (filter->freg->update_arg) { + FSESS_CHECK_THREAD(filter) + filter->freg->update_arg(filter, a->arg_name, &argv); + opaque_arg = GF_FALSE; + + if ((argv.type==GF_PROP_STRING) && argv.value.string) + gf_free(argv.value.string); + } + } + break; + } + } + if (!strlen(szArg)) { + found = GF_TRUE; + } else if (!found) { + if (!strcmp("FID", szArg)) { + if (arg_type != GF_FILTER_ARG_INHERIT) + gf_filter_set_id(filter, value); + found = GF_TRUE; + internal_arg = GF_TRUE; + } + else if (!strcmp("SID", szArg)) { + if (arg_type!=GF_FILTER_ARG_INHERIT) + gf_filter_set_sources(filter, value); + found = GF_TRUE; + internal_arg = GF_TRUE; + } else if (!strcmp("clone", szArg)) { + if ((arg_type==GF_FILTER_ARG_EXPLICIT_SINK) || (arg_type==GF_FILTER_ARG_EXPLICIT)) + filter->clonable=GF_TRUE; + found = GF_TRUE; + internal_arg = GF_TRUE; + } + //codec for generic enc load + else if (!strcmp("c", szArg)) { + found = GF_TRUE; + internal_arg = GF_TRUE; + } else if (!strcmp("N", szArg)) { + if ((arg_type==GF_FILTER_ARG_EXPLICIT_SINK) || (arg_type==GF_FILTER_ARG_EXPLICIT) || (arg_type==GF_FILTER_ARG_EXPLICIT_SOURCE)) + + gf_filter_set_name(filter, value); + found = GF_TRUE; + internal_arg = GF_TRUE; + } + //codec for generic enc load + else if (!strcmp("gfreg", szArg)) { + found = GF_TRUE; + internal_arg = GF_TRUE; + } + //codec for generic enc load + else if (!strcmp("gfopt", szArg)) { + found = GF_TRUE; + internal_arg = GF_TRUE; + opts_optional = GF_TRUE; + } + else if (gf_file_exists(szArg)) { + if (!for_script && (argfile_level<5) ) { + char szLine[2001]; + FILE *arg_file = gf_fopen(szArg, "rt"); + szLine[2000]=0; + while (!gf_feof(arg_file)) { + u32 llen; + char *subarg, *res_line; + szLine[0] = 0; + res_line = gf_fgets(szLine, 2000, arg_file); + if (!res_line) break; + llen = (u32) strlen(szLine); + while (llen && strchr(" \n\r\t", szLine[llen-1])) { + szLine[llen-1]=0; + llen--; + } + if (!llen) + continue; + + subarg = szLine; + while (subarg[0] && strchr(" \n\r\t", subarg[0])) + subarg++; + if ((subarg[0] == '/') && (subarg[1] == '/')) + continue; + + filter_parse_dyn_args(filter, subarg, arg_type, for_script, szSrc, szDst, szEscape, szSecName, has_meta_args, argfile_level+1); + } + gf_fclose(arg_file); + } else if (!for_script) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Filter argument file has too many nested levels of sub-files, maximum allowed is 5\n")); + } + internal_arg = GF_TRUE; + } + else if (has_meta_args && filter->freg->update_arg && value) { + if (for_script || !(filter->freg->flags&GF_FS_REG_SCRIPT) ) { + GF_PropertyValue argv = gf_props_parse_value(GF_PROP_STRING, szArg, value, NULL, filter->session->sep_list); + FSESS_CHECK_THREAD(filter) + filter->freg->update_arg(filter, szArg, &argv); + if (argv.value.string) gf_free(argv.value.string); + } + if (!(filter->freg->flags&GF_FS_REG_SCRIPT)) + found = GF_TRUE; + } + } + + if (!internal_arg && !opaque_arg && !opts_optional) + gf_fs_push_arg(filter->session, szArg, found, 0); + +skip_arg: + if (escaped) { + args=sep+6; + } else if (sep) { + args=sep+1; + if (opaque_arg) + args += 1; + } else { + args=NULL; + } + } + if (szArg) gf_free(szArg); +} + + +static void gf_filter_parse_args(GF_Filter *filter, const char *args, GF_FilterArgType arg_type, Bool for_script) +{ + u32 i=0; + char szSecName[200]; + char szEscape[7]; + char szSrc[5], szDst[5]; + Bool has_meta_args = GF_FALSE; + const GF_FilterArgs *f_args = NULL; + if (!filter) return; + + if (!for_script) { + if (!filter->freg->private_size) { + if (filter->freg->args && filter->freg->args[0].arg_name) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Filter with arguments but no private stack size, no arg passing\n")); + } + } else { + filter->filter_udta = gf_malloc(filter->freg->private_size); + if (!filter->filter_udta) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to allocate private data stack\n")); + return; + } + memset(filter->filter_udta, 0, filter->freg->private_size); + } + } + + sprintf(szEscape, "%cgpac%c", filter->session->sep_args, filter->session->sep_args); + sprintf(szSrc, "src%c", filter->session->sep_name); + sprintf(szDst, "dst%c", filter->session->sep_name); + + snprintf(szSecName, 200, "filter@%s", filter->freg->name); + + //instantiate all args with defauts value + f_args = filter->freg->args; + if (for_script) + f_args = filter->instance_args; + i=0; + while (f_args) { + GF_PropertyValue argv; + const char *def_val; + const GF_FilterArgs *a = &f_args[i]; + if (!a || !a->arg_name) break; + i++; + if (a->flags & GF_FS_ARG_META) { + has_meta_args = GF_TRUE; + continue; + } + + def_val = gf_filter_load_arg_config(filter, szSecName, a->arg_name, a->arg_default_val); + + if (!def_val) continue; + + argv = gf_filter_parse_prop_solve_env_var(filter, a->arg_type, a->arg_name, def_val, a->min_max_enum); + + if (argv.type != GF_PROP_FORBIDEN) { + if (!for_script && (a->offset_in_private>=0)) { + gf_filter_set_arg(filter, a, &argv); + } else if (filter->freg->update_arg) { + FSESS_CHECK_THREAD(filter) + filter->freg->update_arg(filter, a->arg_name, &argv); + gf_props_reset_single(&argv); + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to parse argument %s value %s\n", a->arg_name, a->arg_default_val)); + } + } + //handle meta filter options, not exposed in registry + if (has_meta_args && filter->freg->update_arg && !for_script) + gf_filter_load_meta_args_config(szSecName, filter); + + + filter_parse_dyn_args(filter, args, arg_type, for_script, szSrc, szDst, szEscape, szSecName, has_meta_args, 0); +} + +static void reset_filter_args(GF_Filter *filter) +{ + u32 i=0; + //removed or no stack + if (!filter->filter_udta) return; + + //instantiate all args with defauts value + while (filter->freg->args) { + GF_PropertyValue argv; + const GF_FilterArgs *a = &filter->freg->args[i]; + i++; + if (!a || !a->arg_name) break; + + if (a->arg_type != GF_PROP_FORBIDEN) { + memset(&argv, 0, sizeof(GF_PropertyValue)); + argv.type = a->arg_type; + gf_filter_set_arg(filter, a, &argv); + } + } +} + +void gf_filter_check_output_reconfig(GF_Filter *filter) +{ + u32 i, j; + //not needed + if (!filter->reconfigure_outputs) return; + filter->reconfigure_outputs = GF_FALSE; + //check destinations of all output pids + for (i=0; inum_output_pids; i++) { + GF_FilterPid *pid = gf_list_get(filter->output_pids, i); + for (j=0; jnum_destinations; j++) { + GF_FilterPidInst *pidi = gf_list_get(pid->destinations, j); + //PID was reconfigured, update props + if (pidi->reconfig_pid_props) { + assert(pidi->props); + if (pidi->props != pidi->reconfig_pid_props) { + //unassign old property list and set the new one + assert(pidi->props->reference_count); + if (safe_int_dec(& pidi->props->reference_count) == 0) { + //see \ref gf_filter_pid_merge_properties_internal for mutex + gf_mx_p(pidi->pid->filter->tasks_mx); + gf_list_del_item(pidi->pid->properties, pidi->props); + gf_mx_v(pidi->pid->filter->tasks_mx); + gf_props_del(pidi->props); + } + pidi->props = pidi->reconfig_pid_props; + safe_int_inc( & pidi->props->reference_count ); + } + pidi->reconfig_pid_props = NULL; + gf_fs_post_task(filter->session, gf_filter_pid_reconfigure_task, pidi->filter, pid, "pidinst_reconfigure", NULL); + } + } + } +} + +static GF_FilterPidInst *filter_relink_get_upper_pid(GF_FilterPidInst *cur_pidinst, Bool *needs_flush) +{ + GF_FilterPidInst *pidinst = cur_pidinst; + *needs_flush = GF_FALSE; + //locate the true destination + while (1) { + GF_FilterPid *opid; + if (pidinst->filter->num_input_pids != 1) break; + if (pidinst->filter->num_output_pids != 1) break; + //filter was explicitly loaded, cannot go beyond + if (! pidinst->filter->dynamic_filter) break; + opid = gf_list_get(pidinst->filter->output_pids, 0); + assert(opid); + //we have a fan-out, we cannot replace the filter graph after that point + //this would affect the other branches of the upper graph + if (opid->num_destinations != 1) break; + pidinst = gf_list_get(opid->destinations, 0); + + if (gf_fq_count(pidinst->packets)) + (*needs_flush) = GF_TRUE; + } + return pidinst; +} + +void gf_filter_relink_task(GF_FSTask *task) +{ + Bool needs_flush; + GF_FilterPidInst *cur_pidinst = task->udta; + /*GF_FilterPidInst *pidinst = */filter_relink_get_upper_pid(cur_pidinst, &needs_flush); + if (needs_flush) { + task->requeue_request = GF_TRUE; + return; + } + //good do go, unprotect pid + assert(cur_pidinst->detach_pending); + safe_int_dec(&cur_pidinst->detach_pending); + task->filter->removed = GF_FALSE; + + gf_filter_relink_dst(cur_pidinst); +} + +void gf_filter_relink_dst(GF_FilterPidInst *from_pidinst) +{ + GF_Filter *filter_dst; + GF_FilterPid *link_from_pid = NULL; + u32 min_chain_len = 0; + Bool needs_flush = GF_FALSE; + GF_FilterPidInst *src_pidinst = from_pidinst; + GF_FilterPidInst *dst_pidinst; + GF_Filter *cur_filter = from_pidinst->filter; + + //locate the true destination + dst_pidinst = filter_relink_get_upper_pid(src_pidinst, &needs_flush); + assert(dst_pidinst); + + //make sure we flush the end of the pipeline ! + if (needs_flush) { + cur_filter->removed = GF_TRUE; + //prevent any fetch from pid + safe_int_inc(&src_pidinst->detach_pending); + gf_fs_post_task(cur_filter->session, gf_filter_relink_task, cur_filter, NULL, "relink_dst", src_pidinst); + return; + } + filter_dst = dst_pidinst->filter; + + gf_fs_check_graph_load(cur_filter->session, GF_TRUE); + + //walk down the filter chain and find the shortest path to our destination + //stop when the current filter is not a one-to-one filter + while (1) { + u32 fchain_len; + GF_FilterPidInst *an_inpid = NULL; + if (cur_filter->num_input_pids>1) break; + if (cur_filter->num_output_pids>1) break; + + an_inpid = gf_list_get(cur_filter->input_pids, 0); + if (!an_inpid) + break; + + if (gf_filter_pid_caps_match((GF_FilterPid *)an_inpid, filter_dst->freg, filter_dst, NULL, NULL, NULL, -1)) { + link_from_pid = an_inpid->pid; + break; + } + fchain_len = gf_filter_pid_resolve_link_length(an_inpid->pid, filter_dst); + if (fchain_len && (!min_chain_len || (min_chain_len > fchain_len))) { + min_chain_len = fchain_len; + link_from_pid = an_inpid->pid; + } + cur_filter = an_inpid->pid->filter; + } + + if (!link_from_pid) { + gf_fs_check_graph_load(cur_filter->session, GF_FALSE); + return; + } + //detach the pidinst, and relink from the new input pid + gf_filter_renegociate_output_dst(link_from_pid, link_from_pid->filter, filter_dst, dst_pidinst, src_pidinst); +} + +void gf_filter_renegociate_output_dst(GF_FilterPid *pid, GF_Filter *filter, GF_Filter *filter_dst, GF_FilterPidInst *dst_pidi, GF_FilterPidInst *src_pidi) +{ + Bool is_new_chain = GF_TRUE; + GF_Filter *new_f; + Bool reconfig_only = src_pidi ? GF_FALSE: GF_TRUE; + + assert(filter); + + if (!filter_dst) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Internal error, lost destination for pid %s in filter %s while negotiating caps !!\n", pid->name, filter->name)); + return; + } + + //try to load filters to reconnect output pid + //we pass the source pid if given, so that we are sure the active property set is used to match the caps + if (!reconfig_only && gf_filter_pid_caps_match(src_pidi ? (GF_FilterPid *)src_pidi : pid, filter_dst->freg, filter_dst, NULL, NULL, NULL, -1)) { + GF_FilterPidInst *a_dst_pidi; + new_f = pid->filter; + assert(pid->num_destinations==1); + a_dst_pidi = gf_list_get(pid->destinations, 0); + //we are replacing the chain, remove filters until dest, keeping the final PID connected since we will detach + // and reattach it + if (!filter_dst->sticky) filter_dst->sticky = 2; + gf_filter_remove_internal(a_dst_pidi->filter, filter_dst, GF_TRUE); + is_new_chain = GF_FALSE; + + //we will reassign packets from that pid instance to the new connection + filter_dst->swap_pidinst_dst = a_dst_pidi; + + } + //we are inserting a new chain for reconfiguration only + else if (reconfig_only) { + gf_fs_check_graph_load(filter_dst->session, GF_TRUE); + //make sure we don't try the PID parent filter since we just failed reconfiguring it + gf_list_add(pid->filter->blacklisted, (void *) pid->filter->freg); + new_f = gf_filter_pid_resolve_link_for_caps(pid, filter_dst); + gf_list_del_item(pid->filter->blacklisted, (void *)pid->filter->freg); + } + //we are inserting a new chain + else { + Bool reassigned; + gf_fs_check_graph_load(filter_dst->session, GF_TRUE); + new_f = gf_filter_pid_resolve_link(pid, filter_dst, &reassigned); + } + + gf_fs_check_graph_load(filter_dst->session, GF_FALSE); + + if (! new_f) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("No suitable filter to adapt caps between pid %s in filter %s to filter %s, disconnecting pid!\n", pid->name, filter->name, filter_dst->name)); + filter->session->last_connect_error = GF_FILTER_NOT_FOUND; + + if (pid->adapters_blacklist) { + gf_list_del(pid->adapters_blacklist); + pid->adapters_blacklist = NULL; + } + if (dst_pidi) { + gf_fs_post_task(filter->session, gf_filter_pid_disconnect_task, dst_pidi->filter, dst_pidi->pid, "pidinst_disconnect", NULL); + } + return; + } + //detach pid instance from its source pid + if (dst_pidi) { + //signal as detached, this will prevent any further packet access + safe_int_inc(&dst_pidi->detach_pending); + + //we need to first reconnect the pid and then detach the output + if (is_new_chain) { + //signal a stream reset is pending to prevent filter entering endless loop + safe_int_inc(&dst_pidi->filter->stream_reset_pending); + //keep track of the pidinst being detached in the target filter + new_f->swap_pidinst_dst = dst_pidi; + //keep track of the pidinst being detached from the source filter + new_f->swap_pidinst_src = src_pidi; + new_f->swap_needs_init = GF_TRUE; + } + //we directly detach the pid + else { + safe_int_inc(&dst_pidi->pid->filter->detach_pid_tasks_pending); + safe_int_inc(&filter_dst->detach_pid_tasks_pending); + gf_fs_post_task(filter->session, gf_filter_pid_detach_task, filter_dst, dst_pidi->pid, "pidinst_detach", filter_dst); + } + } + + if (reconfig_only) { + assert(pid->caps_negociate); + new_f->caps_negociate = pid->caps_negociate; + safe_int_inc(&new_f->caps_negociate->reference_count); + } + + if (is_new_chain) { + //mark this filter has having pid connection pending to prevent packet dispatch until the connection is done + safe_int_inc(&pid->filter->out_pid_connection_pending); + gf_filter_pid_post_connect_task(new_f, pid); + } else { + gf_fs_post_task(filter->session, gf_filter_pid_reconfigure_task, filter_dst, pid, "pidinst_reconfigure", NULL); + } +} + +Bool gf_filter_reconf_output(GF_Filter *filter, GF_FilterPid *pid) +{ + GF_Err e; + GF_FilterPidInst *src_pidi = gf_list_get(filter->input_pids, 0); + GF_FilterPid *src_pid = src_pidi->pid; + if (filter->is_pid_adaptation_filter) { + //do not remove from destination_filters, needed for end of pid_init task + if (!filter->dst_filter) filter->dst_filter = gf_list_get(filter->destination_filters, 0); + assert(filter->dst_filter); + assert(filter->num_input_pids==1); + } + assert(filter->freg->reconfigure_output); + //swap to pid + pid->caps_negociate = filter->caps_negociate; + filter->caps_negociate = NULL; + e = filter->freg->reconfigure_output(filter, pid); + + + if (e!=GF_OK) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("PID Adaptation Filter %s output reconfiguration error %s, discarding filter and reloading new adaptation chain\n", filter->name, gf_error_to_string(e))); + gf_filter_pid_retry_caps_negotiate(src_pid, pid, filter->dst_filter); + return GF_FALSE; + } + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("PID Adaptation Filter %s output reconfiguration OK (between filters %s and %s)\n", filter->name, src_pid->filter->name, filter->dst_filter->name)); + + gf_filter_check_output_reconfig(filter); + + //success ! + if (src_pid->adapters_blacklist) { + gf_list_del(pid->adapters_blacklist); + src_pid->adapters_blacklist = NULL; + } + assert(pid->caps_negociate->reference_count); + if (safe_int_dec(&pid->caps_negociate->reference_count) == 0) { + gf_props_del(pid->caps_negociate); + } + pid->caps_negociate = NULL; + if (filter->is_pid_adaptation_filter) { + filter->dst_filter = NULL; + } + return GF_TRUE; +} + +void gf_filter_reconfigure_output_task(GF_FSTask *task) +{ + GF_Filter *filter = task->filter; + GF_FilterPid *pid; + assert(filter->num_output_pids==1); + pid = gf_list_get(filter->output_pids, 0); + gf_filter_reconf_output(filter, pid); +} + + +static void gf_filter_renegociate_output(GF_Filter *filter, Bool force_afchain_insert) +{ + u32 i, j; + assert(filter->nb_caps_renegociate ); + safe_int_dec(& filter->nb_caps_renegociate ); + + for (i=0; inum_output_pids; i++) { + GF_FilterPid *pid = gf_list_get(filter->output_pids, i); + if (pid->caps_negociate) { + Bool is_ok = GF_FALSE; + + //the property map is create with ref count 1 + + //we cannot reconfigure output if more than one destination + if ((pid->num_destinations<=1) && filter->freg->reconfigure_output && !force_afchain_insert) { + GF_Err e = filter->freg->reconfigure_output(filter, pid); + if (e) { + if (filter->is_pid_adaptation_filter) { + GF_FilterPidInst *src_pidi = gf_list_get(filter->input_pids, 0); + GF_FilterPidInst *pidi = gf_list_get(pid->destinations, 0); + + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("PID Adaptation Filter %s output reconfiguration error %s, discarding filter and reloading new adaptation chain\n", filter->name, gf_error_to_string(e))); + + assert(filter->num_input_pids==1); + + gf_filter_pid_retry_caps_negotiate(src_pidi->pid, pid, pidi->filter); + + continue; + } + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Filter %s output reconfiguration error %s, loading filter chain for renegociation\n", filter->name, gf_error_to_string(e))); + } else { + is_ok = GF_TRUE; + gf_filter_check_output_reconfig(filter); + } + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s cannot reconfigure output pids, loading filter chain for renegociation\n", filter->name)); + } + + if (!is_ok) { + GF_Filter *filter_dst; + //we are currently connected to output + if (pid->num_destinations) { + for (j=0; jnum_destinations; j++) { + GF_FilterPidInst *pidi = gf_list_get(pid->destinations, j); + if (pid->caps_negociate_pidi != pidi) continue; + filter_dst = pidi->filter; + + if (filter_dst->freg->reconfigure_output) { + assert(!filter_dst->caps_negociate); + filter_dst->caps_negociate = pid->caps_negociate; + safe_int_inc(&filter_dst->caps_negociate->reference_count); + + gf_fs_post_task(filter->session, gf_filter_reconfigure_output_task, filter_dst, NULL, "filter reconfigure output", NULL); + } else { + //disconnect pid, but prevent filter from unloading + if (!filter_dst->sticky) filter_dst->sticky = 2; + gf_filter_renegociate_output_dst(pid, filter, filter_dst, pidi, NULL); + } + } + } + //we are deconnected (unload of a previous adaptation filter) + else { + filter_dst = pid->caps_dst_filter; + assert(pid->num_destinations==0); + pid->caps_dst_filter = NULL; + gf_filter_renegociate_output_dst(pid, filter, filter_dst, NULL, NULL); + } + } + assert(pid->caps_negociate->reference_count); + if (safe_int_dec(&pid->caps_negociate->reference_count) == 0) { + gf_props_del(pid->caps_negociate); + } + pid->caps_negociate = NULL; + pid->caps_negociate_pidi = NULL; + } + } +} + +void gf_filter_renegociate_output_task(GF_FSTask *task) +{ + //it is possible that the cap renegociation was already done at the time we process this task + if (task->filter->nb_caps_renegociate) + gf_filter_renegociate_output(task->filter, GF_TRUE); +} + +static void gf_filter_check_pending_tasks(GF_Filter *filter, GF_FSTask *task) +{ + if (filter->session->run_status!=GF_OK) { + return; + } + + //lock task mx to take the decision whether to requeue a new task or not (cf gf_filter_post_process_task) + //TODO: find a way to bypass this mutex ? + gf_mx_p(filter->tasks_mx); + + assert(filter->scheduled_for_next_task || filter->session->direct_mode); + assert(filter->process_task_queued); + if (safe_int_dec(&filter->process_task_queued) == 0) { + //we have pending packets, auto-post and requeue + if (filter->pending_packets) { + safe_int_inc(&filter->process_task_queued); + task->requeue_request = GF_TRUE; + } + //special case here: no more pending packets, filter has detected eos on one of its input but is still generating packet, + //we reschedule (typically flush of decoder) + else if (filter->eos_probe_state == 2) { + safe_int_inc(&filter->process_task_queued); + task->requeue_request = GF_TRUE; + filter->eos_probe_state = 0; + } + //we are done for now + else { + task->requeue_request = GF_FALSE; + } + } else { + //we have some more process() requests queued, requeue + task->requeue_request = GF_TRUE; + } + if (task->requeue_request) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("[Filter] %s kept in scheduler blocking %d\n", filter->name, filter->would_block)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("[Filter] %s removed from scheduler - blocking %d\n", filter->name, filter->would_block)); + } + gf_mx_v(filter->tasks_mx); +} + +#ifdef GPAC_MEMORY_TRACKING +static GF_Err gf_filter_process_check_alloc(GF_Filter *filter) +{ + GF_Err e; + u32 nb_allocs=0, nb_callocs=0, nb_reallocs=0, nb_free=0; + u32 prev_nb_allocs=0, prev_nb_callocs=0, prev_nb_reallocs=0, prev_nb_free=0; + + //reset alloc/realloc stats of filter + filter->session->nb_alloc_pck = 0; + filter->session->nb_realloc_pck = 0; + //get current alloc state + gf_mem_get_stats(&prev_nb_allocs, &prev_nb_callocs, &prev_nb_reallocs, &prev_nb_free); + e = filter->freg->process(filter); + + //get new alloc state + gf_mem_get_stats(&nb_allocs, &nb_callocs, &nb_reallocs, &nb_free); + //remove prev alloc stats + nb_allocs -= prev_nb_allocs; + nb_callocs -= prev_nb_callocs; + nb_reallocs -= prev_nb_reallocs; + nb_free -= prev_nb_free; + + //remove internal allocs/reallocs due to filter lib + if (nb_allocs>filter->session->nb_alloc_pck) + nb_allocs -= filter->session->nb_alloc_pck; + else + nb_allocs = 0; + + if (nb_reallocs>filter->session->nb_realloc_pck) + nb_reallocs -= filter->session->nb_realloc_pck; + else + nb_reallocs = 0; + + //we now have nomber of allocs/realloc used by the filter internally during its process + if (nb_allocs || nb_callocs || nb_reallocs /* || nb_free */) { + filter->stats_nb_alloc += nb_allocs; + filter->stats_nb_calloc += nb_callocs; + filter->stats_nb_realloc += nb_reallocs; + filter->stats_nb_free += nb_free; + } else { + filter->nb_consecutive_process ++; + } + filter->nb_process_since_reset++; + return e; + +} +#endif + +static GFINLINE void check_filter_error(GF_Filter *filter, GF_Err e, Bool for_reconnection) +{ + GF_Err out_e = e; + Bool kill_filter = GF_FALSE; + if (e>GF_OK) e = GF_OK; + else if (e==GF_IP_NETWORK_EMPTY) e = GF_OK; + + if (e) { + u64 diff; + filter->session->last_process_error = e; + + filter->nb_errors ++; + if (!filter->nb_consecutive_errors) filter->time_at_first_error = gf_sys_clock_high_res(); + + filter->nb_consecutive_errors ++; + if (filter->nb_pck_io) filter->nb_consecutive_errors = 0; + //give it at most one second + diff = gf_sys_clock_high_res() - filter->time_at_first_error; + if (diff >= 1000000) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("[Filter] %s in error / not responding properly: %d consecutive errors in "LLU" us with no packet discarded or sent\n\tdiscarding all inputs and notifying end of stream on all outputs\n", filter->name, filter->nb_consecutive_errors, diff)); + kill_filter = GF_TRUE; + } + } else { + if ((!filter->nb_pck_io && filter->pending_packets && (filter->nb_pids_playing>0) ) || for_reconnection) { + filter->nb_consecutive_errors++; + + out_e = GF_SERVICE_ERROR; + if (filter->nb_consecutive_errors >= 100000) { + if (for_reconnection) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("[Filter] %s not responding properly: %d consecutive attempts at reconfiguring\n\tdiscarding all inputs and notifying end of stream on all outputs\n", filter->name, filter->nb_consecutive_errors)); + } else if (!filter->session->in_final_flush) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("[Filter] %s not responding properly: %d consecutive process with no packet discarded or sent, but %d packets pending\n\tdiscarding all inputs and notifying end of stream on all outputs\n", filter->name, filter->nb_consecutive_errors, filter->pending_packets)); + } else { + out_e = GF_OK; + } + kill_filter = GF_TRUE; + } + } else { + filter->nb_consecutive_errors = 0; + filter->nb_pck_io = 0; + } + } + + if (kill_filter) { + u32 i; + for (i=0; inum_input_pids; i++) { + GF_FilterPidInst *pidi = gf_list_get(filter->input_pids, i); + gf_filter_pid_set_discard((GF_FilterPid *)pidi, GF_TRUE); + } + for (i=0; inum_output_pids; i++) { + GF_FilterPid *pid = gf_list_get(filter->output_pids, i); + gf_filter_pid_set_eos(pid); + } + filter->session->last_process_error = out_e; + filter->disabled = GF_TRUE; + } +} + +static void gf_filter_process_task(GF_FSTask *task) +{ + GF_Err e; + Bool skip_block_mode = GF_FALSE; + GF_Filter *filter = task->filter; + Bool force_block_state_check=GF_FALSE; + assert(task->filter); + assert(filter->freg); + assert(filter->freg->process); + task->can_swap = GF_TRUE; + + filter->schedule_next_time = 0; + + if (filter->disabled) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s is disabled, cancelling process\n", filter->name)); + gf_mx_p(task->filter->tasks_mx); + task->filter->process_task_queued = 0; + gf_mx_v(task->filter->tasks_mx); + return; + } + + if (filter->out_pid_connection_pending || filter->detached_pid_inst || filter->caps_negociate) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s has %s pending, requeuing process\n", filter->name, filter->out_pid_connection_pending ? "connections" : filter->caps_negociate ? "caps negociation" : "input pid reassignments")); + //do not cancel the process task since it might have been triggered by the filter itself, + //we would not longer call it + task->requeue_request = GF_TRUE; + task->schedule_next_time = gf_sys_clock_high_res() + 10000; + assert(filter->process_task_queued); + check_filter_error(filter, GF_OK, GF_TRUE); + return; + } + if (filter->removed || filter->finalized) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s has been %s, skipping process\n", filter->name, filter->finalized ? "finalized" : "removed")); + return; + } + + if (filter->prevent_blocking) skip_block_mode = GF_TRUE; + else if (filter->session->in_final_flush) skip_block_mode = GF_TRUE; + + //blocking filter: remove filter process task - task will be reinserted upon unblock() + if (!skip_block_mode && filter->would_block && (filter->would_block + filter->num_out_pids_not_connected == filter->num_output_pids ) ) { + gf_mx_p(task->filter->tasks_mx); + //it may happen that by the time we get the lock, the filter has been unblocked by another thread. If so, don't skip task + if (filter->would_block) { + filter->nb_tasks_done--; + task->filter->process_task_queued = 0; + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s blocked, skipping process\n", filter->name)); + gf_mx_v(task->filter->tasks_mx); + return; + } + gf_mx_v(task->filter->tasks_mx); + } + if (filter->stream_reset_pending) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s has stream reset pending, postponing process\n", filter->name)); + filter->nb_tasks_done--; + task->requeue_request = GF_TRUE; + assert(filter->process_task_queued); + return; + } + assert(filter->process_task_queued); + assert(!filter->multi_sink_target); + + //the following breaks demuxers where PIDs are not all known from start: if we filter some pids due to user request, + //we may end up with the following test true but not all PIDs yet declared, hence no more processing + //we could add a filter cap for that, but for now we simply rely on the blocking mode algo only +#if 0 + if (filter->num_output_pids && (filter->num_out_pids_not_connected==filter->num_output_pids)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s has no valid connected outputs, skipping process\n", filter->name)); + return; + } +#endif + + //we have postponed packets on filter, flush them + if (task->filter->postponed_packets) { + while (gf_list_count(task->filter->postponed_packets)) { + GF_FilterPacket *pck = gf_list_pop_front(task->filter->postponed_packets); + e = gf_filter_pck_send_internal(pck, GF_FALSE); + if (e==GF_PENDING_PACKET) { + //packet is pending so was added at the end of our postponed queue - remove from queue and reinsert in front + gf_list_del_item(task->filter->postponed_packets, pck); + gf_list_insert(task->filter->postponed_packets, pck, 0); + task->requeue_request = GF_TRUE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s still has postponed packets, postponing process\n", filter->name)); + return; + } + } + gf_list_del(task->filter->postponed_packets); + task->filter->postponed_packets = NULL; + } + FSESS_CHECK_THREAD(filter) + + filter->nb_pck_io = 0; + + if (filter->nb_caps_renegociate) { + gf_filter_renegociate_output(filter, GF_FALSE); + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s process\n", filter->name)); + gf_rmt_begin_hash(filter->name, GF_RMT_AGGREGATE, &filter->rmt_hash); + + filter->in_process_callback = GF_TRUE; + +#ifdef GPAC_MEMORY_TRACKING + if (filter->session->check_allocs) + e = gf_filter_process_check_alloc(filter); + else +#endif + e = filter->freg->process(filter); + + filter->in_process_callback = GF_FALSE; + gf_rmt_end(); + + //flush all pending pid init requests following the call to init + if (filter->has_pending_pids) { + filter->has_pending_pids=GF_FALSE; + while (gf_fq_count(filter->pending_pids)) { + GF_FilterPid *pid=gf_fq_pop(filter->pending_pids); + gf_filter_pid_post_init_task(filter, pid); + } + } + //no requeue if end of session + if (filter->session->run_status != GF_OK) { + return; + } + //if eos but we still have pending packets or process tasks queued, move to GF_OK so that + //we evaluate the blocking state + if (e==GF_EOS) { + if (filter->postponed_packets) { + e = GF_OK; + } else if (filter->process_task_queued) { + e = GF_OK; + force_block_state_check = GF_TRUE; + } + } + + if ((e==GF_EOS) || filter->removed || filter->finalized) { + gf_mx_p(filter->tasks_mx); + filter->process_task_queued = 0; + gf_mx_v(filter->tasks_mx); + return; + } + + if ((e==GF_PROFILE_NOT_SUPPORTED) && filter->has_out_caps && !(filter->session->flags & GF_FS_FLAG_NO_REASSIGN)) { + u32 i; + //disconnect all other inputs, and post a re-init + gf_mx_p(filter->tasks_mx); + for (i=0; inum_input_pids; i++) { + GF_FilterPidInst *a_pidinst = gf_list_get(filter->input_pids, i); + + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Not supported profile for filter %s - blacklisting as output from %s and retrying connections\n", filter->name, a_pidinst->pid->filter->name)); + + gf_list_add(a_pidinst->pid->filter->blacklisted, (void *) filter->freg); + + gf_filter_relink_dst(a_pidinst); + } + filter->process_task_queued = 0; + gf_mx_v(filter->tasks_mx); + return; + } + check_filter_error(filter, e, GF_FALSE); + + //source filters, flush data if enough space available. + if ( (!filter->num_output_pids || (filter->would_block + filter->num_out_pids_not_connected < filter->num_output_pids) ) + && !filter->input_pids + && (e!=GF_EOS) + && !force_block_state_check + ) { + if (filter->schedule_next_time) + task->schedule_next_time = filter->schedule_next_time; + task->requeue_request = GF_TRUE; + assert(filter->process_task_queued); + } + //filter requested a requeue + else if (filter->schedule_next_time) { + if (!filter->session->in_final_flush) { + task->schedule_next_time = filter->schedule_next_time; + task->requeue_request = GF_TRUE; + assert(filter->process_task_queued); + } + } + //last task for filter but pending packets and not blocking, requeue in main scheduler + else if ((filter->would_block < filter->num_output_pids) + && filter->pending_packets + && (gf_fq_count(filter->tasks)<=1) + ) { + task->requeue_request = GF_TRUE; + assert(filter->process_task_queued); + } + else { + assert (!filter->schedule_next_time); + gf_filter_check_pending_tasks(filter, task); + if (task->requeue_request) { + assert(filter->process_task_queued); + } + } +} + +void gf_filter_process_inline(GF_Filter *filter) +{ + GF_Err e; + if (filter->out_pid_connection_pending || filter->removed || filter->stream_reset_pending || filter->multi_sink_target) { + return; + } + if (filter->would_block && (filter->would_block == filter->num_output_pids) ) { + return; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s inline process\n", filter->name)); + + if (filter->postponed_packets) { + while (gf_list_count(filter->postponed_packets)) { + GF_FilterPacket *pck = gf_list_pop_front(filter->postponed_packets); + gf_filter_pck_send(pck); + } + gf_list_del(filter->postponed_packets); + filter->postponed_packets = NULL; + if (filter->process_task_queued==1) { + gf_mx_p(filter->tasks_mx); + filter->process_task_queued = 0; + gf_mx_v(filter->tasks_mx); + return; + } + } + FSESS_CHECK_THREAD(filter) + +#ifdef GPAC_MEMORY_TRACKING + if (filter->session->check_allocs) + e = gf_filter_process_check_alloc(filter); + else +#endif + e = filter->freg->process(filter); + + //flush all pending pid init requests following the call to init + if (filter->has_pending_pids) { + filter->has_pending_pids=GF_FALSE; + while (gf_fq_count(filter->pending_pids)) { + GF_FilterPid *pid=gf_fq_pop(filter->pending_pids); + gf_filter_pid_post_init_task(filter, pid); + } + } + //no requeue if end of session + if (filter->session->run_status != GF_OK) { + return; + } + if ((e==GF_EOS) || filter->removed || filter->finalized) { + gf_mx_p(filter->tasks_mx); + filter->process_task_queued = 0; + gf_mx_v(filter->tasks_mx); + return; + } + check_filter_error(filter, e, GF_FALSE); +} + +GF_EXPORT +void gf_filter_send_update(GF_Filter *filter, const char *fid, const char *name, const char *val, GF_EventPropagateType propagate_mask) +{ + if (filter) gf_fs_send_update(filter->session, fid, fid ? NULL : filter, name, val, propagate_mask); +} + +GF_Filter *gf_filter_clone(GF_Filter *filter) +{ + GF_Filter *new_filter = gf_filter_new(filter->session, filter->freg, filter->orig_args, NULL, filter->arg_type, NULL, NULL, GF_FALSE); + if (!new_filter) return NULL; + new_filter->cloned_from = filter; + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter cloned (register %s, args %s)\n", filter->freg->name, filter->orig_args ? filter->orig_args : "none")); + + return new_filter; +} + +GF_EXPORT +u32 gf_filter_get_ipid_count(GF_Filter *filter) +{ + return filter->num_input_pids; +} + +GF_EXPORT +GF_FilterPid *gf_filter_get_ipid(GF_Filter *filter, u32 idx) +{ + return gf_list_get(filter->input_pids, idx); +} + +GF_EXPORT +u32 gf_filter_get_opid_count(GF_Filter *filter) +{ + return filter->num_output_pids; +} + +GF_EXPORT +GF_FilterPid *gf_filter_get_opid(GF_Filter *filter, u32 idx) +{ + return gf_list_get(filter->output_pids, idx); +} + +void gf_filter_post_process_task_internal(GF_Filter *filter, Bool use_direct_dispatch) +{ + if (filter->finalized || filter->removed) + return; + + //although this is theoretically OK, it breaks quite some tests. Need further investigation +#if 0 + //if regular posting (not direct) and our caller is the main process function, no need to lock task mutex, just increase + //the next scheduled time + if (!use_direct_dispatch && filter->in_process_callback) { + filter->schedule_next_time = 1 + gf_sys_clock_high_res(); + return; + } +#endif + + //lock task mx to take the decision whether to post a new task or not (cf gf_filter_check_pending_tasks) + gf_mx_p(filter->tasks_mx); + assert((s32)filter->process_task_queued>=0); + + if (use_direct_dispatch) { + safe_int_inc(&filter->process_task_queued); + gf_fs_post_task_ex(filter->session, gf_filter_process_task, filter, NULL, "process", NULL, GF_FALSE, GF_TRUE); + } else if (safe_int_inc(&filter->process_task_queued) <= 1) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s added to scheduler\n", filter->freg->name)); + gf_fs_post_task(filter->session, gf_filter_process_task, filter, NULL, "process", NULL); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s skip post process task\n", filter->freg->name)); + assert(filter->session->run_status + || filter->session->in_final_flush + || filter->disabled + || filter->scheduled_for_next_task + || filter->session->direct_mode + || gf_fq_count(filter->tasks) + ); + } + if (!filter->session->direct_mode && !use_direct_dispatch) { + assert(filter->process_task_queued); + } + gf_mx_v(filter->tasks_mx); +} + +GF_EXPORT +void gf_filter_post_process_task(GF_Filter *filter) +{ + gf_filter_post_process_task_internal(filter, GF_FALSE); +} +GF_EXPORT +void gf_filter_ask_rt_reschedule(GF_Filter *filter, u32 us_until_next) +{ + if (!filter->in_process) { + if (filter->session->direct_mode) return; + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Filter %s request for real-time reschedule but filter is not in process\n", filter->name)); + return; + } + if (filter->session->in_final_flush) + us_until_next = 0; + + //if the filter requests rescheduling, consider it is in a valid state and increment pck IOs to avoid flagging it as broken + filter->nb_pck_io++; + if (!us_until_next) { + return; + } + filter->schedule_next_time = 1+us_until_next + gf_sys_clock_high_res(); + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Filter %s real-time reschedule in %d us (at "LLU" sys clock)\n", filter->name, us_until_next, filter->schedule_next_time)); +} + +GF_EXPORT +void gf_filter_set_setup_failure_callback(GF_Filter *filter, GF_Filter *source_filter, void (*on_setup_error)(GF_Filter *f, void *on_setup_error_udta, GF_Err e), void *udta) +{ + if (!filter) return; + if (!source_filter) return; + source_filter->on_setup_error = on_setup_error; + source_filter->on_setup_error_filter = filter; + source_filter->on_setup_error_udta = udta; +} + +struct _gf_filter_setup_failure +{ + GF_Err e; + GF_Filter *filter; + GF_Filter *notify_filter; + Bool do_disconnect; +} filter_setup_failure; + +static void gf_filter_setup_failure_task(GF_FSTask *task) +{ + s32 res; + GF_Err e; + GF_Filter *f = ((struct _gf_filter_setup_failure *)task->udta)->filter; + if (task->udta) { + e = ((struct _gf_filter_setup_failure *)task->udta)->e; + gf_free(task->udta); + + f->session->last_connect_error = e; + } + + if (!f->finalized && f->freg->finalize) { + FSESS_CHECK_THREAD(f) + f->freg->finalize(f); + } + if (f->session->filters_mx) gf_mx_p(f->session->filters_mx); + + res = gf_list_del_item(f->session->filters, f); + if (res < 0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Filter %s task failure callback on already removed filter!\n", f->name)); + } + + if (f->session->filters_mx) gf_mx_v(f->session->filters_mx); + + gf_filter_del(f); +} + +static void gf_filter_setup_failure_notify_task(GF_FSTask *task) +{ + struct _gf_filter_setup_failure *st = (struct _gf_filter_setup_failure *)task->udta; + if (st->notify_filter && st->filter->on_setup_error) + st->filter->on_setup_error(st->filter, st->filter->on_setup_error_udta, st->e); + + if (st->do_disconnect) { + gf_fs_post_task(st->filter->session, gf_filter_setup_failure_task, NULL, NULL, "setup_failure", st); + } else { + gf_free(st); + } +} + +GF_EXPORT +void gf_filter_notification_failure(GF_Filter *filter, GF_Err reason, Bool force_disconnect) +{ + struct _gf_filter_setup_failure *stack; + if (!filter->on_setup_error_filter && !force_disconnect) return; + + stack = gf_malloc(sizeof(struct _gf_filter_setup_failure)); + stack->e = reason; + stack->notify_filter = filter->on_setup_error_filter; + stack->filter = filter; + stack->do_disconnect = force_disconnect; + if (force_disconnect) { + filter->removed = GF_TRUE; + } + if (filter->on_setup_error_filter) { + gf_fs_post_task(filter->session, gf_filter_setup_failure_notify_task, filter->on_setup_error_filter, NULL, "setup_failure_notify", stack); + } else if (force_disconnect) { + gf_fs_post_task(filter->session, gf_filter_setup_failure_task, NULL, NULL, "setup_failure", stack); + } +} + +GF_EXPORT +void gf_filter_setup_failure(GF_Filter *filter, GF_Err reason) +{ + + if (filter->in_connect_err) { + filter->in_connect_err = reason; + return; + } + + //filter was already connected, trigger removal of all pid instances + if (filter->num_input_pids) { + gf_filter_reset_pending_packets(filter); + filter->removed = GF_TRUE; + while (filter->num_input_pids) { + GF_FilterPidInst *pidinst = gf_list_get(filter->input_pids, 0); + GF_Filter *sfilter = pidinst->pid->filter; + gf_list_del_item(filter->input_pids, pidinst); + pidinst->filter = NULL; + gf_mx_p(filter->tasks_mx); + filter->num_input_pids = gf_list_count(filter->input_pids); + gf_mx_v(filter->tasks_mx); + + //post a pid_delete task to also trigger removal of the filter if needed + gf_fs_post_task(filter->session, gf_filter_pid_inst_delete_task, sfilter, pidinst->pid, "pid_inst_delete", pidinst); + } + filter->session->last_connect_error = reason; + return; + } + + //don't accept twice a notif + if (filter->setup_notified) return; + filter->setup_notified = GF_TRUE; + + gf_filter_notification_failure(filter, reason, GF_TRUE); +} + +#ifndef GPAC_DISABLE_LOG +static Bool task_postponed_log(void *udta, void *item) +{ + GF_FSTask *at = (GF_FSTask *)item; + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("\ttask %s\n", at->log_name)); + return GF_FALSE; +} +#endif + +void gf_filter_remove_task(GF_FSTask *task) +{ + s32 res; + GF_Filter *f = task->filter; + u32 count = gf_fq_count(f->tasks); + + if (f->out_pid_connection_pending || f->detach_pid_tasks_pending) { + task->requeue_request = GF_TRUE; + return; + } + + assert(f->finalized); + + if (count!=1) { + task->requeue_request = GF_TRUE; + task->can_swap = GF_TRUE; +#ifndef GPAC_DISABLE_LOG + if (gf_log_tool_level_on(GF_LOG_FILTER, GF_LOG_DEBUG) ) { + gf_fq_enum(f->tasks, task_postponed_log, NULL); + } +#endif //GPAC_DISABLE_LOG + return; + } + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s destruction task\n", f->name)); + + //avoid destruction of the task + gf_fq_pop(f->tasks); + + if (f->freg->finalize) { + FSESS_CHECK_THREAD(f) + f->freg->finalize(f); + } + + if (f->session->filters_mx) gf_mx_p(f->session->filters_mx); + + res = gf_list_del_item(f->session->filters, f); + if (res<0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Filter %s destruction task on already removed filter\n", f->name)); + } + + if (f->session->filters_mx) gf_mx_v(f->session->filters_mx); + + //detach all input pids + while (gf_list_count(f->input_pids)) { + GF_FilterPidInst *pidinst = gf_list_pop_back(f->input_pids); + pidinst->filter = NULL; + } + + gf_filter_del(f); + task->filter = NULL; + task->requeue_request = GF_FALSE; +} + +void gf_filter_post_remove(GF_Filter *filter) +{ + assert(!filter->swap_pidinst_dst); + assert(!filter->swap_pidinst_src); + assert(!filter->finalized); + filter->finalized = GF_TRUE; + gf_fs_post_task(filter->session, gf_filter_remove_task, filter, NULL, "filter_destroy", NULL); +} + +static void gf_filter_tag_remove(GF_Filter *filter, GF_Filter *source_filter, GF_Filter *until_filter) +{ + u32 i, count, j, nb_inst; + u32 nb_rem_inst=0; + Bool mark_only = GF_FALSE; + if (filter==until_filter) return; + + count = gf_list_count(filter->input_pids); + for (i=0; iinput_pids, i); + if (pidi->pid->filter==source_filter) nb_rem_inst++; + } + if (!nb_rem_inst) + return; + filter->marked_for_removal = GF_TRUE; + if (nb_rem_inst != count) + mark_only = GF_TRUE; + //already removed + if (filter->removed) return; + + //filter will be removed, propagate on all output pids + if (!mark_only) + filter->removed = GF_TRUE; + + count = gf_list_count(filter->output_pids); + for (i=0; ioutput_pids, i); + pid->has_seen_eos = GF_TRUE; + nb_inst = pid->num_destinations; + for (j=0; jdestinations, j); + gf_filter_tag_remove(pidi->filter, filter, until_filter); + if (!mark_only) + gf_fs_post_task(filter->session, gf_filter_pid_disconnect_task, pidi->filter, pid, "pidinst_disconnect", NULL); + } + } +} + +void gf_filter_remove_internal(GF_Filter *filter, GF_Filter *until_filter, Bool keep_end_connections) +{ + u32 i, j, count; + + if (!filter) return; + + if (filter->removed) + return; + + if (filter==until_filter) + return; + + if (until_filter) { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Disconnecting filter %s up to %s\n", filter->name, until_filter->name)); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Disconnecting filter %s from session\n", filter->name)); + } + //get all dest pids, post disconnect and mark filters as removed + assert(!filter->removed); + filter->removed = GF_TRUE; + for (i=0; inum_output_pids; i++) { + GF_FilterPid *pid = gf_list_get(filter->output_pids, i); + count = pid->num_destinations; + for (j=0; jdestinations, j); + + if (until_filter) { + gf_filter_tag_remove(pidi->filter, filter, until_filter); + } + + if (keep_end_connections && (pidi->filter == until_filter)) { + + } else { + gf_fs_post_task(filter->session, gf_filter_pid_disconnect_task, pidi->filter, pid, "pidinst_disconnect", NULL); + } + } + } + if (keep_end_connections) return; + + //check all pids connected to this filter, ensure their owner is only connected to this filter + for (i=0; inum_input_pids; i++) { + GF_FilterPid *pid; + GF_FilterPidInst *pidi = gf_list_get(filter->input_pids, i); + Bool can_remove = GF_TRUE; + //check all output pids of the filter owning this pid are connected to ourselves + pid = pidi->pid; + count = pid->num_destinations; + for (j=0; jdestinations, j); + if (pidi_o->filter != filter) { + can_remove = GF_FALSE; + break; + } + } + if (can_remove && !pid->filter->removed) { + gf_filter_remove_internal(pid->filter, NULL, GF_FALSE); + } + } +} + +GF_EXPORT +void gf_filter_remove_src(GF_Filter *filter, GF_Filter *src_filter) +{ + gf_filter_remove_internal(src_filter, filter, GF_FALSE); +} + +GF_EXPORT +void gf_filter_remove(GF_Filter *filter) +{ + u32 i; + if (!filter) return; + + //locate source filter(s) + for (i=0; inum_input_pids; i++) { + GF_FilterPidInst *pidi = gf_list_get(filter->input_pids, i); + //fanout, only disconnect this pid instance + if (pidi->pid->num_destinations>1) { + //post disconnect + gf_fs_post_task(filter->session, gf_filter_pid_disconnect_task, filter, pidi->pid, "pidinst_disconnect", NULL); + } + //this is a source for the chain + else if (!pidi->pid->filter->num_input_pids) { + gf_filter_remove_internal(pidi->pid->filter, NULL, GF_FALSE); + } + //otherwise walk down the chain + else { + gf_filter_remove(pidi->pid->filter); + } + } +} + +#if 0 +GF_EXPORT +void gf_filter_remove_dst(GF_Filter *filter, GF_Filter *dst_filter) +{ + u32 i, j; + Bool removed; + if (!filter) return; + removed = filter->removed; + filter->removed = GF_TRUE; + gf_filter_remove_internal(dst_filter, filter, GF_FALSE); + filter->removed = removed; + + for (i=0; inum_output_pids; i++) { + GF_FilterPid *pid = gf_list_get(filter->output_pids, i); + for (j=0; jnum_destinations; j++) { + GF_FilterPidInst *pidi = gf_list_get(pid->destinations, j); + if (pidi->filter->removed) { + gf_fs_post_task(pidi->filter->session, gf_filter_pid_disconnect_task, pidi->filter, pidi->pid, "pidinst_disconnect", NULL); + } + } + } +} +#endif + +Bool gf_filter_swap_source_register(GF_Filter *filter) +{ + u32 i; + char *src_url=NULL; + GF_Filter *target_filter=NULL; + GF_Err e; + const GF_FilterArgs *src_arg=NULL; + + while (gf_list_count(filter->postponed_packets)) { + GF_FilterPacket *pck = gf_list_pop_front(filter->postponed_packets); + gf_filter_packet_destroy(pck); + } + + while (gf_list_count(filter->output_pids)) { + GF_FilterPid *pid = gf_list_pop_back(filter->output_pids); + pid->destroyed = GF_TRUE; + gf_fs_post_task(filter->session, gf_filter_pid_del_task, filter, pid, "pid_delete", NULL); + } + gf_mx_p(filter->tasks_mx); + filter->num_output_pids = 0; + gf_mx_v(filter->tasks_mx); + + if (filter->freg->finalize) { + FSESS_CHECK_THREAD(filter) + filter->freg->finalize(filter); + filter->finalized = GF_TRUE; + } + gf_list_add(filter->blacklisted, (void *)filter->freg); + + i=0; + while (filter->freg->args) { + src_arg = &filter->freg->args[i]; + if (!src_arg || !src_arg->arg_name) { + src_arg=NULL; + break; + } + i++; + if (strcmp(src_arg->arg_name, "src")) continue; + //found it, get the url + if (src_arg->offset_in_private<0) continue; + +#ifdef WIN32 + src_url = *(char **)( ((char *)filter->filter_udta) + src_arg->offset_in_private); + *(char **)(((char *)filter->filter_udta) + src_arg->offset_in_private) = NULL; +#else + src_url = *(char **) (filter->filter_udta + src_arg->offset_in_private); + *(char **)(filter->filter_udta + src_arg->offset_in_private) = NULL; +#endif + break; + } + + gf_free(filter->filter_udta); + filter->filter_udta = NULL; + if (!src_url) return GF_FALSE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Swaping source filter for URL %s\n", src_url)); + + target_filter = filter->target_filter; + filter->finalized = GF_FALSE; + gf_fs_load_source_dest_internal(filter->session, src_url, NULL, NULL, &e, filter, filter->target_filter ? filter->target_filter : filter->dst_filter, GF_TRUE, filter->no_dst_arg_inherit, NULL); + //we manage to reassign an input registry + if (e==GF_OK) { + gf_free(src_url); + if (target_filter) filter->dst_filter = NULL; + return GF_TRUE; + } + if (!filter->finalized) { + gf_free(src_url); + return gf_filter_swap_source_register(filter); + } + + for (i=0; idestination_links); i++) { + GF_Filter *af = gf_list_get(filter->destination_links, i); + if (af->num_input_pids) { + u32 j; + for (j=0; jnum_input_pids; j++) { + GF_FilterPidInst *pidi = gf_list_get(af->input_pids, j); + pidi->is_end_of_stream = GF_TRUE; + } + } + if (af->sticky) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to find any filter for URL %s\n", src_url)); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to find any filter for URL %s, disabling destination filter %s\n", src_url, af->name)); + af->removed = GF_TRUE; + } + } + if (e==GF_NOT_SUPPORTED) + e = GF_FILTER_NOT_FOUND; + //nope ... + gf_filter_setup_failure(filter, e); + gf_free(src_url); + return GF_FALSE; +} + +void gf_filter_forward_clock(GF_Filter *filter) +{ + u32 i; + u64 clock_val; + if (!filter->next_clock_dispatch_type) return; + if (!filter->num_output_pids) return; + + for (i=0; inum_output_pids; i++) { + GF_FilterPacket *pck; + Bool req_props_map, info_modified; + GF_FilterPid *pid = gf_list_get(filter->output_pids, i); + GF_PropertyMap *map; + + //see \ref gf_filter_pid_merge_properties_internal for mutex + gf_mx_p(pid->filter->tasks_mx); + map = gf_list_last(pid->properties); + gf_mx_v(pid->filter->tasks_mx); + + clock_val = filter->next_clock_dispatch; + if (map->timescale != filter->next_clock_dispatch_timescale) { + clock_val *= map->timescale; + clock_val /= filter->next_clock_dispatch_timescale; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s internal forward of clock reference\n", pid->filter->name, pid->name)); + pck = gf_filter_pck_new_shared((GF_FilterPid *)pid, NULL, 0, NULL); + gf_filter_pck_set_cts(pck, clock_val); + gf_filter_pck_set_clock_type(pck, filter->next_clock_dispatch_type); + + //do not let the clock packet carry the props/info change flags since it is an internal + //packet discarded before processing these flags + req_props_map = pid->request_property_map; + pid->request_property_map = GF_TRUE; + info_modified = pid->pid_info_changed; + pid->pid_info_changed = GF_FALSE; + + gf_filter_pck_send(pck); + pid->request_property_map = req_props_map; + pid->pid_info_changed = info_modified; + } + filter->next_clock_dispatch_type = 0; +} + +GF_EXPORT +Bool gf_filter_is_supported_source(GF_Filter *filter, const char *url, const char *parent_url) +{ + GF_Err e; + Bool is_supported = GF_FALSE; + gf_fs_load_source_dest_internal(filter->session, url, NULL, parent_url, &e, NULL, filter, GF_TRUE, GF_TRUE, &is_supported); + return is_supported; +} + +GF_EXPORT +GF_Filter *gf_filter_connect_source(GF_Filter *filter, const char *url, const char *parent_url, Bool inherit_args, GF_Err *err) +{ + GF_Filter *filter_src; + const char *args; + char *full_args = NULL; + if (!filter) { + if (err) *err = GF_BAD_PARAM; + return NULL; + } + args = inherit_args ? gf_filter_get_dst_args(filter) : NULL; + if (args) { + char szSep[10]; + char *loc_args; + u32 len = (u32) strlen(args); + sprintf(szSep, "%cgfloc%c", filter->session->sep_args, filter->session->sep_args); + loc_args = strstr(args, szSep); + if (loc_args) { + len = (u32) (ptrdiff_t) (loc_args - args); + } + if (len) { + gf_dynstrcat(&full_args, url, NULL); + sprintf(szSep, "%cgpac%c", filter->session->sep_args, filter->session->sep_args); + if ((filter->session->sep_args==':') && strstr(url, "://") && !strstr(url, szSep)) { + gf_dynstrcat(&full_args, szSep, NULL); + } else { + sprintf(szSep, "%c", filter->session->sep_args); + gf_dynstrcat(&full_args, szSep, NULL); + } + gf_dynstrcat(&full_args, args, NULL); + sprintf(szSep, "%cgfloc%c", filter->session->sep_args, filter->session->sep_args); + loc_args = strstr(full_args, "gfloc"); + if (loc_args) loc_args[0] = 0; + + url = full_args; + } + } + + filter_src = gf_fs_load_source_dest_internal(filter->session, url, NULL, parent_url, err, NULL, filter, GF_TRUE, GF_TRUE, NULL); + if (full_args) gf_free(full_args); + + if (!filter_src) return NULL; + + if (!filter->source_filters) + filter->source_filters = gf_list_new(); + gf_list_add(filter->source_filters, filter_src); + return filter_src; +} + +GF_EXPORT +GF_Filter *gf_filter_connect_destination(GF_Filter *filter, const char *url, GF_Err *err) +{ + return gf_fs_load_source_dest_internal(filter->session, url, NULL, NULL, err, NULL, filter, GF_FALSE, GF_FALSE, NULL); +} + + +GF_EXPORT +void gf_filter_get_output_buffer_max(GF_Filter *filter, u32 *max_buf, u32 *max_playout_buf) +{ + u32 i; + u32 buf_max = 0; + u32 buf_play_max = 0; + for (i=0; inum_output_pids; i++) { + u32 j; + GF_FilterPid *pid = gf_list_get(filter->output_pids, i); + if (buf_max < pid->user_max_buffer_time) buf_max = (u32) pid->user_max_buffer_time; + if (buf_max < pid->max_buffer_time) buf_max = (u32) pid->max_buffer_time; + + if (buf_play_max < pid->user_max_playout_time) buf_play_max = pid->user_max_playout_time; + if (buf_play_max < pid->max_buffer_time) buf_play_max = (u32) pid->max_buffer_time; + + for (j=0; jnum_destinations; j++) { + u32 mb, pb; + GF_FilterPidInst *pidi = gf_list_get(pid->destinations, j); + gf_filter_get_output_buffer_max(pidi->filter, &mb, &pb); + if (buf_max < mb) buf_max = mb; + if (buf_play_max < pb) buf_play_max = pb; + } + } + if (max_buf) *max_buf = buf_max; + if (max_playout_buf) *max_playout_buf = buf_play_max; + return; +} + +GF_EXPORT +void gf_filter_make_sticky(GF_Filter *filter) +{ + if (filter) filter->sticky = 1; +} + +static u32 gf_filter_get_num_events_queued_internal(GF_Filter *filter) +{ + u32 i; + u32 nb_events = 0; + if (!filter) return 0; + nb_events = filter->num_events_queued; + + for (i=0; inum_output_pids; i++) { + u32 k; + GF_FilterPid *pid = gf_list_get(filter->output_pids, i); + for (k=0; knum_destinations; k++) { + GF_FilterPidInst *pidi = gf_list_get(pid->destinations, k); + nb_events += gf_filter_get_num_events_queued(pidi->filter); + } + } + return nb_events; +} +GF_EXPORT +u32 gf_filter_get_num_events_queued(GF_Filter *filter) +{ + u32 res; + GF_FilterSession *fsess; + if (!filter) return 0; + fsess = filter->session; + + if (fsess->filters_mx) + gf_mx_p(fsess->filters_mx); + + res = gf_filter_get_num_events_queued_internal(filter); + if (fsess->filters_mx) + gf_mx_v(fsess->filters_mx); + + return res; +} + +GF_EXPORT +void gf_filter_hint_single_clock(GF_Filter *filter, u64 time_in_us, GF_Fraction64 media_timestamp) +{ + //for now only one clock hint possible ... + filter->session->hint_clock_us = time_in_us; + filter->session->hint_timestamp = media_timestamp; +} + +GF_EXPORT +void gf_filter_get_clock_hint(GF_Filter *filter, u64 *time_in_us, GF_Fraction64 *media_timestamp) +{ + //for now only one clock hint possible ... + if (time_in_us) *time_in_us = filter->session->hint_clock_us; + if (media_timestamp) *media_timestamp = filter->session->hint_timestamp; +} + +GF_EXPORT +GF_Err gf_filter_assign_id(GF_Filter *filter, const char *id) +{ + if (!filter || filter->id) return GF_BAD_PARAM; + + if (!id) { + char szID[1024]; + sprintf(szID, "_%p_", filter); + filter->id = gf_strdup(szID); + } else { + filter->id = gf_strdup(id); + } + return GF_OK; +} + +GF_EXPORT +const char *gf_filter_get_id(GF_Filter *filter) +{ + if (filter) return filter->id; + return NULL; +} + +GF_EXPORT +GF_Err gf_filter_set_source(GF_Filter *filter, GF_Filter *link_from, const char *link_ext) +{ + if (!filter || !link_from) return GF_BAD_PARAM; + if (filter == link_from) return GF_OK; + //don't allow loops + if (gf_filter_in_parent_chain(filter, link_from)) return GF_BAD_PARAM; + + if (!link_from->id) { + gf_filter_assign_id(link_from, NULL); + } + if (link_ext) { + char szID[1024]; + sprintf(szID, "%s%c%s", link_from->id, link_from->session->sep_frag, link_ext); + gf_filter_set_sources(filter, szID); + } else { + gf_filter_set_sources(filter, link_from->id); + } + return GF_OK; +} + +GF_EXPORT +GF_Err gf_filter_override_caps(GF_Filter *filter, const GF_FilterCapability *caps, u32 nb_caps ) +{ + if (!filter) return GF_BAD_PARAM; + if (filter->num_output_pids) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempts at setting input caps on filter %s during execution of filter, not supported\n", filter->name)); + return GF_NOT_SUPPORTED; + } + filter->forced_caps = nb_caps ? caps : NULL; + filter->nb_forced_caps = nb_caps; + return GF_OK; +} + +GF_EXPORT +void gf_filter_pid_init_play_event(GF_FilterPid *pid, GF_FilterEvent *evt, Double start, Double speed, const char *log_name) +{ + u32 pmode = GF_PLAYBACK_MODE_NONE; + const GF_PropertyValue *p; + Bool was_end = GF_FALSE; + memset(evt, 0, sizeof(GF_FilterEvent)); + evt->base.type = GF_FEVT_PLAY; + evt->base.on_pid = pid; + + evt->play.speed = 1.0; + + if ((speed<0) && !start) start = -1.0; + + p = gf_filter_pid_get_property_first(pid, GF_PROP_PID_PLAYBACK_MODE); + if (p) pmode = p->value.uint; + + evt->play.start_range = start; + if (start<0) { + was_end = GF_TRUE; + p = gf_filter_pid_get_property_first(pid, GF_PROP_PID_DURATION); + if (p && p->value.lfrac.den) { + evt->play.start_range *= -100; + evt->play.start_range *= p->value.lfrac.num; + evt->play.start_range /= 100 * p->value.lfrac.den; + } + } + switch (pmode) { + case GF_PLAYBACK_MODE_NONE: + evt->play.start_range = 0; + if (start) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[%s] Media PID does not support seek, ignoring start directive\n", log_name)); + } + break; + case GF_PLAYBACK_MODE_SEEK: + if (speed != 1.0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[%s] Media PID does not support speed, ignoring speed directive\n", log_name)); + } + break; + case GF_PLAYBACK_MODE_FASTFORWARD: + if (speed<0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[%s] Media PID does not support negative speed, ignoring speed directive\n", log_name)); + if (was_end) evt->play.start_range = 0; + } else { + evt->play.speed = speed; + } + break; + default: + evt->play.speed = speed; + break; + } +} + +void gf_filter_set_max_extra_input_pids(GF_Filter *filter, u32 max_extra_pids) +{ + if (filter) filter->max_extra_pids = max_extra_pids; +} +GF_EXPORT +u32 gf_filter_get_max_extra_input_pids(GF_Filter *filter) +{ + if (filter) return filter->max_extra_pids; + return 0; +} + +Bool gf_filter_block_enabled(GF_Filter *filter) +{ + if (!filter) return GF_FALSE; + return (filter->session->blocking_mode==GF_FS_NOBLOCK) ? GF_FALSE : GF_TRUE; +} + +GF_EXPORT +GF_Err gf_filter_pid_raw_new(GF_Filter *filter, const char *url, const char *local_file, const char *mime_type, const char *fext, u8 *probe_data, u32 probe_size, Bool trust_mime, GF_FilterPid **out_pid) +{ + char tmp_ext[50]; + u32 ext_len=0; + Bool ext_not_trusted, is_new_pid = GF_FALSE; + GF_FilterPid *pid = *out_pid; + if (!pid) { + pid = gf_filter_pid_new(filter); + if (!pid) { + return GF_OUT_OF_MEM; + } + pid->max_buffer_unit = 1; + is_new_pid = GF_TRUE; + *out_pid = pid; + } + + gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); + + if (local_file) + gf_filter_pid_set_property(pid, GF_PROP_PID_FILEPATH, &PROP_STRING(local_file)); + + + if (url) { + gf_filter_pid_set_property(pid, GF_PROP_PID_URL, &PROP_STRING(url)); + + if (!strnicmp(url, "isobmff://", 10)) { + gf_filter_pid_set_name(pid, "isobmff://"); + fext = "mp4"; + mime_type = "video/mp4"; + if (is_new_pid) + gf_filter_pid_set_eos(pid); + } else { + char *sep = gf_file_basename(url); + + gf_filter_pid_set_name(pid, sep); + } + + if (fext) { + strncpy(tmp_ext, fext, 20); + tmp_ext[20] = 0; + strlwr(tmp_ext); + gf_filter_pid_set_property(pid, GF_PROP_PID_FILE_EXT, &PROP_STRING(tmp_ext)); + ext_len = (u32) strlen(tmp_ext); + } else { + char *ext = gf_file_ext_start(url); + if (ext) ext++; + + if (ext) { + char *s = strchr(ext, '#'); + if (s) s[0] = 0; + + strncpy(tmp_ext, ext, 20); + tmp_ext[20] = 0; + strlwr(tmp_ext); + gf_filter_pid_set_property(pid, GF_PROP_PID_FILE_EXT, &PROP_STRING(tmp_ext)); + ext_len = (u32) strlen(tmp_ext); + if (s) s[0] = '#'; + } + } + } + + ext_not_trusted = GF_FALSE; + //probe data + if ((!mime_type || !trust_mime) && !filter->no_probe && is_new_pid && probe_data && probe_size && !(filter->session->flags & GF_FS_FLAG_NO_PROBE)) { + u32 i, count; + GF_FilterProbeScore score, max_score = GF_FPROBE_NOT_SUPPORTED; + const char *probe_mime = NULL; + gf_mx_p(filter->session->filters_mx); + count = gf_list_count(filter->session->registry); + for (i=0; isession->registry, i); + if (!freg || !freg->probe_data) continue; + score = GF_FPROBE_NOT_SUPPORTED; + a_mime = freg->probe_data(probe_data, probe_size, &score); + if (score==GF_FPROBE_NOT_SUPPORTED) { + u32 k; + for (k=0;knb_caps && !ext_not_trusted && ext_len; k++) { + const char *value; + const GF_FilterCapability *cap = &freg->caps[k]; + if (!(cap->flags & GF_CAPFLAG_IN_BUNDLE)) continue; + if (!(cap->flags & GF_CAPFLAG_INPUT)) continue; + if (cap->code != GF_PROP_PID_FILE_EXT) continue; + value = cap->val.value.string; + while (value && ext_len) { + const char *match = strstr(value, tmp_ext); + if (!match) break; + if (!match[ext_len] || (match[ext_len]=='|')) { + ext_not_trusted = GF_TRUE; + break; + } + value = match+ext_len; + } + } + } else if (score==GF_FPROBE_EXT_MATCH) { + if (a_mime && ext_len && strstr(a_mime, tmp_ext)) { + ext_not_trusted = GF_FALSE; + probe_mime = NULL; + break; + } + } else { + if (a_mime) { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Data Prober (filter %s) detected format is%s mime %s\n", freg->name, (score==GF_FPROBE_MAYBE_SUPPORTED) ? " maybe" : "", a_mime)); + } + if (a_mime && (score > max_score)) { + probe_mime = a_mime; + max_score = score; + } + } + } + gf_mx_v(filter->session->filters_mx); + + pid->ext_not_trusted = ext_not_trusted; + + if (probe_mime) { + mime_type = probe_mime; + } + } + //we ignore mime type on pid reconfigure - some server will overwrite valid type to application/octet-string or binary/octet-string + //on a byte-range request or secondary fetch + //this is safe for now as the only reconfig we do is in HTTP streaming context + //we furthermore blacklist *octet-* mimes + if (mime_type && is_new_pid && !strstr(mime_type, "/octet-")) { + strncpy(tmp_ext, mime_type, 50); + tmp_ext[49] = 0; + strlwr(tmp_ext); + gf_filter_pid_set_property(pid, GF_PROP_PID_MIME, &PROP_STRING( tmp_ext )); + //we have a mime, disable extension checking + pid->ext_not_trusted = GF_TRUE; + } + + return GF_OK; +} + +GF_EXPORT +const char *gf_filter_probe_data(GF_Filter *filter, u8 *data, u32 size) +{ + u32 i, count; + GF_FilterProbeScore score, max_score = GF_FPROBE_NOT_SUPPORTED; + const char *probe_mime = NULL; + if (!size) return NULL; + gf_mx_p(filter->session->filters_mx); + count = gf_list_count(filter->session->registry); + for (i=0; isession->registry, i); + if (!freg || !freg->probe_data) continue; + score = GF_FPROBE_NOT_SUPPORTED; + a_mime = freg->probe_data(data, size, &score); + if (score==GF_FPROBE_NOT_SUPPORTED) { + } else if (score==GF_FPROBE_EXT_MATCH) { + probe_mime = NULL; + } else { + if (a_mime && (score > max_score)) { + probe_mime = a_mime; + max_score = score; + } + } + } + gf_mx_v(filter->session->filters_mx); + return probe_mime; +} + +static Bool gf_filter_get_arg_internal(GF_Filter *filter, const char *arg_name, GF_PropertyValue *prop, const char **min_max_enum) +{ + u32 i=0; + if (!filter || !arg_name) return GF_FALSE; + + while (1) { + GF_PropertyValue p; + const GF_FilterArgs *arg = &filter->freg->args[i]; + if (!arg || !arg->arg_name) break; + i++; + + if (strcmp(arg->arg_name, arg_name)) continue; + if (arg->offset_in_private < 0) continue; + + p.type = arg->arg_type; + switch (arg->arg_type) { + case GF_PROP_BOOL: + p.value.boolean = * (Bool *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_UINT: + p.value.uint = * (u32 *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_SINT: + p.value.sint = * (s32 *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_LUINT: + p.value.longuint = * (u64 *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_LSINT: + p.value.longsint = * (s64 *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_FLOAT: + p.value.fnumber = * (Fixed *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_DOUBLE: + p.value.number = * (Double *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_VEC2I: + p.value.vec2i = * (GF_PropVec2i *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_VEC2: + p.value.vec2 = * (GF_PropVec2 *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_VEC3I: + p.value.vec3i = * (GF_PropVec3i *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_VEC3: + p.value.vec3 = * (GF_PropVec3 *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_VEC4I: + p.value.vec4i = * (GF_PropVec4i *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_VEC4: + p.value.vec4 = * (GF_PropVec4 *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_FRACTION: + p.value.frac = * (GF_Fraction *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_FRACTION64: + p.value.lfrac = * (GF_Fraction64 *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_DATA: + case GF_PROP_DATA_NO_COPY: + case GF_PROP_CONST_DATA: + p.value.data = * (GF_PropData *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_POINTER: + p.value.ptr = * (void **) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_STRING_NO_COPY: + case GF_PROP_STRING: + case GF_PROP_NAME: + p.value.ptr = * (char **) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_STRING_LIST: + p.value.string_list = * (GF_List **) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_UINT_LIST: + p.value.uint_list = * (GF_PropUIntList *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_PIXFMT: + p.value.uint = * (u32 *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + case GF_PROP_PCMFMT: + p.value.uint = * (u32 *) ((char *)filter->filter_udta + arg->offset_in_private); + break; + default: + return GF_FALSE; + } + if (min_max_enum) *min_max_enum = arg->min_max_enum; + *prop = p; + return GF_TRUE; + } + return GF_FALSE; +} + +GF_EXPORT +const char *gf_filter_get_arg_str(GF_Filter *filter, const char *arg_name, char dump[GF_PROP_DUMP_ARG_SIZE]) +{ + GF_PropertyValue p; + const char *arg_min_max = NULL; + if (!dump || !gf_filter_get_arg_internal(filter, arg_name, &p, &arg_min_max)) + return NULL; + if (p.type==GF_PROP_PIXFMT) + return gf_pixel_fmt_name(p.value.uint); + if (p.type==GF_PROP_PCMFMT) + return gf_audio_fmt_name(p.value.uint); + + return gf_props_dump_val(&p, dump, GF_FALSE, arg_min_max); +} + +GF_EXPORT +Bool gf_filter_get_arg(GF_Filter *filter, const char *arg_name, GF_PropertyValue *prop) +{ + const char *arg_min_max = NULL; + if (!prop) return GF_FALSE; + return gf_filter_get_arg_internal(filter, arg_name, prop, &arg_min_max); +} + +GF_EXPORT +Bool gf_filter_is_supported_mime(GF_Filter *filter, const char *mime) +{ + return gf_fs_mime_supported(filter->session, mime); +} + +GF_EXPORT +Bool gf_filter_ui_event(GF_Filter *filter, GF_Event *uievt) +{ + return gf_fs_ui_event(filter->session, uievt); +} + +GF_EXPORT +Bool gf_filter_all_sinks_done(GF_Filter *filter) +{ + u32 i, count; + Bool res = GF_TRUE; + if (!filter || filter->session->in_final_flush || (filter->session->run_status==GF_EOS) ) + return GF_TRUE; + + gf_mx_p(filter->session->filters_mx); + count = gf_list_count(filter->session->filters); + for (i=0; isession->filters, i); + if (f->num_output_pids) continue; + for (j=0; jnum_input_pids; j++) { + GF_FilterPidInst *pidi = gf_list_get(f->input_pids, j); + if (pidi->pid->is_playing && !pidi->is_end_of_stream) { + res = GF_FALSE; + goto exit; + } + } + } +exit: + gf_mx_v(filter->session->filters_mx); + return res; +} + + +GF_EXPORT +void gf_filter_register_opengl_provider(GF_Filter *filter, Bool do_register) +{ +#ifndef GPAC_DISABLE_3D + GF_Err e; + if (filter->removed || filter->finalized) return; + + if (do_register) { + if (gf_list_find(filter->session->gl_providers, filter)<0) + gf_list_add(filter->session->gl_providers, filter); + return; + } + gf_list_del_item(filter->session->gl_providers, filter); + e = gf_fs_check_gl_provider(filter->session); + if (e && filter->session->nb_gl_filters) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to reload an OpenGL provider and some filters require openGL, aborting\n")); + gf_fs_abort(filter->session, GF_FALSE); + } +#endif + +} + +GF_EXPORT +GF_Err gf_filter_request_opengl(GF_Filter *filter) +{ +#ifndef GPAC_DISABLE_3D + GF_Err e; + if (filter->finalized || filter->removed) return GF_OK; + + filter->session->nb_gl_filters++; + e = gf_fs_check_gl_provider(filter->session); + if (e) { + filter->session->nb_gl_filters--; + filter->main_thread_forced = GF_FALSE; + return e; + } + if (!(filter->freg->flags & GF_FS_REG_CONFIGURE_MAIN_THREAD)) + filter->main_thread_forced = GF_TRUE; + return GF_OK; +#else + return GF_NOT_SUPPORTED; +#endif +} + +GF_EXPORT +GF_Err gf_filter_set_active_opengl_context(GF_Filter *filter) +{ +#ifndef GPAC_DISABLE_3D + if (filter->finalized || filter->removed) return GF_OK; + return gf_fs_set_gl(filter->session); +#else + return GF_NOT_SUPPORTED; +#endif +} + + +GF_EXPORT +u32 gf_filter_count_source_by_protocol(GF_Filter *filter, const char *protocol_scheme, Bool expand_proto, GF_FilterPid * (*enum_pids)(void *udta, u32 *idx), void *udta) +{ + u32 i, count, len, res=0; + if (!filter || !protocol_scheme) return 0; + gf_mx_p(filter->session->filters_mx); + len = (u32) strlen(protocol_scheme); + count = gf_list_count(filter->session->filters); + for (i=0; isession->filters, i); + //check only sinks + if (src->freg->configure_pid) continue; + args = src->src_args; + if (!args) args = src->orig_args; + if (!args || (strlen(args)<5) ) continue; + + if (strncmp(args + 4, protocol_scheme, len)) continue; + if (!expand_proto && (args[len] != ':')) continue; + + if (! gf_filter_in_parent_chain(filter, src)) { + u32 j=0; + Bool found=GF_FALSE; + if (!enum_pids) continue; + while (1) { + GF_FilterPid *pid = enum_pids(udta, &j); + if (!pid) break; + j++; + if ( gf_filter_in_parent_chain(pid->pid->filter, src)) { + found=GF_TRUE; + break; + } + } + if (!found) continue; + } + + res++; + } + + gf_mx_v(filter->session->filters_mx); + return res; +} + +GF_EXPORT +void gf_filter_disable_probe(GF_Filter *filter) +{ + if (filter) + filter->no_probe = GF_TRUE; +} + +GF_EXPORT +void gf_filter_disable_inputs(GF_Filter *filter) +{ + if (filter) + filter->no_inputs = GF_TRUE; +} + +static Bool gf_filter_has_pid_connection_pending_internal(GF_Filter *filter, GF_Filter *stop_at_filter) +{ + u32 i, j; + if (filter == stop_at_filter) return GF_FALSE; + + if (filter->has_pending_pids) return GF_TRUE; + + for (i=0; inum_output_pids; i++) { + GF_FilterPid *pid = gf_list_get(filter->output_pids, i); + if (pid->init_task_pending) return GF_TRUE; + for (j=0; jnum_destinations; j++) { + GF_FilterPidInst *pidi = gf_list_get(pid->destinations, j); + Bool res = gf_filter_has_pid_connection_pending_internal(pidi->filter, stop_at_filter); + if (res) return GF_TRUE; + } + } + return GF_FALSE; +} + +GF_EXPORT +Bool gf_filter_has_pid_connection_pending(GF_Filter *filter, GF_Filter *stop_at_filter) +{ + GF_FilterSession *fsess; + Bool res; + if (!filter) return GF_FALSE; + //lock session, this is an unsafe call + fsess = filter->session; + if (fsess->filters_mx) gf_mx_p(fsess->filters_mx); + res = gf_filter_has_pid_connection_pending_internal(filter, stop_at_filter); + if (fsess->filters_mx) gf_mx_v(fsess->filters_mx); + return res; +} + +GF_EXPORT +Bool gf_filter_reporting_enabled(GF_Filter *filter) +{ + if (filter) return filter->session->reporting_on; + return GF_FALSE; +} + +GF_EXPORT +GF_Err gf_filter_update_status(GF_Filter *filter, u32 percent, char *szStatus) +{ + GF_Event evt; + u32 len; + if (!filter) return GF_BAD_PARAM; + if (!filter->session->reporting_on) + return GF_OK; + + if (!szStatus) { + if (filter->status_str) filter->status_str[0] = 0; + return GF_OK; + } + len = (u32) strlen(szStatus); + if (len>=filter->status_str_alloc) { + filter->status_str_alloc = len+1; + filter->status_str = gf_realloc(filter->status_str, filter->status_str_alloc); + if (!filter->status_str) { + filter->status_str_alloc = 0; + return GF_OUT_OF_MEM; + } + } + memcpy(filter->status_str, szStatus, len+1); + filter->status_percent = percent; + filter->report_updated = GF_TRUE; + + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_PROGRESS; + evt.progress.progress_type = 3; + evt.progress.done = percent; + evt.progress.total = ((s32)percent>0) ? 10000 : 0; + evt.progress.filter_idx = gf_list_find(filter->session->filters, filter); + gf_fs_ui_event(filter->session, &evt); + return GF_OK; +} + +void gf_filter_report_unused_meta_option(GF_Filter *filter, const char *arg) +{ + if (!filter->session || filter->removed || filter->finalized) return; + if (filter->orig_args) { + char *opt_arg = strstr(filter->orig_args, "gfopt"); + if (opt_arg) { + char *arg_pos = strstr(filter->orig_args, arg); + if (arg_pos && (arg_pos>opt_arg)) + return; + } + } + gf_mx_p(filter->session->filters_mx); + gf_fs_push_arg(filter->session, arg, GF_FALSE, 2); + gf_mx_v(filter->session->filters_mx); +} + +GF_Err gf_filter_set_description(GF_Filter *filter, const char *new_desc) +{ + if (!filter) return GF_BAD_PARAM; + if (filter->instance_description) + gf_free(filter->instance_description); + + filter->instance_description = new_desc ? gf_strdup(new_desc) : NULL; + return GF_OK; +} +GF_EXPORT +const char *gf_filter_get_description(GF_Filter *filter) +{ + return filter ? filter->instance_description : NULL; +} + +GF_Err gf_filter_set_version(GF_Filter *filter, const char *new_desc) +{ + if (!filter) return GF_BAD_PARAM; + if (filter->instance_version) + gf_free(filter->instance_version); + + filter->instance_version = new_desc ? gf_strdup(new_desc) : NULL; + return GF_OK; +} +GF_EXPORT +const char *gf_filter_get_version(GF_Filter *filter) +{ + return filter ? filter->instance_version : NULL; +} + +GF_Err gf_filter_set_author(GF_Filter *filter, const char *new_desc) +{ + if (!filter) return GF_BAD_PARAM; + if (filter->instance_author) + gf_free(filter->instance_author); + + filter->instance_author = new_desc ? gf_strdup(new_desc) : NULL; + return GF_OK; +} +GF_EXPORT +const char *gf_filter_get_author(GF_Filter *filter) +{ + return filter ? filter->instance_author : NULL; +} + +GF_Err gf_filter_set_help(GF_Filter *filter, const char *new_desc) +{ + if (!filter) return GF_BAD_PARAM; + if (filter->instance_help) + gf_free(filter->instance_help); + + filter->instance_help = new_desc ? gf_strdup(new_desc) : NULL; + return GF_OK; +} +GF_EXPORT +const char *gf_filter_get_help(GF_Filter *filter) +{ + return filter ? filter->instance_help : NULL; +} + +GF_Err gf_filter_define_args(GF_Filter *filter, GF_FilterArgs *args) +{ + if (!filter) return GF_BAD_PARAM; + filter->instance_args = args; + return GF_OK; +} +GF_EXPORT +GF_FilterArgs *gf_filter_get_args(GF_Filter *filter) +{ + return filter ? filter->instance_args : NULL; +} + +GF_EXPORT +GF_Filter *gf_filter_load_filter(GF_Filter *filter, const char *name, GF_Err *err_code) +{ + if (!filter) return NULL; + return gf_fs_load_filter(filter->session, name, err_code); +} + +GF_EXPORT +Bool gf_filter_end_of_session(GF_Filter *filter) +{ + if (!filter) return GF_TRUE; + return filter->session->in_final_flush; +} + +GF_EXPORT +Bool gf_filter_is_alias(GF_Filter *filter) +{ + if (filter && filter->multi_sink_target) return GF_TRUE; + return GF_FALSE; +} + +/*! checks if the some PID connection tasks are still pending at the session level +\param filter target filter +\return GF_TRUE if some connection tasks are pending, GF_FALSE otherwise +*/ +Bool gf_filter_connections_pending(GF_Filter *filter) +{ + u32 i, count; + Bool res = GF_FALSE; + if (!filter) return GF_FALSE; + if (filter->session->pid_connect_tasks_pending) return GF_TRUE; + gf_mx_p(filter->session->filters_mx); + count = gf_list_count(filter->session->filters); + for (i=0; isession->filters, i); + if (f==filter) continue; + + gf_mx_p(filter->tasks_mx); + if (f->num_input_pids && ! f->num_output_pids) { + if (f->forced_caps) { + res = gf_filter_has_out_caps(f->forced_caps, f->nb_forced_caps); + } else { + res = gf_filter_has_out_caps(f->freg->caps, f->freg->nb_caps); + } + } + gf_mx_v(filter->tasks_mx); + if (res) + break; + } + gf_mx_v(filter->session->filters_mx); + + return res; +} + +GF_EXPORT +GF_Err gf_filter_prevent_blocking(GF_Filter *filter, Bool prevent_blocking_enabled) +{ + if (!filter) return GF_BAD_PARAM; + filter->prevent_blocking = prevent_blocking_enabled; + return GF_OK; +} + +GF_EXPORT +Bool gf_filter_is_dynamic(GF_Filter *filter) +{ + return filter ? filter->dynamic_filter : GF_FALSE; +} + +GF_EXPORT +void gf_filter_block_eos(GF_Filter *filter, Bool do_block) +{ + if (filter) filter->block_eos = do_block; +} + +GF_EXPORT +GF_Err gf_filter_reconnect_output(GF_Filter *filter) +{ + u32 i; + if (!filter) return GF_BAD_PARAM; + for (i=0; inum_output_pids; i++) { + GF_FilterPid *pid = gf_list_get(filter->output_pids, i); + gf_filter_pid_post_init_task(filter, pid); + } + return GF_OK; +} + +GF_EXPORT +GF_Err gf_filter_set_event_target(GF_Filter *filter, Bool enable_events) +{ + if (!filter) return GF_BAD_PARAM; + filter->event_target = enable_events; + return GF_OK; +} + diff --git a/src/filter_core/filter_pck.c b/src/filter_core/filter_pck.c new file mode 100644 index 0000000..7aca346 --- /dev/null +++ b/src/filter_core/filter_pck.c @@ -0,0 +1,1662 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2018 + * All rights reserved + * + * This file is part of GPAC / filters sub-project + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terfsess of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "filter_session.h" + +static void gf_filter_pck_reset_props(GF_FilterPacket *pck, GF_FilterPid *pid) +{ + memset(&pck->info, 0, sizeof(GF_FilterPckInfo)); + pck->info.dts = pck->info.cts = GF_FILTER_NO_TS; + pck->info.byte_offset = GF_FILTER_NO_BO; + pck->info.flags = GF_PCKF_BLOCK_START | GF_PCKF_BLOCK_END; + pck->pid = pid; + pck->src_filter = pid->filter; + pck->session = pid->filter->session; +} + +GF_EXPORT +GF_Err gf_filter_pck_merge_properties_filter(GF_FilterPacket *pck_src, GF_FilterPacket *pck_dst, gf_filter_prop_filter filter_prop, void *cbk) +{ + if (PCK_IS_INPUT(pck_dst)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to set property on an input packet in filter %s\n", pck_dst->pid->filter->name)); + return GF_BAD_PARAM; + } + //we allow copying over properties from dest packets to dest packets + //get true packet pointer + pck_src=pck_src->pck; + pck_dst=pck_dst->pck; + + pck_dst->info = pck_src->info; + pck_dst->info.flags &= ~GF_PCKF_PROPS_REFERENCE; + + if (!pck_src->props) { + return GF_OK; + } + if (!pck_dst->props) { + pck_dst->props = gf_props_new(pck_dst->pid->filter); + + if (!pck_dst->props) return GF_OUT_OF_MEM; + } + return gf_props_merge_property(pck_dst->props, pck_src->props, filter_prop, cbk); +} + +GF_EXPORT +GF_Err gf_filter_pck_merge_properties(GF_FilterPacket *pck_src, GF_FilterPacket *pck_dst) +{ + return gf_filter_pck_merge_properties_filter(pck_src, pck_dst, NULL, NULL); +} + +typedef struct +{ + u32 data_size; + GF_FilterPacket *pck; + GF_FilterPacket *closest; +} GF_PckQueueEnum; + +static Bool pck_queue_enum(void *udta, void *item) +{ + GF_PckQueueEnum *enum_state = (GF_PckQueueEnum *) udta; + GF_FilterPacket *cur = (GF_FilterPacket *) item; + + if (cur->alloc_size >= enum_state->data_size) { + if (!enum_state->pck || (enum_state->pck->alloc_size > cur->alloc_size)) { + enum_state->pck = cur; + } + } + else if (!enum_state->closest) enum_state->closest = cur; + //small data blocks, find smaller one + else if (enum_state->data_size<1000) { + if (enum_state->closest->alloc_size > cur->alloc_size) enum_state->closest = cur; + } + //otherwise find largest one below our target size + else if (enum_state->closest->alloc_size < cur->alloc_size) enum_state->closest = cur; + return GF_FALSE; +} + +static GF_FilterPacket *gf_filter_pck_new_alloc_internal(GF_FilterPid *pid, u32 data_size, u8 **data, Bool no_block_check) +{ + GF_FilterPacket *pck=NULL; + GF_FilterPacket *closest=NULL; + u32 count, max_reservoir_size; + + if (PID_IS_INPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to allocate a packet on an input PID in filter %s\n", pid->filter->name)); + return NULL; + } + if (!no_block_check && gf_filter_pid_would_block(pid)) + return NULL; + + count = gf_fq_count(pid->filter->pcks_alloc_reservoir); + if (count) { + GF_PckQueueEnum pck_enum_state; + memset(&pck_enum_state, 0, sizeof(GF_PckQueueEnum)); + pck_enum_state.data_size = data_size; + gf_fq_enum(pid->filter->pcks_alloc_reservoir, pck_queue_enum, &pck_enum_state); + pck = pck_enum_state.pck; + closest = pck_enum_state.closest; + } + + //stop allocating after a while - TODO we for sur can design a better algo... + max_reservoir_size = pid->num_destinations ? 10 : 1; + //if pid is file, force 1 max + if (!pck && (pid->stream_type==GF_STREAM_FILE)) + max_reservoir_size = 1; + + if (!pck && (count>=max_reservoir_size)) { + assert(closest); + closest->alloc_size = data_size; + closest->data = gf_realloc(closest->data, closest->alloc_size); + pck = closest; +#ifdef GPAC_MEMORY_TRACKING + pid->filter->session->nb_realloc_pck++; +#endif + } + + if (!pck) { + GF_SAFEALLOC(pck, GF_FilterPacket); + if (!pck) + return NULL; + pck->data = gf_malloc(sizeof(char)*data_size); + pck->alloc_size = data_size; +#ifdef GPAC_MEMORY_TRACKING + pid->filter->session->nb_alloc_pck+=2; +#endif + } else { + //pop first item and swap pointers. We can safely do this since this filter + //is the only one accessing the queue in pop mode, all others are just pushing to it + //this may however imply that we don't get the best matching block size if new packets + //were added to the list + + GF_FilterPacket *head_pck = gf_fq_pop(pid->filter->pcks_alloc_reservoir); + char *pck_data = pck->data; + u32 alloc_size = pck->alloc_size; + pck->data = head_pck->data; + pck->alloc_size = head_pck->alloc_size; + head_pck->data = pck_data; + head_pck->alloc_size = alloc_size; + pck = head_pck; + } + + pck->pck = pck; + pck->data_length = data_size; + if (data) *data = pck->data; + pck->filter_owns_mem = 0; + + gf_filter_pck_reset_props(pck, pid); + return pck; +} + +GF_EXPORT +GF_FilterPacket *gf_filter_pck_new_alloc(GF_FilterPid *pid, u32 data_size, u8 **data) +{ + return gf_filter_pck_new_alloc_internal(pid, data_size, data, GF_TRUE); +} + +static GF_FilterPacket *clone_frame_interface(GF_FilterPid *pid, GF_FilterPacket *pck_source, u8 **data) +{ + u32 i, w, h, stride, stride_uv, pf, osize; + u32 nb_planes, uv_height; + GF_FilterPacket *dst, *ref; + GF_FilterPacketInstance *pcki = (GF_FilterPacketInstance *) pck_source; + const GF_PropertyValue *p; + + ref = pcki->pck; + + p = gf_filter_pid_get_property(ref->pid , GF_PROP_PID_WIDTH); + w = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(ref->pid, GF_PROP_PID_HEIGHT); + h = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(ref->pid, GF_PROP_PID_PIXFMT); + pf = p ? p->value.uint : 0; + //not supported + if (!w || !h || !pf) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Missing width/height/pf in frame interface cloning, not supported\n")); + return NULL; + } + stride = stride_uv = 0; + + if (gf_pixel_get_size_info(pf, w, h, &osize, &stride, &stride_uv, &nb_planes, &uv_height) == GF_FALSE) { + return NULL; + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE); + if (!p || (p->value.uint != stride)) { + gf_filter_pid_set_property(pid, GF_PROP_PID_STRIDE, &PROP_UINT(stride)); + gf_filter_pid_set_property(pid, GF_PROP_PID_STRIDE_UV, &PROP_UINT(stride_uv)); + } + + dst = gf_filter_pck_new_alloc(pid, osize, data); + if (!dst) return NULL; + + u8 *out_ptr = *data; + for (i=0; iframe_ifce->get_plane(ref->frame_ifce, i, &in_ptr, &src_stride); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to fetch plane data from hardware frame, cannot clone\n")); + break; + } + if (i) { + write_h = uv_height; + dst_stride = stride_uv; + } else { + write_h = h; + dst_stride = stride; + } + for (j=0; jpck->frame_ifce) + return clone_frame_interface(pid, pck_source, data); + + if (force_copy) { + max_ref = 2; + } else { + ref = pcki->pck; + while (ref) { + if (ref->filter_owns_mem==2) { + max_ref = 2; + break; + } + if (ref->reference_count>max_ref) + max_ref = ref->reference_count; + ref = ref->reference; + } + } + + if (max_ref>1) { + dst = gf_filter_pck_new_alloc_internal(pid, pcki->pck->data_length, data, GF_TRUE); + if (dst && data) { + memcpy(*data, pcki->pck->data, sizeof(char)*pcki->pck->data_length); + } + if (dst) gf_filter_pck_merge_properties(pck_source, dst); + return dst; + } + dst = gf_filter_pck_new_ref(pid, NULL, 0, pck_source); + if (dst) { + gf_filter_pck_merge_properties(pck_source, dst); + *data = dst->data; + } + return dst; +} + + +GF_EXPORT +GF_FilterPacket *gf_filter_pck_new_clone(GF_FilterPid *pid, GF_FilterPacket *pck_source, u8 **data) +{ + return gf_filter_pck_new_clone_internal(pid, pck_source, data, GF_FALSE); +} +GF_EXPORT +GF_FilterPacket *gf_filter_pck_new_copy(GF_FilterPid *pid, GF_FilterPacket *pck_source, u8 **data) +{ + return gf_filter_pck_new_clone_internal(pid, pck_source, data, GF_TRUE); +} + + +GF_EXPORT +GF_FilterPacket *gf_filter_pck_new_alloc_destructor(GF_FilterPid *pid, u32 data_size, u8 **data, gf_fsess_packet_destructor destruct) +{ + GF_FilterPacket *pck = gf_filter_pck_new_alloc_internal(pid, data_size, data, GF_TRUE); + if (pck) pck->destructor = destruct; + return pck; +} + +GF_EXPORT +GF_FilterPacket *gf_filter_pck_new_shared_internal(GF_FilterPid *pid, const u8 *data, u32 data_size, gf_fsess_packet_destructor destruct, Bool intern_pck) +{ + GF_FilterPacket *pck; + + if (PID_IS_INPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to allocate a packet on an input PID in filter %s\n", pid->filter->name)); + return NULL; + } + + pck = gf_fq_pop(pid->filter->pcks_shared_reservoir); + if (!pck) { + GF_SAFEALLOC(pck, GF_FilterPacket); + if (!pck) + return NULL; + } + pck->pck = pck; + pck->data = (char *) data; + pck->data_length = data_size; + pck->destructor = destruct; + pck->filter_owns_mem = 1; + if (!intern_pck) { + safe_int_inc(&pid->nb_shared_packets_out); + safe_int_inc(&pid->filter->nb_shared_packets_out); + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s has %d shared packets out\n", pid->filter->name, pid->name, pid->nb_shared_packets_out)); + } + gf_filter_pck_reset_props(pck, pid); + + assert(pck->pid); + return pck; +} + +GF_EXPORT +GF_FilterPacket *gf_filter_pck_new_shared(GF_FilterPid *pid, const u8 *data, u32 data_size, gf_fsess_packet_destructor destruct) +{ + return gf_filter_pck_new_shared_internal(pid, data, data_size, destruct, GF_FALSE); + +} + +GF_EXPORT +GF_FilterPacket *gf_filter_pck_new_ref(GF_FilterPid *pid, const u8 *data, u32 data_size, GF_FilterPacket *reference) +{ + GF_FilterPacket *pck; + if (!reference) return NULL; + reference=reference->pck; + pck = gf_filter_pck_new_shared(pid, data, data_size, NULL); + if (!pck) return NULL; + pck->reference = reference; + assert(reference->reference_count); + safe_int_inc(&reference->reference_count); + if (!data && !data_size) { + pck->data = reference->data; + pck->data_length = reference->data_length; + pck->frame_ifce = reference->frame_ifce; + } + safe_int_inc(&reference->pid->nb_shared_packets_out); + safe_int_inc(&reference->pid->filter->nb_shared_packets_out); + return pck; +} + +GF_EXPORT +GF_Err gf_filter_pck_set_readonly(GF_FilterPacket *pck) +{ + if (PCK_IS_INPUT(pck)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to set readonly on an input packet in filter %s\n", pck->pid->filter->name)); + return GF_BAD_PARAM; + } + pck->filter_owns_mem = 2; + return GF_OK; +} + +GF_EXPORT +GF_FilterPacket *gf_filter_pck_new_frame_interface(GF_FilterPid *pid, GF_FilterFrameInterface *frame_ifce, gf_fsess_packet_destructor destruct) +{ + GF_FilterPacket *pck; + if (!frame_ifce) return NULL; + pck = gf_filter_pck_new_shared(pid, NULL, 0, NULL); + if (!pck) return NULL; + pck->destructor = destruct; + pck->frame_ifce = frame_ifce; + pck->filter_owns_mem = 2; + return pck; +} + +GF_EXPORT +GF_Err gf_filter_pck_forward(GF_FilterPacket *reference, GF_FilterPid *pid) +{ + GF_FilterPacket *pck; + if (!reference) return GF_OUT_OF_MEM; + reference=reference->pck; + pck = gf_filter_pck_new_shared(pid, NULL, 0, NULL); + if (!pck) return GF_OUT_OF_MEM; + pck->reference = reference; + assert(reference->reference_count); + safe_int_inc(&reference->reference_count); + safe_int_inc(&reference->pid->nb_shared_packets_out); + safe_int_inc(&reference->pid->filter->nb_shared_packets_out); + + gf_filter_pck_merge_properties(reference, pck); + pck->data = reference->data; + pck->data_length = reference->data_length; + pck->frame_ifce = reference->frame_ifce; + + return gf_filter_pck_send(pck); +} + +/*internal*/ +void gf_filter_packet_destroy(GF_FilterPacket *pck) +{ + Bool is_filter_destroyed = GF_FALSE; + GF_FilterPid *pid = pck->pid; + Bool is_ref_props_packet = GF_FALSE; + + //this is a ref props packet, its destruction can happen at any time, included after destruction + //of source filter/pid. pck->src_filter or pck->pid shall not be trusted ! + if (pck->info.flags & GF_PCKF_PROPS_REFERENCE) { + is_ref_props_packet = GF_TRUE; + is_filter_destroyed = GF_TRUE; + pck->src_filter = NULL; + pck->pid = NULL; + assert(!pck->destructor); + assert(!pck->filter_owns_mem); + assert(!pck->reference); + + if (pck->info.cts != GF_FILTER_NO_TS) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Destroying packet property reference CTS "LLU" size %d\n", pck->info.cts, pck->data_length)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Destroying packet property reference size %d\n", pck->data_length)); + } + } + //if not null, we discard a non-sent packet + else if (pck->src_filter) { + is_filter_destroyed = pck->src_filter->finalized; + } + + if (!is_filter_destroyed) { + assert(pck->pid); + if (pck->pid->filter) { + if (pck->info.cts != GF_FILTER_NO_TS) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s destroying packet CTS "LLU"\n", pck->pid->filter->name, pck->pid->name, pck->info.cts)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s destroying packet\n", pck->pid->filter->name, pck->pid->name)); + } + } + } + if (pck->destructor) pck->destructor(pid->filter, pid, pck); + + if (pck->pid_props) { + GF_PropertyMap *props = pck->pid_props; + pck->pid_props = NULL; + + if (is_ref_props_packet) { + assert(props->pckrefs_reference_count); + if (safe_int_dec(&props->pckrefs_reference_count) == 0) { + gf_props_del(props); + } + } else { + assert(props->reference_count); + if (safe_int_dec(&props->reference_count) == 0) { + if (!is_filter_destroyed) { + if (pck->pid->filter) { + //see \ref gf_filter_pid_merge_properties_internal for mutex + gf_mx_p(pck->pid->filter->tasks_mx); + gf_list_del_item(pck->pid->properties, props); + gf_mx_v(pck->pid->filter->tasks_mx); + } else { + gf_list_del_item(pck->pid->properties, props); + } + } + gf_props_del(props); + } + } + } + + if (pck->props) { + GF_PropertyMap *props = pck->props; + pck->props=NULL; + assert(props->reference_count); + if (safe_int_dec(&props->reference_count) == 0) { + gf_props_del(props); + } + } + if (pck->filter_owns_mem && !(pck->info.flags & GF_PCK_CMD_MASK) ) { + assert(pck->pid); + assert(pck->pid->nb_shared_packets_out); + assert(pck->pid->filter); + assert(pck->pid->filter->nb_shared_packets_out); + safe_int_dec(&pck->pid->nb_shared_packets_out); + safe_int_dec(&pck->pid->filter->nb_shared_packets_out); + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s has %d shared packets out\n", pck->pid->filter->name, pck->pid->name, pck->pid->nb_shared_packets_out)); + } + + pck->data_length = 0; + pck->pid = NULL; + + if (pck->reference) { + assert(pck->reference->pid->nb_shared_packets_out); + assert(pck->reference->pid->filter->nb_shared_packets_out); + safe_int_dec(&pck->reference->pid->nb_shared_packets_out); + safe_int_dec(&pck->reference->pid->filter->nb_shared_packets_out); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s has %d shared packets out\n", pck->reference->pid->filter->name, pck->reference->pid->name, pck->reference->pid->nb_shared_packets_out)); + assert(pck->reference->reference_count); + if (safe_int_dec(&pck->reference->reference_count) == 0) { + gf_filter_packet_destroy(pck->reference); + } + pck->reference = NULL; + } + /*this is a property reference packet, its destruction may happen at ANY time*/ + if (is_ref_props_packet) { + if (pck->session->pcks_refprops_reservoir) { + gf_fq_add(pck->session->pcks_refprops_reservoir, pck); + } else { + gf_free(pck); + } + } else if (is_filter_destroyed) { + if (!pck->filter_owns_mem && pck->data) gf_free(pck->data); + gf_free(pck); + } else if (pck->filter_owns_mem ) { + if (pid->filter && pid->filter->pcks_shared_reservoir) { + gf_fq_add(pid->filter->pcks_shared_reservoir, pck); + } else { + gf_free(pck); + } + } else { + if (pid->filter && pid->filter->pcks_alloc_reservoir) { + gf_fq_add(pid->filter->pcks_alloc_reservoir, pck); + } else { + if (pck->data) gf_free(pck->data); + gf_free(pck); + } + } +} + +static Bool gf_filter_aggregate_packets(GF_FilterPidInst *dst) +{ + u32 size=0, pos=0; + u64 byte_offset = 0; + u64 first_offset = 0; + u8 *data; + GF_FilterPacket *final; + u32 i, count; + GF_FilterPckInfo info; + + //no need to lock the packet list since only the dispatch thread operates on it + + count=gf_list_count(dst->pck_reassembly); + //no packet to reaggregate + if (!count) return GF_FALSE; + //single packet, update PID buffer and dispatch to packet queue + if (count==1) { + GF_FilterPacketInstance *pcki = gf_list_pop_back(dst->pck_reassembly); + safe_int_inc(&dst->filter->pending_packets); + if (pcki->pck->info.duration && pcki->pck->pid_props->timescale) { + s64 duration = ((u64)pcki->pck->info.duration) * 1000000; + duration /= pcki->pck->pid_props->timescale; + safe_int64_add(&dst->buffer_duration, duration); + } + pcki->pck->info.flags |= GF_PCKF_BLOCK_START | GF_PCKF_BLOCK_END; + gf_fq_add(dst->packets, pcki); + return GF_TRUE; + } + + for (i=0; ipck_reassembly, i); + assert(pck); + assert(! (pck->pck->info.flags & GF_PCKF_BLOCK_START) || ! (pck->pck->info.flags & GF_PCKF_BLOCK_END) ); + size += pck->pck->data_length; + if (!i) { + first_offset = byte_offset = pck->pck->info.byte_offset; + if (byte_offset != GF_FILTER_NO_BO) byte_offset += pck->pck->data_length; + }else if (byte_offset == pck->pck->info.byte_offset) { + byte_offset += pck->pck->data_length; + } else { + byte_offset = GF_FILTER_NO_BO; + } + } + + final = gf_filter_pck_new_alloc(dst->pid, size, &data); + pos=0; + + for (i=0; ipck_reassembly, i); + assert(pcki); + pck = pcki->pck; + + if (!pos) { + info = pck->info; + } else { + if (pcki->pck->info.duration > info.duration) + info.duration = pcki->pck->info.duration; + if ((pcki->pck->info.dts != GF_FILTER_NO_TS) && pcki->pck->info.dts > info.dts) + info.dts = pcki->pck->info.dts; + if ((pcki->pck->info.cts != GF_FILTER_NO_TS) && pcki->pck->info.cts > info.cts) + info.cts = pcki->pck->info.cts; + + info.flags |= pcki->pck->info.flags; + if (pcki->pck->info.carousel_version_number > info.carousel_version_number) + info.carousel_version_number = pcki->pck->info.carousel_version_number; + } + if (final) + memcpy(data+pos, pcki->pck->data, pcki->pck->data_length); + + pos += pcki->pck->data_length; + + if (final) { + gf_filter_pck_merge_properties(pcki->pck, final); + + //copy the first pid_props non null + if (pcki->pck->pid_props && !final->pid_props) { + final->pid_props = pcki->pck->pid_props; + safe_int_inc(&final->pid_props->reference_count); + } + } + + + gf_list_rem(dst->pck_reassembly, i); + + //destroy pcki + if ((i+1pck = NULL; + pcki->pid = NULL; + + if (pck->pid->filter->pcks_inst_reservoir) { + gf_fq_add(pck->pid->filter->pcks_inst_reservoir, pcki); + } else { + gf_free(pcki); + } + } else { + pcki->pck = final; + safe_int_inc(&final->reference_count); + final->info = info; + final->info.flags |= GF_PCKF_BLOCK_START | GF_PCKF_BLOCK_END; + + safe_int_inc(&dst->filter->pending_packets); + + if (info.duration && pck->pid_props && pck->pid_props->timescale) { + s64 duration = ((u64) info.duration) * 1000000; + duration /= pck->pid_props->timescale; + safe_int64_add(&dst->buffer_duration, duration); + } + //not continous set of bytes reaggregated + if (byte_offset == GF_FILTER_NO_BO) final->info.byte_offset = GF_FILTER_NO_BO; + else final->info.byte_offset = first_offset; + + gf_fq_add(dst->packets, pcki); + + } + //unref pck + assert(pck->reference_count); + if (safe_int_dec(&pck->reference_count) == 0) { + gf_filter_packet_destroy(pck); + } + + count--; + i--; + } + if (!final) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to allocate packet for fragment aggregation\n")); + return GF_FALSE; + } + return GF_TRUE; +} + +GF_EXPORT +void gf_filter_pck_discard(GF_FilterPacket *pck) +{ + if (PCK_IS_INPUT(pck)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to discard input packet on output PID in filter %s\n", pck->pid->filter->name)); + return; + } + //function is only used to discard packets allocated but not dispatched, eg with no reference count + //so don't decrement the counter + if (pck->reference_count == 0) { + gf_filter_packet_destroy(pck); + } +} + +GF_Err gf_filter_pck_send_internal(GF_FilterPacket *pck, Bool from_filter) +{ + u32 i, count, nb_dispatch=0, nb_discard=0; + GF_FilterPid *pid; + s64 duration=0; + u32 timescale=0; + GF_FilterClockType cktype; +#ifdef GPAC_MEMORY_TRACKING + u32 nb_allocs=0, nb_reallocs=0, prev_nb_allocs=0, prev_nb_reallocs=0; +#endif + + + if (!pck->src_filter) return GF_BAD_PARAM; + +#ifdef GPAC_MEMORY_TRACKING + if (pck->pid->filter->nb_process_since_reset) + gf_mem_get_stats(&prev_nb_allocs, NULL, &prev_nb_reallocs, NULL); +#endif + + assert(pck); + assert(pck->pid); + pid = pck->pid; + + if (PCK_IS_INPUT(pck)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to dispatch input packet on output PID in filter %s\n", pck->pid->filter->name)); + return GF_BAD_PARAM; + } + + if (pid->discard_input_packets) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s reset pending, discarding input packet\n", pid->filter->name, pid->name)); + safe_int_inc(&pck->reference_count); + assert(pck->reference_count); + if (safe_int_dec(&pck->reference_count) == 0) { + gf_filter_packet_destroy(pck); + } + return GF_OK; + } + + //special case for source filters (no input pids), mark as playing once we have a packet sent + if (!pid->filter->num_input_pids && !pid->initial_play_done && !pid->is_playing) { + pid->initial_play_done = GF_TRUE; + pid->is_playing = GF_TRUE; + pid->filter->nb_pids_playing++; + } + + if (pid->filter->eos_probe_state) + pid->filter->eos_probe_state = 2; + + pid->filter->nb_pck_io++; + + gf_rmt_begin(pck_send, GF_RMT_AGGREGATE); + + //send from filter, update flags + if (from_filter) { + Bool is_cmd = (pck->info.flags & GF_PCK_CKTYPE_MASK) ? GF_TRUE : GF_FALSE; + if (! is_cmd ) { + gf_filter_forward_clock(pck->pid->filter); + } + if ( (pck->info.flags & GF_PCK_CMD_MASK) == GF_PCK_CMD_PID_EOS) { + if (!pid->has_seen_eos) { + pid->has_seen_eos = GF_TRUE; + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s PID %s EOS detected\n", pck->pid->filter->name, pck->pid->name)); + } + } else if (pid->has_seen_eos && !is_cmd) { + pid->has_seen_eos = GF_FALSE; + } + + + if (pid->filter->pid_info_changed) { + pid->filter->pid_info_changed = GF_FALSE; + pid->pid_info_changed = GF_TRUE; + } + + //a new property map was created - flag the packet; don't do this if first packet dispatched on pid + pck->info.flags &= ~GF_PCKF_PROPS_CHANGED; + + if (!pid->request_property_map && !(pck->info.flags & GF_PCK_CMD_MASK) && (pid->nb_pck_sent || pid->props_changed_since_connect) ) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s properties modified, marking packet\n", pck->pid->filter->name, pck->pid->name)); + + pck->info.flags |= GF_PCKF_PROPS_CHANGED; + } + //any new pid_set_property after this packet will trigger a new property map + if (! (pck->info.flags & GF_PCK_CMD_MASK)) { + pid->request_property_map = GF_TRUE; + pid->props_changed_since_connect = GF_FALSE; + } + if (pid->pid_info_changed) { + pck->info.flags |= GF_PCKF_INFO_CHANGED; + pid->pid_info_changed = GF_FALSE; + } + } + + if (pck->pid_props) { + timescale = pck->pid_props->timescale; + } else { + //pid properties applying to this packet are the last defined ones + pck->pid_props = gf_list_last(pid->properties); + if (pck->pid_props) { + safe_int_inc(&pck->pid_props->reference_count); + timescale = pck->pid_props->timescale; + } + } + + if (pid->filter->out_pid_connection_pending || pid->filter->has_pending_pids || pid->init_task_pending) { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s PID %s connection pending, queuing packet\n", pck->pid->filter->name, pck->pid->name)); + if (!pid->filter->postponed_packets) pid->filter->postponed_packets = gf_list_new(); + gf_list_add(pid->filter->postponed_packets, pck); + +#ifdef GPAC_MEMORY_TRACKING + if (pck->pid->filter->session->check_allocs) { + gf_mem_get_stats(&nb_allocs, NULL, &nb_reallocs, NULL); + pck->pid->filter->session->nb_alloc_pck += (nb_allocs - prev_nb_allocs); + pck->pid->filter->session->nb_realloc_pck += (nb_reallocs - prev_nb_reallocs); + } +#endif + gf_rmt_end(); + return GF_PENDING_PACKET; + } + //now dispatched + pck->src_filter = NULL; + + assert(pck->pid); + if (! (pck->info.flags & GF_PCK_CMD_MASK) ) { + pid->nb_pck_sent++; + if (pck->data_length) { + pid->filter->nb_pck_sent++; + pid->filter->nb_bytes_sent += pck->data_length; + } else if (pck->frame_ifce) { + pid->filter->nb_hw_pck_sent++; + } + if (pck->info.cts!=GF_FILTER_NO_TS) { + pid->last_ts_sent.num = pck->info.cts; + pid->last_ts_sent.den = pck->pid_props->timescale; + } + } + + cktype = ( pck->info.flags & GF_PCK_CKTYPE_MASK) >> GF_PCK_CKTYPE_POS; + + if (cktype == GF_FILTER_CLOCK_PCR_DISC) { + pid->duration_init = GF_FALSE; + pid->min_pck_cts = pid->max_pck_cts = 0; + pid->nb_unreliable_dts = 0; + } + + if (!cktype) { + Bool unreliable_dts = GF_FALSE; + if (pck->info.dts==GF_FILTER_NO_TS) { + pck->info.dts = pck->info.cts; + + if (pid->recompute_dts) { + if (pck->info.cts == pid->last_pck_cts) { + pck->info.dts = pid->last_pck_dts; + } else { + s64 min_dur = pck->info.cts; + min_dur -= (s64) pid->min_pck_cts; + if (min_dur<0) min_dur*= -1; + if ((u64) min_dur > pid->min_pck_duration) min_dur = pid->min_pck_duration; + if (!min_dur) { + min_dur = 1; + unreliable_dts = GF_TRUE; + pid->nb_unreliable_dts++; + } else if (pid->nb_unreliable_dts) { + pid->last_pck_dts -= pid->nb_unreliable_dts; + pid->last_pck_dts += min_dur * pid->nb_unreliable_dts; + pid->nb_unreliable_dts = 0; + if (pid->last_pck_dts + min_dur > pck->info.cts) { + if ((s64) pck->info.cts > min_dur) + pid->last_pck_dts = pck->info.cts - min_dur; + else + pid->last_pck_dts = 0; + } + } + if (pid->last_pck_dts) + pck->info.dts = pid->last_pck_dts + min_dur; + } + } + } + else if (pck->info.cts==GF_FILTER_NO_TS) + pck->info.cts = pck->info.dts; + + if (pck->info.cts != GF_FILTER_NO_TS) { + if (! pid->duration_init) { + pid->last_pck_dts = pck->info.dts; + pid->last_pck_cts = pck->info.cts; + pid->max_pck_cts = pid->min_pck_cts = pck->info.cts; + pid->duration_init = GF_TRUE; + } else if (!pck->info.duration && !(pck->info.flags & GF_PCKF_DUR_SET) ) { + if (!unreliable_dts && (pck->info.dts!=GF_FILTER_NO_TS)) { + duration = pck->info.dts - pid->last_pck_dts; + if (duration<0) duration = -duration; + } else if (pck->info.cts!=GF_FILTER_NO_TS) { + duration = pck->info.cts - pid->last_pck_cts; + if (duration<0) duration = -duration; + } + + if (pid->recompute_dts) { + if (pck->info.cts > pid->max_pck_cts) + pid->max_pck_cts = pck->info.cts; + if (pck->info.cts < pid->max_pck_cts) { + if (pck->info.cts <= pid->min_pck_cts) { + pid->min_pck_cts = pck->info.cts; + } else if (pck->info.cts>pid->last_pck_cts) { + pid->min_pck_cts = pck->info.cts; + } + } + } + + pid->last_pck_dts = pck->info.dts; + pid->last_pck_cts = pck->info.cts; + + } else { + duration = pck->info.duration; + pid->last_pck_dts = pck->info.dts; + pid->last_pck_cts = pck->info.cts; + } + } else { + duration = pck->info.duration; + } + + if (duration) { + if (!pid->min_pck_duration) pid->min_pck_duration = (u32) duration; + else if ((u32) duration < pid->min_pck_duration) pid->min_pck_duration = (u32) duration; + } + if (!pck->info.duration && pid->min_pck_duration) + pck->info.duration = (u32) duration; + + //may happen if we don't have DTS, only CTS signaled and B-frames + if ((s32) pck->info.duration < 0) { + pck->info.duration = 0; + } + +#ifndef GPAC_DISABLE_LOG + if (gf_log_tool_level_on(GF_LOG_FILTER, GF_LOG_DEBUG)) { + u8 sap_type = (pck->info.flags & GF_PCK_SAP_MASK) >> GF_PCK_SAP_POS; + u8 seek = (pck->info.flags & GF_PCKF_SEEK) ? 1 : 0; + u8 bstart = (pck->info.flags & GF_PCKF_BLOCK_START) ? 1 : 0; + u8 bend = (pck->info.flags & GF_PCKF_BLOCK_END) ? 1 : 0; + + if ((pck->info.dts != GF_FILTER_NO_TS) && (pck->info.cts != GF_FILTER_NO_TS) ) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s sent packet DTS "LLU" CTS "LLU" SAP %d seek %d duration %d S/E %d/%d\n", pck->pid->filter->name, pck->pid->name, pck->info.dts, pck->info.cts, sap_type, seek, pck->info.duration, bstart, bend)); + } + else if ((pck->info.cts != GF_FILTER_NO_TS) ) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s sent packet CTS "LLU" SAP %d seek %d duration %d S/E %d/%d\n", pck->pid->filter->name, pck->pid->name, pck->info.cts, sap_type, seek, pck->info.duration, bstart, bend)); + } + else if ((pck->info.dts != GF_FILTER_NO_TS) ) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s sent packet DTS "LLU" SAP %d seek %d duration %d S/E %d/%d\n", pck->pid->filter->name, pck->pid->name, pck->info.dts, sap_type, seek, pck->info.duration, bstart, bend)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s sent packet no DTS/PTS SAP %d seek %d duration %d S/E %d/%d\n", pck->pid->filter->name, pck->pid->name, sap_type, seek, pck->info.duration, bstart, bend)); + } + } +#endif + } else { + pck->info.duration = 0; + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s sent clock reference "LLU"%s\n", pck->pid->filter->name, pck->pid->name, pck->info.cts, (cktype==GF_FILTER_CLOCK_PCR_DISC) ? " - discontinuity detected" : "")); + } + + + + //protect packet from destruction - this could happen + //1) during aggregation of packets + //2) after dispatching to the packet queue of the next filter, that packet may be consumed + //by its destination before we are done adding to the other destination + safe_int_inc(&pck->reference_count); + + assert(pck->pid); + count = pck->pid->num_destinations; + for (i=0; ipid->destinations, i); + if (!dst->filter || dst->filter->finalized || dst->filter->removed || !dst->filter->freg->process) continue; + + if (dst->discard_inputs) { + //in discard input mode, we drop all input packets but trigger reconfigure as they happen + if ((pck->info.flags & GF_PCKF_PROPS_CHANGED) && (dst->props != pck->pid_props)) { + //unassign old property list and set the new one + if (dst->props) { + assert(dst->props->reference_count); + if (safe_int_dec(& dst->props->reference_count) == 0) { + //see \ref gf_filter_pid_merge_properties_internal for mutex + gf_mx_p(dst->pid->filter->tasks_mx); + gf_list_del_item(dst->pid->properties, dst->props); + gf_mx_v(dst->pid->filter->tasks_mx); + gf_props_del(dst->props); + } + } + dst->props = pck->pid_props; + safe_int_inc( & dst->props->reference_count); + + assert(dst->filter->freg->configure_pid); + //reset the blacklist whenever reconfiguring, since we may need to reload a new filter chain + //in which a previously blacklisted filter (failing (re)configure for previous state) could + //now work, eg moving from formatA to formatB then back to formatA + gf_list_reset(dst->filter->blacklisted); + //and post a reconfigure task + gf_fs_post_task(dst->filter->session, gf_filter_pid_reconfigure_task, dst->filter, dst->pid, "pidinst_reconfigure", NULL); + } + nb_discard++; + continue; + } + + inst = gf_fq_pop(pck->pid->filter->pcks_inst_reservoir); + if (!inst) { + GF_SAFEALLOC(inst, GF_FilterPacketInstance); + if (!inst) return GF_OUT_OF_MEM; + } + inst->pck = pck; + inst->pid = dst; + inst->pid_props_change_done = 0; + inst->pid_info_change_done = 0; + //if packet is an openGL interface, force scheduling on main thread for the destination + if (pck->frame_ifce&&pck->frame_ifce->get_gl_texture) + dst->filter->main_thread_forced = GF_TRUE; + + if ((inst->pck->info.flags & GF_PCK_CMD_MASK) == GF_PCK_CMD_PID_EOS) { + safe_int_inc(&inst->pid->nb_eos_signaled); + } + + if (!inst->pid->handles_clock_references && cktype) { + safe_int_inc(&inst->pid->nb_clocks_signaled); + } + + safe_int_inc(&pck->reference_count); + nb_dispatch++; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Dispatching packet from filter %s to filter %s - %d packet in PID %s buffer ("LLU" us buffer)\n", pid->filter->name, dst->filter->name, gf_fq_count(dst->packets), pid->name, dst->buffer_duration )); + + if (cktype) { + safe_int_inc(&dst->filter->pending_packets); + gf_fq_add(dst->packets, inst); + post_task = GF_TRUE; + } else if (dst->requires_full_data_block) { + if (pck->info.flags & GF_PCKF_BLOCK_START) { + //missed end of previous, aggregate all before excluding this packet + if (!dst->last_block_ended) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s: Missed end of block signaling but got start of block - queuing for potential reaggregation\n", pid->filter->name)); + + //post process task if we have been reaggregating a packet + post_task = gf_filter_aggregate_packets(dst); + if (post_task && pid->nb_reaggregation_pending) pid->nb_reaggregation_pending--; + } + dst->last_block_ended = GF_TRUE; + } + //block end, aggregate all before and including this packet + if (pck->info.flags & GF_PCKF_BLOCK_END) { + //not starting at this packet, append and aggregate + if (!(pck->info.flags & GF_PCKF_BLOCK_START) && gf_list_count(dst->pck_reassembly) ) { + //insert packet into reassembly (no need to lock here) + gf_list_add(dst->pck_reassembly, inst); + + gf_filter_aggregate_packets(dst); + if (pid->nb_reaggregation_pending) pid->nb_reaggregation_pending--; + } + //single block packet, direct dispatch in packet queue (aggregation done before) + else { + assert(dst->last_block_ended); + + if (pck->info.duration && timescale) { + duration = ((u64)pck->info.duration) * 1000000; + duration /= timescale; + safe_int64_add(&dst->buffer_duration, duration); + } + inst->pck->info.flags |= GF_PCKF_BLOCK_START; + + safe_int_inc(&dst->filter->pending_packets); + gf_fq_add(dst->packets, inst); + } + dst->last_block_ended = GF_TRUE; + post_task = GF_TRUE; + } + //new block start or continuation + else { + if (pck->info.flags & GF_PCKF_BLOCK_START) { + pid->nb_reaggregation_pending++; + } + + //if packet mem is hold by filter we must copy the packet since it is no longer + //consumable until end of block is received, and source might be waiting for this packet to be freed to dispatch further packets + if (inst->pck->filter_owns_mem) { + u8 *data; + u32 alloc_size; + inst->pck = gf_filter_pck_new_alloc_internal(pck->pid, pck->data_length, &data, GF_TRUE); + alloc_size = inst->pck->alloc_size; + memcpy(inst->pck, pck, sizeof(GF_FilterPacket)); + inst->pck->pck = inst->pck; + inst->pck->data = data; + memcpy(inst->pck->data, pck->data, pck->data_length); + inst->pck->alloc_size = alloc_size; + inst->pck->filter_owns_mem = 0; + inst->pck->reference_count = 0; + inst->pck->reference = NULL; + inst->pck->destructor = NULL; + inst->pck->frame_ifce = NULL; + if (pck->props) { + GF_Err e; + inst->pck->props = gf_props_new(pck->pid->filter); + if (inst->pck->props) { + e = gf_props_merge_property(inst->pck->props, pck->props, NULL, NULL); + } else { + e = GF_OUT_OF_MEM; + } + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Filter %s: failed to copy properties for cloned packet: %s\n", pid->filter->name, gf_error_to_string(e) )); + } + } + if (inst->pck->pid_props) { + safe_int_inc(&inst->pck->pid_props->reference_count); + } + + assert(pck->reference_count); + safe_int_dec(&pck->reference_count); + safe_int_inc(&inst->pck->reference_count); + } + //insert packet into reassembly (no need to lock here) + gf_list_add(dst->pck_reassembly, inst); + + dst->last_block_ended = GF_FALSE; + //block not complete, don't post process task + } + + } else { + duration=0; + //store start of block info + if (pck->info.flags & GF_PCKF_BLOCK_START) { + dst->first_block_started = GF_TRUE; + duration = pck->info.duration; + } + if (pck->info.flags & GF_PCKF_BLOCK_END) { + //we didn't get a start for this end of block use the packet duration + if (!dst->first_block_started) { + duration=pck->info.duration; + } + dst->first_block_started = GF_FALSE; + } + + if (duration && timescale) { + duration *= 1000000; + duration /= timescale; + safe_int64_add(&dst->buffer_duration, duration); + } + + safe_int_inc(&dst->filter->pending_packets); +// + gf_fq_add(dst->packets, inst); + post_task = GF_TRUE; + } + if (post_task) { + + //make sure we lock the tasks mutex before getting the packet count, otherwise we might end up with a wrong number of packets + //if one thread consumes one packet while the dispatching thread (the caller here) is still upddating the state for that pid + gf_mx_p(pid->filter->tasks_mx); + u32 nb_pck = gf_fq_count(dst->packets); + //update buffer occupancy before dispatching the task - if target pid is processed before we are done disptching his packet, pid buffer occupancy + //will be updated during packet drop of target + if (pid->nb_buffer_unit < nb_pck) pid->nb_buffer_unit = nb_pck; + if ((s64) pid->buffer_duration < dst->buffer_duration) pid->buffer_duration = dst->buffer_duration; + gf_mx_v(pid->filter->tasks_mx); + + //post process task + gf_filter_post_process_task_internal(dst->filter, pid->direct_dispatch); + } + } + +#ifdef GPAC_MEMORY_TRACKING + if (pck->pid->filter->session->check_allocs) { + gf_mem_get_stats(&nb_allocs, NULL, &nb_reallocs, NULL); + pck->pid->filter->session->nb_alloc_pck += (nb_allocs - prev_nb_allocs); + pck->pid->filter->session->nb_realloc_pck += (nb_reallocs - prev_nb_reallocs); + } +#endif + + gf_filter_pid_would_block(pid); + + //unprotect the packet now that it is safely dispatched + assert(pck->reference_count); + if (safe_int_dec(&pck->reference_count) == 0) { + if (!nb_dispatch) { + if (nb_discard) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("All PID destinations on filter %s are in discard mode - discarding\n", pid->filter->name)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("No PID destination on filter %s for packet - discarding\n", pid->filter->name)); + } + } + gf_filter_packet_destroy(pck); + } + gf_rmt_end(); + return GF_OK; +} + +GF_EXPORT +GF_Err gf_filter_pck_send(GF_FilterPacket *pck) +{ + assert(pck->pid); + return gf_filter_pck_send_internal(pck, GF_TRUE); +} + +GF_EXPORT +GF_Err gf_filter_pck_ref(GF_FilterPacket **pck) +{ + if (! pck ) return GF_BAD_PARAM; + if (! *pck ) return GF_OK; + + (*pck) = (*pck)->pck; + safe_int_inc(& (*pck)->reference_count); + return GF_OK; +} + +GF_EXPORT +GF_Err gf_filter_pck_ref_props(GF_FilterPacket **pck) +{ + GF_FilterPacket *npck, *srcpck; + GF_FilterPid *pid; + if (! pck ) return GF_BAD_PARAM; + if (! *pck ) return GF_OK; + + srcpck = (*pck)->pck; + pid = srcpck->pid; + + npck = gf_fq_pop( pid->filter->session->pcks_refprops_reservoir); + if (!npck) { + GF_SAFEALLOC(npck, GF_FilterPacket); + if (!npck) return GF_OUT_OF_MEM; + } + npck->pck = npck; + npck->data = NULL; + npck->filter_owns_mem = 0; + npck->destructor = NULL; + gf_filter_pck_reset_props(npck, pid); + npck->info = srcpck->info; + npck->info.flags |= GF_PCKF_PROPS_REFERENCE; + //keep data size + npck->data_length = srcpck->data_length; + + if (srcpck->props) { + npck->props = srcpck->props; + safe_int_inc(& npck->props->reference_count); + } + if (srcpck->pid_props) { + npck->pid_props = srcpck->pid_props; + safe_int_inc(& npck->pid_props->pckrefs_reference_count); + } + + safe_int_inc(& npck->reference_count); + *pck = npck; + return GF_OK; +} + +GF_EXPORT +void gf_filter_pck_unref(GF_FilterPacket *pck) +{ + assert(pck); + pck=pck->pck; + + assert(pck->reference_count); + if (safe_int_dec(&pck->reference_count) == 0) { + gf_filter_packet_destroy(pck); + } +} + +GF_EXPORT +const u8 *gf_filter_pck_get_data(GF_FilterPacket *pck, u32 *size) +{ + assert(pck); + assert(size); + //get true packet pointer + pck=pck->pck; + *size = pck->data_length; + return (const char *)pck->data; +} + +static GF_Err gf_filter_pck_set_property_full(GF_FilterPacket *pck, u32 prop_4cc, const char *prop_name, char *dyn_name, const GF_PropertyValue *value) +{ + u32 hash; + assert(pck); + assert(pck->pid); + if (PCK_IS_INPUT(pck)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to set property on an input packet in filter %s\n", pck->pid->filter->name)); + return GF_BAD_PARAM; + } + //get true packet pointer + pck=pck->pck; + hash = gf_props_hash_djb2(prop_4cc, prop_name ? prop_name : dyn_name); + + if (!pck->props) { + pck->props = gf_props_new(pck->pid->filter); + } else { + gf_props_remove_property(pck->props, hash, prop_4cc, prop_name ? prop_name : dyn_name); + } + if (!value) return GF_OK; + + return gf_props_insert_property(pck->props, hash, prop_4cc, prop_name, dyn_name, value); +} + +GF_EXPORT +GF_Err gf_filter_pck_set_property(GF_FilterPacket *pck, u32 prop_4cc, const GF_PropertyValue *value) +{ + return gf_filter_pck_set_property_full(pck, prop_4cc, NULL, NULL, value); +} + +GF_EXPORT +GF_Err gf_filter_pck_set_property_str(GF_FilterPacket *pck, const char *name, const GF_PropertyValue *value) +{ + return gf_filter_pck_set_property_full(pck, 0, name, NULL, value); +} + +GF_EXPORT +GF_Err gf_filter_pck_set_property_dyn(GF_FilterPacket *pck, char *name, const GF_PropertyValue *value) +{ + return gf_filter_pck_set_property_full(pck, 0, NULL, name, value); +} + +GF_EXPORT +const GF_PropertyValue *gf_filter_pck_get_property(GF_FilterPacket *pck, u32 prop_4cc) +{ + //get true packet pointer + pck = pck->pck; + if (!pck->props) return NULL; + return gf_props_get_property(pck->props, prop_4cc, NULL); +} + +GF_EXPORT +const GF_PropertyValue *gf_filter_pck_get_property_str(GF_FilterPacket *pck, const char *prop_name) +{ + //get true packet pointer + pck = pck->pck; + if (!pck->props) return NULL; + return gf_props_get_property(pck->props, 0, prop_name); +} + +GF_EXPORT +const GF_PropertyValue *gf_filter_pck_enum_properties(GF_FilterPacket *pck, u32 *idx, u32 *prop_4cc, const char **prop_name) +{ + if (!pck->pck->props) return NULL; + return gf_props_enum_property(pck->pck->props, idx, prop_4cc, prop_name); +} + +#define PCK_SETTER_CHECK(_pname) \ + if (PCK_IS_INPUT(pck)) { \ + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to set %s on an input packet in filter %s\n", _pname, pck->pid->filter->name));\ + return GF_BAD_PARAM; \ + } \ + + +GF_EXPORT +GF_Err gf_filter_pck_set_framing(GF_FilterPacket *pck, Bool is_start, Bool is_end) +{ + PCK_SETTER_CHECK("framing info") + + if (is_start) pck->info.flags |= GF_PCKF_BLOCK_START; + else pck->info.flags &= ~GF_PCKF_BLOCK_START; + + if (is_end) pck->info.flags |= GF_PCKF_BLOCK_END; + else pck->info.flags &= ~GF_PCKF_BLOCK_END; + return GF_OK; +} + +GF_EXPORT +GF_Err gf_filter_pck_get_framing(GF_FilterPacket *pck, Bool *is_start, Bool *is_end) +{ + assert(pck); + //get true packet pointer + pck=pck->pck; + if (is_start) *is_start = (pck->info.flags & GF_PCKF_BLOCK_START) ? GF_TRUE : GF_FALSE; + if (is_end) *is_end = (pck->info.flags & GF_PCKF_BLOCK_END) ? GF_TRUE : GF_FALSE; + return GF_OK; +} + + +GF_EXPORT +GF_Err gf_filter_pck_set_dts(GF_FilterPacket *pck, u64 dts) +{ + PCK_SETTER_CHECK("DTS") + pck->info.dts = dts; + return GF_OK; +} + +GF_EXPORT +u64 gf_filter_pck_get_dts(GF_FilterPacket *pck) +{ + assert(pck); + //get true packet pointer + return pck->pck->info.dts; +} + +GF_EXPORT +GF_Err gf_filter_pck_set_cts(GF_FilterPacket *pck, u64 cts) +{ + PCK_SETTER_CHECK("CTS") + pck->info.cts = cts; + return GF_OK; +} + +GF_EXPORT +u64 gf_filter_pck_get_cts(GF_FilterPacket *pck) +{ + assert(pck); + //get true packet pointer + return pck->pck->info.cts; +} + +GF_EXPORT +u32 gf_filter_pck_get_timescale(GF_FilterPacket *pck) +{ + assert(pck); + //get true packet pointer + return pck->pck->pid_props->timescale ? pck->pck->pid_props->timescale : 1000; +} + +GF_EXPORT +GF_Err gf_filter_pck_set_sap(GF_FilterPacket *pck, GF_FilterSAPType sap_type) +{ + PCK_SETTER_CHECK("SAP") + pck->info.flags &= ~GF_PCK_SAP_MASK; + pck->info.flags |= (sap_type)<pck->info.flags & GF_PCK_SAP_MASK) >> GF_PCK_SAP_POS); +} + +GF_EXPORT +GF_Err gf_filter_pck_set_roll_info(GF_FilterPacket *pck, s16 roll_count) +{ + PCK_SETTER_CHECK("ROLL") + pck->info.roll = roll_count; + return GF_OK; +} + +GF_EXPORT +s16 gf_filter_pck_get_roll_info(GF_FilterPacket *pck) +{ + assert(pck); + //get true packet pointer + return pck->pck->info.roll; +} + +GF_EXPORT +GF_Err gf_filter_pck_set_interlaced(GF_FilterPacket *pck, u32 is_interlaced) +{ + PCK_SETTER_CHECK("interlaced") + pck->info.flags &= ~GF_PCK_ILACE_MASK; + if (is_interlaced) pck->info.flags |= is_interlaced<pck->info.flags & GF_PCK_ILACE_MASK) >> GF_PCK_ILACE_POS; +} + +GF_EXPORT +GF_Err gf_filter_pck_set_corrupted(GF_FilterPacket *pck, Bool is_corrupted) +{ + PCK_SETTER_CHECK("corrupted") + pck->info.flags &= ~GF_PCKF_CORRUPTED; + if (is_corrupted) pck->info.flags |= GF_PCKF_CORRUPTED; + return GF_OK; +} + +GF_EXPORT +Bool gf_filter_pck_get_corrupted(GF_FilterPacket *pck) +{ + assert(pck); + //get true packet pointer + return (pck->pck->info.flags & GF_PCKF_CORRUPTED) ? GF_TRUE : GF_FALSE; +} + +GF_EXPORT +GF_Err gf_filter_pck_set_duration(GF_FilterPacket *pck, u32 duration) +{ + PCK_SETTER_CHECK("dur") + pck->info.duration = duration; + pck->info.flags |= GF_PCKF_DUR_SET; + return GF_OK; +} + +GF_EXPORT +u32 gf_filter_pck_get_duration(GF_FilterPacket *pck) +{ + assert(pck); + //get true packet pointer + return pck->pck->info.duration; +} + +GF_EXPORT +GF_Err gf_filter_pck_set_seek_flag(GF_FilterPacket *pck, Bool is_seek) +{ + PCK_SETTER_CHECK("seek") + pck->info.flags &= ~GF_PCKF_SEEK; + if (is_seek) pck->info.flags |= GF_PCKF_SEEK; + return GF_OK; +} + +GF_EXPORT +Bool gf_filter_pck_get_seek_flag(GF_FilterPacket *pck) +{ + assert(pck); + //get true packet pointer + return (pck->pck->info.flags & GF_PCKF_SEEK) ? GF_TRUE : GF_FALSE; +} + +GF_EXPORT +GF_Err gf_filter_pck_set_dependency_flags(GF_FilterPacket *pck, u8 dep_flags) +{ + PCK_SETTER_CHECK("dependency_flags") + pck->info.flags &= ~0xFF; + pck->info.flags |= dep_flags; + return GF_OK; +} + +GF_EXPORT +u8 gf_filter_pck_get_dependency_flags(GF_FilterPacket *pck) +{ + assert(pck); + //get true packet pointer + return pck->pck->info.flags & 0xFF; +} + +GF_EXPORT +GF_Err gf_filter_pck_set_carousel_version(GF_FilterPacket *pck, u8 version_number) +{ + PCK_SETTER_CHECK("carousel_version") + pck->info.carousel_version_number = version_number; + return GF_OK; +} + +GF_EXPORT +u8 gf_filter_pck_get_carousel_version(GF_FilterPacket *pck) +{ + assert(pck); + //get true packet pointer + return pck->pck->info.carousel_version_number; +} + +GF_EXPORT +GF_Err gf_filter_pck_set_byte_offset(GF_FilterPacket *pck, u64 byte_offset) +{ + PCK_SETTER_CHECK("byteOffset") + pck->info.byte_offset = byte_offset; + return GF_OK; +} + +GF_EXPORT +u64 gf_filter_pck_get_byte_offset(GF_FilterPacket *pck) +{ + assert(pck); + //get true packet pointer + return pck->pck->info.byte_offset; +} + +GF_EXPORT +GF_Err gf_filter_pck_set_crypt_flags(GF_FilterPacket *pck, u8 crypt_flag) +{ + PCK_SETTER_CHECK("byteOffset") + pck->info.flags &= ~GF_PCK_CRYPT_MASK; + pck->info.flags |= crypt_flag << GF_PCK_CRYPT_POS; + return GF_OK; +} + +GF_EXPORT +u8 gf_filter_pck_get_crypt_flags(GF_FilterPacket *pck) +{ + //get true packet pointer + return (pck->pck->info.flags & GF_PCK_CRYPT_MASK) >> GF_PCK_CRYPT_POS; +} + +GF_EXPORT +GF_Err gf_filter_pck_set_seq_num(GF_FilterPacket *pck, u32 seq_num) +{ + PCK_SETTER_CHECK("seqNum") + pck->info.seq_num = seq_num; + return GF_OK; +} + +GF_EXPORT +u32 gf_filter_pck_get_seq_num(GF_FilterPacket *pck) +{ + //get true packet pointer + return pck->pck->info.seq_num; +} + +GF_EXPORT +GF_Err gf_filter_pck_set_clock_type(GF_FilterPacket *pck, GF_FilterClockType ctype) +{ + PCK_SETTER_CHECK("clock_type") + pck->info.flags &= ~GF_PCK_CKTYPE_MASK; + pck->info.flags |= ctype << GF_PCK_CKTYPE_POS; + return GF_OK; +} + +GF_EXPORT +GF_FilterClockType gf_filter_pck_get_clock_type(GF_FilterPacket *pck) +{ + //get true packet pointer + return (pck->pck->info.flags & GF_PCK_CKTYPE_MASK) >> GF_PCK_CKTYPE_POS; +} + +GF_EXPORT +GF_FilterFrameInterface *gf_filter_pck_get_frame_interface(GF_FilterPacket *pck) +{ + assert(pck); + return pck->pck->frame_ifce; +} + +GF_EXPORT +GF_Err gf_filter_pck_expand(GF_FilterPacket *pck, u32 nb_bytes_to_add, u8 **data_start, u8 **new_range_start, u32 *new_size) +{ + assert(pck); + if (PCK_IS_INPUT(pck)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to reallocate input packet on output PID in filter %s\n", pck->pid->filter->name)); + return GF_BAD_PARAM; + } + if (! pck->src_filter) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to reallocate an already sent packet in filter %s\n", pck->pid->filter->name)); + return GF_BAD_PARAM; + } + if (pck->filter_owns_mem) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to reallocate a shared memory packet in filter %s\n", pck->pid->filter->name)); + return GF_BAD_PARAM; + } + if (!data_start && !new_range_start) + return GF_BAD_PARAM; + + if (pck->data_length + nb_bytes_to_add > pck->alloc_size) { + pck->alloc_size = pck->data_length + nb_bytes_to_add; + pck->data = gf_realloc(pck->data, pck->alloc_size); +#ifdef GPAC_MEMORY_TRACKING + pck->pid->filter->session->nb_realloc_pck++; +#endif + } + pck->info.byte_offset = GF_FILTER_NO_BO; + if (data_start) *data_start = pck->data; + if (new_range_start) *new_range_start = pck->data + pck->data_length; + pck->data_length += nb_bytes_to_add; + if (new_size) *new_size = pck->data_length; + + return GF_OK; +} + +GF_EXPORT +GF_Err gf_filter_pck_truncate(GF_FilterPacket *pck, u32 size) +{ + assert(pck); + if (PCK_IS_INPUT(pck)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to truncate input packet on output PID in filter %s\n", pck->pid->filter->name)); + return GF_BAD_PARAM; + } + if (! pck->src_filter) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to truncate an already sent packet in filter %s\n", pck->pid->filter->name)); + return GF_BAD_PARAM; + } + if (pck->data_length > size) pck->data_length = size; + return GF_OK; +} + +GF_EXPORT +Bool gf_filter_pck_is_blocking_ref(GF_FilterPacket *pck) +{ + pck = pck->pck; + + while (pck) { + if (pck->frame_ifce) { + if (pck->frame_ifce->flags & GF_FRAME_IFCE_BLOCKING) + return GF_TRUE; + } else { + if (pck->destructor && pck->filter_owns_mem) + return GF_TRUE; + } + pck = pck->reference; + } + return GF_FALSE; +} + diff --git a/src/filter_core/filter_pid.c b/src/filter_core/filter_pid.c new file mode 100644 index 0000000..4f32487 --- /dev/null +++ b/src/filter_core/filter_pid.c @@ -0,0 +1,6825 @@ + /* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2018 + * All rights reserved + * + * This file is part of GPAC / filters sub-project + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terfsess of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "filter_session.h" +#include + +void pcki_del(GF_FilterPacketInstance *pcki) +{ + assert(pcki->pck->reference_count); + if (safe_int_dec(&pcki->pck->reference_count) == 0) { + gf_filter_packet_destroy(pcki->pck); + } + gf_free(pcki); +} + +void gf_filter_pid_inst_reset(GF_FilterPidInst *pidinst) +{ + assert(pidinst); + while (gf_fq_count(pidinst->packets)) { + GF_FilterPacketInstance *pcki = gf_fq_pop(pidinst->packets); + pcki_del(pcki); + } + + while (gf_list_count(pidinst->pck_reassembly)) { + GF_FilterPacketInstance *pcki = gf_list_pop_back(pidinst->pck_reassembly); + pcki_del(pcki); + } +} + +void gf_filter_pid_inst_del(GF_FilterPidInst *pidinst) +{ + assert(pidinst); + gf_filter_pid_inst_reset(pidinst); + + gf_fq_del(pidinst->packets, (gf_destruct_fun) pcki_del); + gf_mx_del(pidinst->pck_mx); + gf_list_del(pidinst->pck_reassembly); + if (pidinst->props) { + assert(pidinst->props->reference_count); + if (safe_int_dec(&pidinst->props->reference_count) == 0) { + //see \ref gf_filter_pid_merge_properties_internal for mutex + gf_mx_p(pidinst->pid->filter->tasks_mx); + gf_list_del_item(pidinst->pid->properties, pidinst->props); + gf_mx_v(pidinst->pid->filter->tasks_mx); + gf_props_del(pidinst->props); + } + } + gf_free(pidinst); +} + +static GF_FilterPidInst *gf_filter_pid_inst_new(GF_Filter *filter, GF_FilterPid *pid) +{ + GF_FilterPidInst *pidinst; + GF_SAFEALLOC(pidinst, GF_FilterPidInst); + if (!pidinst) return NULL; + pidinst->pid = pid; + pidinst->filter = filter; + + if (filter->session->use_locks) { + char szName[200]; + u32 pid_idx = 1 + gf_list_find(pid->filter->output_pids, pid); + u32 dst_idx = 1 + pid->num_destinations; + snprintf(szName, 200, "F%sPid%dDest%dPackets", filter->name, pid_idx, dst_idx); + pidinst->pck_mx = gf_mx_new(szName); + } + + pidinst->packets = gf_fq_new(pidinst->pck_mx); + + pidinst->pck_reassembly = gf_list_new(); + pidinst->last_block_ended = GF_TRUE; + return pidinst; +} + +static void gf_filter_pid_check_unblock(GF_FilterPid *pid) +{ + Bool unblock; + + //if we are in end of stream state and done with all packets, stay blocked + if (pid->has_seen_eos && !pid->nb_buffer_unit) { + if (!pid->would_block) { + safe_int_inc(&pid->would_block); + safe_int_inc(&pid->filter->would_block); + assert(pid->filter->would_block + pid->filter->num_out_pids_not_connected <= pid->filter->num_output_pids); + } + return; + } + + unblock=GF_FALSE; + + assert(pid->playback_speed_scaler); + + //we block according to the number of dispatched units (decoder output) or to the requested buffer duration + //for other streams - unblock accordingly + if (pid->max_buffer_unit) { + if (pid->nb_buffer_unit * GF_FILTER_SPEED_SCALER < pid->max_buffer_unit * pid->playback_speed_scaler) { + unblock=GF_TRUE; + } + } else if (pid->buffer_duration * GF_FILTER_SPEED_SCALER < pid->max_buffer_time * pid->playback_speed_scaler) { + unblock=GF_TRUE; + } + + gf_mx_p(pid->filter->tasks_mx); + if (pid->would_block && unblock) { + assert(pid->would_block); + safe_int_dec(&pid->would_block); + + assert(pid->filter->would_block); + safe_int_dec(&pid->filter->would_block); + assert((s32)pid->filter->would_block>=0); + assert(pid->filter->would_block + pid->filter->num_out_pids_not_connected <= pid->filter->num_output_pids); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s unblocked (filter has %d blocking pids)\n", pid->pid->filter->name, pid->pid->name, pid->pid->filter->would_block)); + + if (pid->filter->would_block + pid->filter->num_out_pids_not_connected < pid->filter->num_output_pids) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s has only %d / %d blocked pids, requesting process task (%d queued)\n", pid->filter->name, pid->filter->would_block + pid->filter->num_out_pids_not_connected, pid->filter->num_output_pids, pid->filter->process_task_queued)); + + //post a process task + gf_filter_post_process_task(pid->filter); + } + } + gf_mx_v(pid->filter->tasks_mx); +} + +static void gf_filter_pid_inst_check_dependencies(GF_FilterPidInst *pidi) +{ + const GF_PropertyValue *p; + u32 i, dep_id = 0; + GF_FilterPid *pid = pidi->pid; + GF_Filter *filter = pid->filter; + + //check pid dependency + p = gf_filter_pid_get_property_first(pid, GF_PROP_PID_DEPENDENCY_ID); + if (p) dep_id = p->value.uint; + + if (!dep_id) return; + + for (i=0; inum_output_pids; i++) { + u32 j; + GF_FilterPid *a_pid = gf_list_get(filter->output_pids, i); + if (a_pid==pid) continue; + p = gf_filter_pid_get_property_first(a_pid, GF_PROP_PID_ID); + if (!p) p = gf_filter_pid_get_property_first(a_pid, GF_PROP_PID_ESID); + if (!p || (p->value.uint != dep_id)) continue; + + for (j=0; jnum_destinations; j++) { + GF_FilterPidInst *a_pidi = gf_list_get(a_pid->destinations, j); + if (a_pidi == pidi) continue; + if (! a_pidi->is_decoder_input) continue; + + if (a_pidi->filter == pidi->filter) continue; + + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Filter %s PID %s connected to decoder %s, but dependent stream %s connected to %s - switching pid destination\n", a_pid->filter->name, a_pid->name, a_pidi->filter->name, pidi->pid->name, pidi->filter->name)); + + //disconnect this pid instance from its current decoder + gf_fs_post_task(filter->session, gf_filter_pid_disconnect_task, a_pidi->filter, a_pid, "pidinst_disconnect", NULL); + + //reconnect this pid instance to the new decoder + safe_int_inc(&pid->filter->out_pid_connection_pending); + gf_filter_pid_post_connect_task(pidi->filter, a_pid); + + } + } +} + +static void gf_filter_pid_update_caps(GF_FilterPid *pid) +{ + u32 mtype=0, codecid=0; + u32 i, count; + const GF_PropertyValue *p; + + pid->raw_media = GF_FALSE; + p = gf_filter_pid_get_property_first(pid, GF_PROP_PID_CODECID); + if (p) codecid = p->value.uint; + + p = gf_filter_pid_get_property_first(pid, GF_PROP_PID_STREAM_TYPE); + if (p) mtype = p->value.uint; + + pid->stream_type = mtype; + pid->codecid = codecid; + + if (pid->user_max_buffer_time) { + pid->max_buffer_time = pid->user_max_buffer_time; + pid->max_buffer_unit = 0; + } else { + pid->max_buffer_time = pid->filter->session->default_pid_buffer_max_us; + pid->max_buffer_unit = pid->filter->session->default_pid_buffer_max_units; + } + pid->raw_media = GF_FALSE; + + if (codecid!=GF_CODECID_RAW) { + count=pid->filter->num_input_pids; + for (i=0; ifilter->input_pids, i); + u32 i_codecid=0, i_type=0; + p = gf_filter_pid_get_property_first(pidi->pid, GF_PROP_PID_STREAM_TYPE); + if (p) i_type = p->value.uint; + + p = gf_filter_pid_get_property_first(pidi->pid, GF_PROP_PID_CODECID); + if (p) i_codecid = p->value.uint; + //same stream type but changing from raw to not raw: this is an encoder input pid + if ((mtype==i_type) && (i_codecid==GF_CODECID_RAW)) { + pidi->is_encoder_input = GF_TRUE; + } + } + return; + } + + if (pid->user_max_buffer_time) { + pid->max_buffer_time = pid->user_max_buffer_time; + pid->max_buffer_unit = 0; + } + + + //output is a decoded raw stream: if some input has same type but different codecid this is a decoder + //set input buffer size + count=pid->filter->num_input_pids; + for (i=0; ifilter->input_pids, i); + + p = gf_filter_pid_get_property_first(pidi->pid, GF_PROP_PID_STREAM_TYPE); + if (p) i_type = p->value.uint; + + p = gf_filter_pid_get_property_first(pidi->pid, GF_PROP_PID_CODECID); + if (p) i_codecid = p->value.uint; + + //same stream type but changing format type: this is a decoder input pid, set buffer req + if ((mtype==i_type) && (codecid != i_codecid)) { + //default decoder buffer + if (pidi->pid->user_max_buffer_time) + pidi->pid->max_buffer_time = pidi->pid->user_max_buffer_time; + else + pidi->pid->max_buffer_time = pidi->pid->filter->session->decoder_pid_buffer_max_us; + pidi->pid->max_buffer_unit = 0; + + + if (mtype==GF_STREAM_VISUAL) { + pid->max_buffer_unit = 4; + } else if (mtype==GF_STREAM_AUDIO) { + pid->max_buffer_unit = 20; + } + + if (!pidi->is_decoder_input) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s pid instance %s marked as decoder input\n", pidi->pid->filter->name, pidi->pid->name)); + pidi->is_decoder_input = GF_TRUE; + safe_int_inc(&pidi->pid->nb_decoder_inputs); + + if ((i_type == GF_STREAM_AUDIO) || (i_type == GF_STREAM_VISUAL)) + gf_filter_pid_inst_check_dependencies(pidi); + } + } + //same media type, different codec if is raw stream + else if (mtype==i_type) { + pid->raw_media = GF_TRUE; + } + //input is file, output is not and codec ID is raw, this is a raw media pid + else if ((i_type==GF_STREAM_FILE) && (mtype!=GF_STREAM_FILE) && (codecid==GF_CODECID_RAW) ) { + pid->raw_media = GF_TRUE; + } + } + //source pid, mark raw media + if (!count && pid->num_destinations) { + pid->raw_media = GF_TRUE; + } +} + +#define TASK_REQUEUE(_t) \ + _t->requeue_request = GF_TRUE; \ + _t->schedule_next_time = gf_sys_clock_high_res() + 50; \ + +void gf_filter_pid_inst_delete_task(GF_FSTask *task) +{ + GF_FilterPid *pid = task->pid; + GF_FilterPidInst *pidinst = task->udta; + GF_Filter *filter = pid->filter; + Bool pid_still_alive = GF_FALSE; + + assert(filter); + //reset in process + if ((pidinst->filter && pidinst->discard_packets) || filter->stream_reset_pending) { + TASK_REQUEUE(task) + return; + } + + //reset PID instance buffers before checking number of output shared packets + //otherwise we may block because some of the shared packets are in the + //pid instance buffer (not consumed) + gf_filter_pid_inst_reset(pidinst); + + //we still have packets out there! + if (pidinst->pid->nb_shared_packets_out) { + //special case for disconnect of fanouts, destroy even if shared packets out + if (!pid->num_destinations || ((pid->num_destinations>=1) && (gf_list_find(pid->destinations, pidinst)>=0))) { + TASK_REQUEUE(task) + return; + } + } + + //WARNING at this point pidinst->filter may be destroyed + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s pid instance %s destruction (%d fan-out)\n", filter->name, pid->name, pid->num_destinations)); + gf_mx_p(filter->tasks_mx); + gf_list_del_item(pid->destinations, pidinst); + pid->num_destinations = gf_list_count(pid->destinations); + if (pidinst->pid->num_pidinst_del_pending) { + pidinst->pid->num_pidinst_del_pending--; + if (pidinst->pid->num_pidinst_del_pending) + pid_still_alive = GF_TRUE; + } + gf_mx_v(filter->tasks_mx); + + if (pidinst->is_decoder_input) { + assert(pid->nb_decoder_inputs); + safe_int_dec(&pid->nb_decoder_inputs); + } + gf_filter_pid_inst_del(pidinst); + //recompute max buf dur and nb units to update blocking state + if (pid->num_destinations) { + u32 i; + u32 nb_pck = 0; + s64 buf_dur = 0; + for (i = 0; i < pid->num_destinations; i++) { + GF_FilterPidInst *apidi = gf_list_get(pid->destinations, i); + u32 npck = gf_fq_count(apidi->packets); + if (npck > nb_pck) nb_pck = npck; + if (apidi->buffer_duration > buf_dur) buf_dur = apidi->buffer_duration; + } + pid->nb_buffer_unit = nb_pck; + pid->buffer_duration = buf_dur; + } else { + pid->nb_buffer_unit = 0; + pid->buffer_duration = 0; + } + + assert(pid->filter == filter); + + if (pid_still_alive) + return; + + //some more destinations on pid, update blocking state + if (pid->num_destinations || pid->init_task_pending) { + if (pid->would_block) + gf_filter_pid_check_unblock(pid); + else + gf_filter_pid_would_block(pid); + + return; + } + //still some input to the filter, cannot destroy the output pid + if (gf_list_count(filter->input_pids)) + return; + //no more destinations on pid, destroy it + if (pid->would_block) { + assert(pid->filter->would_block); + safe_int_dec(&pid->filter->would_block); + } + + gf_mx_p(filter->tasks_mx); + gf_list_del_item(filter->output_pids, pid); + filter->num_output_pids = gf_list_count(filter->output_pids); + gf_filter_pid_del(pid); + gf_mx_v(filter->tasks_mx); + + //no more pids on filter, destroy it + if (!gf_list_count(filter->output_pids) && !gf_list_count(filter->input_pids)) { + gf_filter_post_remove(filter); + } +} + +void gf_filter_pid_inst_swap_delete(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPidInst *pidinst, GF_FilterPidInst *dst_swapinst) +{ + u32 i, j; + + //reset PID instance buffers before checking number of output shared packets + //otherwise we may block because some of the shared packets are in the + //pid instance buffer (not consumed) + gf_filter_pid_inst_reset(pidinst); + + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s pid instance %s swap destruction\n", filter->name, pidinst->pid->name)); + gf_mx_p(filter->tasks_mx); + gf_list_del_item(filter->input_pids, pidinst); + filter->num_input_pids = gf_list_count(filter->input_pids); + gf_mx_v(filter->tasks_mx); + + gf_mx_p(pid->filter->tasks_mx); + gf_list_del_item(pid->destinations, pidinst); + pid->num_destinations = gf_list_count(pid->destinations); + gf_mx_v(pid->filter->tasks_mx); + + + if (pidinst->is_decoder_input) { + assert(pid->nb_decoder_inputs); + safe_int_dec(&pid->nb_decoder_inputs); + } + //this pid instance is registered to destination filter for chain reconfigure, don't discard it + if (filter->detached_pid_inst && (gf_list_find(filter->detached_pid_inst, pidinst)>=0) ) + return; + + gf_filter_pid_inst_del(pidinst); + + if (filter->num_input_pids) return; + //we still have other pid instances registered for chain reconfigure, don't discard the filter + if (filter->detached_pid_inst) return; + + //filter no longer used, disconnect chain + for (i=0; inum_output_pids; i++) { + GF_FilterPid *a_pid = gf_list_get(filter->output_pids, i); + for (j=0; jnum_destinations; j++) { + GF_FilterPidInst *a_pidi = gf_list_get(a_pid->destinations, j); + if (a_pidi == dst_swapinst) continue; + + gf_filter_pid_inst_swap_delete(a_pidi->filter, a_pid, a_pidi, dst_swapinst); + } + } + filter->swap_pidinst_dst = NULL; + filter->swap_pidinst_src = NULL; + gf_filter_post_remove(filter); +} + +void gf_filter_pid_inst_swap_delete_task(GF_FSTask *task) +{ + GF_FilterPidInst *pidinst = task->udta; + GF_Filter *filter = pidinst->filter; + GF_FilterPid *pid = task->pid ? task->pid : pidinst->pid; + GF_FilterPidInst *dst_swapinst = pidinst->filter->swap_pidinst_dst; + + //reset in process + if ((pidinst->filter && pidinst->discard_packets) + || filter->stream_reset_pending + || filter->nb_shared_packets_out + ) { + TASK_REQUEUE(task) + return; + } + if (pidinst->filter) + pidinst->filter->swap_pidinst_dst = NULL; + + gf_filter_pid_inst_swap_delete(filter, pid, pidinst, dst_swapinst); +} + +void gf_filter_pid_inst_swap(GF_Filter *filter, GF_FilterPidInst *dst) +{ + GF_PropertyMap *prev_dst_props; + u32 nb_pck_transfer=0; + GF_FilterPidInst *src = filter->swap_pidinst_src; + if (!src) src = filter->swap_pidinst_dst; + + if (src) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s swaping PID %s to PID %s\n", filter->name, src->pid->name, dst->pid->name)); + } + + + if (filter->swap_needs_init) { + //we are in detach state, the packet queue of the old PID is never read + assert(filter->swap_pidinst_dst && filter->swap_pidinst_dst->detach_pending); + //we are in pending stete, the origin of the old PID is never dispatching + assert(dst->pid && dst->pid->filter && dst->pid->filter->out_pid_connection_pending); + //we can therefore swap the packet queues safely and other important info + } + //otherwise we actually swap the pid instance on the same PID + else { + gf_mx_p(dst->pid->filter->tasks_mx); + if (src) + gf_list_del_item(dst->pid->destinations, src); + if (gf_list_find(dst->pid->destinations, dst)<0) + gf_list_add(dst->pid->destinations, dst); + if (gf_list_find(dst->filter->input_pids, dst)<0) { + gf_list_add(dst->filter->input_pids, dst); + dst->filter->num_input_pids = gf_list_count(dst->filter->input_pids); + } + gf_mx_v(dst->pid->filter->tasks_mx); + } + + if (src) { + GF_FilterPacketInstance *pcki; + while (1) { + pcki = gf_fq_pop(src->packets); + if (!pcki) break; + assert(src->filter->pending_packets); + safe_int_dec(&src->filter->pending_packets); + + pcki->pid = dst; + gf_fq_add(dst->packets, pcki); + safe_int_inc(&dst->filter->pending_packets); + nb_pck_transfer++; + } + if (src->requires_full_data_block && gf_list_count(src->pck_reassembly)) { + dst->requires_full_data_block = src->requires_full_data_block; + dst->last_block_ended = src->last_block_ended; + dst->first_block_started = src->first_block_started; + if (!dst->pck_reassembly) dst->pck_reassembly = gf_list_new(); + while (gf_list_count(src->pck_reassembly)) { + pcki = gf_list_pop_front(src->pck_reassembly); + pcki->pid = dst; + gf_list_add(dst->pck_reassembly, pcki); + } + } + //copy over state + dst->is_end_of_stream = src->is_end_of_stream; + dst->nb_eos_signaled = src->nb_eos_signaled; + dst->buffer_duration = src->buffer_duration; + dst->nb_clocks_signaled = src->nb_clocks_signaled; + + //switch previous src property map to this new pid (this avoids rewriting props of already dispatched packets) + //it may happen that we already have props on dest, due to configure of the pid + //use the old props as new ones and merge the previous props of dst in the new props + prev_dst_props = dst->props; + dst->props = src->props; + dst->force_reconfig = GF_TRUE; + src->force_reconfig = GF_TRUE; + src->props = NULL; + if (prev_dst_props) { + gf_props_merge_property(dst->props, prev_dst_props, NULL, NULL); + assert(prev_dst_props->reference_count); + if (safe_int_dec(&prev_dst_props->reference_count) == 0) { + gf_props_del(prev_dst_props); + } + } + + if (nb_pck_transfer && !dst->filter->process_task_queued) { + gf_filter_post_process_task(dst->filter); + } + } + + + src = filter->swap_pidinst_dst; + if (src) { + if (filter->swap_needs_init) { + //exit out special handling of the pid since we are ready to detach + assert(src->filter->stream_reset_pending); + safe_int_dec(&src->filter->stream_reset_pending); + + //post detach task, we will reset the swap_pidinst only once truly deconnected from filter + safe_int_inc(&src->pid->filter->detach_pid_tasks_pending); + safe_int_inc(&filter->detach_pid_tasks_pending); + gf_fs_post_task(filter->session, gf_filter_pid_detach_task, src->filter, src->pid, "pidinst_detach", filter); + } else { + GF_Filter *src_filter = src->filter; + assert(!src->filter->sticky); + assert(src->filter->num_input_pids==1); + + gf_mx_p(src_filter->tasks_mx); + gf_list_del_item(src_filter->input_pids, src); + src_filter->num_input_pids = gf_list_count(src_filter->input_pids); + gf_mx_v(src_filter->tasks_mx); + + gf_list_del_item(src->pid->destinations, src); + src->pid->num_destinations = gf_list_count(src->pid->destinations); + gf_filter_pid_inst_del(src); + + filter->swap_pidinst_dst = NULL; + filter->swap_pidinst_src = NULL; + gf_filter_post_remove(src_filter); + } + } + + if (filter->swap_pidinst_src) { + src = filter->swap_pidinst_src; + src->filter->swap_pidinst_dst = filter->swap_pidinst_dst; + gf_fs_post_task(filter->session, gf_filter_pid_inst_swap_delete_task, src->filter, src->pid, "pid_inst_delete", src); + } +} + + +typedef enum { + GF_PID_CONF_CONNECT, + GF_PID_CONF_RECONFIG, + GF_PID_CONF_REMOVE, +} GF_PidConnectType; + +static GF_Err gf_filter_pid_configure(GF_Filter *filter, GF_FilterPid *pid, GF_PidConnectType ctype) +{ + u32 i, count; + GF_Err e; + Bool new_pid_inst=GF_FALSE; + GF_FilterPidInst *pidinst=NULL; + GF_Filter *alias_orig = NULL; + + if (filter->multi_sink_target) { + alias_orig = filter; + filter = filter->multi_sink_target; + } + + assert(filter->freg->configure_pid); + if (filter->finalized) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Trying to configure PID %s in finalized filter %s\n", pid->name, filter->name)); + if (ctype==GF_PID_CONF_CONNECT) { + assert(pid->filter->out_pid_connection_pending); + safe_int_dec(&pid->filter->out_pid_connection_pending); + } + return GF_SERVICE_ERROR; + } + + if (filter->detached_pid_inst) { + count = gf_list_count(filter->detached_pid_inst); + for (i=0; idetached_pid_inst, i); + if (pidinst->filter==filter) { + gf_list_rem(filter->detached_pid_inst, i); + //reattach new filter and pid + pidinst->filter = filter; + pidinst->pid = pid; + + assert(!pidinst->props); + + //and treat as new pid inst + if (ctype == GF_PID_CONF_CONNECT) { + new_pid_inst=GF_TRUE; + } + assert(pidinst->detach_pending); + safe_int_dec(&pidinst->detach_pending); + break; + } + pidinst=NULL; + } + if (! gf_list_count(filter->detached_pid_inst)) { + gf_list_del(filter->detached_pid_inst); + filter->detached_pid_inst = NULL; + } + } + if (!pidinst) { + count = pid->num_destinations; + for (i=0; idestinations, i); + if (pidinst->filter==filter) { + break; + } + pidinst=NULL; + } + } + + //first connection of this PID to this filter + if (!pidinst) { + if (ctype != GF_PID_CONF_CONNECT) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Trying to disconnect PID %s not found in filter %s inputs\n", pid->name, filter->name)); + return GF_SERVICE_ERROR; + } + pidinst = gf_filter_pid_inst_new(filter, pid); + new_pid_inst=GF_TRUE; + pidinst->alias_orig = alias_orig; + } + + //if new, add the PID to input/output before calling configure + if (new_pid_inst) { + assert(pidinst); + gf_mx_p(pid->filter->tasks_mx); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Registering %s:%s as destination for %s:%s\n", pid->filter->name, pid->name, pidinst->filter->name, pidinst->pid->name)); + gf_list_add(pid->destinations, pidinst); + pid->num_destinations = gf_list_count(pid->destinations); + + if (!filter->input_pids) filter->input_pids = gf_list_new(); + gf_list_add(filter->input_pids, pidinst); + filter->num_input_pids = gf_list_count(filter->input_pids); + + gf_mx_v(pid->filter->tasks_mx); + + //new connection, update caps in case we have events using caps (buffer req) being sent + //while processing the configure (they would be dispatched on the source filter, not the dest one being + //processed here) + gf_filter_pid_update_caps(pid); + } + + //we are swaping a PID instance (dyn insert of a filter), do it before reconnecting + //in order to have properties in place + //TODO: handle error case, we might need to re-switch the pid inst! + if (filter->swap_pidinst_src || filter->swap_pidinst_dst) { + gf_filter_pid_inst_swap(filter, pidinst); + } + + filter->in_connect_err = GF_EOS; + //commented out: audio thread may be pulling packets out of the pid but not in the compositor:process, which + //could be called for video at the same time... +#if 0 + FSESS_CHECK_THREAD(filter) +#endif + + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s reconfigure\n", pidinst->filter->name, pidinst->pid->name)); + e = filter->freg->configure_pid(filter, (GF_FilterPid*) pidinst, (ctype==GF_PID_CONF_REMOVE) ? GF_TRUE : GF_FALSE); + +#ifdef GPAC_MEMORY_TRACKING + if (filter->session->check_allocs) { + if (filter->nb_consecutive_process >= filter->max_nb_consecutive_process) { + filter->max_nb_consecutive_process = filter->nb_consecutive_process; + filter->max_nb_process = filter->nb_process_since_reset; + filter->max_stats_nb_alloc = filter->stats_nb_alloc; + filter->max_stats_nb_calloc = filter->stats_nb_calloc; + filter->max_stats_nb_realloc = filter->stats_nb_realloc; + filter->max_stats_nb_free = filter->stats_nb_free; + } + filter->stats_mem_allocated = 0; + filter->stats_nb_alloc = filter->stats_nb_realloc = filter->stats_nb_free = 0; + filter->nb_process_since_reset = filter->nb_consecutive_process = 0; + } +#endif + if ((e==GF_OK) && (filter->in_connect_errin_connect_err; + + filter->in_connect_err = GF_OK; + + if (e==GF_OK) { + //if new, register the new pid instance, and the source pid as input to this filter + if (new_pid_inst) { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Connected filter %s (%p) PID %s (%p) (%d fan-out) to filter %s (%p)\n", pid->filter->name, pid->filter, pid->name, pid, pid->num_destinations, filter->name, filter)); + } + } + //failure on reconfigure, try reloading a filter chain + else if ((ctype==GF_PID_CONF_RECONFIG) && (e != GF_FILTER_NOT_SUPPORTED)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to reconfigure PID %s:%s in filter %s: %s, reloading filter graph\n", pid->filter->name, pid->name, filter->name, gf_error_to_string(e) )); + + if (e==GF_BAD_PARAM) { + filter->session->last_connect_error = e; + } else { + gf_filter_relink_dst(pidinst); + } + } else { + + //error, remove from input and output + gf_mx_p(filter->tasks_mx); + gf_list_del_item(filter->input_pids, pidinst); + filter->num_input_pids = gf_list_count(filter->input_pids); + filter->freg->configure_pid(filter, (GF_FilterPid *) pidinst, GF_TRUE); + gf_mx_v(filter->tasks_mx); + + gf_mx_p(pidinst->pid->filter->tasks_mx); + gf_list_del_item(pidinst->pid->destinations, pidinst); + pidinst->pid->num_destinations = gf_list_count(pidinst->pid->destinations); + //detach filter from pid instance + pidinst->filter = NULL; + gf_mx_v(pidinst->pid->filter->tasks_mx); + + //if connect and error, direct delete of pid + if (new_pid_inst) { + gf_mx_p(pid->filter->tasks_mx); + gf_list_del_item(pid->destinations, pidinst); + pid->num_destinations = gf_list_count(pid->destinations); + //destroy pid instance + gf_filter_pid_inst_del(pidinst); + gf_mx_v(pid->filter->tasks_mx); + } + + + if (e==GF_REQUIRES_NEW_INSTANCE) { + //TODO: copy over args from current filter + GF_Filter *new_filter = gf_filter_clone(filter); + if (new_filter) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Clone filter %s, new instance for pid %s\n", filter->name, pid->name)); + gf_filter_pid_post_connect_task(new_filter, pid); + return GF_OK; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to clone filter %s\n", filter->name)); + e = GF_OUT_OF_MEM; + } + } + if (e && (ctype==GF_PID_CONF_REMOVE)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to disconnect filter %s PID %s from filter %s: %s\n", pid->filter->name, pid->name, filter->name, gf_error_to_string(e) )); + } + else if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to connect filter %s PID %s to filter %s: %s\n", pid->filter->name, pid->name, filter->name, gf_error_to_string(e) )); + + if ((e==GF_BAD_PARAM) || (e==GF_FILTER_NOT_SUPPORTED) || (filter->session->flags & GF_FS_FLAG_NO_REASSIGN)) { + if (e!=GF_FILTER_NOT_SUPPORTED) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Filter reassignment disabled, skippping chain reload for filter %s PID %s\n", pid->filter->name, pid->name )); + } + filter->session->last_connect_error = e; + + if (ctype==GF_PID_CONF_CONNECT) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_PLAY, pid); + gf_filter_pid_send_event_internal(pid, &evt, GF_TRUE); + + GF_FEVT_INIT(evt, GF_FEVT_STOP, pid); + gf_filter_pid_send_event_internal(pid, &evt, GF_TRUE); + + gf_filter_pid_set_eos(pid); + + if (pid->filter->freg->process_event) { + GF_FEVT_INIT(evt, GF_FEVT_CONNECT_FAIL, pid); + gf_filter_pid_send_event_internal(pid, &evt, GF_TRUE); + } + } + } else if (filter->has_out_caps) { + Bool unload_filter = GF_TRUE; + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Blacklisting %s as output from %s and retrying connections\n", filter->name, pid->filter->name)); + //try to load another filter to handle that connection + //1-blacklist this filter + gf_list_add(pid->filter->blacklisted, (void *) filter->freg); + //2-disconnect all other inputs, and post a re-init + gf_mx_p(filter->tasks_mx); + while (gf_list_count(filter->input_pids)) { + GF_FilterPidInst *a_pidinst = gf_list_pop_back(filter->input_pids); + FSESS_CHECK_THREAD(filter) + filter->freg->configure_pid(filter, (GF_FilterPid *) a_pidinst, GF_TRUE); + + gf_filter_pid_post_init_task(a_pidinst->pid->filter, a_pidinst->pid); + gf_fs_post_task(filter->session, gf_filter_pid_inst_delete_task, a_pidinst->pid->filter, a_pidinst->pid, "pid_inst_delete", a_pidinst); + + unload_filter = GF_FALSE; + } + filter->num_input_pids = 0; + filter->removed = GF_TRUE; + filter->has_pending_pids = GF_FALSE; + gf_mx_v(filter->tasks_mx); + + //do not assign session->last_connect_error since we are retrying a connection + + if (ctype==GF_PID_CONF_CONNECT) { + assert(pid->filter->out_pid_connection_pending); + safe_int_dec(&pid->filter->out_pid_connection_pending); + } + //3- post a re-init on this pid + gf_filter_pid_post_init_task(pid->filter, pid); + + if (unload_filter) { + assert(!gf_list_count(filter->input_pids)); + + if (filter->num_output_pids) { + for (i=0; inum_output_pids; i++) { + u32 j; + GF_FilterPid *opid = gf_list_get(filter->output_pids, i); + for (j=0; j< opid->num_destinations; j++) { + GF_FilterPidInst *a_pidi = gf_list_get(opid->destinations, j); + a_pidi->pid = NULL; + } + gf_list_reset(opid->destinations); + opid->num_destinations = 0; + gf_filter_pid_remove(opid); + } + } + filter->swap_pidinst_src = NULL; + if (filter->swap_pidinst_dst) { + GF_Filter *target = filter->swap_pidinst_dst->filter; + assert(target); + if (!target->detached_pid_inst) { + target->detached_pid_inst = gf_list_new(); + } + //detach props but don't delete them + if (filter->swap_pidinst_dst->props) { + filter->swap_pidinst_dst->props = NULL; + } + filter->swap_pidinst_dst->pid = NULL; + if (gf_list_find(target->detached_pid_inst, filter->swap_pidinst_dst)<0) + gf_list_add(target->detached_pid_inst, filter->swap_pidinst_dst); + } + filter->swap_pidinst_dst = NULL; + if (filter->on_setup_error) { + gf_filter_notification_failure(filter, e, GF_TRUE); + } else { + gf_filter_post_remove(filter); + } + } + return e; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to reconfigure input of sink %s, cannot rebuild graph\n", filter->name)); + if (pid->filter->freg->process_event) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_CONNECT_FAIL, pid); + pid->filter->freg->process_event(pid->filter, &evt); + } + filter->session->last_connect_error = e; + } + } else { + filter->session->last_connect_error = GF_OK; + } + + //try to run filter no matter what + if (filter->session->requires_solved_graph) + return e; + } + + //flush all pending pid init requests following the call to init + if (filter->has_pending_pids) { + filter->has_pending_pids = GF_FALSE; + while (gf_fq_count(filter->pending_pids)) { + GF_FilterPid *a_pid=gf_fq_pop(filter->pending_pids); + + gf_filter_pid_post_init_task(filter, a_pid); + } + } + + if (ctype==GF_PID_CONF_REMOVE) { + gf_mx_p(filter->tasks_mx); + gf_list_del_item(filter->input_pids, pidinst); + filter->num_input_pids = gf_list_count(filter->input_pids); + gf_mx_v(filter->tasks_mx); + + //PID instance is no longer in graph, we must remove it from pid destination to avoid propagating events + //on to-be freed pid instance. + //however we must have fan-outs (N>1 pid instance per PID), and removing the pid inst would trigger a pid destruction + //on the first gf_filter_pid_inst_delete_task executed. + //we therefore track at the PID level the number of gf_filter_pid_inst_delete_task tasks pending and + //won't destroy the PID until that number is O + gf_mx_p(pidinst->pid->filter->tasks_mx); + pidinst->pid->num_pidinst_del_pending ++; + gf_list_del_item(pidinst->pid->destinations, pidinst); + pidinst->pid->num_destinations = gf_list_count(pidinst->pid->destinations); + pidinst->filter = NULL; + gf_mx_v(pidinst->pid->filter->tasks_mx); + + //disconnected the last input, flag as removed + if (!filter->num_input_pids && !filter->sticky) { + gf_filter_reset_pending_packets(filter); + filter->removed = GF_TRUE; + } + //post a pid_delete task to also trigger removal of the filter if needed + gf_fs_post_task(filter->session, gf_filter_pid_inst_delete_task, pid->filter, pid, "pid_inst_delete", pidinst); + + return e; + } + + if (ctype==GF_PID_CONF_CONNECT) { + assert(pid->filter->out_pid_connection_pending); + if (safe_int_dec(&pid->filter->out_pid_connection_pending) == 0) { + + if (e==GF_OK) { + //postponed packets dispatched by source while setting up PID, flush through process() + //pending packets (not yet consumed but in PID buffer), start processing + if (pid->filter->postponed_packets || pid->filter->pending_packets || pid->filter->nb_caps_renegociate) { + gf_filter_post_process_task(pid->filter); + } + } + } + } + //once all pid have been (re)connected, update any internal caps + gf_filter_pid_update_caps(pid); + return e; +} + +static void gf_filter_pid_connect_task(GF_FSTask *task) +{ + GF_Filter *filter = task->filter; + GF_FilterSession *fsess = filter->session; + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s pid %s connecting to %s\n", task->pid->pid->filter->name, task->pid->pid->name, task->filter->name)); + + //filter will require a new instance, clone it + if (filter->num_input_pids && (filter->max_extra_pids <= filter->num_input_pids - 1)) { + GF_Filter *new_filter = gf_filter_clone(filter); + if (new_filter) { + filter = new_filter; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to clone filter %s\n", filter->name)); + assert(filter->in_pid_connection_pending); + safe_int_dec(&filter->in_pid_connection_pending); + return; + } + } + if (task->pid->pid) { + gf_filter_pid_configure(filter, task->pid->pid, GF_PID_CONF_CONNECT); + //once connected, any set_property before the first packet dispatch will have to trigger a reconfigure + if (!task->pid->pid->nb_pck_sent) { + task->pid->pid->request_property_map = GF_TRUE; + task->pid->pid->pid_info_changed = GF_FALSE; + } + } + + //filter may now be the clone, decrement on original filter + assert(task->filter->in_pid_connection_pending); + safe_int_dec(&task->filter->in_pid_connection_pending); + + gf_fs_cleanup_filters(fsess); + +} + +void gf_filter_pid_reconfigure_task(GF_FSTask *task) +{ + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s pid %s reconfigure to %s\n", task->pid->pid->filter->name, task->pid->pid->name, task->filter->name)); + + if (task->pid->pid) { + gf_filter_pid_configure(task->filter, task->pid->pid, GF_PID_CONF_RECONFIG); + //once connected, any set_property before the first packet dispatch will have to trigger a reconfigure + if (!task->pid->pid->nb_pck_sent) { + task->pid->pid->request_property_map = GF_TRUE; + task->pid->pid->pid_info_changed = GF_FALSE; + } + } +} + +void gf_filter_pid_disconnect_task(GF_FSTask *task) +{ + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s pid %s disconnect from %s\n", task->pid->pid->filter->name, task->pid->pid->name, task->filter->name)); + gf_filter_pid_configure(task->filter, task->pid->pid, GF_PID_CONF_REMOVE); + + //if the filter has no more connected ins and outs, remove it + if (task->filter->removed && !gf_list_count(task->filter->output_pids) && !gf_list_count(task->filter->input_pids)) { + Bool direct_mode = task->filter->session->direct_mode; + gf_filter_post_remove(task->filter); + if (direct_mode) task->filter = NULL; + } +} + +void gf_filter_pid_detach_task(GF_FSTask *task) +{ + u32 i, count; + GF_Filter *filter = task->filter; + GF_FilterPid *pid = task->pid->pid; + GF_FilterPidInst *pidinst=NULL; + GF_Filter *new_chain_input = task->udta; + + //we may have concurrent reset (due to play/stop/seek) and caps renegotiation + //wait for the pid to be reset before detaching + if (pid->filter->stream_reset_pending) { + TASK_REQUEUE(task) + return; + } + if (new_chain_input->in_pid_connection_pending) { + TASK_REQUEUE(task) + return; + } + + assert(filter->freg->configure_pid); + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s pid %s detach from %s\n", task->pid->pid->filter->name, task->pid->pid->name, task->filter->name)); + + assert(pid->filter->detach_pid_tasks_pending); + safe_int_dec(&pid->filter->detach_pid_tasks_pending); + count = pid->num_destinations; + for (i=0; idestinations, i); + if (pidinst->filter==filter) { + break; + } + pidinst=NULL; + } + + //first connection of this PID to this filter + if (!pidinst) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Trying to detach PID %s not found in filter %s inputs\n", pid->name, filter->name)); + assert(!new_chain_input->swap_pidinst_dst); + assert(!new_chain_input->swap_pidinst_src); + new_chain_input->swap_needs_init = GF_FALSE; + return; + } + + //detach props + if (pidinst->props) { + assert(pidinst->props->reference_count); + if (safe_int_dec(& pidinst->props->reference_count) == 0) { + //see \ref gf_filter_pid_merge_properties_internal for mutex + gf_mx_p(pidinst->pid->filter->tasks_mx); + gf_list_del_item(pidinst->pid->properties, pidinst->props); + gf_mx_v(pidinst->pid->filter->tasks_mx); + gf_props_del(pidinst->props); + } + } + pidinst->props = NULL; + + gf_mx_p(filter->tasks_mx); + //detach pid - remove all packets in our pid instance and alos update filter pending_packets + count = gf_fq_count(pidinst->packets); + assert(!count || (count >= filter->pending_packets)); + safe_int_sub(&filter->pending_packets, (s32) count); + gf_filter_pid_inst_reset(pidinst); + pidinst->pid = NULL; + gf_list_del_item(pid->destinations, pidinst); + pid->num_destinations = gf_list_count(pid->destinations); + gf_list_del_item(filter->input_pids, pidinst); + filter->num_input_pids = gf_list_count(filter->input_pids); + gf_mx_v(filter->tasks_mx); + + if (!filter->detached_pid_inst) { + filter->detached_pid_inst = gf_list_new(); + } + if (gf_list_find(filter->detached_pid_inst, pidinst)<0) + gf_list_add(filter->detached_pid_inst, pidinst); + + //we are done, reset filter swap instance so that connection can take place + if (new_chain_input->swap_needs_init) { + new_chain_input->swap_pidinst_dst = NULL; + new_chain_input->swap_pidinst_src = NULL; + new_chain_input->swap_needs_init = GF_FALSE; + } + assert(new_chain_input->detach_pid_tasks_pending); + safe_int_dec(&new_chain_input->detach_pid_tasks_pending); +} + +GF_EXPORT +void gf_filter_pid_set_name(GF_FilterPid *pid, const char *name) +{ + if (PID_IS_INPUT(pid)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Attempt to assign name %s to input PID %s in filter %s - ignoring\n", name, pid->pid->name, pid->pid->filter->name)); + } else if (name) { + if (pid->name && !strcmp(pid->name, name)) return; + if (pid->name) gf_free(pid->name); + pid->name = gf_strdup(name); + } +} + +GF_EXPORT +const char *gf_filter_pid_get_name(GF_FilterPid *pid) +{ + return pid->pid->name; +} + +GF_EXPORT +const char *gf_filter_pid_get_filter_name(GF_FilterPid *pid) +{ + if (PID_IS_INPUT(pid)) { + return pid->pid->filter->name; + } + return pid->filter->name; +} + +GF_EXPORT +const char *gf_filter_pid_orig_src_args(GF_FilterPid *pid) +{ + u32 i; + const char *args; + //move to true source pid + pid = pid->pid; + args = pid->filter->src_args; + if (args && strstr(args, "src")) return args; + if (!pid->filter->num_input_pids) return args; + for (i=0; ifilter->num_input_pids; i++) { + GF_FilterPidInst *pidi = gf_list_get(pid->filter->input_pids, i); + const char *arg_src = gf_filter_pid_orig_src_args(pidi->pid); + if (arg_src) return arg_src; + } + return args; +} + +GF_EXPORT +const char *gf_filter_pid_get_source_filter_name(GF_FilterPid *pid) +{ + GF_Filter *filter = pid->pid->filter; + while (filter && filter->num_input_pids) { + GF_FilterPidInst *pidi = gf_list_get(filter->input_pids, 0); + filter = pidi->pid->filter; + } + if (!filter) return NULL; + return filter->name ? filter->name : filter->freg->name; +} + +GF_EXPORT +Bool gf_filter_pid_get_buffer_occupancy(GF_FilterPid *pid, u32 *max_slots, u32 *nb_pck, u32 *max_duration, u32 *duration) +{ + if (max_slots) *max_slots = pid->pid->max_buffer_unit; + if (max_duration) *max_duration = (u32) pid->pid->max_buffer_time; + + if (pid->filter->session->in_final_flush) { + if (duration) *duration = (u32) pid->pid->max_buffer_time; + if (nb_pck) *nb_pck = pid->pid->nb_buffer_unit; + return GF_FALSE; + } + if (nb_pck) *nb_pck = pid->pid->nb_buffer_unit; + if (duration) *duration = (u32) pid->pid->buffer_duration; + return GF_TRUE; +} + +GF_EXPORT +void gf_filter_pid_set_udta(GF_FilterPid *pid, void *udta) +{ + if (PID_IS_INPUT(pid)) { + ((GF_FilterPidInst *)pid)->udta = udta; + } else { + pid->udta = udta; + } +} + +GF_EXPORT +void *gf_filter_pid_get_udta(GF_FilterPid *pid) +{ + if (PID_IS_INPUT(pid)) { + return ((GF_FilterPidInst *)pid)->udta; + } else { + return pid->udta; + } +} + +static Bool filter_pid_check_fragment(GF_FilterPid *src_pid, char *frag_name, Bool *pid_excluded, Bool *needs_resolve, Bool *prop_not_found, char prop_dump_buffer[GF_PROP_DUMP_ARG_SIZE]) +{ + char *psep; + u32 comp_type=0; + Bool is_neg = GF_FALSE; + const GF_PropertyEntry *pent; + const GF_PropertyEntry *pent_val=NULL; + *needs_resolve = GF_FALSE; + *prop_not_found = GF_FALSE; + + if (frag_name[0] == src_pid->filter->session->sep_neg) { + frag_name++; + is_neg = GF_TRUE; + } + //special case for stream types filters + pent = gf_filter_pid_get_property_entry(src_pid, GF_PROP_PID_STREAM_TYPE); + if (pent) { + u32 matched=0; + u32 type=0; + if (!strnicmp(frag_name, "audio", 5)) { + matched=5; + type=GF_STREAM_AUDIO; + } else if (!strnicmp(frag_name, "video", 5)) { + matched=5; + type=GF_STREAM_VISUAL; + } else if (!strnicmp(frag_name, "scene", 5)) { + matched=5; + type=GF_STREAM_SCENE; + } else if (!strnicmp(frag_name, "font", 4)) { + matched=4; + type=GF_STREAM_FONT; + } else if (!strnicmp(frag_name, "text", 4)) { + matched=4; + type=GF_STREAM_TEXT; + } + if (matched && (type != pent->prop.value.uint)) { + //special case: if we request a non-file stream but the pid is a file, we will need a demux to + //move from file to A/V/... streams, so we accept any #MEDIA from file streams + if (pent->prop.value.uint == GF_STREAM_FILE) { + *prop_not_found = GF_TRUE; + return GF_TRUE; + } + *pid_excluded = GF_TRUE; + return GF_FALSE; + } + + if (matched) { + u32 idx=0; + u32 k, count_pid; + if (strlen(frag_name)==matched) return GF_TRUE; + idx = atoi(frag_name+matched); + count_pid = src_pid->filter->num_output_pids; + for (k=0; kfilter->output_pids, k); + pent = gf_filter_pid_get_property_entry(src_pid, GF_PROP_PID_STREAM_TYPE); + if (pent && pent->prop.value.uint==type) { + idx--; + if (!idx) { + if (p==src_pid) return GF_TRUE; + break; + } + } + } + *pid_excluded = GF_TRUE; + return GF_FALSE; + } + } + //special case for codec type filters + if (!strcmp(frag_name, "raw")) { + pent = gf_filter_pid_get_property_entry(src_pid, GF_PROP_PID_CODECID); + if (pent) { + Bool is_eq = (pent->prop.value.uint==GF_CODECID_RAW) ? GF_TRUE : GF_FALSE; + if (is_neg) is_eq = !is_eq; + if (is_eq) return GF_TRUE; + *pid_excluded = GF_TRUE; + return GF_FALSE; + } + //not codec ID set for pid, assume match + return GF_TRUE; + } + + //generic property addressing code(or builtin name)=val + psep = strchr(frag_name, src_pid->filter->session->sep_name); + if (!psep) { + psep = strchr(frag_name, '-'); + if (psep) comp_type = 1; + else { + psep = strchr(frag_name, '+'); + if (psep) comp_type = 2; + } + } + + if (!psep) { + *prop_not_found = GF_TRUE; + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("PID addressing %s not recognized, ignoring and assuming match\n", frag_name )); + return GF_TRUE; + } + + Bool is_equal = GF_FALSE; + Bool use_not_equal = GF_FALSE; + GF_PropertyValue prop_val; + u32 p4cc = 0; + char c=psep[0]; + psep[0] = 0; + pent=NULL; + + //check for built-in property + p4cc = gf_props_get_id(frag_name); + if (!p4cc && !strcmp(frag_name, "PID") ) + p4cc = GF_PROP_PID_ID; + + if (!p4cc && (strlen(frag_name)==4)) + p4cc = GF_4CC(frag_name[0], frag_name[1], frag_name[2], frag_name[3]); + + if (p4cc) pent = gf_filter_pid_get_property_entry(src_pid, p4cc); + //not a built-in property, find prop by name + if (!pent) { + pent = gf_filter_pid_get_property_entry_str(src_pid, frag_name); + } + + psep[0] = c; + + //if the property is not found, we accept the connection + if (!pent) { + *prop_not_found = GF_TRUE; + return GF_TRUE; + } + //check for dynamic assignment + if ( (psep[0]==src_pid->filter->session->sep_name) && ((psep[1]=='*') || (psep[1]=='\0') ) ) { + *needs_resolve = GF_TRUE; + gf_props_dump_val(&pent->prop, prop_dump_buffer, GF_FALSE, NULL); + return GF_FALSE; + } + + //check for negation + if ( (psep[0]==src_pid->filter->session->sep_name) && (psep[1]==src_pid->filter->session->sep_neg) ) { + psep++; + use_not_equal = GF_TRUE; + } + //parse the property, based on its property type + if (pent->p4cc==GF_PROP_PID_CODECID) { + prop_val.type = GF_PROP_UINT; + prop_val.value.uint = gf_codec_parse(psep+1); + } else { + u32 val_is_prop = gf_props_get_id(psep+1); + if (val_is_prop) { + pent_val = gf_filter_pid_get_property_entry(src_pid, val_is_prop); + if (pent_val) { + prop_val = pent_val->prop; + } else { + *pid_excluded = GF_TRUE; + return GF_FALSE; + } + } else { + prop_val = gf_props_parse_value(pent->prop.type, frag_name, psep+1, NULL, src_pid->filter->session->sep_list); + } + } + if (!comp_type) { + is_equal = gf_props_equal(&pent->prop, &prop_val); + if (use_not_equal) is_equal = !is_equal; + } else { + switch (prop_val.type) { + case GF_PROP_SINT: + if (pent->prop.value.sintprop.value.uintprop.value.longsintprop.value.longuintprop.value.fnumberprop.value.numberprop.value.frac.num * prop_val.value.frac.den < pent->prop.value.frac.den * prop_val.value.frac.num) is_equal = GF_TRUE; + if (comp_type == 2) is_equal = !is_equal; + break; + case GF_PROP_FRACTION64: + if (pent->prop.value.lfrac.num * prop_val.value.lfrac.den < pent->prop.value.lfrac.den * prop_val.value.lfrac.num) is_equal = GF_TRUE; + if (comp_type == 2) is_equal = !is_equal; + break; + default: + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("PID addressing uses \'%s\' comparison on property %s which is not a number, defaulting to equal=true\n", (comp_type==1) ? "less than" : "more than", gf_props_4cc_get_name(p4cc) )); + is_equal = GF_TRUE; + break; + } + } + if (!pent_val) + gf_props_reset_single(&prop_val); + + if (!is_equal) *pid_excluded = GF_TRUE; + + return is_equal; +} + +static Bool filter_source_id_match(GF_FilterPid *src_pid, const char *id, GF_Filter *dst_filter, Bool *pid_excluded, Bool *needs_clone) +{ + const char *source_ids; + char *resolved_source_ids = NULL; + Bool result = GF_FALSE; + Bool first_pass = GF_TRUE; + Bool has_default_match; + Bool is_pid_excluded; + *pid_excluded = GF_FALSE; + if (!dst_filter->source_ids) + return GF_TRUE; + if (!id) + return GF_FALSE; + +sourceid_reassign: + source_ids = resolved_source_ids ? resolved_source_ids : dst_filter->source_ids; + if (!first_pass) { + assert(dst_filter->dynamic_source_ids); + source_ids = dst_filter->dynamic_source_ids; + } + has_default_match = GF_FALSE; + is_pid_excluded = GF_FALSE; + + while (source_ids) { + Bool all_matched = GF_TRUE; + Bool all_frags_not_found = GF_TRUE; + u32 len, sublen; + Bool last=GF_FALSE; + char *sep = strchr(source_ids, src_pid->filter->session->sep_list); + char *frag_name, *frag_clone; + if (sep) { + len = (u32) (sep - source_ids); + } else { + len = (u32) strlen(source_ids); + last=GF_TRUE; + } + + frag_name = strchr(source_ids, src_pid->filter->session->sep_frag); + if (frag_name > source_ids + len) frag_name = NULL; + sublen = frag_name ? (u32) (frag_name - source_ids) : len; + //skip frag char + if (frag_name) frag_name++; + + //any ID, always match + if (source_ids[0]=='*') { } + // id does not match + else if (strncmp(id, source_ids, sublen)) { + source_ids += len+1; + if (last) break; + continue; + } + //no fragment or fragment match pid name, OK + if (!frag_name || !strcmp(src_pid->name, frag_name)) { + result = GF_TRUE; + break; + } + frag_clone = NULL; + if (!last) { + frag_clone = gf_strdup(frag_name); + char *nsep = strchr(frag_clone, src_pid->filter->session->sep_list); + assert(nsep); + nsep[0] = 0; + frag_name = frag_clone; + } + + //for all listed fragment extensions + while (frag_name && all_matched) { + char prop_dump_buffer[GF_PROP_DUMP_ARG_SIZE]; + Bool needs_resolve = GF_FALSE; + Bool prop_not_found = GF_FALSE; + Bool local_pid_excluded = GF_FALSE; + char *next_frag = strchr(frag_name, src_pid->filter->session->sep_frag); + if (next_frag) next_frag[0] = 0; + + if (! filter_pid_check_fragment(src_pid, frag_name, &local_pid_excluded, &needs_resolve, &prop_not_found, prop_dump_buffer)) { + if (needs_resolve) { + if (first_pass) { + char *sid = resolved_source_ids ? resolved_source_ids : dst_filter->source_ids; + char *frag_sep = strchr(frag_name, dst_filter->session->sep_name); + assert(frag_sep); + if (next_frag) next_frag[0] = src_pid->filter->session->sep_frag; + + char *new_source_ids = gf_malloc(sizeof(char) * (strlen(sid) + strlen(prop_dump_buffer)+1)); + u32 clen = (u32) (1+frag_sep - sid); + strncpy(new_source_ids, sid, clen); + new_source_ids[clen]=0; + strcat(new_source_ids, prop_dump_buffer); + if (next_frag) strcat(new_source_ids, next_frag); + + if (resolved_source_ids) gf_free(resolved_source_ids); + resolved_source_ids = new_source_ids; + if (frag_clone) gf_free(frag_clone); + goto sourceid_reassign; + } + } + else { + all_matched = GF_FALSE; + //remember we failed because PID was excluded by sourceID + if (local_pid_excluded) + is_pid_excluded = GF_TRUE; + } + } else { + //remember we succeed because PID has no matching property + if (!prop_not_found) + all_frags_not_found = GF_FALSE; + } + + if (!next_frag) break; + + next_frag[0] = src_pid->filter->session->sep_frag; + frag_name = next_frag+1; + } + if (frag_clone) gf_free(frag_clone); + if (all_matched) { + //exact match on one or more properties, don't look any further + if (!all_frags_not_found) { + result = GF_TRUE; + break; + } + //remember we had a default match, but don't set result yet and parse other SIDs + has_default_match = GF_TRUE; + } + *needs_clone = GF_FALSE; + if (!sep) break; + source_ids = sep+1; + } + + if (!result) { + //we had a default match and pid was not excluded by any SIDs, consider we pass + if (has_default_match && !is_pid_excluded) + result = GF_TRUE; + } + + if (!result) { + if (resolved_source_ids) gf_free(resolved_source_ids); + if (dst_filter->dynamic_source_ids && first_pass) { + first_pass = GF_FALSE; + goto sourceid_reassign; + } + *pid_excluded = is_pid_excluded; + return GF_FALSE; + } + if (resolved_source_ids) { + if (!dst_filter->dynamic_source_ids) { + dst_filter->dynamic_source_ids = dst_filter->source_ids; + dst_filter->source_ids = resolved_source_ids; + } else { + gf_free(dst_filter->source_ids); + dst_filter->source_ids = resolved_source_ids; + } + } + if (!first_pass) { + *needs_clone = GF_TRUE; + } + return GF_TRUE; +} + +GF_EXPORT +Bool gf_filter_in_parent_chain(GF_Filter *parent, GF_Filter *filter) +{ + u32 i, count; + if (parent == filter) return GF_TRUE; + //browse all parent PIDs + count = parent->num_input_pids; + if (!count) return GF_FALSE; + for (i=0; iinput_pids, i); + if (gf_filter_in_parent_chain(pid->pid->filter, filter)) return GF_TRUE; + } + return GF_FALSE; +} + +static Bool cap_code_match(u32 c1, u32 c2) +{ + if (c1==c2) return GF_TRUE; + /*we consider GF_PROP_PID_FILE_EXT and GF_PROP_PID_MIME are the same so that we check + if we have at least one match of file ext or mime in a given bundle*/ + if ((c1==GF_PROP_PID_FILE_EXT) && (c2==GF_PROP_PID_MIME)) return GF_TRUE; + if ((c1==GF_PROP_PID_MIME) && (c2==GF_PROP_PID_FILE_EXT)) return GF_TRUE; + return GF_FALSE; +} + + +Bool gf_filter_pid_caps_match(GF_FilterPid *src_pid_or_ipid, const GF_FilterRegister *freg, GF_Filter *filter_inst, u8 *priority, u32 *dst_bundle_idx, GF_Filter *dst_filter, s32 for_bundle_idx) +{ + u32 i=0; + u32 cur_bundle_start = 0; + u32 cap_bundle_idx = 0; + u32 nb_subcaps=0; + Bool skip_explicit_load = GF_FALSE; + Bool all_caps_matched = GF_TRUE; + Bool mime_matched = GF_FALSE; + Bool has_file_ext_cap = GF_FALSE; + Bool ext_not_trusted; + GF_FilterPid *src_pid = src_pid_or_ipid->pid; + const GF_FilterCapability *in_caps; + u32 nb_in_caps; + + if (!freg) { + assert(dst_filter); + freg = dst_filter->freg; + skip_explicit_load = GF_TRUE; + } + + in_caps = freg->caps; + nb_in_caps = freg->nb_caps; + if (filter_inst && (filter_inst->freg==freg)) { + skip_explicit_load = GF_TRUE; + if (filter_inst->forced_caps) { + in_caps = filter_inst->forced_caps; + nb_in_caps = filter_inst->nb_forced_caps; + } + } + ext_not_trusted = src_pid->ext_not_trusted; + if (ext_not_trusted) { + Bool has_mime_cap = GF_FALSE; + + for (i=0; iflags & GF_CAPFLAG_INPUT) ) continue; + if (cap->code == GF_PROP_PID_MIME) { + has_mime_cap = GF_TRUE; + break; + } + } + if (!has_mime_cap) ext_not_trusted = GF_FALSE; + } + + if (filter_inst && filter_inst->encoder_stream_type) { + const GF_PropertyValue *pid_st = gf_filter_pid_get_property_first(src_pid_or_ipid, GF_PROP_PID_STREAM_TYPE); + if (pid_st && (pid_st->value.uint != filter_inst->encoder_stream_type)) + return GF_FALSE; + } + + if (priority) + (*priority) = freg->priority; + + if (dst_bundle_idx) + (*dst_bundle_idx) = 0; + + //filters with no explicit input cap accept anything for now, this should be refined ... + if (!in_caps) + return GF_TRUE; + + //check all input caps of dst filter + for (i=0; iflags & GF_CAPFLAG_IN_BUNDLE) ) { + if (has_file_ext_cap && ext_not_trusted && !mime_matched) + all_caps_matched = GF_FALSE; + + if (all_caps_matched) { + if (dst_bundle_idx) + (*dst_bundle_idx) = cap_bundle_idx; + return GF_TRUE; + } + all_caps_matched = GF_TRUE; + mime_matched = GF_FALSE; + has_file_ext_cap = GF_FALSE; + nb_subcaps=0; + cur_bundle_start = i; + cap_bundle_idx++; + if ((for_bundle_idx>=0) && (cap_bundle_idx > (u32) for_bundle_idx)) { + break; + } + continue; + } + if ((for_bundle_idx>=0) && (cap_bundle_idx < (u32) for_bundle_idx)) { + all_caps_matched = 0; + continue; + } + + //not an input cap + if (! (cap->flags & GF_CAPFLAG_INPUT) ) { + if (!skip_explicit_load && (cap->flags & GF_CAPFLAG_LOADED_FILTER) ) { + all_caps_matched = 0; + } + continue; + } + + nb_subcaps++; + //no match for this cap, go on until new one or end + if (!all_caps_matched) continue; + + if (cap->code) { + pid_cap = gf_filter_pid_get_property_first(src_pid_or_ipid, cap->code); + + //special case for file ext: the pid will likely have only one file extension defined, and the output as well + //we browse all caps of the filter owning the pid, looking for the original file extension property + if (pid_cap && (cap->code==GF_PROP_PID_FILE_EXT) ) { + u32 j; + for (j=0; jfilter->freg->nb_caps; j++) { + const GF_FilterCapability *out_cap = &src_pid->filter->freg->caps[j]; + if (!(out_cap->flags & GF_CAPFLAG_OUTPUT)) continue; + if (out_cap->code != GF_PROP_PID_FILE_EXT) continue; + if (! gf_props_equal(pid_cap, &out_cap->val)) continue; + pid_cap = &out_cap->val; + break; + } + } + /*if PID prop for this cap is not found and the cap is MIME (resp. file ext), fetch file_ext (resp MIME) + so that we check if we have at least one match of file ext or mime in a given bundle*/ + if (!pid_cap) { + if (cap->code==GF_PROP_PID_FILE_EXT) + pid_cap = gf_filter_pid_get_property_first(src_pid_or_ipid, GF_PROP_PID_MIME); + else if (cap->code==GF_PROP_PID_MIME) + pid_cap = gf_filter_pid_get_property_first(src_pid_or_ipid, GF_PROP_PID_FILE_EXT); + } + } + + //optional cap + if (cap->flags & GF_CAPFLAG_OPTIONAL) continue; + + //try by name + if (!pid_cap && cap->name) pid_cap = gf_filter_pid_get_property_str_first(src_pid_or_ipid, cap->name); + + if (ext_not_trusted && (cap->code==GF_PROP_PID_FILE_EXT)) { + has_file_ext_cap = GF_TRUE; + continue; + } + + + //we found a property of that type and it is equal + if (pid_cap) { + u32 j; + Bool prop_excluded = GF_FALSE; + Bool prop_equal = GF_FALSE; + + //this could be optimized by not checking several times the same cap + for (j=0; jcur_bundle_start) && ! (a_cap->flags & GF_CAPFLAG_IN_BUNDLE) ) { + break; + } + //not an input cap + if (! (a_cap->flags & GF_CAPFLAG_INPUT) ) continue; + //not a static and not in bundle + if (! (a_cap->flags & GF_CAPFLAG_STATIC)) { + if (jcode) { + if (!cap_code_match(cap->code, a_cap->code) ) + continue; + } else if (!cap->name || !a_cap->name || strcmp(cap->name, a_cap->name)) { + continue; + } + if (!skip_explicit_load && (a_cap->flags & GF_CAPFLAG_LOADED_FILTER) ) { + if (!dst_filter || (dst_filter != src_pid->filter->dst_filter)) { + prop_equal = GF_FALSE; + break; + } + if (dst_filter->freg != freg) { + prop_equal = GF_FALSE; + break; + } + } + + if (!prop_equal) { + prop_equal = gf_props_equal(pid_cap, &a_cap->val); + //excluded cap: if value match, don't match this cap at all + if (a_cap->flags & GF_CAPFLAG_EXCLUDED) { + if (prop_equal) { + prop_equal = GF_FALSE; + prop_excluded = GF_FALSE; + break; + } + prop_excluded = GF_TRUE; + } + if (prop_equal) break; + } + } + if (!prop_equal && !prop_excluded) { + all_caps_matched=GF_FALSE; + } else if (priority && cap->priority) { + (*priority) = cap->priority; + } + if (ext_not_trusted && prop_equal && (cap->code==GF_PROP_PID_MIME)) + mime_matched = GF_TRUE; + } + else if (! (cap->flags & (GF_CAPFLAG_EXCLUDED | GF_CAPFLAG_OPTIONAL) ) ) { + all_caps_matched=GF_FALSE; + } + } + + if (has_file_ext_cap && ext_not_trusted && !mime_matched) + all_caps_matched = GF_FALSE; + + if (nb_subcaps && all_caps_matched) { + if (dst_bundle_idx) + (*dst_bundle_idx) = cap_bundle_idx; + return GF_TRUE; + } + + return GF_FALSE; +} + +u32 gf_filter_caps_bundle_count(const GF_FilterCapability *caps, u32 nb_caps) +{ + u32 i, nb_bundles = 0, num_in_bundle=0; + for (i=0; iflags & GF_CAPFLAG_IN_BUNDLE)) { + if (num_in_bundle) nb_bundles++; + num_in_bundle=0; + continue; + } + num_in_bundle++; + } + if (num_in_bundle) nb_bundles++; + return nb_bundles; +} + +static Bool gf_filter_has_in_out_caps(const GF_FilterCapability *caps, u32 nb_caps, Bool check_in) +{ + u32 i; + //check all input caps of dst filter, count bundles + for (i=0; iflags & GF_CAPFLAG_INPUT) { + return GF_TRUE; + } + } else { + if (a_cap->flags & GF_CAPFLAG_OUTPUT) { + return GF_TRUE; + } + } + } + return GF_FALSE; + +} +Bool gf_filter_has_out_caps(const GF_FilterCapability *caps, u32 nb_caps) +{ + return gf_filter_has_in_out_caps(caps, nb_caps, GF_FALSE); +} +Bool gf_filter_has_in_caps(const GF_FilterCapability *caps, u32 nb_caps) +{ + return gf_filter_has_in_out_caps(caps, nb_caps, GF_TRUE); +} + +u32 gf_filter_caps_to_caps_match(const GF_FilterRegister *src, u32 src_bundle_idx, const GF_FilterRegister *dst_reg, GF_Filter *dst_filter, u32 *dst_bundle_idx, s32 for_dst_bundle, u32 *loaded_filter_flags, GF_CapsBundleStore *capstore) +{ + u32 i=0; + u32 cur_bundle_start = 0; + u32 cur_bundle_idx = 0; + u32 nb_matched=0; + u32 nb_out_caps=0; + u32 nb_in_bundles=0; + u32 bundle_score = 0; + u32 *bundles_in_ok = NULL; + u32 *bundles_cap_found = NULL; + u32 *bundles_in_scores = NULL; + //initialize caps matched to true for first cap bundle + Bool all_caps_matched = GF_TRUE; + const GF_FilterCapability *dst_caps = dst_reg->caps; + u32 nb_dst_caps = dst_reg->nb_caps; + + if (dst_filter && dst_filter->freg==dst_reg && dst_filter->forced_caps) { + dst_caps = dst_filter->forced_caps; + nb_dst_caps = dst_filter->nb_forced_caps; + } + + //check all input caps of dst filter, count bundles + if (! gf_filter_has_out_caps(src->caps, src->nb_caps)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s has no output caps, cannot match filter %s inputs\n", src->name, dst_reg->name)); + return 0; + } + + //check all input caps of dst filter, count bundles + nb_in_bundles = gf_filter_caps_bundle_count(dst_caps, nb_dst_caps); + if (!nb_in_bundles) { + if (dst_reg->configure_pid) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s has no caps but pid configure possible, assuming possible connection\n", dst_reg->name)); + return 1; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s has no caps and no pid configure, no possible connection\n", dst_reg->name)); + return 0; + } + if (capstore->nb_allocs < nb_in_bundles) { + capstore->nb_allocs = nb_in_bundles; + capstore->bundles_in_ok = gf_realloc(capstore->bundles_in_ok, sizeof(u32) * nb_in_bundles); + capstore->bundles_cap_found = gf_realloc(capstore->bundles_cap_found, sizeof(u32) * nb_in_bundles); + capstore->bundles_in_scores = gf_realloc(capstore->bundles_in_scores, sizeof(u32) * nb_in_bundles); + } + bundles_in_ok = capstore->bundles_in_ok; + bundles_cap_found = capstore->bundles_cap_found; + bundles_in_scores = capstore->bundles_in_scores; + + for (i=0; inb_caps; i++) { + u32 j, k; + Bool already_tested = GF_FALSE; + const GF_FilterCapability *out_cap = &src->caps[i]; + + if (!(out_cap->flags & GF_CAPFLAG_IN_BUNDLE) ) { + all_caps_matched = GF_TRUE; + cur_bundle_start = i+1; + cur_bundle_idx++; + if (src_bundle_idx < cur_bundle_idx) + break; + + continue; + } + + //not our selected output and not static cap + if ((src_bundle_idx != cur_bundle_idx) && ! (out_cap->flags & GF_CAPFLAG_STATIC) ) { + continue; + } + + //not an output cap + if (!(out_cap->flags & GF_CAPFLAG_OUTPUT) ) continue; + + //no match possible for this cap, wait until next cap start + if (!all_caps_matched) continue; + + //check we didn't test a cap with same name/code before us + for (k=cur_bundle_start; kcaps[k]; + if (! (an_out_cap->flags & GF_CAPFLAG_IN_BUNDLE) ) { + break; + } + if (! (an_out_cap->flags & GF_CAPFLAG_OUTPUT) ) { + continue; + } + if (out_cap->code && (out_cap->code == an_out_cap->code) ) { + already_tested = GF_TRUE; + break; + } + if (out_cap->name && an_out_cap->name && !strcmp(out_cap->name, an_out_cap->name)) { + already_tested = GF_TRUE; + break; + } + } + if (already_tested) { + continue; + } + nb_out_caps++; + + //set cap as OK in all bundles + for (k=0; knb_caps; k++) { + u32 cur_dst_bundle=0; + Bool static_matched = GF_FALSE; + u32 nb_caps_tested = 0; + u32 cap_loaded_filter_only = 0; + Bool matched=GF_FALSE; + Bool exclude=GF_FALSE; + Bool prop_found=GF_FALSE; + const GF_FilterCapability *an_out_cap = &src->caps[k]; + if (! (an_out_cap->flags & GF_CAPFLAG_IN_BUNDLE) ) { + break; + } + if (! (an_out_cap->flags & GF_CAPFLAG_OUTPUT) ) { + continue; + } + if (out_cap->code && !cap_code_match(out_cap->code, an_out_cap->code) ) + continue; + + if (out_cap->name && (!an_out_cap->name || strcmp(out_cap->name, an_out_cap->name))) + continue; + + //not our selected output and not static cap + if ((src_bundle_idx != cur_bundle_idx) && ! (an_out_cap->flags & GF_CAPFLAG_STATIC) ) { + continue; + } + + nb_matched = 0; + //check all input caps of dst filter, count ones that are matched + for (j=0; jflags & GF_CAPFLAG_IN_BUNDLE)) { + if (!matched && !nb_caps_tested && (out_cap->flags & GF_CAPFLAG_EXCLUDED)) + matched = GF_TRUE; + + //we found a prop, excluded but with != value hence acceptable, default matching to true + if (!matched && prop_found) matched = GF_TRUE; + + //match, flag this bundle as ok + if (matched) { + if (!bundles_cap_found[cur_dst_bundle]) + bundles_cap_found[cur_dst_bundle] = cap_loaded_filter_only ? 2 : 1; + + nb_matched++; + } + + matched = static_matched ? GF_TRUE : GF_FALSE; + exclude = GF_FALSE; + prop_found = GF_FALSE; + nb_caps_tested = 0; + cur_dst_bundle++; + if ((for_dst_bundle>=0) && (cur_dst_bundle > (u32) for_dst_bundle)) + break; + + continue; + } + //not an input cap + if (!(in_cap->flags & GF_CAPFLAG_INPUT) ) + continue; + + //optional cap, ignore + if (in_cap->flags & GF_CAPFLAG_OPTIONAL) + continue; + + if ((for_dst_bundle>=0) && (cur_dst_bundle < (u32)for_dst_bundle) && !(in_cap->flags & GF_CAPFLAG_STATIC)) + continue; + + //prop was excluded, cannot match in bundle + if (exclude) continue; + //prop was matched, no need to check other caps in the current bundle + if (matched) continue; + + if (out_cap->code && !cap_code_match(out_cap->code, in_cap->code) ) + continue; + + if (out_cap->name && (!in_cap->name || strcmp(out_cap->name, in_cap->name))) + continue; + + nb_caps_tested++; + //we found a property of that type , check if equal equal + prop_equal = gf_props_equal(&in_cap->val, &an_out_cap->val); + if ((in_cap->flags & GF_CAPFLAG_EXCLUDED) && !(an_out_cap->flags & GF_CAPFLAG_EXCLUDED) ) { + //prop type matched, output includes it and input excludes it: no match, don't look any further + if (prop_equal) { + matched = GF_FALSE; + exclude = GF_TRUE; + prop_found = GF_FALSE; + } else { + //remember we found a prop of same type but excluded value + // we will match unless we match an excluded value + prop_found = GF_TRUE; + } + } else if (!(in_cap->flags & GF_CAPFLAG_EXCLUDED) && (an_out_cap->flags & GF_CAPFLAG_EXCLUDED) ) { + //prop type matched, input includes it and output excludes it: no match, don't look any further + if (prop_equal) { + matched = GF_FALSE; + exclude = GF_TRUE; + prop_found = GF_FALSE; + } else { + //remember we found a prop of same type but excluded value + //we will match unless we match an excluded value + prop_found = GF_TRUE; + } + } else if (prop_equal) { + matched = GF_TRUE; +// if (an_out_cap->flags & GF_CAPFLAG_STATIC) +// static_matched = GF_TRUE; + } else if ((in_cap->flags & GF_CAPFLAG_EXCLUDED) && (an_out_cap->flags & GF_CAPFLAG_EXCLUDED) ) { + //prop type matched, input excludes it and output excludes it and no match, remmeber we found the prop type + prop_found = GF_TRUE; + } + + if (prop_found && (in_cap->flags & GF_CAPFLAG_LOADED_FILTER)) + cap_loaded_filter_only = 1; + } + if (nb_caps_tested) { + //we found a prop, excluded but with != value hence acceptable, default matching to true + if (!matched && prop_found) matched = GF_TRUE; + //not match, flag this bundle as not ok + if (matched) { + if (!bundles_cap_found[cur_dst_bundle]) + bundles_cap_found[cur_dst_bundle] = cap_loaded_filter_only ? 2 : 1; + + nb_matched++; + } + } else if (!nb_dst_caps) { + if (!bundles_cap_found[cur_dst_bundle]) + bundles_cap_found[cur_dst_bundle] = cap_loaded_filter_only ? 2 : 1; + + nb_matched++; + } else if (!nb_matched && !prop_found && (an_out_cap->flags & GF_CAPFLAG_EXCLUDED) ) { + if (!bundles_cap_found[cur_dst_bundle]) + bundles_cap_found[cur_dst_bundle] = cap_loaded_filter_only ? 2 : 1; + + nb_matched++; + } + } + //merge bundle cap + nb_matched=0; + for (k=0; kflags & GF_CAPFLAG_LOADED_FILTER) + bundles_in_ok[k] |= 1<<2; + } + } + //not matched and not excluded, skip until next bundle + if (!nb_matched && !(out_cap->flags & GF_CAPFLAG_EXCLUDED)) { + all_caps_matched = GF_FALSE; + } + } + + //get bundle with highest score + bundle_score = 0; + nb_matched = 0; + + for (i=0; i>1); + } + } + if ((for_dst_bundle>=0) && (for_dst_bundle==i)) { + *dst_bundle_idx = i; + if (loaded_filter_flags) { + *loaded_filter_flags = (bundles_in_ok[i]>>1); + } + return bundles_in_scores[i]; + } + } + } + if (!bundle_score) { +// GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s outputs cap bundle %d do not match filter %s inputs\n", src->name, src_bundle_idx, dst_reg->name)); + } else { +// GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s outputs cap bundle %d matches filter %s inputs caps bundle %d (%d total bundle matches, bundle matched %d/%d caps)\n", src->name, src_bundle_idx, dst_reg->name, *dst_bundle_idx, nb_matched, bundle_score, nb_out_caps)); + } + return bundle_score; +} + +GF_EXPORT +Bool gf_filter_pid_check_caps(GF_FilterPid *pid) +{ + u8 priority; + Bool res; + if (PID_IS_OUTPUT(pid)) return GF_FALSE; + pid->pid->local_props = ((GF_FilterPidInst*)pid)->props; + res = gf_filter_pid_caps_match(pid->pid, NULL, pid->filter, &priority, NULL, pid->filter, -1); + pid->pid->local_props = NULL; + return res; +} + + +static void concat_reg(GF_FilterSession *sess, char prefRegister[1001], const char *reg_key, const char *args) +{ + u32 len; + char *forced_reg, *sep; + if (!args) return; + forced_reg = strstr(args, reg_key); + if (!forced_reg) return; + forced_reg += 6; + sep = strchr(forced_reg, sess->sep_args); + len = sep ? (u32) (sep-forced_reg) : (u32) strlen(forced_reg); + if (len+2+strlen(prefRegister)>1000) { + return; + } + if (prefRegister[0]) { + char szSepChar[2]; + szSepChar[0] = sess->sep_args; + szSepChar[1] = 0; + strcat(prefRegister, szSepChar); + } + strncat(prefRegister, forced_reg, len); +} + +static Bool gf_filter_out_caps_solved_by_connection(const GF_FilterRegister *freg, u32 bundle_idx) +{ + u32 i, k, cur_bundle_idx = 0; + u32 nb_out_caps=0; + for (i=0; inb_caps; i++) { + u32 nb_caps = 0; + u32 cap_bundle_idx = 0; + const GF_FilterCapability *cap = &freg->caps[i]; + if (!(cap->flags & GF_CAPFLAG_IN_BUNDLE)) { + cur_bundle_idx++; + if (cur_bundle_idx>bundle_idx) return GF_FALSE; + continue; + } + if (!(cap->flags & GF_CAPFLAG_STATIC) && (bundle_idx>cur_bundle_idx)) continue; + if (!(cap->flags & GF_CAPFLAG_OUTPUT)) continue; + + if (cap->flags & GF_CAPFLAG_OPTIONAL) continue; + + for (k=0; knb_caps; k++) { + const GF_FilterCapability *acap = &freg->caps[k]; + if (!(acap->flags & GF_CAPFLAG_IN_BUNDLE)) { + cap_bundle_idx++; + continue; + } + if (!(acap->flags & GF_CAPFLAG_OUTPUT)) continue; + if (acap->flags & GF_CAPFLAG_OPTIONAL) continue; + if (!(acap->flags & GF_CAPFLAG_STATIC) && (cap_bundle_idx!=bundle_idx) ) continue; + + if (cap->code && (acap->code==cap->code)) { + nb_caps++; + } else if (cap->name && acap->name && !strcmp(cap->name, acap->name)) { + nb_caps++; + } + //if more than one cap with same code in same bundle, consider the filter is undecided + if (nb_caps>1) + return GF_TRUE; + } + if (nb_caps && !(cap->flags & GF_CAPFLAG_EXCLUDED)) + nb_out_caps++; + } + if (!nb_out_caps) + return GF_TRUE; + return GF_FALSE; +} + +static s32 gf_filter_reg_get_bundle_stream_type(const GF_FilterRegister *freg, u32 cap_idx, Bool for_output) +{ + u32 i, cur_bundle, stype=0, nb_stype=0; + + cur_bundle = 0; + for (i=0; inb_caps; i++) { + u32 cap_stype=0; + const GF_FilterCapability *cap = &freg->caps[i]; + if (!(cap->flags & GF_CAPFLAG_IN_BUNDLE)) { + cur_bundle++; + continue; + } + if (for_output) { + if (!(cap->flags & GF_CAPFLAG_OUTPUT)) continue; + } else { + if (!(cap->flags & GF_CAPFLAG_INPUT)) continue; + } + if ((cur_bundle != cap_idx) && !(cap->flags & GF_CAPFLAG_STATIC) ) continue; + //output type is file or same media type, allow looking for filter chains + if (cap->flags & GF_CAPFLAG_EXCLUDED) continue; + + if (cap->code == GF_PROP_PID_STREAM_TYPE) + cap_stype = cap->val.value.uint; + else if ((cap->code == GF_PROP_PID_MIME) || (cap->code == GF_PROP_PID_FILE_EXT) ) + cap_stype = GF_STREAM_FILE; + + if (!cap_stype) continue; + + if (stype != cap_stype) { + stype = cap_stype; + nb_stype++; + } + } + if (nb_stype==1) return (s32) stype; + if (nb_stype) return -1; + return 0; +} + +/*recursively enable edges of the graph. + returns 0 if subgraph shall be disabled (will marke edge at the root of the subgraph disabled) + returns 1 if subgraph shall be enabled (will marke edge at the root of the subgraph enabled) + returns 2 if no decision can be taken because the subgraph is too deep. We don't mark the parent edge as disabled because the same subgraph/edge can be used at other places in a shorter path +*/ +static u32 gf_filter_pid_enable_edges(GF_FilterSession *fsess, GF_FilterRegDesc *reg_desc, u32 src_cap_idx, const GF_FilterRegister *src_freg, u32 rlevel, s32 dst_stream_type, GF_FilterRegDesc *parent_desc, GF_FilterPid *pid, u32 pid_stream_type) +{ + u32 i=0; + Bool enable_graph = GF_FALSE; + Bool aborted_graph_too_deep = GF_FALSE; + + //we found the source reg we want to connect to! + if (src_freg == reg_desc->freg) { + return 1; + } + //the subgraph is too deep, abort marking edges but don't decide + if (rlevel > fsess->max_resolve_chain_len) { + return 2; + } + //we don't allow loops in dynamic chain resolution, so consider the parent edge invalid + if (reg_desc->in_edges_enabling) + return 0; + + /*if dst type is FILE, reg_desc is a muxer or the loaded destination (a demuxer or a file) + we only accept dst type FILE for the first call (ie reg desc is the loaded destination), and forbid muxers in the middle of the chain + for dynamic resolution. This avoids situations such as StreamTypeA->mux->demux->streamtypeB which cannot be resolved + + note that it is still possible to use a mux or demux in the chain, but they have to be explicetly loaded + */ + if ((rlevel>1) && (dst_stream_type==GF_STREAM_FILE)) + return 0; + + reg_desc->in_edges_enabling = 1; + + for (i=0; inb_edges; i++) { + u32 res; + s32 source_stream_type; + GF_FilterRegEdge *edge = ®_desc->edges[i]; + //this edge is not for our target source cap bundle + if (edge->dst_cap_idx != src_cap_idx) continue; + + //edge is already disabled (the subgraph doesn't match our source), don't test it + if (edge->status == EDGE_STATUS_DISABLED) + continue; + + //if source is not edge origin and edge is only valid for explicitly loaded filters, disable edge + if (edge->loaded_filter_only && (edge->src_reg->freg != pid->filter->freg) ) { + edge->status = EDGE_STATUS_DISABLED; + edge->disabled_depth = rlevel+1; + continue; + } + + //edge is already enabled (the subgraph matches our source), don't test it but remember to indicate the graph is valid + if (edge->status == EDGE_STATUS_ENABLED) { + enable_graph = GF_TRUE; + continue; + } + + //candidate edge, check stream type + source_stream_type = edge->src_stream_type; + + if (pid->filter->freg == edge->src_reg->freg) + source_stream_type = pid_stream_type; + + //source edge cap indicates multiple stream types (demuxer/encoder/decoder dundle) + if (source_stream_type<0) { + //if destination type is known (>=0 and NOT file, inherit it + //otherwise, we we can't filter out yet + if ((dst_stream_type>0) && (dst_stream_type != GF_STREAM_FILE)) + source_stream_type = dst_stream_type; + } + //inherit source type if not specified + if (!source_stream_type && dst_stream_type>0) + source_stream_type = dst_stream_type; + //if source is encrypted type and dest type is set, use dest type + if ((source_stream_type==GF_STREAM_ENCRYPTED) && dst_stream_type>0) + source_stream_type = dst_stream_type; + //if dest is encrypted type and source type is set, use source type + if ((dst_stream_type==GF_STREAM_ENCRYPTED) && source_stream_type>0) + dst_stream_type = source_stream_type; + + //if stream types are know (>0) and not source files, do not mark the edges if they mismatch + //moving from non-file type A to non-file type B requires an explicit filter + if ((dst_stream_type>0) && (source_stream_type>0) && (source_stream_type != GF_STREAM_FILE) && (dst_stream_type != GF_STREAM_FILE) && (source_stream_type != dst_stream_type)) { + + //exception: we allow text|scene|od ->video for dynamic compositor + if (!(reg_desc->freg->flags & GF_FS_REG_EXPLICIT_ONLY) && (dst_stream_type==GF_STREAM_VISUAL) + && ((source_stream_type==GF_STREAM_TEXT) || (source_stream_type==GF_STREAM_SCENE) || (source_stream_type==GF_STREAM_OD) ) + ) { + + } else { + edge->status = EDGE_STATUS_DISABLED; + edge->disabled_depth = rlevel+1; + continue; + } + } + + res = gf_filter_pid_enable_edges(fsess, edge->src_reg, edge->src_cap_idx, src_freg, rlevel+1, source_stream_type, reg_desc, pid, pid_stream_type); + //if subgraph matches our source reg, mark the edge towards this subgraph as enabled + if (res==1) { + edge->status = EDGE_STATUS_ENABLED; + enable_graph = GF_TRUE; + } + //if sub-graph below is too deep, don't mark the edge since we might need to resolve it again with a shorter subgraph + else if (res==2) { + aborted_graph_too_deep = GF_TRUE; + } + //otherwise the subgraph doesn't match our source reg, mark as disaled and never test again + else if (res==0) { + edge->status = EDGE_STATUS_DISABLED; + edge->disabled_depth = rlevel+1; + } + } + reg_desc->in_edges_enabling = 0; + //we had enabled edges, the subgraph is valid + if (enable_graph) return 1; + //we aborted because too deep, indicate it to the caller so that the edge is not disabled + if (aborted_graph_too_deep) return 2; + //disable subgraph + return 0; +} + + +static GF_FilterRegDesc *gf_filter_reg_build_graph(GF_List *links, const GF_FilterRegister *freg, GF_CapsBundleStore *capstore, GF_FilterPid *src_pid, GF_Filter *dst_filter) +{ + u32 nb_dst_caps, nb_regs, i, nb_caps; + Bool freg_has_output; + + GF_FilterRegDesc *reg_desc = NULL; + const GF_FilterCapability *caps = freg->caps; + nb_caps = freg->nb_caps; + if (dst_filter && (freg->flags & GF_FS_REG_SCRIPT)) { + caps = dst_filter->forced_caps; + nb_caps = dst_filter->nb_forced_caps; + } + + freg_has_output = gf_filter_has_out_caps(caps, nb_caps); + + GF_SAFEALLOC(reg_desc, GF_FilterRegDesc); + if (!reg_desc) return NULL; + + reg_desc->freg = freg; + + nb_dst_caps = gf_filter_caps_bundle_count(caps, nb_caps); + + + //we are building a register descriptor acting as destination, ignore any output caps + if (src_pid || dst_filter) freg_has_output = GF_FALSE; + + //setup all connections + nb_regs = gf_list_count(links); + for (i=0; ifreg == freg) continue; + + //check which cap of this filter matches our destination + nb_src_caps = gf_filter_caps_bundle_count(a_reg->freg->caps, a_reg->freg->nb_caps); + for (k=0; kfreg->caps, a_reg->freg->nb_caps)) { + u32 loaded_filter_only_flags = 0; + + path_weight = gf_filter_caps_to_caps_match(a_reg->freg, k, (const GF_FilterRegister *) freg, dst_filter, &bundle_idx, l, &loaded_filter_only_flags, capstore); + + if (path_weight && (bundle_idx == l)) { + GF_FilterRegEdge *edge; + if (reg_desc->nb_edges==reg_desc->nb_alloc_edges) { + reg_desc->nb_alloc_edges += 10; + reg_desc->edges = gf_realloc(reg_desc->edges, sizeof(GF_FilterRegEdge) * reg_desc->nb_alloc_edges); + } + assert(path_weight<0xFF); + assert(k<0xFFFF); + assert(l<0xFFFF); + edge = ®_desc->edges[reg_desc->nb_edges]; + memset(edge, 0, sizeof(GF_FilterRegEdge)); + edge->src_reg = a_reg; + edge->weight = (u8) path_weight; + edge->src_cap_idx = (u16) k; + edge->dst_cap_idx = (u16) l; + + //we inverted the caps, invert the flags + if (loaded_filter_only_flags & EDGE_LOADED_SOURCE_ONLY) + edge->loaded_filter_only |= EDGE_LOADED_DEST_ONLY; + if (loaded_filter_only_flags & EDGE_LOADED_DEST_ONLY) + edge->loaded_filter_only |= EDGE_LOADED_SOURCE_ONLY; + edge->src_stream_type = gf_filter_reg_get_bundle_stream_type(edge->src_reg->freg, edge->src_cap_idx, GF_TRUE); + reg_desc->nb_edges++; + } + } + + if ( freg_has_output ) { + u32 loaded_filter_only_flags = 0; + + path_weight = gf_filter_caps_to_caps_match(freg, l, a_reg->freg, dst_filter, &bundle_idx, k, &loaded_filter_only_flags, capstore); + + if (path_weight && (bundle_idx == k)) { + GF_FilterRegEdge *edge; + if (a_reg->nb_edges==a_reg->nb_alloc_edges) { + a_reg->nb_alloc_edges += 10; + a_reg->edges = gf_realloc(a_reg->edges, sizeof(GF_FilterRegEdge) * a_reg->nb_alloc_edges); + } + edge = &a_reg->edges[a_reg->nb_edges]; + edge->src_reg = reg_desc; + edge->weight = (u8) path_weight; + edge->src_cap_idx = (u16) l; + edge->dst_cap_idx = (u16) k; + edge->priority = 0; + edge->loaded_filter_only = loaded_filter_only_flags; + edge->src_stream_type = gf_filter_reg_get_bundle_stream_type(edge->src_reg->freg, edge->src_cap_idx, GF_TRUE); + a_reg->nb_edges++; + } + } + } + } + } + return reg_desc; +} + +void gf_filter_sess_build_graph(GF_FilterSession *fsess, const GF_FilterRegister *for_reg) +{ + u32 i, count; + GF_CapsBundleStore capstore; + memset(&capstore, 0, sizeof(GF_CapsBundleStore)); + + if (!fsess->links) fsess->links = gf_list_new(); + + if (for_reg) { + GF_FilterRegDesc *freg_desc = gf_filter_reg_build_graph(fsess->links, for_reg, &capstore, NULL, NULL); + if (!freg_desc) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to build graph entry for filter %s\n", for_reg->name)); + } else { + gf_list_add(fsess->links, freg_desc); + } + } else { +#ifndef GPAC_DISABLE_LOG + u64 start_time = gf_sys_clock_high_res(); +#endif + count = gf_list_count(fsess->registry); + for (i=0; iregistry, i); + GF_FilterRegDesc *freg_desc = gf_filter_reg_build_graph(fsess->links, freg, &capstore, NULL, NULL); + if (!freg_desc) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to build graph entry for filter %s\n", freg->name)); + } else { + gf_list_add(fsess->links, freg_desc); + } + } + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Build filter graph in "LLU" us\n", gf_sys_clock_high_res() - start_time)); + + if (fsess->flags & GF_FS_FLAG_PRINT_CONNECTIONS) { + u32 j; + count = gf_list_count(fsess->links); + for (i=0; ilinks, i); + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s sources:", freg_desc->freg->name)); + for (j=0; jnb_edges; j++ ) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, (" %s(%d,%d->%d)", freg_desc->edges[j].src_reg->freg->name, freg_desc->edges[j].weight, freg_desc->edges[j].src_cap_idx, freg_desc->edges[j].dst_cap_idx)); + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("\n")); + } + } + } + if (capstore.bundles_cap_found) gf_free(capstore.bundles_cap_found); + if (capstore.bundles_in_ok) gf_free(capstore.bundles_in_ok); + if (capstore.bundles_in_scores) gf_free(capstore.bundles_in_scores); +} + +void gf_filter_sess_reset_graph(GF_FilterSession *fsess, const GF_FilterRegister *freg) +{ + gf_mx_p(fsess->links_mx); + //explicit registry removal and not destroying the session + if (freg && fsess->filters) { + s32 reg_idx=-1; + u32 i, count = gf_list_count(fsess->links); + for (i=0; ilinks, i); + if (rdesc->freg == freg) { + reg_idx = i; + continue; + } + for (j=0; jnb_edges; j++) { + if (rdesc->edges[j].src_reg->freg == freg) { + if (rdesc->nb_edges > j + 1) { + memmove(&rdesc->edges[j], &rdesc->edges[j+1], sizeof (GF_FilterRegEdge) * (rdesc->nb_edges - j - 1)); + } + j--; + rdesc->nb_edges--; + } + } + } + if (reg_idx>=0) { + GF_FilterRegDesc *rdesc = gf_list_get(fsess->links, reg_idx); + gf_list_rem(fsess->links, reg_idx); + gf_free(rdesc->edges); + gf_free(rdesc); + } + } else { + while (gf_list_count(fsess->links)) { + GF_FilterRegDesc *rdesc = gf_list_pop_back(fsess->links); + gf_free(rdesc->edges); + gf_free(rdesc); + } + } + gf_mx_v(fsess->links_mx); +} + +#ifndef GPAC_DISABLE_LOG +void dump_dijstra_edges(Bool is_before, GF_FilterRegDesc *reg_dst, GF_List *dijkstra_nodes) +{ + u32 i, count; + if (! gf_log_tool_level_on(GF_LOG_FILTER, GF_LOG_DEBUG)) + return; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Dijstra edges %s edge solving\n", is_before ? "before" : "after")); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s sources: ", reg_dst->freg->name)); + for (i=0; inb_edges; i++) { + GF_FilterRegEdge *edge = ®_dst->edges[i]; + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, (" %s(%d(%d),%d,%d->%d)", edge->src_reg->freg->name, edge->status, edge->disabled_depth, edge->weight, edge->src_cap_idx, edge->dst_cap_idx)); + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("\n")); + + count = gf_list_count(dijkstra_nodes); + for (i=0; ifreg->name)); + for (j=0; jnb_edges; j++) { + GF_FilterRegEdge *edge = &rdesc->edges[j]; + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, (" %s(%d(%d),%d,%d->%d)", edge->src_reg->freg->name, edge->status, edge->disabled_depth, edge->weight, edge->src_cap_idx, edge->dst_cap_idx)); + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("\n")); + } +} +#endif + +static void gf_filter_pid_resolve_link_dijkstra(GF_FilterPid *pid, GF_Filter *dst, const char *prefRegister, Bool reconfigurable_only, GF_List *out_reg_chain) +{ + GF_FilterRegDesc *reg_dst, *result; + GF_List *dijkstra_nodes; + GF_FilterSession *fsess = pid->filter->session; + //build all edges + u32 i, dijsktra_node_count, dijsktra_edge_count, count; + GF_CapsBundleStore capstore; + Bool first; + u32 path_weight, pid_stream_type, max_weight=0; + u64 dijkstra_time_us, sort_time_us, start_time_us = gf_sys_clock_high_res(); + const GF_PropertyValue *p; + if (!fsess->links || ! gf_list_count( fsess->links)) + gf_filter_sess_build_graph(fsess, NULL); + + dijkstra_nodes = gf_list_new(); + + result = NULL; + + pid_stream_type = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (p) pid_stream_type = p->value.uint; + + //1: select all elligible filters for the graph resolution: exclude sources, sinks, explicits, blacklisted and not reconfigurable if we reconfigure + count = gf_list_count(fsess->links); + for (i=0; ilinks, i); + const GF_FilterRegister *freg = reg_desc->freg; + + //reset state, except for edges which are reseted after each dijkstra resolution + reg_desc->destination = NULL; + reg_desc->cap_idx = 0; + reg_desc->in_edges_enabling = 0; + //set node distance and priority to infinity, whether we are in the final dijsktra set or not + reg_desc->dist = -1; + reg_desc->priority = 0xFF; + + //remember our source descriptor - it may be absent of the final node set in case we want reconfigurable only filters + //and the source is not reconfigurable + if (freg == pid->filter->freg) + result = reg_desc; + + //don't add source filters except if PID is from source + if (!freg->configure_pid && (freg!=pid->filter->freg)) { + assert(freg != dst->freg); + disable_filter = GF_TRUE; + } + //freg shall be instantiated + else if ((freg->flags & (GF_FS_REG_EXPLICIT_ONLY|GF_FS_REG_SCRIPT)) && (freg != pid->filter->freg) && (freg != dst->freg) ) { + assert(freg != dst->freg); + disable_filter = GF_TRUE; + } + //no output caps, cannot add + else if ((freg != dst->freg) && !gf_filter_has_out_caps(freg->caps, freg->nb_caps)) { + disable_filter = GF_TRUE; + } + //we only want reconfigurable output filters + else if (reconfigurable_only && !freg->reconfigure_output && (freg != dst->freg)) { + assert(freg != dst->freg); + disable_filter = GF_TRUE; + } + //blacklisted filter + else if (gf_list_find(pid->filter->blacklisted, (void *) freg)>=0) { + assert(freg != dst->freg); + if (!reconfigurable_only) { + assert(freg != pid->filter->freg); + } + disable_filter = GF_TRUE; + } + //blacklisted adaptation filter + else if (pid->adapters_blacklist && (gf_list_find(pid->adapters_blacklist, (void *) freg)>=0)) { + assert(freg != dst->freg); + disable_filter = GF_TRUE; + } + + //reset edge status + for (j=0; jnb_edges; j++) { + GF_FilterRegEdge *edge = ®_desc->edges[j]; + + edge->disabled_depth = 0; + if (disable_filter) { + edge->status = EDGE_STATUS_DISABLED; + continue; + } + edge->status = EDGE_STATUS_NONE; + + //connection from source, disable edge if pid caps mismatch + if (edge->src_reg->freg == pid->filter->freg) { + u8 priority=0; + u32 dst_bundle_idx; + //check path weight for the given dst cap - we MUST give the target cap otherwise we might get a default match to another cap + path_weight = gf_filter_pid_caps_match(pid, freg, NULL, &priority, &dst_bundle_idx, pid->filter->dst_filter, edge->dst_cap_idx); + if (!path_weight) { + edge->status = EDGE_STATUS_DISABLED; + continue; + } + } + + //if source is not edge origin and edge is only valid for explicitly loaded filters, disable edge + if ((edge->loaded_filter_only & EDGE_LOADED_SOURCE_ONLY) && (edge->src_reg->freg != pid->filter->freg) ) { + edge->status = EDGE_STATUS_DISABLED; + continue; + } + + if ((u32) edge->weight + 1 > max_weight) + max_weight = (u32) edge->weight + 1; + } + //not in set + if (disable_filter) + continue; + + + //do not add destination filter + if (dst->freg == reg_desc->freg) { + reg_desc->dist = 0; + reg_desc->priority = 0; + } else { + gf_list_add(dijkstra_nodes, reg_desc); + } + } + //create a new node for the destination based on elligible filters in the graph + memset(&capstore, 0, sizeof(GF_CapsBundleStore)); + reg_dst = gf_filter_reg_build_graph(dijkstra_nodes, dst->freg, &capstore, pid, dst); + reg_dst->dist = 0; + reg_dst->priority = 0; + reg_dst->in_edges_enabling = 0; + + //enable edges of destination, potentially disabling edges from source filters to dest + for (i=0; inb_edges; i++) { + GF_FilterRegEdge *edge = ®_dst->edges[i]; + edge->status = EDGE_STATUS_NONE; + + //connection from source, disable edge if pid caps mismatch + if (edge->src_reg->freg == pid->filter->freg) { + u8 priority=0; + u32 dst_bundle_idx; + path_weight = gf_filter_pid_caps_match(pid, dst->freg, dst, &priority, &dst_bundle_idx, pid->filter->dst_filter, -1); + if (!path_weight) { + edge->status = EDGE_STATUS_DISABLED; + continue; + } + if (dst_bundle_idx != edge->dst_cap_idx) { + edge->status = EDGE_STATUS_DISABLED; + continue; + } + } + //the edge source filter is not loaded, disable edges marked for loaded filter only + if ( (edge->loaded_filter_only & EDGE_LOADED_SOURCE_ONLY) && (edge->src_reg->freg != pid->filter->freg) ) { + edge->status = EDGE_STATUS_DISABLED; + continue; + } + //we are relinking to a dynamically loaded filter, only accept edges connecting to the same bundle as when + //the initial resolution was done, unless the edge is marked as loaded destination filter only in which case + //we accept connection + if ((dst->bundle_idx_at_resolution>=0) + && !(edge->loaded_filter_only & EDGE_LOADED_DEST_ONLY) + && (edge->dst_cap_idx !=dst->bundle_idx_at_resolution) + ) { + edge->status = EDGE_STATUS_DISABLED; + continue; + } + + if ((u32) edge->weight + 1 > max_weight) + max_weight = edge->weight + 1; + //enable edge and propagate down the graph + edge->status = EDGE_STATUS_ENABLED; + + gf_filter_pid_enable_edges(fsess, edge->src_reg, edge->src_cap_idx, pid->filter->freg, 1, edge->src_stream_type, reg_dst, pid, pid_stream_type); + } + + if (capstore.bundles_cap_found) gf_free(capstore.bundles_cap_found); + if (capstore.bundles_in_ok) gf_free(capstore.bundles_in_ok); + if (capstore.bundles_in_scores) gf_free(capstore.bundles_in_scores); + +#ifndef GPAC_DISABLE_LOG + if (fsess->flags & GF_FS_FLAG_PRINT_CONNECTIONS) { + dump_dijstra_edges(GF_FALSE, reg_dst, dijkstra_nodes); + } +#endif + + //remove all filters not used for this resolution (no enabled edges), except source one + count = gf_list_count(dijkstra_nodes); + for (i=0; ifreg == pid->filter->freg) continue; + + nb_edges = 0; + for (j=0; jnb_edges; j++) { + GF_FilterRegEdge *edge = &rdesc->edges[j]; + if (edge->status == EDGE_STATUS_ENABLED) { + nb_edges++; + break; + } + } + + if (!nb_edges) { + gf_list_rem(dijkstra_nodes, i); + i--; + count--; + } + } +#ifndef GPAC_DISABLE_LOG + if (fsess->flags & GF_FS_FLAG_PRINT_CONNECTIONS) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filters in dijkstra set:")); + count = gf_list_count(dijkstra_nodes); + for (i=0; ifreg->name)); + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("\n")); + } +#endif + + sort_time_us = gf_sys_clock_high_res(); + + + dijsktra_edge_count = 0; + dijsktra_node_count = gf_list_count(dijkstra_nodes)+1; + first = GF_TRUE; + //OK we have the weighted graph, perform a dijkstra on the graph - we assign by weight, and if same weight we check the priority + while (1) { + GF_FilterRegDesc *current_node = NULL; + u32 reg_idx = -1; + u32 min_dist = -1; + + count = gf_list_count(dijkstra_nodes); + if (!count) break; + + if (first) { + current_node = reg_dst; + } else { + //pick up shortest distance + for (i=0; idist < min_dist) { + min_dist = reg_desc->dist; + current_node = reg_desc; + reg_idx = i; + } + } + //remove current + if (!current_node) + break; + gf_list_rem(dijkstra_nodes, reg_idx); + } + + if (current_node->freg == pid->filter->freg) { + result = current_node; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("[Filters] Dijkstra: testing filter %s\n", current_node->freg->name)); + + //compute distances + for (i=0; inb_edges; i++) { + u8 priority=0; + GF_FilterRegEdge *redge = ¤t_node->edges[i]; + u32 dist; + Bool do_switch = GF_FALSE; + dijsktra_edge_count++; + + if (redge->status != EDGE_STATUS_ENABLED) + continue; + + dist = current_node->dist + 1;//(max_weight - redge->weight); + if (current_node->freg->flags & GF_FS_REG_HIDE_WEIGHT) { + dist = current_node->dist; + } + + priority = redge->priority; + if (redge->src_reg->freg == pid->filter->freg) { + s32 dst_bundle_idx; + if (gf_filter_pid_caps_match(pid, current_node->freg, NULL, &priority, &dst_bundle_idx, dst, redge->dst_cap_idx)) { + + } else { + continue; + } + } + + if (dist < redge->src_reg->dist) do_switch = GF_TRUE; + else if (dist == redge->src_reg->dist) { + if (prefRegister[0] && (redge->src_reg->destination != current_node) && strstr(prefRegister, current_node->freg->name)) { + do_switch = GF_TRUE; + priority = 0; + } else if ( (dist == redge->src_reg->dist) && (priority < redge->src_reg->priority) ) + do_switch = GF_TRUE; + } + + if (do_switch) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("[Filters] Dijkstra: assign filter %s distance %d destination to %s in cap %d out cap %d priority %d (previous destination %s distance %d priority %d)\n", redge->src_reg->freg->name, dist, current_node->freg->name, redge->src_cap_idx, redge->dst_cap_idx, redge->priority, redge->src_reg->destination ? redge->src_reg->destination->freg->name : "none", redge->src_reg->dist, redge->src_reg->priority )); + redge->src_reg->dist = dist; + redge->src_reg->priority = priority; + redge->src_reg->destination = current_node; + redge->src_reg->cap_idx = redge->src_cap_idx; + } else if (fsess->flags & GF_FS_FLAG_PRINT_CONNECTIONS) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("[Filters] Dijkstra: no shorter path from filter %s distance %d from destination %s priority %d (tested %s dist %d priority %d)\n", redge->src_reg->freg->name, redge->src_reg->dist, redge->src_reg->destination ? redge->src_reg->destination->freg->name : "none", redge->priority, current_node->freg->name, dist, redge->src_reg->priority)); + } + } + first = GF_FALSE; + } + + sort_time_us -= start_time_us; + dijkstra_time_us = gf_sys_clock_high_res() - start_time_us; + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("[Filters] Dijkstra: sorted filters in "LLU" us, Dijkstra done in "LLU" us on %d nodes %d edges\n", sort_time_us, dijkstra_time_us, dijsktra_node_count, dijsktra_edge_count)); + + if (result && result->destination) { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("[Filters] Dijkstra result: %s(%d)", result->freg->name, result->cap_idx)); + result = result->destination; + while (result->destination) { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, (" %s(%d)", result->freg->name, result->cap_idx )); + gf_list_add(out_reg_chain, (void *) result->freg); + gf_list_add(out_reg_chain, (void *) &result->freg->caps[result->cap_idx]); + result = result->destination; + } + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, (" %s\n", result->freg->name)); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("[Filters] Dijkstra: no results found!\n")); + } + gf_list_del(dijkstra_nodes); + + gf_free(reg_dst->edges); + gf_free(reg_dst); +} + + +/* + !Resolves a link between a PID and a destination filter + +\param pid source pid to connect +\param dst destination filter to connect source PID to +\param filter_reassigned indicates the filter has been destroyed and reassigned +\param reconfigurable_only indicates the chain should be loaded for reconfigurable filters +\return the first filter in the matching chain, or NULL if no match +*/ +static GF_Filter *gf_filter_pid_resolve_link_internal(GF_FilterPid *pid, GF_Filter *dst, Bool *filter_reassigned, Bool reconfigurable_only, u32 *min_chain_len, GF_List *skip_if_in_filter_list, Bool *skipped) +{ + GF_Filter *chain_input = NULL; + GF_FilterSession *fsess = pid->filter->session; + GF_List *filter_chain; + u32 i, count; + char prefRegister[1001]; + char szForceReg[20]; + + if (!fsess->max_resolve_chain_len) return NULL; + + filter_chain = gf_list_new(); + + if (!dst) return NULL; + + sprintf(szForceReg, "gfreg%c", pid->filter->session->sep_name); + prefRegister[0]=0; + //look for reg given in + concat_reg(pid->filter->session, prefRegister, szForceReg, pid->filter->orig_args ? pid->filter->orig_args : pid->filter->src_args); + concat_reg(pid->filter->session, prefRegister, szForceReg, pid->filter->dst_args); + concat_reg(pid->filter->session, prefRegister, szForceReg, dst->src_args); + concat_reg(pid->filter->session, prefRegister, szForceReg, dst->dst_args); + + gf_mx_p(fsess->links_mx); + gf_filter_pid_resolve_link_dijkstra(pid, dst, prefRegister, reconfigurable_only, filter_chain); + gf_mx_v(fsess->links_mx); + + count = gf_list_count(filter_chain); + if (min_chain_len) { + *min_chain_len = count; + } else if (count==0) { + Bool can_reassign = GF_TRUE; + + //reassign only for source filters + if (pid->filter->num_input_pids) can_reassign = GF_FALSE; + //sticky filters cannot be unloaded + else if (pid->filter->sticky) can_reassign = GF_FALSE; + //if we don't have pending PIDs to setup from the source + else if (pid->filter->out_pid_connection_pending) can_reassign = GF_FALSE; + //if we don't have pending PIDs to setup from the source + else if (pid->filter->num_output_pids) { + u32 k; + for (k=0; kfilter->num_output_pids; k++) { + GF_FilterPid *apid = gf_list_get(pid->filter->output_pids, k); + if (apid->num_destinations) can_reassign = GF_FALSE; + else if ((apid==pid) && (apid->init_task_pending>1)) can_reassign = GF_FALSE; + else if ((apid!=pid) && apid->init_task_pending) can_reassign = GF_FALSE; + if (!can_reassign) + break; + } + } + //if source filter, try to load another filter - we should complete this with a cache of filter sources + if (filter_reassigned && can_reassign) { + if (! *filter_reassigned) { + if (! gf_filter_swap_source_register(pid->filter) ) { + //no filter found for this pid ! + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("No suitable filter chain found\n")); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Swap source demux to %s\n", pid->filter->freg->name)); + } + } + *filter_reassigned = GF_TRUE; + } else if (!reconfigurable_only) { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("No suitable filter found for pid %s from filter %s\n", pid->name, pid->filter->name)); + if (filter_reassigned) + *filter_reassigned = GF_FALSE; + } + } else if (reconfigurable_only && (count>2)) { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Cannot find filter chain with only one filter handling reconfigurable output for pid %s from filter %s - not supported\n", pid->name, pid->filter->name)); + } else { + const char *dst_args = NULL; + const char *args = pid->filter->orig_args ? pid->filter->orig_args : pid->filter->src_args; + GF_FilterPid *a_pid = pid; + GF_Filter *prev_af; + + if (skip_if_in_filter_list) { + assert(skipped); + *skipped = GF_FALSE; + u32 nb_skip = gf_list_count(skip_if_in_filter_list); + const GF_FilterRegister *chain_start_freg = gf_list_get(filter_chain, 0); + for (i=0; idestination_filters); j++) { + dest_f = gf_list_get(dst->destination_filters, j); + if ((gf_list_find(f->destination_filters, dest_f)>=0) || (gf_list_find(f->destination_links, dest_f)>=0)) { + true_skip = GF_TRUE; + break; + } + dest_f = NULL; + } + + for (j=0; jdestination_links) && !true_skip; j++) { + dest_f = gf_list_get(dst->destination_links, j); + if ((gf_list_find(f->destination_filters, dest_f)>=0) || (gf_list_find(f->destination_links, dest_f)>=0)) { + true_skip = GF_TRUE; + break; + } + dest_f = NULL; + } + if (true_skip) { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Skip link from %s:%s to %s because both filters share the same destination %s\n", pid->filter->name, pid->name, dst->name, dest_f->name)); + *skipped = GF_TRUE; + gf_list_del(filter_chain); + return NULL; + } + + if (f->freg == chain_start_freg) { + //store destination as future destination link for this new filter + if (gf_list_find(f->destination_links, dst)<0) + gf_list_add(f->destination_links, dst); + + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Skip link from %s:%s to %s because already connected to filter %s which can handle the connection\n", pid->filter->name, pid->name, dst->name, f->name)); + + *skipped = GF_TRUE; + gf_list_del(filter_chain); + return NULL; + } + } + } + + dst_args = dst->src_args ? dst->src_args : dst->orig_args; + + while (a_pid) { + GF_FilterPidInst *pidi; + args = a_pid->filter->src_args; + if (!args) args = a_pid->filter->orig_args; + if (args) break; + pidi = gf_list_get(a_pid->filter->input_pids, 0); + if (!pidi) break; + a_pid = pidi->pid; + } + +#ifndef GPAC_DISABLE_LOG + if (gf_log_tool_level_on(GF_LOG_FILTER, GF_LOG_INFO)) { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Solved %sfilter chain from filter %s PID %s to filter %s - dumping chain:\n", reconfigurable_only ? "adaptation " : "", pid->filter->name, pid->name, dst->freg->name)); + } +#endif + prev_af = NULL; + for (i=0; inb_caps; k++) { + if (&freg->caps[k]==cap) { + bundle_idx = k; + break; + } + } + cur_bundle = 0; + for (k=0; knb_caps; k++) { + cap = &freg->caps[k]; + if (cur_bundle==bundle_idx) { + cap_idx = k; + break; + } + if (!(cap->flags & GF_CAPFLAG_IN_BUNDLE)) { + cur_bundle++; + } + } + //if first filter has multiple possible outputs, don't bother loading the entire chain since it is likely wrong + //(eg demuxers, we don't know yet what's in the file) + if (!i && gf_filter_out_caps_solved_by_connection(freg, bundle_idx)) { + load_first_only = GF_TRUE; + } else if (i) { + Bool break_chain = GF_FALSE; + u32 j, nb_filters = gf_list_count(fsess->filters); + for (j=0; jfilters, j); + if (afilter->freg != freg) continue; + if (!afilter->dynamic_filter) continue; + if (gf_list_find(pid->filter->destination_links, dst)<0) continue; + if (!afilter->max_extra_pids) continue; + + //we load the same dynamic filter and it can accept multiple inputs (probably a mux), we might reuse this filter so stop link resolution now + //not doing so would load e new mux filter which would accept the input pids but with potentially no possible output connections + break_chain = GF_TRUE; + if (prev_af) { + //store destination as future destination link for this new filter + if ( gf_list_find(pid->filter->destination_links, afilter)<0) + gf_list_add(pid->filter->destination_links, afilter); + + //remember to which filter we are trying to connect for cap resolution + prev_af->cap_dst_filter = dst; + } + break; + } + if (break_chain) { + break; + } + } + + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("\t%s\n", freg->name)); + + af = gf_filter_new(fsess, freg, args, dst_args, pid->filter->no_dst_arg_inherit ? GF_FILTER_ARG_INHERIT_SOURCE_ONLY : GF_FILTER_ARG_INHERIT, NULL, NULL, GF_TRUE); + if (!af) goto exit; + + //remember our target cap bundle on that filter + af->bundle_idx_at_resolution = bundle_idx; + //remember our target cap on that filter + af->cap_idx_at_resolution = cap_idx; + //copy source IDs for all filters in the chain + //we cannot figure out the destination sourceID when initializing PID connection tasks + //by walking up the filter chain because PIDs connection might be pending + //(upper chain not yet fully connected) + if (dst->source_ids) + af->source_ids = gf_strdup(dst->source_ids); + + //remember our target filter + if (prev_af) + gf_list_add(prev_af->destination_filters, af); + + //last in chain, add dst + if (i+2==count) { + gf_list_add(af->destination_filters, dst); + } + //we will load several filters in chain, add destination to each of the loaded filter so that we remember what was this filter target + //this avoids browing the chain of filters->destination_filters when doing link resolution + else if (!load_first_only) { + gf_list_add(af->destination_filters, dst); + } + + //also remember our original target in case we got the link wrong + af->target_filter = pid->filter->target_filter; + + prev_af = af; + + if (reconfigurable_only) af->is_pid_adaptation_filter = GF_TRUE; + + //remember the first in the chain + if (!i) chain_input = af; + + if (load_first_only) { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s needs to be connected to decide its outputs, not loading end of the chain\n", freg->name)); + //store destination as future destination link for this new filter + if ( gf_list_find(pid->filter->destination_links, dst)<0) + gf_list_add(pid->filter->destination_links, dst); + + //in case we added it, remove the destination filter + gf_list_del_item(af->destination_filters, dst); + + //remember to which filter we are trying to connect for cap resolution + af->cap_dst_filter = dst; + break; + } + } + } + +exit: + gf_list_del(filter_chain); + return chain_input; +} + +GF_Filter *gf_filter_pid_resolve_link(GF_FilterPid *pid, GF_Filter *dst, Bool *filter_reassigned) +{ + return gf_filter_pid_resolve_link_internal(pid, dst, filter_reassigned, GF_FALSE, NULL, NULL, NULL); +} + +GF_Filter *gf_filter_pid_resolve_link_check_loaded(GF_FilterPid *pid, GF_Filter *dst, Bool *filter_reassigned, GF_List *skip_if_in_filter_list, Bool *skipped) +{ + return gf_filter_pid_resolve_link_internal(pid, dst, filter_reassigned, GF_FALSE, NULL, skip_if_in_filter_list, skipped); +} + +GF_Filter *gf_filter_pid_resolve_link_for_caps(GF_FilterPid *pid, GF_Filter *dst) +{ + return gf_filter_pid_resolve_link_internal(pid, dst, NULL, GF_TRUE, NULL, NULL, NULL); +} + +u32 gf_filter_pid_resolve_link_length(GF_FilterPid *pid, GF_Filter *dst) +{ + u32 chain_len=0; + gf_filter_pid_resolve_link_internal(pid, dst, NULL, GF_FALSE, &chain_len, NULL, NULL); + return chain_len; +} + +static void gf_filter_pid_set_args_internal(GF_Filter *filter, GF_FilterPid *pid, char *args, u32 argfile_level) +{ + //parse each arg + while (args) { + u32 p4cc=0; + u32 prop_type=GF_PROP_FORBIDEN; + Bool parse_prop = GF_TRUE; + char *value_next_list = NULL; + char *value_sep = NULL; + char *value, *name; + //look for our arg separator + + char *sep = (char *)gf_fs_path_escape_colon(filter->session, args); + + if (sep) { + char *xml_start = strchr(args, '<'); + u32 len = (u32) (sep-args); + if (xml_start) { + u32 xlen = (u32) (xml_start-args); + if ((xlen < len) && (args[len-1] != '>')) { + while (1) { + sep = strchr(sep+1, filter->session->sep_args); + if (!sep) { + break; + } + len = (u32) (sep-args); + if (args[len-1] == '>') + break; + } + } + + } + } + + if (sep) sep[0]=0; + + if (args[0] != filter->session->sep_frag) { + //if arg is not one of our reserved keywords and is a valid file, try to open it + if (strcmp(args, "gpac") && strcmp(args, "gfopt") && strcmp(args, "gfloc") && gf_file_exists(args)) { + if (argfile_level<5) { + char szLine[2001]; + FILE *arg_file = gf_fopen(args, "rt"); + szLine[2000]=0; + while (!gf_feof(arg_file)) { + u32 llen; + char *subarg, *res; + szLine[0] = 0; + res = gf_fgets(szLine, 2000, arg_file); + if (!res) break; + llen = (u32) strlen(szLine); + while (llen && strchr(" \n\r\t", szLine[llen-1])) { + szLine[llen-1]=0; + llen--; + } + if (!llen) + continue; + + subarg = szLine; + while (subarg[0] && strchr(" \n\r\t", subarg[0])) + subarg++; + if ((subarg[0] == '/') && (subarg[1] == '/')) + continue; + + gf_filter_pid_set_args_internal(filter, pid, subarg, argfile_level+1); + } + gf_fclose(arg_file); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Filter argument file has too many nested levels of sub-files, maximum allowed is 5\n")); + } + } + goto skip_arg; + } + + value = NULL; + value_sep = strchr(args, filter->session->sep_name); + if (value_sep) { + value_sep[0]=0; + value = value_sep+1; + } + name = args+1; + + if (strlen(name)==4) { + p4cc = GF_4CC(name[0], name[1], name[2], name[3]); + if (p4cc) prop_type = gf_props_4cc_get_type(p4cc); + } + if (prop_type==GF_PROP_FORBIDEN) { + p4cc = gf_props_get_id(name); + if (p4cc) prop_type = gf_props_4cc_get_type(p4cc); + } + + //look for conditional statements: "(PROP=VAL)VALUE" + while (value && (value[0]=='(')) { + Bool pid_excluded, needs_resolve, prop_not_found, prop_matched; + char prop_dump_buffer[GF_PROP_DUMP_ARG_SIZE]; + + char *next_val = NULL; + char *closing = strchr(value, ')'); + if (!closing) break; + + if (!strncmp(value, "()", 2)) { + value = closing+1; + parse_prop = GF_TRUE; + value_next_list = next_val; + break; + } + + parse_prop = GF_FALSE; + + next_val = strchr(closing, filter->session->sep_list); + if (next_val) next_val[0] = 0; + + while (closing) { + char *next_closing; + closing[0] = 0; + prop_matched = filter_pid_check_fragment(pid, value+1, &pid_excluded, &needs_resolve, &prop_not_found, prop_dump_buffer); + if (prop_not_found) prop_matched = GF_FALSE; + closing[0] = ')'; + + if (!prop_matched) + break; + if (strncmp(closing, ")(", 2)) break; + next_closing = strchr(closing+2, ')'); + if (!next_closing) break; + + value = closing+1; + closing = next_closing; + } + + if (prop_matched) { + value = closing+1; + parse_prop = GF_TRUE; + value_next_list = next_val; + break; + } + if (!next_val) break; + next_val[0] = filter->session->sep_list; + value = next_val+1; + } + + if (!parse_prop) + goto skip_arg; + + + if (prop_type != GF_PROP_FORBIDEN) { + GF_PropertyValue p = gf_props_parse_value(prop_type, name, value, NULL, pid->filter->session->sep_list); + if (prop_type==GF_PROP_NAME) { + p.type = GF_PROP_STRING; + gf_filter_pid_set_property(pid, p4cc, &p); + } else { + gf_filter_pid_set_property(pid, p4cc, &p); + } + if (prop_type==GF_PROP_STRING_LIST) { + p.value.string_list = NULL; + } + else if (prop_type==GF_PROP_UINT_LIST) { + p.value.uint_list.vals = NULL; + } + gf_props_reset_single(&p); + } else if (value) { + Bool reset_prop=GF_FALSE; + GF_PropertyValue p; + if (!strncmp(value, "bxml@", 5)) { + p = gf_props_parse_value(GF_PROP_DATA_NO_COPY, name, value, NULL, pid->filter->session->sep_list); + } else if (!strncmp(value, "file@", 5)) { + p = gf_props_parse_value(GF_PROP_STRING, name, value, NULL, pid->filter->session->sep_list); + p.type = GF_PROP_STRING_NO_COPY; + } else { + u32 ptype = GF_PROP_FORBIDEN; + char *type_sep = strchr(value, '@'); + if (type_sep) { + type_sep[0] = 0; + ptype = gf_props_parse_type(value); + if (ptype==GF_PROP_FORBIDEN) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Unrecognized property type %s, defaulting to string\n", value)); + } else { + value = type_sep+1; + } + type_sep[0] = '@'; + } + memset(&p, 0, sizeof(GF_PropertyValue)); + if (ptype == GF_PROP_FORBIDEN) { + p.type = GF_PROP_STRING; + p.value.string = value; + } else { + p = gf_props_parse_value(ptype, name, value, NULL, pid->filter->session->sep_list); + reset_prop = GF_TRUE; + } + } + gf_filter_pid_set_property_dyn(pid, name, &p); + if (reset_prop) gf_props_reset_single(&p); + } + if (value_next_list) + value_next_list[0] = filter->session->sep_list; + +skip_arg: + if (value_sep) + value_sep[0] = filter->session->sep_name; + + if (sep) { + sep[0] = filter->session->sep_args; + args=sep+1; + } else { + break; + } + } +} + +static void gf_filter_pid_set_args(GF_Filter *filter, GF_FilterPid *pid) +{ + char *args; + if (!filter->src_args && !filter->orig_args) return; + args = filter->orig_args ? filter->orig_args : filter->src_args; + + gf_filter_pid_set_args_internal(filter, pid, args, 0); + +} +static const char *gf_filter_last_id_in_chain(GF_Filter *filter, Bool ignore_first) +{ + u32 i; + const char *id; + if (!ignore_first) { + if (filter->id) return filter->id; + if (!filter->dynamic_filter) return NULL; + } + + for (i=0; inum_input_pids; i++) { + GF_FilterPidInst *pidi = gf_list_get(filter->input_pids, i); + if (pidi->pid->filter->id) return pidi->pid->filter->id; + //stop at first non dyn filter + if (!pidi->pid->filter->dynamic_filter) continue; + id = gf_filter_last_id_in_chain(pidi->pid->filter, GF_FALSE); + if (id) return id; + } + return NULL; +} + +void gf_filter_pid_retry_caps_negotiate(GF_FilterPid *src_pid, GF_FilterPid *pid, GF_Filter *dst_filter) +{ + assert(dst_filter); + src_pid->caps_negociate = pid->caps_negociate; + pid->caps_negociate = NULL; + src_pid->caps_dst_filter = dst_filter; + //blacklist filter for adaptation + if (!src_pid->adapters_blacklist) src_pid->adapters_blacklist = gf_list_new(); + gf_list_add(src_pid->adapters_blacklist, (void *) pid->filter->freg); + //once != 0 will trigger reconfiguration, so set this once all vars have been set + safe_int_inc(& src_pid->filter->nb_caps_renegociate ); + + //disconnect source pid from filter - this will unload the filter itself + gf_fs_post_task(src_pid->filter->session, gf_filter_pid_disconnect_task, pid->filter, src_pid, "pidinst_disconnect", NULL); +} + + +static Bool gf_filter_pid_needs_explicit_resolution(GF_FilterPid *pid, GF_Filter *dst) +{ + u32 i; + const GF_FilterCapability *caps; + u32 nb_caps; + Bool dst_has_raw_cid_in = GF_FALSE; + const GF_PropertyValue *stream_type = gf_filter_pid_get_property_first(pid, GF_PROP_PID_STREAM_TYPE); + if (!stream_type) return GF_TRUE; + + if (stream_type->value.uint==GF_STREAM_FILE) return GF_FALSE; + if (stream_type->value.uint==GF_STREAM_ENCRYPTED) { + stream_type = gf_filter_pid_get_property_first(pid, GF_PROP_PID_ORIG_STREAM_TYPE); + if (!stream_type) return GF_TRUE; + } + + caps = dst->forced_caps ? dst->forced_caps : dst->freg->caps; + nb_caps = dst->forced_caps ? dst->nb_forced_caps : dst->freg->nb_caps; + + for (i=0; iflags & GF_CAPFLAG_INPUT)) continue; + + if (cap->code != GF_PROP_PID_CODECID) continue; + if (cap->val.value.uint==GF_CODECID_RAW) + dst_has_raw_cid_in = GF_TRUE; + } + + + for (i=0; iflags & GF_CAPFLAG_INPUT)) continue; + + if (cap->code != GF_PROP_PID_STREAM_TYPE) continue; + //output type is file or same media type, allow looking for filter chains + if ((cap->val.value.uint==GF_STREAM_FILE) || (cap->val.value.uint==stream_type->value.uint)) return GF_FALSE; + //allow text|scene|video -> raw video for dynamic compositor + if (dst_has_raw_cid_in && (cap->val.value.uint==GF_STREAM_VISUAL)) { + switch (stream_type->value.uint) { + case GF_STREAM_TEXT: + case GF_STREAM_SCENE: + case GF_STREAM_OD: + return GF_FALSE; + default: + break; + } + } + } + //no mathing type found, we will need an explicit filter to solve this link (ie the link will be to the explicit filter) + return GF_TRUE; +} + +static void add_possible_link_destination(GF_List *possible_linked_resolutions, GF_Filter *filter_dst) +{ + u32 i; + + for (i=0; imax_extra_pids) continue; + + if ((gf_list_find(filter_dst->destination_links, parent)>=0) || (gf_list_find(filter_dst->destination_filters, parent)>=0)) { + gf_list_rem(possible_linked_resolutions, i); + gf_list_insert(possible_linked_resolutions, filter_dst, i); + return; + } + if ((gf_list_find(parent->destination_links, filter_dst)>=0) || (gf_list_find(parent->destination_filters, filter_dst)>=0)) { + return; + } + } + gf_list_add(possible_linked_resolutions, filter_dst); +} + +#if 0 +static void dump_pid_props(GF_FilterPid *pid) +{ + u32 idx = 0; + char szDump[GF_PROP_DUMP_ARG_SIZE]; + const GF_PropertyEntry *p; + GF_PropertyMap *pmap = gf_list_get(pid->properties, 0); + while (pmap && (p = gf_list_enum(pmap->properties, &idx))) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Pid prop %s: %s\n", gf_props_4cc_get_name(p->p4cc), gf_props_dump(p->p4cc, &p->prop, szDump, GF_FALSE) )); + } +} +#endif + + +static void gf_filter_pid_init_task(GF_FSTask *task) +{ + u32 i, count; + Bool found_dest=GF_FALSE; + Bool found_matching_sourceid; + Bool can_reassign_filter = GF_FALSE; + Bool can_try_link_resolution=GF_FALSE; + u32 num_pass=0; + GF_List *loaded_filters = NULL; + GF_List *linked_dest_filters = NULL; + GF_List *force_link_resolutions = NULL; + GF_List *possible_linked_resolutions = NULL; + GF_Filter *filter = task->filter; + GF_FilterPid *pid = task->pid; + GF_Filter *dynamic_filter_clone = NULL; + Bool filter_found_but_pid_excluded = GF_FALSE; + Bool ignore_source_ids = GF_FALSE; + const char *filter_id; + + if (pid->destroyed || pid->removed) { + assert(pid->init_task_pending); + safe_int_dec(&pid->init_task_pending); + return; + } + pid->props_changed_since_connect = GF_FALSE; + + //swap pid is pending on the possible destination filter + if (filter->swap_pidinst_src || filter->swap_pidinst_dst) { + task->requeue_request = GF_TRUE; + task->can_swap = GF_TRUE; + return; + } + if (filter->caps_negociate) { + if (! gf_filter_reconf_output(filter, pid)) + return; + } + + gf_fs_check_graph_load(filter->session, GF_TRUE); + + if (filter->user_pid_props) + gf_filter_pid_set_args(filter, pid); + + //since we may have inserted filters in the middle (demuxers typically), get the last explicitely + //loaded ID in the chain + filter_id = gf_filter_last_id_in_chain(filter, GF_FALSE); + if (!filter_id && filter->cloned_from) + filter_id = gf_filter_last_id_in_chain(filter->cloned_from, GF_FALSE); + + //we lock the instantiated filter list for the entire resolution process + if (filter->session->filters_mx) gf_mx_p(filter->session->filters_mx); + + linked_dest_filters = gf_list_new(); + force_link_resolutions = gf_list_new(); + possible_linked_resolutions = gf_list_new(); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s:%s init\n", pid->filter->name, pid->name)); + + //we use at max 3 passes: + //pass 1: try direct connections without loading intermediate filter chains. If PID gets connected, skip other passes + //pass 2: try loading intermediate filter chains, but disable filter register swapping. If PID gets connected, skip last + //pass 3: try loading intermediate filter chains, potentially swapping the source register +restart: + + if (num_pass) { + loaded_filters = gf_list_new(); + } + + found_matching_sourceid = GF_FALSE; + //try to connect pid to all running filters + count = gf_list_count(filter->session->filters); + for (i=0; isession->filters, i); + //source filter + if (!filter_dst->freg->configure_pid) continue; + if (filter_dst->finalized || filter_dst->removed || filter_dst->disabled || filter_dst->marked_for_removal || filter_dst->no_inputs) continue; + if (filter_dst->target_filter == pid->filter) continue; + + //we don't allow re-entrant filter registries (eg filter foo of type A output cannot connect to filter bar of type A) + if (pid->pid->filter->freg == filter_dst->freg) { + continue; + } + //we already linked to this one + if (gf_list_find(linked_dest_filters, filter_dst)>=0) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s already linked to filter %s\n", pid->name, filter_dst->name)); + continue; + } + if (gf_list_count(pid->filter->destination_filters)) { + s32 ours = gf_list_find(pid->filter->destination_filters, filter_dst); + if (ours<0) { + ours = num_pass ? gf_list_del_item(pid->filter->destination_links, filter_dst) : -1; + if (!filter_dst->source_ids && (ours<0)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s has destination filters, filter %s not one of them\n", pid->name, filter_dst->name)); + continue; + } + + pid->filter->dst_filter = NULL; + } else { + pid->filter->dst_filter = filter_dst; + } + } + + if (num_pass && gf_list_count(filter->destination_links)) { + s32 ours = gf_list_find(pid->filter->destination_links, filter_dst); + if (ours<0) { + ours = gf_list_find(possible_linked_resolutions, filter_dst); + if (ours<0) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s has destination links, filter %s not one of them\n", pid->name, filter_dst->name)); + continue; + } + } + pid->filter->dst_filter = NULL; + } + + if (gf_list_count(filter_dst->source_filters)) { + u32 j, count2 = gf_list_count(filter_dst->source_filters); + for (j=0; jsource_filters, j); + if (gf_filter_in_parent_chain(pid->filter, srcf)) { + ignore_source_ids = GF_TRUE; + break; + } + } + } + + //if destination accepts only one input and connected or connection pending + //note that if destination uses dynamic clone through source ids, we need to check this filter + if (!filter_dst->max_extra_pids + && !filter_dst->dynamic_source_ids + && (filter_dst->num_input_pids || filter_dst->in_pid_connection_pending) + && (!filter->swap_pidinst_dst || (filter->swap_pidinst_dst->filter != filter_dst)) + ) { + //not explicitly clonable, don't connect to it + if (!filter_dst->clonable) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s not clonable\n", filter_dst->name)); + continue; + } + + //explicitly clonable but caps don't match, don't connect to it + if (!gf_filter_pid_caps_match(pid, filter_dst->freg, NULL, NULL, NULL, pid->filter->dst_filter, -1)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s caps does not match clonable filter %s\n", pid->name, filter_dst->name)); + continue; + } + } + + if (gf_list_find(pid->filter->blacklisted, (void *) filter_dst->freg)>=0) continue; + + //we try to load a filter chain, so don't test against filters loaded for another chain + if (filter_dst->dynamic_filter && (filter_dst != pid->filter->dst_filter)) { + //dst was explicitely set and does not match + if (pid->filter->dst_filter) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s has explicit dest %s not %s\n", pid->name, pid->filter->dst_filter->name, filter_dst->name)); + continue; + } + //dst was not set, we may try to connect to this filter if it allows several input + //this is typically the case for muxers instantiated dynamically + if (!filter_dst->max_extra_pids) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s has explicit dest %s matching but no extra pid possible\n", pid->name, filter_dst->name)); + continue; + } + } + //pid->filter->dst_filter NULL and pid->filter->target_filter is not: we had a wrong resolved chain to target + //so only attempt to relink the chain if dst_filter is the expected target + if (!pid->filter->dst_filter && pid->filter->target_filter && (filter_dst != pid->filter->target_filter)) { + if (filter_dst->target_filter != pid->filter->target_filter) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s has target filter %s not matching %s->%s\n", pid->name, pid->filter->target_filter->name, filter_dst->name, filter_dst->target_filter ? filter_dst->target_filter->name : "null")); + continue; + } + //if the target filter of this filter is the same as ours, try to connect - typically scalable streams decoding + } + + //dynamic filters only connect to their destination, unless explicit connections through sources + //we could remove this but this highly complicates PID resolution + if (!filter_dst->source_ids && pid->filter->dynamic_filter && pid->filter->dst_filter && (filter_dst!=pid->filter->dst_filter)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s has dest filter %s not matching %s\n", pid->name, pid->filter->dst_filter->name, filter_dst->name)); + continue; + } + //walk up through the parent graph and check if this filter is already in. If so don't connect + //since we don't allow re-entrant PIDs + if (gf_filter_in_parent_chain(filter, filter_dst) ) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s has filter %s in its parent chain\n", pid->name, filter_dst->name)); + continue; + } + //if the original filter is in the parent chain of this PID's filter, don't connect (equivalent to re-entrant) + if (filter_dst->cloned_from) { + if (gf_filter_in_parent_chain(filter, filter_dst->cloned_from) ) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s has the original of cloned filter %s in its parent chain\n", pid->name, filter_dst->name)); + continue; + } + } + + //if the filter is in the parent chain of this PID's original filter, don't connect (equivalent to re-entrant) + if (filter->cloned_from) { + if (gf_filter_in_parent_chain(filter->cloned_from, filter_dst) ) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s filter is cloned and has filter %s in its clone parent chain\n", pid->name, filter_dst->name)); + continue; + } + } + + //if we have sourceID info on the destination, check them + needs_clone=GF_FALSE; + if (filter_id) { + if (filter_dst->source_ids) { + Bool pid_excluded=GF_FALSE; + if (!filter_source_id_match(pid, filter_id, filter_dst, &pid_excluded, &needs_clone)) { + Bool not_ours=GF_TRUE; + //if filter is a dynamic one with an ID set, fetch ID from previous filter in chain + //this is need for cases such as "-i source filterFoo @ -o live.mpd": + //the dasher filter will be dynamically loaded AND will also assign itself a filter ID + //due to internal filter (dasher) logic + //that dasher filter ID will be different from the ID assigned by '@' on dst file's sourceID, + //which is the filter ID of filterFoo + if (filter->dynamic_filter && filter->id) { + const char *src_filter_id = gf_filter_last_id_in_chain(filter, GF_TRUE); + if (filter_source_id_match(pid, src_filter_id, filter_dst, &pid_excluded, &needs_clone)) { + not_ours = GF_FALSE; + } + } + if (not_ours) { + if (pid_excluded && !num_pass) filter_found_but_pid_excluded = GF_TRUE; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s does not match source ID for filter %s\n", pid->name, filter_dst->name)); + continue; + } + } + } + //if no source ID on the dst filter, this means the dst filter accepts any possible connections from out filter + //unless prevented for this pid + else if (pid->require_source_id) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s requires source ID, not set for filter %s\n", pid->name, filter_dst->name)); + continue; + } + } + //no filterID and dst expects only specific filters, continue + else if (filter_dst->source_ids && !ignore_source_ids) { + Bool pid_excluded=GF_FALSE; + if ( (filter_dst->source_ids[0]!='*') && (filter_dst->source_ids[0]!=filter->session->sep_frag)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s does not match filter %s source ID\n", pid->name, filter_dst->name)); + continue; + } + if (!filter_source_id_match(pid, "*", filter_dst, &pid_excluded, &needs_clone)) { + if (pid_excluded && !num_pass) filter_found_but_pid_excluded = GF_TRUE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s is excluded by filter %s source ID\n", pid->name, filter_dst->name)); + continue; + } + } + if (needs_clone) { + //remember this filter as clonable (dynamic source id scheme) if none yet found. + //If we had a matching sourceID, clone is not needed + if (!num_pass && !dynamic_filter_clone && !found_matching_sourceid) { + dynamic_filter_clone = filter_dst; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s needs cloning of filter %s\n", pid->name, filter_dst->name)); + continue; + } else if (dynamic_filter_clone && dynamic_filter_clone->freg==filter_dst->freg) { + dynamic_filter_clone = NULL; + } + //remember we had a sourceid match + found_matching_sourceid = GF_TRUE; + + can_try_link_resolution = GF_TRUE; + + //this is the right destination filter. We however need to check if we don't have a possible destination link + //whose destination is this destination (typically mux > fout/pipe/sock case). If that's the case, swap the destination + //filter with the intermediate node before matching caps and resolving link + if (num_pass) { + u32 k, alt_count = gf_list_count(possible_linked_resolutions); + for (k=0; k decoder1 -> compositor + //PID2(PNG) -> decoder2 -> compositor + //However this algo would force a connection of PID2 to decoder1 if decoder1 accepts multiple input, regardless of PID2 caps + if (! (adest->freg->flags & GF_FS_REG_DYNAMIC_REDIRECT)) + continue; + if ((gf_list_find(adest->destination_filters, filter_dst)>=0) || (gf_list_find(adest->destination_links, filter_dst)>=0) ) { + filter_dst = adest; + gf_list_rem(possible_linked_resolutions, k); + break; + } + } + } + + + //we have a match, check if caps are OK + cap_matched = gf_filter_pid_caps_match(pid, filter_dst->freg, filter_dst, NULL, NULL, pid->filter->dst_filter, -1); + + //if clonable filter and no match, check if we would match the caps without caps override of dest + //note we don't do this on sources for the time being, since this might trigger undesired resolution of file->file + if (!cap_matched && filter_dst->clonable && pid->filter->num_input_pids) { + cap_matched = gf_filter_pid_caps_match(pid, filter_dst->freg, NULL, NULL, NULL, pid->filter->dst_filter, -1); + } + + if (!cap_matched) { + Bool skipped = GF_FALSE; + Bool reassigned=GF_FALSE; + GF_Filter *new_f; + + //we don't load filter chains if we have a change of media type from anything except file to anything except file + //i.e. transmodality (eg video->audio) can only be done through explicit filters + if (gf_filter_pid_needs_explicit_resolution(pid, filter_dst)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s needs explicit resolution for linking to filter %s\n", pid->name, filter_dst->name)); + continue; + } + + //we had a destination set during link resolve, and we don't match that filter, consider the link resolution wrong + if (pid->filter->dst_filter && (filter_dst == pid->filter->dst_filter)) { + GF_Filter *old_dst = pid->filter->dst_filter; + pid->filter->dst_filter = NULL; + gf_list_del_item(pid->filter->destination_links, filter_dst); + gf_list_del_item(pid->filter->destination_filters, filter_dst); + //nobody using this filter, destroy + if (old_dst->dynamic_filter + && !old_dst->has_pending_pids + && !old_dst->num_input_pids + && !old_dst->out_pid_connection_pending + ) { + gf_filter_post_remove(old_dst); + } + } + if (!num_pass) { + //we have an explicit link instruction so we must try dynamic link even if we connect to another filter + if (filter_dst->source_ids) { + gf_list_add(force_link_resolutions, filter_dst); + } else { + //register as possible destination link. If a filter already registered is a destination of this possible link + //only the possible link will be kept + add_possible_link_destination(possible_linked_resolutions, filter_dst); + } + continue; + } + filter_found_but_pid_excluded = GF_FALSE; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Attempting to solve link between PID %s and filter %s\n", pid->name, filter_dst->name)); + + if (num_pass==1) reassigned = GF_TRUE; + else reassigned = GF_FALSE; + //we pass the list of loaded filters for this pid, so that we don't instanciate twice the same chain start + new_f = gf_filter_pid_resolve_link_check_loaded(pid, filter_dst, &reassigned, loaded_filters, &skipped); + + //try to load filters + if (! new_f) { + if (skipped) { + continue; + } + if (pid->filter->session->run_status!=GF_OK) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PID %s:%s init canceled (session abort)\n", pid->filter->name, pid->name)); + if (filter->session->filters_mx) gf_mx_v(filter->session->filters_mx); + assert(pid->init_task_pending); + safe_int_dec(&pid->init_task_pending); + if (loaded_filters) gf_list_del(loaded_filters); + gf_list_del(linked_dest_filters); + gf_list_del(force_link_resolutions); + gf_list_del(possible_linked_resolutions); + return; + } + + //filter was reassigned (pid is destroyed), return + if (reassigned) { + if (num_pass==1) { + can_reassign_filter = GF_TRUE; + continue; + } + if (filter->session->filters_mx) gf_mx_v(filter->session->filters_mx); + assert(pid->init_task_pending); + safe_int_dec(&pid->init_task_pending); + if (loaded_filters) gf_list_del(loaded_filters); + gf_list_del(linked_dest_filters); + gf_list_del(force_link_resolutions); + gf_list_del(possible_linked_resolutions); + return; + } + //we might had it wrong solving the chain initially, break the chain + if (filter_dst->dynamic_filter && filter_dst->dst_filter) { + GF_Filter *new_dst = filter_dst; + while (new_dst->dst_filter && new_dst->dynamic_filter) { + GF_Filter *f = new_dst; + new_dst = new_dst->dst_filter; + if (!f->num_input_pids && !f->num_output_pids && !f->in_pid_connection_pending) { + gf_filter_post_remove(f); + } + } + + pid->filter->dst_filter = NULL; + new_f = gf_filter_pid_resolve_link(pid, new_dst, &reassigned); + if (!new_f) { + if (reassigned) { + if (filter->session->filters_mx) gf_mx_v(filter->session->filters_mx); + assert(pid->init_task_pending); + safe_int_dec(&pid->init_task_pending); + if (loaded_filters) gf_list_del(loaded_filters); + gf_list_del(linked_dest_filters); + gf_list_del(force_link_resolutions); + gf_list_del(possible_linked_resolutions); + return; + } else { + continue; + } + } + //good to go ! + } else { + continue; + } + } + gf_list_del_item(filter->destination_links, filter_dst); + filter_dst = new_f; + gf_list_add(loaded_filters, new_f); + } + + assert(pid->pid->filter->freg != filter_dst->freg); + + safe_int_inc(&pid->filter->out_pid_connection_pending); + gf_filter_pid_post_connect_task(filter_dst, pid); + + found_dest = GF_TRUE; + gf_list_add(linked_dest_filters, filter_dst); + gf_list_del_item(filter->destination_links, filter_dst); + /*we are linking to a mux, check all destination filters registered with the muxer and remove them from our possible destination links*/ + if (filter_dst->max_extra_pids) { + u32 k=0; + for (k=0; kdestination_filters); k++) { + GF_Filter *dst_f = gf_list_get(filter_dst->destination_filters, k); + gf_list_del_item(filter->destination_links, dst_f); + } + } + } + + if (!num_pass) { + //cleanup forced link resolution + for (i=0; i< gf_list_count(linked_dest_filters); i++) { + GF_Filter *filter_dst = gf_list_get(linked_dest_filters, i); + u32 k=0; + for (k=0; kdestination_filters, dst_link)>=0) + || (gf_list_find(filter_dst->destination_links, dst_link)>=0) + //if forced filter's target is the same as what we connected to, don't force a link + || (dst_link->target_filter == filter_dst) + ) { + gf_list_rem(force_link_resolutions, k); + k--; + } + } + } + } + + if (loaded_filters) { + gf_list_del(loaded_filters); + loaded_filters = NULL; + } + + //we still have possible destination links and we can try link resolution, do it + if (!num_pass && gf_list_count(filter->destination_links) && can_try_link_resolution && filter->session->max_resolve_chain_len) { + num_pass = 1; + goto restart; + } + //we must do the second pass if a filter has an explicit link set through source ID + if (!num_pass && gf_list_count(force_link_resolutions)) { + num_pass = 1; + goto restart; + } + + //connection task posted, nothing left to do + if (found_dest) { + assert(pid->init_task_pending); + safe_int_dec(&pid->init_task_pending); + if (filter->session->filters_mx) gf_mx_v(filter->session->filters_mx); + pid->filter->disabled = GF_FALSE; + gf_list_del(linked_dest_filters); + gf_list_del(force_link_resolutions); + gf_list_del(possible_linked_resolutions); + gf_fs_check_graph_load(filter->session, GF_FALSE); + return; + } + + //on first pass, if we found a clone (eg a filter using freg:#PropName=*), instantiate this clone and redo the pid linking to this clone (last entry in the filter list) + if (dynamic_filter_clone && !num_pass) { + GF_Filter *clone = gf_filter_clone(dynamic_filter_clone); + if (clone) { + assert(dynamic_filter_clone->dynamic_source_ids); + gf_free(clone->source_ids); + clone->source_ids = gf_strdup(dynamic_filter_clone->dynamic_source_ids); + clone->cloned_from = NULL; + count = gf_list_count(filter->session->filters); + gf_list_add(pid->filter->destination_links, clone); + i = count-1; + num_pass = 1; + goto single_retry; + } + } + + //nothing found, redo a pass, this time allowing for link resolve + if (!num_pass && can_try_link_resolution && filter->session->max_resolve_chain_len) { + num_pass = 1; + goto restart; + } + if ((num_pass==1) && can_reassign_filter) { + if (filter->session->flags & GF_FS_FLAG_NO_REASSIGN) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("PID %s in filter %s not connected, source reassignment was possible but is disabled\n", pid->name, pid->filter->name)); + } else { + num_pass = 2; + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("PID %s in filter %s not connected to any loaded filter, trying source reassignment\n", pid->name, pid->filter->name)); + goto restart; + } + } + + gf_fs_check_graph_load(filter->session, GF_FALSE); + + gf_list_del(linked_dest_filters); + gf_list_del(force_link_resolutions); + gf_list_del(possible_linked_resolutions); + if (filter->session->filters_mx) gf_mx_v(filter->session->filters_mx); + + + filter->num_out_pids_not_connected ++; + + GF_FilterEvent evt; + if (filter_found_but_pid_excluded) { + //PID was not included in explicit connection lists + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("PID %s in filter %s not connected to any loaded filter due to source directives\n", pid->name, pid->filter->name)); + pid->not_connected = 1; + } else { + //no filter found for this pid ! + if (!pid->not_connected_ok && (filter->session->flags & GF_FS_FLAG_FULL_LINK) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("No filter chain found for PID %s in filter %s to any loaded filters - ABORTING!\n", pid->name, pid->filter->name)); + filter->session->last_connect_error = GF_FILTER_NOT_FOUND; + filter->session->run_status = GF_FILTER_NOT_FOUND; + filter->session->in_final_flush = GF_TRUE; + assert(pid->init_task_pending); + safe_int_dec(&pid->init_task_pending); + return; + } + + GF_LOG(pid->not_connected_ok ? GF_LOG_DEBUG : GF_LOG_WARNING, GF_LOG_FILTER, ("No filter chain found for PID %s in filter %s to any loaded filters - NOT CONNECTED\n", pid->name, pid->filter->name)); + if (pid->filter->freg->process_event) { + GF_FEVT_INIT(evt, GF_FEVT_CONNECT_FAIL, pid); + pid->filter->freg->process_event(filter, &evt); + } + pid->not_connected = 1; + } + GF_FEVT_INIT(evt, GF_FEVT_PLAY, pid); + gf_filter_pid_send_event_internal(pid, &evt, GF_TRUE); + + GF_FEVT_INIT(evt, GF_FEVT_STOP, pid); + gf_filter_pid_send_event_internal(pid, &evt, GF_TRUE); + + gf_filter_pid_set_eos(pid); + if (!pid->not_connected_ok && !(pid->filter->freg->flags & GF_FS_REG_DYNAMIC_PIDS ) && (pid->filter->num_out_pids_not_connected == pid->filter->num_output_pids)) { + pid->filter->disabled = GF_TRUE; + + if (can_reassign_filter) { + gf_filter_setup_failure(pid->filter, GF_FILTER_NOT_FOUND); + } + } + + if (!filter_found_but_pid_excluded && !pid->not_connected_ok && !filter->session->max_resolve_chain_len) { + filter->session->last_connect_error = GF_FILTER_NOT_FOUND; + } + + assert(pid->init_task_pending); + safe_int_dec(&pid->init_task_pending); + return; +} + +void gf_filter_pid_post_connect_task(GF_Filter *filter, GF_FilterPid *pid) +{ + assert(pid->pid); + assert(pid->filter != filter); + assert(pid->filter->freg != filter->freg); + assert(filter->freg->configure_pid); + safe_int_inc(&filter->session->pid_connect_tasks_pending); + safe_int_inc(&filter->in_pid_connection_pending); + gf_fs_post_task_ex(filter->session, gf_filter_pid_connect_task, filter, pid, "pid_connect", NULL, GF_TRUE, GF_FALSE); +} + + +void gf_filter_pid_post_init_task(GF_Filter *filter, GF_FilterPid *pid) +{ + if (pid->init_task_pending) return; + + safe_int_inc(&pid->init_task_pending); + gf_fs_post_task(filter->session, gf_filter_pid_init_task, filter, pid, "pid_init", NULL); +} + +GF_EXPORT +GF_Err gf_filter_pid_set_framing_mode(GF_FilterPid *pid, Bool requires_full_blocks) +{ + GF_FilterPidInst *pidinst = (GF_FilterPidInst *)pid; + + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to set framing info on an output pid in filter %s\n", pid->filter->name)); + return GF_BAD_PARAM; + } + pidinst->requires_full_data_block = requires_full_blocks; + return GF_OK; +} + +GF_EXPORT +GF_FilterPid *gf_filter_pid_new(GF_Filter *filter) +{ + char szName[30]; + GF_FilterPid *pid; + GF_SAFEALLOC(pid, GF_FilterPid); + if (!pid) return NULL; + pid->filter = filter; + pid->destinations = gf_list_new(); + pid->properties = gf_list_new(); + if (!filter->output_pids) filter->output_pids = gf_list_new(); + gf_mx_p(filter->tasks_mx); + gf_list_add(filter->output_pids, pid); + filter->num_output_pids = gf_list_count(filter->output_pids); + gf_mx_v(filter->tasks_mx); + pid->pid = pid; + pid->playback_speed_scaler = GF_FILTER_SPEED_SCALER; + + sprintf(szName, "PID%d", filter->num_output_pids); + pid->name = gf_strdup(szName); + + filter->has_pending_pids = GF_TRUE; + gf_fq_add(filter->pending_pids, pid); + + //by default copy properties if only one input pid + if (filter->num_input_pids==1) { + GF_FilterPid *pidi = gf_list_get(filter->input_pids, 0); + gf_filter_pid_copy_properties(pid, pidi); + } + return pid; +} + +void gf_filter_pid_del(GF_FilterPid *pid) +{ + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s pid %s destruction (%p)\n", pid->filter->name, pid->name, pid)); + while (gf_list_count(pid->destinations)) { + gf_filter_pid_inst_del(gf_list_pop_back(pid->destinations)); + } + gf_list_del(pid->destinations); + + while (gf_list_count(pid->properties)) { + GF_PropertyMap *prop = gf_list_pop_back(pid->properties); + assert(prop->reference_count); + if (safe_int_dec(&prop->reference_count) == 0) { + gf_props_del(prop); + } + } + gf_list_del(pid->properties); + + if(pid->caps_negociate) { + assert(pid->caps_negociate->reference_count); + if (safe_int_dec(&pid->caps_negociate->reference_count) == 0) { + gf_props_del(pid->caps_negociate); + } + } + + if (pid->adapters_blacklist) + gf_list_del(pid->adapters_blacklist); + + if (pid->infos) { + assert(pid->infos->reference_count); + if (safe_int_dec(&pid->infos->reference_count) == 0) { + gf_props_del(pid->infos); + } + } + if (pid->name) gf_free(pid->name); + gf_free(pid); +} + +void gf_filter_pid_del_task(GF_FSTask *task) +{ + gf_filter_pid_del(task->pid); +} + +static GF_PropertyMap *check_new_pid_props(GF_FilterPid *pid, Bool merge_props) +{ + u32 i, nb_recf; + GF_PropertyMap *old_map; + GF_PropertyMap *map; + + //see \ref gf_filter_pid_merge_properties_internal for mutex + gf_mx_p(pid->filter->tasks_mx); + old_map = gf_list_last(pid->properties); + gf_mx_v(pid->filter->tasks_mx); + + pid->props_changed_since_connect = GF_TRUE; + if (old_map && !pid->request_property_map) { + return old_map; + } + map = gf_props_new(pid->filter); + if (!map) + return NULL; + //see \ref gf_filter_pid_merge_properties_internal for mutex + gf_mx_p(pid->filter->tasks_mx); + gf_list_add(pid->properties, map); + gf_mx_v(pid->filter->tasks_mx); + + pid->request_property_map = GF_FALSE; + pid->pid_info_changed = GF_FALSE; + + //when creating a new map, ref_count of old map is decremented + if (old_map) { + if (merge_props) + gf_props_merge_property(map, old_map, NULL, NULL); + + assert(old_map->reference_count); + if (safe_int_dec(&old_map->reference_count) == 0) { + //see \ref gf_filter_pid_merge_properties_internal for mutex + gf_mx_p(pid->filter->tasks_mx); + gf_list_del_item(pid->properties, old_map); + gf_mx_v(pid->filter->tasks_mx); + gf_props_del(old_map); + } + } + + //trick here: we may be reconfigured before any packet is being dispatched + //so we need to manually trigger reconfigure of outputs + nb_recf = 0; + for (i=0; inum_destinations; i++) { + GF_FilterPidInst *pidi = gf_list_get(pid->destinations, i); + if (!pidi->filter->process_task_queued) { + //remember the pid prop map to use + pidi->reconfig_pid_props = map; + nb_recf++; + } + } + if (nb_recf) + pid->filter->reconfigure_outputs = GF_TRUE; + return map; +} + +static GF_Err gf_filter_pid_set_property_full(GF_FilterPid *pid, u32 prop_4cc, const char *prop_name, char *dyn_name, const GF_PropertyValue *value, Bool is_info) +{ + GF_PropertyMap *map; + + if (PID_IS_INPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to write property on input PID in filter %s - ignoring\n", pid->filter->name)); + return GF_BAD_PARAM; + } + + //info property, do not request a new property map + if (is_info) { + if (value && (value->type==GF_PROP_POINTER)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to set info property of type pointer is forbidden (filter %s) - ignoring\n", pid->filter->name)); + return GF_BAD_PARAM; + } + map = pid->infos; + if (!map) { + map = pid->infos = gf_props_new(pid->filter); + } + pid->pid_info_changed = GF_TRUE; + } else { + //always merge properties + map = check_new_pid_props(pid, GF_TRUE); + } + if (!map) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("No properties for destination pid in filter %s, ignoring reset\n", pid->filter->name)); + return GF_OUT_OF_MEM; + } + if (value && (prop_4cc==GF_PROP_PID_TIMESCALE)) + map->timescale = value->value.uint; + + if (value && (prop_4cc == GF_PROP_PID_ID) && !pid->name) { + char szName[100]; + sprintf(szName, "PID%d", value->value.uint); + gf_filter_pid_set_name(pid, szName); + } + return gf_props_set_property(map, prop_4cc, prop_name, dyn_name, value); +} + +GF_EXPORT +GF_Err gf_filter_pid_set_property(GF_FilterPid *pid, u32 prop_4cc, const GF_PropertyValue *value) +{ + if (!prop_4cc) return GF_BAD_PARAM; + return gf_filter_pid_set_property_full(pid, prop_4cc, NULL, NULL, value, GF_FALSE); +} + +GF_EXPORT +GF_Err gf_filter_pid_set_property_str(GF_FilterPid *pid, const char *name, const GF_PropertyValue *value) +{ + if (!name) return GF_BAD_PARAM; + return gf_filter_pid_set_property_full(pid, 0, name, NULL, value, GF_FALSE); +} + +GF_EXPORT +GF_Err gf_filter_pid_set_property_dyn(GF_FilterPid *pid, char *name, const GF_PropertyValue *value) +{ + if (!name) return GF_BAD_PARAM; + return gf_filter_pid_set_property_full(pid, 0, NULL, name, value, GF_FALSE); +} + +GF_EXPORT +GF_Err gf_filter_pid_set_info(GF_FilterPid *pid, u32 prop_4cc, const GF_PropertyValue *value) +{ + if (!prop_4cc) return GF_BAD_PARAM; + return gf_filter_pid_set_property_full(pid, prop_4cc, NULL, NULL, value, GF_TRUE); +} + +GF_EXPORT +GF_Err gf_filter_pid_set_info_str(GF_FilterPid *pid, const char *name, const GF_PropertyValue *value) +{ + if (!name) return GF_BAD_PARAM; + return gf_filter_pid_set_property_full(pid, 0, name, NULL, value, GF_TRUE); +} + +GF_EXPORT +GF_Err gf_filter_pid_set_info_dyn(GF_FilterPid *pid, char *name, const GF_PropertyValue *value) +{ + if (!name) return GF_BAD_PARAM; + return gf_filter_pid_set_property_full(pid, 0, NULL, name, value, GF_TRUE); +} + +static GF_Err gf_filter_pid_negociate_property_full(GF_FilterPid *pid, u32 prop_4cc, const char *prop_name, char *dyn_name, const GF_PropertyValue *value) +{ + GF_FilterPidInst *pidi = (GF_FilterPidInst *) pid; + if (!prop_4cc) return GF_BAD_PARAM; + + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to negociate property on output PID in filter %s - ignoring\n", pid->filter->name)); + return GF_BAD_PARAM; + } + pid = pid->pid; + if (!pid->caps_negociate) { + assert(!pid->caps_negociate_pidi); + pid->caps_negociate = gf_props_new(pid->filter); + pid->caps_negociate_pidi = pidi; + //we start a new caps negotiation step, reset any blacklist on pid + if (pid->adapters_blacklist) { + gf_list_del(pid->adapters_blacklist); + pid->adapters_blacklist = NULL; + } + safe_int_inc(&pid->filter->nb_caps_renegociate); + } + //pid is end of stream of pid instance has packet pendings, we will need a new chain to adapt these packets formats + if (pid->has_seen_eos || gf_fq_count(pidi->packets)) { + gf_fs_post_task(pid->filter->session, gf_filter_renegociate_output_task, pid->filter, NULL, "filter renegociate", NULL); + } + return gf_props_set_property(pid->caps_negociate, prop_4cc, prop_name, dyn_name, value); +} + +GF_EXPORT +GF_Err gf_filter_pid_negociate_property(GF_FilterPid *pid, u32 prop_4cc, const GF_PropertyValue *value) +{ + if (!prop_4cc) return GF_BAD_PARAM; + return gf_filter_pid_negociate_property_full(pid, prop_4cc, NULL, NULL, value); +} + +GF_EXPORT +GF_Err gf_filter_pid_negociate_property_str(GF_FilterPid *pid, const char *name, const GF_PropertyValue *value) +{ + if (!name) return GF_BAD_PARAM; + return gf_filter_pid_negociate_property_full(pid, 0, name, NULL, value); +} + +GF_EXPORT +GF_Err gf_filter_pid_negociate_property_dyn(GF_FilterPid *pid, char *name, const GF_PropertyValue *value) +{ + if (!name) return GF_BAD_PARAM; + return gf_filter_pid_negociate_property_full(pid, 0, NULL, name, value); +} + + +static GF_PropertyMap *filter_pid_get_prop_map(GF_FilterPid *pid, Bool first_prop_if_output) +{ + if (PID_IS_INPUT(pid)) { + GF_FilterPidInst *pidi = (GF_FilterPidInst *) pid; + //first time we access the props, use the first entry in the property list + if (!pidi->props) { + //see \ref gf_filter_pid_merge_properties_internal for mutex + gf_mx_p(pid->filter->tasks_mx); + pidi->props = gf_list_get(pid->pid->properties, 0); + gf_mx_v(pid->filter->tasks_mx); + assert(pidi->props); + safe_int_inc(&pidi->props->reference_count); + } + return pidi->props; + } else { + GF_PropertyMap *res_map = NULL; + pid = pid->pid; + if (pid->local_props) return pid->local_props; + + //see \ref gf_filter_pid_merge_properties_internal for mutex + gf_mx_p(pid->filter->tasks_mx); + if (first_prop_if_output) + res_map = gf_list_get(pid->properties, 0); + else + res_map = gf_list_last(pid->properties); + gf_mx_v(pid->filter->tasks_mx); + return res_map; + } + return NULL; +} + +GF_EXPORT +const GF_PropertyValue *gf_filter_pid_get_property(GF_FilterPid *pid, u32 prop_4cc) +{ + GF_PropertyMap *map = filter_pid_get_prop_map(pid, GF_FALSE); + if (!map) + return NULL; + return gf_props_get_property(map, prop_4cc, NULL); +} + +const GF_PropertyValue *gf_filter_pid_get_property_first(GF_FilterPid *pid, u32 prop_4cc) +{ + GF_PropertyMap *map = filter_pid_get_prop_map(pid, GF_TRUE); + if (!map) + return NULL; + return gf_props_get_property(map, prop_4cc, NULL); +} + +GF_EXPORT +const GF_PropertyValue *gf_filter_pid_get_property_str(GF_FilterPid *pid, const char *prop_name) +{ + GF_PropertyMap *map = filter_pid_get_prop_map(pid, GF_FALSE); + if (!map) + return NULL; + return gf_props_get_property(map, 0, prop_name); +} + +const GF_PropertyValue *gf_filter_pid_get_property_str_first(GF_FilterPid *pid, const char *prop_name) +{ + GF_PropertyMap *map = filter_pid_get_prop_map(pid, GF_TRUE); + if (!map) + return NULL; + return gf_props_get_property(map, 0, prop_name); +} + +const GF_PropertyEntry *gf_filter_pid_get_property_entry(GF_FilterPid *pid, u32 prop_4cc) +{ + GF_PropertyMap *map = filter_pid_get_prop_map(pid, GF_FALSE); + if (!map) + return NULL; + return gf_props_get_property_entry(map, prop_4cc, NULL); +} + +GF_EXPORT +const GF_PropertyEntry *gf_filter_pid_get_property_entry_str(GF_FilterPid *pid, const char *prop_name) +{ + GF_PropertyMap *map = filter_pid_get_prop_map(pid, GF_FALSE); + if (!map) + return NULL; + return gf_props_get_property_entry(map, 0, prop_name); +} + +static const GF_PropertyValue *gf_filter_pid_get_info_internal(GF_FilterPid *pid, u32 prop_4cc, const char *prop_name, Bool first_call, GF_PropertyEntry **propentry) +{ + u32 i, count; + const GF_PropertyEntry *prop_ent = NULL; + GF_PropertyMap *map; + *propentry = NULL; + + if (first_call) { + gf_mx_p(pid->filter->session->info_mx); + } + map = filter_pid_get_prop_map(pid, GF_FALSE); + + if (map) { + prop_ent = gf_props_get_property_entry(map, prop_4cc, prop_name); + if (prop_ent) goto exit; + } + if (pid->pid->infos) { + prop_ent = gf_props_get_property_entry(pid->pid->infos, prop_4cc, prop_name); + if (prop_ent) goto exit; + } + if (PID_IS_OUTPUT(pid)) { + prop_ent = NULL; + goto exit; + } + pid = pid->pid; + if (pid->infos) { + prop_ent = gf_props_get_property_entry(pid->infos, prop_4cc, prop_name); + if (prop_ent) goto exit; + } + + count = gf_list_count(pid->filter->input_pids); + for (i=0; ifilter->input_pids, i); + if (!pidinst->pid) continue; + + prop = gf_filter_pid_get_info_internal((GF_FilterPid *)pidinst, prop_4cc, prop_name, GF_FALSE, propentry); + if (prop) { + prop_ent = *propentry; + goto exit; + } + } + prop_ent = NULL; + +exit: + if (first_call) { + gf_mx_v(pid->filter->session->info_mx); + } + if (!prop_ent) { + *propentry = NULL; + return NULL; + } + if (! (*propentry)) { + *propentry = (GF_PropertyEntry *) prop_ent; + safe_int_inc(&prop_ent->reference_count); + } + return &prop_ent->prop; +} + +GF_EXPORT +const GF_PropertyValue *gf_filter_pid_get_info(GF_FilterPid *pid, u32 prop_4cc, GF_PropertyEntry **propentry) +{ + if (!propentry) return NULL; + if (*propentry) { + gf_filter_release_property(*propentry); + *propentry = NULL; + } + return gf_filter_pid_get_info_internal(pid, prop_4cc, NULL, GF_TRUE, propentry); +} + +GF_EXPORT +const GF_PropertyValue *gf_filter_pid_get_info_str(GF_FilterPid *pid, const char *prop_name, GF_PropertyEntry **propentry) +{ + if (!propentry) return NULL; + if (*propentry) { + gf_filter_release_property(*propentry); + *propentry = NULL; + } + return gf_filter_pid_get_info_internal(pid, 0, prop_name, GF_TRUE, propentry); +} + +GF_EXPORT +const GF_PropertyValue *gf_filter_pid_enum_info(GF_FilterPid *pid, u32 *idx, u32 *prop_4cc, const char **prop_name) +{ + u32 i, count, cur_idx=0, nb_in_pid=0; + + if (PID_IS_OUTPUT(pid)) { + return NULL; + } + pid = pid->pid; + cur_idx = *idx; + if (pid->infos) { + cur_idx = *idx; + const GF_PropertyValue *prop = gf_props_enum_property(pid->infos, &cur_idx, prop_4cc, prop_name); + if (prop) { + *idx = cur_idx; + return prop; + } + nb_in_pid = cur_idx; + cur_idx = *idx - nb_in_pid; + } + + count = gf_list_count(pid->filter->input_pids); + for (i=0; ifilter->input_pids, i); + prop = gf_filter_pid_enum_info((GF_FilterPid *)pidinst, &sub_idx, prop_4cc, prop_name); + if (prop) { + *idx = nb_in_pid + sub_idx; + return prop; + } + nb_in_pid += sub_idx; + cur_idx = *idx - nb_in_pid; + } + return NULL; +} + + +static const GF_PropertyValue *gf_filter_get_info_internal(GF_Filter *filter, u32 prop_4cc, const char *prop_name, GF_PropertyEntry **propentry) +{ + u32 i, count; + const GF_PropertyValue *prop=NULL; + + gf_mx_p(filter->session->info_mx); + + //TODO avoid doing back and forth ... + count = gf_list_count(filter->output_pids); + for (i=0; ioutput_pids, i); + prop = gf_filter_pid_get_info_internal(pid, prop_4cc, prop_name, GF_FALSE, propentry); + if (prop) { + gf_mx_v(filter->session->info_mx); + return prop; + } + } + count = gf_list_count(filter->input_pids); + for (i=0; iinput_pids, i); + prop = gf_filter_pid_get_info_internal(pidinst->pid, prop_4cc, prop_name, GF_FALSE, propentry); + if (prop) { + gf_mx_v(filter->session->info_mx); + return prop; + } + } + gf_mx_v(filter->session->info_mx); + return NULL; +} + +GF_EXPORT +const GF_PropertyValue *gf_filter_get_info(GF_Filter *filter, u32 prop_4cc, GF_PropertyEntry **propentry) +{ + if (!propentry) return NULL; + if (*propentry) { + gf_filter_release_property(*propentry); + *propentry = NULL; + } + return gf_filter_get_info_internal(filter, prop_4cc, NULL, propentry); +} + +GF_EXPORT +const GF_PropertyValue *gf_filter_get_info_str(GF_Filter *filter, const char *prop_name, GF_PropertyEntry **propentry) +{ + if (!propentry) return NULL; + if (*propentry) { + gf_filter_release_property(*propentry); + *propentry = NULL; + } + return gf_filter_get_info_internal(filter, 0, prop_name, propentry); +} + +GF_EXPORT +void gf_filter_release_property(GF_PropertyEntry *propentry) +{ + if (propentry) { + gf_props_del_property(propentry); + } +} + +GF_Err gf_filter_pid_reset_properties(GF_FilterPid *pid) +{ + GF_PropertyMap *map; + + if (PID_IS_INPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to reset all properties on input PID in filter %s - ignoring\n", pid->filter->name)); + return GF_BAD_PARAM; + } + //don't merge properties, we will reset them anyway + map = check_new_pid_props(pid, GF_FALSE); + + if (!map) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("No properties for destination pid in filter %s, ignoring reset\n", pid->filter->name)); + return GF_OUT_OF_MEM; + } + gf_props_reset(map); + return GF_OK; + +} + +static GF_Err gf_filter_pid_merge_properties_internal(GF_FilterPid *dst_pid, GF_FilterPid *src_pid, gf_filter_prop_filter filter_prop, void *cbk, Bool is_merge) +{ + GF_PropertyMap *dst_props, *src_props, *old_dst_props=NULL; + if (PID_IS_INPUT(dst_pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to reset all properties on input PID in filter %s - ignoring\n", dst_pid->filter->name)); + return GF_BAD_PARAM; + } + if (is_merge) { + gf_mx_p(src_pid->filter->tasks_mx); + old_dst_props = gf_list_last(dst_pid->properties); + gf_mx_v(src_pid->filter->tasks_mx); + } + + //don't merge properties with old state we merge with source pid + dst_props = check_new_pid_props(dst_pid, GF_FALSE); + + if (!dst_props) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("No properties for destination pid in filter %s, ignoring reset\n", dst_pid->filter->name)); + return GF_OUT_OF_MEM; + } + src_pid = src_pid->pid; + //our list is not thread-safe, so we must lock the filter when destroying the props + //otherwise gf_list_last() (this caller) might use the last entry while another threads sets this last entry to NULL + gf_mx_p(src_pid->filter->tasks_mx); + src_props = gf_list_last(src_pid->properties); + gf_mx_v(src_pid->filter->tasks_mx); + if (!src_props) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("No properties for source pid in filter %s, ignoring merge\n", src_pid->filter->name)); + return GF_OK; + } + if (src_pid->name && !old_dst_props) + gf_filter_pid_set_name(dst_pid, src_pid->name); + + if (!is_merge) { + gf_props_reset(dst_props); + } else { + //we created a new map + if (old_dst_props && (old_dst_props!=dst_props)) { + GF_Err e = gf_props_merge_property(dst_props, old_dst_props, NULL, NULL); + if (e) return e; + } + } + return gf_props_merge_property(dst_props, src_props, filter_prop, cbk); +} + +GF_EXPORT +GF_Err gf_filter_pid_merge_properties(GF_FilterPid *dst_pid, GF_FilterPid *src_pid, gf_filter_prop_filter filter_prop, void *cbk ) +{ + return gf_filter_pid_merge_properties_internal(dst_pid, src_pid, filter_prop, cbk, GF_TRUE); +} +GF_EXPORT +GF_Err gf_filter_pid_copy_properties(GF_FilterPid *dst_pid, GF_FilterPid *src_pid) +{ + return gf_filter_pid_merge_properties_internal(dst_pid, src_pid, NULL, NULL, GF_FALSE); +} + +GF_EXPORT +u32 gf_filter_pid_get_packet_count(GF_FilterPid *pid) +{ + GF_FilterPidInst *pidinst = (GF_FilterPidInst *)pid; + if (PID_IS_OUTPUT(pid)) { + pidinst = gf_list_get(pid->destinations, 0); + if (! pidinst) return 0; + return gf_fq_count(pidinst->packets) - pidinst->nb_eos_signaled - pidinst->nb_clocks_signaled; + + } else { + if (pidinst->discard_packets) return 0; + return gf_fq_count(pidinst->packets) - pidinst->nb_eos_signaled - pidinst->nb_clocks_signaled; + } +} + +static Bool gf_filter_pid_filter_internal_packet(GF_FilterPidInst *pidi, GF_FilterPacketInstance *pcki) +{ + Bool is_internal = GF_FALSE; + u32 ctype = (pcki->pck->info.flags & GF_PCK_CMD_MASK); + if (ctype == GF_PCK_CMD_PID_EOS ) { + pcki->pid->is_end_of_stream = pcki->pid->pid->has_seen_eos ? GF_TRUE : GF_FALSE; + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Found EOS packet in PID %s in filter %s - eos %d\n", pidi->pid->name, pidi->filter->name, pcki->pid->pid->has_seen_eos)); + assert(pcki->pid->nb_eos_signaled); + safe_int_dec(&pcki->pid->nb_eos_signaled); + is_internal = GF_TRUE; + } else if (ctype == GF_PCK_CMD_PID_REM) { + gf_fs_post_task(pidi->filter->session, gf_filter_pid_disconnect_task, pidi->filter, pidi->pid, "pidinst_disconnect", NULL); + + is_internal = GF_TRUE; + } + ctype = (pcki->pck->info.flags & GF_PCK_CKTYPE_MASK) >> GF_PCK_CKTYPE_POS; + + if (ctype) { + if (pcki->pid->handles_clock_references) return GF_FALSE; + assert(pcki->pid->nb_clocks_signaled); + safe_int_dec(&pcki->pid->nb_clocks_signaled); + //signal destination + assert(!pcki->pid->filter->next_clock_dispatch_type || !pcki->pid->filter->num_output_pids); + + pcki->pid->filter->next_clock_dispatch = pcki->pck->info.cts; + pcki->pid->filter->next_clock_dispatch_timescale = pcki->pck->pid_props->timescale; + pcki->pid->filter->next_clock_dispatch_type = ctype; + + //keep clock values but only override clock type if no discontinuity is pending + pcki->pid->last_clock_value = pcki->pck->info.cts; + pcki->pid->last_clock_timescale = pcki->pck->pid_props->timescale; + if (pcki->pid->last_clock_type != GF_FILTER_CLOCK_PCR_DISC) + pcki->pid->last_clock_type = ctype; + + if (ctype == GF_FILTER_CLOCK_PCR_DISC) { + assert(pcki->pid->last_clock_type == GF_FILTER_CLOCK_PCR_DISC); + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Internal clock reference packet filtered - PID %s clock ref "LLU"/%d - type %d\n", pcki->pid->pid->name, pcki->pid->last_clock_value, pcki->pid->last_clock_timescale, pcki->pid->last_clock_type)); + //the following call to drop_packet will trigger clock forwarding to all output pids + is_internal = GF_TRUE; + } + + if (is_internal) gf_filter_pid_drop_packet((GF_FilterPid *)pidi); + return is_internal; +} + +GF_EXPORT +GF_FilterPacket *gf_filter_pid_get_packet(GF_FilterPid *pid) +{ + GF_FilterPacketInstance *pcki; + GF_FilterPidInst *pidinst = (GF_FilterPidInst *)pid; + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to fetch a packet on an output PID in filter %s\n", pid->filter->name)); + return NULL; + } + if (pidinst->discard_packets || pidinst->detach_pending) { + pidinst->filter->nb_pck_io++; + return NULL; + } + + pcki = (GF_FilterPacketInstance *)gf_fq_head(pidinst->packets); + //no packets + if (!pcki) { + if (pidinst->pid->filter->disabled) { + pidinst->is_end_of_stream = pidinst->pid->has_seen_eos = GF_TRUE; + } + if (!pidinst->is_end_of_stream && pidinst->pid->filter->would_block) + gf_filter_pid_check_unblock(pidinst->pid); + pidinst->filter->nb_pck_io++; + return NULL; + } + assert(pcki->pck); + + if (gf_filter_pid_filter_internal_packet(pidinst, pcki)) { + return gf_filter_pid_get_packet(pid); + } + pcki->pid->is_end_of_stream = GF_FALSE; + + if ( (pcki->pck->info.flags & GF_PCKF_PROPS_CHANGED) && !pcki->pid_props_change_done) { + GF_Err e; + Bool skip_props = GF_FALSE; + + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s PID %s property changed at this packet, triggering reconfigure\n", pidinst->pid->filter->name, pidinst->pid->name)); + pcki->pid_props_change_done = 1; + + //it may happen that: + //- the props are not set when querying the first packet (no prop queries on pid) + //- the new props are already set if filter_pid_get_property was queried before the first packet dispatch + if (pidinst->props) { + if (pidinst->force_reconfig || (pidinst->props != pcki->pck->pid_props)) { + //destroy if last occurence, removing it from pid as well + //only remove if last about to be destroyed, since we may have several pid instances consuming from this pid + assert(pidinst->props->reference_count); + if (safe_int_dec(& pidinst->props->reference_count) == 0) { + //see \ref gf_filter_pid_merge_properties_internal for mutex + gf_mx_p(pidinst->pid->filter->tasks_mx); + gf_list_del_item(pidinst->pid->properties, pidinst->props); + gf_mx_v(pidinst->pid->filter->tasks_mx); + gf_props_del(pidinst->props); + } + pidinst->force_reconfig = GF_FALSE; + //set new one + pidinst->props = pcki->pck->pid_props; + safe_int_inc( & pidinst->props->reference_count ); + } else { + //it may happen that pid_configure for destination was called after packet being dispatched, in + //which case we are already properly configured + skip_props = GF_TRUE; + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s PID %s was already configured with the last property set, ignoring reconfigure\n", pidinst->pid->filter->name, pidinst->pid->name)); + } + } + if (!skip_props) { + assert(pidinst->filter->freg->configure_pid); + //reset the blacklist whenever reconfiguring, since we may need to reload a new filter chain + //in which a previously blacklisted filter (failing (re)configure for previous state) could + //now work, eg moving from formatA to formatB then back to formatA + gf_list_reset(pidinst->filter->blacklisted); + + e = gf_filter_pid_configure(pidinst->filter, pidinst->pid, GF_PID_CONF_RECONFIG); + if (e != GF_OK) return NULL; + if (pidinst->pid->caps_negociate) + return NULL; + } + } + if ( (pcki->pck->info.flags & GF_PCKF_INFO_CHANGED) /* && !pcki->pid_info_change_done*/) { + Bool res=GF_FALSE; + + //it may happen that this filter pid is pulled from another thread than ours (eg audio callback), in which case + //we cannot go reentrant, we have to wait until the filter is not in use ... + if (pidinst->filter->freg->process_event && pidinst->filter->process_th_id && (pidinst->filter->process_th_id != gf_th_id()) ) { + return NULL; + } + pcki->pid_info_change_done = 1; + + if (pidinst->filter->freg->process_event) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_INFO_UPDATE, pid); + + //the following may fail when some filters use threading on their own + //FSESS_CHECK_THREAD(pidinst->filter) + res = pidinst->filter->freg->process_event(pidinst->filter, &evt); + } + + if (!res) { + pidinst->filter->pid_info_changed = GF_TRUE; + } + } + pidinst->last_pck_fetch_time = gf_sys_clock_high_res(); + + return (GF_FilterPacket *)pcki; +} + +GF_EXPORT +Bool gf_filter_pid_get_first_packet_cts(GF_FilterPid *pid, u64 *cts) +{ + GF_FilterPacketInstance *pcki; + GF_FilterPidInst *pidinst = (GF_FilterPidInst *)pid; + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to read packet CTS on an output PID in filter %s\n", pid->filter->name)); + return GF_FALSE; + } + if (pidinst->discard_packets) return GF_FALSE; + + pcki = (GF_FilterPacketInstance *)gf_fq_head(pidinst->packets); + //no packets + if (!pcki) { + return GF_FALSE; + } + assert(pcki->pck); + + if (gf_filter_pid_filter_internal_packet(pidinst, pcki)) { + return gf_filter_pid_get_first_packet_cts(pid, cts); + } + + if (pidinst->requires_full_data_block && !(pcki->pck->info.flags & GF_PCKF_BLOCK_END)) + return GF_FALSE; + *cts = pcki->pck->info.cts; + return GF_TRUE; +} + +GF_EXPORT +Bool gf_filter_pid_first_packet_is_empty(GF_FilterPid *pid) +{ + GF_FilterPacketInstance *pcki; + GF_FilterPidInst *pidinst = (GF_FilterPidInst *)pid; + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to read packet CTS on an output PID in filter %s\n", pid->filter->name)); + return GF_TRUE; + } + if (pidinst->discard_packets) return GF_TRUE; + + pcki = (GF_FilterPacketInstance *)gf_fq_head(pidinst->packets); + //no packets + if (!pcki) { + return GF_TRUE; + } + assert(pcki->pck); + + if (pcki->pck->info.flags & (GF_PCK_CMD_MASK|GF_PCK_CKTYPE_MASK)) { + return GF_TRUE; + } + if (pidinst->requires_full_data_block && !(pcki->pck->info.flags & GF_PCKF_BLOCK_END)) + return GF_TRUE; + return (pcki->pck->data_length || pcki->pck->frame_ifce) ? GF_FALSE : GF_TRUE; +} + + +static void gf_filter_pidinst_update_stats(GF_FilterPidInst *pidi, GF_FilterPacket *pck) +{ + u64 now = gf_sys_clock_high_res(); + u64 dec_time = now - pidi->last_pck_fetch_time; + if (pck->info.flags & GF_PCK_CMD_MASK) return; + if (!pidi->filter || pidi->pid->filter->removed) return; + + pidi->filter->nb_pck_processed++; + pidi->filter->nb_bytes_processed += pck->data_length; + + pidi->total_process_time += dec_time; + if (!pidi->nb_processed) { + pidi->first_frame_time = pidi->last_pck_fetch_time; + } + + pidi->nb_processed++; + if (pck->info.flags & GF_PCK_SAP_MASK) { + pidi->nb_sap_processed ++; + if (dec_time > pidi->max_sap_process_time) pidi->max_sap_process_time = dec_time; + pidi->total_sap_process_time += dec_time; + } + + if (dec_time > pidi->max_process_time) pidi->max_process_time = dec_time; + + if (pck->data_length) { + Bool has_ts = GF_TRUE; + u64 ts = (pck->info.dts != GF_FILTER_NO_TS) ? pck->info.dts : pck->info.cts; + if ((ts != GF_FILTER_NO_TS) && (pck->pid_props->timescale)) { + ts *= 1000000; + ts /= pck->pid_props->timescale; + } else { + has_ts = GF_FALSE; + } + + if (!pidi->cur_bit_size) { + pidi->stats_start_ts = ts; + pidi->stats_start_us = now; + pidi->cur_bit_size = 8*pck->data_length; + } else { + Bool flush_stats = GF_FALSE; + pidi->cur_bit_size += 8*pck->data_length; + + if (has_ts) { + if (pidi->stats_start_ts + 1000000 <= ts) flush_stats = GF_TRUE; + } else { + if (pidi->stats_start_us + 1000000 <= now) flush_stats = GF_TRUE; + } + + if (flush_stats) { + u64 rate; + u64 diff_t; + + if (has_ts) { + rate = pidi->cur_bit_size; + rate *= 1000000; + diff_t = ts - pidi->stats_start_ts; + if (!diff_t) diff_t = 1; + rate /= diff_t; + pidi->avg_bit_rate = (u32) rate; + if (pidi->avg_bit_rate > pidi->max_bit_rate) pidi->max_bit_rate = pidi->avg_bit_rate; + } + + rate = pidi->cur_bit_size; + rate *= 1000000; + diff_t = now - pidi->stats_start_us; + if (!diff_t) diff_t = 1; + rate /= diff_t; + pidi->avg_process_rate = (u32) rate; + if (pidi->avg_process_rate > pidi->max_process_rate) pidi->max_process_rate = pidi->avg_process_rate; + + //reset stats + pidi->cur_bit_size = 0; + } + } + } +} + +static void gf_filter_pidinst_reset_stats(GF_FilterPidInst *pidi) +{ + pidi->last_pck_fetch_time = 0; + pidi->stats_start_ts = 0; + pidi->stats_start_us = 0; + pidi->cur_bit_size = 0; + pidi->avg_bit_rate = 0; + pidi->max_bit_rate = 0; + pidi->avg_process_rate = 0; + pidi->max_process_rate = 0; + pidi->nb_processed = 0; + pidi->nb_sap_processed = 0; + pidi->total_process_time = 0; + pidi->total_sap_process_time = 0; + pidi->max_process_time = 0; + pidi->max_sap_process_time = 0; + pidi->first_frame_time = 0; +} + +GF_EXPORT +void gf_filter_pid_drop_packet(GF_FilterPid *pid) +{ +#ifdef GPAC_MEMORY_TRACKING + u32 prev_nb_allocs, prev_nb_reallocs, nb_allocs, nb_reallocs; +#endif + + u32 nb_pck=0; + GF_FilterPacket *pck=NULL; + GF_FilterPacketInstance *pcki; + GF_FilterPidInst *pidinst = (GF_FilterPidInst *)pid; + + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to discard a packet on an output PID in filter %s\n", pid->filter->name)); + return; + } + if (pidinst->filter) + pidinst->filter->nb_pck_io++; + + //remove pck instance + pcki = gf_fq_pop(pidinst->packets); + + if (!pcki) { + if (pidinst->filter && !pidinst->filter->finalized) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Attempt to discard a packet already discarded in filter %s\n", pid->filter->name)); + } + return; + } + + gf_rmt_begin(pck_drop, GF_RMT_AGGREGATE); + pck = pcki->pck; + //move to source pid + pid = pid->pid; + + gf_filter_pidinst_update_stats(pidinst, pck); + if (pck->info.cts!=GF_FILTER_NO_TS) { + pidinst->last_ts_drop.num = pck->info.cts; + pidinst->last_ts_drop.den = pck->pid_props->timescale; + } + + + //make sure we lock the tasks mutex before getting the packet count, otherwise we might end up with a wrong number of packets + //if one thread (the caller here) consumes one packet while the dispatching thread is still upddating the state for that pid + gf_mx_p(pid->filter->tasks_mx); + nb_pck = gf_fq_count(pidinst->packets); + + if (!nb_pck) { + safe_int64_sub(&pidinst->buffer_duration, pidinst->buffer_duration); + } else if (pck->info.duration && (pck->info.flags & GF_PCKF_BLOCK_START) && pck->pid_props->timescale) { + s64 d = ((u64)pck->info.duration) * 1000000; + d /= pck->pid_props->timescale; + if (d > pidinst->buffer_duration) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Corrupted buffer level in PID instance %s (%s -> %s), droping packet duration "LLD" us greater than buffer duration "LLU" us\n", pid->name, pid->filter->name, pidinst->filter ? pidinst->filter->name : "disconnected", d, pidinst->buffer_duration)); + d = pidinst->buffer_duration; + } + assert(d <= pidinst->buffer_duration); + safe_int64_sub(&pidinst->buffer_duration, (s32) d); + } + + if ( (pid->num_destinations==1) || (pid->filter->session->blocking_mode==GF_FS_NOBLOCK_FANOUT)) { + if (nb_pcknb_buffer_unit) { + pid->nb_buffer_unit = nb_pck; + } + if (!pid->buffer_duration || (pidinst->buffer_duration < (s64) pid->buffer_duration)) { + pid->buffer_duration = pidinst->buffer_duration; + } + } + //handle fan-out: we must browse all other pid instances and compute max buffer/nb_pck per pids + //so that we don't unblock the PID if some instance is still blocking + else { + u32 i; + u32 min_pck = nb_pck; + s64 min_dur = pidinst->buffer_duration; + for (i=0; inum_destinations; i++) { + GF_FilterPidInst *a_pidi = gf_list_get(pid->destinations, i); + if (a_pidi==pidinst) continue; + if (a_pidi->buffer_duration > min_dur) + min_dur = a_pidi->buffer_duration; + nb_pck = gf_fq_count(a_pidi->packets); + if (nb_pck>min_pck) + min_pck = nb_pck; + } + pid->buffer_duration = min_dur; + pid->nb_buffer_unit = min_pck; + } + gf_filter_pid_check_unblock(pid); + + gf_mx_v(pid->filter->tasks_mx); + +#ifndef GPAC_DISABLE_LOG + if (gf_log_tool_level_on(GF_LOG_FILTER, GF_LOG_DEBUG)) { + u8 sap_type = (pck->info.flags & GF_PCK_SAP_MASK) >> GF_PCK_SAP_POS; + Bool seek_flag = pck->info.flags & GF_PCKF_SEEK; + + if ((pck->info.dts != GF_FILTER_NO_TS) && (pck->info.cts != GF_FILTER_NO_TS) ) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s (%s) drop packet DTS "LLU" CTS "LLU" SAP %d Seek %d - %d packets remaining buffer "LLU" us\n", pidinst->filter ? pidinst->filter->name : "disconnected", pid->name, pid->filter->name, pck->info.dts, pck->info.cts, sap_type, seek_flag, nb_pck, pidinst->buffer_duration)); + } else if ((pck->info.cts != GF_FILTER_NO_TS) ) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s (%s) drop packet CTS "LLU" SAP %d Seek %d - %d packets remaining buffer "LLU" us\n", pidinst->filter ? pidinst->filter->name : "disconnected", pid->name, pid->filter->name, pck->info.cts, sap_type, seek_flag, nb_pck, pidinst->buffer_duration)); + } else if ((pck->info.dts != GF_FILTER_NO_TS) ) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s (%s) drop packet DTS "LLU" SAP %d Seek %d - %d packets remaining buffer "LLU" us\n", pidinst->filter ? pidinst->filter->name : "disconnected", pid->name, pid->filter->name, pck->info.dts, sap_type, seek_flag, nb_pck, pidinst->buffer_duration)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s (%s) drop packet SAP %d Seek %d - %d packets remaining buffer "LLU" us\n", pidinst->filter ? pidinst->filter->name : "disconnected", pid->name, pid->filter->name, sap_type, seek_flag, nb_pck, pidinst->buffer_duration)); + } + } +#endif + + //destroy pcki + pcki->pck = NULL; + pcki->pid = NULL; + +#ifdef GPAC_MEMORY_TRACKING + if (pid->filter && pid->filter->session->check_allocs) { + gf_mem_get_stats(&prev_nb_allocs, NULL, &prev_nb_reallocs, NULL); + } +#endif + + if (pid->filter->pcks_inst_reservoir) { + gf_fq_add(pid->filter->pcks_inst_reservoir, pcki); + } else { + gf_free(pcki); + } + //unref pck + assert(pck->reference_count); + if (safe_int_dec(&pck->reference_count) == 0) { + gf_filter_packet_destroy(pck); + } + +#ifdef GPAC_MEMORY_TRACKING + if (pid->filter && pid->filter->session->check_allocs) { + gf_mem_get_stats(&nb_allocs, NULL, &nb_reallocs, NULL); + + pid->filter->session->nb_alloc_pck += (nb_allocs - prev_nb_allocs); + pid->filter->session->nb_realloc_pck += (nb_reallocs - prev_nb_reallocs); + } +#endif + + //decrement number of pending packet on target filter if this is not a destroy + if (pidinst->filter) { + assert(pidinst->filter->pending_packets); + safe_int_dec(&pidinst->filter->pending_packets); + + gf_filter_forward_clock(pidinst->filter); + } + + gf_rmt_end(); +} + +GF_EXPORT +Bool gf_filter_pid_is_eos(GF_FilterPid *pid) +{ + GF_FilterPacketInstance *pcki; + GF_FilterPidInst *pidi = (GF_FilterPidInst *)pid; + + if (pidi->detach_pending) + return GF_FALSE; + + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to query EOS on output PID %s in filter %s\n", pid->pid->name, pid->filter->name)); + return GF_FALSE; + } + if (!pid->pid->has_seen_eos && !pidi->discard_inputs && !pidi->discard_packets) { + ((GF_FilterPidInst *)pid)->is_end_of_stream = GF_FALSE; + return GF_FALSE; + } + //peek next for eos + pcki = (GF_FilterPacketInstance *)gf_fq_head(pidi->packets); + if (pcki) + gf_filter_pid_filter_internal_packet(pidi, pcki); + + if (pidi->discard_packets) return GF_FALSE; + if (!pidi->is_end_of_stream) return GF_FALSE; + if (!pidi->filter->eos_probe_state) + pidi->filter->eos_probe_state = 1; + return GF_TRUE; +} + +GF_EXPORT +void gf_filter_pid_set_eos(GF_FilterPid *pid) +{ + GF_FilterPacket *pck; + + if (PID_IS_INPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to signal EOS on input PID %s in filter %s\n", pid->pid->name, pid->filter->name)); + return; + } + if (pid->has_seen_eos) return; + + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("EOS signaled on PID %s in filter %s\n", pid->name, pid->filter->name)); + //we create a fake packet for eos signaling + pck = gf_filter_pck_new_shared_internal(pid, NULL, 0, NULL, GF_TRUE); + gf_filter_pck_set_framing(pck, GF_TRUE, GF_TRUE); + pck->pck->info.flags |= GF_PCK_CMD_PID_EOS; + gf_filter_pck_send(pck); +} + +GF_EXPORT +const GF_PropertyValue *gf_filter_pid_enum_properties(GF_FilterPid *pid, u32 *idx, u32 *prop_4cc, const char **prop_name) +{ + GF_PropertyMap *props; + + if (PID_IS_INPUT(pid)) { + gf_mx_p(pid->filter->tasks_mx); + props = gf_list_last(pid->pid->properties); + gf_mx_v(pid->filter->tasks_mx); + } else { + //props = check_new_pid_props(pid, GF_FALSE); + + gf_mx_p(pid->filter->tasks_mx); + props = gf_list_last(pid->properties); + gf_mx_v(pid->filter->tasks_mx); + } + if (!props) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("No properties for pid in filter %s, ignoring enum\n", pid->filter->name)); + *idx = 0xFFFFFFFF; + return NULL; + } + return gf_props_enum_property(props, idx, prop_4cc, prop_name); +} + +GF_EXPORT +Bool gf_filter_pid_would_block(GF_FilterPid *pid) +{ + Bool would_block=GF_FALSE; + Bool blockmode_broken=GF_FALSE; + + if (PID_IS_INPUT(pid)) { + return GF_FALSE; + } + + if (pid->filter->session->blocking_mode==GF_FS_NOBLOCK) + return GF_FALSE; + + gf_mx_p(pid->filter->tasks_mx); + //either block according to the number of dispatched units (decoder output) or to the requested buffer duration + if (pid->max_buffer_unit) { + if (pid->nb_buffer_unit * GF_FILTER_SPEED_SCALER >= pid->max_buffer_unit * pid->playback_speed_scaler) { + would_block = GF_TRUE; + } + if ((pid->num_destinations==1) && !pid->filter->blockmode_broken && ( (pid->nb_buffer_unit * GF_FILTER_SPEED_SCALER > 100 * pid->max_buffer_unit * pid->playback_speed_scaler) ) ) { + blockmode_broken = GF_TRUE; + } + } else if (pid->max_buffer_time) { + if (pid->buffer_duration * GF_FILTER_SPEED_SCALER > pid->max_buffer_time * pid->playback_speed_scaler) { + would_block = GF_TRUE; + } + if ((pid->num_destinations==1) && !pid->filter->blockmode_broken && (pid->buffer_duration * GF_FILTER_SPEED_SCALER > 100 * pid->max_buffer_time * pid->playback_speed_scaler) ) { + blockmode_broken = GF_TRUE; + } + } + if (blockmode_broken) { + //don't throw a warning since some filters may dispatch a large burst of packets (eg isom muxer) + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s block mode not respected: %u units "LLU" us vs %u max units "LLU" max buffer\n", pid->pid->filter->name, pid->pid->name, pid->nb_buffer_unit, pid->buffer_duration, pid->max_buffer_unit, pid->max_buffer_time)); + + pid->filter->blockmode_broken = GF_TRUE; + } + + if (would_block && !pid->would_block) { + safe_int_inc(&pid->would_block); + safe_int_inc(&pid->filter->would_block); + assert(pid->filter->would_block + pid->filter->num_out_pids_not_connected <= pid->filter->num_output_pids); + +#ifndef GPAC_DISABLE_LOG + if (gf_log_tool_level_on(GF_LOG_FILTER, GF_LOG_DEBUG)) { + if (pid->max_buffer_unit) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s blocked (%d units vs %d max units) - %d filter PIDs blocked\n", pid->pid->filter->name, pid->pid->name, pid->nb_buffer_unit, pid->max_buffer_unit, pid->filter->would_block)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter %s PID %s blocked ("LLU" us vs "LLU" max buffer) - %d filter PIDs blocked\n", pid->pid->filter->name, pid->pid->name, pid->buffer_duration, pid->max_buffer_time, pid->filter->would_block)); + } + } +#endif + } + assert(pid->filter->would_block <= pid->filter->num_output_pids); + gf_mx_v(pid->filter->tasks_mx); + return would_block; +} + +GF_EXPORT +u64 gf_filter_pid_query_buffer_duration(GF_FilterPid *pid, Bool check_pid_full) +{ + u32 count, i, j; + u64 duration=0; + if (!pid || pid->filter->session->in_final_flush) + return GF_FILTER_NO_TS; + + if (PID_IS_INPUT(pid)) { + GF_Filter *filter; + GF_FilterPidInst *pidinst = (GF_FilterPidInst *)pid; + if (!pidinst->pid) return 0; + filter = pidinst->pid->filter; + if (check_pid_full) { + Bool buffer_full = GF_FALSE; + + if (pidinst->pid->max_buffer_unit && (pidinst->pid->max_buffer_unit<=pidinst->pid->nb_buffer_unit)) + buffer_full = GF_TRUE; + + if (pidinst->pid->max_buffer_time && (pidinst->pid->max_buffer_time<=pidinst->pid->buffer_duration)) + buffer_full = GF_TRUE; + + if (!buffer_full) + return 0; + } + count = filter->num_input_pids; + for (i=0; iinput_pids, i), 0); + if (dur > duration) + duration = dur; + } + duration += pidinst->buffer_duration; + return duration; + } else { + u32 count2; + u64 max_dur=0; + + if (check_pid_full) { + if (pid->max_buffer_unit && (pid->max_buffer_unit>pid->nb_buffer_unit)) + return 0; + if (pid->max_buffer_time && (pid->max_buffer_time>pid->buffer_duration)) + return 0; + } + + count = pid->num_destinations; + for (i=0; idestinations, i); + + count2 = pidinst->filter->num_output_pids; + for (j=0; jfilter->output_pids, i); + u64 dur = gf_filter_pid_query_buffer_duration(pid_n, 0); + if (dur > max_dur ) max_dur = dur; + } + } + duration += max_dur; + } + return duration; +} + + +GF_EXPORT +Bool gf_filter_pid_has_seen_eos(GF_FilterPid *pid) +{ + u32 i; + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to query EOS on output PID %s in filter %s\n", pid->pid->name, pid->filter->name)); + return GF_FALSE; + } + if (pid->pid->has_seen_eos) return GF_TRUE; + if (pid->pid->filter->block_eos) return GF_FALSE; + for (i=0; ipid->filter->num_input_pids; i++) { + GF_FilterPidInst *pidi = gf_list_get(pid->pid->filter->input_pids, i); + if (gf_filter_pid_has_seen_eos((GF_FilterPid *) pidi)) return GF_TRUE; + } + return GF_FALSE; +} + +GF_EXPORT +const char *gf_filter_event_name(GF_FEventType type) +{ + switch (type) { + case GF_FEVT_PLAY: return "PLAY"; + case GF_FEVT_SET_SPEED: return "SET_SPEED"; + case GF_FEVT_STOP: return "STOP"; + case GF_FEVT_SOURCE_SEEK: return "SOURCE_SEEK"; + case GF_FEVT_SOURCE_SWITCH: return "SOURCE_SWITCH"; + case GF_FEVT_ATTACH_SCENE: return "ATTACH_SCENE"; + case GF_FEVT_RESET_SCENE: return "RESET_SCENE"; + case GF_FEVT_PAUSE: return "PAUSE"; + case GF_FEVT_RESUME: return "RESUME"; + case GF_FEVT_QUALITY_SWITCH: return "QUALITY_SWITCH"; + case GF_FEVT_VISIBILITY_HINT: return "VISIBILITY_HINT"; + case GF_FEVT_INFO_UPDATE: return "INFO_UPDATE"; + case GF_FEVT_BUFFER_REQ: return "BUFFER_REQ"; + case GF_FEVT_USER: return "USER"; + case GF_FEVT_SEGMENT_SIZE: return "SEGMENT_SIZE"; + case GF_FEVT_CAPS_CHANGE: return "CAPS_CHANGED"; + case GF_FEVT_CONNECT_FAIL: return "CONNECT_FAIL"; + case GF_FEVT_PLAY_HINT: return "PLAY_HINT"; + default: + return "UNKNOWN"; + } +} + +static void gf_filter_pid_reset_task_ex(GF_FSTask *task, Bool *had_eos) +{ + GF_FilterPidInst *pidi = (GF_FilterPidInst *)task->udta; + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s input PID %s (from %s) reseting buffer\n", task->filter->name, pidi->pid->name, pidi->pid->filter->name )); + assert(pidi->pid->discard_input_packets); + + if (had_eos) *had_eos = GF_FALSE; + + while (gf_fq_count(pidi->packets)) { + GF_FilterPacketInstance *pcki = gf_fq_head(pidi->packets); + if ( (pcki->pck->info.flags & GF_PCK_CMD_MASK) == GF_PCK_CMD_PID_EOS) { + if (had_eos) + *had_eos = GF_TRUE; + } + gf_filter_pid_drop_packet((GF_FilterPid *) pidi); + } + while (gf_list_count(pidi->pck_reassembly)) { + GF_FilterPacketInstance *pcki = gf_list_pop_back(pidi->pck_reassembly); + pcki_del(pcki); + } + gf_filter_pidinst_reset_stats(pidi); + + pidi->discard_packets = GF_FALSE; + pidi->last_block_ended = GF_TRUE; + pidi->first_block_started = GF_FALSE; + pidi->is_end_of_stream = GF_FALSE; + pidi->buffer_duration = 0; + pidi->nb_eos_signaled = 0; + pidi->pid->has_seen_eos = GF_FALSE; + + assert(pidi->pid->filter->stream_reset_pending); + safe_int_dec(& pidi->pid->filter->stream_reset_pending ); + + pidi->pid->nb_buffer_unit = 0; + pidi->pid->buffer_duration = 0; + gf_filter_pid_check_unblock(pidi->pid); + + safe_int_dec(& pidi->pid->discard_input_packets ); +} +static void gf_filter_pid_reset_task(GF_FSTask *task) +{ + gf_filter_pid_reset_task_ex(task, NULL); +} + +static void gf_filter_pid_reset_stop_task(GF_FSTask *task) +{ + GF_FilterPidInst *pidi = (GF_FilterPidInst *)task->udta; + Bool has_eos; + gf_filter_pid_reset_task_ex(task, &has_eos); + pidi->is_end_of_stream = has_eos; + pidi->pid->has_seen_eos = has_eos; +} + +typedef struct +{ + u32 ref_count; + char string[1]; +} GF_RefString; + +#define TO_REFSTRING(_v) (GF_RefString *) (_v - offsetof(GF_RefString, string)) + +static GF_RefString *evt_get_refstr(GF_FilterEvent *evt) +{ + if (evt->base.type == GF_FEVT_FILE_DELETE) { + return TO_REFSTRING(evt->file_del.url); + } + if (evt->base.type == GF_FEVT_SOURCE_SWITCH) { + return TO_REFSTRING(evt->seek.source_switch); + } + if (evt->base.type == GF_FEVT_SEGMENT_SIZE) { + return TO_REFSTRING(evt->seg_size.seg_url); + } + return NULL; +} +static GF_FilterEvent *dup_evt(GF_FilterEvent *evt) +{ + GF_FilterEvent *an_evt; + GF_RefString *rstr = evt_get_refstr(evt); + an_evt = gf_malloc(sizeof(GF_FilterEvent)); + memcpy(an_evt, evt, sizeof(GF_FilterEvent)); + if (rstr) { + safe_int_inc(&rstr->ref_count); + } + return an_evt; +} + +static void free_evt(GF_FilterEvent *evt) +{ + GF_RefString *rstr = evt_get_refstr(evt); + if (rstr) { + assert(rstr->ref_count); + if (safe_int_dec(&rstr->ref_count) == 0) { + gf_free(rstr); + } + } + gf_free(evt); +} + +static GF_FilterEvent *init_evt(GF_FilterEvent *evt) +{ + char **url_addr_src = NULL; + char **url_addr_dst = NULL; + GF_FilterEvent *an_evt = gf_malloc(sizeof(GF_FilterEvent)); + memcpy(an_evt, evt, sizeof(GF_FilterEvent)); + + if (evt->base.type==GF_FEVT_FILE_DELETE) { + url_addr_src = (char **) &evt->file_del.url; + url_addr_dst = (char **) &an_evt->file_del.url; + } else if (evt->base.type==GF_FEVT_SOURCE_SWITCH) { + url_addr_src = (char **) &evt->seek.source_switch; + url_addr_dst = (char **) &an_evt->seek.source_switch; + } else if (evt->base.type==GF_FEVT_SEGMENT_SIZE) { + url_addr_src = (char **) &evt->seg_size.seg_url; + url_addr_dst = (char **) &an_evt->seg_size.seg_url; + } + if (url_addr_src) { + char *url = *url_addr_src; + u32 len = url ? (u32) strlen(url) : 0; + GF_RefString *rstr = gf_malloc(sizeof(GF_RefString) + sizeof(char)*len); + rstr->ref_count=1; + strcpy( (char *) &rstr->string[0], url ? url : ""); + *url_addr_dst = (char *) &rstr->string[0]; + } + return an_evt; +} + +void gf_filter_pid_send_event_downstream(GF_FSTask *task) +{ + u32 i, count; + Bool canceled = GF_FALSE; + GF_FilterEvent *evt = task->udta; + GF_Filter *f = task->filter; + GF_List *dispatched_filters = NULL; + Bool evt_reused=GF_FALSE; + + //if stream reset task is posted, wait for it before processing this event + if (f->stream_reset_pending) { + TASK_REQUEUE(task) + return; + } + //if some pids are still detached, wait for the connection before processing this event + if (f->detached_pid_inst) { + TASK_REQUEUE(task) + task->can_swap = GF_TRUE; + return; + } + + if (evt->base.on_pid) { + assert(evt->base.on_pid->filter->num_events_queued); + safe_int_dec(&evt->base.on_pid->filter->num_events_queued); + } + + if (evt->base.type == GF_FEVT_BUFFER_REQ) { + if (!evt->base.on_pid) { + free_evt(evt); + return; + } + if (evt->base.on_pid->nb_decoder_inputs || evt->base.on_pid->raw_media || evt->buffer_req.pid_only) { + evt->base.on_pid->max_buffer_time = evt->base.on_pid->user_max_buffer_time = evt->buffer_req.max_buffer_us; + evt->base.on_pid->user_max_playout_time = evt->buffer_req.max_playout_us; + evt->base.on_pid->user_min_playout_time = evt->buffer_req.min_playout_us; + evt->base.on_pid->max_buffer_unit = 0; + //update blocking state + if (evt->base.on_pid->would_block) + gf_filter_pid_check_unblock(evt->base.on_pid); + else + gf_filter_pid_would_block(evt->base.on_pid); + canceled = GF_TRUE; + } + } else if (evt->base.on_pid && (evt->base.type == GF_FEVT_PLAY) + && (evt->base.on_pid->pid->is_playing || (((GF_FilterPid *) evt->base.on_pid->pid)->not_connected==2)) + ) { + if (evt->base.on_pid->pid->is_playing) { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s PID %s event %s but PID is already playing, discarding\n", f->name, evt->base.on_pid->name, gf_filter_event_name(evt->base.type))); + } + free_evt(evt); + return; + } else if (evt->base.on_pid && (evt->base.type == GF_FEVT_STOP) && !evt->base.on_pid->pid->is_playing) { + GF_FilterPid *pid = (GF_FilterPid *) evt->base.on_pid->pid; + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s PID %s event %s but PID is not playing, discarding\n", f->name, evt->base.on_pid->name, gf_filter_event_name(evt->base.type))); + + for (i=0; inum_destinations; i++) { + GF_FilterPidInst *pidi = (GF_FilterPidInst *) gf_list_get(pid->destinations, i); + //don't forget we pre-processed stop by incrementing the discard counter and setting discard_packets on pid instances + //undo this + if (pidi->discard_packets) { + assert(pidi->pid->discard_input_packets); + safe_int_dec(& pidi->pid->discard_input_packets ); + pidi->discard_packets = GF_FALSE; + } + } + + free_evt(evt); + if ((f->num_input_pids==f->num_output_pids) && (f->num_input_pids==1)) { + gf_filter_pid_set_discard(gf_list_get(f->input_pids, 0), GF_TRUE); + } + if (pid->not_connected) + pid->not_connected = 2; + return; + } else if (f->freg->process_event) { + FSESS_CHECK_THREAD(f) + canceled = f->freg->process_event(f, evt); + } + + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s PID %s processed event %s - canceled %s\n", f->name, evt->base.on_pid ? evt->base.on_pid->name : "none", gf_filter_event_name(evt->base.type), canceled ? "yes" : "no" )); + + if (evt->base.on_pid && ((evt->base.type == GF_FEVT_STOP) || (evt->base.type==GF_FEVT_SOURCE_SEEK) || (evt->base.type==GF_FEVT_PLAY)) ) { + Bool do_reset = GF_TRUE; + Bool is_play_reset = GF_FALSE; + GF_FilterPidInst *p = (GF_FilterPidInst *) evt->base.on_pid; + GF_FilterPid *pid = p->pid; + //we need to force a PID reset when the first PLAY is > 0, since some filters may have dispatched packets during the initialization + //phase + if (evt->base.type==GF_FEVT_PLAY) { + pid->is_playing = GF_TRUE; + pid->filter->nb_pids_playing++; + if (pid->initial_play_done) { + do_reset = GF_FALSE; + } else { + pid->initial_play_done = GF_TRUE; + is_play_reset = GF_TRUE; + if (evt->play.start_range < 0.1) + do_reset = GF_FALSE; + } + } else if (evt->base.type==GF_FEVT_STOP) { + pid->is_playing = GF_FALSE; + pid->filter->nb_pids_playing--; + + if (pid->not_connected) + pid->not_connected = 2; + } else if (evt->base.type==GF_FEVT_SOURCE_SEEK) { + pid->is_playing = GF_TRUE; + pid->filter->nb_pids_playing++; + } + for (i=0; inum_destinations && do_reset; i++) { + GF_FilterPidInst *pidi = gf_list_get(pid->destinations, i); + pidi->discard_packets = GF_TRUE; + //if not play reset, the request was set only once so we need to do it if more than one destination + if (is_play_reset || i) + safe_int_inc(& pid->discard_input_packets ); + + safe_int_inc(& pid->filter->stream_reset_pending ); + + //post task on destination filter + if (evt->base.type==GF_FEVT_STOP) + gf_fs_post_task(pidi->filter->session, gf_filter_pid_reset_stop_task, pidi->filter, NULL, "reset_pid", pidi); + else + gf_fs_post_task(pidi->filter->session, gf_filter_pid_reset_task, pidi->filter, NULL, "reset_pid", pidi); + } + pid->nb_reaggregation_pending = 0; + } + + //after play or seek, request a process task for source filters or filters having pending packets + if (!f->input_pids || f->pending_packets) { + if ((evt->base.type==GF_FEVT_PLAY) || (evt->base.type==GF_FEVT_SOURCE_SEEK)) { + gf_filter_post_process_task(f); + } + } + + //quick hack for filters with one input pid and one outout pid, set discard on/off on the input + //this avoids cases like TS demux dispatching data to inactive filters not checking their input + //which ends up in session deadlock (filter still flagged as active and with pending packets) + //if more than one input or more than one output, only the filter can decide what to do if some of the + //streams are active and other not + if ((f->num_input_pids==f->num_output_pids) && (f->num_input_pids==1)) { + GF_FilterPidInst *apidi = gf_list_get(f->input_pids, 0); + if (apidi->pid) { + if (evt->base.type==GF_FEVT_STOP) { + if (!canceled) + gf_filter_pid_set_discard((GF_FilterPid *)apidi, GF_TRUE); + } else if (evt->base.type==GF_FEVT_PLAY) { + gf_filter_pid_set_discard((GF_FilterPid *)apidi, GF_FALSE); + } + } + } + + if ((evt->base.type==GF_FEVT_PLAY) || (evt->base.type==GF_FEVT_SET_SPEED)) { + if (evt->base.on_pid) { + u32 scaler = (u32) ( (evt->play.speed<0) ? -evt->play.speed : evt->play.speed ) * GF_FILTER_SPEED_SCALER; + if (!scaler) scaler = GF_FILTER_SPEED_SCALER; + if (scaler != evt->base.on_pid->playback_speed_scaler) { + u32 prev_scaler = evt->base.on_pid->playback_speed_scaler; + evt->base.on_pid->playback_speed_scaler = scaler; + //lowering speed, we may need to trigger blocking + if (scalerbase.on_pid); + //increasing speed, we may want to unblock + else + gf_filter_pid_check_unblock(evt->base.on_pid); + } + } + } + else if (evt->base.type==GF_FEVT_SOURCE_SWITCH) { + for (i=0; inum_output_pids; i++) { + GF_FilterPid *apid = gf_list_get(f->output_pids, i); + apid->has_seen_eos = GF_FALSE; + gf_filter_pid_check_unblock(apid); + } + } + + //no more input pids + count = f->num_input_pids; + if (count==0) canceled = GF_TRUE; + + if (canceled) { + free_evt(evt); + return; + } + if (!task->pid) dispatched_filters = gf_list_new(); + + //otherwise forward event to each input PID + for (i=0; iinput_pids, i); + GF_FilterPid *pid = pid_inst->pid; + if (!pid) continue; + + if (dispatched_filters) { + if (gf_list_find(dispatched_filters, pid_inst->pid->filter) >=0 ) + continue; + + gf_list_add(dispatched_filters, pid_inst->pid->filter); + } + + //mark pid instance as about to be reset to avoid processing PID destroy task before + if ((evt->base.type == GF_FEVT_STOP) || (evt->base.type==GF_FEVT_SOURCE_SEEK)) { + pid_inst->discard_packets = GF_TRUE; + safe_int_inc(& pid_inst->pid->discard_input_packets ); + } + //allocate a copy except for the last PID where we use the one from the input + if (evt_reused) { + an_evt = dup_evt(evt); + } else { + an_evt = evt; + evt_reused = GF_TRUE; + } + an_evt->base.on_pid = task->pid ? pid : NULL; + + safe_int_inc(&pid->filter->num_events_queued); + + gf_fs_post_task(pid->filter->session, gf_filter_pid_send_event_downstream, pid->filter, task->pid ? pid : NULL, "downstream_event", an_evt); + } + if (dispatched_filters) gf_list_del(dispatched_filters); + if (!evt_reused) free_evt(evt); + return; +} + +void gf_filter_pid_send_event_upstream(GF_FSTask *task) +{ + u32 i, j; + Bool canceled = GF_FALSE; + GF_FilterEvent *evt = task->udta; + GF_Filter *f = task->filter; + + if (f->stream_reset_pending) { + TASK_REQUEUE(task) + return; + } + + canceled = f->freg->process_event ? f->freg->process_event(f, evt) : GF_TRUE; + if (!canceled) { + for (i=0; inum_output_pids; i++) { + GF_FilterPid *apid = gf_list_get(f->output_pids, i); + for (j=0; jnum_destinations; j++) { + GF_FilterEvent *an_evt; + GF_FilterPidInst *pidi = gf_list_get(apid->destinations, j); + + an_evt = dup_evt(evt); + an_evt->base.on_pid = (GF_FilterPid *)pidi; + gf_fs_post_task(pidi->filter->session, gf_filter_pid_send_event_upstream, pidi->filter, NULL, "upstream_event", an_evt); + } + } + } + free_evt(evt); +} + +void gf_filter_pid_send_event_internal(GF_FilterPid *pid, GF_FilterEvent *evt, Bool force_downstream) +{ + GF_FilterEvent *an_evt; + GF_FilterPid *target_pid=NULL; + Bool upstream=GF_FALSE; + if (!pid) { + pid = evt->base.on_pid; + if (!pid) return; + } + //filter is being shut down, prevent any event posting + if (pid->filter->finalized) return; + + if ((evt->base.type==GF_FEVT_FILE_DELETE) && !evt->file_del.url) return; + + if (!force_downstream && PID_IS_OUTPUT(pid)) { + upstream = GF_TRUE; + } + + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s PID %s queuing %s event %s\n", pid->pid->filter->name, pid->pid->name, upstream ? "upstream" : "downstream", gf_filter_event_name(evt->base.type) )); + + if (upstream) { + u32 i, j; + + an_evt = init_evt(evt); + + for (i=0; ifilter->num_output_pids; i++) { + GF_FilterPid *apid = gf_list_get(pid->filter->output_pids, i); + if (evt->base.on_pid && (apid != evt->base.on_pid)) continue; + for (j=0; jnum_destinations; j++) { + GF_FilterEvent *up_evt; + GF_FilterPidInst *pidi = gf_list_get(apid->destinations, j); + + up_evt = dup_evt(an_evt); + up_evt->base.on_pid = (GF_FilterPid *)pidi; + gf_fs_post_task(pidi->filter->session, gf_filter_pid_send_event_upstream, pidi->filter, NULL, "upstream_event", up_evt); + } + } + free_evt(an_evt); + return; + } + + + if ((evt->base.type == GF_FEVT_STOP) || (evt->base.type == GF_FEVT_PLAY) || (evt->base.type==GF_FEVT_SOURCE_SEEK)) { + u32 i, count = pid->pid->num_destinations; + for (i=0; ipid->destinations, i); + if (evt->base.type == GF_FEVT_PLAY) { + pidi->is_end_of_stream = GF_FALSE; +// gf_filter_pid_clear_eos(pid, GF_FALSE); + } else { + //flag pid instance to discard all packets (cf above note) + pidi->discard_packets = GF_TRUE; + safe_int_inc(& pidi->pid->discard_input_packets ); + } + } + } + + an_evt = init_evt(evt); + if (evt->base.on_pid) { + target_pid = evt->base.on_pid->pid; + an_evt->base.on_pid = target_pid; + safe_int_inc(&target_pid->filter->num_events_queued); + } + gf_fs_post_task(pid->pid->filter->session, gf_filter_pid_send_event_downstream, pid->pid->filter, target_pid, "downstream_event", an_evt); +} + +GF_EXPORT +void gf_filter_pid_send_event(GF_FilterPid *pid, GF_FilterEvent *evt) +{ + if (!evt) return; + if (evt->base.type==GF_FEVT_RESET_SCENE) return; + if (evt->base.type==GF_FEVT_INFO_UPDATE) return; + + gf_filter_pid_send_event_internal(pid, evt, GF_FALSE); +} + +GF_EXPORT +void gf_filter_send_event(GF_Filter *filter, GF_FilterEvent *evt, Bool upstream) +{ + GF_FilterEvent *an_evt; + if (!filter) return; + if (filter->multi_sink_target) + filter = filter->multi_sink_target; + + //filter is being shut down, prevent any event posting + if (filter->finalized) return; + if (!evt) return; + if ((evt->base.type==GF_FEVT_FILE_DELETE) && !evt->file_del.url) return; + + if (evt->base.type==GF_FEVT_RESET_SCENE) + return; + + if (evt->base.on_pid && PID_IS_OUTPUT(evt->base.on_pid)) { + gf_filter_pid_send_event_internal(evt->base.on_pid, evt, GF_FALSE); + return; + } + + //switch and seek events are only sent on source filters + if ((evt->base.type==GF_FEVT_SOURCE_SWITCH) || (evt->base.type==GF_FEVT_SOURCE_SEEK)) { + if (filter->num_input_pids) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Sending %s event on non source filter %s is not allowed, discarding)\n", gf_filter_event_name(evt->base.type), filter->name)); + return; + } + } + + an_evt = init_evt(evt); + + if (evt->base.on_pid) { + safe_int_inc(&evt->base.on_pid->filter->num_events_queued); + } + if (upstream) + gf_fs_post_task(filter->session, gf_filter_pid_send_event_upstream, filter, evt->base.on_pid, "upstream_event", an_evt); + else + gf_fs_post_task(filter->session, gf_filter_pid_send_event_downstream, filter, evt->base.on_pid, "downstream_event", an_evt); +} + + +GF_EXPORT +void gf_filter_pid_exec_event(GF_FilterPid *pid, GF_FilterEvent *evt) +{ + //filter is being shut down, prevent any event posting + if (pid->pid->filter->finalized) return; + if (! (pid->pid->filter->freg->flags & GF_FS_REG_MAIN_THREAD)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Executing event on PID %s created by filter %s not running on main thread, not allowed\n", pid->pid->name, pid->filter->name)); + return; + } + + if (pid->pid->filter->freg->process_event) { + if (evt->base.on_pid) evt->base.on_pid = evt->base.on_pid->pid; + FSESS_CHECK_THREAD(pid->pid->filter) + pid->pid->filter->freg->process_event(pid->pid->filter, evt); + } +} + + +GF_EXPORT +Bool gf_filter_pid_is_filter_in_parents(GF_FilterPid *pid, GF_Filter *filter) +{ + if (!pid || !filter) return GF_FALSE; + pid = pid->pid; + return gf_filter_in_parent_chain(pid->filter, filter); +} + +static void filter_pid_collect_stats(GF_List *pidi_list, GF_FilterPidStatistics *stats) +{ + u32 i; + for (i=0; ipid) continue; + + stats->avgerage_bitrate += pidi->avg_bit_rate; + if (!stats->first_process_time || (stats->first_process_time > pidi->first_frame_time)) + stats->first_process_time = pidi->first_frame_time; + if (stats->last_process_time < pidi->last_pck_fetch_time) + stats->last_process_time = pidi->last_pck_fetch_time; + + stats->max_bitrate += pidi->max_bit_rate; + + if (stats->max_process_time < (u32) pidi->max_process_time) + stats->max_process_time = (u32) pidi->max_process_time; + if (stats->max_sap_process_time < (u32) pidi->max_sap_process_time) + stats->max_sap_process_time = (u32) pidi->max_sap_process_time; + if (!stats->min_frame_dur || (stats->min_frame_dur > pidi->pid->min_pck_duration)) + stats->min_frame_dur = pidi->pid->min_pck_duration; + stats->nb_processed += pidi->nb_processed; + stats->nb_saps += pidi->nb_sap_processed; + stats->total_process_time += pidi->total_process_time; + stats->total_sap_process_time += pidi->total_sap_process_time; + stats->average_process_rate += pidi->avg_process_rate; + stats->max_process_rate += pidi->max_process_rate; + + if (stats->nb_buffer_units < pidi->pid->nb_buffer_unit) + stats->nb_buffer_units = pidi->pid->nb_buffer_unit; + if (stats->max_buffer_time < pidi->pid->max_buffer_time) + stats->max_buffer_time = pidi->pid->max_buffer_time; + + if (stats->max_playout_time < pidi->pid->user_max_playout_time) + stats->max_playout_time = pidi->pid->user_max_playout_time; + if (!stats->min_playout_time || (stats->min_playout_time > pidi->pid->user_min_playout_time)) + stats->min_playout_time = pidi->pid->user_min_playout_time; + + if (stats->buffer_time < pidi->pid->buffer_duration) + stats->buffer_time = pidi->pid->buffer_duration; + } +} + +static GF_Filter *filter_locate_enc_dec_sink(GF_Filter *filter, Bool locate_decoder) +{ + u32 i, j; + + for (i=0; inum_output_pids; i++) { + GF_FilterPid *pid = gf_list_get(filter->output_pids, i); + for (j=0; jnum_destinations; j++) { + GF_Filter *res; + GF_FilterPidInst *pidi = gf_list_get(pid->destinations, j); + if (pidi->is_decoder_input) return pidi->filter; + res = filter_locate_enc_dec_sink(pidi->filter, locate_decoder); + if (res) return res; + } + } + return NULL; +} + +static GF_Filter *filter_locate_enc_dec_src(GF_Filter *filter, Bool locate_decoder) +{ + u32 i; + + for (i=0; inum_input_pids; i++) { + GF_Filter *res; + GF_FilterPidInst *pidi = gf_list_get(filter->input_pids, i); + if (pidi->is_decoder_input) return filter; + + res = filter_locate_enc_dec_sink(pidi->pid->filter, locate_decoder); + if (res) return res; + } + return NULL; +} + + +GF_EXPORT +GF_Err gf_filter_pid_get_statistics(GF_FilterPid *pid, GF_FilterPidStatistics *stats, GF_FilterPidStatsLocation location) +{ + GF_FilterPidInst *pidi = (GF_FilterPidInst *)pid; + GF_Filter *filter=NULL; + Bool for_decoder=GF_TRUE; + + memset(stats, 0, sizeof(GF_FilterPidStatistics) ); + if (!pidi->pid) { + stats->disconnected = GF_TRUE; + return GF_OK; + } + + switch (location) { + case GF_STATS_LOCAL: + if (PID_IS_OUTPUT(pid)) { + filter_pid_collect_stats(pid->destinations, stats); + return GF_OK; + } + stats->avgerage_bitrate = pidi->avg_bit_rate; + stats->first_process_time = pidi->first_frame_time; + stats->last_process_time = pidi->last_pck_fetch_time; + stats->max_bitrate = pidi->max_bit_rate; + stats->max_process_time = (u32) pidi->max_process_time; + stats->max_sap_process_time = (u32) pidi->max_sap_process_time; + stats->min_frame_dur = pidi->pid->min_pck_duration; + stats->nb_processed = pidi->nb_processed; + stats->nb_saps = pidi->nb_sap_processed; + stats->total_process_time = pidi->total_process_time; + stats->total_sap_process_time = pidi->total_sap_process_time; + + stats->average_process_rate = pidi->avg_process_rate; + stats->max_process_rate = pidi->max_process_rate; + return GF_OK; + case GF_STATS_LOCAL_INPUTS: + if (PID_IS_OUTPUT(pid)) { + filter_pid_collect_stats(pid->destinations, stats); + return GF_OK; + } + filter = pidi->pid->filter; + break; + case GF_STATS_ENCODER_SOURCE: + for_decoder = GF_FALSE; + case GF_STATS_DECODER_SOURCE: + filter = filter_locate_enc_dec_src(pidi->pid->filter, for_decoder); + break; + case GF_STATS_ENCODER_SINK: + for_decoder = GF_FALSE; + case GF_STATS_DECODER_SINK: + filter = filter_locate_enc_dec_sink(pidi->pid->filter, for_decoder); + break; + } + if (!filter) { + return GF_NOT_FOUND; + } + filter_pid_collect_stats(filter->input_pids, stats); + return GF_OK; +} + +GF_EXPORT +void gf_filter_pid_remove(GF_FilterPid *pid) +{ + GF_FilterPacket *pck; + if (PID_IS_INPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Removing PID input filter (%s:%s) not allowed\n", pid->filter->name, pid->pid->name)); + } + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Filter %s removed output PID %s\n", pid->filter->name, pid->pid->name)); + + if (pid->filter->removed) { + return; + } + if (pid->removed) { + return; + } + pid->removed = GF_TRUE; + if (pid->filter->marked_for_removal || (pid->has_seen_eos && !pid->nb_buffer_unit)) { + u32 i; + for (i=0; inum_destinations; i++) { + GF_FilterPidInst *pidi = gf_list_get(pid->destinations, i); + gf_fs_post_task(pidi->filter->session, gf_filter_pid_disconnect_task, pidi->filter, pidi->pid, "pidinst_disconnect", NULL); + } + return; + } + + //we create a fake packet for removal signaling + pck = gf_filter_pck_new_shared_internal(pid, NULL, 0, NULL, GF_TRUE); + gf_filter_pck_set_framing(pck, GF_TRUE, GF_TRUE); + pck->pck->info.flags |= GF_PCK_CMD_PID_REM; + gf_filter_pck_send(pck); +} + +GF_EXPORT +void gf_filter_pid_try_pull(GF_FilterPid *pid) +{ + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to pull from output PID %s in filter %s\n", pid->pid->name, pid->filter->name)); + return; + } + pid = pid->pid; + if (pid->filter->session->threads) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Filter pull in multithread mode not yet implementing - defaulting to 1 ms sleep\n", pid->pid->name, pid->filter->name)); + gf_sleep(1); + return; + } + + gf_filter_process_inline(pid->filter); +} + + +GF_EXPORT +GF_FilterClockType gf_filter_pid_get_clock_info(GF_FilterPid *pid, u64 *clock_time, u32 *timescale) +{ + GF_FilterPidInst *pidi = (GF_FilterPidInst *)pid; + GF_FilterClockType res; + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Querying clock on output PID %s in filter %s\n", pid->pid->name, pid->filter->name)); + return GF_FILTER_CLOCK_NONE; + } + if (clock_time) *clock_time = pidi->last_clock_value; + if (timescale) *timescale = pidi->last_clock_timescale; + res = pidi->last_clock_type; + pidi->last_clock_type = 0; + return res; +} + +GF_EXPORT +u32 gf_filter_pid_get_timescale(GF_FilterPid *pid) +{ + GF_PropertyMap *map = pid ? gf_list_get(pid->pid->properties, 0) : 0; + return map ? map->timescale : 0; +} + +GF_EXPORT +void gf_filter_pid_clear_eos(GF_FilterPid *pid, Bool clear_all) +{ + u32 i, j; + Bool was_blocking; + GF_FilterPidInst *pidi = (GF_FilterPidInst *)pid; + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Clearing EOS on output PID %s in filter %s\n", pid->pid->name, pid->filter->name)); + return; + } + pid = pid->pid; + was_blocking = pid->filter->would_block; + for (i=0; ifilter->num_output_pids; i++) { + GF_FilterPid *apid = gf_list_get(pid->filter->output_pids, i); + if (!clear_all && (pid != apid)) continue; + + for (j=0; jnum_destinations; j++) { + GF_FilterPidInst *apidi = gf_list_get(apid->destinations, j); + if (apidi->filter != pidi->filter) continue; + + if (apidi->is_end_of_stream) { + apidi->is_end_of_stream = GF_FALSE; + } + if (apid->has_seen_eos) { + apid->has_seen_eos = GF_FALSE; + gf_filter_pid_check_unblock(apid); + } + + if (apidi->pid->filter->would_block && apidi->pid->filter->num_input_pids) { + u32 k; + for (k=0; kpid->filter->num_input_pids; k++) { + GF_FilterPidInst *source_pid_inst = gf_list_get(apidi->pid->filter->input_pids, k); + gf_filter_pid_clear_eos((GF_FilterPid *) source_pid_inst, clear_all); + } + } + } + } + if (!clear_all || (was_blocking == pid->filter->would_block)) return; + + //unblock parent + for (i=0; ifilter->num_input_pids; i++) { + GF_FilterPidInst *apidi = gf_list_get(pid->filter->input_pids, i); + gf_filter_pid_clear_eos((GF_FilterPid *) apidi, GF_TRUE); + } + +} + +GF_EXPORT +void gf_filter_pid_set_clock_mode(GF_FilterPid *pid, Bool filter_in_charge) +{ + GF_FilterPidInst *pidi = (GF_FilterPidInst *)pid; + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Changing clock mode on output PID %s in filter %s\n", pid->pid->name, pid->filter->name)); + return; + } + pidi->handles_clock_references = filter_in_charge; +} + +GF_EXPORT +const char *gf_filter_pid_get_args(GF_FilterPid *pid) +{ + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Querying args on output PID %s in filter %s\n", pid->pid->name, pid->filter->name)); + return NULL; + } + if (pid->pid->filter->src_args) return pid->pid->filter->src_args; + return pid->pid->filter->orig_args; +} + +GF_EXPORT +void gf_filter_pid_set_max_buffer(GF_FilterPid *pid, u32 total_duration_us) +{ + if (PID_IS_INPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Setting max buffer on input PID %s in filter %s not allowed\n", pid->pid->name, pid->filter->name)); + return; + } + pid->max_buffer_time = pid->user_max_buffer_time = total_duration_us; +} + +GF_EXPORT +u32 gf_filter_pid_get_max_buffer(GF_FilterPid *pid) +{ + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Querying max buffer on output PID %s in filter %s not allowed\n", pid->pid->name, pid->filter->name)); + return 0; + } + return pid->pid->user_max_buffer_time; +} + + +GF_EXPORT +void gf_filter_pid_set_loose_connect(GF_FilterPid *pid) +{ + if (PID_IS_INPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Setting loose connect on input PID %s in filter %s not allowed\n", pid->pid->name, pid->filter->name)); + return; + } + pid->not_connected_ok = GF_TRUE; +} + +GF_EXPORT +const GF_PropertyValue *gf_filter_pid_caps_query(GF_FilterPid *pid, u32 prop_4cc) +{ + u32 i; + GF_PropertyMap *map = pid->pid->caps_negociate; + if (PID_IS_INPUT(pid)) { + u32 k; + GF_Filter *dst = pid->filter->cap_dst_filter; + //the first entry in destination filters may be the fnal destination and won't hold any caps query + //we therefore use the last entry which points to the next filter in the chain + if (!dst) dst = gf_list_last(pid->filter->destination_filters); + if (!dst) dst = gf_list_get(pid->filter->destination_links, 0); + + if (!dst || (dst->cap_idx_at_resolution<0) ) { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Reconfig caps query on input PID %s in filter %s with no destination filter set\n", pid->pid->name, pid->filter->name)); + return NULL; + } + for (k=dst->cap_idx_at_resolution; kfreg->nb_caps; k++) { + const GF_FilterCapability *cap = &dst->freg->caps[k]; + if (!(cap->flags & GF_CAPFLAG_IN_BUNDLE)) return NULL; + + if (!(cap->flags & GF_CAPFLAG_INPUT)) continue; + if (cap->flags & GF_CAPFLAG_OPTIONAL) continue; + if (cap->code == prop_4cc) return &cap->val; + } + return NULL; + } + if (map) return gf_props_get_property(map, prop_4cc, NULL); + for (i=0; inum_destinations; i++) { + u32 j; + GF_FilterPidInst *pidi = gf_list_get(pid->destinations, i); + for (j=0; jfilter->nb_forced_caps; j++) { + if (pidi->filter->forced_caps[j].code==prop_4cc) + return &pidi->filter->forced_caps[j].val; + } + //walk up the chain + for (j=0; jfilter->num_output_pids; j++) { + GF_FilterPid *apid = gf_list_get(pidi->filter->output_pids, j); + if (apid) { + const GF_PropertyValue *p = gf_filter_pid_caps_query(apid, prop_4cc); + if (p) return p; + } + } + + } + + //trick here: we may not be connected yet (called during a configure_pid), use the target destination + //of the filter as caps source + if (gf_list_count(pid->filter->destination_filters) ) { + GF_Filter *a_filter = gf_list_get(pid->filter->destination_filters, 0); + while (a_filter) { + for (i=0; inb_forced_caps; i++) { + if (a_filter->forced_caps[i].code==prop_4cc) + return &a_filter->forced_caps[i].val; + } + a_filter = gf_list_get(a_filter->destination_filters, 0); + } + } + + //second trick here: we may not be connected yet (called during a configure_pid), use the target destination + //of the filter as caps source + if (pid->filter->cap_dst_filter) { + GF_Filter *a_filter = pid->filter->cap_dst_filter; + for (i=0; inb_forced_caps; i++) { + if (a_filter->forced_caps[i].code==prop_4cc) + return &a_filter->forced_caps[i].val; + } + } + + return NULL; +} + +GF_EXPORT +const GF_PropertyValue *gf_filter_pid_caps_query_str(GF_FilterPid *pid, const char *prop_name) +{ + GF_PropertyMap *map = pid->caps_negociate; + if (PID_IS_INPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Reconfig caps query on input PID %s in filter %s not allowed\n", pid->pid->name, pid->filter->name)); + return NULL; + } + return map ? gf_props_get_property(map, 0, prop_name) : NULL; +} + + +GF_EXPORT +GF_Err gf_filter_pid_resolve_file_template(GF_FilterPid *pid, char szTemplate[GF_MAX_PATH], char szFinalName[GF_MAX_PATH], u32 file_idx, const char *file_suffix) +{ + u32 k; + char szFormat[30], szTemplateVal[GF_MAX_PATH], szPropVal[GF_PROP_DUMP_ARG_SIZE]; + char *name = szTemplate; + if (!strchr(szTemplate, '$')) { + strcpy(szFinalName, szTemplate); + return GF_OK; + } + + k = 0; + while (name[0]) { + char *sep=NULL; + char *fsep=NULL; + const char *str_val = NULL; + s64 value = 0; + Bool is_ok = GF_TRUE; + Bool do_skip = GF_FALSE; + Bool has_val = GF_FALSE; + Bool is_file_str = GF_FALSE; + u32 prop_4cc = 0; + GF_PropertyValue prop_val_patched; + const GF_PropertyValue *prop_val = NULL; + + if (k+1==GF_MAX_PATH) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("[Filter] Not enough memory to solve file template %s\n", szTemplate)); + return GF_OUT_OF_MEM; + } + if (name[0] != '$') { + szFinalName[k] = name[0]; + k++; + name++; + continue; + } + if (name[1]=='$') { + szFinalName[k] = '$'; + name++; + k++; + continue; + } + sep = strchr(name+1, '$'); + if (!sep) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[Filter] broken file template expecting $KEYWORD$, couln't find second '$'\n", szTemplate)); + strcpy(szFinalName, szTemplate); + return GF_BAD_PARAM; + } + szFormat[0] = '%'; + szFormat[1] = 'd'; + szFormat[2] = 0; + + szFinalName[k] = 0; + name++; + sep[0]=0; + fsep = strchr(name, '%'); + if (fsep) { + strcpy(szFormat, fsep); + fsep[0]=0; + } + + if (!strcmp(name, "num")) { + name += 3; + value = file_idx; + has_val = GF_TRUE; + } else if (!strcmp(name, "URL")) { + prop_val = gf_filter_pid_get_property_first(pid, GF_PROP_PID_URL); + is_file_str = GF_TRUE; + } else if (!strcmp(name, "File")) { + prop_val = gf_filter_pid_get_property_first(pid, GF_PROP_PID_FILEPATH); + if (!prop_val) prop_val = gf_filter_pid_get_property_first(pid, GF_PROP_PID_URL); + is_file_str = GF_TRUE; + } else if (!strcmp(name, "PID")) { + prop_val = gf_filter_pid_get_property_first(pid, GF_PROP_PID_ID); + } else if (!strcmp(name, "FS")) { + str_val = file_suffix ? file_suffix : ""; + is_ok = GF_TRUE; + } else if (!strncmp(name, "p4cc=", 5)) { + if (strlen(name) != 9) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[Filter] wrong length in 4CC template, expecting 4cc=ABCD\n", name)); + is_ok = GF_FALSE; + } else { + prop_4cc = GF_4CC(name[5],name[6],name[7],name[8]); + prop_val = gf_filter_pid_get_property_first(pid, prop_4cc); + if (!prop_val) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[Filter] no pid property of type %s\n", name+5)); + is_ok = GF_FALSE; + } + } + } else if (!strncmp(name, "pname=", 6)) { + prop_val = gf_filter_pid_get_property_str_first(pid, name+6); + if (!prop_val) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[Filter] no pid property named %s\n", name+6)); + is_ok = GF_FALSE; + } + } else if (!strncmp(name, "Number", 6)) { + do_skip = GF_TRUE; + } else if (!strncmp(name, "Time", 4)) { + do_skip = GF_TRUE; + } else if (!strncmp(name, "RepresentationID", 16)) { + do_skip = GF_TRUE; + } else if (!strncmp(name, "Bandwidth", 9)) { + do_skip = GF_TRUE; + } else if (!strncmp(name, "SubNumber", 9)) { + do_skip = GF_TRUE; + } else if (!strncmp(name, "Init", 4)) { + do_skip = GF_TRUE; + } else if (!strncmp(name, "XInit", 4)) { + do_skip = GF_TRUE; + } else if (!strncmp(name, "Path", 4)) { + do_skip = GF_TRUE; + } else { + char *next_eq = strchr(name, '='); + char *next_sep = strchr(name, '$'); + if (!next_eq || (next_eq - name < next_sep - name)) { + prop_4cc = gf_props_get_id(name); + //not matching, try with name + if (!prop_4cc) { + prop_val = gf_filter_pid_get_property_str_first(pid, name); + if (!prop_val) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[Filter] Unrecognized template %s\n", name)); + is_ok = GF_FALSE; + } + } else { + prop_val = gf_filter_pid_get_property_first(pid, prop_4cc); + if (!prop_val) { + is_ok = GF_FALSE; + } + } + } else { + u32 i, len = (u32) (next_sep ? 1+(next_sep - name) : strlen(name) ); + szFinalName[k]='$'; + k++; + for (i=0; ivalue.uint : 0; + prop_val = gf_filter_pid_get_property_first(pid, GF_PROP_PID_HEIGHT); + prop_val_patched.value.vec2i.y = prop_val ? prop_val->value.uint : 0; + prop_val = &prop_val_patched; + is_ok=GF_TRUE; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[Filter] property %s not found for pid, cannot resolve template\n", name)); + return GF_BAD_PARAM; + } + } + + if (!is_ok) { + if (sep) sep[0] = '$'; + return GF_BAD_PARAM; + } + if (prop_val) { + if ((prop_val->type==GF_PROP_UINT) || (prop_val->type==GF_PROP_SINT)) { + value = prop_val->value.uint; + has_val = GF_TRUE; + } else { + str_val = gf_props_dump_val(prop_val, szPropVal, GF_FALSE, NULL); + } + } + szTemplateVal[0]=0; + if (has_val) { + sprintf(szTemplateVal, szFormat, value); + } else if (str_val) { + if (is_file_str) { + char *ext; + char *sname; + + if (!strncmp(str_val, "gfio://", 7)) + str_val = gf_fileio_translate_url(str_val); + + sname = strrchr(str_val, '/'); + if (!sname) sname = strrchr(str_val, '\\'); + if (!sname) sname = (char *) str_val; + else sname++; + ext = strrchr(str_val, '.'); + + if (ext) { + u32 len = (u32) (ext - sname); + strncpy(szTemplateVal, sname, ext - sname); + szTemplateVal[len] = 0; + } else { + strcpy(szTemplateVal, sname); + } + } else { + strcpy(szTemplateVal, str_val); + } + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[Filter] property %s not found for pid, cannot resolve template\n", name)); + return GF_BAD_PARAM; + } + if (k + strlen(szTemplateVal) > GF_MAX_PATH) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("[Filter] Not enough memory to solve file template %s\n", szTemplate)); + return GF_OUT_OF_MEM; + } + + strcat(szFinalName, szTemplateVal); + k = (u32) strlen(szFinalName); + + if (!sep) break; + sep[0] = '$'; + name = sep+1; + } + szFinalName[k] = 0; + return GF_OK; +} + + +GF_EXPORT +GF_Err gf_filter_pid_set_discard(GF_FilterPid *pid, Bool discard_on) +{ + GF_FilterPidInst *pidi = (GF_FilterPidInst *) pid; + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt at discarding packets on output PID %s in filter %s not allowed\n", pid->pid->name, pid->filter->name)); + return GF_BAD_PARAM; + } + if (discard_on) { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Discarding packets on PID %s (filter %s to %s)\n", pid->pid->name, pid->pid->filter->name, pid->filter->name)); + while (gf_filter_pid_get_packet(pid)) { + gf_filter_pid_drop_packet(pid); + } + pidi->is_end_of_stream = GF_TRUE; + } else { + //no more packets in queue or postponed, we can trust the EOS signal on the PID + //otherwise even though the PID has seen the EOS, it is not yet processed by the pid instance, signaling it + //would break up filters (for example dash demux) relying on precise EOS signals which must be toggled at the EOS packet + //once all previous packets have been processed + if (!gf_fq_count(pidi->packets) && !pid->pid->filter->postponed_packets) + pidi->is_end_of_stream = pid->pid->has_seen_eos; + } + pidi->discard_inputs = discard_on; + return GF_OK; +} + +static char *gf_filter_pid_get_dst_string(GF_FilterSession *sess, const char *_args, Bool is_dst) +{ + char *target, *sep; + char szKey[6]; + u32 len; + if (!_args) return NULL; + + if (is_dst) + sprintf(szKey, "dst%c", sess->sep_name); + else + sprintf(szKey, "src%c", sess->sep_name); + + target = strstr(_args, szKey); + if (!target) return NULL; + + sep = (char *) gf_fs_path_escape_colon(sess, target + 4); + target += 4; + if (sep) len = (u32) (sep - target); + else len = (u32) strlen(target); + + char *res = gf_malloc(sizeof(char)* (len+1)); + memcpy(res, target, sizeof(char)* len); + res[len]=0; + return res; +} + + +GF_EXPORT +char *gf_filter_pid_get_destination(GF_FilterPid *pid) +{ + const char *dst_args; + char *res; + u32 i, j; + if (PID_IS_INPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to query destination on input PID %s in filter %s not allowed\n", pid->pid->name, pid->filter->name)); + return NULL; + } + + dst_args = pid->filter->dst_args; + if (!dst_args) dst_args = pid->filter->src_args; + res = gf_filter_pid_get_dst_string(pid->filter->session, dst_args, GF_TRUE); + if (res) return res; + + //if not set this means we have explicetly loaded the filter + for (i=0; inum_destinations; i++) { + GF_FilterPidInst *pidi = gf_list_get(pid->destinations, i); + + dst_args = pidi->filter->dst_args; + if (!dst_args) dst_args = pidi->filter->src_args; + res = gf_filter_pid_get_dst_string(pid->filter->session, dst_args, GF_TRUE); + if (res) return res; + + for (j=0; jfilter->num_output_pids; j++) { + GF_FilterPid *a_pid = gf_list_get(pidi->filter->output_pids, j); + char *dst = gf_filter_pid_get_destination(a_pid); + if (dst) return dst; + } + } + return NULL; +} + +GF_EXPORT +char *gf_filter_pid_get_source(GF_FilterPid *pid) +{ + const char *src_args; + char *res; +// GF_FilterPidInst *pidi = (GF_FilterPidInst *) pid; + u32 i; + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to query source on output PID %s in filter %s not allowed\n", pid->pid->name, pid->filter->name)); + return NULL; + } + pid = pid->pid; + + src_args = pid->filter->src_args; + if (!src_args) src_args = pid->filter->dst_args; + res = gf_filter_pid_get_dst_string(pid->filter->session, src_args, GF_FALSE); + if (res) return res; + + //if not set this means we have explicetly loaded the filter + for (i=0; ifilter->num_input_pids; i++) { + GF_FilterPidInst *pidi = gf_list_get(pid->filter->input_pids, i); + + src_args = pidi->pid->filter->src_args; + if (!src_args) src_args = pidi->pid->filter->dst_args; + res = gf_filter_pid_get_dst_string(pid->filter->session, src_args, GF_FALSE); + if (res) return res; + } + return NULL; +} + +GF_EXPORT +void gf_filter_pid_discard_block(GF_FilterPid *pid) +{ + if (PID_IS_INPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to reset block mode on input PID %s in filter %s not allowed\n", pid->pid->name, pid->filter->name)); + return; + } + if (!pid->has_seen_eos) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Attempt to reset block mode on PID %s in filter %s not in end of stream, ignoring\n", pid->pid->name, pid->filter->name)); + return; + } + gf_mx_p(pid->filter->tasks_mx); + if (pid->would_block) { + safe_int_dec(&pid->would_block); + assert(pid->filter->would_block); + safe_int_dec(&pid->filter->would_block); + } + gf_mx_v(pid->filter->tasks_mx); +} + +GF_EXPORT +GF_Err gf_filter_pid_require_source_id(GF_FilterPid *pid) +{ + if (PID_IS_INPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to set require_source_id input PID %s in filter %s not allowed\n", pid->pid->name, pid->filter->name)); + return GF_BAD_PARAM; + } + pid->require_source_id = GF_TRUE; + return GF_OK; +} + +GF_EXPORT +u32 gf_filter_pid_get_min_pck_duration(GF_FilterPid *pid) +{ + if (PID_IS_OUTPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to query min_pck_duration on output pid PID %s in filter %s not allowed\n", pid->pid->name, pid->filter->name)); + return 0; + } + return pid->pid->min_pck_duration; +} + +GF_EXPORT +void gf_filter_pid_recompute_dts(GF_FilterPid *pid, Bool do_recompute) +{ + if (PID_IS_INPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to set recompute_dts on input pid %s in filter %s not allowed\n", pid->pid->name, pid->filter->name)); + return; + } + pid->recompute_dts = do_recompute; +} + +GF_EXPORT +Bool gf_filter_pid_is_playing(GF_FilterPid *pid) +{ + if (!pid) return GF_FALSE; + return pid->pid->is_playing; + +} + +GF_EXPORT +GF_Err gf_filter_pid_allow_direct_dispatch(GF_FilterPid *pid) +{ + if (PID_IS_INPUT(pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to set direct dispatch mode on input pid %s in filter %s not allowed\n", pid->pid->name, pid->filter->name)); + return GF_BAD_PARAM; + } + if (pid->filter->session->threads) + return GF_OK; + pid->direct_dispatch = GF_TRUE; + return GF_OK; +} + +GF_EXPORT +void *gf_filter_pid_get_alias_udta(GF_FilterPid *_pid) +{ + GF_FilterPidInst *pidi; + if (PID_IS_OUTPUT(_pid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt to query multi_sink original filter context on output pid %s in filter %s not allowed\n", _pid->pid->name, _pid->filter->name)); + return NULL; + } + pidi = (GF_FilterPidInst *) _pid; + if (!pidi->alias_orig) return NULL; + return pidi->alias_orig->filter_udta; +} diff --git a/src/filter_core/filter_props.c b/src/filter_core/filter_props.c new file mode 100644 index 0000000..64959d4 --- /dev/null +++ b/src/filter_core/filter_props.c @@ -0,0 +1,1595 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2019 + * All rights reserved + * + * This file is part of GPAC / filters sub-project + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terfsess of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "filter_session.h" +#include +//for binxml parsing +#include + +GF_PropertyValue gf_props_parse_value(u32 type, const char *name, const char *value, const char *enum_values, char list_sep_char) +{ + GF_PropertyValue p; + char *unit_sep=NULL; + s32 unit = 0; + memset(&p, 0, sizeof(GF_PropertyValue)); + p.value.data.size=0; + p.type=type; + if (!name) name=""; + + unit_sep = NULL; + if (value) { + u32 len = (u32) strlen(value); + unit_sep = len ? strrchr("kKgGmM", value[len-1]) : NULL; + if (unit_sep) { + u8 unit_char = unit_sep[0]; + if ((unit_char=='k') || (unit_char=='K')) unit = 1000; + else if ((unit_char=='m') || (unit_char=='M')) unit = 1000000; + if ((unit_char=='G') || (unit_char=='g')) unit = 1000000000; + } + } + + switch (p.type) { + case GF_PROP_BOOL: + if (!value || !strcmp(value, "yes") || !strcmp(value, "true") || !strcmp(value, "1")) { + p.value.boolean = GF_TRUE; + } else if ( !strcmp(value, "no") || !strcmp(value, "false") ) { + p.value.boolean = GF_FALSE; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for boolean arg %s - using false\n", value, name)); + p.value.boolean = GF_FALSE; + } + break; + case GF_PROP_SINT: + if (value && !strcmp(value, "+I")) p.value.sint = GF_INT_MAX; + else if (value && !strcmp(value, "-I")) p.value.sint = GF_INT_MIN; + else if (!value || (sscanf(value, "%d", &p.value.sint)!=1)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for int arg %s - using 0\n", value, name)); + p.value.sint = 0; + } else if (unit) { + p.value.sint *= unit; + } + break; + case GF_PROP_UINT: + if (value && !strcmp(value, "+I")) p.value.sint = 0xFFFFFFFF; + else if (!value) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for unsigned int arg %s - using 0\n", value, name)); + p.value.uint = 0; + } else if (enum_values && strchr(enum_values, '|')) { + u32 a_len = (u32) strlen(value); + u32 val = 0; + char *str_start = (char *) enum_values; + while (str_start) { + u32 len; + char *sep = strchr(str_start, '|'); + if (sep) { + len = (u32) (sep - str_start); + } else { + len = (u32) strlen(str_start); + } + if ((a_len == len) && !strncmp(str_start, value, len)) + break; + if (!sep) { + str_start = NULL; + break; + } + str_start = sep+1; + val++; + } + if (!str_start) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for unsigned int arg %s enum %s - using 0\n", value, name, enum_values)); + p.value.uint = 0; + } else { + p.value.uint = val; + } + } else if (!strnicmp(value, "0x", 2)) { + if (sscanf(value, "0x%x", &p.value.uint)!=1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for unsigned int arg %s - using 0\n", value, name)); + } else if (unit) { + p.value.uint *= unit; + } + } else if (sscanf(value, "%d", &p.value.uint)!=1) { + if (strlen(value)==4) { + p.value.uint = GF_4CC(value[0],value[1],value[2],value[3]); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for unsigned int arg %s - using 0\n", value, name)); + } + } else if (unit) { + p.value.uint *= unit; + } + break; + case GF_PROP_LSINT: + if (value && !strcmp(value, "+I")) p.value.longsint = 0x7FFFFFFFFFFFFFFFUL; + else if (value && !strcmp(value, "-I")) p.value.longsint = 0x8000000000000000UL; + else if (!value || (sscanf(value, ""LLD, &p.value.longsint)!=1) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for long int arg %s - using 0\n", value, name)); + p.value.uint = 0; + } else if (unit) { + p.value.longsint *= unit; + } + break; + case GF_PROP_LUINT: + if (value && !strcmp(value, "+I")) p.value.longuint = 0xFFFFFFFFFFFFFFFFUL; + else if (!value || (sscanf(value, ""LLU, &p.value.longuint)!=1) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for long unsigned int arg %s - using 0\n", value, name)); + p.value.uint = 0; + } else if (unit) { + p.value.longuint *= unit; + } + break; + case GF_PROP_FRACTION: + if (!value) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for fraction arg %s - using 0/1\n", value, name)); + p.value.frac.num = 0; + p.value.frac.den = 1; + } else { + if (sscanf(value, "%d/%u", &p.value.frac.num, &p.value.frac.den) != 2) { + if (sscanf(value, "%d-%u", &p.value.frac.num, &p.value.frac.den) != 2) { + u32 ret=0; + p.value.frac.den=1; + if (strchr(value, '.') || strchr(value, ',')) { + Float v; + ret = sscanf(value, "%g", &v); + if (ret==1) { + p.value.frac.num = (u32) (v*1000000); + p.value.frac.den = 1000000; + } else { + ret = 0; + } + } + if (!ret && (sscanf(value, "%d", &p.value.frac.num) != 1)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for fraction arg %s - using 0/1\n", value, name)); + p.value.frac.num = 0; + p.value.frac.den = 1; + } + } + } + } + break; + case GF_PROP_FRACTION64: + if (!value) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for fraction arg %s - using 0/1\n", value, name)); + p.value.frac.num = 0; + p.value.frac.den = 1; + } + else { + if (sscanf(value, LLD"/"LLU, &p.value.lfrac.num, &p.value.lfrac.den) != 2) { + if (sscanf(value, LLD"-"LLU, &p.value.lfrac.num, &p.value.lfrac.den) != 2) { + u32 ret=0; + p.value.lfrac.den = 1; + + if (strchr(value, '.') || strchr(value, ',')) { + Float v; + ret = sscanf(value, "%g", &v); + if (ret==1) { + p.value.lfrac.num = (u64) (v*1000000); + p.value.lfrac.den = 1000000; + } else { + ret = 0; + } + } + + if (!ret && (sscanf(value, LLD, &p.value.lfrac.num) != 1)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for fraction arg %s - using 0/1\n", value, name)); + p.value.lfrac.num = 0; + p.value.lfrac.den = 1; + } + } + } + } + break; + case GF_PROP_FLOAT: + if (value && !strcmp(value, "+I")) p.value.fnumber = FIX_MAX; + else if (value && !strcmp(value, "-I")) p.value.fnumber = FIX_MIN; + else if (!value) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for float arg %s - using 0\n", value, name)); + p.value.fnumber = 0; + } else { + Float f; + if (sscanf(value, "%f", &f) != 1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for float arg %s - using 0\n", value, name)); + p.value.fnumber = 0; + } else { + if (unit) f *= unit; + p.value.fnumber = FLT2FIX(f); + } + } + break; + case GF_PROP_DOUBLE: + if (value && !strcmp(value, "+I")) p.value.number = GF_MAX_DOUBLE; + else if (value && !strcmp(value, "-I")) p.value.number = GF_MIN_DOUBLE; + else if (!value || (sscanf(value, "%lg", &p.value.number) != 1) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for double arg %s - using 0\n", value, name)); + p.value.number = 0; + } else if (unit) { + p.value.number *= unit; + } + break; + case GF_PROP_VEC2I: + if (!value || (sscanf(value, "%dx%d", &p.value.vec2i.x, &p.value.vec2i.y) != 2)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for vec2i arg %s - using {0,0}\n", value, name)); + p.value.vec2i.x = p.value.vec2i.y = 0; + } + break; + case GF_PROP_VEC2: + if (!value || (sscanf(value, "%lgx%lg", &p.value.vec2.x, &p.value.vec2.y) != 2)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for vec2 arg %s - using {0,0}\n", value, name)); + p.value.vec2.x = p.value.vec2.y = 0; + } + break; + case GF_PROP_VEC3I: + if (!value || (sscanf(value, "%dx%dx%d", &p.value.vec3i.x, &p.value.vec3i.y, &p.value.vec3i.z) != 3)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for vec3i arg %s - using {0,0,0}\n", value, name)); + p.value.vec3i.x = p.value.vec3i.y = p.value.vec3i.z = 0; + } + break; + case GF_PROP_VEC3: + if (!value || (sscanf(value, "%lgx%lgx%lg", &p.value.vec3.x, &p.value.vec3.y, &p.value.vec3.z) != 3)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for vec3 arg %s - using {0,0,0}\n", value, name)); + p.value.vec3.x = p.value.vec3.y = p.value.vec3.z = 0; + } + break; + case GF_PROP_VEC4I: + if (!value || (sscanf(value, "%dx%dx%dx%d", &p.value.vec4i.x, &p.value.vec4i.y, &p.value.vec4i.z, &p.value.vec4i.w) != 4)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for vec4i arg %s - using {0,0,0}\n", value, name)); + p.value.vec4i.x = p.value.vec4i.y = p.value.vec4i.z = p.value.vec4i.w = 0; + } + break; + case GF_PROP_VEC4: + if (!value || (sscanf(value, "%lgx%lgx%lgx%lg", &p.value.vec4.x, &p.value.vec4.y, &p.value.vec4.z, &p.value.vec4.w) != 4)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %s for vec4 arg %s - using {0,0,0}\n", value, name)); + p.value.vec4.x = p.value.vec4.y = p.value.vec4.z = p.value.vec4.w = 0; + } + break; + case GF_PROP_PIXFMT: + p.value.uint = gf_pixel_fmt_parse(value); + break; + case GF_PROP_PCMFMT: + p.value.uint = gf_audio_fmt_parse(value); + break; + case GF_PROP_NAME: + case GF_PROP_STRING: + case GF_PROP_STRING_NO_COPY: + p.type=GF_PROP_STRING; + if (value && !strnicmp(value, "file@", 5) ) { + u8 *data; + u32 len; + GF_Err e = gf_file_load_data(value+5, (u8 **) &data, &len); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Cannot load data from file %s\n", value+5)); + } else { + p.value.string = data; + } + } else if (value && !strnicmp(value, "bxml@", 5) ) { + GF_Err e; + GF_DOMParser *dom = gf_xml_dom_new(); + e = gf_xml_dom_parse(dom, value+5, NULL, NULL); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Cannot parse XML from file %s\n", value+5)); + } else { + GF_XMLNode *root = gf_xml_dom_get_root_idx(dom, 0); + //if no root, assume NULL + if (root) { + e = gf_xml_parse_bit_sequence(root, value+5, &p.value.data.ptr, &p.value.data.size); + } + if (e<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to binarize XML file %s: %s\n", value+5, gf_error_to_string(e) )); + } else { + p.type=GF_PROP_DATA; + } + } + gf_xml_dom_del(dom); + } else { + p.value.string = value ? gf_strdup(value) : NULL; + } + break; + case GF_PROP_DATA: + case GF_PROP_CONST_DATA: + case GF_PROP_DATA_NO_COPY: + if (!value) { + p.value.data.ptr=NULL; + p.value.data.size=0; + } else if (sscanf(value, "%d@%p", &p.value.data.size,&p.value.data.ptr)==2) { + p.type = GF_PROP_CONST_DATA; + } else if (!strnicmp(value, "0x", 2) ) { + u32 i; + value += 2; + p.value.data.size = (u32) strlen(value) / 2; + p.value.data.ptr = gf_malloc(sizeof(char)*p.value.data.size); + for (i=0; i'); + len = (u32) (sep - v); + if (xml_end) { + u32 xml_len = (u32) (xml_end - v); + if (xml_len > len) { + sep = strchr(xml_end, list_sep_char); + //assigned below +// if (sep) +// len = (u32) (sep - v); + } + } + } + if (!sep) + len = (u32) strlen(v); + else + len = (u32) (sep - v); + + nv = gf_malloc(sizeof(char)*(len+1)); + strncpy(nv, v, sizeof(char)*len); + nv[len] = 0; + if (!strnicmp(nv, "file@", 5) ) { + u8 *data; + u32 flen; + GF_Err e = gf_file_load_data(nv+5, (u8 **) &data, &flen); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Cannot load data from file %s\n", nv+5)); + } else { + gf_list_add(p.value.string_list, data); + } + gf_free(nv); + } else { + gf_list_add(p.value.string_list, nv); + } + if (!sep) break; + v = sep+1; + } + } + break; + case GF_PROP_UINT_LIST: + { + char *v = (char *) value; + if (!list_sep_char) list_sep_char = ','; + while (v && v[0]) { + char szV[100]; + u32 val_uint=0, len=0; + char *sep = strchr(v, list_sep_char); + if (sep) { + len = (u32) (sep - v); + } + if (!sep) + len = (u32) strlen(v); + if (len>=99) len=99; + strncpy(szV, v, len); + szV[len] = 0; + sscanf(szV, "%u", &val_uint); + p.value.uint_list.vals = gf_realloc(p.value.uint_list.vals, (p.value.uint_list.nb_items+1) * sizeof(u32)); + p.value.uint_list.vals[p.value.uint_list.nb_items] = val_uint; + p.value.uint_list.nb_items++; + if (!sep) break; + v = sep+1; + } + } + break; + case GF_PROP_FORBIDEN: + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Forbidden property type %d for arg %s - ignoring\n", type, name)); + p.type=GF_PROP_FORBIDEN; + break; + } +// if (unit_sep) unit_sep[0] = unit_char; + + return p; +} + +Bool gf_props_equal(const GF_PropertyValue *p1, const GF_PropertyValue *p2) +{ + if (p1->type!=p2->type) { + if ((p1->type == GF_PROP_STRING) && (p2->type == GF_PROP_NAME) ) { + } else if ((p2->type == GF_PROP_STRING) && (p1->type == GF_PROP_NAME) ) { + } else { + return GF_FALSE; + } + } + + switch (p1->type) { + case GF_PROP_SINT: return (p1->value.sint==p2->value.sint) ? GF_TRUE : GF_FALSE; + case GF_PROP_PIXFMT: + case GF_PROP_PCMFMT: + case GF_PROP_UINT: + return (p1->value.uint==p2->value.uint) ? GF_TRUE : GF_FALSE; + case GF_PROP_LSINT: return (p1->value.longsint==p2->value.longsint) ? GF_TRUE : GF_FALSE; + case GF_PROP_LUINT: return (p1->value.longuint==p2->value.longuint) ? GF_TRUE : GF_FALSE; + case GF_PROP_BOOL: return (p1->value.boolean==p2->value.boolean) ? GF_TRUE : GF_FALSE; + case GF_PROP_FRACTION: + return ((s64)p1->value.frac.num * (s64)p2->value.frac.den == (s64)p2->value.frac.num * (s64)p1->value.frac.den) ? GF_TRUE : GF_FALSE; + case GF_PROP_FRACTION64: + return ((s64)p1->value.lfrac.num * (s64)p2->value.lfrac.den == (s64)p2->value.lfrac.num * (s64)p1->value.lfrac.den) ? GF_TRUE : GF_FALSE; + + case GF_PROP_FLOAT: return (p1->value.fnumber==p2->value.fnumber) ? GF_TRUE : GF_FALSE; + case GF_PROP_DOUBLE: return (p1->value.number==p2->value.number) ? GF_TRUE : GF_FALSE; + case GF_PROP_VEC2I: + return ((p1->value.vec2i.x==p2->value.vec2i.x) && (p1->value.vec2i.y==p2->value.vec2i.y)) ? GF_TRUE : GF_FALSE; + case GF_PROP_VEC2: + return ((p1->value.vec2.x==p2->value.vec2.x) && (p1->value.vec2.y==p2->value.vec2.y)) ? GF_TRUE : GF_FALSE; + case GF_PROP_VEC3I: + return ((p1->value.vec3i.x==p2->value.vec3i.x) && (p1->value.vec3i.y==p2->value.vec3i.y) && (p1->value.vec3i.z==p2->value.vec3i.z)) ? GF_TRUE : GF_FALSE; + case GF_PROP_VEC3: + return ((p1->value.vec3.x==p2->value.vec3.x) && (p1->value.vec3.y==p2->value.vec3.y) && (p1->value.vec3.z==p2->value.vec3.z)) ? GF_TRUE : GF_FALSE; + case GF_PROP_VEC4I: + return ((p1->value.vec4i.x==p2->value.vec4i.x) && (p1->value.vec4i.y==p2->value.vec4i.y) && (p1->value.vec4i.z==p2->value.vec4i.z) && (p1->value.vec4i.w==p2->value.vec4i.w)) ? GF_TRUE : GF_FALSE; + case GF_PROP_VEC4: + return ((p1->value.vec4.x==p2->value.vec4.x) && (p1->value.vec4.y==p2->value.vec4.y) && (p1->value.vec4.z==p2->value.vec4.z) && (p1->value.vec4.w==p2->value.vec4.w)) ? GF_TRUE : GF_FALSE; + + + case GF_PROP_STRING: + case GF_PROP_NAME: + if (!p1->value.string) return p2->value.string ? GF_FALSE : GF_TRUE; + if (!p2->value.string) return GF_FALSE; + if (!strcmp(p1->value.string, "*")) return GF_TRUE; + if (!strcmp(p2->value.string, "*")) return GF_TRUE; + if (strchr(p2->value.string, '|')) { + u32 len = (u32) strlen(p1->value.string); + char *cur = p2->value.string; + + while (cur) { + if (!strncmp(p1->value.string, cur, len) && (cur[len]=='|' || !cur[len])) + return GF_TRUE; + cur = strchr(cur, '|'); + if (cur) cur++; + } + return GF_FALSE; + } + if (strchr(p1->value.string, '|')) { + u32 len = (u32) strlen(p2->value.string); + char *cur = p1->value.string; + + while (cur) { + if (!strncmp(p2->value.string, cur, len) && (cur[len]=='|' || !cur[len])) + return GF_TRUE; + cur = strchr(cur, '|'); + if (cur) cur++; + } + return GF_FALSE; + } + assert(strchr(p1->value.string, '|')==NULL); + return !strcmp(p1->value.string, p2->value.string) ? GF_TRUE : GF_FALSE; + + case GF_PROP_DATA: + case GF_PROP_CONST_DATA: + if (!p1->value.data.ptr) return p2->value.data.ptr ? GF_FALSE : GF_TRUE; + if (!p2->value.data.ptr) return GF_FALSE; + if (p1->value.data.size != p2->value.data.size) return GF_FALSE; + return !memcmp(p1->value.data.ptr, p2->value.data.ptr, p1->value.data.size) ? GF_TRUE : GF_FALSE; + + case GF_PROP_STRING_LIST: + { + u32 c1, c2, i, j; + c1 = gf_list_count(p1->value.string_list); + c2 = gf_list_count(p2->value.string_list); + if (c1 != c2) return GF_FALSE; + for (i=0; ivalue.string_list, i); + for (j=0; jvalue.string_list, j); + if (s1 && s2 && !strcmp(s1, s2)) found++; + } + if (found!=1) return GF_FALSE; + } + return GF_TRUE; + } + case GF_PROP_UINT_LIST: + { + u32 i; + if (p1->value.uint_list.nb_items != p2->value.uint_list.nb_items) return GF_FALSE; + for (i=0; ivalue.uint_list.nb_items; i++) { + if (p1->value.uint_list.vals[i] != p2->value.uint_list.vals[i]) return GF_FALSE; + } + return GF_TRUE; + } + + //user-managed pointer + case GF_PROP_POINTER: return (p1->value.ptr==p2->value.ptr) ? GF_TRUE : GF_FALSE; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Comparing forbidden property type %d\n", p1->type)); + break; + } + return GF_FALSE; +} + +#if GF_PROPS_HASHTABLE_SIZE +GFINLINE u32 gf_props_hash_djb2(u32 p4cc, const char *str) +{ + u32 hash = 5381; + + if (p4cc) { + hash = ((hash << 5) + hash) + ((p4cc>>24)&0xFF); + hash = ((hash << 5) + hash) + ((p4cc>>16)&0xFF); + hash = ((hash << 5) + hash) + ((p4cc>>8)&0xFF); + hash = ((hash << 5) + hash) + (p4cc&0xFF); + } else { + int c; + while ( (c = *str++) ) + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + + } + return (hash % GF_PROPS_HASHTABLE_SIZE); +} +#endif + +GF_PropertyMap * gf_props_new(GF_Filter *filter) +{ + GF_PropertyMap *map; + + map = gf_fq_pop(filter->session->prop_maps_reservoir); + + if (!map) { + GF_SAFEALLOC(map, GF_PropertyMap); + if (!map) return NULL; + + map->session = filter->session; +#if GF_PROPS_HASHTABLE_SIZE +#else + map->properties = gf_list_new(); +#endif + } + assert(!map->reference_count); + map->reference_count = 1; + return map; +} + +void gf_props_reset_single(GF_PropertyValue *p) +{ + if (p->type==GF_PROP_STRING) { + gf_free(p->value.string); + p->value.string = NULL; + } + else if (p->type==GF_PROP_DATA) { + gf_free(p->value.data.ptr); + p->value.data.ptr = NULL; + p->value.data.size = 0; + } + else if (p->type==GF_PROP_UINT_LIST) { + gf_free(p->value.uint_list.vals); + p->value.uint_list.vals = NULL; + p->value.uint_list.nb_items = 0; + } + else if (p->type==GF_PROP_STRING_LIST) { + while (gf_list_count(p->value.string_list)) { + char *str = gf_list_pop_back(p->value.string_list); + gf_free(str); + } + gf_list_del(p->value.string_list); + p->value.string_list = NULL; + } +} +void gf_props_del_property(GF_PropertyEntry *it) +{ + assert(it->reference_count); + if (safe_int_dec(&it->reference_count) == 0 ) { + if (it->pname && it->name_alloc) + gf_free(it->pname); + + it->name_alloc = GF_FALSE; + + if (it->prop.type==GF_PROP_STRING) { + gf_free(it->prop.value.string); + it->prop.value.string = NULL; + } + else if (it->prop.type==GF_PROP_DATA) { + assert(it->alloc_size); + //DATA props are collected at session level for future reuse + } + //string list are destroyed + else if (it->prop.type==GF_PROP_STRING_LIST) { + GF_List *l = it->prop.value.string_list; + it->prop.value.string_list = NULL; + while (gf_list_count(l)) { + char *s = gf_list_pop_back(l); + gf_free(s); + } + gf_list_del(l); + } + //string list are destroyed + else if (it->prop.type==GF_PROP_UINT_LIST) { + if (it->prop.value.uint_list.vals) + gf_free(it->prop.value.uint_list.vals); + it->prop.value.uint_list.nb_items = 0; + it->prop.value.uint_list.vals = NULL; + } + it->prop.value.data.size = 0; + if (it->alloc_size) { + assert(it->prop.type==GF_PROP_DATA); + if (it->session->prop_maps_entry_data_alloc_reservoir) { + gf_fq_add(it->session->prop_maps_entry_data_alloc_reservoir, it); + } else { + if (it->prop.value.data.ptr) gf_free(it->prop.value.data.ptr); + gf_free(it); + } + } else { + if (it->session->prop_maps_entry_reservoir) { + gf_fq_add(it->session->prop_maps_entry_reservoir, it); + } else { + gf_free(it); + } + } + } +} + +void gf_propmap_del(void *pmap) +{ +#if GF_PROPS_HASHTABLE_SIZE + gf_free(pamp); +#else + GF_PropertyMap *map = pmap; + gf_list_del(map->properties); + gf_free(map); +#endif + +} +void gf_props_reset(GF_PropertyMap *prop) +{ +#if GF_PROPS_HASHTABLE_SIZE + u32 i; + for (i=0; ihash_table[i]) { + GF_List *l = prop->hash_table[i]; + while (gf_list_count(l)) { + gf_props_del_property((GF_PropertyEntry *) gf_list_pop_back(l) ); + } + prop->hash_table[i] = NULL; + if (prop->session->prop_maps_list_reservoir) { + gf_fq_add(prop->session->prop_maps_list_reservoir, l); + } else { + gf_list_del(l); + } + } + } +#else + while (gf_list_count(prop->properties)) { + gf_props_del_property( (GF_PropertyEntry *) gf_list_pop_back(prop->properties) ); + } +#endif +} + +void gf_props_del(GF_PropertyMap *map) +{ + assert(!map->pckrefs_reference_count || !map->reference_count); + //we still have a ref + if (map->pckrefs_reference_count || map->reference_count) return; + + gf_props_reset(map); + map->reference_count = 0; + if (map->session->prop_maps_reservoir) { + gf_fq_add(map->session->prop_maps_reservoir, map); + } else { + gf_list_del(map->properties); + gf_free(map); + } +} + + + +//purge existing property of same name +void gf_props_remove_property(GF_PropertyMap *map, u32 hash, u32 p4cc, const char *name) +{ +#if GF_PROPS_HASHTABLE_SIZE + if (map->hash_table[hash]) { + u32 i, count = gf_list_count(map->hash_table[hash]); + for (i=0; ihash_table[hash], i); + if ((p4cc && (p4cc==prop->p4cc)) || (name && prop->pname && !strcmp(prop->pname, name)) ) { + gf_list_rem(map->hash_table[hash], i); + gf_props_del_property(prop); + break; + } + } + } +#else + u32 i, count = gf_list_count(map->properties); + for (i=0; iproperties, i); + if ((p4cc && (p4cc==prop->p4cc)) || (name && prop->pname && !strcmp(prop->pname, name)) ) { + gf_list_rem(map->properties, i); + gf_props_del_property(prop); + break; + } + } +#endif +} + + +#if GF_PROPS_HASHTABLE_SIZE +GF_List *gf_props_get_list(GF_PropertyMap *map) +{ + GF_List *l; + + l = gf_fq_pop(map->session->prop_maps_list_reservoir); + + if (!l) l = gf_list_new(); + return l; +} +#endif + +static void gf_props_assign_value(GF_PropertyEntry *prop, const GF_PropertyValue *value, Bool is_old_prop) +{ + char *src_ptr; + //remember source pointer + src_ptr = prop->prop.value.data.ptr; + + if (is_old_prop) { + gf_props_reset_single(&prop->prop); + } + + //copy prop value + memcpy(&prop->prop, value, sizeof(GF_PropertyValue)); + + if (prop->prop.type == GF_PROP_STRING) { + prop->prop.value.string = value->value.string ? gf_strdup(value->value.string) : NULL; + } else if (prop->prop.type == GF_PROP_STRING_NO_COPY) { + prop->prop.value.string = value->value.string; + prop->prop.type = GF_PROP_STRING; + } else if (prop->prop.type == GF_PROP_DATA) { + //restore source pointer, realloc if needed + prop->prop.value.data.ptr = src_ptr; + if (prop->alloc_size < value->value.data.size) { + prop->alloc_size = value->value.data.size; + prop->prop.value.data.ptr = gf_realloc(prop->prop.value.data.ptr, sizeof(char) * value->value.data.size); + assert(prop->alloc_size); + } + memcpy(prop->prop.value.data.ptr, value->value.data.ptr, value->value.data.size); + } else if (prop->prop.type == GF_PROP_DATA_NO_COPY) { + prop->prop.type = GF_PROP_DATA; + prop->alloc_size = value->value.data.size; + assert(prop->alloc_size); + } + else if (prop->prop.type == GF_PROP_UINT_LIST) { + prop->prop.value.uint_list.vals = gf_malloc(sizeof(u32) * value->value.uint_list.nb_items); + memcpy(prop->prop.value.uint_list.vals, value->value.uint_list.vals, sizeof(u32) * value->value.uint_list.nb_items); + prop->prop.value.uint_list.nb_items = value->value.uint_list.nb_items; + } +} + +GF_Err gf_props_insert_property(GF_PropertyMap *map, u32 hash, u32 p4cc, const char *name, char *dyn_name, const GF_PropertyValue *value) +{ + GF_PropertyEntry *prop; +#if GF_PROPS_HASHTABLE_SIZE + u32 i, count; +#endif + if ((value->type == GF_PROP_DATA) || (value->type == GF_PROP_DATA_NO_COPY)) { + if (!value->value.data.ptr) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Attempt at defining data property %s with NULL pointer, not allowed\n", p4cc ? gf_4cc_to_str(p4cc) : name ? name : dyn_name )); + return GF_BAD_PARAM; + } + } +#if GF_PROPS_HASHTABLE_SIZE + if (! map->hash_table[hash] ) { + map->hash_table[hash] = gf_props_get_list(map); + if (!map->hash_table[hash]) return GF_OUT_OF_MEM; + } else { + count = gf_list_count(map->hash_table[hash]); + if (count) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("PropertyMap hash collision for %s - %d entries before insertion:\n", p4cc ? gf_4cc_to_str(p4cc) : name ? name : dyn_name, gf_list_count(map->hash_table[hash]) )); + for (i=0; ihash_table[hash], i); + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("\t%s\n\n", prop_c->pname ? prop_c->pname : gf_4cc_to_str(prop_c->p4cc) )); + assert(!prop_c->p4cc || (prop_c->p4cc != p4cc)); + } + } + } +#endif + if ((value->type == GF_PROP_DATA) && value->value.data.ptr) { + prop = gf_fq_pop(map->session->prop_maps_entry_data_alloc_reservoir); + } else { + prop = gf_fq_pop(map->session->prop_maps_entry_reservoir); + } + + if (!prop) { + GF_SAFEALLOC(prop, GF_PropertyEntry); + if (!prop) return GF_OUT_OF_MEM; + prop->session = map->session; + } + + prop->reference_count = 1; + prop->p4cc = p4cc; + prop->pname = (char *) name; + if (dyn_name) { + prop->pname = gf_strdup(dyn_name); + prop->name_alloc=GF_TRUE; + } + + gf_props_assign_value(prop, value, GF_FALSE); + +#if GF_PROPS_HASHTABLE_SIZE + return gf_list_add(map->hash_table[hash], prop); +#else + return gf_list_add(map->properties, prop); +#endif + +} + +GF_Err gf_props_set_property(GF_PropertyMap *map, u32 p4cc, const char *name, char *dyn_name, const GF_PropertyValue *value) +{ + GF_Err e; + u32 hash = gf_props_hash_djb2(p4cc, name ? name : dyn_name); + gf_mx_p(map->session->info_mx); + gf_props_remove_property(map, hash, p4cc, name ? name : dyn_name); + if (!value) + e = GF_OK; + else + e = gf_props_insert_property(map, hash, p4cc, name, dyn_name, value); + gf_mx_v(map->session->info_mx); + return e; +} + +const GF_PropertyEntry *gf_props_get_property_entry(GF_PropertyMap *map, u32 prop_4cc, const char *name) +{ + u32 i, count; + const GF_PropertyEntry *res=NULL; +#if GF_PROPS_HASHTABLE_SIZE + u32 hash = gf_props_hash_djb2(prop_4cc, name); + if (map->hash_table[hash] ) { + count = gf_list_count(map->hash_table[hash]); + for (i=0; ihash_table[hash], i); + + if ((prop_4cc && (p->p4cc==prop_4cc)) || (p->pname && name && !strcmp(p->pname, name)) ) { + res = p; + break; + } + } + } +#else + count = gf_list_count(map->properties); + for (i=0; iproperties, i); + if (!p) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Concurrent read/write access to property map, cannot query property now\n")); + return NULL; + } + + if ((prop_4cc && (p->p4cc==prop_4cc)) || (p->pname && name && !strcmp(p->pname, name)) ) { + res = p; + break; + } + } +#endif + return res; +} + +const GF_PropertyValue *gf_props_get_property(GF_PropertyMap *map, u32 prop_4cc, const char *name) +{ + const GF_PropertyEntry *ent = gf_props_get_property_entry(map, prop_4cc, name); + if (ent) return &ent->prop; + return NULL; +} + +GF_Err gf_props_merge_property(GF_PropertyMap *dst_props, GF_PropertyMap *src_props, gf_filter_prop_filter filter_prop, void *cbk) +{ + GF_Err e; + u32 i, count; +#if GF_PROPS_HASHTABLE_SIZE + u32 idx; +#endif + GF_List *list; + if (src_props->timescale) + dst_props->timescale = src_props->timescale; + +#if GF_PROPS_HASHTABLE_SIZE + for (idx=0; idxhash_table[idx]) { + list = src_props->hash_table[idx]; +#else + list = src_props->properties; +#endif + count = gf_list_count(list); + for (i=0; ireference_count); + if (!filter_prop || filter_prop(cbk, prop->p4cc, prop->pname, &prop->prop)) { + safe_int_inc(&prop->reference_count); + +#if GF_PROPS_HASHTABLE_SIZE + if (!dst_props->hash_table[idx]) { + dst_props->hash_table[idx] = gf_props_get_list(dst_props); + if (!dst_props->hash_table[idx]) return GF_OUT_OF_MEM; + } + e = gf_list_add(dst_props->hash_table[idx], prop); + if (e) return e; +#else + e = gf_list_add(dst_props->properties, prop); + if (e) return e; +#endif + } + } +#if GF_PROPS_HASHTABLE_SIZE + } + } +#endif + return GF_OK; +} + +const GF_PropertyValue *gf_props_enum_property(GF_PropertyMap *props, u32 *io_idx, u32 *prop_4cc, const char **prop_name) +{ +#if GF_PROPS_HASHTABLE_SIZE + u32 i, nb_items = 0; +#endif + u32 idx, count; + + const GF_PropertyEntry *pe; + if (!io_idx) return NULL; + + idx = *io_idx; + if (idx == 0xFFFFFFFF) return NULL; + +#if GF_PROPS_HASHTABLE_SIZE + for (i=0; ihash_table[i]) { + count = gf_list_count(props->hash_table[i]); + nb_items+=count; + if (idx >= count) { + idx -= count; + continue; + } + pe = gf_list_get(props->hash_table[i], idx); + if (!pe) { + *io_idx = nb_items; + return NULL; + } + if (prop_4cc) *prop_4cc = pe->p4cc; + if (prop_name) *prop_name = pe->pname; + *io_idx = (*io_idx) + 1; + return &pe->prop; + } + } + *io_idx = nb_items; + return NULL; +#else + count = gf_list_count(props->properties); + if (idx >= count) { + *io_idx = count; + return NULL; + } + pe = gf_list_get(props->properties, idx); + if (!pe) { + *io_idx = count; + return NULL; + } + if (prop_4cc) *prop_4cc = pe->p4cc; + if (prop_name) *prop_name = pe->pname; + *io_idx = (*io_idx) + 1; + return &pe->prop; +#endif +} + +typedef struct +{ + GF_PropType type; + const char *name; + const char *desc; +} GF_PropTypeDef; + +GF_PropTypeDef PropTypes[] = +{ + {GF_PROP_SINT, "sint", "signed 32 bit integer"}, + {GF_PROP_UINT, "uint", "unsigned 32 bit integer"}, + {GF_PROP_LSINT, "lsint", "signed 64 bit integer"}, + {GF_PROP_LUINT, "luint", "unsigned 32 bit integer"}, + {GF_PROP_FRACTION, "frac", "32/32 bit fraction"}, + {GF_PROP_FRACTION64, "lfrac", "64/64 bit fraction"}, + {GF_PROP_BOOL, "bool", "boolean"}, + {GF_PROP_FLOAT, "flt", "32 bit float number"}, + {GF_PROP_DOUBLE, "dbl", "64 bit float number"}, + {GF_PROP_NAME, "cstr", "const UTF-8 string"}, + {GF_PROP_STRING, "str", "UTF-8 string"}, + {GF_PROP_STRING_NO_COPY, "str", "UTF-8 string"}, + {GF_PROP_DATA, "mem", "data buffer"}, + {GF_PROP_DATA_NO_COPY, "mem", "data buffer"}, + {GF_PROP_CONST_DATA, "cmem", "const data buffer"}, + {GF_PROP_POINTER, "ptr", "32 or 64 bit pointer"}, + {GF_PROP_VEC2I, "v2di", "2D 32-bit integer vector"}, + {GF_PROP_VEC2, "v2df", "2D 32-bit float vector"}, + {GF_PROP_VEC3I, "v3di", "3D 32-bit integer vector"}, + {GF_PROP_VEC3, "v3df", "3D 32-bit float vector"}, + {GF_PROP_VEC4I, "v4di", "4D 32-bit integer vector"}, + {GF_PROP_VEC4, "v4df", "4D 32-bit float vector"}, + {GF_PROP_PIXFMT, "pfmt", "raw pixel format"}, + {GF_PROP_PCMFMT, "afmt", "raw audio format"}, + {GF_PROP_STRING_LIST, "strl", "UTF-8 string list"}, + {GF_PROP_UINT_LIST, "uintl", "unsigned 32 bit integer list"} +}; + +GF_EXPORT +const char *gf_props_get_type_name(GF_PropType type) +{ + u32 i, nb_props = sizeof(PropTypes) / sizeof(GF_PropTypeDef); + for (i=0; i=0\n- 3: play/pause/seek, reverse playback", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_SCALABLE, "Scalable", "Scalable stream", GF_PROP_BOOL}, + { GF_PROP_PID_TILE_BASE, "TileBase", "Tile base stream", GF_PROP_BOOL}, + { GF_PROP_PID_LANGUAGE, "Language", "Language code: ISO639 2/3 character code or RFC 4646", GF_PROP_NAME}, + { GF_PROP_PID_SERVICE_NAME, "ServiceName", "Name of parent service", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_SERVICE_PROVIDER, "ServiceProvider", "Provider of parent service", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_STREAM_TYPE, "StreamType", "Media stream type", GF_PROP_UINT}, + { GF_PROP_PID_SUBTYPE, "StreamSubtype", "Media subtype 4CC (auxiliary, pic sequence, etc ..)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_ISOM_SUBTYPE, "ISOMSubtype", "ISOM media subtype 4CC (avc1 avc2...)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_ORIG_STREAM_TYPE, "OrigStreamType", "Original stream type before encryption", GF_PROP_UINT}, + { GF_PROP_PID_CODECID, "CodecID", "Codec ID (MPEG-4 OTI or ISOBMFF 4CC)", GF_PROP_UINT}, + { GF_PROP_PID_IN_IOD, "InitialObjectDescriptor", "Indicates if pid is declared in the IOD for MPEG-4", GF_PROP_BOOL}, + { GF_PROP_PID_UNFRAMED, "Unframed", "Indicates that the media data is not framed, i.e. each packet is not a complete AU/frame or is not in internal format (eg annexB for avc/hevc, adts for aac)", GF_PROP_BOOL}, + { GF_PROP_PID_UNFRAMED_FULL_AU, "UnframedAU", "Indicates that the unframed media still has correct AU boundaries: one packet is one full AU, but the packet format might not be the internal one (eg annexB for avc/hevc, adts for aac)", GF_PROP_BOOL}, + { GF_PROP_PID_UNFRAMED_LATM, "LATM", "Indicates media is unframed AAC in LATM format", GF_PROP_BOOL}, + { GF_PROP_PID_DURATION, "Duration", "Media duration", GF_PROP_FRACTION64}, + { GF_PROP_PID_NB_FRAMES, "NumFrames", "Number of frames in the stream", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_FRAME_SIZE, "ConstantFrameSize", "Size of the frames for constant frame size streams", GF_PROP_UINT}, + { GF_PROP_PID_TIMESHIFT_DEPTH, "TimeshiftDepth", "Depth of the timeshift buffer", GF_PROP_FRACTION, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_TIMESHIFT_TIME, "TimeshiftTime", "Time in the timeshift buffer in seconds - changes are signaled through pid info (no reconfigure)", GF_PROP_DOUBLE, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_TIMESHIFT_STATE, "TimeshiftState", "State of timeshift buffer: 0 is OK, 1 is underflow, 2 is overflow - changes are signaled through pid info (no reconfigure)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_TIMESCALE, "Timescale", "Media timescale (a timestamp delta of N is N/timescale seconds)", GF_PROP_UINT}, + { GF_PROP_PID_PROFILE_LEVEL, "ProfileLevel", "MPEG-4 profile and level", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_DECODER_CONFIG, "DecoderConfig", "Decoder configuration data", GF_PROP_DATA}, + { GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, "DecoderConfigEnhancement", "Decoder configuration data of the enhancement layer(s). Also used by 3GPP/Apple text streams to give the full sample description table used in SDP.", GF_PROP_DATA}, + { GF_PROP_PID_CONFIG_IDX, "DecoderConfigIndex", "1-based index of decoder config for ISO base media files", GF_PROP_UINT}, + { GF_PROP_PID_SAMPLE_RATE, "SampleRate", "Audio sample rate", GF_PROP_UINT}, + { GF_PROP_PID_SAMPLES_PER_FRAME, "SamplesPerFrame", "Number of audio sample in one coded frame", GF_PROP_UINT}, + { GF_PROP_PID_NUM_CHANNELS, "NumChannels", "Number of audio channels", GF_PROP_UINT}, + { GF_PROP_PID_AUDIO_BPS, "BPS", "Number of bits per sample in compressed source", GF_PROP_UINT}, + { GF_PROP_PID_CHANNEL_LAYOUT, "ChannelLayout", "Channel Layout mask", GF_PROP_LUINT}, + { GF_PROP_PID_AUDIO_FORMAT, "AudioFormat", "Audio sample format", GF_PROP_PCMFMT}, + { GF_PROP_PID_AUDIO_SPEED, "AudioPlaybackSpeed", "Audio playback speed, only used for audio output reconfiguration", GF_PROP_DOUBLE, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_DELAY, "Delay", "Delay of presentation compared to composition timestamps, in media timescale. Positive value imply holding (delaying) the stream. Negative value imply skipping the beginning of stream", GF_PROP_SINT}, + { GF_PROP_PID_CTS_SHIFT, "CTSShift", "CTS offset to apply in case of negative ctts", GF_PROP_UINT}, + { GF_PROP_PID_WIDTH, "Width", "Visual Width (video / text / graphics)", GF_PROP_UINT}, + { GF_PROP_PID_HEIGHT, "Height", "Visual Height (video / text / graphics)", GF_PROP_UINT}, + { GF_PROP_PID_PIXFMT, "PixelFormat", "Pixel format", GF_PROP_PIXFMT}, + { GF_PROP_PID_PIXFMT_WRAPPED, "PixelFormatWrapped", "Underlying pixel format of video stream if pixel format is external GL texture", GF_PROP_PIXFMT}, + { GF_PROP_PID_STRIDE, "Stride", "Image or Y/alpha plane stride", GF_PROP_UINT}, + { GF_PROP_PID_STRIDE_UV, "StrideUV", "UV plane or U/V planes stride", GF_PROP_UINT}, + { GF_PROP_PID_BIT_DEPTH_Y, "BitDepthLuma", "Bit depth for luma components", GF_PROP_UINT}, + { GF_PROP_PID_BIT_DEPTH_UV, "BitDepthChroma", "Bit depth for chroma components", GF_PROP_UINT}, + { GF_PROP_PID_FPS, "FPS", "Video framerate", GF_PROP_FRACTION}, + { GF_PROP_PID_INTERLACED, "Interlaced", "Video is interlaced", GF_PROP_BOOL}, + { GF_PROP_PID_SAR, "SAR", "Sample (ie pixel) aspect ratio", GF_PROP_FRACTION}, + { GF_PROP_PID_PAR, "PAR", "Picture aspect ratio", GF_PROP_FRACTION, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_WIDTH_MAX, "MaxWidth", "Maximum width (video / text / graphics) of all enhancement layers", GF_PROP_UINT}, + { GF_PROP_PID_HEIGHT_MAX, "MaxHeight", "Maximum height (video / text / graphics) of all enhancement layers", GF_PROP_UINT}, + { GF_PROP_PID_ZORDER, "ZOrder", "Z-order of the video, from 0 (first) to max int (last)", GF_PROP_UINT}, + { GF_PROP_PID_TRANS_X, "TransX", "Horizontal translation of the video", GF_PROP_SINT}, + { GF_PROP_PID_TRANS_Y, "TransY", "Vertical translation of the video", GF_PROP_SINT}, + { GF_PROP_PID_HIDDEN, "Hidden", "Indicates the PID is hidden in visual/audio rendering", GF_PROP_BOOL}, + + { GF_PROP_PID_CROP_POS, "CropOrigin", "Position in source window, X,Y indicates coord in source", GF_PROP_VEC2I}, + { GF_PROP_PID_ORIG_SIZE, "OriginalSize", "Original resolution of video", GF_PROP_VEC2I}, + { GF_PROP_PID_SRD, "SRD", "Position and size of the video in the referential given by SRDRef", GF_PROP_VEC4I}, + { GF_PROP_PID_SRD_REF, "SRDRef", "Width and Height of the SRD referential", GF_PROP_VEC2I}, + { GF_PROP_PID_SRD_MAP, "SRDMap", "Mapping of input videos in reconstructed video, expressed as {Ox,Oy,Ow,Oh,Dx,Dy,Dw,Dh} per input, with:\n" + "- Ox,Oy,Ow,Oh: position and size of the input video (usually matching its `SRD` property), expressed in the output referential given by `SRDRef`\n" + "- Dx,Dy,Dw,Dh: Position and Size of the input video in the reconstructed output, expressed in the output referential given by `SRDRef`", GF_PROP_UINT_LIST}, + + { GF_PROP_PID_ALPHA, "Alpha", "Indicates the video in this pid is an alpha map", GF_PROP_BOOL}, + { GF_PROP_PID_BITRATE, "Bitrate", "Bitrate in bps", GF_PROP_UINT}, + { GF_PROP_PID_MAXRATE, "Maxrate", "Max bitrate in bps", GF_PROP_UINT}, + { GF_PROP_PID_DBSIZE, "DBSize", "Decode buffer size in bytes", GF_PROP_UINT}, + { GF_PROP_PID_MEDIA_DATA_SIZE, "MediaDataSize", "Size in bytes of media data", GF_PROP_LUINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_CAN_DATAREF, "DataRef", "Data referencing is possible (each compressed frame is a continuous set of bytes in source, with no transformation)", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_URL, "URL", "URL of source", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_REMOTE_URL, "RemoteURL", "Remote URL of source - used for MPEG-4 systems", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_REDIRECT_URL, "RedirectURL", "Redirection URL of source", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_FILEPATH, "SourcePath", "Path of source file on file system", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_MIME, "MIMEType", "MIME type of source", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_FILE_EXT, "Extension", "File extension of source", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_FILE_CACHED, "Cached", "indicates the file is completely cached", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_DOWN_RATE, "DownloadRate", "Dowload rate of resource in bits per second - changes are signaled through pid info (no reconfigure)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_DOWN_SIZE, "DownloadSize", "Size of resource in bytes", GF_PROP_LUINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_DOWN_BYTES, "DownBytes", "Number of bytes downloaded - changes are signaled through pid info (no reconfigure)", GF_PROP_LUINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_FILE_RANGE, "ByteRange", "Byte range of resource", GF_PROP_FRACTION64, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_DISABLE_PROGRESSIVE, "DisableProgressive", "indicates that some blocks in file need patching (replace or insertion) upon closing, potentially disabling progressive upload", GF_PROP_UINT, 0}, + { GF_PROP_PID_ISOM_BRANDS, "IsoAltBrands", "indicates ISOBMFF brands associated with PID/file", GF_PROP_UINT_LIST, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_ISOM_MBRAND, "IsoBrand", "indicates ISOBMFF major brand associated with PID/file", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_ISOM_MOVIE_TIME, "MovieTime", "indicates ISOBMFF movie header duration and timescale", GF_PROP_FRACTION64, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_HAS_SYNC, "HasSync", "indicates ISOBMFF track has sync points", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM}, + + { GF_PROP_SERVICE_WIDTH, "ServiceWidth", "Display width of service", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_SERVICE_HEIGHT, "ServiceHeight", "Display height of service", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_CAROUSEL_RATE, "CarouselRate", "Repeat rate in ms for systems carousel data", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_UTC_TIME, "UTC", "UTC date and time of PID", GF_PROP_LUINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_UTC_TIMESTAMP, "UTCTimestamp", "Timestamp corresponding to UTC date and time", GF_PROP_LUINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_AUDIO_VOLUME, "AudioVolume", "Volume of audio", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_AUDIO_PAN, "AudioPan", "Balance/Pan of audio", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_AUDIO_PRIORITY, "AudioPriority", "Audio thread priority", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_PROTECTION_SCHEME_TYPE, "ProtectionScheme", "Protection scheme type (4CC) used", GF_PROP_UINT}, + { GF_PROP_PID_PROTECTION_SCHEME_VERSION, "SchemeVersion", "Protection scheme version used", GF_PROP_UINT}, + { GF_PROP_PID_PROTECTION_SCHEME_URI, "SchemeURI", "Protection scheme URI", GF_PROP_STRING}, + { GF_PROP_PID_PROTECTION_KMS_URI, "KMS_URI", "URI for key management system", GF_PROP_STRING}, + { GF_PROP_PID_ISMA_SELECTIVE_ENC, "SelectiveEncryption", "Indicates if ISMA/OMA selective encryption is used", GF_PROP_BOOL}, + { GF_PROP_PID_ISMA_IV_LENGTH, "IVLength", "ISMA IV size", GF_PROP_UINT}, + { GF_PROP_PID_ISMA_KI_LENGTH, "KILength", "ISMA KeyIndication size", GF_PROP_UINT}, + { GF_PROP_PID_OMA_CRYPT_TYPE, "CryptType", "OMA encryption type", GF_PROP_UINT}, + { GF_PROP_PID_OMA_CID, "ContentID", "OMA Content ID", GF_PROP_STRING}, + { GF_PROP_PID_OMA_TXT_HDR, "TextualHeaders", "OMA textual headers", GF_PROP_STRING}, + { GF_PROP_PID_OMA_CLEAR_LEN, "PlaintextLen", "OMA size of plaintext data", GF_PROP_LUINT}, + { GF_PROP_PID_CRYPT_INFO, "CryptInfo", "URL (local file only) of crypt info file for this pid", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_DECRYPT_INFO, "DecryptInfo", "URL (local file only) of crypt info file for this pid - see decrypter help", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PCK_SENDER_NTP, "SenderNTP", "NTP time at sender side or grabber side", GF_PROP_LUINT, GF_PROP_FLAG_PCK | GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PCK_RECEIVER_NTP, "ReceiverNTP", "Receiver NTP time (usually associated with the sender NTP property)", GF_PROP_LUINT, GF_PROP_FLAG_PCK | GF_PROP_FLAG_GSF_REM}, + + { GF_PROP_PID_ENCRYPTED, "Encrypted", "Packets for the stream are by default encrypted (however the encryption state is carried in packet crypt flags) - changes are signaled through pid_set_info (no reconfigure)", GF_PROP_BOOL}, + { GF_PROP_PID_OMA_PREVIEW_RANGE, "OMAPreview", "OMA Preview range ", GF_PROP_LUINT}, + { GF_PROP_PID_CENC_PSSH, "CENC_PSSH", "PSSH blob for CENC, formatted as (u32)NbSystems [ (bin128)SystemID(u32)version(u32)KID_count[ (bin128)keyID ] (u32)priv_size(char*priv_size)priv_data]", GF_PROP_DATA}, + { GF_PROP_PCK_CENC_SAI, "CENC_SAI", "CENC SAI for the packet, formated as (char(IV_Size))IV(u16)NbSubSamples [(u16)ClearBytes(u32)CryptedBytes]", GF_PROP_DATA, GF_PROP_FLAG_PCK}, + { GF_PROP_PID_KID, "KID", "Key ID for packets of the PID - changes are signaled through pid_set_info (no reconfigure)", GF_PROP_DATA}, + { GF_PROP_PID_CENC_IV_SIZE, "IVSize", "IV size for packets of the PID", GF_PROP_UINT}, + { GF_PROP_PID_CENC_IV_CONST, "ConstantIV", "Constant IV for packets of the PID", GF_PROP_DATA}, + { GF_PROP_PID_CENC_PATTERN, "CENCPattern", "CENC crypt pattern, CENC pattern, skip as frac.num crypt as frac.den", GF_PROP_FRACTION}, + { GF_PROP_PID_CENC_STORE, "CENCStore", "Storage location 4CC of SAI data", GF_PROP_UINT}, + { GF_PROP_PID_CENC_STSD_MODE, "CENCstsdMode", "Mode for CENC sample description when using clear samples:\n" + "- 0: single sample description is used\n" + "- 1: a clear clone of the sample description is created, inserted before the CENC sample description\n" + "- 2: a clear clone of the sample description is created, inserted after the CENC sample description", GF_PROP_UINT}, + { GF_PROP_PID_AMR_MODE_SET, "AMRModeSet", "ModeSet for AMR and AMR-WideBand", GF_PROP_UINT}, + { GF_PROP_PCK_SUBS, "SubSampleInfo", "Binary blob describing N subsamples of the sample, formatted as N [(u32)flags(u32)size(u32)codec_param(u8)priority(u8) discardable]. Subsamples for a given flag MUST appear in order, however flags can be interleaved", GF_PROP_DATA}, + { GF_PROP_PID_MAX_NALU_SIZE, "NALUMaxSize", "Max size of NAL units in stream - changes are signaled through pid_set_info (no reconfigure)", GF_PROP_UINT}, + { GF_PROP_PCK_FILENUM, "FileNumber", "Index of file when dumping to files", GF_PROP_UINT, GF_PROP_FLAG_PCK}, + { GF_PROP_PCK_FILENAME, "FileName", "Name of output file when dumping / dashing. Must be set on first packet belonging to new file", GF_PROP_STRING, GF_PROP_FLAG_PCK}, + { GF_PROP_PCK_IDXFILENAME, "IDXName", "Name of index file when dashing MPEG-2 TS. Must be set on first packet belonging to new file", GF_PROP_STRING, GF_PROP_FLAG_PCK}, + { GF_PROP_PCK_FILESUF, "FileSuffix", "File suffix name, replacement for $FS$ in tile templates", GF_PROP_STRING, GF_PROP_FLAG_PCK}, + + { GF_PROP_PCK_EODS, "EODS", "End of DASH segment", GF_PROP_BOOL, GF_PROP_FLAG_PCK}, + { GF_PROP_PID_MAX_FRAME_SIZE, "MaxFrameSize", "Max size of frame in stream - changes are signaled through pid_set_info (no reconfigure)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_AVG_FRAME_SIZE, "AvgFrameSize", "Average size of frame in stream (isobmff only, static property)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_MAX_TS_DELTA, "MaxTSDelta", "Maximum DTS delta between frames (isobmff only, static property)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_MAX_CTS_OFFSET, "MaxCTSOffset", "Maximum absolute CTS offset (isobmff only, static property)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_CONSTANT_DURATION, "ConstantDuration", "Constant duration of samples, 0 means variable duration (isobmff only, static property)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_ISOM_TRACK_TEMPLATE, "TrackTemplate", "ISOBMFF serialized track box for this PID, without any sample info (empty stbl and empty dref) - used by isomuxer to reinject specific boxes of input ISOBMFF track", GF_PROP_DATA, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_ISOM_TREX_TEMPLATE, "TrexTemplate", "ISOBMFF serialized trex box for this PID - used by isomuxer to remux empty init segments", GF_PROP_DATA, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_ISOM_STSD_TEMPLATE, "STSDTemplate", "ISOBMFF serialized sample description box (stsd entry) for this PID - used by isomuxer to reinject specific boxes of input ISOBMFF track", GF_PROP_DATA, GF_PROP_FLAG_GSF_REM}, + + { GF_PROP_PID_ISOM_UDTA, "MovieUserData", "ISOBMFF serialized moov UDTA and other moov-level boxes (list) for this PID - used by isomuxer to reinject specific boxes of input ISOBMFF moov", GF_PROP_DATA, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_ISOM_HANDLER, "TrackHandler", "ISOBMFF track handler name", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_ISOM_TRACK_FLAGS, "TrackFlags", "ISOBMFF track header flags", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_ISOM_TRACK_MATRIX, "TrackMatrix", "ISOBMFF track header matrix", GF_PROP_UINT_LIST, GF_PROP_FLAG_GSF_REM}, + + { GF_PROP_PID_PERIOD_ID, "Period", "ID of DASH period", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_PERIOD_START, "PStart", "DASH Period start - cf dasher help", GF_PROP_DOUBLE, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_PERIOD_DUR, "PDur", "DASH Period duration - cf dasher help", GF_PROP_DOUBLE, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_REP_ID, "Representation", "ID of DASH representation", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_AS_ID, "ASID", "ID of parent DASH AS", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_MUX_SRC, "MuxSrc", "Name of mux source(s), set by dasher to direct its outputs", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_DASH_MODE, "DashMode", "DASH mode to be used by muxer if any, set by dasher. 0 is no DASH, 1 is regular DASH, 2 is VoD", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_DASH_DUR, "DashDur", "DASH target segment duration in seconds to muxer if any, set by dasher", GF_PROP_DOUBLE, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_DASH_MULTI_PID, NULL, "Pointer to the GF_List of input pids for multi-stsd entries segments, set by dasher", GF_PROP_POINTER, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_DASH_MULTI_PID_IDX, NULL, "1-based index of PID in the multi PID list, set by dasher", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_DASH_MULTI_TRACK, NULL, "Pointer to the GF_List of input pids for multi-tracks segments, set by dasher", GF_PROP_POINTER, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_ROLE, "Role", "List of roles for this pid", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_PERIOD_DESC, "PDesc", "List of descriptors for the DASH period containing this pid", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_AS_COND_DESC, "ASDesc", "List of conditional descriptors for the DASH AdaptationSet containing this pid. If a pid with the same property type but different value is found, the pids will be in different AdaptationSets", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_AS_ANY_DESC, "ASCDesc", "List of common descriptors for the DASH AdaptationSet containing this pid", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_REP_DESC, "RDesc", "List of descriptors for the DASH Representation containing this pid", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_BASE_URL, "BUrl", "List of base URLs for this pid", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_TEMPLATE, "Template", "Template to use for DASH generation for this pid", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_START_NUMBER, "StartNumber", "Start number to use for this pid - cf dasher help", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_XLINK, "xlink", "Remote period URL for DASH - cf dasher help", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_CLAMP_DUR, "CDur", "Max media duration to process from pid in DASH mode", GF_PROP_DOUBLE, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_HLS_PLAYLIST, "HLSPL", "Name of the HLS variant playlist for this media", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_HLS_GROUPID, "HLSGroup", "Name of HLS Group of a stream", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_DASH_CUE, "DCue", "Name of a cue list file for this pid - see dasher help", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_DASH_SEGMENTS, "DSegs", "Number of DASH segments defined by the DASH cue info", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_CODEC, "Codec", "codec parameter string to force. If starting with '.', appended to ISOBMF code point; otherwise replace the codec string", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM}, + + { GF_PROP_PID_SINGLE_SCALE, "SingleScale", "Indicates the movie header should use the media timescale of the first track added", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_UDP, "RequireReorder", "Indicates the PID packets come from source with losses and reordering happening (UDP)", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_PRIMARY_ITEM, "Primary", "Indicates this is a primary item in isobmf", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM}, + + { GF_PROP_PID_COLR_PRIMARIES, "ColorPrimaries", "Indicate color primaries for a visual pid (see ISO/IEC 23001-8 / 23091-2)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_COLR_TRANSFER, "ColorTransfer", "Indicate color transfer characteristics for a visual pid (see ISO/IEC 23001-8 / 23091-2)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_COLR_MX, "ColorMatrixCoef", "Indicate color matrix coeficient for a visual pid (see ISO/IEC 23001-8 / 23091-2)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_COLR_RANGE, "FullRange", "Indicate color full range flag for a visual pid (see ISO/IEC 23001-8 / 23091-2)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_COLR_CHROMALOC, "ChromaLoc", "Indicate chrom location for a visual pid (see ISO/IEC 23001-8 / 23091-2)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_SRC_MAGIC, "SrcMagic", "Indicate a magic number to store in the track, only used by importers", GF_PROP_LUINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_MUX_INDEX, "MuxIndex", "Indicate target track index in destination file, stored by lowest value first (not set by demuxers)", GF_PROP_LUINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_NO_TS_LOOP, "NoTSLoop", "Indicate the timestamps on this PID are adjusted in case of loops (used by TS muxer output)", GF_PROP_BOOL, 0}, + + + { GF_PROP_PCK_FRAG_START, "FragStart", "Indicate if packet is a fragment start (value 1) or a segment start (value 2)", GF_PROP_UINT, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PCK_FRAG_RANGE, "FragRange", "Indicate start and end position in bytes of fragment if packet is a fragment or segment start", GF_PROP_FRACTION64, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PCK_SIDX_RANGE, "SIDXRange", "Indicate start and end position in bytes of sidx if packet is a fragment or segment start", GF_PROP_FRACTION64, GF_PROP_FLAG_GSF_REM}, + + { GF_PROP_PCK_MOOF_TEMPLATE, "MoofTemplate", "Serialized moof box corresponding to the start of a movie fragment or segment (with styp and optionally sidx)", GF_PROP_DATA, GF_PROP_FLAG_GSF_REM}, + + { GF_PROP_PID_RAWGRAB, "RawGrab", "Indicate PID is a raw media grabber (webcam, microphone, etc...)", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_KEEP_AFTER_EOS, "KeepAfterEOS", "Indicate the PID must be kept alive after EOS (LASeR and BIFS)", GF_PROP_BOOL, GF_PROP_FLAG_GSF_REM}, + { GF_PROP_PID_COVER_ART, "CoverArt", "Indicate PID cover art image data. If associated data is NULL, the data is carried in the PID", GF_PROP_DATA, GF_PROP_FLAG_GSF_REM}, + +}; + +GF_EXPORT +u32 gf_props_get_id(const char *name) +{ + u32 i, nb_props; + if (!name) return 0; + nb_props = sizeof(GF_BuiltInProps) / sizeof(GF_BuiltInProperty); + for (i=0; i=nb_props) return NULL; + return &GF_BuiltInProps[prop_idx]; +} + +GF_EXPORT +const char *gf_props_4cc_get_name(u32 prop_4cc) +{ + u32 i, nb_props = sizeof(GF_BuiltInProps) / sizeof(GF_BuiltInProperty); + for (i=0; itype) { + case GF_PROP_NAME: + case GF_PROP_STRING: + case GF_PROP_STRING_NO_COPY: + break; + default: + if (!dump) return NULL; + break; + } + + dump[0] = 0; + switch (att->type) { + case GF_PROP_SINT: + sprintf(dump, "%d", att->value.sint); + break; + case GF_PROP_UINT: + if (min_max_enum && strchr(min_max_enum, '|') ) { + u32 enum_val = 0; + char *str_start = (char *) min_max_enum; + while (str_start) { + u32 len; + char *sep = strchr(str_start, '|'); + if (sep) { + len = (u32) (sep - str_start); + } else { + len = (u32) strlen(str_start); + } + if (att->value.uint == enum_val) { + strncpy(dump, str_start, len); + dump[len]=0; + break; + } + if (!sep) break; + str_start = sep+1; + enum_val++; + } + if (!str_start) { + sprintf(dump, "%u", att->value.uint); + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Wrong argument value %d not found in enums %s - using integer dump\n", att->value.uint_list, min_max_enum)); + } + } else { + sprintf(dump, "%u", att->value.uint); + } + break; + case GF_PROP_LSINT: + sprintf(dump, ""LLD, att->value.longsint); + break; + case GF_PROP_LUINT: + sprintf(dump, ""LLU, att->value.longuint); + break; + case GF_PROP_FRACTION: + //reduce fraction + if (att->value.frac.den && ((att->value.frac.num/att->value.frac.den) * att->value.frac.den == att->value.frac.num)) { + sprintf(dump, "%d", att->value.frac.num / att->value.frac.den); + } else { + sprintf(dump, "%d/%u", att->value.frac.num, att->value.frac.den); + } + break; + case GF_PROP_FRACTION64: + //reduce fraction + if (att->value.lfrac.den && ((att->value.lfrac.num/att->value.lfrac.den) * att->value.lfrac.den == att->value.lfrac.num)) { + sprintf(dump, LLD, att->value.lfrac.num / att->value.lfrac.den); + } else { + sprintf(dump, LLD"/"LLU, att->value.lfrac.num, att->value.lfrac.den); + } + break; + case GF_PROP_BOOL: + sprintf(dump, "%s", att->value.boolean ? "true" : "false"); + break; + case GF_PROP_FLOAT: + sprintf(dump, "%f", FIX2FLT(att->value.fnumber) ); + break; + case GF_PROP_DOUBLE: + sprintf(dump, "%g", att->value.number); + break; + case GF_PROP_VEC2I: + sprintf(dump, "%dx%d", att->value.vec2i.x, att->value.vec2i.y); + break; + case GF_PROP_VEC2: + sprintf(dump, "%lgx%lg", att->value.vec2.x, att->value.vec2.y); + break; + case GF_PROP_VEC3I: + sprintf(dump, "%dx%dx%d", att->value.vec3i.x, att->value.vec3i.y, att->value.vec3i.z); + break; + case GF_PROP_VEC3: + sprintf(dump, "%lgx%lgx%lg", att->value.vec3.x, att->value.vec3.y, att->value.vec3.y); + break; + case GF_PROP_VEC4I: + sprintf(dump, "%dx%dx%dx%d", att->value.vec4i.x, att->value.vec4i.y, att->value.vec4i.z, att->value.vec4i.w); + break; + case GF_PROP_VEC4: + sprintf(dump, "%lgx%lgx%lgx%lg", att->value.vec4.x, att->value.vec4.y, att->value.vec4.y, att->value.vec4.w); + break; + case GF_PROP_PIXFMT: + return gf_pixel_fmt_name(att->value.uint); + case GF_PROP_PCMFMT: + return gf_audio_fmt_name(att->value.uint); + case GF_PROP_NAME: + case GF_PROP_STRING: + case GF_PROP_STRING_NO_COPY: + return att->value.string; + + case GF_PROP_DATA: + case GF_PROP_CONST_DATA: + case GF_PROP_DATA_NO_COPY: + if (dump_data && att->value.data.size < 40) { + u32 i; + sprintf(dump, "%d bytes 0x", att->value.data.size); + for (i=0; ivalue.data.size; i++) { + sprintf(dump, "%02X", (unsigned char) att->value.data.ptr[i]); + } + } else { + sprintf(dump, "%d bytes (CRC32 0x%08X)", att->value.data.size, gf_crc_32(att->value.data.ptr, att->value.data.size)); + } + break; + case GF_PROP_POINTER: + sprintf(dump, "%p", att->value.ptr); + break; + case GF_PROP_STRING_LIST: + { + u32 i, count = gf_list_count(att->value.string_list); + u32 len = GF_PROP_DUMP_ARG_SIZE-1; + dump[len]=0; + for (i=0; ivalue.string_list, i); + if (!i) { + strncpy(dump, s, len); + } else { + strcat(dump, ","); + strncat(dump, s, len-1); + } + len = GF_PROP_DUMP_ARG_SIZE - 1 - (u32) strlen(dump); + if (len<=1) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("String list is too large to fit in predefined property dump buffer of %d bytes, truncating\n", GF_PROP_DUMP_ARG_SIZE)); + return dump; + } + } + return dump; + } + case GF_PROP_UINT_LIST: + { + u32 i, count = att->value.uint_list.nb_items; + u32 len = GF_PROP_DUMP_ARG_SIZE-1; + dump[len]=0; + for (i=0; ivalue.uint_list.vals[i]) ); + } else { + sprintf(szItem, "%u", att->value.uint_list.vals[i]); + } + if (!i) { + strncpy(dump, szItem, len); + } else { + strcat(dump, ","); + strncat(dump, szItem, len-1); + } + len = GF_PROP_DUMP_ARG_SIZE - 1 - (u32) strlen(dump); + if (len<=1) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Uint list is too large to fit in predefined property dump buffer of %d bytes, truncating\n", GF_PROP_DUMP_ARG_SIZE)); + return dump; + } + } + return dump; + } + case GF_PROP_FORBIDEN: + sprintf(dump, "forbiden"); + break; + case GF_PROP_LAST_DEFINED: + sprintf(dump, "lastDefined"); + break; + } + return dump; +} + +GF_EXPORT +const char *gf_props_dump_val(const GF_PropertyValue *att, char dump[GF_PROP_DUMP_ARG_SIZE], Bool dump_data, const char *min_max_enum) +{ + return gf_props_dump_val_ex(att, dump, dump_data, min_max_enum, GF_FALSE); +} + +GF_EXPORT +const char *gf_props_dump(u32 p4cc, const GF_PropertyValue *att, char dump[GF_PROP_DUMP_ARG_SIZE], Bool dump_data) +{ + Bool is_4cc = GF_FALSE; + + switch (p4cc) { + case GF_PROP_PID_STREAM_TYPE: + case GF_PROP_PID_ORIG_STREAM_TYPE: + return gf_stream_type_name(att->value.uint); + case GF_PROP_PID_CODECID: + return gf_codecid_name(att->value.uint); + case GF_PROP_PID_PIXFMT: + return gf_pixel_fmt_name(att->value.uint); + case GF_PROP_PID_AUDIO_FORMAT: + return gf_audio_fmt_name(att->value.uint); + case GF_PROP_PID_PROTECTION_SCHEME_TYPE: + case GF_PROP_PID_CENC_STORE: + case GF_PROP_PID_SUBTYPE: + case GF_PROP_PID_ISOM_SUBTYPE: + case GF_PROP_PID_ISOM_MBRAND: + return gf_4cc_to_str(att->value.uint); + case GF_PROP_PID_PLAYBACK_MODE: + if (att->value.uint == GF_PLAYBACK_MODE_SEEK) return "seek"; + else if (att->value.uint == GF_PLAYBACK_MODE_REWIND) return "rewind"; + else if (att->value.uint == GF_PLAYBACK_MODE_FASTFORWARD) return "forward"; + else return "none"; + case GF_PROP_PID_ISOM_BRANDS: + is_4cc = GF_TRUE; + + default: + return gf_props_dump_val_ex(att, dump, dump_data, NULL, is_4cc); + } + return ""; +} + diff --git a/src/filter_core/filter_queue.c b/src/filter_core/filter_queue.c new file mode 100644 index 0000000..0a93c26 --- /dev/null +++ b/src/filter_core/filter_queue.c @@ -0,0 +1,354 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2018 + * All rights reserved + * + * This file is part of GPAC / filters sub-project + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terfsess of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "filter_session.h" + +#ifdef WIN32 + +#ifndef __GCC__ +#ifdef GPAC_64_BITS +#define atomic_compare_and_swap(_ptr, _comparand, _replacement) (InterlockedCompareExchange64((__int64*)_ptr,(__int64)_replacement,(__int64)_comparand)==(__int64)_comparand) +#else +#define atomic_compare_and_swap(_ptr, _comparand, _replacement) (InterlockedCompareExchange((int *)_ptr,(int )_replacement,(int )_comparand)==(int)_comparand) +#endif +#endif + +#else +#define atomic_compare_and_swap(_ptr, _comparand, _replacement) __sync_bool_compare_and_swap(_ptr, _comparand, _replacement) +#endif + + +typedef struct __lf_item +{ + struct __lf_item *next; + void *data; +} GF_LFQItem; + +struct __gf_filter_queue +{ + //head element is dummy, never swaped + GF_LFQItem *head; + GF_LFQItem *tail; + + GF_LFQItem *res_head; + GF_LFQItem *res_tail; + + volatile u32 nb_items; + + GF_Mutex *mx; +}; + + + + +GF_FilterQueue *gf_fq_new(const GF_Mutex *mx) +{ + GF_FilterQueue *q; + GF_SAFEALLOC(q, GF_FilterQueue); + if (!q) return NULL; + + q->mx = (GF_Mutex *) mx; + if (mx) return q; + + + //lock-free mode, create dummuy slot for head + GF_SAFEALLOC(q->head, GF_LFQItem); + if (!q->head) { + gf_free(q); + return NULL; + } + q->tail = q->head; + + //lock-free mode, create dummuy slot for reservoir head + GF_SAFEALLOC(q->res_head, GF_LFQItem); + if (!q->res_head) { + gf_free(q->head); + gf_free(q); + return NULL; + } + q->res_tail = q->res_head; + return q; +} + + +void gf_fq_del(GF_FilterQueue *q, void (*item_delete)(void *) ) +{ + GF_LFQItem *it = q->head; + //first item is dummy if lock-free mode, doesn't hold a valid pointer + if (! q->mx) it->data=NULL; + + while (it) { + GF_LFQItem *ptr = it; + it = it->next; + if (ptr->data && item_delete) item_delete(ptr->data); + gf_free(ptr); + } + + it = q->res_head; + while (it) { + GF_LFQItem *ptr = it; + it = it->next; + gf_free(ptr); + } + gf_free(q); +} + +static void gf_fq_lockfree_enqueue(GF_LFQItem *it, GF_LFQItem **tail_ptr) +{ + GF_LFQItem *tail; + + while (1) { + GF_LFQItem *next; + tail = *tail_ptr; + next = tail->next; + if (next == tail->next) { + if (next==NULL) { + if (atomic_compare_and_swap(&tail->next, next, it)) { + break; // Enqueue is done. Exit loop + } + } else { + //tail not pointing at last node, move it + atomic_compare_and_swap(tail_ptr, tail, next); + } + } + } + atomic_compare_and_swap(tail_ptr, tail, it); +} + +static void *gf_fq_lockfree_dequeue(GF_LFQItem **head_ptr, GF_LFQItem **tail_ptr, GF_LFQItem **prev_head) +{ + void *data=NULL; + GF_LFQItem *next, *head; + next = NULL; + *prev_head = NULL; + + while (1 ) { + GF_LFQItem *tail; + head = *head_ptr; + tail = *tail_ptr; + next = head->next; + //state seefsess OK + if (head == *head_ptr) { + //head is at tail, we have an empty queue or some state issue + if (head == tail) { + //empty queue (first slot is dummy, we need to check next) + if (next == NULL) + return NULL; + + //swap back tail at next + atomic_compare_and_swap(tail_ptr, tail, next); + } else { + assert(next); + data = next->data; + //try to advance q->head to next + if (atomic_compare_and_swap(head_ptr, head, next)) + break; //success! + } + } + } + *prev_head = head; + return data; +} + +void gf_lfq_add(GF_FilterQueue *q, void *item) +{ + GF_LFQItem *it=NULL; + assert(q); + + gf_fq_lockfree_dequeue( &q->res_head, &q->res_tail, &it); + if (!it) { + GF_SAFEALLOC(it, GF_LFQItem); + if (!it) return; + } else { + it->next = NULL; + } + it->data=item; + gf_fq_lockfree_enqueue(it, &q->tail); + safe_int_inc(&q->nb_items); +} + +void *gf_lfq_pop(GF_FilterQueue *q) +{ + GF_LFQItem *slot=NULL; + if (!q) return NULL; + + void *data = gf_fq_lockfree_dequeue( &q->head, &q->tail, &slot); + if (!data) return NULL; + safe_int_dec(&q->nb_items); + assert(slot); + + slot->data = NULL; + slot->next = NULL; + gf_fq_lockfree_enqueue(slot, &q->res_tail); + + return data; +} + +u32 gf_fq_count(GF_FilterQueue *q) +{ + return q ? q->nb_items : 0; +} + +//TODO - check performances vs function pointer +void gf_fq_add(GF_FilterQueue *fq, void *item) +{ + GF_LFQItem *it; + assert(fq); + + if (! fq->mx) { + gf_lfq_add(fq, item); + } else { + gf_mx_p(fq->mx); + + it = fq->res_head; + if (it) { + fq->res_head = fq->res_head->next; + it->next = NULL; + } else { + GF_SAFEALLOC(it, GF_LFQItem); + if (!it) return; + } + if (! fq->res_head) fq->res_tail = NULL; + + it->data = item; + if (!fq->tail) { + fq->tail = fq->head = it; + } else { + fq->tail->next = it; + fq->tail = it; + } + fq->nb_items++; + gf_mx_v(fq->mx); + } +} + +void *gf_fq_pop(GF_FilterQueue *fq) +{ + GF_LFQItem *it; + if (!fq) + return NULL; + + void *data=NULL; + if (! fq->mx) { + return gf_lfq_pop(fq); + } + + gf_mx_p(fq->mx); + it = fq->head; + if (it) { + fq->head = it->next; + data = it->data; + + it->data = NULL; + it->next = NULL; + + if (fq->res_tail) { + fq->res_tail->next = it; + fq->res_tail = it; + } else { + fq->res_head = fq->res_tail = it; + } + assert(fq->nb_items); + fq->nb_items--; + + if (! fq->head) fq->tail = NULL; + + } + gf_mx_v(fq->mx); + return data; +} + + +void *gf_fq_head(GF_FilterQueue *fq) +{ + void *data; + if (!fq) return NULL; + + if (fq->mx) { + gf_mx_p(fq->mx); + data = fq->head ? fq->head->data : NULL; + gf_mx_v(fq->mx); + } else { + data = fq->head->next ? fq->head->next->data : NULL; + } + return data; +} + + +void *gf_fq_get(GF_FilterQueue *fq, u32 idx) +{ + void *data; + GF_LFQItem *it; + assert(fq); + + if (fq->mx) { + gf_mx_p(fq->mx); + it = fq->head; + + while (it && idx) { + it = it->next; + idx--; + if (!it) break; + } + data = it ? it->data : NULL; + gf_mx_v(fq->mx); + } else { + it = fq->head->next; + while (it && idx) { + it = it->next; + idx--; + if (!it) break; + } + data = it ? it->data : NULL; + } + + return data; +} + +void gf_fq_enum(GF_FilterQueue *fq, Bool (*enum_func)(void *udta1, void *item), void *udta) +{ + GF_LFQItem *it; + if (!enum_func) return; + assert(fq); + + if (fq->mx) { + gf_mx_p(fq->mx); + it = fq->head; + + while (it) { + if (!enum_func(udta, it->data)) + break; + it = it->next; + } + gf_mx_v(fq->mx); + } else { + it = fq->head->next; + while (it) { + if (!enum_func(udta, it->data)) + break; + it = it->next; + } + } +} diff --git a/src/filter_core/filter_register.c b/src/filter_core/filter_register.c new file mode 100644 index 0000000..0b2cb7d --- /dev/null +++ b/src/filter_core/filter_register.c @@ -0,0 +1,293 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2018 + * All rights reserved + * + * This file is part of GPAC / filters sub-project + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terfsess of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "filter_session.h" +#include + + +const GF_FilterRegister *ut_filter_register(GF_FilterSession *session); +const GF_FilterRegister *ut_source_register(GF_FilterSession *session); +const GF_FilterRegister *ut_sink_register(GF_FilterSession *session); +const GF_FilterRegister *ut_sink2_register(GF_FilterSession *session); +const GF_FilterRegister *ffdmx_register(GF_FilterSession *session); +const GF_FilterRegister *ffdec_register(GF_FilterSession *session); +const GF_FilterRegister *ffenc_register(GF_FilterSession *session); +const GF_FilterRegister *ffavin_register(GF_FilterSession *session); +const GF_FilterRegister *ffsws_register(GF_FilterSession *session); +const GF_FilterRegister *ffmx_register(GF_FilterSession *session); +const GF_FilterRegister *ffavf_register(GF_FilterSession *session); +const GF_FilterRegister *inspect_register(GF_FilterSession *session); +const GF_FilterRegister *probe_register(GF_FilterSession *session); +const GF_FilterRegister *compose_filter_register(GF_FilterSession *session); +const GF_FilterRegister *isoffin_register(GF_FilterSession *session); +const GF_FilterRegister *bifs_dec_register(GF_FilterSession *session); +const GF_FilterRegister *odf_dec_register(GF_FilterSession *session); +const GF_FilterRegister *filein_register(GF_FilterSession *session); +const GF_FilterRegister *ctxload_register(GF_FilterSession *session); +const GF_FilterRegister *httpin_register(GF_FilterSession *session); +const GF_FilterRegister *svgin_register(GF_FilterSession *session); +const GF_FilterRegister *img_reframe_register(GF_FilterSession *session); +const GF_FilterRegister *imgdec_register(GF_FilterSession *session); +const GF_FilterRegister *adts_dmx_register(GF_FilterSession *session); +const GF_FilterRegister *latm_dmx_register(GF_FilterSession *session); +const GF_FilterRegister *mp3_dmx_register(GF_FilterSession *session); +const GF_FilterRegister *faad_register(GF_FilterSession *session); +const GF_FilterRegister *maddec_register(GF_FilterSession *session); +const GF_FilterRegister *xviddec_register(GF_FilterSession *session); +const GF_FilterRegister *j2kdec_register(GF_FilterSession *session); +const GF_FilterRegister *ac3dmx_register(GF_FilterSession *session); +const GF_FilterRegister *a52dec_register(GF_FilterSession *session); +const GF_FilterRegister *amrdmx_register(GF_FilterSession *session); +const GF_FilterRegister *oggdmx_register(GF_FilterSession *session); +const GF_FilterRegister *vorbisdec_register(GF_FilterSession *session); +const GF_FilterRegister *theoradec_register(GF_FilterSession *session); +const GF_FilterRegister *m2tsdmx_register(GF_FilterSession *session); +const GF_FilterRegister *sockin_register(GF_FilterSession *session); +const GF_FilterRegister *dvblin_register(GF_FilterSession *session); +const GF_FilterRegister *vtbdec_register(GF_FilterSession *session); +const GF_FilterRegister *lsrdec_register(GF_FilterSession *session); +const GF_FilterRegister *safdmx_register(GF_FilterSession *session); +const GF_FilterRegister *osvcdec_register(GF_FilterSession *session); +#ifdef GPAC_OPENHEVC_STATIC +const GF_FilterRegister *ohevcdec_register(GF_FilterSession *session); +#endif +const GF_FilterRegister *dashdmx_register(GF_FilterSession *session); +const GF_FilterRegister *cenc_decrypt_register(GF_FilterSession *session); +const GF_FilterRegister *cenc_encrypt_register(GF_FilterSession *session); +const GF_FilterRegister *mp4_mux_register(GF_FilterSession *session); +const GF_FilterRegister *qcpdmx_register(GF_FilterSession *session); +const GF_FilterRegister *h263dmx_register(GF_FilterSession *session); +const GF_FilterRegister *mpgviddmx_register(GF_FilterSession *session); +const GF_FilterRegister *nhntdmx_register(GF_FilterSession *session); +const GF_FilterRegister *nhmldmx_register(GF_FilterSession *session); +const GF_FilterRegister *naludmx_register(GF_FilterSession *session); +const GF_FilterRegister *m2psdmx_register(GF_FilterSession *session); +const GF_FilterRegister *avidmx_register(GF_FilterSession *session); +const GF_FilterRegister *txtin_register(GF_FilterSession *session); +const GF_FilterRegister *ttxtdec_register(GF_FilterSession *session); +const GF_FilterRegister *vttdec_register(GF_FilterSession *session); +const GF_FilterRegister *ttmldec_register(GF_FilterSession *session); +const GF_FilterRegister *rtpin_register(GF_FilterSession *session); +const GF_FilterRegister *fileout_register(GF_FilterSession *session); +const GF_FilterRegister *adtsmx_register(GF_FilterSession *session); +const GF_FilterRegister *latm_mx_register(GF_FilterSession *session); +const GF_FilterRegister *reframer_register(GF_FilterSession *session); +const GF_FilterRegister *writegen_register(GF_FilterSession *session); +const GF_FilterRegister *nalumx_register(GF_FilterSession *session); +const GF_FilterRegister *qcpmx_register(GF_FilterSession *session); +const GF_FilterRegister *vttmx_register(GF_FilterSession *session); +const GF_FilterRegister *nhntdump_register(GF_FilterSession *session); +const GF_FilterRegister *nhmldump_register(GF_FilterSession *session); +const GF_FilterRegister *vobsubdmx_register(GF_FilterSession *session); +const GF_FilterRegister *avimux_register(GF_FilterSession *session); +const GF_FilterRegister *aout_register(GF_FilterSession *session); +const GF_FilterRegister *m4vmx_register(GF_FilterSession *session); +const GF_FilterRegister *resample_register(GF_FilterSession *session); +#if !defined(GPAC_CONFIG_ANDROID) +const GF_FilterRegister *vout_register(GF_FilterSession *session); +#endif +const GF_FilterRegister *vcrop_register(GF_FilterSession *session); +const GF_FilterRegister *vflip_register(GF_FilterSession *session); +const GF_FilterRegister *rawvidreframe_register(GF_FilterSession *session); +const GF_FilterRegister *pcmreframe_register(GF_FilterSession *session); +const GF_FilterRegister *jpgenc_register(GF_FilterSession *session); +const GF_FilterRegister *pngenc_register(GF_FilterSession *session); +const GF_FilterRegister *rewind_register(GF_FilterSession *session); +const GF_FilterRegister *filelist_register(GF_FilterSession *session); +const GF_FilterRegister *tsmux_register(GF_FilterSession *session); +const GF_FilterRegister *dasher_register(GF_FilterSession *session); +const GF_FilterRegister *tileagg_register(GF_FilterSession *session); +#if !defined(GPAC_CONFIG_ANDROID) +const GF_FilterRegister *pipein_register(GF_FilterSession *session); +const GF_FilterRegister *pipeout_register(GF_FilterSession *session); +#endif +const GF_FilterRegister *gsfmx_register(GF_FilterSession *session); +const GF_FilterRegister *gsfdmx_register(GF_FilterSession *session); +const GF_FilterRegister *sockout_register(GF_FilterSession *session); +const GF_FilterRegister *av1dmx_register(GF_FilterSession *session); +const GF_FilterRegister *obumx_register(GF_FilterSession *session); +#if !defined(GPAC_CONFIG_IOS) && !defined(GPAC_CONFIG_ANDROID) +const GF_FilterRegister *nvdec_register(GF_FilterSession *session); +#endif +const GF_FilterRegister *atscin_register(GF_FilterSession *session); +const GF_FilterRegister *rtpout_register(GF_FilterSession *session); +const GF_FilterRegister *rtspout_register(GF_FilterSession *session); +const GF_FilterRegister *hevcsplit_register(GF_FilterSession *session); +const GF_FilterRegister *hevcmerge_register(GF_FilterSession *session); + +const GF_FilterRegister *jsfilter_register(GF_FilterSession *session); +const GF_FilterRegister *m2tssplit_register(GF_FilterSession *session); +const GF_FilterRegister *httpout_register(GF_FilterSession *session); + +#if !defined(GPAC_CONFIG_IOS) && !defined(GPAC_CONFIG_ANDROID) && !defined(GPAC_HAVE_DTAPI) && !defined(WIN32) +const GF_FilterRegister *dtout_register(GF_FilterSession *session); +#endif + +#if !defined(GPAC_CONFIG_IOS) +const GF_FilterRegister *mcdec_register(GF_FilterSession *session); +#endif + +const GF_FilterRegister *flac_dmx_register(GF_FilterSession *session); +const GF_FilterRegister *proresdmx_register(GF_FilterSession *session); +const GF_FilterRegister *bsrw_register(GF_FilterSession *session); + +void gf_fs_reg_all(GF_FilterSession *fsess, GF_FilterSession *a_sess) +{ + gf_fs_add_filter_register(fsess, inspect_register(a_sess) ); + gf_fs_add_filter_register(fsess, probe_register(a_sess) ); + gf_fs_add_filter_register(fsess, compose_filter_register(a_sess) ); + gf_fs_add_filter_register(fsess, isoffin_register(a_sess) ); + gf_fs_add_filter_register(fsess, bifs_dec_register(a_sess) ); + gf_fs_add_filter_register(fsess, odf_dec_register(a_sess) ); + gf_fs_add_filter_register(fsess, filein_register(a_sess) ); + gf_fs_add_filter_register(fsess, ctxload_register(a_sess) ); + gf_fs_add_filter_register(fsess, httpin_register(a_sess) ); + gf_fs_add_filter_register(fsess, svgin_register(a_sess) ); + gf_fs_add_filter_register(fsess, img_reframe_register(a_sess) ); + gf_fs_add_filter_register(fsess, imgdec_register(a_sess) ); + gf_fs_add_filter_register(fsess, adts_dmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, latm_dmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, mp3_dmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, faad_register(a_sess) ); + gf_fs_add_filter_register(fsess, maddec_register(a_sess) ); + gf_fs_add_filter_register(fsess, xviddec_register(a_sess) ); + gf_fs_add_filter_register(fsess, j2kdec_register(a_sess) ); + gf_fs_add_filter_register(fsess, ac3dmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, a52dec_register(a_sess) ); + gf_fs_add_filter_register(fsess, amrdmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, oggdmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, vorbisdec_register(a_sess) ); + gf_fs_add_filter_register(fsess, theoradec_register(a_sess) ); + gf_fs_add_filter_register(fsess, m2tsdmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, sockin_register(a_sess) ); + gf_fs_add_filter_register(fsess, dvblin_register(a_sess) ); + gf_fs_add_filter_register(fsess, osvcdec_register(a_sess) ); + gf_fs_add_filter_register(fsess, vtbdec_register(a_sess) ); + +#if !defined(GPAC_CONFIG_IOS) + gf_fs_add_filter_register(fsess, mcdec_register(a_sess) ); +#endif + + gf_fs_add_filter_register(fsess, lsrdec_register(a_sess) ); + gf_fs_add_filter_register(fsess, safdmx_register(a_sess) ); +#ifdef GPAC_OPENHEVC_STATIC + gf_fs_add_filter_register(fsess, ohevcdec_register(a_sess) ); +#endif + gf_fs_add_filter_register(fsess, dashdmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, cenc_decrypt_register(a_sess) ); + gf_fs_add_filter_register(fsess, cenc_encrypt_register(a_sess) ); + gf_fs_add_filter_register(fsess, mp4_mux_register(a_sess) ); + gf_fs_add_filter_register(fsess, qcpdmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, h263dmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, mpgviddmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, nhntdmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, nhmldmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, naludmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, m2psdmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, avidmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, txtin_register(a_sess) ); + gf_fs_add_filter_register(fsess, ttxtdec_register(a_sess) ); + gf_fs_add_filter_register(fsess, vttdec_register(a_sess) ); + gf_fs_add_filter_register(fsess, ttmldec_register(a_sess) ); + gf_fs_add_filter_register(fsess, rtpin_register(a_sess) ); + gf_fs_add_filter_register(fsess, fileout_register(a_sess) ); + gf_fs_add_filter_register(fsess, latm_mx_register(a_sess) ); + gf_fs_add_filter_register(fsess, adtsmx_register(a_sess) ); + + gf_fs_add_filter_register(fsess, reframer_register(a_sess) ); + gf_fs_add_filter_register(fsess, writegen_register(a_sess) ); + gf_fs_add_filter_register(fsess, nalumx_register(a_sess) ); + gf_fs_add_filter_register(fsess, qcpmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, vttmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, nhntdump_register(a_sess) ); + gf_fs_add_filter_register(fsess, nhmldump_register(a_sess) ); + gf_fs_add_filter_register(fsess, vobsubdmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, avimux_register(a_sess) ); + gf_fs_add_filter_register(fsess, aout_register(a_sess) ); + gf_fs_add_filter_register(fsess, m4vmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, resample_register(a_sess) ); +#if !defined(GPAC_CONFIG_ANDROID) + gf_fs_add_filter_register(fsess, vout_register(a_sess) ); +#endif + gf_fs_add_filter_register(fsess, vcrop_register(a_sess) ); + gf_fs_add_filter_register(fsess, vflip_register(a_sess) ); + gf_fs_add_filter_register(fsess, rawvidreframe_register(a_sess) ); + gf_fs_add_filter_register(fsess, pcmreframe_register(a_sess) ); + gf_fs_add_filter_register(fsess, jpgenc_register(a_sess) ); + gf_fs_add_filter_register(fsess, pngenc_register(a_sess) ); + gf_fs_add_filter_register(fsess, rewind_register(a_sess) ); + gf_fs_add_filter_register(fsess, filelist_register(a_sess) ); + gf_fs_add_filter_register(fsess, tsmux_register(a_sess) ); + gf_fs_add_filter_register(fsess, dasher_register(a_sess) ); + gf_fs_add_filter_register(fsess, tileagg_register(a_sess) ); +#if !defined(GPAC_CONFIG_ANDROID) + gf_fs_add_filter_register(fsess, pipein_register(a_sess) ); + gf_fs_add_filter_register(fsess, pipeout_register(a_sess) ); +#endif + gf_fs_add_filter_register(fsess, gsfmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, gsfdmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, sockout_register(a_sess) ); + gf_fs_add_filter_register(fsess, av1dmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, obumx_register(a_sess) ); +#if !defined(GPAC_CONFIG_IOS) && !defined(GPAC_CONFIG_ANDROID) + gf_fs_add_filter_register(fsess, nvdec_register(a_sess)); +#endif + gf_fs_add_filter_register(fsess, atscin_register(a_sess)); + gf_fs_add_filter_register(fsess, rtpout_register(a_sess)); + gf_fs_add_filter_register(fsess, rtspout_register(a_sess)); + gf_fs_add_filter_register(fsess, httpout_register(a_sess)); + + gf_fs_add_filter_register(fsess, hevcsplit_register(a_sess)); + gf_fs_add_filter_register(fsess, hevcmerge_register(a_sess)); + gf_fs_add_filter_register(fsess, flac_dmx_register(a_sess)); + gf_fs_add_filter_register(fsess, proresdmx_register(a_sess)); + gf_fs_add_filter_register(fsess, m2tssplit_register(a_sess)); + gf_fs_add_filter_register(fsess, bsrw_register(a_sess)); + + gf_fs_add_filter_register(fsess, ffdmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, ffdec_register(a_sess) ); + gf_fs_add_filter_register(fsess, ffavin_register(a_sess) ); + gf_fs_add_filter_register(fsess, ffsws_register(a_sess) ); + gf_fs_add_filter_register(fsess, ffenc_register(a_sess) ); + gf_fs_add_filter_register(fsess, ffmx_register(a_sess) ); + gf_fs_add_filter_register(fsess, ffavf_register(a_sess) ); + + gf_fs_add_filter_register(fsess, jsfilter_register(a_sess) ); + +#if !defined(GPAC_CONFIG_IOS) && !defined(GPAC_CONFIG_ANDROID) && !defined(GPAC_HAVE_DTAPI) && !defined(WIN32) + gf_fs_add_filter_register(fsess, dtout_register(a_sess) ); +#endif +} + +GF_EXPORT +void gf_fs_register_test_filters(GF_FilterSession *fsess) +{ + gf_fs_add_filter_register(fsess, ut_source_register(NULL) ); + gf_fs_add_filter_register(fsess, ut_filter_register(NULL) ); + gf_fs_add_filter_register(fsess, ut_sink_register(NULL) ); + gf_fs_add_filter_register(fsess, ut_sink2_register(NULL) ); +} + + diff --git a/src/filter_core/filter_session.c b/src/filter_core/filter_session.c new file mode 100644 index 0000000..0963e9a --- /dev/null +++ b/src/filter_core/filter_session.c @@ -0,0 +1,3254 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / filters sub-project + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terfsess of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "filter_session.h" +#include + +#ifndef GPAC_DISABLE_3D +#include +#endif +//#define CHECK_TASK_LIST_INTEGRITY + +#ifndef GPAC_DISABLE_PLAYER +struct _gf_ft_mgr *gf_font_manager_new(); +void gf_font_manager_del(struct _gf_ft_mgr *fm); +#endif + + +static GFINLINE void gf_fs_sema_io(GF_FilterSession *fsess, Bool notify, Bool main) +{ + GF_Semaphore *sem = main ? fsess->semaphore_main : fsess->semaphore_other; + if (sem) { + if (notify) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u notify scheduler %s semaphore\n", gf_th_id(), main ? "main" : "secondary")); + if ( ! gf_sema_notify(sem, 1)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCHEDULER, ("Cannot notify scheduler of new task, semaphore failure\n")); + } + } else { + u32 nb_tasks; + //if not main and no tasks in main list, this could be the last task to process. + //if main thread is sleeping force a wake to take further actions (only the main thread decides the exit) + //this also ensures that tha main thread will process tasks from secondary task lists if no + //dedicated main thread tasks are present (eg no GL filters) + if (!main && fsess->in_main_sem_wait && !gf_fq_count(fsess->main_thread_tasks)) { + gf_fs_sema_io(fsess, GF_TRUE, GF_TRUE); + } + nb_tasks = 1; + //no active threads, count number of tasks. If no posted tasks we are likely at the end of the session, don't block, rather use a sem_wait + if (!fsess->active_threads) + nb_tasks = gf_fq_count(fsess->main_thread_tasks) + gf_fq_count(fsess->tasks); + + //if main semaphore, keep track that we are going to sleep + if (main) { + fsess->in_main_sem_wait = GF_TRUE; + if (!nb_tasks) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("No tasks scheduled, waiting on main semaphore for at most 100 ms\n")); + if (gf_sema_wait_for(sem, 100)) { + } + } else { + if (gf_sema_wait(sem)) { + } + } + fsess->in_main_sem_wait = GF_FALSE; + } else { + if (!nb_tasks) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("No tasks scheduled, waiting on secondary semaphore for at most 100 ms\n")); + if (gf_sema_wait_for(sem, 100)) { + } + } else { + if (gf_sema_wait(sem)) { + } + } + } + } + } +} + +void gf_fs_add_filter_register(GF_FilterSession *fsess, const GF_FilterRegister *freg) +{ + if (!freg) return; + + if (!freg->name) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Filter missing name - ignoring\n")); + return; + } + if (!freg->process) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Filter %s missing process function - ignoring\n", freg->name)); + return; + } + if (fsess->blacklist) { + char *fname = strstr(fsess->blacklist, freg->name); + if (fname) { + u32 len = (u32) strlen(freg->name); + if (!fname[len] || (fname[len] == fsess->sep_list)) { + return; + } + } + } + gf_list_add(fsess->registry, (void *) freg); + + if (fsess->init_done && fsess->links && gf_list_count( fsess->links)) { + gf_filter_sess_build_graph(fsess, freg); + } +} + + + +static Bool fs_default_event_proc(void *ptr, GF_Event *evt) +{ + GF_FilterSession *fs = (GF_FilterSession *)ptr; +#ifdef GPAC_HAS_QJS + if (fs->on_evt_task) + return jsfs_on_event(fs, evt); +#endif + + if (evt->type==GF_EVENT_QUIT) { + gf_fs_abort(fs, GF_FALSE); + } + return 0; +} + +GF_EXPORT +GF_FilterSession *gf_fs_new(s32 nb_threads, GF_FilterSchedulerType sched_type, u32 flags, const char *blacklist) +{ + const char *opt; + Bool gf_sys_has_filter_global_args(); + Bool gf_sys_has_filter_global_meta_args(); + + u32 i, count; + GF_FilterSession *fsess, *a_sess; + + //safety check: all built-in properties shall have unique 4CCs + if ( ! gf_props_4cc_check_props()) + return NULL; + + GF_SAFEALLOC(fsess, GF_FilterSession); + if (!fsess) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to alloc media session\n")); + return NULL; + } + fsess->flags = flags; + + fsess->filters = gf_list_new(); + fsess->main_th.fsess = fsess; + + if ((s32) nb_threads == -1) { + GF_SystemRTInfo rti; + memset(&rti, 0, sizeof(GF_SystemRTInfo)); + if (gf_sys_get_rti(0, &rti, 0)) { + nb_threads = rti.nb_cores-1; + } + if ((s32)nb_threads<0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Failed to query number of cores, disabling extra threads for session\n")); + nb_threads=0; + } + } + + if (sched_type==GF_FS_SCHEDULER_DIRECT) { + fsess->direct_mode = GF_TRUE; + nb_threads=0; + } + if (nb_threads && (sched_type != GF_FS_SCHEDULER_LOCK_FREE_X)) { + fsess->tasks_mx = gf_mx_new("TasksList"); + } + + //regardless of scheduler type, we don't use lock on the main task list + fsess->tasks = gf_fq_new(fsess->tasks_mx); + + if (nb_threads>0) { + fsess->main_thread_tasks = gf_fq_new(fsess->tasks_mx); + fsess->filters_mx = gf_mx_new("Filters"); + } else { + //otherwise use the same as the global task list + fsess->main_thread_tasks = fsess->tasks; + } + + if (!(flags & GF_FS_FLAG_NO_RESERVOIR)) { + fsess->tasks_reservoir = gf_fq_new(fsess->tasks_mx); + } + + if (nb_threads || (sched_type==GF_FS_SCHEDULER_LOCK_FORCE) ) { + fsess->semaphore_main = fsess->semaphore_other = gf_sema_new(GF_INT_MAX, 0); + if (nb_threads>0) + fsess->semaphore_other = gf_sema_new(GF_INT_MAX, 0); + + //force testing of mutex queues + //fsess->use_locks = GF_TRUE; + } + fsess->ui_event_proc = fs_default_event_proc; + fsess->ui_opaque = fsess; + + if (flags & GF_FS_FLAG_NO_MAIN_THREAD) + fsess->no_main_thread = GF_TRUE; + + if (!fsess->semaphore_main) + nb_threads=0; + + if (nb_threads) { + fsess->threads = gf_list_new(); + if (!fsess->threads) { + gf_sema_del(fsess->semaphore_main); + fsess->semaphore_main=NULL; + gf_sema_del(fsess->semaphore_other); + fsess->semaphore_other=NULL; + nb_threads=0; + } + fsess->use_locks = (sched_type==GF_FS_SCHEDULER_LOCK) ? GF_TRUE : GF_FALSE; + } else { +#ifdef GPAC_MEMORY_TRACKING + extern int gf_mem_track_enabled; + fsess->check_allocs = gf_mem_track_enabled; +#endif + + } + + if (fsess->use_locks) + fsess->props_mx = gf_mx_new("FilterSessionProps"); + + if (!(flags & GF_FS_FLAG_NO_RESERVOIR)) { +#if GF_PROPS_HASHTABLE_SIZE + fsess->prop_maps_list_reservoir = gf_fq_new(fsess->props_mx); +#endif + fsess->prop_maps_reservoir = gf_fq_new(fsess->props_mx); + fsess->prop_maps_entry_reservoir = gf_fq_new(fsess->props_mx); + fsess->prop_maps_entry_data_alloc_reservoir = gf_fq_new(fsess->props_mx); + //we also use the props mutex for the this one + fsess->pcks_refprops_reservoir = gf_fq_new(fsess->props_mx); + } + + +#ifndef GPAC_DISABLE_REMOTERY + sprintf(fsess->main_th.rmt_name, "FSThread0"); +#endif + + if (!fsess->filters || !fsess->tasks) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to alloc media session\n")); + fsess->run_status = GF_OUT_OF_MEM; + gf_fs_del(fsess); + return NULL; + } + + if (nb_threads) { + fsess->info_mx = gf_mx_new("FilterSessionInfo"); + fsess->ui_mx = gf_mx_new("FilterSessionUIProc"); + } + + for (i=0; i<(u32) nb_threads; i++) { + GF_SessionThread *sess_thread; + GF_SAFEALLOC(sess_thread, GF_SessionThread); + if (!sess_thread) continue; +#ifndef GPAC_DISABLE_REMOTERY + sprintf(sess_thread->rmt_name, "FSThread%d", i+1); +#endif + sess_thread->th = gf_th_new("MediaSessionThread"); + if (!sess_thread->th) { + gf_free(sess_thread); + continue; + } + sess_thread->fsess = fsess; + gf_list_add(fsess->threads, sess_thread); + } + + gf_fs_set_separators(fsess, NULL); + + fsess->registry = gf_list_new(); + fsess->blacklist = blacklist; + a_sess = (flags & GF_FS_FLAG_LOAD_META) ? fsess : NULL; + gf_fs_reg_all(fsess, a_sess); + + //load external modules + count = gf_modules_count(); + for (i=0; iflags |= 0x80000000; + gf_fs_add_filter_register(fsess, freg); + } + } + fsess->blacklist = NULL; + + //todo - find a way to handle events without mutex ... + fsess->evt_mx = gf_mx_new("Event mutex"); + + fsess->blocking_mode = GF_FS_BLOCK_ALL; + opt = gf_opts_get_key("core", "no-block"); + if (opt) { + if (!strcmp(opt, "fanout")) { + fsess->blocking_mode = GF_FS_NOBLOCK_FANOUT; + } + else if (!strcmp(opt, "all")) { + fsess->blocking_mode = GF_FS_NOBLOCK; + } + } + fsess->run_status = GF_EOS; + fsess->nb_threads_stopped = 1+nb_threads; + fsess->default_pid_buffer_max_us = 1000; + fsess->decoder_pid_buffer_max_us = 1000000; + fsess->default_pid_buffer_max_units = 1; + fsess->max_resolve_chain_len = 6; + fsess->auto_inc_nums = gf_list_new(); + + if (nb_threads) + fsess->links_mx = gf_mx_new("FilterRegistryGraph"); + fsess->links = gf_list_new(); + +#ifndef GPAC_DISABLE_3D + fsess->gl_providers = gf_list_new(); +#endif + + if (! (fsess->flags & GF_FS_FLAG_NO_GRAPH_CACHE)) + gf_filter_sess_build_graph(fsess, NULL); + + fsess->init_done = GF_TRUE; + + + if (gf_sys_has_filter_global_args() || gf_sys_has_filter_global_meta_args()) { + u32 nb_args = gf_sys_get_argc(); + for (i=0; iflags & GF_FS_FLAG_NO_ARG_CHECK) + return; + + if (!was_found) { + Bool afound = GF_FALSE; + u32 k, acount; + if (!session->parsed_args) session->parsed_args = gf_list_new(); + acount = gf_list_count(session->parsed_args); + for (k=0; kparsed_args, k); + if (!strcmp(ai->argname, szArg)) { + afound = GF_TRUE; + if ((ai->type==2) && (type==2)) + ai->found = GF_FALSE; + break; + } + } + if (!afound) { + GF_FSArgItem *ai; + GF_SAFEALLOC(ai, GF_FSArgItem); + if (ai) { + ai->argname = gf_strdup(szArg); + ai->type = type; + gf_list_add(session->parsed_args, ai ); + } + } + } else { + u32 k, acount; + Bool found = GF_FALSE; + if (!session->parsed_args) session->parsed_args = gf_list_new(); + acount = gf_list_count(session->parsed_args); + for (k=0; kparsed_args, k); + if (!strcmp(ai->argname, szArg)) { + ai->found = GF_TRUE; + found = GF_TRUE; + break; + } + } + if (!found) { + GF_FSArgItem *ai; + GF_SAFEALLOC(ai, GF_FSArgItem); + if (ai) { + ai->argname = gf_strdup(szArg); + ai->type = type; + ai->found = GF_TRUE; + gf_list_add(session->parsed_args, ai ); + } + } + } +} + + +GF_EXPORT +GF_FilterSession *gf_fs_new_defaults(u32 inflags) +{ + GF_FilterSession *fsess; + GF_FilterSchedulerType sched_type = GF_FS_SCHEDULER_LOCK_FREE; + u32 flags = 0; + s32 nb_threads = gf_opts_get_int("core", "threads"); + const char *blacklist = gf_opts_get_key("core", "blacklist"); + const char *opt = gf_opts_get_key("core", "sched"); + + if (!opt) sched_type = GF_FS_SCHEDULER_LOCK_FREE; + else if (!strcmp(opt, "lock")) sched_type = GF_FS_SCHEDULER_LOCK; + else if (!strcmp(opt, "flock")) sched_type = GF_FS_SCHEDULER_LOCK_FORCE; + else if (!strcmp(opt, "direct")) sched_type = GF_FS_SCHEDULER_DIRECT; + else if (!strcmp(opt, "free")) sched_type = GF_FS_SCHEDULER_LOCK_FREE; + else if (!strcmp(opt, "freex")) sched_type = GF_FS_SCHEDULER_LOCK_FREE_X; + else { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Unrecognized scheduler type %s\n", opt)); + return NULL; + } + if (inflags & GF_FS_FLAG_LOAD_META) + flags |= GF_FS_FLAG_LOAD_META; + + if (inflags & GF_FS_FLAG_NO_MAIN_THREAD) + flags |= GF_FS_FLAG_NO_MAIN_THREAD; + + if (inflags & GF_FS_FLAG_NO_GRAPH_CACHE) + flags |= GF_FS_FLAG_NO_GRAPH_CACHE; + + if (gf_opts_get_bool("core", "dbg-edges")) + flags |= GF_FS_FLAG_PRINT_CONNECTIONS; + + if (gf_opts_get_bool("core", "full-link")) + flags |= GF_FS_FLAG_FULL_LINK; + + if (gf_opts_get_bool("core", "no-reg")) + flags |= GF_FS_FLAG_NO_REGULATION; + + if (gf_opts_get_bool("core", "no-reassign")) + flags |= GF_FS_FLAG_NO_REASSIGN; + + if (gf_opts_get_bool("core", "no-graph-cache")) + flags |= GF_FS_FLAG_NO_GRAPH_CACHE; + + if (gf_opts_get_bool("core", "no-probe")) + flags |= GF_FS_FLAG_NO_PROBE; + + if (gf_opts_get_bool("core", "no-argchk")) + flags |= GF_FS_FLAG_NO_ARG_CHECK; + + if (gf_opts_get_bool("core", "no-reservoir")) + flags |= GF_FS_FLAG_NO_RESERVOIR; + + + fsess = gf_fs_new(nb_threads, sched_type, flags, blacklist); + if (!fsess) return NULL; + + gf_fs_set_max_resolution_chain_length(fsess, gf_opts_get_int("core", "max-chain") ); + + gf_fs_set_max_sleep_time(fsess, gf_opts_get_int("core", "max-sleep") ); + + opt = gf_opts_get_key("core", "seps"); + if (opt) + gf_fs_set_separators(fsess, opt); + + return fsess; +} + + +GF_EXPORT +GF_Err gf_fs_set_separators(GF_FilterSession *session, const char *separator_set) +{ + if (!session) return GF_BAD_PARAM; + if (separator_set && (strlen(separator_set)<5)) return GF_BAD_PARAM; + + if (separator_set) { + session->sep_args = separator_set[0]; + session->sep_name = separator_set[1]; + session->sep_frag = separator_set[2]; + session->sep_list = separator_set[3]; + session->sep_neg = separator_set[4]; + } else { + session->sep_args = ':'; + session->sep_name = '='; + session->sep_frag = '#'; + session->sep_list = ','; + session->sep_neg = '!'; + } + return GF_OK; +} + +GF_EXPORT +GF_Err gf_fs_set_max_resolution_chain_length(GF_FilterSession *session, u32 max_chain_length) +{ + if (!session) return GF_BAD_PARAM; + session->max_resolve_chain_len = max_chain_length; + return GF_OK; +} + +GF_EXPORT +GF_Err gf_fs_set_max_sleep_time(GF_FilterSession *session, u32 max_sleep) +{ + if (!session) return GF_BAD_PARAM; + session->max_sleep = max_sleep; + return GF_OK; +} + +GF_EXPORT +u32 gf_fs_get_max_resolution_chain_length(GF_FilterSession *session) +{ + if (!session) return 0; + return session->max_resolve_chain_len; +} + +void gf_fs_remove_filter_register(GF_FilterSession *session, GF_FilterRegister *freg) +{ + gf_list_del_item(session->registry, freg); + gf_filter_sess_reset_graph(session, freg); +} + +GF_EXPORT +void gf_fs_set_ui_callback(GF_FilterSession *fs, Bool (*ui_event_proc)(void *opaque, GF_Event *event), void *cbk_udta) +{ + if (fs) { + fs->ui_event_proc = ui_event_proc; + fs->ui_opaque = cbk_udta; + if (!fs->ui_event_proc) { + fs->ui_event_proc = fs_default_event_proc; + fs->ui_opaque = fs; + } + } +} + +void gf_propalloc_del(void *it) +{ + GF_PropertyEntry *pe = (GF_PropertyEntry *)it; + if (pe->prop.value.data.ptr) gf_free(pe->prop.value.data.ptr); + gf_free(pe); +} + + +GF_EXPORT +Bool gf_fs_enum_unmapped_options(GF_FilterSession *fsess, u32 *idx, char **argname, u32 *argtype) +{ + if (!fsess || !fsess->parsed_args) return GF_FALSE; + u32 i, count = gf_list_count(fsess->parsed_args); + + for (i=*idx; iparsed_args, i); + (*idx)++; + if (ai->found) continue; + if (argname) *argname = ai->argname; + if (argtype) *argtype = ai->type; + return GF_TRUE; + } + return GF_FALSE; +} + + +GF_EXPORT +void gf_fs_del(GF_FilterSession *fsess) +{ + assert(fsess); + + gf_fs_stop(fsess); + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Session destroy begin\n")); + + if (fsess->parsed_args) { + while (gf_list_count(fsess->parsed_args)) { + GF_FSArgItem *ai = gf_list_pop_back(fsess->parsed_args); + gf_free(ai->argname); + gf_free(ai); + } + gf_list_del(fsess->parsed_args); + } + + //temporary until we don't introduce fsess_stop + assert(fsess->run_status != GF_OK); + if (fsess->filters) { + u32 i, count=gf_list_count(fsess->filters); + //first pass: disconnect all filters, since some may have references to property maps or packets + for (i=0; ifilters, i); + filter->process_th_id = 0; + filter->scheduled_for_next_task = GF_TRUE; + + if (filter->detached_pid_inst) { + while (gf_list_count(filter->detached_pid_inst)) { + GF_FilterPidInst *pidi = gf_list_pop_front(filter->detached_pid_inst); + gf_filter_pid_inst_del(pidi); + } + gf_list_del(filter->detached_pid_inst); + filter->detached_pid_inst = NULL; + } + + if (filter->postponed_packets) { + while (gf_list_count(filter->postponed_packets)) { + GF_FilterPacket *pck = gf_list_pop_front(filter->postponed_packets); + gf_filter_packet_destroy(pck); + } + gf_list_del(filter->postponed_packets); + filter->postponed_packets = NULL; + } + for (j=0; jnum_input_pids; j++) { + GF_FilterPidInst *pidi = gf_list_get(filter->input_pids, j); + gf_filter_pid_inst_reset(pidi); + } + filter->scheduled_for_next_task = GF_FALSE; + } + //second pass, finalize all + for (i=0; ifilters, i); + if (filter->freg->finalize && !filter->finalized) { + filter->finalized = GF_TRUE; + FSESS_CHECK_THREAD(filter) + filter->freg->finalize(filter); + } + } + + while (gf_list_count(fsess->filters)) { + GF_Filter *filter = gf_list_pop_back(fsess->filters); + + gf_filter_del(filter); + } + gf_list_del(fsess->filters); + fsess->filters = NULL; + } + + gf_fs_unload_script(fsess, NULL); + + if (fsess->download_manager) gf_dm_del(fsess->download_manager); +#ifndef GPAC_DISABLE_PLAYER + if (fsess->font_manager) gf_font_manager_del(fsess->font_manager); +#endif + + if (fsess->registry) { + while (gf_list_count(fsess->registry)) { + GF_FilterRegister *freg = gf_list_pop_back(fsess->registry); + if (freg->register_free) freg->register_free(fsess, freg); + } + gf_list_del(fsess->registry); + } + + if (fsess->tasks) + gf_fq_del(fsess->tasks, gf_void_del); + + if (fsess->tasks_reservoir) + gf_fq_del(fsess->tasks_reservoir, gf_void_del); + + if (fsess->threads) { + if (fsess->main_thread_tasks) + gf_fq_del(fsess->main_thread_tasks, gf_void_del); + + while (gf_list_count(fsess->threads)) { + GF_SessionThread *sess_th = gf_list_pop_back(fsess->threads); + gf_th_del(sess_th->th); + gf_free(sess_th); + } + gf_list_del(fsess->threads); + } + + if (fsess->prop_maps_reservoir) + gf_fq_del(fsess->prop_maps_reservoir, gf_propmap_del); +#if GF_PROPS_HASHTABLE_SIZE + if (fsess->prop_maps_list_reservoir) + gf_fq_del(fsess->prop_maps_list_reservoir, (gf_destruct_fun) gf_list_del); +#endif + if (fsess->prop_maps_entry_reservoir) + gf_fq_del(fsess->prop_maps_entry_reservoir, gf_void_del); + if (fsess->prop_maps_entry_data_alloc_reservoir) + gf_fq_del(fsess->prop_maps_entry_data_alloc_reservoir, gf_propalloc_del); + if (fsess->pcks_refprops_reservoir) + gf_fq_del(fsess->pcks_refprops_reservoir, gf_void_del); + + + if (fsess->props_mx) + gf_mx_del(fsess->props_mx); + + if (fsess->info_mx) + gf_mx_del(fsess->info_mx); + + if (fsess->ui_mx) + gf_mx_del(fsess->ui_mx); + + if (fsess->semaphore_other && (fsess->semaphore_other != fsess->semaphore_main) ) + gf_sema_del(fsess->semaphore_other); + + if (fsess->semaphore_main) + gf_sema_del(fsess->semaphore_main); + + if (fsess->tasks_mx) + gf_mx_del(fsess->tasks_mx); + + if (fsess->filters_mx) + gf_mx_del(fsess->filters_mx); + + if (fsess->evt_mx) gf_mx_del(fsess->evt_mx); + if (fsess->event_listeners) gf_list_del(fsess->event_listeners); + + if (fsess->links) { + gf_filter_sess_reset_graph(fsess, NULL); + gf_list_del(fsess->links); + } + if (fsess->links_mx) gf_mx_del(fsess->links_mx); + +#ifndef GPAC_DISABLE_3D + gf_list_del(fsess->gl_providers); + if (fsess->gl_driver) { + fsess->gl_driver->Shutdown(fsess->gl_driver); + gf_modules_close_interface((GF_BaseInterface *)fsess->gl_driver); + } +#endif + + if (fsess->auto_inc_nums) { + while(gf_list_count(fsess->auto_inc_nums)) { + GF_FSAutoIncNum *aint = gf_list_pop_back(fsess->auto_inc_nums); + gf_free(aint); + } + gf_list_del(fsess->auto_inc_nums); + } + + gf_free(fsess); + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Session destroyed\n")); +} + +GF_EXPORT +u32 gf_fs_filters_registers_count(GF_FilterSession *fsess) +{ + return fsess ? gf_list_count(fsess->registry) : 0; +} + +GF_EXPORT +const GF_FilterRegister * gf_fs_get_filter_register(GF_FilterSession *fsess, u32 idx) +{ + return gf_list_get(fsess->registry, idx); +} + +#ifdef CHECK_TASK_LIST_INTEGRITY +static Bool check_task_list_enum(void *udta, void *item) +{ + assert(udta != item); + return GF_FALSE; +} +static void check_task_list(GF_FilterQueue *fq, GF_FSTask *task) +{ + if (fq) { + gf_fq_enum(fq, check_task_list_enum, task); + } +} +#endif + + +void gf_fs_post_task_ex(GF_FilterSession *fsess, gf_fs_task_callback task_fun, GF_Filter *filter, GF_FilterPid *pid, const char *log_name, void *udta, Bool is_configure, Bool force_direct_call) +{ + GF_FSTask *task; + Bool force_main_thread = GF_FALSE; + Bool notified = GF_FALSE; + + assert(fsess); + assert(task_fun); + + //only flatten calls if in main thread (we still have some broken filters using threading + //that could trigger tasks + if ((force_direct_call || fsess->direct_mode) + && (!filter || !filter->in_process) + && fsess->tasks_in_process + && (gf_th_id()==fsess->main_th.th_id) + ) { + GF_FSTask atask; + u64 task_time = gf_sys_clock_high_res(); + memset(&atask, 0, sizeof(GF_FSTask)); + atask.filter = filter; + atask.pid = pid; + atask.run_task = task_fun; + atask.log_name = log_name; + atask.udta = udta; + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread 0 task#%d %p executing Filter %s::%s (%d tasks pending)\n", fsess->main_th.nb_tasks, &atask, filter ? filter->name : "none", log_name, fsess->tasks_pending)); + if (filter) + filter->scheduled_for_next_task = GF_TRUE; + task_fun(&atask); + filter = atask.filter; + if (filter) { + filter->time_process += gf_sys_clock_high_res() - task_time; + filter->scheduled_for_next_task = GF_FALSE; + filter->nb_tasks_done++; + } + if (!atask.requeue_request) + return; + //asked to requeue the task, post it + } + task = gf_fq_pop(fsess->tasks_reservoir); + + if (!task) { + GF_SAFEALLOC(task, GF_FSTask); + if (!task) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCHEDULER, ("No more memory to post new task\n")); + return; + } + } + task->filter = filter; + task->pid = pid; + task->run_task = task_fun; + task->log_name = log_name; + task->udta = udta; + + if (filter && is_configure) { + if (filter->freg->flags & GF_FS_REG_CONFIGURE_MAIN_THREAD) + force_main_thread = GF_TRUE; + } + + if (filter) { + gf_mx_p(filter->tasks_mx); + //no tasks and not scheduled + if (! filter->scheduled_for_next_task && !gf_fq_count(filter->tasks)) { + notified = task->notified = GF_TRUE; + + if (!force_main_thread) + force_main_thread = (filter->main_thread_forced || (filter->freg->flags & GF_FS_REG_MAIN_THREAD)) ? GF_TRUE : GF_FALSE; + } else if (force_main_thread) { + force_main_thread = GF_FALSE; + if (filter->process_th_id && (fsess->main_th.th_id != filter->process_th_id)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCHEDULER, ("Cannot post task to main thread, filter is already scheduled\n")); + } + } + if (!force_main_thread) + task->blocking = (filter->freg->flags & GF_FS_REG_BLOCKING) ? GF_TRUE : GF_FALSE; + + gf_fq_add(filter->tasks, task); + gf_mx_v(filter->tasks_mx); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u Posted task %p Filter %s::%s (%d (%d) pending, %d process tasks) on %s task list\n", gf_th_id(), task, filter->name, task->log_name, fsess->tasks_pending, gf_fq_count(filter->tasks), filter->process_task_queued, task->notified ? (force_main_thread ? "main" : "secondary") : "filter")); + } else { + task->notified = notified = GF_TRUE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u Posted filter-less task %s (%d pending) on secondary task list\n", gf_th_id(), task->log_name, fsess->tasks_pending)); + } + + //WARNING, do not use task->notified since the task may have been posted to the filter task list and may already have been swapped + //with a different value ! + if (notified) { +#ifdef CHECK_TASK_LIST_INTEGRITY + check_task_list(fsess->main_thread_tasks, task); + check_task_list(fsess->tasks, task); + check_task_list(fsess->tasks_reservoir, task); +#endif + assert(task->run_task); + if (filter) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u posting filter task, scheduled_for_next_task %d\n", gf_th_id(), filter->scheduled_for_next_task)); + assert(!filter->scheduled_for_next_task); + } + + //notify/count tasks posted on the main task or regular task lists + safe_int_inc(&fsess->tasks_pending); + if (filter && force_main_thread) { + gf_fq_add(fsess->main_thread_tasks, task); + gf_fs_sema_io(fsess, GF_TRUE, GF_TRUE); + } else { + assert(task->run_task); + gf_fq_add(fsess->tasks, task); + gf_fs_sema_io(fsess, GF_TRUE, GF_FALSE); + } + } +} + +void gf_fs_post_task(GF_FilterSession *fsess, gf_fs_task_callback task_fun, GF_Filter *filter, GF_FilterPid *pid, const char *log_name, void *udta) +{ + gf_fs_post_task_ex(fsess, task_fun, filter, pid, log_name, udta, GF_FALSE, GF_FALSE); +} + +GF_EXPORT +Bool gf_fs_check_filter_register_cap(const GF_FilterRegister *f_reg, u32 incode, GF_PropertyValue *cap_input, u32 outcode, GF_PropertyValue *cap_output, Bool exact_match_only) +{ + u32 j; + u32 has_raw_in = 0; + u32 has_cid_match = 0; + u32 exclude_cid_out = 0; + u32 has_exclude_cid_out = 0; + for (j=0; jnb_caps; j++) { + const GF_FilterCapability *cap = &f_reg->caps[j]; + if (!(cap->flags & GF_CAPFLAG_IN_BUNDLE)) { + //CID not excluded, raw in present and CID explicit match or not included in excluded set + if (!exclude_cid_out && has_raw_in && (has_cid_match || (!exact_match_only && has_exclude_cid_out) ) ) { + return GF_TRUE; + } + + if (has_raw_in != 2) has_raw_in = 0; + if (has_cid_match != 2) has_cid_match = 0; + if (exclude_cid_out != 2) exclude_cid_out = 0; + if (has_exclude_cid_out != 2) has_exclude_cid_out = 0; + + continue; + } + + if ( (cap->flags & GF_CAPFLAG_INPUT) && (cap->code == incode) ) { + if (! (cap->flags & GF_CAPFLAG_EXCLUDED) && gf_props_equal(&cap->val, cap_input) ) { + has_raw_in = (cap->flags & GF_CAPS_INPUT_STATIC) ? 2 : 1; + } + } + if ( (cap->flags & GF_CAPFLAG_OUTPUT) && (cap->code == outcode) ) { + if (! (cap->flags & GF_CAPFLAG_EXCLUDED)) { + if (gf_props_equal(&cap->val, cap_output) ) { + has_cid_match = (cap->flags & GF_CAPS_OUTPUT_STATIC) ? 2 : 1; + } + } else { + if (gf_props_equal(&cap->val, cap_output) ) { + exclude_cid_out = (cap->flags & GF_CAPS_OUTPUT_STATIC) ? 2 : 1; + } else { + has_exclude_cid_out = (cap->flags & GF_CAPS_OUTPUT_STATIC) ? 2 : 1; + } + } + } + } + //CID not excluded, raw in present and CID explicit match or not included in excluded set + if (!exclude_cid_out && has_raw_in && (has_cid_match || (!exact_match_only && has_exclude_cid_out) ) ) { + return GF_TRUE; + } + return GF_FALSE; +} +static GF_Filter *gf_fs_load_encoder(GF_FilterSession *fsess, const char *args) +{ + GF_Err e; + char szCodec[3]; + char *cid, *sep; + const GF_FilterRegister *candidate = NULL; + u32 codecid=0; + GF_Filter *filter; + u32 i, count; + GF_PropertyValue cap_in, cap_out; + szCodec[0] = 'c'; + szCodec[1] = fsess->sep_name; + szCodec[2] = 0; + + cid = args ? strstr(args, szCodec) : NULL; + if (!cid) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Missing codec identifier in \"enc\" definition: %s\n", args ? args : "no arguments")); + return NULL; + } + sep = strchr(cid, fsess->sep_args); + if (sep) sep[0] = 0; + + codecid = gf_codec_parse(cid+2); + if (codecid==GF_CODECID_NONE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Unrecognized codec identifier in \"enc\" definition: %s\n", cid)); + if (sep) sep[0] = fsess->sep_args; + return NULL; + } + if (sep) sep[0] = fsess->sep_args; + + cap_in.type = GF_PROP_UINT; + cap_in.value.uint = GF_CODECID_RAW; + cap_out.type = GF_PROP_UINT; + cap_out.value.uint = codecid; + + count = gf_list_count(fsess->registry); + for (i=0; iregistry, i); + + if ( gf_fs_check_filter_register_cap(f_reg, GF_PROP_PID_CODECID, &cap_in, GF_PROP_PID_CODECID, &cap_out, GF_FALSE)) { + if (!candidate || (candidate->priority>f_reg->priority)) + candidate = f_reg; + } + } + if (!candidate) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Cannot find any filter providing encoding for %s\n", cid)); + return NULL; + } + filter = gf_filter_new(fsess, candidate, args, NULL, GF_FILTER_ARG_EXPLICIT, &e, NULL, GF_FALSE); + if (!filter) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to load filter %s: %s\n", candidate->name, gf_error_to_string(e) )); + } else { + filter->encoder_stream_type = gf_codecid_type(codecid); + } + return filter; +} + +GF_EXPORT +Bool gf_fs_filter_exists(GF_FilterSession *fsess, const char *name) +{ + u32 i, count; + + if (!strcmp(name, "enc")) return GF_TRUE; + + count = gf_list_count(fsess->registry); + for (i=0;iregistry, i); + if (!strcmp(f_reg->name, name)) { + return GF_TRUE; + } + } + return GF_FALSE; +} + +GF_EXPORT +GF_Filter *gf_fs_load_filter(GF_FilterSession *fsess, const char *name, GF_Err *err_code) +{ + const char *args=NULL; + const char *sep; + u32 i, len, count = gf_list_count(fsess->registry); + Bool quiet = (err_code && (*err_code == GF_EOS)) ? GF_TRUE : GF_FALSE; + + assert(fsess); + assert(name); + if (err_code) *err_code = GF_OK; + + sep = gf_fs_path_escape_colon(fsess, name); + if (sep) { + args = sep+1; + len = (u32) (sep - name); + } else len = (u32) strlen(name); + + if (!len) { + if (!quiet) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Missing filter name in %s\n", name)); + } + return NULL; + } + + if (!strncmp(name, "enc", len)) { + return gf_fs_load_encoder(fsess, args); + } + /*regular filter loading*/ + for (i=0;iregistry, i); + if ((strlen(f_reg->name)==len) && !strncmp(f_reg->name, name, len)) { + GF_Filter *filter; + GF_FilterArgType argtype = GF_FILTER_ARG_EXPLICIT; + + if ((f_reg->flags & GF_FS_REG_REQUIRES_RESOLVER) && !fsess->max_resolve_chain_len) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Filter %s requires graph resolver but it is disabled\n", name)); + if (err_code) *err_code = GF_BAD_PARAM; + return NULL; + } + + if (f_reg->flags & GF_FS_REG_ACT_AS_SOURCE) argtype = GF_FILTER_ARG_EXPLICIT_SOURCE; + filter = gf_filter_new(fsess, f_reg, args, NULL, argtype, err_code, NULL, GF_FALSE); + if (!filter) return NULL; + if (!filter->num_output_pids) { + const char *src_url = strstr(name, "src"); + if (src_url && (src_url[3]==fsess->sep_name)) + gf_filter_post_process_task(filter); + } + return filter; + } + } + /*check JS file*/ + if (strstr(name, ".js") || strstr(name, ".jsf") || strstr(name, ".mjs") ) { + char szPath[10+GF_MAX_PATH]; + if (len>GF_MAX_PATH) + return NULL; + strncpy(szPath, name, len); + szPath[len]=0; + if (gf_file_exists(szPath)) { + sprintf(szPath, "jsf%cjs%c", fsess->sep_args, fsess->sep_name); + strcat(szPath, name); + return gf_fs_load_filter(fsess, szPath, err_code); + } + } + + if (!quiet) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to load filter %s: no such filter registry\n", name)); + } + if (err_code) *err_code = GF_FILTER_NOT_FOUND; + return NULL; +} + +//in mono thread mode, we cannot always sleep for the requested timeout in case there are more tasks to be processed +//this defines the number of pending tasks above wich we limit sleep +#define MONOTH_MIN_TASKS 2 +//this defines the sleep time for this case +#define MONOTH_MIN_SLEEP 5 + +static u32 gf_fs_thread_proc(GF_SessionThread *sess_thread) +{ + GF_FilterSession *fsess = sess_thread->fsess; + u32 i, th_count = fsess->threads ? gf_list_count(fsess->threads) : 0; + u32 thid = 1 + gf_list_find(fsess->threads, sess_thread); + u64 enter_time = gf_sys_clock_high_res(); + Bool use_main_sema = thid ? GF_FALSE : GF_TRUE; +#ifndef GPAC_DISABLE_LOG + u32 sys_thid = gf_th_id(); +#endif + u64 next_task_schedule_time = 0; + Bool do_regulate = (fsess->flags & GF_FS_FLAG_NO_REGULATION) ? GF_FALSE : GF_TRUE; + u32 consecutive_filter_tasks=0; + Bool force_secondary_tasks = GF_FALSE; + Bool skip_next_sema_wait = GF_FALSE; + + GF_Filter *current_filter = NULL; + sess_thread->th_id = gf_th_id(); + +#ifndef GPAC_DISABLE_REMOTERY + sess_thread->rmt_tasks=40; + gf_rmt_set_thread_name(sess_thread->rmt_name); +#endif + + gf_rmt_begin(fs_thread, 0); + + safe_int_inc(&fsess->active_threads); + + if (!thid && fsess->no_main_thread) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Main thread proc enter\n")); + } + + while (1) { + Bool notified; + Bool requeue = GF_FALSE; + u64 active_start, task_time; + GF_FSTask *task=NULL; +#ifdef CHECK_TASK_LIST_INTEGRITY + GF_Filter *prev_current_filter = NULL; + Bool skip_filter_task_check = GF_FALSE; +#endif + +#ifndef GPAC_DISABLE_REMOTERY + sess_thread->rmt_tasks--; + if (!sess_thread->rmt_tasks) { + gf_rmt_end(); + gf_rmt_begin(fs_thread, 0); + sess_thread->rmt_tasks=40; + } +#endif + + safe_int_dec(&fsess->active_threads); + + if (!skip_next_sema_wait && (current_filter==NULL)) { + gf_rmt_begin(sema_wait, GF_RMT_AGGREGATE); + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u Waiting scheduler %s semaphore\n", sys_thid, use_main_sema ? "main" : "secondary")); + //wait for something to be done + gf_fs_sema_io(fsess, GF_FALSE, use_main_sema); + consecutive_filter_tasks = 0; + gf_rmt_end(); + } + safe_int_inc(&fsess->active_threads); + skip_next_sema_wait = GF_FALSE; + + active_start = gf_sys_clock_high_res(); + + if (current_filter==NULL) { + //main thread + if (thid==0) { + if (!force_secondary_tasks) { + task = gf_fq_pop(fsess->main_thread_tasks); + } + if (!task) { + task = gf_fq_pop(fsess->tasks); + if (task && task->blocking) { + gf_fq_add(fsess->tasks, task); + task = NULL; + gf_fs_sema_io(fsess, GF_TRUE, GF_FALSE); + } + } + force_secondary_tasks = GF_FALSE; + } else { + task = gf_fq_pop(fsess->tasks); + } + if (task) { + assert( task->run_task ); + assert( task->notified ); + } + } else { + //keep task in filter tasks list until done + task = gf_fq_head(current_filter->tasks); + if (task) { + assert( task->run_task ); + assert( ! task->notified ); + } + } + + if (!task) { + u32 force_nb_notif = 0; + next_task_schedule_time = 0; + //no more task and EOS signal + if (fsess->run_status != GF_OK) + break; + + if (!fsess->tasks_pending && fsess->main_th.has_seen_eot) { + //check all threads + Bool all_done = GF_TRUE; + + for (i=0; ithreads, i); + if (!st->has_seen_eot) { + all_done = GF_FALSE; + force_nb_notif++; + } + } + if (all_done) + break; + } + if (current_filter) { + current_filter->scheduled_for_next_task = GF_FALSE; + current_filter->process_th_id = 0; + assert(current_filter->in_process); + current_filter->in_process = GF_FALSE; + } + current_filter = NULL; + sess_thread->active_time += gf_sys_clock_high_res() - active_start; + + + //no pending tasks and first time main task queue is empty, flush to detect if we + //are indeed done + if (!fsess->tasks_pending && !fsess->tasks_in_process && !sess_thread->has_seen_eot && !gf_fq_count(fsess->tasks)) { + //maybe last task, force a notify to check if we are truly done + sess_thread->has_seen_eot = GF_TRUE; + //not main thread and some tasks pending on main, notify only ourselves + if (thid && gf_fq_count(fsess->main_thread_tasks)) { + gf_fs_sema_io(fsess, GF_TRUE, use_main_sema); + } + //main thread exit probing, send a notify to main sema (for this thread), and N for the secondary one + else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u notify scheduler main semaphore\n", gf_th_id())); + gf_sema_notify(fsess->semaphore_main, 1); + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u notify scheduler secondary semaphore %d\n", gf_th_id(), th_count)); + gf_sema_notify(fsess->semaphore_other, th_count); + } + } + //this thread and the main thread are done but we still have unfinished threads, re-notify everyone + else if (!fsess->tasks_pending && fsess->main_th.has_seen_eot && force_nb_notif) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u notify scheduler main semaphore\n", gf_th_id())); + gf_sema_notify(fsess->semaphore_main, 1); + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u notify scheduler secondary semaphore %d\n", gf_th_id(), th_count)); + gf_sema_notify(fsess->semaphore_other, th_count); + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u: no task available\n", sys_thid)); + + //no main thread, return + if (!thid && fsess->no_main_thread) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Main thread proc exit\n")); + return 0; + } + + if (do_regulate) { + gf_sleep(0); + } + continue; + } +#ifdef CHECK_TASK_LIST_INTEGRITY + check_task_list(fsess->main_thread_tasks, task); + check_task_list(fsess->tasks, task); +#endif + if (current_filter) { + assert(current_filter==task->filter); + } + current_filter = task->filter; + + //this is a crude way of scheduling the next task, we should + //1- have a way to make sure we will not repost after a time-consuming task + //2- have a way to wait for the given amount of time rather than just do a sema_wait/notify in loop + if (task->schedule_next_time) { + s64 now = gf_sys_clock_high_res(); + s64 diff = task->schedule_next_time; + diff -= now; + diff /= 1000; + + + if (diff > 0) { + GF_FSTask *next; + s64 tdiff = diff; + s64 ndiff = 0; + + //no filter, just reschedule the task + if (!current_filter) { +#ifndef GPAC_DISABLE_LOG + const char *task_log_name = task->log_name; +#endif + next = gf_fq_head(fsess->tasks); + next_task_schedule_time = task->schedule_next_time; + assert(task->run_task); +#ifdef CHECK_TASK_LIST_INTEGRITY + check_task_list(fsess->main_thread_tasks, task); + check_task_list(fsess->tasks, task); + check_task_list(fsess->tasks_reservoir, task); +#endif + //tasks without filter are currently only posted to the secondary task list + gf_fq_add(fsess->tasks, task); + if (next) { + if (next->schedule_next_time <= (u64) now) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u: task %s reposted, next task time ready for execution\n", sys_thid, task_log_name)); + + skip_next_sema_wait = GF_TRUE; + continue; + } + ndiff = next->schedule_next_time; + ndiff -= now; + ndiff /= 1000; + if (ndifflog_name, next->filter ? next->filter->name : "", (s32) diff, (s32) ndiff)); + diff = ndiff; + } + } + + if (!do_regulate) { + diff = 0; + } + + if (diff && do_regulate) { + if (diff > fsess->max_sleep) + diff = fsess->max_sleep; + if (th_count==0) { + if ( gf_fq_count(fsess->tasks) > MONOTH_MIN_TASKS) + diff = MONOTH_MIN_SLEEP; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u: task %s reposted, %s task scheduled after this task, sleeping for %d ms (task diff %d - next diff %d)\n", sys_thid, task_log_name, next ? "next" : "no", diff, tdiff, ndiff)); + gf_sleep((u32) diff); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u: task %s reposted, next task scheduled after this task, rerun\n", sys_thid, task_log_name)); + } + skip_next_sema_wait = GF_TRUE; + continue; + } + + if (!task->filter->finalized) { +#ifdef CHECK_TASK_LIST_INTEGRITY + next = gf_fq_head(current_filter->tasks); + assert(next == task); + check_task_list(fsess->main_thread_tasks, task); + check_task_list(fsess->tasks_reservoir, task); +#endif + + //next in filter should be handled before this task, move task at the end of the filter task + next = gf_fq_get(current_filter->tasks, 1); + if (next && next->schedule_next_time < task->schedule_next_time) { + if (task->notified) { + assert(fsess->tasks_pending); + safe_int_dec(&fsess->tasks_pending); + task->notified = GF_FALSE; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u: task %s:%s reposted to filter task until task exec time is reached (%d us)\n", sys_thid, current_filter->name, task->log_name, (s32) (task->schedule_next_time - next->schedule_next_time) )); + //remove task + gf_fq_pop(current_filter->tasks); + //and queue it after the next one + gf_fq_add(current_filter->tasks, task); + //and continue with the same filter + continue; + } + //little optim here: if this is the main thread and we have other tasks pending + //check the timing of tasks in the secondary list. If a task is present with smaller time than + //the head of the main task, force a temporary swap to the secondary task list + if (!thid && task->notified && diff>10) { + next = gf_fq_head(fsess->tasks); + if (next && !next->blocking) { + u64 next_time_main = task->schedule_next_time; + u64 next_time_secondary = next->schedule_next_time; + //if we have several threads, also check the next task on the main task list + // (different from secondary tasks in multithread case) + if (th_count) { + GF_FSTask *next_main = gf_fq_head(fsess->main_thread_tasks); + if (next_main && (next_time_main > next_main->schedule_next_time)) + next_time_main = next_main->schedule_next_time; + } + + if (next_time_secondaryschedule_next_time, (s32) diff, next_time_secondary, next->filter->freg->name, next->log_name)); + diff = 0; + force_secondary_tasks = GF_TRUE; + } + } + } + + //move task to main list + if (!task->notified) { + task->notified = GF_TRUE; + safe_int_inc(&fsess->tasks_pending); + } + + sess_thread->active_time += gf_sys_clock_high_res() - active_start; + + if (next_task_schedule_time && (next_task_schedule_time <= task->schedule_next_time)) { + tdiff = next_task_schedule_time; + tdiff -= now; + if (tdiff < 0) tdiff=0; + if (tdiffname, task->log_name, (s32) diff, (s32) tdiff)); + diff = tdiff; + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u: next task has earlier exec time#2 than current task %s:%s, adjusting sleep (old %d - new %d)\n", sys_thid, current_filter->name, task->log_name, (s32) diff, (s32) tdiff)); + + } + } + + if (do_regulate && diff) { + if (diff > fsess->max_sleep) + diff = fsess->max_sleep; + if (th_count==0) { + if ( gf_fq_count(fsess->tasks) > MONOTH_MIN_TASKS) + diff = MONOTH_MIN_SLEEP; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u: task %s:%s postponed for %d ms (scheduled time "LLU" us, next task schedule "LLU" us)\n", sys_thid, current_filter->name, task->log_name, (s32) diff, task->schedule_next_time, next_task_schedule_time)); + + gf_sleep((u32) diff); + active_start = gf_sys_clock_high_res(); + } + diff = (s64)task->schedule_next_time; + diff -= (s64) gf_sys_clock_high_res(); + if (diff > 100 ) { + Bool use_main = (current_filter->freg->flags & GF_FS_REG_MAIN_THREAD) ? GF_TRUE : GF_FALSE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u: releasing current filter %s, exec time due in "LLD" us\n", sys_thid, current_filter->name, diff)); + current_filter->process_th_id = 0; + current_filter->in_process = GF_FALSE; + //don't touch the current filter tasks, just repost the task to the main/secondary list + assert(gf_fq_count(current_filter->tasks)); + current_filter = NULL; + +#ifdef CHECK_TASK_LIST_INTEGRITY + check_task_list(fsess->main_thread_tasks, task); + check_task_list(fsess->tasks, task); + check_task_list(fsess->tasks_reservoir, task); + assert(task->run_task); +#endif + + if (use_main) { + gf_fq_add(fsess->main_thread_tasks, task); + //we are the main thread and reposting to the main task list, don't notify/wait for the sema, just retry + //we are sure to get a task from main list at next iteration + if (use_main_sema) { + skip_next_sema_wait = GF_TRUE; + } else { + gf_fs_sema_io(fsess, GF_TRUE, GF_TRUE); + } + } else { + gf_fq_add(fsess->tasks, task); + //we are not the main thread and we are reposting to the secondary task list, don't notify/wait for the sema, just retry + //we are not sure to get a task from secondary list at next iteration, but the end of thread check will make + //sure we renotify secondary sema if some tasks are still pending + if (!use_main_sema) { + skip_next_sema_wait = GF_TRUE; + } else { + gf_fs_sema_io(fsess, GF_TRUE, GF_FALSE); + } + } + //we temporary force the main thread to fetch a task from the secondary list + //because the first main task was not yet due for execution + //it is likely that the execution of the next task will not wake up the main thread + //but we must reevaluate the previous main task timing, so we force a notification of the main sema + if (force_secondary_tasks) + gf_fs_sema_io(fsess, GF_TRUE, GF_TRUE); + + continue; + } + force_secondary_tasks=GF_FALSE; + } + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u: task %s:%s schedule time "LLU" us reached (diff %d ms)\n", sys_thid, current_filter ? current_filter->name : "", task->log_name, task->schedule_next_time, (s32) diff)); + + } + next_task_schedule_time = 0; + + if (current_filter) { + current_filter->scheduled_for_next_task = GF_TRUE; + assert(!current_filter->in_process); + current_filter->in_process = GF_TRUE; + current_filter->process_th_id = gf_th_id(); + } + + sess_thread->nb_tasks++; + sess_thread->has_seen_eot = GF_FALSE; + if (task->filter) { + assert(gf_fq_count(task->filter->tasks)); + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u task#%d %p executing Filter %s::%s (%d tasks pending, %d(%d) process task queued)\n", sys_thid, sess_thread->nb_tasks, task, task->filter ? task->filter->name : "none", task->log_name, fsess->tasks_pending, task->filter ? task->filter->process_task_queued : 0, task->filter ? gf_fq_count(task->filter->tasks) : 0)); + + safe_int_inc(& fsess->tasks_in_process ); + assert( task->run_task ); + task_time = gf_sys_clock_high_res(); + + task->can_swap = GF_FALSE; + task->requeue_request = GF_FALSE; + task->run_task(task); + requeue = task->requeue_request; + + task_time = gf_sys_clock_high_res() - task_time; + safe_int_dec(& fsess->tasks_in_process ); + + //may now be NULL if task was a filter destruction task + current_filter = task->filter; + +#ifdef CHECK_TASK_LIST_INTEGRITY + prev_current_filter = task->filter; +#endif + + //source task was current filter, pop the filter task list + if (current_filter) { + current_filter->nb_tasks_done++; + current_filter->time_process += task_time; + consecutive_filter_tasks++; + + gf_mx_p(current_filter->tasks_mx); + //if last task + if ( (gf_fq_count(current_filter->tasks)==1) + //if requeue request and stream reset pending (we must exit the filter task loop for the reset task to pe processed) + || (requeue && current_filter->stream_reset_pending) + //or requeue request and pid swap pending (we must exit the filter task loop for the swap task to pe processed) + || (requeue && (current_filter->swap_pidinst_src || current_filter->swap_pidinst_dst) ) + //or requeue request and pid detach / cap negotiate pending + || (requeue && (current_filter->out_pid_connection_pending || current_filter->detached_pid_inst || current_filter->caps_negociate) ) + + //or requeue request and we have been running on that filter for more than 10 times, abort + || (requeue && (consecutive_filter_tasks>10)) + ) { + + if (requeue) { + //filter task can be pushed back the queue of tasks + if (task->can_swap) { + GF_FSTask *next_task; + + //drop task from filter task list + gf_fq_pop(current_filter->tasks); + + next_task = gf_fq_head(current_filter->tasks); + //if first task was notified, swap the flag + if (next_task) { + //see note in post_task_ex for caution about this !! + next_task->notified = task->notified; + task->notified = GF_FALSE; + } + //requeue task + gf_fq_add(current_filter->tasks, task); + + //ans swap task for later requeing + if (next_task) task = next_task; + } + //otherwise (can't swap) keep task first in the list + + //don't reset scheduled_for_next_task flag if requeued to make sure no other task posted from + //another thread will post to main sched + +#ifdef CHECK_TASK_LIST_INTEGRITY + skip_filter_task_check = GF_TRUE; +#endif + } else { + //no requeue, filter no longer scheduled and drop task + current_filter->scheduled_for_next_task = GF_FALSE; + + //drop task from filter task list + gf_fq_pop(current_filter->tasks); + } + + current_filter->process_th_id = 0; + current_filter->in_process = GF_FALSE; + + //unlock once we modified in_process, otherwise this will make our assert fail + gf_mx_v(current_filter->tasks_mx); +#ifdef CHECK_TASK_LIST_INTEGRITY + if (requeue && !skip_filter_task_check) check_task_list(current_filter->tasks, task); +#endif + current_filter = NULL; + } else { + //drop task from filter task list + gf_fq_pop(current_filter->tasks); + + //not requeued, no more tasks, deactivate filter + if (!requeue && !gf_fq_count(current_filter->tasks)) { + current_filter->process_th_id = 0; + current_filter->in_process = GF_FALSE; + current_filter->scheduled_for_next_task = GF_FALSE; + gf_mx_v(current_filter->tasks_mx); + current_filter = NULL; + } else { +#ifdef CHECK_TASK_LIST_INTEGRITY + check_task_list(fsess->main_thread_tasks, task); + check_task_list(fsess->tasks, task); + check_task_list(fsess->tasks_reservoir, task); +#endif + + //requeue task in current filter + if (requeue) + gf_fq_add(current_filter->tasks, task); + + gf_mx_v(current_filter->tasks_mx); + } + } + } + //do not touch the filter task list after this, it has to be mutex protected to ensure proper posting of tasks + + notified = task->notified; + if (requeue) { + //if requeue on a filter active, use filter queue to avoid another thread grabing the task (we would have concurrent access to the filter) + if (current_filter) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u re-posted task Filter %s::%s in filter tasks (%d pending)\n", sys_thid, task->filter->name, task->log_name, fsess->tasks_pending)); + task->notified = GF_FALSE; + //keep this thread running on the current filter no signaling of semaphore + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u re-posted task Filter %s::%s in %s tasks (%d pending)\n", sys_thid, task->filter ? task->filter->name : "none", task->log_name, (task->filter && (task->filter->freg->flags & GF_FS_REG_MAIN_THREAD)) ? "main" : "secondary", fsess->tasks_pending)); + + task->notified = GF_TRUE; + safe_int_inc(&fsess->tasks_pending); + +#ifdef CHECK_TASK_LIST_INTEGRITY + check_task_list(fsess->main_thread_tasks, task); + check_task_list(fsess->tasks, task); + check_task_list(fsess->tasks_reservoir, task); + if (prev_current_filter && !skip_filter_task_check) check_task_list(prev_current_filter->tasks, task); +#endif + + //main thread + if (task->filter && (task->filter->freg->flags & GF_FS_REG_MAIN_THREAD)) { + gf_fq_add(fsess->main_thread_tasks, task); + } else { + gf_fq_add(fsess->tasks, task); + } + gf_fs_sema_io(fsess, GF_TRUE, use_main_sema); + } + } else { +#ifdef CHECK_TASK_LIST_INTEGRITY + check_task_list(fsess->main_thread_tasks, task); + check_task_list(fsess->tasks, task); + check_task_list(fsess->tasks_reservoir, task); + if (prev_current_filter) + check_task_list(prev_current_filter->tasks, task); + + { + gf_mx_p(fsess->filters_mx); + u32 k, c2 = gf_list_count(fsess->filters); + for (k=0; kfilters, k); + check_task_list(af->tasks, task); + } + gf_mx_v(fsess->filters_mx); + } +#endif + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Thread %u task#%d %p pushed to reservoir\n", sys_thid, sess_thread->nb_tasks, task)); + + if (fsess->tasks_reservoir) { + memset(task, 0, sizeof(GF_FSTask)); + gf_fq_add(fsess->tasks_reservoir, task); + } else { + gf_free(task); + } + } + + //decrement task counter + if (notified) { + assert(fsess->tasks_pending); + safe_int_dec(&fsess->tasks_pending); + } + if (current_filter) { + current_filter->process_th_id = 0; + current_filter->in_process = GF_FALSE; + } + //not requeuing and first time we have an empty task queue, flush to detect if we are indeed done + if (!current_filter && !fsess->tasks_pending && !sess_thread->has_seen_eot && !gf_fq_count(fsess->tasks)) { + //if not the main thread, or if main thread and task list is empty, enter end of session probing mode + if (thid || !gf_fq_count(fsess->main_thread_tasks) ) { + //maybe last task, force a notify to check if we are truly done. We only tag "session done" for the non-main + //threads, in order to enter the end-of session signaling above + if (thid) sess_thread->has_seen_eot = GF_TRUE; + gf_fs_sema_io(fsess, GF_TRUE, use_main_sema); + } + } + + sess_thread->active_time += gf_sys_clock_high_res() - active_start; + + + //no main thread, return + if (!thid && fsess->no_main_thread && !current_filter && !fsess->pid_connect_tasks_pending) { + gf_rmt_end(); + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Main thread proc exit\n")); + return 0; + } + } + + gf_rmt_end(); + + //no main thread, return + if (!thid && fsess->no_main_thread) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_SCHEDULER, ("Main thread proc exit\n")); + return 0; + } + sess_thread->run_time = gf_sys_clock_high_res() - enter_time; + + safe_int_inc(&fsess->nb_threads_stopped); + + if (!fsess->run_status) + fsess->run_status = GF_EOS; + + // thread exit, notify the semaphores + if (fsess->semaphore_main && ! gf_sema_notify(fsess->semaphore_main, 1)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCHEDULER, ("Failed to notify main semaphore, might hang up !!\n")); + } + if (fsess->semaphore_other && ! gf_sema_notify(fsess->semaphore_other, th_count)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCHEDULER, ("Failed to notify secondary semaphore, might hang up !!\n")); + } + + return 0; +} + + +GF_EXPORT +GF_Err gf_fs_run(GF_FilterSession *fsess) +{ + u32 i, nb_threads; + assert(fsess); + + fsess->run_status = GF_OK; + fsess->main_th.has_seen_eot = GF_FALSE; + fsess->nb_threads_stopped = 0; + + nb_threads = gf_list_count(fsess->threads); + for (i=0;ithreads, i); + gf_th_run(sess_th->th, (gf_thread_run) gf_fs_thread_proc, sess_th); + } + if (fsess->no_main_thread) return GF_OK; + + gf_fs_thread_proc(&fsess->main_th); + + //wait for all threads to be done + while (nb_threads+1 != fsess->nb_threads_stopped) { + gf_sleep(1); + } + + return fsess->run_status; +} + +void gf_fs_run_step(GF_FilterSession *fsess) +{ + gf_fs_thread_proc(&fsess->main_th); +} + +GF_EXPORT +GF_Err gf_fs_abort(GF_FilterSession *fsess, Bool do_flush) +{ + u32 i, count; + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("Session abort from user, stoping sources\n")); + if (!fsess) return GF_BAD_PARAM; + + if (!do_flush) { + fsess->in_final_flush = GF_TRUE; + fsess->run_status = GF_EOS; + return GF_OK; + } + + gf_mx_p(fsess->filters_mx); + count = gf_list_count(fsess->filters); + //disable all sources + for (i=0; ifilters, i); + //force end of session on all sources, and on all filters connected to these sources, and dispatch end of stream on all outputs pids of these filters + //if we don't propagate on connected filters, we take the risk of not deactivating demuxers working from file + //(eg ignoring input packets) + if (!filter->num_input_pids) { + u32 j, k, l; + filter->disabled = GF_TRUE; + for (j=0; jnum_output_pids; j++) { + GF_FilterPid *pid = gf_list_get(filter->output_pids, j); + gf_filter_pid_set_eos(pid); + for (k=0; knum_destinations; k++) { + GF_FilterPidInst *pidi = gf_list_get(pid->destinations, k); + pidi->filter->disabled = GF_TRUE; + for (l=0; lfilter->num_output_pids; l++) { + GF_FilterPid *opid = gf_list_get(pidi->filter->output_pids, l); + if (opid->filter->freg->process_event) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_STOP, opid); + opid->filter->freg->process_event(opid->filter, &evt); + } + gf_filter_pid_set_eos(opid); + } + } + } + } + } + fsess->in_final_flush = GF_TRUE; + + gf_mx_v(fsess->filters_mx); + return GF_OK; +} + +GF_EXPORT +GF_Err gf_fs_stop(GF_FilterSession *fsess) +{ + u32 i, count = fsess->threads ? gf_list_count(fsess->threads) : 0; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_FILTER, ("Session stop\n")); + if (count+1 == fsess->nb_threads_stopped) { + return GF_OK; + } + + if (!fsess->run_status) + fsess->run_status = GF_EOS; + + for (i=0; i < count; i++) { + gf_fs_sema_io(fsess, GF_TRUE, GF_FALSE); + } + + //wait for all threads to be done, we might still need flushing the main thread queue + while (fsess->no_main_thread) { + gf_fs_thread_proc(&fsess->main_th); + if (gf_fq_count(fsess->main_thread_tasks)) + continue; + + if (count && (count == fsess->nb_threads_stopped) && gf_fq_count(fsess->tasks) ) { + continue; + } + break; + } + if (fsess->no_main_thread) { + safe_int_inc(&fsess->nb_threads_stopped); + fsess->main_th.has_seen_eot = GF_TRUE; + } + + while (count+1 != fsess->nb_threads_stopped) { + for (i=0; i < count; i++) { + gf_fs_sema_io(fsess, GF_TRUE, GF_FALSE); + } + gf_sleep(0); + //we may have tasks in main task list posted by other threads + if (fsess->no_main_thread) { + gf_fs_thread_proc(&fsess->main_th); + fsess->main_th.has_seen_eot = GF_TRUE; + } + } + return GF_OK; +} + +static GFINLINE void print_filter_name(GF_Filter *f, Bool skip_id, Bool skip_args) +{ + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("%s", f->freg->name)); + if (strcmp(f->name, f->freg->name)) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (" \"%s\"", f->name)); + } + if (!skip_id && f->id) GF_LOG(GF_LOG_INFO, GF_LOG_APP, (" ID %s", f->id)); + if (f->dynamic_filter || skip_args) return; + + if (!f->src_args && !f->orig_args && !f->dst_args && !f->dynamic_source_ids) return; + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (" (")); + if (f->src_args) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("%s", f->src_args)); + } + else if (f->orig_args) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("%s", f->orig_args)); + } + else if (f->dst_args) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("%s", f->dst_args)); + } + + if (f->dynamic_source_ids) GF_LOG(GF_LOG_INFO, GF_LOG_APP, (",resolved SID:%s", f->source_ids)); + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (")")); +} + +GF_EXPORT +void gf_fs_print_stats(GF_FilterSession *fsess) +{ + u64 run_time=0, active_time=0, nb_tasks=0, nb_filters=0; + u32 i, count; + + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\n")); + if (fsess->filters_mx) gf_mx_p(fsess->filters_mx); + + count=gf_list_count(fsess->filters); + for (i=0; ifilters, i); + if (f->multi_sink_target) continue; + nb_filters++; + } + + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Filter stats - %d filters\n", nb_filters)); + for (i=0; ifilters, i); + if (f->multi_sink_target) continue; + + ipids = f->num_input_pids; + opids = f->num_output_pids; + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\tFilter ")); + print_filter_name(f, GF_FALSE, GF_FALSE); + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (" : %d input pids %d output pids "LLU" tasks "LLU" us process time\n", ipids, opids, f->nb_tasks_done, f->time_process)); + + if (ipids) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\t\t"LLU" packets processed "LLU" bytes processed", f->nb_pck_processed, f->nb_bytes_processed)); + if (f->time_process) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (" (%g pck/sec %g mbps)", (Double) f->nb_pck_processed*1000000/f->time_process, (Double) f->nb_bytes_processed*8/f->time_process)); + } + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\n")); + } + if (opids) { + if (f->nb_hw_pck_sent) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\t\t"LLU" hardware frames sent", f->nb_hw_pck_sent)); + if (f->time_process) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (" (%g pck/sec)", (Double) f->nb_hw_pck_sent*1000000/f->time_process)); + } + + } else if (f->nb_pck_sent) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\t\t"LLU" packets sent "LLU" bytes sent", f->nb_pck_sent, f->nb_bytes_sent)); + if (f->time_process) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (" (%g pck/sec %g mbps)", (Double) f->nb_pck_sent*1000000/f->time_process, (Double) f->nb_bytes_sent*8/f->time_process)); + } + } + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\n")); + } + + for (k=0; kinput_pids, k); + if (!pid->pid) continue; + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\t\t* input PID %s: %d packets received\n", pid->pid->name, pid->pid->nb_pck_sent)); + } +#ifndef GPAC_DISABLE_LOG + for (k=0; koutput_pids, k); + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\t\t* output PID %s: %d packets sent\n", pid->name, pid->nb_pck_sent)); + } + if (f->nb_errors) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\t\t%d errors while processing\n", f->nb_errors)); + } +#endif + + } + if (fsess->filters_mx) gf_mx_v(fsess->filters_mx); + + count=gf_list_count(fsess->threads); + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Session stats - threads %d\n", 1+count)); + + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\tThread %u: run_time "LLU" us active_time "LLU" us nb_tasks "LLU"\n", 1, fsess->main_th.run_time, fsess->main_th.active_time, fsess->main_th.nb_tasks)); + + run_time+=fsess->main_th.run_time; + active_time+=fsess->main_th.active_time; + nb_tasks+=fsess->main_th.nb_tasks; + + for (i=0; ithreads, i); + + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\tThread %u: run_time "LLU" us active_time "LLU" us nb_tasks "LLU"\n", i+2, s->run_time, s->active_time, s->nb_tasks)); + + run_time+=s->run_time; + active_time+=s->active_time; + nb_tasks+=s->nb_tasks; + } + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\nTotal: run_time "LLU" us active_time "LLU" us nb_tasks "LLU"\n", run_time, active_time, nb_tasks)); +} + +static void gf_fs_print_filter_outputs(GF_Filter *f, GF_List *filters_done, u32 indent, GF_FilterPid *pid, GF_Filter *alias_for) +{ + u32 i=0; + + while (iname)); + } + print_filter_name(f, GF_TRUE, GF_FALSE); + if (f->id) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (" (ID=%s)\n", f->id)); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (" (ptr=%p)\n", f)); + } + if (gf_list_find(filters_done, f)>=0) + return; + + gf_list_add(filters_done, f); + if (alias_for) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (" (<=> ")); + print_filter_name(alias_for, GF_TRUE, GF_TRUE); + if (alias_for->id) { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (" ID=%s", alias_for->id)); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (" ptr=%p", alias_for)); + } + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (")\n")); + } + + for (i=0; inum_output_pids; i++) { + u32 j, k; + GF_FilterPid *pidout = gf_list_get(f->output_pids, i); + for (j=0; jnum_destinations; j++) { + GF_FilterPidInst *pidi = gf_list_get(pidout->destinations, j); + GF_Filter *alias = NULL; + for (k=0; kdestination_filters); k++) { + alias = gf_list_get(f->destination_filters, k); + if (alias->multi_sink_target == pidi->filter) + break; + alias = NULL; + } + if (alias) { + + gf_fs_print_filter_outputs(alias, filters_done, indent+1, pidout, pidi->filter); + } else { + gf_fs_print_filter_outputs(pidi->filter, filters_done, indent+1, pidout, NULL); + } + } + } + +} +GF_EXPORT +void gf_fs_print_connections(GF_FilterSession *fsess) +{ + u32 i, count; + Bool has_undefined=GF_FALSE; + Bool has_connected=GF_FALSE; + Bool has_unconnected=GF_FALSE; + GF_List *filters_done; + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\n")); + if (fsess->filters_mx) gf_mx_p(fsess->filters_mx); + + filters_done = gf_list_new(); + + count=gf_list_count(fsess->filters); + for (i=0; ifilters, i); + //only dump sources + if (f->num_input_pids) continue; + if (!f->num_output_pids) continue; + if (!has_connected) { + has_connected = GF_TRUE; + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Filters connected:\n")); + } + gf_fs_print_filter_outputs(f, filters_done, 0, NULL, NULL); + } + for (i=0; ifilters, i); + //only dump not connected ones + if (f->num_input_pids || f->num_output_pids || f->multi_sink_target) continue; + if (!has_unconnected) { + has_unconnected = GF_TRUE; + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Filters not connected:\n")); + } + gf_fs_print_filter_outputs(f, filters_done, 0, NULL, NULL); + } + for (i=0; ifilters, i); + if (f->multi_sink_target) continue; + if (gf_list_find(filters_done, f)>=0) continue; + if (!has_undefined) { + has_undefined = GF_TRUE; + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Filters in unknown connection state:\n")); + } + gf_fs_print_filter_outputs(f, filters_done, 0, NULL, NULL); + } + + if (fsess->filters_mx) gf_mx_v(fsess->filters_mx); + gf_list_del(filters_done); +} + + +GF_EXPORT +void gf_fs_send_update(GF_FilterSession *fsess, const char *fid, GF_Filter *filter, const char *name, const char *val, GF_EventPropagateType propagate_mask) +{ + GF_FilterUpdate *upd; + u32 i, count; + Bool removed = GF_FALSE; + if ((!fid && !filter) || !name) return; + + if (fsess->filters_mx) gf_mx_p(fsess->filters_mx); + + if (!filter) { + GF_Filter *reg_filter = NULL; + count = gf_list_count(fsess->filters); + for (i=0; ifilters, i); + if (filter->id && !strcmp(filter->id, fid)) { + break; + } + if (filter->name && !strcmp(filter->name, fid)) { + break; + } + if (!reg_filter && !strcmp(filter->freg->name, fid)) + reg_filter = filter; + filter = NULL; + } + if (!filter) + filter = reg_filter; + } + + if (filter && filter->multi_sink_target) + filter = filter->multi_sink_target; + + removed = (!filter || filter->removed || filter->finalized) ? GF_TRUE : GF_FALSE; + if (fsess->filters_mx) gf_mx_v(fsess->filters_mx); + + if (removed) return; + + GF_SAFEALLOC(upd, GF_FilterUpdate); + if (!val) { + char *sep = strchr(name, fsess->sep_name); + if (sep) sep[0] = 0; + upd->name = gf_strdup(name); + upd->val = sep ? gf_strdup(sep+1) : NULL; + if (sep) sep[0] = fsess->sep_name; + } else { + upd->name = gf_strdup(name); + upd->val = gf_strdup(val); + } + upd->recursive = propagate_mask; + gf_fs_post_task(fsess, gf_filter_update_arg_task, filter, NULL, "update_arg", upd); +} + +static GF_FilterProbeScore probe_meta_check_builtin_format(GF_FilterSession *fsess, GF_FilterRegister *freg, const char *url, const char *mime, char *fargs) +{ + char szExt[100]; + const char *ext = gf_file_ext_start(url); + u32 len=0, i, j, count = gf_list_count(fsess->registry); + if (ext) { + ext++; + len = (u32) strlen(ext); + } + //check in filter args if we have a format set, in which case replace URL ext by the given format + if (fargs) { + char szExtN[10]; + char *ext_arg; + sprintf(szExtN, "ext%c", fsess->sep_name); + ext_arg = strstr(fargs, szExtN); + if (ext_arg) { + char *next_arg; + ext_arg+=4; + next_arg = strchr(ext_arg, fsess->sep_args); + if (next_arg) { + len = (u32) (next_arg - ext_arg); + } else { + len = (u32) strlen(ext_arg); + } + if (len>99) len=99; + strncpy(szExt, ext_arg, len); + szExt[len] = 0; + ext = szExt; + } + } + + if (mime) { + if (strstr(mime, "/mp4")) return GF_FPROBE_MAYBE_SUPPORTED; + } + + for (i=0; iregistry, i); + if (reg==freg) continue; + if (reg->flags & GF_FS_REG_META) continue; + + j=0; + while (reg->args) { + dst_arg = ®->args[j]; + if (!dst_arg || !dst_arg->arg_name) { + dst_arg=NULL; + break; + } + if (!strcmp(dst_arg->arg_name, "dst")) break; + dst_arg = NULL; + j++; + } + /*check prober*/ + if (dst_arg) { + if (reg->probe_url) { + GF_FilterProbeScore s = reg->probe_url(url, mime); + if (s==GF_FPROBE_SUPPORTED) + return GF_FPROBE_MAYBE_SUPPORTED; + } + continue; + } + + /* check muxers*/ + for (j=0; jnb_caps; j++) { + char *value=NULL; + const char *pattern = NULL; + const GF_FilterCapability *cap = ®->caps[j]; + if (! (cap->flags & GF_CAPFLAG_OUTPUT) ) + continue; + if (cap->flags & GF_CAPFLAG_EXCLUDED) + continue; + + if (cap->code==GF_PROP_PID_FILE_EXT) { + if (ext) { + value = cap->val.value.string; + pattern = ext; + } + } else if (cap->code==GF_PROP_PID_MIME) { + if (mime) { + value = cap->val.value.string; + pattern = mime; + } + } + while (value) { + char *match = strstr(value, pattern); + if (!match) break; + if (!match[len] || match[len]=='|') + return GF_FPROBE_MAYBE_SUPPORTED; + value = match+1; + } + } + } + return GF_FPROBE_SUPPORTED; +} + + +GF_Filter *gf_fs_load_source_dest_internal(GF_FilterSession *fsess, const char *url, const char *user_args, const char *parent_url, GF_Err *err, GF_Filter *filter, GF_Filter *dst_filter, Bool for_source, Bool no_args_inherit, Bool *probe_only) +{ + GF_FilterProbeScore score = GF_FPROBE_NOT_SUPPORTED; + GF_FilterRegister *candidate_freg=NULL; + GF_Filter *alias_for_filter = NULL; + const GF_FilterArgs *src_dst_arg=NULL; + u32 i, count, user_args_len, arg_type; + char szForceReg[20]; + char szMime[50]; + GF_Err e; + const char *force_freg = NULL; + char *sURL, *mime_type, *args, *sep; + char szExt[50]; + Bool free_url=GF_FALSE; + memset(szExt, 0, sizeof(szExt)); + + if (err) *err = GF_OK; + + mime_type = NULL; + //destination, extract mime from arguments + if (!for_source) { + sprintf(szMime, "%cmime=", fsess->sep_args); + mime_type = strstr(url, szMime); + if (!mime_type && user_args) + mime_type = strstr(user_args, szMime); + + if (mime_type) { + strncpy(szMime, mime_type+6, 49); + szMime[49]=0; + sep = strchr(szMime, fsess->sep_args); + if (sep) sep[0] = 0; + mime_type = szMime; + } + } + sURL = NULL; + if (!url || !strncmp(url, "\\\\", 2) ) { + return NULL; + } + if (filter) { + sURL = (char *) url; + } else { + /*used by GUIs scripts to skip URL concatenation*/ + if (!strncmp(url, "gpac://", 7)) sURL = gf_strdup(url+7); + /*opera-style localhost URLs*/ + else if (!strncmp(url, "file://localhost", 16)) sURL = gf_strdup(url+16); + else if (parent_url) sURL = gf_url_concatenate(parent_url, url); + + /*path absolute*/ + if (!sURL) sURL = gf_strdup(url); + free_url=GF_TRUE; + + if (!strncmp(sURL, "gpac://", 7)) { + u32 ulen = (u32) strlen(sURL+7); + memmove(sURL, sURL+7, ulen); + sURL[ulen]=0; + } + + if (for_source && gf_url_is_local(sURL)) { + char *frag_par, *cgi, *ext_start; + char f_c=0; + gf_url_to_fs_path(sURL); + sep = (char *)gf_fs_path_escape_colon(fsess, sURL); + if (sep) sep[0] = 0; + + ext_start = gf_file_ext_start(sURL); + if (!ext_start) ext_start = sURL; + frag_par = strchr(ext_start, '#'); + cgi = strchr(ext_start, '?'); + if (frag_par && cgi && (cgisep_args; + if (frag_par) frag_par[0] = f_c; + + if (err) *err = GF_URL_ERROR; + gf_free(sURL); + return NULL; + } + if (frag_par) frag_par[0] = f_c; + if (sep) sep[0] = fsess->sep_args; + } + } + sep = (char *)gf_fs_path_escape_colon(fsess, sURL); + + sprintf(szForceReg, "gfreg%c", fsess->sep_name); + force_freg = NULL; + if (sep) { + sep[0] = 0; + force_freg = strstr(sep+1, szForceReg); + } + if (!force_freg && user_args) { + force_freg = strstr(user_args, szForceReg); + } + if (force_freg) + force_freg += 6; + +restart: + //check all our registered filters + count = gf_list_count(fsess->registry); + for (i=0; iregistry, i); + if (! freg->probe_url) continue; + if (force_freg && strncmp(force_freg, freg->name, strlen(freg->name))) continue; + if (! freg->args) continue; + if (filter && (gf_list_find(filter->blacklisted, freg) >=0)) continue; + + j=0; + while (freg->args) { + src_dst_arg = &freg->args[j]; + if (!src_dst_arg || !src_dst_arg->arg_name) { + src_dst_arg=NULL; + break; + } + if (for_source && !strcmp(src_dst_arg->arg_name, "src")) break; + else if (!for_source && !strcmp(src_dst_arg->arg_name, "dst")) break; + src_dst_arg = NULL; + j++; + } + if (!src_dst_arg) + continue; + + s = freg->probe_url(sURL, mime_type); + /* destination meta filter: change GF_FPROBE_SUPPORTED to GF_FPROBE_MAYBE_SUPPORTED for internal mux formats + in order to avoid always giving the hand to the meta filter*/ + if (!for_source && (s == GF_FPROBE_SUPPORTED) && (freg->flags & GF_FS_REG_META)) { + s = probe_meta_check_builtin_format(fsess, freg, sURL, mime_type, sep ? sep+1 : NULL); + } + //higher score, use this new registry + if (s > score) { + candidate_freg = freg; + score = s; + } + //same score and higher priority (0 being highest), use this new registry + else if ((s == score) && candidate_freg && (freg->prioritypriority) ) { + candidate_freg = freg; + score = s; + } + } + if (probe_only) { + *probe_only = candidate_freg ? GF_TRUE : GF_FALSE; + if (free_url) + gf_free(sURL); + if (err) *err = GF_OK; + return NULL; + } + + if (!candidate_freg) { + if (force_freg) { + GF_LOG(GF_LOG_INFO, GF_LOG_FILTER, ("No source filter named %s found, retrying without forcing registry\n", force_freg)); + force_freg = NULL; + goto restart; + } + if (free_url) + gf_free(sURL); + if (err) *err = GF_NOT_SUPPORTED; + if (filter) filter->finalized = GF_TRUE; + return NULL; + } + if (sep) sep[0] = fsess->sep_args; + + user_args_len = user_args ? (u32) strlen(user_args) : 0; + args = gf_malloc(sizeof(char)*(5+strlen(sURL) + (user_args_len ? user_args_len + 8/*for potential :gpac: */ :0) ) ); + + sprintf(args, "%s%c", for_source ? "src" : "dst", fsess->sep_name); + strcat(args, sURL); + if (user_args_len) { + if (fsess->sep_args==':') strcat(args, ":gpac:"); + else { + char szSep[2]; + szSep[0] = fsess->sep_args; + szSep[1] = 0; + strcat(args, szSep); + } + strcat(args, user_args); + } + + e = GF_OK; + arg_type = GF_FILTER_ARG_EXPLICIT_SINK; + if (for_source) { + if (no_args_inherit) arg_type = GF_FILTER_ARG_EXPLICIT_SOURCE_NO_DST_INHERIT; + else arg_type = GF_FILTER_ARG_EXPLICIT_SOURCE; + } + + if (!for_source && candidate_freg->use_alias) { + u32 fcount = gf_list_count(fsess->filters); + for (i=0; ifilters, i); + if (f->freg != candidate_freg) continue; + if (f->freg->use_alias(f, sURL, mime_type)) { + alias_for_filter = f; + break; + } + } + } + + if (!filter) { + filter = gf_filter_new(fsess, candidate_freg, args, NULL, arg_type, err, alias_for_filter, GF_FALSE); + } else { + filter->freg = candidate_freg; + e = gf_filter_new_finalize(filter, args, arg_type); + if (err) *err = e; + } + + if (free_url) + gf_free(sURL); + + if (filter) { + if (filter->src_args) gf_free(filter->src_args); + filter->src_args = args; + //for link resolution + if (dst_filter && for_source) { + if (gf_list_find(filter->destination_links, dst_filter)<0) + gf_list_add(filter->destination_links, dst_filter); + //to remember our connection target + filter->target_filter = dst_filter; + } + } else { + gf_free(args); + } + + if (!e && filter && !filter->num_output_pids && for_source) + gf_filter_post_process_task(filter); + + return filter; +} + +GF_EXPORT +GF_Filter *gf_fs_load_source(GF_FilterSession *fsess, const char *url, const char *args, const char *parent_url, GF_Err *err) +{ + return gf_fs_load_source_dest_internal(fsess, url, args, parent_url, err, NULL, NULL, GF_TRUE, GF_FALSE, NULL); +} + +GF_EXPORT +GF_Filter *gf_fs_load_destination(GF_FilterSession *fsess, const char *url, const char *args, const char *parent_url, GF_Err *err) +{ + return gf_fs_load_source_dest_internal(fsess, url, args, parent_url, err, NULL, NULL, GF_FALSE, GF_FALSE, NULL); +} + + +GF_EXPORT +GF_Err gf_filter_add_event_listener(GF_Filter *filter, GF_FSEventListener *el) +{ + GF_Err e; + if (!filter || !filter->session || !el || !el->on_event) return GF_BAD_PARAM; + while (filter->session->in_event_listener) gf_sleep(1); + gf_mx_p(filter->session->evt_mx); + if (!filter->session->event_listeners) { + filter->session->event_listeners = gf_list_new(); + } + e = gf_list_add(filter->session->event_listeners, el); + gf_mx_v(filter->session->evt_mx); + return e; +} + +GF_EXPORT +GF_Err gf_filter_remove_event_listener(GF_Filter *filter, GF_FSEventListener *el) +{ + if (!filter || !filter->session || !el || !filter->session->event_listeners) return GF_BAD_PARAM; + + while (filter->session->in_event_listener) gf_sleep(1); + gf_mx_p(filter->session->evt_mx); + gf_list_del_item(filter->session->event_listeners, el); + if (!gf_list_count(filter->session->event_listeners)) { + gf_list_del(filter->session->event_listeners); + filter->session->event_listeners=NULL; + } + gf_mx_v(filter->session->evt_mx); + return GF_OK; +} + +GF_EXPORT +Bool gf_filter_forward_gf_event(GF_Filter *filter, GF_Event *evt, Bool consumed, Bool skip_user) +{ + if (!filter || !filter->session || filter->session->in_final_flush) return GF_FALSE; + + if (filter->session->event_listeners) { + GF_FSEventListener *el; + u32 i=0; + + gf_mx_p(filter->session->evt_mx); + filter->session->in_event_listener ++; + gf_mx_v(filter->session->evt_mx); + while ((el = gf_list_enum(filter->session->event_listeners, &i))) { + if (el->on_event(el->udta, evt, consumed)) { + filter->session->in_event_listener --; + return GF_TRUE; + } + } + filter->session->in_event_listener --; + } + + if (!skip_user && !consumed && filter->session->ui_event_proc) { + Bool res; +// term->nb_calls_in_event_proc++; + res = gf_fs_ui_event(filter->session, evt); +// term->nb_calls_in_event_proc--; + return res; + } + return GF_FALSE; +} + +GF_EXPORT +Bool gf_filter_send_gf_event(GF_Filter *filter, GF_Event *evt) +{ + return gf_filter_forward_gf_event(filter, evt, GF_FALSE, GF_FALSE); +} + + +static void gf_fs_print_jsf_connection(GF_FilterSession *session, char *filter_name, void (*print_fn)(FILE *output, GF_SysPrintArgFlags flags, const char *fmt, ...) ) +{ + GF_CapsBundleStore capstore; + GF_Filter *js_filter; + const char *js_name = NULL; + GF_Err e=GF_OK; + u32 i, j, count, nb_js_caps; + GF_List *sources, *sinks; + GF_FilterRegister loaded_freg; + Bool has_output, has_input; + + js_filter = gf_fs_load_filter(session, filter_name, &e); + if (!js_filter) return; + + js_name = strrchr(filter_name, '/'); + if (!js_name) js_name = strrchr(filter_name, '\\'); + if (js_name) js_name++; + else js_name = filter_name; + + nb_js_caps = gf_filter_caps_bundle_count(js_filter->forced_caps, js_filter->nb_forced_caps); + + //fake a new register with only the caps set + memset(&loaded_freg, 0, sizeof(GF_FilterRegister)); + loaded_freg.caps = js_filter->forced_caps; + loaded_freg.nb_caps = js_filter->nb_forced_caps; + + has_output = gf_filter_has_out_caps(js_filter->forced_caps, js_filter->nb_forced_caps); + has_input = gf_filter_has_in_caps(js_filter->forced_caps, js_filter->nb_forced_caps); + + memset(&capstore, 0, sizeof(GF_CapsBundleStore)); + sources = gf_list_new(); + sinks = gf_list_new(); + //edges for JS are for the unloaded JSF (eg accept anything, output anything). + //we need to do a manual check + count = gf_list_count(session->links); + for (i=0; ilinks, i); + if (a_reg->freg == js_filter->freg) continue; + + //check which cap of this filter matches our destination + nb_src_caps = gf_filter_caps_bundle_count(a_reg->freg->caps, a_reg->freg->nb_caps); + for (k=0; kfreg, k, (const GF_FilterRegister *) &loaded_freg, NULL, &bundle_idx, l, &loaded_filter_only_flags, &capstore); + if (path_weight && (bundle_idx == l)) + src_match = GF_TRUE; + } + if (has_output && !sink_match) { + loaded_filter_only_flags = 0; + path_weight = gf_filter_caps_to_caps_match(&loaded_freg, l, a_reg->freg, NULL, &bundle_idx, k, &loaded_filter_only_flags, &capstore); + + if (path_weight && (bundle_idx == k)) + sink_match = GF_TRUE; + } + } + if (src_match && sink_match) + break; + } + if (src_match) gf_list_add(sources, (void *) a_reg->freg); + if (sink_match) gf_list_add(sinks, (void *) a_reg->freg); + } + + for (i=0; i<2; i++) { + GF_List *from = i ? sinks : sources; + char *type = i ? "sources" : "sinks"; + + count = gf_list_count(from); + if (!count) { + if (print_fn) + print_fn(stderr, 1, "%s: no %s\n", js_name, type); + else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("%s: no %s\n", type)); + } + continue; + } + + if (print_fn) + print_fn(stderr, 1, "%s %s:", js_name, type); + else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("%s %s:", js_name, type)); + } + for (j=0; jname); + else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (" %s", a_reg->name)); + } + } + if (print_fn) + print_fn(stderr, 0, "\n"); + else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\n")); + } + } + + if (capstore.bundles_cap_found) gf_free(capstore.bundles_cap_found); + if (capstore.bundles_in_ok) gf_free(capstore.bundles_in_ok); + if (capstore.bundles_in_scores) gf_free(capstore.bundles_in_scores); + gf_list_del(sources); + gf_list_del(sinks); +} + +GF_EXPORT +void gf_fs_print_all_connections(GF_FilterSession *session, char *filter_name, void (*print_fn)(FILE *output, GF_SysPrintArgFlags flags, const char *fmt, ...) ) +{ + Bool found = GF_FALSE; + GF_List *done; + u32 i, j, count; + u32 llev = gf_log_get_tool_level(GF_LOG_FILTER); + + gf_log_set_tool_level(GF_LOG_FILTER, GF_LOG_INFO); + //load JS to inspect its connections + if (filter_name && strstr(filter_name, ".js")) { + gf_fs_print_jsf_connection(session, filter_name, print_fn); + gf_log_set_tool_level(GF_LOG_FILTER, llev); + return; + } + done = gf_list_new(); + count = gf_list_count(session->links); + + for (i=0; ilinks, i); + if (filter_name && strcmp(src->freg->name, filter_name)) + continue; + + if (!src->nb_edges) { + if (print_fn) + print_fn(stderr, 1, "%s: no sources\n", src->freg->name); + else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("%s: no sources\n", src->freg->name)); + } + continue; + } + found = GF_TRUE; + if (print_fn) + print_fn(stderr, 1, "%s sources:", src->freg->name); + else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("%s sources:", src->freg->name)); + } + + for (j=0; jnb_edges; j++) { + if (gf_list_find(done, (void *) src->edges[j].src_reg->freg->name)<0) { + if (print_fn) + print_fn(stderr, 0, " %s", src->edges[j].src_reg->freg->name); + else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (" %s", src->edges[j].src_reg->freg->name)); + } + gf_list_add(done, (void *) src->edges[j].src_reg->freg->name); + } + } + if (print_fn) + print_fn(stderr, 0, "\n"); + else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("\n")); + } + gf_list_reset(done); + } + + if (found && filter_name) { + if (print_fn) + print_fn(stderr, 1, "%s sinks:", filter_name); + else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("%s sinks:", filter_name)); + } + count = gf_list_count(session->links); + for (i=0; ilinks, i); + if (!strcmp(src->freg->name, filter_name)) continue; + + for (j=0; jnb_edges; j++) { + if (strcmp(src->edges[j].src_reg->freg->name, filter_name)) continue; + + if (gf_list_find(done, (void *) src->freg->name)<0) { + if (print_fn) + print_fn(stderr, 0, " %s", src->freg->name); + else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (" %s", src->freg->name)); + } + gf_list_add(done, (void *) src->freg->name); + } + } + gf_list_reset(done); + } + if (print_fn) + print_fn(stderr, 1, " \n"); + else { + GF_LOG(GF_LOG_INFO, GF_LOG_APP, (" \n")); + } + } + + if (!found && filter_name) { + if (print_fn) + print_fn(stderr, 1, "%s filter not found\n", filter_name); + else { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("%s filter not found\n", filter_name)); + } + } + gf_list_del(done); + gf_log_set_tool_level(GF_LOG_FILTER, llev); +} + + +GF_EXPORT +void gf_filter_get_session_caps(GF_Filter *filter, GF_FilterSessionCaps *caps) +{ + if (caps) { + if (filter) { + (*caps) = filter->session->caps; + } else { + memset(caps, 0, sizeof(GF_FilterSessionCaps)); + } + } +} + +GF_EXPORT +void gf_filter_set_session_caps(GF_Filter *filter, GF_FilterSessionCaps *caps) +{ + if (caps && filter) { + filter->session->caps = (*caps); + //TODO fire event + } +} + +GF_EXPORT +u8 gf_filter_get_sep(GF_Filter *filter, GF_FilterSessionSepType sep_type) +{ + switch (sep_type) { + case GF_FS_SEP_ARGS: return filter->session->sep_args; + case GF_FS_SEP_NAME: return filter->session->sep_name; + case GF_FS_SEP_FRAG: return filter->session->sep_frag; + case GF_FS_SEP_LIST: return filter->session->sep_list; + case GF_FS_SEP_NEG: return filter->session->sep_neg; + default: return 0; + } +} + +GF_EXPORT +GF_DownloadManager *gf_filter_get_download_manager(GF_Filter *filter) +{ + GF_FilterSession *fsess; + if (!filter) return NULL; + fsess = filter->session; + + if (!fsess->download_manager) { + fsess->download_manager = gf_dm_new(fsess); + } + return fsess->download_manager; +} + +GF_EXPORT +struct _gf_ft_mgr *gf_filter_get_font_manager(GF_Filter *filter) +{ +#ifdef GPAC_DISABLE_PLAYER + return NULL; +#else + GF_FilterSession *fsess; + if (!filter) return NULL; + fsess = filter->session; + + if (!fsess->font_manager) { + fsess->font_manager = gf_font_manager_new(); + } + return fsess->font_manager; +#endif +} + +void gf_fs_cleanup_filters(GF_FilterSession *fsess) +{ + assert(fsess->pid_connect_tasks_pending); + safe_int_dec(&fsess->pid_connect_tasks_pending); +} + +GF_EXPORT +GF_Err gf_fs_get_last_connect_error(GF_FilterSession *fs) +{ + GF_Err e; + if (!fs) return GF_BAD_PARAM; + e = fs->last_connect_error; + fs->last_connect_error = GF_OK; + return e; +} + +GF_EXPORT +GF_Err gf_fs_get_last_process_error(GF_FilterSession *fs) +{ + GF_Err e; + if (!fs) return GF_BAD_PARAM; + e = fs->last_process_error; + fs->last_process_error = GF_OK; + return e; +} + +typedef struct +{ + GF_FilterSession *fsess; + void *callback; + Bool (*task_execute) (GF_FilterSession *fsess, void *callback, u32 *reschedule_ms); + Bool (*task_execute_filter) (GF_Filter *filter, void *callback, u32 *reschedule_ms); +#ifndef GPAC_DISABLE_REMOTERY + rmtU32 rmt_hash; +#endif +} GF_UserTask; + +static void gf_fs_user_task(GF_FSTask *task) +{ + u32 reschedule_ms=0; + GF_UserTask *utask = (GF_UserTask *)task->udta; + task->schedule_next_time = 0; + +#ifndef GPAC_DISABLE_REMOTERY + gf_rmt_begin_hash(task->log_name, GF_RMT_AGGREGATE, &utask->rmt_hash); +#endif + if (utask->task_execute) { + task->requeue_request = utask->task_execute(utask->fsess, utask->callback, &reschedule_ms); + } else if (task->filter) { + task->requeue_request = utask->task_execute_filter(task->filter, utask->callback, &reschedule_ms); + } else { + task->requeue_request = 0; + } + gf_rmt_end(); + //if no requeue request or if we are in final flush, don't re-execute + if (!task->requeue_request || utask->fsess->in_final_flush) { + gf_free(utask); + task->udta = NULL; + task->requeue_request = GF_FALSE; + } else { + task->schedule_next_time = gf_sys_clock_high_res() + 1000*reschedule_ms; + } +} + +GF_EXPORT +GF_Err gf_fs_post_user_task(GF_FilterSession *fsess, Bool (*task_execute) (GF_FilterSession *fsess, void *callback, u32 *reschedule_ms), void *udta_callback, const char *log_name) +{ + GF_UserTask *utask; + if (!fsess || !task_execute) return GF_BAD_PARAM; + GF_SAFEALLOC(utask, GF_UserTask); + if (!utask) return GF_OUT_OF_MEM; + utask->fsess = fsess; + utask->callback = udta_callback; + utask->task_execute = task_execute; + gf_fs_post_task(fsess, gf_fs_user_task, NULL, NULL, log_name ? log_name : "user_task", utask); + return GF_OK; +} + +GF_EXPORT +GF_Err gf_filter_post_task(GF_Filter *filter, Bool (*task_execute) (GF_Filter *filter, void *callback, u32 *reschedule_ms), void *udta, const char *task_name) +{ + GF_UserTask *utask; + if (!filter || !task_execute) return GF_BAD_PARAM; + GF_SAFEALLOC(utask, GF_UserTask); + if (!utask) return GF_OUT_OF_MEM; + utask->callback = udta; + utask->task_execute_filter = task_execute; + utask->fsess = filter->session; + gf_fs_post_task(filter->session, gf_fs_user_task, filter, NULL, task_name ? task_name : "user_task", utask); + return GF_OK; +} + + +GF_EXPORT +Bool gf_fs_is_last_task(GF_FilterSession *fsess) +{ + if (!fsess) return GF_TRUE; + if (fsess->tasks_pending>1) return GF_FALSE; + if (gf_fq_count(fsess->main_thread_tasks)) return GF_FALSE; + if (gf_fq_count(fsess->tasks)) return GF_FALSE; + return GF_TRUE; +} + +GF_EXPORT +Bool gf_fs_mime_supported(GF_FilterSession *fsess, const char *mime) +{ + u32 i, count; + //first pass on explicit mimes + count = gf_list_count(fsess->registry); + for (i=0; iregistry, i); + for (j=0; jnb_caps; j++) { + const GF_FilterCapability *acap = &freg->caps[j]; + if (!(acap->flags & GF_CAPFLAG_INPUT)) continue; + if (acap->code == GF_PROP_PID_MIME) { + if (acap->val.value.string && strstr(acap->val.value.string, mime)) return GF_TRUE; + } + } + } + return GF_FALSE; +} + + +GF_EXPORT +void gf_fs_enable_reporting(GF_FilterSession *session, Bool reporting_on) +{ + if (session) session->reporting_on = reporting_on; +} + +GF_EXPORT +void gf_fs_lock_filters(GF_FilterSession *session, Bool do_lock) +{ + if (!session || !session->filters_mx) return; + if (do_lock) gf_mx_p(session->filters_mx); + else gf_mx_v(session->filters_mx); +} + +GF_EXPORT +u32 gf_fs_get_filters_count(GF_FilterSession *session) +{ + return session ? gf_list_count(session->filters) : 0; +} + +GF_EXPORT +GF_Err gf_filter_get_stats(GF_Filter *f, GF_FilterStats *stats) +{ + u32 i; + Bool set_name=GF_FALSE; + if (!stats || !f) return GF_BAD_PARAM; + memset(stats, 0, sizeof(GF_FilterStats)); + stats->filter = f; + stats->filter_alias = f->multi_sink_target; + if (f->multi_sink_target) return GF_OK; + + stats->percent = f->status_percent>10000 ? -1 : (s32) f->status_percent; + stats->status = f->status_str; + stats->nb_pck_processed = f->nb_pck_processed; + stats->nb_bytes_processed = f->nb_bytes_processed; + stats->time_process = f->time_process; + stats->nb_hw_pck_sent = f->nb_hw_pck_sent; + stats->nb_pck_sent = f->nb_pck_sent; + stats->nb_bytes_sent = f->nb_bytes_sent; + stats->nb_tasks_done = f->nb_tasks_done; + stats->nb_errors = f->nb_errors; + stats->name = f->name; + stats->reg_name = f->freg->name; + stats->filter_id = f->id; + stats->done = f->removed || f->finalized; + if (stats->name && !strcmp(stats->name, stats->reg_name)) { + set_name=GF_TRUE; + } + stats->report_updated = f->report_updated; + f->report_updated = GF_FALSE; + + + if (!stats->nb_pid_out && stats->nb_pid_in) stats->type = GF_FS_STATS_FILTER_RAWOUT; + else if (!stats->nb_pid_in && stats->nb_pid_out) stats->type = GF_FS_STATS_FILTER_RAWIN; + + stats->nb_pid_out = f->num_output_pids; + for (i=0; inum_output_pids; i++) { + GF_FilterPid *pid = gf_list_get(f->output_pids, i); + stats->nb_out_pck += pid->nb_pck_sent; + if (pid->has_seen_eos) stats->in_eos = GF_TRUE; + + if (pid->last_ts_sent.num * stats->last_ts_sent.den >= stats->last_ts_sent.num * pid->last_ts_sent.den) + stats->last_ts_sent = pid->last_ts_sent; + + if (f->num_output_pids!=1) continue; + + if (!stats->codecid) + stats->codecid = pid->codecid; + if (!stats->stream_type) + stats->stream_type = pid->stream_type; + + //set name if PID name is not a default generated one + if (set_name && strncmp(pid->name, "PID", 3)) { + stats->name = pid->name; + set_name = GF_FALSE; + } + } + stats->nb_pid_in = f->num_input_pids; + for (i=0; inum_input_pids; i++) { + GF_FilterPidInst *pidi = gf_list_get(f->input_pids, i); + stats->nb_in_pck += pidi->nb_processed; + if (pidi->is_end_of_stream) stats->in_eos = GF_TRUE; + + if (pidi->is_decoder_input) stats->type = GF_FS_STATS_FILTER_DECODE; + else if (pidi->is_encoder_input) stats->type = GF_FS_STATS_FILTER_ENCODE; + + if (pidi->pid->stream_type==GF_STREAM_FILE) + stats->type = GF_FS_STATS_FILTER_DEMUX; + + if (pidi->last_ts_drop.num * stats->last_ts_drop.den >= stats->last_ts_drop.num * pidi->last_ts_drop.den) + stats->last_ts_drop = pidi->last_ts_drop; + + if ((f->num_input_pids!=1) && f->num_output_pids) + continue; + + if (!stats->codecid) + stats->codecid = pidi->pid->codecid; + if (!stats->stream_type) + stats->stream_type = pidi->pid->stream_type; + + if (set_name) { + stats->name = pidi->pid->name; + set_name = GF_FALSE; + } + } + if (!stats->type && stats->codecid) { + if (!stats->nb_pid_out) { + stats->type = GF_FS_STATS_FILTER_MEDIA_SINK; + } else if (!stats->nb_pid_in) { + stats->type = GF_FS_STATS_FILTER_MEDIA_SOURCE; + } + } + return GF_OK; +} + +GF_EXPORT +GF_Err gf_fs_get_filter_stats(GF_FilterSession *session, u32 idx, GF_FilterStats *stats) +{ + if (!stats || !session) return GF_BAD_PARAM; + return gf_filter_get_stats(gf_list_get(session->filters, idx), stats); +} + +Bool gf_fs_ui_event(GF_FilterSession *session, GF_Event *uievt) +{ + Bool ret; + gf_mx_p(session->ui_mx); + ret = session->ui_event_proc(session->ui_opaque, uievt); + gf_mx_v(session->ui_mx); + return ret; +} + +void gf_fs_check_graph_load(GF_FilterSession *fsess, Bool for_load) +{ + if (for_load) { + if (!fsess->links || ! gf_list_count( fsess->links)) + gf_filter_sess_build_graph(fsess, NULL); + } else { + if (fsess->flags & GF_FS_FLAG_NO_GRAPH_CACHE) + gf_filter_sess_reset_graph(fsess, NULL); + } +} + +#ifndef GPAC_DISABLE_3D + + +#define GF_VIDEO_HW_INTERNAL (1<<29) +#define GF_VIDEO_HW_ATTACHED (1<<30) + +static Bool fsess_on_event(void *cbk, GF_Event *evt) +{ + return GF_TRUE; +} + +GF_Err gf_fs_check_gl_provider(GF_FilterSession *session) +{ + GF_Event evt; + GF_Err e; + const char *sOpt; + void *os_disp_handler; + + if (!session->nb_gl_filters) return GF_OK; + if (gf_list_count(session->gl_providers)) return GF_OK; + + if (session->gl_driver) return GF_OK; + + + session->gl_driver = (GF_VideoOutput *) gf_module_load(GF_VIDEO_OUTPUT_INTERFACE, gf_opts_get_key("core", "video-output") ); + + if (!session->gl_driver) { + GF_LOG(GF_LOG_ERROR, GF_LOG_FILTER, ("Failed to load a video output for OpenGL context support !\n")); + return GF_IO_ERR; + } + if (!gf_opts_get_key("core", "video-output")) { + gf_opts_set_key("core", "video-output", session->gl_driver->module_name); + } + session->gl_driver->hw_caps |= GF_VIDEO_HW_INTERNAL; + session->gl_driver->on_event = fsess_on_event; + session->gl_driver->evt_cbk_hdl = session; + + os_disp_handler = NULL; + sOpt = gf_opts_get_key("Temp", "OSDisp"); + if (sOpt) sscanf(sOpt, "%p", &os_disp_handler); + + e = session->gl_driver->Setup(session->gl_driver, NULL, os_disp_handler, GF_TERM_INIT_HIDE); + if (e!=GF_OK) { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("Failed to setup Video Driver %s!\n", session->gl_driver->module_name)); + gf_modules_close_interface((GF_BaseInterface *)session->gl_driver); + session->gl_driver = NULL; + return e; + } + + //and initialize GL context + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_VIDEO_SETUP; + evt.setup.width = 128; + evt.setup.height = 128; + evt.setup.use_opengl = GF_TRUE; + evt.setup.back_buffer = 1; + //we anyway should'nt call swapBuffer/flush on this object + evt.setup.disable_vsync = GF_TRUE; + session->gl_driver->ProcessEvent(session->gl_driver, &evt); + + if (evt.setup.use_opengl) { + gf_opengl_init(); + } + return GF_OK; +} + +GF_Err gf_fs_set_gl(GF_FilterSession *session) +{ + GF_Event evt; + if (!session->gl_driver) return GF_BAD_PARAM; + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_SET_GL; + return session->gl_driver->ProcessEvent(session->gl_driver, &evt); +} + +GF_VideoOutput *gf_filter_claim_opengl_provider(GF_Filter *filter) +{ + if (!filter || !filter->session || !filter->session->gl_driver) return NULL; + + if (! (filter->session->gl_driver->hw_caps & GF_VIDEO_HW_INTERNAL)) + return NULL; + if (filter->session->gl_driver->hw_caps & GF_VIDEO_HW_ATTACHED) + return NULL; + + filter->session->gl_driver->hw_caps |= GF_VIDEO_HW_ATTACHED; + return filter->session->gl_driver; +} + +Bool gf_filter_unclaim_opengl_provider(GF_Filter *filter, GF_VideoOutput * video_out) +{ + if (!filter || !video_out) return GF_FALSE; + + if (! (video_out->hw_caps & GF_VIDEO_HW_INTERNAL)) + return GF_FALSE; + if (video_out != filter->session->gl_driver) + return GF_FALSE; + + if (filter->session->gl_driver->hw_caps & GF_VIDEO_HW_ATTACHED) { + filter->session->gl_driver->hw_caps &= ~GF_VIDEO_HW_ATTACHED; + filter->session->gl_driver->on_event = fsess_on_event; + filter->session->gl_driver->evt_cbk_hdl = filter->session; + return GF_TRUE; + } + return GF_FALSE; +} + +#else +void *gf_filter_claim_opengl_provider(GF_Filter *filter) +{ + return NULL; +} +Bool gf_filter_unclaim_opengl_provider(GF_Filter *filter, void *vout) +{ + return GF_FALSE; +} + +#endif + + +#ifdef FILTER_FIXME + + +static Bool term_find_res(GF_TermLocales *loc, char *parent, char *path, char *relocated_path, char *localized_rel_path) +{ + FILE *f; + + if (loc->szAbsRelocatedPath) gf_free(loc->szAbsRelocatedPath); + loc->szAbsRelocatedPath = gf_url_concatenate(parent, path); + if (!loc->szAbsRelocatedPath) loc->szAbsRelocatedPath = gf_strdup(path); + + f = gf_fopen(loc->szAbsRelocatedPath, "rb"); + if (f) { + gf_fclose(f); + strcpy(localized_rel_path, path); + strcpy(relocated_path, loc->szAbsRelocatedPath); + return 1; + } + return 0; +} + +/* Checks if, for a given relative path, there exists a localized version in an given folder + if this is the case, it returns the absolute localized path, otherwise it returns null. + if the resource was localized, the last parameter is set to the localized relative path. +*/ +static Bool term_check_locales(void *__self, const char *locales_parent_path, const char *rel_path, char *relocated_path, char *localized_rel_path) +{ + char path[GF_MAX_PATH]; + const char *opt; + + GF_TermLocales *loc = (GF_TermLocales*)__self; + + /* Checks if the rel_path argument really contains a relative path (no ':', no '/' at the beginning) */ + if (strstr(rel_path, "://") || (rel_path[0]=='/') || strstr(rel_path, ":\\") || !strncmp(rel_path, "\\\\", 2)) { + return 0; + } + + /*Checks if the absolute path is really absolute and points to a local file (no http or else) */ + if (!locales_parent_path || + (locales_parent_path && (locales_parent_path[0] != '/') && strstr(locales_parent_path, "://") && strnicmp(locales_parent_path, "file://", 7))) { + return 0; + } + opt = gf_opts_get_key("core", "lang"); + if (opt && (!strcmp(opt, "*") || !strcmp(opt, "un") ) ) { + opt = NULL; + } + + while (opt) { + char lan[100]; + char *sep; + char *sep_lang = strchr(opt, ';'); + if (sep_lang) sep_lang[0] = 0; + + while (strchr(" \t", opt[0])) + opt++; + + strcpy(lan, opt); + + if (sep_lang) { + sep_lang[0] = ';'; + opt = sep_lang+1; + } else { + opt = NULL; + } + + while (1) { + sep = strstr(lan, "-*"); + if (!sep) break; + strncpy(sep, sep+2, strlen(sep)-2); + } + + sprintf(path, "locales/%s/%s", lan, rel_path); + if (term_find_res(loc, (char *) locales_parent_path, (char *) path, relocated_path, localized_rel_path)) + return 1; + + /*recursively remove region (sub)tags*/ + while (1) { + sep = strrchr(lan, '-'); + if (!sep) break; + sep[0] = 0; + sprintf(path, "locales/%s/%s", lan, rel_path); + if (term_find_res(loc, (char *) locales_parent_path, (char *) path, relocated_path, localized_rel_path)) + return 1; + } + } + + if (term_find_res(loc, (char *) locales_parent_path, (char *) rel_path, relocated_path, localized_rel_path)) + return 1; + /* if we did not find the localized file, both the relocated and localized strings are NULL */ + strcpy(localized_rel_path, ""); + strcpy(relocated_path, ""); + return 0; +} + +#endif + diff --git a/src/filter_core/filter_session.h b/src/filter_core/filter_session.h new file mode 100644 index 0000000..7cb9219 --- /dev/null +++ b/src/filter_core/filter_session.h @@ -0,0 +1,1016 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2018 + * All rights reserved + * + * This file is part of GPAC / filters sub-project + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terfsess of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _GF_FILTER_SESSION_H_ +#define _GF_FILTER_SESSION_H_ + +#include +#include +#include +#include + +#ifdef GPAC_HAS_QJS +#include "../scenegraph/qjs_common.h" +#endif + + +#define GF_FILTER_SPEED_SCALER 1000 + +#define PID_IS_INPUT(__pid) ((__pid->pid==__pid) ? GF_FALSE : GF_TRUE) +#define PID_IS_OUTPUT(__pid) ((__pid->pid==__pid) ? GF_TRUE : GF_FALSE) +#define PCK_IS_INPUT(__pck) ((__pck->pck==__pck) ? GF_FALSE : GF_TRUE) +#define PCK_IS_OUTPUT(__pck) ((__pck->pck==__pck) ? GF_TRUE : GF_FALSE) + +#define FSESS_CHECK_THREAD(__f) assert( !(__f)->process_th_id || ( (__f)->process_th_id == gf_th_id() ) ); + +struct __gf_prop_entry +{ + //parent filter session for property reservoir + GF_FilterSession *session; + volatile u32 reference_count; + u32 p4cc; + Bool name_alloc; + char *pname; + + GF_PropertyValue prop; + u32 alloc_size; +}; + +#ifndef GF_PROPS_HASHTABLE_SIZE +#define GF_PROPS_HASHTABLE_SIZE 0 +#endif + +void gf_propmap_del(void *pmap); + +typedef struct +{ +#if GF_PROPS_HASHTABLE_SIZE + GF_List *hash_table[GF_PROPS_HASHTABLE_SIZE]; +#else + GF_List *properties; +#endif + volatile u32 reference_count; + //number of references hold by packet references - since these may be destroyed at the end of the refering filter + //the pid might be dead. This is only used for pid props maps + volatile u32 pckrefs_reference_count; + GF_FilterSession *session; + //current timescale, cached for duration/buffer compute + u32 timescale; +} GF_PropertyMap; + +GF_PropertyMap * gf_props_new(GF_Filter *filter); +void gf_props_del(GF_PropertyMap *prop); +void gf_props_reset(GF_PropertyMap *prop); + +GF_Err gf_props_set_property(GF_PropertyMap *map, u32 p4cc, const char *name, char *dyn_name, const GF_PropertyValue *value); +GF_Err gf_props_insert_property(GF_PropertyMap *map, u32 hash, u32 p4cc, const char *name, char *dyn_name, const GF_PropertyValue *value); + +void gf_props_remove_property(GF_PropertyMap *map, u32 hash, u32 p4cc, const char *name); + +const GF_PropertyValue *gf_props_get_property(GF_PropertyMap *map, u32 prop_4cc, const char *name); + +const GF_PropertyEntry *gf_props_get_property_entry(GF_PropertyMap *map, u32 prop_4cc, const char *name); + +#if GF_PROPS_HASHTABLE_SIZE +u32 gf_props_hash_djb2(u32 p4cc, const char *str); +#else +#define gf_props_hash_djb2(_a, _b) 0 +#endif + +GF_Err gf_props_merge_property(GF_PropertyMap *dst_props, GF_PropertyMap *src_props, gf_filter_prop_filter filter_prop, void *cbk); + +const GF_PropertyValue *gf_props_enum_property(GF_PropertyMap *props, u32 *io_idx, u32 *prop_4cc, const char **prop_name); + +Bool gf_props_4cc_check_props(); + +void gf_props_del_property(GF_PropertyEntry *it); + + +typedef struct __gf_filter_queue GF_FilterQueue; +//constructs a new fifo queue. If mx is set all pop/add/head operations are protected by the mutex +//otherwise, a lock-free version of the fifo is used +GF_FilterQueue *gf_fq_new(const GF_Mutex *mx); +void gf_fq_del(GF_FilterQueue *fq, void (*item_delete)(void *) ); +void gf_fq_add(GF_FilterQueue *fq, void *item); +void *gf_fq_pop(GF_FilterQueue *fq); +void *gf_fq_head(GF_FilterQueue *fq); +u32 gf_fq_count(GF_FilterQueue *fq); +void *gf_fq_get(GF_FilterQueue *fq, u32 idx); +void gf_fq_enum(GF_FilterQueue *fq, Bool (*enum_func)(void *udta1, void *item), void *udta); + + +typedef void (*gf_destruct_fun)(void *cbck); + +//calls gf_free on p, used for LFQ / lists destructors +void gf_void_del(void *p); + +typedef struct __gf_filter_pid_inst GF_FilterPidInst; + +typedef struct __gf_filter_pck_inst +{ + struct __gf_filter_pck *pck; //source packet + GF_FilterPidInst *pid; + u8 pid_props_change_done; + u8 pid_info_change_done; + + //DO NOT EXTEND UNLESS UPDATING CODE IN gf_filter_pck_send() +} GF_FilterPacketInstance; + + +//packet flags +enum +{ + GF_PCKF_BLOCK_START = 0x80000000, //1<<31 + GF_PCKF_BLOCK_END = 1<<30, + GF_PCKF_PROPS_CHANGED = 1<<29, + GF_PCKF_INFO_CHANGED = 1<<28, + GF_PCKF_CORRUPTED = 1<<27, + GF_PCKF_SEEK = 1<<26, + GF_PCKF_DUR_SET = 1<<25, + GF_PCKF_PROPS_REFERENCE = 1<<24, + //3 bits for SAP + GF_PCK_SAP_POS = 21, + GF_PCK_SAP_MASK = 0x7 << GF_PCK_SAP_POS, + //2 bits for interlaced + GF_PCK_ILACE_POS = 19, + GF_PCK_ILACE_MASK = 0x3 << GF_PCK_ILACE_POS, + //2 bits for clock type + GF_PCK_CKTYPE_POS = 17, + GF_PCK_CKTYPE_MASK = 0x3 << GF_PCK_CKTYPE_POS, + //2 bits for crypt type + GF_PCK_CRYPT_POS = 15, + GF_PCK_CRYPT_MASK = 0x3 << GF_PCK_CRYPT_POS, + //2 bits for crypt type + GF_PCK_CMD_POS = 13, + GF_PCK_CMD_MASK = 0x3 << GF_PCK_CMD_POS, + //RESERVED bits [9,12] + //2 bits for is_leading + GF_PCK_ISLEADING_POS = 6, + GF_PCK_ISLEADING_MASK = 0x3 << GF_PCK_ISLEADING_POS, + //2 bits for depends_on + GF_PCK_DEPENDS_ON_POS = 4, + GF_PCK_DEPENDS_ON_MASK = 0x3 << GF_PCK_DEPENDS_ON_POS, + //2 bits for depended_on + GF_PCK_DEPENDED_ON_POS = 2, + GF_PCK_DEPENDED_ON_MASK = 0x3 << GF_PCK_DEPENDED_ON_POS, + //2 bits for redundant + GF_PCK_REDUNDANT_MASK = 0x3, + + //quick defs + GF_PCK_CMD_NONE = 0<timescale units + u64 dts, cts; + u32 duration; + + u64 byte_offset; + u32 seq_num; + s16 roll; + u8 carousel_version_number; + +} GF_FilterPckInfo; + +struct __gf_filter_pck +{ + struct __gf_filter_pck *pck; //this object + GF_FilterPid *pid; + //filter emiting the packet - this is only needed to handle destruction of packets never dispatched + //by the filter upon destruction. Set to NULL once the packet is dispatched + GF_Filter *src_filter; + + //parent session, used to collect packet props references + GF_FilterSession *session; + + //nb references of this packet + volatile u32 reference_count; + + GF_FilterPckInfo info; + + char *data; + u32 data_length; + + //for allocated memory packets + u32 alloc_size; + //for shared memory packets: 0: cloned mem, 1: read/write mem from source filter, 2: read-only mem from filter + //note that packets with frame_ifce are always considered as read-only memory + u32 filter_owns_mem; + gf_fsess_packet_destructor destructor; + //for packet reference packets (sharing data from other packets) + struct __gf_filter_pck *reference; + + GF_FilterFrameInterface *frame_ifce; + + // properties applying to this packet + GF_PropertyMap *props; + //pid properties applying to this packet + GF_PropertyMap *pid_props; +}; + +/*! + * Filter Session Task process function prototype + */ +typedef void (*gf_fs_task_callback)(GF_FSTask *task); + +struct __gf_fs_task +{ + //flag set for tasks registered with main task list, eg having incremented the task_pending counter. + //some tasks may not increment that counter (eg when requeued to a filer), so this simplifies + //decrementing the counter + Bool notified; + Bool requeue_request; + Bool can_swap; + Bool blocking; + + u64 schedule_next_time; + + + gf_fs_task_callback run_task; + GF_Filter *filter; + GF_FilterPid *pid; + const char *log_name; + void *udta; +}; + +void gf_fs_post_task(GF_FilterSession *fsess, gf_fs_task_callback fun, GF_Filter *filter, GF_FilterPid *pid, const char *log_name, void *udta); +void gf_fs_post_task_ex(GF_FilterSession *fsess, gf_fs_task_callback task_fun, GF_Filter *filter, GF_FilterPid *pid, const char *log_name, void *udta, Bool requires_main_thread, Bool force_direct_call); + +void gf_filter_pid_send_event_downstream(GF_FSTask *task); + + +typedef struct __gf_fs_thread +{ + //NULL for main thread + GF_Thread *th; + struct __gf_filter_session *fsess; + u32 th_id; + + Bool has_seen_eot; //set when no more tasks in global queue + + u64 nb_tasks; + u64 run_time; + u64 active_time; + +#ifndef GPAC_DISABLE_REMOTERY + u32 rmt_tasks; + char rmt_name[20]; +#endif + +} GF_SessionThread; + +typedef struct +{ + char *argname; + u32 type; + Bool found; +} GF_FSArgItem; + +void gf_fs_push_arg(GF_FilterSession *session, const char *szArg, Bool was_found, u32 type); + +enum +{ + GF_FS_BLOCK_ALL=0, + GF_FS_NOBLOCK_FANOUT, + GF_FS_NOBLOCK +}; + +struct __gf_filter_session +{ + u32 flags; + Bool use_locks; + Bool direct_mode; + volatile u32 tasks_in_process; + Bool requires_solved_graph; + Bool no_main_thread; + + GF_List *registry; + GF_List *filters; + + GF_FilterQueue *tasks; + GF_FilterQueue *main_thread_tasks; + GF_FilterQueue *tasks_reservoir; + volatile Bool in_main_sem_wait; + volatile u32 active_threads; + + //if more than one thread, this mutex protects access to loaded filters list, to avoid concurrent calls to destruct and + //filter testing (graph resolution, update sending, non thread-safe graph traversal...) + GF_Mutex *filters_mx; + + //reservoir for property maps for PID and packets properties + GF_FilterQueue *prop_maps_reservoir; + //reservoir for property entries - properties may be inherited between packets + GF_FilterQueue *prop_maps_entry_reservoir; + //reservoir for property entries with allocated data buffers - properties may be inherited between packets + GF_FilterQueue *prop_maps_entry_data_alloc_reservoir; +#if GF_PROPS_HASHTABLE_SIZE + //reservoir for property maps hash table entries (GF_Lists) for PID and packets properties + GF_FilterQueue *prop_maps_list_reservoir; +#endif + //reservoir for reference property packets - we mutualize at session level to collect them + //it is not possible to do so at filter or pid level because a prop ref packet may be destroyed after the source + //pid/packet is destroyed, and we don't want to track them per pid/filter + GF_FilterQueue *pcks_refprops_reservoir; + + + GF_Mutex *props_mx; + + GF_Mutex *info_mx; + + GF_Mutex *ui_mx; + + GF_List *threads; + GF_SessionThread main_th; + + //only used in forced lock mode + GF_Mutex *tasks_mx; + + //we duplicate the semaphores for the main and other task list. This avoids having threads grabing tasks + //posted for main thread, and re-notifying the sema which could then be grabbed by another thread + //than the main, hence producing too many wake-ups + //semaphore for tasks posted in main task list + GF_Semaphore *semaphore_main; + //semaphore for tasks posted in other task list + GF_Semaphore *semaphore_other; + + volatile u32 tasks_pending; + + u32 nb_threads_stopped; + GF_Err run_status; + u32 blocking_mode; + Bool in_final_flush; + + Bool reporting_on; + /*user defined callback for all functions - cannot be NULL*/ + void *ui_opaque; + /*the event proc. Return value depend on the event type, usually 0 + cannot be NULL if os_window_handler is specified and dont_override_window_proc is set + may be NULL otherwise*/ + Bool (*ui_event_proc)(void *opaque, GF_Event *event); + + volatile u32 pid_connect_tasks_pending; + + GF_List *event_listeners; + GF_Mutex *evt_mx; + u32 in_event_listener; + + GF_DownloadManager *download_manager; + struct _gf_ft_mgr *font_manager; + + u32 default_pid_buffer_max_us, decoder_pid_buffer_max_us; + u32 default_pid_buffer_max_units; + +#ifdef GPAC_MEMORY_TRACKING + Bool check_allocs; + u32 nb_alloc_pck, nb_realloc_pck; +#endif + GF_Err last_connect_error, last_process_error; + + GF_FilterSessionCaps caps; + + u64 hint_clock_us; + GF_Fraction64 hint_timestamp; + + //max filter chain allowed in the link resolution process + u32 max_resolve_chain_len; + //max sleep time + u32 max_sleep; + + //protect access to link bank + GF_Mutex *links_mx; + GF_List *links; + + + GF_List *parsed_args; + + char sep_args, sep_name, sep_frag, sep_list, sep_neg; + const char *blacklist; + Bool init_done; + + GF_List *auto_inc_nums; +#ifndef GPAC_DISABLE_3D + GF_List *gl_providers; + volatile u32 nb_gl_filters; +#endif + //internal video output to hidden window for GL context + struct _video_out *gl_driver; + +#ifdef GPAC_HAS_QJS + struct JSContext *js_ctx; + GF_List *jstasks; + struct __jsfs_task *new_f_task, *del_f_task, *on_evt_task; +#endif +}; + +#ifdef GPAC_HAS_QJS +void jsfs_on_filter_created(GF_Filter *new_filter); +void jsfs_on_filter_destroyed(GF_Filter *del_filter); +Bool jsfs_on_event(GF_FilterSession *session, GF_Event *evt); +#endif + +void gf_fs_reg_all(GF_FilterSession *fsess, GF_FilterSession *a_sess); + +typedef struct +{ + u32 crc; + s32 inc_val; + GF_Filter *filter; +} GF_FSAutoIncNum; + +#ifndef GPAC_DISABLE_3D +GF_Err gf_fs_check_gl_provider(GF_FilterSession *session); +GF_Err gf_fs_set_gl(GF_FilterSession *session); +#endif + +typedef enum +{ + GF_FILTER_ARG_EXPLICIT = 0, + GF_FILTER_ARG_INHERIT, + GF_FILTER_ARG_INHERIT_SOURCE_ONLY, + GF_FILTER_ARG_EXPLICIT_SOURCE, + GF_FILTER_ARG_EXPLICIT_SOURCE_NO_DST_INHERIT, + GF_FILTER_ARG_EXPLICIT_SINK, +} GF_FilterArgType; + +struct __gf_filter +{ + const GF_FilterRegister *freg; + + //name of the filter - may be overiden by the filter instance + char *name; + + //ID of the filter to setup connections + char *id; + //list of IDs of filters usable as sources for this filter. NULL means any source + char *source_ids; + //original source IDs in case we use dynamic clone. If present, the source_ids contains resolved identifiers (eg ServiceID=234) + //but dynamic_source_ids still contains the unresolved pattern (eg ServiceID=*) + char *dynamic_source_ids; + + //parent media session + GF_FilterSession *session; + + //indicates the max number of additional input PIDs - muxers and scalable filters typically set this to (u32) -1 + u32 max_extra_pids; + + void (*on_setup_error)(GF_Filter *f, void *on_setup_error_udta, GF_Err e); + void *on_setup_error_udta; + GF_Filter *on_setup_error_filter; + + //internal copy of argument string + char *orig_args; + //remember how the args were set, either by explicit loading of filters or during filter chain + GF_FilterArgType arg_type; + //allocated pointer to the argument string for source filters + char *src_args; + //allocated argument string of the destingation filter for filters dynamically loaded, if any + char *dst_args; + + //tasks pending for this filter. The first task in this list is also present in the filter session + //task list in order to avoid locking the main task list with a mutex + GF_FilterQueue *tasks; + //set to true when the filter is present or to be added in the main task list + //this variable is unset in a zone protected by task_mx + volatile Bool scheduled_for_next_task; + //set to true when the filter is being processed by a thread + volatile Bool in_process; + u32 process_th_id; + //user data for the filter implementation + void *filter_udta; + + Bool has_out_caps; + + Bool disabled; + //set to true before calling filter process() callback, and reset to false right after + Bool in_process_callback; + Bool no_probe; + Bool no_inputs; + + s32 nb_pids_playing; + + //list of pids connected to this filter + GF_List *input_pids; + u32 num_input_pids; + //list of pids created by this filter + GF_List *output_pids; + u32 num_output_pids; + u32 num_out_pids_not_connected; + + //reservoir for packets with allocated memory + GF_FilterQueue *pcks_alloc_reservoir; + //reservoir for packets with shared memory + GF_FilterQueue *pcks_shared_reservoir; + //reservoir for packets instances - the ones stored in the pid destination(s) with shared memory + GF_FilterQueue *pcks_inst_reservoir; + + GF_Mutex *pcks_mx; + + //!this mutex protects: + //- the filter task queue, when reordering tasks for later processing while other threads try to post to the filter task queue + //- the list of input pid and output pid destinations, which can be added from different threads for a same pid (fan-out) + //-the blocking state of the filter + GF_Mutex *tasks_mx; + + //list of output pids to be configured + Bool has_pending_pids; + GF_FilterQueue *pending_pids; + + volatile u32 in_pid_connection_pending; + volatile u32 out_pid_connection_pending; + volatile u32 pending_packets; + + volatile u32 stream_reset_pending; + volatile u32 num_events_queued; + volatile u32 detach_pid_tasks_pending; + volatile u32 nb_shared_packets_out; + GF_List *postponed_packets; + + //list of blacklisted filtered registries + GF_List *blacklisted; + + //pointer to the original filter being cloned - only used for graph setup, reset after + GF_Filter *cloned_from; + + //pointer to the current destination filter to connect to next in the chain - only used for graph setup, reset after + GF_Filter *dst_filter; + //pointer to the target filter to connect to + GF_Filter *target_filter; + + //list of direct destination filters for this filter during graph resolution + GF_List *destination_filters; + //list of potential destination for which we need to perform link resolution + GF_List *destination_links; + + //statistics + //number of tasks executed by this filter + u64 nb_tasks_done; + //number of packets processed by this filter + u64 nb_pck_processed; + //number of bytes processed by this filter + u64 nb_bytes_processed; + //number of packets sent by this filter + u64 nb_pck_sent; + //number of hardware frames packets sent by this filter + u64 nb_hw_pck_sent; + //number of processing errors in the lifetime of the filter + u32 nb_errors; + + //number of bytes sent by this filter + u64 nb_bytes_sent; + //number of microseconds this filter was active + u64 time_process; + +#ifdef GPAC_MEMORY_TRACKING + //various stats in mem tracking mode, mostly used to detect heavy alloc/free usage by the filter + u64 stats_mem_allocated; + u32 stats_nb_alloc, stats_nb_realloc, stats_nb_calloc, stats_nb_free; + u32 nb_process_since_reset, nb_consecutive_process; + u32 max_stats_nb_alloc, max_stats_nb_realloc, max_stats_nb_calloc, max_stats_nb_free; + u32 max_nb_consecutive_process, max_nb_process; +#endif + + volatile u32 would_block; //concurrent inc/dec + //sets once broken blocking mode has been detected + Bool blockmode_broken; + //requested by a filter to disable blocking + Bool prevent_blocking; + + //filter destroy task has been posted + Bool finalized; + //filter is scheduled for removal: any filter connected to this filter will + //not be checked for graph resolution - destroy has not yet been posted + Bool removed; + //setup has been notified + Bool setup_notified; + //filter loaded to solve a filter chain + Bool dynamic_filter; + //filter block EOS queries + Bool block_eos; + //set when one input pid of the filter has been marked for removal through gf_filter_remove_src + //this prevents dispatching pid_remove as packets that would no longer be consumed + Bool marked_for_removal; + //sticky filters won't unload if all inputs are deconnected. Useful for sink filters + //2 means temporary sticky, used when reconfiguring filter chain + u32 sticky; + //explicitly loaded filters are usually not cloned, except if this flag is set + Bool clonable; + //one of the output PID needs reconfiguration + volatile u32 nb_caps_renegociate; + + //number of process tasks queued. There is only one "process" task allocated for the filter, but it is automatically reposted based on this value + volatile u32 process_task_queued; + + //time in system clock at which the process should be called, used for real-time regulation of some filters + u64 schedule_next_time; + + //clock (PCR) dispatch info + u64 next_clock_dispatch; + u32 next_clock_dispatch_timescale; + GF_FilterClockType next_clock_dispatch_type; + + GF_Filter *cap_dst_filter; + //capability negotiation for the input pid + GF_PropertyMap *caps_negociate; + //set to true of this filter was instantiated to resolve a capability negotiation between two filters + Bool is_pid_adaptation_filter; + /*destination pid instance we are swapping*/ + GF_FilterPidInst *swap_pidinst_dst; + /*source pid instance we are swapping*/ + GF_FilterPidInst *swap_pidinst_src; + Bool swap_needs_init; + + //overloaded caps of the filter + const GF_FilterCapability *forced_caps; + u32 nb_forced_caps; + //valid when a pid inst is waiting for a reconnection, NULL otherwise + GF_List *detached_pid_inst; + + //index of the bundle input for dynamic filters + s32 bundle_idx_at_resolution; + //index of the cap bundle input for adaptation filters + s32 cap_idx_at_resolution; + + //set to signal output pids should be reconfigured + Bool reconfigure_outputs; + //when set, indicates the filter uses PID property overwrite in its arguments, needed to rewrite the props at pid init time + Bool user_pid_props; + + //for encoder filters, set to the corresponding stream type - used to discard filters during the resolution + u32 encoder_stream_type; + +#ifndef GPAC_DISABLE_REMOTERY + rmtU32 rmt_hash; +#endif + + //signals tha pid info has changed, to notify the filter chain + Bool pid_info_changed; + + //set to 1 when one or more input pid to the filter is on end of state, set to 2 if the filter dispatch a packet while in this state + u32 eos_probe_state; + + //error checking + //number of packet release or created during a process() call + u32 nb_pck_io; + //number of consecutive errors during process() + u32 nb_consecutive_errors; + //system clock of first error + u64 time_at_first_error; + + GF_Err in_connect_err; + + Bool main_thread_forced; + Bool no_dst_arg_inherit; + GF_List *source_filters; + + char *status_str; + u32 status_str_alloc, status_percent; + Bool report_updated; + + char *instance_description, *instance_version, *instance_author, *instance_help; + GF_FilterArgs *instance_args; + + GF_Filter *multi_sink_target; + + Bool event_target; + +#ifdef GPAC_HAS_QJS + char *iname; + JSValue jsval; +#endif +}; + +GF_Filter *gf_filter_new(GF_FilterSession *fsess, const GF_FilterRegister *freg, const char *args, const char *dst_args, GF_FilterArgType arg_type, GF_Err *err, GF_Filter *multi_sink_target, Bool dynamic_filter); +GF_Filter *gf_filter_clone(GF_Filter *filter); +void gf_filter_del(GF_Filter *filter); + +Bool gf_filter_swap_source_register(GF_Filter *filter); + +GF_Err gf_filter_new_finalize(GF_Filter *filter, const char *args, GF_FilterArgType arg_type); + +GF_Filter *gf_fs_load_source_dest_internal(GF_FilterSession *fsess, const char *url, const char *args, const char *parent_url, GF_Err *err, GF_Filter *filter, GF_Filter *dst_filter, Bool for_source, Bool no_args_inherit, Bool *probe_only); + +void gf_filter_pid_inst_delete_task(GF_FSTask *task); + +void gf_filter_pid_inst_reset(GF_FilterPidInst *pidinst); +void gf_filter_pid_inst_del(GF_FilterPidInst *pidinst); + +void gf_filter_forward_clock(GF_Filter *filter); + +void gf_filter_process_inline(GF_Filter *filter); + +void gf_filter_pid_retry_caps_negotiate(GF_FilterPid *src_pid, GF_FilterPid *pid, GF_Filter *dst_filter); + +void gf_filter_reset_pending_packets(GF_Filter *filter); + +typedef struct +{ + char *name; + char *val; + //0: only on filter, 1: forward downstream, 2: forward upstream + GF_EventPropagateType recursive; +} GF_FilterUpdate; + +//structure for input pids, in order to handle fan-outs of a pid into several filters +struct __gf_filter_pid_inst +{ + //first two fields are the same as in GF_FilterPid for typecast + struct __gf_filter_pid *pid; // source pid + GF_Filter *filter; + + //current properties for the pid - we cannot use pid->properties since there could be + //multiple consumers running at different speed + GF_PropertyMap *props; + + GF_FilterQueue *packets; + + GF_Mutex *pck_mx; + + GF_List *pck_reassembly; + Bool requires_full_data_block; + Bool last_block_ended; + Bool first_block_started; + //set during play/stop/reset phases + Bool discard_packets; + + Bool force_reconfig; + + //set by filter + Bool discard_inputs; + + //amount of media data in us in the packet queue - concurrent inc/dec + volatile s64 buffer_duration; + + volatile s32 detach_pending; + + void *udta; + + //statistics per pid instance + u64 last_pck_fetch_time; + u64 stats_start_ts, stats_start_us; + u32 cur_bit_size, avg_bit_rate, max_bit_rate, avg_process_rate, max_process_rate; + u32 nb_processed, nb_sap_processed; + //all times in us + u64 total_process_time, total_sap_process_time; + u64 max_process_time, max_sap_process_time; + u64 first_frame_time; + Bool is_end_of_stream; + volatile u32 nb_eos_signaled; + + Bool is_encoder_input; + Bool is_decoder_input; + GF_PropertyMap *reconfig_pid_props; + + //clock handling by the consumer: the clock values are not automatically dispatched to the output pids and are kept + //available as regular packets in the input pid + Bool handles_clock_references; + //if clocks are handled internally: + //number of clock packets present + volatile u32 nb_clocks_signaled; + //last clock value found + u64 last_clock_value; + //last clock value's timescale found + u32 last_clock_timescale; + //last clock type found + GF_FilterClockType last_clock_type; + + GF_Filter *alias_orig; + + GF_Fraction64 last_ts_drop; + +}; + +struct __gf_filter_pid +{ + //first two fields are the same as in GF_FilterPidInst for typecast + struct __gf_filter_pid *pid; //self if output pid, or source pid if output + GF_Filter *filter; + + char *name; + GF_List *destinations; + u32 num_destinations; + GF_List *properties; + Bool request_property_map; + Bool pid_info_changed; + Bool destroyed; + u32 not_connected; + Bool not_connected_ok; + Bool removed; + Bool direct_dispatch; + volatile u32 discard_input_packets; + volatile u32 init_task_pending; + volatile Bool props_changed_since_connect; + //number of shared packets (shared, frame interfaces or reference) still out there + volatile u32 nb_shared_packets_out; + + GF_PropertyMap *infos; + + //set whenever an eos packet is dispatched, reset whenever a regular packet is dispatched + Bool has_seen_eos; + u32 nb_reaggregation_pending; + + //only valid for decoder output pids + u32 max_buffer_unit; + //max number of packets in each of the destination pids - concurrent inc/dec + volatile u32 nb_buffer_unit; + + //times in us + u64 max_buffer_time; + u32 user_max_buffer_time, user_max_playout_time, user_min_playout_time; + //max buffered duration of packets in each of the destination pids - concurrent inc/dec + u64 buffer_duration; + //true if the pid carries raw media + Bool raw_media; + //for stats only + u32 stream_type, codecid; + + volatile u32 would_block; // concurrent set + volatile u32 nb_decoder_inputs; + + Bool duration_init; + u64 last_pck_dts, last_pck_cts, min_pck_cts, max_pck_cts; + u32 min_pck_duration, nb_unreliable_dts; + Bool recompute_dts; + + u32 nb_pck_sent; + //1000x speed value + u32 playback_speed_scaler; + + GF_Fraction64 last_ts_sent; + + Bool initial_play_done; + Bool is_playing; + void *udta; + + GF_PropertyMap *caps_negociate; + GF_FilterPidInst *caps_negociate_pidi; + GF_List *adapters_blacklist; + GF_Filter *caps_dst_filter; + + Bool ext_not_trusted; + + Bool require_source_id; + //only used in filter_check_caps + GF_PropertyMap *local_props; + volatile u32 num_pidinst_del_pending; +}; + + +void gf_filter_pid_del(GF_FilterPid *pid); +void gf_filter_pid_del_task(GF_FSTask *task); + +void gf_filter_packet_destroy(GF_FilterPacket *pck); + +void gf_fs_cleanup_filters(GF_FilterSession *fsess); + +/*specific task posting*/ +void gf_filter_pid_post_init_task(GF_Filter *filter, GF_FilterPid *pid); +void gf_filter_pid_post_connect_task(GF_Filter *filter, GF_FilterPid *pid); + +/*internal tasks definitions*/ +void gf_filter_pid_reconfigure_task(GF_FSTask *task); +void gf_filter_update_arg_task(GF_FSTask *task); +void gf_filter_pid_disconnect_task(GF_FSTask *task); +void gf_filter_remove_task(GF_FSTask *task); +void gf_filter_pid_detach_task(GF_FSTask *task); + +u32 gf_filter_caps_bundle_count(const GF_FilterCapability *caps, u32 nb_caps); + +void gf_filter_post_remove(GF_Filter *filter); + +typedef struct +{ + u32 *bundles_in_ok; + u32 *bundles_cap_found; + u32 *bundles_in_scores; + u32 nb_allocs; +} GF_CapsBundleStore; + +#define CAP_MATCH_LOADED_INPUT_ONLY 1 +#define CAP_MATCH_LOADED_OUTPUT_ONLY 1<<1 + +u32 gf_filter_caps_to_caps_match(const GF_FilterRegister *src, u32 src_bundle_idx, const GF_FilterRegister *dst, GF_Filter *dst_filter, u32 *dst_bundle_idx, s32 for_dst_bundle, u32 *loaded_filter_flags, GF_CapsBundleStore *capstore); +Bool gf_filter_has_out_caps(const GF_FilterCapability *caps, u32 nb_caps); +Bool gf_filter_has_in_caps(const GF_FilterCapability *caps, u32 nb_caps); + +void gf_filter_check_output_reconfig(GF_Filter *filter); +Bool gf_filter_reconf_output(GF_Filter *filter, GF_FilterPid *pid); + +void gf_filter_renegociate_output_dst(GF_FilterPid *pid, GF_Filter *filter, GF_Filter *filter_dst, GF_FilterPidInst *dst_pidi, GF_FilterPidInst *src_pidi); + +GF_Filter *gf_filter_pid_resolve_link(GF_FilterPid *pid, GF_Filter *dst, Bool *filter_reassigned); +GF_Filter *gf_filter_pid_resolve_link_check_loaded(GF_FilterPid *pid, GF_Filter *dst, Bool *filter_reassigned, GF_List *skip_if_in_filter_list, Bool *skipped); + +GF_Filter *gf_filter_pid_resolve_link_for_caps(GF_FilterPid *pid, GF_Filter *dst); +u32 gf_filter_pid_resolve_link_length(GF_FilterPid *pid, GF_Filter *dst); + +Bool gf_filter_pid_caps_match(GF_FilterPid *src_pid, const GF_FilterRegister *freg, GF_Filter *filter_inst, u8 *priority, u32 *dst_bundle_idx, GF_Filter *dst_filter, s32 for_bundle_idx); + +void gf_filter_relink_dst(GF_FilterPidInst *pidinst); + +void gf_filter_remove_internal(GF_Filter *filter, GF_Filter *until_filter, Bool keep_end_connections); + +GF_FilterPacket *gf_filter_pck_new_shared_internal(GF_FilterPid *pid, const u8 *data, u32 data_size, gf_fsess_packet_destructor destruct, Bool intern_pck); + +void gf_filter_sess_build_graph(GF_FilterSession *fsess, const GF_FilterRegister *freg); +void gf_filter_sess_reset_graph(GF_FilterSession *fsess, const GF_FilterRegister *freg); + +Bool gf_fs_ui_event(GF_FilterSession *session, GF_Event *uievt); + +GF_Err gf_filter_pck_send_internal(GF_FilterPacket *pck, Bool from_filter); + +void gf_filter_pid_send_event_internal(GF_FilterPid *pid, GF_FilterEvent *evt, Bool force_downstream); + +const GF_PropertyEntry *gf_filter_pid_get_property_entry(GF_FilterPid *pid, u32 prop_4cc); +const GF_PropertyEntry *gf_filter_pid_get_property_entry_str(GF_FilterPid *pid, const char *prop_name); + +const GF_PropertyValue *gf_filter_pid_get_property_first(GF_FilterPid *pid, u32 prop_4cc); +const GF_PropertyValue *gf_filter_pid_get_property_str_first(GF_FilterPid *pid, const char *prop_name); + + +enum +{ + EDGE_STATUS_NONE=0, + EDGE_STATUS_ENABLED, + EDGE_STATUS_DISABLED, +}; + +#define EDGE_LOADED_SOURCE_ONLY (1) +#define EDGE_LOADED_DEST_ONLY (1<<1) + +typedef struct +{ + struct __freg_desc *src_reg; + u16 src_cap_idx; + u16 dst_cap_idx; + u8 weight; + u8 status; + u8 priority; + u8 loaded_filter_only; + u32 disabled_depth; + //stream type of the output cap of src. Might be: + // -1 if multiple stream types are defined in the cap (demuxers, encoders/decoders bundles) + // 0 if not specified + // or a valid GF_STREAM_* + s32 src_stream_type; +} GF_FilterRegEdge; + +typedef struct __freg_desc +{ + const GF_FilterRegister *freg; + u32 nb_edges, nb_alloc_edges; + GF_FilterRegEdge *edges; + u32 dist; + struct __freg_desc *destination; + u32 cap_idx; + u8 priority; + u8 in_edges_enabling; +} GF_FilterRegDesc; + +#ifdef GPAC_MEMORY_TRACKING +size_t gf_mem_get_stats(unsigned int *nb_allocs, unsigned int *nb_callocs, unsigned int *nb_reallocs, unsigned int *nb_free); +#endif + +void gf_filter_post_process_task_internal(GF_Filter *filter, Bool use_direct_dispatch); + +//get next option after path, NULL if not found +const char *gf_fs_path_escape_colon(GF_FilterSession *sess, const char *path); + +void gf_fs_check_graph_load(GF_FilterSession *fsess, Bool for_load); + +void gf_filter_renegociate_output_task(GF_FSTask *task); + +void gf_fs_unload_script(GF_FilterSession *fs, void *js_ctx); + +#endif //_GF_FILTER_SESSION_H_ + + + + diff --git a/src/filter_core/filter_session_js.c b/src/filter_core/filter_session_js.c new file mode 100644 index 0000000..01be44c --- /dev/null +++ b/src/filter_core/filter_session_js.c @@ -0,0 +1,1241 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / filters sub-project + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terfsess of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "filter_session.h" + +#include +#include "../scenegraph/qjs_common.h" + +#ifdef GPAC_HAS_QJS +void js_load_constants(JSContext *ctx, JSValue global_obj); + + + +static JSClassID fs_class_id; +typedef struct __jsfs_task +{ + JSValue fun; + JSValue _obj; + u32 type; + JSContext *ctx; +} JSFS_Task; + +static void jsfs_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) +{ + GF_FilterSession *fs = JS_GetOpaque(val, fs_class_id); + if (fs) { + u32 i, count=gf_list_count(fs->jstasks); + for (i=0; ijstasks, i); + JS_MarkValue(rt, task->fun, mark_func); + JS_MarkValue(rt, task->_obj, mark_func); + } + gf_fs_lock_filters(fs, GF_TRUE); + count = gf_list_count(fs->filters); + for (i=0; ifilters, i); + if (!JS_IsUndefined(f->jsval)) + JS_MarkValue(rt, f->jsval, mark_func); + } + gf_fs_lock_filters(fs, GF_FALSE); + } +} + +static JSClassDef fs_class = { + "FilterSession", + .gc_mark = jsfs_mark +}; + + +static JSClassID fs_f_class_id; +static JSClassDef fs_f_class = { + "Filter", +}; + +enum +{ + JSFS_NB_FILTERS = 0, + JSFS_LAST_TASK, + JSFS_HTTP_MAX_RATE, + JSFS_HTTP_RATE, + JSFS_RMT_SAMPLING +}; + +GF_Filter *jsff_get_filter(JSContext *c, JSValue this_val) +{ + return JS_GetOpaque(this_val, fs_f_class_id); +} +static JSValue jsfs_new_filter_obj(JSContext *ctx, GF_Filter *f); + + +static void jsfs_exec_task_custom(JSFS_Task *task, const char *text, GF_Filter *new_filter, GF_Filter *del_filter) +{ + JSValue ret, arg; + + gf_js_lock(task->ctx, GF_TRUE); + if (text) { + arg = JS_NewString(task->ctx, text); + } else if (new_filter) { + arg = jsfs_new_filter_obj(task->ctx, new_filter); + } else { + arg = JS_DupValue(task->ctx, del_filter->jsval); + } + + ret = JS_Call(task->ctx, task->fun, task->_obj, 1, &arg); + JS_FreeValue(task->ctx, arg); + + if (del_filter) { + JS_SetOpaque(del_filter->jsval, NULL); + JS_FreeValue(task->ctx, del_filter->jsval); + del_filter->jsval = JS_UNDEFINED; + } + + if (JS_IsException(ret)) { + js_dump_error(task->ctx); + } + JS_FreeValue(task->ctx, ret); + js_do_loop(task->ctx); + gf_js_lock(task->ctx, GF_FALSE); +} + +static void jsfs_rmt_user_callback(void *udta, const char* text) +{ + JSFS_Task *task = udta; + if (!task) return; + jsfs_exec_task_custom(task, text, NULL, NULL); +} + +static JSValue jsfs_prop_get(JSContext *ctx, JSValueConst this_val, int magic) +{ + GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + if (!fs) + return JS_EXCEPTION; + + switch (magic) { + case JSFS_NB_FILTERS: + return JS_NewInt32(ctx, gf_fs_get_filters_count(fs)); + case JSFS_LAST_TASK: + return gf_fs_is_last_task(fs) ? JS_TRUE : JS_FALSE; + + case JSFS_HTTP_MAX_RATE: + if (fs->download_manager) + return JS_NewInt32(ctx, gf_dm_get_data_rate(fs->download_manager) ); + return JS_NULL; + + case JSFS_HTTP_RATE: + if (fs->download_manager) + return JS_NewInt32(ctx, gf_dm_get_global_rate(fs->download_manager) / 1000); + return JS_NULL; + case JSFS_RMT_SAMPLING: + return JS_NewBool(ctx, gf_sys_profiler_sampling_enabled() ); + } + return JS_UNDEFINED; +} + +static JSValue jsfs_prop_set(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic) +{ + GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + if (!fs) + return JS_EXCEPTION; + + switch (magic) { + case JSFS_HTTP_MAX_RATE: + if (fs->download_manager) { + s32 ival; + if (JS_ToInt32(ctx, &ival, value)) return JS_EXCEPTION; + gf_dm_set_data_rate(fs->download_manager, (u32) ival); + } + break; + case JSFS_RMT_SAMPLING: + gf_sys_profiler_enable_sampling(JS_ToBool(ctx, value) ? GF_TRUE : GF_FALSE); + break; + } + return JS_UNDEFINED; +} + +static Bool jsfs_task_exec(GF_FilterSession *fs, void *udta, u32 *timeout_ms) +{ + JSValue ret; + s32 ret_val; + Bool do_free=GF_TRUE; + JSFS_Task *task = udta; + gf_js_lock(task->ctx, GF_TRUE); + ret = JS_Call(task->ctx, task->fun, task->_obj, 0, NULL); + + *timeout_ms = 0; + if (JS_IsException(ret)) { + js_dump_error(task->ctx); + } + else if (JS_IsBool(ret)) { + if (JS_ToBool(task->ctx, ret)) + do_free = GF_FALSE; + } + else if (JS_IsInteger(ret)) { + JS_ToInt32(task->ctx, (int*)&ret_val, ret); + if (ret_val>=0) { + *timeout_ms = ret_val; + do_free = GF_FALSE; + } + } + + JS_FreeValue(task->ctx, ret); + js_do_loop(task->ctx); + gf_js_lock(task->ctx, GF_FALSE); + + if (do_free) { + JS_FreeValue(task->ctx, task->fun); + JS_FreeValue(task->ctx, task->_obj); + gf_list_del_item(fs->jstasks, task); + gf_free(task); + return GF_FALSE; + } + return GF_TRUE; +} + +static JSValue jsfs_post_task(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + JSFS_Task *task; + const char *tname = NULL; + GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + if (!fs || !argc) return JS_EXCEPTION; + if (!JS_IsFunction(ctx, argv[0]) ) return JS_EXCEPTION; + + GF_SAFEALLOC(task, JSFS_Task); + if (!task) return JS_EXCEPTION; + task->ctx = ctx; + + if (argc>1) { + tname = JS_ToCString(ctx, argv[1]); + } + task->fun = JS_DupValue(ctx, argv[0]); + task->_obj = JS_DupValue(ctx, this_val); + gf_list_add(fs->jstasks, task); + + gf_fs_post_user_task(fs, jsfs_task_exec, task, tname ? tname : "task"); + if (tname) + JS_FreeCString(ctx, tname); + + return JS_UNDEFINED; +} + +static JSValue jsfs_abort(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + Bool do_flush = GF_FALSE; + GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + if (!fs) return JS_EXCEPTION; + if (argc && JS_IsBool(argv[0]) && JS_ToBool(ctx, argv[0])) { + do_flush = GF_FALSE; + } + gf_fs_abort(fs, do_flush); + return JS_UNDEFINED; +} +static JSValue jsfs_lock_filters(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + Bool do_lock; + GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + if (!fs || !argc) return JS_EXCEPTION; + if (JS_IsBool(argv[0])) do_lock = JS_ToBool(ctx, argv[0]); + else return JS_EXCEPTION; + + gf_fs_lock_filters(fs, do_lock); + return JS_UNDEFINED; +} + + +static JSValue jsfs_rmt_send(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + const char *msg; + GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + if (!fs ||!argc) return JS_EXCEPTION; + msg = JS_ToCString(ctx, argv[0]); + if (!msg) return JS_EXCEPTION; + gf_sys_profiler_send(msg); + JS_FreeCString(ctx, msg); + return JS_UNDEFINED; +} + + +void jsfs_on_filter_created(GF_Filter *new_filter) +{ + new_filter->jsval = JS_UNDEFINED; + if (!new_filter->session->new_f_task) return; + jsfs_exec_task_custom(new_filter->session->new_f_task, NULL, new_filter, NULL); +} + +void jsfs_on_filter_destroyed(GF_Filter *del_filter) +{ + if (! JS_IsUndefined(del_filter->jsval)) { + void *p = JS_GetOpaque(del_filter->jsval, fs_f_class_id); + if (!p) return; + if (del_filter->session->del_f_task) { + jsfs_exec_task_custom(del_filter->session->del_f_task, NULL, NULL, del_filter); + } else { + JSRuntime *gf_js_get_rt(); + JSRuntime *rt = gf_js_get_rt(); + if (rt) { + gf_js_lock(NULL, GF_TRUE); + JS_FreeValueRT(rt, del_filter->jsval); + gf_js_lock(NULL, GF_FALSE); + } + } + del_filter->jsval = JS_UNDEFINED; + } +} + +Bool jsfs_on_event(GF_FilterSession *fs, GF_Event *evt) +{ + GF_FilterEvent fevt; + JSValue js_init_evt_obj(JSContext *ctx, const GF_FilterEvent *evt); + + JSValue arg, ret; + Bool res; + assert(fs->on_evt_task); + gf_js_lock(fs->on_evt_task->ctx, GF_TRUE); + + memset(&fevt, 0, sizeof(GF_FilterEvent)); + fevt.user_event.event = *evt; + fevt.base.type = GF_FEVT_USER; + + arg = js_init_evt_obj(fs->on_evt_task->ctx, &fevt); + + ret = JS_Call(fs->on_evt_task->ctx, fs->on_evt_task->fun, fs->on_evt_task->_obj, 1, &arg); + JS_SetOpaque(arg, NULL); + JS_FreeValue(fs->on_evt_task->ctx, arg); + + if (JS_IsException(ret)) { + js_dump_error(fs->on_evt_task->ctx); + } + fevt.user_event.event.type = evt->type; + *evt = fevt.user_event.event; + res = JS_ToBool(fs->on_evt_task->ctx, ret) ? GF_TRUE : GF_FALSE; + if (!res && (evt->type==GF_EVENT_COPY_TEXT) && evt->clipboard.text) { + gf_free(evt->clipboard.text); + evt->clipboard.text = NULL; + } + JS_FreeValue(fs->on_evt_task->ctx, ret); + js_do_loop(fs->on_evt_task->ctx); + gf_js_lock(fs->on_evt_task->ctx, GF_FALSE); + return res; +} + + +static JSValue jsfs_set_fun_callback(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, u32 cbk_type) +{ + JSFS_Task *task = NULL; + u32 i, count; + Bool is_rem = GF_FALSE; + GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + if (!fs || !argc) return JS_EXCEPTION; + + if (JS_IsNull(argv[0])) + is_rem = GF_TRUE; + else if (!JS_IsFunction(ctx, argv[0]) ) + return JS_EXCEPTION; + + if (cbk_type==2) { + task = fs->new_f_task; + } else if (cbk_type==3) { + task = fs->del_f_task; + } else if (cbk_type==4) { + task = fs->on_evt_task; + } else { + count = gf_list_count(fs->jstasks); + for (i=0; ijstasks, i); + if (task->type==1) break; + task = NULL; + } + } + if (is_rem) { + if (task) { + JS_FreeValue(ctx, task->fun); + JS_FreeValue(ctx, task->_obj); + gf_list_del_item(fs->jstasks, task); + gf_free(task); + } + if (cbk_type == 1) + gf_sys_profiler_set_callback(task, NULL); + else if (cbk_type == 2) + fs->new_f_task = NULL; + else if (cbk_type == 3) + fs->del_f_task = NULL; + else if (cbk_type == 4) + fs->on_evt_task = NULL; + + return JS_UNDEFINED; + } + + if (task) { + JS_FreeValue(ctx, task->fun); + JS_FreeValue(ctx, task->_obj); + } else { + GF_SAFEALLOC(task, JSFS_Task); + if (!task) return JS_EXCEPTION; + gf_list_add(fs->jstasks, task); + task->type = cbk_type; + task->ctx = ctx; + } + task->fun = JS_DupValue(ctx, argv[0]); + task->_obj = JS_DupValue(ctx, this_val); + + if (cbk_type == 1) + gf_sys_profiler_set_callback(task, jsfs_rmt_user_callback); + else if (cbk_type == 2) + fs->new_f_task = task; + else if (cbk_type == 3) + fs->del_f_task = task; + else if (cbk_type == 4) + fs->on_evt_task = task; + return JS_UNDEFINED; +} + +static JSValue jsfs_set_rmt_fun(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + return jsfs_set_fun_callback(ctx, this_val, argc, argv, 1); +} +static JSValue jsfs_set_new_filter_fun(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + return jsfs_set_fun_callback(ctx, this_val, argc, argv, 2); +} +static JSValue jsfs_set_del_filter_fun(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + return jsfs_set_fun_callback(ctx, this_val, argc, argv, 3); +} +static JSValue jsfs_set_event_fun(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + return jsfs_set_fun_callback(ctx, this_val, argc, argv, 4); +} + +enum +{ + JSFF_NAME = 0, + JSFF_TYPE, + JSFF_ID, + JSFF_NB_IPID, + JSFF_NB_OPID, + JSFF_STATUS, + JSFF_ALIAS, + JSFF_ARGS, + JSFF_DYNAMIC, + JSFF_REMOVED, + JSFF_PCK_DONE, + JSFF_BYTES_DONE, + JSFF_TIME, + JSFF_PCK_SENT_IFCE, + JSFF_PCK_SENT, + JSFF_BYTES_SENT, + JSFF_NB_TASKS, + JSFF_NB_ERRORS, + JSFF_REPORT_UPDATED, + JSFF_CLASS, + JSFF_CODEC, + JSFF_STREAMTYPE, + JSFF_INAME, + JSFF_EVENT_TARGET, + JSFF_LAST_TS_SENT, + JSFF_LAST_TS_DROP, +}; + + +static JSValue jsfs_f_prop_get(JSContext *ctx, JSValueConst this_val, int magic) +{ + const char *val_s; + Bool val_b; + GF_FilterStats stats; + GF_Filter *f = JS_GetOpaque(this_val, fs_f_class_id); + if (!f) + return JS_EXCEPTION; + + switch (magic) { + case JSFF_NAME: + return JS_NewString(ctx, gf_filter_get_name(f) ); + case JSFF_TYPE: + return JS_NewString(ctx, f->freg->name ); + case JSFF_ID: + val_s = gf_filter_get_id(f); + if (!val_s) return JS_NULL; + return JS_NewString(ctx, val_s); + case JSFF_NB_IPID: + return JS_NewInt32(ctx, gf_filter_get_ipid_count(f) ); + case JSFF_NB_OPID: + return JS_NewInt32(ctx, gf_filter_get_opid_count(f) ); + case JSFF_STATUS: + return JS_NewString(ctx, f->status_str ? f->status_str : ""); + case JSFF_ALIAS: + return JS_NewBool(ctx, f->multi_sink_target ? 1 : 0); + case JSFF_ARGS: + return JS_NewString(ctx, f->src_args ? f->src_args : ""); + case JSFF_DYNAMIC: + return JS_NewBool(ctx, f->dynamic_filter); + case JSFF_REMOVED: + return JS_NewBool(ctx, (f->removed || f->finalized)); + + case JSFF_PCK_DONE: + return JS_NewInt64(ctx, f->nb_pck_processed); + case JSFF_BYTES_DONE: + return JS_NewInt64(ctx, f->nb_bytes_processed); + case JSFF_TIME: + return JS_NewInt64(ctx, f->time_process); + case JSFF_PCK_SENT_IFCE: + return JS_NewInt64(ctx, f->nb_hw_pck_sent); + case JSFF_PCK_SENT: + return JS_NewInt64(ctx, f->nb_pck_sent); + case JSFF_BYTES_SENT: + return JS_NewInt64(ctx, f->nb_bytes_sent); + case JSFF_NB_TASKS: + return JS_NewInt64(ctx, f->nb_tasks_done); + case JSFF_NB_ERRORS: + return JS_NewInt64(ctx, f->nb_errors); + case JSFF_REPORT_UPDATED: + val_b = f->report_updated; + f->report_updated = GF_FALSE; + return JS_NewBool(ctx, val_b); + case JSFF_CLASS: + gf_filter_get_stats(f, &stats); + switch (stats.type) { + case GF_FS_STATS_FILTER_RAWIN: return JS_NewString(ctx, "rawin"); + case GF_FS_STATS_FILTER_DEMUX: return JS_NewString(ctx, "demuxer"); + case GF_FS_STATS_FILTER_DECODE: return JS_NewString(ctx, "decoder"); + case GF_FS_STATS_FILTER_ENCODE: return JS_NewString(ctx, "encoder"); + case GF_FS_STATS_FILTER_MUX: return JS_NewString(ctx, "muxer"); + case GF_FS_STATS_FILTER_RAWOUT: return JS_NewString(ctx, "rawout"); + case GF_FS_STATS_FILTER_MEDIA_SINK: return JS_NewString(ctx, "mediasink"); + case GF_FS_STATS_FILTER_MEDIA_SOURCE: return JS_NewString(ctx, "mediasource"); + default: return JS_NewString(ctx, "unknown"); + } + case JSFF_CODEC: + gf_filter_get_stats(f, &stats); + if (stats.codecid) { + return JS_NewString(ctx, gf_codecid_name(stats.codecid)); + } + return JS_NewString(ctx, "unknown"); + case JSFF_STREAMTYPE: + gf_filter_get_stats(f, &stats); + if (stats.stream_type) { + return JS_NewString(ctx, gf_stream_type_name(stats.stream_type)); + } + return JS_NewString(ctx, "unknown"); + + case JSFF_LAST_TS_SENT: + gf_filter_get_stats(f, &stats); + if (!stats.last_ts_sent.den) return JS_NULL; + return JS_NewInt64(ctx, stats.last_ts_sent.num); + case JSFF_LAST_TS_DROP: + gf_filter_get_stats(f, &stats); + if (!stats.last_ts_drop.den) return JS_NULL; + return JS_NewInt64(ctx, stats.last_ts_drop.num); + + case JSFF_INAME: + if (f->iname) return JS_NewString(ctx, f->iname); + return JS_NULL; + case JSFF_EVENT_TARGET: + return JS_NewBool(ctx, f->event_target); + } + return JS_UNDEFINED; +} + +static JSValue jsfs_f_prop_set(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic) +{ + const char *s_val; +#ifdef GPAC_ENABLE_COVERAGE + GF_Filter *f = jsff_get_filter(ctx, this_val); +#else + GF_Filter *f = JS_GetOpaque(this_val, fs_f_class_id); +#endif + if (!f) + return JS_EXCEPTION; + + switch (magic) { + case JSFF_INAME: + s_val = JS_ToCString(ctx, value); + if (f->iname) gf_free(f->iname); + f->iname = s_val ? gf_strdup(s_val) : NULL; + JS_FreeCString(ctx, s_val); + break; + } + return JS_UNDEFINED; +} + + +static JSValue jsff_is_destroyed(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_Filter *f = JS_GetOpaque(this_val, fs_f_class_id); + if (!f) + return JS_TRUE; + return JS_FALSE; +} + +JSValue jsf_NewProp(JSContext *ctx, const GF_PropertyValue *new_val); + +static JSValue jsff_enum_pid_props(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, Bool is_output) +{ + u32 idx; + GF_FilterPid *pid; + const char *pname=NULL; + GF_Filter *f = JS_GetOpaque(this_val, fs_f_class_id); + if (!f || (argc!=2) ) + return JS_EXCEPTION; + + if (JS_IsString(argv[1])) { + pname = JS_ToCString(ctx, argv[1]); + if (!pname) return JS_EXCEPTION; + } + else if (!JS_IsFunction(ctx, argv[1])) + return JS_EXCEPTION; + + if (JS_ToInt32(ctx, &idx, argv[0])) + return JS_EXCEPTION; + + if (is_output) { + pid = gf_filter_get_opid(f, idx); + if (!pid) return JS_EXCEPTION; + } else { + pid = gf_filter_get_ipid(f, idx); + if (!pid) return JS_EXCEPTION; + } + + if (pname) { + const GF_PropertyValue *p; + u32 p4cc = gf_props_get_id(pname); + if (p4cc) + p = gf_filter_pid_get_property(pid, p4cc); + else + p = gf_filter_pid_get_property_str(pid, pname); + + JS_FreeCString(ctx, pname); + return jsf_NewProp(ctx, p); + } + + idx = 0; + while (1) { + u32 prop_4cc; + const char *prop_name; + JSValue args[3], ret; + const GF_PropertyValue *prop = gf_filter_pid_enum_properties(pid, &idx, &prop_4cc, &prop_name); + if (!prop) break; + if (prop->type==GF_PROP_POINTER) continue; + + args[0] = JS_NewString(ctx, prop_name ? prop_name : gf_props_4cc_get_name(prop_4cc) ); + args[1] = JS_NewString(ctx, gf_props_get_type_name(prop->type) ); + args[2] = jsf_NewProp(ctx, prop); + ret = JS_Call(ctx, argv[1], this_val, 3, args); + if (JS_IsException(ret)) { + js_dump_error(ctx); + } + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, args[0]); + JS_FreeValue(ctx, args[1]); + JS_FreeValue(ctx, args[2]); + } + return JS_UNDEFINED; +} +static JSValue jsff_enum_ipid_props(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + return jsff_enum_pid_props(ctx, this_val, argc, argv, GF_FALSE); +} +static JSValue jsff_enum_opid_props(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + return jsff_enum_pid_props(ctx, this_val, argc, argv, GF_TRUE); +} + + +static JSValue jsff_get_pid_source(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + u32 idx; + GF_FilterPid *pid; + GF_FilterPidInst *ipid; + GF_Filter *f = JS_GetOpaque(this_val, fs_f_class_id); + if (!f || (argc!=1) ) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &idx, argv[0])) + return JS_EXCEPTION; + + pid = gf_filter_get_ipid(f, idx); + if (!pid) return JS_NULL; + + ipid = (GF_FilterPidInst *)pid; + idx = gf_list_find(f->session->filters, ipid->pid->filter); + + return jsfs_new_filter_obj(ctx, ipid->pid->filter); +} + +static JSValue jsff_get_pid_sinks(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + u32 idx; + JSValue ret; + GF_FilterPid *pid; + GF_Filter *f = JS_GetOpaque(this_val, fs_f_class_id); + if (!f || (argc!=1) ) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &idx, argv[0])) + return JS_EXCEPTION; + + pid = gf_filter_get_opid(f, idx); + if (!pid) return JS_NULL; + + ret = JS_NewArray(ctx); + JS_SetPropertyStr(ctx, ret, "length", JS_NewInt32(ctx, pid->num_destinations) ); + + for (idx=0; idxnum_destinations; idx++) { + GF_FilterPidInst *pid_inst = gf_list_get(pid->destinations, idx); + JS_SetPropertyUint32(ctx, ret, idx, jsfs_new_filter_obj(ctx, pid_inst->filter) ); + } + return ret; +} + +static JSValue jsff_all_args(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + u32 idx, a_idx; + JSValue res; + Bool val_only = GF_TRUE; + const GF_FilterArgs *args; + GF_Filter *f = JS_GetOpaque(this_val, fs_f_class_id); + if (!f) + return JS_EXCEPTION; + + if (argc && JS_IsBool(argv[0]) && JS_ToBool(ctx, argv[0])) + val_only = GF_FALSE; + + res = JS_NewArray(ctx); + idx=0; + a_idx=0; + args = f->instance_args ? f->instance_args : f->freg->args; + while (args && args[idx].arg_name) { + const GF_FilterArgs *arg = &args[idx]; + GF_PropertyValue p; + JSValue aval; + if (arg->flags & GF_FS_ARG_HINT_HIDE) { + idx++; + continue; + } + + aval = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, aval, "name", JS_NewString(ctx, arg->arg_name) ); + if (f->instance_args) { + char szArgName[100]; + char *arg_start; + snprintf(szArgName, 100, "%c%s%c", f->session->sep_args, arg->arg_name, f->session->sep_name); + arg_start = f->orig_args ? strstr(f->orig_args, szArgName) : NULL; + if (arg_start) { + char *arg_end; + arg_start += strlen(szArgName); + arg_start = gf_strdup(arg_start); + arg_end = strchr(arg_start, f->session->sep_args); + if (arg_end) arg_end[0] = 0; + JS_SetPropertyStr(ctx, aval, "value", JS_NewString(ctx, arg_start) ); + gf_free(arg_start); + } else { + snprintf(szArgName, 100, "%c%s", f->session->sep_args, arg->arg_name); + arg_start = f->orig_args ? strstr(f->orig_args, szArgName) : NULL; + if (arg_start && (arg->arg_type==GF_PROP_BOOL)) { + p.type = GF_PROP_BOOL; + p.value.boolean = GF_TRUE; + JS_SetPropertyStr(ctx, aval, "value", jsf_NewProp(ctx, &p) ); + } else { + snprintf(szArgName, 100, "%c%c%s", f->session->sep_args, f->session->sep_neg, arg->arg_name); + arg_start = f->orig_args ? strstr(f->orig_args, szArgName) : NULL; + if (arg_start && (arg->arg_type==GF_PROP_BOOL)) { + p.type = GF_PROP_BOOL; + p.value.boolean = GF_FALSE; + JS_SetPropertyStr(ctx, aval, "value", jsf_NewProp(ctx, &p) ); + } else { + if (arg->arg_default_val) { + gf_props_parse_value(arg->arg_type, arg->arg_name, arg->arg_default_val, arg->min_max_enum, f->session->sep_list); + JS_SetPropertyStr(ctx, aval, "value", jsf_NewProp(ctx, &p) ); + } else { + JS_SetPropertyStr(ctx, aval, "value", JS_NULL); + } + } + } + } + } else { + gf_filter_get_arg(f, arg->arg_name, &p); + JS_SetPropertyStr(ctx, aval, "value", jsf_NewProp(ctx, &p) ); + } + if (!val_only) { + if (arg->arg_desc) + JS_SetPropertyStr(ctx, aval, "desc", JS_NewString(ctx, arg->arg_desc) ); + if (arg->min_max_enum) + JS_SetPropertyStr(ctx, aval, "min_max_enum", JS_NewString(ctx, arg->min_max_enum) ); + if (arg->arg_default_val) + JS_SetPropertyStr(ctx, aval, "default", JS_NewString(ctx, arg->arg_default_val) ); + JS_SetPropertyStr(ctx, aval, "update", JS_NewBool(ctx, (arg->flags & GF_FS_ARG_UPDATE) ) ); + if (arg->flags & GF_FS_ARG_HINT_ADVANCED) { + JS_SetPropertyStr(ctx, aval, "hint", JS_NewString(ctx, "advanced") ); + } + else if (arg->flags & GF_FS_ARG_HINT_EXPERT) { + JS_SetPropertyStr(ctx, aval, "hint", JS_NewString(ctx, "expert") ); + } + } + JS_SetPropertyUint32(ctx, res, idx, aval); + idx++; + a_idx++; + } + return res; +} + +static JSValue jsff_update(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + const char *aname, *aval; + GF_Filter *f = JS_GetOpaque(this_val, fs_f_class_id); + if (!f || (argc!=2) ) + return JS_EXCEPTION; + + aname = JS_ToCString(ctx, argv[0]); + if (!aname) return JS_EXCEPTION; + aval = JS_ToCString(ctx, argv[1]); + if (!aval) { + JS_FreeCString(ctx, aname); + return JS_EXCEPTION; + } + gf_fs_send_update(f->session, NULL, f, aname, aval, 0); + JS_FreeCString(ctx, aname); + JS_FreeCString(ctx, aval); + return JS_UNDEFINED; +} + +static JSValue jsff_remove(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_Filter *f = JS_GetOpaque(this_val, fs_f_class_id); + if (!f) + return JS_EXCEPTION; + + gf_filter_remove(f); + return JS_UNDEFINED; +} + +static JSValue jsff_insert_filter(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + const char *fname, *link_args; + GF_Filter *new_f; + GF_Err e; + GF_Filter *f = JS_GetOpaque(this_val, fs_f_class_id); + if (!f || !argc) + return JS_EXCEPTION; + + fname = JS_ToCString(ctx, argv[0]); + if (!fname) return JS_EXCEPTION; + link_args = NULL; + if (argc>1) { + link_args = JS_ToCString(ctx, argv[1]); + if (!link_args) { + JS_FreeCString(ctx, fname); + return JS_EXCEPTION; + } + } + + if (!strncmp(fname, "src=", 4)) { + new_f = gf_fs_load_source(f->session, fname+4, NULL, NULL, &e); + } else if (!strncmp(fname, "dst=", 4)) { + new_f = gf_fs_load_destination(f->session, fname+4, NULL, NULL, &e); + } else { + new_f = gf_fs_load_filter(f->session, fname, &e); + } + + if (!new_f) { + JSValue ret = js_throw_err_msg(ctx, e, "Cannot load filter %s: %s\n", fname, gf_error_to_string(e)); + JS_FreeCString(ctx, fname); + if (link_args) JS_FreeCString(ctx, link_args); + return ret; + } + gf_filter_set_source(new_f, (GF_Filter *) f, link_args); + //reconnect outputs of source + gf_filter_reconnect_output((GF_Filter *) f); + + JS_FreeCString(ctx, fname); + if (link_args) JS_FreeCString(ctx, link_args); + + return jsfs_new_filter_obj(ctx, new_f); +} + +#define JS_CGETSET_MAGIC_DEF_ENUM(name, fgetter, fsetter, magic) { name, JS_PROP_CONFIGURABLE|JS_PROP_ENUMERABLE, JS_DEF_CGETSET_MAGIC, magic, .u = { .getset = { .get = { .getter_magic = fgetter }, .set = { .setter_magic = fsetter } } } } + +static const JSCFunctionListEntry fs_f_funcs[] = { + JS_CGETSET_MAGIC_DEF_ENUM("name", jsfs_f_prop_get, NULL, JSFF_NAME), + JS_CGETSET_MAGIC_DEF_ENUM("type", jsfs_f_prop_get, NULL, JSFF_TYPE), + JS_CGETSET_MAGIC_DEF_ENUM("ID", jsfs_f_prop_get, NULL, JSFF_ID), + JS_CGETSET_MAGIC_DEF_ENUM("nb_ipid", jsfs_f_prop_get, NULL, JSFF_NB_IPID), + JS_CGETSET_MAGIC_DEF_ENUM("nb_opid", jsfs_f_prop_get, NULL, JSFF_NB_OPID), + JS_CGETSET_MAGIC_DEF_ENUM("status", jsfs_f_prop_get, NULL, JSFF_STATUS), + JS_CGETSET_MAGIC_DEF_ENUM("alias", jsfs_f_prop_get, NULL, JSFF_ALIAS), + JS_CGETSET_MAGIC_DEF_ENUM("args", jsfs_f_prop_get, NULL, JSFF_ARGS), + JS_CGETSET_MAGIC_DEF_ENUM("dynamic", jsfs_f_prop_get, NULL, JSFF_DYNAMIC), + JS_CGETSET_MAGIC_DEF_ENUM("done", jsfs_f_prop_get, NULL, JSFF_REMOVED), + JS_CGETSET_MAGIC_DEF_ENUM("pck_done", jsfs_f_prop_get, NULL, JSFF_PCK_DONE), + JS_CGETSET_MAGIC_DEF_ENUM("bytes_done", jsfs_f_prop_get, NULL, JSFF_BYTES_DONE), + JS_CGETSET_MAGIC_DEF_ENUM("time", jsfs_f_prop_get, NULL, JSFF_TIME), + JS_CGETSET_MAGIC_DEF_ENUM("pck_sent", jsfs_f_prop_get, NULL, JSFF_PCK_SENT), + JS_CGETSET_MAGIC_DEF_ENUM("pck_ifce_sent", jsfs_f_prop_get, NULL, JSFF_PCK_SENT_IFCE), + JS_CGETSET_MAGIC_DEF_ENUM("bytes_sent", jsfs_f_prop_get, NULL, JSFF_BYTES_SENT), + JS_CGETSET_MAGIC_DEF_ENUM("tasks", jsfs_f_prop_get, NULL, JSFF_NB_TASKS), + JS_CGETSET_MAGIC_DEF_ENUM("errors", jsfs_f_prop_get, NULL, JSFF_NB_ERRORS), + JS_CGETSET_MAGIC_DEF_ENUM("report_updated", jsfs_f_prop_get, NULL, JSFF_REPORT_UPDATED), + JS_CGETSET_MAGIC_DEF_ENUM("class", jsfs_f_prop_get, NULL, JSFF_CLASS), + JS_CGETSET_MAGIC_DEF_ENUM("streamtype", jsfs_f_prop_get, NULL, JSFF_STREAMTYPE), + JS_CGETSET_MAGIC_DEF_ENUM("codec", jsfs_f_prop_get, NULL, JSFF_CODEC), + JS_CGETSET_MAGIC_DEF_ENUM("iname", jsfs_f_prop_get, jsfs_f_prop_set, JSFF_INAME), + JS_CGETSET_MAGIC_DEF_ENUM("event_target", jsfs_f_prop_get, NULL, JSFF_EVENT_TARGET), + JS_CGETSET_MAGIC_DEF_ENUM("last_ts_sent", jsfs_f_prop_get, NULL, JSFF_LAST_TS_SENT), + JS_CGETSET_MAGIC_DEF_ENUM("last_ts_drop", jsfs_f_prop_get, NULL, JSFF_LAST_TS_DROP), + + JS_CFUNC_DEF("is_destroyed", 0, jsff_is_destroyed), + JS_CFUNC_DEF("ipid_props", 0, jsff_enum_ipid_props), + JS_CFUNC_DEF("opid_props", 0, jsff_enum_opid_props), + JS_CFUNC_DEF("ipid_source", 0, jsff_get_pid_source), + JS_CFUNC_DEF("opid_sinks", 0, jsff_get_pid_sinks), + JS_CFUNC_DEF("all_args", 0, jsff_all_args), + JS_CFUNC_DEF("update", 0, jsff_update), + JS_CFUNC_DEF("remove", 0, jsff_remove), + JS_CFUNC_DEF("insert", 0, jsff_insert_filter), +}; + +static JSValue jsfs_new_filter_obj(JSContext *ctx, GF_Filter *f) +{ + if (JS_IsUndefined(f->jsval)) { + f->jsval = JS_NewObjectClass(ctx, fs_f_class_id); + JS_SetPropertyFunctionList(ctx, f->jsval, fs_f_funcs, countof(fs_f_funcs)); + JS_SetOpaque(f->jsval, f); + } + return JS_DupValue(ctx, f->jsval); +} + +static JSValue jsfs_get_filter(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + u32 idx; + GF_Filter *f = NULL; + GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + if (!fs || !argc) return JS_EXCEPTION; + + if (JS_IsString(argv[0])) { + const char *iname = JS_ToCString(ctx, argv[0]); + if (iname) { + u32 i, count; + gf_fs_lock_filters(fs, GF_TRUE); + count=gf_list_count(fs->filters); + for (i=0; ifilters, i); + if (f->iname && !strcmp(f->iname, iname)) break; + f = NULL; + } + gf_fs_lock_filters(fs, GF_FALSE); + } + JS_FreeCString(ctx, iname); + } else { + if (JS_ToInt32(ctx, &idx, argv[0])) + return JS_EXCEPTION; + + f = gf_list_get(fs->filters, idx); + } + if (!f) return JS_EXCEPTION; + + return jsfs_new_filter_obj(ctx, f); +} + + +static JSValue jsfs_add_filter(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + const char *fname, *link_args; + GF_Filter *new_f; + GF_Err e; + GF_Filter *link_from = NULL; + GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + if (!fs || !argc) + return JS_EXCEPTION; + + fname = JS_ToCString(ctx, argv[0]); + if (!fname) return JS_EXCEPTION; + + link_args = NULL; + if (argc>1) { + link_from = JS_GetOpaque(argv[1], fs_f_class_id); + if (!link_from) { + JS_FreeCString(ctx, fname); + return JS_EXCEPTION; + } + if (argc>2) { + link_args = JS_ToCString(ctx, argv[2]); + } + } + + if (!strncmp(fname, "src=", 4)) { + new_f = gf_fs_load_source(fs, fname+4, NULL, NULL, &e); + } else if (!strncmp(fname, "dst=", 4)) { + new_f = gf_fs_load_destination(fs, fname+4, NULL, NULL, &e); + } else { + new_f = gf_fs_load_filter(fs, fname, &e); + } + + if (!new_f) { + JSValue ret = js_throw_err_msg(ctx, e, "Cannot load filter %s: %s\n", fname, gf_error_to_string(e)); + JS_FreeCString(ctx, fname); + if (link_args) JS_FreeCString(ctx, link_args); + return ret; + } + JS_FreeCString(ctx, fname); + if (link_from) { + gf_filter_set_source(new_f, link_from, link_args); + } + + if (link_args) JS_FreeCString(ctx, link_args); + + return jsfs_new_filter_obj(ctx, new_f); +} + +static JSValue jsfs_fire_event(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + u32 i, count; + GF_Filter *f = NULL; + Bool ret=GF_FALSE; + GF_FilterPid *on_pid; + GF_FilterEvent *jsf_get_event(JSContext *ctx, JSValueConst this_val); + GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + GF_FilterEvent *evt; + if (!fs || !argc) + return JS_EXCEPTION; + + evt = jsf_get_event(ctx, argv[0]); + if (!evt) return JS_EXCEPTION; + + if (argc>1) { + f = jsff_get_filter(ctx, argv[1]); + } + on_pid = evt->base.on_pid; + evt->base.on_pid = NULL; + if (f) { + Bool upstream = GF_FALSE; + if (evt->base.type==GF_FEVT_USER) { + if (f->freg->process_event && f->event_target) { + gf_mx_p(f->tasks_mx); + f->freg->process_event(f, evt); + gf_mx_v(f->tasks_mx); + ret = GF_TRUE; + } + } + if (!ret) { + if (argc>2) upstream = JS_ToBool(ctx, argv[2]); + gf_mx_p(f->tasks_mx); + if (f->num_output_pids && upstream) ret = GF_TRUE; + else if (f->num_input_pids && !upstream) ret = GF_TRUE; + gf_filter_send_event(f, evt, upstream); + gf_mx_v(f->tasks_mx); + } + } else { + gf_fs_lock_filters(fs, GF_TRUE); + count = gf_list_count(fs->filters); + for (i=0; ifilters, i); + if (f->disabled || f->removed) continue; + if (f->multi_sink_target) continue; + if (!f->freg->process_event) continue; + if (!f->event_target) continue; + + gf_mx_p(f->tasks_mx); + canceled = f->freg->process_event(f, evt); + gf_mx_v(f->tasks_mx); + ret = GF_TRUE; + if (canceled) break; + } + gf_fs_lock_filters(fs, GF_FALSE); + } + evt->base.on_pid = on_pid; + return JS_NewBool(ctx, ret); +} + +static JSValue jsfs_reporting(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + Bool report_on; + GF_FilterSession *fs = JS_GetOpaque(this_val, fs_class_id); + if (!fs || !argc) + return JS_EXCEPTION; + report_on = JS_ToBool(ctx, argv[0]); + gf_fs_enable_reporting(fs, report_on); + return JS_UNDEFINED; +} + + +static const JSCFunctionListEntry fs_funcs[] = { + JS_CGETSET_MAGIC_DEF_ENUM("nb_filters", jsfs_prop_get, NULL, JSFS_NB_FILTERS), + JS_CGETSET_MAGIC_DEF_ENUM("last_task", jsfs_prop_get, NULL, JSFS_LAST_TASK), + JS_CGETSET_MAGIC_DEF("http_max_bitrate", jsfs_prop_get, jsfs_prop_set, JSFS_HTTP_MAX_RATE), + JS_CGETSET_MAGIC_DEF("http_bitrate", jsfs_prop_get, NULL, JSFS_HTTP_RATE), + JS_CGETSET_MAGIC_DEF("rmt_sampling", jsfs_prop_get, jsfs_prop_set, JSFS_RMT_SAMPLING), + + JS_CFUNC_DEF("post_task", 0, jsfs_post_task), + JS_CFUNC_DEF("abort", 0, jsfs_abort), + JS_CFUNC_DEF("get_filter", 0, jsfs_get_filter), + JS_CFUNC_DEF("lock_filters", 0, jsfs_lock_filters), + JS_CFUNC_DEF("rmt_send", 0, jsfs_rmt_send), + JS_CFUNC_DEF("set_rmt_fun", 0, jsfs_set_rmt_fun), + JS_CFUNC_DEF("set_new_filter_fun", 0, jsfs_set_new_filter_fun), + JS_CFUNC_DEF("set_del_filter_fun", 0, jsfs_set_del_filter_fun), + JS_CFUNC_DEF("set_event_fun", 0, jsfs_set_event_fun), + JS_CFUNC_DEF("add_filter", 0, jsfs_add_filter), + JS_CFUNC_DEF("fire_event", 0, jsfs_fire_event), + JS_CFUNC_DEF("reporting", 0, jsfs_reporting), + +}; + +GF_Err gf_fs_load_js_api(JSContext *c, GF_FilterSession *fs) +{ + JSValue fs_obj; + JSRuntime *rt; + JSValue global_obj; + + if (fs->js_ctx) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[JSFS] FilterSession API already loaded by another script, cannot load twice\n")); + return GF_NOT_SUPPORTED; + } + + rt = JS_GetRuntime(c); + global_obj = JS_GetGlobalObject(c); + + js_load_constants(c, global_obj); + if (!fs->jstasks) { + fs->jstasks = gf_list_new(); + if (!fs->jstasks) return GF_OUT_OF_MEM; + } + + + //initialize filter class and create a single filter object in global scope + JS_NewClassID(&fs_class_id); + JS_NewClass(rt, fs_class_id, &fs_class); + + JS_NewClassID(&fs_f_class_id); + JS_NewClass(rt, fs_f_class_id, &fs_f_class); + + + fs_obj = JS_NewObjectClass(c, fs_class_id); + JS_SetPropertyFunctionList(c, fs_obj, fs_funcs, countof(fs_funcs)); + JS_SetOpaque(fs_obj, fs); + JS_SetPropertyStr(c, global_obj, "session", fs_obj); + + JS_FreeValue(c, global_obj); + return GF_OK; +} + +GF_EXPORT +GF_Err gf_fs_load_script(GF_FilterSession *fs, const char *jsfile) +{ + JSValue global_obj; + u8 *buf; + u32 buf_len; + u32 flags = JS_EVAL_TYPE_GLOBAL; + JSValue ret; + JSContext *ctx; + + + if (!fs) return GF_BAD_PARAM; + if (fs->js_ctx) return GF_NOT_SUPPORTED; + +#ifdef GPAC_HAS_QJS + ctx = gf_js_create_context(); + if (!ctx) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[JSF] Failed to load QuickJS context\n")); + return GF_IO_ERR; + } + JS_SetContextOpaque(ctx, fs); + + global_obj = JS_GetGlobalObject(ctx); + gf_fs_load_js_api(ctx, fs); + fs->js_ctx = ctx; + + JS_SetPropertyStr(fs->js_ctx, global_obj, "_gpac_log_name", JS_NewString(fs->js_ctx, gf_file_basename(jsfile) ) ); + JS_FreeValue(fs->js_ctx, global_obj); + + + //load script + GF_Err e = gf_file_load_data(jsfile, &buf, &buf_len); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[JSF] Error loading script file %s: %s\n", jsfile, gf_error_to_string(e) )); + return e; + } + + if (!gf_opts_get_bool("core", "no-js-mods") && JS_DetectModule((char *)buf, buf_len)) { + //init modules + qjs_module_init_gpaccore(fs->js_ctx); + qjs_module_init_xhr(fs->js_ctx); + qjs_module_init_evg(fs->js_ctx); + qjs_module_init_storage(fs->js_ctx); + qjs_module_init_webgl(fs->js_ctx); + flags = JS_EVAL_TYPE_MODULE; + } + + ret = JS_Eval(fs->js_ctx, (char *)buf, buf_len, jsfile, flags); + gf_free(buf); + + if (JS_IsException(ret)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[JSF] Error loading script %s\n", jsfile)); + js_dump_error(fs->js_ctx); + JS_FreeValue(fs->js_ctx, ret); + return GF_BAD_PARAM; + } + JS_FreeValue(fs->js_ctx, ret); + return GF_OK; +#else + return GF_NOT_SUPPORTED; +#endif + +} + +void gf_fs_unload_script(GF_FilterSession *fs, void *js_ctx) +{ + u32 i, count=gf_list_count(fs->jstasks); + for (i=0; ijstasks, i); + if (js_ctx && (task->ctx != js_ctx)) + continue; + JS_FreeValue(task->ctx, task->fun); + JS_FreeValue(task->ctx, task->_obj); + gf_free(task); + gf_list_rem(fs->jstasks, i); + i--; + count--; + } + if (fs->js_ctx) { + gf_js_delete_context(fs->js_ctx); + fs->js_ctx = NULL; + } + if (!js_ctx || !gf_list_count(fs->jstasks)) { + gf_list_del(fs->jstasks); + fs->jstasks = NULL; + } +} + +#else +GF_EXPORT +GF_Err gf_fs_load_script(GF_FilterSession *fs, const char *jsfile) +{ + return GF_NOT_SUPPORTED; +} +void gf_fs_unload_script(GF_FilterSession *fs, void *js_ctx) +{ + +} + +#endif + + + diff --git a/src/filters/base_filter_example.c b/src/filters/base_filter_example.c new file mode 100644 index 0000000..10799b6 --- /dev/null +++ b/src/filters/base_filter_example.c @@ -0,0 +1,168 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017 + * All rights reserved + * + * This file is part of GPAC / filters sub-project + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include + + +typedef struct +{ + u32 opt1; + Bool opt2; + + GF_FilterPid *src_pid; + GF_FilterPid *dst_pid; +} GF_BaseFilterExample; + +static void example_filter_finalize(GF_Filter *filter) +{ + //peform any finalyze routine needed, including potential free in the filter context + //if not needed, set the filter_finalize to NULL +} +static GF_Err example_filter_process(GF_Filter *filter) +{ + char *data_dst; + const char *data_src; + u32 size; + + GF_FilterPacket *pck_dst; + GF_BaseFilterExample *stack = (GF_BaseFilterExample *) gf_filter_get_udta(filter); + + GF_FilterPacket *pck = gf_filter_pid_get_packet(stack->src_pid); + if (!pck) return GF_OK; + data_src = gf_filter_pck_get_data(pck, &size); + + //produce output packet using memory allocation + pck_dst = gf_filter_pck_new_alloc(stack->dst_pid, size, &data_dst); + if (!pck_dst) return GF_OUT_OF_MEM; + memcpy(data_dst, data_src, size); + + //no need to adjust data framing +// gf_filter_pck_set_framing(pck_dst, GF_TRUE, GF_TRUE); + + //copy over src props to dst + gf_filter_pck_merge_properties(pck, pck_dst); + gf_filter_pck_send(pck_dst); + + gf_filter_pid_drop_packet(stack->src_pid); + return GF_OK; + +} + +static GF_Err example_filter_config_input(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *format; + GF_PropertyValue p; + GF_BaseFilterExample *stack = (GF_BaseFilterExample *) gf_filter_get_udta(filter); + + if (stack->src_pid==pid) { + //disconnect of src pid (not yet supported) + if (is_remove) { + } + //update of caps, check everything is fine + else { + } + return GF_OK; + } + //check input pid properties we are interested in + format = gf_filter_pid_get_property(pid, GF_4CC('c','u','s','t') ); + if (!format || !format->value.string || strcmp(format->value.string, "myformat")) { + return GF_NOT_SUPPORTED; + } + + + //setup output (if we are a filter not a sink) + + stack->dst_pid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(stack->dst_pid, stack->src_pid); + p.type = GF_PROP_UINT; + p.value.uint = 10; + gf_filter_pid_set_property(stack->dst_pid, GF_4CC('c','u','s','2'), &p); + + //set framing mode if needed - by default all PIDs require complete data blocks as inputs +// gf_filter_pid_set_framing_mode(pidctx->src_pid, GF_TRUE); + + return GF_OK; +} + +static GF_Err example_filter_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *arg_val) +{ + return GF_OK; +} + +GF_Err example_filter_initialize(GF_Filter *filter) +{ + GF_BaseFilterExample *stack = gf_filter_get_udta(filter); + if (stack->opt2) { + //do something based on options + + } + //if you filter is a source, this is the right place to start declaring output PIDs, such as above + + return GF_OK; +} + +#define OFFS(_n) #_n, offsetof(GF_BaseFilterExample, _n) +static const GF_FilterArgs ExampleFilterArgs[] = +{ + //example uint option using enum, result parsed ranges from 0(=v1) to 2(=v3) + { OFFS(opt1), "Example option 1", GF_PROP_UINT, "v1", "v1|v2|v3", GF_FALSE}, + { OFFS(opt2), "Example option 2", GF_PROP_BOOL, "false", NULL, GF_FALSE}, + { NULL } +}; + +static const GF_FilterCapability ExampleFilterInputs[] = +{ + { .cap_string="cust", .val=PROP_NAME("myformat") }, + { NULL } +}; + +static const GF_FilterCapability ExampleFilterOutputs[] = +{ + {GF_4CC('c','u','s','t'), PROP_UINT(10) }, + { NULL } +}; + +const GF_FilterRegister ExampleFilterRegister = { + .name = "ExampleFilter", + .description = "Example Test Filter, not used", + .private_size = sizeof(GF_BaseFilterExample), + .input_caps = ExampleFilterInputs, + .output_caps = ExampleFilterOutputs, + .args = ExampleFilterArgs, + .initialize = example_filter_initialize, + .finalize = example_filter_finalize, + .process = example_filter_process, + .configure_pid = example_filter_config_input, + .update_arg = example_filter_update_arg +}; + +const GF_FilterRegister *ex_filter_register(GF_FilterSession *session, Bool load_meta_filters) +{ + return &ExampleFilterRegister; +} + + + diff --git a/src/filters/bsrw.c b/src/filters/bsrw.c new file mode 100644 index 0000000..0ee887a --- /dev/null +++ b/src/filters/bsrw.c @@ -0,0 +1,642 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2020 + * All rights reserved + * + * This file is part of GPAC / compressed bitstream metadata rewrite filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +typedef struct _bsrw_pid_ctx BSRWPid; +typedef struct _bsrw_ctx GF_BSRWCtx; + +struct _bsrw_pid_ctx +{ + GF_FilterPid *ipid, *opid; + u32 codec_id; + Bool reconfigure; + s32 clrp, txchar, mxcoef; + GF_Err (*rewrite_pid_config)(GF_BSRWCtx *ctx, BSRWPid *pctx); + GF_Err (*rewrite_packet)(GF_BSRWCtx *ctx, BSRWPid *pctx, GF_FilterPacket *pck); + + s32 prev_clrp, prev_txchar, prev_mxcoef, prev_sar; + + u32 nalu_size_length; +}; + +struct _bsrw_ctx +{ + GF_Fraction sar; + s32 m4vpl, prof, lev, pcomp, pidc, pspace, gpcflags; + char *clrp, *txchar, *mxcoef; + Bool remsei; + + + GF_List *pids; + Bool reconfigure; +}; + +static GF_Err none_rewrite_packet(GF_BSRWCtx *ctx, BSRWPid *pctx, GF_FilterPacket *pck) +{ + return gf_filter_pck_forward(pck, pctx->opid); +} + + +static GF_Err m4v_rewrite_pid_config(GF_BSRWCtx *ctx, BSRWPid *pctx) +{ + GF_Err e; + u8 *dsi; + u32 dsi_size; + const GF_PropertyValue *prop; + + prop = gf_filter_pid_get_property(pctx->ipid, GF_PROP_PID_DECODER_CONFIG); + if (!prop) return GF_OK; + + pctx->reconfigure = GF_FALSE; + + + dsi_size = prop->value.data.size; + dsi = gf_malloc(sizeof(u8) * dsi_size); + memcpy(dsi, prop->value.data.ptr, sizeof(u8) * dsi_size); + + if (ctx->sar.num && ctx->sar.den) { + e = gf_m4v_rewrite_par(&dsi, &dsi_size, ctx->sar.num, ctx->sar.den); + if (e) { + gf_free(dsi); + return e; + } + gf_filter_pid_set_property(pctx->opid, GF_PROP_PID_SAR, &PROP_FRAC(ctx->sar) ); + } + if (ctx->m4vpl>=0) { + gf_m4v_rewrite_pl(&dsi, &dsi_size, (u32) ctx->m4vpl); + gf_filter_pid_set_property(pctx->opid, GF_PROP_PID_PROFILE_LEVEL, &PROP_UINT(ctx->m4vpl) ); + } + + gf_filter_pid_set_property(pctx->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_size) ); + + return GF_OK; +} + +static GF_Err avc_rewrite_packet(GF_BSRWCtx *ctx, BSRWPid *pctx, GF_FilterPacket *pck) +{ + u32 size, pck_size, final_size; + GF_FilterPacket *dst; + u8 *output; + const u8 *data = gf_filter_pck_get_data(pck, &pck_size); + if (!data) + return gf_filter_pck_forward(pck, pctx->opid); + + final_size = 0; + size=0; + while (sizenalu_size_length; + u32 nal_size = 0; + while (nal_hdr) { + nal_size |= data[size]; + size++; + nal_hdr--; + if (!nal_hdr) break; + nal_size<<=8; + } + nal_type = data[size]; + nal_type = nal_type & 0x1F; + if (nal_type != GF_AVC_NALU_SEI) { + final_size += nal_size+pctx->nalu_size_length; + } + size += nal_size; + } + if (final_size == pck_size) + return gf_filter_pck_forward(pck, pctx->opid); + + dst = gf_filter_pck_new_alloc(pctx->opid, final_size, &output); + gf_filter_pck_merge_properties(pck, dst); + + size=0; + while (sizenalu_size_length; + u32 nal_size = 0; + while (nal_hdr) { + nal_size |= data[size]; + size++; + nal_hdr--; + if (!nal_hdr) break; + nal_size<<=8; + } + nal_type = data[size]; + nal_type = nal_type & 0x1F; + if (nal_type == GF_AVC_NALU_SEI) { + size += nal_size; + continue; + } + memcpy(output, &data[size-pctx->nalu_size_length], pctx->nalu_size_length+nal_size); + output += pctx->nalu_size_length+nal_size; + size += nal_size; + } + return gf_filter_pck_send(dst); +} + +static GF_Err avc_rewrite_pid_config(GF_BSRWCtx *ctx, BSRWPid *pctx) +{ + GF_AVCConfig *avcc; + GF_Err e = GF_OK; + u8 *dsi; + u32 dsi_size; + const GF_PropertyValue *prop; + + prop = gf_filter_pid_get_property(pctx->ipid, GF_PROP_PID_DECODER_CONFIG); + if (!prop) return GF_OK; + pctx->reconfigure = GF_FALSE; + + avcc = gf_odf_avc_cfg_read(prop->value.data.ptr, prop->value.data.size); + if (ctx->sar.num && ctx->sar.den) { + e = gf_media_avc_change_par(avcc, ctx->sar.num, ctx->sar.den); + if (!e) + gf_filter_pid_set_property(pctx->opid, GF_PROP_PID_SAR, &PROP_FRAC(ctx->sar) ); + } + + if ((ctx->lev>=0) || (ctx->prof>=0) || (ctx->pcomp>=0)) { + u32 i, count = gf_list_count(avcc->sequenceParameterSets); + for (i=0; isequenceParameterSets, i); + //first byte is nalu header, then profile_idc (8bits), prof_comp (8buts) a,d level_idc (8bits) + if (ctx->prof>=0) { + sps->data[1] = (u8) ctx->prof; + } + if (ctx->pcomp>=0) { + sps->data[2] = (u8) ctx->pcomp; + } + if (ctx->lev>=0) { + sps->data[3] = (u8) ctx->lev; + } + } + if (ctx->lev>=0) avcc->AVCLevelIndication = ctx->lev; + if (ctx->prof>=0) avcc->AVCProfileIndication = ctx->prof; + if (ctx->pcomp>=0) avcc->profile_compatibility = ctx->pcomp; + } + + gf_odf_avc_cfg_write(avcc, &dsi, &dsi_size); + pctx->nalu_size_length = avcc->nal_unit_size; + + gf_odf_avc_cfg_del(avcc); + if (e) return e; + + gf_filter_pid_set_property(pctx->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_size) ); + + if (ctx->remsei) { + pctx->rewrite_packet = avc_rewrite_packet; + } else { + pctx->rewrite_packet = none_rewrite_packet; + } + return GF_OK; +} + +#ifndef GPAC_DISABLE_HEVC + +static GF_Err hevc_rewrite_packet(GF_BSRWCtx *ctx, BSRWPid *pctx, GF_FilterPacket *pck) +{ + u32 size, pck_size, final_size; + GF_FilterPacket *dst; + u8 *output; + const u8 *data = gf_filter_pck_get_data(pck, &pck_size); + if (!data) + return gf_filter_pck_forward(pck, pctx->opid); + + final_size = 0; + size=0; + while (sizenalu_size_length; + u32 nal_size = 0; + while (nal_hdr) { + nal_size |= data[size]; + size++; + nal_hdr--; + if (!nal_hdr) break; + nal_size<<=8; + } + nal_type = data[size]; + nal_type = (nal_type & 0x7E) >> 1; + if ((nal_type != GF_HEVC_NALU_SEI_PREFIX) && (nal_type != GF_HEVC_NALU_SEI_SUFFIX) ) { + final_size += nal_size+pctx->nalu_size_length; + } + size += nal_size; + } + if (final_size == pck_size) + return gf_filter_pck_forward(pck, pctx->opid); + + dst = gf_filter_pck_new_alloc(pctx->opid, final_size, &output); + gf_filter_pck_merge_properties(pck, dst); + + size=0; + while (sizenalu_size_length; + u32 nal_size = 0; + while (nal_hdr) { + nal_size |= data[size]; + size++; + nal_hdr--; + if (!nal_hdr) break; + nal_size<<=8; + } + nal_type = data[size]; + nal_type = (nal_type & 0x7E) >> 1; + if ((nal_type == GF_HEVC_NALU_SEI_PREFIX) || (nal_type == GF_HEVC_NALU_SEI_SUFFIX) ) { + size += nal_size; + continue; + } + memcpy(output, &data[size-pctx->nalu_size_length], pctx->nalu_size_length+nal_size); + output += pctx->nalu_size_length+nal_size; + size += nal_size; + } + return gf_filter_pck_send(dst); +} + +static GF_Err hevc_rewrite_pid_config(GF_BSRWCtx *ctx, BSRWPid *pctx) +{ + GF_HEVCConfig *hvcc; + GF_Err e = GF_OK; + u8 *dsi; + u32 dsi_size; + const GF_PropertyValue *prop; + + prop = gf_filter_pid_get_property(pctx->ipid, GF_PROP_PID_DECODER_CONFIG); + if (!prop) return GF_OK; + pctx->reconfigure = GF_FALSE; + + hvcc = gf_odf_hevc_cfg_read(prop->value.data.ptr, prop->value.data.size, (pctx->codec_id==GF_CODECID_LHVC) ? GF_TRUE : GF_FALSE); + if (ctx->sar.num && ctx->sar.den) { + e = gf_media_hevc_change_par(hvcc, ctx->sar.num, ctx->sar.den); + if (!e) + gf_filter_pid_set_property(pctx->opid, GF_PROP_PID_SAR, &PROP_FRAC(ctx->sar) ); + } + if (ctx->pidc>=0) hvcc->profile_idc = ctx->pidc; + if (ctx->pspace>=0) hvcc->profile_space = ctx->pspace; + if (ctx->gpcflags>=0) hvcc->general_profile_compatibility_flags = ctx->gpcflags; + + + gf_odf_hevc_cfg_write(hvcc, &dsi, &dsi_size); + pctx->nalu_size_length = hvcc->nal_unit_size; + gf_odf_hevc_cfg_del(hvcc); + if (e) return e; + + gf_filter_pid_set_property(pctx->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_size) ); + + if (ctx->remsei) { + pctx->rewrite_packet = hevc_rewrite_packet; + } else { + pctx->rewrite_packet = none_rewrite_packet; + } + return GF_OK; +} +#endif // GPAC_DISABLE_HEVC + +static GF_Err none_rewrite_pid_config(GF_BSRWCtx *ctx, BSRWPid *pctx) +{ + pctx->reconfigure = GF_FALSE; + return GF_OK; +} + +static GF_Err rewrite_pid_config(GF_BSRWCtx *ctx, BSRWPid *pctx) +{ + GF_Err e = pctx->rewrite_pid_config(ctx, pctx); + if (e) return e; + pctx->clrp = pctx->txchar = pctx->mxcoef = -1; + pctx->prev_clrp = pctx->prev_txchar = pctx->prev_mxcoef = pctx->prev_sar = -1; + + if (ctx->clrp) { + if (!stricmp(ctx->clrp, "BT.709") || !stricmp(ctx->clrp, "BT709")) pctx->clrp = 1; + else if (!stricmp(ctx->clrp, "BT.601-625") || !stricmp(ctx->clrp, "BT601-625")) pctx->clrp = 5; + else if (!stricmp(ctx->clrp, "BT.601-525") || !stricmp(ctx->clrp, "BT601-525")) pctx->clrp = 6; + else if (!stricmp(ctx->clrp, "BT.2020") || !stricmp(ctx->clrp, "BT2020")) pctx->clrp = 9; + else if (!stricmp(ctx->clrp, "P3")) pctx->clrp = 11; + else if (!stricmp(ctx->clrp, "P3-D65")) pctx->clrp = 12; + else { + char szCoef[100]; + pctx->clrp = atoi(ctx->clrp); + sprintf(szCoef, "%u", pctx->clrp); + if (stricmp(szCoef, ctx->clrp)) + pctx->clrp = -1; + } + } + if (ctx->txchar) { + if (!strcmp(ctx->txchar, "BT-709") || !strcmp(ctx->txchar, "BT709")) pctx->txchar=1; + else if (!strcmp(ctx->txchar, "ST-2084") || !strcmp(ctx->txchar, "ST2084")) pctx->txchar=16; + else if (!strcmp(ctx->txchar, "STD-B67") || !strcmp(ctx->txchar, "STDB67")) pctx->txchar=18; + else { + char szTxChar[100]; + pctx->txchar = atoi(ctx->txchar); + sprintf(szTxChar, "%u", pctx->txchar); + if (stricmp(szTxChar, ctx->txchar)) + pctx->txchar = -1; + } + } + if (ctx->mxcoef) { + if (!strcmp(ctx->mxcoef, "BT.709") || !strcmp(ctx->mxcoef, "BT709")) pctx->mxcoef = 1; + else if (!strcmp(ctx->mxcoef, "BT.601") || !strcmp(ctx->mxcoef, "BT601")) pctx->mxcoef = 6; + else if (!strcmp(ctx->mxcoef, "BT.2020") || !strcmp(ctx->mxcoef, "BT2020")) pctx->mxcoef = 9; + else { + char szMx[100]; + pctx->mxcoef = atoi(ctx->mxcoef); + sprintf(szMx, "%u", pctx->mxcoef); + if (stricmp(szMx, ctx->mxcoef)) + pctx->mxcoef = -1; + } + } + return GF_OK; +} + + +static GF_Err prores_rewrite_packet(GF_BSRWCtx *ctx, BSRWPid *pctx, GF_FilterPacket *pck) +{ + u8 *output; + GF_FilterPacket *dst_pck = gf_filter_pck_new_clone(pctx->opid, pck, &output); + + //starting at offset 20 in frame: + /* + prores_frame->chroma_format = gf_bs_read_int(bs, 2); + gf_bs_read_int(bs, 2); + prores_frame->interlaced_mode = gf_bs_read_int(bs, 2); + gf_bs_read_int(bs, 2); + prores_frame->aspect_ratio_information = gf_bs_read_int(bs, 4); + prores_frame->framerate_code = gf_bs_read_int(bs, 4); + prores_frame->color_primaries = gf_bs_read_u8(bs); + prores_frame->transfer_characteristics = gf_bs_read_u8(bs); + prores_frame->matrix_coefficients = gf_bs_read_u8(bs); + gf_bs_read_int(bs, 4); + prores_frame->alpha_channel_type = gf_bs_read_int(bs, 4); + */ + + if (ctx->sar.num && ctx->sar.den) { + u32 framerate_code = output[21] & 0xF; + u32 new_ar = 0; + if (ctx->sar.num==ctx->sar.den) new_ar = 1; + else if (ctx->sar.num * 3 == ctx->sar.den * 4) new_ar = 2; + else if (ctx->sar.num * 9 == ctx->sar.den * 16) new_ar = 3; + else { + if (pctx->prev_sar != new_ar) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[BSRW] Aspect ratio %d/%d not registered in ProRes, using 0 (unknown)\n", ctx->sar.num, ctx->sar.den)); + } + } + new_ar <<= 4; + framerate_code |= new_ar; + output[21] = (u8) framerate_code; + if (pctx->prev_sar != new_ar) { + gf_filter_pid_set_property(pctx->opid, GF_PROP_PID_SAR, &PROP_FRAC(ctx->sar) ); + pctx->prev_sar = new_ar; + } + } + if (pctx->clrp>=0) { + output[22] = (u8) pctx->clrp; + if (pctx->prev_clrp != pctx->clrp) { + gf_filter_pid_set_property(pctx->opid, GF_PROP_PID_COLR_PRIMARIES, &PROP_UINT(pctx->clrp) ); + pctx->prev_clrp = pctx->clrp; + } + } + if (pctx->txchar>=0) { + output[23] = (u8) pctx->txchar; + if (pctx->prev_txchar != pctx->txchar) { + gf_filter_pid_set_property(pctx->opid, GF_PROP_PID_COLR_TRANSFER, &PROP_UINT(pctx->txchar) ); + pctx->prev_txchar = pctx->txchar; + } + } + if (pctx->mxcoef>=0) { + output[24] = (u8) pctx->mxcoef; + if (pctx->prev_mxcoef != pctx->mxcoef) { + gf_filter_pid_set_property(pctx->opid, GF_PROP_PID_COLR_MX, &PROP_UINT(pctx->mxcoef) ); + pctx->prev_mxcoef = pctx->mxcoef; + } + } + return gf_filter_pck_send(dst_pck); +} + + +static GF_Err bsrw_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *prop; + GF_BSRWCtx *ctx = (GF_BSRWCtx *) gf_filter_get_udta(filter); + BSRWPid *pctx = gf_filter_pid_get_udta(pid); + + //disconnect of src pid (not yet supported) + if (is_remove) { + if (pctx) { + //one in one out, this is simple + gf_filter_pid_remove(pctx->opid); + gf_filter_pid_set_udta(pid, NULL); + gf_list_del_item(ctx->pids, pctx); + gf_free(pctx); + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + if (!pctx) { + GF_SAFEALLOC(pctx, BSRWPid); + if (!pctx) return GF_OUT_OF_MEM; + pctx->ipid = pid; + gf_filter_pid_set_udta(pid, pctx); + pctx->rewrite_pid_config = none_rewrite_pid_config; + pctx->rewrite_packet = none_rewrite_packet; + gf_list_add(ctx->pids, pctx); + pctx->opid = gf_filter_pid_new(filter); + if (!pctx->opid) return GF_OUT_OF_MEM; + } + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + assert(prop); + switch (prop->value.uint) { + case GF_CODECID_AVC: + case GF_CODECID_SVC: + case GF_CODECID_MVC: + pctx->rewrite_pid_config = avc_rewrite_pid_config; + break; +#ifndef GPAC_DISABLE_HEVC + case GF_CODECID_HEVC: + case GF_CODECID_HEVC_TILES: + case GF_CODECID_LHVC: + pctx->rewrite_pid_config = hevc_rewrite_pid_config; + break; +#endif + case GF_CODECID_MPEG4_PART2: + pctx->rewrite_pid_config = m4v_rewrite_pid_config; + break; + case GF_CODECID_AP4H: + case GF_CODECID_AP4X: + case GF_CODECID_APCH: + case GF_CODECID_APCN: + case GF_CODECID_APCO: + case GF_CODECID_APCS: + pctx->rewrite_pid_config = none_rewrite_pid_config; + pctx->rewrite_packet = prores_rewrite_packet; + break; + + default: + pctx->rewrite_pid_config = none_rewrite_pid_config; + pctx->rewrite_packet = none_rewrite_packet; + break; + } + + gf_filter_pid_copy_properties(pctx->opid, pctx->ipid); + pctx->codec_id = prop->value.uint; + pctx->reconfigure = GF_TRUE; + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + return GF_OK; +} + + +static GF_Err bsrw_process(GF_Filter *filter) +{ + u32 i, count; + GF_BSRWCtx *ctx = (GF_BSRWCtx *) gf_filter_get_udta(filter); + + count = gf_filter_get_ipid_count(filter); + for (i=0; ireconfigure) + pctx->reconfigure = GF_TRUE; + + if (pctx->reconfigure) { + GF_Err e = rewrite_pid_config(ctx, pctx); + if (e) return e; + } + pck = gf_filter_pid_get_packet(pid); + if (!pck) { + if (gf_filter_pid_is_eos(pctx->ipid)) + gf_filter_pid_set_eos(pctx->opid); + continue; + } + pctx->rewrite_packet(ctx, pctx, pck); + gf_filter_pid_drop_packet(pid); + } + ctx->reconfigure = GF_FALSE; + return GF_OK; +} + +static GF_Err bsrw_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *new_val) +{ + GF_BSRWCtx *ctx = (GF_BSRWCtx *) gf_filter_get_udta(filter); + ctx->reconfigure = GF_TRUE; + return GF_OK; +} + +static GF_Err bsrw_initialize(GF_Filter *filter) +{ + GF_BSRWCtx *ctx = (GF_BSRWCtx *) gf_filter_get_udta(filter); + ctx->pids = gf_list_new(); + +#ifdef GPAC_ENABLE_COVERAGE + bsrw_update_arg(filter, NULL, NULL); +#endif + return GF_OK; +} +static void bsrw_finalize(GF_Filter *filter) +{ + GF_BSRWCtx *ctx = (GF_BSRWCtx *) gf_filter_get_udta(filter); + while (gf_list_count(ctx->pids)) { + BSRWPid *pctx = gf_list_pop_back(ctx->pids); + gf_free(pctx); + } + gf_list_del(ctx->pids); +} + +#define OFFS(_n) #_n, offsetof(GF_BSRWCtx, _n) +static const GF_FilterArgs BSRWArgs[] = +{ + { OFFS(sar), "aspect ratio to rewrite", GF_PROP_FRACTION, "0/0", NULL, GF_FS_ARG_UPDATE}, + { OFFS(m4vpl), "set ProfileLevel for MPEG-4 video part two", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_UPDATE}, + { OFFS(clrp), "color primaries according to ISO/IEC 23001-8 / 23091-2. Value can be the integer value or (case insensitive) `BT709`, `BT601-625`, `BT601-525`, `BT2020`, `P3` or `P3-D65`", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_UPDATE}, + { OFFS(txchar), "transfer characteristics according to ISO/IEC 23001-8 / 23091-2. Value can be the integer value or (case insensitive) `BT709`, `ST2084` or `STDB67`", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_UPDATE}, + { OFFS(mxcoef), "matrix coeficients according to ISO/IEC 23001-8 / 23091-2. Value can be the integer value or (case insensitive) `BT709`, `BT601` or `BT2020`", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_UPDATE}, + + { OFFS(prof), "profile indication for AVC|H264", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_UPDATE}, + { OFFS(lev), "level indication for AVC|H264", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_UPDATE}, + { OFFS(pcomp), "profile compatibility for AVC|H264", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_UPDATE}, + { OFFS(pidc), "profile IDC for HEVC", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_UPDATE}, + { OFFS(pspace), "profile space for HEVC", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_UPDATE}, + { OFFS(gpcflags), "general compatibility flags for HEVC", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_UPDATE}, + + { OFFS(remsei), "remove SEI messages from bitstream", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE}, + + {0} +}; + +static const GF_FilterCapability BSRWCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_STATIC ,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED|GF_CAPFLAG_STATIC , GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_AVC), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_SVC), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_MVC), + {0}, + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_HEVC), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_HEVC_TILES), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_LHVC), + {0}, + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG4_PART2), + {0}, + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_AP4H), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_AP4X), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_APCH), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_APCN), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_APCO), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_APCS), +}; + +GF_FilterRegister BSRWRegister = { + .name = "bsrw", + GF_FS_SET_DESCRIPTION("Compressed bitstream rewriter") + GF_FS_SET_HELP("This filter rewrites some metadata of various bitstream formats.\n" + "The filter can currently modify the following properties in the bitstream:\n" + "- MPEG-4 Visual: aspect ratio and profile/level\n" + "- AVC|H264: aspect ratio, profile, level, profile compatibility\n" + "- HEVC: aspect ratio\n" + "- ProRes: aspect ratio, color primaries, transfer characteristics and matrix coefficients\n" + " \n" + "The filter can currently modify the following properties in the stream configuration but not in the bitstream:\n" + "- HEVC: profile IDC, profile space, general compatibility flags\n" + ) + .private_size = sizeof(GF_BSRWCtx), + .max_extra_pids = 0xFFFFFFFF, + .flags = GF_FS_REG_EXPLICIT_ONLY, + .args = BSRWArgs, + SETCAPS(BSRWCaps), + .initialize = bsrw_initialize, + .finalize = bsrw_finalize, + .configure_pid = bsrw_configure_pid, + .process = bsrw_process, + .update_arg = bsrw_update_arg +}; + +const GF_FilterRegister *bsrw_register(GF_FilterSession *session) +{ + return &BSRWRegister; +} diff --git a/src/filters/compose.c b/src/filters/compose.c new file mode 100644 index 0000000..b360746 --- /dev/null +++ b/src/filters/compose.c @@ -0,0 +1,996 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / compositor filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +//to set caps in filter session, to cleanup! +#include "../filter_core/filter_session.h" + +#ifndef GPAC_DISABLE_PLAYER + +GF_Err compose_bifs_dec_config_input(GF_Scene *scene, GF_FilterPid *pid, u32 oti, Bool is_remove); +GF_Err compose_bifs_dec_process(GF_Scene *scene, GF_FilterPid *pid); + +GF_Err compose_odf_dec_config_input(GF_Scene *scene, GF_FilterPid *pid, u32 oti, Bool is_remove); +GF_Err compose_odf_dec_process(GF_Scene *scene, GF_FilterPid *pid); + +#define COMPOSITOR_MAGIC GF_4CC('c','o','m','p') +//a bit ugly, used by terminal (old APIs) +GF_Compositor *gf_sc_from_filter(GF_Filter *filter) +{ + GF_Compositor *ctx = (GF_Compositor *) gf_filter_get_udta(filter); + if (ctx->magic != COMPOSITOR_MAGIC) return NULL; + if (ctx->magic_ptr != ctx) return NULL; + + return ctx; +} + +static GF_Err compose_process(GF_Filter *filter) +{ + u32 i, nb_sys_streams_active; + s32 ms_until_next = 0; + Bool ret; + GF_Compositor *ctx = (GF_Compositor *) gf_filter_get_udta(filter); + if (!ctx) return GF_BAD_PARAM; + + if (ctx->check_eos_state == 2) + return GF_EOS; + + ctx->last_error = GF_OK; + if (ctx->reload_config) { + ctx->reload_config = GF_FALSE; + gf_sc_reload_config(ctx); + } + + nb_sys_streams_active = gf_list_count(ctx->systems_pids); + for (i=0; isystems_pids, i); + GF_ObjectManager *odm = gf_filter_pid_get_udta(pid); + + assert (odm); + + e = GF_OK; + pck = gf_filter_pid_get_packet(pid); + if (!pck && gf_filter_pid_is_eos(pid)) { + e = GF_EOS; + } + if (pck) + gf_filter_pid_drop_packet(pid); + + + if (e==GF_EOS) { + gf_list_rem(ctx->systems_pids, i); + i--; + nb_sys_streams_active--; + gf_odm_on_eos(odm, pid); + } + if (ctx->reload_scene_size) { + u32 w, h; + gf_sg_get_scene_size_info(ctx->root_scene->graph, &w, &h); + if ((ctx->scene_width!=w) || (ctx->scene_height!=h)) { + gf_sc_set_scene_size(ctx, w, h, GF_TRUE); + } + } + } + + ret = gf_sc_draw_frame(ctx, GF_FALSE, &ms_until_next); + + if (!ctx->player) { + Bool forced_eos = GF_FALSE; + /*remember to check for eos*/ + if (ctx->dur<0) { + if (ctx->frame_number >= (u32) -ctx->dur) + ctx->check_eos_state = 2; + } else if (ctx->dur>0) { + Double n = ctx->scene_sampled_clock; + n /= 1000; + if (n>=ctx->dur) + ctx->check_eos_state = 2; + else if (!ret && ctx->vfr && !ctx->check_eos_state && !nb_sys_streams_active && ctx->scene_sampled_clock && !ctx->validator_mode) { + ctx->check_eos_state = 1; + if (!ctx->validator_mode) + ctx->force_next_frame_redraw = GF_TRUE; + } + } else if (!ret && !ctx->check_eos_state && !nb_sys_streams_active) { + ctx->check_eos_state = 1; + } + if (ctx->check_eos_state == 1) { + ctx->last_check_pass++; + if (ctx->last_check_pass > 10000) { + ctx->check_eos_state = 2; + GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] Could not detect end of stream(s) in %d render pass, aborting\n", ctx->last_check_pass)); + forced_eos = GF_TRUE; + } + } else { + ctx->last_check_pass = 0; + } + + if ((ctx->check_eos_state==2) || (ctx->check_eos_state && gf_sc_check_end_of_scene(ctx, GF_TRUE))) { + u32 count; + ctx->force_next_frame_redraw = GF_FALSE; + count = gf_filter_get_ipid_count(ctx->filter); + if (ctx->root_scene) { + gf_filter_pid_set_eos(ctx->vout); + if (ctx->audio_renderer && ctx->audio_renderer->aout) + gf_filter_pid_set_eos(ctx->audio_renderer->aout); + } + //send stop + if (ctx->dur) { + for (i=0; ifilter, i); + if (!gf_filter_pid_is_eos(pid)) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_PLAY, pid); + gf_filter_pid_send_event(pid, &evt); + GF_FEVT_INIT(evt, GF_FEVT_STOP, pid); + gf_filter_pid_send_event(pid, &evt); + //and discard every incoming packet + gf_filter_pid_set_discard(pid, GF_TRUE); + } + } + } + return forced_eos ? GF_SERVICE_ERROR : GF_EOS; + } + ctx->check_eos_state = 0; + //always repost a process task since we maye have things to draw even though no new input + gf_filter_post_process_task(filter); + return ctx->last_error; + } + + + //to clean up,depending on whether we use a thread to poll user inputs, etc... + if ((u32) ms_until_next > 100) + ms_until_next = 100; + + //ask for real-time reschedule + gf_filter_ask_rt_reschedule(filter, ms_until_next ? ms_until_next*1000 : 1); + + return ctx->last_error; +} + +static void merge_properties(GF_Compositor *ctx, GF_FilterPid *pid, u32 mtype, GF_Scene *parent_scene) +{ + const GF_PropertyValue *p; + if (!ctx->vout) return; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_URL); + if (!p) return; + + if (mtype==GF_STREAM_SCENE) { + if (!parent_scene || !parent_scene->is_dynamic_scene) { + gf_filter_pid_set_property(ctx->vout, GF_PROP_PID_URL, p); + } + } else if (parent_scene && parent_scene->is_dynamic_scene) { + if (mtype==GF_STREAM_VISUAL) + gf_filter_pid_set_property(ctx->vout, GF_PROP_PID_URL, p); + } +} + +static GF_Err compose_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_ObjectManager *odm; + const GF_PropertyValue *prop; + u32 mtype, codecid; + u32 i, count; + GF_Scene *scene = NULL; + GF_Scene *top_scene = NULL; + GF_Compositor *ctx = (GF_Compositor *) gf_filter_get_udta(filter); + GF_FilterEvent evt; + Bool in_iod = GF_FALSE; + Bool was_dyn_scene = GF_FALSE; + if (is_remove) { + u32 ID=0; + odm = gf_filter_pid_get_udta(pid); + //already disconnected + if (!odm) return GF_OK; + ID = odm->ID; + scene = odm->parentscene; + if (scene && !scene->is_dynamic_scene) + scene = NULL; + //destroy the object + gf_odm_disconnect(odm, 2); + if (scene) { + if (scene->visual_url.OD_ID == ID) { + scene->visual_url.OD_ID = 0; + gf_scene_regenerate(scene); + } else if (scene->audio_url.OD_ID == ID) { + scene->audio_url.OD_ID = 0; + gf_scene_regenerate(scene); + } else if (scene->text_url.OD_ID == ID) { + scene->text_url.OD_ID = 0; + gf_scene_regenerate(scene); + } else if (scene->dims_url.OD_ID == ID) { + scene->dims_url.OD_ID = 0; + gf_scene_regenerate(scene); + } + } + return GF_OK; + } + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (!prop) return GF_NOT_SUPPORTED; + mtype = prop->value.uint; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!prop) return GF_NOT_SUPPORTED; + codecid = prop->value.uint; + + odm = gf_filter_pid_get_udta(pid); + if (odm) { + if (mtype==GF_STREAM_SCENE) { } + else if (mtype==GF_STREAM_OD) { } + //change of stream type for a given object, no use case yet + else { + if (odm->type != mtype) + return GF_NOT_SUPPORTED; + if (odm->mo) { + odm->mo->config_changed = GF_TRUE; + if ((odm->type == GF_STREAM_VISUAL) && odm->parentscene && odm->parentscene->is_dynamic_scene) { + gf_scene_force_size_to_video(odm->parentscene, odm->mo); + odm->mo->config_changed = GF_TRUE; + } + } + gf_odm_update_duration(odm, pid); + gf_odm_check_clock_mediatime(odm); + } + merge_properties(ctx, pid, mtype, odm->parentscene); + return GF_OK; + } + + //create a default scene + if (!ctx->root_scene) { + const char *service_url = "unknown"; + const GF_PropertyValue *p = gf_filter_pid_get_property(pid, GF_PROP_PID_URL); + if (p) service_url = p->value.string; + + ctx->root_scene = gf_scene_new(ctx, NULL); + ctx->root_scene->root_od = gf_odm_new(); + ctx->root_scene->root_od->scene_ns = gf_scene_ns_new(ctx->root_scene, ctx->root_scene->root_od, service_url, NULL); + ctx->root_scene->root_od->subscene = ctx->root_scene; + ctx->root_scene->root_od->scene_ns->nb_odm_users++; + switch (mtype) { + case GF_STREAM_SCENE: + case GF_STREAM_PRIVATE_SCENE: + case GF_STREAM_OD: + ctx->root_scene->is_dynamic_scene = GF_FALSE; + break; + default: + ctx->root_scene->is_dynamic_scene = GF_TRUE; + break; + } + + if (!ctx->player) + gf_filter_post_process_task(filter); + } + + //default scene is root one + scene = ctx->root_scene; + top_scene = ctx->root_scene; + + //browse all scene namespaces and figure out our parent scene + count = gf_list_count(top_scene->namespaces); + for (i=0; inamespaces, i); + if (!sns->source_filter) { + if (sns->connect_ack && sns->owner) { + scene = sns->owner->subscene ? sns->owner->subscene : sns->owner->parentscene; + break; + } + continue; + } + assert(sns->owner); + if (gf_filter_pid_is_filter_in_parents(pid, sns->source_filter)) { + if (!sns->owner->subscene && sns->owner->parentscene && (mtype!=GF_STREAM_OD) && (mtype!=GF_STREAM_SCENE)) { + u32 j; + for (j=0; jowner->parentscene->scene_objects); j++) { + GF_MediaObject *mo = gf_list_get(sns->owner->parentscene->scene_objects, j); + if (mo->OD_ID == GF_MEDIA_EXTERNAL_ID) continue; + if (mo->OD_ID != sns->owner->ID) continue; + + if (mo->type != GF_MEDIA_OBJECT_SCENE) continue; + //this is a pid from a subservice (inline) inserted through OD commands, create the subscene + sns->owner->subscene = gf_scene_new(NULL, sns->owner->parentscene); + sns->owner->subscene->root_od = sns->owner; + //scenes are by default dynamic + sns->owner->subscene->is_dynamic_scene = GF_TRUE; + sns->owner->mo = mo; + mo->odm = sns->owner; + break; + } + } + //we are attaching an inline, create the subscene if not done already + if (!sns->owner->subscene && ((mtype==GF_STREAM_OD) || (mtype==GF_STREAM_SCENE)) ) { + //ignore system PIDs from subservice - this is typically the case when playing a bt/xmt file + //created from a container (mp4) and still refering to that container for the media streams + if (sns->owner->ignore_sys) { + GF_FEVT_INIT(evt, GF_FEVT_PLAY, pid); + gf_filter_pid_send_event(pid, &evt); + GF_FEVT_INIT(evt, GF_FEVT_STOP, pid); + gf_filter_pid_send_event(pid, &evt); + return GF_OK; + } + + assert(sns->owner->parentscene); + sns->owner->subscene = gf_scene_new(ctx, sns->owner->parentscene); + sns->owner->subscene->root_od = sns->owner; + } + scene = sns->owner->subscene ? sns->owner->subscene : sns->owner->parentscene; + break; + } + } + assert(scene); + + GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Configuring PID %s\n", gf_stream_type_name(mtype))); + + was_dyn_scene = scene->is_dynamic_scene; + + //pure OCR streams are handled by dispatching OCR on the PID(s) + if (codecid != GF_CODECID_RAW) + return GF_NOT_SUPPORTED; + + switch (mtype) { + case GF_STREAM_SCENE: + case GF_STREAM_OD: + //we have an MPEG-4 ESID defined for the PID, this is MPEG-4 systems + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ESID); + if (prop && scene->is_dynamic_scene) { + scene->is_dynamic_scene = GF_FALSE; + } + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_IN_IOD); + if (prop && prop->value.boolean) { + scene->is_dynamic_scene = GF_FALSE; + in_iod = GF_TRUE; + } + break; + } + + if ((mtype==GF_STREAM_OD) && !in_iod) return GF_NOT_SUPPORTED; + + //we inserted a root scene (bt/svg/...) after a pid (passthrough mode), we need to create a new namesapce for + //the scene and reassign the old namespace to the previously created ODM + if (!scene->root_od->parentscene && was_dyn_scene && (was_dyn_scene != scene->is_dynamic_scene)) { + GF_SceneNamespace *new_sns=NULL; + const char *service_url = "unknown"; + const GF_PropertyValue *p = gf_filter_pid_get_property(pid, GF_PROP_PID_URL); + if (p) service_url = p->value.string; + new_sns = gf_scene_ns_new(ctx->root_scene, ctx->root_scene->root_od, service_url, NULL); + + for (i=0; iresources); i++) { + GF_ObjectManager *anodm = gf_list_get(scene->resources, i); + + if (new_sns && (anodm->scene_ns == scene->root_od->scene_ns) && (scene->root_od->scene_ns->owner==scene->root_od)) { + scene->root_od->scene_ns->owner = anodm; + break; + } + } + scene->root_od->scene_ns = new_sns; + gf_sc_set_scene(ctx, NULL); + gf_sg_reset(scene->graph); + gf_sc_set_scene(ctx, scene->graph); + ctx->reload_scene_size = GF_TRUE; + } + + //setup object (clock) and playback requests + gf_scene_insert_pid(scene, scene->root_od->scene_ns, pid, in_iod); + + if (was_dyn_scene != scene->is_dynamic_scene) { + for (i=0; iresources); i++) { + GF_ObjectManager *anodm = gf_list_get(scene->resources, i); + if (anodm->mo) + anodm->flags |= GF_ODM_PASSTHROUGH; + } + } + + + //attach scene to input filters - may be true for dynamic scene (text rendering) and regular scenes + if ((mtype==GF_STREAM_OD) || (mtype==GF_STREAM_SCENE) || (mtype==GF_STREAM_TEXT) ) { + void gf_filter_pid_exec_event(GF_FilterPid *pid, GF_FilterEvent *evt); + + GF_FEVT_INIT(evt, GF_FEVT_ATTACH_SCENE, pid); + evt.attach_scene.object_manager = gf_filter_pid_get_udta(pid); + gf_filter_pid_exec_event(pid, &evt); + } + //scene is dynamic + if (scene->is_dynamic_scene) { + Bool reset = GF_FALSE; + char *sep = scene->root_od->scene_ns->url_frag; + if (sep && ( !strnicmp(sep, "LIVE360", 7) || !strnicmp(sep, "360", 3) || !strnicmp(sep, "VR", 2) ) ) { + if (scene->vr_type != 1) reset = GF_TRUE; + scene->vr_type = 1; + } + if (reset) + gf_sg_reset(scene->graph); + + gf_scene_regenerate(scene); + } + + merge_properties(ctx, pid, mtype, scene); + return GF_OK; +} + +#include "../compositor/visual_manager.h" + +static GF_Err compose_reconfig_output(GF_Filter *filter, GF_FilterPid *pid) +{ + const GF_PropertyValue *p; + u32 sr, o_fmt, nb_ch, afmt; + u64 cfg; + Bool needs_reconfigure = GF_FALSE; + GF_Compositor *ctx = (GF_Compositor *) gf_filter_get_udta(filter); + + if (ctx->vout == pid) { + u32 w, h; + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_PIXFMT); + if (p) { + u32 stride; +#ifndef GPAC_DISABLE_3D + if (ctx->scene && (ctx->hybrid_opengl || ctx->visual->type_3d)) { + switch (p->value.uint) { + case GF_PIXEL_RGBA: + case GF_PIXEL_RGB: + break; + default: + return GF_NOT_SUPPORTED; + } + } +#endif + ctx->opfmt = p->value.uint; + gf_filter_pid_set_property(ctx->vout, GF_PROP_PID_PIXFMT, &PROP_UINT(ctx->opfmt) ); + gf_pixel_get_size_info(ctx->opfmt, ctx->display_width, ctx->display_height, NULL, &stride, NULL, NULL, NULL); + gf_filter_pid_set_property(ctx->vout, GF_PROP_PID_STRIDE, &PROP_UINT(stride) ); + } + + w = h = 0; + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_WIDTH); + if (p) w = p->value.uint; + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_HEIGHT); + if (p) h = p->value.uint; + + if (w && h) { + ctx->osize.x = w; + ctx->osize.y = h; +/* gf_filter_pid_set_property(ctx->vout, GF_PROP_PID_WIDTH, &PROP_UINT(w) ); + gf_filter_pid_set_property(ctx->vout, GF_PROP_PID_HEIGHT, &PROP_UINT(h) ); +*/ } + return GF_OK; + } + + if (ctx->audio_renderer->aout == pid) { + + gf_mixer_get_config(ctx->audio_renderer->mixer, &sr, &nb_ch, &o_fmt, &cfg); + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_SAMPLE_RATE); + if (p && (p->value.uint != sr)) { + sr = p->value.uint; + needs_reconfigure = GF_TRUE; + } + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_NUM_CHANNELS); + if (p && (p->value.uint != nb_ch)) { + nb_ch = p->value.uint; + needs_reconfigure = GF_TRUE; + } + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_AUDIO_FORMAT); + if (p) afmt = p->value.uint; + else afmt = GF_AUDIO_FMT_S16; + + if (o_fmt != afmt) { + needs_reconfigure = GF_TRUE; + } + if (!needs_reconfigure) return GF_OK; + + GF_LOG(GF_LOG_INFO, GF_LOG_AUDIO, ("[Compositor] Audio output caps negotiated to %d Hz %d channels %s \n", sr, nb_ch, gf_audio_fmt_name(afmt) )); + gf_mixer_set_config(ctx->audio_renderer->mixer, sr, nb_ch, afmt, 0); + ctx->audio_renderer->need_reconfig = GF_TRUE; + return GF_OK; + } + return GF_NOT_SUPPORTED; +} + +static Bool compose_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + switch (evt->base.type) { + //event(s) we trigger on ourselves to go up the filter chain + case GF_FEVT_CAPS_CHANGE: + return GF_FALSE; + case GF_FEVT_CONNECT_FAIL: + { + GF_Compositor *ctx = (GF_Compositor *) gf_filter_get_udta(filter); + if (ctx->audio_renderer && (evt->base.on_pid == ctx->audio_renderer->aout)) + ctx->audio_renderer->non_rt_output = GF_FALSE; + } + return GF_FALSE; + case GF_FEVT_BUFFER_REQ: + return GF_TRUE; + + case GF_FEVT_INFO_UPDATE: + { + u32 bps=0; + u64 tot_size=0, down_size=0; + GF_ObjectManager *odm = gf_filter_pid_get_udta(evt->base.on_pid); + GF_PropertyEntry *pe=NULL; + GF_PropertyValue *p = (GF_PropertyValue *) gf_filter_pid_get_info(evt->base.on_pid, GF_PROP_PID_TIMESHIFT_STATE, &pe); + if (p && p->value.uint) { + GF_Event an_evt; + memset(&an_evt, 0, sizeof(GF_Event)); + GF_Compositor *ctx = (GF_Compositor *) gf_filter_get_udta(filter); + if (p->value.uint==1) { + an_evt.type = GF_EVENT_TIMESHIFT_UNDERRUN; + gf_sc_send_event(ctx, &an_evt); + } else if (p->value.uint==2) { + an_evt.type = GF_EVENT_TIMESHIFT_OVERFLOW; + gf_sc_send_event(ctx, &an_evt); + } + p->value.uint = 0; + } + + p = (GF_PropertyValue *) gf_filter_pid_get_info(evt->base.on_pid, GF_PROP_PID_DOWN_RATE, &pe); + if (p) bps = p->value.uint; + p = (GF_PropertyValue *) gf_filter_pid_get_info(evt->base.on_pid, GF_PROP_PID_DOWN_SIZE, &pe); + if (p) tot_size = p->value.longuint; + + p = (GF_PropertyValue *) gf_filter_pid_get_info(evt->base.on_pid, GF_PROP_PID_DOWN_BYTES, &pe); + if (p) down_size = p->value.longuint; + + if (bps && down_size && tot_size) { + odm = gf_filter_pid_get_udta(evt->base.on_pid); + if ((down_size!=odm->last_filesize_signaled) || (down_size != tot_size)) { + odm->last_filesize_signaled = down_size; + gf_odm_service_media_event_with_download(odm, GF_EVENT_MEDIA_PROGRESS, down_size, tot_size, bps/8); + } + } + + gf_filter_release_property(pe); + gf_odm_check_clock_mediatime(odm); + } + return GF_TRUE; + + case GF_FEVT_USER: + return gf_sc_user_event(gf_filter_get_udta(filter), (GF_Event *) &evt->user_event.event); + + default: + break; + } + //all events cancelled (play/stop/etc...) + return GF_TRUE; +} + +static GF_Err compose_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *arg_val) +{ + GF_Compositor *compositor = gf_filter_get_udta(filter); + compositor->reload_config = GF_TRUE; + return GF_OK; +} + +static void compose_finalize(GF_Filter *filter) +{ + GF_Compositor *ctx = gf_filter_get_udta(filter); + + if (ctx) { + gf_sc_set_scene(ctx, NULL); + if (ctx->root_scene) { + gf_odm_disconnect(ctx->root_scene->root_od, GF_TRUE); + } + gf_sc_unload(ctx); + } +} +void compositor_setup_aout(GF_Compositor *ctx) +{ + if (! (ctx->init_flags & GF_TERM_NO_AUDIO) && ctx->audio_renderer && !ctx->audio_renderer->aout) { + GF_FilterPid *pid = ctx->audio_renderer->aout = gf_filter_pid_new(ctx->filter); + gf_filter_pid_set_udta(pid, ctx); + gf_filter_pid_set_name(pid, "aout"); + gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_AUDIO) ); + gf_filter_pid_set_property(pid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_property(pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(GF_AUDIO_FMT_S16) ); + gf_filter_pid_set_property(pid, GF_PROP_PID_TIMESCALE, &PROP_UINT(44100) ); + gf_filter_pid_set_property(pid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(44100) ); + gf_filter_pid_set_property(pid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(2) ); + gf_filter_pid_set_max_buffer(ctx->audio_renderer->aout, 1000*ctx->abuf); + gf_filter_pid_set_loose_connect(pid); + } +} + +static GF_Err compose_initialize(GF_Filter *filter) +{ + GF_Err e; + GF_FilterSessionCaps sess_caps; + GF_FilterPid *pid; + GF_Compositor *ctx = gf_filter_get_udta(filter); + + ctx->magic = COMPOSITOR_MAGIC; + ctx->magic_ptr = (void *) ctx; + ctx->filter = filter; + + if (gf_filter_is_dynamic(filter)) { + ctx->dyn_filter_mode = GF_TRUE; + ctx->vfr = GF_TRUE; + } + + if (ctx->buf > ctx->mbuf) + ctx->buf = ctx->mbuf; + if (ctx->rbuf > ctx->mbuf) + ctx->buf = ctx->mbuf; + if (ctx->rbuf >= ctx->buf) + ctx->rbuf = 0; + + if (ctx->player) { + if (ctx->ogl == GF_SC_GLMODE_AUTO) + ctx->ogl = GF_SC_GLMODE_HYBRID; + + //we operate video output directly and dispatch audio output, we need to disable blocking mode + //otherwise we will only get called when audio output is not blocking, and we will likely missed video frames + gf_filter_prevent_blocking(filter, GF_TRUE); + } + + e = gf_sc_load(ctx); + if (e) return e; + + gf_filter_get_session_caps(filter, &sess_caps); + + sess_caps.max_screen_width = ctx->video_out->max_screen_width; + sess_caps.max_screen_height = ctx->video_out->max_screen_height; + sess_caps.max_screen_bpp = ctx->video_out->max_screen_bpp; + + gf_filter_set_session_caps(filter, &sess_caps); + + if (ctx->player) { + //make filter sticky (no shutdown at eos) + gf_filter_make_sticky(filter); + + //load audio filter chain, declaring audio output pid first + if (! (ctx->init_flags & (GF_TERM_NO_AUDIO|GF_TERM_NO_DEF_AUDIO_OUT)) ) { + GF_Filter *audio_out = gf_filter_load_filter(filter, "aout", &e); + ctx->audio_renderer->non_rt_output = GF_FALSE; + if (!audio_out) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Terminal] Failed to load audio output filter (%s) - audio disabled\n", gf_error_to_string(e) )); + } +// else { +// gf_filter_reconnect_output(filter); +// } + } + compositor_setup_aout(ctx); + } + + //declare video output pid + pid = ctx->vout = gf_filter_pid_new(filter); + gf_filter_pid_set_name(pid, "vout"); + //compositor initiated for RT playback, vout pid may not be connected + if (! (ctx->init_flags & GF_TERM_NO_DEF_AUDIO_OUT)) + gf_filter_pid_set_loose_connect(pid); + + gf_filter_pid_set_property(pid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_VISUAL) ); + if (ctx->timescale) + gf_filter_pid_set_property(pid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->timescale) ); + else + gf_filter_pid_set_property(pid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->fps.num) ); + + gf_filter_pid_set_property(pid, GF_PROP_PID_PIXFMT, &PROP_UINT(ctx->opfmt ? ctx->opfmt : GF_PIXEL_RGB) ); + gf_filter_pid_set_property(pid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->output_width) ); + gf_filter_pid_set_property(pid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->output_height) ); + + gf_filter_pid_set_property(pid, GF_PROP_PID_FPS, &PROP_FRAC(ctx->fps) ); + + //for coverage +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + compose_update_arg(filter, NULL, NULL); + } +#endif + + //always request a process task since we don't depend on input packets arrival (animations, pure scene presentations) + gf_filter_post_process_task(filter); + + gf_filter_set_event_target(filter, GF_TRUE); + if (ctx->player==2) { + const char *gui_path = gf_opts_get_key("General", "StartupFile"); + if (gui_path) + gf_sc_connect_from_time_ex(ctx, gui_path, 0, 0, 0, NULL); + } + return GF_OK; +} + +#define OFFS(_n) #_n, offsetof(GF_Compositor, _n) +static GF_FilterArgs CompositorArgs[] = +{ + { OFFS(aa), "set anti-aliasing mode for raster graphics; whether the setting is applied or not depends on the graphics module or graphic card\n"\ + "- none: no anti-aliasing\n"\ + "- text: anti-aliasing for text only\n"\ + "- all: complete anti-aliasing", GF_PROP_UINT, "all", "none|text|all", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(hlfill), "set highlight fill color (ARGB)", GF_PROP_UINT, "0x0", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(hlline), "set highlight stroke color (ARGB)", GF_PROP_UINT, "0xFF000000", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(hllinew), "set highlight stroke width", GF_PROP_FLOAT, "1.0", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(sz), "enable scalable zoom. When scalable zoom is enabled, resizing the output window will also recompute all vectorial objects. Otherwise only the final buffer is stretched", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(bc), "default background color to use when displaying transparent images or video with no scene composition instructions", GF_PROP_UINT, "0", NULL, GF_FS_ARG_UPDATE}, + { OFFS(yuvhw), "enable YUV hardware for 2D blits", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(blitp), "partial hardware blits (if not set, will force more redraw)", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(softblt), "enable software blit/stretch in 2D. If disabled, vector graphics rasterizer will always be used", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, + + { OFFS(stress), "enable stress mode of compositor (rebuild all vector graphics and texture states at each frame)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(fast), "enable speed optimization - whether the setting is applied or not depends on the graphics module / graphic card", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE}, + { OFFS(bvol), "draw bounding volume of objects\n"\ + "- no: disable bounding box\n"\ + "- box: draws a rectangle (2D) or box (3D mode)\n"\ + "- aabb: draws axis-aligned bounding-box tree (3D only)", GF_PROP_UINT, "no", "no|box|aabb", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(textxt), "specify whether text shall be drawn to a texture and then rendered or directly rendered. Using textured text can improve text rendering in 3D and also improve text-on-video like content\n"\ + "- default: use texturing for OpenGL rendering, no texture for 2D rasterizer\n"\ + "- never: never uses text textures\n"\ + "- always: always render text to texture before drawing"\ + "", GF_PROP_UINT, "default", "default|never|always", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(out8b), "convert 10-bit video to 8 bit texture before GPU upload", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(drop), "drop late frame when drawing. By default frames are not droped until a heavy desync of 1 sec is observed", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE}, + { OFFS(sclock), "force synchronizing all streams on a single clock", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(sgaze), "simulate gaze events through mouse", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(ckey), "color key to use in windowless mode (0xFFRRGGBB). GPAC currently does not support true alpha blitting to desktop due to limitations in most windowing toolkit, it therefore uses color keying mechanism. The alpha part of the key is used for global transparency of the output, if supported", GF_PROP_UINT, "0", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(timeout), "timeout in ms after which a source is considered dead", GF_PROP_UINT, "10000", NULL, GF_FS_ARG_UPDATE}, + { OFFS(fps), "simulation frame rate when animation-only sources are played (ignored when video is present)", GF_PROP_FRACTION, "30/1", NULL, GF_FS_ARG_UPDATE}, + { OFFS(timescale), "timescale used for output packets when no input video pid. A value of 0 means fps numerator", GF_PROP_UINT, "0", NULL, GF_FS_ARG_UPDATE}, + { OFFS(autofps), "use video input fps for output. If no video or not set, uses [-fps](). Ignored in player mode", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(vfr), "only emit frames when changes are detected. Always true in player mode and when filter is dynamically loaded", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + + { OFFS(dur), "duration of generation. Mostly used when no video input is present. Negative values mean number of frames, positive values duration in second, 0 stops as soon as all streams are done", GF_PROP_DOUBLE, "0", NULL, GF_FS_ARG_UPDATE}, + { OFFS(fsize), "force the scene to resize to the biggest bitmap available if no size info is given in the BIFS configuration", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(mode2d), "specify whether immediate drawing should be used or not\n"\ + "- immediate: the screen is completely redrawn at each frame (always on if passthrough mode is detected)\n"\ + "- defer: object positioning is tracked from frame to frame and dirty rectangles info is collected in order to redraw the minimal amount of the screen buffer\n"\ + "- debug: only renders changed areas, reseting other areas\n"\ + "Whether the setting is applied or not depends on the graphics module and player mode", GF_PROP_UINT, "defer", "defer|immediate|debug", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(amc), "audio multichannel support; if disabled always downmix to stereo. Useful if the multichannel output does not work properly", GF_PROP_BOOL, "true", NULL, 0}, + { OFFS(asr), "force output sample rate - 0 for auto", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ach), "force output channels - 0 for auto", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(alayout), "force output channel layout - 0 for auto", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(afmt), "force output channel format - 0 for auto", GF_PROP_PCMFMT, "s16", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(asize), "audio output packet size in samples", GF_PROP_UINT, "1024", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(abuf), "audio output buffer duration in ms - the audio renderer fills the output pid up to this value. A too low value will lower latency but can have real-time playback issues", GF_PROP_UINT, +#ifdef GPAC_CONFIG_ANDROID + "200" +#else + "100" +#endif + , NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(avol), "audio volume in percent", GF_PROP_UINT, "100", NULL, GF_FS_ARG_UPDATE}, + { OFFS(apan), "audio pan in percent, 50 is no pan", GF_PROP_UINT, "50", NULL, GF_FS_ARG_UPDATE}, + { OFFS(async), "audio resynchronization; if disabled, audio data is never dropped but may get out of sync", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(max_aspeed), "silence audio if playback speed is greater than sepcified value", GF_PROP_DOUBLE, "2.0", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(max_vspeed), "move to i-frame only decoding if playback speed is greater than sepcified value", GF_PROP_DOUBLE, "4.0", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + + { OFFS(buf), "playout buffer in ms. overridden by BufferLenth property of input pid", GF_PROP_UINT, "3000", NULL, GF_FS_ARG_UPDATE}, + { OFFS(rbuf), "rebuffer trigger in ms. overridden by RebufferLenth property of input pid", GF_PROP_UINT, "1000", NULL, GF_FS_ARG_UPDATE}, + { OFFS(mbuf), "max buffer in ms (must be greater than playout buffer). overridden by BufferMaxOccupancy property of input pid", GF_PROP_UINT, "3000", NULL, GF_FS_ARG_UPDATE}, + { OFFS(ntpsync), "ntp resync threshold in ms (drops frame if their NTP is more than the given threshold above local ntp), 0 disables ntp drop", GF_PROP_UINT, "0", NULL, GF_FS_ARG_UPDATE}, + + { OFFS(nojs), "disable javascript", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(noback), "ignore background nodes and viewport fill (usefull when dumping to PNG)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + +#ifndef GPAC_DISABLE_3D + { OFFS(ogl), "specify 2D rendering mode\n"\ + "- auto: automatically decides betwwen on, off and hybrid based on content\n"\ + "- off: disables OpenGL; 3D will not be rendered\n"\ + "- on: uses OpenGL for all graphics; this will involve polygon tesselation and 2D graphics will not look as nice as 2D mode\n"\ + "- hybrid: the compositor performs software drawing of 2D graphics with no textures (better quality) and uses OpenGL for all 2D objects with textures and 3D objects"\ + , GF_PROP_UINT, "auto", "auto|off|hybrid|on", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(pbo), "enable PixelBufferObjects to push YUV textures to GPU in OpenGL Mode. This may slightly increase the performances of the playback", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(nav), "override the default navigation mode of MPEG-4/VRML (Walk) and X3D (Examine)\n"\ + "- none: disables navigation\n"\ + "- walk: 3D world walk\n"\ + "- fly: 3D world fly (no ground detection)\n"\ + "- pan: 2D/3D world zomm/pan\n"\ + "- game: 3D world game (mouse gives walk direction)\n"\ + "- slide: 2D/3D world slide\n"\ + "- exam: 2D/3D object examine\n"\ + "- orbit: 3D object orbit\n"\ + "- vr: 3D world VR (yaw/pitch/roll)"\ + "", GF_PROP_UINT, "none", "none|walk|fly|pan|game|slide|exam|orbit|vr", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(linegl), "indicate that outlining shall be done through OpenGL pen width rather than vectorial outlining", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(epow2), "emulate power-of-2 textures for openGL (old hardware). Ignored if OpenGL rectangular texture extension is enabled\n"\ + "- yes: video texture is not resized but emulated with padding. This usually speeds up video mapping on shapes but disables texture transformations\n"\ + "- no: video is resized to a power of 2 texture when mapping to a shape", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(paa), "indicate whether polygon antialiasing should be used in full antialiasing mode. If not set, only lines and points antialiasing are used", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(bcull), "indicate whether backface culling shall be disable or not\n"\ + "- on: enables backface culling\n"\ + "- off: disables backface culling\n"\ + "- alpha: only enables backface culling for transparent meshes"\ + "", GF_PROP_UINT, "on", "off|on|alpha", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(wire), "wireframe mode\n"\ + "- none: objects are drawn as solid\n"\ + "- only: objects are drawn as wireframe only\n"\ + "- solid: objects are drawn as solid and wireframe is then drawn", GF_PROP_UINT, "none", "none|only|solid", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(norms), "normal vector drawing for debug\n" + "- none: no normals drawn\n" + "- face: one normal per face drawn\n" + "- vertex: one normal per vertex drawn" + "", GF_PROP_UINT, "none", "none|face|vertex", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(rext), "use non power of two (rectangular) texture GL extension", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(cull), "use aabb culling: large objects are rendered in multiple calls when not fully in viewport", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(depth_gl_scale), "set depth scaler", GF_PROP_FLOAT, "100", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(depth_gl_type), "set geometry type used to draw depth video\n" + "- none: no geometric conversion\n" + "- point: compute point cloud from pixel+depth\n" + "- strip: same as point but thins point set" + "", GF_PROP_UINT, "none", "none|point|strip", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(nbviews), "number of views to use in stereo mode", GF_PROP_UINT, "0", NULL, GF_FS_ARG_UPDATE}, + { OFFS(stereo), "stereo output type. If your graphic card does not support OpenGL shaders, only `top` and `side` modes will be available\n"\ + "- side: images are displayed side by side from left to right\n"\ + "- top: images are displayed from top (laft view) to bottom (right view)\n"\ + "- hmd: same as side except that view aspect ratio is not changed\n"\ + "- ana: standard color anaglyph (red for left view, green and blue for right view) is used (forces views=2)\n"\ + "- cols: images are interleaved by columns, left view on even columns and left view on odd columns (forces views=2)\n"\ + "- rows: images are interleaved by columns, left view on even rows and left view on odd rows (forces views=2)\n"\ + "- spv5: images are interleaved by for SpatialView 5 views display, fullscreen mode (forces views=5)\n"\ + "- alio8: images are interleaved by for Alioscopy 8 views displays, fullscreen mode (forces views=8)\n"\ + "- custom: images are interleaved according to the shader file indicated in [-mvshader](). The shader is exposed each view as uniform sampler2D gfViewX, where X is the view number starting from the left", GF_PROP_UINT, "none", "none|top|side|hmd|custom|cols|rows|ana|spv5|alio8", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(mvshader), "file path to the custom multiview interleaving shader", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(fpack), "default frame packing of input video\n" + "- none: no frame packing\n" + "- top: top bottom frame packing\n" + "- side: side by side packing" + "", GF_PROP_UINT, "none", "none|top|side", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(camlay), "camera layout in multiview modes\n" + "- straight: camera is moved along a straight line, no rotation\n" + "- offaxis: off-axis projection is used\n" + "- linear: camera is moved along a straight line with rotation\n" + "- circular: camera is moved along a circle with rotation" + "", GF_PROP_UINT, "offaxis", "straight|offaxis|linear|circular", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(iod), "inter-occular distance (eye separation) in cm (distance between the cameras). ", GF_PROP_FLOAT, "6.4", NULL, GF_FS_ARG_UPDATE}, + { OFFS(rview), "reverse view order", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + + { OFFS(tvtn), "number of point sampling for tile visibility algo", GF_PROP_UINT, "30", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(tvtt), "number of points above which the tile is considered visible", GF_PROP_UINT, "0", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(tvtd), "disable the tile having full coverage of the SRD, only displaying partial tiles", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(tvtf), "force all tiles to be considered visible, regardless of viewpoint", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(fov), "default field of view for VR", GF_PROP_FLOAT, "1.570796326794897", NULL, GF_FS_ARG_UPDATE}, + { OFFS(vertshader), "path to vertex shader file", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT }, + { OFFS(fragshader), "path to fragment shader file", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT }, +#endif + +#ifdef GF_SR_USE_DEPTH + { OFFS(autocal), "auto callibration of znear/zfar in depth rendering mode", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(dispdepth), "display depth, negative value uses default screen height", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(dispdist), "distance in cm between the camera and the zero-disparity plane. There is currently no automatic calibration of depth in GPAC", GF_PROP_FLOAT, "50", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, +#ifndef GPAC_DISABLE_3D + { OFFS(focdist), "distance of focus point", GF_PROP_FLOAT, "0", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, +#endif +#endif + +#ifdef GF_SR_USE_VIDEO_CACHE + { OFFS(vcsize), "visual cache size when storing raster graphics to memory", GF_PROP_UINT, "0", "0,+I", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(vcscale), "visual cache scale factor in percent when storing raster graphics to memory", GF_PROP_UINT, "100", "0,100", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(vctol), "visual cache tolerance when storing raster graphics to memory. If the difference between the stored version scale and the target display scale is less than tolerance, the cache will be used, otherwise it will be recomputed", GF_PROP_UINT, "30", "0,100", GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, +#endif + { OFFS(osize), "force output size. If not set, size is derived from inputs", GF_PROP_VEC2I, "0x0", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(dpi), "default dpi if not indicated by video output", GF_PROP_VEC2I, "96x96", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(dbgpvr), "debug scene used by PVR addon", GF_PROP_FLOAT, "0", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_EXPERT}, + { OFFS(player), "set compositor in player mode, see filter help\n" + "- no: regular mode\n" + "- base: player mode\n" + "- gui: player mode with GUI auto-start", GF_PROP_UINT, "no", "no|base|gui", GF_FS_ARG_HINT_EXPERT}, + { OFFS(noaudio), "disable audio output", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(opfmt), "pixel format to use for output. Ignored in [-player]() mode", GF_PROP_PIXFMT, "none", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(drv), "indicate if graphics driver should be used. Ignored in player mode\n"\ + "- no: never loads a graphics driver (software blitting used, no 3D possible)\n"\ + "- yes: always loads a graphics driver. Output pixel format will be RGB\n"\ + "- auto: decides based on the loaded content"\ + , GF_PROP_UINT, "auto", "no|yes|auto", GF_FS_ARG_HINT_EXPERT}, + {0} +}; + +static const GF_FilterCapability CompositorCaps[] = +{ + /*first cap bundle for explicitly loaded compositor: accepts audio and video as well as scene/od*/ + CAP_UINT(GF_CAPS_INPUT|GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), + CAP_UINT(GF_CAPS_INPUT|GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_STREAM_TYPE, GF_STREAM_OD), + CAP_UINT(GF_CAPS_INPUT|GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED|GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_INPUT_OUTPUT|GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT_OUTPUT|GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT_OUTPUT|GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_CODECID, GF_CODECID_RAW), + {0}, + /*second cap bundle for dynmac loaded compositor: only accepts text/scene/od*/ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_OD), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + + +const GF_FilterRegister CompositorFilterRegister = { + .name = "compositor", + GF_FS_SET_DESCRIPTION("Compositor") + GF_FS_SET_HELP("The GPAC compositor allows mixing audio, video, text and graphics in a timed fashion.\n" + "The compositor operates either in media-client or filter-only mode.\n" + "\n" + "# Media-client mode\n" + "In this mode, the compositor acts as a pseudo-sink for the video side and creates its own output window.\n" + "The video frames are dispatched to the output video pid in the form of frame pointers requiring later GPU read if used.\n" + "The audio part acts as a regular filter, potentially mixing and resampling the audio inputs to generate its output.\n" + "User events are directly processed by the filter in this mode.\n" + "\n" + "# Filter mode\n" + "In this mode, the compositor acts as a regular filter generating frames based on the loaded scene.\n" + "It will generate its outputs based on the input video frames and will not process any user event.\n" + "If no input video frames (e.g. pure BIFS / SVG / VRML), the filter will generate frames based on the [-fps](), at constant or variable frame rate.\n" + "It will stop generating frames as soon as all input streams are done, unless extended/reduced by [-dur]().\n" + "If audio streams are loaded, an audio output pid is created.\n" + "\n" + "The default output pixel format in filter mode is:\n" + "- `rgb` when the filter is explictly loaded by the application\n" + "- `rgba` when the filter is loaded during a link resolution\n" + "This can be changed by assigning the [-opfmt]() option.\n" + "\n" + "In filter-only mode, the special URL `gpid://` is used to locate PIDs in the scene description, in order to design scenes independently from source media.\n" + "When such a pid is associated to a `Background2D` node in BIFS (no SVG mapping yet), the compositor operates in passthrough mode.\n" + "In this mode, only new input frames on the passthrough pid will generate new frames, and the scene clock matches the input packet time.\n" + "The output size and pixel format will be set to the input size and pixel format, unless specified otherwise in the filter options.\n" + "\n" + "If only 2D graphics are used and display driver is not forced, 2D rasterizer will happen in the output pixel format (including YUV pixel formats).\n" + "In this case, inplace processing (rasterizing over the input frame data) will be used whenever allowed by input data.\n"\ + "\n" + "If 3D graphics are used or display driver is forced, OpenGL will be used on offscreen surface and the output packet will be an OpenGL texture.\n" + "\n" + "# Specific URL syntaxes\n" + "The compositor accepts any URL type supported by GPAC. It also accepts the following syntaxes for URLs:\n" + "- views: creates an auto-stereo scene of N views from `views://v1:.:vN`. vN can be any type of URL supported by GPAC.\n" + "- mosaic: creates a mosaic of N views from `mosaic://v1:.:vN`. vN can be any type of URL supported by GPAC." + "\n" + ) + .private_size = sizeof(GF_Compositor), + .flags = GF_FS_REG_MAIN_THREAD, + .max_extra_pids = (u32) -1, + SETCAPS(CompositorCaps), + .args = CompositorArgs, + .initialize = compose_initialize, + .finalize = compose_finalize, + .process = compose_process, + .process_event = compose_process_event, + .configure_pid = compose_configure_pid, + .reconfigure_output = compose_reconfig_output, + .update_arg = compose_update_arg +}; + +const GF_FilterRegister *compose_filter_register(GF_FilterSession *session) +{ + u32 i=0; + u32 nb_args = sizeof(CompositorArgs) / sizeof(GF_FilterArgs) - 1; + + for (i=0; i +#include +#include +#include +#include +#include +#include + +#define DEFAULT_PERIOD_ID "_gf_dash_def_period" + +typedef struct +{ + GF_List *streams; + + //period element we will fill + GF_MPD_Period *period; +} GF_DasherPeriod; + +enum +{ + DASHER_BS_SWITCH_DEF=0, + DASHER_BS_SWITCH_OFF, + DASHER_BS_SWITCH_ON, + DASHER_BS_SWITCH_INBAND, + DASHER_BS_SWITCH_FORCE, + DASHER_BS_SWITCH_MULTI, +}; + +typedef enum +{ + DASHER_UTCREF_NONE=0, + DASHER_UTCREF_NTP, + DASHER_UTCREF_HTTP_HEAD, + DASHER_UTCREF_ISO, + DASHER_UTCREF_XSDATE, + DASHER_UTCREF_INBAND, +} DasherUTCTimingType; + +enum +{ + DASHER_NTP_REM=0, + DASHER_NTP_YES, + DASHER_NTP_KEEP, +}; + +enum +{ + DASHER_SAP_OFF=0, + DASHER_SAP_SIG, + DASHER_SAP_ON, +}; + +enum +{ + DASHER_BOUNDS_OUT=0, + DASHER_BOUNDS_CLOSEST, + DASHER_BOUNDS_IN, +}; + +enum +{ + DASHER_MUX_ISOM=0, + DASHER_MUX_TS, + DASHER_MUX_MKV, + DASHER_MUX_WEBM, + DASHER_MUX_OGG, + DASHER_MUX_RAW, + DASHER_MUX_AUTO, +}; + +typedef struct +{ + u32 bs_switch, profile, cp, ntp; + s32 subs_sidx; + s32 buf, timescale; + Bool sfile, sseg, no_sar, mix_codecs, stl, tpl, align, sap, no_frag_def, sidx, split, hlsc, strict_cues, force_flush; + u32 strict_sap; + u32 pssh; + Double segdur; + u32 dmode; + char *template; + char *segext; + char *initext; + u32 muxtype; + char *profX; + Double asto; + char *ast; + char *state; + char *cues; + char *title, *source, *info, *cprt, *lang; + GF_List *location, *base; + Bool check_dur, skip_seg, loop, reschedule, scope_deps; + Double refresh, tsb, subdur; + u64 *_p_gentime, *_p_mpdtime; + Bool m2ts; + Bool cmpd, dual, sreg; + char *styp; + Bool sigfrag; + u32 sbound; + char *utcs; + + + //internal + Bool in_error; + + //Manifest output pid + GF_FilterPid *opid; + + GF_FilterPid *opid_alt; + GF_Filter *alt_dst; + Bool opid_alt_m3u8; + + GF_MPD *mpd; + + Double period_start; + GF_DasherPeriod *current_period, *next_period; + GF_List *pids; + Bool template_use_source; + + Bool use_xlink, use_cenc, check_main_role; + + //options for muxers, constrained by profile + Bool no_fragments_defaults; + + Bool is_eos; + u32 nb_seg_url_pending; + u64 last_evt_check_time; + Bool on_demand_done; + Bool subdur_done; + char *out_path; + + GF_Err setup_failure; + + Double nb_secs_to_discard; + Bool first_context_load, store_init_params; + Bool do_m3u8, do_mpd; + + Bool store_seg_states; + + GF_List *postponed_pids; + u32 last_dyn_period_id; + u32 next_pid_id_in_period; + Bool post_play_events; + + Bool force_period_switch; + Bool streams_not_ready; + + //-1 forces report update, otherwise this is a packet count + s32 update_report; + + Bool purge_segments; + + Bool is_playing; + + Bool no_seg_dur; + + Bool utc_initialized; + DasherUTCTimingType utc_timing_type; + s32 utc_diff; +} GF_DasherCtx; + +typedef enum +{ + DASHER_HDR_NONE=0, + DASHER_HDR_PQ10, + DASHER_HDR_HLG, +} DasherHDRType; + +typedef struct _dash_stream +{ + GF_FilterPid *ipid, *opid; + + //stream properties + u32 codec_id, timescale, stream_type, dsi_crc, dsi_enh_crc, id, dep_id, src_id; + GF_Fraction sar, fps; + u32 width, height; + u32 sr, nb_ch; + const char *lang; + Bool interlaced; + const GF_PropertyValue *p_role; + const GF_PropertyValue *p_period_desc; + const GF_PropertyValue *p_as_desc; + const GF_PropertyValue *p_as_any_desc; + const GF_PropertyValue *p_rep_desc; + const GF_PropertyValue *p_base_url; + const char *template; + const char *xlink; + const char *hls_vp_name; + u32 nb_surround, nb_lfe; + u64 ch_layout; + GF_PropVec4i srd; + DasherHDRType hdr_type; + Bool sscale; + + //TODO: get the values for all below + u32 view_id; + //end of TODO + + + u32 bitrate; + GF_DasherPeriod *period; + + Double dash_dur; + + char *period_id; + Double period_start; + Double period_dur; + //0: not done, 1: eos/abort, 2: subdur exceeded + u32 done; + Bool seg_done; + + u32 nb_comp, nb_comp_done; + + u32 nb_rep, nb_rep_done; + Double set_seg_duration; + + //repID for this stream, generated if not found + char *rep_id; + //AS ID for this stream, may be 0 + u32 as_id; + struct _dash_stream *muxed_base; + GF_List *complementary_streams; + GF_List *comp_pids; + + //the one and only representation element + GF_MPD_Representation *rep; + //the parent adaptation set + GF_MPD_AdaptationSet *set; + Bool owns_set; + //set to true to use inband params + Bool inband_params; + GF_List *multi_pids; + GF_List *multi_tracks; + //in case we share the same init segment, we MUST use the same timescale + u32 force_timescale; + + + u32 startNumber, seg_number; + Bool rep_init; + u64 first_cts; + u64 first_dts; + s32 pts_minus_cts; + Bool is_encrypted; + + //target MPD timescale + u32 mpd_timescale; + //segment start time in target MPD timescale + u64 seg_start_time; + Bool split_set_names; + u64 max_period_dur; + + GF_Filter *dst_filter; + + const char *src_url; + + char *init_seg, *seg_template, *idx_template; + u32 nb_sap_3, nb_sap_4; + //ID of output pid (renumbered), used for content component and making sure output muxers use the same IDs + u32 pid_id; + //dependency ID of output pid (renumbered) + u32 dep_pid_id; + u32 nb_samples_in_source; + Bool has_sync_points; + //seg urls not yet handled (waiting for size/index callbacks) + GF_List *pending_segment_urls; + //segment states not yet handled (waiting for size/index/etc callbacks), used for M3U8 and state mode + GF_List *pending_segment_states; + //next segment start time in this stream timescale (NOT MPD timescale) + u64 next_seg_start; + //adjusted next segment start time in this stream timescale (NOT MPD timescale) + //the value is the same as next_seg_start until the end of segment is found (SAP) + //in which case it is adjusted to the SAP time + u64 adjusted_next_seg_start; + + //force representation time end in this stream timescale (NOT MPD timescale) + u64 force_rep_end; + + Bool segment_started; + u64 first_cts_in_seg; + u64 first_cts_in_next_seg; + //used for last segment computation of segmentTimeline + u64 est_first_cts_in_next_seg; + u64 last_cts, last_dts; + u64 cumulated_dur; + Double cumulated_subdur; + Bool subdur_done; + u64 subdur_forced_use_period_dur; + u64 nb_pck; + u64 est_next_dts; + u64 seek_to_pck; + u64 ts_offset; + u32 nb_repeat; + + Bool splitable; + u32 loop_state; + u32 split_dur_next; + + u32 moof_sn_inc, moof_sn; + Double clamped_dur; + + u32 nb_segments_purged; + Double dur_purged; + Bool tile_base; + + u32 cues_timescale; + u32 nb_cues; + GF_DASHCueInfo *cues; + Bool cues_use_edits; + + Bool clamp_done; + Bool dcd_not_ready; + + Bool reschedule; + + GF_Fraction64 duration; + GF_List *packet_queue; + u32 nb_sap_in_queue; + + //gm_ for gen manifest + Double gm_duration_total, gm_duration_min, gm_duration_max; + u32 gm_nb_segments; + + Bool no_seg_dur; +} GF_DashStream; + +static void dasher_flush_segment(GF_DasherCtx *ctx, GF_DashStream *ds); +static void dasher_update_rep(GF_DasherCtx *ctx, GF_DashStream *ds); + + +static GF_DasherPeriod *dasher_new_period() +{ + GF_DasherPeriod *period; + GF_SAFEALLOC(period, GF_DasherPeriod); + if (period) + period->streams = gf_list_new(); + return period; +} + +#ifndef GPAC_DISABLE_AV_PARSERS +static GF_Err dasher_get_audio_info_with_m4a_sbr_ps(GF_DashStream *ds, const GF_PropertyValue *dsi, u32 *SampleRate, u32 *Channels) +{ + GF_M4ADecSpecInfo a_cfg; + GF_Err e; + if (SampleRate) *SampleRate = ds->sr; + if (Channels) *Channels = ds->nb_ch; + + if (!dsi) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] missing AAC config\n")); + return GF_OK; + } + e = gf_m4a_get_config(dsi->value.data.ptr, dsi->value.data.size, &a_cfg); + if (e) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] corrupted AAC Config, %s\n", gf_error_to_string(e))); + return GF_NOT_SUPPORTED; + } + if (SampleRate && a_cfg.has_sbr) { + *SampleRate = a_cfg.sbr_sr; + } + if (Channels) *Channels = a_cfg.nb_chan; + return e; +} +#endif + + +static GF_Err dasher_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + Bool period_switch = GF_FALSE; + const GF_PropertyValue *p, *dsi=NULL; + u32 dc_crc, dc_enh_crc; + GF_DashStream *ds; + u32 prev_stream_type; + const char *cue_file=NULL; + GF_DasherCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + return GF_OK; + } + + if (!ctx->opid) { + u32 i, nb_opids = ctx->dual ? 2 : 1; + for (i=0; i < nb_opids; i++) { + char *segext=NULL; + char *force_ext=NULL; + GF_FilterPid *opid; + if (i==0) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_name(ctx->opid, "MANIFEST"); + opid = ctx->opid; + } else { + GF_Err e; + if (!ctx->alt_dst && ctx->out_path) { + char szSRC[100]; + GF_FileIO *gfio = NULL; + char *mpath = ctx->out_path; + u32 len; + if (!strncmp(mpath, "gfio://", 7)) { + gfio = gf_fileio_from_url(mpath); + if (!gfio) return GF_BAD_PARAM; + //only use basename as we will create the new resource through factory + mpath = (char *) gf_file_basename(gf_fileio_resource_url(gfio)); + if (!mpath) return GF_OUT_OF_MEM; + } + + len = (u32) strlen(mpath); + char *out_path = gf_malloc(len+10); + if (!out_path) return GF_OUT_OF_MEM; + memcpy(out_path, mpath, len); + out_path[len]=0; + char *sep = gf_file_ext_start(out_path); + if (sep) sep[0] = 0; + if (ctx->do_m3u8) { + strcat(out_path, ".mpd"); + force_ext = "mpd"; + } else { + ctx->opid_alt_m3u8 = GF_TRUE; + ctx->do_m3u8 = GF_TRUE; + strcat(out_path, ".m3u8"); + force_ext = "m3u8"; + } + if (gfio) { + const char *rel = gf_fileio_factory(gfio, out_path); + gf_free(out_path); + out_path = gf_strdup(rel); + if (!out_path) return GF_OUT_OF_MEM; + } + + ctx->alt_dst = gf_filter_connect_destination(filter, out_path, &e); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Couldn't create secondary manifest output %s: %s\n", out_path, gf_error_to_string(e) )); + gf_free(out_path); + break; + } + gf_free(out_path); + + //reset any sourceID given in the dst_arg and assign sourceID to be the dasher filter + gf_filter_reset_source(ctx->alt_dst); + snprintf(szSRC, 100, "MuxSrc%cdasher_%p", gf_filter_get_sep(filter, GF_FS_SEP_NAME), ctx->alt_dst); + gf_filter_set_source(ctx->alt_dst, filter, szSRC); + + ctx->opid_alt = gf_filter_pid_new(filter); + gf_filter_pid_set_name(ctx->opid_alt, "MANIFEST_ALT"); + + snprintf(szSRC, 100, "dasher_%p", ctx->alt_dst); + gf_filter_pid_set_property(ctx->opid_alt, GF_PROP_PID_MUX_SRC, &PROP_STRING(szSRC) ); + //we also need to set the property on main output just to avoid the connection + snprintf(szSRC, 100, "dasher_%p", ctx); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MUX_SRC, &PROP_STRING(szSRC) ); + } + opid = ctx->opid_alt; + } + if (!opid) + continue; + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(opid, pid); + gf_filter_pid_set_property(opid, GF_PROP_PID_DECODER_CONFIG, NULL); + gf_filter_pid_set_property(opid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, NULL); + gf_filter_pid_set_property(opid, GF_PROP_PID_CODECID, NULL); + gf_filter_pid_set_property(opid, GF_PROP_PID_UNFRAMED, NULL); + gf_filter_pid_set_property(opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); + + if (!ctx->out_path) + ctx->out_path = gf_filter_pid_get_destination(ctx->opid); + + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_FILE_EXT); + if (p) { + gf_filter_pid_set_property(opid, GF_PROP_PID_FILE_EXT, p ); + segext = p->value.string; + } else { + segext = NULL; + if (ctx->out_path) { + segext = gf_file_ext_start(ctx->out_path); + } + if (!segext) segext = "mpd"; + else segext++; + if (force_ext) + segext = force_ext; + gf_filter_pid_set_property(opid, GF_PROP_PID_FILE_EXT, &PROP_STRING(segext) ); + } + + if (!strcmp(segext, "m3u8")) { + ctx->do_m3u8 = GF_TRUE; + gf_filter_pid_set_name(opid, "manifest_m3u8" ); + } else { + ctx->do_mpd = GF_TRUE; + gf_filter_pid_set_name(opid, "manifest_mpd" ); + } + } + + ctx->store_seg_states = GF_FALSE; + //in m3u8 mode, always store all seg states. In MPD only if state, not ondemand + if (((ctx->state || ctx->purge_segments) && !ctx->sseg) || ctx->do_m3u8) ctx->store_seg_states = GF_TRUE; + } + + ds = gf_filter_pid_get_udta(pid); + if (!ds) { + GF_SAFEALLOC(ds, GF_DashStream); + if (!ds) return GF_OUT_OF_MEM; + ds->ipid = pid; + gf_list_add(ctx->pids, ds); + ds->complementary_streams = gf_list_new(); + period_switch = GF_TRUE; + gf_filter_pid_set_udta(pid, ds); + if (ctx->sbound!=DASHER_BOUNDS_OUT) + ds->packet_queue = gf_list_new(); + + /*initial connection and we already have sent play event, send a PLAY on this new PID + TODO: we need to send STOP/PLAY depending on period + */ + if (ctx->is_playing) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_PLAY, ds->ipid); + evt.play.speed = 1.0; + gf_filter_pid_send_event(ds->ipid, &evt); + } + //don't create pid at this time + } + + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + +#define CHECK_PROP(_type, _mem, _e) \ + p = gf_filter_pid_get_property(pid, _type); \ + if (!p && (_e<=0) ) return _e; \ + if (p && (p->value.uint != _mem) && _mem) period_switch = GF_TRUE; \ + if (p) _mem = p->value.uint; \ + +#define CHECK_PROPL(_type, _mem, _e) \ + p = gf_filter_pid_get_property(pid, _type); \ + if (!p && (_e<=0) ) return _e; \ + if (p && (p->value.longuint != _mem) && _mem) period_switch = GF_TRUE; \ + if (p) _mem = p->value.longuint; \ + +#define CHECK_PROP_BOOL(_type, _mem, _e) \ + p = gf_filter_pid_get_property(pid, _type); \ + if (!p && (_e<=0) ) return _e; \ + if (p && (p->value.boolean != _mem) && _mem) period_switch = GF_TRUE; \ + if (p) _mem = p->value.uint; \ + +#define CHECK_PROP_FRAC(_type, _mem, _e) \ + p = gf_filter_pid_get_property(pid, _type); \ + if (!p && (_e<=0) ) return _e; \ + if (p && (p->value.frac.num * _mem.den != p->value.frac.den * _mem.num) && _mem.den && _mem.num) period_switch = GF_TRUE; \ + if (p) _mem = p->value.frac; \ + +#define CHECK_PROP_FRAC64(_type, _mem, _e) \ + p = gf_filter_pid_get_property(pid, _type); \ + if (!p && (_e<=0) ) return _e; \ + if (p && (p->value.lfrac.num * _mem.den != p->value.lfrac.den * _mem.num) && _mem.den && _mem.num) period_switch = GF_TRUE; \ + if (p) _mem = p->value.lfrac; \ + + +#define CHECK_PROP_STR(_type, _mem, _e) \ + p = gf_filter_pid_get_property(pid, _type); \ + if (!p && (_e<=0) ) return _e; \ + if (p && _mem && strcmp(_mem, p->value.string)) period_switch = GF_TRUE; \ + if (p) _mem = p->value.string; \ + +#define CHECK_PROP_PROP(_type, _mem, _e) \ + p = gf_filter_pid_get_property(pid, _type); \ + if (!p && (_e<=0) ) return _e; \ + if (p != _mem) period_switch = GF_TRUE; \ + _mem = p; \ + + + prev_stream_type = ds->stream_type; + CHECK_PROP(GF_PROP_PID_STREAM_TYPE, ds->stream_type, GF_NOT_SUPPORTED) + + ds->tile_base = GF_FALSE; + + if (ds->stream_type != GF_STREAM_FILE) { + if (ds->stream_type==GF_STREAM_ENCRYPTED) { + CHECK_PROP(GF_PROP_PID_ORIG_STREAM_TYPE, ds->stream_type, GF_EOS) + ds->is_encrypted = GF_TRUE; + } + if (prev_stream_type==ds->stream_type) + period_switch = GF_FALSE; + + CHECK_PROP(GF_PROP_PID_CODECID, ds->codec_id, GF_NOT_SUPPORTED) + CHECK_PROP(GF_PROP_PID_TIMESCALE, ds->timescale, GF_NOT_SUPPORTED) + CHECK_PROP(GF_PROP_PID_BITRATE, ds->bitrate, GF_EOS) + + if (!ds->bitrate) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] No bitrate property assigned to PID %s, defaulting to 1Mbps\n\tTry specifying bitrate property after your source, e.g. -i source.raw:#Bitrate=VAL\n", gf_filter_pid_get_name(ds->ipid))); + ds->bitrate = 1000000; + } + + if (ds->stream_type==GF_STREAM_VISUAL) { + CHECK_PROP(GF_PROP_PID_WIDTH, ds->width, GF_EOS) + CHECK_PROP(GF_PROP_PID_HEIGHT, ds->height, GF_EOS) + //don't return if not defined + CHECK_PROP_FRAC(GF_PROP_PID_SAR, ds->sar, GF_EOS) + if (!ds->sar.num) ds->sar.num = ds->sar.den = 1; + CHECK_PROP_FRAC(GF_PROP_PID_FPS, ds->fps, GF_EOS) + + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CROP_POS); + if (p && ((p->value.vec2i.x != ds->srd.x) || (p->value.vec2i.y != ds->srd.y) ) ) period_switch = GF_TRUE; + if (p) { + ds->srd.x = p->value.vec2i.x; + ds->srd.y = p->value.vec2i.y; + ds->srd.z = ds->width; + ds->srd.w = ds->height; + } else { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TILE_BASE); + if (p) { + ds->srd.x = ds->srd.y = 0; + ds->srd.z = ds->width; + ds->srd.w = ds->height; + ds->tile_base = GF_TRUE; + } + } + } else if (ds->stream_type==GF_STREAM_AUDIO) { + CHECK_PROP(GF_PROP_PID_SAMPLE_RATE, ds->sr, GF_EOS) + CHECK_PROP(GF_PROP_PID_NUM_CHANNELS, ds->nb_ch, GF_EOS) + CHECK_PROPL(GF_PROP_PID_CHANNEL_LAYOUT, ds->ch_layout, GF_EOS) + } + + CHECK_PROP(GF_PROP_PID_ID, ds->id, GF_EOS) + CHECK_PROP(GF_PROP_PID_DEPENDENCY_ID, ds->dep_id, GF_EOS) + CHECK_PROP(GF_PROP_PID_NB_FRAMES, ds->nb_samples_in_source, GF_EOS) + CHECK_PROP_FRAC64(GF_PROP_PID_DURATION, ds->duration, GF_EOS) + CHECK_PROP_BOOL(GF_PROP_PID_HAS_SYNC, ds->has_sync_points, GF_EOS) + + if (ctx->scope_deps) { + const char *src_args = gf_filter_pid_orig_src_args(pid); + if (src_args) { + ds->src_id = gf_crc_32(src_args, (u32) strlen(src_args)); + } + } + dc_crc = 0; + dsi = p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (p) dc_crc = gf_crc_32(p->value.data.ptr, p->value.data.size); + dc_enh_crc = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + if (p) dc_enh_crc = gf_crc_32(p->value.data.ptr, p->value.data.size); + + if (((dc_crc != ds->dsi_crc) && ds->dsi_crc) + || ((dc_enh_crc != ds->dsi_enh_crc) && ds->dsi_enh_crc) + ) { + //check which codecs can support inband param sets + switch (ds->codec_id) { + case GF_CODECID_AVC: + case GF_CODECID_SVC: + case GF_CODECID_MVC: + case GF_CODECID_HEVC: + case GF_CODECID_LHVC: + if (!ctx->bs_switch) + period_switch = GF_TRUE; + break; + default: + period_switch = GF_TRUE; + break; + } + } + //check if input is ready + if (!dc_crc && !dc_enh_crc) { + switch (ds->codec_id) { + case GF_CODECID_AVC: + case GF_CODECID_SVC: + case GF_CODECID_MVC: + case GF_CODECID_HEVC: + case GF_CODECID_LHVC: + case GF_CODECID_AAC_MPEG4: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + case GF_CODECID_AC3: + case GF_CODECID_EAC3: + case GF_CODECID_AV1: + case GF_CODECID_VP8: + case GF_CODECID_VP9: + ds->dcd_not_ready = GF_TRUE; + ctx->streams_not_ready = GF_TRUE; + break; + default: + break; + } + } + ds->dsi_crc = dc_crc; + + CHECK_PROP_STR(GF_PROP_PID_URL, ds->src_url, GF_EOS) + CHECK_PROP_STR(GF_PROP_PID_TEMPLATE, ds->template, GF_EOS) + CHECK_PROP_STR(GF_PROP_PID_LANGUAGE, ds->lang, GF_EOS) + CHECK_PROP_BOOL(GF_PROP_PID_INTERLACED, ds->interlaced, GF_EOS) + CHECK_PROP_PROP(GF_PROP_PID_AS_COND_DESC, ds->p_as_desc, GF_EOS) + CHECK_PROP_PROP(GF_PROP_PID_AS_ANY_DESC, ds->p_as_any_desc, GF_EOS) + CHECK_PROP_PROP(GF_PROP_PID_REP_DESC, ds->p_rep_desc, GF_EOS) + CHECK_PROP_PROP(GF_PROP_PID_BASE_URL, ds->p_base_url, GF_EOS) + CHECK_PROP_PROP(GF_PROP_PID_ROLE, ds->p_role, GF_EOS) + CHECK_PROP_STR(GF_PROP_PID_HLS_PLAYLIST, ds->hls_vp_name, GF_EOS) + CHECK_PROP_BOOL(GF_PROP_PID_SINGLE_SCALE, ds->sscale, GF_EOS) + + ds->startNumber = 1; + CHECK_PROP(GF_PROP_PID_START_NUMBER, ds->startNumber, GF_EOS) + ds->dash_dur = ctx->segdur; + ds->no_seg_dur = ctx->no_seg_dur; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_DUR); + if (p) { + ds->dash_dur = p->value.number; + ds->no_seg_dur = GF_FALSE; + } + //this avoids very weird cases where (u64) (dash_dur*timescale) is 0. we limit the max segment duration to 1M sec, a bit more than 11.5 days + if (ds->dash_dur>1000000) + ds->dash_dur=1000000; + + ds->splitable = GF_FALSE; + switch (ds->stream_type) { + case GF_STREAM_TEXT: + case GF_STREAM_METADATA: + case GF_STREAM_OD: + case GF_STREAM_SCENE: + ds->splitable = ctx->split; + break; + } + + ds->clamped_dur = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CLAMP_DUR); + if (p) ds->clamped_dur = p->value.number; + + //HDR +#if !defined(GPAC_DISABLE_AV_PARSERS) + if (dsi) { +#if !defined(GPAC_DISABLE_HEVC) + if (ds->codec_id == GF_CODECID_LHVC || ds->codec_id == GF_CODECID_HEVC_TILES || ds->codec_id == GF_CODECID_HEVC) { + GF_HEVCConfig* hevccfg = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, GF_FALSE); + if (hevccfg) { + Bool is_interlaced; + HEVCState hevc; + HEVC_SPS* sps; + memset(&hevc, 0, sizeof(HEVCState)); + gf_media_hevc_parse_ps(hevccfg, &hevc, GF_HEVC_NALU_VID_PARAM); + gf_media_hevc_parse_ps(hevccfg, &hevc, GF_HEVC_NALU_SEQ_PARAM); + sps = &hevc.sps[hevc.sps_active_idx]; + if (sps && sps->colour_description_present_flag) { + DasherHDRType old_hdr_type = ds->hdr_type; + if (sps->colour_primaries == 9 && sps->matrix_coeffs == 9) { + if (sps->transfer_characteristic == 14) ds->hdr_type = DASHER_HDR_HLG; //TODO: parse alternative_transfer_characteristics SEI + if (sps->transfer_characteristic == 16) ds->hdr_type = DASHER_HDR_PQ10; + } + if (old_hdr_type != ds->hdr_type) period_switch = GF_TRUE; + } + is_interlaced = hevccfg->interlaced_source_flag ? GF_TRUE : GF_FALSE; + if (ds->interlaced != is_interlaced) period_switch = GF_TRUE; + ds->interlaced = is_interlaced; + + gf_odf_hevc_cfg_del(hevccfg); + } + } + else +#endif + if (ds->codec_id == GF_CODECID_AVC || ds->codec_id == GF_CODECID_SVC || ds->codec_id == GF_CODECID_MVC) { + AVCState avc; + GF_AVCConfig* avccfg = gf_odf_avc_cfg_read(dsi->value.data.ptr, dsi->value.data.size); + GF_AVCConfigSlot *sl = (GF_AVCConfigSlot *)gf_list_get(avccfg->sequenceParameterSets, 0); + if (sl) { + s32 idx; + memset(&avc, 0, sizeof(AVCState)); + idx = gf_media_avc_read_sps(sl->data, sl->size, &avc, 0, NULL); + if (idx>=0) { + Bool is_interlaced = avc.sps[idx].frame_mbs_only_flag ? GF_FALSE : GF_TRUE; + if (ds->interlaced != is_interlaced) period_switch = GF_TRUE; + ds->interlaced = is_interlaced; + } + } + gf_odf_avc_cfg_del(avccfg); + } + } +#endif /*!GPAC_DISABLE_AV_PARSERS*/ + + if (ds->stream_type==GF_STREAM_AUDIO) { + u32 _sr=0, _nb_ch=0; +#ifndef GPAC_DISABLE_AV_PARSERS + switch (ds->codec_id) { + case GF_CODECID_AAC_MPEG4: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + //DASH-IF and MPEG disagree here: + if (ctx->profile == GF_DASH_PROFILE_AVC264_LIVE || ctx->profile == GF_DASH_PROFILE_AVC264_ONDEMAND) { + GF_Err res = dasher_get_audio_info_with_m4a_sbr_ps(ds, dsi, &_sr, &_nb_ch); + if (res) { + //DASH-IF IOP 3.3 mandates the SBR/PS info + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Could not get AAC info, %s\n", gf_error_to_string(res))); + } + } else if (dsi) { + dasher_get_audio_info_with_m4a_sbr_ps(ds, dsi, NULL, &_nb_ch); + } + break; + case GF_CODECID_AC3: + case GF_CODECID_EAC3: + if (dsi) { + u32 i; + GF_AC3Config ac3; + gf_isom_ac3_config_parse(dsi->value.data.ptr, dsi->value.data.size, (ds->codec_id==GF_CODECID_EAC3) ? GF_TRUE : GF_FALSE, &ac3); + + ds->nb_lfe = ac3.streams[0].lfon ? 1 : 0; + _nb_ch = gf_ac3_get_channels(ac3.streams[0].acmod); + for (i=0; i ds->sr) ds->sr = _sr; + if (_nb_ch > ds->nb_ch) ds->nb_ch = _nb_ch; + } + + //only reload queues if we detected a period switch + if (period_switch) { + cue_file = ctx->cues; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_CUE); + if (p) cue_file = p->value.string; + + if (ds->cues) gf_free(ds->cues); + ds->cues = NULL; + ds->nb_cues = 0; + if (cue_file) { + GF_Err e = gf_mpd_load_cues(cue_file, ds->id, &ds->cues_timescale, &ds->cues_use_edits, &ds->cues, &ds->nb_cues); + if (e) return e; + } + } + } else { + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_URL); + if (!p) p = gf_filter_pid_get_property(pid, GF_PROP_PID_FILEPATH); + if (p) return GF_NOT_SUPPORTED; + + CHECK_PROP_STR(GF_PROP_PID_XLINK, ds->xlink, GF_EOS) + } + CHECK_PROP_STR(GF_PROP_PID_PERIOD_ID, ds->period_id, GF_EOS) + CHECK_PROP_PROP(GF_PROP_PID_PERIOD_DESC, ds->p_period_desc, GF_EOS) + + ds->period_start = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PERIOD_START); + if (p) ds->period_start = p->value.number; + + ds->period_dur = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PERIOD_DUR); + if (p) ds->period_dur = p->value.number; + + if (ds->stream_type==GF_STREAM_FILE) { + if (!ds->xlink && !ds->period_start && !ds->period_dur) { + ds->done = 1; + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] null PID specified without any XLINK/start/duration, ignoring\n")); + } else if (ds->xlink) { + ctx->use_xlink = GF_TRUE; + } + } + + //our stream is already scheduled for next period, don't do anything + if (gf_list_find(ctx->next_period->streams, ds)>=0) + period_switch = GF_FALSE; + + //assign default ID + if (!ds->period_id) + ds->period_id = DEFAULT_PERIOD_ID; + + if (!period_switch) { + if (ds->opid) gf_filter_pid_copy_properties(ds->opid, pid); + if (ds->rep) + dasher_update_rep(ctx, ds); + return GF_OK; + } + + //period switching + s32 res = gf_list_del_item(ctx->current_period->streams, ds); + //force end of segment if stream is not yet done and in current period + if ((res>=0) && !ds->done && !ds->seg_done) { + GF_DashStream *base_ds; + + base_ds = ds->muxed_base ? ds->muxed_base : ds; + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] PID %s config changed during active period, forcing period switch\n", gf_filter_pid_get_name(ds->ipid) )); + ds->seg_done = GF_TRUE; + assert(base_ds->nb_comp_done < base_ds->nb_comp); + base_ds->nb_comp_done ++; + ds->first_cts_in_next_seg = ds->est_next_dts;; + + if (base_ds->nb_comp_done == base_ds->nb_comp) { + dasher_flush_segment(ctx, base_ds); + } + ctx->force_period_switch = GF_TRUE; + } + gf_list_add(ctx->next_period->streams, ds); + ds->period = ctx->next_period; + + + return GF_OK; +} + +static GF_Err dasher_update_mpd(GF_DasherCtx *ctx) +{ + char profiles_string[GF_MAX_PATH]; + GF_XMLAttribute *cenc_att = NULL; + GF_XMLAttribute *xlink_att = NULL; + + u32 i, count=gf_list_count(ctx->mpd->attributes); + for (i=0; impd->attributes, i); + if (!strcmp(att->name, "xmlns:cenc")) cenc_att = att; + if (!strcmp(att->name, "xmlns:xlink")) xlink_att = att; + + } + if (ctx->dmode==GF_MPD_TYPE_DYNAMIC) { + ctx->mpd->type = GF_MPD_TYPE_DYNAMIC; + } else { + ctx->mpd->type = GF_MPD_TYPE_STATIC; + ctx->mpd->availabilityStartTime = 0; + } + + if (ctx->profile==GF_DASH_PROFILE_LIVE) { + if (ctx->use_xlink && !ctx->m2ts) { + strcpy(profiles_string, "urn:mpeg:dash:profile:isoff-segext-live:2014"); + } else { + sprintf(profiles_string, "urn:mpeg:dash:profile:%s:2011", ctx->m2ts ? "mp2t-simple" : "isoff-live"); + } + } else if (ctx->profile==GF_DASH_PROFILE_ONDEMAND) { + if (ctx->use_xlink) { + strcpy(profiles_string, "urn:mpeg:dash:profile:isoff-segext-on-demand:2014"); + } else { + strcpy(profiles_string, "urn:mpeg:dash:profile:isoff-on-demand:2011"); + } + } else if (ctx->profile==GF_DASH_PROFILE_MAIN) { + sprintf(profiles_string, "urn:mpeg:dash:profile:%s:2011", ctx->m2ts ? "mp2t-main" : "isoff-main"); + } else if (ctx->profile==GF_DASH_PROFILE_HBBTV_1_5_ISOBMF_LIVE) { + strcpy(profiles_string, "urn:hbbtv:dash:profile:isoff-live:2012"); + } else if (ctx->profile==GF_DASH_PROFILE_AVC264_LIVE) { + strcpy(profiles_string, "urn:mpeg:dash:profile:isoff-live:2011,http://dashif.org/guidelines/dash264"); + } else if (ctx->profile==GF_DASH_PROFILE_AVC264_ONDEMAND) { + strcpy(profiles_string, "urn:mpeg:dash:profile:isoff-on-demand:2011,http://dashif.org/guidelines/dash264"); + } else { + strcpy(profiles_string, "urn:mpeg:dash:profile:full:2011"); + } + + if (ctx->profX) { + char profiles_w_ext[GF_MAX_PATH+256]; + sprintf(profiles_w_ext, "%s,%s", profiles_string, ctx->profX); + if (ctx->mpd->profiles) gf_free(ctx->mpd->profiles); + ctx->mpd->profiles = gf_strdup(profiles_w_ext); + } else { + if (ctx->mpd->profiles) gf_free(ctx->mpd->profiles); + ctx->mpd->profiles = gf_strdup(profiles_string); + } + + if (ctx->use_cenc && !cenc_att) { + cenc_att = gf_xml_dom_create_attribute("xmlns:cenc", "urn:mpeg:cenc:2013"); + gf_list_add(ctx->mpd->attributes, cenc_att); + } + if (ctx->use_xlink && !xlink_att) { + xlink_att = gf_xml_dom_create_attribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); + gf_list_add(ctx->mpd->attributes, xlink_att); + } + + ctx->mpd->time_shift_buffer_depth = 0; + ctx->mpd->minimum_update_period = 0; + + if (ctx->dmode==GF_MPD_TYPE_DYNAMIC) { + ctx->mpd->time_shift_buffer_depth = (u32) -1; + if (ctx->tsb>=0) ctx->mpd->time_shift_buffer_depth = (u32) (1000*ctx->tsb); + + if (ctx->refresh>=0) { + ctx->mpd->minimum_update_period = (u32) (1000*(ctx->refresh ? ctx->refresh : ctx->segdur) ); + } else { + ctx->mpd->minimum_update_period = 0; + } + } + return GF_OK; +} +static GF_Err dasher_setup_mpd(GF_DasherCtx *ctx) +{ + u32 i, count; + GF_MPD_ProgramInfo *info; + ctx->mpd = gf_mpd_new(); + ctx->mpd->xml_namespace = "urn:mpeg:dash:schema:mpd:2011"; + ctx->mpd->base_URLs = gf_list_new(); + ctx->mpd->locations = gf_list_new(); + ctx->mpd->program_infos = gf_list_new(); + ctx->mpd->periods = gf_list_new(); + ctx->mpd->attributes = gf_list_new(); + if (ctx->buf<0) { + s32 buf = -ctx->buf; + ctx->mpd->min_buffer_time = (u32) ( ctx->segdur*10 * buf ); //*1000 (ms) / 100 (percent) + } + else ctx->mpd->min_buffer_time = ctx->buf; + + GF_SAFEALLOC(info, GF_MPD_ProgramInfo); + if (info) { + gf_list_add(ctx->mpd->program_infos, info); + if (ctx->title) + info->title = gf_strdup(ctx->title); + else { + char tmp[256]; + const char *name = NULL; + if (ctx->out_path) { + const char *url = ctx->out_path; + if (!strncmp(ctx->out_path, "gfio://", 7)) { + url = gf_fileio_translate_url(ctx->out_path); + if (!url) url = ""; + } + name = strrchr(url, '/'); + if (!name) name = strrchr(url, '\\'); + if (!name) name = url; + else name++; + } + snprintf(tmp, 255, "%s generated by GPAC", name ? name : ""); + tmp[255]=0; + info->title = gf_strdup(tmp); + } + if (ctx->cprt) info->copyright = gf_strdup(ctx->cprt); + if (ctx->info) info->more_info_url = gf_strdup(ctx->info); + else info->more_info_url = gf_strdup("http://gpac.io"); + if (ctx->source) info->source = gf_strdup(ctx->source); + if (ctx->lang) info->lang = gf_strdup(ctx->lang); + } + + count = ctx->location ? gf_list_count(ctx->location) : 0; + for (i=0; ilocation, i); + gf_list_add(ctx->mpd->locations, gf_strdup(l)); + } + count = ctx->base ? gf_list_count(ctx->base) : 0; + for (i=0; ibase, i); + GF_SAFEALLOC(base, GF_MPD_BaseURL); + if (base) { + base->URL = gf_strdup(b); + gf_list_add(ctx->mpd->base_URLs, base); + } + } + return dasher_update_mpd(ctx); +} + +static GF_Err dasher_get_rfc_6381_codec_name(GF_DasherCtx *ctx, GF_DashStream *ds, char *szCodec, Bool force_inband, Bool force_sbr) +{ + u32 subtype=0, subtype_src=0, mha_pl=0; + const GF_PropertyValue *dcd, *dcd_enh, *dovi, *codec; + + dcd = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_ISOM_SUBTYPE); + if (dcd) subtype_src = dcd->value.uint; + + dcd = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_DECODER_CONFIG); + dcd_enh = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + + if (!force_inband) { + force_inband = ds->inband_params; + } + if (!force_inband) { + const GF_PropertyValue *p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_ISOM_SUBTYPE); + if (p) { + //input uses inband parameters, force it on output regardless of bitstream switching mode + switch (p->value.uint) { + case GF_ISOM_SUBTYPE_AVC3_H264: + case GF_ISOM_SUBTYPE_AVC4_H264: + case GF_ISOM_SUBTYPE_LHE1: + case GF_ISOM_SUBTYPE_HEV1: + force_inband = GF_TRUE; + ds->inband_params = GF_TRUE; + break; + } + } + } + + codec = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_CODEC); + if (codec && (codec->type==GF_PROP_STRING) && codec->value.string) { + const char *codec_str = codec->value.string; + if (codec_str[0] != '.') { + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", codec_str); + return GF_OK; + } + if (!subtype_src) + subtype_src = gf_codecid_4cc_type(ds->codec_id); + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s%s", gf_4cc_to_str(subtype_src), codec_str); + return GF_OK; + } + + dovi = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_DOLBY_VISION); + if (dovi) { + GF_BitStream *bs = gf_bs_new(dovi->value.data.ptr, dovi->value.data.size, GF_BITSTREAM_READ); + GF_DOVIDecoderConfigurationRecord *dvcc = gf_odf_dovi_cfg_read_bs(bs); + gf_bs_del(bs); + if (!dvcc) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_AUTHOR, ("[ISOM Tools] No config found for Dolby Vision file (\"%s\") when computing RFC6381.\n", gf_4cc_to_str(subtype))); + return GF_BAD_PARAM; + } + + subtype = GF_ISOM_SUBTYPE_DVHE; + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%02u.%02u", gf_4cc_to_str(subtype), dvcc->dv_profile, dvcc->dv_level); + gf_odf_dovi_cfg_del(dvcc); + + return GF_OK; + } + + switch (ds->codec_id) { + case GF_CODECID_AAC_MPEG4: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + if (dcd) { + u8 audio_object_type; + if (dcd->value.data.size < 2) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[RFC6381-AAC] invalid DSI size %u < 2\n", dcd->value.data.size)); + return GF_NON_COMPLIANT_BITSTREAM; + } + /*5 first bits of AAC config*/ + audio_object_type = (dcd->value.data.ptr[0] & 0xF8) >> 3; + if (audio_object_type == 31) { /*escape code*/ + const u8 audio_object_type_ext = ((dcd->value.data.ptr[0] & 0x07) << 3) + ((dcd->value.data.ptr[1] & 0xE0) >> 5); + audio_object_type = 32 + audio_object_type_ext; + } +#ifndef GPAC_DISABLE_AV_PARSERS + if (force_sbr && (audio_object_type==2) ) { + GF_M4ADecSpecInfo a_cfg; + GF_Err e = gf_m4a_get_config(dcd->value.data.ptr, dcd->value.data.size, &a_cfg); + if (e==GF_OK) { + if (a_cfg.sbr_sr) + audio_object_type = a_cfg.sbr_object_type; + if (a_cfg.has_ps) + audio_object_type = 29; + } + } +#endif + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "mp4a.%02X.%01d", ds->codec_id, audio_object_type); + return GF_OK; + } + + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "mp4a.%02X", ds->codec_id); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[Dasher] Cannot find AAC config, using default %s\n", szCodec)); + return GF_OK; + + break; + case GF_CODECID_MPEG4_PART2: +#ifndef GPAC_DISABLE_AV_PARSERS + if (dcd) { + GF_M4VDecSpecInfo dsi; + gf_m4v_get_config(dcd->value.data.ptr, dcd->value.data.size, &dsi); + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "mp4v.%02X.%01x", ds->codec_id, dsi.VideoPL); + } else +#endif + { + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "mp4v.%02X", ds->codec_id); + } + break; + case GF_CODECID_SVC: + case GF_CODECID_MVC: + if (dcd_enh) dcd = dcd_enh; + subtype = (ds->codec_id==GF_CODECID_SVC) ? GF_ISOM_SUBTYPE_SVC_H264 : GF_ISOM_SUBTYPE_MVC_H264; + case GF_CODECID_AVC: + if (!subtype) { + if (force_inband) { + subtype = dcd_enh ? GF_ISOM_SUBTYPE_AVC4_H264 : GF_ISOM_SUBTYPE_AVC3_H264; + } else { + subtype = dcd_enh ? GF_ISOM_SUBTYPE_AVC2_H264 : GF_ISOM_SUBTYPE_AVC_H264; + } + } + + if (dcd) { + GF_AVCConfig *avcc = gf_odf_avc_cfg_read(dcd->value.data.ptr, dcd->value.data.size); + if (avcc) { + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%02X%02X%02X", gf_4cc_to_str(subtype), avcc->AVCProfileIndication, avcc->profile_compatibility, avcc->AVCLevelIndication); + gf_odf_avc_cfg_del(avcc); + return GF_OK; + } + } + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype)); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[Dasher] Cannot find AVC config, using default %s\n", szCodec)); + return GF_OK; +#ifndef GPAC_DISABLE_HEVC + case GF_CODECID_LHVC: + subtype = force_inband ? GF_ISOM_SUBTYPE_LHE1 : GF_ISOM_SUBTYPE_LHV1; + //fallthrough + case GF_CODECID_HEVC_TILES: + if (!subtype) subtype = GF_ISOM_SUBTYPE_HVT1; + if (!dcd && ds->dep_id) { + u32 i, count = gf_list_count(ctx->current_period->streams); + for (i=0; icurrent_period->streams, i); + if (a_ds->id != ds->dep_id) continue; + dcd = gf_filter_pid_get_property(a_ds->ipid, GF_PROP_PID_DECODER_CONFIG); + break; + } + } + //fallthrough + case GF_CODECID_HEVC: + if (!subtype) { + if (ds->tile_base) { + subtype = force_inband ? GF_ISOM_SUBTYPE_HEV2 : GF_ISOM_SUBTYPE_HVC2; + } else if (dcd_enh) { + if (dcd) { + subtype = force_inband ? GF_ISOM_SUBTYPE_HEV2 : GF_ISOM_SUBTYPE_HVC2; + } else { + subtype = force_inband ? GF_ISOM_SUBTYPE_LHE1 : GF_ISOM_SUBTYPE_LHV1; + } + } else { + subtype = force_inband ? GF_ISOM_SUBTYPE_HEV1 : GF_ISOM_SUBTYPE_HVC1; + } + } + if (dcd || dcd_enh) { + u8 c; + GF_HEVCConfig *hvcc = dcd ? gf_odf_hevc_cfg_read(dcd->value.data.ptr, dcd->value.data.size, GF_FALSE) : NULL; + + //TODO - check we do expose hvcC for tiled tracks ! + + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.", gf_4cc_to_str(subtype)); + if (hvcc) { + char szTemp[RFC6381_CODEC_NAME_SIZE_MAX]; + if (hvcc->profile_space==1) strcat(szCodec, "A"); + else if (hvcc->profile_space==2) strcat(szCodec, "B"); + else if (hvcc->profile_space==3) strcat(szCodec, "C"); + //profile idc encoded as a decimal number + sprintf(szTemp, "%d", hvcc->profile_idc); + strcat(szCodec, szTemp); + //general profile compatibility flags: hexa, bit-reversed + { + u32 val = hvcc->general_profile_compatibility_flags; + u32 i, res = 0; + for (i=0; i<32; i++) { + res |= val & 1; + if (i==31) break; + res <<= 1; + val >>=1; + } + sprintf(szTemp, ".%X", res); + strcat(szCodec, szTemp); + } + + if (hvcc->tier_flag) strcat(szCodec, ".H"); + else strcat(szCodec, ".L"); + sprintf(szTemp, "%d", hvcc->level_idc); + strcat(szCodec, szTemp); + + c = hvcc->progressive_source_flag << 7; + c |= hvcc->interlaced_source_flag << 6; + c |= hvcc->non_packed_constraint_flag << 5; + c |= hvcc->frame_only_constraint_flag << 4; + c |= (hvcc->constraint_indicator_flags >> 40); + sprintf(szTemp, ".%X", c); + strcat(szCodec, szTemp); + if (hvcc->constraint_indicator_flags & 0xFFFFFFFF) { + c = (hvcc->constraint_indicator_flags >> 32) & 0xFF; + sprintf(szTemp, ".%X", c); + strcat(szCodec, szTemp); + if (hvcc->constraint_indicator_flags & 0x00FFFFFF) { + c = (hvcc->constraint_indicator_flags >> 24) & 0xFF; + sprintf(szTemp, ".%X", c); + strcat(szCodec, szTemp); + if (hvcc->constraint_indicator_flags & 0x0000FFFF) { + c = (hvcc->constraint_indicator_flags >> 16) & 0xFF; + sprintf(szTemp, ".%X", c); + strcat(szCodec, szTemp); + if (hvcc->constraint_indicator_flags & 0x000000FF) { + c = (hvcc->constraint_indicator_flags >> 8) & 0xFF; + sprintf(szTemp, ".%X", c); + strcat(szCodec, szTemp); + c = (hvcc->constraint_indicator_flags ) & 0xFF; + sprintf(szTemp, ".%X", c); + strcat(szCodec, szTemp); + } + } + } + } + gf_odf_hevc_cfg_del(hvcc); + } + return GF_OK; + } + + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype)); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[Dasher] Cannot find HEVC config, using default %s\n", szCodec)); + return GF_OK; +#endif + +#ifndef GPAC_DISABLE_AV1 + case GF_CODECID_AV1: + if (!subtype) subtype = GF_ISOM_SUBTYPE_AV01; + + if (dcd) { + GF_AV1Config *av1c = gf_odf_av1_cfg_read(dcd->value.data.ptr, dcd->value.data.size); + + if (!av1c) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_AUTHOR, ("[ISOM Tools] Error loading config for AV1 file (\"%s\") when computing RFC6381.\n", gf_4cc_to_str(subtype))); + } else { +#ifndef GPAC_DISABLE_AV_PARSERS + GF_Err e; + u32 i = 0; + AV1State av1_state; + gf_av1_init_state(&av1_state); + av1_state.config = av1c; + + for (i = 0; i < gf_list_count(av1c->obu_array); ++i) { + GF_BitStream *bs; + GF_AV1_OBUArrayEntry *a = gf_list_get(av1c->obu_array, i); + bs = gf_bs_new(a->obu, a->obu_length, GF_BITSTREAM_READ); + if (!av1_is_obu_header(a->obu_type)) + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[ISOM Tools] AV1: unexpected obu_type %d when computing RFC6381. PArsing anyway.\n", a->obu_type, gf_4cc_to_str(subtype))); + + e = aom_av1_parse_temporal_unit_from_section5(bs, &av1_state); + gf_bs_del(bs); + bs = NULL; + if (e) { + gf_odf_av1_cfg_del(av1c); + return e; + } + } + + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%01u.%u%c.%u.%01u.%01u%01u%01u", gf_4cc_to_str(subtype), + av1_state.config->seq_profile, av1_state.config->seq_level_idx_0, av1_state.config->seq_tier_0 ? 'H' : 'M', + av1_state.bit_depth, av1_state.config->monochrome, + av1_state.config->chroma_subsampling_x, av1_state.config->chroma_subsampling_y, + av1_state.config->chroma_subsampling_x && av1_state.config->chroma_subsampling_y ? av1_state.config->chroma_sample_position : 0); + + if (av1_state.color_description_present_flag) { + char tmp[RFC6381_CODEC_NAME_SIZE_MAX]; + snprintf(tmp, RFC6381_CODEC_NAME_SIZE_MAX, "%01u.%01u.%01u.%01u", av1_state.color_primaries, av1_state.transfer_characteristics, av1_state.matrix_coefficients, av1_state.color_range); + strcat(szCodec, tmp); + } else { + if ((av1_state.color_primaries == 2) && (av1_state.transfer_characteristics == 2) && (av1_state.matrix_coefficients == 2) && av1_state.color_range == GF_FALSE) { + + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[AV1] incoherent color characteristics primaries %d transfer %d matrix %d color range %d\n", av1_state.color_primaries, av1_state.transfer_characteristics, av1_state.matrix_coefficients, av1_state.color_range)); + } + } + gf_odf_av1_cfg_del(av1c); + gf_av1_reset_state(&av1_state, GF_TRUE); + return GF_OK; +#else + return GF_NOT_SUPPORTED; +#endif + } + } + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype)); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[Dasher] Cannot find AV1 config, using default %s\n", szCodec)); + return GF_OK; +#endif /*GPAC_DISABLE_AV1*/ + + case GF_CODECID_VP8: + if (!subtype) subtype = GF_ISOM_SUBTYPE_VP08; + case GF_CODECID_VP9: + if (!subtype) subtype = GF_ISOM_SUBTYPE_VP09; + + if (dcd) { + GF_VPConfig *vpcc = gf_odf_vp_cfg_read(dcd->value.data.ptr, dcd->value.data.size); + + if (!vpcc) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_AUTHOR, ("[ISOM Tools] No config found for VP file (\"%s\") when computing RFC6381.\n", gf_4cc_to_str(subtype))); + } else { + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%02u.%02u.%02u.%02u.%02u.%02u.%02u.%02u", gf_4cc_to_str(subtype), + vpcc->profile, + vpcc->level, + vpcc->bit_depth, + vpcc->chroma_subsampling, + vpcc->colour_primaries, + vpcc->transfer_characteristics, + vpcc->matrix_coefficients, + vpcc->video_fullRange_flag); + + gf_odf_vp_cfg_del(vpcc); + return GF_OK; + } + } + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype)); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[Dasher] Cannot find VPX config, using default %s\n", szCodec)); + return GF_OK; + + case GF_CODECID_MPHA: + subtype = subtype_src ? subtype_src : GF_ISOM_SUBTYPE_MH3D_MHA1; + + if (!dcd || (dcd->value.data.size<2) ) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[Dasher] Cannot find MPEG-H Audio Config, defaulting to profile 0x01\n")); + mha_pl = 1; + } else { + mha_pl = dcd->value.data.ptr[1]; + } + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.0x%02X", gf_4cc_to_str(subtype), mha_pl); + return GF_OK; + case GF_CODECID_MHAS: + subtype = subtype_src ? subtype_src : GF_ISOM_SUBTYPE_MH3D_MHM1; + if (dcd && (dcd->value.data.size>2)) { + mha_pl = dcd->value.data.ptr[1]; + } else { + const GF_PropertyValue *pl = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_PROFILE_LEVEL); + if (pl) { + mha_pl = pl->value.uint; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[Dasher] Cannot find MPEG-H Audio Config or audio PL, defaulting to profile 0x01\n")); + mha_pl = 1; + } + } + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.0x%02X", gf_4cc_to_str(subtype), mha_pl); + return GF_OK; + + default: + subtype = gf_codecid_4cc_type(ds->codec_id); + if (!subtype) { + const GF_PropertyValue *p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_ISOM_SUBTYPE); + if (p) subtype = p->value.uint; + } + if (!subtype) { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[Dasher] codec parameters not known, cannot set codec string\n" )); + strcpy(szCodec, "unkn"); + return GF_OK; + } + if (ds->codec_idstream_type==GF_STREAM_VISUAL) { + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "mp4v.%02X", ds->codec_id); + } else if (ds->stream_type==GF_STREAM_AUDIO) { + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "mp4a.%02X", ds->codec_id); + } else { + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "mp4s.%02X", ds->codec_id); + } + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_AUTHOR, ("[Dasher] codec parameters not known - setting codecs string to default value \"%s\"\n", gf_4cc_to_str(subtype) )); + snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype)); + } + } + return GF_OK; +} + +static u32 dasher_get_dep_bitrate(GF_DasherCtx *ctx, GF_DashStream *ds) +{ + u32 bitrate = ds->bitrate; + if (ds->dep_id) { + u32 i, count = gf_list_count(ctx->current_period->streams); + for (i=0; icurrent_period->streams, i); + if (a_ds == ds) continue; + + if (gf_list_find(a_ds->complementary_streams, ds)>=0) { + + bitrate += dasher_get_dep_bitrate(ctx, a_ds); + } + } + } + return bitrate; +} + +static void get_canon_urn(bin128 URN, char *res) +{ + char sres[4]; + u32 i; + /* Output canonical UIID form */ + strcpy(res, ""); + for (i=0; i<4; i++) { sprintf(sres, "%02x", URN[i]); strcat(res, sres); } + strcat(res, "-"); + for (i=4; i<6; i++) { sprintf(sres, "%02x", URN[i]); strcat(res, sres); } + strcat(res, "-"); + for (i=6; i<8; i++) { sprintf(sres, "%02x", URN[i]); strcat(res, sres); } + strcat(res, "-"); + for (i=8; i<10; i++) { sprintf(sres, "%02x", URN[i]); strcat(res, sres); } + strcat(res, "-"); + for (i=10; i<16; i++) { sprintf(sres, "%02x", URN[i]); strcat(res, sres); } +} + +static const char *get_drm_kms_name(const char *canURN) +{ + if (!stricmp(canURN, "67706163-6365-6E63-6472-6D746F6F6C31")) return "GPAC1.0"; + else if (!stricmp(canURN, "5E629AF5-38DA-4063-8977-97FFBD9902D4")) return "Marlin1.0"; + else if (!strcmp(canURN, "adb41c24-2dbf-4a6d-958b-4457c0d27b95")) return "MediaAccess3.0"; + else if (!strcmp(canURN, "A68129D3-575B-4F1A-9CBA-3223846CF7C3")) return "VideoGuard"; + else if (!strcmp(canURN, "9a04f079-9840-4286-ab92-e65be0885f95")) return "PlayReady"; + else if (!strcmp(canURN, "9a27dd82-fde2-4725-8cbc-4234aa06ec09")) return "VCAS"; + else if (!strcmp(canURN, "F239E769-EFA3-4850-9C16-A903C6932EFB")) return "Adobe"; + else if (!strcmp(canURN, "1f83e1e8-6ee9-4f0d-ba2f-5ec4e3ed1a66")) return "SecureMedia"; + else if (!strcmp(canURN, "644FE7B5-260F-4FAD-949A-0762FFB054B4")) return "CMLA (OMA DRM)"; + else if (!strcmp(canURN, "6a99532d-869f-5922-9a91-113ab7b1e2f3")) return "MobiTVDRM"; + else if (!strcmp(canURN, "35BF197B-530E-42D7-8B65-1B4BF415070F")) return "DivX DRM"; + else if (!strcmp(canURN, "B4413586-C58C-FFB0-94A5-D4896C1AF6C3")) return "VODRM"; + else if (!strcmp(canURN, "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed")) return "Widevine"; + else if (!strcmp(canURN, "80a6be7e-1448-4c37-9e70-d5aebe04c8d2")) return "Irdeto"; + else if (!strcmp(canURN, "dcf4e3e3-62f1-5818-7ba6-0a6fe33ff3dd")) return "CA 1.0, DRM+ 2.0"; + else if (!strcmp(canURN, "45d481cb-8fe0-49c0-ada9-ab2d2455b2f2")) return "CoreCrypt"; + else if (!strcmp(canURN, "616C7469-6361-7374-2D50-726F74656374")) return "altiProtect"; + else if (!strcmp(canURN, "992c46e6-c437-4899-b6a0-50fa91ad0e39")) return "Arris SecureMedia SteelKnot version 1"; + else if (!strcmp(canURN, "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b")) return "cenc initData"; + else if (!strcmp(canURN, "e2719d58-a985-b3c9-781a-b030af78d30e")) return "ClearKey1.0"; + else if (!strcmp(canURN, "94CE86FB-07FF-4F43-ADB8-93D2FA968CA2")) return "FairPlay"; + else if (!strcmp(canURN, "279fe473-512c-48fe-ade8-d176fee6b40f")) return "Arris Titanium"; + else if (!strcmp(canURN, "aa11967f-cc01-4a4a-8e99-c5d3dddfea2d")) return "UDRM"; + return "unknown"; +} + +static GF_List *dasher_get_content_protection_desc(GF_DasherCtx *ctx, GF_DashStream *ds, GF_MPD_AdaptationSet *for_set) +{ + char sCan[40]; + u32 prot_scheme=0; + u32 i, count; + const GF_PropertyValue *p; + GF_List *res = NULL; + GF_BitStream *bs_r; + + count = gf_list_count(ctx->current_period->streams); + bs_r = gf_bs_new((const char *) &count, 1, GF_BITSTREAM_READ); + + for (i=0; icurrent_period->streams, i); + if (!a_ds->is_encrypted) continue; + + if (for_set) { + if (a_ds->set != for_set) continue; + //for now only insert for the stream holding the set + if (!a_ds->owns_set) continue; + } else if ((a_ds != ds) && (a_ds->muxed_base != ds) ) { + continue; + } + + p = gf_filter_pid_get_property(a_ds->ipid, GF_PROP_PID_PROTECTION_SCHEME_TYPE); + if (p) prot_scheme = p->value.uint; + + if ((prot_scheme==GF_ISOM_CENC_SCHEME) || (prot_scheme==GF_ISOM_CBC_SCHEME) || (prot_scheme==GF_ISOM_CENS_SCHEME) || (prot_scheme==GF_ISOM_CBCS_SCHEME)) { + u32 j, nb_pssh; + GF_XMLAttribute *att; + char szVal[GF_MAX_PATH]; + + ctx->use_cenc = GF_TRUE; + + p = gf_filter_pid_get_property(a_ds->ipid, GF_PROP_PID_KID); + if (!p) { + continue; + } + + if (!res) res = gf_list_new(); + desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:dash:mp4protection:2011", gf_4cc_to_str(prot_scheme)); + gf_list_add(res, desc); + + + get_canon_urn(p->value.data.ptr, sCan); + att = gf_xml_dom_create_attribute("cenc:default_KID", sCan); + if (!desc->attributes) desc->attributes = gf_list_new(); + gf_list_add(desc->attributes, att); + + if (ctx->pssh <= GF_DASH_PSSH_MOOF) { + continue; + } + //(data) binary blob containing (u32)N [(bin128)SystemID(u32)version(u32)KID_count[(bin128)keyID](u32)priv_size(char*priv_size)priv_data] + p = gf_filter_pid_get_property(a_ds->ipid, GF_PROP_PID_CENC_PSSH); + if (!p) continue; + + gf_bs_reassign_buffer(bs_r, p->value.data.ptr, p->value.data.size); + nb_pssh = gf_bs_read_u32(bs_r); + + //add pssh + for (j=0; jchildren = gf_list_new(); + sprintf(szVal, "urn:uuid:%s", sCan); + desc->scheme_id_uri = gf_strdup(szVal); + desc->value = gf_strdup(get_drm_kms_name(sCan)); + gf_list_add(res, desc); + + GF_SAFEALLOC(node, GF_XMLNode); + if (node) { + GF_XMLNode *pnode; + node->type = GF_XML_NODE_TYPE; + node->name = gf_strdup("cenc:pssh"); + node->content = gf_list_new(); + gf_list_add(desc->children, node); + + GF_SAFEALLOC(pnode, GF_XMLNode); + if (pnode) { + pnode->type = GF_XML_TEXT_TYPE; + gf_list_add(node->content, pnode); + + size_64 = 2*pssh_len; + pnode->name = gf_malloc(size_64); + if (pnode->name) { + size_64 = gf_base64_encode((const char *)pssh_data, pssh_len, (char *)pnode->name, size_64); + pnode->name[size_64] = 0; + } + } + } + gf_free(pssh_data); + } + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Protection scheme %s has no official DASH mapping, using URI \"urn:gpac:dash:mp4protection:2018\"\n", gf_4cc_to_str(prot_scheme))); + if (!res) res = gf_list_new(); + desc = gf_mpd_descriptor_new(NULL, "urn:gpac:dash:mp4protection:2018", gf_4cc_to_str(prot_scheme)); + gf_list_add(res, desc); + } + } + gf_bs_del(bs_r); + return res; +} + +static void dasher_get_mime_and_ext(GF_DasherCtx *ctx, GF_DashStream *ds, const char **out_subtype, const char **out_ext) +{ + const char *subtype = NULL; + const char *mux_ext = NULL; + const char *cstr; + + if (ctx->m2ts) { + subtype = "mp2t"; + } else if (ctx->muxtype!=DASHER_MUX_AUTO) { + switch (ctx->muxtype) { + case DASHER_MUX_ISOM: subtype = "mp4"; mux_ext = "mp4"; break; + case DASHER_MUX_TS: subtype = "mp2t"; mux_ext = "ts"; break; + case DASHER_MUX_MKV: subtype = "x-matroska"; mux_ext = "mkv"; break; + case DASHER_MUX_WEBM: subtype = "webm"; mux_ext = "webm"; break; + case DASHER_MUX_OGG: subtype = "ogg"; mux_ext = "ogg"; break; + case DASHER_MUX_RAW: + cstr = gf_codecid_mime(ds->codec_id); + if (cstr) { + subtype = strchr(cstr, '/'); + if (subtype) subtype++; + else subtype = "raw"; + } + if (out_ext) { + cstr = gf_codecid_file_ext(ds->codec_id); + if (cstr) *out_ext = cstr; + } + break; + } + } else if (ctx->initext) { + mux_ext = ctx->initext; + if (!strcmp(ctx->initext, "ts") || !strcmp(ctx->initext, "m2ts")) { + subtype = "mp2t"; + ctx->muxtype = DASHER_MUX_TS; + } else if (!strcmp(ctx->initext, "mkv") || !strcmp(ctx->initext, "mka") || !strcmp(ctx->initext, "mks") || !strcmp(ctx->initext, "mk3d")) { + subtype = "x-matroska"; + ctx->muxtype = DASHER_MUX_MKV; + } else if (!strcmp(ctx->initext, "webm") || !strcmp(ctx->initext, "weba")) { + subtype = "webm"; + ctx->muxtype = DASHER_MUX_WEBM; + } else if (!strcmp(ctx->initext, "ogg") || !strcmp(ctx->initext, "oga") || !strcmp(ctx->initext, "ogv") || !strcmp(ctx->initext, "spx") || !strcmp(ctx->initext, "oggm") || !strcmp(ctx->initext, "opus")) { + subtype = "ogg"; + ctx->muxtype = DASHER_MUX_OGG; + } + else if (!strcmp(ctx->initext, "null")) { + mux_ext = "mp4"; + ctx->muxtype = DASHER_MUX_ISOM; + } + } + if (!subtype) subtype = "mp4"; + if (out_subtype) *out_subtype = subtype; + if (!mux_ext) mux_ext = "mp4"; + if (out_ext) *out_ext = mux_ext; +} + +static void dasher_update_rep(GF_DasherCtx *ctx, GF_DashStream *ds) +{ + char szCodec[RFC6381_CODEC_NAME_SIZE_MAX]; + + //Outputs are not yet connected, derive mime from init segment extension + if (!ds->rep->mime_type) { + const char *subtype = NULL; + dasher_get_mime_and_ext(ctx, ds, &subtype, NULL); + + if (ds->stream_type==GF_STREAM_VISUAL) + gf_dynstrcat(&ds->rep->mime_type, "video/", NULL); + else if (ds->stream_type==GF_STREAM_AUDIO) + gf_dynstrcat(&ds->rep->mime_type, "audio/", NULL); + else + gf_dynstrcat(&ds->rep->mime_type, "application/", NULL); + + gf_dynstrcat(&ds->rep->mime_type, subtype, NULL); + } + + ds->rep->bandwidth = ds->bitrate; + if (ds->stream_type==GF_STREAM_VISUAL) { + ds->rep->width = ds->width; + ds->rep->height = ds->height; + + + if (!ds->rep->sar) { + GF_SAFEALLOC(ds->rep->sar, GF_MPD_Fractional); + } + if (ds->rep->sar) { + ds->rep->sar->num = ds->sar.num; + ds->rep->sar->den = ds->sar.den; + if (ds->fps.num && ds->fps.den) { + if (!ds->rep->framerate) { + GF_SAFEALLOC(ds->rep->framerate, GF_MPD_Fractional); + } + if (ds->rep->framerate) { + ds->rep->framerate->num = ds->fps.num; + ds->rep->framerate->den = ds->fps.den; + gf_media_get_reduced_frame_rate(&ds->rep->framerate->num, &ds->rep->framerate->den); + } + } + } + } + else if (ds->stream_type==GF_STREAM_AUDIO) { + Bool use_cicp = GF_FALSE; + GF_MPD_Descriptor *desc; + char value[256]; + ds->rep->samplerate = ds->sr; + + if (ds->nb_surround || ds->nb_lfe) use_cicp = GF_TRUE; + if ((ds->codec_id==GF_CODECID_MHAS) || (ds->codec_id==GF_CODECID_MPHA)) use_cicp = GF_TRUE; + + if (!use_cicp) { + sprintf(value, "%d", ds->nb_ch); + desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:dash:23003:3:audio_channel_configuration:2011", value); + } else { + sprintf(value, "%d", gf_audio_fmt_get_cicp_layout(ds->nb_ch, ds->nb_surround, ds->nb_lfe)); + desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:mpegB:cicp:ChannelConfiguration", value); + } + + gf_mpd_del_list(ds->rep->audio_channels, gf_mpd_descriptor_free, GF_TRUE); + + gf_list_add(ds->rep->audio_channels, desc); + } else { + } + + dasher_get_rfc_6381_codec_name(ctx, ds, szCodec, (ctx->bs_switch==DASHER_BS_SWITCH_INBAND) ? GF_TRUE : GF_FALSE, GF_TRUE); + if (ds->rep->codecs) gf_free(ds->rep->codecs); + ds->rep->codecs = gf_strdup(szCodec); + + + if (ds->interlaced) ds->rep->scan_type = GF_MPD_SCANTYPE_INTERLACED; + else { + //profiles forcing scanType=progressive for progressive + switch (ctx->profile) { + case GF_DASH_PROFILE_HBBTV_1_5_ISOBMF_LIVE: + ds->rep->scan_type = GF_MPD_SCANTYPE_PROGRESSIVE; + break; + } + } + + if (ctx->cp!=GF_DASH_CPMODE_ADAPTATION_SET) { + gf_mpd_del_list(ds->rep->content_protection, gf_mpd_descriptor_free, 0); + ds->rep->content_protection = dasher_get_content_protection_desc(ctx, ds, NULL); + } +} + +static void dasher_setup_rep(GF_DasherCtx *ctx, GF_DashStream *ds, u32 *srd_rep_idx) +{ + const GF_PropertyValue *p; + + assert(ds->rep==NULL); + ds->rep = gf_mpd_representation_new(); + ds->rep->playback.udta = ds; + + dasher_update_rep(ctx, ds); + + p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_AS_ID); + ds->as_id = p ? p->value.uint : 0; + + p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_REP_ID); + if (p) { + if (ds->rep_id) gf_free(ds->rep_id); + + if (!ds->tile_base && (ds->srd.w || ds->srd.z) && !ctx->sseg && !ctx->sfile) { + char *rep_name = gf_malloc(sizeof(char) * (strlen(p->value.string) + 15) ); + sprintf(rep_name, "%s_%d", p->value.string, *srd_rep_idx); + ds->rep_id = rep_name; + (*srd_rep_idx) ++; + } else { + ds->rep_id = gf_strdup(p->value.string); + } + + } else if (!ds->rep_id) { + char szRepID[20]; + sprintf(szRepID, "%d", 1 + gf_list_find(ctx->pids, ds)); + ds->rep_id = gf_strdup(szRepID); + } + ds->rep->id = gf_strdup(ds->rep_id); +} + +static Bool dasher_same_roles(GF_DashStream *ds1, GF_DashStream *ds2) +{ + GF_List *list; + if (ds1->p_role && ds2->p_role) { + if (gf_props_equal(ds1->p_role, ds2->p_role)) return GF_TRUE; + } + if (!ds1->p_role && !ds2->p_role) + return GF_TRUE; + + //special case, if one is set and the other is not, compare with "main" role + list = ds2->p_role ? ds2->p_role->value.string_list : ds1->p_role->value.string_list; + if (gf_list_count(list)==1) { + char *s = gf_list_get(list, 0); + if (!strcmp(s, "main")) return GF_TRUE; + } + return GF_FALSE; +} + +static Bool dasher_same_adaptation_set(GF_DasherCtx *ctx, GF_DashStream *ds, GF_DashStream *ds_test) +{ + const char *lang1, *lang2; + //muxed representations + if (ds_test->muxed_base) { + if (ds_test->muxed_base == ds) + return GF_TRUE; + //if muxed base rep has been registered with this AdaptationSet, also register this stream + if (gf_list_find(ds->set->representations, ds_test->muxed_base->rep)>=0) + return GF_TRUE; + } + + //otherwise we have to be of same type + if (ds->stream_type != ds_test->stream_type) return GF_FALSE; + + //not the same roles + if (!dasher_same_roles(ds, ds_test)) return GF_FALSE; + + /* if two inputs don't have the same (number and value) as_desc they don't belong to the same AdaptationSet + (use c_as_desc for AdaptationSet descriptors common to all inputs in an AS) */ + if (!ds->p_as_desc && ds_test->p_as_desc) + return GF_FALSE; + if (ds->p_as_desc && !ds_test->p_as_desc) + return GF_FALSE; + if (ds->p_as_desc && ! gf_props_equal(ds->p_as_desc, ds_test->p_as_desc)) + return GF_FALSE; + + //need same AS ID if specified + if (ds->as_id && ds_test->as_id &&(ds->as_id != ds_test->as_id) ) + return GF_FALSE; + + //need same dash duration if aligned + if (ctx->align) { + if (ds->dash_dur != ds_test->dash_dur) return GF_FALSE; + } + + if (ds->srd.x != ds_test->srd.x) return GF_FALSE; + if (ds->srd.y != ds_test->srd.y) return GF_FALSE; + if (ds->srd.z != ds_test->srd.z) return GF_FALSE; + if (ds->srd.w != ds_test->srd.w) return GF_FALSE; + + if (ds->view_id != ds_test->view_id) return GF_FALSE; + //according to DASH spec mixing interlaced and progressive is OK + //if (ds->interlaced != ds_test->interlaced) return GF_FALSE; + if (ds->nb_ch != ds_test->nb_ch) return GF_FALSE; + + lang1 = ds->lang ? ds->lang : "und"; + lang2 = ds_test->lang ? ds_test->lang : "und"; + if (strcmp(lang1, lang2)) return GF_FALSE; + + if (ds->stream_type==GF_STREAM_VISUAL) { + u32 w, h, tw, th; + if (ctx->no_sar) { + w = ds->width; + h = ds->height; + tw = ds_test->width; + th = ds_test->height; + } else { + w = ds->width * ds->sar.num; + h = ds->height * ds->sar.den; + tw = ds_test->width * ds_test->sar.num; + th = ds_test->height * ds_test->sar.den; + } + + //not the same aspect ratio + if (w * th != h * tw) + return GF_FALSE; + } else if (ds->stream_type==GF_STREAM_AUDIO) { + if (!ctx->mix_codecs && (ds->codec_id != ds_test->codec_id) ) + return GF_FALSE; + //we allow mix of channels config + } else { + if (!ctx->mix_codecs && strcmp(ds->rep->codecs, ds_test->rep->codecs)) return GF_FALSE; + return GF_TRUE; + } + //ok, we are video or audio with mixed codecs + if (ctx->mix_codecs) return GF_TRUE; + //we need dependencies, unless SRD case + if (ds_test->dep_id && (ds_test->src_id==ds->src_id) && gf_list_find(ds->complementary_streams, ds_test) < 0) { + return GF_FALSE; + } + //we should be good + return GF_TRUE; +} + +static void dasher_add_descriptors(GF_List **p_dst_list, const GF_PropertyValue *desc_val) +{ + u32 j, count; + GF_List *dst_list; + if (!desc_val) return; + if (desc_val->type != GF_PROP_STRING_LIST) return; + count = gf_list_count(desc_val->value.string_list); + if (!count) return; + if ( ! (*p_dst_list)) *p_dst_list = gf_list_new(); + dst_list = *p_dst_list; + for (j=0; jvalue.string_list, j); + if (desc[0] == '<') { + GF_MPD_other_descriptors *d; + GF_SAFEALLOC(d, GF_MPD_other_descriptors); + if (d) { + d->xml_desc = gf_strdup(desc); + gf_list_add(dst_list, d); + } + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Invalid descriptor %s, expecting '<' as first character\n", desc)); + } + } +} + +static void dasher_setup_set_defaults(GF_DasherCtx *ctx, GF_MPD_AdaptationSet *set) +{ + u32 i, count; + Bool main_role_set = GF_FALSE; + //by default setup alignment + if (ctx->sseg) set->subsegment_alignment = ctx->align; + else set->segment_alignment = ctx->align; + + //startWithSAP is set when the first packet comes in + + //the rest depends on the various profiles/iop, to check + count = gf_list_count(set->representations); + for (i=0; irepresentations, i); + GF_DashStream *ds = rep->playback.udta; + + if (set->max_width < ds->width) set->max_width = ds->width; + if (set->max_height < ds->height) set->max_height = ds->height; +/* if (set->max_bandwidth < ds->rep->bandwidth) set->max_bandwidth = ds->rep->bandwidth; + if (set->max_framerate * ds->fps.den < ds->fps.num) set->max_framerate = (u32) (ds->fps.num / ds->fps.den); +*/ + + /*set role*/ + if (ds->p_role) { + u32 j, role_count; + role_count = gf_list_count(ds->p_role->value.string_list); + for (j=0; jp_role->value.string_list, j); + GF_MPD_Descriptor *desc; + char *uri; + if (!strcmp(role, "caption") || !strcmp(role, "subtitle") || !strcmp(role, "main") + || !strcmp(role, "alternate") || !strcmp(role, "supplementary") || !strcmp(role, "commentary") + || !strcmp(role, "dub") || !strcmp(role, "description") || !strcmp(role, "sign") + || !strcmp(role, "metadata") || !strcmp(role, "enhanced-audio- intelligibility") + ) { + uri = "urn:mpeg:dash:role:2011"; + if (!strcmp(role, "main")) main_role_set = GF_TRUE; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Unrecognized role %s - using GPAC urn for schemaID\n", role)); + uri = "urn:gpac:dash:role:2013"; + } + desc = gf_mpd_descriptor_new(NULL, uri, role); + gf_list_add(set->role, desc); + } + } + //set SRD + if (!i && ds->srd.z && ds->srd.w) { + char value[256]; + GF_MPD_Descriptor *desc; + if (ds->dep_id) { + sprintf(value, "1,%d,%d,%d,%d", ds->srd.x, ds->srd.y, ds->srd.z, ds->srd.w); + desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:dash:srd:2014", value); + gf_list_add(set->supplemental_properties, desc); + } else { + if (gf_sys_old_arch_compat()) { + sprintf(value, "1,0,0,0,0"); //compat with old arch, don't set W and H + } else { + sprintf(value, "1,0,0,0,0,%d,%d", ds->srd.z, ds->srd.w); + } + desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:dash:srd:2014", value); + gf_list_add(set->essential_properties, desc); + } + } + //set HDR + if (ds->hdr_type > DASHER_HDR_NONE) { + char value[256]; + GF_MPD_Descriptor* desc; + sprintf(value, "9"); + desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:mpegB:cicp:ColourPrimaries", value); + gf_list_add(set->essential_properties, desc); + sprintf(value, "9"); + desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:mpegB:cicp:MatrixCoefficients", value); + gf_list_add(set->essential_properties, desc); + + if (ds->hdr_type==DASHER_HDR_PQ10) { + sprintf(value, "16"); + desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:mpegB:cicp:TransferCharacteristics", value); + gf_list_add(set->essential_properties, desc); + } + + if (ds->hdr_type == DASHER_HDR_HLG) { + sprintf(value, "14"); + desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:mpegB:cicp:TransferCharacteristics", value); + gf_list_add(set->essential_properties, desc); + sprintf(value, "18"); + desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:mpegB:cicp:TransferCharacteristics", value); + gf_list_add(set->supplemental_properties, desc); + } + } + } + if (ctx->check_main_role && !main_role_set) { + GF_MPD_Descriptor *desc; + desc = gf_mpd_descriptor_new(NULL, "urn:mpeg:dash:role:2011", "main"); + gf_list_add(set->role, desc); + } +} + +static void dasher_check_bitstream_swicthing(GF_DasherCtx *ctx, GF_MPD_AdaptationSet *set) +{ + u32 i, j, count; + Bool use_inband = (ctx->bs_switch==DASHER_BS_SWITCH_INBAND) ? GF_TRUE : GF_FALSE; + Bool use_multi = (ctx->bs_switch==DASHER_BS_SWITCH_MULTI) ? GF_TRUE : GF_FALSE; + GF_MPD_Representation *base_rep = gf_list_get(set->representations, 0); + GF_DashStream *base_ds; + + if (ctx->m2ts) { + set->bitstream_switching = GF_TRUE; + return; + } + if (ctx->bs_switch==DASHER_BS_SWITCH_OFF) return; + if (!base_rep) return; + base_ds = base_rep->playback.udta; + + count = gf_list_count(set->representations); + if (count==1) { + if (ctx->bs_switch==DASHER_BS_SWITCH_FORCE) set->bitstream_switching=GF_TRUE; + else if (ctx->bs_switch==DASHER_BS_SWITCH_INBAND) { + base_ds->inband_params = GF_TRUE; + } + return; + } + + for (i=1; irepresentations, i); + GF_DashStream *ds = rep->playback.udta; + //same codec ID + if (ds->codec_id == base_ds->codec_id) { + //we will use inband params, so bs switching is OK + if (use_inband || use_multi) continue; + //we have deps, cannot use bitstream switching + if (ds->dep_id) return; + //we consider we can switch in non-inband only if we have same CRC for the decoder config + if (base_ds->dsi_crc == ds->dsi_crc) continue; + //not the same config, no BS switching + return; + } + //dependencies / different codec IDs, cannot use bitstream switching + return; + } + //ok we can use BS switching, ensure we use the same timescale for every stream + set->bitstream_switching = GF_TRUE; + + for (i=0; irepresentations, i); + GF_DashStream *ds = rep->playback.udta; + for (j=i+1; jrepresentations, j); + a_ds = rep->playback.udta; + if (a_ds->stream_type != ds->stream_type) continue; + if (a_ds->timescale != ds->timescale) + a_ds->force_timescale = ds->timescale; + } + } +} + +static void dasher_open_destination(GF_Filter *filter, GF_DasherCtx *ctx, GF_MPD_Representation *rep, const char *szInitURL, Bool trash_init) +{ + GF_Err e; + Bool has_frag=GF_FALSE; + Bool has_subs=GF_FALSE; + Bool has_strun=GF_FALSE; + Bool has_vodcache=GF_FALSE; + char sep_args = gf_filter_get_sep(filter, GF_FS_SEP_ARGS); + char sep_name = gf_filter_get_sep(filter, GF_FS_SEP_NAME); + const char *dst_args, *trailer_args=NULL; + char *szDST = NULL; + char szSRC[100]; + + if (ctx->sigfrag) + return; + + GF_DashStream *ds = rep->playback.udta; + if (ds->muxed_base) return; + + gf_dynstrcat(&szDST, szInitURL, NULL); + if (ctx->out_path) { + char *rel = NULL; + if (ctx->do_m3u8 && ds->hls_vp_name) { + char *tmp = gf_url_concatenate(ctx->out_path, ds->hls_vp_name); + if (tmp) { + rel = gf_url_concatenate(tmp, szInitURL); + gf_free(tmp); + } + } + if (!rel) + rel = gf_url_concatenate(ctx->out_path, szInitURL); + if (rel) { + gf_free(szDST); + szDST = rel; + } + } + + sprintf(szSRC, "%cgfopt", sep_args); + gf_dynstrcat(&szDST, szSRC, NULL); + + dst_args = gf_filter_get_dst_args(filter); + if (dst_args) { + char szKey[20], *sep; + sprintf(szSRC, "%c", sep_args); + gf_dynstrcat(&szDST, szSRC, NULL); + + gf_dynstrcat(&szDST, dst_args, NULL); + sprintf(szKey, "%c%c", sep_args, sep_args); + sep = strstr(szDST, szKey); + if (sep) { + sep[0] = 0; + trailer_args = strstr(dst_args, szKey); + } + //look for frag arg + sprintf(szKey, "%cfrag", sep_args); + if (strstr(dst_args, szKey)) has_frag = GF_TRUE; + else { + sprintf(szKey, "%csfrag", sep_args); + if (strstr(dst_args, szKey)) has_frag = GF_TRUE; + } + //look for subs_sidx arg + sprintf(szKey, "%csubs_sidx", sep_args); + if (strstr(dst_args, szKey)) has_subs = GF_TRUE; + + sprintf(szKey, "%cstrun", sep_args); + if (strstr(dst_args, szKey)) has_strun = GF_TRUE; + + sprintf(szKey, "%cvodcache", sep_args); + if (strstr(dst_args, szKey)) has_vodcache = GF_TRUE; + } + + if (trash_init) { + sprintf(szSRC, "%cnoinit", sep_args); + gf_dynstrcat(&szDST, szSRC, NULL); + } + if (!has_frag) { + sprintf(szSRC, "%cfrag", sep_args); + gf_dynstrcat(&szDST, szSRC, NULL); + } + if (!has_subs && ctx->sseg) { + sprintf(szSRC, "%csubs_sidx%c0", sep_args, sep_name); + gf_dynstrcat(&szDST, szSRC, NULL); + } + if (ctx->cues && !has_strun) { + sprintf(szSRC, "%cstrun", sep_args); + gf_dynstrcat(&szDST, szSRC, NULL); + } + if (ctx->styp) { + sprintf(szSRC, "%cstyp=%s", sep_args, ctx->styp); + gf_dynstrcat(&szDST, szSRC, NULL); + } + //override xps inband declaration in args + sprintf(szSRC, "%cxps_inband%c%s", sep_args, sep_name, ds->inband_params ? "all" : "no"); + gf_dynstrcat(&szDST, szSRC, NULL); + + if (ctx->no_fragments_defaults) { + sprintf(szSRC, "%cnofragdef", sep_args ); + gf_dynstrcat(&szDST, szSRC, NULL); + } + switch (ctx->pssh) { + case GF_DASH_PSSH_MPD: + sprintf(szSRC, "%cpsshs%cnone", sep_args, sep_name); + break; + case GF_DASH_PSSH_MOOF: + case GF_DASH_PSSH_MOOF_MPD: + sprintf(szSRC, "%cpsshs%cmoof", sep_args, sep_name); + break; + default: + sprintf(szSRC, "%cpsshs%cmoov", sep_args, sep_name); + break; + } + gf_dynstrcat(&szDST, szSRC, NULL); + + //patch for old arch: make sure we don't have any extra free box before the sidx + //we could also use vodcache=insert but this might break http outputs + if (gf_sys_old_arch_compat() && !has_vodcache && ctx->sseg) { + sprintf(szSRC, "%cvodcache=on", sep_args ); + if (!strstr(szDST, szSRC)) + gf_dynstrcat(&szDST, szSRC, NULL); + } + sprintf(szSRC, "%cmime=%s", sep_args, rep->mime_type); + gf_dynstrcat(&szDST, szSRC, NULL); + + if (ds->moof_sn>1) { + sprintf(szSRC, "%cmsn%c%d", sep_args, sep_name, ds->moof_sn); + gf_dynstrcat(&szDST, szSRC, NULL); + } + if (ds->moof_sn_inc>1) { + sprintf(szSRC, "%cmsninc%c%d", sep_args, sep_name, ds->moof_sn_inc); + gf_dynstrcat(&szDST, szSRC, NULL); + } + if (ds->sscale) { + sprintf(szSRC, "%cmoovts%c-1", sep_args, sep_name); + gf_dynstrcat(&szDST, szSRC, NULL); + } + if (trailer_args) + gf_dynstrcat(&szDST, trailer_args, NULL); + + ds->dst_filter = gf_filter_connect_destination(filter, szDST, &e); + gf_free(szDST); + szDST = NULL; + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Couldn't create output file %s: %s\n", szInitURL, gf_error_to_string(e) )); + ctx->in_error = GF_TRUE; + return; + } + //reset any sourceID given in the dst_arg and assign sourceID to be the dasher filter + sprintf(szSRC, "MuxSrc%cdasher_%p", sep_name, ds->dst_filter); + gf_filter_reset_source(ds->dst_filter); + gf_filter_set_source(ds->dst_filter, filter, szSRC); +} + +static void dasher_gather_deps(GF_DasherCtx *ctx, u32 dependency_id, GF_List *multi_tracks) +{ + u32 i, count = gf_list_count(ctx->current_period->streams); + for (i=0; icurrent_period->streams, i); + if (ds->id == dependency_id) { + if (ds->tile_base) continue; + + assert(ds->opid); + gf_list_insert(multi_tracks, ds->opid, 0); + if (ds->dep_id) dasher_gather_deps(ctx, ds->dep_id, multi_tracks); + } + } +} + +static void dasher_update_dep_list(GF_DasherCtx *ctx, GF_DashStream *ds, const char *ref_type) +{ + u32 i, j, count; + GF_PropertyValue *p = (GF_PropertyValue *) gf_filter_pid_get_property_str(ds->opid, ref_type); + if (!p) return; + count = gf_list_count(ctx->current_period->streams); + for (i=0; ivalue.uint_list.nb_items; i++) { + for (j=0; jcurrent_period->streams, j); + if ((a_ds->id == p->value.uint_list.vals[i]) && a_ds->pid_id) { + p->value.uint_list.vals[j] = a_ds->pid_id; + } + } + } +} + +static void dasher_open_pid(GF_Filter *filter, GF_DasherCtx *ctx, GF_DashStream *ds, GF_List *multi_pids, Bool init_trashed) +{ + GF_DashStream *base_ds = ds->muxed_base ? ds->muxed_base : ds; + char szSRC[1024]; + + if (ctx->sigfrag || ctx->in_error) + return; + + assert(!ds->opid); + assert(base_ds->dst_filter); + + if (ds->tile_base && !init_trashed) { + s32 res = gf_list_find(ctx->postponed_pids, ds); + if (res < 0) { + gf_list_add(ctx->postponed_pids, ds); + return; + } else { + gf_list_rem(ctx->postponed_pids, res); + } + } else if (!ds->tile_base) { + gf_list_del_item(ctx->postponed_pids, ds); + } + + //tile base not live profile, make sure all our deps are ready + if (ds->tile_base && !ctx->sseg) { + u32 i, count = gf_list_count(ds->complementary_streams); + for (i=0; icomplementary_streams, i); + //dep not ready + if (!a_ds->opid) { + if (gf_list_find(ctx->postponed_pids, a_ds)<0) { + gf_list_add(ctx->postponed_pids, a_ds); + } + gf_list_del_item(ctx->postponed_pids, ds); + gf_list_add(ctx->postponed_pids, ds); + return; + } + } + } + + if (ctx->sigfrag) + return; + + sprintf(szSRC, "dasher_%p", base_ds->dst_filter); + ds->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ds->opid, ds->ipid); + if (!ds->muxed_base) { + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("*")); + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_MIME, &PROP_STRING(ds->rep->mime_type)); + } + if (ds->nb_cues) { + u32 ncues = ds->nb_cues; + if ((ds->cues[0].sample_num>0) || (ds->cues[0].cts>0) || (ds->cues[0].dts>0)) + ncues++; + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_DASH_SEGMENTS, &PROP_UINT(ncues) ); + } + + gf_filter_pid_require_source_id(ds->opid); + + if (ctx->pssh == GF_DASH_PSSH_MPD) { + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_CENC_PSSH, NULL); + } + + //set init filename +// if (ds->init_seg) gf_filter_pid_set_property(ds->opid, GF_PROP_PID_OUTPATH, &PROP_STRING(ds->init_seg)); + + //force PID ID + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_ID, &PROP_UINT(ds->pid_id) ); + if (ds->dep_pid_id) + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_DEPENDENCY_ID, &PROP_UINT(ds->dep_pid_id) ); + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_MUX_SRC, &PROP_STRING(szSRC) ); + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_DASH_MODE, &PROP_UINT(ctx->sseg ? 2 : 1) ); + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_DASH_DUR, &PROP_DOUBLE(ds->dash_dur) ); + + if (ds->id != ds->pid_id) { + dasher_update_dep_list(ctx, ds, "isom:scal"); + dasher_update_dep_list(ctx, ds, "isom:sabt"); + } + + /*timescale forced (bitstream switching) */ + if (ds->force_timescale) + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ds->force_timescale) ); + + if (multi_pids) { + s32 idx = 1+gf_list_find(multi_pids, ds->ipid); + assert(idx>0); + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_DASH_MULTI_PID, &PROP_POINTER(multi_pids) ); + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_DASH_MULTI_PID_IDX, &PROP_UINT(idx) ); + } + + + if (ds->tile_base && !ctx->sseg && !ctx->sfile) { + u32 i, count = gf_list_count(ds->complementary_streams); + if (!ds->multi_tracks) ds->multi_tracks = gf_list_new(); + gf_list_reset(ds->multi_tracks); + + //gather all streams depending on our base + for (i=0; icomplementary_streams, i); + assert(a_ds->opid); + gf_list_add(ds->multi_tracks, a_ds->opid); + } + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_DASH_MULTI_TRACK, &PROP_POINTER(ds->multi_tracks) ); + } + if (ds->dep_id) { + if (!ds->multi_tracks) ds->multi_tracks = gf_list_new(); + gf_list_reset(ds->multi_tracks); + dasher_gather_deps(ctx, ds->dep_id, ds->multi_tracks); + if (gf_list_count(ds->multi_tracks)) { + gf_filter_pid_set_property(ds->opid, GF_PROP_PID_DASH_MULTI_TRACK, &PROP_POINTER(ds->multi_tracks) ); + } else { + gf_list_del(ds->multi_tracks); + ds->multi_tracks = NULL; + } + + } +} + +static Bool dasher_template_use_source_url(const char *template) +{ + if (strstr(template, "$File$") != NULL) return GF_TRUE; + else if (strstr(template, "$FSRC$") != NULL) return GF_TRUE; + else if (strstr(template, "$SourcePath$") != NULL) return GF_TRUE; + else if (strstr(template, "$FURL$") != NULL) return GF_TRUE; + else if (strstr(template, "$URL$") != NULL) return GF_TRUE; + return GF_FALSE; +} + +static void dasher_set_content_components(GF_DashStream *ds) +{ + GF_MPD_ContentComponent *component; + GF_DashStream *base_ds = ds->muxed_base ? ds->muxed_base : ds; + + GF_SAFEALLOC(component, GF_MPD_ContentComponent); + if (!component) return; + + component->id = ds->pid_id; + switch (ds->stream_type) { + case GF_STREAM_TEXT: + component->type = gf_strdup("text"); + break; + case GF_STREAM_VISUAL: + component->type = gf_strdup("video"); + break; + case GF_STREAM_AUDIO: + component->type = gf_strdup("audio"); + break; + case GF_STREAM_SCENE: + case GF_STREAM_OD: + default: + component->type = gf_strdup("application"); + break; + } + /*if lang not specified at adaptationSet level, put it here*/ + if (!base_ds->set->lang && ds->lang && strcmp(ds->lang, "und")) { + component->lang = gf_strdup(ds->lang); + } + gf_list_add(base_ds->set->content_component, component); +} + +static void dasher_setup_sources(GF_Filter *filter, GF_DasherCtx *ctx, GF_MPD_AdaptationSet *set) +{ + char szDASHTemplate[GF_MAX_PATH]; + char szTemplate[GF_MAX_PATH]; + char szSegmentName[GF_MAX_PATH]; + char szInitSegmentTemplate[GF_MAX_PATH]; + char szInitSegmentFilename[GF_MAX_PATH]; + char szIndexSegmentName[GF_MAX_PATH]; + char szSetFileSuffix[200], szDASHSuffix[220]; + const char *template = NULL; + u32 as_id = 0; + Bool single_template = GF_TRUE; + u32 i, j, count, nb_base, nb_streams; + GF_List *multi_pids = NULL; + u32 set_timescale = 0; + Bool init_template_done=GF_FALSE; + Bool use_inband = (ctx->bs_switch==DASHER_BS_SWITCH_INBAND) ? GF_TRUE : GF_FALSE; + Bool template_use_source = GF_FALSE; + Bool split_rep_names = GF_FALSE; + Bool split_set_names = GF_FALSE; + GF_DashStream *ds; + GF_MPD_Representation *rep = gf_list_get(set->representations, 0); + if (!rep) { + assert(0); + return; + } + ds = rep->playback.udta; + + count = gf_list_count(set->representations); + + if (!ctx->sigfrag) { + assert(ctx->template); + template = ((GF_DashStream *)set->udta)->template; + } + + for (i=0; irepresentations, i); + ds = rep->playback.udta; + if (!ds->template && !template) {} + else if (ds->template && template && !strcmp(ds->template, template) ) { + } else { + single_template = GF_FALSE; + } + if (ds->template) template_use_source = dasher_template_use_source_url(ds->template); + + if (template_use_source) { + single_template = GF_FALSE; + } + + if (ds->as_id && !as_id) + as_id = ds->as_id; + + if (ds->fps.den && ( (ds->fps.num*set->max_framerate.den) >= (s32) (set->max_framerate.num*ds->fps.den) )) { + set->max_framerate.num = ds->fps.num; + set->max_framerate.den = ds->fps.den; + gf_media_get_reduced_frame_rate(&set->max_framerate.num, &set->max_framerate.den); + } + if (ds->width && ds->height) { + if (!set->par) { + GF_SAFEALLOC(set->par, GF_MPD_Fractional); + } + if (set->par) { + set->par->num = ds->width; + set->par->den = ds->height; + gf_media_reduce_aspect_ratio(&set->par->num, &set->par->den); + } + } + } + if (!template) template = ctx->template; + + if (as_id) { + set->id = ds->as_id; + } + if (ctx->sseg) { + set->segment_alignment = GF_TRUE; + set->starts_with_sap = 1; + } + + if (count==1) + single_template = GF_TRUE; + else if (single_template) { + //for regular reps, if we depend on filename we cannot mutualize the template + if (dasher_template_use_source_url(template) ) { + single_template = GF_FALSE; + } + //and for scalable reps, if we don't have bandwidth /repID we cannot mutualize the template + else if (gf_list_count(ds->complementary_streams) ) { + if (strstr(template, "$Bandwidth$") != NULL) single_template = GF_FALSE; + else if (strstr(template, "$RepresentationId$") != NULL) single_template = GF_FALSE; + } + } + + if (set->lang) gf_free(set->lang); + set->lang = gf_strdup(ds->lang ? ds->lang : "und"); + + //check all streams in active period not in this set + nb_streams = gf_list_count(ctx->current_period->streams); + for (i=0; icurrent_period->streams, i); + if (ads->set == set) continue; + frag_uri = strrchr(ds->src_url, '#'); + if (frag_uri) len1 = (u32) (frag_uri-1 - ds->src_url); + else len1 = (u32) strlen(ds->src_url); + frag_uri = strrchr(ads->src_url, '#'); + if (frag_uri) len2 = (u32) (frag_uri-1 - ads->src_url); + else len2 = (u32) strlen(ads->src_url); + + if ((len1==len2) && !strncmp(ds->src_url, ads->src_url, len1)) { + split_set_names = GF_TRUE; + break; + } + } + + if (split_set_names) { + sprintf(szSetFileSuffix, "_track%d_", ds->id); + } + + if (ctx->timescale>0) { + set_timescale = ctx->timescale; + } else { + u32 first_timescale; + rep = gf_list_get(set->representations, 0); + ds = rep->playback.udta; + first_timescale = ds->timescale; + for (i=1; irepresentations, i); + ds = rep->playback.udta; + if (ds->timescale != first_timescale) { + //we cannot use a single template if enforcing timescales which are not identical + single_template = GF_FALSE; + break; + } + } + } + + for (i=0; isigfrag) + break; + + rep = gf_list_get(set->representations, i); + ds = rep->playback.udta; + + if (!dasher_template_use_source_url(template)) + continue; + + if (ds->muxed_base) + continue; + + for (j=i+1; jrepresentations, j); + a_ds = rep->playback.udta; + + if (a_ds->muxed_base == ds) + continue; + + p1 = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_FILEPATH); + p2 = gf_filter_pid_get_property(a_ds->ipid, GF_PROP_PID_FILEPATH); + if (gf_props_equal(p1, p2)) split_rep_names = GF_TRUE; + p1 = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_URL); + p2 = gf_filter_pid_get_property(a_ds->ipid, GF_PROP_PID_URL); + if (gf_props_equal(p1, p2)) split_rep_names = GF_TRUE; + + if (split_rep_names) break; + } + if (split_rep_names) break; + } + + //assign PID IDs - we assume only one component of a given media type per adaptation set + //and assign the same PID ID for each component of the same type + //we could refine this using roles, but most HAS solutions don't use roles at the mulitplexed level + for (i=0; irepresentations, i); + ds = rep->playback.udta; + if (ds->pid_id) continue; + //in bitstream switching mode, ensure each track in the set has the same ID + if (set->bitstream_switching) { + ctx->next_pid_id_in_period++; + ds->pid_id = ctx->next_pid_id_in_period; + + for (j=i+1; jrepresentations, j); + a_ds = rep->playback.udta; + if (a_ds->pid_id) continue; + if (a_ds->dep_id) continue; + if (a_ds->stream_type == ds->stream_type) a_ds->pid_id = ds->pid_id; + } + } + //otherwise copy over the source PID + else { + ds->pid_id = ds->id; + } + } + + for (i=0; irepresentations, i); + ds = rep->playback.udta; + if (!ds->dep_id) continue; + + for (j=i+1; jrepresentations, j); + a_ds = rep->playback.udta; + if (ds->dep_id == a_ds->id) { + ds->dep_pid_id = a_ds->pid_id; + break; + } + } + } + + //this is crude because we don't copy the properties, we just pass a list of pids to the destination muxer !! + //we should cleanup one of these days + if (set->bitstream_switching && (ctx->bs_switch==DASHER_BS_SWITCH_MULTI)) { + multi_pids = gf_list_new(); + for (i=0; irepresentations, i); + ds = rep->playback.udta; + if (ds->owns_set) ds->multi_pids = multi_pids; + gf_list_add(multi_pids, ds->ipid); + } + } + + if (ctx->cp!=GF_DASH_CPMODE_REPRESENTATION) { + gf_mpd_del_list(set->content_protection, gf_mpd_descriptor_free, 0); + set->content_protection = dasher_get_content_protection_desc(ctx, NULL, set); + } + + for (i=0; irepresentations, i); + ds = rep->playback.udta; + + //remove representations for streams muxed with others, but still open the output + if (ds->muxed_base) { + GF_DashStream *ds_set = set->udta; + gf_list_rem(set->representations, i); + i--; + count--; + assert(ds_set->nb_rep); + ds_set->nb_rep--; + assert(ds->muxed_base->dst_filter); + gf_list_transfer(ds->muxed_base->rep->audio_channels, rep->audio_channels); + gf_list_transfer(ds->muxed_base->rep->base_URLs, rep->base_URLs); + gf_list_transfer(ds->muxed_base->rep->content_protection , rep->content_protection); + gf_list_transfer(ds->muxed_base->rep->essential_properties , rep->essential_properties); + gf_list_transfer(ds->muxed_base->rep->frame_packing , rep->frame_packing); + gf_list_transfer(ds->muxed_base->rep->other_descriptors , rep->other_descriptors); + gf_list_transfer(ds->muxed_base->rep->supplemental_properties , rep->supplemental_properties); + + gf_mpd_representation_free(ds->rep); + ds->rep = NULL; + + if (!gf_list_count(ds->set->content_component)) { + dasher_set_content_components(ds->muxed_base); + } + dasher_set_content_components(ds); + assert(!multi_pids); + //open PID + dasher_open_pid(filter, ctx, ds, NULL, GF_FALSE); + continue; + } + if (ds->template) strcpy(szTemplate, ds->template); + else strcpy(szTemplate, ctx->template ? ctx->template : ""); + + if (use_inband) + ds->inband_params = GF_TRUE; + + //if bitstream switching and templating, only set for the first one + if (i && set->bitstream_switching && ctx->stl && single_template) continue; + + if (!set_timescale) set_timescale = ds->timescale; + + if (ctx->timescale<0) ds->mpd_timescale = ds->timescale; + else ds->mpd_timescale = set_timescale; + + if (ds->nb_repeat && !ctx->loop) { + if (split_set_names) { + sprintf(szDASHSuffix, "%sp%d_", szSetFileSuffix, ds->nb_repeat+1); + } else { + sprintf(szDASHSuffix, "p%d_", ds->nb_repeat); + } + use_dash_suffix = GF_TRUE; + } else if (split_set_names) { + strcpy(szDASHSuffix, szSetFileSuffix); + use_dash_suffix = GF_TRUE; + } + + //resolve segment template + e = gf_filter_pid_resolve_file_template(ds->ipid, szTemplate, szDASHTemplate, 0, use_dash_suffix ? szDASHSuffix : NULL); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Cannot resolve template name %s, cannot derive output segment names, disabling rep %s\n", szTemplate, ds->src_url)); + gf_filter_pid_set_discard(ds->ipid, GF_TRUE); + ds->done = 1; + continue; + } + if (single_template && ds->split_set_names && !use_dash_suffix) { + char szStrName[20]; + sprintf(szStrName, "_set%d", 1 + gf_list_find(ctx->current_period->period->adaptation_sets, set) ); + strcat(szDASHTemplate, szStrName); + } + else if (split_rep_names) { + char szStrName[20]; + sprintf(szStrName, "_rep%d", 1 + gf_list_find(set->representations, ds->rep) ); + strcat(szDASHTemplate, szStrName); + } + + idx_ext = NULL; + if (ctx->m2ts) { + seg_ext = init_ext = "ts"; + if (!ctx->do_m3u8 && (ctx->subs_sidx>=0) ) + idx_ext = "idx"; + } else { + const char *def_ext = NULL; + + seg_ext = init_ext = NULL; + + if (ctx->muxtype==DASHER_MUX_TS) def_ext = "ts"; + else if (ctx->muxtype==DASHER_MUX_MKV) def_ext = "mkv"; + else if (ctx->muxtype==DASHER_MUX_WEBM) def_ext = "webm"; + else if (ctx->muxtype==DASHER_MUX_OGG) def_ext = "ogg"; + else if (ctx->muxtype==DASHER_MUX_RAW) { + char *ext = (char *) gf_codecid_file_ext(ds->codec_id); + strncpy(szRawExt, ext ? ext : "raw", 19); + szRawExt[19] = 0; + ext = strchr(szRawExt, '|'); + if (ext) ext[0] = 0; + def_ext = szRawExt; + } + + if (ctx->segext && !stricmp(ctx->segext, "null")) { + seg_ext = NULL; + } else { + seg_ext = ctx->segext; + if (!seg_ext) seg_ext = def_ext ? def_ext : "m4s"; + } + if (ctx->initext && !stricmp(ctx->initext, "null")) { + init_ext = NULL; + } else { + init_ext = ctx->initext; + if (!init_ext) init_ext = def_ext ? def_ext : "mp4"; + } + } + + is_bs_switch = set->bitstream_switching; + //patch for old arch compat + if (ds->tile_base || (ds->codec_id==GF_CODECID_HEVC_TILES)) is_bs_switch = GF_FALSE; + + //get final segment template with path resolution - output file name is NULL, we already have solved this + gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_TEMPLATE_WITH_PATH, is_bs_switch, szSegmentName, ds->rep_id, NULL, szDASHTemplate, seg_ext, 0, 0, 0, ctx->stl); + ds->seg_template = gf_strdup(szSegmentName); + + //get final segment template - output file name is NULL, we already have solved this + gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_TEMPLATE, is_bs_switch, szSegmentName, ds->rep_id, NULL, szDASHTemplate, seg_ext, 0, 0, 0, ctx->stl); + + //get index templates + if (idx_ext) { + gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_REPINDEX_TEMPLATE_WITH_PATH, is_bs_switch, szIndexSegmentName, ds->rep_id, NULL, szDASHTemplate, idx_ext, 0, 0, 0, ctx->stl); + ds->idx_template = gf_strdup(szIndexSegmentName); + + gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_REPINDEX_TEMPLATE, is_bs_switch, szIndexSegmentName, ds->rep_id, NULL, szDASHTemplate, idx_ext, 0, 0, 0, ctx->stl); + } + + //patch for old arch, override template name for init segment UGLY, to remove asap + if (gf_sys_old_arch_compat() && ds->tile_base && !ctx->sseg && !ctx->sfile ) { + char *sep; + char szTemp[100]; + const char *out_path = gf_file_basename(ctx->out_path); + strcpy(szDASHTemplate, out_path); + sep = gf_file_ext_start(szDASHTemplate); + if (sep) sep[0]=0; + sprintf(szTemp, "_set%d_", 1+gf_list_find(ctx->current_period->period->adaptation_sets, ds->set) ); + strcat(szDASHTemplate, szTemp); + } + + //get final init name - output file name is NULL, we already have solved this + gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_INITIALIZATION, is_bs_switch, szInitSegmentFilename, ds->rep_id, NULL, szDASHTemplate, init_ext, 0, ds->bitrate, 0, ctx->stl); + ds->init_seg = gf_strdup(szInitSegmentFilename); + + //get final init template name - output file name is NULL, we already have solved this + gf_media_mpd_format_segment_name(init_template_mode, is_bs_switch, szInitSegmentTemplate, ds->rep_id, NULL, szDASHTemplate, init_ext, 0, 0, 0, ctx->stl); + + if (ctx->sigfrag) { + if (ctx->template || ds->template) { + } else { + strcpy(szInitSegmentFilename, gf_file_basename(ds->src_url)); + strcpy(szSegmentName, gf_file_basename(ds->src_url)); + } + } + + if (ctx->store_seg_states) { + assert(!ds->pending_segment_states); + ds->pending_segment_states = gf_list_new(); + } + /* baseURLs */ + nb_base = ds->p_base_url ? gf_list_count(ds->p_base_url->value.string_list) : 0; + for (j=0; jp_base_url->value.string_list, j); + GF_SAFEALLOC(base_url, GF_MPD_BaseURL); + if (base_url) { + base_url->URL = gf_strdup(url); + gf_list_add(rep->base_URLs, base_url); + } + } + //commented for compat with old arch + //if (ds->codec_id==GF_CODECID_HEVC_TILES) skip_init = GF_TRUE; + if (ctx->m2ts) skip_init = GF_TRUE; + else if (ctx->muxtype==DASHER_MUX_RAW) skip_init = GF_TRUE; + else if (ctx->muxtype==DASHER_MUX_TS) skip_init = GF_TRUE; + + + //we use segment template + if (ctx->tpl) { + GF_MPD_SegmentTemplate *seg_template; + //first rep in set and bs switching or single template, create segment template at set level + if (!i && (set->bitstream_switching || single_template) ) { + init_template_done = GF_TRUE; + seg_template = NULL; + if (!skip_init || single_template) { + GF_SAFEALLOC(seg_template, GF_MPD_SegmentTemplate); + if (seg_template) + seg_template->initialization = skip_init ? NULL : gf_strdup(szInitSegmentTemplate); + } + + dasher_open_destination(filter, ctx, rep, szInitSegmentFilename, skip_init); + + if (single_template) { + seg_template->media = gf_strdup(szSegmentName); + if (ds->idx_template) + seg_template->index = gf_strdup(szIndexSegmentName); + + seg_template->timescale = ds->mpd_timescale; + seg_template->start_number = ds->startNumber ? ds->startNumber : 1; + seg_template->duration = (u64)(ds->dash_dur * ds->mpd_timescale); + if (ctx->asto>0) { + seg_template->availability_time_offset = ctx->asto; + } + } else if (seg_template) { + seg_template->start_number = (u32)-1; + } + set->segment_template = seg_template; + } + //non-first rep in set and single template, only open destination + if (i && single_template) { + dasher_open_destination(filter, ctx, rep, szInitSegmentFilename, (set->bitstream_switching || skip_init) ? GF_TRUE : GF_FALSE); + } + //first rep in set and no bs switching or mutliple templates, create segment template at rep level + else if (i || !single_template) { + GF_SAFEALLOC(seg_template, GF_MPD_SegmentTemplate); + if (seg_template) { + if (!init_template_done) { + seg_template->initialization = skip_init ? NULL : gf_strdup(szInitSegmentTemplate); + dasher_open_destination(filter, ctx, rep, szInitSegmentFilename, skip_init); + } else if (i) { + dasher_open_destination(filter, ctx, rep, szInitSegmentFilename, (set->bitstream_switching || skip_init) ? GF_TRUE : GF_FALSE); + } + seg_template->media = gf_strdup(szSegmentName); + if (ds->idx_template) + seg_template->index = gf_strdup(szIndexSegmentName); + seg_template->duration = (u64)(ds->dash_dur * ds->mpd_timescale); + seg_template->timescale = ds->mpd_timescale; + seg_template->start_number = ds->startNumber ? ds->startNumber : 1; + if (ctx->asto > 0) { + seg_template->availability_time_offset = ctx->asto; + } + rep->segment_template = seg_template; + } + } + } + /*we are using a single file or segment, use base url*/ + else if (ctx->sseg || ctx->sfile) { + GF_MPD_BaseURL *baseURL; +/* char *segext = (ctx->segext && !stricmp(ctx->segext, "null")) ? NULL : "mp4"; + if (ctx->m2ts) segext = "ts"; + + //use GF_DASH_TEMPLATE_INITIALIZATION_SKIPINIT to get rid of default "init" added for init templates + gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_INITIALIZATION, set->bitstream_switching, szInitSegmentName, ds->rep_id, NULL, szDASHTemplate, segext, 0, 0, 0, ctx->stl); +*/ + + if (ds->init_seg) gf_free(ds->init_seg); + ds->init_seg = gf_strdup(szInitSegmentFilename); + + GF_SAFEALLOC(baseURL, GF_MPD_BaseURL); + if (!baseURL) continue; + + if (!rep->base_URLs) rep->base_URLs = gf_list_new(); + gf_list_add(rep->base_URLs, baseURL); + + if (ctx->sseg) { + GF_MPD_SegmentBase *segment_base; + baseURL->URL = gf_strdup(szInitSegmentFilename); + GF_SAFEALLOC(segment_base, GF_MPD_SegmentBase); + if (!segment_base) continue; + rep->segment_base = segment_base; + dasher_open_destination(filter, ctx, rep, szInitSegmentFilename, GF_FALSE); + } else { + GF_MPD_SegmentList *seg_list; + GF_SAFEALLOC(seg_list, GF_MPD_SegmentList); + if (!seg_list) continue; + GF_SAFEALLOC(seg_list->initialization_segment, GF_MPD_URL); + if (!seg_list->initialization_segment) continue; + seg_list->start_number = (u32) -1; + baseURL->URL = gf_strdup(szInitSegmentFilename); + seg_list->dasher_segment_name = gf_strdup(szSegmentName); + seg_list->timescale = ds->mpd_timescale; + seg_list->duration = (u64) (ds->mpd_timescale * ds->dash_dur); + seg_list->segment_URLs = gf_list_new(); + rep->segment_list = seg_list; + ds->pending_segment_urls = gf_list_new(); + + dasher_open_destination(filter, ctx, rep, szInitSegmentFilename, skip_init); + } + } + //no template, no single file, we need a file list + else { + GF_MPD_SegmentList *seg_list; + GF_SAFEALLOC(seg_list, GF_MPD_SegmentList); + if (!seg_list) continue; + + if (!skip_init) { + GF_SAFEALLOC(seg_list->initialization_segment, GF_MPD_URL); + if (!seg_list->initialization_segment) continue; + + seg_list->initialization_segment->sourceURL = gf_strdup(szInitSegmentFilename); + } + seg_list->dasher_segment_name = gf_strdup(szSegmentName); + seg_list->timescale = ds->mpd_timescale; + seg_list->segment_URLs = gf_list_new(); + seg_list->start_number = (u32) -1; + seg_list->duration = (u64) (ds->mpd_timescale * ds->dash_dur); + rep->segment_list = seg_list; + ds->pending_segment_urls = gf_list_new(); + + dasher_open_destination(filter, ctx, rep, szInitSegmentFilename, skip_init); + } + + //open PID + dasher_open_pid(filter, ctx, ds, multi_pids, skip_init); + } +} + +static void dahser_purge_segment_timeline(GF_DashStream *ds, GF_MPD_SegmentTimeline *stl, GF_DASH_SegmentContext *sctx) +{ + GF_MPD_SegmentTimelineEntry *stl_e = gf_list_get(stl->entries, 0); + if (!stl_e) return; + + if (stl_e->repeat_count) { + stl_e->repeat_count--; + stl_e->start_time += stl_e->duration; + } else { + u64 start_time = stl_e->start_time + stl_e->duration; + gf_list_rem(stl->entries, 0); + gf_free(stl_e); + stl_e = gf_list_get(stl->entries, 0); + if (!stl_e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] No timeline entry after currently removed segment, cannot update start time\n" )); + return; + } + + if (!stl_e->start_time) stl_e->start_time = start_time; + else if (stl_e->start_time != start_time) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Mismatch in segment timeline while purging, new start time "LLU" but entry indicates "LLU", keeping original one\n", start_time, stl_e->start_time )); + } + } +} + +static void dasher_purge_segments(GF_DasherCtx *ctx, u64 *period_dur) +{ + Double min_valid_mpd_time; + u64 max_rem_dur = 0; + u32 i, count; + + //non-static mode, purge segments + if (ctx->dmode == GF_MPD_TYPE_STATIC) return; + if (ctx->tsb<0) return; + + + min_valid_mpd_time = (Double) *period_dur; + min_valid_mpd_time /= 1000; + min_valid_mpd_time -= ctx->tsb; + //negative asto, we produce segments earlier but we don't want to delete them before the asto + if (ctx->asto<0) { + min_valid_mpd_time += ctx->asto; + } + if (min_valid_mpd_time<=0) return; + + count = gf_list_count(ctx->current_period->streams); + for (i=0; icurrent_period->streams, i); + if (ds->muxed_base) continue; + if (!ds->rep) continue; + if (!ds->rep->state_seg_list) continue; + + while (1) { + Double time, dur; + Bool seg_url_found = GF_FALSE; + Bool has_seg_list = GF_FALSE; + GF_DASH_SegmentContext *sctx = gf_list_get(ds->rep->state_seg_list, 0); + if (!sctx) break; + /*not yet flushed*/ + if (gf_list_find(ds->pending_segment_states, sctx)>=0) break; + time = (Double) sctx->time; + time /= ds->mpd_timescale; + dur = (Double) sctx->dur; + dur/= ds->timescale; + if (time + dur >= min_valid_mpd_time) break; + if (sctx->filepath) { + GF_FilterEvent evt; + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[Dasher] removing segment %s\n", sctx->filename ? sctx->filename : sctx->filepath)); + + GF_FEVT_INIT(evt, GF_FEVT_FILE_DELETE, ds->opid); + evt.file_del.url = sctx->filepath; + gf_filter_pid_send_event(ds->opid, &evt); + gf_free(sctx->filepath); + } + + if (ds->rep->segment_list) { + GF_MPD_SegmentURL *surl = gf_list_pop_front(ds->rep->segment_list->segment_URLs); + has_seg_list = GF_TRUE; + //can be NULL if we mutualize everything at AdaptatioSet level + if (surl) { + gf_mpd_segment_url_free(surl); + seg_url_found = GF_TRUE; + } + } + //not an else due to inheritance + if (ds->owns_set && ds->set->segment_list) { + GF_MPD_SegmentURL *surl = gf_list_pop_front(ds->set->segment_list->segment_URLs); + has_seg_list = GF_TRUE; + //can be NULL if we don't mutualize at AdaptatioSet level + if (surl) { + gf_mpd_segment_url_free(surl); + seg_url_found = GF_TRUE; + } + } + //but we must have at least one segment URL entry + if (has_seg_list && !seg_url_found) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] purging segment %s for AS %d rep %s but segment list is empty!\n", + sctx->filename ? sctx->filename : "", ds->set->id, ds->rep->id)); + } + + if (ds->rep->segment_template) { + if (ds->rep->segment_template->segment_timeline) { + dahser_purge_segment_timeline(ds, ds->rep->segment_template->segment_timeline, sctx); + } + } + //not an else due to inheritance + if (ds->owns_set && ds->set->segment_template) { + if (ds->set->segment_template->segment_timeline) { + dahser_purge_segment_timeline(ds, ds->set->segment_template->segment_timeline, sctx); + } + } + + ds->nb_segments_purged ++; + ds->dur_purged += dur; + assert(gf_list_find(ds->pending_segment_states, sctx)<0); + if (sctx->filename) gf_free(sctx->filename); + gf_free(sctx); + gf_list_rem(ds->rep->state_seg_list, 0); + } + if (max_rem_dur < ds->dur_purged*1000) max_rem_dur = (u64) (ds->dur_purged * 1000); + //final flush to static of live session: update start number + if (ctx->dmode!=GF_MPD_TYPE_DYNAMIC) { + if (ds->owns_set && ds->set && ds->set->segment_template) { + ds->set->segment_template->start_number += ds->nb_segments_purged; + } else if (ds->rep && ds->rep->segment_template) { + ds->rep->segment_template->start_number += ds->nb_segments_purged; + } + ds->nb_segments_purged = 0; + } + } + //final flush to static of live session: update period duration + if (ctx->dmode!=GF_MPD_TYPE_DYNAMIC) { + if (max_rem_dur > *period_dur) *period_dur = 0; + else *period_dur = *period_dur - max_rem_dur; + } +} + +static void dasher_update_period_duration(GF_DasherCtx *ctx, Bool is_period_switch) +{ + u32 i, count; + u64 pdur = 0; + u64 min_dur = 0; + u64 p_start=0; + GF_MPD_Period *prev_p = NULL; + count = gf_list_count(ctx->current_period->streams); + for (i=0; icurrent_period->streams, i); + if (ds->muxed_base) continue; + + if (ds->xlink) { + pdur = (u32) (1000*ds->period_dur); + } else { + u64 ds_dur = ds->max_period_dur; + //we had to generate one extra segment to unlock looping, but we don't want to advertise it in the manifest duration + //because other sets may not be ready for this time interval + if (ds->subdur_forced_use_period_dur) + ds_dur = ds->subdur_forced_use_period_dur; + + if (ds->clamped_dur && !ctx->loop) { + u64 clamp_dur = (u64) (ds->clamped_dur * 1000); + if (clamp_durdur_purged && (ctx->mpd->type != GF_MPD_TYPE_DYNAMIC)) { + u64 rem_dur = (u64) (ds->dur_purged * 1000); + if (ds_dur>rem_dur) ds_dur -= rem_dur; + else ds_dur = 0; + } + + if (!min_dur || (min_dur > ds_dur)) min_dur = ds->max_period_dur; + if (pdur < ds_dur) pdur = ds_dur; + } + } + if (!count && ctx->current_period->period && ctx->current_period->period->duration) + pdur = ctx->current_period->period->duration; + + if (!ctx->check_dur) { + s32 diff = (s32) ((s64) pdur - (s64) min_dur); + if (ABS(diff)>2000) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Adaptation sets in period are of unequal duration min %g max %g seconds\n", ((Double)min_dur)/1000, ((Double)pdur)/1000)); + } + } + + dasher_purge_segments(ctx, &pdur); + + if (ctx->current_period->period) { + ctx->current_period->period->duration = pdur; + + //update MPD duration in any case + if (ctx->current_period->period->start) { + ctx->mpd->media_presentation_duration = ctx->current_period->period->start + pdur; + } else { + u32 k, pcount = gf_list_count(ctx->mpd->periods); + ctx->mpd->media_presentation_duration = 0; + for (k=0; kmpd->periods, k); + if (p->start) + ctx->mpd->media_presentation_duration = p->start + p->duration; + else + ctx->mpd->media_presentation_duration += p->duration; + if (p==ctx->current_period->period) + break; + } + } + } + + if (ctx->refresh<0) + ctx->mpd->media_presentation_duration = (u32) ( (-ctx->refresh) * 1000 ); + + //static mode, done + if (ctx->dmode != GF_MPD_TYPE_DYNAMIC) { + return; + } + assert(ctx->current_period->period); + //dynamic mode only, reset durations + + ctx->mpd->gpac_mpd_time = ctx->mpd->media_presentation_duration; + + //not done yet for this period, keep duration to 0 + if (ctx->subdur_done) { + if (ctx->mpd->media_presentation_duration > ctx->current_period->period->duration) + ctx->mpd->media_presentation_duration -= ctx->current_period->period->duration; + else + ctx->mpd->media_presentation_duration = 0; + ctx->current_period->period->duration = 0; + } + + ctx->mpd->gpac_next_ntp_ms = ctx->mpd->gpac_init_ntp_ms + ctx->mpd->gpac_mpd_time; + if (ctx->asto<0) + ctx->mpd->gpac_next_ntp_ms -= (u64) (-ctx->asto * 1000); + if (ctx->_p_gentime) (*ctx->_p_gentime) = ctx->mpd->gpac_next_ntp_ms; + if (ctx->_p_mpdtime) (*ctx->_p_mpdtime) = ctx->mpd->gpac_mpd_time; + + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[Dasher] updated period %s duration "LLU" MPD time "LLU"\n", ctx->current_period->period->ID, pdur, ctx->mpd->gpac_mpd_time )); + + count = gf_list_count(ctx->mpd->periods); + for (i=0; impd->periods, i); + if (p->start) { + p_start = p->start; + } else { + p->start = p_start; + } + if (prev_p && (prev_p->start + prev_p->duration == p_start)) { + prev_p->duration = 0; + } + p_start += p->duration; + prev_p = p; + } +} + +static void dasher_transfer_file(FILE *f, GF_FilterPid *opid, const char *name) +{ + GF_FilterPacket *pck; + u32 size, nb_read; + u8 *output; + + size = (u32) gf_fsize(f); + + pck = gf_filter_pck_new_alloc(opid, size, &output); + nb_read = (u32) gf_fread(output, size, f); + if (nb_read != size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Error reading temp MPD file, read %d bytes but file size is %d\n", nb_read, size )); + } + gf_filter_pck_set_framing(pck, GF_TRUE, GF_TRUE); + gf_filter_pck_set_seek_flag(pck, GF_TRUE); + if (name) { + gf_filter_pck_set_property(pck, GF_PROP_PCK_FILENAME, &PROP_STRING(name) ); + } + gf_filter_pck_send(pck); +} + +static u64 dasher_get_utc(GF_DasherCtx *ctx) +{ + return gf_net_get_utc() - ctx->utc_diff; +} + +GF_Err dasher_send_manifest(GF_Filter *filter, GF_DasherCtx *ctx, Bool for_mpd_only) +{ + GF_Err e; + u32 i, max_opid; + FILE *tmp; + u64 store_mpd_dur=0; + u64 max_seg_dur=0; + u64 last_period_dur; + //UGLY PATCH, to remove - we don't have the same algos in old arch and new arch, which result in slightly different max segment duration + //on audio for our test suite - patch it manually to avoid hash failures :( + //TODO, remove as soon as we switch archs + if (gf_sys_old_arch_compat() && (ctx->mpd->max_segment_duration==1022) && (ctx->mpd->media_presentation_duration==10160) ) { + ctx->mpd->max_segment_duration = 1080; + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] patch for old regression tests hit, changing max seg dur from 1022 to 1080\nPlease notify GPAC devs to remove this, and do not use fot_test modes in dash filter\n")); + } + + ctx->mpd->publishTime = dasher_get_utc(ctx); + if (ctx->utc_timing_type==DASHER_UTCREF_INBAND) { + GF_MPD_Descriptor *d = gf_list_get(ctx->mpd->utc_timings, 0); + if (d) { + time_t gtime; + struct tm *t; + u32 sec; + u32 ms; + char szTime[100]; + if (d->value) gf_free(d->value); + + gtime = ctx->mpd->publishTime / 1000; + sec = (u32)(ctx->mpd->publishTime / 1000); + ms = (u32)(ctx->mpd->publishTime - ((u64)sec) * 1000); + + t = gf_gmtime(>ime); + sec = t->tm_sec; + //see issue #859, no clue how this happened... + if (sec > 60) + sec = 60; + snprintf(szTime, 100, "%d-%02d-%02dT%02d:%02d:%02d.%03dZ", 1900 + t->tm_year, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, sec, ms); + d->value = gf_strdup(szTime); + } + } + dasher_update_mpd(ctx); + ctx->mpd->write_context = GF_FALSE; + ctx->mpd->was_dynamic = GF_FALSE; + if (ctx->dmode==GF_DASH_DYNAMIC_LAST) + ctx->mpd->was_dynamic = GF_TRUE; + + if ((ctx->refresh>=0) && (ctx->dmode==GF_DASH_DYNAMIC)) { + store_mpd_dur= ctx->mpd->media_presentation_duration; + } + + if (ctx->sseg && ctx->mpd->max_segment_duration) { + max_seg_dur = ctx->mpd->max_subsegment_duration = ctx->mpd->max_segment_duration; + ctx->mpd->max_segment_duration = 0; + } + + last_period_dur = 0; + if (ctx->current_period->period) { + last_period_dur = ctx->current_period->period->duration; + if (ctx->dmode==GF_DASH_DYNAMIC) { + ctx->current_period->period->duration = 0; + ctx->mpd->media_presentation_duration = 0; + } + } + + max_opid = (ctx->dual && ctx->opid_alt) ? 2 : 1; + for (i=0; i < max_opid; i++) { + Bool do_m3u8 = GF_FALSE; + tmp = gf_file_temp(NULL); + GF_FilterPid *opid; + + if (i==0) { + if (max_opid>1) { + do_m3u8 = ctx->opid_alt_m3u8 ? GF_FALSE : GF_TRUE; + } else { + do_m3u8 = ctx->do_m3u8; + } + opid = ctx->opid; + } else { + do_m3u8 = ctx->opid_alt_m3u8; + opid = ctx->opid_alt; + } + + if (do_m3u8) { + if (for_mpd_only) continue; + ctx->mpd->m3u8_time = ctx->hlsc; + e = gf_mpd_write_m3u8_master_playlist(ctx->mpd, tmp, ctx->out_path, gf_list_get(ctx->mpd->periods, 0) ); + } else { + e = gf_mpd_write(ctx->mpd, tmp, ctx->cmpd); + } + + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] failed to write %s file: %s\n", do_m3u8 ? "M3U8" : "MPD", gf_error_to_string(e) )); + gf_fclose(tmp); + if (ctx->current_period->period) + ctx->current_period->period->duration = last_period_dur; + return e; + } + + if (ctx->profile == GF_DASH_PROFILE_HBBTV_1_5_ISOBMF_LIVE) { + if (gf_ftell(tmp) > 100 * 1024) + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] manifest MPD is too big for HbbTV 1.5. Limit is 100kB, current size is "LLU"kB\n", gf_ftell(tmp) / 1024)); + } + + dasher_transfer_file(tmp, opid, NULL); + gf_fclose(tmp); + } + + if (ctx->current_period->period) + ctx->current_period->period->duration = last_period_dur; + + if (store_mpd_dur) { + ctx->mpd->media_presentation_duration = store_mpd_dur; + } + if (max_seg_dur) { + ctx->mpd->max_segment_duration = (u32) max_seg_dur; + ctx->mpd->max_subsegment_duration = 0; + } + + if (ctx->do_m3u8) { + u32 j; + GF_MPD_Period *period = gf_list_get(ctx->mpd->periods, 0); + GF_MPD_AdaptationSet *as; + GF_MPD_Representation *rep; + assert(period); + i=0; + while ( (as = (GF_MPD_AdaptationSet *) gf_list_enum(period->adaptation_sets, &i))) { + j=0; + while ( (rep = (GF_MPD_Representation *) gf_list_enum(as->representations, &j))) { + if (rep->m3u8_var_file) { + char *outfile = rep->m3u8_var_name; + Bool do_free = GF_FALSE; + + if (rep->m3u8_name) { + outfile = (char *) rep->m3u8_name; + if (ctx->out_path) { + outfile = gf_url_concatenate(ctx->out_path, rep->m3u8_name); + do_free = GF_TRUE; + } + } + dasher_transfer_file(rep->m3u8_var_file, ctx->opid, outfile); + gf_fclose(rep->m3u8_var_file); + rep->m3u8_var_file = NULL; + if (do_free) gf_free(outfile); + } + } + } + } + + + if (ctx->state) { + tmp = gf_fopen(ctx->state, "w"); + if (!tmp) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] failed to open context MPD %s for write\n", ctx->state )); + return GF_IO_ERR; + } + ctx->mpd->write_context = GF_TRUE; + e = gf_mpd_write(ctx->mpd, tmp, ctx->cmpd); + gf_fclose(tmp); + ctx->mpd->write_context = GF_FALSE; + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] failed to write MPD file: %s\n", gf_error_to_string(e) )); + } + return e; + } + return GF_OK; +} + +static void dasher_reset_stream(GF_Filter *filter, GF_DashStream *ds, Bool is_destroy) +{ + //we do not remove the destination filter, it will be removed automatically once all remove_pids are called + //removing it explicetly will discard the upper chain and any packets not yet processed + + ds->dst_filter = NULL; + if (ds->seg_template) gf_free(ds->seg_template); + if (ds->idx_template) gf_free(ds->idx_template); + if (ds->init_seg) gf_free(ds->init_seg); + if (ds->multi_pids) gf_list_del(ds->multi_pids); + ds->multi_pids = NULL; + if (ds->multi_tracks) gf_list_del(ds->multi_tracks); + ds->multi_tracks = NULL; + + if (ds->pending_segment_urls) gf_list_del(ds->pending_segment_urls); + ds->pending_segment_urls = NULL; + if (ds->pending_segment_states) gf_list_del(ds->pending_segment_states); + ds->pending_segment_states = NULL; + + if (is_destroy) { + if (ds->cues) gf_free(ds->cues); + gf_list_del(ds->complementary_streams); + gf_free(ds->rep_id); + return; + } + ds->init_seg = ds->seg_template = ds->idx_template = NULL; + ds->split_set_names = GF_FALSE; + ds->nb_sap_3 = 0; + ds->nb_sap_4 = 0; + ds->pid_id = 0; + ds->force_timescale = 0; + ds->set = NULL; + ds->owns_set = GF_FALSE; + ds->rep = NULL; + ds->muxed_base = NULL; + ds->nb_comp = ds->nb_comp_done = 0; + gf_list_reset(ds->complementary_streams); + ds->inband_params = GF_FALSE; + ds->seg_start_time = 0; + ds->seg_number = ds->startNumber; + ds->nb_segments_purged = 0; + ds->dur_purged = 0; + ds->moof_sn_inc = 0; + ds->moof_sn = 0; + ds->seg_done = 0; + ds->subdur_done = 0; + if (ds->packet_queue) { + while (gf_list_count(ds->packet_queue)) { + GF_FilterPacket *pck = gf_list_pop_front(ds->packet_queue); + gf_filter_pck_unref(pck); + } + ds->nb_sap_in_queue = 0; + } +} + +void dasher_context_update_period_end(GF_DasherCtx *ctx) +{ + u32 i, count; + + if (!ctx->mpd) return; + + count = gf_list_count(ctx->current_period->streams); + for (i=0; icurrent_period->streams, i); + if (!ds->rep) continue; + if (!ds->rep->dasher_ctx) continue; + if (ds->done == 1) { + ds->rep->dasher_ctx->done = 1; + } else { + //store all dynamic parameters of the rep + ds->rep->dasher_ctx->last_pck_idx = ds->nb_pck; + ds->seek_to_pck = ds->nb_pck; + ds->rep->dasher_ctx->seg_number = ds->seg_number; + ds->rep->dasher_ctx->next_seg_start = ds->next_seg_start; + ds->rep->dasher_ctx->first_cts = ds->first_cts; + ds->rep->dasher_ctx->first_dts = ds->first_dts; + ds->rep->dasher_ctx->ts_offset = ds->ts_offset; + ds->rep->dasher_ctx->segs_purged = ds->nb_segments_purged; + ds->rep->dasher_ctx->dur_purged = ds->dur_purged; + ds->rep->dasher_ctx->moof_sn = ds->moof_sn; + ds->rep->dasher_ctx->moof_sn_inc = ds->moof_sn_inc; + ds->rep->dasher_ctx->subdur_forced = ds->subdur_forced_use_period_dur ? GF_TRUE : GF_FALSE; + } + if (ctx->subdur) { + ds->rep->dasher_ctx->cumulated_subdur = ds->cumulated_subdur + ctx->subdur; + ds->rep->dasher_ctx->cumulated_dur = ((Double)ds->cumulated_dur) / ds->timescale; + + } + ds->rep->dasher_ctx->nb_repeat = ds->nb_repeat; + ds->rep->dasher_ctx->est_next_dts = ds->est_next_dts; + ds->rep->dasher_ctx->source_pid = ds->id; + ds->rep->dasher_ctx->mpd_timescale = ds->mpd_timescale; + ds->rep->dasher_ctx->last_dyn_period_id = ctx->last_dyn_period_id; + + assert(ds->rep->dasher_ctx->init_seg); + assert(ds->rep->dasher_ctx->src_url); + assert(ds->rep->dasher_ctx->template_seg); + } +} + +void dasher_context_update_period_start(GF_DasherCtx *ctx) +{ + u32 i, j, count; + + if (!ctx->mpd) return; + count = gf_list_count(ctx->current_period->streams); + for (i=0; icurrent_period->streams, i); + if (!ds->rep) continue; + if (ds->rep->dasher_ctx) continue; + + //store all static parameters of the rep + GF_SAFEALLOC(ds->rep->dasher_ctx, GF_DASH_SegmenterContext); + if (!ds->rep->dasher_ctx) return; + + ds->rep->dasher_ctx->done = 0; + + assert(ds->init_seg); + ds->rep->dasher_ctx->init_seg = gf_strdup(ds->init_seg); + assert(ds->src_url); + ds->rep->dasher_ctx->src_url = gf_strdup(ds->src_url); + assert(ds->seg_template); + ds->rep->dasher_ctx->template_seg = gf_strdup(ds->seg_template); + if (ds->idx_template) + ds->rep->dasher_ctx->template_idx = gf_strdup(ds->idx_template); + + ds->rep->dasher_ctx->pid_id = ds->pid_id; + ds->rep->dasher_ctx->dep_pid_id = ds->dep_pid_id; + ds->rep->dasher_ctx->period_start = ds->period_start; + ds->rep->dasher_ctx->period_duration = ds->period_dur; + ds->rep->dasher_ctx->multi_pids = ds->multi_pids ? GF_TRUE : GF_FALSE; + ds->rep->dasher_ctx->dash_dur = ds->dash_dur; + + if (strcmp(ds->period_id, DEFAULT_PERIOD_ID)) + ds->rep->dasher_ctx->period_id = ds->period_id; + + ds->rep->dasher_ctx->owns_set = (ds->set->udta == ds) ? GF_TRUE : GF_FALSE; + + if (ds->rep->dasher_ctx->mux_pids) gf_free(ds->rep->dasher_ctx->mux_pids); + ds->rep->dasher_ctx->mux_pids = NULL; + for (j=0; jcurrent_period->streams, j); + if (a_ds==ds) continue; + if (a_ds->muxed_base != ds) continue; + + if (ds->rep->dasher_ctx->mux_pids) + sprintf(szMuxPID, " %d", a_ds->id); + else + sprintf(szMuxPID, "%d", a_ds->id); + + gf_dynstrcat(&ds->rep->dasher_ctx->mux_pids, szMuxPID, NULL); + } + + } +} + +static GF_DashStream *dasher_get_stream(GF_DasherCtx *ctx, const char *src_url, u32 original_pid, u32 pid_id) +{ + u32 i, count = gf_list_count(ctx->pids); + for (i=0; ipids, i); + if (pid_id && (ds->pid_id==pid_id)) return ds; + if (src_url && ds->src_url && !strcmp(ds->src_url, src_url) && (ds->id == original_pid) ) return ds; + } + return NULL; +} + +static GF_Err dasher_reload_muxed_comp(GF_DasherCtx *ctx, GF_DashStream *base_ds, char *mux_pids, Bool check_only) +{ + GF_Err e = GF_OK; + while (mux_pids) { + u32 pid_id; + GF_DashStream *ds; + char *sep = strchr(mux_pids, ' '); + if (sep) sep[0] = 0; + + pid_id = atoi(mux_pids); + ds = dasher_get_stream(ctx, base_ds->src_url, pid_id, 0); + if (ds) { + if (!check_only) { + if (ds->rep) gf_mpd_representation_free(ds->rep); + ds->rep = NULL; + ds->set = base_ds->set; + ds->muxed_base = base_ds; + base_ds->nb_comp ++; + ds->nb_comp = 1; + ds->done = base_ds->done; + ds->subdur_done = base_ds->subdur_done; + ds->period = ctx->current_period; + + gf_list_del_item(ctx->next_period->streams, ds); + gf_list_add(ctx->current_period->streams, ds); + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Couldn't find muxed PID %d in source %s, did you modify the source ?\n", pid_id, base_ds->src_url)); + e = GF_BAD_PARAM; + } + + if (!sep) break; + sep[0] = ' '; + mux_pids = sep+1; + + if (e) return e; + } + return GF_OK; +} + +static GF_Err dasher_reload_context(GF_Filter *filter, GF_DasherCtx *ctx) +{ + GF_Err e; + Bool last_period_active = GF_FALSE; + u32 i, j, k, nb_p, nb_as, nb_rep, count; + GF_DOMParser *mpd_parser; + + ctx->first_context_load = GF_FALSE; + + if (!gf_file_exists(ctx->state)) return GF_OK; + + /* parse the MPD */ + mpd_parser = gf_xml_dom_new(); + e = gf_xml_dom_parse(mpd_parser, ctx->state, NULL, NULL); + + if (e != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Cannot parse MPD state %s: %s\n", ctx->state, gf_xml_dom_get_error(mpd_parser) )); + gf_xml_dom_del(mpd_parser); + return GF_URL_ERROR; + } + if (ctx->mpd) gf_mpd_del(ctx->mpd); + ctx->mpd = gf_mpd_new(); + e = gf_mpd_init_from_dom(gf_xml_dom_get_root(mpd_parser), ctx->mpd, ctx->state); + gf_xml_dom_del(mpd_parser); + //test mode, strip URL path + if (gf_sys_is_test_mode()) { + count = gf_list_count(ctx->mpd->program_infos); + for (i=0; impd->program_infos, i); + if (info->title && strstr(info->title, "generated by GPAC")) { + gf_free(info->title); + char tmp[256]; + char *name = strrchr(ctx->out_path, '/'); + if (!name) name = strrchr(ctx->out_path, '\\'); + if (!name) name = ctx->out_path; + else name++; + sprintf(tmp, "%s generated by GPAC", name); + info->title = gf_strdup(tmp); + } + } + } + + if (!ctx->mpd->xml_namespace) + ctx->mpd->xml_namespace = "urn:mpeg:dash:schema:mpd:2011"; + + if (e != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Cannot reload MPD state %s: %s\n", ctx->state, gf_error_to_string(e) )); + gf_mpd_del(ctx->mpd); + ctx->mpd = NULL; + return GF_URL_ERROR; + } + + //do a first pass to detect any potential changes in input config, if so consider the period over. + nb_p = gf_list_count(ctx->mpd->periods); + for (i=0; impd->periods, i); + nb_as = gf_list_count(p->adaptation_sets); + for (j=0; jadaptation_sets, j); + nb_rep = gf_list_count(set->representations); + for (k=0; krepresentations, k); + if (! rep->dasher_ctx) continue; + + //ensure we have the same settings - if not consider the dash stream has been resetup for a new period + ds = dasher_get_stream(ctx, rep->dasher_ctx->src_url, rep->dasher_ctx->source_pid, 0); + if (!ds) { + rep->dasher_ctx->done = 1; + nb_done_in_period++; + if (rep->dasher_ctx->last_dyn_period_id >= ctx->last_dyn_period_id) + ctx->last_dyn_period_id = 1 + rep->dasher_ctx->last_dyn_period_id; + continue; + } + + if (rep->dasher_ctx->done) { + nb_done_in_period++; + ds->nb_repeat = rep->dasher_ctx->nb_repeat + 1; + if (rep->dasher_ctx->last_dyn_period_id > ctx->last_dyn_period_id) + ctx->last_dyn_period_id = rep->dasher_ctx->last_dyn_period_id; + continue; + } + + p_id = DEFAULT_PERIOD_ID; + if (rep->dasher_ctx->period_id) p_id = rep->dasher_ctx->period_id; + + if (ds->period_id && p_id && !strcmp(ds->period_id, p_id)) { + } else if (!ds->period_id && !rep->dasher_ctx->period_id) { + } else { + rep->dasher_ctx->done = 1; + nb_done_in_period++; + continue; + } + if (ds->period_start != rep->dasher_ctx->period_start) { + rep->dasher_ctx->done = 1; + nb_done_in_period++; + continue; + } + if (ds->period_dur != rep->dasher_ctx->period_duration) { + rep->dasher_ctx->done = 1; + nb_done_in_period++; + continue; + } + //check we can reload muxed components - if not consider this source as removed + if (rep->dasher_ctx->mux_pids) { + e = dasher_reload_muxed_comp(ctx, ds, rep->dasher_ctx->mux_pids, GF_TRUE); + if (e) { + rep->dasher_ctx->done = 1; + nb_done_in_period++; + continue; + } + } + nb_remain_in_period++; + } + } + if (nb_remain_in_period) { + assert(i+1==nb_p); + last_period_active = GF_TRUE; + } + else if (nb_done_in_period && ctx->subdur ) { + //we are done but we loop the entire streams + for (j=0; jpids); j++) { + GF_DashStream *ds = gf_list_get(ctx->pids, j); + ds->done = 0; + ds->segment_started = GF_FALSE; + ds->seg_done = GF_FALSE; + ds->cumulated_dur = 0; + ds->cumulated_subdur = 0; + } + } + } + + if (!last_period_active) return GF_OK; + ctx->current_period->period = gf_list_last(ctx->mpd->periods); + gf_list_reset(ctx->current_period->streams); + gf_list_del(ctx->next_period->streams); + ctx->next_period->streams = gf_list_clone(ctx->pids); + + if (ctx->current_period->period->duration) { + //reset last period duration and cumulated dur of MPD + if (ctx->mpd->media_presentation_duration>ctx->current_period->period->duration) + ctx->mpd->media_presentation_duration -= ctx->current_period->period->duration; + else + ctx->mpd->media_presentation_duration = 0; + ctx->current_period->period->duration = 0; + } + + nb_as = gf_list_count(ctx->current_period->period->adaptation_sets); + for (j=0; jcurrent_period->period->adaptation_sets, j); + nb_rep = gf_list_count(set->representations); + for (k=0; krepresentations, k); + if (! rep->dasher_ctx) continue; + + ds = dasher_get_stream(ctx, rep->dasher_ctx->src_url, rep->dasher_ctx->source_pid, 0); + if (!ds) continue; + + //restore everything + ds->done = rep->dasher_ctx->done; + ds->seg_number = rep->dasher_ctx->seg_number; + + if (ds->init_seg) gf_free(ds->init_seg); + ds->init_seg = gf_strdup(rep->dasher_ctx->init_seg); + + if (ds->seg_template) gf_free(ds->seg_template); + ds->seg_template = gf_strdup(rep->dasher_ctx->template_seg); + + if (ds->idx_template) gf_free(ds->idx_template); + ds->idx_template = rep->dasher_ctx->template_idx ? gf_strdup(rep->dasher_ctx->template_idx) : NULL; + + if (rep->dasher_ctx->period_id) { + if (ds->period_id) gf_free(ds->period_id); + ds->period_id = gf_strdup(rep->dasher_ctx->period_id); + } + + ds->period_start = rep->dasher_ctx->period_start; + ds->period_dur = rep->dasher_ctx->period_duration; + ds->pid_id = rep->dasher_ctx->pid_id; + ds->dep_pid_id = rep->dasher_ctx->dep_pid_id; + ds->seek_to_pck = rep->dasher_ctx->last_pck_idx; + ds->dash_dur = rep->dasher_ctx->dash_dur; + ds->next_seg_start = rep->dasher_ctx->next_seg_start; + ds->adjusted_next_seg_start = ds->next_seg_start; + ds->first_cts = rep->dasher_ctx->first_cts; + ds->first_dts = rep->dasher_ctx->first_dts; + ds->ts_offset = rep->dasher_ctx->ts_offset; + ds->est_next_dts = rep->dasher_ctx->est_next_dts; + ds->mpd_timescale = rep->dasher_ctx->mpd_timescale; + ds->cumulated_dur = (u64) (rep->dasher_ctx->cumulated_dur * ds->timescale); + ds->cumulated_subdur = rep->dasher_ctx->cumulated_subdur; + ds->rep_init = GF_TRUE; + ds->subdur_done = rep->dasher_ctx->subdur_forced ? GF_TRUE : GF_FALSE; + ds->subdur_forced_use_period_dur = 0; + ds->nb_pck = 0; + if (!ctx->subdur) { + ds->nb_pck = ds->seek_to_pck; + ds->seek_to_pck = 0; + } + ds->nb_segments_purged = rep->dasher_ctx->segs_purged; + ds->dur_purged = rep->dasher_ctx->dur_purged; + ds->moof_sn = rep->dasher_ctx->moof_sn; + ds->moof_sn_inc = rep->dasher_ctx->moof_sn_inc; + ctx->last_dyn_period_id = rep->dasher_ctx->last_dyn_period_id; + + if (ctx->store_seg_states && !ds->pending_segment_states) + ds->pending_segment_states = gf_list_new(); + + if (rep->segment_list && !ds->pending_segment_urls) + ds->pending_segment_urls = gf_list_new(); + + ds->owns_set = rep->dasher_ctx->owns_set; + if (ds->owns_set) set_ds = ds; + + if (rep->dasher_ctx->done) { + ds->done = 1; + if (ds->rep) gf_mpd_representation_free(ds->rep); + ds->rep = NULL; + continue; + } + + ds->nb_comp = 1; + + if (ds->rep) gf_mpd_representation_free(ds->rep); + ds->rep = rep; + ds->set = set; + rep->playback.udta = ds; + if (ds->owns_set) + set->udta = ds; + if (rep->dasher_ctx->multi_pids) + use_multi_pid_init = GF_TRUE; + + ds->period = ctx->current_period; + + gf_list_del_item(ctx->next_period->streams, ds); + + //non-muxed component or main comp of muxed goes first in the list + if (ds->nb_comp>1) { + gf_list_insert(ctx->current_period->streams, ds, 0); + } else { + gf_list_add(ctx->current_period->streams, ds); + } + + if (rep->dasher_ctx->mux_pids) { + e = dasher_reload_muxed_comp(ctx, ds, rep->dasher_ctx->mux_pids, GF_FALSE); + if (e) return e; + } + } + assert(set_ds); + set_ds->nb_rep = gf_list_count(set->representations); + + //if multi PID init, gather pids + if (use_multi_pid_init) { + multi_pids = gf_list_new(); + for (i=0; irepresentations, i); + GF_DashStream *ds = rep->playback.udta; + if (ds->owns_set) ds->multi_pids = multi_pids; + gf_list_add(multi_pids, ds->ipid); + } + } + count = gf_list_count(ctx->current_period->streams); + for (i=0; irepresentations, i); + GF_DashStream *ds = rep->playback.udta; + if (!ds || ds->done) continue; + //happens when reloading context without closing the filter + if (ds->dst_filter || ds->opid) continue; + + //open destination, trashing init + assert(!ds->muxed_base); + dasher_open_destination(filter, ctx, rep, ds->init_seg, GF_TRUE); + + dasher_open_pid(filter, ctx, ds, multi_pids, GF_TRUE); + + for (j=0; jcurrent_period->streams, j); + if (a_ds->muxed_base != ds) continue; + + dasher_open_pid(filter, ctx, a_ds, multi_pids, GF_TRUE); + } + } + } + + return GF_OK; +} + +static void dasher_udpate_periods_and_manifest(GF_Filter *filter, GF_DasherCtx *ctx) +{ + if (!ctx->subdur_done) { + ctx->last_dyn_period_id++; + ctx->next_pid_id_in_period = 0; + } + //update duration + dasher_update_period_duration(ctx, GF_TRUE); + + if (ctx->state) + dasher_context_update_period_end(ctx); + + //we have a MPD ready, flush it + if (ctx->mpd) + dasher_send_manifest(filter, ctx, GF_FALSE); +} + +typedef struct +{ + Double period_start; + const char *period_id; +} PeriodInfo; + +static u32 dasher_period_count(GF_List *streams_in /*GF_DashStream*/) +{ + u32 nb_periods, i, j; + PeriodInfo *info; + GF_List *pinfos = gf_list_new(); + + for (i=0; i < gf_list_count(streams_in); i++) { + Bool same_period = GF_FALSE; + GF_DashStream *ds = gf_list_get(streams_in, i); + //check if we already have a period info with same ID or same start time + nb_periods = gf_list_count(pinfos); + for (j=0; j < nb_periods; j++) { + info = gf_list_get(pinfos, j); + if (info->period_start == ds->period_start) { + same_period = GF_TRUE; + break; + } + if (info->period_id && ds->period_id && !strcmp(info->period_id, ds->period_id)) { + same_period = GF_TRUE; + break; + } + } + //nope, register it + if (!same_period) { + GF_SAFEALLOC(info, PeriodInfo); + if (info) { + info->period_start = ds->period_start; + info->period_id = ds->period_id; + gf_list_add(pinfos, info); + } + } + } + nb_periods = gf_list_count(pinfos); + while (1) { + info = gf_list_pop_back(pinfos); + if (!info) break; + gf_free(info); + } + gf_list_del(pinfos); + + return nb_periods; +} + +static void dasher_init_utc(GF_Filter *filter, GF_DasherCtx *ctx) +{ + const char *cache_name; + u32 size; + u8 *data; + u64 remote_utc; + GF_Err e; + GF_DownloadSession *sess; + GF_DownloadManager *dm; + char *url; + DasherUTCTimingType def_type = DASHER_UTCREF_NONE; + + ctx->utc_initialized = GF_TRUE; + ctx->utc_timing_type = DASHER_UTCREF_NONE; + if (!ctx->utcs) { + return; + } + url = ctx->utcs; + if (!strncmp(url, "xsd@", 4)) { + def_type = DASHER_UTCREF_XSDATE; + url += 4; + } + + dm = gf_filter_get_download_manager(filter); + if (!dm) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Failed to get download manager, cannot sync to remote UTC clock\n")); + return; + } + if (!strcmp(ctx->utcs, "inband")) { + ctx->utc_timing_type = DASHER_UTCREF_INBAND; + return; + } + + sess = gf_dm_sess_new(dm, url, GF_NETIO_SESSION_MEMORY_CACHE|GF_NETIO_SESSION_NOT_THREADED, NULL, NULL, &e); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Failed to create session for remote UTC source %s: %s\n", url, gf_error_to_string(e) )); + return; + } + while (1) { + GF_NetIOStatus status; + e = gf_dm_sess_process(sess); + if (e) break; + gf_dm_sess_get_stats(sess, NULL, NULL, NULL, NULL, NULL, &status); + if (status>=GF_NETIO_DATA_TRANSFERED) break; + } + if (e<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Failed to fetch remote UTC source %s: %s\n", url, gf_error_to_string(e) )); + gf_dm_sess_del(sess); + return; + } + cache_name = gf_dm_sess_get_cache_name(sess); + gf_blob_get_data(cache_name, &data, &size); + if (data) { + //xsDate or isoDate - we always signal using iso + if (strchr(data, 'T')) { + remote_utc = gf_net_parse_date(data); + if (remote_utc) + ctx->utc_timing_type = def_type ? def_type : DASHER_UTCREF_ISO; + } + //ntp + else if (sscanf(data, LLU, &remote_utc) == 1) { + remote_utc = gf_net_ntp_to_utc(remote_utc); + if (remote_utc) + ctx->utc_timing_type = DASHER_UTCREF_NTP; + } + } + //not match, try http date + if (!ctx->utc_timing_type) { + const char *hdr = gf_dm_sess_get_header(sess, "Date"); + if (hdr) { + //http-head + remote_utc = gf_net_parse_date(hdr); + if (remote_utc) + ctx->utc_timing_type = DASHER_UTCREF_HTTP_HEAD; + } + } + + if (!ctx->utc_timing_type) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Failed to parse response %s from remote UTC source %s\n", data, url )); + } else { + ctx->utc_diff = (s32) ( (s64) gf_net_get_utc() - (s64) remote_utc ); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[Dasher] Synchronized clock to remote %s - UTC diff (local - remote) %d ms\n", url, ctx->utc_diff)); + + if (!gf_list_count(ctx->mpd->utc_timings) ) { + GF_MPD_Descriptor *utc_t; + GF_SAFEALLOC(utc_t, GF_MPD_Descriptor); + utc_t->value = gf_strdup(url); + switch (ctx->utc_timing_type) { + case DASHER_UTCREF_HTTP_HEAD: + utc_t->scheme_id_uri = gf_strdup("urn:mpeg:dash:utc:http-head:2014"); + break; + case DASHER_UTCREF_XSDATE: + utc_t->scheme_id_uri = gf_strdup("urn:mpeg:dash:utc:http- xsdate:2014"); + break; + case DASHER_UTCREF_ISO: + utc_t->scheme_id_uri = gf_strdup("urn:mpeg:dash:utc:http-iso:2014"); + break; + case DASHER_UTCREF_NTP: + utc_t->scheme_id_uri = gf_strdup("urn:mpeg:dash:utc:http-ntp:2014"); + break; + case DASHER_UTCREF_INBAND: + utc_t->scheme_id_uri = gf_strdup("urn:mpeg:dash:utc:direct:2014"); + break; + default: + break; + } + gf_list_add(ctx->mpd->utc_timings, utc_t); + } + } + gf_dm_sess_del(sess); +} + +static GF_Err dasher_switch_period(GF_Filter *filter, GF_DasherCtx *ctx) +{ + u32 i, count, j, nb_sets, nb_done, srd_rep_idx; + u32 nb_periods = 0; + Bool has_muxed_bases=GF_FALSE; + char *period_id; + const char *remote_xlink = NULL; + u64 remote_dur = 0; + Bool empty_period = GF_FALSE; + Bool is_restore = GF_FALSE; + Bool has_as_id = GF_FALSE; + Bool has_deps = GF_FALSE; + GF_DasherPeriod *p; + Double period_idx, period_start, next_period_start, min_dur, min_adur, max_adur; + GF_DashStream *first_in_period=NULL; + p = ctx->current_period; + + if (!ctx->out_path) { + ctx->out_path = gf_filter_pid_get_destination(ctx->opid); + } + if (ctx->current_period->period) { + dasher_udpate_periods_and_manifest(filter, ctx); + } + + if (ctx->subdur_done || (ctx->current_period->period && (ctx->dmode == GF_MPD_TYPE_DYNAMIC_LAST)) ) + return GF_EOS; + + if (ctx->current_period->period) { + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[Dasher] End of Period %s\n", ctx->current_period->period->ID ? ctx->current_period->period->ID : "")); + } + //reset - don't destroy, it is in the MPD + ctx->current_period->period = NULL; + //switch + ctx->current_period = ctx->next_period; + ctx->next_period = p; + ctx->on_demand_done = GF_FALSE; + + //reset input pids and detach output pids + count = gf_list_count(ctx->current_period->streams); + for (i=0; icurrent_period->streams, i); + if (ds->opid) { + gf_filter_pid_remove(ds->opid); + ds->opid = NULL; + } + dasher_reset_stream(filter, ds, GF_FALSE); + if (ds->reschedule) { + ds->reschedule = GF_FALSE; + ds->done = 0; + } + } + + //figure out next period + count = gf_list_count(ctx->current_period->streams); + period_idx = 0; + period_start = -1; + for (i=0; icurrent_period->streams, i); + + if (ds->done) continue; + if (ds->period_start < 0) { + pstart = -ds->period_start; + if (!period_idx || (pstartperiod_start < period_start)) period_start = ds->period_start; + } + } + + if (period_start>=0) + period_idx = 0; + + if (ctx->profile == GF_DASH_PROFILE_HBBTV_1_5_ISOBMF_LIVE) { + nb_periods = dasher_period_count(ctx->current_period->streams); + } + + if (ctx->first_context_load) { + GF_Err e = dasher_reload_context(filter, ctx); + if (e) { + ctx->setup_failure = e; + return e; + } + if (ctx->current_period->period) is_restore = GF_TRUE; + + if (ctx->dmode==GF_DASH_DYNAMIC_LAST) { + dasher_udpate_periods_and_manifest(filter, ctx); + count = gf_list_count(ctx->pids); + for (i=0; ipids, i); + gf_filter_pid_set_discard(ds->ipid, GF_TRUE); + } + return GF_EOS; + } + } + + //filter out PIDs not for this period + count = gf_list_count(ctx->current_period->streams); + period_id = NULL; + srd_rep_idx = 2; //2 for compat with old arch + for (i=0; icurrent_period->streams, i); + + if (ds->done) { + in_period=GF_FALSE; + } else if (!period_id) { + period_id = ds->period_id; + first_in_period = ds; + } else if (strcmp(period_id, ds->period_id)) { + in_period = GF_FALSE; + } + if (in_period) { + if ((period_start>=0) && (ds->period_start != period_start)) in_period = GF_FALSE; + else if ((period_idx>0) && (-ds->period_start != period_idx)) in_period = GF_FALSE; + if (!in_period && (first_in_period == ds)) + period_id = NULL; + } + + //if not in period, move to next period + if (!in_period) { + gf_list_rem(ctx->current_period->streams, i); + i--; + count--; + ds->period = NULL; + gf_list_add(ctx->next_period->streams, ds); + continue; + } + if (ds->stream_type == GF_STREAM_FILE) { + if (ds->xlink) remote_xlink = ds->xlink; + else empty_period = GF_TRUE; + remote_dur = (u64) (ds->period_dur * 1000); + } else if (!is_restore) { + //setup representation - the representation is created independently from the period + dasher_setup_rep(ctx, ds, &srd_rep_idx); + + if (ctx->post_play_events) { + GF_FilterEvent evt; + + GF_FEVT_INIT(evt, GF_FEVT_STOP, ds->ipid); + gf_filter_pid_send_event(ds->ipid, &evt); + + gf_filter_pid_set_discard(ds->ipid, GF_FALSE); + + GF_FEVT_INIT(evt, GF_FEVT_PLAY, ds->ipid); + evt.play.speed = 1.0; + gf_filter_pid_send_event(ds->ipid, &evt); + } + } + } + ctx->post_play_events = GF_FALSE; + + count = gf_list_count(ctx->current_period->streams); + if (!count) { + count = gf_list_count(ctx->next_period->streams); + nb_done = 0; + for (i=0; inext_period->streams, i); + if (ds->done) nb_done++; + } + if (nb_done == count) { + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[Dasher] End of MPD (no more active streams)\n")); + return GF_EOS; + } + } + + //we need a new period unless created during reload, create it + if (!is_restore) { + ctx->current_period->period = gf_mpd_period_new(); + if (!ctx->mpd) dasher_setup_mpd(ctx); + gf_list_add(ctx->mpd->periods, ctx->current_period->period); + } + + + if (remote_xlink) { + ctx->current_period->period->xlink_href = gf_strdup(remote_xlink); + ctx->current_period->period->duration = remote_dur; + } + + assert(period_id); + + next_period_start = -1; + if (period_start>=0) { + ctx->current_period->period->start = (u64)(period_start*1000); + //check next period start + count = gf_list_count(ctx->next_period->streams); + for (i=0; inext_period->streams, i); + if (ds->done) continue; + if (ds->period_startds->period_start)) + next_period_start = ds->period_start; + } + //check current period dur + count = gf_list_count(ctx->current_period->streams); + for (i=0; icurrent_period->streams, i); + if (!ds->period_dur) continue; + dur = period_start + ds->period_dur; + if ((next_period_start<0) || (next_period_start>dur)) + next_period_start = dur; + } + if (next_period_start>0) { + ctx->current_period->period->duration = (u32) ( (next_period_start - period_start) * 1000 ); + } + } + + //assign period ID if none specified + if (strcmp(period_id, DEFAULT_PERIOD_ID)) + ctx->current_period->period->ID = gf_strdup(period_id); + //assign ID if dynamic - if dash_ctx also assign ID since we could have moved from dynamic to static + else if (!ctx->current_period->period->ID && ((ctx->dmode != GF_MPD_TYPE_STATIC) || ctx->state) ) { + char szPName[50]; + sprintf(szPName, "DID%d", ctx->last_dyn_period_id + 1); + ctx->current_period->period->ID = gf_strdup(szPName); + } + + min_dur = min_adur = max_adur = 0; + //setup representation dependency / components (muxed) + count = gf_list_count(ctx->current_period->streams); + for (i=0; icurrent_period->streams, i); + ds->period = ctx->current_period; + + if (ctx->loop) { + Double d=0; + const GF_PropertyValue *prop = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_DURATION); + if (prop && prop->value.lfrac.den) { + d = (Double) prop->value.lfrac.num; + d /= prop->value.lfrac.den; + if (ds->clamped_dur && (ds->clamped_durclamped_dur; + + if (ds->stream_type == GF_STREAM_AUDIO) { + if (d > max_adur) max_adur = d; + if (!min_adur || (d < min_adur)) min_adur = d; + } else { + if (!min_dur || (d < min_dur)) min_dur = d; + } + } + } + + if (ds->stream_type == GF_STREAM_FILE) { + remove = GF_TRUE; + } else if (remote_xlink) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] period uses xlink but other media source %s, ignoring source\n", ds->src_url)); + remove = GF_TRUE; + } else if (empty_period) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] empty period defines but other media source %s, ignoring source\n", ds->src_url)); + remove = GF_TRUE; + } + + if (remove) { + ds->done = 1; + ds->period = NULL; + gf_list_rem(ctx->current_period->streams, i); + gf_list_add(ctx->next_period->streams, ds); + i--; + count--; + continue; + } + + if (next_period_start>0) { + ds->force_rep_end = (u64) ((next_period_start - period_start) * ds->timescale); + } + + if (is_restore) continue; + + //add period descriptors + dasher_add_descriptors(&ctx->current_period->period->other_descriptors, ds->p_period_desc); + //add representation descriptors + dasher_add_descriptors(&ds->rep->other_descriptors, ds->p_rep_desc); + + if (ds->muxed_base) continue; + + if (ds->stream_type==GF_STREAM_VISUAL) + ds_video = ds; + ds->nb_comp = 1; + + for (j=0; jcurrent_period->streams, j); + if (a_ds->dep_id && (a_ds->src_id==ds->src_id) && (a_ds->dep_id==ds->id) ) { + gf_list_add(ds->complementary_streams, a_ds); + has_deps = GF_TRUE; + if (!a_ds->rep->dependency_id) a_ds->rep->dependency_id = gf_strdup(ds->rep->id); + } + if (!a_ds->muxed_base && !strcmp(a_ds->rep_id, ds->rep_id) && (ctx->muxtype!=DASHER_MUX_RAW) ) { + a_ds->muxed_base = ds; + a_ds->dash_dur = ds->dash_dur; + has_muxed_bases = GF_TRUE; + ds->nb_comp++; + + if (ctx->bs_switch==DASHER_BS_SWITCH_MULTI) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Bitstream Swicthing mode \"multi\" is not supported with multiplexed representations, disabling bitstream switching\n")); + ctx->bs_switch = DASHER_BS_SWITCH_OFF; + } + if (!ds->rep->codecs || !strstr(ds->rep->codecs, a_ds->rep->codecs)) { + gf_dynstrcat(&ds->rep->codecs, a_ds->rep->codecs, ","); + } + + if (ctx->profile == GF_DASH_PROFILE_AVC264_LIVE) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Muxed representations not allowed in DASH-IF AVC264 live profile\n\tswitching to regular live profile\n")); + ctx->profile = GF_DASH_PROFILE_LIVE; + } else if (ctx->profile == GF_DASH_PROFILE_HBBTV_1_5_ISOBMF_LIVE) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Muxed representations not allowed in HbbTV 1.5 ISOBMF live profile\n\tswitching to regular live profile\n")); + ctx->profile = GF_DASH_PROFILE_LIVE; + } else if (ctx->profile == GF_DASH_PROFILE_AVC264_ONDEMAND) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Muxed representations not allowed in DASH-IF AVC264 onDemand profile\n\tswitching to regular onDemand profile\n")); + ctx->profile = GF_DASH_PROFILE_ONDEMAND; + } + } + } + //use video as main stream for segmentation of muxed sources + if (ds_video != ds) { + for (j=0; jcurrent_period->streams, j); + if ((a_ds->muxed_base==ds) || (a_ds==ds)) { + if (a_ds == ds_video) + a_ds->muxed_base = NULL; + else + a_ds->muxed_base = ds_video; + } + } + } + } + + + if (ctx->loop && max_adur) { + if (max_adur != min_adur) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Audio streams in the period have different durations (min %lf, max %lf), may result in bad synchronization while looping\n", min_adur, max_adur)); + } + for (i=0; icurrent_period->streams, i); + if (ds->duration.num > max_adur * ds->duration.den) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Input %s: max audio duration (%lf) in the period is less than duration (%lf), clamping will happen\n", ds->src_url , max_adur, ((Double)ds->duration.num)/ds->duration.den )); + } + ds->clamped_dur = max_adur; + } + } + + + if (is_restore) return GF_OK; + + if (has_deps) { + for (i=0; icurrent_period->streams, i); + //assign rep bitrates + if (ds->dep_id) + ds->rep->bandwidth = dasher_get_dep_bitrate(ctx, ds); + + if (gf_list_count(ds->complementary_streams)) { + u32 nb_str = gf_list_count(ds->complementary_streams); + ds->moof_sn_inc = 1+nb_str; + ds->moof_sn = 1; + for (j=0; jcomplementary_streams, j); + a_ds->moof_sn_inc = ds->moof_sn_inc; + a_ds->moof_sn = ds->moof_sn + 1 + j; + } + } + } + } + + //moved all mux components after the base one, so that we do the segmentation on the main component + if (has_muxed_bases) { + //setup reps in adaptation sets + for (i=0; icurrent_period->streams, i); + if (!ds->muxed_base) continue; + gf_list_rem(ctx->current_period->streams, i); + gf_list_add(ctx->current_period->streams, ds); + } + } + + //setup reps in adaptation sets + for (i=0; icurrent_period->streams, i); + if (ds->muxed_base) continue; + //already setup + if (ds->set) continue; + + //not setup, create new AS + ds->set = gf_mpd_adaptation_set_new(); + ds->owns_set = GF_TRUE; + ds->set->udta = ds; + + if (!ds->set->representations) + ds->set->representations = gf_list_new(); + if (!ds->period->period->adaptation_sets) + ds->period->period->adaptation_sets = gf_list_new(); + gf_list_add(ds->period->period->adaptation_sets, ds->set); + + gf_list_add(ds->set->representations, ds->rep); + ds->nb_rep = 1; + + //add non-conditional adaptation set descriptors + dasher_add_descriptors(&ds->set->other_descriptors, ds->p_as_any_desc); + //new AS, add conditional adaptation set descriptors + dasher_add_descriptors(&ds->set->other_descriptors, ds->p_as_desc); + + if (ds->as_id) has_as_id = GF_TRUE; + //for each following, check if same AS is possible + for (j=i+1; jcurrent_period->streams, j); + //we add to the adaptation set even if shared rep, we will remove it when assigning templates and pids + if (dasher_same_adaptation_set(ctx, ds, a_ds)) { + a_ds->set = ds->set; + gf_list_add(ds->set->representations, a_ds->rep); + ds->nb_rep++; + //add non-conditional adaptation set descriptors + dasher_add_descriptors(&ds->set->other_descriptors, a_ds->p_as_any_desc); + } + } + } + if (has_as_id) { + for (i=0; icurrent_period->streams, i); + if (!ds->owns_set) continue; + for (j=i+1; jcurrent_period->streams, j); + //avoid as id duplicates + if (ds->owns_set && a_ds->owns_set && (a_ds->as_id == ds->as_id)) { + a_ds->as_id = 0; + } + } + } + } + + //we need a pass on adaptation sets to figure out if they share the same source URL + //in case we use file name in templates + nb_sets = gf_list_count(ctx->current_period->period->adaptation_sets); + for (i=0; isigfrag) + break; + + set = gf_list_get(ctx->current_period->period->adaptation_sets, i); + assert(set); + rep = gf_list_get(set->representations, 0); + assert(rep); + ds = rep->playback.udta; + + if (!dasher_template_use_source_url(ds->template ? ds->template : ctx->template)) + continue; + + for (j=i+1; jcurrent_period->period->adaptation_sets, j); + rep = gf_list_get(set->representations, 0); + assert(rep); + a_ds = rep->playback.udta; + + if (!dasher_template_use_source_url(a_ds->template ? a_ds->template : ctx->template)) + continue; + + p1 = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_URL); + p2 = gf_filter_pid_get_property(a_ds->ipid, GF_PROP_PID_URL); + if (p1 && p2) { + if (gf_props_equal(p1, p2)) split_init = GF_TRUE; + } else { + p1 = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_FILEPATH); + p2 = gf_filter_pid_get_property(a_ds->ipid, GF_PROP_PID_FILEPATH); + if (p1 && p2 && gf_props_equal(p1, p2)) split_init = GF_TRUE; + } + + if (split_init) { + ds->split_set_names = GF_TRUE; + a_ds->split_set_names = GF_TRUE; + } + } + } + + /*HbbTV 1.5 ISO live specific checks*/ + if (ctx->profile == GF_DASH_PROFILE_HBBTV_1_5_ISOBMF_LIVE) { + if (nb_sets > 16) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Max 16 adaptation sets in HbbTV 1.5 ISO live profile\n\tswitching to DASH AVC/264 live profile\n")); + ctx->profile = GF_DASH_PROFILE_AVC264_LIVE; + } + if (nb_periods > 32) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Max 32 periods in HbbTV 1.5 ISO live profile\n\tswitching to regular DASH AVC/264 live profile\n")); + ctx->profile = GF_DASH_PROFILE_AVC264_LIVE; + } + if (ctx->segdur < 1.0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Min segment duration 1s in HbbTV 1.5 ISO live profile\n\tcapping to 1s\n")); + ctx->segdur = 1.0; + } + if (ctx->segdur > 15.0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Max segment duration 15s in HbbTV 1.5 ISO live profile\n\tcapping to 15s\n")); + ctx->segdur = 15.0; + } + } + + //init UTC reference time for dynamic + if (!ctx->mpd->availabilityStartTime && (ctx->dmode!=GF_MPD_TYPE_STATIC) ) { + u64 dash_start_date = ctx->ast ? gf_net_parse_date(ctx->ast) : 0; + + if (!ctx->utc_initialized) { + dasher_init_utc(filter, ctx); + } + + ctx->mpd->gpac_init_ntp_ms = gf_net_get_ntp_ms(); + ctx->mpd->availabilityStartTime = dasher_get_utc(ctx); + + if (dash_start_date && (dash_start_date < ctx->mpd->availabilityStartTime)) { + u64 start_date_sec_ntp, secs; + Double ms; + //recompute NTP init time matching the required ast + secs = dash_start_date/1000; + start_date_sec_ntp = (u32) secs; + start_date_sec_ntp += GF_NTP_SEC_1900_TO_1970; + ms = (Double) (dash_start_date - secs*1000); + ms /= 1000.0; + ctx->mpd->gpac_init_ntp_ms = (u64) (start_date_sec_ntp * 1000 + ms); + //compute number of seconds to discard + ctx->nb_secs_to_discard = (Double) (ctx->mpd->availabilityStartTime - dash_start_date); + ctx->nb_secs_to_discard /= 1000; + //don't discard TSB, this will be done automatically + + ctx->mpd->availabilityStartTime = dash_start_date; + + } else if (dash_start_date) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] specified AST %s seems in the future, ignoring it\n", ctx->ast)); + } + } + + //setup adaptation sets bitstream switching + for (i=0; icurrent_period->streams, i); + if (!ds->owns_set) continue; + //check bitstream switching + dasher_check_bitstream_swicthing(ctx, ds->set); + //setup AS defaults, roles and co + dasher_setup_set_defaults(ctx, ds->set); + //setup sources, templates & co + dasher_setup_sources(filter, ctx, ds->set); + } + + while (gf_list_count(ctx->postponed_pids)) { + GF_DashStream *ds = gf_list_get(ctx->postponed_pids, 0); + dasher_open_pid(filter, ctx, ds, ds->multi_pids, GF_FALSE); + } + + //good to go ! + for (i=0; icurrent_period->streams, i); + //setup segmentation + ds->rep_init = GF_FALSE; + ds->seg_done = GF_FALSE; + ds->next_seg_start = (u32) ( ds->dash_dur * ds->timescale ); + ds->adjusted_next_seg_start = ds->next_seg_start; + ds->segment_started = GF_FALSE; + ds->seg_number = ds->startNumber; + ds->first_cts = ds->first_dts = ds->max_period_dur = 0; + + //simulate N loops of the source + if (ctx->nb_secs_to_discard) { + u64 period_dur, seg_dur; + u32 nb_skip=0; + + period_dur = (u64) (ctx->nb_secs_to_discard * ds->timescale); + seg_dur = (u64) (ds->dash_dur * ds->timescale); + + nb_skip = (u32) (period_dur / seg_dur); + ds->ts_offset += nb_skip*seg_dur; + ds->seg_number += nb_skip; + +// ds->max_period_dur += nb_skip*seg_dur; + ds->max_period_dur = ds->cumulated_dur; + ds->adjusted_next_seg_start += ds->ts_offset; + ds->next_seg_start += ds->ts_offset; + } + } + + ctx->nb_secs_to_discard = 0; + + if (ctx->state) + dasher_context_update_period_start(ctx); + return GF_OK; +} + +static void dasher_insert_timeline_entry(GF_DasherCtx *ctx, GF_DashStream *ds) +{ + GF_MPD_SegmentTimelineEntry *s; + u64 duration; + Bool is_first = GF_FALSE; + Bool seg_align = GF_FALSE; + GF_MPD_SegmentTimeline *tl=NULL; + + //we only store segment timeline for the main component in the representation + if (ds->muxed_base) return; + + if (ds->rep && ds->rep->state_seg_list) { + GF_DASH_SegmentContext *sctx = gf_list_last(ds->rep->state_seg_list); + if (sctx) + sctx->dur = ds->first_cts_in_next_seg - ds->first_cts_in_seg; + } + + //we only use segment timeline with templates + if (!ctx->stl) return; + + if (gf_list_find(ds->set->representations, ds->rep)==0) is_first = GF_TRUE; + assert(ds->first_cts_in_next_seg > ds->first_cts_in_seg); + duration = ds->first_cts_in_next_seg - ds->first_cts_in_seg; + if (ds->timescale != ds->mpd_timescale) { + duration *= ds->mpd_timescale; + duration /= ds->timescale; + } + seg_align = (ds->set->segment_alignment || ds->set->subsegment_alignment) ? GF_TRUE : GF_FALSE; + //not first and segment alignment, ignore + if (!is_first && seg_align) { + return; + } + + //no segment alignment store in each rep + if (!seg_align) { + GF_MPD_SegmentTimeline **p_tl=NULL; + if (!ds->rep) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] failed to store timeline entru, no representation assigned !\n")); + return; + } + + if (ctx->tpl) { + p_tl = &ds->rep->segment_template->segment_timeline; + ds->rep->segment_template->duration = 0; + } else { + p_tl = &ds->rep->segment_list->segment_timeline; + ds->rep->segment_list->duration = 0; + } + if (! (*p_tl)) { + (*p_tl) = gf_mpd_segmentimeline_new(); + } + tl = (*p_tl); + } else { + Bool new_tl = GF_FALSE; + GF_MPD_SegmentTimeline **p_tl=NULL; + + if (!ds->set) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] failed to store timeline entru, no AdpatationSet assigned !\n")); + return; + } + assert(ds->set); + if (ctx->tpl) { + //in case we had no template at set level + if (!ds->set->segment_template) { + GF_SAFEALLOC(ds->set->segment_template, GF_MPD_SegmentTemplate); + if (ds->set->segment_template) { + ds->set->segment_template->start_number = (u32) -1; + ds->set->segment_template->timescale = ds->timescale; + } + new_tl = GF_TRUE; + } + p_tl = &ds->set->segment_template->segment_timeline; + ds->set->segment_template->duration = 0; + } else { + //in case we had no template at set level + if (!ds->set->segment_list) { + GF_SAFEALLOC(ds->set->segment_list, GF_MPD_SegmentList); + if (ds->set->segment_list) { + ds->set->segment_list->start_number = (u32) -1; + ds->set->segment_list->timescale = ds->timescale; + } + new_tl = GF_TRUE; + } + p_tl = &ds->set->segment_list->segment_timeline; + ds->set->segment_list->duration = 0; + } + + if (! (*p_tl) ) { + (*p_tl) = gf_mpd_segmentimeline_new(); + } + tl = (*p_tl); + if (new_tl) { + u32 i, count = gf_list_count(ds->set->representations); + for (i=0; iset->representations, i); + if (arep && arep->segment_template) arep->segment_template->duration = 0; + if (arep && arep->segment_list) arep->segment_list->duration = 0; + } + } + } + + //append to previous entry if possible + s = gf_list_last(tl->entries); + if (s && (s->duration == duration) && (s->start_time + (s->repeat_count+1) * s->duration == ds->seg_start_time)) { + s->repeat_count++; + return; + } + //nope, allocate + GF_SAFEALLOC(s, GF_MPD_SegmentTimelineEntry); + if (!s) return; + s->start_time = ds->seg_start_time; + s->duration = (u32) duration; + gf_list_add(tl->entries, s); +} + +static void dasher_copy_segment_timelines(GF_DasherCtx *ctx, GF_MPD_AdaptationSet *set) +{ + GF_MPD_SegmentTimeline *src_tl = NULL; + u32 i, j, count, nb_s; + if (!ctx->stl) return; + //get as level segment timeline, set it to NULL, reassign it to first rep and clone for other reps + if (ctx->tpl) { + assert(set->segment_template->segment_timeline); + src_tl = set->segment_template->segment_timeline; + set->segment_template->segment_timeline = NULL; + } else { + assert(set->segment_list->segment_timeline); + src_tl = set->segment_list->segment_timeline; + set->segment_list->segment_timeline = NULL; + } + nb_s = gf_list_count(src_tl->entries); + + count = gf_list_count(set->representations); + for (i=0; irepresentations, i); + if (ctx->tpl) { + if (!rep->segment_template) { + GF_SAFEALLOC(rep->segment_template, GF_MPD_SegmentTemplate); + if (!rep->segment_template) continue; + } + if (!i) { + rep->segment_template->segment_timeline = src_tl; + continue; + } + if (!rep->segment_template->segment_timeline) { + rep->segment_template->segment_timeline = gf_mpd_segmentimeline_new(); + } + tl = rep->segment_template->segment_timeline; + } else { + if (!rep->segment_list) { + GF_SAFEALLOC(rep->segment_list, GF_MPD_SegmentList); + if (!rep->segment_list) continue; + rep->segment_list->start_number = (u32) -1; + } + if (!i) { + rep->segment_list->segment_timeline = src_tl; + continue; + } + if (!rep->segment_list->segment_timeline) { + rep->segment_list->segment_timeline = gf_mpd_segmentimeline_new(); + } + tl = rep->segment_list->segment_timeline; + } + assert(tl); + for (j=0; jentries, j); + GF_SAFEALLOC(s, GF_MPD_SegmentTimelineEntry); + if (!s) continue; + + s->duration = src_s->duration; + s->repeat_count = src_s->repeat_count; + s->start_time = src_s->start_time; + gf_list_add(tl->entries, s); + } + } +} + +static void dasher_flush_segment(GF_DasherCtx *ctx, GF_DashStream *ds) +{ + u32 i, count; + GF_DashStream *ds_done = NULL, *ds_not_done = NULL; + GF_DashStream *set_ds = ds->set->udta; + GF_DashStream *base_ds = ds->muxed_base ? ds->muxed_base : ds; + Bool has_ds_done = GF_FALSE; + u32 seg_dur_ms=0; + GF_DashStream *ds_log = NULL; + u64 first_cts_in_cur_seg=0; + + ctx->update_report = -1; + + if (ds->segment_started) { + Double seg_duration; + u64 seg_duration_unscale = base_ds->first_cts_in_next_seg - ds->first_cts_in_seg; + //seg_duration /= base_ds->timescale; + if (!seg_duration_unscale) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Segment %d is empty - pid end of stream %d\n", ds->seg_number, gf_filter_pid_is_eos(ds->ipid) )); + } + seg_dur_ms = (u32) (seg_duration_unscale*1000 / base_ds->timescale); + if (seg_dur_ms * base_ds->timescale < seg_duration_unscale* 1000) seg_dur_ms++; + + first_cts_in_cur_seg = ds->first_cts_in_seg; + if (ctx->mpd->max_segment_duration < seg_dur_ms) + ctx->mpd->max_segment_duration = seg_dur_ms; + + seg_duration = (Double) base_ds->first_cts_in_next_seg - ds->first_cts_in_seg; + seg_duration /= base_ds->timescale; + + if (ctx->sigfrag) { + if (ds->no_seg_dur) { + ds->gm_duration_total += seg_duration; + ds->gm_nb_segments++; + if (!ds->gm_duration_min || (ds->gm_duration_min>seg_duration) ) + ds->gm_duration_min = seg_duration; + if (ds->gm_duration_maxgm_duration_max = seg_duration; + ds->dash_dur = ds->gm_duration_total; + ds->dash_dur /= ds->gm_nb_segments; + ds->rep->dash_dur = ds->dash_dur; + } + + if (ds->rep->segment_list && (ds->rep->segment_list->duration != ds->dash_dur) ) { + ds->rep->segment_list->duration = (u64) (ds->dash_dur * ds->rep->segment_list->timescale); + } + if (ds->set && ds->set->segment_list && ( ds->set->segment_list->duration != ds->dash_dur) ) { + ds->set->segment_list->duration = (u64) (ds->dash_dur * ds->set->segment_list->timescale); + } + if (ds->rep->segment_template && (ds->rep->segment_template->duration != ds->dash_dur) ) { + ds->rep->segment_template->duration = (u64) (ds->dash_dur * ds->rep->segment_template->timescale); + } + if (ds->set && ds->set->segment_template && (ds->set->segment_template->duration != ds->dash_dur) ) { + ds->set->segment_template->duration = (u64) (ds->dash_dur * ds->set->segment_template->timescale); + } + } + if (!base_ds->done && !ctx->stl && ctx->tpl && !ctx->cues) { + + if (seg_duration< ds->dash_dur/2) { + + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Segment %d duration %g less than half DASH duration, consider reencoding or using segment timeline\n", ds->seg_number, seg_duration)); + } else if (seg_duration > 3 * ds->dash_dur / 2) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Segment %d duration %g more than 3/2 DASH duration, consider reencoding or using segment timeline\n", ds->seg_number, seg_duration)); + } + } + dasher_insert_timeline_entry(ctx, base_ds); + + if (ctx->align) { + if (!set_ds->nb_rep_done || !set_ds->set_seg_duration) { + set_ds->set_seg_duration = seg_duration; + } else { + Double diff = set_ds->set_seg_duration - seg_duration; + + if (ABS(diff) > 0.001) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Segments are not aligned across representations: first rep segment duration %g but new segment duration %g for the same segment %d\n", set_ds->set_seg_duration, seg_duration, set_ds->seg_number)); + + if (ctx->profile != GF_DASH_PROFILE_FULL) { + set_ds->set->segment_alignment = GF_FALSE; + set_ds->set->subsegment_alignment = GF_FALSE; + ctx->profile = GF_DASH_PROFILE_FULL; + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] No segment alignment, switching to full profile\n")); + dasher_copy_segment_timelines(ctx, set_ds->set); + } + } + } + set_ds->nb_rep_done++; + if (set_ds->nb_rep_done < set_ds->nb_rep) { + if (ctx->subdur && (ds->cumulated_dur >= 0.8 * (ds->cumulated_subdur + ctx->subdur) * ds->timescale)) + ds->subdur_done = GF_TRUE; + return; + } + set_ds->set_seg_duration = 0; + set_ds->nb_rep_done = 0; + } + + ds_log = ds; + } else { + if (ctx->align) { + set_ds->nb_rep_done++; + if (set_ds->nb_rep_done < set_ds->nb_rep) return; + + set_ds->set_seg_duration = 0; + set_ds->nb_rep_done = 0; + } + } + + if (ctx->subdur && (ds->cumulated_dur >= 0.8 * (ds->cumulated_subdur + ctx->subdur) * ds->timescale)) + ds->subdur_done = GF_TRUE; + + count = gf_list_count(ctx->current_period->streams); + + if (ctx->subdur) { + u32 nb_sub_done=0; + if (ctx->subdur_done) return; + for (i=0; icurrent_period->streams, i); + if (a_ds->muxed_base) { + if (a_ds->muxed_base->subdur_done) a_ds->subdur_done = GF_TRUE; + } + + if (a_ds->subdur_done) { + nb_sub_done++; + } + } + // if one of the AS is done and we are at 30% of target subdur, abort + if (nb_sub_done && !ds->subdur_done + && (ctx->subdur && (ds->cumulated_dur >= (0.7 * (ds->cumulated_subdur + ctx->subdur)) * ds->timescale)) + ) { + ds->subdur_done = GF_TRUE; + nb_sub_done++; + } + if (nb_sub_done==count) + ctx->subdur_done = GF_TRUE; + } + + //reset all streams from our rep or our set + for (i=0; icurrent_period->streams, i); + //reset all in set if segment alignment + if (ctx->align) { + if (ds->set != set_ds->set) continue; + } else { + //otherwise reset only media components for this rep + if ((ds->muxed_base != base_ds) && (ds != base_ds)) continue; + } + + if (!ds->done) { + ds->first_cts_in_next_seg = ds->first_cts_in_seg = ds->est_first_cts_in_next_seg = 0; + } + + if (ds->muxed_base) { + if (!ds->done) { + ds->segment_started = GF_FALSE; + ds->seg_done = GF_FALSE; + } else { + has_ds_done = GF_TRUE; + } + continue; + } + base_ds = ds; + + if (base_ds->done) + ds_done = base_ds; + else if (base_ds->nb_comp_done==base_ds->nb_comp) ds_not_done = base_ds; + + if (!base_ds->done && base_ds->seg_done) { + base_ds->seg_done = GF_FALSE; + base_ds->nb_comp_done = 0; + +#ifndef GPAC_DISABLE_LOG + if (ctx->dmode>=GF_DASH_DYNAMIC) { + u32 asid; + s64 ast_diff; + u64 seg_ast = ctx->mpd->availabilityStartTime; + seg_ast += ctx->current_period->period->start; + seg_ast += (base_ds->adjusted_next_seg_start*1000) / base_ds->timescale; + + //if theoretical AST of the segment is less than the current UTC, we are producing the segment too late. + ast_diff = (s64) dasher_get_utc(ctx); + ast_diff -= seg_ast; + + asid = base_ds->set->id; + if (!asid) + asid = gf_list_find(ctx->current_period->period->adaptation_sets, base_ds->set) + 1; + + if (ast_diff>10) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] AS%d Rep %s segment %d done TOO LATE by %d ms\n", asid, base_ds->rep->id, base_ds->seg_number, (s32) ast_diff)); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[Dasher] AS%d Rep %s segment %d done %d ms %s UTC due time\n", asid, base_ds->rep->id, base_ds->seg_number, ABS(ast_diff), (ast_diff<0) ? "before" : "after")); + } + } +#endif + + + assert(base_ds->segment_started); + base_ds->segment_started = GF_FALSE; + + base_ds->next_seg_start += (u64) (base_ds->dash_dur * base_ds->timescale); + while (base_ds->next_seg_start <= base_ds->adjusted_next_seg_start) { + base_ds->next_seg_start += (u64) (base_ds->dash_dur * base_ds->timescale); + if (ctx->skip_seg) + base_ds->seg_number++; + } + base_ds->adjusted_next_seg_start = base_ds->next_seg_start; + base_ds->seg_number++; + } + } + + if (ds_log) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[Dasher] Rep#%s flush seg %d start %g duration %g next seg end time %g\n", ds_log->rep->id, ds_log->seg_number-1, ((Double)first_cts_in_cur_seg)/ds_log->timescale, ((Double)seg_dur_ms)/1000, ((Double)ds_log->adjusted_next_seg_start)/ds_log->timescale)); + } + + //muxed representation with unaligned duration, + if (has_ds_done) { + for (i=0; icurrent_period->streams, i); + //otherwise reset only media components for this rep + if ((ds->muxed_base != base_ds) && (ds != base_ds)) continue; + + if (ds->done && (base_ds->nb_comp_done < base_ds->nb_comp)) { + base_ds->nb_comp_done++; + } + } + } + + //some reps are done, other not, force a max time on all AS in the period + if (ds_done && ds_not_done) { + for (i=0; icurrent_period->streams, i); + + if (ds->done) { + if (ds->set->udta == set_ds) + set_ds->nb_rep_done++; + } else if (ctx->check_dur && !ds->force_rep_end) { + ds->force_rep_end = ds_done->first_cts_in_next_seg * ds->timescale / ds_done->timescale; + } + } + } +} + +static char *dasher_strip_base(GF_DasherCtx *ctx, char *url) +{ + char *manifest_path = ctx->out_path; + char *file_path = url; + char *res = url; + + if (!strncmp(manifest_path, "./", 2)) manifest_path+=2; + if (!strncmp(file_path, "./", 2)) file_path+=2; + + const char *base_manifest = gf_file_basename(manifest_path); + u32 diff = (u32) (base_manifest - manifest_path); + if (!strncmp(file_path, manifest_path, diff)) { + res = file_path + diff; + } + return res; +} + +static void dasher_mark_segment_start(GF_DasherCtx *ctx, GF_DashStream *ds, GF_FilterPacket *pck, GF_FilterPacket *in_pck) +{ + GF_DASH_SegmentContext *seg_state=NULL; + char szSegmentName[GF_MAX_PATH], szSegmentFullPath[GF_MAX_PATH], szIndexName[GF_MAX_PATH]; + GF_DashStream *base_ds = ds->muxed_base ? ds->muxed_base : ds; + + if (pck) { + if (ctx->ntp==DASHER_NTP_YES) { + u64 ntpts = gf_net_get_ntp_ts(); + gf_filter_pck_set_property(pck, GF_PROP_PCK_SENDER_NTP, &PROP_LONGUINT(ntpts)); + } else if (ctx->ntp==DASHER_NTP_REM) { + gf_filter_pck_set_property(pck, GF_PROP_PCK_SENDER_NTP, NULL); + } + + gf_filter_pck_set_property(pck, GF_PROP_PCK_FILENUM, &PROP_UINT(base_ds->seg_number ) ); + } + + //only signal file name & insert timelines on one stream for muxed representations + if (ds->muxed_base) return; + + ds->seg_start_time = ds->first_cts_in_seg; + if (ds->timescale != ds->mpd_timescale) { + ds->seg_start_time *= ds->mpd_timescale; + ds->seg_start_time /= ds->timescale; + } + + if (ctx->store_seg_states) { + if (!ds->rep->state_seg_list) { + ds->rep->state_seg_list = gf_list_new(); + } + if (!ds->rep->dash_dur) { + const GF_PropertyValue *p; + ds->rep->timescale = ds->timescale; + ds->rep->streamtype = ds->stream_type; + ds->rep->timescale_mpd = ds->mpd_timescale; + p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_HLS_GROUPID); + if (p) + ds->rep->groupID = p->value.string; + + ds->rep->dash_dur = ds->dash_dur; + if (ctx->sigfrag) { + const GF_PropertyValue *pid_url = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_URL); + if (pid_url && pid_url->value.string) { + ds->rep->hls_single_file_name = dasher_strip_base(ctx, pid_url->value.string); + } + } + + if (!ds->rep->hls_single_file_name && !ctx->m2ts) { + switch (ctx->muxtype) { + case DASHER_MUX_TS: + case DASHER_MUX_OGG: + case DASHER_MUX_RAW: + break; + default: + ds->rep->hls_single_file_name = ds->init_seg; + } + } + ds->rep->nb_chan = ds->nb_ch; + ds->rep->m3u8_name = ds->hls_vp_name; + if (ds->fps.den) { + ds->rep->fps = ds->fps.num; + ds->rep->fps /= ds->fps.den; + } + } + GF_SAFEALLOC(seg_state, GF_DASH_SegmentContext); + if (!seg_state) return; + seg_state->time = ds->seg_start_time; + seg_state->seg_num = ds->seg_number; + gf_list_add(ds->rep->state_seg_list, seg_state); + if (ctx->sigfrag) { + const GF_PropertyValue *frag_range = gf_filter_pck_get_property(in_pck, GF_PROP_PCK_FRAG_RANGE); + const GF_PropertyValue *frag_url = gf_filter_pck_get_property(in_pck, GF_PROP_PID_URL); + if (frag_url && frag_url->value.string) { + char *f_url = dasher_strip_base(ctx, frag_url->value.string); + seg_state->filename = gf_strdup(f_url); + } + else if (frag_range) { + seg_state->file_offset = frag_range->value.lfrac.num; + seg_state->file_size = (u32) (frag_range->value.lfrac.den - seg_state->file_offset); + + if (ds->rep->segment_base && !ds->rep->segment_base->initialization_segment) { + GF_MPD_URL *url; + GF_SAFEALLOC(url, GF_MPD_URL); + if (url) { + GF_SAFEALLOC(url->byte_range, GF_MPD_ByteRange); + if (url->byte_range) { + url->byte_range->start_range = 0; + url->byte_range->end_range = seg_state->file_offset-1; + } + } + ds->rep->segment_base->initialization_segment = url; + } + } else { + gf_list_del_item(ds->rep->state_seg_list, seg_state); + gf_free(seg_state); + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Manifest generation only but not fragment information in packet, source demux not properly configured\n")); + ctx->in_error = GF_TRUE; + } + } else { + gf_list_add(ds->pending_segment_states, seg_state); + ctx->nb_seg_url_pending++; + } + } + + szIndexName[0] = 0; + if (ds->idx_template) { + //get final segment template - output file name is NULL, we already have solved this in source_setup + gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_REPINDEX, ds->set->bitstream_switching, szIndexName, base_ds->rep_id, NULL, base_ds->idx_template, NULL, base_ds->seg_start_time, base_ds->rep->bandwidth, base_ds->seg_number, ctx->stl); + + strcpy(szSegmentFullPath, szIndexName); + if (ctx->out_path) { + char *rel = gf_url_concatenate(ctx->out_path, szIndexName); + if (rel) { + strcpy(szSegmentFullPath, rel); + gf_free(rel); + } + } + if (pck) + gf_filter_pck_set_property(pck, GF_PROP_PCK_IDXFILENAME, &PROP_STRING(szSegmentFullPath) ); + } + + if (ctx->sseg) { + if (ctx->sigfrag) { + const GF_PropertyValue *p = gf_filter_pck_get_property(in_pck, GF_PROP_PCK_SIDX_RANGE); + if (p) { + if (ds->rep->segment_base && !ds->rep->segment_base->index_range) { + GF_SAFEALLOC(ds->rep->segment_base->index_range, GF_MPD_ByteRange); + if (ds->rep->segment_base->index_range) { + ds->rep->segment_base->index_range->start_range = p->value.lfrac.num; + ds->rep->segment_base->index_range->end_range = p->value.lfrac.den; + ds->rep->segment_base->index_range_exact = GF_TRUE; + } + + if (!ds->rep->segment_base->initialization_segment) { + GF_SAFEALLOC(ds->rep->segment_base->initialization_segment, GF_MPD_URL); + } + if (ds->rep->segment_base->initialization_segment && !ds->rep->segment_base->initialization_segment->byte_range) { + GF_SAFEALLOC(ds->rep->segment_base->initialization_segment->byte_range, GF_MPD_ByteRange); + if (ds->rep->segment_base->initialization_segment->byte_range) { + ds->rep->segment_base->initialization_segment->byte_range->start_range = 0; + ds->rep->segment_base->initialization_segment->byte_range->end_range = p->value.lfrac.num-1; + } + } + } else { + ctx->in_error = GF_TRUE; + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Several SIDX found but trying to regenerate an on-demand MPD, source file is not compatible. Try re-dashing the content or use main or full profiles\n")); + } + } + } + return; + } + + if (ctx->sfile) { + GF_MPD_SegmentURL *seg_url; + assert(ds->rep->segment_list); + GF_SAFEALLOC(seg_url, GF_MPD_SegmentURL); + if (!seg_url) return; + + gf_list_add(ds->rep->segment_list->segment_URLs, seg_url); + if (szIndexName[0]) + seg_url->index = gf_strdup(szIndexName); + + if (ctx->sigfrag) { + const GF_PropertyValue *frag_range = gf_filter_pck_get_property(in_pck, GF_PROP_PCK_FRAG_RANGE); + const GF_PropertyValue *frag_url = gf_filter_pck_get_property(in_pck, GF_PROP_PID_URL); + if (frag_url && frag_url->value.string) { + seg_url->media = gf_strdup(dasher_strip_base(ctx, frag_url->value.string)); + if (ds->rep->segment_list && ds->rep->segment_list->initialization_segment && !ds->rep->segment_list->initialization_segment->sourceURL) { + + frag_url = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_URL); + if (frag_url && frag_url->value.string) { + u32 j, nb_base; + ds->rep->segment_list->initialization_segment->sourceURL = gf_strdup(dasher_strip_base(ctx, frag_url->value.string) ); + + nb_base = gf_list_count(ds->rep->base_URLs); + for (j=0; jrep->base_URLs, j); + if (! strcmp(burl->URL, frag_url->value.string)) { + gf_list_rem(ds->rep->base_URLs, j); + gf_mpd_base_url_free(burl); + break; + } + } + } + } + } + else if (frag_range) { + GF_SAFEALLOC(seg_url->media_range, GF_MPD_ByteRange); + if (seg_url->media_range) { + seg_url->media_range->start_range = frag_range->value.lfrac.num; + seg_url->media_range->end_range = frag_range->value.lfrac.den - 1; + } + if (ds->rep->segment_list && ds->rep->segment_list->initialization_segment && !ds->rep->segment_list->initialization_segment->byte_range) { + GF_SAFEALLOC(ds->rep->segment_list->initialization_segment->byte_range, GF_MPD_ByteRange); + if (ds->rep->segment_list->initialization_segment->byte_range) { + ds->rep->segment_list->initialization_segment->byte_range->start_range = 0; + ds->rep->segment_list->initialization_segment->byte_range->end_range = frag_range->value.lfrac.num-1; + } + } + } + } else { + gf_list_add(ds->pending_segment_urls, seg_url); + ctx->nb_seg_url_pending++; + } + return; + } + + if (!ctx->stl && !ctx->cues) { + Double drift, seg_start = (Double) ds->seg_start_time; + seg_start /= ds->mpd_timescale; + drift = seg_start - (ds->seg_number - ds->startNumber) * ds->dash_dur; + + if ((ds->dash_dur>0) && (ABS(drift) > ds->dash_dur/2)) { + u64 cts = 0; + if (pck) { + cts = gf_filter_pck_get_cts(pck); + cts -= ds->first_cts; + } + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] First CTS "LLU" in segment %d drifting by %g (more than half a segment duration) from segment time, consider reencoding or using segment timeline\n", cts, ds->seg_number, drift)); + } + } + + //get final segment template - output file name is NULL, we already have solved this in source_setup + gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_SEGMENT, ds->set->bitstream_switching, szSegmentName, base_ds->rep_id, NULL, base_ds->seg_template, NULL, base_ds->seg_start_time, base_ds->rep->bandwidth, base_ds->seg_number, ctx->stl); + + strcpy(szSegmentFullPath, szSegmentName); + + if (ctx->out_path) { + char *rel = NULL; + if (ctx->do_m3u8 && ds->hls_vp_name) { + char *tmp = gf_url_concatenate(ctx->out_path, ds->hls_vp_name); + if (tmp) { + rel = gf_url_concatenate(tmp, szSegmentName); + gf_free(tmp); + } + } + if (!rel) + rel = gf_url_concatenate(ctx->out_path, szSegmentName); + + if (rel) { + strcpy(szSegmentFullPath, rel); + gf_free(rel); + } + } + + if (seg_state) { + seg_state->filepath = gf_strdup(szSegmentFullPath); + seg_state->filename = gf_strdup(szSegmentName); + } + + if (ds->rep->segment_list) { + GF_MPD_SegmentURL *seg_url; + GF_SAFEALLOC(seg_url, GF_MPD_SegmentURL); + if (seg_url) { + gf_list_add(ds->rep->segment_list->segment_URLs, seg_url); + seg_url->media = gf_strdup(szSegmentName); + gf_list_add(ds->pending_segment_urls, seg_url); + if (szIndexName[0]) + seg_url->index = gf_strdup(szIndexName); + } + ctx->nb_seg_url_pending++; + } + if (pck) + gf_filter_pck_set_property(pck, GF_PROP_PCK_FILENAME, &PROP_STRING(szSegmentFullPath) ); +} + +static Bool dasher_check_loop(GF_DasherCtx *ctx, GF_DashStream *ds) +{ + u32 i, count; + u32 pmode = GF_PLAYBACK_MODE_NONE; + u64 ts_offset, max_ts_offset, max_ts_scale; + const GF_PropertyValue *p; + if (!ds->src_url) return GF_FALSE; + + //loop requested + if (ds->loop_state==2) return GF_TRUE; + + count = gf_list_count(ctx->current_period->streams); + if (!ds->loop_state) { + for (i=0; icurrent_period->streams, i); + + p = gf_filter_pid_get_property(a_ds->ipid, GF_PROP_PID_PLAYBACK_MODE); + if (p) pmode = p->value.uint; + if (pmode == GF_PLAYBACK_MODE_NONE) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Loop requested in subdur mode, but source cannot seek, defaulting to multi period for all streams\n")); + ctx->loop = GF_FALSE; + return GF_FALSE; + } + } + ds->loop_state = 1; + } + + max_ts_offset = 0; + max_ts_scale = 1; + //check all input media duration + for (i=0; icurrent_period->streams, i); + + //one pid is waiting for loop while another has done its subdur and won't process any new segment until the next subdur call, which + //will never happen since the first PID waits for loop. We must force early generation in this case + if (a_ds->subdur_done) { + a_ds->subdur_done = GF_FALSE; + //remember the max period dur before this forced segment generation + a_ds->subdur_forced_use_period_dur = a_ds->max_period_dur; + } + + //wait for each input to query loop + if (!a_ds->loop_state) { + a_ds->done = 0; + return GF_TRUE; + } + + //get max duration + ts_offset = a_ds->est_next_dts; + + if (max_ts_offset * a_ds->timescale < ts_offset * max_ts_scale) { + max_ts_offset = ts_offset; + max_ts_scale = a_ds->timescale; + } + } + + //assign ts offset and send stop/play + for (i=0; icurrent_period->streams, i); + + if (a_ds->subdur_done) + continue; + + ts_offset = max_ts_offset; + ts_offset *= a_ds->timescale; + ts_offset /= max_ts_scale; + + a_ds->ts_offset = ts_offset; + if (a_ds->done) continue; + if (a_ds->ts_offset > a_ds->est_next_dts) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Looping streams of unequal duration, inserting "LLU" us of timestamp delay in pid %s from %s\n", ((a_ds->ts_offset - a_ds->est_next_dts) * 1000000) / a_ds->timescale, gf_filter_pid_get_name(a_ds->ipid), a_ds->src_url)); + } + + a_ds->seek_to_pck = 0; + a_ds->nb_pck = 0; + a_ds->clamp_done = GF_FALSE; + + a_ds->loop_state = 2; + + if (ctx->subdur) { + GF_FilterEvent evt; + + GF_FEVT_INIT(evt, GF_FEVT_STOP, a_ds->ipid); + gf_filter_pid_send_event(a_ds->ipid, &evt); + + gf_filter_pid_set_discard(a_ds->ipid, GF_FALSE); + + GF_FEVT_INIT(evt, GF_FEVT_PLAY, a_ds->ipid); + evt.play.speed = 1.0; + gf_filter_pid_send_event(a_ds->ipid, &evt); + } + } + + return GF_TRUE; +} + +//depending on input formats, streams may be declared with or without DCD. For streams requiring the config, wait for it +static Bool dasher_check_streams_ready(GF_DasherCtx *ctx) +{ + u32 i=0; + GF_DashStream *ds; + ctx->streams_not_ready = GF_FALSE;; + while ((ds = gf_list_enum(ctx->pids, &i))) { + if (ds->dcd_not_ready) { + GF_FilterPacket *pck; + ds->dcd_not_ready = GF_FALSE; + pck = gf_filter_pid_get_packet(ds->ipid); + if (!pck) { + ds->dcd_not_ready = GF_TRUE; + ctx->streams_not_ready = GF_TRUE; + return GF_FALSE; + } + if (ds->dcd_not_ready) { + ds->dcd_not_ready = GF_FALSE; + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] PID %s first packet dispatched but no decoder config available, will use default one\n", gf_filter_pid_get_name(ds->ipid) )); + } + } + } + return GF_TRUE; +} + +void dasher_format_report(GF_Filter *filter, GF_DasherCtx *ctx) +{ + u32 i, count; + Double max_ts=0; + u32 total_pc = 0; + char szDS[200]; + char *szStatus = NULL; + + if (!gf_filter_reporting_enabled(filter)) + return; + if (!ctx->update_report) + return; + //don't update at each packet, this would be too much + if ((ctx->update_report>0) && (ctx->update_report < 20)) + return; + + ctx->update_report = 0; + + sprintf(szDS, "P%s", ctx->current_period->period->ID ? ctx->current_period->period->ID : "1"); + gf_dynstrcat(&szStatus, szDS, NULL); + + count = gf_list_count(ctx->current_period->streams); + for (i=0; icurrent_period->streams, i); + if (ds->muxed_base) continue; + + set_idx = 1 + gf_list_find(ctx->current_period->period->adaptation_sets, ds->set); + rep_idx = 1 + gf_list_find(ds->set->representations, ds->rep); + if (ds->stream_type==GF_STREAM_VISUAL) stype='V'; + else if (ds->stream_type==GF_STREAM_AUDIO) stype='A'; + else if (ds->stream_type==GF_STREAM_TEXT) stype='T'; + else stype='M'; + + if (ds->done || ds->subdur_done) { + sprintf(szDS, "AS#%d.%d(%c) done (%d segs)", set_idx, rep_idx, stype, ds->seg_number); + pc = 10000; + } else { + Double done; + if (ctx->cues) { + done = (Double) (ds->last_dts); + done /= ds->timescale; + snprintf(szDS, 200, "AS#%d.%d(%c) seg #%d %.2fs", set_idx, rep_idx, stype, ds->seg_number, done); + } else { + Double pcent; + done = (Double) ds->adjusted_next_seg_start; + done -= (Double) ds->last_dts; + if (done<0) + done=0; + done /= ds->timescale; + done = ds->dash_dur-done; + //this may happen since we don't print info at segment start + if (done<0) + done=0; + pcent = done / ds->dash_dur; + pc = (s32) (done * 10000); + snprintf(szDS, 200, "AS#%d.%d(%c) seg #%d %.2fs (%.2f %%)", set_idx, rep_idx, stype, ds->seg_number, done, 100*pcent); + } + + mpdtime = (Double) ds->last_dts; + mpdtime -= (Double) ds->first_cts; + if (mpdtime<0) mpdtime=0; + mpdtime /= ds->timescale; + + if (ds->duration.den && ds->duration.num) { + done = mpdtime; + + done *= ds->duration.den; + done /= ds->duration.num; + pc = (u32) (10000*done); + } + if (max_ts (s32) total_pc) total_pc = (u32) pc; + gf_dynstrcat(&szStatus, szDS, " "); + } + if (total_pc!=10000) { + sprintf(szDS, " / MPD %.2fs %d %%", max_ts, total_pc/100); + gf_dynstrcat(&szStatus, szDS, NULL); + } + gf_filter_update_status(filter, total_pc, szStatus); + gf_free(szStatus); +} + +static void dasher_drop_input(GF_DasherCtx *ctx, GF_DashStream *ds, Bool discard_all) +{ + if (ctx->sbound) { + while (gf_list_count(ds->packet_queue)) { + GF_FilterPacket *pck = gf_list_pop_front(ds->packet_queue); + if (gf_filter_pck_get_sap(pck)) { + assert(ds->nb_sap_in_queue); + ds->nb_sap_in_queue --; + } + gf_filter_pck_unref(pck); + if (!discard_all) break; + } + } else { + gf_filter_pid_drop_packet(ds->ipid); + } + if (discard_all) { + gf_filter_pid_set_discard(ds->ipid, GF_TRUE); + } +} + +static GF_Err dasher_process(GF_Filter *filter) +{ + u32 i, count, nb_init, has_init, nb_reg_done; + GF_DasherCtx *ctx = gf_filter_get_udta(filter); + GF_Err e; + Bool seg_done = GF_FALSE; + + if (ctx->in_error) + return GF_SERVICE_ERROR; + + //session regulation is on and we have a an MPD (setup done) and a next time (first seg processed) + //check if we have reached the next time + if (ctx->sreg && !ctx->state && ctx->mpd && ctx->mpd->gpac_next_ntp_ms) { + s64 diff = (s64) ctx->mpd->gpac_next_ntp_ms; + diff -= (s64) gf_net_get_ntp_ms(); + if (diff>100) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[Dasher] Next generation scheduled in %d ms, nothing to do\n", diff)); + gf_filter_ask_rt_reschedule(filter, (u32) (diff*1000)); + return GF_OK; + } + } + + if (ctx->streams_not_ready) { + if (! dasher_check_streams_ready(ctx)) return GF_OK; + } + if (gf_filter_connections_pending(filter)) + return GF_OK; + + if (ctx->is_eos) + return GF_EOS; + if (ctx->setup_failure) return ctx->setup_failure; + + nb_init = has_init = nb_reg_done = 0; + + count = gf_list_count(ctx->current_period->streams); + for (i=0; icurrent_period->streams, i); + + if (ds->done) continue; + base_ds = ds->muxed_base ? ds->muxed_base : ds; + //subdur mode abort, don't process + if (ds->subdur_done) { + continue; + } + if (ds->seg_done) continue; + + if (ctx->dmode == GF_MPD_TYPE_DYNAMIC_LAST) { + ds->done = 1; + continue; + } + + //flush as much as possible + while (1) { + u32 sap_type, dur, o_dur, split_dur; + s32 check_dur; + u64 cts, orig_cts, dts, ncts, split_dur_next; + Bool seg_over = GF_FALSE; + Bool is_packet_split = GF_FALSE; + Bool is_queue_flush = GF_FALSE; + GF_FilterPacket *pck; + GF_FilterPacket *dst; + + assert(ds->period == ctx->current_period); + pck = gf_filter_pid_get_packet(ds->ipid); + //we may change period after a packet fetch (reconfigure of input pid) + if (ds->period != ctx->current_period) { + //in closest mode, flush queue + if (!ctx->sbound || !gf_list_count(ds->packet_queue)) { + assert(gf_list_find(ctx->current_period->streams, ds)<0); + count = gf_list_count(ctx->current_period->streams); + i--; + break; + } + is_queue_flush = GF_TRUE; + } + + //queue mode + if (ctx->sbound) { + if (!is_queue_flush && pck) { + gf_filter_pck_ref(&pck); + gf_filter_pid_drop_packet(ds->ipid); + gf_list_add(ds->packet_queue, pck); + if (gf_filter_pck_get_sap(pck)) + ds->nb_sap_in_queue ++; + } + if ( + //we are flushing due to period switch + is_queue_flush + //we are flushing due to end of stream + || gf_filter_pid_is_eos(ds->ipid) || ds->clamp_done + ) { + pck = gf_list_get(ds->packet_queue, 0); + is_queue_flush = GF_TRUE; + } else if ( + //if current segment is not started, always get packet from queue + !ds->segment_started + //wait until we have more than 2 saps to get packet from queue, to check if next sap will be closer or not + || (ds->nb_sap_in_queue>=2) + ) { + pck = gf_list_get(ds->packet_queue, 0); + } else { + pck = NULL; + } + } + + + if (!pck) { + if (gf_filter_pid_is_eos(ds->ipid) || ds->clamp_done) { + u32 ds_done = 1; + if (ctx->loop && dasher_check_loop(ctx, ds)) { + if (ctx->subdur) + break; + //loop on the entire source, consider the stream not done for segment flush + ds_done = 0; + } + + ds->clamp_done = GF_FALSE; + + ctx->update_report = -1; + if (!ctx->sigfrag) + gf_filter_pid_set_eos(ds->opid); + + if (!ds->done) ds->done = ds_done; + ds->seg_done = GF_TRUE; + seg_done = GF_TRUE; + ds->first_cts_in_next_seg = ds->est_first_cts_in_next_seg; + ds->est_first_cts_in_next_seg = 0; + assert(base_ds->nb_comp_done < base_ds->nb_comp); + base_ds->nb_comp_done ++; + if (base_ds->nb_comp_done == base_ds->nb_comp) { + dasher_flush_segment(ctx, base_ds); + base_ds->nb_comp_done = 0; + } + //loop on the entire source, mark as done for subdur and check if all other streams are done + if (!ds->done) { + u32 j; + ds->done = 2; + ds->subdur_done = GF_TRUE; + u32 nb_sub_done=0; + for (j=0; jcurrent_period->streams, j); + if (a_ds->muxed_base) a_ds = a_ds->muxed_base; + if (a_ds->subdur_done) { + nb_sub_done++; + } + } + if (nb_sub_done==count) + ctx->subdur_done = GF_TRUE; + } else if (ctx->reschedule && !ctx->loop && (ctx->dmode==GF_MPD_TYPE_DYNAMIC) && !strcmp(ds->period_id, DEFAULT_PERIOD_ID) ) { + if (gf_list_find(ctx->next_period->streams, ds)<0) { + gf_list_add(ctx->next_period->streams, ds); + } + ctx->post_play_events = GF_TRUE; + ds->nb_repeat++; + ds->reschedule = GF_TRUE; + gf_filter_pid_discard_block(ds->opid); + } + } + break; + } + if (ds->seek_to_pck) { + u32 sn = gf_filter_pck_get_seq_num(pck); + if (sn) { + if (sn <= ds->seek_to_pck) { + dasher_drop_input(ctx, ds, GF_FALSE); + continue; + } + ds->nb_pck = sn-1; + } else { + //no sn signaled, this implies we played from the begining + if (ds->nb_pck < ds->seek_to_pck) { + ds->nb_pck ++; + dasher_drop_input(ctx, ds, GF_FALSE); + continue; + } + } + } + sap_type = gf_filter_pck_get_sap(pck); + ds->loop_state = 0; + + cts = gf_filter_pck_get_cts(pck); + dts = gf_filter_pck_get_dts(pck); + if (dts==GF_FILTER_NO_TS) dts = cts; + + if (!ds->rep_init) { + u32 set_start_with_sap; + const GF_PropertyValue *p; + if (!sap_type) { + dasher_drop_input(ctx, ds, GF_FALSE); + break; + } + p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_DELAY); + if (p) ds->pts_minus_cts = p->value.sint; + + set_start_with_sap = ctx->sseg ? base_ds->set->subsegment_starts_with_sap : base_ds->set->starts_with_sap; + if (!ds->muxed_base) { + //force sap type to 1 for non-visual streams if strict_sap is set to off + if ((ds->stream_type!=GF_STREAM_VISUAL) && (ctx->strict_sap==DASHER_SAP_OFF) ) { + sap_type = 1; + } + //set AS sap type + if (!set_start_with_sap) { + //don't set SAP type if not a base rep - could be further checked + //if (!gf_list_count(ds->complementary_streams) ) + { + if (ctx->sseg) { + ds->set->subsegment_starts_with_sap = sap_type; + } else { + ds->set->starts_with_sap = sap_type; + } + } + } + else if (set_start_with_sap != sap_type) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Segments do not start with the same SAP types: set initialized with %d but first packet got %d - bitstream will not be compliant\n", set_start_with_sap, sap_type)); + } + //TODO setup proper PTO, the code below will break sync by realigning first AU of each stream + if (cts > ds->pts_minus_cts) { + u64 pto = cts - ds->pts_minus_cts; + if (ds->rep->segment_list) + ds->rep->segment_list->presentation_time_offset = pto; + else if (ds->rep->segment_template) + ds->rep->segment_template->presentation_time_offset = pto; + else if (ds->set->segment_template) + ds->set->segment_template->presentation_time_offset = pto; + } + } + + ds->first_cts = cts; + ds->first_dts = dts; + ds->rep_init++; + has_init++; + } + nb_init++; + + if (ds->ts_offset) { + cts += ds->ts_offset; + dts += ds->ts_offset; + } + + //ready to write MPD for the first time in dynamic mode with template + if (has_init && (nb_init==count) && (ctx->dmode==GF_MPD_TYPE_DYNAMIC) && ctx->tpl && ctx->do_mpd) { + e = dasher_send_manifest(filter, ctx, GF_TRUE); + if (e) return e; + } + cts -= ds->first_cts; + dts -= ds->first_dts; + + if (ctx->sreg && ctx->mpd->gpac_mpd_time && (dts * 1000 > ctx->mpd->gpac_mpd_time * ds->timescale)) { + nb_reg_done++; + break; + } + + dur = o_dur = gf_filter_pck_get_duration(pck); + + split_dur = 0; + split_dur_next = 0; + + //patch to align old arch with new + check_dur = 0; + if (ds->stream_type==GF_STREAM_AUDIO) + check_dur = dur; + + //adjust duration and cts + orig_cts = cts; + if (ds->split_dur_next) { + cts += ds->split_dur_next; + assert(dur > ds->split_dur_next); + dur -= ds->split_dur_next; + split_dur_next = ds->split_dur_next; + ds->split_dur_next = 0; + is_packet_split = GF_TRUE; + } + + if (ds->splitable && !ds->split_dur_next) { + //adding this sampl would exceed the segment duration + if ( (cts + dur) * base_ds->timescale >= base_ds->adjusted_next_seg_start * ds->timescale ) { + //this sample starts in the current segment - split it + if (cts * base_ds->timescale < base_ds->adjusted_next_seg_start * ds->timescale ) { + split_dur = (u32) (base_ds->adjusted_next_seg_start * ds->timescale / base_ds->timescale - ds->last_cts); + //we should patch the above >= once we get rid of master, but changing it might break some hashes + if (split_dur==dur) split_dur=0; + } + } + } + + //mux rep, wait for a CTS more than our base if base not yet over + if ((base_ds != ds) && !base_ds->seg_done && (cts * base_ds->timescale > base_ds->last_cts * ds->timescale ) ) + break; + + if (ds->seek_to_pck) { + ds->seek_to_pck = 0; + } + //force flush mode, segment is done upon eos + else if (ctx->force_flush) { + } + //source-driven fragmentation check for segment start + else if (ctx->sigfrag) { + const GF_PropertyValue *p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FRAG_START); + if (p && (p->value.uint>=1) && base_ds->segment_started) { + seg_over = GF_TRUE; + if (ds == base_ds) { + base_ds->adjusted_next_seg_start = cts; + } + } + } + //cue base - FIXME, check what was wrong before we merged PR #1121 + else if (ds->cues) { + u32 cidx; + GF_DASHCueInfo *cue=NULL; + Bool is_cue_split = GF_FALSE; + s32 has_mismatch = -1; + + for (cidx=0;cidxnb_cues; cidx++) { + cue = &ds->cues[cidx]; + if (cue->sample_num) { + if (cue->sample_num == ds->nb_pck + 1) { + is_cue_split = GF_TRUE; + break; + } else if (cue->sample_num < ds->nb_pck) { + has_mismatch = cidx; + } else { + break; + } + } + else if (cue->dts) { + u64 ts = cue->dts * ds->timescale; + u64 ts2 = dts * ds->cues_timescale; + if (ts == ts2) { + is_cue_split = GF_TRUE; + break; + } else if (ts < ts2) { + has_mismatch = cidx; + } else { + break; + } + } + else if (cue->cts) { + s64 ts = cue->cts * ds->timescale; + s64 ts2 = (cts + ds->first_cts) * ds->cues_timescale; + + //cues are given in track timeline (presentation time), substract the media time to pres time offset + if (ds->cues_use_edits) { + ts2 += (s64) (ds->pts_minus_cts) * ds->cues_timescale; + } + if (ts == ts2) { + is_cue_split = GF_TRUE; + break; + } else if (ts < ts2) { + has_mismatch = cidx; + } else { + break; + } + } + } + //start of first segment + if (is_cue_split && !ds->segment_started) { + memmove(ds->cues, &ds->cues[cidx+1], (ds->nb_cues-cidx-1) * sizeof(GF_DASHCueInfo)); + ds->nb_cues -= cidx+1; + is_cue_split = 0; + } + + if (is_cue_split) { + if (!sap_type) { + GF_LOG(ctx->strict_cues ? GF_LOG_ERROR : GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] cue found (sn %d - dts "LLD" - cts "LLD") for PID %s but packet %d is not RAP !\n", cue->sample_num, cue->dts, cue->cts, gf_filter_pid_get_name(ds->ipid), ds->nb_pck)); + if (ctx->strict_cues) { + gf_filter_pid_drop_packet(ds->ipid); + gf_filter_pid_set_discard(ds->ipid, GF_TRUE); + return GF_BAD_PARAM; + } + } + memmove(ds->cues, &ds->cues[cidx+1], (ds->nb_cues-cidx-1) * sizeof(GF_DASHCueInfo)); + ds->nb_cues -= cidx+1; + + if (sap_type==3) + ds->nb_sap_3 ++; + else if (sap_type>3) + ds->nb_sap_4 ++; + + /*check requested profiles can be generated, or adjust them*/ + if ( + (ds->nb_sap_4 || (ds->nb_sap_3 > 1)) + && (ctx->profile != GF_DASH_PROFILE_FULL) + /*TODO: store at DS level whether the usage of sap4 is ok or not (eg roll info for AAC is OK, not for xHEAAC-v2) + for now we only complain for video*/ + && ((ds->stream_type==GF_STREAM_VISUAL) || (ctx->strict_sap==DASHER_SAP_ON) ) + ) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] WARNING! Max SAP type %d detected - switching to FULL profile\n", ds->nb_sap_4 ? 4 : 3)); + ctx->profile = GF_DASH_PROFILE_FULL; + if (ctx->sseg) + ds->set->subsegment_starts_with_sap = sap_type; + else + ds->set->starts_with_sap = sap_type; + } + + + seg_over = GF_TRUE; + if (ds == base_ds) { + base_ds->adjusted_next_seg_start = cts; + } + } + + if (has_mismatch>=0) { + cue = &ds->cues[has_mismatch]; + GF_LOG(ctx->strict_cues ? GF_LOG_ERROR : GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] found cue (sn %d - dts "LLD" - cts "LLD") in stream %s before current packet (sn %d - dts "LLD" - cts "LLD") , buggy source cues ?\n", cue->sample_num, cue->dts, cue->cts, gf_filter_pid_get_name(ds->ipid), ds->nb_pck+1, dts + ds->first_cts, cts + ds->first_cts)); + if (ctx->strict_cues) { + gf_filter_pid_drop_packet(ds->ipid); + gf_filter_pid_set_discard(ds->ipid, GF_TRUE); + return GF_BAD_PARAM; + } + } + } + //forcing max time + else if ((base_ds->force_rep_end && (cts * base_ds->timescale >= base_ds->force_rep_end * ds->timescale) ) + || (base_ds->clamped_dur && (cts + o_dur > ds->ts_offset + base_ds->clamped_dur * ds->timescale)) + ) { + if (!base_ds->period->period->duration && base_ds->force_rep_end) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Inputs duration do not match, %s truncated to %g duration\n", ds->src_url, ((Double)base_ds->force_rep_end)/base_ds->timescale )); + } + dasher_drop_input(ctx, ds, GF_TRUE); + ds->clamp_done = GF_TRUE; + continue; + } + //we have a SAP and we work in closest mode: check the next SAP in the queue, and decide if we + //split the segment at this SAP or wait for the next one + else if (ds->segment_started && ctx->sbound && sap_type && !is_queue_flush) { + u32 idx, nb_queued = gf_list_count(ds->packet_queue); + for (idx=1; idxpacket_queue, idx); + u32 sap_next = gf_filter_pck_get_sap(next); + if (!sap_next) continue; + u32 next_dur = gf_filter_pck_get_duration(next); + //compute cts next + u64 cts_next = gf_filter_pck_get_cts(next); + if (ds->ts_offset) { + cts_next += ds->ts_offset; + } + cts_next -= ds->first_cts; + //same rule as above + if ((cts_next + next_dur) * base_ds->timescale >= base_ds->adjusted_next_seg_start * ds->timescale ) { + Bool force_seg_flush = GF_FALSE; + s64 diff_next = cts_next * base_ds->timescale / ds->timescale; + diff_next -= base_ds->adjusted_next_seg_start; + //bounds at closest: if this SAP is closer to the target next segment start than the next SAP, split at this packet + if (ctx->sbound==DASHER_BOUNDS_CLOSEST) { + s64 diff = cts * base_ds->timescale / ds->timescale; + diff -= base_ds->adjusted_next_seg_start; + //this one may be negative, but we always want diff_next positive (next SAP in next segment) + if (diff<0) + diff = -diff; + //old arch was only using closest for tracks with sync points + if (gf_sys_old_arch_compat() && !base_ds->has_sync_points) { + if (diff_next > 0) { + force_seg_flush = GF_TRUE; + } + } + else if (diff 0) { + force_seg_flush = GF_TRUE; + } + } + if (force_seg_flush) { + seg_over = GF_TRUE; + if (ds == base_ds) { + base_ds->adjusted_next_seg_start = cts; + } + break; + } + } + } + } + //we exceed segment duration - if segment was started, check if we need to stop segment + //if segment was not started we insert the packet anyway + else if (!ctx->sbound && ds->segment_started && ((cts + check_dur) * base_ds->timescale >= base_ds->adjusted_next_seg_start * ds->timescale ) ) { + //no sap, segment is over + if (! ctx->sap) { + seg_over = GF_TRUE; + } + // sap, segment is over + else if (sap_type) { + + if (sap_type==3) + ds->nb_sap_3 ++; + else if (sap_type>3) + ds->nb_sap_4 ++; + + /*check requested profiles can be generated, or adjust them*/ + if ((ctx->profile != GF_DASH_PROFILE_FULL) + && (ds->nb_sap_4 || (ds->nb_sap_3 > 1)) + /*TODO: store at DS level whether the usage of sap4 is ok or not (eg roll info for AAC is OK, not for xHEAAC-v2) + for now we only complain for video*/ + && ((ds->stream_type==GF_STREAM_VISUAL) || (ctx->strict_sap==DASHER_SAP_ON) ) + ) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] WARNING! Max SAP type %d detected - switching to FULL profile\n", ds->nb_sap_4 ? 4 : 3)); + ctx->profile = GF_DASH_PROFILE_FULL; + if (ctx->sseg) + ds->set->subsegment_starts_with_sap = sap_type; + else + ds->set->starts_with_sap = sap_type; + } + + seg_over = GF_TRUE; + if (ds == base_ds) { + base_ds->adjusted_next_seg_start = cts; + } + } + } + + + if (ds->muxed_base && ds->muxed_base->done) { + seg_over = GF_FALSE; + } + //if flushing now will result in a one sample fragment afterwards + //because this is the before-last sample, don't flush unless: + //- we have an asto set (low latency) + //- this is not an audio stream or all samples are SAPs + else if (seg_over && ds->nb_samples_in_source && !ctx->loop + && (ds->nb_pck+1 == ds->nb_samples_in_source) + && !ctx->asto + && !(!ds->has_sync_points && (ds->stream_type!=GF_STREAM_AUDIO)) + ) { + seg_over = GF_FALSE; + } + //if dur=0 (some text streams), don't flush segment + if (seg_over && dur) { + assert(!ds->seg_done); + ds->seg_done = GF_TRUE; + ds->first_cts_in_next_seg = cts; + assert(base_ds->nb_comp_done < base_ds->nb_comp); + base_ds->nb_comp_done ++; + + if (split_dur_next) + ds->split_dur_next = (u32) split_dur_next; + + if (base_ds->nb_comp_done == base_ds->nb_comp) { + dasher_flush_segment(ctx, base_ds); + seg_done = GF_TRUE; + } + break; + } + + if (cts==GF_FILTER_NO_TS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] WARNING! Source packet has no timestamp !\n")); + + cts = ds->last_cts; + dts = ds->last_dts; + } else { + + ncts = cts + (split_dur ? split_dur : dur); + if (ncts>ds->est_first_cts_in_next_seg) + ds->est_first_cts_in_next_seg = ncts; + + ncts *= 1000; + ncts /= ds->timescale; + if (ncts>base_ds->max_period_dur) + base_ds->max_period_dur = ncts; + + ds->last_cts = cts + (split_dur ? split_dur : dur); + ds->last_dts = dts; + ds->est_next_dts = dts + o_dur; + } + ds->nb_pck ++; + + if (ctx->sigfrag) { + if (!ds->segment_started) { + ds->first_cts_in_seg = cts; + dasher_mark_segment_start(ctx, ds, NULL, pck); + ds->segment_started = GF_TRUE; + } + + ds->cumulated_dur += dur; + + //drop packet if not spliting + if (!ds->split_dur_next) + gf_filter_pid_drop_packet(ds->ipid); + + if (ctx->in_error) { + gf_filter_pid_set_discard(ds->ipid, GF_TRUE); + gf_filter_pid_set_eos(ctx->opid); + return GF_BAD_PARAM; + } + continue; + } + //create new ref to input + dst = gf_filter_pck_new_ref(ds->opid, NULL, 0, pck); + //merge all props + gf_filter_pck_merge_properties(pck, dst); + //we have ts offset, use computed cts and dts + if (ds->ts_offset) { + gf_filter_pck_set_cts(dst, cts + ds->first_cts); + gf_filter_pck_set_dts(dst, dts + ds->first_dts); + } + + if (gf_sys_old_arch_compat() && ds->clamped_dur && ctx->loop && (cts + 2*o_dur >= ds->ts_offset + base_ds->clamped_dur * ds->timescale) + ) { + u32 _dur = dur; + /* simple round with (int)+.5 to avoid trucating .99999 to 0 */ + dur = (u32)( ds->clamped_dur * ds->timescale - (dts - ds->ts_offset) + 0.5); + //it may happen that the sample duration is 0 if the clamp duration is right after the sample DTS and timescale is not big enough to express it - force to 1 + if (dur==0) + dur=1; + + gf_filter_pck_set_duration(dst, dur); + ds->est_next_dts += (s32) dur - (s32) _dur;; + } + + if (!ds->segment_started) { + ds->first_cts_in_seg = cts; + dasher_mark_segment_start(ctx, ds, dst, pck); + ds->segment_started = GF_TRUE; + } + //if split, adjust duration + if (split_dur) { + u32 cumulated_split_dur = split_dur; + gf_filter_pck_set_duration(dst, split_dur); + //adjust dur + cumulated_split_dur += (u32) (cts - orig_cts); + assert( dur > split_dur); + ds->split_dur_next = cumulated_split_dur; + dur = split_dur; + } + //prev packet was split + else if (is_packet_split) { + u64 diff=0; + u8 dep_flags = gf_filter_pck_get_dependency_flags(pck); + u64 ts = gf_filter_pck_get_cts(pck); + if (ts != GF_FILTER_NO_TS) { + cts += ds->first_cts; + assert(cts >= ts); + diff = cts - ts; + } else { + cts = ds->last_cts; + } + gf_filter_pck_set_cts(dst, cts + ds->ts_offset); + + ts = gf_filter_pck_get_dts(pck); + if (ts != GF_FILTER_NO_TS) + gf_filter_pck_set_dts(dst, ts + diff + ds->ts_offset); + + //add sample is redundant flag + dep_flags |= 0x1; + gf_filter_pck_set_dependency_flags(dst, dep_flags); + gf_filter_pck_set_duration(dst, dur); + } + + //remove NTP + if (ctx->ntp != DASHER_NTP_KEEP) + gf_filter_pck_set_property(dst, GF_PROP_PCK_SENDER_NTP, NULL); + + //change packet times + if (ds->force_timescale) { + u64 ats; + ats = gf_filter_pck_get_dts(dst); + if (ats!=GF_FILTER_NO_TS) { + ats *= ds->force_timescale; + ats /= ds->timescale; + gf_filter_pck_set_dts(dst, ats); + } + ats = gf_filter_pck_get_cts(dst); + if (ats!=GF_FILTER_NO_TS) { + ats *= ds->force_timescale; + ats /= ds->timescale; + gf_filter_pck_set_cts(dst, ats); + } + ats = (u64) gf_filter_pck_get_duration(dst); + if (ats) { + ats *= ds->force_timescale; + ats /= ds->timescale; + gf_filter_pck_set_duration(dst, (u32) ats); + } + } + + ds->cumulated_dur += dur; + + //TODO check drift between MPD start time and min CTS in segment (not just first CTS in segment) + + //send packet + gf_filter_pck_send(dst); + + if (ctx->update_report>=0) + ctx->update_report++; + + //drop packet if not spliting + if (!ds->split_dur_next) + dasher_drop_input(ctx, ds, GF_FALSE); + } + } + nb_init = 0; + for (i=0; icurrent_period->streams, i); + //if (ds->muxed_base) ds = ds->muxed_base; + if (ds->done || ds->subdur_done) nb_init++; + else if (ds->seg_done && ctx->force_period_switch) nb_init++; + else if (ds->seg_done && ds->muxed_base && ds->muxed_base->done) { + nb_init++; + ds->done = 1; + } + } + + if (nb_reg_done && (nb_reg_done == count)) { + ctx->mpd->gpac_mpd_time = 0; + } + + dasher_format_report(filter, ctx); + + if (seg_done) { + Bool update_period = GF_FALSE; + Bool update_manifest = GF_FALSE; + if (ctx->purge_segments) update_period = GF_TRUE; + if (ctx->mpd) { + //segment timeline used, always update manifest + if (ctx->stl) + update_manifest = GF_TRUE; + else if (ctx->dmode==GF_DASH_DYNAMIC) { + //publish time not set, we never send the manifest, do it + if (!ctx->mpd->publishTime) { + update_manifest = GF_TRUE; + } + //we have a minimum ipdate period + else if (ctx->mpd->minimum_update_period) { + u64 diff = dasher_get_utc(ctx) - ctx->mpd->publishTime; + if (diff >= ctx->mpd->minimum_update_period) + update_manifest = GF_TRUE; + } + } + if (update_period) + dasher_update_period_duration(ctx, GF_FALSE); + + if (update_manifest) + dasher_send_manifest(filter, ctx, GF_FALSE); + } + + } + + //still some running streams in period + if (count && (nb_initsubdur) { + for (i=0; icurrent_period->streams, i); + if (ds->done) continue; + ds->done = 2; + ds->subdur_done = GF_TRUE; + eods_pck = gf_filter_pck_new_alloc(ds->opid, 0, NULL); + gf_filter_pck_set_property(eods_pck, GF_PROP_PCK_EODS, &PROP_BOOL(GF_TRUE) ); + gf_filter_pck_send(eods_pck); + + dasher_drop_input(ctx, ds, GF_TRUE); + } + } + + //we need to wait for full flush of packets before switching periods in order to get the + //proper segment size for segment_list+byte_range mode + if (ctx->nb_seg_url_pending) { + u64 diff; + if (!ctx->last_evt_check_time) ctx->last_evt_check_time = gf_sys_clock_high_res(); + + diff = gf_sys_clock_high_res() - ctx->last_evt_check_time; + if (diff < 1000000) { + gf_filter_post_process_task(filter); + return GF_OK; + } + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] timeout %d segment info still pending but no event from muxer after "LLU" us, aborting\n", ctx->nb_seg_url_pending, diff)); + ctx->nb_seg_url_pending = 0; + return GF_SERVICE_ERROR; + } + if (ctx->sseg && !ctx->on_demand_done && !ctx->sigfrag) { + return GF_OK; + } + ctx->force_period_switch = GF_FALSE; + //done with this period, do period switch - this will update the MPD if needed + e = dasher_switch_period(filter, ctx); + //no more periods + if (e==GF_EOS) { + if (!ctx->is_eos) { + ctx->is_eos = GF_TRUE; + gf_filter_pid_set_eos(ctx->opid); + } + } + return e; +} + + + +static void dasher_resume_subdur(GF_Filter *filter, GF_DasherCtx *ctx) +{ + GF_FilterEvent evt; + u32 i, count; + Bool is_last = (ctx->dmode == GF_MPD_TYPE_DYNAMIC_LAST) ? GF_TRUE : GF_FALSE; + if (!ctx->state) return; + + count = gf_list_count(ctx->pids); + for (i=0; ipids, i); + ds->rep = NULL; + if ((ds->done==1) && !ctx->subdur && ctx->loop) {} + else if (ds->reschedule) { + //we possibly dispatched end of stream on all outputs, we need to force unblockink to get called again + gf_filter_pid_discard_block(ds->opid); + continue; + } + else if (ds->done != 2) continue; + + if (is_last) continue; + + gf_filter_pid_set_discard(ds->ipid, GF_FALSE); + + //send stop and play + GF_FEVT_INIT(evt, GF_FEVT_STOP, ds->ipid); + gf_filter_pid_send_event(ds->ipid, &evt); + + GF_FEVT_INIT(evt, GF_FEVT_PLAY, ds->ipid); + evt.play.speed = 1.0; + if (!ctx->subdur || !ctx->loop) { + ds->seek_to_pck = 0; + } else { + //request start after the last packet we processed + evt.play.from_pck = (u32) ds->seek_to_pck+1; + } + gf_filter_pid_send_event(ds->ipid, &evt); + + //full stream looping + if (ds->subdur_done && !ctx->subdur) { + ds->loop_state = 0; + //mark as subdur done to force a context reload through period switch + ds->done = 2; + ds->seg_done = GF_FALSE; + ds->subdur_done = GF_FALSE; + } + } + + ctx->subdur_done = GF_FALSE; + ctx->is_eos = GF_FALSE; + if (!ctx->post_play_events && !is_last) { + ctx->current_period->period = NULL; + ctx->first_context_load = GF_TRUE; + ctx->post_play_events = GF_TRUE; + } + gf_filter_post_process_task(filter); +} + + +static Bool dasher_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i, count; + Bool flush_mpd = GF_FALSE; + GF_DasherCtx *ctx = gf_filter_get_udta(filter); + + ctx->last_evt_check_time = 0; + + if (evt->base.type == GF_FEVT_RESUME) { + //only process resume event when coming from main output PID, but always cancel it + //this is needed in case the output filter where the resume event was initiated consumes both + //manifest and segment PIDs, as is the case with httpout + if (evt->base.on_pid == ctx->opid) + dasher_resume_subdur(filter, ctx); + return GF_TRUE; + } + if (evt->base.type == GF_FEVT_CONNECT_FAIL) { + ctx->in_error = GF_TRUE; + gf_filter_pid_set_eos(ctx->opid); + if (ctx->opid_alt) + gf_filter_pid_set_eos(ctx->opid_alt); + return GF_TRUE; + } + + if (evt->base.type == GF_FEVT_PLAY) { + ctx->is_playing = GF_TRUE; + return GF_FALSE; + } + if (evt->base.type == GF_FEVT_STOP) { + ctx->is_playing = GF_FALSE; + return GF_FALSE; + } + + if (evt->base.type != GF_FEVT_SEGMENT_SIZE) return GF_FALSE; + + count = gf_list_count(ctx->pids); + for (i=0; ipids, i); + if (ds->opid != evt->base.on_pid) continue; + + if (ds->muxed_base) + ds = ds->muxed_base; + + if (ctx->store_seg_states && !evt->seg_size.is_init) { + GF_DASH_SegmentContext *sctx = gf_list_pop_front(ds->pending_segment_states); + if (!sctx || !ctx->nb_seg_url_pending) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] Received segment size info event but no pending segments\n")); + return GF_TRUE; + } + assert(sctx); + assert(ctx->nb_seg_url_pending); + ctx->nb_seg_url_pending--; + sctx->file_size = 1 + (u32) (evt->seg_size.media_range_end - evt->seg_size.media_range_start); + sctx->file_offset = evt->seg_size.media_range_start; + sctx->index_size = 1 + (u32) (evt->seg_size.idx_range_end - evt->seg_size.idx_range_start); + sctx->index_offset = evt->seg_size.idx_range_start; + } + + //in state mode we store everything + //don't set segment sizes in template mode + if (ctx->tpl) continue; + //only set size/index size for init segment when doing onDemand/single index + if (ctx->sseg && !evt->seg_size.is_init) continue; + + if (evt->seg_size.media_range_end) { + r_start = evt->seg_size.media_range_start; + r_end = evt->seg_size.media_range_end; + } else { + r_start = evt->seg_size.idx_range_start; + r_end = evt->seg_size.idx_range_end; + } + //init segment or representation index, set it in on demand and main single source + if ((ctx->sfile || ctx->sseg) && (evt->seg_size.is_init==1)) { + GF_MPD_URL *url, **s_url; + + if (ds->rep->segment_list) { + if (!evt->seg_size.media_range_start && !evt->seg_size.media_range_end) { + if (ds->rep->segment_list->initialization_segment) { + gf_mpd_url_free(ds->rep->segment_list->initialization_segment); + ds->rep->segment_list->initialization_segment = NULL; + } + continue; + } + } + + if (ds->rep->segment_base && !evt->seg_size.media_range_end) { + if (! ds->rep->segment_base->index_range) { + GF_SAFEALLOC(ds->rep->segment_base->index_range, GF_MPD_ByteRange); + } + if (ds->rep->segment_base->index_range) { + ds->rep->segment_base->index_range->start_range = r_start; + ds->rep->segment_base->index_range->end_range = r_end; + ds->rep->segment_base->index_range_exact = GF_TRUE; + } + flush_mpd = GF_TRUE; + continue; + } + + GF_SAFEALLOC(url, GF_MPD_URL); + if (!url) return GF_TRUE; + + GF_SAFEALLOC(url->byte_range, GF_MPD_ByteRange); + if (!url->byte_range) return GF_TRUE; + url->byte_range->start_range = r_start; + url->byte_range->end_range = r_end; + + s_url = NULL; + if (ds->rep->segment_base) { + if (evt->seg_size.media_range_end) s_url = &ds->rep->segment_base->initialization_segment; + } else { + assert(ds->rep->segment_list); + if (evt->seg_size.media_range_end) s_url = &ds->rep->segment_list->initialization_segment; + else s_url = &ds->rep->segment_list->representation_index; + } + assert(s_url); + if (*s_url) gf_mpd_url_free(*s_url); + *s_url = url; + } else if (ds->rep->segment_list && !evt->seg_size.is_init) { + GF_MPD_SegmentURL *url = gf_list_pop_front(ds->pending_segment_urls); + assert(url); + assert(ctx->nb_seg_url_pending); + ctx->nb_seg_url_pending--; + + if (!url->media && ctx->sfile) { + GF_SAFEALLOC(url->media_range, GF_MPD_ByteRange); + if (url->media_range) { + url->media_range->start_range = evt->seg_size.media_range_start; + url->media_range->end_range = evt->seg_size.media_range_end; + } + } + //patch in test mode, old arch was not generating the index size for segment lists + if (evt->seg_size.idx_range_end && (!gf_sys_old_arch_compat() || ctx->sfile) ) { + GF_SAFEALLOC(url->index_range, GF_MPD_ByteRange); + if (url->index_range) { + url->index_range->start_range = evt->seg_size.idx_range_start; + url->index_range->end_range = evt->seg_size.idx_range_end; + } + } + } + } + if (!ctx->sseg || !flush_mpd) return GF_TRUE; + + flush_mpd = GF_TRUE; + for (i=0; ipids, i); + if (!ds->rep) continue; + if (! ds->rep->segment_base) continue; + if (ds->rep->segment_base->index_range) continue; + flush_mpd = GF_FALSE; + break; + } + if (flush_mpd) { + ctx->on_demand_done = GF_TRUE; + gf_filter_post_process_task(filter); + } + return GF_TRUE; +} + +static GF_Err dasher_setup_profile(GF_DasherCtx *ctx) +{ + switch (ctx->profile) { + case GF_DASH_PROFILE_AVC264_LIVE: + case GF_DASH_PROFILE_AVC264_ONDEMAND: + if (ctx->cp == GF_DASH_CPMODE_REPRESENTATION) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] ERROR! The selected DASH profile (DASH-IF IOP) requires the ContentProtection element to be present in the AdaptationSet element, updating.\n")); + ctx->cp = GF_DASH_CPMODE_ADAPTATION_SET; + } + default: + break; + } + if (ctx->m2ts) { + switch (ctx->profile) { + case GF_DASH_PROFILE_HBBTV_1_5_ISOBMF_LIVE: + case GF_DASH_PROFILE_AVC264_LIVE: + ctx->profile = GF_DASH_PROFILE_LIVE; + break; + case GF_DASH_PROFILE_ONDEMAND: + case GF_DASH_PROFILE_AVC264_ONDEMAND: + ctx->profile = GF_DASH_PROFILE_ONDEMAND; + break; + } + } + + /*adjust params based on profiles*/ + switch (ctx->profile) { + case GF_DASH_PROFILE_LIVE: + ctx->sseg = ctx->sfile = GF_FALSE; + ctx->tpl = ctx->align = ctx->sap = GF_TRUE; + break; + case GF_DASH_PROFILE_HBBTV_1_5_ISOBMF_LIVE: + ctx->check_main_role = GF_TRUE; + ctx->bs_switch = DASHER_BS_SWITCH_MULTI; + //FALLTHROUGH + case GF_DASH_PROFILE_AVC264_LIVE: + ctx->sseg = ctx->sfile = GF_FALSE; + ctx->no_fragments_defaults = ctx->align = ctx->tpl = ctx->sap = GF_TRUE; + break; + case GF_DASH_PROFILE_AVC264_ONDEMAND: + ctx->tpl = GF_FALSE; + ctx->no_fragments_defaults = ctx->align = ctx->sseg = ctx->sap = GF_TRUE; + break; + case GF_DASH_PROFILE_ONDEMAND: + ctx->sseg = ctx->align = ctx->sap = ctx->sfile = GF_TRUE; + ctx->tpl = GF_FALSE; + + if (ctx->m2ts) { + ctx->sseg = GF_FALSE; + ctx->tpl = GF_TRUE; + ctx->profile = GF_DASH_PROFILE_MAIN; + } else { + if ((ctx->bs_switch != DASHER_BS_SWITCH_DEF) && (ctx->bs_switch != DASHER_BS_SWITCH_OFF)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] onDemand profile, bitstream switching mode cannot be used, defaulting to off.\n")); + } + } + /*BS switching is meaningless in onDemand profile*/ + ctx->bs_switch = DASHER_BS_SWITCH_OFF; + break; + case GF_DASH_PROFILE_MAIN: + ctx->align = ctx->sap = GF_TRUE; + ctx->sseg = ctx->tpl = GF_FALSE; + break; + default: + break; + } + + if (ctx->sseg) + ctx->tpl = GF_FALSE; + + //commented out, not sure why we had inband by default in live + if (ctx->bs_switch == DASHER_BS_SWITCH_DEF) { +#if 0 + ctx->bs_switch = DASHER_BS_SWITCH_INBAND; +#else + ctx->bs_switch = DASHER_BS_SWITCH_ON; +#endif + + } + + if (! ctx->align) { + if (ctx->profile != GF_DASH_PROFILE_FULL) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Segments are not time-aligned in each representation of each period\n\tswitching to FULL profile\n")); + ctx->profile = GF_DASH_PROFILE_FULL; + } + //commented out, this does not seem correct since BS switching is orthogonal to segment alignment + //one could have inband params working even in non time-aligned setup +#if 0 + if (ctx->bs_switch != DASHER_BS_SWITCH_OFF) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Segments are not time-aligned in each representation of each period\n\tdisabling bitstream switching\n")); + ctx->bs_switch = DASHER_BS_SWITCH_OFF; + } +#endif + + } + + //check we have a segment template + if (!ctx->template) { + if (!ctx->sigfrag) { + ctx->template = gf_strdup( ctx->sfile ? "$File$$FS$_dash" : (ctx->stl ? "$File$_dash$FS$$Time$" : "$File$_dash$FS$$Number$") ); + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[Dasher] No template assigned, using %s\n", ctx->template)); + } + + if (ctx->profile == GF_DASH_PROFILE_FULL) { + ctx->sfile = GF_TRUE; + } + } + //backward compatibility with old arch using %s + else { + char *sep = strstr(ctx->template, "%s"); + if (sep) { + char *new_template = NULL; + sep[0] = 0; + gf_dynstrcat(&new_template, ctx->template, NULL); + gf_dynstrcat(&new_template, "$File$", NULL); + gf_dynstrcat(&new_template, sep+2, NULL); + gf_free(ctx->template); + ctx->template = new_template; + } + + } + return GF_OK; +} + +static GF_Err dasher_initialize(GF_Filter *filter) +{ + GF_Err e; + GF_DasherCtx *ctx = gf_filter_get_udta(filter); + gf_filter_set_max_extra_input_pids(filter, -1); + + ctx->pids = gf_list_new(); + ctx->postponed_pids = gf_list_new(); + + if (!ctx->initext && (ctx->muxtype==DASHER_MUX_AUTO)) + ctx->muxtype = DASHER_MUX_ISOM; + + if (ctx->segdur<=0) { + ctx->segdur = 1.0; + ctx->no_seg_dur = GF_TRUE; + } + + e = dasher_setup_profile(ctx); + if (e) return e; + + if (ctx->sfile && ctx->tpl) + ctx->tpl = GF_FALSE; + + ctx->current_period = dasher_new_period(); + ctx->next_period = dasher_new_period(); + ctx->on_demand_done = GF_TRUE; + + if (ctx->state) { + ctx->first_context_load = GF_TRUE; + } + if (ctx->subdur && !ctx->state) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] subdur mode specified but no context set, will only dash %g seconds of media\n", ctx->subdur)); + } + //we build manifest from input frag/seg, always use single frag + if (ctx->sigfrag) { + if (ctx->tpl) { + if (!ctx->template) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Warning, manifest generation only mode requested for live-based profile but no template provided, switching to main profile.\n")); + ctx->profile = GF_DASH_PROFILE_MAIN; + ctx->tpl = GF_FALSE; + dasher_setup_profile(ctx); + //we force single file in this mode, but we will replace byte ranges by source URL + ctx->sfile = GF_TRUE; + } else { + ctx->sseg = GF_FALSE; + ctx->sfile = GF_FALSE; + } + } else { + if (!ctx->sseg) + ctx->sfile = GF_TRUE; + } + } + + if (!ctx->sap || ctx->sigfrag || ctx->cues) + ctx->sbound = DASHER_BOUNDS_OUT; + + + if ((ctx->tsb>=0) && (ctx->dmode!=GF_DASH_STATIC)) + ctx->purge_segments = GF_TRUE; + + if (ctx->state && ctx->sreg) { + u32 diff; + u64 next_gen_ntp; + GF_Err dash_state_check_timing(const char *dash_state, u64 *next_gen_ntp_ms, u32 *next_time_ms); + + e = dash_state_check_timing(ctx->state, &next_gen_ntp, &diff); + if (e<0) return e; + if (e==GF_EOS) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] generation called too early by %d ms\n", (s32) diff)); + return e; + } + } + return GF_OK; +} + + +static void dasher_finalize(GF_Filter *filter) +{ + GF_DasherCtx *ctx = gf_filter_get_udta(filter); + + while (gf_list_count(ctx->pids)) { + GF_DashStream *ds = gf_list_pop_back(ctx->pids); + dasher_reset_stream(filter, ds, GF_TRUE); + if (ds->packet_queue) gf_list_del(ds->packet_queue); + gf_free(ds); + } + gf_list_del(ctx->pids); + if (ctx->mpd) gf_mpd_del(ctx->mpd); + + if (ctx->next_period->period) gf_mpd_period_free(ctx->next_period->period); + gf_list_del(ctx->current_period->streams); + gf_free(ctx->current_period); + gf_list_del(ctx->next_period->streams); + gf_free(ctx->next_period); + if (ctx->out_path) gf_free(ctx->out_path); + gf_list_del(ctx->postponed_pids); +} + +#define MPD_EXTS "mpd|m3u8|3gm|ism" +#define MPD_MIMES "application/dash+xml|video/vnd.3gpp.mpd|audio/vnd.3gpp.mpd|video/vnd.mpeg.dash.mpd|audio/vnd.mpeg.dash.mpd|audio/mpegurl|video/mpegurl|application/vnd.ms-sstr+xml" + +static const GF_FilterCapability DasherCaps[] = +{ + //we accept files as input, but only for NULL file (no source) + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + //only with no source + CAP_STRING(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_URL, "*"), + CAP_STRING(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_FILEPATH, "*"), + + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, MPD_EXTS), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, MPD_MIMES), + {0}, + //anything AV pid framed result in manifest PID + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, MPD_EXTS), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, MPD_MIMES), + {0}, + //anything else (not file, not AV and framed) in compressed format result in manifest PID + //we cannot handle RAW format for such streams as these are in-memory data (scene graph, decoded text, etc ..) + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, MPD_EXTS), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, MPD_MIMES), + {0}, + //anything else (not file and framed) result in media pids not file + CAP_UINT(GF_CAPS_INPUT_EXCLUDED | GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED | GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_CODECID, GF_CODECID_NONE), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED | GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED | GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + +}; + + +#define OFFS(_n) #_n, offsetof(GF_DasherCtx, _n) +static const GF_FilterArgs DasherArgs[] = +{ + { OFFS(segdur), "target segment duration in seconds. A value less than or equal to 0 means to 1.0 second", GF_PROP_DOUBLE, "0.0", NULL, 0}, + { OFFS(tpl), "use template mode (multiple segment, template URLs)", GF_PROP_BOOL, "true", NULL, 0}, + { OFFS(stl), "use segment timeline (ignored in on_demand mode)", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(dmode), "dash content mode\n" + "- static: static content\n" + "- dynamic: live generation\n" + "- dynlast: last call for live, will turn the MPD into static" + "", GF_PROP_UINT, "static", "static|dynamic|dynlast", GF_FS_ARG_UPDATE}, + { OFFS(sseg), "single segment is used", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(sfile), "use a single file for all segments (default in on_demand)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(align), "enable segment time alignment between representations", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(sap), "enable spliting segments at SAP boundaries", GF_PROP_BOOL, "true", NULL, 0}, + { OFFS(mix_codecs), "enable mixing different codecs in an adaptation set", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(ntp), "insert/override NTP clock at the begining of each segment\n" + "- rem: removes NTP from all input packets\n" + "- yes: inserts NTP at each segment start\n" + "- keep: leaves input packet NTP untouched", GF_PROP_UINT, "rem", "rem|yes|keep", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(no_sar), "do not check for identical sample aspect ratio for adaptation sets", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(m2ts), "generate MPEG-2 TS output", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(bs_switch), "bitstream switching mode (single init segment)\n" + "- def: resolves to off for onDemand and inband for live\n" + "- off: disables BS switching\n" + "- on: enables it if same decoder configuration is possible\n" + "- inband: moves decoder config inband if possible\n" + "- force: enables it even if only one representation\n" + "- multi: uses multiple stsd entries in ISOBMFF", GF_PROP_UINT, "def", "def|off|on|inband|force|multi", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(template), "template string to use to generate segment name - see filter help", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(segext), "file extension to use for segments", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(initext), "file extension to use for the init segment", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(muxtype), "muxtype to use for the segments\n" + "- mp4: uses ISOBMFF format\n" + "- ts: uses MPEG-2 TS format\n" + "- mkv: uses Matroska format\n" + "- webm: uses WebM format\n" + "- raw: uses raw media format (disables muxed representations)\n" + "- auto: guess format based on extension, default to mp4 if no extension", GF_PROP_UINT, "auto", "mp4|ts|mkv|webm|ogg|raw|auto", 0}, + { OFFS(asto), "availabilityStartTimeOffset to use in seconds. A negative value simply increases the AST, a positive value sets the ASToffset to representations", GF_PROP_DOUBLE, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(profile), "target DASH profile. This will set default option values to ensure conformance to the desired profile. For MPEG-2 TS, only main and live are used, others default to main\n" + "- auto: turns profile to live for dynamic and full for non-dynamic\n" + "- live: DASH live profile, using segment template\n" + "- onDemand: MPEG-DASH live profile\n" + "- main: MPEG-DASH main profile, using segment list\n" + "- full: MPEG-DASH full profile\n" + "- hbbtv1.5.live: HBBTV 1.5 DASH profile\n" + "- dashavc264.live: DASH-IF live profile\n" + "- dashavc264.onDemand: DASH-IF onDemand profile" + "", GF_PROP_UINT, "auto", "auto|live|onDemand|main|full|hbbtv1.5.live|dashavc264.live|dashavc264.onDemand", 0 }, + { OFFS(profX), "list of profile extensions, as used by DASH-IF and DVB. The string will be colon-concatenated with the profile used", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED }, + { OFFS(cp), "content protection element location\n" + "- set: in adaptation set element\n" + "- rep: in representation element\n" + "- both: in both adaptation set and representation elements" + "", GF_PROP_UINT, "set", "set|rep|both", GF_FS_ARG_HINT_ADVANCED }, + { OFFS(pssh), "storage mode for PSSH box\n" + "- f: stores in movie fragment only\n" + "- v: stores in movie only\n" + "- m: stores in mpd only\n" + "- mf: stores in mpd and movie fragment\n" + "- mv: stores in mpd and movie\n" + "- n: discard pssh from mpd and segments", GF_PROP_UINT, "v", "v|f|mv|mf|m|n", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(buf), "min buffer duration in ms. negative value means percent of segment duration (eg -150 = 1.5*seg_dur)", GF_PROP_SINT, "-100", NULL, 0}, + { OFFS(timescale), "set timescale for timeline and segment list/template. A value of 0 picks up the first timescale of the first stream in an adaptation set. A negative value forces using stream timescales for each timed element (multiplication of segment list/template/timelines). A positive value enforces the MPD timescale", GF_PROP_SINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(check_dur), "check duration of sources in period, trying to have roughly equal duration. Enforced whenever period start times are used", GF_PROP_BOOL, "true", NULL, 0}, + { OFFS(skip_seg), "increment segment number whenever an empty segment would be produced - NOT DASH COMPLIANT", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(title), "MPD title", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(source), "MPD Source", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(info), "MPD info url", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(cprt), "MPD copyright string", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(lang), "language of MPD Info", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(location), "set MPD locations to given URL", GF_PROP_STRING_LIST, NULL, NULL, 0}, + { OFFS(base), "set base URLs of MPD", GF_PROP_STRING_LIST, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(refresh), "refresh rate for dynamic manifests, in seconds. A negative value sets the MPD duration. If 0, uses dash duration", GF_PROP_DOUBLE, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(tsb), "time-shift buffer depth in seconds. A negative value means infinity", GF_PROP_DOUBLE, "30", NULL, 0}, + { OFFS(subdur), "maximum duration of the input file to be segmented. This does not change the segment duration, segmentation stops once segments produced exceeded the duration", GF_PROP_DOUBLE, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ast), "set start date (as xs:date, eg YYYY-MM-DDTHH:MM:SSZ) for live mode. Default is now. !! Do not use with multiple periods, nor when DASH duration is not a multiple of GOP size !!", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(state), "path to file used to store/reload state info when simulating live. This is stored as a valid MPD with GPAC XML extensions", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(loop), "loop sources when dashing with subdur and state. If not set, a new period is created once the sources are over", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(split), "enable cloning samples for text/metadata/scene description streams, marking further clones as redundant", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(hlsc), "insert clock reference in variant playlist in live HLS", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(cues), "set cue file - see filter help", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(strict_cues), "strict mode for cues, complains if spliting is not on SAP type 1/2/3 or if unused cue is found", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(strict_sap), "strict mode for sap\n" + "- off: ignore SAP types for PID other than video, enforcing _startsWithSAP=1_\n" + "- sig: same as [-off]() but keep _startsWithSAP_ to the true SAP value\n" + "- on: warn if any PID uses SAP 3 or 4 and switch to FULL profile", GF_PROP_UINT, "off", "off|sig|on", GF_FS_ARG_HINT_EXPERT}, + + { OFFS(subs_sidx), "number of subsegments per sidx. negative value disables sidx. Only used to inherit sidx option of destination", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(cmpd), "skip line feed and spaces in MPD XML for more compacity", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(styp), "indicate the 4CC to use for styp boxes when using ISOBMFF output", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(dual), "indicate to produce both MPD and M3U files", GF_PROP_BOOL, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(sigfrag), "use manifest generation only mode - see filter help", GF_PROP_BOOL, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(_p_gentime), "pointer to u64 holding the ntp clock in ms of next DASH generation in live mode", GF_PROP_POINTER, NULL, NULL, GF_FS_ARG_HINT_HIDE}, + { OFFS(_p_mpdtime), "pointer to u64 holding the mpd time in ms of the last generated segment", GF_PROP_POINTER, NULL, NULL, GF_FS_ARG_HINT_HIDE}, + { OFFS(sbound), "indicate how the theoretical segment start `TSS (= segment_number * duration)` should be handled\n" + "- out: segment split as soon as `TSS` is exceeded (`TSS` <= segment_start)\n" + "- closest: segment split at closest SAP to theoretical bound\n" + "- in: `TSS` is always in segment (`TSS` >= segment_start)", GF_PROP_UINT, "out", "out|closest|in", GF_FS_ARG_HINT_EXPERT}, + { OFFS(reschedule), "reschedule sources with no period ID assigned once done (dynamic mode only)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(sreg), "regulate the session\n" + "- when using subdur and context, only generate segments from the past up to live edge\n" + "- otherwise in dynamic mode without context, do not generate segments ahead of time", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(scope_deps), "scope PID dependencies to be within source. If disabled, PID dependencies will be checked across all input PIDs regardless of their sources", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(utcs), "URL to use as time server / UTCTiming source. Special value `inband` enables inband UTC (same as publishTime), special prefix `xsd@` uses xsDateTime schemeURI rather than ISO", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(force_flush), "force generating a single segment for each input. This can be usefull in batch mode when average source duration is known and used as segment duration but actual duration may sometimes be greater", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + {0} +}; + + +GF_FilterRegister DasherRegister = { + .name = "dasher", + GF_FS_SET_DESCRIPTION("DASH and HLS segmenter") + GF_FS_SET_HELP("# GPAC DASH and HLS segmenter\n" + "This filter provides segmentation and manifest generation for MPEG-DASH and HLS formats.\n" + "The segmenter currently supports:\n" + "- MPD and m3u8 generation (potentially in parallel)\n" + "- ISOBMFF, MPEG-2 TS, MKV and raw bitstream segment formats\n" + "- override of profiles and levels in manifest for codecs\n" + "- most MPEG-DASH profiles\n" + "- static and dynamic (live) manifest offering\n" + "- context store and reload for batch processing of live/dynamic sessions\n" + "\n" + "The filter does perform per-segment real-time regulation using [-sreg]().\n" + "If you need per-frame real-time regulation on non-real-time inputs, insert a [reframer](reframer) before to perform real-time regulation.\n" + "EX src=file.mp4 reframer:rt=on @ -o live.mpd:dmode=dynamic\n" + "## Template strings\n" + "The segmenter uses templates to derive output file names, regardless of the DASH mode (even when templates are not used). " + "The default one is `$File$_dash` for ondemand and single file modes, and `$File$_$Number$` for separate segment files\n" + "EX template=Great_$File$_$Width$_$Number$\n" + "If input is foo.mp4 with 640x360 video, this will resolve in Great_foo_640_$Number$ for the DASH template.\n" + "EX template=Great_$File$_$Width$\n" + "If input is foo.mp4 with 640x360 video, this will resolve in Great_foo_640.mp4 for onDemand case.\n" + "\n" + "Standard DASH replacement strings: \n" + "- $Number[%%0Nd]$: replaced by the segment number, possibly prefixed with 0\n" + "- $RepresentationID$: replaced by representation name\n" + "- $Time$: replaced by segment start time\n" + "- $Bandwidth$: replaced by representation bandwidth.\n" + "Note: these strings are not replaced in the manifest templates elements.\n" + "\n" + "Additional replacement strings (not DASH, not generic GPAC replacements but may occur multiple times in template):\n" + "- $Init=NAME$: replaced by NAME for init segment, ignored otherwise\n" + "- $XInit=NAME$: complete replace by NAME for init segment, ignored otherwise\n" + "- $Index=NAME$: replaced by NAME for index segments, ignored otherwise\n" + "- $Path=PATH$: replaced by PATH when creating segments, ignored otherwise\n" + "- $Segment=NAME$: replaced by NAME for media segments, ignored for init segments\n" + "- $FS$ (FileSuffix): replaced by `_trackN` in case the input is an AV multiplex, or kept empty otherwise\n" + "Note: these strings are replaced in the manifest templates elements.\n" + "\n" + "## PID assignment and configuration\n" + "To assign PIDs into periods and adaptation sets and configure the session, the segmenter looks for the following properties on each input pid:\n" + "- Representation: assigns representation ID to input pid. If not set, the default behaviour is to have each media component in different adaptation sets. Setting the RepresentationID allows explicit multiplexing of the source(s)\n" + "- Period: assigns period ID to input pid. If not set, the default behaviour is to have all media in the same period with the same start time\n" + "- PStart: assigns period start. If not set, 0 is assumed, and periods appear in the Period ID declaration order. If negative, this gives the period order (-1 first, then -2 ...). If positive, this gives the true start time and will abort DASHing at period end\n" + "Note: When both positive and negative values are found, the by-order periods (negative) will be inserted AFTER the timed period (positive)\n" + "- ASID: assigns parent adaptation set ID. If not 0, only sources with same AS ID will be in the same adaptation set\n" + "Note: If multiple streams in source, only the first stream will have an AS ID assigned\n" + "- xlink: for remote periods, only checked for null pid\n" + "- Role, PDesc, ASDesc, ASCDesc, RDesc: various descriptors to set for period, AS or representation\n" + "- BUrl: overrides segmenter [-base] with a set of BaseURLs to use for the pid (per representation)\n" + "- Template: overrides segmenter [-template]() for this PID\n" + "- DashDur: overrides segmenter segment duration for this PID\n" + "- StartNumber: sets the start number for the first segment in the PID, default is 1\n" + "- Non-dash properties: Bitrate, SAR, Language, Width, Height, SampleRate, NumChannels, Language, ID, DependencyID, FPS, Interlaced, Codec. These properties are used to setup each representation and can be overridden on input PIDs using the general PID property settings (cf global help).\n" + " \n" + "EX src=test.mp4:#Bitrate=1M dst=test.mpd\n" + "This will force declaring a bitrate of 1M for the representation, regardless of actual input bitrate.\n" + "EX src=muxav.mp4 dst=test.mpd\n" + "This will create unmuxed DASH segments.\n" + "EX src=muxav.mp4:#Representation=1 dst=test.mpd\n" + "This will create muxed DASH segments.\n" + "EX src=m1.mp4 src=m2.mp4:#Period=Yep dst=test.mpd\n" + "This will put src m1.mp4 in first period, m2.mp4 in second period.\n" + "EX src=m1.mp4:#BUrl=http://foo/bar dst=test.mpd\n" + "This will assign a baseURL to src m1.mp4.\n" + "EX src=m1.mp4:#ASCDesc=text dst=test.mpd\n" + "This will assign the specified XML descriptor to the adaptation set.\n" + "Note: this can be used to inject most DASH descriptors not natively handled by the segmenter.\n" + "The segmenter handles the XML descriptor as a string and does not attempt to validate it. Descriptors, as well as some segmenter filter arguments, are string lists (comma-separated by default), so that multiple descriptors can be added:\n" + "EX src=m1.mp4:#RDesc=,text dst=test.mpd\n" + "This will insert two descriptors in the representation(s) of m1.mp4.\n" + "EX src=video.mp4:#Template=foo$Number$ src=audio.mp4:#Template=bar$Number$ dst=test.mpd\n" + "This will assign different templates to the audio and video sources.\n" + "EX src=null:#xlink=http://foo/bar.xml:#PDur=4 src=m.mp4:#PStart=-1\n" + "This will insert an create an MPD with first a remote period then a regular one.\n" + "EX src=null:#xlink=http://foo/bar.xml:#PStart=6 src=m.mp4\n" + "This will create an MPD with first a regular period, dashing ony 6s of content, then a remote one.\n" + "\n" + "The segmenter will create muxing filter chains for each representation and will reassign PID IDs so that each media component (video, audio, ...) in an adaptation set has the same ID.\n" + "\n" + "For HLS, the output pid will deliver the master playlist **and** the variant playlists.\n" + "The default variant playlist are $NAME_$N.m3u8, where $NAME is the radical of the output file name and $N is the 1-based index of the variant.\n" + "\n" + "## Segmentation\n" + "The default behavior of the segmenter is to estimate the theoretical start time of each segment based on target segment duration, and start a new segment when a packet with SAP type 1,2,3 or 4 with time greater than the theoretical time is found.\n" + "This behavior can be changed to find the best SAP packet around a segment theoretical boundary using [-sbound]():\n" + "- closest mode: the segment will start at the closest SAP of the theoretical boundary\n" + "- in mode: the segment will start at or before the theoretical boundary\n" + "Warning: These modes will introduce delay in the segmenter (typically buffering of one GOP) and should not be used for low-latency modes.\n" + "The segmenter can also be configured to:\n" + "- completely ignore SAP when segmenting using [-sap]().\n" + "- ignore SAP on non-video streams when segmenting using [-strict_sap]().\n" + "\n" + "## Cue-driven segmentation\n" + "The segmenter can take a list of instructions, or Cues, to use for the segmentation process, in which case only these are used to derive segment boundaries.\n" + "Cue files can be specified for the entire segmenter, or per PID using DashCue property.\n" + "Cues are given in an XML file with a root element called , with currently no attribute specified. The children are one or more elements, with attributes:\n" + "- id: integer for stream/track/pid ID\n" + "- timescale: integer giving the units of following timestamps\n" + "- mode: if present and value is `edit`, the timestamp are in presentation time (edit list applied) otherwise they are in media time\n" + "\nThe children of are one or more elements, with attributes:\n" + "- sample: integer giving the sample/frame number of a sample at which spliting shall happen\n" + "- dts: long integer giving the decoding time stamp of a sample at which spliting shall happen\n" + "- cts: long integer giving the composition / presentation time stamp of a sample at which spliting shall happen\n" + "Warning: Cues shall be listed in decoding order.\n" + "\n" + "## Manifest Generation only mode\n" + "The segmenter can be used to generate manifests from already fragmented ISOBMFF inputs using [-sigfrag]().\n" + "In this case, segment boundaries are attached to each packet starting a segment and used to drive the segmentation.\n" + "This can be used with single-track ISOBMFF sources, either single file or multi file.\n" + "For single file source:\n" + "- if onDemand [-profile]() is requested, sources have to be formatted as a DASH self-initializing media segment with the proper sidx.\n" + "- templates are disabled.\n" + "- [-sseg]() is forced for all profiles except onDemand ones.\n" + "For multi files source:\n" + "- input shall be a playlist containing the initial file followed by the ordered list of segments.\n" + "- if no [-template]() is provided, the full or main [-profile]() will be used\n" + "- if [-template]() is provided, it shall be correct: the filter will not try to guess one from the input file names and will not validate it either.\n" + "\n" + "The manifest generation-only mode supports both MPD and HLS generation.\n" + "\n" + "EX -i ondemand_src.mp4 -o dash.mpd:sigfrag:profile=onDemand\n" + "This will generate a DASH manifest for onDemand Profile based on the input file.\n" + "EX -i ondemand_src.mp4 -o dash.m3u8:sigfrag\n" + "This will generate a HLS manifest based on the input file.\n" + "EX -i seglist.txt -o dash.mpd:sigfrag\n" + "This will generate a DASH manifest in Main Profile based on the input files.\n" + "EX -i seglist.txt:Template=$XInit=init$$q1/$Number$ -o dash.mpd:sigfrag:profile=live\n" + "This will generate a DASH manifest in live Profile based on the input files. The input file will contain `init.mp4`, `q1/1.m4s`, `q1/2.m4s`...\n" + "\n" + "## Muxer development considerations\n" + "Output muxers allowing segmented output must obey the following:\n" + "- inspect packet properties\n" + " - FileNumber: if set, indicate the start of a new DASH segment\n" + " - FileName: if set, indicate the file name. If not present, output shall be a single file. This is only set for packet carrying the `FileNumber` property, and only on one PID (usually the first) for multiplexed outputs\n" + " - IDXName: gives the optional index name (if not present, index shall be in the same file as dash segment). Only used for MPEG-2 TS for now\n" + " - EODS: property is set on packets with no payload and no timestamp to signal the end of a DASH segment. This is only used when stoping/resuming the segmentation process, in order to flush segments without dispatching an EOS (see [-subdur]() )\n" + "- for each segment done, send a downstream event on the first connected PID signaling the size of the segment and the size of its index if any\n" + "- for muxers with init data, send a downstream event signaling the size of the init and the size of the global index if any\n" + "- the following filter options are passed to muxers, which should declare them as arguments:\n" + " - noinit: disables output of init segment for the muxer (used to handle bitstream switching with single init in DASH)\n" + " - frag: indicates muxer shall use fragmented format (used for ISOBMFF mostly)\n" + " - subs_sidx=0: indicates an SIDX shall be generated - only added if not already specified by user\n" + " - xps_inband=all|no: indicates AVC/HEVC/... parameter sets shall be sent inband or out of band\n" + " - nofragdef: indicates fragment defaults should be set in each segment rather than in init segment\n" + "\n" + "The segmenter will add the following properties to the output PIDs:\n" + "- DashMode: identifies VoD (single file with global index) or regular DASH mode used by segmenter\n" + "- DashDur: identifies target DASH segment duration - this can be used to estimate the SIDX size for example\n" + ) + .private_size = sizeof(GF_DasherCtx), + .args = DasherArgs, + .initialize = dasher_initialize, + .finalize = dasher_finalize, + SETCAPS(DasherCaps), + .flags = GF_FS_REG_REQUIRES_RESOLVER, + .configure_pid = dasher_configure_pid, + .process = dasher_process, + .process_event = dasher_process_event, +}; + + +const GF_FilterRegister *dasher_register(GF_FilterSession *session) +{ + return &DasherRegister; +} diff --git a/src/filters/dec_ac52.c b/src/filters/dec_ac52.c new file mode 100644 index 0000000..85b5523 --- /dev/null +++ b/src/filters/dec_ac52.c @@ -0,0 +1,298 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / AC3 liba52 decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include + +#ifdef GPAC_HAS_LIBA52 + +#if !defined(__GNUC__) +# if defined(_WIN32_WCE) || defined (WIN32) +# pragma comment(lib, "liba52") +# endif +#endif + + +#ifndef uint32_t +#define uint32_t u32 +#endif +#ifndef uint8_t +#define uint8_t u8 +#endif + +#include +#include + +#define AC3_FRAME_SIZE 1536 + +typedef struct +{ + GF_FilterPid *ipid, *opid; + + a52_state_t *codec; + sample_t* samples; + + u32 sample_rate, flags, bit_rate; + u8 num_channels; + u32 channel_mask; + u64 last_cts; + u32 timescale; +} GF_A52DecCtx; + + +static GF_Err a52dec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_A52DecCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + ctx->ipid = NULL; + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + gf_filter_pid_set_framing_mode(ctx->ipid, GF_TRUE); + if (!ctx->opid) { + u32 flags = MM_ACCEL_DJBFFT; +#if !defined (GPAC_CONFIG_IOS) && !defined (GPAC_CONFIG_ANDROID) + flags |= MM_ACCEL_X86_MMX | MM_ACCEL_X86_3DNOW | MM_ACCEL_X86_MMXEXT; +#endif + ctx->codec = a52_init(flags); + if (!ctx->codec) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[A52] Error initializing decoder\n")); + return GF_IO_ERR; + } + + ctx->samples = a52_samples(ctx->codec); + if (!ctx->samples) { + a52_free(ctx->codec); + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[A52] Error initializing decoder\n")); + return GF_IO_ERR; + } + + ctx->opid = gf_filter_pid_new(filter); + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(GF_AUDIO_FMT_S16) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLES_PER_FRAME, &PROP_UINT(AC3_FRAME_SIZE) ); + return GF_OK; +} + +static void a52dec_finalize(GF_Filter *filter) +{ + GF_A52DecCtx *ctx = gf_filter_get_udta(filter); + if (ctx->codec) a52_free(ctx->codec); +} + + +static void a52dec_check_mc_config(GF_A52DecCtx *ctx) +{ + u32 channel_mask = 0; + + switch (ctx->flags & A52_CHANNEL_MASK) { + case A52_CHANNEL1: + case A52_CHANNEL2: + case A52_MONO: + channel_mask = GF_AUDIO_CH_FRONT_CENTER; + break; + case A52_CHANNEL: + case A52_STEREO: + channel_mask = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT; + break; + case A52_DOLBY: + break; + case A52_3F: + channel_mask = GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT; + break; + case A52_2F1R: + channel_mask = GF_AUDIO_CH_REAR_CENTER | GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT; + break; + case A52_3F1R: + channel_mask = GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_REAR_CENTER; + break; + case A52_2F2R: + channel_mask = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT; + break; + case A52_3F2R: + channel_mask = GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT; + break; + } + if (ctx->flags & A52_LFE) + channel_mask |= GF_AUDIO_CH_LFE; + + if (ctx->channel_mask != channel_mask) { + ctx->channel_mask = channel_mask; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CHANNEL_LAYOUT, &PROP_LONGUINT(channel_mask) ); + } +} + + +/**** the following two functions comes from a52dec */ +static GFINLINE s32 blah (s32 i) +{ + if (i > 0x43c07fff) + return 32767; + else if (i < 0x43bf8000) + return -32768; + return i - 0x43c00000; +} + +static GFINLINE void float_to_int (float * _f, s16 *samps, int nchannels) +{ + int i, j, c; + s32 * f = (s32 *) _f; // XXX assumes IEEE float format + + j = 0; + nchannels *= 256; + for (i = 0; i < 256; i++) { + for (c = 0; c < nchannels; c += 256) + samps[j++] = blah (f[i + c]); + } +} + +/**** end */ + +static const int ac3_channels[8] = { + 2, 1, 2, 3, 3, 4, 4, 5 +}; + +static GF_Err a52dec_process(GF_Filter *filter) +{ + short *out_samples; + int i, len, bit_rate; + u32 size; + const char *data; + u8 *buffer; + u32 sample_rate, flags; + u8 num_channels; + sample_t level; + GF_A52DecCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *dst_pck; + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->ipid); + + if (!pck) { + if ( gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[A52] Decoding AU\n")); + + data = gf_filter_pck_get_data(pck, &size); + len = a52_syncinfo((u8 *) data, &flags, &sample_rate, &bit_rate); + if (!len) return GF_NON_COMPLIANT_BITSTREAM; + + num_channels = ac3_channels[flags & 7]; + if (flags & A52_LFE) num_channels++; + flags |= A52_ADJUST_LEVEL; + if ((sample_rate != ctx->sample_rate) || (num_channels != ctx->num_channels) || (ctx->flags != flags) ) { + ctx->num_channels = num_channels; + ctx->sample_rate = sample_rate; + ctx->flags = flags; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(ctx->sample_rate) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(ctx->num_channels) ); + a52dec_check_mc_config(ctx); + } + if (ctx->bit_rate != bit_rate) { + ctx->bit_rate = bit_rate; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_BITRATE, &PROP_UINT(ctx->bit_rate) ); + } + level = 1; + if ( a52_frame(ctx->codec, (u8 *) data, &ctx->flags, &level, 384)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[A52] Error decoding AU\n" )); + if (pck) gf_filter_pid_drop_packet(ctx->ipid); + return GF_NON_COMPLIANT_BITSTREAM; + } + dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->num_channels * sizeof(short) * AC3_FRAME_SIZE, &buffer); + + if (pck) { + ctx->last_cts = gf_filter_pck_get_cts(pck); + ctx->timescale = gf_filter_pck_get_timescale(pck); + gf_filter_pck_merge_properties(pck, dst_pck); + + gf_filter_pid_drop_packet(ctx->ipid); + } + gf_filter_pck_set_cts(dst_pck, ctx->last_cts); + if (ctx->timescale != ctx->sample_rate) { + u64 dur = AC3_FRAME_SIZE * ctx->timescale; + dur /= ctx->sample_rate; + gf_filter_pck_set_duration(dst_pck, (u32) dur); + ctx->last_cts += dur; + } else { + gf_filter_pck_set_duration(dst_pck, AC3_FRAME_SIZE); + ctx->last_cts += AC3_FRAME_SIZE; + } + + out_samples = (short*)buffer; + for (i=0; i<6; i++) { + if (a52_block(ctx->codec)) + return GF_NON_COMPLIANT_BITSTREAM; + + float_to_int(ctx->samples, out_samples + i * 256 * ctx->num_channels, ctx->num_channels); + } + gf_filter_pck_send(dst_pck); + + return GF_OK; +} + + + +static const GF_FilterCapability A52DecCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AC3), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +GF_FilterRegister A52DecRegister = { + .name = "a52dec", + GF_FS_SET_DESCRIPTION("A52 decoder") + GF_FS_SET_HELP("This filter decodes AC3 streams through a52dec library.") + .private_size = sizeof(GF_A52DecCtx), + .priority = 1, + SETCAPS(A52DecCaps), + .configure_pid = a52dec_configure_pid, + .process = a52dec_process, + .finalize = a52dec_finalize +}; + +#endif + +const GF_FilterRegister *a52dec_register(GF_FilterSession *session) +{ +#ifdef GPAC_HAS_LIBA52 + return &A52DecRegister; +#else + return NULL; +#endif +} diff --git a/src/filters/dec_bifs.c b/src/filters/dec_bifs.c new file mode 100644 index 0000000..cabb50c --- /dev/null +++ b/src/filters/dec_bifs.c @@ -0,0 +1,300 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / BIFS decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_BIFS + + +typedef struct +{ + GF_BifsDecoder *bifs_dec; + GF_ObjectManager *odm; + GF_Scene *scene; + + Bool is_playing; + GF_FilterPid *out_pid; +} GF_BIFSDecCtx; + +static GF_Err bifs_dec_configure_bifs_dec(GF_BIFSDecCtx *ctx, GF_FilterPid *pid) +{ + GF_Err e; + u32 es_id=0; + u32 codecid=0; + const GF_PropertyValue *prop; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + if (prop) es_id = prop->value.uint; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (prop) codecid = prop->value.uint; + + + //we must have a dsi + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (!prop || !prop->value.data.ptr || !prop->value.data.size) { + return GF_NON_COMPLIANT_BITSTREAM; + } + + if (!ctx->bifs_dec) { + /*if a node asked for this media object, use the scene graph of the node (AnimationStream in PROTO)*/ + if (ctx->odm->mo && ctx->odm->mo->node_ptr) { + GF_SceneGraph *sg = gf_node_get_graph((GF_Node*)ctx->odm->mo->node_ptr); + ctx->bifs_dec = gf_bifs_decoder_new(sg, GF_TRUE); + ctx->odm->mo->node_ptr = NULL; + } else { + ctx->bifs_dec = gf_bifs_decoder_new(ctx->scene->graph, GF_FALSE); + } + } + + + e = gf_bifs_decoder_configure_stream(ctx->bifs_dec, es_id, prop->value.data.ptr, prop->value.data.size, codecid); + if (e) return e; + + return GF_OK; +} + +GF_Err bifs_dec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_FilterPid *out_pid; + GF_BIFSDecCtx *ctx = gf_filter_get_udta(filter); + const GF_PropertyValue *prop; + + //we must have streamtype SCENE + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (!prop || (prop->value.uint != GF_STREAM_SCENE)) { + return GF_NOT_SUPPORTED; + } + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!prop || ( (prop->value.uint != GF_CODECID_BIFS) && (prop->value.uint != GF_CODECID_BIFS_V2)) ) { + return GF_NOT_SUPPORTED; + } + //we must have a dsi + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (!prop || !prop->value.data.ptr || !prop->value.data.size) { + return GF_NON_COMPLIANT_BITSTREAM; + } + + if (is_remove) { + out_pid = gf_filter_pid_get_udta(pid); + if (ctx->out_pid==out_pid) + ctx->out_pid = NULL; + gf_filter_pid_remove(out_pid); + return GF_OK; + } + //this is a reconfigure + if (gf_filter_pid_get_udta(pid)) { + return bifs_dec_configure_bifs_dec(ctx, pid); + } + + //check our namespace + if (ctx->scene && ! gf_filter_pid_is_filter_in_parents(pid, ctx->scene->root_od->scene_ns->source_filter)) { + return GF_REQUIRES_NEW_INSTANCE; + } + + + //declare a new output PID of type SCENE, codecid RAW + out_pid = gf_filter_pid_new(filter); + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(out_pid, pid); + gf_filter_pid_set_property(out_pid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_udta(pid, out_pid); + + if (!ctx->out_pid) + ctx->out_pid = out_pid; + return GF_OK; +} + + + +GF_Err bifs_dec_process(GF_Filter *filter) +{ + GF_Err e; + Double ts_offset; + u64 now, cts; + u32 obj_time; + u32 i, count; + const char *data; + u32 size, ESID=0; + const GF_PropertyValue *prop; + GF_FilterPacket *pck; + GF_BIFSDecCtx *ctx = gf_filter_get_udta(filter); + + GF_Scene *scene = ctx->scene; + + if (!scene) { + if (ctx->is_playing) { + if (ctx->out_pid && gf_bifs_decode_has_conditionnals(ctx->bifs_dec)) { + gf_filter_pid_set_info(ctx->out_pid, GF_PROP_PID_KEEP_AFTER_EOS, &PROP_BOOL(GF_TRUE)); + } + gf_filter_pid_set_eos(ctx->out_pid); + return GF_EOS; + } + return GF_OK; + } + if (!ctx->bifs_dec) return GF_OK; + + count = gf_filter_get_ipid_count(filter); + for (i=0; ick); + + pck = gf_filter_pid_get_packet(pid); + if (!pck) { + Bool is_eos = gf_filter_pid_is_eos(pid); + if (is_eos) { + if (opid && gf_bifs_decode_has_conditionnals(ctx->bifs_dec)) { + gf_filter_pid_set_info(opid, GF_PROP_PID_KEEP_AFTER_EOS, &PROP_BOOL(GF_TRUE)); + } + gf_filter_pid_set_eos(opid); + } + continue; + } + data = gf_filter_pck_get_data(pck, &size); + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + if (prop) ESID = prop->value.uint; + + cts = gf_filter_pck_get_cts( pck ); + ts_offset = (Double) cts; + ts_offset /= gf_filter_pck_get_timescale(pck); + + gf_odm_check_buffering(odm, pid); + + + //we still process any frame before our clock time even when buffering + obj_time = gf_clock_time(odm->ck); + if (ts_offset * 1000 > obj_time) { + gf_sc_sys_frame_pending(scene->compositor, ts_offset, obj_time, filter); + continue; + } + + now = gf_sys_clock_high_res(); + e = gf_bifs_decode_au(ctx->bifs_dec, ESID, data, size, ts_offset); + now = gf_sys_clock_high_res() - now; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[BIFS] ODM%d #CH%d at %d decoded AU TS %u in "LLU" us\n", odm->ID, ESID, obj_time, cts, now)); + + gf_filter_pid_drop_packet(pid); + + if (e) return e; + if (odm == ctx->odm) + gf_scene_attach_to_compositor(scene); + } + return GF_OK; +} + +static void bifs_dec_finalize(GF_Filter *filter) +{ + GF_BIFSDecCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bifs_dec) gf_bifs_decoder_del(ctx->bifs_dec); +} + + +static Bool bifs_dec_process_event(GF_Filter *filter, const GF_FilterEvent *com) +{ + u32 count, i; + GF_BIFSDecCtx *ctx = gf_filter_get_udta(filter); + //check for scene attach + switch (com->base.type) { + case GF_FEVT_ATTACH_SCENE: + break; + case GF_FEVT_PLAY: + ctx->is_playing = GF_TRUE; + return GF_FALSE; + case GF_FEVT_RESET_SCENE: + return GF_FALSE; + + default: + return GF_FALSE; + } + if (!com->attach_scene.on_pid) return GF_TRUE; + + count = gf_filter_get_ipid_count(filter); + for (i=0; iattach_scene.on_pid) { + if (!ctx->odm) { + ctx->odm = com->attach_scene.object_manager; + ctx->scene = ctx->odm->subscene ? ctx->odm->subscene : ctx->odm->parentscene; + } + bifs_dec_configure_bifs_dec(ctx, ipid); + gf_filter_pid_set_udta(opid, com->attach_scene.object_manager); + return GF_TRUE; + } + } + + return GF_TRUE; +} + +static const GF_FilterCapability BIFSDecCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_BIFS), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_BIFS_V2), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +GF_FilterRegister BIFSDecRegister = { + .name = "bifsdec", + GF_FS_SET_DESCRIPTION("MPEG-4 BIFS decoder") + GF_FS_SET_HELP("This filter decodes MPEG-4 BIFS frames directly into the scene graph of the compositor. It cannot be used to dump BIFS content.") + .private_size = sizeof(GF_BIFSDecCtx), + .flags = GF_FS_REG_MAIN_THREAD, + .priority = 1, + SETCAPS(BIFSDecCaps), + .finalize = bifs_dec_finalize, + .process = bifs_dec_process, + .configure_pid = bifs_dec_configure_pid, + .process_event = bifs_dec_process_event, +}; + +#endif /*GPAC_DISABLE_BIFS*/ + +const GF_FilterRegister *bifs_dec_register(GF_FilterSession *session) +{ +#ifdef GPAC_DISABLE_BIFS + return NULL; +#else + return &BIFSDecRegister; +#endif /*GPAC_DISABLE_BIFS*/ +} + + + diff --git a/src/filters/dec_faad.c b/src/filters/dec_faad.c new file mode 100644 index 0000000..62b139e --- /dev/null +++ b/src/filters/dec_faad.c @@ -0,0 +1,430 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / AAC FAAD2 decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include + +#ifdef GPAC_HAS_FAAD + +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#endif + +#if !defined(__GNUC__) +# if defined(_WIN32_WCE) || defined (WIN32) +# pragma comment(lib, "libfaad") +# endif +#endif + +#include +#include +#include + + +typedef struct +{ + NeAACDecHandle codec; + NeAACDecFrameInfo info; u32 sample_rate, timescale, num_samples; + u8 num_channels; + + GF_FilterPid *ipid, *opid; + u32 cfg_crc; + + Bool signal_mc; + Bool is_sbr; + + u32 channel_mask; + char ch_reorder[16]; + u64 last_cts; +} GF_FAADCtx; + +static void faaddec_check_mc_config(GF_FAADCtx *ctx) +{ + u32 i, channel_mask = 0; + for (i=0; inum_channels; i++) { + switch (ctx->info.channel_position[i]) { + case FRONT_CHANNEL_CENTER: + channel_mask |= GF_AUDIO_CH_FRONT_CENTER; + break; + case FRONT_CHANNEL_LEFT: + channel_mask |= GF_AUDIO_CH_FRONT_LEFT; + break; + case FRONT_CHANNEL_RIGHT: + channel_mask |= GF_AUDIO_CH_FRONT_RIGHT; + break; + case SIDE_CHANNEL_LEFT: + channel_mask |= GF_AUDIO_CH_REAR_SURROUND_LEFT; + break; + case SIDE_CHANNEL_RIGHT: + channel_mask |= GF_AUDIO_CH_REAR_SURROUND_RIGHT; + break; + case BACK_CHANNEL_LEFT: + channel_mask |= GF_AUDIO_CH_SURROUND_LEFT; + break; + case BACK_CHANNEL_RIGHT: + channel_mask |= GF_AUDIO_CH_SURROUND_RIGHT; + break; + case BACK_CHANNEL_CENTER: + channel_mask |= GF_AUDIO_CH_REAR_CENTER; + break; + case LFE_CHANNEL: + channel_mask |= GF_AUDIO_CH_LFE; + break; + default: + break; + } + } + if (ctx->channel_mask != channel_mask) { + ctx->channel_mask = channel_mask; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CHANNEL_LAYOUT, &PROP_LONGUINT(channel_mask) ); + } +} + +static GF_Err faaddec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_FAADCtx *ctx = gf_filter_get_udta(filter); +#ifndef GPAC_DISABLE_AV_PARSERS + GF_Err e; + GF_M4ADecSpecInfo a_cfg; +#endif + + if (is_remove) { + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + ctx->ipid = NULL; + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, pid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(GF_AUDIO_FMT_S16) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL ); + ctx->ipid = pid; + + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (p && p->value.data.ptr && p->value.data.size) { + u32 ex_crc = gf_crc_32(p->value.data.ptr, p->value.data.size); + if (ctx->cfg_crc && (ctx->cfg_crc != ex_crc)) { + //shoud we flush ? + if (ctx->codec) NeAACDecClose(ctx->codec); + ctx->codec = NULL; + } + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[FAAD] Reconfiguring but no DSI set, skipping\n")); + return GF_OK; + } + + ctx->codec = NeAACDecOpen(); + if (!ctx->codec) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FAAD] Error initializing decoder\n")); + return GF_IO_ERR; + } + +#ifndef GPAC_DISABLE_AV_PARSERS + e = gf_m4a_get_config(p->value.data.ptr, p->value.data.size, &a_cfg); + if (e) return e; +#endif + if (NeAACDecInit2(ctx->codec, (unsigned char *)p->value.data.ptr, p->value.data.size, (unsigned long*)&ctx->sample_rate, (u8*)&ctx->num_channels) < 0) + { +#ifndef GPAC_DISABLE_AV_PARSERS + s8 res; + u8 *dsi, *s_base_object_type; + u32 dsi_len; + switch (a_cfg.base_object_type) { + case GF_M4A_AAC_MAIN: + s_base_object_type = gf_stringizer(GF_M4A_AAC_MAIN); + goto base_object_type_error; + case GF_M4A_AAC_LC: + s_base_object_type = gf_stringizer(GF_M4A_AAC_LC); + goto base_object_type_error; + case GF_M4A_AAC_SSR: + s_base_object_type = gf_stringizer(GF_M4A_AAC_SSR); + goto base_object_type_error; + case GF_M4A_AAC_LTP: + s_base_object_type = gf_stringizer(GF_M4A_AAC_LTP); + goto base_object_type_error; + case GF_M4A_AAC_SBR: + s_base_object_type = gf_stringizer(GF_M4A_AAC_SBR); + goto base_object_type_error; + case GF_M4A_AAC_PS: + s_base_object_type = gf_stringizer(GF_M4A_AAC_PS); +base_object_type_error: /*error case*/ + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[FAAD] Error: unsupported %s format for stream - defaulting to AAC LC\n", s_base_object_type)); + default: + break; + } + a_cfg.base_object_type = GF_M4A_AAC_LC; + a_cfg.has_sbr = GF_FALSE; + a_cfg.nb_chan = a_cfg.nb_chan > 2 ? 1 : a_cfg.nb_chan; + + gf_m4a_write_config(&a_cfg, &dsi, &dsi_len); + res = NeAACDecInit2(ctx->codec, (unsigned char *) dsi, dsi_len, (unsigned long *) &ctx->sample_rate, (u8 *) &ctx->num_channels); + gf_free(dsi); + if (res < 0) +#endif + { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FAAD] Error when initializing AAC decoder for stream\n")); + return GF_NOT_SUPPORTED; + } + } + +#ifndef GPAC_DISABLE_AV_PARSERS + ctx->is_sbr = a_cfg.has_sbr; +#endif + ctx->num_samples = 1024; + ctx->signal_mc = ctx->num_channels>2 ? GF_TRUE : GF_FALSE; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(ctx->sample_rate) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLES_PER_FRAME, &PROP_UINT(ctx->num_samples) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(ctx->num_channels) ); + +#if 0 + if (!strcmp(FAAD2_VERSION, "unknown")) { + if (ctx->is_sbr) gf_filter_set_name(filter, "dec_faad:FAAD2-" FAAD2_VERSION "-SBR"); + else gf_filter_set_name(filter, "dec_faad:FAAD2-" FAAD2_VERSION); + } else +#endif + { + if (ctx->is_sbr) gf_filter_set_name(filter, "dec_faad:FAAD2-SBR"); + else gf_filter_set_name(filter, "dec_faad:FAAD2"); + } + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + return GF_OK; +} + +static s8 faaddec_get_channel_pos(GF_FAADCtx *ffd, u32 ch_cfg) +{ + u32 i; + for (i=0; iinfo.channels; i++) { + switch (ffd->info.channel_position[i]) { + case FRONT_CHANNEL_CENTER: + if (ch_cfg==GF_AUDIO_CH_FRONT_CENTER) return i; + break; + case FRONT_CHANNEL_LEFT: + if (ch_cfg==GF_AUDIO_CH_FRONT_LEFT) return i; + break; + case FRONT_CHANNEL_RIGHT: + if (ch_cfg==GF_AUDIO_CH_FRONT_RIGHT) return i; + break; + case SIDE_CHANNEL_LEFT: + if (ch_cfg==GF_AUDIO_CH_REAR_SURROUND_LEFT) return i; + break; + case SIDE_CHANNEL_RIGHT: + if (ch_cfg==GF_AUDIO_CH_REAR_SURROUND_RIGHT) return i; + break; + case BACK_CHANNEL_LEFT: + if (ch_cfg==GF_AUDIO_CH_SURROUND_LEFT) return i; + break; + case BACK_CHANNEL_RIGHT: + if (ch_cfg==GF_AUDIO_CH_SURROUND_RIGHT) return i; + break; + case BACK_CHANNEL_CENTER: + if (ch_cfg==GF_AUDIO_CH_REAR_CENTER) return i; + break; + case LFE_CHANNEL: + if (ch_cfg==GF_AUDIO_CH_LFE) return i; + break; + } + } + return -1; +} + +static GF_Err faaddec_process(GF_Filter *filter) +{ + GF_FAADCtx *ctx = gf_filter_get_udta(filter); + void *buffer; + u8 *output; + u32 i, j; + Bool is_eos = GF_FALSE; + GF_FilterPacket *dst_pck; + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->ipid); + + if (!pck) { + is_eos = gf_filter_pid_is_eos(ctx->ipid); + if (!is_eos) return GF_OK; + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[FAAD] Decoding AU\n")); + if (!pck) { + buffer = NeAACDecDecode(ctx->codec, &ctx->info, NULL, 0); + } else { + Bool start, end; + u32 size; + const char *data = gf_filter_pck_get_data(pck, &size); + buffer = NeAACDecDecode(ctx->codec, &ctx->info, (char *) data, size); + + gf_filter_pck_get_framing(pck, &start, &end); + assert(start && end); + } + + if (ctx->info.error>0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[FAAD] Error decoding AU %s\n", NeAACDecGetErrorMessage(ctx->info.error) )); + if (pck) gf_filter_pid_drop_packet(ctx->ipid); + return GF_NON_COMPLIANT_BITSTREAM; + } + if (!ctx->info.samples || !buffer || !ctx->info.bytesconsumed) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[FAAD] empty/non complete AU\n")); + if (is_eos) gf_filter_pid_set_eos(ctx->opid); + if (pck) gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[FAAD] AU decoded\n")); + + /*FAAD froces us to decode a frame to get channel cfg*/ + if (ctx->signal_mc) { + s32 ch, idx; + ctx->signal_mc = GF_FALSE; + idx = 0; + /*NOW WATCH OUT!! FAAD may very well decide to output more channels than indicated!!!*/ + ctx->num_channels = ctx->info.channels; + + /*get cfg*/ + ch = faaddec_get_channel_pos(ctx, GF_AUDIO_CH_FRONT_LEFT); + if (ch>=0) { + ctx->ch_reorder[idx] = ch; + idx++; + } + ch = faaddec_get_channel_pos(ctx, GF_AUDIO_CH_FRONT_RIGHT); + if (ch>=0) { + ctx->ch_reorder[idx] = ch; + idx++; + } + ch = faaddec_get_channel_pos(ctx, GF_AUDIO_CH_FRONT_CENTER); + if (ch>=0) { + ctx->ch_reorder[idx] = ch; + idx++; + } + ch = faaddec_get_channel_pos(ctx, GF_AUDIO_CH_LFE); + if (ch>=0) { + ctx->ch_reorder[idx] = ch; + idx++; + } + ch = faaddec_get_channel_pos(ctx, GF_AUDIO_CH_SURROUND_LEFT); + if (ch>=0) { + ctx->ch_reorder[idx] = ch; + idx++; + } + ch = faaddec_get_channel_pos(ctx, GF_AUDIO_CH_SURROUND_RIGHT); + if (ch>=0) { + ctx->ch_reorder[idx] = ch; + idx++; + } + ch = faaddec_get_channel_pos(ctx, GF_AUDIO_CH_REAR_CENTER); + if (ch>=0) { + ctx->ch_reorder[idx] = ch; + idx++; + } + ch = faaddec_get_channel_pos(ctx, GF_AUDIO_CH_REAR_SURROUND_LEFT); + if (ch>=0) { + ctx->ch_reorder[idx] = ch; + idx++; + } + ch = faaddec_get_channel_pos(ctx, GF_AUDIO_CH_REAR_SURROUND_RIGHT); + if (ch>=0) { + ctx->ch_reorder[idx] = ch; + //last one, no need to increment idx++; + } + faaddec_check_mc_config(ctx); + } + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, (u32) (sizeof(short) * ctx->info.samples), &output); + if (!dst_pck) { + if (pck) gf_filter_pid_drop_packet(ctx->ipid); + return GF_OUT_OF_MEM; + } + if (pck) { + ctx->last_cts = gf_filter_pck_get_cts(pck); + ctx->timescale = gf_filter_pck_get_timescale(pck); + gf_filter_pck_merge_properties(pck, dst_pck); + } + gf_filter_pck_set_cts(dst_pck, ctx->last_cts); + if (ctx->timescale != ctx->sample_rate) { + u64 dur =ctx->info.samples * ctx->timescale; + dur /= ctx->sample_rate; + gf_filter_pck_set_duration(dst_pck, (u32) dur); + ctx->last_cts += dur; + } else { + gf_filter_pck_set_duration(dst_pck, (u32) ctx->info.samples); + ctx->last_cts += ctx->info.samples; + } + /*we assume left/right order*/ + if (ctx->num_channels<=2) { + memcpy(output, buffer, sizeof(short)* ctx->info.samples); + } else { + unsigned short *conv_in, *conv_out; + conv_in = (unsigned short *) buffer; + conv_out = (unsigned short *) output; + for (i=0; iinfo.samples; i+=ctx->info.channels) { + for (j=0; jinfo.channels; j++) { + conv_out[i + j] = conv_in[i + ctx->ch_reorder[j]]; + } + } + } + gf_filter_pck_send(dst_pck); + if (pck) gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + +static const GF_FilterCapability FAADCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG4), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_MP), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_LCP), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_SSRP), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +GF_FilterRegister FAADRegister = { + .name = "faad", + GF_FS_SET_DESCRIPTION("FAAD decoder") + GF_FS_SET_HELP("This filter decodes AAC streams through faad library.") + .private_size = sizeof(GF_FAADCtx), + .priority = 1, + SETCAPS(FAADCaps), + .configure_pid = faaddec_configure_pid, + .process = faaddec_process, +}; + +#endif + +const GF_FilterRegister *faad_register(GF_FilterSession *session) +{ +#ifdef GPAC_HAS_FAAD + return &FAADRegister; +#else + return NULL; +#endif +} diff --git a/src/filters/dec_img.c b/src/filters/dec_img.c new file mode 100644 index 0000000..4f016cf --- /dev/null +++ b/src/filters/dec_img.c @@ -0,0 +1,173 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / libjpeg and libpng decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +typedef struct +{ + u32 codecid; + GF_FilterPid *ipid, *opid; + u32 width, height, pixel_format, BPP; +} GF_IMGDecCtx; + +static GF_Err imgdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *prop; + GF_IMGDecCtx *ctx = (GF_IMGDecCtx *) gf_filter_get_udta(filter); + + //disconnect of src pid (not yet supported) + if (is_remove) { + //one in one out, this is simple + gf_filter_pid_remove(ctx->opid); + ctx->ipid = NULL; + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!prop) return GF_NOT_SUPPORTED; + ctx->codecid = prop->value.uint; + ctx->ipid = pid; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT( GF_CODECID_RAW )); + + if (ctx->codecid==GF_CODECID_JPEG) { + gf_filter_set_name(filter, "imgdec:libjpeg"); + } else if (ctx->codecid==GF_CODECID_PNG) { + gf_filter_set_name(filter, "imgdec:libpng"); + } + return GF_OK; +} + +static GF_Err imgdec_process(GF_Filter *filter) +{ +#ifndef GPAC_DISABLE_AV_PARSERS + GF_Err e; + GF_FilterPacket *pck; + u8 *data, *output; + u32 size; + GF_IMGDecCtx *ctx = (GF_IMGDecCtx *) gf_filter_get_udta(filter); + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + data = (char *) gf_filter_pck_get_data(pck, &size); + + if ((ctx->codecid == GF_CODECID_JPEG) || (ctx->codecid == GF_CODECID_PNG)) { + GF_FilterPacket *dst_pck; + u32 out_size = 0; + u32 w = ctx->width; + u32 h = ctx->height; + u32 pf = ctx->pixel_format; + + if (ctx->codecid == GF_CODECID_JPEG) { + e = gf_img_jpeg_dec(data, size, &ctx->width, &ctx->height, &ctx->pixel_format, NULL, &out_size, ctx->BPP); + } else { + e = gf_img_png_dec(data, size, &ctx->width, &ctx->height, &ctx->pixel_format, NULL, &out_size); + } + + if (e != GF_BUFFER_TOO_SMALL) { + gf_filter_pid_drop_packet(ctx->ipid); + return e; + } + if ((w != ctx->width) || (h!=ctx->height) || (pf!=ctx->pixel_format)) { + switch (ctx->pixel_format) { + case GF_PIXEL_GREYSCALE: + ctx->BPP = 1; + break; + case GF_PIXEL_RGB: + ctx->BPP = 3; + break; + case GF_PIXEL_RGBA: + ctx->BPP = 4; + break; + } + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, & PROP_UINT(ctx->width)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, & PROP_UINT(ctx->height)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, & PROP_UINT(ctx->pixel_format)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->BPP*ctx->width) ); + } + dst_pck = gf_filter_pck_new_alloc(ctx->opid, out_size, &output); + if (!dst_pck) return GF_SERVICE_ERROR; + + if (ctx->codecid == GF_CODECID_JPEG) { + e = gf_img_jpeg_dec(data, size, &ctx->width, &ctx->height, &ctx->pixel_format, output, &out_size, ctx->BPP); + } else { + e = gf_img_png_dec(data, size, &ctx->width, &ctx->height, &ctx->pixel_format, output, &out_size); + } + + if (e) { + gf_filter_pck_discard(dst_pck); + } else { + gf_filter_pck_merge_properties(pck, dst_pck); + gf_filter_pck_send(dst_pck); + } + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } +#endif //GPAC_DISABLE_AV_PARSERS + + return GF_NOT_SUPPORTED; +} + + +static const GF_FilterCapability ImgDecCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_JPEG), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_PNG), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +GF_FilterRegister ImgDecRegister = { + .name = "imgdec", + GF_FS_SET_DESCRIPTION("PNG/JPG decoder") + GF_FS_SET_HELP("This filter decodes JPEG and PNG images.") + .private_size = sizeof(GF_IMGDecCtx), + .priority = 1, + SETCAPS(ImgDecCaps), + .configure_pid = imgdec_configure_pid, + .process = imgdec_process, +}; + +const GF_FilterRegister *imgdec_register(GF_FilterSession *session) +{ + return &ImgDecRegister; +} diff --git a/src/filters/dec_j2k.c b/src/filters/dec_j2k.c new file mode 100644 index 0000000..cfd8059 --- /dev/null +++ b/src/filters/dec_j2k.c @@ -0,0 +1,625 @@ +/* +* GPAC - Multimedia Framework C SDK +* + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 +* All rights reserved +* +* This file is part of GPAC / openjpeg2k decoder filter +* +* GPAC is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* GPAC is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; see the file COPYING. If not, write to +* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +* +*/ + +#include + +#ifdef GPAC_HAS_JP2 + +//we MUST set OPJ_STATIC before including openjpeg.h +#if !defined(__GNUC__) && (defined(_WIN32_WCE) || defined (WIN32)) +# define OPJ_STATIC +#endif + +#include +#include + +#include + +#ifdef OPJ_PROFILE_NONE +#define OPENJP2 1 + +# if !defined(__GNUC__) && (defined(_WIN32_WCE) || defined (WIN32)) +# if defined(_DEBUG) +# pragma comment(lib, "libopenjp2d") +# else +# pragma comment(lib, "libopenjp2") +# endif +# endif + +#else + +#define OPENJP2 0 + +# if !defined(__GNUC__) && (defined(_WIN32_WCE) || defined (WIN32)) +# if defined(_DEBUG) +# pragma comment(lib, "LibOpenJPEGd") +# else +# pragma comment(lib, "LibOpenJPEG") +# endif +# endif + +#endif + +typedef struct +{ + GF_FilterPid *ipid, *opid; + u32 cfg_crc; + /*no support for scalability with JPEG (progressive JPEG to test)*/ + u32 bpp, nb_comp, width, height, out_size, pixel_format, dsi_size; + char *dsi; +} GF_J2KCtx; + + +static GF_Err j2kdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_J2KCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + ctx->ipid = NULL; + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (p && p->value.data.ptr && p->value.data.size) { + GF_BitStream *bs; + u32 d4cc; + Bool dsi_ok=GF_FALSE; + u32 ex_crc = gf_crc_32(p->value.data.ptr, p->value.data.size); + if (ctx->cfg_crc == ex_crc) { + return GF_OK; + } + ctx->cfg_crc = ex_crc; + + //old gpac version used to store only the payload of ihdr in the dsi, but the new version exposes the entire jp2h (without box size and type) as dsi + //so handle both on read + d4cc = 0; + if (p->value.data.size>8) + d4cc = GF_4CC(p->value.data.ptr[4], p->value.data.ptr[5], p->value.data.ptr[6], p->value.data.ptr[7]); + + bs = gf_bs_new(p->value.data.ptr, p->value.data.size, GF_BITSTREAM_READ); + if ((d4cc==GF_4CC('i','h','d','r')) || (d4cc==GF_4CC('c','o','l','r'))) { + while (gf_bs_available(bs)) { + u32 bsize = gf_bs_read_u32(bs); + u32 btype = gf_bs_read_u32(bs); + if (btype==GF_4CC('i','h','d','r')) { + dsi_ok=GF_TRUE; + break; + } + gf_bs_skip_bytes(bs, bsize-8); + } + } else { + dsi_ok=GF_TRUE; + } + if (dsi_ok) { + ctx->height = gf_bs_read_u32(bs); + ctx->width = gf_bs_read_u32(bs); + ctx->nb_comp = gf_bs_read_u16(bs); + ctx->bpp = 1 + gf_bs_read_u8(bs); + } + gf_bs_del(bs); + + if (!dsi_ok) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[OpenJPEG] Broken decoder config in j2k stream, cannot decode\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + + ctx->out_size = ctx->width * ctx->height * ctx->nb_comp /* * ctx->bpp / 8 */; + + switch (ctx->nb_comp) { + case 1: + ctx->pixel_format = GF_PIXEL_GREYSCALE; + break; + case 2: + ctx->pixel_format = GF_PIXEL_ALPHAGREY; + break; + case 3: + ctx->pixel_format = GF_PIXEL_RGB; + break; + case 4: + ctx->pixel_format = GF_PIXEL_RGBA; + break; + default: + return GF_NOT_SUPPORTED; + } + } else { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + if (p) ctx->width = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + if (p) ctx->height = p->value.uint; + ctx->nb_comp = 0; + ctx->pixel_format = 0; + } + + ctx->ipid = pid; + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_framing_mode(ctx->ipid, GF_TRUE); + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->width) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->height) ); + if (ctx->pixel_format) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT( (ctx->pixel_format == GF_PIXEL_YUV) ? ctx->width : ctx->width * ctx->nb_comp) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, &PROP_UINT(ctx->pixel_format) ); + } + return GF_OK; +} + +/** +sample error callback expecting a FILE* client object +*/ +void error_callback(const char *msg, void *client_data) +{ + if (msg) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[OpenJPEG] Error %s", msg)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[OpenJPEG] coverage test\n")); + } +} +/** +sample warning callback expecting a FILE* client object +*/ +void warning_callback(const char *msg, void *client_data) +{ + if (msg) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[OpenJPEG] Warning %s", msg)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[OpenJPEG] coverage test\n")); + } +} +/** +sample debug callback expecting no client object +*/ +void info_callback(const char *msg, void *client_data) +{ + if (msg) { + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[OpenJPEG] Info %s", msg)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[OpenJPEG] coverage test\n")); + } +} + +/* +* Divide an integer by a power of 2 and round upwards. +* +* a divided by 2^b +*/ +static int int_ceildivpow2(int a, int b) { + return (a + (1 << b) - 1) >> b; +} + +#if OPENJP2 +typedef struct +{ + char *data; + u32 len, pos; +} OJP2Frame; + +static OPJ_SIZE_T j2kdec_stream_read(void *out_buffer, OPJ_SIZE_T nb_bytes, void *user_data) +{ + OJP2Frame *frame = user_data; + u32 remain; + if (frame->pos == frame->len) return (OPJ_SIZE_T)-1; + remain = frame->len - frame->pos; + if (nb_bytes > remain) nb_bytes = remain; + memcpy(out_buffer, frame->data + frame->pos, nb_bytes); + frame->pos += (u32) nb_bytes; + return nb_bytes; +} + +static OPJ_OFF_T j2kdec_stream_skip(OPJ_OFF_T nb_bytes, void *user_data) +{ + OJP2Frame *frame = user_data; + if (!user_data) return 0; + + if (nb_bytes < 0) { + if (frame->pos == 0) return (OPJ_SIZE_T)-1; + if (nb_bytes + (s32) frame->pos < 0) { + nb_bytes = -frame->pos; + } + } else { + u32 remain; + if (frame->pos == frame->len) { + return (OPJ_SIZE_T)-1; + } + remain = frame->len - frame->pos; + if (nb_bytes > remain) { + nb_bytes = remain; + } + } + frame->pos += (u32) nb_bytes; + return nb_bytes; +} + +static OPJ_BOOL j2kdec_stream_seek(OPJ_OFF_T nb_bytes, void *user_data) +{ + OJP2Frame *frame = user_data; + if (nb_bytes < 0 || nb_bytes > frame->pos) return OPJ_FALSE; + frame->pos = (u32)nb_bytes; + return OPJ_TRUE; +} +#endif + + +static GF_Err j2kdec_process(GF_Filter *filter) +{ + u32 i, w, wr, h, hr, wh, size, pf; + u8 *data, *buffer; + opj_dparameters_t parameters; /* decompression parameters */ +#if OPENJP2 + s32 res; + opj_codec_t *codec = NULL; + opj_stream_t * stream = NULL; + OJP2Frame ojp2frame; +#else + opj_event_mgr_t event_mgr; /* event manager */ + opj_dinfo_t* dinfo = NULL; /* handle to a decompressor */ + opj_cio_t *cio = NULL; + opj_codestream_info_t cinfo; +#endif + opj_image_t *image = NULL; + u32 start_offset=0; + GF_J2KCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *pck_dst; + Bool changed = GF_FALSE; + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) + gf_filter_pid_set_eos(ctx->opid); + return GF_OK; + } + data = (char *) gf_filter_pck_get_data(pck, &size); + if (!data) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_IO_ERR; + } + + if (size>=8) { + if ((data[4]=='j') && (data[5]=='p') && (data[6]=='2') && (data[7]=='c')) + start_offset = 8; + } + + /* set decoding parameters to default values */ + opj_set_default_decoder_parameters(¶meters); + +#if OPENJP2 + codec = opj_create_decompress(OPJ_CODEC_J2K); + if (codec) res = 1; + else res=0; + + if (res) res = opj_set_info_handler(codec, info_callback, NULL); + if (res) res = opj_set_warning_handler(codec, warning_callback, NULL); + if (res) res = opj_set_error_handler(codec, error_callback, NULL); + + if (res) res = opj_setup_decoder(codec, ¶meters); + + stream = opj_stream_default_create(OPJ_STREAM_READ); + opj_stream_set_read_function(stream, j2kdec_stream_read); + opj_stream_set_skip_function(stream, j2kdec_stream_skip); + opj_stream_set_seek_function(stream, j2kdec_stream_seek); + ojp2frame.data = data+start_offset; + ojp2frame.len = size-start_offset; + ojp2frame.pos = 0; + opj_stream_set_user_data(stream, &ojp2frame, NULL); + opj_stream_set_user_data_length(stream, ojp2frame.len); + + if (res) res = opj_read_header(stream, codec, &image); + if (res) res = opj_set_decode_area(codec, image, 0, 0, image->x1, image->y1); + if (res) { + res = opj_decode(codec, stream, image); + if (!res) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[OpenJPEG] Decoding failed\n")); + opj_image_destroy(image); + image = NULL; + } + } + +#else + + /* get a decoder handle for raw J2K frames*/ + dinfo = opj_create_decompress(CODEC_J2K); + + /* configure the event callbacks (not required) */ + memset(&event_mgr, 0, sizeof(opj_event_mgr_t)); + event_mgr.error_handler = error_callback; + event_mgr.warning_handler = warning_callback; + event_mgr.info_handler = info_callback; + + /* catch events using our callbacks and give a local context */ + opj_set_event_mgr((opj_common_ptr)dinfo, &event_mgr, stderr); + + /* setup the decoder decoding parameters using the current image and user parameters */ + opj_setup_decoder(dinfo, ¶meters); + + cio = opj_cio_open((opj_common_ptr)dinfo, data+start_offset, size-start_offset); + /* decode the stream and fill the image structure */ + image = opj_decode_with_info(dinfo, cio, &cinfo); +#endif + + if (!image) { +#if OPENJP2 + opj_stream_destroy(stream); + opj_destroy_codec(codec); +#else + opj_destroy_decompress(dinfo); + opj_cio_close(cio); +#endif + gf_filter_pid_drop_packet(ctx->ipid); + return GF_IO_ERR; + } + +#if OPENJP2 + ctx->nb_comp = image->numcomps; + w = image->x1; + h = image->y1; +#else + ctx->nb_comp = cinfo.numcomps; + w = cinfo.image_w; + h = cinfo.image_h; +#endif + ctx->bpp = ctx->nb_comp * 8; + ctx->out_size = ctx->width * ctx->height * ctx->nb_comp /* * ctx->bpp / 8 */; + + switch (ctx->nb_comp) { + case 1: + pf = GF_PIXEL_GREYSCALE; + break; + case 2: + pf = GF_PIXEL_ALPHAGREY; + break; + case 3: + pf = GF_PIXEL_RGB; + break; + case 4: + pf = GF_PIXEL_RGBA; + break; + default: + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NOT_SUPPORTED; + } + if ((image->comps[0].w==2*image->comps[1].w) + && (image->comps[1].w==image->comps[2].w) + && (image->comps[0].h==2*image->comps[1].h) + && (image->comps[1].h==image->comps[2].h)) { + pf = GF_PIXEL_YUV; + ctx->out_size = 3*ctx->width*ctx->height/2; + changed = GF_TRUE; + } + if (ctx->pixel_format!=pf) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, &PROP_UINT(pf) ); + ctx->pixel_format = pf; + changed = GF_TRUE; + } + if (ctx->width!=w) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(w) ); + ctx->width = w; + changed = GF_TRUE; + } + if (ctx->height!=h) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(h) ); + ctx->height = h; + changed = GF_TRUE; + } + if (changed) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT( (ctx->pixel_format == GF_PIXEL_YUV) ? ctx->width : ctx->width * ctx->nb_comp) ); + } + +#if OPENJP2 + opj_end_decompress(codec, stream); + opj_stream_destroy(stream); + stream = NULL; + opj_destroy_codec(codec); + codec = NULL; +#else + /* close the byte stream */ + opj_cio_close(cio); + cio = NULL; + + /* gf_free( remaining structures */ + if(dinfo) { + opj_destroy_decompress(dinfo); + dinfo = NULL; + } +#endif + + pck_dst = gf_filter_pck_new_alloc(ctx->opid, ctx->out_size, &buffer); + + w = image->comps[0].w; + wr = int_ceildivpow2(image->comps[0].w, image->comps[0].factor); + h = image->comps[0].h; + hr = int_ceildivpow2(image->comps[0].h, image->comps[0].factor); + wh = wr*hr; + + if (ctx->nb_comp==1) { + if ((w==wr) && (h==hr)) { + for (i=0; icomps[0].data[i]; + } + } else { + for (i=0; icomps[0].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; + } + } + } + else if (ctx->nb_comp==3) { + + if ((image->comps[0].w==2*image->comps[1].w) && (image->comps[1].w==image->comps[2].w) + && (image->comps[0].h==2*image->comps[1].h) && (image->comps[1].h==image->comps[2].h)) { + + if ((w==wr) && (h==hr)) { + for (i=0; icomps[0].data[i]; + buffer++; + } +// w = image->comps[1].w; + wr = int_ceildivpow2(image->comps[1].w, image->comps[1].factor); +// h = image->comps[1].h; + hr = int_ceildivpow2(image->comps[1].h, image->comps[1].factor); + wh = wr*hr; + for (i=0; icomps[1].data[i]; + buffer++; + } + for (i=0; icomps[2].data[i]; + buffer++; + } + } else { + for (i=0; icomps[0].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; + } + w = image->comps[1].w; + wr = int_ceildivpow2(image->comps[1].w, image->comps[1].factor); +// h = image->comps[1].h; + hr = int_ceildivpow2(image->comps[1].h, image->comps[1].factor); + wh = wr*hr; + for (i=0; icomps[1].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; + } + for (i=0; icomps[2].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; + } + } + + + } else if ((image->comps[0].w==image->comps[1].w) && (image->comps[1].w==image->comps[2].w) + && (image->comps[0].h==image->comps[1].h) && (image->comps[1].h==image->comps[2].h)) { + + if ((w==wr) && (h==hr)) { + for (i=0; icomps[0].data[i]; + buffer[idx+1] = image->comps[1].data[i]; + buffer[idx+2] = image->comps[2].data[i]; + } + } else { + for (i=0; icomps[0].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; + buffer[idx+1] = image->comps[1].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; + buffer[idx+2] = image->comps[2].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; + } + } + } + } + else if (ctx->nb_comp==4) { + if ((image->comps[0].w==image->comps[1].w) && (image->comps[1].w==image->comps[2].w) && (image->comps[2].w==image->comps[3].w) + && (image->comps[0].h==image->comps[1].h) && (image->comps[1].h==image->comps[2].h) && (image->comps[2].h==image->comps[3].h)) { + + if ((w==wr) && (h==hr)) { + for (i=0; icomps[0].data[i]; + buffer[idx+1] = image->comps[1].data[i]; + buffer[idx+2] = image->comps[2].data[i]; + buffer[idx+3] = image->comps[3].data[i]; + } + } else { + for (i=0; icomps[0].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; + buffer[idx+1] = image->comps[1].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; + buffer[idx+2] = image->comps[2].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; + buffer[idx+3] = image->comps[3].data[w * hr - ((i) / (wr) + 1) * w + (i) % (wr)]; + } + } + } + } + + opj_image_destroy(image); + image = NULL; + + if (gf_filter_pck_get_seek_flag(pck)) { + gf_filter_pck_discard(pck_dst); + } else { + gf_filter_pck_merge_properties(pck, pck_dst); + gf_filter_pck_send(pck_dst); + } + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + +static GF_Err j2kdec_initialize(GF_Filter *filter) +{ +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + error_callback(NULL, NULL); + warning_callback(NULL, NULL); + info_callback(NULL, NULL); +#if OPENJP2 + j2kdec_stream_skip(0, NULL); +#endif + } +#endif + return GF_OK; +} + +static const GF_FilterCapability J2KCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_J2K), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +GF_FilterRegister J2KRegister = { + .name = "j2kdec", +#ifdef OPENJPEG_VERSION + .version = ""OPENJPEG_VERSION, +#elif OPENJP2 + .version = "2.x", +#else + .version = "1.x", +#endif + GF_FS_SET_DESCRIPTION("OpenJPEG2000 decoder") + GF_FS_SET_HELP("This filter decodes JPEG2000 streams through OpenJPEG2000 library.") + .private_size = sizeof(GF_J2KCtx), + .priority = 1, + SETCAPS(J2KCaps), + .initialize = j2kdec_initialize, + .configure_pid = j2kdec_configure_pid, + .process = j2kdec_process, +}; + +#endif + +const GF_FilterRegister *j2kdec_register(GF_FilterSession *session) +{ +#ifdef GPAC_HAS_JP2 + return &J2KRegister; +#else + return NULL; +#endif +} + diff --git a/src/filters/dec_laser.c b/src/filters/dec_laser.c new file mode 100644 index 0000000..fb93a52 --- /dev/null +++ b/src/filters/dec_laser.c @@ -0,0 +1,273 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2017 + * All rights reserved + * + * This file is part of GPAC / LASeR decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include + +#ifndef GPAC_DISABLE_LASER + +#include +#include +#include + +typedef struct +{ + GF_Scene *scene; + GF_ObjectManager *odm; +// GF_Terminal *app; + GF_LASeRCodec *codec; + u32 PL, nb_streams; + Bool is_playing; + GF_FilterPid *out_pid; +} GF_LSRDecCtx; + +static GF_Err lsrdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_FilterPid *out_pid; + u32 ESID=0; + GF_LSRDecCtx *ctx = gf_filter_get_udta(filter); + const GF_PropertyValue *prop; + + //we must have streamtype SCENE + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (!prop || (prop->value.uint != GF_STREAM_SCENE)) { + return GF_NOT_SUPPORTED; + } + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!prop || (prop->value.uint != GF_CODECID_LASER) ) { + return GF_NOT_SUPPORTED; + } + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ESID); + if (prop) ESID = prop->value.uint; + else { + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + if (prop) ESID = prop->value.uint; + } + + //we must have a dsi + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (!prop || !prop->value.data.ptr || !prop->value.data.size) { + return GF_NON_COMPLIANT_BITSTREAM; + } + + if (is_remove) { + out_pid = gf_filter_pid_get_udta(pid); + if (ctx->out_pid==out_pid) + ctx->out_pid = NULL; + gf_filter_pid_remove(out_pid); + ctx->nb_streams--; + if (ctx->codec && ESID) { + return gf_laser_decoder_remove_stream(ctx->codec, ESID); + } + return GF_OK; + } + //this is a reconfigure + if (gf_filter_pid_get_udta(pid)) { + gf_laser_decoder_remove_stream(ctx->codec, ESID); + return gf_laser_decoder_configure_stream(ctx->codec, ESID, prop->value.data.ptr, prop->value.data.size); + } + + //check our namespace + if (ctx->scene && ! gf_filter_pid_is_filter_in_parents(pid, ctx->scene->root_od->scene_ns->source_filter)) { + return GF_REQUIRES_NEW_INSTANCE; + } + + //declare a new output PID of type scene, codecid RAW + out_pid = gf_filter_pid_new(filter); + ctx->nb_streams++; + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(out_pid, pid); + gf_filter_pid_set_property(out_pid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_udta(pid, out_pid); + + if (!ctx->out_pid) + ctx->out_pid = out_pid; + return GF_OK; +} + +static Bool lsrdec_process_event(GF_Filter *filter, const GF_FilterEvent *com) +{ + u32 count, i; + GF_LSRDecCtx *ctx = gf_filter_get_udta(filter); + //check for scene attach + switch (com->base.type) { + case GF_FEVT_ATTACH_SCENE: + break; + case GF_FEVT_PLAY: + ctx->is_playing = GF_TRUE; + return GF_FALSE; + default: + return GF_FALSE; + } + if (!com->attach_scene.on_pid) return GF_TRUE; + + count = gf_filter_get_ipid_count(filter); + for (i=0; iattach_scene.on_pid) { + if (!ctx->odm) { + ctx->odm = com->attach_scene.object_manager; + ctx->scene = ctx->odm->subscene ? ctx->odm->subscene : ctx->odm->parentscene; + + ctx->codec = gf_laser_decoder_new(ctx->scene->graph); + /*attach the clock*/ + gf_laser_decoder_set_clock(ctx->codec, gf_scene_get_time, ctx->scene); + gf_filter_pid_set_udta(opid, com->attach_scene.object_manager); + lsrdec_configure_pid(filter, ipid, GF_FALSE); + } + return GF_TRUE; + } + } + + return GF_TRUE; +} + +static GF_Err lsrdec_process(GF_Filter *filter) +{ + GF_Err e = GF_OK; + GF_FilterPacket *pck; + const char *data; + u32 size, ESID=0; + Double ts_offset; + u64 now, cts; + u32 obj_time; + u32 i, count; + const GF_PropertyValue *prop; + GF_LSRDecCtx *ctx = gf_filter_get_udta(filter); + GF_Scene *scene = ctx->scene; + + if (!scene) { + if (ctx->is_playing) { + if (ctx->out_pid && gf_laser_decode_has_conditionnals(ctx->codec)) { + gf_filter_pid_set_info(ctx->out_pid, GF_PROP_PID_KEEP_AFTER_EOS, &PROP_BOOL(GF_TRUE)); + } + gf_filter_pid_set_eos(ctx->out_pid); + return GF_EOS; + } + return GF_OK; + } + if (!ctx->codec) return GF_OK; + + + count = gf_filter_get_ipid_count(filter); + for (i=0; ick); + + pck = gf_filter_pid_get_packet(pid); + if (!pck) { + Bool is_eos = gf_filter_pid_is_eos(pid); + if (is_eos) { + if (ctx->out_pid && gf_laser_decode_has_conditionnals(ctx->codec)) { + gf_filter_pid_set_info(ctx->out_pid, GF_PROP_PID_KEEP_AFTER_EOS, &PROP_BOOL(GF_TRUE)); + } + gf_filter_pid_set_eos(opid); + } + continue; + } + data = gf_filter_pck_get_data(pck, &size); + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + if (prop) ESID = prop->value.uint; + + cts = gf_filter_pck_get_cts( pck ); + ts_offset = (Double) cts; + ts_offset /= gf_filter_pck_get_timescale(pck); + + gf_odm_check_buffering(odm, pid); + + + //we still process any frame before our clock time even when buffering + obj_time = gf_clock_time(odm->ck); + if (ts_offset * 1000 > obj_time) { + gf_sc_sys_frame_pending(ctx->scene->compositor, ts_offset, obj_time, filter); + continue; + } + + now = gf_sys_clock_high_res(); + e = gf_laser_decode_au(ctx->codec, ESID, data, size); + now = gf_sys_clock_high_res() - now; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[BIFS] ODM%d #CH%d at %d decoded AU TS %u in "LLU" us\n", odm->ID, ESID, obj_time, cts, now)); + + gf_filter_pid_drop_packet(pid); + + if (e) return e; + if (odm == ctx->odm) + gf_scene_attach_to_compositor(scene); + } + return GF_OK; +} + +static void lsrdec_finalize(GF_Filter *filter) +{ + GF_LSRDecCtx *ctx = gf_filter_get_udta(filter); + if (ctx->codec) gf_laser_decoder_del(ctx->codec); +} + + +static const GF_FilterCapability LSRDecCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_LASER), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + + +GF_FilterRegister LSRDecRegister = { + .name = "lsrdec", + GF_FS_SET_DESCRIPTION("MPEG-4 LASeR decoder") + GF_FS_SET_HELP("This filter decodes MPEG-4 LASeR frames directly into the scene graph of the compositor. It cannot be used to dump LASeR content.") + .private_size = sizeof(GF_LSRDecCtx), + .flags = GF_FS_REG_MAIN_THREAD, + .priority = 1, + SETCAPS(LSRDecCaps), + .finalize = lsrdec_finalize, + .process = lsrdec_process, + .configure_pid = lsrdec_configure_pid, + .process_event = lsrdec_process_event, +}; + +#endif /*GPAC_DISABLE_LASER*/ + +const GF_FilterRegister *lsrdec_register(GF_FilterSession *session) +{ +#ifdef GPAC_DISABLE_LASER + return NULL; +#else + return &LSRDecRegister; +#endif /*GPAC_DISABLE_LASER*/ +} + diff --git a/src/filters/dec_mad.c b/src/filters/dec_mad.c new file mode 100644 index 0000000..2ee81de --- /dev/null +++ b/src/filters/dec_mad.c @@ -0,0 +1,298 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2017 + * All rights reserved + * + * This file is part of GPAC / MP3 libmad decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include + +#ifdef GPAC_HAS_MAD + +#include + +#if defined(_WIN32_WCE) || defined(_WIN64) || defined(__SYMBIAN32__) +#ifndef FPM_DEFAULT +#ifdef GPAC_64_BITS +#define FPM_64BIT +#else +#define FPM_DEFAULT +#endif +#endif +#endif + +#include + +#if !defined(__GNUC__) +# if defined(_WIN32_WCE) || defined (WIN32) +# pragma comment(lib, "libmad") +# endif +#endif + + +typedef struct +{ + GF_FilterPid *ipid, *opid; + + Bool configured; + + u32 sample_rate, num_samples, num_channels; + u32 timescale; + u64 last_cts; + + unsigned char *buffer; + u32 len; + + struct mad_frame frame; + struct mad_stream stream; + struct mad_synth synth; + +} GF_MADCtx; + +static GF_Err maddec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_MADCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + ctx->ipid = NULL; + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + if (ctx->configured) { + mad_stream_finish(&ctx->stream); + mad_frame_finish(&ctx->frame); + mad_synth_finish(&ctx->synth); + } + mad_stream_init(&ctx->stream); + mad_frame_init(&ctx->frame); + mad_synth_init(&ctx->synth); + ctx->configured = GF_TRUE; + + /*we need a frame to init, so use default values*/ + ctx->num_samples = 1152; + ctx->num_channels = 0; + ctx->sample_rate = 0; + ctx->ipid = pid; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); + if (p) ctx->sample_rate = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS); + if (p) ctx->num_channels = p->value.uint; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(GF_AUDIO_FMT_S16) ); + + if (!ctx->buffer) { + ctx->buffer = (unsigned char*)gf_malloc(sizeof(char) * 2*MAD_BUFFER_MDLEN); + } + + if (ctx->sample_rate) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(ctx->sample_rate) ); + + if (ctx->num_channels) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(ctx->num_channels) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CHANNEL_LAYOUT, &PROP_LONGUINT((ctx->num_channels==1) ? GF_AUDIO_CH_FRONT_CENTER : GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT) ); + } + + gf_filter_set_name(filter, "dec_mad:MAD " MAD_VERSION); + + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + return GF_OK; +} + +static void maddec_finalize(GF_Filter *filter) +{ + GF_MADCtx *ctx = gf_filter_get_udta(filter); + + if (ctx->buffer) gf_free(ctx->buffer); + + if (ctx->configured) { + mad_stream_finish(&ctx->stream); + mad_frame_finish(&ctx->frame); + mad_synth_finish(&ctx->synth); + } +} + +/*from miniMad.c*/ +#define MAD_SCALE(ret, s_chan) \ + chan = s_chan; \ + chan += (1L << (MAD_F_FRACBITS - 16)); \ + if (chan >= MAD_F_ONE) \ + chan = MAD_F_ONE - 1; \ + else if (chan < -MAD_F_ONE) \ + chan = -MAD_F_ONE; \ + ret = chan >> (MAD_F_FRACBITS + 1 - 16); \ + +static GF_Err maddec_process(GF_Filter *filter) +{ + mad_fixed_t *left_ch, *right_ch, chan; + u8 *ptr; + u8 *data; + u32 num, in_size; //, outSize=0; + GF_MADCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *dst_pck; + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->ipid); + + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) + gf_filter_pid_set_eos(ctx->opid); + return GF_OK; + } + data = (char *) gf_filter_pck_get_data(pck, &in_size); + + if (ctx->len + in_size > 2*MAD_BUFFER_MDLEN) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[MAD] MAD buffer overflow, truncating\n")); + in_size = 2*MAD_BUFFER_MDLEN - ctx->len; + } + + memcpy(ctx->buffer + ctx->len, data, in_size); + ctx->len += in_size; + +mad_resync: + mad_stream_buffer(&ctx->stream, ctx->buffer, ctx->len); + + if (mad_frame_decode(&ctx->frame, &ctx->stream) == -1) { + if (ctx->stream.error==MAD_ERROR_BUFLEN) { + if (pck) gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[MAD] Decoding failed error %s (%d)\n", mad_stream_errorstr(&ctx->stream), ctx->stream.error ) ); + if (ctx->len==in_size) { + if (pck) gf_filter_pid_drop_packet(ctx->ipid); + return GF_NON_COMPLIANT_BITSTREAM; + } + //try resynchro + memcpy(ctx->buffer, data, in_size); + ctx->len = in_size; + goto mad_resync; + } + + mad_synth_frame(&ctx->synth, &ctx->frame); + + if ((ctx->sample_rate != ctx->synth.pcm.samplerate) || (ctx->num_channels != ctx->synth.pcm.channels) || (ctx->num_samples != ctx->synth.pcm.length)) { + ctx->sample_rate = ctx->synth.pcm.samplerate; + ctx->num_channels = (u8) ctx->synth.pcm.channels; + ctx->num_samples = ctx->synth.pcm.length; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(ctx->sample_rate) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(ctx->num_channels) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CHANNEL_LAYOUT, &PROP_LONGUINT((ctx->num_channels==1) ? GF_AUDIO_CH_FRONT_CENTER : GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT) ); + } + + if (ctx->stream.next_frame) { + ctx->len = (u32) (&ctx->buffer[ctx->len] - ctx->stream.next_frame); + memmove(ctx->buffer, ctx->stream.next_frame, ctx->len); + } + + num = ctx->synth.pcm.length; + left_ch = ctx->synth.pcm.samples[0]; + right_ch = ctx->synth.pcm.samples[1]; + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, num*2*ctx->num_channels, &ptr); + + if (pck) { + ctx->last_cts = gf_filter_pck_get_cts(pck); + ctx->timescale = gf_filter_pck_get_timescale(pck); + gf_filter_pck_merge_properties(pck, dst_pck); + + gf_filter_pid_drop_packet(ctx->ipid); + } + gf_filter_pck_set_cts(dst_pck, ctx->last_cts); + if (ctx->timescale != ctx->sample_rate) { + u64 dur = num * ctx->timescale; + dur /= ctx->sample_rate; + gf_filter_pck_set_duration(dst_pck, (u32) dur); + ctx->last_cts += dur; + } else { + gf_filter_pck_set_duration(dst_pck, num); + ctx->last_cts += num; + } + + + while (num--) { + s32 rs; + MAD_SCALE(rs, (*left_ch++) ); + + *ptr = (rs >> 0) & 0xff; + ptr++; + *ptr = (rs >> 8) & 0xff; + ptr++; + //outSize += 2; + + if (ctx->num_channels == 2) { + MAD_SCALE(rs, (*right_ch++) ); + *ptr = (rs >> 0) & 0xff; + ptr++; + *ptr = (rs >> 8) & 0xff; + ptr++; + //outSize += 2; + } + } + gf_filter_pck_send(dst_pck); + return GF_OK; +} + + +static const GF_FilterCapability MADCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_PART3), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +GF_FilterRegister MADRegister = { + .name = "maddec", + GF_FS_SET_DESCRIPTION("MAD decoder") + GF_FS_SET_HELP("This filter decodes MPEG 1/2 audio streams through libmad library.") + .private_size = sizeof(GF_MADCtx), + .priority = 1, + SETCAPS(MADCaps), + .finalize = maddec_finalize, + .configure_pid = maddec_configure_pid, + .process = maddec_process, +}; + +#endif + +const GF_FilterRegister *maddec_register(GF_FilterSession *session) +{ +#ifdef GPAC_HAS_MAD + return &MADRegister; +#else + return NULL; +#endif +} + diff --git a/src/filters/dec_mediacodec.c b/src/filters/dec_mediacodec.c new file mode 100644 index 0000000..89caac9 --- /dev/null +++ b/src/filters/dec_mediacodec.c @@ -0,0 +1,1278 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2020 + * All rights reserved + * + * This file is part of GPAC / mediacodec decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include + +#if defined(GPAC_CONFIG_ANDROID) && defined(GPAC_HAS_MEDIACODEC) + +#include "dec_mediacodec.h" + +typedef struct +{ + //opts + Bool disable_gl; + + + GF_List *streams; + GF_List *frames_res; + GF_Filter *filter; + GF_FilterPid *opid; + u32 codecid; + u32 cfg_crc; + + AMediaCodec *codec; + AMediaFormat *format; + ANativeWindow *window; + + Bool use_gl_textures; + u32 dequeue_timeout; + u32 width, height, stride, out_size; + u32 pix_fmt; + GF_Fraction sar; + + u32 crop_left, crop_right, crop_top, crop_bottom; + + const char *mime; + u8 chroma_format, luma_bit_depth, chroma_bit_depth; + + Bool is_adaptive; + Bool surface_rendering; + Bool frame_size_changed; + Bool inputEOS, outputEOS; + + GF_MCDecSurfaceTexture surfaceTex; + + //NAL-based specific + GF_List *SPSs, *PPSs, *VPSs; + s32 active_sps, active_pps, active_vps; + u32 nalu_size_length; + Bool inject_xps; + + AVCState avc; + HEVCState hevc; + + u32 decoded_frames_pending; + Bool reconfig_needed; + Bool before_exit_registered; + + //hevc + u32 luma_bpp, chroma_bpp; + u8 chroma_format_idc; + + ssize_t outIndex; + GLuint tex_id; +} GF_MCDecCtx; + +typedef struct { + GF_FilterFrameInterface frame_ifce; + u8 * frame; + GF_MCDecCtx * ctx; + ssize_t outIndex; + Bool flushed; +} GF_MCDecFrame; + +enum { + MCDEC_PPS = 0, + MCDEC_SPS, + MCDEC_VPS, +}; + +#if 0 +u8 sdkInt() +{ + char sdk_str[3] = "0"; + //__system_property_get("ro.build.version.sdk", sdk_str, "0"); + return atoi(sdk_str); +} +#endif + + +void mcdec_init_media_format(GF_MCDecCtx *ctx, AMediaFormat *format) +{ + AMediaFormat_setString(ctx->format, AMEDIAFORMAT_KEY_MIME, ctx->mime); + AMediaFormat_setInt32(ctx->format, AMEDIAFORMAT_KEY_WIDTH, ctx->width); + AMediaFormat_setInt32(ctx->format, AMEDIAFORMAT_KEY_HEIGHT, ctx->height); + AMediaFormat_setInt32(ctx->format, AMEDIAFORMAT_KEY_STRIDE, ctx->stride); + AMediaFormat_setInt32(ctx->format, AMEDIAFORMAT_KEY_MAX_INPUT_SIZE, ctx->width * ctx->height); +} + + +static void mcdec_reset_ps_list(GF_List *list) +{ + while (list && gf_list_count(list)) { + GF_AVCConfigSlot *slc = gf_list_get(list, 0); + gf_free(slc->data); + gf_free(slc); + gf_list_rem(list, 0); + } +} + + +GF_Err mcdec_init_avc_dec(GF_MCDecCtx *ctx) +{ + s32 idx; + u32 i; + GF_AVCConfigSlot *sps = NULL; + GF_AVCConfigSlot *pps = NULL; + + for (i=0; iSPSs); i++) { + sps = gf_list_get(ctx->SPSs, i); + if (ctx->active_sps<0) ctx->active_sps = sps->id; + + if (sps->id==ctx->active_sps) break; + sps = NULL; + } + if (!sps) return GF_NON_COMPLIANT_BITSTREAM; + for (i=0; iPPSs); i++) { + pps = gf_list_get(ctx->PPSs, i); + if (ctx->active_pps<0) ctx->active_pps = pps->id; + if (pps->id==ctx->active_pps) break; + pps = NULL; + } + if (!pps) return GF_NON_COMPLIANT_BITSTREAM; + ctx->reconfig_needed = GF_FALSE; + ctx->inject_xps = GF_TRUE; + + idx = ctx->active_sps; + ctx->width = ctx->avc.sps[idx].width; + ctx->height = ctx->avc.sps[idx].height; + ctx->crop_left = ctx->avc.sps[idx].crop.left; + ctx->crop_right = ctx->avc.sps[idx].crop.right; + ctx->crop_top = ctx->avc.sps[idx].crop.top; + ctx->crop_bottom = ctx->avc.sps[idx].crop.bottom; + ctx->crop_left = ctx->avc.sps[idx].crop.left; + ctx->width += ctx->crop_left + ctx->crop_right; + ctx->height += ctx->crop_top + ctx->crop_bottom; + ctx->out_size = ctx->height * ctx->width * 3/2 ; + + if (ctx->avc.sps[idx].vui.par_num && ctx->avc.sps[idx].vui.par_den) { + ctx->sar.num = ctx->avc.sps[idx].vui.par_num; + ctx->sar.den = ctx->avc.sps[idx].vui.par_den; + } + ctx->chroma_format = ctx->avc.sps[idx].chroma_format; + ctx->luma_bit_depth = 8 + ctx->avc.sps[idx].luma_bit_depth_m8; + ctx->chroma_bit_depth = 8 + ctx->avc.sps[idx].chroma_bit_depth_m8; + switch (ctx->chroma_format) { + case 2: + if (ctx->luma_bit_depth>8) { + ctx->pix_fmt = GF_PIXEL_YUV422_10; + } else { + ctx->pix_fmt = GF_PIXEL_YUV422; + } + break; + case 3: + if (ctx->luma_bit_depth>8) { + ctx->pix_fmt = GF_PIXEL_YUV444_10; + } else { + ctx->pix_fmt = GF_PIXEL_YUV444; + } + break; + default: + if (ctx->luma_bit_depth>8) { + ctx->pix_fmt = GF_PIXEL_YUV_10; + } + break; + } + ctx->frame_size_changed = GF_TRUE; + return GF_OK; +} + + +GF_Err mcdec_init_m4vp2_dec(GF_MCDecCtx *ctx) +{ + GF_ESD *esd; + GF_M4VDecSpecInfo vcfg; + GF_BitStream *bs; + u8 *dsi_data; + u32 dsi_size; + GF_FilterPid *ipid = gf_list_get(ctx->streams, 0); + const GF_PropertyValue *dcd = gf_filter_pid_get_property(ipid, GF_PROP_PID_DECODER_CONFIG); + + if (!dcd) return GF_NON_COMPLIANT_BITSTREAM; + + + gf_m4v_get_config(dcd->value.data.ptr, dcd->value.data.size, &vcfg); + ctx->width = vcfg.width; + ctx->height = vcfg.height; + + esd = gf_odf_desc_esd_new(2); + esd->decoderConfig->decoderSpecificInfo->data = dcd->value.data.ptr; + esd->decoderConfig->decoderSpecificInfo->dataLength = dcd->value.data.size; + + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + gf_bs_write_u32(bs, 1); + gf_odf_desc_write_bs((GF_Descriptor *) esd, bs); + gf_bs_get_content(bs, &dsi_data, &dsi_size); + gf_bs_del(bs); + + ctx->mime = "video/mp4v-es"; + AMediaFormat_setBuffer(ctx->format, "csd-0", dsi_data, dsi_size); + + gf_free(dsi_data); + gf_odf_desc_del((GF_Descriptor*)esd); + return GF_OK; +} + + +GF_Err mcdec_init_hevc_dec(GF_MCDecCtx *ctx) +{ + + s32 idx; + + u32 i; + GF_AVCConfigSlot *sps = NULL; + GF_AVCConfigSlot *pps = NULL; + GF_AVCConfigSlot *vps = NULL; + + for (i=0; iSPSs); i++) { + sps = gf_list_get(ctx->SPSs, i); + if (ctx->active_sps<0) ctx->active_sps = sps->id; + + if (sps->id==ctx->active_sps) break; + sps = NULL; + } + if (!sps) return GF_NON_COMPLIANT_BITSTREAM; + for (i=0; iPPSs); i++) { + pps = gf_list_get(ctx->PPSs, i); + if (ctx->active_pps<0) ctx->active_pps = pps->id; + + if (pps->id==ctx->active_pps) break; + pps = NULL; + } + for (i=0; iVPSs); i++) { + vps = gf_list_get(ctx->VPSs, i); + if (ctx->active_vps<0) ctx->active_vps = vps->id; + + if (vps->id==ctx->active_vps) break; + vps = NULL; + } + if (!vps) return GF_NON_COMPLIANT_BITSTREAM; + ctx->reconfig_needed = GF_FALSE; + ctx->inject_xps = GF_TRUE; + + idx = ctx->active_sps; + ctx->width = ctx->hevc.sps[idx].width; + ctx->height = ctx->hevc.sps[idx].height; + ctx->luma_bpp = ctx->hevc.sps[idx].bit_depth_luma; + ctx->chroma_bpp = ctx->hevc.sps[idx].bit_depth_chroma; + ctx->chroma_format_idc = ctx->hevc.sps[idx].chroma_format_idc; + + ctx->stride = ((ctx->luma_bpp==8) && (ctx->chroma_bpp==8)) ? ctx->width : ctx->width * 2; + if ( ctx->chroma_format_idc == 1) { // 4:2:0 + ctx->out_size = ctx->stride * ctx->height * 3 / 2; + } + else if ( ctx->chroma_format_idc == 2) { // 4:2:2 + ctx->out_size = ctx->stride * ctx->height * 2 ; + } + else if ( ctx->chroma_format_idc == 3) { // 4:4:4 + ctx->out_size = ctx->stride * ctx->height * 3; + } + else { + return GF_NOT_SUPPORTED; + } + + switch (ctx->chroma_format) { + case 2: + if (ctx->luma_bit_depth>8) { + ctx->pix_fmt = GF_PIXEL_YUV422_10; + } else { + ctx->pix_fmt = GF_PIXEL_YUV422; + } + break; + case 3: + if (ctx->luma_bit_depth>8) { + ctx->pix_fmt = GF_PIXEL_YUV444_10; + } else { + ctx->pix_fmt = GF_PIXEL_YUV444; + } + break; + default: + if (ctx->luma_bit_depth>8) { + ctx->pix_fmt = GF_PIXEL_NV12; + } + break; + } + + ctx->frame_size_changed = GF_TRUE; + return GF_OK; +} + + +static GF_Err mcdec_init_decoder(GF_MCDecCtx *ctx) { + + GF_Err err; + + ctx->format = AMediaFormat_new(); + + if(!ctx->format) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[MCDec] AMediaFormat_new failed\n")); + return GF_FILTER_NOT_FOUND; + } + + ctx->sar.num = ctx->sar.den = 0; + ctx->pix_fmt = GF_PIXEL_NV12; + + switch (ctx->codecid) { + case GF_CODECID_AVC : + err = mcdec_init_avc_dec(ctx); + break; + case GF_CODECID_MPEG4_PART2 : + err = mcdec_init_m4vp2_dec(ctx); + break; + case GF_CODECID_HEVC: + err = mcdec_init_hevc_dec(ctx); + break; + default: + return GF_NOT_SUPPORTED; + } + + if (err != GF_OK) { + return err; + } + + ctx->dequeue_timeout = 5000; + ctx->stride = ctx->width; + mcdec_init_media_format(ctx, ctx->format); + + if (!ctx->codec) { + char *decoder_name = mcdec_find_decoder(ctx->mime, ctx->width, ctx->height, &ctx->is_adaptive); + if(!decoder_name) return GF_PROFILE_NOT_SUPPORTED; + + ctx->codec = AMediaCodec_createCodecByName(decoder_name); + gf_free(decoder_name); + } + + if (!ctx->codec) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[MCDec] AMediaCodec_createDecoderByType failed\n")); + return GF_FILTER_NOT_FOUND; + } + + if (ctx->disable_gl) { + ctx->surface_rendering = 0; + } else if (!ctx->window) { + if(mcdec_create_surface(ctx->tex_id, &ctx->window, &ctx->surface_rendering, &ctx->surfaceTex) != GF_OK) + return GF_BAD_PARAM; + } + + //TODO add support for crypto + if( AMediaCodec_configure(ctx->codec, ctx->format, (ctx->surface_rendering) ? ctx->window : NULL, NULL, 0) != AMEDIA_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[MCDec] AMediaCodec_configure failed\n")); + return GF_BAD_PARAM; + } + + if( AMediaCodec_start(ctx->codec) != AMEDIA_OK){ + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC , ("[MCDec] AMediaCodec_start failed\n")); + return GF_BAD_PARAM; + } + + ctx->inputEOS = GF_FALSE; + ctx->outputEOS = GF_FALSE; + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[MCDec] Video size: %d x %d\n", ctx->width, ctx->height)); + + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->width) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->height) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->stride) ); + if (ctx->sar.den) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, &PROP_FRAC(ctx->sar) ); + + if (ctx->surface_rendering) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, &PROP_UINT(GF_PIXEL_GL_EXTERNAL) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT_WRAPPED, &PROP_UINT(ctx->pix_fmt) ); + } else { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, &PROP_UINT(ctx->pix_fmt) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT_WRAPPED, NULL ); + } + + switch (ctx->codecid) { + case GF_CODECID_AVC: + gf_filter_set_name(ctx->filter, "MediaCodec hardware AVC|H264"); + break; + case GF_CODECID_HEVC: + gf_filter_set_name(ctx->filter, "MediaCodec hardware HEVC|H265"); + break; + case GF_CODECID_MPEG4_PART2: + gf_filter_set_name(ctx->filter, "MediaCodec hardware MPEG-4 Part2"); + break; + default: + gf_filter_set_name(ctx->filter, "MediaCodec unsupported"); + break; + } + return GF_OK; +} + +static void mcdec_register_avc_param_set(GF_MCDecCtx *ctx, u8 *data, u32 size, u8 xps) +{ + Bool add = GF_TRUE; + u32 i, count; + s32 ps_id; + GF_List *dest = (xps == MCDEC_SPS) ? ctx->SPSs : ctx->PPSs; + + if (xps == MCDEC_SPS) { + ps_id = gf_media_avc_read_sps(data, size, &ctx->avc, 0, NULL); + if (ps_id<0) return; + } + else { + ps_id = gf_media_avc_read_pps(data, size, &ctx->avc); + if (ps_id<0) return; + } + count = gf_list_count(dest); + for (i = 0; iid != ps_id) continue; + //not same size or different content but same ID, remove old xPS + if ((a_slc->size != size) || memcmp(a_slc->data, data, size)) { + gf_free(a_slc->data); + gf_free(a_slc); + gf_list_rem(dest, i); + break; + } + else { + add = GF_FALSE; + } + break; + } + if (add) { + GF_AVCConfigSlot *slc; + GF_SAFEALLOC(slc, GF_AVCConfigSlot); + if (!slc) return; + slc->data = gf_malloc(size); + if (!slc->data) { + gf_free(slc); + return; + } + memcpy(slc->data, data, size); + slc->size = size; + slc->id = ps_id; + gf_list_add(dest, slc); + + //force re-activation of sps/pps + if (xps == MCDEC_SPS) ctx->active_sps = -1; + else ctx->active_pps = -1; + } +} + +static void mcdec_register_hevc_param_set(GF_MCDecCtx *ctx, u8 *data, u32 size, u8 xps) +{ + Bool add = GF_TRUE; + u32 i, count; + s32 ps_id; + GF_List *dest = NULL; + + switch(xps) { + case MCDEC_SPS: + dest = ctx->SPSs; + ps_id = gf_media_hevc_read_sps(data, size, &ctx->hevc); + if (ps_id<0) return; + break; + case MCDEC_PPS: + dest = ctx->PPSs; + ps_id = gf_media_hevc_read_pps(data, size, &ctx->hevc); + if (ps_id<0) return; + break; + case MCDEC_VPS: + dest = ctx->VPSs; + ps_id = gf_media_hevc_read_vps(data, size, &ctx->hevc); + if (ps_id<0) return; + break; + default: + break; + } + + count = gf_list_count(dest); + for (i = 0; iid != ps_id) continue; + //not same size or different content but same ID, remove old xPS + if ((a_slc->size != size) || memcmp(a_slc->data, data, size)) { + gf_free(a_slc->data); + gf_free(a_slc); + gf_list_rem(dest, i); + break; + } + else { + add = GF_FALSE; + } + break; + } + if (add && dest) { + GF_AVCConfigSlot *slc; + GF_SAFEALLOC(slc, GF_AVCConfigSlot); + if (!slc) return; + slc->data = gf_malloc(size); + if (!slc->data) { + gf_free(slc); + return; + } + memcpy(slc->data, data, size); + slc->size = size; + slc->id = ps_id; + gf_list_add(dest, slc); + + //force re-activation of sps/pps/vps + if (xps == MCDEC_SPS) ctx->active_sps = -1; + else if (xps == MCDEC_PPS) ctx->active_pps = -1; + else ctx->active_vps = -1; + } +} + +static GF_Err mcdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p, *dsi; + const GF_PropertyValue *dcd; + u32 codecid, dsi_crc; + Bool do_reset=GF_FALSE; + u32 i; + GF_FilterPid *base_pid = NULL; + GF_MCDecCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + gf_list_del_item(ctx->streams, pid); + if (!gf_list_count(ctx->streams)) { + if (ctx->codec) AMediaCodec_stop(ctx->codec); + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) { + return GF_NOT_SUPPORTED; + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[MCDec] Missing codecid, cannot initialize\n")); + return GF_NOT_SUPPORTED; + } + codecid = p->value.uint; + + base_pid = gf_list_get(ctx->streams, 0); + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DEPENDENCY_ID); + if (!p && base_pid && (base_pid != pid)) return GF_REQUIRES_NEW_INSTANCE; + else if (p) { + u32 base_idx_plus_one = 0; + + if (ctx->codecid != GF_CODECID_HEVC) return GF_REQUIRES_NEW_INSTANCE; + + for (i=0; istreams); i++) { + GF_FilterPid *ipid = gf_list_get(ctx->streams, i); + const GF_PropertyValue *p_dep; + if (ipid==pid) continue; + + p_dep = gf_filter_pid_get_property(ipid, GF_PROP_PID_ID); + if (p_dep && p_dep->value.uint == p->value.uint) { + base_idx_plus_one = i+1; + break; + } + } + if (!base_idx_plus_one) return GF_REQUIRES_NEW_INSTANCE; + + //no support for L-HEVC + if (codecid != GF_CODECID_HEVC) return GF_NOT_SUPPORTED; + if (gf_list_find(ctx->streams, pid) < 0) { + gf_list_insert(ctx->streams, pid, base_idx_plus_one); + } + //no configure for temporal enhancements + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (ctx->opid && p) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FPS, p); + return GF_OK; + } + + + dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + dsi_crc = dsi ? gf_crc_32(dsi->value.data.ptr, dsi->value.data.size) : 0; + if ((codecid==ctx->codecid) && (dsi_crc == ctx->cfg_crc)) return GF_OK; + + if (gf_list_find(ctx->streams, pid) < 0) { + gf_list_insert(ctx->streams, pid, 0); + } + ctx->cfg_crc = dsi_crc; + + gf_filter_set_max_extra_input_pids(filter, (codecid==GF_CODECID_HEVC) ? 5 : 0); + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + } + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, pid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL ); + + dcd = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + //not ready yet + if (!dcd) return GF_OK; + + if (codecid!=ctx->codecid) do_reset = GF_TRUE; + else if (!ctx->is_adaptive) do_reset = GF_TRUE; + else ctx->inject_xps = GF_TRUE; + + ctx->codecid = codecid; + + if (do_reset && ctx->codec) { + AMediaCodec_delete(ctx->codec); + ctx->codec = NULL; + mcdec_reset_ps_list(ctx->SPSs); + mcdec_reset_ps_list(ctx->VPSs); + mcdec_reset_ps_list(ctx->PPSs); + + if (ctx->format) AMediaFormat_delete(ctx->format); + ctx->format = NULL; + } + + if (!ctx->tex_id) + glGenTextures(1, &ctx->tex_id); + + //check AVC config + if (ctx->codecid == GF_CODECID_AVC) { + GF_AVCConfig *cfg; + GF_AVCConfigSlot *slc; + + ctx->mime = "video/avc"; + ctx->avc.sps_active_idx = -1; + ctx->active_sps = ctx->active_pps = -1; + + cfg = gf_odf_avc_cfg_read(dcd->value.data.ptr, dcd->value.data.size); + for (i = 0; isequenceParameterSets); i++) { + slc = gf_list_get(cfg->sequenceParameterSets, i); + slc->id = -1; + mcdec_register_avc_param_set(ctx, slc->data, slc->size, MCDEC_SPS); + } + + for (i = 0; ipictureParameterSets); i++) { + slc = gf_list_get(cfg->pictureParameterSets, i); + slc->id = -1; + mcdec_register_avc_param_set(ctx, slc->data, slc->size, MCDEC_PPS); + } + //activate first SPS/PPS by default + slc = gf_list_get(ctx->SPSs, 0); + if (slc) ctx->active_sps = slc->id; + + slc = gf_list_get(ctx->PPSs, 0); + if (slc) ctx->active_pps = slc->id; + + ctx->nalu_size_length = cfg->nal_unit_size; + gf_odf_avc_cfg_del(cfg); + } + else if (codecid == GF_CODECID_MPEG4_PART2) { + GF_M4VDecSpecInfo vcfg; + gf_m4v_get_config(dcd->value.data.ptr, dcd->value.data.size, &vcfg); + ctx->width = vcfg.width; + ctx->height = vcfg.height; + ctx->out_size = ctx->width*ctx->height*3/2; + ctx->pix_fmt = GF_PIXEL_NV12; + } + else if (codecid == GF_CODECID_HEVC) { + ctx->mime = "video/hevc"; + GF_HEVCConfig *hvcc; + GF_AVCConfigSlot *sl; + HEVCState hevc; + u32 j; + + memset(&hevc, 0, sizeof(HEVCState)); + + hvcc = gf_odf_hevc_cfg_read(dcd->value.data.ptr, dcd->value.data.size, GF_FALSE); + if (!hvcc) return GF_NON_COMPLIANT_BITSTREAM; + ctx->nalu_size_length = hvcc->nal_unit_size; + + for (i=0; i< gf_list_count(hvcc->param_array); i++) { + GF_HEVCParamArray *ar = (GF_HEVCParamArray *)gf_list_get(hvcc->param_array, i); + for (j=0; j< gf_list_count(ar->nalus); j++) { + sl = (GF_AVCConfigSlot *)gf_list_get(ar->nalus, j); + + if (ar->type==GF_HEVC_NALU_SEQ_PARAM) { + mcdec_register_hevc_param_set(ctx, sl->data, sl->size, MCDEC_SPS); + } else if (ar->type==GF_HEVC_NALU_VID_PARAM) { + mcdec_register_hevc_param_set(ctx, sl->data, sl->size, MCDEC_VPS); + } else if (ar->type==GF_HEVC_NALU_PIC_PARAM) { + mcdec_register_hevc_param_set(ctx, sl->data, sl->size, MCDEC_PPS); + } + } + } + //activate first VPS/SPS/PPS by default + sl = gf_list_get(ctx->VPSs, 0); + if (sl) ctx->active_vps = sl->id; + + sl = gf_list_get(ctx->SPSs, 0); + if (sl) ctx->active_sps = sl->id; + + sl = gf_list_get(ctx->PPSs, 0); + if (sl) ctx->active_pps = sl->id; + + gf_odf_hevc_cfg_del(hvcc); + } + if (ctx->codec) return GF_OK; + return mcdec_init_decoder(ctx); +} + +static void mcdec_write_ps(GF_BitStream *bs, GF_List *ps, s32 active_ps, Bool all) +{ + u32 i, count; + if (!all && (active_ps<0)) return; + count = gf_list_count(ps); + for (i=0; iid==active_ps)) { + gf_bs_write_u32(bs, 1); + gf_bs_write_data(bs, sl->data, sl->size); + if (!all) return; + } + } +} + + +static GF_Err mcdec_rewrite_annex_b(GF_MCDecCtx *ctx, u8 *inBuffer, u32 inBufferLength, u8 **out_buffer, u32 *out_size) +{ + u32 i; + u8 *ptr = inBuffer; + u32 nal_size; + GF_Err e = GF_OK; + GF_BitStream *bs = NULL; + + *out_buffer = NULL; + *out_size = 0; + + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + if (!bs) return GF_OUT_OF_MEM; + + if (ctx->inject_xps) { + ctx->inject_xps = GF_FALSE; + mcdec_write_ps(bs, ctx->VPSs, ctx->active_vps, ctx->is_adaptive); + mcdec_write_ps(bs, ctx->SPSs, ctx->active_sps, ctx->is_adaptive); + mcdec_write_ps(bs, ctx->PPSs, ctx->active_pps, ctx->is_adaptive); + } + + while (inBufferLength) { + nal_size = 0; + for (i = 0; inalu_size_length; i++) { + nal_size = (nal_size << 8) + ((u8)ptr[i]); + } + ptr += ctx->nalu_size_length; + + gf_bs_write_u32(bs, 1); + gf_bs_write_data(bs, ptr, nal_size); + + + ptr += nal_size; + + if (inBufferLength < nal_size + ctx->nalu_size_length) break; + inBufferLength -= nal_size + ctx->nalu_size_length; + } + + gf_bs_get_content(bs, out_buffer, out_size); + gf_bs_del(bs); + return e; +} + +static GF_Err mcdec_check_ps_state(GF_MCDecCtx *ctx, u8 *inBuffer, u32 inBufferLength) +{ + u32 nal_size; + GF_Err e = GF_OK; + GF_BitStream *bs = gf_bs_new(inBuffer, inBufferLength, GF_BITSTREAM_READ); + + + while (gf_bs_available(bs)) { + u8 nal_type; + u32 pos; + //turn off epb removal when reading nal size + gf_bs_enable_emulation_byte_removal(bs, GF_FALSE); + nal_size = gf_bs_read_int(bs, ctx->nalu_size_length * 8); + pos = gf_bs_get_position(bs); + + if (ctx->codecid==GF_CODECID_AVC) { + //this will turn it back on + gf_media_avc_parse_nalu (bs, &ctx->avc); + gf_bs_seek(bs, pos + nal_size); + + nal_type = ctx->avc.last_nal_type_parsed; + switch (nal_type) { + case GF_AVC_NALU_SEQ_PARAM: + case GF_AVC_NALU_PIC_PARAM: + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[MCDec] AVC Parameter sets still present in packet payload !\n")); + break; + default: + if ((nal_type <= GF_AVC_NALU_IDR_SLICE) && ctx->avc.s_info.sps) { + if (ctx->avc.sps_active_idx != ctx->active_sps) { + ctx->reconfig_needed = GF_TRUE; + ctx->active_sps = ctx->avc.sps_active_idx; + ctx->active_pps = ctx->avc.s_info.pps->id; + gf_bs_del(bs); + return GF_OK; + } + } + break; + } + } else { + u8 quality_id, temporal_id; + gf_media_hevc_parse_nalu_bs (bs, &ctx->hevc, &nal_type, &temporal_id, &quality_id); + gf_bs_seek(bs, pos + nal_size); + + switch(nal_type) { + case GF_HEVC_NALU_SEQ_PARAM: + case GF_HEVC_NALU_PIC_PARAM: + case GF_HEVC_NALU_VID_PARAM: + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[MCDec] HEVC Parameter sets still present in packet payload !\n")); + break; + default: + if ((nal_type <= GF_HEVC_NALU_SLICE_CRA) && ctx->hevc.s_info.sps) { + if ((ctx->hevc.sps_active_idx != ctx->active_sps) || (ctx->hevc.sps[ctx->active_sps].vps_id != ctx->active_vps)) { + ctx->reconfig_needed = GF_TRUE; + ctx->active_sps = ctx->hevc.sps_active_idx; + ctx->active_pps = ctx->hevc.s_info.pps->id; + ctx->active_vps = ctx->hevc.sps[ctx->active_sps].vps_id; + return GF_OK; + } + } + break; + } + } + } + gf_bs_del(bs); + return e; +} + +static GF_Err mcdec_send_frame(GF_MCDecCtx *ctx, u8 *frame_buffer, u64 cts); + +static GF_Err mcdec_process(GF_Filter *filter) +{ + u64 cts=0; + u8 *in_buffer; + u32 in_buffer_size, i, count, nb_eos; + u64 min_dts; + GF_Err e; + GF_MCDecCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + GF_FilterPid *ref_pid = NULL; + u8 *dec_frame=NULL; + + Bool mcdec_buffer_available = GF_FALSE; + +#if FILTER_FIXME + if (!ctx->before_exit_registered) { + ctx->before_exit_registered = GF_TRUE; + if (gf_register_before_exit_function(gf_th_current(), &mcdec_exit_callback) != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[MCDec] Failed to register exit function for the decoder thread %p, try to continue anyway...\n", gf_th_current())); + } + } +#endif + + + //figure out min DTS + count = gf_list_count(ctx->streams); + nb_eos = 0; + min_dts = 0; + for (i=0; istreams, i); + + pck = gf_filter_pid_get_packet(pid); + if (!pck) { + if (gf_filter_pid_is_eos(pid)) { + nb_eos++; + continue; + } else { + return GF_OK; + } + } + dts = gf_filter_pck_get_dts(pck); + dts *= 1000; + dts /= gf_filter_pck_get_timescale(pck); + if (!min_dts || (min_dts>dts)) { + min_dts = dts; + ref_pid = pid; + } + } + + if (nb_eos==count) { + in_buffer = NULL; + in_buffer_size = 0; + } else { + assert(ref_pid); + pck = gf_filter_pid_get_packet(ref_pid); + assert(pck); + + in_buffer = (u8 *) gf_filter_pck_get_data(pck, &in_buffer_size); + cts = gf_filter_pck_get_cts(pck); + + //no reconfig but no adaptive, parse NALUs to figure out active ps + if (!ctx->reconfig_needed && !ctx->is_adaptive) { + mcdec_check_ps_state(ctx, in_buffer, in_buffer_size); + } + + if (ctx->reconfig_needed) { + if (ctx->codec) { + AMediaCodec_flush(ctx->codec); + AMediaCodec_stop(ctx->codec); + } + e = mcdec_init_decoder(ctx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[MCDec] Failed to reconfigure decoder: %s\n", gf_error_to_string(e) )); + return e; + } + } + } + + if (!ctx->inputEOS) { + ssize_t inIndex = AMediaCodec_dequeueInputBuffer(ctx->codec, ctx->dequeue_timeout); + + if (inIndex >= 0) { + size_t inSize; + u32 flags = 0; + Bool do_free = GF_TRUE; + u8 *in_data=NULL; + u32 in_data_size=0; + u8 *buffer = (u8 *)AMediaCodec_getInputBuffer(ctx->codec, inIndex, &inSize); + + //rewrite input from isobmf to annexB - this will write the param sets if needed + if (in_buffer) { + mcdec_rewrite_annex_b(ctx, in_buffer, in_buffer_size, &in_data, &in_data_size); + + if (!in_data) { + in_data = in_buffer; + in_data_size = in_buffer_size; + do_free = GF_FALSE; + } + } + + if (in_data_size > inSize) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[MCDec] The returned buffer is too small\n")); + if (do_free) gf_free(in_data); + return GF_OK; + } + + if (in_data_size) { + memcpy(buffer, in_data, in_data_size); + gf_filter_pid_drop_packet(ref_pid); + if (do_free) gf_free(in_data); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[MCDec] AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM input\n")); + ctx->inputEOS = GF_TRUE; + flags = AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM; + } + + if (AMediaCodec_queueInputBuffer(ctx->codec, inIndex, 0, in_data_size, cts, flags) != AMEDIA_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[MCDec] AMediaCodec_queueInputBuffer failed\n")); + return GF_BAD_PARAM; + } + mcdec_buffer_available = GF_TRUE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[MCDec] AMediaCodec_queueInputBuffer OK, %d bytes queued (in EOS %d)\n", in_data_size, ctx->inputEOS)); + } + } + + if (!ctx->outputEOS) { + u32 width, height, stride; + AMediaCodecBufferInfo info; + + ctx->outIndex = AMediaCodec_dequeueOutputBuffer(ctx->codec, &info, ctx->dequeue_timeout); + + switch(ctx->outIndex) { + case AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED: + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[MCDec] AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED\n")); + ctx->format = AMediaCodec_getOutputFormat(ctx->codec); + AMediaFormat_getInt32(ctx->format, AMEDIAFORMAT_KEY_WIDTH, &width); + AMediaFormat_getInt32(ctx->format, AMEDIAFORMAT_KEY_HEIGHT, &height); + AMediaFormat_getInt32(ctx->format, AMEDIAFORMAT_KEY_STRIDE, &stride); + if((ctx->width != width) || (ctx->height != height) || (ctx->stride != stride)){ + ctx->out_size = ctx->stride * ctx->height * 3 / 2; + ctx->frame_size_changed = GF_TRUE; + } + break; + + case AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED: + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[MCDec] AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED\n")); + break; + + case AMEDIACODEC_INFO_TRY_AGAIN_LATER: + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[MCDec] AMEDIACODEC_INFO_TRY_AGAIN_LATER\n")); + break; + + default: + if (ctx->outIndex < 0) + break; + + dec_frame = NULL; + + if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) { + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[MCDec] AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM output\n")); + ctx->outputEOS = GF_TRUE; + } + + if (!ctx->surface_rendering) { + size_t outSize; + uint8_t * buffer = AMediaCodec_getOutputBuffer(ctx->codec, ctx->outIndex, &outSize); + dec_frame = buffer + info.offset; + + if (!dec_frame) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[MCDec] AMediaCodec_getOutputBuffer failed\n")); + break; + } + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[MCDec] got output buffer CTS "LLU" (out EOS %d)\n", info.presentationTimeUs, ctx->outputEOS)); + + e = mcdec_send_frame(ctx, dec_frame, info.presentationTimeUs); + if (e) return e; + break; + } + } + + if (ctx->outputEOS) gf_filter_pid_set_eos(ctx->opid); + return GF_OK; +} + +static void mcdec_hw_del(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck) +{ + GF_FilterFrameInterface *frame_ifce = (GF_FilterFrameInterface *) gf_filter_pck_get_frame_interface(pck); + GF_MCDecFrame *f = frame_ifce ? frame_ifce->user_data : NULL; + if (!f) return; + + if (f->ctx->codec && !f->flushed) { + if ( AMediaCodec_releaseOutputBuffer(f->ctx->codec, f->outIndex, GF_FALSE) != AMEDIA_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[MCDec] NOT Release Output Buffer Index: %d\n", f->outIndex)); + } + } + f->ctx->decoded_frames_pending--; + gf_list_add(f->ctx->frames_res, f); +} + +GF_Err mcdec_hw_get_plane(GF_FilterFrameInterface *frame, u32 plane_idx, const u8 **outPlane, u32 *outStride) +{ + GF_MCDecFrame *f = (GF_MCDecFrame *)frame->user_data; + if (! outPlane || !outStride) return GF_BAD_PARAM; + *outPlane = NULL; + *outStride = 0; + f->ctx->format = AMediaCodec_getOutputFormat(f->ctx->codec); + + AMediaFormat_getInt32(f->ctx->format, AMEDIAFORMAT_KEY_STRIDE, outStride); + + switch(plane_idx) { + case 0 : + *outPlane = f->frame ; + break; + case 1 : + *outPlane = f->frame + *outStride * f->ctx->height ; + *outStride/=2; + break; + case 2: + *outPlane = f->frame + 5 * *outStride * f->ctx->height/4 ; + *outStride/=2; + break; + default : + return GF_BAD_PARAM; + } + + return GF_OK; +} + +GF_Err mcdec_hw_get_gl_texture(GF_FilterFrameInterface *frame, u32 plane_idx, u32 *gl_tex_format, u32 *gl_tex_id, GF_Matrix * texcoordmatrix) +{ + GF_MCDecFrame *f = (GF_MCDecFrame *)frame->user_data; + if (!gl_tex_format || !gl_tex_id) return GF_BAD_PARAM; +#ifndef MEDIACODEC_EMUL_API + *gl_tex_format = GL_TEXTURE_EXTERNAL_OES; +#endif + + *gl_tex_id = f->ctx->tex_id; + + if(!f->flushed && f->ctx->codec) { + if (AMediaCodec_releaseOutputBuffer(f->ctx->codec, f->outIndex, GF_TRUE) != AMEDIA_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[MCDec] NOT Release Output Buffer Index: %d to surface\n", f->outIndex)); + return GF_OK; + } + if(mcdec_update_surface(f->ctx->surfaceTex) != GF_OK) return GF_BAD_PARAM; + if(mcdec_get_transform_matrix(texcoordmatrix, f->ctx->surfaceTex) != GF_OK) return GF_BAD_PARAM; + + f->flushed = GF_TRUE; + } + return GF_OK; +} + +static GF_Err mcdec_send_frame(GF_MCDecCtx *ctx, u8 *frame_buffer, u64 cts) +{ + GF_MCDecFrame *mc_frame; + GF_FilterPacket *dst_pck; + + if(ctx->outIndex < 0 || (!frame_buffer && !ctx->surface_rendering)) return GF_BAD_PARAM; + + mc_frame = gf_list_pop_front(ctx->frames_res); + if (!mc_frame) { + GF_SAFEALLOC(mc_frame, GF_MCDecFrame); + if (!mc_frame) return GF_OUT_OF_MEM; + } + memset(&mc_frame->frame_ifce, 0, sizeof(GF_FilterFrameInterface)); + mc_frame->frame_ifce.user_data = mc_frame; + mc_frame->ctx = ctx; + mc_frame->frame = frame_buffer; + mc_frame->outIndex = ctx->outIndex; + mc_frame->flushed = GF_FALSE; + + if (ctx->surface_rendering) { + mc_frame->frame_ifce.get_gl_texture = mcdec_hw_get_gl_texture; + } else { + mc_frame->frame_ifce.get_plane = mcdec_hw_get_plane; + } + + if (ctx->frame_size_changed) { + mc_frame->frame_ifce.flags = GF_FRAME_IFCE_BLOCKING; + ctx->frame_size_changed = GF_FALSE; + } + ctx->decoded_frames_pending++; + + + dst_pck = gf_filter_pck_new_frame_interface(ctx->opid, &mc_frame->frame_ifce, mcdec_hw_del); + if (!dst_pck) return GF_OUT_OF_MEM; + + if (dst_pck) { + gf_filter_pck_set_cts(dst_pck, cts); + //TODO, copy properties from source packet ! + + gf_filter_pck_send(dst_pck); + } + + return GF_OK; +} + +GF_Err mcdec_initialize(GF_Filter *filter) +{ + GF_MCDecCtx *ctx = gf_filter_get_udta(filter); + ctx->filter = filter; + + ctx->frames_res = gf_list_new(); + ctx->streams = gf_list_new(); + + ctx->SPSs = gf_list_new(); + ctx->PPSs = gf_list_new(); + ctx->VPSs = gf_list_new(); + + ctx->active_vps = -1; + ctx->active_sps = -1; + ctx->active_pps = -1; + + return GF_OK; +} + +void mcdec_finalize(GF_Filter *filter) +{ + GF_MCDecCtx *ctx = gf_filter_get_udta(filter); + + if (ctx->format && AMediaFormat_delete(ctx->format) != AMEDIA_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[MCDec] AMediaFormat_delete failed\n")); + } + + if (ctx->codec && AMediaCodec_delete(ctx->codec) != AMEDIA_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[MCDec] AMediaCodec_delete failed\n")); + } + + if(ctx->window) { + ANativeWindow_release(ctx->window); + ctx->window = NULL; + } + + if (ctx->surfaceTex.texture_id) + mcdec_delete_surface(ctx->surfaceTex); + + if(ctx->tex_id) + glDeleteTextures (1, &ctx->tex_id); + + mcdec_reset_ps_list(ctx->SPSs); + gf_list_del(ctx->SPSs); + mcdec_reset_ps_list(ctx->PPSs); + gf_list_del(ctx->PPSs); + mcdec_reset_ps_list(ctx->VPSs); + gf_list_del(ctx->VPSs); + + while (gf_list_count(ctx->frames_res) ) { + GF_MCDecFrame *f = gf_list_pop_back(ctx->frames_res); + gf_free(f); + } + gf_list_del(ctx->frames_res); + gf_list_del(ctx->streams); +} + +#else +static GF_Err mcdec_process(GF_Filter *filter) +{ + return GF_EOS; +} +#endif + + +static const GF_FilterCapability MCDecCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + //commented as some devices seem to have issues with this one +// CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG4_PART2), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AVC), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_HEVC), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED,GF_PROP_PID_TILE_BASE, GF_TRUE), + + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +#if defined(GPAC_CONFIG_ANDROID) && defined(GPAC_HAS_MEDIACODEC) +#define OFFS(_n) #_n, offsetof(GF_MCDecCtx, _n) +#else +#define OFFS(_n) #_n, -1 +#endif + +static const GF_FilterArgs MCDecArgs[] = +{ + { OFFS(disable_gl), "disable OpenGL texture transfer", GF_PROP_BOOL, "false", NULL, 0}, + {0} +}; + +GF_FilterRegister GF_MCDecCtxRegister = { + .name = "mcdec", + GF_FS_SET_DESCRIPTION("MediaCodec decoder") + GF_FS_SET_HELP("This filter decodes video streams using hardware decoder on android devices") + .args = MCDecArgs, + SETCAPS(MCDecCaps), +#if defined(GPAC_CONFIG_ANDROID) && defined(GPAC_HAS_MEDIACODEC) + .private_size = sizeof(GF_MCDecCtx), + .initialize = mcdec_initialize, + .finalize = mcdec_finalize, + .configure_pid = mcdec_configure_pid, +#endif + .process = mcdec_process, +}; + + +const GF_FilterRegister *mcdec_register(GF_FilterSession *session) +{ +#ifndef GPAC_CONFIG_ANDROID + if (!gf_opts_get_bool("temp", "gendoc")) + return NULL; + GF_MCDecCtxRegister.version = "! Warning: MediaCodec SDK NOT AVAILABLE IN THIS BUILD !"; + +#ifdef GPAC_ENABLE_COVERAGE + mcdec_process(NULL); +#endif + +#endif + return &GF_MCDecCtxRegister; +} diff --git a/src/filters/dec_mediacodec.h b/src/filters/dec_mediacodec.h new file mode 100644 index 0000000..e569ca2 --- /dev/null +++ b/src/filters/dec_mediacodec.h @@ -0,0 +1,128 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2020 + * All rights reserved + * + * This file is part of GPAC / mediacodec decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _MEDIACODEC_DEC_H_ +#define _MEDIACODEC_DEC_H_ + +#include + +#ifndef GPAC_CONFIG_ANDROID +#define MEDIACODEC_EMUL_API +#endif + +#ifdef MEDIACODEC_EMUL_API + +typedef void AMediaCodec; +typedef void AMediaFormat; +typedef void ANativeWindow; +typedef int window; +typedef void *jobject; + +typedef struct { + u32 flags; + s32 offset; + s64 presentationTimeUs; + s32 size; +} AMediaCodecBufferInfo; + +void AMediaFormat_setString(void *, const char *, const char *); +void AMediaFormat_setInt32(void *, const char *, int); +void AMediaFormat_setBuffer(void *, const char *, char *, int); +AMediaFormat *AMediaFormat_new(); +AMediaCodec *AMediaCodec_createCodecByName(char *); +int AMediaCodec_configure(AMediaCodec *, const AMediaFormat *format, ANativeWindow *surface, void *crypto, uint32_t flags); +int AMediaCodec_start(AMediaCodec *); +int AMediaCodec_stop(AMediaCodec *); + +ssize_t AMediaCodec_dequeueInputBuffer(AMediaCodec *, int64_t timeoutUs); +int AMediaCodec_flush(AMediaCodec *); +char *AMediaCodec_getInputBuffer(AMediaCodec *, size_t idx, size_t *out_size); +int AMediaCodec_queueInputBuffer(AMediaCodec *, size_t idx, int offset, size_t size, uint64_t time, uint32_t flags); +int AMediaCodec_dequeueOutputBuffer(AMediaCodec *, AMediaCodecBufferInfo *info, int64_t timeoutUs); +char *AMediaCodec_getOutputBuffer(AMediaCodec *, size_t idx, size_t *out_size); +int AMediaFormat_getInt32(AMediaFormat *, const char *name, int32_t *out); +int AMediaCodec_releaseOutputBuffer(AMediaCodec*, size_t idx, int render); +int AMediaFormat_delete(AMediaFormat *); +AMediaFormat *AMediaCodec_getOutputFormat(AMediaCodec *); +int AMediaCodec_delete(AMediaCodec *); +int ANativeWindow_release(window); + +#define AMEDIA_OK 0 +#define AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED 1 +#define AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED 2 +#define AMEDIACODEC_INFO_TRY_AGAIN_LATER 3 +#define AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM 1 + +const char* AMEDIAFORMAT_KEY_AAC_PROFILE = "aac-profile"; +const char* AMEDIAFORMAT_KEY_BIT_RATE = "bitrate"; +const char* AMEDIAFORMAT_KEY_CHANNEL_COUNT = "channel-count"; +const char* AMEDIAFORMAT_KEY_CHANNEL_MASK = "channel-mask"; +const char* AMEDIAFORMAT_KEY_COLOR_FORMAT = "color-format"; +const char* AMEDIAFORMAT_KEY_DURATION = "durationUs"; +const char* AMEDIAFORMAT_KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level"; +const char* AMEDIAFORMAT_KEY_FRAME_RATE = "frame-rate"; +const char* AMEDIAFORMAT_KEY_HEIGHT = "height"; +const char* AMEDIAFORMAT_KEY_IS_ADTS = "is-adts"; +const char* AMEDIAFORMAT_KEY_IS_AUTOSELECT = "is-autoselect"; +const char* AMEDIAFORMAT_KEY_IS_DEFAULT = "is-default"; +const char* AMEDIAFORMAT_KEY_IS_FORCED_SUBTITLE = "is-forced-subtitle"; +const char* AMEDIAFORMAT_KEY_I_FRAME_INTERVAL = "i-frame-interval"; +const char* AMEDIAFORMAT_KEY_LANGUAGE = "language"; +const char* AMEDIAFORMAT_KEY_MAX_HEIGHT = "max-height"; +const char* AMEDIAFORMAT_KEY_MAX_INPUT_SIZE = "max-input-size"; +const char* AMEDIAFORMAT_KEY_MAX_WIDTH = "max-width"; +const char* AMEDIAFORMAT_KEY_MIME = "mime"; +const char* AMEDIAFORMAT_KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown"; +const char* AMEDIAFORMAT_KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after"; +const char* AMEDIAFORMAT_KEY_SAMPLE_RATE = "sample-rate"; +const char* AMEDIAFORMAT_KEY_WIDTH = "width"; +const char* AMEDIAFORMAT_KEY_STRIDE = "stride"; + +#else + +#include +#include +#include +#include + +#endif + +#include "../../src/compositor/gl_inc.h" + +typedef struct { + jobject oSurfaceTex; + int texture_id; +} GF_MCDecSurfaceTexture; + +GF_Err mcdec_create_surface(GLuint tex_id, ANativeWindow ** window, Bool * surface_rendering, GF_MCDecSurfaceTexture * surfaceTex); +GF_Err mcdec_delete_surface(GF_MCDecSurfaceTexture surfaceTex); +char * mcdec_find_decoder(const char * mime, u32 width, u32 height, Bool * is_adaptive); +u32 mcdec_exit_callback(void * param); + +GF_Err mcdec_update_surface(GF_MCDecSurfaceTexture surfaceTex); +GF_Err mcdec_get_transform_matrix(struct __matrix * mx, GF_MCDecSurfaceTexture surfaceTex); + +#endif //_MEDIACODEC_DEC_H_ + diff --git a/src/filters/dec_mediacodec_jni.c b/src/filters/dec_mediacodec_jni.c new file mode 100644 index 0000000..39884ca --- /dev/null +++ b/src/filters/dec_mediacodec_jni.c @@ -0,0 +1,408 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2018 + * All rights reserved + * + * This file is part of GPAC / codec pack module + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include + +// for native window JNI +#include +#include +#include + +#include + +#include "dec_mediacodec.h" +#define RANDOM_JAVA_APP_CLASS "com/gpac/Osmo4/GPACInstance" +#define ALL_CODECS 1 + +static jclass cSurfaceTexture = NULL; +static jclass cSurface = NULL; +static jclass cMediaCodecList = NULL; +static jclass cMediaFormat = NULL; +static jmethodID mUpdateTexImage; +static jmethodID mSurfaceTexConstructor; +static jmethodID mSurfaceConstructor; +static jmethodID mSurfaceRelease; +static jmethodID mGetTransformMatrix; +static jmethodID mFindClassMethod; +static jmethodID mMediaCodecListConstructor; +static jmethodID mFindDecoderForFormat; +static jmethodID mCreateVideoFormat; +static jmethodID mSetFeatureEnabled; +static jobject oClassLoader = NULL; +static JavaVM* javaVM = 0; + +static void HandleJNIError(JNIEnv *env) +{ + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); +} +static jclass FindAppClass(JNIEnv* env, const char* name) +{ + + return (jclass)((*env)->CallObjectMethod(env, oClassLoader, mFindClassMethod, (*env)->NewStringUTF(env, name))); +} + +static GF_Err CacheAppClassLoader(JNIEnv* env, const char* name) +{ + GF_Err ret = GF_BAD_PARAM; + jclass randomClass = (*env)->FindClass(env, name); + if (!randomClass) goto cache_calassloader_failed; + jclass classClass = (*env)->GetObjectClass(env, randomClass); + if (!classClass) goto cache_calassloader_failed; + jclass classLoaderClass = (*env)->FindClass(env, "java/lang/ClassLoader"); + if (!classLoaderClass) goto cache_calassloader_failed; + jmethodID getClassLoaderMethod = (*env)->GetMethodID(env, classClass, "getClassLoader", "()Ljava/lang/ClassLoader;"); + if (!getClassLoaderMethod) goto cache_calassloader_failed; + oClassLoader = (jobject) (*env)->NewGlobalRef(env,(*env)->CallObjectMethod(env,randomClass, getClassLoaderMethod)); + if(!oClassLoader) goto cache_calassloader_failed; + mFindClassMethod = (*env)->GetMethodID(env, classLoaderClass, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;"); + if(!mFindClassMethod) goto cache_calassloader_failed; + ret = GF_OK; + +cache_calassloader_failed: + if(randomClass) + (*env)->DeleteLocalRef(env, randomClass); + if(classClass) + (*env)->DeleteLocalRef(env, classClass); + if(classLoaderClass) + (*env)->DeleteLocalRef(env, classLoaderClass); + return ret; +} + +jint JNI_OnLoad(JavaVM* vm, void* reserved) +{ + JNIEnv* env = NULL; + javaVM = vm; + + if ((*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_2) != JNI_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("GetEnv failed")); + return GF_BAD_PARAM; + } + return JNI_VERSION_1_2; +} + +JNIEnv* GetEnv() +{ + JNIEnv* env = 0; + if (javaVM) (*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_2); + return env; +} + +JavaVM* GetJavaVM() +{ + return javaVM; +} + +u32 mcdec_exit_callback(void * param) { + + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[MCDec] Detach decoder thread\n")); + (*GetJavaVM())->DetachCurrentThread(GetJavaVM()); + return GF_OK; +} + +GF_Err mcdec_create_surface (GLuint tex_id, ANativeWindow ** window, Bool * surface_rendering, GF_MCDecSurfaceTexture * surfaceTex) +{ + JNIEnv* env = NULL; + jobject otmp= NULL; + jclass ctmp = NULL; + jint res = 0; + jobject oSurface = NULL; + GF_Err ret = GF_BAD_PARAM; + + res = (*GetJavaVM())->GetEnv(GetJavaVM(), (void**)&env, JNI_VERSION_1_2); + if ( res == JNI_EDETACHED ) { + (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); + } + if (!env) goto create_surface_failed; + + // cache classes + if (!cSurfaceTexture) { + ctmp = (*env)->FindClass(env, "android/graphics/SurfaceTexture"); + cSurfaceTexture = (*env)->NewGlobalRef(env, ctmp); + if(!cSurfaceTexture) goto create_surface_failed; + } + if (!cSurface) { + ctmp = (*env)->FindClass(env, "android/view/Surface"); + cSurface = (*env)->NewGlobalRef(env, ctmp); + if (!cSurface) goto create_surface_failed; + } + + // cache methods + if(!mSurfaceTexConstructor) { + mSurfaceTexConstructor = (*env)->GetMethodID(env, cSurfaceTexture, "", "(I)V"); + if(!mSurfaceTexConstructor) goto create_surface_failed; + } + if(!mSurfaceConstructor) { + mSurfaceConstructor = (*env)->GetMethodID(env, cSurface, "", "(Landroid/graphics/SurfaceTexture;)V"); + if(!mSurfaceConstructor) goto create_surface_failed; + } + if(!mSurfaceRelease) { + mSurfaceRelease = (*env)->GetMethodID(env, cSurface, "release", "()V"); + if(!mSurfaceRelease) goto create_surface_failed; + } + if(!mUpdateTexImage) { + mUpdateTexImage = (*env)->GetMethodID(env, cSurfaceTexture, "updateTexImage", "()V"); + if(!mUpdateTexImage) goto create_surface_failed; + } + if(!mGetTransformMatrix) { + mGetTransformMatrix = (*env)->GetMethodID(env, cSurfaceTexture, "getTransformMatrix", "([F)V"); + if(!mGetTransformMatrix) goto create_surface_failed; + } + + surfaceTex->texture_id = tex_id; + // create objects + otmp = (*env)->NewObject(env, cSurfaceTexture, mSurfaceTexConstructor, surfaceTex->texture_id); + surfaceTex->oSurfaceTex = (jobject) (*env)->NewGlobalRef(env,otmp); + if(!surfaceTex->oSurfaceTex) goto create_surface_failed; + + oSurface = (*env)->NewObject(env, cSurface, mSurfaceConstructor, surfaceTex->oSurfaceTex); + if(!oSurface) goto create_surface_failed; + + *window = ANativeWindow_fromSurface(env, oSurface); + *surface_rendering = (*window) ? GF_TRUE : GF_FALSE; + (*env)->CallVoidMethod(env, oSurface, mSurfaceRelease); + if ((*env)->ExceptionCheck(env)) { + HandleJNIError(env); + } + ret = GF_OK; + +create_surface_failed: + if(otmp) + (*env)->DeleteLocalRef(env, otmp); + if(ctmp) + (*env)->DeleteLocalRef(env, ctmp); + if(oSurface) + (*env)->DeleteLocalRef(env, oSurface); + + return ret; +} + +static char * mcdec_get_decoder_name(JNIEnv * env, jobject mediaCodecList, jmethodID findId, jobject mediaFomat) +{ + jint res = 0; + jstring jdecoder_name; + char * decoder_name = NULL; + + jdecoder_name = (*env)->CallObjectMethod(env, mediaCodecList, findId, mediaFomat); + if ((*env)->ExceptionCheck(env)) { + HandleJNIError(env); + goto get_decoder_exit; + } + if(jdecoder_name) { + const char *decoder_name_tmp; + decoder_name_tmp = (*env)->GetStringUTFChars(env, jdecoder_name, 0); + decoder_name = gf_strdup(decoder_name_tmp); + (*env)->ReleaseStringUTFChars(env, jdecoder_name, decoder_name_tmp); + } + +get_decoder_exit: + if(jdecoder_name) + (*env)->DeleteLocalRef(env, jdecoder_name); + + return decoder_name; +} + +char * mcdec_find_decoder(const char * mime, u32 width, u32 height, Bool * is_adaptive) +{ + JNIEnv* env = NULL; + jobject oMediaCodecList = NULL, oMediaFormat = NULL; + jclass ctmp = NULL; + jint res = 0; + jstring jmime, jfeature; + char *decoder_name = NULL, *decoder_name_tmp; + + + if(!mime || !is_adaptive) goto find_decoder_exit; + + res = (*GetJavaVM())->GetEnv(GetJavaVM(), (void**)&env, JNI_VERSION_1_2); + if ( res == JNI_EDETACHED ) { + (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); + } + if (!env) goto find_decoder_exit; + + // cache classes + if (!cMediaCodecList) { + ctmp = (*env)->FindClass(env, "android/media/MediaCodecList"); + cMediaCodecList = (*env)->NewGlobalRef(env, ctmp); + if(!cMediaCodecList) goto find_decoder_exit; + } + if (!cMediaFormat) { + ctmp = (*env)->FindClass(env, "android/media/MediaFormat"); + cMediaFormat = (*env)->NewGlobalRef(env, ctmp); + if(!cMediaFormat) goto find_decoder_exit; + } + + //methods + if(!mMediaCodecListConstructor) { + mMediaCodecListConstructor = (*env)->GetMethodID(env, cMediaCodecList, "", "(I)V"); + if(!mMediaCodecListConstructor) goto find_decoder_exit; + } + if(!mFindDecoderForFormat) { + mFindDecoderForFormat = (*env)->GetMethodID(env, cMediaCodecList, "findDecoderForFormat", "(Landroid/media/MediaFormat;)Ljava/lang/String;"); + if(!mFindDecoderForFormat) goto find_decoder_exit; + } + if(!mCreateVideoFormat) { + mCreateVideoFormat = (*env)->GetStaticMethodID(env, cMediaFormat, "createVideoFormat", "(Ljava/lang/String;II)Landroid/media/MediaFormat;"); + if(!mCreateVideoFormat) goto find_decoder_exit; + } + if(!mSetFeatureEnabled ) { + mSetFeatureEnabled = (*env)->GetMethodID(env, cMediaFormat, "setFeatureEnabled", "(Ljava/lang/String;Z)V"); + if(!mSetFeatureEnabled) goto find_decoder_exit; + } + oMediaCodecList = (*env)->NewObject(env, cMediaCodecList, mMediaCodecListConstructor, ALL_CODECS); + if(!oMediaCodecList) goto find_decoder_exit; + + jmime = (*env)->NewStringUTF(env, mime); + oMediaFormat = (*env)->CallStaticObjectMethod(env, cMediaFormat, mCreateVideoFormat, jmime, width, height); + if ((*env)->ExceptionCheck(env)) { + HandleJNIError(env); + goto find_decoder_exit; + } + decoder_name_tmp = mcdec_get_decoder_name(env, oMediaCodecList, mFindDecoderForFormat, oMediaFormat); + if(!decoder_name_tmp) goto find_decoder_exit; + + jfeature = (*env)->NewStringUTF(env, "adaptive-playback"); + (*env)->CallVoidMethod(env, oMediaFormat, mSetFeatureEnabled, jfeature, JNI_TRUE); + if ((*env)->ExceptionCheck(env)) { + HandleJNIError(env); + goto find_decoder_exit; + } + decoder_name = mcdec_get_decoder_name(env, oMediaCodecList, mFindDecoderForFormat, oMediaFormat); + if(decoder_name){ + *is_adaptive = GF_TRUE; + gf_free(decoder_name_tmp); + decoder_name_tmp = NULL; + } else { + *is_adaptive = GF_FALSE; + decoder_name = decoder_name_tmp; + } + +find_decoder_exit: + if(ctmp) + (*env)->DeleteLocalRef(env, ctmp); + if(oMediaCodecList) + (*env)->DeleteLocalRef(env, oMediaCodecList); + if(oMediaFormat) + (*env)->DeleteLocalRef(env, oMediaFormat); + if(jmime) + (*env)->DeleteLocalRef(env, jmime); + if(jfeature) + (*env)->DeleteLocalRef(env, jfeature); + + return decoder_name; + +} + +GF_Err mcdec_update_surface(GF_MCDecSurfaceTexture surfaceTex) +{ + JNIEnv* env = NULL; + jint res = 0; + res = (*GetJavaVM())->GetEnv(GetJavaVM(), (void**)&env, JNI_VERSION_1_2); + if ( res == JNI_EDETACHED ) { + (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); + } + if (!env) return GF_BAD_PARAM; + + (*env)->CallVoidMethod(env, surfaceTex.oSurfaceTex, mUpdateTexImage); + if ((*env)->ExceptionCheck(env)) { + HandleJNIError(env); + return GF_BAD_PARAM; + } + return GF_OK; +} + +GF_Err mcdec_get_transform_matrix(GF_Matrix * mx, GF_MCDecSurfaceTexture surfaceTex) +{ + JNIEnv* env = NULL; + jint res = 0; + int i =0; + jfloatArray texMx; + jsize len = 0; + jfloat *body; + GF_Err ret = GF_BAD_PARAM; + + res = (*GetJavaVM())->GetEnv(GetJavaVM(), (void**)&env, JNI_VERSION_1_2); + if ( res == JNI_EDETACHED ) { + (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); + } + if (!env) goto get_matrix_failed; + + texMx = (*env)->NewFloatArray(env, 16); + if (texMx == NULL) goto get_matrix_failed; + + (*env)->CallVoidMethod(env, surfaceTex.oSurfaceTex, mGetTransformMatrix,texMx); + if ((*env)->ExceptionCheck(env)) { + HandleJNIError(env); + goto get_matrix_failed; + } + len = (*env)->GetArrayLength(env, texMx); + body = (*env)->GetFloatArrayElements(env, texMx, 0); + if ((*env)->ExceptionCheck(env)) { + HandleJNIError(env); + goto get_matrix_failed; + } + for (i=0; im[i] = FLT2FIX(body[i]); + } + + (*env)->ReleaseFloatArrayElements(env, texMx, body, 0); + if ((*env)->ExceptionCheck(env)) { + HandleJNIError(env); + goto get_matrix_failed; + } + ret = GF_OK; + +get_matrix_failed: + if(texMx) + (*env)->DeleteLocalRef(env, texMx); + return ret; +} + +GF_Err mcdec_delete_surface(GF_MCDecSurfaceTexture surfaceTex) +{ + JNIEnv* env = NULL; + jint res = 0; + + res = (*GetJavaVM())->GetEnv(GetJavaVM(), (void**)&env, JNI_VERSION_1_2); + if ( res == JNI_EDETACHED ) { + (*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL); + } + if (!env) return GF_BAD_PARAM; + + (*env)->DeleteGlobalRef(env, cSurface); + cSurface = NULL; + (*env)->DeleteGlobalRef(env, cSurfaceTexture); + cSurfaceTexture = NULL; + (*env)->DeleteGlobalRef(env, cMediaCodecList); + cMediaCodecList = NULL; + (*env)->DeleteGlobalRef(env, cMediaFormat); + cMediaFormat = NULL; + (*env)->DeleteGlobalRef(env, surfaceTex.oSurfaceTex); + surfaceTex.oSurfaceTex = NULL; + + return GF_OK; +} + diff --git a/src/filters/dec_nvdec.c b/src/filters/dec_nvdec.c new file mode 100644 index 0000000..31c10a0 --- /dev/null +++ b/src/filters/dec_nvdec.c @@ -0,0 +1,1508 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / NVidia Hardware decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +#if (defined(WIN32) || defined(GPAC_CONFIG_LINUX) || defined(GPAC_CONFIG_DARWIN)) + +#include "dec_nvdec_sdk.h" + +//#define EMUL_NV_DLL + +#ifndef GPAC_DISABLE_3D + + +#ifdef LOAD_GL_1_5 +GLDECL_EXTERN(glGenBuffers); +GLDECL_EXTERN(glBindBuffer); +GLDECL_EXTERN(glBufferData); +#endif + +#endif + +typedef struct _nv_dec_inst NVDecInstance; + +typedef enum +{ + NVDEC_COPY = 0, + NVDEC_SINGLE, + NVDEC_GL +} NVDecFrameMode ; + +typedef enum +{ + NVDEC_CUVID = 0, + NVDEC_CUDA, + NVDEC_DXVA +} NVDecVideoMode; + +typedef struct _nv_dec_ctx +{ + u32 unload; + NVDecFrameMode fmode; + NVDecVideoMode vmode; + u32 num_surfaces; + + GF_FilterPid *ipid, *opid; + u32 codec_id; + Bool use_gl_texture; + u32 width, height, bpp_luma, bpp_chroma; + cudaVideoCodec codec_type; + cudaVideoChromaFormat chroma_fmt; + + u32 out_size, stride, pix_fmt, stride_uv, nb_planes, uv_height; + u32 reload_decoder_state; + Bool skip_next_frame; + CUresult decode_error, dec_create_error; + Bool frame_size_changed; + Bool needs_resetup; + unsigned long prefer_dec_mode; + + NVDecInstance *dec_inst; + + GF_List *frames; + GF_List *frames_res; + GF_List *src_packets; + + struct __nv_frame *pending_frame; + + + u8 *xps_buf; + u32 xps_buf_size; + u32 nal_size_length; + Bool inject_xps; + u8 *nal_buffer; + u32 nal_buffer_alloc; + + u8 *single_frame_data; + u32 single_frame_data_alloc; + +#ifndef GPAC_DISABLE_3D + Bool gl_provider_requested; + GLint y_tx_id, uv_tx_id; + GLint y_pbo_id, uv_pbo_id; +#endif +} NVDecCtx; + + +struct _nv_dec_inst +{ + u32 width, height, bpp_luma, bpp_chroma, stride; + cudaVideoCodec codec_type; + cudaVideoChromaFormat chroma_fmt; + u32 id; + u32 th_id; + + //allocated video parser and decoder + CUvideoparser cu_parser; + CUvideodecoder cu_decoder; + + //current associated context, 0 is none + NVDecCtx *ctx; +}; + + +typedef struct __nv_frame +{ + CUVIDPARSERDISPINFO frame_info; + NVDecCtx *ctx; + GF_FilterFrameInterface gframe; + Bool y_mapped, uv_mapped; +} NVDecFrame; + +static GF_List *global_unactive_decoders=NULL; +static u32 global_nb_loaded_nvdec = 0; +static u32 global_nb_loaded_decoders = 0; +static GF_Mutex *global_inst_mutex = NULL; +static CUcontext cuda_ctx = NULL; +#ifndef EMUL_NV_DLL +static Bool cuda_ctx_gl = GF_FALSE; +static CUdevice cuda_dev = -1; +#endif + +//#define ENABLE_10BIT_OUTPUT + +static GF_Err nvdec_init_decoder(NVDecCtx *ctx) +{ + CUresult res; + CUVIDDECODECREATEINFO cuvid_info; + + assert(ctx->dec_inst); + + memset(&cuvid_info, 0, sizeof(CUVIDDECODECREATEINFO)); + cuvid_info.CodecType = ctx->codec_type; + cuvid_info.ulWidth = ctx->width; + cuvid_info.ulHeight = ctx->height; + cuvid_info.ulNumDecodeSurfaces = ctx->num_surfaces; + cuvid_info.ChromaFormat = ctx->chroma_fmt; + cuvid_info.OutputFormat = cudaVideoSurfaceFormat_NV12; +#ifdef ENABLE_10BIT_OUTPUT + if (ctx->bpp_luma + ctx->bpp_chroma > 16) + cuvid_info.OutputFormat = cudaVideoSurfaceFormat_P016; +#endif + cuvid_info.DeinterlaceMode = cudaVideoDeinterlaceMode_Adaptive; + cuvid_info.bitDepthMinus8 = ctx->bpp_luma - 8; + cuvid_info.ulTargetWidth = ctx->width; + cuvid_info.ulTargetHeight = ctx->height; + cuvid_info.display_area.left = 0; + cuvid_info.display_area.right = ctx->width; + cuvid_info.display_area.top = 0; + cuvid_info.display_area.bottom = ctx->height; + + cuvid_info.ulNumOutputSurfaces = 1; + cuvid_info.ulCreationFlags = ctx->prefer_dec_mode; + + // create the decoder + res = cuvidCreateDecoder(&ctx->dec_inst->cu_decoder, &cuvid_info); + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to create cuvid decoder %s\n", cudaGetErrorEnum(res) ) ); + ctx->dec_create_error = res; + return GF_IO_ERR; + } + global_nb_loaded_decoders++; + assert(global_nb_loaded_decoders); + ctx->dec_inst->id = global_nb_loaded_decoders; + ctx->dec_inst->th_id = gf_th_id(); + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[NVDec] decoder instance %d created (%dx%d) - %d total decoders loaded\n", ctx->dec_inst->id, ctx->width, ctx->height, global_nb_loaded_decoders) ); + return GF_OK; +} + +Bool load_inactive_dec(NVDecCtx *ctx) +{ + u32 i, count; + //look for unactive decoder with same settings + if (global_unactive_decoders) { + + gf_mx_p(global_inst_mutex); + count = gf_list_count(global_unactive_decoders); + for (i=0; iwidth==ctx->width) && (inst->height==ctx->height) && (inst->bpp_luma == ctx->bpp_luma ) + && (inst->bpp_chroma == ctx->bpp_chroma ) && (inst->codec_type == ctx->codec_type) && (inst->chroma_fmt == ctx->chroma_fmt ) + ) { + + gf_list_rem(global_unactive_decoders, i); + ctx->dec_inst = inst; + inst->ctx = ctx; + gf_mx_v(global_inst_mutex); + return GF_TRUE; + } + } + if (ctx->dec_inst && !ctx->dec_inst->cu_decoder) { + ctx->dec_inst->ctx = ctx; + gf_mx_v(global_inst_mutex); + return GF_FALSE; + } + if (ctx->dec_inst) { + NVDecInstance *inst = ctx->dec_inst; + if ((inst->width==ctx->width) && (inst->height==ctx->height) && (inst->bpp_luma == ctx->bpp_luma ) + && (inst->bpp_chroma == ctx->bpp_chroma ) && (inst->codec_type == ctx->codec_type) && (inst->chroma_fmt == ctx->chroma_fmt ) + ) { + ctx->dec_inst = inst; + inst->ctx = ctx; + gf_mx_v(global_inst_mutex); + return GF_TRUE; + } + } else { + ctx->dec_inst = gf_list_pop_back(global_unactive_decoders); + } + gf_mx_v(global_inst_mutex); + } + if (!ctx->dec_inst) { + GF_SAFEALLOC(ctx->dec_inst, NVDecInstance); + if (!ctx->dec_inst) + return GF_FALSE; + } + ctx->dec_inst->ctx = ctx; + return GF_FALSE; +} + +static void nvdec_destroy_decoder(NVDecInstance *inst) +{ + if (inst->cu_decoder) { + cuvidDestroyDecoder(inst->cu_decoder); + inst->cu_decoder = NULL; + global_nb_loaded_decoders--; + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[NVDec] decoder instance %d destruction - %d decoders still loaded\n", inst->id, global_nb_loaded_decoders ) ); + } +} + +static void update_pix_fmt(NVDecCtx *ctx, Bool use_10bits) +{ + switch (ctx->chroma_fmt) { + case cudaVideoChromaFormat_420: + ctx->pix_fmt = use_10bits ? GF_PIXEL_NV12_10 : GF_PIXEL_NV12; + break; + case cudaVideoChromaFormat_422: + ctx->pix_fmt = use_10bits ? GF_PIXEL_YUV422_10 : GF_PIXEL_YUV422; + break; + case cudaVideoChromaFormat_444: + ctx->pix_fmt = use_10bits ? GF_PIXEL_YUV444_10 : GF_PIXEL_YUV444; + break; + default: + ctx->pix_fmt = 0; + return; + } + gf_pixel_get_size_info(ctx->pix_fmt, ctx->width, ctx->height, &ctx->out_size, &ctx->stride, &ctx->stride_uv, &ctx->nb_planes, &ctx->uv_height); +} + +static int CUDAAPI HandleVideoSequence(void *pUserData, CUVIDEOFORMAT *pFormat) +{ + Bool use_10bits=GF_FALSE; + Bool skip_output_resize=GF_FALSE; + NVDecInstance *inst= (NVDecInstance *)pUserData; + NVDecCtx *ctx = inst->ctx; + + u32 w, h; + w = pFormat->coded_width; + h = pFormat->coded_height; + + if (pFormat->display_area.right && (pFormat->display_area.right<(s32)w)) w = pFormat->display_area.right; + if (pFormat->display_area.bottom && (pFormat->display_area.bottom<(s32)h)) h = pFormat->display_area.bottom; + + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[NVDec] Decoder instance %d Video sequence change detected - new setup %u x %u, %u bpp\n", inst->id, pFormat->coded_width, pFormat->coded_height, pFormat->bit_depth_luma_minus8 + 8) ); + //no change in config + if ((ctx->width == w) + && (ctx->height == h) + && (ctx->bpp_luma == 8 + pFormat->bit_depth_luma_minus8) + && (ctx->bpp_chroma == 8 + pFormat->bit_depth_chroma_minus8) + && (ctx->codec_type == pFormat->codec) + && (ctx->chroma_fmt == pFormat->chroma_format) + ) { + if (ctx->dec_inst && ctx->dec_inst->cu_decoder) + return 1; + skip_output_resize = GF_TRUE; + } + + //commented out since this falls back to soft decoding ! +#ifdef ENABLE_10BIT_OUTPUT + if (ctx->bpp_luma + ctx->bpp_chroma > 16) use_10bits = GF_TRUE; +#endif + + ctx->width = w; + ctx->height = h; + ctx->bpp_luma = 8 + pFormat->bit_depth_luma_minus8; + ctx->bpp_chroma = 8 + pFormat->bit_depth_chroma_minus8; + ctx->codec_type = pFormat->codec; + ctx->chroma_fmt = pFormat->chroma_format; + ctx->stride = pFormat->coded_width; + + //if load_inatcive returns TRUE, we are reusing an existing decoder with the same config, no need to recreate one + if (load_inactive_dec(ctx)) { + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[NVDec] reusing inactive decoder %dx%d - %d total decoders loaded\n", ctx->width, ctx->height, global_nb_loaded_decoders) ); + ctx->stride = ctx->dec_inst->stride; + //initial config, need to trigger output resize + if (!ctx->out_size) ctx->reload_decoder_state = 1; + + update_pix_fmt(ctx, use_10bits); + return GF_OK; + } + if (!ctx->dec_inst) return GF_OUT_OF_MEM; + //if we have an existing decoder but with a different config, let's reload + nvdec_destroy_decoder(ctx->dec_inst); + + ctx->dec_inst->width = ctx->width; + ctx->dec_inst->height = ctx->height; + ctx->dec_inst->bpp_luma = ctx->bpp_luma; + ctx->dec_inst->bpp_chroma = ctx->bpp_chroma; + ctx->dec_inst->codec_type = ctx->codec_type; + ctx->dec_inst->chroma_fmt = ctx->chroma_fmt; + ctx->dec_inst->ctx = ctx; + ctx->stride = use_10bits ? 2*ctx->width : ctx->width; + + update_pix_fmt(ctx, use_10bits); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->width)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->height)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->stride)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_BIT_DEPTH_Y, &PROP_UINT(use_10bits ? ctx->bpp_luma : 8)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_BIT_DEPTH_UV, &PROP_UINT(use_10bits ? ctx->bpp_chroma : 8)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, &PROP_UINT(ctx->pix_fmt)); + + + assert(ctx->out_size); + assert(ctx->stride); + ctx->dec_inst->stride = ctx->stride; + + if (! ctx->dec_inst->cu_decoder) { + nvdec_init_decoder(ctx); + if (!skip_output_resize) { + ctx->reload_decoder_state = 1; + } + } else { + ctx->reload_decoder_state = 2; + } + return 1; +} + +static int CUDAAPI HandlePictureDecode(void *pUserData, CUVIDPICPARAMS *pPicParams) +{ + NVDecInstance *inst = (NVDecInstance *)pUserData; + inst->ctx->decode_error = cuvidDecodePicture(inst->cu_decoder, pPicParams); + + if (inst->ctx->decode_error != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] decoder instance %d failed to decode picture %s\n", inst->id, cudaGetErrorEnum(inst->ctx->decode_error) ) ); + return GF_IO_ERR; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[NVDec] decoded picture %u OK\n", pPicParams->CurrPicIdx ) ); + + return 1; +} + +static int CUDAAPI HandlePictureDisplay(void *pUserData, CUVIDPARSERDISPINFO *pPicParams) +{ + u32 i, count; + NVDecFrame *f; + NVDecInstance *inst = (NVDecInstance *)pUserData; + NVDecCtx *ctx = (NVDecCtx *)inst->ctx; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[NVDec] picture %u CTS "LLU" ready for display, queuing it\n", pPicParams->picture_index, pPicParams->timestamp) ); + + f = gf_list_pop_back(ctx->frames_res); + if (!f) { + GF_SAFEALLOC(f, NVDecFrame); + if (!f) return 0; + } + f->frame_info = *pPicParams; + f->frame_info.timestamp = pPicParams->timestamp; + f->ctx = ctx; + count = gf_list_count(ctx->frames); + for (i=0; iframes, i); + if (af->frame_info.timestamp > f->frame_info.timestamp) { + gf_list_insert(ctx->frames, f, i); + return 1; + } + } + gf_list_add(ctx->frames, f); + return 1; +} + +static void nvdec_store_paramlist(GF_BitStream *bs, GF_List *psl) +{ + u32 i, count; + count = gf_list_count(psl); + for (i=0; idata, slc->size); + } +} + +static void nvdec_store_xps(NVDecCtx *ctx, GF_AVCConfig *avc_cfg, GF_HEVCConfig *hevc_cfg) +{ + u32 i, count; + GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + if (avc_cfg) { + ctx->nal_size_length = avc_cfg->nal_unit_size; + nvdec_store_paramlist(bs, avc_cfg->sequenceParameterSets); + nvdec_store_paramlist(bs, avc_cfg->sequenceParameterSetExtensions); + nvdec_store_paramlist(bs, avc_cfg->pictureParameterSets); + gf_odf_avc_cfg_del(avc_cfg); + } else if (hevc_cfg) { + ctx->nal_size_length = hevc_cfg->nal_unit_size; + count = gf_list_count(hevc_cfg->param_array); + for (i=0; iparam_array, i); + nvdec_store_paramlist(bs, pa->nalus); + } + gf_odf_hevc_cfg_del(hevc_cfg); + } + if (ctx->xps_buf) gf_free(ctx->xps_buf); + ctx->xps_buf = NULL; + ctx->xps_buf_size = 0; + gf_bs_get_content(bs, &ctx->xps_buf, &ctx->xps_buf_size); + gf_bs_del(bs); + ctx->inject_xps = GF_TRUE; +} + + +static GF_Err nvdec_configure_stream(GF_Filter *filter, NVDecCtx *ctx) +{ +#ifndef EMUL_NV_DLL + CUresult res; +#endif + GF_HEVCConfig *hevc_cfg = NULL; + GF_AVCConfig *avc_cfg = NULL; + const GF_PropertyValue *dcd = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_DECODER_CONFIG); + CUVIDPARSERPARAMS oVideoParserParameters; + + switch (ctx->codec_id) { + case GF_CODECID_MPEG1: + ctx->codec_type = cudaVideoCodec_MPEG1; + break; + case GF_CODECID_MPEG2_SIMPLE: + case GF_CODECID_MPEG2_MAIN: + case GF_CODECID_MPEG2_SNR: + case GF_CODECID_MPEG2_SPATIAL: + case GF_CODECID_MPEG2_HIGH: + case GF_CODECID_MPEG2_422: + ctx->codec_type = cudaVideoCodec_MPEG2; + break; + case GF_CODECID_MPEG4_PART2: + ctx->codec_type = cudaVideoCodec_MPEG4; + break; + case GF_CODECID_AVC: + if (!dcd) return GF_EOS; + ctx->codec_type = cudaVideoCodec_H264; + avc_cfg = gf_odf_avc_cfg_read(dcd->value.data.ptr, dcd->value.data.size); + if (!avc_cfg) return GF_NON_COMPLIANT_BITSTREAM; + break; + case GF_CODECID_HEVC: + if (!dcd) return GF_EOS; + ctx->codec_type = cudaVideoCodec_HEVC; + hevc_cfg = gf_odf_hevc_cfg_read(dcd->value.data.ptr, dcd->value.data.size, GF_FALSE); + if (!hevc_cfg) return GF_NON_COMPLIANT_BITSTREAM; + break; + } + + //create a video parser and a video decoder + memset(&oVideoParserParameters, 0, sizeof(CUVIDPARSERPARAMS)); + ctx->needs_resetup = GF_FALSE; + ctx->nal_size_length = 0; + + //this destroys avc_cfg / hevc_cfg + if (avc_cfg || hevc_cfg) + nvdec_store_xps(ctx, avc_cfg, hevc_cfg); + + if (load_inactive_dec(ctx)) { + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[NVDec] reusing inactive decoder %dx%d - %d total decoders loaded\n", ctx->width, ctx->height, global_nb_loaded_decoders ) ); + ctx->stride = ctx->dec_inst->stride; + } + if (!ctx->dec_inst) { + return GF_OUT_OF_MEM; + } + ctx->decode_error = CUDA_SUCCESS; + + oVideoParserParameters.CodecType = ctx->codec_type; + oVideoParserParameters.ulMaxNumDecodeSurfaces = ctx->num_surfaces; + oVideoParserParameters.ulMaxDisplayDelay = 4; + oVideoParserParameters.ulClockRate = 1000; + oVideoParserParameters.pExtVideoInfo = NULL; + oVideoParserParameters.pfnSequenceCallback = HandleVideoSequence; // Called before decoding frames and/or whenever there is a format change + oVideoParserParameters.pfnDecodePicture = HandlePictureDecode; // Called when a picture is ready to be decoded (decode order) + oVideoParserParameters.pfnDisplayPicture = HandlePictureDisplay; // Called whenever a picture is ready to be displayed (display order) + oVideoParserParameters.pUserData = ctx->dec_inst; + +#ifndef EMUL_NV_DLL + res = cuCtxPushCurrent(cuda_ctx); + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to push CUDA CTX %s\n", cudaGetErrorEnum(res) ) ); + } + res = cuvidCreateVideoParser(&ctx->dec_inst->cu_parser, &oVideoParserParameters); + cuCtxPopCurrent(NULL); + + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to create CUVID parserCTX %s\n", cudaGetErrorEnum(res) ) ); + return GF_PROFILE_NOT_SUPPORTED; + } +#endif + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[NVDec] video parser init OK\n") ); + + switch (ctx->codec_type) { + case cudaVideoCodec_MPEG1: + gf_filter_set_name(filter, ctx->use_gl_texture ? "NVidia HWGL MPEG-1" : "NVidia HW MPEG-1"); + break; + case cudaVideoCodec_MPEG2: + gf_filter_set_name(filter, ctx->use_gl_texture ? "NVidia HWGLMPEG-2" : "NVidia HW MPEG-2"); + break; + case cudaVideoCodec_MPEG4: + gf_filter_set_name(filter, ctx->use_gl_texture ? "NVidia HWGL MPEG-4 part2" : "NVidia HW MPEG-4 part2"); + break; + case cudaVideoCodec_H264: + gf_filter_set_name(filter, ctx->use_gl_texture ? "NVidia HWGL AVC|H264" : "NVidia HW AVC|H264"); + break; + case cudaVideoCodec_HEVC: + gf_filter_set_name(filter, ctx->use_gl_texture ? "NVidia HWGL HEVC" : "NVidia HW HEVC"); + break; + case cudaVideoCodec_VC1: + gf_filter_set_name(filter, ctx->use_gl_texture ? "NVidia HWGL VC1" : "NVidia HW VC1"); + break; + default: + break; + } + return GF_OK; +} + +static GF_Err nvdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *prop; +#ifndef EMUL_NV_DLL + CUresult res; +#endif + NVDecCtx *ctx = (NVDecCtx *) gf_filter_get_udta(filter); + + if (is_remove) { + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + ctx->ipid = NULL; + + if (ctx->unload == 2) { + global_nb_loaded_nvdec--; + if (ctx->dec_inst) { + assert(global_unactive_decoders); + gf_mx_p(global_inst_mutex); + ctx->dec_inst->ctx = NULL; + gf_list_add(global_unactive_decoders, ctx->dec_inst); + ctx->dec_inst = NULL; + gf_mx_v(global_inst_mutex); + } + } + } + + + if (ctx->ipid && (ctx->ipid != pid)) return GF_REQUIRES_NEW_INSTANCE; + + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + ctx->ipid = pid; + ctx->use_gl_texture = (ctx->fmode == NVDEC_GL) ? GF_TRUE : GF_FALSE; + +#ifndef GPAC_DISABLE_3D + if (ctx->use_gl_texture && (ctx->fmode==NVDEC_GL) && !ctx->gl_provider_requested) { + GF_Err e = gf_filter_request_opengl(filter); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to request an openGL provider (error %s), will not use OpenGL output\n", gf_error_to_string(e) )); + ctx->use_gl_texture = GF_FALSE; + } + ctx->gl_provider_requested = GF_TRUE; + } +#endif + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!prop) return GF_NOT_SUPPORTED; + ctx->codec_id = prop->value.uint; + + switch (ctx->codec_id) { + case GF_CODECID_MPEG1: + case GF_CODECID_MPEG2_SIMPLE: + case GF_CODECID_MPEG2_MAIN: + case GF_CODECID_MPEG2_SNR: + case GF_CODECID_MPEG2_SPATIAL: + case GF_CODECID_MPEG2_HIGH: + case GF_CODECID_MPEG2_422: + case GF_CODECID_MPEG4_PART2: + case GF_CODECID_AVC: + case GF_CODECID_HEVC: + break; + default: + return GF_NOT_SUPPORTED; + } + + if (!ctx->opid) + ctx->opid = gf_filter_pid_new(filter); + + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + + gf_filter_pid_set_framing_mode(ctx->ipid, GF_TRUE); + + switch (ctx->codec_id) { + case GF_CODECID_AVC: + case GF_CODECID_HEVC: + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + //not ready yet + if (!prop) return GF_OK; + break; + } + +#ifdef GPAC_DISABLE_3D + ctx->use_gl_texture = GF_FALSE; +#endif + + if (! cuda_ctx) { +#ifndef EMUL_NV_DLL + int major, minor; + char deviceName[256]; + res = cuDeviceGet(&cuda_dev, 0); + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to init cuda device %s\n", cudaGetErrorEnum(res) ) ); + return GF_IO_ERR; + } + + cuDeviceComputeCapability(&major, &minor, cuda_dev); + cuDeviceGetName(deviceName, 256, cuda_dev); + + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[NVDec] GPU Device %s (idx 0) has SM %d.%d compute capability\n", deviceName, major, minor)); + + if (ctx->use_gl_texture) { +#ifndef GPAC_DISABLE_3D + res = cuGLCtxCreate(&cuda_ctx, CU_CTX_BLOCKING_SYNC, cuda_dev); + +#ifdef LOAD_GL_1_5 + GET_GLFUN(glGenBuffers); + GET_GLFUN(glBindBuffer); + GET_GLFUN(glBufferData); +#endif + cuda_ctx_gl = GF_TRUE; + +#endif + } else { + res = cuCtxCreate(&cuda_ctx, CU_CTX_BLOCKING_SYNC, cuda_dev); + cuda_ctx_gl = GF_FALSE; + } + if (res != CUDA_SUCCESS) { + if (ctx->use_gl_texture) { + cuda_ctx_gl = GF_FALSE; + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[NVDec] Couldn't initialize cuda OpenGL context (error %s), retrying without OpenGL support\n", cudaGetErrorEnum(res) ) ); + res = cuCtxCreate(&cuda_ctx, CU_CTX_BLOCKING_SYNC, cuda_dev); + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to init cuda context %s\n", cudaGetErrorEnum(res) ) ); + } else { + ctx->use_gl_texture = GF_FALSE; + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to init cuda context %s\n", cudaGetErrorEnum(res) ) ); + } + + if (res != CUDA_SUCCESS) { + return GF_IO_ERR; + } + } +#endif + + } + + if (ctx->vmode == NVDEC_DXVA) + ctx->prefer_dec_mode = cudaVideoCreate_PreferDXVA; + else if (ctx->vmode == NVDEC_CUDA) + ctx->prefer_dec_mode = cudaVideoCreate_PreferCUDA; + else + ctx->prefer_dec_mode = cudaVideoCreate_PreferCUVID; + + if (ctx->unload == 2) { + global_nb_loaded_nvdec++; + if (!global_inst_mutex ) global_inst_mutex = gf_mx_new("NVDecGlobal"); + gf_mx_p(global_inst_mutex); + if (!global_unactive_decoders) global_unactive_decoders = gf_list_new(); + gf_mx_v(global_inst_mutex); + } + + + ctx->needs_resetup = GF_TRUE; + + return GF_OK; +} + +static Bool nvdec_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + NVDecCtx *ctx = (NVDecCtx *)gf_filter_get_udta(filter); + if (evt->base.type == GF_FEVT_PLAY) { + while (gf_list_count(ctx->frames)) { + NVDecFrame *f = gf_list_pop_back(ctx->frames); + gf_list_add(ctx->frames_res, f); + } + } + return GF_FALSE; +} + + +#if 0 + case GF_CODEC_ABORT: + while (gf_list_count(ctx->frames)) { + NVDecFrame *f = (NVDecFrame *) gf_list_pop_back(ctx->frames); + memset(f, 0, sizeof(NVDecFrame)); + gf_list_add(ctx->frames_res, f); + } + if (ctx->unload == 2) { + if (ctx->dec_inst) { + assert(global_unactive_decoders); + gf_mx_p(global_inst_mutex); + if (ctx->decode_error) { + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[NVDec] deactivating decoder %dx%d and destroying instance\n", ctx->width, ctx->height ) ); + nvdec_destroy_decoder(ctx->dec_inst); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[NVDec] deactivating decoder %dx%d\n", ctx->width, ctx->height ) ); + } + ctx->dec_inst->ctx = NULL; + gf_list_add(global_unactive_decoders, ctx->dec_inst); + ctx->dec_inst = NULL; + gf_mx_v(global_inst_mutex); + } + ctx->needs_resetup = GF_TRUE; + ctx->dec_create_error = CUDA_SUCCESS; + } else if (ctx->unload == 1) { + if (ctx->dec_inst) { + nvdec_destroy_decoder(ctx->dec_inst); + } + ctx->needs_resetup = GF_TRUE; + ctx->dec_create_error = CUDA_SUCCESS; + } + return GF_OK; + } +#endif + +static GF_Err nvdec_send_hw_frame(NVDecCtx *ctx); + +static void nvdec_reset_pcks(NVDecCtx *ctx) +{ + while (gf_list_count(ctx->src_packets)) { + GF_FilterPacket *pck = gf_list_pop_back(ctx->src_packets); + gf_filter_pck_unref(pck); + } +} + +static void nvdec_merge_pck_props(NVDecCtx *ctx, NVDecFrame *f, GF_FilterPacket *dst_pck) +{ + u32 i, count; + GF_FilterPacket *src_pck = NULL; + count = gf_list_count(ctx->src_packets); + for (i = 0; isrc_packets, i); + if (gf_filter_pck_get_cts(src_pck) == f->frame_info.timestamp) { + gf_filter_pck_merge_properties(src_pck, dst_pck); + gf_list_rem(ctx->src_packets, i); + gf_filter_pck_unref(src_pck); + return; + } + } + //not found ! + gf_filter_pck_set_cts(dst_pck, f->frame_info.timestamp); + if (!gf_filter_pck_get_interlaced(dst_pck) && !f->frame_info.progressive_frame) { + gf_filter_pck_set_interlaced(dst_pck, f->frame_info.top_field_first ? 1 : 2); + } +} + +static GF_Err nvdec_process(GF_Filter *filter) +{ + NVDecFrame *f; + CUdeviceptr map_mem = 0; + CUVIDPROCPARAMS params; + unsigned int pitch = 0; + GF_Err e; + u32 pck_size; + const u8 *data; + u8 *output; + GF_FilterPacket *ipck, *dst_pck; + CUVIDSOURCEDATAPACKET cu_pkt; + CUresult res; + NVDecCtx *ctx = (NVDecCtx *) gf_filter_get_udta(filter); + + ipck = gf_filter_pid_get_packet(ctx->ipid); + + if (ctx->needs_resetup) { + e = nvdec_configure_stream(filter, ctx); + if (e<0) return e; + //not ready + if (e==GF_EOS) return GF_OK; + } + + memset(&cu_pkt, 0, sizeof(CUVIDSOURCEDATAPACKET)); + cu_pkt.flags = CUVID_PKT_TIMESTAMP; + pck_size = 0; + data = NULL; + if (!ipck) { + if (!gf_filter_pid_is_eos(ctx->ipid)) + return GF_OK; + + cu_pkt.flags |= CUVID_PKT_ENDOFSTREAM; + ctx->skip_next_frame = GF_FALSE; + } else { + data = gf_filter_pck_get_data(ipck, &pck_size); + } + + if (ctx->dec_create_error) { + if (ipck) gf_filter_pid_drop_packet(ctx->ipid); + else if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_IO_ERR; + } + + if (data && ctx->nal_size_length) { + GF_BitStream *bs = gf_bs_new(ctx->nal_buffer, ctx->nal_buffer_alloc, GF_BITSTREAM_WRITE_DYN); + if (!bs) return GF_OUT_OF_MEM; + + if (gf_filter_pck_get_sap(ipck)) ctx->inject_xps = GF_TRUE; + + if (ctx->inject_xps) { + ctx->inject_xps = GF_FALSE; + gf_bs_write_data(bs, ctx->xps_buf, ctx->xps_buf_size); + } + + while (pck_size) { + u32 i, nal_size = 0; + for (i = 0; inal_size_length; i++) { + nal_size = (nal_size << 8) + ((u8)data[i]); + } + data += ctx->nal_size_length; + + if (pck_size < nal_size + ctx->nal_size_length) break; + + gf_bs_write_u32(bs, 1); + gf_bs_write_data(bs, data, nal_size); + data += nal_size; + pck_size -= nal_size + ctx->nal_size_length; + } + + gf_bs_get_content_no_truncate(bs, &ctx->nal_buffer, &pck_size, &ctx->nal_buffer_alloc); + gf_bs_del(bs); + data = ctx->nal_buffer; + } + + cu_pkt.payload_size = pck_size; + cu_pkt.payload = data; + if (ipck) cu_pkt.timestamp = gf_filter_pck_get_cts(ipck); + +#ifndef EMUL_NV_DLL + res = cuCtxPushCurrent(cuda_ctx); + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to push CUDA CTX %s\n", cudaGetErrorEnum(res) ) ); + } + if (ctx->skip_next_frame) { + ctx->skip_next_frame = GF_FALSE; + } else { + res = cuvidParseVideoData(ctx->dec_inst->cu_parser, &cu_pkt); + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] decoder instance %d failed to parse data %s\n", ctx->dec_inst->id, cudaGetErrorEnum(res) ) ); + } + } +#endif //EMUL_NV_DLL + + //queue reference to source packet props + if (ipck) { + gf_filter_pck_ref_props(&ipck); + gf_list_add(ctx->src_packets, ipck); + } + + if (ctx->reload_decoder_state) { + if (ctx->reload_decoder_state==2) { + nvdec_destroy_decoder(ctx->dec_inst); + } else { + ctx->skip_next_frame = GF_TRUE; + } + + ctx->reload_decoder_state = 0; + if (!ctx->out_size || !ctx->pix_fmt) { + cuCtxPopCurrent(NULL); + return GF_NOT_SUPPORTED; + } + + //need to setup decoder + if (! ctx->dec_inst->cu_decoder) { + nvdec_init_decoder(ctx); + } + cuCtxPopCurrent(NULL); + return GF_OK; + } + //drop packet + if (ipck) + gf_filter_pid_drop_packet(ctx->ipid); + + f = gf_list_pop_front(ctx->frames); + if (!f) { +#ifndef EMUL_NV_DLL + cuCtxPopCurrent(NULL); +#endif + if (!ipck && gf_filter_pid_is_eos(ctx->ipid)) { + nvdec_reset_pcks(ctx); + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + if (ctx->use_gl_texture || (ctx->fmode==NVDEC_SINGLE) ) { + assert(!ctx->pending_frame); + ctx->pending_frame = f; + return nvdec_send_hw_frame(ctx); + } + + assert(ctx->out_size); + dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->out_size, &output); + + memset(¶ms, 0, sizeof(params)); + params.progressive_frame = f->frame_info.progressive_frame; + params.top_field_first = f->frame_info.top_field_first; + + nvdec_merge_pck_props(ctx, f, dst_pck); + e = GF_OK; + if (gf_filter_pck_get_seek_flag(dst_pck)) { + gf_filter_pck_discard(dst_pck); + } else { + res = cuvidMapVideoFrame(ctx->dec_inst->cu_decoder, f->frame_info.picture_index, &map_mem, &pitch, ¶ms); + if (res == CUDA_SUCCESS) { + CUDA_MEMCPY2D mcpi; + memset(&mcpi, 0, sizeof(CUDA_MEMCPY2D)); + mcpi.srcMemoryType = CU_MEMORYTYPE_DEVICE; + mcpi.srcDevice = map_mem; + mcpi.srcPitch = pitch; + + mcpi.dstMemoryType = CU_MEMORYTYPE_HOST; + mcpi.dstHost = output; + mcpi.dstPitch = ctx->stride; + mcpi.WidthInBytes = MIN(pitch, ctx->stride); + mcpi.Height = ctx->height; + + res = cuMemcpy2D(&mcpi); + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to copy Y video plane from GPU to host mem %s\n", cudaGetErrorEnum(res))); + e = GF_IO_ERR; + } else { + + mcpi.srcDevice = map_mem + ctx->height * pitch; + mcpi.dstHost = output + ctx->stride * ctx->height; + mcpi.dstPitch = ctx->stride_uv; + mcpi.WidthInBytes = MIN(pitch, ctx->stride); + mcpi.Height = ctx->uv_height; + + res = cuMemcpy2D(&mcpi); + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to copy UV video plane from GPU to host mem %s\n", cudaGetErrorEnum(res))); + e = GF_IO_ERR; + } + } + cuvidUnmapVideoFrame(ctx->dec_inst->cu_decoder, map_mem); + + gf_filter_pck_send(dst_pck); + + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to map video frame data %s\n", cudaGetErrorEnum(res))); + e = GF_IO_ERR; + gf_filter_pck_discard(dst_pck); + } + } + cuCtxPopCurrent(NULL); + + memset(f, 0, sizeof(NVDecFrame)); + gf_list_add(ctx->frames_res, f); + + return e; +} + +void nvframe_release(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck) +{ + GF_FilterFrameInterface *frame = gf_filter_pck_get_frame_interface(pck); + NVDecFrame *f = (NVDecFrame*) frame->user_data; + NVDecCtx *ctx = (NVDecCtx *)f->ctx; + + memset(f, 0, sizeof(NVDecFrame)); + gf_list_add(ctx->frames_res, f); +} + +#ifndef GPAC_DISABLE_3D + +/*Define codec matrix*/ +typedef struct __matrix GF_NVCodecMatrix; + +GF_Err nvframe_get_gl_texture(GF_FilterFrameInterface *frame, u32 plane_idx, u32 *gl_tex_format, u32 *gl_tex_id, GF_NVCodecMatrix * texcoordmatrix) +{ + CUDA_MEMCPY2D mcpi; + CUVIDPROCPARAMS params; + CUresult res; + GF_Err e = GF_OK; + CUdeviceptr tx_data, vid_data; + size_t tx_pitch; + u32 vid_pitch; + u32 pbo_id, tx_id, gl_fmt, gl_btype = GL_UNSIGNED_BYTE; + NVDecFrame *f = (NVDecFrame *)frame->user_data; + NVDecCtx *ctx = (NVDecCtx *)f->ctx; + + if (plane_idx>1) return GF_BAD_PARAM; + + res = cuCtxPushCurrent(cuda_ctx); + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to push CUDA CTX %s\n", cudaGetErrorEnum(res) ) ); + } + + if (! *gl_tex_id && ! plane_idx && ctx->y_tx_id ) { + cuGLUnregisterBufferObject(ctx->y_pbo_id); + ctx->y_pbo_id = 0; + ctx->y_tx_id = 0; + } + if (! *gl_tex_id && plane_idx && ctx->uv_tx_id ) { + cuGLUnregisterBufferObject(ctx->uv_pbo_id); + ctx->uv_pbo_id = 0; + ctx->uv_tx_id = 0; + } + +#ifdef ENABLE_10BIT_OUTPUT + if ((ctx->bpp_luma>8) || (ctx->bpp_chroma>8)) { + gl_btype = GL_UNSIGNED_SHORT; + } +#endif + if (!plane_idx) { + if (!ctx->y_pbo_id) { + glGenBuffers(1, &ctx->y_pbo_id); + glGenTextures(1, &ctx->y_tx_id); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, ctx->y_pbo_id); + glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, ctx->stride * ctx->height, NULL, GL_STREAM_DRAW_ARB); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); + + cuGLRegisterBufferObject(ctx->y_pbo_id); + + glBindTexture(GL_TEXTURE_2D, ctx->y_tx_id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, ctx->width, ctx->height, 0, GL_LUMINANCE, gl_btype, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + f->y_mapped = GF_FALSE; + } + *gl_tex_format = GL_TEXTURE_2D; + *gl_tex_id = tx_id = ctx->y_tx_id; + if (f->y_mapped) { + cuCtxPopCurrent(NULL); + return GF_OK; + } + f->y_mapped = GF_TRUE; + pbo_id = ctx->y_pbo_id; + gl_fmt = GL_LUMINANCE; + } else { + if (!ctx->uv_pbo_id) { + glGenBuffers(1, &ctx->uv_pbo_id); + glGenTextures(1, &ctx->uv_tx_id); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, ctx->uv_pbo_id); + glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, ctx->stride * ctx->height / 2, NULL, GL_STREAM_DRAW_ARB); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); + + cuGLRegisterBufferObject(ctx->uv_pbo_id); + + glBindTexture(GL_TEXTURE_2D, ctx->uv_tx_id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, ctx->width/2, ctx->height/2, 0, GL_LUMINANCE_ALPHA, gl_btype, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + f->uv_mapped = GF_FALSE; + } + *gl_tex_format = GL_TEXTURE_2D; + *gl_tex_id = tx_id = ctx->uv_tx_id; + if (f->uv_mapped) { + cuCtxPopCurrent(NULL); + return GF_OK; + } + f->uv_mapped = GF_TRUE; + pbo_id = ctx->uv_pbo_id; + gl_fmt = GL_LUMINANCE_ALPHA; + } + + cuGLMapBufferObject(&tx_data, &tx_pitch, pbo_id); + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to map GL texture data %s\n", cudaGetErrorEnum(res) ) ); + return GF_IO_ERR; + } + assert(tx_pitch != 0); + + memset(¶ms, 0, sizeof(params)); + params.progressive_frame = f->frame_info.progressive_frame; + //params.second_field = 0; + params.top_field_first = f->frame_info.top_field_first; + res = cuvidMapVideoFrame(ctx->dec_inst->cu_decoder, f->frame_info.picture_index, &vid_data, &vid_pitch, ¶ms); + + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to map decoded picture data %s\n", cudaGetErrorEnum(res) ) ); + return GF_IO_ERR; + } + assert(vid_pitch != 0); + + memset(&mcpi, 0, sizeof(CUDA_MEMCPY2D)); + mcpi.srcMemoryType = CU_MEMORYTYPE_DEVICE; + if (plane_idx) { + mcpi.srcDevice = vid_data + ctx->height * vid_pitch; + tx_pitch *= 2; //2 bytes per pixel + } else { + mcpi.srcDevice = vid_data; + } + mcpi.srcPitch = vid_pitch; + + mcpi.dstMemoryType = CU_MEMORYTYPE_DEVICE; + mcpi.dstDevice = tx_data; + mcpi.dstPitch = tx_pitch / ctx->height; + + mcpi.WidthInBytes = MIN(mcpi.dstPitch, vid_pitch); + mcpi.Height = ctx->height; + if (plane_idx) mcpi.Height /= 2; + + res = cuMemcpy2D(&mcpi); + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to copy Y video plane from GPU to host mem %s\n", cudaGetErrorEnum(res) ) ); + e = GF_IO_ERR; + } + + cuvidUnmapVideoFrame(ctx->dec_inst->cu_decoder, vid_data); + cuGLUnmapBufferObject(pbo_id); + + + cuCtxPopCurrent(NULL); + + /*bind PBO to texture and call glTexSubImage2D only after PBO transfer is queued, otherwise we'll have a one frame delay*/ + glBindTexture(GL_TEXTURE_2D, tx_id); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo_id); + +#ifdef ENABLE_10BIT_OUTPUT + if (ctx->bpp_chroma+ctx->bpp_luma>16) { + Float a, b; +#error "FIX NVDEC GL color mapping in 10 bit" + a = 65535.0f / (65472.0f - 63.0f); + b = -63.0f * a / 65535.0f; + + glPixelStorei(GL_UNPACK_ALIGNMENT, 2); + //glPixelStorei(GL_UNPACK_SWAP_BYTES, 1); + //glPixelStorei(GL_UNPACK_LSB_FIRST, 1); + //we use 10 bits but GL will normalise using 16 bits, so we need to multiply the nomralized result by 2^6 + //glPixelTransferf(GL_RED_BIAS, 0.00096317f); + //glPixelTransferf(GL_RED_SCALE, 0.000015288f); + + glPixelTransferf(GL_RED_SCALE, a); + glPixelTransferf(GL_RED_BIAS, b); + + if (plane_idx) { + glPixelTransferf(GL_ALPHA_SCALE, a); + glPixelTransferf(GL_ALPHA_BIAS, b); + } + } +#endif + if (!plane_idx) { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ctx->width, ctx->height, gl_fmt , gl_btype, NULL); + } else { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ctx->width/2, ctx->height/2, gl_fmt , gl_btype, NULL); + } + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); + glBindTexture(GL_TEXTURE_2D, 0); + + return e; +} + +#endif + +GF_Err nvframe_get_frame(GF_FilterFrameInterface *frame, u32 plane_idx, const u8 **outPlane, u32 *outStride) +{ + unsigned int pitch = 0; + GF_Err e = GF_OK; + NVDecFrame *f = (NVDecFrame *)frame->user_data; + NVDecCtx *ctx = (NVDecCtx *)f->ctx; + + if (plane_idx>=ctx->nb_planes) return GF_BAD_PARAM; + + e = GF_OK; + if (!f->y_mapped) { + CUVIDPROCPARAMS params; + CUdeviceptr map_mem = 0; + CUresult res; + + if (ctx->out_size > ctx->single_frame_data_alloc) { + ctx->single_frame_data_alloc = ctx->out_size; + ctx->single_frame_data = gf_realloc(ctx->single_frame_data, ctx->out_size); + } + f->y_mapped = GF_TRUE; + + res = cuCtxPushCurrent(cuda_ctx); + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to push CUDA CTX %s\n", cudaGetErrorEnum(res))); + return GF_IO_ERR; + } + + memset(¶ms, 0, sizeof(params)); + params.progressive_frame = f->frame_info.progressive_frame; + params.top_field_first = f->frame_info.top_field_first; + + res = cuvidMapVideoFrame(ctx->dec_inst->cu_decoder, f->frame_info.picture_index, &map_mem, &pitch, ¶ms); + if (res == CUDA_SUCCESS) { + CUDA_MEMCPY2D mcpi; + memset(&mcpi, 0, sizeof(CUDA_MEMCPY2D)); + mcpi.srcMemoryType = CU_MEMORYTYPE_DEVICE; + mcpi.srcDevice = map_mem; + mcpi.srcPitch = pitch; + + mcpi.dstMemoryType = CU_MEMORYTYPE_HOST; + mcpi.dstHost = ctx->single_frame_data; + mcpi.dstPitch = ctx->stride; + mcpi.WidthInBytes = MIN(pitch, ctx->stride); + mcpi.Height = ctx->height; + + res = cuMemcpy2D(&mcpi); + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to copy Y video plane from GPU to host mem %s\n", cudaGetErrorEnum(res))); + e = GF_IO_ERR; + } + else { + + mcpi.srcDevice = map_mem + ctx->height * pitch; + mcpi.dstHost = ctx->single_frame_data + ctx->stride * ctx->height; + mcpi.dstPitch = ctx->stride_uv; + mcpi.Height = ctx->uv_height; + + res = cuMemcpy2D(&mcpi); + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to copy UV video plane from GPU to host mem %s\n", cudaGetErrorEnum(res))); + e = GF_IO_ERR; + } + } + cuvidUnmapVideoFrame(ctx->dec_inst->cu_decoder, map_mem); + } + else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to map video frame %s\n", cudaGetErrorEnum(res))); + e = GF_IO_ERR; + } + cuCtxPopCurrent(NULL); + } + if (e) return e; + + switch (plane_idx) { + case 0: + *outPlane = ctx->single_frame_data; + *outStride = ctx->stride; + break; + case 1: + *outPlane = ctx->single_frame_data + ctx->stride * ctx->height; + *outStride = ctx->stride_uv; + break; + case 2: + *outPlane = ctx->single_frame_data + ctx->stride * ctx->height + ctx->stride_uv * ctx->uv_height; + *outStride = ctx->stride_uv; + break; + default: + return GF_BAD_PARAM; + } + return GF_OK; +} + + +GF_Err nvdec_send_hw_frame(NVDecCtx *ctx) +{ + GF_FilterPacket *dst_pck; + NVDecFrame *f; + + if (!ctx->pending_frame) return GF_BAD_PARAM; + f = ctx->pending_frame; + ctx->pending_frame = NULL; + + f->gframe.user_data = f; + f->gframe.get_plane = nvframe_get_frame; +#ifndef GPAC_DISABLE_3D + f->gframe.get_gl_texture = nvframe_get_gl_texture; +#endif + + if (ctx->frame_size_changed) { + ctx->frame_size_changed = GF_FALSE; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->width)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->height)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->stride)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, &PROP_UINT(ctx->pix_fmt)); + } + + + if (!gf_list_count(ctx->frames) && ctx->needs_resetup) + f->gframe.flags = GF_FRAME_IFCE_BLOCKING; + + dst_pck = gf_filter_pck_new_frame_interface(ctx->opid, &f->gframe, nvframe_release); + + nvdec_merge_pck_props(ctx, f, dst_pck); + if (gf_filter_pck_get_seek_flag(dst_pck)) { + gf_filter_pck_discard(dst_pck); + memset(f, 0, sizeof(NVDecFrame)); + gf_list_add(ctx->frames_res, f); + } else { + gf_filter_pck_send(dst_pck); + } + + return GF_OK; +} + + + + +static u32 cuvid_load_state = 0; +static u32 nb_cuvid_inst=0; +static void init_cuda_sdk() +{ + if (!cuvid_load_state) { +#ifdef EMUL_NV_DLL + cuvid_load_state = 2; + nb_cuvid_inst++; +#else + CUresult res; + int device_count; + res = cuInit(0, __CUDA_API_VERSION); + nb_cuvid_inst++; + cuvid_load_state = 1; + if (res == CUDA_ERROR_SHARED_OBJECT_INIT_FAILED) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[NVDec] cuda lib not found on system\n") ); + } else if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[NVDec] failed to init cuda %s\n", cudaGetErrorEnum(res) ) ); + } else { + res = cuDeviceGetCount(&device_count); + if (res != CUDA_SUCCESS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] failed to query cuda/nvidia cards %s\n", cudaGetErrorEnum(res) ) ); + } else { + if (! device_count) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[NVDec] no device found\n" ) ); + } else { + cuvid_load_state = 2; + } + } + } +#endif + + } else { + nb_cuvid_inst++; + } +} + +static GF_Err nvdec_initialize(GF_Filter *filter) +{ + NVDecCtx *ctx = gf_filter_get_udta(filter); + + ctx->frames = gf_list_new(); + ctx->frames_res = gf_list_new(); + ctx->src_packets = gf_list_new(); + return GF_OK; +} + +static void nvdec_finalize(GF_Filter *filter) +{ + NVDecCtx *ctx = gf_filter_get_udta(filter); + + nvdec_reset_pcks(ctx); + gf_list_del(ctx->src_packets); + + if (!global_nb_loaded_nvdec && global_unactive_decoders) { + while (gf_list_count(global_unactive_decoders)) { + NVDecInstance *inst = gf_list_pop_back(global_unactive_decoders); + nvdec_destroy_decoder(inst); + if (inst->cu_parser) cuvidDestroyVideoParser(inst->cu_parser); + gf_free(inst); + } + gf_list_del(global_unactive_decoders); + + gf_mx_del(global_inst_mutex); + } + + if (ctx->dec_inst) { + nvdec_destroy_decoder(ctx->dec_inst); + if (ctx->dec_inst->cu_parser) cuvidDestroyVideoParser(ctx->dec_inst->cu_parser); + gf_free(ctx->dec_inst); + } + + + assert(nb_cuvid_inst); + nb_cuvid_inst--; + if (!nb_cuvid_inst) { + if (cuda_ctx) cuCtxDestroy(cuda_ctx); + cuda_ctx = NULL; + cuUninit(); + cuvid_load_state = 0; + } + while (gf_list_count(ctx->frames)) { + NVDecFrame *f = (NVDecFrame *) gf_list_pop_back(ctx->frames); + gf_free(f); + } + gf_list_del(ctx->frames); + while (gf_list_count(ctx->frames_res)) { + NVDecFrame *f = (NVDecFrame *) gf_list_pop_back(ctx->frames_res); + gf_free(f); + } + gf_list_del(ctx->frames_res); + + if (ctx->single_frame_data) gf_free(ctx->single_frame_data); + if (ctx->xps_buf) gf_free(ctx->xps_buf); + if (ctx->nal_buffer) gf_free(ctx->nal_buffer); +} + + +static const GF_FilterCapability NVDecCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_MPEG4_PART2), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_MPEG2_MAIN), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_MPEG2_SNR), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_MPEG2_SPATIAL), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_MPEG2_HIGH), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_MPEG2_422), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_HEVC), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_AVC), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED,GF_PROP_PID_TILE_BASE, GF_TRUE), + //CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_VC1), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_RAW) +}; + +#define OFFS(_n) #_n, offsetof(NVDecCtx, _n) + +static const GF_FilterArgs NVDecArgs[] = +{ + { OFFS(num_surfaces), "number of hardware surfaces to allocate", GF_PROP_UINT, "20", NULL, GF_FS_ARG_HINT_ADVANCED }, + { OFFS(unload), "decoder unload mode\n" + "- no: keep inactive decoder alive\n" + "- destroy: destroy inactive decoder\n" + "- reuse: detach decoder from inactive PIDs and reattach to active ones", GF_PROP_UINT, "no", "no|destroy|reuse", GF_FS_ARG_HINT_EXPERT }, + { OFFS(vmode), "video decoder backend\n" + "- cuvid: use dedicated video engines directly\n" + "- cuda: use a CUDA-based decoder if faster than dedicated engines\n" + "- dxva: go through DXVA internally if possible (requires D3D9 interop)", GF_PROP_UINT, "cuvid", "cuvid|cuda|dxva", GF_FS_ARG_HINT_ADVANCED }, + { OFFS(fmode), "frame output mode\n" + "- copy: each frame is copied and dispatched\n" + "- single: frame data is only retrieved when used, single memory space for all frames (not safe if multiple consummers)\n" + "- gl: frame data is mapped to an OpenGL texture" + , GF_PROP_UINT, "gl", "copy|single|gl|", 0 }, + + { 0 } +}; + +GF_FilterRegister NVDecRegister = { + .name = "nvdec", + GF_FS_SET_DESCRIPTION("NVidia decoder") + GF_FS_SET_HELP("This filter decodes MPEG-2, MPEG-4 Part 2, AVC|H264 and HEVC streams through NVideia decoder. It allows GPU frame dispatch or direct frame copy.") + .private_size = sizeof(NVDecCtx), + SETCAPS(NVDecCaps), + .flags = GF_FS_REG_CONFIGURE_MAIN_THREAD, + .initialize = nvdec_initialize, + .finalize = nvdec_finalize, + .args = NVDecArgs, + .configure_pid = nvdec_configure_pid, + .process = nvdec_process, + .process_event = nvdec_process_event +}; + + +const GF_FilterRegister *nvdec_register(GF_FilterSession *session) +{ + init_cuda_sdk(); + //do not register if no SDK + if (cuvid_load_state != 2) { + // this is man / md generation, load filter + if (!gf_opts_get_bool("temp", "gendoc")) + return NULL; + NVDecRegister.version = "! Warning: CUVID SDK NOT AVAILABLE ON THIS SYSTEM !"; + } + + return &NVDecRegister; +} + +#else + +const GF_FilterRegister *nvdec_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // defined(WIN32) || defined(GPAC_CONFIG_LINUX) || defined(GPAC_CONFIG_DARWIN) + + diff --git a/src/filters/dec_nvdec_sdk.c b/src/filters/dec_nvdec_sdk.c new file mode 100644 index 0000000..27535d2 --- /dev/null +++ b/src/filters/dec_nvdec_sdk.c @@ -0,0 +1,712 @@ +/* + * Copyright 1993-2015 NVIDIA Corporation. All rights reserved. + * + * Please refer to the NVIDIA end user license agreement (EULA) associated + * with this source code for terms and conditions that govern your use of + * this software. Any use, reproduction, disclosure, or distribution of + * this software and related documentation outside the terms of the EULA + * is strictly prohibited. + * + */ + + +// With these flags defined, this source file will dynamically +// load the corresponding functions. Disabled by default. +#define __CUDA_API_VERSION 4000 + +#include +#include + +#include + + +#if (defined(WIN32) || defined(GPAC_CONFIG_LINUX) || defined(GPAC_CONFIG_DARWIN)) + +#include "dec_nvdec_sdk.h" + +tcuInit *_cuInit; +tcuDriverGetVersion *cuDriverGetVersion; +tcuDeviceGet *cuDeviceGet; +tcuDeviceGetCount *cuDeviceGetCount; +tcuDeviceGetName *cuDeviceGetName; +tcuDeviceComputeCapability *cuDeviceComputeCapability; +tcuDeviceTotalMem *cuDeviceTotalMem; +tcuDeviceGetProperties *cuDeviceGetProperties; +tcuDeviceGetAttribute *cuDeviceGetAttribute; +tcuCtxCreate *cuCtxCreate; +tcuCtxDestroy *cuCtxDestroy; +tcuCtxAttach *cuCtxAttach; +tcuCtxDetach *cuCtxDetach; +tcuCtxPushCurrent *cuCtxPushCurrent; +tcuCtxPopCurrent *cuCtxPopCurrent; +tcuCtxGetCurrent *cuCtxGetCurrent; +tcuCtxSetCurrent *cuCtxSetCurrent; +tcuCtxGetDevice *cuCtxGetDevice; +tcuCtxSynchronize *cuCtxSynchronize; +tcuModuleLoad *cuModuleLoad; +tcuModuleLoadData *cuModuleLoadData; +tcuModuleLoadDataEx *cuModuleLoadDataEx; +tcuModuleLoadFatBinary *cuModuleLoadFatBinary; +tcuModuleUnload *cuModuleUnload; +tcuModuleGetFunction *cuModuleGetFunction; +tcuModuleGetGlobal *cuModuleGetGlobal; +tcuModuleGetTexRef *cuModuleGetTexRef; +tcuModuleGetSurfRef *cuModuleGetSurfRef; +tcuMemGetInfo *cuMemGetInfo; +tcuMemAlloc *cuMemAlloc; +tcuMemAllocPitch *cuMemAllocPitch; +tcuMemFree *cuMemFree; +tcuMemGetAddressRange *cuMemGetAddressRange; +tcuMemAllocHost *cuMemAllocHost; +tcuMemFreeHost *cuMemFreeHost; +tcuMemHostAlloc *cuMemHostAlloc; +tcuMemHostGetDevicePointer *cuMemHostGetDevicePointer; +tcuMemHostRegister *cuMemHostRegister; +tcuMemHostUnregister *cuMemHostUnregister; +tcuMemcpyHtoD *cuMemcpyHtoD; +tcuMemcpyDtoH *cuMemcpyDtoH; +tcuMemcpyDtoD *cuMemcpyDtoD; +tcuMemcpyDtoA *cuMemcpyDtoA; +tcuMemcpyAtoD *cuMemcpyAtoD; +tcuMemcpyHtoA *cuMemcpyHtoA; +tcuMemcpyAtoH *cuMemcpyAtoH; +tcuMemcpyAtoA *cuMemcpyAtoA; +tcuMemcpy2D *cuMemcpy2D; +tcuMemcpy2DUnaligned *cuMemcpy2DUnaligned; +tcuMemcpy3D *cuMemcpy3D; +tcuMemcpyHtoDAsync *cuMemcpyHtoDAsync; +tcuMemcpyDtoHAsync *cuMemcpyDtoHAsync; +tcuMemcpyDtoDAsync *cuMemcpyDtoDAsync; +tcuMemcpyHtoAAsync *cuMemcpyHtoAAsync; +tcuMemcpyAtoHAsync *cuMemcpyAtoHAsync; +tcuMemcpy2DAsync *cuMemcpy2DAsync; +tcuMemcpy3DAsync *cuMemcpy3DAsync; +tcuMemcpy *cuMemcpy; +tcuMemcpyPeer *cuMemcpyPeer; +tcuMemsetD8 *cuMemsetD8; +tcuMemsetD16 *cuMemsetD16; +tcuMemsetD32 *cuMemsetD32; +tcuMemsetD2D8 *cuMemsetD2D8; +tcuMemsetD2D16 *cuMemsetD2D16; +tcuMemsetD2D32 *cuMemsetD2D32; +tcuFuncSetBlockShape *cuFuncSetBlockShape; +tcuFuncSetSharedSize *cuFuncSetSharedSize; +tcuFuncGetAttribute *cuFuncGetAttribute; +tcuFuncSetCacheConfig *cuFuncSetCacheConfig; +tcuLaunchKernel *cuLaunchKernel; +tcuArrayCreate *cuArrayCreate; +tcuArrayGetDescriptor *cuArrayGetDescriptor; +tcuArrayDestroy *cuArrayDestroy; +tcuArray3DCreate *cuArray3DCreate; +tcuArray3DGetDescriptor *cuArray3DGetDescriptor; +tcuTexRefCreate *cuTexRefCreate; +tcuTexRefDestroy *cuTexRefDestroy; +tcuTexRefSetArray *cuTexRefSetArray; +tcuTexRefSetAddress *cuTexRefSetAddress; +tcuTexRefSetAddress2D *cuTexRefSetAddress2D; +tcuTexRefSetFormat *cuTexRefSetFormat; +tcuTexRefSetAddressMode *cuTexRefSetAddressMode; +tcuTexRefSetFilterMode *cuTexRefSetFilterMode; +tcuTexRefSetFlags *cuTexRefSetFlags; +tcuTexRefGetAddress *cuTexRefGetAddress; +tcuTexRefGetArray *cuTexRefGetArray; +tcuTexRefGetAddressMode *cuTexRefGetAddressMode; +tcuTexRefGetFilterMode *cuTexRefGetFilterMode; +tcuTexRefGetFormat *cuTexRefGetFormat; +tcuTexRefGetFlags *cuTexRefGetFlags; +tcuSurfRefSetArray *cuSurfRefSetArray; +tcuSurfRefGetArray *cuSurfRefGetArray; +tcuParamSetSize *cuParamSetSize; +tcuParamSeti *cuParamSeti; +tcuParamSetf *cuParamSetf; +tcuParamSetv *cuParamSetv; +tcuParamSetTexRef *cuParamSetTexRef; +tcuLaunch *cuLaunch; +tcuLaunchGrid *cuLaunchGrid; +tcuLaunchGridAsync *cuLaunchGridAsync; +tcuEventCreate *cuEventCreate; +tcuEventRecord *cuEventRecord; +tcuEventQuery *cuEventQuery; +tcuEventSynchronize *cuEventSynchronize; +tcuEventDestroy *cuEventDestroy; +tcuEventElapsedTime *cuEventElapsedTime; +tcuStreamCreate *cuStreamCreate; +tcuStreamQuery *cuStreamQuery; +tcuStreamSynchronize *cuStreamSynchronize; +tcuStreamDestroy *cuStreamDestroy; +tcuGraphicsUnregisterResource *cuGraphicsUnregisterResource; +tcuGraphicsSubResourceGetMappedArray *cuGraphicsSubResourceGetMappedArray; +tcuGraphicsResourceGetMappedPointer *cuGraphicsResourceGetMappedPointer; +tcuGraphicsResourceSetMapFlags *cuGraphicsResourceSetMapFlags; +tcuGraphicsMapResources *cuGraphicsMapResources; +tcuGraphicsUnmapResources *cuGraphicsUnmapResources; +tcuGetExportTable *cuGetExportTable; +tcuCtxSetLimit *cuCtxSetLimit; +tcuCtxGetLimit *cuCtxGetLimit; +tcuMemHostGetFlags *cuMemHostGetFlags; + + // GL/CUDA interop + #if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) + tcuWGLGetDevice *cuWGLGetDevice; + #endif + + //#if __CUDA_API_VERSION >= 3020 + tcuGLCtxCreate *cuGLCtxCreate; + tcuGLCtxCreate *cuGLCtxCreate_v2; + tcuGLMapBufferObject *cuGLMapBufferObject; + tcuGLMapBufferObject *cuGLMapBufferObject_v2; + tcuGLMapBufferObjectAsync *cuGLMapBufferObjectAsync; + //#endif + +#if __CUDA_API_VERSION >= 6050 + tcuGLGetDevices *cuGLGetDevices; +#endif + + tcuGLInit *cuGLInit; // deprecated in CUDA 3.0 + tcuGraphicsGLRegisterBuffer *cuGraphicsGLRegisterBuffer; + tcuGraphicsGLRegisterImage *cuGraphicsGLRegisterImage; + tcuGLSetBufferObjectMapFlags *cuGLSetBufferObjectMapFlags; + tcuGLRegisterBufferObject *cuGLRegisterBufferObject; + + tcuGLUnmapBufferObject *cuGLUnmapBufferObject; + tcuGLUnmapBufferObjectAsync *cuGLUnmapBufferObjectAsync; + + tcuGLUnregisterBufferObject *cuGLUnregisterBufferObject; + tcuGLGetDevices *cuGLGetDevices; // CUDA 6.5 only + + + +#if !defined(__APPLE__) +tcuvidCreateVideoSource *cuvidCreateVideoSource; +tcuvidCreateVideoSourceW *cuvidCreateVideoSourceW; +tcuvidDestroyVideoSource *cuvidDestroyVideoSource; +tcuvidSetVideoSourceState *cuvidSetVideoSourceState; +tcuvidGetVideoSourceState *cuvidGetVideoSourceState; +tcuvidGetSourceVideoFormat *cuvidGetSourceVideoFormat; +tcuvidGetSourceAudioFormat *cuvidGetSourceAudioFormat; +#endif + +tcuvidCreateVideoParser *cuvidCreateVideoParser; +tcuvidParseVideoData *cuvidParseVideoData; +tcuvidDestroyVideoParser *cuvidDestroyVideoParser; + +tcuvidCreateDecoder *cuvidCreateDecoder; +tcuvidDestroyDecoder *cuvidDestroyDecoder; +tcuvidDecodePicture *cuvidDecodePicture; + +tcuvidMapVideoFrame *cuvidMapVideoFrame; +tcuvidUnmapVideoFrame *cuvidUnmapVideoFrame; + +#if defined(WIN64) || defined(_WIN64) || defined(__x86_64) || defined(AMD64) || defined(_M_AMD64) +tcuvidMapVideoFrame64 *cuvidMapVideoFrame64; +tcuvidUnmapVideoFrame64 *cuvidUnmapVideoFrame64; +#endif + +//tcuvidGetVideoFrameSurface *cuvidGetVideoFrameSurface; +tcuvidCtxLockCreate *cuvidCtxLockCreate; +tcuvidCtxLockDestroy *cuvidCtxLockDestroy; +tcuvidCtxLock *cuvidCtxLock; +tcuvidCtxUnlock *cuvidCtxUnlock; + + + +#define STRINGIFY(X) #X + +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) +#include + +#ifdef UNICODE +static LPCWSTR __CudaLibName = L"nvcuda.dll"; +static LPCWSTR __CuvidLibName = L"nvcuvid.dll"; +#else +static LPCSTR __CudaLibName = "nvcuda.dll"; +static LPCSTR __CuvidLibName = "nvcuvid.dll"; +#endif + +typedef HMODULE CUDADRIVER; +typedef HMODULE CUVIDDRIVER; + +static CUresult LOAD_LIBRARY_CUDA(CUDADRIVER *pInstance) +{ + *pInstance = LoadLibrary(__CudaLibName); + if (*pInstance == NULL) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[NVDec] LoadLibrary \"%s\" failed!\n", __CudaLibName)); + return CUDA_ERROR_UNKNOWN; + } + return CUDA_SUCCESS; +} + +static CUresult LOAD_LIBRARY_CUVID(CUVIDDRIVER *pInstance) +{ + *pInstance = LoadLibrary(__CuvidLibName); + if (*pInstance == NULL) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[NVDec] LoadLibrary \"%s\" failed!\n", __CuvidLibName)); + return CUDA_ERROR_UNKNOWN; + } + return CUDA_SUCCESS; +} + +#define GET_PROC_EX(name, alias, required) \ + alias = (t##name *)GetProcAddress(curr_lib, #name); \ + if (alias == NULL && required) { \ + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[NVDec] Failed to find required function \"%s\" \n", \ + #name)); \ + } + +#define GET_PROC_EX_V2(name, alias, required) \ + alias = (t##name *)GetProcAddress(curr_lib, STRINGIFY(name##_v2));\ + if (alias == NULL && required) { \ + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[NVDec] Failed to find required function \"%s\" \n", \ + STRINGIFY(name##_v2))); \ + } + +#elif defined(__unix__) || defined(__APPLE__) || defined(__MACOSX) + +#include + +#if defined(__APPLE__) || defined(__MACOSX) +static char __CudaLibName[] = "/usr/local/cuda/lib/libcuda.dylib"; +#else +static char __CudaLibName[] = "libcuda.so"; +#endif + +static char __CuvidLibName[] = "libnvcuvid.so"; + +typedef void *CUVIDDRIVER; + +static CUresult LOAD_LIBRARY_CUDA(CUDADRIVER *pInstance) +{ + *pInstance = dlopen(__CudaLibName, RTLD_NOW); + if (*pInstance == NULL) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[NVDec] dlopen \"%s\" failed!\n", __CudaLibName)); + return CUDA_ERROR_SHARED_OBJECT_INIT_FAILED; + } + return CUDA_SUCCESS; +} + +static CUresult LOAD_LIBRARY_CUVID(CUVIDDRIVER *pInstance) +{ + *pInstance = dlopen(__CuvidLibName, RTLD_NOW); + if (*pInstance == NULL) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[NVDec]dlopen \"%s\" failed!\n", __CuvidLibName)); + return CUDA_ERROR_UNKNOWN; + } + return CUDA_SUCCESS; +} + +#define GET_PROC_EX(name, alias, required) \ + alias = (t##name *)dlsym(curr_lib, #name); \ + if (alias == NULL && required) { \ + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[NVDec] Failed to find required function \"%s\"\n", \ + #name)); \ + } + +#define GET_PROC_EX_V2(name, alias, required) \ + alias = (t##name *)dlsym(curr_lib, STRINGIFY(name##_v2)); \ + if (alias == NULL && required) { \ + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[NVDec]Failed to find required function \"%s\"\n", \ + STRINGIFY(name##_v2))); \ + } + +#else +#error unsupported platform +#endif + +#define CHECKED_CALL(call) \ + do { \ + CUresult result = (call); \ + if (CUDA_SUCCESS != result) { \ + return result; \ + } \ + } while(0) + +#define GET_PROC_REQUIRED(name) GET_PROC_EX(name,name,1) +#define GET_PROC_OPTIONAL(name) GET_PROC_EX(name,name,0) +#define GET_PROC(name) GET_PROC_REQUIRED(name) +#define GET_PROC_V2(name) GET_PROC_EX_V2(name,name,1) + +CUresult CUDAAPI cuInitGL(unsigned int Flags, int cudaVersion, CUDADRIVER curr_lib) +{ + + return CUDA_SUCCESS; +} + +CUDADRIVER CudaDrvLib=0; +CUVIDDRIVER CuvidDrvLib = 0; + +void CUDAAPI cuUninit() +{ +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) + if (CudaDrvLib) FreeLibrary((HMODULE)CudaDrvLib); + if (CuvidDrvLib) FreeLibrary((HMODULE)CuvidDrvLib); +#else + if (CudaDrvLib) dlclose(CudaDrvLib); + if (CuvidDrvLib) dlclose(CuvidDrvLib); +#endif + CudaDrvLib = 0; + CuvidDrvLib = 0; +} + +CUresult CUDAAPI cuInit(unsigned int Flags, int cudaVersion) +{ + int driverVer = 1000; + CUDADRIVER curr_lib ; + + if (CudaDrvLib) return CUDA_SUCCESS; + + assert(CudaDrvLib == 0); + + CHECKED_CALL(LOAD_LIBRARY_CUDA(&CudaDrvLib)); + curr_lib = CudaDrvLib; + + // cuInit is required; alias it to _cuInit + GET_PROC_EX(cuInit, _cuInit, 1); + CHECKED_CALL(_cuInit(Flags)); + + // available since 2.2. if not present, version 1.0 is assumed + GET_PROC_OPTIONAL(cuDriverGetVersion); + + if (cuDriverGetVersion) + { + CHECKED_CALL(cuDriverGetVersion(&driverVer)); + } + + // fetch all function pointers + GET_PROC(cuDeviceGet); + GET_PROC(cuDeviceGetCount); + GET_PROC(cuDeviceGetName); + GET_PROC(cuDeviceComputeCapability); + GET_PROC(cuDeviceGetProperties); + GET_PROC(cuDeviceGetAttribute); + GET_PROC(cuCtxDestroy); + GET_PROC(cuCtxAttach); + GET_PROC(cuCtxDetach); + GET_PROC(cuCtxPushCurrent); + GET_PROC(cuCtxPopCurrent); + GET_PROC(cuCtxGetDevice); + GET_PROC(cuCtxSynchronize); + GET_PROC(cuModuleLoad); + GET_PROC(cuModuleLoadData); + GET_PROC(cuModuleUnload); + GET_PROC(cuModuleGetFunction); + GET_PROC(cuModuleGetTexRef); + GET_PROC(cuMemFreeHost); + GET_PROC(cuMemHostAlloc); + GET_PROC(cuFuncSetBlockShape); + GET_PROC(cuFuncSetSharedSize); + GET_PROC(cuFuncGetAttribute); + GET_PROC(cuArrayDestroy); + GET_PROC(cuTexRefCreate); + GET_PROC(cuTexRefDestroy); + GET_PROC(cuTexRefSetArray); + GET_PROC(cuTexRefSetFormat); + GET_PROC(cuTexRefSetAddressMode); + GET_PROC(cuTexRefSetFilterMode); + GET_PROC(cuTexRefSetFlags); + GET_PROC(cuTexRefGetArray); + GET_PROC(cuTexRefGetAddressMode); + GET_PROC(cuTexRefGetFilterMode); + GET_PROC(cuTexRefGetFormat); + GET_PROC(cuTexRefGetFlags); + GET_PROC(cuParamSetSize); + GET_PROC(cuParamSeti); + GET_PROC(cuParamSetf); + GET_PROC(cuParamSetv); + GET_PROC(cuParamSetTexRef); + GET_PROC(cuLaunch); + GET_PROC(cuLaunchGrid); + GET_PROC(cuLaunchGridAsync); + GET_PROC(cuEventCreate); + GET_PROC(cuEventRecord); + GET_PROC(cuEventQuery); + GET_PROC(cuEventSynchronize); + GET_PROC(cuEventDestroy); + GET_PROC(cuEventElapsedTime); + GET_PROC(cuStreamCreate); + GET_PROC(cuStreamQuery); + GET_PROC(cuStreamSynchronize); + GET_PROC(cuStreamDestroy); + + // These could be _v2 interfaces + if (cudaVersion >= 4000) + { + GET_PROC_V2(cuCtxDestroy); + GET_PROC_V2(cuCtxPopCurrent); + GET_PROC_V2(cuCtxPushCurrent); + GET_PROC_V2(cuStreamDestroy); + GET_PROC_V2(cuEventDestroy); + } + + if (cudaVersion >= 3020) + { + GET_PROC_V2(cuDeviceTotalMem); + GET_PROC_V2(cuCtxCreate); + GET_PROC_V2(cuModuleGetGlobal); + GET_PROC_V2(cuMemGetInfo); + GET_PROC_V2(cuMemAlloc); + GET_PROC_V2(cuMemAllocPitch); + GET_PROC_V2(cuMemFree); + GET_PROC_V2(cuMemGetAddressRange); + GET_PROC_V2(cuMemAllocHost); + GET_PROC_V2(cuMemHostGetDevicePointer); + GET_PROC_V2(cuMemcpyHtoD); + GET_PROC_V2(cuMemcpyDtoH); + GET_PROC_V2(cuMemcpyDtoD); + GET_PROC_V2(cuMemcpyDtoA); + GET_PROC_V2(cuMemcpyAtoD); + GET_PROC_V2(cuMemcpyHtoA); + GET_PROC_V2(cuMemcpyAtoH); + GET_PROC_V2(cuMemcpyAtoA); + GET_PROC_V2(cuMemcpy2D); + GET_PROC_V2(cuMemcpy2DUnaligned); + GET_PROC_V2(cuMemcpy3D); + GET_PROC_V2(cuMemcpyHtoDAsync); + GET_PROC_V2(cuMemcpyDtoHAsync); + GET_PROC_V2(cuMemcpyHtoAAsync); + GET_PROC_V2(cuMemcpyAtoHAsync); + GET_PROC_V2(cuMemcpy2DAsync); + GET_PROC_V2(cuMemcpy3DAsync); + GET_PROC_V2(cuMemsetD8); + GET_PROC_V2(cuMemsetD16); + GET_PROC_V2(cuMemsetD32); + GET_PROC_V2(cuMemsetD2D8); + GET_PROC_V2(cuMemsetD2D16); + GET_PROC_V2(cuMemsetD2D32); + GET_PROC_V2(cuArrayCreate); + GET_PROC_V2(cuArrayGetDescriptor); + GET_PROC_V2(cuArray3DCreate); + GET_PROC_V2(cuArray3DGetDescriptor); + GET_PROC_V2(cuTexRefSetAddress); + GET_PROC_V2(cuTexRefSetAddress2D); + GET_PROC_V2(cuTexRefGetAddress); + } + else + { + GET_PROC(cuDeviceTotalMem); + GET_PROC(cuCtxCreate); + GET_PROC(cuModuleGetGlobal); + GET_PROC(cuMemGetInfo); + GET_PROC(cuMemAlloc); + GET_PROC(cuMemAllocPitch); + GET_PROC(cuMemFree); + GET_PROC(cuMemGetAddressRange); + GET_PROC(cuMemAllocHost); + GET_PROC(cuMemHostGetDevicePointer); + GET_PROC(cuMemcpyHtoD); + GET_PROC(cuMemcpyDtoH); + GET_PROC(cuMemcpyDtoD); + GET_PROC(cuMemcpyDtoA); + GET_PROC(cuMemcpyAtoD); + GET_PROC(cuMemcpyHtoA); + GET_PROC(cuMemcpyAtoH); + GET_PROC(cuMemcpyAtoA); + GET_PROC(cuMemcpy2D); + GET_PROC(cuMemcpy2DUnaligned); + GET_PROC(cuMemcpy3D); + GET_PROC(cuMemcpyHtoDAsync); + GET_PROC(cuMemcpyDtoHAsync); + GET_PROC(cuMemcpyHtoAAsync); + GET_PROC(cuMemcpyAtoHAsync); + GET_PROC(cuMemcpy2DAsync); + GET_PROC(cuMemcpy3DAsync); + GET_PROC(cuMemsetD8); + GET_PROC(cuMemsetD16); + GET_PROC(cuMemsetD32); + GET_PROC(cuMemsetD2D8); + GET_PROC(cuMemsetD2D16); + GET_PROC(cuMemsetD2D32); + GET_PROC(cuArrayCreate); + GET_PROC(cuArrayGetDescriptor); + GET_PROC(cuArray3DCreate); + GET_PROC(cuArray3DGetDescriptor); + GET_PROC(cuTexRefSetAddress); + GET_PROC(cuTexRefSetAddress2D); + GET_PROC(cuTexRefGetAddress); + } + + // The following functions are specific to CUDA versions + if (driverVer >= 2010) + { + GET_PROC(cuModuleLoadDataEx); + GET_PROC(cuModuleLoadFatBinary); + } + + if (driverVer >= 2030) + { + GET_PROC(cuMemHostGetFlags); + } + + if (driverVer >= 3000) + { + GET_PROC(cuMemcpyDtoDAsync); + GET_PROC(cuFuncSetCacheConfig); + + GET_PROC(cuGraphicsUnregisterResource); + GET_PROC(cuGraphicsSubResourceGetMappedArray); + +#if (__CUDA_API_VERSION >= 3020) + if (cudaVersion >= 3020) + { + GET_PROC_V2(cuGraphicsResourceGetMappedPointer); + } + else + { + GET_PROC(cuGraphicsResourceGetMappedPointer); + } +#endif + GET_PROC(cuGraphicsResourceSetMapFlags); + GET_PROC(cuGraphicsMapResources); + GET_PROC(cuGraphicsUnmapResources); + GET_PROC(cuGetExportTable); + } + + if (driverVer >= 3010) + { + GET_PROC(cuModuleGetSurfRef); + GET_PROC(cuSurfRefSetArray); + GET_PROC(cuSurfRefGetArray); + GET_PROC(cuCtxSetLimit); + GET_PROC(cuCtxGetLimit); + } + + if (driverVer >= 4000) + { + GET_PROC(cuCtxSetCurrent); + GET_PROC(cuCtxGetCurrent); + GET_PROC(cuMemHostRegister); + GET_PROC(cuMemHostUnregister); + GET_PROC(cuMemcpy); + GET_PROC(cuMemcpyPeer); + GET_PROC(cuLaunchKernel); + } + + /*load openGL*/ + if (cudaVersion >= 2010) + { + GET_PROC(cuGLCtxCreate); + GET_PROC(cuGraphicsGLRegisterBuffer); + GET_PROC(cuGraphicsGLRegisterImage); +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) + GET_PROC(cuWGLGetDevice); +#endif + } + if (cudaVersion >= 2030) + { + GET_PROC(cuGraphicsGLRegisterBuffer); + GET_PROC(cuGraphicsGLRegisterImage); + } + if (cudaVersion >= 3000) + { + GET_PROC(cuGLGetDevices); +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) + GET_PROC(cuWGLGetDevice); +#endif + GET_PROC_V2(cuGLCtxCreate); + + GET_PROC_V2(cuGLMapBufferObject); + GET_PROC(cuGLUnmapBufferObject); + GET_PROC(cuGLMapBufferObjectAsync); + GET_PROC(cuGLUnmapBufferObjectAsync); + GET_PROC(cuGLRegisterBufferObject); + GET_PROC(cuGLUnregisterBufferObject); + GET_PROC(cuGLSetBufferObjectMapFlags); + } + + //init cuvid + CHECKED_CALL(LOAD_LIBRARY_CUVID(&CuvidDrvLib)); + + curr_lib = CuvidDrvLib; +#if !defined(__APPLE__) + // fetch all function pointers + GET_PROC(cuvidCreateVideoSource); + GET_PROC(cuvidCreateVideoSourceW); + GET_PROC(cuvidDestroyVideoSource); + GET_PROC(cuvidSetVideoSourceState); + GET_PROC(cuvidGetVideoSourceState); + GET_PROC(cuvidGetSourceVideoFormat); + GET_PROC(cuvidGetSourceAudioFormat); +#endif + + GET_PROC(cuvidCreateVideoParser); + GET_PROC(cuvidParseVideoData); + GET_PROC(cuvidDestroyVideoParser); + + GET_PROC(cuvidCreateDecoder); + GET_PROC(cuvidDestroyDecoder); + GET_PROC(cuvidDecodePicture); + +#if defined(WIN64) || defined(_WIN64) || defined(__x86_64) || defined(AMD64) || defined(_M_AMD64) + GET_PROC(cuvidMapVideoFrame64); + GET_PROC(cuvidUnmapVideoFrame64); + cuvidMapVideoFrame = cuvidMapVideoFrame64; + cuvidUnmapVideoFrame = cuvidUnmapVideoFrame64; +#else + GET_PROC(cuvidMapVideoFrame); + GET_PROC(cuvidUnmapVideoFrame); +#endif + +// GET_PROC(cuvidGetVideoFrameSurface); + GET_PROC(cuvidCtxLockCreate); + GET_PROC(cuvidCtxLockDestroy); + GET_PROC(cuvidCtxLock); + GET_PROC(cuvidCtxUnlock); + + + return CUDA_SUCCESS; +} + + +// CUDA Driver API errors +const char *cudaGetErrorEnum(CUresult error) +{ + switch (error) { + case CUDA_SUCCESS: return "CUDA_SUCCESS"; + case CUDA_ERROR_INVALID_VALUE: return "CUDA_ERROR_INVALID_VALUE"; + case CUDA_ERROR_OUT_OF_MEMORY: return "CUDA_ERROR_OUT_OF_MEMORY"; + case CUDA_ERROR_NOT_INITIALIZED: return "CUDA_ERROR_NOT_INITIALIZED"; + case CUDA_ERROR_DEINITIALIZED: return "CUDA_ERROR_DEINITIALIZED"; + case CUDA_ERROR_PROFILER_DISABLED: return "CUDA_ERROR_PROFILER_DISABLED"; + case CUDA_ERROR_PROFILER_NOT_INITIALIZED: return "CUDA_ERROR_PROFILER_NOT_INITIALIZED"; + case CUDA_ERROR_PROFILER_ALREADY_STARTED: return "CUDA_ERROR_PROFILER_ALREADY_STARTED"; + case CUDA_ERROR_PROFILER_ALREADY_STOPPED: return "CUDA_ERROR_PROFILER_ALREADY_STOPPED"; + case CUDA_ERROR_NO_DEVICE: return "CUDA_ERROR_NO_DEVICE"; + case CUDA_ERROR_INVALID_DEVICE: return "CUDA_ERROR_INVALID_DEVICE"; + case CUDA_ERROR_INVALID_IMAGE: return "CUDA_ERROR_INVALID_IMAGE"; + case CUDA_ERROR_INVALID_CONTEXT: return "CUDA_ERROR_INVALID_CONTEXT"; + case CUDA_ERROR_CONTEXT_ALREADY_CURRENT: return "CUDA_ERROR_CONTEXT_ALREADY_CURRENT"; + case CUDA_ERROR_MAP_FAILED: return "CUDA_ERROR_MAP_FAILED"; + case CUDA_ERROR_UNMAP_FAILED: return "CUDA_ERROR_UNMAP_FAILED"; + case CUDA_ERROR_ARRAY_IS_MAPPED: return "CUDA_ERROR_ARRAY_IS_MAPPED"; + case CUDA_ERROR_ALREADY_MAPPED: return "CUDA_ERROR_ALREADY_MAPPED"; + case CUDA_ERROR_NO_BINARY_FOR_GPU: return "CUDA_ERROR_NO_BINARY_FOR_GPU"; + case CUDA_ERROR_ALREADY_ACQUIRED: return "CUDA_ERROR_ALREADY_ACQUIRED"; + case CUDA_ERROR_NOT_MAPPED: return "CUDA_ERROR_NOT_MAPPED"; + case CUDA_ERROR_NOT_MAPPED_AS_ARRAY: return "CUDA_ERROR_NOT_MAPPED_AS_ARRAY"; + case CUDA_ERROR_NOT_MAPPED_AS_POINTER: return "CUDA_ERROR_NOT_MAPPED_AS_POINTER"; + case CUDA_ERROR_ECC_UNCORRECTABLE: return "CUDA_ERROR_ECC_UNCORRECTABLE"; + case CUDA_ERROR_UNSUPPORTED_LIMIT: return "CUDA_ERROR_UNSUPPORTED_LIMIT"; + case CUDA_ERROR_CONTEXT_ALREADY_IN_USE: return "CUDA_ERROR_CONTEXT_ALREADY_IN_USE"; + case CUDA_ERROR_INVALID_SOURCE: return "CUDA_ERROR_INVALID_SOURCE"; + case CUDA_ERROR_FILE_NOT_FOUND: return "CUDA_ERROR_FILE_NOT_FOUND"; + case CUDA_ERROR_SHARED_OBJECT_SYMBOL_NOT_FOUND: return "CUDA_ERROR_SHARED_OBJECT_SYMBOL_NOT_FOUND"; + case CUDA_ERROR_SHARED_OBJECT_INIT_FAILED: return "CUDA_ERROR_SHARED_OBJECT_INIT_FAILED"; + case CUDA_ERROR_OPERATING_SYSTEM: return "CUDA_ERROR_OPERATING_SYSTEM"; + case CUDA_ERROR_INVALID_HANDLE: return "CUDA_ERROR_INVALID_HANDLE"; + case CUDA_ERROR_NOT_FOUND: return "CUDA_ERROR_NOT_FOUND"; + case CUDA_ERROR_NOT_READY: return "CUDA_ERROR_NOT_READY"; + case CUDA_ERROR_LAUNCH_FAILED: return "CUDA_ERROR_LAUNCH_FAILED"; + case CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES: return "CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES"; + case CUDA_ERROR_LAUNCH_TIMEOUT: return "CUDA_ERROR_LAUNCH_TIMEOUT"; + case CUDA_ERROR_LAUNCH_INCOMPATIBLE_TEXTURING: return "CUDA_ERROR_LAUNCH_INCOMPATIBLE_TEXTURING"; + case CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED: return "CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED"; + case CUDA_ERROR_PEER_ACCESS_NOT_ENABLED: return "CUDA_ERROR_PEER_ACCESS_NOT_ENABLED"; + case CUDA_ERROR_PRIMARY_CONTEXT_ACTIVE: return "CUDA_ERROR_PRIMARY_CONTEXT_ACTIVE"; + case CUDA_ERROR_CONTEXT_IS_DESTROYED: return "CUDA_ERROR_CONTEXT_IS_DESTROYED"; + case CUDA_ERROR_PEER_MEMORY_ALREADY_REGISTERED: return "CUDA_ERROR_PEER_MEMORY_ALREADY_REGISTERED"; + case CUDA_ERROR_PEER_MEMORY_NOT_REGISTERED: return "CUDA_ERROR_PEER_MEMORY_NOT_REGISTERED"; + case CUDA_ERROR_UNKNOWN: return "CUDA_ERROR_UNKNOWN"; + } + return ""; +} + +#endif //WIN32 diff --git a/src/filters/dec_nvdec_sdk.h b/src/filters/dec_nvdec_sdk.h new file mode 100644 index 0000000..d257ecf --- /dev/null +++ b/src/filters/dec_nvdec_sdk.h @@ -0,0 +1,2898 @@ +/* + * Copyright 1993-2015 NVIDIA Corporation. All rights reserved. + * + * Please refer to the NVIDIA end user license agreement (EULA) associated + * with this source code for terms and conditions that govern your use of + * this software. Any use, reproduction, disclosure, or distribution of + * this software and related documentation outside the terms of the EULA + * is strictly prohibited. + * + */ + +#ifndef __cuda_tools_h__ +#define __cuda_tools_h__ + +#include + +#if defined(WIN32) || defined(GPAC_CONFIG_LINUX) || defined(GPAC_CONFIG_DARWIN) + +#ifdef __cplusplus +extern "C" { +#endif + +//needed for dec_nvdec_sdk.h which uses GL prototypes +#ifndef GPAC_DISABLE_3D +#include "../compositor/gl_inc.h" +#else +typedef u32 GLuint; +typedef u32 GLenum; +#endif + +#ifndef __CUDA_API_VERSION +#define __CUDA_API_VERSION 4000 +#endif + +/** + * \defgroup CUDA_DRIVER CUDA Driver API + * + * This section describes the low-level CUDA driver application programming + * interface. + * + * @{ + */ + +/** + * \defgroup CUDA_TYPES Data types used by CUDA driver + * @{ + */ + +/** + * CUDA API version number + */ +#define CUDA_VERSION 4000 /* 4.0 */ + +/** + * CUDA device pointer + */ +#if __CUDA_API_VERSION >= 3020 + +#if defined(__x86_64) || defined(AMD64) || defined(_M_AMD64) || defined(__aarch64__) + typedef unsigned long long CUdeviceptr; +#else + typedef unsigned int CUdeviceptr; +#endif + +#endif /* __CUDA_API_VERSION >= 3020 */ + +typedef int CUdevice; /**< CUDA device */ +typedef struct CUctx_st *CUcontext; /**< CUDA context */ +typedef struct CUmod_st *CUmodule; /**< CUDA module */ +typedef struct CUfunc_st *CUfunction; /**< CUDA function */ +typedef struct CUarray_st *CUarray; /**< CUDA array */ +typedef struct CUtexref_st *CUtexref; /**< CUDA texture reference */ +typedef struct CUsurfref_st *CUsurfref; /**< CUDA surface reference */ +typedef struct CUevent_st *CUevent; /**< CUDA event */ +typedef struct CUstream_st *CUstream; /**< CUDA stream */ +typedef struct CUgraphicsResource_st *CUgraphicsResource; /**< CUDA graphics interop resource */ + +typedef struct CUuuid_st /**< CUDA definition of UUID */ +{ + char bytes[16]; +} CUuuid; + +/** + * Context creation flags + */ +typedef enum CUctx_flags_enum +{ + CU_CTX_SCHED_AUTO = 0x00, /**< Automatic scheduling */ + CU_CTX_SCHED_SPIN = 0x01, /**< Set spin as default scheduling */ + CU_CTX_SCHED_YIELD = 0x02, /**< Set yield as default scheduling */ + CU_CTX_SCHED_BLOCKING_SYNC = 0x04, /**< Set blocking synchronization as default scheduling */ + CU_CTX_BLOCKING_SYNC = 0x04, /**< Set blocking synchronization as default scheduling \deprecated */ + CU_CTX_MAP_HOST = 0x08, /**< Support mapped pinned allocations */ + CU_CTX_LMEM_RESIZE_TO_MAX = 0x10, /**< Keep local memory allocation after launch */ +#if __CUDA_API_VERSION < 4000 + CU_CTX_SCHED_MASK = 0x03, + CU_CTX_FLAGS_MASK = 0x1f +#else + CU_CTX_SCHED_MASK = 0x07, + CU_CTX_PRIMARY = 0x20, /**< Initialize and return the primary context */ + CU_CTX_FLAGS_MASK = 0x3f +#endif +} CUctx_flags; + +/** + * Event creation flags + */ +typedef enum CUevent_flags_enum +{ + CU_EVENT_DEFAULT = 0, /**< Default event flag */ + CU_EVENT_BLOCKING_SYNC = 1, /**< Event uses blocking synchronization */ + CU_EVENT_DISABLE_TIMING = 2 /**< Event will not record timing data */ +} CUevent_flags; + +/** + * Array formats + */ +typedef enum CUarray_format_enum +{ + CU_AD_FORMAT_UNSIGNED_INT8 = 0x01, /**< Unsigned 8-bit integers */ + CU_AD_FORMAT_UNSIGNED_INT16 = 0x02, /**< Unsigned 16-bit integers */ + CU_AD_FORMAT_UNSIGNED_INT32 = 0x03, /**< Unsigned 32-bit integers */ + CU_AD_FORMAT_SIGNED_INT8 = 0x08, /**< Signed 8-bit integers */ + CU_AD_FORMAT_SIGNED_INT16 = 0x09, /**< Signed 16-bit integers */ + CU_AD_FORMAT_SIGNED_INT32 = 0x0a, /**< Signed 32-bit integers */ + CU_AD_FORMAT_HALF = 0x10, /**< 16-bit floating point */ + CU_AD_FORMAT_FLOAT = 0x20 /**< 32-bit floating point */ +} CUarray_format; + +/** + * Texture reference addressing modes + */ +typedef enum CUaddress_mode_enum +{ + CU_TR_ADDRESS_MODE_WRAP = 0, /**< Wrapping address mode */ + CU_TR_ADDRESS_MODE_CLAMP = 1, /**< Clamp to edge address mode */ + CU_TR_ADDRESS_MODE_MIRROR = 2, /**< Mirror address mode */ + CU_TR_ADDRESS_MODE_BORDER = 3 /**< Border address mode */ +} CUaddress_mode; + +/** + * Texture reference filtering modes + */ +typedef enum CUfilter_mode_enum +{ + CU_TR_FILTER_MODE_POINT = 0, /**< Point filter mode */ + CU_TR_FILTER_MODE_LINEAR = 1 /**< Linear filter mode */ +} CUfilter_mode; + +/** + * Device properties + */ +typedef enum CUdevice_attribute_enum +{ + CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_BLOCK = 1, /**< Maximum number of threads per block */ + CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_X = 2, /**< Maximum block dimension X */ + CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_Y = 3, /**< Maximum block dimension Y */ + CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_Z = 4, /**< Maximum block dimension Z */ + CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_X = 5, /**< Maximum grid dimension X */ + CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Y = 6, /**< Maximum grid dimension Y */ + CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Z = 7, /**< Maximum grid dimension Z */ + CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK = 8, /**< Maximum shared memory available per block in bytes */ + CU_DEVICE_ATTRIBUTE_SHARED_MEMORY_PER_BLOCK = 8, /**< Deprecated, use CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK */ + CU_DEVICE_ATTRIBUTE_TOTAL_CONSTANT_MEMORY = 9, /**< Memory available on device for __constant__ variables in a CUDA C kernel in bytes */ + CU_DEVICE_ATTRIBUTE_WARP_SIZE = 10, /**< Warp size in threads */ + CU_DEVICE_ATTRIBUTE_MAX_PITCH = 11, /**< Maximum pitch in bytes allowed by memory copies */ + CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_BLOCK = 12, /**< Maximum number of 32-bit registers available per block */ + CU_DEVICE_ATTRIBUTE_REGISTERS_PER_BLOCK = 12, /**< Deprecated, use CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_BLOCK */ + CU_DEVICE_ATTRIBUTE_CLOCK_RATE = 13, /**< Peak clock frequency in kilohertz */ + CU_DEVICE_ATTRIBUTE_TEXTURE_ALIGNMENT = 14, /**< Alignment requirement for textures */ + CU_DEVICE_ATTRIBUTE_GPU_OVERLAP = 15, /**< Device can possibly copy memory and execute a kernel concurrently */ + CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT = 16, /**< Number of multiprocessors on device */ + CU_DEVICE_ATTRIBUTE_KERNEL_EXEC_TIMEOUT = 17, /**< Specifies whether there is a run time limit on kernels */ + CU_DEVICE_ATTRIBUTE_INTEGRATED = 18, /**< Device is integrated with host memory */ + CU_DEVICE_ATTRIBUTE_CAN_MAP_HOST_MEMORY = 19, /**< Device can map host memory into CUDA address space */ + CU_DEVICE_ATTRIBUTE_COMPUTE_MODE = 20, /**< Compute mode (See ::CUcomputemode for details) */ + CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_WIDTH = 21, /**< Maximum 1D texture width */ + CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_WIDTH = 22, /**< Maximum 2D texture width */ + CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_HEIGHT = 23, /**< Maximum 2D texture height */ + CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_WIDTH = 24, /**< Maximum 3D texture width */ + CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_HEIGHT = 25, /**< Maximum 3D texture height */ + CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_DEPTH = 26, /**< Maximum 3D texture depth */ + CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_ARRAY_WIDTH = 27, /**< Maximum texture array width */ + CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_ARRAY_HEIGHT = 28, /**< Maximum texture array height */ + CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_ARRAY_NUMSLICES = 29, /**< Maximum slices in a texture array */ + CU_DEVICE_ATTRIBUTE_SURFACE_ALIGNMENT = 30, /**< Alignment requirement for surfaces */ + CU_DEVICE_ATTRIBUTE_CONCURRENT_KERNELS = 31, /**< Device can possibly execute multiple kernels concurrently */ + CU_DEVICE_ATTRIBUTE_ECC_ENABLED = 32, /**< Device has ECC support enabled */ + CU_DEVICE_ATTRIBUTE_PCI_BUS_ID = 33, /**< PCI bus ID of the device */ + CU_DEVICE_ATTRIBUTE_PCI_DEVICE_ID = 34, /**< PCI device ID of the device */ + CU_DEVICE_ATTRIBUTE_TCC_DRIVER = 35 /**< Device is using TCC driver model */ +#if __CUDA_API_VERSION >= 4000 + , CU_DEVICE_ATTRIBUTE_MEMORY_CLOCK_RATE = 36, /**< Peak memory clock frequency in kilohertz */ + CU_DEVICE_ATTRIBUTE_GLOBAL_MEMORY_BUS_WIDTH = 37, /**< Global memory bus width in bits */ + CU_DEVICE_ATTRIBUTE_L2_CACHE_SIZE = 38, /**< Size of L2 cache in bytes */ + CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_MULTIPROCESSOR = 39, /**< Maximum resident threads per multiprocessor */ + CU_DEVICE_ATTRIBUTE_ASYNC_ENGINE_COUNT = 40, /**< Number of asynchronous engines */ + CU_DEVICE_ATTRIBUTE_UNIFIED_ADDRESSING = 41, /**< Device uses shares a unified address space with the host */ + CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_LAYERED_WIDTH = 42, /**< Maximum 1D layered texture width */ + CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_LAYERED_LAYERS = 43 /**< Maximum layers in a 1D layered texture */ +#endif +} CUdevice_attribute; + +/** + * Legacy device properties + */ +typedef struct CUdevprop_st +{ + int maxThreadsPerBlock; /**< Maximum number of threads per block */ + int maxThreadsDim[3]; /**< Maximum size of each dimension of a block */ + int maxGridSize[3]; /**< Maximum size of each dimension of a grid */ + int sharedMemPerBlock; /**< Shared memory available per block in bytes */ + int totalConstantMemory; /**< Constant memory available on device in bytes */ + int SIMDWidth; /**< Warp size in threads */ + int memPitch; /**< Maximum pitch in bytes allowed by memory copies */ + int regsPerBlock; /**< 32-bit registers available per block */ + int clockRate; /**< Clock frequency in kilohertz */ + int textureAlign; /**< Alignment requirement for textures */ +} CUdevprop; + +/** + * Function properties + */ +typedef enum CUfunction_attribute_enum +{ + /** + * The maximum number of threads per block, beyond which a launch of the + * function would fail. This number depends on both the function and the + * device on which the function is currently loaded. + */ + CU_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK = 0, + + /** + * The size in bytes of statically-allocated shared memory required by + * this function. This does not include dynamically-allocated shared + * memory requested by the user at runtime. + */ + CU_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES = 1, + + /** + * The size in bytes of user-allocated constant memory required by this + * function. + */ + CU_FUNC_ATTRIBUTE_CONST_SIZE_BYTES = 2, + + /** + * The size in bytes of local memory used by each thread of this function. + */ + CU_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES = 3, + + /** + * The number of registers used by each thread of this function. + */ + CU_FUNC_ATTRIBUTE_NUM_REGS = 4, + + /** + * The PTX virtual architecture version for which the function was + * compiled. This value is the major PTX version * 10 + the minor PTX + * version, so a PTX version 1.3 function would return the value 13. + * Note that this may return the undefined value of 0 for cubins + * compiled prior to CUDA 3.0. + */ + CU_FUNC_ATTRIBUTE_PTX_VERSION = 5, + + /** + * The binary architecture version for which the function was compiled. + * This value is the major binary version * 10 + the minor binary version, + * so a binary version 1.3 function would return the value 13. Note that + * this will return a value of 10 for legacy cubins that do not have a + * properly-encoded binary architecture version. + */ + CU_FUNC_ATTRIBUTE_BINARY_VERSION = 6, + + CU_FUNC_ATTRIBUTE_MAX +} CUfunction_attribute; + +/** + * Function cache configurations + */ +typedef enum CUfunc_cache_enum +{ + CU_FUNC_CACHE_PREFER_NONE = 0x00, /**< no preference for shared memory or L1 (default) */ + CU_FUNC_CACHE_PREFER_SHARED = 0x01, /**< prefer larger shared memory and smaller L1 cache */ + CU_FUNC_CACHE_PREFER_L1 = 0x02 /**< prefer larger L1 cache and smaller shared memory */ +} CUfunc_cache; + +/** + * Memory types + */ +typedef enum CUmemorytype_enum +{ + CU_MEMORYTYPE_HOST = 0x01, /**< Host memory */ + CU_MEMORYTYPE_DEVICE = 0x02, /**< Device memory */ + CU_MEMORYTYPE_ARRAY = 0x03 /**< Array memory */ +#if __CUDA_API_VERSION >= 4000 + , CU_MEMORYTYPE_UNIFIED = 0x04 /**< Unified device or host memory */ +#endif +} CUmemorytype; + +/** + * Compute Modes + */ +typedef enum CUcomputemode_enum +{ + CU_COMPUTEMODE_DEFAULT = 0, /**< Default compute mode (Multiple contexts allowed per device) */ + CU_COMPUTEMODE_EXCLUSIVE = 1, /**< Compute-exclusive-thread mode (Only one context used by a single thread can be present on this device at a time) */ + CU_COMPUTEMODE_PROHIBITED = 2 /**< Compute-prohibited mode (No contexts can be created on this device at this time) */ +#if __CUDA_API_VERSION >= 4000 + , CU_COMPUTEMODE_EXCLUSIVE_PROCESS = 3 /**< Compute-exclusive-process mode (Only one context used by a single process can be present on this device at a time) */ +#endif +} CUcomputemode; + +/** + * Online compiler options + */ +typedef enum CUjit_option_enum +{ + /** + * Max number of registers that a thread may use.\n + * Option type: unsigned int + */ + CU_JIT_MAX_REGISTERS = 0, + + /** + * IN: Specifies minimum number of threads per block to target compilation + * for\n + * OUT: Returns the number of threads the compiler actually targeted. + * This restricts the resource utilization fo the compiler (e.g. max + * registers) such that a block with the given number of threads should be + * able to launch based on register limitations. Note, this option does not + * currently take into account any other resource limitations, such as + * shared memory utilization.\n + * Option type: unsigned int + */ + CU_JIT_THREADS_PER_BLOCK, + + /** + * Returns a float value in the option of the wall clock time, in + * milliseconds, spent creating the cubin\n + * Option type: float + */ + CU_JIT_WALL_TIME, + + /** + * Pointer to a buffer in which to print any log messsages from PTXAS + * that are informational in nature (the buffer size is specified via + * option ::CU_JIT_INFO_LOG_BUFFER_SIZE_BYTES) \n + * Option type: char* + */ + CU_JIT_INFO_LOG_BUFFER, + + /** + * IN: Log buffer size in bytes. Log messages will be capped at this size + * (including null terminator)\n + * OUT: Amount of log buffer filled with messages\n + * Option type: unsigned int + */ + CU_JIT_INFO_LOG_BUFFER_SIZE_BYTES, + + /** + * Pointer to a buffer in which to print any log messages from PTXAS that + * reflect errors (the buffer size is specified via option + * ::CU_JIT_ERROR_LOG_BUFFER_SIZE_BYTES)\n + * Option type: char* + */ + CU_JIT_ERROR_LOG_BUFFER, + + /** + * IN: Log buffer size in bytes. Log messages will be capped at this size + * (including null terminator)\n + * OUT: Amount of log buffer filled with messages\n + * Option type: unsigned int + */ + CU_JIT_ERROR_LOG_BUFFER_SIZE_BYTES, + + /** + * Level of optimizations to apply to generated code (0 - 4), with 4 + * being the default and highest level of optimizations.\n + * Option type: unsigned int + */ + CU_JIT_OPTIMIZATION_LEVEL, + + /** + * No option value required. Determines the target based on the current + * attached context (default)\n + * Option type: No option value needed + */ + CU_JIT_TARGET_FROM_CUCONTEXT, + + /** + * Target is chosen based on supplied ::CUjit_target_enum.\n + * Option type: unsigned int for enumerated type ::CUjit_target_enum + */ + CU_JIT_TARGET, + + /** + * Specifies choice of fallback strategy if matching cubin is not found. + * Choice is based on supplied ::CUjit_fallback_enum.\n + * Option type: unsigned int for enumerated type ::CUjit_fallback_enum + */ + CU_JIT_FALLBACK_STRATEGY + +} CUjit_option; + +/** + * Online compilation targets + */ +typedef enum CUjit_target_enum +{ + CU_TARGET_COMPUTE_10 = 0, /**< Compute device class 1.0 */ + CU_TARGET_COMPUTE_11, /**< Compute device class 1.1 */ + CU_TARGET_COMPUTE_12, /**< Compute device class 1.2 */ + CU_TARGET_COMPUTE_13, /**< Compute device class 1.3 */ + CU_TARGET_COMPUTE_20, /**< Compute device class 2.0 */ + CU_TARGET_COMPUTE_21 /**< Compute device class 2.1 */ +} CUjit_target; + +/** + * Cubin matching fallback strategies + */ +typedef enum CUjit_fallback_enum +{ + CU_PREFER_PTX = 0, /**< Prefer to compile ptx */ + + CU_PREFER_BINARY /**< Prefer to fall back to compatible binary code */ + +} CUjit_fallback; + +/** + * Flags to register a graphics resource + */ +typedef enum CUgraphicsRegisterFlags_enum +{ + CU_GRAPHICS_REGISTER_FLAGS_NONE = 0x00, + CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY = 0x01, + CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD = 0x02, + CU_GRAPHICS_REGISTER_FLAGS_SURFACE_LDST = 0x04 +} CUgraphicsRegisterFlags; + +/** + * Flags for mapping and unmapping interop resources + */ +typedef enum CUgraphicsMapResourceFlags_enum +{ + CU_GRAPHICS_MAP_RESOURCE_FLAGS_NONE = 0x00, + CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY = 0x01, + CU_GRAPHICS_MAP_RESOURCE_FLAGS_WRITE_DISCARD = 0x02 +} CUgraphicsMapResourceFlags; + +/** + * Array indices for cube faces + */ +typedef enum CUarray_cubemap_face_enum +{ + CU_CUBEMAP_FACE_POSITIVE_X = 0x00, /**< Positive X face of cubemap */ + CU_CUBEMAP_FACE_NEGATIVE_X = 0x01, /**< Negative X face of cubemap */ + CU_CUBEMAP_FACE_POSITIVE_Y = 0x02, /**< Positive Y face of cubemap */ + CU_CUBEMAP_FACE_NEGATIVE_Y = 0x03, /**< Negative Y face of cubemap */ + CU_CUBEMAP_FACE_POSITIVE_Z = 0x04, /**< Positive Z face of cubemap */ + CU_CUBEMAP_FACE_NEGATIVE_Z = 0x05 /**< Negative Z face of cubemap */ +} CUarray_cubemap_face; + +/** + * Limits + */ +typedef enum CUlimit_enum +{ + CU_LIMIT_STACK_SIZE = 0x00, /**< GPU thread stack size */ + CU_LIMIT_PRINTF_FIFO_SIZE = 0x01, /**< GPU printf FIFO size */ + CU_LIMIT_MALLOC_HEAP_SIZE = 0x02 /**< GPU malloc heap size */ +} CUlimit; + +/** + * Error codes + */ +typedef enum cudaError_enum +{ + /** + * The API call returned with no errors. In the case of query calls, this + * can also mean that the operation being queried is complete (see + * ::cuEventQuery() and ::cuStreamQuery()). + */ + CUDA_SUCCESS = 0, + + /** + * This indicates that one or more of the parameters passed to the API call + * is not within an acceptable range of values. + */ + CUDA_ERROR_INVALID_VALUE = 1, + + /** + * The API call failed because it was unable to allocate enough memory to + * perform the requested operation. + */ + CUDA_ERROR_OUT_OF_MEMORY = 2, + + /** + * This indicates that the CUDA driver has not been initialized with + * ::cuInit() or that initialization has failed. + */ + CUDA_ERROR_NOT_INITIALIZED = 3, + + /** + * This indicates that the CUDA driver is in the process of shutting down. + */ + CUDA_ERROR_DEINITIALIZED = 4, + + /** + * This indicates profiling APIs are called while application is running + * in visual profiler mode. + */ + CUDA_ERROR_PROFILER_DISABLED = 5, + /** + * This indicates profiling has not been initialized for this context. + * Call cuProfilerInitialize() to resolve this. + */ + CUDA_ERROR_PROFILER_NOT_INITIALIZED = 6, + /** + * This indicates profiler has already been started and probably + * cuProfilerStart() is incorrectly called. + */ + CUDA_ERROR_PROFILER_ALREADY_STARTED = 7, + /** + * This indicates profiler has already been stopped and probably + * cuProfilerStop() is incorrectly called. + */ + CUDA_ERROR_PROFILER_ALREADY_STOPPED = 8, + /** + * This indicates that no CUDA-capable devices were detected by the installed + * CUDA driver. + */ + CUDA_ERROR_NO_DEVICE = 100, + + /** + * This indicates that the device ordinal supplied by the user does not + * correspond to a valid CUDA device. + */ + CUDA_ERROR_INVALID_DEVICE = 101, + + + /** + * This indicates that the device kernel image is invalid. This can also + * indicate an invalid CUDA module. + */ + CUDA_ERROR_INVALID_IMAGE = 200, + + /** + * This most frequently indicates that there is no context bound to the + * current thread. This can also be returned if the context passed to an + * API call is not a valid handle (such as a context that has had + * ::cuCtxDestroy() invoked on it). This can also be returned if a user + * mixes different API versions (i.e. 3010 context with 3020 API calls). + * See ::cuCtxGetApiVersion() for more details. + */ + CUDA_ERROR_INVALID_CONTEXT = 201, + + /** + * This indicated that the context being supplied as a parameter to the + * API call was already the active context. + * \deprecated + * This error return is deprecated as of CUDA 3.2. It is no longer an + * error to attempt to push the active context via ::cuCtxPushCurrent(). + */ + CUDA_ERROR_CONTEXT_ALREADY_CURRENT = 202, + + /** + * This indicates that a map or register operation has failed. + */ + CUDA_ERROR_MAP_FAILED = 205, + + /** + * This indicates that an unmap or unregister operation has failed. + */ + CUDA_ERROR_UNMAP_FAILED = 206, + + /** + * This indicates that the specified array is currently mapped and thus + * cannot be destroyed. + */ + CUDA_ERROR_ARRAY_IS_MAPPED = 207, + + /** + * This indicates that the resource is already mapped. + */ + CUDA_ERROR_ALREADY_MAPPED = 208, + + /** + * This indicates that there is no kernel image available that is suitable + * for the device. This can occur when a user specifies code generation + * options for a particular CUDA source file that do not include the + * corresponding device configuration. + */ + CUDA_ERROR_NO_BINARY_FOR_GPU = 209, + + /** + * This indicates that a resource has already been acquired. + */ + CUDA_ERROR_ALREADY_ACQUIRED = 210, + + /** + * This indicates that a resource is not mapped. + */ + CUDA_ERROR_NOT_MAPPED = 211, + + /** + * This indicates that a mapped resource is not available for access as an + * array. + */ + CUDA_ERROR_NOT_MAPPED_AS_ARRAY = 212, + + /** + * This indicates that a mapped resource is not available for access as a + * pointer. + */ + CUDA_ERROR_NOT_MAPPED_AS_POINTER = 213, + + /** + * This indicates that an uncorrectable ECC error was detected during + * execution. + */ + CUDA_ERROR_ECC_UNCORRECTABLE = 214, + + /** + * This indicates that the ::CUlimit passed to the API call is not + * supported by the active device. + */ + CUDA_ERROR_UNSUPPORTED_LIMIT = 215, + + /** + * This indicates that the ::CUcontext passed to the API call can + * only be bound to a single CPU thread at a time but is already + * bound to a CPU thread. + */ + CUDA_ERROR_CONTEXT_ALREADY_IN_USE = 216, + + /** + * This indicates that the device kernel source is invalid. + */ + CUDA_ERROR_INVALID_SOURCE = 300, + + /** + * This indicates that the file specified was not found. + */ + CUDA_ERROR_FILE_NOT_FOUND = 301, + + /** + * This indicates that a link to a shared object failed to resolve. + */ + CUDA_ERROR_SHARED_OBJECT_SYMBOL_NOT_FOUND = 302, + + /** + * This indicates that initialization of a shared object failed. + */ + CUDA_ERROR_SHARED_OBJECT_INIT_FAILED = 303, + + /** + * This indicates that an OS call failed. + */ + CUDA_ERROR_OPERATING_SYSTEM = 304, + + + /** + * This indicates that a resource handle passed to the API call was not + * valid. Resource handles are opaque types like ::CUstream and ::CUevent. + */ + CUDA_ERROR_INVALID_HANDLE = 400, + + + /** + * This indicates that a named symbol was not found. Examples of symbols + * are global/constant variable names, texture names, and surface names. + */ + CUDA_ERROR_NOT_FOUND = 500, + + + /** + * This indicates that asynchronous operations issued previously have not + * completed yet. This result is not actually an error, but must be indicated + * differently than ::CUDA_SUCCESS (which indicates completion). Calls that + * may return this value include ::cuEventQuery() and ::cuStreamQuery(). + */ + CUDA_ERROR_NOT_READY = 600, + + + /** + * An exception occurred on the device while executing a kernel. Common + * causes include dereferencing an invalid device pointer and accessing + * out of bounds shared memory. The context cannot be used, so it must + * be destroyed (and a new one should be created). All existing device + * memory allocations from this context are invalid and must be + * reconstructed if the program is to continue using CUDA. + */ + CUDA_ERROR_LAUNCH_FAILED = 700, + + /** + * This indicates that a launch did not occur because it did not have + * appropriate resources. This error usually indicates that the user has + * attempted to pass too many arguments to the device kernel, or the + * kernel launch specifies too many threads for the kernel's register + * count. Passing arguments of the wrong size (i.e. a 64-bit pointer + * when a 32-bit int is expected) is equivalent to passing too many + * arguments and can also result in this error. + */ + CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES = 701, + + /** + * This indicates that the device kernel took too long to execute. This can + * only occur if timeouts are enabled - see the device attribute + * ::CU_DEVICE_ATTRIBUTE_KERNEL_EXEC_TIMEOUT for more information. The + * context cannot be used (and must be destroyed similar to + * ::CUDA_ERROR_LAUNCH_FAILED). All existing device memory allocations from + * this context are invalid and must be reconstructed if the program is to + * continue using CUDA. + */ + CUDA_ERROR_LAUNCH_TIMEOUT = 702, + + /** + * This error indicates a kernel launch that uses an incompatible texturing + * mode. + */ + CUDA_ERROR_LAUNCH_INCOMPATIBLE_TEXTURING = 703, + + /** + * This error indicates that a call to ::cuCtxEnablePeerAccess() is + * trying to re-enable peer access to a context which has already + * had peer access to it enabled. + */ + CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED = 704, + + /** + * This error indicates that a call to ::cuMemPeerRegister is trying to + * register memory from a context which has not had peer access + * enabled yet via ::cuCtxEnablePeerAccess(), or that + * ::cuCtxDisablePeerAccess() is trying to disable peer access + * which has not been enabled yet. + */ + CUDA_ERROR_PEER_ACCESS_NOT_ENABLED = 705, + + /** + * This error indicates that a call to ::cuMemPeerRegister is trying to + * register already-registered memory. + */ + CUDA_ERROR_PEER_MEMORY_ALREADY_REGISTERED = 706, + + /** + * This error indicates that a call to ::cuMemPeerUnregister is trying to + * unregister memory that has not been registered. + */ + CUDA_ERROR_PEER_MEMORY_NOT_REGISTERED = 707, + + /** + * This error indicates that ::cuCtxCreate was called with the flag + * ::CU_CTX_PRIMARY on a device which already has initialized its + * primary context. + */ + CUDA_ERROR_PRIMARY_CONTEXT_ACTIVE = 708, + + /** + * This error indicates that the context current to the calling thread + * has been destroyed using ::cuCtxDestroy, or is a primary context which + * has not yet been initialized. + */ + CUDA_ERROR_CONTEXT_IS_DESTROYED = 709, + + /** + * This indicates that an unknown internal error has occurred. + */ + CUDA_ERROR_UNKNOWN = 999 +} CUresult; + +#if __CUDA_API_VERSION >= 4000 +/** + * If set, host memory is portable between CUDA contexts. + * Flag for ::cuMemHostAlloc() + */ +#define CU_MEMHOSTALLOC_PORTABLE 0x01 + +/** + * If set, host memory is mapped into CUDA address space and + * ::cuMemHostGetDevicePointer() may be called on the host pointer. + * Flag for ::cuMemHostAlloc() + */ +#define CU_MEMHOSTALLOC_DEVICEMAP 0x02 + +/** + * If set, host memory is allocated as write-combined - fast to write, + * faster to DMA, slow to read except via SSE4 streaming load instruction + * (MOVNTDQA). + * Flag for ::cuMemHostAlloc() + */ +#define CU_MEMHOSTALLOC_WRITECOMBINED 0x04 + +/** + * If set, host memory is portable between CUDA contexts. + * Flag for ::cuMemHostRegister() + */ +#define CU_MEMHOSTREGISTER_PORTABLE 0x01 + +/** + * If set, host memory is mapped into CUDA address space and + * ::cuMemHostGetDevicePointer() may be called on the host pointer. + * Flag for ::cuMemHostRegister() + */ +#define CU_MEMHOSTREGISTER_DEVICEMAP 0x02 + +/** + * If set, peer memory is mapped into CUDA address space and + * ::cuMemPeerGetDevicePointer() may be called on the host pointer. + * Flag for ::cuMemPeerRegister() + */ +#define CU_MEMPEERREGISTER_DEVICEMAP 0x02 +#endif + +#if __CUDA_API_VERSION >= 3020 +/** + * 2D memory copy parameters + */ +typedef struct CUDA_MEMCPY2D_st +{ + size_t srcXInBytes; /**< Source X in bytes */ + size_t srcY; /**< Source Y */ + + CUmemorytype srcMemoryType; /**< Source memory type (host, device, array) */ + const void *srcHost; /**< Source host pointer */ + CUdeviceptr srcDevice; /**< Source device pointer */ + CUarray srcArray; /**< Source array reference */ + size_t srcPitch; /**< Source pitch (ignored when src is array) */ + + size_t dstXInBytes; /**< Destination X in bytes */ + size_t dstY; /**< Destination Y */ + + CUmemorytype dstMemoryType; /**< Destination memory type (host, device, array) */ + void *dstHost; /**< Destination host pointer */ + CUdeviceptr dstDevice; /**< Destination device pointer */ + CUarray dstArray; /**< Destination array reference */ + size_t dstPitch; /**< Destination pitch (ignored when dst is array) */ + + size_t WidthInBytes; /**< Width of 2D memory copy in bytes */ + size_t Height; /**< Height of 2D memory copy */ +} CUDA_MEMCPY2D; + +/** + * 3D memory copy parameters + */ +typedef struct CUDA_MEMCPY3D_st +{ + size_t srcXInBytes; /**< Source X in bytes */ + size_t srcY; /**< Source Y */ + size_t srcZ; /**< Source Z */ + size_t srcLOD; /**< Source LOD */ + CUmemorytype srcMemoryType; /**< Source memory type (host, device, array) */ + const void *srcHost; /**< Source host pointer */ + CUdeviceptr srcDevice; /**< Source device pointer */ + CUarray srcArray; /**< Source array reference */ + void *reserved0; /**< Must be NULL */ + size_t srcPitch; /**< Source pitch (ignored when src is array) */ + size_t srcHeight; /**< Source height (ignored when src is array; may be 0 if Depth==1) */ + + size_t dstXInBytes; /**< Destination X in bytes */ + size_t dstY; /**< Destination Y */ + size_t dstZ; /**< Destination Z */ + size_t dstLOD; /**< Destination LOD */ + CUmemorytype dstMemoryType; /**< Destination memory type (host, device, array) */ + void *dstHost; /**< Destination host pointer */ + CUdeviceptr dstDevice; /**< Destination device pointer */ + CUarray dstArray; /**< Destination array reference */ + void *reserved1; /**< Must be NULL */ + size_t dstPitch; /**< Destination pitch (ignored when dst is array) */ + size_t dstHeight; /**< Destination height (ignored when dst is array; may be 0 if Depth==1) */ + + size_t WidthInBytes; /**< Width of 3D memory copy in bytes */ + size_t Height; /**< Height of 3D memory copy */ + size_t Depth; /**< Depth of 3D memory copy */ +} CUDA_MEMCPY3D; + +/** + * 3D memory cross-context copy parameters + */ +typedef struct CUDA_MEMCPY3D_PEER_st +{ + size_t srcXInBytes; /**< Source X in bytes */ + size_t srcY; /**< Source Y */ + size_t srcZ; /**< Source Z */ + size_t srcLOD; /**< Source LOD */ + CUmemorytype srcMemoryType; /**< Source memory type (host, device, array) */ + const void *srcHost; /**< Source host pointer */ + CUdeviceptr srcDevice; /**< Source device pointer */ + CUarray srcArray; /**< Source array reference */ + CUcontext srcContext; /**< Source context (ignored with srcMemoryType is ::CU_MEMORYTYPE_ARRAY) */ + size_t srcPitch; /**< Source pitch (ignored when src is array) */ + size_t srcHeight; /**< Source height (ignored when src is array; may be 0 if Depth==1) */ + + size_t dstXInBytes; /**< Destination X in bytes */ + size_t dstY; /**< Destination Y */ + size_t dstZ; /**< Destination Z */ + size_t dstLOD; /**< Destination LOD */ + CUmemorytype dstMemoryType; /**< Destination memory type (host, device, array) */ + void *dstHost; /**< Destination host pointer */ + CUdeviceptr dstDevice; /**< Destination device pointer */ + CUarray dstArray; /**< Destination array reference */ + CUcontext dstContext; /**< Destination context (ignored with dstMemoryType is ::CU_MEMORYTYPE_ARRAY) */ + size_t dstPitch; /**< Destination pitch (ignored when dst is array) */ + size_t dstHeight; /**< Destination height (ignored when dst is array; may be 0 if Depth==1) */ + + size_t WidthInBytes; /**< Width of 3D memory copy in bytes */ + size_t Height; /**< Height of 3D memory copy */ + size_t Depth; /**< Depth of 3D memory copy */ +} CUDA_MEMCPY3D_PEER; + +/** + * Array descriptor + */ +typedef struct CUDA_ARRAY_DESCRIPTOR_st +{ + size_t Width; /**< Width of array */ + size_t Height; /**< Height of array */ + + CUarray_format Format; /**< Array format */ + unsigned int NumChannels; /**< Channels per array element */ +} CUDA_ARRAY_DESCRIPTOR; + +/** + * 3D array descriptor + */ +typedef struct CUDA_ARRAY3D_DESCRIPTOR_st +{ + size_t Width; /**< Width of 3D array */ + size_t Height; /**< Height of 3D array */ + size_t Depth; /**< Depth of 3D array */ + + CUarray_format Format; /**< Array format */ + unsigned int NumChannels; /**< Channels per array element */ + unsigned int Flags; /**< Flags */ +} CUDA_ARRAY3D_DESCRIPTOR; + +#endif /* __CUDA_API_VERSION >= 3020 */ + +/** + * If set, the CUDA array is a collection of layers, where each layer is either a 1D + * or a 2D array and the Depth member of CUDA_ARRAY3D_DESCRIPTOR specifies the number + * of layers, not the depth of a 3D array. + */ +#define CUDA_ARRAY3D_LAYERED 0x01 + +/** + * Deprecated, use CUDA_ARRAY3D_LAYERED + */ +#define CUDA_ARRAY3D_2DARRAY 0x01 + +/** + * This flag must be set in order to bind a surface reference + * to the CUDA array + */ +#define CUDA_ARRAY3D_SURFACE_LDST 0x02 + +/** + * Override the texref format with a format inferred from the array. + * Flag for ::cuTexRefSetArray() + */ +#define CU_TRSA_OVERRIDE_FORMAT 0x01 + +/** + * Read the texture as integers rather than promoting the values to floats + * in the range [0,1]. + * Flag for ::cuTexRefSetFlags() + */ +#define CU_TRSF_READ_AS_INTEGER 0x01 + +/** + * Use normalized texture coordinates in the range [0,1) instead of [0,dim). + * Flag for ::cuTexRefSetFlags() + */ +#define CU_TRSF_NORMALIZED_COORDINATES 0x02 + +/** + * Perform sRGB->linear conversion during texture read. + * Flag for ::cuTexRefSetFlags() + */ +#define CU_TRSF_SRGB 0x10 + +/** + * End of array terminator for the \p extra parameter to + * ::cuLaunchKernel + */ +#define CU_LAUNCH_PARAM_END ((void*)0x00) + +/** + * Indicator that the next value in the \p extra parameter to + * ::cuLaunchKernel will be a pointer to a buffer containing all kernel + * parameters used for launching kernel \p f. This buffer needs to + * honor all alignment/padding requirements of the individual parameters. + * If ::CU_LAUNCH_PARAM_BUFFER_SIZE is not also specified in the + * \p extra array, then ::CU_LAUNCH_PARAM_BUFFER_POINTER will have no + * effect. + */ +#define CU_LAUNCH_PARAM_BUFFER_POINTER ((void*)0x01) + +/** + * Indicator that the next value in the \p extra parameter to + * ::cuLaunchKernel will be a pointer to a size_t which contains the + * size of the buffer specified with ::CU_LAUNCH_PARAM_BUFFER_POINTER. + * It is required that ::CU_LAUNCH_PARAM_BUFFER_POINTER also be specified + * in the \p extra array if the value associated with + * ::CU_LAUNCH_PARAM_BUFFER_SIZE is not zero. + */ +#define CU_LAUNCH_PARAM_BUFFER_SIZE ((void*)0x02) + +/** + * For texture references loaded into the module, use default texunit from + * texture reference. + */ +#define CU_PARAM_TR_DEFAULT -1 + +/** + * CUDA API made obselete at API version 3020 + */ +#if defined(__CUDA_API_VERSION_INTERNAL) + #define CUdeviceptr CUdeviceptr_v1 + #define CUDA_MEMCPY2D_st CUDA_MEMCPY2D_v1_st + #define CUDA_MEMCPY2D CUDA_MEMCPY2D_v1 + #define CUDA_MEMCPY3D_st CUDA_MEMCPY3D_v1_st + #define CUDA_MEMCPY3D CUDA_MEMCPY3D_v1 + #define CUDA_ARRAY_DESCRIPTOR_st CUDA_ARRAY_DESCRIPTOR_v1_st + #define CUDA_ARRAY_DESCRIPTOR CUDA_ARRAY_DESCRIPTOR_v1 + #define CUDA_ARRAY3D_DESCRIPTOR_st CUDA_ARRAY3D_DESCRIPTOR_v1_st + #define CUDA_ARRAY3D_DESCRIPTOR CUDA_ARRAY3D_DESCRIPTOR_v1 +#endif /* CUDA_FORCE_LEGACY32_INTERNAL */ + +#if defined(__CUDA_API_VERSION_INTERNAL) || __CUDA_API_VERSION < 3020 +typedef unsigned int CUdeviceptr; + +typedef struct CUDA_MEMCPY2D_st +{ + unsigned int srcXInBytes; /**< Source X in bytes */ + unsigned int srcY; /**< Source Y */ + CUmemorytype srcMemoryType; /**< Source memory type (host, device, array) */ + const void *srcHost; /**< Source host pointer */ + CUdeviceptr srcDevice; /**< Source device pointer */ + CUarray srcArray; /**< Source array reference */ + unsigned int srcPitch; /**< Source pitch (ignored when src is array) */ + + unsigned int dstXInBytes; /**< Destination X in bytes */ + unsigned int dstY; /**< Destination Y */ + CUmemorytype dstMemoryType; /**< Destination memory type (host, device, array) */ + void *dstHost; /**< Destination host pointer */ + CUdeviceptr dstDevice; /**< Destination device pointer */ + CUarray dstArray; /**< Destination array reference */ + unsigned int dstPitch; /**< Destination pitch (ignored when dst is array) */ + + unsigned int WidthInBytes; /**< Width of 2D memory copy in bytes */ + unsigned int Height; /**< Height of 2D memory copy */ +} CUDA_MEMCPY2D; + +typedef struct CUDA_MEMCPY3D_st +{ + unsigned int srcXInBytes; /**< Source X in bytes */ + unsigned int srcY; /**< Source Y */ + unsigned int srcZ; /**< Source Z */ + unsigned int srcLOD; /**< Source LOD */ + CUmemorytype srcMemoryType; /**< Source memory type (host, device, array) */ + const void *srcHost; /**< Source host pointer */ + CUdeviceptr srcDevice; /**< Source device pointer */ + CUarray srcArray; /**< Source array reference */ + void *reserved0; /**< Must be NULL */ + unsigned int srcPitch; /**< Source pitch (ignored when src is array) */ + unsigned int srcHeight; /**< Source height (ignored when src is array; may be 0 if Depth==1) */ + + unsigned int dstXInBytes; /**< Destination X in bytes */ + unsigned int dstY; /**< Destination Y */ + unsigned int dstZ; /**< Destination Z */ + unsigned int dstLOD; /**< Destination LOD */ + CUmemorytype dstMemoryType; /**< Destination memory type (host, device, array) */ + void *dstHost; /**< Destination host pointer */ + CUdeviceptr dstDevice; /**< Destination device pointer */ + CUarray dstArray; /**< Destination array reference */ + void *reserved1; /**< Must be NULL */ + unsigned int dstPitch; /**< Destination pitch (ignored when dst is array) */ + unsigned int dstHeight; /**< Destination height (ignored when dst is array; may be 0 if Depth==1) */ + + unsigned int WidthInBytes; /**< Width of 3D memory copy in bytes */ + unsigned int Height; /**< Height of 3D memory copy */ + unsigned int Depth; /**< Depth of 3D memory copy */ +} CUDA_MEMCPY3D; + +typedef struct CUDA_ARRAY_DESCRIPTOR_st +{ + unsigned int Width; /**< Width of array */ + unsigned int Height; /**< Height of array */ + + CUarray_format Format; /**< Array format */ + unsigned int NumChannels; /**< Channels per array element */ +} CUDA_ARRAY_DESCRIPTOR; + +typedef struct CUDA_ARRAY3D_DESCRIPTOR_st +{ + unsigned int Width; /**< Width of 3D array */ + unsigned int Height; /**< Height of 3D array */ + unsigned int Depth; /**< Depth of 3D array */ + + CUarray_format Format; /**< Array format */ + unsigned int NumChannels; /**< Channels per array element */ + unsigned int Flags; /**< Flags */ +} CUDA_ARRAY3D_DESCRIPTOR; + +#endif /* (__CUDA_API_VERSION_INTERNAL) || __CUDA_API_VERSION < 3020 */ + +/* + * If set, the CUDA array contains an array of 2D slices + * and the Depth member of CUDA_ARRAY3D_DESCRIPTOR specifies + * the number of slices, not the depth of a 3D array. + */ +#define CUDA_ARRAY3D_2DARRAY 0x01 + +/** + * This flag must be set in order to bind a surface reference + * to the CUDA array + */ +#define CUDA_ARRAY3D_SURFACE_LDST 0x02 + +/** + * Override the texref format with a format inferred from the array. + * Flag for ::cuTexRefSetArray() + */ +#define CU_TRSA_OVERRIDE_FORMAT 0x01 + +/** + * Read the texture as integers rather than promoting the values to floats + * in the range [0,1]. + * Flag for ::cuTexRefSetFlags() + */ +#define CU_TRSF_READ_AS_INTEGER 0x01 + +/** + * Use normalized texture coordinates in the range [0,1) instead of [0,dim). + * Flag for ::cuTexRefSetFlags() + */ +#define CU_TRSF_NORMALIZED_COORDINATES 0x02 + +/** + * Perform sRGB->linear conversion during texture read. + * Flag for ::cuTexRefSetFlags() + */ +#define CU_TRSF_SRGB 0x10 + +/** + * For texture references loaded into the module, use default texunit from + * texture reference. + */ +#define CU_PARAM_TR_DEFAULT -1 + +/** @} */ /* END CUDA_TYPES */ + +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) + #define CUDAAPI __stdcall +#else + #define CUDAAPI +#endif + +/** + * \defgroup CUDA_INITIALIZE Initialization + * + * This section describes the initialization functions of the low-level CUDA + * driver application programming interface. + * + * @{ + */ + +/********************************* + ** Initialization + *********************************/ +typedef CUresult CUDAAPI tcuInit(unsigned int Flags); + +/********************************* + ** Driver Version Query + *********************************/ +typedef CUresult CUDAAPI tcuDriverGetVersion(int *driverVersion); + +/************************************ + ** + ** Device management + ** + ***********************************/ + +typedef CUresult CUDAAPI tcuDeviceGet(CUdevice *device, int ordinal); +typedef CUresult CUDAAPI tcuDeviceGetCount(int *count); +typedef CUresult CUDAAPI tcuDeviceGetName(char *name, int len, CUdevice dev); +typedef CUresult CUDAAPI tcuDeviceComputeCapability(int *major, int *minor, CUdevice dev); +#if __CUDA_API_VERSION >= 3020 + typedef CUresult CUDAAPI tcuDeviceTotalMem(size_t *bytes, CUdevice dev); +#else + typedef CUresult CUDAAPI tcuDeviceTotalMem(unsigned int *bytes, CUdevice dev); +#endif + +typedef CUresult CUDAAPI tcuDeviceGetProperties(CUdevprop *prop, CUdevice dev); +typedef CUresult CUDAAPI tcuDeviceGetAttribute(int *pi, CUdevice_attribute attrib, CUdevice dev); + +/************************************ + ** + ** Context management + ** + ***********************************/ +typedef CUresult CUDAAPI tcuCtxCreate(CUcontext *pctx, unsigned int flags, CUdevice dev); +typedef CUresult CUDAAPI tcuCtxDestroy(CUcontext ctx); +typedef CUresult CUDAAPI tcuCtxAttach(CUcontext *pctx, unsigned int flags); +typedef CUresult CUDAAPI tcuCtxDetach(CUcontext ctx); +typedef CUresult CUDAAPI tcuCtxPushCurrent(CUcontext ctx); +typedef CUresult CUDAAPI tcuCtxPopCurrent(CUcontext *pctx); + +typedef CUresult CUDAAPI tcuCtxSetCurrent(CUcontext ctx); +typedef CUresult CUDAAPI tcuCtxGetCurrent(CUcontext *pctx); + +typedef CUresult CUDAAPI tcuCtxGetDevice(CUdevice *device); +typedef CUresult CUDAAPI tcuCtxSynchronize(void); + + +/************************************ + ** + ** Module management + ** + ***********************************/ +typedef CUresult CUDAAPI tcuModuleLoad(CUmodule *module, const char *fname); +typedef CUresult CUDAAPI tcuModuleLoadData(CUmodule *module, const void *image); +typedef CUresult CUDAAPI tcuModuleLoadDataEx(CUmodule *module, const void *image, unsigned int numOptions, CUjit_option *options, void **optionValues); +typedef CUresult CUDAAPI tcuModuleLoadFatBinary(CUmodule *module, const void *fatCubin); +typedef CUresult CUDAAPI tcuModuleUnload(CUmodule hmod); +typedef CUresult CUDAAPI tcuModuleGetFunction(CUfunction *hfunc, CUmodule hmod, const char *name); + +#if __CUDA_API_VERSION >= 3020 + typedef CUresult CUDAAPI tcuModuleGetGlobal(CUdeviceptr *dptr, size_t *bytes, CUmodule hmod, const char *name); +#else + typedef CUresult CUDAAPI tcuModuleGetGlobal(CUdeviceptr *dptr, unsigned int *bytes, CUmodule hmod, const char *name); +#endif + +typedef CUresult CUDAAPI tcuModuleGetTexRef(CUtexref *pTexRef, CUmodule hmod, const char *name); +typedef CUresult CUDAAPI tcuModuleGetSurfRef(CUsurfref *pSurfRef, CUmodule hmod, const char *name); + +/************************************ + ** + ** Memory management + ** + ***********************************/ +#if __CUDA_API_VERSION >= 3020 + typedef CUresult CUDAAPI tcuMemGetInfo(size_t *free, size_t *total); + typedef CUresult CUDAAPI tcuMemAlloc(CUdeviceptr *dptr, size_t bytesize); + typedef CUresult CUDAAPI tcuMemGetAddressRange(CUdeviceptr *pbase, size_t *psize, CUdeviceptr dptr); + typedef CUresult CUDAAPI tcuMemAllocPitch(CUdeviceptr *dptr, + size_t *pPitch, + size_t WidthInBytes, + size_t Height, + // size of biggest r/w to be performed by kernels on this memory + // 4, 8 or 16 bytes + unsigned int ElementSizeBytes + ); +#else + typedef CUresult CUDAAPI tcuMemGetInfo(unsigned int *free, unsigned int *total); + typedef CUresult CUDAAPI tcuMemAlloc(CUdeviceptr *dptr, unsigned int bytesize); + typedef CUresult CUDAAPI tcuMemGetAddressRange(CUdeviceptr *pbase, unsigned int *psize, CUdeviceptr dptr); + typedef CUresult CUDAAPI tcuMemAllocPitch(CUdeviceptr *dptr, + unsigned int *pPitch, + unsigned int WidthInBytes, + unsigned int Height, + // size of biggest r/w to be performed by kernels on this memory + // 4, 8 or 16 bytes + unsigned int ElementSizeBytes + ); +#endif + +typedef CUresult CUDAAPI tcuMemFree(CUdeviceptr dptr); + +#if __CUDA_API_VERSION >= 3020 + typedef CUresult CUDAAPI tcuMemAllocHost(void **pp, size_t bytesize); +#else + typedef CUresult CUDAAPI tcuMemAllocHost(void **pp, unsigned int bytesize); +#endif + +typedef CUresult CUDAAPI tcuMemFreeHost(void *p); +typedef CUresult CUDAAPI tcuMemHostAlloc(void **pp, size_t bytesize, unsigned int Flags); + +typedef CUresult CUDAAPI tcuMemHostGetDevicePointer(CUdeviceptr *pdptr, void *p, unsigned int Flags); +typedef CUresult CUDAAPI tcuMemHostGetFlags(unsigned int *pFlags, void *p); + +typedef CUresult CUDAAPI tcuMemHostRegister(void *p, size_t bytesize, unsigned int Flags); +typedef CUresult CUDAAPI tcuMemHostUnregister(void *p);; +typedef CUresult CUDAAPI tcuMemcpy(CUdeviceptr dst, CUdeviceptr src, size_t ByteCount); +typedef CUresult CUDAAPI tcuMemcpyPeer(CUdeviceptr dstDevice, CUcontext dstContext, CUdeviceptr srcDevice, CUcontext srcContext, size_t ByteCount); + +/************************************ + ** + ** Synchronous Memcpy + ** + ** Intra-device memcpy's done with these functions may execute in parallel with the CPU, + ** but if host memory is involved, they wait until the copy is done before returning. + ** + ***********************************/ +// 1D functions +#if __CUDA_API_VERSION >= 3020 + // system <-> device memory + typedef CUresult CUDAAPI tcuMemcpyHtoD(CUdeviceptr dstDevice, const void *srcHost, size_t ByteCount); + typedef CUresult CUDAAPI tcuMemcpyDtoH(void *dstHost, CUdeviceptr srcDevice, size_t ByteCount); + + // device <-> device memory + typedef CUresult CUDAAPI tcuMemcpyDtoD(CUdeviceptr dstDevice, CUdeviceptr srcDevice, size_t ByteCount); + + // device <-> array memory + typedef CUresult CUDAAPI tcuMemcpyDtoA(CUarray dstArray, size_t dstOffset, CUdeviceptr srcDevice, size_t ByteCount); + typedef CUresult CUDAAPI tcuMemcpyAtoD(CUdeviceptr dstDevice, CUarray srcArray, size_t srcOffset, size_t ByteCount); + + // system <-> array memory + typedef CUresult CUDAAPI tcuMemcpyHtoA(CUarray dstArray, size_t dstOffset, const void *srcHost, size_t ByteCount); + typedef CUresult CUDAAPI tcuMemcpyAtoH(void *dstHost, CUarray srcArray, size_t srcOffset, size_t ByteCount); + + // array <-> array memory + typedef CUresult CUDAAPI tcuMemcpyAtoA(CUarray dstArray, size_t dstOffset, CUarray srcArray, size_t srcOffset, size_t ByteCount); +#else + // system <-> device memory + typedef CUresult CUDAAPI tcuMemcpyHtoD(CUdeviceptr dstDevice, const void *srcHost, unsigned int ByteCount); + typedef CUresult CUDAAPI tcuMemcpyDtoH(void *dstHost, CUdeviceptr srcDevice, unsigned int ByteCount); + + // device <-> device memory + typedef CUresult CUDAAPI tcuMemcpyDtoD(CUdeviceptr dstDevice, CUdeviceptr srcDevice, unsigned int ByteCount); + + // device <-> array memory + typedef CUresult CUDAAPI tcuMemcpyDtoA(CUarray dstArray, unsigned int dstOffset, CUdeviceptr srcDevice, unsigned int ByteCount); + typedef CUresult CUDAAPI tcuMemcpyAtoD(CUdeviceptr dstDevice, CUarray srcArray, unsigned int srcOffset, unsigned int ByteCount); + + // system <-> array memory + typedef CUresult CUDAAPI tcuMemcpyHtoA(CUarray dstArray, unsigned int dstOffset, const void *srcHost, unsigned int ByteCount); + typedef CUresult CUDAAPI tcuMemcpyAtoH(void *dstHost, CUarray srcArray, unsigned int srcOffset, unsigned int ByteCount); + + // array <-> array memory + typedef CUresult CUDAAPI tcuMemcpyAtoA(CUarray dstArray, unsigned int dstOffset, CUarray srcArray, unsigned int srcOffset, unsigned int ByteCount); +#endif + +// 2D memcpy +typedef CUresult CUDAAPI tcuMemcpy2D(const CUDA_MEMCPY2D *pCopy); +typedef CUresult CUDAAPI tcuMemcpy2DUnaligned(const CUDA_MEMCPY2D *pCopy); + +// 3D memcpy +typedef CUresult CUDAAPI tcuMemcpy3D(const CUDA_MEMCPY3D *pCopy); + +/************************************ + ** + ** Asynchronous Memcpy + ** + ** Any host memory involved must be DMA'able (e.g., allocated with cuMemAllocHost). + ** memcpy's done with these functions execute in parallel with the CPU and, if + ** the hardware is available, may execute in parallel with the GPU. + ** Asynchronous memcpy must be accompanied by appropriate stream synchronization. + ** + ***********************************/ + +// 1D functions +#if __CUDA_API_VERSION >= 3020 + // system <-> device memory + typedef CUresult CUDAAPI tcuMemcpyHtoDAsync(CUdeviceptr dstDevice, + const void *srcHost, size_t ByteCount, CUstream hStream); + typedef CUresult CUDAAPI tcuMemcpyDtoHAsync(void *dstHost, + CUdeviceptr srcDevice, size_t ByteCount, CUstream hStream); + + // device <-> device memory + typedef CUresult CUDAAPI tcuMemcpyDtoDAsync(CUdeviceptr dstDevice, + CUdeviceptr srcDevice, size_t ByteCount, CUstream hStream); + + // system <-> array memory + typedef CUresult CUDAAPI tcuMemcpyHtoAAsync(CUarray dstArray, size_t dstOffset, + const void *srcHost, size_t ByteCount, CUstream hStream); + typedef CUresult CUDAAPI tcuMemcpyAtoHAsync(void *dstHost, CUarray srcArray, size_t srcOffset, + size_t ByteCount, CUstream hStream); +#else + // system <-> device memory + typedef CUresult CUDAAPI tcuMemcpyHtoDAsync(CUdeviceptr dstDevice, + const void *srcHost, unsigned int ByteCount, CUstream hStream); + typedef CUresult CUDAAPI tcuMemcpyDtoHAsync(void *dstHost, + CUdeviceptr srcDevice, unsigned int ByteCount, CUstream hStream); + + // device <-> device memory + typedef CUresult CUDAAPI tcuMemcpyDtoDAsync(CUdeviceptr dstDevice, + CUdeviceptr srcDevice, unsigned int ByteCount, CUstream hStream); + + // system <-> array memory + typedef CUresult CUDAAPI tcuMemcpyHtoAAsync(CUarray dstArray, unsigned int dstOffset, + const void *srcHost, unsigned int ByteCount, CUstream hStream); + typedef CUresult CUDAAPI tcuMemcpyAtoHAsync(void *dstHost, CUarray srcArray, unsigned int srcOffset, + unsigned int ByteCount, CUstream hStream); +#endif + +// 2D memcpy +typedef CUresult CUDAAPI tcuMemcpy2DAsync(const CUDA_MEMCPY2D *pCopy, CUstream hStream); + +// 3D memcpy +typedef CUresult CUDAAPI tcuMemcpy3DAsync(const CUDA_MEMCPY3D *pCopy, CUstream hStream); + +/************************************ + ** + ** Memset + ** + ***********************************/ +typedef CUresult CUDAAPI tcuMemsetD8(CUdeviceptr dstDevice, unsigned char uc, unsigned int N); +typedef CUresult CUDAAPI tcuMemsetD16(CUdeviceptr dstDevice, unsigned short us, unsigned int N); +typedef CUresult CUDAAPI tcuMemsetD32(CUdeviceptr dstDevice, unsigned int ui, unsigned int N); + +#if __CUDA_API_VERSION >= 3020 + typedef CUresult CUDAAPI tcuMemsetD2D8(CUdeviceptr dstDevice, unsigned int dstPitch, unsigned char uc, size_t Width, size_t Height); + typedef CUresult CUDAAPI tcuMemsetD2D16(CUdeviceptr dstDevice, unsigned int dstPitch, unsigned short us, size_t Width, size_t Height); + typedef CUresult CUDAAPI tcuMemsetD2D32(CUdeviceptr dstDevice, unsigned int dstPitch, unsigned int ui, size_t Width, size_t Height); +#else + typedef CUresult CUDAAPI tcuMemsetD2D8(CUdeviceptr dstDevice, unsigned int dstPitch, unsigned char uc, unsigned int Width, unsigned int Height); + typedef CUresult CUDAAPI tcuMemsetD2D16(CUdeviceptr dstDevice, unsigned int dstPitch, unsigned short us, unsigned int Width, unsigned int Height); + typedef CUresult CUDAAPI tcuMemsetD2D32(CUdeviceptr dstDevice, unsigned int dstPitch, unsigned int ui, unsigned int Width, unsigned int Height); +#endif + +/************************************ + ** + ** Function management + ** + ***********************************/ + + +typedef CUresult CUDAAPI tcuFuncSetBlockShape(CUfunction hfunc, int x, int y, int z); +typedef CUresult CUDAAPI tcuFuncSetSharedSize(CUfunction hfunc, unsigned int bytes); +typedef CUresult CUDAAPI tcuFuncGetAttribute(int *pi, CUfunction_attribute attrib, CUfunction hfunc); +typedef CUresult CUDAAPI tcuFuncSetCacheConfig(CUfunction hfunc, CUfunc_cache config); +typedef CUresult CUDAAPI tcuLaunchKernel(CUfunction f, + unsigned int gridDimX, unsigned int gridDimY, unsigned int gridDimZ, + unsigned int blockDimX, unsigned int blockDimY, unsigned int blockDimZ, + unsigned int sharedMemBytes, + CUstream hStream, void **kernelParams, void **extra); + +/************************************ + ** + ** Array management + ** + ***********************************/ + +typedef CUresult CUDAAPI tcuArrayCreate(CUarray *pHandle, const CUDA_ARRAY_DESCRIPTOR *pAllocateArray); +typedef CUresult CUDAAPI tcuArrayGetDescriptor(CUDA_ARRAY_DESCRIPTOR *pArrayDescriptor, CUarray hArray); +typedef CUresult CUDAAPI tcuArrayDestroy(CUarray hArray); + +typedef CUresult CUDAAPI tcuArray3DCreate(CUarray *pHandle, const CUDA_ARRAY3D_DESCRIPTOR *pAllocateArray); +typedef CUresult CUDAAPI tcuArray3DGetDescriptor(CUDA_ARRAY3D_DESCRIPTOR *pArrayDescriptor, CUarray hArray); + + +/************************************ + ** + ** Texture reference management + ** + ***********************************/ +typedef CUresult CUDAAPI tcuTexRefCreate(CUtexref *pTexRef); +typedef CUresult CUDAAPI tcuTexRefDestroy(CUtexref hTexRef); + +typedef CUresult CUDAAPI tcuTexRefSetArray(CUtexref hTexRef, CUarray hArray, unsigned int Flags); + +#if __CUDA_API_VERSION >= 3020 + typedef CUresult CUDAAPI tcuTexRefSetAddress(size_t *ByteOffset, CUtexref hTexRef, CUdeviceptr dptr, size_t bytes); + typedef CUresult CUDAAPI tcuTexRefSetAddress2D(CUtexref hTexRef, const CUDA_ARRAY_DESCRIPTOR *desc, CUdeviceptr dptr, size_t Pitch); +#else + typedef CUresult CUDAAPI tcuTexRefSetAddress(unsigned int *ByteOffset, CUtexref hTexRef, CUdeviceptr dptr, unsigned int bytes); + typedef CUresult CUDAAPI tcuTexRefSetAddress2D(CUtexref hTexRef, const CUDA_ARRAY_DESCRIPTOR *desc, CUdeviceptr dptr, unsigned int Pitch); +#endif + +typedef CUresult CUDAAPI tcuTexRefSetFormat(CUtexref hTexRef, CUarray_format fmt, int NumPackedComponents); +typedef CUresult CUDAAPI tcuTexRefSetAddressMode(CUtexref hTexRef, int dim, CUaddress_mode am); +typedef CUresult CUDAAPI tcuTexRefSetFilterMode(CUtexref hTexRef, CUfilter_mode fm); +typedef CUresult CUDAAPI tcuTexRefSetFlags(CUtexref hTexRef, unsigned int Flags); + +typedef CUresult CUDAAPI tcuTexRefGetAddress(CUdeviceptr *pdptr, CUtexref hTexRef); +typedef CUresult CUDAAPI tcuTexRefGetArray(CUarray *phArray, CUtexref hTexRef); +typedef CUresult CUDAAPI tcuTexRefGetAddressMode(CUaddress_mode *pam, CUtexref hTexRef, int dim); +typedef CUresult CUDAAPI tcuTexRefGetFilterMode(CUfilter_mode *pfm, CUtexref hTexRef); +typedef CUresult CUDAAPI tcuTexRefGetFormat(CUarray_format *pFormat, int *pNumChannels, CUtexref hTexRef); +typedef CUresult CUDAAPI tcuTexRefGetFlags(unsigned int *pFlags, CUtexref hTexRef); + +/************************************ + ** + ** Surface reference management + ** + ***********************************/ +typedef CUresult CUDAAPI tcuSurfRefSetArray(CUsurfref hSurfRef, CUarray hArray, unsigned int Flags); +typedef CUresult CUDAAPI tcuSurfRefGetArray(CUarray *phArray, CUsurfref hSurfRef); + +/************************************ + ** + ** Parameter management + ** + ***********************************/ + +typedef CUresult CUDAAPI tcuParamSetSize(CUfunction hfunc, unsigned int numbytes); +typedef CUresult CUDAAPI tcuParamSeti(CUfunction hfunc, int offset, unsigned int value); +typedef CUresult CUDAAPI tcuParamSetf(CUfunction hfunc, int offset, float value); +typedef CUresult CUDAAPI tcuParamSetv(CUfunction hfunc, int offset, void *ptr, unsigned int numbytes); +typedef CUresult CUDAAPI tcuParamSetTexRef(CUfunction hfunc, int texunit, CUtexref hTexRef); + + +/************************************ + ** + ** Launch functions + ** + ***********************************/ + +typedef CUresult CUDAAPI tcuLaunch(CUfunction f); +typedef CUresult CUDAAPI tcuLaunchGrid(CUfunction f, int grid_width, int grid_height); +typedef CUresult CUDAAPI tcuLaunchGridAsync(CUfunction f, int grid_width, int grid_height, CUstream hStream); + +/************************************ + ** + ** Events + ** + ***********************************/ +typedef CUresult CUDAAPI tcuEventCreate(CUevent *phEvent, unsigned int Flags); +typedef CUresult CUDAAPI tcuEventRecord(CUevent hEvent, CUstream hStream); +typedef CUresult CUDAAPI tcuEventQuery(CUevent hEvent); +typedef CUresult CUDAAPI tcuEventSynchronize(CUevent hEvent); +typedef CUresult CUDAAPI tcuEventDestroy(CUevent hEvent); +typedef CUresult CUDAAPI tcuEventElapsedTime(float *pMilliseconds, CUevent hStart, CUevent hEnd); + +/************************************ + ** + ** Streams + ** + ***********************************/ +typedef CUresult CUDAAPI tcuStreamCreate(CUstream *phStream, unsigned int Flags); +typedef CUresult CUDAAPI tcuStreamQuery(CUstream hStream); +typedef CUresult CUDAAPI tcuStreamSynchronize(CUstream hStream); +typedef CUresult CUDAAPI tcuStreamDestroy(CUstream hStream); + +/************************************ + ** + ** Graphics interop + ** + ***********************************/ +typedef CUresult CUDAAPI tcuGraphicsUnregisterResource(CUgraphicsResource resource); +typedef CUresult CUDAAPI tcuGraphicsSubResourceGetMappedArray(CUarray *pArray, CUgraphicsResource resource, unsigned int arrayIndex, unsigned int mipLevel); + +#if __CUDA_API_VERSION >= 3020 + typedef CUresult CUDAAPI tcuGraphicsResourceGetMappedPointer(CUdeviceptr *pDevPtr, size_t *pSize, CUgraphicsResource resource); +#else + typedef CUresult CUDAAPI tcuGraphicsResourceGetMappedPointer(CUdeviceptr *pDevPtr, unsigned int *pSize, CUgraphicsResource resource); +#endif + +typedef CUresult CUDAAPI tcuGraphicsResourceSetMapFlags(CUgraphicsResource resource, unsigned int flags); +typedef CUresult CUDAAPI tcuGraphicsMapResources(unsigned int count, CUgraphicsResource *resources, CUstream hStream); +typedef CUresult CUDAAPI tcuGraphicsUnmapResources(unsigned int count, CUgraphicsResource *resources, CUstream hStream); + +/************************************ + ** + ** Export tables + ** + ***********************************/ +typedef CUresult CUDAAPI tcuGetExportTable(const void **ppExportTable, const CUuuid *pExportTableId); + +/************************************ + ** + ** Limits + ** + ***********************************/ + +typedef CUresult CUDAAPI tcuCtxSetLimit(CUlimit limit, size_t value); +typedef CUresult CUDAAPI tcuCtxGetLimit(size_t *pvalue, CUlimit limit); + + +extern tcuDriverGetVersion *cuDriverGetVersion; +extern tcuDeviceGet *cuDeviceGet; +extern tcuDeviceGetCount *cuDeviceGetCount; +extern tcuDeviceGetName *cuDeviceGetName; +extern tcuDeviceComputeCapability *cuDeviceComputeCapability; +extern tcuDeviceGetProperties *cuDeviceGetProperties; +extern tcuDeviceGetAttribute *cuDeviceGetAttribute; +extern tcuCtxDestroy *cuCtxDestroy; +extern tcuCtxAttach *cuCtxAttach; +extern tcuCtxDetach *cuCtxDetach; +extern tcuCtxPushCurrent *cuCtxPushCurrent; +extern tcuCtxPopCurrent *cuCtxPopCurrent; + +extern tcuCtxSetCurrent *cuCtxSetCurrent; +extern tcuCtxGetCurrent *cuCtxGetCurrent; + +extern tcuCtxGetDevice *cuCtxGetDevice; +extern tcuCtxSynchronize *cuCtxSynchronize; +extern tcuModuleLoad *cuModuleLoad; +extern tcuModuleLoadData *cuModuleLoadData; +extern tcuModuleLoadDataEx *cuModuleLoadDataEx; +extern tcuModuleLoadFatBinary *cuModuleLoadFatBinary; +extern tcuModuleUnload *cuModuleUnload; +extern tcuModuleGetFunction *cuModuleGetFunction; +extern tcuModuleGetTexRef *cuModuleGetTexRef; +extern tcuModuleGetSurfRef *cuModuleGetSurfRef; +extern tcuMemFreeHost *cuMemFreeHost; +extern tcuMemHostAlloc *cuMemHostAlloc; +extern tcuMemHostGetFlags *cuMemHostGetFlags; + +extern tcuMemHostRegister *cuMemHostRegister; +extern tcuMemHostUnregister *cuMemHostUnregister; +extern tcuMemcpy *cuMemcpy; +extern tcuMemcpyPeer *cuMemcpyPeer; + +extern tcuDeviceTotalMem *cuDeviceTotalMem; +extern tcuCtxCreate *cuCtxCreate; +extern tcuModuleGetGlobal *cuModuleGetGlobal; +extern tcuMemGetInfo *cuMemGetInfo; +extern tcuMemAlloc *cuMemAlloc; +extern tcuMemAllocPitch *cuMemAllocPitch; +extern tcuMemFree *cuMemFree; +extern tcuMemGetAddressRange *cuMemGetAddressRange; +extern tcuMemAllocHost *cuMemAllocHost; +extern tcuMemHostGetDevicePointer *cuMemHostGetDevicePointer; +extern tcuFuncSetBlockShape *cuFuncSetBlockShape; +extern tcuFuncSetSharedSize *cuFuncSetSharedSize; +extern tcuFuncGetAttribute *cuFuncGetAttribute; +extern tcuFuncSetCacheConfig *cuFuncSetCacheConfig; +extern tcuLaunchKernel *cuLaunchKernel; +extern tcuArrayDestroy *cuArrayDestroy; +extern tcuTexRefCreate *cuTexRefCreate; +extern tcuTexRefDestroy *cuTexRefDestroy; +extern tcuTexRefSetArray *cuTexRefSetArray; +extern tcuTexRefSetFormat *cuTexRefSetFormat; +extern tcuTexRefSetAddressMode *cuTexRefSetAddressMode; +extern tcuTexRefSetFilterMode *cuTexRefSetFilterMode; +extern tcuTexRefSetFlags *cuTexRefSetFlags; +extern tcuTexRefGetArray *cuTexRefGetArray; +extern tcuTexRefGetAddressMode *cuTexRefGetAddressMode; +extern tcuTexRefGetFilterMode *cuTexRefGetFilterMode; +extern tcuTexRefGetFormat *cuTexRefGetFormat; +extern tcuTexRefGetFlags *cuTexRefGetFlags; +extern tcuSurfRefSetArray *cuSurfRefSetArray; +extern tcuSurfRefGetArray *cuSurfRefGetArray; +extern tcuParamSetSize *cuParamSetSize; +extern tcuParamSeti *cuParamSeti; +extern tcuParamSetf *cuParamSetf; +extern tcuParamSetv *cuParamSetv; +extern tcuParamSetTexRef *cuParamSetTexRef; +extern tcuLaunch *cuLaunch; +extern tcuLaunchGrid *cuLaunchGrid; +extern tcuLaunchGridAsync *cuLaunchGridAsync; +extern tcuEventCreate *cuEventCreate; +extern tcuEventRecord *cuEventRecord; +extern tcuEventQuery *cuEventQuery; +extern tcuEventSynchronize *cuEventSynchronize; +extern tcuEventDestroy *cuEventDestroy; +extern tcuEventElapsedTime *cuEventElapsedTime; +extern tcuStreamCreate *cuStreamCreate; +extern tcuStreamQuery *cuStreamQuery; +extern tcuStreamSynchronize *cuStreamSynchronize; +extern tcuStreamDestroy *cuStreamDestroy; +extern tcuGraphicsUnregisterResource *cuGraphicsUnregisterResource; +extern tcuGraphicsSubResourceGetMappedArray *cuGraphicsSubResourceGetMappedArray; +extern tcuGraphicsResourceSetMapFlags *cuGraphicsResourceSetMapFlags; +extern tcuGraphicsMapResources *cuGraphicsMapResources; +extern tcuGraphicsUnmapResources *cuGraphicsUnmapResources; +extern tcuGetExportTable *cuGetExportTable; +extern tcuCtxSetLimit *cuCtxSetLimit; +extern tcuCtxGetLimit *cuCtxGetLimit; + +// These functions could be using the CUDA 3.2 interface (_v2) +extern tcuMemcpyHtoD *cuMemcpyHtoD; +extern tcuMemcpyDtoH *cuMemcpyDtoH; +extern tcuMemcpyDtoD *cuMemcpyDtoD; +extern tcuMemcpyDtoA *cuMemcpyDtoA; +extern tcuMemcpyAtoD *cuMemcpyAtoD; +extern tcuMemcpyHtoA *cuMemcpyHtoA; +extern tcuMemcpyAtoH *cuMemcpyAtoH; +extern tcuMemcpyAtoA *cuMemcpyAtoA; +extern tcuMemcpy2D *cuMemcpy2D; +extern tcuMemcpy2DUnaligned *cuMemcpy2DUnaligned; +extern tcuMemcpy3D *cuMemcpy3D; +extern tcuMemcpyHtoDAsync *cuMemcpyHtoDAsync; +extern tcuMemcpyDtoHAsync *cuMemcpyDtoHAsync; +extern tcuMemcpyDtoDAsync *cuMemcpyDtoDAsync; +extern tcuMemcpyHtoAAsync *cuMemcpyHtoAAsync; +extern tcuMemcpyAtoHAsync *cuMemcpyAtoHAsync; +extern tcuMemcpy2DAsync *cuMemcpy2DAsync; +extern tcuMemcpy3DAsync *cuMemcpy3DAsync; +extern tcuMemsetD8 *cuMemsetD8; +extern tcuMemsetD16 *cuMemsetD16; +extern tcuMemsetD32 *cuMemsetD32; +extern tcuMemsetD2D8 *cuMemsetD2D8; +extern tcuMemsetD2D16 *cuMemsetD2D16; +extern tcuMemsetD2D32 *cuMemsetD2D32; +extern tcuArrayCreate *cuArrayCreate; +extern tcuArrayGetDescriptor *cuArrayGetDescriptor; +extern tcuArray3DCreate *cuArray3DCreate; +extern tcuArray3DGetDescriptor *cuArray3DGetDescriptor; +extern tcuTexRefSetAddress *cuTexRefSetAddress; +extern tcuTexRefSetAddress2D *cuTexRefSetAddress2D; +extern tcuTexRefGetAddress *cuTexRefGetAddress; +extern tcuGraphicsResourceGetMappedPointer *cuGraphicsResourceGetMappedPointer; + +/************************************/ +CUresult CUDAAPI cuInit (unsigned int, int cudaVersion); +void CUDAAPI cuUninit(); +/************************************/ + + +#ifndef __CUDA_API_VERSION +#define __CUDA_API_VERSION 4000 +#endif + + +#include +#include "../../src/compositor/gl_inc.h" + +/** + * \file dynlink_cudaGL.h + * \brief Header file for the OpenGL interoperability functions of the + * low-level CUDA driver application programming interface. + */ + +/** + * \defgroup CUDA_GL OpenGL Interoperability + * \ingroup CUDA_DRIVER + * + * ___MANBRIEF___ OpenGL interoperability functions of the low-level CUDA + * driver API (___CURRENT_FILE___) ___ENDMANBRIEF___ + * + * This section describes the OpenGL interoperability functions of the + * low-level CUDA driver application programming interface. Note that mapping + * of OpenGL resources is performed with the graphics API agnostic, resource + * mapping interface described in \ref CUDA_GRAPHICS "Graphics Interoperability". + * + * @{ + */ + +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) +#if !defined(WGL_NV_gpu_affinity) +typedef void* HGPUNV; +#endif +#endif /* _WIN32 */ + +typedef CUresult CUDAAPI tcuGraphicsGLRegisterBuffer(CUgraphicsResource *pCudaResource, GLuint buffer, unsigned int Flags); +typedef CUresult CUDAAPI tcuGraphicsGLRegisterImage(CUgraphicsResource *pCudaResource, GLuint image, GLenum target, unsigned int Flags); + +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) +typedef CUresult CUDAAPI tcuWGLGetDevice(CUdevice *pDevice, HGPUNV hGpu); +#endif /* _WIN32 */ + +/** + * CUDA devices corresponding to an OpenGL device + */ +typedef enum { + CU_GL_DEVICE_LIST_ALL = 0x01, /**< The CUDA devices for all GPUs used by the current OpenGL context */ + CU_GL_DEVICE_LIST_CURRENT_FRAME = 0x02, /**< The CUDA devices for the GPUs used by the current OpenGL context in its currently rendering frame */ + CU_GL_DEVICE_LIST_NEXT_FRAME = 0x03, /**< The CUDA devices for the GPUs to be used by the current OpenGL context in the next frame */ +} CUGLDeviceList; + +#if __CUDA_API_VERSION >= 6050 +typedef CUresult CUDAAPI tcuGLGetDevices(unsigned int *pCudaDeviceCount, CUdevice *pCudaDevices, unsigned int cudaDeviceCount, CUGLDeviceList deviceList); +#endif /* __CUDA_API_VERSION >= 6050 */ + +/** + * \defgroup CUDA_GL_DEPRECATED OpenGL Interoperability [DEPRECATED] + * + * ___MANBRIEF___ deprecated OpenGL interoperability functions of the low-level + * CUDA driver API (___CURRENT_FILE___) ___ENDMANBRIEF___ + * + * This section describes deprecated OpenGL interoperability functionality. + * + * @{ + */ + +/** Flags to map or unmap a resource */ +typedef enum CUGLmap_flags_enum { + CU_GL_MAP_RESOURCE_FLAGS_NONE = 0x00, + CU_GL_MAP_RESOURCE_FLAGS_READ_ONLY = 0x01, + CU_GL_MAP_RESOURCE_FLAGS_WRITE_DISCARD = 0x02, +} CUGLmap_flags; + +//#if __CUDA_API_VERSION >= 3020 +typedef CUresult CUDAAPI tcuGLCtxCreate(CUcontext *pCtx, unsigned int Flags, CUdevice device); +//#endif /* __CUDA_API_VERSION >= 3020 */ + +typedef CUresult CUDAAPI tcuGLInit(void); +typedef CUresult CUDAAPI tcuGLRegisterBufferObject(GLuint buffer); + +#if __CUDA_API_VERSION >= 3020 +typedef CUresult CUDAAPI tcuGLMapBufferObject(CUdeviceptr *dptr, size_t *size, GLuint buffer); +#endif /* __CUDA_API_VERSION >= 3020 */ + +typedef CUresult CUDAAPI tcuGLUnmapBufferObject(GLuint buffer); +typedef CUresult CUDAAPI tcuGLUnregisterBufferObject(GLuint buffer); +typedef CUresult CUDAAPI tcuGLSetBufferObjectMapFlags(GLuint buffer, unsigned int Flags); + +#if __CUDA_API_VERSION >= 3020 +typedef CUresult CUDAAPI tcuGLMapBufferObjectAsync(CUdeviceptr *dptr, size_t *size, GLuint buffer, CUstream hStream); +#endif /* __CUDA_API_VERSION >= 3020 */ + +typedef CUresult CUDAAPI tcuGLUnmapBufferObjectAsync(GLuint buffer, CUstream hStream); +typedef CUresult CUDAAPI tcuGLGetDevices(unsigned int *pCudaDeviceCount, CUdevice *pCudaDevices, unsigned int cudaDeviceCount, CUGLDeviceList deviceList); + +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) +extern tcuWGLGetDevice *cuWGLGetDevice; +#endif + +extern tcuGLCtxCreate *cuGLCtxCreate; +extern tcuGLCtxCreate *cuGLCtxCreate_v2; +extern tcuGLMapBufferObject *cuGLMapBufferObject; +extern tcuGLMapBufferObject *cuGLMapBufferObject_v2; +extern tcuGLMapBufferObjectAsync *cuGLMapBufferObjectAsync; + +#if __CUDA_API_VERSION >= 6050 +extern tcuGLGetDevices *cuGLGetDevices; +#endif + +extern tcuGraphicsGLRegisterBuffer *cuGraphicsGLRegisterBuffer; +extern tcuGraphicsGLRegisterImage *cuGraphicsGLRegisterImage; +extern tcuGLSetBufferObjectMapFlags *cuGLSetBufferObjectMapFlags; +extern tcuGLRegisterBufferObject *cuGLRegisterBufferObject; + +extern tcuGLUnmapBufferObject *cuGLUnmapBufferObject; +extern tcuGLUnmapBufferObjectAsync *cuGLUnmapBufferObjectAsync; + +extern tcuGLUnregisterBufferObject *cuGLUnregisterBufferObject; +extern tcuGLGetDevices *cuGLGetDevices; // CUDA 6.5 only + + +#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) +#include +typedef HMODULE CUDADRIVER; +#else +typedef void *CUDADRIVER; +#endif + + + + +#if defined(__x86_64) || defined(AMD64) || defined(_M_AMD64) +#if (CUDA_VERSION >= 3020) && (!defined(CUDA_FORCE_API_VERSION) || (CUDA_FORCE_API_VERSION >= 3020)) +#define __CUVID_DEVPTR64 +#endif +#endif + + +typedef void *CUvideodecoder; +typedef struct _CUcontextlock_st *CUvideoctxlock; + +/** + * \addtogroup VIDEO_DECODER Video Decoder + * @{ + */ + +/*! + * \enum cudaVideoCodec + * Video Codec Enums + */ +typedef enum cudaVideoCodec_enum { + cudaVideoCodec_MPEG1=0, /**< MPEG1 */ + cudaVideoCodec_MPEG2, /**< MPEG2 */ + cudaVideoCodec_MPEG4, /**< MPEG4 */ + cudaVideoCodec_VC1, /**< VC1 */ + cudaVideoCodec_H264, /**< H264 */ + cudaVideoCodec_JPEG, /**< JPEG */ + cudaVideoCodec_H264_SVC, /**< H264-SVC */ + cudaVideoCodec_H264_MVC, /**< H264-MVC */ + cudaVideoCodec_HEVC, /**< HEVC */ + cudaVideoCodec_VP8, /**< VP8 */ + cudaVideoCodec_VP9, /**< VP9 */ + cudaVideoCodec_NumCodecs, /**< Max COdecs */ + // Uncompressed YUV + cudaVideoCodec_YUV420 = (('I'<<24)|('Y'<<16)|('U'<<8)|('V')), /**< Y,U,V (4:2:0) */ + cudaVideoCodec_YV12 = (('Y'<<24)|('V'<<16)|('1'<<8)|('2')), /**< Y,V,U (4:2:0) */ + cudaVideoCodec_NV12 = (('N'<<24)|('V'<<16)|('1'<<8)|('2')), /**< Y,UV (4:2:0) */ + cudaVideoCodec_YUYV = (('Y'<<24)|('U'<<16)|('Y'<<8)|('V')), /**< YUYV/YUY2 (4:2:2) */ + cudaVideoCodec_UYVY = (('U'<<24)|('Y'<<16)|('V'<<8)|('Y')) /**< UYVY (4:2:2) */ +} cudaVideoCodec; + +/*! + * \enum cudaVideoSurfaceFormat + * Video Surface Formats Enums + */ +typedef enum cudaVideoSurfaceFormat_enum { + cudaVideoSurfaceFormat_NV12=0, /**< NV12 (currently the only supported output format) */ + cudaVideoSurfaceFormat_P016=1 +} cudaVideoSurfaceFormat; + +/*! + * \enum cudaVideoDeinterlaceMode + * Deinterlacing Modes Enums + */ +typedef enum cudaVideoDeinterlaceMode_enum { + cudaVideoDeinterlaceMode_Weave=0, /**< Weave both fields (no deinterlacing) */ + cudaVideoDeinterlaceMode_Bob, /**< Drop one field */ + cudaVideoDeinterlaceMode_Adaptive /**< Adaptive deinterlacing */ +} cudaVideoDeinterlaceMode; + +/*! + * \enum cudaVideoChromaFormat + * Chroma Formats Enums + */ +typedef enum cudaVideoChromaFormat_enum { + cudaVideoChromaFormat_Monochrome=0, /**< MonoChrome */ + cudaVideoChromaFormat_420, /**< 4:2:0 */ + cudaVideoChromaFormat_422, /**< 4:2:2 */ + cudaVideoChromaFormat_444 /**< 4:4:4 */ +} cudaVideoChromaFormat; + +/*! + * \enum cudaVideoCreateFlags + * Decoder Flags Enums + */ +typedef enum cudaVideoCreateFlags_enum { + cudaVideoCreate_Default = 0x00, /**< Default operation mode: use dedicated video engines */ + cudaVideoCreate_PreferCUDA = 0x01, /**< Use a CUDA-based decoder if faster than dedicated engines (requires a valid vidLock object for multi-threading) */ + cudaVideoCreate_PreferDXVA = 0x02, /**< Go through DXVA internally if possible (requires D3D9 interop) */ + cudaVideoCreate_PreferCUVID = 0x04 /**< Use dedicated video engines directly */ +} cudaVideoCreateFlags; + +/*! + * \struct CUVIDDECODECREATEINFO + * Struct used in create decoder + */ +typedef struct _CUVIDDECODECREATEINFO +{ + unsigned long ulWidth; /**< Coded Sequence Width */ + unsigned long ulHeight; /**< Coded Sequence Height */ + unsigned long ulNumDecodeSurfaces; /**< Maximum number of internal decode surfaces */ + cudaVideoCodec CodecType; /**< cudaVideoCodec_XXX */ + cudaVideoChromaFormat ChromaFormat; /**< cudaVideoChromaFormat_XXX (only 4:2:0 is currently supported) */ + unsigned long ulCreationFlags; /**< Decoder creation flags (cudaVideoCreateFlags_XXX) */ + unsigned long bitDepthMinus8; + unsigned long Reserved1[4]; /**< Reserved for future use - set to zero */ + /** + * area of the frame that should be displayed + */ + struct { + short left; + short top; + short right; + short bottom; + } display_area; + + cudaVideoSurfaceFormat OutputFormat; /**< cudaVideoSurfaceFormat_XXX */ + cudaVideoDeinterlaceMode DeinterlaceMode; /**< cudaVideoDeinterlaceMode_XXX */ + unsigned long ulTargetWidth; /**< Post-processed Output Width (Should be aligned to 2) */ + unsigned long ulTargetHeight; /**< Post-processed Output Height (Should be aligbed to 2) */ + unsigned long ulNumOutputSurfaces; /**< Maximum number of output surfaces simultaneously mapped */ + CUvideoctxlock vidLock; /**< If non-NULL, context lock used for synchronizing ownership of the cuda context */ + /** + * target rectangle in the output frame (for aspect ratio conversion) + * if a null rectangle is specified, {0,0,ulTargetWidth,ulTargetHeight} will be used + */ + struct { + short left; + short top; + short right; + short bottom; + } target_rect; + unsigned long Reserved2[5]; /**< Reserved for future use - set to zero */ +} CUVIDDECODECREATEINFO; + +/*! + * \struct CUVIDH264DPBENTRY + * H.264 DPB Entry + */ +typedef struct _CUVIDH264DPBENTRY +{ + int PicIdx; /**< picture index of reference frame */ + int FrameIdx; /**< frame_num(short-term) or LongTermFrameIdx(long-term) */ + int is_long_term; /**< 0=short term reference, 1=long term reference */ + int not_existing; /**< non-existing reference frame (corresponding PicIdx should be set to -1) */ + int used_for_reference; /**< 0=unused, 1=top_field, 2=bottom_field, 3=both_fields */ + int FieldOrderCnt[2]; /**< field order count of top and bottom fields */ +} CUVIDH264DPBENTRY; + +/*! + * \struct CUVIDH264MVCEXT + * H.264 MVC Picture Parameters Ext + */ +typedef struct _CUVIDH264MVCEXT +{ + int num_views_minus1; + int view_id; + unsigned char inter_view_flag; + unsigned char num_inter_view_refs_l0; + unsigned char num_inter_view_refs_l1; + unsigned char MVCReserved8Bits; + int InterViewRefsL0[16]; + int InterViewRefsL1[16]; +} CUVIDH264MVCEXT; + +/*! + * \struct CUVIDH264SVCEXT + * H.264 SVC Picture Parameters Ext + */ +typedef struct _CUVIDH264SVCEXT +{ + unsigned char profile_idc; + unsigned char level_idc; + unsigned char DQId; + unsigned char DQIdMax; + unsigned char disable_inter_layer_deblocking_filter_idc; + unsigned char ref_layer_chroma_phase_y_plus1; + signed char inter_layer_slice_alpha_c0_offset_div2; + signed char inter_layer_slice_beta_offset_div2; + + unsigned short DPBEntryValidFlag; + unsigned char inter_layer_deblocking_filter_control_present_flag; + unsigned char extended_spatial_scalability_idc; + unsigned char adaptive_tcoeff_level_prediction_flag; + unsigned char slice_header_restriction_flag; + unsigned char chroma_phase_x_plus1_flag; + unsigned char chroma_phase_y_plus1; + + unsigned char tcoeff_level_prediction_flag; + unsigned char constrained_intra_resampling_flag; + unsigned char ref_layer_chroma_phase_x_plus1_flag; + unsigned char store_ref_base_pic_flag; + unsigned char Reserved8BitsA; + unsigned char Reserved8BitsB; + // For the 4 scaled_ref_layer_XX fields below, + // if (extended_spatial_scalability_idc == 1), SPS field, G.7.3.2.1.4, add prefix "seq_" + // if (extended_spatial_scalability_idc == 2), SLH field, G.7.3.3.4, + short scaled_ref_layer_left_offset; + short scaled_ref_layer_top_offset; + short scaled_ref_layer_right_offset; + short scaled_ref_layer_bottom_offset; + unsigned short Reserved16Bits; + struct _CUVIDPICPARAMS *pNextLayer; /**< Points to the picparams for the next layer to be decoded. Linked list ends at the target layer. */ + int bRefBaseLayer; /**< whether to store ref base pic */ +} CUVIDH264SVCEXT; + +/*! + * \struct CUVIDH264PICPARAMS + * H.264 Picture Parameters + */ +typedef struct _CUVIDH264PICPARAMS +{ + // SPS + int log2_max_frame_num_minus4; + int pic_order_cnt_type; + int log2_max_pic_order_cnt_lsb_minus4; + int delta_pic_order_always_zero_flag; + int frame_mbs_only_flag; + int direct_8x8_inference_flag; + int num_ref_frames; // NOTE: shall meet level 4.1 restrictions + unsigned char residual_colour_transform_flag; + unsigned char bit_depth_luma_minus8; // Must be 0 (only 8-bit supported) + unsigned char bit_depth_chroma_minus8; // Must be 0 (only 8-bit supported) + unsigned char qpprime_y_zero_transform_bypass_flag; + // PPS + int entropy_coding_mode_flag; + int pic_order_present_flag; + int num_ref_idx_l0_active_minus1; + int num_ref_idx_l1_active_minus1; + int weighted_pred_flag; + int weighted_bipred_idc; + int pic_init_qp_minus26; + int deblocking_filter_control_present_flag; + int redundant_pic_cnt_present_flag; + int transform_8x8_mode_flag; + int MbaffFrameFlag; + int constrained_intra_pred_flag; + int chroma_qp_index_offset; + int second_chroma_qp_index_offset; + int ref_pic_flag; + int frame_num; + int CurrFieldOrderCnt[2]; + // DPB + CUVIDH264DPBENTRY dpb[16]; // List of reference frames within the DPB + // Quantization Matrices (raster-order) + unsigned char WeightScale4x4[6][16]; + unsigned char WeightScale8x8[2][64]; + // FMO/ASO + unsigned char fmo_aso_enable; + unsigned char num_slice_groups_minus1; + unsigned char slice_group_map_type; + signed char pic_init_qs_minus26; + unsigned int slice_group_change_rate_minus1; + union + { + unsigned long long slice_group_map_addr; + const unsigned char *pMb2SliceGroupMap; + } fmo; + unsigned int Reserved[12]; + // SVC/MVC + union + { + CUVIDH264MVCEXT mvcext; + CUVIDH264SVCEXT svcext; + }; +} CUVIDH264PICPARAMS; + + +/*! + * \struct CUVIDMPEG2PICPARAMS + * MPEG-2 Picture Parameters + */ +typedef struct _CUVIDMPEG2PICPARAMS +{ + int ForwardRefIdx; // Picture index of forward reference (P/B-frames) + int BackwardRefIdx; // Picture index of backward reference (B-frames) + int picture_coding_type; + int full_pel_forward_vector; + int full_pel_backward_vector; + int f_code[2][2]; + int intra_dc_precision; + int frame_pred_frame_dct; + int concealment_motion_vectors; + int q_scale_type; + int intra_vlc_format; + int alternate_scan; + int top_field_first; + // Quantization matrices (raster order) + unsigned char QuantMatrixIntra[64]; + unsigned char QuantMatrixInter[64]; +} CUVIDMPEG2PICPARAMS; + +//////////////////////////////////////////////////////////////////////////////////////////////// +// +// MPEG-4 Picture Parameters +// + +// MPEG-4 has VOP types instead of Picture types +#define I_VOP 0 +#define P_VOP 1 +#define B_VOP 2 +#define S_VOP 3 + +/*! + * \struct CUVIDMPEG4PICPARAMS + * MPEG-4 Picture Parameters + */ +typedef struct _CUVIDMPEG4PICPARAMS +{ + int ForwardRefIdx; // Picture index of forward reference (P/B-frames) + int BackwardRefIdx; // Picture index of backward reference (B-frames) + // VOL + int video_object_layer_width; + int video_object_layer_height; + int vop_time_increment_bitcount; + int top_field_first; + int resync_marker_disable; + int quant_type; + int quarter_sample; + int short_video_header; + int divx_flags; + // VOP + int vop_coding_type; + int vop_coded; + int vop_rounding_type; + int alternate_vertical_scan_flag; + int interlaced; + int vop_fcode_forward; + int vop_fcode_backward; + int trd[2]; + int trb[2]; + // Quantization matrices (raster order) + unsigned char QuantMatrixIntra[64]; + unsigned char QuantMatrixInter[64]; + int gmc_enabled; +} CUVIDMPEG4PICPARAMS; + +/*! + * \struct CUVIDVC1PICPARAMS + * VC1 Picture Parameters + */ +typedef struct _CUVIDVC1PICPARAMS +{ + int ForwardRefIdx; /**< Picture index of forward reference (P/B-frames) */ + int BackwardRefIdx; /**< Picture index of backward reference (B-frames) */ + int FrameWidth; /**< Actual frame width */ + int FrameHeight; /**< Actual frame height */ + // PICTURE + int intra_pic_flag; /**< Set to 1 for I,BI frames */ + int ref_pic_flag; /**< Set to 1 for I,P frames */ + int progressive_fcm; /**< Progressive frame */ + // SEQUENCE + int profile; + int postprocflag; + int pulldown; + int interlace; + int tfcntrflag; + int finterpflag; + int psf; + int multires; + int syncmarker; + int rangered; + int maxbframes; + // ENTRYPOINT + int panscan_flag; + int refdist_flag; + int extended_mv; + int dquant; + int vstransform; + int loopfilter; + int fastuvmc; + int overlap; + int quantizer; + int extended_dmv; + int range_mapy_flag; + int range_mapy; + int range_mapuv_flag; + int range_mapuv; + int rangeredfrm; // range reduction state +} CUVIDVC1PICPARAMS; + +/*! + * \struct CUVIDJPEGPICPARAMS + * JPEG Picture Parameters + */ +typedef struct _CUVIDJPEGPICPARAMS +{ + int Reserved; +} CUVIDJPEGPICPARAMS; + + + /*! + * \struct CUVIDHEVCPICPARAMS + * HEVC Picture Parameters + */ +typedef struct _CUVIDHEVCPICPARAMS +{ + // sps + int pic_width_in_luma_samples; + int pic_height_in_luma_samples; + unsigned char log2_min_luma_coding_block_size_minus3; + unsigned char log2_diff_max_min_luma_coding_block_size; + unsigned char log2_min_transform_block_size_minus2; + unsigned char log2_diff_max_min_transform_block_size; + unsigned char pcm_enabled_flag; + unsigned char log2_min_pcm_luma_coding_block_size_minus3; + unsigned char log2_diff_max_min_pcm_luma_coding_block_size; + unsigned char pcm_sample_bit_depth_luma_minus1; + + unsigned char pcm_sample_bit_depth_chroma_minus1; + unsigned char pcm_loop_filter_disabled_flag; + unsigned char strong_intra_smoothing_enabled_flag; + unsigned char max_transform_hierarchy_depth_intra; + unsigned char max_transform_hierarchy_depth_inter; + unsigned char amp_enabled_flag; + unsigned char separate_colour_plane_flag; + unsigned char log2_max_pic_order_cnt_lsb_minus4; + + unsigned char num_short_term_ref_pic_sets; + unsigned char long_term_ref_pics_present_flag; + unsigned char num_long_term_ref_pics_sps; + unsigned char sps_temporal_mvp_enabled_flag; + unsigned char sample_adaptive_offset_enabled_flag; + unsigned char scaling_list_enable_flag; + unsigned char IrapPicFlag; + unsigned char IdrPicFlag; + + unsigned char bit_depth_luma_minus8; + unsigned char bit_depth_chroma_minus8; + unsigned char reserved1[14]; + + // pps + unsigned char dependent_slice_segments_enabled_flag; + unsigned char slice_segment_header_extension_present_flag; + unsigned char sign_data_hiding_enabled_flag; + unsigned char cu_qp_delta_enabled_flag; + unsigned char diff_cu_qp_delta_depth; + signed char init_qp_minus26; + signed char pps_cb_qp_offset; + signed char pps_cr_qp_offset; + + unsigned char constrained_intra_pred_flag; + unsigned char weighted_pred_flag; + unsigned char weighted_bipred_flag; + unsigned char transform_skip_enabled_flag; + unsigned char transquant_bypass_enabled_flag; + unsigned char entropy_coding_sync_enabled_flag; + unsigned char log2_parallel_merge_level_minus2; + unsigned char num_extra_slice_header_bits; + + unsigned char loop_filter_across_tiles_enabled_flag; + unsigned char loop_filter_across_slices_enabled_flag; + unsigned char output_flag_present_flag; + unsigned char num_ref_idx_l0_default_active_minus1; + unsigned char num_ref_idx_l1_default_active_minus1; + unsigned char lists_modification_present_flag; + unsigned char cabac_init_present_flag; + unsigned char pps_slice_chroma_qp_offsets_present_flag; + + unsigned char deblocking_filter_override_enabled_flag; + unsigned char pps_deblocking_filter_disabled_flag; + signed char pps_beta_offset_div2; + signed char pps_tc_offset_div2; + unsigned char tiles_enabled_flag; + unsigned char uniform_spacing_flag; + unsigned char num_tile_columns_minus1; + unsigned char num_tile_rows_minus1; + + unsigned short column_width_minus1[21]; + unsigned short row_height_minus1[21]; + unsigned int reserved3[15]; + + // RefPicSets + int NumBitsForShortTermRPSInSlice; + int NumDeltaPocsOfRefRpsIdx; + int NumPocTotalCurr; + int NumPocStCurrBefore; + int NumPocStCurrAfter; + int NumPocLtCurr; + int CurrPicOrderCntVal; + int RefPicIdx[16]; // [refpic] Indices of valid reference pictures (-1 if unused for reference) + int PicOrderCntVal[16]; // [refpic] + unsigned char IsLongTerm[16]; // [refpic] 0=not a long-term reference, 1=long-term reference + unsigned char RefPicSetStCurrBefore[8]; // [0..NumPocStCurrBefore-1] -> refpic (0..15) + unsigned char RefPicSetStCurrAfter[8]; // [0..NumPocStCurrAfter-1] -> refpic (0..15) + unsigned char RefPicSetLtCurr[8]; // [0..NumPocLtCurr-1] -> refpic (0..15) + unsigned char RefPicSetInterLayer0[8]; + unsigned char RefPicSetInterLayer1[8]; + unsigned int reserved4[12]; + + // scaling lists (diag order) + unsigned char ScalingList4x4[6][16]; // [matrixId][i] + unsigned char ScalingList8x8[6][64]; // [matrixId][i] + unsigned char ScalingList16x16[6][64]; // [matrixId][i] + unsigned char ScalingList32x32[2][64]; // [matrixId][i] + unsigned char ScalingListDCCoeff16x16[6]; // [matrixId] + unsigned char ScalingListDCCoeff32x32[2]; // [matrixId] +} CUVIDHEVCPICPARAMS; + + +/*! + * \struct CUVIDVP8PICPARAMS + * VP8 Picture Parameters + */ +typedef struct _CUVIDVP8PICPARAMS +{ + int width; + int height; + unsigned int first_partition_size; + //Frame Indexes + unsigned char LastRefIdx; + unsigned char GoldenRefIdx; + unsigned char AltRefIdx; + union { + struct { + unsigned char frame_type : 1; /**< 0 = KEYFRAME, 1 = INTERFRAME */ + unsigned char version : 3; + unsigned char show_frame : 1; + unsigned char update_mb_segmentation_data : 1; /**< Must be 0 if segmentation is not enabled */ + unsigned char Reserved2Bits : 2; + }; + unsigned char wFrameTagFlags; + }; + unsigned char Reserved1[4]; + unsigned int Reserved2[3]; +} CUVIDVP8PICPARAMS; + +/*! + * \struct CUVIDVP9PICPARAMS + * VP9 Picture Parameters + */ +typedef struct _CUVIDVP9PICPARAMS +{ + unsigned int width; + unsigned int height; + + //Frame Indices + unsigned char LastRefIdx; + unsigned char GoldenRefIdx; + unsigned char AltRefIdx; + unsigned char colorSpace; + + unsigned short profile : 3; + unsigned short frameContextIdx : 2; + unsigned short frameType : 1; + unsigned short showFrame : 1; + unsigned short errorResilient : 1; + unsigned short frameParallelDecoding : 1; + unsigned short subSamplingX : 1; + unsigned short subSamplingY : 1; + unsigned short intraOnly : 1; + unsigned short allow_high_precision_mv : 1; + unsigned short refreshEntropyProbs : 1; + unsigned short reserved2Bits : 2; + + unsigned short reserved16Bits; + + unsigned char refFrameSignBias[4]; + + unsigned char bitDepthMinus8Luma; + unsigned char bitDepthMinus8Chroma; + unsigned char loopFilterLevel; + unsigned char loopFilterSharpness; + + unsigned char modeRefLfEnabled; + unsigned char log2_tile_columns; + unsigned char log2_tile_rows; + + unsigned char segmentEnabled : 1; + unsigned char segmentMapUpdate : 1; + unsigned char segmentMapTemporalUpdate : 1; + unsigned char segmentFeatureMode : 1; + unsigned char reserved4Bits : 4; + + + unsigned char segmentFeatureEnable[8][4]; + short segmentFeatureData[8][4]; + unsigned char mb_segment_tree_probs[7]; + unsigned char segment_pred_probs[3]; + unsigned char reservedSegment16Bits[2]; + + int qpYAc; + int qpYDc; + int qpChDc; + int qpChAc; + + unsigned int activeRefIdx[3]; + unsigned int resetFrameContext; + unsigned int mcomp_filter_type; + unsigned int mbRefLfDelta[4]; + unsigned int mbModeLfDelta[2]; + unsigned int frameTagSize; + unsigned int offsetToDctParts; + unsigned int reserved128Bits[4]; + +} CUVIDVP9PICPARAMS; + + +/*! + * \struct CUVIDPICPARAMS + * Picture Parameters for Decoding + */ +typedef struct _CUVIDPICPARAMS +{ + int PicWidthInMbs; /**< Coded Frame Size */ + int FrameHeightInMbs; /**< Coded Frame Height */ + int CurrPicIdx; /**< Output index of the current picture */ + int field_pic_flag; /**< 0=frame picture, 1=field picture */ + int bottom_field_flag; /**< 0=top field, 1=bottom field (ignored if field_pic_flag=0) */ + int second_field; /**< Second field of a complementary field pair */ + // Bitstream data + unsigned int nBitstreamDataLen; /**< Number of bytes in bitstream data buffer */ + const unsigned char *pBitstreamData; /**< Ptr to bitstream data for this picture (slice-layer) */ + unsigned int nNumSlices; /**< Number of slices in this picture */ + const unsigned int *pSliceDataOffsets; /**< nNumSlices entries, contains offset of each slice within the bitstream data buffer */ + int ref_pic_flag; /**< This picture is a reference picture */ + int intra_pic_flag; /**< This picture is entirely intra coded */ + unsigned int Reserved[30]; /**< Reserved for future use */ + // Codec-specific data + union { + CUVIDMPEG2PICPARAMS mpeg2; /**< Also used for MPEG-1 */ + CUVIDH264PICPARAMS h264; + CUVIDVC1PICPARAMS vc1; + CUVIDMPEG4PICPARAMS mpeg4; + CUVIDJPEGPICPARAMS jpeg; + CUVIDHEVCPICPARAMS hevc; + CUVIDVP8PICPARAMS vp8; + CUVIDVP9PICPARAMS vp9; + unsigned int CodecReserved[1024]; + } CodecSpecific; +} CUVIDPICPARAMS; + + +/*! + * \struct CUVIDPROCPARAMS + * Picture Parameters for Postprocessing + */ +typedef struct _CUVIDPROCPARAMS +{ + int progressive_frame; /**< Input is progressive (deinterlace_mode will be ignored) */ + int second_field; /**< Output the second field (ignored if deinterlace mode is Weave) */ + int top_field_first; /**< Input frame is top field first (1st field is top, 2nd field is bottom) */ + int unpaired_field; /**< Input only contains one field (2nd field is invalid) */ + // The fields below are used for raw YUV input + unsigned int reserved_flags; /**< Reserved for future use (set to zero) */ + unsigned int reserved_zero; /**< Reserved (set to zero) */ + unsigned long long raw_input_dptr; /**< Input CUdeviceptr for raw YUV extensions */ + unsigned int raw_input_pitch; /**< pitch in bytes of raw YUV input (should be aligned appropriately) */ + unsigned int raw_input_format; /**< Reserved for future use (set to zero) */ + unsigned long long raw_output_dptr; /**< Reserved for future use (set to zero) */ + unsigned int raw_output_pitch; /**< Reserved for future use (set to zero) */ + unsigned int Reserved[48]; + void *Reserved3[3]; +} CUVIDPROCPARAMS; + + +/** + * + * In order to minimize decode latencies, there should be always at least 2 pictures in the decode + * queue at any time, in order to make sure that all decode engines are always busy. + * + * Overall data flow: + * - cuvidCreateDecoder(...) + * For each picture: + * - cuvidDecodePicture(N) + * - cuvidMapVideoFrame(N-4) + * - do some processing in cuda + * - cuvidUnmapVideoFrame(N-4) + * - cuvidDecodePicture(N+1) + * - cuvidMapVideoFrame(N-3) + * ... + * - cuvidDestroyDecoder(...) + * + * NOTE: + * - When the cuda context is created from a D3D device, the D3D device must also be created + * with the D3DCREATE_MULTITHREADED flag. + * - There is a limit to how many pictures can be mapped simultaneously (ulNumOutputSurfaces) + * - cuVidDecodePicture may block the calling thread if there are too many pictures pending + * in the decode queue + */ + +/** + * \fn CUresult CUDAAPI cuvidCreateDecoder(CUvideodecoder *phDecoder, CUVIDDECODECREATEINFO *pdci) + * Create the decoder object + */ +typedef CUresult CUDAAPI tcuvidCreateDecoder(CUvideodecoder *phDecoder, CUVIDDECODECREATEINFO *pdci); + +/** + * \fn CUresult CUDAAPI cuvidDestroyDecoder(CUvideodecoder hDecoder) + * Destroy the decoder object + */ +typedef CUresult CUDAAPI tcuvidDestroyDecoder(CUvideodecoder hDecoder); + +/** + * \fn CUresult CUDAAPI cuvidDecodePicture(CUvideodecoder hDecoder, CUVIDPICPARAMS *pPicParams) + * Decode a single picture (field or frame) + */ +typedef CUresult CUDAAPI tcuvidDecodePicture(CUvideodecoder hDecoder, CUVIDPICPARAMS *pPicParams); + + +#if !defined(__CUVID_DEVPTR64) || defined(__CUVID_INTERNAL) +/** + * \fn CUresult CUDAAPI cuvidMapVideoFrame(CUvideodecoder hDecoder, int nPicIdx, unsigned int *pDevPtr, unsigned int *pPitch, CUVIDPROCPARAMS *pVPP); + * Post-process and map a video frame for use in cuda + */ +typedef CUresult CUDAAPI tcuvidMapVideoFrame(CUvideodecoder hDecoder, int nPicIdx, + unsigned int *pDevPtr, unsigned int *pPitch, + CUVIDPROCPARAMS *pVPP); + +/** + * \fn CUresult CUDAAPI cuvidUnmapVideoFrame(CUvideodecoder hDecoder, unsigned int DevPtr) + * Unmap a previously mapped video frame + */ +typedef CUresult CUDAAPI tcuvidUnmapVideoFrame(CUvideodecoder hDecoder, unsigned int DevPtr); +#endif + +#if defined(WIN64) || defined(_WIN64) || defined(__x86_64) || defined(AMD64) || defined(_M_AMD64) +/** + * \fn CUresult CUDAAPI cuvidMapVideoFrame64(CUvideodecoder hDecoder, int nPicIdx, unsigned long long *pDevPtr, unsigned int *pPitch, CUVIDPROCPARAMS *pVPP); + * map a video frame + */ +typedef CUresult CUDAAPI tcuvidMapVideoFrame64(CUvideodecoder hDecoder, int nPicIdx, unsigned long long *pDevPtr, + unsigned int *pPitch, CUVIDPROCPARAMS *pVPP); + +/** + * \fn CUresult CUDAAPI cuvidUnmapVideoFrame64(CUvideodecoder hDecoder, unsigned long long DevPtr); + * Unmap a previously mapped video frame + */ +typedef CUresult CUDAAPI tcuvidUnmapVideoFrame64(CUvideodecoder hDecoder, unsigned long long DevPtr); + +#if defined(__CUVID_DEVPTR64) && !defined(__CUVID_INTERNAL) +#define tcuvidMapVideoFrame tcuvidMapVideoFrame64 +#define tcuvidUnmapVideoFrame tcuvidUnmapVideoFrame64 +#endif +#endif + + + +/** + * + * Context-locking: to facilitate multi-threaded implementations, the following 4 functions + * provide a simple mutex-style host synchronization. If a non-NULL context is specified + * in CUVIDDECODECREATEINFO, the codec library will acquire the mutex associated with the given + * context before making any cuda calls. + * A multi-threaded application could create a lock associated with a context handle so that + * multiple threads can safely share the same cuda context: + * - use cuCtxPopCurrent immediately after context creation in order to create a 'floating' context + * that can be passed to cuvidCtxLockCreate. + * - When using a floating context, all cuda calls should only be made within a cuvidCtxLock/cuvidCtxUnlock section. + * + * NOTE: This is a safer alternative to cuCtxPushCurrent and cuCtxPopCurrent, and is not related to video + * decoder in any way (implemented as a critical section associated with cuCtx{Push|Pop}Current calls). +*/ + +/** + * \fn CUresult CUDAAPI cuvidCtxLockCreate(CUvideoctxlock *pLock, CUcontext ctx) + */ +typedef CUresult CUDAAPI tcuvidCtxLockCreate(CUvideoctxlock *pLock, CUcontext ctx); + +/** + * \fn CUresult CUDAAPI cuvidCtxLockDestroy(CUvideoctxlock lck) + */ +typedef CUresult CUDAAPI tcuvidCtxLockDestroy(CUvideoctxlock lck); + +/** + * \fn CUresult CUDAAPI cuvidCtxLock(CUvideoctxlock lck, unsigned int reserved_flags) + */ +typedef CUresult CUDAAPI tcuvidCtxLock(CUvideoctxlock lck, unsigned int reserved_flags); + +/** + * \fn CUresult CUDAAPI cuvidCtxUnlock(CUvideoctxlock lck, unsigned int reserved_flags) + */ +typedef CUresult CUDAAPI tcuvidCtxUnlock(CUvideoctxlock lck, unsigned int reserved_flags); + +/** @} */ /* End VIDEO_DECODER */ +//////////////////////////////////////////////////////////////////////////////////////////////// + + +extern tcuvidCreateDecoder *cuvidCreateDecoder; +extern tcuvidDestroyDecoder *cuvidDestroyDecoder; +extern tcuvidDecodePicture *cuvidDecodePicture; +extern tcuvidMapVideoFrame *cuvidMapVideoFrame; +extern tcuvidUnmapVideoFrame *cuvidUnmapVideoFrame; + +#if defined(__x86_64) || defined(AMD64) || defined(_M_AMD64) +extern tcuvidMapVideoFrame64 *cuvidMapVideoFrame64; +extern tcuvidUnmapVideoFrame64 *cuvidUnmapVideoFrame64; +#endif + +//extern tcuvidGetVideoFrameSurface *cuvidGetVideoFrameSurface; + +extern tcuvidCtxLockCreate *cuvidCtxLockCreate; +extern tcuvidCtxLockDestroy *cuvidCtxLockDestroy; +extern tcuvidCtxLock *cuvidCtxLock; +extern tcuvidCtxUnlock *cuvidCtxUnlock; + +//////////////////////////////////////////////////////////////////////////////////////////////// +// +// High-level helper APIs for video sources +// + +typedef void *CUvideosource; +typedef void *CUvideoparser; +typedef long long CUvideotimestamp; + +/** + * \addtogroup VIDEO_PARSER Video Parser + * @{ + */ + +/*! + * \enum cudaVideoState + * Video Source State + */ +typedef enum { + cudaVideoState_Error = -1, /**< Error state (invalid source) */ + cudaVideoState_Stopped = 0, /**< Source is stopped (or reached end-of-stream) */ + cudaVideoState_Started = 1 /**< Source is running and delivering data */ +} cudaVideoState; + +/*! + * \enum cudaAudioCodec + * Audio compression + */ +typedef enum { + cudaAudioCodec_MPEG1=0, /**< MPEG-1 Audio */ + cudaAudioCodec_MPEG2, /**< MPEG-2 Audio */ + cudaAudioCodec_MP3, /**< MPEG-1 Layer III Audio */ + cudaAudioCodec_AC3, /**< Dolby Digital (AC3) Audio */ + cudaAudioCodec_LPCM /**< PCM Audio */ +} cudaAudioCodec; + +/*! + * \struct CUVIDEOFORMAT + * Video format + */ +typedef struct +{ + cudaVideoCodec codec; /**< Compression format */ + /** + * frame rate = numerator / denominator (for example: 30000/1001) + */ + struct { + unsigned int numerator; /**< frame rate numerator (0 = unspecified or variable frame rate) */ + unsigned int denominator; /**< frame rate denominator (0 = unspecified or variable frame rate) */ + } frame_rate; + unsigned char progressive_sequence; /**< 0=interlaced, 1=progressive */ + unsigned char bit_depth_luma_minus8; /**< high bit depth Luma */ + unsigned char bit_depth_chroma_minus8; /**< high bit depth Chroma */ + unsigned char reserved1; /**< Reserved for future use */ + unsigned int coded_width; /**< coded frame width */ + unsigned int coded_height; /**< coded frame height */ + /** + * area of the frame that should be displayed + * typical example: + * coded_width = 1920, coded_height = 1088 + * display_area = { 0,0,1920,1080 } + */ + struct { + int left; /**< left position of display rect */ + int top; /**< top position of display rect */ + int right; /**< right position of display rect */ + int bottom; /**< bottom position of display rect */ + } display_area; + cudaVideoChromaFormat chroma_format; /**< Chroma format */ + unsigned int bitrate; /**< video bitrate (bps, 0=unknown) */ + /** + * Display Aspect Ratio = x:y (4:3, 16:9, etc) + */ + struct { + int x; + int y; + } display_aspect_ratio; + /** + * Video Signal Description + */ + struct { + unsigned char video_format : 3; + unsigned char video_full_range_flag : 1; + unsigned char reserved_zero_bits : 4; + unsigned char color_primaries; + unsigned char transfer_characteristics; + unsigned char matrix_coefficients; + } video_signal_description; + unsigned int seqhdr_data_length; /**< Additional bytes following (CUVIDEOFORMATEX) */ +} CUVIDEOFORMAT; + +/*! + * \struct CUVIDEOFORMATEX + * Video format including raw sequence header information + */ +typedef struct +{ + CUVIDEOFORMAT format; + unsigned char raw_seqhdr_data[1024]; +} CUVIDEOFORMATEX; + +/*! + * \struct CUAUDIOFORMAT + * Audio Formats + */ +typedef struct +{ + cudaAudioCodec codec; /**< Compression format */ + unsigned int channels; /**< number of audio channels */ + unsigned int samplespersec; /**< sampling frequency */ + unsigned int bitrate; /**< For uncompressed, can also be used to determine bits per sample */ + unsigned int reserved1; /**< Reserved for future use */ + unsigned int reserved2; /**< Reserved for future use */ +} CUAUDIOFORMAT; + + +/*! + * \enum CUvideopacketflags + * Data packet flags + */ +typedef enum { + CUVID_PKT_ENDOFSTREAM = 0x01, /**< Set when this is the last packet for this stream */ + CUVID_PKT_TIMESTAMP = 0x02, /**< Timestamp is valid */ + CUVID_PKT_DISCONTINUITY = 0x04 /**< Set when a discontinuity has to be signalled */ +} CUvideopacketflags; + +/*! + * \struct CUVIDSOURCEDATAPACKET + * Data Packet + */ +typedef struct _CUVIDSOURCEDATAPACKET +{ + unsigned long flags; /**< Combination of CUVID_PKT_XXX flags */ + unsigned long payload_size; /**< number of bytes in the payload (may be zero if EOS flag is set) */ + const unsigned char *payload; /**< Pointer to packet payload data (may be NULL if EOS flag is set) */ + CUvideotimestamp timestamp; /**< Presentation timestamp (10MHz clock), only valid if CUVID_PKT_TIMESTAMP flag is set */ +} CUVIDSOURCEDATAPACKET; + +// Callback for packet delivery +typedef int (CUDAAPI *PFNVIDSOURCECALLBACK)(void *, CUVIDSOURCEDATAPACKET *); + +/*! + * \struct CUVIDSOURCEPARAMS + * Source Params + */ +typedef struct _CUVIDSOURCEPARAMS +{ + unsigned int ulClockRate; /**< Timestamp units in Hz (0=default=10000000Hz) */ + unsigned int uReserved1[7]; /**< Reserved for future use - set to zero */ + void *pUserData; /**< Parameter passed in to the data handlers */ + PFNVIDSOURCECALLBACK pfnVideoDataHandler; /**< Called to deliver audio packets */ + PFNVIDSOURCECALLBACK pfnAudioDataHandler; /**< Called to deliver video packets */ + void *pvReserved2[8]; /**< Reserved for future use - set to NULL */ +} CUVIDSOURCEPARAMS; + +/*! + * \enum CUvideosourceformat_flags + * CUvideosourceformat_flags + */ +typedef enum { + CUVID_FMT_EXTFORMATINFO = 0x100 /**< Return extended format structure (CUVIDEOFORMATEX) */ +} CUvideosourceformat_flags; + +#if !defined(__APPLE__) +/** + * \fn CUresult CUDAAPI cuvidCreateVideoSource(CUvideosource *pObj, const char *pszFileName, CUVIDSOURCEPARAMS *pParams) + * Create Video Source + */ +typedef CUresult CUDAAPI tcuvidCreateVideoSource(CUvideosource *pObj, const char *pszFileName, CUVIDSOURCEPARAMS *pParams); + +/** + * \fn CUresult CUDAAPI cuvidCreateVideoSourceW(CUvideosource *pObj, const wchar_t *pwszFileName, CUVIDSOURCEPARAMS *pParams) + * Create Video Source + */ +typedef CUresult CUDAAPI tcuvidCreateVideoSourceW(CUvideosource *pObj, const wchar_t *pwszFileName, CUVIDSOURCEPARAMS *pParams); + +/** + * \fn CUresult CUDAAPI cuvidDestroyVideoSource(CUvideosource obj) + * Destroy Video Source + */ +typedef CUresult CUDAAPI tcuvidDestroyVideoSource(CUvideosource obj); + +/** + * \fn CUresult CUDAAPI cuvidSetVideoSourceState(CUvideosource obj, cudaVideoState state) + * Set Video Source state + */ +typedef CUresult CUDAAPI tcuvidSetVideoSourceState(CUvideosource obj, cudaVideoState state); + +/** + * \fn cudaVideoState CUDAAPI cuvidGetVideoSourceState(CUvideosource obj) + * Get Video Source state + */ +typedef cudaVideoState CUDAAPI tcuvidGetVideoSourceState(CUvideosource obj); + +/** + * \fn CUresult CUDAAPI cuvidGetSourceVideoFormat(CUvideosource obj, CUVIDEOFORMAT *pvidfmt, unsigned int flags) + * Get Video Source Format + */ +typedef CUresult CUDAAPI tcuvidGetSourceVideoFormat(CUvideosource obj, CUVIDEOFORMAT *pvidfmt, unsigned int flags); + +/** + * \fn CUresult CUDAAPI cuvidGetSourceAudioFormat(CUvideosource obj, CUAUDIOFORMAT *paudfmt, unsigned int flags) + * Set Video Source state + */ +typedef CUresult CUDAAPI tcuvidGetSourceAudioFormat(CUvideosource obj, CUAUDIOFORMAT *paudfmt, unsigned int flags); + +#endif + +/** + * \struct CUVIDPARSERDISPINFO + */ +typedef struct _CUVIDPARSERDISPINFO +{ + int picture_index; /**< */ + int progressive_frame; /**< */ + int top_field_first; /**< */ + int repeat_first_field; /**< Number of additional fields (1=ivtc, 2=frame doubling, 4=frame tripling, -1=unpaired field) */ + CUvideotimestamp timestamp; /**< */ +} CUVIDPARSERDISPINFO; + +// +// Parser callbacks +// The parser will call these synchronously from within cuvidParseVideoData(), whenever a picture is ready to +// be decoded and/or displayed. +// +typedef int (CUDAAPI *PFNVIDSEQUENCECALLBACK)(void *, CUVIDEOFORMAT *); +typedef int (CUDAAPI *PFNVIDDECODECALLBACK)(void *, CUVIDPICPARAMS *); +typedef int (CUDAAPI *PFNVIDDISPLAYCALLBACK)(void *, CUVIDPARSERDISPINFO *); + +/** + * \struct CUVIDPARSERPARAMS + */ +typedef struct _CUVIDPARSERPARAMS +{ + cudaVideoCodec CodecType; /**< cudaVideoCodec_XXX */ + unsigned int ulMaxNumDecodeSurfaces; /**< Max # of decode surfaces (parser will cycle through these) */ + unsigned int ulClockRate; /**< Timestamp units in Hz (0=default=10000000Hz) */ + unsigned int ulErrorThreshold; /**< % Error threshold (0-100) for calling pfnDecodePicture (100=always call pfnDecodePicture even if picture bitstream is fully corrupted) */ + unsigned int ulMaxDisplayDelay; /**< Max display queue delay (improves pipelining of decode with display) - 0=no delay (recommended values: 2..4) */ + unsigned int uReserved1[5]; /**< Reserved for future use - set to 0 */ + void *pUserData; /**< User data for callbacks */ + PFNVIDSEQUENCECALLBACK pfnSequenceCallback; /**< Called before decoding frames and/or whenever there is a format change */ + PFNVIDDECODECALLBACK pfnDecodePicture; /**< Called when a picture is ready to be decoded (decode order) */ + PFNVIDDISPLAYCALLBACK pfnDisplayPicture; /**< Called whenever a picture is ready to be displayed (display order) */ + void *pvReserved2[7]; /**< Reserved for future use - set to NULL */ + CUVIDEOFORMATEX *pExtVideoInfo; /**< [Optional] sequence header data from system layer */ +} CUVIDPARSERPARAMS; + +/** + * \fn CUresult CUDAAPI cuvidCreateVideoParser(CUvideoparser *pObj, CUVIDPARSERPARAMS *pParams) + */ +typedef CUresult CUDAAPI tcuvidCreateVideoParser(CUvideoparser *pObj, CUVIDPARSERPARAMS *pParams); + +/** + * \fn CUresult CUDAAPI cuvidParseVideoData(CUvideoparser obj, CUVIDSOURCEDATAPACKET *pPacket) + */ +typedef CUresult CUDAAPI tcuvidParseVideoData(CUvideoparser obj, CUVIDSOURCEDATAPACKET *pPacket); + +/** + * \fn CUresult CUDAAPI cuvidDestroyVideoParser(CUvideoparser obj) + */ +typedef CUresult CUDAAPI tcuvidDestroyVideoParser(CUvideoparser obj); + +#if !defined(__APPLE__) +extern tcuvidCreateVideoSource *cuvidCreateVideoSource; +extern tcuvidCreateVideoSourceW *cuvidCreateVideoSourceW; +extern tcuvidDestroyVideoSource *cuvidDestroyVideoSource; +extern tcuvidSetVideoSourceState *cuvidSetVideoSourceState; +extern tcuvidGetVideoSourceState *cuvidGetVideoSourceState; +extern tcuvidGetSourceVideoFormat *cuvidGetSourceVideoFormat; +extern tcuvidGetSourceAudioFormat *cuvidGetSourceAudioFormat; +#endif + + +extern tcuvidCreateVideoParser *cuvidCreateVideoParser; +extern tcuvidParseVideoData *cuvidParseVideoData; +extern tcuvidDestroyVideoParser *cuvidDestroyVideoParser; + +/** @} */ /* END VIDEO_PARSER */ +//////////////////////////////////////////////////////////////////////////////////////////////// + +const char *cudaGetErrorEnum(CUresult error); + +#ifdef __cplusplus +} +#endif + +#endif // defined(WIN32) || defined(GPAC_CONFIG_LINUX) + +#endif //__cuda_tools_h__ diff --git a/src/filters/dec_odf.c b/src/filters/dec_odf.c new file mode 100644 index 0000000..180cd1e --- /dev/null +++ b/src/filters/dec_odf.c @@ -0,0 +1,482 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2017 + * All rights reserved + * + * This file is part of GPAC / OD decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +#ifndef GPAC_DISABLE_PLAYER + +typedef struct +{ + GF_ObjectManager *odm; + GF_Scene *scene; + Bool is_playing; + GF_FilterPid *out_pid; +} GF_ODFDecCtx; + + +GF_Err odf_dec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_ODFDecCtx *ctx = gf_filter_get_udta(filter); + Bool in_iod = GF_FALSE; + GF_FilterPid *out_pid; + const GF_PropertyValue *prop; + + //we must have streamtype SCENE + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (!prop || (prop->value.uint != GF_STREAM_OD)) { + return GF_NOT_SUPPORTED; + } + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!prop || ( (prop->value.uint != GF_CODECID_OD_V1) && (prop->value.uint != GF_CODECID_OD_V2)) ) { + return GF_NOT_SUPPORTED; + } + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_IN_IOD); + if (prop && prop->value.boolean) in_iod = GF_TRUE; + if (! in_iod) { + return GF_NOT_SUPPORTED; + } + + if (is_remove) { + out_pid = gf_filter_pid_get_udta(pid); + if (out_pid==ctx->out_pid) + ctx->out_pid = NULL; + gf_filter_pid_remove(out_pid); + return GF_OK; + } + + //this is a reconfigure + if (gf_filter_pid_get_udta(pid)) { + return GF_OK; + } + + //check our namespace + if (ctx->scene && ! gf_filter_pid_is_filter_in_parents(pid, ctx->scene->root_od->scene_ns->source_filter)) { + return GF_REQUIRES_NEW_INSTANCE; + } + + //declare a new output PID of type scene, codecid RAW + out_pid = gf_filter_pid_new(filter); + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(out_pid, pid); + gf_filter_pid_set_property(out_pid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_udta(pid, out_pid); + if (!ctx->out_pid) + ctx->out_pid = out_pid; + return GF_OK; +} + +void ODS_SetupOD(GF_Scene *scene, GF_ObjectDescriptor *od) +{ + u32 i, j, count, nb_scene, nb_od, nb_esd; + GF_ESD *esd; + GF_ObjectManager *odm; + + if (od->URLString) { + odm = gf_odm_new(); + odm->ID = od->objectDescriptorID; + odm->parentscene = scene; + if (od->fake_remote) + odm->ignore_sys = GF_TRUE; + gf_list_add(scene->resources, odm); + gf_odm_setup_remote_object(odm, scene->root_od->scene_ns, od->URLString); + return; + } + + odm = NULL; + nb_esd = gf_list_count(od->ESDescriptors); + nb_scene = nb_od = 0; + for (i=0; iESDescriptors, i); + if (esd->decoderConfig && (esd->decoderConfig->streamType==GF_STREAM_SCENE)) nb_scene++; + else if (esd->decoderConfig && (esd->decoderConfig->streamType==GF_STREAM_OD)) nb_od++; + } + + for (j=0; jESDescriptors, j); + + count = gf_list_count(scene->resources); + for (i=0; iresources, i); + //can happen with interaction streams + if (!odm->pid) { + odm = NULL; + continue; + } + + if (odm->pid_id == esd->ESID) { + pid = odm->pid; + break; + } + while ( (xpid = gf_list_enum(odm->extra_pids, &k))) { + if (xpid->pid_id == esd->ESID) { + pid = odm->pid; + break; + } + } + if (pid) break; + odm = NULL; + } + + //OCR streams and input sensors don't have PIDs associated for now (only local sensors supported) + if ((esd->decoderConfig->streamType == GF_STREAM_INTERACT) + || (esd->decoderConfig->streamType == GF_STREAM_OCR) + ) { +#ifndef GPAC_DISABLE_VRML + //first time we setup this stream, create an ODM + if (!odm) { + odm = gf_odm_new(); + odm->type = GF_STREAM_INTERACT; + odm->parentscene = scene; + odm->ID = od->objectDescriptorID; + odm->pid_id = esd->ESID; + odm->ck = scene->root_od->ck; + odm->scene_ns = scene->root_od->scene_ns; + odm->scene_ns->nb_odm_users++; + gf_list_add(scene->resources, odm); + } + if (esd->decoderConfig->streamType == GF_STREAM_INTERACT) { + gf_scene_setup_object(scene, odm); + gf_input_sensor_setup_object(odm, esd); + } + else if (esd->decoderConfig->streamType == GF_STREAM_OCR) { + odm->mo = gf_mo_new(); + odm->mo->odm = odm; + odm->mo->OD_ID = od->objectDescriptorID; + odm->mo->type = GF_MEDIA_OBJECT_UNDEF; + gf_list_add(scene->scene_objects, odm->mo); + + gf_clock_set_time(odm->ck, 0); + } +#endif + return; + } else if (!odm || !pid ) { + GF_LOG(GF_LOG_WARNING, GF_LOG_SCENE, ("Cannot match OD ID %d to any PID in the service, ignoring OD\n", od->objectDescriptorID)); + return; + } + + /*if there is BIFS and OD streams in the OD, we need an GF_Scene (except if we already + have one, which means this is the first IOD)*/ + if (nb_scene && nb_od && !odm->subscene) { + odm->subscene = gf_scene_new(NULL, odm->parentscene); + odm->subscene->root_od = odm; + } + + odm->ID = od->objectDescriptorID; + odm->ServiceID = od->ServiceID; + + /*setup PID for this object */ + gf_odm_setup_object(odm, scene->root_od->scene_ns, pid); + + } +} + +static GF_Err ODS_ODUpdate(GF_Scene *scene, GF_ODUpdate *odU) +{ + u32 i, count; + + /*extract all our ODs and compare with what we already have...*/ + count = gf_list_count(odU->objectDescriptors); + if (count > 255) return GF_ODF_INVALID_DESCRIPTOR; + + for (i=0; iobjectDescriptors, i); + ODS_SetupOD(scene, od); + } + return GF_OK; +} + + + +static GF_Err ODS_RemoveOD(GF_Scene *scene, GF_ODRemove *odR) +{ + u32 i; + for (i=0; i< odR->NbODs; i++) { + GF_ObjectManager *odm = gf_scene_find_odm(scene, odR->OD_ID[i]); + if (odm) gf_odm_disconnect(odm, 1); + } + return GF_OK; +} + +static GF_Err ODS_UpdateESD(GF_Scene *scene, GF_ESDUpdate *ESDs) +{ +#if FILTER_FIXME + GF_ObjectManager *odm; + u32 count, i; + + odm = gf_scene_find_odm(priv->scene, ESDs->ODID); + /*spec: "ignore"*/ + if (!odm) return GF_OK; + + count = gf_list_count(ESDs->ESDescriptors); + + while (count) { + GF_ESD *esd, *prev; + esd = (GF_ESD*)gf_list_get(ESDs->ESDescriptors, 0); + /*spec: "ES_Descriptors with ES_IDs that have already been received within the same name scope shall be ignored."*/ + prev = NULL; + i=0; + while ((prev = (GF_ESD*)gf_list_enum(odm->OD->ESDescriptors, &i))) { + if (prev->ESID == esd->ESID) break; + } + if (prev) { + gf_odf_desc_del((GF_Descriptor *)esd); + } else { + /*and register new stream*/ + gf_list_add(odm->OD->ESDescriptors, esd); + gf_odm_setup_es(odm, esd, odm->net_service, NULL); + } + + /*remove the desc from the AU*/ + gf_list_rem(ESDs->ESDescriptors, 0); + count--; + } + /*resetup object since a new ES has been inserted + (typically an empty object first sent, then a stream added - cf how ogg demuxer works)*/ + gf_scene_setup_object(priv->scene, odm); + return GF_OK; +#else + return GF_NOT_SUPPORTED; +#endif +} + + +static GF_Err ODS_RemoveESD(GF_Scene *scene, GF_ESDRemove *ESDs) +{ + GF_ObjectManager *odm = gf_scene_find_odm(scene, ESDs->ODID); + /*spec: "ignore"*/ + if (!odm) return GF_OK; + + return GF_NOT_SUPPORTED; + //todo one day (never used before in MPEG-4 systems): find the stream with the given ESID + //in the parent filter, and remove it +} + +GF_Err odf_dec_process(GF_Filter *filter) +{ + GF_Err e; + GF_ODCom *com; + GF_ODCodec *oddec; + Double ts_offset; + u64 cts, now; + u32 obj_time; + u32 count, i; + const char *data; + u32 size, ESID=0; + const GF_PropertyValue *prop; + GF_ODFDecCtx *ctx = gf_filter_get_udta(filter); + + if (!ctx->scene) { + if (ctx->is_playing) { + gf_filter_pid_set_eos(ctx->out_pid); + return GF_EOS; + } + return GF_OK; + } + + count = gf_filter_get_ipid_count(filter); + for (i=0; isubscene; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + if (prop) ESID = prop->value.uint; + + cts = gf_filter_pck_get_cts( pck ); + ts_offset = (Double) cts; + ts_offset /= gf_filter_pck_get_timescale(pck); + + gf_odm_check_buffering(odm, pid); + + + //we still process any frame before our clock time even when buffering + obj_time = gf_clock_time(odm->ck); + if (ts_offset * 1000 > obj_time) { + gf_sc_sys_frame_pending(scene->compositor, ts_offset, obj_time, filter); + continue; + } + + now = gf_sys_clock_high_res(); + oddec = gf_odf_codec_new(); + + e = gf_odf_codec_set_au(oddec, data, size); + if (!e) e = gf_odf_codec_decode(oddec); + + gf_filter_pid_drop_packet(pid); + + //3- process all the commands in this AU, in order + while (e == GF_OK) { + com = gf_odf_codec_get_com(oddec); + if (!com) break; + + //ok, we have a command + switch (com->tag) { + case GF_ODF_OD_UPDATE_TAG: + e = ODS_ODUpdate(scene, (GF_ODUpdate *) com); + break; + case GF_ODF_OD_REMOVE_TAG: + e = ODS_RemoveOD(scene, (GF_ODRemove *) com); + break; + case GF_ODF_ESD_UPDATE_TAG: + e = ODS_UpdateESD(scene, (GF_ESDUpdate *)com); + break; + case GF_ODF_ESD_REMOVE_TAG: + e = ODS_RemoveESD(scene, (GF_ESDRemove *)com); + break; + case GF_ODF_IPMP_UPDATE_TAG: +#if 0 + { + GF_IPMPUpdate *ipmpU = (GF_IPMPUpdate *)com; + while (gf_list_count(ipmpU->IPMPDescList)) { + GF_IPMP_Descriptor *ipmp = gf_list_get(ipmpU->IPMPDescList, 0); + gf_list_rem(ipmpU->IPMPDescList, 0); + IS_UpdateIPMP(priv->scene, ipmp); + } + e = GF_OK; + } +#else + e = GF_OK; +#endif + break; + case GF_ODF_IPMP_REMOVE_TAG: + e = GF_NOT_SUPPORTED; + break; + /*should NEVER exist outside the file format*/ + case GF_ODF_ESD_REMOVE_REF_TAG: + e = GF_NON_COMPLIANT_BITSTREAM; + break; + default: + if (com->tag >= GF_ODF_COM_ISO_BEGIN_TAG && com->tag <= GF_ODF_COM_ISO_END_TAG) { + e = GF_ODF_FORBIDDEN_DESCRIPTOR; + } else { + /*we don't process user commands*/ + e = GF_OK; + } + break; + } + gf_odf_com_del(&com); + } + + gf_odf_codec_del(oddec); + + now = gf_sys_clock_high_res() - now; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[ODF] ODM%d #CH%d at %d decoded AU TS %u in "LLU" us\n", odm->ID, ESID, obj_time, cts, now)); + } + + return GF_OK; +} + + + +static Bool odf_dec_process_event(GF_Filter *filter, const GF_FilterEvent *com) +{ + u32 count, i; + GF_ODFDecCtx *ctx = gf_filter_get_udta(filter); + + switch (com->base.type) { + case GF_FEVT_ATTACH_SCENE: + break; + case GF_FEVT_PLAY: + ctx->is_playing = GF_TRUE; + return GF_FALSE; + default: + return GF_FALSE; + } + if (!com->attach_scene.on_pid) return GF_TRUE; + + count = gf_filter_get_ipid_count(filter); + for (i=0; iattach_scene.on_pid) { + if (!ctx->odm) { + ctx->odm = com->attach_scene.object_manager; + ctx->scene = ctx->odm->subscene ? ctx->odm->subscene : ctx->odm->parentscene; + } + gf_filter_pid_set_udta(opid, com->attach_scene.object_manager); + return GF_TRUE; + } + } + + return GF_TRUE; +} + +static const GF_FilterCapability ODFDecCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_OD), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_OD_V1), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_OD_V2), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_OD), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +GF_FilterRegister ODFDecRegister = { + .name = "odfdec", + GF_FS_SET_DESCRIPTION("MPEG-4 OD decoder") + GF_FS_SET_HELP("This filter decodes MPEG-4 OD frames directly into the scene manager of the compositor. It cannot be used to dump OD content.") + .private_size = sizeof(GF_ODFDecCtx), + .flags = GF_FS_REG_MAIN_THREAD, + .priority = 1, + SETCAPS(ODFDecCaps), + .process = odf_dec_process, + .configure_pid = odf_dec_configure_pid, + .process_event = odf_dec_process_event, +}; + +const GF_FilterRegister *odf_dec_register(GF_FilterSession *session) +{ + return &ODFDecRegister; +} +#else +const GF_FilterRegister *odf_dec_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // GPAC_DISABLE_PLAYER diff --git a/src/filters/dec_openhevc.c b/src/filters/dec_openhevc.c new file mode 100644 index 0000000..cd767a0 --- /dev/null +++ b/src/filters/dec_openhevc.c @@ -0,0 +1,1294 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2010-2017 + * All rights reserved + * + * This file is part of GPAC / OpenHEVC decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include +#include + +#if defined(GPAC_HAS_OPENHEVC) && !defined(GPAC_DISABLE_AV_PARSERS) + +#include +#include + +#if defined(WIN32) && !defined(_WIN32_WCE) && !defined(__GNUC__) +# pragma comment(lib, "openhevc") +#endif + + +#define HEVC_MAX_STREAMS 2 + +typedef struct +{ + GF_FilterPid *ipid; + u32 cfg_crc; + u32 id; + u32 dep_id; + u32 codec_id; + Bool sublayer; + + u8 *inject_hdr; + u32 inject_hdr_size; +} GF_HEVCStream; + + +typedef struct +{ + //options + u32 threading; + Bool no_copy; + u32 nb_threads; + Bool pack_hfr; + Bool seek_reset; + Bool force_stereo; + Bool reset_switch; + + //internal + GF_Filter *filter; + GF_FilterPid *opid; + GF_HEVCStream streams[HEVC_MAX_STREAMS]; + u32 nb_streams; + Bool is_multiview; + + u32 width, stride, height, out_size, luma_bpp, chroma_bpp; + GF_Fraction sar; + GF_FilterPacket *packed_pck; + u8 *packed_data; + + u32 hevc_nalu_size_length; + Bool has_pic; + + OHHandle codec; + u32 nb_layers, cur_layer; + + Bool decoder_started; + + u32 frame_idx; + u32 dec_frames; + u8 chroma_format_idc; + +#ifdef OPENHEVC_HAS_AVC_BASE + u32 avc_base_id; + u32 avc_nalu_size_length; + char *avc_base; + u32 avc_base_size; + u32 avc_base_pts; +#endif + + Bool force_stereo_reset; + + GF_FilterFrameInterface frame_ifce; + OHFrame frame_ptr; + Bool frame_out; + + char *reaggregation_buffer; + u32 reaggregation_alloc_size, reaggregation_size; + + char *inject_buffer; + u32 inject_buffer_alloc_size, reaggregatioinject_buffer_size; + + Bool reconfig_pending; + + Bool signal_reconfig; + + GF_List *src_packets; + Bool drop_non_refs; +} GF_OHEVCDecCtx; + +static GF_Err ohevcdec_configure_scalable_pid(GF_OHEVCDecCtx *ctx, GF_FilterPid *pid, u32 codecid) +{ + GF_HEVCConfig *cfg = NULL; + u8 *data; + u32 data_len; + GF_BitStream *bs; + Bool is_lhvc = GF_TRUE; + u32 i, j; + const GF_PropertyValue *dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + + if (!dsi) { + dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + is_lhvc = GF_FALSE; + } + + if (!ctx->codec) return GF_NOT_SUPPORTED; + + if (!dsi || !dsi->value.data.size) { + ctx->nb_layers++; + ctx->cur_layer++; + oh_select_active_layer(ctx->codec, ctx->cur_layer-1); + oh_select_view_layer(ctx->codec, ctx->cur_layer-1); + return GF_OK; + } + + cfg = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, is_lhvc); + + if (!cfg) return GF_NON_COMPLIANT_BITSTREAM; + if (!ctx->hevc_nalu_size_length) ctx->hevc_nalu_size_length = cfg->nal_unit_size; + else if (ctx->hevc_nalu_size_length != cfg->nal_unit_size) + return GF_NON_COMPLIANT_BITSTREAM; + + ctx->nb_layers++; + ctx->cur_layer++; + oh_select_active_layer(ctx->codec, ctx->nb_layers-1); + oh_select_view_layer(ctx->codec, ctx->nb_layers-1); + +#ifdef OPENHEVC_HAS_AVC_BASE + //LHVC mode with base AVC layer: set extradata for LHVC + if (ctx->avc_base_id) { + oh_extradata_cpy_lhvc(ctx->codec, NULL, (u8 *) dsi->value.data.ptr, 0, dsi->value.data.size); + } else +#endif + //LHVC mode with base HEVC layer: decode the LHVC SPS/PPS/VPS + { + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + for (i=0; i< gf_list_count(cfg->param_array); i++) { + GF_HEVCParamArray *ar = (GF_HEVCParamArray *)gf_list_get(cfg->param_array, i); + for (j=0; j< gf_list_count(ar->nalus); j++) { + GF_AVCConfigSlot *sl = (GF_AVCConfigSlot *)gf_list_get(ar->nalus, j); + gf_bs_write_int(bs, sl->size, 8*ctx->hevc_nalu_size_length); + gf_bs_write_data(bs, sl->data, sl->size); + } + } + + gf_bs_get_content(bs, &data, &data_len); + gf_bs_del(bs); + //the decoder may not be already started + if (!ctx->decoder_started) { + oh_start(ctx->codec); + ctx->decoder_started=1; + } + + oh_decode(ctx->codec, (u8 *)data, data_len, 0); + gf_free(data); + } + gf_odf_hevc_cfg_del(cfg); + return GF_OK; +} + + +static void ohevcdec_write_ps_list(GF_BitStream *bs, GF_List *list, u32 nalu_size_length) +{ + u32 i, count = list ? gf_list_count(list) : 0; + for (i=0; isize, 8*nalu_size_length); + gf_bs_write_data(bs, sl->data, sl->size); + } +} + +static void ohevcdec_create_inband(GF_HEVCStream *stream, u32 nal_unit_size, GF_List *sps, GF_List *pps, GF_List *vps) +{ + GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + if (vps) ohevcdec_write_ps_list(bs, vps, nal_unit_size); + if (sps) ohevcdec_write_ps_list(bs, sps, nal_unit_size); + if (pps) ohevcdec_write_ps_list(bs, pps, nal_unit_size); + if (stream->inject_hdr) gf_free(stream->inject_hdr); + gf_bs_get_content(bs, &stream->inject_hdr, &stream->inject_hdr_size); + gf_bs_del(bs); +} + + +#if defined(OPENHEVC_HAS_AVC_BASE) && !defined(GPAC_DISABLE_LOG) +void openhevc_log_callback(void *udta, int l, const char*fmt, va_list vl) +{ + u32 level = GF_LOG_DEBUG; + if (l <= OHEVC_LOG_ERROR) level = GF_LOG_ERROR; + else if (l <= OHEVC_LOG_WARNING) level = GF_LOG_WARNING; + else if (l <= OHEVC_LOG_INFO) level = GF_LOG_INFO; +// else if (l >= OHEVC_LOG_VERBOSE) return; + + if (gf_log_tool_level_on(GF_LOG_CODEC, level)) { + gf_log_va_list(level, GF_LOG_CODEC, fmt, vl); + } +} +#endif + +static void ohevcdec_set_codec_name(GF_Filter *filter) +{ + GF_OHEVCDecCtx *ctx = (GF_OHEVCDecCtx*) gf_filter_get_udta(filter); +#ifdef OPENHEVC_HAS_AVC_BASE + if (ctx->avc_base_id) { + if (ctx->cur_layer==1) gf_filter_set_name(filter, "OpenHEVC-v"NV_VERSION"-AVC|H264"); + else gf_filter_set_name(filter, "OpenHEVC-v"NV_VERSION"-AVC|H264+LHVC"); + } + if (ctx->cur_layer==1) gf_filter_set_name(filter, "OpenHEVC-v"NV_VERSION); + else gf_filter_set_name(filter, "OpenHEVC-v"NV_VERSION"-LHVC"); +#else + return gf_filter_set_name(filter, libOpenHevcVersion(ctx->codec) ); +#endif +} + +static u32 ohevcdec_get_pixel_format( u32 luma_bpp, u8 chroma_format_idc) +{ + switch (chroma_format_idc) { + case 1: + return (luma_bpp==10) ? GF_PIXEL_YUV_10 : GF_PIXEL_YUV; + case 2: + return (luma_bpp==10) ? GF_PIXEL_YUV422_10 : GF_PIXEL_YUV422; + case 3: + return (luma_bpp==10) ? GF_PIXEL_YUV444_10 : GF_PIXEL_YUV444; + default: + return 0; + } + return 0; +} + +static void ohevc_set_out_props(GF_OHEVCDecCtx *ctx) +{ + u32 pixfmt; + + gf_filter_pid_copy_properties(ctx->opid, ctx->streams[0].ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, NULL ); + + if (ctx->pack_hfr) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(2*ctx->width) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(2*ctx->height) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(2*ctx->stride) ); + } else { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->width) ); + if (ctx->force_stereo && ctx->is_multiview && ctx->cur_layer>1) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(2*ctx->height) ); + } else { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->height) ); + } + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->stride) ); + } + pixfmt = ohevcdec_get_pixel_format(ctx->luma_bpp, ctx->chroma_format_idc); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, &PROP_UINT(pixfmt) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, &PROP_FRAC(ctx->sar) ); +} + +static GF_Err ohevcdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 i, dep_id=0, id=0, cfg_crc=0, codecid; + Bool has_scalable = GF_FALSE; + Bool is_sublayer = GF_FALSE; + u8 *patched_dsi=NULL; + u32 patched_dsi_size=0; + GF_HEVCStream *stream; + const GF_PropertyValue *p, *dsi, *dsi_enh=NULL; + GF_OHEVCDecCtx *ctx = (GF_OHEVCDecCtx*) gf_filter_get_udta(filter); + + if (is_remove) { + if (ctx->streams[0].ipid == pid) { + memset(ctx->streams, 0, HEVC_MAX_STREAMS*sizeof(GF_HEVCStream)); + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + ctx->nb_streams = 0; + if (ctx->codec) oh_close(ctx->codec); + ctx->codec = NULL; + return GF_OK; + } else { + for (i=0; inb_streams; i++) { + if (ctx->streams[i].ipid == pid) { + ctx->streams[i].ipid = NULL; + ctx->streams[i].cfg_crc = 0; + memmove(&ctx->streams[i], &ctx->streams[i+1], sizeof(GF_HEVCStream)*(ctx->nb_streams-1)); + ctx->nb_streams--; + return GF_OK; + } + } + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DEPENDENCY_ID); + if (p) dep_id = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + if (!p) p = gf_filter_pid_get_property(pid, GF_PROP_PID_ESID); + if (p) id = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + codecid = p ? p->value.uint : 0; + if (!codecid) return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SCALABLE); + if (p) has_scalable = p->value.boolean; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SUBLAYER); + if (p) is_sublayer = p->value.boolean; + + dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + cfg_crc = 0; + if (dsi && dsi->value.data.ptr && dsi->value.data.size) { + cfg_crc = gf_crc_32(dsi->value.data.ptr, dsi->value.data.size); + } + dsi_enh = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + + stream = NULL; + //check if this is an update + for (i=0; inb_streams; i++) { + if (ctx->streams[i].ipid == pid) { + if (ctx->streams[i].cfg_crc == cfg_crc) return GF_OK; + if (ctx->codec && (ctx->streams[i].codec_id != codecid)) { + //we are already instantiated, flush all frames and reconfig + ctx->reconfig_pending = GF_TRUE; + return GF_OK; + } + ctx->streams[i].codec_id = codecid; + ctx->streams[i].cfg_crc = cfg_crc; + stream = &ctx->streams[i]; + break; + } + } + + if (!stream) { + if (ctx->nb_streams==HEVC_MAX_STREAMS) { + return GF_NOT_SUPPORTED; + } + //insert new pid in order of dependencies + for (i=0; inb_streams; i++) { + + if (!dep_id && !ctx->streams[i].dep_id) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[SVC Decoder] Detected multiple independent base (%s and %s)\n", gf_filter_pid_get_name(pid), gf_filter_pid_get_name(ctx->streams[i].ipid))); + return GF_REQUIRES_NEW_INSTANCE; + } + + if (ctx->streams[i].id == dep_id) { + if (ctx->nb_streams > i+2) + memmove(&ctx->streams[i+1], &ctx->streams[i+2], sizeof(GF_HEVCStream) * (ctx->nb_streams-i-1)); + + ctx->streams[i+1].ipid = pid; + ctx->streams[i+1].cfg_crc = cfg_crc; + ctx->streams[i+1].dep_id = dep_id; + ctx->streams[i+1].id = id; + ctx->streams[i+1].codec_id = codecid; + ctx->streams[i+1].sublayer = is_sublayer; + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + stream = &ctx->streams[i+1]; + break; + } + if (ctx->streams[i].dep_id == id) { + if (ctx->nb_streams > i+1) + memmove(&ctx->streams[i+1], &ctx->streams[i], sizeof(GF_HEVCStream) * (ctx->nb_streams-i)); + + ctx->streams[i].ipid = pid; + ctx->streams[i].cfg_crc = cfg_crc; + ctx->streams[i].dep_id = dep_id; + ctx->streams[i].id = id; + ctx->streams[i].codec_id = codecid; + ctx->streams[i].sublayer = is_sublayer; + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + stream = &ctx->streams[i]; + break; + } + } + if (!stream) { + ctx->streams[ctx->nb_streams].ipid = pid; + ctx->streams[ctx->nb_streams].cfg_crc = cfg_crc; + ctx->streams[ctx->nb_streams].id = id; + ctx->streams[ctx->nb_streams].dep_id = dep_id; + ctx->streams[ctx->nb_streams].codec_id = codecid; + ctx->streams[ctx->nb_streams].sublayer = is_sublayer; + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + stream = &ctx->streams[ctx->nb_streams]; + } + ctx->nb_streams++; + ctx->signal_reconfig = GF_TRUE; + } + ctx->nb_layers = ctx->cur_layer = 1; + + //temporal sublayer stream setup, do nothing + if (stream->sublayer) + return GF_OK; + + //scalable stream setup + if (stream->dep_id) { + GF_Err e; + e = ohevcdec_configure_scalable_pid(ctx, pid, codecid); + ohevcdec_set_codec_name(filter); + return e; + } + + + if (codecid == GF_CODECID_AVC) +#ifdef OPENHEVC_HAS_AVC_BASE + ctx->avc_base_id = id; +#else + return GF_NOT_SUPPORTED; +#endif + + if (dsi && dsi->value.data.size) { +#ifdef OPENHEVC_HAS_AVC_BASE + if (codecid==GF_CODECID_AVC) { + GF_AVCConfig *avcc = NULL; + AVCState avc; + memset(&avc, 0, sizeof(AVCState)); + + avcc = gf_odf_avc_cfg_read(dsi->value.data.ptr, dsi->value.data.size); + if (!avcc) return GF_NON_COMPLIANT_BITSTREAM; + ctx->avc_nalu_size_length = avcc->nal_unit_size; + + for (i=0; i< gf_list_count(avcc->sequenceParameterSets); i++) { + GF_AVCConfigSlot *sl = (GF_AVCConfigSlot *)gf_list_get(avcc->sequenceParameterSets, i); + s32 idx = gf_media_avc_read_sps(sl->data, sl->size, &avc, 0, NULL); + ctx->width = MAX(avc.sps[idx].width, ctx->width); + ctx->height = MAX(avc.sps[idx].height, ctx->height); + ctx->luma_bpp = avcc->luma_bit_depth; + ctx->chroma_bpp = avcc->chroma_bit_depth; + ctx->chroma_format_idc = avcc->chroma_format; + } + + if (ctx->codec && ctx->decoder_started) { + //this seems to be broken in openhevc, we need to reconfigure the decoder ... + if (ctx->reset_switch) { + ctx->reconfig_pending = GF_TRUE; + stream->cfg_crc = 0; + gf_odf_avc_cfg_del(avcc); + return GF_OK; + } else { + ohevcdec_create_inband(stream, avcc->nal_unit_size, avcc->sequenceParameterSets, avcc->pictureParameterSets, NULL); + } + } + gf_odf_avc_cfg_del(avcc); + } else + +#endif + { + GF_HEVCConfig *hvcc = NULL; + GF_HEVCConfig *hvcc_enh = NULL; + HEVCState hevc; + u32 j; + GF_List *SPSs=NULL, *PPSs=NULL, *VPSs=NULL; + memset(&hevc, 0, sizeof(HEVCState)); + + hvcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, GF_FALSE); + if (!hvcc) return GF_NON_COMPLIANT_BITSTREAM; + + ctx->hevc_nalu_size_length = hvcc->nal_unit_size; + + if (dsi_enh) { + hvcc_enh = gf_odf_hevc_cfg_read(dsi_enh->value.data.ptr, dsi_enh->value.data.size, GF_TRUE); + } + + for (i=0; i< gf_list_count(hvcc->param_array); i++) { + GF_HEVCParamArray *ar = (GF_HEVCParamArray *)gf_list_get(hvcc->param_array, i); + + if (hvcc_enh) { + for (j=0; j< gf_list_count(hvcc_enh->param_array); j++) { + GF_HEVCParamArray *ar_enh = (GF_HEVCParamArray *)gf_list_get(hvcc_enh->param_array, j); + if (ar->type==ar_enh->type) + gf_list_transfer(ar->nalus, ar_enh->nalus); + } + } + for (j=0; j< gf_list_count(ar->nalus); j++) { + GF_AVCConfigSlot *sl = (GF_AVCConfigSlot *)gf_list_get(ar->nalus, j); + s32 idx; + u16 hdr = sl->data[0] << 8 | sl->data[1]; + + if (ar->type==GF_HEVC_NALU_SEQ_PARAM) { + idx = gf_media_hevc_read_sps(sl->data, sl->size, &hevc); + ctx->width = MAX(hevc.sps[idx].width, ctx->width); + ctx->height = MAX(hevc.sps[idx].height, ctx->height); + ctx->luma_bpp = MAX(hevc.sps[idx].bit_depth_luma, ctx->luma_bpp); + ctx->chroma_bpp = MAX(hevc.sps[idx].bit_depth_chroma, ctx->chroma_bpp); + ctx->chroma_format_idc = hevc.sps[idx].chroma_format_idc; + + if (hdr & 0x1f8) { + ctx->nb_layers ++; + } + SPSs = ar->nalus; + } + else if (ar->type==GF_HEVC_NALU_VID_PARAM) { + s32 vps_id = gf_media_hevc_read_vps(sl->data, sl->size, &hevc); + //multiview + if ((vps_id>=0) && (hevc.vps[vps_id].scalability_mask[1])) { + ctx->is_multiview = GF_TRUE; + } + VPSs = ar->nalus; + } + else if (ar->type==GF_HEVC_NALU_PIC_PARAM) { + gf_media_hevc_read_pps(sl->data, sl->size, &hevc); + PPSs = ar->nalus; + } + } + } + if (ctx->codec && ctx->decoder_started) { + if (ctx->reset_switch) { + ctx->reconfig_pending = GF_TRUE; + stream->cfg_crc = 0; + gf_odf_hevc_cfg_del(hvcc); + return GF_OK; + } else { + ohevcdec_create_inband(stream, hvcc->nal_unit_size, SPSs, PPSs, VPSs); + } + } + if (hvcc_enh) { + gf_odf_hevc_cfg_del(hvcc_enh); + gf_odf_hevc_cfg_write(hvcc, &patched_dsi, &patched_dsi_size); + } + gf_odf_hevc_cfg_del(hvcc); + } + } + + if (!ctx->codec) { +#ifdef OPENHEVC_HAS_AVC_BASE + if (ctx->avc_base_id) { + ctx->codec = oh_init_lhvc(ctx->nb_threads, ctx->threading); + } else +#endif + { + ctx->codec = oh_init(ctx->nb_threads, ctx->threading); + } + } + + +#if defined(OPENHEVC_HAS_AVC_BASE) && !defined(GPAC_DISABLE_LOG) + if (gf_log_tool_level_on(GF_LOG_CODEC, GF_LOG_DEBUG) ) { + oh_set_log_level(ctx->codec, OHEVC_LOG_DEBUG); + } else if (gf_log_tool_level_on(GF_LOG_CODEC, GF_LOG_INFO) ) { + oh_set_log_level(ctx->codec, OHEVC_LOG_INFO); + } else if (gf_log_tool_level_on(GF_LOG_CODEC, GF_LOG_WARNING) ) { + oh_set_log_level(ctx->codec, OHEVC_LOG_WARNING); + } else { + oh_set_log_level(ctx->codec, OHEVC_LOG_ERROR); + } + oh_set_log_callback(ctx->codec, openhevc_log_callback); +#endif + + if (dsi) { + if (has_scalable) { + ctx->cur_layer = ctx->nb_layers; + oh_select_active_layer(ctx->codec, ctx->cur_layer-1); + oh_select_view_layer(ctx->codec, ctx->cur_layer-1); + } else { + //there is a bug with select active layer on win32 with avc base +#ifdef WIN32 + if (!ctx->avc_base_id) { +#endif + oh_select_active_layer(ctx->codec, 1); + if (!ctx->avc_base_id) + oh_select_view_layer(ctx->codec, 0); +#ifdef WIN32 + } +#endif + } + + if (!ctx->decoder_started) { +#ifdef OPENHEVC_HAS_AVC_BASE + if (ctx->avc_base_id) { + oh_extradata_cpy_lhvc(ctx->codec, (u8 *) dsi->value.data.ptr, NULL, dsi->value.data.size, 0); + } else +#endif + { + if (patched_dsi) { + oh_extradata_cpy(ctx->codec, (u8 *) patched_dsi, patched_dsi_size); + } else { + oh_extradata_cpy(ctx->codec, (u8 *) dsi->value.data.ptr, dsi->value.data.size); + } + } + } + } else { + //decode and display layer 0 by default - will be changed when attaching enhancement layers + + //has_scalable_layers is set, the esd describes a set of HEVC stream but we don't know how many - for now only two decoders so easy, + //but should be fixed in the future + if (has_scalable) { + ctx->nb_layers = 2; + ctx->cur_layer = 2; + } + oh_select_active_layer(ctx->codec, ctx->cur_layer-1); + oh_select_view_layer(ctx->codec, ctx->cur_layer-1); + } + + if (patched_dsi) gf_free(patched_dsi); + + //in case we don't have a config record + if (!ctx->chroma_format_idc) ctx->chroma_format_idc = 1; + + //we start decoder on the first frame + ctx->dec_frames = 0; + ohevcdec_set_codec_name(filter); + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + + //copy properties at init or reconfig + if (ctx->signal_reconfig) { + ohevc_set_out_props(ctx); + ctx->signal_reconfig = GF_FALSE; + } else { + ctx->signal_reconfig = GF_TRUE; + } + return GF_OK; +} + + + +static Bool ohevcdec_process_event(GF_Filter *filter, const GF_FilterEvent *fevt) +{ + GF_OHEVCDecCtx *ctx = (GF_OHEVCDecCtx*) gf_filter_get_udta(filter); + + if (fevt->base.type == GF_FEVT_QUALITY_SWITCH) { + + if (ctx->nb_layers==1) return GF_FALSE; + /*switch up*/ + if (fevt->quality_switch.up > 0) { + if (ctx->cur_layer>=ctx->nb_layers) return GF_FALSE; + ctx->cur_layer++; + } else { + if (ctx->cur_layer<=1) return GF_FALSE; + ctx->cur_layer--; + } + oh_select_view_layer(ctx->codec, ctx->cur_layer-1); + oh_select_active_layer(ctx->codec, ctx->cur_layer-1); + if (ctx->is_multiview) + ctx->force_stereo_reset = ctx->force_stereo; + + //todo: we should get the set of pids active and trigger the switch up/down based on that + //rather than not canceling the event + return GF_FALSE; + } else if (fevt->base.type == GF_FEVT_STOP) { + if (ctx->seek_reset && ctx->dec_frames) { + u32 i; + u32 cl = ctx->cur_layer; + u32 nl = ctx->nb_layers; + + //quick hack, we have an issue with openHEVC resuming after being flushed ... + oh_close(ctx->codec); + ctx->codec = NULL; + ctx->decoder_started = GF_FALSE; + for (i=0; inb_streams; i++) { + ohevcdec_configure_pid(filter, ctx->streams[i].ipid, GF_FALSE); + } + ctx->cur_layer = cl; + ctx->nb_layers = nl; + if (ctx->codec) { + oh_select_active_layer(ctx->codec, ctx->cur_layer-1); + oh_select_view_layer(ctx->codec, ctx->cur_layer-1); + } + } + } + else if ((fevt->base.type==GF_FEVT_PLAY) || (fevt->base.type==GF_FEVT_SET_SPEED) || (fevt->base.type==GF_FEVT_RESUME)) { + ctx->drop_non_refs = fevt->play.drop_non_ref; + } + return GF_FALSE; +} + + +void ohevcframe_release(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck) +{ + GF_OHEVCDecCtx *ctx = (GF_OHEVCDecCtx *) gf_filter_get_udta(filter); + ctx->frame_out = GF_FALSE; + gf_filter_post_process_task(ctx->filter); +} + +GF_Err ohevcframe_get_plane(GF_FilterFrameInterface *frame, u32 plane_idx, const u8 **outPlane, u32 *outStride) +{ + GF_OHEVCDecCtx *ctx = (GF_OHEVCDecCtx *)frame->user_data; + if (! outPlane || !outStride) return GF_BAD_PARAM; + *outPlane = NULL; + *outStride = 0; + + if (plane_idx==0) { + *outPlane = (const u8 *) ctx->frame_ptr.data_y_p; + *outStride = ctx->frame_ptr.frame_par.linesize_y; + } else if (plane_idx==1) { + *outPlane = (const u8 *) ctx->frame_ptr.data_cb_p; + *outStride = ctx->frame_ptr.frame_par.linesize_cb; + } else if (plane_idx==2) { + *outPlane = (const u8 *) ctx->frame_ptr.data_cr_p; + *outStride = ctx->frame_ptr.frame_par.linesize_cr; + } else + return GF_BAD_PARAM; + + return GF_OK; +} + +static GF_Err ohevcdec_send_output_frame(GF_OHEVCDecCtx *ctx) +{ + GF_FilterPacket *dst_pck, *src_pck; + u32 i, count; + + ctx->frame_ifce.user_data = ctx; + ctx->frame_ifce.get_plane = ohevcframe_get_plane; + //we only keep one frame out, force releasing it + ctx->frame_ifce.flags = GF_FRAME_IFCE_BLOCKING; + oh_output_update(ctx->codec, 1, &ctx->frame_ptr); + + dst_pck = gf_filter_pck_new_frame_interface(ctx->opid, &ctx->frame_ifce, ohevcframe_release); + + src_pck = NULL; + count = gf_list_count(ctx->src_packets); + for (i=0;isrc_packets, i); + if (gf_filter_pck_get_cts(src_pck) == ctx->frame_ptr.frame_par.pts) { + u8 car_v = gf_filter_pck_get_carousel_version(src_pck); + gf_filter_pck_set_carousel_version(src_pck, 0); + + gf_filter_pck_merge_properties(src_pck, dst_pck); + gf_list_rem(ctx->src_packets, i); + gf_filter_pck_unref(src_pck); + + if (car_v) + ohevc_set_out_props(ctx); + + break; + } + src_pck = NULL; + } + if (!src_pck) + gf_filter_pck_set_cts(dst_pck, ctx->frame_ptr.frame_par.pts); + + ctx->frame_out = GF_TRUE; + gf_filter_pck_send(dst_pck); + return GF_OK; +} +static GF_Err ohevcdec_flush_picture(GF_OHEVCDecCtx *ctx) +{ + GF_FilterPacket *pck, *src_pck; + u8 *data; + u32 a_w, a_h, a_stride, bit_depth, i, count; + u64 cts; + OHFrame_cpy openHevcFrame_FL, openHevcFrame_SL; + int chromat_format; + + if (ctx->no_copy && !ctx->pack_hfr) { + oh_frameinfo_update(ctx->codec, &openHevcFrame_FL.frame_par); + } else { + oh_cropped_frameinfo(ctx->codec, &openHevcFrame_FL.frame_par); + if (ctx->nb_layers == 2) oh_cropped_frameinfo_from_layer(ctx->codec, &openHevcFrame_SL.frame_par, 1); + } + + a_w = openHevcFrame_FL.frame_par.width; + a_h = openHevcFrame_FL.frame_par.height; + a_stride = openHevcFrame_FL.frame_par.linesize_y; + bit_depth = openHevcFrame_FL.frame_par.bitdepth; + chromat_format = openHevcFrame_FL.frame_par.chromat_format; + cts = (u32) openHevcFrame_FL.frame_par.pts; + if (!openHevcFrame_FL.frame_par.sample_aspect_ratio.den || !openHevcFrame_FL.frame_par.sample_aspect_ratio.num) + openHevcFrame_FL.frame_par.sample_aspect_ratio.den = openHevcFrame_FL.frame_par.sample_aspect_ratio.num = 1; + + src_pck = NULL; + count = gf_list_count(ctx->src_packets); + for (i=0;isrc_packets, i); + if (gf_filter_pck_get_cts(src_pck) == cts) break; + src_pck = NULL; + } + + if (src_pck && gf_filter_pck_get_seek_flag(src_pck)) { + gf_list_del_item(ctx->src_packets, src_pck); + gf_filter_pck_unref(src_pck); + return GF_OK; + } + + if (ctx->force_stereo_reset || !ctx->out_size || (ctx->width != a_w) || (ctx->height!=a_h) || (ctx->stride != a_stride) + || (ctx->luma_bpp!= bit_depth) || (ctx->chroma_bpp != bit_depth) || (ctx->chroma_format_idc != (chromat_format + 1)) + || (ctx->sar.num*openHevcFrame_FL.frame_par.sample_aspect_ratio.den != ctx->sar.den*openHevcFrame_FL.frame_par.sample_aspect_ratio.num) + ) { + ctx->width = a_w; + ctx->stride = a_stride; + ctx->height = a_h; + if( chromat_format == OH_YUV420 ) { + ctx->out_size = ctx->stride * ctx->height * 3 / 2; + } else if ( chromat_format == OH_YUV422 ) { + ctx->out_size = ctx->stride * ctx->height * 2; + } else if ( chromat_format == OH_YUV444 ) { + ctx->out_size = ctx->stride * ctx->height * 3; + } + //force top/bottom output of left and right frame, double height + if (ctx->pack_hfr) { + ctx->out_size *= 4; + } else if ((ctx->cur_layer==2) && (ctx->is_multiview || ctx->force_stereo) ){ + ctx->out_size *= 2; + } + + ctx->luma_bpp = ctx->chroma_bpp = bit_depth; + ctx->chroma_format_idc = chromat_format + 1; + ctx->sar.num = openHevcFrame_FL.frame_par.sample_aspect_ratio.num; + ctx->sar.den = openHevcFrame_FL.frame_par.sample_aspect_ratio.den; + if (!ctx->sar.num) ctx->sar.num = ctx->sar.den = 1; + + ohevc_set_out_props(ctx); + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[HEVC Decoder] Sending ouput frame CTS "LLU"\n", openHevcFrame_FL.frame_par.pts )); + + if (ctx->no_copy && !ctx->pack_hfr) { + return ohevcdec_send_output_frame(ctx); + } + + if (ctx->pack_hfr) { + OHFrame openHFrame; + u8 *pY, *pU, *pV; + u32 idx_w, idx_h; + + idx_w = ((ctx->frame_idx==0) || (ctx->frame_idx==2)) ? 0 : ctx->stride; + idx_h = ((ctx->frame_idx==0) || (ctx->frame_idx==1)) ? 0 : ctx->height*2*ctx->stride; + + if (!ctx->packed_pck) { + ctx->packed_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->out_size, &ctx->packed_data); + if (src_pck) gf_filter_pck_merge_properties(src_pck, ctx->packed_pck); + else gf_filter_pck_set_cts(ctx->packed_pck, cts); + } + if (src_pck) { + gf_list_del_item(ctx->src_packets, src_pck); + gf_filter_pck_unref(src_pck); + } + + pY = (u8*) (ctx->packed_data + idx_h + idx_w ); + + if (chromat_format == OH_YUV422) { + pU = (u8*)(ctx->packed_data + 4 * ctx->stride * ctx->height + idx_w / 2 + idx_h / 2); + pV = (u8*)(ctx->packed_data + 4 * (3 * ctx->stride * ctx->height /2) + idx_w / 2 + idx_h / 2); + } else if (chromat_format == OH_YUV444) { + pU = (u8*)(ctx->packed_data + 4 * ctx->stride * ctx->height + idx_w + idx_h); + pV = (u8*)(ctx->packed_data + 4 * ( 2 * ctx->stride * ctx->height) + idx_w + idx_h); + } else { + pU = (u8*)(ctx->packed_data + 2 * ctx->stride * 2 * ctx->height + idx_w / 2 + idx_h / 4); + pV = (u8*)(ctx->packed_data + 4 * ( 5 *ctx->stride * ctx->height / 4) + idx_w / 2 + idx_h / 4); + + } + + if (oh_output_update(ctx->codec, 1, &openHFrame)) { + u32 i, s_stride, hs_stride, qs_stride, d_stride, dd_stride, hd_stride; + + s_stride = openHFrame.frame_par.linesize_y; + qs_stride = s_stride / 4; + hs_stride = s_stride / 2; + + d_stride = ctx->stride; + dd_stride = 2*ctx->stride; + hd_stride = ctx->stride/2; + + if (chromat_format == OH_YUV422) { + for (i = 0; i < ctx->height; i++) { + memcpy(pY, (u8 *)openHFrame.data_y_p + i*s_stride, d_stride); + pY += dd_stride; + + memcpy(pU, (u8 *)openHFrame.data_cb_p + i*hs_stride, hd_stride); + pU += d_stride; + + memcpy(pV, (u8 *)openHFrame.data_cr_p + i*hs_stride, hd_stride); + pV += d_stride; + } + } else if (chromat_format == OH_YUV444) { + for (i = 0; i < ctx->height; i++) { + memcpy(pY, (u8 *)openHFrame.data_y_p + i*s_stride, d_stride); + pY += dd_stride; + + memcpy(pU, (u8 *)openHFrame.data_cb_p + i*s_stride, d_stride); + pU += dd_stride; + + memcpy(pV, (u8 *)openHFrame.data_cr_p + i*s_stride, d_stride); + pV += dd_stride; + } + } else { + for (i = 0; iheight; i++) { + memcpy(pY, (u8 *)openHFrame.data_y_p + i*s_stride, d_stride); + pY += dd_stride; + + if (!(i % 2)) { + memcpy(pU, (u8 *)openHFrame.data_cb_p + i*qs_stride, hd_stride); + pU += d_stride; + + memcpy(pV, (u8 *)openHFrame.data_cr_p + i*qs_stride, hd_stride); + pV += d_stride; + } + } + } + + ctx->frame_idx++; + if (ctx->frame_idx==4) { + gf_filter_pck_send(ctx->packed_pck); + ctx->packed_pck = NULL; + ctx->frame_idx = 0; + } + } + return GF_OK; + } + + pck = gf_filter_pck_new_alloc(ctx->opid, ctx->out_size, &data); + + openHevcFrame_FL.data_y = (void*) data; + + if (ctx->nb_layers==2 && ctx->is_multiview && !ctx->no_copy){ + int out1, out2; + if( chromat_format == OH_YUV420){ + openHevcFrame_SL.data_y = (void*) (data + ctx->stride * ctx->height); + openHevcFrame_FL.data_cb = (void*) (data + 2*ctx->stride * ctx->height); + openHevcFrame_SL.data_cb = (void*) (data + 9*ctx->stride * ctx->height/4); + openHevcFrame_FL.data_cr = (void*) (data + 5*ctx->stride * ctx->height/2); + openHevcFrame_SL.data_cr = (void*) (data + 11*ctx->stride * ctx->height/4); + } + + out1 = oh_output_cropped_cpy_from_layer(ctx->codec, &openHevcFrame_FL, 0); + out2 = oh_output_cropped_cpy_from_layer(ctx->codec, &openHevcFrame_SL, 1); + + if (out1 && out2) { + gf_filter_pck_set_cts(pck, cts); + gf_filter_pck_send(pck); + } else { + gf_filter_pck_discard(pck); + } + } else { + openHevcFrame_FL.data_cb = (void*) (data + ctx->stride * ctx->height); + if( chromat_format == OH_YUV420) { + openHevcFrame_FL.data_cr = (void*) (data + 5*ctx->stride * ctx->height/4); + } else if (chromat_format == OH_YUV422) { + openHevcFrame_FL.data_cr = (void*) (data + 3*ctx->stride * ctx->height/2); + } else if ( chromat_format == OH_YUV444) { + openHevcFrame_FL.data_cr = (void*) (data + 2*ctx->stride * ctx->height); + } + + if (oh_output_cropped_cpy(ctx->codec, &openHevcFrame_FL)) { + if (src_pck) { + u8 car_v = gf_filter_pck_get_carousel_version(src_pck); + gf_filter_pck_set_carousel_version(src_pck, 0); + + gf_filter_pck_merge_properties(src_pck, pck); + gf_list_del_item(ctx->src_packets, src_pck); + gf_filter_pck_unref(src_pck); + + if (car_v) + ohevc_set_out_props(ctx); + } else { + gf_filter_pck_set_cts(pck, cts); + } + gf_filter_pck_send(pck); + } else + gf_filter_pck_discard(pck); + } + return GF_OK; +} + +static GF_Err ohevcdec_process(GF_Filter *filter) +{ + s32 got_pic; + u64 min_dts = GF_FILTER_NO_TS; + u64 min_cts = GF_FILTER_NO_TS; + u32 idx, nb_eos=0; + u32 data_size, nbpck; + char *data; + Bool has_pic = GF_FALSE; + GF_FilterPacket *pck_ref = NULL; + GF_OHEVCDecCtx *ctx = (GF_OHEVCDecCtx*) gf_filter_get_udta(filter); + + if (ctx->frame_out) return GF_EOS; + + + if (ctx->reconfig_pending) { + //wait for each input pid to be ready - this will force reconfig on pids if needed + for (idx=0; idxnb_streams; idx++) { + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->streams[idx].ipid); + if (!pck) return GF_OK; + } + oh_flush(ctx->codec); + //flush +#ifdef OPENHEVC_HAS_AVC_BASE + if (ctx->avc_base_id) { + got_pic = oh_decode_lhvc(ctx->codec, NULL, NULL, 0, 0, 0, 0); + } else +#endif + got_pic = oh_decode(ctx->codec, NULL, 0, 0); + + if ( got_pic ) { + return ohevcdec_flush_picture(ctx); + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[HEVC Decoder] Closing decoder for reconfigure\n")); + //good to go: no more pics in decoder, no frame out + ctx->reconfig_pending = GF_FALSE; + oh_close(ctx->codec); + ctx->codec = NULL; + ctx->decoder_started = GF_FALSE; + for (idx=0; idxnb_streams; idx++) { + ohevcdec_configure_pid(filter, ctx->streams[idx].ipid, GF_FALSE); + } + } + if (!ctx->codec) return GF_SERVICE_ERROR; + + //probe all streams + for (idx=0; idxnb_streams; idx++) { + u64 dts, cts; + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->streams[idx].ipid); + + if (ctx->reconfig_pending) return GF_OK; + + if (!pck) { + if (gf_filter_pid_is_eos(ctx->streams[idx].ipid)) + nb_eos++; + //make sure we do have a packet on the enhancement + else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[OpenHEVC] no input packets on running pid %s - postponing decode\n", gf_filter_pid_get_name(ctx->streams[idx].ipid) ) ); + return GF_OK; + } + continue; + } + if (ctx->drop_non_refs && !gf_filter_pck_get_sap(pck)) { + gf_filter_pid_drop_packet(ctx->streams[idx].ipid); + continue; + } + + dts = gf_filter_pck_get_dts(pck); + cts = gf_filter_pck_get_cts(pck); + + data = (char *) gf_filter_pck_get_data(pck, &data_size); + //TODO: this is a clock signaling, for now just trash .. + if (!data) { + gf_filter_pid_drop_packet(ctx->streams[idx].ipid); + idx--; + continue; + } + if (dts==GF_FILTER_NO_TS) dts = cts; + //get packet with min dts (either a timestamp or a decode order number) + if (min_dts > dts) { + min_dts = dts; + if (cts == GF_FILTER_NO_TS) min_cts = min_dts; + else min_cts = cts; + pck_ref = pck; + } + } + + //start decoder + if (!ctx->decoder_started) { + oh_start(ctx->codec); + ctx->decoder_started = GF_TRUE; + } + + if (nb_eos == ctx->nb_streams) { + while (1) { +#ifdef OPENHEVC_HAS_AVC_BASE + if (ctx->avc_base_id) + got_pic = oh_decode_lhvc(ctx->codec, NULL, NULL, 0, 0, 0, 0); + else +#endif + got_pic = oh_decode(ctx->codec, NULL, 0, 0); + + if (got_pic) { + ohevcdec_flush_picture(ctx); + //we are in direct output mode, wait for frame to be consummed before flushing next frame + if (ctx->frame_out) return GF_OK; + } + else + break; + } + gf_filter_pid_set_eos(ctx->opid); + while (gf_list_count(ctx->src_packets)) { + GF_FilterPacket *pck = gf_list_pop_back(ctx->src_packets); + gf_filter_pck_unref(pck); + } + return GF_EOS; + } + + if (min_cts == GF_FILTER_NO_TS) + return GF_OK; + + //queue reference to source packet props + gf_filter_pck_ref_props(&pck_ref); + gf_list_add(ctx->src_packets, pck_ref); + gf_filter_pck_set_carousel_version(pck_ref, ctx->signal_reconfig ? 1 : 0); + ctx->signal_reconfig = GF_FALSE; + + ctx->dec_frames++; + got_pic = 0; + ctx->reaggregation_size = 0; + nbpck = 0; + + for (idx=0; idxnb_streams; idx++) { + u64 dts, cts; + + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->streams[idx].ipid); + if (!pck) continue; + + dts = gf_filter_pck_get_dts(pck); + cts = gf_filter_pck_get_cts(pck); + if (dts==GF_FILTER_NO_TS) dts = cts; + + if (min_dts != GF_FILTER_NO_TS) { + if (min_dts != dts) continue; + } else if (min_cts != cts) { + continue; + } + + data = (char *) gf_filter_pck_get_data(pck, &data_size); + + if (ctx->streams[idx].inject_hdr) { + if (ctx->inject_buffer_alloc_size < ctx->streams[idx].inject_hdr_size + data_size) { + ctx->inject_buffer_alloc_size = ctx->streams[idx].inject_hdr_size + data_size; + ctx->inject_buffer = gf_realloc(ctx->inject_buffer, ctx->inject_buffer_alloc_size); + } + memcpy(ctx->inject_buffer, ctx->streams[idx].inject_hdr, ctx->streams[idx].inject_hdr_size); + memcpy(ctx->inject_buffer+ctx->streams[idx].inject_hdr_size, data, data_size); + data = ctx->inject_buffer; + data_size += ctx->streams[idx].inject_hdr_size; + + gf_free(ctx->streams[idx].inject_hdr); + ctx->streams[idx].inject_hdr=NULL; + ctx->streams[idx].inject_hdr_size=0; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[HEVC Decoder] Config changed, injecting param sets inband at DTS "LLU" CTS "LLU"\n", dts, cts)); + } + +#ifdef OPENHEVC_HAS_AVC_BASE + if (ctx->avc_base_id) { + if (ctx->avc_base_id == ctx->streams[idx].id) { + got_pic = oh_decode_lhvc(ctx->codec, (u8 *) data, NULL, data_size, 0, cts, 0); + } else if (ctx->cur_layer>1) { + got_pic = oh_decode_lhvc(ctx->codec, (u8*)NULL, (u8 *) data, 0, data_size, 0, cts); + } + } else +#endif + { + if (ctx->nb_streams>1) { + if (ctx->reaggregation_alloc_size < ctx->reaggregation_size + data_size) { + ctx->reaggregation_alloc_size = ctx->reaggregation_size + data_size; + ctx->reaggregation_buffer = gf_realloc(ctx->reaggregation_buffer, sizeof(char)*ctx->reaggregation_alloc_size); + } + memcpy(ctx->reaggregation_buffer + ctx->reaggregation_size, data, sizeof(char)*data_size); + ctx->reaggregation_size += data_size; + } else { + got_pic = oh_decode(ctx->codec, (u8 *) data, data_size, cts); + } + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[HEVC Decoder] PID %s Decode CTS %d - size %d - got pic %d\n", gf_filter_pid_get_name(ctx->streams[idx].ipid), min_cts, data_size, got_pic)); + + if (got_pic>0) + has_pic = GF_TRUE; + + gf_filter_pid_drop_packet(ctx->streams[idx].ipid); + nbpck++; + } + + if (ctx->reaggregation_size) { + got_pic = oh_decode(ctx->codec, (u8 *) ctx->reaggregation_buffer, ctx->reaggregation_size, min_cts); + ctx->reaggregation_size = 0; + if (got_pic) + has_pic = GF_TRUE; + } + + + if (!has_pic) return GF_OK; + + return ohevcdec_flush_picture(ctx); +} + +static GF_Err ohevcdec_initialize(GF_Filter *filter) +{ + GF_SystemRTInfo rti; + GF_OHEVCDecCtx *ctx = (GF_OHEVCDecCtx *) gf_filter_get_udta(filter); + ctx->filter = filter; + if (!ctx->nb_threads) { + if (gf_sys_get_rti(0, &rti, 0) ) { + ctx->nb_threads = (rti.nb_cores>1) ? rti.nb_cores-1 : 1; + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[OpenHEVCDec] Initializing with %d threads\n", ctx->nb_threads)); + } + } + ctx->src_packets = gf_list_new(); + return GF_OK; +} + +static void ohevcdec_finalize(GF_Filter *filter) +{ + GF_OHEVCDecCtx *ctx = (GF_OHEVCDecCtx *) gf_filter_get_udta(filter); + if (ctx->codec) { + if (!ctx->decoder_started) oh_start(ctx->codec); + oh_close(ctx->codec); + } + if (ctx->reaggregation_buffer) gf_free(ctx->reaggregation_buffer); + if (ctx->inject_buffer) gf_free(ctx->inject_buffer); + + while (gf_list_count(ctx->src_packets)) { + GF_FilterPacket *pck = gf_list_pop_back(ctx->src_packets); + gf_filter_pck_unref(pck); + } + gf_list_del(ctx->src_packets); +} + +static const GF_FilterCapability OHEVCDecCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_HEVC), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_LHVC), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED,GF_PROP_PID_TILE_BASE, GF_TRUE), + +#ifdef OPENHEVC_HAS_AVC_BASE + CAP_UINT_PRIORITY(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AVC, 255), +#endif + + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW) +}; + +#define OFFS(_n) #_n, offsetof(GF_OHEVCDecCtx, _n) + +static const GF_FilterArgs OHEVCDecArgs[] = +{ + { OFFS(threading), "set threading mode\n" + "- frameslice: parallel decoding of both frames and slices\n" + "- frame: parallel decoding of frames\n" + "- slice: parallel decoding of slices", GF_PROP_UINT, "frame", "frameslice|frame|slice", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(nb_threads), "set number of threads. If 0, uses number of cores minus one", GF_PROP_UINT, "0", NULL, 0}, + { OFFS(no_copy), "directly dispatch internal decoded frame without copy", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(pack_hfr), "pack 4 consecutive frames in a single output", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(seek_reset), "reset decoder when seeking", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(force_stereo), "force stereo output for multiview (top-bottom only)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(reset_switch), "reset decoder at config change", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + {0} +}; + +GF_FilterRegister OHEVCDecRegister = { + .name = "ohevcdec", + GF_FS_SET_DESCRIPTION("OpenHEVC decoder") + GF_FS_SET_HELP("This filter decodes HEVC and LHVC (HEVC scalable extensions) from one or more PIDs through the OpenHEVC library") + .private_size = sizeof(GF_OHEVCDecCtx), + SETCAPS(OHEVCDecCaps), + .initialize = ohevcdec_initialize, + .finalize = ohevcdec_finalize, + .args = OHEVCDecArgs, + .configure_pid = ohevcdec_configure_pid, + .process = ohevcdec_process, + .process_event = ohevcdec_process_event, + .max_extra_pids = (HEVC_MAX_STREAMS-1), + //by default take over FFMPEG + .priority = 100 +}; + +#endif // defined(GPAC_HAS_OPENHEVC) && !defined(GPAC_DISABLE_AV_PARSERS) + + +#ifndef GPAC_OPENHEVC_STATIC + +GPAC_MODULE_EXPORT +GF_FilterRegister *RegisterFilter(GF_FilterSession *session) +#else +const GF_FilterRegister *ohevcdec_register(GF_FilterSession *session) +#endif + +{ +#if defined(GPAC_HAS_OPENHEVC) && !defined(GPAC_DISABLE_AV_PARSERS) + return &OHEVCDecRegister; +#else + return NULL; +#endif +} diff --git a/src/filters/dec_opensvc.c b/src/filters/dec_opensvc.c new file mode 100644 index 0000000..5f35292 --- /dev/null +++ b/src/filters/dec_opensvc.c @@ -0,0 +1,597 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2010-2017 + * All rights reserved + * + * This file is part of GPAC / OpenSVC Decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include + +#ifdef GPAC_HAS_OPENSVC + +#include +#include +#include + + +#if (defined(WIN32) || defined(_WIN32_WCE)) && !defined(__GNUC__) +# pragma comment(lib, "OpenSVCDecoder") +#endif + +#include + +#define SVC_MAX_STREAMS 3 + +typedef struct +{ + GF_FilterPid *ipid; + u32 cfg_crc; + u32 id; + u32 dep_id; +} GF_SVCStream; + +typedef struct +{ + GF_FilterPid *opid; + GF_SVCStream streams[SVC_MAX_STREAMS]; + u32 nb_streams, active_streams; + u32 width, stride, height, out_size; + GF_Fraction pixel_ar; + + u32 nalu_size_length; + + /*OpenSVC things*/ + void *codec; + int LimitDqId; + int MaxDqId; + int DqIdTable[8]; + int TemporalId; + int TemporalCom; + + int layers[4]; + GF_List *src_packets; +} GF_OSVCDecCtx; + +static GF_Err osvcdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + Bool found=GF_TRUE; + const GF_PropertyValue *p; + u32 i, count, dep_id=0, id=0, cfg_crc=0; + s32 res; + OPENSVCFRAME Picture; + GF_OSVCDecCtx *ctx = (GF_OSVCDecCtx*) gf_filter_get_udta(filter); + + if (is_remove) { + if (ctx->streams[0].ipid == pid) { + memset(ctx->streams, 0, SVC_MAX_STREAMS*sizeof(GF_SVCStream)); + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + ctx->nb_streams = ctx->active_streams = 0; + if (ctx->codec) SVCDecoder_close(ctx->codec); + ctx->codec = NULL; + return GF_OK; + } else { + for (i=0; inb_streams; i++) { + if (ctx->streams[i].ipid == pid) { + ctx->streams[i].ipid = NULL; + ctx->streams[i].cfg_crc = 0; + memmove(&ctx->streams[i], &ctx->streams[i+1], sizeof(GF_SVCStream)*(ctx->nb_streams-1)); + ctx->nb_streams--; + ctx->active_streams--; + return GF_OK; + } + } + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DEPENDENCY_ID); + if (p) dep_id = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + if (!p) p = gf_filter_pid_get_property(pid, GF_PROP_PID_ESID); + if (p) id = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (p && p->value.data.ptr && p->value.data.size) { + cfg_crc = gf_crc_32(p->value.data.ptr, p->value.data.size); + for (i=0; inb_streams; i++) { + if ((ctx->streams[i].ipid == pid) && (ctx->streams[i].cfg_crc == cfg_crc)) return GF_OK; + } + } + + found = GF_FALSE; + for (i=0; iactive_streams; i++) { + if (ctx->streams[i].ipid == pid) { + ctx->streams[i].cfg_crc = cfg_crc; + found = GF_TRUE; + } + } + if (!found) { + if (ctx->nb_streams==SVC_MAX_STREAMS) { + return GF_NOT_SUPPORTED; + } + //insert new pid in order of dependencies + for (i=0; inb_streams; i++) { + + if (!dep_id && !ctx->streams[i].dep_id) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[SVC Decoder] Detected multiple independent base (%s and %s)\n", gf_filter_pid_get_name(pid), gf_filter_pid_get_name(ctx->streams[i].ipid))); + return GF_REQUIRES_NEW_INSTANCE; + } + + if (ctx->streams[i].id == dep_id) { + if (ctx->nb_streams > i+2) + memmove(&ctx->streams[i+1], &ctx->streams[i+2], sizeof(GF_SVCStream) * (ctx->nb_streams-i-1)); + + ctx->streams[i+1].ipid = pid; + ctx->streams[i+1].cfg_crc = cfg_crc; + ctx->streams[i+1].dep_id = dep_id; + ctx->streams[i+1].id = id; + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + found = GF_TRUE; + if (!p) + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + + break; + } + if (ctx->streams[i].dep_id == id) { + if (ctx->nb_streams > i+1) + memmove(&ctx->streams[i+1], &ctx->streams[i], sizeof(GF_SVCStream) * (ctx->nb_streams-i)); + + ctx->streams[i].ipid = pid; + ctx->streams[i].cfg_crc = cfg_crc; + ctx->streams[i].dep_id = dep_id; + ctx->streams[i].id = id; + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + found = GF_TRUE; + break; + } + } + if (!found) { + ctx->streams[ctx->nb_streams].ipid = pid; + ctx->streams[ctx->nb_streams].cfg_crc = cfg_crc; + ctx->streams[ctx->nb_streams].id = id; + ctx->streams[ctx->nb_streams].dep_id = dep_id; + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + } + ctx->nb_streams++; + ctx->active_streams = ctx->nb_streams; + } + + if (p && p->value.data.ptr) { + GF_AVCConfig *cfg = gf_odf_avc_cfg_read(p->value.data.ptr, p->value.data.size); + if (!cfg) return GF_NON_COMPLIANT_BITSTREAM; + if (!dep_id) { + ctx->nalu_size_length = cfg->nal_unit_size; + if (SVCDecoder_init(&ctx->codec) == SVC_STATUS_ERROR) return GF_IO_ERR; + } + + /*decode all NALUs*/ + count = gf_list_count(cfg->sequenceParameterSets); + SetCommandLayer(ctx->layers, 255, 0, &res, 0);//bufindex can be reset without pb + for (i=0; isequenceParameterSets, i); + +#ifndef GPAC_DISABLE_AV_PARSERS + gf_avc_get_sps_info(slc->data, slc->size, &sid, &w, &h, &par_n, &par_d); +#endif + /*by default use the base layer*/ + if (!i) { + if ((ctx->widthheightwidth = w; + ctx->height = h; +#ifndef GPAC_DISABLE_AV_PARSERS + if ( ((s32)par_n>0) && ((s32)par_d>0) ) { + ctx->pixel_ar.num = par_n; + ctx->pixel_ar.den = par_d; + } +#endif + } + } + res = decodeNAL(ctx->codec, (unsigned char *) slc->data, slc->size, &Picture, ctx->layers); + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[SVC Decoder] Error decoding SPS %d\n", res)); + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[SVC Decoder] Attach: SPS id=\"%d\" code=\"%d\" size=\"%d\"\n", slc->id, slc->data[0] & 0x1F, slc->size)); + } + + count = gf_list_count(cfg->pictureParameterSets); + for (i=0; ipictureParameterSets, i); +#ifndef GPAC_DISABLE_AV_PARSERS + u32 sps_id, pps_id; + gf_avc_get_pps_info(slc->data, slc->size, &pps_id, &sps_id); +#endif + res = decodeNAL(ctx->codec, (unsigned char *) slc->data, slc->size, &Picture, ctx->layers); + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[SVC Decoder] Error decoding PPS %d\n", res)); + } +#ifndef GPAC_DISABLE_AV_PARSERS + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[SVC Decoder] Attach: PPS id=\"%d\" code=\"%d\" size=\"%d\" sps_id=\"%d\"\n", pps_id, slc->data[0] & 0x1F, slc->size, sps_id)); +#endif + + } + gf_odf_avc_cfg_del(cfg); + } else { + if (ctx->nalu_size_length) { + return GF_NOT_SUPPORTED; + } + ctx->nalu_size_length = 0; + if (!ctx->codec) { + if (SVCDecoder_init(&ctx->codec) == SVC_STATUS_ERROR) return GF_IO_ERR; + SetCommandLayer(ctx->layers, 255, 0, &res, 0); + } + ctx->pixel_ar = (GF_Fraction){1, 1}; + } + ctx->stride = ctx->width + 32; + ctx->LimitDqId = -1; + ctx->MaxDqId = 0; + ctx->out_size = ctx->stride * ctx->height * 3 / 2; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->streams[0].ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + + if (ctx->width) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->width) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->height) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->stride) ); + if (ctx->pixel_ar.num) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PAR, &PROP_FRAC(ctx->pixel_ar) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, &PROP_UINT(GF_PIXEL_YUV) ); + } + return GF_OK; +} + + +static Bool osvcdec_process_event(GF_Filter *filter, const GF_FilterEvent *fevt) +{ + GF_OSVCDecCtx *ctx = (GF_OSVCDecCtx*) gf_filter_get_udta(filter); + + if (fevt->base.type == GF_FEVT_QUALITY_SWITCH) { + if (fevt->quality_switch.up) { + if (ctx->LimitDqId == -1) + ctx->LimitDqId = ctx->MaxDqId; + if (ctx->LimitDqId < ctx->MaxDqId) + // set layer up (command=1) + UpdateLayer( ctx->DqIdTable, &ctx->LimitDqId, &ctx->TemporalCom, &ctx->TemporalId, ctx->MaxDqId, 1 ); + } else { + if (ctx->LimitDqId > 0) + // set layer down (command=0) + UpdateLayer( ctx->DqIdTable, &ctx->LimitDqId, &ctx->TemporalCom, &ctx->TemporalId, ctx->MaxDqId, 0 ); + } + //todo: we should get the set of pids active and trigger the switch up/down based on that + //rather than not canceling the event + return GF_FALSE; + } + return GF_FALSE; +} + +static GF_Err osvcdec_process(GF_Filter *filter) +{ + s32 got_pic; + u64 min_dts = GF_FILTER_NO_TS; + u64 min_cts = GF_FILTER_NO_TS; + OPENSVCFRAME pic; + u32 i, count, idx, nalu_size, sc_size, nb_eos=0; + u8 *ptr; + u32 data_size; + u8 *data; + Bool has_pic = GF_FALSE; + GF_OSVCDecCtx *ctx = (GF_OSVCDecCtx*) gf_filter_get_udta(filter); + GF_FilterPacket *dst_pck, *src_pck, *pck_ref = NULL; + + for (idx=0; idxactive_streams; idx++) { + u64 dts, cts; + if (!ctx->streams[idx].ipid) { + if (nb_eos) nb_eos++; + continue; + } + + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->streams[idx].ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->streams[idx].ipid)) nb_eos++; + //make sure we do have a packet on the enhancement + else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[OpenSVC] no input packets on running pid %s - postponing decode\n", gf_filter_pid_get_name(ctx->streams[idx].ipid) ) ); + return GF_OK; + } + continue; + } + dts = gf_filter_pck_get_dts(pck); + cts = gf_filter_pck_get_cts(pck); + + data = (char *) gf_filter_pck_get_data(pck, &data_size); + //TODO: this is a clock signaling, for now just trash .. + if (!data) { + gf_filter_pid_drop_packet(ctx->streams[idx].ipid); + idx--; + continue; + } + if (dts==GF_FILTER_NO_TS) dts = cts; + //get packet with min dts (either a timestamp or a decode order number) + if (min_dts > dts) { + min_dts = dts; + if (cts == GF_FILTER_NO_TS) min_cts = min_dts; + else min_cts = cts; + pck_ref = pck; + } + } + if (nb_eos == ctx->active_streams) { + gf_filter_pid_set_eos(ctx->opid); + return GF_OK; + } + if (min_cts == GF_FILTER_NO_TS) return GF_OK; + + //append in cts order since we get output frames in cts order + gf_filter_pck_ref_props(&pck_ref); + count = gf_list_count(ctx->src_packets); + src_pck = NULL; + for (i=0; isrc_packets, i); + acts = gf_filter_pck_get_cts(src_pck); + if (acts==min_cts) { + gf_filter_pck_unref(pck_ref); + break; + } + if (acts>min_cts) { + gf_list_insert(ctx->src_packets, pck_ref, i); + break; + } + src_pck = NULL; + } + if (!src_pck) + gf_list_add(ctx->src_packets, pck_ref); + + pic.Width = pic.Height = 0; + for (idx=0; idxnb_streams; idx++) { + u64 dts, cts; +#ifndef GPAC_DISABLE_AV_PARSERS +#ifndef GPAC_DISABLE_LOG + u32 sps_id, pps_id; +#endif +#endif + u32 maxDqIdInAU; + if (!ctx->streams[idx].ipid) continue; + + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->streams[idx].ipid); + if (!pck) continue; + + if (idx>=ctx->active_streams) { + gf_filter_pid_drop_packet(ctx->streams[idx].ipid); + continue; + } + + dts = gf_filter_pck_get_dts(pck); + cts = gf_filter_pck_get_cts(pck); + if (dts==GF_FILTER_NO_TS) dts = cts; + + if (min_dts != GF_FILTER_NO_TS) { + if (min_dts != dts) continue; + } else if (min_cts != cts) { + continue; + } + + data = (char *) gf_filter_pck_get_data(pck, &data_size); + + maxDqIdInAU = GetDqIdMax((unsigned char *) data, data_size, ctx->nalu_size_length, ctx->DqIdTable, ctx->nalu_size_length ? 1 : 0); + if (ctx->MaxDqId <= (s32) maxDqIdInAU) { + ctx->MaxDqId = (s32) maxDqIdInAU; + } + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[OpenSVC] decode from stream %s - DTS "LLU" PTS "LLU" size %d - max DQID %d\n", gf_filter_pid_get_name(ctx->streams[idx].ipid), dts, cts, data_size, maxDqIdInAU) ); + + + //we are asked to use a lower quality + if ((ctx->LimitDqId>=0) && (ctx->LimitDqId < (s32) maxDqIdInAU)) + maxDqIdInAU = ctx->LimitDqId; + + /*decode only current layer*/ + SetCommandLayer(ctx->layers, ctx->MaxDqId, maxDqIdInAU, &ctx->TemporalCom, ctx->TemporalId); + + got_pic = 0; + nalu_size = 0; + ptr = (u8 *) data; + sc_size = 0; + + if (!ctx->nalu_size_length) { +#ifndef GPAC_DISABLE_AV_PARSERS + u32 size = gf_media_nalu_next_start_code((u8 *) data, data_size, &sc_size); + if (sc_size) { + ptr += size+sc_size; + assert(data_size >= size+sc_size); + data_size -= size+sc_size; + } else +#endif + { + /*no annex-B start-code found, discard */ + gf_filter_pid_drop_packet(ctx->streams[idx].ipid); + idx--; + continue; + } + } + + while (data_size) { + int res; + if (ctx->nalu_size_length) { + for (i=0; inalu_size_length; i++) { + nalu_size = (nalu_size<<8) + ptr[i]; + } + ptr += ctx->nalu_size_length; + } else { +#ifndef GPAC_DISABLE_AV_PARSERS + nalu_size = gf_media_nalu_next_start_code(ptr, data_size, &sc_size); +#else + nalu_size = data_size; + sc_size=0; +#endif + + } + +#if !defined(GPAC_DISABLE_LOG) && !defined(GPAC_DISABLE_AV_PARSERS) + if (gf_log_tool_level_on(GF_LOG_CODEC, GF_LOG_DEBUG)) { + switch (ptr[0] & 0x1F) { + case GF_AVC_NALU_SEQ_PARAM: + case GF_AVC_NALU_SVC_SUBSEQ_PARAM: + gf_avc_get_sps_info((char *)ptr, nalu_size, &sps_id, NULL, NULL, NULL, NULL); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[SVCDec] PID %s: SPS id=\"%d\" code=\"%d\" size=\"%d\"\n", gf_filter_pid_get_name(ctx->streams[idx].ipid), sps_id, ptr[0] & 0x1F, nalu_size)); + break; + case GF_AVC_NALU_PIC_PARAM: + gf_avc_get_pps_info((char *)ptr, nalu_size, &pps_id, &sps_id); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[SVC Decoder] PID %s: PPS id=\"%d\" code=\"%d\" size=\"%d\" sps_id=\"%d\"\n", gf_filter_pid_get_name(ctx->streams[idx].ipid), pps_id, ptr[0] & 0x1F, nalu_size, sps_id)); + break; + case GF_AVC_NALU_VDRD: + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[SVC Decoder] PID %s: VDRD found\n", gf_filter_pid_get_name(ctx->streams[idx].ipid))); + break; + default: + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[SVC Decoder] PID %s: NALU code=\"%d\" size=\"%d\"\n", gf_filter_pid_get_name(ctx->streams[idx].ipid), ptr[0] & 0x1F, nalu_size)); + } + } +#endif + + if (!got_pic) { + res = decodeNAL(ctx->codec, ptr, nalu_size, &pic, ctx->layers); + if (res>0) got_pic = res; + } else { + res = decodeNAL(ctx->codec, ptr, nalu_size, &pic, ctx->layers); + } + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[SVC Decoder] Error decoding NAL: %d\n", res)); + } + + ptr += nalu_size; + if (ctx->nalu_size_length) { + if (data_size < nalu_size + ctx->nalu_size_length) break; + data_size -= nalu_size + ctx->nalu_size_length; + } else { + if (!sc_size || (data_size < nalu_size + sc_size)) break; + data_size -= nalu_size + sc_size; + ptr += sc_size; + } + } + gf_filter_pid_drop_packet(ctx->streams[idx].ipid); + + if (got_pic) has_pic = GF_TRUE; + } + if (!has_pic) return GF_OK; + + if ((pic.Width != ctx->width) || (pic.Height!=ctx->height)) { + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[SVC Decoder] Resizing from %dx%d to %dx%d\n", ctx->width, ctx->height, pic.Width, pic.Height )); + ctx->width = pic.Width; + ctx->stride = pic.Width + 32; + ctx->height = pic.Height; + ctx->out_size = ctx->stride * ctx->height * 3 / 2; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->width) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->height) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->stride) ); + if (ctx->pixel_ar.num) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PAR, &PROP_FRAC(ctx->pixel_ar) ); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, &PROP_UINT(GF_PIXEL_YUV) ); + } + + src_pck = gf_list_pop_front(ctx->src_packets); + + if (src_pck && gf_filter_pck_get_seek_flag(src_pck)) { + gf_filter_pck_unref(src_pck); + return GF_OK; + } + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->out_size, &data); + memcpy(data, pic.pY[0], ctx->stride*ctx->height); + memcpy(data + ctx->stride * ctx->height, pic.pU[0], ctx->stride*ctx->height/4); + memcpy(data + 5*ctx->stride * ctx->height/4, pic.pV[0], ctx->stride*ctx->height/4); + + if (src_pck) { + gf_filter_pck_merge_properties(src_pck, dst_pck); + gf_filter_pck_unref(src_pck); + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[OpenSVC] decoded out frame PTS "LLU"\n", gf_filter_pck_get_cts(dst_pck) )); + gf_filter_pck_send(dst_pck); + return GF_OK; +} + +static GF_Err osvcdec_initialize(GF_Filter *filter) +{ + GF_OSVCDecCtx *ctx = (GF_OSVCDecCtx*) gf_filter_get_udta(filter); + ctx->src_packets = gf_list_new(); + return GF_OK; +} + +static void osvcdec_finalize(GF_Filter *filter) +{ + GF_OSVCDecCtx *ctx = (GF_OSVCDecCtx*) gf_filter_get_udta(filter); + if (ctx->codec) SVCDecoder_close(ctx->codec); + while (gf_list_count(ctx->src_packets)) { + GF_FilterPacket *pck = gf_list_pop_back(ctx->src_packets); + gf_filter_pck_unref(pck); + } + gf_list_del(ctx->src_packets); +} + +static const GF_FilterCapability OSVCDecCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AVC), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_SVC), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +GF_FilterRegister OSVCDecRegister = { + .name = "osvcdec", + GF_FS_SET_DESCRIPTION("OpenSVC decoder") + GF_FS_SET_HELP("This filter decodes scalable AVC|H264 streams through OpenSVC library.") + .private_size = sizeof(GF_OSVCDecCtx), + SETCAPS(OSVCDecCaps), + .initialize = osvcdec_initialize, + .finalize = osvcdec_finalize, + .configure_pid = osvcdec_configure_pid, + .process = osvcdec_process, + .process_event = osvcdec_process_event, + .max_extra_pids = (SVC_MAX_STREAMS-1), + .priority = 255 +}; + +#endif //GPAC_HAS_OPENSVC + +const GF_FilterRegister *osvcdec_register(GF_FilterSession *session) +{ +#ifdef GPAC_HAS_OPENSVC + return &OSVCDecRegister; +#else + return NULL; +#endif +} diff --git a/src/filters/dec_theora.c b/src/filters/dec_theora.c new file mode 100644 index 0000000..d084af7 --- /dev/null +++ b/src/filters/dec_theora.c @@ -0,0 +1,313 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / XIPH Theora decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include +#include + +#ifdef GPAC_HAS_THEORA + +#include + +#if !defined(__GNUC__) +# if defined(_WIN32_WCE) || defined (WIN32) +# pragma comment(lib, "libtheora_static") +# endif +#endif + +typedef struct +{ + GF_FilterPid *ipid, *opid; + theora_info ti, the_ti; + theora_state td; + theora_comment tc; + ogg_packet op; + + u32 cfg_crc; + + GF_List *src_packets; + u64 next_cts; + Bool has_reconfigured; +} GF_TheoraDecCtx; + +static GF_Err theoradec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + ogg_packet oggpacket; + GF_BitStream *bs; + GF_TheoraDecCtx *ctx = gf_filter_get_udta(filter); + + + if (is_remove) { + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + ctx->ipid = NULL; + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (p && p->value.data.ptr && p->value.data.size) { + u32 ex_crc; + if (strncmp(&p->value.data.ptr[3], "theora", 6)) return GF_NON_COMPLIANT_BITSTREAM; + ex_crc = gf_crc_32(p->value.data.ptr, p->value.data.size); + if (ctx->cfg_crc == ex_crc) return GF_OK; + ctx->cfg_crc = ex_crc; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[Theora] Reconfiguring without DSI not yet supported\n")); + return GF_NOT_SUPPORTED; + } + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, pid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + + if (ctx->ipid) { + theora_clear(&ctx->td); + theora_info_clear(&ctx->ti); + theora_comment_clear(&ctx->tc); + } + ctx->ipid = pid; + gf_filter_pid_set_framing_mode(ctx->ipid, GF_TRUE); + + oggpacket.granulepos = -1; + oggpacket.b_o_s = 1; + oggpacket.e_o_s = 0; + oggpacket.packetno = 0; + + theora_info_init(&ctx->ti); + theora_comment_init(&ctx->tc); + + bs = gf_bs_new(p->value.data.ptr, p->value.data.size, GF_BITSTREAM_READ); + while (gf_bs_available(bs)) { + GF_Err e = GF_OK; + oggpacket.bytes = gf_bs_read_u16(bs); + oggpacket.packet = gf_malloc(sizeof(char) * oggpacket.bytes); + gf_bs_read_data(bs, oggpacket.packet, oggpacket.bytes); + if (theora_decode_header(&ctx->ti, &ctx->tc, &oggpacket) < 0 ) { + e = GF_NON_COMPLIANT_BITSTREAM; + } + gf_free(oggpacket.packet); + if (e) { + gf_bs_del(bs); + return e; + } + } + theora_decode_init(&ctx->td, &ctx->ti); + gf_bs_del(bs); + + return GF_OK; +} + +static GF_Err theoradec_process(GF_Filter *filter) +{ + ogg_packet op; + yuv_buffer yuv; + u32 i, count; + u8 *buffer; + u8 *pYO, *pUO, *pVO; + u8 *pYD, *pUD, *pVD; + GF_TheoraDecCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck, *src_pck, *pck_ref; + Bool is_seek; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck && !gf_filter_pid_is_eos(ctx->ipid)) + return GF_OK; + + memset(&yuv, 0, sizeof(yuv_buffer)); + memset(&op, 0, sizeof(ogg_packet)); + op.granulepos = -1; + + if (pck) { + u64 cts = gf_filter_pck_get_cts(pck); + u32 psize; + op.packet = (char *) gf_filter_pck_get_data(pck, &psize); + op.bytes = psize; + + + //append in cts order since we get output frames in cts order + pck_ref = pck; + gf_filter_pck_ref_props(&pck_ref); + count = gf_list_count(ctx->src_packets); + src_pck = NULL; + for (i=0; isrc_packets, i); + acts = gf_filter_pck_get_cts(src_pck); + if (acts==cts) { + gf_filter_pck_unref(pck_ref); + break; + } + if (acts>cts) { + gf_list_insert(ctx->src_packets, pck_ref, i); + break; + } + src_pck = NULL; + } + if (!src_pck) + gf_list_add(ctx->src_packets, pck_ref); + + } else { + //weird bug in theora dec: even though we feed empty packets, we still keep getting frames ! + if (!gf_list_count(ctx->src_packets)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + op.packet = NULL; + op.bytes = 0; + } + + if (theora_decode_packetin(&ctx->td, &op) != 0) { + src_pck = gf_list_pop_front(ctx->src_packets); + gf_filter_pck_unref(src_pck); + if (pck) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NON_COMPLIANT_BITSTREAM; + } + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + //no frame + if (theora_decode_YUVout(&ctx->td, &yuv) != 0) { + if (pck) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + + if (memcmp(&ctx->ti, &ctx->the_ti, sizeof(theora_info))) { + memcpy(&ctx->the_ti, &ctx->ti, sizeof(theora_info)); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->ti.width)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->ti.height)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->ti.width)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FPS, &PROP_FRAC_INT(ctx->ti.fps_numerator, ctx->ti.fps_denominator) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, &PROP_UINT(GF_PIXEL_YUV) ); + } + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->ti.width*ctx->ti.height * 3 / 2, &buffer); + + pYO = yuv.y; + pUO = yuv.u; + pVO = yuv.v; + pYD = buffer; + pUD = buffer + ctx->ti.width * ctx->ti.height; + pVD = buffer + 5 * ctx->ti.width * ctx->ti.height / 4; + + for (i=0; i<(u32)yuv.y_height; i++) { + memcpy(pYD, pYO, sizeof(char) * yuv.y_width); + pYD += ctx->ti.width; + pYO += yuv.y_stride; + if (i%2) continue; + + memcpy(pUD, pUO, sizeof(char) * yuv.uv_width); + memcpy(pVD, pVO, sizeof(char) * yuv.uv_width); + pUD += ctx->ti.width/2; + pVD += ctx->ti.width/2; + pUO += yuv.uv_stride; + pVO += yuv.uv_stride; + } + + src_pck = gf_list_pop_front(ctx->src_packets); + if (src_pck) { + gf_filter_pck_merge_properties(src_pck, dst_pck); + is_seek = gf_filter_pck_get_seek_flag(src_pck); + ctx->next_cts = gf_filter_pck_get_cts(src_pck); + ctx->next_cts += gf_filter_pck_get_duration(src_pck); + gf_filter_pck_unref(src_pck); + } else { + is_seek = 0; + gf_filter_pck_set_cts(dst_pck, ctx->next_cts); + } + + if (!pck || !is_seek ) + gf_filter_pck_send(dst_pck); + else + gf_filter_pck_discard(dst_pck); + + if (pck) gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + +static GF_Err theoradec_initialize(GF_Filter *filter) +{ + GF_TheoraDecCtx *ctx = gf_filter_get_udta(filter); + ctx->src_packets = gf_list_new(); + return GF_OK; +} +static void theoradec_finalize(GF_Filter *filter) +{ + GF_TheoraDecCtx *ctx = gf_filter_get_udta(filter); + + theora_clear(&ctx->td); + theora_info_clear(&ctx->ti); + theora_comment_clear(&ctx->tc); + + while (gf_list_count(ctx->src_packets)) { + GF_FilterPacket *pck = gf_list_pop_back(ctx->src_packets); + gf_filter_pck_unref(pck); + } + gf_list_del(ctx->src_packets); +} + +static const GF_FilterCapability TheoraDecCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_THEORA), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +GF_FilterRegister TheoraDecRegister = { + .name = "theoradec", + GF_FS_SET_DESCRIPTION("Theora decoder") + GF_FS_SET_HELP("This filter decodes Theora streams through libtheora library.") + .private_size = sizeof(GF_TheoraDecCtx), + .priority = 1, + SETCAPS(TheoraDecCaps), + .initialize = theoradec_initialize, + .finalize = theoradec_finalize, + .configure_pid = theoradec_configure_pid, + .process = theoradec_process, +}; + +#endif + +const GF_FilterRegister *theoradec_register(GF_FilterSession *session) +{ +#ifdef GPAC_HAS_THEORA + return &TheoraDecRegister; +#else + return NULL; +#endif +} + diff --git a/src/filters/dec_ttml.c b/src/filters/dec_ttml.c new file mode 100644 index 0000000..a299dae --- /dev/null +++ b/src/filters/dec_ttml.c @@ -0,0 +1,536 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2020 + * All rights reserved + * + * This file is part of GPAC / TTML decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include + +#if !defined(GPAC_DISABLE_SVG) && defined(GPAC_HAS_QJS) + +#include +#include +#include +#include + +#include "../scenegraph/qjs_common.h" + +typedef struct +{ + //opts + char *script; + char *color, *font; + Float fontSize, lineSpacing, txtx, txty; + u32 txtw, txth, valign; + + GF_FilterPid *ipid, *opid; + + /* Boolean indicating the internal graph is registered with the compositor */ + Bool graph_registered; + /* Boolean indicating the stream is playing */ + Bool is_playing; + + /* Parent scene to which this TTML stream is linked */ + GF_Scene *scene; + /* root object manager of parent scene (used to get clock info)*/ + GF_ObjectManager *odm; + + /* Scene graph for the subtitle content */ + GF_SceneGraph *scenegraph; + + Bool update_args; + + GF_BitStream *subs_bs; + s32 notify_clock; + +} GF_TTMLDec; + +void ttmldec_update_size_info(GF_TTMLDec *ctx) +{ + u32 w, h; + GF_FieldInfo info; + Bool has_size; + char szVB[100]; + GF_Node *root = gf_sg_get_root_node(ctx->scenegraph); + if (!root) return; + + has_size = gf_sg_get_scene_size_info(ctx->scene->graph, &w, &h); + /*no size info is given in main scene, override by associated video size if any, or by text track size*/ + if (!has_size) { + const GF_PropertyValue *p; + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_WIDTH); + if (p) w = p->value.uint; + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_HEIGHT); + if (p) h = p->value.uint; + + if (!w) w = ctx->txtw; + if (!h) h = ctx->txth; + + gf_sg_set_scene_size_info(ctx->scenegraph, w, h, GF_TRUE); + gf_scene_force_size(ctx->scene, w, h); + } + + gf_sg_set_scene_size_info(ctx->scenegraph, w, h, GF_TRUE); + + sprintf(szVB, "0 0 %d %d", w, h); + gf_node_get_attribute_by_tag(root, TAG_SVG_ATT_viewBox, GF_TRUE, GF_FALSE, &info); + gf_svg_parse_attribute(root, &info, szVB, 0); + + /*apply*/ + gf_sg_set_scene_size_info(ctx->scenegraph, w, h, GF_TRUE); + + sprintf(szVB, "0 0 %d %d", w, h); + gf_node_get_attribute_by_tag(root, TAG_SVG_ATT_viewBox, GF_TRUE, GF_FALSE, &info); + gf_svg_parse_attribute(root, &info, szVB, 0); + +} + +void ttmldec_setup_scene(GF_TTMLDec *ctx) +{ + GF_Node *n, *root; + GF_FieldInfo info; + + ctx->scenegraph = gf_sg_new_subscene(ctx->scene->graph); + + if (!ctx->scenegraph) return; + gf_sg_add_namespace(ctx->scenegraph, "http://www.w3.org/2000/svg", NULL); + gf_sg_add_namespace(ctx->scenegraph, "http://www.w3.org/1999/xlink", "xlink"); + gf_sg_add_namespace(ctx->scenegraph, "http://www.w3.org/2001/xml-events", "ev"); + gf_sg_set_scene_size_info(ctx->scenegraph, 800, 600, GF_TRUE); + + /* modify the scene with an Inline/Animation pointing to the TTML Renderer */ + n = root = gf_node_new(ctx->scenegraph, TAG_SVG_svg); + gf_node_register(root, NULL); + gf_sg_set_root_node(ctx->scenegraph, root); + gf_node_get_attribute_by_name(n, "xmlns", 0, GF_TRUE, GF_FALSE, &info); + gf_svg_parse_attribute(n, &info, "http://www.w3.org/2000/svg", 0); + ttmldec_update_size_info(ctx); + gf_node_init(n); + + n = gf_node_new(ctx->scenegraph, TAG_SVG_script); + gf_node_register(n, root); + gf_node_list_add_child(&((GF_ParentNode *)root)->children, n); + + gf_node_get_attribute_by_tag(n, TAG_XLINK_ATT_href, GF_TRUE, GF_FALSE, &info); + if (strstr(ctx->script, ":\\")) { + gf_svg_parse_attribute(n, &info, (char *) ctx->script, 0); + } else { + char szPath[GF_MAX_PATH]; + strcpy(szPath, "file://"); + strcat(szPath, ctx->script); + gf_svg_parse_attribute(n, &info, (char *) szPath, 0); + } + + gf_node_init(n); + +} + +static GF_Err ttmldec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_TTMLDec *ctx = (GF_TTMLDec *)gf_filter_get_udta(filter); + + if (is_remove) { + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + ctx->opid = ctx->ipid = NULL; + return GF_OK; + } + //TODO: we need to cleanup cap checking upon reconfigure + if (ctx->ipid && !gf_filter_pid_check_caps(pid)) return GF_NOT_SUPPORTED; + assert(!ctx->ipid || (ctx->ipid == pid)); + + ctx->ipid = pid; + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_TEXT)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW)); + + return GF_OK; +} + + +static void ttmldec_toggle_display(GF_TTMLDec *ctx) +{ + if (!ctx->scenegraph) return; + + if (ctx->is_playing) { + if (!ctx->graph_registered) { + ttmldec_update_size_info(ctx); + gf_scene_register_extra_graph(ctx->scene, ctx->scenegraph, GF_FALSE); + ctx->graph_registered = GF_TRUE; + } + } else { + if (ctx->graph_registered) { + gf_scene_register_extra_graph(ctx->scene, ctx->scenegraph, GF_TRUE); + ctx->graph_registered = GF_FALSE; + } + } +} + +static Bool ttmldec_process_event(GF_Filter *filter, const GF_FilterEvent *com) +{ + GF_TTMLDec *ctx = gf_filter_get_udta(filter); + + //check for scene attach + switch (com->base.type) { + case GF_FEVT_ATTACH_SCENE: + break; + case GF_FEVT_RESET_SCENE: + if (ctx->opid != com->attach_scene.on_pid) return GF_TRUE; + ctx->is_playing = GF_FALSE; + ttmldec_toggle_display(ctx); + + gf_sg_del(ctx->scenegraph); + ctx->scenegraph = NULL; + ctx->scene = NULL; + return GF_TRUE; + case GF_FEVT_PLAY: + ctx->is_playing = GF_TRUE; + ttmldec_toggle_display(ctx); + return GF_FALSE; + case GF_FEVT_STOP: + ctx->is_playing = GF_FALSE; + ttmldec_toggle_display(ctx); + return GF_FALSE; + default: + return GF_FALSE; + } + if (ctx->opid != com->attach_scene.on_pid) return GF_TRUE; + + ctx->odm = com->attach_scene.object_manager; + ctx->scene = ctx->odm->subscene ? ctx->odm->subscene : ctx->odm->parentscene; + + /*timedtext cannot be a root scene object*/ + if (ctx->odm->subscene) { + ctx->odm = NULL; + ctx->scene = NULL; + } else { + ttmldec_setup_scene(ctx); + ttmldec_toggle_display(ctx); + } + return GF_TRUE; +} + +void js_dump_error(JSContext *ctx); + +JSContext *ttmldec_get_js_context(GF_TTMLDec *ctx) +{ + GF_SceneGraph *sg = ctx->scenegraph->RootNode->sgprivate->scenegraph; + JSContext *svg_script_get_context(GF_SceneGraph *sg); + JSContext *c = svg_script_get_context(sg); + + if (ctx->update_args) { + JSValue global = JS_GetGlobalObject(c); + + JS_SetPropertyStr(c, global, "fontSize", JS_NewFloat64(c, ctx->fontSize)); + JS_SetPropertyStr(c, global, "fontFamily", JS_NewString(c, ctx->font)); + JS_SetPropertyStr(c, global, "textColor", JS_NewString(c, ctx->color)); + JS_SetPropertyStr(c, global, "lineSpaceFactor", JS_NewFloat64(c, ctx->lineSpacing)); + JS_SetPropertyStr(c, global, "xOffset", JS_NewFloat64(c, ctx->txtx)); + JS_SetPropertyStr(c, global, "yOffset", JS_NewFloat64(c, ctx->txty)); + JS_SetPropertyStr(c, global, "v_align", JS_NewInt32(c, ctx->valign)); + + JS_FreeValue(c, global); + ctx->update_args = GF_FALSE; + } + return c; +} + + + +static GF_Err ttmldec_process(GF_Filter *filter) +{ + GF_Err e = GF_OK; + const GF_PropertyValue *subs; + GF_FilterPacket *pck; + const u8 *pck_data; + char *pck_alloc=NULL, *ttml_doc; + u64 cts; + u32 timescale, obj_time; + u32 pck_size; + JSValue fun_val; + JSValue global; + JSContext *c; + GF_TTMLDec *ctx = (GF_TTMLDec *) gf_filter_get_udta(filter); + + if (!ctx->scene) { + if (ctx->is_playing) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + c = ttmldec_get_js_context(ctx); + if (!c) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_SERVICE_ERROR; + } + + //object clock shall be valid + if (!ctx->odm->ck) + return GF_OK; + + cts = gf_filter_pck_get_cts( pck ); + timescale = gf_filter_pck_get_timescale(pck); + + gf_odm_check_buffering(ctx->odm, ctx->ipid); + + //we still process any frame before our clock time even when buffering + obj_time = gf_clock_time(ctx->odm->ck); + + if (ctx->notify_clock>0) { + gf_js_lock(c, GF_TRUE); + global = JS_GetGlobalObject(c); + fun_val = JS_GetPropertyStr(c, global, "on_ttml_clock"); + if (JS_IsFunction(c, fun_val) ) { + JSValue ret, argv[1]; + argv[0] = JS_NewInt64(c, obj_time); + + ret = JS_Call(c, fun_val, global, 1, argv); + if (JS_IsException(ret)) { + js_dump_error(c); + e = GF_BAD_PARAM; + } + + if (JS_IsNumber(ret)) { + JS_ToInt32(c, &ctx->notify_clock, ret); + } else { + ctx->notify_clock = 0; + } + + JS_FreeValue(c, ret); + JS_FreeValue(c, argv[0]); + } + JS_FreeValue(c, global); + JS_FreeValue(c, fun_val); + gf_js_lock(c, GF_FALSE); + if (e) return e; + } + + if (cts * 1000 > obj_time * timescale) { + gf_sc_sys_frame_pending(ctx->scene->compositor, ((Double) cts / timescale), obj_time, filter); + return GF_OK; + } + pck_data = gf_filter_pck_get_data(pck, &pck_size); + subs = gf_filter_pck_get_property(pck, GF_PROP_PCK_SUBS); + + ttml_doc = (char *) pck_data; + if (subs) { + Bool first = GF_TRUE; + gf_bs_reassign_buffer(ctx->subs_bs, subs->value.data.ptr, subs->value.data.size); + while (gf_bs_available(ctx->subs_bs)) { + /*u32 flags = */gf_bs_read_u32(ctx->subs_bs); + u32 subs_size = gf_bs_read_u32(ctx->subs_bs); + /*u32 reserved = */gf_bs_read_u32(ctx->subs_bs); + /*u8 priority = */gf_bs_read_u8(ctx->subs_bs); + /*u8 discardable = */gf_bs_read_u8(ctx->subs_bs); + + if (first) { + first = GF_FALSE; + if (subs_size>pck_size) { + gf_filter_pid_drop_packet(ctx->ipid); + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[TTMLDec] Invalid subsample size %d for packet size %d\n", subs_size, pck_size)); + return GF_NON_COMPLIANT_BITSTREAM; + } + pck_alloc = gf_malloc(sizeof(char)*subs_size+2); + memcpy(pck_alloc, pck_data, sizeof(char)*subs_size); + pck_alloc[subs_size] = 0; + pck_alloc[subs_size+1] = 0; + ttml_doc = pck_alloc; + } + } + } + + GF_DOMParser *dom = gf_xml_dom_new(); + gf_xml_dom_parse_string(dom, ttml_doc); + + + gf_js_lock(c, GF_TRUE); + global = JS_GetGlobalObject(c); + fun_val = JS_GetPropertyStr(c, global, "on_ttml_sample"); + if (!JS_IsFunction(c, fun_val) ) { + e = GF_BAD_PARAM; + } else { + JSValue ret, argv[3]; + Double scene_ck; + GF_SceneGraph *doc; + + doc = gf_sg_new(); + doc->reference_count = 1; + e = gf_sg_init_from_xml_node(doc, gf_xml_dom_get_root(dom)); + argv[0] = dom_document_construct_external(c, doc); + + argv[1] = JS_NewInt64(c, obj_time); + scene_ck = gf_filter_pck_get_duration(pck); + scene_ck /= timescale; + argv[2] = JS_NewFloat64(c, scene_ck); + + ret = JS_Call(c, fun_val, global, 3, argv); + if (JS_IsException(ret)) { + js_dump_error(c); + e = GF_BAD_PARAM; + } + if (JS_IsNumber(ret)) { + JS_ToInt32(c, &ctx->notify_clock, ret); + } else { + ctx->notify_clock = 0; + } + + doc->reference_count --; + JS_FreeValue(c, ret); + JS_FreeValue(c, argv[0]); + JS_FreeValue(c, argv[1]); + JS_FreeValue(c, argv[2]); + } + JS_FreeValue(c, global); + JS_FreeValue(c, fun_val); + + gf_js_lock(c, GF_FALSE); + + + gf_xml_dom_del(dom); + + if (pck_alloc) gf_free(pck_alloc); + gf_filter_pid_drop_packet(ctx->ipid); + + if (ctx->notify_clock>0) { + if (ctx->scene && ctx->scene->compositor->player) + gf_filter_ask_rt_reschedule(filter, ctx->notify_clock*1000); + else + gf_filter_post_process_task(filter); + } + return e; +} + +static GF_Err ttmldec_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *new_val) +{ + GF_TTMLDec *ctx = gf_filter_get_udta(filter); + ctx->update_args = GF_TRUE; + return GF_OK; +} + +static GF_Err ttmldec_initialize(GF_Filter *filter) +{ + GF_TTMLDec *ctx = gf_filter_get_udta(filter); + + if (!ctx->script) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[TTMLDec] TTML JS renderer script not set\n")); + return GF_BAD_PARAM; + } else if (!gf_file_exists(ctx->script)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[TTMLDec] TTML JS renderer script %s not found\n", ctx->script)); + return GF_URL_ERROR; + } + ctx->subs_bs = gf_bs_new((u8 *)ctx, 1, GF_BITSTREAM_READ); + + ctx->update_args = GF_TRUE; +#ifdef GPAC_ENABLE_COVERAGE + ttmldec_update_arg(filter, NULL, NULL); +#endif + return GF_OK; +} + +void ttmldec_finalize(GF_Filter *filter) +{ + GF_TTMLDec *ctx = (GF_TTMLDec *) gf_filter_get_udta(filter); + + if (ctx->scenegraph) { + ctx->is_playing = GF_FALSE; + ttmldec_toggle_display(ctx); + gf_sg_del(ctx->scenegraph); + } + gf_bs_del(ctx->subs_bs); +} + +#define OFFS(_n) #_n, offsetof(GF_TTMLDec, _n) +static const GF_FilterArgs TTMLDecArgs[] = +{ + { OFFS(script), "location of TTML SVG JS renderer", GF_PROP_STRING, "$GSHARE/scripts/ttml-renderer.js", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(font), "font to use", GF_PROP_STRING, "SANS", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(fontSize), "font size to use", GF_PROP_FLOAT, "20", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(color), "color to use", GF_PROP_STRING, "white", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(valign), "vertical alignment\n" + "- bottom: align text at bottom of text area\n" + "- center: align text at center of text area\n" + "- top: align text at top of text area", GF_PROP_UINT, "bottom", "bottom|center|top", GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(lineSpacing), "line spacing as scaling factor to font size", GF_PROP_FLOAT, "1.0", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(txtx), "horizontal offset", GF_PROP_FLOAT, "5", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(txty), "vertical offset", GF_PROP_FLOAT, "5", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(txtw), "default width in standalone rendering", GF_PROP_UINT, "400", NULL, 0}, + { OFFS(txth), "default height in standalone rendering", GF_PROP_UINT, "200", NULL, 0}, + {0} +}; + +static const GF_FilterCapability TTMLDecCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_SUBS_XML), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +GF_FilterRegister TTMLDecRegister = { + .name = "ttmldec", + GF_FS_SET_DESCRIPTION("TTML decoder") + GF_FS_SET_HELP("This filter decodes TTML streams into a SVG scene graph of the compositor filter.\n" + "The scene graph creation is done through JavaScript.\n" + "The filter options are used to override the JS global variables of the TTML renderer.") + .private_size = sizeof(GF_TTMLDec), + .flags = GF_FS_REG_MAIN_THREAD, + .args = TTMLDecArgs, + .priority = 1, + SETCAPS(TTMLDecCaps), + .initialize = ttmldec_initialize, + .finalize = ttmldec_finalize, + .process = ttmldec_process, + .configure_pid = ttmldec_configure_pid, + .process_event = ttmldec_process_event, + .update_arg = ttmldec_update_arg +}; + +#endif + +const GF_FilterRegister *ttmldec_register(GF_FilterSession *session) +{ +#if !defined(GPAC_DISABLE_SVG) && defined(GPAC_HAS_QJS) + return &TTMLDecRegister; +#else + return NULL; +#endif +} + + diff --git a/modules/timedtext/timedtext_dec.c b/src/filters/dec_ttxt.c similarity index 51% rename from modules/timedtext/timedtext_dec.c rename to src/filters/dec_ttxt.c index 1a3d2df..336f474 100644 --- a/modules/timedtext/timedtext_dec.c +++ b/src/filters/dec_ttxt.c @@ -2,10 +2,10 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2017 * All rights reserved * - * This file is part of GPAC / 3GPP/MPEG4 timed text module + * This file is part of GPAC / 3GPP/MPEG4 text renderer filter * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -23,11 +23,14 @@ * */ -#include +#include +#include + #include #include -#include #include +#include + #if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_ISOM) @@ -69,13 +72,23 @@ typedef struct { - GF_Scene *inlineScene; - GF_Terminal *app; - u32 nb_streams; + //opts + Bool texture, outline; + u32 txtw, txth; + + GF_FilterPid *ipid, *opid; + + GF_ObjectManager *odm; + GF_Scene *scene; + u32 dsi_crc; + Bool is_tx3g; + Bool is_playing, graph_registered, is_eos; + GF_TextConfig *cfg; + GF_BitStream *bs_r; - GF_SceneGraph *sg; + GF_SceneGraph *scenegraph; /*avoid searching the graph for things we know...*/ M_Transform2D *tr_track, *tr_box, *tr_scroll; @@ -89,296 +102,287 @@ typedef struct GF_List *blink_nodes; u32 scroll_type, scroll_mode; Fixed scroll_time, scroll_delay; - Bool is_active, use_texture, outline; -} TTDPriv; + Bool timer_active; +} GF_TTXTDec; static void ttd_set_blink_fraction(GF_Node *node, GF_Route *route); static void ttd_set_scroll_fraction(GF_Node *node, GF_Route *route); -static void TTD_ResetDisplay(TTDPriv *priv); +static void ttd_reset_display(GF_TTXTDec *ctx); /*the WORST thing about 3GP in MPEG4 is positioning of the text track...*/ -static void TTD_UpdateSizeInfo(TTDPriv *priv) +static void ttd_update_size_info(GF_TTXTDec *ctx) { u32 w, h; Bool has_size; s32 offset, thw, thh, vw, vh; - has_size = gf_sg_get_scene_size_info(priv->inlineScene->graph, &w, &h); + has_size = gf_sg_get_scene_size_info(ctx->scene->graph, &w, &h); /*no size info is given in main scene, override by associated video size if any, or by text track size*/ if (!has_size) { - if (priv->cfg->has_vid_info && priv->cfg->video_width && priv->cfg->video_height) { - gf_sg_set_scene_size_info(priv->sg, priv->cfg->video_width, priv->cfg->video_height, GF_TRUE); + if (ctx->cfg->has_vid_info && ctx->cfg->video_width && ctx->cfg->video_height) { + gf_sg_set_scene_size_info(ctx->scenegraph, ctx->cfg->video_width, ctx->cfg->video_height, GF_TRUE); + } else if (ctx->cfg->text_width && ctx->cfg->text_height) { + gf_sg_set_scene_size_info(ctx->scenegraph, ctx->cfg->text_width, ctx->cfg->text_height, GF_TRUE); } else { - gf_sg_set_scene_size_info(priv->sg, priv->cfg->text_width, priv->cfg->text_height, GF_TRUE); + gf_sg_set_scene_size_info(ctx->scenegraph, ctx->txtw, ctx->txth, GF_TRUE); } - gf_sg_get_scene_size_info(priv->sg, &w, &h); + gf_sg_get_scene_size_info(ctx->scenegraph, &w, &h); if (!w || !h) return; - gf_scene_force_size(priv->inlineScene, w, h); + gf_scene_force_size(ctx->scene, w, h); } if (!w || !h) return; /*apply*/ - gf_sg_set_scene_size_info(priv->sg, w, h, GF_TRUE); + gf_sg_set_scene_size_info(ctx->scenegraph, w, h, GF_TRUE); /*make sure the scene size is big enough to contain the text track after positioning. RESULTS ARE UNDEFINED if offsets are negative: since MPEG-4 uses centered coord system, we must assume video is aligned to top-left*/ - if (priv->cfg->has_vid_info) { + if (ctx->cfg->has_vid_info) { Bool set_size = GF_FALSE; - vw = priv->cfg->horiz_offset; + vw = ctx->cfg->horiz_offset; if (vw<0) vw = 0; - vh = priv->cfg->vert_offset; + vh = ctx->cfg->vert_offset; if (vh<0) vh = 0; - if (priv->cfg->text_width + (u32) vw > w) { - w = priv->cfg->text_width+vw; + if (ctx->cfg->text_width + (u32) vw > w) { + w = ctx->cfg->text_width+vw; set_size = GF_TRUE; } - if (priv->cfg->text_height + (u32) vh > h) { - h = priv->cfg->text_height+vh; + if (ctx->cfg->text_height + (u32) vh > h) { + h = ctx->cfg->text_height+vh; set_size = GF_TRUE; } if (set_size) { - gf_sg_set_scene_size_info(priv->sg, w, h, GF_TRUE); - gf_scene_force_size(priv->inlineScene, w, h); + gf_sg_set_scene_size_info(ctx->scenegraph, w, h, GF_TRUE); + gf_scene_force_size(ctx->scene, w, h); } } else { /*otherwise override (mainly used for SRT & TTXT file direct loading*/ - priv->cfg->text_width = w; - priv->cfg->text_height = h; + ctx->cfg->text_width = w; + ctx->cfg->text_height = h; } /*ok override video size with main scene size*/ - priv->cfg->video_width = w; - priv->cfg->video_height = h; + ctx->cfg->video_width = w; + ctx->cfg->video_height = h; vw = (s32) w; vh = (s32) h; - thw = priv->cfg->text_width / 2; - thh = priv->cfg->text_height / 2; + thw = ctx->cfg->text_width / 2; + thh = ctx->cfg->text_height / 2; /*check translation, we must not get out of scene size - not supported in GPAC*/ - offset = priv->cfg->horiz_offset - vw/2 + thw; + offset = ctx->cfg->horiz_offset - vw/2 + thw; /*safety checks ? if (offset + thw < - vw/2) offset = - vw/2 + thw; else if (offset - thw > vw/2) offset = vw/2 - thw; */ - priv->tr_track->translation.x = INT2FIX(offset); + ctx->tr_track->translation.x = INT2FIX(offset); - offset = vh/2 - priv->cfg->vert_offset - thh; + offset = vh/2 - ctx->cfg->vert_offset - thh; /*safety checks ? if (offset + thh > vh/2) offset = vh/2 - thh; else if (offset - thh < -vh/2) offset = -vh/2 + thh; */ - priv->tr_track->translation.y = INT2FIX(offset); - - gf_node_changed((GF_Node *)priv->tr_track, NULL); -} + ctx->tr_track->translation.y = INT2FIX(offset); -static GF_Err TTD_GetCapabilities(GF_BaseDecoder *plug, GF_CodecCapability *capability) -{ - TTDPriv *priv = (TTDPriv *)plug->privateStack; - switch (capability->CapCode) { - case GF_CODEC_WIDTH: - capability->cap.valueInt = priv->cfg->text_width; - return GF_OK; - case GF_CODEC_HEIGHT: - capability->cap.valueInt = priv->cfg->text_height; - return GF_OK; - case GF_CODEC_MEDIA_NOT_OVER: - capability->cap.valueInt = priv->is_active; - return GF_OK; - default: - capability->cap.valueInt = 0; - return GF_OK; - } -} - -static GF_Err TTD_SetCapabilities(GF_BaseDecoder *plug, const GF_CodecCapability capability) -{ - TTDPriv *priv = (TTDPriv *)plug->privateStack; - if (capability.CapCode==GF_CODEC_SHOW_SCENE) { - if (capability.cap.valueInt) { - TTD_ResetDisplay(priv); - TTD_UpdateSizeInfo(priv); - gf_scene_register_extra_graph(priv->inlineScene, priv->sg, GF_FALSE); - } else { - gf_scene_register_extra_graph(priv->inlineScene, priv->sg, GF_TRUE); - } - } - return GF_OK; -} - -GF_Err TTD_AttachScene(GF_SceneDecoder *plug, GF_Scene *scene, Bool is_scene_decoder) -{ - TTDPriv *priv = (TTDPriv *)plug->privateStack; - if (priv->nb_streams) return GF_BAD_PARAM; - /*timedtext cannot be a root scene object*/ - if (is_scene_decoder) return GF_BAD_PARAM; - priv->inlineScene = scene; - priv->app = scene->root_od->term; - return GF_OK; -} - -GF_Err TTD_ReleaseScene(GF_SceneDecoder *plug) -{ - TTDPriv *priv = (TTDPriv *)plug->privateStack; - if (priv->nb_streams) return GF_BAD_PARAM; - return GF_OK; + gf_node_changed((GF_Node *)ctx->tr_track, NULL); } -static GFINLINE void add_child(GF_Node *n1, GF_Node *par) +static GFINLINE void ttd_add_child(GF_Node *n1, GF_Node *par) { gf_node_list_add_child( & ((GF_ParentNode *)par)->children, n1); gf_node_register(n1, par); } -static GFINLINE GF_Node *ttd_create_node(TTDPriv *ttd, u32 tag, const char *def_name) +static GFINLINE GF_Node *ttd_create_node(GF_TTXTDec *ttd, u32 tag, const char *def_name) { - GF_Node *n = gf_node_new(ttd->sg, tag); + GF_Node *n = gf_node_new(ttd->scenegraph, tag); if (n) { - if (def_name) gf_node_set_id(n, gf_sg_get_next_available_node_id(ttd->sg), def_name); + if (def_name) gf_node_set_id(n, gf_sg_get_next_available_node_id(ttd->scenegraph), def_name); gf_node_init(n); } return n; } -static GF_Err TTD_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd) +static GF_Err ttd_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) { - TTDPriv *priv = (TTDPriv *)plug->privateStack; + GF_TTXTDec *ctx = gf_filter_get_udta(filter); GF_Err e; - GF_Node *root, *n1, *n2; - const char *opt; - /*no scalable, no upstream*/ - if (priv->nb_streams || esd->decoderConfig->upstream) return GF_NOT_SUPPORTED; - if (!esd->decoderConfig->decoderSpecificInfo || !esd->decoderConfig->decoderSpecificInfo->data) return GF_NON_COMPLIANT_BITSTREAM; - - priv->cfg = (GF_TextConfig *) gf_odf_desc_new(GF_ODF_TEXT_CFG_TAG); - e = gf_odf_get_text_config(esd->decoderConfig->decoderSpecificInfo, (u8) esd->decoderConfig->objectTypeIndication, priv->cfg); - if (e) { - gf_odf_desc_del((GF_Descriptor *) priv->cfg); - priv->cfg = NULL; - return e; + u32 st, codecid, dsi_crc; + const GF_PropertyValue *p, *dsi; + + if (is_remove) { + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + ctx->opid = ctx->ipid = NULL; + return GF_OK; + } + //TODO: we need to cleanup cap checking upon reconfigure + if (ctx->ipid && !gf_filter_pid_check_caps(pid)) return GF_NOT_SUPPORTED; + assert(!ctx->ipid || (ctx->ipid == pid)); + + st = codecid = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (p) st = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (p) codecid = p->value.uint; + + dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (!dsi) return GF_NOT_SUPPORTED; + + if (st != GF_STREAM_TEXT) return GF_NOT_SUPPORTED; + + dsi_crc = gf_crc_32(dsi->value.data.ptr, dsi->value.data.size); + if (dsi_crc == ctx->dsi_crc) return GF_OK; + ctx->dsi_crc = dsi_crc; + + if (ctx->cfg) gf_odf_desc_del((GF_Descriptor *) ctx->cfg); + ctx->cfg = (GF_TextConfig *) gf_odf_desc_new(GF_ODF_TEXT_CFG_TAG); + if (codecid == GF_CODECID_TEXT_MPEG4) { + e = gf_odf_get_text_config(dsi->value.data.ptr, dsi->value.data.size, codecid, ctx->cfg); + if (e) { + gf_odf_desc_del((GF_Descriptor *) ctx->cfg); + ctx->cfg = NULL; + return e; + } + ctx->is_tx3g = GF_FALSE; + } else if (codecid == GF_CODECID_TX3G) { + GF_TextSampleDescriptor * sd = gf_odf_tx3g_read(dsi->value.data.ptr, dsi->value.data.size); + if (!sd) { + gf_odf_desc_del((GF_Descriptor *) ctx->cfg); + ctx->cfg = NULL; + return GF_NON_COMPLIANT_BITSTREAM; + } + gf_list_add(ctx->cfg->sample_descriptions, sd); + ctx->is_tx3g = GF_TRUE; + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p && !ctx->cfg->timescale) ctx->cfg->timescale = p->value.uint; + + ctx->ipid = pid; + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); } - priv->nb_streams++; - if (!priv->cfg->timescale) priv->cfg->timescale = 1000; - priv->sg = gf_sg_new_subscene(priv->inlineScene->graph); + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_TEXT)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW)); + + return GF_OK; +} + +static void ttd_setup_scene(GF_TTXTDec *ctx) +{ + GF_Node *root, *n1, *n2; + if (ctx->scenegraph) return; + + ctx->scenegraph = gf_sg_new_subscene(ctx->scene->graph); - root = ttd_create_node(priv, TAG_MPEG4_OrderedGroup, NULL); - gf_sg_set_root_node(priv->sg, root); + root = ttd_create_node(ctx, TAG_MPEG4_OrderedGroup, NULL); + gf_sg_set_root_node(ctx->scenegraph, root); gf_node_register(root, NULL); /*root transform*/ - priv->tr_track = (M_Transform2D *)ttd_create_node(priv, TAG_MPEG4_Transform2D, NULL); - add_child((GF_Node *) priv->tr_track, root); + ctx->tr_track = (M_Transform2D *)ttd_create_node(ctx, TAG_MPEG4_Transform2D, NULL); + ttd_add_child((GF_Node *) ctx->tr_track, root); - TTD_UpdateSizeInfo(priv); + ttd_update_size_info(ctx); /*txt track background*/ - n1 = ttd_create_node(priv, TAG_MPEG4_Shape, NULL); - add_child(n1, (GF_Node *) priv->tr_track); - ((M_Shape *)n1)->appearance = ttd_create_node(priv, TAG_MPEG4_Appearance, NULL); + n1 = ttd_create_node(ctx, TAG_MPEG4_Shape, NULL); + ttd_add_child(n1, (GF_Node *) ctx->tr_track); + ((M_Shape *)n1)->appearance = ttd_create_node(ctx, TAG_MPEG4_Appearance, NULL); gf_node_register(((M_Shape *)n1)->appearance, n1); - priv->mat_track = (M_Material2D *) ttd_create_node(priv, TAG_MPEG4_Material2D, NULL); - priv->mat_track->filled = 1; - priv->mat_track->transparency = 1; - ((M_Appearance *) ((M_Shape *)n1)->appearance)->material = (GF_Node *) priv->mat_track; - gf_node_register((GF_Node *) priv->mat_track, ((M_Shape *)n1)->appearance); - n2 = ttd_create_node(priv, TAG_MPEG4_Rectangle, NULL); + ctx->mat_track = (M_Material2D *) ttd_create_node(ctx, TAG_MPEG4_Material2D, NULL); + ctx->mat_track->filled = 1; + ctx->mat_track->transparency = 1; + ((M_Appearance *) ((M_Shape *)n1)->appearance)->material = (GF_Node *) ctx->mat_track; + gf_node_register((GF_Node *) ctx->mat_track, ((M_Shape *)n1)->appearance); + n2 = ttd_create_node(ctx, TAG_MPEG4_Rectangle, NULL); ((M_Rectangle *)n2)->size.x = 0; ((M_Rectangle *)n2)->size.y = 0; ((M_Shape *)n1)->geometry = n2; gf_node_register(n2, n1); - priv->rec_track = (M_Rectangle *)n2; + ctx->rec_track = (M_Rectangle *)n2; /*txt box background*/ - priv->tr_box = (M_Transform2D *) ttd_create_node(priv, TAG_MPEG4_Transform2D, NULL); - add_child((GF_Node*) priv->tr_box, (GF_Node*)priv->tr_track); - n1 = ttd_create_node(priv, TAG_MPEG4_Shape, NULL); - add_child(n1, (GF_Node*)priv->tr_box); - ((M_Shape *)n1)->appearance = ttd_create_node(priv, TAG_MPEG4_Appearance, NULL); + ctx->tr_box = (M_Transform2D *) ttd_create_node(ctx, TAG_MPEG4_Transform2D, NULL); + ttd_add_child((GF_Node*) ctx->tr_box, (GF_Node*)ctx->tr_track); + n1 = ttd_create_node(ctx, TAG_MPEG4_Shape, NULL); + ttd_add_child(n1, (GF_Node*)ctx->tr_box); + ((M_Shape *)n1)->appearance = ttd_create_node(ctx, TAG_MPEG4_Appearance, NULL); gf_node_register(((M_Shape *)n1)->appearance, n1); - priv->mat_box = (M_Material2D *) ttd_create_node(priv, TAG_MPEG4_Material2D, NULL); - priv->mat_box->filled = 1; - priv->mat_box->transparency = 1; - ((M_Appearance *) ((M_Shape *)n1)->appearance)->material = (GF_Node *)priv->mat_box; - gf_node_register((GF_Node *)priv->mat_box, ((M_Shape *)n1)->appearance); - priv->rec_box = (M_Rectangle *) ttd_create_node(priv, TAG_MPEG4_Rectangle, NULL); - priv->rec_box->size.x = 0; - priv->rec_box->size.y = 0; - ((M_Shape *)n1)->geometry = (GF_Node *) priv->rec_box; - gf_node_register((GF_Node *) priv->rec_box, n1); - - priv->dlist = (M_Layer2D *) ttd_create_node(priv, TAG_MPEG4_Layer2D, NULL); - priv->dlist->size.x = priv->cfg->text_width; - priv->dlist->size.y = priv->cfg->text_height; - add_child((GF_Node *)priv->dlist, (GF_Node *)priv->tr_box); - - priv->blink_nodes = gf_list_new(); - priv->ts_blink = (M_TimeSensor *) ttd_create_node(priv, TAG_MPEG4_TimeSensor, "TimerBlink"); - priv->ts_blink->cycleInterval = 0.25; - priv->ts_blink->startTime = 0.0; - priv->ts_blink->loop = 1; - priv->process_blink = (M_ScalarInterpolator *) ttd_create_node(priv, TAG_MPEG4_ScalarInterpolator, NULL); + ctx->mat_box = (M_Material2D *) ttd_create_node(ctx, TAG_MPEG4_Material2D, NULL); + ctx->mat_box->filled = 1; + ctx->mat_box->transparency = 1; + ((M_Appearance *) ((M_Shape *)n1)->appearance)->material = (GF_Node *)ctx->mat_box; + gf_node_register((GF_Node *)ctx->mat_box, ((M_Shape *)n1)->appearance); + ctx->rec_box = (M_Rectangle *) ttd_create_node(ctx, TAG_MPEG4_Rectangle, NULL); + ctx->rec_box->size.x = 0; + ctx->rec_box->size.y = 0; + ((M_Shape *)n1)->geometry = (GF_Node *) ctx->rec_box; + gf_node_register((GF_Node *) ctx->rec_box, n1); + + ctx->dlist = (M_Layer2D *) ttd_create_node(ctx, TAG_MPEG4_Layer2D, NULL); + ctx->dlist->size.x = ctx->cfg->text_width; + ctx->dlist->size.y = ctx->cfg->text_height; + ttd_add_child((GF_Node *)ctx->dlist, (GF_Node *)ctx->tr_box); + + ctx->blink_nodes = gf_list_new(); + ctx->ts_blink = (M_TimeSensor *) ttd_create_node(ctx, TAG_MPEG4_TimeSensor, "TimerBlink"); + ctx->ts_blink->cycleInterval = 0.25; + ctx->ts_blink->startTime = 0.0; + ctx->ts_blink->loop = 1; + ctx->process_blink = (M_ScalarInterpolator *) ttd_create_node(ctx, TAG_MPEG4_ScalarInterpolator, NULL); /*override set_fraction*/ - priv->process_blink->on_set_fraction = ttd_set_blink_fraction; - gf_node_set_private((GF_Node *) priv->process_blink, priv); + ctx->process_blink->on_set_fraction = ttd_set_blink_fraction; + gf_node_set_private((GF_Node *) ctx->process_blink, ctx); /*route from fraction_changed to set_fraction*/ - gf_sg_route_new(priv->sg, (GF_Node *) priv->ts_blink, 6, (GF_Node *) priv->process_blink, 0); + gf_sg_route_new(ctx->scenegraph, (GF_Node *) ctx->ts_blink, 6, (GF_Node *) ctx->process_blink, 0); - priv->ts_scroll = (M_TimeSensor *) ttd_create_node(priv, TAG_MPEG4_TimeSensor, "TimerScroll"); - priv->ts_scroll->cycleInterval = 0; - priv->ts_scroll->startTime = -1; - priv->ts_scroll->loop = 0; - priv->process_scroll = (M_ScalarInterpolator *) ttd_create_node(priv, TAG_MPEG4_ScalarInterpolator, NULL); + ctx->ts_scroll = (M_TimeSensor *) ttd_create_node(ctx, TAG_MPEG4_TimeSensor, "TimerScroll"); + ctx->ts_scroll->cycleInterval = 0; + ctx->ts_scroll->startTime = -1; + ctx->ts_scroll->loop = 0; + ctx->process_scroll = (M_ScalarInterpolator *) ttd_create_node(ctx, TAG_MPEG4_ScalarInterpolator, NULL); /*override set_fraction*/ - priv->process_scroll->on_set_fraction = ttd_set_scroll_fraction; - gf_node_set_private((GF_Node *) priv->process_scroll, priv); + ctx->process_scroll->on_set_fraction = ttd_set_scroll_fraction; + gf_node_set_private((GF_Node *) ctx->process_scroll, ctx); /*route from fraction_changed to set_fraction*/ - gf_sg_route_new(priv->sg, (GF_Node *) priv->ts_scroll, 6, (GF_Node *) priv->process_scroll, 0); - - gf_node_register((GF_Node *) priv->ts_blink, NULL); - gf_node_register((GF_Node *) priv->process_blink, NULL); - gf_node_register((GF_Node *) priv->ts_scroll, NULL); - gf_node_register((GF_Node *) priv->process_scroll, NULL); - - /*option setup*/ - opt = gf_modules_get_option((GF_BaseInterface *)plug, "StreamingText", "UseTexturing"); - priv->use_texture = (opt && !strcmp(opt, "yes")) ? GF_TRUE : GF_FALSE; - opt = gf_modules_get_option((GF_BaseInterface *)plug, "StreamingText", "OutlineText"); - priv->outline = (opt && !strcmp(opt, "yes")) ? GF_TRUE : GF_FALSE; - return e; + gf_sg_route_new(ctx->scenegraph, (GF_Node *) ctx->ts_scroll, 6, (GF_Node *) ctx->process_scroll, 0); + + gf_node_register((GF_Node *) ctx->ts_blink, NULL); + gf_node_register((GF_Node *) ctx->process_blink, NULL); + gf_node_register((GF_Node *) ctx->ts_scroll, NULL); + gf_node_register((GF_Node *) ctx->process_scroll, NULL); + } -static GF_Err TTD_DetachStream(GF_BaseDecoder *plug, u16 ES_ID) +static void ttd_reset_scene(GF_TTXTDec *ctx) { - TTDPriv *priv = (TTDPriv *)plug->privateStack; - if (!priv->nb_streams) return GF_BAD_PARAM; - - gf_scene_register_extra_graph(priv->inlineScene, priv->sg, GF_TRUE); - - gf_node_unregister((GF_Node *) priv->ts_blink, NULL); - gf_node_unregister((GF_Node *) priv->process_blink, NULL); - gf_node_unregister((GF_Node *) priv->ts_scroll, NULL); - gf_node_unregister((GF_Node *) priv->process_scroll, NULL); - - gf_sg_del(priv->sg); - priv->sg = NULL; - if (priv->cfg) gf_odf_desc_del((GF_Descriptor *) priv->cfg); - priv->cfg = NULL; - priv->nb_streams = 0; - gf_list_del(priv->blink_nodes); - return GF_OK; + if (!ctx->scenegraph) return; + + gf_scene_register_extra_graph(ctx->scene, ctx->scenegraph, GF_TRUE); + + gf_node_unregister((GF_Node *) ctx->ts_blink, NULL); + gf_node_unregister((GF_Node *) ctx->process_blink, NULL); + gf_node_unregister((GF_Node *) ctx->ts_scroll, NULL); + gf_node_unregister((GF_Node *) ctx->process_scroll, NULL); + + gf_sg_del(ctx->scenegraph); + ctx->scenegraph = NULL; + gf_list_del(ctx->blink_nodes); } static void ttd_set_blink_fraction(GF_Node *node, GF_Route *route) { M_Material2D *m; u32 i; - TTDPriv *priv = (TTDPriv *)gf_node_get_private(node); + GF_TTXTDec *ctx = (GF_TTXTDec *)gf_node_get_private(node); Bool blink_on = GF_TRUE; - if (priv->process_blink->set_fraction>FIX_ONE/2) blink_on = GF_FALSE; + if (ctx->process_blink->set_fraction>FIX_ONE/2) blink_on = GF_FALSE; i=0; - while ((m = (M_Material2D*)gf_list_enum(priv->blink_nodes, &i))) { + while ((m = (M_Material2D*)gf_list_enum(ctx->blink_nodes, &i))) { if (m->filled != blink_on) { m->filled = blink_on; gf_node_changed((GF_Node *) m, NULL); @@ -389,70 +393,70 @@ static void ttd_set_blink_fraction(GF_Node *node, GF_Route *route) static void ttd_set_scroll_fraction(GF_Node *node, GF_Route *route) { Fixed frac; - TTDPriv *priv = (TTDPriv *)gf_node_get_private(node); - frac = priv->process_scroll->set_fraction; - if (frac==FIX_ONE) priv->is_active = GF_FALSE; - if (!priv->tr_scroll) return; + GF_TTXTDec *ctx = (GF_TTXTDec *)gf_node_get_private(node); + frac = ctx->process_scroll->set_fraction; + if (frac==FIX_ONE) ctx->timer_active = GF_FALSE; + if (!ctx->tr_scroll) return; - switch (priv->scroll_type - 1) { + switch (ctx->scroll_type - 1) { case GF_TXT_SCROLL_CREDITS: case GF_TXT_SCROLL_DOWN: - priv->tr_scroll->translation.x = 0; - if (priv->scroll_mode & GF_TXT_SCROLL_IN) { - if (frac>priv->scroll_time) { - priv->scroll_mode &= ~GF_TXT_SCROLL_IN; - priv->tr_scroll->translation.y = 0; + ctx->tr_scroll->translation.x = 0; + if (ctx->scroll_mode & GF_TXT_SCROLL_IN) { + if (frac>ctx->scroll_time) { + ctx->scroll_mode &= ~GF_TXT_SCROLL_IN; + ctx->tr_scroll->translation.y = 0; } else { - priv->tr_scroll->translation.y = gf_muldiv(priv->dlist->size.y, frac, priv->scroll_time) - priv->dlist->size.y; + ctx->tr_scroll->translation.y = gf_muldiv(ctx->dlist->size.y, frac, ctx->scroll_time) - ctx->dlist->size.y; } - } else if (priv->scroll_mode & GF_TXT_SCROLL_OUT) { - if (frac < FIX_ONE - priv->scroll_time) return; - frac -= FIX_ONE - priv->scroll_time; - if (priv->scroll_type - 1 == GF_TXT_SCROLL_DOWN) { - priv->tr_scroll->translation.y = gf_muldiv(priv->dlist->size.y, frac, priv->scroll_time); + } else if (ctx->scroll_mode & GF_TXT_SCROLL_OUT) { + if (frac < FIX_ONE - ctx->scroll_time) return; + frac -= FIX_ONE - ctx->scroll_time; + if (ctx->scroll_type - 1 == GF_TXT_SCROLL_DOWN) { + ctx->tr_scroll->translation.y = gf_muldiv(ctx->dlist->size.y, frac, ctx->scroll_time); } else { - priv->tr_scroll->translation.y = gf_muldiv(priv->dlist->size.y, frac, priv->scroll_time); + ctx->tr_scroll->translation.y = gf_muldiv(ctx->dlist->size.y, frac, ctx->scroll_time); } } - if (priv->scroll_type - 1 == GF_TXT_SCROLL_DOWN) priv->tr_scroll->translation.y *= -1; + if (ctx->scroll_type - 1 == GF_TXT_SCROLL_DOWN) ctx->tr_scroll->translation.y *= -1; break; case GF_TXT_SCROLL_MARQUEE: case GF_TXT_SCROLL_RIGHT: - priv->tr_scroll->translation.y = 0; - if (priv->scroll_mode & GF_TXT_SCROLL_IN) { - if (! (priv->scroll_mode & GF_TXT_SCROLL_OUT)) { - if (fracscroll_delay) return; - frac-=priv->scroll_delay; + ctx->tr_scroll->translation.y = 0; + if (ctx->scroll_mode & GF_TXT_SCROLL_IN) { + if (! (ctx->scroll_mode & GF_TXT_SCROLL_OUT)) { + if (fracscroll_delay) return; + frac-=ctx->scroll_delay; } - if (frac>priv->scroll_time) { - priv->scroll_mode &= ~GF_TXT_SCROLL_IN; - priv->tr_scroll->translation.x = 0; + if (frac>ctx->scroll_time) { + ctx->scroll_mode &= ~GF_TXT_SCROLL_IN; + ctx->tr_scroll->translation.x = 0; } else { - priv->tr_scroll->translation.x = gf_muldiv(priv->dlist->size.x, frac, priv->scroll_time) - priv->dlist->size.x; + ctx->tr_scroll->translation.x = gf_muldiv(ctx->dlist->size.x, frac, ctx->scroll_time) - ctx->dlist->size.x; } - } else if (priv->scroll_mode & GF_TXT_SCROLL_OUT) { - if (frac < FIX_ONE - priv->scroll_time) return; - frac -= FIX_ONE - priv->scroll_time; - priv->tr_scroll->translation.x = gf_muldiv(priv->dlist->size.x, frac, priv->scroll_time); + } else if (ctx->scroll_mode & GF_TXT_SCROLL_OUT) { + if (frac < FIX_ONE - ctx->scroll_time) return; + frac -= FIX_ONE - ctx->scroll_time; + ctx->tr_scroll->translation.x = gf_muldiv(ctx->dlist->size.x, frac, ctx->scroll_time); } - if (priv->scroll_type - 1 == GF_TXT_SCROLL_MARQUEE) priv->tr_scroll->translation.x *= -1; + if (ctx->scroll_type - 1 == GF_TXT_SCROLL_MARQUEE) ctx->tr_scroll->translation.x *= -1; break; default: break; } - gf_node_changed((GF_Node *)priv->tr_scroll, NULL); + gf_node_changed((GF_Node *)ctx->tr_scroll, NULL); } -static void TTD_ResetDisplay(TTDPriv *priv) +static void ttd_reset_display(GF_TTXTDec *ctx) { - gf_list_reset(priv->blink_nodes); - gf_node_unregister_children((GF_Node*)priv->dlist, priv->dlist->children); - priv->dlist->children = NULL; - gf_node_changed((GF_Node *) priv->dlist, NULL); - priv->tr_scroll = NULL; + gf_list_reset(ctx->blink_nodes); + gf_node_unregister_children((GF_Node*)ctx->dlist, ctx->dlist->children); + ctx->dlist->children = NULL; + gf_node_changed((GF_Node *) ctx->dlist, NULL); + ctx->tr_scroll = NULL; } -char *TTD_FindFont(GF_TextSampleDescriptor *tsd, u32 ID) +static char *ttd_find_font(GF_TextSampleDescriptor *tsd, u32 ID) { u32 i; for (i=0; ifont_count; i++) { @@ -493,7 +497,7 @@ typedef struct layout to handle new lines and proper scrolling*/ } TTDTextChunk; -static void TTD_NewTextChunk(TTDPriv *priv, GF_TextSampleDescriptor *tsd, M_Form *form, u16 *utf16_txt, TTDTextChunk *tc) +static void ttd_new_text_chunk(GF_TTXTDec *ctx, GF_TextSampleDescriptor *tsd, M_Form *form, u16 *utf16_txt, TTDTextChunk *tc) { GF_Node *txt_model, *n2, *txt_material; M_Text *text; @@ -503,24 +507,24 @@ static void TTD_NewTextChunk(TTDPriv *priv, GF_TextSampleDescriptor *tsd, M_Form u32 fontSize, styleFlags, color, i, start_char; if (!tc->srec) { - fontName = TTD_FindFont(tsd, tsd->default_style.fontID); + fontName = ttd_find_font(tsd, tsd->default_style.fontID); fontSize = tsd->default_style.font_size; styleFlags = tsd->default_style.style_flags; color = tsd->default_style.text_color; } else { - fontName = TTD_FindFont(tsd, tc->srec->fontID); + fontName = ttd_find_font(tsd, tc->srec->fontID); fontSize = tc->srec->font_size; styleFlags = tc->srec->style_flags; color = tc->srec->text_color; } /*create base model for text node. It will then be cloned for each text item*/ - txt_model = ttd_create_node(priv, TAG_MPEG4_Shape, NULL); + txt_model = ttd_create_node(ctx, TAG_MPEG4_Shape, NULL); gf_node_register(txt_model, NULL); - n2 = ttd_create_node(priv, TAG_MPEG4_Appearance, NULL); + n2 = ttd_create_node(ctx, TAG_MPEG4_Appearance, NULL); ((M_Shape *)txt_model)->appearance = n2; gf_node_register(n2, txt_model); - txt_material = ttd_create_node(priv, TAG_MPEG4_Material2D, NULL); + txt_material = ttd_create_node(ctx, TAG_MPEG4_Material2D, NULL); ((M_Appearance *)n2)->material = txt_material; gf_node_register(txt_material, n2); @@ -531,16 +535,16 @@ static void TTD_NewTextChunk(TTDPriv *priv, GF_TextSampleDescriptor *tsd, M_Form ((M_Material2D *)txt_material)->emissiveColor.blue = INT2FIX((color) & 0xFF) / 255; /*force 0 lineWidth if blinking (the stupid MPEG-4 default values once again..)*/ if (tc->has_blink) { - ((M_Material2D *)txt_material)->lineProps = ttd_create_node(priv, TAG_MPEG4_LineProperties, NULL); + ((M_Material2D *)txt_material)->lineProps = ttd_create_node(ctx, TAG_MPEG4_LineProperties, NULL); ((M_LineProperties *)((M_Material2D *)txt_material)->lineProps)->width = 0; gf_node_register(((M_Material2D *)txt_material)->lineProps, txt_material); } - n2 = ttd_create_node(priv, TAG_MPEG4_Text, NULL); + n2 = ttd_create_node(ctx, TAG_MPEG4_Text, NULL); ((M_Shape *)txt_model)->geometry = n2; gf_node_register(n2, txt_model); text = (M_Text *) n2; - fs = (M_FontStyle *) ttd_create_node(priv, TAG_MPEG4_FontStyle, NULL); + fs = (M_FontStyle *) ttd_create_node(ctx, TAG_MPEG4_FontStyle, NULL); gf_free(fs->family.vals[0]); /*translate default fonts to MPEG-4/VRML names*/ @@ -572,8 +576,8 @@ static void TTD_NewTextChunk(TTDPriv *priv, GF_TextSampleDescriptor *tsd, M_Form /*a better way would be to draw the entire text box in a composite texture & bitmap but we can't really rely on text box size (in MP4Box, it actually defaults to the entire video area) and drawing a too large texture & bitmap could slow down rendering*/ - if (priv->use_texture) strcat(szStyle, " TEXTURED"); - if (priv->outline) strcat(szStyle, " OUTLINED"); + if (ctx->texture) strcat(szStyle, " TEXTURED"); + if (ctx->outline) strcat(szStyle, " OUTLINED"); fs->style.buffer = gf_strdup(szStyle); fs->horizontal = (tsd->displayFlags & GF_TXT_VERTICAL) ? 0 : 1; @@ -583,13 +587,14 @@ static void TTD_NewTextChunk(TTDPriv *priv, GF_TextSampleDescriptor *tsd, M_Form if (tc->hlink && tc->hlink->URL) { SFURL *s; - M_Anchor *anc = (M_Anchor *) ttd_create_node(priv, TAG_MPEG4_Anchor, NULL); + M_Anchor *anc = (M_Anchor *) ttd_create_node(ctx, TAG_MPEG4_Anchor, NULL); gf_sg_vrml_mf_append(&anc->url, GF_SG_VRML_MFURL, (void **) &s); s->OD_ID = 0; s->url = gf_strdup(tc->hlink->URL); if (tc->hlink->URL_hint) anc->description.buffer = gf_strdup(tc->hlink->URL_hint); gf_node_list_add_child(& anc->children, txt_model); gf_node_register(txt_model, (GF_Node *)anc); + gf_node_unregister(txt_model, NULL); txt_model = (GF_Node *)anc; gf_node_register((GF_Node *)anc, NULL); } @@ -612,7 +617,7 @@ static void TTD_NewTextChunk(TTDPriv *priv, GF_TextSampleDescriptor *tsd, M_Form /*splitting lines, duplicate node*/ - n2 = gf_node_clone(priv->sg, txt_model, NULL, "", GF_TRUE); + n2 = gf_node_clone(ctx->scenegraph, txt_model, NULL, "", GF_TRUE); if (tc->hlink && tc->hlink->URL) { GF_Node *t = ((M_Anchor *)n2)->children->node; text = (M_Text *) ((M_Shape *)t)->geometry; @@ -628,7 +633,7 @@ static void TTD_NewTextChunk(TTDPriv *priv, GF_TextSampleDescriptor *tsd, M_Form /*clone node always register by default*/ gf_node_unregister(n2, NULL); - if (tc->has_blink && txt_material) gf_list_add(priv->blink_nodes, txt_material); + if (tc->has_blink && txt_material) gf_list_add(ctx->blink_nodes, txt_material); memcpy(wsChunk, &utf16_txt[start_char], sizeof(s16)*(i-start_char)); @@ -653,7 +658,7 @@ static void TTD_NewTextChunk(TTDPriv *priv, GF_TextSampleDescriptor *tsd, M_Form /*mod can be any of TextHighlight, TextKaraoke, TextHyperText, TextBlink*/ -void TTD_SplitChunks(GF_TextSample *txt, u32 nb_chars, GF_List *chunks, GF_Box *mod) +static void ttd_split_chunks(GF_TextSample *txt, u32 nb_chars, GF_List *chunks, GF_Box *mod) { TTDTextChunk *tc; u32 start_char, end_char; @@ -719,7 +724,7 @@ void TTD_SplitChunks(GF_TextSample *txt, u32 nb_chars, GF_List *chunks, GF_Box * } -static void TTD_ApplySample(TTDPriv *priv, GF_TextSample *txt, u32 sdi, Bool is_utf_16, u32 sample_duration) +static void ttd_apply_sample(GF_TTXTDec *ctx, GF_TextSample *txt, u32 sample_desc_index, Bool is_utf_16, u32 sample_duration) { u32 i, nb_lines, start_idx, count; s32 *id, thw, thh, tw, th, offset; @@ -737,22 +742,26 @@ static void TTD_ApplySample(TTDPriv *priv, GF_TextSample *txt, u32 sdi, Bool is_ GF_TextSampleDescriptor *td = NULL; /*stop timer sensor*/ - if (gf_list_count(priv->blink_nodes)) { - priv->ts_blink->stopTime = gf_node_get_scene_time((GF_Node *) priv->ts_blink); - gf_node_changed((GF_Node *) priv->ts_blink, NULL); + if (gf_list_count(ctx->blink_nodes)) { + ctx->ts_blink->stopTime = gf_node_get_scene_time((GF_Node *) ctx->ts_blink); + gf_node_changed((GF_Node *) ctx->ts_blink, NULL); } - priv->ts_scroll->stopTime = gf_node_get_scene_time((GF_Node *) priv->ts_scroll); - gf_node_changed((GF_Node *) priv->ts_scroll, NULL); + ctx->ts_scroll->stopTime = gf_node_get_scene_time((GF_Node *) ctx->ts_scroll); + gf_node_changed((GF_Node *) ctx->ts_scroll, NULL); /*flush routes to avoid getting the set_fraction of the scroll sensor deactivation*/ - gf_sg_activate_routes(priv->inlineScene->graph); + gf_sg_activate_routes(ctx->scene->graph); - TTD_ResetDisplay(priv); - if (!sdi || !txt || !txt->len) return; + ttd_reset_display(ctx); + if (!sample_desc_index || !txt || !txt->len) return; - i=0; - while ((td = (GF_TextSampleDescriptor *)gf_list_enum(priv->cfg->sample_descriptions, &i))) { - if (td->sample_index==sdi) break; - td = NULL; + if (ctx->is_tx3g) { + td = (GF_TextSampleDescriptor *)gf_list_get(ctx->cfg->sample_descriptions, 0); + } else { + i=0; + while ((td = (GF_TextSampleDescriptor *)gf_list_enum(ctx->cfg->sample_descriptions, &i))) { + if (td->sample_index==sample_desc_index) break; + td = NULL; + } } if (!td) return; @@ -762,11 +771,11 @@ static void TTD_ApplySample(TTDPriv *priv, GF_TextSample *txt, u32 sdi, Bool is_ /*set back color*/ /*do we fill the text box or the entire text track region*/ if (td->displayFlags & GF_TXT_FILL_REGION) { - priv->mat_box->transparency = FIX_ONE; - n = priv->mat_track; + ctx->mat_box->transparency = FIX_ONE; + n = ctx->mat_track; } else { - priv->mat_track->transparency = FIX_ONE; - n = priv->mat_box; + ctx->mat_track->transparency = FIX_ONE; + n = ctx->mat_box; } n->transparency = FIX_ONE - INT2FIX((td->back_color>>24) & 0xFF) / 255; @@ -782,107 +791,107 @@ static void TTD_ApplySample(TTDPriv *priv, GF_TextSample *txt, u32 sdi, Bool is_ } if (!br.right || !br.bottom) { br.top = br.left = 0; - br.right = priv->cfg->text_width; - br.bottom = priv->cfg->text_height; + br.right = ctx->cfg->text_width; + br.bottom = ctx->cfg->text_height; } thw = br.right - br.left; thh = br.bottom - br.top; if (!thw || !thh) { br.top = br.left = 0; - thw = priv->cfg->text_width; - thh = priv->cfg->text_height; + thw = ctx->cfg->text_width; + thh = ctx->cfg->text_height; } - priv->dlist->size.x = INT2FIX(thw); - priv->dlist->size.y = INT2FIX(thh); + ctx->dlist->size.x = INT2FIX(thw); + ctx->dlist->size.y = INT2FIX(thh); /*disable backgrounds if not used*/ - if (priv->mat_track->transparencyrec_track->size.x != priv->cfg->text_width) { - priv->rec_track->size.x = priv->cfg->text_width; - priv->rec_track->size.y = priv->cfg->text_height; - gf_node_changed((GF_Node *) priv->rec_track, NULL); + if (ctx->mat_track->transparencyrec_track->size.x != ctx->cfg->text_width) { + ctx->rec_track->size.x = ctx->cfg->text_width; + ctx->rec_track->size.y = ctx->cfg->text_height; + gf_node_changed((GF_Node *) ctx->rec_track, NULL); } - } else if (priv->rec_track->size.x) { - priv->rec_track->size.x = priv->rec_track->size.y = 0; - gf_node_changed((GF_Node *) priv->rec_box, NULL); + } else if (ctx->rec_track->size.x) { + ctx->rec_track->size.x = ctx->rec_track->size.y = 0; + gf_node_changed((GF_Node *) ctx->rec_box, NULL); } - if (priv->mat_box->transparencyrec_box->size.x != priv->dlist->size.x) { - priv->rec_box->size.x = priv->dlist->size.x; - priv->rec_box->size.y = priv->dlist->size.y; - gf_node_changed((GF_Node *) priv->rec_box, NULL); + if (ctx->mat_box->transparencyrec_box->size.x != ctx->dlist->size.x) { + ctx->rec_box->size.x = ctx->dlist->size.x; + ctx->rec_box->size.y = ctx->dlist->size.y; + gf_node_changed((GF_Node *) ctx->rec_box, NULL); } - } else if (priv->rec_box->size.x) { - priv->rec_box->size.x = priv->rec_box->size.y = 0; - gf_node_changed((GF_Node *) priv->rec_box, NULL); + } else if (ctx->rec_box->size.x) { + ctx->rec_box->size.x = ctx->rec_box->size.y = 0; + gf_node_changed((GF_Node *) ctx->rec_box, NULL); } - form = (M_Form *) ttd_create_node(priv, TAG_MPEG4_Form, NULL); + form = (M_Form *) ttd_create_node(ctx, TAG_MPEG4_Form, NULL); form->size.x = INT2FIX(thw); form->size.y = INT2FIX(thh); thw /= 2; thh /= 2; - tw = priv->cfg->text_width; - th = priv->cfg->text_height; + tw = ctx->cfg->text_width; + th = ctx->cfg->text_height; /*check translation, we must not get out of scene size - not supported in GPAC*/ offset = br.left - tw/2 + thw; if (offset + thw < - tw/2) offset = - tw/2 + thw; else if (offset - thw > tw/2) offset = tw/2 - thw; - priv->tr_box->translation.x = INT2FIX(offset); + ctx->tr_box->translation.x = INT2FIX(offset); offset = th/2 - br.top - thh; if (offset + thh > th/2) offset = th/2 - thh; else if (offset - thh < -th/2) offset = -th/2 + thh; - priv->tr_box->translation.y = INT2FIX(offset); + ctx->tr_box->translation.y = INT2FIX(offset); - gf_node_dirty_set((GF_Node *)priv->tr_box, 0, GF_TRUE); + gf_node_dirty_set((GF_Node *)ctx->tr_box, 0, GF_TRUE); - if (priv->scroll_type) { - priv->ts_scroll->stopTime = gf_node_get_scene_time((GF_Node *) priv->ts_scroll); - gf_node_changed((GF_Node *) priv->ts_scroll, NULL); + if (ctx->scroll_type) { + ctx->ts_scroll->stopTime = gf_node_get_scene_time((GF_Node *) ctx->ts_scroll); + gf_node_changed((GF_Node *) ctx->ts_scroll, NULL); } - priv->scroll_mode = 0; - if (td->displayFlags & GF_TXT_SCROLL_IN) priv->scroll_mode |= GF_TXT_SCROLL_IN; - if (td->displayFlags & GF_TXT_SCROLL_OUT) priv->scroll_mode |= GF_TXT_SCROLL_OUT; - - priv->scroll_type = 0; - if (priv->scroll_mode) { - priv->scroll_type = (td->displayFlags & GF_TXT_SCROLL_DIRECTION)>>7; - priv->scroll_type ++; + ctx->scroll_mode = 0; + if (td->displayFlags & GF_TXT_SCROLL_IN) ctx->scroll_mode |= GF_TXT_SCROLL_IN; + if (td->displayFlags & GF_TXT_SCROLL_OUT) ctx->scroll_mode |= GF_TXT_SCROLL_OUT; + + ctx->scroll_type = 0; + if (ctx->scroll_mode) { + ctx->scroll_type = (td->displayFlags & GF_TXT_SCROLL_DIRECTION)>>7; + ctx->scroll_type ++; } /*no sample duration, cannot determine scroll rate, so just show*/ - if (!sample_duration) priv->scroll_type = 0; + if (!sample_duration) ctx->scroll_type = 0; /*no scroll*/ - if (!priv->scroll_mode) priv->scroll_type = 0; - - if (priv->scroll_type) { - priv->tr_scroll = (M_Transform2D *) ttd_create_node(priv, TAG_MPEG4_Transform2D, NULL); - gf_node_list_add_child( &priv->dlist->children, (GF_Node*)priv->tr_scroll); - gf_node_register((GF_Node *) priv->tr_scroll, (GF_Node *) priv->dlist); - gf_node_list_add_child( &priv->tr_scroll->children, (GF_Node*)form); - gf_node_register((GF_Node *) form, (GF_Node *) priv->tr_scroll); - priv->tr_scroll->translation.x = priv->tr_scroll->translation.y = (priv->scroll_mode & GF_TXT_SCROLL_IN) ? -INT2FIX(1000) : 0; + if (!ctx->scroll_mode) ctx->scroll_type = 0; + + if (ctx->scroll_type) { + ctx->tr_scroll = (M_Transform2D *) ttd_create_node(ctx, TAG_MPEG4_Transform2D, NULL); + gf_node_list_add_child( &ctx->dlist->children, (GF_Node*)ctx->tr_scroll); + gf_node_register((GF_Node *) ctx->tr_scroll, (GF_Node *) ctx->dlist); + gf_node_list_add_child( &ctx->tr_scroll->children, (GF_Node*)form); + gf_node_register((GF_Node *) form, (GF_Node *) ctx->tr_scroll); + ctx->tr_scroll->translation.x = ctx->tr_scroll->translation.y = (ctx->scroll_mode & GF_TXT_SCROLL_IN) ? -INT2FIX(1000) : 0; /*if no delay, text is in motion for the duration of the sample*/ - priv->scroll_time = FIX_ONE; - priv->scroll_delay = 0; + ctx->scroll_time = FIX_ONE; + ctx->scroll_delay = 0; if (txt->scroll_delay) { - priv->scroll_delay = gf_divfix(INT2FIX(txt->scroll_delay->scroll_delay), INT2FIX(sample_duration)); - if (priv->scroll_delay>FIX_ONE) priv->scroll_delay = FIX_ONE; - priv->scroll_time = (FIX_ONE - priv->scroll_delay); + ctx->scroll_delay = gf_divfix(INT2FIX(txt->scroll_delay->scroll_delay), INT2FIX(sample_duration)); + if (ctx->scroll_delay>FIX_ONE) ctx->scroll_delay = FIX_ONE; + ctx->scroll_time = (FIX_ONE - ctx->scroll_delay); } /*if both scroll (in and out), use same scroll duration for both*/ - if ((priv->scroll_mode & GF_TXT_SCROLL_IN) && (priv->scroll_mode & GF_TXT_SCROLL_OUT)) priv->scroll_time /= 2; + if ((ctx->scroll_mode & GF_TXT_SCROLL_IN) && (ctx->scroll_mode & GF_TXT_SCROLL_OUT)) ctx->scroll_time /= 2; } else { - gf_node_list_add_child( &priv->dlist->children, (GF_Node*)form); - gf_node_register((GF_Node *) form, (GF_Node *) priv->dlist); - priv->tr_scroll = NULL; + gf_node_list_add_child( &ctx->dlist->children, (GF_Node*)form); + gf_node_register((GF_Node *) form, (GF_Node *) ctx->dlist); + ctx->tr_scroll = NULL; } if (is_utf_16) { @@ -909,7 +918,6 @@ static void TTD_ApplySample(TTDPriv *priv, GF_TextSample *txt, u32 sdi, Bool is_ GF_StyleRecord *srec = NULL; char_offset = 0; for (i=0; istyles->entry_count; i++) { - TTDTextChunk *tc; srec = &txt->styles->styles[i]; if (srec->startCharOffset==srec->endCharOffset) continue; /*handle not continuous modifiers*/ @@ -949,13 +957,13 @@ static void TTD_ApplySample(TTDPriv *priv, GF_TextSample *txt, u32 sdi, Bool is_ /*apply all other modifiers*/ i=0; while ((a = (GF_Box*)gf_list_enum(txt->others, &i))) { - TTD_SplitChunks(txt, char_count, chunks, a); + ttd_split_chunks(txt, char_count, chunks, a); } while (gf_list_count(chunks)) { tc = (TTDTextChunk*)gf_list_get(chunks, 0); gf_list_rem(chunks, 0); - TTD_NewTextChunk(priv, td, form, utf16_text, tc); + ttd_new_text_chunk(ctx, td, form, utf16_text, tc); gf_free(tc); } gf_list_del(chunks); @@ -973,7 +981,6 @@ static void TTD_ApplySample(TTDPriv *priv, GF_TextSample *txt, u32 sdi, Bool is_ start_idx = 0; for (i=0; idlist, NULL); + gf_node_changed((GF_Node *) ctx->dlist, NULL); - if (gf_list_count(priv->blink_nodes)) { + if (gf_list_count(ctx->blink_nodes)) { /*restart time sensor*/ - priv->ts_blink->startTime = gf_node_get_scene_time((GF_Node *) priv->ts_blink); - gf_node_changed((GF_Node *) priv->ts_blink, NULL); + ctx->ts_blink->startTime = gf_node_get_scene_time((GF_Node *) ctx->ts_blink); + gf_node_changed((GF_Node *) ctx->ts_blink, NULL); } - priv->is_active = GF_TRUE; + ctx->timer_active = GF_TRUE; /*scroll timer also acts as AU timer*/ - priv->ts_scroll->startTime = gf_node_get_scene_time((GF_Node *) priv->ts_scroll); - priv->ts_scroll->stopTime = priv->ts_scroll->startTime - 1.0; - priv->ts_scroll->cycleInterval = sample_duration; - priv->ts_scroll->cycleInterval /= priv->cfg->timescale; - priv->ts_scroll->cycleInterval -= 0.1; - gf_node_changed((GF_Node *) priv->ts_scroll, NULL); + ctx->ts_scroll->startTime = gf_node_get_scene_time((GF_Node *) ctx->ts_scroll); + ctx->ts_scroll->stopTime = ctx->ts_scroll->startTime - 1.0; + ctx->ts_scroll->cycleInterval = sample_duration; + ctx->ts_scroll->cycleInterval /= ctx->cfg->timescale; + ctx->ts_scroll->cycleInterval -= 0.1; + gf_node_changed((GF_Node *) ctx->ts_scroll, NULL); } -static GF_Err TTD_ProcessData(GF_SceneDecoder*plug, const char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 AU_time, u32 mmlevel) +static void ttd_toggle_display(GF_TTXTDec *ctx) { - GF_BitStream *bs; - GF_Err e = GF_OK; - TTDPriv *priv = (TTDPriv *)plug->privateStack; - - bs = gf_bs_new(inBuffer, inBufferLength, GF_BITSTREAM_READ); - while (gf_bs_available(bs)) { - GF_TextSample *txt; - Bool is_utf_16; - u32 type, /*length, */sample_index, sample_duration; - is_utf_16 = (Bool)gf_bs_read_int(bs, 1); - gf_bs_read_int(bs, 4); - type = gf_bs_read_int(bs, 3); - /*length = */gf_bs_read_u16(bs); - - /*currently only full text samples are supported*/ - if (type != 1) { - gf_bs_del(bs); - return GF_NOT_SUPPORTED; + if (!ctx->scenegraph) return; + + if (ctx->is_playing) { + if (!ctx->graph_registered) { + ttd_reset_display(ctx); + ttd_update_size_info(ctx); + gf_scene_register_extra_graph(ctx->scene, ctx->scenegraph, GF_FALSE); + ctx->graph_registered = GF_TRUE; + } + } else { + if (ctx->graph_registered) { + gf_scene_register_extra_graph(ctx->scene, ctx->scenegraph, GF_TRUE); + ctx->graph_registered = GF_FALSE; } - sample_index = gf_bs_read_u8(bs); - /*duration*/ - sample_duration = gf_bs_read_u24(bs); - /*txt length is parsed with the sample*/ - txt = gf_isom_parse_texte_sample(bs); - TTD_ApplySample(priv, txt, sample_index, is_utf_16, sample_duration); - gf_isom_delete_text_sample(txt); - /*since we support only TTU(1), no need to go on*/ - break; } - gf_bs_del(bs); - return e; } -static u32 TTD_CanHandleStream(GF_BaseDecoder *ifce, u32 StreamType, GF_ESD *esd, u8 PL) +static Bool ttd_process_event(GF_Filter *filter, const GF_FilterEvent *evt) { - /*TTDPriv *priv = (TTDPriv *)ifce->privateStack;*/ - if (StreamType!=GF_STREAM_TEXT) return GF_CODEC_NOT_SUPPORTED; - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; + GF_TTXTDec *ctx = gf_filter_get_udta(filter); - if (esd->decoderConfig->objectTypeIndication==0x08) return GF_CODEC_SUPPORTED; - - return GF_CODEC_NOT_SUPPORTED; -} + //check for scene attach + switch (evt->base.type) { + case GF_FEVT_ATTACH_SCENE: + break; + case GF_FEVT_RESET_SCENE: + if (ctx->opid != evt->attach_scene.on_pid) return GF_TRUE; + ctx->is_playing = GF_FALSE; + ttd_toggle_display(ctx); + ttd_reset_scene(ctx); + ctx->scene = NULL; + return GF_TRUE; + case GF_FEVT_PLAY: + ctx->is_playing = GF_TRUE; + ttd_toggle_display(ctx); + return GF_FALSE; + case GF_FEVT_STOP: + ctx->is_playing = GF_FALSE; + ttd_toggle_display(ctx); + return GF_FALSE; + default: + return GF_FALSE; + } + if (ctx->opid != evt->attach_scene.on_pid) return GF_TRUE; + ctx->odm = evt->attach_scene.object_manager; + ctx->scene = ctx->odm->subscene ? ctx->odm->subscene : ctx->odm->parentscene; -void DeleteTimedTextDec(GF_BaseDecoder *plug) -{ - TTDPriv *priv = (TTDPriv *)plug->privateStack; - /*in case something went wrong*/ - if (priv->cfg) gf_odf_desc_del((GF_Descriptor *) priv->cfg); - gf_free(priv); - gf_free(plug); + /*timedtext cannot be a root scene object*/ + if (ctx->odm->subscene) { + ctx->odm = NULL; + ctx->scene = NULL; + } else { + ttd_setup_scene(ctx); + ttd_toggle_display(ctx); + } + return GF_TRUE; } -GF_BaseDecoder *NewTimedTextDec() +static GF_Err ttd_process(GF_Filter *filter) { - TTDPriv *priv; - GF_SceneDecoder *tmp; - - GF_SAFEALLOC(tmp, GF_SceneDecoder); - if (!tmp) return NULL; - GF_SAFEALLOC(priv, TTDPriv); - - tmp->privateStack = priv; - tmp->AttachStream = TTD_AttachStream; - tmp->DetachStream = TTD_DetachStream; - tmp->GetCapabilities = TTD_GetCapabilities; - tmp->SetCapabilities = TTD_SetCapabilities; - tmp->ProcessData = TTD_ProcessData; - tmp->AttachScene = TTD_AttachScene; - tmp->CanHandleStream = TTD_CanHandleStream; - tmp->ReleaseScene = TTD_ReleaseScene; - GF_REGISTER_MODULE_INTERFACE(tmp, GF_SCENE_DECODER_INTERFACE, "GPAC TimedText Decoder", "gpac distribution") - return (GF_BaseDecoder *) tmp; -} + const char *pck_data; + u32 pck_size, obj_time, timescale; + u64 cts; + GF_FilterPacket *pck; + GF_TTXTDec *ctx = gf_filter_get_udta(filter); + + if (!ctx->scene) { + if (ctx->is_playing) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } -#if !defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_MEDIA_IMPORT) -void DeleteTTReader(void *ifce); -void *NewTTReader(); -#endif + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (!ctx->is_eos) { + gf_filter_pid_set_eos(ctx->opid); + ctx->ts_blink->stopTime = gf_node_get_scene_time((GF_Node *) ctx->ts_blink); + gf_node_changed((GF_Node *) ctx->ts_blink, NULL); + ctx->ts_scroll->stopTime = gf_node_get_scene_time((GF_Node *) ctx->ts_scroll); + gf_node_changed((GF_Node *) ctx->ts_scroll, NULL); + ctx->is_eos = GF_TRUE; + } + return GF_EOS; + } + return GF_OK; + } + ctx->is_eos = GF_FALSE; -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - switch (InterfaceType) { - case GF_SCENE_DECODER_INTERFACE: - return (GF_BaseInterface *)NewTimedTextDec(); -#if !defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_MEDIA_IMPORT) - case GF_NET_CLIENT_INTERFACE: - return (GF_BaseInterface *)NewTTReader(); -#endif - default: - return NULL; + //object clock shall be valid + assert(ctx->odm->ck); + cts = gf_filter_pck_get_cts( pck ); + timescale = gf_filter_pck_get_timescale(pck); + + gf_odm_check_buffering(ctx->odm, ctx->ipid); + + //we still process any frame before our clock time even when buffering + obj_time = gf_clock_time(ctx->odm->ck); + if (cts * 1000 > obj_time * timescale) { + Double ts_offset = (Double) cts; + ts_offset /= timescale; + + gf_sc_sys_frame_pending(ctx->scene->compositor, ts_offset, obj_time, filter); + return GF_OK; } + + pck_data = gf_filter_pck_get_data(pck, &pck_size); + gf_bs_reassign_buffer(ctx->bs_r, pck_data, pck_size); + + while (gf_bs_available(ctx->bs_r)) { + GF_TextSample *txt; + Bool is_utf_16=0; + u32 type, /*length, */sample_index, sample_duration; + + if (!ctx->is_tx3g) { + is_utf_16 = (Bool)gf_bs_read_int(ctx->bs_r, 1); + gf_bs_read_int(ctx->bs_r, 4); + type = gf_bs_read_int(ctx->bs_r, 3); + /*length = */gf_bs_read_u16(ctx->bs_r); + + /*currently only full text samples are supported*/ + if (type != 1) { + return GF_NOT_SUPPORTED; + } + sample_index = gf_bs_read_u8(ctx->bs_r); + /*duration*/ + sample_duration = gf_bs_read_u24(ctx->bs_r); + } else { + sample_index = 1; + /*duration*/ + sample_duration = gf_filter_pck_get_duration(pck); + } + /*txt length is parsed with the sample*/ + txt = gf_isom_parse_text_sample(ctx->bs_r); + if (!txt) return GF_NON_COMPLIANT_BITSTREAM; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[TTXTDec] Applying new sample - duration %d text \"%s\"\n", sample_duration, txt->text ? txt->text : "")); + ttd_apply_sample(ctx, txt, sample_index, is_utf_16, sample_duration); + gf_isom_delete_text_sample(txt); + + /*since we support only TTU(1), no need to go on*/ + if (!ctx->is_tx3g) { + break; + } else { + //tx3g mode, single sample per AU + assert(gf_bs_available(ctx->bs_r)==0); + break; + } + } + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; } -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) +static GF_Err ttd_initialize(GF_Filter *filter) { - switch (ifce->InterfaceType) { - case GF_SCENE_DECODER_INTERFACE: - DeleteTimedTextDec((GF_BaseDecoder *)ifce); - break; -#if !defined(GPAC_DISABLE_ISOM_WRITE) && !defined(GPAC_DISABLE_MEDIA_IMPORT) - case GF_NET_CLIENT_INTERFACE: - DeleteTTReader(ifce); - break; -#endif - } + GF_TTXTDec *ctx = gf_filter_get_udta(filter); + ctx->bs_r = gf_bs_new((char *) "", 1, GF_BITSTREAM_READ); + return GF_OK; } -#else +void ttd_finalize(GF_Filter *filter) +{ + GF_TTXTDec *ctx = gf_filter_get_udta(filter); -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) { - return NULL; + ttd_reset_scene(ctx); + + if (ctx->cfg) gf_odf_desc_del((GF_Descriptor *) ctx->cfg); + gf_bs_del(ctx->bs_r); } -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) {} -#endif /*!defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_ISOM)*/ +#define OFFS(_n) #_n, offsetof(GF_TTXTDec, _n) +static const GF_FilterArgs TTXTDecArgs[] = +{ + { OFFS(texture), "use texturing for output text", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(outline), "draw text outline", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(txtw), "default width in standalone rendering", GF_PROP_UINT, "400", NULL, 0}, + { OFFS(txth), "default height in standalone rendering", GF_PROP_UINT, "200", NULL, 0}, + {0} +}; + +static const GF_FilterCapability TTXTDecCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_TEXT_MPEG4), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_TX3G), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +GF_FilterRegister TTXTDecRegister = { + .name = "ttxtdec", + GF_FS_SET_DESCRIPTION("TTXT/TX3G decoder") + GF_FS_SET_HELP("This filter decodes TTXT/TX3G streams into a BIFS scene graph of the compositor filter.\n" + "The TTXT documentation is available at https://wiki.gpac.io/TTXT-Format-Documentation\n") + .private_size = sizeof(GF_TTXTDec), + .flags = GF_FS_REG_MAIN_THREAD, + .args = TTXTDecArgs, + .priority = 1, + SETCAPS(TTXTDecCaps), + .initialize = ttd_initialize, + .finalize = ttd_finalize, + .process = ttd_process, + .configure_pid = ttd_configure_pid, + .process_event = ttd_process_event, +}; + +#endif -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() +const GF_FilterRegister *ttxtdec_register(GF_FilterSession *session) { - static u32 si [] = { #if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_ISOM) - GF_SCENE_DECODER_INTERFACE, - GF_NET_CLIENT_INTERFACE, + return &TTXTDecRegister; +#else + return NULL; #endif - 0 - }; - return si; } -GPAC_MODULE_STATIC_DECLARATION( timedtext ) + + diff --git a/src/filters/dec_vorbis.c b/src/filters/dec_vorbis.c new file mode 100644 index 0000000..18be83f --- /dev/null +++ b/src/filters/dec_vorbis.c @@ -0,0 +1,313 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2019 + * All rights reserved + * + * This file is part of GPAC / XIPH Vorbis decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include +#include + +#ifdef GPAC_HAS_VORBIS + +#include + +#if !defined(__GNUC__) +# if defined(_WIN32_WCE) || defined (WIN32) +# pragma comment(lib, "libvorbis_static") +# endif +#endif + +typedef struct +{ + GF_FilterPid *ipid, *opid; + u32 cfg_crc, sample_rate, nb_chan, timescale; + u64 last_cts; + + vorbis_info vi; + vorbis_dsp_state vd; + vorbis_block vb; + vorbis_comment vc; + ogg_packet op; + + Bool has_reconfigured; +} GF_VorbisDecCtx; + +static GF_Err vorbisdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + ogg_packet oggpacket; + GF_BitStream *bs; + GF_VorbisDecCtx *ctx = gf_filter_get_udta(filter); + + + if (is_remove) { + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + ctx->ipid = NULL; + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (p && p->value.data.ptr && p->value.data.size) { + u32 ex_crc; + if (strncmp(&p->value.data.ptr[3], "vorbis", 6)) return GF_NON_COMPLIANT_BITSTREAM; + ex_crc = gf_crc_32(p->value.data.ptr, p->value.data.size); + if (ctx->cfg_crc == ex_crc) return GF_OK; + ctx->cfg_crc = ex_crc; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[XVID] Reconfiguring without DSI not yet supported\n")); + return GF_NOT_SUPPORTED; + } + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, pid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + + if (ctx->ipid) { + vorbis_block_clear(&ctx->vb); + vorbis_dsp_clear(&ctx->vd); + vorbis_info_clear(&ctx->vi); + vorbis_comment_clear(&ctx->vc); + } + ctx->ipid = pid; + gf_filter_pid_set_framing_mode(ctx->ipid, GF_TRUE); + + vorbis_info_init(&ctx->vi); + vorbis_comment_init(&ctx->vc); + + oggpacket.granulepos = -1; + oggpacket.b_o_s = 1; + oggpacket.e_o_s = 0; + oggpacket.packetno = 0; + + bs = gf_bs_new(p->value.data.ptr, p->value.data.size, GF_BITSTREAM_READ); + while (gf_bs_available(bs)) { + GF_Err e = GF_OK; + oggpacket.bytes = gf_bs_read_u16(bs); + oggpacket.packet = gf_malloc(sizeof(char) * oggpacket.bytes); + gf_bs_read_data(bs, oggpacket.packet, oggpacket.bytes); + if (vorbis_synthesis_headerin(&ctx->vi, &ctx->vc, &oggpacket) < 0 ) { + e = GF_NON_COMPLIANT_BITSTREAM; + } + gf_free(oggpacket.packet); + if (e) { + gf_bs_del(bs); + return e; + } + } + vorbis_synthesis_init(&ctx->vd, &ctx->vi); + vorbis_block_init(&ctx->vd, &ctx->vb); + gf_bs_del(bs); + + return GF_OK; +} + +static GFINLINE void vorbis_to_intern(u32 samples, Float **pcm, char *buf, u32 channels) +{ + u32 i, j; + s32 val; + ogg_int16_t *data = (ogg_int16_t*)buf ; + + for (i=0 ; i2) { + /*center is third in gpac*/ + if (i==1) ptr = &data[2]; + /*right is 2nd in gpac*/ + else if (i==2) ptr = &data[1]; + /*LFE is 4th in gpac*/ + if ((channels==6) && (i>3)) { + if (i==6) ptr = &data[4]; /*LFE*/ + else ptr = &data[i+1]; /*back l/r*/ + } + } + + mono = pcm[i]; + for (j=0; j 32767) val = 32767; + if (val < -32768) val = -32768; + (*ptr) = val; + ptr += channels; + } + } +} + +static GF_Err vorbisdec_process(GF_Filter *filter) +{ + ogg_packet op; + u8 *buffer=NULL; + Float **pcm; + u32 samples, total_samples, total_bytes, size; + GF_VorbisDecCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck=NULL; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (!gf_filter_pid_is_eos(ctx->ipid)) { + return GF_OK; + } + } + op.granulepos = -1; + op.b_o_s = 0; + op.e_o_s = 0; + op.packetno = 0; + + if (pck) { + u32 psize; + op.packet = (u8 *) gf_filter_pck_get_data(pck, &psize); + op.bytes = psize; + } else { + op.packet = NULL; + op.bytes = 0; + } + + if (vorbis_synthesis(&ctx->vb, &op) == 0) + vorbis_synthesis_blockin(&ctx->vd, &ctx->vb) ; + + if ( (ctx->vi.channels != ctx->nb_chan) || (ctx->vi.rate != ctx->sample_rate) ) { + u64 chan_mask = 0; + ctx->nb_chan = ctx->vi.channels; + ctx->sample_rate = ctx->vi.rate; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(ctx->sample_rate) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(ctx->nb_chan) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(GF_AUDIO_FMT_S16) ); + + switch (ctx->vi.channels) { + case 1: + chan_mask = GF_AUDIO_CH_FRONT_CENTER; + break; + case 2: + chan_mask = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT; + break; + case 3: + chan_mask = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER; + break; + case 4: + chan_mask = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT; + break; + case 5: + chan_mask = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT; + break; + case 6: + chan_mask = GF_AUDIO_CH_FRONT_LEFT | GF_AUDIO_CH_FRONT_RIGHT | GF_AUDIO_CH_FRONT_CENTER | GF_AUDIO_CH_SURROUND_LEFT | GF_AUDIO_CH_SURROUND_RIGHT | GF_AUDIO_CH_LFE; + break; + } + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CHANNEL_LAYOUT, &PROP_LONGUINT(chan_mask) ); + + } + size = (ctx->vd.pcm_current - ctx->vd.pcm_returned) * 2 * ctx->vi.channels; + if (size) dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &buffer); + + if (pck && dst_pck) gf_filter_pck_merge_properties(pck, dst_pck); + + /*trust vorbis max block info*/ + total_samples = 0; + total_bytes = 0; + while ((samples = vorbis_synthesis_pcmout(&ctx->vd, &pcm)) > 0) { + vorbis_to_intern(samples, pcm, (char*) buffer + total_bytes, ctx->vi.channels); + total_bytes += samples * 2 * ctx->vi.channels; + total_samples += samples; + vorbis_synthesis_read(&ctx->vd, samples); + } + if (!size) { + if (pck) return GF_OK; + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + + if (pck) { + ctx->last_cts = gf_filter_pck_get_cts(pck); + ctx->timescale = gf_filter_pck_get_timescale(pck); + gf_filter_pid_drop_packet(ctx->ipid); + } + gf_filter_pck_set_cts(dst_pck, ctx->last_cts); + + if (ctx->timescale != ctx->sample_rate) { + u64 dur = total_samples * ctx->timescale; + dur /= ctx->sample_rate; + gf_filter_pck_set_duration(dst_pck, (u32) dur); + ctx->last_cts += dur; + } else { + gf_filter_pck_set_duration(dst_pck, total_samples); + ctx->last_cts += total_samples; + } + + assert(size == total_bytes); + gf_filter_pck_send(dst_pck); + return GF_OK; +} + +static void vorbisdec_finalize(GF_Filter *filter) +{ + GF_VorbisDecCtx *ctx = gf_filter_get_udta(filter); + + vorbis_block_clear(&ctx->vb); + vorbis_dsp_clear(&ctx->vd); + vorbis_info_clear(&ctx->vi); + vorbis_comment_clear(&ctx->vc); +} + +static const GF_FilterCapability VorbisDecCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_VORBIS), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +GF_FilterRegister VorbisDecRegister = { + .name = "vorbisdec", + GF_FS_SET_DESCRIPTION("Vorbis decoder") + GF_FS_SET_HELP("This filter decodes Vorbis streams through libvorbis library.") + .private_size = sizeof(GF_VorbisDecCtx), + .priority = 1, + SETCAPS(VorbisDecCaps), + .finalize = vorbisdec_finalize, + .configure_pid = vorbisdec_configure_pid, + .process = vorbisdec_process, +}; + +#endif + +const GF_FilterRegister *vorbisdec_register(GF_FilterSession *session) +{ +#ifdef GPAC_HAS_VORBIS + return &VorbisDecRegister; +#else + return NULL; +#endif +} + + diff --git a/src/filters/dec_vtb.c b/src/filters/dec_vtb.c new file mode 100644 index 0000000..9ad6e6d --- /dev/null +++ b/src/filters/dec_vtb.c @@ -0,0 +1,2017 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2018 + * All rights reserved + * + * This file is part of GPAC / VideoToolBox decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +//do not include math.h we would have a conflict with Fixed ... we're lucky we don't need maths routines here +#define _GF_MATH_H_ + +#include + +#if !defined(GPAC_DISABLE_AV_PARSERS) && ( defined(GPAC_CONFIG_DARWIN) || defined(GPAC_CONFIG_IOS) ) + +#include + +#define Picture QuickdrawPicture +#include +#undef Picture + +#ifndef kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder +# define kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder CFSTR("RequireHardwareAcceleratedVideoDecoder") +#endif + +#include +#include +#include +#include + +#include "../../src/compositor/gl_inc.h" + + +#ifdef GPAC_CONFIG_IOS +#define VTB_GL_TEXTURE + +#define GF_CVGLTextureREF CVOpenGLESTextureRef +#define GF_CVGLTextureCacheREF CVOpenGLESTextureCacheRef +#define GF_kCVPixelBufferOpenGLCompatibilityKey kCVPixelBufferOpenGLESCompatibilityKey +#define GF_CVOpenGLTextureCacheFlush CVOpenGLESTextureCacheFlush +#define GF_CVOpenGLTextureGetTarget CVOpenGLESTextureGetTarget +#define GF_CVOpenGLTextureGetName CVOpenGLESTextureGetName + +#else + +//not working yet, not sure why +//#define VTB_GL_TEXTURE + +#include + +#define GF_CVGLTextureREF CVOpenGLTextureRef +#define GF_CVGLTextureCacheREF CVOpenGLTextureCacheRef +#define GF_kCVPixelBufferOpenGLCompatibilityKey kCVPixelBufferOpenGLCompatibilityKey +#define GF_CVOpenGLTextureCacheFlush CVOpenGLTextureCacheFlush +#define GF_CVOpenGLTextureGetTarget CVOpenGLTextureGetTarget +#define GF_CVOpenGLTextureGetName CVOpenGLTextureGetName + + +#endif + +#ifndef GPAC_DISABLE_AV_PARSERS + +typedef struct +{ + //opts + u32 reorder, ofmt; + Bool no_copy; + Bool disable_hw; + + //internal +// GF_FilterPid *ipid; + GF_List *streams; + GF_FilterPid *opid; + u32 width, height, stride; + GF_Fraction pixel_ar; + u32 pix_fmt; + u32 out_size; + u32 cfg_crc; + u32 codecid; + Bool is_hardware; + + GF_Err last_error; + + int vtb_type; + VTDecompressionSessionRef vtb_session; + CMFormatDescriptionRef fmt_desc; + + GF_List *frames, *frames_res; + GF_FilterPacket *cur_pck; + + u8 chroma_format, luma_bit_depth, chroma_bit_depth; + Bool frame_size_changed; + Bool reorder_detected; + Bool drop_non_refs; + + volatile u32 decoded_frames_pending; + u32 reorder_probe; + Bool reconfig_needed; + u64 last_cts_out; + u32 last_timescale_out; + + //MPEG-1/2 specific + Bool init_mpeg12; + + //MPEG-4 specific + Bool skip_mpeg4_vosh; + char *vosh; + u32 vosh_size; + + //NAL-based specific + GF_BitStream *nal_bs; + GF_BitStream *ps_bs; + + GF_BitStream *nalu_rewrite_bs; + u8 *nalu_buffer; + u32 nalu_buffer_alloc; + + Bool is_avc; + Bool is_annex_b; + + u32 nalu_size_length; + + GF_List *SPSs, *PPSs, *VPSs; + s32 active_sps, active_pps, active_vps; + u32 active_sps_crc, active_pps_crc, active_vps_crc; + + AVCState avc; + Bool check_h264_isma; + + HEVCState hevc; + Bool is_hevc; + + Bool profile_supported, can_reconfig; + u32 nb_consecutive_errors; + //openGL output +#ifdef VTB_GL_TEXTURE + Bool use_gl_textures; + GF_CVGLTextureCacheREF cache_texture; +#endif + void *gl_context; + + struct __vtb_frame_ifce *last_frame_sent; +} GF_VTBDecCtx; + + +typedef struct __vtb_frame_ifce +{ + GF_FilterFrameInterface frame_ifce; + + Bool locked; + CVPixelBufferRef frame; + GF_VTBDecCtx *ctx; + GF_FilterPacket *pck_src; + //openGL mode +#ifdef VTB_GL_TEXTURE + GF_CVGLTextureREF y, u, v; +#endif +} GF_VTBHWFrame; + +static void vtbdec_delete_decoder(GF_VTBDecCtx *ctx); +static GF_Err vtbdec_flush_frame(GF_Filter *filter, GF_VTBDecCtx *ctx); + +static void vtbdec_on_frame(void *opaque, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags flags, CVImageBufferRef image, CMTime pts, CMTime duration) +{ + GF_VTBDecCtx *ctx = (GF_VTBDecCtx *)opaque; + GF_VTBHWFrame *frame; + u32 i, count, timescale; + u64 cts, dts; + assert(ctx->cur_pck); + + if (!image) { + if (status != kCVReturnSuccess) { + ctx->last_error = GF_NON_COMPLIANT_BITSTREAM; + ctx->nb_consecutive_errors++; + //if we can reconfigure and this is a SAP, reconfig if too many errors or first frame after reconfig + if (ctx->can_reconfig && gf_filter_pck_get_sap(ctx->cur_pck) + && (!ctx->profile_supported || (ctx->nb_consecutive_errors>10)) + ) { + ctx->last_error = GF_PROFILE_NOT_SUPPORTED; + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Decode error - status %d - trying filter chain reload\n", status)); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Decode error - status %d\n", status)); + } + return; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[VTB] No output buffer\n")); + return; + } + if (gf_filter_pck_get_seek_flag(ctx->cur_pck) ) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[VTB] Frame marked as seek, not dispatching - status %d\n", status)); + return; + } + + if (ctx->reorder_probe) { + ctx->reorder_probe--; + } + + ctx->profile_supported = GF_TRUE; + ctx->nb_consecutive_errors=0; + frame = gf_list_pop_back(ctx->frames_res); + if (!frame) { + GF_SAFEALLOC(frame, GF_VTBHWFrame); + if (!frame) return; + } else { + memset(frame, 0, sizeof(GF_VTBHWFrame)); + } + + assert( gf_filter_pck_get_seek_flag(ctx->cur_pck) == 0 ); + + frame->frame_ifce.user_data = frame; + frame->frame = CVPixelBufferRetain(image); + frame->pck_src = ctx->cur_pck; + gf_filter_pck_ref_props(&frame->pck_src); + + frame->ctx = ctx; + cts = gf_filter_pck_get_cts(frame->pck_src); + dts = gf_filter_pck_get_dts(frame->pck_src); + timescale = gf_filter_pck_get_timescale(frame->pck_src); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[VTB] Decoded frame DTS "LLU" CTS "LLU" timescale %d\n", dts, cts, timescale)); + + if (!ctx->last_timescale_out) + ctx->last_timescale_out = gf_filter_pck_get_timescale(frame->pck_src); + + count = gf_list_count(ctx->frames); + for (i=0; iframes, i); + Bool insert = GF_FALSE; + u64 acts, adts, atimescale; + s64 diff; + + acts = gf_filter_pck_get_cts(aframe->pck_src); + adts = gf_filter_pck_get_dts(aframe->pck_src); + atimescale = gf_filter_pck_get_timescale(aframe->pck_src); + + if (adts > dts) { + ctx->reorder_probe=0; + ctx->reorder_detected=GF_FALSE; + break; + } + if ((timescale == atimescale) && (ctx->last_timescale_out == timescale)) { + diff = (s64) acts - (s64) cts; + if ((diff>0) && (cts > ctx->last_cts_out) ) { + insert = GF_TRUE; + } + } else { + diff = (s64) (acts * timescale) - (s64) (cts * atimescale); + if ((diff>0) && (ctx->last_timescale_out * cts > timescale * ctx->last_cts_out) ) { + insert = GF_TRUE; + } + } + if (insert) { + gf_list_insert(ctx->frames, frame, i); + ctx->reorder_detected = GF_TRUE; + return; + } + } + gf_list_add(ctx->frames, frame); +} + +static CFDictionaryRef vtbdec_create_buffer_attributes(GF_VTBDecCtx *ctx, OSType pix_fmt) +{ + CFMutableDictionaryRef buffer_attributes; + CFMutableDictionaryRef surf_props; + CFNumberRef w; + CFNumberRef h; + CFNumberRef pixel_fmt; + + w = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &ctx->width); + h = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &ctx->height); + pixel_fmt = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pix_fmt); + + buffer_attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + surf_props = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + CFDictionarySetValue(buffer_attributes, kCVPixelBufferWidthKey, w); + CFRelease(w); + CFDictionarySetValue(buffer_attributes, kCVPixelBufferHeightKey, h); + CFRelease(h); + CFDictionarySetValue(buffer_attributes, kCVPixelBufferPixelFormatTypeKey, pixel_fmt); + CFRelease(pixel_fmt); + +#ifdef VTB_GL_TEXTURE + if (ctx->use_gl_textures) + CFDictionarySetValue(buffer_attributes, GF_kCVPixelBufferOpenGLCompatibilityKey, kCFBooleanTrue); +#endif + + CFDictionarySetValue(buffer_attributes, kCVPixelBufferIOSurfacePropertiesKey, surf_props); + CFRelease(surf_props); + + return buffer_attributes; +} + +static GF_Err vtbdec_init_decoder(GF_Filter *filter, GF_VTBDecCtx *ctx) +{ + CFMutableDictionaryRef dec_dsi, dec_type; + CFMutableDictionaryRef dsi; + VTDecompressionOutputCallbackRecord cbacks; + CFDictionaryRef buffer_attribs; + OSStatus status; + OSType kColorSpace; + const GF_PropertyValue *p; + CFDataRef data = NULL; + u8 *dsi_data=NULL; + u32 dsi_data_size=0; + u32 w, h; + GF_FilterPid *pid; + w = h = 0; + + dec_dsi = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + if (ctx->ofmt==1) { + kColorSpace = kCVPixelFormatType_420YpCbCr8Planar; + ctx->pix_fmt = GF_PIXEL_YUV; + } else if (ctx->ofmt==2) { + kColorSpace = kCVPixelFormatType_24RGB; + ctx->pix_fmt = GF_PIXEL_RGB; + } else { + kColorSpace = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; + ctx->pix_fmt = GF_PIXEL_NV12; + } + + ctx->reorder_probe = ctx->reorder; + ctx->reorder_detected = GF_FALSE; + pid = gf_list_get(ctx->streams, 0); + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + if (p) w = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + if (p) h = p->value.uint; + + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + + switch (ctx->codecid) { + case GF_CODECID_AVC: + if (gf_list_count(ctx->SPSs) && gf_list_count(ctx->PPSs)) { + s32 idx; + u32 i; + GF_AVCConfig *cfg; + GF_AVCConfigSlot *sps = NULL; + GF_AVCConfigSlot *pps = NULL; + + for (i=0; iSPSs); i++) { + sps = gf_list_get(ctx->SPSs, i); + if (ctx->active_sps<0) ctx->active_sps = sps->id; + + if (sps->id==ctx->active_sps) { + ctx->active_sps_crc = gf_crc_32(sps->data, sps->size); + break; + } + sps = NULL; + } + if (!sps) return GF_NON_COMPLIANT_BITSTREAM; + for (i=0; iPPSs); i++) { + pps = gf_list_get(ctx->PPSs, i); + if (ctx->active_pps<0) ctx->active_pps = pps->id; + + if (pps->id==ctx->active_pps) { + ctx->active_pps_crc = gf_crc_32(pps->data, pps->size); + break; + } + pps = NULL; + } + if (!pps) return GF_NON_COMPLIANT_BITSTREAM; + ctx->reconfig_needed = GF_FALSE; + + ctx->vtb_type = kCMVideoCodecType_H264; + + if (gf_media_avc_read_sps(sps->data, sps->size, &ctx->avc, 0, NULL)<0) + return GF_NON_COMPLIANT_BITSTREAM; + if (gf_media_avc_read_pps(pps->data, pps->size, &ctx->avc)<0) + return GF_NON_COMPLIANT_BITSTREAM; + + idx = ctx->active_sps; + ctx->width = ctx->avc.sps[idx].width; + ctx->height = ctx->avc.sps[idx].height; + if (ctx->avc.sps[idx].vui.par_num && ctx->avc.sps[idx].vui.par_den) { + ctx->pixel_ar.num = ctx->avc.sps[idx].vui.par_num; + ctx->pixel_ar.den = ctx->avc.sps[idx].vui.par_den; + } else { + ctx->pixel_ar.num = ctx->pixel_ar.den = 1; + } + ctx->chroma_format = ctx->avc.sps[idx].chroma_format; + ctx->luma_bit_depth = 8 + ctx->avc.sps[idx].luma_bit_depth_m8; + ctx->chroma_bit_depth = 8 + ctx->avc.sps[idx].chroma_bit_depth_m8; + + switch (ctx->chroma_format) { + case 2: +#ifndef GPAC_CONFIG_IOS + //422 decoding doesn't seem supported ... + if (ctx->luma_bit_depth>8) { + kColorSpace = kCVPixelFormatType_422YpCbCr10; + ctx->pix_fmt = GF_PIXEL_YUV422_10; + } else +#endif + { + kColorSpace = kCVPixelFormatType_422YpCbCr8; + ctx->pix_fmt = GF_PIXEL_YUV422; + } + break; + case 3: +#ifndef GPAC_CONFIG_IOS + if (ctx->luma_bit_depth>8) { + kColorSpace = kCVPixelFormatType_444YpCbCr10; + ctx->pix_fmt = GF_PIXEL_YUV444_10; + } else +#endif + { + kColorSpace = kCVPixelFormatType_444YpCbCr8; + ctx->pix_fmt = GF_PIXEL_YUV444; + } + break; + default: +#if !defined(GPAC_CONFIG_IOS) && defined(AVAILABLE_MAC_OS_X_VERSION_10_13_AND_LATER) + if (ctx->luma_bit_depth>8) { + kColorSpace = kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange; + ctx->pix_fmt = GF_PIXEL_NV12_10; + } +#endif + break; + } + //always rewrite with current sps and pps + cfg = gf_odf_avc_cfg_new(); + cfg->configurationVersion = 1; + cfg->profile_compatibility = ctx->avc.sps[idx].prof_compat; + cfg->AVCProfileIndication = ctx->avc.sps[idx].profile_idc; + cfg->AVCLevelIndication = ctx->avc.sps[idx].level_idc; + cfg->chroma_format = ctx->avc.sps[idx].chroma_format; + cfg->luma_bit_depth = 8 + ctx->avc.sps[idx].luma_bit_depth_m8; + cfg->chroma_bit_depth = 8 + ctx->avc.sps[idx].chroma_bit_depth_m8; + cfg->nal_unit_size = 4; + + //we send only the active SPS and PPS, otherwise vtb complains !! + gf_list_add(cfg->sequenceParameterSets, sps); + gf_list_add(cfg->pictureParameterSets, pps); + gf_odf_avc_cfg_write(cfg, &dsi_data, &dsi_data_size); + gf_list_reset(cfg->sequenceParameterSets); + gf_list_reset(cfg->pictureParameterSets); + gf_odf_avc_cfg_del((cfg)); + + dsi = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)dsi_data, dsi_data_size); + if (data) { + CFDictionarySetValue(dsi, CFSTR("avcC"), data); + CFDictionarySetValue(dec_dsi, kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms, dsi); + CFRelease(data); + } + CFRelease(dsi); + + gf_free(dsi_data); + } + break; + + case GF_CODECID_HEVC: + if (gf_list_count(ctx->SPSs) && gf_list_count(ctx->PPSs) && gf_list_count(ctx->VPSs)) { + s32 idx; + u32 i; + GF_HEVCConfig *cfg; + GF_HEVCParamArray *vpsa = NULL; + GF_HEVCParamArray *spsa = NULL; + GF_HEVCParamArray *ppsa = NULL; + GF_AVCConfigSlot *vps = NULL; + GF_AVCConfigSlot *sps = NULL; + GF_AVCConfigSlot *pps = NULL; + + for (i=0; iVPSs); i++) { + vps = gf_list_get(ctx->VPSs, i); + if (ctx->active_vps<0) ctx->active_vps = vps->id; + + if (vps->id==ctx->active_vps) break; + vps = NULL; + } + if (!vps) return GF_NON_COMPLIANT_BITSTREAM; + + for (i=0; iSPSs); i++) { + sps = gf_list_get(ctx->SPSs, i); + if (ctx->active_sps<0) ctx->active_sps = sps->id; + + if (sps->id==ctx->active_sps) break; + sps = NULL; + } + if (!sps) return GF_NON_COMPLIANT_BITSTREAM; + for (i=0; iPPSs); i++) { + pps = gf_list_get(ctx->PPSs, i); + if (ctx->active_pps<0) ctx->active_pps = pps->id; + + if (pps->id==ctx->active_pps) break; + pps = NULL; + } + if (!pps) return GF_NON_COMPLIANT_BITSTREAM; + ctx->reconfig_needed = GF_FALSE; + + ctx->vtb_type = kCMVideoCodecType_HEVC; + + idx = ctx->active_sps; + ctx->width = ctx->hevc.sps[idx].width; + ctx->height = ctx->hevc.sps[idx].height; + if (ctx->hevc.sps[idx].aspect_ratio_info_present_flag && ctx->hevc.sps[idx].sar_width && ctx->hevc.sps[idx].sar_height) { + ctx->pixel_ar.num = ctx->hevc.sps[idx].sar_width; + ctx->pixel_ar.den = ctx->hevc.sps[idx].sar_height; + } else { + ctx->pixel_ar.num = ctx->pixel_ar.den = 1; + } + ctx->chroma_format = ctx->hevc.sps[idx].chroma_format_idc; + ctx->luma_bit_depth = ctx->hevc.sps[idx].bit_depth_luma; + ctx->chroma_bit_depth = ctx->hevc.sps[idx].bit_depth_chroma; + + switch (ctx->chroma_format) { + case 2: + //422 decoding doesn't seem supported ... + if (ctx->luma_bit_depth>8) { + kColorSpace = kCVPixelFormatType_422YpCbCr10; + ctx->pix_fmt = GF_PIXEL_YUV422_10; + } else { + kColorSpace = kCVPixelFormatType_422YpCbCr8; + ctx->pix_fmt = GF_PIXEL_YUV422; + } + break; + case 3: + if (ctx->luma_bit_depth>8) { + kColorSpace = kCVPixelFormatType_444YpCbCr10; + ctx->pix_fmt = GF_PIXEL_YUV444_10; + } else { + kColorSpace = kCVPixelFormatType_444YpCbCr8; + ctx->pix_fmt = GF_PIXEL_YUV444; + } + break; + default: + if (ctx->luma_bit_depth>8) { + kColorSpace = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; + ctx->pix_fmt = GF_PIXEL_NV12; + } + break; + } + //always rewrite with current sps and pps + cfg = gf_odf_hevc_cfg_new(); + cfg->configurationVersion = 1; + cfg->profile_space = ctx->hevc.sps[idx].ptl.profile_space; + cfg->tier_flag = ctx->hevc.sps[idx].ptl.tier_flag; + cfg->profile_idc = ctx->hevc.sps[idx].ptl.profile_idc; + cfg->general_profile_compatibility_flags = ctx->hevc.sps[idx].ptl.profile_compatibility_flag; + cfg->progressive_source_flag = ctx->hevc.sps[idx].ptl.general_progressive_source_flag; + cfg->interlaced_source_flag = ctx->hevc.sps[idx].ptl.general_interlaced_source_flag; + cfg->non_packed_constraint_flag = ctx->hevc.sps[idx].ptl.general_non_packed_constraint_flag; + cfg->frame_only_constraint_flag = ctx->hevc.sps[idx].ptl.general_frame_only_constraint_flag; + + cfg->constraint_indicator_flags = ctx->hevc.sps[idx].ptl.general_reserved_44bits; + cfg->level_idc = ctx->hevc.sps[idx].ptl.level_idc; + + cfg->luma_bit_depth = ctx->hevc.sps[idx].bit_depth_luma; + cfg->chroma_bit_depth = ctx->hevc.sps[idx].bit_depth_chroma; + cfg->chromaFormat = ctx->hevc.sps[idx].chroma_format_idc; + cfg->complete_representation = GF_TRUE; + + cfg->nal_unit_size = 4; + + GF_SAFEALLOC(vpsa, GF_HEVCParamArray); + if (!vpsa) return GF_OUT_OF_MEM; + vpsa->array_completeness = 1; + vpsa->type = GF_HEVC_NALU_VID_PARAM; + vpsa->nalus = gf_list_new(); + gf_list_add(vpsa->nalus, vps); + gf_list_add(cfg->param_array, vpsa); + + GF_SAFEALLOC(spsa, GF_HEVCParamArray); + if (!spsa) return GF_OUT_OF_MEM; + spsa->array_completeness = 1; + spsa->type = GF_HEVC_NALU_SEQ_PARAM; + spsa->nalus = gf_list_new(); + gf_list_add(spsa->nalus, sps); + gf_list_add(cfg->param_array, spsa); + + GF_SAFEALLOC(ppsa, GF_HEVCParamArray); + if (!ppsa) return GF_OUT_OF_MEM; + ppsa->array_completeness = 1; + ppsa->type = GF_HEVC_NALU_PIC_PARAM; + //we send all PPS + ppsa->nalus = ctx->PPSs; + + gf_list_add(cfg->param_array, ppsa); + + gf_odf_hevc_cfg_write(cfg, &dsi_data, &dsi_data_size); + gf_list_reset(vpsa->nalus); + gf_list_reset(spsa->nalus); + ppsa->nalus = NULL; + gf_odf_hevc_cfg_del(cfg); + + dsi = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)dsi_data, dsi_data_size); + if (data) { + CFDictionarySetValue(dsi, CFSTR("hvcC"), data); + CFDictionarySetValue(dec_dsi, kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms, dsi); + CFRelease(data); + } + CFRelease(dsi); + + gf_free(dsi_data); + } + break; + + case GF_CODECID_MPEG2_SIMPLE: + case GF_CODECID_MPEG2_MAIN: + case GF_CODECID_MPEG2_SNR: + case GF_CODECID_MPEG2_SPATIAL: + case GF_CODECID_MPEG2_HIGH: + case GF_CODECID_MPEG2_422: + + ctx->vtb_type = kCMVideoCodecType_MPEG2Video; + if (!ctx->width || !ctx->height) { + ctx->init_mpeg12 = GF_TRUE; + return GF_OK; + } + ctx->init_mpeg12 = GF_FALSE; + ctx->reconfig_needed = GF_FALSE; + break; + + case GF_CODECID_MPEG1: + ctx->vtb_type = kCMVideoCodecType_MPEG1Video; + if (!ctx->width || !ctx->height) { + ctx->init_mpeg12 = GF_TRUE; + return GF_OK; + } + ctx->init_mpeg12 = GF_FALSE; + ctx->reconfig_needed = GF_FALSE; + break; + case GF_CODECID_MPEG4_PART2 : + { + char *vosh = NULL; + u32 vosh_size = 0; + ctx->vtb_type = kCMVideoCodecType_MPEG4Video; + + if (!p || !p->value.data.ptr) { + vosh = ctx->vosh; + vosh_size = ctx->vosh_size; + } else { + vosh = p->value.data.ptr; + vosh_size = p->value.data.size; + } + ctx->reconfig_needed = GF_FALSE; + + if (vosh) { + GF_M4VDecSpecInfo vcfg; + GF_BitStream *bs; + GF_ESD *esd; + + gf_m4v_get_config(vosh, vosh_size, &vcfg); + ctx->width = vcfg.width; + ctx->height = vcfg.height; + esd = gf_odf_desc_esd_new(2); + esd->decoderConfig->decoderSpecificInfo->data = vosh; + esd->decoderConfig->decoderSpecificInfo->dataLength = vosh_size; + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + gf_bs_write_u32(bs, 0); + gf_odf_desc_write_bs((GF_Descriptor *) esd, bs); + gf_bs_get_content(bs, &dsi_data, &dsi_data_size); + gf_bs_del(bs); + esd->decoderConfig->decoderSpecificInfo->data = NULL; + esd->decoderConfig->decoderSpecificInfo->dataLength = 0; + gf_odf_desc_del((GF_Descriptor*)esd); + + dsi = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + data = CFDataCreate(kCFAllocatorDefault, (const UInt8*) dsi_data, dsi_data_size); + gf_free(dsi_data); + + if (data) { + CFDictionarySetValue(dsi, CFSTR("esds"), data); + CFDictionarySetValue(dec_dsi, kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms, dsi); + CFRelease(data); + } + CFRelease(dsi); + + ctx->skip_mpeg4_vosh = GF_FALSE; + } else { + ctx->skip_mpeg4_vosh = GF_TRUE; + return GF_OK; + } + break; + } + case GF_CODECID_H263: + case GF_CODECID_S263: + ctx->reorder_probe = 0; + ctx->reconfig_needed = GF_FALSE; + if (w && h) { + ctx->width = w; + ctx->height = h; + ctx->vtb_type = kCMVideoCodecType_H263; + break; + } + break; + + default : + ctx->reconfig_needed = GF_FALSE; + return GF_NOT_SUPPORTED; + } + //not yet ready + if (! ctx->width || !ctx->height) return GF_OK; + + /*status = */CMVideoFormatDescriptionCreate(kCFAllocatorDefault, ctx->vtb_type, ctx->width, ctx->height, dec_dsi, &ctx->fmt_desc); + + if (!ctx->fmt_desc) { + if (dec_dsi) CFRelease(dec_dsi); + return GF_NON_COMPLIANT_BITSTREAM; + } + buffer_attribs = vtbdec_create_buffer_attributes(ctx, kColorSpace); + + cbacks.decompressionOutputCallback = vtbdec_on_frame; + cbacks.decompressionOutputRefCon = ctx; + + status = 1; + if (!ctx->disable_hw) { + dec_type = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFDictionarySetValue(dec_type, kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder, kCFBooleanTrue); + ctx->is_hardware = GF_TRUE; + + status = VTDecompressionSessionCreate(NULL, ctx->fmt_desc, dec_type, buffer_attribs, &cbacks, &ctx->vtb_session); + + if (dec_type) + CFRelease(dec_type); + } + + //if HW decoder not available or disabled , try soft one + if (status) { + status = VTDecompressionSessionCreate(NULL, ctx->fmt_desc, NULL, buffer_attribs, &cbacks, &ctx->vtb_session); + ctx->is_hardware = GF_FALSE; + } + + if (dec_dsi) + CFRelease(dec_dsi); + if (buffer_attribs) + CFRelease(buffer_attribs); + + switch (status) { + case kVTVideoDecoderNotAvailableNowErr: + case kVTVideoDecoderUnsupportedDataFormatErr: + return GF_NOT_SUPPORTED; + case kVTVideoDecoderMalfunctionErr: + return GF_IO_ERR; + case kVTVideoDecoderBadDataErr : + return GF_NOT_SUPPORTED; + + case kVTPixelTransferNotSupportedErr: + case kVTCouldNotFindVideoDecoderErr: + return GF_NOT_SUPPORTED; + case 0: + break; + default: + return GF_SERVICE_ERROR; + } + + //good to go ! + ctx->stride = ctx->width; + if (ctx->pix_fmt == GF_PIXEL_YUV422) { + ctx->out_size = ctx->width*ctx->height*2; + } else if (ctx->pix_fmt == GF_PIXEL_YUV444) { + ctx->out_size = ctx->width*ctx->height*3; + } else if (ctx->pix_fmt == GF_PIXEL_RGB) { + ctx->out_size = ctx->width*ctx->height*3; + ctx->stride *= 3; + } else { + // (ctx->pix_fmt == GF_PIXEL_YUV) + ctx->out_size = ctx->width*ctx->height*3/2; + } + if (ctx->luma_bit_depth>8) { + ctx->out_size *= 2; + } + ctx->frame_size_changed = GF_TRUE; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->width) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->height) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->stride) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PAR, &PROP_FRAC(ctx->pixel_ar) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, &PROP_UINT(ctx->pix_fmt) ); + ctx->profile_supported = GF_FALSE; + ctx->can_reconfig = !gf_opts_get_bool("core", "no-reassign"); + + switch (ctx->vtb_type) { + case kCMVideoCodecType_H264: + gf_filter_set_name(filter, ctx->is_hardware ? "VTB:Hardware:AVC|H264" : "VTB:Software:AVC|H264"); + break; + case kCMVideoCodecType_MPEG2Video: + gf_filter_set_name(filter, ctx->is_hardware ? "VTB:Hardware:MPEG2" : "VTB:Software:MPEG2"); + break; + case kCMVideoCodecType_MPEG4Video: + gf_filter_set_name(filter, ctx->is_hardware ? "VTB:Hardware:MPEG4P2" : "VTB:Software:MPEG4P2"); + break; + case kCMVideoCodecType_H263: + gf_filter_set_name(filter, ctx->is_hardware ? "VTB:Hardware:H263" : "VTB:Software:H263"); + break; + case kCMVideoCodecType_MPEG1Video: + gf_filter_set_name(filter, ctx->is_hardware ? "VTB:Hardware:MPEG1" : "VTB:Software:MPEG1"); + break; + default: + break; + } + return GF_OK; +} + +static void vtbdec_register_param_sets(GF_VTBDecCtx *ctx, char *data, u32 size, Bool is_sps, u8 hevc_nal_type) +{ + Bool add = GF_TRUE; + u32 i, count; + s32 ps_id; + GF_List *dest = NULL; + + if (!ctx->ps_bs) ctx->ps_bs = gf_bs_new(data, size, GF_BITSTREAM_READ); + else gf_bs_reassign_buffer(ctx->ps_bs, data, size); + + if (hevc_nal_type) { +#ifndef GPAC_DISABLE_HEVC + if (hevc_nal_type==GF_HEVC_NALU_SEQ_PARAM) { + dest = ctx->SPSs; + ps_id = gf_media_hevc_read_sps_bs(ctx->ps_bs, &ctx->hevc); + if (ps_id<0) return; + } + else if (hevc_nal_type==GF_HEVC_NALU_PIC_PARAM) { + dest = ctx->PPSs; + ps_id = gf_media_hevc_read_pps_bs(ctx->ps_bs, &ctx->hevc); + if (ps_id<0) return; + } + else if (hevc_nal_type==GF_HEVC_NALU_VID_PARAM) { + dest = ctx->VPSs; + ps_id = gf_media_hevc_read_vps_bs(ctx->ps_bs, &ctx->hevc); + if (ps_id<0) return; + } +#endif //GPAC_DISABLE_HEVC + + } else { + dest = is_sps ? ctx->SPSs : ctx->PPSs; + + if (is_sps) { + ps_id = gf_media_avc_read_sps_bs(ctx->ps_bs, &ctx->avc, 0, NULL); + if (ps_id<0) return; + } else { + ps_id = gf_media_avc_read_pps_bs(ctx->ps_bs, &ctx->avc); + if (ps_id<0) return; + } + } + + count = gf_list_count(dest); + for (i=0; iid != ps_id) continue; + //not same size or different content but same ID, remove old xPS + if ((a_slc->size != size) || memcmp(a_slc->data, data, size) ) { + gf_free(a_slc->data); + gf_free(a_slc); + gf_list_rem(dest, i); + break; + } else { + add = GF_FALSE; + } + break; + } + if (add) { + GF_AVCConfigSlot *slc; + GF_SAFEALLOC(slc, GF_AVCConfigSlot); + if (!slc) return; + slc->data = gf_malloc(size); + if (!slc->data) { + gf_free(slc); + return; + } + memcpy(slc->data, data, size); + slc->size = size; + slc->id = ps_id; + slc->crc = gf_crc_32(data, size); + gf_list_add(dest, slc); + } +} + +static u32 vtbdec_purge_param_sets(GF_VTBDecCtx *ctx, Bool is_sps, s32 idx) +{ + u32 i, j, count, crc_res = 0; + GF_List *dest = is_sps ? ctx->SPSs : ctx->PPSs; + + //remove all xPS sharing the same ID, use only the last occurence + count = gf_list_count(dest); + for (i=0; iid != idx) continue; + crc_res = slc->crc; + + for (j=i+1; jid != slc->id) continue; + //not same size or different content but same ID, remove old xPS + if ((slc->size != a_slc->size) || memcmp(a_slc->data, slc->data, a_slc->size) ) { + crc_res = a_slc->crc; + gf_free(slc->data); + gf_free(slc); + gf_list_rem(dest, i); + i--; + count--; + break; + } + } + } + return crc_res; +} + +static void vtbdec_del_param_list(GF_List *list) +{ + while (gf_list_count(list)) { + GF_AVCConfigSlot *slc = gf_list_get(list, 0); + gf_free(slc->data); + gf_free(slc); + gf_list_rem(list, 0); + } + gf_list_del(list); +} + +static GF_Err vtbdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p, *dsi; + u32 codecid, dsi_crc; + GF_Err e; + GF_FilterPid *base_pid = NULL; + GF_VTBDecCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + gf_list_del_item(ctx->streams, pid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) { + while (gf_list_count(ctx->frames)) { + vtbdec_flush_frame(filter, ctx); + } + return GF_NOT_SUPPORTED; + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTBDec] Missing codecid, cannot initialize\n")); + return GF_NOT_SUPPORTED; + } + codecid = p->value.uint; + + base_pid = gf_list_get(ctx->streams, 0); + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DEPENDENCY_ID); + if (!p && base_pid && (base_pid != pid)) return GF_REQUIRES_NEW_INSTANCE; + else if (p) { + u32 i; + u32 base_idx_plus_one = 0; + + if (ctx->codecid != GF_CODECID_HEVC) return GF_REQUIRES_NEW_INSTANCE; + + for (i=0; istreams); i++) { + GF_FilterPid *ipid = gf_list_get(ctx->streams, i); + const GF_PropertyValue *p_dep; + if (ipid==pid) continue; + + p_dep = gf_filter_pid_get_property(ipid, GF_PROP_PID_ID); + if (p_dep && p_dep->value.uint == p->value.uint) { + base_idx_plus_one = i+1; + break; + } + } + if (!base_idx_plus_one) return GF_REQUIRES_NEW_INSTANCE; + + //no support for L-HEVC + if (codecid != GF_CODECID_HEVC) return GF_NOT_SUPPORTED; + if (gf_list_find(ctx->streams, pid) < 0) { + gf_list_insert(ctx->streams, pid, base_idx_plus_one); + } + //no configure for temporal enhancements + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (ctx->opid && p) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FPS, p); + return GF_OK; + } + + + dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + dsi_crc = dsi ? gf_crc_32(dsi->value.data.ptr, dsi->value.data.size) : 0; + if ((codecid==ctx->codecid) && (dsi_crc == ctx->cfg_crc) && ctx->width && ctx->height) { + gf_filter_pid_copy_properties(ctx->opid, pid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->width) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->height) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->stride) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PAR, &PROP_FRAC(ctx->pixel_ar) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, &PROP_UINT(ctx->pix_fmt) ); + return GF_OK; + } + //need a reset ! + if (ctx->vtb_session) { + //flush all pending frames and mark reconfigure as pending + ctx->reconfig_needed = GF_TRUE; + while (gf_list_count(ctx->frames)) { + vtbdec_flush_frame(filter, ctx); + } + } + if (gf_list_find(ctx->streams, pid) < 0) { + gf_list_insert(ctx->streams, pid, 0); + } + ctx->cfg_crc = dsi_crc; + ctx->codecid = codecid; + gf_filter_set_max_extra_input_pids(filter, (codecid==GF_CODECID_HEVC) ? 5 : 0); + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + } + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, pid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, NULL); + + ctx->nalu_size_length = 0; + ctx->is_annex_b = GF_FALSE; + + //check AVC config + if (codecid==GF_CODECID_AVC) { + + if (ctx->SPSs) vtbdec_del_param_list(ctx->SPSs); + ctx->SPSs = gf_list_new(); + if (ctx->PPSs) vtbdec_del_param_list(ctx->PPSs); + ctx->PPSs = gf_list_new(); + + ctx->is_avc = GF_TRUE; + ctx->check_h264_isma = GF_TRUE; + + ctx->avc.sps_active_idx = ctx->avc.pps_active_idx = -1; + ctx->active_sps = ctx->active_pps = -1; + ctx->active_sps_crc = ctx->active_pps_crc = 0; + + if (!dsi || !dsi->value.data.ptr) { + ctx->is_annex_b = GF_TRUE; + ctx->width=ctx->height=128; + ctx->out_size = ctx->width*ctx->height*3/2; + ctx->pix_fmt = GF_PIXEL_YUV; + return GF_OK; + } else { + u32 i; + GF_AVCConfigSlot *slc; + GF_AVCConfig *cfg = gf_odf_avc_cfg_read(dsi->value.data.ptr, dsi->value.data.size); + for (i=0; isequenceParameterSets); i++) { + slc = gf_list_get(cfg->sequenceParameterSets, i); + slc->id = -1; + vtbdec_register_param_sets(ctx, slc->data, slc->size, GF_TRUE, 0); + } + + for (i=0; ipictureParameterSets); i++) { + slc = gf_list_get(cfg->pictureParameterSets, i); + slc->id = -1; + vtbdec_register_param_sets(ctx, slc->data, slc->size, GF_FALSE, 0); + } + + slc = gf_list_get(ctx->SPSs, 0); + if (slc) { + ctx->active_sps = slc->id; + ctx->active_sps_crc = gf_crc_32(slc->data, slc->size); + } + + slc = gf_list_get(ctx->PPSs, 0); + if (slc) { + ctx->active_pps = slc->id; + ctx->active_pps_crc = gf_crc_32(slc->data, slc->size); + } + + ctx->nalu_size_length = cfg->nal_unit_size; + if (gf_list_count(ctx->SPSs) && gf_list_count(ctx->PPSs) && !ctx->reconfig_needed ) { + e = vtbdec_init_decoder(filter, ctx); + } else { + e = GF_OK; + } + gf_odf_avc_cfg_del(cfg); + return e; + } + } + + //check HEVC config + if (codecid==GF_CODECID_HEVC) { + if (ctx->SPSs) vtbdec_del_param_list(ctx->SPSs); + ctx->SPSs = gf_list_new(); + if (ctx->SPSs) vtbdec_del_param_list(ctx->PPSs); + ctx->PPSs = gf_list_new(); + if (ctx->SPSs) vtbdec_del_param_list(ctx->VPSs); + ctx->VPSs = gf_list_new(); + ctx->is_hevc = GF_TRUE; + + ctx->hevc.sps_active_idx = -1; + ctx->active_sps = ctx->active_pps = ctx->active_vps = -1; + + if (!dsi || !dsi->value.data.ptr) { + ctx->is_annex_b = GF_TRUE; + ctx->width=ctx->height=128; + ctx->out_size = ctx->width*ctx->height*3/2; + ctx->pix_fmt = GF_PIXEL_YUV; + return GF_OK; + } else { + u32 i, j; + GF_AVCConfigSlot *slc; + GF_HEVCConfig *cfg = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, GF_FALSE); + + for (i=0; iparam_array); i++) { + GF_HEVCParamArray *pa = gf_list_get(cfg->param_array, i); + + + for (j=0; jnalus); j++) { + slc = gf_list_get(pa->nalus, j); + slc->id = -1; + + vtbdec_register_param_sets(ctx, slc->data, slc->size, GF_FALSE, pa->type); + } + } + + slc = gf_list_get(ctx->SPSs, 0); + if (slc) ctx->active_sps = slc->id; + + slc = gf_list_get(ctx->PPSs, 0); + if (slc) ctx->active_pps = slc->id; + + slc = gf_list_get(ctx->VPSs, 0); + if (slc) ctx->active_vps = slc->id; + + ctx->nalu_size_length = cfg->nal_unit_size; + if (gf_list_count(ctx->SPSs) && gf_list_count(ctx->PPSs) && gf_list_count(ctx->VPSs) && !ctx->reconfig_needed) { + e = vtbdec_init_decoder(filter, ctx); + } else { + e = GF_OK; + } + gf_odf_hevc_cfg_del(cfg); + return e; + } + } + + if (ctx->vtb_session) { + assert(ctx->reconfig_needed); + return GF_OK; + } + + //check VOSH config + if (codecid==GF_CODECID_MPEG4_PART2) { + if (!dsi || !dsi->value.data.ptr) { + ctx->width=ctx->height=128; + ctx->out_size = ctx->width*ctx->height*3/2; + ctx->pix_fmt = GF_PIXEL_YUV; + } else { + return vtbdec_init_decoder(filter, ctx); + } + } + + return vtbdec_init_decoder(filter, ctx); +} + + +static void vtbdec_delete_decoder(GF_VTBDecCtx *ctx) +{ + if (ctx->fmt_desc) { + CFRelease(ctx->fmt_desc); + ctx->fmt_desc = NULL; + } + if (ctx->vtb_session) { + VTDecompressionSessionInvalidate(ctx->vtb_session); + ctx->vtb_session=NULL; + } + vtbdec_del_param_list(ctx->SPSs); + ctx->SPSs = NULL; + vtbdec_del_param_list(ctx->PPSs); + ctx->PPSs = NULL; + vtbdec_del_param_list(ctx->VPSs); + ctx->VPSs = NULL; +} + +static GF_Err vtbdec_parse_nal_units(GF_Filter *filter, GF_VTBDecCtx *ctx, char *inBuffer, u32 inBufferLength, char **out_buffer, u32 *out_size) +{ + u32 i, sc_size=0; + char *ptr = inBuffer; + u32 nal_size; + GF_Err e = GF_OK; + Bool reassign_bs = GF_TRUE; + Bool check_reconfig = GF_FALSE; + + if (out_buffer) { + *out_buffer = NULL; + *out_size = 0; + } + + if (!ctx->nalu_size_length) { + nal_size = gf_media_nalu_next_start_code((u8 *) inBuffer, inBufferLength, &sc_size); + if (!sc_size) return GF_NON_COMPLIANT_BITSTREAM; + ptr += nal_size + sc_size; + assert(inBufferLength >= nal_size + sc_size); + inBufferLength -= nal_size + sc_size; + } + + while (inBufferLength) { + Bool add_nal = GF_TRUE; + u8 nal_type, nal_hdr; + + if (ctx->nalu_size_length) { + nal_size = 0; + for (i=0; inalu_size_length; i++) { + nal_size = (nal_size<<8) + ((u8) ptr[i]); + } + + if (nal_size > inBufferLength) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Error parsing NAL: size indicated %d but %d bytes only in payload\n", nal_size, inBufferLength)); + break; + } + ptr += ctx->nalu_size_length; + } else { + nal_size = gf_media_nalu_next_start_code((const u8 *) ptr, inBufferLength, &sc_size); + } + + if (nal_size==0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Error parsing NAL: size 0 shall never happen\n", nal_size)); + + if (ctx->nalu_size_length) { + if (inBufferLength < ctx->nalu_size_length) break; + inBufferLength -= ctx->nalu_size_length; + } else { + if (!sc_size || (inBufferLength < sc_size)) break; + inBufferLength -= sc_size; + ptr += sc_size; + } + continue; + } + + if (ctx->is_avc) { + if (!ctx->nal_bs) ctx->nal_bs = gf_bs_new(ptr, nal_size, GF_BITSTREAM_READ); + else gf_bs_reassign_buffer(ctx->nal_bs, ptr, nal_size); + + nal_hdr = ptr[0]; + nal_type = nal_hdr & 0x1F; + switch (nal_type) { + case GF_AVC_NALU_SEQ_PARAM: + vtbdec_register_param_sets(ctx, ptr, nal_size, GF_TRUE, 0); + add_nal = GF_FALSE; + break; + case GF_AVC_NALU_PIC_PARAM: + vtbdec_register_param_sets(ctx, ptr, nal_size, GF_FALSE, 0); + add_nal = GF_FALSE; + break; + case GF_AVC_NALU_ACCESS_UNIT: + case GF_AVC_NALU_END_OF_SEQ: + case GF_AVC_NALU_END_OF_STREAM: + case GF_AVC_NALU_FILLER_DATA: + add_nal = GF_FALSE; + break; + default: + break; + } + + gf_media_avc_parse_nalu(ctx->nal_bs, &ctx->avc); + + if ((nal_type<=GF_AVC_NALU_IDR_SLICE) && ctx->avc.s_info.sps) { + if (ctx->avc.sps_active_idx != ctx->active_sps) { + ctx->reconfig_needed = 1; + ctx->active_sps = ctx->avc.sps_active_idx; + ctx->active_pps = ctx->avc.s_info.pps->id; + return GF_OK; + } + } + } else if (ctx->is_hevc) { +#ifdef GPAC_DISABLE_HEVC + return GF_NOT_SUPPORTED; +#else + u8 temporal_id, ayer_id; + + if (!ctx->nal_bs) ctx->nal_bs = gf_bs_new(ptr, nal_size, GF_BITSTREAM_READ); + else gf_bs_reassign_buffer(ctx->nal_bs, ptr, nal_size); + + s32 res = gf_media_hevc_parse_nalu_bs(ctx->nal_bs, &ctx->hevc, &nal_type, &temporal_id, &ayer_id); + if (res>=0) { + switch (nal_type) { + case GF_HEVC_NALU_VID_PARAM: + case GF_HEVC_NALU_SEQ_PARAM: + case GF_HEVC_NALU_PIC_PARAM: + vtbdec_register_param_sets(ctx, ptr, nal_size, GF_FALSE, nal_type); + add_nal = GF_FALSE; + break; + case GF_HEVC_NALU_ACCESS_UNIT: + case GF_HEVC_NALU_END_OF_SEQ: + case GF_HEVC_NALU_END_OF_STREAM: + case GF_HEVC_NALU_FILLER_DATA: + add_nal = GF_FALSE; + break; + default: + break; + } + + if ((nal_type<=GF_HEVC_NALU_SLICE_CRA) && ctx->hevc.s_info.sps) { + if (ctx->hevc.sps_active_idx != ctx->active_sps) { + ctx->reconfig_needed = 1; + ctx->active_sps = ctx->hevc.sps_active_idx; + ctx->active_pps = ctx->hevc.s_info.pps->id; + ctx->active_vps = ctx->hevc.s_info.sps->vps_id; + return GF_OK; + } + } + } +#endif + + } + + //if sps and pps are ready, init decoder + if (!ctx->vtb_session && gf_list_count(ctx->SPSs) && gf_list_count(ctx->PPSs) ) { + e = vtbdec_init_decoder(filter, ctx); + if (e) { + return e; + } + } + + if (!out_buffer) add_nal = GF_FALSE; + else if (add_nal && !ctx->vtb_session) add_nal = GF_FALSE; + + if (add_nal) { + if (reassign_bs) { + if (!ctx->nalu_rewrite_bs) ctx->nalu_rewrite_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + else { + gf_bs_reassign_buffer(ctx->nalu_rewrite_bs, ctx->nalu_buffer, ctx->nalu_buffer_alloc); + //detach from context until we get the output of the bistream + ctx->nalu_buffer = NULL; + ctx->nalu_buffer_alloc = 0; + } + reassign_bs = GF_FALSE; + } + + gf_bs_write_u32(ctx->nalu_rewrite_bs, nal_size); + gf_bs_write_data(ctx->nalu_rewrite_bs, ptr, nal_size); + } + + ptr += nal_size; + if (ctx->nalu_size_length) { + if (inBufferLength < nal_size + ctx->nalu_size_length) break; + inBufferLength -= nal_size + ctx->nalu_size_length; + } else { + if (!sc_size || (inBufferLength < nal_size + sc_size)) break; + inBufferLength -= nal_size + sc_size; + ptr += sc_size; + } + } + + if (check_reconfig && ctx->avc.s_info.pps ) { + u32 sps_crc, pps_crc; + sps_crc = vtbdec_purge_param_sets(ctx, GF_TRUE, ctx->avc.s_info.pps->sps_id); + pps_crc = vtbdec_purge_param_sets(ctx, GF_FALSE, ctx->avc.s_info.pps->id); + + if ((sps_crc != ctx->active_sps_crc) || (pps_crc != ctx->active_pps_crc) ) { + ctx->reconfig_needed = 1; + ctx->active_sps = ctx->avc.s_info.pps->sps_id; + ctx->active_pps = ctx->avc.s_info.pps->id; + ctx->active_sps_crc = sps_crc; + ctx->active_pps_crc = pps_crc; + } + } + + if (!reassign_bs) { + //get output without truncating the allocated buffer, repass the buffer at the next AU + gf_bs_get_content_no_truncate(ctx->nalu_rewrite_bs, &ctx->nalu_buffer, out_size, &ctx->nalu_buffer_alloc); + *out_buffer = ctx->nalu_buffer; + } + return e; +} + + +static GF_Err vtbdec_send_output_frame(GF_Filter *filter, GF_VTBDecCtx *ctx); + +static GF_Err vtbdec_flush_frame(GF_Filter *filter, GF_VTBDecCtx *ctx) +{ + GF_VTBHWFrame *vtbframe; + OSStatus status; + OSType type; + + if (ctx->no_copy) return vtbdec_send_output_frame(filter, ctx); + + vtbframe = gf_list_pop_front(ctx->frames); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[VTB] Outputing frame DTS "LLU" CTS "LLU" timescale %d\n", gf_filter_pck_get_dts(vtbframe->pck_src), gf_filter_pck_get_cts(vtbframe->pck_src), gf_filter_pck_get_timescale(vtbframe->pck_src))); + + + status = CVPixelBufferLockBaseAddress(vtbframe->frame, kCVPixelBufferLock_ReadOnly); + if (status != kCVReturnSuccess) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Error locking frame data\n")); + gf_list_add(ctx->frames_res, vtbframe); + return GF_IO_ERR; + } + + type = CVPixelBufferGetPixelFormatType(vtbframe->frame); + + if ((type==kCVPixelFormatType_420YpCbCr8Planar) + || (type==kCVPixelFormatType_420YpCbCr8PlanarFullRange) + || (type==kCVPixelFormatType_422YpCbCr8_yuvs) + || (type==kCVPixelFormatType_444YpCbCr8) + || (type=='444v') + || (type==kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) + || (type==kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) + ) { + u32 i, j, nb_planes = (u32) CVPixelBufferGetPlaneCount(vtbframe->frame); + u8 *dst; + u32 stride = (u32) CVPixelBufferGetBytesPerRowOfPlane(vtbframe->frame, 0); + + GF_FilterPacket *dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->out_size, &dst); + + //TOCHECK - for now the 3 planes are consecutive in VideoToolbox + if (stride==ctx->width) { + char *data = CVPixelBufferGetBaseAddressOfPlane(vtbframe->frame, 0); + memcpy(dst, data, sizeof(char)*ctx->out_size); + } else { + for (i=0; iframe, i); + u32 stride = (u32) CVPixelBufferGetBytesPerRowOfPlane(vtbframe->frame, i); + u32 w, h = (u32) CVPixelBufferGetHeightOfPlane(vtbframe->frame, i); + w = ctx->width; + if (i) { + switch (ctx->pix_fmt) { + case GF_PIXEL_YUV444: + break; + case GF_PIXEL_YUV422: + case GF_PIXEL_YUV: + w /= 2; + break; + } + } + if (stride != w) { + for (j=0; jpck_src, dst_pck); + ctx->last_cts_out = gf_filter_pck_get_cts(vtbframe->pck_src); + ctx->last_timescale_out = gf_filter_pck_get_timescale(vtbframe->pck_src); + gf_filter_pck_unref(vtbframe->pck_src); + vtbframe->pck_src = NULL; + gf_filter_pck_send(dst_pck); + } + CVPixelBufferUnlockBaseAddress(vtbframe->frame, kCVPixelBufferLock_ReadOnly); + gf_list_add(ctx->frames_res, vtbframe); + return GF_OK; +} +static GF_Err vtbdec_process(GF_Filter *filter) +{ + OSStatus status; + CMSampleBufferRef sample = NULL; + CMBlockBufferRef block_buffer = NULL; + char *in_data=NULL; + u32 in_data_size; + char *in_buffer; + u32 in_buffer_size, frames_count, i, count, nb_eos; + u64 min_dts; + GF_Err e; + GF_VTBDecCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + GF_FilterPid *ref_pid = NULL; + + //figure out min DTS + count = gf_list_count(ctx->streams); + nb_eos = 0; + min_dts = 0; + for (i=0; istreams, i); + + pck = gf_filter_pid_get_packet(pid); + if (!pck) { + if (gf_filter_pid_is_eos(pid)) { + nb_eos++; + continue; + } else { + return GF_OK; + } + } + dts = gf_filter_pck_get_dts(pck); + dts *= 1000; + dts /= gf_filter_pck_get_timescale(pck); + if (!min_dts || (min_dts>dts)) { + min_dts = dts; + ref_pid = pid; + } + } + + if (nb_eos==count) { + while (gf_list_count(ctx->frames)) { + vtbdec_flush_frame(filter, ctx); + } + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + assert(ref_pid); + pck = gf_filter_pid_get_packet(ref_pid); + assert(pck); + + if (ctx->drop_non_refs && !gf_filter_pck_get_sap(pck)) { + gf_filter_pid_drop_packet(ref_pid); + return GF_OK; + } + + in_buffer = (char *) gf_filter_pck_get_data(pck, &in_buffer_size); + + //discard empty packets + if (!in_buffer || !in_buffer_size) { + gf_filter_pid_drop_packet(ref_pid); + //if inbuffer is null this is a hardware frame, should never happen + return in_buffer ? GF_OK : GF_NOT_SUPPORTED; + } + + if (ctx->skip_mpeg4_vosh) { + GF_M4VDecSpecInfo dsi; + dsi.width = dsi.height = 0; + e = gf_m4v_get_config(in_buffer, in_buffer_size, &dsi); + //found a vosh - remove it from payload, init decoder if needed + if ((e==GF_OK) && dsi.width && dsi.height) { + if (!ctx->vtb_session) { + ctx->vosh = in_buffer; + ctx->vosh_size = dsi.next_object_start; + e = vtbdec_init_decoder(filter, ctx); + if (e) { + gf_filter_pid_drop_packet(ref_pid); + return e; + } + + //enfoce removal for all frames + ctx->skip_mpeg4_vosh = GF_TRUE; + } + ctx->vosh_size = dsi.next_object_start; + } else if (!ctx->vtb_session) { + gf_filter_pid_drop_packet(ref_pid); + return GF_OK; + } + } + + if (ctx->init_mpeg12) { + GF_M4VDecSpecInfo dsi; + dsi.width = dsi.height = 0; + + e = gf_mpegv12_get_config(in_buffer, in_buffer_size, &dsi); + if ((e==GF_OK) && dsi.width && dsi.height) { + ctx->width = dsi.width; + ctx->height = dsi.height; + ctx->pixel_ar.num = dsi.par_num; + ctx->pixel_ar.den = dsi.par_den; + + e = vtbdec_init_decoder(filter, ctx); + if (e) { + gf_filter_pid_drop_packet(ref_pid); + return e; + } + } + + if (!ctx->vtb_session) { + gf_filter_pid_drop_packet(ref_pid); + return GF_OK; + } + } + + if (ctx->check_h264_isma) { + if (in_buffer && !in_buffer[0] && !in_buffer[1] && !in_buffer[2] && (in_buffer[3]==0x01)) { + ctx->check_h264_isma=GF_FALSE; + ctx->nalu_size_length=0; + ctx->is_annex_b=GF_TRUE; + } + } + + //Always parse AVC data , remove SPS/PPS/... and reconfig if needed + if (ctx->is_annex_b || ctx->nalu_size_length) { + + e = vtbdec_parse_nal_units(filter, ctx, in_buffer, in_buffer_size, &in_data, &in_data_size); + if (e) { + gf_filter_pid_drop_packet(ref_pid); + return e; + } + } else if (ctx->vosh_size) { + in_data = in_buffer + ctx->vosh_size; + in_data_size = in_buffer_size - ctx->vosh_size; + ctx->vosh_size = 0; + } else { + in_data = in_buffer; + in_data_size = in_buffer_size; + } + + if (ctx->reconfig_needed) { + //flush all pending frames + while (gf_list_count(ctx->frames)) { + vtbdec_flush_frame(filter, ctx); + } + //waiting for last frame to be discarded + if (ctx->no_copy && ctx->decoded_frames_pending) { + ctx->last_frame_sent->frame_ifce.flags = GF_FRAME_IFCE_BLOCKING; + gf_filter_ask_rt_reschedule(filter, 0); + return GF_OK; + } + if (ctx->fmt_desc) { + CFRelease(ctx->fmt_desc); + ctx->fmt_desc = NULL; + } + if (ctx->vtb_session) { + VTDecompressionSessionInvalidate(ctx->vtb_session); + ctx->vtb_session=NULL; + } + vtbdec_init_decoder(filter, ctx); + return GF_OK; + } + if (!ctx->vtb_session) { + gf_filter_pid_drop_packet(ref_pid); + return GF_OK; + } + + + status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, in_data, in_data_size, kCFAllocatorNull, NULL, 0, in_data_size, 0, &block_buffer); + + if (status || (block_buffer == NULL) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Failed to allocate block buffer of %d bytes\n", in_data_size)); + gf_filter_pid_drop_packet(ref_pid); + return GF_IO_ERR; + } + + status = CMSampleBufferCreate(kCFAllocatorDefault, block_buffer, TRUE, NULL, NULL, ctx->fmt_desc, 1, 0, NULL, 0, NULL, &sample); + + if (status || (sample==NULL)) { + if (block_buffer) + CFRelease(block_buffer); + + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Failed to create sample buffer for %d bytes\n", in_data_size)); + gf_filter_pid_drop_packet(ref_pid); + return GF_IO_ERR; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[VTB] Decoding frame DTS "LLU" ms\n", min_dts)); + ctx->cur_pck = pck; + ctx->last_error = GF_OK; + status = VTDecompressionSessionDecodeFrame(ctx->vtb_session, sample, 0, NULL, 0); + if (!status) + status = VTDecompressionSessionWaitForAsynchronousFrames(ctx->vtb_session); + + + CFRelease(block_buffer); + CFRelease(sample); + + //profile not supported, request codec change - do not discard input frame + if (ctx->last_error==GF_PROFILE_NOT_SUPPORTED) { + ctx->cur_pck = NULL; + return ctx->last_error; + } + + gf_filter_pid_drop_packet(ref_pid); + ctx->cur_pck = NULL; + + if (ctx->last_error) return ctx->last_error; + + if (status) + return GF_NON_COMPLIANT_BITSTREAM; + + frames_count = gf_list_count(ctx->frames); + if (!frames_count) { + return ctx->last_error; + } + //probing for reordering, or reordering is on but not enough frames: wait before we dispatch + if (ctx->reorder_probe) { + return GF_OK; + } + + if (ctx->reorder_detected && (frames_countreorder) ) + return GF_OK; + + return vtbdec_flush_frame(filter, ctx); +} + +void vtbframe_release(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck) +{ + GF_FilterFrameInterface *frame = gf_filter_pck_get_frame_interface(pck); + GF_VTBHWFrame *f = (GF_VTBHWFrame *)frame->user_data; + if (f->locked) { + CVPixelBufferUnlockBaseAddress(f->frame, kCVPixelBufferLock_ReadOnly); + } +#ifdef VTB_GL_TEXTURE + if (f->y) CVBufferRelease(f->y); + if (f->u) CVBufferRelease(f->u); + if (f->v) CVBufferRelease(f->v); + if (f->ctx->cache_texture) + GF_CVOpenGLTextureCacheFlush(f->ctx->cache_texture, 0); +#endif + + if (f->frame) { + CVPixelBufferRelease(f->frame); + f->frame = NULL; + } + + safe_int_dec(&f->ctx->decoded_frames_pending); + gf_list_add(f->ctx->frames_res, f); +} + +GF_Err vtbframe_get_plane(GF_FilterFrameInterface *frame, u32 plane_idx, const u8 **outPlane, u32 *outStride) +{ + OSStatus status; + GF_Err e; + GF_VTBHWFrame *f = (GF_VTBHWFrame *)frame->user_data; + if (! outPlane || !outStride) return GF_BAD_PARAM; + *outPlane = NULL; + assert(f->frame); + if (!f->locked) { + status = CVPixelBufferLockBaseAddress(f->frame, kCVPixelBufferLock_ReadOnly); + if (status != kCVReturnSuccess) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Error locking frame data\n")); + return GF_IO_ERR; + } + f->locked = GF_TRUE; + } + e = GF_OK; + + if (CVPixelBufferIsPlanar(f->frame)) { + *outPlane = CVPixelBufferGetBaseAddressOfPlane(f->frame, plane_idx); + if (*outPlane) + *outStride = (u32) CVPixelBufferGetBytesPerRowOfPlane(f->frame, plane_idx); + else + e = GF_EOS; + } else if (plane_idx==0) { + *outStride = (u32) CVPixelBufferGetBytesPerRow(f->frame); + *outPlane = CVPixelBufferGetBaseAddress(f->frame); + } else { + e = GF_BAD_PARAM; + } + return e; +} + +#ifdef VTB_GL_TEXTURE + +/*Define codec matrix*/ +typedef struct __matrix GF_CodecMatrix; + +#ifdef GPAC_CONFIG_IOS +void *myGetGLContext(); +#else + +#include +void *myGetGLContext() +{ + return CGLGetCurrentContext(); +} +#endif + + +GF_Err vtbframe_get_gl_texture(GF_FilterFrameInterface *frame, u32 plane_idx, u32 *gl_tex_format, u32 *gl_tex_id, GF_CodecMatrix * texcoordmatrix) +{ + OSStatus status; + GLenum target_fmt; + u32 w, h; + GF_CVGLTextureREF *outTexture=NULL; + GF_VTBHWFrame *f = (GF_VTBHWFrame *)frame->user_data; + if (! gl_tex_format || !gl_tex_id) return GF_BAD_PARAM; + *gl_tex_format = 0; + *gl_tex_id = 0; + + if (!f->ctx->gl_context) { + f->ctx->gl_context = myGetGLContext(); + if (!f->ctx->gl_context) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Error locating current GL context\n")); + return GF_IO_ERR; + } + } + if (! f->ctx->decoded_frames_pending) return GF_IO_ERR; + + if (!f->ctx->cache_texture) { +#ifdef GPAC_CONFIG_IOS + status = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, f->ctx->gl_context, NULL, &f->ctx->cache_texture); +#else + status = CVOpenGLTextureCacheCreate(kCFAllocatorDefault, NULL, f->ctx->gl_context, CGLGetPixelFormat(f->ctx->gl_context), NULL, &f->ctx->cache_texture); +#endif + if (status != kCVReturnSuccess) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Error creating cache texture\n")); + return GF_IO_ERR; + } + } + + if (!f->locked) { + status = CVPixelBufferLockBaseAddress(f->frame, kCVPixelBufferLock_ReadOnly); + if (status != kCVReturnSuccess) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Error locking frame data\n")); + return GF_IO_ERR; + } + f->locked = GF_TRUE; + } + + if (CVPixelBufferIsPlanar(f->frame)) { + w = (u32) CVPixelBufferGetPlaneCount(f->frame); + if (plane_idx >= (u32) CVPixelBufferGetPlaneCount(f->frame)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Wrong plane index\n")); + return GF_BAD_PARAM; + } + } else if (plane_idx!=0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Wrong plane index %d on interleaved format\n", plane_idx)); + return GF_BAD_PARAM; + } + + target_fmt = GL_LUMINANCE; + w = f->ctx->width; + h = f->ctx->height; + if (plane_idx) { + w /= 2; + h /= 2; + target_fmt = GL_LUMINANCE_ALPHA; + } + if (plane_idx==0) { + outTexture = &f->y; + } + else if (plane_idx==1) { + outTexture = &f->u; + } + //don't create texture if already done ! + if ( *outTexture == NULL) { +#ifdef GPAC_CONFIG_IOS + status = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, f->ctx->cache_texture, f->frame, NULL, GL_TEXTURE_2D, target_fmt, w, h, target_fmt, GL_UNSIGNED_BYTE, plane_idx, outTexture); +#else + status = CVOpenGLTextureCacheCreateTextureFromImage(kCFAllocatorDefault, f->ctx->cache_texture, f->frame, NULL, outTexture); +#endif + + if (status != kCVReturnSuccess) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTB] Error creating cache texture for plane %d\n", plane_idx)); + return GF_IO_ERR; + } + } + *gl_tex_format = GF_CVOpenGLTextureGetTarget(*outTexture); + *gl_tex_id = GF_CVOpenGLTextureGetName(*outTexture); + + return GF_OK; +} +#endif + +static GF_Err vtbdec_send_output_frame(GF_Filter *filter, GF_VTBDecCtx *ctx) +{ + GF_VTBHWFrame *vtb_frame; + GF_FilterPacket *dst_pck; + + vtb_frame = gf_list_pop_front(ctx->frames); + if (!vtb_frame) return GF_BAD_PARAM; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[VTB] Outputing frame DTS "LLU" CTS "LLU" timescale %d\n", gf_filter_pck_get_dts(vtb_frame->pck_src), gf_filter_pck_get_cts(vtb_frame->pck_src), gf_filter_pck_get_timescale(vtb_frame->pck_src))); + + vtb_frame->frame_ifce.user_data = vtb_frame; + vtb_frame->frame_ifce.get_plane = vtbframe_get_plane; +#ifdef VTB_GL_TEXTURE + if (ctx->use_gl_textures) + vtb_frame->frame_ifce.get_gl_texture = vtbframe_get_gl_texture; +#endif + + if (ctx->reconfig_needed) + vtb_frame->frame_ifce.flags = GF_FRAME_IFCE_BLOCKING; + + safe_int_inc(&ctx->decoded_frames_pending); + + dst_pck = gf_filter_pck_new_frame_interface(ctx->opid, &vtb_frame->frame_ifce, vtbframe_release); + + gf_filter_pck_merge_properties(vtb_frame->pck_src, dst_pck); + + ctx->last_cts_out = gf_filter_pck_get_cts(vtb_frame->pck_src); + ctx->last_timescale_out = gf_filter_pck_get_timescale(vtb_frame->pck_src); + gf_filter_pck_unref(vtb_frame->pck_src); + vtb_frame->pck_src = NULL; + ctx->last_frame_sent = vtb_frame; + gf_filter_pck_send(dst_pck); + return GF_OK; +} + + +#endif + +static Bool vtbdec_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_VTBDecCtx *ctx = (GF_VTBDecCtx *) gf_filter_get_udta(filter); + if (evt->base.type==GF_FEVT_PLAY) { + while (gf_list_count(ctx->frames) ) { + GF_VTBHWFrame *f = gf_list_pop_back(ctx->frames); + if (f->pck_src) gf_filter_pck_unref(f->pck_src); + f->pck_src = NULL; + gf_list_add(ctx->frames_res, f); + } + ctx->drop_non_refs = evt->play.drop_non_ref; + } + else if ((evt->base.type==GF_FEVT_SET_SPEED) || (evt->base.type==GF_FEVT_RESUME)) { + ctx->drop_non_refs = evt->play.drop_non_ref; + } + return GF_FALSE; +} + +static GF_Err vtbdec_initialize(GF_Filter *filter) +{ + GF_VTBDecCtx *ctx = (GF_VTBDecCtx *) gf_filter_get_udta(filter); +#ifdef VTB_GL_TEXTURE + if (ctx->no_copy) + ctx->use_gl_textures = GF_TRUE; +#endif + + ctx->frames_res = gf_list_new(); + ctx->frames = gf_list_new(); + ctx->streams = gf_list_new(); + return GF_OK; +} + +static void vtbdec_finalize(GF_Filter *filter) +{ + GF_VTBDecCtx *ctx = (GF_VTBDecCtx *) gf_filter_get_udta(filter); + vtbdec_delete_decoder(ctx); + +#ifdef VTB_GL_TEXTURE + if (ctx->cache_texture) { + CFRelease(ctx->cache_texture); + } +#endif + + if (ctx->frames) { + while (gf_list_count(ctx->frames) ) { + GF_VTBHWFrame *f = gf_list_pop_back(ctx->frames); + if (f->pck_src) gf_filter_pck_unref(f->pck_src); + gf_free(f); + } + gf_list_del(ctx->frames); + } + + if (ctx->frames_res) { + while (gf_list_count(ctx->frames_res) ) { + GF_VTBHWFrame *f = gf_list_pop_back(ctx->frames_res); + if (f->pck_src) gf_filter_pck_unref(f->pck_src); + gf_free(f); + } + gf_list_del(ctx->frames_res); + } + gf_list_del(ctx->streams); + + if (ctx->nal_bs) gf_bs_del(ctx->nal_bs); + if (ctx->ps_bs) gf_bs_del(ctx->ps_bs); + if (ctx->nalu_rewrite_bs) gf_bs_del(ctx->nalu_rewrite_bs); + if (ctx->nalu_buffer) gf_free(ctx->nalu_buffer); +} + + +static const GF_FilterCapability VTBDecCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG4_PART2), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AVC), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_HEVC), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED,GF_PROP_PID_TILE_BASE, GF_TRUE), + +#ifndef GPAC_CONFIG_IOS + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_SIMPLE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_MAIN), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_SNR), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_SPATIAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_HIGH), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_422), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_H263), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_S263), +#endif + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +#define OFFS(_n) #_n, offsetof(GF_VTBDecCtx, _n) + +static const GF_FilterArgs VTBDecArgs[] = +{ + { OFFS(reorder), "number of frames to wait for temporal re-ordering", GF_PROP_UINT, "6", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(no_copy), "dispatch VTB frames into filter chain (no copy)", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ofmt), "set default pixel format for decoded video. If not matched default to nv12", GF_PROP_PIXFMT, "nv12", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(disable_hw), "disable hardware decoding", GF_PROP_BOOL, "false", NULL, 0}, + {} +}; + +GF_FilterRegister GF_VTBDecCtxRegister = { + .name = "vtbdec", + GF_FS_SET_DESCRIPTION("VideoToolBox decoder") + GF_FS_SET_HELP("This filter decodes MPEG-2, H263, AVC|H264 and HEVC streams through VideoToolBox. It allows GPU frame dispatch or direct frame copy.") + .private_size = sizeof(GF_VTBDecCtx), + .args = VTBDecArgs, + .priority = 1, + SETCAPS(VTBDecCaps), + .initialize = vtbdec_initialize, + .finalize = vtbdec_finalize, + .configure_pid = vtbdec_configure_pid, + .process = vtbdec_process, + .max_extra_pids = 5, + .process_event = vtbdec_process_event, +}; + +#else +#undef _GF_MATH_H_ +#include +#include + +#endif // !defined(GPAC_DISABLE_AV_PARSERS) && ( defined(GPAC_CONFIG_DARWIN) || defined(GPAC_CONFIG_IOS) ) + +const GF_FilterRegister *vtbdec_register(GF_FilterSession *session) +{ +#if !defined(GPAC_DISABLE_AV_PARSERS) && ( defined(GPAC_CONFIG_DARWIN) || defined(GPAC_CONFIG_IOS) ) + return &GF_VTBDecCtxRegister; +#else + return NULL; +#endif +} diff --git a/modules/vtb_decode/glcontext.m b/src/filters/dec_vtb_glctx.m similarity index 100% rename from modules/vtb_decode/glcontext.m rename to src/filters/dec_vtb_glctx.m diff --git a/src/filters/dec_webvtt.c b/src/filters/dec_webvtt.c new file mode 100644 index 0000000..1c2805e --- /dev/null +++ b/src/filters/dec_webvtt.c @@ -0,0 +1,513 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Cyril Concolato - Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2013-2017 + * All rights reserved + * + * This file is part of GPAC / WebVTT decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include + +#if !defined(GPAC_DISABLE_VTT) && !defined(GPAC_DISABLE_SVG) && defined(GPAC_HAS_QJS) + +#include +#include +#include +#include +#include +#include + +#include "../scenegraph/qjs_common.h" + +typedef struct +{ + //opts + char *script; + char *color, *font; + Float fontSize, lineSpacing, txtx, txty; + u32 txtw, txth; + + GF_FilterPid *ipid, *opid; + + /* config of the VTT stream - not used by the renderer for now */ + char *dsi; + //CRC of the config string + u32 dsi_crc; + + /* Boolean indicating the internal graph is registered with the compositor */ + Bool graph_registered; + /* Boolean indicating the stream is playing */ + Bool is_playing; + + /* Scene to which this WebVTT stream is linked */ + GF_Scene *scene; + /* object manager corresponding to the output pid declared*/ + GF_ObjectManager *odm; + + GF_List *cues; + + /* Scene graph for the subtitle content */ + GF_SceneGraph *scenegraph; + + Bool update_args; +} GF_VTTDec; + +void vttd_update_size_info(GF_VTTDec *ctx) +{ + u32 w, h; + GF_FieldInfo info; + Bool has_size; + char szVB[100]; + GF_Node *root = gf_sg_get_root_node(ctx->scenegraph); + if (!root) return; + + has_size = gf_sg_get_scene_size_info(ctx->scene->graph, &w, &h); + /*no size info is given in main scene, override by associated video size if any, or by text track size*/ + if (!has_size) { + const GF_PropertyValue *p; + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_WIDTH); + if (p) w = p->value.uint; + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_HEIGHT); + if (p) h = p->value.uint; + + if (!w) w = ctx->txtw; + if (!h) h = ctx->txth; + + gf_sg_set_scene_size_info(ctx->scenegraph, w, h, GF_TRUE); + gf_scene_force_size(ctx->scene, w, h); + } + + gf_sg_set_scene_size_info(ctx->scenegraph, w, h, GF_TRUE); + + sprintf(szVB, "0 0 %d %d", w, h); + gf_node_get_attribute_by_tag(root, TAG_SVG_ATT_viewBox, GF_TRUE, GF_FALSE, &info); + gf_svg_parse_attribute(root, &info, szVB, 0); + + /*apply*/ + gf_sg_set_scene_size_info(ctx->scenegraph, w, h, GF_TRUE); + + sprintf(szVB, "0 0 %d %d", w, h); + gf_node_get_attribute_by_tag(root, TAG_SVG_ATT_viewBox, GF_TRUE, GF_FALSE, &info); + gf_svg_parse_attribute(root, &info, szVB, 0); + +} + +void vttd_setup_scene(GF_VTTDec *ctx) +{ + GF_Node *n, *root; + GF_FieldInfo info; + + ctx->scenegraph = gf_sg_new_subscene(ctx->scene->graph); + + if (!ctx->scenegraph) return; + gf_sg_add_namespace(ctx->scenegraph, "http://www.w3.org/2000/svg", NULL); + gf_sg_add_namespace(ctx->scenegraph, "http://www.w3.org/1999/xlink", "xlink"); + gf_sg_add_namespace(ctx->scenegraph, "http://www.w3.org/2001/xml-events", "ev"); + gf_sg_set_scene_size_info(ctx->scenegraph, 800, 600, GF_TRUE); + + /* modify the scene with an Inline/Animation pointing to the VTT Renderer */ + n = root = gf_node_new(ctx->scenegraph, TAG_SVG_svg); + gf_node_register(root, NULL); + gf_sg_set_root_node(ctx->scenegraph, root); + gf_node_get_attribute_by_name(n, "xmlns", 0, GF_TRUE, GF_FALSE, &info); + gf_svg_parse_attribute(n, &info, "http://www.w3.org/2000/svg", 0); + vttd_update_size_info(ctx); + gf_node_init(n); + + n = gf_node_new(ctx->scenegraph, TAG_SVG_script); + gf_node_register(n, root); + gf_node_list_add_child(&((GF_ParentNode *)root)->children, n); + + gf_node_get_attribute_by_tag(n, TAG_XLINK_ATT_href, GF_TRUE, GF_FALSE, &info); + if (strstr(ctx->script, ":\\")) { + gf_svg_parse_attribute(n, &info, (char *) ctx->script, 0); + } else { + char szPath[GF_MAX_PATH]; + strcpy(szPath, "file://"); + strcat(szPath, ctx->script); + gf_svg_parse_attribute(n, &info, (char *) szPath, 0); + } + + gf_node_init(n); + +} + +static GF_Err vttd_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 entry_type; + GF_BitStream *bs; + u32 dsi_crc; + const GF_PropertyValue *dsi; + GF_VTTDec *ctx = (GF_VTTDec *)gf_filter_get_udta(filter); + + if (is_remove) { + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + ctx->opid = ctx->ipid = NULL; + return GF_OK; + } + //TODO: we need to cleanup cap checking upon reconfigure + if (ctx->ipid && !gf_filter_pid_check_caps(pid)) return GF_NOT_SUPPORTED; + assert(!ctx->ipid || (ctx->ipid == pid)); + + dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (!dsi) return GF_NOT_SUPPORTED; + + dsi_crc = gf_crc_32(dsi->value.data.ptr, dsi->value.data.size); + if (dsi_crc == ctx->dsi_crc) return GF_OK; + ctx->dsi_crc = dsi_crc; + + //parse DSI + bs = gf_bs_new(dsi->value.data.ptr, dsi->value.data.size, GF_BITSTREAM_READ); + entry_type = gf_bs_read_u32(bs); + if (entry_type == GF_ISOM_BOX_TYPE_WVTT) { + GF_Box *b; + gf_isom_box_parse(&b, bs); + ctx->dsi = ((GF_StringBox *)b)->string; + ((GF_StringBox *)b)->string = NULL; + gf_isom_box_del(b); + } else { + ctx->dsi = gf_malloc( dsi->value.data.size + 1); + memcpy(ctx->dsi, dsi->value.data.ptr, dsi->value.data.size); + ctx->dsi[dsi->value.data.size] = 0; + } + gf_bs_del(bs); + + ctx->ipid = pid; + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_TEXT)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW)); + + return GF_OK; +} + + +static void vttd_toggle_display(GF_VTTDec *ctx) +{ + if (!ctx->scenegraph) return; + + if (ctx->is_playing) { + if (!ctx->graph_registered) { + vttd_update_size_info(ctx); + gf_scene_register_extra_graph(ctx->scene, ctx->scenegraph, GF_FALSE); + ctx->graph_registered = GF_TRUE; + } + } else { + if (ctx->graph_registered) { + gf_scene_register_extra_graph(ctx->scene, ctx->scenegraph, GF_TRUE); + ctx->graph_registered = GF_FALSE; + } + } +} + +static Bool vttd_process_event(GF_Filter *filter, const GF_FilterEvent *com) +{ + GF_VTTDec *ctx = gf_filter_get_udta(filter); + + //check for scene attach + switch (com->base.type) { + case GF_FEVT_ATTACH_SCENE: + break; + case GF_FEVT_RESET_SCENE: + if (ctx->opid != com->attach_scene.on_pid) return GF_TRUE; + ctx->is_playing = GF_FALSE; + vttd_toggle_display(ctx); + + gf_sg_del(ctx->scenegraph); + ctx->scenegraph = NULL; + ctx->scene = NULL; + return GF_TRUE; + case GF_FEVT_PLAY: + ctx->is_playing = GF_TRUE; + vttd_toggle_display(ctx); + return GF_FALSE; + case GF_FEVT_STOP: + ctx->is_playing = GF_FALSE; + vttd_toggle_display(ctx); + return GF_FALSE; + default: + return GF_FALSE; + } + if (ctx->opid != com->attach_scene.on_pid) return GF_TRUE; + + ctx->odm = com->attach_scene.object_manager; + ctx->scene = ctx->odm->subscene ? ctx->odm->subscene : ctx->odm->parentscene; + + /*timedtext cannot be a root scene object*/ + if (ctx->odm->subscene) { + ctx->odm = NULL; + ctx->scene = NULL; + } else { + vttd_setup_scene(ctx); + vttd_toggle_display(ctx); + } + return GF_TRUE; +} + +void js_dump_error(JSContext *ctx); + +JSContext *vtt_script_get_context(GF_VTTDec *ctx, GF_SceneGraph *sg) +{ + JSContext *svg_script_get_context(GF_SceneGraph *sg); + JSContext *c = svg_script_get_context(sg); + + if (ctx->update_args) { + JSValue global = JS_GetGlobalObject(c); + + JS_SetPropertyStr(c, global, "fontSize", JS_NewFloat64(c, ctx->fontSize)); + JS_SetPropertyStr(c, global, "fontFamily", JS_NewString(c, ctx->font)); + JS_SetPropertyStr(c, global, "textColor", JS_NewString(c, ctx->color)); + JS_SetPropertyStr(c, global, "lineSpaceFactor", JS_NewFloat64(c, ctx->lineSpacing)); + JS_SetPropertyStr(c, global, "xOffset", JS_NewFloat64(c, ctx->txtx)); + JS_SetPropertyStr(c, global, "yOffset", JS_NewFloat64(c, ctx->txty)); + + JS_FreeValue(c, global); + ctx->update_args = GF_FALSE; + } + return c; +} + + +static GF_Err vttd_js_add_cue(GF_VTTDec *ctx, GF_Node *node, const char *id, const char *start, const char *end, const char *settings, const char *payload) +{ + GF_Err e=GF_OK; + JSValue fun_val; + JSValue global; + JSContext *c = vtt_script_get_context(ctx, node->sgprivate->scenegraph); + if (!c) return GF_SERVICE_ERROR; + + gf_js_lock(c, GF_TRUE); + global = JS_GetGlobalObject(c); + fun_val = JS_GetPropertyStr(c, global, "addCue"); + if (!JS_IsFunction(c, fun_val) ) { + e = GF_BAD_PARAM; + } else { + JSValue ret, argv[5]; + + argv[0] = JS_NewString(c, id ? id : ""); + argv[1] = JS_NewString(c, start ? start : ""); + argv[2] = JS_NewString(c, end ? end : ""); + argv[3] = JS_NewString(c, settings ? settings : ""); + argv[4] = JS_NewString(c, payload ? payload : ""); + + ret = JS_Call(c, fun_val, global, 5, argv); + if (JS_IsException(ret)) { + js_dump_error(c); + e = GF_BAD_PARAM; + } + JS_FreeValue(c, ret); + JS_FreeValue(c, argv[0]); + JS_FreeValue(c, argv[1]); + JS_FreeValue(c, argv[2]); + JS_FreeValue(c, argv[3]); + JS_FreeValue(c, argv[4]); + } + JS_FreeValue(c, global); + JS_FreeValue(c, fun_val); + + gf_js_lock(c, GF_FALSE); + return e; +} + +static GF_Err vttd_js_remove_cues(GF_VTTDec *ctx, GF_Node *node) +{ + GF_Err e = GF_OK; + JSValue fun_val; + JSValue global; + JSContext *c = vtt_script_get_context(ctx, node->sgprivate->scenegraph); + if (!c) return GF_SERVICE_ERROR; + + gf_js_lock(c, GF_TRUE); + global = JS_GetGlobalObject(c); + fun_val = JS_GetPropertyStr(c, global, "removeCues"); + if (!JS_IsFunction(c, fun_val) ) { + e = GF_BAD_PARAM; + } else { + JSValue ret = JS_Call(c, fun_val, global, 0, NULL); + if (JS_IsException(ret)) { + js_dump_error(c); + e = GF_BAD_PARAM; + } + JS_FreeValue(c, ret); + } + JS_FreeValue(c, global); + JS_FreeValue(c, fun_val); + gf_js_lock(c, GF_FALSE); + return e; +} + +static GF_Err vttd_process(GF_Filter *filter) +{ + GF_Err e = GF_OK; + GF_FilterPacket *pck; + GF_List *cues; + const char *pck_data; + u64 cts; + u32 timescale, obj_time; + u32 pck_size; + GF_VTTDec *ctx = (GF_VTTDec *) gf_filter_get_udta(filter); + + if (!ctx->scene) { + if (ctx->is_playing) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + //object clock shall be valid + if (!ctx->odm->ck) + return GF_OK; + + cts = gf_filter_pck_get_cts( pck ); + timescale = gf_filter_pck_get_timescale(pck); + + gf_odm_check_buffering(ctx->odm, ctx->ipid); + + //we still process any frame before our clock time even when buffering + obj_time = gf_clock_time(ctx->odm->ck); + if (cts * 1000 > obj_time * timescale) { + gf_sc_sys_frame_pending(ctx->scene->compositor, ((Double) cts / timescale), obj_time, filter); + return GF_OK; + } + pck_data = gf_filter_pck_get_data(pck, &pck_size); + + cues = gf_webvtt_parse_cues_from_data(pck_data, pck_size, 0); + vttd_js_remove_cues(ctx, ctx->scenegraph->RootNode); + if (gf_list_count(cues)) { + while (gf_list_count(cues)) { + char start[100], end[100]; + GF_WebVTTCue *cue = (GF_WebVTTCue *)gf_list_get(cues, 0); + gf_list_rem(cues, 0); + sprintf(start, "%02d:%02d:%02d.%03d", cue->start.hour, cue->start.min, cue->start.sec, cue->start.ms); + sprintf(end, "%02d:%02d:%02d.%03d", cue->end.hour, cue->end.min, cue->end.sec, cue->end.ms); + vttd_js_add_cue(ctx, ctx->scenegraph->RootNode, cue->id, start, end, cue->settings, cue->text); + + gf_webvtt_cue_del(cue); + } + } + gf_list_del(cues); + gf_filter_pid_drop_packet(ctx->ipid); + return e; +} + +static GF_Err vtt_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *new_val) +{ + GF_VTTDec *ctx = gf_filter_get_udta(filter); + ctx->update_args = GF_TRUE; + return GF_OK; +} + +static GF_Err vttd_initialize(GF_Filter *filter) +{ + GF_VTTDec *ctx = gf_filter_get_udta(filter); + + if (!ctx->script) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTTDec] WebVTT JS renderer script not set\n")); + return GF_BAD_PARAM; + } else if (!gf_file_exists(ctx->script)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[VTTDec] WebVTT JS renderer script %s not found\n", ctx->script)); + return GF_URL_ERROR; + } + ctx->cues = gf_list_new(); + ctx->update_args = GF_TRUE; +#ifdef GPAC_ENABLE_COVERAGE + vtt_update_arg(filter, NULL, NULL); +#endif + return GF_OK; +} + +void vttd_finalize(GF_Filter *filter) +{ + GF_VTTDec *ctx = (GF_VTTDec *) gf_filter_get_udta(filter); + if (ctx->cues) gf_list_del(ctx->cues); + if (ctx->dsi) gf_free(ctx->dsi); +} + +#define OFFS(_n) #_n, offsetof(GF_VTTDec, _n) +static const GF_FilterArgs VTTDecArgs[] = +{ + { OFFS(script), "location of WebVTT SVG JS renderer", GF_PROP_STRING, "$GSHARE/scripts/webvtt-renderer.js", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(font), "font to use", GF_PROP_STRING, "SANS", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(fontSize), "font size to use", GF_PROP_FLOAT, "20", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(color), "color to use", GF_PROP_STRING, "white", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(lineSpacing), "line spacing as scaling factor to font size", GF_PROP_FLOAT, "1.0", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(txtx), "horizontal offset", GF_PROP_FLOAT, "5", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(txty), "vertical offset", GF_PROP_FLOAT, "5", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(txtw), "default width in standalone rendering", GF_PROP_UINT, "400", NULL, 0}, + { OFFS(txth), "default height in standalone rendering", GF_PROP_UINT, "200", NULL, 0}, + {0} +}; + +static const GF_FilterCapability VTTDecCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_ISOM_SUBTYPE_WVTT), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +GF_FilterRegister VTTDecRegister = { + .name = "vttdec", + GF_FS_SET_DESCRIPTION("WebVTT decoder") + GF_FS_SET_HELP("This filter decodes WebVTT streams into a SVG scene graph of the compositor filter.\n" + "The scene graph creation is done through JavaScript.\n" + "The filter options are used to override the JS global variables of the WebVTT renderer.") + .private_size = sizeof(GF_VTTDec), + .flags = GF_FS_REG_MAIN_THREAD, + .args = VTTDecArgs, + .priority = 1, + SETCAPS(VTTDecCaps), + .initialize = vttd_initialize, + .finalize = vttd_finalize, + .process = vttd_process, + .configure_pid = vttd_configure_pid, + .process_event = vttd_process_event, + .update_arg = vtt_update_arg +}; + +#endif + +const GF_FilterRegister *vttdec_register(GF_FilterSession *session) +{ +#if !defined(GPAC_DISABLE_VTT) && !defined(GPAC_DISABLE_SVG) && defined(GPAC_HAS_QJS) + return &VTTDecRegister; +#else + return NULL; +#endif +} + + diff --git a/src/filters/dec_xvid.c b/src/filters/dec_xvid.c new file mode 100644 index 0000000..dc50121 --- /dev/null +++ b/src/filters/dec_xvid.c @@ -0,0 +1,461 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / MPEG-4 visual p2 xvid decoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include +#include + + +/*if we don't have M4V (A)SP parser, we con't get width and height and xvid is then unusable ...*/ +#if !defined(GPAC_DISABLE_AV_PARSERS) && defined(GPAC_HAS_XVID) + +#if !defined(__GNUC__) +# if defined(_WIN32_WCE) || defined (WIN32) +# pragma comment(lib, "libxvidcore") +# endif +#endif + + +#include + +#ifndef XVID_DEC_FRAME +#define XVID_DEC_FRAME xvid_dec_frame_t +#define XVID_DEC_PARAM xvid_dec_create_t +#else +#define XVID_USE_OLD_API +#endif + +//#undef XVID_USE_OLD_API + +static Bool xvid_is_init = GF_FALSE; + +typedef struct +{ + Bool deblock_y; + Bool deblock_uv; +#ifndef XVID_USE_OLD_API + Bool film_effect; + Bool dering_y; + Bool dering_uv; +#endif + + GF_FilterPid *ipid, *opid; + u32 cfg_crc; + void *codec; + + u32 width, height, out_size; + GF_Fraction pixel_ar; + Bool first_frame; + s32 base_filters; + Float FPS; + u32 offset; + + GF_List *src_packets; + u64 next_cts; + +} GF_XVIDCtx; + +static GF_Err xviddec_initialize(GF_Filter *filter) +{ + GF_XVIDCtx *ctx = gf_filter_get_udta(filter); + if (!xvid_is_init) { +#ifdef XVID_USE_OLD_API + XVID_INIT_PARAM init; + init.api_version = 0; + init.core_build = 0; + /*get info*/ + init.cpu_flags = XVID_CPU_CHKONLY; + xvid_init(NULL, 0, &init, NULL); + /*then init*/ + xvid_init(NULL, 0, &init, NULL); +#else + xvid_gbl_init_t init; + init.debug = 0; + init.version = XVID_VERSION; + init.cpu_flags = 0; /*autodetect*/ + xvid_global(NULL, 0, &init, NULL); +#endif + xvid_is_init = GF_TRUE; + } + +#ifndef XVID_USE_OLD_API + if (ctx->film_effect) ctx->base_filters |= XVID_FILMEFFECT; +#endif + +#ifdef XVID_USE_OLD_API + if (ctx->deblock_y) ctx->base_filters |= XVID_DEC_DEBLOCKY; +#else + if (ctx->deblock_y) ctx->base_filters |= XVID_DEBLOCKY; +#endif + +#ifdef XVID_USE_OLD_API + if (ctx->deblock_uv) ctx->base_filters |= XVID_DEC_DEBLOCKUV; +#else + if (ctx->deblock_uv) ctx->base_filters |= XVID_DEBLOCKUV; +#endif + +#ifndef XVID_USE_OLD_API + if (ctx->dering_y) ctx->base_filters |= XVID_DERINGY | XVID_DEBLOCKY; + if (ctx->dering_uv) ctx->base_filters |= XVID_DERINGUV | XVID_DEBLOCKUV; +#endif + ctx->src_packets = gf_list_new(); + return GF_OK; +} + +static GF_Err xviddec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_M4VDecSpecInfo dsi; + GF_Err e; + Bool is_first = GF_FALSE; +#ifdef XVID_USE_OLD_API + XVID_DEC_FRAME frame; + XVID_DEC_PARAM par; +#else + xvid_dec_frame_t frame; + xvid_dec_create_t par; +#endif + GF_XVIDCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + ctx->ipid = NULL; + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_framing_mode(ctx->ipid, GF_TRUE); + is_first = GF_TRUE; + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (p && p->value.data.ptr && p->value.data.size) { + u32 ex_crc = gf_crc_32(p->value.data.ptr, p->value.data.size); + if (ctx->cfg_crc == ex_crc) return GF_OK; + + //shoud we flush ? + if (ctx->codec) xvid_decore(ctx->codec, XVID_DEC_DESTROY, NULL, NULL); + ctx->codec = NULL; + + ctx->cfg_crc = ex_crc; + } else if (!is_first) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[XVID] Reconfiguring without DSI not yet supported\n")); + return GF_NOT_SUPPORTED; + } else { + return GF_OK; + } + + /*decode DSI*/ + e = gf_m4v_get_config(p->value.data.ptr, p->value.data.size, &dsi); + if (e) return e; + if (!dsi.width || !dsi.height) return GF_NON_COMPLIANT_BITSTREAM; + + memset(&par, 0, sizeof(par)); + par.width = dsi.width; + par.height = dsi.height; + /*note that this may be irrelevant when used through systems (FPS is driven by systems CTS)*/ + ctx->FPS = dsi.clock_rate; + ctx->FPS /= 1000; + if (!ctx->FPS) ctx->FPS = 30.0f; + ctx->pixel_ar.num = dsi.par_num; + ctx->pixel_ar.den = dsi.par_den; + +#ifndef XVID_USE_OLD_API + par.version = XVID_VERSION; +#endif + + if (xvid_decore(NULL, XVID_DEC_CREATE, &par, NULL) < 0) return GF_NON_COMPLIANT_BITSTREAM; + + ctx->width = par.width; + ctx->height = par.height; + ctx->codec = par.handle; + + /*init decoder*/ + memset(&frame, 0, sizeof(frame)); + frame.bitstream = (void *) p->value.data.ptr; + frame.length = p->value.data.size; +#ifndef XVID_USE_OLD_API + frame.version = XVID_VERSION; + xvid_decore(ctx->codec, XVID_DEC_DECODE, &frame, NULL); +#else + /*don't perform error check, XviD doesn't like DSI only frame ...*/ + xvid_decore(ctx->codec, XVID_DEC_DECODE, &frame, NULL); +#endif + + ctx->first_frame = GF_TRUE; + ctx->out_size = ctx->width * ctx->height * 3 / 2; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->width) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->height) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->width) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PAR, &PROP_FRAC(ctx->pixel_ar) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, &PROP_UINT(GF_PIXEL_YUV) ); + + return GF_OK; +} + +static void xviddec_finalize(GF_Filter *filter) +{ + GF_XVIDCtx *ctx = gf_filter_get_udta(filter); + if (ctx->codec) xvid_decore(ctx->codec, XVID_DEC_DESTROY, NULL, NULL); + while (gf_list_count(ctx->src_packets)) { + GF_FilterPacket *pck = gf_list_pop_back(ctx->src_packets); + gf_filter_pck_unref(pck); + } + gf_list_del(ctx->src_packets); +} + +static GF_Err xviddec_process(GF_Filter *filter) +{ +#ifdef XVID_USE_OLD_API + XVID_DEC_FRAME frame; +#else + xvid_dec_frame_t frame; +#endif + u8 *buffer; + u32 i, count; + Bool is_seek; +#if 0 + s32 postproc; +#endif + s32 res; + GF_XVIDCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *pck_ref, *src_pck, *dst_pck; + + pck = gf_filter_pid_get_packet(ctx->ipid); + + if (!ctx->codec) + return ctx->cfg_crc ? GF_SERVICE_ERROR : GF_OK; + + memset(&frame, 0, sizeof(frame)); + if (pck) { + u64 cts = gf_filter_pck_get_cts(pck);; + frame.bitstream = (char *) gf_filter_pck_get_data(pck, &frame.length); + + //append in cts order since we get output frames in cts order + pck_ref = pck; + gf_filter_pck_ref_props(&pck_ref); + count = gf_list_count(ctx->src_packets); + src_pck = NULL; + for (i=0; isrc_packets, i); + acts = gf_filter_pck_get_cts(src_pck); + if (acts==cts) { + gf_filter_pck_unref(pck_ref); + break; + } + if (acts>cts) { + gf_list_insert(ctx->src_packets, pck_ref, i); + break; + } + src_pck = NULL; + } + if (!src_pck) + gf_list_add(ctx->src_packets, pck_ref); + + + } else { + frame.bitstream = NULL; + frame.length = -1; + } + +packed_frame : + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->width*ctx->height*3/2, &buffer); + +#ifdef XVID_USE_OLD_API + frame.colorspace = XVID_CSP_I420; + frame.stride = ctx->width; + frame.image = (void *) buffer; +#else + frame.version = XVID_VERSION; + frame.output.csp = XVID_CSP_I420; + frame.output.stride[0] = ctx->width; + frame.output.plane[0] = (void *) buffer; +#endif + + +#if 0 + postproc = ctx->base_filters; + /*to check, not convinced yet by results...*/ + switch (mmlevel) { + case GF_CODEC_LEVEL_SEEK: + case GF_CODEC_LEVEL_DROP: + /*turn off all post-proc*/ +#ifdef XVID_USE_OLD_API + postproc &= ~XVID_DEC_DEBLOCKY; + postproc &= ~XVID_DEC_DEBLOCKUV; +#else + postproc &= ~XVID_DEBLOCKY; + postproc &= ~XVID_DEBLOCKUV; + postproc &= ~XVID_FILMEFFECT; +#endif + break; + case GF_CODEC_LEVEL_VERY_LATE: + /*turn off post-proc*/ +#ifdef XVID_USE_OLD_API + postproc &= ~XVID_DEC_DEBLOCKY; +#else + postproc &= ~XVID_FILMEFFECT; + postproc &= ~XVID_DEBLOCKY; +#endif + break; + case GF_CODEC_LEVEL_LATE: +#ifdef XVID_USE_OLD_API + postproc &= ~XVID_DEC_DEBLOCKUV; +#else + postproc &= ~XVID_DEBLOCKUV; + postproc &= ~XVID_FILMEFFECT; +#endif + break; + } +#endif + + /*xvid may keep the first I frame and force a 1-frame delay, so we simply trick it*/ + if (ctx->first_frame) { + buffer[0] = 'v'; + buffer[1] = 'o'; + buffer[2] = 'i'; + buffer[3] = 'd'; + } + src_pck = gf_list_get(ctx->src_packets, 0); + + res = xvid_decore(ctx->codec, XVID_DEC_DECODE, &frame, NULL); + if (res < 0) { + gf_filter_pck_discard(dst_pck); + if (pck) gf_filter_pid_drop_packet(ctx->ipid); + if (src_pck) { + gf_filter_pck_unref(src_pck); + gf_list_pop_front(ctx->src_packets); + } + if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return pck ? GF_NON_COMPLIANT_BITSTREAM : GF_OK; + } + + if (ctx->first_frame) { + ctx->first_frame = GF_FALSE; + if ((buffer[0] == 'v') && (buffer[1] == 'o') && (buffer[2] == 'i') && (buffer[3] =='d')) { + gf_filter_pck_discard(dst_pck); + if (pck) gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + } + + if (src_pck) { + gf_filter_pck_merge_properties(src_pck, dst_pck); + is_seek = gf_filter_pck_get_seek_flag(src_pck); + ctx->next_cts = gf_filter_pck_get_cts(src_pck); + ctx->next_cts += gf_filter_pck_get_duration(src_pck); + gf_filter_pck_unref(src_pck); + gf_list_pop_front(ctx->src_packets); + } else { + is_seek = 0; + gf_filter_pck_set_cts(dst_pck, ctx->next_cts); + } + + if (!pck || !is_seek ) + gf_filter_pck_send(dst_pck); + else + gf_filter_pck_discard(dst_pck); + + if (res + 6 < frame.length) { + frame.bitstream = ((char *)frame.bitstream) + res; + frame.length -= res; + goto packed_frame; + } + + if (pck) { + gf_filter_pid_drop_packet(ctx->ipid); + } + //flush all frames if eos is detected + else if (gf_filter_pid_is_eos(ctx->ipid)) { + return xviddec_process(filter); + } + + return GF_OK; +} + +static const GF_FilterCapability XVIDCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG4_PART2), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +#define OFFS(_n) #_n, offsetof(GF_XVIDCtx, _n) + +static const GF_FilterArgs XVIDArgs[] = +{ + { OFFS(deblock_y), "enable Y deblocking", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(deblock_uv), "enable UV deblocking", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, +#ifndef XVID_USE_OLD_API + { OFFS(film_effect), "enable film effect", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(dering_y), "enable Y deblocking", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(dering_uv), "enable UV deblocking", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, +#endif + {0} +}; + +GF_FilterRegister XVIDRegister = { + .name = "xviddec", + GF_FS_SET_DESCRIPTION("XVid decoder") + GF_FS_SET_HELP("This filter decodes MPEG-4 part 2 (and DivX) through libxvidcore library.") + .private_size = sizeof(GF_XVIDCtx), + .args = XVIDArgs, + SETCAPS(XVIDCaps), + .initialize = xviddec_initialize, + .finalize = xviddec_finalize, + .configure_pid = xviddec_configure_pid, + .process = xviddec_process, + //use low priorty, below ffmpeg one, so that hardware decs/other native impl in gpac can take over if needed + //don't use lowest one since we use this for scalable codecs + .priority = 100 +}; + +#endif + +const GF_FilterRegister *xviddec_register(GF_FilterSession *session) +{ +#if !defined(GPAC_DISABLE_AV_PARSERS) && defined(GPAC_HAS_XVID) + return &XVIDRegister; +#else + return NULL; +#endif +} diff --git a/src/filters/decrypt_cenc_isma.c b/src/filters/decrypt_cenc_isma.c new file mode 100644 index 0000000..abbc725 --- /dev/null +++ b/src/filters/decrypt_cenc_isma.c @@ -0,0 +1,1335 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018 + * All rights reserved + * + * This file is part of GPAC / CENC and ISMA decrypt filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_CRYPTO + +//#define OLD_KEY_FETCHERS + +enum +{ + DECRYPT_STATE_ERROR, + DECRYPT_STATE_SETUP, + DECRYPT_STATE_PLAY, +}; + +typedef struct +{ + GF_FilterPid *ipid, *opid; + + GF_Crypt *crypt; + bin128 key, kid; + u32 state; + u32 pssh_crc; + u32 scheme_type, scheme_version; + + GF_Err key_error; + + //ISMA & OMA + char salt[8]; + u64 last_IV; + u32 nb_allow_play; + Bool is_oma, is_adobe; + u32 preview_range; + Bool is_nalu; + Bool selective_encryption; + u32 IV_length, KI_length; + + /*CENC*/ + Bool is_cenc; + Bool is_cbc; + u32 KID_count; + bin128 *KIDs; + bin128 *keys; + + /*adobe and CENC*/ + Bool crypt_init; +} GF_CENCDecStream; + +typedef struct +{ + const char *cfile; + + GF_CryptInfo *cinfo; + + GF_List *streams; + GF_BitStream *bs_r; + + GF_DownloadManager *dm; +} GF_CENCDecCtx; + + +#ifdef OLD_KEY_FETCHERS +static void cenc_dec_kms_netio(void *cbck, GF_NETIO_Parameter *par) +{ +} + +static GF_Err gf_ismacryp_gpac_get_info(u32 stream_id, char *drm_file, char *key, char *salt) +{ + GF_Err e; + u32 i, count; + GF_CryptInfo *info; + + e = GF_OK; + info = gf_crypt_info_load(drm_file, &e); + if (!info) return e; + count = gf_list_count(info->tcis); + for (i=0; itcis, i); + if ((info->has_common_key && !tci->trackID) || (tci->trackID == stream_id) ) { + if (tci->KID_count) + memcpy(key, tci->keys[0], sizeof(char)*16); + memcpy(salt, tci->first_IV, sizeof(char)*8); + e = GF_OK; + break; + } + } + gf_crypt_info_del(info); + return e; +} + +static GF_Err cenc_dec_get_gpac_kms(GF_CENCDecCtx *ctx, GF_CENCDecStream *cstr, const char *kms_url) +{ + const GF_PropertyValue *prop; + FILE *t; + u32 id = 0; + GF_FilterPid *pid = cstr->ipid; + GF_Err e; + GF_DownloadSession * sess; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + if (!prop) prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ESID); + if (prop) id = prop->value.uint; + + if (!strnicmp(kms_url, "(ipmp)", 6)) return GF_NOT_SUPPORTED; + else if (!strnicmp(kms_url, "(uri)", 5)) kms_url += 5; + else if (!strnicmp(kms_url, "file://", 7)) kms_url += 7; + + /*try local*/ + t = (strstr(kms_url, "://") == NULL) ? gf_fopen(kms_url, "rb") : NULL; + if (t) { + gf_fclose(t); + return gf_ismacryp_gpac_get_info(id, (char *)kms_url, cstr->key, cstr->salt); + } + /*note that gpac doesn't have TLS support -> not really useful. As a general remark, ISMACryp + is supported as a proof of concept, crypto and IPMP being the last priority on gpac...*/ + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[CENC/ISMA] Fetching ISMACryp key for channel %d\n", id) ); + + sess = gf_dm_sess_new(ctx->dm, kms_url, 0, cenc_dec_kms_netio, ctx, NULL); + if (!sess) return GF_IO_ERR; + /*start our download (threaded)*/ + gf_dm_sess_process(sess); + + while (1) { + e = gf_dm_sess_get_stats(sess, NULL, NULL, NULL, NULL, NULL, NULL); + if (e) break; + } + if (e==GF_EOS) { + e = gf_ismacryp_gpac_get_info(id, (char *) gf_dm_sess_get_cache_name(sess), cstr->key, cstr->salt); + } + gf_dm_sess_del(sess); + return e; +} + +static Bool gf_ismacryp_mpeg4ip_get_info(char *kms_uri, char *key, char *salt) +{ + char szPath[1024], catKey[24], line[101]; + u32 i, x; + Bool got_it; + FILE *kms; + strcpy(szPath, getenv("HOME")); + strcat(szPath , "/.kms_data"); + got_it = 0; + kms = gf_fopen(szPath, "rt"); + while (kms && !gf_feof(kms)) { + if (!gf_fgets(szPath, 1024, kms)) break; + szPath[strlen(szPath) - 1] = 0; + if (stricmp(szPath, kms_uri)) continue; + gf_fgets(line, 1, 100, kms); + line[100] = 0; + for (i=0; i<24; i++) { + char szV[3]; + szV[0] = line[2*i]; + szV[1] = line[2*i + 1]; + szV[2] = 0; + if (!sscanf(szV, "%x", &x)) break; + catKey[i] = x; + } + if (i==24) got_it = 1; + break; + } + if (kms) gf_fclose(kms); + if (got_it) { + /*watchout, MPEG4IP stores SALT|KEY, NOT KEY|SALT*/ + memcpy(key, catKey+8, sizeof(char)*16); + memcpy(salt, catKey, sizeof(char)*8); + return 1; + } + return 0; +} +#endif + + +static GF_Err cenc_dec_setup_isma(GF_CENCDecCtx *ctx, GF_CENCDecStream *cstr, u32 scheme_type, u32 scheme_version, const char *scheme_uri, const char *kms_uri) +{ + u32 kms_crc=0; + const GF_PropertyValue *p; + cstr->state = DECRYPT_STATE_ERROR; + + if (scheme_type != GF_ISOM_ISMACRYP_SCHEME) return GF_NOT_SUPPORTED; + if (scheme_version != 1) return GF_NOT_SUPPORTED; + if (!kms_uri) return GF_NON_COMPLIANT_BITSTREAM; + + kms_crc = gf_crc_32(kms_uri, (u32) strlen(kms_uri)); + if (cstr->pssh_crc == kms_crc) return GF_OK; + cstr->pssh_crc = kms_crc; + + /*try to fetch the keys*/ + /*base64 inband encoding*/ + if (!strnicmp(kms_uri, "(key)", 5)) { + char data[100]; + gf_base64_decode((char*) kms_uri+5, (u32)strlen(kms_uri)-5, data, 100); + memcpy(cstr->key, data, sizeof(char)*16); + memcpy(cstr->salt, data+16, sizeof(char)*8); + } + /*hexadecimal inband encoding*/ + else if (!strnicmp(kms_uri, "(key-hexa)", 10)) { + u32 v; + char szT[3], *k; + u32 i; + szT[2] = 0; + if (strlen(kms_uri) < 10+32+16) return GF_NON_COMPLIANT_BITSTREAM; + + k = (char *)kms_uri + 10; + for (i=0; i<16; i++) { + szT[0] = k[2*i]; + szT[1] = k[2*i + 1]; + sscanf(szT, "%X", &v); + cstr->key[i] = v; + } + + k = (char *)kms_uri + 10 + 32; + for (i=0; i<8; i++) { + szT[0] = k[2*i]; + szT[1] = k[2*i + 1]; + sscanf(szT, "%X", &v); + cstr->salt[i] = v; + } + } + /*MPEG4-IP KMS*/ + else if (!stricmp(kms_uri, "AudioKey") || !stricmp(kms_uri, "VideoKey")) { +#ifdef OLD_KEY_FETCHERS + if (!gf_ismacryp_mpeg4ip_get_info((char *) kms_uri, cstr->key, cstr->salt)) { + return GF_BAD_PARAM; + } +#else + return GF_NOT_SUPPORTED; +#endif + } + /*gpac default scheme is used, fetch file from KMS and load keys*/ + else if (scheme_uri && !stricmp(scheme_uri, "urn:gpac:isma:encryption_scheme")) { +#ifdef OLD_KEY_FETCHERS + e = cenc_dec_get_gpac_kms(ctx, cstr, kms_uri); + if (e) return e; +#else + return GF_NOT_SUPPORTED; +#endif + } + /*hardcoded keys*/ + else { + static u8 mysalt[] = { 8,7,6,5,4,3,2,1, 0,0,0,0,0,0,0,0 }; + static u8 mykey[][16] = { + { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 + } + }; + memcpy(cstr->salt, mysalt, sizeof(char)*8); + memcpy(cstr->key, mykey, sizeof(char)*16); + } + cstr->state = DECRYPT_STATE_SETUP; + + p = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_ISMA_SELECTIVE_ENC); + if (p) cstr->selective_encryption = p->value.boolean; + p = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_ISMA_IV_LENGTH); + if (p) cstr->IV_length = p->value.uint; + p = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_ISMA_KI_LENGTH); + if (p) cstr->KI_length = p->value.uint; + + + //ctx->nb_allow_play = 1; + return GF_OK; +} + +static GF_Err cenc_dec_access_isma(GF_CENCDecCtx *ctx, GF_CENCDecStream *cstr, Bool is_play) +{ + GF_Err e; + + if (is_play) { + char IV[16]; + if (cstr->state != DECRYPT_STATE_SETUP) + return GF_SERVICE_ERROR; + assert(!cstr->crypt); + + //if (!ctx->nb_allow_play) return GF_AUTHENTICATION_FAILURE; + //ctx->nb_allow_play--; + + /*init decrypter*/ + cstr->crypt = gf_crypt_open(GF_AES_128, GF_CTR); + if (!cstr->crypt) return GF_IO_ERR; + + memset(IV, 0, sizeof(char)*16); + memcpy(IV, cstr->salt, sizeof(char)*8); + e = gf_crypt_init(cstr->crypt, cstr->key, IV); + if (e) return e; + + cstr->state = DECRYPT_STATE_PLAY; + return GF_OK; + } else { + if (cstr->state != DECRYPT_STATE_PLAY) + return GF_SERVICE_ERROR; + if (cstr->crypt) gf_crypt_close(cstr->crypt); + cstr->crypt = NULL; + cstr->state = DECRYPT_STATE_SETUP; + return GF_OK; + } + return GF_BAD_PARAM; +} + +static GF_Err cenc_dec_process_isma(GF_CENCDecCtx *ctx, GF_CENCDecStream *cstr, GF_FilterPacket *in_pck) +{ + u32 data_size; + const char *in_data; + u8 *out_data; + u64 isma_BSO = 0; + u32 offset=0; + GF_FilterPacket *out_pck; + Bool is_encrypted=GF_FALSE; + if (!cstr->crypt) + return GF_SERVICE_ERROR; + + if (! gf_filter_pck_get_crypt_flags(in_pck)) { + out_pck = gf_filter_pck_new_ref(cstr->opid, NULL, 0, in_pck); + gf_filter_pck_merge_properties(in_pck, out_pck); + gf_filter_pck_set_crypt_flags(out_pck, 0); + gf_filter_pck_send(out_pck); + return GF_OK; + } + + in_data = gf_filter_pck_get_data(in_pck, &data_size); + + gf_bs_reassign_buffer(ctx->bs_r, in_data, data_size); + + if (cstr->selective_encryption) { + if (gf_bs_read_int(ctx->bs_r, 1)) is_encrypted=GF_TRUE; + gf_bs_read_int(ctx->bs_r, 7); + offset = 1; + } else { + is_encrypted=GF_TRUE; + } + if (is_encrypted) { + if (cstr->IV_length != 0) { + isma_BSO = gf_bs_read_long_int(ctx->bs_r, 8*cstr->IV_length); + offset += cstr->IV_length; + } + if (cstr->KI_length) { + offset += cstr->KI_length; + } + } + + /*resync IV*/ + if (!cstr->last_IV || (cstr->last_IV != isma_BSO)) { + char IV[17]; + u64 count; + u32 remain; + GF_BitStream *bs; + count = isma_BSO / 16; + remain = (u32) (isma_BSO % 16); + + /*format IV to begin of counter*/ + bs = gf_bs_new(IV, 17, GF_BITSTREAM_WRITE); + gf_bs_write_u8(bs, 0); /*begin of counter*/ + gf_bs_write_data(bs, cstr->salt, 8); + gf_bs_write_u64(bs, (s64) count); + gf_bs_del(bs); + gf_crypt_set_IV(cstr->crypt, IV, 17); + + /*decrypt remain bytes*/ + if (remain) { + char dummy[20]; + gf_crypt_decrypt(cstr->crypt, dummy, remain); + } + cstr->last_IV = isma_BSO; + } + in_data += offset; + data_size -= offset; + + out_pck = gf_filter_pck_new_alloc(cstr->opid, data_size, &out_data); + + memcpy(out_data, in_data, data_size); + /*decrypt*/ + gf_crypt_decrypt(cstr->crypt, out_data, data_size); + cstr->last_IV += data_size; + + /*replace AVC start codes (0x00000001) by nalu size*/ + if (cstr->is_nalu) { + u32 nalu_size; + u32 remain = data_size; + char *start, *end; + start = out_data; + end = start + 4; + while (remain>4) { + if (!end[0] && !end[1] && !end[2] && (end[3]==0x01)) { + nalu_size = (u32) (end - start - 4); + start[0] = (nalu_size>>24)&0xFF; + start[1] = (nalu_size>>16)&0xFF; + start[2] = (nalu_size>>8)&0xFF; + start[3] = (nalu_size)&0xFF; + start = end; + end = start+4; + remain -= 4; + continue; + } + end++; + remain--; + } + nalu_size = (u32) (end - start - 4); + start[0] = (nalu_size>>24)&0xFF; + start[1] = (nalu_size>>16)&0xFF; + start[2] = (nalu_size>>8)&0xFF; + start[3] = (nalu_size)&0xFF; + } + + gf_filter_pck_merge_properties(in_pck, out_pck); + gf_filter_pck_set_crypt_flags(out_pck, 0); + + gf_filter_pck_send(out_pck); + return GF_OK; +} + +#ifdef OLD_KEY_FETCHERS + +static GF_Err cenc_dec_setup_oma(GF_CENCDecCtx *ctx, GF_CENCDecStream *cstr, u32 scheme_type, u32 scheme_version, const char *scheme_uri, const char *kms_uri) +{ + const GF_PropertyValue *prop; + GF_FilterPid *pid = cstr->ipid; + + cstr->state = DECRYPT_STATE_ERROR; + if (scheme_type != GF_ISOM_OMADRM_SCHEME) return GF_NOT_SUPPORTED; + if (scheme_version != 0x00000200) return GF_NOT_SUPPORTED; + + cstr->is_oma = GF_TRUE; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_OMA_PREVIEW_RANGE); + if (prop) cstr->preview_range = (u32) prop->value.longuint; + + /*TODO: call DRM agent, fetch keys*/ + if (!kms_uri) return GF_NON_COMPLIANT_BITSTREAM; + cstr->state = DECRYPT_STATE_SETUP; + + /*we have preview*/ + if (cstr->preview_range) return GF_OK; + + return GF_NOT_SUPPORTED; +} +#endif + +static GF_Err cenc_dec_setup_cenc(GF_CENCDecCtx *ctx, GF_CENCDecStream *cstr, u32 scheme_type, u32 scheme_version, const char *scheme_uri, const char *kms_uri) +{ + GF_CryptInfo *cinfo=NULL; + u32 i, nb_pssh, pssh_crc=0; + const GF_PropertyValue *prop, *cinfo_prop; + char *pssh_data=NULL; + GF_FilterPid *pid = cstr->ipid; + Bool is_playing = (cstr->state == DECRYPT_STATE_PLAY) ? GF_TRUE : GF_FALSE; + + cstr->state = DECRYPT_STATE_ERROR; + + if ((scheme_type != GF_ISOM_CENC_SCHEME) + && (scheme_type != GF_ISOM_CBC_SCHEME) + && (scheme_type != GF_ISOM_CENS_SCHEME) + && (scheme_type != GF_ISOM_CBCS_SCHEME) + && (scheme_type != GF_ISOM_PIFF_SCHEME) + ) + return GF_NOT_SUPPORTED; + + if (scheme_version != 0x00010000) { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[CENC/ISMA] Invalid scheme version %08X for scheme type %s, results might be wrong\n", scheme_version, gf_4cc_to_str(scheme_type) )); + } + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CENC_PSSH); + if (prop) { + pssh_data = (char *) prop->value.data.ptr; + pssh_crc = gf_crc_32(prop->value.data.ptr, prop->value.data.size); + } + if ((cstr->scheme_type==scheme_type) && (cstr->scheme_version==scheme_version) && (cstr->pssh_crc==pssh_crc) ) + return GF_OK; + + cstr->scheme_version = scheme_version; + cstr->scheme_type = scheme_type; + cstr->pssh_crc = pssh_crc; + + if ((scheme_type == GF_ISOM_CENC_SCHEME) || (scheme_type == GF_ISOM_PIFF_SCHEME) || (scheme_type == GF_ISOM_CENS_SCHEME)) + cstr->is_cenc = GF_TRUE; + else + cstr->is_cbc = GF_TRUE; + + cstr->state = is_playing ? DECRYPT_STATE_PLAY : DECRYPT_STATE_SETUP; + //ctx->nb_allow_play = 1; + + cinfo_prop = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_DECRYPT_INFO); + if (!cinfo_prop) cinfo_prop = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_CRYPT_INFO); + + if (pssh_data) gf_bs_reassign_buffer(ctx->bs_r, prop->value.data.ptr, prop->value.data.size); + nb_pssh = pssh_data ? gf_bs_read_u32(ctx->bs_r) : 0; + for (i = 0; i < nb_pssh; i++) { + u8 cypherOffset; + char *enc_data; + bin128 cypherKey, cypherIV; + GF_Crypt *mc; + u32 pos, priv_len; + char szSystemID[33]; + bin128 sysID; + u32 j, kid_count; + + gf_bs_read_data(ctx->bs_r, sysID, 16); + /*version =*/ gf_bs_read_u32(ctx->bs_r); + kid_count = gf_bs_read_u32(ctx->bs_r); + + memset(szSystemID, 0, 33); + for (j=0; j<16; j++) { + sprintf(szSystemID+j*2, "%02X", (unsigned char) sysID[j]); + } + + /*SystemID for GPAC Player: 67706163-6365-6E63-6472-6D746F6F6C31*/ + if (strcmp(szSystemID, "6770616363656E6364726D746F6F6C31")) { + if (!cinfo_prop && !ctx->cfile) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("[CENC/ISMA] System ID %s not supported\n", szSystemID)); + } + gf_bs_skip_bytes(ctx->bs_r, kid_count*16); + j=gf_bs_read_u32(ctx->bs_r); + gf_bs_skip_bytes(ctx->bs_r, j); + continue; + } + + /*store key IDs*/ + cstr->KID_count = kid_count; + pos = (u32) gf_bs_get_position(ctx->bs_r); + cstr->KIDs = (bin128 *)gf_realloc(cstr->KIDs, cstr->KID_count*sizeof(bin128)); + cstr->keys = (bin128 *)gf_realloc(cstr->keys, cstr->KID_count*sizeof(bin128)); + + memmove(cstr->KIDs, pssh_data + pos, cstr->KID_count*sizeof(bin128)); + gf_bs_skip_bytes(ctx->bs_r, cstr->KID_count*sizeof(bin128)); + priv_len = gf_bs_read_u32(ctx->bs_r); + pos = (u32) gf_bs_get_position(ctx->bs_r); + + /*GPAC DRM TEST system info, used to validate cypher offset in CENC packager + keyIDs as usual (before private data) + URL len on 8 bits + URL + keys, cyphered with our magic key :) + */ + cypherOffset = pssh_data[pos] + 1; + gf_bin128_parse("0x6770616363656E6364726D746F6F6C31", cypherKey); + gf_bin128_parse("0x00000000000000000000000000000001", cypherIV); + + mc = gf_crypt_open(GF_AES_128, GF_CTR); + if (!mc) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC/ISMA] Cannot open AES-128 CTR\n")); + return GF_IO_ERR; + } + enc_data = gf_malloc(priv_len - cypherOffset); + memcpy(enc_data, pssh_data + pos + cypherOffset, priv_len - cypherOffset); + gf_crypt_init(mc, cypherKey, cypherIV); + gf_crypt_decrypt(mc, enc_data, priv_len - cypherOffset); + gf_crypt_close(mc); + + memmove(cstr->keys, enc_data, cstr->KID_count*sizeof(bin128)); + gf_bs_skip_bytes(ctx->bs_r, cstr->KID_count*sizeof(bin128)); + gf_free(enc_data); + return GF_OK; + } + + if (cinfo_prop) { + GF_Err e; + cinfo = gf_crypt_info_load(cinfo_prop->value.string, &e); + if (!cinfo) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC/ISMA] Failed to open crypt info file %s\n", cinfo_prop->value.string)); + return e; + } + } + if (!cinfo) cinfo = ctx->cinfo; + + if (cinfo) { + GF_TrackCryptInfo *any_tci = NULL; + GF_TrackCryptInfo *tci = NULL; + u32 count = gf_list_count(cinfo->tcis); + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + for (i=0; itcis, i); + if (tci->trackID && prop && (tci->trackID==prop->value.uint)) { + break; + } + if (!any_tci && !tci->trackID) any_tci = tci; + if ((cinfo != ctx->cinfo) && !any_tci) any_tci = tci; + tci = NULL; + } + if (!tci && any_tci) tci = any_tci; + + if (tci) { + cstr->KID_count = tci->KID_count; + cstr->KIDs = (bin128 *)gf_realloc(cstr->KIDs, tci->KID_count*sizeof(bin128)); + cstr->keys = (bin128 *)gf_realloc(cstr->keys, tci->KID_count*sizeof(bin128)); + for (i=0; iKID_count; i++) { + memcpy(cstr->KIDs[i], tci->KIDs[i], sizeof(bin128)); + memcpy(cstr->keys[i], tci->keys[i], sizeof(bin128)); + } + if (cinfo != ctx->cinfo) + gf_crypt_info_del(cinfo); + return GF_OK; + } + if (cinfo != ctx->cinfo) + gf_crypt_info_del(cinfo); + } + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[CENC/ISMA] No supported system ID, no key found\n")); + return GF_NOT_SUPPORTED; +} + +static GF_Err cenc_dec_setup_adobe(GF_CENCDecCtx *ctx, GF_CENCDecStream *cstr, u32 scheme_type, u32 scheme_version, const char *scheme_uri, const char *kms_uri) +{ + GF_CryptInfo *cinfo=NULL; + const GF_PropertyValue *prop; + GF_FilterPid *pid = cstr->ipid; + Bool is_playing = (cstr->state == DECRYPT_STATE_PLAY) ? GF_TRUE : GF_FALSE; + + cstr->state = DECRYPT_STATE_ERROR; + + if (scheme_type != GF_ISOM_ADOBE_SCHEME) return GF_NOT_SUPPORTED; + if (scheme_version != 1) return GF_NOT_SUPPORTED; + + + if (cstr->KID_count) return GF_OK; + + cstr->is_adobe = GF_TRUE; + + cstr->state = is_playing ? DECRYPT_STATE_PLAY : DECRYPT_STATE_SETUP; + + prop = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_DECRYPT_INFO); + if (!prop) prop = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_CRYPT_INFO); + + if (prop) { + GF_Err e; + cinfo = gf_crypt_info_load(prop->value.string, &e); + if (!cinfo) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC/ISMA] Failed to open crypt info file %s\n", prop->value.string)); + return e; + } + } + if (!cinfo) cinfo = ctx->cinfo; + + if (cinfo) { + GF_TrackCryptInfo *any_tci = NULL; + GF_TrackCryptInfo *tci = NULL; + u32 i, count = gf_list_count(cinfo->tcis); + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + for (i=0; itcis, i); + if (tci->trackID && prop && (tci->trackID==prop->value.uint)) { + break; + } + if (!any_tci && !tci->trackID) any_tci = tci; + if ((cinfo != ctx->cinfo) && !any_tci) any_tci = tci; + tci = NULL; + } + if (!tci && any_tci) tci = any_tci; + + if (tci) { + cstr->KID_count = tci->KID_count; + cstr->KIDs = (bin128 *)gf_realloc(cstr->KIDs, tci->KID_count*sizeof(bin128)); + cstr->keys = (bin128 *)gf_realloc(cstr->keys, tci->KID_count*sizeof(bin128)); + for (i=0; iKID_count; i++) { + memcpy(cstr->KIDs[i], tci->KIDs[i], sizeof(bin128)); + memcpy(cstr->keys[i], tci->keys[i], sizeof(bin128)); + } + if (cinfo != ctx->cinfo) + gf_crypt_info_del(cinfo); + return GF_OK; + } + if (cinfo != ctx->cinfo) + gf_crypt_info_del(cinfo); + } + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[CENC/ISMA] No supported system ID, no key found\n")); + return GF_NOT_SUPPORTED; +} + +static GF_Err cenc_dec_access_cenc(GF_CENCDecCtx *ctx, GF_CENCDecStream *cstr, Bool is_play) +{ + if (is_play) { + if (cstr->state != DECRYPT_STATE_SETUP) + return GF_SERVICE_ERROR; + assert(!cstr->crypt); + + //if (!ctx->nb_allow_play) return GF_AUTHENTICATION_FAILURE; + //ctx->nb_allow_play--; + + /*open decrypter - we do NOT initialize decrypter; it wil be done when we decrypt the first crypted sample*/ + if (cstr->is_cenc) + cstr->crypt = gf_crypt_open(GF_AES_128, GF_CTR); + else + cstr->crypt = gf_crypt_open(GF_AES_128, GF_CBC); + if (!cstr->crypt) return GF_IO_ERR; + + cstr->state = DECRYPT_STATE_PLAY; + return GF_OK; + } else { + if (cstr->state != DECRYPT_STATE_PLAY) + return GF_SERVICE_ERROR; + if (cstr->crypt) gf_crypt_close(cstr->crypt); + cstr->crypt = NULL; + cstr->state = DECRYPT_STATE_SETUP; + return GF_OK; + } + return GF_BAD_PARAM; +} + +static GF_Err cenc_dec_access_adobe(GF_CENCDecCtx *ctx, GF_CENCDecStream *cstr, Bool is_play) +{ + if (is_play) { + if (cstr->state != DECRYPT_STATE_SETUP) + return GF_SERVICE_ERROR; + assert(!cstr->crypt); + + cstr->crypt = gf_crypt_open(GF_AES_128, GF_CBC); + if (!cstr->crypt) return GF_IO_ERR; + + cstr->state = DECRYPT_STATE_PLAY; + return GF_OK; + } else { + if (cstr->state != DECRYPT_STATE_PLAY) + return GF_SERVICE_ERROR; + if (cstr->crypt) gf_crypt_close(cstr->crypt); + cstr->crypt = NULL; + cstr->state = DECRYPT_STATE_SETUP; + return GF_OK; + } + return GF_BAD_PARAM; +} + + +static GF_Err cenc_dec_process_cenc(GF_CENCDecCtx *ctx, GF_CENCDecStream *cstr, GF_FilterPacket *in_pck) +{ + GF_Err e; + char IV[17]; + bin128 KID; + u32 i, subsample_count; + u32 data_size; + u8 *out_data; + const char *sai_payload=NULL; + u32 saiz=0; + u32 IV_size; + Bool crypt_reinit = GF_FALSE; + GF_FilterPacket *out_pck; + const GF_PropertyValue *prop, *const_IV=NULL, *cbc_pattern=NULL; + + if (!cstr->crypt) + return GF_SERVICE_ERROR; + + gf_filter_pck_get_data(in_pck, &data_size); + + if (!data_size || ! gf_filter_pck_get_crypt_flags(in_pck)) { + out_pck = gf_filter_pck_new_ref(cstr->opid, NULL, 0, in_pck); + gf_filter_pck_merge_properties(in_pck, out_pck); + gf_filter_pck_set_property(out_pck, GF_PROP_PCK_CENC_SAI, NULL); + gf_filter_pck_set_crypt_flags(out_pck, 0); + gf_filter_pck_send(out_pck); + return GF_OK; + } + prop = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_KID); + if (!prop) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Packet encrypted but no KID info\n" ) ); + return GF_SERVICE_ERROR; + } + memcpy(KID, prop->value.data.ptr, 16); + + prop = gf_filter_pck_get_property(in_pck, GF_PROP_PCK_CENC_SAI); + if (prop) { + sai_payload = prop->value.data.ptr; + saiz = prop->value.data.size; + gf_bs_reassign_buffer(ctx->bs_r, sai_payload, saiz); + } + + //CENC can use inplace processing for decryption + out_pck = gf_filter_pck_new_clone(cstr->opid, in_pck, &out_data); + if (!out_pck) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Failed to allocated/clone packet for decrypting payload\n" ) ); + gf_filter_pid_drop_packet(cstr->ipid); + return GF_SERVICE_ERROR; + } + + //memset to 0 in case we use <16 byte key + memset(IV, 0, sizeof(char)*17); + prop = NULL; + if (sai_payload) + prop = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_CENC_IV_SIZE); + + if (!prop) { + const_IV = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_CENC_IV_CONST); + IV_size = 0; + } else { + IV_size = prop->value.uint; + } + if (!sai_payload && !const_IV) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Packet encrypted but no SAI info nor constant IV\n" ) ); + return GF_SERVICE_ERROR; + + } + + cbc_pattern = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_CENC_PATTERN); + + /*read sample auxiliary information from bitstream*/ + subsample_count = 0; + if (sai_payload) { + gf_bs_read_data(ctx->bs_r, (char *) IV, IV_size); + + if (gf_bs_available(ctx->bs_r)) + subsample_count = gf_bs_read_u16(ctx->bs_r); + } + + if (const_IV) IV_size = const_IV->value.data.size; + + if (strncmp(cstr->kid, KID, 16)) { + bin128 blank_KID; + Bool found = GF_FALSE; + memcpy(cstr->kid, KID, 16); + memset(blank_KID, 0, 16); + for (i = 0; i < cstr->KID_count; i++) { + if (!strncmp((const char *)KID, (const char *)cstr->KIDs[i], 16) + || !strncmp((const char *)blank_KID, (const char *)cstr->KIDs[i], 16) + ) { + memcpy(cstr->key, cstr->keys[i], 16); + found = GF_TRUE; + break; + } + } + if (!found) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Cannot locate key with given KID !\n" )); + cstr->key_error = e = GF_SERVICE_ERROR; + goto exit; + } + cstr->key_error = GF_OK; + crypt_reinit = GF_TRUE; + cstr->crypt_init =GF_TRUE; + } + if (!cstr->crypt_init) { + crypt_reinit = GF_TRUE; + cstr->crypt_init = GF_TRUE; + } + if (cstr->key_error) { + e = cstr->key_error; + goto exit; + } + + if (crypt_reinit) { + if (const_IV) { + memcpy(IV, const_IV->value.data.ptr, const_IV->value.data.size); + } + e = gf_crypt_init(cstr->crypt, cstr->key, IV); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Cannot initialize AES-128 AES-128 %s (%s)\n", cstr->is_cenc ? "CTR" : "CBC", gf_error_to_string(e)) ); + e = GF_IO_ERR; + goto exit; + } + } else { + //always restore IV at begining of sample regardless of the mode (const IV or IV CBC or CTR + if (!cstr->is_cbc) { + memmove(&IV[1], &IV[0], sizeof(char) * IV_size); + IV[0] = 0; /*begin of counter*/ + e = gf_crypt_set_IV(cstr->crypt, IV, 17); + } else { + if (const_IV) { + memcpy(IV, const_IV->value.data.ptr, const_IV->value.data.size); + } + e = gf_crypt_set_IV(cstr->crypt, IV, 16); + } + + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Cannot set key AES-128 %s (%s)\n", cstr->is_cenc ? "CTR" : "CBC", gf_error_to_string(e)) ); + e = GF_IO_ERR; + goto exit; + } + } + + //sub-sample encryption + if (subsample_count) { + u32 cur_pos = 0; + + while (cur_pos < data_size) { + u32 bytes_clear_data, bytes_encrypted_data; + if (subsample_count==0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Corrupted CENC sai, not enough subsamples described\n" )); + e = GF_NON_COMPLIANT_BITSTREAM; + goto exit; + } + if (gf_bs_available(ctx->bs_r) < 6) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Corrupted CENC sai, not enough bytes in subsample info\n" )); + e = GF_NON_COMPLIANT_BITSTREAM; + goto exit; + } + bytes_clear_data = gf_bs_read_u16(ctx->bs_r); + bytes_encrypted_data = gf_bs_read_u32(ctx->bs_r); + subsample_count--; + + if (const_IV) { + memmove(IV, const_IV->value.data.ptr, const_IV->value.data.size); + if (const_IV->value.data.size == 8) + memset(IV+8, 0, sizeof(char)*8); + gf_crypt_set_IV(cstr->crypt, IV, 16); + } + if (cur_pos + bytes_clear_data + bytes_encrypted_data > data_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Corrupted CENC sai, subsample info describe more bytes (%d) than in packet (%d)\n", cur_pos + bytes_clear_data + bytes_encrypted_data , data_size )); + e = GF_NON_COMPLIANT_BITSTREAM; + goto exit; + } + /*skip clear data*/ + cur_pos += bytes_clear_data; + + //pattern decryption + if (cbc_pattern && cbc_pattern->value.frac.den && cbc_pattern->value.frac.num) { + u32 pos = cur_pos; + u32 res = bytes_encrypted_data; + u32 skip_byte_block = cbc_pattern->value.frac.num; + u32 crypt_byte_block = cbc_pattern->value.frac.den; + + if (cstr->is_cbc) { + u32 clear_trailing = res % 16; + res -= clear_trailing; + } + + while (res) { + gf_crypt_decrypt(cstr->crypt, out_data + pos, res >= (u32) (16* crypt_byte_block) ? 16* crypt_byte_block : res); + if (res >= (u32) (16 * (crypt_byte_block + skip_byte_block))) { + pos += 16 * (crypt_byte_block + skip_byte_block); + res -= 16 * (crypt_byte_block + skip_byte_block); + } else { + res = 0; + } + } + } + //full subsample decryption + else { + gf_crypt_decrypt(cstr->crypt, out_data+cur_pos, bytes_encrypted_data); + } + cur_pos += bytes_encrypted_data; + } + } + //full sample encryption + else { + if (cstr->is_cenc) { + gf_crypt_decrypt(cstr->crypt, out_data, data_size); + } else { + u32 ret = data_size % 16; + if (data_size >= 16) { + gf_crypt_decrypt(cstr->crypt, out_data, data_size-ret); + } + } + } + + gf_filter_pck_merge_properties(in_pck, out_pck); + gf_filter_pck_set_property(out_pck, GF_PROP_PCK_CENC_SAI, NULL); + gf_filter_pck_set_crypt_flags(out_pck, 0); + + gf_filter_pck_send(out_pck); + +exit: + if (e && out_pck) { + gf_filter_pck_discard(out_pck); + } + return e; +} + + +static GF_Err cenc_dec_process_adobe(GF_CENCDecCtx *ctx, GF_CENCDecStream *cstr, GF_FilterPacket *in_pck) +{ + u32 data_size; + const u8 *in_data; + u8 *out_data; + GF_FilterPacket *out_pck; + GF_Err e; + u32 offset, size; + Bool encrypted_au; + + if (!cstr->crypt) + return GF_SERVICE_ERROR; + + in_data = gf_filter_pck_get_data(in_pck, &data_size); + out_pck = gf_filter_pck_new_alloc(cstr->opid, data_size, &out_data); + + memcpy(out_data, in_data, data_size); + + offset=0; + size = data_size; + encrypted_au = out_data[0] ? GF_TRUE : GF_FALSE; + if (encrypted_au) { + u32 trim_bytes; + char IV[17]; + if (size<17) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[ADOBE] Error in sample size, %d bytes remain but at least 17 are required\n", size ) ); + return GF_NON_COMPLIANT_BITSTREAM; + } + memmove(IV, out_data+1, 16); + if (!cstr->crypt_init) { + e = gf_crypt_init(cstr->crypt, cstr->keys[0], IV); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[ADOBE] Cannot initialize AES-128 CBC (%s)\n", gf_error_to_string(e)) ); + return GF_IO_ERR; + } + cstr->crypt_init = GF_TRUE; + } else { + e = gf_crypt_set_IV(cstr->crypt, IV, GF_AES_128_KEYSIZE); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[ADOBE] Cannot set state AES-128 CBC (%s)\n", gf_error_to_string(e)) ); + return GF_IO_ERR; + } + } + offset += 17; + size -= 17; + + gf_crypt_decrypt(cstr->crypt, out_data+offset, size); + trim_bytes = out_data[offset + size - 1]; + size -= trim_bytes; + } else { + offset += 1; + size -= 1; + } + memmove(out_data, out_data+offset, size); + gf_filter_pck_truncate(out_pck, size); + gf_filter_pck_merge_properties(in_pck, out_pck); + + gf_filter_pck_send(out_pck); + return GF_OK; +} + +static void cenc_dec_stream_del(GF_CENCDecStream *cstr) +{ + if (cstr->crypt) gf_crypt_close(cstr->crypt); + if (cstr->KIDs) gf_free(cstr->KIDs); + if (cstr->keys) gf_free(cstr->keys); + + + gf_free(cstr); +} + +static GF_Err cenc_dec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *prop; + GF_Err e = GF_OK; + u32 scheme_type = 0; + u32 scheme_version = 0; + const char *scheme_uri = NULL; + const char *kms_uri = NULL; + GF_CENCDecStream *cstr; + GF_CENCDecCtx *ctx = (GF_CENCDecCtx *)gf_filter_get_udta(filter); + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_PROTECTION_SCHEME_TYPE); + if (prop) scheme_type = prop->value.uint; + + if (is_remove) { + cstr = gf_filter_pid_get_udta(pid); + if (cstr->opid) { + gf_filter_pid_remove(cstr->opid); + } + gf_list_del_item(ctx->streams, cstr); + cenc_dec_stream_del(cstr); + return GF_OK; + } + + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + cstr = gf_filter_pid_get_udta(pid); + if (!cstr) { + GF_SAFEALLOC(cstr, GF_CENCDecStream); + if (!cstr) return GF_OUT_OF_MEM; + cstr->opid = gf_filter_pid_new(filter); + cstr->ipid = pid; + gf_list_add(ctx->streams, cstr); + gf_filter_pid_set_udta(pid, cstr); + gf_filter_pid_set_udta(cstr->opid, cstr); + } + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_PROTECTION_SCHEME_VERSION); + if (prop) scheme_version = prop->value.uint; + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_PROTECTION_SCHEME_URI); + if (prop) scheme_uri = prop->value.string; + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_PROTECTION_KMS_URI); + if (prop) kms_uri = prop->value.string; + + if (ctx->cinfo) { + u32 stream_id=0; + u32 i, count = gf_list_count(ctx->cinfo->tcis); + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + if (!prop) prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ESID); + if (prop) stream_id = prop->value.uint; + + for (i=0; icinfo->tcis, i); + if ((ctx->cinfo->has_common_key && !tci->trackID) || (tci->trackID == stream_id) ) { + if (tci->force_type) scheme_type = tci->scheme_type; + break; + } + } + + } + + switch (scheme_type) { + case GF_ISOM_ISMACRYP_SCHEME: + e = cenc_dec_setup_isma(ctx, cstr, scheme_type, scheme_version, scheme_uri, kms_uri); + break; + case GF_ISOM_OMADRM_SCHEME: +#ifdef OLD_KEY_FETCHERS + e = cenc_dec_setup_oma(ctx, cstr, scheme_type, scheme_version, scheme_uri, kms_uri); +#else + e = GF_NOT_SUPPORTED; +#endif + break; + case GF_ISOM_CENC_SCHEME: + case GF_ISOM_PIFF_SCHEME: + case GF_ISOM_CBC_SCHEME: + case GF_ISOM_CENS_SCHEME: + case GF_ISOM_CBCS_SCHEME: + e = cenc_dec_setup_cenc(ctx, cstr, scheme_type, scheme_version, scheme_uri, kms_uri); + break; + case GF_ISOM_ADOBE_SCHEME: + e = cenc_dec_setup_adobe(ctx, cstr, scheme_type, scheme_version, scheme_uri, kms_uri); + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[CENC/ISMA] Protection scheme type %s not supported\n", gf_4cc_to_str(scheme_type) ) ); + return GF_SERVICE_ERROR; + } + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[CENC/ISMA] Error setting up protection scheme type %s\n", gf_4cc_to_str(scheme_type) ) ); + return e; + } + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(cstr->opid, pid); + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ORIG_STREAM_TYPE); + if (prop) { + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT(prop->value.uint) ); + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_ORIG_STREAM_TYPE, NULL); + } + //remove all cenc properties on output + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_PROTECTION_SCHEME_TYPE, NULL); + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_PROTECTION_SCHEME_VERSION, NULL); + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_PROTECTION_SCHEME_URI, NULL); + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_PROTECTION_KMS_URI, NULL); + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_OMA_PREVIEW_RANGE, NULL); + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_CENC_PSSH, NULL); + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_ENCRYPTED, NULL); + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_CENC_IV_SIZE, NULL); + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_CENC_IV_CONST, NULL); + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_CENC_PATTERN, NULL); + + cstr->is_nalu = GF_FALSE;; + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (prop) { + switch (prop->value.uint) { + case GF_CODECID_AVC: + case GF_CODECID_SVC: + case GF_CODECID_MVC: + case GF_CODECID_HEVC: + case GF_CODECID_HEVC_TILES: + case GF_CODECID_LHVC: + cstr->is_nalu = GF_TRUE;; + break; + } + } + return e; +} + + +static Bool cenc_dec_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_FilterPid *ipid; + GF_CENCDecStream *cstr; + Bool is_play = GF_FALSE; + GF_CENCDecCtx *ctx = (GF_CENCDecCtx *)gf_filter_get_udta(filter); + + ipid = evt->base.on_pid; + if (!ipid) return GF_FALSE; + cstr = gf_filter_pid_get_udta(ipid); + if (!cstr) return GF_FALSE; + + switch (evt->base.type) { + case GF_FEVT_PLAY: + is_play = GF_TRUE; + case GF_FEVT_STOP: + if (cstr->is_cenc || cstr->is_cbc) { + cenc_dec_access_cenc(ctx, cstr, is_play); + } else if (cstr->is_adobe) { + cenc_dec_access_adobe(ctx, cstr, is_play); + } else if (cstr->is_oma) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[CENC/ISMA] OMA not supported, canceling filter event\n") ); + return GF_TRUE; + } else { + cenc_dec_access_isma(ctx, cstr, is_play); + } + break; + default: + break; + } + return GF_FALSE; +} +static GF_Err cenc_dec_process(GF_Filter *filter) +{ + GF_CENCDecCtx *ctx = (GF_CENCDecCtx *)gf_filter_get_udta(filter); + u32 i, nb_eos, count = gf_list_count(ctx->streams); + + nb_eos = 0; + for (i=0; istreams, i); + GF_FilterPacket *pck = gf_filter_pid_get_packet(cstr->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(cstr->ipid)) { + nb_eos++; + gf_filter_pid_set_eos(cstr->opid); + } + continue; + } + + if (cstr->is_cenc || cstr->is_cbc) { + e = cenc_dec_process_cenc(ctx, cstr, pck); + } else if (cstr->is_oma) { + e = GF_NOT_SUPPORTED; + } else if (cstr->is_adobe) { + e = cenc_dec_process_adobe(ctx, cstr, pck); + } else { + e = cenc_dec_process_isma(ctx, cstr, pck); + } + gf_filter_pid_drop_packet(cstr->ipid); + if (e) return e; + } + if (nb_eos && (nb_eos==count)) return GF_EOS; + return GF_OK; +} + +static GF_Err cenc_dec_initialize(GF_Filter *filter) +{ + GF_CENCDecCtx *ctx = (GF_CENCDecCtx *)gf_filter_get_udta(filter); + ctx->streams = gf_list_new(); + if (!ctx->streams) return GF_OUT_OF_MEM; + + if (ctx->cfile) { + GF_Err e; + ctx->cinfo = gf_crypt_info_load(ctx->cfile, &e); + if (!ctx->cinfo) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENCCrypt] Cannot load config file %s\n", ctx->cfile )); + return e; + } + } + ctx->bs_r = gf_bs_new((char *) ctx, 1, GF_BITSTREAM_READ); + return GF_OK; +} + +static void cenc_dec_finalize(GF_Filter *filter) +{ + GF_CENCDecCtx *ctx = (GF_CENCDecCtx *)gf_filter_get_udta(filter); + + while (gf_list_count(ctx->streams)) { + GF_CENCDecStream *cstr = gf_list_pop_back(ctx->streams); + cenc_dec_stream_del(cstr); + } + gf_list_del(ctx->streams); + + if (ctx->bs_r) gf_bs_del(ctx->bs_r); + if (ctx->cinfo) gf_crypt_info_del(ctx->cinfo); +} + + +static const GF_FilterCapability CENCDecCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_ENCRYPTED), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_PROTECTION_SCHEME_TYPE, GF_ISOM_ISMACRYP_SCHEME), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_PROTECTION_SCHEME_TYPE, GF_ISOM_OMADRM_SCHEME), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_PROTECTION_SCHEME_TYPE, GF_ISOM_CENC_SCHEME), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_PROTECTION_SCHEME_TYPE, GF_ISOM_CENS_SCHEME), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_PROTECTION_SCHEME_TYPE, GF_ISOM_CBC_SCHEME), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_PROTECTION_SCHEME_TYPE, GF_ISOM_CBCS_SCHEME), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_PROTECTION_SCHEME_TYPE, GF_ISOM_ADOBE_SCHEME), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_PROTECTION_SCHEME_TYPE, GF_ISOM_PIFF_SCHEME), + + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_ENCRYPTED), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + //CAP_UINT(GF_CAPS_OUTPUT_STATIC_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_UNFRAMED, GF_FALSE), +}; + + +#define OFFS(_n) #_n, offsetof(GF_CENCDecCtx, _n) +static const GF_FilterArgs GF_CENCDecArgs[] = +{ + { OFFS(cfile), "crypt file location - see filter help", GF_PROP_STRING, NULL, NULL, 0}, + {0} +}; + +GF_FilterRegister CENCDecRegister = { + .name = "cdcrypt", + GF_FS_SET_DESCRIPTION("CENC decryptor") + GF_FS_SET_HELP("The CENC decryptor supports decrypting CENC, ISMA and Adobe streams. It uses a configuration file for retrieving keys.\n" + "The syntax is available at https://wiki.gpac.io/Common-Encryption\n" + "The file can be set per PID using the property `DecryptInfo` (highest priority), `CryptInfo` (lower priority) " + "or set at the filter level using [-cfile]() (lowest priority).\n" + "When the file is set per PID, the first `CryptInfo` with the same ID is used, otherwise the first `CryptInfo` is used.") + .private_size = sizeof(GF_CENCDecCtx), + .max_extra_pids=-1, + .args = GF_CENCDecArgs, + SETCAPS(CENCDecCaps), + .configure_pid = cenc_dec_configure_pid, + .initialize = cenc_dec_initialize, + .finalize = cenc_dec_finalize, + .process = cenc_dec_process, + .process_event = cenc_dec_process_event + //for now only one PID per CENC decryptor instance, could be further optimized +}; + +#endif /*GPAC_DISABLE_CRYPTO*/ + +const GF_FilterRegister *cenc_decrypt_register(GF_FilterSession *session) +{ +#ifndef GPAC_DISABLE_CRYPTO + return &CENCDecRegister; +#else + return NULL; +#endif +} diff --git a/src/filters/dmx_avi.c b/src/filters/dmx_avi.c new file mode 100644 index 0000000..c1cc5a8 --- /dev/null +++ b/src/filters/dmx_avi.c @@ -0,0 +1,647 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2019 + * All rights reserved + * + * This file is part of GPAC / AVI demuxer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_AVILIB +#include +#include + +typedef struct +{ + GF_FilterPid *opid; + u32 stream_num; + Bool in_use; + u32 aud_frame, audio_bps, nb_channels, freq; + u64 audio_ts, seek_to_ts; + Bool audio_done, is_aac, playing, init_play_done; +} AVIAstream; + + +typedef struct +{ + //opts + GF_Fraction fps; + Bool importer; + + GF_FilterPid *ipid; + + GF_FilterPid *v_opid; + Bool v_in_use; + u32 nb_frames, cur_frame, nb_frame_sent; + u32 dummy, nvops; + const char *src_url; + avi_t *avi; + + Bool use_file_fps; + GF_Fraction64 duration; + Bool v_playing; + Bool v_init_play_done; + + GF_List *audios; +} GF_AVIDmxCtx; + + +static void avidmx_setup(GF_Filter *filter, GF_AVIDmxCtx *ctx) +{ + u32 sync_id = 0; + u32 codecid = 0; + Bool unframed; + u32 i, count, pfmt=0; + GF_Fraction64 dur; + char *comp; + + if (ctx->use_file_fps) { + Double fps = AVI_frame_rate(ctx->avi); + gf_media_get_video_timing(fps, &ctx->fps.num, &ctx->fps.den); + } + + dur.den = ctx->fps.num; + dur.num = (u64) (ctx->fps.den * AVI_video_frames(ctx->avi)); + + unframed = GF_TRUE; + comp = AVI_video_compressor(ctx->avi); + if (!comp) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[AVIDmx] Cannot retrieve video compressor name, ignoring video stream\n")); + } + /*these are/should be OK*/ + else if (!stricmp(comp, "DIVX") || !stricmp(comp, "DX50") /*DivX*/ + || !stricmp(comp, "XVID") /*XviD*/ + || !stricmp(comp, "3iv2") /*3ivX*/ + || !stricmp(comp, "fvfw") /*ffmpeg*/ + || !stricmp(comp, "NDIG") /*nero*/ + || !stricmp(comp, "MP4V") /*!! not tested*/ + || !stricmp(comp, "M4CC") /*Divio - not tested*/ + || !stricmp(comp, "PVMM") /*PacketVideo - not tested*/ + || !stricmp(comp, "SEDG") /*Samsung - not tested*/ + || !stricmp(comp, "RMP4") /*Sigma - not tested*/ + || !stricmp(comp, "MP43") /*not tested*/ + || !stricmp(comp, "FMP4") /*not tested*/ + ) { + codecid = GF_CODECID_MPEG4_PART2; + } else if ( !stricmp(comp, "H264") /*not tested*/ + || !stricmp(comp, "X264") /*not tested*/ + ) { + codecid = GF_CODECID_AVC; + } else if ( !stricmp(comp, "avc1") ) { + codecid = GF_CODECID_AVC; + unframed = GF_FALSE; + } else if (!stricmp(comp, "DIV3") || !stricmp(comp, "DIV4")) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[AVIDmx] Video format %s not compliant with MPEG-4 Visual - please recompress the file first\n", comp)); + } else if (!comp[0]) { + codecid = GF_CODECID_RAW; + pfmt = GF_PIXEL_BGR; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[AVIDmx] Video format %s not supported, patch welcome\n", comp)); + } + + ctx->v_in_use = GF_FALSE; + if (codecid) { + u32 w, h; + if (!ctx->v_opid) { + ctx->v_opid = gf_filter_pid_new(filter); + } + ctx->nb_frames = (u32) AVI_video_frames(ctx->avi); + ctx->cur_frame = 0; + sync_id = 1; + ctx->v_in_use = GF_TRUE; + + gf_filter_pid_set_property(ctx->v_opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_VISUAL) ); + gf_filter_pid_set_property(ctx->v_opid, GF_PROP_PID_CODECID, &PROP_UINT(codecid) ); + gf_filter_pid_set_property(ctx->v_opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->fps.num) ); + + gf_filter_pid_set_property(ctx->v_opid, GF_PROP_PID_ID, &PROP_UINT( sync_id) ); + gf_filter_pid_set_property(ctx->v_opid, GF_PROP_PID_CLOCK_ID, &PROP_UINT( sync_id ) ); + gf_filter_pid_set_property(ctx->v_opid, GF_PROP_PID_FPS, &PROP_FRAC( ctx->fps ) ); + w = AVI_video_width(ctx->avi); + h = AVI_video_height(ctx->avi); + gf_filter_pid_set_property(ctx->v_opid, GF_PROP_PID_WIDTH, &PROP_UINT( w ) ); + gf_filter_pid_set_property(ctx->v_opid, GF_PROP_PID_HEIGHT, &PROP_UINT( h ) ); + gf_filter_pid_set_property(ctx->v_opid, GF_PROP_PID_DURATION, &PROP_FRAC64( dur ) ); + + gf_filter_pid_set_property(ctx->v_opid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_SEEK ) ); + + if (pfmt) { + u32 stride=0; + gf_pixel_get_size_info(pfmt, w, h, NULL, &stride, NULL, NULL, NULL); + gf_filter_pid_set_property(ctx->v_opid, GF_PROP_PID_STRIDE, &PROP_UINT( stride ) ); + gf_filter_pid_set_property(ctx->v_opid, GF_PROP_PID_PIXFMT, &PROP_UINT( pfmt ) ); + } else if (unframed) { + gf_filter_pid_set_property(ctx->v_opid, GF_PROP_PID_UNFRAMED, &PROP_BOOL(GF_TRUE) ); + gf_filter_pid_set_property_str(ctx->v_opid, "nocts", &PROP_BOOL( GF_TRUE ) ); + } else if (ctx->avi->extradata_size && ctx->avi->extradata) { + gf_filter_pid_set_property(ctx->v_opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA(ctx->avi->extradata, ctx->avi->extradata_size) ); + + } + } + + unframed = GF_FALSE; + count = AVI_audio_tracks(ctx->avi); + for (i=0; iavi, i); + + codecid = 0; + a_fmt = AVI_audio_format(ctx->avi); + nb_bits = AVI_audio_bits(ctx->avi); + switch (a_fmt) { + case WAVE_FORMAT_PCM: + case GF_4CC('P','C','M',' '): + codecid = GF_CODECID_RAW; + switch (nb_bits) { + case 8: + afmt = GF_AUDIO_FMT_U8; + break; + case 16: + afmt = GF_AUDIO_FMT_S16; + break; + case 24: + afmt = GF_AUDIO_FMT_S24; + break; + case 32: + afmt = GF_AUDIO_FMT_S32; + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[AVIDmx] Audio bit depth %d not mapped, patch welcome\n", nb_bits)); + afmt = GF_AUDIO_FMT_S16; + break; + } + break; + case WAVE_FORMAT_ADPCM: + codecid = GF_CODECID_ADPCM; + break; + case WAVE_FORMAT_IBM_CVSD: + codecid = GF_CODECID_IBM_CVSD; + break; + case WAVE_FORMAT_ALAW: + codecid = GF_CODECID_ALAW; + break; + case WAVE_FORMAT_MULAW: + codecid = GF_CODECID_MULAW; + break; + case WAVE_FORMAT_OKI_ADPCM: + codecid = GF_CODECID_OKI_ADPCM; + break; + case WAVE_FORMAT_DVI_ADPCM: + codecid = GF_CODECID_DVI_ADPCM; + break; + case WAVE_FORMAT_DIGISTD: + codecid = GF_CODECID_DIGISTD; + break; + case WAVE_FORMAT_YAMAHA_ADPCM: + codecid = GF_CODECID_YAMAHA_ADPCM; + break; + case WAVE_FORMAT_DSP_TRUESPEECH: + codecid = GF_CODECID_DSP_TRUESPEECH; + break; + case WAVE_FORMAT_GSM610: + codecid = GF_CODECID_GSM610; + break; + case IBM_FORMAT_MULAW: + codecid = GF_CODECID_IBM_MULAW; + break; + case IBM_FORMAT_ALAW: + codecid = GF_CODECID_IBM_ALAW; + break; + case IBM_FORMAT_ADPCM: + codecid = GF_CODECID_IBM_ADPCM; + break; + case 0x55: + codecid = GF_CODECID_MPEG_AUDIO; + unframed = GF_TRUE; + break; + case 0x0000706d: + codecid = GF_CODECID_AAC_MPEG4; + unframed = GF_TRUE; + break; + case 255: + codecid = GF_CODECID_AAC_MPEG4; + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[AVIDmx] Audio format %d not supported, patch welcome\n", a_fmt)); + break; + } + + if (codecid) { + AVIAstream *st = NULL; + u32 brate, j, c = gf_list_count(ctx->audios); + for (j=0; jaudios, j); + if (!st->in_use) break; + st = NULL; + } + if (!st) { + GF_SAFEALLOC(st, AVIAstream); + if (!st) continue; + st->opid = gf_filter_pid_new(filter); + gf_list_add(ctx->audios, st); + } + st->in_use = GF_TRUE; + st->stream_num = i; + if (!sync_id) sync_id = 2 + st->stream_num; + st->audio_done = GF_FALSE; + + gf_filter_pid_set_property(st->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_AUDIO) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_CODECID, &PROP_UINT( codecid) ); + st->freq = AVI_audio_rate(ctx->avi); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT( st->freq ) ); + st->nb_channels = AVI_audio_channels(ctx->avi); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT( st->nb_channels ) ); + brate = AVI_audio_mp3rate(ctx->avi); + //for mp3 and aac + if (brate && (unframed || (codecid == GF_CODECID_AAC_MPEG4))) + gf_filter_pid_set_property(st->opid, GF_PROP_PID_BITRATE, &PROP_UINT( brate ) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_ID, &PROP_UINT( 2 + st->stream_num) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_CLOCK_ID, &PROP_UINT( sync_id ) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_DURATION, &PROP_FRAC64( dur ) ); + + gf_filter_pid_set_property(st->opid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_SEEK ) ); + st->audio_bps = 0; + if (unframed) { + gf_filter_pid_set_property(st->opid, GF_PROP_PID_UNFRAMED, &PROP_BOOL( GF_TRUE ) ); + //we don't set timescale, let the reframer handle it + } else { + if (afmt) { + gf_filter_pid_set_property(st->opid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(afmt) ); + } + st->audio_bps = AVI_audio_bits(ctx->avi); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(st->freq) ); + + if (codecid == GF_CODECID_AAC_MPEG4) { +#ifndef GPAC_DISABLE_AV_PARSERS + GF_M4ADecSpecInfo acfg; + u8 *dsi=NULL; + u32 dsi_len=0; + memset(&acfg, 0, sizeof(GF_M4ADecSpecInfo)); + acfg.base_object_type = GF_M4A_AAC_LC; + acfg.base_sr = st->freq; + acfg.nb_chan = st->nb_channels; + acfg.sbr_object_type = 0; + acfg.audioPL = gf_m4a_get_profile(&acfg); + gf_m4a_write_config(&acfg, &dsi, &dsi_len); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_len) ); +#endif + st->audio_bps = 0; + st->is_aac = GF_TRUE; + } + } + + } + } +} + +GF_Err avidmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_AVIDmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + if (ctx->v_opid) gf_filter_pid_remove(ctx->v_opid); + ctx->v_opid = NULL; + while (gf_list_count(ctx->audios) ) { + AVIAstream *st = gf_list_pop_back(ctx->audios); + gf_filter_pid_remove(st->opid); + gf_free(st); + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + if (!ctx->ipid) { + GF_FilterEvent fevt; + ctx->ipid = pid; + + //we work with full file only, send a play event on source to indicate that + GF_FEVT_INIT(fevt, GF_FEVT_PLAY, pid); + fevt.play.start_range = 0; + fevt.base.on_pid = ctx->ipid; + fevt.play.full_file_only = GF_TRUE; + gf_filter_pid_send_event(ctx->ipid, &fevt); + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p) return GF_NOT_SUPPORTED; + + if (ctx->src_url && !strcmp(ctx->src_url, p->value.string)) return GF_OK; + + if (ctx->avi) { + u32 i; + AVI_close(ctx->avi); + ctx->v_in_use = GF_FALSE; + for (i=0; iaudios); i++) { + AVIAstream *st = gf_list_get(ctx->audios, i); + st->in_use = GF_FALSE; + } + } + ctx->avi = NULL; + + ctx->src_url = p->value.string; + + return GF_OK; +} + +static Bool avidmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_AVIDmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + gf_filter_post_process_task(filter); + if (evt->base.on_pid==ctx->v_opid) { + u32 frame_idx = 0; + ctx->v_playing = GF_TRUE; + if ((evt->play.start_range==0) && !ctx->v_init_play_done) { + ctx->v_init_play_done = GF_TRUE; + return GF_TRUE; + } + ctx->v_init_play_done = GF_TRUE; + frame_idx = (u32) (ctx->avi->fps * evt->play.start_range); + if (frame_idx) { + AVI_set_video_position(ctx->avi, frame_idx); + } else { + AVI_seek_start(ctx->avi); + gf_filter_post_process_task(filter); + } + ctx->cur_frame = frame_idx; + } else { + u32 i; + for (i=0; iaudios); i++) { + AVIAstream *st = gf_list_get(ctx->audios, i); + if (st->opid != evt->base.on_pid) continue; + st->playing = GF_TRUE; + if ((evt->play.start_range==0) && !st->init_play_done) { + st->init_play_done = GF_TRUE; + return GF_TRUE; + } + st->init_play_done = GF_TRUE; + AVI_set_audio_position(ctx->avi, 0); + st->seek_to_ts = (u32) (evt->play.start_range * st->freq); + } + } + + //cancel play event, we work with full file + return GF_TRUE; + + case GF_FEVT_STOP: + if (evt->base.on_pid==ctx->v_opid) { + ctx->v_playing = GF_FALSE; + } else { + u32 i; + for (i=0; iaudios); i++) { + AVIAstream *st = gf_list_get(ctx->audios, i); + if (st->opid != evt->base.on_pid) continue; + st->playing = GF_TRUE; + } + } + //don't cancel event + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +GF_Err avidmx_process(GF_Filter *filter) +{ + GF_AVIDmxCtx *ctx = gf_filter_get_udta(filter); + u32 i, count, nb_done, nb_active=0; + Bool start, end, video_done; + + if (!ctx->avi) { + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + return GF_OK; + } + gf_filter_pck_get_framing(pck, &start, &end); + gf_filter_pid_drop_packet(ctx->ipid); + + if (!end) { + return GF_OK; + } + + ctx->avi = AVI_open_input_file((char *)ctx->src_url, 1); + if (!ctx->avi) { + GF_Err e = GF_NON_COMPLIANT_BITSTREAM; + if (! gf_file_exists(ctx->src_url)) e = GF_URL_ERROR; + gf_filter_setup_failure(filter, e); + return GF_NOT_SUPPORTED; + } + avidmx_setup(filter, ctx); + return GF_OK; + } + video_done = GF_FALSE; + if (ctx->v_in_use && ctx->v_playing && (ctx->cur_frame < ctx->nb_frames) && !gf_filter_pid_would_block(ctx->v_opid) ) { + u32 key; + u64 file_offset, cts; + u8 *pck_data; + s32 size = AVI_frame_size(ctx->avi, ctx->cur_frame); + if (!size) { + AVI_read_frame(ctx->avi, NULL, &key); + ctx->dummy++; + } + //remove dummy frames + else { + file_offset = (u64) AVI_get_video_position(ctx->avi, ctx->cur_frame); + cts = ctx->nb_frame_sent * ctx->fps.den; + + if (size > 4) { + GF_FilterPacket *dst_pck = gf_filter_pck_new_alloc(ctx->v_opid, size, &pck_data); + AVI_read_frame(ctx->avi, pck_data, &key); + gf_filter_pck_set_byte_offset(dst_pck, file_offset); + gf_filter_pck_set_cts(dst_pck, cts); + gf_filter_pck_set_duration(dst_pck, ctx->fps.den); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + if (key) gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + gf_filter_pck_send(dst_pck); + ctx->nb_frame_sent++; + } else { + AVI_read_frame(ctx->avi, NULL, &key); + ctx->nvops++; + } + } + nb_active++; + + ctx->cur_frame++; + if (ctx->cur_frame < ctx->nb_frames) + gf_filter_post_process_task(filter); + else + video_done = GF_TRUE; + } else if (!ctx->v_in_use) { + video_done = GF_TRUE; + } else if (ctx->cur_frame >= ctx->nb_frames) { + video_done = GF_TRUE; + } + + nb_done = 0; + count = gf_list_count(ctx->audios); + for (i=0; iaudios, i); + if (st->audio_done || !st->in_use) { + nb_done++; + continue; + } + if (!st->playing || gf_filter_pid_would_block(st->opid) ) + continue; + AVI_set_audio_track(ctx->avi, st->stream_num); + nb_active++; + + size = AVI_audio_size(ctx->avi, st->aud_frame); + + if (st->seek_to_ts && size) { + if (st->seek_to_ts > st->audio_ts) { + st->aud_frame ++; + continue; + } + else + st->seek_to_ts = 0; + } + + if (size>0) { + int continuous; + u8 *pck_data; + u64 file_offset; + GF_FilterPacket *dst_pck = gf_filter_pck_new_alloc(st->opid, size, &pck_data); + + file_offset = gf_ftell(ctx->avi->fdes); + AVI_read_audio(ctx->avi, pck_data, size, (int*)&continuous); + + if (st->audio_bps) { + u32 nb_samples = (8*size) / (st->audio_bps * st->nb_channels); + gf_filter_pck_set_cts(dst_pck, st->audio_ts); + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + st->audio_ts += nb_samples; + } else if (st->is_aac) { + gf_filter_pck_set_cts(dst_pck, st->audio_ts); + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + st->audio_ts += 1024; + } + + if (continuous) + gf_filter_pck_set_byte_offset(dst_pck, file_offset); + + gf_filter_pck_send(dst_pck); + + st->aud_frame ++; + } else { + st->audio_done = GF_TRUE; + nb_done++; + } + } + if (!nb_active) + return GF_OK; + if (video_done && (nb_done==count) ) { + if (ctx->v_opid && ctx->v_in_use) gf_filter_pid_set_eos(ctx->v_opid); + + for (i=0; iaudios, i); + gf_filter_pid_set_eos(st->opid); + } + return GF_EOS; + } + gf_filter_post_process_task(filter); + return GF_OK; +} + +GF_Err avidmx_initialize(GF_Filter *filter) +{ + GF_AVIDmxCtx *ctx = gf_filter_get_udta(filter); + ctx->use_file_fps = ctx->fps.den ? GF_FALSE : GF_TRUE; + ctx->audios = gf_list_new(); + return GF_OK; +} + +void avidmx_finalize(GF_Filter *filter) +{ + GF_AVIDmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->avi) AVI_close(ctx->avi); + while (gf_list_count(ctx->audios)) { + AVIAstream *st = gf_list_pop_back(ctx->audios); + gf_free(st); + } + gf_list_del(ctx->audios); + + if (ctx->importer) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("AVI Removed Frames: 1 VFW delay frames - 296 N-VOPs\n", ctx->dummy, ctx->nvops)); + } + +} + + +static const GF_FilterCapability AVIDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "avi"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "video/avi|video/x-avi"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), +}; + + +#define OFFS(_n) #_n, offsetof(GF_AVIDmxCtx, _n) +static const GF_FilterArgs AVIDmxArgs[] = +{ + { OFFS(fps), "import frame rate, default is AVI one", GF_PROP_FRACTION, "1/0", NULL, 0}, + { OFFS(importer), "compatibility with old importer, displays import results", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + + +GF_FilterRegister AVIDmxRegister = { + .name = "avidmx", + GF_FS_SET_DESCRIPTION("AVI demuxer") + GF_FS_SET_HELP("This filter demultiplexes AVI files/data to produce media PIDs and frames.") + .private_size = sizeof(GF_AVIDmxCtx), + .initialize = avidmx_initialize, + .finalize = avidmx_finalize, + .args = AVIDmxArgs, + SETCAPS(AVIDmxCaps), + .configure_pid = avidmx_configure_pid, + .process = avidmx_process, + .process_event = avidmx_process_event, +}; + +#endif // GPAC_DISABLE_AVILIB + +const GF_FilterRegister *avidmx_register(GF_FilterSession *session) +{ +#ifndef GPAC_DISABLE_AVILIB + return &AVIDmxRegister; +#else + return NULL; +#endif +} + diff --git a/src/filters/dmx_dash.c b/src/filters/dmx_dash.c new file mode 100644 index 0000000..9e86486 --- /dev/null +++ b/src/filters/dmx_dash.c @@ -0,0 +1,1851 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / DASH/HLS demux filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include + +#ifndef GPAC_DISABLE_DASH_CLIENT + +#include + +typedef struct +{ + //opts + s32 shift_utc, debug_as, atsc_shift; + u32 max_buffer, auto_switch, init_timeshift, tiles_rate, segstore, delay40X, exp_threshold, switch_count; + Bool server_utc, screen_res, aggressive, speedadapt; + GF_DASHInitialSelectionMode start_with; + GF_DASHTileAdaptationMode tile_mode; + GF_DASHAdaptationAlgorithm algo; + Bool max_res, immediate, abort, use_bmin; + char *query; + Bool noxlink, split_as, noseek; + u32 lowlat; + + GF_FilterPid *mpd_pid; + GF_Filter *filter; + + GF_DashClient *dash; + //http io for manifest + GF_DASHFileIO dash_io; + GF_DownloadManager *dm; + + Bool closed; + Bool reuse_download_session; + + Bool initial_setup_done; + u32 nb_playing; + + /*max width & height in all active representations*/ + u32 width, height; + + Double seek_request; + Double media_start_range; + + Bool mpd_open; + Bool initial_play; + Bool check_eos; +} GF_DASHDmxCtx; + +typedef struct +{ + GF_DASHDmxCtx *ctx; + GF_Filter *seg_filter_src; + + u32 idx; + Bool init_switch_seg_sent; + Bool segment_sent; + + u32 nb_eos, nb_pids; + Bool stats_uploaded; + Bool wait_for_pck; + Bool eos_detected; + + GF_DownloadSession *sess; + Bool is_timestamp_based, pto_setup; + Bool prev_is_init_segment; + u32 timescale; + s64 pto; + s64 max_cts_in_period; + bin128 key_IV; + + Bool seg_was_not_ready; + Bool in_error; + Bool is_playing; + Bool force_seg_switch; + u32 nb_group_deps, current_group_dep; +} GF_DASHGroup; + + +void dashdmx_forward_packet(GF_DASHDmxCtx *ctx, GF_FilterPacket *in_pck, GF_FilterPid *in_pid, GF_FilterPid *out_pid, GF_DASHGroup *group) +{ + GF_FilterPacket *dst_pck; + Bool do_map_time = GF_FALSE; + Bool seek_flag = 0; + u64 cts, dts; + + if (gf_dash_is_m3u8(ctx->dash)) { + gf_filter_pck_forward(in_pck, out_pid); + + if (!group->pto_setup) { + cts = gf_filter_pck_get_cts(in_pck); + gf_filter_pid_set_property_str(out_pid, "time:timestamp", &PROP_LONGUINT(cts) ); + gf_filter_pid_set_property_str(out_pid, "time:media", &PROP_DOUBLE(ctx->media_start_range) ); + group->pto_setup = GF_TRUE; + } + + gf_filter_pid_drop_packet(in_pid); + return; + } + + //filter any packet outside the current period + dts = gf_filter_pck_get_dts(in_pck); + cts = gf_filter_pck_get_cts(in_pck); + seek_flag = gf_filter_pck_get_seek_flag(in_pck); + + //if sync is based on timestamps do not adjust the timestamps back + if (! group->is_timestamp_based) { + if (!group->pto_setup) { + Double scale; + s64 start, dur; + u64 pto; + u32 ts = gf_filter_pck_get_timescale(in_pck); + gf_dash_group_get_presentation_time_offset(ctx->dash, group->idx, &pto, &group->timescale); + group->pto = (s64) pto; + group->pto_setup = 1; + + if (group->timescale && (group->timescale != ts)) { + group->pto *= ts; + group->pto /= group->timescale; + } + scale = ts; + scale /= 1000; + + dur = (u64) (scale * gf_dash_get_period_duration(ctx->dash)); + if (dur) { + group->max_cts_in_period = group->pto + dur; + } else { + group->max_cts_in_period = 0; + } + + start = (u64) (scale * gf_dash_get_period_start(ctx->dash)); + group->pto -= start; + } + + if (group->max_cts_in_period && (s64) cts > group->max_cts_in_period) { + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASHDmx] Packet timestamp "LLU" larger than max CTS in period "LLU" - forcing seek flag\n", cts, group->max_cts_in_period)); + + seek_flag = 1; + } + + //remap timestamps to our timeline + if (dts != GF_FILTER_NO_TS) { + if ((s64) dts >= group->pto) + dts -= group->pto; + else { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASHDmx] Packet DTS "LLU" less than PTO "LLU" - forcing DTS to 0\n", dts, group->pto)); + dts = 0; + seek_flag = 1; + } + } + if (cts!=GF_FILTER_NO_TS) { + if ((s64) cts >= group->pto) + cts -= group->pto; + else { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASHDmx] Packet CTS "LLU" less than PTO "LLU" - forcing CTS to 0\n", cts, group->pto)); + cts = 0; + seek_flag = 1; + } + } + } else if (!group->pto_setup) { + do_map_time = 1; + group->pto_setup = 1; + } + + dst_pck = gf_filter_pck_new_ref(out_pid, NULL, 0, in_pck); + //this will copy over clock info for PCR in TS + gf_filter_pck_merge_properties(in_pck, dst_pck); + gf_filter_pck_set_dts(dst_pck, dts); + gf_filter_pck_set_cts(dst_pck, cts); + gf_filter_pck_set_seek_flag(dst_pck, seek_flag); + gf_filter_pck_send(dst_pck); + gf_filter_pid_drop_packet(in_pid); + + if (do_map_time) { + gf_filter_pid_set_property_str(out_pid, "time:timestamp", &PROP_LONGUINT(cts) ); + gf_filter_pid_set_property_str(out_pid, "time:media", &PROP_DOUBLE(ctx->media_start_range) ); + } +} + + +static void dashdmx_on_filter_setup_error(GF_Filter *failed_filter, void *udta, GF_Err err) +{ + GF_DASHGroup *group = (GF_DASHGroup *)udta; + if (!udta) return; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] group %d download setup error %s\n", group->idx, gf_error_to_string(err) )); + + gf_dash_set_group_download_state(group->ctx->dash, group->idx, err); + if (err) { + Bool group_done=GF_FALSE; + + gf_dash_group_get_num_segments_ready(group->ctx->dash, group->idx, &group_done); + + group->stats_uploaded = GF_TRUE; + group->segment_sent = GF_FALSE; + gf_filter_post_process_task(group->ctx->filter); + if (group_done) { + group->eos_detected = GF_TRUE; + } else { + group->in_error = GF_TRUE; + } + } +} + +/*locates input service (demuxer) based on mime type or segment name*/ +static GF_Err dashdmx_load_source(GF_DASHDmxCtx *ctx, u32 group_index, const char *mime, const char *init_segment_name, u64 start_range, u64 end_range) +{ + GF_DASHGroup *group; + GF_Err e; + u32 url_type=0; + Bool has_sep = GF_FALSE; + char *sURL = NULL; + const char *base_url; + if (!init_segment_name) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASHDmx] group %d Error locating plugin for segment - mime type %s\n", group_index, mime)); + return GF_FILTER_NOT_FOUND; + } + + GF_SAFEALLOC(group, GF_DASHGroup); + if (!group) return GF_OUT_OF_MEM; + group->ctx = ctx; + group->idx = group_index; + gf_dash_set_group_udta(ctx->dash, group_index, group); + + base_url = gf_dash_get_url(ctx->dash); + if (!strnicmp(base_url, "http://", 7)) url_type=1; + else if (!strnicmp(base_url, "https://", 7)) url_type=2; + else url_type=0; + + sURL = gf_malloc(sizeof(char) * (strlen(init_segment_name) + 200) ); + strcpy(sURL, init_segment_name); + if (!strncmp(sURL, "isobmff://", 10)) { + if (url_type==1) + sprintf(sURL, "http://%s", init_segment_name); + else if (url_type==2) + sprintf(sURL, "http://%s", init_segment_name); + else + sprintf(sURL, "file://%s", init_segment_name); + } + //not from file system, set cache option + if (url_type) { + if (!ctx->segstore) { + if (!has_sep) { strcat(sURL, ":gpac"); has_sep = GF_TRUE; } + strcat(sURL, ":cache=mem"); + } + else if (ctx->segstore==2) { + if (!has_sep) { strcat(sURL, ":gpac"); has_sep = GF_TRUE; } + strcat(sURL, ":cache=keep"); + } + } + + if (start_range || end_range) { + char szRange[500]; + if (!has_sep) { strcat(sURL, ":gpac"); /* has_sep = GF_TRUE; */ } + snprintf(szRange, 500, ":range="LLU"-"LLU, start_range, end_range); + strcat(sURL, szRange); + } + + group->seg_filter_src = gf_filter_connect_source(ctx->filter, sURL, NULL, GF_FALSE, &e); + if (!group->seg_filter_src) { + gf_free(sURL); + gf_free(group); + gf_dash_set_group_udta(ctx->dash, group_index, NULL); + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASHDmx] group %d error locating plugin for segment - mime type %s name %s\n", group_index, mime, sURL)); + return e; + } + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASHDmx] setting up group %d from %s\n", group->idx, sURL)); + + gf_filter_set_setup_failure_callback(ctx->filter, group->seg_filter_src, dashdmx_on_filter_setup_error, group); + gf_dash_group_discard_segment(ctx->dash, group->idx); + group->prev_is_init_segment = GF_TRUE; + group->nb_group_deps = gf_dash_group_get_num_groups_depending_on(ctx->dash, group_index); + group->current_group_dep = 0; + gf_free(sURL); + return GF_OK; +} + +void dashdmx_io_delete_cache_file(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, const char *cache_url) +{ + if (!session) { + return; + } + gf_dm_delete_cached_file_entry_session((GF_DownloadSession *)session, cache_url); +} + +GF_DASHFileIOSession dashdmx_io_create(GF_DASHFileIO *dashio, Bool persistent, const char *url, s32 group_idx) +{ + GF_DownloadSession *sess; + GF_Err e; + u32 flags = GF_NETIO_SESSION_NOT_THREADED; + GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx *)dashio->udta; + + //we work in non-threaded mode, only MPD fetcher is allowed + if (group_idx>=0) + return NULL; + + //crude hack when using gpac downloader to initialize the MPD pid: get the pointer to the download session + //this should be safe unless the mpd_pid is destroyed, which should only happen upon destruction of the DASH session + if (group_idx==-1) { + const GF_PropertyValue *p = gf_filter_pid_get_property(ctx->mpd_pid, GF_PROP_PID_DOWNLOAD_SESSION); + if (p) { + sess = (GF_DownloadSession *) p->value.ptr; + if (!ctx->segstore) { + gf_dm_sess_force_memory_mode(sess); + } + ctx->reuse_download_session = GF_TRUE; + return (GF_DASHFileIOSession) sess; + } + } + + if (group_idx<-1) { + flags |= GF_NETIO_SESSION_MEMORY_CACHE; + } else { + if (!ctx->segstore) flags |= GF_NETIO_SESSION_MEMORY_CACHE; + if (persistent) flags |= GF_NETIO_SESSION_PERSISTENT; + } + sess = gf_dm_sess_new(ctx->dm, url, flags, NULL, NULL, &e); + return (GF_DASHFileIOSession) sess; +} +void dashdmx_io_del(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) +{ + GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx *)dashio->udta; + if (!ctx->reuse_download_session) + gf_dm_sess_del((GF_DownloadSession *)session); +} +GF_Err dashdmx_io_init(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) +{ + return gf_dm_sess_process_headers((GF_DownloadSession *)session); +} +GF_Err dashdmx_io_run(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) +{ + return gf_dm_sess_process((GF_DownloadSession *)session); +} +const char *dashdmx_io_get_url(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) +{ + return gf_dm_sess_get_resource_name((GF_DownloadSession *)session); +} +const char *dashdmx_io_get_cache_name(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) +{ + return gf_dm_sess_get_cache_name((GF_DownloadSession *)session); +} +const char *dashdmx_io_get_mime(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) +{ + return gf_dm_sess_mime_type((GF_DownloadSession *)session); +} +const char *dashdmx_io_get_header_value(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, const char *header_name) +{ + return gf_dm_sess_get_header((GF_DownloadSession *)session, header_name); +} +u64 dashdmx_io_get_utc_start_time(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) +{ + return gf_dm_sess_get_utc_start((GF_DownloadSession *)session); +} +GF_Err dashdmx_io_setup_from_url(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, const char *url, s32 group_idx) +{ + return gf_dm_sess_setup_from_url((GF_DownloadSession *)session, url, GF_FALSE); +} +GF_Err dashdmx_io_set_range(GF_DASHFileIO *dashio, GF_DASHFileIOSession session, u64 start_range, u64 end_range, Bool discontinue_cache) +{ + return gf_dm_sess_set_range((GF_DownloadSession *)session, start_range, end_range, discontinue_cache); +} + +#if 0 //unused since we are in non threaded mode +void dashdmx_io_abort(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) +{ + gf_dm_sess_abort((GF_DownloadSession *)session); +} + +u32 dashdmx_io_get_bytes_per_sec(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) +{ + u32 bps=0; + if (session) { + gf_dm_sess_get_stats((GF_DownloadSession *)session, NULL, NULL, NULL, NULL, &bps, NULL); + } else { + GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx *)dashio->udta; + bps = gf_dm_get_data_rate(ctx->dm); + bps/=8; + } + return bps; +} +u32 dashdmx_io_get_total_size(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) +{ + u64 size=0; + gf_dm_sess_get_stats((GF_DownloadSession *)session, NULL, NULL, &size, NULL, NULL, NULL); + return (u32) size; +} +u32 dashdmx_io_get_bytes_done(GF_DASHFileIO *dashio, GF_DASHFileIOSession session) +{ + u64 size=0; + gf_dm_sess_get_stats((GF_DownloadSession *)session, NULL, NULL, NULL, &size, NULL, NULL); + return (u32) size; +} +#endif + +GF_Err dashdmx_io_on_dash_event(GF_DASHFileIO *dashio, GF_DASHEventType dash_evt, s32 group_idx, GF_Err error_code) +{ + GF_Err e; + u32 i; + GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx *)dashio->udta; + + if (dash_evt==GF_DASH_EVENT_PERIOD_SETUP_ERROR) { + if (!ctx->initial_setup_done) { + gf_filter_setup_failure(ctx->filter, error_code); + ctx->initial_setup_done= GF_TRUE; + } + return GF_OK; + } + + if (dash_evt==GF_DASH_EVENT_SELECT_GROUPS) { +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + gf_dash_groups_set_language(ctx->dash, gf_opts_get_key("core", "lang")); + //these are not used in the test suite (require JS) + gf_dash_switch_quality(ctx->dash, GF_TRUE, GF_TRUE); + } +#endif + + //let the player decide which group to play: we declare everything + return GF_OK; + } + + /*for all selected groups, create input service and connect to init/first segment*/ + if (dash_evt==GF_DASH_EVENT_CREATE_PLAYBACK) { + //coverage of a few functions from old arch not deprecated (yet) +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + Bool done; + gf_dash_is_group_selected(ctx->dash, 0); + gf_dash_get_url(ctx->dash); + gf_dash_group_get_segment_init_keys(ctx->dash, 0, NULL); + gf_dash_group_get_max_segments_in_cache(ctx->dash, 0); + gf_dash_group_get_num_segments_ready(ctx->dash, 0, &done); + gf_dash_group_probe_current_download_segment_location(ctx->dash, 0, NULL, NULL, NULL, NULL, NULL); + gf_dash_group_loop_detected(ctx->dash, 0); + gf_dash_is_dynamic_mpd(ctx->dash); + gf_dash_group_get_language(ctx->dash, 0); + gf_dash_group_get_num_components(ctx->dash, 0); + gf_dash_group_get_download_rate(ctx->dash, 0); + gf_dash_get_utc_drift_estimate(ctx->dash); + + + //these are not used in the test suite (require decoding) + gf_dash_group_set_codec_stat(ctx->dash, 0, 0, 0, 0, 0, GF_FALSE, GF_FALSE); + gf_dash_group_set_buffer_levels(ctx->dash, 0, 0, 0, 0); + + //these are not used in the test suite (require JS) + if (ctx->algo==GF_DASH_ALGO_NONE) + gf_dash_set_automatic_switching(ctx->dash, GF_FALSE); + gf_dash_group_select_quality(ctx->dash, (u32) -1, NULL, 0); + + //these are not used yet in the test suite (require TEMI) + gf_dash_override_ntp(ctx->dash, 0); + + //these are not used yet in the test suite (require tiling + long run) + gf_dash_group_set_quality_degradation_hint(ctx->dash, 0, 0); + gf_dash_group_set_visible_rect(ctx->dash, 0, 0, 0, 0, 0, 0); + //this happen only when error downloading a segment + gf_dash_set_group_download_state(ctx->dash, 0, GF_OK); + } +#endif + /*select input services if possible*/ + for (i=0; idash); i++) { + const char *mime, *init_segment; + u64 start_range, end_range; + u32 j; + Bool playable = GF_TRUE; + //let the player decide which group to play + if (!gf_dash_is_group_selectable(ctx->dash, i)) + continue; + + j=0; + while (1) { + const char *desc_id, *desc_scheme, *desc_value; + if (! gf_dash_group_enum_descriptor(ctx->dash, i, GF_MPD_DESC_ESSENTIAL_PROPERTIES, j, &desc_id, &desc_scheme, &desc_value)) + break; + j++; + if (!strcmp(desc_scheme, "urn:mpeg:dash:srd:2014")) { + } else { + playable = GF_FALSE; + break; + } + } + if (!playable) { + gf_dash_group_select(ctx->dash, i, GF_FALSE); + continue; + } + + if (gf_dash_group_has_dependent_group(ctx->dash, i) >=0 ) { + gf_dash_group_select(ctx->dash, i, GF_TRUE); + continue; + } + + mime = gf_dash_group_get_segment_mime(ctx->dash, i); + init_segment = gf_dash_group_get_segment_init_url(ctx->dash, i, &start_range, &end_range); + + e = dashdmx_load_source(ctx, i, mime, init_segment, start_range, end_range); + if (e != GF_OK) { + gf_dash_group_select(ctx->dash, i, GF_FALSE); + } else { + u32 w, h; + /*connect our media service*/ + gf_dash_group_get_video_info(ctx->dash, i, &w, &h); + if (w && h && w>ctx->width && h>ctx->height) { + ctx->width = w; + ctx->height = h; + } + if (gf_dash_group_get_srd_max_size_info(ctx->dash, i, &w, &h)) { + ctx->width = w; + ctx->height = h; + } + if (ctx->closed) return GF_OK; + } + } + + if (!ctx->initial_setup_done) { + ctx->initial_setup_done = GF_TRUE; + } + + //we had a seek outside of the period we were setting up, during period setup ! + //request the seek again from the player + if (ctx->seek_request>=0) { +#ifdef FILTER_FIXME + GF_NetworkCommand com; + memset(&com, 0, sizeof(GF_NetworkCommand)); + com.command_type = GF_NET_SERVICE_SEEK; + com.play.start_range = ctx->seek_request; + ctx->seek_request = 0; + gf_service_command(ctx->service, &com, GF_OK); +#endif + + } + return GF_OK; + } + + /*for all running services, stop service*/ + if (dash_evt==GF_DASH_EVENT_DESTROY_PLAYBACK) { + for (i=0; idash); i++) { + GF_DASHGroup *group = gf_dash_get_group_udta(ctx->dash, i); + if (!group) continue; + if (group->seg_filter_src) { + gf_filter_remove_src(ctx->filter, group->seg_filter_src); + group->seg_filter_src = NULL; + } + gf_free(group); + gf_dash_set_group_udta(ctx->dash, i, NULL); + } + for (i=0; ifilter); i++) { + GF_FilterPid *opid = gf_filter_get_opid(ctx->filter, i); + gf_filter_pid_set_udta(opid, NULL); + } + return GF_OK; + } + if (dash_evt==GF_DASH_EVENT_ABORT_DOWNLOAD) { + GF_DASHGroup *group = gf_dash_get_group_udta(ctx->dash, group_idx); + if (group) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_SOURCE_SWITCH, NULL); + evt.seek.start_offset = -1; + evt.seek.source_switch = NULL; + gf_filter_send_event(group->seg_filter_src, &evt, GF_FALSE); + } + return GF_OK; + } + + if (dash_evt==GF_DASH_EVENT_QUALITY_SWITCH) { + if (group_idx>=0) { + GF_DASHGroup *group = gf_dash_get_group_udta(ctx->dash, group_idx); + if (!group) { + group_idx = gf_dash_group_has_dependent_group(ctx->dash, group_idx); + group = gf_dash_get_group_udta(ctx->dash, group_idx); + } + if (group) { + for (i=0; ifilter); i++) { + GF_FilterPid *opid = gf_filter_get_opid(ctx->filter, i); + if (gf_filter_pid_get_udta(opid) != group) continue; + + s32 sel = gf_dash_group_get_active_quality(ctx->dash, group_idx); + if (sel>=0) { + gf_filter_pid_set_property_str(opid, "has:selected", &PROP_UINT(sel) ); + } + gf_filter_pid_set_property_str(opid, "has:auto", &PROP_UINT(gf_dash_get_automatic_switching(ctx->dash) ) ); + gf_filter_pid_set_property_str(opid, "has:tilemode", &PROP_UINT(gf_dash_get_tile_adaptation_mode(ctx->dash) ) ); + } + } + } + return GF_OK; + } + if (dash_evt==GF_DASH_EVENT_TIMESHIFT_UPDATE) { + Double timeshift = gf_dash_get_timeshift_buffer_pos(ctx->dash); + for (i=0; ifilter); i++) { + GF_FilterPid *opid = gf_filter_get_opid(ctx->filter, i); + gf_filter_pid_set_info(opid, GF_PROP_PID_TIMESHIFT_TIME, &PROP_DOUBLE(timeshift) ); + } + return GF_OK; + } + if (dash_evt==GF_DASH_EVENT_TIMESHIFT_OVERFLOW) { + u32 evttype = (group_idx>=0) ? 2 : 1; + for (i=0; ifilter); i++) { + GF_FilterPid *opid = gf_filter_get_opid(ctx->filter, i); + gf_filter_pid_set_info(opid, GF_PROP_PID_TIMESHIFT_STATE, &PROP_UINT(evttype) ); + } + } + + if (dash_evt==GF_DASH_EVENT_CODEC_STAT_QUERY) { + GF_DASHGroup *group = gf_dash_get_group_udta(ctx->dash, group_idx); + for (i=0; ifilter); i++) { + GF_FilterPid *opid = gf_filter_get_opid(ctx->filter, i); + if (gf_filter_pid_get_udta(opid) == group) { + GF_FilterPidStatistics stats; + if (gf_filter_pid_get_statistics(opid, &stats, GF_STATS_DECODER_SINK) != GF_OK) + continue; + if (stats.disconnected) + continue; + + if (!stats.nb_processed) stats.nb_processed=1; + if (!stats.nb_saps) stats.nb_saps=1; + + gf_dash_group_set_codec_stat(ctx->dash, group_idx, (u32) (stats.total_process_time/stats.nb_processed), stats.max_process_time, (u32) (stats.total_sap_process_time/stats.nb_saps), stats.max_sap_process_time, GF_FALSE, GF_FALSE); + + + gf_dash_group_set_buffer_levels(ctx->dash, group_idx, (u32) (stats.min_playout_time/1000), (u32) (stats.max_buffer_time/1000), (u32) (stats.buffer_time/1000) ); + + break; + } + } + } + return GF_OK; +} + +static void dashdmx_setup_buffer(GF_DASHDmxCtx *ctx, GF_DASHGroup *group) +{ + u32 buffer_ms, play_buf_ms; + gf_filter_get_output_buffer_max(ctx->filter, &buffer_ms, &play_buf_ms); + buffer_ms /= 1000; + play_buf_ms /= 1000; + + //use min buffer from MPD + if (ctx->use_bmin) { + u64 mpd_buffer_ms = gf_dash_get_min_buffer_time(ctx->dash); + if (mpd_buffer_ms > buffer_ms) + buffer_ms = (u32) mpd_buffer_ms; + } + if (buffer_ms) { + gf_dash_set_user_buffer(ctx->dash, buffer_ms); + } + gf_dash_group_set_max_buffer_playout(ctx->dash, group->idx, play_buf_ms); +} + +/*check in all groups if the service can support reverse playback (speed<0); return GF_OK only if service is supported in all groups*/ +static u32 dashdmx_dash_playback_mode(GF_DASHDmxCtx *ctx) +{ + u32 pmode, mode, i, count = gf_filter_get_ipid_count(ctx->filter); + mode = GF_PLAYBACK_MODE_REWIND; + for (i=0; ifilter, i); + if (ctx->mpd_pid == pid) continue; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PLAYBACK_MODE); + pmode = p ? p->value.uint : GF_PLAYBACK_MODE_REWIND; + if (pmode < mode) mode = pmode; + } + return mode; +} + + +static s32 dashdmx_group_idx_from_pid(GF_DASHDmxCtx *ctx, GF_FilterPid *src_pid) +{ + s32 i; + + for (i=0; (u32) i < gf_dash_get_group_count(ctx->dash); i++) { + GF_DASHGroup *group = gf_dash_get_group_udta(ctx->dash, i); + if (!group) continue; + if (gf_filter_pid_is_filter_in_parents(src_pid, group->seg_filter_src)) + return i; + } + return -1; +} + +static GF_FilterPid *dashdmx_create_output_pid(GF_Filter *filter, GF_FilterPid *input, u32 *run_status) +{ + u32 global_score=0; + GF_FilterPid *output_pid = NULL; + u32 i, count = gf_filter_get_opid_count(filter); + const GF_PropertyValue *codec, *streamtype, *role, *lang; + + *run_status = 0; + + streamtype = gf_filter_pid_get_property(input, GF_PROP_PID_STREAM_TYPE); + if (streamtype && streamtype->value.uint==GF_STREAM_ENCRYPTED) + streamtype = gf_filter_pid_get_property(input, GF_PROP_PID_ORIG_STREAM_TYPE); + + codec = gf_filter_pid_get_property(input, GF_PROP_PID_CODECID); + role = gf_filter_pid_get_property(input, GF_PROP_PID_ROLE); + lang = gf_filter_pid_get_property(input, GF_PROP_PID_LANGUAGE); + + for (i=0; ivalue.uint==GF_STREAM_ENCRYPTED) + o_streamtype = gf_filter_pid_get_property(opid, GF_PROP_PID_ORIG_STREAM_TYPE); + + o_codec = gf_filter_pid_get_property(opid, GF_PROP_PID_CODECID); + o_role = gf_filter_pid_get_property(opid, GF_PROP_PID_ROLE); + o_lang = gf_filter_pid_get_property(opid, GF_PROP_PID_LANGUAGE); + + if (!o_streamtype || !streamtype || !gf_props_equal(streamtype, o_streamtype)) + continue; + + score = 1; + //get highest priority for streams with same role + if (o_role && role && gf_props_equal(role, o_role)) score += 10; + //then high priority for streams with same lang + if (o_lang && lang && gf_props_equal(lang, o_lang)) score += 5; + + //otherwise favour streams with same codec + if (!o_codec && codec && gf_props_equal(codec, o_codec)) score++; + + if (global_scorenb_group_deps) { + u32 asid; + char as_name[100]; + asid = gf_dash_group_get_as_id(ctx->dash, group_idx); + if (!asid) asid = group_idx+1; + sprintf(as_name, "AS%d", asid); + gf_filter_pid_set_name(opid, as_name); + } + + mode = dashdmx_dash_playback_mode(ctx); + gf_filter_pid_set_property(opid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(mode)); + + if (ctx->max_res && gf_filter_pid_get_property(ipid, GF_PROP_PID_WIDTH)) { + gf_filter_pid_set_property(opid, GF_PROP_SERVICE_WIDTH, &PROP_UINT(ctx->width)); + gf_filter_pid_set_property(opid, GF_PROP_SERVICE_HEIGHT, &PROP_UINT(ctx->height)); + } + + dur = (u32) (1000*gf_dash_get_duration(ctx->dash) ); + if (dur>0) + gf_filter_pid_set_property(opid, GF_PROP_PID_DURATION, &PROP_FRAC64_INT(dur, 1000) ); + + dur = (1000*gf_dash_group_get_time_shift_buffer_depth(ctx->dash, group_idx) ); + if (dur>0) + gf_filter_pid_set_property(opid, GF_PROP_PID_TIMESHIFT_DEPTH, &PROP_FRAC_INT(dur, 1000) ); + + + if (ctx->use_bmin) { + u32 max = gf_dash_get_min_buffer_time(ctx->dash); + gf_filter_pid_set_property_str(opid, "BufferLength", &PROP_UINT(max)); + } + + memset(&qualities, 0, sizeof(GF_PropertyValue)); + qualities.type = GF_PROP_STRING_LIST; + qualities.value.string_list = gf_list_new(); + + count = gf_dash_group_get_num_qualities(ctx->dash, group_idx); + for (i=0; idash, group_idx, i, &qinfo); + if (e) break; + if (!qinfo.ID) qinfo.ID=""; + if (!qinfo.mime) qinfo.mime="unknown"; + if (!qinfo.codec) qinfo.codec="codec"; + + snprintf(szInfo, 500, "id=%s", qinfo.ID); + + e = gf_dynstrcat(&qdesc, szInfo, "::"); + if (e) break; + + snprintf(szInfo, 500, "codec=%s", qinfo.codec); + e = gf_dynstrcat(&qdesc, szInfo, "::"); + if (e) break; + + snprintf(szInfo, 500, "mime=%s", qinfo.mime); + e = gf_dynstrcat(&qdesc, szInfo, "::"); + if (e) break; + + snprintf(szInfo, 500, "bw=%d", qinfo.bandwidth); + e = gf_dynstrcat(&qdesc, szInfo, "::"); + if (e) break; + + if (qinfo.disabled) { + e = gf_dynstrcat(&qdesc, "disabled", "::"); + if (e) break; + } + + if (qinfo.width && qinfo.height) { + snprintf(szInfo, 500, "w=%d", qinfo.width); + e = gf_dynstrcat(&qdesc, szInfo, "::"); + if (e) break; + + snprintf(szInfo, 500, "h=%d", qinfo.height); + e = gf_dynstrcat(&qdesc, szInfo, "::"); + if (e) break; + + if (qinfo.interlaced) { + e = gf_dynstrcat(&qdesc, "interlaced", "::"); + if (e) break; + } + if (qinfo.fps_den) { + snprintf(szInfo, 500, "fps=%d/%d", qinfo.fps_num, qinfo.fps_den); + } else { + snprintf(szInfo, 500, "fps=%d", qinfo.fps_num); + } + e = gf_dynstrcat(&qdesc, szInfo, "::"); + if (e) break; + + if (qinfo.par_den && qinfo.par_num && (qinfo.par_den != qinfo.par_num)) { + snprintf(szInfo, 500, "sar=%d/%d", qinfo.par_num, qinfo.par_den); + e = gf_dynstrcat(&qdesc, szInfo, "::"); + if (e) break; + } + } + if (qinfo.sample_rate) { + snprintf(szInfo, 500, "sr=%d", qinfo.sample_rate); + e = gf_dynstrcat(&qdesc, szInfo, "::"); + if (e) break; + + snprintf(szInfo, 500, "ch=%d", qinfo.nb_channels); + e = gf_dynstrcat(&qdesc, szInfo, "::"); + if (e) break; + } + gf_list_add(qualities.value.string_list, qdesc); + qdesc = NULL; + } + gf_filter_pid_set_info_str(opid, "has:qualities", &qualities); + + const char *title, *source; + gf_dash_get_info(ctx->dash, &title, &source); + gf_filter_pid_set_info(opid, GF_PROP_PID_SERVICE_NAME, &PROP_STRING(title) ); + gf_filter_pid_set_info(opid, GF_PROP_PID_SERVICE_PROVIDER, &PROP_STRING(source) ); + + title = NULL; + gf_dash_group_enum_descriptor(ctx->dash, group_idx, GF_MPD_DESC_ROLE, 0, NULL, NULL, &title); + if (title) + gf_filter_pid_set_property(opid, GF_PROP_PID_ROLE, &PROP_STRING(title) ); + + title = NULL; + gf_dash_group_enum_descriptor(ctx->dash, group_idx, GF_MPD_DESC_ACCESSIBILITY, 0, NULL, NULL, &title); + if (title) + gf_filter_pid_set_property_str(opid, "accessibility", &PROP_STRING(title) ); + + title = NULL; + gf_dash_group_enum_descriptor(ctx->dash, group_idx, GF_MPD_DESC_RATING, 0, NULL, NULL, &title); + if (title) + gf_filter_pid_set_property_str(opid, "rating", &PROP_STRING(title) ); + + if (!gf_sys_is_test_mode()) { + gf_filter_pid_set_info_str(opid, "ntpdiff", &PROP_SINT(gf_dash_get_utc_drift_estimate(ctx->dash) ) ); + } + +#ifdef FILTER_FIXME + //need to implement back dependent group SRD and qualities (fir HEVC tiles) + if (com->srd.dependent_group_index) { + if (com->srd.dependent_group_index > gf_dash_group_get_num_groups_depending_on(ctx->dash, idx)) + return GF_BAD_PARAM; + + idx = gf_dash_get_dependent_group_index(ctx->dash, idx, com->srd.dependent_group_index-1); + } +#endif + + memset(&srd, 0, sizeof(GF_PropertyValue)); + memset(&srdref, 0, sizeof(GF_PropertyValue)); + srd.type = GF_PROP_VEC4I; + srdref.type = GF_PROP_VEC2I; + if (gf_dash_group_get_srd_info(ctx->dash, group_idx, NULL, + &srd.value.vec4i.x, + &srd.value.vec4i.y, + &srd.value.vec4i.z, + &srd.value.vec4i.w, + &srdref.value.vec2i.x, + &srdref.value.vec2i.y)) { + + gf_filter_pid_set_property(opid, GF_PROP_PID_SRD, &srd); + gf_filter_pid_set_property(opid, GF_PROP_PID_SRD_REF, &srdref); + } + + //setup initial quality - this is disabled in test mode for the time being (invalidates all dash playback hashes) + if (!gf_sys_is_test_mode()) + dashdmx_io_on_dash_event(&ctx->dash_io, GF_DASH_EVENT_QUALITY_SWITCH, group->idx, GF_OK); + + + //if MPD file pid is defined, merge its properties. This will allow forwarding user-defined properties, + // eg -i dash.mpd:#MyProp=toto to all PIDs coming from media sources + if (ctx->mpd_pid) { + gf_filter_pid_merge_properties(opid, ctx->mpd_pid, dashdmx_merge_prop, ipid); + } +} + +static GF_Err dashdmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + s32 group_idx; + GF_FilterPid *opid; + GF_Err e; + GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx*) gf_filter_get_udta(filter); + GF_DASHGroup *group; + + if (is_remove) { + //TODO + if (pid==ctx->mpd_pid) { + u32 i; + for (i=0; iseg_filter_src) { + gf_filter_remove_src(filter, group->seg_filter_src); + } + } + gf_dash_close(ctx->dash); + ctx->mpd_pid = NULL; + } else { + opid = gf_filter_pid_get_udta(pid); + if (opid) { + if (!ctx->mpd_pid) { + gf_filter_pid_remove(opid); + } else if (gf_dash_all_groups_done(ctx->dash) && gf_dash_in_last_period(ctx->dash, GF_TRUE)) { + gf_filter_pid_remove(opid); + } else { + gf_filter_pid_set_udta(opid, NULL); + gf_filter_pid_set_udta(pid, NULL); + } + } + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASHDmx] Mismatch in input pid caps\n")); + return GF_NOT_SUPPORTED; + } + + //configure MPD pid + if (!ctx->mpd_pid) { + const GF_PropertyValue *p; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_URL); + if (!p || !p->value.string) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASHDmx] no URL on MPD pid\n")); + return GF_NOT_SUPPORTED; + } + + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + ctx->mpd_pid = pid; + ctx->seek_request = -1; + ctx->nb_playing = 0; + + e = gf_dash_open(ctx->dash, p->value.string); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASHDmx] Error - cannot initialize DASH Client for %s: %s\n", p->value.string, gf_error_to_string(e))); + gf_filter_setup_failure(filter, e); + return e; + } + //we have a redirect URL on mpd pid, this means this comes from a service feeding the cache so we won't get any data on the pid. + //request a process task + p = gf_filter_pid_get_property(pid, GF_PROP_PID_REDIRECT_URL); + if (p && p->value.string) + gf_filter_post_process_task(filter); + return GF_OK; + } else if (ctx->mpd_pid == pid) { + return GF_OK; + } + + //figure out group for this pid + group_idx = dashdmx_group_idx_from_pid(ctx, pid); + if (group_idx<0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASHDmx] Failed to locate adaptation set for input pid\n")); + return GF_SERVICE_ERROR; + } + group = gf_dash_get_group_udta(ctx->dash, group_idx); + + //initial configure + opid = gf_filter_pid_get_udta(pid); + if (opid == NULL) { + u32 run_status; + group = gf_dash_get_group_udta(ctx->dash, group_idx); + assert(group); + //for now we declare every component from the input source + opid = dashdmx_create_output_pid(filter, pid, &run_status); + gf_filter_pid_set_udta(opid, group); + gf_filter_pid_set_udta(pid, opid); + group->nb_pids ++; + + if (run_status) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_PLAY, pid); + gf_filter_pid_send_event(pid, &evt); + group->is_playing = GF_TRUE; + gf_dash_group_select(ctx->dash, group->idx, GF_TRUE); + + if (run_status==2) { + gf_dash_set_group_done(ctx->dash, group->idx, GF_TRUE); + gf_dash_group_select(ctx->dash, group->idx, GF_FALSE); + + GF_FEVT_INIT(evt, GF_FEVT_STOP, pid); + gf_filter_pid_send_event(pid, &evt); + group->is_playing = GF_FALSE; + } + } + } + dashdmx_declare_properties(ctx, group, group_idx, opid, pid); + + + //reset the file cache property (init segment could be cached but not the rest) + gf_filter_pid_set_property(opid, GF_PROP_PID_FILE_CACHED, NULL); + + return GF_OK; +} + +static GF_Err dashdmx_initialize(GF_Filter *filter) +{ + GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx*) gf_filter_get_udta(filter); + ctx->filter = filter; + ctx->dm = gf_filter_get_download_manager(filter); + if (!ctx->dm) return GF_SERVICE_ERROR; + + ctx->dash_io.udta = ctx; + ctx->dash_io.delete_cache_file = dashdmx_io_delete_cache_file; + ctx->dash_io.create = dashdmx_io_create; + ctx->dash_io.del = dashdmx_io_del; + ctx->dash_io.init = dashdmx_io_init; + ctx->dash_io.run = dashdmx_io_run; + ctx->dash_io.get_url = dashdmx_io_get_url; + ctx->dash_io.get_cache_name = dashdmx_io_get_cache_name; + ctx->dash_io.get_mime = dashdmx_io_get_mime; + ctx->dash_io.get_header_value = dashdmx_io_get_header_value; + ctx->dash_io.get_utc_start_time = dashdmx_io_get_utc_start_time; + ctx->dash_io.setup_from_url = dashdmx_io_setup_from_url; + ctx->dash_io.set_range = dashdmx_io_set_range; + +#if 0 //unused since we are in non threaded mode + ctx->dash_io.abort = dashdmx_io_abort; + ctx->dash_io.get_bytes_per_sec = dashdmx_io_get_bytes_per_sec; + ctx->dash_io.get_total_size = dashdmx_io_get_total_size; + ctx->dash_io.get_bytes_done = dashdmx_io_get_bytes_done; +#endif + + ctx->dash_io.on_dash_event = dashdmx_io_on_dash_event; + + ctx->dash = gf_dash_new(&ctx->dash_io, GF_DASH_THREAD_NONE, 0, ctx->auto_switch, (ctx->segstore==2) ? GF_TRUE : GF_FALSE, (ctx->algo==GF_DASH_ALGO_NONE) ? GF_TRUE : GF_FALSE, ctx->start_with, ctx->init_timeshift); + + if (!ctx->dash) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASHDmx] Error - cannot create DASH Client\n")); + return GF_IO_ERR; + } + + if (ctx->screen_res) { + GF_FilterSessionCaps caps; + gf_filter_get_session_caps(ctx->filter, &caps); + gf_dash_set_max_resolution(ctx->dash, caps.max_screen_width, caps.max_screen_height, caps.max_screen_bpp); + } + + gf_dash_set_algo(ctx->dash, ctx->algo); + gf_dash_set_utc_shift(ctx->dash, ctx->shift_utc); + gf_dash_set_atsc_ast_shift(ctx->dash, ctx->atsc_shift); + gf_dash_enable_utc_drift_compensation(ctx->dash, ctx->server_utc); + gf_dash_set_tile_adaptation_mode(ctx->dash, ctx->tile_mode, ctx->tiles_rate); + + gf_dash_set_min_timeout_between_404(ctx->dash, ctx->delay40X); + gf_dash_set_segment_expiration_threshold(ctx->dash, ctx->exp_threshold); + gf_dash_set_switching_probe_count(ctx->dash, ctx->switch_count); + gf_dash_set_agressive_adaptation(ctx->dash, ctx->aggressive); + gf_dash_debug_group(ctx->dash, ctx->debug_as); + gf_dash_disable_speed_adaptation(ctx->dash, ctx->speedadapt); + gf_dash_ignore_xlink(ctx->dash, ctx->noxlink); + gf_dash_set_period_xlink_query_string(ctx->dash, ctx->query); + gf_dash_set_low_latency_mode(ctx->dash, ctx->lowlat); + if (ctx->split_as) + gf_dash_split_adaptation_sets(ctx->dash); + + ctx->initial_play = GF_TRUE; + gf_filter_block_eos(filter, GF_TRUE); + + //for coverage +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + dashdmx_on_filter_setup_error(NULL, NULL, GF_OK); + } +#endif + return GF_OK; +} + + +static void dashdmx_finalize(GF_Filter *filter) +{ + GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx*) gf_filter_get_udta(filter); + assert(ctx); + + if (ctx->dash) + gf_dash_del(ctx->dash); +} + +static Bool dashdmx_process_event(GF_Filter *filter, const GF_FilterEvent *fevt) +{ + u32 i, count; + GF_FilterEvent src_evt; + GF_FilterPid *ipid; + Bool initial_play; + Double offset; + GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx*) gf_filter_get_udta(filter); + GF_DASHGroup *group; + + + switch (fevt->base.type) { + case GF_FEVT_QUALITY_SWITCH: + if (fevt->quality_switch.set_tile_mode_plus_one) { + GF_DASHTileAdaptationMode tile_mode = fevt->quality_switch.set_tile_mode_plus_one - 1; + gf_dash_set_tile_adaptation_mode(ctx->dash, tile_mode, 100); + } else if (fevt->quality_switch.q_idx < 0) { + gf_dash_set_automatic_switching(ctx->dash, 1); + } else if (fevt->base.on_pid) { + s32 idx; + group = gf_filter_pid_get_udta(fevt->base.on_pid); + if (!group) return GF_TRUE; + idx = group->idx; + + gf_dash_group_set_quality_degradation_hint(ctx->dash, group->idx, fevt->quality_switch.quality_degradation); + + if (fevt->quality_switch.dependent_group_index) { + if (fevt->quality_switch.dependent_group_index > gf_dash_group_get_num_groups_depending_on(ctx->dash, group->idx)) + return GF_BAD_PARAM; + + idx = gf_dash_get_dependent_group_index(ctx->dash, group->idx, fevt->quality_switch.dependent_group_index-1); + if (idx==-1) return GF_BAD_PARAM; + } + + gf_dash_set_automatic_switching(ctx->dash, 0); + gf_dash_group_select_quality(ctx->dash, idx, NULL, fevt->quality_switch.q_idx); + } else { + gf_dash_switch_quality(ctx->dash, fevt->quality_switch.up, ctx->immediate); + } + return GF_TRUE; + +#ifdef FILTER_FIXME + + case GF_NET_ASSOCIATED_CONTENT_TIMING: + gf_dash_override_ntp(ctx->dash, com->addon_time.ntp); + return GF_OK; +#endif + default: + break; + } + + /*not supported*/ + if (!fevt->base.on_pid) return GF_NOT_SUPPORTED; + group = gf_filter_pid_get_udta(fevt->base.on_pid); + if (!group) return GF_NOT_SUPPORTED; + count = gf_filter_get_ipid_count(filter); + ipid = NULL; + for (i=0; ibase.on_pid) break; + ipid = NULL; + } + + switch (fevt->base.type) { + case GF_FEVT_VISIBILITY_HINT: + group = gf_filter_pid_get_udta(fevt->base.on_pid); + if (!group) return GF_TRUE; + + gf_dash_group_set_visible_rect(ctx->dash, group->idx, fevt->visibility_hint.min_x, fevt->visibility_hint.max_x, fevt->visibility_hint.min_y, fevt->visibility_hint.max_y, fevt->visibility_hint.is_gaze); + return GF_TRUE; + + case GF_FEVT_PLAY: + src_evt = *fevt; + group->is_playing = GF_TRUE; + ctx->check_eos = GF_FALSE; + + //adjust play range from media timestamps to MPD time + if (fevt->play.timestamp_based) { + + if (fevt->play.timestamp_based==1) { + u64 pto; + u32 timescale; + gf_dash_group_get_presentation_time_offset(ctx->dash, group->idx, &pto, ×cale); + offset = (Double) pto; + offset /= timescale; + src_evt.play.start_range -= offset; + if (src_evt.play.start_range < 0) src_evt.play.start_range = 0; + } + + group->is_timestamp_based = 1; + group->pto_setup = 0; + ctx->media_start_range = fevt->play.start_range; + } else { + group->is_timestamp_based = 0; + group->pto_setup = 0; + if (fevt->play.start_range<0) src_evt.play.start_range = 0; + //in m3u8, we need also media start time for mapping time + if (gf_dash_is_m3u8(ctx->dash)) + ctx->media_start_range = fevt->play.start_range; + } + + //we cannot handle seek request outside of a period being setup, this messes up our internal service setup + //we postpone the seek and will request it later on ... + if (gf_dash_in_period_setup(ctx->dash)) { + u64 p_end = gf_dash_get_period_duration(ctx->dash); + if (p_end) { + p_end += gf_dash_get_period_start(ctx->dash); + if (p_end<1000*fevt->play.start_range) { + ctx->seek_request = fevt->play.start_range; + return GF_TRUE; + } + } + } + + if (fevt->play.speed) + gf_dash_set_speed(ctx->dash, fevt->play.speed); + + initial_play = ctx->initial_play; + if (fevt->play.initial_broadcast_play) initial_play = GF_TRUE; + + /*don't seek if this command is the first PLAY request of objects declared by the subservice, unless start range is not default one (0) */ + if (!ctx->nb_playing) { + if (!initial_play || (fevt->play.start_range>1.0)) { + + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] Received Play command on group %d\n", group->idx)); + + if (fevt->play.end_range<=0) { + u32 ms = (u32) ( 1000 * (-fevt->play.end_range) ); + if (ms<1000) ms = 0; + gf_dash_set_timeshift(ctx->dash, ms); + } + gf_dash_seek(ctx->dash, fevt->play.start_range); + + //to remove once we manage to keep the service alive + /*don't forward commands if a switch of period is to be scheduled, we are killing the service anyway ...*/ + if (gf_dash_get_period_switch_status(ctx->dash)) return GF_TRUE; + } + } + //otherwise in static mode, perform a group seek + else if (!initial_play && !gf_dash_is_dynamic_mpd(ctx->dash) ) { + /*don't forward commands if a switch of period is to be scheduled, we are killing the service anyway ...*/ + if (gf_dash_get_period_switch_status(ctx->dash)) return GF_TRUE; + + //seek on a single group + + gf_dash_group_seek(ctx->dash, group->idx, fevt->play.start_range); + } + + //check if current segment playback should be aborted + src_evt.play.forced_dash_segment_switch = gf_dash_group_segment_switch_forced(ctx->dash, group->idx); + + gf_dash_group_select(ctx->dash, group->idx, GF_TRUE); + gf_dash_set_group_done(ctx->dash, (u32) group->idx, 0); + + //adjust start range from MPD time to media time + if (gf_dash_is_dynamic_mpd(ctx->dash) && ctx->noseek) { + src_evt.play.start_range=0; + } else { + u64 pto; + u32 timescale; + src_evt.play.start_range = gf_dash_group_get_start_range(ctx->dash, group->idx); + gf_dash_group_get_presentation_time_offset(ctx->dash, group->idx, &pto, ×cale); + src_evt.play.start_range += ((Double)pto) / timescale; + } + src_evt.play.no_byterange_forward = 1; + dashdmx_setup_buffer(ctx, group); + + gf_filter_prevent_blocking(filter, GF_TRUE); + + ctx->nb_playing++; + //forward new event to source pid + src_evt.base.on_pid = ipid; + + gf_filter_pid_send_event(ipid, &src_evt); + gf_filter_post_process_task(filter); + //cancel the event + return GF_TRUE; + + case GF_FEVT_STOP: + gf_dash_set_group_done(ctx->dash, (u32) group->idx, 1); + gf_dash_group_select(ctx->dash, (u32) group->idx, GF_FALSE); + group->is_playing = GF_FALSE; + if (ctx->nb_playing) { + ctx->initial_play = GF_FALSE; + group->force_seg_switch = GF_TRUE; + ctx->nb_playing--; + if (!ctx->nb_playing) ctx->check_eos = GF_TRUE; + } + //forward new event to source pid + src_evt = *fevt; + src_evt.base.on_pid = ipid; + gf_filter_pid_send_event(ipid, &src_evt); + + //cancel the event + return GF_TRUE; + + case GF_FEVT_CAPS_CHANGE: + if (ctx->screen_res) { + GF_FilterSessionCaps caps; + gf_filter_get_session_caps(ctx->filter, &caps); + gf_dash_set_max_resolution(ctx->dash, caps.max_screen_width, caps.max_screen_height, caps.max_screen_bpp); + } + return GF_TRUE; + case GF_FEVT_INFO_UPDATE: + //propagate + return GF_FALSE; + default: + break; + } + + //by default cancel all events + return GF_TRUE; +} + +static void dashdmx_switch_segment(GF_DASHDmxCtx *ctx, GF_DASHGroup *group) +{ + u32 dependent_representation_index = 0; + GF_Err e; + Bool has_next; + GF_FilterEvent evt; + const char *next_url, *next_url_init_or_switch_segment, *src_url, *key_url; + u64 start_range, end_range, switch_start_range, switch_end_range; + bin128 key_IV; + u32 group_idx; + + assert(group->nb_eos || group->seg_was_not_ready || group->in_error); + group->wait_for_pck = GF_TRUE; + group->in_error = GF_FALSE; + if (group->segment_sent) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] group %d drop current segment\n", group->idx)); + if (!group->current_group_dep) + gf_dash_group_discard_segment(ctx->dash, group->idx); + + group->segment_sent = GF_FALSE; + //no thread mode, we work with at most one entry in cache, call process right away to get the group next URL ready + gf_dash_process(ctx->dash); + } + + group->stats_uploaded = GF_FALSE; + +#if 0 + if (group_done) { + if (!gf_dash_get_period_switch_status(ctx->dash) && gf_dash_in_last_period(ctx->dash, GF_TRUE)) { + return; + } + } +#endif + + group_idx = group->idx; + if (group->nb_group_deps) { + if (group->current_group_dep) { + dependent_representation_index = group->current_group_dep; +// s32 res = gf_dash_get_dependent_group_index(ctx->dash, group->idx, group->current_group_dep- + } + } + e = gf_dash_group_get_next_segment_location(ctx->dash, group_idx, dependent_representation_index, &next_url, &start_range, &end_range, + NULL, &next_url_init_or_switch_segment, &switch_start_range , &switch_end_range, + &src_url, &has_next, &key_url, &key_IV); + + if (e == GF_EOS) { + group->eos_detected = GF_TRUE; + return; + } + group->eos_detected = GF_FALSE; + + if (e != GF_OK) { + if (e == GF_BUFFER_TOO_SMALL) { + group->seg_was_not_ready = GF_TRUE; + group->stats_uploaded = GF_TRUE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] group %d next segment name not known yet!\n", group->idx)); + gf_filter_ask_rt_reschedule(ctx->filter, 10000); +// gf_filter_post_process_task(ctx->filter); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASHDmx] group %d error fetching next segment name: %s\n", group->idx, gf_error_to_string(e) )); + } + return; + } + + if (group->nb_group_deps) { + group->current_group_dep++; + if (group->current_group_dep>group->nb_group_deps) + group->current_group_dep = 0; + } + + assert(next_url); + group->seg_was_not_ready = GF_FALSE; + + if (next_url_init_or_switch_segment && !group->init_switch_seg_sent) { + GF_FEVT_INIT(evt, GF_FEVT_SOURCE_SWITCH, NULL); + evt.seek.start_offset = switch_start_range; + evt.seek.end_offset = switch_end_range; + evt.seek.source_switch = next_url_init_or_switch_segment; + evt.seek.previous_is_init_segment = group->prev_is_init_segment; + evt.seek.skip_cache_expiration = GF_TRUE; + + group->prev_is_init_segment = GF_TRUE; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] group %d queuing next init/switching segment %s\n", group->idx, next_url_init_or_switch_segment)); + + group->init_switch_seg_sent = GF_TRUE; + gf_filter_send_event(group->seg_filter_src, &evt, GF_FALSE); + return; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] group %d queuing next media segment %s\n", group->idx, next_url)); + + GF_FEVT_INIT(evt, GF_FEVT_SOURCE_SWITCH, NULL); + evt.seek.source_switch = next_url; + evt.seek.start_offset = start_range; + evt.seek.end_offset = end_range; + evt.seek.previous_is_init_segment = group->prev_is_init_segment; + group->segment_sent = GF_TRUE; + group->prev_is_init_segment = GF_FALSE; + group->init_switch_seg_sent = GF_FALSE; + gf_filter_send_event(group->seg_filter_src, &evt, GF_FALSE); +} + +static void dashdmx_update_group_stats(GF_DASHDmxCtx *ctx, GF_DASHGroup *group) +{ + u32 bytes_per_sec = 0; + u64 file_size = 0, bytes_done = 0; + const GF_PropertyValue *p; + GF_PropertyEntry *pe=NULL; + Bool broadcast_flag = GF_FALSE; + if (group->stats_uploaded) return; + if (group->prev_is_init_segment) return; + if (!group->seg_filter_src) return; + + p = gf_filter_get_info(group->seg_filter_src, GF_PROP_PID_FILE_CACHED, &pe); + if (!p || !p->value.boolean) { + gf_filter_release_property(pe); + return; + } + group->stats_uploaded = GF_TRUE; + + p = gf_filter_get_info(group->seg_filter_src, GF_PROP_PID_DOWN_RATE, &pe); + if (p) bytes_per_sec = p->value.uint / 8; + + p = gf_filter_get_info(group->seg_filter_src, GF_PROP_PID_DOWN_SIZE, &pe); + if (p) file_size = p->value.longuint; + + p = gf_filter_get_info(group->seg_filter_src, GF_PROP_PID_DOWN_BYTES, &pe); + if (p) bytes_done = p->value.longuint; + + p = gf_filter_get_info_str(group->seg_filter_src, "x-atsc", &pe); + if (p && p->value.string && !strcmp(p->value.string, "yes")) { + broadcast_flag = GF_TRUE; + } + + gf_dash_group_store_stats(ctx->dash, group->idx, bytes_per_sec, (u32) file_size, (u32) bytes_done, broadcast_flag); + + //we allow file abort, check the download + if (ctx->abort) + gf_dash_group_check_bandwidth(ctx->dash, group->idx); + + p = gf_filter_get_info(group->seg_filter_src, GF_PROP_PID_FILE_CACHED, &pe); + if (p && p->value.boolean) + group->stats_uploaded = GF_TRUE; + + gf_filter_release_property(pe); +} + +GF_Err dashdmx_process(GF_Filter *filter) +{ + u32 i, count; + GF_FilterPacket *pck; + GF_Err e; + u32 next_time_ms = 0; + GF_DASHDmxCtx *ctx = (GF_DASHDmxCtx*) gf_filter_get_udta(filter); + Bool check_eos = ctx->check_eos; + Bool has_pck = GF_FALSE; + + //reset group states and update stats + count = gf_dash_get_group_count(ctx->dash); + for (i=0; idash, i); + if (!group) continue; + group->nb_eos = 0; + if (group->eos_detected) check_eos = GF_TRUE; + } + + if (!ctx->mpd_pid) + return GF_EOS; + + //check MPD pid + pck = gf_filter_pid_get_packet(ctx->mpd_pid); + if (pck) { + gf_filter_pid_drop_packet(ctx->mpd_pid); + } + e = gf_dash_process(ctx->dash); + if (e == GF_IP_NETWORK_EMPTY) { + gf_filter_ask_rt_reschedule(filter, 100000); + return GF_OK; + } + if (e) + return e; + + next_time_ms = gf_dash_get_min_wait_ms(ctx->dash); + if (next_time_ms>1000) + next_time_ms=1000; + + //flush all media input + count = gf_filter_get_ipid_count(filter); + for (i=0; impd_pid) continue; + opid = gf_filter_pid_get_udta(ipid); + group = gf_filter_pid_get_udta(opid); + + if (!group) + continue; + + while (1) { + pck = gf_filter_pid_get_packet(ipid); + if (!group->is_playing) { + if (pck) { + gf_filter_pid_drop_packet(ipid); + continue; + } + break; + } + + if (!pck) { + if (gf_filter_pid_is_eos(ipid) || !gf_filter_pid_is_playing(opid) || group->force_seg_switch) { + group->nb_eos++; + + //wait until all our inputs are done + if (group->nb_eos == group->nb_pids) { + u32 j, nb_block = 0; + //check all pids in this group, postpone segment switch if blocking + for (j=0; jmpd_pid) continue; + agroup = gf_filter_pid_get_udta(an_opid); + if (!agroup || (agroup != group)) continue; + + if (gf_filter_pid_would_block(an_opid)) { + nb_block++; + } + } + if (nb_block == group->nb_pids) { + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASHDmx] End of segment for group %d but %d output pid(s) would block, postponing\n", nb_block, group->idx)); + break; + } + + //good to switch, cancel all end of stream signals on pids from this group and switch + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] End of segment for group %d, updating stats and switching segment\n", group->idx)); + for (j=0; jmpd_pid) continue; + agroup = gf_filter_pid_get_udta(an_opid); + if (!agroup || (agroup != group)) continue; + + if (gf_filter_pid_is_eos(an_ipid)) { + gf_filter_pid_clear_eos(an_ipid, GF_TRUE); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] Clearing EOS on pids from group %d\n", group->idx)); + } + } + dashdmx_update_group_stats(ctx, group); + group->stats_uploaded = GF_TRUE; + group->force_seg_switch = GF_FALSE; + dashdmx_switch_segment(ctx, group); + + gf_filter_prevent_blocking(filter, GF_FALSE); + if (group->eos_detected && !has_pck) check_eos = GF_TRUE; + } + } + else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASHDmx] No source packet group %d and not in end of stream\n", group->idx)); + } + if (group->in_error || group->seg_was_not_ready) { + dashdmx_switch_segment(ctx, group); + gf_filter_prevent_blocking(filter, GF_FALSE); + if (group->eos_detected && !has_pck) check_eos = GF_TRUE; + } + break; + } + has_pck = GF_TRUE; + check_eos = GF_FALSE; + dashdmx_forward_packet(ctx, pck, ipid, opid, group); + group->wait_for_pck = GF_FALSE; + dashdmx_update_group_stats(ctx, group); + } + } + + if (check_eos) { + Bool all_groups_done = GF_TRUE; + Bool groups_not_playing = GF_TRUE; + Bool is_in_last_period = gf_dash_in_last_period(ctx->dash, GF_TRUE); + + //not last period, check if we are done playing all groups due to stop requests + if (!is_in_last_period && !ctx->nb_playing) { + Bool groups_done=GF_TRUE; + for (i=0; impd_pid) continue; + opid = gf_filter_pid_get_udta(ipid); + group = gf_filter_pid_get_udta(opid); + if (!group) continue; + if (!group->is_playing && group->eos_detected) continue; + groups_done=GF_FALSE; + } + if (groups_done) + is_in_last_period = GF_TRUE; + } + + for (i=0; impd_pid) continue; + opid = gf_filter_pid_get_udta(ipid); + group = gf_filter_pid_get_udta(opid); + //reset in progress + if (!group) { + all_groups_done = GF_FALSE; + continue; + } + if (group->is_playing) + groups_not_playing = GF_FALSE; + + if (!group->eos_detected && group->is_playing) { + all_groups_done = GF_FALSE; + } else if (is_in_last_period) { + if (gf_filter_pid_is_eos(ipid) || group->eos_detected) + gf_filter_pid_set_eos(opid); + else + all_groups_done = GF_FALSE; + } + } + if (all_groups_done) { + if (is_in_last_period || groups_not_playing) + return GF_EOS; + if (!gf_dash_get_period_switch_status(ctx->dash)) { + for (i=0; idash, i); + if (!group) continue; + group->nb_eos = 0; + group->eos_detected = GF_FALSE; + } + + gf_dash_request_period_switch(ctx->dash); + } + } + } + if (gf_dash_is_in_setup(ctx->dash)) + gf_filter_post_process_task(filter); + else if (next_time_ms) { + gf_filter_ask_rt_reschedule(filter, 1000 * next_time_ms); + } + return GF_OK; +} + +static const char *dashdmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + char *d = (char *)data; + char *res_dash, *res_m3u, *res_smooth; + char last_c = d[size-1]; + d[size-1] = 0; + res_dash = strstr(data, "0) or in per-cent of timeshift buffer (if <0)", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(tile_mode), "tile adaptation mode\n"\ + "- none: bitrate is shared equaly accross all tiles\n"\ + "- rows: bitrate decreases for each row of tiles starting from the top, same rate for each tile on the row\n"\ + "- rrows: bitrate decreases for each row of tiles starting from the bottom, same rate for each tile on the row\n"\ + "- mrows: bitrate decreased for top and bottom rows only, same rate for each tile on the row\n"\ + "- cols: bitrate decreases for each columns of tiles starting from the left, same rate for each tile on the columns\n"\ + "- rcols: bitrate decreases for each columns of tiles starting from the right, same rate for each tile on the columns\n"\ + "- mcols: bitrate decreased for left and right columns only, same rate for each tile on the columns\n"\ + "- center: bitrate decreased for all tiles on the edge of the picture\n"\ + "- edges: bitrate decreased for all tiles on the center of the picture" + , GF_PROP_UINT, "none", "none|rows|rrows|mrows|cols|rcols|mcols|center|edges", GF_FS_ARG_HINT_EXPERT}, + { OFFS(tiles_rate), "indicate the amount of bandwidth to use at each quality level. The rate is recursively applied at each level, e.g. if 50%, Level1 gets 50%, level2 gets 25%, ... If 100, automatic rate allocation will be done by maximizing the quality in order of priority. If 0, bitstream will not be smoothed across tiles/qualities, and concurrency may happen between different media", GF_PROP_UINT, "100", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(delay40X), "delay in millisconds to wait between two 40X on the same segment", GF_PROP_UINT, "500", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(exp_threshold), "delay in millisconds to wait after the segment AvailabilityEndDate before considering the segment lost", GF_PROP_UINT, "100", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(switch_count), "indicate how many segments the client shall wait before switching up bandwidth. If 0, switch will happen as soon as the bandwidth is enough, but this is more prone to network variations", GF_PROP_UINT, "1", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(aggressive), "if enabled, switching algo targets the closest bandwidth fitting the available download rate. If no, switching algo targets the lowest bitrate representation that is above the currently played (eg does not try to switch to max bandwidth)", GF_PROP_BOOL, "no", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(debug_as), "play only the adaptation set indicated by its index in the MPD; if negative, all sets are used", GF_PROP_UINT, "-1", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(speedadapt), "enable adaptation based on playback speed", GF_PROP_BOOL, "no", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(noxlink), "disable xlink if period has both xlink and adaptation sets", GF_PROP_BOOL, "no", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(query), "set query string (without initial '?') to append to xlink of periods", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(split_as), "separate all qualities into different adaptation sets and stream all qualities", GF_PROP_BOOL, "no", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(noseek), "disable seeking of initial segment(s) in dynamic mode (useful when UTC clocks do not match)", GF_PROP_BOOL, "no", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(lowlat), "segment scheduling policy in low latency mode\n" + "- no: disable low latency\n" + "- strict: strict respect of AST offset in low latency\n" + "- early: allow fetching segments earlier than their AST in low latency when input demux is empty", GF_PROP_UINT, "early", "no|strict|early", GF_FS_ARG_HINT_EXPERT}, + {0} +}; + + + +static const GF_FilterCapability DASHDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "mpd|m3u8|3gm|ism"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "application/dash+xml|video/vnd.3gpp.mpd|audio/vnd.3gpp.mpd|video/vnd.mpeg.dash.mpd|audio/vnd.mpeg.dash.mpd|audio/mpegurl|video/mpegurl|application/vnd.ms-sstr+xml"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + {0}, + //accept any stream but files, framed + { .code=GF_PROP_PID_STREAM_TYPE, .val.type=GF_PROP_UINT, .val.value.uint=GF_STREAM_FILE, .flags=(GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_EXCLUDED|GF_CAPFLAG_LOADED_FILTER) }, + { .code=GF_PROP_PID_UNFRAMED, .val.type=GF_PROP_BOOL, .val.value.boolean=GF_TRUE, .flags=(GF_CAPFLAG_IN_BUNDLE|GF_CAPFLAG_INPUT|GF_CAPFLAG_EXCLUDED|GF_CAPFLAG_LOADED_FILTER) }, + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + + +GF_FilterRegister DASHDmxRegister = { + .name = "dashin", + GF_FS_SET_DESCRIPTION("MPEG-DASH and HLS client") + GF_FS_SET_HELP("This filter reads MPEG-DASH, HLS and MS Smooth (on demand only for now) manifests and produces media PIDs and frames.") + .private_size = sizeof(GF_DASHDmxCtx), + .initialize = dashdmx_initialize, + .finalize = dashdmx_finalize, + .args = DASHDmxArgs, + SETCAPS(DASHDmxCaps), + .flags = GF_FS_REG_REQUIRES_RESOLVER, + .configure_pid = dashdmx_configure_pid, + .process = dashdmx_process, + .process_event = dashdmx_process_event, + .probe_data = dashdmx_probe_data, + //we accept as many input pids as loaded by the session + .max_extra_pids = (u32) -1, +}; + + +#endif //GPAC_DISABLE_DASH_CLIENT + +const GF_FilterRegister *dashdmx_register(GF_FilterSession *session) +{ +#ifndef GPAC_DISABLE_DASH_CLIENT + return &DASHDmxRegister; +#else + return NULL; +#endif +} diff --git a/src/filters/dmx_gsf.c b/src/filters/dmx_gsf.c new file mode 100644 index 0000000..64f7dd7 --- /dev/null +++ b/src/filters/dmx_gsf.c @@ -0,0 +1,1290 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018-2019 + * All rights reserved + * + * This file is part of GPAC / GPAC stream serializer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include + + +typedef struct +{ + u32 offset; + u32 size; +} GSF_PacketFragment; + +typedef struct +{ + GF_FilterPacket *pck; + u8 *output; + u32 full_block_size, nb_bytes, recv_bytes; + Bool corrupted; + u16 frame_sn; + u8 pck_type; + u8 crypted; + + u32 nb_frags, nb_alloc_frags, nb_recv_frags; + Bool complete; + GSF_PacketFragment *frags; + +} GSF_Packet; + + +typedef struct +{ + GF_FilterPid *opid; + GF_List *packets; + + u32 idx; + u16 nb_pck; + u8 config_version; + + u16 last_frame_sn; +} GSF_Stream; + +typedef struct +{ + //opts + const char *magic; + GF_PropData key; + u32 pad, mq; + + + //only one output pid declared + GF_FilterPid *ipid; + + GF_List *streams; + + bin128 crypt_IV; + GF_Crypt *crypt; + u16 crypt_blocks, skip_blocks; + + u32 nb_playing; + Double start_range; + Bool is_file; + u64 file_pos, file_size; + GF_Fraction64 duration; + Bool initial_play_done; + + GF_BitStream *bs_r; + GF_BitStream *bs_pck; + + //signaling stream + GSF_Stream *signal_st; + + //where we store incoming packets + char *buffer; + u32 alloc_size, buf_size; + + u32 missing_bytes; + Bool tuned; + Bool tune_error; + Bool use_seq_num; + Bool wait_for_play; + + GF_List *pck_res; + Bool buffer_too_small; +} GSF_DemuxCtx; + + +typedef enum +{ + GFS_PCKTYPE_HDR=0, + GFS_PCKTYPE_PID_CONFIG, + GFS_PCKTYPE_PID_INFO_UPDATE, + GFS_PCKTYPE_PID_REMOVE, + GFS_PCKTYPE_PID_EOS, + GFS_PCKTYPE_PCK, + + GFS_PCKTYPE_UNDEF1, + GFS_PCKTYPE_UNDEF2, + GFS_PCKTYPE_UNDEF3, + GFS_PCKTYPE_UNDEF4, + GFS_PCKTYPE_UNDEF5, + GFS_PCKTYPE_UNDEF6, + GFS_PCKTYPE_UNDEF7, + GFS_PCKTYPE_UNDEF8, + GFS_PCKTYPE_UNDEF9, + GFS_PCKTYPE_UNDEF10, + + /*NO MORE PACKET TYPE AVAILABLE*/ + +} GSF_PacketType; + + + +GF_Err gsfdmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GSF_DemuxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + + while (gf_list_count(ctx->streams)) { + GSF_Stream *st = gf_list_pop_back(ctx->streams); + if (st->opid) + gf_filter_pid_remove(st->opid); + gf_free(st); + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + return GF_OK; +} + +static Bool gsfdmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_FilterEvent fevt; + GSF_DemuxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (ctx->nb_playing && (ctx->start_range == evt->play.start_range)) { + return GF_TRUE; + } + ctx->nb_playing++; + ctx->wait_for_play = GF_FALSE; + + if (! ctx->is_file) { + return GF_FALSE; + } +// safdmx_check_dur(ctx); + + ctx->start_range = evt->play.start_range; + ctx->file_pos = 0; + if (ctx->duration.num) { + ctx->file_pos = (u64) (ctx->file_size * ctx->start_range); + ctx->file_pos *= ctx->duration.den; + ctx->file_pos /= ctx->duration.num; + if (ctx->file_pos>ctx->file_size) return GF_TRUE; + } + + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!ctx->file_pos) + return GF_TRUE; + } + //post a seek to 0 - we would need to build an index of AUs to find the next place to seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = 0; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + ctx->nb_playing--; + //don't cancel event + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +static void gsfdmx_decrypt(GSF_DemuxCtx *ctx, char *data, u32 size) +{ +#ifndef GPAC_DISABLE_CRYPTO + u32 pos=0; + u32 clear_tail = size%16; + u32 bytes_crypted = size - clear_tail; + + if (!bytes_crypted) return; + + gf_crypt_set_IV(ctx->crypt, ctx->crypt_IV, 16); + if (ctx->crypt_blocks && ctx->skip_blocks) { + u32 pattern_length = 16 * (ctx->crypt_blocks + ctx->skip_blocks); + u32 cryp_len = 16 * ctx->crypt_blocks; + while (bytes_crypted) { + gf_crypt_decrypt(ctx->crypt, data+pos, bytes_crypted >= cryp_len ? cryp_len : bytes_crypted); + if (bytes_crypted >= pattern_length) { + pos += pattern_length; + bytes_crypted -= pattern_length; + } else { + bytes_crypted = 0; + } + } + } else { + gf_crypt_decrypt(ctx->crypt, data, bytes_crypted); + } +#endif +} + +static GFINLINE u32 gsfdmx_read_vlen(GF_BitStream *bs) +{ + if (!gf_bs_read_int(bs, 1)) + return gf_bs_read_int(bs, 7); + if (!gf_bs_read_int(bs, 1)) + return gf_bs_read_int(bs, 14); + if (!gf_bs_read_int(bs, 1)) + return gf_bs_read_int(bs, 21); + if (!gf_bs_read_int(bs, 1)) + return gf_bs_read_int(bs, 28); + + return (u32) gf_bs_read_long_int(bs, 36); +} + +static GF_Err gsfdmx_read_prop(GF_BitStream *bs, GF_PropertyValue *p) +{ + u32 len, len2, i; + + switch (p->type) { + case GF_PROP_SINT: + case GF_PROP_UINT: + case GF_PROP_PIXFMT: + case GF_PROP_PCMFMT: + p->value.uint = gsfdmx_read_vlen(bs); + break; + case GF_PROP_LSINT: + case GF_PROP_LUINT: + p->value.longuint = gf_bs_read_u64(bs); + break; + case GF_PROP_BOOL: + p->value.boolean = gf_bs_read_u8(bs) ? 1 : 0; + break; + case GF_PROP_FRACTION: + p->value.frac.num = gsfdmx_read_vlen(bs); + p->value.frac.den = gsfdmx_read_vlen(bs); + break; + case GF_PROP_FRACTION64: + p->value.lfrac.num = gf_bs_read_u64(bs); + p->value.lfrac.den = gf_bs_read_u64(bs); + break; + case GF_PROP_FLOAT: + p->value.fnumber = FLT2FIX( gf_bs_read_float(bs) ); + break; + case GF_PROP_DOUBLE: + p->value.number = gf_bs_read_double(bs); + break; + case GF_PROP_VEC2I: + p->value.vec2i.x = gsfdmx_read_vlen(bs); + p->value.vec2i.y = gsfdmx_read_vlen(bs); + break; + case GF_PROP_VEC2: + p->value.vec2.x = gf_bs_read_double(bs); + p->value.vec2.y = gf_bs_read_double(bs); + break; + case GF_PROP_VEC3I: + p->value.vec3i.x = gsfdmx_read_vlen(bs); + p->value.vec3i.y = gsfdmx_read_vlen(bs); + p->value.vec3i.z = gsfdmx_read_vlen(bs); + break; + case GF_PROP_VEC3: + p->value.vec3.x = gf_bs_read_double(bs); + p->value.vec3.y = gf_bs_read_double(bs); + p->value.vec3.z = gf_bs_read_double(bs); + break; + case GF_PROP_VEC4I: + p->value.vec4i.x = gsfdmx_read_vlen(bs); + p->value.vec4i.y = gsfdmx_read_vlen(bs); + p->value.vec4i.z = gsfdmx_read_vlen(bs); + p->value.vec4i.w = gsfdmx_read_vlen(bs); + break; + case GF_PROP_VEC4: + p->value.vec4.x = gf_bs_read_double(bs); + p->value.vec4.y = gf_bs_read_double(bs); + p->value.vec4.z = gf_bs_read_double(bs); + p->value.vec4.w = gf_bs_read_double(bs); + break; + case GF_PROP_STRING: + case GF_PROP_STRING_NO_COPY: + case GF_PROP_NAME: + p->type = GF_PROP_STRING_NO_COPY; + len = gsfdmx_read_vlen(bs); + p->value.string = gf_malloc(sizeof(char)*(len+1)); + gf_bs_read_data(bs, p->value.string, len); + p->value.string[len]=0; + break; + + case GF_PROP_DATA: + case GF_PROP_DATA_NO_COPY: + case GF_PROP_CONST_DATA: + p->type = GF_PROP_DATA_NO_COPY; + p->value.data.size = gsfdmx_read_vlen(bs); + p->value.data.ptr = gf_malloc(sizeof(char) * p->value.data.size); + gf_bs_read_data(bs, p->value.data.ptr, p->value.data.size); + break; + + case GF_PROP_STRING_LIST: + p->value.string_list = gf_list_new(); + len2 = gsfdmx_read_vlen(bs); + for (i=0; ivalue.string_list, str); + } + break; + + case GF_PROP_UINT_LIST: + p->value.uint_list.nb_items = len = gsfdmx_read_vlen(bs); + p->value.uint_list.vals = gf_malloc(sizeof(u32)*len); + for (i=0; ivalue.uint_list.vals[i] = gsfdmx_read_vlen(bs); + } + break; + case GF_PROP_POINTER: + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] pointer property found in serialized stream, illegal\n")); + return GF_NON_COMPLIANT_BITSTREAM; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] Cannot deserialize property of unknown type\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + return GF_OK; +} + +static GSF_Stream *gsfdmx_get_stream(GF_Filter *filter, GSF_DemuxCtx *ctx, u32 idx, u32 pkt_type) +{ + GSF_Stream *gst; + u32 i, count; + + count = gf_list_count(ctx->streams); + for (i=0; istreams, i); + if (gst->idx == idx) return gst; + } + + if ((pkt_type==GFS_PCKTYPE_PID_CONFIG) || (pkt_type==GFS_PCKTYPE_PID_INFO_UPDATE) ) { + GF_SAFEALLOC(gst, GSF_Stream); + if (!gst) return NULL; + gst->packets = gf_list_new(); + gst->idx = idx; + gf_list_add(ctx->streams, gst); + gst->opid = gf_filter_pid_new(filter); + return gst; + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[GSFDemux] no stream found for idx %d\n", idx)); + return NULL; +} +static GF_Err gsfdmx_parse_pid_info(GF_Filter *filter, GSF_DemuxCtx *ctx, GSF_Stream *gst, GSF_Packet *pck, Bool is_info_update) +{ + GF_Err e; + u32 nb_4cc_props=0; + u32 nb_str_props=0; + u32 i; + u8 cfg_version; + GF_BitStream *bs=NULL; + + if (pck->crypted) { + gsfdmx_decrypt(ctx, pck->output, pck->full_block_size); + } + e = gf_bs_reassign_buffer(ctx->bs_pck, pck->output, pck->full_block_size); + if (e) return e; + bs = ctx->bs_pck; + + cfg_version = gf_bs_read_u8(bs); + if ((gst->config_version == 1 + cfg_version) && !is_info_update) + return GF_OK; + gst->config_version = 1 + cfg_version; + + nb_4cc_props = gsfdmx_read_vlen(bs); + nb_str_props = gsfdmx_read_vlen(bs); + + for (i=0; iopid, p4cc, &p); + else gf_filter_pid_set_property(gst->opid, p4cc, &p); + } + for (i=0; iopid, pname, &p); + else gf_filter_pid_set_property_dyn(gst->opid, pname, &p); + gf_free(pname); + } + return GF_OK; +} + +static GF_Err gsfdmx_tune(GF_Filter *filter, GSF_DemuxCtx *ctx, char *pck_data, u32 pck_size, Bool is_crypted) +{ + u32 len; + GF_BitStream *bs; + GF_Err e = gf_bs_reassign_buffer(ctx->bs_pck, pck_data, pck_size); + if (e) return e; + bs = ctx->bs_pck; + + u32 sig = gf_bs_read_u32(bs); + if (sig != GF_4CC('G','S','5','F') ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] Wrong GPAC serialized signature %s, expecting \"GS5F\"\n", gf_4cc_to_str(sig) )); + ctx->tune_error = GF_TRUE; + return GF_NOT_SUPPORTED; + } + sig = gf_bs_read_u8(bs); + if (sig != 1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] Wrong GPAC serialized version %d\n", sig )); + ctx->tune_error = GF_TRUE; + return GF_NOT_SUPPORTED; + } + if (is_crypted) { +#ifdef GPAC_DISABLE_CRYPTO + return GF_NOT_SUPPORTED; +#else + if (pck_size<25) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] Wrong serialized header size %d, should be at least 25 bytes for encrypted streams\n", pck_size )); + ctx->tune_error = GF_TRUE; + return GF_NOT_SUPPORTED; + } + gf_bs_read_data(bs, ctx->crypt_IV, 16); + ctx->crypt_blocks = gf_bs_read_u16(bs); + ctx->skip_blocks = gf_bs_read_u16(bs); + + if (!ctx->key.size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] stream is encrypted but no key provided\n" )); + ctx->tune_error = GF_TRUE; + return GF_BAD_PARAM; + } + ctx->crypt = gf_crypt_open(GF_AES_128, GF_CBC); + if (!ctx->crypt) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] failed to create decryptor\n" )); + ctx->tune_error = GF_TRUE; + return GF_IO_ERR; + } + gf_crypt_init(ctx->crypt, ctx->key.ptr, ctx->crypt_IV); + + gsfdmx_decrypt(ctx, pck_data+25, pck_size - 25); +#endif + } + ctx->use_seq_num = gf_bs_read_int(bs, 1); + gf_bs_read_int(bs, 7); + + //header:magic + len = gsfdmx_read_vlen(bs); + if (len) { + Bool wrongm=GF_FALSE; + char *magic = gf_malloc(sizeof(char)*len); + gf_bs_read_data(bs, magic, len); + + if (ctx->magic && !memcmp(ctx->magic, magic, len)) wrongm = GF_TRUE; + if (!wrongm) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] wrong magic word in stream config\n" )); + ctx->tune_error = GF_TRUE; + gf_free(magic); + return GF_NOT_SUPPORTED; + } + GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[GSFDemux] tuning in stream, magic %s\n", magic)); + gf_free(magic); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[GSFDemux] tuning in stream\n")); + } + + ctx->tuned = GF_TRUE; + return GF_OK; +} + +static GFINLINE GSF_Packet *gsfdmx_get_packet(GSF_DemuxCtx *ctx, GSF_Stream *gst, Bool pck_frag, s32 frame_sn, u8 pkt_type, u32 frame_size) +{ + u32 i=0, count; + GSF_Packet *gpck = NULL; + + if ((frame_sn>=0) || pck_frag) { + while (( gpck = gf_list_enum(gst->packets, &i))) { + if (gpck->frame_sn == frame_sn) { + assert(gpck->pck_type == pkt_type); + assert(gpck->full_block_size == frame_size); + + break; + } + gpck = NULL; + } + } + if (!gpck) { + Bool inserted = GF_FALSE; + gpck = gf_list_pop_back(ctx->pck_res); + if (!gpck) { + GF_SAFEALLOC(gpck, GSF_Packet); + if (!gpck) return NULL; + gpck->nb_alloc_frags = 10; + gpck->frags = gf_malloc(sizeof(GSF_PacketFragment) * gpck->nb_alloc_frags); + } + gpck->frame_sn = frame_sn; + gpck->pck_type = pkt_type; + gpck->full_block_size = frame_size; + gpck->pck = gf_filter_pck_new_alloc(gst->opid, frame_size, &gpck->output); + memset(gpck->output, (u8) ctx->pad, sizeof(char) * gpck->full_block_size); + + count = gf_list_count(gst->packets); + for (i=0; ipackets, i); + + if ( ( (apck->frame_sn > frame_sn) && (apck->frame_sn - frame_sn <= 32768) ) + || ( (apck->frame_sn < frame_sn ) && (frame_sn - apck->frame_sn > 32768) ) + ) { + inserted = GF_TRUE; + gf_list_insert(gst->packets, gpck, i); + } + } + if (!inserted) gf_list_add(gst->packets, gpck); + } + return gpck; +} + +static void gsfdmx_packet_append_frag(GSF_Packet *pck, u32 size, u32 offset) +{ + u32 i; + Bool inserted = GF_FALSE; + pck->recv_bytes += size; + pck->nb_recv_frags++; + + assert(offset + size <= pck->full_block_size); + + for (i=0; inb_frags; i++) { + if ((pck->frags[i].offset <= offset) && (pck->frags[i].offset + pck->frags[i].size >= offset + size) ) { + return; + } + + //insert fragment + if (pck->frags[i].offset > offset) { + if (pck->nb_frags==pck->nb_alloc_frags) { + pck->nb_alloc_frags *= 2; + pck->frags = gf_realloc(pck->frags, sizeof(GSF_PacketFragment)*pck->nb_alloc_frags); + } + memmove(&pck->frags[i+1], &pck->frags[i], sizeof(GSF_PacketFragment) * (pck->nb_frags - i) ); + pck->frags[i].offset = offset; + pck->frags[i].size = size; + pck->nb_bytes += size; + pck->nb_frags++; + inserted = GF_TRUE; + break; + } + //expand fragment + if (pck->frags[i].offset + pck->frags[i].size == offset) { + pck->frags[i].size += size; + pck->nb_bytes += size; + inserted = GF_TRUE; + break; + } + } + + if (!inserted) { + if (pck->nb_frags==pck->nb_alloc_frags) { + pck->nb_alloc_frags *= 2; + pck->frags = gf_realloc(pck->frags, sizeof(GSF_PacketFragment)*pck->nb_alloc_frags); + } + pck->frags[pck->nb_frags].offset = offset; + pck->frags[pck->nb_frags].size = size; + pck->nb_frags++; + pck->nb_bytes += size; + } + if (pck->nb_bytes >= pck->full_block_size) { + if (pck->nb_bytes>pck->full_block_size) pck->corrupted=GF_TRUE; + pck->complete = GF_TRUE; + } +} + +GF_Err gsfdmx_read_data_pck(GSF_DemuxCtx *ctx, GSF_Stream *gst, GSF_Packet *gpck, u32 pck_len, Bool full_pck, GF_BitStream *bs) +{ + u64 dts=GF_FILTER_NO_TS, cts=GF_FILTER_NO_TS, bo=GF_FILTER_NO_BO; + u32 copy_size, consummed, dur, dep_flags=0, tsmodebits, durmodebits, spos; + s16 roll=0; + u8 carv=0; + + //not yet setup + if (!gst || !gpck) return GF_NOT_FOUND; + + +// gsfdmx_flush_dst_pck(gst, GF_TRUE); + + spos = (u32) gf_bs_get_position(bs); + + //flags first byte: 1(has_dts) 1(has_cts) 1(has_dur) 1(cts_diff_neg) 2(ts_mode) 2(ts_diff_mode) + //flags second byte: 3(sap) 2(encrypted) 1(has_sample_deps) 1(has builtin props) 1(has_ext) + //if (ext) { + // flags third byte: 1(has_byteoffset) 1(corrupted) 1(seek) 1(has_carousel) 2(ilaced) 2(cktype) + // flags fourth byte: 1(au start) 1(au end) 1(has props) reserved(5) + //} + //if has_dts, dts on tsmodebits + //if has_cts, has_dts ? cts_diff on ts_diff_mode : cts on tsmodebits + //if durmode>0, dur on ts_diff_mode + //if sap==4, roll on signed 16 bits + //if (has sample deps) sample_deps_flags on 8 bits + //if has_carousel, carrousel version on 8 bits + //if has_byteoffset, byte offset on 64 bits + //if (has builtin) vlen nb builtin_props then props[builtin_props] + //if (has props) vlen nb_str_props then props[nb_str_props] + + //first flags byte + u8 has_dts = gf_bs_read_int(bs, 1); + u8 has_cts = gf_bs_read_int(bs, 1); + u8 has_dur = gf_bs_read_int(bs, 1); + u8 cts_diff_neg = gf_bs_read_int(bs, 1); + u8 tsmode = gf_bs_read_int(bs, 2); + u8 tsdiffmode = gf_bs_read_int(bs, 2); + + u8 sap = gf_bs_read_int(bs, 3); + u8 crypt = gf_bs_read_int(bs, 2); + u8 has_dep = gf_bs_read_int(bs, 1); + u8 has_4cc_props = gf_bs_read_int(bs, 1); + u8 has_ext = gf_bs_read_int(bs, 1); + + //default field values without ext + u8 has_bo = 0, corr = 0, seek = 0, has_carv = 0, interl = 0, cktype = 0, has_str_props = 0; + u8 is_start = 1, is_end = 1; + if (has_ext) { + has_bo = gf_bs_read_int(bs, 1); + corr = gf_bs_read_int(bs, 1); + seek = gf_bs_read_int(bs, 1); + has_carv = gf_bs_read_int(bs, 1); + interl = gf_bs_read_int(bs, 2); + cktype = gf_bs_read_int(bs, 2); + + is_start = gf_bs_read_int(bs, 1); + is_end = gf_bs_read_int(bs, 1); + has_str_props = gf_bs_read_int(bs, 1); + gf_bs_read_int(bs, 5); //reserved + } + + if (tsmode==3) tsmodebits = 64; + else if (tsmode==2) tsmodebits = 32; + else if (tsmode==1) tsmodebits = 24; + else tsmodebits = 16; + + if (tsdiffmode==3) durmodebits = 32; + else if (tsdiffmode==2) durmodebits = 24; + else if (tsdiffmode==1) durmodebits = 16; + else durmodebits = 8; + + if (has_dts) { + if (tsmode==3) dts = gf_bs_read_long_int(bs, tsmodebits); + else dts = gf_bs_read_int(bs, tsmodebits); + } + if (has_cts) { + if (has_dts) { + s32 cts_diff = gf_bs_read_int(bs, durmodebits); + if (cts_diff_neg) { + cts = dts - cts_diff; + } else { + cts = dts + cts_diff; + } + } else { + if (tsmode==3) cts = gf_bs_read_long_int(bs, tsmodebits); + else cts = gf_bs_read_int(bs, tsmodebits); + } + } + + if (has_dur) { + dur = gf_bs_read_int(bs, durmodebits); + } + + if ((sap==GF_FILTER_SAP_4) || (sap==GF_FILTER_SAP_4_PROL)) { + roll = gf_bs_read_u16(bs); + } + if (has_dep) + dep_flags = gf_bs_read_u8(bs); + + if (has_carv) { + carv = gf_bs_read_u8(bs); + } + if (has_bo) { + bo = gf_bs_read_u64(bs); + } + + if (has_4cc_props) { + u32 nb_4cc = gsfdmx_read_vlen(bs); + while (nb_4cc) { + GF_Err e; + GF_PropertyValue p; + memset(&p, 0, sizeof(GF_PropertyValue)); + u32 p4cc = gf_bs_read_u32(bs); + p.type = gf_props_4cc_get_type(p4cc); + if (p.type==GF_PROP_FORBIDEN) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] Wrong GPAC property type for property 4CC %s\n", gf_4cc_to_str(p4cc) )); + gf_filter_pck_discard(gpck->pck); + gpck->pck = NULL; + return GF_NON_COMPLIANT_BITSTREAM; + } + nb_4cc--; + + e = gsfdmx_read_prop(bs, &p); + if (e) { + gf_filter_pck_discard(gpck->pck); + gpck->pck = NULL; + return e; + } + gf_filter_pck_set_property(gpck->pck, p4cc, &p); + } + } + if (has_str_props) { + u32 nb_props = gsfdmx_read_vlen(bs); + while (nb_props) { + GF_Err e; + GF_PropertyValue p; + char *pname; + memset(&p, 0, sizeof(GF_PropertyValue)); + u32 len = gsfdmx_read_vlen(bs); + pname = gf_malloc(sizeof(char)*(len+1) ); + gf_bs_read_data(bs, pname, len); + pname[len] = 0; + p.type = gf_bs_read_u8(bs); + if (p.type==GF_PROP_FORBIDEN) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] Wrong GPAC property type for property %s\n", pname )); + gf_free(pname); + gf_filter_pck_discard(gpck->pck); + gpck->pck = NULL; + return GF_NON_COMPLIANT_BITSTREAM; + } + nb_props--; + + e = gsfdmx_read_prop(bs, &p); + if (e) { + gf_filter_pck_discard(gpck->pck); + gf_free(pname); + gpck->pck = NULL; + return e; + } + gf_filter_pck_set_property_dyn(gpck->pck, pname, &p); + gf_free(pname); + if ((p.type==GF_PROP_UINT_LIST) && p.value.uint_list.vals) + gf_free(p.value.uint_list.vals); + } + } + + consummed = (u32) gf_bs_get_position(bs) - spos; + pck_len -= consummed; + if (full_pck) { + assert(gpck->full_block_size > consummed); + gpck->full_block_size -= consummed; + assert(gpck->full_block_size == pck_len); + gf_filter_pck_truncate(gpck->pck, gpck->full_block_size); + } + copy_size = gpck->full_block_size; + if (copy_size > pck_len) + copy_size = pck_len; + gf_bs_read_data(bs, gpck->output, copy_size); + gsfdmx_packet_append_frag(gpck, copy_size, 0); + + gf_filter_pck_set_framing(gpck->pck, is_start, is_end); + if (has_dts) gf_filter_pck_set_dts(gpck->pck, dts); + if (has_cts) gf_filter_pck_set_cts(gpck->pck, cts); + if (has_dur) gf_filter_pck_set_duration(gpck->pck, dur); + if (has_bo) gf_filter_pck_set_byte_offset(gpck->pck, bo); + if (corr) gf_filter_pck_set_corrupted(gpck->pck, corr); + if (interl) gf_filter_pck_set_interlaced(gpck->pck, interl); + if (has_carv) gf_filter_pck_set_carousel_version(gpck->pck, carv); + if (has_dep) gf_filter_pck_set_dependency_flags(gpck->pck, dep_flags); + if (cktype) gf_filter_pck_set_clock_type(gpck->pck, cktype); + if (seek) gf_filter_pck_set_seek_flag(gpck->pck, seek); + if (crypt) gf_filter_pck_set_crypt_flags(gpck->pck, crypt); + if (sap) gf_filter_pck_set_sap(gpck->pck, sap); + if ((sap==GF_FILTER_SAP_4) || (sap==GF_FILTER_SAP_4_PROL)) + gf_filter_pck_set_roll_info(gpck->pck, roll); + return GF_OK; +} + +#ifndef GPAC_DISABLE_LOG +static const char *gsfdmx_pck_name(u32 pck_type) +{ + switch (pck_type) { + case GFS_PCKTYPE_HDR: return "TUNE"; + case GFS_PCKTYPE_PID_CONFIG: return "CONFIG"; + case GFS_PCKTYPE_PID_INFO_UPDATE: return "INFO"; + case GFS_PCKTYPE_PID_REMOVE: return "PIDREM"; + case GFS_PCKTYPE_PID_EOS: return "PIDEOS"; + case GFS_PCKTYPE_PCK: return "PCK"; + default: return "FORBIDDEN"; + } +} +#endif + +static GFINLINE void gsfdmx_pck_reset(GSF_Packet *pck) +{ + u32 alloc_frags = pck->nb_alloc_frags; + GSF_PacketFragment *frags = pck->frags; + memset(pck, 0, sizeof(GSF_Packet)); + pck->nb_alloc_frags = alloc_frags; + pck->frags = frags; +} + +static void gsfdmx_stream_del(GSF_DemuxCtx *ctx, GSF_Stream *gst, Bool is_flush) +{ + while (gf_list_count(gst->packets)) { + GSF_Packet *gpck = gf_list_pop_front(gst->packets); + + if (gpck->pck) { + if (is_flush && (gpck->pck_type == GFS_PCKTYPE_PCK)) { + gf_filter_pck_set_corrupted(gpck->pck, GF_TRUE); + gf_filter_pck_send(gpck->pck); + } else { + gf_filter_pck_discard(gpck->pck); + } + } + gsfdmx_pck_reset(gpck); + gf_list_add(ctx->pck_res, gpck); + } + if (is_flush) + gf_filter_pid_remove(gst->opid); + + gf_list_del(gst->packets); + gf_list_del_item(ctx->streams, gst); + gf_free(gst); +} + +static GF_Err gsfdmx_process_packets(GF_Filter *filter, GSF_DemuxCtx *ctx, GSF_Stream *gst) +{ + GSF_Packet *gpck; + GF_Err e; + + if (ctx->tune_error) { + return GF_SERVICE_ERROR; + } + while (1) { + gpck = gf_list_get(gst->packets, 0); + + if (!gpck) + return GF_OK; + + if (!gpck->complete) { + u32 pck_count = gf_list_count(gst->packets); + if (ctx->mq && (pck_count > ctx->mq + 1)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[GSFDemux] packets queue too large (%d vs %d max), processing incomplete packet size %d receive %d\n", pck_count, ctx->mq, gpck->full_block_size, gpck->nb_bytes)); + gpck->corrupted = 1; + } else { + return GF_OK; + } + } + assert(gpck->pck); + if (ctx->use_seq_num) { + u32 frame_sn; + if (!gst->last_frame_sn) frame_sn = gpck->frame_sn; + else { + frame_sn = gst->last_frame_sn; + if (frame_sn>0xFFFF) frame_sn=0; + } + if (gpck->frame_sn != frame_sn) { + u32 pck_count = gf_list_count(gst->packets); + if (ctx->mq && (pck_count > 2*ctx->mq + 1)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[GSFDemux] packets queue too large (%d vs %d max), missed one full packet\n", pck_count, 2*ctx->mq)); + } else { + return GF_OK; + } + } + gst->last_frame_sn = gpck->frame_sn+1; + } + + switch (gpck->pck_type) { + case GFS_PCKTYPE_PID_CONFIG: + case GFS_PCKTYPE_PID_INFO_UPDATE: + if (!gpck->corrupted) + e = gsfdmx_parse_pid_info(filter, ctx, gst, gpck, (gpck->pck_type==GFS_PCKTYPE_PID_INFO_UPDATE) ? GF_TRUE : GF_FALSE); + else + e = GF_CORRUPTED_DATA; + if (gpck->pck) gf_filter_pck_discard(gpck->pck); + break; + case GFS_PCKTYPE_PID_REMOVE: + if (gpck->pck) gf_filter_pck_discard(gpck->pck); + gsfdmx_stream_del(ctx, gst, GF_TRUE); + return GF_OK; + + case GFS_PCKTYPE_PID_EOS: + if (gpck->pck) gf_filter_pck_discard(gpck->pck); + gf_filter_pid_set_eos(gst->opid); + e = GF_EOS; + break; + case GFS_PCKTYPE_PCK: + if (gpck->corrupted) gf_filter_pck_set_corrupted(gpck->pck, GF_TRUE); + e = gf_filter_pck_send(gpck->pck); + break; + default: + e = GF_OK; + break; + } + gf_list_rem(gst->packets, 0); + gsfdmx_pck_reset(gpck); + gf_list_add(ctx->pck_res, gpck); + if (e>GF_OK) e = GF_OK; + if (e) return e; + } + return GF_OK; +} + +static GF_Err gsfdmx_demux(GF_Filter *filter, GSF_DemuxCtx *ctx, char *data, u32 data_size) +{ + u32 last_pck_end=0; + + //always reset input buffer if not tuned - since in reliable (pipe/file/...) this is the first packet and it is less than 40 bytes at max whe should be fine + if (!ctx->tuned) + ctx->buf_size = 0; + + if (ctx->alloc_size < ctx->buf_size + data_size) { + ctx->buffer = (char*)gf_realloc(ctx->buffer, sizeof(char)*(ctx->buf_size + data_size) ); + ctx->alloc_size = ctx->buf_size + data_size; + } + + memcpy(ctx->buffer + ctx->buf_size, data, sizeof(char)*data_size); + ctx->buf_size += data_size; + + gf_bs_reassign_buffer(ctx->bs_r, ctx->buffer, ctx->buf_size); + while (gf_bs_available(ctx->bs_r) > 4) { //1 byte header + 3 vlen field at least 1 bytes + GF_Err e = GF_OK; + u32 pck_len, block_size, block_offset; +#ifndef GPAC_DISABLE_LOG + u32 hdr_pos = (u32) gf_bs_get_position(ctx->bs_r); +#endif + /*Bool reserved =*/ gf_bs_read_int(ctx->bs_r, 1); + u32 frag_flags = gf_bs_read_int(ctx->bs_r, 2); + Bool is_crypted = gf_bs_read_int(ctx->bs_r, 1); + u32 pck_type = gf_bs_read_int(ctx->bs_r, 4); +#ifndef GPAC_DISABLE_LOG + u16 sn = 0; +#endif + u32 st_idx; + Bool needs_agg = GF_FALSE; + u16 frame_sn = 0; + Bool has_sn = (pck_type==GFS_PCKTYPE_HDR) ? GF_FALSE : ctx->use_seq_num; + + Bool full_pck = (frag_flags==0) ? GF_TRUE : GF_FALSE; + Bool pck_frag = (frag_flags>=2) ? GF_TRUE : GF_FALSE; + + //reset buffer too small flag, and blindly parse the following vlen fields + ctx->buffer_too_small = GF_FALSE; + + st_idx = gsfdmx_read_vlen(ctx->bs_r); + if (has_sn) { + frame_sn = gf_bs_read_u16(ctx->bs_r); + } + + block_size = block_offset = 0; + if (!full_pck) { + block_size = gsfdmx_read_vlen(ctx->bs_r); + if (pck_frag) block_offset = gsfdmx_read_vlen(ctx->bs_r); + } + + pck_len = gsfdmx_read_vlen(ctx->bs_r); + if (pck_len > gf_bs_available(ctx->bs_r)) { + break; + } + //buffer was not big enough to contain all the vlen fields, we need more data + if (ctx->buffer_too_small) + break; + + if (full_pck) { + block_size = pck_len; + block_offset = 0; + } + + if ( (pck_type==GFS_PCKTYPE_PCK) && !ctx->nb_playing && ctx->tuned && gf_list_count(ctx->streams) ) { + ctx->wait_for_play = GF_TRUE; + break; + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[GSFDemux] found %s on stream %d type %s cryped %d sn %d size %d block_offset %d, hdr size %d at position %d\n", + (pck_len==block_size) ? "full packet" : (pck_frag ? "packet fragment" : "packet start"), + st_idx, + gsfdmx_pck_name(pck_type), + is_crypted, sn, pck_len, block_offset, gf_bs_get_position(ctx->bs_r)-hdr_pos, hdr_pos)); + + e = GF_OK; + + if ((pck_type != GFS_PCKTYPE_PCK) && (pck_frag || (pck_len < block_size))) + needs_agg = GF_TRUE; + + //tunein, we don't care about the seq num (for now, might chenge if we want key roll or other order-dependent features) + if (!st_idx) { + if (ctx->tuned) { + } else if (needs_agg) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] tune-in packet shall not be fragmented\n")); + e = GF_NON_COMPLIANT_BITSTREAM; + } else { + u32 pos = (u32) gf_bs_get_position(ctx->bs_r); + e = gsfdmx_tune(filter, ctx, ctx->buffer + pos, pck_len, is_crypted); + } + } + //stream signaling or packet + else { + u32 cur_pos = (u32) gf_bs_get_position(ctx->bs_r); + + GSF_Stream *gst = gsfdmx_get_stream(filter, ctx, st_idx, pck_type); + if (!gst) { + e = GF_OK; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[GSFDemux] cannot find stream idx %d\n", st_idx)); + } else { + GSF_Packet *gpck = gsfdmx_get_packet(ctx, gst, pck_frag, frame_sn, pck_type, block_size); + + //aggregate data + if (!gpck) { + e = GF_OUT_OF_MEM; + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] cannot allocate packet\n")); + } else if (!gpck->pck) { + e = GF_CORRUPTED_DATA; + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] lost first packet in frame, cannot reaggregate fragment\n")); + } else { + Bool append = GF_TRUE; + + //packet: decrypt on per-packet base, and decode if not first fragment + if (pck_type==GFS_PCKTYPE_PCK) { + if (is_crypted) { + gsfdmx_decrypt(ctx, ctx->buffer + cur_pos, pck_len); + } + if (!pck_frag) { + gf_bs_reassign_buffer(ctx->bs_pck, ctx->buffer + cur_pos, pck_len); + e = gsfdmx_read_data_pck(ctx, gst, gpck, pck_len, full_pck, ctx->bs_pck); + append = GF_FALSE; + } + } else { + //otherwise decryption will happen upon decoding the complete packet + if (is_crypted) gpck->crypted = 1; + } + + if (append) { + if (block_offset + pck_len > gpck->full_block_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] packet fragment out of bounds of current frame (offset %d size %d max size %d)\n", block_offset, pck_len, gpck->full_block_size)); + e = GF_NON_COMPLIANT_BITSTREAM; + } else { + //append fragment + memcpy(gpck->output + block_offset, ctx->buffer + cur_pos, pck_len); + + gsfdmx_packet_append_frag(gpck, pck_len, block_offset); + } + } + if (!e) + e = gsfdmx_process_packets(filter, ctx, gst); + } + } + } + + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFDemux] error decoding packet: %s\n", gf_error_to_string(e) )); + if (ctx->tune_error) return e; + } + gf_bs_skip_bytes(ctx->bs_r, pck_len); + last_pck_end = (u32) gf_bs_get_position(ctx->bs_r); + } + + if (last_pck_end) { + assert(ctx->buf_size>=last_pck_end); + memmove(ctx->buffer, ctx->buffer+last_pck_end, sizeof(char) * (ctx->buf_size-last_pck_end)); + ctx->buf_size -= last_pck_end; + } + return GF_OK; +} + +GF_Err gsfdmx_process(GF_Filter *filter) +{ + GF_Err e; + GSF_DemuxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + GSF_Stream *st; + u32 i=0, pkt_size; + const char *data; + u32 would_block = 0; + Bool is_eos = GF_FALSE; + + if (ctx->wait_for_play) return GF_OK; + if (ctx->tune_error) return GF_SERVICE_ERROR; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) is_eos = GF_TRUE; + else return GF_OK; + } + + //check if all the streams are in block state, if so return. + //we need to check for all output since one pid could still be buffering + while ((st = gf_list_enum(ctx->streams, &i))) { + if (st->opid) { + if (is_eos) { + gf_filter_pid_set_eos(st->opid); + } else if (gf_filter_pid_would_block(st->opid)) { + would_block++; + } + } + } + if (is_eos) return GF_EOS; + + if (would_block && (would_block+1==i)) + return GF_OK; + + data = gf_filter_pck_get_data(pck, &pkt_size); + e = gsfdmx_demux(filter, ctx, (char *) data, pkt_size); + gf_filter_pid_drop_packet(ctx->ipid); + if (ctx->tune_error) + gf_filter_pid_set_discard(ctx->ipid, GF_TRUE); + + return e; +} + +static const char *gsfdmx_probe_data(const u8 *data, u32 data_size, GF_FilterProbeScore *score) +{ + u32 avail = data_size; + if (data_size < 10) return NULL; + char *buf = (char *) data; + while (buf) { + char *start_sig = memchr(buf, 'G', avail); + if (!start_sig) return NULL; + //signature found and version is 1 + if (!strncmp(start_sig, "GS5F", 4) && (start_sig[4] == 1)) { + *score = GF_FPROBE_SUPPORTED; + return "application/x-gpac-sf"; + } + buf = start_sig+1; + avail = data_size - (u32) ( buf - (char *) data); + } + return NULL; +} + +static void gsfdmx_not_enough_bytes(void *par) +{ + GSF_DemuxCtx *ctx = (GSF_DemuxCtx *)par; + if (ctx) + ctx->buffer_too_small = GF_TRUE; +} + +static GF_Err gsfdmx_initialize(GF_Filter *filter) +{ + GSF_DemuxCtx *ctx = gf_filter_get_udta(filter); + ctx->streams = gf_list_new(); + if (!ctx->streams) return GF_OUT_OF_MEM; + ctx->bs_r = gf_bs_new((char *) ctx, 1, GF_BITSTREAM_READ); + gf_bs_set_eos_callback(ctx->bs_r, gsfdmx_not_enough_bytes, ctx); + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) + gsfdmx_not_enough_bytes(NULL); +#endif + + ctx->bs_pck = gf_bs_new((char *) ctx, 1, GF_BITSTREAM_READ); + ctx->pck_res = gf_list_new(); + return GF_OK; +} + +static void gsfdmx_finalize(GF_Filter *filter) +{ + GSF_DemuxCtx *ctx = gf_filter_get_udta(filter); + while (gf_list_count(ctx->streams)) { + GSF_Stream *gst = gf_list_pop_back(ctx->streams); + gsfdmx_stream_del(ctx, gst, GF_FALSE); + } + gf_list_del(ctx->streams); + + while (gf_list_count(ctx->pck_res)) { + GSF_Packet *gsp = gf_list_pop_back(ctx->pck_res); + if (gsp->frags) gf_free(gsp->frags); + gf_free(gsp); + } + gf_list_del(ctx->pck_res); + +#ifndef GPAC_DISABLE_CRYPTO + if (ctx->crypt) gf_crypt_close(ctx->crypt); +#endif + if (ctx->buffer) gf_free(ctx->buffer); + if (ctx->bs_r) gf_bs_del(ctx->bs_r); + if (ctx->bs_pck) gf_bs_del(ctx->bs_pck); +} + +static const GF_FilterCapability GSFDemuxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "gsf"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "application/x-gpac-sf"), + //we deliver more than these two but this make the filter chain loading stop until we declare a pid + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), +}; + + + +#define OFFS(_n) #_n, offsetof(GSF_DemuxCtx, _n) +static const GF_FilterArgs GSFDemuxArgs[] = +{ +#ifndef GPAC_DISABLE_CRYPTO + { OFFS(key), "key for decrypting packets", GF_PROP_DATA, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, +#endif + { OFFS(magic), "magic string to check in setup packet", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(mq), "set max packet queue length for loss detection. 0 will flush incomplete packet when a new one starts", GF_PROP_UINT, "4", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(pad), "byte value used to pad lost packets", GF_PROP_UINT, "0", "0-255", GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + + +GF_FilterRegister GSFDemuxRegister = { + .name = "gsfdmx", + GF_FS_SET_DESCRIPTION("GSF Demuxer") +#ifndef GPAC_DISABLE_DOC + .help = "This filter provides GSF (__GPAC Super/Simple/Serialized/Stream/State Format__) demultiplexing.\n" + "It deserializes the stream states (config/reconfig/info update/remove/eos) and packets of input PIDs.\n" + "This allows either reading a session saved to file, or receiving the state/data of streams from another instance of GPAC using either pipes or sockets\n" + "\n" +#ifndef GPAC_DISABLE_CRYPTO + "The stream format can be encrypted in AES 128 CBC mode, in which case the demux filters must be given a 128 bit key." +#endif + , +#endif + + .private_size = sizeof(GSF_DemuxCtx), + .max_extra_pids = (u32) -1, + .args = GSFDemuxArgs, + .flags = GF_FS_REG_DYNAMIC_PIDS, + SETCAPS(GSFDemuxCaps), + .initialize = gsfdmx_initialize, + .finalize = gsfdmx_finalize, + .configure_pid = gsfdmx_configure_pid, + .process = gsfdmx_process, + .process_event = gsfdmx_process_event, + .probe_data = gsfdmx_probe_data, +}; + + +const GF_FilterRegister *gsfdmx_register(GF_FilterSession *session) +{ + return &GSFDemuxRegister; +} diff --git a/src/filters/dmx_m2ts.c b/src/filters/dmx_m2ts.c new file mode 100644 index 0000000..2f7cd6d --- /dev/null +++ b/src/filters/dmx_m2ts.c @@ -0,0 +1,1153 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2020 + * All rights reserved + * + * This file is part of GPAC / M2TS demux filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include + + +#ifndef GPAC_DISABLE_MPEG2TS + +#include +#include + +typedef struct { + char *fragment; + u32 id; + /*if only pid is requested*/ + u32 pid; +} GF_M2TSDmxCtx_Prog; + +typedef struct { + u32 timeline_id; + Bool is_loc; + u32 len; + u8 *data; +} GF_TEMIInfo; + +enum +{ + DMX_TUNE_DONE=0, + DMX_TUNE_INIT, + DMX_TUNE_WAIT_PROGS, + DMX_TUNE_WAIT_SEEK, + +}; + +typedef struct +{ + //opts + const char *temi_url; + Bool dsmcc, seeksrc; + + GF_Filter *filter; + GF_FilterPid *ipid; + + GF_M2TS_Demuxer *ts; + + GF_FilterPid *eit_pid; + + Bool is_file; + u64 file_size; + Bool in_seek; + Bool initial_play_done; + u32 nb_playing; + + //duration estimation + GF_Fraction64 duration; + u64 first_pcr_found; + u16 pcr_pid; + u64 nb_pck_at_pcr; + + u32 map_time_on_prog_id; + Double media_start_range; + + u32 mux_tune_state; + u32 wait_for_progs; +} GF_M2TSDmxCtx; + + +static void m2tsdmx_estimate_duration(GF_M2TSDmxCtx *ctx, GF_M2TS_ES *stream) +{ + Bool changed; + Double pck_dur; + const GF_PropertyValue *p; + + if (ctx->duration.num) return; + if (!ctx->file_size) { + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_DOWN_SIZE); + if (p) { + ctx->file_size = p->value.longuint; + } else { + ctx->duration.num = 1; + return; + } + } + + if (!ctx->first_pcr_found) { + ctx->first_pcr_found = stream->program->last_pcr_value; + ctx->pcr_pid = stream->pid; + ctx->nb_pck_at_pcr = ctx->ts->pck_number; + return; + } + if (ctx->pcr_pid != stream->pid) return; + if (stream->program->last_pcr_value < ctx->first_pcr_found) { + ctx->first_pcr_found = stream->program->last_pcr_value; + ctx->pcr_pid = stream->pid; + ctx->nb_pck_at_pcr = ctx->ts->pck_number; + return; + } + if (stream->program->last_pcr_value - ctx->first_pcr_found <= 2*27000000) + return; + + changed = GF_FALSE; + + pck_dur = (Double) (stream->program->last_pcr_value - ctx->first_pcr_found); + pck_dur /= (ctx->ts->pck_number - ctx->nb_pck_at_pcr); + pck_dur /= 27000; + + pck_dur *= ctx->file_size; + pck_dur /= ctx->ts->prefix_present ? 192 : 188; + if ((u32) ctx->duration.num != (u32) pck_dur) { + ctx->duration.num = (s32) pck_dur; + ctx->duration.den = 1000; + changed = GF_TRUE; + } + ctx->first_pcr_found = stream->program->last_pcr_value; + ctx->pcr_pid = stream->pid; + ctx->nb_pck_at_pcr = ctx->ts->pck_number; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[M2TSDmx] Estimated duration based on instant bitrate: %g sec\n", pck_dur/1000)); + + if (changed) { + u32 i, nb_streams = gf_filter_get_opid_count(ctx->filter); + for (i=0; ifilter, i); + gf_filter_pid_set_property(opid, GF_PROP_PID_DURATION, &PROP_FRAC64(ctx->duration) ); + } + } +} + +static void m2tsdmx_on_event_duration_probe(GF_M2TS_Demuxer *ts, u32 evt_type, void *param) +{ + GF_Filter *filter = (GF_Filter *) ts->user; + GF_M2TSDmxCtx *ctx = gf_filter_get_udta(filter); + + if (evt_type == GF_M2TS_EVT_PES_PCR) { + GF_M2TS_PES_PCK *pck = ((GF_M2TS_PES_PCK *) param); + + if (pck->stream) m2tsdmx_estimate_duration(ctx, (GF_M2TS_ES *) pck->stream); + } +} + +static void m2tsdmx_declare_pid(GF_M2TSDmxCtx *ctx, GF_M2TS_PES *stream, GF_ESD *esd) +{ + u32 i, count, codecid=0, stype=0; + GF_FilterPid *opid; + Bool m4sys_stream = GF_FALSE; + Bool m4sys_iod_stream = GF_FALSE; + Bool has_scal_layer = GF_FALSE; + Bool unframed = GF_FALSE; + char szName[20]; + const char *stname; + if (stream->user) return; + + if (stream->flags & GF_M2TS_GPAC_CODEC_ID) { + codecid = stream->stream_type; + stype = gf_codecid_type(codecid); + unframed = GF_TRUE; + if (!stype) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[M2TSDmx] Unrecognized gpac codec %s - ignoring pid\n", gf_4cc_to_str(codecid) )); + return; + } + } else { + + switch (stream->stream_type) { + case GF_M2TS_VIDEO_MPEG1: + stype = GF_STREAM_VISUAL; + codecid = GF_CODECID_MPEG1; + unframed = GF_TRUE; + break; + case GF_M2TS_VIDEO_MPEG2: + case GF_M2TS_VIDEO_DCII: + stype = GF_STREAM_VISUAL; + codecid = GF_CODECID_MPEG2_MAIN; + unframed = GF_TRUE; + break; + case GF_M2TS_VIDEO_MPEG4: + stype = GF_STREAM_VISUAL; + codecid = GF_CODECID_MPEG4_PART2; + unframed = GF_TRUE; + break; + case GF_M2TS_VIDEO_H264: + stype = GF_STREAM_VISUAL; + codecid = GF_CODECID_AVC; + unframed = GF_TRUE; + if (stream->program->is_scalable) + has_scal_layer = GF_TRUE; + break; + case GF_M2TS_VIDEO_SVC: + stype = GF_STREAM_VISUAL; + codecid = GF_CODECID_SVC; + has_scal_layer = GF_TRUE; + unframed = GF_TRUE; + break; + case GF_M2TS_VIDEO_HEVC: + case GF_M2TS_VIDEO_HEVC_TEMPORAL: + case GF_M2TS_VIDEO_HEVC_MCTS: + stype = GF_STREAM_VISUAL; + codecid = GF_CODECID_HEVC; + unframed = GF_TRUE; + if (stream->program->is_scalable) + has_scal_layer = GF_TRUE; + break; + case GF_M2TS_VIDEO_SHVC: + case GF_M2TS_VIDEO_SHVC_TEMPORAL: + case GF_M2TS_VIDEO_MHVC: + case GF_M2TS_VIDEO_MHVC_TEMPORAL: + stype = GF_STREAM_VISUAL; + codecid = GF_CODECID_LHVC; + has_scal_layer = GF_TRUE; + break; + case GF_M2TS_AUDIO_MPEG1: + stype = GF_STREAM_AUDIO; + codecid = GF_CODECID_MPEG_AUDIO; + unframed = GF_TRUE; + break; + case GF_M2TS_AUDIO_MPEG2: + stype = GF_STREAM_AUDIO; + codecid = GF_CODECID_MPEG2_PART3; + unframed = GF_TRUE; + break; + case GF_M2TS_AUDIO_LATM_AAC: + case GF_M2TS_AUDIO_AAC: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + stype = GF_STREAM_AUDIO; + codecid = GF_CODECID_AAC_MPEG4; + unframed = GF_TRUE; + break; + case GF_M2TS_AUDIO_AC3: + stype = GF_STREAM_AUDIO; + codecid = GF_CODECID_AC3; + unframed = GF_TRUE; + break; + case GF_M2TS_AUDIO_EC3: + stype = GF_STREAM_AUDIO; + codecid = GF_CODECID_EAC3; + break; + case GF_M2TS_SYSTEMS_MPEG4_SECTIONS: + ((GF_M2TS_ES*)stream)->flags |= GF_M2TS_ES_SEND_REPEATED_SECTIONS; + //fallthrough + case GF_M2TS_SYSTEMS_MPEG4_PES: + if (!esd) { + m4sys_iod_stream = GF_TRUE; + count = gf_list_count(stream->program->pmt_iod->ESDescriptors); + for (i=0; iprogram->pmt_iod->ESDescriptors, i); + if (esd->ESID == stream->mpeg4_es_id) break; + esd = NULL; + } + } + m4sys_stream = GF_TRUE; + //cannot setup stream yet + if (!esd) return; + break; + case GF_M2TS_METADATA_PES: + case GF_M2TS_METADATA_ID3_HLS: + stype = GF_STREAM_METADATA; + codecid = GF_CODECID_SIMPLE_TEXT; + break; + case 0xA1: + stype = GF_STREAM_AUDIO; + codecid = GF_CODECID_EAC3; + break; + default: + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[M2TSDmx] Stream type %d not supported - ignoring pid\n", stream->stream_type)); + return; + } + } + + opid = gf_filter_pid_new(ctx->filter); + stream->user = opid; + stream->flags |= GF_M2TS_ES_ALREADY_DECLARED; + + stname = gf_stream_type_name(stype); + sprintf(szName, "P%d%c%d", stream->program->number, stname[0], 1+gf_list_find(stream->program->streams, stream)); + gf_filter_pid_set_name(opid, szName); + + gf_filter_pid_set_property(opid, GF_PROP_PID_ID, &PROP_UINT(stream->pid) ); + if (stream->mpeg4_es_id) + gf_filter_pid_set_property(opid, GF_PROP_PID_ESID, &PROP_UINT(stream->mpeg4_es_id) ); + + if (m4sys_stream) { + if (stream->slcfg) gf_free(stream->slcfg); + + stream->slcfg = esd->slConfig; + esd->slConfig = NULL; + + gf_filter_pid_set_property(opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(esd->decoderConfig ? esd->decoderConfig->streamType : GF_STREAM_SCENE) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_CODECID, &PROP_UINT(esd->decoderConfig ? esd->decoderConfig->objectTypeIndication : GF_CODECID_BIFS) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_CLOCK_ID, &PROP_UINT(esd->OCRESID ? esd->OCRESID : esd->ESID) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_DEPENDENCY_ID, &PROP_UINT(esd->dependsOnESID) ); + if (esd->decoderConfig && esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->dataLength) + gf_filter_pid_set_property(opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength) ); + + gf_filter_pid_set_property(opid, GF_PROP_PID_IN_IOD, &PROP_BOOL(m4sys_iod_stream) ); + + gf_filter_pid_set_property(opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(((GF_M2TS_ES*)stream)->slcfg->timestampResolution) ); + if (esd->decoderConfig && (esd->decoderConfig->streamType==GF_STREAM_OD)) + stream->flags |= GF_M2TS_ES_IS_MPEG4_OD; + } else { + gf_filter_pid_set_property(opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(stype) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_CODECID, &PROP_UINT(codecid) ); + if (unframed) + gf_filter_pid_set_property(opid, GF_PROP_PID_UNFRAMED, &PROP_BOOL(GF_TRUE) ); + + gf_filter_pid_set_property(opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(90000) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_CLOCK_ID, &PROP_UINT(stream->program->pcr_pid) ); + } + if (has_scal_layer) + gf_filter_pid_set_property(opid, GF_PROP_PID_SCALABLE, &PROP_BOOL(GF_TRUE)); + + gf_filter_pid_set_property(opid, GF_PROP_PID_SERVICE_ID, &PROP_UINT(stream->program->number) ); + + if (ctx->duration.num>1) { + gf_filter_pid_set_property(opid, GF_PROP_PID_DURATION, &PROP_FRAC64(ctx->duration) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_SEEK ) ); + } + /*indicate our coding dependencies if any*/ + if (!m4sys_stream) { + if (stream->depends_on_pid) { + gf_filter_pid_set_property(opid, GF_PROP_PID_DEPENDENCY_ID, &PROP_UINT(stream->depends_on_pid) ); + if ((stream->stream_type == GF_M2TS_VIDEO_HEVC_TEMPORAL) || (stream->stream_type == GF_M2TS_VIDEO_HEVC_MCTS)) { + gf_filter_pid_set_property(opid, GF_PROP_PID_SUBLAYER, &PROP_BOOL(GF_TRUE) ); + } + } + } + gf_m2ts_set_pes_framing((GF_M2TS_PES *)stream, GF_M2TS_PES_FRAMING_DEFAULT); +} + +static void m2tsdmx_setup_program(GF_M2TSDmxCtx *ctx, GF_M2TS_Program *prog) +{ + u32 i, count; + + count = gf_list_count(prog->streams); +#ifdef FILTER_FIXME + if (ctx->ts->tuner) { + Bool found = 0; + for (i=0; istreams, i); + if (pes->pid==ctx->ts->tuner->vpid) found = 1; + else if (pes->pid==ctx->ts->tuner->apid) found = 1; + } + if (!found) return; + } +#endif + + for (i=0; istreams, i); + if (es->pid==prog->pmt_pid) continue; + if (! (es->flags & GF_M2TS_ES_IS_PES)) continue; + + if (es->stream_type == GF_M2TS_VIDEO_HEVC_TEMPORAL ) continue; + if (es->depends_on_pid ) { + prog->is_scalable = GF_TRUE; + break; + } + } + + for (i=0; istreams, i); + if (es->pid==prog->pmt_pid) continue; + + if (! (es->flags & GF_M2TS_ES_ALREADY_DECLARED)) { + m2tsdmx_declare_pid(ctx, (GF_M2TS_PES *)es, NULL); + } + ncount = gf_list_count(prog->streams); + while (ncountprops) { + char szID[100]; + while (gf_list_count(stream->props)) { + GF_TEMIInfo *t = gf_list_pop_front(stream->props); + snprintf(szID, 100, "%s:%d", t->is_loc ? "temi_l" : "temi_t", t->timeline_id); + + gf_filter_pck_set_property_dyn(pck, szID, &PROP_DATA_NO_COPY(t->data, t->len)); + gf_free(t); + } + gf_list_del(stream->props); + stream->props = NULL; + } +} + +static void m2tsdmx_send_packet(GF_M2TSDmxCtx *ctx, GF_M2TS_PES_PCK *pck) +{ + GF_FilterPid *opid; + GF_FilterPacket *dst_pck; + u8 * data; + + /*pcr not initialized, don't send any data*/ +// if (! pck->stream->program->first_dts) return; + if (!pck->stream->user) return; + opid = pck->stream->user; + + dst_pck = gf_filter_pck_new_alloc(opid, pck->data_len, &data); + memcpy(data, pck->data, pck->data_len); + //we don't have end of frame signaling + gf_filter_pck_set_framing(dst_pck, (pck->flags & GF_M2TS_PES_PCK_AU_START) ? GF_TRUE : GF_FALSE, GF_FALSE); + + if (pck->flags & GF_M2TS_PES_PCK_AU_START) { + gf_filter_pck_set_cts(dst_pck, pck->PTS); + if (pck->DTS != pck->PTS) { + gf_filter_pck_set_dts(dst_pck, pck->DTS); + } + gf_filter_pck_set_sap(dst_pck, (pck->flags & GF_M2TS_PES_PCK_RAP) ? GF_FILTER_SAP_1 : GF_FILTER_SAP_NONE); + } + m2tdmx_merge_temi((GF_M2TS_ES *)pck->stream, dst_pck); + gf_filter_pck_send(dst_pck); +} + +static GF_M2TS_ES *m2tsdmx_get_m4sys_stream(GF_M2TSDmxCtx *ctx, u32 m4sys_es_id) +{ + u32 i, j, count, count2; + count = gf_list_count(ctx->ts->programs); + for (i=0; its->programs, i); + count2 = gf_list_count(prog->streams); + for (j=0; jstreams, j); + if (pes->mpeg4_es_id == m4sys_es_id) return pes; + } + } + return NULL; +} +static GFINLINE void m2tsdmx_send_sl_packet(GF_M2TSDmxCtx *ctx, GF_M2TS_SL_PCK *pck) +{ + GF_SLConfig *slc = ((GF_M2TS_ES*)pck->stream)->slcfg; + GF_FilterPid *opid; + GF_FilterPacket *dst_pck; + u8 * data; + Bool start, end; + GF_SLHeader slh; + u32 slh_len = 0; + + if (!pck->stream->user) return; + opid = pck->stream->user; + + /*depacketize SL Header*/ + if (((GF_M2TS_ES*)pck->stream)->slcfg) { + gf_sl_depacketize(slc, &slh, pck->data, pck->data_len, &slh_len); + slh.m2ts_version_number_plus_one = pck->version_number + 1; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[M2TSDmx] MPEG-4 SL-packetized stream without SLConfig assigned - ignoring packet\n") ); + return; + } + + dst_pck = gf_filter_pck_new_alloc(opid, pck->data_len - slh_len, &data); + memcpy(data, pck->data + slh_len, pck->data_len - slh_len); + start = end = GF_FALSE; + if (slc->useAccessUnitStartFlag && slh.accessUnitStartFlag) start = GF_TRUE; + if (slc->useAccessUnitEndFlag && slh.accessUnitEndFlag) end = GF_TRUE; + gf_filter_pck_set_framing(dst_pck, start, end); + + if (slc->useTimestampsFlag && slh.decodingTimeStampFlag) + gf_filter_pck_set_dts(dst_pck, slh.decodingTimeStamp); + + if (slc->useTimestampsFlag && slh.compositionTimeStampFlag) + gf_filter_pck_set_cts(dst_pck, slh.compositionTimeStamp); + + if (slc->hasRandomAccessUnitsOnlyFlag || slh.randomAccessPointFlag) + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + + gf_filter_pck_set_carousel_version(dst_pck, pck->version_number); + + m2tdmx_merge_temi(pck->stream, dst_pck); + gf_filter_pck_send(dst_pck); + + if (pck->version_number + 1 == pck->stream->slcfg->carousel_version) + return; + pck->stream->slcfg->carousel_version = 1 + pck->version_number; + + + if (pck->stream->flags & GF_M2TS_ES_IS_MPEG4_OD) { + /* We need to decode OD streams to get the SL config for other streams :( */ + GF_ESD *esd; + GF_ODUpdate* odU; + GF_ESDUpdate* esdU; + u32 com_count, com_index, od_count, od_index, esd_index; + GF_ODCodec *od_codec = gf_odf_codec_new(); + + gf_odf_codec_set_au(od_codec, pck->data + slh_len, pck->data_len - slh_len); + gf_odf_codec_decode(od_codec); + com_count = gf_list_count(od_codec->CommandList); + for (com_index = 0; com_index < com_count; com_index++) { + GF_ODCom *com; + com = (GF_ODCom *)gf_list_get(od_codec->CommandList, com_index); + switch (com->tag) { + case GF_ODF_OD_UPDATE_TAG: + odU = (GF_ODUpdate*)com; + od_count = gf_list_count(odU->objectDescriptors); + for (od_index=0; od_indexobjectDescriptors, od_index); + esd_index = 0; + while ( (esd = gf_list_enum(od->ESDescriptors, &esd_index)) ) { + GF_M2TS_ES *es = m2tsdmx_get_m4sys_stream(ctx, esd->ESID); + + if (es && ! (es->flags & GF_M2TS_ES_ALREADY_DECLARED)) { + m2tsdmx_declare_pid(ctx, (GF_M2TS_PES *)es, esd); + } + } + } + break; + case GF_ODF_ESD_UPDATE_TAG: + esdU = (GF_ESDUpdate*)com; + esd_index = 0; + while ( (esd = gf_list_enum(esdU->ESDescriptors, &esd_index)) ) { + GF_M2TS_ES *es = m2tsdmx_get_m4sys_stream(ctx, esd->ESID); + if (es && ! (es->flags & GF_M2TS_ES_ALREADY_DECLARED)) { + m2tsdmx_declare_pid(ctx, (GF_M2TS_PES *)es, esd); + } + } + break; + } + } + gf_odf_codec_del(od_codec); + } +} + +#ifdef FILTER_FIXME +static void m2tsdmx_declare_epg_pid(GF_M2TSDmxCtx *ctx) +{ + assert(ctx->eit_pid == NULL); + ctx->eit_pid = gf_filter_pid_new(ctx->filter); + gf_filter_pid_set_property(ctx->eit_pid, GF_PROP_PID_ID, &PROP_UINT(GF_M2TS_PID_EIT_ST_CIT) ); + gf_filter_pid_set_property(ctx->eit_pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_PRIVATE_SCENE) ); + gf_filter_pid_set_property(ctx->eit_pid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_DVB_EIT) ); + gf_filter_pid_set_property(ctx->eit_pid, GF_PROP_PID_TIMESCALE, &PROP_UINT(90000) ); + gf_filter_pid_set_property(ctx->eit_pid, GF_PROP_PID_CLOCK_ID, &PROP_UINT(GF_M2TS_PID_EIT_ST_CIT) ); +} +#endif + +static void m2tsdmx_on_event(GF_M2TS_Demuxer *ts, u32 evt_type, void *param) +{ + u32 i, count; + GF_Filter *filter = (GF_Filter *) ts->user; + GF_M2TSDmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt_type) { + case GF_M2TS_EVT_PAT_UPDATE: + break; + case GF_M2TS_EVT_AIT_FOUND: + break; + case GF_M2TS_EVT_PAT_FOUND: + if (ctx->mux_tune_state==DMX_TUNE_INIT) { + ctx->mux_tune_state = DMX_TUNE_WAIT_PROGS; + ctx->wait_for_progs = gf_list_count(ts->programs); + } + break; + case GF_M2TS_EVT_DSMCC_FOUND: + break; + case GF_M2TS_EVT_PMT_FOUND: + m2tsdmx_setup_program(ctx, param); + if (ctx->mux_tune_state == DMX_TUNE_WAIT_PROGS) { + assert(ctx->wait_for_progs); + ctx->wait_for_progs--; + if (!ctx->wait_for_progs) { + ctx->mux_tune_state = DMX_TUNE_WAIT_SEEK; + } + } + break; + case GF_M2TS_EVT_PMT_REPEAT: + break; + case GF_M2TS_EVT_PMT_UPDATE: + m2tsdmx_setup_program(ctx, param); + break; + + case GF_M2TS_EVT_SDT_FOUND: + case GF_M2TS_EVT_SDT_UPDATE: +// case GF_M2TS_EVT_SDT_REPEAT: + count = gf_list_count(ts->programs); + for (i=0; iprograms, i); + GF_M2TS_SDT *sdt = gf_m2ts_get_sdt_info(ts, prog->number); + if (sdt) { + u32 j, nb_streams; + nb_streams = gf_list_count(prog->streams); + for (j=0; jstreams, j); + if (es->user) { + //TODO, translate non standard character maps to UTF8 + //we for now comment in test mode to avoid non UTF characters in text dumps + if (isalnum(sdt->service[0]) || !gf_sys_is_test_mode()) + gf_filter_pid_set_property((GF_FilterPid *)es->user, GF_PROP_PID_SERVICE_NAME, &PROP_NAME( sdt->service ) ); + + if (isalnum(sdt->provider[0]) || !gf_sys_is_test_mode()) + gf_filter_pid_set_property((GF_FilterPid *)es->user, GF_PROP_PID_SERVICE_PROVIDER, &PROP_NAME( sdt->provider ) ); + } + } + } + } + break; + case GF_M2TS_EVT_DVB_GENERAL: + if (ctx->eit_pid) { + GF_M2TS_SL_PCK *pck = (GF_M2TS_SL_PCK *)param; + u8 *data; + GF_FilterPacket *dst_pck = gf_filter_pck_new_alloc(ctx->eit_pid, pck->data_len, &data); + memcpy(data, pck->data, pck->data_len); + gf_filter_pck_send(dst_pck); + } + break; + case GF_M2TS_EVT_PES_PCK: + if (ctx->mux_tune_state) break; + m2tsdmx_send_packet(ctx, param); + break; + case GF_M2TS_EVT_SL_PCK: /* DMB specific */ + if (ctx->mux_tune_state) break; + m2tsdmx_send_sl_packet(ctx, param); + break; + case GF_M2TS_EVT_PES_PCR: + if (ctx->mux_tune_state) break; + { + u64 pcr; + Bool map_time = GF_FALSE; + GF_M2TS_PES_PCK *pck = ((GF_M2TS_PES_PCK *) param); + Bool discontinuity = ( ((GF_M2TS_PES_PCK *) param)->flags & GF_M2TS_PES_PCK_DISCONTINUITY) ? 1 : 0; + + assert(pck->stream); + m2tsdmx_estimate_duration(ctx, (GF_M2TS_ES *) pck->stream); + + if (ctx->map_time_on_prog_id && (ctx->map_time_on_prog_id==pck->stream->program->number)) { + map_time = GF_TRUE; + } + + //we forward the PCR on each pid + pcr = ((GF_M2TS_PES_PCK *) param)->PTS; + pcr /= 300; + count = gf_list_count(pck->stream->program->streams); + for (i=0; istream->program->streams, i); + if (!stream->user) continue; + + dst_pck = gf_filter_pck_new_shared(stream->user, NULL, 0, NULL); + gf_filter_pck_set_cts(dst_pck, pcr); + gf_filter_pck_set_clock_type(dst_pck, discontinuity ? GF_FILTER_CLOCK_PCR_DISC : GF_FILTER_CLOCK_PCR); + gf_filter_pck_send(dst_pck); + + if (map_time) { + gf_filter_pid_set_info_str(stream->user, "time:timestamp", &PROP_LONGUINT(pcr) ); + gf_filter_pid_set_info_str(stream->user, "time:media", &PROP_DOUBLE(ctx->media_start_range) ); + } + } + + if (map_time) { + ctx->map_time_on_prog_id = 0; + } + } + break; + + case GF_M2TS_EVT_TDT: + if (ctx->mux_tune_state) break; + { + GF_M2TS_TDT_TOT *tdt = (GF_M2TS_TDT_TOT *)param; + u64 utc_ts = gf_net_get_utc_ts(tdt->year, tdt->month, tdt->day, tdt->hour, tdt->minute, tdt->second); + count = gf_list_count(ts->programs ); + for (i=0; iprograms, i); + u32 j, count2 = gf_list_count(prog->streams); + for (j=0; jstreams, j); + if (stream->user) { + gf_filter_pid_set_info(stream->user, GF_PROP_PID_UTC_TIME, & PROP_LONGUINT(utc_ts) ); + gf_filter_pid_set_info(stream->user, GF_PROP_PID_UTC_TIMESTAMP, & PROP_LONGUINT(prog->last_pcr_value / 300) ); + } + } + GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[M2TS In] Mapping TDT Time %04d/%02d/%02d %02d:%02d:%02d and PCR time "LLD" on program %d\n", + tdt->year, tdt->month, tdt->day, tdt->hour, tdt->minute, tdt->second, prog->last_pcr_value/300, prog->number)); + } + } + break; + case GF_M2TS_EVT_TOT: + break; + + case GF_M2TS_EVT_DURATION_ESTIMATED: + { + u64 duration = ((GF_M2TS_PES_PCK *) param)->PTS; + count = gf_list_count(ts->programs); + for (i=0; iprograms, i); + u32 j, count2; + count2 = gf_list_count(prog->streams); + for (j=0; jstreams, j); + if (stream->user) { + gf_filter_pid_set_property(stream->user, GF_PROP_PID_DURATION, & PROP_FRAC64_INT(duration, 1000) ); + } + } + } + } + break; + + case GF_M2TS_EVT_TEMI_LOCATION: + { + GF_M2TS_TemiLocationDescriptor *temi_l = (GF_M2TS_TemiLocationDescriptor *)param; + const char *url; + u32 len; + GF_BitStream *bs; + GF_M2TS_ES *es=NULL; + GF_TEMIInfo *t; + if ((temi_l->pid<8192) && (ctx->ts->ess[temi_l->pid])) { + es = ctx->ts->ess[temi_l->pid]; + } + if (!es || !es->user) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[M2TSDmx] TEMI location not assigned to a given PID, not supported\n")); + break; + } + GF_SAFEALLOC(t, GF_TEMIInfo); + if (!t) break; + t->timeline_id = temi_l->timeline_id; + t->is_loc = GF_TRUE; + + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + if (ctx->temi_url) + url = ctx->temi_url; + else + url = temi_l->external_URL; + len = url ? (u32) strlen(url) : 0; + gf_bs_write_data(bs, url, len); + gf_bs_write_u8(bs, 0); + gf_bs_write_int(bs, temi_l->is_announce, 1); + gf_bs_write_int(bs, temi_l->is_splicing, 1); + gf_bs_write_int(bs, temi_l->reload_external, 1); + gf_bs_write_int(bs, 0, 5); + gf_bs_write_double(bs, temi_l->activation_countdown); + gf_bs_get_content(bs, &t->data, &t->len); + gf_bs_del(bs); + + if (!es->props) { + es->props = gf_list_new(); + } + gf_list_add(es->props, t); + } + break; + case GF_M2TS_EVT_TEMI_TIMECODE: + { + GF_M2TS_TemiTimecodeDescriptor *temi_t = (GF_M2TS_TemiTimecodeDescriptor*)param; + GF_BitStream *bs; + GF_TEMIInfo *t; + GF_M2TS_ES *es=NULL; + if ((temi_t->pid<8192) && (ctx->ts->ess[temi_t->pid])) { + es = ctx->ts->ess[temi_t->pid]; + } + if (!es || !es->user) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[M2TSDmx] TEMI timing not assigned to a given PID, not supported\n")); + break; + } + GF_SAFEALLOC(t, GF_TEMIInfo); + if (!t) break; + t->timeline_id = temi_t->timeline_id; + + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + gf_bs_write_u32(bs, temi_t->media_timescale); + gf_bs_write_u64(bs, temi_t->media_timestamp); + gf_bs_write_u64(bs, temi_t->pes_pts); + gf_bs_write_int(bs, temi_t->force_reload, 1); + gf_bs_write_int(bs, temi_t->is_paused, 1); + gf_bs_write_int(bs, temi_t->is_discontinuity, 1); + gf_bs_write_int(bs, temi_t->ntp ? 1 : 0, 1); + gf_bs_write_int(bs, 0, 4); + if (temi_t->ntp) + gf_bs_write_u64(bs, temi_t->ntp); + + gf_bs_get_content(bs, &t->data, &t->len); + gf_bs_del(bs); + + if (!es->props) { + es->props = gf_list_new(); + } + gf_list_add(es->props, t); + } + break; + } +} + +static GF_Err m2tsdmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p;; + FILE *stream = NULL; + GF_M2TSDmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; +// gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + //by default for all URLs, send packets as soon as the program is configured + ctx->mux_tune_state = DMX_TUNE_DONE; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FILEPATH); + if (p && p->value.string && !ctx->duration.num) { + stream = gf_fopen(p->value.string, "rb"); + } + + if (stream) { + if (ctx->seeksrc) { + //for local file we will send a seek and stop once all programs are configured, and reparse from start + p = gf_filter_pid_get_property(pid, GF_PROP_PID_URL); + if (p && p->value.string && gf_file_exists(p->value.string)) { + ctx->mux_tune_state = DMX_TUNE_INIT; + } + } + + ctx->ipid = pid; + ctx->is_file = GF_TRUE; + ctx->ts->seek_mode = GF_TRUE; + ctx->ts->on_event = m2tsdmx_on_event_duration_probe; + while (!gf_feof(stream)) { + char buf[1880]; + u32 nb_read = (u32) gf_fread(buf, 1880, stream); + gf_m2ts_process_data(ctx->ts, buf, nb_read); + if (ctx->duration.num || (nb_read!=1880)) break; + } + gf_m2ts_demux_del(ctx->ts); + gf_fclose(stream); + ctx->ts = gf_m2ts_demux_new(); + ctx->ts->on_event = m2tsdmx_on_event; + ctx->ts->user = filter; + } else if (!p) { + GF_FilterEvent evt; + ctx->duration.num = 1; + + //not-file base TS, we need to start demuxing the first time we see the PID + if (!ctx->ipid) { + GF_FEVT_INIT(evt, GF_FEVT_PLAY, pid); + gf_filter_pid_send_event(pid, &evt); + } + } + ctx->ipid = pid; + return GF_OK; +} + + +static GF_M2TS_PES *m2tsdmx_get_stream(GF_M2TSDmxCtx *ctx, GF_FilterPid *pid) +{ + u32 i, j, count, count2; + count = gf_list_count(ctx->ts->programs); + for (i=0; its->programs, i); + count2 = gf_list_count(prog->streams); + for (j=0; jstreams, j); + if (pes->user == pid) return pes; + } + } + return NULL; +} + +static void m2tsdmx_switch_quality(GF_M2TS_Program *prog, GF_M2TS_Demuxer *ts, Bool switch_up) +{ + GF_M2TS_ES *es; + u32 i, count; + + if (!prog->is_scalable) + return; + + if (switch_up) { + for (i = 0; i < GF_M2TS_MAX_STREAMS; i++) { + es = ts->ess[i]; + if (es && (es->flags & GF_M2TS_ES_IS_PES) && (((GF_M2TS_PES *)es)->depends_on_pid == prog->pid_playing)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("Turn on ES%d\n", es->pid)); + gf_m2ts_set_pes_framing((GF_M2TS_PES *)ts->ess[es->pid], GF_M2TS_PES_FRAMING_DEFAULT); + prog->pid_playing = es->pid; + return; + } + } + } + else { + count = gf_list_count(prog->streams); + for (i = 0; i < count; i++) { + es = (GF_M2TS_ES *)gf_list_get(prog->streams, i); + if (es && (es->pid == prog->pid_playing) && ((GF_M2TS_PES *)es)->depends_on_pid) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("Turn off ES%d - playing ES%d\n", es->pid, ((GF_M2TS_PES *)es)->depends_on_pid)); + gf_m2ts_set_pes_framing((GF_M2TS_PES *)ts->ess[es->pid], GF_M2TS_PES_FRAMING_SKIP); + + //do we want to send a reset ? + prog->pid_playing = ((GF_M2TS_PES *)es)->depends_on_pid; + return; + } + } + } +} + +static Bool m2tsdmx_process_event(GF_Filter *filter, const GF_FilterEvent *com) +{ + GF_M2TS_PES *pes; + u64 file_pos = 0; + GF_FilterEvent fevt; + GF_M2TSDmxCtx *ctx = gf_filter_get_udta(filter); + GF_M2TS_Demuxer *ts = ctx->ts; + + if (com->base.type == GF_FEVT_QUALITY_SWITCH) { + u32 i, count; + count = gf_list_count(ts->programs); + for (i = 0; i < count; i++) { + GF_M2TS_Program *prog = (GF_M2TS_Program *)gf_list_get(ts->programs, i); + m2tsdmx_switch_quality(prog, ts, com->quality_switch.up); + } + //don't cancel event for RTP source + return GF_FALSE; + } + + //don't cancel event for RTP source + if (!com->base.on_pid) return GF_FALSE; + switch (com->base.type) { + case GF_FEVT_PLAY: + pes = m2tsdmx_get_stream(ctx, com->base.on_pid); + if (!pes) { + if (com->base.on_pid == ctx->eit_pid) { + return GF_FALSE; + } + return GF_FALSE; + } + /*mark pcr as not initialized*/ + if (pes->program->pcr_pid==pes->pid) pes->program->first_dts=0; + gf_m2ts_set_pes_framing(pes, GF_M2TS_PES_FRAMING_DEFAULT); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[M2TSDmx] Setting default reframing for PID %d\n", pes->pid)); + + /*this is a multplex, only trigger the play command for the first stream activated*/ + ctx->nb_playing++; + if (ctx->nb_playing>1) return GF_TRUE; + + //not file, don't cancel the event + if (!ctx->is_file) { + ctx->initial_play_done = GF_TRUE; + return GF_FALSE; + } + + ctx->map_time_on_prog_id = pes->program->number; + ctx->media_start_range = com->play.start_range; + + + if (ctx->is_file && ctx->duration.num) { + file_pos = (u64) (ctx->file_size * com->play.start_range); + file_pos *= ctx->duration.den; + file_pos /= ctx->duration.num; + if (file_pos > ctx->file_size) return GF_TRUE; + } + + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!file_pos) + return GF_TRUE; + } + + //file and seek, cancel the event and post a seek event to source + ctx->in_seek = GF_TRUE; + //we seek so consider the mux tuned in + ctx->mux_tune_state = DMX_TUNE_DONE; + + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + return GF_TRUE; + + case GF_FEVT_STOP: + pes = m2tsdmx_get_stream(ctx, com->base.on_pid); + if (!pes) { + if (com->base.on_pid == ctx->eit_pid) { + return GF_FALSE; + } + return GF_FALSE; + } + /* In case of EOS, we may receive a stop command after no one is playing */ + if (ctx->nb_playing) + ctx->nb_playing--; + + gf_m2ts_set_pes_framing(pes, GF_M2TS_PES_FRAMING_SKIP); + //don't cancel event if still playing + return ctx->nb_playing ? GF_TRUE : GF_FALSE; + + case GF_FEVT_PAUSE: + case GF_FEVT_RESUME: + return GF_FALSE; + default: + return GF_FALSE; + } +} + + + +static GF_Err m2tsdmx_initialize(GF_Filter *filter) +{ + GF_M2TSDmxCtx *ctx = gf_filter_get_udta(filter); + + ctx->ts = gf_m2ts_demux_new(); + if (!ctx->ts) return GF_OUT_OF_MEM; + + ctx->ts->on_event = m2tsdmx_on_event; + ctx->ts->user = filter; + + ctx->filter = filter; + if (ctx->dsmcc) { + gf_m2ts_demux_dmscc_init(ctx->ts); + } + + return GF_OK; +} + + +static void m2tsdmx_finalize(GF_Filter *filter) +{ + GF_M2TSDmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->ts) gf_m2ts_demux_del(ctx->ts); + +} + +static GF_Err m2tsdmx_process(GF_Filter *filter) +{ + GF_M2TSDmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->ipid); + const char *data; + u32 size; + + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + u32 i, nb_streams = gf_filter_get_opid_count(filter); + + gf_m2ts_flush_all(ctx->ts); + for (i=0; iin_seek) { + gf_m2ts_reset_parsers(ctx->ts); + ctx->in_seek = GF_FALSE; + } else { + u32 i, nb_streams, would_block = 0; + nb_streams = gf_filter_get_opid_count(filter); + for (i=0; its, (char*) data, size); + + gf_filter_pid_drop_packet(ctx->ipid); + + if (ctx->mux_tune_state==DMX_TUNE_WAIT_SEEK) { + GF_FilterEvent fevt; + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + gf_filter_pid_send_event(ctx->ipid, &fevt); + ctx->mux_tune_state = DMX_TUNE_DONE; + gf_m2ts_reset_parsers(ctx->ts); + } + return GF_OK; +} + +static const char *m2tsdmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + if (gf_m2ts_probe_data(data, size)) { + *score = GF_FPROBE_SUPPORTED; + return "video/mp2t"; + } + return NULL; +} + +static const GF_FilterCapability M2TSDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "ts|m2t|mts|dmb|trp"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "video/mpeg-2|video/mp2t|video/mpeg"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_OD), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_PRIVATE_SCENE), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +#define OFFS(_n) #_n, offsetof(GF_M2TSDmxCtx, _n) +static const GF_FilterArgs M2TSDmxArgs[] = +{ + { OFFS(temi_url), "force TEMI URL", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(dsmcc), "enable DSMCC receiver", GF_PROP_BOOL, "no", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(seeksrc), "seek local source file back to origin once all programs are setup", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, + {0} +}; + + +GF_FilterRegister M2TSDmxRegister = { + .name = "m2tsdmx", + GF_FS_SET_DESCRIPTION("MPEG-2 TS demuxer") + GF_FS_SET_HELP("This filter demultiplexes MPEG-2 Transport Stream files/data into a set of media PIDs and frames.") + .private_size = sizeof(GF_M2TSDmxCtx), + .initialize = m2tsdmx_initialize, + .finalize = m2tsdmx_finalize, + .args = M2TSDmxArgs, + .flags = GF_FS_REG_DYNAMIC_PIDS, + SETCAPS(M2TSDmxCaps), + .configure_pid = m2tsdmx_configure_pid, + .process = m2tsdmx_process, + .process_event = m2tsdmx_process_event, + .probe_data = m2tsdmx_probe_data, +}; + + +#endif + +const GF_FilterRegister *m2tsdmx_register(GF_FilterSession *session) +{ +#ifndef GPAC_DISABLE_MPEG2TS + return &M2TSDmxRegister; +#else + return NULL; +#endif +} diff --git a/src/filters/dmx_mpegps.c b/src/filters/dmx_mpegps.c new file mode 100644 index 0000000..fbb7637 --- /dev/null +++ b/src/filters/dmx_mpegps.c @@ -0,0 +1,493 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2017 + * All rights reserved + * + * This file is part of GPAC / MPEG Program Stream demuxer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_MPEG2PS +#include "../media_tools/mpeg2_ps.h" +#include + +typedef struct +{ + GF_FilterPid *opid; + u32 stream_type; + u32 stream_num; + Bool in_use; + u64 last_dts; +} M2PSStream; + + +typedef struct +{ + GF_FilterPid *ipid; + + const char *src_url; + mpeg2ps_t *ps; + + Double start_range; + u64 first_dts; + + u32 nb_playing; + GF_Fraction64 duration; + Bool in_seek; + + GF_List *streams; + Bool initial_play_done; +} GF_M2PSDmxCtx; + +static void m2psdmx_setup(GF_Filter *filter, GF_M2PSDmxCtx *ctx) +{ + u32 i, nb_streams; + u32 sync_id = 0; + Double fps; + GF_Fraction64 dur; + + dur.den = 1000; + dur.num = (s32) mpeg2ps_get_max_time_msec(ctx->ps); + + ctx->first_dts = mpeg2ps_get_first_cts(ctx->ps); + + nb_streams = mpeg2ps_get_video_stream_count(ctx->ps); + for (i=0; istreams); + for (j=0; jstreams, j); + if ((st->stream_type==GF_STREAM_VISUAL) && !st->in_use) break; + st = NULL; + } + if (!st) { + GF_SAFEALLOC(st, M2PSStream); + if (!st) continue; + st->opid = gf_filter_pid_new(filter); + st->stream_type = GF_STREAM_VISUAL; + gf_list_add(ctx->streams, st); + } + st->in_use = GF_TRUE; + st->stream_num = i; + id = 0x100 | mpeg2ps_get_video_stream_id(ctx->ps, st->stream_num); + if (!sync_id) sync_id = id; + + gf_filter_pid_copy_properties(st->opid, ctx->ipid); + gf_filter_pid_set_name(st->opid, "Video"); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(st->stream_type) ); + switch (mpeg2ps_get_video_stream_type(ctx->ps, st->stream_num)) { + case MPEG_VIDEO_MPEG1: + gf_filter_pid_set_property(st->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_MPEG1) ); + break; + case MPEG_VIDEO_MPEG2: + gf_filter_pid_set_property(st->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_MPEG2_MAIN) ); + break; + default: + break; + } + gf_filter_pid_set_property(st->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(90000) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_ID, &PROP_UINT( id) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_CLOCK_ID, &PROP_UINT( sync_id ) ); + + fps = mpeg2ps_get_video_stream_framerate(ctx->ps, i); + if (fps) { + gf_media_get_video_timing(fps, &frac.num, &frac.den); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_FPS, &PROP_FRAC( frac ) ); + } + gf_filter_pid_set_property(st->opid, GF_PROP_PID_WIDTH, &PROP_UINT( mpeg2ps_get_video_stream_width(ctx->ps, i) ) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_HEIGHT, &PROP_UINT( mpeg2ps_get_video_stream_height(ctx->ps, i) ) ); + par = mpeg2ps_get_video_stream_aspect_ratio(ctx->ps, i); + if (par) { + frac.num = par>>16; + frac.den = (par&0xffff); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_SAR, &PROP_FRAC( frac ) ); + } + gf_filter_pid_set_property(st->opid, GF_PROP_PID_DURATION, &PROP_FRAC64( dur ) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_UNFRAMED, &PROP_BOOL( GF_TRUE ) ); + gf_filter_pid_set_property_str(st->opid, "nocts", &PROP_BOOL(GF_TRUE )); + + gf_filter_pid_set_property(st->opid, GF_PROP_PID_BITRATE, &PROP_UINT((u32) mpeg2ps_get_video_stream_bitrate(ctx->ps, i) ) ); + + gf_filter_pid_set_property(st->opid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_SEEK ) ); + } + + nb_streams = mpeg2ps_get_audio_stream_count(ctx->ps); + for (i=0; istreams); + + if (mpeg2ps_get_audio_stream_type(ctx->ps, i) == MPEG_AUDIO_UNKNOWN) { + continue; + } + + for (j=0; jstreams, j); + if ((st->stream_type==GF_STREAM_AUDIO) && !st->in_use) break; + st = NULL; + } + if (!st) { + GF_SAFEALLOC(st, M2PSStream); + if (!st) continue; + st->opid = gf_filter_pid_new(filter); + st->stream_type = GF_STREAM_AUDIO; + gf_list_add(ctx->streams, st); + } + st->in_use = GF_TRUE; + st->stream_num = i; + id = 0x100 | mpeg2ps_get_audio_stream_id(ctx->ps, st->stream_num); + if (!sync_id) sync_id = id; + + gf_filter_pid_copy_properties(st->opid, ctx->ipid); + sprintf(szName, "Audio%d", i+1); + gf_filter_pid_set_name(st->opid, szName); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(st->stream_type) ); + switch (mpeg2ps_get_audio_stream_type(ctx->ps, st->stream_num)) { + case MPEG_AUDIO_MPEG: + gf_filter_pid_set_property(st->opid, GF_PROP_PID_CODECID, &PROP_UINT( GF_CODECID_MPEG_AUDIO) ); + break; + case MPEG_AUDIO_AC3: + gf_filter_pid_set_property(st->opid, GF_PROP_PID_CODECID, &PROP_UINT( GF_CODECID_AC3) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_UNFRAMED, &PROP_BOOL( GF_TRUE) ); + break; + case MPEG_AUDIO_LPCM: + gf_filter_pid_set_property(st->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_4CC('L','P','C','M') ) ); + break; + default: + break; + } + gf_filter_pid_set_property(st->opid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT( mpeg2ps_get_audio_stream_sample_freq(ctx->ps, i) ) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT( mpeg2ps_get_audio_stream_channels(ctx->ps, i) ) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_BITRATE, &PROP_UINT( mpeg2ps_get_audio_stream_bitrate(ctx->ps, i) ) ); + + gf_filter_pid_set_property(st->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(90000) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_ID, &PROP_UINT( id) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_CLOCK_ID, &PROP_UINT( sync_id ) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_DURATION, &PROP_FRAC64( dur ) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_UNFRAMED, &PROP_BOOL( GF_TRUE ) ); + gf_filter_pid_set_property_str(st->opid, "nocts", &PROP_BOOL(GF_TRUE )); + + gf_filter_pid_set_property(st->opid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_SEEK ) ); + } +} + + +GF_Err m2psdmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 i; + const GF_PropertyValue *p; + GF_M2PSDmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + while (gf_list_count(ctx->streams) ) { + M2PSStream *st = gf_list_pop_back(ctx->streams); + gf_filter_pid_remove(st->opid); + gf_free(st); + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + if (!ctx->ipid) { + GF_FilterEvent fevt; + ctx->ipid = pid; + + //we work with full file only, send a play event on source to indicate that + GF_FEVT_INIT(fevt, GF_FEVT_PLAY, pid); + fevt.play.start_range = 0; + fevt.base.on_pid = ctx->ipid; + fevt.play.full_file_only = GF_TRUE; + gf_filter_pid_send_event(ctx->ipid, &fevt); + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p) return GF_NOT_SUPPORTED; + + if (ctx->src_url && !strcmp(ctx->src_url, p->value.string)) return GF_OK; + + if (ctx->ps) { + mpeg2ps_close(ctx->ps); + for (i=0; istreams); i++) { + M2PSStream *st = gf_list_get(ctx->streams, i); + st->in_use = GF_FALSE; + } + } + ctx->ps = NULL; + + ctx->src_url = p->value.string; + + return GF_OK; +} + +static Bool m2psdmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i; + GF_M2PSDmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (ctx->nb_playing && (ctx->start_range == evt->play.start_range)) { + ctx->nb_playing++; + return GF_TRUE; + } + ctx->nb_playing++; + ctx->start_range = evt->play.start_range; + gf_filter_post_process_task(filter); + + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (ctx->start_range<0.5) + return GF_TRUE; + } + + for (i=0; istreams); i++) { + M2PSStream *pss = gf_list_get(ctx->streams, i); + if (pss->opid == evt->base.on_pid) + pss->in_use = GF_TRUE; + if (!pss->in_use) continue; + + if (pss->stream_type==GF_STREAM_VISUAL) { + mpeg2ps_seek_video_frame(ctx->ps, pss->stream_num, (u64) (ctx->start_range*1000)); + } else { + mpeg2ps_seek_audio_frame(ctx->ps, pss->stream_num, (u64) (ctx->start_range*1000) ); + } + } + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + ctx->nb_playing--; + for (i=0; istreams); i++) { + M2PSStream *pss = gf_list_get(ctx->streams, i); + if (pss->opid == evt->base.on_pid) + pss->in_use = GF_FALSE; + } + //cancel event if not last stream playing + if (ctx->nb_playing) return GF_TRUE; + + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +GF_Err m2psdmx_process(GF_Filter *filter) +{ + GF_M2PSDmxCtx *ctx = gf_filter_get_udta(filter); + Bool start, end; + u32 i, count, nb_done; + if (!ctx->ps) { + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + return GF_OK; + } + gf_filter_pck_get_framing(pck, &start, &end); + if (!end) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + gf_filter_pid_drop_packet(ctx->ipid); + + ctx->ps = mpeg2ps_init(ctx->src_url); + if (!ctx->ps) { + GF_Err e = GF_NON_COMPLIANT_BITSTREAM; + if (! gf_file_exists(ctx->src_url)) e = GF_URL_ERROR; + gf_filter_setup_failure(filter, e); + return GF_NOT_SUPPORTED; + } + m2psdmx_setup(filter, ctx); + } + if (!ctx->nb_playing) return GF_OK; + + + nb_done = 0; + count = gf_list_count(ctx->streams); + + if (ctx->in_seek) { + u64 seek_to = (u64) (1000*ctx->start_range); + for (i=0; istreams, i); + if (!st->in_use) continue; + if (st->stream_type==GF_STREAM_VISUAL) { + mpeg2ps_seek_video_frame(ctx->ps, st->stream_num, seek_to); + } else { + mpeg2ps_seek_audio_frame(ctx->ps, st->stream_num, seek_to); + } + } + ctx->in_seek = GF_FALSE; + } + + + for (i=0; istreams, i); + if (!st->in_use) { + nb_done++; + continue; + } + + if (gf_filter_pid_would_block(st->opid)) continue; + + if (st->stream_type==GF_STREAM_VISUAL) { + u8 ftype; + u64 dts, cts; + u32 res = mpeg2ps_get_video_frame(ctx->ps, st->stream_num, (u8 **) &buf, &buf_len, &ftype, TS_90000, &dts, &cts); + if (!res) { + nb_done++; + continue; + } + + //bug in some streams, make sure we don't dispatch twice the same ts + if (st->last_dts == dts) dts++;; + st->last_dts = dts; + + if ((buf[buf_len - 4] == 0) && (buf[buf_len - 3] == 0) && (buf[buf_len - 2] == 1)) buf_len -= 4; + dst_pck = gf_filter_pck_new_alloc(st->opid, buf_len, &pck_data); + memcpy(pck_data, buf, buf_len); + if (ftype==1) gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + + if (cts != GF_FILTER_NO_TS) { + if (dts == GF_FILTER_NO_TS) dts = cts; + dts -= ctx->first_dts; + cts -= ctx->first_dts; + gf_filter_pck_set_dts(dst_pck, dts); + gf_filter_pck_set_cts(dst_pck, cts); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_FALSE); + } else { + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE); + } + gf_filter_pck_send(dst_pck); + } else { + u64 cts; + u32 res = mpeg2ps_get_audio_frame(ctx->ps, st->stream_num, (u8**)&buf, &buf_len, TS_90000, NULL, &cts); + if (!res) { + nb_done++; + continue; + } + dst_pck = gf_filter_pck_new_alloc(st->opid, buf_len, &pck_data); + memcpy(pck_data, buf, buf_len); + if (cts != GF_FILTER_NO_TS) { + cts -= ctx->first_dts; + gf_filter_pck_set_cts(dst_pck, cts); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_FALSE); + } else { + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE); + } + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + + gf_filter_pck_send(dst_pck); + } + } + + if (nb_done==count) { + for (i=0; istreams, i); + gf_filter_pid_set_eos(st->opid); + } + return GF_EOS; + } + return GF_OK; +} + +GF_Err m2psdmx_initialize(GF_Filter *filter) +{ + GF_M2PSDmxCtx *ctx = gf_filter_get_udta(filter); + ctx->streams = gf_list_new(); + return GF_OK; +} + +void m2psdmx_finalize(GF_Filter *filter) +{ + GF_M2PSDmxCtx *ctx = gf_filter_get_udta(filter); + + while (gf_list_count(ctx->streams)) { + M2PSStream *st = gf_list_pop_back(ctx->streams); + gf_free(st); + } + gf_list_del(ctx->streams); + if (ctx->ps) mpeg2ps_close(ctx->ps); +} + +static const char *m2psdmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + *score = GF_FPROBE_EXT_MATCH; + return "mpg|mpeg|vob"; +} + +static const GF_FilterCapability M2PSDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "mpg|mpeg|vob"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "video/mpeg|audio/mpeg"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + + + +GF_FilterRegister M2PSDmxRegister = { + .name = "m2psdmx", + GF_FS_SET_DESCRIPTION("MPEG PS demuxer") + GF_FS_SET_HELP("This filter demultiplexes MPEG-2 program stream files/data to produce media PIDs and frames.") + .private_size = sizeof(GF_M2PSDmxCtx), + .initialize = m2psdmx_initialize, + .finalize = m2psdmx_finalize, + SETCAPS(M2PSDmxCaps), + .configure_pid = m2psdmx_configure_pid, + .process = m2psdmx_process, + .process_event = m2psdmx_process_event, + .probe_data = m2psdmx_probe_data, + //this filter is not very reliable, prefer ffmpeg when available + .priority = 255 +}; + +#endif // GPAC_DISABLE_MPEG2PS + +const GF_FilterRegister *m2psdmx_register(GF_FilterSession *session) +{ +#ifndef GPAC_DISABLE_MPEG2PS + return &M2PSDmxRegister; +#else + return NULL; +#endif +} + diff --git a/src/filters/dmx_nhml.c b/src/filters/dmx_nhml.c new file mode 100644 index 0000000..6c3a775 --- /dev/null +++ b/src/filters/dmx_nhml.c @@ -0,0 +1,1411 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2020 + * All rights reserved + * + * This file is part of GPAC / NHML demuxer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_AV_PARSERS +#include +#endif + +typedef struct +{ + //opts + Bool reframe; + Double index; + + GF_FilterPid *ipid; + GF_FilterPid *opid; + + Bool is_dims; + + Double start_range; + u64 first_dts; + + Bool is_playing; + GF_Fraction64 duration; + Bool in_seek; + + u32 timescale; + u32 sample_num; + + FILE *mdia; + char szMedia[GF_MAX_PATH]; + + GF_DOMParser *parser; + GF_XMLNode *root; + //0: not initialized, 1: OK, samples can be sent, 2: EOS, 3: error + u32 parsing_state; + u32 current_child_idx; + Bool has_sap; + u32 compress_type; + const char *src_url; + u64 last_dts; + u32 dts_inc; + + u8 *samp_buffer; + u32 samp_buffer_alloc, samp_buffer_size; + char *zlib_buffer; + u32 zlib_buffer_alloc, zlib_buffer_size; +#ifndef GPAC_DISABLE_ZLIB + Bool use_dict; + char *dictionary; +#endif + + u64 media_done; + Bool is_img; + u32 header_end; + + GF_BitStream *bs_w; + GF_BitStream *bs_r; +} GF_NHMLDmxCtx; + + +GF_Err nhmldmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_NHMLDmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + //gf_filter_pid_remove(st->opid); + + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_MIME); + if (p && p->value.string && strstr(p->value.string, "dims")) ctx->is_dims = GF_TRUE; + else { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FILE_EXT); + if (p && p->value.string && strstr(p->value.string, "dims")) ctx->is_dims = GF_TRUE; + } + + return GF_OK; +} + +static Bool nhmldmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i=0; + u64 cur_dts = 0; + u64 byte_offset = 0; + u32 sample_num = 0; + GF_XMLNode *node; + GF_NHMLDmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (ctx->is_playing && (ctx->start_range == evt->play.start_range)) { + return GF_TRUE; + } + + ctx->start_range = evt->play.start_range; + ctx->current_child_idx = 0; + ctx->media_done = ctx->header_end; + ctx->is_playing = GF_TRUE; + //post a seek + ctx->in_seek = GF_TRUE; + + //lcoate previous RAP sample + while ((node = (GF_XMLNode *) gf_list_enum(ctx->root->content, &i))) { + u32 j=0; + u64 dts=0; + u32 datalen=0; + Bool is_rap = ctx->has_sap ? GF_FALSE : GF_TRUE; + s32 cts_offset=0; + u64 sample_duration = 0; + GF_XMLAttribute *att; + if (node->type) continue; + if (stricmp(node->name, ctx->is_dims ? "DIMSUnit" : "NHNTSample") ) continue; + + while ( (att = (GF_XMLAttribute *)gf_list_enum(node->attributes, &j))) { + if (!stricmp(att->name, "DTS") || !stricmp(att->name, "time")) { + u32 h, m, s, ms; + u64 dst_val; + if (strchr(att->value, ':') && sscanf(att->value, "%u:%u:%u.%u", &h, &m, &s, &ms) == 4) { + dts = (u64) ( (Double) ( ((h*3600.0 + m*60.0 + s)*1000 + ms) / 1000.0) * ctx->timescale ); + } else if (sscanf(att->value, ""LLU, &dst_val)==1) { + dts = dst_val; + } + } + else if (!stricmp(att->name, "CTSOffset")) cts_offset = atoi(att->value); + else if (!stricmp(att->name, "duration") ) sscanf(att->value, ""LLU, &sample_duration); + else if (!stricmp(att->name, "isRAP") ) { + is_rap = (!stricmp(att->value, "yes")) ? GF_TRUE : GF_FALSE; + } + else if (!stricmp(att->name, "mediaOffset")) + byte_offset = (s64) atof(att->value) ; + else if (!stricmp(att->name, "dataLength")) + datalen = atoi(att->value); + } + + dts += cts_offset; + if ((s64) dts < 0) dts = 0; + + if (dts) cur_dts = dts; + if (sample_duration) cur_dts += sample_duration; + else if (ctx->dts_inc) cur_dts += ctx->dts_inc; + + if (cur_dts >= ctx->timescale * evt->play.start_range) { + break; + } + if (is_rap) { + ctx->current_child_idx = i-1; + ctx->media_done = byte_offset; + ctx->sample_num = sample_num; + } + byte_offset += datalen; + sample_num++; + } + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + ctx->is_playing = GF_FALSE; + //don't cancel event + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + + +typedef struct +{ + Bool from_is_start, from_is_end, to_is_start, to_is_end; + u64 from_pos, to_pos; + char *from_id, *to_id; + GF_List *id_stack; + GF_SAXParser *sax; +} XMLBreaker; + + +static void nhml_node_start(void *sax_cbck, const char *node_name, const char *name_space, const GF_XMLAttribute *attributes, u32 nb_attributes) +{ + XMLBreaker *breaker = (XMLBreaker *)sax_cbck; + char *node_id; + u32 i; + node_id = NULL; + for (i=0; iname, "DEF") && stricmp(att->name, "id")) continue; + node_id = gf_strdup(att->value); + break; + } + if (!node_id) { + node_id = gf_strdup("__nhml__none"); + gf_list_add(breaker->id_stack, node_id); + return; + } + gf_list_add(breaker->id_stack, node_id); + + if (breaker->from_is_start && breaker->from_id && !strcmp(breaker->from_id, node_id)) { + breaker->from_pos = gf_xml_sax_get_node_start_pos(breaker->sax); + breaker->from_is_start = GF_FALSE; + } + if (breaker->to_is_start && breaker->to_id && !strcmp(breaker->to_id, node_id)) { + breaker->to_pos = gf_xml_sax_get_node_start_pos(breaker->sax); + breaker->to_is_start = GF_FALSE; + } + if (!breaker->to_is_start && !breaker->from_is_start && !breaker->to_is_end && !breaker->from_is_end) { + gf_xml_sax_suspend(breaker->sax, GF_TRUE); + } + +} + +static void nhml_node_end(void *sax_cbck, const char *node_name, const char *name_space) +{ + XMLBreaker *breaker = (XMLBreaker *)sax_cbck; + char *node_id = (char *)gf_list_last(breaker->id_stack); + gf_list_rem_last(breaker->id_stack); + if (breaker->from_is_end && breaker->from_id && !strcmp(breaker->from_id, node_id)) { + breaker->from_pos = gf_xml_sax_get_node_end_pos(breaker->sax); + breaker->from_is_end = GF_FALSE; + } + if (breaker->to_is_end && breaker->to_id && !strcmp(breaker->to_id, node_id)) { + breaker->to_pos = gf_xml_sax_get_node_end_pos(breaker->sax); + breaker->to_is_end = GF_FALSE; + } + gf_free(node_id); + if (!breaker->to_is_start && !breaker->from_is_start && !breaker->to_is_end && !breaker->from_is_end) { + gf_xml_sax_suspend(breaker->sax, GF_TRUE); + } +} + + +static GF_Err nhml_sample_from_xml(GF_NHMLDmxCtx *ctx, char *xml_file, char *xmlFrom, char *xmlTo) +{ + GF_Err e = GF_OK; + u32 read; + XMLBreaker breaker; + char *tmp; + FILE *xml; + u8 szBOM[3]; + if (!xml_file || !xmlFrom || !xmlTo) return GF_BAD_PARAM; + + memset(&breaker, 0, sizeof(XMLBreaker)); + + xml = gf_fopen(xml_file, "rb"); + if (!xml) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] import failure: file %s not found", xml_file )); + goto exit; + } + //we cannot use files with BOM since the XML position we get from the parser are offsets in the UTF-8 version of the XML. + //TODO: to support files with BOM we would need to serialize on the fly the callback from the sax parser + read = (u32) gf_fread(szBOM, 3, xml); + if (read==3) { + gf_fseek(xml, 0, SEEK_SET); + if ((szBOM[0]==0xFF) || (szBOM[0]==0xFE) || (szBOM[0]==0xEF)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] import failure: XML file %s uses unsupported BOM, please convert to plain UTF-8 or ANSI first", xml_file)); + goto exit; + } + } + + + memset(&breaker, 0, sizeof(XMLBreaker)); + breaker.id_stack = gf_list_new(); + + if (strstr(xmlFrom, ".start")) breaker.from_is_start = GF_TRUE; + else breaker.from_is_end = GF_TRUE; + tmp = strchr(xmlFrom, '.'); + *tmp = 0; + if (stricmp(xmlFrom, "doc")) breaker.from_id = gf_strdup(xmlFrom); + /*doc start pos is 0, no need to look for it*/ + else if (breaker.from_is_start) breaker.from_is_start = GF_FALSE; + *tmp = '.'; + + if (strstr(xmlTo, ".start")) breaker.to_is_start = GF_TRUE; + else breaker.to_is_end = GF_TRUE; + tmp = strchr(xmlTo, '.'); + *tmp = 0; + if (stricmp(xmlTo, "doc")) breaker.to_id = gf_strdup(xmlTo); + /*doc end pos is file size, no need to look for it*/ + else if (breaker.to_is_end) breaker.to_is_end = GF_FALSE; + *tmp = '.'; + + breaker.sax = gf_xml_sax_new(nhml_node_start, nhml_node_end, NULL, &breaker); + e = gf_xml_sax_parse_file(breaker.sax, xml_file, NULL); + gf_xml_sax_del(breaker.sax); + if (e<0) goto exit; + + if (!breaker.to_id) { + breaker.to_pos = gf_fsize(xml); + } + if (breaker.to_pos < breaker.from_pos) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] import failure: xmlFrom %s is located after xmlTo %s", xmlFrom, xmlTo)); + goto exit; + } + + assert(breaker.to_pos > breaker.from_pos); + + + ctx->samp_buffer_size = (u32) (breaker.to_pos - breaker.from_pos); + if (ctx->samp_buffer_alloc < ctx->samp_buffer_size) { + ctx->samp_buffer_alloc = ctx->samp_buffer_size; + ctx->samp_buffer = (char*)gf_realloc(ctx->samp_buffer, sizeof(char)*ctx->samp_buffer_alloc); + } + gf_fseek(xml, breaker.from_pos, SEEK_SET); + if (0 == gf_fread(ctx->samp_buffer, ctx->samp_buffer_size, xml)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[NHMLDmx] Failed to read samp->dataLength\n")); + } + e = GF_OK; + +exit: + if (xml) gf_fclose(xml); + while (gf_list_count(breaker.id_stack)) { + char *id = (char *)gf_list_last(breaker.id_stack); + gf_list_rem_last(breaker.id_stack); + gf_free(id); + } + gf_list_del(breaker.id_stack); + if (breaker.from_id) gf_free(breaker.from_id); + if (breaker.to_id) gf_free(breaker.to_id); + return e; +} + + +#ifndef GPAC_DISABLE_ZLIB + +/*since 0.2.2, we use zlib for xmt/x3d reading to handle gz files*/ +#include + +#define ZLIB_COMPRESS_SAFE 4 + +static GF_Err compress_sample_data(GF_NHMLDmxCtx *ctx, u32 compress_type, char **dict, u32 offset) +{ + z_stream stream; + int err; + u32 size; + + if (!ctx) return GF_OK; + + size = ctx->samp_buffer_size*ZLIB_COMPRESS_SAFE; + if (ctx->zlib_buffer_alloc < size) { + ctx->zlib_buffer_alloc = size; + ctx->zlib_buffer = gf_realloc(ctx->zlib_buffer, sizeof(char)*size); + } + + stream.next_in = (Bytef*) ctx->samp_buffer + offset; + stream.avail_in = (uInt)ctx->samp_buffer_size - offset; + stream.next_out = ( Bytef*)ctx->zlib_buffer; + stream.avail_out = (uInt)size; + stream.zalloc = (alloc_func)NULL; + stream.zfree = (free_func)NULL; + stream.opaque = (voidpf)NULL; + + if (compress_type==1) { + err = deflateInit(&stream, 9); + } else { + err = deflateInit2(&stream, 9, Z_DEFLATED, 16+MAX_WBITS, 8, Z_DEFAULT_STRATEGY); + } + if (err != Z_OK) { + return GF_IO_ERR; + } + if (dict && *dict) { + err = deflateSetDictionary(&stream, (Bytef *)*dict, (u32) strlen(*dict)); + if (err != Z_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] Error assigning dictionary\n")); + deflateEnd(&stream); + return GF_IO_ERR; + } + } + err = deflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + deflateEnd(&stream); + return GF_IO_ERR; + } + if (ctx->samp_buffer_size - offset < stream.total_out) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[NHMLDmx] compressed data (%d) bigger than input data (%d)\n", (u32) stream.total_out, (u32) ctx->samp_buffer_size - offset)); + } + if (dict) { + if (*dict) gf_free(*dict); + *dict = (char*)gf_malloc(sizeof(char) * ctx->samp_buffer_size); + memcpy(*dict, ctx->samp_buffer, ctx->samp_buffer_size); + } + if (ctx->samp_buffer_alloc < stream.total_out) { + ctx->samp_buffer_alloc = (u32) (stream.total_out*2); + ctx->samp_buffer = (char*)gf_realloc(ctx->samp_buffer, ctx->samp_buffer_alloc * sizeof(char)); + } + + memcpy(ctx->samp_buffer + offset, ctx->zlib_buffer, sizeof(char)*stream.total_out); + ctx->samp_buffer_size = (u32) (offset + stream.total_out); + + deflateEnd(&stream); + return GF_OK; +} + +#endif /*GPAC_DISABLE_ZLIB*/ + +#define NHML_SCAN_INT(_fmt, _value) \ + {\ + if (strstr(att->value, "0x")) { u32 __i; sscanf(att->value+2, "%x", &__i); _value = __i; }\ + else if (strstr(att->value, "0X")) { u32 __i; sscanf(att->value+2, "%X", &__i); _value = __i; }\ + else sscanf(att->value, _fmt, &_value); \ + }\ + + +static GF_Err nhmldmx_init_parsing(GF_Filter *filter, GF_NHMLDmxCtx *ctx) +{ + GF_Err e; + Bool inRootOD; + u32 i, tkID, mtype, streamType, codecid, specInfoSize, par_den, par_num; + GF_XMLAttribute *att; + u32 width, height, codec_tag, sample_rate, nb_channels, version, revision, vendor_code, temporal_quality, spatial_quality, h_res, v_res, bit_depth, bits_per_sample; + + u32 dims_profile, dims_level, dims_pathComponents, dims_fullRequestHost, dims_streamType, dims_containsRedundant; + char *textEncoding, *contentEncoding, *dims_content_script_types, *mime_type, *xml_schema_loc, *xmlns; + FILE *nhml; + const GF_PropertyValue *p; + char *auxiliary_mime_types = NULL; + char *ext, szName[1000], szInfo[GF_MAX_PATH], szXmlFrom[1000], szXmlHeaderEnd[1000]; + u8 *specInfo; + char compressor_name[100]; + GF_XMLNode *node; + FILE *finfo; + u64 media_size, last_dts; + char *szRootName, *szSampleName, *szImpName; + + szRootName = ctx->is_dims ? "DIMSStream" : "NHNTStream"; + szSampleName = ctx->is_dims ? "DIMSUnit" : "NHNTSample"; + szImpName = ctx->is_dims ? "DIMS" : "NHML"; + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NOT_SUPPORTED; + } + ctx->src_url = p->value.string; + nhml = gf_fopen(ctx->src_url, "rt"); + if (!nhml) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] Cannot find %s file %s", szImpName, ctx->src_url)); + return GF_URL_ERROR; + } + + szName[0] = 0; + if (!strncmp(ctx->src_url, "gfio://", 7)) { + char *base = gf_file_basename( gf_fileio_translate_url(ctx->src_url) ); + if (base) strcpy(szName, base); + } else { + strcpy(szName, ctx->src_url); + } + ext = gf_file_ext_start(szName); + if (ext) ext[0] = 0; + strcpy(ctx->szMedia, szName); + strcpy(szInfo, szName); + strcat(ctx->szMedia, ".media"); + strcat(szInfo, ".info"); + + ctx->parser = gf_xml_dom_new(); + e = gf_xml_dom_parse(ctx->parser, p->value.string, NULL, NULL); + if (e) { + gf_fclose(nhml); + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] Error parsing %s file: Line %d - %s", szImpName, gf_xml_dom_get_line(ctx->parser), gf_xml_dom_get_error(ctx->parser) )); + return GF_NON_COMPLIANT_BITSTREAM; + } + gf_fclose(nhml); + + ctx->root = gf_xml_dom_get_root(ctx->parser); + if (!ctx->root) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] Error parsing %s file - no root node found", szImpName )); + return GF_NON_COMPLIANT_BITSTREAM; + } + + ctx->dts_inc = 0; + inRootOD = GF_FALSE; + ctx->compress_type = 0; + specInfo = NULL; + +#ifndef GPAC_DISABLE_ZLIB + ctx->use_dict = GF_FALSE; +#endif + + if (stricmp(ctx->root->name, szRootName)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] Error parsing %s file - \"%s\" root expected, got \"%s\"", szImpName, szRootName, ctx->root->name)); + return GF_NON_COMPLIANT_BITSTREAM; + } + + tkID = mtype = streamType = codecid = par_den = par_num = 0; + ctx->timescale = 1000; + i=0; + strcpy(szXmlHeaderEnd, ""); + ctx->header_end = 0; + + width = height = codec_tag = sample_rate = nb_channels = version = revision = vendor_code = temporal_quality = spatial_quality = h_res = v_res = bit_depth = bits_per_sample = 0; + + dims_pathComponents = dims_fullRequestHost = 0; + textEncoding = contentEncoding = dims_content_script_types = mime_type = xml_schema_loc = xmlns = NULL; + dims_profile = dims_level = 255; + dims_streamType = GF_TRUE; + dims_containsRedundant = 1; + + while ((att = (GF_XMLAttribute *)gf_list_enum(ctx->root->attributes, &i))) { + if (!stricmp(att->name, "streamType")) { + NHML_SCAN_INT("%u", streamType) + } else if (!stricmp(att->name, "mediaType") && (strlen(att->value)==4)) { + mtype = GF_4CC(att->value[0], att->value[1], att->value[2], att->value[3]); + } else if (!stricmp(att->name, "mediaSubType") && (strlen(att->value)==4)) { + codec_tag = GF_4CC(att->value[0], att->value[1], att->value[2], att->value[3]); + } else if (!stricmp(att->name, "objectTypeIndication")) { + NHML_SCAN_INT("%u", codecid) + } else if (!stricmp(att->name, "codecID") && (strlen(att->value)==4)) { + codecid = GF_4CC(att->value[0], att->value[1], att->value[2], att->value[3]); + } else if (!stricmp(att->name, "timeScale")) { + NHML_SCAN_INT("%u", ctx->timescale) + } else if (!stricmp(att->name, "width")) { + NHML_SCAN_INT("%u", width) + } else if (!stricmp(att->name, "height")) { + NHML_SCAN_INT("%u", height) + } else if (!stricmp(att->name, "parNum")) { + NHML_SCAN_INT("%u", par_num) + } else if (!stricmp(att->name, "parDen")) { + NHML_SCAN_INT("%u", par_den) + } else if (!stricmp(att->name, "sampleRate")) { + NHML_SCAN_INT("%u", sample_rate) + } else if (!stricmp(att->name, "numChannels")) { + NHML_SCAN_INT("%u", nb_channels) + } else if (!stricmp(att->name, "baseMediaFile")) { + char *url = gf_url_concatenate(ctx->src_url, att->value); + strcpy(ctx->szMedia, url ? url : att->value); + if (url) gf_free(url); + } else if (!stricmp(att->name, "specificInfoFile")) { + char *url = gf_url_concatenate(ctx->src_url, att->value); + strcpy(szInfo, url ? url : att->value); + if (url) gf_free(url); + } else if (!stricmp(att->name, "headerEnd")) { + NHML_SCAN_INT("%u", ctx->header_end) + } else if (!stricmp(att->name, "trackID")) { + NHML_SCAN_INT("%u", tkID) + } else if (!stricmp(att->name, "inRootOD")) { + inRootOD = (!stricmp(att->value, "yes") ); + } else if (!stricmp(att->name, "DTS_increment")) { + NHML_SCAN_INT("%u", ctx->dts_inc) + } else if (!stricmp(att->name, "gzipSamples")) { + if (!stricmp(att->value, "yes") || !stricmp(att->value, "gzip")) + ctx->compress_type = 2; + else if (!stricmp(att->value, "deflate")) + ctx->compress_type = 1; + } else if (!stricmp(att->name, "auxiliaryMimeTypes")) { + auxiliary_mime_types = gf_strdup(att->name); + } +#ifndef GPAC_DISABLE_ZLIB + else if (!stricmp(att->name, "gzipDictionary")) { + u32 d_size; + if (stricmp(att->value, "self")) { + char *url = gf_url_concatenate(ctx->src_url, att->value); + + e = gf_file_load_data(url ? url : att->value, (u8 **) &ctx->dictionary, &d_size); + + if (url) gf_free(url); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] Cannot open dictionary file %s: %s", att->value, gf_error_to_string(e) )); + continue; + } + } + ctx->use_dict = GF_TRUE; + } +#endif + /*unknown desc related*/ + else if (!stricmp(att->name, "compressorName")) { + strncpy(compressor_name, att->value, 99); + compressor_name[99]=0; + } else if (!stricmp(att->name, "codecVersion")) { + NHML_SCAN_INT("%u", version) + } else if (!stricmp(att->name, "codecRevision")) { + NHML_SCAN_INT("%u", revision) + } else if (!stricmp(att->name, "codecVendor") && (strlen(att->value)==4)) { + vendor_code = GF_4CC(att->value[0], att->value[1], att->value[2], att->value[3]); + } else if (!stricmp(att->name, "temporalQuality")) { + NHML_SCAN_INT("%u", temporal_quality) + } else if (!stricmp(att->name, "spatialQuality")) { + NHML_SCAN_INT("%u", spatial_quality) + } else if (!stricmp(att->name, "horizontalResolution")) { + NHML_SCAN_INT("%u", h_res) + } else if (!stricmp(att->name, "verticalResolution")) { + NHML_SCAN_INT("%u", v_res) + } else if (!stricmp(att->name, "bitDepth")) { + NHML_SCAN_INT("%u", bit_depth) + } else if (!stricmp(att->name, "bitsPerSample")) { + NHML_SCAN_INT("%u", bits_per_sample) + } + /*DIMS stuff*/ + else if (!stricmp(att->name, "profile")) { + NHML_SCAN_INT("%u", dims_profile) + } else if (!stricmp(att->name, "level")) { + NHML_SCAN_INT("%u", dims_level) + } else if (!stricmp(att->name, "pathComponents")) { + NHML_SCAN_INT("%u", dims_pathComponents) + } else if (!stricmp(att->name, "useFullRequestHost") && !stricmp(att->value, "yes")) { + dims_fullRequestHost = GF_TRUE; + } else if (!stricmp(att->name, "stream_type") && !stricmp(att->value, "secondary")) { + dims_streamType = GF_FALSE; + } else if (!stricmp(att->name, "contains_redundant")) { + if (!stricmp(att->value, "main")) { + dims_containsRedundant = 1; + } else if (!stricmp(att->value, "redundant")) { + dims_containsRedundant = 2; + } else if (!stricmp(att->value, "main+redundant")) { + dims_containsRedundant = 3; + } + } else if (!stricmp(att->name, "text_encoding") || !stricmp(att->name, "encoding")) { + textEncoding = att->value; + } else if (!stricmp(att->name, "content_encoding")) { + if (!strcmp(att->value, "deflate")) { + contentEncoding = att->value; + ctx->compress_type = 1; + } + else if (!strcmp(att->value, "gzip")) { + contentEncoding = att->value; + ctx->compress_type = 2; + } + } else if (!stricmp(att->name, "content_script_types")) { + dims_content_script_types = att->value; + } else if (!stricmp(att->name, "mime_type")) { + mime_type = att->value; + } else if (!stricmp(att->name, "media_namespace")) { + xmlns = att->value; + } else if (!stricmp(att->name, "media_schema_location")) { + xml_schema_loc = att->value; + } else if (!stricmp(att->name, "xml_namespace")) { + xmlns = att->value; + } else if (!stricmp(att->name, "xml_schema_location")) { + xml_schema_loc = att->value; + } else if (!stricmp(att->name, "xmlHeaderEnd")) { + strcpy(szXmlHeaderEnd, att->value); + } + } + if (sample_rate && !ctx->timescale) { + ctx->timescale = sample_rate; + } + if (!bits_per_sample) { + bits_per_sample = 16; + } + + if (ctx->is_dims || (codec_tag==GF_ISOM_SUBTYPE_3GP_DIMS)) { + mtype = GF_ISOM_MEDIA_DIMS; + codec_tag=GF_ISOM_SUBTYPE_3GP_DIMS; + codecid = GF_CODECID_DIMS; + streamType = GF_STREAM_SCENE; + } + if (gf_file_exists_ex(ctx->szMedia, ctx->src_url)) + ctx->mdia = gf_fopen_ex(ctx->szMedia, ctx->src_url, "rb"); + + specInfoSize = 0; + if (!streamType && !mtype && !codec_tag) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] parsing %s file - StreamType or MediaType not specified", szImpName)); + return GF_NON_COMPLIANT_BITSTREAM; + } + + finfo = NULL; + if (gf_file_exists_ex(szInfo, ctx->src_url)) + finfo = gf_fopen_ex(szInfo, ctx->src_url, "rb"); + + if (finfo) { + e = gf_file_load_data_filep(finfo, (u8 **)&specInfo, &specInfoSize); + gf_fclose(finfo); + if (e) return e; + } else if (ctx->header_end) { + /* for text based streams, the decoder specific info can be at the beginning of the file */ + specInfoSize = ctx->header_end; + specInfo = (char*)gf_malloc(sizeof(char) * (specInfoSize+1)); + specInfoSize = (u32) gf_fread(specInfo, specInfoSize, ctx->mdia); + specInfo[specInfoSize] = 0; + ctx->header_end = specInfoSize; + } else if (strlen(szXmlHeaderEnd)) { + /* for XML based streams, the decoder specific info can be up to some element in the file */ + strcpy(szXmlFrom, "doc.start"); + ctx->samp_buffer_size = 0; + e = nhml_sample_from_xml(ctx, ctx->szMedia, szXmlFrom, szXmlHeaderEnd); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] failed to load XML header: %s", gf_error_to_string(e) )); + return e; + } + + specInfo = (char*)gf_malloc(sizeof(char) * (ctx->samp_buffer_size +1)); + memcpy(specInfo, ctx->samp_buffer, ctx->samp_buffer_size); + specInfoSize = ctx->samp_buffer_size; + specInfo[specInfoSize] = 0; + } + + i=0; + while ((node = (GF_XMLNode *) gf_list_enum(ctx->root->content, &i))) { + if (node->type) continue; + if (stricmp(node->name, "DecoderSpecificInfo") ) continue; + + e = gf_xml_parse_bit_sequence(node, ctx->src_url, &specInfo, &specInfoSize); + if (e) { + if (specInfo) gf_free(specInfo); + return e; + } + break; + } + + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(streamType) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(codecid) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->timescale) ); + if (ctx->reframe) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, &PROP_UINT(GF_TRUE) ); + +#ifndef GPAC_DISABLE_AV_PARSERS + if (!width && !height && specInfo && (codecid==GF_CODECID_MPEG4_PART2)) { + GF_M4VDecSpecInfo dsi; + e = gf_m4v_get_config(specInfo, specInfoSize, &dsi); + if (!e) { + width = dsi.width; + height = dsi.height; + par_num = dsi.par_num; + par_den = dsi.par_den; + } + } +#endif + + if (tkID) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ESID, &PROP_UINT(tkID) ); + if (width) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(width) ); + if (height) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(height) ); + + if (par_den) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, &PROP_FRAC_INT(par_num, par_den) ); + switch (bits_per_sample) { + case 8: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(GF_AUDIO_FMT_U8) ); + break; + case 16: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(GF_AUDIO_FMT_S16) ); + break; + case 24: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(GF_AUDIO_FMT_S24) ); + break; + case 32: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(GF_AUDIO_FMT_S32) ); + break; + default: + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[NHMLDmx] Unsupported audio bit depth %d\n", bits_per_sample)); + break; + } + + if (sample_rate) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(sample_rate) ); + if (nb_channels) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(nb_channels) ); + if (bit_depth) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_BIT_DEPTH_Y, &PROP_UINT(bit_depth) ); + + if (ctx->is_dims) { + if (dims_profile) gf_filter_pid_set_property_str(ctx->opid, "dims:profile", &PROP_UINT(dims_profile) ); + if (dims_level) gf_filter_pid_set_property_str(ctx->opid, "dims:level", &PROP_UINT(dims_level) ); + if (dims_pathComponents) gf_filter_pid_set_property_str(ctx->opid, "dims:pathComponents", &PROP_UINT(dims_pathComponents) ); + if (dims_fullRequestHost) gf_filter_pid_set_property_str(ctx->opid, "dims:fullRequestHost", &PROP_UINT(dims_fullRequestHost) ); + if (dims_streamType) gf_filter_pid_set_property_str(ctx->opid, "dims:streamType", &PROP_BOOL(dims_streamType) ); + if (dims_containsRedundant) gf_filter_pid_set_property_str(ctx->opid, "dims:redundant", &PROP_UINT(dims_containsRedundant) ); + if (textEncoding) gf_filter_pid_set_property_str(ctx->opid, "meta:encoding", &PROP_STRING(textEncoding) ); + if (contentEncoding) gf_filter_pid_set_property_str(ctx->opid, "meta:content_encoding", &PROP_STRING(contentEncoding) ); + if (dims_content_script_types) gf_filter_pid_set_property_str(ctx->opid, "dims:scriptTypes", &PROP_STRING(dims_content_script_types) ); + if (mime_type) gf_filter_pid_set_property_str(ctx->opid, "meta:mime", &PROP_STRING(mime_type) ); + if (xml_schema_loc) gf_filter_pid_set_property_str(ctx->opid, "meta:schemaloc", &PROP_STRING(xml_schema_loc) ); + + } else if (mtype == GF_ISOM_MEDIA_MPEG_SUBT || mtype == GF_ISOM_MEDIA_SUBT || mtype == GF_ISOM_MEDIA_TEXT) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(mtype) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(codec_tag) ); + + if (codec_tag == GF_ISOM_SUBTYPE_STPP) { + if (xmlns) gf_filter_pid_set_property_str(ctx->opid, "meta:xmlns", &PROP_STRING(xmlns) ); + if (xml_schema_loc) gf_filter_pid_set_property_str(ctx->opid, "meta:schemaloc", &PROP_STRING(xml_schema_loc) ); + if (auxiliary_mime_types) gf_filter_pid_set_property_str(ctx->opid, "meta:aux_mimes", &PROP_STRING(auxiliary_mime_types) ); + + } else if (codec_tag == GF_ISOM_SUBTYPE_SBTT) { + if (mime_type) gf_filter_pid_set_property_str(ctx->opid, "meta:mime", &PROP_STRING(mime_type) ); + if (textEncoding) gf_filter_pid_set_property_str(ctx->opid, "meta:encoding", &PROP_STRING(textEncoding) ); + } else if (codec_tag == GF_ISOM_SUBTYPE_STXT) { + if (mime_type) gf_filter_pid_set_property_str(ctx->opid, "meta:mime", &PROP_STRING(mime_type) ); + if (textEncoding) gf_filter_pid_set_property_str(ctx->opid, "meta:encoding", &PROP_STRING(textEncoding) ); + if (contentEncoding) gf_filter_pid_set_property_str(ctx->opid, "meta:content_encoding", &PROP_STRING(contentEncoding) ); + } else { + e = GF_NOT_SUPPORTED; + } + } else if (mtype == GF_ISOM_MEDIA_META) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(mtype) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(codec_tag) ); + + if (codec_tag == GF_ISOM_SUBTYPE_METX) { + if (xmlns) gf_filter_pid_set_property_str(ctx->opid, "meta:xmlns", &PROP_STRING(xmlns) ); + if (xml_schema_loc) gf_filter_pid_set_property_str(ctx->opid, "meta:schemaloc", &PROP_STRING(xml_schema_loc) ); + if (textEncoding) gf_filter_pid_set_property_str(ctx->opid, "meta:encoding", &PROP_STRING(textEncoding) ); + } else if (codec_tag == GF_ISOM_SUBTYPE_METT) { + if (mime_type) gf_filter_pid_set_property_str(ctx->opid, "meta:mime", &PROP_STRING(mime_type) ); + if (textEncoding) gf_filter_pid_set_property_str(ctx->opid, "meta:encoding", &PROP_STRING(textEncoding) ); + } else { + e = GF_NOT_SUPPORTED; + } + } else if (!streamType) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(mtype) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(codec_tag) ); + + if (version) gf_filter_pid_set_property_str(ctx->opid, "gene:version", &PROP_UINT(version) ); + if (revision) gf_filter_pid_set_property_str(ctx->opid, "gene:revision", &PROP_UINT(revision) ); + if (vendor_code) gf_filter_pid_set_property_str(ctx->opid, "gene:vendor", &PROP_UINT(vendor_code) ); + if (temporal_quality) gf_filter_pid_set_property_str(ctx->opid, "gene:temporal_quality", &PROP_UINT(temporal_quality) ); + if (spatial_quality) gf_filter_pid_set_property_str(ctx->opid, "gene:spatial_quality", &PROP_UINT(spatial_quality) ); + if (h_res) gf_filter_pid_set_property_str(ctx->opid, "gene:horizontal_res", &PROP_UINT(h_res) ); + if (v_res) gf_filter_pid_set_property_str(ctx->opid, "gene:vertical_res", &PROP_UINT(v_res) ); + } + + + if (specInfo) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(specInfo, specInfoSize) ); + specInfo = NULL; + specInfoSize = 0; + } + + if (inRootOD) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_IN_IOD, &PROP_BOOL(GF_TRUE) ); + + ctx->media_done = 0; + ctx->current_child_idx = 0; + ctx->last_dts = GF_FILTER_NO_TS; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILEPATH, & PROP_STRING(ctx->szMedia)); + + if (ctx->mdia) { + media_size = gf_fsize(ctx->mdia); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DOWN_SIZE, & PROP_LONGUINT(media_size) ); + } + + if (specInfo) gf_free(specInfo); + if (auxiliary_mime_types) gf_free(auxiliary_mime_types); + + //compute duration + ctx->duration.den = ctx->timescale; + ctx->duration.num = 0; + last_dts = 0; + i=0; + ctx->has_sap = GF_FALSE; + while ((node = (GF_XMLNode *) gf_list_enum(ctx->root->content, &i))) { + u32 j=0; + u64 dts=0; + s32 cts_offset=0; + u64 sample_duration = 0; + if (node->type) continue; + if (stricmp(node->name, szSampleName) ) continue; + + while ( (att = (GF_XMLAttribute *)gf_list_enum(node->attributes, &j))) { + if (!stricmp(att->name, "DTS") || !stricmp(att->name, "time")) { + u32 h, m, s, ms; + u64 dst_val; + if (strchr(att->value, ':') && sscanf(att->value, "%u:%u:%u.%u", &h, &m, &s, &ms) == 4) { + dts = (u64) ( (Double) ( ((h*3600.0 + m*60.0 + s)*1000 + ms) / 1000.0) * ctx->timescale ); + } else if (sscanf(att->value, ""LLU, &dst_val)==1) { + dts = dst_val; + } + } + else if (!stricmp(att->name, "CTSOffset")) cts_offset = atoi(att->value); + else if (!stricmp(att->name, "duration") ) sscanf(att->value, ""LLU, &sample_duration); + else if (!stricmp(att->name, "isRAP") ) ctx->has_sap = GF_TRUE; + } + last_dts = ctx->duration.num; + if (dts) ctx->duration.num = (u32) (dts + cts_offset); + if (sample_duration) { + last_dts = 0; + ctx->duration.num += (u32) sample_duration; + } else if (ctx->dts_inc) { + last_dts = 0; + ctx->duration.num += ctx->dts_inc; + } + } + if (last_dts) { + ctx->duration.num += (u32) (ctx->duration.num - last_dts); + } + //assume image, one sec (default for old arch) + if ((streamType==4) && !ctx->duration.num) { + ctx->is_img = GF_TRUE; + ctx->duration.num =ctx->duration.den; + } + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration) ); + + return e; +} + +void nhml_get_bs(GF_BitStream **bs, char *data, u32 size, u32 mode) +{ + if (*bs) gf_bs_reassign_buffer(*bs, data, size); + else (*bs) = gf_bs_new(data, size, mode); +} + +static GF_Err nhmldmx_send_sample(GF_Filter *filter, GF_NHMLDmxCtx *ctx) +{ + GF_XMLNode *node, *childnode; + u64 sample_duration = 0; + char szMediaTemp[GF_MAX_PATH], szXmlFrom[1000], szXmlTo[1000]; + char *szSubSampleName = ctx->is_dims ? "DIMSSubUnit" : "NHNTSubSample"; + + while ((node = (GF_XMLNode *) gf_list_enum(ctx->root->content, &ctx->current_child_idx))) { + u8 *data; + GF_FilterPacket *pck; + u32 j, dims_flags; + GF_FilterSAPType sap_type; + GF_XMLAttribute *att; + u64 dts=0; + GF_Err e=GF_OK; + s32 cts_offset; + u64 offset=0, byte_offset = GF_FILTER_NO_BO; + u32 nb_subsamples = 0; + Bool redundant_rap, append, has_subbs, first_subsample_is_first = GF_FALSE; + u32 compress_type; + char *base_data = NULL; + if (node->type) continue; + if (stricmp(node->name, ctx->is_dims ? "DIMSUnit" : "NHNTSample") ) continue; + + strcpy(szMediaTemp, ""); + strcpy(szXmlFrom, ""); + strcpy(szXmlTo, ""); + + /*by default handle all samples as contiguous*/ + ctx->samp_buffer_size = 0; + dims_flags = 0; + append = GF_FALSE; + compress_type = ctx->compress_type; + sample_duration = 0; + redundant_rap = 0; + sap_type = ctx->has_sap ? GF_FILTER_SAP_NONE : GF_FILTER_SAP_1; + + cts_offset = 0; + if (ctx->last_dts != GF_FILTER_NO_TS) + dts = ctx->last_dts; + else + dts = 0; + + ctx->sample_num++; + + + j=0; + while ( (att = (GF_XMLAttribute *)gf_list_enum(node->attributes, &j))) { + if (!stricmp(att->name, "DTS") || !stricmp(att->name, "time")) { + u32 h, m, s, ms; + u64 dst_val; + if (strchr(att->value, ':') && sscanf(att->value, "%u:%u:%u.%u", &h, &m, &s, &ms) == 4) { + dts = (u64) ( (Double) ( ((h*3600.0 + m*60.0 + s)*1000 + ms) / 1000.0) * ctx->timescale ); + } else if (sscanf(att->value, ""LLU, &dst_val)==1) { + dts = dst_val; + } + } + else if (!stricmp(att->name, "CTSOffset")) cts_offset = atoi(att->value); + else if (!stricmp(att->name, "isRAP") ) { + sap_type = (!stricmp(att->value, "yes")) ? GF_FILTER_SAP_1 : GF_FILTER_SAP_NONE; + } + else if (!stricmp(att->name, "isSyncShadow")) redundant_rap = !stricmp(att->value, "yes") ? 1 : 0; + else if (!stricmp(att->name, "SAPType") ) sap_type = atoi(att->value); + else if (!stricmp(att->name, "mediaOffset")) offset = (s64) atof(att->value) ; + else if (!stricmp(att->name, "dataLength")) ctx->samp_buffer_size = atoi(att->value); + else if (!stricmp(att->name, "mediaFile")) { + if (!strncmp(att->value, "data:", 5)) { + char *base = strstr(att->value, "base64,"); + if (!base) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[NHMLDmx] Data encoding scheme not recognized in sample %d - skipping\n", ctx->sample_num)); + } else { + base_data = att->value; + } + } else { + char *url = gf_url_concatenate(ctx->src_url, att->value); + strcpy(szMediaTemp, url ? url : att->value); + if (url) gf_free(url); + } + } + else if (!stricmp(att->name, "xmlFrom")) strcpy(szXmlFrom, att->value); + else if (!stricmp(att->name, "xmlTo")) strcpy(szXmlTo, att->value); + /*DIMS flags*/ + else if (!stricmp(att->name, "is-Scene") && !stricmp(att->value, "yes")) + dims_flags |= GF_DIMS_UNIT_S; + else if (!stricmp(att->name, "is-RAP") && !stricmp(att->value, "yes")) { + dims_flags |= GF_DIMS_UNIT_M; + sap_type = GF_FILTER_SAP_1; + } + else if (!stricmp(att->name, "is-redundant") && !stricmp(att->value, "yes")) + dims_flags |= GF_DIMS_UNIT_I; + else if (!stricmp(att->name, "redundant-exit") && !stricmp(att->value, "yes")) + dims_flags |= GF_DIMS_UNIT_D; + else if (!stricmp(att->name, "priority") && !stricmp(att->value, "high")) + dims_flags |= GF_DIMS_UNIT_P; + else if (!stricmp(att->name, "compress") && !stricmp(att->value, "yes")) + dims_flags |= GF_DIMS_UNIT_C; + else if (!stricmp(att->name, "duration") ) + sscanf(att->value, ""LLU, &sample_duration); + } + if (sap_type==GF_FILTER_SAP_1) + dims_flags |= GF_DIMS_UNIT_M; + + if (ctx->is_dims) + compress_type = (dims_flags & GF_DIMS_UNIT_C) ? 2 : 0 ; + + if (ctx->is_img) sample_duration = ctx->duration.den; + + has_subbs = GF_FALSE; + j=0; + while ((childnode = (GF_XMLNode *) gf_list_enum(node->content, &j))) { + if (childnode->type) continue; + if (!stricmp(childnode->name, "BS")) { + has_subbs = GF_TRUE; + break; + } + } + + if (strlen(szXmlFrom) && strlen(szXmlTo)) { + char *xml_file; + if (strlen(szMediaTemp)) xml_file = szMediaTemp; + else xml_file = ctx->szMedia; + ctx->samp_buffer_size = 0; + e = nhml_sample_from_xml(ctx, xml_file, szXmlFrom, szXmlTo); + } else if (ctx->is_dims && !strlen(szMediaTemp)) { + + char *content = gf_xml_dom_serialize(node, GF_TRUE); + + ctx->samp_buffer_size = 3 + (u32) strlen(content); + if (ctx->samp_buffer_alloc < ctx->samp_buffer_size) { + ctx->samp_buffer_alloc = ctx->samp_buffer_size; + ctx->samp_buffer = gf_realloc(ctx->samp_buffer, ctx->samp_buffer_size); + } + nhml_get_bs(&ctx->bs_w, ctx->samp_buffer, ctx->samp_buffer_size, GF_BITSTREAM_WRITE); + gf_bs_write_u16(ctx->bs_w, ctx->samp_buffer_size - 2); + gf_bs_write_u8(ctx->bs_w, (u8) dims_flags); + gf_bs_write_data(ctx->bs_w, content, (ctx->samp_buffer_size - 3)); + gf_free(content); + + /*same DIMS unit*/ + if (ctx->last_dts == dts) + append = GF_TRUE; + + } else if (has_subbs) { + gf_bs_reassign_buffer(ctx->bs_w, ctx->samp_buffer, ctx->samp_buffer_alloc); + gf_xml_parse_bit_sequence_bs(node, ctx->src_url, ctx->bs_w); + gf_bs_get_content(ctx->bs_w, &ctx->samp_buffer, &ctx->samp_buffer_size); + if (ctx->samp_buffer_size > ctx->samp_buffer_alloc) + ctx->samp_buffer_alloc = ctx->samp_buffer_size; + + } else if (base_data) { + char *start = strchr(base_data, ','); + if (start) { + u32 len = (u32)strlen(start+1); + if (len > ctx->samp_buffer_alloc) { + ctx->samp_buffer_alloc = len; + ctx->samp_buffer = gf_realloc(ctx->samp_buffer, sizeof(char)*ctx->samp_buffer_alloc); + } + ctx->samp_buffer_size = gf_base64_decode(start, len, ctx->samp_buffer, ctx->samp_buffer_alloc); + } + } else { + Bool close = GF_FALSE; + FILE *f = ctx->mdia; + + j = 0; + while ((childnode = (GF_XMLNode *)gf_list_enum(node->content, &j))) { + if (childnode->type) continue; + if (!stricmp(childnode->name, szSubSampleName)) { + nb_subsamples++; + } + } + + if (strlen(szMediaTemp)) { + f = gf_fopen(szMediaTemp, "rb"); + if (!f) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] import failure in sample %d: file %s not found", ctx->sample_num, close ? szMediaTemp : ctx->szMedia)); + return GF_NON_COMPLIANT_BITSTREAM; + } + close = GF_TRUE; + if (offset) gf_fseek(f, offset, SEEK_SET); + //when using dedicated source files per samples, we don't allow for data reference yet + } else { + if (!offset) offset = ctx->media_done; + byte_offset = offset; + } + + if (f) { + if (!ctx->samp_buffer_size) { + u64 ssize = gf_fsize(f); + assert(ssize < 0x80000000); + ctx->samp_buffer_size = (u32) ssize; + } + gf_fseek(f, offset, SEEK_SET); + + if (ctx->is_dims) { + u32 read; + if (ctx->samp_buffer_size + 3 > ctx->samp_buffer_alloc) { + ctx->samp_buffer_alloc = ctx->samp_buffer_size + 3; + ctx->samp_buffer = (char*)gf_realloc(ctx->samp_buffer, sizeof(char) * ctx->samp_buffer_alloc); + } + nhml_get_bs(&ctx->bs_w, ctx->samp_buffer, ctx->samp_buffer_alloc, GF_BITSTREAM_WRITE); + gf_bs_write_u16(ctx->bs_w, ctx->samp_buffer_size+1); + gf_bs_write_u8(ctx->bs_w, (u8) dims_flags); + read = (u32) gf_fread( ctx->samp_buffer + 3, ctx->samp_buffer_size, f); + if (ctx->samp_buffer_size != read) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] Failed to fully read sample %d: dataLength %d read %d\n", ctx->sample_num, ctx->samp_buffer_size, read)); + } + ctx->samp_buffer_size += 3; + + /*same DIMS unit*/ + if (ctx->last_dts == dts) + append = GF_TRUE; + } else { + u32 read; + if (ctx->samp_buffer_alloc < ctx->samp_buffer_size) { + ctx->samp_buffer_alloc = ctx->samp_buffer_size; + ctx->samp_buffer = (char*)gf_realloc(ctx->samp_buffer, sizeof(char) * ctx->samp_buffer_alloc); + } + read = (u32) gf_fread(ctx->samp_buffer, ctx->samp_buffer_size, f); + if (ctx->samp_buffer_size != read) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] Failed to fully read sample %d: dataLength %d read %d\n", ctx->sample_num, ctx->samp_buffer_size, read)); + } + } + if (close) gf_fclose(f); + } else if (!nb_subsamples) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] No media file associated with sample %d!\n", ctx->sample_num)); + e = GF_URL_ERROR; + } + } + + if (e) return e; + + //override DIMS flags + if (ctx->is_dims) { + if (strstr(ctx->samp_buffer + 3, "svg ")) dims_flags |= GF_DIMS_UNIT_S; + if (dims_flags & GF_DIMS_UNIT_S) dims_flags |= GF_DIMS_UNIT_P; + ctx->samp_buffer[2] = dims_flags; + } + + if (compress_type) { +#ifndef GPAC_DISABLE_ZLIB + e = compress_sample_data(ctx, compress_type, ctx->use_dict ? &ctx->dictionary : NULL, ctx->is_dims ? 3 : 0); + if (e) return e; + + if (ctx->is_dims) { + nhml_get_bs(&ctx->bs_w, ctx->samp_buffer, ctx->samp_buffer_size, GF_BITSTREAM_WRITE); + gf_bs_write_u16(ctx->bs_w, ctx->samp_buffer_size-2); + } +#else + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] Error: your version of GPAC was compiled with no libz support. Abort.")); + return GF_NOT_SUPPORTED; +#endif + } + + if (ctx->is_dims && (ctx->samp_buffer_size > 0xFFFF)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] DIMS import failure: sample %d data is too long - maximum size allowed: 65532 bytes", ctx->sample_num)); + return GF_NON_COMPLIANT_BITSTREAM; + } + if (ctx->samp_buffer_size) { + pck = gf_filter_pck_new_alloc(ctx->opid, ctx->samp_buffer_size, &data); + memcpy(data, ctx->samp_buffer, ctx->samp_buffer_size); + gf_filter_pck_set_framing(pck, append ? GF_FALSE : GF_TRUE, nb_subsamples ? GF_FALSE : GF_TRUE); + if (!append) { + gf_filter_pck_set_sap(pck, sap_type); + gf_filter_pck_set_dts(pck, dts); + gf_filter_pck_set_cts(pck, dts+cts_offset); + if (redundant_rap && !ctx->is_dims) gf_filter_pck_set_dependency_flags(pck, 0x1); + + if (sample_duration || ctx->dts_inc) + gf_filter_pck_set_duration(pck, sample_duration ? (u32) sample_duration : ctx->dts_inc); + + if (byte_offset != GF_FILTER_NO_BO) + gf_filter_pck_set_byte_offset(pck, byte_offset); + + if (ctx->in_seek) { + if (dts+cts_offset >= ctx->start_range * ctx->timescale) + ctx->in_seek = GF_FALSE; + else + gf_filter_pck_set_seek_flag(pck, GF_TRUE); + } + } + gf_filter_pck_send(pck); + } else { + first_subsample_is_first = GF_TRUE; + } + + if (nb_subsamples) { + if (ctx->samp_buffer_alloc<14*nb_subsamples) { + ctx->samp_buffer_alloc = 14*nb_subsamples; + ctx->samp_buffer = gf_realloc(ctx->samp_buffer, ctx->samp_buffer_alloc); + } + assert(ctx->samp_buffer); + nhml_get_bs(&ctx->bs_w, ctx->samp_buffer, ctx->samp_buffer_alloc, GF_BITSTREAM_WRITE); + } + + j = 0; + while (!append && nb_subsamples && (childnode = (GF_XMLNode *)gf_list_enum(node->content, &j))) { + if (childnode->type) continue; + if (!stricmp(childnode->name, szSubSampleName)) { + u32 k = 0; + while ((att = (GF_XMLAttribute *)gf_list_enum(childnode->attributes, &k))) { + if (!stricmp(att->name, "mediaFile")) { + u64 subsMediaFileSize = 0; + FILE *f = NULL; + char *sub_file_url = gf_url_concatenate(ctx->src_url, att->value); + if (sub_file_url) { + f = gf_fopen(sub_file_url, "rb"); + gf_free(sub_file_url); + } + + if (!f) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[NHMLDmx] Error: mediaFile \"%s\" not found for subsample in sample %d. Abort.\n", att->value, ctx->sample_num)); + return GF_URL_ERROR; + } + subsMediaFileSize = gf_fsize(f); + assert(subsMediaFileSize < 0x80000000); + + //send continuation frame + pck = gf_filter_pck_new_alloc(ctx->opid, (u32) subsMediaFileSize, &data); + subsMediaFileSize = (u32) gf_fread(data, (u32) subsMediaFileSize, f); + gf_fclose(f); + + nb_subsamples--; + if (first_subsample_is_first) { + gf_filter_pck_set_framing(pck, GF_TRUE, nb_subsamples ? GF_FALSE : GF_TRUE); + + gf_filter_pck_set_sap(pck, sap_type); + gf_filter_pck_set_dts(pck, dts); + gf_filter_pck_set_cts(pck, dts+cts_offset); + if (redundant_rap && !ctx->is_dims) gf_filter_pck_set_dependency_flags(pck, 0x1); + + if (sample_duration || ctx->dts_inc) + gf_filter_pck_set_duration(pck, sample_duration ? (u32) sample_duration : ctx->dts_inc); + + if (ctx->in_seek) { + if (dts+cts_offset >= ctx->start_range * ctx->timescale) + ctx->in_seek = GF_FALSE; + else + gf_filter_pck_set_seek_flag(pck, GF_TRUE); + } + + first_subsample_is_first = GF_FALSE; + } else { + gf_filter_pck_set_framing(pck, GF_FALSE, nb_subsamples ? GF_FALSE : GF_TRUE); + } + + + gf_bs_write_u32(ctx->bs_w, 0); //flags + gf_bs_write_u32(ctx->bs_w, (u32) subsMediaFileSize); + gf_bs_write_u32(ctx->bs_w, 0); //reserved + gf_bs_write_u8(ctx->bs_w, 0); //priority + gf_bs_write_u8(ctx->bs_w, 0); //discardable + + if (!nb_subsamples) { + u32 subs_size = (u32) gf_bs_get_position(ctx->bs_w); + gf_filter_pck_set_property(pck, GF_PROP_PCK_SUBS, &PROP_DATA(ctx->samp_buffer, subs_size) ); + } + + gf_filter_pck_send(pck); + } + } + } + } + + ctx->last_dts = dts; + + if (sample_duration) + ctx->last_dts += sample_duration; + else + ctx->last_dts += ctx->dts_inc; + ctx->media_done += ctx->samp_buffer_size; + + if (gf_filter_pid_would_block(ctx->opid)) + return GF_OK; + } + ctx->parsing_state = 2; + return GF_OK; +} + +GF_Err nhmldmx_process(GF_Filter *filter) +{ + GF_NHMLDmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + GF_Err e; + Bool start, end; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (pck) { + gf_filter_pck_get_framing(pck, &start, &end); + //for now we only work with complete files + assert(end); + } + + + //need init ? + switch (ctx->parsing_state) { + case 0: + e = nhmldmx_init_parsing(filter, ctx); + if (e) { + ctx->parsing_state = 3; + return e; + } + ctx->parsing_state = 1; + //fall-through + case 1: + if (!ctx->is_playing) return GF_OK; + + e = nhmldmx_send_sample(filter, ctx); + if (e) return e; + break; + case 2: + default: + if (pck) gf_filter_pid_drop_packet(ctx->ipid); + if (ctx->opid) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + break; + } + return GF_OK; +} + +GF_Err nhmldmx_initialize(GF_Filter *filter) +{ +// GF_NHMLDmxCtx *ctx = gf_filter_get_udta(filter); + return GF_OK; +} + +void nhmldmx_finalize(GF_Filter *filter) +{ + GF_NHMLDmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->mdia) gf_fclose(ctx->mdia); + if (ctx->parser) + gf_xml_dom_del(ctx->parser); + +#ifndef GPAC_DISABLE_ZLIB + if (ctx->dictionary) gf_free(ctx->dictionary); +#endif + if (ctx->bs_r) gf_bs_del(ctx->bs_r); + if (ctx->bs_w) gf_bs_del(ctx->bs_w); + if (ctx->samp_buffer) gf_free(ctx->samp_buffer); + if (ctx->zlib_buffer) gf_free(ctx->zlib_buffer); +} + + +#define OFFS(_n) #_n, offsetof(GF_NHMLDmxCtx, _n) +static const GF_FilterArgs GF_NHMLDmxArgs[] = +{ + { OFFS(reframe), "force reparsing of referenced content", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(index), "indexing window length", GF_PROP_DOUBLE, "1.0", NULL, 0}, + {0} +}; + + +static const GF_FilterCapability NHMLDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "nhml|dims|dml"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "application/x-nhml|application/dims"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), +}; + +GF_FilterRegister NHMLDmxRegister = { + .name = "nhmlr", + GF_FS_SET_DESCRIPTION("NHML parser") + GF_FS_SET_HELP("This filter reads NHML files/data to produce a media PID and frames.\n" + "NHML documentation is available at https://wiki.gpac.io/NHML-Format\n") + .private_size = sizeof(GF_NHMLDmxCtx), + .args = GF_NHMLDmxArgs, + .initialize = nhmldmx_initialize, + .finalize = nhmldmx_finalize, + SETCAPS(NHMLDmxCaps), + .configure_pid = nhmldmx_configure_pid, + .process = nhmldmx_process, + .process_event = nhmldmx_process_event +}; + +const GF_FilterRegister *nhmldmx_register(GF_FilterSession *session) +{ + return &NHMLDmxRegister; +} + diff --git a/src/filters/dmx_nhnt.c b/src/filters/dmx_nhnt.c new file mode 100644 index 0000000..9c42b7c --- /dev/null +++ b/src/filters/dmx_nhnt.c @@ -0,0 +1,513 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2020 + * All rights reserved + * + * This file is part of GPAC / NHNT demuxer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_AV_PARSERS +#include +#endif + + +enum +{ + GF_MEDIA_TYPE_NHNL = GF_4CC('N','H','n','l'), + GF_MEDIA_TYPE_NHNT = GF_4CC('N','H','n','t') +}; + +typedef struct +{ + u64 pos; + Double duration; +} NHNTIdx; + +typedef struct +{ + //opts + Bool reframe; + Double index; + + GF_FilterPid *ipid; + GF_FilterPid *opid; + + Double start_range; + u64 first_dts; + + Bool is_playing; + GF_Fraction64 duration; + Bool need_reassign, in_seek; + + Bool initial_play_done; + Bool header_parsed; + u32 sig; + u32 timescale; + + FILE *mdia; + GF_BitStream *bs; + + NHNTIdx *indexes; + u32 index_alloc_size, index_size; + GF_Err in_error; +} GF_NHNTDmxCtx; + + + +static void nhntdmx_check_dur(GF_NHNTDmxCtx *ctx) +{ + GF_Fraction64 dur; + FILE *stream; + GF_BitStream *bs; + u32 sig, timescale; + u64 dts_prev; + u32 prev_dur; + u64 cur_dur=0; + const GF_PropertyValue *p; + + if (ctx->duration.num) return; + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) { + ctx->duration.num = 1; + return; + } + if (ctx->duration.num) return; + + stream = gf_fopen(p->value.string, "rb"); + if (!stream) return; + +// ctx->index_size = 0; + + bs = gf_bs_from_file(stream, GF_BITSTREAM_READ); + + + sig = GF_4CC(gf_bs_read_u8(bs), gf_bs_read_u8(bs), gf_bs_read_u8(bs), gf_bs_read_u8(bs)); + if (sig == GF_MEDIA_TYPE_NHNT) sig = 0; + else if (sig == GF_MEDIA_TYPE_NHNL) sig = 1; + else { + gf_bs_del(bs); + gf_fclose(stream); + return; + } + gf_bs_read_u8(bs); /*version*/ + gf_bs_read_u8(bs); //streamType + gf_bs_read_u8(bs); //oti + gf_bs_read_u16(bs); + gf_bs_read_u24(bs); //bufferSizeDB + gf_bs_read_u32(bs); //avgBitrate + gf_bs_read_u32(bs); //maxBitrate + timescale = gf_bs_read_u32(bs); + + dur.num = 0; + dts_prev = 0; + prev_dur = 0; + while (gf_bs_available(bs)) { + u64 dts; + u64 pos =gf_bs_get_position(bs); + /*u32 len = */gf_bs_read_u24(bs); + Bool is_rap = gf_bs_read_int(bs, 1); + /*Bool is_start = (Bool)*/gf_bs_read_int(bs, 1); + /*Bool is_end = (Bool)*/gf_bs_read_int(bs, 1); + /*3 reserved + AU type (2)*/ + gf_bs_read_int(bs, 5); + + if (sig) { + /*offset = */gf_bs_read_u64(bs); + /*cts = */gf_bs_read_u64(bs); + dts = gf_bs_read_u64(bs); + } else { + /*offset = */gf_bs_read_u32(bs); + /*cts = */gf_bs_read_u32(bs); + dts = gf_bs_read_u32(bs); + } + if (!ctx->first_dts) + ctx->first_dts = 1 + dts; + prev_dur = (u32) (dts - dts_prev); + dur.num += prev_dur; + dts_prev = dts; + + cur_dur += prev_dur; + //only index at I-frame start + if (is_rap && (cur_dur >= ctx->index * ctx->timescale) ) { + if (!ctx->index_alloc_size) ctx->index_alloc_size = 10; + else if (ctx->index_alloc_size == ctx->index_size) ctx->index_alloc_size *= 2; + ctx->indexes = gf_realloc(ctx->indexes, sizeof(NHNTIdx)*ctx->index_alloc_size); + ctx->indexes[ctx->index_size].pos = pos; + ctx->indexes[ctx->index_size].duration = (Double) dur.num; + ctx->indexes[ctx->index_size].duration /= ctx->timescale; + ctx->index_size ++; + cur_dur = 0; + } + + } + gf_bs_del(bs); + gf_fclose(stream); + + dur.num += prev_dur; + dur.den = timescale; + if (!ctx->duration.num || (ctx->duration.num * dur.den != dur.num * ctx->duration.den)) { + ctx->duration = dur; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + } +} + + +GF_Err nhntdmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_NHNTDmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + //gf_filter_pid_remove(st->opid); + + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + + return GF_OK; +} + +static Bool nhntdmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_FilterEvent fevt; + u64 file_pos = 0; + u32 i; + GF_NHNTDmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (ctx->is_playing && (ctx->start_range == evt->play.start_range)) { + return GF_TRUE; + } + nhntdmx_check_dur(ctx); + ctx->start_range = evt->play.start_range; + ctx->is_playing = GF_TRUE; + + if (ctx->start_range) { + for (i=1; iindex_size; i++) { + if (ctx->indexes[i].duration>ctx->start_range) { + file_pos = ctx->indexes[i-1].pos; + break; + } + } + } + + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!file_pos) + return GF_TRUE; + } + //post a seek + ctx->need_reassign = GF_TRUE; + ctx->in_seek = GF_TRUE; + ctx->header_parsed = file_pos ? GF_TRUE : GF_FALSE; + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + ctx->is_playing = GF_FALSE; + //don't cancel event + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +GF_Err nhntdmx_process(GF_Filter *filter) +{ + GF_NHNTDmxCtx *ctx = gf_filter_get_udta(filter); + const GF_PropertyValue *p; + GF_FilterPacket *pck; + u32 pkt_size; + Bool start, end; + u8 *data; + if (ctx->in_error) + return ctx->in_error; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) gf_filter_pid_set_eos(ctx->opid); + return GF_OK; + } + data = (u8 *)gf_filter_pck_get_data(pck, &pkt_size); + gf_filter_pck_get_framing(pck, &start, &end); + //for now we only work with complete files + assert(end); + + if (!ctx->bs) { + ctx->bs = gf_bs_new(data, pkt_size, GF_BITSTREAM_READ); + } else if (ctx->need_reassign) { + ctx->need_reassign = GF_FALSE; + gf_bs_reassign_buffer(ctx->bs, data, pkt_size); + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NOT_SUPPORTED; + } + + if (!ctx->header_parsed) { + if (!ctx->opid) { + char *ext; + char szMedia[1000]; + char *dsi; + u32 dsi_size; + u32 val, oti; + u64 media_size; + Bool use_gfio = GF_FALSE; + FILE *finfo; + + + if (!strncmp(p->value.string, "gfio://", 7)) { + use_gfio = GF_TRUE; + strcpy(szMedia, gf_fileio_translate_url(p->value.string) ); + } else { + strcpy(szMedia, p->value.string); + } + + ext = strrchr(szMedia, '.'); + if (ext) ext[0] = 0; + strcat(szMedia, ".media"); + ctx->mdia = gf_fopen_ex(szMedia, p->value.string, "rb"); + + if (!ctx->mdia) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[NHNT] Cannot find MEDIA file %s\n", szMedia)); + gf_filter_pid_drop_packet(ctx->ipid); + gf_filter_pid_set_discard(ctx->ipid, GF_TRUE); + ctx->in_error = GF_URL_ERROR; + return GF_URL_ERROR; + } + + ctx->sig = GF_4CC(gf_bs_read_u8(ctx->bs), gf_bs_read_u8(ctx->bs), gf_bs_read_u8(ctx->bs), gf_bs_read_u8(ctx->bs)); + if (ctx->sig == GF_MEDIA_TYPE_NHNT) ctx->sig = 0; + else if (ctx->sig == GF_MEDIA_TYPE_NHNL) ctx->sig = 1; + else { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[NHNT] Invalid NHNT signature %s\n", gf_4cc_to_str(ctx->sig) )); + gf_filter_pid_drop_packet(ctx->ipid); + gf_filter_pid_set_discard(ctx->ipid, GF_TRUE); + ctx->in_error = GF_NON_COMPLIANT_BITSTREAM; + return GF_NON_COMPLIANT_BITSTREAM; + } + ctx->opid = gf_filter_pid_new(filter); + + //we change the file path of the pid to point to the media stream, not the nhnt stream + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILEPATH, & PROP_STRING(szMedia)); + + media_size = gf_fsize(ctx->mdia); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DOWN_SIZE, & PROP_LONGUINT(media_size) ); + + /*version*/ + gf_bs_read_u8(ctx->bs); + + val = gf_bs_read_u8(ctx->bs); + if (val == GF_STREAM_OD) { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[NHNT] OD stream detected, might result in broken import\n")); + } + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(val)); + + oti = gf_bs_read_u8(ctx->bs); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(oti)); + + gf_bs_read_u16(ctx->bs); + /*val = */gf_bs_read_u24(ctx->bs); //bufferSizeDB + val = gf_bs_read_u32(ctx->bs); //avgBitrate + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_BITRATE, &PROP_UINT(val)); + /*val = */gf_bs_read_u32(ctx->bs); //maxBitrate + ctx->timescale = gf_bs_read_u32(ctx->bs); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->timescale)); + + if (use_gfio) { + strcpy(szMedia, gf_fileio_translate_url(p->value.string) ); + } else { + strcpy(szMedia, p->value.string); + } + ext = gf_file_ext_start(szMedia); + if (ext) ext[0] = 0; + strcat(szMedia, ".info"); + + finfo = gf_fopen_ex(szMedia, p->value.string, "rb"); + dsi = NULL; + if (finfo) { + if ( gf_file_load_data_filep(finfo, (u8 **) &dsi, &dsi_size) != GF_OK) { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[NHNT] Failed to read decoder config\n")); + } else { + +#ifndef GPAC_DISABLE_AV_PARSERS + if (oti==GF_CODECID_MPEG4_PART2) { + GF_M4VDecSpecInfo cfg; + GF_Err e = gf_m4v_get_config(dsi, dsi_size, &cfg); + if (e>=0) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(cfg.width) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(cfg.height) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PROFILE_LEVEL, &PROP_UINT(cfg.VideoPL) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, &PROP_FRAC_INT(cfg.par_num, cfg.par_den) ); + } + } +#endif + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_size) ); + } + gf_fclose(finfo); + } + + if (ctx->reframe) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, &PROP_BOOL(ctx->reframe) ); + + nhntdmx_check_dur(ctx); + ctx->header_parsed = GF_TRUE; + } else { + /*version*/ + gf_bs_skip_bytes(ctx->bs, 4+1+1+1+2+3+4+4+4); + ctx->header_parsed = GF_TRUE; + } + } + + if (!ctx->is_playing) return GF_OK; + + while (gf_bs_available(ctx->bs) >= 16 ) { + GF_FilterPacket *dst_pck; + u8 *output; + u64 dts, cts, offset; + u32 res, len = gf_bs_read_u24(ctx->bs); + Bool is_rap = gf_bs_read_int(ctx->bs, 1); + Bool is_start = (Bool)gf_bs_read_int(ctx->bs, 1); + Bool is_end = (Bool)gf_bs_read_int(ctx->bs, 1); + /*3 reserved + AU type (2)*/ + gf_bs_read_int(ctx->bs, 5); + + if (ctx->sig) { + offset = gf_bs_read_u64(ctx->bs); + cts = gf_bs_read_u64(ctx->bs); + dts = gf_bs_read_u64(ctx->bs); + } else { + offset = gf_bs_read_u32(ctx->bs); + cts = gf_bs_read_u32(ctx->bs); + dts = gf_bs_read_u32(ctx->bs); + } + + if (!ctx->first_dts) + ctx->first_dts = 1 + dts; + + if (ctx->in_seek) { + Double now = (Double) (dts - (ctx->first_dts-1) ); + now /= ctx->timescale; + if (now >= ctx->start_range) ctx->in_seek = GF_FALSE; + } + gf_fseek(ctx->mdia, offset, SEEK_SET); + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, len, &output); + res = (u32) gf_fread(output, len, ctx->mdia); + if (res != len) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[NHNT] Read failure, expecting %d bytes got %d", len, res)); + } + gf_filter_pck_set_framing(dst_pck, is_start, is_end); + if (is_rap) + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + gf_filter_pck_set_dts(dst_pck, dts); + gf_filter_pck_set_cts(dst_pck, cts); + gf_filter_pck_set_byte_offset(dst_pck, offset); + if (ctx->in_seek) gf_filter_pck_set_seek_flag(dst_pck, GF_TRUE); + gf_filter_pck_send(dst_pck); + + if (gf_filter_pid_would_block(ctx->opid)) + return GF_OK; + } + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + +GF_Err nhntdmx_initialize(GF_Filter *filter) +{ +// GF_NHNTDmxCtx *ctx = gf_filter_get_udta(filter); + return GF_OK; +} + +void nhntdmx_finalize(GF_Filter *filter) +{ + GF_NHNTDmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->mdia) gf_fclose(ctx->mdia); + if (ctx->bs) gf_bs_del(ctx->bs); + if (ctx->indexes) gf_free(ctx->indexes); +} + + +#define OFFS(_n) #_n, offsetof(GF_NHNTDmxCtx, _n) +static const GF_FilterArgs GF_NHNTDmxArgs[] = +{ + { OFFS(reframe), "force reparsing of referenced content", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(index), "indexing window length", GF_PROP_DOUBLE, "1.0", NULL, 0}, + {0} +}; + + +static const GF_FilterCapability NHNTDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "nhnt"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "application/x-nhnt"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), +}; + + +GF_FilterRegister NHNTDmxRegister = { + .name = "nhntr", + GF_FS_SET_DESCRIPTION("NHNT reader") + GF_FS_SET_HELP("This filter reads NHNT files/data to produce a media PID and frames.\n" + "NHNT documentation is available at https://wiki.gpac.io/NHNT-Format\n") + .private_size = sizeof(GF_NHNTDmxCtx), + .args = GF_NHNTDmxArgs, + .initialize = nhntdmx_initialize, + .finalize = nhntdmx_finalize, + SETCAPS(NHNTDmxCaps), + .configure_pid = nhntdmx_configure_pid, + .process = nhntdmx_process, + .process_event = nhntdmx_process_event +}; + +const GF_FilterRegister *nhntdmx_register(GF_FilterSession *session) +{ + return &NHNTDmxRegister; +} + diff --git a/src/filters/dmx_ogg.c b/src/filters/dmx_ogg.c new file mode 100644 index 0000000..3cf1de2 --- /dev/null +++ b/src/filters/dmx_ogg.c @@ -0,0 +1,845 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2020 + * All rights reserved + * + * This file is part of GPAC / XIPH OGG demux filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_OGG) +#include +#include +//#include +#include + + + +typedef struct +{ + u32 streamType; /*MPEG-4 streamType*/ + u32 num_init_headers; + u32 sample_rate, bitrate, nb_chan; + u32 width, height; + GF_Fraction sar; + + u32 theora_kgs; + GF_Fraction frame_rate; + + u32 type; +} OGGInfo; + +typedef struct +{ + //only one output pid declared + GF_FilterPid *opid; + + ogg_stream_state os; + u32 serial_no; + /*DSI for ogg - cf constants.h*/ + GF_BitStream *dsi_bs; + + OGGInfo info; + Bool got_headers; + + u32 parse_headers; + + Bool eos_detected; + + u32 recomputed_ts; + + GF_VorbisParser *vorbis_parser; + + GF_OpusParser *opus_parser; +} GF_OGGStream; + +typedef struct +{ + Double index; + + //only one input pid declared + GF_FilterPid *ipid; + + u64 file_pos, file_size; + u32 global_rate; + GF_Fraction64 duration; + Double start_range; + Bool seek_file; + u32 nb_playing; + Bool is_file; + Bool initial_play_done, file_loaded; + + GF_List *streams; + + /*ogg ogfile state*/ + ogg_sync_state oy; +} GF_OGGDmxCtx; + +void oggdmx_signal_eos(GF_OGGDmxCtx *ctx) +{ + GF_OGGStream *st; + u32 i=0; + while ((st = (GF_OGGStream*)gf_list_enum(ctx->streams, &i))) { + if (st->opid) + gf_filter_pid_set_eos(st->opid); + } +} + +static GF_OGGStream *oggdmx_find_stream_for_page(GF_OGGDmxCtx *ctx, ogg_page *oggpage) +{ + u32 i, count; + count = gf_list_count(ctx->streams); + for (i=0; istreams, i); + if (ogg_stream_pagein(&st->os, oggpage) == 0) return st; + } + return NULL; +} + +static void oggdmx_get_stream_info(ogg_packet *oggpacket, OGGInfo *info) +{ + oggpack_buffer opb; + + memset(info, 0, sizeof(OGGInfo)); + + /*vorbis*/ + if ((oggpacket->bytes >= 7) && !strncmp((char *) &oggpacket->packet[1], "vorbis", 6)) { + info->streamType = GF_STREAM_AUDIO; + oggpack_readinit(&opb, oggpacket->packet, oggpacket->bytes); + oggpack_adv( &opb, 88); + info->nb_chan = oggpack_read( &opb, 8); /*nb chan*/ + info->sample_rate = oggpack_read(&opb, 32); + oggpack_adv( &opb, 32); /*max rate*/ + info->bitrate = oggpack_read(&opb, 32); + info->num_init_headers = 3; + info->type = GF_CODECID_VORBIS; + } + /*speex*/ + else if ((oggpacket->bytes >= 7) && !strncmp((char *) &oggpacket->packet[0], "Speex", 5)) { + info->streamType = GF_STREAM_AUDIO; + oggpack_readinit(&opb, oggpacket->packet, oggpacket->bytes); + oggpack_adv(&opb, 224); + oggpack_adv(&opb, 32); + oggpack_adv( &opb, 32); + info->sample_rate = oggpack_read(&opb, 32); + info->type = GF_CODECID_SPEEX; + info->num_init_headers = 1; + } + /*flac*/ + else if ((oggpacket->bytes >= 4) && !strncmp((char *) &oggpacket->packet[0], "fLaC", 4)) { + info->streamType = GF_STREAM_AUDIO; + info->type = GF_CODECID_FLAC; + info->num_init_headers = 3; + } + /*opus*/ + else if ((oggpacket->bytes >= 8) && !strncmp((char *) &oggpacket->packet[0], "OpusHead", 8)) { + info->streamType = GF_STREAM_AUDIO; + info->type = GF_CODECID_OPUS; + info->num_init_headers = 1; + info->sample_rate = 48000; + } + /*theora*/ + else if ((oggpacket->bytes >= 7) && !strncmp((char *) &oggpacket->packet[1], "theora", 6)) { + GF_BitStream *bs; + u32 keyframe_freq_force; + + info->streamType = GF_STREAM_VISUAL; + info->type = GF_CODECID_THEORA; + bs = gf_bs_new((char *) oggpacket->packet, oggpacket->bytes, GF_BITSTREAM_READ); + gf_bs_read_int(bs, 56); + gf_bs_read_int(bs, 8); /* major version num */ + gf_bs_read_int(bs, 8); /* minor version num */ + gf_bs_read_int(bs, 8); /* subminor version num */ + info->width = gf_bs_read_int(bs, 16) << 4; /* width */ + info->height = gf_bs_read_int(bs, 16) << 4; /* height */ + gf_bs_read_int(bs, 24); /* frame width */ + gf_bs_read_int(bs, 24); /* frame height */ + gf_bs_read_int(bs, 8); /* x offset */ + gf_bs_read_int(bs, 8); /* y offset */ + info->frame_rate.den = gf_bs_read_u32(bs); + info->frame_rate.num = gf_bs_read_u32(bs); + info->sar.num = gf_bs_read_int(bs, 24); /* aspect_numerator */ + info->sar.den =gf_bs_read_int(bs, 24); /* aspect_denominator */ + gf_bs_read_int(bs, 8); /* colorspace */ + info->bitrate = gf_bs_read_int(bs, 24);/* bitrate */ + gf_bs_read_int(bs, 6); /* quality */ + + /*patch for compatibility with old arch*/ + if ((info->frame_rate.den==25025) && (info->frame_rate.num==1001) ) { + info->frame_rate.den = 25000; + info->frame_rate.num = 1000; + } + + keyframe_freq_force = 1 << gf_bs_read_int(bs, 5); + info->theora_kgs = 0; + keyframe_freq_force--; + while (keyframe_freq_force) { + info->theora_kgs ++; + keyframe_freq_force >>= 1; + } + info->num_init_headers = 3; + gf_bs_del(bs); + } +} + +static void oggdmx_declare_pid(GF_Filter *filter, GF_OGGDmxCtx *ctx, GF_OGGStream *st) +{ + if (!st->opid) { + st->opid = gf_filter_pid_new(filter); + } +// gf_filter_pid_set_property(st->opid, GF_PROP_PID_ID, &PROP_UINT(st->serial_no) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_ID, &PROP_UINT(1 + gf_list_find(ctx->streams, st) ) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(st->info.streamType) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_CODECID, &PROP_UINT(st->info.type) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_BITRATE, &PROP_UINT(st->info.bitrate) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(st->info.sample_rate ? st->info.sample_rate : st->info.frame_rate.den) ); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_PROFILE_LEVEL, &PROP_UINT(0xFE) ); + + //opus DSI is formatted as box (ffmpeg compat) we might want to change that to avoid the box header + if (st->info.type==GF_CODECID_OPUS) { + GF_OpusSpecificBox *opus = (GF_OpusSpecificBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_DOPS); + st->dsi_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + opus->version = 0; + + opus->OutputChannelCount = st->opus_parser->OutputChannelCount; + opus->PreSkip = st->opus_parser->PreSkip; + opus->InputSampleRate = st->opus_parser->InputSampleRate; + opus->OutputGain = st->opus_parser->OutputGain; + opus->ChannelMappingFamily = st->opus_parser->ChannelMappingFamily; + opus->StreamCount = st->opus_parser->StreamCount; + opus->CoupledCount = st->opus_parser->CoupledCount; + memcpy(opus->ChannelMapping, st->opus_parser->ChannelMapping, sizeof(char)*255); + gf_isom_box_size((GF_Box *) opus); + gf_isom_box_write((GF_Box *) opus, st->dsi_bs); + gf_isom_box_del((GF_Box *) opus); + + st->info.nb_chan = st->opus_parser->OutputChannelCount; + } + + if (st->dsi_bs) { + u8 *data; + u32 size; + gf_bs_get_content(st->dsi_bs, &data, &size); + gf_bs_del(st->dsi_bs); + st->dsi_bs = NULL; + gf_filter_pid_set_property(st->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(data, size) ); + } + + if (st->info.sample_rate) + gf_filter_pid_set_property(st->opid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(st->info.sample_rate) ); + + if (st->info.nb_chan) + gf_filter_pid_set_property(st->opid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(st->info.nb_chan) ); + + if (st->info.width) + gf_filter_pid_set_property(st->opid, GF_PROP_PID_WIDTH, &PROP_UINT(st->info.width) ); + if (st->info.height) + gf_filter_pid_set_property(st->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(st->info.height) ); + if (st->info.sar.den) + gf_filter_pid_set_property(st->opid, GF_PROP_PID_SAR, &PROP_FRAC(st->info.sar) ); + if (st->info.frame_rate.den) + gf_filter_pid_set_property(st->opid, GF_PROP_PID_FPS, &PROP_FRAC(st->info.frame_rate) ); + + if (ctx->duration.num) + gf_filter_pid_set_property(st->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + +} + +static void oggdmx_new_stream(GF_Filter *filter, GF_OGGDmxCtx *ctx, ogg_page *oggpage) +{ + ogg_packet oggpacket; + u32 serial_no, i; + GF_OGGStream *st; + + /*reannounce of stream (caroussel in live streams) */ + serial_no = ogg_page_serialno(oggpage); + i=0; + while ((st = (GF_OGGStream*)gf_list_enum(ctx->streams, &i))) { + if (st->serial_no==serial_no) { + //resetup stream + ogg_stream_clear(&st->os); + ogg_stream_init(&st->os, st->serial_no); + ogg_stream_pagein(&st->os, oggpage); + st->parse_headers = st->info.num_init_headers; + return; + } + } + + /*look if we have the same stream defined (eg, reuse first stream dead with same header page)*/ + i=0; + while ((st = (GF_OGGStream*)gf_list_enum(ctx->streams, &i))) { + if (st->eos_detected) { + gf_filter_pid_set_eos(st->opid); + //and reuse the pid connection + break; + } + } + if (!st) { + GF_SAFEALLOC(st, GF_OGGStream); + if (!st) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[OGG] Failed to allocate stream for demux\n")); + return; + } + } + st->eos_detected = GF_FALSE; + st->serial_no = serial_no; + ogg_stream_init(&st->os, st->serial_no); + ogg_stream_pagein(&st->os, oggpage); + + ogg_stream_packetpeek(&st->os, &oggpacket); + oggdmx_get_stream_info(&oggpacket, &st->info); + + gf_list_add(ctx->streams, st); + st->parse_headers = st->info.num_init_headers; + switch (st->info.type) { + case GF_CODECID_VORBIS: + GF_SAFEALLOC(st->vorbis_parser, GF_VorbisParser); + break; + case GF_CODECID_OPUS: + GF_SAFEALLOC(st->opus_parser, GF_OpusParser); + break; + default: + break; + } + + if (st->got_headers) { + oggdmx_declare_pid(filter, ctx, st); + } + i=0; + ctx->global_rate = 0; + while ((st = (GF_OGGStream*)gf_list_enum(ctx->streams, &i))) { + if (!st->eos_detected) ctx->global_rate += st->info.bitrate; + } + if (ctx->global_rate && ctx->is_file && !ctx->file_loaded) { + if (!ctx->file_size) { + GF_PropertyEntry *pe=NULL; + const GF_PropertyValue *p = gf_filter_pid_get_info(ctx->ipid, GF_PROP_PID_DOWN_SIZE, &pe); + if (p) ctx->file_size = p->value.longuint; + gf_filter_release_property(pe); + } + if (ctx->file_size) { + ctx->duration.num = (u32) (8 * ctx->file_size); + ctx->duration.den = ctx->global_rate; + } + } +} + +GF_Err oggdmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 i; + GF_OGGDmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + GF_OGGStream *st; + ctx->ipid = NULL; + + while ((st = (GF_OGGStream*)gf_list_enum(ctx->streams, &i))) { + if (st->opid) + gf_filter_pid_remove(st->opid); + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + return GF_OK; +} + +static void oggdmx_check_dur(GF_Filter *filter, GF_OGGDmxCtx *ctx) +{ + ogg_sync_state oy; + FILE *stream; + const GF_PropertyValue *p; + OGGInfo info, the_info; + ogg_page oggpage; + ogg_packet oggpacket; + ogg_stream_state os, the_os; + u64 max_gran; + Bool has_stream = GF_FALSE; + GF_VorbisParser vp; + GF_OpusParser op; + u64 recompute_ts; + GF_Fraction64 dur; + + if (!ctx->index || ctx->duration.num) return; + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7) ) { + ctx->is_file = GF_FALSE; + ctx->duration.num=1; + return; + } + ctx->is_file = GF_TRUE; + if (!ctx->file_loaded) return; + + stream = gf_fopen(p->value.string, "rb"); + if (!stream) return; + + ogg_sync_init(&oy); + memset(&the_info, 0, sizeof(OGGInfo)); + memset(&vp, 0, sizeof(GF_VorbisParser)); + recompute_ts = 0; + max_gran = 0; + while (1) { + char buf[10000]; + while (ogg_sync_pageout(&oy, &oggpage) != 1 ) { + char *buffer; + u32 bytes; + + if (gf_feof(stream)) + break; + + bytes = (u32) gf_fread(buf, 10000, stream); + if (!bytes) break; + buffer = ogg_sync_buffer(&oy, bytes); + memcpy(buffer, buf, bytes); + ogg_sync_wrote(&oy, bytes); + } + if (gf_feof(stream)) + break; + + if (ogg_page_bos(&oggpage)) { + ogg_stream_init(&os, ogg_page_serialno(&oggpage)); + if (ogg_stream_pagein(&os, &oggpage) >= 0 ) { + ogg_stream_packetpeek(&os, &oggpacket); + if (ogg_stream_pagein(&os, &oggpage) >= 0 ) { + ogg_stream_packetpeek(&os, &oggpacket); + oggdmx_get_stream_info(&oggpacket, &info); + } + if (!has_stream) { + has_stream = GF_TRUE; + ogg_stream_init(&the_os, ogg_page_serialno(&oggpage)); + the_info = info; + } + } + ogg_stream_clear(&os); + } + if (has_stream && (ogg_stream_pagein(&the_os, &oggpage) >= 0) ) { + while (ogg_stream_packetout(&the_os, &oggpacket ) > 0 ) { + if (the_info.type==GF_CODECID_VORBIS) { + if (the_info.num_init_headers) { + the_info.num_init_headers--; + gf_vorbis_parse_header(&vp, oggpacket.packet, oggpacket.bytes); + } else { + recompute_ts += gf_vorbis_check_frame(&vp, (char *) oggpacket.packet, oggpacket.bytes); + } + } else if (the_info.type==GF_CODECID_OPUS) { + if (the_info.num_init_headers) { + the_info.num_init_headers--; + gf_opus_parse_header(&op, oggpacket.packet, oggpacket.bytes); + } else { + recompute_ts += gf_opus_check_frame(&op, (char *) oggpacket.packet, oggpacket.bytes); + } + + } else if ((oggpacket.granulepos>=0) && ((u64) oggpacket.granulepos>max_gran) ) { + max_gran = oggpacket.granulepos; + } + } + } + } + ogg_sync_clear(&oy); + ctx->file_size = gf_ftell(stream); + if (has_stream) { + ogg_stream_clear(&the_os); + if (recompute_ts) { + dur.num = (u32) recompute_ts; + dur.den = the_info.sample_rate; + } else { + //convert granule to time + if (the_info.sample_rate) { + dur.num = (s32) max_gran; + } else if (the_info.frame_rate.num) { + s64 iframe = max_gran >> the_info.theora_kgs; + s64 pframe = max_gran - (iframe << the_info.theora_kgs); + pframe += iframe; + dur.num = (s32) (pframe / the_info.frame_rate.num); + } else { + dur.num = 0; + } + if (the_info.sample_rate) dur.den = the_info.sample_rate; + else dur.den = the_info.frame_rate.den; + } + + if (!ctx->duration.num || (ctx->duration.num * dur.den != dur.num * ctx->duration.den)) { + u32 i=0; + GF_OGGStream *st; + ctx->duration = dur; + while ( (st = gf_list_enum(ctx->streams, &i)) ) { + gf_filter_pid_set_property(st->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + } + } + } + gf_fclose(stream); +} + +static Bool oggdmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i; + GF_OGGStream *st; + GF_FilterEvent fevt; + GF_OGGDmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (ctx->nb_playing && (ctx->start_range == evt->play.start_range)) { + ctx->nb_playing++; + return GF_TRUE; + } + ctx->nb_playing++; + if (! ctx->is_file) { + return GF_FALSE; + } + oggdmx_check_dur(filter, ctx); + + + ctx->start_range = evt->play.start_range; + ctx->file_pos = 0; + if (ctx->duration.num) { + ctx->file_pos = (u32) (ctx->file_size * ctx->start_range); + ctx->file_pos *= ctx->duration.den; + ctx->file_pos /= ctx->duration.num; + if (ctx->file_pos>ctx->file_size) return GF_TRUE; + } + + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!ctx->file_pos) + return GF_TRUE; + } + ctx->seek_file = GF_TRUE; + i=0; + while ((st = gf_list_enum(ctx->streams, &i)) ) { + if (st->info.sample_rate) { + st->recomputed_ts = (u32) (ctx->start_range * st->info.sample_rate); + } else { + st->recomputed_ts = (u32) (ctx->start_range * st->info.frame_rate.den); + } + } + + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = ctx->file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + ctx->nb_playing --; + //cancel event if not last stream + if (ctx->nb_playing) return GF_TRUE; + + //cancel event if we didn't get all stream headers yet not last stream + i=0; + while ((st = gf_list_enum(ctx->streams, &i))) { + if (!st->got_headers) return GF_TRUE; + } + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +GF_Err oggdmx_process(GF_Filter *filter) +{ + ogg_page oggpage; + GF_OGGDmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + GF_OGGStream *st; + s64 granulepos_init = -1; + + //update duration + oggdmx_check_dur(filter, ctx); + + + if (ctx->seek_file) { + ogg_sync_clear(&ctx->oy); + ogg_sync_init(&ctx->oy); + ctx->seek_file = GF_FALSE; + } else { + u32 i=0; + u32 would_block = 0; + //check if all the streams are in block state, if so return. + //we need to check for all output since one pid could still be buffering + while ((st = gf_list_enum(ctx->streams, &i))) { + if (st->got_headers && gf_filter_pid_would_block(st->opid)) + would_block++; + } + if (would_block && (would_block+1==i)) + return GF_OK; + } + + while (1) { + ogg_packet oggpacket; + + if (ogg_sync_pageout(&ctx->oy, &oggpage ) != 1 ) { + u32 pck_size; + char *data, *buffer; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) oggdmx_signal_eos(ctx); + return GF_OK; + } + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + buffer = ogg_sync_buffer(&ctx->oy, pck_size); + memcpy(buffer, data, pck_size); + if (ogg_sync_wrote(&ctx->oy, pck_size) >= 0) { + gf_filter_pid_drop_packet(ctx->ipid); + } + continue; + } + + if (ogg_page_bos(&oggpage)) { + oggdmx_new_stream(filter, ctx, &oggpage); + continue; + } + + st = oggdmx_find_stream_for_page(ctx, &oggpage); + if (!st) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[OGG] cannot find stream for ogg page\n")); + continue; + } + + if (ogg_page_eos(&oggpage)) + st->eos_detected = GF_TRUE; + + while (ogg_stream_packetout(&st->os, &oggpacket ) > 0 ) { + if (st->parse_headers && !st->got_headers) { + Bool res = GF_FALSE; + Bool add_page = GF_FALSE; + u32 bytes = oggpacket.bytes; + //bug in some files where first header is repeated + if ( (st->parse_headers + 1 == st->info.num_init_headers) && st->dsi_bs && (gf_bs_get_position(st->dsi_bs) == 2 + bytes) ) + continue; + + switch (st->info.type) { + case GF_CODECID_VORBIS: + res = gf_vorbis_parse_header(st->vorbis_parser, (char *) oggpacket.packet, oggpacket.bytes); + if (!res) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[OGG] Failed to parse Vorbis header\n")); + } else { + add_page = GF_TRUE; + } + break; + case GF_CODECID_OPUS: + res = gf_opus_parse_header(st->opus_parser, (char *) oggpacket.packet, oggpacket.bytes); + if (!res) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[OGG] Failed to parse Opus header\n")); + } + break; + case GF_CODECID_THEORA: + add_page = GF_TRUE; + break; + } + + if (add_page) { + if (!st->dsi_bs) st->dsi_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + gf_bs_write_u16(st->dsi_bs, oggpacket.bytes); + gf_bs_write_data(st->dsi_bs, (char *) oggpacket.packet, oggpacket.bytes); + } + + st->parse_headers--; + if (!st->parse_headers) { + st->got_headers = GF_TRUE; + oggdmx_declare_pid(filter, ctx, st); + } + + granulepos_init = oggpacket.granulepos; + } else if (st->parse_headers && st->got_headers) { + st->parse_headers--; + } else if (!st->opid) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[OGG] Channel %d packet before configure done - discarding\n", st->serial_no)); + } else { + u8 *output; + GF_FilterPacket *dst_pck; + + if (st->info.type==GF_CODECID_THEORA) { + oggpack_buffer opb; + oggpackB_readinit(&opb, oggpacket.packet, oggpacket.bytes); + /*not a new frame*/ + if (oggpackB_read(&opb, 1) != 0) continue; + + dst_pck = gf_filter_pck_new_alloc(st->opid, oggpacket.bytes, &output); + memcpy(output, (char *) oggpacket.packet, oggpacket.bytes); + gf_filter_pck_set_cts(dst_pck, st->recomputed_ts); + gf_filter_pck_set_sap(dst_pck, oggpackB_read(&opb, 1) ? GF_FILTER_SAP_NONE : GF_FILTER_SAP_1); + st->recomputed_ts += st->info.frame_rate.num; + } + //this is audio + else { + u32 block_size = 0; + + if (st->info.type==GF_CODECID_VORBIS) { + block_size = gf_vorbis_check_frame(st->vorbis_parser, (char *) oggpacket.packet, oggpacket.bytes); + if (!block_size) continue; + } + else if (st->info.type==GF_CODECID_OPUS) { + block_size = gf_opus_check_frame(st->opus_parser, (char *) oggpacket.packet, oggpacket.bytes); + if (!block_size) continue; + + if (!st->recomputed_ts) { + //compat with old arch (keep same hashes), to remove once droping it + if (!gf_sys_old_arch_compat()) { + gf_filter_pid_set_property(st->opid, GF_PROP_PID_DELAY, &PROP_SINT((s32)-st->opus_parser->PreSkip)); + } + } + } + + if (ogg_page_eos(&oggpage)) { + //compat with old arch (keep same hashes), to remove once droping it + if (!gf_sys_old_arch_compat()) { + if (oggpacket.granulepos != -1 && granulepos_init != -1) + block_size = (u32)(oggpacket.granulepos - granulepos_init - st->recomputed_ts); /*4.4 End Trimming, cf https://tools.ietf.org/html/rfc7845*/ + } + } + dst_pck = gf_filter_pck_new_alloc(st->opid, oggpacket.bytes, &output); + memcpy(output, (char *) oggpacket.packet, oggpacket.bytes); + gf_filter_pck_set_cts(dst_pck, st->recomputed_ts); + //compat with old arch (keep same hashes), to remove once droping it + if (!gf_sys_old_arch_compat()) { + gf_filter_pck_set_duration(dst_pck, block_size); + } + + if (st->info.type == GF_CODECID_VORBIS) { + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + } else if (st->info.type == GF_CODECID_OPUS) { + //compat with old arch (keep same hashes), to remove once droping it + if (!gf_sys_old_arch_compat()) { + gf_filter_pck_set_roll_info(dst_pck, 3840); + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_4); + } else { + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + } + } else { + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + } + + st->recomputed_ts += block_size; + } + + gf_filter_pck_send(dst_pck); + } + } + } + return GF_OK; +} + +static GF_Err oggdmx_initialize(GF_Filter *filter) +{ + GF_OGGDmxCtx *ctx = gf_filter_get_udta(filter); + ctx->streams = gf_list_new(); + ogg_sync_init(&ctx->oy); + return GF_OK; +} + +static void oggdmx_finalize(GF_Filter *filter) +{ + GF_OGGDmxCtx *ctx = gf_filter_get_udta(filter); + + /*just in case something went wrong*/ + while (gf_list_count(ctx->streams)) { + GF_OGGStream *st = (GF_OGGStream*)gf_list_get(ctx->streams, 0); + gf_list_rem(ctx->streams, 0); + ogg_stream_clear(&st->os); + if (st->dsi_bs) gf_bs_del(st->dsi_bs); + if (st->vorbis_parser) gf_free(st->vorbis_parser); + if (st->opus_parser) gf_free(st->opus_parser); + gf_free(st); + } + gf_list_del(ctx->streams); + ogg_sync_clear(&ctx->oy); +} + +static const char *oggdmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + if (!strncmp(data, "OggS", 4)) { + *score = GF_FPROBE_SUPPORTED; + return "video/ogg"; + } + return NULL; +} + + +static const GF_FilterCapability OGGDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "oga|spx|ogg|ogv|oggm|opus"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "audio/ogg|audio/x-ogg|audio/x-vorbis+ogg|application/ogg|application/x-ogg|video/ogg|video/x-ogg|video/x-ogm+ogg"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_VORBIS), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_FLAC), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_OPUS), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_SPEEX), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_THEORA), +}; + +#define OFFS(_n) #_n, offsetof(GF_OGGDmxCtx, _n) +static const GF_FilterArgs OGGDmxArgs[] = +{ + { OFFS(index), "indexing window length (unimplemented, only 0 disables stream probing for duration), ", GF_PROP_DOUBLE, "1.0", NULL, 0}, + {0} +}; + + +GF_FilterRegister OGGDmxRegister = { + .name = "oggdmx", + GF_FS_SET_DESCRIPTION("OGG demuxer") + GF_FS_SET_HELP("This filter demultiplexes OGG files/data into a set of media PIDs and frames.") + .private_size = sizeof(GF_OGGDmxCtx), + .initialize = oggdmx_initialize, + .finalize = oggdmx_finalize, + .args = OGGDmxArgs, + .flags = GF_FS_REG_DYNAMIC_PIDS, + SETCAPS(OGGDmxCaps), + .configure_pid = oggdmx_configure_pid, + .process = oggdmx_process, + .process_event = oggdmx_process_event, + .probe_data = oggdmx_probe_data, +}; + +#endif // !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_OGG) + +const GF_FilterRegister *oggdmx_register(GF_FilterSession *session) +{ +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_OGG) + return &OGGDmxRegister; +#else + return NULL; +#endif + +} + diff --git a/src/filters/dmx_saf.c b/src/filters/dmx_saf.c new file mode 100644 index 0000000..f0e9771 --- /dev/null +++ b/src/filters/dmx_saf.c @@ -0,0 +1,464 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2017 + * All rights reserved + * + * This file is part of GPAC / SAF demuxer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include + +typedef struct +{ + GF_FilterPid *opid; + u32 au_sn, stream_id, ts_res, buffer_min; +} GF_SAFStream; + +enum +{ + SAF_FILE_LOCAL, + SAF_FILE_REMOTE, + SAF_LIVE_STREAM +}; + +typedef struct +{ + GF_FilterPid *ipid; + + + GF_List *streams; + + u32 saf_type; + + Double start_range, end_range; + u32 nb_playing; + Bool is_file, file_loaded; + GF_Fraction64 duration; + u64 file_pos, file_size; + + Bool initial_play_done; + + char *saf_data; + u32 alloc_size, saf_size; +} GF_SAFDmxCtx; + + +static GFINLINE GF_SAFStream *saf_get_channel(GF_SAFDmxCtx *saf, u32 stream_id) +{ + GF_SAFStream *st; + u32 i=0; + while ((st = (GF_SAFStream *)gf_list_enum(saf->streams, &i))) { + if (st->stream_id==stream_id) return st; + } + return NULL; +} + +static void safdmx_demux(GF_Filter *filter, GF_SAFDmxCtx *ctx, char *data, u32 data_size) +{ + Bool is_rap, go; + u32 cts, au_size, type, i, stream_id; + u64 bs_pos; + GF_BitStream *bs; + + if (ctx->alloc_size < ctx->saf_size + data_size) { + ctx->saf_data = (char*)gf_realloc(ctx->saf_data, sizeof(char)*(ctx->saf_size + data_size) ); + ctx->alloc_size = ctx->saf_size + data_size; + } + //we could avoid a full copy of the buffer, but given how much SAF is used that's not very urgent ... + memcpy(ctx->saf_data + ctx->saf_size, data, sizeof(char)*data_size); + ctx->saf_size += data_size; + + /*first AU not complete yet*/ + if (ctx->saf_size<10) return; + + bs = gf_bs_new(ctx->saf_data, ctx->saf_size, GF_BITSTREAM_READ); + bs_pos = 0; + + go = GF_TRUE; + while (go) { + GF_SAFStream *st; + u64 avail = gf_bs_available(bs); + bs_pos = gf_bs_get_position(bs); + + if (avail<10) break; + + is_rap = (Bool)gf_bs_read_int(bs, 1); + /*au_sn = */gf_bs_read_int(bs, 15); + gf_bs_read_int(bs, 2); + cts = gf_bs_read_int(bs, 30); + au_size = gf_bs_read_int(bs, 16); + avail-=8; + + if (au_size > avail) break; + assert(au_size>=2); + + type = gf_bs_read_int(bs, 4); + stream_id = gf_bs_read_int(bs, 12); + au_size -= 2; + + st = saf_get_channel(ctx, stream_id); + switch (type) { + case 1: + case 2: + case 7: + if (st) { + gf_bs_skip_bytes(bs, au_size); + } else { + u32 oti, stype; + GF_SAFStream *first = (GF_SAFStream *)gf_list_get(ctx->streams, 0); + GF_SAFEALLOC(st, GF_SAFStream); + if (!st) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[SAF] Failed to allocate SAF channel")); + gf_bs_del(bs); + return; + } + st->stream_id = stream_id; + st->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_ID, &PROP_UINT(stream_id)); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_ESID, &PROP_UINT(stream_id)); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_CLOCK_ID, &PROP_UINT(first ? first->stream_id : stream_id)); + if (!first) { + gf_filter_pid_set_property(st->opid, GF_PROP_PID_IN_IOD, &PROP_BOOL(GF_TRUE)); + } + oti = gf_bs_read_u8(bs); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_CODECID, &PROP_UINT(oti)); + stype = gf_bs_read_u8(bs); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(stype)); + st->ts_res = gf_bs_read_u24(bs); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(st->ts_res)); + if (ctx->duration.num) + gf_filter_pid_set_property(st->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + + /*bufferSizeDB = */gf_bs_read_u16(bs); + au_size -= 7; + if ((oti == 0xFF) && (stype == 0xFF) ) { + u16 mimeLen = gf_bs_read_u16(bs); + gf_bs_skip_bytes(bs, mimeLen); + au_size -= mimeLen+2; + } + if (type==7) { + u16 urlLen = gf_bs_read_u16(bs); + char *url_string = (char*)gf_malloc(sizeof(char)*(urlLen+1)); + gf_bs_read_data(bs, url_string, urlLen); + url_string[urlLen] = 0; + au_size -= urlLen+2; + gf_filter_pid_set_property(st->opid, GF_PROP_PID_REMOTE_URL, &PROP_NAME(url_string)); + } + if (au_size) { + char *dsi = (char*)gf_malloc(sizeof(char)*au_size); + gf_bs_read_data(bs, dsi, au_size); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, au_size) ); + } + gf_list_add(ctx->streams, st); + } + break; + case 4: + if (st) { + GF_FilterPacket *pck; + u8 *pck_data; + bs_pos = gf_bs_get_position(bs); + pck = gf_filter_pck_new_alloc(st->opid, au_size, &pck_data); + memcpy(pck_data, ctx->saf_data+bs_pos, au_size); + //TODO: map byte range pos ? + //TODO: map AU SN ? + + gf_filter_pck_set_cts(pck, cts); + if (is_rap) + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + + if (ctx->start_range && (ctx->start_range * st->ts_res > cts*1000)) { + gf_filter_pck_set_seek_flag(pck, GF_TRUE); + } + gf_filter_pck_send(pck); + } + gf_bs_skip_bytes(bs, au_size); + break; + case 3: + if (st && st->opid) gf_filter_pid_set_eos(st->opid); + break; + case 5: + i=0; + while ((st = (GF_SAFStream *)gf_list_enum(ctx->streams, &i))) { + if (st->opid) gf_filter_pid_set_eos(st->opid); + } + break; + } + } + + gf_bs_del(bs); + if (bs_pos) { + u32 remain = (u32) (ctx->saf_size - bs_pos); + if (remain) memmove(ctx->saf_data, ctx->saf_data+bs_pos, sizeof(char)*remain); + ctx->saf_size = remain; + } +} + + +typedef struct +{ + u32 stream_id; + u32 ts_res; +} StreamInfo; + +static void safdmx_check_dur(GF_SAFDmxCtx *ctx) +{ + u32 nb_streams, i, cts, au_size, au_type, stream_id, ts_res; + GF_BitStream *bs; + GF_Fraction64 dur; + const GF_PropertyValue *p; + StreamInfo si[1024]; + FILE *stream; + + + if (ctx->duration.num) return; + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) { + ctx->is_file = GF_FALSE; + ctx->duration.num=1; + return; + } + ctx->is_file = GF_TRUE; + if (!ctx->file_loaded) return; + + stream = gf_fopen(p->value.string, "rb"); + if (!stream) return; + + bs = gf_bs_from_file(stream, GF_BITSTREAM_READ); + ctx->file_size = gf_bs_get_size(bs); + + dur.num = 0; + dur.den = 1000; + nb_streams=0; + while (gf_bs_available(bs)) { + gf_bs_read_u16(bs); + gf_bs_read_int(bs, 2); + cts = gf_bs_read_int(bs, 30); + au_size = gf_bs_read_int(bs, 16); + au_type = gf_bs_read_int(bs, 4); + stream_id = gf_bs_read_int(bs, 12); + au_size-=2; + ts_res = 0; + for (i=0; i dur.num) dur.num = (u32) (ts * 1000); + } + gf_bs_skip_bytes(bs, au_size); + } + gf_bs_del(bs); + gf_fclose(stream); + + if (!ctx->duration.num || (ctx->duration.num * dur.den != dur.num * ctx->duration.den)) { + GF_SAFStream *st; + ctx->duration = dur; + i=0; + while ( (st = gf_list_enum(ctx->streams, &i)) ) { + gf_filter_pid_set_property(st->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + } + } +} + + +GF_Err safdmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 i; + GF_SAFDmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + GF_SAFStream *st; + ctx->ipid = NULL; + + while ((st = (GF_SAFStream*)gf_list_enum(ctx->streams, &i))) { + if (st->opid) + gf_filter_pid_remove(st->opid); + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + return GF_OK; +} + +static Bool safdmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_FilterEvent fevt; + GF_SAFDmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (ctx->nb_playing && (ctx->start_range == evt->play.start_range)) { + return GF_TRUE; + } + if (! ctx->is_file) { + return GF_FALSE; + } + safdmx_check_dur(ctx); + + ctx->nb_playing++; + + ctx->start_range = evt->play.start_range; + ctx->file_pos = 0; + if (ctx->duration.num) { + ctx->file_pos = (u64) (ctx->file_size * ctx->start_range); + ctx->file_pos *= ctx->duration.den; + ctx->file_pos /= ctx->duration.num; + if (ctx->file_pos>ctx->file_size) return GF_TRUE; + } + + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!ctx->file_pos) + return GF_TRUE; + } + //post a seek to 0 - we would need to build an index of AUs to find the next place to seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = 0; + ctx->saf_size = 0; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + ctx->nb_playing--; + //don't cancel event + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +GF_Err safdmx_process(GF_Filter *filter) +{ + GF_SAFDmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + GF_SAFStream *st; + u32 i=0, pkt_size; + const char *data; + u32 would_block = 0; + + //update duration + safdmx_check_dur(ctx); + + //check if all the streams are in block state, if so return. + //we need to check for all output since one pid could still be buffering + while ((st = gf_list_enum(ctx->streams, &i))) { + if (st->opid && gf_filter_pid_would_block(st->opid)) + would_block++; + } + if (would_block && (would_block+1==i)) + return GF_OK; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { +// if (gf_filter_pid_is_eos(ctx->ipid)) oggdmx_signal_eos(ctx); + return GF_OK; + } + data = gf_filter_pck_get_data(pck, &pkt_size); + safdmx_demux(filter, ctx, (char *) data, pkt_size); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + +GF_Err safdmx_initialize(GF_Filter *filter) +{ + GF_SAFDmxCtx *ctx = gf_filter_get_udta(filter); + ctx->streams = gf_list_new(); + return GF_OK; +} + +void safdmx_finalize(GF_Filter *filter) +{ + GF_SAFDmxCtx *ctx = gf_filter_get_udta(filter); + + while (gf_list_count(ctx->streams)) { + GF_SAFStream *st = (GF_SAFStream *)gf_list_last(ctx->streams); + gf_list_rem_last(ctx->streams); + gf_free(st); + } + if (ctx->saf_data) gf_free(ctx->saf_data); + gf_list_del(ctx->streams); +} + +static const char *safdmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + *score = GF_FPROBE_EXT_MATCH; + return "saf|lsr"; +} + +static const GF_FilterCapability SAFDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "saf|lsr"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "application/x-saf|application/saf"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), +}; + +GF_FilterRegister SAFDmxRegister = { + .name = "safdmx", + GF_FS_SET_DESCRIPTION("SAF demuxer") + GF_FS_SET_HELP("This filter demultiplexes SAF (MPEG-4 Simple Aggregation Format for LASeR) files/data into a set of media PIDs and frames.") + .private_size = sizeof(GF_SAFDmxCtx), + .initialize = safdmx_initialize, + .finalize = safdmx_finalize, + SETCAPS(SAFDmxCaps), + .configure_pid = safdmx_configure_pid, + .process = safdmx_process, + .process_event = safdmx_process_event, + .probe_data = safdmx_probe_data +}; + +const GF_FilterRegister *safdmx_register(GF_FilterSession *session) +{ + return &SAFDmxRegister; +} + diff --git a/src/filters/dmx_vobsub.c b/src/filters/dmx_vobsub.c new file mode 100644 index 0000000..681b36a --- /dev/null +++ b/src/filters/dmx_vobsub.c @@ -0,0 +1,486 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2020 + * All rights reserved + * + * This file is part of GPAC / NHNT demuxer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_VOBSUB + +#include + + +typedef struct +{ + //opts + Bool blankframe; + + GF_FilterPid *idx_pid, *sub_pid; + GF_Filter *sub_filter; + GF_List *opids; + Bool first; + + u32 idx_file_crc; + FILE *mdia; + + Double start_range; + u64 first_dts; + + u32 nb_playing; + GF_Fraction64 duration; + Bool in_seek; + + Bool initial_play_done; + Bool idx_parsed; + + u32 timescale; + + vobsub_file *vobsub; +} GF_VOBSubDmxCtx; + + +GF_Err vobsubdmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 crc; + const GF_PropertyValue *p; + GF_VOBSubDmxCtx *ctx = gf_filter_get_udta(filter); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_URL); + if (!p) { + gf_filter_setup_failure(filter, GF_URL_ERROR); + return GF_EOS; + } + + if (is_remove) { + u32 i, count; + ctx->idx_pid = NULL; + ctx->sub_pid = NULL; + count = gf_filter_get_opid_count(filter); + for (i=0; iidx_pid) { + ctx->idx_pid = pid; + } else if (ctx->idx_pid != pid) { + ctx->sub_pid = pid; + } + + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + + if (ctx->idx_pid==pid) { + GF_Err e; + Bool use_gfio = GF_FALSE; + char sURL[GF_MAX_PATH], *ext; + crc = gf_crc_32(p->value.string, (u32) strlen(p->value.string)); + if (ctx->idx_file_crc == crc) return GF_OK; + ctx->idx_file_crc = crc; + + if (ctx->sub_filter) { + gf_filter_remove_src(filter, ctx->sub_filter); + ctx->sub_filter = NULL; + } + if (!strncmp(p->value.string, "gfio://", 7)) { + use_gfio = GF_TRUE; + strcpy(sURL, gf_fileio_translate_url(p->value.string) ); + } else { + strcpy(sURL, p->value.string); + } + ext = gf_file_ext_start(sURL); + if (ext) ext[0] = 0; + strcat(sURL, ".sub"); + if (use_gfio) { + GF_FileIO *gfio = gf_fileio_from_url(p->value.string); + char *base = gf_file_basename(sURL); + const char *new_url = gf_fileio_factory(gfio, base ? base : sURL); + if (new_url) { + strcpy(sURL, new_url); + } + } + + ctx->sub_filter = gf_filter_connect_source(filter, sURL, NULL, GF_TRUE, &e); + if (e) return e; + if (ctx->mdia) gf_fclose(ctx->mdia); + ctx->mdia = NULL; + gf_filter_disable_probe(ctx->sub_filter); + + ctx->first = GF_TRUE; + } + + return GF_OK; +} + +static Bool vobsubdmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_VOBSubDmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (ctx->nb_playing) return GF_TRUE; + if (ctx->vobsub && (ctx->start_range != evt->play.start_range)) { + u32 i; + for (i=0; ivobsub->num_langs; i++) { + ctx->vobsub->langs[i].last_dts = 0; + ctx->vobsub->langs[i].current = 0; + ctx->vobsub->langs[i].is_seek = GF_TRUE; + } + } + ctx->start_range = evt->play.start_range; + ctx->nb_playing++; + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + ctx->nb_playing--; + //don't cancel event + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +GF_Err vobsubdmx_parse_idx(GF_Filter *filter, GF_VOBSubDmxCtx *ctx) +{ + FILE *file = NULL; + int version; + GF_Err err = GF_OK; + const GF_PropertyValue *p; + + if (!ctx->idx_pid) return GF_SERVICE_ERROR; + + p = gf_filter_pid_get_property(ctx->idx_pid, GF_PROP_PID_FILEPATH); + if (!p) { + gf_filter_setup_failure(filter, GF_URL_ERROR); + return GF_EOS; + } + + file = gf_fopen(p->value.string, "rb"); + if (!file) { + gf_filter_setup_failure(filter, GF_URL_ERROR); + return GF_EOS; + } + + GF_SAFEALLOC(ctx->vobsub, vobsub_file); + if (!ctx->vobsub) { + gf_fclose(file); + gf_filter_setup_failure(filter, GF_URL_ERROR); + return GF_EOS; + } + + err = vobsub_read_idx(file, ctx->vobsub, &version); + gf_fclose(file); + if (err) { + gf_filter_setup_failure(filter, GF_URL_ERROR); + return GF_EOS; + } + if (!gf_filter_get_opid_count(filter) ) { + u32 i; + ctx->duration.num = 0; + + for (i=0; ivobsub->num_langs; i++) { + vobsub_pos *pos = (vobsub_pos*)gf_list_last(ctx->vobsub->langs[i].subpos); + if ((u64) ctx->duration.num < pos->start*90) + ctx->duration.num = (s64) (pos->start*90); + } + ctx->duration.den = 90000; + + for (i=0; ivobsub->num_langs; i++) { + GF_FilterPid *opid = gf_filter_pid_new(filter); + + //copy properties from idx pid + gf_filter_pid_copy_properties(opid, ctx->idx_pid); + + gf_filter_pid_set_property(opid, GF_PROP_PID_ID, &PROP_UINT(i+1) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_TEXT) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_SUBPIC) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(90000) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA((char*)&ctx->vobsub->palette[0][0], sizeof(ctx->vobsub->palette)) ); + + + gf_filter_pid_set_property(opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->vobsub->width) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->vobsub->height) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_LANGUAGE, &PROP_STRING(ctx->vobsub->langs[i].name) ); + gf_filter_pid_set_property(opid, GF_PROP_PID_DURATION, &PROP_FRAC64(ctx->duration) ); + + gf_filter_pid_set_udta(opid, &ctx->vobsub->langs[i]); + } + } + return GF_OK; +} + +static GF_Err vobsubdmx_send_stream(GF_VOBSubDmxCtx *ctx, GF_FilterPid *pid) +{ + static const u8 null_subpic[] = { 0x00, 0x09, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0xFF }; + GF_List *subpic; + vobsub_lang *vslang; + u32 c, count; + GF_FilterPacket *dst_pck; + + unsigned char buf[0x800]; + + vslang = gf_filter_pid_get_udta(pid); + subpic = vslang->subpos; + + count = gf_list_count(subpic); + + c = vslang->current; + + for (; cis_seek) { + if (pos->start*90 < ctx->start_range * 90000) { + continue; + } + vslang->is_seek = GF_FALSE; + } + + gf_fseek(ctx->mdia, pos->filepos, SEEK_SET); + if (gf_ftell(ctx->mdia) != pos->filepos) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VobSub] Could not seek in file\n")); + return GF_IO_ERR; + } + + if (!gf_fread(buf, sizeof(buf), ctx->mdia)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VobSub] Could not read from file\n")); + return GF_IO_ERR; + } + + if (*(u32*)&buf[0x00] != 0xba010000 || + *(u32*)&buf[0x0e] != 0xbd010000 || + !(buf[0x15] & 0x80) || + (buf[0x17] & 0xf0) != 0x20 || + (buf[buf[0x16] + 0x17] & 0xe0) != 0x20) + { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VobSub] Corrupted data found in file (1)\n")); + return GF_CORRUPTED_DATA; + } + + psize = (buf[buf[0x16] + 0x18] << 8) + buf[buf[0x16] + 0x19]; + dsize = (buf[buf[0x16] + 0x1a] << 8) + buf[buf[0x16] + 0x1b]; + + if (ctx->blankframe && !c && (pos->start>0)) { + dst_pck = gf_filter_pck_new_alloc(pid, sizeof(null_subpic), &packet); + if (!dst_pck) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VobSub] Memory allocation failed\n")); + return GF_OUT_OF_MEM; + } + memcpy(packet, null_subpic, sizeof(null_subpic)); + gf_filter_pck_set_cts(dst_pck, 0); + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + gf_filter_pck_set_duration(dst_pck, (u32) (pos->start * 90) ); + gf_filter_pck_send(dst_pck); + } + + dst_pck = gf_filter_pck_new_alloc(pid, psize, &packet); + if (!dst_pck) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VobSub] Memory allocation failed\n")); + return GF_OUT_OF_MEM; + } + + for (i = 0, left = psize; i < psize; i += size, left -= size) { + hsize = 0x18 + buf[0x16]; + size = MIN(left, 0x800 - hsize); + memcpy(packet + i, buf + hsize, size); + + if (size != left) { + while (gf_fread(buf, sizeof(buf), ctx->mdia)) { + if (buf[buf[0x16] + 0x17] == (vslang->idx | 0x20)) { + break; + } + } + } + } + + if (i != psize || left > 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VobSub] Corrupted data found in file (2)\n")); + return GF_CORRUPTED_DATA; + } + + if (vobsub_get_subpic_duration(packet, psize, dsize, &duration) != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VobSub] Corrupted data found in file (3)\n")); + return GF_CORRUPTED_DATA; + } + + gf_filter_pck_set_cts(dst_pck, pos->start * 90); + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + gf_filter_pck_set_duration(dst_pck, duration); + + if (vslang->last_dts && (vslang->last_dts >= pos->start * 90)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[VobSub] Out of order timestamps in vobsub file\n")); + } + gf_filter_pck_send(dst_pck); + vslang->last_dts = pos->start * 90; + + vslang->current++; + if (gf_filter_pid_would_block(pid)) return GF_OK; + } + return GF_EOS; +} + +GF_Err vobsubdmx_process(GF_Filter *filter) +{ + GF_VOBSubDmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + u32 pkt_size, i, count, nb_eos; + Bool start, end; + + if (!ctx->idx_parsed) { + GF_Err e; + pck = gf_filter_pid_get_packet(ctx->idx_pid); + if (!pck) return GF_OK; + gf_filter_pck_get_framing(pck, &start, &end); + //for now we only work with complete files + assert(end); + + e = vobsubdmx_parse_idx(filter, ctx); + ctx->idx_parsed = GF_TRUE; + gf_filter_pid_drop_packet(ctx->idx_pid); + if (e) { + gf_filter_setup_failure(filter, e); + return e; + } + } + + if (!ctx->nb_playing) return GF_OK; + + if (!ctx->mdia) { + const GF_PropertyValue *p; + if (!ctx->sub_pid) return GF_OK; + p = gf_filter_pid_get_property(ctx->sub_pid, GF_PROP_PID_FILEPATH); + if (!p) { + gf_filter_setup_failure(filter, GF_URL_ERROR); + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VobSub] Cannot open sub file\n")); + return GF_EOS; + } + ctx->mdia = gf_fopen(p->value.string, "rb"); + } + + pck = gf_filter_pid_get_packet(ctx->sub_pid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->sub_pid)) return GF_EOS; + return GF_OK; + } + + /*data =*/ gf_filter_pck_get_data(pck, &pkt_size); + gf_filter_pck_get_framing(pck, &start, &end); + //for now we only work with complete files + assert(end); + + nb_eos = 0; + count = gf_filter_get_opid_count(filter); + for (i=0; isub_pid); + return GF_EOS; + } + return GF_OK; +} + +static void vobsubdmx_finalize(GF_Filter *filter) +{ + GF_VOBSubDmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->vobsub) vobsub_free(ctx->vobsub); + if (ctx->mdia) gf_fclose(ctx->mdia); +} + +static const char * vobsubdmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + if (!strncmp(data, "# VobSub", 8)) { + *score = GF_FPROBE_SUPPORTED; + return "text/vobsub"; + } + return NULL; +} + +#define OFFS(_n) #_n, offsetof(GF_VOBSubDmxCtx, _n) +static const GF_FilterArgs GF_VOBSubDmxArgs[] = +{ + { OFFS(blankframe), "force inserting a blank frame if first subpic is not at 0", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + + +static const GF_FilterCapability VOBSubDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "idx|sub"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "text/vobsub"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_SUBPIC), +}; + + +GF_FilterRegister VOBSubDmxRegister = { + .name = "vobsubdmx", + GF_FS_SET_DESCRIPTION("VobSub demuxer") + GF_FS_SET_HELP("This filter demultiplexes VobSub files/data to produce media PIDs and frames.") + .private_size = sizeof(GF_VOBSubDmxCtx), + .max_extra_pids = 1, + .args = GF_VOBSubDmxArgs, + .finalize = vobsubdmx_finalize, + SETCAPS(VOBSubDmxCaps), + .configure_pid = vobsubdmx_configure_pid, + .process = vobsubdmx_process, + .probe_data = vobsubdmx_probe_data, + .process_event = vobsubdmx_process_event +}; + +#endif + +const GF_FilterRegister *vobsubdmx_register(GF_FilterSession *session) +{ +#ifndef GPAC_DISABLE_VOBSUB + return &VOBSubDmxRegister; +#else + return NULL; +#endif + +} + diff --git a/src/filters/enc_jpg.c b/src/filters/enc_jpg.c new file mode 100644 index 0000000..bafb5bc --- /dev/null +++ b/src/filters/enc_jpg.c @@ -0,0 +1,409 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018 + * All rights reserved + * + * This file is part of GPAC / libjpeg encoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include + +#ifdef GPAC_HAS_JPEG + +#ifdef WIN32 +#define HAVE_UNSIGNED_CHAR +#endif + +#include +#include + +typedef struct +{ + //opts + u32 dctmode; + u32 quality; + + GF_FilterPid *ipid, *opid; + u32 width, height, pixel_format, stride, stride_uv, nb_planes, uv_height; + + GF_FilterPacket *dst_pck; + u8 *output; + + /*io manager*/ + struct jpeg_destination_mgr dst; + u32 dst_pck_size, max_size; + + struct jpeg_error_mgr pub; + jmp_buf jmpbuf; + + Bool in_fmt_negotiate; +} GF_JPGEncCtx; + +static GF_Err jpgenc_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + char n[100]; + const GF_PropertyValue *prop; + GF_JPGEncCtx *ctx = (GF_JPGEncCtx *) gf_filter_get_udta(filter); + + //disconnect of src pid (not yet supported) + if (is_remove) { + //one in one out, this is simple + gf_filter_pid_remove(ctx->opid); + ctx->ipid = NULL; + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + + ctx->ipid = pid; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT( GF_CODECID_JPEG )); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE_UV, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, NULL); + +#ifdef JPEG_LIB_VERSION_MAJOR + sprintf(n, "encjpg:%d.%d", JPEG_LIB_VERSION_MAJOR, JPEG_LIB_VERSION_MINOR); +#else + sprintf(n, "encjpg:%d", JPEG_LIB_VERSION); +#endif + + gf_filter_set_name(filter, n); + + //some props may not be set yet + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + if (!prop) return GF_OK; + ctx->width = prop->value.uint; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + if (!prop) return GF_OK; + ctx->height = prop->value.uint; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); + if (!prop) return GF_OK; + ctx->pixel_format = prop->value.uint; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE); + if (prop) ctx->stride = prop->value.uint; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE_UV); + if (prop) ctx->stride_uv = prop->value.uint; + + gf_pixel_get_size_info(ctx->pixel_format, ctx->width, ctx->height, NULL, &ctx->stride, &ctx->stride_uv, &ctx->nb_planes, &ctx->uv_height); + + + //TODO: for now we only allow YUV420p input, we should refine this to allow any YUV + if (ctx->pixel_format != GF_PIXEL_YUV) { + gf_filter_pid_negociate_property(pid, GF_PROP_PID_PIXFMT, &PROP_UINT(GF_PIXEL_YUV)); + ctx->in_fmt_negotiate = GF_TRUE; + return GF_OK; + } + ctx->in_fmt_negotiate = GF_FALSE; + return GF_OK; +} + +static void jpgenc_output_message (j_common_ptr cinfo) +{ + if (cinfo) { + char buffer[JMSG_LENGTH_MAX]; + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[JPGEnc]: %s\n", buffer)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[JPGEnc] coverage test\n")); + } +} +static void jpgenc_nonfatal_error2(j_common_ptr cinfo, int lev) +{ + if (cinfo) { + char buffer[JMSG_LENGTH_MAX]; + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[JPGEnc]: %s\n", buffer)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[JPGEnc] coverage test\n")); + } +} + +static void jpgenc_fatal_error(j_common_ptr cinfo) +{ + if (!cinfo) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[JPGEnc] coverage test\n")); + } else { + GF_JPGEncCtx *ctx = (GF_JPGEncCtx *) cinfo->client_data; + jpgenc_output_message(cinfo); + longjmp(ctx->jmpbuf, 1); + } +} + +//start with 4k when we have no clue about the size +#define ALLOC_STEP_SIZE 4096 +static void jpgenc_init_dest(j_compress_ptr cinfo) +{ + GF_JPGEncCtx *ctx = (GF_JPGEncCtx *) cinfo->client_data; + if (ctx->dst_pck) + return; + + ctx->dst_pck = gf_filter_pck_new_alloc(ctx->opid, ALLOC_STEP_SIZE, &ctx->output); + cinfo->dest->next_output_byte = ctx->output; + cinfo->dest->free_in_buffer = ALLOC_STEP_SIZE; + ctx->dst_pck_size += ALLOC_STEP_SIZE; +} + +static boolean jpgenc_empty_output(j_compress_ptr cinfo) +{ + u8 *data; + u32 new_size; + GF_JPGEncCtx *ctx = (GF_JPGEncCtx *) cinfo->client_data; + + if (!ctx->dst_pck) + return FALSE; + + if (gf_filter_pck_expand(ctx->dst_pck, ALLOC_STEP_SIZE, &ctx->output, &data, &new_size) != GF_OK) { + return FALSE; + } + cinfo->dest->next_output_byte = data; + cinfo->dest->free_in_buffer = ALLOC_STEP_SIZE; + ctx->dst_pck_size += ALLOC_STEP_SIZE; + return TRUE; +} + +static void jpgenc_term_dest(j_compress_ptr cinfo) +{ + GF_JPGEncCtx *ctx = (GF_JPGEncCtx *) cinfo->client_data; + + ctx->dst_pck_size -= (u32) cinfo->dest->free_in_buffer; + gf_filter_pck_truncate(ctx->dst_pck, ctx->dst_pck_size); +} + +static GF_Err jpgenc_process(GF_Filter *filter) +{ + GF_JPGEncCtx *ctx = (GF_JPGEncCtx *) gf_filter_get_udta(filter); + GF_Err e = GF_OK; + struct jpeg_compress_struct cinfo; + GF_FilterPacket *pck = NULL; + char *in_data; + GF_FilterFrameInterface *frame_ifce = NULL; + u32 size, stride, stride_uv; + u32 i, j; + u8 *pY, *pU, *pV; + JSAMPROW y[16],cb[16],cr[16]; + JSAMPARRAY block[3]; + + if (ctx->ipid) + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!ctx->ipid) + return GF_EOS; + + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + if (ctx->in_fmt_negotiate) return GF_OK; + + in_data = (char *) gf_filter_pck_get_data(pck, &size); + if (!in_data) { + frame_ifce = gf_filter_pck_get_frame_interface(pck); + if (!frame_ifce || !frame_ifce->get_plane) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NOT_SUPPORTED; + } + } + + block[0] = y; + block[1] = cb; + block[2] = cr; + + cinfo.err = jpeg_std_error(&(ctx->pub)); + cinfo.client_data = ctx; + ctx->pub.error_exit = jpgenc_fatal_error; + ctx->pub.output_message = jpgenc_output_message; + ctx->pub.emit_message = jpgenc_nonfatal_error2; + if (setjmp(ctx->jmpbuf)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[JPGEnc] : Failed to encode\n")); + e = GF_NON_COMPLIANT_BITSTREAM; + goto exit; + } + + ctx->dst.init_destination = jpgenc_init_dest; + ctx->dst.empty_output_buffer = jpgenc_empty_output; + ctx->dst.term_destination = jpgenc_term_dest; + + if (ctx->max_size) { + ctx->dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->max_size, &ctx->output); + ctx->dst.next_output_byte = ctx->output; + ctx->dst.free_in_buffer = ctx->max_size; + ctx->dst_pck_size = ctx->max_size; + } + + jpeg_create_compress(&cinfo); + cinfo.image_width = ctx->width; + cinfo.image_height = ctx->height; + cinfo.input_components = 3; + cinfo.in_color_space = JCS_YCbCr; + if (ctx->dctmode==0) cinfo.dct_method = JDCT_ISLOW; + else if (ctx->dctmode==2) cinfo.dct_method = JDCT_FLOAT; + else cinfo.dct_method = JDCT_IFAST; + cinfo.optimize_coding = TRUE; + jpeg_set_defaults (&cinfo); + + cinfo.raw_data_in = TRUE; + cinfo.comp_info[0].h_samp_factor = 2; + cinfo.comp_info[0].v_samp_factor = 2; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; +#ifdef JPEG_LIB_VERSION_MAJOR + cinfo.do_fancy_downsampling = FALSE; +#endif + jpeg_set_colorspace(&cinfo, JCS_YCbCr); + jpeg_set_quality(&cinfo, MIN(100, ctx->quality), TRUE); + + cinfo.dest = &ctx->dst; + + jpeg_start_compress (&cinfo, TRUE); + + stride = ctx->stride; + stride_uv = ctx->stride_uv; + pY = pU = pV = 0; + if (in_data) { + pY = in_data; + pU = pY + ctx->stride * ctx->height; + pV = pU + ctx->stride_uv * ctx->height/2; + } else { + e = frame_ifce->get_plane(frame_ifce, 0, (const u8 **)&pY, &stride); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[JPGEnc] Failed to fetch first plane in hardware frame\n")); + goto exit; + } + if (ctx->nb_planes>1) { + e = frame_ifce->get_plane(frame_ifce, 1, (const u8 **)&pU, &stride_uv); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[JPGEnc] Failed to fetch first plane in hardware frame\n")); + goto exit; + } + if (ctx->nb_planes>2) { + e = frame_ifce->get_plane(frame_ifce, 2, (const u8 **)&pV, &stride_uv); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[JPGEnc] Failed to fetch first plane in hardware frame\n")); + goto exit; + } + } + } + } + + for (j=0; jheight; j+=16) { + for (i=0;i<16;i++) { + y[i] = pY + stride*(i+j); + if (i%2 == 0) { + cb[i/2] = pU + stride_uv*((i+j)/2); + cr[i/2] = pV + stride_uv*((i+j)/2); + } + } + jpeg_write_raw_data (&cinfo, block, 16); + } + jpeg_finish_compress(&cinfo); + +exit: + + jpeg_destroy_compress(&cinfo); + if (ctx->dst_pck) { + if (!e) { + gf_filter_pck_merge_properties(pck, ctx->dst_pck); + gf_filter_pck_send(ctx->dst_pck); + } else { + gf_filter_pck_discard(ctx->dst_pck); + } + } + if (ctx->max_sizedst_pck_size) + ctx->max_size = ctx->dst_pck_size; + + ctx->dst_pck = NULL; + ctx->output = NULL; + ctx->dst_pck_size = 0; + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + +static GF_Err jpgenc_initialize(GF_Filter *filter) +{ +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + jpgenc_output_message(NULL); + jpgenc_nonfatal_error2(NULL, 0); + jpgenc_fatal_error(NULL); + } +#endif + return GF_OK; +} + +static const GF_FilterCapability JPGEncCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_UINT(GF_CAPS_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_JPEG) +}; + +#define OFFS(_n) #_n, offsetof(GF_JPGEncCtx, _n) +static GF_FilterArgs JPGEncArgs[] = +{ + { OFFS(dctmode), "type of DCT used\n" + "- slow: precise but slow integer DCT\n" + "- fast: less precise but faster integer DCT\n" + "- float: float DCT" + "", GF_PROP_UINT, "fast", "slow|fast|float", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(quality), "compression quality", GF_PROP_UINT, "100", "0-100", GF_FS_ARG_UPDATE}, + {0} +}; + +GF_FilterRegister JPGEncRegister = { + .name = "jpgenc", + GF_FS_SET_DESCRIPTION("JPG encoder") + GF_FS_SET_HELP("This filter encodes a single uncompressed video PID to JPEG using libjpeg.") + .private_size = sizeof(GF_JPGEncCtx), + .args = JPGEncArgs, + SETCAPS(JPGEncCaps), + .initialize = jpgenc_initialize, + .configure_pid = jpgenc_configure_pid, + .process = jpgenc_process, +}; + +#endif + +const GF_FilterRegister *jpgenc_register(GF_FilterSession *session) +{ +#ifdef GPAC_HAS_JPEG + return &JPGEncRegister; +#else + return NULL; +#endif +} diff --git a/src/filters/enc_png.c b/src/filters/enc_png.c new file mode 100644 index 0000000..6714d3b --- /dev/null +++ b/src/filters/enc_png.c @@ -0,0 +1,387 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018 + * All rights reserved + * + * This file is part of GPAC / libpng encoder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +#ifdef GPAC_HAS_PNG + +#include + +typedef struct +{ + //opts + u32 dctmode; + u32 quality; + + GF_FilterPid *ipid, *opid; + u32 width, height, pixel_format, stride, stride_uv, nb_planes, uv_height; + u32 nb_alloc_rows; + + u32 max_size, pos, alloc_size; + u32 png_type; + png_bytep *row_pointers; + + GF_FilterPacket *dst_pck; + u8 *output; + +} GF_PNGEncCtx; + +static GF_Err pngenc_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *prop; + GF_PNGEncCtx *ctx = (GF_PNGEncCtx *) gf_filter_get_udta(filter); + + //disconnect of src pid (not yet supported) + if (is_remove) { + //one in one out, this is simple + gf_filter_pid_remove(ctx->opid); + ctx->ipid = NULL; + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT( GF_CODECID_PNG )); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE_UV, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, NULL); + + gf_filter_set_name(filter, "encpng:"PNG_LIBPNG_VER_STRING ); + //not yeat ready + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + if (!prop) return GF_OK; + ctx->width = prop->value.uint; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + if (!prop) return GF_OK; + ctx->height = prop->value.uint; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); + if (!prop) return GF_OK; + ctx->pixel_format = prop->value.uint; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE); + if (prop) ctx->stride = prop->value.uint; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE_UV); + if (prop) ctx->stride_uv = prop->value.uint; + + gf_pixel_get_size_info(ctx->pixel_format, ctx->width, ctx->height, NULL, &ctx->stride, &ctx->stride_uv, &ctx->nb_planes, &ctx->uv_height); + + switch (ctx->pixel_format) { + case GF_PIXEL_GREYSCALE: + ctx->png_type = PNG_COLOR_TYPE_GRAY; + break; + case GF_PIXEL_GREYALPHA: + ctx->png_type = PNG_COLOR_TYPE_GRAY_ALPHA; + break; + case GF_PIXEL_RGB: + case GF_PIXEL_BGR: + case GF_PIXEL_RGBX: + case GF_PIXEL_XRGB: + case GF_PIXEL_BGRX: + case GF_PIXEL_XBGR: + ctx->png_type = PNG_COLOR_TYPE_RGB; + break; + case GF_PIXEL_RGBA: + ctx->png_type = PNG_COLOR_TYPE_RGB_ALPHA; + break; + default: + gf_filter_pid_negociate_property(pid, GF_PROP_PID_PIXFMT, &PROP_UINT(GF_PIXEL_RGB)); + break; + } + if (ctx->height > ctx->nb_alloc_rows) { + ctx->nb_alloc_rows = ctx->height; + ctx->row_pointers = gf_realloc(ctx->row_pointers, sizeof(png_bytep) * ctx->height); + } + return GF_OK; +} + +static void pngenc_finalize(GF_Filter *filter) +{ + GF_PNGEncCtx *ctx = (GF_PNGEncCtx *) gf_filter_get_udta(filter); + if (ctx->row_pointers) gf_free(ctx->row_pointers); +} + +#define PNG_BLOCK_SIZE 4096 + +static void pngenc_write(png_structp png, png_bytep data, png_size_t size) +{ + GF_PNGEncCtx *ctx = (GF_PNGEncCtx *)png_get_io_ptr(png); + if (!ctx->dst_pck) { + while (ctx->alloc_sizealloc_size+=PNG_BLOCK_SIZE; + ctx->dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->alloc_size, &ctx->output); + } else if (ctx->pos + size > ctx->alloc_size) { + u8 *new_data; + u32 new_size; + u32 old_size = ctx->alloc_size; + while (ctx->pos + size > ctx->alloc_size) + ctx->alloc_size += PNG_BLOCK_SIZE; + + if (gf_filter_pck_expand(ctx->dst_pck, ctx->alloc_size - old_size, &ctx->output, &new_data, &new_size) != GF_OK) { + return; + } + } + + memcpy(ctx->output + ctx->pos, data, sizeof(char)*size); + ctx->pos += (u32) size; +} + +void pngenc_flush(png_structp png) +{ + if (!png) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[PNGEnc] coverage test\n")); + } +} + +static void pngenc_error(png_structp cbk, png_const_charp msg) +{ + if (msg) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[PNGEnc] Error %s", msg)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[PNGEnc] coverage test\n")); + } +} +static void pngenc_warn(png_structp cbk, png_const_charp msg) +{ + if (msg) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[PNGEnc] Warning %s", msg)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[PNGEnc] coverage test\n")); + } +} + +static GF_Err pngenc_process(GF_Filter *filter) +{ + GF_FilterPacket *pck; + GF_PNGEncCtx *ctx = (GF_PNGEncCtx *) gf_filter_get_udta(filter); + png_color_8 sig_bit; + u32 k; + GF_Err e = GF_OK; + png_structp png_ptr; + png_infop info_ptr; + char *in_data; + u32 size, stride; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + stride = ctx->stride; + in_data = (char *) gf_filter_pck_get_data(pck, &size); + if (!in_data) { + GF_FilterFrameInterface *frame_ifce = gf_filter_pck_get_frame_interface(pck); + if (!frame_ifce || !frame_ifce->get_plane) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NOT_SUPPORTED; + } + e = frame_ifce->get_plane(frame_ifce, 0, (const u8 **) &in_data, &stride); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[PNGEnc] Failed to fetch first plane in hardware frame\n")); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NOT_SUPPORTED; + } + } + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, ctx, pngenc_error, pngenc_warn); + + if (png_ptr == NULL) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_IO_ERR; + } + + /* Allocate/initialize the image information data. REQUIRED */ + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + png_destroy_write_struct(&png_ptr, NULL); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_IO_ERR; + } + + /* Set error handling. REQUIRED if you aren't supplying your own + * error handling functions in the png_create_write_struct() call. + */ + if (setjmp(png_jmpbuf(png_ptr))) { + e = GF_NON_COMPLIANT_BITSTREAM; + goto exit; + } + + ctx->output = NULL; + ctx->pos = 0; + if (ctx->max_size) { + ctx->dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->max_size, &ctx->output); + ctx->alloc_size = ctx->max_size; + } + png_set_write_fn(png_ptr, ctx, pngenc_write, pngenc_flush); + + png_set_IHDR(png_ptr, info_ptr, ctx->width, ctx->height, 8, ctx->png_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + memset(&sig_bit, 0, sizeof(sig_bit)); + switch (ctx->png_type) { + case PNG_COLOR_TYPE_GRAY: + sig_bit.gray = 8; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + sig_bit.gray = 8; + sig_bit.alpha = 8; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + sig_bit.alpha = 8; + case PNG_COLOR_TYPE_RGB: + sig_bit.red = 8; + sig_bit.green = 8; + sig_bit.blue = 8; + break; + default: + break; + } + png_set_sBIT(png_ptr, info_ptr, &sig_bit); + + //todo add support for tags +#if 0 + { + png_text text_ptr[3]; + /* Optionally write comments into the image */ + text_ptr[0].key = "Title"; + text_ptr[0].text = "Mona Lisa"; + text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE; + text_ptr[1].key = "Author"; + text_ptr[1].text = "Leonardo DaVinci"; + text_ptr[1].compression = PNG_TEXT_COMPRESSION_NONE; + text_ptr[2].key = "Description"; + text_ptr[2].text = ""; + text_ptr[2].compression = PNG_TEXT_COMPRESSION_zTXt; + png_set_text(png_ptr, info_ptr, text_ptr, 3); + } +#endif + + png_write_info(png_ptr, info_ptr); + + /* Shift the pixels up to a legal bit depth and fill in + * as appropriate to correctly scale the image. + */ + png_set_shift(png_ptr, &sig_bit); + + /* pack pixels into bytes */ + png_set_packing(png_ptr); + + switch (ctx->pixel_format) { + case GF_PIXEL_ARGB: + png_set_bgr(png_ptr); + break; + case GF_PIXEL_RGBX: + png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); + png_set_bgr(png_ptr); + break; + case GF_PIXEL_BGRX: + png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); + break; + case GF_PIXEL_BGR: + png_set_bgr(png_ptr); + break; + } + for (k=0; kheight; k++) { + ctx->row_pointers[k] = (png_bytep) in_data + k*stride; + } + + png_write_image(png_ptr, ctx->row_pointers); + png_write_end(png_ptr, info_ptr); + +exit: + /* clean up after the write, and free any memory allocated */ + png_destroy_write_struct(&png_ptr, &info_ptr); + if (ctx->dst_pck) { + if (!e) { + gf_filter_pck_truncate(ctx->dst_pck, ctx->pos); + gf_filter_pck_merge_properties(pck, ctx->dst_pck); + gf_filter_pck_send(ctx->dst_pck); + } else { + gf_filter_pck_discard(ctx->dst_pck); + } + } + if (ctx->max_sizepos) + ctx->max_size = ctx->pos; + + ctx->dst_pck = NULL; + ctx->output = NULL; + ctx->pos = ctx->alloc_size = 0; + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + +static GF_Err pngenc_initialize(GF_Filter *filter) +{ +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + pngenc_flush(NULL); + pngenc_error(NULL, NULL); + pngenc_warn(NULL, NULL); + } +#endif + return GF_OK; +} + +static const GF_FilterCapability PNGEncCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_UINT(GF_CAPS_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_PNG) +}; + +GF_FilterRegister PNGEncRegister = { + .name = "pngenc", + GF_FS_SET_DESCRIPTION("PNG encoder") + GF_FS_SET_HELP("This filter encodes a single uncompressed video PID to PNG using libpng.") + .private_size = sizeof(GF_PNGEncCtx), + .initialize = pngenc_initialize, + .finalize = pngenc_finalize, + SETCAPS(PNGEncCaps), + .configure_pid = pngenc_configure_pid, + .process = pngenc_process, +}; + +#endif + +const GF_FilterRegister *pngenc_register(GF_FilterSession *session) +{ +#ifdef GPAC_HAS_PNG + return &PNGEncRegister; +#else + return NULL; +#endif +} diff --git a/src/filters/encrypt_cenc_isma.c b/src/filters/encrypt_cenc_isma.c new file mode 100644 index 0000000..ff162a3 --- /dev/null +++ b/src/filters/encrypt_cenc_isma.c @@ -0,0 +1,1890 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018-2020 + * All rights reserved + * + * This file is part of GPAC / CENC and ISMA encrypt module + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifndef GPAC_DISABLE_CRYPTO + + +enum +{ + ISMAEA_STATE_ERROR, + ISMAEA_STATE_SETUP, + ISMAEA_STATE_PLAY, +}; + + +typedef enum { + CENC_FULL_SAMPLE, + + /*below types may have several ranges (clear/encrypted) per sample*/ + CENC_AVC, /*AVC, nalu-based*/ + CENC_HEVC, /*HEVC, nalu-based*/ + CENC_AV1, /*AV1, OBU-based*/ + CENC_VPX, /*VPX, custom, see https://www.webmproject.org/vp9/mp4/ */ +} CENCCodecMode; + + +typedef struct +{ + Bool passthrough; + + GF_CryptInfo *cinfo; + + GF_FilterPid *ipid; + GF_FilterPid *opid; + GF_TrackCryptInfo *tci; + GF_Crypt *crypt; + u32 codec_id; + + u32 nb_pck; + + //ISMA/OMA var + u32 nalu_size_length; + u32 dsi_crc; + Bool isma_oma; + u64 BSO; + u64 range_end; + Bool prev_pck_encryped; + u32 KI_length; + u32 isma_IV_size; + Bool has_crypted_pck; + + Bool is_adobe; + bin128 key; + + CENCCodecMode cenc_codec; + + u32 bytes_in_nal_hdr; + Bool use_subsamples; + Bool cenc_init; + u32 nb_pck_encrypted, kidx; + char IV[16]; + bin128 default_KID; + + //true if using AES-CTR mode, false if using AES-CBC mode + Bool ctr_mode; + +#ifndef GPAC_DISABLE_AV_PARSERS + AVCState avc; +#ifndef GPAC_DISABLE_HEVC + HEVCState hevc; +#endif + AV1State av1; +#endif + Bool slice_header_clear; +} GF_CENCStream; + +typedef struct +{ + //options + const char *cfile; + Bool allc; + + //internal + GF_CryptInfo *cinfo; + + GF_List *streams; + GF_BitStream *bs_w, *bs_r; +} GF_CENCEncCtx; + + +static GF_Err isma_enc_configure(GF_CENCEncCtx *ctx, GF_CENCStream *cstr, Bool is_isma, const char *scheme_uri, const char *kms_uri) +{ + GF_Err e; + bin128 IV; + const GF_PropertyValue *p; + p = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_DECODER_CONFIG); + if (!p) p = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + + switch (cstr->codec_id) { + case GF_CODECID_AVC: + case GF_CODECID_SVC: + case GF_CODECID_MVC: + if (p) { + GF_AVCConfig *avcc = gf_odf_avc_cfg_read(p->value.data.ptr, p->value.data.size); + cstr->nalu_size_length = avcc ? avcc->nal_unit_size : 0; + if (avcc) gf_odf_avc_cfg_del(avcc); + } + if (!cstr->nalu_size_length) { + cstr->nalu_size_length = 4; + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[ISMACrypt] Missing NALU length size, assuming 4\n") ); + } + break; + case GF_CODECID_HEVC: + case GF_CODECID_LHVC: + if (p) { + GF_HEVCConfig *hvcc = gf_odf_hevc_cfg_read(p->value.data.ptr, p->value.data.size, (cstr->codec_id==GF_CODECID_LHVC) ? GF_TRUE : GF_FALSE); + cstr->nalu_size_length = hvcc ? hvcc->nal_unit_size : 0; + if (hvcc) gf_odf_hevc_cfg_del(hvcc); + } + if (!cstr->nalu_size_length) { + cstr->nalu_size_length = 4; + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[ISMACrypt] Missing NALU length size, assuming 4\n") ); + } + break; + } + + if (!scheme_uri || !strlen(scheme_uri)) scheme_uri = "urn:gpac:isma:encryption_scheme"; + + if (cstr->tci && ((cstr->tci->sel_enc_type==GF_CRYPT_SELENC_RAND) || (cstr->tci->sel_enc_type==GF_CRYPT_SELENC_RAND_RANGE)) ) { + gf_rand_init(GF_TRUE); + } + + cstr->isma_IV_size = 0; + if (!is_isma) { + /*128 bit IV in OMA*/ + cstr->isma_IV_size = 16; + } else { + p = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_CENC_IV_SIZE); + if (p) { + cstr->isma_IV_size = p->value.uint; + } else { + p = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_MEDIA_DATA_SIZE); + if (p) { + u64 BSO = p->value.longuint; + + if (BSO<0xFFFF) cstr->isma_IV_size = 2; + else if (BSO<0xFFFFFFFF) cstr->isma_IV_size = 4; + else cstr->isma_IV_size = 8; + } + } + if (!cstr->isma_IV_size || (cstr->isma_IV_size > 8)) cstr->isma_IV_size = 8; + } + + if (!cstr->tci || !cstr->tci->KID_count) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[ISMACrypt] No keys specified for ISMA\n")); + return GF_BAD_PARAM; + } + + /*init crypto*/ + memset(IV, 0, sizeof(char)*16); + memcpy(IV, cstr->tci->first_IV, sizeof(char)*8); + if (cstr->crypt) gf_crypt_close(cstr->crypt); + cstr->crypt = gf_crypt_open(GF_AES_128, GF_CTR); + if (!cstr->crypt) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[ISMACrypt] Cannot open AES-128 CTR\n")); + return GF_IO_ERR; + } + + e = gf_crypt_init(cstr->crypt, cstr->tci->keys[0], IV); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[ISMACrypt] Cannot initialize AES-128 CTR (%s)\n", gf_error_to_string(e)) ); + return GF_IO_ERR; + } + + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("[ISMACrypt] Encrypting stream %s - KMS: %s%s\n", gf_filter_pid_get_name(cstr->ipid), cstr->tci->KMS_URI, cstr->tci->sel_enc_type ? " - Selective Encryption" : "")); + + if (!stricmp(kms_uri, "self")) { + char Data[100], d64[100]; + u32 size_b64; + memcpy(Data, cstr->tci->keys[0], sizeof(char)*16); + memcpy(Data+16, cstr->tci->first_IV, sizeof(char)*8); + size_b64 = gf_base64_encode(Data, 24, d64, 100); + d64[size_b64] = 0; + cstr->tci->KMS_URI = gf_realloc(cstr->tci->KMS_URI, size_b64+6); + strcpy(cstr->tci->KMS_URI, "(key)"); + strcat(cstr->tci->KMS_URI, d64); + kms_uri = cstr->tci->KMS_URI; + } + + /*create ISMA protection*/ + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_PROTECTION_SCHEME_VERSION, &PROP_UINT(1) ); + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_PROTECTION_KMS_URI, kms_uri ? &PROP_STRING(kms_uri) : NULL); + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_ISMA_SELECTIVE_ENC, &PROP_BOOL( (cstr->tci->sel_enc_type!=0) ? GF_TRUE : GF_FALSE) ); + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_ISMA_IV_LENGTH, &PROP_UINT(cstr->isma_IV_size) ); + + if (is_isma) { + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_PROTECTION_SCHEME_URI, scheme_uri ? &PROP_STRING(scheme_uri) : NULL ); + } else { + if ((cstr->tci->sel_enc_type==GF_CRYPT_SELENC_PREVIEW) && cstr->tci->sel_enc_range) { + char szSTR[100]; + u32 len; + char *szPreview; + + sprintf(szSTR, "PreviewRange:%d", cstr->tci->sel_enc_range); + len = (u32) strlen(szSTR) + ( cstr->tci->TextualHeaders ? (u32) strlen(cstr->tci->TextualHeaders) : 0 ) + 1; + szPreview = gf_malloc(sizeof(char) * len); + strcpy(szPreview, cstr->tci->TextualHeaders ? cstr->tci->TextualHeaders : ""); + strcat(szPreview, szSTR); + + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_OMA_TXT_HDR, &PROP_STRING_NO_COPY(szPreview)); + } else { + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_OMA_TXT_HDR, cstr->tci->TextualHeaders ? &PROP_STRING(cstr->tci->TextualHeaders) : NULL); + } + + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_OMA_CID, scheme_uri ? &PROP_STRING(scheme_uri) : NULL ); + + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_OMA_CRYPT_TYPE, &PROP_UINT(cstr->tci->encryption)); + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_OMA_CLEAR_LEN, &PROP_LONGUINT(cstr->tci->sel_enc_range) ); + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_ISMA_KI_LENGTH, &PROP_UINT(0) ); + } + + cstr->range_end = 0; + if (cstr->tci->sel_enc_type==GF_CRYPT_SELENC_PREVIEW) { + p = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_TIMESCALE); + if (p) + cstr->range_end = p->value.uint *cstr->tci->sel_enc_range; + } + + cstr->isma_oma = GF_TRUE; + cstr->prev_pck_encryped = GF_TRUE; + cstr->has_crypted_pck = GF_FALSE; + + //we drop IPMPX support for now +#if GPAC_DEPRECTAED + /*format as IPMP(X) - note that the ISMACryp spec is broken since it always uses IPMPPointers to a + single desc which would assume the same protection (eg key & salt) for all streams using it...*/ + if (!tci->ipmp_type) return GF_OK; + + ipmpdp = (GF_IPMPPtr*)gf_odf_desc_new(GF_ODF_IPMP_PTR_TAG); + if (!tci->ipmp_desc_id) tci->ipmp_desc_id = track; + if (tci->ipmp_type==2) { + ipmpdp->IPMP_DescriptorID = 0xFF; + ipmpdp->IPMP_DescriptorIDEx = tci->ipmp_desc_id; + } else { + ipmpdp->IPMP_DescriptorID = tci->ipmp_desc_id; + } + gf_isom_add_desc_to_description(mp4, track, 1, (GF_Descriptor *)ipmpdp); + gf_odf_desc_del((GF_Descriptor*)ipmpdp); + + ipmpdU = (GF_IPMPUpdate*)gf_odf_com_new(GF_ODF_IPMP_UPDATE_TAG); + /*format IPMPD*/ + ipmpd = (GF_IPMP_Descriptor*)gf_odf_desc_new(GF_ODF_IPMP_TAG); + if (tci->ipmp_type==2) { +#ifndef GPAC_MINIMAL_ODF + ipmpd->IPMP_DescriptorID = 0xFF; + ipmpd->IPMP_DescriptorIDEx = tci->ipmp_desc_id; + ipmpd->IPMPS_Type = 0xFFFF; + ipmpd->IPMP_ToolID[14] = 0x49; + ipmpd->IPMP_ToolID[15] = 0x53; + ipmpd->control_point = 1; + ipmpd->cp_sequence_code = 0x80; + /*format IPMPXData*/ + ismac = (GF_IPMPX_ISMACryp *) gf_ipmpx_data_new(GF_IPMPX_ISMACRYP_TAG); + ismac->cryptoSuite = 1; /*default ISMA AESCTR128*/ + ismac->IV_length = IV_size; + ismac->key_indicator_length = 0; + ismac->use_selective_encryption = (tci->sel_enc_type!=0)? 1 : 0; + gf_list_add(ipmpd->ipmpx_data, ismac); +#endif + } else { + ipmpd->IPMP_DescriptorID = tci->ipmp_desc_id; + } + gf_list_add(ipmpdU->IPMPDescList, ipmpd); + + for (i=0; idata, samp->dataLength); + gf_odf_codec_decode(cod); + gf_odf_codec_add_com(cod, (GF_ODCom *) ipmpdU); + gf_free(samp->data); + samp->data = NULL; + samp->dataLength = 0; + gf_odf_codec_encode(cod, 1); + gf_odf_codec_get_au(cod, &samp->data, &samp->dataLength); + ipmpdU = NULL; + gf_odf_codec_del(cod); + gf_isom_update_sample(mp4, i+1, 1, samp, 1); + gf_isom_sample_del(&samp); + + if (tci->ipmp_type==2) { + GF_IPMP_ToolList*ipmptl = (GF_IPMP_ToolList*)gf_odf_desc_new(GF_ODF_IPMP_TL_TAG); + GF_IPMP_Tool *ipmpt = (GF_IPMP_Tool*)gf_odf_desc_new(GF_ODF_IPMP_TOOL_TAG); + gf_list_add(ipmptl->ipmp_tools, ipmpt); + ipmpt->IPMP_ToolID[14] = 0x49; + ipmpt->IPMP_ToolID[15] = 0x53; + gf_isom_add_desc_to_root_od(mp4, (GF_Descriptor *)ipmptl); + gf_odf_desc_del((GF_Descriptor *)ipmptl); + } + break; + } +#endif + return GF_OK; +} + +static GF_Err adobe_enc_configure(GF_CENCEncCtx *ctx, GF_CENCStream *cstr) +{ + if (cstr->crypt) gf_crypt_close(cstr->crypt); + cstr->crypt = gf_crypt_open(GF_AES_128, GF_CBC); + if (!cstr->crypt) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[Adobe] Cannot open AES-128 CBC \n")); + return GF_IO_ERR; + } + + /*Adobe's protection scheme does not support selective key*/ + memcpy(cstr->key, cstr->tci->keys[0], 16); + + if (cstr->tci->metadata) + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_ADOBE_CRYPT_META, &PROP_DATA(cstr->tci->metadata, (u32) strlen(cstr->tci->metadata) ) ); + + cstr->is_adobe = GF_TRUE; + + return GF_OK; +} + +static GF_Err cenc_parse_pssh(GF_CENCEncCtx *ctx, GF_CENCStream *cstr, const char *cfile_name) +{ + GF_DOMParser *parser; + GF_XMLNode *root, *node; + u32 i; + GF_Err e; + u32 nb_pssh=0; + GF_BitStream *pssh_bs=NULL; + + parser = gf_xml_dom_new(); + e = gf_xml_dom_parse(parser, cfile_name, NULL, NULL); + if (e) { + gf_xml_dom_del(parser); + return e; + } + root = gf_xml_dom_get_root(parser); + if (!root) { + gf_xml_dom_del(parser); + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC/ISMA] Cannot open or validate xml file %s\n", ctx->cfile)); + return GF_NOT_SUPPORTED; + } + pssh_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + gf_bs_write_u32(pssh_bs, 0); + + i=0; + while ((node = (GF_XMLNode *) gf_list_enum(root->content, &i))) { + Bool is_pssh; + u32 version, cypherMode, specInfoSize, len, KID_count, j; + bin128 cypherKey, cypherIV, systemID; + GF_XMLAttribute *att; + u8 *data, *specInfo; + GF_BitStream *bs; + bin128 *KIDs; + s32 cypherOffset = -1; + Bool has_key = GF_FALSE, has_IV = GF_FALSE; + + if (strcmp(node->name, "DRMInfo")) continue; + + j = 0; + is_pssh = GF_FALSE; + version = cypherMode = 0; + data = specInfo = NULL; + bs = NULL; + + while ( (att = (GF_XMLAttribute *)gf_list_enum(node->attributes, &j))) { + if (!strcmp(att->name, "type")) { + if (!strcmp(att->value, "pssh")) + is_pssh = GF_TRUE; + } else if (!strcmp(att->name, "version")) { + version = atoi(att->value); + } else if (!strcmp(att->name, "cypherMode")) { + /*cypher-mode: 0: ctr (default mode) - 1: cbc - 2: clear*/ + if (!strcmp(att->value, "yes")) + cypherMode = 0; + else if (!strcmp(att->value, "no")) + cypherMode = 1; + } else if (!strcmp(att->name, "cypherKey")) { + e = gf_bin128_parse(att->value, cypherKey); + if (e != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Cannnot parse cypherKey\n")); + return e; + } + has_key = GF_TRUE; + } else if (!strcmp(att->name, "cypherIV")) { + e = gf_bin128_parse(att->value, cypherIV); + if (e != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Cannnot parse cypherIV\n")); + return e; + } + has_IV = GF_TRUE; + } else if (!strcmp(att->name, "cypherOffset")) { + cypherOffset = atoi(att->value); + } + } + + if (!is_pssh) { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[CENC/ISMA] Not a Protection System Specific Header Box - skipping\n")); + continue; + } + + e = gf_xml_parse_bit_sequence(node, cfile_name, &specInfo, &specInfoSize); + if (e) { + if (specInfo) gf_free(specInfo); + gf_xml_dom_del(parser); + return e; + } + + bs = gf_bs_new(specInfo, specInfoSize, GF_BITSTREAM_READ); + gf_bs_read_data(bs, (char *)systemID, 16); + if (version) { + KID_count = gf_bs_read_u32(bs); + KIDs = (bin128 *)gf_malloc(KID_count*sizeof(bin128)); + for (j = 0; j < KID_count; j++) { + gf_bs_read_data(bs, (char *)KIDs[j], 16); + } + } + else { + KID_count = 0; + KIDs = NULL; + } + if (specInfoSize < 16 + (version ? 4 + 16*KID_count : 0)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[CENC/ISMA] Invalid PSSH blob in version %d: size %d key count %d - ignoring PSSH\n", version, specInfoSize, KID_count)); + continue; + } + len = specInfoSize - 16 - (version ? 4 + 16*KID_count : 0); + data = (char *)gf_malloc(len*sizeof(char)); + gf_bs_read_data(bs, data, len); + + if (has_key && has_IV && (cypherOffset >= 0) && (cypherMode != 1)) { + GF_Crypt *gc = gf_crypt_open(GF_AES_128, GF_CTR); + if (!gc) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC/ISMA] Cannot open AES-128 CTR\n")); + return GF_IO_ERR; + } + e = gf_crypt_init(gc, cypherKey, cypherIV); + gf_crypt_encrypt(gc, data+cypherOffset, len-cypherOffset); + gf_crypt_close(gc); + } + if (!e) { + nb_pssh++; + gf_bs_write_data(pssh_bs, systemID, 16); + gf_bs_write_u32(pssh_bs, version); + gf_bs_write_u32(pssh_bs, KID_count); + for (j=0; jopid, GF_PROP_PID_CENC_PSSH, &PROP_DATA_NO_COPY(pssh, pssh_size)); + } + gf_bs_del(pssh_bs); + + return e; +} + + +static GF_Err cenc_enc_configure(GF_CENCEncCtx *ctx, GF_CENCStream *cstr, const char *cfile_name) +{ +#if !defined(GPAC_DISABLE_AV_PARSERS) + u32 i; +#endif + u32 dsi_crc=0; + Bool is_reinit=GF_FALSE; + GF_AVCConfig *avccfg; + GF_HEVCConfig *hevccfg; + const GF_PropertyValue *p; + + p = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_DECODER_CONFIG); + if (!p) p = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + + if (p) { + dsi_crc = gf_crc_32(p->value.data.ptr, p->value.data.size); + } + if (dsi_crc != cstr->dsi_crc) + is_reinit = GF_TRUE; + else if (!dsi_crc && !cstr->crypt) + is_reinit = GF_TRUE; + + cstr->dsi_crc = dsi_crc; + + if (is_reinit) { + const GF_PropertyValue *p2; + cstr->nalu_size_length = 0; + switch (cstr->codec_id) { + case GF_CODECID_AVC: + case GF_CODECID_SVC: + case GF_CODECID_MVC: + if (!p) + return GF_OK; + + avccfg = gf_odf_avc_cfg_read(p->value.data.ptr, p->value.data.size); + if (avccfg) cstr->nalu_size_length = avccfg->nal_unit_size; + + cstr->cenc_codec = CENC_AVC; + + p2 = gf_filter_pid_get_property(cstr->ipid, GF_PROP_PID_ISOM_SUBTYPE); + if (p2 && (p2->value.uint==GF_ISOM_BOX_TYPE_AVC1)) { + if (!cstr->tci->allow_encrypted_slice_header) { + cstr->slice_header_clear = GF_TRUE; + } else if (cstr->tci->scheme_type==GF_CRYPT_TYPE_CBCS) { + cstr->slice_header_clear = GF_TRUE; + } + } else { + cstr->slice_header_clear = GF_TRUE; + } +#ifndef GPAC_DISABLE_AV_PARSERS + if (avccfg) { + for (i=0; isequenceParameterSets); i++) { + GF_AVCConfigSlot *slc = gf_list_get(avccfg->sequenceParameterSets, i); + gf_media_avc_read_sps(slc->data, slc->size, &cstr->avc, 0, NULL); + } + for (i=0; ipictureParameterSets); i++) { + GF_AVCConfigSlot *slc = gf_list_get(avccfg->pictureParameterSets, i); + gf_media_avc_read_pps(slc->data, slc->size, &cstr->avc); + } + + gf_odf_avc_cfg_del(avccfg); + } +#else + if (avccfg) gf_odf_avc_cfg_del(avccfg); +#endif + cstr->bytes_in_nal_hdr = 1; + + if (!cstr->slice_header_clear && cstr->tci->clear_bytes) + cstr->bytes_in_nal_hdr = cstr->tci->clear_bytes; + + if (!cstr->nalu_size_length) { + cstr->nalu_size_length = 4; + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[CENCCrypt] Missing NALU length size, assuming 4\n") ); + } + break; + case GF_CODECID_HEVC: + case GF_CODECID_LHVC: + if (!p) + return GF_OK; + + hevccfg = gf_odf_hevc_cfg_read(p->value.data.ptr, p->value.data.size, (cstr->codec_id==GF_CODECID_LHVC) ? GF_TRUE : GF_FALSE); + if (hevccfg) cstr->nalu_size_length = hevccfg->nal_unit_size; + +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) + gf_media_hevc_parse_ps(hevccfg, &cstr->hevc, GF_HEVC_NALU_VID_PARAM); + gf_media_hevc_parse_ps(hevccfg, &cstr->hevc, GF_HEVC_NALU_SEQ_PARAM); + gf_media_hevc_parse_ps(hevccfg, &cstr->hevc, GF_HEVC_NALU_PIC_PARAM); +#endif + + //mandatory for HEVC + cstr->slice_header_clear = GF_TRUE; + + cstr->cenc_codec = CENC_HEVC; + + if (hevccfg) gf_odf_hevc_cfg_del(hevccfg); + cstr->bytes_in_nal_hdr = 2; + + if (!cstr->nalu_size_length) { + cstr->nalu_size_length = 4; + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[CENCCrypt] Missing NALU length size, assuming 4\n") ); + } + break; + case GF_CODECID_AV1: + if (!p) + return GF_OK; + +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_AV1) + cstr->av1.config = gf_odf_av1_cfg_read(p->value.data.ptr, p->value.data.size); + cstr->cenc_codec = CENC_AV1; + cstr->bytes_in_nal_hdr = 2; +#endif + //mandatory for AV1 + cstr->slice_header_clear = GF_TRUE; + break; + case GF_CODECID_VP8: + case GF_CODECID_VP9: + if (p) { + cstr->cenc_codec = CENC_VPX; + cstr->bytes_in_nal_hdr = 2; + } + break; + } + } + + if (((cstr->tci->scheme_type == GF_CRYPT_TYPE_CENS) || (cstr->tci->scheme_type == GF_CRYPT_TYPE_CBCS) ) && (cstr->cenc_codec>CENC_FULL_SAMPLE) && (cstr->cenc_codec<=CENC_AV1) + ) { + if (!cstr->tci->crypt_byte_block || !cstr->tci->skip_byte_block) { + if (cstr->tci->crypt_byte_block || cstr->tci->skip_byte_block) { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[CENC] Using pattern mode, crypt_byte_block and skip_byte_block shall be 0 only for track other than video, using 1 crypt + 9 skip\n")); + } + cstr->tci->crypt_byte_block = 1; + cstr->tci->skip_byte_block = 9; + } + } + + if ( (cstr->cenc_codec==CENC_VPX) && (cstr->tci->scheme_type != GF_CRYPT_TYPE_CENC) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Encryption mode %s is not supported with VP8/VP9, only cenc is\n", gf_4cc_to_str(cstr->tci->scheme_type) )); + return GF_NOT_SUPPORTED; + } + + + cstr->use_subsamples = GF_FALSE; + if (cstr->cenc_codec != CENC_FULL_SAMPLE) cstr->use_subsamples = GF_TRUE; + //CBCS mode with skip byte block may be used for any track, in which case we need subsamples + else if (cstr->tci->scheme_type == GF_CRYPT_TYPE_CBCS) { + if (cstr->tci->skip_byte_block) { + cstr->use_subsamples = GF_TRUE; + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("\n[CENC] Using cbcs pattern mode on non NAL video track, this may not be supported by most devices; consider setting skip_byte_block to 0\n\n")); + //cbcs allows bytes of clear data + cstr->bytes_in_nal_hdr = cstr->tci->clear_bytes; + } + //This is not clear in the spec, setting skip and crypt to 0 means no pattern, in which case tenc version shall be 0 + //but cbcs asks for 1 - needs further clarification +#if 0 + //setup defaults + else if (!cstr->tci->crypt_byte_block) { + cstr->tci->crypt_byte_block = 1; + } +#else + else { + cstr->tci->crypt_byte_block = 0; + } +#endif + } + else if ((cstr->tci->scheme_type == GF_CRYPT_TYPE_CENS) && cstr->tci->skip_byte_block) { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[CENC] Using cens pattern mode on non NAL video track not allowed, forcing skip_byte_block to 0\n")); + cstr->tci->skip_byte_block = 0; + if (!cstr->tci->crypt_byte_block) { + cstr->tci->crypt_byte_block = 1; + } + } + + if (is_reinit) { + if (cstr->crypt) gf_crypt_close(cstr->crypt); + if (cstr->ctr_mode) { + cstr->crypt = gf_crypt_open(GF_AES_128, GF_CTR); + } + else { + cstr->crypt = gf_crypt_open(GF_AES_128, GF_CBC); + } + + if (!cstr->crypt) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Cannot open AES-128 %s\n", cstr->ctr_mode ? "CTR" : "CBC")); + return GF_IO_ERR; + } + + /*select key*/ + if (!cstr->tci->keys) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] No key specified\n")); + return GF_BAD_PARAM; + } + if (cstr->tci->defaultKeyIdx && (cstr->tci->defaultKeyIdx < cstr->tci->KID_count)) { + memcpy(cstr->key, cstr->tci->keys[cstr->tci->defaultKeyIdx], 16); + memcpy(cstr->default_KID, cstr->tci->KIDs[cstr->tci->defaultKeyIdx], 16); + cstr->kidx = cstr->tci->defaultKeyIdx; + } else { + memcpy(cstr->key, cstr->tci->keys[0], 16); + memcpy(cstr->default_KID, cstr->tci->KIDs[0], 16); + cstr->kidx = 0; + } + cstr->prev_pck_encryped = cstr->tci->IsEncrypted; + } + + /*set CENC protection properties*/ + + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_PROTECTION_SCHEME_VERSION, &PROP_UINT(0x00010000) ); + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_ENCRYPTED, &PROP_BOOL(cstr->tci->IsEncrypted ? GF_TRUE : GF_FALSE) ); + + if (cstr->tci->IV_size) { + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_CENC_IV_SIZE, &PROP_UINT(cstr->tci->IV_size) ); + } else { + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_CENC_IV_SIZE, NULL); + } + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_KID, &PROP_DATA(cstr->default_KID, sizeof(bin128) ) ); + + if (cstr->tci->skip_byte_block || cstr->tci->crypt_byte_block) { + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_CENC_PATTERN, &PROP_FRAC_INT(cstr->tci->skip_byte_block, cstr->tci->crypt_byte_block ) ); + } else { + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_CENC_PATTERN, NULL); + } + if (cstr->tci->constant_IV_size) { + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_CENC_IV_CONST, &PROP_DATA(cstr->tci->constant_IV, cstr->tci->constant_IV_size) ); + } else { + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_CENC_IV_CONST, NULL ); + } + //if constantIV and not using CENC subsample, no CENC auxiliary info + if (!cstr->tci->constant_IV_size || cstr->use_subsamples) { + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_CENC_STORE, &PROP_UINT(cstr->tci->sai_saved_box_type) ); + } + + //parse pssh even if reinit since we need to reassign pssh property + return cenc_parse_pssh(ctx, cstr, cfile_name); +} + +static GF_Err cenc_enc_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *prop; + GF_Err e = GF_OK; + u32 scheme_type = 0; + GF_TrackCryptInfo *tci = NULL; + const char *scheme_uri = NULL; + const char *kms_uri = NULL; + GF_CryptInfo *cinfo = NULL; + GF_CENCStream *cstr; + GF_CENCEncCtx *ctx = (GF_CENCEncCtx *)gf_filter_get_udta(filter); + const char *cfile_name = ctx->cfile; + GF_TrackCryptInfo *tci_any = NULL; + u32 i, count; + + + if (is_remove) { + cstr = gf_filter_pid_get_udta(pid); + if (cstr) { + gf_list_del_item(ctx->streams, cstr); + gf_filter_pid_remove(cstr->opid); + if (cstr->crypt) gf_crypt_close(cstr->crypt); + if (cstr->cinfo) gf_crypt_info_del(cstr->cinfo); + gf_free(cstr); + } + return GF_OK; + } + + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CRYPT_INFO); + if (prop) { + cinfo = gf_crypt_info_load(prop->value.string, &e); + if (!cinfo) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[CENCrypt] failed to load crypt info file %s for pid %s\n", prop->value.string, gf_filter_pid_get_name(pid) ) ); + return e; + } + cfile_name = prop->value.string; + } + if (!cinfo) cinfo = ctx->cinfo; + + + if (cinfo) { + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + count = gf_list_count(cinfo->tcis); + for (i=0; itcis, i); + if (prop && tci->trackID && (tci->trackID==prop->value.uint)) break; + if (!tci_any && !tci->trackID) tci_any = tci; + if ((cinfo != ctx->cinfo) && !tci_any) tci_any = tci; + tci = NULL; + } + if (!tci) tci = tci_any; + } else if (ctx->allc) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[CENCrypt] Missing DRM config file\n") ); + return GF_NOT_SUPPORTED; + } + + if (!tci) { + if (cinfo) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[CENCrypt] Missing track crypt info in DRM config file, PID will not be crypted\n") ); + } + } else { + scheme_type = tci->scheme_type; + + switch (scheme_type) { + case GF_CRYPT_TYPE_ISMA: + case GF_CRYPT_TYPE_OMA: + case GF_CRYPT_TYPE_ADOBE: + case GF_CRYPT_TYPE_CENC: + case GF_CRYPT_TYPE_CBC1: + case GF_CRYPT_TYPE_CENS: + case GF_CRYPT_TYPE_CBCS: + case GF_CRYPT_TYPE_PIFF: + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[CENCrypt] Unsupported scheme type %s\n", gf_4cc_to_str(scheme_type) )); + if (cinfo != ctx->cinfo) gf_crypt_info_del(cinfo); + return GF_NOT_SUPPORTED; + } + } + + cstr = gf_filter_pid_get_udta(pid); + if (!cstr) { + GF_SAFEALLOC(cstr, GF_CENCStream); + if (!cstr) return GF_OUT_OF_MEM; + cstr->ipid = pid; + cstr->opid = gf_filter_pid_new(filter); + cstr->tci = tci; + gf_list_add(ctx->streams, cstr); + gf_filter_pid_set_udta(pid, cstr); + } + if (cstr->cinfo) gf_crypt_info_del(cstr->cinfo); + cstr->cinfo = (cinfo != ctx->cinfo) ? cinfo : NULL; + cstr->tci = tci; + cstr->passthrough = tci ? GF_FALSE : GF_TRUE; + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(cstr->opid, pid); + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (prop) cstr->codec_id = prop->value.uint; + + if (cstr->tci) { + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_PROTECTION_SCHEME_TYPE, &PROP_UINT(scheme_type) ); + + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (prop) { + switch (prop->value.uint) { + case GF_STREAM_VISUAL: + case GF_STREAM_AUDIO: + case GF_STREAM_SCENE: + case GF_STREAM_FONT: + case GF_STREAM_TEXT: + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_ORIG_STREAM_TYPE, & PROP_UINT(prop->value.uint) ); + break; + default: + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("[CENCrypt] Stream type %s cannot be encrypted, using passthrough\n", gf_stream_type_name(prop->value.uint) )); + cstr->passthrough = GF_TRUE; + break; + } + } + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_CENC_STSD_MODE, &PROP_UINT(tci->force_clear_stsd_idx) ); + } + + if (cstr->passthrough) return GF_OK; + + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_ENCRYPTED) ); + + scheme_uri = cstr->tci->Scheme_URI; + kms_uri = cstr->tci->KMS_URI; + + /*default to FILE uri*/ + if (!kms_uri || !strlen(kms_uri) ) { + kms_uri = cfile_name; + } + + cstr->ctr_mode = GF_FALSE; + + switch (scheme_type) { + case GF_CRYPT_TYPE_ISMA: + cstr->ctr_mode = GF_TRUE; + return isma_enc_configure(ctx, cstr, GF_TRUE, scheme_uri, kms_uri); + case GF_CRYPT_TYPE_OMA: + return isma_enc_configure(ctx, cstr, GF_FALSE, scheme_uri, kms_uri); + case GF_CRYPT_TYPE_ADOBE: + return adobe_enc_configure(ctx, cstr); + case GF_CRYPT_TYPE_CENC: + case GF_CRYPT_TYPE_CENS: + case GF_CRYPT_TYPE_PIFF: + cstr->ctr_mode = GF_TRUE; + case GF_CRYPT_TYPE_CBC1: + case GF_CRYPT_TYPE_CBCS: + return cenc_enc_configure(ctx, cstr, cfile_name); + default: + return GF_NOT_SUPPORTED; + } + + return e; +} + + +static GF_Err isma_process(GF_CENCEncCtx *ctx, GF_CENCStream *cstr, GF_FilterPacket *pck) +{ + u32 isma_hdr_size=0; + GF_FilterPacket *dst_pck; + const u8 *data; + u8 *output; + u32 size, rand, flags; + u64 cts = gf_filter_pck_get_cts(pck); + u8 sap = gf_filter_pck_get_sap(pck); + + + flags = 0; + + switch (cstr->tci->sel_enc_type) { + case GF_CRYPT_SELENC_RAP: + if (sap) flags |= GF_ISOM_ISMA_IS_ENCRYPTED; + break; + case GF_CRYPT_SELENC_NON_RAP: + if (!sap) flags |= GF_ISOM_ISMA_IS_ENCRYPTED; + break; + /*random*/ + case GF_CRYPT_SELENC_RAND: + rand = gf_rand(); + if (rand%2) flags |= GF_ISOM_ISMA_IS_ENCRYPTED; + break; + /*random every sel_freq samples*/ + case GF_CRYPT_SELENC_RAND_RANGE: + if (! (cstr->nb_pck % cstr->tci->sel_enc_range) ) cstr->has_crypted_pck = 0; + if (! cstr->has_crypted_pck) { + rand = gf_rand(); + if (!(rand % cstr->tci->sel_enc_range)) flags |= GF_ISOM_ISMA_IS_ENCRYPTED; + + if (!(flags & GF_ISOM_ISMA_IS_ENCRYPTED) && !( (1+cstr->nb_pck) % cstr->tci->sel_enc_range)) { + flags |= GF_ISOM_ISMA_IS_ENCRYPTED; + } + cstr->has_crypted_pck = (flags & GF_ISOM_ISMA_IS_ENCRYPTED); + } + break; + /*every sel_freq samples*/ + case GF_CRYPT_SELENC_RANGE: + if (!(cstr->nb_pck % cstr->tci->sel_enc_type)) flags |= GF_ISOM_ISMA_IS_ENCRYPTED; + break; + case GF_CRYPT_SELENC_PREVIEW: + if (cts >= cstr->range_end) + flags |= GF_ISOM_ISMA_IS_ENCRYPTED; + break; + case 0: + flags |= GF_ISOM_ISMA_IS_ENCRYPTED; + break; + default: + break; + } + + if (cstr->tci->sel_enc_type) flags |= GF_ISOM_ISMA_USE_SEL_ENC; + + if (flags & GF_ISOM_ISMA_USE_SEL_ENC) isma_hdr_size = 1; + if (flags & GF_ISOM_ISMA_IS_ENCRYPTED) isma_hdr_size += cstr->isma_IV_size + cstr->KI_length; + + data = gf_filter_pck_get_data(pck, &size); + if (!data) { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[ISMACrypt] No data associated with packet\n" )); + return GF_OK; + } + dst_pck = gf_filter_pck_new_alloc(cstr->opid, size+isma_hdr_size, &output); + memcpy(output+isma_hdr_size, data, sizeof(char)*size); + gf_filter_pck_merge_properties(pck, dst_pck); + + + /*isma e&a stores AVC1 in AVC/H264 annex B bitstream fashion, with 0x00000001 start codes*/ + if (cstr->nalu_size_length) { + u32 done = 0; + u8 *d = (u8*) output+isma_hdr_size; + while (done < size) { + u32 nal_size = GF_4CC(d[0], d[1], d[2], d[3]); + d[0] = d[1] = d[2] = 0; + d[3] = 1; + d += 4 + nal_size; + done += 4 + nal_size; + } + } + + //encrypt + if (flags & GF_ISOM_ISMA_IS_ENCRYPTED) { + /*resync IV*/ + if (!cstr->prev_pck_encryped) { + char IV[17]; + u64 count; + u32 remain; + GF_BitStream *bs; + count = cstr->BSO / 16; + remain = (u32) (cstr->BSO % 16); + + /*format IV to begin of counter*/ + bs = gf_bs_new(IV, 17, GF_BITSTREAM_WRITE); + gf_bs_write_u8(bs, 0); /*begin of counter*/ + gf_bs_write_data(bs, cstr->tci->first_IV, 8); + gf_bs_write_u64(bs, (s64) count); + gf_bs_del(bs); + gf_crypt_set_IV(cstr->crypt, IV, GF_AES_128_KEYSIZE+1); + + /*decrypt remain bytes*/ + if (remain) { + char dummy[20]; + gf_crypt_decrypt(cstr->crypt, dummy, remain); + } + } + gf_crypt_encrypt(cstr->crypt, output+isma_hdr_size, size); + cstr->prev_pck_encryped = GF_TRUE; + } else { + cstr->prev_pck_encryped = GF_FALSE; + } + + //rewrite ISMA header + if (!ctx->bs_w) ctx->bs_w = gf_bs_new(output, isma_hdr_size, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs_w, output, isma_hdr_size); + + if (flags & GF_ISOM_ISMA_USE_SEL_ENC) { + gf_bs_write_int(ctx->bs_w, (flags & GF_ISOM_ISMA_IS_ENCRYPTED) ? 1 : 0, 1); + gf_bs_write_int(ctx->bs_w, 0, 7); + } + if (flags & GF_ISOM_ISMA_IS_ENCRYPTED) { + if (cstr->isma_IV_size) gf_bs_write_long_int(ctx->bs_w, (s64) cstr->BSO, 8*cstr->isma_IV_size); + //not yet implemented +// if (cstr->KI_length) gf_bs_write_data(ctx->bs_w, (char*) key_indicator, cstr->KI_length); + } + + cstr->BSO += size; + + gf_filter_pck_send(dst_pck); + return GF_OK; +} + +static GF_Err adobe_process(GF_CENCEncCtx *ctx, GF_CENCStream *cstr, GF_FilterPacket *pck) +{ + Bool is_encrypted = GF_TRUE; + GF_FilterPacket *dst_pck; + const u8 *data; + u8 *output; + GF_Err e; + bin128 IV; + u32 size, adobe_hdr_size; + u32 len, padding_bytes; + u8 sap = gf_filter_pck_get_sap(pck); + + switch (cstr->tci->sel_enc_type) { + case GF_CRYPT_SELENC_RAP: + if (!sap) is_encrypted = GF_FALSE; + break; + case GF_CRYPT_SELENC_NON_RAP: + if (sap) is_encrypted = GF_FALSE; + break; + default: + break; + } + + adobe_hdr_size = 1; + if (is_encrypted) adobe_hdr_size += 16; + + data = gf_filter_pck_get_data(pck, &size); + if (!data) { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[ISMACrypt] No data associated with packet\n" )); + return GF_OK; + } + padding_bytes = 0; + len = size; + if (is_encrypted) { + padding_bytes = 16 - len % 16; + len += padding_bytes; + } + dst_pck = gf_filter_pck_new_alloc(cstr->opid, len + adobe_hdr_size, &output); + memcpy(output+adobe_hdr_size, data, sizeof(char)*size); + gf_filter_pck_merge_properties(pck, dst_pck); + + + if (is_encrypted) { + if (!cstr->prev_pck_encryped) { + memcpy(IV, cstr->tci->first_IV, sizeof(char)*16); + e = gf_crypt_init(cstr->crypt, cstr->key, IV); + cstr->prev_pck_encryped = GF_TRUE; + } else { + cstr->isma_IV_size = 16; + e = gf_crypt_get_IV(cstr->crypt, IV, &cstr->isma_IV_size); + } + + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[ADOBE] Cannot initialize AES-128 CBC (%s)\n", gf_error_to_string(e)) ); + gf_filter_pck_discard(dst_pck); + return GF_IO_ERR; + } + + memset(output+adobe_hdr_size+size, padding_bytes, sizeof(char)*padding_bytes); + + gf_crypt_encrypt(cstr->crypt, output+adobe_hdr_size, len); + + /*write encrypted AU header*/ + output[0] = 0x10; + memcpy(output+1, (char *) IV, sizeof(char) * 16); + } else { + /*write encrypted AU header*/ + output[0] = 0x0; + } + + gf_filter_pck_send(dst_pck); + return GF_OK; +} + + +/*Common Encryption*/ +static void increase_counter(char *x, int x_size) { + register int i; + + for (i=x_size-1; i>=0; i--) { + register int y=0; + if ((u8) x[i] == 0xFF) { + x[i] = 0; + y = 1; + } else x[i]++; + + if (y==0) break; + } + + return; +} + +static void cenc_resync_IV(GF_Crypt *mc, char IV[16], u8 IV_size) +{ + char next_IV[17]; + u32 size = 17; + + gf_crypt_get_IV(mc, (u8 *) next_IV, &size); + /* + NOTE 1: the next_IV returned by get_state has 17 bytes, the first byte being the current counter position in the following 16 bytes. + If this index is 0, this means that we are at the begining of a new block and we can use it as IV for next sample, + otherwise we must discard unsued bytes in the counter (next sample shall begin with counter at 0) + if less than 16 blocks were cyphered, we must force increasing the next IV for next sample, not doing so would produce the same IV for the next bytes cyphered, + which is forbidden by CENC (unique IV per sample). In GPAC, we ALWAYS force counter increase + + NOTE 2: in case where IV_size is 8, because the cypher block is treated as 16 bytes while processing, + we need to increment manually the 8-bytes IV (bytes 0 to 7) for the next sample, otherwise we would likely the same IV (eg unless we had cyphered 16 * 2^64 - 1 + bytes in the last sample , quite unlikely !) + + NOTE 3: Bytes 8 to 15 are set to 0 when forcing a new IV for 8-bytes IVs. + + NOTE 4: Since CENC forces declaration of a unique, potentially random, IV per sample, we could increase the IV counter at each sample start + but this is currently not done + */ + if (IV_size == 8) { + /*cf note 2*/ + increase_counter(&next_IV[1], IV_size); + next_IV[0] = 0; + /*cf note 3*/ + memset(&next_IV[9], 0, 8*sizeof(char)); + } else if (next_IV[0]) { + /*cf note 1*/ + increase_counter(&next_IV[1], IV_size); + next_IV[0] = 0; + } + + gf_crypt_set_IV(mc, next_IV, size); + + memset(IV, 0, 16*sizeof(char)); + memcpy(IV, next_IV+1, 16*sizeof(char)); +} + +#ifndef GPAC_DISABLE_AV_PARSERS +//parses slice header and returns its size +static u32 cenc_get_clear_bytes(GF_CENCStream *cstr, GF_BitStream *plaintext_bs, char *samp_data, u32 nal_size, u32 bytes_in_nalhr) +{ + u32 clear_bytes = 0; + if (cstr->slice_header_clear) { + u32 nal_start = (u32) gf_bs_get_position(plaintext_bs); + if (cstr->cenc_codec==CENC_AVC) { + u32 ntype; + gf_media_avc_parse_nalu(plaintext_bs, &cstr->avc); + ntype = cstr->avc.last_nal_type_parsed; + switch (ntype) { + case GF_AVC_NALU_NON_IDR_SLICE: + case GF_AVC_NALU_DP_A_SLICE: + case GF_AVC_NALU_DP_B_SLICE: + case GF_AVC_NALU_DP_C_SLICE: + case GF_AVC_NALU_IDR_SLICE: + case GF_AVC_NALU_SLICE_AUX: + case GF_AVC_NALU_SVC_SLICE: + gf_bs_align(plaintext_bs); + clear_bytes = (u32) gf_bs_get_position(plaintext_bs) - nal_start; + break; + default: + clear_bytes = nal_size; + break; + } + } else { +#if !defined(GPAC_DISABLE_HEVC) + u8 ntype, ntid, nlid; + cstr->hevc.full_slice_header_parse = GF_TRUE; + gf_media_hevc_parse_nalu (samp_data + nal_start, nal_size, &cstr->hevc, &ntype, &ntid, &nlid); + if (ntype<=GF_HEVC_NALU_SLICE_CRA) { + clear_bytes = cstr->hevc.s_info.payload_start_offset; + } else { + clear_bytes = nal_size; + } +#endif + } + gf_bs_seek(plaintext_bs, nal_start); + } else { + clear_bytes = bytes_in_nalhr; + } + gf_bs_enable_emulation_byte_removal(plaintext_bs, GF_FALSE); + return clear_bytes; +} +#endif + +static GF_Err cenc_encrypt_packet(GF_CENCEncCtx *ctx, GF_CENCStream *cstr, GF_FilterPacket *pck) +{ + GF_BitStream *sai_bs; + u32 prev_entry_bytes_clear=0; + u32 prev_entry_bytes_crypt=0; + u32 pck_size; + GF_FilterPacket *dst_pck; + const u8 *data; + u8 *output; + u32 sai_size = cstr->tci->IV_size; + u32 nb_subsamples=0; + + //in cbcs scheme, if Per_Sample_IV_size is not 0 (no constant IV), fetch current IV + if (!cstr->ctr_mode && cstr->tci->IV_size) { + u32 IV_size = 16; + gf_crypt_get_IV(cstr->crypt, cstr->IV, &IV_size); + } + + + data = gf_filter_pck_get_data(pck, &pck_size); + + //CENC can use inplace processing for decryption + dst_pck = gf_filter_pck_new_clone(cstr->opid, pck, &output); + if (!dst_pck) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Failed to allocated/clone packet for encrypting payload\n" ) ); + gf_filter_pid_drop_packet(cstr->ipid); + return GF_SERVICE_ERROR; + } + + gf_filter_pck_merge_properties(pck, dst_pck); + gf_filter_pck_set_crypt_flags(dst_pck, GF_FILTER_PCK_CRYPT); + + if (!ctx->bs_r) ctx->bs_r = gf_bs_new(data, pck_size, GF_BITSTREAM_READ); + else gf_bs_reassign_buffer(ctx->bs_r, data, pck_size); + + sai_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + + gf_bs_write_data(sai_bs, cstr->IV, cstr->tci->IV_size); + + while (gf_bs_available(ctx->bs_r)) { + GF_Err e=GF_OK; + + if (cstr->use_subsamples) { +#ifndef GPAC_DISABLE_AV_PARSERS + ObuType obut; + u32 num_frames_in_superframe = 0, superframe_index_size = 0; + u32 frame_sizes[VP9_MAX_FRAMES_IN_SUPERFRAME]; + struct { + int clear, encrypted; + } ranges[AV1_MAX_TILE_ROWS * AV1_MAX_TILE_COLS]; + u64 obu_size; + u32 hdr_size; + u32 i; +#else + struct { + int clear, encrypted; + } ranges[1]; +#endif + u64 pos; + u32 clear_bytes_at_end = 0; + u32 clear_bytes = 0; + u32 nb_ranges = 1; + u32 range_idx = 0; + u32 nalu_size = 0; + +#ifndef GPAC_DISABLE_AV_PARSERS + switch (cstr->cenc_codec) { + case CENC_AVC: + case CENC_HEVC: + nalu_size = gf_bs_read_int(ctx->bs_r, 8*cstr->nalu_size_length); + if (nalu_size == 0) { + continue; + } + clear_bytes = cenc_get_clear_bytes(cstr, ctx->bs_r, (char *) data, nalu_size, cstr->bytes_in_nal_hdr); + break; + + case CENC_AV1: + pos = gf_bs_get_position(ctx->bs_r); + e = gf_media_aom_av1_parse_obu(ctx->bs_r, &obut, &obu_size, &hdr_size, &cstr->av1); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Failed to parse OBU\n" )); + return e; + } + gf_bs_seek(ctx->bs_r, pos); + + nalu_size = (u32)obu_size; + switch (obut) { + //we only encrypt frame and tile group + case OBU_FRAME: + case OBU_TILE_GROUP: + if (!cstr->av1.frame_state.nb_tiles_in_obu) { + clear_bytes = (u32) obu_size; + } else { + nb_ranges = cstr->av1.frame_state.nb_tiles_in_obu; + + ranges[0].clear = cstr->av1.frame_state.tiles[0].obu_start_offset; + ranges[0].encrypted = cstr->av1.frame_state.tiles[0].size; + for (i = 1; i < nb_ranges; ++i) { + ranges[i].clear = cstr->av1.frame_state.tiles[i].obu_start_offset - (cstr->av1.frame_state.tiles[i - 1].obu_start_offset + cstr->av1.frame_state.tiles[i - 1].size); + ranges[i].encrypted = cstr->av1.frame_state.tiles[i].size; + } + clear_bytes = ranges[0].clear; + nalu_size = clear_bytes + ranges[0].encrypted; + + /* A subsample SHALL be created for each tile even if less than 16 bytes + see https://github.com/AOMediaCodec/av1-isobmff/pull/116#discussion_r340176740 + + If previous range had encrypted bytes, create a new one, otherwise merge in prev + */ + if (prev_entry_bytes_crypt) { + if (!nb_subsamples) gf_bs_write_u16(sai_bs, 0); + nb_subsamples++; + gf_bs_write_u16(sai_bs, prev_entry_bytes_clear); + gf_bs_write_u32(sai_bs, prev_entry_bytes_crypt); + sai_size+=6; + + prev_entry_bytes_crypt = 0; + prev_entry_bytes_clear = 0; + } + } + break; + default: + clear_bytes = (u32) obu_size; + break; + } + break; + case CENC_VPX: + if (cstr->tci->block_align != 2) { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[CENC] VP9 mandates that blockAlign=\"always\". Forcing value.\n")); + cstr->tci->block_align = 2; + } + + pos = gf_bs_get_position(ctx->bs_r); + e = gf_media_vp9_parse_superframe(ctx->bs_r, pck_size, &num_frames_in_superframe, frame_sizes, &superframe_index_size); + if (e || !num_frames_in_superframe) return e; + gf_bs_seek(ctx->bs_r, pos); + + nb_ranges = num_frames_in_superframe; + + for (i = 0; i < num_frames_in_superframe; ++i) { + Bool key_frame; + u32 width = 0, height = 0, renderWidth = 0, renderHeight = 0; + GF_VPConfig *vp9_cfg = gf_odf_vp_cfg_new(); + u64 pos2 = gf_bs_get_position(ctx->bs_r); + e = gf_media_vp9_parse_sample(ctx->bs_r, vp9_cfg, &key_frame, &width, &height, &renderWidth, &renderHeight); + gf_odf_vp_cfg_del(vp9_cfg); + if (e) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[CENC] Error parsing VP9 frame at DTS "LLU"\n", gf_filter_pck_get_dts(pck) )); + return e; + } + + ranges[i].clear = (int)(gf_bs_get_position(ctx->bs_r) - pos2); + ranges[i].encrypted = frame_sizes[i] - ranges[i].clear; + + gf_bs_seek(ctx->bs_r, pos2 + frame_sizes[i]); + } + if (gf_bs_get_position(ctx->bs_r) + superframe_index_size != pos + pck_size) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[CENC] Inconsistent VP9 size %u (parsed "LLU") at DTS "LLU". Re-import raw VP9/IVF for more details.\n", + pck_size, gf_bs_get_position(ctx->bs_r) + superframe_index_size - pos, gf_filter_pck_get_dts(pck))); + } + gf_bs_seek(ctx->bs_r, pos); + + clear_bytes = ranges[0].clear; + assert(frame_sizes[0] == ranges[0].clear + ranges[0].encrypted); + nalu_size = frame_sizes[0]; + + //final superframe index must be in clear + if (superframe_index_size > 0) { + ranges[nb_ranges].clear = superframe_index_size; + ranges[nb_ranges].encrypted = 0; + nb_ranges++; + } + + //not clearly defined in the spec (so we do the same as in AV1 which is more clearly defined): + if (frame_sizes[0] - clear_bytes >= 16) { + //A subsample SHALL be created for each tile >= 16 bytes. If previous range had encrypted bytes, create a new one, otherwise merge in prev + if (prev_entry_bytes_crypt) { + if (!nb_subsamples) gf_bs_write_u16(sai_bs, 0); + nb_subsamples++; + gf_bs_write_u16(sai_bs, prev_entry_bytes_clear); + gf_bs_write_u32(sai_bs, prev_entry_bytes_crypt); + sai_size+=6; + + prev_entry_bytes_crypt = 0; + prev_entry_bytes_clear = 0; + } + } else { + clear_bytes = nalu_size; + } + break; + default: + assert(0); + } +#else + clear_bytes = nalu_size; +#endif + + while (nb_ranges) { + if (cstr->ctr_mode) { + + // adjust so that encrypted bytes are a multiple of 16 bytes: cenc SHOULD, cens SHALL, we always do it + if (nalu_size > clear_bytes) { + u32 ret = (nalu_size - clear_bytes) % 16; + //in AV1 always enforced + if (cstr->cenc_codec==CENC_AV1) { + clear_bytes += ret; + } + //for CENC (should), + else if ((cstr->tci->scheme_type == GF_CRYPT_TYPE_CENC) || (cstr->tci->scheme_type == GF_CRYPT_TYPE_PIFF)) { + //do it if not disabled by user + if (cstr->tci->block_align != 1) { + //always align even if sample is not encrypted in the end + if (cstr->tci->block_align==2) { + clear_bytes += ret; + } + //or if we don't end up with sample in the clear + else if (nalu_size > clear_bytes + ret) { + clear_bytes += ret; + } + } + } else { + clear_bytes += ret; + } + } + } else { + //in cbcs, we don't adjust bytes_encrypted_data to be a multiple of 16 bytes and leave the last block unencrypted + //except in VPX, where BytesOfProtectedData SHALL end on the last byte of the decode_tile structure + if ((cstr->cenc_codec != CENC_VPX) && (cstr->tci->scheme_type == GF_CRYPT_TYPE_CBCS)) { + u32 ret = (nalu_size - clear_bytes) % 16; + clear_bytes_at_end = ret; + } + //in cbc1 or cbcs+VPX, we adjust bytes_encrypted_data to be a multiple of 16 bytes + else { + u32 ret = (nalu_size - clear_bytes) % 16; + clear_bytes += ret; + clear_bytes_at_end = 0; + } + + } + + /*skip bytes of clear data*/ + gf_bs_skip_bytes(ctx->bs_r, clear_bytes); + + //read data to encrypt + if (nalu_size > clear_bytes) { + /*get encrypted data start*/ + u32 cur_pos = (u32) gf_bs_get_position(ctx->bs_r); + + /*skip bytes of encrypted data*/ + gf_bs_skip_bytes(ctx->bs_r, nalu_size - clear_bytes); + + //cbcs scheme with constant IV, reinit at each sub sample, + if (!cstr->ctr_mode && !cstr->tci->IV_size) + gf_crypt_set_IV(cstr->crypt, cstr->IV, 16); + + //pattern encryption + if (cstr->tci->crypt_byte_block && cstr->tci->skip_byte_block) { + u32 res = nalu_size - clear_bytes - clear_bytes_at_end; + pos = cur_pos; + assert((res % 16) == 0); + + while (res) { + e = gf_crypt_encrypt(cstr->crypt, output+pos, res >= (u32) (16*cstr->tci->crypt_byte_block) ? 16*cstr->tci->crypt_byte_block : res); + if (res >= (u32) (16 * (cstr->tci->crypt_byte_block + cstr->tci->skip_byte_block))) { + pos += 16 * (cstr->tci->crypt_byte_block + cstr->tci->skip_byte_block); + res -= 16 * (cstr->tci->crypt_byte_block + cstr->tci->skip_byte_block); + } else { + res = 0; + } + } + } + //full subsample encryption + else { + e = gf_crypt_encrypt(cstr->crypt, output+cur_pos, nalu_size - clear_bytes); + } + } + + + //prev entry is not a VCL, append this NAL + if (!prev_entry_bytes_crypt) { + prev_entry_bytes_clear += cstr->nalu_size_length + clear_bytes; + prev_entry_bytes_crypt += nalu_size - clear_bytes; + } else { + //store current + if (!nb_subsamples) gf_bs_write_u16(sai_bs, 0); + nb_subsamples++; + gf_bs_write_u16(sai_bs, prev_entry_bytes_clear); + gf_bs_write_u32(sai_bs, prev_entry_bytes_crypt); + sai_size+=6; + + prev_entry_bytes_clear = cstr->nalu_size_length + clear_bytes; + prev_entry_bytes_crypt = nalu_size - clear_bytes; + } + + //check bytes of clear is not larger than 16bits + while (prev_entry_bytes_clear > 0xFFFF) { + //store current + if (!nb_subsamples) gf_bs_write_u16(sai_bs, 0); + nb_subsamples++; + gf_bs_write_u16(sai_bs, 0xFFFF); + gf_bs_write_u32(sai_bs, 0); + sai_size+=6; + + prev_entry_bytes_clear -= 0xFFFF; + } + + + nb_ranges--; + if (!nb_ranges) break; + + range_idx++; + if (range_idx >= AV1_MAX_TILE_ROWS * AV1_MAX_TILE_COLS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] More ranges than tiles allowed spec, bitstream error ?\n")); + return GF_BAD_PARAM; + } + switch (cstr->cenc_codec) { + case CENC_AV1: + clear_bytes = ranges[range_idx].clear; + nalu_size = clear_bytes + ranges[range_idx].encrypted; + break; + case CENC_VPX: + if (nb_ranges > 1) { + clear_bytes = ranges[range_idx].clear; + nalu_size = clear_bytes + ranges[range_idx].encrypted; + } else { /*last*/ + nalu_size = clear_bytes = ranges[range_idx].clear; + assert(ranges[range_idx].encrypted == 0); + } + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Unexpected subrange for sample format, only allowed for VPX and AV1\n")); + return GF_BAD_PARAM; + } + } + } else if (cstr->ctr_mode) { + gf_bs_skip_bytes(ctx->bs_r, pck_size); + e = gf_crypt_encrypt(cstr->crypt, output, pck_size); + } else { + u32 clear_trailing; + + clear_trailing = pck_size % 16; + + //cbcs scheme with constant IV, reinit at each sample, + if (!cstr->tci->IV_size) + gf_crypt_set_IV(cstr->crypt, cstr->IV, 16); + + if (pck_size >= 16) { + gf_crypt_encrypt(cstr->crypt, output, pck_size - clear_trailing); + } + gf_bs_skip_bytes(ctx->bs_r, pck_size); + } + + if (e) { + gf_filter_pck_discard(dst_pck); + return e; + } + } + + if (prev_entry_bytes_clear || prev_entry_bytes_crypt) { + if (!nb_subsamples) gf_bs_write_u16(sai_bs, 0); + nb_subsamples++; + gf_bs_write_u16(sai_bs, prev_entry_bytes_clear); + gf_bs_write_u32(sai_bs, prev_entry_bytes_crypt); + sai_size+=6; + } + if (cstr->ctr_mode) + cenc_resync_IV(cstr->crypt, cstr->IV, cstr->tci->IV_size); + + if (sai_size) { + u8 *sai=NULL; + sai_size = (u32) gf_bs_get_position(sai_bs); + gf_bs_seek(sai_bs, cstr->tci->IV_size); + gf_bs_write_u16(sai_bs, nb_subsamples); + gf_bs_seek(sai_bs, sai_size); + sai_size = 0; + gf_bs_get_content(sai_bs, &sai, &sai_size); + + gf_filter_pck_set_property(dst_pck, GF_PROP_PCK_CENC_SAI, &PROP_DATA_NO_COPY(sai, sai_size) ); + } + gf_bs_del(sai_bs); + + gf_filter_pck_send(dst_pck); + return GF_OK; +} + +static GF_Err cenc_process(GF_CENCEncCtx *ctx, GF_CENCStream *cstr, GF_FilterPacket *pck) +{ + Bool is_encrypted = GF_TRUE; + const u8 *data; + GF_Err e; + Bool all_rap=GF_FALSE; + u32 pck_size; + Bool key_changed = GF_FALSE; + Bool force_clear = GF_FALSE; + u8 sap = gf_filter_pck_get_sap(pck); + + data = gf_filter_pck_get_data(pck, &pck_size); + if (!data) { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[ISMACrypt] No data associated with packet\n" )); + return GF_OK; + } + + if (!ctx->bs_w) ctx->bs_w = gf_bs_new((char *) &sap, 1, GF_BITSTREAM_WRITE); + + switch (cstr->tci->sel_enc_type) { + case GF_CRYPT_SELENC_RAP: + if (!sap && !all_rap) { + is_encrypted = GF_FALSE; + } + break; + case GF_CRYPT_SELENC_NON_RAP: + if (sap || all_rap) { + is_encrypted = GF_FALSE; + } + break; + case GF_CRYPT_SELENC_CLEAR: + is_encrypted = GF_FALSE; + break; + case GF_CRYPT_SELENC_CLEAR_FORCED: + is_encrypted = GF_FALSE; + force_clear = GF_TRUE; + if (cstr->tci->sel_enc_range && (cstr->nb_pck+1 >= cstr->tci->sel_enc_range)) { + cstr->tci->sel_enc_type = GF_CRYPT_SELENC_NONE; + } + break; + default: + break; + } + + if (!is_encrypted) { + u8 *sai=NULL; + u32 i, sai_size = 0; + Bool signal_sai = GF_FALSE; + GF_FilterPacket *dst_pck; + dst_pck = gf_filter_pck_new_ref(cstr->opid, NULL, 0, pck); + gf_filter_pck_merge_properties(pck, dst_pck); + + if (force_clear && !cstr->tci->force_clear_stsd_idx) + signal_sai = GF_TRUE; + //format NULL bitstream only for forced clear mode + if (cstr->use_subsamples && signal_sai) { + GF_BitStream *bs; + u32 subsample_count = 1; + u32 olen = pck_size; + while (olen>0xFFFF) { + olen -= 0xFFFF; + subsample_count ++; + } + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + if (force_clear) { + bin128 NULL_IV; + memset(NULL_IV, 0, 16); + memcpy(NULL_IV, (char *) &cstr->nb_pck, 4); + gf_bs_write_data(bs, NULL_IV, cstr->tci->IV_size); + } + gf_bs_write_u16(bs, subsample_count); + olen = pck_size; + for (i = 0; i < subsample_count; i++) { + if (olen<0xFFFF) { + gf_bs_write_u16(bs, olen); + } else { + gf_bs_write_u16(bs, 0xFFFF); + olen -= 0xFFFF; + } + gf_bs_write_u32(bs, 0); + } + gf_bs_get_content(bs, &sai, &sai_size); + gf_bs_del(bs); + } + if (sai) + gf_filter_pck_set_property(dst_pck, GF_PROP_PCK_CENC_SAI, &PROP_DATA_NO_COPY(sai, sai_size) ); + + gf_filter_pck_set_crypt_flags(dst_pck, signal_sai ? GF_FILTER_PCK_CRYPT : 0); + gf_filter_pck_send(dst_pck); + return GF_OK; + } + + /*generate initialization vector for the first sample in track ... */ + if (!cstr->cenc_init) { + memset(cstr->IV, 0, sizeof(char)*16); + if (cstr->tci->IV_size == 8) { + memcpy(cstr->IV, cstr->tci->first_IV, sizeof(char)*8); + memset(cstr->IV+8, 0, sizeof(char)*8); + } + else if (cstr->tci->IV_size == 16) { + memcpy(cstr->IV, cstr->tci->first_IV, sizeof(char)*16); + } + else if (!cstr->tci->IV_size) { + if (cstr->tci->constant_IV_size == 8) { + memcpy(cstr->IV, cstr->tci->constant_IV, sizeof(char)*8); + memset(cstr->IV+8, 0, sizeof(char)*8); + } + else if (cstr->tci->constant_IV_size == 16) { + memcpy(cstr->IV, cstr->tci->constant_IV, sizeof(char)*16); + } else { + return GF_NOT_SUPPORTED; + } + } else { + return GF_NOT_SUPPORTED; + } + + e = gf_crypt_init(cstr->crypt, cstr->key, cstr->IV); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Cannot initialize AES-128 %s (%s)\n", cstr->ctr_mode ? "CTR" : "CBC", gf_error_to_string(e)) ); + return GF_IO_ERR; + } + cstr->cenc_init = GF_TRUE; + } else { + if (cstr->tci->keyRoll) { + u32 new_idx = (cstr->nb_pck_encrypted / cstr->tci->keyRoll) % cstr->tci->KID_count; + if (cstr->kidx != new_idx) { + cstr->kidx = new_idx; + memcpy(cstr->key, cstr->tci->keys[cstr->kidx], 16); + key_changed = GF_TRUE; + e = gf_crypt_set_key(cstr->crypt, cstr->key); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Cannot set key AES-128 %s (%s)\n", cstr->ctr_mode ? "CTR" : "CBC", gf_error_to_string(e)) ); + return e; + } + } + } + } + + if (key_changed) { + gf_filter_pid_set_property(cstr->opid, GF_PROP_PID_KID, &PROP_DATA( cstr->tci->KIDs[cstr->kidx], sizeof(bin128) ) ); + //TODO add support for multiple patterns and keys ? + //TODO add support for multiple IV size in key roll ? + } + + e = cenc_encrypt_packet(ctx, cstr, pck); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENC] Error encrypting packet %d in PID %s: %s\n", cstr->nb_pck, gf_filter_pid_get_name(cstr->ipid), gf_error_to_string(e)) ); + return e; + } + + cstr->nb_pck_encrypted++; + return GF_OK; +} + +static GF_Err cenc_enc_process(GF_Filter *filter) +{ + GF_CENCEncCtx *ctx = (GF_CENCEncCtx *)gf_filter_get_udta(filter); + u32 i, nb_eos, count = gf_list_count(ctx->streams); + + nb_eos = 0; + for (i=0; istreams, i); + GF_FilterPacket *pck = gf_filter_pid_get_packet(cstr->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(cstr->ipid)) { + gf_filter_pid_set_eos(cstr->opid); + nb_eos++; + } + continue; + } + + if (cstr->passthrough) { + gf_filter_pck_forward(pck, cstr->opid); + } + else if (cstr->isma_oma) { + e = isma_process(ctx, cstr, pck); + } else if (cstr->is_adobe) { + e = adobe_process(ctx, cstr, pck); + } else { + e = cenc_process(ctx, cstr, pck); + } + gf_filter_pid_drop_packet(cstr->ipid); + cstr->nb_pck++; + + if (e) return e; + } + if (nb_eos==count) return GF_EOS; + + return GF_OK; +} + +static GF_Err cenc_enc_initialize(GF_Filter *filter) +{ + GF_CENCEncCtx *ctx = (GF_CENCEncCtx *)gf_filter_get_udta(filter); + + if (ctx->cfile) { + GF_Err e; + ctx->cinfo = gf_crypt_info_load(ctx->cfile, &e); + if (!ctx->cinfo) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[CENCCrypt] Cannot load config file %s\n", ctx->cfile )); + return GF_BAD_PARAM; + } + } + + ctx->streams = gf_list_new(); + return GF_OK; +} + +static void cenc_enc_finalize(GF_Filter *filter) +{ + GF_CENCEncCtx *ctx = (GF_CENCEncCtx *)gf_filter_get_udta(filter); + if (ctx->cinfo) gf_crypt_info_del(ctx->cinfo); + while (gf_list_count(ctx->streams)) { + GF_CENCStream *s = gf_list_pop_back(ctx->streams); + if (s->crypt) gf_crypt_close(s->crypt); + if (s->cinfo) gf_crypt_info_del(s->cinfo); +#ifndef GPAC_DISABLE_AV_PARSERS + if (s->av1.config) gf_odf_av1_cfg_del(s->av1.config); +#endif + gf_free(s); + } + gf_list_del(ctx->streams); + if (ctx->bs_w) gf_bs_del(ctx->bs_w); + if (ctx->bs_r) gf_bs_del(ctx->bs_r); +} + + +static const GF_FilterCapability CENCEncCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_ENCRYPTED), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_ENCRYPTED), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), +// CAP_UINT(GF_CAPS_OUTPUT_STATIC_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + +#define OFFS(_n) #_n, offsetof(GF_CENCEncCtx, _n) +static const GF_FilterArgs GF_CENCEncArgs[] = +{ + { OFFS(cfile), "crypt file location - see filter help", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(allc), "throw error if no DRM config file is found for a PID - see filter help", GF_PROP_STRING, NULL, NULL, 0}, + {0} +}; + +GF_FilterRegister CENCEncRegister = { + .name = "cecrypt", + GF_FS_SET_DESCRIPTION("CENC encryptor") + GF_FS_SET_HELP("The CENC encryptor supports CENC, ISMA and Adobe encryption. It uses a DRM config file for declaring keys.\n" + "The syntax is available at https://wiki.gpac.io/Common-Encryption\n" + "The DRM config file can be set per PID using the property `CryptInfo`, or set at the filter level using [-cfile]().\n" + "When the DRM config file is set per PID, the first `CrypTrack` in the DRM config file with the same ID is used, otherwise the first `CrypTrack` is used.\n" + "If no DRM config file is defined for a given PID, this PID will not be encrypted, or an error will be thrown if [-allc]() is specified.\n" + ) + .private_size = sizeof(GF_CENCEncCtx), + .max_extra_pids=-1, + //encryptor shall be explicitly loaded + .flags = GF_FS_REG_EXPLICIT_ONLY, + .args = GF_CENCEncArgs, + SETCAPS(CENCEncCaps), + .configure_pid = cenc_enc_configure_pid, + .initialize = cenc_enc_initialize, + .finalize = cenc_enc_finalize, + .process = cenc_enc_process + +}; + +#endif /*GPAC_DISABLE_CRYPTO*/ + +const GF_FilterRegister *cenc_encrypt_register(GF_FilterSession *session) +{ +#ifndef GPAC_DISABLE_CRYPTO + return &CENCEncRegister; +#else + return NULL; +#endif +} diff --git a/src/filters/ff_avf.c b/src/filters/ff_avf.c new file mode 100644 index 0000000..27ff715 --- /dev/null +++ b/src/filters/ff_avf.c @@ -0,0 +1,1032 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom Paris 2019 + * All rights reserved + * + * This file is part of GPAC / ffmpeg avfilter filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include + +#ifdef GPAC_HAS_FFMPEG + +#include "ff_common.h" +#include +#include +#include +#include + +#if (LIBAVFILTER_VERSION_MAJOR < 7) +#undef GPAC_HAS_FFMPEG +#endif + +#endif + +#ifdef GPAC_HAS_FFMPEG + +typedef struct +{ + AVFilterContext *io_filter_ctx; + GF_FilterPid *io_pid; + u32 timescale, pfmt, width, height, sr, nb_ch, bps; + u64 ch_layout; + GF_Fraction sar; + u32 stride, stride_uv, nb_planes; + //output only + Bool is_video; + u32 gf_pfmt, out_size, uv_height, uv_width, tb_num; +} GF_FFAVPid; + + +typedef struct +{ + //options + u32 pfmt, afmt, sr, ch; + Bool dump; + + //internal + + GF_List *ipids; + GF_List *opids; + + //decode options + AVDictionary *options; + + AVFilterGraph *filter_graph; + char *filter_desc; + + GF_FilterCapability filter_caps[7]; + //0: not loaded, 1: graph config requested but graph not loaded, 2: graph loaded + u32 configure_state; + u32 nb_v_out, nb_a_out; + + AVFilterInOut *outputs; + AVFrame *frame; + + //0: no flush, 1: graph flush (push EOS in input), 2: wait for EOS in output + u32 flush_state; + GF_Err in_error; +} GF_FFAVFilterCtx; + + +static void ffavf_reset_graph(GF_FFAVFilterCtx *ctx) +{ + if (ctx->outputs) { + avfilter_inout_free(&ctx->outputs); + ctx->outputs = NULL; + } + if (ctx->filter_graph) { + avfilter_graph_free(&ctx->filter_graph); + ctx->filter_graph = NULL; + } +} + + +static GF_Err ffavf_setup_input(GF_FFAVFilterCtx *ctx, GF_FFAVPid *avpid) +{ + int ret; + char args[1024]; + const AVFilter *avf = NULL; + const char *pid_name = gf_filter_pid_get_name(avpid->io_pid); + + if (avpid->width) { + avf = avfilter_get_by_name("buffer"); + snprintf(args, sizeof(args), + "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", + avpid->width, avpid->height, avpid->pfmt, 1, avpid->timescale, avpid->sar.num, avpid->sar.den); + } else { + avf = avfilter_get_by_name("abuffer"); + snprintf(args, sizeof(args), + "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x"LLU, + 1, avpid->timescale, avpid->sr, av_get_sample_fmt_name(avpid->pfmt), avpid->ch_layout); + } + avpid->io_filter_ctx = NULL; + ret = avfilter_graph_create_filter(&avpid->io_filter_ctx, avf, pid_name, args, NULL, ctx->filter_graph); + if (ret<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFAVF] Fail to create filter graph: %s\n", av_err2str(ret) )); + return GF_BAD_PARAM; + } + return GF_OK; +} + +static GF_Err ffavf_setup_outputs(GF_Filter *filter, GF_FFAVFilterCtx *ctx) +{ + AVFilterInOut *io; + u32 i, nb_outputs; + int ret; + + //outputs are configured + if (ctx->outputs) return GF_OK; + + //allocate output pids + ctx->outputs = avfilter_inout_alloc(); + io = ctx->outputs; + nb_outputs = ctx->nb_v_out + ctx->nb_a_out; + for (i=0; inb_v_out ? GF_TRUE : GF_FALSE; + + for (k=0; kopids); k++) { + opid = gf_list_get(ctx->opids, k); + if (opid->is_video && is_video) break; + if (!opid->is_video && !is_video) break; + opid = NULL; + } + if (!opid) { + GF_SAFEALLOC(opid, GF_FFAVPid); + if (!opid) continue; + + gf_list_add(ctx->opids, opid); + opid->io_pid = gf_filter_pid_new(filter); + opid->is_video = inb_v_out ? GF_TRUE : GF_FALSE; + //remove properties since we may have change format + if (is_video) { + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_VISUAL)); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_SAMPLE_RATE, NULL); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_CHANNEL_LAYOUT, NULL); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_NUM_CHANNELS, NULL); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_AUDIO_BPS, NULL); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_AUDIO_FORMAT, NULL); + } else { + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_AUDIO)); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_WIDTH, NULL); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_HEIGHT, NULL); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_PIXFMT, NULL); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_STRIDE, NULL); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_STRIDE_UV, NULL); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_SAR, NULL); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_COLR_MX, NULL); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_COLR_RANGE, NULL); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_COLR_SPACE, NULL); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_COLR_TRANSFER, NULL); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_COLR_PRIMARIES, NULL); + } + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW)); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_FILE_EXT, NULL); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_MIME, NULL); + } + opid->io_filter_ctx = NULL; + + if (opid->is_video) { + sprintf(szName, "vout%d", i+1); + avf = avfilter_get_by_name("buffersink"); + } else { + sprintf(szName, "aout%d", (i-ctx->nb_v_out) + 1); + avf = avfilter_get_by_name("abuffersink"); + } + + if (nb_outputs==1) + sprintf(szName, "out"); + + ret = avfilter_graph_create_filter(&opid->io_filter_ctx, avf, szName, NULL, NULL, ctx->filter_graph); + if (ret<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFAVF] Fail to create %s filter: %s\n", avf->name, av_err2str(ret) )); + return GF_BAD_PARAM; + } + if (opid->is_video) { + if (ctx->pfmt) { + enum AVPixelFormat pfmt = ffmpeg_pixfmt_from_gpac(ctx->pfmt); + ret = av_opt_set_bin(opid->io_filter_ctx, "pix_fmts", (uint8_t*)&pfmt, sizeof(pfmt), AV_OPT_SEARCH_CHILDREN); + if (ret < 0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[FFAVF] Fail to set %s pixel format: %s\n", avf->name, av_err2str(ret) )); + } + } + } else { + if (ctx->afmt) { + enum AVSampleFormat afmt = ffmpeg_audio_fmt_from_gpac(ctx->afmt); + ret = av_opt_set_bin(opid->io_filter_ctx, "sample_fmts", (uint8_t*)&afmt, sizeof(afmt), AV_OPT_SEARCH_CHILDREN); + if (ret < 0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[FFAVF] Fail to set %s audio format: %s\n", avf->name, av_err2str(ret) )); + } + } + if (ctx->sr) { + ret = av_opt_set_bin(opid->io_filter_ctx, "sample_rates", (uint8_t*)&ctx->sr, sizeof(ctx->sr), AV_OPT_SEARCH_CHILDREN); + if (ret < 0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[FFAVF] Fail to set %s audio sample rate: %s\n", avf->name, av_err2str(ret) )); + } + } + if (ctx->ch) { + ret = av_opt_set_bin(opid->io_filter_ctx, "channels", (uint8_t*)&ctx->ch, sizeof(ctx->ch), AV_OPT_SEARCH_CHILDREN); + if (ret < 0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[FFAVF] Fail to set %s audio sample rate: %s\n", avf->name, av_err2str(ret) )); + } + } + + } + io->name = av_strdup(szName); + io->filter_ctx = opid->io_filter_ctx; + io->pad_idx = 0; + io->next = NULL; + if (i+1==nb_outputs) break; + io->next = avfilter_inout_alloc(); + io = io->next; + } + return GF_OK; +} + + +static GF_Err ffavf_reconfigure_graph(GF_Filter *filter, GF_FFAVFilterCtx *ctx) +{ + u32 i, count; + GF_Err e = GF_OK; + ctx->flush_state = 0; + ffavf_reset_graph(ctx); + ctx->filter_graph = avfilter_graph_alloc(); + + count = gf_list_count(ctx->ipids); + for (i=0; iipids, i); + e = ffavf_setup_input(ctx, ipid); + if (e) break; + } + ctx->configure_state = 2; + if (!e) + e = ffavf_setup_outputs(filter, ctx); + + if (e) ctx->in_error = e; + return e; +} + +static GF_Err ffavf_initialize(GF_Filter *filter) +{ + u32 nb_v_in=0, nb_a_in=0; + u32 i; + AVFilterInOut *inputs; + AVFilterInOut *outputs; + AVFilterInOut *io; + int ret; + Bool dyn_inputs = GF_FALSE; + GF_FFAVFilterCtx *ctx = (GF_FFAVFilterCtx *) gf_filter_get_udta(filter); + if (!ctx->filter_desc) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFAVF] Missing filter graph description, cannot load\n")); + return GF_BAD_PARAM; + } + + ctx->ipids = gf_list_new(); + ctx->opids = gf_list_new(); + ctx->frame = av_frame_alloc(); + + + ctx->filter_graph = avfilter_graph_alloc(); + ret = avfilter_graph_parse2(ctx->filter_graph, ctx->filter_desc, &inputs, &outputs); + if (ret<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFAVF] Fail to parse filter description: %s\nFilter description was %s\n", av_err2str(ret), ctx->filter_desc)); + return GF_BAD_PARAM; + } + + io = inputs; + while (io) { + if (io->filter_ctx->filter->flags & AVFILTER_FLAG_DYNAMIC_INPUTS) + dyn_inputs = GF_TRUE; + for (i=0; ifilter_ctx->nb_inputs; i++) { + enum AVMediaType mt = avfilter_pad_get_type(io->filter_ctx->input_pads, i); + u32 streamtype = ffmpeg_stream_type_to_gpac(mt); + + switch (streamtype) { + case GF_STREAM_VISUAL: nb_v_in++; break; + case GF_STREAM_AUDIO: nb_a_in++; break; + } + } + io = io->next; + } + + ctx->nb_v_out = ctx->nb_a_out = 0; + io = outputs; + while (io) { + for (i=0; ifilter_ctx->nb_outputs; i++) { + enum AVMediaType mt = avfilter_pad_get_type(io->filter_ctx->output_pads, i); + u32 streamtype = ffmpeg_stream_type_to_gpac(mt); + switch (streamtype) { + case GF_STREAM_VISUAL: ctx->nb_v_out++; break; + case GF_STREAM_AUDIO: ctx->nb_a_out++; break; + } + } + io = io->next; + } + avfilter_inout_free(&inputs); + avfilter_inout_free(&outputs); + avfilter_graph_free(&ctx->filter_graph); + ctx->filter_graph = avfilter_graph_alloc(); + + if (dyn_inputs) { + gf_filter_set_max_extra_input_pids(filter, -1); + } else if (nb_v_in + nb_a_in > 1) + gf_filter_set_max_extra_input_pids(filter, nb_v_in + nb_a_in - 1); + + /*update filter caps*/ + memset(ctx->filter_caps, 0, sizeof(GF_FilterCapability) * 7); + ctx->filter_caps[0].flags = GF_CAPS_INPUT_OUTPUT; + ctx->filter_caps[0].code = GF_PROP_PID_CODECID; + ctx->filter_caps[0].val = PROP_UINT(GF_CODECID_RAW); + i=1; + if (nb_v_in) { + ctx->filter_caps[i].flags = ctx->nb_v_out ? GF_CAPS_INPUT_OUTPUT : GF_CAPS_INPUT; + ctx->filter_caps[i].code = GF_PROP_PID_STREAM_TYPE; + ctx->filter_caps[i].val = PROP_UINT(GF_STREAM_VISUAL); + i++; + } + if (nb_a_in) { + ctx->filter_caps[i].flags = ctx->nb_a_out ? GF_CAPS_INPUT_OUTPUT : GF_CAPS_INPUT; + ctx->filter_caps[i].code = GF_PROP_PID_STREAM_TYPE; + ctx->filter_caps[i].val = PROP_UINT(GF_STREAM_AUDIO); + i++; + } + if (ctx->nb_v_out && !nb_v_in) { + ctx->filter_caps[i].flags = GF_CAPS_OUTPUT; + ctx->filter_caps[i].code = GF_PROP_PID_STREAM_TYPE; + ctx->filter_caps[i].val = PROP_UINT(GF_STREAM_VISUAL); + i++; + } + if (ctx->nb_a_out && !nb_a_in) { + ctx->filter_caps[i].flags = GF_CAPS_OUTPUT; + ctx->filter_caps[i].code = GF_PROP_PID_STREAM_TYPE; + ctx->filter_caps[i].val = PROP_UINT(GF_STREAM_AUDIO); + i++; + } + gf_filter_override_caps(filter, ctx->filter_caps, i); + + return GF_OK; +} + +static void ffavf_dump_graph(GF_FFAVFilterCtx *ctx, const char *opt) +{ + char *graphdump = avfilter_graph_dump(ctx->filter_graph, opt); + + if (graphdump) { +#ifndef GPAC_DISABLE_LOG + if (gf_log_tool_level_on(GF_LOG_MEDIA, GF_LOG_INFO)) { + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[FFAVF] Graph dump:\n%s\n\n", graphdump )); + } else +#endif + fprintf(stderr, "[FFAVF] Graph dump:\n%s\n\n", graphdump); + + av_free(graphdump); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFAVF] Failed to dump graph %s\n", ctx->filter_desc)); + } + +} +static GF_Err ffavf_setup_filter(GF_Filter *filter, GF_FFAVFilterCtx *ctx) +{ + int ret; + AVFilterInOut *io, *inputs; + u32 i, count = gf_list_count(ctx->ipids); + + //wait until we have one packet on each input + for (i=0; iipids, i); + GF_FilterPacket *pck = gf_filter_pid_get_packet(pid_ctx->io_pid); + if (!pck) return GF_OK; + } + ctx->configure_state = 2; + + /*create inputs*/ + inputs = avfilter_inout_alloc(); + io = inputs; + for (i=0; iipids, i); + + if (count==1) + io->name = av_strdup("in"); + else { + const GF_PropertyValue *p = gf_filter_pid_get_property_str(pid_ctx->io_pid, "ffid"); + if (p && p->value.string) { + io->name = av_strdup(p->value.string); + } else { + if (i) + sprintf(szName, "in%d", i+1); + else + sprintf(szName, "in"); + io->name = av_strdup(szName); + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[FFAVF] Multiple input for graph but no names assigned to input PIDs (missing ffid property), using %s as default name. Filter linking might fail\n", szName)); + } + } + io->filter_ctx = pid_ctx->io_filter_ctx; + + if (i+1==count) break; + io->next = avfilter_inout_alloc(); + io = io->next; + } + //our outputs describe the filter graph outputs and our inputs desscribe the filter graph input + //however avfilter_graph_parse_ptr expects: + // inputs: the inputs of the next filter graph to connect to, hence our outputs + // outputs: the outputs of the previous filter graph to connect to, hence our inputs + ret = avfilter_graph_parse_ptr(ctx->filter_graph, ctx->filter_desc, &ctx->outputs, &inputs, NULL); + avfilter_inout_free(&inputs); + if (ret < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFAVF] Fail to parse filter description: %s\nFilter description was %s\n", av_err2str(ret), ctx->filter_desc)); + return ctx->in_error = GF_BAD_PARAM; + } + ret = avfilter_graph_config(ctx->filter_graph, NULL); + if (ret < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFAVF] Fail to validate filter graph: %s\n", av_err2str(ret) )); + return ctx->in_error = GF_BAD_PARAM; + } + + if (ctx->dump) + ffavf_dump_graph(ctx, NULL); + return GF_OK; +} + +static GF_Err ffavf_process(GF_Filter *filter) +{ + int ret; + GF_Err e = GF_OK; + u32 i, count, nb_eos; + GF_FFAVFilterCtx *ctx = (GF_FFAVFilterCtx *) gf_filter_get_udta(filter); + + if (ctx->in_error) + return ctx->in_error; + + //graph needs to be loaded + if (ctx->configure_state==1) { + return ffavf_setup_filter(filter, ctx); + } + + //push input + nb_eos = 0; + count = gf_list_count(ctx->ipids); + if (ctx->flush_state==2) + count = 0; + + for (i=0; iipids, i); + GF_FilterPacket *pck; + pck = gf_filter_pid_get_packet(ipid->io_pid); + + //config changed at this packet, start flushing the graph + if (ctx->flush_state==1) { + ret = av_buffersrc_add_frame_flags(ipid->io_filter_ctx, NULL, 0); + if (ret<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFAVF] Fail to flush filter graph: %s\n", av_err2str(ret) )); + } + continue; + } + + if (!pck) { + if (gf_filter_pid_is_eos(ipid->io_pid)) { + ret = av_buffersrc_add_frame_flags(ipid->io_filter_ctx, NULL, 0); + if (ret<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFAVF] Fail to signal EOS: %s\n", av_err2str(ret) )); + e = GF_SERVICE_ERROR; + } + nb_eos++; + } + continue; + } + data = gf_filter_pck_get_data(pck, &data_size); + if (data) { + ctx->frame->data[0] = (uint8_t *) data; + if (ipid->width) { + ctx->frame->linesize[0] = ipid->stride; + if (ipid->stride_uv) { + ctx->frame->data[1] = ctx->frame->data[0] + ipid->height * ipid->stride; + ctx->frame->linesize[1] = ipid->stride_uv; + if (ipid->nb_planes==3) { + ctx->frame->data[2] = ctx->frame->data[1] + ipid->uv_height * ipid->stride_uv; + ctx->frame->linesize[2] = ipid->stride_uv; + } + } + } else { + ctx->frame->linesize[0] = data_size; + } + frame_ok = GF_TRUE; + } else { + u32 j; + GF_FilterFrameInterface *fifce = gf_filter_pck_get_frame_interface(pck); + if (fifce->get_plane) { + frame_ok = GF_TRUE; + for (j=0; jnb_planes; j++) { + e = fifce->get_plane(fifce, j, (const u8 **) &ctx->frame->data[j], &ctx->frame->linesize[j]); + if (e) { + frame_ok = GF_FALSE; + break; + } + } + } + } + + if (frame_ok) { + u64 cts = gf_filter_pck_get_cts(pck); + ctx->frame->pts = cts; + if (ipid->width) { + ctx->frame->width = ipid->width; + ctx->frame->height = ipid->height; + ctx->frame->format = ipid->pfmt; + ctx->frame->sample_aspect_ratio.num = ipid->sar.num; + ctx->frame->sample_aspect_ratio.den = ipid->sar.den; + } else { + ctx->frame->channel_layout = ipid->ch_layout; + ctx->frame->channels = ipid->nb_ch; + ctx->frame->sample_rate = ipid->sr; + ctx->frame->format = ipid->pfmt; + ctx->frame->nb_samples = data_size / ipid->nb_ch / ipid->bps; + } + /* push the decoded frame into the filtergraph */ + ret = av_buffersrc_add_frame_flags(ipid->io_filter_ctx, ctx->frame, 0); + if (ret < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFAVF] Fail to push frame to filtergaph: %s\n", av_err2str(ret) )); + e = GF_SERVICE_ERROR; + break; + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFAVF] Fail to fetch data from frame\n")); + e = GF_SERVICE_ERROR; + break; + } + gf_filter_pid_drop_packet(ipid->io_pid); + } + if (count>nb_eos) nb_eos=0; + + if (ctx->flush_state==1) { + ctx->flush_state = 2; + nb_eos = 0; + } + + //pull output + count = gf_list_count(ctx->opids); + for (i=0; iopids, i); + if (!nb_eos && gf_filter_pid_would_block(opid->io_pid)) { + continue; + } + + AVFrame *frame = av_frame_alloc(); + + ret = av_buffersink_get_frame(opid->io_filter_ctx, frame); + if (ret < 0) { + if (ret == AVERROR_EOF) { + if (ctx->flush_state==2) { + nb_eos++; + } else if (nb_eos) { + gf_filter_pid_set_eos(opid->io_pid); + } + } else if (ret != AVERROR(EAGAIN)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFAVF] Fail to pull frame from filtergaph: %s\n", av_err2str(ret) )); + e = GF_SERVICE_ERROR; + } + av_frame_free(&frame); + break; + } + if (opid->is_video) { + u8 *buffer; + u32 j; + GF_FilterPacket *pck; + Bool update_props=GF_TRUE; + if (frame->width!=opid->width) {} + else if (frame->height!=opid->height) {} + else if (frame->format != opid->pfmt) {} + else { + update_props = GF_FALSE; + } + if (update_props) { + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_WIDTH, &PROP_UINT(frame->width)); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_HEIGHT, &PROP_UINT(frame->height)); + opid->gf_pfmt = ffmpeg_pixfmt_to_gpac(frame->format); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_PIXFMT, &PROP_UINT(opid->gf_pfmt)); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_STRIDE, &PROP_UINT(frame->linesize[0])); + if (frame->linesize[1]) + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_STRIDE_UV, &PROP_UINT(frame->linesize[1])); + else + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_STRIDE_UV, NULL); + + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_TIMESCALE, &PROP_UINT(opid->io_filter_ctx->inputs[0]->time_base.den) ); + + opid->width = frame->width; + opid->height = frame->height; + opid->pfmt = frame->format; + opid->tb_num = opid->io_filter_ctx->inputs[0]->time_base.num; + opid->stride = 0; + opid->stride_uv = 0; + gf_pixel_get_size_info(opid->gf_pfmt, opid->width, opid->height, &opid->out_size, &opid->stride, &opid->stride_uv, NULL, &opid->uv_height); + if ((opid->gf_pfmt==GF_PIXEL_YUV444) || (opid->gf_pfmt==GF_PIXEL_YUV444_10)) { + opid->uv_width = opid->width; + } else if (opid->uv_height) { + opid->uv_width = opid->width/2; + } else { + opid->uv_width = 0; + } + if (ctx->nb_a_out+ctx->nb_v_out>1) { + gf_filter_pid_set_property_str(opid->io_pid, "ffid", &PROP_STRING(opid->io_filter_ctx->name)); + } + } + pck = gf_filter_pck_new_alloc(opid->io_pid, opid->out_size, &buffer); + if (!pck) return GF_OUT_OF_MEM; + + for (j=0; jheight; j++) { + memcpy(buffer + j*opid->stride, frame->data[0] + j*frame->linesize[0], opid->stride); + } + if (frame->linesize[1]) { + buffer += opid->height*opid->stride; + for (j=0; juv_height; j++) { + memcpy(buffer + j*opid->stride_uv, frame->data[1] + j*frame->linesize[1], opid->uv_width); + } + } + if (frame->linesize[2]) { + buffer += opid->uv_height*opid->stride_uv; + for (j=0; juv_height; j++) { + memcpy(buffer + j*opid->stride_uv, frame->data[2] + j*frame->linesize[2], opid->uv_width); + } + } + if (frame->interlaced_frame) + gf_filter_pck_set_interlaced(pck, frame->top_field_first ? 1 : 2); + + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + gf_filter_pck_set_cts(pck, frame->pts * opid->tb_num); + gf_filter_pck_send(pck); + } else { + u8 *buffer; + u32 j, out_size; + GF_FilterPacket *pck; + Bool update_props=GF_TRUE; + if (frame->sample_rate!=opid->sr) {} + else if (frame->channel_layout!=opid->ch_layout) {} + else if (frame->channels != opid->nb_ch) {} + else if (frame->format != opid->pfmt) {} + else { + update_props = GF_FALSE; + } + if (update_props) { + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(frame->sample_rate)); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_CHANNEL_LAYOUT, &PROP_LONGUINT(frame->channel_layout)); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(frame->channels)); + opid->gf_pfmt = ffmpeg_audio_fmt_to_gpac(frame->format); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(opid->gf_pfmt)); + gf_filter_pid_set_property(opid->io_pid, GF_PROP_PID_TIMESCALE, &PROP_UINT(opid->io_filter_ctx->inputs[0]->time_base.den) ); + + opid->sr = frame->sample_rate; + opid->ch_layout = frame->channel_layout; + opid->nb_ch = frame->channels; + opid->pfmt = frame->format; + opid->tb_num = opid->io_filter_ctx->inputs[0]->time_base.num; + opid->bps = gf_audio_fmt_bit_depth(opid->gf_pfmt) / 8; + if (ctx->nb_a_out+ctx->nb_v_out>1) { + gf_filter_pid_set_property_str(opid->io_pid, "ffid", &PROP_STRING(opid->io_filter_ctx->name)); + } + } + out_size = 0; + for (j=0; j<8; j++) { + if (!frame->linesize[j]) break; + out_size += frame->linesize[j]; + } + + pck = gf_filter_pck_new_alloc(opid->io_pid, out_size, &buffer); + if (!pck) return GF_OUT_OF_MEM; + + for (j=0; j<8; j++) { + if (!frame->linesize[j]) break; + memcpy(buffer, frame->data[0], frame->linesize[j]); + buffer += frame->linesize[j]; + } + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + gf_filter_pck_set_cts(pck, frame->pts * opid->tb_num); + gf_filter_pck_send(pck); + } + av_frame_free(&frame); + } + if (e) return e; + if (ctx->flush_state==2) { + if (nb_eosvalue.uint; + + pid_ctx = gf_filter_pid_get_udta(pid); + if (!pid_ctx) { + GF_SAFEALLOC(pid_ctx, GF_FFAVPid); + if (!pid_ctx) return GF_OUT_OF_MEM; + + pid_ctx->io_pid = pid; + gf_filter_pid_set_udta(pid, pid_ctx); + gf_list_add(ctx->ipids, pid_ctx); + } else { + check_recfg = GF_TRUE; + } + + timebase.num = 1; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (!p) return GF_BAD_PARAM; + timebase.den = p->value.uint; + + ctx->in_error = GF_OK; + + if (streamtype==GF_STREAM_VISUAL) { + u32 width, height, pix_fmt, gf_pfmt; + GF_Fraction sar={1,1}; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + if (!p) return GF_OK; //not ready yet + width = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + if (!p) return GF_OK; //not ready yet + height = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); + if (!p) return GF_OK; //not ready yet + gf_pfmt = p->value.uint; + pix_fmt = ffmpeg_pixfmt_from_gpac(gf_pfmt); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); + if (p) sar = p->value.frac; + + pid_ctx->stride = pid_ctx->stride_uv = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE); + if (p) pid_ctx->stride = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE_UV); + if (p) pid_ctx->stride_uv = p->value.uint; + gf_pixel_get_size_info(gf_pfmt, width, height, NULL, &pid_ctx->stride, &pid_ctx->stride_uv, &pid_ctx->nb_planes, &pid_ctx->uv_height); + + if (check_recfg) { + check_recfg = GF_FALSE; + if (width!=pid_ctx->width) {} + else if (height!=pid_ctx->height) {} + else if (pix_fmt!=pid_ctx->pfmt) {} + else if (timebase.den!=pid_ctx->timescale) {} + else if (sar.den * pid_ctx->sar.num != sar.num * pid_ctx->sar.den) {} + else { + return GF_OK; + } + } + pid_ctx->width = width; + pid_ctx->height = height; + pid_ctx->pfmt = pix_fmt; + pid_ctx->timescale = timebase.den; + pid_ctx->sar = sar; + } else if (streamtype==GF_STREAM_AUDIO) { + u64 ch_layout=0; + u32 sr, afmt, nb_ch; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CHANNEL_LAYOUT); + if (p) ch_layout = p->value.longuint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS); + if (!p) return GF_OK; //not ready yet + nb_ch = p->value.uint; + if (!ch_layout) ch_layout = av_get_default_channel_layout(p->value.uint); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); + if (!p) return GF_OK; //not ready yet + sr = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT); + if (!p) return GF_OK; //not ready yet + afmt = ffmpeg_audio_fmt_from_gpac(p->value.uint); + pid_ctx->bps = gf_audio_fmt_bit_depth(p->value.uint) / 8; + + if (check_recfg) { + if (sr!=pid_ctx->sr) {} + else if (afmt!=pid_ctx->pfmt) {} + else if (nb_ch!=pid_ctx->nb_ch) {} + else if (ch_layout!=pid_ctx->ch_layout) {} + else if (timebase.den!=pid_ctx->timescale) {} + else { + return GF_OK; + } + } + pid_ctx->sr = sr; + pid_ctx->ch_layout = ch_layout; + pid_ctx->pfmt = afmt; + pid_ctx->nb_ch = nb_ch; + pid_ctx->timescale = timebase.den; + } else { + return GF_NOT_SUPPORTED; + } + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + + //we have not yet configured graph, no need to flush + if (ctx->configure_state==1) { + check_recfg = GF_FALSE; + } + + //pid config change, flush current graph and resetup + if (check_recfg) ctx->flush_state = 1; + //graph has already been loaded, we need to flush/resetup to add the pid + if (ctx->configure_state==2) ctx->flush_state = 1; + //in flush state, either because of this reconfig or a previous one + if (ctx->flush_state) + return GF_OK; + + //setup input connections + ctx->in_error = ffavf_setup_input(ctx, pid_ctx); + if (ctx->in_error) return ctx->in_error; + + //mark graph to be rebuild + ctx->configure_state = 1; + //setup output connections + ctx->in_error = ffavf_setup_outputs(filter, ctx); + return ctx->in_error; +} + + +static void ffavf_finalize(GF_Filter *filter) +{ + GF_FFAVFilterCtx *ctx = (GF_FFAVFilterCtx *) gf_filter_get_udta(filter); + + ffavf_reset_graph(ctx); + while (gf_list_count(ctx->ipids)) { + GF_FFAVPid *ipid = gf_list_pop_back(ctx->ipids); + //io_filter_ctx is destroyed while reseting the graph + gf_free(ipid); + } + gf_list_del(ctx->ipids); + while (gf_list_count(ctx->opids)) { + GF_FFAVPid *opid = gf_list_pop_back(ctx->opids); + //io_filter_ctx is destroyed while reseting the graph + gf_free(opid); + } + gf_list_del(ctx->opids); + if (ctx->filter_desc) gf_free(ctx->filter_desc); + if (ctx->frame) av_frame_free(&ctx->frame); + + if (ctx->options) av_dict_free(&ctx->options); + return; +} + +static GF_Err ffavf_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *arg_val) +{ + int ret; + char *arg_value; + + GF_FFAVFilterCtx *ctx = gf_filter_get_udta(filter); + + if (!strcmp(arg_name, "f")) { + if (ctx->filter_graph) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFAVF] Cannot update filter description while running, not supported\n")); + return GF_NOT_SUPPORTED; + } + if (ctx->filter_desc) gf_free(ctx->filter_desc); + ctx->filter_desc = gf_strdup(arg_val->value.string); + return GF_OK; + } + + arg_value = NULL; + if (arg_val->type == GF_PROP_STRING) { + arg_value = arg_val->value.string; + } + + if (!strcmp(arg_name, "dump")) { + ffavf_dump_graph(ctx, (arg_value && strlen(arg_value) ) ? arg_value : NULL); + //do not change the dump value + return GF_NOT_FOUND; + } + + if (!arg_value) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFAVF] Failed to set option %s:%s, unrecognized type %d\n", arg_name, arg_val, arg_val->type )); + return GF_NOT_SUPPORTED; + } + + if (ctx->filter_graph) { + char *arg = (char *) arg_name; + char szTargetName[101]; + char szCommandRes[1025]; + char *target = strchr(arg_name, gf_filter_get_sep(filter, GF_FS_SEP_FRAG)); + if (target) { + u32 len = (u32) (target - arg_name); + if (len>=100) len=100; + strncpy(szTargetName, arg_name, len); + szTargetName[100] = 0; + arg = target+1; + } else { + strcpy(szTargetName, "all"); + } + ret = avfilter_graph_send_command(ctx->filter_graph, szTargetName, arg, arg_value, szCommandRes, 1024, 0); + if (ret<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFAVF] Failed to execute command %s: %s\n", arg_name, av_err2str(ret) )); + } + } + ret = av_dict_set(&ctx->options, arg_name, arg_val->value.string, 0); + if (ret<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFAVF] Failed to set option %s:%s\n", arg_name, arg_val )); + } + return GF_OK; +} + + +static const GF_FilterCapability FFAVFilterCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), + {0}, + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +GF_FilterRegister FFAVFilterRegister = { + .name = "ffavf", + .version = LIBAVFORMAT_IDENT, + GF_FS_SET_DESCRIPTION("FFMPEG AVFilter") + GF_FS_SET_HELP("This filter provides libavfilter raw audio and video tools. See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details\n" + "\n" + "# Declaring a filter\n" + "The filter loads a filter or a filter chain description from the [-f]() option.\n" + "EX ffavf:f=showspectrum\n" + "\n" + "Unlike other FFMPEG bindings in GPAC, this filter does not parse other libavfilter options, you must specify them directly in the filter chain, and the [-f]() option will have to be escaped.\n" + "EX ffavf::f=showspectrum=size=320x320 or ffavf::f=showspectrum=size=320x320::pfmt=rgb\n" + "\n" + "The filter will automatically create `buffer` and `buffersink` AV filters for data exchange between GPAC and libavfilter.\n" + "The builtin options ( [-pfmt](), [-afmt]() ...) can be used to configure the `buffersink` filter to set the output format of the filter.\n" + "\n" + "# Naming of PIDs\n" + "For simple filter graphs with only one input and one output, the input PID is assigned the avfilter name `in` and the output PID is assigned the avfilter name `out`\n" + "\n" + "When a graph has several inputs, input PID names shall be assigned by the user using the `ffid` property, and mapping must be done in the filter.\n" + "EX src=video:#ffid=a src=logo:#ffid=b ffavf::f=[a][b]overlay=main_w-overlay_w-10:main_h-overlay_h-10\n" + "In this example:\n" + "- the video source is identified as `a`\n" + "- the logo source is identified as `b`\n" + "- the filter declaration maps `a` to its first input (in this case, main video) and `b` to its second input (in this case the overlay)\n" + "\n" + "When a graph has several outputs, output PIDs will be identified using the `ffid` property set to the output avfilter name.\n" + "EX src=source ffavf::f=split inspect:SID=#ffid=out0 vout#SID=out1\n" + "In this example:\n" + "- the spliter produces 2 video streams `out0` and `out1`\n" + "- the inspecter only process stream with ffid `out0`\n" + "- the video output only displays stream with ffid `out1`\n" + "\n" + "The name(s) of the final output of the avfilter graph cannot be configured in GPAC. You can however name intermediate output(s) in a complex filter chain as usual.\n" + "\n" + "# Filter graph commands\n" + "The filter handles option updates as commands passed to the AV filter graph. The syntax expected in the option name is:\n" + "- com_name=value: sends command `com_name` with value `value` to all filters\n" + "- name#com_name=value: sends command `com_name` with value `value` to filter named `name`\n" + "\n" + ) + .flags = GF_FS_REG_META | GF_FS_REG_EXPLICIT_ONLY, + .private_size = sizeof(GF_FFAVFilterCtx), + SETCAPS(FFAVFilterCaps), + .initialize = ffavf_initialize, + .finalize = ffavf_finalize, + .configure_pid = ffavf_configure_pid, + .process = ffavf_process, + .update_arg = ffavf_update_arg, +}; + +#define OFFS(_n) #_n, offsetof(GF_FFAVFilterCtx, _n) + +static const GF_FilterArgs FFAVFilterArgs[] = +{ + { "f", -1, "filter or filter chain description - see filter help", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_META}, + { OFFS(pfmt), "pixel format of output. If not set, let AVFilter decide", GF_PROP_PIXFMT, "none", NULL, 0}, + { OFFS(afmt), "audio format of output. If not set, let AVFilter decide", GF_PROP_PCMFMT, "none", NULL, 0}, + { OFFS(sr), "sample rate of output. If not set, let AVFilter decide", GF_PROP_UINT, "0", NULL, 0}, + { OFFS(ch), "number of channels of output. If not set, let AVFilter decide", GF_PROP_UINT, "0", NULL, 0}, + { OFFS(dump), "dump graph as log madia@info or stderr if not set", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE}, + { "*", -1, "any possible options defined for AVFilter and sub-classes. See `gpac -hx ffavf` and `gpac -hx ffavf:*`", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_META}, + {0} +}; + +const int FFAVF_STATIC_ARGS = (sizeof (FFAVFilterArgs) / sizeof (GF_FilterArgs)) - 1; + +const GF_FilterRegister *ffavf_register(GF_FilterSession *session) +{ + ffmpeg_build_register(session, &FFAVFilterRegister, FFAVFilterArgs, FFAVF_STATIC_ARGS, FF_REG_TYPE_AVF); + return &FFAVFilterRegister; +} + +#else +#include +const GF_FilterRegister *ffavf_register(GF_FilterSession *session) +{ + return NULL; +} +#endif diff --git a/src/filters/ff_common.c b/src/filters/ff_common.c new file mode 100644 index 0000000..b555a74 --- /dev/null +++ b/src/filters/ff_common.c @@ -0,0 +1,1256 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2018 + * All rights reserved + * + * This file is part of GPAC / common ffmpeg filters + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include + +#ifdef GPAC_HAS_FFMPEG + +#include "ff_common.h" + +#include + +#if !defined(__GNUC__) +# if defined(_WIN32_WCE) || defined (WIN32) +# pragma comment(lib, "avutil") +# pragma comment(lib, "avformat") +# pragma comment(lib, "avcodec") +# pragma comment(lib, "avdevice") +# pragma comment(lib, "swscale") +# pragma comment(lib, "avfilter") +# endif +#endif + + +static Bool ffmpeg_init = GF_FALSE; + +typedef struct +{ + GF_List *all_filters; + u32 nb_arg_skip; +} GF_FFRegistryExt; + +typedef struct +{ + u32 ff_pf; + u32 gpac_pf; +} GF_FF_PFREG; + +static const GF_FF_PFREG FF2GPAC_PixelFormats[] = +{ + {AV_PIX_FMT_YUV420P, GF_PIXEL_YUV}, + {AV_PIX_FMT_YUV420P10LE, GF_PIXEL_YUV_10}, + {AV_PIX_FMT_YUV422P, GF_PIXEL_YUV422}, + {AV_PIX_FMT_YUV422P10LE, GF_PIXEL_YUV422_10}, + {AV_PIX_FMT_YUV444P, GF_PIXEL_YUV444}, + {AV_PIX_FMT_YUV444P10LE, GF_PIXEL_YUV444_10}, + {AV_PIX_FMT_RGBA, GF_PIXEL_RGBA}, + {AV_PIX_FMT_RGB24, GF_PIXEL_RGB}, + {AV_PIX_FMT_BGR24, GF_PIXEL_BGR}, + {AV_PIX_FMT_UYVY422, GF_PIXEL_UYVY}, + {AV_PIX_FMT_YUYV422, GF_PIXEL_YUYV}, + //remap unsupported pix formats + {AV_PIX_FMT_UYVY422, GF_PIXEL_VYUY}, + {AV_PIX_FMT_YUYV422, GF_PIXEL_YVYU}, + + {AV_PIX_FMT_NV12, GF_PIXEL_NV12}, +#if LIBAVCODEC_VERSION_MAJOR >= 58 + {AV_PIX_FMT_P010LE, GF_PIXEL_NV12_10}, +#endif + {AV_PIX_FMT_NV21, GF_PIXEL_NV21}, + {AV_PIX_FMT_0RGB, GF_PIXEL_XRGB}, + {AV_PIX_FMT_RGB0, GF_PIXEL_RGBX}, + {AV_PIX_FMT_0BGR, GF_PIXEL_XBGR}, + {AV_PIX_FMT_BGR0, GF_PIXEL_BGRX}, + {AV_PIX_FMT_GRAY8, GF_PIXEL_GREYSCALE}, + {AV_PIX_FMT_YA8, GF_PIXEL_GREYALPHA}, + {AV_PIX_FMT_RGB444, GF_PIXEL_RGB_444}, + {AV_PIX_FMT_RGB555, GF_PIXEL_RGB_555}, + {AV_PIX_FMT_RGB565, GF_PIXEL_RGB_565}, + {AV_PIX_FMT_RGBA, GF_PIXEL_RGBA}, + {AV_PIX_FMT_ARGB, GF_PIXEL_ARGB}, + {AV_PIX_FMT_ABGR, GF_PIXEL_ABGR}, + {AV_PIX_FMT_BGRA, GF_PIXEL_BGRA}, + + /*aliases*/ + {AV_PIX_FMT_YUVJ420P, GF_PIXEL_YUV}, + {AV_PIX_FMT_YUVJ422P, GF_PIXEL_YUV422}, + {AV_PIX_FMT_YUVJ444P, GF_PIXEL_YUV444}, + + {0}, +}; + +u32 ffmpeg_pixfmt_from_gpac(u32 pfmt) +{ + u32 i=0; + while (FF2GPAC_PixelFormats[i].gpac_pf) { + if (FF2GPAC_PixelFormats[i].gpac_pf == pfmt) + return FF2GPAC_PixelFormats[i].ff_pf; + i++; + } + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[FFMPEG] Unmapped GPAC pixel format %s, patch welcome\n", gf_4cc_to_str(pfmt) )); + return 0; +} + +u32 ffmpeg_pixfmt_to_gpac(u32 pfmt) +{ + const AVPixFmtDescriptor *ffdesc = av_pix_fmt_desc_get(pfmt); + if (!ffdesc) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFMPEG] Unrecognized FFMPEG pixel format %d\n", pfmt )); + return 0; + } + u32 i=0; + while (FF2GPAC_PixelFormats[i].gpac_pf) { + if (FF2GPAC_PixelFormats[i].ff_pf == pfmt) + return FF2GPAC_PixelFormats[i].gpac_pf; + i++; + } + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[FFMPEG] Unmapped FFMPEG pixel format %s, patch welcome\n", ffdesc->name)); + return 0; +} + + +typedef struct +{ + u32 ff_sf; + u32 gpac_sf; +} GF_FF_AFREG; + +static const GF_FF_AFREG FF2GPAC_AudioFormats[] = +{ + {AV_SAMPLE_FMT_U8, GF_AUDIO_FMT_U8}, + {AV_SAMPLE_FMT_S16, GF_AUDIO_FMT_S16}, + {AV_SAMPLE_FMT_S32, GF_AUDIO_FMT_S32}, + {AV_SAMPLE_FMT_FLT, GF_AUDIO_FMT_FLT}, + {AV_SAMPLE_FMT_DBL, GF_AUDIO_FMT_DBL}, + {AV_SAMPLE_FMT_U8P, GF_AUDIO_FMT_U8P}, + {AV_SAMPLE_FMT_S16P, GF_AUDIO_FMT_S16P}, + {AV_SAMPLE_FMT_S32P, GF_AUDIO_FMT_S32P}, + {AV_SAMPLE_FMT_FLTP, GF_AUDIO_FMT_FLTP}, + {AV_SAMPLE_FMT_DBLP, GF_AUDIO_FMT_DBLP}, + {0}, +}; + +u32 ffmpeg_audio_fmt_from_gpac(u32 sfmt) +{ + u32 i=0; + while (FF2GPAC_AudioFormats[i].gpac_sf) { + if (FF2GPAC_AudioFormats[i].gpac_sf == sfmt) + return FF2GPAC_AudioFormats[i].ff_sf; + i++; + } + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[FFMPEG] Unmapped GPAC audio format %s, patch welcome\n", gf_4cc_to_str(sfmt) )); + return 0; +} + +u32 ffmpeg_audio_fmt_to_gpac(u32 sfmt) +{ + u32 i=0; + while (FF2GPAC_AudioFormats[i].gpac_sf) { + if (FF2GPAC_AudioFormats[i].ff_sf == sfmt) + return FF2GPAC_AudioFormats[i].gpac_sf; + i++; + } + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[FFMPEG] Unmapped FFMPEG audio format %d, patch welcome\n", sfmt )); + return 0; +} + + +typedef struct +{ + u64 ff_ch_mask; + u64 gpac_ch_mask; +} GF_FF_LAYOUTREG; + +static const GF_FF_LAYOUTREG FF2GPAC_ChannelLayout[] = +{ + {AV_CH_FRONT_LEFT, GF_AUDIO_CH_FRONT_LEFT}, + {AV_CH_FRONT_RIGHT, GF_AUDIO_CH_FRONT_RIGHT}, + {AV_CH_FRONT_CENTER, GF_AUDIO_CH_FRONT_CENTER}, + {AV_CH_LOW_FREQUENCY, GF_AUDIO_CH_LFE}, + {AV_CH_BACK_LEFT, GF_AUDIO_CH_SURROUND_LEFT}, + {AV_CH_BACK_RIGHT, GF_AUDIO_CH_SURROUND_RIGHT}, + {AV_CH_FRONT_LEFT_OF_CENTER, GF_AUDIO_CH_FRONT_CENTER_LEFT}, + {AV_CH_FRONT_RIGHT_OF_CENTER, GF_AUDIO_CH_FRONT_CENTER_RIGHT}, + {AV_CH_BACK_CENTER, GF_AUDIO_CH_REAR_CENTER}, + {AV_CH_SIDE_LEFT, GF_AUDIO_CH_REAR_SURROUND_LEFT}, + {AV_CH_SIDE_RIGHT, GF_AUDIO_CH_REAR_SURROUND_RIGHT}, + {AV_CH_TOP_CENTER, GF_AUDIO_CH_CENTER_SURROUND_TOP}, + {AV_CH_TOP_FRONT_LEFT, GF_AUDIO_CH_FRONT_TOP_LEFT}, + {AV_CH_TOP_FRONT_CENTER, GF_AUDIO_CH_FRONT_TOP_CENTER}, + {AV_CH_TOP_FRONT_RIGHT, GF_AUDIO_CH_FRONT_TOP_RIGHT}, + {AV_CH_TOP_BACK_LEFT, GF_AUDIO_CH_SURROUND_TOP_LEFT}, + {AV_CH_TOP_BACK_CENTER, GF_AUDIO_CH_REAR_CENTER_TOP}, + {AV_CH_TOP_BACK_RIGHT, GF_AUDIO_CH_SURROUND_TOP_RIGHT}, + {AV_CH_WIDE_LEFT, GF_AUDIO_CH_WIDE_FRONT_LEFT}, + {AV_CH_WIDE_RIGHT, GF_AUDIO_CH_WIDE_FRONT_RIGHT}, + {AV_CH_SURROUND_DIRECT_LEFT, GF_AUDIO_CH_SURROUND_DIRECT_LEFT}, + {AV_CH_SURROUND_DIRECT_RIGHT, GF_AUDIO_CH_SURROUND_DIRECT_RIGHT}, + {AV_CH_LOW_FREQUENCY_2, GF_AUDIO_CH_LFE2}, +/* {AV_CH_STEREO_LEFT, }, + {AV_CH_STEREO_RIGHT, }, +*/ +}; + +u64 ffmpeg_channel_layout_from_gpac(u64 gpac_ch_layout) +{ + u32 i, nb_layout = sizeof(FF2GPAC_ChannelLayout) / sizeof(GF_FF_LAYOUTREG); + u64 res = 0; + for (i=0; i= 58 + {AV_CODEC_ID_AV1, GF_CODECID_AV1, 0}, +#endif + {AV_CODEC_ID_VP8, GF_CODECID_VP8, 0}, + {AV_CODEC_ID_VP9, GF_CODECID_VP9, 0}, + {AV_CODEC_ID_VC1, GF_CODECID_SMPTE_VC1, 0}, + + {AV_CODEC_ID_OPUS, GF_CODECID_OPUS, 0}, + + + //ProRes + {AV_CODEC_ID_PRORES, GF_CODECID_APCH, GF_4CC('h','c','p','a') }, + {AV_CODEC_ID_PRORES, GF_CODECID_APCO, GF_4CC('o','c','p','a') }, + {AV_CODEC_ID_PRORES, GF_CODECID_APCN, GF_4CC('n','c','p','a')}, + {AV_CODEC_ID_PRORES, GF_CODECID_APCS, GF_4CC('s','c','p','a')}, + {AV_CODEC_ID_PRORES, GF_CODECID_AP4X, GF_4CC('x','4','p','a')}, + {AV_CODEC_ID_PRORES, GF_CODECID_AP4H, GF_4CC('h','4','p','a')}, + + //RAW codecs + {AV_CODEC_ID_RAWVIDEO, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_S16LE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_S16BE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_U16LE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_U16BE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_S8, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_U8, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_MULAW, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_ALAW, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_S32LE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_S32BE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_U32LE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_U32BE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_S24LE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_S24BE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_U24LE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_U24BE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_S24DAUD, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_ZORK, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_S16LE_PLANAR, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_DVD, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_F32BE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_F32LE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_F64BE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_F64LE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_BLURAY, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_LXF, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_S302M, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_S8_PLANAR, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_S24LE_PLANAR, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_S32LE_PLANAR, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_S16BE_PLANAR, GF_CODECID_RAW, 0}, +#if LIBAVCODEC_VERSION_MAJOR >= 58 + {AV_CODEC_ID_PCM_S64LE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_S64BE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_F16LE, GF_CODECID_RAW, 0}, + {AV_CODEC_ID_PCM_F24LE, GF_CODECID_RAW, 0}, +#endif + {0} +}; + +u32 ffmpeg_codecid_from_gpac(u32 codec_id, u32 *ff_codectag) +{ + u32 i=0; + if (ff_codectag) *ff_codectag = 0; + + while (FF2GPAC_CodecIDs[i].gpac_codec_id) { + if (FF2GPAC_CodecIDs[i].gpac_codec_id == codec_id) { + if (ff_codectag) *ff_codectag = FF2GPAC_CodecIDs[i].ff_codectag; + return FF2GPAC_CodecIDs[i].ff_codec_id; + } + i++; + } + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[FFMPEG] Unmapped GPAC codec %s\n", gf_codecid_name(codec_id) )); + return 0; +} + +u32 ffmpeg_codecid_to_gpac(u32 codec_id) +{ + u32 i=0; + while (FF2GPAC_CodecIDs[i].ff_codec_id) { + if (FF2GPAC_CodecIDs[i].ff_codec_id == codec_id) + return FF2GPAC_CodecIDs[i].gpac_codec_id; + i++; + } + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[FFMPEG] Unmapped FFMPEG codec ID %s\n", avcodec_get_name(codec_id) )); + return 0; +} + + +typedef struct +{ + u32 ff_st; + u32 gpac_st; +} GF_FF_ST; + +static const GF_FF_ST FF2GPAC_StreamTypes[] = +{ + {AVMEDIA_TYPE_VIDEO, GF_STREAM_VISUAL}, + {AVMEDIA_TYPE_AUDIO, GF_STREAM_AUDIO}, + {AVMEDIA_TYPE_DATA, GF_STREAM_METADATA}, + {AVMEDIA_TYPE_SUBTITLE, GF_STREAM_TEXT}, + {AVMEDIA_TYPE_ATTACHMENT, GF_STREAM_METADATA}, + {AVMEDIA_TYPE_UNKNOWN, GF_STREAM_UNKNOWN}, + {0}, +}; + +u32 ffmpeg_stream_type_from_gpac(u32 streamtype) +{ + u32 i=0; + while (FF2GPAC_StreamTypes[i].gpac_st) { + if (FF2GPAC_StreamTypes[i].gpac_st == streamtype) + return FF2GPAC_StreamTypes[i].ff_st; + i++; + } + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFMPEG] Unmapped GPAC stream type %s, assuming data\n", gf_stream_type_name(streamtype) )); + return AVMEDIA_TYPE_DATA; +} + + +u32 ffmpeg_stream_type_to_gpac(u32 streamtype) +{ + u32 i=0; + while (FF2GPAC_StreamTypes[i].gpac_st) { + if (FF2GPAC_StreamTypes[i].ff_st == streamtype) + return FF2GPAC_StreamTypes[i].gpac_st; + i++; + } + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFMPEG] Unmapped FFMPEG stream type %d, assuming data\n", streamtype )); + return GF_STREAM_METADATA; +} + +//static void ff_log_callback(void *avcl, int level, const char *fmt, va_list vl) { } + +void ffmpeg_setup_logs(u32 log_class) +{ + u32 level = gf_log_get_tool_level(log_class); + switch (level) { + case GF_LOG_DEBUG: + av_log_set_level(AV_LOG_DEBUG); + break; + case GF_LOG_INFO: + av_log_set_level(AV_LOG_INFO); + break; + case GF_LOG_WARNING: + av_log_set_level(AV_LOG_WARNING); + break; + default: + av_log_set_level(AV_LOG_ERROR); + break; + } +// av_log_set_callback(ff_log_callback); +} + +void ffmpeg_initialize() +{ + if (ffmpeg_init) return; + av_register_all(); + avformat_network_init(); + ffmpeg_init = GF_TRUE; +} + +static void ffmpeg_register_free(GF_FilterSession *session, GF_FilterRegister *reg) +{ + u32 i; + GF_FFRegistryExt *ffregext = reg->udta; + GF_List *all_filters = ffregext->all_filters; + u32 nb_skip_begin = ffregext->nb_arg_skip; + gf_free(ffregext); + reg->udta = NULL; + + if (all_filters) { + while (gf_list_count(all_filters)) { + i=0; + GF_FilterRegister *f = gf_list_pop_back(all_filters); + if (f->caps) + gf_free((void *)f->caps); + + while (f->args) { + GF_FilterArgs *arg = (GF_FilterArgs *) &f->args[i]; + if (!arg || !arg->arg_name) break; + i++; + if (arg->arg_default_val) gf_free((void *) arg->arg_default_val); + if (arg->min_max_enum) gf_free((void *) arg->min_max_enum); + if (arg->flags & GF_FS_ARG_META_ALLOC) gf_free((void *) arg->arg_desc); + } + gf_free((void *) f->args); + gf_free((char*) f->name); + gf_fs_remove_filter_register(session, f); + gf_free(f); + } + gf_list_del(all_filters); + } + i=nb_skip_begin; + while (reg->args) { + GF_FilterArgs *arg = (GF_FilterArgs *) ®->args[i]; + if (!arg || !arg->arg_name) break; + i++; + if (arg->arg_default_val) gf_free((void *) arg->arg_default_val); + if (arg->min_max_enum) gf_free((void *) arg->min_max_enum); + if (arg->flags & GF_FS_ARG_META_ALLOC) gf_free((void *) arg->arg_desc); + } + if (reg->args) gf_free((void *) reg->args); + +} + +GF_FilterArgs ffmpeg_arg_translate(const struct AVOption *opt) +{ + char szDef[200]; + GF_FilterArgs arg; + memset(&arg, 0, sizeof(GF_FilterArgs)); + arg.arg_name = opt->name; + arg.arg_desc = opt->help; + arg.offset_in_private=-1; + arg.flags = GF_FS_ARG_META; + if (opt->name[0] == 0) + arg.flags = GF_FS_ARG_META; + + switch (opt->type) { + case AV_OPT_TYPE_INT64: + case AV_OPT_TYPE_INT: + case AV_OPT_TYPE_CHANNEL_LAYOUT: + if (opt->type==AV_OPT_TYPE_INT64) arg.arg_type = GF_PROP_LSINT; + else if (opt->type==AV_OPT_TYPE_INT) arg.arg_type = GF_PROP_SINT; + else arg.arg_type = GF_PROP_UINT; //channel layout, map to int + + sprintf(szDef, LLD, opt->default_val.i64); + arg.arg_default_val = gf_strdup(szDef); + if (opt->max>=(Double) GF_INT_MAX) + sprintf(szDef, LLD"-I", (s64) opt->min); + else + sprintf(szDef, LLD"-"LLD, (s64) opt->min, (s64) opt->max); + arg.min_max_enum = gf_strdup(szDef); + break; +#if LIBAVCODEC_VERSION_MAJOR >= 57 + case AV_OPT_TYPE_UINT64: +// case AV_OPT_TYPE_UINT: + arg.arg_type = GF_PROP_LUINT; + sprintf(szDef, LLU, opt->default_val.i64); + arg.arg_default_val = gf_strdup(szDef); + if (opt->max>=(Double) GF_INT_MAX) + sprintf(szDef, LLU"-I", (s64) opt->min); + else + sprintf(szDef, LLU"-"LLU, (u64) opt->min, (u64) opt->max); + arg.min_max_enum = gf_strdup(szDef); + break; + case AV_OPT_TYPE_BOOL: + arg.arg_type = GF_PROP_BOOL; + arg.arg_default_val = gf_strdup(opt->default_val.i64 ? "true" : "false"); + break; +#endif + case AV_OPT_TYPE_FLOAT: + arg.arg_type = GF_PROP_FLOAT; + sprintf(szDef, "%g", opt->default_val.dbl); + arg.arg_default_val = gf_strdup(szDef); + break; + case AV_OPT_TYPE_DOUBLE: + arg.arg_type = GF_PROP_DOUBLE; + sprintf(szDef, "%g", opt->default_val.dbl); + arg.arg_default_val = gf_strdup(szDef); + break; + case AV_OPT_TYPE_VIDEO_RATE: + case AV_OPT_TYPE_RATIONAL: + arg.arg_type = GF_PROP_FRACTION; + sprintf(szDef, "%d/%d", opt->default_val.q.num, opt->default_val.q.den); + arg.arg_default_val = gf_strdup(szDef); + break; + case AV_OPT_TYPE_COLOR://color is a string in libavfilter + case AV_OPT_TYPE_STRING: + case AV_OPT_TYPE_DICT: + arg.arg_type = GF_PROP_STRING; + if (opt->default_val.str) + arg.arg_default_val = gf_strdup(opt->default_val.str); + break; + case AV_OPT_TYPE_IMAGE_SIZE: + arg.arg_type = GF_PROP_STRING; + break; + case AV_OPT_TYPE_CONST: + arg.arg_type = GF_PROP_BOOL; + arg.arg_default_val = gf_strdup("false"); + break; + case AV_OPT_TYPE_FLAGS: + arg.arg_type = GF_PROP_UINT; + sprintf(szDef, ""LLD, opt->default_val.i64); + arg.arg_default_val = gf_strdup(szDef); + break; + case AV_OPT_TYPE_BINARY: + case AV_OPT_TYPE_DURATION: + arg.arg_type = GF_PROP_UINT; + break; + case AV_OPT_TYPE_SAMPLE_FMT: + arg.arg_type = GF_PROP_UINT; + arg.arg_default_val = gf_strdup("s16"); + arg.min_max_enum = gf_strdup("u8|s16|s32|flt|dbl|u8p|s16p|s32p|fltp|dblp"); + break; + case AV_OPT_TYPE_PIXEL_FMT: + arg.arg_type = GF_PROP_UINT; + arg.arg_default_val = gf_strdup(av_get_pix_fmt_name(AV_PIX_FMT_YUV420P) ); + { + u32 i, all_len=1, def_size=1000; + char *enum_val = gf_malloc(sizeof(char)*def_size); + enum_val[0] = 0; + for (i=0; idef_size) { + def_size+=1000; + enum_val = gf_realloc(enum_val, sizeof(char)*def_size); + } + if (i) strcat(enum_val, "|"); + strcat(enum_val, n); + all_len+=len-1; + } + arg.min_max_enum = enum_val; + } + break; + default: + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[FFMPEG] Unknown ffmpeg option type %d\n", opt->type)); + break; + } + return arg; +} + +static u32 ff_streamtype(u32 st) +{ + switch (st) { + case AVMEDIA_TYPE_AUDIO: return GF_STREAM_AUDIO; + case AVMEDIA_TYPE_VIDEO: return GF_STREAM_VISUAL; + case AVMEDIA_TYPE_DATA: return GF_STREAM_METADATA; + case AVMEDIA_TYPE_SUBTITLE: return GF_STREAM_TEXT; + } + return GF_STREAM_UNKNOWN; +} + +#if (LIBAVFORMAT_VERSION_MAJOR >= 58) && (LIBAVFORMAT_VERSION_MINOR>=30) +#else +#define NO_AVIO_PROTO +#endif + +static void ffmpeg_expand_register(GF_FilterSession *session, GF_FilterRegister *orig_reg, u32 type) +{ + u32 i=0, idx=0, flags=0; +#ifndef NO_AVIO_PROTO + Bool protocol_pass = GF_FALSE; +#endif + const struct AVOption *opt; + GF_List *all_filters = gf_list_new(); + AVInputFormat *fmt = NULL; + const AVOutputFormat *ofmt = NULL; + AVCodec *codec = NULL; +#if (LIBAVFILTER_VERSION_MAJOR > 5) + const AVFilter *avf = NULL; +#endif + +#if !defined(NO_AVIO_PROTO) || (LIBAVFILTER_VERSION_MAJOR > 6) + void *av_it; +#endif + + const char *fname = ""; + if (type==FF_REG_TYPE_DEMUX) fname = "ffdmx"; + else if (type==FF_REG_TYPE_DECODE) { + fname = "ffdec"; + flags=AV_OPT_FLAG_DECODING_PARAM; + } + else if (type==FF_REG_TYPE_DEV_IN) { + fname = "ffavin"; + avdevice_register_all(); + flags=AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_DECODING_PARAM; + } + else if (type==FF_REG_TYPE_ENCODE) { + fname = "ffenc"; + flags=AV_OPT_FLAG_ENCODING_PARAM; + } + else if (type==FF_REG_TYPE_MUX) { + fname = "ffmx"; + } + else if (type==FF_REG_TYPE_AVF) { + fname = "ffavf"; + } + else return; + + + ((GF_FFRegistryExt *)orig_reg->udta)->all_filters = all_filters; + + +#ifndef NO_AVIO_PROTO +second_pass: +#endif + +#if !defined(NO_AVIO_PROTO) || (LIBAVFILTER_VERSION_MAJOR > 6) + av_it = NULL; +#endif + + while (1) { + const AVClass *av_class=NULL; + const char *subname = NULL; +#ifndef GPAC_DISABLE_DOC + const char *description = "description unavailable in ffmpeg"; +#endif + char szDef[100]; + GF_FilterRegister *freg; + if (type==FF_REG_TYPE_DEMUX) { +#ifndef NO_AVIO_PROTO + if (protocol_pass) { + subname = avio_enum_protocols(&av_it, 0); + if (!subname) break; + av_class = avio_protocol_get_class(subname); +#ifndef GPAC_DISABLE_DOC + description = "Input protocol"; +#endif + } else +#endif + { + fmt = av_iformat_next(fmt); + if (!fmt) break; + av_class = fmt->priv_class; + subname = fmt->name; +#ifndef GPAC_DISABLE_DOC + description = fmt->long_name; +#endif + } + } else if (type==FF_REG_TYPE_DECODE) { + codec = av_codec_next(codec); + if (!codec) break; + av_class = codec->priv_class; + subname = codec->name; +#ifndef GPAC_DISABLE_DOC + description = codec->long_name; +#endif + } else if (type==FF_REG_TYPE_DEV_IN) { +#if (LIBAVCODEC_VERSION_MAJOR >= 58) && (LIBAVCODEC_VERSION_MINOR>=20) + fmt = av_input_video_device_next(fmt); + if (!fmt) break; + av_class = fmt->priv_class; + subname = fmt->name; +#ifndef GPAC_DISABLE_DOC + description = fmt->long_name; +#endif + if (!av_class || (av_class->category!=AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT) ) continue; +#else + fmt = av_iformat_next(fmt); + if (!fmt) break; + av_class = fmt->priv_class; + subname = fmt->name; +#ifndef GPAC_DISABLE_DOC + description = fmt->long_name; +#endif + //brute force ... + if (!strcmp(subname, "dshow")) {} + else if (!strcmp(subname, "avfoundation")) {} + else if (!strcmp(subname, "video4linux2")) {} + else if (!strcmp(subname, "x11grab")) {} + else if (!strcmp(subname, "alsa")) {} + else if (!strcmp(subname, "decklink")) {} + else if (!strcmp(subname, "kmsgrab")) {} + else if (!strcmp(subname, "libndi_newtek")) {} + else if (!strcmp(subname, "fbdev")) {} + else if (!strcmp(subname, "gdigrab")) {} + else if (!strcmp(subname, "iec61883")) {} + else if (!strcmp(subname, "lavfi")) {} + else if (!strcmp(subname, "libcdio")) {} + else if (!strcmp(subname, "openal")) {} + else if (!strcmp(subname, "oss")) {} + else if (!strcmp(subname, "pulse")) {} + else if (!strcmp(subname, "sndio")) {} + else if (!stricmp(subname, "vfw")) {} + else continue; +#endif + } else if (type==FF_REG_TYPE_ENCODE) { + codec = av_codec_next(codec); + if (!codec) break; + av_class = codec->priv_class; + subname = codec->name; +#ifndef GPAC_DISABLE_DOC + description = codec->long_name; +#endif + } else if (type==FF_REG_TYPE_MUX) { +#ifndef NO_AVIO_PROTO + if (protocol_pass) { + subname = avio_enum_protocols(&av_it, 1); + if (!subname) break; + av_class = avio_protocol_get_class(subname); +#ifndef GPAC_DISABLE_DOC + description = "Output protocol"; +#endif + } else +#endif + + { +#if (LIBAVFILTER_VERSION_MAJOR > 6) + ofmt = av_muxer_iterate(&av_it); +#else + ofmt = av_oformat_next(ofmt); +#endif + if (!ofmt) break; + av_class = ofmt->priv_class; + subname = ofmt->name; +#ifndef GPAC_DISABLE_DOC + description = ofmt->long_name; +#endif + } + } else if (type==FF_REG_TYPE_AVF) { +#if (LIBAVFILTER_VERSION_MAJOR > 5) +#if (LIBAVFILTER_VERSION_MAJOR > 6) + avf = av_filter_iterate(&av_it); +#else + avf = avfilter_next(avf); +#endif + if (!avf) break; + av_class = avf->priv_class; + subname = avf->name; +#ifndef GPAC_DISABLE_DOC + description = avf->description; +#endif + +#else + break; + +#endif + } else { + break; + } + GF_SAFEALLOC(freg, GF_FilterRegister); + if (!freg) continue; + memcpy(freg, orig_reg, sizeof(GF_FilterRegister)); + freg->args = NULL; + freg->caps = NULL; + freg->nb_caps = 0; + + gf_list_add(all_filters, freg); + + sprintf(szDef, "%s:%s", fname, subname); + freg->name = gf_strdup(szDef); +#ifndef GPAC_DISABLE_DOC + freg->description = description; +#endif + if ((type==FF_REG_TYPE_ENCODE) || (type==FF_REG_TYPE_DECODE)) { + GF_FilterCapability *caps; + u32 cid = ffmpeg_codecid_to_gpac(codec->id); + freg->nb_caps = 3; + + caps = gf_malloc(sizeof(GF_FilterCapability)*3); + memset(caps, 0, sizeof(GF_FilterCapability)*3); + caps[0].code = GF_PROP_PID_STREAM_TYPE; + caps[0].val.type = GF_PROP_UINT; + caps[0].val.value.uint = ff_streamtype(codec->type); + caps[0].flags = GF_CAPS_INPUT_OUTPUT; + + caps[1].code = GF_PROP_PID_CODECID; + caps[1].val.type = GF_PROP_UINT; + caps[1].val.value.uint = (type==FF_REG_TYPE_DECODE) ? cid : GF_CODECID_RAW; + caps[1].flags = GF_CAPS_INPUT; + + caps[2].code = GF_PROP_PID_CODECID; + caps[2].val.type = GF_PROP_UINT; + caps[2].val.value.uint = (type==FF_REG_TYPE_DECODE) ? GF_CODECID_RAW : cid; + caps[2].flags = GF_CAPS_OUTPUT; + + freg->caps = caps; + } + else if ((type==FF_REG_TYPE_DEMUX) + && fmt +#if LIBAVCODEC_VERSION_MAJOR >= 58 + && (fmt->mime_type || fmt->extensions) +#else + && fmt->extensions +#endif + ){ + GF_FilterCapability *caps; +#if LIBAVCODEC_VERSION_MAJOR >= 58 + freg->nb_caps = (fmt->mime_type && fmt->extensions) ? 4 : 2; +#else + freg->nb_caps = 2; +#endif + caps = gf_malloc(sizeof(GF_FilterCapability)*freg->nb_caps); + memset(caps, 0, sizeof(GF_FilterCapability)*freg->nb_caps); + caps[0].code = GF_PROP_PID_STREAM_TYPE; + caps[0].val.type = GF_PROP_UINT; + caps[0].val.value.uint = GF_STREAM_FILE; + caps[0].flags = GF_CAPS_INPUT_STATIC; + + caps[1].code = fmt->extensions ? GF_PROP_PID_FILE_EXT : GF_PROP_PID_MIME; + caps[1].val.type = GF_PROP_NAME; +#if LIBAVCODEC_VERSION_MAJOR >= 58 + caps[1].val.value.string = (char *) ( fmt->extensions ? fmt->extensions : fmt->mime_type ); +#else + caps[1].val.value.string = (char *) fmt->extensions; +#endif + caps[1].flags = GF_CAPS_INPUT; + +#if LIBAVCODEC_VERSION_MAJOR >= 58 + if (fmt->mime_type && fmt->extensions) { + caps[2].flags = 0; + caps[3].code = GF_PROP_PID_MIME; + caps[3].val.type = GF_PROP_NAME; + caps[3].val.value.string = (char *) fmt->mime_type; + caps[3].flags = GF_CAPS_INPUT; + } +#endif + //TODO map codec IDs if known ? + freg->caps = caps; + } + else if ((type==FF_REG_TYPE_MUX) + && ofmt +#if LIBAVCODEC_VERSION_MAJOR >= 58 + && (ofmt->mime_type || ofmt->extensions) +#else + && ofmt->extensions +#endif + ){ + GF_FilterCapability *caps; +#if LIBAVCODEC_VERSION_MAJOR >= 58 + freg->nb_caps = (ofmt->mime_type && ofmt->extensions) ? 4 : 2; +#else + freg->nb_caps = 2; +#endif + caps = gf_malloc(sizeof(GF_FilterCapability)*freg->nb_caps); + memset(caps, 0, sizeof(GF_FilterCapability)*freg->nb_caps); + caps[0].code = GF_PROP_PID_STREAM_TYPE; + caps[0].val.type = GF_PROP_UINT; + caps[0].val.value.uint = GF_STREAM_FILE; + caps[0].flags = GF_CAPS_OUTPUT_STATIC; + + caps[1].code = ofmt->extensions ? GF_PROP_PID_FILE_EXT : GF_PROP_PID_MIME; + caps[1].val.type = GF_PROP_NAME; +#if LIBAVCODEC_VERSION_MAJOR >= 58 + caps[1].val.value.string = (char *) ( ofmt->extensions ? ofmt->extensions : ofmt->mime_type ); +#else + caps[1].val.value.string = (char *) ofmt->extensions; +#endif + caps[1].flags = GF_CAPS_OUTPUT; + +#if LIBAVCODEC_VERSION_MAJOR >= 58 + if (ofmt->mime_type && ofmt->extensions) { + caps[2].flags = 0; + caps[3].code = GF_PROP_PID_MIME; + caps[3].val.type = GF_PROP_NAME; + caps[3].val.value.string = (char *) ofmt->mime_type; + caps[3].flags = GF_CAPS_OUTPUT; + } +#endif + //TODO map codec IDs if known ? + freg->caps = caps; + } + + idx=0; + i=0; + while (av_class) { + opt = &av_class->option[idx]; + if (!opt || !opt->name) break; + if (!flags || (opt->flags & flags) ) { + if (!opt->unit) + i++; + else { + u32 k; + Bool is_first = GF_TRUE; + for (k=0; koption[k]; + if (an_opt && an_opt->unit && !strcmp(opt->unit, an_opt->unit)) { + //patch some broken AVClass where the option name declaring the option unit is not declared first + u32 l; + for (l=0; loption[l]; + if (!strcmp(par_opt->name, an_opt->name)) { + is_first=GF_FALSE; + break; + } + } + break; + } + } + if (is_first) + i++; + } + } + idx++; + } + if (i) { + GF_FilterArgs *args = gf_malloc(sizeof(GF_FilterArgs)*(i+1)); + memset(args, 0, sizeof(GF_FilterArgs)*(i+1)); + freg->args = args; + + i=0; + idx=0; + while (av_class) { + opt = &av_class->option[idx]; + if (!opt || !opt->name) break; + if (flags && !(opt->flags & flags) ) { + idx++; + continue; + } + + if (opt->unit) { + u32 k; + const char *opt_name = NULL; + GF_FilterArgs *par_arg=NULL; + for (k=0; koption[k]; + if (an_opt && an_opt->unit && !strcmp(opt->unit, an_opt->unit)) { + opt_name = an_opt->name; + break; + } + } + if (opt_name) { + for (k=0; karg_name, opt_name)) + break; + par_arg = NULL; + } + } + + if (par_arg) { + GF_FilterArgs an_arg = ffmpeg_arg_translate(opt); + if (!(par_arg->flags & GF_FS_ARG_META_ALLOC)) { + par_arg->arg_desc = par_arg->arg_desc ? gf_strdup(par_arg->arg_desc) : NULL; + par_arg->flags |= GF_FS_ARG_META_ALLOC; + } + gf_dynstrcat((char **) &par_arg->arg_desc, an_arg.arg_name, "\n - "); + gf_dynstrcat((char **) &par_arg->arg_desc, an_arg.arg_desc, ": "); + + if (an_arg.arg_default_val) + gf_free((void *) an_arg.arg_default_val); + if (an_arg.min_max_enum) + gf_free((void *) an_arg.min_max_enum); + + idx++; + continue; + } + } + + args[i] = ffmpeg_arg_translate(opt); + i++; + + idx++; + } + } + gf_fs_add_filter_register(session, freg); + } + +#ifndef NO_AVIO_PROTO + if ((type==FF_REG_TYPE_MUX) || (type==FF_REG_TYPE_DEMUX)) { + if (!protocol_pass) { + protocol_pass = GF_TRUE; + goto second_pass; + } + } +#endif + +} + +void ffmpeg_build_register(GF_FilterSession *session, GF_FilterRegister *orig_reg, const GF_FilterArgs *default_args, u32 nb_def_args, u32 reg_type) +{ + GF_FilterArgs *args; + u32 i=0, idx=0, nb_args; + const struct AVOption *opt; + u32 opt_type = AV_OPT_FLAG_DECODING_PARAM; + Bool load_meta_filters = session ? GF_TRUE : GF_FALSE; + AVFormatContext *format_ctx = NULL; + AVCodecContext *codec_ctx = NULL; + const AVClass *av_class = NULL; + GF_FFRegistryExt *ffregext; + + ffmpeg_initialize(); + + //by default no need to load option descriptions, everything is handled by av_set_opt in update_args + if (!load_meta_filters) { + orig_reg->args = default_args; + orig_reg->register_free = NULL; + return; + } + + + if (reg_type==FF_REG_TYPE_ENCODE) opt_type = AV_OPT_FLAG_ENCODING_PARAM; + else if (reg_type==FF_REG_TYPE_MUX) opt_type = AV_OPT_FLAG_ENCODING_PARAM; + else if (reg_type==FF_REG_TYPE_AVF) opt_type = 0xFFFFFFFF; + + if ((reg_type==FF_REG_TYPE_ENCODE) || (reg_type==FF_REG_TYPE_DECODE)) { + codec_ctx = avcodec_alloc_context3(NULL); + av_class = codec_ctx->av_class; + } else if (reg_type==FF_REG_TYPE_AVF) { +#if (LIBAVFILTER_VERSION_MAJOR > 5) + av_class = avfilter_get_class(); +#endif + } else { + format_ctx = avformat_alloc_context(); + av_class = format_ctx->av_class; + } + + i=0; + idx=0; + while (av_class && av_class->option) { + opt = &av_class->option[idx]; + if (!opt || !opt->name) break; + + if (opt->flags & opt_type) { + if (!opt->unit) { + i++; + } else { + u32 k; + Bool is_first = GF_TRUE; + for (k=0; koption[k]; + if (an_opt && an_opt->unit && !strcmp(opt->unit, an_opt->unit)) { + //patch some broken AVClass where the option name declaring the option unit is not declared first + u32 l; + for (l=0; loption[l]; + if (!strcmp(par_opt->name, an_opt->name)) { + is_first=GF_FALSE; + break; + } + } + break; + } + } + if (is_first) + i++; + } + } + idx++; + } + i += nb_def_args; + + nb_args = i+1; + args = gf_malloc(sizeof(GF_FilterArgs)*nb_args); + memset(args, 0, sizeof(GF_FilterArgs)*nb_args); + orig_reg->args = args; + + for (i=0; ioption) { + opt = &av_class->option[idx]; + if (!opt || !opt->name) break; + + if (opt->flags & opt_type) { + + if (opt->unit) { + u32 k; + const char *opt_name = NULL; + GF_FilterArgs *par_arg=NULL; + for (k=0; koption[k]; + if (an_opt && an_opt->unit && !strcmp(opt->unit, an_opt->unit)) { + opt_name = an_opt->name; + break; + } + } + if (opt_name) { + for (k=0; karg_name, opt_name)) + break; + par_arg = NULL; + } + } + + if (par_arg) { + GF_FilterArgs an_arg = ffmpeg_arg_translate(opt); + if (!(par_arg->flags & GF_FS_ARG_META_ALLOC)) { + //move from const to allocated memory + par_arg->arg_desc = gf_strdup(par_arg->arg_desc ? par_arg->arg_desc : " "); + par_arg->flags |= GF_FS_ARG_META_ALLOC; + /*trash default values and min_max_enum for flags*/ + if (par_arg->arg_default_val) { + gf_free((char *) par_arg->arg_default_val); + par_arg->arg_default_val = NULL; + } + if (par_arg->min_max_enum) { + gf_free((char *) par_arg->min_max_enum); + par_arg->min_max_enum = NULL; + } + } + gf_dynstrcat((char **) &par_arg->arg_desc, an_arg.arg_name, "\n- "); + gf_dynstrcat((char **) &par_arg->arg_desc, an_arg.arg_desc ? an_arg.arg_desc : "", ": "); + + if (an_arg.arg_default_val) + gf_free((void *) an_arg.arg_default_val); + if (an_arg.min_max_enum) + gf_free((void *) an_arg.min_max_enum); + + idx++; + continue; + } + } + assert(nb_args>i); + args[i] = ffmpeg_arg_translate(opt); + i++; + } + idx++; + } + memcpy(&args[i], &default_args[nb_def_args-1], sizeof(GF_FilterArgs)); + + if (codec_ctx) { +#if (LIBAVCODEC_VERSION_MAJOR >= 58) && (LIBAVCODEC_VERSION_MINOR>=20) + avcodec_free_context(&codec_ctx); +#else + av_free(codec_ctx); +#endif + } else { + avformat_free_context(format_ctx); + } + + GF_SAFEALLOC(ffregext, GF_FFRegistryExt); + if (!ffregext) return; + + orig_reg->udta = ffregext; + ffregext->nb_arg_skip = nb_def_args-1; + orig_reg->register_free = ffmpeg_register_free; + + ffmpeg_expand_register(session, orig_reg, reg_type); + +} + +void ffmpeg_set_enc_dec_flags(const AVDictionary *options, AVCodecContext *ctx) +{ + AVDictionaryEntry *de=NULL; + + ctx->flags = 0; + ctx->flags2 = 0; + while (1) { + u32 idx=0; + de = av_dict_get((AVDictionary *)options, "", (const AVDictionaryEntry *) de, AV_DICT_IGNORE_SUFFIX); + if (!de) break; + + while (ctx->av_class->option) { + const struct AVOption *opt = &ctx->av_class->option[idx]; + if (!opt || !opt->name) break; + if (opt->name && !strcmp(opt->name, de->key) && (!stricmp(de->value, "true") || !stricmp(de->value, "yes") || !stricmp(de->value, "1") )) { + if (opt->unit && !strcmp(opt->unit, "flags")) + ctx->flags |= (int) opt->default_val.i64; + else if (opt->unit && !strcmp(opt->unit, "flags2")) + ctx->flags2 |= (int) opt->default_val.i64; + break; + } + idx++; + } + } +} + +void ffmpeg_set_mx_dmx_flags(const AVDictionary *options, AVFormatContext *ctx) +{ + AVDictionaryEntry *de=NULL; + + while (1) { + u32 idx=0; + de = av_dict_get((AVDictionary *)options, "", de, AV_DICT_IGNORE_SUFFIX); + if (!de) break; + + while (ctx->av_class->option) { + const struct AVOption *opt = &ctx->av_class->option[idx]; + if (!opt || !opt->name) break; + if (opt->name && !strcmp(opt->name, de->key)) { + if (opt->unit && !strcmp(opt->unit, "fflags")) + ctx->flags |= (int) opt->default_val.i64; + else if (opt->unit && !strcmp(opt->unit, "avioflags")) + ctx->avio_flags |= (int) opt->default_val.i64; + break; + } + idx++; + } + } +} + +void ffmpeg_report_unused_options(GF_Filter *filter, AVDictionary *options) +{ + AVDictionaryEntry *prev_e = NULL; + while (1) { + prev_e = av_dict_get(options, "", prev_e, AV_DICT_IGNORE_SUFFIX); + if (!prev_e) break; + gf_filter_report_unused_meta_option(filter, prev_e->key); + } + +} +#endif diff --git a/src/filters/ff_common.h b/src/filters/ff_common.h new file mode 100644 index 0000000..e531579 --- /dev/null +++ b/src/filters/ff_common.h @@ -0,0 +1,75 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2018 + * All rights reserved + * + * This file is part of GPAC / common ffmpeg filters + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#define JPEGLIB_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GF_FFMPEG_DECODER_CONFIG GF_4CC('f','f','D','C') + + +//rendering/translating to internal supported formats for text streams is not yet implemented +//#define FF_SUB_SUPPORT + + +GF_FilterArgs ffmpeg_arg_translate(const struct AVOption *opt); +void ffmpeg_setup_logs(u32 log_class); + +enum{ + FF_REG_TYPE_DEMUX=0, + FF_REG_TYPE_DECODE, + FF_REG_TYPE_DEV_IN, + FF_REG_TYPE_ENCODE, + FF_REG_TYPE_MUX, + FF_REG_TYPE_AVF, +}; + +void ffmpeg_build_register(GF_FilterSession *session, GF_FilterRegister *orig_reg, const GF_FilterArgs *default_args, u32 nb_def_args, u32 reg_type); + +u32 ffmpeg_pixfmt_from_gpac(u32 pfmt); +u32 ffmpeg_pixfmt_to_gpac(u32 pfmt); +u32 ffmpeg_audio_fmt_from_gpac(u32 sfmt); +u32 ffmpeg_audio_fmt_to_gpac(u32 sfmt); +u32 ffmpeg_codecid_from_gpac(u32 codec_id, u32 *ff_codectag); +u32 ffmpeg_codecid_to_gpac(u32 codec_id); + +u32 ffmpeg_stream_type_from_gpac(u32 streamtype); +u32 ffmpeg_stream_type_to_gpac(u32 streamtype); + +void ffmpeg_set_enc_dec_flags(const AVDictionary *options, AVCodecContext *ctx); +void ffmpeg_set_mx_dmx_flags(const AVDictionary *options, AVFormatContext *ctx); + +u64 ffmpeg_channel_layout_from_gpac(u64 gpac_ch_layout); +u64 ffmpeg_channel_layout_to_gpac(u64 ff_ch_layout); + +void ffmpeg_report_unused_options(GF_Filter *filter, AVDictionary *options); diff --git a/src/filters/ff_dec.c b/src/filters/ff_dec.c new file mode 100644 index 0000000..f59cb80 --- /dev/null +++ b/src/filters/ff_dec.c @@ -0,0 +1,933 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2018 + * All rights reserved + * + * This file is part of GPAC / ffmpeg decode filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include + +#ifdef GPAC_HAS_FFMPEG + +#include "ff_common.h" + +#include + +#define FF_CHECK_PROP(_name, _ffname, _type) if (ctx->_name != ctx->decoder->_ffname) { \ + gf_filter_pid_set_property(ctx->out_pid, _type, &PROP_UINT( (u32) ctx->decoder->_ffname ) ); \ + ctx->_name = (u32) ctx->decoder->_ffname; \ + } \ + +#define FF_CHECK_PROPL(_name, _ffname, _type) if (ctx->_name != ctx->decoder->_ffname) { \ + gf_filter_pid_set_property(ctx->out_pid, _type, &PROP_LONGUINT( (u32) ctx->decoder->_ffname ) ); \ + ctx->_name = (u32) ctx->decoder->_ffname; \ +} \ + +#define FF_CHECK_PROP_VAL(_name, _val, _type) if (ctx->_name != _val) { \ + gf_filter_pid_set_property(ctx->out_pid, _type, &PROP_UINT( _val ) ); \ + ctx->_name = _val; \ + } \ + +static GF_Err ffdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove); + + +typedef struct _gf_ffdec_ctx +{ + //internal data + Bool initialized; + + Bool owns_context; + AVCodecContext *decoder; + //decode options + AVDictionary *options; + + Bool reconfig_pending; + + GF_FilterPid *in_pid, *out_pid; + //media type + u32 type; + //CRC32 of extra_data + u32 extra_data_crc; + + GF_Err (*process)(GF_Filter *filter, struct _gf_ffdec_ctx *ctx); + + u32 flush_done; + + //for now we don't share the data + AVFrame *frame; + //audio state + u32 channels, sample_rate, sample_fmt, bytes_per_sample; + u64 channel_layout; + u32 frame_start; + u32 nb_samples_already_in_frame; + + //video state + u32 width, height, pixel_fmt, stride, stride_uv; + GF_Fraction sar; + struct SwsContext *sws_ctx; + + GF_List *src_packets; + + Bool drop_non_refs; +} GF_FFDecodeCtx; + +static GF_Err ffdec_initialize(GF_Filter *filter) +{ + GF_FFDecodeCtx *ctx = (GF_FFDecodeCtx *) gf_filter_get_udta(filter); + ctx->initialized = GF_TRUE; + ctx->src_packets = gf_list_new(); + + ffmpeg_setup_logs(GF_LOG_CODEC); + return GF_OK; +} + +static void ffdec_finalize(GF_Filter *filter) +{ + GF_FFDecodeCtx *ctx = (GF_FFDecodeCtx *) gf_filter_get_udta(filter); + + if (ctx->options) av_dict_free(&ctx->options); + if (ctx->frame) av_frame_free(&ctx->frame); + if (ctx->sws_ctx) sws_freeContext(ctx->sws_ctx); + + while (gf_list_count(ctx->src_packets)) { + GF_FilterPacket *pck = gf_list_pop_back(ctx->src_packets); + gf_filter_pck_unref(pck); + } + gf_list_del(ctx->src_packets); + + if (ctx->owns_context && ctx->decoder) { + if (ctx->decoder->extradata) gf_free(ctx->decoder->extradata); + avcodec_close(ctx->decoder); + } + return; +} + +static GF_Err ffdec_process_video(GF_Filter *filter, struct _gf_ffdec_ctx *ctx) +{ + AVPacket pkt; + AVFrame *frame; + AVPicture pict; + Bool is_eos=GF_FALSE; + s32 res; + s32 gotpic; + const char *data = NULL; + Bool seek_flag = GF_FALSE; + u32 i, count; + u32 size=0, pix_fmt, outsize, pix_out, stride, stride_uv, uv_height, nb_planes; + u8 *out_buffer; + GF_FilterPacket *pck_src; + GF_FilterPacket *dst_pck; + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->in_pid); + + if (ctx->reconfig_pending) { + pck = NULL; + } else if (!pck) { + is_eos = gf_filter_pid_is_eos(ctx->in_pid); + if (!is_eos) return GF_OK; + } + + if (pck && ctx->drop_non_refs && !gf_filter_pck_get_sap(pck)) { + gf_filter_pid_drop_packet(ctx->in_pid); + return GF_OK; + } + + frame = ctx->frame; + + av_init_packet(&pkt); + + if (pck) { + data = gf_filter_pck_get_data(pck, &size); + + if (!size) { + gf_filter_pid_drop_packet(ctx->in_pid); + return GF_OK; + } + + pck_src = pck; + gf_filter_pck_ref_props(&pck_src); + if (pck_src) gf_list_add(ctx->src_packets, pck_src); + + //seems ffmpeg is not properly handling the decoding after a flush, we close and reopen the codec + if (ctx->flush_done) { + const AVCodec *codec = ctx->decoder->codec; + avcodec_close(ctx->decoder); + avcodec_open2(ctx->decoder, codec, NULL ); + ctx->flush_done = GF_FALSE; + } + + pkt.dts = gf_filter_pck_get_dts(pck); + pkt.pts = gf_filter_pck_get_cts(pck); + pkt.duration = gf_filter_pck_get_duration(pck); + if (gf_filter_pck_get_sap(pck)>0) + pkt.flags = AV_PKT_FLAG_KEY; + } + pkt.data = (uint8_t*)data; + pkt.size = size; + + /*TOCHECK: for AVC bitstreams after ISMA decryption, in case (as we do) the decryption DRM tool + doesn't put back nalu size, we have to do it ourselves, but we can't modify input data...*/ + + gotpic=0; + res = avcodec_decode_video2(ctx->decoder, frame, &gotpic, &pkt); + if (pck) gf_filter_pid_drop_packet(ctx->in_pid); + + if (!gotpic) { + if (is_eos) { + ctx->flush_done = GF_TRUE; + gf_filter_pid_set_eos(ctx->out_pid); + return GF_EOS; + } + if (ctx->reconfig_pending) { + if (ctx->decoder->extradata) gf_free(ctx->decoder->extradata); + ctx->decoder->extradata = NULL; + avcodec_close(ctx->decoder); + ctx->decoder = NULL; + ctx->reconfig_pending = GF_FALSE; + //these properties are checked after decode, when we reconfigure we copy props from input to output + //so we need to make sure we retrigger pid config even if these did not change + ctx->pixel_fmt = 0; + ctx->width = 0; + ctx->height = 0; + ctx->stride = 0; + ctx->stride_uv = 0; + ctx->sar.num = ctx->sar.den = 0; + while (gf_list_count(ctx->src_packets)) { + GF_FilterPacket *ref_pck = gf_list_pop_back(ctx->src_packets); + gf_filter_pck_unref(ref_pck); + } + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[FFDec] PID %s reconfigure pending and all frames flushed, reconfguring\n", gf_filter_pid_get_name(ctx->in_pid) )); + return ffdec_configure_pid(filter, ctx->in_pid, GF_FALSE); + } + } + + if (res < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to decode frame PTS "LLU": %s\n", gf_filter_pid_get_name(ctx->in_pid), pkt.pts, av_err2str(res) )); + return GF_NON_COMPLIANT_BITSTREAM; + } + if (!gotpic) return GF_OK; + + pix_fmt = ffmpeg_pixfmt_to_gpac(ctx->decoder->pix_fmt); + if (!pix_fmt) pix_fmt = GF_PIXEL_RGB; + + //update all props + FF_CHECK_PROP_VAL(pixel_fmt, pix_fmt, GF_PROP_PID_PIXFMT) + FF_CHECK_PROP(width, width, GF_PROP_PID_WIDTH) + FF_CHECK_PROP(height, height, GF_PROP_PID_HEIGHT) + + stride = stride_uv = uv_height = nb_planes = 0; + if (! gf_pixel_get_size_info(pix_fmt, ctx->width, ctx->height, &outsize, &stride, &stride_uv, &nb_planes, &uv_height) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to query pixelformat size infon", gf_filter_pid_get_name(ctx->in_pid) )); + return GF_NOT_SUPPORTED; + } + + FF_CHECK_PROP_VAL(stride, stride, GF_PROP_PID_STRIDE) + FF_CHECK_PROP_VAL(stride_uv, stride_uv, GF_PROP_PID_STRIDE_UV) + if (ctx->sar.num * ctx->decoder->sample_aspect_ratio.den != ctx->sar.den * ctx->decoder->sample_aspect_ratio.num) { + ctx->sar.num = ctx->decoder->sample_aspect_ratio.num; + ctx->sar.den = ctx->decoder->sample_aspect_ratio.den; + + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_SAR, &PROP_FRAC( ctx->sar ) ); + } + + memset(&pict, 0, sizeof(pict)); + + + pck_src = NULL; + count = gf_list_count(ctx->src_packets); + for (i=0; isrc_packets, i); + if (gf_filter_pck_get_cts(pck_src) == frame->pkt_pts) break; + pck_src = NULL; + } + + seek_flag = GF_FALSE; + if (pck_src) { + seek_flag = gf_filter_pck_get_seek_flag(pck_src); + } + //this was a seek frame, do not dispatch + if (seek_flag) { + if (pck_src) { + gf_list_del_item(ctx->src_packets, pck_src); + gf_filter_pck_unref(pck_src); + } + return GF_OK; + } + + dst_pck = gf_filter_pck_new_alloc(ctx->out_pid, outsize, &out_buffer); + + if (pck_src) { + if (dst_pck) gf_filter_pck_merge_properties(pck_src, dst_pck); + gf_list_del_item(ctx->src_packets, pck_src); + gf_filter_pck_unref(pck_src); + } else { + if (dst_pck) gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + } + if (!dst_pck) return GF_OUT_OF_MEM; + + + //TODO: cleanup, we should not convert pixel format in the decoder but through filters ! + switch (ctx->pixel_fmt) { + case GF_PIXEL_RGB: + pict.data[0] = (uint8_t *)out_buffer; + pict.linesize[0] = 3*ctx->width; + pix_out = AV_PIX_FMT_RGB24; + break; + case GF_PIXEL_RGBA: + pict.data[0] = (uint8_t *)out_buffer; + pict.linesize[0] = 4*ctx->width; + pix_out = AV_PIX_FMT_RGBA; + break; + case GF_PIXEL_YUV: + case GF_PIXEL_YUV_10: + pict.data[0] = (uint8_t *)out_buffer; + pict.data[1] = (uint8_t *)out_buffer + ctx->stride * ctx->height; + pict.data[2] = (uint8_t *)pict.data[1] + ctx->stride_uv * uv_height; + pict.linesize[0] = ctx->stride; + pict.linesize[1] = pict.linesize[2] = ctx->stride_uv; + if (ctx->pixel_fmt == GF_PIXEL_YUV_10) + pix_out = AV_PIX_FMT_YUV420P10LE; + else + pix_out = AV_PIX_FMT_YUV420P; + break; + + case GF_PIXEL_YUV422: + case GF_PIXEL_YUV422_10: + pict.data[0] = (uint8_t *)out_buffer; + pict.data[1] = (uint8_t *)out_buffer + ctx->stride * ctx->height; + pict.data[2] = (uint8_t *)pict.data[1] + ctx->stride_uv * ctx->height; + pict.linesize[0] = ctx->stride; + pict.linesize[1] = pict.linesize[2] = ctx->stride_uv; + if (ctx->pixel_fmt == GF_PIXEL_YUV422_10) + pix_out = AV_PIX_FMT_YUV422P10LE; + else + pix_out = AV_PIX_FMT_YUV422P; + break; + + case GF_PIXEL_YUV444: + case GF_PIXEL_YUV444_10: + pict.data[0] = (uint8_t *)out_buffer; + pict.data[1] = (uint8_t *)out_buffer + ctx->stride * ctx->height; + pict.data[2] = (uint8_t *)out_buffer + 2*ctx->stride * ctx->height; + pict.linesize[0] = pict.linesize[1] = pict.linesize[2] = ctx->stride; + if (ctx->pixel_fmt == GF_PIXEL_YUV444_10) + pix_out = AV_PIX_FMT_YUV444P10LE; + else + pix_out = AV_PIX_FMT_YUV444P; + break; + + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] Unsupported pixel format %s, patch welcome\n", av_get_pix_fmt_name(ctx->decoder->pix_fmt) )); + + gf_filter_pck_discard(dst_pck); + + return GF_NOT_SUPPORTED; + } + + ctx->sws_ctx = sws_getCachedContext(ctx->sws_ctx, + ctx->decoder->width, ctx->decoder->height, ctx->decoder->pix_fmt, + ctx->width, ctx->height, pix_out, SWS_BICUBIC, NULL, NULL, NULL); + if (ctx->sws_ctx) { + sws_scale(ctx->sws_ctx, (const uint8_t * const*)frame->data, frame->linesize, 0, ctx->height, pict.data, pict.linesize); + } + + gf_filter_pck_set_seek_flag(dst_pck, GF_FALSE); + + if (frame->interlaced_frame) + gf_filter_pck_set_interlaced(dst_pck, frame->top_field_first ? 2 : 1); + + gf_filter_pck_send(dst_pck); + return GF_OK; +} + + +static GF_Err ffdec_process_audio(GF_Filter *filter, struct _gf_ffdec_ctx *ctx) +{ + AVPacket pkt; + s32 gotpic; + s32 len, in_size, i; + u32 output_size; + Bool is_eos=GF_FALSE; + u8 *data; + AVFrame *frame; + GF_FilterPacket *dst_pck, *src_pck; + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->in_pid); + + if (ctx->reconfig_pending) { + pck = NULL; + } else if (!pck) { + is_eos = gf_filter_pid_is_eos(ctx->in_pid); + if (!is_eos) return GF_OK; + } + + av_init_packet(&pkt); + if (pck) pkt.data = (uint8_t *) gf_filter_pck_get_data(pck, &in_size); + + if (pck) { + src_pck = pck; + gf_filter_pck_ref_props(&src_pck); + if (src_pck) gf_list_add(ctx->src_packets, src_pck); + + if (!pkt.data) { + gf_filter_pid_drop_packet(ctx->in_pid); + return GF_OK; + } + + pkt.pts = gf_filter_pck_get_cts(pck); + pkt.dts = gf_filter_pck_get_dts(pck); + + pkt.size = in_size; + if ((s32) ctx->frame_start > pkt.size) ctx->frame_start = 0; + //seek to last byte consumed by the previous decode4() + else if (ctx->frame_start) { + pkt.data += ctx->frame_start; + pkt.size -= ctx->frame_start; + } + pkt.duration = gf_filter_pck_get_duration(pck); + if (gf_filter_pck_get_sap(pck)>0) + pkt.flags = AV_PKT_FLAG_KEY; + + } else { + pkt.size = 0; + } + + frame = ctx->frame; + len = avcodec_decode_audio4(ctx->decoder, frame, &gotpic, &pkt); + + //this will handle eos as well + if ((len<0) || !gotpic) { + ctx->frame_start = 0; + if (pck) gf_filter_pid_drop_packet(ctx->in_pid); + if (pkt.size && (len<0)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to decode frame PTS "LLU": %s\n", gf_filter_pid_get_name(ctx->in_pid), pkt.pts, av_err2str(len) )); + return GF_NON_COMPLIANT_BITSTREAM; + } + if (is_eos) { + gf_filter_pid_set_eos(ctx->out_pid); + return GF_EOS; + } + if (ctx->reconfig_pending) { + if (ctx->decoder->extradata) gf_free(ctx->decoder->extradata); + ctx->decoder->extradata = NULL; + avcodec_close(ctx->decoder); + ctx->decoder = NULL; + ctx->reconfig_pending = GF_FALSE; + //these properties are checked after decode, when we reconfigure we copy props from input to output + //so we need to make sure we retrigger pid config even if these did not change + ctx->sample_fmt = 0; + ctx->sample_rate = 0; + ctx->channels = 0; + ctx->channel_layout = 0; + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[FFDec] PID %s reconfigure pending and all frames flushed, reconfguring\n", gf_filter_pid_get_name(ctx->in_pid) )); + return ffdec_configure_pid(filter, ctx->in_pid, GF_FALSE); + } + return GF_OK; + } + + FF_CHECK_PROP(channels, channels, GF_PROP_PID_NUM_CHANNELS) + FF_CHECK_PROPL(channel_layout, channel_layout, GF_PROP_PID_CHANNEL_LAYOUT) + FF_CHECK_PROP(sample_rate, sample_rate, GF_PROP_PID_SAMPLE_RATE) + + output_size = frame->nb_samples*ctx->channels*ctx->bytes_per_sample; + dst_pck = gf_filter_pck_new_alloc(ctx->out_pid, output_size, &data); + + switch (frame->format) { + case AV_SAMPLE_FMT_U8P: + case AV_SAMPLE_FMT_S16P: + case AV_SAMPLE_FMT_S32P: + case AV_SAMPLE_FMT_FLTP: + case AV_SAMPLE_FMT_DBLP: + for (i=0; (u32) i< ctx->channels; i++) { + char *inputChannel = frame->extended_data[i]; + memcpy(data, inputChannel, ctx->bytes_per_sample * frame->nb_samples); + data += ctx->bytes_per_sample * frame->nb_samples; + } + break; + default: + memcpy(data, ctx->frame->data[0], ctx->bytes_per_sample * frame->nb_samples * ctx->channels); + break; + } + + //we don't follow the same approach as in video, we assume the codec works with one in one out + //and use the first entry in src packets to match in order to copy the properties + //a nicer approach would be to count delay frames (number of frames used to initialize) + //and backmerge properties from the last packet in to the last-nb_init_frames + src_pck = gf_list_get(ctx->src_packets, 0); + + if (src_pck) { + gf_filter_pck_merge_properties(src_pck, dst_pck); + gf_list_rem(ctx->src_packets, 0); + gf_filter_pck_unref(src_pck); + } + + if (frame->pkt_pts != AV_NOPTS_VALUE) { + u64 pts = frame->pkt_pts; + u32 timescale = gf_filter_pck_get_timescale(pck); + if (ctx->nb_samples_already_in_frame) { + if (ctx->sample_rate == timescale) { + pts += ctx->nb_samples_already_in_frame; + } + } + gf_filter_pck_set_cts(dst_pck, pts); + } + if (frame->pkt_dts != AV_NOPTS_VALUE) { + gf_filter_pck_set_dts(dst_pck, frame->pkt_dts); + } + + + gf_filter_pck_send(dst_pck); + + ctx->frame_start += len; + //done with this input packet + if (in_size <= (s32) ctx->frame_start) { + frame->nb_samples = 0; + ctx->frame_start = 0; + ctx->nb_samples_already_in_frame = 0; + gf_filter_pid_drop_packet(ctx->in_pid); + return GF_OK; + } + //still some data to decode in packet, don't drop it + //todo: check if frame->pkt_pts or frame->pts is updated by ffmpeg, otherwise do it ourselves ! + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] Code not yet tested - frame PTS was "LLU" - nb samples dec %d\n", frame->pkt_pts, frame->nb_samples)); + ctx->nb_samples_already_in_frame += frame->nb_samples; + frame->nb_samples = 0; + + return ffdec_process_audio(filter, ctx); +} + +#ifdef FF_SUB_SUPPORT +static GF_Err ffdec_process_subtitle(GF_Filter *filter, struct _gf_ffdec_ctx *ctx) +{ + AVPacket pkt; + AVSubtitle subs; + s32 gotpic; + s32 len, in_size; + Bool is_eos=GF_FALSE; + + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->in_pid); + + if (!pck) { + is_eos = gf_filter_pid_is_eos(ctx->in_pid); + if (!is_eos) return GF_OK; + } + av_init_packet(&pkt); + if (pck) pkt.data = (uint8_t *) gf_filter_pck_get_data(pck, &in_size); + + if (!is_eos) { + u64 dts; + pkt.pts = gf_filter_pck_get_cts(pck); + + //copy over SAP and duration in dts + dts = gf_filter_pck_get_sap(pck); + dts <<= 32; + dts |= gf_filter_pck_get_duration(pck); + pkt.dts = dts; + + pkt.size = in_size; + if (ctx->frame_start > pkt.size) ctx->frame_start = 0; + //seek to last byte consumed by the previous decode4() + else if (ctx->frame_start) { + pkt.data += ctx->frame_start; + pkt.size -= ctx->frame_start; + } + } else { + pkt.size = 0; + } + + memset(&subs, 0, sizeof(AVSubtitle)); + len = avcodec_decode_subtitle2(ctx->decoder, &subs, &gotpic, &pkt); + + //this will handle eos as well + if ((len<0) || !gotpic) { + ctx->frame_start = 0; + if (pck) gf_filter_pid_drop_packet(ctx->in_pid); + if (len<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to decode frame PTS "LLU": %s\n", gf_filter_pid_get_name(ctx->in_pid), pkt.pts, av_err2str(len) )); + return GF_NON_COMPLIANT_BITSTREAM; + } + if (is_eos) { + gf_filter_pid_set_eos(ctx->out_pid); + return GF_EOS; + } + return GF_OK; + } + //TODO - do we want to remap to TX3G/other and handle the rendering some place else, or do we do the rendering here ? + + + avsubtitle_free(&subs); + if (pck) gf_filter_pid_drop_packet(ctx->in_pid); + return GF_OK; +} +#endif + +static GF_Err ffdec_process(GF_Filter *filter) +{ + GF_FFDecodeCtx *ctx = (GF_FFDecodeCtx *) gf_filter_get_udta(filter); + if (gf_filter_pid_would_block(ctx->out_pid)) + return GF_OK; + return ctx->process(filter, ctx); +} + +static GF_Err ffdec_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + s32 res; + u32 type=0, gpac_codecid=0; + const GF_PropertyValue *prop; + GF_FFDecodeCtx *ctx = (GF_FFDecodeCtx *) gf_filter_get_udta(filter); + + //disconnect of src pid (not yet supported) + if (is_remove) { + //one in one out, this is simple + if (ctx->out_pid) gf_filter_pid_remove(ctx->out_pid); + return GF_OK; + } + + //check our PID: streamtype and codecid + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (!prop) return GF_NOT_SUPPORTED; + + type = prop->value.uint; + switch (type) { + case GF_STREAM_AUDIO: + case GF_STREAM_VISUAL: +#ifdef FF_SUB_SUPPORT + case GF_STREAM_TEXT: +#endif + break; + default: + return GF_NOT_SUPPORTED; + } + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!prop) return GF_NOT_SUPPORTED; + gpac_codecid = prop->value.uint; + if (gpac_codecid==GF_CODECID_RAW) + return GF_NOT_SUPPORTED; + + + //initial config or update + if (!ctx->in_pid || (ctx->in_pid==pid)) { + ctx->in_pid = pid; + if (!ctx->type) ctx->type = type; + else if (ctx->type != type) { + return GF_NOT_SUPPORTED; + } + } else { + //only one input pid in ctx + if (ctx->in_pid) return GF_REQUIRES_NEW_INSTANCE; + } + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + ctx->width = prop ? prop->value.uint : 320; + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + ctx->height = prop ? prop->value.uint : 240; + + + if (gpac_codecid == GF_CODECID_FFMPEG) { + AVCodec *codec=NULL; + prop = gf_filter_pid_get_property(pid, GF_FFMPEG_DECODER_CONFIG); + if (!prop || !prop->value.ptr) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s codec context not exposed by demuxer !\n", gf_filter_pid_get_name(pid) )); + return GF_SERVICE_ERROR; + } + ctx->decoder = prop->value.ptr; + codec = avcodec_find_decoder(ctx->decoder->codec_id); + if (!codec) return GF_NOT_SUPPORTED; + + res = avcodec_open2(ctx->decoder, codec, NULL ); + if (res < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to open codec context: %s\n", gf_filter_pid_get_name(pid), av_err2str(res) )); + return GF_NON_COMPLIANT_BITSTREAM; + } + } + //we reconfigure the stream + else { + AVCodec *codec=NULL; + u32 codec_id, ff_codectag=0; + if (ctx->decoder) { + codec_id = ffmpeg_codecid_from_gpac(gpac_codecid, NULL); + //same codec, same config, don't reinit + if (ctx->decoder->codec->id == codec_id) { + u32 cfg_crc=0; + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (prop && prop->value.data.ptr && prop->value.data.size) { + cfg_crc = gf_crc_32(prop->value.data.ptr, prop->value.data.size); + } + if (cfg_crc == ctx->extra_data_crc) { + goto reuse_codec_context; + } + } + + //we could further optimize by detecting we have the same codecid and injecting the extradata + //but this is not 100% reliable, and will require parsing AVC/HEVC config + //since this seems to work properly with decoder close/open, we keep it as is + ctx->reconfig_pending = GF_TRUE; + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[FFDec] PID %s reconfigure detected, flushing frame\n", gf_filter_pid_get_name(pid) )); + return GF_OK; + } + + codec_id = ffmpeg_codecid_from_gpac(gpac_codecid, &ff_codectag); + //specific remaps + if (!codec_id) { + switch (gpac_codecid) { + case GF_CODECID_MVC: codec_id = AV_CODEC_ID_H264; break; + } + + } + if (codec_id) codec = avcodec_find_decoder(codec_id); + if (!codec) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] No decoder found for codec %s\n", gf_codecid_name(gpac_codecid) )); + return GF_NOT_SUPPORTED; + } + + ctx->decoder = avcodec_alloc_context3(NULL); + if (! ctx->decoder) return GF_OUT_OF_MEM; + ctx->owns_context = GF_TRUE; + if (ff_codectag) + ctx->decoder->codec_tag = ff_codectag; + + ffmpeg_set_enc_dec_flags(ctx->options, ctx->decoder); + + //we may have a dsi here! + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (prop && prop->value.data.ptr && prop->value.data.size) { + //looks loke ffmpeg wants the fLaC keyword + if (gpac_codecid==GF_CODECID_FLAC) { + ctx->decoder->extradata_size = prop->value.data.size+4; + ctx->decoder->extradata = gf_malloc(sizeof(char) * prop->value.data.size+4); + memcpy(ctx->decoder->extradata, "fLaC", 4); + memcpy(ctx->decoder->extradata+4, prop->value.data.ptr, prop->value.data.size); + } else { + ctx->decoder->extradata_size = prop->value.data.size; + ctx->decoder->extradata = gf_malloc(sizeof(char) * prop->value.data.size); + memcpy(ctx->decoder->extradata, prop->value.data.ptr, prop->value.data.size); + } + ctx->extra_data_crc = gf_crc_32(prop->value.data.ptr, prop->value.data.size); + } + + res = avcodec_open2(ctx->decoder, codec, NULL ); + if (res < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] PID %s failed to open codec context: %s\n", gf_filter_pid_get_name(pid), av_err2str(res) )); + return GF_NON_COMPLIANT_BITSTREAM; + } + } + + ffmpeg_report_unused_options(filter, ctx->options); + + //we're good to go, declare our output pid + ctx->in_pid = pid; + if (!ctx->out_pid) { + char szCodecName[1000]; + ctx->out_pid = gf_filter_pid_new(filter); + + //to change once we implement on-the-fly codec change + sprintf(szCodecName, "ffdec:%s", ctx->decoder->codec->name ? ctx->decoder->codec->name : "unknown"); + gf_filter_set_name(filter, szCodecName); + gf_filter_pid_set_framing_mode(ctx->in_pid, GF_TRUE); + } + +reuse_codec_context: + //copy props it at init config or at reconfig + if (ctx->out_pid) { + gf_filter_pid_copy_properties(ctx->out_pid, ctx->in_pid); + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_DECODER_CONFIG, NULL ); + } + + if (type==GF_STREAM_VISUAL) { + u32 pix_fmt; + ctx->process = ffdec_process_video; + //for some streams, we don't have w/h/pixfmt after opening the decoder + //to make sure we are not confusing potential filters expecting them, init to default values + if (ctx->decoder->pix_fmt>=0) { + pix_fmt = ffmpeg_pixfmt_to_gpac(ctx->decoder->pix_fmt); + if (!pix_fmt) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[FFDec] Unsupported pixel format %d, defaulting to RGB\n", pix_fmt)); + pix_fmt = GF_PIXEL_RGB; + } + FF_CHECK_PROP_VAL(pixel_fmt, pix_fmt, GF_PROP_PID_PIXFMT) + } else { + ctx->pixel_fmt = GF_PIXEL_YUV; + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_PIXFMT, &PROP_UINT( ctx->pixel_fmt) ); + } + + if (ctx->decoder->width) { + FF_CHECK_PROP(width, width, GF_PROP_PID_WIDTH) + } else { + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_WIDTH, &PROP_UINT( ctx->width) ); + } + if (ctx->decoder->height) { + FF_CHECK_PROP(height, height, GF_PROP_PID_HEIGHT) + } else { + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_HEIGHT, &PROP_UINT( ctx->height) ); + } + if (ctx->decoder->sample_aspect_ratio.num && ctx->decoder->sample_aspect_ratio.den) { + ctx->sar.num = ctx->decoder->sample_aspect_ratio.num; + ctx->sar.den = ctx->decoder->sample_aspect_ratio.den; + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_SAR, &PROP_FRAC( ctx->sar) ); + } + if (!ctx->frame) + ctx->frame = av_frame_alloc(); + + if (ctx->pixel_fmt) { + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_PIXFMT, &PROP_UINT( ctx->pixel_fmt) ); + } + + } else if (type==GF_STREAM_AUDIO) { + ctx->process = ffdec_process_audio; + ctx->sample_fmt = ffmpeg_audio_fmt_to_gpac(ctx->decoder->sample_fmt); + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(ctx->sample_fmt) ); + ctx->bytes_per_sample = gf_audio_fmt_bit_depth(ctx->sample_fmt) / 8; + + //override PID props with what decoder gives us + if (ctx->decoder->channels) { + FF_CHECK_PROP(channels, channels, GF_PROP_PID_NUM_CHANNELS) + } + if (ctx->decoder->channel_layout) { + u64 ch_lay = ffmpeg_channel_layout_to_gpac(ctx->decoder->channel_layout); + if (ctx->channel_layout != ch_lay) { + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_CHANNEL_LAYOUT, &PROP_LONGUINT(ch_lay ) ); + ctx->channel_layout = ch_lay; + } + } + if (ctx->decoder->sample_rate) { + FF_CHECK_PROP(sample_rate, sample_rate, GF_PROP_PID_SAMPLE_RATE) + } + if (!ctx->frame) + ctx->frame = av_frame_alloc(); + + if (ctx->sample_fmt) { + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT( ctx->sample_fmt) ); + } + + } else { +#ifdef FF_SUB_SUPPORT + ctx->process = ffdec_process_subtitle; +#endif + } + return GF_OK; +} + + +static GF_Err ffdec_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *arg_val) +{ + s32 res; + GF_FFDecodeCtx *ctx = gf_filter_get_udta(filter); + + //initial parsing of arguments + if (!ctx->initialized) { + switch (arg_val->type) { + case GF_PROP_STRING: + res = av_dict_set(&ctx->options, arg_name, arg_val->value.string, 0); + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] Failed to set option %s:%s\n", arg_name, arg_val )); + } + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] Failed to set option %s:%s, unrecognized type %d\n", arg_name, arg_val, arg_val->type )); + return GF_NOT_SUPPORTED; + } + return GF_OK; + } + //updates of arguments, not supported for ffmpeg decoders + return GF_NOT_SUPPORTED; +} + +static Bool ffdec_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_FFDecodeCtx *ctx = (GF_FFDecodeCtx *) gf_filter_get_udta(filter); + + if ((evt->base.type==GF_FEVT_PLAY) || (evt->base.type==GF_FEVT_SET_SPEED) || (evt->base.type==GF_FEVT_RESUME)) { + ctx->drop_non_refs = evt->play.drop_non_ref; + } + return GF_FALSE; +} + +static const GF_FilterCapability FFDecodeCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_SVC), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_LHVC), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_TILE_BASE, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), + +#ifdef FF_SUB_SUPPORT + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_TEXT_MPEG4), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_TX3G), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_WEBVTT), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_SUBS_XML), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_SIMPLE_TEXT), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +#endif + +}; + +GF_FilterRegister FFDecodeRegister = { + .name = "ffdec", + .version = LIBAVCODEC_IDENT, + GF_FS_SET_DESCRIPTION("FFMPEG decoder") + GF_FS_SET_HELP("See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details") + .private_size = sizeof(GF_FFDecodeCtx), + SETCAPS(FFDecodeCaps), + .initialize = ffdec_initialize, + .finalize = ffdec_finalize, + .configure_pid = ffdec_configure_pid, + .process = ffdec_process, + .update_arg = ffdec_update_arg, + .process_event = ffdec_process_event, + .flags = GF_FS_REG_META, + //use middle priorty, so that hardware decs/other native impl in gpac can take over if needed + //don't use lowest one since we use this for scalable codecs + .priority = 128 + +}; + + +static const GF_FilterArgs FFDecodeArgs[] = +{ + { "*", -1, "any possible options defined for AVCodecContext and sub-classes. See `gpac -hx ffdec` and `gpac -hx ffdec:*`", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_META}, + {0} +}; + +const GF_FilterRegister *ffdec_register(GF_FilterSession *session) +{ + ffmpeg_build_register(session, &FFDecodeRegister, FFDecodeArgs, 1, FF_REG_TYPE_DECODE); + return &FFDecodeRegister; +} + +#else +#include +const GF_FilterRegister *ffdec_register(GF_FilterSession *session) +{ + return NULL; +} +#endif + diff --git a/src/filters/ff_dmx.c b/src/filters/ff_dmx.c new file mode 100644 index 0000000..6d025f7 --- /dev/null +++ b/src/filters/ff_dmx.c @@ -0,0 +1,1029 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / ffmpeg demux filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include + +#ifdef GPAC_HAS_FFMPEG + +#include "ff_common.h" + +//for NTP clock +#include + +enum +{ + COPY_NO, + COPY_A, + COPY_V, + COPY_AV +}; + +typedef struct +{ + //options + const char *src; + u32 block_size; + u32 copy, probes; + Bool sclock; + const char *fmt, *dev; + + //internal data + const char *fname; + u32 log_class; + + Bool initialized; + Bool raw_data; + //input file + AVFormatContext *demuxer; + //demux options + AVDictionary *options; + + GF_FilterPid **pids; + + Bool raw_pck_out; + u32 nb_playing; + Bool stop_seen; + u64 first_sample_clock, last_frame_ts; + u32 probe_frames; + + AVPacket pkt; + s32 audio_idx, video_idx; + + u64 *probe_times; + + Bool copy_audio, copy_video; + + u8 *avio_ctx_buffer; + AVIOContext *avio_ctx; + FILE *gfio; +} GF_FFDemuxCtx; + +static void ffdmx_finalize(GF_Filter *filter) +{ + GF_FFDemuxCtx *ctx = (GF_FFDemuxCtx *) gf_filter_get_udta(filter); + if (ctx->pids) + gf_free(ctx->pids); + if (ctx->options) + av_dict_free(&ctx->options); + if (ctx->probe_times) + gf_free(ctx->probe_times); + if (ctx->demuxer) { + avformat_close_input(&ctx->demuxer); + avformat_free_context(ctx->demuxer); + } + if (ctx->avio_ctx) { + if (ctx->avio_ctx->buffer) av_freep(&ctx->avio_ctx->buffer); + av_freep(&ctx->avio_ctx); + } + if (ctx->gfio) gf_fclose(ctx->gfio); + return; +} + +void ffdmx_shared_pck_release(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck) +{ + GF_FFDemuxCtx *ctx = (GF_FFDemuxCtx *) gf_filter_get_udta(filter); + if (ctx->raw_pck_out) { + av_free_packet(&ctx->pkt); + ctx->raw_pck_out = GF_FALSE; + gf_filter_post_process_task(filter); + } +} + +static GF_Err ffdmx_process(GF_Filter *filter) +{ + GF_Err e; + u32 i; + u64 sample_time; + u8 *data_dst; + Bool copy = GF_TRUE; + GF_FilterPacket *pck_dst; + GF_FFDemuxCtx *ctx = (GF_FFDemuxCtx *) gf_filter_get_udta(filter); + + if (!ctx->nb_playing) + return GF_EOS; + + if (ctx->raw_pck_out) + return GF_EOS; + + sample_time = gf_sys_clock_high_res(); + av_init_packet(&ctx->pkt); + ctx->pkt.stream_index = -1; + /*EOF*/ + if (av_read_frame(ctx->demuxer, &ctx->pkt) <0) { + av_free_packet(&ctx->pkt); + if (!ctx->raw_data) { + for (i=0; idemuxer->nb_streams; i++) { + if (ctx->pids[i]) gf_filter_pid_set_eos(ctx->pids[i]); + } + return GF_EOS; + } + return GF_OK; + } + + assert(ctx->pkt.stream_index>=0); + assert(ctx->pkt.stream_index < (s32) ctx->demuxer->nb_streams); + + if (ctx->pkt.pts == AV_NOPTS_VALUE) { + if (ctx->pkt.dts == AV_NOPTS_VALUE) { + GF_LOG(GF_LOG_WARNING, ctx->log_class, ("[%s] No PTS for packet on stream %d\n", ctx->fname, ctx->pkt.stream_index )); + } else { + ctx->pkt.pts = ctx->pkt.dts; + } + } + + if (! ctx->pids[ctx->pkt.stream_index] ) { + GF_LOG(GF_LOG_DEBUG, ctx->log_class, ("[%s] No PID defined for given stream %d\n", ctx->fname, ctx->pkt.stream_index )); + av_free_packet(&ctx->pkt); + return GF_OK; + } + + if (ctx->raw_data && (ctx->probe_framesprobes) ) { + if (ctx->pkt.stream_index==ctx->audio_idx) { + av_free_packet(&ctx->pkt); + return GF_OK; + } + + ctx->probe_times[ctx->probe_frames] = ctx->sclock ? sample_time : ctx->pkt.pts; + ctx->probe_frames++; + if (ctx->probe_frames==ctx->probes) { + u32 best_diff=0, max_stat=0; + for (i=0; iprobes; i++) { + if (i) { + u32 j, nb_stats=0; + u32 diff = (u32) (ctx->probe_times[i]-ctx->probe_times[i-1]); + for (j=1; jprobes; j++) { + s32 sdiff = (s32) (ctx->probe_times[j]-ctx->probe_times[j-1]); + sdiff -= (s32) diff; + if (sdiff<0) sdiff = -sdiff; + if (sdiff<2000) nb_stats++; + } + if (max_statlog_class, ("[%s] Video probing done, frame diff is %d us (for %d frames out of %d)\n", ctx->fname, best_diff, max_stat, ctx->probes)); + } else { + av_free_packet(&ctx->pkt); + return GF_OK; + } + } + + if (ctx->raw_data) { + if (ctx->pkt.stream_index==ctx->audio_idx) copy = ctx->copy_audio; + else copy = ctx->copy_video; + } + + if (ctx->raw_data && !copy) { + //we don't use shared memory on demuxers since they are usually the ones performing all the buffering + pck_dst = gf_filter_pck_new_shared(ctx->pids[ctx->pkt.stream_index], ctx->pkt.data, ctx->pkt.size, ffdmx_shared_pck_release); + ctx->raw_pck_out = GF_TRUE; + } else { + //we don't use shared memory on demuxers since they are usually the ones performing all the buffering + pck_dst = gf_filter_pck_new_alloc(ctx->pids[ctx->pkt.stream_index] , ctx->pkt.size, &data_dst); + assert(pck_dst); + memcpy(data_dst, ctx->pkt.data, ctx->pkt.size); + } + + + if (ctx->raw_data && ctx->sclock) { + u64 ts; + if (!ctx->first_sample_clock) { + ctx->last_frame_ts = ctx->first_sample_clock = sample_time; + } + ts = sample_time - ctx->first_sample_clock; + gf_filter_pck_set_cts(pck_dst, ts ); + ctx->last_frame_ts = ts; + } else if (ctx->pkt.pts != AV_NOPTS_VALUE) { + AVStream *stream = ctx->demuxer->streams[ctx->pkt.stream_index]; + u64 ts = ctx->pkt.pts * stream->time_base.num; + + gf_filter_pck_set_cts(pck_dst, ts ); + + if (ctx->pkt.dts != AV_NOPTS_VALUE) { + ts = ctx->pkt.dts * stream->time_base.num; + gf_filter_pck_set_dts(pck_dst, ts); + } + + if (ctx->pkt.duration) + gf_filter_pck_set_duration(pck_dst, (u32) ctx->pkt.duration); + } + + //fixme: try to identify SAP type 2 and more + if (ctx->pkt.flags & AV_PKT_FLAG_KEY) + gf_filter_pck_set_sap(pck_dst, GF_FILTER_SAP_1); + + if (ctx->pkt.flags & AV_PKT_FLAG_CORRUPT) + gf_filter_pck_set_corrupted(pck_dst, GF_TRUE); + + gf_net_get_utc(); + + if (ctx->raw_data) { + u64 ntp = gf_net_get_ntp_ts(); + gf_filter_pck_set_property(pck_dst, GF_PROP_PCK_SENDER_NTP, &PROP_LONGUINT(ntp) ); + } + e = gf_filter_pck_send(pck_dst); + + if (!ctx->raw_pck_out) + av_free_packet(&ctx->pkt); + return e; +} + + +static GF_Err ffdmx_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *arg_val) +{ + s32 res; + GF_FFDemuxCtx *ctx = gf_filter_get_udta(filter); + + //initial parsing of arguments + if (!ctx->initialized) { + switch (arg_val->type) { + case GF_PROP_STRING: + res = av_dict_set(&ctx->options, arg_name, arg_val->value.string, 0); + if (res<0) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] Failed to set option %s:%s\n", ctx->fname, arg_name, arg_val )); + } + break; + default: + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] Failed to set option %s:%s, unrecognized type %d\n", ctx->fname, arg_name, arg_val, arg_val->type )); + return GF_NOT_SUPPORTED; + } + return GF_OK; + } + //updates of arguments, not supported for ffmpeg demuxers + return GF_NOT_SUPPORTED; +} + +GF_Err ffdmx_init_common(GF_Filter *filter, GF_FFDemuxCtx *ctx, Bool is_grab) +{ + u32 i; + u32 nb_a, nb_v; +#ifdef FF_SUB_SUPPORT + u32 nb_t = 0; +#endif + char szName[50]; + + + ctx->pids = gf_malloc(sizeof(GF_FilterPid *)*ctx->demuxer->nb_streams); + memset(ctx->pids, 0, sizeof(GF_FilterPid *)*ctx->demuxer->nb_streams); + + nb_a = nb_v = 0; + for (i = 0; i < ctx->demuxer->nb_streams; i++) { + GF_FilterPid *pid=NULL; + Bool force_reframer = GF_FALSE; + Bool expose_ffdec=GF_FALSE; + AVStream *stream = ctx->demuxer->streams[i]; + AVCodecContext *codec = stream->codec; + u32 gpac_codec_id; + + switch(codec->codec_type) { + case AVMEDIA_TYPE_AUDIO: + pid = gf_filter_pid_new(filter); + if (!pid) return GF_OUT_OF_MEM; + gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_AUDIO) ); + nb_a++; + sprintf(szName, "audio%d", nb_a); + if (ctx->audio_idx<0) + ctx->audio_idx = i; + break; + + case AVMEDIA_TYPE_VIDEO: + pid = gf_filter_pid_new(filter); + if (!pid) return GF_OUT_OF_MEM; + gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_VISUAL) ); + nb_v++; + sprintf(szName, "video%d", nb_v); + if (ctx->video_idx<0) + ctx->video_idx = i; + break; +#ifdef FF_SUB_SUPPORT + case AVMEDIA_TYPE_SUBTITLE: + pid = gf_filter_pid_new(filter); + if (!pid) return GF_OUT_OF_MEM; + gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_TEXT) ); + nb_t++; + sprintf(szName, "text%d", nb_t); + break; +#endif + default: + sprintf(szName, "ffdmx%d", i+1); + break; + } + if (!pid) continue; + ctx->pids[i] = pid; + gf_filter_pid_set_udta(pid, stream); + + gf_filter_pid_set_property(pid, GF_PROP_PID_ID, &PROP_UINT(stream->id ? stream->id : i+1) ); + gf_filter_pid_set_name(pid, szName); + + if (ctx->raw_data && ctx->sclock) { + gf_filter_pid_set_property(pid, GF_PROP_PID_TIMESCALE, &PROP_UINT(1000000) ); + } else { + gf_filter_pid_set_property(pid, GF_PROP_PID_TIMESCALE, &PROP_UINT(stream->time_base.den) ); + } + + if (!ctx->raw_data) { + if (stream->duration>=0) + gf_filter_pid_set_property(pid, GF_PROP_PID_DURATION, &PROP_FRAC64_INT(stream->duration, stream->time_base.den) ); + else if (ctx->demuxer->duration>=0) + gf_filter_pid_set_property(pid, GF_PROP_PID_DURATION, &PROP_FRAC64_INT(ctx->demuxer->duration, AV_TIME_BASE) ); + } + + if (stream->sample_aspect_ratio.num && stream->sample_aspect_ratio.den) + gf_filter_pid_set_property(pid, GF_PROP_PID_SAR, &PROP_FRAC_INT( stream->sample_aspect_ratio.num, stream->sample_aspect_ratio.den ) ); + + if (stream->metadata) { + AVDictionaryEntry *ent=NULL; + while (1) { + ent = av_dict_get(stream->metadata, "", ent, AV_DICT_IGNORE_SUFFIX); + if (!ent) break; + + //we use the same syntax as ffmpeg here + gf_filter_pid_set_property_str(pid, ent->key, &PROP_STRING(ent->value) ); + } + } + + gpac_codec_id = ffmpeg_codecid_to_gpac(codec->codec_id); + if (!gpac_codec_id) { + gf_filter_pid_set_property(pid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_FFMPEG) ); + expose_ffdec=GF_TRUE; + } else { + gf_filter_pid_set_property(pid, GF_PROP_PID_CODECID, &PROP_UINT(gpac_codec_id) ); + } + + if (is_grab) + gf_filter_pid_set_property(pid, GF_PROP_PID_RAWGRAB, &PROP_BOOL(GF_TRUE) ); + else if (ctx->demuxer->iformat) { + if ((ctx->demuxer->iformat->flags & AVFMT_SEEK_TO_PTS) || ctx->demuxer->iformat->read_seek) + gf_filter_pid_set_property(pid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_SEEK ) ); + } + + //force reframer for the following formats if no DSI is found + if (!codec->extradata_size) { + switch (gpac_codec_id) { + case GF_CODECID_AC3: + case GF_CODECID_AAC_MPEG4: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + case GF_CODECID_AVC: + case GF_CODECID_HEVC: + case GF_CODECID_AV1: + force_reframer = GF_TRUE; + break; + } + } + if (expose_ffdec) { + const char *cname = avcodec_get_name(codec->codec_id); + gf_filter_pid_set_property(pid, GF_FFMPEG_DECODER_CONFIG, &PROP_POINTER( (void*)codec ) ); + + if (cname) + gf_filter_pid_set_property_str(pid, "ffmpeg:codec", &PROP_STRING(cname ) ); + } else if (codec->extradata_size) { + + //avc/hevc read by ffmpeg is still in annex B format + if (ctx->demuxer->iformat) { + if (!strcmp(ctx->demuxer->iformat->name, "h264") || !strcmp(ctx->demuxer->iformat->name, "hevc")) { + force_reframer = GF_TRUE; + } + } + + if (!force_reframer) { + //expose as const data + gf_filter_pid_set_property(pid, GF_PROP_PID_DECODER_CONFIG, &PROP_CONST_DATA( (char *)codec->extradata, codec->extradata_size) ); + } + } + + if (force_reframer) { + gf_filter_pid_set_property(pid, GF_PROP_PID_UNFRAMED, &PROP_BOOL(GF_TRUE) ); + } + + + if (codec->sample_rate) + gf_filter_pid_set_property(pid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT( codec->sample_rate ) ); + if (codec->frame_size) + gf_filter_pid_set_property(pid, GF_PROP_PID_SAMPLES_PER_FRAME, &PROP_UINT( codec->frame_size ) ); + if (codec->channels) + gf_filter_pid_set_property(pid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT( codec->channels ) ); + + if (codec->width) + gf_filter_pid_set_property(pid, GF_PROP_PID_WIDTH, &PROP_UINT( codec->width ) ); + if (codec->height) + gf_filter_pid_set_property(pid, GF_PROP_PID_HEIGHT, &PROP_UINT( codec->height ) ); +#if LIBAVCODEC_VERSION_MAJOR >= 58 + if (codec->framerate.num && codec->framerate.den) + gf_filter_pid_set_property(pid, GF_PROP_PID_FPS, &PROP_FRAC_INT( codec->framerate.num, codec->framerate.den ) ); +#endif + + if (codec->field_order>AV_FIELD_PROGRESSIVE) + gf_filter_pid_set_property(pid, GF_PROP_PID_INTERLACED, &PROP_BOOL(GF_TRUE) ); + + if ((codec->codec_type==AVMEDIA_TYPE_VIDEO) + && (codec->pix_fmt || ((codec->codec_id==AV_CODEC_ID_RAWVIDEO) && codec->codec_tag)) + ) { + u32 pfmt = 0; + +#define CHECK_FF_PFMT(__ff_cid, __gp_cid)\ + if ((codec->pix_fmt && (codec->pix_fmt==__ff_cid)) \ + || (codec->codec_tag && (avcodec_pix_fmt_to_codec_tag(__ff_cid) == codec->codec_tag))) { \ + pfmt = __gp_cid; \ + } \ + + CHECK_FF_PFMT(AV_PIX_FMT_YUV420P, GF_PIXEL_YUV) + else CHECK_FF_PFMT(AV_PIX_FMT_YUV420P10LE, GF_PIXEL_YUV_10) + else CHECK_FF_PFMT(AV_PIX_FMT_YUV422P, GF_PIXEL_YUV422) + else CHECK_FF_PFMT(AV_PIX_FMT_YUV422P10LE, GF_PIXEL_YUV422_10) + else CHECK_FF_PFMT(AV_PIX_FMT_YUV444P, GF_PIXEL_YUV444) + else CHECK_FF_PFMT(AV_PIX_FMT_YUV444P10LE, GF_PIXEL_YUV444_10) + else CHECK_FF_PFMT(AV_PIX_FMT_RGBA, GF_PIXEL_RGBA) + else CHECK_FF_PFMT(AV_PIX_FMT_RGB24, GF_PIXEL_RGB) + else CHECK_FF_PFMT(AV_PIX_FMT_BGR24, GF_PIXEL_BGR) + else CHECK_FF_PFMT(AV_PIX_FMT_UYVY422, GF_PIXEL_UYVY) + else CHECK_FF_PFMT(AV_PIX_FMT_YUYV422, GF_PIXEL_YUYV) + else CHECK_FF_PFMT(AV_PIX_FMT_NV12, GF_PIXEL_NV12) + else CHECK_FF_PFMT(AV_PIX_FMT_NV21, GF_PIXEL_NV21) + else CHECK_FF_PFMT(AV_PIX_FMT_0RGB, GF_PIXEL_XRGB) + else CHECK_FF_PFMT(AV_PIX_FMT_RGB0, GF_PIXEL_RGBX) + else CHECK_FF_PFMT(AV_PIX_FMT_0BGR, GF_PIXEL_XBGR) + else CHECK_FF_PFMT(AV_PIX_FMT_BGR0, GF_PIXEL_BGRX) + else { + GF_LOG(GF_LOG_WARNING, ctx->log_class, ("[%s] Unsupported pixel format %d\n", ctx->fname, codec->pix_fmt)); + } + gf_filter_pid_set_property(pid, GF_PROP_PID_PIXFMT, &PROP_UINT( pfmt) ); + +#undef CHECK_FF_PFMT + } + + + if (codec->sample_fmt>0) { + u32 sfmt = 0; + switch (codec->sample_fmt) { + case AV_SAMPLE_FMT_U8: sfmt = GF_AUDIO_FMT_U8; break; + case AV_SAMPLE_FMT_S16: sfmt = GF_AUDIO_FMT_S16; break; + case AV_SAMPLE_FMT_S32: sfmt = GF_AUDIO_FMT_S32; break; + case AV_SAMPLE_FMT_FLT: sfmt = GF_AUDIO_FMT_FLT; break; + case AV_SAMPLE_FMT_DBL: sfmt = GF_AUDIO_FMT_DBL; break; + case AV_SAMPLE_FMT_U8P: sfmt = GF_AUDIO_FMT_U8P; break; + case AV_SAMPLE_FMT_S16P: sfmt = GF_AUDIO_FMT_S16P; break; + case AV_SAMPLE_FMT_S32P: sfmt = GF_AUDIO_FMT_S32P; break; + case AV_SAMPLE_FMT_FLTP: sfmt = GF_AUDIO_FMT_FLTP; break; + case AV_SAMPLE_FMT_DBLP: sfmt = GF_AUDIO_FMT_DBLP; break; + default: + GF_LOG(GF_LOG_WARNING, ctx->log_class, ("[%s] Unsupported sample format %d\n", ctx->fname, codec->sample_fmt)); + } + gf_filter_pid_set_property(pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT( sfmt) ); + } + + if (codec->bit_rate) + gf_filter_pid_set_property(pid, GF_PROP_PID_BITRATE, &PROP_UINT( (u32) codec->bit_rate ) ); + + gf_filter_pid_set_property(pid, GF_PROP_PID_URL, &PROP_STRING(ctx->demuxer->filename)); + } + + if (!nb_a && !nb_v +#ifdef FF_SUB_SUPPORT + && !nb_t +#endif + ) + return GF_NOT_SUPPORTED; + + return GF_OK; +} + +static int ffavio_read_packet(void *opaque, uint8_t *buf, int buf_size) +{ + GF_FFDemuxCtx *ctx = (GF_FFDemuxCtx *)opaque; + return (int) gf_fread(buf, buf_size, ctx->gfio); +} + +static int64_t ffavio_seek(void *opaque, int64_t offset, int whence) +{ + GF_FFDemuxCtx *ctx = (GF_FFDemuxCtx *)opaque; + return (int64_t) gf_fseek(ctx->gfio, offset, whence); +} + +static GF_Err ffdmx_initialize(GF_Filter *filter) +{ + GF_FFDemuxCtx *ctx = gf_filter_get_udta(filter); + GF_Err e; + s32 res; + char *ext; + const char *url; + AVInputFormat *av_in = NULL; + ctx->fname = "FFDmx"; + ctx->log_class = GF_LOG_CONTAINER; + + ffmpeg_setup_logs(ctx->log_class); + + ctx->initialized = GF_TRUE; +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + ffdmx_update_arg(filter, NULL, NULL); + } +#endif + if (!ctx->src) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] Missing file name, cannot open\n", ctx->fname)); + return GF_SERVICE_ERROR; + } + /*some extensions not supported by ffmpeg, overload input format*/ + ext = strrchr(ctx->src, '.'); + if (ext) { + if (!stricmp(ext+1, "cmp")) av_in = av_find_input_format("m4v"); + } + + GF_LOG(GF_LOG_DEBUG, ctx->log_class, ("[%s] opening file %s - av_in %08x\n", ctx->fname, ctx->src, av_in)); + + ctx->demuxer = avformat_alloc_context(); + ffmpeg_set_mx_dmx_flags(ctx->options, ctx->demuxer); + + url = ctx->src; + if (!strncmp(ctx->src, "gfio://", 7)) { + ctx->gfio = gf_fopen(ctx->src, "rb"); + if (!ctx->gfio) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] Failed to open %s\n", ctx->fname, ctx->src)); + return GF_URL_ERROR; + } + ctx->avio_ctx_buffer = av_malloc(ctx->block_size); + if (!ctx->avio_ctx_buffer) { + return GF_OUT_OF_MEM; + } + ctx->avio_ctx = avio_alloc_context(ctx->avio_ctx_buffer, ctx->block_size, + 0, ctx, &ffavio_read_packet, NULL, &ffavio_seek); + + if (!ctx->avio_ctx) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] Failed to create AVIO context for %s\n", ctx->fname, ctx->src)); + return GF_OUT_OF_MEM; + } + ctx->demuxer->pb = ctx->avio_ctx; + url = gf_fileio_translate_url(ctx->src); + } + + res = avformat_open_input(&ctx->demuxer, url, av_in, ctx->options ? &ctx->options : NULL); + + switch (res) { + case 0: + e = GF_OK; + break; + case AVERROR_INVALIDDATA: + e = GF_NON_COMPLIANT_BITSTREAM; + break; + case AVERROR_DEMUXER_NOT_FOUND: + case -ENOENT: + e = GF_URL_ERROR; + break; + case AVERROR_EOF: + e = GF_EOS; + break; + default: + e = GF_NOT_SUPPORTED; + break; + } + if (e) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] Fail to open %s - error %s\n", ctx->fname, ctx->src, av_err2str(res) )); + avformat_close_input(&ctx->demuxer); + avformat_free_context(ctx->demuxer); + return e; + } + + + ffmpeg_report_unused_options(filter, ctx->options); + + res = avformat_find_stream_info(ctx->demuxer, ctx->options ? &ctx->options : NULL); + if (res <0) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] cannot locate streams - error %s\n", ctx->fname, av_err2str(res))); + e = GF_NOT_SUPPORTED; + avformat_close_input(&ctx->demuxer); + avformat_free_context(ctx->demuxer); + return e; + } + GF_LOG(GF_LOG_DEBUG, ctx->log_class, ("[%s] file %s opened - %d streams\n", ctx->fname, ctx->src, ctx->demuxer->nb_streams)); + + return ffdmx_init_common(filter, ctx, GF_FALSE); +} + + +static Bool ffdmx_process_event(GF_Filter *filter, const GF_FilterEvent *com) +{ + GF_FFDemuxCtx *ctx = gf_filter_get_udta(filter); + + switch (com->base.type) { + case GF_FEVT_PLAY: + if (!ctx->nb_playing && !ctx->raw_data + && (ctx->stop_seen || (com->play.start_range>0)) + ) { + int res = av_seek_frame(ctx->demuxer, -1, (s64) (AV_TIME_BASE*com->play.start_range), AVSEEK_FLAG_BACKWARD); + if (res<0) { + GF_LOG(GF_LOG_WARNING, ctx->log_class, ("[%s] Fail to seek %s to %g - error %s\n", ctx->fname, ctx->src, com->play.start_range, av_err2str(res) )); + } + } + ctx->nb_playing++; + ctx->stop_seen = GF_FALSE; + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + if (ctx->nb_playing) { + ctx->nb_playing--; + if (!ctx->nb_playing) + ctx->stop_seen = GF_TRUE; + } + //cancel event + return GF_TRUE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +static GF_FilterProbeScore ffdmx_probe_url(const char *url, const char *mime) +{ + if (!strncmp(url, "video://", 8)) return GF_FPROBE_NOT_SUPPORTED; + if (!strncmp(url, "audio://", 8)) return GF_FPROBE_NOT_SUPPORTED; + if (!strncmp(url, "av://", 5)) return GF_FPROBE_NOT_SUPPORTED; + if (!strncmp(url, "pipe://", 7)) return GF_FPROBE_NOT_SUPPORTED; + return GF_FPROBE_MAYBE_SUPPORTED; +} + + +static const char *ffdmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + int ffscore; + AVInputFormat *probe_fmt; + AVProbeData pb; + + av_register_all(); + + + memset(&pb, 0, sizeof(AVProbeData)); + //not setting this crashes some probers in ffmpeg + pb.filename = ""; + if (size <= AVPROBE_PADDING_SIZE) { + pb.buf = gf_malloc(sizeof(char)*(size+AVPROBE_PADDING_SIZE) ); + memcpy(pb.buf, data, sizeof(char)*size); + pb.buf_size = size; + probe_fmt = av_probe_input_format3(&pb, GF_FALSE, &ffscore); + gf_free(pb.buf); + } else { + pb.buf = (char *) data; + pb.buf_size = size - AVPROBE_PADDING_SIZE; + probe_fmt = av_probe_input_format3(&pb, GF_FALSE, &ffscore); + } + + if (!probe_fmt) return NULL; + if (probe_fmt->mime_type) { + //TODO try to refine based on ffprobe score + *score = GF_FPROBE_MAYBE_SUPPORTED; + return probe_fmt->mime_type; + } + return NULL; +} + +#define OFFS(_n) #_n, offsetof(GF_FFDemuxCtx, _n) + +static const GF_FilterCapability FFDmxCaps[] = +{ + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), +}; + + +GF_FilterRegister FFDemuxRegister = { + .name = "ffdmx", + .version=LIBAVFORMAT_IDENT, + GF_FS_SET_DESCRIPTION("FFMPEG demuxer") + GF_FS_SET_HELP("See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details") + .private_size = sizeof(GF_FFDemuxCtx), + SETCAPS(FFDmxCaps), + .initialize = ffdmx_initialize, + .finalize = ffdmx_finalize, + .process = ffdmx_process, + .update_arg = ffdmx_update_arg, + .probe_url = ffdmx_probe_url, + .probe_data = ffdmx_probe_data, + .process_event = ffdmx_process_event, + .flags = GF_FS_REG_META, +}; + + +static const GF_FilterArgs FFDemuxArgs[] = +{ + { OFFS(src), "location of source content", GF_PROP_NAME, NULL, NULL, 0}, + { "*", -1, "any possible options defined for AVFormatContext and sub-classes. See `gpac -hx ffdmx` and `gpac -hx ffdmx:*`", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_META}, + {0} +}; + +const GF_FilterRegister *ffdmx_register(GF_FilterSession *session) +{ + ffmpeg_build_register(session, &FFDemuxRegister, FFDemuxArgs, 2, FF_REG_TYPE_DEMUX); + return &FFDemuxRegister; +} + + + +static GF_Err ffavin_initialize(GF_Filter *filter) +{ + s32 res, i; + Bool has_a, has_v; + char szPatchedName[1024]; + const char *dev_name=NULL; + const char *default_fmt=NULL; + AVInputFormat *dev_fmt=NULL; + GF_FFDemuxCtx *ctx = gf_filter_get_udta(filter); + Bool wants_audio = GF_FALSE; + Bool wants_video = GF_FALSE; + ctx->fname = "FFAVIn"; + ctx->log_class = GF_LOG_MMIO; + + ffmpeg_setup_logs(ctx->log_class); + + avdevice_register_all(); + ctx->initialized = GF_TRUE; + if (!ctx->src) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] No source URL specified, expecting video://, audio:/ or av://\n", ctx->fname)); + return GF_SERVICE_ERROR; + } + default_fmt = ctx->fmt; + if (!default_fmt) { +#ifdef WIN32 + default_fmt = "dshow"; +#elif defined(__DARWIN) || defined(__APPLE__) + default_fmt = "avfoundation"; +#else + default_fmt = "video4linux2"; +#endif + } + + if (default_fmt) { + dev_fmt = av_find_input_format(default_fmt); + if (dev_fmt == NULL) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] Cannot find the input format %s\n", ctx->fname, ctx->fmt)); + } +#if LIBAVCODEC_VERSION_MAJOR >= 58 + else if (dev_fmt->priv_class->category!=AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s]] %s is not a video input device\n", ctx->fname, ctx->fmt)); + dev_fmt = NULL; + } +#else + //not supported for old FFMPE versions +#endif + } +#if (LIBAVCODEC_VERSION_MAJOR >= 58) && (LIBAVCODEC_VERSION_MINOR>=20) + if (!dev_fmt) { + while (1) { + dev_fmt = av_input_video_device_next(dev_fmt); + if (!dev_fmt) break; + if (!dev_fmt || !dev_fmt->priv_class) continue; + if ((dev_fmt->priv_class->category != AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT) && (dev_fmt->priv_class->category != AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT)) + continue; + + //listing devices is on its way of implementation in ffmpeg ... for now break at first provider + break; + } + } +#endif + if (!dev_fmt) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] No input format specified\n", ctx->fname, ctx->fmt)); + return GF_BAD_PARAM; + } + + dev_name = ctx->dev; + + if (!strncmp(ctx->src, "video://", 8)) wants_video = GF_TRUE; + else if (!strncmp(ctx->src, "audio://", 8)) wants_audio = GF_TRUE; + else if (!strncmp(ctx->src, "av://", 5)) wants_video = wants_audio = GF_TRUE; + + szPatchedName[0]=0; + +#if defined(__DARWIN) || defined(__APPLE__) + if (!strncmp(dev_name, "screen", 6)) { + strcpy(szPatchedName, "Capture screen "); + strcat(szPatchedName, dev_name+6); + dev_name = (char *) szPatchedName; + } +#endif + + if (wants_video && wants_audio && !strcmp(dev_name, "0")) { + strcpy(szPatchedName, "0:0"); + dev_name = (char *) szPatchedName; + } +#if defined(__APPLE__) && !defined(GPAC_CONFIG_IOS) + else if (!strncmp(dev_fmt->priv_class->class_name, "AVFoundation", 12) && wants_audio) { + if (ctx->dev[0] != ':') { + strcpy(szPatchedName, ":"); + strcat(szPatchedName, ctx->dev); + dev_name = (char *) szPatchedName; + } + } +#endif + + /* Open video */ + ctx->demuxer = avformat_alloc_context(); + ffmpeg_set_mx_dmx_flags(ctx->options, ctx->demuxer); + + res = avformat_open_input(&ctx->demuxer, dev_name, dev_fmt, &ctx->options); + if ( (res < 0) && !stricmp(ctx->dev, "screen-capture-recorder") ) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] Buggy screen capture input (open failed with code %d), retrying without specifying resolution\n", ctx->fname, res)); + av_dict_set(&ctx->options, "video_size", NULL, 0); + res = avformat_open_input(&ctx->demuxer, ctx->dev, dev_fmt, &ctx->options); + } + + if (res < 0) { + if (ctx->options) { + av_dict_free(&ctx->options); + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] Error %d opening input - retrying without options\n", ctx->fname, res)); + res = avformat_open_input(&ctx->demuxer, dev_name, dev_fmt, NULL); + } + if (res<0) { + av_dict_set(&ctx->options, "framerate", "30", 0); + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] Error %d opening input - retrying with 30 fps\n", ctx->fname, res)); + res = avformat_open_input(&ctx->demuxer, dev_name, dev_fmt, &ctx->options); + if (res < 0) { + av_dict_set(&ctx->options, "framerate", "25", 0); + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] Error %d opening input - retrying with 25 fps\n", ctx->fname, res)); + res = avformat_open_input(&ctx->demuxer, dev_name, dev_fmt, &ctx->options); + } + } + } + + if (res < 0) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] Cannot open device %s:%s\n", ctx->fname, dev_fmt->priv_class->class_name, ctx->dev)); + return -1; + } + + ffmpeg_report_unused_options(filter, ctx->options); + + av_dump_format(ctx->demuxer, 0, ctx->dev, 0); + ctx->raw_data = GF_TRUE; + ctx->audio_idx = ctx->video_idx = -1; + + res = avformat_find_stream_info(ctx->demuxer, ctx->options ? &ctx->options : NULL); + + if (res <0) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] cannot locate streams - error %s\n", ctx->fname, av_err2str(res))); + return GF_NOT_SUPPORTED; + } + + //check we have the stream we want + has_a = has_v = GF_FALSE; + for (i = 0; (u32) i < ctx->demuxer->nb_streams; i++) { + switch(ctx->demuxer->streams[i]->codec->codec_type) { + case AVMEDIA_TYPE_AUDIO: + has_a = GF_TRUE; + break; + case AVMEDIA_TYPE_VIDEO: + has_v = GF_TRUE; + break; + default: + break; + } + } + if (wants_audio && !has_a) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] No audio stream in input device\n", ctx->fname)); + return GF_NOT_SUPPORTED; + } + if (wants_video && !has_v) { + GF_LOG(GF_LOG_ERROR, ctx->log_class, ("[%s] No video stream in input device\n", ctx->fname)); + return GF_NOT_SUPPORTED; + } + ctx->probe_frames = ctx->probes; + if (has_v && ctx->probes) { + ctx->probe_times = gf_malloc(sizeof(u64) * ctx->probes); + memset(ctx->probe_times, 0, sizeof(u64) * ctx->probes); + //we probe timestamps in either modes because timestamps of first frames are sometimes off + ctx->probe_frames = 0; + } + + GF_LOG(GF_LOG_INFO, ctx->log_class, ("[%s] device %s:%s opened - %d streams\n", ctx->fname, dev_fmt->priv_class->class_name, ctx->dev, ctx->demuxer->nb_streams)); + + ctx->copy_audio = ctx->copy_video = GF_FALSE; + switch (ctx->copy) { + case COPY_NO: + break; + case COPY_A: + ctx->copy_audio = GF_TRUE; + break; + case COPY_V: + ctx->copy_video = GF_TRUE; + break; + default: + ctx->copy_audio = ctx->copy_video = GF_TRUE; + break; + } + if (wants_audio && wants_video) { + if (!ctx->copy_audio || !ctx->copy_video) { + GF_LOG(GF_LOG_WARNING, ctx->log_class, ("[%s] using muxed capture av:// without copy on %s, this might introduce packet losses due to blocking modes or delayed consumption of the frames. If experiencing problems, either set [-copy]() to `AV` or consider using two filters video:// and audio://\n", ctx->fname, (!ctx->copy_audio && !ctx->copy_video) ? "audio and video streams" : ctx->copy_video ? "audio stream" : "video stream")); + } + } + return ffdmx_init_common(filter, ctx, GF_TRUE); +} + +static GF_FilterProbeScore ffavin_probe_url(const char *url, const char *mime) +{ + if (!strncmp(url, "video://", 8)) return GF_FPROBE_MAYBE_SUPPORTED; + if (!strncmp(url, "audio://", 8)) return GF_FPROBE_MAYBE_SUPPORTED; + if (!strncmp(url, "av://", 5)) return GF_FPROBE_MAYBE_SUPPORTED; + return GF_FPROBE_NOT_SUPPORTED; +} + +static const GF_FilterCapability FFAVInCaps[] = +{ + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +GF_FilterRegister FFAVInRegister = { + .name = "ffavin", + .version = LIBAVDEVICE_IDENT, + GF_FS_SET_DESCRIPTION("FFMPEG AV Capture") + GF_FS_SET_HELP("Typical classes are `dshow` on windows, `avfoundation` on OSX, `video4linux2` or `x11grab` on linux\n\n"\ + "Typical device name can be the webcam name:\n"\ + "`FaceTime HD Camera` on OSX, device name on windows, `/dev/video0` on linux\n"\ + "`screen-capture-recorder`, see http://screencapturer.sf.net/ on windows\n"\ + "`Capture screen 0` on OSX (0=first screen), or `screenN` for short\n"\ + "X display name (eg `:0.0`) on linux"\ + "\n"\ + "See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details") + .private_size = sizeof(GF_FFDemuxCtx), + SETCAPS(FFAVInCaps), + .initialize = ffavin_initialize, + .finalize = ffdmx_finalize, + .process = ffdmx_process, + .update_arg = ffdmx_update_arg, + .probe_url = ffavin_probe_url, + .process_event = ffdmx_process_event, + .flags = GF_FS_REG_META, +}; + + +static const GF_FilterArgs FFAVInArgs[] = +{ + { OFFS(src), "url of device, `video://`, `audio://` or `av://`", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(fmt), "name of device class - see filter help. If not set, defaults to first device class", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(dev), "name of device or index of device - see filter help", GF_PROP_STRING, "0", NULL, 0}, + { OFFS(copy), "set copy mode of raw frames\n" + "- N: frames are only forwarded (shared memory, no copy)\n" + "- A: audio frames are copied, video frames are forwarded\n" + "- V: video frames are copied, audio frames are forwarded\n" + "- AV: all frames are copied" + "", GF_PROP_UINT, "A", "N|A|V|AV", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(sclock), "use system clock (us) instead of device timestamp (for buggy devices)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(probes), "probe a given number of video frames before emitting - this usually helps with bad timing of the first frames", GF_PROP_UINT, "10", "0-100", GF_FS_ARG_HINT_EXPERT}, + { OFFS(block_size), "block size used to read file when using avio context", GF_PROP_UINT, "4096", NULL, GF_FS_ARG_HINT_EXPERT}, + { "*", -1, "any possible options defined for AVInputFormat and AVFormatContext. See `gpac -hx ffavin` and `gpac -hx ffavin:*`", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_META}, + {0} +}; + + +//number of arguments defined above +const int FFAVIN_STATIC_ARGS = (sizeof (FFAVInArgs) / sizeof (GF_FilterArgs)) - 1; + +const GF_FilterRegister *ffavin_register(GF_FilterSession *session) +{ + ffmpeg_build_register(session, &FFAVInRegister, FFAVInArgs, FFAVIN_STATIC_ARGS, FF_REG_TYPE_DEV_IN); + return &FFAVInRegister; +} + +#else + +#include + +const GF_FilterRegister *ffdmx_register(GF_FilterSession *session) +{ + return NULL; +} + +const GF_FilterRegister *ffavin_register(GF_FilterSession *session) +{ + return NULL; +} +#endif diff --git a/src/filters/ff_enc.c b/src/filters/ff_enc.c new file mode 100644 index 0000000..5d1b9b3 --- /dev/null +++ b/src/filters/ff_enc.c @@ -0,0 +1,1460 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018 + * All rights reserved + * + * This file is part of GPAC / ffmpeg encode filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +#ifdef GPAC_HAS_FFMPEG + +#include "ff_common.h" + +#define ENC_BUF_ALLOC_SAFE 10000 + +typedef struct _gf_ffenc_ctx +{ + //opts + Bool all_intra; + char *c, *ffc; + Bool ls; + u32 pfmt; + GF_Fraction fintra; + + //internal data + Bool initialized; + + u32 gop_size; + u32 target_rate; + + AVCodecContext *encoder; + //decode options + AVDictionary *options; + + GF_FilterPid *in_pid, *out_pid; + //media type + u32 type; + u32 timescale; + + u32 nb_frames_out, nb_frames_in; + u64 time_spent; + + Bool low_delay; + + GF_Err (*process)(GF_Filter *filter, struct _gf_ffenc_ctx *ctx); + //gpac one + u32 codecid; + //done flushing encoder (eg sent NULL frames) + u32 flush_done; + //frame used by both video and audio encoder + AVFrame *frame; + + //encoding buffer - we allocate ENC_BUF_ALLOC_SAFE+WxH for the video (some image codecs in ffmpeg require more than WxH for headers), ENC_BUF_ALLOC_SAFE+nb_ch*samplerate for the audio + //this should be enough to hold any lossless compression formats + char *enc_buffer; + u32 enc_buffer_size; + + Bool init_cts_setup; + + //video state + u32 width, height, stride, stride_uv, nb_planes, uv_height; + //ffmpeg one + enum AVPixelFormat pixel_fmt; + u64 cts_first_frame_plus_one; + + //audio state + u32 channels, sample_rate, channel_layout, bytes_per_sample; + //ffmpeg one + u32 sample_fmt; + //we store input audio frame in this buffer untill we have enough data for one encoder frame + //we also store the remaining of a consumed frame here, so that input packet is realeased ASAP + char *audio_buffer; + u32 audio_buffer_size; + u32 samples_in_audio_buffer; + //cts of first byte in frame + u64 first_byte_cts; + Bool planar_audio; + + //shift of TS - ffmpeg may give pkt-> PTS < frame->PTS to indicate discard samples + //we convert back to frame PTS but signal discard samples at the PID level + s32 ts_shift; + + GF_List *src_packets; + + GF_BitStream *sdbs; + + Bool reconfig_pending; + Bool infmt_negociate; + Bool remap_ts; + Bool force_reconfig; + + u32 dsi_crc; + + u32 gpac_pixel_fmt; + u32 gpac_audio_fmt; + + Bool fintra_setup; + u64 orig_ts; + u32 nb_forced; + +} GF_FFEncodeCtx; + +static GF_Err ffenc_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove); + +static GF_Err ffenc_initialize(GF_Filter *filter) +{ + GF_FFEncodeCtx *ctx = (GF_FFEncodeCtx *) gf_filter_get_udta(filter); + ctx->initialized = GF_TRUE; + ctx->src_packets = gf_list_new(); + ctx->sdbs = gf_bs_new((u8*)ctx, 1, GF_BITSTREAM_READ); + + ffmpeg_setup_logs(GF_LOG_CODEC); + return GF_OK; +} + +static void ffenc_finalize(GF_Filter *filter) +{ + GF_FFEncodeCtx *ctx = (GF_FFEncodeCtx *) gf_filter_get_udta(filter); + if (ctx->options) av_dict_free(&ctx->options); + if (ctx->frame) av_frame_free(&ctx->frame); + if (ctx->enc_buffer) gf_free(ctx->enc_buffer); + if (ctx->audio_buffer) gf_free(ctx->audio_buffer); + + while (gf_list_count(ctx->src_packets)) { + GF_FilterPacket *pck = gf_list_pop_back(ctx->src_packets); + gf_filter_pck_unref(pck); + } + gf_list_del(ctx->src_packets); + + if (ctx->encoder) { + avcodec_close(ctx->encoder); + } + if (ctx->sdbs) gf_bs_del(ctx->sdbs); + return; +} + +//TODO add more feedback +static void ffenc_log_video(GF_Filter *filter, struct _gf_ffenc_ctx *ctx, AVPacket *pkt, Bool do_reporting) +{ + Double fps=0; + s32 q=-1; + u8 pictype=0; +#if LIBAVCODEC_VERSION_MAJOR >= 58 + u64 errors[10]; + u32 i; + u8 nb_errors = 0; +#endif + const char *ptype = "U"; + + if (!ctx->ls && !do_reporting) return; + + +#if LIBAVCODEC_VERSION_MAJOR >= 58 + u32 sq_size; + u8 *side_q = av_packet_get_side_data(pkt, AV_PKT_DATA_QUALITY_STATS, &sq_size); + if (side_q) { + gf_bs_reassign_buffer(ctx->sdbs, side_q, sq_size); + q = gf_bs_read_u32_le(ctx->sdbs); + pictype = gf_bs_read_u8(ctx->sdbs); + nb_errors = gf_bs_read_u8(ctx->sdbs); + /*res*/gf_bs_read_u16(ctx->sdbs); + if (nb_errors>10) nb_errors = 10; + for (i=0; isdbs); + } + } +#endif + if (ctx->time_spent) { + fps = ctx->nb_frames_out; + fps *= 1000000; + fps /= ctx->time_spent; + } + switch (pictype) { + case AV_PICTURE_TYPE_I: ptype = "I"; break; + case AV_PICTURE_TYPE_P: ptype = "P"; break; + case AV_PICTURE_TYPE_S: ptype = "S"; break; + case AV_PICTURE_TYPE_SP: ptype = "SP"; break; + case AV_PICTURE_TYPE_B: ptype = "B"; break; + case AV_PICTURE_TYPE_BI: ptype = "B"; break; + } + + if (ctx->ls) { + fprintf(stderr, "[FFEnc] FPS %.02f F %d DTS "LLD" CTS "LLD" Q %02.02f PT %s (F_in %d)", fps, ctx->nb_frames_out, pkt->dts+ctx->ts_shift, pkt->pts+ctx->ts_shift, ((Double)q) / FF_QP2LAMBDA, ptype, ctx->nb_frames_in); +#if LIBAVCODEC_VERSION_MAJOR >= 58 + if (nb_errors) { + fprintf(stderr, "PSNR"); + for (i=0; iwidth * ctx->height * 255.0 * 255.0; + fprintf(stderr, " %02.02f", psnr); + } + } +#endif + fprintf(stderr, "\r"); + } + + if (do_reporting) { + char szStatus[1024]; + sprintf(szStatus, "[FFEnc] FPS %.02f F %d DTS "LLD" CTS "LLD" Q %02.02f PT %s (F_in %d)", fps, ctx->nb_frames_out, pkt->dts+ctx->ts_shift, pkt->pts+ctx->ts_shift, ((Double)q) / FF_QP2LAMBDA, ptype, ctx->nb_frames_in); + gf_filter_update_status(filter, -1, szStatus); + } +} + +static GF_Err ffenc_process_video(GF_Filter *filter, struct _gf_ffenc_ctx *ctx) +{ + AVPacket pkt; + s32 gotpck; + const char *data = NULL; + u32 size=0, i, count, offset, to_copy; + s32 res; + u64 now; + u8 *output; + u32 force_intra = 0; + Bool insert_jp2c = GF_FALSE; + GF_FilterPacket *dst_pck, *src_pck; + GF_FilterPacket *pck; + + if (!ctx->in_pid) return GF_EOS; + + pck = gf_filter_pid_get_packet(ctx->in_pid); + + if (!ctx->encoder) { + if (ctx->infmt_negociate) return GF_OK; + + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFEnc] encoder reconfiguration failed, aborting stream\n")); + gf_filter_pid_set_eos(ctx->out_pid); + return GF_EOS; + } + + if (!pck) { + if (! gf_filter_pid_is_eos(ctx->in_pid)) return GF_OK; + if (ctx->flush_done) return GF_OK; + } + + if (ctx->reconfig_pending) pck = NULL; + + if (pck) data = gf_filter_pck_get_data(pck, &size); + + av_init_packet(&pkt); + pkt.data = (uint8_t*)ctx->enc_buffer; + pkt.size = ctx->enc_buffer_size; + + ctx->frame->pict_type = 0; + ctx->frame->width = ctx->width; + ctx->frame->height = ctx->height; + ctx->frame->format = ctx->pixel_fmt; + + ctx->frame->pict_type = AV_PICTURE_TYPE_NONE; + + //force picture type + if (ctx->all_intra) + ctx->frame->pict_type = AV_PICTURE_TYPE_I; + + //if PCK_FILENUM is set on input, this is a file boundary, force IDR sync + if (pck && gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM)) { + force_intra = 2; + } + //check if we need to force a closed gop + if (pck && (ctx->fintra.den && ctx->fintra.num) && !ctx->force_reconfig) { + u64 cts = gf_filter_pck_get_cts(pck); + if (!ctx->fintra_setup) { + ctx->fintra_setup = GF_TRUE; + ctx->orig_ts = cts; + force_intra = 1; + ctx->nb_forced=1; + } else if (cts < ctx->orig_ts) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[FFEnc] timestamps not increasing monotonuously, reseting forced intra state !\n")); + ctx->orig_ts = cts; + force_intra = 1; + ctx->nb_forced=1; + } else { + u64 ts_diff = cts - ctx->orig_ts; + if (ts_diff * ctx->fintra.den >= ctx->nb_forced * ctx->fintra.num * ctx->timescale) { + force_intra = 1; + ctx->nb_forced++; + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[FFEnc] Forcing IDR at frame %d (CTS %d / %d)\n", ctx->nb_frames_in, cts, ctx->timescale)); + } + } + } + if (force_intra) { + //file switch we force a full reset to force injecting xPS in the stream + //we could also inject them manually but we don't have them !! + if (force_intra==2) { + if (!ctx->force_reconfig) { + ctx->reconfig_pending = GF_TRUE; + ctx->force_reconfig = GF_TRUE; + pck = NULL; + } else { + ctx->force_reconfig = GF_FALSE; + } + } + ctx->frame->pict_type = AV_PICTURE_TYPE_I; + } + + + now = gf_sys_clock_high_res(); + gotpck = 0; + if (pck) { + u32 ilaced; + if (data) { + ctx->frame->data[0] = (u8 *) data; + ctx->frame->linesize[0] = ctx->stride; + if (ctx->nb_planes>1) { + ctx->frame->data[1] = (u8 *) data + ctx->stride * ctx->height; + ctx->frame->linesize[1] = ctx->stride_uv ? ctx->stride_uv : ctx->stride/2; + if (ctx->nb_planes>2) { + ctx->frame->data[2] = (u8 *) ctx->frame->data[1] + ctx->stride_uv * ctx->height/2; + ctx->frame->linesize[2] = ctx->frame->linesize[1]; + } else { + ctx->frame->linesize[2] = 0; + } + } else { + ctx->frame->linesize[1] = 0; + } + } else { + GF_Err e=GF_NOT_SUPPORTED; + GF_FilterFrameInterface *frame_ifce = gf_filter_pck_get_frame_interface(pck); + if (frame_ifce && frame_ifce->get_plane) { + e = frame_ifce->get_plane(frame_ifce, 0, (const u8 **) &ctx->frame->data[0], &ctx->frame->linesize[0]); + if (!e && (ctx->nb_planes>1)) { + e = frame_ifce->get_plane(frame_ifce, 1, (const u8 **) &ctx->frame->data[1], &ctx->frame->linesize[1]); + if (!e && (ctx->nb_planes>2)) { + e = frame_ifce->get_plane(frame_ifce, 1, (const u8 **) &ctx->frame->data[2], &ctx->frame->linesize[2]); + } + } + } + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFEnc] Failed to fetch %sframe data: %s\n", frame_ifce ? "hardware " : "", gf_error_to_string(e) )); + gf_filter_pid_drop_packet(ctx->in_pid); + return e; + } + } + + ilaced = gf_filter_pck_get_interlaced(pck); + if (!ilaced) { + ctx->frame->interlaced_frame = 0; + } else { + ctx->frame->interlaced_frame = 1; + ctx->frame->top_field_first = (ilaced==2) ? 1 : 0; + } + ctx->frame->pts = gf_filter_pck_get_cts(pck); + ctx->frame->pkt_duration = gf_filter_pck_get_duration(pck); + +#define SCALE_TS(_ts) if (_ts != GF_FILTER_NO_TS) { _ts *= ctx->encoder->time_base.den; _ts /= ctx->encoder->time_base.num; _ts /= ctx->timescale; } +#define UNSCALE_TS(_ts) if (_ts != AV_NOPTS_VALUE) { _ts *= ctx->encoder->time_base.num; _ts *= ctx->timescale; _ts /= ctx->encoder->time_base.den; } +#define UNSCALE_DUR(_ts) { _ts *= ctx->encoder->time_base.num; _ts *= ctx->timescale; _ts /= ctx->encoder->time_base.den; } + + //store first frame CTS before rescaling, we use it after rescaling the output packet timing to compute CTS-DTS + if (!ctx->cts_first_frame_plus_one) { + ctx->cts_first_frame_plus_one = 1 + ctx->frame->pts; + } + + if (ctx->remap_ts) { + SCALE_TS(ctx->frame->pts); + + SCALE_TS(ctx->frame->pkt_duration); + } + + ctx->frame->pkt_dts = ctx->frame->pkt_pts = ctx->frame->pts; + + res = avcodec_encode_video2(ctx->encoder, &pkt, ctx->frame, &gotpck); + ctx->nb_frames_in++; + + //keep ref to ource properties + gf_filter_pck_ref_props(&pck); + gf_list_add(ctx->src_packets, pck); + + gf_filter_pid_drop_packet(ctx->in_pid); + + if (ctx->remap_ts) { + UNSCALE_TS(ctx->frame->pts); + UNSCALE_TS(ctx->frame->pkt_duration); + + UNSCALE_TS(pkt.dts); + UNSCALE_TS(pkt.pts); + UNSCALE_DUR(pkt.duration); + } + } else { + res = avcodec_encode_video2(ctx->encoder, &pkt, NULL, &gotpck); + if (!gotpck) { + //done flushing encoder while reconfiguring + if (ctx->reconfig_pending) { + ctx->reconfig_pending = GF_FALSE; + avcodec_close(ctx->encoder); + ctx->encoder = NULL; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[FFEnc] codec flush done, triggering reconfiguration\n")); + return ffenc_configure_pid(filter, ctx->in_pid, GF_FALSE); + } + ctx->flush_done = 1; + gf_filter_pid_set_eos(ctx->out_pid); + return GF_EOS; + } + if (ctx->remap_ts) { + UNSCALE_TS(pkt.dts); + UNSCALE_TS(pkt.pts); + UNSCALE_DUR(pkt.duration); + } + } + now = gf_sys_clock_high_res() - now; + ctx->time_spent += now; + + if (res<0) { + ctx->nb_frames_out++; + return GF_SERVICE_ERROR; + } + + if (!gotpck) { + return GF_OK; + } + + ctx->nb_frames_out++; + if (ctx->init_cts_setup) { + ctx->init_cts_setup = GF_FALSE; + if (ctx->frame->pts != pkt.pts) { + //check shift in PTS + ctx->ts_shift = (s32) ( (s64) ctx->cts_first_frame_plus_one - 1 - (s64) pkt.pts ); + + //check shift in DTS + ctx->ts_shift += (s32) ( (s64) ctx->cts_first_frame_plus_one - 1 - (s64) pkt.dts ); + } + if (ctx->ts_shift) { + s64 shift = ctx->ts_shift; + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_DELAY, &PROP_SINT((s32) shift) ); + } + } + + src_pck = NULL; + count = gf_list_count(ctx->src_packets); + for (i=0; isrc_packets, i); + if (gf_filter_pck_get_cts(src_pck) == pkt.pts) break; + src_pck = NULL; + } + + offset = 0; + to_copy = size = pkt.size; + + + if (ctx->codecid == GF_CODECID_J2K) { + u32 b4cc = GF_4CC(pkt.data[4], pkt.data[5], pkt.data[6], pkt.data[7]); + if (b4cc == GF_4CC('j','P',' ',' ')) { + u32 jp2h_offset = 0; + offset = 12; + while (offset+8 < (u32) pkt.size) { + b4cc = GF_4CC(pkt.data[offset+4], pkt.data[offset+5], pkt.data[offset+6], pkt.data[offset+7]); + if (b4cc == GF_4CC('j','p','2','c')) { + break; + } + if (b4cc == GF_4CC('j','p','2','h')) { + jp2h_offset = offset; + } + offset++; + } + if (jp2h_offset) { + u32 len = pkt.data[jp2h_offset]; + len <<= 8; + len |= pkt.data[jp2h_offset+1]; + len <<= 8; + len |= pkt.data[jp2h_offset+2]; + len <<= 8; + len |= pkt.data[jp2h_offset+3]; + + u32 dsi_crc = gf_crc_32(pkt.data + jp2h_offset + 8, len-8); + if (dsi_crc != ctx->dsi_crc) { + ctx->dsi_crc = dsi_crc; + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA(pkt.data + jp2h_offset + 8, len-8) ); + } + } + size -= offset; + to_copy -= offset; + } else { + size += 8; + + if (!ctx->dsi_crc) { + u8 *dsi; + u32 dsi_len; + GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + gf_bs_write_u32(bs, 14+8); + gf_bs_write_u32(bs, GF_4CC('i','h','d','r')); + gf_bs_write_u32(bs, ctx->height); + gf_bs_write_u32(bs, ctx->width); + gf_bs_write_u16(bs, ctx->nb_planes); + gf_bs_write_u8(bs, gf_pixel_get_bytes_per_pixel(ctx->gpac_pixel_fmt)); + gf_bs_write_u8(bs, 7); //COMP + gf_bs_write_u8(bs, 0); + gf_bs_write_u8(bs, 0); + gf_bs_get_content(bs, &dsi, &dsi_len); + gf_bs_del(bs); + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_len) ); + ctx->dsi_crc = 1; + } + } + } + + dst_pck = gf_filter_pck_new_alloc(ctx->out_pid, size, &output); + if (insert_jp2c) { + u32 bsize = pkt.size + 8; + output[0] = (bsize >> 24) & 0xFF; + output[1] = (bsize >> 16) & 0xFF; + output[2] = (bsize >> 8) & 0xFF; + output[3] = (bsize) & 0xFF; + output[4] = 'j'; + output[5] = 'p'; + output[6] = '2'; + output[7] = 'c'; + output += 8; + } + memcpy(output, pkt.data + offset, to_copy); + + if (src_pck) { + gf_filter_pck_merge_properties(src_pck, dst_pck); + gf_list_del_item(ctx->src_packets, src_pck); + gf_filter_pck_unref(src_pck); + } else { + if (pkt.duration) { + gf_filter_pck_set_duration(dst_pck, (u32) pkt.duration); + } else { + gf_filter_pck_set_duration(dst_pck, (u32) ctx->frame->pkt_duration); + } + } + + ffenc_log_video(filter, ctx, &pkt, gf_filter_reporting_enabled(filter)); + + gf_filter_pck_set_cts(dst_pck, pkt.pts + ctx->ts_shift); + gf_filter_pck_set_dts(dst_pck, pkt.dts + ctx->ts_shift); + + //this is not 100% correct since we don't have any clue if this is SAP1/2/3/4 ... + //since we send the output to our reframers we should be fine + if (pkt.flags & AV_PKT_FLAG_KEY) { + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[FFEnc] frame %d is SAP\n", ctx->nb_frames_out)); + } + else + gf_filter_pck_set_sap(dst_pck, 0); + +#if LIBAVCODEC_VERSION_MAJOR >= 58 + if (pkt.flags & AV_PKT_FLAG_DISPOSABLE) { + gf_filter_pck_set_dependency_flags(dst_pck, 0x8); + } +#endif + gf_filter_pck_send(dst_pck); + + //we're in final flush, request a process task until all frames flushe + //we could recursiveley call ourselves, same result + if (!pck) { + gf_filter_post_process_task(filter); + } + return GF_OK; +} + + + +static void ffenc_audio_append_samples(struct _gf_ffenc_ctx *ctx, const u8 *data, u32 size, u32 sample_offset, u32 nb_samples) +{ + u8 *dst; + u32 f_idx, s_idx; + u32 i, bytes_per_chan, src_frame_size; + + if (!ctx->audio_buffer || !data) + return; + + if (!ctx->planar_audio) { + u32 offset_src = sample_offset * ctx->bytes_per_sample; + u32 offset_dst = ctx->samples_in_audio_buffer * ctx->bytes_per_sample; + u32 len = nb_samples * ctx->bytes_per_sample; + memcpy(ctx->audio_buffer + offset_dst, data + offset_src, sizeof(u8)*len); + ctx->samples_in_audio_buffer += nb_samples; + return; + } + + bytes_per_chan = ctx->bytes_per_sample / ctx->channels; + src_frame_size = size / ctx->bytes_per_sample; + assert(ctx->samples_in_audio_buffer + nb_samples <= (u32) ctx->audio_buffer_size); + assert(sample_offset + nb_samples <= src_frame_size); + assert(ctx->encoder->frame_size); + + f_idx = ctx->samples_in_audio_buffer / ctx->encoder->frame_size; + s_idx = ctx->samples_in_audio_buffer % ctx->encoder->frame_size; + if (s_idx) { + assert(s_idx + nb_samples <= (u32) ctx->encoder->frame_size); + } + dst = ctx->audio_buffer + (f_idx * ctx->channels * ctx->encoder->frame_size + s_idx) * bytes_per_chan; + while (nb_samples) { + const u8 *src; + u32 nb_samples_to_copy = nb_samples; + if (nb_samples_to_copy > (u32) ctx->encoder->frame_size) + nb_samples_to_copy = ctx->encoder->frame_size; + + assert(sample_offsetchannels; i++) { + memcpy(dst, src, sizeof(u8) * nb_samples_to_copy * bytes_per_chan); + + dst += ctx->encoder->frame_size * bytes_per_chan; + src += src_frame_size * bytes_per_chan; + } + ctx->samples_in_audio_buffer += nb_samples_to_copy; + nb_samples -= nb_samples_to_copy; + sample_offset += nb_samples_to_copy; + } +} + +static GF_Err ffenc_process_audio(GF_Filter *filter, struct _gf_ffenc_ctx *ctx) +{ + AVPacket pkt; + s32 gotpck; + const char *data = NULL; + u32 size=0, nb_copy=0, i, count; + Bool from_internal_buffer_only = GF_FALSE; + s32 res; + u32 nb_samples=0; + u64 ts_diff; + u8 *output; + GF_FilterPacket *dst_pck, *src_pck; + GF_FilterPacket *pck; + + if (!ctx->in_pid) return GF_EOS; + + pck = gf_filter_pid_get_packet(ctx->in_pid); + + if (!ctx->encoder) { + if (ctx->infmt_negociate) return GF_OK; + + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFEnc] encoder reconfiguration failed, aborting stream\n")); + gf_filter_pid_set_eos(ctx->out_pid); + return GF_EOS; + } + + if (!pck) { + if (! gf_filter_pid_is_eos(ctx->in_pid)) return GF_OK; + if (ctx->flush_done) return GF_EOS; + } + + if (ctx->reconfig_pending) pck = NULL; + + if (ctx->encoder->frame_size && (ctx->encoder->frame_size <= (s32) ctx->samples_in_audio_buffer)) { + avcodec_fill_audio_frame(ctx->frame, ctx->channels, ctx->sample_fmt, ctx->audio_buffer, ctx->bytes_per_sample * ctx->encoder->frame_size, 0); + + from_internal_buffer_only = GF_TRUE; + + } else if (pck) { + data = gf_filter_pck_get_data(pck, &size); + if (!data) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[FFEnc] Packet without associated data\n")); + gf_filter_pid_drop_packet(ctx->in_pid); + return GF_OK; + } + + if (!ctx->samples_in_audio_buffer) { + ctx->first_byte_cts = gf_filter_pck_get_cts(pck); + } + + src_pck = pck; + gf_filter_pck_ref_props(&src_pck); + gf_list_add(ctx->src_packets, src_pck); + + nb_samples = size / ctx->bytes_per_sample; + if (ctx->encoder->frame_size && (nb_samples + ctx->samples_in_audio_buffer < (u32) ctx->encoder->frame_size)) { + ffenc_audio_append_samples(ctx, data, size, 0, nb_samples); + gf_filter_pid_drop_packet(ctx->in_pid); + return GF_OK; + } + + if (ctx->encoder->frame_size) { + nb_copy = ctx->encoder->frame_size - ctx->samples_in_audio_buffer; + ffenc_audio_append_samples(ctx, data, size, 0, nb_copy); + + ctx->frame->nb_samples = ctx->encoder->frame_size; + res = avcodec_fill_audio_frame(ctx->frame, ctx->channels, ctx->sample_fmt, ctx->audio_buffer, ctx->encoder->frame_size*ctx->bytes_per_sample, 0); + + } else { + ctx->frame->nb_samples = size / ctx->bytes_per_sample; + res = avcodec_fill_audio_frame(ctx->frame, ctx->channels, ctx->sample_fmt, data, size, 0); + data = NULL; + size = 0; + } + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFEnc] Error filling raw audio frame: %s\n", av_err2str(res) )); + //discard + ctx->samples_in_audio_buffer = 0; + if (data && (nb_samples > nb_copy)) { + ffenc_audio_append_samples(ctx, data, size, nb_copy, nb_samples - nb_copy); + ts_diff = nb_copy; + ts_diff *= ctx->timescale; + ts_diff /= ctx->sample_rate; + ctx->first_byte_cts = gf_filter_pck_get_cts(pck) + ts_diff; + } + gf_filter_pid_drop_packet(ctx->in_pid); + return GF_SERVICE_ERROR; + } + } + + av_init_packet(&pkt); + pkt.data = (uint8_t*)ctx->enc_buffer; + pkt.size = ctx->enc_buffer_size; + + ctx->frame->nb_samples = ctx->encoder->frame_size; + ctx->frame->format = ctx->encoder->sample_fmt; + ctx->frame->channels = ctx->encoder->channels; + ctx->frame->channel_layout = ctx->encoder->channel_layout; + gotpck = 0; + if (pck) { + ctx->frame->pkt_dts = ctx->frame->pkt_pts = ctx->frame->pts = ctx->first_byte_cts; + res = avcodec_encode_audio2(ctx->encoder, &pkt, ctx->frame, &gotpck); + } else { + res = avcodec_encode_audio2(ctx->encoder, &pkt, NULL, &gotpck); + if (!gotpck) { + //done flushing encoder while reconfiguring + if (ctx->reconfig_pending) { + ctx->reconfig_pending = GF_FALSE; + avcodec_close(ctx->encoder); + ctx->encoder = NULL; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[FFEnc] codec flush done, triggering reconfiguration\n")); + return ffenc_configure_pid(filter, ctx->in_pid, GF_FALSE); + } + ctx->flush_done = 1; + gf_filter_pid_set_eos(ctx->out_pid); + return GF_EOS; + } + } + + if (from_internal_buffer_only) { + //avcodec_fill_audio_frame does not perform copy, so make sure we discard internal buffer AFTER we encode + u32 offset, len, nb_samples_to_drop; + + //we always drop a complete encoder frame size, so same code for planar and packed + nb_samples_to_drop = ctx->encoder->frame_size; + + if (ctx->samples_in_audio_buffer > nb_samples_to_drop) { + offset = nb_samples_to_drop * ctx->bytes_per_sample; + len = (ctx->samples_in_audio_buffer - nb_samples_to_drop) * ctx->bytes_per_sample; + memmove(ctx->audio_buffer, ctx->audio_buffer + offset, sizeof(u8)*len); + ctx->samples_in_audio_buffer -= nb_samples_to_drop; + } else { + ctx->samples_in_audio_buffer = 0; + } + + } + + //increase timestamp + ts_diff = ctx->frame->nb_samples; + if (ctx->timescale!=ctx->sample_rate) { + ts_diff *= ctx->timescale; + ts_diff /= ctx->sample_rate; + } + ctx->first_byte_cts += ts_diff; + + if (pck && !from_internal_buffer_only) { + ctx->samples_in_audio_buffer = 0; + if (nb_samples > nb_copy) { + ffenc_audio_append_samples(ctx, data, size, nb_copy, nb_samples - nb_copy); + } + gf_filter_pid_drop_packet(ctx->in_pid); + } + + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFEnc] Error encoding frame: %s\n", av_err2str(res) )); + return GF_SERVICE_ERROR; + } + if (!gotpck) { + return GF_OK; + } + dst_pck = gf_filter_pck_new_alloc(ctx->out_pid, pkt.size, &output); + memcpy(output, pkt.data, pkt.size); + + if (ctx->init_cts_setup) { + ctx->init_cts_setup = GF_FALSE; + if (ctx->frame->pts != pkt.pts) { + ctx->ts_shift = (s32) ( (s64) ctx->frame->pts - (s64) pkt.pts ); + } +// if (ctx->ts_shift) { +// s64 shift = ctx->ts_shift; +// gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_DELAY, &PROP_SINT((s32) -shift) ); +// } + } + + //try to locate first source packet with cts greater than this packet cts and use it as source for properties + //this is not optimal because we dont produce N for N because of different window coding sizes + src_pck = NULL; + count = gf_list_count(ctx->src_packets); + for (i=0; isrc_packets, i); + acts = gf_filter_pck_get_cts(src_pck); + adur = gf_filter_pck_get_duration(src_pck); + + if ((s64) acts >= pkt.pts) { + break; + } + + if (acts + adur <= (u64) ( pkt.pts + ctx->ts_shift) ) { + gf_list_rem(ctx->src_packets, i); + gf_filter_pck_unref(src_pck); + i--; + count--; + } + src_pck = NULL; + } + if (src_pck) { + gf_filter_pck_merge_properties(src_pck, dst_pck); + gf_list_del_item(ctx->src_packets, src_pck); + gf_filter_pck_unref(src_pck); + } + gf_filter_pck_set_cts(dst_pck, pkt.pts + ctx->ts_shift); + gf_filter_pck_set_dts(dst_pck, pkt.dts + ctx->ts_shift); + //this is not 100% correct since we don't have any clue if this is SAP1/4 (roll info missing) + if (pkt.flags & AV_PKT_FLAG_KEY) + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + else + gf_filter_pck_set_sap(dst_pck, 0); + + gf_filter_pck_set_duration(dst_pck, (u32) pkt.duration); + + gf_filter_pck_send(dst_pck); + + //we're in final flush, request a process task until all frames flushe + //we could recursiveley call ourselves, same result + if (!pck) { + gf_filter_post_process_task(filter); + } + return GF_OK; +} + +static GF_Err ffenc_process(GF_Filter *filter) +{ + GF_FFEncodeCtx *ctx = (GF_FFEncodeCtx *) gf_filter_get_udta(filter); + if (!ctx->out_pid || gf_filter_pid_would_block(ctx->out_pid)) + return GF_OK; + return ctx->process(filter, ctx); +} + +static void ffenc_copy_pid_props(GF_FFEncodeCtx *ctx) +{ + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->out_pid, ctx->in_pid); + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_DECODER_CONFIG, NULL); + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_CODECID, &PROP_UINT(ctx->codecid) ); + + switch (ctx->codecid) { + case GF_CODECID_AVC: + case GF_CODECID_HEVC: + case GF_CODECID_MPEG4_PART2: + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_UNFRAMED, &PROP_BOOL(GF_TRUE) ); + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_UNFRAMED_FULL_AU, &PROP_BOOL(GF_TRUE) ); + break; + default: + if (ctx->encoder && ctx->encoder->extradata_size && ctx->encoder->extradata) { + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA(ctx->encoder->extradata, ctx->encoder->extradata_size) ); + } + break; + } +} + +static GF_Err ffenc_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + s32 res; + u32 type=0, fftype, ff_codectag=0; + u32 i=0; + u32 change_input_fmt = 0; + const GF_PropertyValue *prop; + const AVCodec *codec=NULL; + const AVCodec *desired_codec=NULL; + u32 codec_id, pfmt, afmt; + GF_FFEncodeCtx *ctx = (GF_FFEncodeCtx *) gf_filter_get_udta(filter); + + //disconnect of src pid (not yet supported) + if (is_remove) { + ctx->in_pid = NULL; + //one in one out, this is simple + if (ctx->out_pid) gf_filter_pid_remove(ctx->out_pid); + return GF_OK; + } + //check our PID: streamtype and codecid + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (!prop) return GF_NOT_SUPPORTED; + + type = prop->value.uint; + switch (type) { + case GF_STREAM_AUDIO: + case GF_STREAM_VISUAL: + break; + default: + return GF_NOT_SUPPORTED; + } + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!prop || prop->value.uint!=GF_CODECID_RAW) return GF_NOT_SUPPORTED; + + //figure out if output was preconfigured during filter chain setup + prop = gf_filter_pid_caps_query(pid, GF_PROP_PID_CODECID); + if (prop) { + ctx->codecid = prop->value.uint; + } else if (!ctx->codecid && ctx->c) { + ctx->codecid = gf_codec_parse(ctx->c); + if (!ctx->codecid) { + codec = avcodec_find_encoder_by_name(ctx->c); + if (codec) + ctx->codecid = ffmpeg_codecid_to_gpac(codec->id); + } + } + //if the codec was set using ffc, get it + if (ctx->ffc) { + desired_codec = avcodec_find_encoder_by_name(ctx->ffc); + } + + if (!ctx->codecid && !desired_codec) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFEnc] No codecid specified\n" )); + return GF_BAD_PARAM; + } + + //initial config or update + if (!ctx->in_pid || (ctx->in_pid==pid)) { + ctx->in_pid = pid; + if (!ctx->type) ctx->type = type; + //no support for dynamic changes of stream types + else if (ctx->type != type) { + return GF_NOT_SUPPORTED; + } + } else { + //only one input pid in ctx + if (ctx->in_pid) return GF_REQUIRES_NEW_INSTANCE; + } + + if (ctx->codecid) { + codec_id = ffmpeg_codecid_from_gpac(ctx->codecid, &ff_codectag); + if (codec_id) { + if (desired_codec && desired_codec->id==codec_id) + codec = desired_codec; + else + codec = avcodec_find_encoder(codec_id); + } + } else { + codec = desired_codec; + } + if (!codec) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFEnc] Cannot find encoder for codec %s\n", gf_codecid_name(ctx->codecid) )); + return GF_NOT_SUPPORTED; + } + codec_id = codec->id; + if (!ctx->codecid) + ctx->codecid = ffmpeg_codecid_to_gpac(codec->id); + + fftype = ffmpeg_stream_type_to_gpac(codec->type); + if (fftype != type) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFEnc] Mismatch between stream type, codec indicates %s but source type is %s\n", gf_stream_type_name(fftype), gf_stream_type_name(type) )); + return GF_NOT_SUPPORTED; + } + + //declare our output pid to make sure we connect the chain + ctx->in_pid = pid; + if (!ctx->out_pid) { + char szCodecName[1000]; + ctx->out_pid = gf_filter_pid_new(filter); + + //to change once we implement on-the-fly codec change + sprintf(szCodecName, "ffenc:%s", codec->name ? codec->name : "unknown"); + gf_filter_set_name(filter, szCodecName); + gf_filter_pid_set_framing_mode(ctx->in_pid, GF_TRUE); + } + if (type==GF_STREAM_AUDIO) { + ctx->process = ffenc_process_audio; + } else { + ctx->process = ffenc_process_video; + } + + ffenc_copy_pid_props(ctx); + if (ctx->target_rate) + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_BITRATE, &PROP_UINT(ctx->target_rate)); + + +#define GET_PROP(_a, _code, _name) \ + prop = gf_filter_pid_get_property(pid, _code); \ + if (!prop) {\ + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[FFEnc] Input %s unknown, waiting for reconfigure\n", _name)); \ + return GF_OK; \ + }\ + _a =prop->value.uint; + + pfmt = afmt = 0; + if (type==GF_STREAM_VISUAL) { + GET_PROP(ctx->width, GF_PROP_PID_WIDTH, "width") + GET_PROP(ctx->height, GF_PROP_PID_HEIGHT, "height") + GET_PROP(pfmt, GF_PROP_PID_PIXFMT, "pixel format") + + prop = gf_filter_pid_caps_query(pid, GF_PROP_PID_STRIDE); + //keep stride and stride_uv to 0 i fnot set, and recompute from pixel format + if (prop) ctx->stride = prop->value.uint; + prop = gf_filter_pid_caps_query(pid, GF_PROP_PID_STRIDE_UV); + if (prop) ctx->stride_uv = prop->value.uint; + } else { + GET_PROP(ctx->sample_rate, GF_PROP_PID_SAMPLE_RATE, "sample rate") + GET_PROP(ctx->channels, GF_PROP_PID_NUM_CHANNELS, "nb channels") + GET_PROP(afmt, GF_PROP_PID_AUDIO_FORMAT, "audio format") + } + + if (ctx->encoder) { + codec_id = ffmpeg_codecid_from_gpac(ctx->codecid, &ff_codectag); + + if (type==GF_STREAM_AUDIO) { + if ((ctx->encoder->codec->id==codec_id) && (ctx->encoder->sample_rate==ctx->sample_rate) && (ctx->encoder->channels==ctx->channels) && (ctx->gpac_audio_fmt == afmt ) ) { + return GF_OK; + } + } else { + if ((ctx->encoder->codec->id==codec_id) && (ctx->encoder->width==ctx->width) && (ctx->encoder->height==ctx->height) && (ctx->gpac_pixel_fmt == pfmt ) ) { + return GF_OK; + } + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[FFEnc] codec reconfiguration, begining flush\n")); + ctx->reconfig_pending = GF_TRUE; + return GF_OK; + } + + if (type==GF_STREAM_VISUAL) { + u32 force_pfmt = AV_PIX_FMT_NONE; + if (ctx->pfmt) { + u32 ff_pfmt = ffmpeg_pixfmt_from_gpac(ctx->pfmt); + i=0; + while (codec->pix_fmts) { + if (codec->pix_fmts[i] == AV_PIX_FMT_NONE) break; + if (codec->pix_fmts[i] == ff_pfmt) { + force_pfmt = ff_pfmt; + break; + } + //handle pixel formats aliases + if (ffmpeg_pixfmt_to_gpac(codec->pix_fmts[i]) == ctx->pfmt) { + force_pfmt = ctx->pixel_fmt; + break; + } + i++; + } + if (force_pfmt == AV_PIX_FMT_NONE) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[FFEnc] Requested source format %s not supported by codec, using default one\n", gf_pixel_fmt_name(ctx->pfmt) )); + } else { + change_input_fmt = force_pfmt; + } + } + ctx->pixel_fmt = ffmpeg_pixfmt_from_gpac(pfmt); + //check pixel format + if (force_pfmt == AV_PIX_FMT_NONE) { + change_input_fmt = AV_PIX_FMT_NONE; + i=0; + while (codec->pix_fmts) { + if (codec->pix_fmts[i] == AV_PIX_FMT_NONE) break; + if (codec->pix_fmts[i] == ctx->pixel_fmt) { + change_input_fmt = ctx->pixel_fmt; + break; + } + //handle pixel formats aliases + if (ffmpeg_pixfmt_to_gpac(codec->pix_fmts[i]) == pfmt) { + ctx->pixel_fmt = change_input_fmt = codec->pix_fmts[i]; + break; + } + i++; + } + if (!ctx->ffc && (change_input_fmt == AV_PIX_FMT_NONE)) { +#if (LIBAVCODEC_VERSION_MAJOR >= 58) && (LIBAVCODEC_VERSION_MINOR>=20) + void *ff_opaque=NULL; +#else + AVCodec *codec_alt = NULL; +#endif + while (1) { +#if (LIBAVCODEC_VERSION_MAJOR >= 58) && (LIBAVCODEC_VERSION_MINOR>=20) + const AVCodec *codec_alt = av_codec_iterate(&ff_opaque); +#else + codec_alt = av_codec_next(codec_alt); +#endif + if (!codec_alt) break; + if (codec_alt==codec) continue; + if (codec_alt->id == codec_id) { + i=0; + while (codec_alt->pix_fmts) { + if (codec_alt->pix_fmts[i] == AV_PIX_FMT_NONE) break; + if (codec_alt->pix_fmts[i] == ctx->pixel_fmt) { + change_input_fmt = ctx->pixel_fmt; + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[FFEnc] Reassigning codec from %s to %s to match pixel format\n", codec->name, codec_alt->name )); + codec = codec_alt; + break; + } + i++; + } + } + } + } + } + + if (ctx->pixel_fmt != change_input_fmt) { + u32 ff_pmft = ctx->pixel_fmt; + + if (force_pfmt == AV_PIX_FMT_NONE) { + ff_pmft = AV_PIX_FMT_NONE; + i=0; + //find a mapped pixel format + while (codec->pix_fmts) { + if (codec->pix_fmts[i] == AV_PIX_FMT_NONE) break; + if (ffmpeg_pixfmt_to_gpac(codec->pix_fmts[i])) { + ff_pmft = codec->pix_fmts[i]; + break; + } + i++; + } + if (ff_pmft == AV_PIX_FMT_NONE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFEnc] Could not find a matching GPAC pixel format for encoder %s\n", codec->name )); + return GF_NOT_SUPPORTED; + } + } else if (ctx->pfmt) { + ff_pmft = ffmpeg_pixfmt_from_gpac(ctx->pfmt); + } + pfmt = ffmpeg_pixfmt_to_gpac(ff_pmft); + gf_filter_pid_negociate_property(ctx->in_pid, GF_PROP_PID_PIXFMT, &PROP_UINT(pfmt) ); + ctx->infmt_negociate = GF_TRUE; + } else { + ctx->infmt_negociate = GF_FALSE; + } + } else { + u32 change_input_sr = 0; + //check audio format + ctx->sample_fmt = ffmpeg_audio_fmt_from_gpac(afmt); + change_input_fmt = 0; + while (codec->sample_fmts) { + if (codec->sample_fmts[i] == AV_SAMPLE_FMT_NONE) break; + if (codec->sample_fmts[i] == ctx->sample_fmt) { + change_input_fmt = ctx->sample_fmt; + break; + } + i++; + } + i=0; + if (!codec->supported_samplerates) + change_input_sr = ctx->sample_rate; + + while (codec->supported_samplerates) { + if (!codec->supported_samplerates[i]) break; + if (codec->supported_samplerates[i]==ctx->sample_rate) { + change_input_sr = ctx->sample_rate; + break; + } + i++; + } + if ((ctx->sample_fmt != change_input_fmt) || (ctx->sample_rate != change_input_sr)) { + if (ctx->sample_fmt != change_input_fmt) { + ctx->sample_fmt = codec->sample_fmts ? codec->sample_fmts[0] : AV_SAMPLE_FMT_S16; + afmt = ffmpeg_audio_fmt_to_gpac(ctx->sample_fmt); + gf_filter_pid_negociate_property(ctx->in_pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(afmt) ); + } + if (ctx->sample_rate != change_input_sr) { + gf_filter_pid_negociate_property(ctx->in_pid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(codec->supported_samplerates[0]) ); + } + ctx->infmt_negociate = GF_TRUE; + } else { + ctx->infmt_negociate = GF_FALSE; + } + } + + //renegociate input, wait for reconfig call + if (ctx->infmt_negociate) return GF_OK; + + ctx->gpac_pixel_fmt = pfmt; + ctx->gpac_audio_fmt = afmt; + ctx->dsi_crc = 0; + + ctx->encoder = avcodec_alloc_context3(codec); + if (! ctx->encoder) return GF_OUT_OF_MEM; + + ctx->encoder->codec_tag = ff_codectag; + if (type==GF_STREAM_VISUAL) { + ctx->encoder->width = ctx->width; + ctx->encoder->height = ctx->height; + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); + if (prop) { + ctx->encoder->sample_aspect_ratio.num = prop->value.frac.num; + ctx->timescale = ctx->encoder->sample_aspect_ratio.den = prop->value.frac.den; + } else { + ctx->encoder->sample_aspect_ratio.num = 1; + ctx->encoder->sample_aspect_ratio.den = 1; + } + //CHECKME: do we need to use 1/FPS ? + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (prop) { + ctx->encoder->time_base.num = 1; + ctx->timescale = ctx->encoder->time_base.den = prop->value.uint; + } + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (prop) { + ctx->encoder->gop_size = prop->value.frac.num / prop->value.frac.den; + ctx->encoder->time_base.num = prop->value.frac.den; + ctx->encoder->time_base.den = prop->value.frac.num; + } + + gf_media_get_reduced_frame_rate(&ctx->encoder->time_base.den, &ctx->encoder->time_base.num); + + if (ctx->low_delay) { + av_dict_set(&ctx->options, "vprofile", "baseline", 0); + av_dict_set(&ctx->options, "preset", "ultrafast", 0); + av_dict_set(&ctx->options, "tune", "zerolatency", 0); + if (ctx->codecid==GF_CODECID_AVC) { + av_dict_set(&ctx->options, "x264opts", "no-mbtree:sliced-threads:sync-lookahead=0", 0); + } +#if LIBAVCODEC_VERSION_MAJOR >= 58 + ctx->encoder->flags |= AV_CODEC_FLAG_LOW_DELAY; +#endif + } + + if (ctx->fintra.den && ctx->fintra.num) { + av_dict_set(&ctx->options, "forced-idr", "1", 0); + } + + //we don't use out of band headers, since x264 in ffmpeg (and likely other) do not output in MP4 format but + //in annexB (extradata only contains SPS/PPS/etc in annexB) + //so we indicate unframed for these codecs and use our own filter for annexB->MP4 + + if (!ctx->frame) + ctx->frame = av_frame_alloc(); + + ctx->enc_buffer_size = ctx->width*ctx->height + ENC_BUF_ALLOC_SAFE; + ctx->enc_buffer = gf_realloc(ctx->enc_buffer, sizeof(char)*ctx->enc_buffer_size); + + gf_pixel_get_size_info(pfmt, ctx->width, ctx->height, NULL, &ctx->stride, &ctx->stride_uv, &ctx->nb_planes, &ctx->uv_height); + + ctx->encoder->pix_fmt = ctx->pixel_fmt; + ctx->init_cts_setup = GF_TRUE; + ctx->frame->format = ctx->encoder->pix_fmt; + } else if (type==GF_STREAM_AUDIO) { + ctx->process = ffenc_process_audio; + + ctx->encoder->sample_rate = ctx->sample_rate; + ctx->encoder->channels = ctx->channels; + + //TODO + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CHANNEL_LAYOUT); + if (prop) { + ctx->encoder->channel_layout = ffmpeg_channel_layout_from_gpac(prop->value.longuint); + } else if (ctx->channels==1) { + ctx->encoder->channel_layout = AV_CH_LAYOUT_MONO; + } else if (ctx->channels==2) { + ctx->encoder->channel_layout = AV_CH_LAYOUT_STEREO; + } + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (prop) { + ctx->encoder->time_base.num = 1; + ctx->encoder->time_base.den = prop->value.uint; + ctx->timescale = prop->value.uint; + } else { + ctx->encoder->time_base.num = 1; + ctx->encoder->time_base.den = ctx->sample_rate; + ctx->timescale = ctx->sample_rate; + } + + //for aac + switch (ctx->codecid) { + case GF_CODECID_AAC_MPEG4: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + av_dict_set(&ctx->options, "strict", "experimental", 0); + break; + } + + if (!ctx->frame) + ctx->frame = av_frame_alloc(); + + ctx->enc_buffer_size = ctx->channels*ctx->sample_rate + ENC_BUF_ALLOC_SAFE; + ctx->enc_buffer = gf_realloc(ctx->enc_buffer, sizeof(char) * ctx->enc_buffer_size); + + ctx->encoder->sample_fmt = ctx->sample_fmt; + ctx->planar_audio = gf_audio_fmt_is_planar(afmt); + ctx->frame->format = ctx->encoder->sample_fmt; + + ctx->audio_buffer_size = ctx->sample_rate; + ctx->audio_buffer = gf_realloc(ctx->audio_buffer, sizeof(char) * ctx->enc_buffer_size); + ctx->bytes_per_sample = ctx->channels * gf_audio_fmt_bit_depth(afmt) / 8; + ctx->init_cts_setup = GF_TRUE; + + switch (ctx->codecid) { + case GF_CODECID_AAC_MPEG4: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + { +#ifndef GPAC_DISABLE_AV_PARSERS + GF_M4ADecSpecInfo acfg; + u8 *dsi; + u32 dsi_len; + memset(&acfg, 0, sizeof(GF_M4ADecSpecInfo)); + acfg.base_object_type = GF_M4A_AAC_LC; + acfg.base_sr = ctx->sample_rate; + acfg.nb_chan = ctx->channels; + acfg.sbr_object_type = 0; + acfg.audioPL = gf_m4a_get_profile(&acfg); + + gf_m4a_write_config(&acfg, &dsi, &dsi_len); + gf_filter_pid_set_property(ctx->out_pid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_len) ); +#endif + + } + break; + } + } + + ffmpeg_set_enc_dec_flags(ctx->options, ctx->encoder); + + if (ctx->all_intra) ctx->encoder->gop_size = 0; + else if (ctx->gop_size) ctx->encoder->gop_size = ctx->gop_size; + + res = avcodec_open2(ctx->encoder, codec, &ctx->options ); + if (res < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFEnc] PID %s failed to open codec context: %s\n", gf_filter_pid_get_name(pid), av_err2str(res) )); + return GF_BAD_PARAM; + } + ctx->remap_ts = (ctx->encoder->time_base.den != ctx->timescale) ? GF_TRUE : GF_FALSE; + + + ffmpeg_report_unused_options(filter, ctx->options); + + ffenc_copy_pid_props(ctx); + return GF_OK; +} + + +static GF_Err ffenc_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *arg_val) +{ + s32 res; + GF_FFEncodeCtx *ctx = gf_filter_get_udta(filter); + + if (!strcmp(arg_name, "global_header")) return GF_OK; + else if (!strcmp(arg_name, "local_header")) return GF_OK; + else if (!strcmp(arg_name, "low_delay")) ctx->low_delay = GF_TRUE; + //remap some options + else if (!strcmp(arg_name, "bitrate") || !strcmp(arg_name, "rate")) arg_name = "b"; +// else if (!strcmp(arg_name, "gop")) arg_name = "g"; + //disable low delay if these options are set + else if (!strcmp(arg_name, "x264opts")) ctx->low_delay = GF_FALSE; + else if (!strcmp(arg_name, "vprofile")) ctx->low_delay = GF_FALSE; + else if (!strcmp(arg_name, "preset")) ctx->low_delay = GF_FALSE; + else if (!strcmp(arg_name, "tune")) ctx->low_delay = GF_FALSE; + + if (!strcmp(arg_name, "g") || !strcmp(arg_name, "gop")) + ctx->gop_size = arg_val->value.string ? atoi(arg_val->value.string) : 25; + + if (!strcmp(arg_name, "b") && arg_val->value.string) { + ctx->target_rate = atoi(arg_val->value.string); + if (strchr(arg_val->value.string, 'm') || strchr(arg_val->value.string, 'M')) + ctx->target_rate *= 1000000; + else if (strchr(arg_val->value.string, 'k') || strchr(arg_val->value.string, 'K')) + ctx->target_rate *= 1000; + } + + //initial parsing of arguments + if (!ctx->initialized) { + const char *arg_val_str; + switch (arg_val->type) { + case GF_PROP_STRING: + arg_val_str = arg_val->value.string; + if (!arg_val_str) arg_val_str = "1"; + res = av_dict_set(&ctx->options, arg_name, arg_val_str, 0); + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFEnc] Failed to set option %s:%s\n", arg_name, arg_val )); + } + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFEnc] Failed to set option %s:%s, unrecognized type %d\n", arg_name, arg_val, arg_val->type )); + return GF_NOT_SUPPORTED; + } + return GF_OK; + } + //updates of arguments, not supported for ffmpeg decoders + return GF_NOT_SUPPORTED; +} + +static const GF_FilterCapability FFEncodeCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + //some video encoding dumps in unframe mode, we declare the pid property at runtime + {0}, + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + +}; + +GF_FilterRegister FFEncodeRegister = { + .name = "ffenc", + .version=LIBAVCODEC_IDENT, + GF_FS_SET_DESCRIPTION("FFMPEG encoder") + GF_FS_SET_HELP("Encodes audio and video streams.\nSee FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details" + "\n" + "Note: if no codec is explicited through [-ffc]() option and no pixel format is given, codecs will be enumerated to find a matching pixel format.\n" + "\n" + "The encoder will force a closed gop boundary at each packet with a `FileNumber` property set.\n" + ) + .private_size = sizeof(GF_FFEncodeCtx), + SETCAPS(FFEncodeCaps), + .initialize = ffenc_initialize, + .finalize = ffenc_finalize, + .configure_pid = ffenc_configure_pid, + .process = ffenc_process, + .update_arg = ffenc_update_arg, + .flags = GF_FS_REG_META, +}; + +#define OFFS(_n) #_n, offsetof(GF_FFEncodeCtx, _n) +static const GF_FilterArgs FFEncodeArgs[] = +{ + { OFFS(c), "codec identifier. Can be any supported GPAC ID or ffmpeg ID or filter subclass name", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(pfmt), "pixel format for input video. When not set, input format is used", GF_PROP_PIXFMT, "none", NULL, 0}, + { OFFS(fintra), "force intra / IDR frames at the given period in sec, eg `fintra=60000/1001` will force an intra every 2 seconds on 29.97 fps video; ignored for audio", GF_PROP_FRACTION, "0", NULL, 0}, + + { OFFS(all_intra), "only produce intra frames", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ls), "log stats", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ffc), "ffmpeg codec name. This allows enforcing a given codec if multiple codecs support the codec ID set (eg aac vs vo_aacenc)", GF_PROP_STRING, NULL, NULL, 0}, + + { "*", -1, "any possible options defined for AVCodecContext and sub-classes. see `gpac -hx ffenc` and `gpac -hx ffenc:*`", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_META}, + {0} +}; + +const int FFENC_STATIC_ARGS = (sizeof (FFEncodeArgs) / sizeof (GF_FilterArgs)) - 1; + +const GF_FilterRegister *ffenc_register(GF_FilterSession *session) +{ + ffmpeg_build_register(session, &FFEncodeRegister, FFEncodeArgs, FFENC_STATIC_ARGS, FF_REG_TYPE_ENCODE); + return &FFEncodeRegister; +} + + +#else +#include +const GF_FilterRegister *ffenc_register(GF_FilterSession *session) +{ + return NULL; +} + +#endif //GPAC_HAS_FFMPEG + diff --git a/src/filters/ff_mx.c b/src/filters/ff_mx.c new file mode 100644 index 0000000..105baef --- /dev/null +++ b/src/filters/ff_mx.c @@ -0,0 +1,1037 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom Paris 2019-2020 + * All rights reserved + * + * This file is part of GPAC / ffmpeg muxer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include + +#ifdef GPAC_HAS_FFMPEG + +#include "ff_common.h" +#include + +#if (LIBAVFORMAT_VERSION_MAJOR <= 56) +#undef GPAC_HAS_FFMPEG +#endif + +#endif + +#ifdef GPAC_HAS_FFMPEG + +typedef struct +{ + AVStream *stream; + Bool ts_rescale; + AVRational in_scale; + Bool in_seg_flush; + u32 cts_shift; + + Bool suspended; +} GF_FFMuxStream; + +enum{ + FFMX_STATE_ALLOC=0, + FFMX_STATE_AVIO_OPEN, + FFMX_STATE_HDR_DONE, + FFMX_STATE_TRAILER_DONE, + FFMX_STATE_EOS, + FFMX_STATE_ERROR +}; + +typedef struct +{ + //options + char *dst, *mime, *ffmt; + Double start, speed; + u32 block_size; + Bool interleave, nodisc, ffiles, noinit; + + AVFormatContext *muxer; + //decode options + AVDictionary *options; + + GF_List *streams; + + //0 not created, 1 created, header written, 2 finalized, trailer written, 3: end of stream + u32 status; + + Bool dash_mode, init_done; + u32 nb_pck_in_seg; + u32 dash_seg_num; + u64 offset_at_seg_start; + + u8 *avio_ctx_buffer; + AVIOContext *avio_ctx; + FILE *gfio; + + + u32 cur_file_idx_plus_one; +} GF_FFMuxCtx; + +static GF_Err ffmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove); + +static GF_Err ffmx_init_mux(GF_Filter *filter, GF_FFMuxCtx *ctx) +{ + u32 i, nb_pids; + int res; + + assert(ctx->status==FFMX_STATE_AVIO_OPEN); + + ctx->status = FFMX_STATE_HDR_DONE; + res = avformat_init_output(ctx->muxer, &ctx->options); + + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Fail to open %s - error %s\n", ctx->dst, av_err2str(res) )); + ctx->status = FFMX_STATE_ERROR; + return GF_NOT_SUPPORTED; + } + + nb_pids = gf_filter_get_ipid_count(filter); + for (i=0; iin_scale.den == st->stream->time_base.den) && (st->in_scale.num == st->stream->time_base.num)) { + st->ts_rescale = GF_FALSE; + } else { + st->ts_rescale = GF_TRUE; + } + } + + res = avformat_write_header(ctx->muxer, &ctx->options); + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Fail to write header for %s - error %s\n", ctx->dst, av_err2str(res) )); + ctx->status = FFMX_STATE_ERROR; + return GF_SERVICE_ERROR; + } + + ffmpeg_report_unused_options(filter, ctx->options); + + return GF_OK; +} + + +static int ffavio_write_packet(void *opaque, uint8_t *buf, int buf_size) +{ + GF_FFMuxCtx *ctx = (GF_FFMuxCtx *)opaque; + return (int) gf_fwrite(buf, buf_size, ctx->gfio); +} + +static int64_t ffavio_seek(void *opaque, int64_t offset, int whence) +{ + GF_FFMuxCtx *ctx = (GF_FFMuxCtx *)opaque; + return (int64_t) gf_fseek(ctx->gfio, offset, whence); +} + +static GF_Err ffmx_open_url(GF_FFMuxCtx *ctx, char *final_name) +{ + const char *dst; + Bool use_gfio=GF_FALSE; + AVOutputFormat *ofmt = ctx->muxer->oformat; + + dst = final_name ? final_name : ctx->dst; + if (!strncmp(dst, "gfio://", 7)) { + use_gfio = GF_TRUE; + } + + if (ofmt) { + //handle local urls some_dir/some_other_dir/file.ext, ffmpeg doesn't create the dirs for us + if (! (ofmt->flags & AVFMT_NOFILE) && gf_url_is_local(dst)) { + FILE *f = gf_fopen(dst, "wb"); + gf_fclose(f); + } + } + + if (use_gfio) { + ctx->gfio = gf_fopen(dst, "wb"); + if (!ctx->gfio) return GF_URL_ERROR; + ctx->avio_ctx_buffer = av_malloc(ctx->block_size); + if (!ctx->avio_ctx_buffer) return GF_OUT_OF_MEM; + + ctx->avio_ctx = avio_alloc_context(ctx->avio_ctx_buffer, ctx->block_size, + 1, ctx, NULL, &ffavio_write_packet, &ffavio_seek); + + if (!ctx->avio_ctx) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Failed to create AVIO context for %s\n", dst)); + return GF_OUT_OF_MEM; + } + ctx->muxer->pb = ctx->avio_ctx; + } else if (!ofmt || !(ofmt->flags & AVFMT_NOFILE) ) { + int res = avio_open2(&ctx->muxer->pb, dst, AVIO_FLAG_WRITE, NULL, &ctx->options); + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Fail to open AVIO context for %s - error %s\n", dst, av_err2str(res) )); + return GF_FILTER_NOT_SUPPORTED; + } + } + ctx->status = FFMX_STATE_AVIO_OPEN; + return GF_OK; +} +static GF_Err ffmx_initialize_ex(GF_Filter *filter, Bool use_templates) +{ + const char *url, *sep; + AVOutputFormat *ofmt; + GF_FFMuxCtx *ctx = (GF_FFMuxCtx *) gf_filter_get_udta(filter); + + ffmpeg_setup_logs(GF_LOG_CONTAINER); + + ctx->muxer = avformat_alloc_context(); + if (!ctx->muxer) return GF_OUT_OF_MEM; + + if (!ctx->streams) + ctx->streams = gf_list_new(); + + url = ctx->dst; + + if (!strncmp(ctx->dst, "gfio://", 7)) { + url = gf_fileio_translate_url(ctx->dst); + } + sep = strchr(url, '$'); + if (sep && strchr(sep+1, '$')) + use_templates = GF_TRUE; + + ofmt = av_guess_format(ctx->ffmt, url, ctx->mime); + //if protocol is present, we may fail at guessing the format + if (!ofmt && !ctx->ffmt) { + u32 len; + char szProto[20]; + char *proto = strstr(url, "://"); + if (!proto) + return GF_FILTER_NOT_SUPPORTED; + szProto[19] = 0; + len = (u32) (proto - url); + if (len>19) len=19; + strncpy(szProto, url, len); + szProto[len] = 0; + ofmt = av_guess_format(szProto, url, ctx->mime); + } + if (!ofmt) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Failed to guess output format for %s, cannot run\n", ctx->dst)); + if (!ctx->ffmt) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Try specifying the format through :ffmt option\n")); + } + return GF_SERVICE_ERROR; + } + ctx->muxer->oformat = ofmt; + + ctx->status = FFMX_STATE_ALLOC; + //templates are used, we need to postpone opening the url until we have a PID and a first packet + if (use_templates) + return GF_OK; + + return ffmx_open_url(ctx, NULL); +} + +static GF_Err ffmx_initialize(GF_Filter *filter) +{ + return ffmx_initialize_ex(filter, GF_FALSE); +} +static GF_Err ffmx_start_seg(GF_Filter *filter, GF_FFMuxCtx *ctx, const char *seg_name) +{ + int i; + int res; + + /*create a new output context, clone all streams from current one and swap output context*/ + if (ctx->ffiles) { + AVFormatContext *segmux; + + res = avformat_alloc_output_context2(&segmux, ctx->muxer->oformat, NULL, NULL); + if (res<0) { + return GF_IO_ERR; + } + segmux->interrupt_callback = ctx->muxer->interrupt_callback; + segmux->max_delay = ctx->muxer->max_delay; + av_dict_copy(&segmux->metadata, ctx->muxer->metadata, 0); + segmux->opaque = ctx->muxer->opaque; + segmux->io_close = ctx->muxer->io_close; + segmux->io_open = ctx->muxer->io_open; + segmux->flags = ctx->muxer->flags; + + for (i=0; i< (s32) ctx->muxer->nb_streams; i++) { + u32 j; + AVStream *st; + AVCodecParameters *ipar, *opar; + + if (!(st = avformat_new_stream(segmux, NULL))) + return GF_OUT_OF_MEM; + ipar = ctx->muxer->streams[i]->codecpar; + opar = st->codecpar; + avcodec_parameters_copy(opar, ipar); + if (!segmux->oformat->codec_tag || + av_codec_get_id (segmux->oformat->codec_tag, ipar->codec_tag) == opar->codec_id || + av_codec_get_tag(segmux->oformat->codec_tag, ipar->codec_id) <= 0) { + opar->codec_tag = ipar->codec_tag; + } else { + opar->codec_tag = 0; + } + st->sample_aspect_ratio = ctx->muxer->streams[i]->sample_aspect_ratio; + st->time_base = ctx->muxer->streams[i]->time_base; + st->avg_frame_rate = ctx->muxer->streams[i]->avg_frame_rate; + + if (ctx->muxer->streams[i]->codecpar->codec_tag == MKTAG('t','m','c','d')) + st->codec->time_base = ctx->muxer->streams[i]->codec->time_base; + + av_dict_copy(&st->metadata, ctx->muxer->streams[i]->metadata, 0); + + /*reassign stream*/ + for (j=0; jstreams); j++) { + GF_FFMuxStream *ast = gf_list_get(ctx->streams, j); + if (ast->stream == ctx->muxer->streams[i]) { + ast->stream = st; + break; + } + } + } + //swap context + avformat_free_context(ctx->muxer); + ctx->muxer = segmux; + } else { + ctx->muxer->io_close(ctx->muxer, ctx->muxer->pb); + ctx->muxer->pb = NULL; + } + +// av_freep(&ctx->muxer->url); +// ctx->muxer->url = av_strdup(seg_name); + strncpy(ctx->muxer->filename, seg_name, 1023); + ctx->muxer->filename[1023]=0; + ctx->offset_at_seg_start = 0; + + if (!(ctx->muxer->oformat->flags & AVFMT_NOFILE)) { + //handle local urls some_dir/some_other_dir/file.ext, ffmpeg doesn't create the dirs for us + if (gf_url_is_local(seg_name)) { + FILE *f = gf_fopen(seg_name, "wb"); + gf_fclose(f); + } + + res = ctx->muxer->io_open(ctx->muxer, &ctx->muxer->pb, /*ctx->muxer->url*/seg_name, AVIO_FLAG_WRITE, NULL); + if (res < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Fail to open new segment %s - error %s\n", seg_name, av_err2str(res) )); + return GF_IO_ERR; + } + } + ctx->muxer->pb->seekable = 0; + + if (ctx->muxer->oformat->priv_class && ctx->muxer->priv_data) + av_opt_set(ctx->muxer->priv_data, "mpegts_flags", "+resend_headers", 0); + + if (ctx->ffiles) { + AVDictionary *options = NULL; + av_dict_copy(&options, ctx->options, 0); + av_dict_set(&options, "fflags", "-autobsf", 0); + res = avformat_write_header(ctx->muxer, &options); + av_dict_free(&options); + if (res < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Fail to configure segment %s - error %s\n", seg_name, av_err2str(res) )); + return GF_IO_ERR; + } + } + ctx->status = FFMX_STATE_HDR_DONE; + return GF_OK; +} + + +static GF_Err ffmx_close_seg(GF_Filter *filter, GF_FFMuxCtx *ctx, Bool send_evt_only) +{ + GF_FilterEvent evt; + GF_FilterPid *pid; + + av_write_frame(ctx->muxer, NULL); + + if (!send_evt_only) { + int res=0; + + if (ctx->ffiles) { + if (ctx->status==FFMX_STATE_HDR_DONE) { + res = av_write_trailer(ctx->muxer); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Invalid state %d for segment %s close\n", ctx->status, /*ctx->muxer->url*/ctx->muxer->filename)); + return GF_SERVICE_ERROR; + } + } + + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Fail to flush segment %s - error %s\n", /*ctx->muxer->url*/ctx->muxer->filename, av_err2str(res) )); + return GF_SERVICE_ERROR; + } + ctx->status = FFMX_STATE_TRAILER_DONE; + } + ctx->nb_pck_in_seg = 0; + + pid = gf_filter_get_ipid(filter, 0); + GF_FEVT_INIT(evt, GF_FEVT_SEGMENT_SIZE, pid); + evt.seg_size.seg_url = NULL; + + //init seg + if (!ctx->init_done) { + evt.seg_size.is_init = GF_TRUE; + ctx->init_done = GF_TRUE; + } else { + evt.seg_size.is_init = GF_FALSE; + } + evt.seg_size.media_range_start = ctx->offset_at_seg_start; + evt.seg_size.media_range_end = ctx->muxer->pb ? ctx->muxer->pb->written-1 : 0; + ctx->offset_at_seg_start = evt.seg_size.media_range_end; + + gf_filter_pid_send_event(pid, &evt); + return GF_OK; +} + +static GF_Err ffmx_process(GF_Filter *filter) +{ + GF_Err e = GF_OK; + GF_FFMuxCtx *ctx = (GF_FFMuxCtx *) gf_filter_get_udta(filter); + u32 nb_done, nb_segs_done, nb_suspended, i, nb_pids = gf_filter_get_ipid_count(filter); + + if (ctx->statusstatus==FFMX_STATE_ALLOC) ? GF_TRUE : GF_FALSE; + + for (i=0; istream->codecpar->codec_type==AVMEDIA_TYPE_VIDEO) { + u8 ilced = gf_filter_pck_get_interlaced(ipck); + if (ilced==2) st->stream->codecpar->field_order = AV_FIELD_BB; + else if (ilced==1) st->stream->codecpar->field_order = AV_FIELD_TT; + else st->stream->codecpar->field_order = AV_FIELD_PROGRESSIVE; + } + else if (st->stream->codecpar->codec_type==AVMEDIA_TYPE_AUDIO) { + st->stream->codecpar->seek_preroll = gf_filter_pck_get_roll_info(ipck); + } + } + if (!all_ready || (ctx->status==FFMX_STATE_ALLOC)) return GF_OK; + + //good to go, finalize mux + ffmx_init_mux(filter, ctx); + } + if (ctx->status==FFMX_STATE_EOS) return GF_EOS; + else if (ctx->status==FFMX_STATE_ERROR) return GF_SERVICE_ERROR; + + nb_segs_done = 0; + nb_done = 0; + nb_suspended = 0; + for (i=0; iin_seg_flush) { + nb_segs_done ++; + continue; + } + if (st->suspended) { + nb_suspended++; + continue; + } + + while (1) { + u64 cts; + int res; + const GF_PropertyValue *p; + u32 sap; + AVPacket ffpck; + GF_FilterPacket *ipck = gf_filter_pid_get_packet(ipid); + if (!ipck) { + if (gf_filter_pid_is_eos(ipid)) { + nb_done++; + } + break; + } + cts = gf_filter_pck_get_cts(ipck); + + if (cts == GF_FILTER_NO_TS) { + p = gf_filter_pck_get_property(ipck, GF_PROP_PCK_EODS); + if (p && p->value.boolean) { + st->in_seg_flush = GF_TRUE; + nb_segs_done ++; + nb_done++; + gf_filter_pid_drop_packet(ipid); + break; + } + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Packet with no CTS assigned, cannot store to track, ignoring\n")); + gf_filter_pid_drop_packet(ipid); + continue; + } + + p = gf_filter_pck_get_property(ipck, GF_PROP_PCK_FILENUM); + if (ctx->dash_mode) { + if (p) { + //flush segment + if (ctx->dash_seg_num && (p->value.uint != ctx->dash_seg_num)) { + st->in_seg_flush = GF_TRUE; + nb_segs_done++; + break; + } + ctx->dash_seg_num = p->value.uint; + //segment done, check if we switch file + p = gf_filter_pck_get_property(ipck, GF_PROP_PCK_FILENAME); + if (p && p->value.string) { + ffmx_close_seg(filter, ctx, GF_FALSE); + ffmx_start_seg(filter, ctx, p->value.string); + } + //otherwise close segment for event processing only + else + ffmx_close_seg(filter, ctx, GF_TRUE); + } + } else if (p) { + if (ctx->cur_file_idx_plus_one == p->value.uint+1) { + } else if (!st->suspended) { + st->suspended = GF_TRUE; + nb_suspended++; + break; + } + } + + av_init_packet(&ffpck); + ffpck.stream_index = st->stream->index; + ffpck.dts = gf_filter_pck_get_dts(ipck); + ffpck.pts = gf_filter_pck_get_cts(ipck); + if (st->cts_shift) ffpck.pts += st->cts_shift; + + if (ffpck.dts > ffpck.pts) { + st->cts_shift = (u32) (ffpck.dts - ffpck.pts); + GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[FFMux] Negative CTS offset -%d found, adjusting offset\n", st->cts_shift)); + ffpck.pts = ffpck.dts; + } + ffpck.duration = gf_filter_pck_get_duration(ipck); + sap = gf_filter_pck_get_sap(ipck); + if (sap==GF_FILTER_SAP_1) ffpck.flags = AV_PKT_FLAG_KEY; + ffpck.data = (u8 *) gf_filter_pck_get_data(ipck, &ffpck.size); + + if (st->ts_rescale) { + av_packet_rescale_ts(&ffpck, st->in_scale, st->stream->time_base); + } + + if (ctx->interleave) { + res = av_interleaved_write_frame(ctx->muxer, &ffpck); + } else { + res = av_write_frame(ctx->muxer, &ffpck); + } + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Fail to write packet to %s - error %s\n", /*ctx->muxer->url*/ctx->muxer->filename, av_err2str(res) )); + e = GF_IO_ERR; + } + + gf_filter_pid_drop_packet(ipid); + ctx->nb_pck_in_seg++; + } + } + + //done writing file + if (nb_suspended && (nb_suspended==nb_pids)) { + av_write_trailer(ctx->muxer); + if (ctx->muxer) avformat_free_context(ctx->muxer); + ctx->muxer = NULL; + ctx->status = FFMX_STATE_ALLOC; + ffmx_initialize_ex(filter, GF_TRUE); + for (i=0; isuspended = GF_FALSE; + st->stream = 0; + ffmx_configure_pid(filter, ipid, GF_FALSE); + } + nb_done = 0; + } + + //done writing segment + if (nb_segs_done==nb_pids) { + ctx->dash_seg_num = 0; + for (i=0; iin_seg_flush = GF_FALSE; + } + } + + if (e) return e; + + if (nb_done==nb_pids) { + if (ctx->status==FFMX_STATE_HDR_DONE) { + if (ctx->dash_mode) { + ffmx_close_seg(filter, ctx, GF_FALSE); + } else { + av_write_trailer(ctx->muxer); + } + ctx->status = FFMX_STATE_EOS; + } + return GF_EOS; + } + return GF_OK; +} + +static GF_Err ffmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + int res; + AVStream *avst; + Bool check_disc = GF_FALSE; + u32 streamtype, codec_id; + u32 ff_codec_id, ff_st, ff_codec_tag; + const GF_PropertyValue *p, *dsi; + GF_FFMuxStream *st; + GF_FFMuxCtx *ctx = (GF_FFMuxCtx *) gf_filter_get_udta(filter); + + //disconnect of src pid (not yet supported) + if (is_remove) { + //one in one out, this is simple + return GF_OK; + } + st = gf_filter_pid_get_udta(pid); + if (st) { + if (ctx->status>FFMX_STATE_HDR_DONE) + check_disc = GF_TRUE; + } else { + if (ctx->status>FFMX_STATE_HDR_DONE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Cannot dynamically add new stream to %s, not supported\n", ctx->dst )); + return GF_NOT_SUPPORTED; + } + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (!p) return GF_NOT_SUPPORTED; + streamtype = p->value.uint; + + //not supported yet + if (streamtype==GF_STREAM_ENCRYPTED) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Cannot import protected stream, not supported\n")); + return GF_NOT_SUPPORTED; + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p) return GF_NOT_SUPPORTED; + codec_id = p->value.uint; + + ff_st = ffmpeg_stream_type_from_gpac(streamtype); + if (codec_id==GF_CODECID_RAW) { + ff_codec_tag = 0; + if (streamtype==GF_STREAM_VISUAL) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); + if (!p) return GF_NOT_SUPPORTED; + switch (p->value.uint) { + default: + ff_codec_id = AV_CODEC_ID_RAWVIDEO; + break; + } + } else { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT); + if (!p) return GF_NOT_SUPPORTED; + + switch (p->value.uint) { + case GF_AUDIO_FMT_U8: + case GF_AUDIO_FMT_U8P: + ff_codec_id = AV_CODEC_ID_PCM_U8; + break; + case GF_AUDIO_FMT_S16: + case GF_AUDIO_FMT_S16P: + ff_codec_id = AV_CODEC_ID_PCM_S16LE; + break; + case GF_AUDIO_FMT_S24: + case GF_AUDIO_FMT_S24P: + ff_codec_id = AV_CODEC_ID_PCM_S24LE; + break; + case GF_AUDIO_FMT_S32: + case GF_AUDIO_FMT_S32P: + ff_codec_id = AV_CODEC_ID_PCM_S32LE; + break; + case GF_AUDIO_FMT_FLT: + case GF_AUDIO_FMT_FLTP: + ff_codec_id = AV_CODEC_ID_PCM_F32LE; + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Unmapped raw audio format %s to FFMPEG, patch welcome\n", gf_audio_fmt_name(p->value.uint) )); + return GF_NOT_SUPPORTED; + } + } + } else { + ff_codec_id = ffmpeg_codecid_from_gpac(codec_id, &ff_codec_tag); + } + + if (ctx->muxer->oformat && ctx->muxer->oformat->query_codec) { + res = ctx->muxer->oformat->query_codec(ff_codec_id, 1); + if (!res) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Codec %s not supported in container %s\n", gf_codecid_name(codec_id), ctx->muxer->oformat->name)); + return GF_NOT_SUPPORTED; + } + } + const AVCodec *c = avcodec_find_decoder(ff_codec_id); + if (!c) return GF_NOT_SUPPORTED; + + if (!st) { + GF_FilterEvent evt; + + GF_SAFEALLOC(st, GF_FFMuxStream); + if (!st) return GF_OUT_OF_MEM; + gf_filter_pid_set_udta(pid, st); + gf_list_add(ctx->streams, st); + + gf_filter_pid_init_play_event(pid, &evt, ctx->start, ctx->speed, "FFMux"); + gf_filter_pid_send_event(pid, &evt); + } + + if (ctx->status==FFMX_STATE_ALLOC) { + char szPath[GF_MAX_PATH]; + const GF_PropertyValue *p; + const char *file_suffix = NULL; + GF_FilterPacket *pck = gf_filter_pid_get_packet(pid); + if (!pck) { + if (gf_filter_pid_is_eos(pid)) + return GF_SERVICE_ERROR; + return GF_OK; + } + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM); + ctx->cur_file_idx_plus_one = p ? (p->value.uint + 1) : 1; + + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILESUF); + if (p && p->value.string) file_suffix = p->value.string; + + gf_filter_pid_resolve_file_template(pid, ctx->dst, szPath, ctx->cur_file_idx_plus_one-1, file_suffix); + ffmx_open_url(ctx, szPath); + } + + + if (!st->stream) { + st->stream = avformat_new_stream(ctx->muxer, c); + if (!st->stream) return GF_NOT_SUPPORTED; + } + avst = st->stream; + + dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + + if (check_disc) { + u32 dsi_crc, old_dsi_crc; + Bool is_ok = GF_TRUE; + if (avst->codecpar->codec_id != ff_codec_id) is_ok = GF_FALSE; + else if (avst->codecpar->codec_type != ff_st) is_ok = GF_FALSE; + + dsi_crc = old_dsi_crc = 0; + if (avst->codecpar->extradata) old_dsi_crc = gf_crc_32(avst->codecpar->extradata, avst->codecpar->extradata_size); + if (dsi) dsi_crc = gf_crc_32(dsi->value.data.ptr, dsi->value.data.size); + + if (dsi_crc != old_dsi_crc) is_ok = GF_FALSE; + if (!is_ok) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[FFMux] Dynamic stream update in mux %s not supported\n", ctx->dst)); + return GF_NOT_SUPPORTED; + } + } + + avst->codecpar->codec_id = ff_codec_id; + avst->codecpar->codec_type = ff_st; + avst->codecpar->codec_tag = ff_codec_tag; + + if (dsi && dsi->value.data.ptr) { + if (avst->codecpar->extradata) av_free(avst->codecpar->extradata); + avst->codecpar->extradata_size = dsi->value.data.size; + avst->codecpar->extradata = av_malloc(avst->codecpar->extradata_size+ AV_INPUT_BUFFER_PADDING_SIZE); + if (avst->codecpar->extradata) { + memcpy(avst->codecpar->extradata, dsi->value.data.ptr, avst->codecpar->extradata_size); + memset(avst->codecpar->extradata + dsi->value.data.size, 0, AV_INPUT_BUFFER_PADDING_SIZE); + } + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + if (p) avst->id = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) { + avst->time_base.den = p->value.uint; + avst->time_base.num = 1; + } + st->in_scale = avst->time_base; + + avst->start_time = 0; + avst->duration = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DURATION); + if (p && p->value.lfrac.den) { + avst->duration = p->value.lfrac.num; + avst->duration *= avst->time_base.den; + avst->duration /= p->value.lfrac.den; + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_BITRATE); + if (p) avst->codecpar->bit_rate = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CTS_SHIFT); + if (p) st->cts_shift = p->value.uint; + + if (streamtype==GF_STREAM_VISUAL) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + if (p) avst->codecpar->width = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + if (p) avst->codecpar->height = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (p) { + avst->r_frame_rate.num = p->value.frac.num; + avst->r_frame_rate.den = p->value.frac.den; + avst->avg_frame_rate = avst->r_frame_rate; + } + if (codec_id==GF_CODECID_RAW) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); + if (p) { + avst->codecpar->format = ffmpeg_pixfmt_from_gpac(p->value.uint); + avst->codecpar->codec_tag = avcodec_pix_fmt_to_codec_tag(avst->codecpar->format); + } + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); + if (p) { + avst->codecpar->sample_aspect_ratio.num = p->value.frac.num; + avst->codecpar->sample_aspect_ratio.den = p->value.frac.den; + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_COLR_PRIMARIES); + if (p) avst->codecpar->color_primaries = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_COLR_RANGE); + if (p) avst->codecpar->color_range = (p->value.uint==1) ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_COLR_TRANSFER); + if (p) avst->codecpar->color_trc = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_COLR_MX); + if (p) avst->codecpar->color_space = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_COLR_CHROMALOC); + if (p) avst->codecpar->chroma_location = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DELAY); + if (p && avst->r_frame_rate.num && avst->r_frame_rate.den) { + avst->codecpar->video_delay = p->value.sint; + avst->codecpar->video_delay *= avst->r_frame_rate.num; + avst->codecpar->video_delay /= avst->r_frame_rate.den; + } + + } + else if (streamtype==GF_STREAM_AUDIO) { + u64 ch_layout; + u32 samplerate=0; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); + if (p) avst->codecpar->sample_rate = samplerate = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS); + if (p) avst->codecpar->channels = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLES_PER_FRAME); + if (p) avst->codecpar->frame_size = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_BPS); + if (p) avst->codecpar->bits_per_raw_sample = p->value.uint; + if (codec_id==GF_CODECID_RAW) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT); + if (p) avst->codecpar->format = ffmpeg_audio_fmt_from_gpac(p->value.uint); + } + + ch_layout = AV_CH_LAYOUT_MONO; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CHANNEL_LAYOUT); + if (p) + ch_layout = p->value.longuint; + else if (avst->codecpar->channels==2) + ch_layout = AV_CH_LAYOUT_STEREO; + avst->codecpar->channel_layout = ffmpeg_channel_layout_from_gpac(ch_layout); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DELAY); + if (p && (p->value.sint<0) && samplerate) { + avst->codecpar->initial_padding = p->value.sint; + if (st->in_scale.den != samplerate) { + avst->codecpar->initial_padding *= samplerate; + avst->codecpar->initial_padding /= st->in_scale.den; + } + } + /* + //not mapped in gpac + int trailing_padding; + */ + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_MODE); + if (p && (p->value.uint==1)) { + ctx->dash_mode = GF_TRUE; + } + + + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + return GF_OK; +} + +static void ffmx_finalize(GF_Filter *filter) +{ + GF_FFMuxCtx *ctx = (GF_FFMuxCtx *) gf_filter_get_udta(filter); + + if (ctx->status==FFMX_STATE_HDR_DONE) { + if (ctx->dash_mode) { + ffmx_close_seg(filter, ctx, GF_FALSE); + } else { + av_write_trailer(ctx->muxer); + } + ctx->status = FFMX_STATE_TRAILER_DONE; + } + + if (ctx->options) av_dict_free(&ctx->options); + if (ctx->muxer) avformat_free_context(ctx->muxer); + while (gf_list_count(ctx->streams)) { + GF_FFMuxStream *st = gf_list_pop_back(ctx->streams); + gf_free(st); + } + gf_list_del(ctx->streams); + if (ctx->avio_ctx) { + if (ctx->avio_ctx->buffer) av_freep(&ctx->avio_ctx->buffer); + av_freep(&ctx->avio_ctx); + } + if (ctx->gfio) gf_fclose(ctx->gfio); + return; +} + +static GF_Err ffmx_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *arg_val) +{ + s32 res; + GF_FFMuxCtx *ctx = gf_filter_get_udta(filter); + + //initial parsing of arguments + if (!ctx->muxer) { + switch (arg_val->type) { + case GF_PROP_STRING: + res = av_dict_set(&ctx->options, arg_name, arg_val->value.string, 0); + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] Failed to set option %s:%s\n", arg_name, arg_val )); + } + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[FFDec] Failed to set option %s:%s, unrecognized type %d\n", arg_name, arg_val, arg_val->type )); + return GF_NOT_SUPPORTED; + } + return GF_OK; + } + //updates of arguments, not supported for ffmpeg decoders + return GF_NOT_SUPPORTED; +} + +static GF_FilterProbeScore ffmx_probe_url(const char *url, const char *mime) +{ + const char *proto; + if (url && !strncmp(url, "gfio://", 7)) { + url = gf_fileio_translate_url(url); + } + if (!url) + return GF_FPROBE_NOT_SUPPORTED; + + + AVOutputFormat *ofmt = av_guess_format(NULL, url, mime); + if (!ofmt && mime) ofmt = av_guess_format(NULL, NULL, mime); + if (!ofmt && url) ofmt = av_guess_format(NULL, url, NULL); + + if (ofmt) return GF_FPROBE_SUPPORTED; + + proto = strstr(url, "://"); + if (!proto) + return GF_FPROBE_NOT_SUPPORTED; + + proto = avio_find_protocol_name(url); + if (proto) + return GF_FPROBE_MAYBE_SUPPORTED; + return GF_FPROBE_NOT_SUPPORTED; +} + +static const GF_FilterCapability FFMuxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + +#ifdef FF_SUB_SUPPORT + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), +#endif +}; + +GF_FilterRegister FFMuxRegister = { + .name = "ffmx", + .version = LIBAVFORMAT_IDENT, + GF_FS_SET_DESCRIPTION("FFMPEG muxer") + GF_FS_SET_HELP("FFMPEG output for files and streamers.\n" + "See FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details\n" + "\n" + "Note: Some URL formats may not be sufficient to derive the multiplexing format, you must then use [-ffmt]() to specify the desired format.\n" + "\n" + "Unlike other multiplexing filters in GPAC, this filter is a sink filter and does not produce any PID to be redirected in the graph.\n" + "The filter can however use template names for its output, using the first input PID to resolve the final name.\n" + "The filter watches the property `FileNumber` on incoming packets to create new files.\n" + ) + .private_size = sizeof(GF_FFMuxCtx), + SETCAPS(FFMuxCaps), + .initialize = ffmx_initialize, + .finalize = ffmx_finalize, + .configure_pid = ffmx_configure_pid, + .process = ffmx_process, + .update_arg = ffmx_update_arg, + .probe_url = ffmx_probe_url, + .flags = GF_FS_REG_META, + .max_extra_pids = (u32) -1, + + + //use lowest priorty, so that we still use our default built-in muxers + .priority = 255 +}; + +#define OFFS(_n) #_n, offsetof(GF_FFMuxCtx, _n) + + +static const GF_FilterArgs FFMuxArgs[] = +{ + { OFFS(dst), "location of source content", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(start), "set playback start offset. Negative value means percent of media dur with -1 <=> dur", GF_PROP_DOUBLE, "0.0", NULL, 0}, + { OFFS(speed), "set playback speed. If speed is negative and start is 0, start is set to -1", GF_PROP_DOUBLE, "1.0", NULL, 0}, + { OFFS(interleave), "write frame in interleave mode", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(nodisc), "ignore stream configuration changes while muxing, may result in broken streams", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(mime), "set mime type for graph resolution", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(ffiles), "force complete files to be created for each segment in DASH modes", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(ffmt), "force ffmpeg output format for the given URL", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(block_size), "block size used to read file when using avio context", GF_PROP_UINT, "4096", NULL, GF_FS_ARG_HINT_EXPERT}, + { "*", -1, "any possible options defined for AVFormatContext and sub-classes. See `gpac -hx ffmx` and `gpac -hx ffmx:*`", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_META}, + {0} +}; + +const int FFMX_STATIC_ARGS = (sizeof (FFMuxArgs) / sizeof (GF_FilterArgs)) - 1; + +const GF_FilterRegister *ffmx_register(GF_FilterSession *session) +{ + ffmpeg_build_register(session, &FFMuxRegister, FFMuxArgs, FFMX_STATIC_ARGS, FF_REG_TYPE_MUX); + return &FFMuxRegister; +} + +#else +#include +const GF_FilterRegister *ffmx_register(GF_FilterSession *session) +{ + return NULL; +} +#endif + diff --git a/src/filters/ff_rescale.c b/src/filters/ff_rescale.c new file mode 100644 index 0000000..711da7c --- /dev/null +++ b/src/filters/ff_rescale.c @@ -0,0 +1,422 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018 + * All rights reserved + * + * This file is part of GPAC / ffmpeg video rescaler filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include + +#ifdef GPAC_HAS_FFMPEG + +#include "ff_common.h" + +typedef struct +{ + //options + GF_PropVec2i osize; + u32 ofmt, scale; + Double p1, p2; + + //internal data + Bool initialized; + + GF_FilterPid *ipid, *opid; + u32 w, h, stride, s_pfmt; + GF_Fraction ar; + Bool passthrough; + + struct SwsContext *swscaler; + + u32 dst_stride[5]; + u32 src_stride[5]; + u32 nb_planes, nb_src_planes, out_size, out_src_size, src_uv_height, dst_uv_height, ow, oh; + + u32 swap_idx_1, swap_idx_2; + +} GF_FFSWScaleCtx; + +static GF_Err ffsws_process(GF_Filter *filter) +{ + const char *data; + u8 *output; + u32 osize; + s32 res; + u8 *src_planes[5]; + u8 *dst_planes[5]; + GF_FilterPacket *dst_pck; + GF_FilterFrameInterface *frame_ifce; + GF_FFSWScaleCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + + pck = gf_filter_pid_get_packet(ctx->ipid); + + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + if (ctx->passthrough) { + gf_filter_pck_forward(pck, ctx->opid); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + //not yet configured + if (!ctx->ofmt && !ctx->ow && !ctx->oh) + return GF_OK; + + if (!ctx->swscaler) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NOT_SUPPORTED; + } + + data = gf_filter_pck_get_data(pck, &osize); + frame_ifce = gf_filter_pck_get_frame_interface(pck); + //we may have biffer input (padding) but shall not have smaller + if (osize && (ctx->out_src_size > osize) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFSWS] Mismatched in source osize, expected %d got %d - stride issue ?\n", ctx->out_src_size, osize)); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NOT_SUPPORTED; + } + + memset(src_planes, 0, sizeof(src_planes)); + memset(dst_planes, 0, sizeof(dst_planes)); + dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->out_size, &output); + if (!dst_pck) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OUT_OF_MEM; + } + gf_filter_pck_merge_properties(pck, dst_pck); + + if (data) { + src_planes[0] = (u8 *) data; + + if (ctx->nb_src_planes==1) { + } else if (ctx->nb_src_planes==2) { + src_planes[1] = src_planes[0] + ctx->src_stride[0]*ctx->h; + } else if (ctx->nb_src_planes==3) { + src_planes[1] = src_planes[0] + ctx->src_stride[0] * ctx->h; + src_planes[2] = src_planes[1] + ctx->src_stride[1] * ctx->src_uv_height; + } else if (ctx->nb_src_planes==4) { + src_planes[1] = src_planes[0] + ctx->src_stride[0] * ctx->h; + src_planes[2] = src_planes[1] + ctx->src_stride[1] * ctx->src_uv_height; + src_planes[3] = src_planes[2] + ctx->src_stride[2] * ctx->src_uv_height; + } + } else if (frame_ifce && frame_ifce->get_plane) { + u32 i=0; + for (i=0; inb_src_planes; i++) { + if (frame_ifce->get_plane(frame_ifce, i, (const u8 **) &src_planes[i], &ctx->src_stride[i])!=GF_OK) + break; + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFSWS] No data associated with packet, not supported\n")); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NOT_SUPPORTED; + } + dst_planes[0] = output; + if (ctx->nb_planes==1) { + } else if (ctx->nb_planes==2) { + dst_planes[1] = output + ctx->dst_stride[0] * ctx->oh; + } else if (ctx->nb_planes==3) { + dst_planes[1] = output + ctx->dst_stride[0] * ctx->oh; + dst_planes[2] = dst_planes[1] + ctx->dst_stride[1]*ctx->dst_uv_height; + } else if (ctx->nb_planes==4) { + dst_planes[1] = output + ctx->dst_stride[0] * ctx->oh; + dst_planes[2] = dst_planes[1] + ctx->dst_stride[1]*ctx->dst_uv_height; + dst_planes[3] = dst_planes[2] + ctx->dst_stride[2]*ctx->dst_uv_height; + } + + //rescale the cropped frame + res = sws_scale(ctx->swscaler, (const u8**) src_planes, ctx->src_stride, 0, ctx->h, dst_planes, ctx->dst_stride); + if (res != ctx->oh) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFSWS] Error during scale, expected height %d got %d\n", ctx->oh, res)); + gf_filter_pid_drop_packet(ctx->ipid); + gf_filter_pck_discard(dst_pck); + return GF_NOT_SUPPORTED; + } + + if (ctx->swap_idx_1 || ctx->swap_idx_2) { + u32 i, j; + for (i=0; ih; i++) { + u8 *dst = output + ctx->dst_stride[0]*i; + for (j=0; jdst_stride[0]; j+=4) { + u8 tmp = dst[ctx->swap_idx_1]; + dst[ctx->swap_idx_1] = dst[ctx->swap_idx_2]; + dst[ctx->swap_idx_2] = tmp; + dst += 4; + } + } + } + + gf_filter_pck_send(dst_pck); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + +static u32 get_sws_mode(u32 mode, u32 *has_param) +{ + switch (mode) { + case 0: return SWS_FAST_BILINEAR; + case 1: return SWS_BILINEAR; + case 2: *has_param = 2; return SWS_BICUBIC; + case 3: return SWS_X; + case 4: return SWS_POINT; + case 5: return SWS_AREA; + case 6: return SWS_BICUBLIN; + case 7: *has_param = 1; return SWS_GAUSS; + case 8: return SWS_SINC; + case 9: *has_param = 1; return SWS_LANCZOS; + case 10: return SWS_SPLINE; + default: break; + } + *has_param = 2; + return SWS_BICUBIC; +} + +static GF_Err ffsws_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + u32 w, h, stride, ofmt; + GF_Fraction sar; + Double par[2], *par_p=NULL; + GF_FFSWScaleCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + if (ctx->opid) { + gf_filter_pid_remove(ctx->opid); + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + + if (!ctx->ipid) { + ctx->ipid = pid; + } + + //if nothing is set we, consider we run as an adaptation filter, wait for caps to be set to declare output + if (!ctx->ofmt && !ctx->osize.x && !ctx->osize.y) + return GF_OK; + + + + w = h = ofmt = stride = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + if (p) w = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + if (p) h = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE); + if (p) stride = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); + if (p) ofmt = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); + if (p) sar = p->value.frac; + else sar.den = sar.num = 1; + + //ctx->ofmt may be 0 if the filter is instantiated dynamically, we haven't yet been called for reconfigure + if (!w || !h || !ofmt) { + return GF_OK; + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + + if (!ctx->ofmt) + ctx->ofmt = ofmt; + + ctx->passthrough = GF_FALSE; + + ctx->ow = ctx->osize.x ? ctx->osize.x : w; + ctx->oh = ctx->osize.y ? ctx->osize.y : h; + if ((ctx->w == w) && (ctx->h == h) && (ctx->s_pfmt == ofmt) && (ctx->stride == stride)) { + //nothing to reconfigure + } + //passthrough mode + else if ((ctx->ow == w) && (ctx->oh == h) && (ctx->s_pfmt == ofmt) && (ofmt==ctx->ofmt) ) { + memset(ctx->dst_stride, 0, sizeof(ctx->dst_stride)); + gf_pixel_get_size_info(ctx->ofmt, ctx->ow, ctx->oh, &ctx->out_size, &ctx->dst_stride[0], &ctx->dst_stride[1], &ctx->nb_planes, &ctx->dst_uv_height); + ctx->passthrough = GF_TRUE; + } else { + u32 nb_par = 0; + nb_par = 0; + Bool res; + u32 mode = get_sws_mode(ctx->scale, &nb_par); + u32 ff_src_pfmt = ffmpeg_pixfmt_from_gpac(ofmt); + u32 ff_dst_pfmt = ffmpeg_pixfmt_from_gpac(ctx->ofmt); + + //get layout info for source + memset(ctx->src_stride, 0, sizeof(ctx->src_stride)); + if (ctx->stride) ctx->src_stride[0] = ctx->stride; + + + res = gf_pixel_get_size_info(ofmt, w, h, &ctx->out_src_size, &ctx->src_stride[0], &ctx->src_stride[1], &ctx->nb_src_planes, &ctx->src_uv_height); + if (!res) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFSWS] Failed to query source pixel format characteristics\n")); + return GF_NOT_SUPPORTED; + } + if (ctx->nb_src_planes==3) ctx->src_stride[2] = ctx->src_stride[1]; + if (ctx->nb_src_planes==4) ctx->src_stride[3] = ctx->src_stride[0]; + + //get layout info for dest + memset(ctx->dst_stride, 0, sizeof(ctx->dst_stride)); + res = gf_pixel_get_size_info(ctx->ofmt, ctx->ow, ctx->oh, &ctx->out_size, &ctx->dst_stride[0], &ctx->dst_stride[1], &ctx->nb_planes, &ctx->dst_uv_height); + if (!res) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFSWS] Failed to query output pixel format characteristics\n")); + return GF_NOT_SUPPORTED; + } + if (ctx->nb_planes==3) ctx->dst_stride[2] = ctx->dst_stride[1]; + if (ctx->nb_planes==4) ctx->dst_stride[3] = ctx->dst_stride[0]; + + + if (nb_par) { + if ((nb_par==1) && (ctx->p1!=GF_MAX_DOUBLE) ) { + par[0] = ctx->p1; + par_p = (Double *)par; + } else if ((nb_par==2) && (ctx->p1!=GF_MAX_DOUBLE) && (ctx->p2!=GF_MAX_DOUBLE)) { + par[0] = ctx->p1; + par[1] = ctx->p2; + par_p = (Double *)par; + } + } + //create/get a swscale context + ctx->swscaler = sws_getCachedContext(ctx->swscaler, w, h, ff_src_pfmt, ctx->ow, ctx->oh, ff_dst_pfmt, mode, NULL, NULL, par_p); + + if (!ctx->swscaler) { +#ifndef GPAC_DISABLE_LOG + Bool in_ok = sws_isSupportedInput(ff_src_pfmt); + Bool out_ok = sws_isSupportedInput(ff_dst_pfmt); + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[FFSWS] Cannot allocate context for required format - input %s output %s\n", in_ok ? "OK" : "not supported" , out_ok ? "OK" : "not supported")); +#endif + return GF_NOT_SUPPORTED; + } + ctx->w = w; + ctx->h = h; + ctx->s_pfmt = ofmt; + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[FFSWS] Setup rescaler from %dx%d fmt %s to %dx%d fmt %s\n", w, h, gf_pixel_fmt_name(ofmt), ctx->ow, ctx->oh, gf_pixel_fmt_name(ctx->ofmt))); + + ctx->swap_idx_1 = ctx->swap_idx_2 = 0; + //if same source / dest pixel format, don'( swap UV components + if (ctx->s_pfmt != ctx->ofmt) { + if (ctx->ofmt==GF_PIXEL_VYUY) { + ctx->swap_idx_1 = 0; + ctx->swap_idx_2 = 2; + } + else if (ctx->ofmt==GF_PIXEL_YVYU) { + ctx->swap_idx_1 = 1; + ctx->swap_idx_2 = 3; + } + } + } + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->ow)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->oh)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->dst_stride[0])); + if (ctx->nb_planes>1) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE_UV, &PROP_UINT(ctx->dst_stride[1])); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, &PROP_UINT(ctx->ofmt)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, &PROP_FRAC(sar) ); + return GF_OK; +} + +static void ffsws_finalize(GF_Filter *filter) +{ + GF_FFSWScaleCtx *ctx = gf_filter_get_udta(filter); + if (ctx->swscaler) sws_freeContext(ctx->swscaler); + return; +} + + +static GF_Err ffsws_reconfigure_output(GF_Filter *filter, GF_FilterPid *pid) +{ + const GF_PropertyValue *p; + GF_FFSWScaleCtx *ctx = gf_filter_get_udta(filter); + if (ctx->opid != pid) return GF_BAD_PARAM; + + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_WIDTH); + if (p) ctx->osize.x = p->value.uint; + + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_HEIGHT); + if (p) ctx->osize.y = p->value.uint; + + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_PIXFMT); + if (p) ctx->ofmt = p->value.uint; + return ffsws_configure_pid(filter, ctx->ipid, GF_FALSE); +} + + +#define OFFS(_n) #_n, offsetof(GF_FFSWScaleCtx, _n) +static GF_FilterArgs FFSWSArgs[] = +{ + { OFFS(osize), "osize of output video. When not set, input osize is used", GF_PROP_VEC2I, NULL, NULL, 0}, + { OFFS(ofmt), "pixel format for output video. When not set, input format is used", GF_PROP_PIXFMT, "none", NULL, 0}, + { OFFS(scale), "scaling mode - see filter info", GF_PROP_UINT, "bicubic", "fastbilinear|bilinear|bicubic|X|point|area|bicublin|gauss|sinc|lanzcos|spline", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(p1), "scaling algo param1 - see filter info", GF_PROP_DOUBLE, "+I", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(p2), "scaling algo param2 - see filter info", GF_PROP_DOUBLE, "+I", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + +static const GF_FilterCapability FFSWSCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW) +}; + + +GF_FilterRegister FFSWSRegister = { + .name = "ffsws", + .version=LIBSWSCALE_IDENT, + GF_FS_SET_DESCRIPTION("FFMPEG video rescaler") + GF_FS_SET_HELP("For bicubic, to tune the shape of the basis function, [-p1]() tunes f(1) and [-p2]() f´(1)\n"\ + "For gauss [-p1]() tunes the exponent and thus cutoff frequency\n"\ + "For lanczos [-p1]() tunes the width of the window function"\ + "\nSee FFMPEG documentation (https://ffmpeg.org/documentation.html) for more details") + + .private_size = sizeof(GF_FFSWScaleCtx), + .args = FFSWSArgs, + .configure_pid = ffsws_configure_pid, + SETCAPS(FFSWSCaps), + .finalize = ffsws_finalize, + .process = ffsws_process, + .reconfigure_output = ffsws_reconfigure_output, +}; + +#else +#include +#endif //GPAC_HAS_FFMPEG + +const GF_FilterRegister *ffsws_register(GF_FilterSession *session) +{ +#ifdef GPAC_HAS_FFMPEG + FFSWSArgs[1].min_max_enum = gf_pixel_fmt_all_names(); + return &FFSWSRegister; +#else + return NULL; +#endif +} + diff --git a/src/filters/filelist.c b/src/filters/filelist.c new file mode 100644 index 0000000..88e8bd0 --- /dev/null +++ b/src/filters/filelist.c @@ -0,0 +1,1049 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018-2020 + * All rights reserved + * + * This file is part of GPAC / file concatenator filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_AV_PARSERS +#include +#endif + +typedef struct +{ + GF_FilterPid *ipid; + GF_FilterPid *opid; + u32 stream_type; + //in current timescale + u64 max_cts, max_dts; + u32 o_timescale, timescale; + //in original timescale + u64 cts_o, dts_o; + Bool single_frame; + Bool is_eos; + u64 dts_sub; + u64 first_dts_plus_one; + + Bool is_playing; +} FileListPid; + +typedef struct +{ + char *file_name; + u64 last_mod_time; + u64 file_size; +} FileListEntry; + +enum +{ + FL_SORT_NONE=0, + FL_SORT_NAME, + FL_SORT_SIZE, + FL_SORT_DATE, + FL_SORT_DATEX, +}; + +typedef struct +{ + //opts + Bool revert; + s32 floop; + u32 fsort; + u32 ka; + GF_List *srcs; + GF_Fraction fdur; + u32 timescale; + + GF_FilterPid *file_pid; + char *file_path; + u32 last_url_crc; + u32 last_url_lineno; + Bool load_next; + + GF_List *filter_srcs; + GF_List *io_pids; + Bool is_eos; + + //in 1000000 Hz + u64 cts_offset, dts_offset, dts_sub_plus_one; + + u32 nb_repeat; + Double start, stop; + Bool do_cat; + u64 start_range, end_range; + GF_List *file_list; + s32 file_list_idx; + + u64 current_file_dur; + Bool last_is_isom; + char szCom[GF_MAX_PATH]; + + Bool wait_update; + u64 last_file_modif_time; +} GF_FileListCtx; + +static const GF_FilterCapability FileListCapsSrc[] = +{ + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), +}; + +static void filelist_start_ipid(GF_FileListCtx *ctx, FileListPid *iopid) +{ + iopid->is_eos = GF_FALSE; + + if (!ctx->do_cat) { + GF_FilterEvent evt; + //if we reattached the input, we must send a play request + gf_filter_pid_init_play_event(iopid->ipid, &evt, ctx->start, 1.0, "FileList"); + gf_filter_pid_send_event(iopid->ipid, &evt); + } + + //and convert back cts/dts offsets from 1Mhs to OLD timescale (since we dispatch in this timescale) + iopid->dts_o = ctx->dts_offset; + iopid->dts_o *= iopid->o_timescale; + iopid->dts_o /= 1000000; + + iopid->cts_o = ctx->cts_offset; + iopid->cts_o *= iopid->o_timescale; + iopid->cts_o /= 1000000; + iopid->max_cts = iopid->max_dts = 0; + + iopid->first_dts_plus_one = 0; +} + +Bool filelist_merge_prop(void *cbk, u32 prop_4cc, const char *prop_name, const GF_PropertyValue *src_prop) +{ + const GF_PropertyValue *p; + GF_FilterPid *pid = (GF_FilterPid *) cbk; + + if (prop_4cc) p = gf_filter_pid_get_property(pid, prop_4cc); + else p = gf_filter_pid_get_property_str(pid, prop_name); + if (p) return GF_FALSE; + return GF_TRUE; +} + +GF_Err filelist_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + FileListPid *iopid; + const GF_PropertyValue *p; + u32 i, count; + Bool reassign = GF_FALSE; + GF_FileListCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + if (pid==ctx->file_pid) + ctx->file_pid = NULL; + else { + iopid = gf_filter_pid_get_udta(pid); + if (iopid) iopid->ipid = NULL; + } + return GF_OK; + } + + if (!ctx->file_pid && !ctx->file_list) { + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + ctx->file_pid = pid; + //we want the complete file + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + + //we will declare pids later + + //from now on we only accept the above caps + gf_filter_override_caps(filter, FileListCapsSrc, 2); + + } + if (ctx->file_pid == pid) return GF_OK; + + iopid = NULL; + count = gf_list_count(ctx->io_pids); + for (i=0; iio_pids, i); + if (iopid->ipid==pid) break; + //check matching stream types if out pit not connected, and reuse if matching + if (!iopid->ipid) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + assert(p); + if (p->value.uint == iopid->stream_type) { + iopid->ipid = pid; + iopid->timescale = gf_filter_pid_get_timescale(pid); + reassign = GF_TRUE; + break; + } + } + iopid = NULL; + } + + if (!iopid) { + GF_SAFEALLOC(iopid, FileListPid); + if (!iopid) return GF_OUT_OF_MEM; + iopid->ipid = pid; + if (ctx->timescale) iopid->o_timescale = ctx->timescale; + else { + iopid->o_timescale = gf_filter_pid_get_timescale(pid); + if (!iopid->o_timescale) iopid->o_timescale = 1000; + } + gf_list_add(ctx->io_pids, iopid); + } + gf_filter_pid_set_udta(pid, iopid); + + if (!iopid->opid) { + iopid->opid = gf_filter_pid_new(filter); + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + assert(p); + iopid->stream_type = p->value.uint; + } + + //copy properties at init or reconfig: + //reset (no more props) + gf_filter_pid_reset_properties(iopid->opid); + + gf_filter_pid_copy_properties(iopid->opid, iopid->ipid); + + //if file pid is defined, merge its properties. This will allow forwarding user-defined properties, + // eg -i list.txt:#MyProp=toto to all PIDs coming from the sources + if (ctx->file_pid) { + gf_filter_pid_merge_properties(iopid->opid, ctx->file_pid, filelist_merge_prop, iopid->ipid); + } + + //we could further optimize by querying the duration of all sources in the list + gf_filter_pid_set_property(iopid->opid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_NONE) ); + gf_filter_pid_set_property(iopid->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(iopid->o_timescale) ); + gf_filter_pid_set_property(iopid->opid, GF_PROP_PID_NB_FRAMES, NULL ); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_NB_FRAMES); + iopid->single_frame = (p && (p->value.uint==1)) ? GF_TRUE : GF_FALSE ; + iopid->timescale = gf_filter_pid_get_timescale(pid); + if (!iopid->timescale) iopid->timescale = 1000; + +#if 0 + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DELAY); + if (p) { + s64 delay = p->value.sint; + delay *= iopid->timescale; + delay /= iopid->o_timescale; + } +#endif + + //if we reattached the input, we must send a play request + if (reassign) { + filelist_start_ipid(ctx, iopid); + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_URL); + if (p) + gf_filter_pid_set_property(iopid->opid, GF_PROP_PID_URL, p); + + return GF_OK; +} + +static Bool filelist_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i, count; + FileListPid *iopid; + GF_FilterEvent fevt; + GF_FileListCtx *ctx = gf_filter_get_udta(filter); + + //manually forward event to all input, except our file in pid + memcpy(&fevt, evt, sizeof(GF_FilterEvent)); + count = gf_list_count(ctx->io_pids); + for (i=0; iio_pids, i); + if (!iopid->ipid) continue; + + fevt.base.on_pid = iopid->ipid; + if (evt->base.type==GF_FEVT_PLAY) { + gf_filter_pid_init_play_event(iopid->ipid, &fevt, ctx->start, 1.0, "FileList"); + iopid->is_playing = GF_TRUE; + iopid->is_eos = GF_FALSE; + } else if (evt->base.type==GF_FEVT_STOP) { + iopid->is_playing = GF_FALSE; + iopid->is_eos = GF_TRUE; + } + gf_filter_pid_send_event(iopid->ipid, &fevt); + } + //and cancel + return GF_TRUE; +} + +void filelist_parse_arg(char *com, char *name, u32 type, u32 *int_val, Double *float_val, u64 *luint_val) +{ + char *val = strstr(com, name); + if (val) { + char c= 0; + char *sep = strchr(val, ' '); + if (!sep) sep = strchr(val, ','); + if (!sep) sep = strchr(val, '\n'); + if (!sep) sep = strchr(val, '\r'); + if (sep) { + c = sep[0]; + sep[0] = 0; + } + val += strlen(name); + if (type==1) { + (*int_val) = atoi(val); + } else if (type==2) { + (*int_val) = atoi(val); + } else if (type==3) { + sscanf(val, LLU, luint_val); + } else if (type==0) { + (*int_val) = 1; + } + if (sep) sep[0] = c; + } +} + +void filelist_check_implicit_cat(GF_FileListCtx *ctx, char *szURL) +{ + char *res_url = NULL; + if (ctx->file_path) { + res_url = gf_url_concatenate(ctx->file_path, szURL); + szURL = res_url; + } + + switch (gf_isom_probe_file(szURL)) { + //this is a fragment + case 3: + if (ctx->last_is_isom) { + ctx->do_cat = GF_TRUE; + } + break; + //this is a movie, reload + case 2: + case 1: + ctx->do_cat = GF_FALSE; + ctx->last_is_isom = GF_TRUE; + break; + default: + ctx->do_cat = GF_FALSE; + ctx->last_is_isom = GF_FALSE; + } + if (res_url) + gf_free(res_url); +} + +Bool filelist_next_url(GF_FileListCtx *ctx, char szURL[GF_MAX_PATH]) +{ + u32 len; + Bool last_found = GF_FALSE; + FILE *f; + u32 nb_repeat=0, lineno=0; + u64 start_range, end_range; + Double start=0, stop=0; + Bool do_cat=0; + Bool is_end=0; + + if (ctx->file_list) { + FileListEntry *fentry, *next; + + if (ctx->revert) { + if (!ctx->file_list_idx) { + if (!ctx->floop) return GF_FALSE; + if (ctx->floop>0) ctx->floop--; + ctx->file_list_idx = gf_list_count(ctx->file_list); + } + ctx->file_list_idx --; + } else { + ctx->file_list_idx ++; + if (ctx->file_list_idx >= (s32) gf_list_count(ctx->file_list)) { + if (!ctx->floop) return GF_FALSE; + if (ctx->floop>0) ctx->floop--; + ctx->file_list_idx = 0; + } + } + fentry = gf_list_get(ctx->file_list, ctx->file_list_idx); + strncpy(szURL, fentry->file_name, sizeof(char)*(GF_MAX_PATH-1) ); + szURL[GF_MAX_PATH-1] = 0; + filelist_check_implicit_cat(ctx, szURL); + next = gf_list_get(ctx->file_list, ctx->file_list_idx + 1); + if (next) + ctx->current_file_dur = next->last_mod_time - fentry->last_mod_time; + return GF_TRUE; + } + + if (ctx->wait_update) { + u64 last_modif_time = gf_file_modification_time(ctx->file_path); + if (ctx->last_file_modif_time >= last_modif_time) + return GF_FALSE; + ctx->wait_update = GF_FALSE; + ctx->last_file_modif_time = last_modif_time; + } + + f = gf_fopen(ctx->file_path, "rt"); + while (f) { + u32 crc; + char *l = gf_fgets(szURL, GF_MAX_PATH, f); + if (!l || gf_feof(f)) { + if (ctx->floop != 0) { + gf_fseek(f, 0, SEEK_SET); + //load first line + last_found = GF_TRUE; + continue; + } + gf_fclose(f); + if (is_end) { + ctx->ka = 0; + } else if (ctx->ka) { + ctx->wait_update = GF_TRUE; + ctx->last_file_modif_time = gf_file_modification_time(ctx->file_path); + } + return GF_FALSE; + } + lineno++; + + len = (u32) strlen(szURL); + while (len && strchr("\n\r\t ", szURL[len-1])) { + szURL[len-1] = 0; + len--; + } + if (!len) continue; + + //comment + if (szURL[0] == '#') { + nb_repeat=0; + start=stop=0; + do_cat = 0; + start_range=end_range=0; + + filelist_parse_arg(szURL, "repeat=", 1, &nb_repeat, NULL, NULL); + filelist_parse_arg(szURL, "start=", 2, NULL, &start, NULL); + filelist_parse_arg(szURL, "stop=", 2, NULL, &stop, NULL); + filelist_parse_arg(szURL, "cat", 0, (u32 *)&do_cat, NULL, NULL); + if (do_cat) { + filelist_parse_arg(szURL, "srange=", 3, NULL, NULL, &start_range); + filelist_parse_arg(szURL, "send=", 3, NULL, NULL, &end_range); + } + if (ctx->ka) { + filelist_parse_arg(szURL, "end", 0, (u32 *)&is_end, NULL, NULL); + } + strcpy(ctx->szCom, szURL); + continue; + } + crc = gf_crc_32(szURL, (u32) strlen(szURL) ); + if (!ctx->last_url_crc) { + ctx->last_url_crc = crc; + ctx->last_url_lineno = lineno; + break; + } + if ((ctx->last_url_crc == crc) && (ctx->last_url_lineno == lineno)) { + last_found = GF_TRUE; + nb_repeat=0; + start=stop=0; + do_cat=0; + start_range=end_range=0; + ctx->szCom[0] = 0; + continue; + } + if (last_found) { + ctx->last_url_crc = crc; + ctx->last_url_lineno = lineno; + break; + } + nb_repeat=0; + start=stop=0; + do_cat=0; + start_range=end_range=0; + ctx->szCom[0] = 0; + } + gf_fclose(f); + + len = (u32) strlen(szURL); + while (len && strchr("\r\n", szURL[len-1])) { + szURL[len-1] = 0; + len--; + } + ctx->nb_repeat = nb_repeat; + ctx->start = start; + ctx->stop = stop; + ctx->do_cat = do_cat; + ctx->start_range = start_range; + ctx->end_range = end_range; + filelist_check_implicit_cat(ctx, szURL); + return GF_TRUE; +} + +GF_Err filelist_process(GF_Filter *filter) +{ + Bool start, end; + GF_Err e; + u32 i, count, nb_done, nb_inactive; + FileListPid *iopid; + GF_FileListCtx *ctx = gf_filter_get_udta(filter); + + if (ctx->is_eos) + return GF_EOS; + + if (!ctx->file_list) { + GF_FilterPacket *pck; + if (!ctx->file_pid) { + return GF_EOS; + } + pck = gf_filter_pid_get_packet(ctx->file_pid); + if (pck) { + gf_filter_pck_get_framing(pck, &start, &end); + gf_filter_pid_drop_packet(ctx->file_pid); + + if (end) { + const GF_PropertyValue *p; + Bool is_first = GF_TRUE; + FILE *f=NULL; + p = gf_filter_pid_get_property(ctx->file_pid, GF_PROP_PID_FILEPATH); + if (p) { + if (ctx->file_path) { + gf_free(ctx->file_path); + is_first = GF_FALSE; + } + ctx->file_path = gf_strdup(p->value.string); + f = gf_fopen(p->value.string, "rt"); + } + if (!f) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[FileList] Unable to open file %s\n", ctx->file_path ? ctx->file_path : "no source path")); + return GF_SERVICE_ERROR; + } else { + gf_fclose(f); + ctx->load_next = is_first; + } + } + } + } + + count = gf_list_count(ctx->io_pids); + + if (ctx->load_next) { + GF_Filter *fsrc; + u32 s_idx; + char *url; + char szURL[GF_MAX_PATH]; + Bool next_url_ok; + + next_url_ok = filelist_next_url(ctx, szURL); + + if (!next_url_ok && ctx->ka) { + gf_filter_ask_rt_reschedule(filter, ctx->ka*1000); + return GF_OK; + } + if (!next_url_ok || !ctx->do_cat) { + while (gf_list_count(ctx->filter_srcs)) { + fsrc = gf_list_pop_back(ctx->filter_srcs); + gf_filter_remove_src(filter, fsrc); + } + } + + ctx->load_next = GF_FALSE; + + if (! next_url_ok) { + for (i=0; iio_pids, i); + gf_filter_pid_set_eos(iopid->opid); + iopid->ipid = NULL; + } + ctx->is_eos = GF_TRUE; + return GF_EOS; + } + for (i=0; iio_pids); i++) { + FileListPid *an_iopid = gf_list_get(ctx->io_pids, i); + if (ctx->do_cat) { + gf_filter_pid_clear_eos(an_iopid->ipid, GF_TRUE); + filelist_start_ipid(ctx, an_iopid); + } else { + an_iopid->ipid = NULL; + } + } + s_idx = 0; + url = szURL; + while (url) { + char *sep = strstr(url, " && "); + if (sep) sep[0] = 0; + + if (ctx->do_cat) { + char *f_url; + GF_FilterEvent evt; + fsrc = gf_list_get(ctx->filter_srcs, s_idx); + if (!fsrc) { + if (sep) sep[0] = ' '; + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[FileList] More URL to cat than opened service!\n")); + return GF_BAD_PARAM; + } + f_url = gf_url_concatenate(ctx->file_path, url); + memset(&evt, 0, sizeof(GF_FilterEvent)); + evt.base.type = GF_FEVT_SOURCE_SWITCH; + evt.seek.source_switch = f_url ? f_url : url; + evt.seek.start_offset = ctx->start_range; + evt.seek.end_offset = ctx->end_range; + gf_filter_send_event(fsrc, &evt, GF_FALSE); + if (f_url) + gf_free(f_url); + } else { + fsrc = gf_filter_connect_source(filter, url, ctx->file_path, GF_TRUE, &e); + if (e) { + if (sep) sep[0] = ' '; + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[FileList] Failed to open file %s: %s\n", szURL, gf_error_to_string(e) )); + return GF_SERVICE_ERROR; + } + gf_list_add(ctx->filter_srcs, fsrc); + } + if (!sep) break; + sep[0] = ' '; + url = sep+4; + } + //wait for PIDs to connect + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("[FileList] Switching to file %s\n", szURL)); + + } + + //init first timestamp + if (!ctx->dts_sub_plus_one) { + u32 nb_eos = 0; + for (i=0; iio_pids, i); + if (!iopid->ipid) return GF_OK; + pck = gf_filter_pid_get_packet(iopid->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(iopid->ipid)) { + nb_eos++; + continue; + } + return GF_OK; + } + dts = gf_filter_pck_get_dts(pck); + if (dts==GF_FILTER_NO_TS) dts=0; + + dts *= 1000000; + dts /= iopid->timescale; + if (!ctx->dts_sub_plus_one) ctx->dts_sub_plus_one = dts + 1; + else if (dts < ctx->dts_sub_plus_one - 1) ctx->dts_sub_plus_one = dts + 1; + } + if (nb_eos) { + if (nb_eos==count) { + //force load + ctx->load_next = GF_TRUE; + return filelist_process(filter); + } + return GF_OK; + } + for (i=0; iio_pids, i); + iopid->dts_sub = ctx->dts_sub_plus_one - 1; + iopid->dts_sub *= iopid->o_timescale; + iopid->dts_sub /= 1000000; + } + } + + nb_done = nb_inactive = 0; + for (i=0; iio_pids, i); + if (!iopid->ipid) { + nb_inactive++; + continue; + } + if (iopid->is_eos) { + nb_done++; + continue; + } + if (!iopid->is_playing) { + continue; + } + if (gf_filter_pid_would_block(iopid->opid)) + continue; + + while (1) { + GF_FilterPacket *dst_pck; + u64 cts, dts; + u32 dur; + GF_FilterPacket *pck; + + pck = gf_filter_pid_get_packet(iopid->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(iopid->ipid)) + iopid->is_eos = GF_TRUE; + + if (iopid->is_eos) + nb_done++; + break; + } + + if (gf_filter_pid_would_block(iopid->opid)) + break; + + dst_pck = gf_filter_pck_new_ref(iopid->opid, NULL, 0, pck); + gf_filter_pck_merge_properties(pck, dst_pck); + dts = gf_filter_pck_get_dts(pck); + if (dts==GF_FILTER_NO_TS) dts=0; + + cts = gf_filter_pck_get_cts(pck); + if (cts==GF_FILTER_NO_TS) cts=0; + + if (iopid->single_frame && (ctx->fsort==FL_SORT_DATEX) ) { + dur = (u32) ctx->current_file_dur; + //move from second to input pid timescale + dur *= iopid->timescale; + } else if (iopid->single_frame && ctx->fdur.num && ctx->fdur.den) { + s64 pdur = ctx->fdur.num; + pdur *= iopid->timescale; + pdur /= ctx->fdur.den; + dur = (u32) pdur; + } else { + dur = gf_filter_pck_get_duration(pck); + } + + if (iopid->timescale == iopid->o_timescale) { + gf_filter_pck_set_dts(dst_pck, iopid->dts_o + dts - iopid->dts_sub); + gf_filter_pck_set_cts(dst_pck, iopid->cts_o + cts - iopid->dts_sub); + gf_filter_pck_set_duration(dst_pck, dur); + } else { + u64 ts = dts; + ts *= iopid->o_timescale; + ts /= iopid->timescale; + gf_filter_pck_set_dts(dst_pck, iopid->dts_o + ts - iopid->dts_sub); + ts = cts; + ts *= iopid->o_timescale; + ts /= iopid->timescale; + gf_filter_pck_set_cts(dst_pck, iopid->cts_o + ts - iopid->dts_sub); + + ts = dur; + ts *= iopid->o_timescale; + ts /= iopid->timescale; + gf_filter_pck_set_duration(dst_pck, (u32) ts); + } + dts += dur; + cts += dur; + if (dts > iopid->max_dts) iopid->max_dts = dts; + if (cts > iopid->max_cts) iopid->max_cts = cts; + + //remember our first DTS + if (!iopid->first_dts_plus_one) { + iopid->first_dts_plus_one = dts + 1; + } + gf_filter_pck_send(dst_pck); + gf_filter_pid_drop_packet(iopid->ipid); + //if we have an end range, compute max_dts (includes dur) - firrst_dts + if (ctx->stop>ctx->start) { + if ( (ctx->stop-ctx->start) * iopid->timescale <= (iopid->max_dts - iopid->first_dts_plus_one + 1)) { + iopid->is_eos = GF_TRUE; + nb_done++; + break; + } + } + } + } + if ((nb_inactive!=count) && (nb_done+nb_inactive==count)) { + //compute max cts and dts in 1Mhz timescale + u64 max_cts = 0, max_dts = 0; + + if (gf_filter_end_of_session(filter)) { + for (i=0; iio_pids, i); + gf_filter_pid_set_eos(iopid->opid); + } + ctx->is_eos = GF_TRUE; + return GF_EOS; + } + ctx->dts_sub_plus_one = 0; + for (i=0; iio_pids, i); + if (iopid->ipid) { + u64 ts; + ts = iopid->max_cts - iopid->dts_sub; + ts *= 1000000; + ts /= iopid->timescale; + if (max_cts < ts) max_cts = ts; + + ts = iopid->max_dts - iopid->dts_sub; + ts *= 1000000; + ts /= iopid->timescale; + if (max_dts < ts) max_dts = ts; + } + } + ctx->cts_offset += max_dts; + ctx->dts_offset += max_dts; + + if (ctx->nb_repeat) { + ctx->nb_repeat--; + for (i=0; iio_pids, i); + if (!iopid->ipid) continue; + + GF_FEVT_INIT(evt, GF_FEVT_STOP, iopid->ipid); + gf_filter_pid_send_event(iopid->ipid, &evt); + + iopid->is_eos = GF_FALSE; + filelist_start_ipid(ctx, iopid); + } + } else { + //force load + ctx->load_next = GF_TRUE; + return filelist_process(filter); + } + } + return GF_OK; +} + +void filelist_add_entry(GF_FileListCtx *ctx, FileListEntry *fentry) +{ + u32 i, count; + GF_LOG(GF_LOG_DEBUG, GF_LOG_AUTHOR, ("[FileList] Adding file %s to list\n", fentry->file_name)); + if (ctx->fsort==FL_SORT_NONE) { + gf_list_add(ctx->file_list, fentry); + return; + } + count = gf_list_count(ctx->file_list); + for (i=0; ifile_list, i); + switch (ctx->fsort) { + case FL_SORT_SIZE: + if (cur->file_size>fentry->file_size) insert = GF_TRUE; + break; + case FL_SORT_DATE: + case FL_SORT_DATEX: + if (cur->last_mod_time>fentry->last_mod_time) insert = GF_TRUE; + break; + case FL_SORT_NAME: + if (strcmp(cur->file_name, fentry->file_name) > 0) insert = GF_TRUE; + break; + } + if (insert) { + gf_list_insert(ctx->file_list, fentry, i); + return; + } + } + gf_list_add(ctx->file_list, fentry); +} + +Bool filelist_enum(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) +{ + FileListEntry *fentry; + GF_FileListCtx *ctx = cbck; + if (file_info->hidden) return GF_FALSE; + if (file_info->directory) return GF_FALSE; + if (file_info->drive) return GF_FALSE; + if (file_info->system) return GF_FALSE; + + GF_SAFEALLOC(fentry, FileListEntry); + if (!fentry) return GF_TRUE; + + fentry->file_name = gf_strdup(item_path); + fentry->file_size = file_info->size; + fentry->last_mod_time = file_info->last_modified; + filelist_add_entry(ctx, fentry); + + return GF_FALSE; +} + +GF_Err filelist_initialize(GF_Filter *filter) +{ + u32 i, count; + char *sep_dir, c=0, *dir, *pattern; + GF_FileListCtx *ctx = gf_filter_get_udta(filter); + ctx->io_pids = gf_list_new(); + + ctx->filter_srcs = gf_list_new(); + if (ctx->ka) + ctx->floop = 0; + + if (!ctx->srcs || !gf_list_count(ctx->srcs)) { + if (! gf_filter_is_dynamic(filter)) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("[FileList] No inputs\n")); + } + return GF_OK; + } + ctx->file_list = gf_list_new(); + count = gf_list_count(ctx->srcs); + for (i=0; isrcs, i); + + if (strchr(list, '*') ) { + sep_dir = strrchr(list, '/'); + if (!sep_dir) sep_dir = strrchr(list, '\\'); + if (sep_dir) { + c = sep_dir[0]; + sep_dir[0] = 0; + dir = list; + pattern = sep_dir+2; + } else { + dir = "."; + pattern = list; + } + gf_enum_directory(dir, GF_FALSE, filelist_enum, ctx, pattern); + if (c && sep_dir) sep_dir[0] = c; + } else { + if (gf_file_exists(list)) { + FileListEntry *fentry; + GF_SAFEALLOC(fentry, FileListEntry); + if (fentry) { + FILE *fo; + fentry->file_name = gf_strdup(list); + fentry->last_mod_time = gf_file_modification_time(list); + fo = gf_fopen(list, "rb"); + if (fo) { + fentry->file_size = gf_fsize(fo); + gf_fclose(fo); + } + filelist_add_entry(ctx, fentry); + } + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[FileList] File %s not found, ignoring\n", list)); + } + } + } + + if (!gf_list_count(ctx->file_list)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[FileList] No files found in list %s\n", ctx->srcs)); + return GF_BAD_PARAM; + } + if (ctx->fsort==FL_SORT_DATEX) { + ctx->revert = GF_FALSE; + ctx->floop = 0; + } + ctx->file_list_idx = ctx->revert ? gf_list_count(ctx->file_list) : -1; + ctx->load_next = GF_TRUE; + //from now on we only accept the above caps + gf_filter_override_caps(filter, FileListCapsSrc, 2); + //and we act as a source, request processing + gf_filter_post_process_task(filter); + //prevent deconnection of filter when no input + gf_filter_make_sticky(filter); + return GF_OK; +} + +void filelist_finalize(GF_Filter *filter) +{ + GF_FileListCtx *ctx = gf_filter_get_udta(filter); + while (gf_list_count(ctx->io_pids)) { + FileListPid *iopid = gf_list_pop_back(ctx->io_pids); + gf_free(iopid); + } + if (ctx->file_list) { + while (gf_list_count(ctx->file_list)) { + FileListEntry *fentry = gf_list_pop_back(ctx->file_list); + gf_free(fentry->file_name); + gf_free(fentry); + } + gf_list_del(ctx->file_list); + } + gf_list_del(ctx->io_pids); + gf_list_del(ctx->filter_srcs); + if (ctx->file_path) gf_free(ctx->file_path); +} + + +#define OFFS(_n) #_n, offsetof(GF_FileListCtx, _n) +static const GF_FilterArgs GF_FileListArgs[] = +{ + { OFFS(floop), "loop playlist/list of files, `0` for one time, `n` for n+1 times, `-1` for indefinitely", GF_PROP_SINT, "0", NULL, 0}, + { OFFS(srcs), "list of files to play - see filter help", GF_PROP_STRING_LIST, NULL, NULL, 0}, + { OFFS(fdur), "for source files with a single frame, sets frame duration. 0/NaN fraction means reuse source timing which is usually not set!", GF_PROP_FRACTION, "1/25", NULL, 0}, + { OFFS(revert), "revert list of files (not playlist)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(timescale), "force output timescale on all pids. 0 uses the timescale of the first pid found", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ka), "keep playlist alive (disable loop), waiting the for a new input to be added or `#end` to end playlist. The value specify the refresh rate in ms", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + + { OFFS(fsort), "sort list of files\n" + "- no: no sorting, use default directory enumeration of OS\n" + "- name: sort by alphabetical name\n" + "- size: sort by increasing size\n" + "- date: sort by increasing modification time\n" + "- datex: sort by increasing modification time - see filter help" + , GF_PROP_UINT, "no", "no|name|size|date|datex", 0}, + {0} +}; + + +static const GF_FilterCapability FileListCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "txt|m3u"), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), +}; + + +GF_FilterRegister FileListRegister = { + .name = "flist", + GF_FS_SET_DESCRIPTION("Sources concatenator") + GF_FS_SET_HELP("This filter can be used to play playlist files or a list of sources.\n" + "\n" + "The filter loads any source supported by GPAC: remote or local files or streaming sessions (TS, RTP, DASH or other).\n" + "The filter forces input demultiplex and recomputes the input timestamps into a continuous timeline.\n" + "At each new source, the filter tries to remap input PIDs to already declared output PIDs of the same type, if any, or declares new output PIDs otherwise. If no input PID matches the type of an output, no packets are send for that PID.\n" + "\n" + "# Source list mode\n" + "The source list mode is activated by using `flist:srcs=f1[,f2]`, where f1 can be a file or a directory to enum.\n" + "The syntax for directory enum is:\n" + "- dir/*: enumerates everything in dir\n" + "- foo/*.png: enumerates all files with extension png in foo\n" + "- foo/*.png;*.jpg: enumerates all files with extension png or jpg in foo\n" + "\n" + "The resulting file list can be sorted using [-fsort]().\n" + "If the sort mode is `datex` and source files are images or single frame files, the following applies:\n" + "- options [-floop](), [-revert]() and [-dur]() are ignored\n" + "- the files are sorted by modification time\n" + "- the first frame is assigned a timestamp of 0\n" + "- each frame (coming from each file) is assigned a duration equal to the difference of modification time between the file and the next file\n" + "- the last frame is assigned the same duration as the previous one\n" + "\n" + "# Playlist mode\n" + "The playlist mode is activated when opening a playlist file (extension txt or m3u).\n" + "In this mode, directives can be given in a comment line, i.e. a line starting with '#' before the line with the file name.\n" + "The following directives, separated with space or comma, are supported:\n" + "- repeat=N: repeats N times the content (hence played N+1).\n" + "- start=T: tries to play the file from start time T seconds (double format only). This may not work with some files/formats not supporting seeking.\n" + "- stop=T: stops source playback after T seconds (double format only). This works on any source (implemented independently from seek support).\n" + "- cat: specifies that the following entry should be concatenated to the previous source rather than opening a new source. This can optionnally specify a byte range if desired, otherwise the full file is concatenated.\n" + "- srange=T: when cat is set, indicates the start T (64 bit decimal, default 0) of the byte range from the next entry to concatenate.\n" + "- send=T: when cat is set, indicates the end T (64 bit decimal, default 0) of the byte range from the next entry to concatenate.\n" + "\n" + "Note: When sources are ISOBMFF files or segments on local storage or GF_FileIO objects, the concatenation will be automatically detected.\n" + "\n" + "The source lines follow the usual source syntax, see `gpac -h`.\n" + "Additional pid properties can be added per source (see `gpac -h doc`), but are valid only for the current source, and reset at next source.\n" + "\n" + "The URL given can either be a single URL, or a list of URLs separated by \" && \" to load several sources for the active entry.\n" + "EX audio.mp4 && video.mp4\n" + "\n" + "The playlist file is refreshed whenever the next source has to be reloaded in order to allow for dynamic pushing of sources in the playlist.\n"\ + "If the last URL played cannot be found in the playlist, the first URL in the playlist file will be loaded.\n") + .private_size = sizeof(GF_FileListCtx), + .max_extra_pids = -1, + .flags = GF_FS_REG_ACT_AS_SOURCE | GF_FS_REG_REQUIRES_RESOLVER, + .args = GF_FileListArgs, + .initialize = filelist_initialize, + .finalize = filelist_finalize, + SETCAPS(FileListCaps), + .configure_pid = filelist_configure_pid, + .process = filelist_process, + .process_event = filelist_process_event +}; + +const GF_FilterRegister *filelist_register(GF_FilterSession *session) +{ + return &FileListRegister; +} + diff --git a/src/filters/hevcmerge.c b/src/filters/hevcmerge.c new file mode 100644 index 0000000..794afc9 --- /dev/null +++ b/src/filters/hevcmerge.c @@ -0,0 +1,1652 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Yacine Mathurin Boubacar Aziakou + * Samir Mustapha + * Copyright (c) Telecom ParisTech 2019 + * All rights reserved + * + * This file is part of GPAC / HEVC tile merger filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include + +#if !defined(GPAC_DISABLE_HEVC) && !defined(GPAC_DISABLE_AV_PARSERS) + +typedef struct +{ + GF_FilterPid *pid; + u32 slice_segment_address, width, height; + Bool in_error; + + u32 nalu_size_length; + u32 dsi_crc; + HEVCState hevc_state; + + //final position in grid - the row index is only used to push non multiple of CU height at the bottom of the grid + u32 pos_row, pos_col; + + //timescale of source pid + //number of packets processed + u32 timescale; + u32 nb_pck; + + //true if positioning in pixel is given + Bool has_pos; + // >=0: positioning in pixel in the Y plane as given by CropOrigin + // <=0: positioning relative to top-left tile + s32 pos_x, pos_y; +} HEVCTilePidCtx; + + +typedef struct { + //width of column + u32 width; + //cumulated height of all slices in column + u32 height; + //only used while computing the grid, current position in rows in the column + u32 row_pos, max_row_pos; + u32 last_row_idx; + u32 pos_x; +} HEVCGridInfo; + +typedef struct +{ + //options + Bool strict, mrows; + + GF_FilterPid *opid; + s32 base_pps_init_qp_delta_minus26; + u32 nb_bits_per_address_dst; + u32 out_width, out_height; + u8 *buffer_nal, *buffer_nal_no_epb, *buffer_nal_in_no_epb; + u32 buffer_nal_alloc, buffer_nal_no_epb_alloc, buffer_nal_in_no_epb_alloc; + GF_BitStream *bs_au_in; + + GF_BitStream *bs_nal_in; + GF_BitStream *bs_nal_out; + + HEVCGridInfo *grid; + u32 nb_cols; + + u8 *sei_suffix_buf; + u32 sei_suffix_len, sei_suffix_alloc; + u32 hevc_nalu_size_length; + u32 max_CU_width, max_CU_height; + + GF_List *pids, *ordered_pids; + Bool in_error; + Bool enable_multi_rows; + u32 nb_rows; +} GF_HEVCMergeCtx; + +//in src/filters/hevcsplit.c +void hevc_rewrite_sps(char *in_SPS, u32 in_SPS_length, u32 width, u32 height, char **out_SPS, u32 *out_SPS_length); + +#if 0 //todo +//rewrite the profile and level +static void write_profile_tier_level(GF_BitStream *ctx->bs_nal_in, GF_BitStream *ctx->bs_nal_out, Bool ProfilePresentFlag, u8 MaxNumSubLayersMinus1) +{ + u8 j; + Bool sub_layer_profile_present_flag[8], sub_layer_level_present_flag[8]; + if (ProfilePresentFlag) { + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 8), 8); + gf_bs_write_long_int(ctx->bs_nal_out, gf_bs_read_long_int(ctx->bs_nal_in, 32), 32); + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 4), 4); + gf_bs_write_long_int(ctx->bs_nal_out, gf_bs_read_long_int(ctx->bs_nal_in, 44), 44); + } + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 8), 8); + + for (j = 0; j < MaxNumSubLayersMinus1; j++) { + sub_layer_profile_present_flag[j] = gf_bs_read_int(ctx->bs_nal_in, 1); + gf_bs_write_int(ctx->bs_nal_out, sub_layer_profile_present_flag[j], 1); + sub_layer_level_present_flag[j] = gf_bs_read_int(ctx->bs_nal_in, 1); + gf_bs_write_int(ctx->bs_nal_out, sub_layer_level_present_flag[j], 1); + } + if (MaxNumSubLayersMinus1 > 0) + for (j = MaxNumSubLayersMinus1; j < 8; j++) + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 2), 2); + + + for (j = 0; j < MaxNumSubLayersMinus1; j++) { + if (sub_layer_profile_present_flag[j]) { + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 8), 8); + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 32), 32); + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 4), 4); + gf_bs_write_long_int(ctx->bs_nal_out, gf_bs_read_long_int(ctx->bs_nal_in, 44), 44); + } + if (sub_layer_level_present_flag[j]) + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 8), 8); + } +} +#endif + +static void hevcmerge_rewrite_pps(GF_HEVCMergeCtx *ctx, char *in_PPS, u32 in_PPS_length, char **out_PPS, u32 *out_PPS_length) +{ + u8 cu_qp_delta_enabled_flag; + u32 loop_filter_flag; + u32 pps_size_no_epb; + + gf_bs_reassign_buffer(ctx->bs_nal_in, in_PPS, in_PPS_length); + gf_bs_enable_emulation_byte_removal(ctx->bs_nal_in, GF_TRUE); + if (!ctx->bs_nal_out) ctx->bs_nal_out = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs_nal_out, ctx->buffer_nal_no_epb, ctx->buffer_nal_no_epb_alloc); + + //Read and write NAL header bits + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 16), 16); + gf_bs_set_ue(ctx->bs_nal_out, gf_bs_get_ue(ctx->bs_nal_in)); //pps_pic_parameter_set_id + gf_bs_set_ue(ctx->bs_nal_out, gf_bs_get_ue(ctx->bs_nal_in)); //pps_seq_parameter_set_id + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 7), 7); //from dependent_slice_segments_enabled_flag to cabac_init_present_flag + gf_bs_set_ue(ctx->bs_nal_out, gf_bs_get_ue(ctx->bs_nal_in)); //num_ref_idx_l0_default_active_minus1 + gf_bs_set_ue(ctx->bs_nal_out, gf_bs_get_ue(ctx->bs_nal_in)); //num_ref_idx_l1_default_active_minus1 + gf_bs_set_se(ctx->bs_nal_out, gf_bs_get_se(ctx->bs_nal_in)); //init_qp_minus26 + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 2), 2); //from constrained_intra_pred_flag to transform_skip_enabled_flag + cu_qp_delta_enabled_flag = gf_bs_read_int(ctx->bs_nal_in, 1); //cu_qp_delta_enabled_flag + gf_bs_write_int(ctx->bs_nal_out, cu_qp_delta_enabled_flag, 1); // + if (cu_qp_delta_enabled_flag) + gf_bs_set_ue(ctx->bs_nal_out, gf_bs_get_ue(ctx->bs_nal_in)); // diff_cu_qp_delta_depth + gf_bs_set_se(ctx->bs_nal_out, gf_bs_get_se(ctx->bs_nal_in)); // pps_cb_qp_offset + gf_bs_set_se(ctx->bs_nal_out, gf_bs_get_se(ctx->bs_nal_in)); // pps_cr_qp_offset + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 4), 4); // from pps_slice_chroma_qp_offsets_present_flag to transquant_bypass_enabled_flag + + + gf_bs_read_int(ctx->bs_nal_in, 1); + gf_bs_write_int(ctx->bs_nal_out, 1, 1); + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 1), 1);//entropy_coding_sync_enabled_flag + gf_bs_set_ue(ctx->bs_nal_out, ctx->nb_cols-1);//write num_tile_columns_minus1 + //num_tile_rows_minus1 + if (ctx->enable_multi_rows) { + u32 nb_rows = ctx->nb_rows - 1; + assert(ctx->nb_rows); + gf_bs_set_ue(ctx->bs_nal_out, nb_rows); + } else { + gf_bs_set_ue(ctx->bs_nal_out, 0);//num_tile_rows_minus1 + } + gf_bs_write_int(ctx->bs_nal_out, 0, 1); //uniform_spacing_flag + +// if (!uniform_spacing_flag) //always 0 + { + u32 i; + for (i = 0; i < ctx->nb_cols-1; i++) + gf_bs_set_ue(ctx->bs_nal_out, (ctx->grid[i].width / ctx->max_CU_width - 1)); + + //if multi row is possible, declare the row height + if (ctx->enable_multi_rows && (ctx->nb_rows>1)) { + u32 nb_rows = ctx->nb_rows - 1; + u32 nb_pids = gf_list_count(ctx->pids); + + for (i=0; iordered_pids, i); + //only check height in the first column + if (tile->pos_col) continue; + gf_bs_set_ue(ctx->bs_nal_out, (tile->height / ctx->max_CU_width - 1)); // row_height_minus1[i] + nb_rows--; + if (!nb_rows) break; + } + } + //otherwise nothing to declare since we use a single row + } + loop_filter_flag = 1; + gf_bs_write_int(ctx->bs_nal_out, loop_filter_flag, 1); + + loop_filter_flag = gf_bs_read_int(ctx->bs_nal_in, 1); + gf_bs_write_int(ctx->bs_nal_out, loop_filter_flag, 1); + + // copy and write the rest of the bits in the byte + while (gf_bs_get_bit_position(ctx->bs_nal_in) != 8) { + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 1), 1); + } + + //copy and write the rest of the bytes + while (gf_bs_get_size(ctx->bs_nal_in) != gf_bs_get_position(ctx->bs_nal_in)) { + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_u8(ctx->bs_nal_in), 8); //watchout, not aligned in destination bitstream + } + + gf_bs_align(ctx->bs_nal_out); + + gf_bs_get_content_no_truncate(ctx->bs_nal_out, &ctx->buffer_nal_no_epb, &pps_size_no_epb, &ctx->buffer_nal_no_epb_alloc); + + *out_PPS_length = pps_size_no_epb + gf_media_nalu_emulation_bytes_add_count(ctx->buffer_nal_no_epb, pps_size_no_epb); + *out_PPS = gf_malloc(*out_PPS_length); + gf_media_nalu_add_emulation_bytes(ctx->buffer_nal_no_epb, *out_PPS, pps_size_no_epb); +} + +u32 hevcmerge_rewrite_slice(GF_HEVCMergeCtx *ctx, HEVCTilePidCtx *tile_pid, char *in_slice, u32 in_slice_length) +{ + u64 header_end; + u32 out_slice_size_no_epb = 0, out_slice_length; + u32 num_entry_point_start; + u32 pps_id; + Bool RapPicFlag = GF_FALSE; + u32 slice_qp_delta_start; + HEVC_PPS *pps; + HEVC_SPS *sps; + u32 al, slice_size, in_slice_size_no_epb, slice_offset_orig, slice_offset_dst; + u32 first_slice_segment_in_pic_flag; + u32 dependent_slice_segment_flag; + u8 nal_unit_type; + s32 new_slice_qp_delta; + + HEVCState *hevc = &tile_pid->hevc_state; + + //we remove EPB directly rather than from bs reader, since we will have to copy the entire payload without EPB + //and gf_bs_read_data does not check for EPB + if (ctx->buffer_nal_in_no_epb_allocbuffer_nal_in_no_epb_alloc = in_slice_length; + ctx->buffer_nal_in_no_epb = gf_realloc(ctx->buffer_nal_in_no_epb, in_slice_length); + } + in_slice_size_no_epb = gf_media_nalu_remove_emulation_bytes(in_slice, ctx->buffer_nal_in_no_epb, in_slice_length); + gf_bs_reassign_buffer(ctx->bs_nal_in, ctx->buffer_nal_in_no_epb, in_slice_size_no_epb); + //disable EPB removal + gf_bs_enable_emulation_byte_removal(ctx->bs_nal_in, GF_FALSE); + if (!ctx->bs_nal_out) ctx->bs_nal_out = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs_nal_out, ctx->buffer_nal_no_epb, ctx->buffer_nal_no_epb_alloc); + + assert(hevc->s_info.header_size_bits >= 0); + assert(hevc->s_info.entry_point_start_bits >= 0); + header_end = (u64)hevc->s_info.header_size_bits; + + num_entry_point_start = (u32)hevc->s_info.entry_point_start_bits; + slice_qp_delta_start = (u32)hevc->s_info.slice_qp_delta_start_bits; + + // nal_unit_header + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 1), 1); + nal_unit_type = gf_bs_read_int(ctx->bs_nal_in, 6); + gf_bs_write_int(ctx->bs_nal_out, nal_unit_type, 6); + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 9), 9); + + first_slice_segment_in_pic_flag = gf_bs_read_int(ctx->bs_nal_in, 1); //first_slice_segment_in_pic_flag + if (tile_pid->slice_segment_address == 0) + gf_bs_write_int(ctx->bs_nal_out, 1, 1); + else + gf_bs_write_int(ctx->bs_nal_out, 0, 1); + + switch (nal_unit_type) { + case GF_HEVC_NALU_SLICE_IDR_W_DLP: + case GF_HEVC_NALU_SLICE_IDR_N_LP: + RapPicFlag = GF_TRUE; + break; + case GF_HEVC_NALU_SLICE_BLA_W_LP: + case GF_HEVC_NALU_SLICE_BLA_W_DLP: + case GF_HEVC_NALU_SLICE_BLA_N_LP: + case GF_HEVC_NALU_SLICE_CRA: + RapPicFlag = GF_TRUE; + break; + } + + if (RapPicFlag) { + //no_output_of_prior_pics_flag + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 1), 1); + } + + pps_id = gf_bs_get_ue(ctx->bs_nal_in); + gf_bs_set_ue(ctx->bs_nal_out, pps_id); + + pps = &hevc->pps[pps_id]; + sps = &hevc->sps[pps->sps_id]; + + if (!first_slice_segment_in_pic_flag && pps->dependent_slice_segments_enabled_flag) { + dependent_slice_segment_flag = gf_bs_read_int(ctx->bs_nal_in, 1); + } else { + dependent_slice_segment_flag = GF_FALSE; + } + if (!first_slice_segment_in_pic_flag) { + /*address_ori = */gf_bs_read_int(ctx->bs_nal_in, sps->bitsSliceSegmentAddress); + } + //else original slice segment address = 0 + + if (tile_pid->slice_segment_address > 0) { + if (pps->dependent_slice_segments_enabled_flag) { + gf_bs_write_int(ctx->bs_nal_out, dependent_slice_segment_flag, 1); + } + gf_bs_write_int(ctx->bs_nal_out, tile_pid->slice_segment_address, ctx->nb_bits_per_address_dst); + } + //else first slice in pic, no address + + //copy over bits until start of slice_qp_delta + while (slice_qp_delta_start != (gf_bs_get_position(ctx->bs_nal_in) - 1) * 8 + gf_bs_get_bit_position(ctx->bs_nal_in)) { + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 1), 1); + } + //compute new qp delta + new_slice_qp_delta = hevc->s_info.pps->pic_init_qp_minus26 + hevc->s_info.slice_qp_delta - ctx->base_pps_init_qp_delta_minus26; + gf_bs_set_se(ctx->bs_nal_out, new_slice_qp_delta); + gf_bs_get_se(ctx->bs_nal_in); + + //copy over until num_entry_points + while (num_entry_point_start != (gf_bs_get_position(ctx->bs_nal_in) - 1) * 8 + gf_bs_get_bit_position(ctx->bs_nal_in)) { + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 1), 1); + } + //write num_entry_points to 0 (always present since we use tiling) + gf_bs_set_ue(ctx->bs_nal_out, 0); + + //write slice extension to 0 + if (pps->slice_segment_header_extension_present_flag) + gf_bs_write_int(ctx->bs_nal_out, 0, 1); + + + //we may have unparsed data in the source bitstream (slice header) due to entry points or slice segment extensions + //TODO: we might want to copy over the slice extension header bits + while (header_end != (gf_bs_get_position(ctx->bs_nal_in) - 1) * 8 + gf_bs_get_bit_position(ctx->bs_nal_in)) + gf_bs_read_int(ctx->bs_nal_in, 1); + + //read byte_alignment() is bit=1 + x bit=0 + al = gf_bs_read_int(ctx->bs_nal_in, 1); + if (al != 1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[HEVCMerge] source slice header not properly aligned\n")); + } + gf_bs_align(ctx->bs_nal_in); + + //write byte_alignment() is bit=1 + x bit=0 + gf_bs_write_int(ctx->bs_nal_out, 1, 1); + gf_bs_align(ctx->bs_nal_out); // align + + //get the final slice header + gf_bs_get_content_no_truncate(ctx->bs_nal_out, &ctx->buffer_nal_no_epb, &out_slice_size_no_epb, &ctx->buffer_nal_no_epb_alloc); + + //get the slice payload size (no EPB in it) and offset in payload + slice_size = (u32) gf_bs_available(ctx->bs_nal_in); + slice_offset_orig = (u32) gf_bs_get_position(ctx->bs_nal_in); + //slice data offset in output slice + slice_offset_dst = out_slice_size_no_epb; + //update output slice size + out_slice_size_no_epb += slice_size; + + //copy slice data + if (ctx->buffer_nal_no_epb_alloc < out_slice_size_no_epb) { + ctx->buffer_nal_no_epb_alloc = out_slice_size_no_epb; + ctx->buffer_nal_no_epb = gf_realloc(ctx->buffer_nal_no_epb, out_slice_size_no_epb); + } + memcpy(ctx->buffer_nal_no_epb + slice_offset_dst, ctx->buffer_nal_in_no_epb + slice_offset_orig, sizeof(char) * slice_size); + + //insert epb + out_slice_length = out_slice_size_no_epb + gf_media_nalu_emulation_bytes_add_count(ctx->buffer_nal_no_epb, out_slice_size_no_epb); + if (ctx->buffer_nal_alloc < out_slice_length) { + ctx->buffer_nal_alloc = out_slice_length; + ctx->buffer_nal = gf_realloc(ctx->buffer_nal, out_slice_length); + } + gf_media_nalu_add_emulation_bytes(ctx->buffer_nal_no_epb, ctx->buffer_nal, out_slice_size_no_epb); + return out_slice_length; +} + +static GF_Err hevcmerge_rewrite_config(GF_HEVCMergeCtx *ctx, GF_FilterPid *opid, char *data, u32 size) +{ + u32 i, j; + u8 *new_dsi; + u32 new_size; + GF_HEVCConfig *hvcc = gf_odf_hevc_cfg_read(data, size, GF_FALSE); + if (!hvcc) return GF_NON_COMPLIANT_BITSTREAM; + + // for all the list objects in param_array + for (i = 0; i < gf_list_count(hvcc->param_array); i++) { // hvcc->param_array:list object + GF_HEVCParamArray *ar = (GF_HEVCParamArray *) gf_list_get(hvcc->param_array, i); // ar contains the i-th item in param_array + for (j = 0; j < gf_list_count(ar->nalus); j++) { // for all the nalus the i-th param got + /*! used for storing AVC sequenceParameterSetNALUnit and pictureParameterSetNALUnit*/ + GF_AVCConfigSlot *sl = (GF_AVCConfigSlot *)gf_list_get(ar->nalus, j); // store j-th nalus in *sl + + if (ar->type == GF_HEVC_NALU_SEQ_PARAM) { + char *outSPS=NULL; + u32 outSize=0; + hevc_rewrite_sps(sl->data, sl->size, ctx->out_width, ctx->out_height, &outSPS, &outSize); + gf_free(sl->data); + sl->data = outSPS; + sl->size = outSize; + } + else if (ar->type == GF_HEVC_NALU_VID_PARAM) { + } + else if (ar->type == GF_HEVC_NALU_PIC_PARAM) { + char *outPPS=NULL; + u32 outSize=0; + hevcmerge_rewrite_pps(ctx, sl->data, sl->size, &outPPS, &outSize); + gf_free(sl->data); + sl->data = outPPS; + sl->size = outSize; + } + } + } + gf_odf_hevc_cfg_write(hvcc, &new_dsi, &new_size); + gf_odf_hevc_cfg_del(hvcc); + + gf_filter_pid_set_property(opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(new_dsi, new_size)); + return GF_OK; +} + +static void hevcmerge_write_nal(GF_HEVCMergeCtx *ctx, char *output_nal, char *rewritten_nal, u32 out_nal_size) +{ + u32 n = 8*(ctx->hevc_nalu_size_length); + while (n) { + u32 v = (out_nal_size >> (n-8)) & 0xFF; + *output_nal = v; + output_nal++; + n-=8; + } + memcpy(output_nal, rewritten_nal, out_nal_size); +} + +static u32 hevcmerge_compute_address(GF_HEVCMergeCtx *ctx, HEVCTilePidCtx *tile_pid, Bool use_y_coord) +{ + u32 i, nb_pids, sum_height = 0, sum_width = 0; + for (i=0; ipos_col; i++) { + sum_width += ctx->grid[i].width / ctx->max_CU_width; + } + + if (use_y_coord) { + sum_height = tile_pid->pos_y / ctx->max_CU_height; + } else { + nb_pids = gf_list_count(ctx->pids); + for (i=0; ipids, i); + if (actx->pos_col == tile_pid->pos_col) { + if (actx->pos_rowpos_row) { + sum_height += actx->height / ctx->max_CU_height; + } + } + } + } + return sum_height * (ctx->out_width / ctx->max_CU_width) + sum_width; +} + +void hevcmerge_build_srdmap(GF_HEVCMergeCtx *ctx, Bool use_abs_pos) +{ + GF_PropertyValue srdmap; + u32 i, nb_pids; + Bool srd_map_valid = GF_TRUE; + + if (!ctx->out_width || !ctx->out_height) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SRD_MAP, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SRD_REF, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SRD, NULL); + return; + } + + nb_pids = gf_list_count(ctx->ordered_pids); + + + memset(&srdmap, 0, sizeof(GF_PropertyValue)); + srdmap.type = GF_PROP_UINT_LIST; + //8 integers per PID + srdmap.value.uint_list.nb_items = nb_pids*8; + srdmap.value.uint_list.vals = gf_malloc(sizeof(s32)*nb_pids*8); + + s32 *vals = srdmap.value.uint_list.vals; + + for (i=0; iordered_pids, i); + + const GF_PropertyValue *srd = gf_filter_pid_get_property(tile->pid, GF_PROP_PID_SRD); + const GF_PropertyValue *srd_ref = gf_filter_pid_get_property(tile->pid, GF_PROP_PID_SRD_REF); + const GF_PropertyValue *crop_pos = gf_filter_pid_get_property(tile->pid, GF_PROP_PID_CROP_POS); + + //get original SRD of the PID, translate into output referential {out_width,out_height} + if (srd && srd_ref) { + srd_x = srd->value.vec4i.x; + srd_y = srd->value.vec4i.y; + srd_w = srd->value.vec4i.z; + srd_h = srd->value.vec4i.w; + + if (srd_ref->value.vec2i.x != 0) { + x = (srd_x * ctx->out_width) / srd_ref->value.vec2i.x; + w = (srd_w * ctx->out_width) / srd_ref->value.vec2i.x; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("[HEVCMerge] width=0 in source pid SRD referential, cannot output SRD map\n" )); + srd_map_valid = GF_FALSE; + break; + } + if (srd_ref->value.vec2i.y != 0) { + y = (srd_y * ctx->out_height) / srd_ref->value.vec2i.y; + h = (srd_h * ctx->out_height) / srd_ref->value.vec2i.y; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("[HEVCMerge] height=0 in source pid SRD referential, undefined results\n" )); + srd_map_valid = GF_FALSE; + break; + } + } else if (crop_pos) { + x = crop_pos->value.vec2i.x; + y = crop_pos->value.vec2i.y; + w = (s32)(tile->width); + h = (s32)(tile->height); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_FILTER, ("[HEVCMerge] SRD, SRD_REF and CROP_POS are not defined for source pid, undefined results\n" )); + srd_map_valid = GF_FALSE; + break; + } + vals[8*i] = x; + vals[8*i+1] = y; + vals[8*i+2] = w; + vals[8*i+3] = h; + + //get final position in the recomputed video, expressed in output referential {out_width,out_height} + //we recompute the position directly from the slice segment address + width_in_CU = ctx->out_width / ctx->max_CU_width; + nb_cols = tile->slice_segment_address % width_in_CU; + nb_rows = (tile->slice_segment_address - nb_cols) / width_in_CU; + + assert(tile->slice_segment_address == (nb_rows * width_in_CU + nb_cols)); + + //tile_x and tile_y should start at zero + tile_x = use_abs_pos ? nb_cols * ctx->max_CU_width : nb_cols; + tile_y = use_abs_pos ? nb_rows * ctx->max_CU_height : nb_rows; + + vals[8*i+4] = (tile_x * ctx->out_width) / ctx->out_width; + vals[8*i+5] = (tile_y * ctx->out_height) / ctx->out_height; + vals[8*i+6] = (tile->width * ctx->out_width) / ctx->out_width; + vals[8*i+7] = (tile->height * ctx->out_height) / ctx->out_height; + } + if (srd_map_valid) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SRD_MAP, &srdmap); + + gf_free(srdmap.value.uint_list.vals); + + //assign reference space + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SRD_REF, &PROP_VEC2I_INT(ctx->out_width, ctx->out_height)); + //remove any SRD copied from inputs - we could also create an SRD {0,0,out_width,out_height} + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SRD, NULL); +} + +static GF_Err hevcmerge_rebuild_grid(GF_HEVCMergeCtx *ctx, GF_FilterPid *pid) +{ + u32 nb_cols=0; + u32 nb_rows=0; + Bool reorder_rows=GF_FALSE; + u32 force_last_col_plus_one=0; + u32 nb_has_pos=0, nb_no_pos=0, nb_rel_pos=0, nb_abs_pos=0; + u32 min_rel_pos_x = (u32) -1; + u32 min_rel_pos_y = (u32) -1; + u32 i, j, max_cols, nb_pids = gf_list_count(ctx->pids); + + for (i=0; ipids, i); + if (apidctx->width % ctx->max_CU_width) nb_cols++; + if (apidctx->height % ctx->max_CU_height) nb_rows++; + if (apidctx->has_pos) { + nb_has_pos++; + if ((apidctx->pos_x<0) || (apidctx->pos_y<0)) { + nb_rel_pos++; + } + else if ((apidctx->pos_x>0) || (apidctx->pos_y>0)) nb_abs_pos++; + + if ((apidctx->pos_x<=0) && ((u32) (-apidctx->pos_x) < min_rel_pos_x)) + min_rel_pos_x = -apidctx->pos_x; + if ((apidctx->pos_y<=0) && ((u32) (-apidctx->pos_y) < min_rel_pos_y)) + min_rel_pos_y = -apidctx->pos_y; + } else { + nb_no_pos++; + } + } + if ((nb_cols>1) && (nb_rows>1)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[HEVCMerge] Cannot merge more than one tile not a multiple of CTUs in both width and height, not possible in standard\n")); + return GF_BAD_PARAM; + } + if (nb_has_pos && nb_no_pos) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[HEVCMerge] Cannot merge tiles with explicit positioning and tiles with implicit positioning, not supported\n")); + return GF_BAD_PARAM; + } + if (nb_rel_pos && nb_abs_pos) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[HEVCMerge] Cannot merge tiles with both relative explicit positioning and absolute explicit positioning, not supported\n")); + return GF_BAD_PARAM; + } + if (ctx->grid) gf_free(ctx->grid); + ctx->grid = NULL; + + if (min_rel_pos_x == (u32)-1) min_rel_pos_x=0; + + if (nb_has_pos) { + max_cols = 0; + for (i=0; ipids, i); + if (!tile1->has_pos) continue; + + for (j=0; jpids, j); + if (tile2 == tile1) continue; + if (!tile2->has_pos) continue; + + //for relative positioning, only check we don't have the same indexes + if (nb_rel_pos) { + continue; + } + + //make sure we are aligned + if ((tile1->pos_xpos_x) && (tile1->pos_x + (s32) tile1->width > tile2->pos_x)) { + overlap = GF_TRUE; + } + else if ((tile1->pos_x==tile2->pos_x) && (tile1->width != tile2->width)) { + overlap = GF_TRUE; + } + if (tile1->pos_x==tile2->pos_x) { + if ((tile1->pos_ypos_y) && (tile1->pos_y + (s32) tile1->height > tile2->pos_y)) { + overlap = GF_TRUE; + } + else if ((tile1->pos_y==tile2->pos_y) && (tile1->height != tile2->height)) { + overlap = GF_TRUE; + } + } + + if (overlap) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[HEVCMerge] Overlapping tiles detected, cannot merge\n")); + return GF_BAD_PARAM; + } + } + + if (!max_cols) { + ctx->grid = gf_malloc(sizeof(HEVCGridInfo)); + memset(&ctx->grid[0], 0, sizeof(HEVCGridInfo)); + + ctx->grid[0].width = tile1->width; + if (nb_rel_pos) { + ctx->grid[0].pos_x = -tile1->pos_x - min_rel_pos_x; + } else { + ctx->grid[0].pos_x = tile1->pos_x; + } + max_cols=1; + } else if (ctx->grid[max_cols-1].pos_x != tile1->pos_x) { + if (nb_rel_pos) { + //append + if (ctx->grid[max_cols-1].pos_x <(u32) -tile1->pos_x) { + ctx->grid = gf_realloc(ctx->grid, sizeof(HEVCGridInfo) * (max_cols+1) ); + memset(&ctx->grid[max_cols], 0, sizeof(HEVCGridInfo)); + ctx->grid[max_cols].width = tile1->width; + ctx->grid[max_cols].pos_x = -tile1->pos_x; + max_cols+=1; + } + //insert + else { + Bool found=GF_FALSE; + for (j=0; jgrid[j].pos_x == -tile1->pos_x) { + found=GF_TRUE; + break; + } + } + if (!found) { + for (j=0; jgrid[j].pos_x > (u32) -tile1->pos_x) { + ctx->grid = gf_realloc(ctx->grid, sizeof(HEVCGridInfo) * (max_cols+1) ); + memmove(&ctx->grid[j+1], &ctx->grid[j], sizeof(HEVCGridInfo) * (max_cols-j)); + memset(&ctx->grid[j], 0, sizeof(HEVCGridInfo)); + ctx->grid[j].width = tile1->width; + ctx->grid[j].pos_x = -tile1->pos_x; + max_cols+=1; + break; + } + } + } + } + } else { + //append + if (ctx->grid[max_cols-1].pos_x + ctx->grid[max_cols-1].width <= (u32) tile1->pos_x) { + ctx->grid = gf_realloc(ctx->grid, sizeof(HEVCGridInfo) * (max_cols+1) ); + memset(&ctx->grid[max_cols], 0, sizeof(HEVCGridInfo)); + ctx->grid[max_cols].width = tile1->width; + ctx->grid[max_cols].pos_x = tile1->pos_x; + max_cols+=1; + } + //insert + else { + for (j=0; jgrid[j].pos_x == (u32) tile1->pos_x) { + break; + } + if (ctx->grid[j].pos_x > (u32) tile1->pos_x) { + ctx->grid = gf_realloc(ctx->grid, sizeof(HEVCGridInfo) * (max_cols+1) ); + memmove(&ctx->grid[j+1], &ctx->grid[j], sizeof(HEVCGridInfo) * (max_cols-j)); + memset(&ctx->grid[j], 0, sizeof(HEVCGridInfo)); + ctx->grid[j].width = tile1->width; + ctx->grid[j].pos_x = tile1->pos_x; + max_cols+=1; + break; + } + } + } + } + } + } + if (!ctx->grid) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[HEVCMerge] Failed to create grid\n")); + return GF_OUT_OF_MEM; + } + // pass on the grid to insert empty columns + if (!nb_rel_pos && ctx->grid) { + for (j=0; jgrid[j].pos_x + ctx->grid[j].width <= ctx->grid[j+1].pos_x); + if (ctx->grid[j].pos_x + ctx->grid[j].width != ctx->grid[j+1].pos_x) { + u32 new_width = ctx->grid[j+1].pos_x - ctx->grid[j].pos_x - ctx->grid[j].width; + u32 new_x = ctx->grid[j].pos_x + ctx->grid[j].width; + + ctx->grid = gf_realloc(ctx->grid, sizeof(HEVCGridInfo) * (max_cols+1) ); + memmove(&ctx->grid[j+2], &ctx->grid[j+1], sizeof(HEVCGridInfo) * (max_cols-j-1)); + + memset(&ctx->grid[j+1], 0, sizeof(HEVCGridInfo)); + ctx->grid[j+1].width = new_width; + ctx->grid[j+1].pos_x = new_x; + max_cols+=1; + j++; + } + } + } + + //assign cols and rows + for (i=0; ipids, i); + tile->pos_col=0; + tile->pos_row=0; + } + + for (j=0; jgrid[j].height = 0; + } + //check non-last columns are multiple of max CU width + if ((j+1grid[j].width % ctx->max_CU_height) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[HEVCMerge] Invalid grid specification, column %d width %d not a multiple of max CU width and not the last one\n", j+1, ctx->grid[j].width)); + return GF_BAD_PARAM; + } + for (i=0; ipids, i); + if (nb_rel_pos) { + if (-tile->pos_x != ctx->grid[j].pos_x) continue; + if (ctx->grid[j].width != tile->width) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[HEVCMerge] Invalid relative positioning in the same column of tiles with different width %d vs %d\n", tile->width, ctx->grid[j].width)); + return GF_BAD_PARAM; + } + tile->pos_col = j; + tile->pos_row = -tile->pos_y; + ctx->grid[j].height += tile->height; + } else { + if (tile->pos_x != ctx->grid[j].pos_x) continue; + tile->pos_col = j; + tile->pos_row = tile->pos_y / ctx->max_CU_height; + if (tile->pos_row * ctx->max_CU_height != tile->pos_y) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[HEVCMerge] Tile y %d not a multiple of CU height %d, adjusting to next boundary\n", tile->pos_y, ctx->max_CU_height)); + tile->pos_row++; + } + if (tile->pos_y + tile->height > ctx->grid[j].height) + ctx->grid[j].height = tile->pos_y + tile->height; + } + + if (tile->pos_row > ctx->grid[j].max_row_pos) + ctx->grid[j].max_row_pos = tile->pos_row; + } + } + //check non-last rows are multiple of max CU height + for (j=0; jpids, i); + if (tile->pos_col != j) continue; + if ((tile->pos_row < ctx->grid[j].max_row_pos) && (tile->height % ctx->max_CU_height)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[HEVCMerge] Invalid grid specification, row %d in column %d height %d not a multiple of max CU height and not the last one\n", tile->pos_row, j+1, tile->height)); + return GF_BAD_PARAM; + } + } + } + nb_cols = max_cols; + + } else { + //gather tiles per columns + ctx->grid = gf_malloc(sizeof(HEVCGridInfo)*nb_pids); + memset(ctx->grid, 0, sizeof(HEVCGridInfo)*nb_pids); + + nb_cols=0; + for (i=0; ipids, i); + //do we fit on a col + for (j=0; jgrid[j].width == apidctx->width) { + found = GF_TRUE; + break; + } + } + //force new column + if ((apidctx->height % ctx->max_CU_height) && found && ctx->grid[j].last_row_idx) + found = GF_FALSE; + + if (!found) { + ctx->grid[nb_cols].width = apidctx->width; + ctx->grid[nb_cols].height = apidctx->height; + if (apidctx->width % ctx->max_CU_width) { + assert(!force_last_col_plus_one); + force_last_col_plus_one = nb_cols+1; + } + if (apidctx->height % ctx->max_CU_height) { + ctx->grid[nb_cols].last_row_idx = ctx->grid[nb_cols].row_pos +1 ; + reorder_rows=GF_TRUE; + } + + ctx->grid[nb_cols].row_pos = 1; + nb_cols++; + j=nb_cols-1; + } else { + ctx->grid[j].height += apidctx->height; + if (apidctx->height % ctx->max_CU_height) { + ctx->grid[j].last_row_idx = ctx->grid[j].row_pos+1; + reorder_rows=GF_TRUE; + } + ctx->grid[j].row_pos ++; + } + assert(ctx->grid[j].row_pos); + apidctx->pos_row = ctx->grid[j].row_pos - 1; + apidctx->pos_col = j; + } + //move last column at end if not a multiple of CTU + if (force_last_col_plus_one) { + for (i=0; ipids, i); + //do we fit on a col + if (apidctx->pos_col == force_last_col_plus_one - 1) { + apidctx->pos_col = nb_cols-1; + } else if (apidctx->pos_col > force_last_col_plus_one - 1) { + apidctx->pos_col -= 1; + } + } + } + //in each column, push the last_row_idx if any to the bottom and move up other tiles accordingly + //this ensures that slices with non multiple of maxCTU height are always at the bottom of the grid + if (reorder_rows) { + for (i=0; igrid[i].last_row_idx) continue; + + for (j=0; jpids, j); + + if (apidctx->pos_col != i) continue; + + if (apidctx->pos_row==ctx->grid[i].last_row_idx-1) { + apidctx->pos_row = ctx->grid[i].row_pos; + } else if (apidctx->pos_row > ctx->grid[i].last_row_idx-1) { + apidctx->pos_row -= 1; + } + } + } + } + } + + //update final pos + ctx->out_width = 0; + ctx->out_height = 0; + for (i=0; iout_width += ctx->grid[i].width; + if (ctx->grid[i].height > ctx->out_height) + ctx->out_height = ctx->grid[i].height; + } + ctx->nb_cols = nb_cols; + + //recompute slice addresses + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[HEVCMerge] Grid reconfigured, output size %dx%d, %d input pids:\n", ctx->out_width, ctx->out_height, nb_pids)); + for (i=0; ipids, i); + pidctx->slice_segment_address = hevcmerge_compute_address(ctx, pidctx, nb_abs_pos ? GF_TRUE : GF_FALSE); + + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("- pid %s (pos %dx%d) size %dx%d new address %d\n", + gf_filter_pid_get_name(pidctx->pid), + nb_has_pos ? pidctx->pos_x : pidctx->pos_col, + nb_has_pos ? pidctx->pos_y : pidctx->pos_row, + pidctx->width, pidctx->height, pidctx->slice_segment_address)); + } + + //check if we can use a multi-row grid + ctx->enable_multi_rows = GF_FALSE; + if (ctx->mrows) { + ctx->enable_multi_rows = GF_TRUE; + ctx->nb_rows=0; + for (i=0; ipids, i); + s32 pos_y = nb_has_pos ? tile->pos_y : tile->pos_row; + + if (!tile->pos_col) + ctx->nb_rows++; + + for (j=0; jpids, j); + s32 apos_y = nb_has_pos ? atile->pos_y : atile->pos_row; + if (apos_y != pos_y) continue; + if (atile->height != tile->height) { + ctx->enable_multi_rows = GF_FALSE; + break; + } + } + if (!ctx->enable_multi_rows) { + ctx->nb_rows = 0; + break; + } + } + } + + //sort pids according to columns (tiles) and slice addresses to be conformant with spec + gf_list_reset(ctx->ordered_pids); + for (i=0; ipids, j); + // + if (!ctx->enable_multi_rows && (tile->pos_col != i)) + continue; + for (k=0; kordered_pids); k++) { + HEVCTilePidCtx *tile2 = gf_list_get(ctx->ordered_pids, k); + if (!ctx->enable_multi_rows && (tile2->pos_col != i)) + continue; + if (tile2->slice_segment_address > tile->slice_segment_address) { + inserted = GF_TRUE; + gf_list_insert(ctx->ordered_pids, tile, k); + break; + } + } + if (!inserted) { + for (k=0; kordered_pids); k++) { + HEVCTilePidCtx *tile2 = gf_list_get(ctx->ordered_pids, k); + if (!ctx->enable_multi_rows) { + if (tile2->pos_col > tile->pos_col) { + inserted = GF_TRUE; + gf_list_insert(ctx->ordered_pids, tile, k); + break; + } + } else { + if (tile2->slice_segment_address > tile->slice_segment_address) { + inserted = GF_TRUE; + gf_list_insert(ctx->ordered_pids, tile, k); + break; + } + } + } + if (!inserted) + gf_list_add(ctx->ordered_pids, tile); + } + } + if (ctx->enable_multi_rows) + break; + } + + assert(gf_list_count(ctx->ordered_pids) == gf_list_count(ctx->pids)); + + //build SRD map + hevcmerge_build_srdmap(ctx, nb_abs_pos ? GF_TRUE : GF_FALSE); + + return GF_OK; +} + +static GF_Err hevcmerge_check_sps_pps(GF_HEVCMergeCtx *ctx, HEVCTilePidCtx *pid_base, HEVCTilePidCtx *pid_o) +{ + Bool all_ok = GF_TRUE; + u32 i; + HEVCState *state_base = &pid_base->hevc_state; + HEVCState *state_o = &pid_o->hevc_state; + char *src_base = gf_filter_pid_get_source(pid_base->pid); + char *src_o = gf_filter_pid_get_source(pid_o->pid); + + for (i=0; i<16; i++) { + HEVC_SPS *sps_base = &state_base->sps[i]; + HEVC_SPS *sps_o = &state_o->sps[i]; + +#define CHECK_SPS_VAL(__name) \ + if (sps_base->__name != sps_o->__name ) { \ + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[HEVCMerge] "#__name" differs in SPS between %s and %s, undefined results\n", src_base, src_o));\ + all_ok = GF_FALSE;\ + }\ + + if (!sps_base->state && !sps_o->state) continue; + if (!sps_base->state && sps_o->state) { + all_ok = GF_FALSE; + break; + } + if (sps_base->state && !sps_o->state) { + all_ok = GF_FALSE; + break; + } + CHECK_SPS_VAL(aspect_ratio_info_present_flag) + CHECK_SPS_VAL(chroma_format_idc) + CHECK_SPS_VAL(cw_flag) + CHECK_SPS_VAL(cw_flag) + CHECK_SPS_VAL(cw_left) + CHECK_SPS_VAL(cw_right) + CHECK_SPS_VAL(cw_top) + CHECK_SPS_VAL(cw_bottom) + CHECK_SPS_VAL(bit_depth_luma) + CHECK_SPS_VAL(bit_depth_chroma) + CHECK_SPS_VAL(log2_max_pic_order_cnt_lsb) + CHECK_SPS_VAL(separate_colour_plane_flag) + CHECK_SPS_VAL(max_CU_depth) + CHECK_SPS_VAL(num_short_term_ref_pic_sets) + CHECK_SPS_VAL(num_long_term_ref_pic_sps) + //HEVC_ReferencePictureSets rps[64]; + CHECK_SPS_VAL(aspect_ratio_info_present_flag) + CHECK_SPS_VAL(long_term_ref_pics_present_flag) + CHECK_SPS_VAL(temporal_mvp_enable_flag) + CHECK_SPS_VAL(sample_adaptive_offset_enabled_flag) + CHECK_SPS_VAL(sar_idc) + CHECK_SPS_VAL(sar_width) + CHECK_SPS_VAL(sar_height) +// CHECK_SPS_VAL(has_timing_info) +// CHECK_SPS_VAL(num_units_in_tick) +// CHECK_SPS_VAL(time_scale) +// CHECK_SPS_VAL(poc_proportional_to_timing_flag) + CHECK_SPS_VAL(num_ticks_poc_diff_one_minus1) + CHECK_SPS_VAL(video_full_range_flag) + CHECK_SPS_VAL(colour_description_present_flag) + CHECK_SPS_VAL(colour_primaries) + CHECK_SPS_VAL(transfer_characteristic) + CHECK_SPS_VAL(matrix_coeffs) + CHECK_SPS_VAL(rep_format_idx); + CHECK_SPS_VAL(sps_ext_or_max_sub_layers_minus1) + CHECK_SPS_VAL(max_sub_layers_minus1) + CHECK_SPS_VAL(update_rep_format_flag) + CHECK_SPS_VAL(sub_layer_ordering_info_present_flag) + CHECK_SPS_VAL(scaling_list_enable_flag) + CHECK_SPS_VAL(infer_scaling_list_flag) + CHECK_SPS_VAL(scaling_list_ref_layer_id) + CHECK_SPS_VAL(scaling_list_data_present_flag) + CHECK_SPS_VAL(asymmetric_motion_partitions_enabled_flag) + CHECK_SPS_VAL(pcm_enabled_flag) + CHECK_SPS_VAL(strong_intra_smoothing_enable_flag) + CHECK_SPS_VAL(vui_parameters_present_flag) + CHECK_SPS_VAL(log2_diff_max_min_luma_coding_block_size) + CHECK_SPS_VAL(log2_min_transform_block_size) + CHECK_SPS_VAL(log2_min_luma_coding_block_size) + CHECK_SPS_VAL(log2_max_transform_block_size) + CHECK_SPS_VAL(max_transform_hierarchy_depth_inter) + CHECK_SPS_VAL(max_transform_hierarchy_depth_intra) + CHECK_SPS_VAL(pcm_sample_bit_depth_luma_minus1) + CHECK_SPS_VAL(pcm_sample_bit_depth_chroma_minus1) + CHECK_SPS_VAL(pcm_loop_filter_disable_flag) + CHECK_SPS_VAL(log2_min_pcm_luma_coding_block_size_minus3) + CHECK_SPS_VAL(log2_diff_max_min_pcm_luma_coding_block_size) + CHECK_SPS_VAL(overscan_info_present) + CHECK_SPS_VAL(overscan_appropriate) + CHECK_SPS_VAL(video_signal_type_present_flag) + CHECK_SPS_VAL(video_format) + CHECK_SPS_VAL(chroma_loc_info_present_flag) + CHECK_SPS_VAL(chroma_sample_loc_type_top_field) + CHECK_SPS_VAL(chroma_sample_loc_type_bottom_field) + CHECK_SPS_VAL(neutra_chroma_indication_flag) + CHECK_SPS_VAL(field_seq_flag) + CHECK_SPS_VAL(frame_field_info_present_flag) + CHECK_SPS_VAL(default_display_window_flag) + CHECK_SPS_VAL(left_offset) + CHECK_SPS_VAL(right_offset) + CHECK_SPS_VAL(top_offset) + CHECK_SPS_VAL(bottom_offset) + } + + for (i=0; i<64; i++) { + HEVC_PPS *pps_base = &state_base->pps[i]; + HEVC_PPS *pps_o = &state_o->pps[i]; + +#define CHECK_PPS_VAL(__name) \ + if (pps_base->__name != pps_o->__name ) { \ + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[HEVCMerge] "#__name" differs in PPS, undefined results\n"));\ + all_ok = GF_FALSE;\ + }\ + + if (!pps_base->state && !pps_o->state) continue; + if (!pps_base->state && pps_o->state) { + all_ok = GF_FALSE; + break; + } + if (pps_base->state && !pps_o->state) { + all_ok = GF_FALSE; + break; + } + //we don't check tile config nor init qp since we rewrite these +// CHECK_PPS_VAL(loop_filter_across_tiles_enabled_flag) +// CHECK_PPS_VAL(pic_init_qp_minus26) + + CHECK_PPS_VAL(dependent_slice_segments_enabled_flag) + CHECK_PPS_VAL(num_extra_slice_header_bits) + CHECK_PPS_VAL(num_ref_idx_l0_default_active) + CHECK_PPS_VAL(num_ref_idx_l1_default_active) + CHECK_PPS_VAL(slice_segment_header_extension_present_flag) + CHECK_PPS_VAL(output_flag_present_flag) + CHECK_PPS_VAL(lists_modification_present_flag) + CHECK_PPS_VAL(cabac_init_present_flag) + CHECK_PPS_VAL(weighted_pred_flag) + CHECK_PPS_VAL(weighted_bipred_flag) + CHECK_PPS_VAL(slice_chroma_qp_offsets_present_flag) + CHECK_PPS_VAL(deblocking_filter_override_enabled_flag) + CHECK_PPS_VAL(loop_filter_across_slices_enabled_flag) + CHECK_PPS_VAL(entropy_coding_sync_enabled_flag) + CHECK_PPS_VAL(sign_data_hiding_flag) + CHECK_PPS_VAL(constrained_intra_pred_flag) + CHECK_PPS_VAL(transform_skip_enabled_flag) + CHECK_PPS_VAL(cu_qp_delta_enabled_flag) + CHECK_PPS_VAL(transquant_bypass_enable_flag) + CHECK_PPS_VAL(diff_cu_qp_delta_depth) + CHECK_PPS_VAL(pic_cb_qp_offset) + CHECK_PPS_VAL(pic_cr_qp_offset) + CHECK_PPS_VAL(deblocking_filter_control_present_flag) + CHECK_PPS_VAL(pic_disable_deblocking_filter_flag) + CHECK_PPS_VAL(pic_scaling_list_data_present_flag) + CHECK_PPS_VAL(beta_offset_div2) + CHECK_PPS_VAL(tc_offset_div2) + CHECK_PPS_VAL(log2_parallel_merge_level_minus2) + } + + if (src_base) gf_free(src_base); + if (src_o) gf_free(src_o); + if (all_ok || !ctx->strict) return GF_OK; + return GF_BAD_PARAM; +} + + +static GF_Err hevcmerge_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + Bool grid_config_changed = GF_FALSE; + u32 cfg_crc = 0, pid_width, pid_height; + const GF_PropertyValue *p, *dsi; + GF_Err e; + u8 j, i; + GF_HEVCMergeCtx *ctx = (GF_HEVCMergeCtx*)gf_filter_get_udta(filter); + HEVCTilePidCtx *tile_pid; + + if (ctx->in_error) + return GF_BAD_PARAM; + + if (is_remove) { + tile_pid = gf_filter_pid_get_udta(pid); + gf_list_del_item(ctx->pids, tile_pid); + gf_free(tile_pid); + if (!gf_list_count(ctx->pids)) { + if (ctx->opid) + gf_filter_pid_remove(ctx->opid); + + return GF_OK; + } + grid_config_changed = GF_TRUE; + goto reconfig_grid; + } + + if (!gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + if (!p) return GF_OK; + pid_width = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + if (!p) return GF_OK; + pid_height = p->value.uint; + + dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (!dsi) return GF_OK; + + tile_pid = gf_filter_pid_get_udta(pid); + //not set, first time we see this pid + if (!tile_pid) { + GF_SAFEALLOC(tile_pid, HEVCTilePidCtx); + if (!tile_pid) return GF_OUT_OF_MEM; + + gf_filter_pid_set_udta(pid, tile_pid); + tile_pid->pid = pid; + tile_pid->hevc_state.full_slice_header_parse = GF_TRUE; + gf_list_add(ctx->pids, tile_pid); + + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + } + assert(tile_pid); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) tile_pid->timescale = p->value.uint; + + //check if config (vps/sps/pps) has changed - if not, we ignore the reconfig + //note that we don't copy all properties of input pids to the output in this case + cfg_crc = 0; + if (dsi->value.data.ptr && dsi->value.data.size) { + cfg_crc = gf_crc_32(dsi->value.data.ptr, dsi->value.data.size); + } + if (cfg_crc == tile_pid->dsi_crc) + return GF_OK; + tile_pid->dsi_crc = cfg_crc; + + //update this pid's config by parsing sps/vps/pps and check if we need to change anything + GF_HEVCConfig *hvcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, GF_FALSE); + if (!hvcc) return GF_NON_COMPLIANT_BITSTREAM; + tile_pid->nalu_size_length = hvcc->nal_unit_size; + ctx->hevc_nalu_size_length = 4; + e = GF_OK; + for (i = 0; i < gf_list_count(hvcc->param_array); i++) { + GF_HEVCParamArray *ar = (GF_HEVCParamArray *) gf_list_get(hvcc->param_array, i); + for (j = 0; j < gf_list_count(ar->nalus); j++) { + GF_AVCConfigSlot *sl = (GF_AVCConfigSlot *)gf_list_get(ar->nalus, j); + s32 idx = 0; + + if (ar->type == GF_HEVC_NALU_SEQ_PARAM) { + idx = gf_media_hevc_read_sps(sl->data, sl->size, &tile_pid->hevc_state); + if (idx>=0) { + if (!ctx->max_CU_width) { + ctx->max_CU_width = tile_pid->hevc_state.sps[idx].max_CU_width; + } else if (ctx->max_CU_width != tile_pid->hevc_state.sps[idx].max_CU_width) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[HEVCMerge] Cannot merge tiles not using the same max CU width (%d vs %d)\n", ctx->max_CU_width, tile_pid->hevc_state.sps[idx].max_CU_width)); + e = GF_BAD_PARAM; + break; + } + if (!ctx->max_CU_height) { + ctx->max_CU_height = tile_pid->hevc_state.sps[idx].max_CU_height; + } else if (ctx->max_CU_height != tile_pid->hevc_state.sps[idx].max_CU_height) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[HEVCMerge] Cannot merge tiles not using the same max CU height (%d vs %d)\n", ctx->max_CU_height, tile_pid->hevc_state.sps[idx].max_CU_height)); + e = GF_BAD_PARAM; + break; + } + } + } + else if (ar->type == GF_HEVC_NALU_VID_PARAM) { + idx = gf_media_hevc_read_vps(sl->data, sl->size, &tile_pid->hevc_state); + } + else if (ar->type == GF_HEVC_NALU_PIC_PARAM) { + idx = gf_media_hevc_read_pps(sl->data, sl->size, &tile_pid->hevc_state); + } + if (idx < 0) { + // WARNING + e = GF_NON_COMPLIANT_BITSTREAM; + break; + } + } + if (e) break; + } + gf_odf_hevc_cfg_del(hvcc); + if (e) return e; + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, pid); + //remove all SRD related properties + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SRD, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SRD_REF, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SRD_MAP, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CROP_POS, NULL); + //TODO, we might want to compute a cumulate of these properties on the output ? + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ORIG_SIZE, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DOWN_SIZE, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DOWN_RATE, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DOWN_BYTES, NULL); + } + + if ((pid_width != tile_pid->width) || (pid_height != tile_pid->height)) { + tile_pid->width = pid_width; + tile_pid->height = pid_height; + grid_config_changed = GF_TRUE; + } + //todo further testing we might want to force a grid reconfig even if same width / height + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CROP_POS); + if (p) { + s32 pos_x, pos_y; + if (!tile_pid->has_pos) + grid_config_changed = GF_TRUE; + + tile_pid->has_pos = GF_TRUE; + if (p->value.vec2i.x>0) { + pos_x = p->value.vec2i.x / ctx->max_CU_width; + if (pos_x * ctx->max_CU_width != p->value.vec2i.x) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[HEVCMerge] CropOrigin X %d is not a multiple of max CU width %d, adjusting to next boundary\n", p->value.vec2i.x, ctx->max_CU_width)); + pos_x++; + } + pos_x *= ctx->max_CU_width; + } else { + pos_x = p->value.vec2i.x; + } + + if (p->value.vec2i.y>0) { + pos_y = p->value.vec2i.y / ctx->max_CU_height; + if (pos_y * ctx->max_CU_height != p->value.vec2i.y) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[HEVCMerge] CropOrigin Y %d is not a multiple of max CU height %d, adjusting to next boundary\n", p->value.vec2i.y, ctx->max_CU_height)); + pos_y++; + } + pos_y *= ctx->max_CU_height; + } else { + pos_y = p->value.vec2i.y; + } + if ((pos_x != tile_pid->pos_x) || (pos_y != tile_pid->pos_y)) + grid_config_changed = GF_TRUE; + tile_pid->pos_x = pos_x; + tile_pid->pos_y = pos_y; + } else { + if (tile_pid->has_pos) + grid_config_changed = GF_TRUE; + tile_pid->has_pos = GF_FALSE; + tile_pid->pos_x = tile_pid->pos_y = 0; + } + +reconfig_grid: + // Update grid + if (grid_config_changed) { + e = hevcmerge_rebuild_grid(ctx, pid); + if (e) { + ctx->in_error = GF_TRUE; + return e; + } + } + + //check SPS/PPS are compatible - for now we only warn but still process + tile_pid = gf_list_get(ctx->pids, 0); + for (i=1; ipids); i++) { + HEVCTilePidCtx *apidctx = gf_list_get(ctx->pids, i); + e = hevcmerge_check_sps_pps(ctx, tile_pid, apidctx); + if (e) { + apidctx->in_error = GF_TRUE; + return e; + } + } + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->out_width)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->out_height)); + + //recreate DSI based on first in the ordered set of pid we have + //this avoids cases where the input pid order changes due to scheduling, and they don't have exactly the same xPS + tile_pid = gf_list_get(ctx->ordered_pids, 0); + ctx->base_pps_init_qp_delta_minus26 = tile_pid->hevc_state.pps->pic_init_qp_minus26; + + u32 nb_CTUs = ((ctx->out_width + ctx->max_CU_width - 1) / ctx->max_CU_width) * ((ctx->out_height + ctx->max_CU_height - 1) / ctx->max_CU_height); + ctx->nb_bits_per_address_dst = 0; + while (nb_CTUs > (u32)(1 << ctx->nb_bits_per_address_dst)) { + ctx->nb_bits_per_address_dst++; + } + + dsi = gf_filter_pid_get_property(tile_pid->pid, GF_PROP_PID_DECODER_CONFIG); + assert(dsi); + return hevcmerge_rewrite_config(ctx, ctx->opid, dsi->value.data.ptr, dsi->value.data.size); +} + +static GF_Err hevcmerge_process(GF_Filter *filter) +{ + char *data; + u32 pos, nal_length, data_size, i; + s32 current_poc=0; + u8 temporal_id, layer_id, nal_unit_type; + u32 nb_eos, nb_ipid; + Bool found_sei_prefix=GF_FALSE, found_sei_suffix=GF_FALSE; + u64 min_dts = GF_FILTER_NO_TS; + u32 min_dts_timescale=0; + GF_FilterPacket *output_pck = NULL; + GF_HEVCMergeCtx *ctx = (GF_HEVCMergeCtx*) gf_filter_get_udta (filter); + + if (ctx->in_error) + return GF_BAD_PARAM; + nb_eos = 0; + nb_ipid = gf_list_count(ctx->pids); + + //probe input for at least one packet on each pid + for (i = 0; i < nb_ipid; i++) { + u64 dts; + GF_FilterPacket *pck_src; + HEVCTilePidCtx *tile_pid = gf_list_get(ctx->pids, i); + if (tile_pid->in_error) { + nb_eos++; + continue; + } + + pck_src = gf_filter_pid_get_packet(tile_pid->pid); + if (!pck_src) { + if (gf_filter_pid_is_eos(tile_pid->pid)) { + nb_eos++; + continue; + } + return GF_OK; + } + dts = gf_filter_pck_get_dts(pck_src); + + if (dts * min_dts_timescale < min_dts * tile_pid->timescale) { + min_dts = dts; + min_dts_timescale = tile_pid->timescale; + } + } + if (nb_eos == nb_ipid) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + + //reassemble based on the ordered list of pids + for (i = 0; i < nb_ipid; i++) { + u64 dts; + GF_FilterPacket *pck_src; + HEVCTilePidCtx *tile_pid = gf_list_get(ctx->ordered_pids, i); + assert(tile_pid); + if (tile_pid->in_error) + continue; + pck_src = gf_filter_pid_get_packet(tile_pid->pid); + if (nb_eos) { + if (pck_src) { + tile_pid->nb_pck++; + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[HEVCMerge] pids of unequal duration, skipping packet %d on pid %d\n", tile_pid->nb_pck, i+1)); + gf_filter_pid_drop_packet(tile_pid->pid); + } + continue; + } + if (!pck_src) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[HEVCMerge] no data on pid %d while merging, eos detected %d\n", i+1, gf_filter_pid_is_eos(tile_pid->pid) )); + continue; + } + + dts = gf_filter_pck_get_dts(pck_src); + if (dts * min_dts_timescale != min_dts * tile_pid->timescale) continue; + data = (char *)gf_filter_pck_get_data(pck_src, &data_size); // data contains only a packet + // TODO: this is a clock signaling, for now just trash .. + if (!data) { + gf_filter_pid_drop_packet(tile_pid->pid); + continue; + } + tile_pid->nb_pck++; + + //parse the access unit for this pid + gf_bs_reassign_buffer(ctx->bs_au_in, data, data_size); + + while (gf_bs_available(ctx->bs_au_in)) { + u8 *output_nal; + u8 *nal_pck; + u32 nal_pck_size; + + nal_length = gf_bs_read_int(ctx->bs_au_in, tile_pid->nalu_size_length * 8); + pos = (u32) gf_bs_get_position(ctx->bs_au_in); + gf_media_hevc_parse_nalu(data + pos, nal_length, &tile_pid->hevc_state, &nal_unit_type, &temporal_id, &layer_id); + gf_bs_skip_bytes(ctx->bs_au_in, nal_length); //skip nal in bs + + //VCL nal, rewrite slice header + if (nal_unit_type < 32) { + if (!i) current_poc = tile_pid->hevc_state.s_info.poc; + else if (current_poc != tile_pid->hevc_state.s_info.poc) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[HEVCMerge] merging AU %u with different POC (%d vs %d), undefined results.\n", tile_pid->nb_pck, current_poc, tile_pid->hevc_state.s_info.poc)); + } + + nal_pck_size = hevcmerge_rewrite_slice(ctx, tile_pid, data + pos, nal_length); + nal_pck = ctx->buffer_nal; + } + //NON-vcl, copy for SEI or drop (we should not have any SPS/PPS/VPS in the bitstream, they are in the decoder config prop) + else { + gf_media_hevc_parse_nalu(data + pos, nal_length, &tile_pid->hevc_state, &nal_unit_type, &temporal_id, &layer_id); + // Copy SEI_PREFIX only for the first sample. + if (nal_unit_type == GF_HEVC_NALU_SEI_PREFIX && !found_sei_prefix) { + found_sei_prefix = GF_TRUE; + nal_pck = data + pos; + nal_pck_size = nal_length; + } + // Copy SEI_SUFFIX only as last nalu of the sample. + else if (nal_unit_type == GF_HEVC_NALU_SEI_SUFFIX && ((i+1 == nb_ipid) || !found_sei_suffix) ) { + found_sei_suffix = GF_TRUE; + if (ctx->sei_suffix_allocsei_suffix_alloc = nal_length; + ctx->sei_suffix_buf = gf_realloc(ctx->sei_suffix_buf, nal_length); + } + ctx->sei_suffix_len = nal_length; + memcpy(ctx->sei_suffix_buf, data+pos, nal_length); + continue; + } + else continue; + } + if (!output_pck) { + output_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->hevc_nalu_size_length + nal_pck_size, &output_nal); + // todo: might need to rewrite crypto info + gf_filter_pck_merge_properties(pck_src, output_pck); + } + else { + u8 *data_start; + u32 new_size; + gf_filter_pck_expand(output_pck, ctx->hevc_nalu_size_length + nal_pck_size, &data_start, &output_nal, &new_size); + } + hevcmerge_write_nal(ctx, output_nal, nal_pck, nal_pck_size); + } + gf_filter_pid_drop_packet(tile_pid->pid); + } + // end of loop on inputs + + //if we had a SEI suffix, append it + if (ctx->sei_suffix_len) { + if (output_pck) { + u8 *output_nal; + u8 *data_start; + u32 new_size; + gf_filter_pck_expand(output_pck, ctx->hevc_nalu_size_length + ctx->sei_suffix_len, &data_start, &output_nal, &new_size); + hevcmerge_write_nal(ctx, output_nal, ctx->sei_suffix_buf, ctx->sei_suffix_len); + } + ctx->sei_suffix_len = 0; + } + + if (output_pck) + gf_filter_pck_send(output_pck); + + return GF_OK; +} + +static GF_Err hevcmerge_initialize(GF_Filter *filter) +{ + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[HEVCMerge] hevcmerge_initialize started.\n")); + GF_HEVCMergeCtx *ctx = (GF_HEVCMergeCtx *)gf_filter_get_udta(filter); + ctx->bs_au_in = gf_bs_new((char *)ctx, 1, GF_BITSTREAM_READ); + ctx->bs_nal_in = gf_bs_new((char *)ctx, 1, GF_BITSTREAM_READ); + ctx->pids = gf_list_new(); + ctx->ordered_pids = gf_list_new(); + return GF_OK; +} + +static void hevcmerge_finalize(GF_Filter *filter) +{ + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[HEVCMerge] hevcmerge_finalize.\n")); + GF_HEVCMergeCtx *ctx = (GF_HEVCMergeCtx *)gf_filter_get_udta(filter); + if (ctx->buffer_nal) gf_free(ctx->buffer_nal); + if (ctx->buffer_nal_no_epb) gf_free(ctx->buffer_nal_no_epb); + if (ctx->buffer_nal_in_no_epb) gf_free(ctx->buffer_nal_in_no_epb); + gf_bs_del(ctx->bs_au_in); + gf_bs_del(ctx->bs_nal_in); + if (ctx->bs_nal_out) + gf_bs_del(ctx->bs_nal_out); + + if (ctx->grid) gf_free(ctx->grid); + while (gf_list_count(ctx->pids)) { + HEVCTilePidCtx *pctx = gf_list_pop_back(ctx->pids); + gf_free(pctx); + } + gf_list_del(ctx->pids); + gf_list_del(ctx->ordered_pids); + + if (ctx->sei_suffix_buf) gf_free(ctx->sei_suffix_buf); + +} + +static const GF_FilterCapability HEVCMergeCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_HEVC), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_HEVC) +}; + +#define OFFS(_n) #_n, offsetof(GF_HEVCMergeCtx, _n) + +static const GF_FilterArgs HEVCMergeArgs[] = +{ + { OFFS(strict), "strict comparison of SPS and PPS of input pids - see filter help", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(mrows), "signal multiple rows in tile grid when possible", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + +GF_FilterRegister HEVCMergeRegister = { + .name = "hevcmerge", + GF_FS_SET_DESCRIPTION("HEVC Tile merger") + GF_FS_SET_HELP("This filter merges a set of HEVC PIDs into a single motion-constrained tiled HEVC PID.\n" + "The filter creates a tiling grid with a single row and as many columns as needed.\n" + "If [-mrows]() is set and tiles properly align on the final grid, multiple rows will be declared in the PPS.\n" + "Positioning of tiles can be automatic (implicit) or explicit.\n" + "The filter will check the SPS and PPS configurations of input PID and warn if they are not aligned but will still process them unless [-strict]() is set.\n" + "The filter assumes that all input PIDs are synchronized (frames share the same timestamp) and will reassemble frames with the same dts. If pids are of unequal duration, the filter will drop frames as soon as one pid is over.\n" + "## Implicit Positioning\n" + "In implicit positioning, results may vary based on the order of input pids declaration.\n" + "In this mode the filter will automatically allocate new columns for tiles with height not a multiple of max CU height.\n" + "## Explicit Positioning\n" + "In explicit positioning, the `CropOrigin` property on input PIDs is used to setup the tile grid. In this case, tiles shall not overlap in the final output.\n" + "If `CropOrigin` is used, it shall be set on all input sources.\n" + "If positive coordinates are used, they specify absolute positioning in pixels of the tiles. The coordinates are automatically adjusted to the next multiple of max CU width and height.\n" + "If negative coordinates are used, they specify relative positioning (eg `0x-1` indicates to place the tile below the tile 0x0).\n" + "In this mode, it is the caller responsibility to set coordinates so that all tiles in a column have the same width and only the last row/column uses non-multiple of max CU width/height values. The filter will complain and abort if this is not respected.\n" + "- If an horizontal blank is detected in the layout, an empty column in the tiling grid will be inserted.\n" + "- If a vertical blank is detected in the layout, it is ignored.\n" + " \n" + "## Spatial Relationship Description (SRD)\n" + "\n" + "The filter will create an `SRDMap` property in the output PID if `SRDRef` and `SRD`or `CropOrigin` are set on all input PIDs.\n" + "The `SRDMap` allows forwarding the logical sources `SRD` in the merged PID.\n" + "The output pid `SRDRef` is set to the output video size.\n" + "The input `SRDRef` and `SRD` are usually specified in DASH MPD, but can be manually assigned to inputs.\n" + "- `SRDRef` gives the size of the referential used for the input `SRD` (usually matches the original video size, but not always)\n" + "- `SRD` gives the size and position of the input in the original video, expressed in `SRDRef` referential of the input.\n" + "The inputs do not need to have matching `SRDRef`." + "EX src1:SRD=0x0x640x480:SRDRef=1280x720\n" + "This indicates that `src1` contains a video located at 0,0, with a size of 640x480 pixels in a virtual source of 1280x720 pixels.\n" + "EX src2:SRD=640x0x640x480:SRDRef=1280x720\n" + "This indicates that `src1` contains a video located at 640,0, with a size of 640x480 pixels in a virtual source of 1280x720 pixels.\n" + " \n" + "Each merged input is described by 8 intergers in the output `SRDMap`:\n" + "- the source `SRD` is rescaled in the output `SRDRef` to form the first part (4 integers) of the `SRDMap` (i.e. __where was the input ?__)\n" + "- the source location in the reconstructed video forms the second part (4 integers) of the `SRDMap` (i.e. __where are the input pixels in the output ?__)\n" + "Assuming the two sources are encoded at 320x240 and merged as src2 above src1, the output will be a 320x480 video with a `SRDMap` of {0,160,160,240,0,0,320,240,0,0,160,240,0,240,320,240}\n" + "Note: merged inputs are always listed in `SRDMap` in their tile order in the output bitstream.\n" + "\n" + "Alternatively to using `SRD` and `SRDRef`, it is possible to specify `CropOrigin` property on the inputs, in which case:\n" + "- the `CropOrigin` gives the location in the source\n" + "- the input size gives the size in the source, and no rescaling of referential is done\n" + "EX src1:CropOrigin=0x0 src1:CropOrigin=640x0 \n" + "Assuming the two sources are encoded at 320x240 and merged as src1 above src2, the output will be a 320x480 video with a `SRDMap` of {0,0,320,240,0,0,320,240,640,0,320,240,0,240,320,240}\n" + ) + .private_size = sizeof(GF_HEVCMergeCtx), + SETCAPS(HEVCMergeCaps), + .flags = GF_FS_REG_EXPLICIT_ONLY, + .initialize = hevcmerge_initialize, + .finalize = hevcmerge_finalize, + .args = HEVCMergeArgs, + .configure_pid = hevcmerge_configure_pid, + .process = hevcmerge_process, + .max_extra_pids = -1, +}; + +const GF_FilterRegister *hevcmerge_register(GF_FilterSession *session) +{ + return &HEVCMergeRegister; +} + +#else +const GF_FilterRegister *hevcmerge_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // GPAC_DISABLE_AV_PARSERS diff --git a/src/filters/hevcsplit.c b/src/filters/hevcsplit.c new file mode 100644 index 0000000..0b66d19 --- /dev/null +++ b/src/filters/hevcsplit.c @@ -0,0 +1,962 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Yacine Mathurin Boubacar Aziakou + * Samir Mustapha + * Copyright (c) Telecom ParisTech 2019 + * All rights reserved + * + * This file is part of GPAC / HEVC tile split and rewrite filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include + +#if !defined(GPAC_DISABLE_HEVC) && !defined(GPAC_DISABLE_AV_PARSERS) + +typedef struct +{ + GF_FilterPid *opid; + u32 width, height, orig_x, orig_y; + GF_FilterPacket *cur_pck; +} HEVCTilePid; + +typedef struct +{ + GF_FilterPid *ipid; + GF_List *outputs; + u32 num_tiles, got_p; + HEVCState hevc_state; + u32 hevc_nalu_size_length, cfg_crc, nb_pck; + + //static read/write objects to avoid allocs + GF_BitStream *bs_au_in; + GF_BitStream *bs_nal_in; + GF_BitStream *bs_nal_out; + + //buffer where we will store the rewritten slice with EPB + u8 *buffer_nal; + u32 buffer_nal_alloc; + + //buffer where we will store the rewritten slice or nal (sps, pps) without EPB + u8 *output_no_epb; + u32 output_no_epb_alloc; + + //buffer where we will store the input slice without EPB - we cannot use EPB removal functions from bitstream object since + //we don't have gf_bs_read_data support in this mode + u8 *input_no_epb; + u32 input_no_epb_alloc; +} GF_HEVCSplitCtx; + +//get tiles coordinates in as index in grid +static u32 hevcsplit_get_slice_tile_index(HEVCState *hevc) +{ + u32 tile_x, tile_y; + + HEVCSliceInfo si = hevc->s_info; + u32 i, tbX = 0, tbY = 0, slX, slY, PicWidthInCtbsY, PicHeightInCtbsX; + + PicWidthInCtbsY = (si.sps->width + si.sps->max_CU_width - 1) / si.sps->max_CU_width; + PicHeightInCtbsX = (si.sps->height + si.sps->max_CU_height - 1) / si.sps->max_CU_height; + + slY = si.slice_segment_address % PicWidthInCtbsY; + slX = si.slice_segment_address / PicWidthInCtbsY; + tile_x = 0; + tile_y = 0; + if (si.pps->tiles_enabled_flag) { + u32 valX, valY; + if (si.pps->uniform_spacing_flag) { + for (i = 0; i < si.pps->num_tile_rows; i++) { + if (i < si.pps->num_tile_rows - 1) + valX = (i + 1) * PicHeightInCtbsX / si.pps->num_tile_rows - i * PicHeightInCtbsX / si.pps->num_tile_rows; + else + valX = PicHeightInCtbsX - tbX; + + if (slX < tbX + valX) { + tile_x = i; + break; + } + + tbX += valX; + } + for (i = 0; i < si.pps->num_tile_columns; i++) { + if (i < si.pps->num_tile_columns - 1) + valY = (i + 1) * PicWidthInCtbsY / si.pps->num_tile_columns - i * PicWidthInCtbsY / si.pps->num_tile_columns; + else + valY = PicWidthInCtbsY - tbY; + if (slY < tbY + valY) { + tile_y = i; + break; + } + + tbY += valY; + } + } else { + for (i = 0; i < si.pps->num_tile_rows; i++) { + if (i < si.pps->num_tile_rows - 1) + valX = si.pps->row_height[i]; + else + valX = PicHeightInCtbsX - tbX; + if (slX < tbX + valX) { + tile_x = i; + break; + } + + tbX += valX; + } + + for (i = 0; i < si.pps->num_tile_columns; i++) { + if (i < si.pps->num_tile_columns - 1) + valY = si.pps->column_width[i]; + else + valY = PicWidthInCtbsY - tbY; + + if (slY < tbY + valY) { + tile_y = i; + break; + } + tbY += valY; + } + } + } + return tile_x * hevc->s_info.pps->num_tile_columns + tile_y; +} + +//get the width and the height of the tile in pixel +static void hevcsplit_get_tile_pixel_coords(HEVCState *hevc, u32 index_row, u32 index_col, u32 pps_id, u32 *width, u32 *height, u32 *tx, u32 *ty) +{ + u32 i, sps_id, tbX = 0, tbY = 0, PicWidthInCtbsY, PicHeightInCtbsX, max_CU_width, max_CU_height; + + sps_id = hevc->pps[pps_id].sps_id; + max_CU_width = hevc->sps[sps_id].max_CU_width; + max_CU_height = hevc->sps[sps_id].max_CU_height; + PicWidthInCtbsY = (hevc->sps[sps_id].width + max_CU_width - 1) / max_CU_width; + PicHeightInCtbsX = (hevc->sps[sps_id].height + max_CU_height - 1) / max_CU_height; + + *tx = 0; + *ty = 0; + + if (!hevc->pps[pps_id].tiles_enabled_flag) { + *width = hevc->sps[sps_id].width; + *height = hevc->sps[sps_id].height; + return; + } + + if (hevc->pps[pps_id].uniform_spacing_flag) { + if (index_row < hevc->pps[pps_id].num_tile_rows - 1) { + for (i = 0; i < index_row; i++) + tbX += ((i + 1) * PicHeightInCtbsX / hevc->pps[pps_id].num_tile_rows - i * PicHeightInCtbsX / hevc->pps[pps_id].num_tile_rows); + + *height = (index_row + 1) * PicHeightInCtbsX / hevc->pps[pps_id].num_tile_rows - index_row * PicHeightInCtbsX / hevc->pps[pps_id].num_tile_rows; + } else { + for (i = 0; i < hevc->pps[pps_id].num_tile_rows - 1; i++) + tbX += ((i + 1) * PicHeightInCtbsX / hevc->pps[pps_id].num_tile_rows - i * PicHeightInCtbsX / hevc->pps[pps_id].num_tile_rows); + *height = PicHeightInCtbsX - tbX; + } + + if (index_col < hevc->pps[pps_id].num_tile_columns - 1) { + for (i = 0; i < index_col; i++) + tbY += ((i + 1) * PicWidthInCtbsY / hevc->pps[pps_id].num_tile_columns - i * PicWidthInCtbsY / hevc->pps[pps_id].num_tile_columns); + *width = (index_col + 1) * PicWidthInCtbsY / hevc->pps[pps_id].num_tile_columns - index_col * PicWidthInCtbsY / hevc->pps[pps_id].num_tile_columns; + } else { + for (i = 0; i < hevc->pps[pps_id].num_tile_columns - 1; i++) + tbY += ((i + 1) * PicWidthInCtbsY / hevc->pps[pps_id].num_tile_columns - i * PicWidthInCtbsY / hevc->pps[pps_id].num_tile_columns); + *width = PicWidthInCtbsY - tbY; + } + } else { + if (index_row < hevc->pps[pps_id].num_tile_rows - 1) { + + for (i = 0; i < index_row; i++) + tbX += hevc->pps[pps_id].row_height[i]; + + *height = hevc->pps[pps_id].row_height[index_row]; + } else { + for (i = 0; i < hevc->pps[pps_id].num_tile_rows - 1; i++) + tbX += hevc->pps[pps_id].row_height[i]; + *height = PicHeightInCtbsX - tbX; + } + + if (index_col < hevc->pps[pps_id].num_tile_columns - 1) { + *width = hevc->pps[pps_id].column_width[index_col]; + + for (i = 0; i < index_col; i++) + tbY += hevc->pps[pps_id].column_width[i]; + } else { + for (i = 0; i < hevc->pps[pps_id].num_tile_columns - 1; i++) + tbY += hevc->pps[pps_id].column_width[i]; + *width = PicWidthInCtbsY - tbY; + } + } + + *width = *width * max_CU_width; + *height = *height * max_CU_height; + *ty = tbX * max_CU_height; + *tx = tbY * max_CU_width; + + if (*tx + *width > hevc->sps[sps_id].width) + *width = hevc->sps[sps_id].width - *tx; + if (*ty + *height > hevc->sps[sps_id].height) + *height = hevc->sps[sps_id].height - *ty; +} + +//rewrite the profile and level +static void hevc_write_profile_tier_level(GF_BitStream *bs_in, GF_BitStream *bs_out, Bool ProfilePresentFlag, u8 MaxNumSubLayersMinus1) +{ + u8 j; + Bool sub_layer_profile_present_flag[8], sub_layer_level_present_flag[8]; + if (ProfilePresentFlag) { + gf_bs_write_int(bs_out, gf_bs_read_int(bs_in, 8), 8); + gf_bs_write_long_int(bs_out, gf_bs_read_long_int(bs_in, 32), 32); + gf_bs_write_int(bs_out, gf_bs_read_int(bs_in, 4), 4); + gf_bs_write_long_int(bs_out, gf_bs_read_long_int(bs_in, 44), 44); + } + gf_bs_write_int(bs_out, gf_bs_read_int(bs_in, 8), 8); + + for (j = 0; j < MaxNumSubLayersMinus1; j++) { + sub_layer_profile_present_flag[j] = gf_bs_read_int(bs_in, 1); + gf_bs_write_int(bs_out, sub_layer_profile_present_flag[j], 1); + sub_layer_level_present_flag[j] = gf_bs_read_int(bs_in, 1); + gf_bs_write_int(bs_out, sub_layer_level_present_flag[j], 1); + } + if (MaxNumSubLayersMinus1 > 0) + for (j = MaxNumSubLayersMinus1; j < 8; j++) + gf_bs_write_int(bs_out, gf_bs_read_int(bs_in, 2), 2); + + + for (j = 0; j < MaxNumSubLayersMinus1; j++) { + if (sub_layer_profile_present_flag[j]) { + gf_bs_write_int(bs_out, gf_bs_read_int(bs_in, 8), 8); + gf_bs_write_int(bs_out, gf_bs_read_int(bs_in, 32), 32); + gf_bs_write_int(bs_out, gf_bs_read_int(bs_in, 4), 4); + gf_bs_write_long_int(bs_out, gf_bs_read_long_int(bs_in, 44), 44); + } + if (sub_layer_level_present_flag[j]) + gf_bs_write_int(bs_out, gf_bs_read_int(bs_in, 8), 8); + } +} + +//also used by HEVCmerge, don't use context bitstream objects +void hevc_rewrite_sps(char *in_SPS, u32 in_SPS_length, u32 width, u32 height, char **out_SPS, u32 *out_SPS_length) +{ + GF_BitStream *bs_in, *bs_out; + u64 length_no_use = 4096; + u8 *data_without_emulation_bytes = NULL; + u32 data_without_emulation_bytes_size = 0, sps_ext_or_max_sub_layers_minus1; + u8 max_sub_layers_minus1 = 0, layer_id; + Bool conformance_window_flag, multiLayerExtSpsFlag; + u32 chroma_format_idc; + + bs_in = gf_bs_new(in_SPS, in_SPS_length, GF_BITSTREAM_READ); + gf_bs_enable_emulation_byte_removal(bs_in, GF_TRUE); + bs_out = gf_bs_new(NULL, length_no_use, GF_BITSTREAM_WRITE); + /*dst_buffer_size =*/ gf_bs_get_size(bs_out); + if (!bs_in) goto exit; + + //copy NAL Header + gf_bs_write_int(bs_out, gf_bs_read_int(bs_in, 7), 7); + layer_id = gf_bs_read_int(bs_in, 6); + gf_bs_write_int(bs_out, layer_id, 6); + gf_bs_write_int(bs_out, gf_bs_read_int(bs_in, 3), 3); + + gf_bs_write_int(bs_out, gf_bs_read_int(bs_in, 4), 4); //copy vps_id + + if (layer_id == 0) + { + max_sub_layers_minus1 = gf_bs_read_int(bs_in, 3); + gf_bs_write_int(bs_out, max_sub_layers_minus1, 3); + } + else + { + sps_ext_or_max_sub_layers_minus1 = gf_bs_read_int(bs_in, 3); + gf_bs_write_int(bs_out, sps_ext_or_max_sub_layers_minus1, 3); + } + multiLayerExtSpsFlag = (layer_id != 0) && (sps_ext_or_max_sub_layers_minus1 == 7); + if (!multiLayerExtSpsFlag) { + gf_bs_write_int(bs_out, gf_bs_read_int(bs_in, 1), 1); + hevc_write_profile_tier_level(bs_in, bs_out, 1, max_sub_layers_minus1); + } + + gf_bs_set_ue(bs_out, gf_bs_get_ue(bs_in)); //copy sps_id + + if (multiLayerExtSpsFlag) { + u8 update_rep_format_flag = gf_bs_read_int(bs_in, 1); + gf_bs_write_int(bs_out, update_rep_format_flag, 1); + if (update_rep_format_flag) { + gf_bs_write_int(bs_out, gf_bs_read_int(bs_in, 8), 8); + } + } + else { + chroma_format_idc = gf_bs_get_ue(bs_in); + gf_bs_set_ue(bs_out, chroma_format_idc); + if (chroma_format_idc == 3) + gf_bs_write_int(bs_out, gf_bs_read_int(bs_in, 1), 1); // copy separate_colour_plane_flag + /*w =*/ gf_bs_get_ue(bs_in); //skip width bits in input bitstream + /*h =*/ gf_bs_get_ue(bs_in); //skip height bits in input bitstream + + //Copy the new width and height in output bitstream + gf_bs_set_ue(bs_out, width); + gf_bs_set_ue(bs_out, height); + + //Get rid of the bit conformance_window_flag + conformance_window_flag = gf_bs_read_int(bs_in, 1); + //put the new conformance flag to zero + gf_bs_write_int(bs_out, 0, 1); + + //Skip the bits related to conformance_window_offset + if (conformance_window_flag) + { + gf_bs_get_ue(bs_in); + gf_bs_get_ue(bs_in); + gf_bs_get_ue(bs_in); + gf_bs_get_ue(bs_in); + } + } + + //copy and write the rest of the bits in the byte + while (gf_bs_get_bit_position(bs_in) != 8) { + gf_bs_write_int(bs_out, gf_bs_read_int(bs_in, 1), 1); + } + + //copy and write the rest of the bytes + while (gf_bs_get_size(bs_in) != gf_bs_get_position(bs_in)) { + gf_bs_write_int(bs_out, gf_bs_read_u8(bs_in), 8); //watchout, not aligned in destination bitstream + } + + + gf_bs_align(bs_out); //align + + gf_free(data_without_emulation_bytes); + data_without_emulation_bytes = NULL; + data_without_emulation_bytes_size = 0; + + gf_bs_get_content(bs_out, &data_without_emulation_bytes, &data_without_emulation_bytes_size); + + *out_SPS_length = data_without_emulation_bytes_size + gf_media_nalu_emulation_bytes_add_count(data_without_emulation_bytes, data_without_emulation_bytes_size); + *out_SPS = gf_malloc(*out_SPS_length); + *out_SPS_length = gf_media_nalu_add_emulation_bytes(data_without_emulation_bytes, *out_SPS, data_without_emulation_bytes_size); + + exit: + gf_bs_del(bs_in); + gf_bs_del(bs_out); + gf_free(data_without_emulation_bytes); +} + + +static void hevcsplit_rewrite_pps_no_grid(GF_HEVCSplitCtx *ctx, char *in_PPS, u32 in_PPS_length, char **out_PPS, u32 *out_PPS_length) +{ + u32 i, out_size_no_epb; + u8 cu_qp_delta_enabled_flag, tiles_enabled_flag, loop_filter_across_slices_enabled_flag; + + gf_bs_reassign_buffer(ctx->bs_nal_in, in_PPS, in_PPS_length); + gf_bs_enable_emulation_byte_removal(ctx->bs_nal_in, GF_TRUE); + + + if (!ctx->bs_nal_out) ctx->bs_nal_out = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs_nal_out, ctx->output_no_epb, ctx->output_no_epb_alloc); + + //Read and write NAL header bits + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 16), 16); + gf_bs_set_ue(ctx->bs_nal_out, gf_bs_get_ue(ctx->bs_nal_in)); //pps_pic_parameter_set_id + gf_bs_set_ue(ctx->bs_nal_out, gf_bs_get_ue(ctx->bs_nal_in)); //pps_seq_parameter_set_id + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 7), 7); //from dependent_slice_segments_enabled_flag to cabac_init_present_flag + gf_bs_set_ue(ctx->bs_nal_out, gf_bs_get_ue(ctx->bs_nal_in)); //num_ref_idx_l0_default_active_minus1 + gf_bs_set_ue(ctx->bs_nal_out, gf_bs_get_ue(ctx->bs_nal_in)); //num_ref_idx_l1_default_active_minus1 + gf_bs_set_se(ctx->bs_nal_out, gf_bs_get_se(ctx->bs_nal_in)); //init_qp_minus26 + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 2), 2); //from constrained_intra_pred_flag to transform_skip_enabled_flag + cu_qp_delta_enabled_flag = gf_bs_read_int(ctx->bs_nal_in, 1); //cu_qp_delta_enabled_flag + gf_bs_write_int(ctx->bs_nal_out, cu_qp_delta_enabled_flag, 1); // + if (cu_qp_delta_enabled_flag) + gf_bs_set_ue(ctx->bs_nal_out, gf_bs_get_ue(ctx->bs_nal_in)); // diff_cu_qp_delta_depth + gf_bs_set_se(ctx->bs_nal_out, gf_bs_get_se(ctx->bs_nal_in)); // pps_cb_qp_offset + gf_bs_set_se(ctx->bs_nal_out, gf_bs_get_se(ctx->bs_nal_in)); // pps_cr_qp_offset + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 4), 4); // from pps_slice_chroma_qp_offsets_present_flag to transquant_bypass_enabled_flag + + tiles_enabled_flag = gf_bs_read_int(ctx->bs_nal_in, 1); // tiles_enabled_flag + gf_bs_write_int(ctx->bs_nal_out, 0, 1); //discard tile enable in dest + + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 1), 1); // entropy_coding_sync_enabled_flag + + //read tile info from source + if (tiles_enabled_flag) { + u32 num_tile_columns_minus1 = gf_bs_get_ue(ctx->bs_nal_in); + u32 num_tile_rows_minus1 = gf_bs_get_ue(ctx->bs_nal_in); + u8 uniform_spacing_flag = gf_bs_read_int(ctx->bs_nal_in, 1); + + if (!uniform_spacing_flag) { + for (i = 0; i < num_tile_columns_minus1; i++) + gf_bs_get_ue(ctx->bs_nal_in); + for (i = 0; i < num_tile_rows_minus1; i++) + gf_bs_get_ue(ctx->bs_nal_in); + } + gf_bs_read_int(ctx->bs_nal_in, 1); + } + + loop_filter_across_slices_enabled_flag = gf_bs_read_int(ctx->bs_nal_in, 1); + gf_bs_write_int(ctx->bs_nal_out, loop_filter_across_slices_enabled_flag, 1); + + //copy and write the rest of the bits in the byte + while (gf_bs_get_bit_position(ctx->bs_nal_in) != 8) { + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 1), 1); + } + + //copy and write the rest of the bytes + while (gf_bs_get_size(ctx->bs_nal_in) != gf_bs_get_position(ctx->bs_nal_in)) { + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_u8(ctx->bs_nal_in), 8); //watchout, not aligned in destination bitstream + } + + //align + gf_bs_align(ctx->bs_nal_out); + + gf_bs_get_content_no_truncate(ctx->bs_nal_out, &ctx->output_no_epb, &out_size_no_epb, &ctx->output_no_epb_alloc); + + *out_PPS_length = out_size_no_epb + gf_media_nalu_emulation_bytes_add_count(ctx->output_no_epb, out_size_no_epb); + *out_PPS = gf_malloc(*out_PPS_length); + gf_media_nalu_add_emulation_bytes(ctx->output_no_epb, *out_PPS, out_size_no_epb); +} + +//return the new size slice - slice data is stored in ctx->buffer_nal +static u32 hevcsplit_remove_slice_address(GF_HEVCSplitCtx *ctx, u8 *in_slice, u32 in_slice_length) +{ + u32 inslice_size_no_epb, outslice_size_epb; + u64 header_end; + u32 num_entry_point_start; + u32 pps_id; + Bool RapPicFlag = GF_FALSE; + HEVC_PPS *pps; + HEVC_SPS *sps; + u32 al, slice_size, slice_offset_orig, slice_offset_dst; + u32 first_slice_segment_in_pic_flag; + //u32 dependent_slice_segment_flag; + u8 nal_unit_type; + HEVCState *hevc = &ctx->hevc_state; + + if (ctx->input_no_epb_alloc < in_slice_length) { + ctx->input_no_epb = gf_realloc(ctx->input_no_epb, in_slice_length); + ctx->input_no_epb_alloc = in_slice_length; + } + inslice_size_no_epb = gf_media_nalu_remove_emulation_bytes(in_slice, ctx->input_no_epb, in_slice_length); + + gf_bs_reassign_buffer(ctx->bs_nal_in, ctx->input_no_epb, inslice_size_no_epb); + gf_bs_enable_emulation_byte_removal(ctx->bs_nal_in, GF_FALSE); + + if (!ctx->bs_nal_out) ctx->bs_nal_out = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs_nal_out, ctx->output_no_epb, ctx->output_no_epb_alloc); + + + assert(hevc->s_info.header_size_bits >= 0); + assert(hevc->s_info.entry_point_start_bits >= 0); + header_end = (u64) hevc->s_info.header_size_bits; + + num_entry_point_start = (u32) hevc->s_info.entry_point_start_bits; + + // nal_unit_header + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 1), 1); + nal_unit_type = gf_bs_read_int(ctx->bs_nal_in, 6); + gf_bs_write_int(ctx->bs_nal_out, nal_unit_type, 6); + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 9), 9); + + first_slice_segment_in_pic_flag = gf_bs_read_int(ctx->bs_nal_in, 1); //first_slice_segment_in_pic_flag + gf_bs_write_int(ctx->bs_nal_out, 1, 1); + + switch (nal_unit_type) { + case GF_HEVC_NALU_SLICE_IDR_W_DLP: + case GF_HEVC_NALU_SLICE_IDR_N_LP: + RapPicFlag = GF_TRUE; + break; + case GF_HEVC_NALU_SLICE_BLA_W_LP: + case GF_HEVC_NALU_SLICE_BLA_W_DLP: + case GF_HEVC_NALU_SLICE_BLA_N_LP: + case GF_HEVC_NALU_SLICE_CRA: + RapPicFlag = GF_TRUE; + break; + } + + if (RapPicFlag) { + //no_output_of_prior_pics_flag + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 1), 1); + } + + pps_id = gf_bs_get_ue(ctx->bs_nal_in); //pps_id + gf_bs_set_ue(ctx->bs_nal_out, pps_id); + + pps = &hevc->pps[pps_id]; + sps = &hevc->sps[pps->sps_id]; + + //dependent_slice_segment_flag = 0; + if (!first_slice_segment_in_pic_flag && pps->dependent_slice_segments_enabled_flag) { + /*dependent_slice_segment_flag =*/ gf_bs_read_int(ctx->bs_nal_in, 1); +// } else { +// dependent_slice_segment_flag = GF_FALSE; + } + + if (!first_slice_segment_in_pic_flag) { + gf_bs_read_int(ctx->bs_nal_in, sps->bitsSliceSegmentAddress); + } + //else original slice segment address = 0 + + //nothing to write for slice address, we remove the address + + //copy over until num_entry_points + while (num_entry_point_start != (gf_bs_get_position(ctx->bs_nal_in) - 1) * 8 + gf_bs_get_bit_position(ctx->bs_nal_in)) { + gf_bs_write_int(ctx->bs_nal_out, gf_bs_read_int(ctx->bs_nal_in, 1), 1); + } + + //no tilin, don't write num_entry_points + + //write slice extension to 0 + if (pps->slice_segment_header_extension_present_flag) + gf_bs_write_int(ctx->bs_nal_out, 0, 1); + + //we may have unparsed data in the source bitstream (slice header) due to entry points or slice segment extensions + //TODO: we might want to copy over the slice extension header bits + while (header_end != (gf_bs_get_position(ctx->bs_nal_in) - 1) * 8 + gf_bs_get_bit_position(ctx->bs_nal_in)) + gf_bs_read_int(ctx->bs_nal_in, 1); + + //read byte_alignment() is bit=1 + x bit=0 + al = gf_bs_read_int(ctx->bs_nal_in, 1); + if (al != 1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[HEVCTileSplit] source slice header not properly aligned\n")); + } + + //write byte_alignment() is bit=1 + x bit=0 + gf_bs_write_int(ctx->bs_nal_out, 1, 1); + gf_bs_align(ctx->bs_nal_out); //align + + /* get output slice header*/ + u32 outslice_size_no_epb; + gf_bs_get_content_no_truncate(ctx->bs_nal_out, &ctx->output_no_epb, &outslice_size_no_epb, &ctx->output_no_epb_alloc); + /* slice_size: the rest of the slice after the header (no_emulation_bytes in it) in source.*/ + slice_size = (u32) gf_bs_available(ctx->bs_nal_in); + /* slice_offset_orig: Immediate next byte after header_end (start of the slice_payload) */ + slice_offset_orig = (u32) gf_bs_get_position(ctx->bs_nal_in); + /* slice_offset_dst: end of header in dest (=dst_buf_size since we have not copied slice data yet)*/ + slice_offset_dst = outslice_size_no_epb; + /*final slice size: add source slice size */ + outslice_size_no_epb += slice_size; + if (ctx->output_no_epb_alloc < outslice_size_no_epb) { + ctx->output_no_epb = gf_realloc(ctx->output_no_epb, outslice_size_no_epb); + ctx->output_no_epb_alloc = outslice_size_no_epb; + } + memcpy(ctx->output_no_epb + slice_offset_dst, ctx->input_no_epb + slice_offset_orig, sizeof(char) * slice_size); + + outslice_size_epb = outslice_size_no_epb + gf_media_nalu_emulation_bytes_add_count(ctx->output_no_epb, outslice_size_no_epb); + + if (ctx->buffer_nal_alloc < outslice_size_epb) { + ctx->buffer_nal = gf_realloc(ctx->buffer_nal, outslice_size_epb); + ctx->buffer_nal_alloc = outslice_size_epb; + } + gf_media_nalu_add_emulation_bytes(ctx->output_no_epb, ctx->buffer_nal, outslice_size_no_epb); + return outslice_size_epb; +} + +static char *hevcsplit_rewrite_nal(GF_Filter *filter, GF_HEVCSplitCtx *ctx, char *in_nal, u32 in_nal_size, u32 *out_tile_index, u32 *out_nal_size) +{ + u8 nal_unit_type, temporal_id, layer_id; + u32 buf_size; + HEVCState *hevc = &ctx->hevc_state; + + gf_media_hevc_parse_nalu(in_nal, in_nal_size, hevc, &nal_unit_type, &temporal_id, &layer_id); + switch (nal_unit_type) { + //all VCL nal, remove slice address + case GF_HEVC_NALU_SLICE_TRAIL_N: + case GF_HEVC_NALU_SLICE_TRAIL_R: + case GF_HEVC_NALU_SLICE_TSA_N: + case GF_HEVC_NALU_SLICE_TSA_R: + case GF_HEVC_NALU_SLICE_STSA_N: + case GF_HEVC_NALU_SLICE_STSA_R: + case GF_HEVC_NALU_SLICE_RADL_N: + case GF_HEVC_NALU_SLICE_RADL_R: + case GF_HEVC_NALU_SLICE_RASL_N: + case GF_HEVC_NALU_SLICE_RASL_R: + case GF_HEVC_NALU_SLICE_BLA_W_LP: + case GF_HEVC_NALU_SLICE_BLA_W_DLP: + case GF_HEVC_NALU_SLICE_BLA_N_LP: + case GF_HEVC_NALU_SLICE_IDR_W_DLP: + case GF_HEVC_NALU_SLICE_IDR_N_LP: + case GF_HEVC_NALU_SLICE_CRA: + *out_tile_index = hevcsplit_get_slice_tile_index(hevc); + buf_size = hevcsplit_remove_slice_address(ctx, in_nal, in_nal_size); + *out_nal_size = buf_size; + return ctx->buffer_nal; + //non-vcl, write to bitstream + default: + *out_nal_size = in_nal_size; + return in_nal; + } + return NULL; +} + +static GF_Err hevcsplit_rewrite_dsi(GF_HEVCSplitCtx *ctx, GF_FilterPid *opid, char *data, u32 size, u32 new_width, u32 new_height) +{ + u32 i, j; + u8 *new_dsi; + u32 new_size; + GF_HEVCConfig *hvcc = gf_odf_hevc_cfg_read(data, size, GF_FALSE); + if (!hvcc) return GF_NON_COMPLIANT_BITSTREAM; + + // for all the list objects in param_array + for (i = 0; i < gf_list_count(hvcc->param_array); i++) { // hvcc->param_array:list object + GF_HEVCParamArray *ar = (GF_HEVCParamArray *)gf_list_get(hvcc->param_array, i); // ar contains the i-th item in param_array + for (j = 0; j < gf_list_count(ar->nalus); j++) { // for all the nalus the i-th param got + /*! used for storing AVC sequenceParameterSetNALUnit and pictureParameterSetNALUnit*/ + GF_AVCConfigSlot *sl = (GF_AVCConfigSlot *)gf_list_get(ar->nalus, j); // store j-th nalus in *sl + + if (ar->type == GF_HEVC_NALU_SEQ_PARAM) { + char *outSPS=NULL; + u32 outSize=0; + hevc_rewrite_sps(sl->data, sl->size, new_width, new_height, &outSPS, &outSize); + gf_free(sl->data); + sl->data = outSPS; + sl->size= outSize; + } + else if (ar->type == GF_HEVC_NALU_VID_PARAM) { + } + else if (ar->type == GF_HEVC_NALU_PIC_PARAM) { + char *outPPS=NULL; + u32 outSize=0; + hevcsplit_rewrite_pps_no_grid(ctx, sl->data, sl->size, &outPPS, &outSize); + gf_free(sl->data); + sl->data = outPPS; + sl->size = outSize; + } + } + } + gf_odf_hevc_cfg_write(hvcc, &new_dsi, &new_size); + gf_odf_hevc_cfg_del(hvcc); + + gf_filter_pid_set_property(opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(new_dsi, new_size)); + return GF_OK; +} + +static void hevcsplit_write_nal(char *output_nal, char *rewritten_nal, u32 out_nal_size, u32 hevc_nalu_size_length) +{ + u32 n = 8*(hevc_nalu_size_length); + while (n) { + u32 v = (out_nal_size >> (n-8)) & 0xFF; + *output_nal = v; + output_nal++; + n-=8; + } + memcpy(output_nal, rewritten_nal, out_nal_size); +} + +static GF_Err hevcsplit_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 cfg_crc = 0, codecid, o_width, o_height; + s32 pps_id = -1, sps_id = -1; + const GF_PropertyValue *p, *dsi; + GF_Err e; + GF_HEVCSplitCtx *ctx = (GF_HEVCSplitCtx*)gf_filter_get_udta(filter); + + if (is_remove) { + u32 i, count; + count = gf_filter_get_opid_count(filter); + for (i=0; iopid) { + gf_filter_pid_remove(tpid->opid); + tpid->opid = NULL; + } + if (tpid) gf_free(tpid); + if(opid){ + gf_filter_pid_set_udta(opid, NULL); + gf_filter_pid_remove(opid); + } + } + ctx->ipid = NULL; + return GF_OK; + } + // checks if input pid matchs its destination filter. + if (!gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + codecid = p ? p->value.uint : 0; + if (!codecid || (codecid != GF_CODECID_HEVC)) { + return GF_NOT_SUPPORTED; + } + + dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + cfg_crc = 0; + if (dsi && dsi->value.data.ptr && dsi->value.data.size) { + cfg_crc = gf_crc_32(dsi->value.data.ptr, dsi->value.data.size); + } + //same config, skip reconf + if (!dsi || (cfg_crc == ctx->cfg_crc)) return GF_OK; + ctx->cfg_crc = cfg_crc; + ctx->ipid = pid; + + // parse otherwise they should refer to something else + u32 i, j; + GF_HEVCConfig *hvcc; + + memset(&ctx->hevc_state, 0, sizeof(HEVCState)); + ctx->hevc_state.full_slice_header_parse = GF_TRUE; + + hvcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, GF_FALSE); + if (!hvcc) return GF_NON_COMPLIANT_BITSTREAM; + ctx->hevc_nalu_size_length = hvcc->nal_unit_size; + + for (i = 0; i < gf_list_count(hvcc->param_array); i++) { // hvcc->param_array:list object + // ar contains the i-th item in param_array + GF_HEVCParamArray *ar = (GF_HEVCParamArray *)gf_list_get(hvcc->param_array, i); + for (j = 0; j < gf_list_count(ar->nalus); j++) { // for all the nalus the i-th param got + /*! used for storing AVC sequenceParameterSetNALUnit and pictureParameterSetNALUnit*/ + GF_AVCConfigSlot *sl = (GF_AVCConfigSlot *)gf_list_get(ar->nalus, j); // store j-th nalus in *sl + s32 idx; + + if (ar->type == GF_HEVC_NALU_SEQ_PARAM) { + idx = gf_media_hevc_read_sps(sl->data, sl->size, &ctx->hevc_state); + if ((idx >= 0) && sps_id<0) + sps_id = idx; + } + else if (ar->type == GF_HEVC_NALU_PIC_PARAM) { + s32 id = gf_media_hevc_read_pps(sl->data, sl->size, &ctx->hevc_state); + if ((id >= 0) && pps_id<0) + pps_id = id; + } + else if (ar->type == GF_HEVC_NALU_VID_PARAM) { + /*s32 id = */ gf_media_hevc_read_vps(sl->data, sl->size, &ctx->hevc_state); + } + } + } + gf_odf_hevc_cfg_del(hvcc); + + if (pps_id < 0) return GF_NON_COMPLIANT_BITSTREAM; + if (sps_id < 0) return GF_NON_COMPLIANT_BITSTREAM; + + o_width = ctx->hevc_state.sps[sps_id].width; + o_height = ctx->hevc_state.sps[sps_id].height; + ctx->num_tiles = ctx->hevc_state.pps[pps_id].num_tile_rows * ctx->hevc_state.pps[pps_id].num_tile_columns; + + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[HEVCTileSplit] Input stream %dx%d with %d tiles:\n", o_width, o_height, ctx->num_tiles)); + + // Done with parsing SPS/pps/vps + u32 rows = ctx->hevc_state.pps[pps_id].num_tile_rows; + u32 cols = ctx->hevc_state.pps[pps_id].num_tile_columns; + + for (i = 0; i < rows; i++) { + for (j = 0; j < cols; j++) { + u32 tile_idx = i * cols + j; + HEVCTilePid *tpid = gf_list_get(ctx->outputs, tile_idx); + if (!tpid) { + assert(gf_list_count(ctx->outputs) == tile_idx); + + GF_SAFEALLOC(tpid, HEVCTilePid); + if (!tpid) return GF_OUT_OF_MEM; + gf_list_add(ctx->outputs, tpid); + tpid->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_udta(tpid->opid, tpid); + } + + hevcsplit_get_tile_pixel_coords(&ctx->hevc_state, i, j, pps_id, &tpid->width, &tpid->height, &tpid->orig_x, &tpid->orig_y); + gf_filter_pid_copy_properties(tpid->opid, ctx->ipid); + // for each output pid, set decoder_config, width and height. + gf_filter_pid_set_property(tpid->opid, GF_PROP_PID_WIDTH, &PROP_UINT(tpid->width)); + gf_filter_pid_set_property(tpid->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(tpid->height)); + gf_filter_pid_set_property(tpid->opid, GF_PROP_PID_CROP_POS, &PROP_VEC2I_INT(tpid->orig_x, tpid->orig_y)); + gf_filter_pid_set_property(tpid->opid, GF_PROP_PID_ORIG_SIZE, &PROP_VEC2I_INT(o_width, o_height)); + // rewrite the decoder config + e = hevcsplit_rewrite_dsi(ctx, tpid->opid, dsi->value.data.ptr, dsi->value.data.size, tpid->width, tpid->height); + if (e) return e; + + GF_LOG(GF_LOG_INFO, GF_LOG_CODEC, ("[HEVCTileSplit] output pid %dx%d (position was %dx%d)\n", tpid->width, tpid->height, tpid->orig_x, tpid->orig_y)); + } + } + // reaggregate to form complete frame. + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + return GF_OK; +} + +static GF_Err hevcsplit_process(GF_Filter *filter) +{ + u32 hevc_nalu_size_length; + u32 data_size, nal_length, opid_idx = 0; + u8 temporal_id, layer_id, nal_unit_type, i; + u8 *data; + u8 *output_nal; + u32 out_nal_size; + GF_FilterPid * opid; + HEVCTilePid * tpid; + GF_HEVCSplitCtx *ctx = (GF_HEVCSplitCtx*)gf_filter_get_udta(filter); + GF_FilterPacket *pck_src = gf_filter_pid_get_packet(ctx->ipid); + if (!pck_src) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + return GF_EOS; + } + return GF_OK; + } + data = (u8*)gf_filter_pck_get_data(pck_src, &data_size); + //this is a clock signaling, for now just trash .. + if (!data) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + /*create a bitstream reader for the whole packet*/ + gf_bs_reassign_buffer(ctx->bs_au_in, data, data_size); + ctx->nb_pck++; + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[HEVCTileSplit] splitting frame %d DTS "LLU" CTS "LLU"\n", ctx->nb_pck, gf_filter_pck_get_dts(pck_src), gf_filter_pck_get_cts(pck_src))); + + while (gf_bs_available(ctx->bs_au_in)) { + u8 *rewritten_nal; + // ctx->hevc_nalu_size_length filled using hvcc + nal_length = gf_bs_read_int(ctx->bs_au_in, ctx->hevc_nalu_size_length * 8); + u32 pos = (u32) gf_bs_get_position(ctx->bs_au_in); + // skip the content of the nal from bs to buffer (useful for the next nal) + gf_bs_skip_bytes(ctx->bs_au_in, nal_length); + // data+pos is an address + gf_media_hevc_parse_nalu(data+pos, nal_length, &ctx->hevc_state, &nal_unit_type, &temporal_id, &layer_id); + + // todo: might need to rewrite crypto info + + rewritten_nal = hevcsplit_rewrite_nal(filter, ctx, data+pos, nal_length, &opid_idx, &out_nal_size); + if (!rewritten_nal) continue; + + hevc_nalu_size_length = ctx->hevc_nalu_size_length; + //non-vcl NAL, forward to all outputs + if (nal_unit_type > 34) { + for (i=0; inum_tiles; i++) { + opid = gf_filter_get_opid(filter, i); + tpid = gf_filter_pid_get_udta(opid); + if (!tpid->opid) { + continue; + } + if (!tpid->cur_pck) { + tpid->cur_pck = gf_filter_pck_new_alloc(tpid->opid, ctx->hevc_nalu_size_length + out_nal_size, &output_nal); + gf_filter_pck_merge_properties(pck_src, tpid->cur_pck); + } else { + u8 *data_start; + u32 new_size; + gf_filter_pck_expand(tpid->cur_pck, ctx->hevc_nalu_size_length + out_nal_size, &data_start, &output_nal, &new_size); + } + hevcsplit_write_nal(output_nal, rewritten_nal, out_nal_size, hevc_nalu_size_length); + } + } else { + opid = gf_filter_get_opid(filter, opid_idx); + tpid = gf_filter_pid_get_udta(opid); + if (!tpid->opid) { + continue; + } + if (!tpid->cur_pck) { + tpid->cur_pck = gf_filter_pck_new_alloc(tpid->opid, ctx->hevc_nalu_size_length + out_nal_size, &output_nal); + gf_filter_pck_merge_properties(pck_src, tpid->cur_pck); + } else { + u8 *data_start; + u32 new_size; + gf_filter_pck_expand(tpid->cur_pck, ctx->hevc_nalu_size_length + out_nal_size, &data_start, &output_nal, &new_size); + } + hevcsplit_write_nal(output_nal, rewritten_nal, out_nal_size, hevc_nalu_size_length); + } + } + gf_filter_pid_drop_packet(ctx->ipid); + // done rewriting all nals from input, send all output + for (i=0; i < ctx->num_tiles; i++) { + opid = gf_filter_get_opid(filter, i); + tpid = gf_filter_pid_get_udta(opid); + + if (!tpid->opid) continue; + if (tpid->cur_pck) { + gf_filter_pck_send(tpid->cur_pck); + tpid->cur_pck = NULL; + } + } + return GF_OK; +} + +static GF_Err hevcsplit_initialize(GF_Filter *filter) +{ + GF_HEVCSplitCtx *ctx = (GF_HEVCSplitCtx *) gf_filter_get_udta(filter); + ctx->bs_au_in = gf_bs_new((char *)ctx, 1, GF_BITSTREAM_READ); + ctx->bs_nal_in = gf_bs_new((char *)ctx, 1, GF_BITSTREAM_READ); + ctx->outputs = gf_list_new(); + return GF_OK; +} + +static void hevcsplit_finalize(GF_Filter *filter) +{ + u32 i, count; + GF_HEVCSplitCtx *ctx = (GF_HEVCSplitCtx *) gf_filter_get_udta(filter); + if (ctx->buffer_nal) gf_free(ctx->buffer_nal); + if (ctx->output_no_epb) gf_free(ctx->output_no_epb); + if (ctx->input_no_epb) gf_free(ctx->input_no_epb); + + gf_bs_del(ctx->bs_au_in); + gf_bs_del(ctx->bs_nal_in); + if (ctx->bs_nal_out) gf_bs_del(ctx->bs_nal_out); + + count = gf_list_count(ctx->outputs); + for (i=0; ioutputs, i); + gf_free(tpid); + } + gf_list_del(ctx->outputs); +} + +static const GF_FilterCapability HEVCSplitCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_HEVC), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_HEVC) +}; + +#define OFFS(_n) #_n, offsetof(GF_HEVCSplitCtx, _n) + +static const GF_FilterArgs HEVCSplitArgs[] = +{ + {0} +}; + +GF_FilterRegister HEVCSplitRegister = { + .name = "hevcsplit", + GF_FS_SET_DESCRIPTION("HEVC tile spliter") + GF_FS_SET_HELP("This filter splits a motion-constrained tiled HEVC PID into N independent HEVC PIDs.\n" + "Use hevcmerge filter to merge initially motion-constrained tiled HEVC PID in a single output.") + .private_size = sizeof(GF_HEVCSplitCtx), + SETCAPS(HEVCSplitCaps), + //hevc split shall be explicitly loaded + .flags = GF_FS_REG_EXPLICIT_ONLY, + .initialize = hevcsplit_initialize, + .finalize = hevcsplit_finalize, + .args = HEVCSplitArgs, + .configure_pid = hevcsplit_configure_pid, + .process = hevcsplit_process, +}; + +const GF_FilterRegister* hevcsplit_register(GF_FilterSession *session) +{ + return &HEVCSplitRegister; +} + +#else +const GF_FilterRegister* hevcsplit_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // GPAC_DISABLE_AV_PARSERS + diff --git a/src/filters/in_atsc.c b/src/filters/in_atsc.c new file mode 100644 index 0000000..8e673fd --- /dev/null +++ b/src/filters/in_atsc.c @@ -0,0 +1,507 @@ + /* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018-2020 + * All rights reserved + * + * This file is part of GPAC / ATSC input filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include + +#ifndef GPAC_DISABLE_ATSC + +typedef struct +{ + u32 sid; + u32 tsi; + GF_FilterPid *opid; + Bool init_sent; +} TSI_Output; + +typedef struct +{ + GF_FilterPid *opid; + char *seg_name; +} SegInfo; + +typedef struct +{ + //options + char *src, *ifce, *odir; + Bool gcache, kc, sr, reorder; + u32 buffer, timeout, stats, max_segs, tsidbg, rtimeout; + s32 tunein, stsi; + + //internal + GF_Filter *filter; + GF_DownloadManager *dm; + + char *clock_init_seg; + GF_ATSCDmx *atsc_dmx; + u32 tune_service_id; + + u32 sync_tsi, last_toi; + + u32 start_time, tune_time; + GF_FilterPid *opid; + GF_List *tsi_outs; + + u32 nb_stats; + GF_List *received_seg_names; +} ATSCInCtx; + + +static GF_FilterProbeScore atscin_probe_url(const char *url, const char *mime) +{ + if (!strnicmp(url, "atsc://", 7)) return GF_FPROBE_SUPPORTED; + return GF_FPROBE_NOT_SUPPORTED; +} + + +static void atscin_finalize(GF_Filter *filter) +{ + ATSCInCtx *ctx = gf_filter_get_udta(filter); + if (ctx->clock_init_seg) gf_free(ctx->clock_init_seg); + if (ctx->atsc_dmx) gf_atsc3_dmx_del(ctx->atsc_dmx); + + if (ctx->tsi_outs) { + while (gf_list_count(ctx->tsi_outs)) { + TSI_Output *tsio = gf_list_pop_back(ctx->tsi_outs); + gf_free(tsio); + } + gf_list_del(ctx->tsi_outs); + } + if (ctx->received_seg_names) { + while (gf_list_count(ctx->received_seg_names)) { + SegInfo *si = gf_list_pop_back(ctx->received_seg_names); + gf_free(si->seg_name); + gf_free(si); + } + gf_list_del(ctx->received_seg_names); + } +} + +static void atscin_send_file(ATSCInCtx *ctx, u32 service_id, GF_ATSCEventFileInfo *finfo, u32 evt_type) +{ + if (!ctx->kc || !finfo->corrupted) { + u8 *output; + char *ext; + GF_FilterPid *pid, **p_pid; + GF_FilterPacket *pck; + TSI_Output *tsio = NULL; + + p_pid = &ctx->opid; + if (finfo->tsi && ctx->stsi) { + u32 i, count = gf_list_count(ctx->tsi_outs); + for (i=0; itsi_outs, i); + if ((tsio->sid==service_id) && (tsio->tsi==finfo->tsi)) { + break; + } + tsio=NULL; + } + if (!tsio) { + GF_SAFEALLOC(tsio, TSI_Output); + if (!tsio) return; + + tsio->tsi = finfo->tsi; + tsio->sid = service_id; + gf_list_add(ctx->tsi_outs, tsio); + } + p_pid = &tsio->opid; + + if ((evt_type==GF_ATSC_EVT_INIT_SEG) || (evt_type==GF_ATSC_EVT_MPD)) { + if (ctx->sr && tsio->init_sent) return; + tsio->init_sent = GF_TRUE; + } + } + pid = *p_pid; + + if (!pid) { + pid = gf_filter_pid_new(ctx->filter); + (*p_pid) = pid; + gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE)); + } + gf_filter_pid_set_property(pid, GF_PROP_PID_ID, &PROP_UINT(tsio ? tsio->tsi : service_id)); + gf_filter_pid_set_property(pid, GF_PROP_PID_SERVICE_ID, &PROP_UINT(service_id)); + gf_filter_pid_set_property(pid, GF_PROP_PID_URL, &PROP_STRING(finfo->filename)); + ext = gf_file_ext_start(finfo->filename); + gf_filter_pid_set_property(pid, GF_PROP_PID_FILE_EXT, &PROP_STRING(ext ? (ext+1) : "*" )); + + pck = gf_filter_pck_new_alloc(pid, finfo->size, &output); + memcpy(output, finfo->data, finfo->size); + if (finfo->corrupted) gf_filter_pck_set_corrupted(pck, GF_TRUE); + gf_filter_pck_send(pck); + + if (ctx->received_seg_names && (evt_type==GF_ATSC_EVT_SEG)) { + SegInfo *si; + GF_SAFEALLOC(si, SegInfo); + if (!si) return; + si->opid = pid; + si->seg_name = gf_strdup(finfo->filename); + gf_list_add(ctx->received_seg_names, si); + } + } + + while (gf_atsc3_dmx_get_object_count(ctx->atsc_dmx, service_id)>1) { + if (! gf_atsc3_dmx_remove_first_object(ctx->atsc_dmx, service_id)) + break; + } + + if (ctx->max_segs) { + while (gf_list_count(ctx->received_seg_names) > ctx->max_segs) { + GF_FilterEvent evt; + SegInfo *si = gf_list_pop_front(ctx->received_seg_names); + GF_FEVT_INIT(evt, GF_FEVT_FILE_DELETE, si->opid); + evt.file_del.url = si->seg_name; + gf_filter_pid_send_event(si->opid, &evt); + gf_free(si->seg_name); + gf_free(si); + } + } +} + +void atscin_on_event(void *udta, GF_ATSCEventType evt, u32 evt_param, GF_ATSCEventFileInfo *finfo) +{ + char szPath[GF_MAX_PATH]; + ATSCInCtx *ctx = (ATSCInCtx *)udta; + u32 nb_obj; + Bool is_init = GF_TRUE; + Bool is_loop = GF_FALSE; + DownloadedCacheEntry cache_entry; + + switch (evt) { + case GF_ATSC_EVT_SERVICE_FOUND: + if (!ctx->tune_time) ctx->tune_time = gf_sys_clock(); + + break; + case GF_ATSC_EVT_SERVICE_SCAN: + if (ctx->tune_service_id && !gf_atsc3_dmx_find_service(ctx->atsc_dmx, ctx->tune_service_id)) { + + GF_LOG(GF_LOG_ERROR, GF_LOG_ATSC, ("[ATSCDmx] Asked to tune to service %d but no such service, tuning to first one\n", ctx->tune_service_id)); + + ctx->tune_service_id = 0; + gf_atsc3_tune_in(ctx->atsc_dmx, (u32) -2, GF_TRUE); + } + break; + case GF_ATSC_EVT_MPD: + + if (!ctx->gcache) { + atscin_send_file(ctx, evt_param, finfo, evt); + break; + } + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(ctx->filter); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE)); + } + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ID, &PROP_UINT(evt_param)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SERVICE_ID, &PROP_UINT(evt_param)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("mpd")); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING("application/dash+xml")); + + sprintf(szPath, "http://gpatsc/service%d/%s", evt_param, finfo->filename); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_REDIRECT_URL, &PROP_STRING(szPath)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_URL, &PROP_STRING(szPath)); + + cache_entry = gf_dm_add_cache_entry(ctx->dm, szPath, finfo->data, finfo->size, 0, 0, "application/dash+xml", GF_TRUE, 0); + + sprintf(szPath, "x-dash-atsc: %d\r\n", evt_param); + gf_dm_force_headers(ctx->dm, cache_entry, szPath); + gf_atsc3_dmx_set_service_udta(ctx->atsc_dmx, evt_param, cache_entry); + + ctx->sync_tsi = 0; + ctx->last_toi = 0; + if (ctx->clock_init_seg) gf_free(ctx->clock_init_seg); + ctx->clock_init_seg = NULL; + ctx->tune_service_id = evt_param; + break; + case GF_ATSC_EVT_SEG: + + if (!ctx->gcache) { + atscin_send_file(ctx, evt_param, finfo, evt); + break; + } + if (finfo->corrupted && !ctx->kc) return; + cache_entry = gf_atsc3_dmx_get_service_udta(ctx->atsc_dmx, evt_param); + if (cache_entry) { + if (!ctx->clock_init_seg) ctx->clock_init_seg = gf_strdup(finfo->filename); + sprintf(szPath, "x-dash-atsc: %d\r\nx-dash-first-seg: %s\r\n", evt_param, ctx->clock_init_seg); + gf_dm_force_headers(ctx->dm, cache_entry, szPath); + } + is_init = GF_FALSE; + if (!ctx->sync_tsi) { + ctx->sync_tsi = finfo->tsi; + ctx->last_toi = finfo->toi; + } else if (ctx->sync_tsi == finfo->tsi) { + if (ctx->last_toi > finfo->toi) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ATSC, ("[ATSCDmx] Loop detected on service %d for TSI %u: prev TOI %u this toi %u\n", ctx->tune_service_id, finfo->tsi, ctx->last_toi, finfo->toi)); + + gf_atsc3_dmx_purge_objects(ctx->atsc_dmx, evt_param); + is_loop = GF_TRUE; + if (cache_entry) { + if (ctx->clock_init_seg) gf_free(ctx->clock_init_seg); + ctx->clock_init_seg = gf_strdup(finfo->filename); + sprintf(szPath, "x-dash-atsc: %d\r\nx-dash-first-seg: %s\r\nx-atsc-loop: yes\r\n", evt_param, ctx->clock_init_seg); + gf_dm_force_headers(ctx->dm, cache_entry, szPath); + } + } + ctx->last_toi = finfo->toi; + } + //fallthrough + + case GF_ATSC_EVT_INIT_SEG: + if (!ctx->gcache) { + atscin_send_file(ctx, evt_param, finfo, evt); + break; + } + + if (finfo->corrupted && !ctx->kc) return; + + sprintf(szPath, "http://gpatsc/service%d/%s", evt_param, finfo->filename); + //we copy over the init segment, but only share the data pointer for segments + + cache_entry = gf_dm_add_cache_entry(ctx->dm, szPath, finfo->data, finfo->size, 0, 0, "video/mp4", is_init ? GF_TRUE : GF_FALSE, finfo->download_ms); + + if (cache_entry) gf_dm_force_headers(ctx->dm, cache_entry, "x-atsc: yes\r\n"); + + GF_LOG(GF_LOG_INFO, GF_LOG_ATSC, ("[ATSCDmx] Pushing file %s to cache\n", szPath)); + + if (is_loop) break; + + nb_obj = gf_atsc3_dmx_get_object_count(ctx->atsc_dmx, evt_param); + if (nb_obj>10) { + while (nb_obj>10) { + gf_atsc3_dmx_remove_first_object(ctx->atsc_dmx, evt_param); + nb_obj = gf_atsc3_dmx_get_object_count(ctx->atsc_dmx, evt_param); + } + } + break; + default: + break; + } +} + +static Bool atscin_local_cache_probe(void *par, char *url, Bool is_destroy) +{ + ATSCInCtx *ctx = (ATSCInCtx *)par; + u32 sid=0; + char *subr; + if (strncmp(url, "http://gpatsc/service", 21)) return GF_FALSE; + + subr = strchr(url+21, '/'); + subr[0] = 0; + sid = atoi(url+21); + subr[0] = '/'; + if (is_destroy) { + gf_atsc3_dmx_remove_object_by_name(ctx->atsc_dmx, sid, subr+1, GF_TRUE); + } else if (sid && (sid != ctx->tune_service_id)) { + GF_LOG(GF_LOG_INFO, GF_LOG_ATSC, ("[ATSCDmx] Request on service %d but tuned on service %d, retuning\n", sid, ctx->tune_service_id)); + ctx->tune_service_id = sid; + ctx->sync_tsi = 0; + ctx->last_toi = 0; + if (ctx->clock_init_seg) gf_free(ctx->clock_init_seg); + ctx->clock_init_seg = NULL; + gf_atsc3_tune_in(ctx->atsc_dmx, sid, GF_TRUE); + } + return GF_TRUE; +} + +static GF_Err atscin_process(GF_Filter *filter) +{ + ATSCInCtx *ctx = gf_filter_get_udta(filter); + + while (1) { + GF_Err e = gf_atsc3_dmx_process(ctx->atsc_dmx); + if (e == GF_IP_NETWORK_EMPTY) { + gf_filter_ask_rt_reschedule(filter, 1000); + break; + } + } + if (!ctx->tune_time) { + u32 diff = gf_sys_clock() - ctx->start_time; + if (diff>ctx->timeout) { + GF_LOG(GF_LOG_ERROR, GF_LOG_ATSC, ("[ATSCDmx] No data for %d ms, aborting\n", diff)); + gf_filter_setup_failure(filter, GF_SERVICE_ERROR); + return GF_EOS; + } + } + + if (ctx->stats) { + u32 now = gf_sys_clock() - ctx->start_time; + if (now >= ctx->nb_stats*ctx->stats) { + ctx->nb_stats+=1; + if (gf_filter_reporting_enabled(filter)) { + Double rate=0.0; + char szRpt[1024]; + + u64 st = gf_atsc3_dmx_get_first_packet_time(ctx->atsc_dmx); + u64 et = gf_atsc3_dmx_get_last_packet_time(ctx->atsc_dmx); + u64 nb_pck = gf_atsc3_dmx_get_nb_packets(ctx->atsc_dmx); + u64 nb_bytes = gf_atsc3_dmx_get_recv_bytes(ctx->atsc_dmx); + + et -= st; + if (et) { + rate = (Double)nb_bytes*8; + rate /= et; + } + sprintf(szRpt, "[%us] "LLU" bytes "LLU" packets in "LLU" ms rate %.02f mbps", now/1000, nb_bytes, nb_pck, et/1000, rate); + gf_filter_update_status(filter, 0, szRpt); + } + } + } + + return GF_OK; +} + + +static GF_Err atscin_initialize(GF_Filter *filter) +{ + ATSCInCtx *ctx = gf_filter_get_udta(filter); + ctx->filter = filter; + + if (!ctx->src) return GF_BAD_PARAM; + if (strcmp(ctx->src, "atsc://")) return GF_BAD_PARAM; + + if (ctx->odir) + ctx->gcache = GF_FALSE; + + if (ctx->gcache) { + ctx->dm = gf_filter_get_download_manager(filter); + if (!ctx->dm) return GF_SERVICE_ERROR; + gf_dm_set_localcache_provider(ctx->dm, atscin_local_cache_probe, ctx); + } + + ctx->atsc_dmx = gf_atsc3_dmx_new(ctx->ifce, ctx->odir, ctx->buffer); + if (ctx->odir && ctx->max_segs) { + gf_atsc3_set_max_objects_store(ctx->atsc_dmx, (u32) ctx->max_segs); + } + + gf_atsc3_set_reorder(ctx->atsc_dmx, ctx->reorder, ctx->rtimeout); + + if (ctx->tsidbg) { + gf_atsc3_dmx_debug_tsi(ctx->atsc_dmx, ctx->tsidbg); + } + + gf_atsc3_set_callback(ctx->atsc_dmx, atscin_on_event, ctx); + if (ctx->tunein>0) ctx->tune_service_id = ctx->tunein; + + if (ctx->tune_service_id) + gf_atsc3_tune_in(ctx->atsc_dmx, ctx->tune_service_id, GF_FALSE); + else + gf_atsc3_tune_in(ctx->atsc_dmx, (u32) ctx->tunein, GF_TRUE); + + ctx->start_time = gf_sys_clock(); + + if (ctx->stsi) ctx->tsi_outs = gf_list_new(); + if (ctx->max_segs) + ctx->received_seg_names = gf_list_new(); + + return GF_OK; +} + +#define OFFS(_n) #_n, offsetof(ATSCInCtx, _n) +static const GF_FilterArgs ATSCInArgs[] = +{ + { OFFS(src), "location of source content - see filter help", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(ifce), "default interface to use for multicast. If NULL, the default system interface will be used", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(gcache), "indicate the files should populate GPAC HTTP cache - see filter help", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(tunein), "service ID to bootstrap on. 0 means tune to no service, -1 tune all services -2 means tune on first service found", GF_PROP_SINT, "-2", NULL, 0}, + { OFFS(buffer), "receive buffer size to use in bytes", GF_PROP_UINT, "0x80000", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(timeout), "timeout in ms after which tunein fails", GF_PROP_UINT, "5000", NULL, 0}, + { OFFS(kc), "keep corrupted file", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(sr), "skip repeated files - ignored in cache mode", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(stsi), "define one output pid per tsi/serviceID - ignored in cache mode, see filter help", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(stats), "log statistics at the given rate in ms (0 disables stats)", GF_PROP_UINT, "1000", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(tsidbg), "gather only objects with given TSI (debug)", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(max_segs), "maximum number of segments to keep - ignored in cache mode", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(odir), "output directory for stand-alone mode - see filter help", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(reorder), "ignore order flag in ROUTE/LCT packets, avoiding considering object done when TOI changes", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(rtimeout), "default timeout in ms to wait when gathering out-of-order packets", GF_PROP_UINT, "5000", NULL, GF_FS_ARG_HINT_EXPERT}, + {0} +}; + +static const GF_FilterCapability ATSCInCaps[] = +{ + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), +}; + +GF_FilterRegister ATSCInRegister = { + .name = "atscin", + GF_FS_SET_DESCRIPTION("ATSC input") +#ifndef GPAC_DISABLE_DOC + .help = "This filter is a receiver for ATSC 3.0 ROUTE sessions. Source is identified using the string `atsc://`.\n" + "The filter can work in cached mode, source mode or standalone mode.\n" + "# Cached mode\n" + "The cached mode is the default filter behaviour. It populates GPAC HTTP Cache with the recieved files, using `http://gpatsc/serviceN/` as service root, N being the ATSC service ID.\n" + "In cached mode, repeated files are always send.\n" + " \n" + "The cached MPD is assigned the following headers:\n" + "- x-dash-atsc: integer value, indicates the ATSC service ID.\n" + "- x-dash-first-seg: string value, indicates the name of the first segment completely retrieved from the broadcast.\n" + "- x-atsc-loop: boolean value, if yes indicates a loop in the service has been detected (usually pcap replay loop).\n" + " \n" + "The cached files are assigned the following headers:\n" + "- x-atsc: boolean value, if yes indicates the file comes from an ATSC session.\n" + "\n" + "# Source mode\n" + "In source mode, the filter outputs files on a single output pid of type `file`. " + "The files are dispatched once fully received, the output pid carries a sequence of complete files. Repeated files are not sent unless requested.\n" + "If needed, one pid per TSI can be used rather than a single pid. This avoids mixing files of different mime types on the same pid (e.g. mpd and isobmff).\n" + "EX gpac -i atsc://cache=false -o $ServiceID$/$File$:dynext\n" + "This will grab the files and forward them as output PIDs, consumed by the [fout](fout) filter.\n" + "\n" + "# Standalone mode\n" + "In standalone mode, the filter does not produce any output pid and writes received files to the [-odir]() directory.\n" + "EX gpac -i atsc://odir=output\n" + "This will grab the files and write them to `output` directory.\n" + "\n" + "# Interface setup\n" + "On some systems (OSX), when using VM packet replay, you may need to force multicast routing on your local interface.\n" + "You will have to do this for the base ATSC3 multicast (224.0.23.60):\n" + "EX route add -net 224.0.23.60/32 -interface vboxnet0\n" + "and on each service multicast:\n" + "EX route add -net 239.255.1.4/32 -interface vboxnet0\n" + "", +#endif //GPAC_DISABLE_DOC + .private_size = sizeof(ATSCInCtx), + .args = ATSCInArgs, + .initialize = atscin_initialize, + .finalize = atscin_finalize, + SETCAPS(ATSCInCaps), + .process = atscin_process, + .probe_url = atscin_probe_url +}; + +const GF_FilterRegister *atscin_register(GF_FilterSession *session) +{ + return &ATSCInRegister; +} + +#else + +const GF_FilterRegister *atscin_register(GF_FilterSession *session) +{ + return NULL; +} + +#endif /* GPAC_DISABLE_ATSC */ + diff --git a/src/filters/in_dvb4linux.c b/src/filters/in_dvb4linux.c new file mode 100644 index 0000000..6a466fc --- /dev/null +++ b/src/filters/in_dvb4linux.c @@ -0,0 +1,438 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017 + * All rights reserved + * + * This file is part of GPAC / DVB4Linux input filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include +#include + +#ifndef WIN32 +//#define GPAC_HAS_LINUX_DVB +//#define GPAC_SIM_LINUX_DVB +#endif + + +#ifdef GPAC_HAS_LINUX_DVB + +#include +#include +#include +#include + +#ifndef GPAC_SIM_LINUX_DVB +#include +#include +#endif + +typedef struct +{ + //options + const char *src; + const char *chcfg; + u32 block_size; + + //only one output pid declared + GF_FilterPid *pid; + + u32 freq; + u16 vpid; + u16 apid; + +#ifndef GPAC_SIM_LINUX_DVB + fe_spectral_inversion_t specInv; + fe_modulation_t modulation; + fe_bandwidth_t bandwidth; + fe_transmit_mode_t TransmissionMode; + fe_guard_interval_t guardInterval; + fe_code_rate_t HP_CodeRate; + fe_code_rate_t LP_CodeRate; + fe_hierarchy_t hierarchy; +#endif + + int demux_fd; + + char *block; + +} GF_DVBLinuxCtx; + + +static GF_Err dvblin_tune(GF_DVBLinuxCtx *ctx) +{ + FILE *chanfile; + char line[255]; +#ifndef GPAC_SIM_LINUX_DVB + int demux1, front1; + struct dmx_pes_filter_params pesFilterParams; + struct dvb_frontend_parameters frp; + char chan_name_t[255]; + char freq_str[255], inv[255], bw[255], lcr[255], hier[255], cr[255], + mod[255], transm[255], gi[255], apid_str[255], vpid_str[255]; + const char *chan_conf = ":%255[^:]:%255[^:]:%255[^:]:%255[^:]:%255[^:]:%255[^:]:%255[^:]:%255[^:]:%255[^:]:%255[^:]:%255[^:]:"; +#endif + char *chan_name; + char *tmp; + char frontend_name[100], demux_name[100], dvr_name[100]; + u32 adapter_num; + + if (!ctx->src) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[DVB4Lin] Missing URL\n")); + return GF_BAD_PARAM; + } + if (!ctx->chcfg) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[DVB4Lin] Missing channels config file\n")); + return GF_BAD_PARAM; + } + chanfile = gf_fopen(ctx->chcfg, "rb"); + if (!chanfile) return GF_BAD_PARAM; + + chan_name = (char *) ctx->src+6; // 6 = strlen("dvb://") + + // support for multiple frontends + tmp = strchr(chan_name, '@'); + if (tmp) { + adapter_num = atoi(tmp+1); + tmp[0] = 0; + } else { + adapter_num = 0; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("Channel name %s\n", chan_name)); + + while(!gf_feof(chanfile)) { + if ( gf_fgets(line, 255, chanfile) != NULL) { + if (line[0]=='#') continue; + if (line[0]=='\r') continue; + if (line[0]=='\n') continue; + +#ifndef GPAC_SIM_LINUX_DVB + strncpy(chan_name_t, line, index(line, ':')-line); + chan_name_t[254] = 0; + + if (strncmp(chan_name,chan_name_t,strlen(chan_name))==0) { + sscanf(strstr(line,":"), chan_conf, freq_str, inv, bw, lcr, cr, mod, transm, gi, hier, apid_str, vpid_str); + ctx->freq = (uint32_t) atoi(freq_str); + ctx->apid = (uint16_t) atoi(apid_str); + ctx->vpid = (uint16_t) atoi(vpid_str); + //Inversion + if(! strcmp(inv, "INVERSION_ON")) ctx->specInv = INVERSION_ON; + else if(! strcmp(inv, "INVERSION_OFF")) ctx->specInv = INVERSION_OFF; + else ctx->specInv = INVERSION_AUTO; + //LP Code Rate + if(! strcmp(lcr, "FEC_1_2")) ctx->LP_CodeRate =FEC_1_2; + else if(! strcmp(lcr, "FEC_2_3")) ctx->LP_CodeRate =FEC_2_3; + else if(! strcmp(lcr, "FEC_3_4")) ctx->LP_CodeRate =FEC_3_4; + else if(! strcmp(lcr, "FEC_4_5")) ctx->LP_CodeRate =FEC_4_5; + else if(! strcmp(lcr, "FEC_6_7")) ctx->LP_CodeRate =FEC_6_7; + else if(! strcmp(lcr, "FEC_8_9")) ctx->LP_CodeRate =FEC_8_9; + else if(! strcmp(lcr, "FEC_5_6")) ctx->LP_CodeRate =FEC_5_6; + else if(! strcmp(lcr, "FEC_7_8")) ctx->LP_CodeRate =FEC_7_8; + else if(! strcmp(lcr, "FEC_NONE")) ctx->LP_CodeRate =FEC_NONE; + else ctx->LP_CodeRate =FEC_AUTO; + //HP Code Rate + if(! strcmp(cr, "FEC_1_2")) ctx->HP_CodeRate =FEC_1_2; + else if(! strcmp(cr, "FEC_2_3")) ctx->HP_CodeRate =FEC_2_3; + else if(! strcmp(cr, "FEC_3_4")) ctx->HP_CodeRate =FEC_3_4; + else if(! strcmp(cr, "FEC_4_5")) ctx->HP_CodeRate =FEC_4_5; + else if(! strcmp(cr, "FEC_6_7")) ctx->HP_CodeRate =FEC_6_7; + else if(! strcmp(cr, "FEC_8_9")) ctx->HP_CodeRate =FEC_8_9; + else if(! strcmp(cr, "FEC_5_6")) ctx->HP_CodeRate =FEC_5_6; + else if(! strcmp(cr, "FEC_7_8")) ctx->HP_CodeRate =FEC_7_8; + else if(! strcmp(cr, "FEC_NONE")) ctx->HP_CodeRate =FEC_NONE; + else ctx->HP_CodeRate =FEC_AUTO; + //Modulation + if(! strcmp(mod, "QAM_128")) ctx->modulation = QAM_128; + else if(! strcmp(mod, "QAM_256")) ctx->modulation = QAM_256; + else if(! strcmp(mod, "QAM_64")) ctx->modulation = QAM_64; + else if(! strcmp(mod, "QAM_32")) ctx->modulation = QAM_32; + else if(! strcmp(mod, "QAM_16")) ctx->modulation = QAM_16; + //Bandwidth + if(! strcmp(bw, "BANDWIDTH_6_MHZ")) ctx->bandwidth = BANDWIDTH_6_MHZ; + else if(! strcmp(bw, "BANDWIDTH_7_MHZ")) ctx->bandwidth = BANDWIDTH_7_MHZ; + else if(! strcmp(bw, "BANDWIDTH_8_MHZ")) ctx->bandwidth = BANDWIDTH_8_MHZ; + //Transmission Mode + if(! strcmp(transm, "TRANSMISSION_MODE_2K")) ctx->TransmissionMode = TRANSMISSION_MODE_2K; + else if(! strcmp(transm, "TRANSMISSION_MODE_8K")) ctx->TransmissionMode = TRANSMISSION_MODE_8K; + //Guard Interval + if(! strcmp(gi, "GUARD_INTERVAL_1_32")) ctx->guardInterval = GUARD_INTERVAL_1_32; + else if(! strcmp(gi, "GUARD_INTERVAL_1_16")) ctx->guardInterval = GUARD_INTERVAL_1_16; + else if(! strcmp(gi, "GUARD_INTERVAL_1_8")) ctx->guardInterval = GUARD_INTERVAL_1_8; + else ctx->guardInterval = GUARD_INTERVAL_1_4; + //Hierarchy + if(! strcmp(hier, "HIERARCHY_1")) ctx->hierarchy = HIERARCHY_1; + else if(! strcmp(hier, "HIERARCHY_2")) ctx->hierarchy = HIERARCHY_2; + else if(! strcmp(hier, "HIERARCHY_4")) ctx->hierarchy = HIERARCHY_4; + else if(! strcmp(hier, "HIERARCHY_AUTO")) ctx->hierarchy = HIERARCHY_AUTO; + else ctx->hierarchy = HIERARCHY_NONE; + + break; + } +#endif + + } + } + gf_fclose(chanfile); + + sprintf(frontend_name, "/dev/dvb/adapter%d/frontend0", adapter_num); + sprintf(demux_name, "/dev/dvb/adapter%d/demux0", adapter_num); + sprintf(dvr_name, "/dev/dvb/adapter%d/dvr0", adapter_num); + +#ifndef GPAC_SIM_LINUX_DVB + // Open frontend + if((front1 = open(frontend_name,O_RDWR|O_NONBLOCK)) < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Cannot open frontend %s.\n", frontend_name)); + return GF_IO_ERR; + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("Frontend %s opened.\n", frontend_name)); + } + // Open demuxes + if ((demux1=open(demux_name, O_RDWR|O_NONBLOCK)) < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Cannot open demux %s\n", demux_name)); + return GF_IO_ERR; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Demux %s opened.\n", demux_name)); + } + // Set FrontendParameters - DVB-T + frp.frequency = ctx->freq; + frp.inversion = ctx->specInv; + frp.u.ofdm.bandwidth = ctx->bandwidth; + frp.u.ofdm.code_rate_HP = ctx->HP_CodeRate; + frp.u.ofdm.code_rate_LP = ctx->LP_CodeRate; + frp.u.ofdm.constellation = ctx->modulation; + frp.u.ofdm.transmission_mode = ctx->TransmissionMode; + frp.u.ofdm.guard_interval = ctx->guardInterval; + frp.u.ofdm.hierarchy_information = ctx->hierarchy; + // Set frontend + if (ioctl(front1, FE_SET_FRONTEND, &frp) < 0) { + return GF_IO_ERR; + } + + // Set dumex + pesFilterParams.pid = 0x2000; // Linux-DVB API take PID=2000 for FULL/RAW TS flag + pesFilterParams.input = DMX_IN_FRONTEND; + pesFilterParams.output = DMX_OUT_TS_TAP; + pesFilterParams.pes_type = DMX_PES_OTHER; + pesFilterParams.flags = DMX_IMMEDIATE_START; + if (ioctl(demux1, DMX_SET_PES_FILTER, &pesFilterParams) < 0) { + return GF_IO_ERR; + } + /* The following code differs from mplayer and alike because the device is opened in blocking mode */ + if ((ctx->demux_fd = open(dvr_name, O_RDONLY/*|O_NONBLOCK*/)) < 0) { + return GF_IO_ERR; + } +#endif + + return GF_OK; +} + +static u32 gf_dvblin_get_freq_from_url(GF_DVBLinuxCtx *ctx, const char *url) +{ + FILE *chcfgig_file; + char line[255], *tmp, *channel_name; + + u32 freq; + + /* get rid of trailing @ */ + tmp = strchr(url, '@'); + if (tmp) tmp[0] = 0; + + channel_name = (char *)url+6; + + chcfgig_file = gf_fopen(ctx->chcfg, "rb"); + if (!chcfgig_file) return GF_BAD_PARAM; + + freq = 0; + while(!gf_feof(chcfgig_file)) { + if ( gf_fgets(line, 255, chcfgig_file) != NULL) { + if (line[0]=='#') continue; + if (line[0]=='\r') continue; + if (line[0]=='\n') continue; + + tmp = strchr(line, ':'); + tmp[0] = 0; + if (!strcmp(line, channel_name)) { + char *tmp2; + tmp++; + tmp2 = strchr(tmp, ':'); + if (tmp2) tmp2[0] = 0; + freq = (u32)atoi(tmp); + break; + } + } + } + return freq; +} + +GF_Err dvblin_setup_demux(GF_DVBLinuxCtx *ctx) +{ + GF_Err e = GF_OK; + + if (!ctx->chcfg) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[DVB4Lin] Missing channels config file\n")); + return GF_BAD_PARAM; + } + if (strnicmp(ctx->src, "dvb://", 6)) return GF_NOT_SUPPORTED; + + if ((ctx->freq != 0) && (ctx->freq == gf_dvblin_get_freq_from_url(ctx, ctx->src)) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[M2TSDemux] Tuner already tuned to that frequency\n")); + return GF_OK; + } + + e = dvblin_tune(ctx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[M2TSDemux] Unable to tune to frequency\n")); + return GF_SERVICE_ERROR; + } + return GF_OK; +} + + + + +GF_Err dvblin_initialize(GF_Filter *filter) +{ + GF_Err e = GF_OK; + GF_DVBLinuxCtx *ctx = (GF_DVBLinuxCtx *) gf_filter_get_udta(filter); + + if (!ctx || !ctx->src) return GF_BAD_PARAM; + e = dvblin_setup_demux(ctx); + + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[DVBLinux] Failed to open %s\n", ctx->src)); + gf_filter_setup_failure(filter, e); + return GF_URL_ERROR; + } + GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[DVBLinux] opening %s\n", ctx->src)); + + ctx->block = gf_malloc(ctx->block_size +1); + return GF_OK; +} + +void dvblin_finalize(GF_Filter *filter) +{ + GF_DVBLinuxCtx *ctx = (GF_DVBLinuxCtx *) gf_filter_get_udta(filter); +#ifndef GPAC_SIM_LINUX_DVB + if (ctx->demux_fd) close(ctx->demux_fd); +#endif + if (ctx->block) gf_free(ctx->block); +} + +GF_FilterProbeScore dvblin_probe_url(const char *url, const char *mime_type) +{ + if (!strnicmp(url, "dvb://", 6)) return GF_FPROBE_SUPPORTED; + return GF_FPROBE_NOT_SUPPORTED; +} + + +static Bool dvblin_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_DVBLinuxCtx *ctx = (GF_DVBLinuxCtx *) gf_filter_get_udta(filter); + + if (!evt->base.on_pid) return GF_FALSE; + if (evt->base.on_pid != ctx->pid) return GF_FALSE; + + switch (evt->base.type) { + case GF_FEVT_PLAY: + dvblin_setup_demux(ctx); + return GF_TRUE; + case GF_FEVT_STOP: +#ifndef GPAC_SIM_LINUX_DVB + if (ctx->demux_fd) close(ctx->demux_fd); + ctx->demux_fd = 0; +#endif + return GF_TRUE; + default: + break; + } + return GF_FALSE; +} + +static GF_Err dvblin_process(GF_Filter *filter) +{ + GF_FilterPacket *dst_pck; + u8 *out_data; + GF_DVBLinuxCtx *ctx = (GF_DVBLinuxCtx *) gf_filter_get_udta(filter); + + if (!ctx->freq) return GF_EOS; + +#ifndef GPAC_SIM_LINUX_DVB + u32 nb_read = read(ctx->demux_fd, ctx->block, ctx->block_size); + if (!nb_read) return GF_OK; +#endif + + dst_pck = gf_filter_pck_new_alloc(ctx->pid, nb_read, &out_data); + memcpy(out_data, ctx->block, nb_read); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + gf_filter_pck_send(dst_pck); + return GF_OK; +} + +#else +static GF_Err dvblin_process(GF_Filter *filter) +{ + return GF_EOS; +} +#endif //GPAC_HAS_LINUX_DVB + + +#ifdef GPAC_HAS_LINUX_DVB +#define OFFS(_n) #_n, offsetof(GF_DVBLinuxCtx, _n) +#else +#define OFFS(_n) #_n, -1 +#endif + +static const GF_FilterArgs DVBLinuxArgs[] = +{ + { OFFS(src), "location of source content", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(block_size), "block size used to read file", GF_PROP_UINT, "65536", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(chcfg), "path to channels.conf file", GF_PROP_NAME, NULL, NULL, 0}, + {0} +}; + +GF_FilterRegister DVBLinuxRegister = { + .name = "dvbin", + GF_FS_SET_DESCRIPTION("DVB for Linux") + GF_FS_SET_HELP("Experimental DVB support for linux, requires a channel config file through [-chcfg]()") + .args = DVBLinuxArgs, +#ifdef GPAC_HAS_LINUX_DVB + .private_size = sizeof(GF_DVBLinuxCtx), + .initialize = dvblin_initialize, + .finalize = dvblin_finalize, + .process = dvblin_process, + .process_event = dvblin_process_event, + .probe_url = dvblin_probe_url +#else + .process = dvblin_process, +#endif +}; + +const GF_FilterRegister *dvblin_register(GF_FilterSession *session) +{ +#if !defined(GPAC_HAS_LINUX_DVB) || defined(GPAC_SIM_LINUX_DVB) + if (!gf_opts_get_bool("temp", "gendoc")) + return NULL; + DVBLinuxRegister.version = "! Warning: DVB4Linux NOT AVAILABLE IN THIS BUILD !"; +#endif + return &DVBLinuxRegister; +} + diff --git a/src/filters/in_file.c b/src/filters/in_file.c new file mode 100644 index 0000000..a4cc2f0 --- /dev/null +++ b/src/filters/in_file.c @@ -0,0 +1,593 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / generic FILE input filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include + +enum{ + FILE_RAND_NONE=0, + FILE_RAND_ANY, + FILE_RAND_SC_ANY, + FILE_RAND_SC_AVC, + FILE_RAND_SC_HEVC, + FILE_RAND_SC_AV1 +}; + +typedef struct +{ + //options + char *src; + char *ext, *mime; + u32 block_size; + GF_Fraction64 range; + + //only one output pid declared + GF_FilterPid *pid; + + FILE *file; + u64 file_size; + u64 file_pos, end_pos; + Bool is_end, pck_out; + Bool is_null; + Bool full_file_only; + Bool do_reconfigure; + char *block; + u32 is_random; + Bool cached_set; + Bool no_failure; +} GF_FileInCtx; + + +static GF_Err filein_initialize(GF_Filter *filter) +{ + GF_FileInCtx *ctx = (GF_FileInCtx *) gf_filter_get_udta(filter); + FILE *old_file = NULL; + char *ext_start = NULL; + char *frag_par = NULL; + char *cgi_par = NULL; + char *src, *path; + const char *prev_url=NULL; + + if (!ctx || !ctx->src) return GF_BAD_PARAM; + + if (!strcmp(ctx->src, "null")) { + ctx->pid = gf_filter_pid_new(filter); + gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE)); + gf_filter_pid_set_eos(ctx->pid); + ctx->is_end = GF_TRUE; + return GF_OK; + } + if (!strcmp(ctx->src, "rand") || !strcmp(ctx->src, "randsc")) { + gf_rand_init(GF_FALSE); + ctx->is_random = FILE_RAND_ANY; + if (!strcmp(ctx->src, "randsc")) { + ctx->is_random = FILE_RAND_SC_ANY; + if (ctx->mime) { + if (!strcmp(ctx->mime, "video/avc")) ctx->is_random = FILE_RAND_SC_AVC; + if (!strcmp(ctx->mime, "video/hevc")) ctx->is_random = FILE_RAND_SC_HEVC; + if (!strcmp(ctx->mime, "video/av1")) ctx->is_random = FILE_RAND_SC_AV1; + } + } + + if (!ctx->block_size) ctx->block_size = 5000; + while (ctx->block_size % 4) ctx->block_size++; + ctx->block = gf_malloc(ctx->block_size +1); + return GF_OK; + } + + + + if (strnicmp(ctx->src, "file:/", 6) && strnicmp(ctx->src, "gfio:/", 6) && strstr(ctx->src, "://")) { + gf_filter_setup_failure(filter, GF_NOT_SUPPORTED); + return GF_NOT_SUPPORTED; + } + path = strstr(ctx->src, "://"); + if (path) path += 3; + if (path && strstr(path, "://")) { + ctx->is_end = GF_TRUE; + return gf_filter_pid_raw_new(filter, path, path, NULL, NULL, NULL, 0, GF_TRUE, &ctx->pid); + } + + //local file + + //strip any fragment identifer + ext_start = gf_file_ext_start(ctx->src); + frag_par = strchr(ext_start ? ext_start : ctx->src, '#'); + if (frag_par) frag_par[0] = 0; + cgi_par = strchr(ctx->src, '?'); + if (cgi_par) cgi_par[0] = 0; + + src = (char *) ctx->src; + if (!strnicmp(ctx->src, "file://", 7)) src += 7; + else if (!strnicmp(ctx->src, "file:", 5)) src += 5; + + //for gfio, do not close file until we open the new one + if (ctx->do_reconfigure) { + old_file = ctx->file; + ctx->file = NULL; + if (gf_fileio_check(old_file)) + prev_url = gf_fileio_url((GF_FileIO *)old_file); + } + + if (!ctx->file) { + ctx->file = gf_fopen_ex(src, prev_url, "rb"); + } + + if (old_file) { + gf_fclose(old_file); + } + + if (!ctx->file) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileIn] Failed to open %s\n", src)); + + if (frag_par) frag_par[0] = '#'; + if (cgi_par) cgi_par[0] = '?'; + + if (ctx->no_failure) { + gf_filter_notification_failure(filter, GF_URL_ERROR, GF_FALSE); + ctx->is_end = GF_TRUE; + return GF_OK; + } + + gf_filter_setup_failure(filter, GF_URL_ERROR); +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode() && !strcmp(src, "blob")) + return GF_OK; +#endif + return GF_URL_ERROR; + } + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[FileIn] opening %s\n", src)); + ctx->file_size = gf_fsize(ctx->file); + + ctx->cached_set = GF_FALSE; + ctx->full_file_only = GF_FALSE; + + if (ctx->do_reconfigure && gf_fileio_check(ctx->file)) { + GF_FileIO *gfio = (GF_FileIO *)ctx->file; + gf_free(ctx->src); + ctx->src = gf_strdup(gf_fileio_url(gfio)); + } + + + ctx->file_pos = ctx->range.num; + if (ctx->range.den) { + ctx->end_pos = ctx->range.den; + if (ctx->end_pos>ctx->file_size) { + ctx->range.den = ctx->end_pos = ctx->file_size; + } + } + gf_fseek(ctx->file, ctx->file_pos, SEEK_SET); + ctx->is_end = GF_FALSE; + + if (frag_par) frag_par[0] = '#'; + if (cgi_par) cgi_par[0] = '?'; + + if (!ctx->block) { + if (!ctx->block_size) { + if (ctx->file_size>500000000) ctx->block_size = 1000000; + else ctx->block_size = 5000; + } + ctx->block = gf_malloc(ctx->block_size +1); + } + return GF_OK; +} + +static void filein_finalize(GF_Filter *filter) +{ + GF_FileInCtx *ctx = (GF_FileInCtx *) gf_filter_get_udta(filter); + + if (ctx->file) gf_fclose(ctx->file); + if (ctx->block) gf_free(ctx->block); +} + +static GF_FilterProbeScore filein_probe_url(const char *url, const char *mime_type) +{ + char *ext_start = NULL; + char *frag_par = NULL; + char *cgi_par = NULL; + char *src = (char *) url; + Bool res; + + if (!strcmp(url, "-") || !strcmp(url, "stdin")) return GF_FPROBE_NOT_SUPPORTED; + + if (!strnicmp(url, "file://", 7)) src += 7; + else if (!strnicmp(url, "file:", 5)) src += 5; + + if (!strcmp(url, "null")) return GF_FPROBE_SUPPORTED; + if (!strcmp(url, "rand")) return GF_FPROBE_SUPPORTED; + if (!strcmp(url, "randsc")) return GF_FPROBE_SUPPORTED; + if (!strncmp(url, "gfio://", 7)) { + GF_FileIO *gfio = gf_fileio_from_url(url); + if (gfio && gf_fileio_read_mode(gfio)) + return GF_FPROBE_SUPPORTED; + return GF_FPROBE_NOT_SUPPORTED; + } + + //strip any fragment identifer + ext_start = gf_file_ext_start(url); + frag_par = strchr(ext_start ? ext_start : url, '#'); + if (frag_par) frag_par[0] = 0; + cgi_par = strchr(url, '?'); + if (cgi_par) cgi_par[0] = 0; + + res = gf_file_exists(src); + + if (frag_par) frag_par[0] = '#'; + if (cgi_par) cgi_par[0] = '?'; + + return res ? GF_FPROBE_SUPPORTED : GF_FPROBE_NOT_SUPPORTED; +} + +static Bool filein_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_FileInCtx *ctx = (GF_FileInCtx *) gf_filter_get_udta(filter); + + if (evt->base.on_pid && (evt->base.on_pid != ctx->pid)) + return GF_FALSE; + + switch (evt->base.type) { + case GF_FEVT_PLAY: + case GF_FEVT_PLAY_HINT: + ctx->full_file_only = evt->play.full_file_only; + return GF_TRUE; + case GF_FEVT_STOP: + //stop sending data + ctx->is_end = GF_TRUE; + gf_filter_pid_set_eos(ctx->pid); + return GF_TRUE; + case GF_FEVT_SOURCE_SEEK: + if (ctx->is_random) + return GF_TRUE; + if (ctx->file_size && (evt->seek.start_offset >= ctx->file_size)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileIn] Seek request outside of file %s range ("LLU" vs size "LLU")\n", ctx->src, evt->seek.start_offset, ctx->file_size)); + return GF_TRUE; + } + + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[FileIn] Asked to seek source to range "LLU"-"LLU"\n", evt->seek.start_offset, evt->seek.end_offset)); + ctx->is_end = GF_FALSE; + + if (gf_fileio_check(ctx->file)) { + ctx->cached_set = GF_FALSE; + } + + if (ctx->file) { + int res = gf_fseek(ctx->file, evt->seek.start_offset, SEEK_SET); + if (res) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileIn] Seek on file failed: %d\n", res)); + return GF_TRUE; + } + } + + ctx->file_pos = evt->seek.start_offset; + ctx->end_pos = evt->seek.end_offset; + if (ctx->end_pos>ctx->file_size) ctx->end_pos = ctx->file_size; + ctx->range.num = evt->seek.start_offset; + ctx->range.den = ctx->end_pos; + if (evt->seek.hint_block_size > ctx->block_size) { + ctx->block_size = evt->seek.hint_block_size; + ctx->block = gf_realloc(ctx->block, ctx->block_size+1); + } + return GF_TRUE; + case GF_FEVT_SOURCE_SWITCH: + if (ctx->is_random) + return GF_TRUE; + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[FileIn] Asked to switch source to %s (range "LLU"-"LLU")\n", evt->seek.source_switch ? evt->seek.source_switch : "self", evt->seek.start_offset, evt->seek.end_offset)); + assert(ctx->is_end); + ctx->range.num = evt->seek.start_offset; + ctx->range.den = evt->seek.end_offset; + if (evt->seek.source_switch) { + if (strcmp(evt->seek.source_switch, ctx->src)) { + gf_free(ctx->src); + ctx->src = gf_strdup(evt->seek.source_switch); + } + ctx->do_reconfigure = GF_TRUE; + } + //don't send a setup failure on source switch (this would destroy ourselves which we don't want in DASH) + ctx->no_failure = GF_TRUE; + filein_initialize(filter); + gf_filter_post_process_task(filter); + break; + default: + break; + } + return GF_FALSE; +} + + +static void filein_pck_destructor(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck) +{ + GF_FileInCtx *ctx = (GF_FileInCtx *) gf_filter_get_udta(filter); + ctx->pck_out = GF_FALSE; + //ready to process again + gf_filter_post_process_task(filter); +} + +static GF_Err filein_process(GF_Filter *filter) +{ + GF_Err e; + u32 nb_read, to_read; + u64 lto_read; + GF_FilterPacket *pck; + GF_FileInCtx *ctx = (GF_FileInCtx *) gf_filter_get_udta(filter); + + if (ctx->is_end) + return GF_EOS; + + //until packet is released we return EOS (no processing), and ask for processing again upon release + if (ctx->pck_out) + return GF_EOS; + if (ctx->pid && gf_filter_pid_would_block(ctx->pid)) { + assert(0); + return GF_OK; + } + + if (ctx->full_file_only && ctx->pid && !ctx->do_reconfigure && ctx->cached_set) { + ctx->is_end = GF_TRUE; + pck = gf_filter_pck_new_shared(ctx->pid, ctx->block, 0, filein_pck_destructor); + gf_filter_pck_set_framing(pck, ctx->file_pos ? GF_FALSE : GF_TRUE, ctx->is_end); + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + ctx->pck_out = GF_TRUE; + gf_filter_pck_send(pck); + gf_filter_pid_set_eos(ctx->pid); + + if (ctx->file_size && gf_filter_reporting_enabled(filter)) { + char szStatus[1024], *szSrc; + szSrc = gf_file_basename(ctx->src); + + sprintf(szStatus, "%s: EOS (dispatch canceled after "LLD" b, file size "LLD" b)", szSrc, (s64) ctx->file_pos, (s64) ctx->file_size); + gf_filter_update_status(filter, 10000, szStatus); + } + return GF_OK; + } + if (ctx->is_random) { + u32 i; + if (ctx->is_random>=FILE_RAND_SC_ANY) { + for (i=0; iblock_size; i+= 4) { + u32 val = gf_rand(); + + if (i+4>=ctx->block_size) { + * ((u32 *) (ctx->block + i)) = val; + continue; + } + if (val % 100) { + * ((u32 *) (ctx->block + i)) = val; + continue; + } + if (ctx->is_random==FILE_RAND_SC_AVC) { + u32 rand_high = val>>24; + * ((u32 *) (ctx->block + i)) = 0x00000001; + i += 4; + val &= 0x00FFFFFF; + rand_high = rand_high%31; + rand_high <<= 24; + val |= rand_high; + * ((u32 *) (ctx->block + i)) = val; + } else if (ctx->is_random==FILE_RAND_SC_HEVC) { + u32 rand_high = val>>16; + * ((u32 *) (ctx->block + i)) = 0x00000001; + i += 4; + val &= 0x0000FFFF; + rand_high = rand_high % 63; + rand_high <<= 8; + rand_high |= 1; //end of layerid (=0) and temporal sublayer (=1) + rand_high <<= 16; + val |= rand_high; + * ((u32 *) (ctx->block + i)) = val; + } + else { + val &= 0x000001FF; + * ((u32 *) (ctx->block + i)) = val; + } + } + } else { + for (i=0; iblock_size; i+= 4) { + * ((u32 *) (ctx->block + i)) = gf_rand(); + } + } + ctx->block[ctx->block_size]=0; + + if (!ctx->pid) { + e = gf_filter_pid_raw_new(filter, ctx->src, ctx->src, ctx->mime, ctx->ext, ctx->block, ctx->block_size, GF_TRUE, &ctx->pid); + if (e) return e; + } + pck = gf_filter_pck_new_shared(ctx->pid, ctx->block, ctx->block_size, filein_pck_destructor); + if (!pck) + return GF_OK; + + gf_filter_pck_set_framing(pck, ctx->file_pos ? GF_FALSE : GF_TRUE, GF_FALSE); + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + ctx->file_pos += ctx->block_size; + + ctx->pck_out = GF_TRUE; + gf_filter_pck_send(pck); + return GF_OK; + } + + //compute size to read as u64 (large file) + if (ctx->end_pos > ctx->file_pos) + lto_read = ctx->end_pos - ctx->file_pos; + else if (ctx->file_size) + lto_read = ctx->file_size - ctx->file_pos; + else + lto_read = ctx->block_size; + + //and clamp based on blocksize as u32 + if (lto_read > (u64) ctx->block_size) + to_read = (u64) ctx->block_size; + else + to_read = (u32) lto_read; + + nb_read = (u32) gf_fread(ctx->block, to_read, ctx->file); + if (!nb_read) + ctx->file_size = ctx->file_pos; + + ctx->block[nb_read] = 0; + if (!ctx->pid || ctx->do_reconfigure) { + //quick hack for ID3v2: if detected, increase block size to have the full id3v2 + some frames in the initial block + //to avoid relying on file extension for demux + if (!ctx->pid && (nb_read>10) + && (ctx->block[0] == 'I' && ctx->block[1] == 'D' && ctx->block[2] == '3') + ) { + u32 tag_size = ((ctx->block[9] & 0x7f) + ((ctx->block[8] & 0x7f) << 7) + ((ctx->block[7] & 0x7f) << 14) + ((ctx->block[6] & 0x7f) << 21)); + if (tag_size > nb_read) { + u32 probe_size = tag_size + ctx->block_size; + if (probe_size>ctx->file_size) + probe_size = (u32) ctx->file_size; + + ctx->block_size = probe_size; + ctx->block = gf_realloc(ctx->block, ctx->block_size+1); + nb_read += (u32) gf_fread(ctx->block + nb_read, probe_size-nb_read, ctx->file); + } + } + + ctx->do_reconfigure = GF_FALSE; + e = gf_filter_pid_raw_new(filter, ctx->src, ctx->src, ctx->mime, ctx->ext, ctx->block, nb_read, GF_TRUE, &ctx->pid); + if (e) return e; + + if (!gf_fileio_check(ctx->file)) { + gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_FILE_CACHED, &PROP_BOOL(GF_TRUE) ); + + gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_DOWN_SIZE, ctx->file_size ? &PROP_LONGUINT(ctx->file_size) : NULL); + + ctx->cached_set = GF_TRUE; + } + + if (ctx->range.num || ctx->range.den) + gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_FILE_RANGE, &PROP_FRAC64(ctx->range) ); + } + + //GFIO wrapper, gets stats and update + if (!ctx->cached_set) { + u64 bdone, btotal; + Bool fcached; + u32 bytes_per_sec; + if (gf_fileio_get_stats((GF_FileIO *)ctx->file, &bdone, &btotal, &fcached, &bytes_per_sec)) { + if (fcached) { + gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_FILE_CACHED, &PROP_BOOL(fcached) ); + ctx->cached_set = GF_TRUE; + } + + gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_SIZE, &PROP_LONGUINT(btotal) ); + gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT(bdone) ); + gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_RATE, &PROP_UINT(bytes_per_sec*8) ); + } + } + + pck = gf_filter_pck_new_shared(ctx->pid, ctx->block, nb_read, filein_pck_destructor); + if (!pck) + return GF_OK; + + gf_filter_pck_set_byte_offset(pck, ctx->file_pos); + + if (ctx->file_size && (ctx->file_pos + nb_read == ctx->file_size)) { + ctx->is_end = GF_TRUE; + gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT(ctx->file_size) ); + } else { + if (nb_read < to_read) { + Bool is_eof; + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[FileIn] Asked to read %d but got only %d\n", to_read, nb_read)); + + is_eof = gf_feof(ctx->file); + + if (is_eof) { + gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT(ctx->file_size) ); + ctx->is_end = GF_TRUE; + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileIn] IO error EOF found after reading %d bytes but file %s size is %d\n", ctx->file_pos+nb_read, ctx->src, ctx->file_size)); + } + } else { + gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT(ctx->file_pos) ); + } + } + gf_filter_pck_set_framing(pck, ctx->file_pos ? GF_FALSE : GF_TRUE, ctx->is_end); + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + ctx->file_pos += nb_read; + + ctx->pck_out = GF_TRUE; + gf_filter_pck_send(pck); + + if (ctx->file_size && gf_filter_reporting_enabled(filter)) { + char szStatus[1024], *szSrc; + szSrc = gf_file_basename(ctx->src); + + sprintf(szStatus, "%s: % 16"LLD_SUF" /% 16"LLD_SUF" (%02.02f)", szSrc, (s64) ctx->file_pos, (s64) ctx->file_size, ((Double)ctx->file_pos*100.0)/ctx->file_size); + gf_filter_update_status(filter, (u32) (ctx->file_pos*10000/ctx->file_size), szStatus); + } + + if (ctx->is_end) { + gf_filter_pid_set_eos(ctx->pid); + return GF_EOS; + } + return ctx->pck_out ? GF_EOS : GF_OK; +} + + + +#define OFFS(_n) #_n, offsetof(GF_FileInCtx, _n) + +static const GF_FilterArgs FileInArgs[] = +{ + { OFFS(src), "location of source content", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(block_size), "block size used to read file. 0 means 5000 if file less than 500m, 1M otherwise", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(range), "byte range", GF_PROP_FRACTION64, "0-0", NULL, 0}, + { OFFS(ext), "override file extension", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(mime), "set file mime type", GF_PROP_NAME, NULL, NULL, 0}, + {0} +}; + +static const GF_FilterCapability FileInCaps[] = +{ + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), +}; + +GF_FilterRegister FileInRegister = { + .name = "fin", + GF_FS_SET_DESCRIPTION("File input") + GF_FS_SET_HELP("This filter dispatch raw blocks from input file into a filter chain.\n" + "Block size can be adjusted using [-block_size]().\n" + "Content format can be forced through [-mime]() and file extension can be changed through [-ext]().\n" + "Note: Unless disabled at session level (see [-no-probe](CORE) ), file extensions are usually ignored and format probing is done on the first data block.\n" + "The special file name `null` is used for creating a file with no data, needed by some filters such as [dasher](dasher).\n" + "The special file name `rand` is used to generate random data.\n" + "The special file name `randsc` is used to generate random data with fake startcodes (0x000001).\n" + "\n" + "The filter handles both files and GF_FileIO objects as input URL.\n" + ) + .private_size = sizeof(GF_FileInCtx), + .args = FileInArgs, + .initialize = filein_initialize, + SETCAPS(FileInCaps), + .finalize = filein_finalize, + .process = filein_process, + .process_event = filein_process_event, + .probe_url = filein_probe_url +}; + + +const GF_FilterRegister *filein_register(GF_FilterSession *session) +{ + return &FileInRegister; +} + diff --git a/src/filters/in_http.c b/src/filters/in_http.c new file mode 100644 index 0000000..82c6752 --- /dev/null +++ b/src/filters/in_http.c @@ -0,0 +1,478 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / HTTP input filter using GPAC http stack + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include +#include + + +typedef enum +{ + GF_HTTPIN_STORE_DISK=0, + GF_HTTPIN_STORE_DISK_KEEP, + GF_HTTPIN_STORE_MEM, + GF_HTTPIN_STORE_NONE, +} GF_HTTPInStoreMode; + +typedef struct +{ + //options + char *src; + u32 block_size; + GF_HTTPInStoreMode cache; + GF_Fraction64 range; + char *ext; + char *mime; + + //internal + Bool initial_ack_done; + GF_DownloadManager *dm; + + //only one output pid declared + GF_FilterPid *pid; + + GF_DownloadSession *sess; + + char *block; + Bool pck_out, is_end; + u64 nb_read, file_size; + FILE *cached; + + Bool do_reconfigure; + Bool full_file_only; + GF_Err last_state; +} GF_HTTPInCtx; + +static void httpin_notify_error(GF_Filter *filter, GF_HTTPInCtx *ctx, GF_Err e) +{ + if (filter && (ctx->last_state == GF_OK)) { + if (!ctx->initial_ack_done) { + gf_filter_setup_failure(filter, e); + ctx->initial_ack_done = GF_TRUE; + } else { + gf_filter_notification_failure(filter, e, GF_FALSE); + } + ctx->last_state = e; + } +} + +static GF_Err httpin_initialize(GF_Filter *filter) +{ + GF_HTTPInCtx *ctx = (GF_HTTPInCtx *) gf_filter_get_udta(filter); + GF_Err e; + char *server; + u32 flags = 0; + + if (!ctx || !ctx->src) return GF_BAD_PARAM; + ctx->dm = gf_filter_get_download_manager(filter); + if (!ctx->dm) return GF_SERVICE_ERROR; + + ctx->block = gf_malloc(ctx->block_size +1); + + flags = GF_NETIO_SESSION_NOT_THREADED | GF_NETIO_SESSION_PERSISTENT; + if (ctx->cache==GF_HTTPIN_STORE_MEM) flags |= GF_NETIO_SESSION_MEMORY_CACHE; + else if (ctx->cache==GF_HTTPIN_STORE_NONE) flags |= GF_NETIO_SESSION_NOT_CACHED; + else if (ctx->cache==GF_HTTPIN_STORE_DISK_KEEP) flags |= GF_NETIO_SESSION_KEEP_CACHE; + + server = strstr(ctx->src, "://"); + if (server) server += 3; + if (server && strstr(server, "://")) { + ctx->is_end = GF_TRUE; + return gf_filter_pid_raw_new(filter, server, server, NULL, NULL, NULL, 0, GF_FALSE, &ctx->pid); + } + + ctx->sess = gf_dm_sess_new(ctx->dm, ctx->src, flags, NULL, NULL, &e); + if (e) { + gf_filter_setup_failure(filter, e); + ctx->initial_ack_done = GF_TRUE; + return e; + } + if (ctx->range.den) { + gf_dm_sess_set_range(ctx->sess, ctx->range.num, ctx->range.den, GF_TRUE); + } + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) + httpin_notify_error(NULL, NULL, GF_OK); +#endif + + return GF_OK; +} + +void httpin_finalize(GF_Filter *filter) +{ + GF_HTTPInCtx *ctx = (GF_HTTPInCtx *) gf_filter_get_udta(filter); + + if (ctx->sess) gf_dm_sess_del(ctx->sess); + + if (ctx->block) gf_free(ctx->block); + if (ctx->cached) gf_fclose(ctx->cached); +} + +static GF_FilterProbeScore httpin_probe_url(const char *url, const char *mime_type) +{ + if (!strnicmp(url, "http://", 7) ) return GF_FPROBE_SUPPORTED; + if (!strnicmp(url, "https://", 8) ) return GF_FPROBE_SUPPORTED; + if (!strnicmp(url, "gmem://", 7) ) return GF_FPROBE_SUPPORTED; + return GF_FPROBE_NOT_SUPPORTED; +} + +static Bool httpin_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_Err e; + GF_HTTPInCtx *ctx = (GF_HTTPInCtx *) gf_filter_get_udta(filter); + + if (evt->base.on_pid && (evt->base.on_pid != ctx->pid)) return GF_FALSE; + + switch (evt->base.type) { + case GF_FEVT_PLAY: + ctx->is_end = GF_FALSE; + ctx->full_file_only = evt->play.full_file_only; + return GF_TRUE; + case GF_FEVT_STOP: + if (!ctx->is_end) { + //abort session + gf_filter_pid_set_eos(ctx->pid); + ctx->is_end = GF_TRUE; + if (ctx->sess) { + gf_dm_sess_abort(ctx->sess); + gf_dm_sess_del(ctx->sess); + ctx->sess = NULL; + } + } + return GF_TRUE; + case GF_FEVT_SOURCE_SEEK: + if (evt->seek.start_offset < ctx->file_size) { + ctx->is_end = GF_FALSE; + //open cache if needed + if (!ctx->cached && ctx->file_size && (ctx->nb_read==ctx->file_size) && ctx->sess) { + const char *cached = gf_dm_sess_get_cache_name(ctx->sess); + if (cached) ctx->cached = gf_fopen(cached, "rb"); + } + ctx->nb_read = evt->seek.start_offset; + + if (ctx->cached) { + gf_fseek(ctx->cached, ctx->nb_read, SEEK_SET); + } else if (ctx->sess) { + gf_dm_sess_abort(ctx->sess); + gf_dm_sess_set_range(ctx->sess, ctx->nb_read, 0, GF_TRUE); + } + ctx->range.den = 0; + ctx->range.num = ctx->nb_read; + ctx->last_state = GF_OK; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPIn] Requested seek outside file range !\n") ); + ctx->is_end = GF_TRUE; + gf_filter_pid_set_eos(ctx->pid); + } + return GF_TRUE; + case GF_FEVT_SOURCE_SWITCH: + assert(ctx->is_end); + assert(!ctx->pck_out); + if (evt->seek.source_switch) { + if (ctx->src && ctx->sess && (ctx->cache!=GF_HTTPIN_STORE_DISK_KEEP) && !evt->seek.previous_is_init_segment) { + gf_dm_delete_cached_file_entry_session(ctx->sess, ctx->src); + } + if (ctx->src) gf_free(ctx->src); + ctx->src = gf_strdup(evt->seek.source_switch); + } + if (ctx->cached) gf_fclose(ctx->cached); + ctx->cached = NULL; + + //abort type + if (evt->seek.start_offset == (u64) -1) { + if (!ctx->is_end) { + if (ctx->sess) + gf_dm_sess_abort(ctx->sess); + ctx->is_end = GF_TRUE; + gf_filter_pid_set_eos(ctx->pid); + } + ctx->nb_read = 0; + ctx->last_state = GF_OK; + return GF_TRUE; + } + ctx->last_state = GF_OK; + if (ctx->sess) { + e = gf_dm_sess_setup_from_url(ctx->sess, ctx->src, evt->seek.skip_cache_expiration); + } else { + u32 flags; + + flags = GF_NETIO_SESSION_NOT_THREADED | GF_NETIO_SESSION_PERSISTENT; + if (ctx->cache==GF_HTTPIN_STORE_MEM) flags |= GF_NETIO_SESSION_MEMORY_CACHE; + else if (ctx->cache==GF_HTTPIN_STORE_NONE) flags |= GF_NETIO_SESSION_NOT_CACHED; + + ctx->sess = gf_dm_sess_new(ctx->dm, ctx->src, flags, NULL, NULL, &e); + } + + if (!e) e = gf_dm_sess_set_range(ctx->sess, evt->seek.start_offset, evt->seek.end_offset, GF_TRUE); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPIn] Cannot resetup session from URL %s: %s\n", ctx->src, gf_error_to_string(e) ) ); + httpin_notify_error(filter, ctx, e); + ctx->is_end = GF_TRUE; + if (ctx->src) gf_free(ctx->src); + ctx->src = NULL; + return GF_TRUE; + } + ctx->nb_read = ctx->file_size = 0; + ctx->do_reconfigure = GF_TRUE; + ctx->is_end = GF_FALSE; + ctx->last_state = GF_OK; + gf_filter_post_process_task(filter); + return GF_TRUE; + default: + break; + } + return GF_TRUE; +} + +static void httpin_rel_pck(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck) +{ + GF_HTTPInCtx *ctx = (GF_HTTPInCtx *) gf_filter_get_udta(filter); + ctx->pck_out = GF_FALSE; + //ready to process again + gf_filter_post_process_task(filter); +} + +static GF_Err httpin_process(GF_Filter *filter) +{ + Bool is_start; + u32 nb_read=0; + GF_FilterPacket *pck; + GF_Err e=GF_OK; + u32 bytes_per_sec=0; + u64 bytes_done=0, total_size, byte_offset; + GF_NetIOStatus net_status; + GF_HTTPInCtx *ctx = (GF_HTTPInCtx *) gf_filter_get_udta(filter); + + //until packet is released we return EOS (no processing), and ask for processing again upon release + if (ctx->pck_out) + return GF_EOS; + + if (ctx->is_end) + return GF_EOS; + + if (!ctx->sess) + return GF_EOS; + + if (!ctx->pid) { + if (ctx->nb_read) return GF_SERVICE_ERROR; + } else { + //TODO: go on fetching data to cache even when not consuming, and reread from cache + if (gf_filter_pid_would_block(ctx->pid)) + return GF_OK; + } + + is_start = ctx->nb_read ? GF_FALSE : GF_TRUE; + ctx->is_end = GF_FALSE; + + //we read from cache file + if (ctx->cached) { + u32 to_read; + u64 lto_read = ctx->file_size - ctx->nb_read; + + if (lto_read > (u64) ctx->block_size) + to_read = (u64) ctx->block_size; + else + to_read = (u32) lto_read; + + if (ctx->full_file_only) { + ctx->is_end = GF_TRUE; + pck = gf_filter_pck_new_shared(ctx->pid, ctx->block, 0, httpin_rel_pck); + gf_filter_pck_set_framing(pck, is_start, ctx->is_end); + + //mark packet out BEFORE sending, since the call to send() may destroy the packet if cloned + ctx->pck_out = GF_TRUE; + gf_filter_pck_send(pck); + + gf_filter_pid_set_eos(ctx->pid); + return GF_EOS; + } + nb_read = (u32) gf_fread(ctx->block, to_read, ctx->cached); + bytes_per_sec = 0; + + } + //we read from network + else { + + e = gf_dm_sess_fetch_data(ctx->sess, ctx->block, ctx->block_size, &nb_read); + if (e<0) { + if (e==GF_IP_NETWORK_EMPTY) { + if (ctx->pid) { + gf_dm_sess_get_stats(ctx->sess, NULL, NULL, NULL, NULL, &bytes_per_sec, NULL); + gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_RATE, &PROP_UINT(8*bytes_per_sec) ); + } + gf_filter_ask_rt_reschedule(filter, 1000); + return GF_OK; + } + if (! ctx->nb_read) + httpin_notify_error(filter, ctx, e); + + ctx->is_end = GF_TRUE; + if (ctx->pid) + gf_filter_pid_set_eos(ctx->pid); + return e; + } + gf_dm_sess_get_stats(ctx->sess, NULL, NULL, &total_size, &bytes_done, &bytes_per_sec, &net_status); + + //wait until we have some data to declare the pid + if ((e!= GF_EOS) && !nb_read) return GF_OK; + + if (!ctx->pid || ctx->do_reconfigure) { + u32 idx; + const char *hname, *hval; + const char *cached = gf_dm_sess_get_cache_name(ctx->sess); + + ctx->do_reconfigure = GF_FALSE; + + if ((e==GF_EOS) && cached && strnicmp(cached, "gmem://", 7)) { + ctx->cached = gf_fopen(cached, "rb"); + if (ctx->cached) { + nb_read = (u32) gf_fread(ctx->block, ctx->block_size, ctx->cached); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPIn] Failed to open cached file %s\n", cached)); + } + } + ctx->file_size = total_size; + ctx->block[nb_read] = 0; + e = gf_filter_pid_raw_new(filter, ctx->src, cached, ctx->mime ? ctx->mime : gf_dm_sess_mime_type(ctx->sess), ctx->ext, ctx->block, nb_read, ctx->mime ? GF_TRUE : GF_FALSE, &ctx->pid); + if (e) return e; + + gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_FILE_CACHED, &PROP_BOOL(GF_FALSE) ); + + if (!ctx->initial_ack_done) { + ctx->initial_ack_done = GF_TRUE; + gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_DOWNLOAD_SESSION, &PROP_POINTER( (void*)ctx->sess ) ); + } + + /*in test mode don't expose http headers (they contain date/version/etc)*/ + if (! gf_sys_is_test_mode()) { + idx = 0; + while (gf_dm_sess_enum_headers(ctx->sess, &idx, &hname, &hval) == GF_OK) { + gf_filter_pid_set_property_dyn(ctx->pid, (char *) hname, & PROP_STRING(hval)); + } + } + } + + gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_RATE, &PROP_UINT(8*bytes_per_sec) ); + if (ctx->range.num && ctx->file_size) { + gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT(bytes_done + ctx->range.num) ); + gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_SIZE, &PROP_LONGUINT(ctx->file_size) ); + } else { + gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT(bytes_done) ); + gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_SIZE, &PROP_LONGUINT(ctx->file_size ? ctx->file_size : bytes_done) ); + } + } + + byte_offset = ctx->nb_read; + + ctx->nb_read += nb_read; + if (ctx->file_size && (ctx->nb_read==ctx->file_size)) { + ctx->is_end = GF_TRUE; + } else if (e==GF_EOS) { + ctx->is_end = GF_TRUE; + } + + pck = gf_filter_pck_new_shared(ctx->pid, ctx->block, nb_read, httpin_rel_pck); + if (!pck) return GF_OK; + + gf_filter_pck_set_cts(pck, 0); + + gf_filter_pck_set_framing(pck, is_start, ctx->is_end); + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + gf_filter_pck_set_byte_offset(pck, byte_offset); + + //mark packet out BEFORE sending, since the call to send() may destroy the packet if cloned + ctx->pck_out = GF_TRUE; + gf_filter_pck_send(pck); + + if (ctx->file_size && gf_filter_reporting_enabled(filter)) { + char szStatus[1024], *szSrc; + szSrc = gf_file_basename(ctx->src); + + sprintf(szStatus, "%s: % 16"LLD_SUF" /% 16"LLD_SUF" (%02.02f) % 8d kbps", szSrc, (s64) bytes_done, (s64) ctx->file_size, ((Double)bytes_done*100.0)/ctx->file_size, bytes_per_sec*8/1000); + gf_filter_update_status(filter, (u32) (bytes_done*10000/ctx->file_size), szStatus); + } + + if (ctx->is_end) { + const char *cached = gf_dm_sess_get_cache_name(ctx->sess); + if (cached) + gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_FILE_CACHED, &PROP_BOOL(GF_TRUE) ); + + gf_filter_pid_set_eos(ctx->pid); + return GF_EOS; + } + + return ctx->pck_out ? GF_EOS : GF_OK; +} + + + +#define OFFS(_n) #_n, offsetof(GF_HTTPInCtx, _n) + +static const GF_FilterArgs HTTPInArgs[] = +{ + { OFFS(src), "location of source content", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(block_size), "block size used to read file", GF_PROP_UINT, "100000", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(cache), "set cache mode\n" + "- disk: cache to disk, discard once session is no longer used\n" + "- keep: cache to disk and keep\n" + "- mem: stores to memory, discard once session is no longer used\n" + "- none: no cache", GF_PROP_UINT, "disk", "disk|keep|mem|none", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(range), "set byte range, as fraction", GF_PROP_FRACTION64, "0-0", NULL, 0}, + { OFFS(ext), "override file extension", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(mime), "set file mime type", GF_PROP_NAME, NULL, NULL, 0}, + {0} +}; + +static const GF_FilterCapability HTTPInCaps[] = +{ + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), +}; + +GF_FilterRegister HTTPInRegister = { + .name = "httpin", + GF_FS_SET_DESCRIPTION("HTTP input") + GF_FS_SET_HELP("This filter dispatch raw blocks from a remote HTTP resource into a filter chain.\n" + "Block size can be adjusted using [-block_size](), and disk caching policies can be adjusted.\n" + "Content format can be forced through [-mime]() and file extension can be changed through [-ext]().\n" + "Note: Unless disabled at session level (see [-no-probe](CORE) ), file extensions are usually ignored and format probing is done on the first data block.") + .private_size = sizeof(GF_HTTPInCtx), + .flags = GF_FS_REG_BLOCKING, + .args = HTTPInArgs, + SETCAPS(HTTPInCaps), + .initialize = httpin_initialize, + .finalize = httpin_finalize, + .process = httpin_process, + .process_event = httpin_process_event, + .probe_url = httpin_probe_url +}; + + +const GF_FilterRegister *httpin_register(GF_FilterSession *session) +{ + return &HTTPInRegister; +} + diff --git a/src/filters/in_pipe.c b/src/filters/in_pipe.c new file mode 100644 index 0000000..2c71408 --- /dev/null +++ b/src/filters/in_pipe.c @@ -0,0 +1,534 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018 + * All rights reserved + * + * This file is part of GPAC / pipe input filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include +#include + +#ifdef WIN32 + +#include + +#else + +#include +#include + +#ifdef GPAC_CONFIG_LINUX +#include +#include +#endif + +#ifndef __BEOS__ +#include +#endif + +#endif + + +typedef struct +{ + //options + char *src; + char *ext; + char *mime; + u32 block_size; + Bool blk, ka, mkp; + + //only one output pid declared + GF_FilterPid *pid; + +#ifdef WIN32 + HANDLE pipe; + HANDLE event; + OVERLAPPED overlap; +#else + int fd; +#endif + u64 bytes_read; + + Bool is_end, pck_out, is_first, owns_pipe; + Bool do_reconfigure; + char *buffer; + Bool is_stdin; +} GF_PipeInCtx; + +static Bool pipein_process_event(GF_Filter *filter, const GF_FilterEvent *evt); + +#ifdef WIN32 +#include +#include +#endif //WIN32 + +static GF_Err pipein_initialize(GF_Filter *filter) +{ + GF_Err e = GF_OK; + GF_PipeInCtx *ctx = (GF_PipeInCtx *) gf_filter_get_udta(filter); + char *frag_par = NULL; + char *cgi_par = NULL; + char *src; + + if (!ctx->src) return GF_BAD_PARAM; + +#ifdef WIN32 + ctx->pipe = INVALID_HANDLE_VALUE; +#else + ctx->fd = -1; +#endif + + if (!strcmp(ctx->src, "-") || !strcmp(ctx->src, "stdin")) { + ctx->is_stdin = GF_TRUE; + ctx->mkp = GF_FALSE; +#ifdef WIN32 + _setmode(_fileno(stdin), _O_BINARY); +#endif + } + else if (strnicmp(ctx->src, "pipe:/", 6) && strstr(ctx->src, "://")) { + gf_filter_setup_failure(filter, GF_NOT_SUPPORTED); + return GF_NOT_SUPPORTED; + } + + if (ctx->mkp) ctx->blk = GF_TRUE; + + //strip any fragment identifer + frag_par = strchr(ctx->src, '#'); + if (frag_par) frag_par[0] = 0; + cgi_par = strchr(ctx->src, '?'); + if (cgi_par) cgi_par[0] = 0; + + src = (char *) ctx->src; + if (!strnicmp(ctx->src, "pipe://", 7)) src += 7; + else if (!strnicmp(ctx->src, "pipe:", 5)) src += 5; + + if (ctx->is_stdin) { + e = GF_OK; + goto setup_done; + } + +#ifdef WIN32 + char szNamedPipe[GF_MAX_PATH]; + if (!strncmp(src, "\\\\", 2)) { + strcpy(szNamedPipe, src); + } + else { + strcpy(szNamedPipe, "\\\\.\\pipe\\gpac\\"); + strcat(szNamedPipe, src); + } + if (strchr(szNamedPipe, '/')) { + u32 i, len = (u32)strlen(szNamedPipe); + for (i = 0; i < len; i++) { + if (szNamedPipe[i] == '/') + szNamedPipe[i] = '\\'; + } + } + + if (WaitNamedPipeA(szNamedPipe, 1) == FALSE) { + if (!ctx->mkp) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeIn] Failed to open %s: %d\n", szNamedPipe, GetLastError())); + e = GF_URL_ERROR; + goto err_exit; + } + DWORD pflags = PIPE_ACCESS_INBOUND; + DWORD flags = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE; + if (ctx->blk) flags |= PIPE_WAIT; + else { + flags |= PIPE_NOWAIT; + pflags |= FILE_FLAG_OVERLAPPED; + if (!ctx->event) ctx->event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!ctx->event) { + e = GF_IO_ERR; + goto err_exit; + } + ctx->overlap.hEvent = ctx->event; + } + ctx->pipe = CreateNamedPipe(szNamedPipe, pflags, flags, 10, ctx->block_size, ctx->block_size, 0, NULL); + + if (ctx->pipe == INVALID_HANDLE_VALUE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeIn] Failed to create named pipe %s: %d\n", szNamedPipe, GetLastError())); + e = GF_URL_ERROR; + goto err_exit; + } + if (ctx->blk) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[PipeIn] Waiting for client connection for %s, blocking\n", szNamedPipe)); + } + if (!ConnectNamedPipe(ctx->pipe, ctx->blk ? NULL : &ctx->overlap) && (GetLastError() != ERROR_PIPE_CONNECTED) && (GetLastError() != ERROR_PIPE_LISTENING)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Failed to connect named pipe %s: %d\n", szNamedPipe, GetLastError())); + e = GF_IO_ERR; + CloseHandle(ctx->pipe); + ctx->pipe = INVALID_HANDLE_VALUE; + } + else { + ctx->owns_pipe = GF_TRUE; + } + } + else { + ctx->pipe = CreateFile(szNamedPipe, GENERIC_READ, ctx->blk ? PIPE_WAIT : PIPE_NOWAIT, NULL, OPEN_EXISTING, 0, NULL); + if (ctx->pipe == INVALID_HANDLE_VALUE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeIn] Failed to open %s: %d\n", szNamedPipe, GetLastError())); + e = GF_URL_ERROR; + } + } + +err_exit: + +#else + if (!gf_file_exists(src) && ctx->mkp) { + +#ifdef GPAC_CONFIG_DARWIN + mknod(src,S_IFIFO | 0666, 0); +#else + mkfifo(src, 0666); +#endif + ctx->owns_pipe = GF_TRUE; + } + + ctx->fd = open(src, ctx->blk ? O_RDONLY : O_RDONLY|O_NONBLOCK ); + + if (ctx->fd < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeIn] Failed to open %s: %s\n", src, gf_errno_str(errno))); + e = GF_URL_ERROR; + } +#endif + +setup_done: + + if (e) { + if (frag_par) frag_par[0] = '#'; + if (cgi_par) cgi_par[0] = '?'; + + gf_filter_setup_failure(filter, GF_URL_ERROR); + ctx->owns_pipe = GF_FALSE; + return GF_URL_ERROR; + } + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[PipeIn] opening %s\n", src)); + + ctx->is_end = GF_FALSE; + + if (frag_par) frag_par[0] = '#'; + if (cgi_par) cgi_par[0] = '?'; + + ctx->is_first = GF_TRUE; + if (!ctx->buffer) + ctx->buffer = gf_malloc(ctx->block_size +1); + + gf_filter_post_process_task(filter); + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + pipein_process_event(NULL, NULL); + } +#endif + return GF_OK; +} + + +static void pipein_finalize(GF_Filter *filter) +{ + GF_PipeInCtx *ctx = (GF_PipeInCtx *) gf_filter_get_udta(filter); + + if (!ctx->is_stdin) { +#ifdef WIN32 + if (ctx->pipe != INVALID_HANDLE_VALUE) CloseHandle(ctx->pipe); +#else + if (ctx->fd>=0) close(ctx->fd); +#endif + if (ctx->owns_pipe) + gf_file_delete(ctx->src); + } + if (ctx->buffer) gf_free(ctx->buffer); + +} + +static GF_FilterProbeScore pipein_probe_url(const char *url, const char *mime_type) +{ + if (!strnicmp(url, "pipe://", 7)) return GF_FPROBE_SUPPORTED; + else if (!strnicmp(url, "pipe:", 5)) return GF_FPROBE_SUPPORTED; + else if (!strcmp(url, "-") || !strcmp(url, "stdin")) return GF_FPROBE_SUPPORTED; + + return GF_FPROBE_NOT_SUPPORTED; +} + +static Bool pipein_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_PipeInCtx *ctx; + if (!filter || !evt) return GF_TRUE; + + ctx = (GF_PipeInCtx *) gf_filter_get_udta(filter); + if (evt->base.on_pid && (evt->base.on_pid != ctx->pid)) + return GF_TRUE; + + switch (evt->base.type) { + case GF_FEVT_PLAY: + return GF_TRUE; + case GF_FEVT_STOP: + //stop sending data + ctx->is_end = GF_TRUE; + gf_filter_pid_set_eos(ctx->pid); + return GF_TRUE; + case GF_FEVT_SOURCE_SEEK: + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[PipeIn] Seek request not possible on pipes, ignoring\n")); + return GF_TRUE; + case GF_FEVT_SOURCE_SWITCH: + assert(ctx->is_end); + if (evt->seek.source_switch) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[PipeIn] source switch request not possible on pipes, ignoring\n")); + } + pipein_initialize(filter); + gf_filter_post_process_task(filter); + break; + default: + break; + } + return GF_TRUE; +} + + +static void pipein_pck_destructor(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck) +{ + GF_PipeInCtx *ctx = (GF_PipeInCtx *) gf_filter_get_udta(filter); + ctx->pck_out = GF_FALSE; + //ready to process again + gf_filter_post_process_task(filter); +} + +static GF_Err pipein_process(GF_Filter *filter) +{ + GF_Err e; + u32 to_read; + s32 nb_read; + GF_FilterPacket *pck; + GF_PipeInCtx *ctx = (GF_PipeInCtx *) gf_filter_get_udta(filter); + + if (ctx->is_end) + return GF_EOS; + + //until packet is released we return EOS (no processing), and ask for processing again upon release + if (ctx->pck_out) + return GF_EOS; + + if (ctx->pid && gf_filter_pid_would_block(ctx->pid)) { + assert(0); + return GF_OK; + } + + to_read = ctx->block_size; + + if (ctx->is_stdin) { + nb_read = 0; + if (feof(stdin)) { + if (!ctx->ka) { + gf_filter_pid_set_eos(ctx->pid); + return GF_EOS; + } + } else { + nb_read = (s32) fread(ctx->buffer, 1, to_read, stdin); + if (nb_read<0) { + if (!ctx->ka) { + gf_filter_pid_set_eos(ctx->pid); + return GF_EOS; + } + } + } + } else { + + errno = 0; +#ifdef WIN32 + nb_read = -1; + if (!ctx->blk && ctx->mkp) { + DWORD res = WaitForMultipleObjects(1, &ctx->event, FALSE, 1); + ResetEvent(ctx->event); + if (res == WAIT_FAILED) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeIn] WaitForMultipleObjects failed!\n")); + return GF_IO_ERR; + } + if (GetOverlappedResult(ctx->pipe, &ctx->overlap, &res, FALSE) == 0) { + if (GetLastError() == ERROR_IO_INCOMPLETE) { + } + else { + CloseHandle(ctx->pipe); + ctx->pipe = INVALID_HANDLE_VALUE; + if (!ctx->ka) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[PipeIn] end of stream detected\n")); + gf_filter_pid_set_eos(ctx->pid); + return GF_EOS; + } + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[PipeIn] Pipe closed by remote side, reopening!\n")); + return pipein_initialize(filter); + } + } + } + if (! ReadFile(ctx->pipe, ctx->buffer, to_read, &nb_read, ctx->blk ? NULL : &ctx->overlap) ) { + s32 error = GetLastError(); + if (error == ERROR_PIPE_LISTENING) return GF_OK; + else if ((error == ERROR_IO_PENDING) || (error== ERROR_MORE_DATA)) { + //non blocking pipe with writers active + } + else if (nb_read<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeIn] Failed to read, error %d\n", error)); + return GF_IO_ERR; + } + else if (!ctx->ka) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[PipeIn] end of stream detected\n")); + gf_filter_pid_set_eos(ctx->pid); + CloseHandle(ctx->pipe); + ctx->pipe = INVALID_HANDLE_VALUE; + ctx->is_end = GF_TRUE; + return GF_EOS; + } + else if (error = ERROR_BROKEN_PIPE) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[PipeIn] Pipe closed by remote side, reopening!\n")); + CloseHandle(ctx->pipe); + ctx->pipe = INVALID_HANDLE_VALUE; + return pipein_initialize(filter); + } + return GF_OK; + } +#else + nb_read = (s32) read(ctx->fd, ctx->buffer, to_read); + if (nb_read <= 0) { + s32 res = errno; + if (res == EAGAIN) { + //non blocking pipe with writers active + } else if (nb_read<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeIn] Failed to read, error %s\n", gf_errno_str(res) )); + return GF_IO_ERR; + } else if (!ctx->ka) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[PipeIn] end of stream detected\n")); + if (ctx->pid) gf_filter_pid_set_eos(ctx->pid); + close(ctx->fd); + ctx->fd=-1; + ctx->is_end = GF_TRUE; + return GF_EOS; + } + return GF_OK; + } +#endif + } + + if (!nb_read) + return GF_OK; + + ctx->buffer[nb_read] = 0; + if (!ctx->pid || ctx->do_reconfigure) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[PipeIn] configuring stream %d probe bytes\n", nb_read)); + ctx->do_reconfigure = GF_FALSE; + e = gf_filter_pid_raw_new(filter, ctx->src, NULL, ctx->mime, ctx->ext, ctx->buffer, nb_read, GF_TRUE, &ctx->pid); + if (e) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[PipeIn] failed to configure stream: %s\n", gf_error_to_string(e) )); + return e; + } + gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_FILE_CACHED, &PROP_BOOL(GF_FALSE) ); + gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_NONE) ); + } + pck = gf_filter_pck_new_shared(ctx->pid, ctx->buffer, nb_read, pipein_pck_destructor); + if (!pck) + return GF_OK; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[PipeIn] sending %d bytes\n", nb_read)); + gf_filter_pck_set_framing(pck, ctx->is_first, ctx->is_end); + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + + ctx->is_first = GF_FALSE; + ctx->pck_out = GF_TRUE; + gf_filter_pck_send(pck); + ctx->bytes_read += nb_read; + + if (ctx->is_end) { + gf_filter_pid_set_eos(ctx->pid); + return GF_EOS; + } + return ctx->pck_out ? GF_EOS : GF_OK; +} + + + +#define OFFS(_n) #_n, offsetof(GF_PipeInCtx, _n) + +static const GF_FilterArgs PipeInArgs[] = +{ + { OFFS(src), "location of source content", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(block_size), "buffer size used to read pipe", GF_PROP_UINT, "5000", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ext), "indicate file extension of pipe data", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(mime), "indicate mime type of pipe data", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(blk), "open pipe in block mode - see filter help", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ka), "keep-alive pipe when end of input is detected - see filter help", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(mkp), "create pipe if not found - see filter help", GF_PROP_BOOL, "false", NULL, 0}, + {0} +}; + +static const GF_FilterCapability PipeInCaps[] = +{ + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), +}; + +GF_FilterRegister PipeInRegister = { + .name = "pin", + GF_FS_SET_DESCRIPTION("pipe input") + GF_FS_SET_HELP( "This filter handles generic input pipes (mono-directional) in blocking or non blocking mode.\n" + "Warning: Input pipes cannot seek.\n" + "Data format of the pipe may be specified using extension (either in file name or through [-ext]()) or MIME type through [-mime]().\n" + "Note: Unless disabled at session level (see [-no-probe](CORE) ), file extensions are usually ignored and format probing is done on the first data block.\n" + "\n" + "# stdin pipe\n" + "The filter can handle reading from stdin, by using `-`or `stdin` as input file name.\n" + "EX gpac -i - vout\n" + "EX gpac -i stdin vout\n" + "\n" + "# Named pipes\n" + "The filter can handle reading from named pipes. The associated protocol scheme is `pipe://` when loaded as a generic input (eg, `-i pipe://URL` where URL is a relative or absolute pipe name).\n" + "On Windows hosts, the default pipe prefix is `\\\\.\\pipe\\gpac\\` if no prefix is set.\n" + "EX dst=mypipe resolves in \\\\.\\pipe\\gpac\\mypipe\n" + "EX dst=\\\\.\\pipe\\myapp\\mypipe resolves in \\\\.\\pipe\\myapp\\mypipe\n" + "Any destination name starting with `\\\\` is used as is, with `\\` translated in `/`.\n" + "\n" + "Input pipes are created by default in non-blocking mode.\n" + "\n" + "The filter can create the pipe if not found using [-mkp](). On windows hosts, this will create a pipe server.\n" + "On non windows hosts, the created pipe will delete the pipe file upon filter destruction.\n" + "\n" + "Input pipes can be setup to run forever using [-ka](). In this case, any potential pipe close on the writing side and end of stream will only be triggered upon session close.\n" + "This can be useful to pipe raw streams from different process into gpac:\n" + "Receiver side: `gpac -i pipe://mypipe:ext=.264:mkp:ka`\n" + "Sender side: `cat raw1.264 > mypipe && gpac -i raw2.264 -o pipe://mypipe:ext=.264`" + "\n" + "The pipe input can be created in blocking mode or non-blocking mode. If the filter creates the pipe, blocking mode is always enabled.\n" + "") + .private_size = sizeof(GF_PipeInCtx), + .args = PipeInArgs, + .flags = GF_FS_REG_BLOCKING, + SETCAPS(PipeInCaps), + .initialize = pipein_initialize, + .finalize = pipein_finalize, + .process = pipein_process, + .process_event = pipein_process_event, + .probe_url = pipein_probe_url +}; + + +const GF_FilterRegister *pipein_register(GF_FilterSession *session) +{ + return &PipeInRegister; +} diff --git a/src/filters/in_rtp.c b/src/filters/in_rtp.c new file mode 100644 index 0000000..41cd673 --- /dev/null +++ b/src/filters/in_rtp.c @@ -0,0 +1,809 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / RTP/RTSP input filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "in_rtp.h" + +#ifndef GPAC_DISABLE_STREAMING + + +static void rtpin_reset(GF_RTPIn *ctx, Bool is_finalized) +{ + while (gf_list_count(ctx->streams)) { + GF_RTPInStream *st = (GF_RTPInStream *)gf_list_get(ctx->streams, 0); + gf_list_rem(ctx->streams, 0); + if (!is_finalized && st->opid) gf_filter_pid_remove(st->opid); + st->opid = NULL; + rtpin_stream_del(st); + } + + rtpin_rtsp_del(ctx->session); + ctx->session = NULL; + + if (ctx->iod_desc) gf_odf_desc_del(ctx->iod_desc); + ctx->iod_desc = NULL; +} + +static GF_FilterProbeScore rtpin_probe_url(const char *url, const char *mime) +{ + /*embedded data - TO TEST*/ + if (strstr(url, "data:application/mpeg4-od-au;base64") + || strstr(url, "data:application/mpeg4-bifs-au;base64") + || strstr(url, "data:application/mpeg4-es-au;base64")) + { + return GF_FPROBE_SUPPORTED; + } + + /*we need rtsp/tcp , rtsp/udp or direct RTP sender (no control)*/ + if (!strnicmp(url, "rtsp://", 7) + || !strnicmp(url, "rtspu://", 8) + || !strnicmp(url, "rtp://", 6) + || !strnicmp(url, "satip://", 6)) + { + return GF_FPROBE_SUPPORTED; + } + + return GF_FPROBE_NOT_SUPPORTED; +} + +//simplified version of RTSP_UnpackURL for SAT>IP +static void rtpin_satip_get_server_ip(const char *sURL, char *Server) +{ + char schema[10], *test, text[1024], *retest; + u32 i, len; + Bool is_ipv6; + + strcpy(Server, ""); + + //extract the schema + i = 0; + while (i <= strlen(sURL)) { + if (sURL[i] == ':') + goto found; + schema[i] = sURL[i]; + i += 1; + } + return; + +found: + schema[i] = 0; + if (stricmp(schema, "satip")) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTP] Wrong SATIP schema %s - not setting up\n", schema)); + return; + } + test = strstr(sURL, "://"); + test += 3; + + //check for port + retest = strrchr(test, ':'); + /*IPV6 address*/ + if (retest && strchr(retest, ']')) retest = NULL; + + if (retest && strstr(retest, "/")) { + retest += 1; + i = 0; + while (isrc) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[RTPIn] Configure pid called on filter instanciated with SRC %s\n", ctx->src)); + return GF_BAD_PARAM; + } + + if (is_remove) { + ctx->ipid = NULL; + //reset session, remove pids + rtpin_reset(ctx, GF_FALSE); + return GF_OK; + } + + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + //we must have a file path + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_FILEPATH); + if (prop && prop->value.string) src = prop->value.string; + if (!src) + return GF_NOT_SUPPORTED; + + crc = gf_crc_32(src, (u32) strlen(src) ); + + if (!ctx->ipid) { + ctx->ipid = pid; + } else { + if (pid != ctx->ipid) { + return GF_REQUIRES_NEW_INSTANCE; + } + if (ctx->sdp_url_crc == crc) return GF_OK; + + //reset session, remove pids for now + rtpin_reset(ctx, GF_FALSE); + } + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + ctx->sdp_url_crc = crc; + ctx->sdp_loaded = GF_FALSE; + return GF_OK; +} + + +static void gf_rtp_switch_quality(GF_RTPIn *rtp, Bool switch_up) +{ + u32 i,count; + GF_RTPInStream *stream, *cur_stream; + + count = gf_list_count(rtp->streams); + /*find the current stream*/ + stream = cur_stream = NULL; + for (i = 0; i < count; i++) { + cur_stream = (GF_RTPInStream *) gf_list_get(rtp->streams, i); + if (cur_stream->mid != rtp->cur_mid) { + cur_stream = NULL; + continue; + } + break; + } + if (!cur_stream) return; + + if (switch_up) { + /*this is the highest stream*/ + if (!cur_stream->next_stream) { + cur_stream->status = RTP_Running; + return; + } else { + for (i = 0; i < count; i++) { + stream = (GF_RTPInStream *) gf_list_get(rtp->streams, i); + if (stream->mid == cur_stream->next_stream) { + /*resume streaming next channel*/ + rtpin_stream_init(stream, GF_FALSE); + stream->status = RTP_Running; + rtp->cur_mid = stream->mid; + break; + } + + } + } + } else { + /*this is the lowest stream i.e base layer*/ + if (!cur_stream->prev_stream) { + cur_stream->status = RTP_Running; + return; + } else { + for (i = 0; i < count; i++) { + stream = (GF_RTPInStream *) gf_list_get(rtp->streams, i); + if (stream->mid == cur_stream->prev_stream) { + /*stop streaming current channel*/ + gf_rtp_stop(cur_stream->rtp_ch); + cur_stream->status = RTP_Connected; + rtp->cur_mid = stream->mid; + break; + } + } + } + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("Switch from ES%d to ES %d\n", cur_stream->mid, stream->mid)); + return; +} + + +#ifdef FILTER_FIXME +static void rtpin_send_data_base64(GF_RTPInStream *stream) +{ + u32 size; + char *pck_data; + GF_FilterPacket *pck; + char *data; + + if (stream->current_start<0) return; + + data = strstr(stream->control, ";base64"); + if (!data) return; + + /*decode data*/ + data = strstr(data, ","); + data += 1; + size = gf_base64_decode(data, (u32) strlen(data), stream->buffer, stream->rtpin->block_size); + + pck = gf_filter_pck_new_alloc(stream->opid, size, &pck_data); + memcpy(pck_data, stream->buffer, size); + gf_filter_pck_set_cts(pck, (u64) (stream->current_start * stream->ts_res)); + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + gf_filter_pck_send(pck); + + stream->flags &= ~GF_RTP_NEW_AU; + stream->current_start = -1; +} +#endif + +static void rtpin_check_setup(GF_RTPInStream *stream) +{ + RTPIn_StreamDescribe ch_desc; + switch (stream->status) { + case RTP_Connected: + case RTP_Running: + rtpin_stream_ack_connect(stream, GF_OK); + return; + default: + break; + } + memset(&ch_desc, 0, sizeof(RTPIn_StreamDescribe)); + ch_desc.opid = stream->opid; + rtpin_stream_setup(stream, &ch_desc); +} + +static Bool rtpin_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_RTPInStream *stream; + GF_RTPIn *ctx = (GF_RTPIn *) gf_filter_get_udta(filter); + Bool reset_stream = GF_FALSE; + Bool skip_rtsp_teardown = GF_FALSE; + + if (evt->base.type == GF_FEVT_QUALITY_SWITCH) { + gf_rtp_switch_quality(ctx, evt->quality_switch.up); + return GF_TRUE; + } + + /*ignore commands other than channels one*/ + if (!evt->base.on_pid) { + return GF_TRUE; + } + + stream = rtpin_find_stream(ctx, evt->base.on_pid, 0, NULL, GF_FALSE); + if (!stream) return GF_TRUE; + + switch (evt->base.type) { + case GF_FEVT_PLAY: + + ctx->is_eos = GF_FALSE; + + if ((stream->status==RTP_Running) && ((ctx->last_start_range >= 0) && (ctx->last_start_range==evt->play.start_range))) + return GF_TRUE; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Processing play on channel @%08x - %s\n", stream, stream->rtsp ? "RTSP control" : "No control (RTP)" )); + /*is this RTSP or direct RTP?*/ + stream->flags &= ~RTP_EOS; + stream->flags &= ~RTP_EOS_FLUSHED; + + if (!(stream->flags & RTP_INTERLEAVED)) { + if (stream->rtp_ch->rtp) + gf_sk_group_register(ctx->sockgroup, stream->rtp_ch->rtp); + if (stream->rtp_ch->rtcp) + gf_sk_group_register(ctx->sockgroup, stream->rtp_ch->rtcp); + } + + if (stream->rtsp) { + //send a setup if needed + rtpin_check_setup(stream); + + //if not aggregated control or no more queued events send a play + if (! (stream->rtsp->flags & RTSP_AGG_CONTROL) ) { + rtpin_rtsp_usercom_send(stream->rtsp, stream, evt); + ctx->last_start_range = evt->play.start_range; + } else { + //tricky point here: the play events may get at different times depending on the length + //of each filter chain connected to our output pids. + //we store the event and wait for no more pending events + if (!ctx->postponed_play_stream) { + ctx->postponed_play = evt->play; + ctx->postponed_play_stream = stream; + ctx->last_start_range = evt->play.start_range; + } + return GF_TRUE; + } + } else { + ctx->last_start_range = evt->play.start_range; + stream->status = RTP_Running; + if (!stream->next_stream) + ctx->cur_mid = stream->mid; + + if (stream->rtp_ch) { + //wait for RTCP to perform stream sync + stream->rtcp_init = GF_FALSE; + rtpin_stream_init(stream, (stream->flags & RTP_CONNECTED) ? GF_TRUE : GF_FALSE); + gf_rtp_set_info_rtp(stream->rtp_ch, 0, 0, 0); + } else { + /*direct channel, store current start*/ + stream->current_start = evt->play.start_range; + stream->flags |= GF_RTP_NEW_AU; + gf_rtp_depacketizer_reset(stream->depacketizer, GF_FALSE); + } + } + break; + case GF_FEVT_STOP: + + while (1) { + u32 size = gf_rtp_read_flush(stream->rtp_ch, stream->buffer, stream->rtpin->block_size); + if (!size) break; + rtpin_stream_on_rtp_pck(stream, stream->buffer, size); + } + + /*is this RTSP or direct RTP?*/ + if (stream->rtsp) { + if (!ctx->is_eos) + rtpin_rtsp_usercom_send(stream->rtsp, stream, evt); + else + skip_rtsp_teardown=GF_TRUE; + } else { + stream->status = RTP_Connected; + stream->rtpin->last_ntp = 0; + stream->flags |= RTP_EOS; + } + ctx->last_start_range = -1.0; + stream->rtcp_init = GF_FALSE; + reset_stream = stream->pck_queue ? GF_TRUE : GF_FALSE; + + if (!(stream->flags & RTP_INTERLEAVED)) { + if (stream->rtp_ch->rtp) + gf_sk_group_unregister(ctx->sockgroup, stream->rtp_ch->rtp); + if (stream->rtp_ch->rtcp) + gf_sk_group_unregister(ctx->sockgroup, stream->rtp_ch->rtcp); + } + if (reset_stream) { + while (gf_list_count(stream->pck_queue)) { + GF_FilterPacket *pck = gf_list_pop_front(stream->pck_queue); + gf_filter_pck_send(pck); + } + } + break; + case GF_FEVT_SET_SPEED: + case GF_FEVT_PAUSE: + case GF_FEVT_RESUME: + assert(stream->rtsp); + rtpin_rtsp_usercom_send(stream->rtsp, stream, evt); + break; + default: + break; + } + + //flush rtsp commands + if (ctx->session && !skip_rtsp_teardown) { + rtpin_rtsp_process_commands(ctx->session); + } + + if (reset_stream) rtpin_stream_reset_queue(stream); + //cancel event + return GF_TRUE; +} + +static void rtpin_rtsp_flush(GF_RTPInRTSP *session) +{ + /*process teardown on all sessions*/ + while (!session->connect_error) { + if (!gf_list_count(session->rtsp_commands)) + break; + rtpin_rtsp_process_commands(session); + } +} + +static GF_Err rtpin_process(GF_Filter *filter) +{ + u32 i; + GF_RTPInStream *stream; + GF_RTPIn *ctx = gf_filter_get_udta(filter); + + if (ctx->is_eos) return GF_EOS; + + if (ctx->ipid) { + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->ipid); + + if (!ctx->sdp_loaded && pck) { + Bool start, end; + u32 sdp_len; + const char *sdp_data; + gf_filter_pck_get_framing(pck, &start, &end); + assert(end); + + sdp_data = gf_filter_pck_get_data(pck, &sdp_len); + rtpin_load_sdp(ctx, (char *)sdp_data, sdp_len, NULL); + gf_filter_pid_drop_packet(ctx->ipid); + ctx->sdp_loaded = GF_TRUE; + } + //we act as a source filter, request process task + gf_filter_post_process_task(filter); + } + + if (ctx->postponed_play_stream) { + GF_FilterEvent evt; + if (gf_filter_get_num_events_queued(filter)) + return GF_OK; + stream = ctx->postponed_play_stream; + ctx->postponed_play_stream = NULL; + evt.play = ctx->postponed_play; + rtpin_rtsp_usercom_send(stream->rtsp, stream, &evt); + } + + + if (ctx->retry_tcp && ctx->session) { + GF_FilterEvent evt; + Bool send_agg_play = GF_TRUE; + GF_List *streams = gf_list_new(); + ctx->retry_tcp = GF_FALSE; + ctx->interleave = 1; + i=0; + while ((stream = (GF_RTPInStream *)gf_list_enum(ctx->streams, &i))) { + if (stream->status >= RTP_Setup) { + gf_list_add(streams, stream); + } + } + rtpin_rtsp_flush(ctx->session); + /*send teardown*/ + rtpin_rtsp_teardown(ctx->session, NULL); + rtpin_rtsp_flush(ctx->session); + //for safety reset the session, some servers don't handle teardown that well + gf_rtsp_session_reset(ctx->session->session, GF_TRUE); + + ctx->session->flags |= RTSP_FORCE_INTER; + evt.play = ctx->postponed_play; + if (!evt.base.type) evt.base.type = GF_FEVT_PLAY; + gf_rtsp_set_buffer_size(ctx->session->session, ctx->block_size); + + stream = NULL; + for (i=0; istatus = RTP_Disconnected; + //reset all dynamic flags + stream->flags &= ~(RTP_EOS | RTP_EOS_FLUSHED | RTP_SKIP_NEXT_COM | RTP_CONNECTED); + //mark as interleaved + stream->flags |= RTP_INTERLEAVED; + //reset SSRC since some servers don't include it in interleave response + gf_rtp_reset_ssrc(stream->rtp_ch); + + //send setup + rtpin_check_setup(stream); + rtpin_rtsp_flush(ctx->session); + + //if not aggregated control or no more queued events send a play + if (! (stream->rtsp->flags & RTSP_AGG_CONTROL) ) { + evt.base.on_pid = stream->opid; + rtpin_rtsp_usercom_send(stream->rtsp, stream, &evt); + send_agg_play = GF_FALSE; + } + rtpin_rtsp_flush(ctx->session); + } + if (stream && send_agg_play) { + evt.base.on_pid = stream->opid; + rtpin_rtsp_usercom_send(ctx->session, stream, &evt); + } + + gf_list_del(streams); + } + + + /*fetch data on udp*/ + u32 tot_read=0; + while (1) { + u32 read=0; + //select both read and write + GF_Err e = gf_sk_group_select(ctx->sockgroup, 10, GF_SK_SELECT_BOTH); + if (e) { + if ((e==GF_IP_NETWORK_EMPTY) && !ctx->eos_probe_start) + ctx->eos_probe_start = gf_sys_clock(); + break; + } + + ctx->eos_probe_start = 0; + + i=0; + while ((stream = (GF_RTPInStream *)gf_list_enum(ctx->streams, &i))) { + if (stream->status==RTP_Running) { + /*for interleaved channels don't read too fast, query the buffer occupancy*/ + read += rtpin_stream_read(stream); + } + + if (stream->flags & RTP_EOS) { + ctx->eos_probe_start = gf_sys_clock(); + } + } + + if (!read) { + break; + } + tot_read+=read; + } + + //we wait max 300ms to detect eos + if (ctx->eos_probe_start && (gf_sys_clock() - ctx->eos_probe_start > 300) ) { + u32 nb_eos=0; + i=0; + while ((stream = (GF_RTPInStream *)gf_list_enum(ctx->streams, &i))) { + if (! (stream->flags & RTP_EOS)) break; + if (stream->flags & RTP_EOS_FLUSHED) { + nb_eos++; + continue; + } + while (1) { + u32 size = gf_rtp_flush_rtp(stream->rtp_ch, stream->buffer, stream->rtpin->block_size); + if (!size) break; + rtpin_stream_on_rtp_pck(stream, stream->buffer, size); + } + + stream->stat_stop_time = gf_sys_clock(); + if (stream->pck_queue) { + while (gf_list_count(stream->pck_queue)) { + GF_FilterPacket *pck = gf_list_pop_front(stream->pck_queue); + gf_filter_pck_send(pck); + } + } + gf_filter_pid_set_eos(stream->opid); + stream->flags |= RTP_EOS_FLUSHED; + nb_eos++; + } + if (nb_eos==gf_list_count(ctx->streams)) { + if (!ctx->is_eos) { + ctx->is_eos = GF_TRUE; + if (ctx->session) { + /*send teardown*/ + rtpin_rtsp_teardown(ctx->session, NULL); + rtpin_rtsp_flush(ctx->session); + } + } + return GF_EOS; + } + ctx->eos_probe_start = 0; + } + + /*and process commands / flush TCP*/ + if (ctx->session) { + rtpin_rtsp_process_commands(ctx->session); + + if (ctx->session->connect_error) { + gf_filter_setup_failure(filter, ctx->session->connect_error); + ctx->session->connect_error = GF_OK; + } + } + if (ctx->max_sleep<0) + gf_filter_ask_rt_reschedule(filter, (u32) ((-ctx->max_sleep) *1000) ); + else { + assert(ctx->min_frame_dur_ms <= (u32) ctx->max_sleep); + gf_filter_ask_rt_reschedule(filter, ctx->min_frame_dur_ms*1000); + } + return GF_OK; +} + + +static GF_Err rtpin_initialize(GF_Filter *filter) +{ + GF_RTPIn *ctx = gf_filter_get_udta(filter); + char *the_ext; + + ctx->streams = gf_list_new(); + ctx->filter = filter; + //turn on interleave on http port + if ((ctx->default_port == 80) || (ctx->default_port == 8080)) + ctx->interleave = 1; + + ctx->last_start_range = -1.0; + + ctx->sockgroup = gf_sk_group_new(); + + //sdp mode, we will have a configure_pid + if (!ctx->src) return GF_OK; + + /*rtsp and rtsp over udp*/ + + the_ext = strrchr(ctx->src, '#'); + if (the_ext) { + if (!stricmp(the_ext, "#audio")) ctx->stream_type = GF_STREAM_AUDIO; + else if (!stricmp(the_ext, "#video")) ctx->stream_type = GF_STREAM_VISUAL; + the_ext[0] = 0; + } + gf_filter_disable_inputs(filter); + + if (!strnicmp(ctx->src, "rtp://", 6)) { + GF_RTPInStream *stream; + GF_Err e = GF_OK; + u32 port = 1234; + char *ip = ctx->src + 6; + char *sep = strchr(ip, ':'); + if (sep) { + port = atoi(sep+1); + sep[0] = 0; + ip = gf_strdup(ip); + sep[0] = ':'; + } else { + ip = gf_strdup(ip); + } + stream = rtpin_stream_new_standalone(ctx, ip, port); + gf_free(ip); + if (!stream) + e = GF_OUT_OF_MEM; + + if (!e) + e = rtpin_add_stream(ctx, stream, NULL); + + if (!e) + e = rtpin_stream_init(stream, GF_FALSE); + + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTPIn]] Couldn't setup RTP stream: %s\n", gf_error_to_string(e) )); + return e; + } + stream->status = RTP_Running; + return GF_OK; + } + ctx->session = rtpin_rtsp_new(ctx, (char *) ctx->src); + if (!strnicmp(ctx->src, "satip://", 8)) { + ctx->session->satip = GF_TRUE; + ctx->session->satip_server = gf_malloc(GF_MAX_PATH); + rtpin_satip_get_server_ip(ctx->src, ctx->session->satip_server); + } + + if (!ctx->session) { + return GF_NOT_SUPPORTED; + } else { + rtpin_rtsp_describe_send(ctx->session, 0, NULL); + } + return GF_OK; +} + + +static void rtpin_finalize(GF_Filter *filter) +{ + GF_RTPIn *ctx = gf_filter_get_udta(filter); + ctx->done = GF_TRUE; + if (ctx->session) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Closing RTSP service\n")); + rtpin_rtsp_flush(ctx->session); + if (!ctx->is_eos) { + /*send teardown*/ + rtpin_rtsp_teardown(ctx->session, NULL); + rtpin_rtsp_flush(ctx->session); + } + } + + rtpin_reset(ctx, GF_TRUE); + gf_list_del(ctx->streams); + + gf_sk_group_del(ctx->sockgroup); +} + +static const char *rtpin_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + Bool is_sdp = GF_TRUE; + char *pdata = (char *)data; + char cend = pdata[size-1]; + pdata[size-1] = 0; + if (!strstr(pdata, "\n")) is_sdp = GF_FALSE; + else if (!strstr(pdata, "v=0")) is_sdp = GF_FALSE; + else if (!strstr(pdata, "o=")) is_sdp = GF_FALSE; + else if (!strstr(pdata, "c=")) is_sdp = GF_FALSE; + pdata[size-1] = cend; + if (is_sdp) { + *score = GF_FPROBE_SUPPORTED; + return "application/sdp"; + } + return NULL; +} + +static const GF_FilterCapability RTPInCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "sdp"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "application/sdp"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_OD), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_METADATA), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_ENCRYPTED), + {0}, + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "ts|m2t|mts|dmb|trp"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "video/mpeg-2|video/mp2t|video/mpeg"), +}; + +#define OFFS(_n) #_n, offsetof(GF_RTPIn, _n) +static const GF_FilterArgs RTPInArgs[] = +{ + { OFFS(src), "location of source content (SDP, RTP or RTSP URL)", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(firstport), "default first port number to use. 0 lets the filter decide", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ifce), "default interface IP to use for multicast. If NULL, the default system interface will be used", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ttl), "multicast TTL", GF_PROP_UINT, "127", "0-127", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(reorder_len), "reorder length in packets", GF_PROP_UINT, "1000", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(reorder_delay), "max delay in RTP reorderer, packets will be dispatched after that", GF_PROP_UINT, "50", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(block_size), "buffer size fur RTP/UDP or RTSP when interleaved", GF_PROP_UINT, "0x200000", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(disable_rtcp), "disable RTCP reporting", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(nat_keepalive), "delay in ms of NAT keepalive, disabled by default (except for SatIP, set to 30s by default)", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(force_mcast), "force multicast on indicated IP in RTSP setup", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(use_client_ports), "force using client ports (hack for some RTSP servers overriding client ports)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(bandwidth), "set bandwidth param for RTSP requests", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(default_port), "set default RTSP port", GF_PROP_UINT, "554", "0-65535", 0}, + { OFFS(satip_port), "set default port for SATIP", GF_PROP_UINT, "1400", "0-65535", 0}, + { OFFS(interleave), "set RTP over RTSP", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(udp_timeout), "default timeout before considering UDP is down", GF_PROP_UINT, "10000", NULL, 0}, + { OFFS(rtsp_timeout), "default timeout before considering RTSP is down", GF_PROP_UINT, "3000", NULL, 0}, + { OFFS(rtcp_timeout), "default timeout for RTCP trafic in ms. After this timeout, playback will start unsync. If 0 always wait for RTCP", GF_PROP_UINT, "5000", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(autortsp), "automatically reconfig RTSP interleaving if UDP timeout", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(first_packet_drop), "set number of first RTP packet to drop - 0 if no drop", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(frequency_drop), "drop 1 out of N packet - 0 disable droping", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(user_agent), "user agent string, by default solved from GPAC preferences", GF_PROP_STRING, "$GUA", NULL, 0}, + { OFFS(languages), "user languages, by default solved from GPAC preferences", GF_PROP_STRING, "$GLANG", NULL, 0}, + { OFFS(stats), "update statistics to the user every given MS, 0 disables reporting", GF_PROP_UINT, "500", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(max_sleep), "set max sleep in milliseconds. A negative value -N means to always sleep for N ms, a positive value N means to sleep at most N ms but will sleep less if frame duration is shorter", GF_PROP_SINT, "1000", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(rtcpsync), "use RTCP to adjust synchronization", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, + {0} +}; + + +GF_FilterRegister RTPInRegister = { + .name = "rtpin", + GF_FS_SET_DESCRIPTION("RTP/RTSP/SDP input") + GF_FS_SET_HELP("This filter handles SDP/RTSP/RTP input reading. It supports:\n" + "- SDP file reading\n" + "- RTP direct url through `rtp://` protocol scheme\n" + "- RTSP session processing through `rtsp://` and `satip://` protocol schemes\n" + " \n" + "The filter produces either media PIDs and compressed media frames, or file PIDs and multiplex data (e.g., MPEG-2 TS).") + .private_size = sizeof(GF_RTPIn), + .args = RTPInArgs, + .initialize = rtpin_initialize, + .finalize = rtpin_finalize, + SETCAPS(RTPInCaps), + .configure_pid = rtpin_configure_pid, + .process = rtpin_process, + .process_event = rtpin_process_event, + .probe_url = rtpin_probe_url, + .probe_data = rtpin_probe_data +}; + +#endif + + +const GF_FilterRegister *rtpin_register(GF_FilterSession *session) +{ +#ifndef GPAC_DISABLE_STREAMING + return &RTPInRegister; +#else + return NULL; +#endif +} + diff --git a/modules/rtp_in/rtp_in.h b/src/filters/in_rtp.h similarity index 52% rename from modules/rtp_in/rtp_in.h rename to src/filters/in_rtp.h index f2dca67..a997a1e 100644 --- a/modules/rtp_in/rtp_in.h +++ b/src/filters/in_rtp.h @@ -2,10 +2,10 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2017 * All rights reserved * - * This file is part of GPAC / RTP input module + * This file is part of GPAC / RTP/RTSP input filter * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -23,81 +23,88 @@ * */ -#ifndef RTP_IN_H -#define RTP_IN_H +#ifndef _IN_RTP_H_ +#define _IN_RTP_H_ /*module interface*/ -#include +#include +#include #ifndef GPAC_DISABLE_STREAMING -#include -#include #include +#include /*IETF lib*/ -#include +#include -#define RTP_BUFFER_SIZE 0x100000ul #define RTSP_BUFFER_SIZE 5000 -#define RTSP_TCP_BUFFER_SIZE 0x100000ul -#define RTSP_LANGUAGE "English" +typedef struct _rtsp_session GF_RTPInRTSP; +typedef struct __rtpin_stream GF_RTPInStream; /*the rtsp/rtp client*/ typedef struct { - /*the service we're responsible for*/ - GF_ClientService *service; + //opts + char *src; + u32 firstport, ttl, satip_port; + const char *ifce, *force_mcast, *user_agent, *languages; + Bool use_client_ports; + u32 bandwidth, reorder_len, reorder_delay, nat_keepalive, block_size; + Bool disable_rtcp; + u32 default_port; + u32 rtsp_timeout, udp_timeout, rtcp_timeout, stats; + /*transport mode. 0 is udp, 1 is tcp, 3 is tcp if unreliable media */ + u32 interleave; + s32 max_sleep; + Bool autortsp, rtcpsync; - /*the one and only IOD*/ - GF_Descriptor *session_desc; + //internal - /*RTSP sessions*/ - GF_List *sessions; - /*RTP/RTCP media channels*/ - GF_List *channels; + GF_Filter *filter; + + /*the service we're responsible for*/ + GF_FilterPid *ipid; + u32 sdp_url_crc; + Bool sdp_loaded; - /*sdp downloader*/ - GF_DownloadSession * dnload; - /*initial sdp download if any (temp storage)*/ - struct _sdp_fetch *sdp_temp; + /*one and only RTSP session*/ + GF_RTPInRTSP *session; - /*RTSP communication/deinterleaver thread*/ - GF_Mutex *mx; - GF_Thread *th; - u32 th_state; + /*RTP/RTCP media streams*/ + GF_List *streams; /*RTSP config*/ - /*transport mode. 0 is udp, 1 is tcp, 3 is tcp if unreliable media */ - u32 transport_mode; - /*default RTSP port*/ - u16 default_port; - /*signaling timeout in msec*/ - u32 time_out; - /*udp timeout in msec*/ - u32 udp_time_out; /*packet drop emulation*/ u32 first_packet_drop; u32 frequency_drop; /*for single-object control*/ - u32 media_type; + u32 stream_type; - /*session state data, formatyed as "data:application/sdp;SDPDATA"*/ - char *session_state_data; + GF_Descriptor *iod_desc; + + GF_RTPInStream *postponed_play_stream; + GF_FEVT_Play postponed_play; /*if set ANNOUNCE (sent by server) will be handled*/ // Bool handle_announce; Double last_ntp; - Bool session_migration; + Bool is_scalable, done, retry_tcp; - Bool is_scalable; + Double last_start_range; u32 cur_mid; -} RTPClient; + + u32 min_frame_dur_ms; + + GF_SockGroup *sockgroup; + Bool is_eos; + u32 eos_probe_start; +} GF_RTPIn; enum { @@ -110,12 +117,12 @@ enum }; /*rtsp session*/ -typedef struct _rtp_session +struct _rtsp_session { u32 flags; /*owner*/ - RTPClient *owner; + GF_RTPIn *rtpin; /*RTSP session object*/ GF_RTSPSession *session; @@ -136,20 +143,18 @@ typedef struct _rtp_session /*SAT>IP uses a non-conformant version of RTSP*/ Bool satip; char *satip_server; -} RTSPSession; +}; /*creates new RTSP session handler*/ -RTSPSession *RP_NewSession(RTPClient *rtp, char *session_control); +GF_RTPInRTSP *rtpin_rtsp_new(GF_RTPIn *rtp, char *session_control); /*disconnects and destroy RTSP session handler - if immediate_shutdown do not wait for response*/ -void RP_DelSession(RTSPSession *sess); +void rtpin_rtsp_del(GF_RTPInRTSP *sess); /*check session by control string*/ -RTSPSession *RP_CheckSession(RTPClient *rtp, char *control); - -void RP_SetupObjects(RTPClient *rtp); +GF_RTPInRTSP *rtpin_rtsp_check(GF_RTPIn *rtp, char *control); -void RP_ProcessCommands(RTSPSession *sess); +void rtpin_rtsp_process_commands(GF_RTPInRTSP *sess); -void RP_SendMessage(GF_ClientService *service, GF_Err e, const char *message); +void rtpin_send_message(GF_RTPIn *ctx, GF_Err e, const char *message); /*RTP channel state*/ enum @@ -166,9 +171,8 @@ enum RTP_Disconnected, /*service/channel is not (no longer) available/found and should be removed*/ RTP_Unavailable, - - RTP_SessionResume - + /*service/channel is not (no longer) available/found and should be removed*/ + RTP_WaitForTCPRetry, }; @@ -195,10 +199,7 @@ enum RTP_CONNECTED = (1<<5), /*EOS signaled (RTCP or range-based)*/ RTP_EOS = (1<<6), - - /*RTP stream is using mobileIP - this will disable RTP over RTSP*/ - RTP_MOBILEIP = (1<<7), - + RTP_EOS_FLUSHED = (1<<7), }; enum @@ -208,19 +209,17 @@ enum RTP_SET_TIME_RTP_SEEK, }; -#define RTCP_DEFAULT_TIMEOUT_MS 5000 - /*rtp channel*/ -typedef struct +struct __rtpin_stream { /*module*/ - RTPClient *owner; + GF_RTPIn *rtpin; /*channel flags*/ u32 flags; /*control session (may be null)*/ - RTSPSession *rtsp; + GF_RTPInRTSP *rtsp; /*session ID for independent stream control*/ char *session_id; @@ -230,15 +229,17 @@ typedef struct /*depacketizer*/ GF_RTPDepacketizer *depacketizer; - /*logical app channel*/ - LPNETCHANNEL channel; + GF_FilterPid *opid; + u32 status; + u32 last_stats_time; + u32 ES_ID, OD_ID; char *control; /*rtp receive buffer*/ - char buffer[RTP_BUFFER_SIZE]; + char *buffer; /*set at play/seek stages to sync app NPT to RTP time (RTSP) or NTP to RTP (RTCP) */ u32 check_rtp_time; @@ -256,6 +257,10 @@ typedef struct u32 rtp_bytes, rtcp_bytes, stat_start_time, stat_stop_time; u32 ts_res; + //MPEG-4 deinterleaver + Bool first_in_rtp_pck; + GF_List *pck_queue; + /*stream id*/ u32 mid; @@ -264,67 +269,50 @@ typedef struct u32 base_stream; u32 rtcp_check_start; + u32 first_rtp_ts; + s64 ts_adjust; + //source NTP of first packet received, ercomputed at first RTCP sender report + u64 init_ntp_us; - u64 ts_offset; - - /*SAT>IP M2TS demux*/ - GF_InputService *satip_m2ts_ifce; - Bool satip_m2ts_service_connected; -} RTPStream; - -GF_Err RP_ConnectServiceEx(GF_InputService *plug, GF_ClientService *serv, const char *url, Bool skip_migration); + u32 min_dur_us, min_dur_rtp; + u32 prev_cts; + u32 sr, nb_ch; +}; /*creates new RTP stream from SDP info*/ -RTPStream *RP_NewStream(RTPClient *rtp, GF_SDPMedia *media, GF_SDPInfo *sdp, RTPStream *input_stream); +GF_RTPInStream *rtpin_stream_new(GF_RTPIn *rtp, GF_SDPMedia *media, GF_SDPInfo *sdp, GF_RTPInStream *input_stream); /*creates new SAT>IP stream*/ -RTPStream *RP_NewSatipStream(RTPClient *rtp, const char *server_ip); +GF_RTPInStream *rtpin_stream_new_satip(GF_RTPIn *rtp, const char *server_ip); +GF_RTPInStream *rtpin_stream_new_standalone(GF_RTPIn *rtp, const char *flow_ip, u32 port); + /*destroys RTP stream */ -void RP_DeleteStream(RTPStream *ch); +void rtpin_stream_del(GF_RTPInStream *stream); /*resets stream state and inits RTP sockets if ResetOnly is false*/ -GF_Err RP_InitStream(RTPStream *ch, Bool ResetOnly); +GF_Err rtpin_stream_init(GF_RTPInStream *stream, Bool ResetOnly); + +void rtpin_stream_reset_queue(GF_RTPInStream *stream); /*RTSP -> RTP de-interleaving callback*/ -GF_Err RP_DataOnTCP(GF_RTSPSession *sess, void *cbck, char *buffer, u32 bufferSize, Bool IsRTCP); +GF_Err rtpin_rtsp_data_cbk(GF_RTSPSession *sess, void *cbck, u8 *buffer, u32 bufferSize, Bool IsRTCP); /*send confirmation of connection - if no error, also setup SL based on payload*/ -void RP_ConfirmChannelConnect(RTPStream *ch, GF_Err e); - -/*fetch sdp file - stream is the RTP channel this sdp describes, or NULL if session sdp*/ -void RP_FetchSDP(RTPClient *rtp, char *url, RTPStream *stream, char *original_url); +void rtpin_stream_ack_connect(GF_RTPInStream *stream, GF_Err e); /*locate RTP stream by channel or ES_ID or control*/ -RTPStream *RP_FindChannel(RTPClient *rtp, LPNETCHANNEL ch, u32 ES_ID, char *es_control, Bool remove_stream); +GF_RTPInStream *rtpin_find_stream(GF_RTPIn *rtp, GF_FilterPid *opid, u32 ES_ID, char *es_control, Bool remove_stream); /*adds channel to session identified by session_control. If no session exists, the session is created if needed*/ -GF_Err RP_AddStream(RTPClient *rtp, RTPStream *stream, char *session_control); +GF_Err rtpin_add_stream(GF_RTPIn *rtp, GF_RTPInStream *stream, char *session_control); /*removes stream from session*/ -void RP_RemoveStream(RTPClient *rtp, RTPStream *ch); +void rtpin_remove_stream(GF_RTPIn *rtp, GF_RTPInStream *stream); /*reads input socket and process*/ -void RP_ReadStream(RTPStream *ch); - -/*parse RTP payload for MPEG4*/ -void RP_ParsePayloadMPEG4(RTPStream *ch, GF_RTPHeader *hdr, char *payload, u32 size); -/*parse RTP payload for MPEG12*/ -void RP_ParsePayloadMPEG12(RTPStream *ch, GF_RTPHeader *hdr, char *payload, u32 size); -/*parse RTP payload for AMR*/ -void RP_ParsePayloadAMR(RTPStream *ch, GF_RTPHeader *hdr, char *payload, u32 size); -/*parse RTP payload for H263+*/ -void RP_ParsePayloadH263(RTPStream *ch, GF_RTPHeader *hdr, char *payload, u32 size); -/*parse RTP payload for 3GPP Text*/ -void RP_ParsePayloadText(RTPStream *ch, GF_RTPHeader *hdr, char *payload, u32 size); -/*parse RTP payload for H264/AVC*/ -void RP_ParsePayloadH264(RTPStream *ch, GF_RTPHeader *hdr, char *payload, u32 size); -/*parse RTP payload for LATM audio*/ -void RP_ParsePayloadLATM(RTPStream *ch, GF_RTPHeader *hdr, char *payload, u32 size); +u32 rtpin_stream_read(GF_RTPInStream *stream); /*load SDP and setup described media in SDP. If stream is null this is the root SDP and IOD will be extracted, otherwise this a channel SDP*/ -void RP_LoadSDP(RTPClient *rtp, char *sdp, u32 sdp_len, RTPStream *stream); - -/*returns 1 if payload type is supported*/ -u32 payt_get_type(RTPClient *rtp, GF_RTPMap *map, GF_SDPMedia *media); -/*setup payload type, returns 1 if success, 0 otherwise (in which case the stream will be deleted)*/ -Bool payt_setup(RTPStream *st, GF_RTPMap *map, GF_SDPMedia *media); +void rtpin_load_sdp(GF_RTPIn *rtp, char *sdp, u32 sdp_len, GF_RTPInStream *stream); +/*for standalone rtp streams*/ +void rtpin_declare_pid(GF_RTPInStream *stream, Bool force_iod, u32 ch_idx, u32 *ocr_es_id); /*RTSP signaling is handled by stacking commands and processing them in the main session thread. Each RTSP command has an associated private stack as follows*/ @@ -333,55 +321,39 @@ in the main session thread. Each RTSP command has an associated private stack as typedef struct { u32 ES_ID; - LPNETCHANNEL channel; + GF_FilterPid *opid; char *esd_url; -} ChannelDescribe; +} RTPIn_StreamDescribe; typedef struct { - RTPStream *ch; - GF_NetworkCommand com; -} ChannelControl; + GF_RTPInStream *stream; + GF_FilterEvent evt; +} RTPIn_StreamControl; /*RTSP signaling */ -Bool RP_PreprocessDescribe(RTSPSession *sess, GF_RTSPCommand *com); -GF_Err RP_ProcessDescribe(RTSPSession *sess, GF_RTSPCommand *com, GF_Err e); -void RP_ProcessSetup(RTSPSession *sess, GF_RTSPCommand *com, GF_Err e); -void RP_ProcessTeardown(RTSPSession *sess, GF_RTSPCommand *com, GF_Err e); -Bool RP_PreprocessUserCom(RTSPSession *sess, GF_RTSPCommand *com); -void RP_ProcessUserCommand(RTSPSession *sess, GF_RTSPCommand *com, GF_Err e); +Bool rtpin_rtsp_describe_preprocess(GF_RTPInRTSP *sess, GF_RTSPCommand *com); +GF_Err rtpin_rtsp_describe_process(GF_RTPInRTSP *sess, GF_RTSPCommand *com, GF_Err e); +void rtpin_rtsp_setup_process(GF_RTPInRTSP *sess, GF_RTSPCommand *com, GF_Err e); +void rtpin_rtsp_teardown_process(GF_RTPInRTSP *sess, GF_RTSPCommand *com, GF_Err e); +Bool rtpin_rtsp_usercom_preprocess(GF_RTPInRTSP *sess, GF_RTSPCommand *com); +void rtpin_rtsp_usercom_process(GF_RTPInRTSP *sess, GF_RTSPCommand *com, GF_Err e); /*send describe - if esd_url is given, this is a describe on es*/ -void RP_Describe(RTSPSession *sess, char *esd_url, LPNETCHANNEL channel); +void rtpin_rtsp_describe_send(GF_RTPInRTSP *sess, char *esd_url, GF_FilterPid *opid); /*send setup for stream*/ -void RP_Setup(RTPStream *ch); +void rtpin_rtsp_setup_send(GF_RTPInStream *stream); /*filter setup if no session (rtp only), otherwise setup channel - ch_desc may be NULL if channel association is already done*/ -GF_Err RP_SetupChannel(RTPStream *ch, ChannelDescribe *ch_desc); +GF_Err rtpin_stream_setup(GF_RTPInStream *stream, RTPIn_StreamDescribe *ch_desc); /*send command for stream - handles aggregation*/ -void RP_UserCommand(RTSPSession *sess, RTPStream *ch, GF_NetworkCommand *command); +void rtpin_rtsp_usercom_send(GF_RTPInRTSP *sess, GF_RTPInStream *stream, const GF_FilterEvent *fevt); /*disconnect the session - if @ch, only the channel is teardown*/ -void RP_Teardown(RTSPSession *sess, RTPStream *ch); - -/*emulate IOD*/ -GF_Descriptor *RP_EmulateIOD(RTPClient *rtp, const char *sub_url); - - -/*sdp file downloader*/ -typedef struct _sdp_fetch -{ - RTPClient *client; - /*when loading a channel from SDP*/ - RTPStream *chan; - - char *remote_url; - char *original_url; -} SDPFetch; - +void rtpin_rtsp_teardown(GF_RTPInRTSP *sess, GF_RTPInStream *stream); -void RP_SaveSessionState(RTPClient *rtp); +void rtpin_stream_on_rtp_pck(GF_RTPInStream *stream, char *pck, u32 size); #endif /*GPAC_DISABLE_STREAMING*/ -#endif +#endif //_IN_RTP_H_ diff --git a/modules/rtp_in/rtp_session.c b/src/filters/in_rtp_rtsp.c similarity index 62% rename from modules/rtp_in/rtp_session.c rename to src/filters/in_rtp_rtsp.c index c8b2f9f..38bdfdb 100644 --- a/modules/rtp_in/rtp_session.c +++ b/src/filters/in_rtp_rtsp.c @@ -2,10 +2,10 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2017 * All rights reserved * - * This file is part of GPAC / RTP input module + * This file is part of GPAC / RTP/RTSP input filter * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -23,57 +23,31 @@ * */ -#include "rtp_in.h" +#include "in_rtp.h" #ifndef GPAC_DISABLE_STREAMING - -void RP_SendFailure(RTSPSession *sess, GF_RTSPCommand *com, GF_Err e) -{ - char sMsg[1000]; - sprintf(sMsg, "Cannot send %s", com->method); - RP_SendMessage(sess->owner->service, e, sMsg); -} - -GF_Err RP_ProcessResponse(RTSPSession *sess, GF_RTSPCommand *com, GF_Err e) +static GF_Err rtpin_rtsp_process_response(GF_RTPInRTSP *sess, GF_RTSPCommand *com, GF_Err e) { if (!strcmp(com->method, GF_RTSP_DESCRIBE)) - return RP_ProcessDescribe(sess, com, e); + return rtpin_rtsp_describe_process(sess, com, e); else if (!strcmp(com->method, GF_RTSP_SETUP)) - RP_ProcessSetup(sess, com, e); + rtpin_rtsp_setup_process(sess, com, e); else if (!strcmp(com->method, GF_RTSP_TEARDOWN)) - RP_ProcessTeardown(sess, com, e); + rtpin_rtsp_teardown_process(sess, com, e); else if (!strcmp(com->method, GF_RTSP_PLAY) || !strcmp(com->method, GF_RTSP_PAUSE)) - RP_ProcessUserCommand(sess, com, e); + rtpin_rtsp_usercom_process(sess, com, e); return GF_OK; } -/*access to command list is protected bymutex, BUT ONLY ACCESS - this way we're sure that command queueing -from app will not deadlock if we're waiting for the app to release any mutex (don't forget play request may -come on stream N while we're processing stream P setup)*/ -static GF_RTSPCommand *RP_GetCommand(RTSPSession *sess) -{ - GF_RTSPCommand *com; - gf_mx_p(sess->owner->mx); - com = (GF_RTSPCommand *)gf_list_get(sess->rtsp_commands, 0); - gf_mx_v(sess->owner->mx); - return com; -} - -static void RP_RemoveCommand(RTSPSession *sess) -{ - gf_mx_p(sess->owner->mx); - gf_list_rem(sess->rtsp_commands, 0); - gf_mx_v(sess->owner->mx); -} - -void RP_ProcessCommands(RTSPSession *sess) +void rtpin_rtsp_process_commands(GF_RTPInRTSP *sess) { GF_RTSPCommand *com; GF_Err e; u32 time; + char sMsg[1000]; - com = RP_GetCommand(sess); + com = (GF_RTSPCommand *)gf_list_get(sess->rtsp_commands, 0); /*if asked or command to send, flushout TCP - TODO: check what's going on with ANNOUNCE*/ if ((com && !(sess->flags & RTSP_WAIT_REPLY) ) || (sess->flags & RTSP_TCP_FLUSH) ) { @@ -81,28 +55,27 @@ void RP_ProcessCommands(RTSPSession *sess) e = gf_rtsp_session_read(sess->session); if (e) break; } - sess->flags &= ~RTSP_TCP_FLUSH; } /*handle response or announce*/ - if ( (com && (sess->flags & RTSP_WAIT_REPLY) ) /*|| (!com && sess->owner->handle_announce)*/) { + if ( (com && (sess->flags & RTSP_WAIT_REPLY) ) /*|| (!com && sess->rtpin->handle_announce)*/) { e = gf_rtsp_get_response(sess->session, sess->rtsp_rsp); if (e!= GF_IP_NETWORK_EMPTY) { - e = RP_ProcessResponse(sess, com, e); + e = rtpin_rtsp_process_response(sess, com, e); /*this is a service connect error -> plugin may be discarded */ if (e!=GF_OK) { - RP_RemoveCommand(sess); + gf_list_rem(sess->rtsp_commands, 0); gf_rtsp_command_del(com); - gf_service_connect_ack(sess->owner->service, NULL, e); + gf_filter_setup_failure(sess->rtpin->filter, e); return; } - RP_RemoveCommand(sess); + gf_list_rem(sess->rtsp_commands, 0); gf_rtsp_command_del(com); sess->flags &= ~RTSP_WAIT_REPLY; sess->command_time = 0; } else { - u32 time_out = sess->owner->time_out; + u32 time_out = sess->rtpin->rtsp_timeout; /*evaluate timeout*/ time = gf_sys_clock() - sess->command_time; @@ -120,8 +93,8 @@ void RP_ProcessCommands(RTSPSession *sess) GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTP] Request Timeout for command %s after %d ms\n", com->method, time)); } - RP_ProcessResponse(sess, com, GF_IP_NETWORK_FAILURE); - RP_RemoveCommand(sess); + rtpin_rtsp_process_response(sess, com, GF_IP_NETWORK_FAILURE); + gf_list_rem(sess->rtsp_commands, 0); gf_rtsp_command_del(com); sess->flags &= ~RTSP_WAIT_REPLY; sess->command_time = 0; @@ -139,17 +112,17 @@ void RP_ProcessCommands(RTSPSession *sess) case GF_RTSP_STATE_WAIT_FOR_CONTROL: return; case GF_RTSP_STATE_INVALIDATED: - RP_SendFailure(sess, com, GF_IP_NETWORK_FAILURE); - RP_RemoveCommand(sess); + sprintf(sMsg, "Cannot send %s", com->method); + rtpin_send_message(sess->rtpin, GF_IP_NETWORK_FAILURE, sMsg); + gf_list_rem(sess->rtsp_commands, 0); gf_rtsp_command_del(com); sess->flags &= ~RTSP_WAIT_REPLY; sess->command_time = 0; return; } /*process*/ - com->User_Agent = (char*)gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(sess->owner->service), "Downloader", "UserAgent"); - if (!com->User_Agent) com->User_Agent = "GPAC " GPAC_VERSION " RTSP Client"; - com->Accept_Language = RTSP_LANGUAGE; + if (!com->User_Agent) com->User_Agent = (char *) sess->rtpin->user_agent; + com->Accept_Language = (char *) sess->rtpin->languages; /*if no session assigned and a session ID is valid, use it*/ if (sess->session_id && !com->Session) com->Session = sess->session_id; @@ -157,7 +130,7 @@ void RP_ProcessCommands(RTSPSession *sess) /*preprocess describe before sending (always the ESD url thing)*/ if (!strcmp(com->method, GF_RTSP_DESCRIBE)) { com->Session = NULL; - if (!RP_PreprocessDescribe(sess, com)) { + if (!rtpin_rtsp_describe_preprocess(sess, com)) { e = GF_BAD_PARAM; goto exit; } @@ -167,15 +140,16 @@ void RP_ProcessCommands(RTSPSession *sess) || !strcmp(com->method, GF_RTSP_PAUSE) || !strcmp(com->method, GF_RTSP_TEARDOWN)) { //command is skipped - if (!RP_PreprocessUserCom(sess, com)) { + if (!rtpin_rtsp_usercom_preprocess(sess, com)) { e = GF_BAD_PARAM; goto exit; } } e = gf_rtsp_send_command(sess->session, com); if (e) { - RP_SendFailure(sess, com, e); - RP_ProcessResponse(sess, com, e); + sprintf(sMsg, "Cannot send %s", com->method); + rtpin_send_message(sess->rtpin, e, sMsg); + rtpin_rtsp_process_response(sess, com, e); } else { sess->command_time = gf_sys_clock(); sess->flags |= RTSP_WAIT_REPLY; @@ -188,7 +162,7 @@ exit: com->Session = NULL; /*remove command*/ if (e) { - RP_RemoveCommand(sess); + gf_list_rem(sess->rtsp_commands, 0); gf_rtsp_command_del(com); sess->flags &= ~RTSP_WAIT_REPLY; sess->command_time = 0; @@ -197,13 +171,13 @@ exit: /*locate channel - if requested remove from session*/ -RTPStream *RP_FindChannel(RTPClient *rtp, LPNETCHANNEL ch, u32 ES_ID, char *es_control, Bool remove_stream) +GF_RTPInStream *rtpin_find_stream(GF_RTPIn *rtp, GF_FilterPid *opid, u32 ES_ID, char *es_control, Bool remove_stream) { u32 i=0; - RTPStream *st; + GF_RTPInStream *st; - while ((st = (RTPStream *)gf_list_enum(rtp->channels, &i))) { - if (ch && (st->channel==ch)) goto found; + while ((st = (GF_RTPInStream *)gf_list_enum(rtp->streams, &i))) { + if (opid && (st->opid==opid)) goto found; if (ES_ID && (st->ES_ID==ES_ID)) goto found; if (es_control && st->control) { char *ctrl_start = strstr(es_control, st->control); @@ -213,37 +187,36 @@ RTPStream *RP_FindChannel(RTPClient *rtp, LPNETCHANNEL ch, u32 ES_ID, char *es_c return NULL; found: - if (remove_stream) gf_list_rem(rtp->channels, i-1); + if (remove_stream) gf_list_rem(rtp->streams, i-1); return st; } /*locate session by control*/ -RTSPSession *RP_CheckSession(RTPClient *rtp, char *control) +GF_RTPInRTSP *rtpin_rtsp_check(GF_RTPIn *rtp, char *control) { - u32 i; - RTSPSession *sess; if (!control) return NULL; - if (!strcmp(control, "*")) control = (char *) gf_service_get_url(rtp->service); - - i=0; - while ( (sess = (RTSPSession *)gf_list_enum(rtp->sessions, &i)) ) { - if (gf_rtsp_is_my_session(sess->session, control)) return sess; - } + if (!strcmp(control, "*")) control = (char *) rtp->src; + if (gf_rtsp_is_my_session(rtp->session->session, control)) return rtp->session; return NULL; } -RTSPSession *RP_NewSession(RTPClient *rtp, char *session_control) +GF_RTPInRTSP *rtpin_rtsp_new(GF_RTPIn *rtp, char *session_control) { char *szCtrl, *szExt; - RTSPSession *tmp; + GF_RTPInRTSP *tmp; GF_RTSPSession *rtsp; if (!session_control) return NULL; + if (rtp->session) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTP] Request more than one RTSP session for more URL, old code to patch !!\n")); + return rtp->session; + } + /*little fix: some servers don't understand DESCRIBE URL/trackID=, so remove the trackID...*/ szCtrl = gf_strdup(session_control); - szExt = szCtrl ? strrchr(szCtrl, '.') : NULL; + szExt = szCtrl ? gf_file_ext_start(szCtrl) : NULL; if (szExt) { szExt = strchr(szExt, '/'); if (szExt) { @@ -256,47 +229,40 @@ RTSPSession *RP_NewSession(RTPClient *rtp, char *session_control) if (!rtsp) return NULL; - GF_SAFEALLOC(tmp, RTSPSession); + GF_SAFEALLOC(tmp, GF_RTPInRTSP); if (!tmp) return NULL; - tmp->owner = rtp; + tmp->rtpin = rtp; tmp->session = rtsp; - szCtrl = (char *)gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(rtp->service), "Network", "MobileIPEnabled"); - if (szCtrl && !strcmp(szCtrl, "yes")) { - char *ip = (char *)gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(rtp->service), "Network", "MobileIP"); - gf_rtsp_set_mobile_ip(rtsp, ip); - } - - if (rtp->transport_mode) { - gf_rtsp_set_buffer_size(rtsp, RTSP_TCP_BUFFER_SIZE); + if (rtp->interleave) { + gf_rtsp_set_buffer_size(rtsp, rtp->block_size); } else { gf_rtsp_set_buffer_size(rtsp, RTSP_BUFFER_SIZE); } tmp->rtsp_commands = gf_list_new(); tmp->rtsp_rsp = gf_rtsp_response_new(); - gf_list_add(rtp->sessions, tmp); - + rtp->session = tmp; return tmp; } -GF_Err RP_AddStream(RTPClient *rtp, RTPStream *stream, char *session_control) +GF_Err rtpin_add_stream(GF_RTPIn *rtp, GF_RTPInStream *stream, char *session_control) { Bool has_aggregated_control; - char *service_name, *ctrl; - RTSPSession *in_session = RP_CheckSession(rtp, session_control); + char *service_name; + GF_RTPInRTSP *in_session = rtpin_rtsp_check(rtp, session_control); has_aggregated_control = GF_FALSE; if (session_control) { //if (!strcmp(session_control, "*")) session_control = NULL; - if (session_control) has_aggregated_control = GF_TRUE; + has_aggregated_control = GF_TRUE; } /*regular setup in an established session (RTSP DESCRIBE)*/ if (in_session) { in_session->flags |= RTSP_AGG_CONTROL; stream->rtsp = in_session; - gf_list_add(rtp->channels, stream); + gf_list_add(rtp->streams, stream); return GF_OK; } @@ -306,25 +272,26 @@ GF_Err RP_AddStream(RTPClient *rtp, RTPStream *stream, char *session_control) if (strnicmp(stream->control, "rtsp://", 7) && strnicmp(stream->control, "rtspu://", 8) && strnicmp(stream->control, "satip://", 8)) { /*locate session by control - if no control was provided for the session, use default session*/ - if (!in_session) in_session = RP_CheckSession(rtp, session_control ? session_control : "*"); + if (!in_session) in_session = rtpin_rtsp_check(rtp, session_control ? session_control : "*"); /*none found, try to create one*/ - if (!in_session) in_session = RP_NewSession(rtp, session_control); + if (!in_session) in_session = rtpin_rtsp_new(rtp, session_control); /*cannot add an RTSP session for this channel, check if multicast*/ // if (!in_session && gf_rtp_is_unicast(stream->rtp_ch) ) return GF_SERVICE_ERROR; } /*stream control is absolute*/ else { - in_session = RP_CheckSession(rtp, stream->control); - if (!in_session) in_session = RP_CheckSession(rtp, session_control); + char *ctrl; + in_session = rtpin_rtsp_check(rtp, stream->control); + if (!in_session) in_session = rtpin_rtsp_check(rtp, session_control); if (!in_session) { if (session_control && strstr(stream->control, session_control)) - in_session = RP_NewSession(rtp, session_control); + in_session = rtpin_rtsp_new(rtp, session_control); else - in_session = RP_NewSession(rtp, stream->control); + in_session = rtpin_rtsp_new(rtp, stream->control); if (!in_session) return GF_SERVICE_ERROR; } /*remove session control part from channel control*/ - service_name = gf_rtsp_get_service_name(in_session->session); + service_name = in_session->session->Service; ctrl = strstr(stream->control, service_name); if (ctrl && (strlen(ctrl) != strlen(service_name)) ) { ctrl += strlen(service_name) + 1; @@ -347,36 +314,37 @@ GF_Err RP_AddStream(RTPClient *rtp, RTPStream *stream, char *session_control) stream->control = NULL; } stream->rtsp = in_session; - gf_list_add(rtp->channels, stream); + gf_list_add(rtp->streams, stream); return GF_OK; } -void RP_RemoveStream(RTPClient *rtp, RTPStream *ch) +void rtpin_remove_stream(GF_RTPIn *rtp, GF_RTPInStream *stream) { u32 i=0; - RTPStream *st; - gf_mx_p(rtp->mx); - while ((st = (RTPStream *)gf_list_enum(rtp->channels, &i))) { - if (st == ch) { - gf_list_rem(rtp->channels, i-1); + GF_RTPInStream *st; + while ((st = (GF_RTPInStream *)gf_list_enum(rtp->streams, &i))) { + if (st == stream) { + gf_list_rem(rtp->streams, i-1); break; } } - gf_mx_v(rtp->mx); } -void RP_ResetSession(RTSPSession *sess, GF_Err e) +static void rtpin_rtsp_reset(GF_RTPInRTSP *sess, GF_Err e) { - GF_RTSPCommand *com; - //u32 first = 1; + if (!sess) return; //destroy command list while (gf_list_count(sess->rtsp_commands)) { - com = (GF_RTSPCommand *)gf_list_get(sess->rtsp_commands, 0); + GF_RTSPCommand *com = (GF_RTSPCommand *)gf_list_get(sess->rtsp_commands, 0); gf_list_rem(sess->rtsp_commands, 0); - //this destroys stacks if any -// RP_SendFailure(sess, com, first ? e : GF_OK); + + /*reset static strings*/ + com->User_Agent = NULL; + com->Accept_Language = NULL; + com->Session = NULL; + gf_rtsp_command_del(com); //first = 0; } @@ -386,9 +354,10 @@ void RP_ResetSession(RTSPSession *sess, GF_Err e) } -void RP_DelSession(RTSPSession *sess) +void rtpin_rtsp_del(GF_RTPInRTSP *sess) { - RP_ResetSession(sess, GF_OK); + if (!sess) return; + rtpin_rtsp_reset(sess, GF_OK); gf_list_del(sess->rtsp_commands); gf_rtsp_response_del(sess->rtsp_rsp); gf_rtsp_session_del(sess->session); @@ -398,15 +367,17 @@ void RP_DelSession(RTSPSession *sess) gf_free(sess); } -void RP_SendMessage(GF_ClientService *service, GF_Err e, const char *message) +void rtpin_send_message(GF_RTPIn *ctx, GF_Err e, const char *message) { - GF_NetworkCommand com; +/* GF_NetworkCommand com; memset(&com, 0, sizeof(com)); com.command_type = GF_NET_SERVICE_EVENT; com.send_event.evt.type = GF_EVENT_MESSAGE; com.send_event.evt.message.message = message; com.send_event.evt.message.error = e; gf_service_command(service, &com, GF_OK); +*/ + } #endif /*GPAC_DISABLE_STREAMING*/ diff --git a/src/filters/in_rtp_sdp.c b/src/filters/in_rtp_sdp.c new file mode 100644 index 0000000..61b7ffe --- /dev/null +++ b/src/filters/in_rtp_sdp.c @@ -0,0 +1,378 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / RTP/RTSP input filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "in_rtp.h" +#include +//for ismacrypt scheme +#include +#include + +#ifndef GPAC_DISABLE_STREAMING + +static GF_Err rtpin_setup_sdp(GF_RTPIn *rtp, GF_SDPInfo *sdp, GF_RTPInStream *for_stream) +{ + GF_Err e; + GF_SDPMedia *media; + Double Start, End; + u32 i; + char *sess_ctrl; + GF_X_Attribute *att; + GF_RTSPRange *range; + + Start = 0.0; + End = -1.0; + + sess_ctrl = NULL; + range = NULL; + + i=0; + while ((att = (GF_X_Attribute*)gf_list_enum(sdp->Attributes, &i))) { + if (!att->Name || !att->Value) continue; + //session-level control string. Keep it in the current session if any + if (!strcmp(att->Name, "control")) sess_ctrl = att->Value; + //NPT range only for now + else if (!strcmp(att->Name, "range") && !range) range = gf_rtsp_range_parse(att->Value); + /*we have the H264-SVC streams*/ + else if (!strcmp(att->Name, "group") && !strncmp(att->Value, "DDP", 3)) rtp->is_scalable = GF_TRUE; + } + if (range) { + Start = range->start; + End = range->end; + gf_rtsp_range_del(range); + } + + //setup all streams + i=0; + while ((media = (GF_SDPMedia*)gf_list_enum(sdp->media_desc, &i))) { + GF_RTPInStream *stream = rtpin_stream_new(rtp, media, sdp, for_stream); + //do not generate error if the channel is not created, just assume + //1 - this is not an MPEG-4 configured channel -> not needed + //2 - this is a 2nd describe and the channel was already created + if (!stream) continue; + + e = rtpin_add_stream(rtp, stream, sess_ctrl); + if (e) { + rtpin_stream_del(stream); + return e; + } + + if (!(stream->flags & RTP_HAS_RANGE)) { + stream->range_start = Start; + stream->range_end = End; + if (End > 0) stream->flags |= RTP_HAS_RANGE; + } + + /*force interleaving whenever needed*/ + if (stream->rtsp) { + switch (stream->depacketizer->sl_map.StreamType) { + case GF_STREAM_VISUAL: + case GF_STREAM_AUDIO: + if ((rtp->interleave==1) && ! (stream->rtsp->flags & RTSP_FORCE_INTER) ) { + gf_rtsp_set_buffer_size(stream->rtsp->session, stream->rtpin->block_size); + stream->rtsp->flags |= RTSP_FORCE_INTER; + } + break; + default: + if (rtp->interleave && ! (stream->rtsp->flags & RTSP_FORCE_INTER) ) { + gf_rtsp_set_buffer_size(stream->rtsp->session, stream->rtpin->block_size); + stream->rtsp->flags |= RTSP_FORCE_INTER; + } + break; + } + } + + } + return GF_OK; +} + +/*load iod from data:application/mpeg4-iod;base64*/ +static GF_Err rtpin_sdp_load_iod(GF_RTPIn *rtp, char *iod_str) +{ + char buf[2000]; + u32 size; + + if (rtp->iod_desc) return GF_SERVICE_ERROR; + /*the only IOD format we support*/ + iod_str += 1; + if (!strnicmp(iod_str, "data:application/mpeg4-iod;base64", strlen("data:application/mpeg4-iod;base64"))) { + char *buf64; + u32 size64; + + buf64 = strstr(iod_str, ","); + if (!buf64) return GF_URL_ERROR; + buf64 += 1; + size64 = (u32) strlen(buf64) - 1; + + size = gf_base64_decode(buf64, size64, buf, 2000); + if (!size) return GF_SERVICE_ERROR; + } else if (!strnicmp(iod_str, "data:application/mpeg4-iod;base16", strlen("data:application/mpeg4-iod;base16"))) { + char *buf16; + u32 size16; + + buf16 = strstr(iod_str, ","); + if (!buf16) return GF_URL_ERROR; + buf16 += 1; + size16 = (u32) strlen(buf16) - 1; + + size = gf_base16_decode(buf16, size16, buf, 2000); + if (!size) return GF_SERVICE_ERROR; + } else { + return GF_NOT_SUPPORTED; + } + + gf_odf_desc_read(buf, size, &rtp->iod_desc); + return GF_OK; +} + +void rtpin_declare_pid(GF_RTPInStream *stream, Bool force_iod, u32 ch_idx, u32 *ocr_es_id) +{ + GP_RTPSLMap *sl_map; + const GF_RTPStaticMap *static_map; + + if (!stream->depacketizer) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("RTP Stream channel %u has no depacketizer - not supported\n", ch_idx)); + return; + } + + assert(!stream->opid); + stream->opid = gf_filter_pid_new(stream->rtpin->filter); + + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_ID, &PROP_UINT(ch_idx) ); + if (stream->ES_ID && (force_iod || stream->rtpin->iod_desc)) + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_ESID, &PROP_UINT(stream->ES_ID) ); + + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT( gf_rtp_get_clockrate(stream->rtp_ch) ) ); + + + if (stream->rtpin->session && (stream->flags & RTP_HAS_RANGE)) { + GF_Fraction64 dur; + dur.den = 1000; + dur.num = (s64) (1000 * (stream->range_end - stream->range_start)); + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_DURATION, &PROP_FRAC64(dur) ); + } else { + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_NONE ) ); + } + if (stream->mid) + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_SCALABLE, &PROP_BOOL( GF_TRUE) ); + + //TOCHECK: do we need to map ODID ? + //od->objectDescriptorID = stream->OD_ID ? stream->OD_ID : stream->ES_ID; + + // for each channel depending on this channel, get esd, set esd->dependsOnESID and add to od + if (stream->rtpin->is_scalable) { + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_DEPENDENCY_ID, &PROP_UINT( stream->prev_stream) ); + } + + if (stream->rtpin->iod_desc) { + u32 i, count; + GF_ObjectDescriptor *iod = (GF_ObjectDescriptor *)stream->rtpin->iod_desc; + count = gf_list_count(iod->ESDescriptors); + for (i=0; iESDescriptors, i); + if (esd->ESID == stream->ES_ID) { + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_IN_IOD, &PROP_BOOL( GF_TRUE ) ); + } + } + } else if (force_iod) { + switch (stream->depacketizer->sl_map.StreamType) { + case GF_STREAM_OD: + case GF_STREAM_SCENE: + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_IN_IOD, &PROP_BOOL( GF_TRUE ) ); + break; + } + } + if (!stream->ES_ID) + stream->ES_ID = ch_idx; + + if (ocr_es_id) { + if (! *ocr_es_id) *ocr_es_id = stream->ES_ID; + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_CLOCK_ID, &PROP_UINT( *ocr_es_id) ); + } + + sl_map = &stream->depacketizer->sl_map; + static_map = stream->depacketizer->static_map; + + if (sl_map->StreamType) { + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(sl_map->StreamType) ); + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_CODECID, &PROP_UINT(sl_map->CodecID) ); + + if (sl_map->config) + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA(sl_map->config, sl_map->configSize) ); + + if (sl_map->rvc_predef) { + gf_filter_pid_set_property_str(stream->opid, "rvc:predef", &PROP_UINT( sl_map->rvc_predef) ); + } else if (sl_map->rvc_config) { + gf_filter_pid_set_property_str(stream->opid, "rvc:config", &PROP_DATA_NO_COPY( sl_map->rvc_config, sl_map->rvc_config_size) ); + sl_map->rvc_config = NULL; + sl_map->rvc_config_size = 0; + } + } else if (static_map) { + if (static_map->stream_type) + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(static_map->stream_type) ); + if (static_map->codec_id) + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_CODECID, &PROP_UINT(static_map->codec_id) ); + if (static_map->mime) + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_MIME, &PROP_STRING((char*)static_map->mime) ); + } + if (stream->depacketizer->w) + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_WIDTH, &PROP_UINT(stream->depacketizer->w) ); + if (stream->depacketizer->h) + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(stream->depacketizer->h) ); + + + /*ISMACryp config*/ + if (stream->depacketizer->flags & GF_RTP_HAS_ISMACRYP) { + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_PROTECTION_SCHEME_TYPE, &PROP_UINT(GF_ISOM_ISMACRYP_SCHEME) ); + + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_PROTECTION_SCHEME_VERSION, &PROP_UINT(1) ); + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_PROTECTION_KMS_URI, &PROP_STRING(stream->depacketizer->key) ); + } + + if (sl_map->StreamType==GF_STREAM_VISUAL) { + if (stream->depacketizer->payt != GF_RTP_PAYT_MPEG4) { + gf_filter_pid_recompute_dts(stream->opid, GF_TRUE); + } else if (!stream->depacketizer->sl_map.DTSDeltaLength && !stream->depacketizer->sl_map.CTSDeltaLength) { + gf_filter_pid_recompute_dts(stream->opid, GF_TRUE); + } + } + + + if (sl_map->StreamType==GF_STREAM_AUDIO) { + switch (sl_map->CodecID) { + case GF_CODECID_AAC_MPEG4: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + if (sl_map->config) { +#ifndef GPAC_DISABLE_AV_PARSERS + GF_M4ADecSpecInfo acfg; + gf_m4a_get_config(sl_map->config, sl_map->configSize, &acfg); + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(acfg.base_sr) ); + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(acfg.nb_chan) ); +#endif + + } else { + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(gf_rtp_get_clockrate(stream->rtp_ch) ) ); + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(2) ); + + } + break; + } + } +} + + +static void rtpin_declare_media(GF_RTPIn *rtp, Bool force_iod) +{ + GF_RTPInStream *stream; + u32 i, ocr_es_id; + ocr_es_id = 0; + /*add everything*/ + i=0; + while ((stream = (GF_RTPInStream *)gf_list_enum(rtp->streams, &i))) { + if (stream->control && !strnicmp(stream->control, "data:", 5)) continue; + if (stream->prev_stream) continue; + if (rtp->stream_type && (rtp->stream_type!=stream->depacketizer->sl_map.StreamType)) continue; + + rtpin_declare_pid(stream, force_iod, i, &ocr_es_id); + } + rtp->stream_type = 0; +} + +void rtpin_load_sdp(GF_RTPIn *rtp, char *sdp_text, u32 sdp_len, GF_RTPInStream *stream) +{ + GF_Err e; + u32 i; + GF_SDPInfo *sdp; + Bool is_isma_1; + char *iod_str; + GF_X_Attribute *att; + Bool force_in_iod = GF_FALSE; + + is_isma_1 = GF_FALSE; + iod_str = NULL; + sdp = gf_sdp_info_new(); + e = gf_sdp_info_parse(sdp, sdp_text, sdp_len); + + if (e == GF_OK) e = rtpin_setup_sdp(rtp, sdp, stream); + + if (e != GF_OK) { + if (!stream) { + gf_filter_setup_failure(rtp->filter, e); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTPIn] code not tested file %s line %d !!\n", __FILE__, __LINE__)); + gf_filter_setup_failure(rtp->filter, e); + stream->status = RTP_Unavailable; + } + gf_sdp_info_del(sdp); + return; + } + + /*root SDP, attach service*/ + if (!stream) { + /*look for IOD*/ + i=0; + while ((att = (GF_X_Attribute*)gf_list_enum(sdp->Attributes, &i))) { + if (!iod_str && !strcmp(att->Name, "mpeg4-iod") ) iod_str = att->Value; + if (!is_isma_1 && !strcmp(att->Name, "isma-compliance") ) { + if (!stricmp(att->Value, "1,1.0,1")) is_isma_1 = GF_TRUE; + } + } + + /*force iod reconstruction with ISMA to use proper clock dependencies*/ + if (is_isma_1) iod_str = NULL; + + if (!iod_str) { + GF_RTPInStream *a_stream; + i=0; + while (!force_in_iod && (a_stream = (GF_RTPInStream *)gf_list_enum(rtp->streams, &i))) { + if (!a_stream->depacketizer) continue; + if (a_stream->depacketizer->payt!=GF_RTP_PAYT_MPEG4) continue; + switch (a_stream->depacketizer->sl_map.StreamType) { + case GF_STREAM_SCENE: + case GF_STREAM_OD: + force_in_iod = GF_TRUE; + break; + default: + break; + } + } + } + + if (iod_str) e = rtpin_sdp_load_iod(rtp, iod_str); + + /* service failed*/ + if (e) gf_filter_setup_failure(rtp->filter, e); + else rtpin_declare_media(rtp, force_in_iod); + } + /*channel SDP */ + else { + /*connect*/ + rtpin_stream_setup(stream, NULL); + } + gf_sdp_info_del(sdp); +} + +#endif /*GPAC_DISABLE_STREAMING*/ diff --git a/src/filters/in_rtp_signaling.c b/src/filters/in_rtp_signaling.c new file mode 100644 index 0000000..3a3c975 --- /dev/null +++ b/src/filters/in_rtp_signaling.c @@ -0,0 +1,867 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / RTP/RTSP input filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "in_rtp.h" +#include + +#ifndef GPAC_DISABLE_STREAMING + +static Bool rtpin_stream_is_valid(GF_RTPIn *rtp, GF_RTPInStream *stream) +{ + u32 i=0; + GF_RTPInStream *st; + while ((st = (GF_RTPInStream *)gf_list_enum(rtp->streams, &i))) { + if (st == stream) return GF_TRUE; + } + return GF_FALSE; +} + +/*this prevent sending teardown on session with running channels*/ +static Bool rtpin_rtsp_is_active(GF_RTPInStream *stream) +{ + GF_RTPInStream *a_st; + u32 i, count; + i = count = 0; + while ((a_st = (GF_RTPInStream *)gf_list_enum(stream->rtpin->streams, &i))) { + if (a_st->rtsp != stream->rtsp) continue; + /*count only active channels*/ + if (a_st->status == RTP_Running) count++; + } + return count ? GF_TRUE : GF_FALSE; +} + +static void rtpin_rtsp_queue_command(GF_RTPInRTSP *sess, GF_RTPInStream *stream, GF_RTSPCommand *com, Bool needs_sess_id) +{ + if (needs_sess_id) { + com->Session = sess->session_id; + } + gf_list_add(sess->rtsp_commands, com); +} + + + +/* + channel setup functions + */ + +void rtpin_rtsp_setup_send(GF_RTPInStream *stream) +{ + GF_RTSPCommand *com; + GF_RTSPTransport *trans; + + com = gf_rtsp_command_new(); + com->method = gf_strdup(GF_RTSP_SETUP); + + //setup ports if unicast non interleaved or multicast + if (gf_rtp_is_unicast(stream->rtp_ch) && (stream->rtpin->interleave != 1) && !gf_rtp_is_interleaved(stream->rtp_ch) ) { + gf_rtp_set_ports(stream->rtp_ch, stream->rtpin->firstport); + } else if (stream->rtpin->force_mcast) { + gf_rtp_set_ports(stream->rtp_ch, stream->rtpin->firstport); + } + + trans = gf_rtsp_transport_clone((GF_RTSPTransport *)gf_rtp_get_transport(stream->rtp_ch)); + + /*some servers get confused when trying to resetup on the same remote ports, so reset info*/ + trans->port_first = trans->port_last = 0; + trans->SSRC = 0; + + /*override transport: */ + /*1: multicast forced*/ + if (stream->rtpin->force_mcast) { + trans->IsUnicast = GF_FALSE; + trans->destination = gf_strdup(stream->rtpin->force_mcast); + trans->TTL = stream->rtpin->ttl; + if (trans->Profile) gf_free(trans->Profile); + trans->Profile = gf_strdup(GF_RTSP_PROFILE_RTP_AVP); + if (!(stream->rtsp->flags & RTSP_DSS_SERVER) ) { + trans->port_first = trans->client_port_first; + trans->port_last = trans->client_port_last; + /*this is correct but doesn't work with DSS: the server expects "client_port" to indicate + the multicast port, not "port" - this will send both*/ + //trans->client_port_first = trans->client_port_last = 0; + } + gf_rtp_setup_transport(stream->rtp_ch, trans, NULL); + } + /*2: RTP over RTSP forced*/ + else if (stream->rtsp->flags & RTSP_FORCE_INTER) { + if (trans->Profile) gf_free(trans->Profile); + trans->Profile = gf_strdup(GF_RTSP_PROFILE_RTP_AVP_TCP); + //some servers expect the interleaved to be set during the setup request + trans->IsInterleaved = GF_TRUE; + trans->rtpID = 2*gf_list_find(stream->rtpin->streams, stream); + trans->rtcpID = trans->rtpID+1; + gf_rtp_setup_transport(stream->rtp_ch, trans, NULL); + } + + if (trans->source) { + gf_free(trans->source); + trans->source = NULL; + } + + /*turn off interleaving in case of re-setup, some servers don't like it (we still signal it + through RTP/AVP/TCP profile so it's OK)*/ +// trans->IsInterleaved = 0; + gf_list_add(com->Transports, trans); + if (strlen(stream->control)) com->ControlString = gf_strdup(stream->control); + + com->user_data = stream; + stream->status = RTP_WaitingForAck; + + rtpin_rtsp_queue_command(stream->rtsp, stream, com, GF_TRUE); +} + +/*filter setup if no session (rtp only)*/ +GF_Err rtpin_stream_setup(GF_RTPInStream *stream, RTPIn_StreamDescribe *ch_desc) +{ + GF_Err resp; + + /*assign ES_ID of the channel*/ + if (ch_desc && !stream->ES_ID && ch_desc->ES_ID) stream->ES_ID = ch_desc->ES_ID; + + stream->status = RTP_Setup; + + /*assign channel handle if not done*/ + if (ch_desc && stream->opid) { + assert(stream->opid == ch_desc->opid); + } else if (!stream->opid && stream->rtsp && !stream->rtsp->satip) { + assert(ch_desc); + assert(ch_desc->opid); + stream->opid = ch_desc->opid; + } + + /*no session , setup for pure rtp*/ + if (!stream->rtsp) { + stream->flags |= RTP_CONNECTED; + /*init rtp*/ + resp = rtpin_stream_init(stream, GF_FALSE); + /*send confirmation to user*/ + rtpin_stream_ack_connect(stream, resp); + } else { + rtpin_rtsp_setup_send(stream); + } + return GF_OK; +} + +static GF_Err rtpin_rtsp_tcp_send_report(void *par, void *par2, Bool is_rtcp, u8 *pck, u32 pck_size) +{ + return GF_OK; +} + +void rtpin_rtsp_setup_process(GF_RTPInRTSP *sess, GF_RTSPCommand *com, GF_Err e) +{ + GF_RTPInStream *stream; + u32 i; + GF_RTSPTransport *trans; + + stream = (GF_RTPInStream *)com->user_data; + if (e) goto exit; + + switch (sess->rtsp_rsp->ResponseCode) { + case NC_RTSP_OK: + break; + case NC_RTSP_Not_Found: + e = GF_STREAM_NOT_FOUND; + goto exit; + default: + e = GF_SERVICE_ERROR; + goto exit; + } + e = GF_SERVICE_ERROR; + if (!stream) goto exit; + + /*assign session ID*/ + if (!sess->rtsp_rsp->Session) { + e = GF_SERVICE_ERROR; + goto exit; + } + if (!sess->session_id) sess->session_id = gf_strdup(sess->rtsp_rsp->Session); + assert(!stream->session_id); + + /*transport setup: break at the first correct transport */ + i=0; + while ((trans = (GF_RTSPTransport *)gf_list_enum(sess->rtsp_rsp->Transports, &i))) { + /*copy over previous ports (hack for some servers overriding client ports)*/ + if (stream->rtpin->use_client_ports) + gf_rtp_get_ports(stream->rtp_ch, &trans->client_port_first, &trans->client_port_last); + + if (gf_rtp_is_interleaved(stream->rtp_ch) && !trans->IsInterleaved) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTSP] Requested interleaved RTP over RTSP but server did not setup interleave - cannot process command\n")); + e = GF_REMOTE_SERVICE_ERROR; + continue; + } + + e = gf_rtp_setup_transport(stream->rtp_ch, trans, gf_rtsp_get_server_name(sess->session)); + if (!e) break; + } + if (e) goto exit; + + e = rtpin_stream_init(stream, GF_FALSE); + if (e) goto exit; + stream->status = RTP_Connected; + + //in case this is TCP channel, setup callbacks + stream->flags &= ~RTP_INTERLEAVED; + if (gf_rtp_is_interleaved(stream->rtp_ch)) { + stream->flags |= RTP_INTERLEAVED; + gf_rtsp_set_interleave_callback(sess->session, rtpin_rtsp_data_cbk); + + gf_rtp_set_interleave_callbacks(stream->rtp_ch, rtpin_rtsp_tcp_send_report, stream, stream); + sess->flags |= RTSP_TCP_FLUSH; +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) + rtpin_rtsp_tcp_send_report(NULL, NULL, GF_FALSE, NULL, 0); +#endif + } + + if (sess->satip) { + RTPIn_StreamControl *ch_ctrl = NULL; + GF_RTSPCommand *a_com = gf_rtsp_command_new(); + a_com->method = gf_strdup(GF_RTSP_PLAY); + GF_SAFEALLOC(ch_ctrl, RTPIn_StreamControl); + if (ch_ctrl) { + ch_ctrl->stream = stream; + a_com->user_data = ch_ctrl; + } + rtpin_rtsp_queue_command(sess, stream, a_com, GF_TRUE); + } + +exit: + /*confirm only on first connect, otherwise this is a re-SETUP of the rtsp session, not the channel*/ + if (stream && ! (stream->flags & RTP_CONNECTED) ) { + if (!e) + stream->flags |= RTP_CONNECTED; + rtpin_stream_ack_connect(stream, e); + } + com->user_data = NULL; +} + + +/* + session/channel describe functions + */ +/*filter describe commands in case of ESD URLs*/ +Bool rtpin_rtsp_describe_preprocess(GF_RTPInRTSP *sess, GF_RTSPCommand *com) +{ + GF_RTPInStream *stream; + RTPIn_StreamDescribe *ch_desc; + /*not a channel describe*/ + if (!com->user_data) { + rtpin_send_message(sess->rtpin, GF_OK, "Connecting..."); + return GF_TRUE; + } + + ch_desc = (RTPIn_StreamDescribe *)com->user_data; + stream = rtpin_find_stream(sess->rtpin, NULL, ch_desc->ES_ID, ch_desc->esd_url, GF_FALSE); + if (!stream) return GF_TRUE; + + /*channel has been described already, skip describe and send setup directly*/ + rtpin_stream_setup(stream, ch_desc); + + if (ch_desc->esd_url) gf_free(ch_desc->esd_url); + gf_free(ch_desc); + return GF_FALSE; +} + +/*process describe reply*/ +GF_Err rtpin_rtsp_describe_process(GF_RTPInRTSP *sess, GF_RTSPCommand *com, GF_Err e) +{ + GF_RTPInStream *stream; + RTPIn_StreamDescribe *ch_desc; + + stream = NULL; + ch_desc = (RTPIn_StreamDescribe *)com->user_data; + if (e) goto exit; + + switch (sess->rtsp_rsp->ResponseCode) { + //TODO handle all 3xx codes (redirections) + case NC_RTSP_Multiple_Choice: + e = ch_desc ? GF_STREAM_NOT_FOUND : GF_URL_ERROR; + goto exit; + case NC_RTSP_Not_Found: + e = GF_URL_ERROR; + goto exit; + case NC_RTSP_OK: + break; + default: + //we should have a basic error code mapping here + e = GF_SERVICE_ERROR; + goto exit; + } + + stream = NULL; + if (ch_desc) { + stream = rtpin_find_stream(sess->rtpin, ch_desc->opid, ch_desc->ES_ID, ch_desc->esd_url, GF_FALSE); + } else { + rtpin_send_message(sess->rtpin, GF_OK, "Connected"); + } + + /*error on loading SDP is done internally*/ + rtpin_load_sdp(sess->rtpin, sess->rtsp_rsp->body, sess->rtsp_rsp->Content_Length, stream); + + if (!ch_desc) goto exit; + if (!stream) { + e = GF_STREAM_NOT_FOUND; + goto exit; + } + e = rtpin_stream_setup(stream, ch_desc); + +exit: + com->user_data = NULL; + if (e) { + if (!ch_desc) { + sess->connect_error = e; + return e; + } else if (stream) { + rtpin_stream_ack_connect(stream, e); + } else { + //TODO - check if this is correct + GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTPIn] code not tested file %s line %d !!\n", __FILE__, __LINE__)); + gf_filter_setup_failure(sess->rtpin->filter, e); + } + } + if (ch_desc) gf_free(ch_desc); + return GF_OK; +} + +/*send describe*/ +void rtpin_rtsp_describe_send(GF_RTPInRTSP *sess, char *esd_url, GF_FilterPid *opid) +{ + RTPIn_StreamDescribe *ch_desc; + GF_RTSPCommand *com; + + /*locate the channel by URL - if we have one, this means the channel is already described + this happens when 2 ESD with URL use the same RTSP service - skip describe and send setup*/ + if (esd_url || opid) { + GF_RTPInStream *stream = rtpin_find_stream(sess->rtpin, opid, 0, esd_url, GF_FALSE); + if (stream) { + if (!stream->opid) stream->opid = opid; + switch (stream->status) { + case RTP_Connected: + case RTP_Running: + rtpin_stream_ack_connect(stream, GF_OK); + return; + default: + break; + } + ch_desc = (RTPIn_StreamDescribe *)gf_malloc(sizeof(RTPIn_StreamDescribe)); + ch_desc->esd_url = esd_url ? gf_strdup(esd_url) : NULL; + ch_desc->opid = opid; + rtpin_stream_setup(stream, ch_desc); + + if (esd_url) gf_free(ch_desc->esd_url); + gf_free(ch_desc); + return; + } + /*channel not found, send describe on service*/ + } + + /*send describe*/ + com = gf_rtsp_command_new(); + if (!sess->satip) { + com->method = gf_strdup(GF_RTSP_DESCRIBE); + } else { + GF_Err e; + GF_RTSPTransport *trans; + GF_RTPInStream *stream = NULL; + + com->method = gf_strdup(GF_RTSP_SETUP); + + /*setup transport ports*/ + GF_SAFEALLOC(trans, GF_RTSPTransport); + if (trans) { + trans->IsUnicast = GF_TRUE; + trans->client_port_first = sess->rtpin->satip_port; + trans->client_port_last = sess->rtpin->satip_port+1; + trans->Profile = gf_strdup(GF_RTSP_PROFILE_RTP_AVP); + gf_list_add(com->Transports, trans); + } + + /*hardcoded channel*/ + stream = rtpin_stream_new_satip(sess->rtpin, sess->satip_server); + if (!stream) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("SAT>IP: couldn't create the RTP stream.\n")); + return; + } + e = rtpin_add_stream(sess->rtpin, stream, "*"); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("SAT>IP: couldn't add the RTP stream.\n")); + return; + } + com->user_data = stream; + } + + if (opid || esd_url) { + com->Accept = gf_strdup("application/sdp"); + com->ControlString = esd_url ? gf_strdup(esd_url) : NULL; + + ch_desc = (RTPIn_StreamDescribe *)gf_malloc(sizeof(RTPIn_StreamDescribe)); + ch_desc->esd_url = esd_url ? gf_strdup(esd_url) : NULL; + ch_desc->opid = opid; + + com->user_data = ch_desc; + } else { + //always accept both SDP and IOD + com->Accept = gf_strdup("application/sdp, application/mpeg4-iod"); +// com->Accept = gf_strdup("application/sdp"); + } + + /*need better tuning ...*/ + if (sess->rtpin->bandwidth) + com->Bandwidth = sess->rtpin->bandwidth; + + rtpin_rtsp_queue_command(sess, NULL, com, GF_FALSE); +} + + +static void rtpin_rtsp_skip_command(GF_RTPInStream *stream) +{ + u32 i; + GF_RTPInStream *a_st; + if (!stream || (stream->flags & RTP_SKIP_NEXT_COM) || !(stream->rtsp->flags & RTSP_AGG_CONTROL) ) return; + i=0; + while ((a_st = (GF_RTPInStream *)gf_list_enum(stream->rtpin->streams, &i))) { + if ((stream == a_st) || (a_st->rtsp != stream->rtsp) ) continue; + if (a_st->status>=RTP_Connected) + a_st->flags |= RTP_SKIP_NEXT_COM; + } +} + + +/* + channel control functions + */ +/*remove command if session is using aggregated control*/ +Bool rtpin_rtsp_usercom_preprocess(GF_RTPInRTSP *sess, GF_RTSPCommand *com) +{ + RTPIn_StreamControl *ch_ctrl; + GF_RTPInStream *stream; + GF_Err e; + Bool skip_it; + + ch_ctrl = NULL; + if (strcmp(com->method, GF_RTSP_TEARDOWN)) ch_ctrl = (RTPIn_StreamControl *)com->user_data; + if (!ch_ctrl || !ch_ctrl->stream) return GF_TRUE; + stream = ch_ctrl->stream; + + if (!sess->satip) { + if (!stream->opid || !rtpin_stream_is_valid(sess->rtpin, stream)) { + gf_free(ch_ctrl); + com->user_data = NULL; + return GF_FALSE; + } + + assert(stream->rtsp == sess); + assert(stream->opid == ch_ctrl->evt.base.on_pid); + } + + skip_it = GF_FALSE; + if (!com->Session) { + /*re-SETUP failed*/ + if (!strcmp(com->method, GF_RTSP_PLAY) || !strcmp(com->method, GF_RTSP_PAUSE)) { + e = GF_SERVICE_ERROR; + goto err_exit; + } + /*this is a stop, no need for SessionID just skip*/ + skip_it = GF_TRUE; + } else { + rtpin_rtsp_skip_command(stream); + } + + /*check if aggregation discards this command*/ + if (skip_it || ( (sess->flags & RTSP_AGG_CONTROL) && (stream->flags & RTP_SKIP_NEXT_COM) )) { + stream->flags &= ~RTP_SKIP_NEXT_COM; + gf_free(ch_ctrl); + com->user_data = NULL; + return GF_FALSE; + } + return GF_TRUE; + +err_exit: + gf_rtsp_reset_aggregation(stream->rtsp->session); + stream->status = RTP_Disconnected; + stream->check_rtp_time = RTP_SET_TIME_NONE; + + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTSP] Error processing event %s: %s\n", gf_filter_event_name(ch_ctrl->evt.base.type), gf_error_to_string(e) )); + + gf_free(ch_ctrl); + com->user_data = NULL; + return GF_FALSE; +} + +void rtpin_rtsp_usercom_process(GF_RTPInRTSP *sess, GF_RTSPCommand *com, GF_Err e) +{ + RTPIn_StreamControl *ch_ctrl; + GF_RTPInStream *stream, *agg_st; + u32 i, count; + GF_RTPInfo *info; + + ch_ctrl = (RTPIn_StreamControl *)com->user_data; + stream = ch_ctrl->stream; + + if (stream) { + if (!stream->opid || !rtpin_stream_is_valid(sess->rtpin, stream)) { + gf_free(ch_ctrl); + com->user_data = NULL; + return; + } + + assert(stream->opid==ch_ctrl->evt.base.on_pid); + } + + /*some consistency checking: on interleaved sessions, some servers do NOT reply to the + teardown. If our command is STOP just skip the error notif*/ + if (e) { + if (!strcmp(com->method, GF_RTSP_TEARDOWN)) { + goto process_reply; + } else { + /*spec is not really clear about what happens if the server doesn't support + non aggregated operations. Since this happen only on pause/play, we consider + that no error occured and wait for next play*/ + if (sess->rtsp_rsp->ResponseCode == NC_RTSP_Only_Aggregate_Operation_Allowed) { + sess->flags |= RTSP_AGG_ONLY; + sess->rtsp_rsp->ResponseCode = NC_RTSP_OK; + } else { + goto err_exit; + } + } + } + + switch (sess->rtsp_rsp->ResponseCode) { + //handle all 3xx codes (redirections) + case NC_RTSP_Method_Not_Allowed: + e = GF_NOT_SUPPORTED; + goto err_exit; + case NC_RTSP_OK: + break; + default: + //we should have a basic error code mapping here + e = GF_SERVICE_ERROR; + goto err_exit; + } + +process_reply: + + if ( (ch_ctrl->evt.base.type==GF_FEVT_PLAY) + || (ch_ctrl->evt.base.type==GF_FEVT_SET_SPEED) + || (ch_ctrl->evt.base.type==GF_FEVT_RESUME) ) { + + //auto-detect any aggregated control if not done yet + if (gf_list_count(sess->rtsp_rsp->RTP_Infos) > 1) { + sess->flags |= RTSP_AGG_CONTROL; + } + + //process all RTP infos + count = gf_list_count(sess->rtsp_rsp->RTP_Infos); + for (i=0; irtsp_rsp->RTP_Infos, i); + agg_st = rtpin_find_stream(sess->rtpin, NULL, 0, info->url, GF_FALSE); + + if (!agg_st || (agg_st->rtsp != sess) ) continue; + /*channel is already playing*/ + if (agg_st->status == RTP_Running) { + gf_rtp_set_info_rtp(agg_st->rtp_ch, info->seq, info->rtp_time, info->ssrc); + agg_st->check_rtp_time = RTP_SET_TIME_RTP; + continue; + } + + /*if play/seeking we must send update RTP/NPT link*/ + if (ch_ctrl->evt.base.type != GF_FEVT_RESUME) { + agg_st->check_rtp_time = RTP_SET_TIME_RTP; + } + /*this is used to discard RTP packets re-sent on resume*/ + else { + agg_st->check_rtp_time = RTP_SET_TIME_RTP_SEEK; + } + /* reset the buffers */ + rtpin_stream_init(agg_st, GF_TRUE); + + gf_rtp_set_info_rtp(agg_st->rtp_ch, info->seq, info->rtp_time, info->ssrc); + agg_st->status = RTP_Running; + + /*skip next play command on this channel if aggregated control*/ + if ((stream != agg_st) && stream && (stream->rtsp->flags & RTSP_AGG_CONTROL) ) agg_st->flags |= RTP_SKIP_NEXT_COM; + + + if (gf_rtp_is_interleaved(agg_st->rtp_ch)) { + gf_rtsp_register_interleave(sess->session, + agg_st, + gf_rtp_get_low_interleave_id(agg_st->rtp_ch), + gf_rtp_get_hight_interleave_id(agg_st->rtp_ch)); + } + } + /*no rtp info (just in case), no time mapped - set to 0 and specify we're not interactive*/ + if (stream && !i) { + stream->current_start = 0.0; + stream->check_rtp_time = RTP_SET_TIME_RTP; + rtpin_stream_init(stream, GF_TRUE); + stream->status = RTP_Running; + if (gf_rtp_is_interleaved(stream->rtp_ch)) { + gf_rtsp_register_interleave(sess->session, + stream, gf_rtp_get_low_interleave_id(stream->rtp_ch), gf_rtp_get_hight_interleave_id(stream->rtp_ch)); + } + } + if (stream) stream->flags &= ~RTP_SKIP_NEXT_COM; + } else if (ch_ctrl->evt.base.type == GF_FEVT_PAUSE) { + if (stream) { + rtpin_rtsp_skip_command(stream); + stream->flags &= ~RTP_SKIP_NEXT_COM; + + } + } else if (ch_ctrl->evt.base.type == GF_FEVT_STOP) { + sess->rtpin->eos_probe_start = gf_sys_clock(); + if (stream) + stream->flags |= RTP_EOS; + } + gf_free(ch_ctrl); + com->user_data = NULL; + return; + + +err_exit: + if (stream) { + stream->status = RTP_Disconnected; + stream->flags |= RTP_EOS; + gf_rtsp_reset_aggregation(stream->rtsp->session); + stream->check_rtp_time = RTP_SET_TIME_NONE; + } + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTSP] Error processing user command %s\n", gf_error_to_string(e) )); + gf_free(ch_ctrl); + com->user_data = NULL; + if (sess->flags & RTSP_TCP_FLUSH) { + sess->rtpin->eos_probe_start = gf_sys_clock(); + } +} + + +void rtpin_rtsp_usercom_send(GF_RTPInRTSP *sess, GF_RTPInStream *stream, const GF_FilterEvent *evt) +{ + RTPIn_StreamControl *ch_ctrl; + u32 i; + Bool needs_setup = GF_FALSE; + GF_RTSPCommand *com; + GF_RTSPRange *range; + + switch (evt->base.type) { + case GF_FEVT_PLAY: + case GF_FEVT_RESUME: + needs_setup = GF_TRUE; + break; + case GF_FEVT_PAUSE: + case GF_FEVT_STOP: + break; + //TODO + case GF_FEVT_SET_SPEED: + default: + return; + } + + + /*we may need to re-setup stream/session*/ + if (needs_setup) { + if (stream->status == RTP_Disconnected) { + if (sess->flags & RTSP_AGG_CONTROL) { + GF_RTPInStream *a_st; + i=0; + while ((a_st = (GF_RTPInStream *)gf_list_enum(sess->rtpin->streams, &i))) { + if (a_st->rtsp != sess) continue; + if (a_st->status == RTP_Disconnected) + rtpin_rtsp_setup_send(a_st); + } + } else { + rtpin_rtsp_setup_send(stream); + } + } + } + + com = gf_rtsp_command_new(); + range = NULL; + + if ( (evt->base.type == GF_FEVT_PLAY) || (evt->base.type == GF_FEVT_RESUME) ) { + + range = gf_rtsp_range_new(); + range->start = stream->range_start; + range->end = stream->range_end; + + com->method = gf_strdup(GF_RTSP_PLAY); + + stream->paused = GF_FALSE; + + /*specify pause range on resume - this is not mandatory but most servers need it*/ + if (evt->base.type == GF_FEVT_RESUME) { + range->start = stream->current_start; + + stream->stat_start_time -= stream->stat_stop_time; + stream->stat_start_time += gf_sys_clock(); + stream->stat_stop_time = 0; + } else { + range->start = stream->range_start; + if (evt->play.start_range>=0) range->start += evt->play.start_range; + range->end = stream->range_start; + if (evt->play.end_range >=0) { + range->end += evt->play.end_range; + if (range->end > stream->range_end) range->end = stream->range_end; + } + + stream->stat_start_time = gf_sys_clock(); + stream->stat_stop_time = 0; + } + /*if aggregated the command is sent once, so store info at session level*/ + if (stream->flags & RTP_SKIP_NEXT_COM) { + stream->current_start = stream->rtsp->last_range; + } else { + stream->rtsp->last_range = range->start; + stream->current_start = range->start; + } + /*some RTSP servers don't accept Range=npt:0.0- (for ex, broadcast only...), so skip it if: + - a range was given in initial describe + - the command is not a RESUME + */ + if (!(stream->flags & RTP_HAS_RANGE) && (evt->base.type != GF_FEVT_RESUME) ) { + gf_rtsp_range_del(range); + com->Range = NULL; + } else { + com->Range = range; + } + + if (sess->flags & RTSP_AGG_CONTROL) + rtpin_rtsp_skip_command(stream); + else if (strlen(stream->control)) + com->ControlString = gf_strdup(stream->control); + + if (rtpin_rtsp_is_active(stream)) { + if (!com->ControlString && stream->control) com->ControlString = gf_strdup(stream->control); + } else { + if (com->ControlString) { + gf_free(com->ControlString); + com->ControlString=NULL; + } + } + + } else if (evt->base.type == GF_FEVT_PAUSE) { + com->method = gf_strdup(GF_RTSP_PAUSE); + if (stream) { + range = gf_rtsp_range_new(); + /*update current time*/ + stream->current_start += gf_rtp_get_current_time(stream->rtp_ch); + stream->stat_stop_time = gf_sys_clock(); + range->start = stream->current_start; + range->end = -1.0; + com->Range = range; + + if (sess->flags & RTSP_AGG_CONTROL) + rtpin_rtsp_skip_command(stream); + else if (strlen(stream->control)) + com->ControlString = gf_strdup(stream->control); + + stream->paused = GF_TRUE; + } + } + else if (evt->base.type == GF_FEVT_STOP) { + stream->current_start = 0; + stream->stat_stop_time = gf_sys_clock(); + + stream->status = RTP_Connected; + rtpin_stream_init(stream, GF_TRUE); + + /*if server only support aggregation on pause, skip the command or issue + a teardown if last active stream*/ + if (stream->rtsp->flags & RTSP_AGG_ONLY) { + stream->flags &= ~RTP_SKIP_NEXT_COM; + //remove interleaved + if (gf_rtp_is_interleaved(stream->rtp_ch)) { + gf_rtsp_unregister_interleave(stream->rtsp->session, gf_rtp_get_low_interleave_id(stream->rtp_ch)); + } + + if (com) gf_rtsp_command_del(com); + if (!rtpin_rtsp_is_active(stream)) + rtpin_rtsp_teardown(sess, stream); + return; + } + /* otherwise send a PAUSE on the stream */ + else { + if (stream->paused) { + if (com) gf_rtsp_command_del(com); + return; + } + range = gf_rtsp_range_new(); + range->start = 0; + range->end = -1; + com->method = gf_strdup(GF_RTSP_PAUSE); + com->Range = range; + /*only pause the specified stream*/ + if (stream->control) com->ControlString = gf_strdup(stream->control); + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTSP] Unsupported command %s\n", gf_filter_event_name(evt->base.type) )); + gf_rtsp_command_del(com); + return; + } + + ch_ctrl = (RTPIn_StreamControl *)gf_malloc(sizeof(RTPIn_StreamControl)); + ch_ctrl->stream = stream; + memcpy(&ch_ctrl->evt, evt, sizeof(GF_FilterEvent)); + com->user_data = ch_ctrl; + + rtpin_rtsp_queue_command(sess, stream, com, GF_TRUE); + return; +} + + +/* + session/channel teardown functions + */ +void rtpin_rtsp_teardown_process(GF_RTPInRTSP *sess, GF_RTSPCommand *com, GF_Err e) +{ + GF_RTPInStream *stream = (GF_RTPInStream *)com->user_data; + if (stream) { + if (stream->session_id) gf_free(stream->session_id); + stream->session_id = NULL; + } else { + if (sess->session_id) gf_free(sess->session_id); + sess->session_id = NULL; + } +} + +void rtpin_rtsp_teardown(GF_RTPInRTSP *sess, GF_RTPInStream *stream) +{ + GF_RTSPCommand *com; + + /*we need a session id*/ + if (!sess->session_id) return; + /*ignore teardown on channels*/ + if ((sess->flags & RTSP_AGG_CONTROL) && stream) return; + + com = gf_rtsp_command_new(); + com->method = gf_strdup(GF_RTSP_TEARDOWN); + /*this only works in RTSP2*/ + if (stream && stream->control) { + com->ControlString = gf_strdup(stream->control); + com->user_data = stream; + } + + rtpin_rtsp_queue_command(sess, stream, com, GF_TRUE); +} + +#endif /*GPAC_DISABLE_STREAMING*/ diff --git a/src/filters/in_rtp_stream.c b/src/filters/in_rtp_stream.c new file mode 100644 index 0000000..2c13f3c --- /dev/null +++ b/src/filters/in_rtp_stream.c @@ -0,0 +1,766 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / RTP/RTSP input filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "in_rtp.h" + + +#ifndef GPAC_DISABLE_STREAMING + +void rtpin_stream_ack_connect(GF_RTPInStream *stream, GF_Err e) +{ + /*in case the channel has been disconnected while SETUP was issued&processed. We also could + clean up the command stack*/ + if (!stream->opid) return; + + if (e != GF_OK || !stream->rtp_ch) return; +} + +GF_Err rtpin_stream_init(GF_RTPInStream *stream, Bool ResetOnly) +{ + gf_rtp_depacketizer_reset(stream->depacketizer, !ResetOnly); + + if (!ResetOnly) { + GF_Err e; + const char *ip_ifce = NULL; + if (!stream->rtpin->interleave) { + ip_ifce = stream->rtpin->ifce; + } + if (stream->rtp_ch->rtp) + gf_sk_group_unregister(stream->rtpin->sockgroup, stream->rtp_ch->rtp); + if (stream->rtp_ch->rtcp) + gf_sk_group_unregister(stream->rtpin->sockgroup, stream->rtp_ch->rtcp); + + e = gf_rtp_initialize(stream->rtp_ch, stream->rtpin->block_size, GF_FALSE, 0, stream->rtpin->reorder_len, stream->rtpin->reorder_delay, (char *)ip_ifce); + if (e) return e; + + if (stream->rtp_ch->rtp) + gf_sk_group_register(stream->rtpin->sockgroup, stream->rtp_ch->rtp); + if (stream->rtp_ch->rtcp) + gf_sk_group_register(stream->rtpin->sockgroup, stream->rtp_ch->rtcp); + + } + //just reset the sockets + gf_rtp_reset_buffers(stream->rtp_ch); + return GF_OK; +} + +void rtpin_stream_reset_queue(GF_RTPInStream *stream) +{ + if (!stream->pck_queue) return; + while (gf_list_count(stream->pck_queue)) { + GF_FilterPacket *pck = gf_list_pop_back(stream->pck_queue); + gf_filter_pck_discard(pck); + } +} + +void rtpin_stream_del(GF_RTPInStream *stream) +{ + if (stream->rtsp) { + if (stream->status == RTP_Running) { + rtpin_rtsp_teardown(stream->rtsp, stream); + stream->status = RTP_Disconnected; + } + rtpin_remove_stream(stream->rtpin, stream); + } else { + rtpin_find_stream(stream->rtpin, stream->opid, 0, NULL, GF_TRUE); + } + + if (stream->depacketizer) gf_rtp_depacketizer_del(stream->depacketizer); + if (stream->rtp_ch) gf_rtp_del(stream->rtp_ch); + if (stream->control) gf_free(stream->control); + if (stream->session_id) gf_free(stream->session_id); + if (stream->buffer) gf_free(stream->buffer); + if (stream->pck_queue) { + rtpin_stream_reset_queue(stream); + gf_list_del(stream->pck_queue); + } + gf_free(stream); +} + +static void rtpin_stream_queue_pck(GF_RTPInStream *stream, GF_FilterPacket *pck, u64 cts) +{ + //get all packets in queue, dispatch all packets with a cts less than the cts of the packet being queued + //if this is teh first packet in the RTP packet + //This is only used for MPEG4, and we are sure we only have [AU(n),AU(n+i)..], [AU(n+j), AU(n+k) ...] + //with i,j,k >0 + while (stream->first_in_rtp_pck) { + u64 prev_cts; + GF_FilterPacket *prev = gf_list_get(stream->pck_queue, 0); + if (!prev) break; + prev_cts = gf_filter_pck_get_cts(prev); + if (prev_cts>cts) break; + gf_list_rem(stream->pck_queue, 0); + gf_filter_pck_send(prev); + } + stream->first_in_rtp_pck = GF_FALSE; + gf_list_add(stream->pck_queue, pck); +} + +static void rtp_sl_packet_cbk(void *udta, u8 *payload, u32 size, GF_SLHeader *hdr, GF_Err e) +{ + u64 cts, dts; + s64 diff; + GF_FilterPacket *pck; + u8 *pck_data; + GF_RTPInStream *stream = (GF_RTPInStream *)udta; + + + if (!stream->rtcp_init) { + if (!stream->rtcp_check_start) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] No RTCP packet received yet, synchronization might be wrong for a while\n")); + stream->rtcp_check_start = gf_sys_clock(); + } + else if (gf_sys_clock() - stream->rtcp_check_start <= stream->rtpin->rtcp_timeout) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] No RTCP packet received yet, synchronization might be wrong for a while\n")); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTP] Timeout for RTCP: no SR recevied after %d ms - sync may be broken\n", stream->rtpin->rtcp_timeout)); + stream->rtcp_init = GF_TRUE; + } + } + + if (stream->rtpin->first_packet_drop && (hdr->packetSequenceNumber >= stream->rtpin->first_packet_drop) ) { + if (! ( (hdr->packetSequenceNumber - stream->rtpin->first_packet_drop) % stream->rtpin->frequency_drop) ) + return; + } + + cts = hdr->compositionTimeStamp; + dts = hdr->decodingTimeStamp; + + hdr->seekFlag = 0; + hdr->compositionTimeStamp += stream->ts_adjust; + if (stream->first_rtp_ts) { + assert(hdr->compositionTimeStamp >= stream->first_rtp_ts - 1); + hdr->compositionTimeStamp -= stream->first_rtp_ts - 1; + } + + if (hdr->decodingTimeStamp) { + hdr->decodingTimeStamp += stream->ts_adjust; + if (stream->first_rtp_ts) { + assert(hdr->decodingTimeStamp >= stream->first_rtp_ts - 1); + hdr->decodingTimeStamp -= stream->first_rtp_ts - 1; + } + } + + pck = gf_filter_pck_new_alloc(stream->opid, size, &pck_data); + memcpy(pck_data, payload, size); + if (hdr->decodingTimeStampFlag) + gf_filter_pck_set_dts(pck, hdr->decodingTimeStamp); + + gf_filter_pck_set_cts(pck, hdr->compositionTimeStamp); + + diff = (s64) hdr->compositionTimeStamp - (s64) stream->prev_cts; + if ((stream->rtpin->max_sleep>0) && stream->prev_cts && diff) { + if (diff<0) diff = -diff; + if (!stream->min_dur_rtp || (diff < stream->min_dur_rtp)) { + u64 dur = diff; + dur *= 1000; + dur /= stream->rtp_ch->TimeScale; + stream->min_dur_rtp = (u32) dur; + if (dur > stream->rtpin->max_sleep) dur = stream->rtpin->max_sleep; + if (!stream->rtpin->min_frame_dur_ms || ( (u32) dur < stream->rtpin->min_frame_dur_ms)) { + stream->rtpin->min_frame_dur_ms = (u32) dur; + } + } + } + stream->prev_cts = (u32) hdr->compositionTimeStamp; + + gf_filter_pck_set_sap(pck, hdr->randomAccessPointFlag ? GF_FILTER_SAP_1 :GF_FILTER_SAP_NONE); + if (hdr->au_duration) + gf_filter_pck_set_duration(pck, hdr->au_duration); + + if (hdr->samplerate && (hdr->samplerate != stream->sr)) { + stream->sr = hdr->samplerate; + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(stream->sr)); + } + if (hdr->channels && (hdr->channels != stream->nb_ch)) { + stream->nb_ch = hdr->channels; + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(stream->nb_ch)); + } + gf_filter_pck_set_framing(pck, hdr->accessUnitStartFlag, hdr->accessUnitEndFlag); + + if (stream->rtp_ch->packet_loss) + gf_filter_pck_set_corrupted(pck, 1); + +#if 0 //not yet implemented + if (hdr->seekFlag) + gf_filter_pck_set_seek_flag(pck, GF_TRUE); +#endif + + if (stream->depacketizer->sl_map.IndexDeltaLength) { + rtpin_stream_queue_pck(stream, pck, hdr->compositionTimeStamp); + } else { + gf_filter_pck_send(pck); + } + + hdr->compositionTimeStamp = cts; + hdr->decodingTimeStamp = dts; +} + +GF_RTPInStream *rtpin_stream_new_satip(GF_RTPIn *rtp, const char *server_ip) +{ + GF_RTSPTransport trans; + GF_RTPInStream *tmp; + GF_SAFEALLOC(tmp, GF_RTPInStream); + if (!tmp) return NULL; + tmp->rtpin = rtp; + tmp->buffer = gf_malloc(sizeof(char) * rtp->block_size); + + /*create an RTP channel*/ + tmp->rtp_ch = gf_rtp_new(); + tmp->control = gf_strdup("*"); + + memset(&trans, 0, sizeof(GF_RTSPTransport)); + trans.Profile = "RTP/AVP"; + trans.source = (char *) server_ip; + trans.IsUnicast = GF_TRUE; + trans.client_port_first = 0; + trans.client_port_last = 0; + trans.port_first = 0; + trans.port_last = 0; + + if (gf_rtp_setup_transport(tmp->rtp_ch, &trans, NULL) != GF_OK) { + rtpin_stream_del(tmp); + return NULL; + } + + gf_rtp_setup_payload(tmp->rtp_ch, 33, 90000); + + if (rtp->disable_rtcp) tmp->flags |= RTP_ENABLE_RTCP; + + /*setup NAT keep-alive*/ + gf_rtp_enable_nat_keepalive(tmp->rtp_ch, rtp->nat_keepalive ? rtp->nat_keepalive : 30000); + + tmp->range_start = 0; + tmp->range_end = 0; + + return tmp; +} + +GF_RTPInStream *rtpin_stream_new_standalone(GF_RTPIn *rtp, const char *flow_ip, u32 port) +{ + GF_RTSPTransport trans; + GF_RTPInStream *tmp; + GF_SAFEALLOC(tmp, GF_RTPInStream); + if (!tmp) return NULL; + tmp->rtpin = rtp; + tmp->buffer = gf_malloc(sizeof(char) * rtp->block_size); + + /*create an RTP channel*/ + tmp->rtp_ch = gf_rtp_new(); + + memset(&trans, 0, sizeof(GF_RTSPTransport)); + trans.Profile = "RTP/AVP"; + trans.source = (char *) flow_ip; + trans.IsUnicast = gf_sk_is_multicast_address(flow_ip); + trans.client_port_first = port; + trans.client_port_last = port+1; + trans.port_first = port; + trans.port_last = port+1; + + if (gf_rtp_setup_transport(tmp->rtp_ch, &trans, NULL) != GF_OK) { + rtpin_stream_del(tmp); + return NULL; + } + +// gf_rtp_setup_payload(tmp->rtp_ch, 33, 90000); + + if (rtp->disable_rtcp) tmp->flags |= RTP_ENABLE_RTCP; + + /*setup NAT keep-alive*/ + gf_rtp_enable_nat_keepalive(tmp->rtp_ch, rtp->nat_keepalive ? rtp->nat_keepalive : 30000); + tmp->range_start = 0; + tmp->range_end = 0; + return tmp; +} + +GF_RTPInStream *rtpin_stream_new(GF_RTPIn *rtp, GF_SDPMedia *media, GF_SDPInfo *sdp, GF_RTPInStream *input_stream) +{ + GF_RTSPRange *range; + GF_RTPInStream *tmp; + GF_RTPMap *map; + u32 i, ESID, ODID; + Bool force_bcast = GF_FALSE; + Double Start, End; + u16 rvc_predef = 0; + char *rvc_config_att = NULL; + GF_X_Attribute *att; + char *ctrl; + GF_SDPConnection *conn; + GF_RTSPTransport trans; + u32 mid, prev_stream, base_stream; + + //extract all relevant info from the GF_SDPMedia + Start = 0.0; + End = -1.0; + ODID = 0; + ESID = 0; + ctrl = NULL; + range = NULL; + mid = prev_stream = base_stream = 0; + i=0; + while ((att = (GF_X_Attribute*)gf_list_enum(media->Attributes, &i))) { + if (!stricmp(att->Name, "control")) ctrl = att->Value; + else if (!stricmp(att->Name, "gpac-broadcast")) force_bcast = GF_TRUE; + else if (!stricmp(att->Name, "mpeg4-esid") && att->Value) ESID = atoi(att->Value); + else if (!stricmp(att->Name, "mpeg4-odid") && att->Value) ODID = atoi(att->Value); + else if (!stricmp(att->Name, "range") && !range) range = gf_rtsp_range_parse(att->Value); + else if (!stricmp(att->Name, "rvc-config-predef") && att->Value) { + rvc_predef = atoi(att->Value); + } else if (!stricmp(att->Name, "rvc-config")) { + rvc_config_att = att->Value; + } else if (!stricmp(att->Name, "mid")) { + sscanf(att->Value, "L%d", &mid); + } else if (!stricmp(att->Name, "depend")) { + char buf[3000]; + memset(buf, 0, 3000); + sscanf(att->Value, "%*d lay L%d %*s %2999s", &base_stream, buf); + if (!strlen(buf)) + sscanf(att->Value, "%*d lay %2999s", buf); + sscanf(buf, "L%d", &prev_stream); + } + } + + if (range) { + Start = range->start; + End = range->end; + gf_rtsp_range_del(range); + } + + /*check connection*/ + conn = sdp->c_connection; + if (conn && (!conn->host || !strcmp(conn->host, "0.0.0.0"))) conn = NULL; + + if (!conn) conn = (GF_SDPConnection*)gf_list_get(media->Connections, 0); + if (conn && (!conn->host || !strcmp(conn->host, "0.0.0.0"))) conn = NULL; + + if (!conn) { + /*RTSP RFC recommends an empty "c= " line but some server don't send it. Use session info (o=)*/ + if (!sdp->o_net_type || !sdp->o_add_type || strcmp(sdp->o_net_type, "IN")) return NULL; + if (strcmp(sdp->o_add_type, "IP4") && strcmp(sdp->o_add_type, "IP6")) return NULL; + } else { + if (strcmp(conn->net_type, "IN")) return NULL; + if (strcmp(conn->add_type, "IP4") && strcmp(conn->add_type, "IP6")) return NULL; + } + /*do we support transport*/ + if (strcmp(media->Profile, "RTP/AVP") && strcmp(media->Profile, "RTP/AVP/TCP") + && strcmp(media->Profile, "RTP/SAVP") && strcmp(media->Profile, "RTP/SAVP/TCP") + ) return NULL; + + /*check RTP map. For now we only support 1 RTPMap*/ + if (gf_list_count(media->RTPMaps) > 1) return NULL; + + /*check payload type*/ + map = (GF_RTPMap*)gf_list_get(media->RTPMaps, 0); + if (!map) { + if (!media->fmt_list) return NULL; + if (strchr(media->fmt_list, ' ')) return NULL; + } + + /*this is an ESD-URL setup, we likely have namespace conflicts so overwrite given ES_ID + by the app one (client side), but keep control (server side) if provided*/ + if (input_stream) { + ESID = input_stream->ES_ID; + if (!ctrl) ctrl = input_stream->control; + tmp = input_stream; + } else { + tmp = rtpin_find_stream(rtp, NULL, ESID, NULL, GF_FALSE); + if (tmp) return NULL; + + GF_SAFEALLOC(tmp, GF_RTPInStream); + if (!tmp) return NULL; + tmp->rtpin = rtp; + tmp->buffer = gf_malloc(sizeof(char) * rtp->block_size); + } + + /*create an RTP channel*/ + tmp->rtp_ch = gf_rtp_new(); + if (ctrl) tmp->control = gf_strdup(ctrl); + tmp->ES_ID = ESID; + tmp->OD_ID = ODID; + tmp->mid = mid; + tmp->prev_stream = prev_stream; + tmp->base_stream = base_stream; + + memset(&trans, 0, sizeof(GF_RTSPTransport)); + trans.Profile = media->Profile; + trans.source = conn ? conn->host : sdp->o_address; + trans.IsUnicast = gf_sk_is_multicast_address(trans.source) ? GF_FALSE : GF_TRUE; + if (!trans.IsUnicast) { + trans.port_first = media->PortNumber; + trans.port_last = media->PortNumber + 1; + trans.TTL = conn ? conn->TTL : 0; + } else { + trans.client_port_first = media->PortNumber; + trans.client_port_last = media->PortNumber + 1; + trans.port_first = trans.client_port_first; + trans.port_last = trans.client_port_last; + } + + if (gf_rtp_setup_transport(tmp->rtp_ch, &trans, NULL) != GF_OK) { + rtpin_stream_del(tmp); + return NULL; + } + /*setup depacketizer*/ + tmp->depacketizer = gf_rtp_depacketizer_new(media, 0, rtp_sl_packet_cbk, tmp); + if (!tmp->depacketizer) { + GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTP] Failed to create RTP depacketizer for payload type %d/%s - ignoring stream)\n", map ? map->PayloadType : 0, media->fmt_list ? media->fmt_list : "")); + rtpin_stream_del(tmp); + return NULL; + } + /*setup channel*/ + gf_rtp_setup_payload(tmp->rtp_ch, map ? map->PayloadType : tmp->depacketizer->payt, tmp->depacketizer->clock_rate); + + if (tmp->depacketizer->sl_map.IndexDeltaLength) { + tmp->pck_queue = gf_list_new(); + } + +// tmp->status = NM_Disconnected; + + if (!rtp->disable_rtcp) tmp->flags |= RTP_ENABLE_RTCP; + + /*setup NAT keep-alive*/ + if (rtp->nat_keepalive) gf_rtp_enable_nat_keepalive(tmp->rtp_ch, rtp->nat_keepalive); + + tmp->range_start = Start; + tmp->range_end = End; + if (End != -1.0) tmp->flags |= RTP_HAS_RANGE; + + if (force_bcast) tmp->flags |= RTP_FORCE_BROADCAST; + + if (rvc_predef) { + tmp->depacketizer->sl_map.rvc_predef = rvc_predef ; + } else if (rvc_config_att) { + char *rvc_data=NULL; + u32 rvc_size=0; + Bool is_gz = GF_FALSE; + if (!strncmp(rvc_config_att, "data:application/rvc-config+xml", 32) && strstr(rvc_config_att, "base64") ) { + char *data = strchr(rvc_config_att, ','); + if (data) { + rvc_size = (u32) strlen(data) * 3 / 4 + 1; + rvc_data = (char*)gf_malloc(sizeof(char) * rvc_size); + rvc_size = gf_base64_decode(data, (u32) strlen(data), rvc_data, rvc_size); + rvc_data[rvc_size] = 0; + } + if (!strncmp(rvc_config_att, "data:application/rvc-config+xml+gz", 35)) is_gz = GF_TRUE; + } else if (!strnicmp(rvc_config_att, "http://", 7) || !strnicmp(rvc_config_att, "https://", 8) ) { + return NULL; + } + if (rvc_data) { + if (is_gz) { +#ifdef GPAC_DISABLE_ZLIB + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("Error: no zlib support - RVC not supported in RTP\n")); + gf_free(rvc_data); + return NULL; +#else + gf_gz_decompress_payload(rvc_data, rvc_size, &tmp->depacketizer->sl_map.rvc_config, &tmp->depacketizer->sl_map.rvc_config_size); + gf_free(rvc_data); +#endif + } else { + tmp->depacketizer->sl_map.rvc_config = rvc_data; + tmp->depacketizer->sl_map.rvc_config_size = rvc_size; + } + } + } + + return tmp; +} + +static void rtpin_stream_update_stats(GF_RTPInStream *stream) +{ + u32 time; + Float bps; + if (!stream->rtp_ch) return; + + gf_filter_pid_set_info_str(stream->opid, "nets:loss", &PROP_FLOAT( gf_rtp_get_loss(stream->rtp_ch) ) ); + if (stream->rtsp && (stream->flags & RTP_INTERLEAVED)) { + gf_filter_pid_set_info_str(stream->opid, "nets:interleaved", &PROP_UINT( gf_rtsp_get_session_port(stream->rtsp->session) ) ); + gf_filter_pid_set_info_str(stream->opid, "nets:rtpid", &PROP_UINT( gf_rtp_get_low_interleave_id(stream->rtp_ch) ) ); + gf_filter_pid_set_info_str(stream->opid, "nets:rctpid", &PROP_UINT( gf_rtp_get_hight_interleave_id(stream->rtp_ch) ) ); + + } else { + gf_filter_pid_set_info_str(stream->opid, "nets:rtpp", &PROP_UINT( stream->rtp_ch->net_info.client_port_first ) ); + gf_filter_pid_set_info_str(stream->opid, "nets:rtcpp", &PROP_UINT( stream->rtp_ch->net_info.client_port_last ) ); + } + if (stream->stat_stop_time) { + time = stream->stat_stop_time - stream->stat_start_time; + } else { + time = gf_sys_clock() - stream->stat_start_time; + } + if (!time) return; + + bps = 8.0f * stream->rtp_bytes; + bps *= 1000; + bps /= time; + gf_filter_pid_set_info_str(stream->opid, "nets:bw_down", &PROP_UINT( (u32) bps ) ); + + bps = 8.0f * stream->rtcp_bytes; + bps *= 1000; + bps /= time; + gf_filter_pid_set_info_str(stream->opid, "nets:ctrl_bw_down", &PROP_UINT( (u32) bps ) ); + + bps = 8.0f * gf_rtp_get_tcp_bytes_sent(stream->rtp_ch); + bps *= 1000; + bps /= time; + gf_filter_pid_set_info_str(stream->opid, "nets:ctrl_bw_up", &PROP_UINT( (u32) bps ) ); +} + + +void rtpin_stream_on_rtp_pck(GF_RTPInStream *stream, char *pck, u32 size) +{ + GF_Err e; + GF_RTPHeader hdr; + u32 PayloadStart; + stream->rtp_bytes += size; + stream->first_in_rtp_pck = GF_TRUE; + + /*first decode RTP*/ + e = gf_rtp_decode_rtp(stream->rtp_ch, pck, size, &hdr, &PayloadStart); + + /*corrupted or NULL data*/ + if (e || (PayloadStart >= size)) { + //gf_service_send_packet(stream->rtpin->service, stream->opid, NULL, 0, NULL, GF_CORRUPTED_DATA); + return; + } + if (!stream->opid && !stream->depacketizer) { + stream->depacketizer = gf_rtp_depacketizer_new(NULL, hdr.PayloadType, rtp_sl_packet_cbk, stream); + if (!stream->depacketizer) { + return; + } + stream->rtp_ch->PayloadType = hdr.PayloadType; + stream->rtp_ch->TimeScale = stream->depacketizer->clock_rate; + rtpin_declare_pid(stream, GF_FALSE, 0, NULL); + } + if (!stream->depacketizer) { + return; + } + + /*first we only work with one payload type...*/ + if (hdr.PayloadType != stream->rtp_ch->PayloadType) { + GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTP] Mismatched in packet payload type %d vs channel payload type %d, only one payload type per channel is currently supported\n", hdr.PayloadType, stream->rtp_ch->PayloadType)); + } + + //pure RTP, remap all streams to 0, adjust sync later + if (!stream->rtsp) { + if (!stream->first_rtp_ts || (hdr.TimeStamp < stream->first_rtp_ts-1) ) + stream->first_rtp_ts = 1 + hdr.TimeStamp; + } + + /*if we must notify some timing, do it now. */ + if (stream->check_rtp_time) { + Double ch_time; + + /*it may happen that we still receive packets from a previous "play" request. If this is the case, + filter until we reach the indicated rtptime*/ + if (stream->rtp_ch->rtp_time + && (stream->rtp_ch->rtp_first_SN > hdr.SequenceNumber) + && (stream->rtp_ch->rtp_time > hdr.TimeStamp) + ) { + GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTP] Rejecting too early packet (TS %d vs signaled rtp time %d - diff %d ms)\n", + hdr.TimeStamp, stream->rtp_ch->rtp_time, ((hdr.TimeStamp - stream->rtp_ch->rtp_time)*1000) / stream->rtp_ch->TimeScale)); + return; + } + + ch_time = gf_rtp_get_current_time(stream->rtp_ch); + + /*this is the first packet on the channel (no PAUSE)*/ + if (stream->check_rtp_time == RTP_SET_TIME_RTP) { + Double media_time = 0; + if (stream->rtsp) { + media_time = stream->current_start + ch_time; + } + gf_filter_pid_set_property_str(stream->opid, "time:timestamp", &PROP_LONGUINT(hdr.TimeStamp) ); + gf_filter_pid_set_property_str(stream->opid, "time:media", &PROP_DOUBLE(media_time) ); + + GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTP] Mapping RTP Time seq %d TS %d Media Time %g - rtp info seq %d TS %d\n", + hdr.SequenceNumber, hdr.TimeStamp, stream->current_start + ch_time, stream->rtp_ch->rtp_first_SN, stream->rtp_ch->rtp_time + )); + + /*skip RTCP clock init when RTSP is used*/ + if (stream->rtsp) stream->rtcp_init = GF_TRUE; + + if (stream->depacketizer->payt==GF_RTP_PAYT_H264_AVC) stream->depacketizer->flags |= GF_RTP_AVC_WAIT_RAP; + } + /*this is RESUME on channel, filter packet based on time (darwin seems to send + couple of packet before) + do not fetch if we're below 10 ms or <0, because this means we already have + this packet - as the PAUSE is issued with the RTP currentTime*/ + else if (ch_time <= 0.021) { + return; + } + stream->check_rtp_time = RTP_SET_TIME_NONE; + } + + gf_rtp_depacketizer_process(stream->depacketizer, &hdr, pck + PayloadStart, size - PayloadStart); + + /*last check: signal EOS if we're close to end range in case the server do not send RTCP BYE*/ + if ((stream->flags & RTP_HAS_RANGE) && !(stream->flags & RTP_EOS) ) { + /*also check last CTS*/ + Double ts = (Double) ((u32) stream->depacketizer->sl_hdr.compositionTimeStamp - hdr.TimeStamp); + ts /= gf_rtp_get_clockrate(stream->rtp_ch); + if (ABSDIFF(stream->range_end, (ts + stream->current_start + gf_rtp_get_current_time(stream->rtp_ch)) ) < 0.2) { + stream->flags |= RTP_EOS; + stream->stat_stop_time = gf_sys_clock(); + gf_filter_pid_set_eos(stream->opid); + } + } + + if (stream->rtpin->stats) { + u32 now = gf_sys_clock(); + if (stream->last_stats_time + stream->rtpin->stats < now) { + stream->last_stats_time = now; + rtpin_stream_update_stats(stream); + } + } +} + +static void rtpin_adjust_sync(GF_RTPIn *ctx) +{ + u32 i, count = gf_list_count(ctx->streams); + u64 max_ntp_us = 0; + + for (i=0; istreams, i); + if (!stream->rtcp_init) return; + + if (max_ntp_us < stream->init_ntp_us) { + max_ntp_us = stream->init_ntp_us; + } + } + + for (i=0; istreams, i); + + ntp_diff_us = max_ntp_us; + ntp_diff_us -= stream->init_ntp_us; + ntp_diff_us *= stream->rtp_ch->TimeScale; + ntp_diff_us /= 1000000; + stream->ts_adjust = ntp_diff_us; + } +} + +static void rtpin_stream_on_rtcp_pck(GF_RTPInStream *stream, char *pck, u32 size) +{ + Bool has_sr; + GF_Err e; + + if (stream->status == RTP_Connected) return; + //not configured yes + if (!stream->rtp_ch->TimeScale) return; + stream->rtcp_bytes += size; + + e = gf_rtp_decode_rtcp(stream->rtp_ch, pck, size, &has_sr); + if (e<0) return; + + /*update sync if on pure RTP*/ + if (!stream->rtcp_init && has_sr) { + u64 ntp_clock_us, rtp_diff_us; + + ntp_clock_us = stream->rtp_ch->last_SR_NTP_frac; + ntp_clock_us *= 1000000; + ntp_clock_us /= 0xFFFFFFFF; + ntp_clock_us += 1000000 * (u64) stream->rtp_ch->last_SR_NTP_sec; + + //ntp time at origin: ntp(now) - ntp(first_pck) = rtpts(now) - rtpts(orig) -> ntp_first = ntp - rtp_diff + rtp_diff_us = stream->rtp_ch->last_SR_rtp_time - (stream->first_rtp_ts - 1); + rtp_diff_us *= 1000000; + rtp_diff_us /= stream->rtp_ch->TimeScale; + + stream->init_ntp_us = ntp_clock_us - rtp_diff_us; + + + GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTCP] At %d Using Sender Report to map RTP TS %d to NTP clock "LLU" us - NTP origin "LLU"\n", gf_sys_clock(), stream->rtp_ch->last_SR_rtp_time, ntp_clock_us, stream->init_ntp_us)); + + stream->rtcp_init = GF_TRUE; + + if (stream->rtpin->rtcpsync) + rtpin_adjust_sync(stream->rtpin); + } + + if (e == GF_EOS) { + stream->flags |= RTP_EOS; + } +} + +GF_Err rtpin_rtsp_data_cbk(GF_RTSPSession *sess, void *cbk, u8 *buffer, u32 bufferSize, Bool IsRTCP) +{ + GF_RTPInStream *stream = (GF_RTPInStream *) cbk; + if (!stream || stream->rtpin->done) return GF_OK; + if (IsRTCP) { + rtpin_stream_on_rtcp_pck(stream, buffer, bufferSize); + } else { + rtpin_stream_on_rtp_pck(stream, buffer, bufferSize); + } + return GF_OK; +} + + + +u32 rtpin_stream_read(GF_RTPInStream *stream) +{ + u32 size, tot_size = 0; + + if (!stream->rtp_ch) return 0; + if (gf_sk_group_sock_is_set(stream->rtpin->sockgroup, stream->rtp_ch->rtcp, GF_SK_SELECT_READ)) { + size = gf_rtp_read_rtcp(stream->rtp_ch, stream->buffer, stream->rtpin->block_size); + if (size) { + tot_size += size; + rtpin_stream_on_rtcp_pck(stream, stream->buffer, size); + } + } + + if (gf_sk_group_sock_is_set(stream->rtpin->sockgroup, stream->rtp_ch->rtp, GF_SK_SELECT_READ)) { + size = gf_rtp_read_rtp(stream->rtp_ch, stream->buffer, stream->rtpin->block_size); + if (size) { + tot_size += size; + stream->rtpin->udp_timeout = 0; + rtpin_stream_on_rtp_pck(stream, stream->buffer, size); + } + } + if (!tot_size) return 0; + + /*and send the report*/ + if (stream->flags & RTP_ENABLE_RTCP) gf_rtp_send_rtcp_report(stream->rtp_ch); + + /*detect timeout*/ + if (stream->rtpin->udp_timeout) { + if (!stream->last_udp_time) { + stream->last_udp_time = gf_sys_clock(); + } else if (stream->rtp_ch->net_info.IsUnicast) { + u32 diff = gf_sys_clock() - stream->last_udp_time; + if (diff >= stream->rtpin->udp_timeout) { + char szMessage[1024]; + GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTP] UDP Timeout after %d ms\n", diff)); + sprintf(szMessage, "No data received in %d ms%s", diff, stream->rtpin->autortsp ? ", retrying using TCP" : ""); + rtpin_send_message(stream->rtpin, GF_IP_UDP_TIMEOUT, szMessage); + if (stream->rtpin->autortsp) + stream->rtpin->retry_tcp = GF_TRUE; + } + } + } + return tot_size; +} + +#endif /*GPAC_DISABLE_STREAMING*/ diff --git a/src/filters/in_sock.c b/src/filters/in_sock.c new file mode 100644 index 0000000..a2b19db --- /dev/null +++ b/src/filters/in_sock.c @@ -0,0 +1,543 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2019 + * All rights reserved + * + * This file is part of GPAC / generic TCP/UDP input filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include +#include + +#ifndef GPAC_DISABLE_STREAMING +#include +#endif + +typedef struct +{ + GF_FilterPid *pid; + GF_Socket *socket; + Bool pck_out; +#ifndef GPAC_DISABLE_STREAMING + GF_RTPReorder *rtp_reorder; +#else + Bool is_rtp; +#endif + char address[GF_MAX_IP_NAME_LEN]; + + u64 start_time; + u64 nb_bytes; + Bool done; + +} GF_SockInClient; + +typedef struct +{ + //options + const char *src; + u32 block_size, sockbuf; + u32 port, maxc; + char *ifce; + const char *ext; + const char *mime; + Bool tsprobe, listen, ka, block; + u32 timeout; +#ifndef GPAC_DISABLE_STREAMING + u32 reorder_pck; + u32 reorder_delay; +#endif + + GF_SockInClient sock_c; + GF_List *clients; + Bool had_clients; + Bool is_udp; + + char *buffer; + + GF_SockGroup *active_sockets; + u64 last_rcv_time; +} GF_SockInCtx; + + + +static GF_Err sockin_initialize(GF_Filter *filter) +{ + char *str, *url; + u16 port; + u32 sock_type = 0; + GF_Err e = GF_OK; + GF_SockInCtx *ctx = (GF_SockInCtx *) gf_filter_get_udta(filter); + + if (!ctx || !ctx->src) return GF_BAD_PARAM; + + ctx->active_sockets = gf_sk_group_new(); + if (!ctx->active_sockets) return GF_OUT_OF_MEM; + + if (!strnicmp(ctx->src, "udp://", 6)) { + sock_type = GF_SOCK_TYPE_UDP; + ctx->listen = GF_FALSE; + ctx->is_udp = GF_TRUE; + } else if (!strnicmp(ctx->src, "tcp://", 6)) { + sock_type = GF_SOCK_TYPE_TCP; +#ifdef GPAC_HAS_SOCK_UN + } else if (!strnicmp(ctx->src, "tcpu://", 7) ) { + sock_type = GF_SOCK_TYPE_TCP_UN; + } else if (!strnicmp(ctx->src, "udpu://", 7) ) { + sock_type = GF_SOCK_TYPE_UDP_UN; + ctx->listen = GF_FALSE; +#endif + } else { + return GF_NOT_SUPPORTED; + } + + url = strchr(ctx->src, ':'); + url += 3; + + ctx->sock_c.socket = gf_sk_new(sock_type); + if (! ctx->sock_c.socket ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[SockIn] Failed to open socket for %s\n", ctx->src)); + return GF_IO_ERR; + } + gf_sk_group_register(ctx->active_sockets, ctx->sock_c.socket); + + /*setup port and src*/ + port = ctx->port; + str = strrchr(url, ':'); + /*take care of IPv6 address*/ + if (str && strchr(str, ']')) str = strchr(url, ':'); + if (str) { + port = atoi(str+1); + str[0] = 0; + } + + /*do we have a source ?*/ + if (gf_sk_is_multicast_address(url)) { + e = gf_sk_setup_multicast(ctx->sock_c.socket, url, port, 0, 0, ctx->ifce); + ctx->listen = GF_FALSE; + } else if ((sock_type==GF_SOCK_TYPE_UDP) +#ifdef GPAC_HAS_SOCK_UN + || (sock_type==GF_SOCK_TYPE_UDP_UN) +#endif + ) { + e = gf_sk_bind(ctx->sock_c.socket, ctx->ifce, port, url, port, GF_SOCK_REUSE_PORT); + ctx->listen = GF_FALSE; + if (!e) + e = gf_sk_connect(ctx->sock_c.socket, url, port, NULL); + } else if (ctx->listen) { + e = gf_sk_bind(ctx->sock_c.socket, NULL, port, url, 0, GF_SOCK_REUSE_PORT); + if (!e) + e = gf_sk_listen(ctx->sock_c.socket, ctx->maxc); + if (!e) { + gf_filter_post_process_task(filter); + gf_sk_server_mode(ctx->sock_c.socket, GF_TRUE); + } + + } else { + e = gf_sk_connect(ctx->sock_c.socket, url, port, ctx->ifce); + } + + if (str) str[0] = ':'; + + if (e) { + gf_sk_del(ctx->sock_c.socket); + ctx->sock_c.socket = NULL; + return e; + } + + gf_sk_set_buffer_size(ctx->sock_c.socket, 0, ctx->sockbuf); + gf_sk_set_block_mode(ctx->sock_c.socket, !ctx->block); + + + GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[SockIn] opening %s%s\n", ctx->src, ctx->listen ? " in server mode" : "")); + + if (ctx->block_size<2000) + ctx->block_size = 2000; + ctx->buffer = gf_malloc(ctx->block_size + 1); + if (!ctx->buffer) return GF_OUT_OF_MEM; + //ext/mime given and not mpeg2, disable probe + if (ctx->ext && !strstr("ts|m2t|mts|dmb|trp", ctx->ext)) ctx->tsprobe = GF_FALSE; + if (ctx->mime && !strstr(ctx->mime, "mpeg-2") && !strstr(ctx->mime, "mp2t")) ctx->tsprobe = GF_FALSE; + + if (ctx->listen) { + ctx->clients = gf_list_new(); + if (!ctx->clients) return GF_OUT_OF_MEM; + } + return GF_OK; +} + +static void sockin_client_reset(GF_SockInClient *sc) +{ + if (sc->socket) gf_sk_del(sc->socket); +#ifndef GPAC_DISABLE_STREAMING + if (sc->rtp_reorder) gf_rtp_reorderer_del(sc->rtp_reorder); +#endif +} + +static void sockin_finalize(GF_Filter *filter) +{ + GF_SockInCtx *ctx = (GF_SockInCtx *) gf_filter_get_udta(filter); + + if (ctx->clients) { + while (gf_list_count(ctx->clients)) { + GF_SockInClient *sc = gf_list_pop_back(ctx->clients); + sockin_client_reset(sc); + gf_free(sc); + } + gf_list_del(ctx->clients); + } + sockin_client_reset(&ctx->sock_c); + if (ctx->buffer) gf_free(ctx->buffer); + if (ctx->active_sockets) gf_sk_group_del(ctx->active_sockets); +} + +static GF_FilterProbeScore sockin_probe_url(const char *url, const char *mime_type) +{ + if (!strnicmp(url, "udp://", 6)) return GF_FPROBE_SUPPORTED; + if (!strnicmp(url, "tcp://", 6)) return GF_FPROBE_SUPPORTED; +#ifdef GPAC_HAS_SOCK_UN + if (!strnicmp(url, "udpu://", 7)) return GF_FPROBE_SUPPORTED; + if (!strnicmp(url, "tcpu://", 7)) return GF_FPROBE_SUPPORTED; +#endif + return GF_FPROBE_NOT_SUPPORTED; +} + +#ifndef GPAC_DISABLE_STREAMING +static void sockin_rtp_destructor(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck) +{ + u32 size; + char *data; + GF_SockInClient *sc = (GF_SockInClient *) gf_filter_pid_get_udta(pid); + sc->pck_out = GF_FALSE; + data = (char *) gf_filter_pck_get_data(pck, &size); + if (data) gf_free(data); +} +#endif + +static Bool sockin_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + if (!evt->base.on_pid) return GF_FALSE; + + switch (evt->base.type) { + case GF_FEVT_PLAY: + return GF_TRUE; + case GF_FEVT_STOP: + return GF_TRUE; + default: + break; + } + return GF_FALSE; +} + +static GF_Err sockin_read_client(GF_Filter *filter, GF_SockInCtx *ctx, GF_SockInClient *sock_c) +{ + u32 nb_read; + u64 bitrate; + GF_Err e; + GF_FilterPacket *dst_pck; + u8 *out_data, *in_data; + + if (!sock_c->socket) + return GF_EOS; + if (sock_c->pck_out) + return GF_OK; + + if (sock_c->pid && gf_filter_pid_would_block(sock_c->pid)) { + return GF_OK; + } + + if (!sock_c->start_time) sock_c->start_time = gf_sys_clock_high_res(); + + e = gf_sk_receive_no_select(sock_c->socket, ctx->buffer, ctx->block_size, &nb_read); + switch (e) { + case GF_IP_NETWORK_EMPTY: + return GF_OK; + case GF_OK: + break; + case GF_IP_CONNECTION_CLOSED: + if (!sock_c->done) { + sock_c->done = GF_TRUE; + gf_filter_pid_set_eos(sock_c->pid); + } + return GF_EOS; + default: + return e; + } + if (!nb_read) return GF_OK; + sock_c->nb_bytes += nb_read; + sock_c->done = GF_FALSE; + + //we allocated one more byte for that + ctx->buffer[nb_read] = 0; + + //first run, probe data + if (!sock_c->pid) { + const char *mime = ctx->mime; + //probe MPEG-2 + if (ctx->tsprobe) { + /*TS over RTP signaled as udp */ + if ((ctx->buffer[0] != 0x47) && ((ctx->buffer[1] & 0x7F) == 33) ) { +#ifndef GPAC_DISABLE_STREAMING + sock_c->rtp_reorder = gf_rtp_reorderer_new(ctx->reorder_pck, ctx->reorder_delay); +#else + sock_c->is_rtp = GF_TRUE; +#endif + mime = "video/mp2t"; + } else if (ctx->buffer[0] == 0x47) { + mime = "video/mp2t"; + } + } + + e = gf_filter_pid_raw_new(filter, ctx->src, NULL, mime, ctx->ext, ctx->buffer, nb_read, GF_TRUE, &sock_c->pid); + if (e) return e; + +// if (ctx->is_udp) gf_filter_pid_set_property(sock_c->pid, GF_PROP_PID_UDP, &PROP_BOOL(GF_TRUE) ); + + gf_filter_pid_set_udta(sock_c->pid, sock_c); + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + GF_FilterEvent evt; + memset(&evt, 0, sizeof(GF_FilterEvent)); + evt.base.type = GF_FEVT_PLAY; + evt.base.on_pid = sock_c->pid; + sockin_process_event(filter, &evt); + } +#endif + + } + + in_data = ctx->buffer; + +#ifndef GPAC_DISABLE_STREAMING + if (sock_c->rtp_reorder) { + char *pck; + u16 seq_num = ((ctx->buffer[2] << 8) & 0xFF00) | (ctx->buffer[3] & 0xFF); + gf_rtp_reorderer_add(sock_c->rtp_reorder, (void *) ctx->buffer, nb_read, seq_num); + + pck = (char *) gf_rtp_reorderer_get(sock_c->rtp_reorder, &nb_read, GF_FALSE); + if (pck) { + dst_pck = gf_filter_pck_new_shared(sock_c->pid, pck+12, nb_read-12, sockin_rtp_destructor); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + gf_filter_pck_send(dst_pck); + } + return GF_OK; + } +#else + if (sock_c->is_rtp) { + in_data = ctx->buffer + 12; + nb_read -= 12; + } +#endif + + dst_pck = gf_filter_pck_new_alloc(sock_c->pid, nb_read, &out_data); + memcpy(out_data, in_data, nb_read); + + gf_filter_pck_set_framing(dst_pck, (sock_c->nb_bytes == nb_read) ? GF_TRUE : GF_FALSE, GF_FALSE); + gf_filter_pck_send(dst_pck); + + //send bitrate + bitrate = ( gf_sys_clock_high_res() - sock_c->start_time ); + if (bitrate) { + bitrate = (sock_c->nb_bytes * 8 * 1000000) / bitrate; + gf_filter_pid_set_property(sock_c->pid, GF_PROP_PID_DOWN_RATE, &PROP_UINT((u32) bitrate) ); + GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[SockIn] Receiving from %s at %d kbps\r", sock_c->address, (u32) (bitrate/10))); + } + + return GF_OK; +} + +static Bool sockin_check_eos(GF_SockInCtx *ctx) +{ + u64 now = gf_sys_clock_high_res(); + if (!ctx->last_rcv_time) { + ctx->last_rcv_time = now; + return GF_FALSE; + } + if (now - ctx->last_rcv_time < ctx->timeout*1000) { + return GF_FALSE; + } + if (ctx->sock_c.pid && !ctx->sock_c.done) { + gf_filter_pid_set_eos(ctx->sock_c.pid); + ctx->sock_c.done = GF_TRUE; + } + return GF_TRUE; +} + +static GF_Err sockin_process(GF_Filter *filter) +{ + GF_Socket *new_conn=NULL; + GF_Err e; + u32 i, count; + GF_SockInCtx *ctx = (GF_SockInCtx *) gf_filter_get_udta(filter); + + e = gf_sk_group_select(ctx->active_sockets, 10, GF_SK_SELECT_READ); + if (e==GF_IP_NETWORK_EMPTY) { + if (ctx->is_udp) { + if (sockin_check_eos(ctx) ) + return GF_EOS; + } else if (!gf_list_count(ctx->clients)) { + gf_filter_ask_rt_reschedule(filter, 1000); + return GF_OK; + } + + gf_filter_ask_rt_reschedule(filter, 1000); + return GF_OK; + } + else if (e) return e; + + if (gf_sk_group_sock_is_set(ctx->active_sockets, ctx->sock_c.socket, GF_SK_SELECT_READ)) { + if (!ctx->listen) { + return sockin_read_client(filter, ctx, &ctx->sock_c); + } + + if (gf_sk_group_sock_is_set(ctx->active_sockets, ctx->sock_c.socket, GF_SK_SELECT_READ)) { + e = gf_sk_accept(ctx->sock_c.socket, &new_conn); + if ((e==GF_OK) && new_conn) { + GF_SockInClient *sc; + GF_SAFEALLOC(sc, GF_SockInClient); + if (!sc) return GF_OUT_OF_MEM; + + sc->socket = new_conn; + strcpy(sc->address, "unknown"); + gf_sk_get_remote_address(new_conn, sc->address); + gf_sk_set_block_mode(new_conn, !ctx->block); + + GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[SockIn] Accepting new connection from %s\n", sc->address)); + gf_list_add(ctx->clients, sc); + ctx->had_clients = GF_TRUE; + gf_sk_group_register(ctx->active_sockets, sc->socket); + } + } + } + if (!ctx->listen) return GF_OK; + + count = gf_list_count(ctx->clients); + for (i=0; iclients, i); + + if (!gf_sk_group_sock_is_set(ctx->active_sockets, sc->socket, GF_SK_SELECT_READ)) continue; + + e = sockin_read_client(filter, ctx, sc); + if (e == GF_IP_CONNECTION_CLOSED) { + GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("[SockIn] Connection to %s lost, removing input\n", sc->address)); + if (sc->socket) + gf_sk_group_unregister(ctx->active_sockets, sc->socket); + + sockin_client_reset(sc); + if (sc->pid) { + gf_filter_pid_set_eos(sc->pid); + gf_filter_pid_remove(sc->pid); + } + gf_free(sc); + gf_list_del_item(ctx->clients, sc); + i--; + count--; + } else { + if (e) return e; + } + } + if (!ctx->had_clients) { + //we should use socket groups and selects ! + gf_filter_ask_rt_reschedule(filter, 100000); + return GF_OK; + } + + if (!count) { + if (ctx->ka) { + //keep alive, ask for real-time reschedule of 100 ms - we should use socket groups and selects ! + gf_filter_ask_rt_reschedule(filter, 100000); + } else { + return GF_EOS; + } + } + return GF_OK; +} + + + +#define OFFS(_n) #_n, offsetof(GF_SockInCtx, _n) + +static const GF_FilterArgs SockInArgs[] = +{ + { OFFS(src), "location of source content", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(block_size), "block size used to read socket", GF_PROP_UINT, "10000", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(sockbuf), "socket max buffer size", GF_PROP_UINT, "65536", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(port), "default port if not specified", GF_PROP_UINT, "1234", NULL, 0}, + { OFFS(ifce), "default multicast interface", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(listen), "indicate the input socket works in server mode", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(ka), "keep socket alive if no more connections", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(maxc), "max number of concurrent connections", GF_PROP_UINT, "+I", NULL, 0}, + { OFFS(tsprobe), "probe for MPEG-2 TS data, either RTP or raw UDP. Disabled if mime or ext are given and do not match MPEG-2 TS mimes/extensions", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ext), "indicate file extension of udp data", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(mime), "indicate mime type of udp data", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(block), "set blocking mode for socket(s)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(timeout), "set timeout in ms for UDP socket(s)", GF_PROP_UINT, "5000", NULL, GF_FS_ARG_HINT_ADVANCED}, + +#ifndef GPAC_DISABLE_STREAMING + { OFFS(reorder_pck), "number of packets delay for RTP reordering (M2TS over RTP) ", GF_PROP_UINT, "100", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(reorder_delay), "number of ms delay for RTP reordering (M2TS over RTP)", GF_PROP_UINT, "10", NULL, GF_FS_ARG_HINT_ADVANCED}, +#endif + {0} +}; + +static const GF_FilterCapability SockInCaps[] = +{ + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), +}; + +GF_FilterRegister SockInRegister = { + .name = "sockin", + GF_FS_SET_DESCRIPTION("UDP/TCP input") +#ifndef GPAC_DISABLE_DOC + .help = "This filter handles generic TCP and UDP input sockets. It can also probe for MPEG-2 TS over RTP input. Probing of MPEG-2 TS over UDP/RTP is enabled by default but can be turned off.\n" + "\nData format can be specified by setting either [-ext]() or [-mime]() options. If not set, the format will be guessed by probing the first data packet\n" + "\n" + "- UDP sockets are used for source URLs formatted as `udp://NAME`\n" + "- TCP sockets are used for source URLs formatted as `tcp://NAME`\n" +#ifdef GPAC_HAS_SOCK_UN + "- UDP unix domain sockets are used for source URLs formatted as `udpu://NAME`\n" + "- TCP unix domain sockets are used for source URLs formatted as `tcpu://NAME`\n" +#ifdef GPAC_CONFIG_DARWIN + "\nOn OSX with VM packet replay you will need to force multicast routing, eg: route add -net 239.255.1.4/32 -interface vboxnet0" +#endif + "" +#else + "Your platform does not supports unix domain sockets, udpu:// and tcpu:// schemes not supported." +#endif + , +#endif //GPAC_DISABLE_DOC + .private_size = sizeof(GF_SockInCtx), + .args = SockInArgs, + SETCAPS(SockInCaps), + .initialize = sockin_initialize, + .finalize = sockin_finalize, + .process = sockin_process, + .process_event = sockin_process_event, + .probe_url = sockin_probe_url +}; + + +const GF_FilterRegister *sockin_register(GF_FilterSession *session) +{ + return &SockInRegister; +} + diff --git a/src/filters/inspect.c b/src/filters/inspect.c new file mode 100644 index 0000000..92b227c --- /dev/null +++ b/src/filters/inspect.c @@ -0,0 +1,2799 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / inspection filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include + +typedef struct +{ + GF_FilterPid *src_pid; + FILE *tmp; + u64 pck_num; + u32 idx; + u8 dump_pid; //0: no, 1: configure/reconfig, 2: info update + u8 init_pid_config_done; + u64 pck_for_config; + u64 prev_dts, prev_cts, init_ts; + u32 codec_id; + u32 stream_type; + +#ifndef GPAC_DISABLE_AV_PARSERS + HEVCState *hevc_state; + AVCState *avc_state; + AV1State *av1_state; + GF_M4VParser *mv124_state; + GF_M4VDecSpecInfo dsi; +#endif + //for analyzing + GF_BitStream *bs; + + Bool has_svcc; + u32 nalu_size_length; + Bool is_adobe_protected; + Bool is_cenc_protected; + + GF_Fraction tmcd_rate; + u32 tmcd_flags; + u32 tmcd_fpt; +} PidCtx; + +enum +{ + INSPECT_MODE_PCK=0, + INSPECT_MODE_BLOCK, + INSPECT_MODE_REFRAME, + INSPECT_MODE_RAW +}; + +enum +{ + INSPECT_TEST_NO=0, + INSPECT_TEST_NOPROP, + INSPECT_TEST_NETWORK, + INSPECT_TEST_NETX, + INSPECT_TEST_ENCODE, + INSPECT_TEST_ENCX, +}; + +typedef struct +{ + u32 mode; + Bool interleave; + Bool dump_data; + Bool deep; + char *log; + char *fmt; + Bool props, hdr, allp, info, pcr, analyze, xml; + Double speed, start; + u32 test; + GF_Fraction dur; + Bool dump_crc, dtype; + Bool fftmcd; + + FILE *dump; + + GF_List *src_pids; + + Bool is_prober, probe_done, hdr_done, dump_pck; + Bool args_updated; +} GF_InspectCtx; + +#define DUMP_ATT_STR(_name, _val) if (ctx->xml) { \ + gf_fprintf(dump, " %s=\"%s\"", _name, _val); \ + } else { \ + gf_fprintf(dump, " %s %s", _name, _val); \ + } + +#define DUMP_ATT_LLU(_name, _val) if (ctx->xml) { \ + gf_fprintf(dump, " %s=\""LLU"\"", _name, _val);\ + } else {\ + gf_fprintf(dump, " %s "LLU, _name, _val);\ + } + +#define DUMP_ATT_U(_name, _val) if (ctx->xml) { \ + gf_fprintf(dump, " %s=\"%u\"", _name, _val);\ + } else {\ + gf_fprintf(dump, " %s %u", _name, _val);\ + } + +#define DUMP_ATT_D(_name, _val) if (ctx->xml) { \ + gf_fprintf(dump, " %s=\"%d\"", _name, _val);\ + } else {\ + gf_fprintf(dump, " %s %d", _name, _val);\ + } + +#define DUMP_ATT_X(_name, _val) if (ctx->xml) { \ + gf_fprintf(dump, " %s=\"0x%08X\"", _name, _val);\ + } else {\ + gf_fprintf(dump, " %s 0x%08X", _name, _val);\ + } + +#define DUMP_ATT_F(_name, _val) if (ctx->xml) { \ + gf_fprintf(dump, " %s=\"%f\"", _name, _val);\ + } else {\ + gf_fprintf(dump, " %s %f", _name, _val);\ + } + + + +#ifndef GPAC_DISABLE_AV_PARSERS +static u32 inspect_get_nal_size(char *ptr, u32 nalh_size) +{ + u32 nal_size=0; + u32 v = nalh_size; + while (v) { + nal_size |= (u8) *ptr; + ptr++; + v-=1; + if (v) nal_size <<= 8; + } + return nal_size; +} + +static void dump_sei(FILE *dump, GF_BitStream *bs, Bool is_hevc) +{ + u32 sei_idx=0; + u32 i; + gf_bs_enable_emulation_byte_removal(bs, GF_TRUE); + + //skip nal header + gf_bs_read_int(bs, is_hevc ? 16 : 8); + + gf_fprintf(dump, " SEI=\""); + while (gf_bs_available(bs) ) { + u32 sei_type = 0; + u32 sei_size = 0; + while (gf_bs_peek_bits(bs, 8, 0) == 0xFF) { + sei_type += 255; + gf_bs_read_int(bs, 8); + } + sei_type += gf_bs_read_int(bs, 8); + while (gf_bs_peek_bits(bs, 8, 0) == 0xFF) { + sei_size += 255; + gf_bs_read_int(bs, 8); + } + sei_size += gf_bs_read_int(bs, 8); + i=0; + while (i < sei_size) { + gf_bs_read_u8(bs); + i++; + } + if (sei_idx) gf_fprintf(dump, ","); + gf_fprintf(dump, "(type=%u, size=%u)", sei_type, sei_size); + sei_idx++; + if (gf_bs_peek_bits(bs, 8, 0) == 0x80) { + break; + } + } + gf_fprintf(dump, "\""); +} + +static void gf_inspect_dump_nalu_internal(FILE *dump, u8 *ptr, u32 ptr_size, Bool is_svc, HEVCState *hevc, AVCState *avc, u32 nalh_size, Bool dump_crc, Bool is_encrypted, PidCtx *pctx) +{ + s32 res = 0; + u8 type, nal_ref_idc; + u8 dependency_id, quality_id, temporal_id; + u8 track_ref_index; + s8 sample_offset; + u32 data_offset, data_size; + s32 idx; + GF_BitStream *bs; + + if (!ptr_size) { + gf_fprintf(dump, "error=\"invalid nal size 0\""); + return; + } + + if (dump_crc) gf_fprintf(dump, "crc=\"%u\" ", gf_crc_32(ptr, ptr_size) ); + + if (hevc) { +#ifndef GPAC_DISABLE_HEVC + if (ptr_size==1) { + gf_fprintf(dump, "error=\"invalid nal size 1\""); + return; + } + res = gf_media_hevc_parse_nalu(ptr, ptr_size, hevc, &type, &temporal_id, &quality_id); + + gf_fprintf(dump, "code=\"%d\" type=\"", type); + + switch (type) { + case GF_HEVC_NALU_SLICE_TRAIL_N: + gf_fputs("TRAIL_N slice segment", dump); + break; + case GF_HEVC_NALU_SLICE_TRAIL_R: + gf_fputs("TRAIL_R slice segment", dump); + break; + case GF_HEVC_NALU_SLICE_TSA_N: + gf_fputs("TSA_N slice segment", dump); + break; + case GF_HEVC_NALU_SLICE_TSA_R: + gf_fputs("TSA_R slice segment", dump); + break; + case GF_HEVC_NALU_SLICE_STSA_N: + gf_fputs("STSA_N slice segment", dump); + break; + case GF_HEVC_NALU_SLICE_STSA_R: + gf_fputs("STSA_R slice segment", dump); + break; + case GF_HEVC_NALU_SLICE_RADL_N: + gf_fputs("RADL_N slice segment", dump); + break; + case GF_HEVC_NALU_SLICE_RADL_R: + gf_fputs("RADL_R slice segment", dump); + break; + case GF_HEVC_NALU_SLICE_RASL_N: + gf_fputs("RASL_N slice segment", dump); + break; + case GF_HEVC_NALU_SLICE_RASL_R: + gf_fputs("RASL_R slice segment", dump); + break; + case GF_HEVC_NALU_SLICE_BLA_W_LP: + gf_fputs("Broken link access slice (W LP)", dump); + break; + case GF_HEVC_NALU_SLICE_BLA_W_DLP: + gf_fputs("Broken link access slice (W DLP)", dump); + break; + case GF_HEVC_NALU_SLICE_BLA_N_LP: + gf_fputs("Broken link access slice (N LP)", dump); + break; + case GF_HEVC_NALU_SLICE_IDR_W_DLP: + gf_fputs("IDR slice (W DLP)", dump); + break; + case GF_HEVC_NALU_SLICE_IDR_N_LP: + gf_fputs("IDR slice (N LP)", dump); + break; + case GF_HEVC_NALU_SLICE_CRA: + gf_fputs("CRA slice", dump); + break; + + case GF_HEVC_NALU_VID_PARAM: + gf_fputs("Video Parameter Set", dump); + idx = gf_media_hevc_read_vps(ptr, ptr_size, hevc); + if (idx<0) gf_fprintf(dump, "\" vps_id=\"PARSING FAILURE"); + else gf_fprintf(dump, "\" vps_id=\"%d", idx); + break; + case GF_HEVC_NALU_SEQ_PARAM: + idx = gf_media_hevc_read_sps(ptr, ptr_size, hevc); + gf_fputs("Sequence Parameter Set", dump); + if (idx<0) gf_fprintf(dump, "\" sps_id=\"PARSING FAILURE"); + else { + HEVC_SPS *sps= &hevc->sps[idx]; + gf_fprintf(dump, "\" sps_id=\"%d", idx); + if (gf_sys_is_test_mode()) break; + + gf_fprintf(dump, "\" aspect_ratio_info_present_flag=\"%d", sps->aspect_ratio_info_present_flag); + gf_fprintf(dump, "\" bit_depth_chroma=\"%d", sps->bit_depth_chroma); + gf_fprintf(dump, "\" bit_depth_luma=\"%d", sps->bit_depth_luma); + gf_fprintf(dump, "\" chroma_format_idc=\"%d", sps->chroma_format_idc); + gf_fprintf(dump, "\" colour_description_present_flag=\"%d", sps->colour_description_present_flag); + gf_fprintf(dump, "\" colour_primaries=\"%d", sps->colour_primaries); + gf_fprintf(dump, "\" cw_flag=\"%d", sps->cw_flag); + if (sps->cw_flag) { + gf_fprintf(dump, "\" cw_bottom=\"%d", sps->cw_bottom); + gf_fprintf(dump, "\" cw_top=\"%d", sps->cw_top); + gf_fprintf(dump, "\" cw_left=\"%d", sps->cw_left); + gf_fprintf(dump, "\" cw_bottom=\"%d", sps->cw_bottom); + } + gf_fprintf(dump, "\" height=\"%d", sps->height); + gf_fprintf(dump, "\" width=\"%d", sps->width); + gf_fprintf(dump, "\" log2_max_pic_order_cnt_lsb=\"%d", sps->log2_max_pic_order_cnt_lsb); + gf_fprintf(dump, "\" long_term_ref_pics_present_flag=\"%d", sps->long_term_ref_pics_present_flag); + gf_fprintf(dump, "\" matrix_coeffs=\"%d", sps->matrix_coeffs); + gf_fprintf(dump, "\" max_CU_depth=\"%d", sps->max_CU_depth); + gf_fprintf(dump, "\" max_CU_width=\"%d", sps->max_CU_width); + gf_fprintf(dump, "\" max_CU_height=\"%d", sps->max_CU_height); + gf_fprintf(dump, "\" num_long_term_ref_pic_sps=\"%d", sps->num_long_term_ref_pic_sps); + gf_fprintf(dump, "\" num_short_term_ref_pic_sets=\"%d", sps->num_short_term_ref_pic_sets); + gf_fprintf(dump, "\" has_timing_info=\"%d", sps->has_timing_info); + if (sps->has_timing_info) { + gf_fprintf(dump, "\" time_scale=\"%d", sps->time_scale); + gf_fprintf(dump, "\" num_ticks_poc_diff_one_minus1=\"%d", sps->num_ticks_poc_diff_one_minus1); + gf_fprintf(dump, "\" num_units_in_tick=\"%d", sps->num_units_in_tick); + gf_fprintf(dump, "\" poc_proportional_to_timing_flag=\"%d", sps->poc_proportional_to_timing_flag); + } + gf_fprintf(dump, "\" rep_format_idx=\"%d", sps->rep_format_idx); + gf_fprintf(dump, "\" sample_adaptive_offset_enabled_flag=\"%d", sps->sample_adaptive_offset_enabled_flag); + gf_fprintf(dump, "\" sar_idc=\"%d", sps->sar_idc); + gf_fprintf(dump, "\" separate_colour_plane_flag=\"%d", sps->separate_colour_plane_flag); + gf_fprintf(dump, "\" temporal_mvp_enable_flag=\"%d", sps->temporal_mvp_enable_flag); + gf_fprintf(dump, "\" transfer_characteristic=\"%d", sps->transfer_characteristic); + gf_fprintf(dump, "\" video_full_range_flag=\"%d", sps->video_full_range_flag); + gf_fprintf(dump, "\" sps_ext_or_max_sub_layers_minus1=\"%d", sps->sps_ext_or_max_sub_layers_minus1); + gf_fprintf(dump, "\" max_sub_layers_minus1=\"%d", sps->max_sub_layers_minus1); + gf_fprintf(dump, "\" update_rep_format_flag=\"%d", sps->update_rep_format_flag); + gf_fprintf(dump, "\" sub_layer_ordering_info_present_flag=\"%d", sps->sub_layer_ordering_info_present_flag); + gf_fprintf(dump, "\" scaling_list_enable_flag=\"%d", sps->scaling_list_enable_flag); + gf_fprintf(dump, "\" infer_scaling_list_flag=\"%d", sps->infer_scaling_list_flag); + gf_fprintf(dump, "\" scaling_list_ref_layer_id=\"%d", sps->scaling_list_ref_layer_id); + gf_fprintf(dump, "\" scaling_list_data_present_flag=\"%d", sps->scaling_list_data_present_flag); + gf_fprintf(dump, "\" asymmetric_motion_partitions_enabled_flag=\"%d", sps->asymmetric_motion_partitions_enabled_flag); + gf_fprintf(dump, "\" pcm_enabled_flag=\"%d", sps->pcm_enabled_flag); + gf_fprintf(dump, "\" strong_intra_smoothing_enable_flag=\"%d", sps->strong_intra_smoothing_enable_flag); + gf_fprintf(dump, "\" vui_parameters_present_flag=\"%d", sps->vui_parameters_present_flag); + gf_fprintf(dump, "\" log2_diff_max_min_luma_coding_block_size=\"%d", sps->log2_diff_max_min_luma_coding_block_size); + gf_fprintf(dump, "\" log2_min_transform_block_size=\"%d", sps->log2_min_transform_block_size); + gf_fprintf(dump, "\" log2_min_luma_coding_block_size=\"%d", sps->log2_min_luma_coding_block_size); + gf_fprintf(dump, "\" log2_max_transform_block_size=\"%d", sps->log2_max_transform_block_size); + gf_fprintf(dump, "\" max_transform_hierarchy_depth_inter=\"%d", sps->max_transform_hierarchy_depth_inter); + gf_fprintf(dump, "\" max_transform_hierarchy_depth_intra=\"%d", sps->max_transform_hierarchy_depth_intra); + gf_fprintf(dump, "\" pcm_sample_bit_depth_luma_minus1=\"%d", sps->pcm_sample_bit_depth_luma_minus1); + gf_fprintf(dump, "\" pcm_sample_bit_depth_chroma_minus1=\"%d", sps->pcm_sample_bit_depth_chroma_minus1); + gf_fprintf(dump, "\" pcm_loop_filter_disable_flag=\"%d", sps->pcm_loop_filter_disable_flag); + gf_fprintf(dump, "\" log2_min_pcm_luma_coding_block_size_minus3=\"%d", sps->log2_min_pcm_luma_coding_block_size_minus3); + gf_fprintf(dump, "\" log2_diff_max_min_pcm_luma_coding_block_size=\"%d", sps->log2_diff_max_min_pcm_luma_coding_block_size); + gf_fprintf(dump, "\" overscan_info_present=\"%d", sps->overscan_info_present); + gf_fprintf(dump, "\" overscan_appropriate=\"%d", sps->overscan_appropriate); + gf_fprintf(dump, "\" video_signal_type_present_flag=\"%d", sps->video_signal_type_present_flag); + gf_fprintf(dump, "\" video_format=\"%d", sps->video_format); + gf_fprintf(dump, "\" chroma_loc_info_present_flag=\"%d", sps->chroma_loc_info_present_flag); + gf_fprintf(dump, "\" chroma_sample_loc_type_top_field=\"%d", sps->chroma_sample_loc_type_top_field); + gf_fprintf(dump, "\" chroma_sample_loc_type_bottom_field=\"%d", sps->chroma_sample_loc_type_bottom_field); + gf_fprintf(dump, "\" neutra_chroma_indication_flag=\"%d", sps->neutra_chroma_indication_flag); + gf_fprintf(dump, "\" field_seq_flag=\"%d", sps->field_seq_flag); + gf_fprintf(dump, "\" frame_field_info_present_flag=\"%d", sps->frame_field_info_present_flag); + gf_fprintf(dump, "\" default_display_window_flag=\"%d", sps->default_display_window_flag); + gf_fprintf(dump, "\" left_offset=\"%d", sps->left_offset); + gf_fprintf(dump, "\" right_offset=\"%d", sps->right_offset); + gf_fprintf(dump, "\" top_offset=\"%d", sps->top_offset); + gf_fprintf(dump, "\" bottom_offset=\"%d", sps->bottom_offset); + gf_fprintf(dump, "\" hrd_parameters_present_flag=\"%d", sps->hrd_parameters_present_flag); + } + break; + case GF_HEVC_NALU_PIC_PARAM: + idx = gf_media_hevc_read_pps(ptr, ptr_size, hevc); + gf_fputs("Picture Parameter Set", dump); + if (idx<0) gf_fprintf(dump, "\" pps_id=\"PARSING FAILURE"); + else { + HEVC_PPS *pps= &hevc->pps[idx]; + gf_fprintf(dump, "\" pps_id=\"%d", idx); + + if (gf_sys_is_test_mode()) break; + + gf_fprintf(dump, "\" cabac_init_present_flag=\"%d", pps->cabac_init_present_flag); + gf_fprintf(dump, "\" dependent_slice_segments_enabled_flag=\"%d", pps->dependent_slice_segments_enabled_flag); + gf_fprintf(dump, "\" entropy_coding_sync_enabled_flag=\"%d", pps->entropy_coding_sync_enabled_flag); + gf_fprintf(dump, "\" lists_modification_present_flag=\"%d", pps->lists_modification_present_flag); + gf_fprintf(dump, "\" loop_filter_across_slices_enabled_flag=\"%d", pps->loop_filter_across_slices_enabled_flag); + gf_fprintf(dump, "\" loop_filter_across_tiles_enabled_flag=\"%d", pps->loop_filter_across_tiles_enabled_flag); + gf_fprintf(dump, "\" num_extra_slice_header_bits=\"%d", pps->num_extra_slice_header_bits); + gf_fprintf(dump, "\" num_ref_idx_l0_default_active=\"%d", pps->num_ref_idx_l0_default_active); + gf_fprintf(dump, "\" num_ref_idx_l1_default_active=\"%d", pps->num_ref_idx_l1_default_active); + gf_fprintf(dump, "\" tiles_enabled_flag=\"%d", pps->tiles_enabled_flag); + if (pps->tiles_enabled_flag) { + gf_fprintf(dump, "\" uniform_spacing_flag=\"%d", pps->uniform_spacing_flag); + if (!pps->uniform_spacing_flag) { + u32 k; + gf_fprintf(dump, "\" num_tile_columns=\"%d", pps->num_tile_columns); + gf_fprintf(dump, "\" num_tile_rows=\"%d", pps->num_tile_rows); + gf_fprintf(dump, "\" colomns_width=\""); + for (k=0; knum_tile_columns-1; k++) + gf_fprintf(dump, "%d ", pps->column_width[k]); + gf_fprintf(dump, "\" rows_height=\""); + for (k=0; knum_tile_rows-1; k++) + gf_fprintf(dump, "%d ", pps->row_height[k]); + } + } + gf_fprintf(dump, "\" output_flag_present_flag=\"%d", pps->output_flag_present_flag); + gf_fprintf(dump, "\" pic_init_qp_minus26=\"%d", pps->pic_init_qp_minus26); + gf_fprintf(dump, "\" slice_chroma_qp_offsets_present_flag=\"%d", pps->slice_chroma_qp_offsets_present_flag); + gf_fprintf(dump, "\" slice_segment_header_extension_present_flag=\"%d", pps->slice_segment_header_extension_present_flag); + gf_fprintf(dump, "\" weighted_pred_flag=\"%d", pps->weighted_pred_flag); + gf_fprintf(dump, "\" weighted_bipred_flag=\"%d", pps->weighted_bipred_flag); + + gf_fprintf(dump, "\" sign_data_hiding_flag=\"%d", pps->sign_data_hiding_flag); + gf_fprintf(dump, "\" constrained_intra_pred_flag=\"%d", pps->constrained_intra_pred_flag); + gf_fprintf(dump, "\" transform_skip_enabled_flag=\"%d", pps->transform_skip_enabled_flag); + gf_fprintf(dump, "\" cu_qp_delta_enabled_flag=\"%d", pps->cu_qp_delta_enabled_flag); + if (pps->cu_qp_delta_enabled_flag) + gf_fprintf(dump, "\" diff_cu_qp_delta_depth=\"%d", pps->diff_cu_qp_delta_depth); + gf_fprintf(dump, "\" transquant_bypass_enable_flag=\"%d", pps->transquant_bypass_enable_flag); + gf_fprintf(dump, "\" pic_cb_qp_offset=\"%d", pps->pic_cb_qp_offset); + gf_fprintf(dump, "\" pic_cr_qp_offset=\"%d", pps->pic_cr_qp_offset); + + gf_fprintf(dump, "\" deblocking_filter_control_present_flag=\"%d", pps->deblocking_filter_control_present_flag); + if (pps->deblocking_filter_control_present_flag) { + gf_fprintf(dump, "\" deblocking_filter_override_enabled_flag=\"%d", pps->deblocking_filter_override_enabled_flag); + gf_fprintf(dump, "\" pic_disable_deblocking_filter_flag=\"%d", pps->pic_disable_deblocking_filter_flag); + gf_fprintf(dump, "\" beta_offset_div2=\"%d", pps->beta_offset_div2); + gf_fprintf(dump, "\" tc_offset_div2=\"%d", pps->tc_offset_div2); + } + gf_fprintf(dump, "\" pic_scaling_list_data_present_flag=\"%d", pps->pic_scaling_list_data_present_flag); + gf_fprintf(dump, "\" log2_parallel_merge_level_minus2=\"%d", pps->log2_parallel_merge_level_minus2); + } + break; + case GF_HEVC_NALU_ACCESS_UNIT: + gf_fputs("AU Delimiter", dump); + gf_fprintf(dump, "\" primary_pic_type=\"%d", ptr[2] >> 5); + break; + case GF_HEVC_NALU_END_OF_SEQ: + gf_fputs("End of Sequence", dump); + break; + case GF_HEVC_NALU_END_OF_STREAM: + gf_fputs("End of Stream", dump); + break; + case GF_HEVC_NALU_FILLER_DATA: + gf_fputs("Filler Data", dump); + break; + case GF_HEVC_NALU_SEI_PREFIX: + gf_fputs("SEI Prefix", dump); + break; + case GF_HEVC_NALU_SEI_SUFFIX: + gf_fputs("SEI Suffix", dump); + break; + case 48: + gf_fputs("HEVCAggregator", dump); + break; + case 49: + { + u32 remain = ptr_size-2; + char *s = ptr+2; + + gf_fputs("HEVCExtractor ", dump); + + while (remain) { + u32 mode = s[0]; + remain -= 1; + s += 1; + if (mode) { + u32 len = s[0]; + if (len+1>remain) { + gf_fprintf(dump, "error=\"invalid inband data extractor size: %d vs %d remaining\"", len, remain); + return; + } + remain -= len+1; + s += len+1; + gf_fprintf(dump, "\" inband_size=\"%d", len); + } else { + if (remain < 2 + 2*nalh_size) { + gf_fprintf(dump, "error=\"invalid ref data extractor size: %d vs %d remaining\"", 2 + 2*nalh_size, remain); + return; + } + track_ref_index = (u8) s[0]; + sample_offset = (s8) s[1]; + data_offset = inspect_get_nal_size(&s[2], nalh_size); + data_size = inspect_get_nal_size(&s[2+nalh_size], nalh_size); + gf_fprintf(dump, "\" track_ref_index=\"%d\" sample_offset=\"%d\" data_offset=\"%d\" data_size=\"%d", track_ref_index, sample_offset, data_offset, data_size); + + remain -= 2 + 2*nalh_size; + s += 2 + 2*nalh_size; + } + } + } + break; + default: + gf_fprintf(dump, "UNKNOWN (parsing return %d)", res); + break; + } + gf_fputs("\"", dump); + + if ((type == GF_HEVC_NALU_SEI_PREFIX) || (type == GF_HEVC_NALU_SEI_SUFFIX)) { + if (pctx) { + if (!pctx->bs) + pctx->bs = gf_bs_new(ptr, ptr_size, GF_BITSTREAM_READ); + else + gf_bs_reassign_buffer(pctx->bs, ptr, ptr_size); + bs = pctx->bs; + } else { + bs = gf_bs_new(ptr, ptr_size, GF_BITSTREAM_READ); + } + dump_sei(dump, bs, GF_TRUE); + if (!pctx) gf_bs_del(bs); + } + + if (type < GF_HEVC_NALU_VID_PARAM) { + gf_fprintf(dump, " slice=\"%s\" poc=\"%d\"", (hevc->s_info.slice_type==GF_HEVC_SLICE_TYPE_I) ? "I" : (hevc->s_info.slice_type==GF_HEVC_SLICE_TYPE_P) ? "P" : (hevc->s_info.slice_type==GF_HEVC_SLICE_TYPE_B) ? "B" : "Unknown", hevc->s_info.poc); + gf_fprintf(dump, " first_slice_in_pic=\"%d\"", hevc->s_info.first_slice_segment_in_pic_flag); + gf_fprintf(dump, " dependent_slice_segment=\"%d\"", hevc->s_info.dependent_slice_segment_flag); + + if (!gf_sys_is_test_mode()) { + gf_fprintf(dump, " redundant_pic_cnt=\"%d\"", hevc->s_info.redundant_pic_cnt); + gf_fprintf(dump, " slice_qp_delta=\"%d\"", hevc->s_info.slice_qp_delta); + gf_fprintf(dump, " slice_segment_address=\"%d\"", hevc->s_info.slice_segment_address); + gf_fprintf(dump, " slice_type=\"%d\"", hevc->s_info.slice_type); + } + } + + gf_fprintf(dump, " layer_id=\"%d\" temporal_id=\"%d\"", quality_id, temporal_id); + +#endif //GPAC_DISABLE_HEVC + return; + } + + type = ptr[0] & 0x1F; + nal_ref_idc = ptr[0] & 0x60; + nal_ref_idc>>=5; + gf_fprintf(dump, "code=\"%d\" type=\"", type); + res = -2; + + if (pctx) { + if (!pctx->bs) + pctx->bs = gf_bs_new(ptr, ptr_size, GF_BITSTREAM_READ); + else + gf_bs_reassign_buffer(pctx->bs, ptr, ptr_size); + bs = pctx->bs; + } else { + bs = gf_bs_new(ptr, ptr_size, GF_BITSTREAM_READ); + } + switch (type) { + case GF_AVC_NALU_NON_IDR_SLICE: + gf_fputs("Non IDR slice", dump); + if (is_encrypted) break; + res = gf_media_avc_parse_nalu(bs, avc); + break; + case GF_AVC_NALU_DP_A_SLICE: + gf_fputs("DP Type A slice", dump); + break; + case GF_AVC_NALU_DP_B_SLICE: + gf_fputs("DP Type B slice", dump); + break; + case GF_AVC_NALU_DP_C_SLICE: + gf_fputs("DP Type C slice", dump); + break; + case GF_AVC_NALU_IDR_SLICE: + gf_fputs("IDR slice", dump); + if (is_encrypted) break; + res = gf_media_avc_parse_nalu(bs, avc); + break; + case GF_AVC_NALU_SEI: + gf_fputs("SEI Message", dump); + break; + case GF_AVC_NALU_SEQ_PARAM: + gf_fputs("SequenceParameterSet", dump); + if (is_encrypted) break; + idx = gf_media_avc_read_sps_bs(bs, avc, 0, NULL); + if (idx<0) gf_fprintf(dump, "\" sps_id=\"PARSING FAILURE"); + else gf_fprintf(dump, "\" sps_id=\"%d", idx); + gf_fprintf(dump, "\" frame_mbs_only_flag=\"%d", avc->sps->frame_mbs_only_flag); + gf_fprintf(dump, "\" mb_adaptive_frame_field_flag=\"%d", avc->sps->mb_adaptive_frame_field_flag); + gf_fprintf(dump, "\" vui_parameters_present_flag=\"%d", avc->sps->vui_parameters_present_flag); + gf_fprintf(dump, "\" max_num_ref_frames=\"%d", avc->sps->max_num_ref_frames); + gf_fprintf(dump, "\" gaps_in_frame_num_value_allowed_flag=\"%d", avc->sps->gaps_in_frame_num_value_allowed_flag); + gf_fprintf(dump, "\" chroma_format_idc=\"%d", avc->sps->chroma_format); + gf_fprintf(dump, "\" bit_depth_luma_minus8=\"%d", avc->sps->luma_bit_depth_m8); + gf_fprintf(dump, "\" bit_depth_chroma_minus8=\"%d", avc->sps->chroma_bit_depth_m8); + gf_fprintf(dump, "\" width=\"%d", avc->sps->width); + gf_fprintf(dump, "\" height=\"%d", avc->sps->height); + gf_fprintf(dump, "\" crop_top=\"%d", avc->sps->crop.top); + gf_fprintf(dump, "\" crop_left=\"%d", avc->sps->crop.left); + gf_fprintf(dump, "\" crop_bottom=\"%d", avc->sps->crop.bottom); + gf_fprintf(dump, "\" crop_right=\"%d", avc->sps->crop.right); + if (avc->sps->vui_parameters_present_flag) { + gf_fprintf(dump, "\" vui_video_full_range_flag=\"%d", avc->sps->vui.video_full_range_flag); + gf_fprintf(dump, "\" vui_video_signal_type_present_flag=\"%d", avc->sps->vui.video_signal_type_present_flag); + gf_fprintf(dump, "\" vui_aspect_ratio_info_present_flag=\"%d", avc->sps->vui.aspect_ratio_info_present_flag); + gf_fprintf(dump, "\" vui_aspect_ratio_num=\"%d", avc->sps->vui.par_num); + gf_fprintf(dump, "\" vui_aspect_ratio_den=\"%d", avc->sps->vui.par_den); + gf_fprintf(dump, "\" vui_overscan_info_present_flag=\"%d", avc->sps->vui.overscan_info_present_flag); + gf_fprintf(dump, "\" vui_colour_description_present_flag=\"%d", avc->sps->vui.colour_description_present_flag); + gf_fprintf(dump, "\" vui_colour_primaries=\"%d", avc->sps->vui.colour_primaries); + gf_fprintf(dump, "\" vui_transfer_characteristics=\"%d", avc->sps->vui.transfer_characteristics); + gf_fprintf(dump, "\" vui_matrix_coefficients=\"%d", avc->sps->vui.matrix_coefficients); + gf_fprintf(dump, "\" vui_low_delay_hrd_flag=\"%d", avc->sps->vui.low_delay_hrd_flag); + } + if (gf_sys_is_test_mode()) break; + gf_fprintf(dump, "\" log2_max_poc_lsb=\"%d", avc->sps->log2_max_poc_lsb); + gf_fprintf(dump, "\" log2_max_frame_num=\"%d", avc->sps->log2_max_frame_num); + gf_fprintf(dump, "\" delta_pic_order_always_zero_flag=\"%d", avc->sps->delta_pic_order_always_zero_flag); + gf_fprintf(dump, "\" offset_for_non_ref_pic=\"%d", avc->sps->offset_for_non_ref_pic); + + break; + case GF_AVC_NALU_PIC_PARAM: + gf_fputs("PictureParameterSet", dump); + if (is_encrypted) break; + idx = gf_media_avc_read_pps_bs(bs, avc); + if (idx<0) gf_fprintf(dump, "\" pps_id=\"PARSING FAILURE\" "); + else gf_fprintf(dump, "\" pps_id=\"%d\" sps_id=\"%d", idx, avc->pps[idx].sps_id); + gf_fprintf(dump, "\" entropy_coding_mode_flag=\"%d", avc->pps[idx].entropy_coding_mode_flag); + if (gf_sys_is_test_mode()) break; + gf_fprintf(dump, "\" deblocking_filter_control_present_flag=\"%d", avc->pps[idx].deblocking_filter_control_present_flag); + gf_fprintf(dump, "\" mb_slice_group_map_type=\"%d", avc->pps[idx].mb_slice_group_map_type); + gf_fprintf(dump, "\" num_ref_idx_l0_default_active_minus1=\"%d", avc->pps[idx].num_ref_idx_l0_default_active_minus1); + gf_fprintf(dump, "\" num_ref_idx_l1_default_active_minus1=\"%d", avc->pps[idx].num_ref_idx_l1_default_active_minus1); + gf_fprintf(dump, "\" pic_order_present=\"%d", avc->pps[idx].pic_order_present); + gf_fprintf(dump, "\" pic_size_in_map_units_minus1=\"%d", avc->pps[idx].pic_size_in_map_units_minus1); + gf_fprintf(dump, "\" redundant_pic_cnt_present=\"%d", avc->pps[idx].redundant_pic_cnt_present); + gf_fprintf(dump, "\" slice_group_change_rate_minus1=\"%d", avc->pps[idx].slice_group_change_rate_minus1); + gf_fprintf(dump, "\" slice_group_count=\"%d", avc->pps[idx].slice_group_count); + gf_fprintf(dump, "\" weighted_pred_flag=\"%d", avc->pps[idx].weighted_pred_flag); + gf_fprintf(dump, "\" weighted_bipred_idc=\"%d", avc->pps[idx].weighted_bipred_idc); + break; + case GF_AVC_NALU_ACCESS_UNIT: + gf_fputs("AccessUnit delimiter", dump); + if (is_encrypted) break; + gf_fprintf(dump, "\" primary_pic_type=\"%d", gf_bs_read_u8(bs) >> 5); + break; + case GF_AVC_NALU_END_OF_SEQ: + gf_fputs("EndOfSequence", dump); + break; + case GF_AVC_NALU_END_OF_STREAM: + gf_fputs("EndOfStream", dump); + break; + case GF_AVC_NALU_FILLER_DATA: + gf_fputs("Filler data", dump); + break; + case GF_AVC_NALU_SEQ_PARAM_EXT: + gf_fputs("SequenceParameterSetExtension", dump); + break; + case GF_AVC_NALU_SVC_PREFIX_NALU: + gf_fputs("SVCPrefix", dump); + break; + case GF_AVC_NALU_SVC_SUBSEQ_PARAM: + gf_fputs("SVCSubsequenceParameterSet", dump); + if (is_encrypted) break; + idx = gf_media_avc_read_sps_bs(bs, avc, 1, NULL); + assert (idx >= 0); + gf_fprintf(dump, "\" sps_id=\"%d", idx - GF_SVC_SSPS_ID_SHIFT); + break; + case GF_AVC_NALU_SLICE_AUX: + gf_fputs("Auxiliary Slice", dump); + break; + + case GF_AVC_NALU_SVC_SLICE: + gf_fputs(is_svc ? "SVCSlice" : "CodedSliceExtension", dump); + if (is_encrypted) break; + gf_media_avc_parse_nalu(bs, avc); + dependency_id = (ptr[2] & 0x70) >> 4; + quality_id = (ptr[2] & 0x0F); + temporal_id = (ptr[3] & 0xE0) >> 5; + gf_fprintf(dump, "\" dependency_id=\"%d\" quality_id=\"%d\" temporal_id=\"%d", dependency_id, quality_id, temporal_id); + gf_fprintf(dump, "\" poc=\"%d", avc->s_info.poc); + break; + case 30: + gf_fputs("SVCAggregator", dump); + break; + case 31: + gf_fputs("SVCExtractor", dump); + if (is_encrypted) break; + track_ref_index = (u8) ptr[4]; + sample_offset = (s8) ptr[5]; + data_offset = inspect_get_nal_size(&ptr[6], nalh_size); + data_size = inspect_get_nal_size(&ptr[6+nalh_size], nalh_size); + gf_fprintf(dump, "\" track_ref_index=\"%d\" sample_offset=\"%d\" data_offset=\"%d\" data_size=\"%d\"", track_ref_index, sample_offset, data_offset, data_size); + break; + + default: + gf_fputs("UNKNOWN", dump); + break; + } + gf_fputs("\"", dump); + + if (nal_ref_idc) { + gf_fprintf(dump, " nal_ref_idc=\"%d\"", nal_ref_idc); + } + if (res>=0) { + gf_fprintf(dump, " poc=\"%d\" pps_id=\"%d\" field_pic_flag=\"%d\"", avc->s_info.poc, avc->s_info.pps->id, (int)avc->s_info.field_pic_flag); + } + + if (!is_encrypted) { + if (type == GF_AVC_NALU_SEI) { + dump_sei(dump, bs, GF_FALSE); + } + } + + if (res == -1) + gf_fprintf(dump, " status=\"error decoding slice\""); + + if (bs && !pctx) gf_bs_del(bs); +} + +GF_EXPORT +void gf_inspect_dump_nalu(FILE *dump, u8 *ptr, u32 ptr_size, Bool is_svc, HEVCState *hevc, AVCState *avc, u32 nalh_size, Bool dump_crc, Bool is_encrypted) +{ + gf_inspect_dump_nalu_internal(dump, ptr, ptr_size, is_svc, hevc, avc, nalh_size, dump_crc, is_encrypted, NULL); +} + +static void av1_dump_tile(FILE *dump, u32 idx, AV1Tile *tile) +{ + gf_fprintf(dump, " \n", idx, tile->obu_start_offset, tile->size); +} + +GF_EXPORT +void gf_inspect_dump_obu(FILE *dump, AV1State *av1, u8 *obu, u64 obu_length, ObuType obu_type, u64 obu_size, u32 hdr_size, Bool dump_crc) +{ +#define DUMP_OBU_INT(_v) gf_fprintf(dump, #_v"=\"%d\" ", av1->_v); +#define DUMP_OBU_INT2(_n, _v) gf_fprintf(dump, _n"=\"%d\" ", _v); + + gf_fprintf(dump, " obu_has_size_field, av1->obu_extension_flag, av1->temporal_id , av1->spatial_id); + if (dump_crc && (obu_length<0xFFFFFFFFUL)) + gf_fprintf(dump, "crc=\"%u\" ", gf_crc_32(obu, (u32) obu_length) ); + switch (obu_type) { + case OBU_SEQUENCE_HEADER: + DUMP_OBU_INT(width) + DUMP_OBU_INT(height) + DUMP_OBU_INT(bit_depth) + DUMP_OBU_INT(still_picture) + DUMP_OBU_INT(OperatingPointIdc) + DUMP_OBU_INT(color_range) + DUMP_OBU_INT(color_description_present_flag) + DUMP_OBU_INT(color_primaries) + DUMP_OBU_INT(transfer_characteristics) + DUMP_OBU_INT(matrix_coefficients) + DUMP_OBU_INT2("profile", av1->config->seq_profile) + DUMP_OBU_INT2("level", av1->config->seq_level_idx_0) + break; + case OBU_FRAME_HEADER: + case OBU_FRAME: + if (av1->frame_id_numbers_present_flag) { + DUMP_OBU_INT2("delta_frame_id_length_minus_2", av1->delta_frame_id_length_minus_2) + } + if (av1->reduced_still_picture_header) { + DUMP_OBU_INT(reduced_still_picture_header) + } + DUMP_OBU_INT2("uncompressed_header_bytes", av1->frame_state.uncompressed_header_bytes); + if (av1->frame_state.uncompressed_header_bytes) { + if (av1->frame_state.frame_type==AV1_KEY_FRAME) gf_fprintf(dump, "frame_type=\"key\" "); + else if (av1->frame_state.frame_type==AV1_INTER_FRAME) gf_fprintf(dump, "frame_type=\"inter\" "); + else if (av1->frame_state.frame_type==AV1_INTRA_ONLY_FRAME) gf_fprintf(dump, "frame_type=\"intra_only\" "); + else if (av1->frame_state.frame_type==AV1_SWITCH_FRAME) gf_fprintf(dump, "frame_type=\"switch\" "); + gf_fprintf(dump, "refresh_frame_flags=\"%d\" ", av1->frame_state.refresh_frame_flags); + + DUMP_OBU_INT2("show_frame", av1->frame_state.show_frame); + DUMP_OBU_INT2("show_existing_frame", av1->frame_state.show_existing_frame); + } + if (obu_type==OBU_FRAME_HEADER) + break; + + case OBU_TILE_GROUP: + if (av1->frame_state.nb_tiles_in_obu) { + u32 i; + DUMP_OBU_INT2("nb_tiles", av1->frame_state.nb_tiles_in_obu) + fprintf(dump, ">\n"); + for (i = 0; i < av1->frame_state.nb_tiles_in_obu; i++) { + av1_dump_tile(dump, i, &av1->frame_state.tiles[i]); + } + } else { + gf_fprintf(dump, "nb_tiles=\"unknown\">\n"); + } + gf_fprintf(dump, "\n"); + break; + default: + break; + + } + if (obu_type != OBU_TILE_GROUP) + gf_fprintf(dump, "/>\n"); +} + +static void gf_inspect_dump_prores_internal(FILE *dump, u8 *ptr, u64 frame_size, Bool dump_crc, PidCtx *pctx) +{ + GF_ProResFrameInfo prores_frame; + GF_Err e; + GF_BitStream *bs; + if (pctx) { + gf_bs_reassign_buffer(pctx->bs, ptr, frame_size); + bs = pctx->bs; + } else { + bs = gf_bs_new(ptr, frame_size, GF_BITSTREAM_READ); + } + e = gf_media_prores_parse_bs(bs, &prores_frame); + if (!pctx) gf_bs_del(bs); + + if (e) { + gf_fprintf(dump, " \n", gf_error_to_string(e) ); + return; + } + gf_fprintf(dump, " \n"); + } else { + u32 j, k; + gf_fprintf(dump, ">\n"); + if (prores_frame.load_luma_quant_matrix) { + gf_fprintf(dump, " \n"); + } + if (prores_frame.load_chroma_quant_matrix) { + gf_fprintf(dump, " \n"); + } + gf_fprintf(dump, " \n"); + } +} + +GF_EXPORT +void gf_inspect_dump_prores(FILE *dump, u8 *ptr, u64 frame_size, Bool dump_crc) +{ + gf_inspect_dump_prores_internal(dump, ptr, frame_size, dump_crc, NULL); +} +enum { + MHAS_FILLER = 0, + MHAS_CONFIG, + MHAS_FRAME, + MHAS_SCENE_INFO, + MHAS_RES4, + MHAS_RES5, + MHAS_SYNC, + MHAS_SYNC_GAP, + MHAS_MARKER, + MHAS_CRC16, + MHAS_CRC32, + MHAS_DESCRIPTOR, + MHAS_INTERACTION, + MHAS_LOUDNESS_CRC, + MHAS_BUFFER_INFO, + MHAS_GLOBAL_CRC16, + MHAS_GLOBAL_CRC32, + MHAS_AUDIO_TRUNCATION, + MHAS_GEN_DATA, +}; +static struct { + u32 type; + const char *name; +} mhas_pack_types[] = +{ + {MHAS_FILLER, "filler"}, + {MHAS_CONFIG, "config"}, + {MHAS_FRAME, "frame"}, + {MHAS_SCENE_INFO, "scene_info"}, + {MHAS_SYNC, "sync"}, + {MHAS_SYNC_GAP, "sync_gap"}, + {MHAS_MARKER, "marker"}, + {MHAS_CRC16, "crc16"}, + {MHAS_CRC32, "crc32"}, + {MHAS_DESCRIPTOR, "descriptor"}, + {MHAS_INTERACTION, "interaction"}, + {MHAS_LOUDNESS_CRC, "loudness_drc"}, + {MHAS_BUFFER_INFO, "buffer_info"}, + {MHAS_GLOBAL_CRC16, "global_crc16"}, + {MHAS_GLOBAL_CRC32, "global_crc32"}, + {MHAS_AUDIO_TRUNCATION, "audio_truncation"}, + {MHAS_GEN_DATA, "gen_data"}, +}; + +static void dump_mha_config(FILE *dump, GF_BitStream *bs, const char *indent) +{ + u32 val; + gf_fprintf(dump, "%s\n"); +} +static void gf_inspect_dump_mha_frame(FILE *dump, GF_BitStream *bs, const char *indent) +{ + u32 val; + gf_fprintf(dump, "%s\n"); +} +static void gf_inspect_dump_mhas(FILE *dump, u8 *ptr, u64 frame_size, Bool dump_crc, PidCtx *pctx) +{ + u64 gf_mpegh_escaped_value(GF_BitStream *bs, u32 nBits1, u32 nBits2, u32 nBits3); + gf_bs_reassign_buffer(pctx->bs, ptr, frame_size); + GF_BitStream *bs = pctx->bs; + + gf_fprintf(dump, " \n"); + + while (gf_bs_available(bs)) { + u32 i, count; + const char *type_name="uknown"; + u64 pos; + u32 type = (u32) gf_mpegh_escaped_value(bs, 3, 8, 8); + u64 label = gf_mpegh_escaped_value(bs, 2, 8, 32); + u64 size = gf_mpegh_escaped_value(bs, 11, 24, 24); + + count = GF_ARRAY_LENGTH(mhas_pack_types); + for (i=0; i\n"); + dump_mha_config(dump, bs, " "); + gf_fprintf(dump, " \n"); + break; + case MHAS_FRAME: + gf_fprintf(dump, ">\n"); + gf_inspect_dump_mha_frame(dump, bs, " "); + gf_fprintf(dump, " \n"); + break; + case MHAS_BUFFER_INFO: + if (gf_bs_read_int(bs, 1)) { + gf_fprintf(dump, " buffer_fullness_present=\"1\" buffer_fullness=\""LLU"\"", gf_mpegh_escaped_value(bs, 15,24,32) ); + } else { + gf_fprintf(dump, " buffer_fullness_present=\"0\""); + + } + gf_fprintf(dump, "/>\n"); + break; + case MHAS_SYNC: + gf_fprintf(dump, " sync=\"0x%02X\"/>\n", gf_bs_read_u8(bs) ); + break; + case MHAS_SYNC_GAP: + gf_fprintf(dump, " syncSpacingLength=\"0x%02" LLX_SUF "\"/>\n", gf_mpegh_escaped_value(bs, 16, 24, 24) ); + break; + case MHAS_MARKER: + case MHAS_DESCRIPTOR: + gf_fprintf(dump, " %s=\"0x", (type==MHAS_MARKER) ? "markers" : "descriptors"); + for (i=0; i\n"); + break; + default: + gf_fprintf(dump, "/>\n"); + break; + } + gf_bs_align(bs); + pos = gf_bs_get_position(bs) - pos; + if (pos < size) + gf_bs_skip_bytes(bs, size - pos); + + } + gf_fprintf(dump, " \n"); +} + +#endif + + +static void finalize_dump(GF_InspectCtx *ctx, u32 streamtype, Bool concat) +{ + char szLine[1025]; + u32 i, count = gf_list_count(ctx->src_pids); + for (i=0; isrc_pids, i); + //already done + if (!pctx->tmp) continue; + //not our streamtype + if (streamtype && (pctx->stream_type!=streamtype)) continue; + + if (concat) { + gf_fseek(pctx->tmp, 0, SEEK_SET); + while (!gf_feof(pctx->tmp)) { + u32 read = (u32) gf_fread(szLine, 1024, pctx->tmp); + gf_fwrite(szLine, read, ctx->dump); + } + } + gf_fclose(pctx->tmp); + if (ctx->xml) + gf_fprintf(ctx->dump, ""); + pctx->tmp = NULL; + } +} + +static void inspect_finalize(GF_Filter *filter) +{ + Bool concat=GF_FALSE; + GF_InspectCtx *ctx = (GF_InspectCtx *) gf_filter_get_udta(filter); + + if (ctx->dump) { + if ((ctx->dump!=stderr) && (ctx->dump!=stdout)) concat=GF_TRUE; + else if (!ctx->interleave) concat=GF_TRUE; + } + if (!ctx->interleave) { + finalize_dump(ctx, GF_STREAM_AUDIO, concat); + finalize_dump(ctx, GF_STREAM_VISUAL, concat); + finalize_dump(ctx, GF_STREAM_SCENE, concat); + finalize_dump(ctx, GF_STREAM_OD, concat); + finalize_dump(ctx, GF_STREAM_TEXT, concat); + finalize_dump(ctx, 0, concat); + } + + while (gf_list_count(ctx->src_pids)) { + PidCtx *pctx = gf_list_pop_front(ctx->src_pids); + +#ifndef GPAC_DISABLE_AV_PARSERS + if (pctx->avc_state) gf_free(pctx->avc_state); + if (pctx->hevc_state) gf_free(pctx->hevc_state); + if (pctx->av1_state) { + if (pctx->av1_state->config) gf_odf_av1_cfg_del(pctx->av1_state->config); + gf_av1_reset_state(pctx->av1_state, GF_TRUE); + gf_free(pctx->av1_state); + } +#endif + if (pctx->bs) gf_bs_del(pctx->bs); + gf_free(pctx); + } + gf_list_del(ctx->src_pids); + + if (ctx->dump) { + if (ctx->xml) gf_fprintf(ctx->dump, "\n"); + if ((ctx->dump!=stderr) && (ctx->dump!=stdout)) { + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + gf_fflush(ctx->dump); + gf_ferror(ctx->dump); + } +#endif + gf_fclose(ctx->dump); + } + } + +} + +static void dump_temi_loc(GF_InspectCtx *ctx, PidCtx *pctx, FILE *dump, const char *pname, const GF_PropertyValue *att) +{ + u32 val; + Double dval; + if (ctx->xml) { + gf_fprintf(dump, " bs) + pctx->bs = gf_bs_new(att->value.data.ptr, att->value.data.size, GF_BITSTREAM_READ); + else + gf_bs_reassign_buffer(pctx->bs, att->value.data.ptr, att->value.data.size); + + while (1) { + u8 achar = gf_bs_read_u8(pctx->bs); + if (!achar) break; + } + + val = atoi(pname+7); + + DUMP_ATT_D("timeline", val) + DUMP_ATT_STR("url", att->value.data.ptr) + if (gf_bs_read_int(pctx->bs, 1)) { + DUMP_ATT_D("announce", 1) + } + if (gf_bs_read_int(pctx->bs, 1)) { + DUMP_ATT_D("splicing", 1) + } + if (gf_bs_read_int(pctx->bs, 1)) { + DUMP_ATT_D("reload", 1) + } + gf_bs_read_int(pctx->bs, 5); + dval = gf_bs_read_double(pctx->bs); + if (dval) { + DUMP_ATT_F("splice_start", dval) + } + dval = gf_bs_read_double(pctx->bs); + if (dval) { + DUMP_ATT_F("splice_end", dval) + } + if (ctx->xml) { + gf_fprintf(dump, "/>\n"); + } else { + gf_fprintf(dump, "\n"); + } +} + +static void dump_temi_time(GF_InspectCtx *ctx, PidCtx *pctx, FILE *dump, const char *pname, const GF_PropertyValue *att) +{ + u32 val; + u64 lval; + if (ctx->xml) { + gf_fprintf(dump, " bs) + pctx->bs = gf_bs_new(att->value.data.ptr, att->value.data.size, GF_BITSTREAM_READ); + else + gf_bs_reassign_buffer(pctx->bs, att->value.data.ptr, att->value.data.size); + + DUMP_ATT_D("timeline", val) + val = gf_bs_read_u32(pctx->bs); + DUMP_ATT_D("media_timescale", val) + lval = gf_bs_read_u64(pctx->bs); + DUMP_ATT_LLU("media_timestamp", lval) + lval = gf_bs_read_u64(pctx->bs); + DUMP_ATT_LLU("media_pts", lval) + + if (gf_bs_read_int(pctx->bs, 1)) { + DUMP_ATT_D("reload", 1) + } + if (gf_bs_read_int(pctx->bs, 1)) { + DUMP_ATT_D("paused", 1) + } + if (gf_bs_read_int(pctx->bs, 1)) { + DUMP_ATT_D("discontinuity", 1) + } + val = gf_bs_read_int(pctx->bs, 1); + gf_bs_read_int(pctx->bs, 4); + + if (val) { + lval = gf_bs_read_u64(pctx->bs); + DUMP_ATT_LLU("ntp", lval) + } + if (ctx->xml) { + gf_fprintf(dump, "/>\n"); + } else { + gf_fprintf(dump, "\n"); + } +} + +static void inspect_dump_property(GF_InspectCtx *ctx, FILE *dump, u32 p4cc, const char *pname, const GF_PropertyValue *att, PidCtx *pctx) +{ + char szDump[GF_PROP_DUMP_ARG_SIZE]; + + if (!pname) pname = gf_props_4cc_get_name(p4cc); + + if (p4cc==GF_PROP_PID_DOWNLOAD_SESSION) + return; + + if (gf_sys_is_test_mode() || ctx->test) { + switch (p4cc) { + case GF_PROP_PID_FILEPATH: + case GF_PROP_PID_URL: + return; + case GF_PROP_PID_FILE_CACHED: + case GF_PROP_PID_DURATION: + if ((ctx->test==INSPECT_TEST_NETWORK) || (ctx->test==INSPECT_TEST_NETX)) + return; + break; + case GF_PROP_PID_DECODER_CONFIG: + case GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT: + case GF_PROP_PID_DOWN_SIZE: + if (ctx->test>=INSPECT_TEST_ENCODE) + return; + break; + case GF_PROP_PID_MEDIA_DATA_SIZE: + case GF_PROP_PID_BITRATE: + case GF_PROP_PID_MAXRATE: + case GF_PROP_PID_AVG_FRAME_SIZE: + case GF_PROP_PID_MAX_FRAME_SIZE: + case GF_PROP_PID_DBSIZE: + if (ctx->test==INSPECT_TEST_ENCX) + return; + break; + + case GF_PROP_PID_ISOM_TRACK_TEMPLATE: + case GF_PROP_PID_ISOM_MOVIE_TIME: + if (ctx->test==INSPECT_TEST_NETX) + return; + break; + + case GF_PROP_PID_ISOM_TREX_TEMPLATE: + case GF_PROP_PID_ISOM_STSD_TEMPLATE: + //TODO once all OK: remove this test and regenerate all hashes + if (gf_sys_is_test_mode()) + return; + default: + if (gf_sys_is_test_mode() && (att->type==GF_PROP_POINTER) ) + return; + break; + } + } + + if (ctx->xml) { + if (ctx->dtype) + gf_fprintf(dump, " type=\"%s\"", gf_props_get_type_name(att->type) ); + + if (pname && strchr(pname, ' ')) { + u32 i=0, k; + char *pname_no_space = gf_strdup(pname); + while (pname_no_space[i]) { + if (pname_no_space[i]==' ') pname_no_space[i]='_'; + i++; + } + + if (att->type==GF_PROP_UINT_LIST) { + for (k=0; k < att->value.uint_list.nb_items; k++) { + if (k) gf_fprintf(dump, ", "); + switch (p4cc) { + case GF_PROP_PID_ISOM_BRANDS: + if (! gf_sys_is_test_mode()) { + gf_fprintf(dump, "%s", gf_4cc_to_str(att->value.uint_list.vals[k]) ); + break; + } + default: + gf_fprintf(dump, "%d", att->value.uint_list.vals[k]); + break; + } + } + } else if (att->type==GF_PROP_STRING_LIST) { + u32 plist_count = gf_list_count(att->value.string_list); + for (k=0; k < plist_count; k++) { + if (k) gf_fprintf(dump, ", "); + gf_xml_dump_string(dump, NULL, (char *) gf_list_get(att->value.string_list, k), NULL); + } + } else if ((att->type==GF_PROP_STRING) || (att->type==GF_PROP_STRING_NO_COPY)) { + gf_xml_dump_string(dump, NULL, att->value.string, NULL); + } else { + gf_fprintf(dump, " %s=\"%s\"", pname_no_space, gf_props_dump(p4cc, att, szDump, ctx->dump_data)); + } + gf_free(pname_no_space); + } else { + gf_fprintf(dump, " %s=\"%s\"", pname ? pname : gf_4cc_to_str(p4cc), gf_props_dump(p4cc, att, szDump, ctx->dump_data)); + } + } else { + if (ctx->dtype) { + gf_fprintf(dump, "\t%s (%s): ", pname ? pname : gf_4cc_to_str(p4cc), gf_props_get_type_name(att->type)); + } else { + if (!p4cc && !strncmp(pname, "temi_l", 6)) + dump_temi_loc(ctx, pctx, dump, pname, att); + else if (!p4cc && !strncmp(pname, "temi_t", 6)) + dump_temi_time(ctx, pctx, dump, pname, att); + else + gf_fprintf(dump, "\t%s: ", pname ? pname : gf_4cc_to_str(p4cc)); + } + + if (att->type==GF_PROP_UINT_LIST) { + u32 k; + for (k=0; k < att->value.uint_list.nb_items; k++) { + if (k) gf_fprintf(dump, ", "); + switch (p4cc) { + case GF_PROP_PID_ISOM_BRANDS: + if (! gf_sys_is_test_mode()) { + gf_fprintf(dump, "%s", gf_4cc_to_str(att->value.uint_list.vals[k]) ); + break; + } + default: + gf_fprintf(dump, "%d", att->value.uint_list.vals[k]); + break; + } + } + } else if (att->type==GF_PROP_STRING_LIST) { + u32 k, plist_count = gf_list_count(att->value.string_list); + for (k=0; k < plist_count; k++) { + if (k) gf_fprintf(dump, ", "); + gf_fprintf(dump, "%s", (const char *) gf_list_get(att->value.string_list, k)); + } + }else{ + gf_fprintf(dump, "%s", gf_props_dump(p4cc, att, szDump, ctx->dump_data) ); + } + gf_fprintf(dump, "\n"); + } +} + +static void inspect_dump_packet_fmt(GF_InspectCtx *ctx, FILE *dump, GF_FilterPacket *pck, PidCtx *pctx, u64 pck_num) +{ + char szDump[GF_PROP_DUMP_ARG_SIZE]; + u32 size=0; + const char *data=NULL; + char *str = ctx->fmt; + if (!dump) return; + assert(str); + + if (pck) + data = gf_filter_pck_get_data(pck, &size); + + while (str) { + char csep; + char *sep, *key; + if (!str[0]) break; + + if ((str[0] != '$') && (str[0] != '%') && (str[0] != '@')) { + gf_fprintf(dump, "%c", str[0]); + str = str+1; + continue; + } + csep = str[0]; + if (str[1] == csep) { + gf_fprintf(dump, "%c", str[0]); + str = str+2; + continue; + } + sep = strchr(str+1, csep); + if (!sep) { + gf_fprintf(dump, "%c", str[0]); + str = str+1; + continue; + } + sep[0] = 0; + key = str+1; + + if (!pck) { + if (!strcmp(key, "lf")) gf_fprintf(dump, "\n" ); + else if (!strcmp(key, "cr")) gf_fprintf(dump, "\r" ); + else if (!strcmp(key, "t")) gf_fprintf(dump, "\t" ); + else if (!strncmp(key, "pid.", 4)) gf_fprintf(dump, "%s", key+4); + else gf_fprintf(dump, "%s", key); + sep[0] = csep; + str = sep+1; + continue; + } + + if (!strcmp(key, "pn")) gf_fprintf(dump, LLU, pck_num); + else if (!strcmp(key, "dts")) { + u64 ts = gf_filter_pck_get_dts(pck); + if (ts==GF_FILTER_NO_TS) gf_fprintf(dump, "N/A"); + else gf_fprintf(dump, LLU, ts ); + } + else if (!strcmp(key, "cts")) { + u64 ts = gf_filter_pck_get_cts(pck); + if (ts==GF_FILTER_NO_TS) gf_fprintf(dump, "N/A"); + else gf_fprintf(dump, LLU, ts ); + } + else if (!strcmp(key, "ddts")) { + u64 ts = gf_filter_pck_get_dts(pck); + if ((ts==GF_FILTER_NO_TS) || (pctx->prev_dts==GF_FILTER_NO_TS)) gf_fprintf(dump, "N/A"); + else if (pctx->pck_num<=1) gf_fprintf(dump, "N/A"); + else { + s64 diff = ts; + diff -= pctx->prev_dts; + gf_fprintf(dump, LLD, diff); + } + pctx->prev_dts = ts; + } + else if (!strcmp(key, "dcts")) { + u64 ts = gf_filter_pck_get_cts(pck); + if ((ts==GF_FILTER_NO_TS) || (pctx->prev_cts==GF_FILTER_NO_TS)) gf_fprintf(dump, "N/A"); + else if (pctx->pck_num<=1) gf_fprintf(dump, "N/A"); + else { + s64 diff = ts; + diff -= pctx->prev_cts; + gf_fprintf(dump, LLD, diff); + } + pctx->prev_cts = ts; + } + else if (!strcmp(key, "ctso")) { + u64 dts = gf_filter_pck_get_dts(pck); + u64 cts = gf_filter_pck_get_cts(pck); + if (dts==GF_FILTER_NO_TS) dts = cts; + if (cts==GF_FILTER_NO_TS) gf_fprintf(dump, "N/A"); + else gf_fprintf(dump, LLD, ((s64)cts) - ((s64)dts) ); + } + + else if (!strcmp(key, "dur")) gf_fprintf(dump, "%u", gf_filter_pck_get_duration(pck) ); + else if (!strcmp(key, "frame")) { + Bool start, end; + gf_filter_pck_get_framing(pck, &start, &end); + if (start && end) gf_fprintf(dump, "frame_full"); + else if (start) gf_fprintf(dump, "frame_start"); + else if (end) gf_fprintf(dump, "frame_end"); + else gf_fprintf(dump, "frame_cont"); + } + else if (!strcmp(key, "sap") || !strcmp(key, "rap")) { + u32 sap = gf_filter_pck_get_sap(pck); + if (sap==GF_FILTER_SAP_4_PROL) { + gf_fprintf(dump, "4 (prol)"); + } else { + gf_fprintf(dump, "%u", sap ); + } + } + else if (!strcmp(key, "ilace")) gf_fprintf(dump, "%d", gf_filter_pck_get_interlaced(pck) ); + else if (!strcmp(key, "corr")) gf_fprintf(dump, "%d", gf_filter_pck_get_corrupted(pck) ); + else if (!strcmp(key, "seek")) gf_fprintf(dump, "%d", gf_filter_pck_get_seek_flag(pck) ); + else if (!strcmp(key, "bo")) { + u64 bo = gf_filter_pck_get_byte_offset(pck); + if (bo==GF_FILTER_NO_BO) gf_fprintf(dump, "N/A"); + else gf_fprintf(dump, LLU, bo); + } + else if (!strcmp(key, "roll")) gf_fprintf(dump, "%d", gf_filter_pck_get_roll_info(pck) ); + else if (!strcmp(key, "crypt")) gf_fprintf(dump, "%d", gf_filter_pck_get_crypt_flags(pck) ); + else if (!strcmp(key, "vers")) gf_fprintf(dump, "%d", gf_filter_pck_get_carousel_version(pck) ); + else if (!strcmp(key, "size")) gf_fprintf(dump, "%d", size ); + else if (!strcmp(key, "crc")) gf_fprintf(dump, "0x%08X", gf_crc_32(data, size) ); + else if (!strcmp(key, "lf")) gf_fprintf(dump, "\n" ); + else if (!strcmp(key, "cr")) gf_fprintf(dump, "\r" ); + else if (!strcmp(key, "t")) gf_fprintf(dump, "\t" ); + else if (!strcmp(key, "data")) { + u32 i; + for (i=0; i>= 6; + gf_fprintf(dump, "%u", flags); + } + else if (!strcmp(key, "depo")) { + u8 flags = gf_filter_pck_get_dependency_flags(pck); + flags >>= 4; + flags &= 0x3; + gf_fprintf(dump, "%u", flags); + } + else if (!strcmp(key, "depf")) { + u8 flags = gf_filter_pck_get_dependency_flags(pck); + flags >>= 2; + flags &= 0x3; + gf_fprintf(dump, "%u", flags); + } + else if (!strcmp(key, "red")) { + u8 flags = gf_filter_pck_get_dependency_flags(pck); + flags &= 0x3; + gf_fprintf(dump, "%u", flags); + } + else if (!strcmp(key, "ck")) gf_fprintf(dump, "%d", gf_filter_pck_get_clock_type(pck) ); + else if (!strncmp(key, "pid.", 4)) { + const GF_PropertyValue *prop = NULL; + u32 prop_4cc=0; + const char *pkey = key+4; + prop_4cc = gf_props_get_id(pkey); + if (!prop_4cc && strlen(pkey)==4) prop_4cc = GF_4CC(pkey[0],pkey[1],pkey[2],pkey[3]); + + if (prop_4cc) { + prop = gf_filter_pid_get_property(pctx->src_pid, prop_4cc); + } + if (!prop) prop = gf_filter_pid_get_property_str(pctx->src_pid, key); + + if (prop) { + gf_fprintf(dump, "%s", gf_props_dump(prop_4cc, prop, szDump, ctx->dump_data) ); + } + } + else { + const GF_PropertyValue *prop = NULL; + u32 prop_4cc = gf_props_get_id(key); + if (!prop_4cc && strlen(key)==4) prop_4cc = GF_4CC(key[0],key[1],key[2],key[3]); + + if (prop_4cc) { + prop = gf_filter_pck_get_property(pck, prop_4cc); + } + if (!prop) prop = gf_filter_pck_get_property_str(pck, key); + + if (prop) { + gf_fprintf(dump, "%s", gf_props_dump(prop_4cc, prop, szDump, ctx->dump_data) ); + } + } + + sep[0] = csep; + str = sep+1; + } +} + +void gf_m4v_parser_set_inspect(GF_M4VParser *m4v); +u32 gf_m4v_parser_get_obj_type(GF_M4VParser *m4v); + +#ifndef GPAC_DISABLE_AV_PARSERS +static void inspect_dump_mpeg124(PidCtx *pctx, char *data, u32 size, FILE *dump) +{ + u8 ftype; + u32 tinc, o_type; + u64 fsize, start; + Bool is_coded, is_m4v=(pctx->codec_id==GF_CODECID_MPEG4_PART2) ? GF_TRUE : GF_FALSE; + GF_Err e; + GF_M4VParser *m4v = gf_m4v_parser_new(data, size, !is_m4v); + + gf_m4v_parser_set_inspect(m4v); + while (1) { + ftype = 0; + is_coded = GF_FALSE; + e = gf_m4v_parse_frame(m4v, &pctx->dsi, &ftype, &tinc, &fsize, &start, &is_coded); + if (e) + break; + + o_type = gf_m4v_parser_get_obj_type(m4v); + if (is_m4v) { + gf_fprintf(dump, "dsi.VideoPL); + break; + case M4V_VOL_START_CODE: + gf_fprintf(dump, " name=\"VOL\" RAP=\"%d\" objectType=\"%d\" par=\"%d/%d\" hasShape=\"%d\"", pctx->dsi.RAP_stream, pctx->dsi.objectType, pctx->dsi.par_num, pctx->dsi.par_den, pctx->dsi.has_shape); + if (pctx->dsi.clock_rate) + gf_fprintf(dump, " clockRate=\"%d\"", pctx->dsi.clock_rate); + if (pctx->dsi.time_increment) + gf_fprintf(dump, " timeIncrement=\"%d\"", pctx->dsi.time_increment); + if (!pctx->dsi.has_shape) + gf_fprintf(dump, " width=\"%d\" height=\"%d\"", pctx->dsi.width, pctx->dsi.height); + + break; + + case M4V_VOP_START_CODE: + gf_fprintf(dump, " name=\"VOP\" frameType=\"%d\" timeInc=\"%d\" isCoded=\"%d\"", ftype, tinc, is_coded); + break; + case M4V_GOV_START_CODE: + gf_fprintf(dump, " name=\"GOV\""); + break; + /*don't interest us*/ + case M4V_UDTA_START_CODE: + gf_fprintf(dump, " name=\"UDTA\""); + break; + + case M4V_VO_START_CODE: + gf_fprintf(dump, " name=\"VO\""); + break; + case M4V_VISOBJ_START_CODE: + gf_fprintf(dump, " name=\"VisObj\""); + break; + default: + break; + } + gf_fprintf(dump, "/>\n"); + } else { + gf_fprintf(dump, "dsi.width, pctx->dsi.height, pctx->dsi.par_num, pctx->dsi.par_den, pctx->dsi.fps); + break; + + case M2V_EXT_START_CODE: + gf_fprintf(dump, " name=\"SeqStartEXT\" width=\"%d\" height=\"%d\" PL=\"%d\"", pctx->dsi.width, pctx->dsi.height, pctx->dsi.VideoPL); + break; + case M2V_PIC_START_CODE: + gf_fprintf(dump, " name=\"PicStart\" frameType=\"%d\" isCoded=\"%d\"", ftype, is_coded); + break; + case M2V_GOP_START_CODE: + gf_fprintf(dump, " name=\"GOPStart\""); + break; + default: + break; + } + gf_fprintf(dump, "/>\n"); + } + } + gf_m4v_parser_del(m4v); +} +#endif + +static void inspect_dump_tmcd(GF_InspectCtx *ctx, PidCtx *pctx, char *data, u32 size, FILE *dump) +{ + u32 h, m, s, f; + Bool neg=GF_FALSE; + + if (!pctx->tmcd_rate.den || !pctx->tmcd_rate.num) + return; + gf_bs_reassign_buffer(pctx->bs, data, size); + + u32 value = gf_bs_read_u32(pctx->bs); + gf_bs_seek(pctx->bs, 0); + gf_fprintf(dump, "fftmcd || (pctx->tmcd_flags & 0x00000008)) { + u64 nb_secs, nb_frames = value; + Bool is_drop = GF_FALSE; + if (!ctx->fftmcd && pctx->tmcd_fpt) + nb_frames *= pctx->tmcd_fpt; + + if (ctx->fftmcd) { + u32 fps = pctx->tmcd_rate.num / pctx->tmcd_rate.den; + if (fps * pctx->tmcd_rate.den != pctx->tmcd_rate.num) + is_drop = GF_TRUE; + } + else if (pctx->tmcd_flags & 0x00000001) { + is_drop = GF_TRUE; + } + + if (is_drop) { + u32 drop_frames, frame_base; + + frame_base = 100 * pctx->tmcd_rate.num; + frame_base /= pctx->tmcd_rate.den; + + drop_frames = (u32) (nb_frames / frame_base); + nb_frames -= 3*drop_frames; + } + + nb_secs = nb_frames * pctx->tmcd_rate.den / pctx->tmcd_rate.num; + + gf_fprintf(dump, " counter=\"%d\"", value); + h = (u32) nb_secs/3600; + m = (u32) (nb_secs/60 - h*60); + s = (u32) (nb_secs - m*60 - h*3600); + + nb_secs *= pctx->tmcd_rate.num; + nb_secs /= pctx->tmcd_rate.den; + f = (u32) (nb_frames - nb_secs); + if (pctx->tmcd_fpt && (f==pctx->tmcd_fpt)) { + f = 0; + s++; + if (s==60) { + s = 0; + m++; + if (m==60) { + m = 0; + h++; + } + } + } + } else { + h = gf_bs_read_u8(pctx->bs); + neg = gf_bs_read_int(pctx->bs, 1); + m = gf_bs_read_int(pctx->bs, 7); + s = gf_bs_read_u8(pctx->bs); + f = gf_bs_read_u8(pctx->bs); + } + gf_fprintf(dump, " time=\"%s%02d:%02d:%02d:%02d\"/>\n", neg ? "-" : "", h, m, s, f); +} + +static void inspect_dump_packet(GF_InspectCtx *ctx, FILE *dump, GF_FilterPacket *pck, u32 pid_idx, u64 pck_num, PidCtx *pctx) +{ + u32 idx=0, size, sap; + u64 ts; + u8 dflags = 0; + GF_FilterClockType ck_type; + GF_FilterFrameInterface *fifce=NULL; + Bool start, end; + u8 *data; + if (!dump) return; + + if (!ctx->deep && !ctx->fmt) return; + + data = (u8 *) gf_filter_pck_get_data(pck, &size); + gf_filter_pck_get_framing(pck, &start, &end); + + ck_type = ctx->pcr ? gf_filter_pck_get_clock_type(pck) : 0; + if (!size && !ck_type) { + fifce = gf_filter_pck_get_frame_interface(pck); + if (!fifce) return; + } + + if (ctx->xml) { + gf_fprintf(dump, "interleave) + gf_fprintf(dump, " PID=\"%d\"", pid_idx); + } else { + gf_fprintf(dump, "PID %d PCK "LLU" - ", pid_idx, pck_num); + } + if (ck_type) { + ts = gf_filter_pck_get_cts(pck); + if (ctx->xml) { + if (ts==GF_FILTER_NO_TS) gf_fprintf(dump, " PCR=\"N/A\""); + else gf_fprintf(dump, " PCR=\""LLU"\" ", ts ); + if (ck_type!=GF_FILTER_CLOCK_PCR) gf_fprintf(dump, " discontinuity=\"true\""); + gf_fprintf(dump, "/>"); + } else { + if (ts==GF_FILTER_NO_TS) gf_fprintf(dump, " PCR N/A"); + else gf_fprintf(dump, " PCR%s "LLU"\n", (ck_type==GF_FILTER_CLOCK_PCR) ? "" : " discontinuity", ts ); + } + return; + } + if (ctx->xml) { + if (fifce) gf_fprintf(dump, " framing=\"interface\""); + else if (start && end) gf_fprintf(dump, " framing=\"complete\""); + else if (start) gf_fprintf(dump, " framing=\"start\""); + else if (end) gf_fprintf(dump, " framing=\"end\""); + else gf_fprintf(dump, " framing=\"continuation\""); + } else { + if (fifce) gf_fprintf(dump, "interface"); + else if (start && end) gf_fprintf(dump, "full frame"); + else if (start) gf_fprintf(dump, "frame start"); + else if (end) gf_fprintf(dump, "frame end"); + else gf_fprintf(dump, "frame continuation"); + } + ts = gf_filter_pck_get_dts(pck); + if (ts==GF_FILTER_NO_TS) DUMP_ATT_STR("dts", "N/A") + else DUMP_ATT_LLU("dts", ts ) + + ts = gf_filter_pck_get_cts(pck); + if (ts==GF_FILTER_NO_TS) DUMP_ATT_STR("cts", "N/A") + else DUMP_ATT_LLU("cts", ts ) + + DUMP_ATT_U("dur", gf_filter_pck_get_duration(pck) ) + sap = gf_filter_pck_get_sap(pck); + if (sap==GF_FILTER_SAP_4_PROL) { + DUMP_ATT_STR(sap, "4 (prol)"); + } else { + DUMP_ATT_U("sap", gf_filter_pck_get_sap(pck) ) + } + DUMP_ATT_D("ilace", gf_filter_pck_get_interlaced(pck) ) + DUMP_ATT_D("corr", gf_filter_pck_get_corrupted(pck) ) + DUMP_ATT_D("seek", gf_filter_pck_get_seek_flag(pck) ) + + ts = gf_filter_pck_get_byte_offset(pck); + if (ts==GF_FILTER_NO_BO) DUMP_ATT_STR("bo", "N/A") + else DUMP_ATT_LLU("bo", ts ) + + DUMP_ATT_U("roll", gf_filter_pck_get_roll_info(pck) ) + DUMP_ATT_U("crypt", gf_filter_pck_get_crypt_flags(pck) ) + DUMP_ATT_U("vers", gf_filter_pck_get_carousel_version(pck) ) + + if (!fifce) { + DUMP_ATT_U("size", size ) + } + dflags = gf_filter_pck_get_dependency_flags(pck); + DUMP_ATT_U("lp", (dflags>>6) & 0x3 ) + DUMP_ATT_U("depo", (dflags>>4) & 0x3 ) + DUMP_ATT_U("depf", (dflags>>2) & 0x3 ) + DUMP_ATT_U("red", (dflags) & 0x3 ) + + if (!data) size = 0; + if (ctx->dump_data) { + u32 i; + DUMP_ATT_STR("data", "") + for (i=0; ixml) gf_fprintf(dump, "\""); + } else if (fifce) { + u32 i; + char *name = fifce->get_gl_texture ? "Interface_GLTexID" : "Interface_NumPlanes"; + if (ctx->xml) { + gf_fprintf(dump, " %s=\"", name); + } else { + gf_fprintf(dump, " %s ", name); + } + for (i=0; i<4; i++) { + if (fifce->get_gl_texture) { + GF_Matrix mx; + gf_mx_init(mx); + u32 gl_tex_format, gl_tex_id; + if (fifce->get_gl_texture(fifce, i, &gl_tex_format, &gl_tex_id, &mx) != GF_OK) + break; + if (i) gf_fprintf(dump, ","); + gf_fprintf(dump, "%d", gl_tex_id); + } else { + u32 stride; + const u8 *plane; + if (fifce->get_plane(fifce, i, &plane, &stride) != GF_OK) + break; + } + } + if (!fifce->get_gl_texture) { + gf_fprintf(dump, "%d", i); + } + + if (ctx->xml) { + gf_fprintf(dump, "\""); + } + } else if (data) { + DUMP_ATT_X("CRC32", gf_crc_32(data, size) ) + } + if (ctx->xml) { + if (!ctx->props) goto props_done; + + } else { + gf_fprintf(dump, "\n"); + } + + if (!ctx->props) return; + while (1) { + u32 prop_4cc; + const char *prop_name; + const GF_PropertyValue * p = gf_filter_pck_enum_properties(pck, &idx, &prop_4cc, &prop_name); + if (!p) break; + if (idx==0) gf_fprintf(dump, "properties:\n"); + + inspect_dump_property(ctx, dump, prop_4cc, prop_name, p, pctx); + } + + +props_done: + if (!ctx->analyze) { + if (ctx->xml) { + gf_fprintf(dump, "/>\n"); + } + return; + } + gf_fprintf(dump, ">\n"); + +#ifndef GPAC_DISABLE_AV_PARSERS + if (pctx->hevc_state || pctx->avc_state) { + idx=1; + + if (pctx->is_adobe_protected && size) { + u8 encrypted_au = data[0]; + if (encrypted_au) { + gf_fprintf(dump, " \n"); + gf_fprintf(dump, "\n"); + return; + } + else { + data++; + size--; + } + } + while (size) { + u32 nal_size = inspect_get_nal_size((char*)data, pctx->nalu_size_length); + data += pctx->nalu_size_length; + + if (pctx->nalu_size_length + nal_size > size) { + gf_fprintf(dump, " \n", nal_size, size); + break; + } else { + gf_fprintf(dump, " has_svcc ? 1 : 0, pctx->hevc_state, pctx->avc_state, pctx->nalu_size_length, ctx->dump_crc, pctx->is_cenc_protected, pctx); + gf_fprintf(dump, "/>\n"); + } + idx++; + data += nal_size; + size -= nal_size + pctx->nalu_size_length; + } + } else if (pctx->av1_state) { + gf_bs_reassign_buffer(pctx->bs, data, size); + while (size) { + ObuType obu_type; + u64 obu_size; + u32 hdr_size; + gf_media_aom_av1_parse_obu(pctx->bs, &obu_type, &obu_size, &hdr_size, pctx->av1_state); + + if (obu_size > size) { + gf_fprintf(dump, " \n", (u32) obu_size, size); + break; + } + + gf_inspect_dump_obu(dump, pctx->av1_state, (char *) data, obu_size, obu_type, obu_size, hdr_size, ctx->dump_crc); + data += obu_size; + size -= (u32)obu_size; + idx++; + } + } else { + u32 hdr, pos, fsize, i; + switch (pctx->codec_id) { + case GF_CODECID_MPEG1: + case GF_CODECID_MPEG2_422: + case GF_CODECID_MPEG2_SNR: + case GF_CODECID_MPEG2_HIGH: + case GF_CODECID_MPEG2_MAIN: + case GF_CODECID_MPEG4_PART2: + inspect_dump_mpeg124(pctx, (char *) data, size, dump); + break; + case GF_CODECID_MPEG_AUDIO: + case GF_CODECID_MPEG2_PART3: + pos = 0; + while (size) { + hdr = gf_mp3_get_next_header_mem(data, size, &pos); + fsize = gf_mp3_frame_size(hdr); + gf_fprintf(dump, "\n", fsize, gf_mp3_layer(hdr), gf_mp3_version(hdr), gf_mp3_bit_rate(hdr), gf_mp3_num_channels(hdr), gf_mp3_window_size(hdr), gf_mp3_sampling_rate(hdr)); + if (size\n"); + break; + case GF_CODECID_SUBS_XML: + case GF_CODECID_META_XML: + for (i=0; idump_crc, pctx); + break; + + case GF_CODECID_MPHA: + gf_bs_reassign_buffer(pctx->bs, data, size); + gf_inspect_dump_mha_frame(dump, pctx->bs, ""); + break; + + case GF_CODECID_MHAS: + gf_inspect_dump_mhas(dump, (char *) data, size, ctx->dump_crc, pctx); + break; + + } + } +#endif + gf_fprintf(dump, "\n"); +} + +#define DUMP_ARRAY(arr, name, loc, _is_svc)\ + if (arr && gf_list_count(arr)) {\ + gf_fprintf(dump, " <%sArray location=\"%s\">\n", name, loc);\ + for (i=0; isize);\ + gf_inspect_dump_nalu_internal(dump, slc->data, slc->size, _is_svc, pctx->hevc_state, pctx->avc_state, nalh_size, ctx->dump_crc, GF_FALSE, pctx);\ + gf_fprintf(dump, "/>\n");\ + }\ + gf_fprintf(dump, " \n", name);\ + }\ + + +#ifndef GPAC_DISABLE_AV_PARSERS +static void inspect_reset_parsers(PidCtx *pctx, void *keep_parser_address) +{ + if ((&pctx->hevc_state != keep_parser_address) && pctx->hevc_state) { + gf_free(pctx->hevc_state); + pctx->hevc_state = NULL; + } + if ((&pctx->avc_state != keep_parser_address) && pctx->avc_state) { + gf_free(pctx->avc_state); + pctx->avc_state = NULL; + } + if ((&pctx->av1_state != keep_parser_address) && pctx->av1_state) { + if (pctx->av1_state->config) gf_odf_av1_cfg_del(pctx->av1_state->config); + gf_free(pctx->av1_state); + pctx->av1_state = NULL; + } + if ((&pctx->mv124_state != keep_parser_address) && pctx->mv124_state) { + gf_m4v_parser_del(pctx->mv124_state); + pctx->mv124_state = NULL; + } +} +#endif + +static void inspect_dump_pid(GF_InspectCtx *ctx, FILE *dump, GF_FilterPid *pid, u32 pid_idx, Bool is_connect, Bool is_remove, u64 pck_for_config, Bool is_info, PidCtx *pctx) +{ + u32 idx=0, nalh_size; +#ifndef GPAC_DISABLE_AV_PARSERS + u32 i; + GF_AVCConfigSlot *slc; +#endif + GF_AVCConfig *avcc, *svcc; + GF_HEVCConfig *hvcc, *lhcc; + Bool is_enh = GF_FALSE; + Bool is_svc=GF_FALSE; + char *elt_name = NULL; + const GF_PropertyValue *p, *dsi, *dsi_enh; + + if (!ctx->dump) return; + if (ctx->test==INSPECT_TEST_NOPROP) return; + + //disconnect of src pid (not yet supported) + if (ctx->xml) { + if (is_info) { + elt_name = "PIDInfoUpdate"; + } else if (is_remove) { + elt_name = "PIDRemove"; + } else { + elt_name = is_connect ? "PIDConfigure" : "PIDReconfigure"; + } + gf_fprintf(dump, "<%s PID=\"%d\" name=\"%s\"",elt_name, pid_idx, gf_filter_pid_get_name(pid) ); + + if (pck_for_config) + gf_fprintf(dump, " packetsSinceLastConfig=\""LLU"\"", pck_for_config); + } else { + if (is_info) { + gf_fprintf(dump, "PID %d name %s info update\n", pid_idx, gf_filter_pid_get_name(pid) ); + } else if (is_remove) { + gf_fprintf(dump, "PID %d name %s delete\n", pid_idx, gf_filter_pid_get_name(pid) ); + } else { + gf_fprintf(dump, "PID %d name %s %sonfigure", pid_idx, gf_filter_pid_get_name(pid), is_connect ? "C" : "Rec" ); + if (pck_for_config) + gf_fprintf(dump, " after "LLU" packets", pck_for_config); + gf_fprintf(dump, " - properties:\n"); + } + } + + if (!is_info) { + while (1) { + u32 prop_4cc; + const char *prop_name; + p = gf_filter_pid_enum_properties(pid, &idx, &prop_4cc, &prop_name); + if (!p) break; + inspect_dump_property(ctx, dump, prop_4cc, prop_name, p, pctx); + + if (prop_name) { + if (!strcmp(prop_name, "tmcd:flags")) + pctx->tmcd_flags = p->value.uint; + else if (!strcmp(prop_name, "tmcd:framerate")) + pctx->tmcd_rate = p->value.frac; + else if (!strcmp(prop_name, "tmcd:frames_per_tick")) + pctx->tmcd_fpt = p->value.uint; + + } + } + } else { + while (ctx->info) { + u32 prop_4cc; + const char *prop_name; + p = gf_filter_pid_enum_info(pid, &idx, &prop_4cc, &prop_name); + if (!p) break; + inspect_dump_property(ctx, dump, prop_4cc, prop_name, p, pctx); + } + } + + if (!ctx->analyze) { + if (ctx->xml) { + gf_fprintf(dump, "/>\n"); + } + return; + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p) { + gf_fprintf(dump, "/>\n"); + return; + } + pctx->codec_id = p->value.uint; + + dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + dsi_enh = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + + avcc = NULL; + svcc = NULL; + hvcc = NULL; + lhcc = NULL; + pctx->has_svcc = 0; + + switch (pctx->codec_id) { + case GF_CODECID_SVC: + case GF_CODECID_MVC: + if (!dsi) is_enh = GF_TRUE; + case GF_CODECID_AVC: + case GF_CODECID_AVC_PS: + if (!dsi && !dsi_enh) { + gf_fprintf(dump, "/>\n"); + return; + } +#ifndef GPAC_DISABLE_AV_PARSERS + inspect_reset_parsers(pctx, &pctx->avc_state); + + if (!pctx->avc_state) { + GF_SAFEALLOC(pctx->avc_state, AVCState); + if (!pctx->avc_state) return; + } +#endif + gf_fprintf(dump, ">\n"); + gf_fprintf(dump, "\n"); + if (dsi) { + if (is_enh) { + svcc = gf_odf_avc_cfg_read(dsi->value.data.ptr, dsi->value.data.size); + if (svcc) + pctx->nalu_size_length = svcc->nal_unit_size; + } else { + avcc = gf_odf_avc_cfg_read(dsi->value.data.ptr, dsi->value.data.size); + if (avcc) + pctx->nalu_size_length = avcc->nal_unit_size; + } + } + if (dsi_enh && !svcc) { + svcc = gf_odf_avc_cfg_read(dsi_enh->value.data.ptr, dsi_enh->value.data.size); + if (svcc) + pctx->nalu_size_length = svcc->nal_unit_size; + } + nalh_size = pctx->nalu_size_length; + is_svc = (svcc != NULL) ? 1 : 0; +#ifndef GPAC_DISABLE_AV_PARSERS + if (avcc) { + DUMP_ARRAY(avcc->sequenceParameterSets, "AVCSPS", "decoderConfig", is_svc) + DUMP_ARRAY(avcc->pictureParameterSets, "AVCPPS", "decoderConfig", is_svc) + DUMP_ARRAY(avcc->sequenceParameterSetExtensions, "AVCSPSEx", "decoderConfig", is_svc) + } + if (is_svc) { + DUMP_ARRAY(svcc->sequenceParameterSets, "SVCSPS", dsi_enh ? "decoderConfigEnhancement" : "decoderConfig", is_svc) + DUMP_ARRAY(svcc->pictureParameterSets, "SVCPPS", dsi_enh ? "decoderConfigEnhancement" : "decoderConfig", is_svc) + pctx->has_svcc = 1; + } +#endif + gf_fprintf(dump, "\n"); + break; + case GF_CODECID_LHVC: + is_enh = GF_TRUE; + case GF_CODECID_HEVC: + case GF_CODECID_HEVC_TILES: + if (!dsi && !dsi_enh) { + gf_fprintf(dump, "/>\n"); + return; + } + +#ifndef GPAC_DISABLE_AV_PARSERS + inspect_reset_parsers(pctx, &pctx->hevc_state); + + if (!pctx->hevc_state) { + GF_SAFEALLOC(pctx->hevc_state, HEVCState); + if (!pctx->hevc_state) return; + } +#endif + + if (dsi) { + if (is_enh && !dsi_enh) { + lhcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, GF_TRUE); + if (lhcc) + pctx->nalu_size_length = lhcc->nal_unit_size; + } else { + hvcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, GF_FALSE); + if (hvcc) + pctx->nalu_size_length = hvcc->nal_unit_size; + } + } + if (dsi_enh && !lhcc) { + lhcc = gf_odf_hevc_cfg_read(dsi_enh->value.data.ptr, dsi_enh->value.data.size, GF_TRUE); + if (lhcc) + pctx->nalu_size_length = lhcc->nal_unit_size; + } + nalh_size = pctx->nalu_size_length; + + gf_fprintf(dump, ">\n"); + gf_fprintf(dump, "\n"); +#ifndef GPAC_DISABLE_AV_PARSERS + if (hvcc) { + for (idx=0; idxparam_array); idx++) { + GF_HEVCParamArray *ar = gf_list_get(hvcc->param_array, idx); + if (ar->type==GF_HEVC_NALU_SEQ_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCSPS", "hvcC", 0) + } else if (ar->type==GF_HEVC_NALU_PIC_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCPPS", "hvcC", 0) + } else if (ar->type==GF_HEVC_NALU_VID_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCVPS", "hvcC", 0) + } else { + DUMP_ARRAY(ar->nalus, "HEVCUnknownPS", "hvcC", 0) + } + } + } + if (lhcc) { + for (idx=0; idxparam_array); idx++) { + GF_HEVCParamArray *ar = gf_list_get(lhcc->param_array, idx); + if (ar->type==GF_HEVC_NALU_SEQ_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCSPS", "lhcC", 0) + } else if (ar->type==GF_HEVC_NALU_PIC_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCPPS", "lhcC", 0) + } else if (ar->type==GF_HEVC_NALU_VID_PARAM) { + DUMP_ARRAY(ar->nalus, "HEVCVPS", "lhcC", 0) + } else { + DUMP_ARRAY(ar->nalus, "HEVCUnknownPS", "lhcC", 0) + } + } + } +#endif + gf_fprintf(dump, "\n"); + break; + + case GF_CODECID_AV1: +#ifndef GPAC_DISABLE_AV_PARSERS + inspect_reset_parsers(pctx, &pctx->av1_state); + if (!pctx->av1_state) { + GF_SAFEALLOC(pctx->av1_state, AV1State); + if (!pctx->av1_state) return; + gf_av1_init_state(pctx->av1_state); + } +#endif + if (!dsi) { + gf_fprintf(dump, "/>\n"); + return; + } +#ifndef GPAC_DISABLE_AV_PARSERS + if (pctx->av1_state->config) gf_odf_av1_cfg_del(pctx->av1_state->config); + pctx->av1_state->config = gf_odf_av1_cfg_read(dsi->value.data.ptr, dsi->value.data.size); + gf_fprintf(dump, ">\n"); + gf_fprintf(dump, " \n"); + + idx = 1; + for (i=0; iav1_state->config->obu_array); i++) { + ObuType obu_type; + u64 obu_size; + u32 hdr_size; + GF_AV1_OBUArrayEntry *obu = gf_list_get(pctx->av1_state->config->obu_array, i); + + if (!pctx->bs) + pctx->bs = gf_bs_new((const u8 *)obu->obu, (u32) obu->obu_length, GF_BITSTREAM_READ); + else + gf_bs_reassign_buffer(pctx->bs, (const u8 *)obu->obu, (u32) obu->obu_length); + + gf_media_aom_av1_parse_obu(pctx->bs, &obu_type, &obu_size, &hdr_size, pctx->av1_state); + gf_inspect_dump_obu(dump, pctx->av1_state, (char*)obu->obu, obu->obu_length, obu_type, obu_size, hdr_size, ctx->dump_crc); + idx++; + } +#endif + gf_fprintf(dump, " \n"); + break; + + case GF_CODECID_MPEG1: + case GF_CODECID_MPEG2_422: + case GF_CODECID_MPEG2_SNR: + case GF_CODECID_MPEG2_HIGH: + case GF_CODECID_MPEG2_MAIN: + case GF_CODECID_MPEG4_PART2: + +#ifndef GPAC_DISABLE_AV_PARSERS + inspect_reset_parsers(pctx, &pctx->mv124_state); +#endif + if (!dsi) { + gf_fprintf(dump, "/>\n"); + return; + } + gf_fprintf(dump, ">\n"); + gf_fprintf(dump, " \n"); +#ifndef GPAC_DISABLE_AV_PARSERS + inspect_dump_mpeg124(pctx, dsi->value.data.ptr, dsi->value.data.size, dump); +#endif + gf_fprintf(dump, " \n"); +#ifndef GPAC_DISABLE_AV_PARSERS + if (pctx->mv124_state) gf_m4v_parser_del(pctx->mv124_state); +#endif + break; + case GF_CODECID_MPEG_AUDIO: + case GF_CODECID_MPEG2_PART3: + case GF_CODECID_TMCD: + gf_fprintf(dump, "/>\n"); + return; + case GF_CODECID_SUBS_XML: + case GF_CODECID_META_XML: + if (!dsi) { + gf_fprintf(dump, "/>\n"); + return; + } + gf_fprintf(dump, " \n"); + for (i=0; ivalue.data.size; i++) { + gf_fputc(dsi->value.data.ptr[i], dump); + } + gf_fprintf(dump, "\n \n"); + gf_fprintf(dump, "/>\n"); + return; + case GF_CODECID_SUBS_TEXT: + case GF_CODECID_META_TEXT: + if (!dsi) { + gf_fprintf(dump, "/>\n"); + return; + } + gf_fprintf(dump, " \n"); + gf_fprintf(dump, "value.data.size; i++) { + gf_fputc(dsi->value.data.ptr[i], dump); + } + gf_fprintf(dump, "]]>\n"); + gf_fprintf(dump, " \n"); + gf_fprintf(dump, "/>\n"); + return; + case GF_CODECID_APCH: + case GF_CODECID_APCN: + case GF_CODECID_APCS: + case GF_CODECID_APCO: + case GF_CODECID_AP4H: + case GF_CODECID_AP4X: + gf_fprintf(dump, "/>\n"); + return; + case GF_CODECID_MPHA: + case GF_CODECID_MHAS: + if (dsi) { + u16 size; + if (!pctx->bs) + pctx->bs = gf_bs_new(dsi->value.data.ptr, dsi->value.data.size, GF_BITSTREAM_READ); + else + gf_bs_reassign_buffer(pctx->bs, dsi->value.data.ptr, dsi->value.data.size); + + gf_fprintf(dump, " bs) ); + gf_fprintf(dump, " ProfileLevelIndication=\"%d\"", gf_bs_read_u8(pctx->bs) ); + gf_fprintf(dump, " ReferenceChannelLayout=\"%d\"", gf_bs_read_u8(pctx->bs) ); + size = gf_bs_read_u16(pctx->bs); + if (size) { + gf_fprintf(dump, ">\n"); + dump_mha_config(dump, pctx->bs, " "); + gf_fprintf(dump, " \n"); + } else { + gf_fprintf(dump, "/>\n"); + } + gf_fprintf(dump, "/>\n"); + } + gf_fprintf(dump, "/>\n"); + return; + default: + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[Inspect] bitstream analysis for codec %s not supported\n", gf_codecid_name(pctx->codec_id))); + gf_fprintf(dump, "/>\n"); + return; + } + + if (avcc) gf_odf_avc_cfg_del(avcc); + if (svcc) gf_odf_avc_cfg_del(svcc); + if (hvcc) gf_odf_hevc_cfg_del(hvcc); + if (lhcc) gf_odf_hevc_cfg_del(lhcc); + + gf_fprintf(dump, "\n", elt_name); +} + +static GF_Err inspect_process(GF_Filter *filter) +{ + u32 i, count, nb_done=0; + GF_InspectCtx *ctx = (GF_InspectCtx *) gf_filter_get_udta(filter); + + count = gf_list_count(ctx->src_pids); + + if (ctx->args_updated) { + ctx->args_updated = GF_FALSE; + for (i=0; isrc_pids, i); + switch (ctx->mode) { + case INSPECT_MODE_PCK: + case INSPECT_MODE_REFRAME: + gf_filter_pid_set_framing_mode(pctx->src_pid, GF_TRUE); + break; + default: + gf_filter_pid_set_framing_mode(pctx->src_pid, GF_FALSE); + break; + } + gf_filter_pid_set_clock_mode(pctx->src_pid, ctx->pcr); + } + if (ctx->is_prober || ctx->deep || ctx->fmt) { + ctx->dump_pck = GF_TRUE; + } else { + ctx->dump_pck = GF_FALSE; + } + } + count = gf_list_count(ctx->src_pids); + for (i=0; isrc_pids, i); + GF_FilterPacket *pck = gf_filter_pid_get_packet(pctx->src_pid); + + if (!pck && !gf_filter_pid_is_eos(pctx->src_pid)) + continue; + + if (pctx->dump_pid) { + inspect_dump_pid(ctx, pctx->tmp, pctx->src_pid, pctx->idx, pctx->init_pid_config_done ? GF_FALSE : GF_TRUE, GF_FALSE, pctx->pck_for_config, (pctx->dump_pid==2) ? GF_TRUE : GF_FALSE, pctx); + pctx->dump_pid = 0; + pctx->init_pid_config_done = 1; + pctx->pck_for_config=0; + + if (!ctx->hdr_done) { + ctx->hdr_done=GF_TRUE; + //dump header on main output file, not on pid one! + if (ctx->hdr && ctx->fmt && !ctx->xml) + inspect_dump_packet_fmt(ctx, ctx->dump, NULL, 0, 0); + } + } + if (!pck) continue; + + pctx->pck_for_config++; + pctx->pck_num++; + + if (ctx->dump_pck) { + + if (ctx->is_prober) { + nb_done++; + } else if (ctx->fmt) { + inspect_dump_packet_fmt(ctx, pctx->tmp, pck, pctx, pctx->pck_num); + } else { + inspect_dump_packet(ctx, pctx->tmp, pck, pctx->idx, pctx->pck_num, pctx); + } + } + + if (ctx->dur.num && ctx->dur.den) { + u64 timescale = gf_filter_pck_get_timescale(pck); + u64 ts = gf_filter_pck_get_dts(pck); + if (ts == GF_FILTER_NO_TS) ts = gf_filter_pck_get_cts(pck); + + if (!pctx->init_ts) pctx->init_ts = ts; + else if (ctx->dur.den * (ts - pctx->init_ts) >= ctx->dur.num * timescale) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_STOP, pctx->src_pid); + gf_filter_pid_send_event(pctx->src_pid, &evt); + gf_filter_pid_set_discard(pctx->src_pid, GF_TRUE); + break; + } + } + gf_filter_pid_drop_packet(pctx->src_pid); + } + if (ctx->is_prober && !ctx->probe_done && (nb_done==count) && !ctx->allp) { + for (i=0; isrc_pids, i); + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_STOP, pctx->src_pid); + gf_filter_pid_send_event(pctx->src_pid, &evt); + } + ctx->probe_done = GF_TRUE; + return GF_EOS; + } + return GF_OK; +} + +static GF_Err inspect_config_input(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_FilterEvent evt; + PidCtx *pctx; + const GF_PropertyValue *p; + GF_InspectCtx *ctx = (GF_InspectCtx *) gf_filter_get_udta(filter); + + if (!ctx->src_pids) ctx->src_pids = gf_list_new(); + + pctx = gf_filter_pid_get_udta(pid); + if (pctx) { + assert(pctx->src_pid == pid); + if (!ctx->is_prober) pctx->dump_pid = 1; + return GF_OK; + } + GF_SAFEALLOC(pctx, PidCtx); + if (!pctx) return GF_OUT_OF_MEM; + if (ctx->analyze) + pctx->bs = gf_bs_new((u8 *)pctx, 0, GF_BITSTREAM_READ); + + pctx->src_pid = pid; + gf_filter_pid_set_udta(pid, pctx); + + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + pctx->stream_type = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + pctx->codec_id = p ? p->value.uint : 0; + + if (! ctx->interleave) { + u32 insert_idx = 0; + u32 i; + //sort all PIDs by codec IDs + for (i=0; isrc_pids); i++) { + PidCtx *actx = gf_list_get(ctx->src_pids, i); + insert_idx = i; + + p = gf_filter_pid_get_property(actx->src_pid, GF_PROP_PID_CODECID); + if (!p || (pctx->codec_id < p->value.uint)) { + break; + } + } + + gf_list_insert(ctx->src_pids, pctx, insert_idx); + + for (i=insert_idx+1; isrc_pids); i++) { + PidCtx *actx = gf_list_get(ctx->src_pids, i); + actx->idx = i+1; + } + } else { + pctx->tmp = ctx->dump; + gf_list_add(ctx->src_pids, pctx); + } + + pctx->idx = gf_list_find(ctx->src_pids, pctx) + 1; + + if (! ctx->interleave && !pctx->tmp && ctx->dump) { + pctx->tmp = gf_file_temp(NULL); + if (ctx->xml) + gf_fprintf(ctx->dump, "\n", pctx->idx, gf_filter_pid_get_name(pid) ); + } + + switch (ctx->mode) { + case INSPECT_MODE_PCK: + case INSPECT_MODE_REFRAME: + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + break; + default: + gf_filter_pid_set_framing_mode(pid, GF_FALSE); + break; + } + + if (!ctx->is_prober) { + pctx->dump_pid = 1; + } + + gf_filter_pid_init_play_event(pid, &evt, ctx->start, ctx->speed, "Inspect"); + gf_filter_pid_send_event(pid, &evt); + + if (ctx->is_prober || ctx->deep || ctx->fmt) { + ctx->dump_pck = GF_TRUE; + } else { + ctx->dump_pck = GF_FALSE; + } + if (ctx->pcr) + gf_filter_pid_set_clock_mode(pid, GF_TRUE); + return GF_OK; +} + +static const GF_FilterCapability InspecterDemuxedCaps[] = +{ + //accept any stream but files, framed + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + {0}, +}; + +static const GF_FilterCapability InspecterReframeCaps[] = +{ + //accept any stream but files, framed + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, +}; + +static GF_Err inspect_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *new_val) +{ + GF_InspectCtx *ctx = (GF_InspectCtx *) gf_filter_get_udta(filter); + ctx->args_updated = GF_TRUE; + return GF_OK; +} + +GF_Err inspect_initialize(GF_Filter *filter) +{ + const char *name = gf_filter_get_name(filter); + GF_InspectCtx *ctx = (GF_InspectCtx *) gf_filter_get_udta(filter); + + if (name && !strcmp(name, "probe") ) { + ctx->is_prober = GF_TRUE; + return GF_OK; + } + + if (!ctx->log) return GF_BAD_PARAM; + if (!strcmp(ctx->log, "stderr")) ctx->dump = stderr; + else if (!strcmp(ctx->log, "stdout")) ctx->dump = stdout; + else if (!strcmp(ctx->log, "null")) ctx->dump = NULL; + else { + ctx->dump = gf_fopen(ctx->log, "wt"); + if (!ctx->dump) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Inspec] Failed to open file %s\n", ctx->log)); + return GF_IO_ERR; + } + } + if (ctx->analyze) { + ctx->xml = GF_TRUE; + } + + if (ctx->xml && ctx->dump) { + ctx->fmt = NULL; + gf_fprintf(ctx->dump, "\n"); + gf_fprintf(ctx->dump, "\n"); + } + + switch (ctx->mode) { + case INSPECT_MODE_RAW: + break; + case INSPECT_MODE_REFRAME: + gf_filter_override_caps(filter, InspecterReframeCaps, sizeof(InspecterReframeCaps)/sizeof(GF_FilterCapability) ); + break; + default: + gf_filter_override_caps(filter, InspecterDemuxedCaps, sizeof(InspecterDemuxedCaps)/sizeof(GF_FilterCapability) ); + break; + } + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + inspect_update_arg(filter, NULL, NULL); + ctx->args_updated = GF_FALSE; + } +#endif + return GF_OK; +} + +static Bool inspect_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + PidCtx *pctx; + GF_InspectCtx *ctx = (GF_InspectCtx *) gf_filter_get_udta(filter); + if (evt->base.type != GF_FEVT_INFO_UPDATE) return GF_TRUE; + if (!ctx->info) return GF_TRUE; + pctx = gf_filter_pid_get_udta(evt->base.on_pid); + pctx->dump_pid = 2; + return GF_TRUE; +} + +#define OFFS(_n) #_n, offsetof(GF_InspectCtx, _n) +static const GF_FilterArgs InspectArgs[] = +{ + { OFFS(log), "set inspect log filename", GF_PROP_STRING, "stderr", "fileName, stderr, stdout or null", 0}, + { OFFS(mode), "dump mode\n" + "- pck: dump full packet\n" + "- blk: dump packets before reconstruction\n" + "- frame: force reframer\n" + "- raw: dump source packets without demuxing", GF_PROP_UINT, "pck", "pck|blk|frame|raw", 0}, + { OFFS(interleave), "dump packets as they are received on each pid. If false, report per pid is generated", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(deep), "dump packets along with PID state change, implied when [-fmt]() is set", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(props), "dump packet properties, ignored when [-fmt]() is set (see filter help)", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(dump_data), "enable full data dump (__WARNING__ heavy!), ignored when [-fmt]() is set (see filter help)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(fmt), "set packet dump format (see filter help)", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_UPDATE|GF_FS_ARG_HINT_ADVANCED}, + { OFFS(hdr), "print a header corresponding to fmt string without \'$ \'or \"pid.\"", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(allp), "analyse for the entire duration, rather than stoping when all pids are found", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(info), "monitor PID info changes", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(pcr), "dump M2TS PCR info", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT|GF_FS_ARG_UPDATE}, + { OFFS(speed), "set playback command speed. If speed is negative and start is 0, start is set to -1", GF_PROP_DOUBLE, "1.0", NULL, 0}, + { OFFS(start), "set playback start offset. Negative value means percent of media dur with -1 <=> dur", GF_PROP_DOUBLE, "0.0", NULL, 0}, + { OFFS(dur), "set inspect duration", GF_PROP_FRACTION, "0/0", NULL, 0}, + { OFFS(analyze), "analyze sample content (NALU, OBU)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(xml), "use xml formatting (implied if (-analyze]() is set) and disable [-fmt]()", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE}, + { OFFS(fftmcd), "consider timecodes use ffmpeg-compatible signaling rather than QT compliant one", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT|GF_FS_ARG_UPDATE}, + { OFFS(dtype), "dump property type", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE}, + { OFFS(test), "skip predefined set of properties, used for test mode\n" + "- no: no properties skipped\n" + "- noprop: all properties/info changes on pid are skipped, only packets are dumped\n" + "- network: URL/path dump, cache state, file size properties skipped (used for hashing network results)\n" + "- netx: same as network but skip track duration and templates (used for hashing progressive load of fmp4)\n" + "- encode: same as network plus skip decoder config (used for hashing encoding results)\n" + "- encx: same as encode and skip bitrates, media data size and co", GF_PROP_UINT, "no", "no|noprop|network|netx|encode|encx", GF_FS_ARG_HINT_EXPERT|GF_FS_ARG_UPDATE}, + {0} +}; + +static const GF_FilterCapability InspectCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_UNKNOWN), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), +}; + +const GF_FilterRegister InspectRegister = { + .name = "inspect", + GF_FS_SET_DESCRIPTION("Inspect packets") + GF_FS_SET_HELP("The inspect filter can be used to dump pid and packets. It may also be used to check parts of payload of the packets. The default options inspect only pid changes.\n"\ + "The packet inspector can be configured to dump specific properties of packets using [-fmt]().\n"\ + "When the option is not present, all properties are dumped. Otherwise, only properties identified by `$TOKEN$` " + "are printed. You may use '$', '@' or '%' for `TOKEN` separator. `TOKEN` can be:\n"\ + "- pn: packet (frame in framed mode) number\n"\ + "- dts: decoding time stamp in stream timescale, N/A if not available\n"\ + "- ddts: difference between current and previous packets decoding time stamp in stream timescale, N/A if not available\n"\ + "- cts: composition time stamp in stream timescale, N/A if not available\n"\ + "- dcts: difference between current and previous packets composition time stamp in stream timescale, N/A if not available\n"\ + "- ctso: difference between composition time stamp and decoding time stamp in stream timescale, N/A if not available\n"\ + "- dur: duration in stream timescale\n"\ + "- frame: framing status\n" + " - interface: complete AU, interface object (no size info). Typically a GL texture\n" + " - frame_full: complete AU\n" + " - frame_start: begining of frame\n" + " - frame_end: end of frame\n" + " - frame_cont: frame continuation (not begining, not end)\n" + "- sap or rap: SAP type of the frame\n"\ + "- ilace: interlacing flag (0: progressive, 1: top field, 2: bottom field)\n"\ + "- corr: corrupted packet flag\n"\ + "- seek: seek flag\n"\ + "- bo: byte offset in source, N/A if not available\n"\ + "- roll: roll info\n"\ + "- crypt: crypt flag\n"\ + "- vers: carrousel version number\n"\ + "- size: size of packet\n"\ + "- crc: 32 bit CRC of packet\n"\ + "- lf: insert linefeed\n"\ + "- cr: insert carriage return\n"\ + "- t: insert tab\n"\ + "- data: hex dump of packet (** WARNING, BIG OUTPUT !! **)\n"\ + "- lp: leading picture flag\n"\ + "- depo: depends on other packet flag\n"\ + "- depf: is depended on other packet flag\n"\ + "- red: redundant coding flag\n"\ + "- ck: clock type used for PCR discontinuities\n"\ + "- P4CC: 4CC of packet property\n"\ + "- PropName: Name of packet property\n"\ + "- pid.P4CC: 4CC of PID property\n"\ + "- pid.PropName: Name of PID property\n"\ + "\n"\ + "EX fmt=\"PID $pid.ID$ packet $pn$ DTS $dts$ CTS $cts$ $lf$\"\n" + "This dumps packet number, cts and dts as follows: `PID 1 packet 10 DTS 100 CTS 108 \\n`\n"\ + " \n"\ + "An unrecognized keywork or missing property will resolve to an empty string.\n"\ + "\n"\ + "Note: when dumping in interleaved mode, there is no guarantee that the packets will be dumped in their original sequence order since the inspector fetches one packet at a time on each PID.\n") + .private_size = sizeof(GF_InspectCtx), + .flags = GF_FS_REG_EXPLICIT_ONLY, + .max_extra_pids = (u32) -1, + .args = InspectArgs, + SETCAPS(InspectCaps), + .initialize = inspect_initialize, + .finalize = inspect_finalize, + .process = inspect_process, + .process_event = inspect_process_event, + .configure_pid = inspect_config_input, + .update_arg = inspect_update_arg, +}; + +static const GF_FilterCapability ProberCaps[] = +{ + //accept any stream but files, framed + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_OD), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_OD), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, +}; + + +const GF_FilterRegister ProbeRegister = { + .name = "probe", + GF_FS_SET_DESCRIPTION("Probe source") + GF_FS_SET_HELP("The Probe filter is used by applications (typically `MP4Box`) to query demuxed pids available in a source chain.\n" + "The filter does not produce any output nor feedback, it is up to the app developper to query input pids of the prober and take appropriated decisions.") + .private_size = sizeof(GF_InspectCtx), + .flags = GF_FS_REG_EXPLICIT_ONLY, + .max_extra_pids = (u32) -1, + .args = InspectArgs, + .initialize = inspect_initialize, + SETCAPS(ProberCaps), + .finalize = inspect_finalize, + .process = inspect_process, + .configure_pid = inspect_config_input, +}; + +const GF_FilterRegister *inspect_register(GF_FilterSession *session) +{ + return &InspectRegister; +} + +const GF_FilterRegister *probe_register(GF_FilterSession *session) +{ + return &ProbeRegister; +} + + + diff --git a/src/filters/isoffin.h b/src/filters/isoffin.h new file mode 100644 index 0000000..fa21ed2 --- /dev/null +++ b/src/filters/isoffin.h @@ -0,0 +1,222 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2018 + * All rights reserved + * + * This file is part of GPAC / ISOBMFF reader filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#ifndef _ISMO_IN_H_ +#define _ISMO_IN_H_ + +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_ISOM + +/* + reader module + +*/ + +//#define DASH_USE_PULL + +enum +{ + MP4DMX_SPLIT=0, + MP4DMX_SPLIT_EXTRACTORS, + MP4DMX_SINGLE, +}; + +typedef struct +{ + //options + char *src; + Bool allt, noedit, itt, itemid; + u32 smode; + u32 stsd; + Bool expart; + Bool alltk; + u32 frame_size; + char* tkid; + Bool analyze; + char *catseg; + Bool sigfrag; + Bool nocrypt, strtxt; + u32 mstore_purge, mstore_samples, mstore_size; + + //internal + + GF_Filter *filter; + + /*current channels*/ + GF_List *channels; + + /*input file*/ + GF_ISOFile *mov; + Bool extern_mov; + u32 time_scale; + u32 nb_playing; + //source data is completely available + Bool input_loaded; + //fragmented file to be refreshed before processing it + Bool refresh_fragmented; + Bool input_is_stop; + u64 missing_bytes, last_size; + + Bool seg_name_changed; + u32 play_only_track_id; + u32 play_only_first_media; + Bool full_segment_flush; + /*0: not fragmented - 1 fragmented - 2 fragmented and last fragment received*/ + u32 frag_type; + Bool waiting_for_data, reset_frag_state; + + + u32 pending_scalable_enhancement_segment_index; + + Bool drop_next_segment; + Bool in_data_flush; + u32 has_pending_segments, nb_force_flush; + + Bool clock_discontinuity; + Bool disconnected; + Bool no_order_check; + Bool moov_not_loaded; + + u64 last_sender_ntp, ntp_at_last_sender_ntp, cts_for_last_sender_ntp; + Bool is_partial_download, wait_for_source; + + u32 src_crc; + u64 start_range, end_range; + GF_FilterPid *pid; + + Bool eos_signaled; + u32 mem_load_mode; + u8 *mem_url; + GF_Blob mem_blob; + u64 bytes_removed; + u64 last_min_offset; + GF_Err in_error; + Bool force_fetch; +} ISOMReader; + +typedef struct +{ + u32 track, track_id; + u32 item_id, item_idx; + /*base track if scalable media, 0 otherwise*/ + u32 base_track; + u32 next_track; + GF_FilterPid *pid; + ISOMReader *owner; + u64 duration; + + /*current sample*/ + GF_ISOSample *static_sample; + GF_ISOSample *sample; + u64 sample_data_offset, last_valid_sample_data_offset; + GF_Err last_state; + Bool sap_3; + GF_ISOSampleRollType sap_4_type; + s32 roll; + + Bool has_edit_list; + u32 sample_num; + s64 ts_offset; +/* + s64 dts_offset; + Bool do_dts_shift_test; +*/ + + /*for edit lists*/ + u32 edit_sync_frame; + u64 sample_time, last_rap_sample_time, start, end; + Double speed; + + u32 time_scale; + Bool to_init, has_rap; + Bool playing, eos_sent; + u8 streamType; + Bool initial_play_seen; + + Bool is_encrypted, is_cenc; + + Bool disable_seek; + u32 nalu_extract_mode; + + //channel sample state + u32 last_sample_desc_index; + u32 isLeading, dependsOn, dependedOn, redundant; + u64 dts, cts; + u8 skip_byte_block, crypt_byte_block; + Bool is_protected; + u8 constant_IV_size; + bin128 constant_IV; + u8 IV_size; + u32 au_seq_num; + u64 sender_ntp, ntp_at_server_ntp; + u32 seek_flag; + u32 au_duration; + Bool set_disc; + Bool cenc_state_changed; + u64 isma_BSO; + bin128 KID; + Bool pck_encrypted; + + u8 *sai_buffer; + u32 sai_alloc_size, sai_buffer_size; + + Bool check_avc_ps, check_hevc_ps, check_mhas_pl; + GF_HEVCConfig *hvcc; + GF_AVCConfig *avcc; + GF_BitStream *nal_bs; + u32 dsi_crc; + + Bool needs_pid_reconfig; + u32 sap_only; +} ISOMChannel; + +void isor_reset_reader(ISOMChannel *ch); +void isor_reader_get_sample(ISOMChannel *ch); +void isor_reader_release_sample(ISOMChannel *ch); +void isor_update_channel_config(ISOMChannel *ch); + +void isor_check_producer_ref_time(ISOMReader *read); + +//ISOMChannel *isor_get_channel(ISOMReader *reader, GF_FilterPid *pid); +ISOMChannel *isor_create_channel(ISOMReader *read, GF_FilterPid *pid, u32 track, u32 item_id, Bool force_no_extractors); + +GF_Err isor_declare_objects(ISOMReader *read); + +void isor_reader_get_sample_from_item(ISOMChannel *ch); +void isor_set_crypt_config(ISOMChannel *ch); + +void isor_reader_check_config(ISOMChannel *ch); + +Bool isor_declare_item_properties(ISOMReader *read, ISOMChannel *ch, u32 item_idx); + +#endif /*GPAC_DISABLE_ISOM*/ + +#endif /*_ISMO_IN_H_*/ + diff --git a/src/filters/isoffin_load.c b/src/filters/isoffin_load.c new file mode 100644 index 0000000..61f188e --- /dev/null +++ b/src/filters/isoffin_load.c @@ -0,0 +1,1260 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2018 + * All rights reserved + * + * This file is part of GPAC / ISOBMFF reader filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "isoffin.h" +#include +#include +#include + +#ifndef GPAC_DISABLE_ISOM + +#if 0 //deprecated - we need to rework chapter information to deal with static chapters and chapter tracks +void isor_emulate_chapters(GF_ISOFile *file, GF_InitialObjectDescriptor *iod) +{ + GF_Segment *prev_seg; + u64 prev_start; + u64 start; + u32 i, count; + if (!iod || gf_list_count(iod->OCIDescriptors)) return; + count = gf_isom_get_chapter_count(file, 0); + if (!count) return; + + prev_seg = NULL; + start = prev_start = 0; + for (i=0; istartTime = (Double) (s64) start; + seg->startTime /= 1000; + seg->SegmentName = gf_strdup(name); + gf_list_add(iod->OCIDescriptors, seg); + if (prev_seg) { + prev_seg->Duration = (Double) (s64) (start - prev_start); + prev_seg->Duration /= 1000; + } else if (start) { + prev_seg = (GF_Segment *) gf_odf_desc_new(GF_ODF_SEGMENT_TAG); + prev_seg->startTime = 0; + prev_seg->Duration = (Double) (s64) (start); + prev_seg->Duration /= 1000; + gf_list_insert(iod->OCIDescriptors, prev_seg, 0); + } + prev_seg = seg; + prev_start = start; + } + if (prev_seg) { + start = 1000*gf_isom_get_duration(file); + start /= gf_isom_get_timescale(file); + if (start>prev_start) { + prev_seg->Duration = (Double) (s64) (start - prev_start); + prev_seg->Duration /= 1000; + } + } +} +#endif + +static void isor_declare_track(ISOMReader *read, ISOMChannel *ch, u32 track, u32 stsd_idx, u32 streamtype, Bool use_iod) +{ + u32 w, h, sr, nb_ch, nb_bps, codec_id, depends_on_id, esid, avg_rate, max_rate, buffer_size, sample_count, max_size, nb_refs, exp_refs, base_track, audio_fmt, pix_fmt; + GF_ESD *an_esd; + const char *mime, *encoding, *stxtcfg, *namespace, *schemaloc; +#if !defined(GPAC_DISABLE_ISOM_WRITE) + u8 *tk_template; + u32 tk_template_size; +#endif + GF_Language *lang_desc = NULL; + Bool external_base=GF_FALSE; + Bool has_scalable_layers = GF_FALSE; + u8 *dsi = NULL, *enh_dsi = NULL; + u32 dsi_size = 0, enh_dsi_size = 0; + Double track_dur=0; + u32 srd_id=0, srd_indep=0, srd_x=0, srd_y=0, srd_w=0, srd_h=0; + u32 base_tile_track=0; + Bool srd_full_frame=GF_FALSE; + u32 mtype, m_subtype; + GF_GenericSampleDescription *udesc = NULL; + GF_Err e; + u32 ocr_es_id; + Bool first_config = GF_FALSE; + + + depends_on_id = avg_rate = max_rate = buffer_size = 0; + mime = encoding = stxtcfg = namespace = schemaloc = NULL; + + if ( gf_isom_is_media_encrypted(read->mov, track, stsd_idx)) { + gf_isom_get_original_format_type(read->mov, track, stsd_idx, &m_subtype); + } else { + m_subtype = gf_isom_get_media_subtype(read->mov, track, stsd_idx); + } + + audio_fmt = 0; + pix_fmt = 0; + ocr_es_id = 0; + an_esd = gf_media_map_esd(read->mov, track, stsd_idx); + if (an_esd && an_esd->decoderConfig) { + if (an_esd->decoderConfig->streamType==GF_STREAM_INTERACT) { + gf_odf_desc_del((GF_Descriptor *)an_esd); + return; + } + streamtype = an_esd->decoderConfig->streamType; + if (an_esd->decoderConfig->objectTypeIndication < GF_CODECID_LAST_MPEG4_MAPPING) { + codec_id = gf_codecid_from_oti(streamtype, an_esd->decoderConfig->objectTypeIndication); + } else { + codec_id = an_esd->decoderConfig->objectTypeIndication; + } + ocr_es_id = an_esd->OCRESID; + depends_on_id = an_esd->dependsOnESID; + lang_desc = an_esd->langDesc; + an_esd->langDesc = NULL; + esid = an_esd->ESID; + + if (an_esd->decoderConfig->decoderSpecificInfo && an_esd->decoderConfig->decoderSpecificInfo->data) { + dsi = an_esd->decoderConfig->decoderSpecificInfo->data; + dsi_size = an_esd->decoderConfig->decoderSpecificInfo->dataLength; + an_esd->decoderConfig->decoderSpecificInfo->data = NULL; + an_esd->decoderConfig->decoderSpecificInfo->dataLength = 0; + } + + gf_odf_desc_del((GF_Descriptor *)an_esd); + } else { + u32 pcm_flags, pcm_size; + Bool load_default = GF_FALSE; + lang_desc = (GF_Language *) gf_odf_desc_new(GF_ODF_LANG_TAG); + gf_isom_get_media_language(read->mov, track, &lang_desc->full_lang_code); + esid = gf_isom_get_track_id(read->mov, track); + + if (!streamtype) streamtype = gf_codecid_type(m_subtype); + codec_id = 0; + + switch (m_subtype) { + case GF_ISOM_SUBTYPE_STXT: + case GF_ISOM_SUBTYPE_METT: + case GF_ISOM_SUBTYPE_SBTT: + case GF_ISOM_MEDIA_SUBT: + + codec_id = GF_CODECID_SIMPLE_TEXT; + gf_isom_stxt_get_description(read->mov, track, stsd_idx, &mime, &encoding, &stxtcfg); + break; + case GF_ISOM_SUBTYPE_STPP: + codec_id = GF_CODECID_SUBS_XML; + gf_isom_xml_subtitle_get_description(read->mov, track, stsd_idx, &namespace, &schemaloc, &mime); + break; + case GF_ISOM_SUBTYPE_METX: + codec_id = GF_CODECID_META_XML; + gf_isom_xml_subtitle_get_description(read->mov, track, stsd_idx, &namespace, &schemaloc, &mime); + break; + case GF_ISOM_SUBTYPE_WVTT: + codec_id = GF_CODECID_WEBVTT; + stxtcfg = gf_isom_get_webvtt_config(read->mov, track, stsd_idx); + break; + case GF_ISOM_SUBTYPE_MJP2: + codec_id = GF_CODECID_J2K; + gf_isom_get_jp2_config(read->mov, track, stsd_idx, &dsi, &dsi_size); + break; + case GF_ISOM_SUBTYPE_HVT1: + codec_id = GF_CODECID_HEVC_TILES; + gf_isom_get_reference(read->mov, track, GF_ISOM_REF_TBAS, 1, &base_tile_track); + if (base_tile_track) { + depends_on_id = gf_isom_get_track_id(read->mov, base_tile_track); + } + gf_isom_get_tile_info(read->mov, track, 1, NULL, &srd_id, &srd_indep, &srd_full_frame, &srd_x, &srd_y, &srd_w, &srd_h); + break; + case GF_ISOM_SUBTYPE_TEXT: + case GF_ISOM_SUBTYPE_TX3G: + { + GF_TextSampleDescriptor *txtcfg = NULL; + codec_id = GF_CODECID_TX3G; + e = gf_isom_get_text_description(read->mov, track, stsd_idx, &txtcfg); + if (e) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[IsoMedia] Track %d unable to fetch TX3G config\n", track)); + } + if (txtcfg) { + gf_odf_tx3g_write(txtcfg, &dsi, &dsi_size); + gf_odf_desc_del((GF_Descriptor *) txtcfg); + } + } + break; + + case GF_ISOM_SUBTYPE_FLAC: + codec_id = GF_CODECID_FLAC; + gf_isom_flac_config_get(read->mov, track, stsd_idx, &dsi, &dsi_size); + break; + case GF_ISOM_SUBTYPE_OPUS: + codec_id = GF_CODECID_OPUS; + gf_isom_opus_config_get(read->mov, track, stsd_idx, &dsi, &dsi_size); + break; + + case GF_QT_SUBTYPE_TWOS: + case GF_QT_SUBTYPE_SOWT: + case GF_QT_SUBTYPE_FL32: + case GF_QT_SUBTYPE_FL64: + case GF_QT_SUBTYPE_IN24: + case GF_QT_SUBTYPE_IN32: + codec_id = GF_CODECID_RAW; + audio_fmt = gf_audio_fmt_from_isobmf(m_subtype); + break; + case GF_ISOM_MEDIA_TIMECODE: + codec_id = GF_CODECID_TMCD; + streamtype = GF_STREAM_METADATA; + break; + + case GF_QT_SUBTYPE_RAW: + codec_id = GF_CODECID_RAW; + if (streamtype==GF_STREAM_AUDIO) + audio_fmt = GF_AUDIO_FMT_U8; + else + pix_fmt = GF_PIXEL_RGB; + break; + case GF_QT_SUBTYPE_YUV422: + codec_id = GF_CODECID_RAW; + pix_fmt = GF_PIXEL_YUV422; + break; + case GF_QT_SUBTYPE_YUV444: + codec_id = GF_CODECID_RAW; + pix_fmt = GF_PIXEL_YUV444; + break; + case GF_QT_SUBTYPE_YUV422_10: + codec_id = GF_CODECID_RAW; + pix_fmt = GF_PIXEL_YUV422_10; + break; + case GF_QT_SUBTYPE_YUV444_10: + codec_id = GF_CODECID_RAW; + pix_fmt = GF_PIXEL_YUV444_10; + break; + case GF_ISOM_SUBTYPE_IPCM: + if (gf_isom_get_pcm_config(read->mov, track, stsd_idx, &pcm_flags, &pcm_size) == GF_OK) { + codec_id = GF_CODECID_RAW; + if (pcm_size==16) audio_fmt = GF_AUDIO_FMT_S16; + else if (pcm_size==24) audio_fmt = GF_AUDIO_FMT_S24; + else if (pcm_size==32) audio_fmt = GF_AUDIO_FMT_S32; + } + break; + case GF_ISOM_SUBTYPE_FPCM: + if (gf_isom_get_pcm_config(read->mov, track, stsd_idx, &pcm_flags, &pcm_size) == GF_OK) { + codec_id = GF_CODECID_RAW; + audio_fmt = (pcm_size==64) ? GF_AUDIO_FMT_DBL : GF_AUDIO_FMT_FLT; + } + break; + case GF_ISOM_SUBTYPE_MP3: + codec_id = GF_CODECID_MPEG_AUDIO; + break; + + default: + codec_id = gf_codec_id_from_isobmf(m_subtype); + if (!codec_id) + load_default = GF_TRUE; + break; + } + + if (load_default) { + if (!codec_id) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[IsoMedia] Track %d type %s not natively handled\n", track, gf_4cc_to_str(m_subtype) )); + + codec_id = m_subtype; + } + udesc = gf_isom_get_generic_sample_description(read->mov, track, stsd_idx); + if (udesc) { + dsi = udesc->extension_buf; + dsi_size = udesc->extension_buf_size; + udesc->extension_buf = NULL; + udesc->extension_buf_size = 0; + } + } + } + if (!streamtype || !codec_id) { + if (udesc) gf_free(udesc); + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[IsoMedia] Failed to %s pid for track %d, couldnot extract codec/streamtype info\n", ch ? "update" : "create", track)); + if (lang_desc) gf_odf_desc_del((GF_Descriptor *)lang_desc); + if (dsi) gf_free(dsi); + return; + } + + //first setup, creation of PID and channel + if (!ch) { + Bool use_sidx_dur = GF_FALSE; + GF_FilterPid *pid; + first_config = GF_TRUE; + + gf_isom_get_reference(read->mov, track, GF_ISOM_REF_BASE, 1, &base_track); + + if (base_track) { + u32 base_subtype=0; + if (read->smode==MP4DMX_SINGLE) + depends_on_id = 0; + + switch (m_subtype) { + case GF_ISOM_SUBTYPE_LHV1: + case GF_ISOM_SUBTYPE_LHE1: + base_subtype = gf_isom_get_media_subtype(read->mov, base_track, stsd_idx); + switch (base_subtype) { + case GF_ISOM_SUBTYPE_HVC1: + case GF_ISOM_SUBTYPE_HEV1: + case GF_ISOM_SUBTYPE_HVC2: + case GF_ISOM_SUBTYPE_HEV2: + break; + default: + external_base=GF_TRUE; + break; + } + } + if (external_base) { + depends_on_id = gf_isom_get_track_id(read->mov, base_track); + has_scalable_layers = GF_TRUE; + } else { + switch (gf_isom_get_hevc_lhvc_type(read->mov, track, stsd_idx)) { + case GF_ISOM_HEVCTYPE_HEVC_LHVC: + case GF_ISOM_HEVCTYPE_LHVC_ONLY: + has_scalable_layers = GF_TRUE; + break; + //this is likely temporal sublayer of base + case GF_ISOM_HEVCTYPE_HEVC_ONLY: + has_scalable_layers = GF_FALSE; + if (gf_isom_get_reference_count(read->mov, track, GF_ISOM_REF_SCAL)<=0) { + depends_on_id = gf_isom_get_track_id(read->mov, base_track); + } + break; + default: + break; + } + } + } else { + switch (gf_isom_get_hevc_lhvc_type(read->mov, track, stsd_idx)) { + case GF_ISOM_HEVCTYPE_HEVC_LHVC: + case GF_ISOM_HEVCTYPE_LHVC_ONLY: + has_scalable_layers = GF_TRUE; + break; + default: + break; + } + } + + if (base_track && !ocr_es_id) { + ocr_es_id = gf_isom_get_track_id(read->mov, base_track); + } + if (!ocr_es_id) ocr_es_id = esid; + + //OK declare PID + pid = gf_filter_pid_new(read->filter); + if (read->pid) + gf_filter_pid_copy_properties(pid, read->pid); + + gf_filter_pid_set_property(pid, GF_PROP_PID_ID, &PROP_UINT(esid)); + gf_filter_pid_set_property(pid, GF_PROP_PID_CLOCK_ID, &PROP_UINT(ocr_es_id)); + if (depends_on_id && (depends_on_id != esid)) + gf_filter_pid_set_property(pid, GF_PROP_PID_DEPENDENCY_ID, &PROP_UINT(depends_on_id)); + + if (gf_isom_get_track_count(read->mov)>1) { + char szPName[1024]; + const char *szST = gf_stream_type_name(streamtype); + sprintf(szPName, "%c%d", szST[0], esid); + gf_filter_pid_set_name(pid, szPName); + } + + //MPEG-4 systems present + if (use_iod) + gf_filter_pid_set_property(pid, GF_PROP_PID_ESID, &PROP_UINT(esid)); + + if (gf_isom_is_track_in_root_od(read->mov, track)) { + switch (streamtype) { + case GF_STREAM_SCENE: + case GF_STREAM_OD: + gf_filter_pid_set_property(pid, GF_PROP_PID_IN_IOD, &PROP_BOOL(GF_TRUE)); + break; + } + } + + gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(streamtype)); + gf_filter_pid_set_property(pid, GF_PROP_PID_TIMESCALE, &PROP_UINT( gf_isom_get_media_timescale(read->mov, track) ) ); + + if (!gf_sys_is_test_mode()) + gf_filter_pid_set_property(pid, GF_PROP_PID_TRACK_NUM, &PROP_UINT(track) ); + + //Dolby Vision + if (m_subtype == GF_ISOM_SUBTYPE_DVHE) { + GF_DOVIDecoderConfigurationRecord *dovi = gf_isom_dovi_config_get(read->mov, track, 1); + if (dovi) { + u8 *data = NULL; + u32 size = 0; + GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + gf_odf_dovi_cfg_write_bs(dovi, bs); + gf_bs_get_content(bs, &data, &size); + gf_filter_pid_set_property(pid, GF_PROP_PID_DOLBY_VISION, &PROP_DATA_NO_COPY(data, size)); + gf_bs_del(bs); + gf_odf_dovi_cfg_del(dovi); + } + } + + //create our channel + ch = isor_create_channel(read, pid, track, 0, (codec_id==GF_CODECID_LHVC) ? GF_TRUE : GF_FALSE); + + if (lang_desc) { + char *lang=NULL; + gf_isom_get_media_language(read->mov, track, &lang); + //s32 idx = gf_lang_find(lang); + gf_filter_pid_set_property(pid, GF_PROP_PID_LANGUAGE, &PROP_STRING( lang )); + if (lang) gf_free(lang); + gf_odf_desc_del((GF_Descriptor *)lang_desc); + lang_desc = NULL; + } + + + if (has_scalable_layers) + gf_filter_pid_set_property(pid, GF_PROP_PID_SCALABLE, &PROP_BOOL(GF_TRUE)); + + if (gf_isom_get_reference_count(read->mov, track, GF_ISOM_REF_SABT)>0) { + gf_filter_pid_set_property(pid, GF_PROP_PID_TILE_BASE, &PROP_BOOL(GF_TRUE)); + } + + if (srd_w && srd_h) { + gf_filter_pid_set_property(pid, GF_PROP_PID_CROP_POS, &PROP_VEC2I_INT(srd_x, srd_y) ); + if (base_tile_track) { + gf_isom_get_visual_info(read->mov, base_tile_track, stsd_idx, &w, &h); + if (w && h) { + gf_filter_pid_set_property(pid, GF_PROP_PID_ORIG_SIZE, &PROP_VEC2I_INT(w, h) ); + } + } + } + + for (exp_refs=0; exp_refs<3; exp_refs++) { + u32 rtype = (exp_refs==2) ? GF_ISOM_REF_TBAS : exp_refs ? GF_ISOM_REF_SABT : GF_ISOM_REF_SCAL; + const char *rname = (exp_refs==2) ? "isom:tbas" : exp_refs ? "isom:sabt" : "isom:scal"; + if (!exp_refs && (codec_id==GF_CODECID_LHVC)) + continue; + + nb_refs = gf_isom_get_reference_count(read->mov, track, rtype); + if (nb_refs) { + u32 j; + GF_PropertyValue prop; + prop.type = GF_PROP_UINT_LIST; + prop.value.uint_list.nb_items = nb_refs; + prop.value.uint_list.vals = gf_malloc(sizeof(u32)*nb_refs); + for (j=0; jmov, track, rtype, j+1, &prop.value.uint_list.vals[j] ); + } + gf_filter_pid_set_property_str(pid, rname, &prop); + gf_free(prop.value.uint_list.vals); + } + } + + ch->duration = gf_isom_get_track_duration(read->mov, ch->track); + if (!ch->duration) { + ch->duration = gf_isom_get_duration(read->mov); + } + sample_count = gf_isom_get_sample_count(read->mov, ch->track); + + if (read->frag_type && !read->input_loaded) { + u32 ts; + u64 dur; + if (gf_isom_get_sidx_duration(read->mov, &dur, &ts)==GF_OK) { + dur *= read->time_scale; + dur /= ts; + ch->duration = dur; + use_sidx_dur = GF_TRUE; + sample_count = 0; + } + } + + if (!read->mem_load_mode) { + //if no edit list (whether complex or simple TS offset) and no sidx, use media duration + if (!ch->has_edit_list && !use_sidx_dur && !ch->ts_offset) { + u64 dur = gf_isom_get_media_duration(read->mov, ch->track); + gf_filter_pid_set_property(pid, GF_PROP_PID_DURATION, &PROP_FRAC64_INT(dur, ch->time_scale)); + } + //otherwise trust track duration + else { + gf_filter_pid_set_property(pid, GF_PROP_PID_DURATION, &PROP_FRAC64_INT(ch->duration, read->time_scale)); + } + gf_filter_pid_set_property(pid, GF_PROP_PID_NB_FRAMES, &PROP_UINT(sample_count)); + } + + if (sample_count && (streamtype==GF_STREAM_VISUAL)) { + u64 mdur = gf_isom_get_media_duration(read->mov, track); + mdur /= sample_count; + gf_filter_pid_set_property(pid, GF_PROP_PID_FPS, &PROP_FRAC_INT(ch->time_scale, (u32) mdur)); + } + + track_dur = (Double) (s64) ch->duration; + track_dur /= read->time_scale; + //move channel duration in media timescale + ch->duration = (u32) (track_dur * ch->time_scale); + + + //set stream subtype + mtype = gf_isom_get_media_type(read->mov, track); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_SUBTYPE, &PROP_UINT(mtype) ); + + if (!read->mem_load_mode) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_MEDIA_DATA_SIZE, &PROP_LONGUINT(gf_isom_get_media_data_size(read->mov, track) ) ); + } + //in no cache mode, depending on fetch speed we may have fetched a fragment or not, resulting in has_rap set + //always for HAS_SYNC to false + else if (gf_sys_is_test_mode() && !sample_count) { + gf_filter_pid_set_property(pid, GF_PROP_PID_HAS_SYNC, &PROP_BOOL(GF_FALSE) ); + } + + + w = gf_isom_get_constant_sample_size(read->mov, track); + if (w) + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_FRAME_SIZE, &PROP_UINT(w)); + + if (read->mem_load_mode) { + gf_filter_pid_set_property(pid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_NONE) ); + } else { + gf_filter_pid_set_property(pid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_REWIND) ); + } + + GF_PropertyValue brands; + brands.type = GF_PROP_UINT_LIST; + u32 major_brand=0; + gf_isom_get_brand_info(read->mov, &major_brand, NULL, &brands.value.uint_list.nb_items); + brands.value.uint_list.vals = (u32 *) gf_isom_get_brands(read->mov); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_BRANDS, &brands); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_MBRAND, &PROP_UINT(major_brand) ); + + //we cannot expose average size/dur in mem mode with fragmented files (sample_count=0) + if (sample_count) { + max_size = gf_isom_get_max_sample_size(read->mov, ch->track); + if (max_size) gf_filter_pid_set_property(pid, GF_PROP_PID_MAX_FRAME_SIZE, &PROP_UINT(max_size) ); + + max_size = gf_isom_get_avg_sample_size(read->mov, ch->track); + if (max_size) gf_filter_pid_set_property(pid, GF_PROP_PID_AVG_FRAME_SIZE, &PROP_UINT(max_size) ); + + max_size = gf_isom_get_max_sample_delta(read->mov, ch->track); + if (max_size) gf_filter_pid_set_property(pid, GF_PROP_PID_MAX_TS_DELTA, &PROP_UINT(max_size) ); + + max_size = gf_isom_get_max_sample_cts_offset(read->mov, ch->track); + if (max_size) gf_filter_pid_set_property(pid, GF_PROP_PID_MAX_CTS_OFFSET, &PROP_UINT(max_size) ); + + max_size = gf_isom_get_constant_sample_duration(read->mov, ch->track); + if (max_size) gf_filter_pid_set_property(pid, GF_PROP_PID_CONSTANT_DURATION, &PROP_UINT(max_size) ); + } + + + u32 media_pl=0; + if (streamtype==GF_STREAM_VISUAL) { + media_pl = gf_isom_get_pl_indication(read->mov, GF_ISOM_PL_VISUAL); + } else if (streamtype==GF_STREAM_AUDIO) { + media_pl = gf_isom_get_pl_indication(read->mov, GF_ISOM_PL_AUDIO); + } + if (media_pl && (media_pl!=0xFF) ) gf_filter_pid_set_property(pid, GF_PROP_PID_PROFILE_LEVEL, &PROP_UINT(media_pl) ); + +#if !defined(GPAC_DISABLE_ISOM_WRITE) + e = gf_isom_get_track_template(read->mov, ch->track, &tk_template, &tk_template_size); + if (e == GF_OK) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_TRACK_TEMPLATE, &PROP_DATA_NO_COPY(tk_template, tk_template_size) ); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[IsoMedia] Failed to serialize track box: %s\n", gf_error_to_string(e) )); + } + + e = gf_isom_get_trex_template(read->mov, ch->track, &tk_template, &tk_template_size); + if (e == GF_OK) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_TREX_TEMPLATE, &PROP_DATA_NO_COPY(tk_template, tk_template_size) ); + } + + e = gf_isom_get_raw_user_data(read->mov, &tk_template, &tk_template_size); + if (e==GF_OK) { + if (tk_template_size) + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_UDTA, &PROP_DATA_NO_COPY(tk_template, tk_template_size) ); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[IsoMedia] Failed to serialize moov UDTA box: %s\n", gf_error_to_string(e) )); + } +#endif + + GF_Fraction64 moov_time; + moov_time.num = gf_isom_get_duration(read->mov); + moov_time.den = gf_isom_get_timescale(read->mov); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_MOVIE_TIME, &PROP_FRAC64(moov_time) ); + + + u32 i; + s32 tx, ty; + s16 l; + gf_isom_get_track_layout_info(read->mov, ch->track, &w, &h, &tx, &ty, &l); + if (w && h) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_WIDTH, &PROP_UINT(w) ); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_HEIGHT, &PROP_UINT(h) ); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_TRANS_X, &PROP_SINT(tx) ); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_TRANS_Y, &PROP_SINT(ty) ); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ZORDER, &PROP_SINT(l) ); + } + if (codec_id==GF_CODECID_TX3G) { + u32 m_w = w; + u32 m_h = h; + for (i=0; imov); i++) { + switch (gf_isom_get_media_type(read->mov, i+1)) { + case GF_ISOM_MEDIA_SCENE: + case GF_ISOM_MEDIA_VISUAL: + case GF_ISOM_MEDIA_AUXV: + case GF_ISOM_MEDIA_PICT: + gf_isom_get_track_layout_info(read->mov, i+1, &w, &h, &tx, &ty, &l); + if (w>m_w) m_w = w; + if (h>m_h) m_h = h; + break; + default: + break; + } + } + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_WIDTH_MAX, &PROP_UINT(m_w) ); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_HEIGHT_MAX, &PROP_UINT(m_h) ); + char *tx3g_config_sdp = NULL; + for (i=0; imov, ch->track); i++) { + u8 *tx3g; + u32 l1; + u32 tx3g_len, len; + e = gf_isom_text_get_encoded_tx3g(read->mov, ch->track, i+1, GF_RTP_TX3G_SIDX_OFFSET, &tx3g, &tx3g_len); + if (e==GF_OK) { + char buffer[2000]; + len = gf_base64_encode(tx3g, tx3g_len, buffer, 2000); + gf_free(tx3g); + buffer[len] = 0; + + l1 = tx3g_config_sdp ? (u32) strlen(tx3g_config_sdp) : 0; + tx3g_config_sdp = gf_realloc(tx3g_config_sdp, len+3+l1); + tx3g_config_sdp[l1] = 0; + if (i) strcat(tx3g_config_sdp, ", "); + strcat(tx3g_config_sdp, buffer); + } + } + if (tx3g_config_sdp) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, &PROP_STRING_NO_COPY(tx3g_config_sdp) ); + } + } + + u32 idx=0; + while (1) { + u32 tag_len; + const u8 *tag; + u32 itag; + const char *name = gf_itags_enum_tags(&idx, &itag, NULL, NULL); + if (!name) break; + if (gf_isom_apple_get_tag(read->mov, itag, &tag, &tag_len)==GF_OK) { + switch (itag) { + case GF_ISOM_ITUNE_TRACK: + case GF_ISOM_ITUNE_TRACKNUMBER: + case GF_ISOM_ITUNE_DISK: + if (tag_len>=6) { + u16 tk_n = (tag[2]<<8)|tag[3]; + u16 tk_all = (tag[4]<<8)|tag[5]; + gf_filter_pid_set_property_str(ch->pid, name, &PROP_FRAC_INT(tk_n, tk_all) ); + } + break; + case GF_ISOM_ITUNE_GAPLESS: + case GF_ISOM_ITUNE_COMPILATION: + if (tag_len) + gf_filter_pid_set_property_str(ch->pid, name, &PROP_BOOL(tag[0]) ); + break; + case GF_ISOM_ITUNE_GENRE: + if (tag_len>=2) { + u32 genre_t = tag[0]<<8 | tag[1]; + const char *genre = gf_id3_get_genre(genre_t); + if (genre) { + gf_filter_pid_set_property_str(ch->pid, name, &PROP_STRING(genre) ); + break; + } + } + gf_filter_pid_set_property_str(ch->pid, name, &PROP_STRING(tag) ); + break; + case GF_ISOM_ITUNE_TEMPO: + if (tag_len==2) { + u32 tempo = tag[0]<<8 | tag[1]; + gf_filter_pid_set_property_str(ch->pid, name, &PROP_UINT(tempo) ); + } else { + gf_filter_pid_set_property_str(ch->pid, name, &PROP_STRING(tag) ); + } + break; + + default: + gf_filter_pid_set_property_str(ch->pid, name, &PROP_STRING(tag) ); + } + } + } + + + if (codec_id==GF_CODECID_TMCD) { + u32 tmcd_flags=0, tmcd_fps_num=0, tmcd_fps_den=0, tmcd_fpt=0; + gf_isom_get_tmcd_config(read->mov, track, stsd_idx, &tmcd_flags, &tmcd_fps_num, &tmcd_fps_den, &tmcd_fpt); + gf_filter_pid_set_property_str(ch->pid, "tmcd:flags", &PROP_UINT(tmcd_flags) ); + gf_filter_pid_set_property_str(ch->pid, "tmcd:framerate", &PROP_FRAC_INT(tmcd_fps_num, tmcd_fps_den) ); + gf_filter_pid_set_property_str(ch->pid, "tmcd:frames_per_tick", &PROP_UINT(tmcd_fpt) ); + + } + + if (!gf_sys_is_test_mode()) { + const char *hdlr = NULL; + gf_isom_get_handler_name(read->mov, ch->track, &hdlr); + if (hdlr) + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_HANDLER, &PROP_STRING(hdlr)); + + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_TRACK_FLAGS, &PROP_UINT( gf_isom_get_track_flags(read->mov, ch->track) )); + + if (streamtype==GF_STREAM_VISUAL) { + GF_PropertyValue p; + u32 vals[9]; + memset(vals, 0, sizeof(u32)*9); + memset(&p, 0, sizeof(GF_PropertyValue)); + p.type = GF_PROP_UINT_LIST; + p.value.uint_list.nb_items = 9; + p.value.uint_list.vals = vals; + gf_isom_get_track_matrix(read->mov, ch->track, vals); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_TRACK_MATRIX, &p); + } + } + } + + //update decoder configs + ch->check_avc_ps = ch->check_hevc_ps = GF_FALSE; + if (ch->avcc) gf_odf_avc_cfg_del(ch->avcc); + ch->avcc = NULL; + if (ch->hvcc) gf_odf_hevc_cfg_del(ch->hvcc); + ch->hvcc = NULL; + + if (lang_desc) { + gf_odf_desc_del((GF_Descriptor *)lang_desc); + lang_desc = NULL; + } + + if (read->smode != MP4DMX_SINGLE) { + if ((codec_id==GF_CODECID_LHVC) || (codec_id==GF_CODECID_HEVC)) { + Bool signal_lhv = (read->smode==MP4DMX_SPLIT) ? GF_TRUE : GF_FALSE; + GF_HEVCConfig *hvcc = gf_isom_hevc_config_get(read->mov, track, stsd_idx); + GF_HEVCConfig *lhcc = gf_isom_lhvc_config_get(read->mov, track, stsd_idx); + + if (hvcc || lhcc) { + if (dsi) gf_free(dsi); + dsi = NULL; + //no base layer config + if (!hvcc) signal_lhv = GF_TRUE; + + if (signal_lhv && lhcc) { + if (hvcc) { + hvcc->is_lhvc = GF_FALSE; + gf_odf_hevc_cfg_write(hvcc, &dsi, &dsi_size); + } + lhcc->is_lhvc = GF_TRUE; + gf_odf_hevc_cfg_write(lhcc, &enh_dsi, &enh_dsi_size); + codec_id = GF_CODECID_LHVC; + } else { + if (hvcc) { + hvcc->is_lhvc = GF_FALSE; + gf_odf_hevc_cfg_write(hvcc, &dsi, &dsi_size); + } + if (lhcc) { + lhcc->is_lhvc = GF_TRUE; + gf_odf_hevc_cfg_write(lhcc, &enh_dsi, &enh_dsi_size); + } + codec_id = GF_CODECID_HEVC; + } + } + if (hvcc) gf_odf_hevc_cfg_del(hvcc); + if (lhcc) gf_odf_hevc_cfg_del(lhcc); + + } + if ((codec_id==GF_CODECID_AVC) || (codec_id==GF_CODECID_SVC) || (codec_id==GF_CODECID_MVC)) { + Bool is_mvc = GF_FALSE; + Bool signal_svc = (read->smode==MP4DMX_SPLIT) ? GF_TRUE : GF_FALSE; + GF_AVCConfig *avcc = gf_isom_avc_config_get(read->mov, track, stsd_idx); + GF_AVCConfig *svcc = gf_isom_svc_config_get(read->mov, track, stsd_idx); + if (!svcc) { + svcc = gf_isom_mvc_config_get(read->mov, track, stsd_idx); + is_mvc = GF_TRUE; + } + + if (avcc || svcc) { + if (dsi) gf_free(dsi); + dsi = NULL; + //no base layer config + if (!avcc) signal_svc = GF_TRUE; + + if (signal_svc && svcc) { + if (avcc) { + gf_odf_avc_cfg_write(avcc, &dsi, &dsi_size); + } + gf_odf_avc_cfg_write(svcc, &enh_dsi, &enh_dsi_size); + codec_id = is_mvc ? GF_CODECID_MVC : GF_CODECID_SVC; + } else { + if (avcc) { + gf_odf_avc_cfg_write(avcc, &dsi, &dsi_size); + } + if (svcc) { + gf_odf_avc_cfg_write(svcc, &enh_dsi, &enh_dsi_size); + } + codec_id = GF_CODECID_AVC; + } + } + if (avcc) gf_odf_avc_cfg_del(avcc); + if (svcc) gf_odf_avc_cfg_del(svcc); + } + } + + //all stsd specific init/reconfig + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CODECID, &PROP_UINT(codec_id)); + if (dsi) { + ch->dsi_crc = gf_crc_32(dsi, dsi_size); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_size)); + } + if (enh_dsi) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, &PROP_DATA_NO_COPY(enh_dsi, enh_dsi_size)); + } + if (audio_fmt) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(audio_fmt)); + if (codec_id == GF_CODECID_RAW) { + gf_isom_enable_raw_pack(read->mov, track, read->frame_size); + } + } + if (pix_fmt) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_PIXFMT, &PROP_UINT(pix_fmt)); + } + + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CONFIG_IDX, &PROP_UINT(stsd_idx) ); + + w = h = 0; + gf_isom_get_visual_info(read->mov, track, stsd_idx, &w, &h); + if (w && h) { + u32 hspace, vspace; + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_WIDTH, &PROP_UINT(w)); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_HEIGHT, &PROP_UINT(h)); + + gf_isom_get_pixel_aspect_ratio(read->mov, track, stsd_idx, &hspace, &vspace); + if (hspace != vspace) + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_SAR, &PROP_FRAC_INT(hspace, vspace) ); + } + sr = nb_ch = nb_bps = 0; + gf_isom_get_audio_info(read->mov,track, stsd_idx, &sr, &nb_ch, &nb_bps); + //nb_ch may be set to 0 for "not applicable" (3D / object coding audio) + if (sr) { + u32 d1, d2; + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(sr)); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(nb_ch)); + + //to remove once we deprecate master + if (!gf_sys_old_arch_compat()) { + GF_AudioChannelLayout layout; + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_AUDIO_BPS, &PROP_UINT(nb_bps)); + + if (gf_isom_get_audio_layout(read->mov, track, stsd_idx, &layout)==GF_OK) { + if (layout.definedLayout) { + u64 lay = gf_audio_fmt_get_layout_from_cicp(layout.definedLayout); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CHANNEL_LAYOUT, &PROP_LONGUINT(lay)); + } + + } + + } + + if (first_config ) { + d1 = gf_isom_get_sample_duration(read->mov, ch->track, 1); + d2 = gf_isom_get_sample_duration(read->mov, ch->track, 2); + if (d1 && d2 && (d1==d2)) { + d1 *= sr; + d1 /= ch->time_scale; + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_SAMPLES_PER_FRAME, &PROP_UINT(d1)); + } + } + } + + gf_isom_get_bitrate(read->mov, ch->track, stsd_idx, &avg_rate, &max_rate, &buffer_size); + + if (!avg_rate) { + if (first_config && ch->duration) { + u64 avgrate = 8 * gf_isom_get_media_data_size(read->mov, ch->track); + avgrate = (u64) (avgrate / track_dur); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_BITRATE, &PROP_UINT((u32) avgrate)); + } + } else { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_BITRATE, &PROP_UINT((u32) avg_rate)); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_MAXRATE, &PROP_UINT((u32) max_rate)); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_DBSIZE, &PROP_UINT((u32) buffer_size)); + } + + if (mime) gf_filter_pid_set_property_str(ch->pid, "meta:mime", &PROP_STRING(mime) ); + if (encoding) gf_filter_pid_set_property_str(ch->pid, "meta:encoding", &PROP_STRING(encoding) ); + if (namespace) gf_filter_pid_set_property_str(ch->pid, "meta:xmlns", &PROP_STRING(namespace) ); + if (schemaloc) gf_filter_pid_set_property_str(ch->pid, "meta:schemaloc", &PROP_STRING(schemaloc) ); + + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_SUBTYPE, &PROP_UINT(m_subtype) ); + if (stxtcfg) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA((char *)stxtcfg, (u32) strlen(stxtcfg) )); + + +#if !defined(GPAC_DISABLE_ISOM_WRITE) + tk_template=NULL; + tk_template_size=0; + e = gf_isom_get_stsd_template(read->mov, ch->track, stsd_idx, &tk_template, &tk_template_size); + if (e == GF_OK) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISOM_STSD_TEMPLATE, &PROP_DATA_NO_COPY(tk_template, tk_template_size) ); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[IsoMedia] Failed to serialize stsd box: %s\n", gf_error_to_string(e) )); + } +#endif + + if (codec_id == GF_CODECID_DIMS) { + GF_DIMSDescription dims; + memset(&dims, 0, sizeof(GF_DIMSDescription)); + + gf_isom_get_dims_description(read->mov, ch->track, stsd_idx, &dims); + gf_filter_pid_set_property_str(ch->pid, "dims:profile", &PROP_UINT(dims.profile)); + gf_filter_pid_set_property_str(ch->pid, "dims:level", &PROP_UINT(dims.level)); + gf_filter_pid_set_property_str(ch->pid, "dims:pathComponents", &PROP_UINT(dims.pathComponents)); + gf_filter_pid_set_property_str(ch->pid, "dims:fullRequestHost", &PROP_BOOL(dims.fullRequestHost)); + gf_filter_pid_set_property_str(ch->pid, "dims:streamType", &PROP_BOOL(dims.streamType)); + gf_filter_pid_set_property_str(ch->pid, "dims:redundant", &PROP_BOOL(dims.containsRedundant)); + if (dims.content_script_types) + gf_filter_pid_set_property_str(ch->pid, "dims:scriptTypes", &PROP_STRING(dims.content_script_types)); + if (dims.textEncoding) + gf_filter_pid_set_property_str(ch->pid, "meta:encoding", &PROP_STRING(dims.textEncoding)); + if (dims.contentEncoding) + gf_filter_pid_set_property_str(ch->pid, "meta:content_encoding", &PROP_STRING(dims.contentEncoding)); + if (dims.xml_schema_loc) + gf_filter_pid_set_property_str(ch->pid, "meta:schemaloc", &PROP_STRING(dims.xml_schema_loc)); + if (dims.mime_type) + gf_filter_pid_set_property_str(ch->pid, "meta:mime", &PROP_STRING(dims.mime_type)); + } + else if (codec_id==GF_CODECID_AVC) + ch->check_avc_ps = GF_TRUE; + else if (codec_id==GF_CODECID_HEVC) + ch->check_hevc_ps = GF_TRUE; + else if (codec_id==GF_CODECID_MHAS) { + if (!dsi) { + ch->check_mhas_pl = GF_TRUE; + GF_ISOSample *samp = gf_isom_get_sample(ch->owner->mov, ch->track, 1, NULL); + if (samp) { + s32 PL = gf_mpegh_get_mhas_pl(samp->data, samp->dataLength); + if (PL>0) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_PROFILE_LEVEL, &PROP_UINT((u32) PL)); + ch->check_mhas_pl = GF_FALSE; + } + gf_isom_sample_del(&samp); + } + } + } + + if (udesc) { + gf_filter_pid_set_property_str(ch->pid, "codec_vendor", &PROP_UINT(udesc->vendor_code)); + gf_filter_pid_set_property_str(ch->pid, "codec_version", &PROP_UINT(udesc->version)); + gf_filter_pid_set_property_str(ch->pid, "codec_revision", &PROP_UINT(udesc->revision)); + gf_filter_pid_set_property_str(ch->pid, "compressor_name", &PROP_STRING(udesc->compressor_name)); + gf_filter_pid_set_property_str(ch->pid, "temporal_quality", &PROP_UINT(udesc->temporal_quality)); + gf_filter_pid_set_property_str(ch->pid, "spatial_quality", &PROP_UINT(udesc->spatial_quality)); + if (udesc->h_res) { + gf_filter_pid_set_property_str(ch->pid, "hres", &PROP_UINT(udesc->h_res)); + gf_filter_pid_set_property_str(ch->pid, "vres", &PROP_UINT(udesc->v_res)); + } else if (udesc->nb_channels) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(udesc->nb_channels)); + switch (udesc->bits_per_sample) { + case 8: + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(GF_AUDIO_FMT_U8)); + break; + case 16: + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(GF_AUDIO_FMT_S16)); + break; + case 24: + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(GF_AUDIO_FMT_S24)); + break; + case 32: + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(GF_AUDIO_FMT_S32)); + break; + default: + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[IsoMedia] Track %d unsupported audio bit depth %d\n", track, udesc->bits_per_sample )); + break; + } + } + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_BIT_DEPTH_Y, &PROP_UINT(udesc->depth)); + + gf_free(udesc); + } + + + if (ch->check_avc_ps) { + ch->avcc = gf_isom_avc_config_get(ch->owner->mov, ch->track, ch->last_sample_desc_index ? ch->last_sample_desc_index : 1); + } + else if (ch->check_hevc_ps) { + ch->hvcc = gf_isom_hevc_config_get(ch->owner->mov, ch->track, ch->last_sample_desc_index ? ch->last_sample_desc_index : 1); + } +} + +void isor_update_channel_config(ISOMChannel *ch) +{ + isor_declare_track(ch->owner, ch, ch->track, ch->last_sample_desc_index, GF_STREAM_UNKNOWN, GF_FALSE); + +} + +GF_Err isor_declare_objects(ISOMReader *read) +{ + const u8 *tag; + u32 tlen; + u32 i, count, j, track_id; + Bool highest_stream; + Bool single_media_found = GF_FALSE; + Bool use_iod = GF_FALSE; + GF_Err e; + Bool isom_contains_video = GF_FALSE; + GF_Descriptor *od = gf_isom_get_root_od(read->mov); + if (od && gf_list_count(((GF_ObjectDescriptor*)od)->ESDescriptors)) { + use_iod = GF_TRUE; + } + if (od) gf_odf_desc_del(od); + + /*TODO + check for alternate tracks + */ + count = gf_isom_get_track_count(read->mov); + for (i=0; imov, i+1); + + if (read->tkid) { + u32 for_id=0; + if (sscanf(read->tkid, "%d", &for_id)) { + u32 id = gf_isom_get_track_id(read->mov, i+1); + if (id != for_id) continue; + } else if (!strcmp(read->tkid, "audio")) { + if (mtype!=GF_ISOM_MEDIA_AUDIO) continue; + } else if (!strcmp(read->tkid, "video")) { + if (mtype!=GF_ISOM_MEDIA_VISUAL) continue; + } else if (strlen(read->tkid)==4) { + u32 t = GF_4CC(read->tkid[0], read->tkid[1], read->tkid[2], read->tkid[3]); + if (mtype!=t) continue; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[IsoMedia] Bad format for tkid option %s, no match\n", read->tkid)); + return GF_BAD_PARAM; + } + } + + switch (mtype) { + case GF_ISOM_MEDIA_AUDIO: + streamtype = GF_STREAM_AUDIO; + break; + case GF_ISOM_MEDIA_VISUAL: + case GF_ISOM_MEDIA_AUXV: + case GF_ISOM_MEDIA_PICT: + case GF_ISOM_MEDIA_QTVR: + streamtype = GF_STREAM_VISUAL; + isom_contains_video = GF_TRUE; + break; + case GF_ISOM_MEDIA_TEXT: + case GF_ISOM_MEDIA_SUBT: + case GF_ISOM_MEDIA_SUBPIC: + case GF_ISOM_MEDIA_MPEG_SUBT: + case GF_ISOM_MEDIA_CLOSED_CAPTION: + streamtype = GF_STREAM_TEXT; + break; + case GF_ISOM_MEDIA_FLASH: + case GF_ISOM_MEDIA_DIMS: + case GF_ISOM_MEDIA_SCENE: + streamtype = GF_STREAM_SCENE; + break; + case GF_ISOM_MEDIA_OD: + streamtype = GF_STREAM_OD; + break; + case GF_ISOM_MEDIA_META: + case GF_ISOM_MEDIA_TIMECODE: + streamtype = GF_STREAM_METADATA; + break; + /*hint tracks are never exported*/ + case GF_ISOM_MEDIA_HINT: + continue; + default: + if (!read->allt) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[IsoMedia] Track %d type %s not supported, ignoring track - you may retry by specifying allt option\n", i+1, gf_4cc_to_str(mtype) )); + continue; + } + streamtype = GF_STREAM_UNKNOWN; + break; + } + + if (!read->alltk && !read->tkid && !gf_isom_is_track_enabled(read->mov, i+1)) { + if (count>1) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[IsoMedia] Track %d is disabled, ignoring track - you may retry by specifying alltk option\n", i+1)); + continue; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[IsoMedia] Track %d is disabled but single track in file, considering it enabled\n", i+1 )); + } + } + + stsd_idx = read->stsd ? read->stsd : 1; + //some subtypes are not declared as readable objects + m_subtype = gf_isom_get_media_subtype(read->mov, i+1, stsd_idx); + switch (m_subtype) { + case GF_ISOM_SUBTYPE_HVT1: + if (read->smode == MP4DMX_SINGLE) + continue; + + break; + default: + break; + } + + /*we declare only the highest video track (i.e the track we play)*/ + highest_stream = GF_TRUE; + track_id = gf_isom_get_track_id(read->mov, i+1); + if (read->play_only_track_id && (read->play_only_track_id != track_id)) continue; + + if (read->play_only_first_media) { + if (read->play_only_first_media != mtype) continue; + if (single_media_found) continue; + single_media_found = GF_TRUE; + } + + for (j = 0; j < count; j++) { + if (gf_isom_has_track_reference(read->mov, j+1, GF_ISOM_REF_SCAL, track_id) > 0) { + highest_stream = GF_FALSE; + break; + } + if (gf_isom_has_track_reference(read->mov, j+1, GF_ISOM_REF_BASE, track_id) > 0) { + highest_stream = GF_FALSE; + break; + } + } + + if ((read->smode==MP4DMX_SINGLE) && (gf_isom_get_media_type(read->mov, i+1) == GF_ISOM_MEDIA_VISUAL) && !highest_stream) + continue; + + + isor_declare_track(read, NULL, i+1, stsd_idx, streamtype, use_iod); + + if (read->tkid) + break; + } + + if (!read->tkid) { + /*declare image items*/ + count = gf_isom_get_meta_item_count(read->mov, GF_TRUE, 0); + for (i=0; iitt) break; + } + } + + /*if cover art, declare a video pid*/ + if (gf_isom_apple_get_tag(read->mov, GF_ISOM_ITUNE_COVER_ART, &tag, &tlen)==GF_OK) { + + /*write cover data*/ + assert(!(tlen & 0x80000000)); + tlen &= 0x7FFFFFFF; + + if (read->expart && !isom_contains_video) { + GF_FilterPid *cover_pid=NULL; + e = gf_filter_pid_raw_new(read->filter, NULL, NULL, NULL, NULL, (char *) tag, tlen, GF_FALSE, &cover_pid); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[MP3Dmx] error setting up video pid for cover art: %s\n", gf_error_to_string(e) )); + } + if (cover_pid) { + u8 *out_buffer; + GF_FilterPacket *dst_pck; + gf_filter_pid_set_property(cover_pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); + gf_filter_pid_set_name(cover_pid, "CoverArt"); + dst_pck = gf_filter_pck_new_alloc(cover_pid, tlen, &out_buffer); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + memcpy(out_buffer, tag, tlen); + gf_filter_pck_send(dst_pck); + gf_filter_pid_set_eos(cover_pid); + } + } + } + return GF_OK; +} + +Bool isor_declare_item_properties(ISOMReader *read, ISOMChannel *ch, u32 item_idx) +{ + GF_ImageItemProperties props; + GF_FilterPid *pid; + GF_ESD *esd; + u32 item_id=0; + const char *item_name, *item_mime_type, *item_encoding; + + if (item_idx>gf_isom_get_meta_item_count(read->mov, GF_TRUE, 0)) + return GF_FALSE; + + item_name = item_mime_type = item_encoding = NULL; + gf_isom_get_meta_item_info(read->mov, GF_TRUE, 0, item_idx, &item_id, NULL, NULL, NULL, NULL, NULL, &item_name, &item_mime_type, &item_encoding); + + if (!item_id) return GF_FALSE; + + gf_isom_get_meta_image_props(read->mov, GF_TRUE, 0, item_id, &props); + + esd = gf_media_map_item_esd(read->mov, item_id); + if (!esd) return GF_FALSE; + + //OK declare PID + if (!ch) + pid = gf_filter_pid_new(read->filter); + else + pid = ch->pid; + + if (read->pid) + gf_filter_pid_copy_properties(pid, read->pid); + + gf_filter_pid_set_property(pid, GF_PROP_PID_ID, &PROP_UINT(esd->ESID)); + if (read->itemid) + gf_filter_pid_set_property(pid, GF_PROP_PID_ITEM_ID, &PROP_UINT(item_id)); + + //TODO: no support for LHEVC images + //gf_filter_pid_set_property(pid, GF_PROP_PID_DEPENDENCY_ID, &PROP_UINT(esd->dependsOnESID)); + + gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(esd->decoderConfig->streamType)); + gf_filter_pid_set_property(pid, GF_PROP_PID_CODECID, &PROP_UINT(esd->decoderConfig->objectTypeIndication)); + gf_filter_pid_set_property(pid, GF_PROP_PID_TIMESCALE, &PROP_UINT(1000)); + if (esd->decoderConfig->decoderSpecificInfo) { + gf_filter_pid_set_property(pid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength)); + + esd->decoderConfig->decoderSpecificInfo->data=NULL; + esd->decoderConfig->decoderSpecificInfo->dataLength=0; + } + + if (props.width && props.height) { + gf_filter_pid_set_property(pid, GF_PROP_PID_WIDTH, &PROP_UINT(props.width)); + gf_filter_pid_set_property(pid, GF_PROP_PID_HEIGHT, &PROP_UINT(props.height)); + } + if (props.hidden) { + gf_filter_pid_set_property(pid, GF_PROP_PID_HIDDEN, &PROP_BOOL(props.hidden)); + } + gf_odf_desc_del((GF_Descriptor *)esd); + + if (gf_isom_get_meta_primary_item_id(read->mov, GF_TRUE, 0) == item_id) { + gf_filter_pid_set_property(pid, GF_PROP_PID_PRIMARY_ITEM, &PROP_BOOL(GF_TRUE)); + } else { + gf_filter_pid_set_property(pid, GF_PROP_PID_PRIMARY_ITEM, &PROP_BOOL(GF_FALSE)); + } + + if (!gf_sys_is_test_mode()) + gf_filter_pid_set_property(pid, GF_PROP_PID_ITEM_NUM, &PROP_UINT(item_idx) ); + + gf_filter_pid_set_property_str(pid, "meta:mime", item_mime_type ? &PROP_STRING(item_mime_type) : NULL ); + gf_filter_pid_set_property_str(pid, "meta:name", item_name ? &PROP_STRING(item_name) : NULL ); + gf_filter_pid_set_property_str(pid, "meta:encoding", item_encoding ? &PROP_STRING(item_encoding) : NULL ); + + if (!ch) { + /*ch = */isor_create_channel(read, pid, 0, item_id, GF_FALSE); + } + return GF_TRUE; +} + +#endif /*GPAC_DISABLE_ISOM*/ + + + diff --git a/src/filters/isoffin_read.c b/src/filters/isoffin_read.c new file mode 100644 index 0000000..2c216b8 --- /dev/null +++ b/src/filters/isoffin_read.c @@ -0,0 +1,1428 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2020 + * All rights reserved + * + * This file is part of GPAC / ISOBMFF reader filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "isoffin.h" + +#ifndef GPAC_DISABLE_ISOM + +#include +#include + +ISOMChannel *isor_get_channel(ISOMReader *reader, GF_FilterPid *pid) +{ + u32 i=0; + ISOMChannel *ch; + while ((ch = (ISOMChannel *)gf_list_enum(reader->channels, &i))) { + if (ch->pid == pid) return ch; + } + return NULL; +} + + +static GFINLINE Bool isor_is_local(const char *url) +{ + if (!strnicmp(url, "file://", 7)) return GF_TRUE; + if (!strnicmp(url, "gmem://", 7)) return GF_TRUE; + if (!strnicmp(url, "gfio://", 7)) return GF_TRUE; + if (!strnicmp(url, "isobmff://", 10)) return GF_TRUE; + if (strstr(url, "://")) return GF_FALSE; + /*the rest is local (mounted on FS)*/ + return GF_TRUE; +} + + +static GF_Err isoffin_setup(GF_Filter *filter, ISOMReader *read) +{ + char szURL[2048]; + char *tmp, *src; + GF_Err e; + const GF_PropertyValue *prop; + if (!read) return GF_SERVICE_ERROR; + + if (read->pid) { + prop = gf_filter_pid_get_property(read->pid, GF_PROP_PID_FILEPATH); + assert(prop); + src = prop->value.string; + } else { + src = read->src; + } + if (!src) return GF_SERVICE_ERROR; + + read->src_crc = gf_crc_32(src, (u32) strlen(src)); + + strcpy(szURL, src); + tmp = gf_file_ext_start(szURL); + if (tmp) { + Bool truncate = GF_TRUE; + tmp = strchr(tmp, '#'); + if (!tmp && read->pid) { + prop = gf_filter_pid_get_property(read->pid, GF_PROP_PID_URL); + if (prop && prop->value.string) { + tmp = gf_file_ext_start(prop->value.string); + if (tmp) tmp = strchr(tmp, '#'); + truncate = GF_FALSE; + } + } + if (tmp) { + if (!strnicmp(tmp, "#audio", 6)) { + read->play_only_first_media = GF_ISOM_MEDIA_AUDIO; + } else if (!strnicmp(tmp, "#video", 6)) { + read->play_only_first_media = GF_ISOM_MEDIA_VISUAL; + } else if (!strnicmp(tmp, "#auxv", 5)) { + read->play_only_first_media = GF_ISOM_MEDIA_AUXV; + } else if (!strnicmp(tmp, "#pict", 5)) { + read->play_only_first_media = GF_ISOM_MEDIA_PICT; + } else if (!strnicmp(tmp, "#text", 5)) { + read->play_only_first_media = GF_ISOM_MEDIA_TEXT; + } else if (!strnicmp(tmp, "#trackID=", 9)) { + read->play_only_track_id = atoi(tmp+9); + } else if (!strnicmp(tmp, "#ID=", 4)) { + read->play_only_track_id = atoi(tmp+4); + } else { + read->play_only_track_id = atoi(tmp+1); + } + if (truncate) tmp[0] = 0; + } + } + + if (! isor_is_local(szURL)) { + return GF_NOT_SUPPORTED; + } + read->start_range = read->end_range = 0; + prop = gf_filter_pid_get_property(read->pid, GF_PROP_PID_FILE_RANGE); + if (prop) { + read->start_range = prop->value.lfrac.num; + read->end_range = prop->value.lfrac.den; + } + + e = gf_isom_open_progressive(szURL, read->start_range, read->end_range, read->sigfrag, &read->mov, &read->missing_bytes); + + if (e == GF_ISOM_INCOMPLETE_FILE) { + read->moov_not_loaded = GF_TRUE; + return GF_OK; + } + + if (e != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[IsoMedia] error while opening %s, error=%s\n", szURL,gf_error_to_string(e))); + gf_filter_setup_failure(filter, e); + return e; + } + read->frag_type = gf_isom_is_fragmented(read->mov) ? 1 : 0; + if (!read->frag_type && read->sigfrag) { + e = GF_BAD_PARAM; + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[IsoMedia] sigfrag requested but file %s is not fragmented\n", szURL)); + gf_filter_setup_failure(filter, e); + return e; + } + + read->time_scale = gf_isom_get_timescale(read->mov); + if (!read->input_loaded && read->frag_type) + read->refresh_fragmented = GF_TRUE; + + if (read->strtxt) + gf_isom_text_set_streaming_mode(read->mov, GF_TRUE); + + return isor_declare_objects(read); +} + +static void isoffin_delete_channel(ISOMChannel *ch) +{ + isor_reset_reader(ch); + if (ch->nal_bs) gf_bs_del(ch->nal_bs); + if (ch->avcc) gf_odf_avc_cfg_del(ch->avcc); + if (ch->hvcc) gf_odf_hevc_cfg_del(ch->hvcc); + gf_free(ch); +} + +static void isoffin_disconnect(ISOMReader *read) +{ + read->disconnected = GF_TRUE; + while (gf_list_count(read->channels)) { + ISOMChannel *ch = (ISOMChannel *)gf_list_get(read->channels, 0); + gf_list_rem(read->channels, 0); + gf_filter_pid_remove(ch->pid); + isoffin_delete_channel(ch); + } + + if (read->mov) gf_isom_close(read->mov); + read->mov = NULL; + + read->pid = NULL; +} + +static GF_Err isoffin_reconfigure(GF_Filter *filter, ISOMReader *read, const char *next_url) +{ + const GF_PropertyValue *prop; + u32 i, count; + Bool is_new_mov = GF_FALSE; + u64 tfdt; + GF_ISOTrackID trackID; + GF_ISOSegOpenMode flags=0; + GF_Err e; + + prop = gf_filter_pid_get_property(read->pid, GF_PROP_PID_FILE_CACHED); + if (prop && prop->value.boolean) + read->input_loaded = GF_TRUE; + read->refresh_fragmented = GF_FALSE; + read->full_segment_flush = GF_TRUE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] reconfigure triggered, URL %s\n", next_url)); + + switch (gf_isom_probe_file_range(next_url, read->start_range, read->end_range)) { + //this is a fragment + case 3: + gf_isom_release_segment(read->mov, 1); + gf_isom_reset_fragment_info(read->mov, GF_TRUE); + + if (read->no_order_check) flags |= GF_ISOM_SEGMENT_NO_ORDER_FLAG; +#ifdef FILTER_FIXME + if (scalable_segment) flags |= GF_ISOM_SEGMENT_SCALABLE_FLAG; +#endif + e = gf_isom_open_segment(read->mov, next_url, read->start_range, read->end_range, flags); + if (!read->input_loaded && (e==GF_ISOM_INCOMPLETE_FILE)) { + read->refresh_fragmented = GF_TRUE; + e = GF_OK; + } + read->seg_name_changed = GF_TRUE; + + for (i=0; ichannels); i++) { + ISOMChannel *ch = gf_list_get(read->channels, i); + if (ch->last_state==GF_EOS) + ch->last_state=GF_OK; + } + +#ifndef GPAC_DISABLE_LOG + if (e<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Error opening new segment %s at UTC "LLU": %s\n", next_url, gf_net_get_utc(), gf_error_to_string(e) )); + } else if (read->end_range) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Playing new range in %s: "LLU"-"LLU"\n", next_url, read->start_range, read->end_range)); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] playing new segment %s (has next dep %d TODO)\n", next_url, 0)); + } +#endif + break; + //this is a movie, reload + case 2: + case 1: + tfdt = gf_isom_get_current_tfdt(read->mov, 1); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Switching between files - opening new init segment %s (time offset="LLU") - range "LLU"-"LLU"\n", next_url, tfdt, read->start_range, read->end_range)); + + if (gf_isom_is_smooth_streaming_moov(read->mov)) { + char *tfdt_val = strstr(next_url, "tfdt="); + //smooth addressing, replace tfdt=0000000000000000000 with proper value + if (tfdt_val) { + sprintf(tfdt_val+5, LLU, tfdt); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[IsoMedia] Error finding init time for init segment %s at UTC "LLU"\n", next_url, gf_net_get_utc() )); + } + } + + if (read->mov) gf_isom_close(read->mov); + e = gf_isom_open_progressive(next_url, read->start_range, read->end_range, read->sigfrag, &read->mov, &read->missing_bytes); + if (e < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Error opening init segment %s at UTC "LLU": %s\n", next_url, gf_net_get_utc(), gf_error_to_string(e) )); + } + if (read->sigfrag) + gf_isom_enable_traf_map_templates(read->mov); + + is_new_mov = GF_TRUE; + break; + //empty file + case 4: + return GF_OK; + default: + return GF_NOT_SUPPORTED; + } + + gf_filter_post_process_task(filter); + + count = gf_list_count(read->channels); + + if (e<0) { + count = gf_list_count(read->channels); + gf_isom_release_segment(read->mov, 1); + //gf_isom_reset_fragment_info(read->mov, GF_TRUE); + read->drop_next_segment = 1; + //error opening the segment, reset everything ... + gf_isom_reset_fragment_info(read->mov, GF_FALSE); + for (i=0; ichannels, i); + if (ch) + ch->sample_num = 0; + } + return e; + } + //segment is the first in our cache, we may need a refresh + if (!read->input_loaded) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Opening current segment in progressive mode (download in progress)\n")); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Opening current segment in non-progressive mode (completely downloaded)\n")); + } + + isor_check_producer_ref_time(read); + + for (i=0; ichannels, i); + ch->last_state = GF_OK; + ch->eos_sent = GF_FALSE; + + if (ch->base_track) { +#ifdef FILTER_FIXME + if (scalable_segment) +#endif + if ((0)) { + trackID = gf_isom_get_highest_track_in_scalable_segment(read->mov, ch->base_track); + if (trackID) { + ch->track_id = trackID; + ch->track = gf_isom_get_track_by_id(read->mov, ch->track_id); + } + } else { + ch->track = ch->base_track; + ch->track_id = gf_isom_get_track_id(read->mov, ch->track); + } + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Track %d - cur sample %d - new sample count %d\n", ch->track, ch->sample_num, gf_isom_get_sample_count(ch->owner->mov, ch->track) )); + + //TODO: signal all discontinuities here + if (is_new_mov) { + ch->track = gf_isom_get_track_by_id(read->mov, ch->track_id); + if (!ch->track) { + if (gf_isom_get_track_count(read->mov)==1) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Mismatch between track IDs of different representations\n")); + ch->track = 1; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Mismatch between track IDs of different representations\n")); + } + } + + /*we changed our moov structure, sample_num now starts from 0*/ + ch->sample_num = 0; + //this may happen if we reload moov before initializing the channel + if (!ch->last_sample_desc_index) + ch->last_sample_desc_index = 1; + //and update channel config + isor_update_channel_config(ch); + + /*restore NAL extraction mode*/ + gf_isom_set_nalu_extract_mode(read->mov, ch->track, ch->nalu_extract_mode); + + if (ch->is_cenc) { + isor_set_crypt_config(ch); + } + } + + ch->last_state = GF_OK; + } + return e; +} + +GF_Err isoffin_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *prop; + ISOMReader *read = gf_filter_get_udta(filter); + + if (is_remove) { + isoffin_disconnect(read); + return GF_OK; + } + //we must have a file path for now, if not this is a pure stream of boxes (no local file cache) + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_FILEPATH); + if (!prop || !prop->value.string) { + read->mem_load_mode = 1; + if (!read->pid) read->pid = pid; + return GF_OK; + } + + if (read->pid && prop->value.string) { + const char *next_url = prop->value.string; + u64 sr, er; + u32 crc = gf_crc_32(next_url, (u32) strlen(next_url) ); + + sr = er = 0; + prop = gf_filter_pid_get_property(read->pid, GF_PROP_PID_FILE_RANGE); + if (prop) { + sr = prop->value.lfrac.num; + er = prop->value.lfrac.den; + } + + //if eos is signaled, don't check for crc since we might have the same blob address (same alloc) + if (!read->eos_signaled && (read->src_crc == crc) && (read->start_range==sr) && (read->end_range==er)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] same URL crc and range for %s, skipping reconfigure\n", next_url)); + return GF_OK; + } + read->src_crc = crc; + read->start_range = sr; + read->end_range = er; + read->input_loaded = GF_FALSE; + read->eos_signaled = GF_FALSE; + + //we need to reconfigure + return isoffin_reconfigure(filter, read, next_url); + } + + read->pid = pid; + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_FILE_CACHED); + if (prop && prop->value.boolean) { + GF_FilterEvent evt; + read->input_loaded = GF_TRUE; + GF_FEVT_INIT(evt, GF_FEVT_PLAY_HINT, pid); + evt.play.full_file_only=1; + gf_filter_pid_send_event(pid, &evt); + } + return isoffin_setup(filter, read); +} + +GF_Err isoffin_initialize(GF_Filter *filter) +{ + ISOMReader *read = gf_filter_get_udta(filter); + GF_Err e = GF_OK; + read->filter = filter; + read->channels = gf_list_new(); + + if (read->src) { + read->input_loaded = GF_TRUE; + return isoffin_setup(filter, read); + } + else if (read->mov) { + read->extern_mov = GF_TRUE; + read->input_loaded = GF_TRUE; + read->frag_type = gf_isom_is_fragmented(read->mov) ? 1 : 0; + read->time_scale = gf_isom_get_timescale(read->mov); + + if (read->sigfrag) { + gf_isom_enable_traf_map_templates(read->mov); + } + + if (read->catseg) { + e = gf_isom_open_segment(read->mov, read->catseg, 0, 0, 0); + } + if (!e) + e = isor_declare_objects(read); + + gf_filter_post_process_task(filter); + } + return e; +} + + +static void isoffin_finalize(GF_Filter *filter) +{ + ISOMReader *read = (ISOMReader *) gf_filter_get_udta(filter); + + read->disconnected = GF_TRUE; + + while (gf_list_count(read->channels)) { + ISOMChannel *ch = (ISOMChannel *)gf_list_get(read->channels, 0); + gf_list_rem(read->channels, 0); + isoffin_delete_channel(ch); + } + gf_list_del(read->channels); + + if (!read->extern_mov && read->mov) gf_isom_close(read->mov); + read->mov = NULL; + + if (read->mem_blob.data) gf_free(read->mem_blob.data); + if (read->mem_url) gf_free(read->mem_url); +} + +void isor_set_crypt_config(ISOMChannel *ch) +{ + GF_ISOFile *mov = ch->owner->mov; + u32 track = ch->track; + u32 scheme_type, scheme_version, PSSH_count=0, i, count; + const char *kms_uri, *scheme_uri; + Bool selectiveEncryption=0; + u32 IVLength=0; + u32 KeyIndicationLength=0; + const char *txtHdr=NULL; + const char *contentID=NULL; + u32 txtHdrLen=0; + u64 plainTextLen=0; + u32 crypt_type=0; + u32 stsd_idx = ch->owner->stsd ? ch->owner->stsd : 1; + + if (!ch->is_encrypted) return; + + scheme_type = scheme_version = 0; + kms_uri = scheme_uri = NULL; + + /*ugly fix to detect when an stsd uses both clear and encrypted sample descriptions*/ + count = gf_isom_get_sample_description_count(ch->owner->mov, ch->track); + if (count>1) { + u32 first_crypted_stsd = 0; + u32 nb_same_mtype = 1; + u32 nb_clear=0, nb_encrypted=0; + u32 base_subtype = 0; + Bool first_is_clear = GF_FALSE; + for (i=0; iowner->mov, ch->track, i+1); + if ( gf_isom_is_media_encrypted(ch->owner->mov, track, i+1)) { + gf_isom_get_original_format_type(ch->owner->mov, ch->track, i+1, &mtype); + nb_encrypted++; + if (!first_crypted_stsd) first_crypted_stsd = i+1; + } else { + nb_clear++; + if (!i) first_is_clear = GF_TRUE; + } + if (!i) base_subtype = mtype; + else if (base_subtype==mtype) { + nb_same_mtype++; + } + } + if ((nb_same_mtype==count) && (nb_clear==nb_encrypted)) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CENC_STSD_MODE, &PROP_UINT(first_is_clear ? 1 : 2) ); + stsd_idx = first_crypted_stsd; + } + + } + + if (gf_isom_is_ismacryp_media(mov, track, stsd_idx)) { + gf_isom_get_ismacryp_info(mov, track, stsd_idx, NULL, &scheme_type, &scheme_version, &scheme_uri, &kms_uri, &selectiveEncryption, &IVLength, &KeyIndicationLength); + } else if (gf_isom_is_omadrm_media(mov, track, stsd_idx)) { + //u8 hash[20]; + gf_isom_get_omadrm_info(mov, track, stsd_idx, NULL, &scheme_type, &scheme_version, &contentID, &kms_uri, &txtHdr, &txtHdrLen, &plainTextLen, &crypt_type, &selectiveEncryption, &IVLength, &KeyIndicationLength); + + //gf_media_get_file_hash(gf_isom_get_filename(mov), hash); + } else if (gf_isom_is_cenc_media(mov, track, stsd_idx)) { + ch->is_cenc = GF_TRUE; + + gf_isom_get_cenc_info(ch->owner->mov, ch->track, stsd_idx, NULL, &scheme_type, &scheme_version, NULL); + + PSSH_count = gf_isom_get_pssh_count(ch->owner->mov); + //if no PSSH declared, DO update the properties (PSSH is not mandatory) + } else if (gf_isom_is_adobe_protection_media(mov, track, stsd_idx)) { + u32 ofmt; + scheme_version = 1; + scheme_type = GF_ISOM_ADOBE_SCHEME; + const char *metadata = NULL; + + gf_isom_get_adobe_protection_info(mov, track, stsd_idx, &ofmt, &scheme_type, &scheme_version, &metadata); + if (metadata) + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ADOBE_CRYPT_META, &PROP_DATA((char *)metadata, (u32) strlen(metadata) ) ); + } + + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_PROTECTION_SCHEME_TYPE, &PROP_UINT(scheme_type) ); + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_PROTECTION_SCHEME_VERSION, &PROP_UINT(scheme_version) ); + if (scheme_uri) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_PROTECTION_SCHEME_URI, &PROP_STRING((char*) scheme_uri) ); + if (kms_uri) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_PROTECTION_KMS_URI, &PROP_STRING((char*) kms_uri) ); + + if (selectiveEncryption) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISMA_SELECTIVE_ENC, &PROP_BOOL(GF_TRUE) ); + if (IVLength) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISMA_IV_LENGTH, &PROP_UINT(IVLength) ); + if (KeyIndicationLength) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ISMA_KI_LENGTH, &PROP_UINT(KeyIndicationLength) ); + if (crypt_type) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_OMA_CRYPT_TYPE, &PROP_UINT(crypt_type) ); + if (contentID) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_OMA_CID, &PROP_STRING(contentID) ); + if (txtHdr) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_OMA_TXT_HDR, &PROP_STRING(txtHdr) ); + if (plainTextLen) gf_filter_pid_set_property(ch->pid, GF_PROP_PID_OMA_CLEAR_LEN, &PROP_LONGUINT(plainTextLen) ); + + if (ch->is_cenc) { + u8 *psshd; + GF_BitStream *pssh_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + u32 s, container_type; + + gf_bs_write_u32(pssh_bs, PSSH_count); + + /*fill PSSH in the structure. We will free it in CENC_Setup*/ + for (i=0; iowner->mov, i+1, SystemID, &version, &KID_count, (const bin128 **) & KIDs, (const u8 **) &private_data, &private_data_size); + + gf_bs_write_data(pssh_bs, SystemID, 16); + gf_bs_write_u32(pssh_bs, version); + gf_bs_write_u32(pssh_bs, KID_count); + for (s=0; spid, GF_PROP_PID_CENC_PSSH, & PROP_DATA_NO_COPY(psshd, s) ); + + gf_isom_cenc_get_default_info(ch->owner->mov, ch->track, stsd_idx, &container_type, &ch->pck_encrypted, &ch->IV_size, &ch->KID, &ch->constant_IV_size, &ch->constant_IV, &ch->crypt_byte_block, &ch->skip_byte_block); + + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CENC_STORE, &PROP_UINT(container_type) ); + + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_ENCRYPTED, &PROP_BOOL(ch->pck_encrypted) ); + + if (ch->skip_byte_block || ch->crypt_byte_block) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CENC_PATTERN, &PROP_FRAC_INT(ch->skip_byte_block, ch->crypt_byte_block) ); + } + + if (ch->IV_size) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CENC_IV_SIZE, &PROP_UINT(ch->IV_size) ); + } + if (ch->constant_IV_size) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CENC_IV_CONST, &PROP_DATA(ch->constant_IV, ch->constant_IV_size) ); + } + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_KID, &PROP_DATA(ch->KID, sizeof(bin128) ) ); + } +} + + +ISOMChannel *isor_create_channel(ISOMReader *read, GF_FilterPid *pid, u32 track, u32 item_id, Bool force_no_extractors) +{ + ISOMChannel *ch; + const GF_PropertyValue *p; + s64 ts_shift; + if (!read->mov) return NULL; + + GF_SAFEALLOC(ch, ISOMChannel); + if (!ch) { + return NULL; + } + ch->owner = read; + ch->pid = pid; + ch->to_init = GF_TRUE; + gf_list_add(read->channels, ch); + ch->track = track; + ch->item_id = item_id; + + ch->nalu_extract_mode = 0; + ch->track_id = gf_isom_get_track_id(read->mov, ch->track); + switch (gf_isom_get_media_type(ch->owner->mov, ch->track)) { + case GF_ISOM_MEDIA_OCR: + ch->streamType = GF_STREAM_OCR; + break; + case GF_ISOM_MEDIA_SCENE: + ch->streamType = GF_STREAM_SCENE; + break; + case GF_ISOM_MEDIA_VISUAL: + case GF_ISOM_MEDIA_AUXV: + case GF_ISOM_MEDIA_PICT: + gf_isom_get_reference(ch->owner->mov, ch->track, GF_ISOM_REF_BASE, 1, &ch->base_track); + //use base track only if avc/svc or hevc/lhvc. If avc+lhvc we need different rules + if ( gf_isom_get_avc_svc_type(ch->owner->mov, ch->base_track, 1) == GF_ISOM_AVCTYPE_AVC_ONLY) { + if ( gf_isom_get_hevc_lhvc_type(ch->owner->mov, ch->track, 1) >= GF_ISOM_HEVCTYPE_HEVC_ONLY) { + ch->base_track=0; + } + } + ch->next_track = 0; + /*in scalable mode add SPS/PPS in-band*/ + if (ch->base_track) + ch->nalu_extract_mode = GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG /*| GF_ISOM_NALU_EXTRACT_ANNEXB_FLAG*/; + break; + } + if (!read->noedit) { + ch->ts_offset = 0; + ch->has_edit_list = gf_isom_get_edit_list_type(ch->owner->mov, ch->track, &ch->ts_offset) ? GF_TRUE : GF_FALSE; + if (!ch->has_edit_list && ch->ts_offset) { + //if >0 this is a hold, we signal positive delay + //if <0 this is a skip, we signal negative delay + gf_filter_pid_set_property(pid, GF_PROP_PID_DELAY, &PROP_SINT((s32) ch->ts_offset) ); + } + } else + ch->has_edit_list = GF_FALSE; + + ch->has_rap = (gf_isom_has_sync_points(ch->owner->mov, ch->track)==1) ? GF_TRUE : GF_FALSE; + gf_filter_pid_set_property(pid, GF_PROP_PID_HAS_SYNC, &PROP_BOOL(ch->has_rap) ); + ch->time_scale = gf_isom_get_media_timescale(ch->owner->mov, ch->track); + + ts_shift = gf_isom_get_cts_to_dts_shift(ch->owner->mov, ch->track); + if (ts_shift) { + gf_filter_pid_set_property(pid, GF_PROP_PID_CTS_SHIFT, &PROP_UINT((u32) ts_shift) ); + } + + if (!track || !gf_isom_is_track_encrypted(read->mov, track)) { + if (force_no_extractors) { + ch->nalu_extract_mode = GF_ISOM_NALU_EXTRACT_LAYER_ONLY; + } else { + switch (read->smode) { + case MP4DMX_SPLIT_EXTRACTORS: + ch->nalu_extract_mode = GF_ISOM_NALU_EXTRACT_INSPECT | GF_ISOM_NALU_EXTRACT_TILE_ONLY; + break; + case MP4DMX_SPLIT: + ch->nalu_extract_mode = GF_ISOM_NALU_EXTRACT_LAYER_ONLY | GF_ISOM_NALU_EXTRACT_TILE_ONLY; + break; + default: + break; + } + } + + if (ch->nalu_extract_mode) { + gf_isom_set_nalu_extract_mode(ch->owner->mov, ch->track, ch->nalu_extract_mode); + } + return ch; + } + if (ch->owner->nocrypt) { + ch->is_encrypted = GF_FALSE; + return ch; + } + ch->is_encrypted = GF_TRUE; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (p) gf_filter_pid_set_property(pid, GF_PROP_PID_ORIG_STREAM_TYPE, &PROP_UINT(p->value.uint) ); + + gf_filter_pid_set_property(pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_ENCRYPTED) ); + + isor_set_crypt_config(ch); + + if (ch->nalu_extract_mode) { + if (ch->is_encrypted) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[IsoMedia] using sample NAL rewrite with encryption is not yet supported, patch welcome\n")); + } else { + gf_isom_set_nalu_extract_mode(ch->owner->mov, ch->track, ch->nalu_extract_mode); + } + } + return ch; +} + +/*switch channel quality. Return next channel or current channel if error*/ +static +u32 isoffin_channel_switch_quality(ISOMChannel *ch, GF_ISOFile *the_file, Bool switch_up) +{ + u32 i, count, next_track, trackID, cur_track; + s32 ref_count; + + cur_track = ch->next_track ? ch->next_track : ch->track; + count = gf_isom_get_track_count(the_file); + trackID = gf_isom_get_track_id(the_file, cur_track); + next_track = 0; + + if (switch_up) { + for (i = 0; i < count; i++) { + ref_count = gf_isom_get_reference_count(the_file, i+1, GF_ISOM_REF_SCAL); + if (ref_count < 0) + return cur_track; //error + if (ref_count == 0) + continue; + /*next track is the one that has the last reference of type GF_ISOM_REF_SCAL refers to this current track*/ + if ((u32)ref_count == gf_isom_has_track_reference(the_file, i+1, GF_ISOM_REF_SCAL, trackID)) { + next_track = i+1; + break; + } + } + /*this is the highest quality*/ + if (!next_track) + return cur_track; + } else { + if (cur_track == ch->base_track) + return cur_track; + ref_count = gf_isom_get_reference_count(the_file, cur_track, GF_ISOM_REF_SCAL); + if (ref_count <= 0) + return cur_track; + gf_isom_get_reference(the_file, cur_track, GF_ISOM_REF_SCAL, ref_count, &next_track); + if (!next_track) + return cur_track; + } + + /*in scalable mode add SPS/PPS in-band*/ + gf_isom_set_nalu_extract_mode(the_file, next_track, ch->nalu_extract_mode); + + return next_track; +} + +static Bool isoffin_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 count, i; + Bool cancel_event = GF_TRUE; + ISOMChannel *ch; + ISOMReader *read = gf_filter_get_udta(filter); + + if (!read || read->disconnected) return GF_FALSE; + + if (evt->base.type == GF_FEVT_QUALITY_SWITCH) { + count = gf_list_count(read->channels); + for (i = 0; i < count; i++) { + ch = (ISOMChannel *)gf_list_get(read->channels, i); + if (ch->base_track && gf_isom_needs_layer_reconstruction(read->mov)) { + ch->next_track = isoffin_channel_switch_quality(ch, read->mov, evt->quality_switch.up); + } +#ifdef GPAC_ENABLE_COVERAGE + else if (gf_sys_is_cov_mode()) { + isoffin_channel_switch_quality(ch, read->mov, evt->quality_switch.up); + } +#endif + + } + return GF_TRUE; + } + + if (!evt->base.on_pid) return GF_FALSE; + + ch = isor_get_channel(read, evt->base.on_pid); + if (!ch) + return GF_FALSE; + + switch (evt->base.type) { + case GF_FEVT_PLAY: + isor_reset_reader(ch); + ch->eos_sent = GF_FALSE; + ch->speed = evt->play.speed; + ch->initial_play_seen = GF_TRUE; + read->reset_frag_state = 1; + if (read->frag_type) + read->frag_type = 1; + + ch->start = ch->end = 0; + if (evt->play.speed>=0) { + Double t; + if (evt->play.start_range>=0) { + t = evt->play.start_range; + t *= ch->time_scale; + ch->start = (u64) t; + } + if (evt->play.end_range >= evt->play.start_range) { + ch->end = (u64) -1; + if (evt->play.end_rangeplay.end_range; + t *= ch->time_scale; + ch->end = (u64) t; + } + } + } else { + Double end = evt->play.end_range; + if (end==-1) end = 0; + ch->start = (u64) (s64) (evt->play.start_range * ch->time_scale); + if (end <= evt->play.start_range) + ch->end = (u64) (s64) (end * ch->time_scale); + } + ch->playing = GF_TRUE; + ch->sample_num = evt->play.from_pck; + + ch->sap_only = evt->play.drop_non_ref ? GF_TRUE : GF_FALSE; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[IsoMedia] Starting channel playback "LLD" to "LLD" (%g to %g)\n", ch->start, ch->end, evt->play.start_range, evt->play.end_range)); + + if (!read->nb_playing) + gf_isom_reset_seq_num(read->mov); + + if (read->is_partial_download) read->input_loaded = GF_FALSE; + + if (evt->play.no_byterange_forward) { + //new segment will be loaded, reset + gf_isom_reset_tables(read->mov, GF_TRUE); + gf_isom_reset_data_offset(read->mov, NULL); + read->refresh_fragmented = GF_TRUE; + //send play event + cancel_event = GF_FALSE; + } else if (!read->nb_playing && read->pid && !read->input_loaded) { + GF_FilterEvent fevt; + Bool is_sidx_seek = GF_FALSE; + u64 max_offset = GF_FILTER_NO_BO; + count = gf_list_count(read->channels); + + //try sidx + if (read->frag_type) { + u32 ts; + u64 dur=0; + GF_Err e = gf_isom_get_file_offset_for_time(read->mov, evt->play.start_range, &max_offset); + if (e==GF_OK) { + if (evt->play.start_range>0) + gf_isom_reset_tables(read->mov, GF_TRUE); + + is_sidx_seek = GF_TRUE; + //in case we loaded moov but not sidx, update duration + if ((gf_isom_get_sidx_duration(read->mov, &dur, &ts)==GF_OK) && dur) { + dur *= read->time_scale; + dur /= ts; + if (ch->duration != dur) { + ch->duration = dur; + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_DURATION, &PROP_FRAC64_INT(ch->duration, read->time_scale)); + } + } + } + } + + if (!is_sidx_seek) { + for (i=0; i< count; i++) { + u32 mode, sample_desc_index, sample_num; + u64 data_offset; + GF_Err e; + u64 time; + ch = gf_list_get(read->channels, i); + mode = ch->disable_seek ? GF_ISOM_SEARCH_BACKWARD : GF_ISOM_SEARCH_SYNC_BACKWARD; + time = (u64) (evt->play.start_range * ch->time_scale); + + /*take care of seeking out of the track range*/ + if (!read->frag_type && (ch->duration < time)) { + e = gf_isom_get_sample_for_movie_time(read->mov, ch->track, ch->duration, &sample_desc_index, mode, NULL, &sample_num, &data_offset); + } else { + e = gf_isom_get_sample_for_movie_time(read->mov, ch->track, time, &sample_desc_index, mode, NULL, &sample_num, &data_offset); + } + if ((e == GF_OK) && (data_offsetplay.start_range || read->is_partial_download) && (max_offset != GF_FILTER_NO_BO) ) { + + //send a seek request + read->is_partial_download = GF_TRUE; + read->wait_for_source = GF_TRUE; + read->refresh_fragmented = GF_TRUE; + + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, read->pid); + fevt.seek.start_offset = max_offset; + gf_filter_pid_send_event(read->pid, &fevt); + gf_isom_set_byte_offset(read->mov, is_sidx_seek ? 0 : max_offset); + + } + } + //always request a process task upon a play + gf_filter_post_process_task(read->filter); + read->nb_playing++; + //cancel event unless dash mode + return cancel_event; + + case GF_FEVT_STOP: + if (read->nb_playing) read->nb_playing--; + isor_reset_reader(ch); + //don't send a stop if some of our channels are still waiting for initial play + for (i=0; ichannels); i++) { + ISOMChannel *a_ch = gf_list_get(read->channels, i); + if (ch==a_ch) continue; + if (!a_ch->initial_play_seen) return GF_TRUE; + } + //cancel event if nothing playing + if (read->nb_playing) return GF_TRUE; + read->input_is_stop = GF_TRUE; + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + case GF_FEVT_RESUME: + ch->speed = evt->play.speed; + if (ch->sap_only && !evt->play.drop_non_ref) { + ch->sap_only = 2; + } else { + ch->sap_only = evt->play.drop_non_ref ? GF_TRUE : GF_FALSE; + } + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +static void isoffin_push_buffer(GF_Filter *filter, ISOMReader *read, const u8 *pck_data, u32 data_size) +{ + u64 bytes_missing; + GF_Err e; + + if (!read->mem_url) { + char szPath[200]; + sprintf(szPath, "gmem://%p", &read->mem_blob); + read->mem_url = gf_strdup(szPath); + } + read->mem_blob.data = gf_realloc(read->mem_blob.data, read->mem_blob.size + data_size); + memcpy(read->mem_blob.data + read->mem_blob.size, pck_data, data_size); + read->mem_blob.size += data_size; + + if (read->mem_load_mode==1) { + u32 box_type; + e = gf_isom_open_progressive_ex(read->mem_url, 0, 0, GF_FALSE, &read->mov, &bytes_missing, &box_type); + + if (e && (e != GF_ISOM_INCOMPLETE_FILE)) { + gf_filter_setup_failure(filter, e); + read->mem_load_mode = 0; + read->in_error = e; + return; + } + if (!read->mov) { + switch (box_type) { + case GF_4CC('m','d','a','t'): + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[IsoMedia] non fragmented ISOBMFF with moof after mdat and no underlying file cache (pipe or other stream input), not supported !\n")); + gf_filter_setup_failure(filter, GF_NOT_SUPPORTED); + read->mem_load_mode = 0; + read->in_error = GF_NOT_SUPPORTED; + break; + default: + read->moov_not_loaded = GF_TRUE; + break; + } + return; + } + + read->frag_type = gf_isom_is_fragmented(read->mov) ? 1 : 0; + read->time_scale = gf_isom_get_timescale(read->mov); + isor_declare_objects(read); + read->mem_load_mode = 2; + read->moov_not_loaded = GF_FALSE; + return; + } + //refresh file + gf_isom_refresh_fragmented(read->mov, &bytes_missing, read->mem_url); +} + +static void isoffin_purge_mem(ISOMReader *read, u64 min_offset) +{ + u32 i, count; + u64 top_offset; + u32 nb_bytes_to_purge; + u64 bytes_missing; + + //purge every + if (read->mstore_purge && (min_offset - read->last_min_offset < read->mstore_purge)) + return; + + if (read->frag_type) { + //get position of current box being parsed - if new offset is greater than this box we cannot remove + //bytes (we would trash the top-level box header) + gf_isom_get_current_top_box_offset(read->mov, &top_offset); + if (top_offsetlast_min_offset = min_offset; + + assert(min_offset>=read->bytes_removed); + //min_offset is given in absolute file position + nb_bytes_to_purge = (u32) (min_offset - read->bytes_removed); + assert(nb_bytes_to_purge<=read->mem_blob.size); + + memmove(read->mem_blob.data, read->mem_blob.data+nb_bytes_to_purge, read->mem_blob.size - nb_bytes_to_purge); + read->mem_blob.size -= nb_bytes_to_purge; + read->bytes_removed += nb_bytes_to_purge; + gf_isom_set_removed_bytes(read->mov, read->bytes_removed); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[IsoMedia] mem mode %d bytes in mem, "LLU" bytes trashed since start\n", read->mem_blob.size, read->bytes_removed)); + + //force a refresh + gf_isom_refresh_fragmented(read->mov, &bytes_missing, read->mem_url); + + if (!read->frag_type) + return; + + //fragmented file, cleanup sample tables + count = gf_list_count(read->channels); + for (i=0; ichannels, i); + u32 num_samples; +#ifndef GPAC_DISABLE_LOG + u32 prev_samples = gf_isom_get_sample_count(read->mov, ch->track); +#endif + //don't run this too often + if (ch->sample_num<=1+read->mstore_samples) continue; + + if (gf_isom_purge_samples(read->mov, ch->track, ch->sample_num-1) == GF_OK) + ch->sample_num = 1; + + num_samples = gf_isom_get_sample_count(read->mov, ch->track); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[IsoMedia] mem mode %d samples now in track %d (prev %d)\n", num_samples, ch->track_id, prev_samples)); + } +} + +static GF_Err isoffin_process(GF_Filter *filter) +{ + ISOMReader *read = gf_filter_get_udta(filter); + u32 i, count = gf_list_count(read->channels); + Bool is_active = GF_FALSE; + Bool in_is_eos = GF_FALSE; + Bool has_new_data = GF_FALSE; + u64 min_offset_plus_one = 0; + if (read->in_error) + return read->in_error; + + if (read->pid) { + Bool fetch_input = GF_TRUE; + if (read->mem_load_mode==2) { + if (!read->force_fetch && read->mem_blob.size > read->mstore_size) { + fetch_input = GF_FALSE; + } + read->force_fetch = GF_FALSE; + } + while (fetch_input) { + GF_FilterPacket *pck = gf_filter_pid_get_packet(read->pid); + if (!pck) { + //we issued a seek, wait for the first packet to be received before fetching channels + //otherwise we could end up reading from the wrong cache + if (read->wait_for_source) { + //something went wrong during the seek request + if (gf_filter_pid_is_eos(read->pid)) + return GF_EOS; + return GF_OK; + } + break; + } + read->wait_for_source = GF_FALSE; + + if (read->mem_load_mode) { + u32 data_size; + const u8 *pck_data = gf_filter_pck_get_data(pck, &data_size); + isoffin_push_buffer(filter, read, pck_data, data_size); + } + gf_filter_pid_drop_packet(read->pid); + has_new_data = GF_TRUE; + if (read->in_error) + return read->in_error; + } + if (gf_filter_pid_is_eos(read->pid)) { + read->input_loaded = GF_TRUE; + in_is_eos = GF_TRUE; + } + if (read->input_is_stop) { + read->input_loaded = GF_TRUE; + in_is_eos = GF_TRUE; + read->input_is_stop = GF_FALSE; + } + if (!read->frag_type && read->input_loaded) { + in_is_eos = GF_TRUE; + } + } else if (read->extern_mov) { + in_is_eos = GF_TRUE; + read->input_loaded = GF_TRUE; + } + if (read->moov_not_loaded) { + if (read->mem_load_mode) + return GF_OK; + read->moov_not_loaded = GF_FALSE; + return isoffin_setup(filter, read); + } + + if (read->refresh_fragmented) { + const GF_PropertyValue *prop; + + if (in_is_eos) { + read->refresh_fragmented = GF_FALSE; + } else { + prop = gf_filter_pid_get_property(read->pid, GF_PROP_PID_FILE_CACHED); + if (prop && prop->value.boolean) + read->refresh_fragmented = GF_FALSE; + } + + if (has_new_data) { + u64 bytesMissing=0; + GF_Err e; + const char *new_url = NULL; + prop = gf_filter_pid_get_property(read->pid, GF_PROP_PID_FILEPATH); + if (prop) new_url = prop->value.string; + + e = gf_isom_refresh_fragmented(read->mov, &bytesMissing, new_url); + + if (e && (e!= GF_ISOM_INCOMPLETE_FILE)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Failed to refresh current segment: %s\n", gf_error_to_string(e) )); + read->refresh_fragmented = GF_FALSE; + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Refreshing current segment at UTC "LLU" - "LLU" bytes still missing - input is EOS %d\n", gf_net_get_utc(), bytesMissing, in_is_eos)); + } + + if (!read->refresh_fragmented && (e==GF_ISOM_INCOMPLETE_FILE)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[IsoMedia] Incomplete Segment received - "LLU" bytes missing but EOF found\n", bytesMissing )); + } + +#ifndef GPAC_DISABLE_LOG + if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_DEBUG)) { + for (i=0; ichannels, i); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] refresh track %d fragment - cur sample %d - new sample count %d\n", ch->track, ch->sample_num, gf_isom_get_sample_count(ch->owner->mov, ch->track) )); + } + } +#endif + isor_check_producer_ref_time(read); + if (!read->frag_type) + read->refresh_fragmented = GF_FALSE; + } + } + + for (i=0; ichannels, i); + if (!ch->playing) { + continue; + } + //eos not sent on this channel, we are active + if (!ch->eos_sent) + is_active = GF_TRUE; + + while (nb_pck) { + ch->sample_data_offset = 0; + if (!read->full_segment_flush && gf_filter_pid_would_block(ch->pid) ) + break; + + if (ch->item_id) { + isor_reader_get_sample_from_item(ch); + } else { + isor_reader_get_sample(ch); + } + + if (read->stsd && (ch->last_sample_desc_index != read->stsd) && ch->sample) { + isor_reader_release_sample(ch); + continue; + } + if (ch->sample) { + u32 sample_dur; + u8 dep_flags; + u8 *subs_buf; + u32 subs_buf_size; + GF_FilterPacket *pck; + if (ch->needs_pid_reconfig) { + isor_update_channel_config(ch); + ch->needs_pid_reconfig = GF_FALSE; + } + + //strip param sets from payload, trigger reconfig if needed + isor_reader_check_config(ch); + + pck = gf_filter_pck_new_alloc(ch->pid, ch->sample->dataLength, &data); + assert(pck); + + memcpy(data, ch->sample->data, ch->sample->dataLength); + + gf_filter_pck_set_dts(pck, ch->dts); + gf_filter_pck_set_cts(pck, ch->cts); + if (ch->sample->IsRAP==-1) { + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + ch->redundant = 1; + } else { + gf_filter_pck_set_sap(pck, (GF_FilterSAPType) ch->sample->IsRAP); + } + + if (ch->sap_3) + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_3); + else if (ch->sap_4_type) { + gf_filter_pck_set_sap(pck, (ch->sap_4_type==GF_ISOM_SAMPLE_PREROLL) ? GF_FILTER_SAP_4_PROL : GF_FILTER_SAP_4); + gf_filter_pck_set_roll_info(pck, ch->roll); + } + + sample_dur = ch->au_duration; + if (ch->sample->nb_pack) + sample_dur *= ch->sample->nb_pack; + gf_filter_pck_set_duration(pck, sample_dur); + gf_filter_pck_set_seek_flag(pck, ch->seek_flag); + + dep_flags = ch->isLeading; + dep_flags <<= 2; + dep_flags |= ch->dependsOn; + dep_flags <<= 2; + dep_flags |= ch->dependedOn; + dep_flags <<= 2; + dep_flags |= ch->redundant; + + if (dep_flags) + gf_filter_pck_set_dependency_flags(pck, dep_flags); + + gf_filter_pck_set_crypt_flags(pck, ch->pck_encrypted ? GF_FILTER_PCK_CRYPT : 0); + gf_filter_pck_set_seq_num(pck, ch->sample_num); + + + subs_buf = gf_isom_sample_get_subsamples_buffer(read->mov, ch->track, ch->sample_num, &subs_buf_size); + if (subs_buf) { + gf_filter_pck_set_property(pck, GF_PROP_PCK_SUBS, &PROP_DATA_NO_COPY(subs_buf, subs_buf_size) ); + } + + /**/ + if (ch->cenc_state_changed && ch->pck_encrypted) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CENC_PATTERN, &PROP_FRAC_INT(ch->skip_byte_block, ch->crypt_byte_block) ); + + if (!ch->IV_size) { + if (ch->constant_IV_size) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CENC_IV_CONST, &PROP_DATA(ch->constant_IV, ch->constant_IV_size) ); + } else { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CENC_IV_CONST, NULL); + } + } else { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_CENC_IV_SIZE, &PROP_UINT(ch->IV_size) ); + } + + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_KID, &PROP_DATA(ch->KID, sizeof(bin128) ) ); + } + if (ch->sai_buffer && ch->pck_encrypted) { + assert(ch->sai_buffer_size); + gf_filter_pck_set_property(pck, GF_PROP_PCK_CENC_SAI, &PROP_DATA(ch->sai_buffer, ch->sai_buffer_size) ); + } + + if (read->sigfrag) { + GF_ISOFragmentBoundaryInfo finfo; + if (gf_isom_sample_is_fragment_start(read->mov, ch->track, ch->sample_num, &finfo) ) { + u64 start=0; + u32 traf_start = finfo.seg_start_plus_one ? 2 : 1; + gf_filter_pck_set_property(pck, GF_PROP_PCK_FRAG_START, &PROP_UINT(traf_start)); + + start = finfo.frag_start; + if (finfo.seg_start_plus_one) start = finfo.seg_start_plus_one-1; + gf_filter_pck_set_property(pck, GF_PROP_PCK_FRAG_RANGE, &PROP_FRAC64_INT(start, finfo.mdat_end)); + if (finfo.moof_template) { + gf_filter_pck_set_property(pck, GF_PROP_PCK_MOOF_TEMPLATE, &PROP_DATA((u8 *)finfo.moof_template, finfo.moof_template_size)); + } + if (finfo.sidx_end) { + gf_filter_pck_set_property(pck, GF_PROP_PCK_SIDX_RANGE, &PROP_FRAC64_INT(finfo.sidx_start , finfo.sidx_end)); + } + + if (read->seg_name_changed) { + const GF_PropertyValue *p = gf_filter_pid_get_property(read->pid, GF_PROP_PID_URL); + read->seg_name_changed = GF_FALSE; + if (p && p->value.string) { + gf_filter_pck_set_property(pck, GF_PROP_PID_URL, &PROP_STRING(p->value.string)); + } + } + } + } + if (ch->sender_ntp) { + gf_filter_pck_set_property(pck, GF_PROP_PCK_SENDER_NTP, &PROP_LONGUINT(ch->sender_ntp)); + if (ch->ntp_at_server_ntp) { + gf_filter_pck_set_property(pck, GF_PROP_PCK_RECEIVER_NTP, &PROP_LONGUINT(ch->ntp_at_server_ntp)); + } + } + ch->eos_sent = GF_FALSE; + gf_filter_pck_send(pck); + isor_reader_release_sample(ch); + + ch->last_valid_sample_data_offset = ch->sample_data_offset; + nb_pck--; + } else if (ch->last_state==GF_EOS) { + if (in_is_eos && !ch->eos_sent) { + ch->eos_sent = GF_TRUE; + read->eos_signaled = GF_TRUE; + gf_filter_pid_set_eos(ch->pid); + } + break; + } else { + read->force_fetch = GF_TRUE; + break; + } + } + if (!min_offset_plus_one || (min_offset_plus_one - 1 > ch->last_valid_sample_data_offset)) + min_offset_plus_one = 1 + ch->last_valid_sample_data_offset; + } + if (read->mem_load_mode && min_offset_plus_one) { + isoffin_purge_mem(read, min_offset_plus_one-1); + } + + if (!is_active) { + return GF_EOS; + } + //if (in_is_eos) + gf_filter_ask_rt_reschedule(filter, 1); + return GF_OK; + +} + +static const char *isoffin_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + if (gf_isom_probe_data(data, size)) { + *score = GF_FPROBE_SUPPORTED; + return "video/mp4"; + } + return NULL; +} + +#define OFFS(_n) #_n, offsetof(ISOMReader, _n) + +static const GF_FilterArgs ISOFFInArgs[] = +{ + { OFFS(src), "location of source content", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(allt), "load all tracks even if unknown", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(noedit), "do not use edit lists", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(itt), "convert all items of root meta into a single PID", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(itemid), "keep item IDs in PID properties", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(smode), "load mode for scalable/tile tracks\n" + "- split: each track is declared, extractors are removed\n" + "- splitx: each track is declared, extractors are kept\n" + "- single: a single track is declared (highest level for scalable, tile base for tiling)", GF_PROP_UINT, "split", "split|splitx|single", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(alltk), "declare all tracks even disabled ones", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(frame_size), "frame size for raw audio samples (dispatches frame_size samples per packet)", GF_PROP_UINT, "1024", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(expart), "expose cover art as a dedicated video pid", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(sigfrag), "signal fragment and segment boundaries of source on output packets", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + + { OFFS(tkid), "declare only track based on given param" + "- integer value: declares track with the given ID\n" + "- audio: declares first audio track\n" + "- video: declares first video track\n" + "- 4CC: declares first track with matching 4CC for handler type", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(stsd), "only extract sample mapped to the given sample desciption index. 0 means no filter", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(mov), "pointer to a read/edit ISOBMF file used internally by importers and exporters", GF_PROP_POINTER, NULL, NULL, GF_FS_ARG_HINT_HIDE}, + { OFFS(analyze), "skip reformat of decoder config and SEI and dispatch all NAL in input order - shall only be used with inspect filter analyze mode!", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_HIDE}, + { OFFS(catseg), "append the given segment to the movie at init time (only local file supported)", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_HIDE}, + { OFFS(nocrypt), "signal encrypted tracks as non encrypted (mostly used for export)", GF_PROP_BOOL, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(mstore_size), "target buffer size in bytes", GF_PROP_UINT, "1000000", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(mstore_purge), "minimum size in bytes between memory purges when reading from memory stream (pipe etc...), 0 means purge as soon as possible", GF_PROP_UINT, "50000", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(mstore_samples), "minimum number of samples to be present before purging sample tables when reading from memory stream (pipe etc...), 0 means purge as soon as possible", GF_PROP_UINT, "50", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(strtxt), "load text tracks (apple/tx3g) as MPEG-4 streaming text tracks", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + + {0} +}; + +static const GF_FilterCapability ISOFFInCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "mp4|mpg4|m4a|m4i|3gp|3gpp|3g2|3gp2|iso|m4s|heif|heic|avci|mj2|mov|qt"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "application/x-isomedia|application/mp4|video/mp4|audio/mp4|video/3gpp|audio/3gpp|video/3gp2|audio/3gp2|video/iso.segment|audio/iso.segment|image/heif|image/heic|image/avci|video/quicktime"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_OD), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_METADATA), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_ENCRYPTED), + + CAP_BOOL(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + //we don't set output cap for streamtype FILE for now. +}; + +GF_FilterRegister ISOFFInRegister = { + .name = "mp4dmx", + GF_FS_SET_DESCRIPTION("ISOBMFF/QT demuxer") + GF_FS_SET_HELP("This filter demultiplexes ISOBMF and QT files (regular or fragmented).\n" + "# Track Selection\n" + "The filter can use fragment identifiers of source to select a single track for playback. The allowed fragments are:\n" + " - #audio: only use the first audio track\n" + " - #video: only use the first video track\n" + " - #auxv: only use the first auxiliary video track\n" + " - #pict: only use the first picture track\n" + " - #text: only use the first text track\n" + " - #trackID=VAL: only use the track with given ID\n" + " - #ID=VAL: only use the track with given ID\n" + " - #VAL: only use the track with given ID\n" + "\n" + "# Scalable Tracks\n" + "When scalable tracks are present in a file, the reader can operate in 3 modes using [-smode]() option:\n"\ + "- smode=single: resolves all extractors to extract a single bitstream from a scalable set. The highest level is used\n"\ + "In this mode, there is no enhancement decoder config, only a base one resulting from the merge of the configs\n"\ + "- smode=split: all extractors are removed and every track of the scalable set is declared. In this mode, each enhancement track has no base decoder config\n" + "and an enhancement decoder config.\n"\ + "- smode=splitx: extractors are kept in the bitstream, and every track of the scalable set is declared. In this mode, each enhancement track has a base decoder config\n" + " (copied from base) and an enhancement decoder config. This is mostly used for DASHing content.\n"\ + "Warning: smode=splitx will result in extractor NAL units still present in the output bitstream, which shall only be true if the output is ISOBMFF based\n") + .private_size = sizeof(ISOMReader), + .args = ISOFFInArgs, + .initialize = isoffin_initialize, + .finalize = isoffin_finalize, + .process = isoffin_process, + .configure_pid = isoffin_configure_pid, + SETCAPS(ISOFFInCaps), + .process_event = isoffin_process_event, + .probe_data = isoffin_probe_data +}; + + +#endif /*GPAC_DISABLE_ISOM*/ + +const GF_FilterRegister *isoffin_register(GF_FilterSession *session) +{ +#ifdef GPAC_DISABLE_ISOM + return NULL; +#else + return &ISOFFInRegister; +#endif /*GPAC_DISABLE_ISOM*/ +} + diff --git a/src/filters/isoffin_read_ch.c b/src/filters/isoffin_read_ch.c new file mode 100644 index 0000000..59866ad --- /dev/null +++ b/src/filters/isoffin_read_ch.c @@ -0,0 +1,693 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2018 + * All rights reserved + * + * This file is part of GPAC / ISOBMFF reader filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include "isoffin.h" + +#ifndef GPAC_DISABLE_ISOM +#include +#include + + +void isor_reset_reader(ISOMChannel *ch) +{ + ch->last_state = GF_OK; + isor_reader_release_sample(ch); + + if (ch->static_sample) { + ch->static_sample->dataLength = ch->static_sample->alloc_size; + gf_isom_sample_del(&ch->static_sample); + } + ch->sample = NULL; + ch->sample_num = 0; + ch->speed = 1.0; + ch->start = ch->end = 0; + ch->to_init = 1; + ch->playing = GF_FALSE; + if (ch->sai_buffer) gf_free(ch->sai_buffer); + ch->sai_buffer = NULL; + ch->sai_alloc_size = 0; + ch->dts = ch->cts = 0; + ch->seek_flag = 0; +} + +void isor_check_producer_ref_time(ISOMReader *read) +{ + GF_ISOTrackID trackID; + u64 ntp; + u64 timestamp; + + if (gf_sys_is_test_mode()) { + return; + } + + if (gf_isom_get_last_producer_time_box(read->mov, &trackID, &ntp, ×tamp, GF_TRUE)) { +#if !defined(_WIN32_WCE) && !defined(GPAC_DISABLE_LOG) + + if (gf_log_tool_level_on(GF_LOG_DASH, GF_LOG_DEBUG)) { + time_t secs; + struct tm t; + + s32 diff = gf_net_get_ntp_diff_ms(ntp); + + secs = (ntp>>32) - GF_NTP_SEC_1900_TO_1970; + t = *gf_gmtime(&secs); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] TrackID %d: Timestamp "LLU" matches sender NTP time %d-%02d-%02dT%02d:%02d:%02dZ - NTP clock diff (local - remote): %d ms\n", trackID, timestamp, 1900+t.tm_year, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, diff)); + } +#endif + read->last_sender_ntp = ntp; + read->cts_for_last_sender_ntp = timestamp; + read->ntp_at_last_sender_ntp = gf_net_get_ntp_ts(); + } +} + + +static void init_reader(ISOMChannel *ch) +{ + u32 sample_desc_index=0; + + ch->au_seq_num = 1; + + assert(ch->sample==NULL); + if (!ch->static_sample) { + ch->static_sample = gf_isom_sample_new(); + } + + if (ch->streamType==GF_STREAM_OCR) { + assert(!ch->sample); + ch->sample = gf_isom_sample_new(); + ch->sample->IsRAP = RAP; + ch->sample->DTS = ch->start; + ch->last_state=GF_OK; + } else if (ch->sample_num) { + ch->sample = gf_isom_get_sample_ex(ch->owner->mov, ch->track, ch->sample_num, &sample_desc_index, ch->static_sample, &ch->sample_data_offset); + ch->disable_seek = GF_TRUE; + ch->au_seq_num = ch->sample_num; + } else { + //if seek is disabled, get the next closest sample for this time; otherwose, get the previous RAP sample for this time + u32 mode = ch->disable_seek ? GF_ISOM_SEARCH_BACKWARD : GF_ISOM_SEARCH_SYNC_BACKWARD; + + /*take care of seeking out of the track range*/ + if (!ch->owner->frag_type && (ch->duration<=ch->start)) { + ch->last_state = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->duration-1, &sample_desc_index, mode, &ch->static_sample, &ch->sample_num, &ch->sample_data_offset); + } else if (ch->start || ch->has_edit_list) { + ch->last_state = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->start, &sample_desc_index, mode, &ch->static_sample, &ch->sample_num, &ch->sample_data_offset); + } else { + ch->sample_num = 1; + ch->sample = gf_isom_get_sample_ex(ch->owner->mov, ch->track, ch->sample_num, &sample_desc_index, ch->static_sample, &ch->sample_data_offset); + if (!ch->sample) ch->last_state = GF_EOS; + } + if (ch->last_state) { + ch->sample = NULL; + ch->last_state = GF_OK; + } else { + ch->sample = ch->static_sample; + } + + if (ch->has_rap && ch->has_edit_list) { + ch->edit_sync_frame = ch->sample_num; + } + + if (ch->sample && !ch->sample->data && ch->owner->frag_type && !ch->has_edit_list) { + ch->sample = NULL; + ch->sample_num = 1; + ch->sample = gf_isom_get_sample_ex(ch->owner->mov, ch->track, ch->sample_num, &sample_desc_index, ch->static_sample, &ch->sample_data_offset); + } + } + + + /*no sample means we're not in the track range - stop*/ + if (!ch->sample) { + /*incomplete file - check if we're still downloading or not*/ + if (gf_isom_get_missing_bytes(ch->owner->mov, ch->track)) { + if (!ch->owner->input_loaded) { + ch->last_state = GF_OK; + return; + } + ch->last_state = GF_ISOM_INCOMPLETE_FILE; + } else if (ch->sample_num) { + ch->last_state = (ch->owner->frag_type==1) ? GF_OK : GF_EOS; + ch->to_init = 0; + } + return; + } + + ch->sample_time = ch->sample->DTS; + + ch->to_init = GF_FALSE; + + ch->seek_flag = 0; + if (ch->disable_seek) { + ch->dts = ch->sample->DTS; + ch->cts = ch->sample->DTS + ch->sample->CTS_Offset; + ch->start = 0; + } else { + ch->dts = ch->start; + ch->cts = ch->start; + + //TODO - we need to notify scene decoder how many secs elapsed between RAP and seek point + if (ch->cts != ch->sample->DTS + ch->sample->CTS_Offset) { + ch->seek_flag = 1; + } + } + if (!sample_desc_index) sample_desc_index = 1; + ch->last_sample_desc_index = sample_desc_index; + ch->owner->no_order_check = ch->speed < 0 ? GF_TRUE : GF_FALSE; +} + +void isor_reader_get_sample_from_item(ISOMChannel *ch) +{ + if (ch->au_seq_num) { + if (!ch->owner->itt || !isor_declare_item_properties(ch->owner, ch, 1+ch->au_seq_num)) { + ch->last_state = GF_EOS; + return; + } + } + ch->sample_time = 0; + ch->last_state = GF_OK; + if (!ch->static_sample) { + ch->static_sample = gf_isom_sample_new(); + } + + ch->sample = ch->static_sample; + ch->sample->IsRAP = RAP; + ch->dts = ch->cts = 1000 * ch->au_seq_num; + gf_isom_extract_meta_item_mem(ch->owner->mov, GF_TRUE, 0, ch->item_id, &ch->sample->data, &ch->sample->dataLength, &ch->static_sample->alloc_size, NULL, GF_FALSE); +} + +void isor_reader_get_sample(ISOMChannel *ch) +{ + GF_Err e; + u32 sample_desc_index; + if (ch->sample) return; + + if (ch->next_track) { + ch->track = ch->next_track; + ch->next_track = 0; + } + + if (ch->to_init) { + init_reader(ch); + sample_desc_index = ch->last_sample_desc_index; + } else if (ch->speed < 0) { + if (ch->last_state == GF_EOS) { + ch->sample = NULL; + return; + } + + if (ch->static_sample->IsRAP) { + ch->last_rap_sample_time = ch->sample_time; + } + + e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->sample_time + 1, &sample_desc_index, GF_ISOM_SEARCH_FORWARD, &ch->static_sample, &ch->sample_num, NULL); + + if ((e==GF_EOS) || (ch->static_sample->IsRAP)) { + if (!ch->last_rap_sample_time) { + e = GF_EOS; + } else { + e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->last_rap_sample_time - 1, &sample_desc_index, GF_ISOM_SEARCH_SYNC_BACKWARD, &ch->static_sample, &ch->sample_num, NULL); + } + } + + if (e) { + if ((e==GF_EOS) && !ch->owner->frag_type) { + ch->last_state = GF_EOS; + } + ch->sample = NULL; + return; + } + ch->sample = ch->static_sample; + + if (ch->sample->DTS == ch->sample_time) { + if (!ch->owner->frag_type) { + ch->last_state = GF_EOS; + } + } + if (ch->sample) { + ch->sample_time = ch->sample->DTS; + } + + } else if (ch->has_edit_list) { + u32 prev_sample = ch->sample_num; + e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->sample_time + 1, &sample_desc_index, GF_ISOM_SEARCH_FORWARD, &ch->static_sample, &ch->sample_num, &ch->sample_data_offset); + + if (e == GF_OK) { + ch->sample = ch->static_sample; + + /*we are in forced seek mode: fetch all samples before the one matching the sample time*/ + if (ch->edit_sync_frame) { + ch->edit_sync_frame++; + if (ch->edit_sync_frame < ch->sample_num) { + ch->sample = gf_isom_get_sample_ex(ch->owner->mov, ch->track, ch->edit_sync_frame, &sample_desc_index, ch->static_sample, &ch->sample_data_offset); + ch->sample->DTS = ch->sample_time; + ch->sample->CTS_Offset = 0; + } else { + ch->edit_sync_frame = 0; + if (ch->sample) ch->sample_time = ch->sample->DTS; + } + } else { + /*if we get the same sample, figure out next interesting time (current sample + DTS gap to next sample should be a good bet)*/ + if (prev_sample == ch->sample_num) { + if (ch->owner->frag_type && (ch->sample_num==gf_isom_get_sample_count(ch->owner->mov, ch->track))) { + ch->sample = NULL; + } else { + u32 sample_num = ch->sample_num ? ch->sample_num : 1; + + if (sample_num >= gf_isom_get_sample_count(ch->owner->mov, ch->track) ) { + //e = GF_EOS; + } else { + u32 time_diff = gf_isom_get_sample_duration(ch->owner->mov, ch->track, sample_num); + e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->sample_time + time_diff, &sample_desc_index, GF_ISOM_SEARCH_FORWARD, &ch->static_sample, &ch->sample_num, &ch->sample_data_offset); + if (e==GF_OK) { + ch->sample = ch->static_sample; + } + } + } + } + + /*we jumped to another segment - if RAP is needed look for closest rap in decoding order and + force seek mode*/ + if (ch->sample && !ch->sample->IsRAP && ch->has_rap && (ch->sample_num != prev_sample+1)) { + GF_ISOSample *found = ch->static_sample; + u32 samp_num = ch->sample_num; + ch->sample = NULL; + e = gf_isom_get_sample_for_movie_time(ch->owner->mov, ch->track, ch->sample_time + 1, &sample_desc_index, GF_ISOM_SEARCH_SYNC_BACKWARD, &ch->static_sample, &ch->sample_num, &ch->sample_data_offset); + + if (e == GF_OK) ch->sample = ch->static_sample; + + /*if no sync point in the past, use the first non-sync for the given time*/ + if (!ch->sample || !ch->sample->data) { + ch->sample = ch->static_sample = found; + ch->sample_time = ch->sample->DTS; + ch->sample_num = samp_num; + } else { + ch->sample = ch->static_sample; + ch->edit_sync_frame = ch->sample_num; + ch->sample->DTS = ch->sample_time; + ch->sample->CTS_Offset = 0; + } + } else { + if (ch->sample) ch->sample_time = ch->sample->DTS; + } + } + } + } else { + Bool do_fetch = GF_TRUE; + ch->sample_num++; + + if (ch->sap_only) { + Bool is_rap = gf_isom_get_sample_sync(ch->owner->mov, ch->track, ch->sample_num); + if (!is_rap) { + GF_ISOSampleRollType roll_type; + gf_isom_get_sample_rap_roll_info(ch->owner->mov, ch->track, ch->sample_num, &is_rap, &roll_type, NULL); + if (roll_type) is_rap = GF_TRUE; + } + + if (!is_rap) { + do_fetch = GF_FALSE; + } else if (ch->sap_only==2) { + ch->sap_only = 0; + } + } + if (do_fetch) { + ch->sample = gf_isom_get_sample_ex(ch->owner->mov, ch->track, ch->sample_num, &sample_desc_index, ch->static_sample, &ch->sample_data_offset); + /*if sync shadow / carousel RAP skip*/ + if (ch->sample && (ch->sample->IsRAP==RAP_REDUNDANT)) { + ch->sample = NULL; + ch->sample_num++; + isor_reader_get_sample(ch); + return; + } + } + } + + //check scalable track change + if (ch->sample && ch->sample->IsRAP && ch->next_track) { + ch->track = ch->next_track; + ch->next_track = 0; + ch->sample = NULL; + isor_reader_get_sample(ch); + return; + } + + if (!ch->sample) { + u32 sample_count = gf_isom_get_sample_count(ch->owner->mov, ch->track); + ch->sample_data_offset = 0; + /*incomplete file - check if we're still downloading or not*/ + if (gf_isom_get_missing_bytes(ch->owner->mov, ch->track)) { + ch->last_state = GF_ISOM_INCOMPLETE_FILE; + if (!ch->owner->input_loaded) { + ch->last_state = GF_OK; + if (!ch->has_edit_list && ch->sample_num) + ch->sample_num--; + } else { + if (ch->sample_num >= gf_isom_get_sample_count(ch->owner->mov, ch->track)) { + ch->last_state = GF_EOS; + } + } + } + else if (!ch->sample_num + || ((ch->speed >= 0) && (ch->sample_num >= sample_count)) + || ((ch->speed < 0) && (ch->sample_time == gf_isom_get_current_tfdt(ch->owner->mov, ch->track) )) + ) { + + if (ch->owner->frag_type==1) { + /*if sample cannot be found and file is fragmented, rewind sample*/ + if (ch->sample_num) ch->sample_num--; + ch->last_state = GF_EOS; + } else if (ch->last_state != GF_EOS) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[IsoMedia] Track #%d end of stream reached\n", ch->track)); + ch->last_state = GF_EOS; + if (ch->sample_num>sample_count) ch->sample_num = sample_count; + } else { + if (ch->sample_num>sample_count) ch->sample_num = sample_count; + } + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] Track #%d fail to fetch sample %d / %d: %s\n", ch->track, ch->sample_num, gf_isom_get_sample_count(ch->owner->mov, ch->track), gf_error_to_string(gf_isom_last_error(ch->owner->mov)) )); + } + return; + } + + if (sample_desc_index != ch->last_sample_desc_index) { + if (!ch->owner->stsd) { + //we used sample entry 1 by default to setup, if no active prev sample (edit list might trigger this) + //and new sample desc is 1, do not reconfigure + if (!ch->last_sample_desc_index && (sample_desc_index==1)) { + + } else { + ch->needs_pid_reconfig = GF_TRUE; + } + } + ch->last_sample_desc_index = sample_desc_index; + } + + ch->last_state = GF_OK; + ch->au_duration = gf_isom_get_sample_duration(ch->owner->mov, ch->track, ch->sample_num); + + ch->sap_3 = GF_FALSE; + ch->sap_4_type = 0; + ch->roll = 0; + if (ch->sample) { + gf_isom_get_sample_rap_roll_info(ch->owner->mov, ch->track, ch->sample_num, &ch->sap_3, &ch->sap_4_type, &ch->roll); + } + + /*still seeking or not ? + 1- when speed is negative, the RAP found is "after" the seek point in playback order since we used backward RAP search: nothing to do + 2- otherwise set DTS+CTS to start value + */ + if ((ch->speed < 0) || (ch->start <= ch->sample->DTS + ch->sample->CTS_Offset)) { + ch->dts = ch->sample->DTS; + ch->cts = ch->sample->DTS + ch->sample->CTS_Offset; + ch->seek_flag = 0; + } else { + ch->cts = ch->start; + ch->seek_flag = 1; + ch->dts = ch->start; + } + ch->set_disc = ch->owner->clock_discontinuity ? 2 : 0; + ch->owner->clock_discontinuity = 0; + + if (ch->end && (ch->end < ch->sample->DTS + ch->sample->CTS_Offset + ch->au_duration)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[IsoMedia] End of Channel "LLD" (CTS "LLD")\n", ch->end, ch->sample->DTS + ch->sample->CTS_Offset)); + ch->last_state = GF_EOS; + ch->playing = GF_FALSE; + } + if (ch->owner->last_sender_ntp && ch->cts==ch->owner->cts_for_last_sender_ntp) { + ch->sender_ntp = ch->owner->last_sender_ntp; + ch->ntp_at_server_ntp = ch->owner->ntp_at_last_sender_ntp; + } else if (ch->owner->last_sender_ntp && ch->dts==ch->owner->cts_for_last_sender_ntp) { + ch->sender_ntp = ch->owner->last_sender_ntp; + ch->ntp_at_server_ntp = ch->owner->ntp_at_last_sender_ntp; + } else { + ch->sender_ntp = ch->ntp_at_server_ntp = 0; + } + + if (!ch->sample_num) return; + + gf_isom_get_sample_flags(ch->owner->mov, ch->track, ch->sample_num, &ch->isLeading, &ch->dependsOn, &ch->dependedOn, &ch->redundant); + + if (ch->is_encrypted) { + /*in case of CENC: we write sample auxiliary information to slh->sai; its size is in saiz*/ + if (gf_isom_is_cenc_media(ch->owner->mov, ch->track, ch->last_sample_desc_index)) { + Bool Is_Encrypted; + u32 out_size; + u8 IV_size; + bin128 KID; + u8 crypt_byte_block, skip_byte_block; + u8 constant_IV_size; + bin128 constant_IV; + + ch->cenc_state_changed = 0; + gf_isom_get_sample_cenc_info(ch->owner->mov, ch->track, ch->sample_num, &Is_Encrypted, &IV_size, &KID, &crypt_byte_block, &skip_byte_block, &constant_IV_size, &constant_IV); + + if (Is_Encrypted != ch->pck_encrypted) { + ch->pck_encrypted = Is_Encrypted; + ch->cenc_state_changed = 1; + } + /*notify change of IV/KID only when packet is encrypted + 1- these info are ignored when packet is not encrypted + 2- this allows us to define the initial CENC state for multi-stsd cases*/ + if (Is_Encrypted) { + if (ch->IV_size != IV_size) { + ch->IV_size = IV_size; + ch->cenc_state_changed = 1; + } + if ((ch->crypt_byte_block != crypt_byte_block) || (ch->skip_byte_block != skip_byte_block)) { + ch->crypt_byte_block = crypt_byte_block; + ch->skip_byte_block = skip_byte_block; + ch->cenc_state_changed = 1; + } + if (!ch->IV_size) { + if (ch->constant_IV_size != constant_IV_size) { + ch->constant_IV_size = constant_IV_size; + ch->cenc_state_changed = 1; + } else if (memcmp(ch->constant_IV, constant_IV, ch->constant_IV_size)) { + ch->cenc_state_changed = 1; + } + memmove(ch->constant_IV, constant_IV, ch->constant_IV_size); + } + if (memcmp(ch->KID, KID, sizeof(bin128))) { + memcpy(ch->KID, KID, sizeof(bin128)); + ch->cenc_state_changed = 1; + } + } + + out_size = ch->sai_alloc_size; + + gf_isom_cenc_get_sample_aux_info_buffer(ch->owner->mov, ch->track, ch->sample_num, ch->last_sample_desc_index, NULL, &ch->sai_buffer, &out_size); + if (out_size > ch->sai_alloc_size) ch->sai_alloc_size = out_size; + ch->sai_buffer_size = out_size; + + } else if (gf_isom_is_media_encrypted(ch->owner->mov, ch->track, ch->last_sample_desc_index)) { + ch->pck_encrypted = GF_TRUE; + } else { + ch->pck_encrypted = GF_FALSE; + } + } + if (ch->sample && ch->sample->nb_pack) + ch->sample_num += ch->sample->nb_pack-1; +} + +void isor_reader_release_sample(ISOMChannel *ch) +{ + if (ch->sample) + ch->au_seq_num++; + ch->sample = NULL; + ch->sai_buffer_size = 0; +} + +static void isor_reset_seq_list(GF_List *list) +{ + while (gf_list_count(list)) { + GF_AVCConfigSlot *sl = gf_list_pop_back(list); + gf_free(sl->data); + gf_free(sl); + } +} + +enum +{ + RESET_STATE_VPS=1, + RESET_STATE_SPS=1<<1, + RESET_STATE_PPS=1<<2, + RESET_STATE_SPS_EXT=1<<3, +}; + +static void isor_replace_nal(GF_AVCConfig *avcc, GF_HEVCConfig *hvcc, u8 *data, u32 size, u8 nal_type, u32 *reset_state) +{ + u32 i, count, state=0; + GF_AVCConfigSlot *sl; + GF_List *list=NULL; + if (avcc) { + if (nal_type==GF_AVC_NALU_PIC_PARAM) { + list = avcc->pictureParameterSets; + state=RESET_STATE_PPS; + } else if (nal_type==GF_AVC_NALU_SEQ_PARAM) { + list = avcc->sequenceParameterSets; + state=RESET_STATE_SPS; + } else if (nal_type==GF_AVC_NALU_SEQ_PARAM_EXT) { + list = avcc->sequenceParameterSetExtensions; + state=RESET_STATE_SPS_EXT; + } else return; + } else if (hvcc) { + GF_HEVCParamArray *hvca=NULL; + count = gf_list_count(hvcc->param_array); + for (i=0; iparam_array, i); + if (hvca->type==nal_type) { + list = hvca->nalus; + break; + } + hvca = NULL; + } + if (!hvca) { + GF_SAFEALLOC(hvca, GF_HEVCParamArray); + if (hvca) { + list = hvca->nalus = gf_list_new(); + hvca->type = nal_type; + gf_list_add(hvcc->param_array, hvca); + } + } + switch (nal_type) { + case GF_HEVC_NALU_VID_PARAM: + state = RESET_STATE_VPS; + break; + case GF_HEVC_NALU_SEQ_PARAM: + state = RESET_STATE_SPS; + break; + case GF_HEVC_NALU_PIC_PARAM: + state = RESET_STATE_PPS; + break; + } + } + + count = gf_list_count(list); + for (i=0; isize==size) && !memcmp(sl->data, data, size)) return; + } + if (! (*reset_state & state)) { + isor_reset_seq_list(list); + *reset_state |= state; + } + GF_SAFEALLOC(sl, GF_AVCConfigSlot); + if (!sl) return; + sl->data = gf_malloc(sizeof(char)*size); + memcpy(sl->data, data, size); + sl->size = size; + gf_list_add(list, sl); +} + +void isor_reader_check_config(ISOMChannel *ch) +{ + u32 nalu_len, reset_state; + if (!ch->check_hevc_ps && !ch->check_avc_ps && !ch->check_mhas_pl) return; + + if (!ch->sample) return; + //we cannot touch the payload if encrypted !! + //TODO, in CENC try to remove non-encrypted NALUs and update saiz accordingly + if (ch->is_encrypted) return; + + if (ch->check_mhas_pl) { + s32 PL = gf_mpegh_get_mhas_pl(ch->sample->data, ch->sample->dataLength); + if (PL>0) { + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_PROFILE_LEVEL, &PROP_UINT((u32) PL)); + ch->check_mhas_pl = GF_FALSE; + } + return; + } + + if (ch->owner->analyze) return; + + nalu_len = ch->hvcc ? ch->hvcc->nal_unit_size : (ch->avcc ? ch->avcc->nal_unit_size : 4); + reset_state = 0; + + if (!ch->nal_bs) ch->nal_bs = gf_bs_new(ch->sample->data, ch->sample->dataLength, GF_BITSTREAM_READ); + else gf_bs_reassign_buffer(ch->nal_bs, ch->sample->data, ch->sample->dataLength); + + while (gf_bs_available(ch->nal_bs)) { + Bool replace_nal = GF_FALSE; + u8 nal_type=0; + u32 pos = (u32) gf_bs_get_position(ch->nal_bs); + u32 size = gf_bs_read_int(ch->nal_bs, nalu_len*8); + if (ch->sample->dataLength < size + pos + nalu_len) break; + u8 hdr = gf_bs_peek_bits(ch->nal_bs, 8, 0); + if (ch->check_avc_ps) { + nal_type = hdr & 0x1F; + switch (nal_type) { + case GF_AVC_NALU_SEQ_PARAM: + case GF_AVC_NALU_SEQ_PARAM_EXT: + case GF_AVC_NALU_PIC_PARAM: + replace_nal = GF_TRUE; + break; + } + } + if (ch->check_hevc_ps) { + nal_type = (hdr & 0x7E) >> 1; + switch (nal_type) { + case GF_HEVC_NALU_VID_PARAM: + case GF_HEVC_NALU_SEQ_PARAM: + case GF_HEVC_NALU_PIC_PARAM: + replace_nal = GF_TRUE; + break; + } + } + gf_bs_skip_bytes(ch->nal_bs, size); + + if (replace_nal) { + u32 move_size = ch->sample->dataLength - size - pos - nalu_len; + isor_replace_nal(ch->avcc, ch->hvcc, ch->sample->data + pos + nalu_len, size, nal_type, &reset_state); + if (move_size) + memmove(ch->sample->data + pos, ch->sample->data + pos + size + nalu_len, ch->sample->dataLength - size - pos - nalu_len); + + ch->sample->dataLength -= size + nalu_len; + gf_bs_reassign_buffer(ch->nal_bs, ch->sample->data, ch->sample->dataLength); + gf_bs_seek(ch->nal_bs, pos); + } + } + + if (reset_state) { + u8 *dsi=NULL; + u32 dsi_size=0; + if (ch->check_avc_ps) { + gf_odf_avc_cfg_write(ch->avcc, &dsi, &dsi_size); + } + else if (ch->check_hevc_ps) { + gf_odf_hevc_cfg_write(ch->hvcc, &dsi, &dsi_size); + } + if (dsi && dsi_size) { + u32 dsi_crc = gf_crc_32(dsi, dsi_size); + if (ch->dsi_crc == dsi_crc) { + gf_free(dsi); + } else { + ch->dsi_crc = dsi_crc; + gf_filter_pid_set_property(ch->pid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_size) ); + } + } + } +} + +#endif /*GPAC_DISABLE_ISOM*/ diff --git a/src/filters/jsfilter.c b/src/filters/jsfilter.c new file mode 100644 index 0000000..351f4d5 --- /dev/null +++ b/src/filters/jsfilter.c @@ -0,0 +1,4397 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2019 + * All rights reserved + * + * This file is part of GPAC / QuickJS bindings for GF_Filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +/* + ANY CHANGE TO THE API MUST BE REFLECTED IN THE DOCUMENTATION IN gpac/share/doc/idl/jsf.idl + (no way to define inline JS doc with doxygen) +*/ + +#include +#include +#include +#include + +#ifdef GPAC_HAS_QJS + +#include +#include "../scenegraph/qjs_common.h" + + +//to load session API +#include "../filter_core/filter_session.h" + +/* + Currently unmapped functions + +//likely not needed +u32 gf_filter_count_source_by_protocol (GF_Filter *filter, const char *protocol_scheme, Bool expand_proto, GF_FilterPid *(*enum_pids)(void *udta, u32 *idx), void *udta) +GF_FilterPacket * gf_filter_pck_new_frame_interface (GF_FilterPid *PID, GF_FilterFrameInterface *frame_ifce, gf_fsess_packet_destructor destruct) +GF_FilterFrameInterface * gf_filter_pck_get_frame_interface (GF_FilterPacket *pck) + +//todo +GF_Err gf_filter_add_event_listener (GF_Filter *filter, GF_FSEventListener *el) +GF_Err gf_filter_remove_event_listener (GF_Filter *filter, GF_FSEventListener *el) +Bool gf_filter_forward_gf_event (GF_Filter *filter, GF_Event *evt, Bool consumed, Bool skip_user) +Bool gf_filter_send_gf_event (GF_Filter *filter, GF_Event *evt) +Bool gf_filter_ui_event (GF_Filter *filter, GF_Event *uievt) +void gf_filter_register_opengl_provider (GF_Filter *filter, Bool do_register) +GF_Err gf_filter_request_opengl (GF_Filter *filter) + +//either not needed or require a dedicated module +GF_DownloadManager *gf_filter_get_download_manager (GF_Filter *filter) + + +*/ + +enum +{ + JSF_EVT_INITIALIZE=0, + JSF_EVT_FINALIZE, + JSF_EVT_CONFIGURE_PID, + JSF_EVT_PROCESS, + JSF_EVT_PROCESS_EVENT, + JSF_EVT_UPDATE_ARG, + JSF_EVT_PROBE_URL, + JSF_EVT_PROBE_DATA, + JSF_EVT_RECONFIGURE_OUTPUT, + JSF_EVT_REMOVE_PID, + + JSF_EVT_LAST_DEFINED, + + JSF_FILTER_MAX_PIDS, + JSF_FILTER_BLOCK_ENABLED, + JSF_FILTER_OUTPUT_BUFFER, + JSF_FILTER_OUTPUT_PLAYOUT, + JSF_FILTER_SEP_ARGS, + JSF_FILTER_SEP_NAME, + JSF_FILTER_SEP_LIST, + JSF_FILTER_DST_ARGS, + JSF_FILTER_DST_NAME, + JSF_FILTER_SINKS_DONE, + JSF_FILTER_REPORTING_ENABLED, + + JSF_FILTER_CAPS_MAX_WIDTH, + JSF_FILTER_CAPS_MAX_HEIGHT, + JSF_FILTER_CAPS_MAX_DISPLAY_DEPTH, + JSF_FILTER_CAPS_MAX_FPS, + JSF_FILTER_CAPS_MAX_VIEWS, + JSF_FILTER_CAPS_MAX_CHANNELS, + JSF_FILTER_CAPS_MAX_SAMPLERATE, + JSF_FILTER_CAPS_MAX_AUDIO_DEPTH, + JSF_FILTER_NB_EVTS_QUEUED, + JSF_FILTER_CLOCK_HINT_TIME, + JSF_FILTER_CLOCK_HINT_MEDIATIME, + JSF_FILTER_CONNECTIONS_PENDING, + JSF_FILTER_INAME +}; + +enum +{ + JSF_SETUP_ERROR=0, + JSF_NOTIF_ERROR, + JSF_NOTIF_ERROR_AND_DISCONNECT +}; + + +typedef struct +{ + //options + const char *js; + + GF_Filter *filter; + + JSContext *ctx; + + Bool initialized; + JSValue funcs[JSF_EVT_LAST_DEFINED]; + JSValue filter_obj; + + GF_FilterArgs *args; + u32 nb_args; + Bool has_wilcard_arg; + + GF_FilterCapability *caps; + u32 nb_caps; + + GF_List *pids; + char *log_name; + + GF_List *pck_res; + + Bool unload_session_api; + Bool disable_filter; +} GF_JSFilterCtx; + +enum +{ + JSF_PID_NAME=0, + JSF_PID_EOS, + JSF_PID_EOS_SEEN, + JSF_PID_WOULD_BLOCK, + JSF_PID_FILTER_NAME, + JSF_PID_FILTER_SRC, + JSF_PID_FILTER_ARGS, + JSF_PID_FILTER_SRC_ARGS, + JSF_PID_MAX_BUFFER, + JSF_PID_LOOSE_CONNECT, + JSF_PID_FRAMING_MODE, + JSF_PID_BUFFER, + JSF_PID_IS_FULL, + JSF_PID_FIRST_EMPTY, + JSF_PID_FIRST_CTS, + JSF_PID_NB_PACKETS, + JSF_PID_TIMESCALE, + JSF_PID_CLOCK_MODE, + JSF_PID_DISCARD, + JSF_PID_SRC_URL, + JSF_PID_DST_URL, + JSF_PID_REQUIRE_SOURCEID, + JSF_PID_RECOMPUTE_DTS, + JSF_PID_MIN_PCK_DUR, + JSF_PID_IS_PLAYING, +}; +typedef struct +{ + GF_JSFilterCtx *jsf; + GF_FilterPid *pid; + JSValue jsobj; + struct _js_pck_ctx *pck_head; + GF_List *shared_pck; +} GF_JSPidCtx; + +enum +{ + GF_JS_PCK_IS_REF = 1, + GF_JS_PCK_IS_SHARED = 1<<1, + GF_JS_PCK_IS_OUTPUT = 1<<2 +}; + +typedef struct _js_pck_ctx +{ + GF_JSPidCtx *jspid; + GF_FilterPacket *pck; + JSValue jsobj; + //shared packet, this is a string or an array buffer + JSValue ref_val; + //shared packet callback + JSValue cbck_val; + //array buffer + JSValue data_ab; + u32 flags; +} GF_JSPckCtx; + +typedef enum +{ + JSF_FINST_SOURCE=0, + JSF_FINST_DEST, + JSF_FINST_FILTER, +} GF_JSFilterMode; +typedef struct +{ + GF_JSFilterCtx *jsf; + GF_Filter *filter; + JSValue filter_obj; + GF_JSFilterMode fmode; + JSValue setup_failure_fun; + +} GF_JSFilterInstanceCtx; + + +static JSClassID jsf_filter_class_id; + +static void jsf_filter_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) +{ + GF_JSFilterCtx *jsf = JS_GetOpaque(val, jsf_filter_class_id); + if (jsf) { + u32 i; + for (i=0; ifuncs[i], mark_func); + } + } +} +static JSClassDef jsf_filter_class = { + "JSFilter", + .gc_mark = jsf_filter_mark +}; + +static JSClassID jsf_filter_inst_class_id; + +static void jsf_filter_inst_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) +{ + GF_JSFilterInstanceCtx *f_inst = JS_GetOpaque(val, jsf_filter_inst_class_id); + if (f_inst) { + JS_MarkValue(rt, f_inst->setup_failure_fun, mark_func); + } +} +static void jsf_filter_inst_finalizer(JSRuntime *rt, JSValue val) { + GF_JSFilterInstanceCtx *f_inst = JS_GetOpaque(val, jsf_filter_inst_class_id); + if (!f_inst) return; + JS_FreeValueRT(rt, f_inst->setup_failure_fun); + gf_free(f_inst); +} +static JSClassDef jsf_filter_inst_class = { + "FilterInstance", + .finalizer = jsf_filter_inst_finalizer, + .gc_mark = jsf_filter_inst_mark +}; + +static JSClassID jsf_pid_class_id; +static JSClassDef jsf_pid_class = { + "FilterPid", +}; + +static JSClassID jsf_event_class_id; +static void jsf_evt_finalizer(JSRuntime *rt, JSValue val) +{ + GF_FilterEvent *evt = JS_GetOpaque(val, jsf_event_class_id); + if (!evt) return; + if (evt->base.type==GF_FEVT_USER) { + if (evt->user_event.event.type==GF_EVENT_SET_CAPTION) { + if (evt->user_event.event.caption.caption) + gf_free((char *) evt->user_event.event.caption.caption); + } + } + gf_free(evt); +} +static JSClassDef jsf_event_class = { + "FilterEvent", + .finalizer = jsf_evt_finalizer +}; + +static JSClassID jsf_pck_class_id; + +static void jsf_pck_detach_ab(JSContext *ctx, GF_JSPckCtx *pckctx) +{ + if (!JS_IsUndefined(pckctx->data_ab)) { + JS_DetachArrayBuffer(ctx, pckctx->data_ab); + JS_FreeValue(ctx, pckctx->data_ab); + pckctx->data_ab = JS_UNDEFINED; + } +} + +static void jsf_pck_finalizer(JSRuntime *rt, JSValue val) +{ + GF_JSPckCtx *pckctx = JS_GetOpaque(val, jsf_pck_class_id); + if (!pckctx) return; + pckctx->jspid->pck_head = NULL; + + /*we only keep a ref for input packet(s)*/ + if (pckctx->pck && !(pckctx->flags & GF_JS_PCK_IS_OUTPUT)) + JS_FreeValueRT(rt, pckctx->jsobj); + + if (!JS_IsUndefined(pckctx->data_ab)) { + JS_FreeValueRT(rt, pckctx->data_ab); + pckctx->data_ab = JS_UNDEFINED; + } + + if (JS_IsUndefined(pckctx->ref_val) && pckctx->jspid && pckctx->jspid->jsf) { + gf_list_add(pckctx->jspid->jsf->pck_res, pckctx); + memset(pckctx, 0, sizeof(GF_JSPckCtx)); + } +} + +static void jsf_filter_pck_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) +{ + GF_JSPckCtx *pckctx = JS_GetOpaque(val, jsf_pck_class_id); + if (!pckctx) return; + + if (!(pckctx->flags & GF_JS_PCK_IS_OUTPUT)) + JS_MarkValue(rt, pckctx->jsobj, mark_func); + + if (!JS_IsUndefined(pckctx->ref_val)) { + JS_MarkValue(rt, pckctx->ref_val, mark_func); + } + + if (!JS_IsUndefined(pckctx->data_ab)) { + JS_MarkValue(rt, pckctx->data_ab, mark_func); + } +} + +static JSClassDef jsf_pck_class = { + "FilterPacket", + .finalizer = jsf_pck_finalizer, + .gc_mark = jsf_filter_pck_mark +}; + +GF_DownloadManager *jsf_get_download_manager(JSContext *c) +{ + GF_JSFilterCtx *jsf; + JSValue global = JS_GetGlobalObject(c); + + JSValue filter_obj = JS_GetPropertyStr(c, global, "filter"); + JS_FreeValue(c, global); + if (JS_IsNull(filter_obj) || JS_IsException(filter_obj)) return NULL; + jsf = JS_GetOpaque(filter_obj, jsf_filter_class_id); + JS_FreeValue(c, filter_obj); + if (!jsf) return NULL; + return gf_filter_get_download_manager(jsf->filter); +} + +struct _gf_ft_mgr *jsf_get_font_manager(JSContext *c) +{ + GF_JSFilterCtx *jsf; + JSValue global = JS_GetGlobalObject(c); + + JSValue filter_obj = JS_GetPropertyStr(c, global, "filter"); + JS_FreeValue(c, global); + if (JS_IsNull(filter_obj) || JS_IsException(filter_obj)) return NULL; + jsf = JS_GetOpaque(filter_obj, jsf_filter_class_id); + JS_FreeValue(c, filter_obj); + if (!jsf) return NULL; + return gf_filter_get_font_manager(jsf->filter); +} + +GF_Err jsf_request_opengl(JSContext *c) +{ + GF_JSFilterCtx *jsf; + JSValue global = JS_GetGlobalObject(c); + + JSValue filter_obj = JS_GetPropertyStr(c, global, "filter"); + JS_FreeValue(c, global); + if (JS_IsNull(filter_obj) || JS_IsException(filter_obj)) return GF_BAD_PARAM; + jsf = JS_GetOpaque(filter_obj, jsf_filter_class_id); + JS_FreeValue(c, filter_obj); + + return gf_filter_request_opengl(jsf->filter); +} +GF_Err jsf_set_gl_active(JSContext *c) +{ + GF_JSFilterCtx *jsf; + JSValue global = JS_GetGlobalObject(c); + + JSValue filter_obj = JS_GetPropertyStr(c, global, "filter"); + JS_FreeValue(c, global); + if (JS_IsNull(filter_obj) || JS_IsException(filter_obj)) return GF_BAD_PARAM; + jsf = JS_GetOpaque(filter_obj, jsf_filter_class_id); + JS_FreeValue(c, filter_obj); + + return gf_filter_set_active_opengl_context(jsf->filter); +} + +Bool jsf_is_packet(JSContext *c, JSValue obj) +{ + GF_JSPckCtx *pckc = JS_GetOpaque(obj, jsf_pck_class_id); + if (pckc) return GF_TRUE; + return GF_FALSE; +} +const GF_FilterPacket *jsf_get_packet(JSContext *c, JSValue obj) +{ + GF_JSPckCtx *pckc = JS_GetOpaque(obj, jsf_pck_class_id); + if (pckc) return pckc->pck; + return NULL; +} + +GF_Err jsf_get_filter_packet_planes(JSContext *c, JSValue obj, u32 *width, u32 *height, u32 *pf, u32 *stride, u32 *stride_uv, const u8 **data, const u8 **p_u, const u8 **p_v, const u8 **p_a) +{ + u32 cid; + GF_FilterFrameInterface *frame_ifce; + const GF_PropertyValue *p; + GF_JSPckCtx *pckc = JS_GetOpaque(obj, jsf_pck_class_id); + if (!pckc) return GF_BAD_PARAM; + +#define GETPROP(_code, _v, opt)\ + p = gf_filter_pid_get_property(pckc->jspid->pid, _code);\ + if (!opt && !p) return GF_BAD_PARAM;\ + _v = p ? p->value.uint : 0;\ + + GETPROP(GF_PROP_PID_CODECID, cid, 0) + if (cid != GF_CODECID_RAW) return GF_BAD_PARAM; + GETPROP(GF_PROP_PID_WIDTH, *width, 0) + GETPROP(GF_PROP_PID_HEIGHT, *height, 0) + GETPROP(GF_PROP_PID_PIXFMT, *pf, 0) + GETPROP(GF_PROP_PID_STRIDE, *stride, 1) + GETPROP(GF_PROP_PID_STRIDE_UV, *stride_uv, 1) +#undef GETPROP + + if (!data || !p_u || !p_v || !p_a) return GF_OK; + + frame_ifce = gf_filter_pck_get_frame_interface(pckc->pck); + if (!frame_ifce) { + u32 size=0; + *data = (u8 *) gf_filter_pck_get_data(pckc->pck, &size); + } else { + u32 st_o; + frame_ifce->get_plane(frame_ifce, 0, data, stride); + frame_ifce->get_plane(frame_ifce, 1, p_u, stride_uv); + //todo we need to cleanup alpha frame fetch, how do we differentiate between NV12+alpha (3 planes) and YUV420 ? + frame_ifce->get_plane(frame_ifce, 2, p_v, &st_o); + frame_ifce->get_plane(frame_ifce, 3, p_a, &st_o); + } + return GF_OK; +} + +const char *jsf_get_script_filename(JSContext *c) +{ + GF_JSFilterCtx *jsf; + JSValue global = JS_GetGlobalObject(c); + JSValue filter_obj = JS_GetPropertyStr(c, global, "filter"); + JS_FreeValue(c, global); + if (JS_IsNull(filter_obj) || JS_IsException(filter_obj)) return NULL; + jsf = JS_GetOpaque(filter_obj, jsf_filter_class_id); + JS_FreeValue(c, filter_obj); + if (!jsf) return NULL; + return jsf->js; +} + +JSValue jsf_NewProp(JSContext *ctx, const GF_PropertyValue *new_val) +{ + JSValue res; + u32 i; + if (!new_val) return JS_NULL; + + switch (new_val->type) { + case GF_PROP_BOOL: + return JS_NewBool(ctx, new_val->value.boolean); + case GF_PROP_UINT: + return JS_NewInt32(ctx, new_val->value.uint); + case GF_PROP_SINT: + return JS_NewInt32(ctx, new_val->value.sint); + case GF_PROP_LUINT: + return JS_NewInt64(ctx, new_val->value.longuint); + case GF_PROP_LSINT: + return JS_NewInt64(ctx, new_val->value.longsint); + case GF_PROP_FLOAT: + return JS_NewFloat64(ctx, FIX2FLT(new_val->value.fnumber)); + case GF_PROP_DOUBLE: + return JS_NewFloat64(ctx, new_val->value.number); + case GF_PROP_STRING: + case GF_PROP_STRING_NO_COPY: + case GF_PROP_NAME: + if (!new_val->value.string) return JS_NULL; + return JS_NewString(ctx, new_val->value.string); + case GF_PROP_VEC2: + res = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, res, "x", JS_NewFloat64(ctx, new_val->value.vec2.x)); + JS_SetPropertyStr(ctx, res, "y", JS_NewFloat64(ctx, new_val->value.vec2.y)); + return res; + case GF_PROP_VEC3: + res = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, res, "x", JS_NewFloat64(ctx, new_val->value.vec3.x)); + JS_SetPropertyStr(ctx, res, "y", JS_NewFloat64(ctx, new_val->value.vec3.y)); + JS_SetPropertyStr(ctx, res, "z", JS_NewFloat64(ctx, new_val->value.vec3.z)); + return res; + case GF_PROP_VEC4: + res = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, res, "x", JS_NewFloat64(ctx, new_val->value.vec4.x)); + JS_SetPropertyStr(ctx, res, "y", JS_NewFloat64(ctx, new_val->value.vec4.y)); + JS_SetPropertyStr(ctx, res, "z", JS_NewFloat64(ctx, new_val->value.vec4.z)); + JS_SetPropertyStr(ctx, res, "w", JS_NewFloat64(ctx, new_val->value.vec4.w)); + return res; + case GF_PROP_VEC2I: + res = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, res, "x", JS_NewInt32(ctx, new_val->value.vec2i.x)); + JS_SetPropertyStr(ctx, res, "y", JS_NewInt32(ctx, new_val->value.vec2i.y)); + return res; + case GF_PROP_VEC3I: + res = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, res, "x", JS_NewInt32(ctx, new_val->value.vec3i.x)); + JS_SetPropertyStr(ctx, res, "y", JS_NewInt32(ctx, new_val->value.vec3i.y)); + JS_SetPropertyStr(ctx, res, "z", JS_NewInt32(ctx, new_val->value.vec3i.z)); + return res; + case GF_PROP_VEC4I: + res = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, res, "x", JS_NewInt32(ctx, new_val->value.vec4i.x)); + JS_SetPropertyStr(ctx, res, "y", JS_NewInt32(ctx, new_val->value.vec4i.y)); + JS_SetPropertyStr(ctx, res, "z", JS_NewInt32(ctx, new_val->value.vec4i.z)); + JS_SetPropertyStr(ctx, res, "w", JS_NewInt32(ctx, new_val->value.vec4i.w)); + return res; + case GF_PROP_FRACTION: + res = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, res, "n", JS_NewInt32(ctx, new_val->value.frac.num)); + JS_SetPropertyStr(ctx, res, "d", JS_NewInt32(ctx, new_val->value.frac.den)); + return res; + case GF_PROP_FRACTION64: + res = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, res, "n", JS_NewInt64(ctx, new_val->value.lfrac.num)); + JS_SetPropertyStr(ctx, res, "d", JS_NewInt64(ctx, new_val->value.lfrac.den)); + return res; + case GF_PROP_PIXFMT: + return JS_NewString(ctx, gf_pixel_fmt_name(new_val->value.uint)); + case GF_PROP_PCMFMT: + return JS_NewString(ctx, gf_audio_fmt_name(new_val->value.uint)); + case GF_PROP_UINT_LIST: + res = JS_NewArray(ctx); + for (i=0; ivalue.uint_list.nb_items; i++) { + JS_SetPropertyUint32(ctx, res, i, JS_NewInt32(ctx, new_val->value.uint_list.vals[i]) ); + } + return res; + case GF_PROP_STRING_LIST: + res = JS_NewArray(ctx); + for (i=0; ivalue.string_list); i++) { + JS_SetPropertyUint32(ctx, res, i, JS_NewString(ctx, gf_list_get(new_val->value.string_list, i) ) ); + } + return res; + case GF_PROP_DATA: + return JS_NewArrayBufferCopy(ctx, new_val->value.data.ptr, new_val->value.data.size); + + default: + return JS_NULL; + } +} + +JSValue jsf_NewPropTranslate(JSContext *ctx, const GF_PropertyValue *prop, u32 p4cc) +{ + const char *cid; + JSValue res; + switch (p4cc) { + case GF_PROP_PID_CODECID: + cid = gf_codecid_file_ext(prop->value.uint); + if (!cid) res = JS_EXCEPTION; + else { + char *sep = strchr(cid, '|'); + if (sep) + res = JS_NewStringLen(ctx, cid, sep-cid); + else + res = JS_NewString(ctx, cid); + } + break; + case GF_PROP_PID_STREAM_TYPE: + res = JS_NewString(ctx, gf_stream_type_name(prop->value.uint)); + break; + case GF_PROP_PID_AUDIO_FORMAT: + res = JS_NewString(ctx, gf_audio_fmt_name(prop->value.uint)); + break; + case GF_PROP_PID_PIXFMT: + res = JS_NewString(ctx, gf_pixel_fmt_name(prop->value.uint)); + break; + default: + res = jsf_NewProp(ctx, prop); + break; + } + return res; +} + +GF_Err jsf_ToProp(GF_Filter *filter, JSContext *ctx, JSValue value, u32 p4cc, GF_PropertyValue *prop) +{ + u32 type; + memset(prop, 0, sizeof(GF_PropertyValue)); + + type = GF_PROP_STRING; + if (p4cc) + type = gf_props_4cc_get_type(p4cc); + + if (JS_IsString(value)) { + const char *val_str = JS_ToCString(ctx, value); + if (p4cc==GF_PROP_PID_STREAM_TYPE) { + prop->type = GF_PROP_UINT; + prop->value.uint = gf_stream_type_by_name(val_str); + } else if (p4cc==GF_PROP_PID_CODECID) { + prop->type = GF_PROP_UINT; + prop->value.uint = gf_codec_parse(val_str); + } else { + *prop = gf_props_parse_value(type, NULL, val_str, NULL, gf_filter_get_sep(filter, GF_FS_SEP_LIST)); + } + JS_FreeCString(ctx, val_str); + if (prop->type==GF_PROP_FORBIDEN) return GF_BAD_PARAM; + return GF_OK; + } + if (JS_IsBool(value)) { + prop->type = GF_PROP_BOOL; + prop->value.boolean = JS_ToBool(ctx, value); + } + else if (JS_IsInteger(value)) { + if (!JS_ToInt32(ctx, &prop->value.sint, value)) { + prop->type = GF_PROP_SINT; + } else if (!JS_ToInt64(ctx, &prop->value.longsint, value)) { + prop->type = GF_PROP_LSINT; + } + } + else if (JS_IsNumber(value)) { + JS_ToFloat64(ctx, &prop->value.number, value); + prop->type = GF_PROP_DOUBLE; + } + else if (JS_IsArray(ctx, value)) { + u64 len = 0; + u32 i; + u32 atype = 0; + JSValue js_length = JS_GetPropertyStr(ctx, value, "length"); + if (JS_ToIndex(ctx, &len, js_length)) { + JS_FreeValue(ctx, js_length); + return GF_BAD_PARAM; + } + JS_FreeValue(ctx, js_length); + for (i=0; ivalue.string_list = gf_list_new(); + } + } else { + prop->value.uint_list.nb_items = (u32) len; + prop->value.uint_list.vals = gf_malloc((u32) (sizeof(s32)*len)); + } + if (atype==1) { + const char *str = JS_ToCString(ctx, v); + gf_list_add(prop->value.string_list, gf_strdup(str)); + JS_FreeCString(ctx, str); + } else { + JS_ToInt32(ctx, &prop->value.uint_list.vals[i], v); + } + JS_FreeValue(ctx, v); + } + } + else if (JS_IsObject(value)) { + u32 is_vec4 = 0; + u32 is_vec3 = 0; + u32 is_vec2 = 0; + u32 is_frac = 0; + Bool is_vec = GF_FALSE; + GF_PropVec4 val_d; + GF_PropVec4i val_i; + GF_Fraction frac; + GF_Fraction64 frac_l; + + JSValue res = JS_GetPropertyStr(ctx, value, "w"); + if (!JS_IsUndefined(res)) { + is_vec4 = 1; + if (JS_ToFloat64(ctx, &val_d.w, res)) { + JS_ToInt32(ctx, &val_i.w, res); + is_vec4 = 2; + } + } + JS_FreeValue(ctx, res); + + res = JS_GetPropertyStr(ctx, value, "z"); + if (!JS_IsUndefined(res)) { + is_vec3 = 1; + if (JS_ToFloat64(ctx, &val_d.z, res)) { + JS_ToInt32(ctx, &val_i.z, res); + is_vec3 = 2; + } + } + JS_FreeValue(ctx, res); + + res = JS_GetPropertyStr(ctx, value, "y"); + if (!JS_IsUndefined(res)) { + is_vec2 = 1; + if (JS_ToFloat64(ctx, &val_d.y, res)) { + JS_ToInt32(ctx, &val_i.y, res); + is_vec2 = 2; + } + } + JS_FreeValue(ctx, res); + + res = JS_GetPropertyStr(ctx, value, "x"); + if (!JS_IsUndefined(res)) { + is_vec = GF_TRUE; + if (JS_ToFloat64(ctx, &val_d.x, res)) { + JS_ToInt32(ctx, &val_i.x, res); + } + } + JS_FreeValue(ctx, res); + + res = JS_GetPropertyStr(ctx, value, "d"); + if (!JS_IsUndefined(res)) { + is_frac = 2; + if (JS_ToInt32(ctx, &frac.den, res)) { + JS_ToInt64(ctx, &frac_l.den, res); + is_frac = 1; + } + } + JS_FreeValue(ctx, res); + if (is_frac) { + res = JS_GetPropertyStr(ctx, value, "n"); + if (!JS_IsUndefined(res)) { + is_frac = 2; + if (JS_ToInt32(ctx, &frac.num, res)) { + JS_ToInt64(ctx, &frac_l.num, res); + is_frac = 1; + } + } else { + is_frac = 0; + } + JS_FreeValue(ctx, res); + } + + if (is_vec) { + if (is_vec4==2) { + prop->type = GF_PROP_VEC4I; + prop->value.vec4i = val_i; + } else if (is_vec4==1) { + prop->type = GF_PROP_VEC4; + prop->value.vec4 = val_d; + } else if (is_vec3==2) { + prop->type = GF_PROP_VEC3I; + prop->value.vec3i.x = val_i.x; + prop->value.vec3i.y = val_i.y; + prop->value.vec3i.z = val_i.z; + } else if (is_vec3==1) { + prop->type = GF_PROP_VEC3; + prop->value.vec3.x = val_d.x; + prop->value.vec3.y = val_d.y; + prop->value.vec3.z = val_d.z; + } else if (is_vec2==2) { + prop->type = GF_PROP_VEC2I; + prop->value.vec3i.x = val_i.x; + prop->value.vec3i.y = val_i.y; + } else if (is_vec2==1) { + prop->type = GF_PROP_VEC2; + prop->value.vec3.x = val_d.x; + prop->value.vec3.y = val_d.y; + } + } else if (is_frac) { + if (is_frac==2) { + prop->type = GF_PROP_FRACTION; + prop->value.frac = frac; + } else { + prop->type = GF_PROP_FRACTION64; + prop->value.lfrac = frac_l; + } + } + //try array buffer + else { + size_t ab_size; + u8 *data = JS_GetArrayBuffer(ctx, &ab_size, value); + if (!data) + return GF_BAD_PARAM; + prop->value.data.ptr = gf_malloc(ab_size); + memcpy(prop->value.data.ptr, data, ab_size); + prop->value.data.size = (u32) ab_size; + prop->type = GF_PROP_DATA; + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[JSF] Unmapped JS object type, cannot convert back to property\n")); + return GF_NOT_SUPPORTED; + } + + if (prop->type==GF_PROP_STRING) { + if ((type==GF_PROP_DATA) || (type==GF_PROP_CONST_DATA)) { + char *str = prop->value.string; + prop->value.data.ptr = str; + prop->value.data.size = (u32) strlen(str); + prop->type = GF_PROP_DATA; + } + } + return GF_OK; +} + + +static JSValue jsf_filter_prop_set(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic) +{ + u32 ival; + GF_FilterSessionCaps caps; + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf) + return JS_EXCEPTION; + + if (magic < JSF_EVT_LAST_DEFINED) { + if (JS_IsFunction(ctx, value) || JS_IsUndefined(value) || JS_IsNull(value)) { + JS_FreeValue(ctx, jsf->funcs[magic]); + jsf->funcs[magic] = JS_DupValue(ctx, value); + } + return JS_UNDEFINED; + } + switch (magic) { + case JSF_FILTER_MAX_PIDS: + if (JS_ToInt32(ctx, &ival, value)) + return JS_EXCEPTION; + gf_filter_set_max_extra_input_pids(jsf->filter, ival); + break; + + case JSF_FILTER_CAPS_MAX_WIDTH: + gf_filter_get_session_caps(jsf->filter, &caps); + if (JS_ToInt32(ctx, &caps.max_screen_width, value)) return JS_EXCEPTION; + gf_filter_set_session_caps(jsf->filter, &caps); + break; + case JSF_FILTER_CAPS_MAX_HEIGHT: + gf_filter_get_session_caps(jsf->filter, &caps); + if (JS_ToInt32(ctx, &caps.max_screen_height, value)) return JS_EXCEPTION; + gf_filter_set_session_caps(jsf->filter, &caps); + break; + case JSF_FILTER_CAPS_MAX_DISPLAY_DEPTH: + gf_filter_get_session_caps(jsf->filter, &caps); + if (JS_ToInt32(ctx, &caps.max_screen_bpp, value)) return JS_EXCEPTION; + gf_filter_set_session_caps(jsf->filter, &caps); + break; + case JSF_FILTER_CAPS_MAX_FPS: + gf_filter_get_session_caps(jsf->filter, &caps); + if (JS_ToInt32(ctx, &caps.max_screen_fps, value)) return JS_EXCEPTION; + gf_filter_set_session_caps(jsf->filter, &caps); + break; + case JSF_FILTER_CAPS_MAX_VIEWS: + gf_filter_get_session_caps(jsf->filter, &caps); + if (JS_ToInt32(ctx, &caps.max_screen_nb_views, value)) return JS_EXCEPTION; + gf_filter_set_session_caps(jsf->filter, &caps); + break; + case JSF_FILTER_CAPS_MAX_CHANNELS: + gf_filter_get_session_caps(jsf->filter, &caps); + if (JS_ToInt32(ctx, &caps.max_audio_channels, value)) return JS_EXCEPTION; + gf_filter_set_session_caps(jsf->filter, &caps); + break; + case JSF_FILTER_CAPS_MAX_SAMPLERATE: + gf_filter_get_session_caps(jsf->filter, &caps); + if (JS_ToInt32(ctx, &caps.max_audio_sample_rate, value)) return JS_EXCEPTION; + gf_filter_set_session_caps(jsf->filter, &caps); + break; + case JSF_FILTER_CAPS_MAX_AUDIO_DEPTH: + gf_filter_get_session_caps(jsf->filter, &caps); + if (JS_ToInt32(ctx, &caps.max_audio_bit_depth, value)) return JS_EXCEPTION; + gf_filter_set_session_caps(jsf->filter, &caps); + break; + + case JSF_FILTER_INAME: + { + const char *val = JS_ToCString(ctx, value); + if (jsf->filter->iname) gf_free(jsf->filter->iname); + if (val) + jsf->filter->iname = gf_strdup(val); + else + jsf->filter->iname = NULL; + JS_FreeCString(ctx, val); + } + break; + } + return JS_UNDEFINED; +} + +static JSValue jsf_filter_prop_get(JSContext *ctx, JSValueConst this_val, int magic) +{ + u32 ival; + s64 lival; + Double dval; + char *str; + char szSep[2]; + GF_FilterSessionCaps caps; + JSValue res; + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf) + return JS_EXCEPTION; + + if (magicctx, jsf->funcs[magic]); + + switch (magic) { + case JSF_FILTER_MAX_PIDS: + return JS_NewInt32(ctx, gf_filter_get_max_extra_input_pids(jsf->filter)); + case JSF_FILTER_BLOCK_ENABLED: + return JS_NewBool(jsf->ctx, gf_filter_block_enabled(jsf->filter)); + case JSF_FILTER_OUTPUT_BUFFER: + gf_filter_get_output_buffer_max(jsf->filter, &ival, NULL); + return JS_NewInt32(jsf->ctx, ival); + case JSF_FILTER_OUTPUT_PLAYOUT: + gf_filter_get_output_buffer_max(jsf->filter, NULL, &ival); + return JS_NewInt32(jsf->ctx, ival); + + case JSF_FILTER_SEP_ARGS: + szSep[1]=0; + szSep[0] = gf_filter_get_sep(jsf->filter, GF_FS_SEP_ARGS); + return JS_NewString(jsf->ctx, szSep); + case JSF_FILTER_SEP_NAME: + szSep[1]=0; + szSep[0] = gf_filter_get_sep(jsf->filter, GF_FS_SEP_NAME); + return JS_NewString(jsf->ctx, szSep); + case JSF_FILTER_SEP_LIST: + szSep[1]=0; + szSep[0] = gf_filter_get_sep(jsf->filter, GF_FS_SEP_LIST); + return JS_NewString(jsf->ctx, szSep); + + case JSF_FILTER_DST_ARGS: + str = (char *) gf_filter_get_dst_args(jsf->filter); + return str ? JS_NewString(jsf->ctx, str) : JS_NULL; + case JSF_FILTER_DST_NAME: + str = gf_filter_get_dst_name(jsf->filter); + res = str ? JS_NewString(jsf->ctx, str) : JS_NULL; + if (str) gf_free(str); + return res; + case JSF_FILTER_SINKS_DONE: + return JS_NewBool(jsf->ctx, gf_filter_all_sinks_done(jsf->filter) ); + case JSF_FILTER_REPORTING_ENABLED: + return JS_NewBool(jsf->ctx, gf_filter_reporting_enabled(jsf->filter) ); + + case JSF_FILTER_CAPS_MAX_WIDTH: + gf_filter_get_session_caps(jsf->filter, &caps); + return JS_NewInt32(ctx, caps.max_screen_width); + case JSF_FILTER_CAPS_MAX_HEIGHT: + gf_filter_get_session_caps(jsf->filter, &caps); + return JS_NewInt32(ctx, caps.max_screen_height); + case JSF_FILTER_CAPS_MAX_DISPLAY_DEPTH: + gf_filter_get_session_caps(jsf->filter, &caps); + return JS_NewInt32(ctx, caps.max_screen_bpp); + case JSF_FILTER_CAPS_MAX_FPS: + gf_filter_get_session_caps(jsf->filter, &caps); + return JS_NewInt32(ctx, caps.max_screen_fps); + case JSF_FILTER_CAPS_MAX_VIEWS: + gf_filter_get_session_caps(jsf->filter, &caps); + return JS_NewInt32(ctx, caps.max_screen_nb_views); + case JSF_FILTER_CAPS_MAX_CHANNELS: + gf_filter_get_session_caps(jsf->filter, &caps); + return JS_NewInt32(ctx, caps.max_audio_channels); + case JSF_FILTER_CAPS_MAX_SAMPLERATE: + gf_filter_get_session_caps(jsf->filter, &caps); + return JS_NewInt32(ctx, caps.max_audio_sample_rate); + case JSF_FILTER_CAPS_MAX_AUDIO_DEPTH: + gf_filter_get_session_caps(jsf->filter, &caps); + return JS_NewInt32(ctx, caps.max_audio_bit_depth); + + case JSF_FILTER_NB_EVTS_QUEUED: + return JS_NewInt32(ctx, gf_filter_get_num_events_queued(jsf->filter) ); + case JSF_FILTER_CLOCK_HINT_TIME: + gf_filter_get_clock_hint(jsf->filter, &lival, NULL); + return JS_NewInt64(ctx, lival); + case JSF_FILTER_CLOCK_HINT_MEDIATIME: + { + GF_Fraction64 frac; + gf_filter_get_clock_hint(jsf->filter, NULL, &frac); + dval = ((Double)frac.num) / frac.den; + } + return JS_NewFloat64(ctx, dval); + case JSF_FILTER_CONNECTIONS_PENDING: + return JS_NewBool(ctx, gf_filter_connections_pending(jsf->filter) ); + case JSF_FILTER_INAME: + if (jsf->filter->iname) return JS_NewString(ctx, jsf->filter->iname); + return JS_NULL; + } + + return JS_UNDEFINED; +} + +static JSValue jsf_filter_set_arg(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + JSValue v; + const char *desc=NULL; + const char *name=NULL; + const char *def = NULL; + const char *min_enum = NULL; + u32 type = 0; + Bool is_wildcard=GF_FALSE; + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf || !argc) return JS_EXCEPTION; + + v = JS_GetPropertyStr(ctx, argv[0], "name"); + if (!JS_IsUndefined(v)) name = JS_ToCString(ctx, v); + JS_FreeValue(ctx, v); + if (!name) return JS_EXCEPTION; + + if (!strcmp(name, "*")) { + if (jsf->has_wilcard_arg) return JS_UNDEFINED; + is_wildcard = GF_TRUE; + jsf->has_wilcard_arg = GF_TRUE; + } + + v = JS_GetPropertyStr(ctx, argv[0], "desc"); + if (!JS_IsUndefined(v)) desc = JS_ToCString(ctx, v); + JS_FreeValue(ctx, v); + if (!desc) { + JS_FreeCString(ctx, name); + return JS_EXCEPTION; + } + + v = JS_GetPropertyStr(ctx, argv[0], "type"); + if (!JS_IsUndefined(v)) + JS_ToInt32(ctx, &type, v); + JS_FreeValue(ctx, v); + if (!type) { + if (is_wildcard) { + type = GF_PROP_STRING; + } else { + JS_FreeCString(ctx, name); + JS_FreeCString(ctx, desc); + return JS_EXCEPTION; + } + } + + v = JS_GetPropertyStr(ctx, argv[0], "def"); + if (!JS_IsUndefined(v)) def = JS_ToCString(ctx, v); + JS_FreeValue(ctx, v); + + v = JS_GetPropertyStr(ctx, argv[0], "minmax_enum"); + if (!JS_IsUndefined(v)) min_enum = JS_ToCString(ctx, v); + JS_FreeValue(ctx, v); + + jsf->args = gf_realloc(jsf->args, sizeof(GF_FilterArgs)*(jsf->nb_args+2)); + memset(&jsf->args[jsf->nb_args], 0, 2*sizeof(GF_FilterArgs)); + jsf->args[jsf->nb_args].arg_name = gf_strdup(name); + jsf->args[jsf->nb_args].arg_desc = desc ? gf_strdup(desc) : NULL; + jsf->args[jsf->nb_args].arg_default_val = def ? gf_strdup(def) : NULL; + jsf->args[jsf->nb_args].min_max_enum = min_enum ? gf_strdup(min_enum) : NULL; + jsf->args[jsf->nb_args].arg_type = type; + jsf->args[jsf->nb_args].offset_in_private = -1; + + jsf->nb_args ++; + + gf_filter_define_args(jsf->filter, jsf->args); + + JS_FreeCString(ctx, name); + JS_FreeCString(ctx, desc); + JS_FreeCString(ctx, def); + JS_FreeCString(ctx, min_enum); + return JS_UNDEFINED; +} + + +static JSValue jsf_filter_set_cap(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + u32 prop_id = 0; + Bool is_output=GF_FALSE; + Bool is_inputoutput=GF_FALSE; + Bool is_excluded=GF_FALSE; + Bool is_static=GF_FALSE; + Bool is_loaded_filter_only=GF_FALSE; + Bool is_optional=GF_FALSE; + GF_PropertyValue p; + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf) return JS_EXCEPTION; + jsf->disable_filter = GF_FALSE; + + memset(&p, 0, sizeof(GF_PropertyValue)); + if (argc) { + u32 prop_type; + JSValue v; + const char *name = NULL; + + v = JS_GetPropertyStr(ctx, argv[0], "id"); + if (!JS_IsUndefined(v)) name = JS_ToCString(ctx, v); + JS_FreeValue(ctx, v); + if (!name) return JS_EXCEPTION; + prop_id = gf_props_get_id(name); + JS_FreeCString(ctx, name); + if (!prop_id) + return js_throw_err(ctx, GF_BAD_PARAM); + prop_type = gf_props_4cc_get_type(prop_id); + + name = NULL; + v = JS_GetPropertyStr(ctx, argv[0], "value"); + if (!JS_IsUndefined(v)) name = JS_ToCString(ctx, v); + JS_FreeValue(ctx, v); + + if (prop_id==GF_PROP_PID_STREAM_TYPE) { + p.type = GF_PROP_UINT; + p.value.uint = gf_stream_type_by_name(name); + } else if (prop_id==GF_PROP_PID_CODECID) { + p.type = GF_PROP_UINT; + p.value.uint = gf_codec_parse(name); + } else { + p = gf_props_parse_value(prop_type, NULL, name, NULL, gf_filter_get_sep(jsf->filter, GF_FS_SEP_LIST) ); + } + JS_FreeCString(ctx, name); + +#define GETB(_v, _n)\ + v = JS_GetPropertyStr(ctx, argv[0], _n);\ + if (!JS_IsUndefined(v)) _v = JS_ToBool(ctx, v);\ + JS_FreeValue(ctx, v);\ + + GETB(is_output, "output"); + GETB(is_inputoutput, "inout"); + GETB(is_excluded, "excluded"); + GETB(is_loaded_filter_only, "loaded_filter_only"); + GETB(is_static, "static"); + GETB(is_optional, "optional"); +#undef GETB + + } + + jsf->caps = gf_realloc(jsf->caps, sizeof(GF_FilterCapability)*(jsf->nb_caps+1)); + memset(&jsf->caps[jsf->nb_caps], 0, sizeof(GF_FilterCapability)); + if (prop_id) { + GF_FilterCapability *cap = &jsf->caps[jsf->nb_caps]; + cap->code = prop_id; + cap->val = p; + cap->flags = GF_CAPFLAG_IN_BUNDLE; + if (is_inputoutput) { + cap->flags |= GF_CAPFLAG_INPUT|GF_CAPFLAG_OUTPUT; + } else if (is_output) { + cap->flags |= GF_CAPFLAG_OUTPUT; + } else { + cap->flags |= GF_CAPFLAG_INPUT; + } + if (is_excluded) + cap->flags |= GF_CAPFLAG_EXCLUDED; + if (is_static) + cap->flags |= GF_CAPFLAG_STATIC; + if (is_loaded_filter_only) + cap->flags |= GF_CAPFLAG_LOADED_FILTER; + if (is_optional) + cap->flags |= GF_CAPFLAG_OPTIONAL; + } + jsf->nb_caps ++; + gf_filter_override_caps(jsf->filter, jsf->caps, jsf->nb_caps); + return JS_UNDEFINED; +} + +static JSValue jsf_filter_set_desc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf) return JS_EXCEPTION; + const char *str = JS_ToCString(ctx, argv[0]); + if (!str) return JS_EXCEPTION; + gf_filter_set_description(jsf->filter, str); + JS_FreeCString(ctx, str); + return JS_UNDEFINED; +} + +static JSValue jsf_filter_set_version(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf) return JS_EXCEPTION; + const char *str = JS_ToCString(ctx, argv[0]); + if (!str) return JS_EXCEPTION; + gf_filter_set_version(jsf->filter, str); + JS_FreeCString(ctx, str); + return JS_UNDEFINED; +} + +static JSValue jsf_filter_set_author(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf) return JS_EXCEPTION; + const char *str = JS_ToCString(ctx, argv[0]); + if (!str) return JS_EXCEPTION; + gf_filter_set_author(jsf->filter, str); + JS_FreeCString(ctx, str); + return JS_UNDEFINED; +} + + +static JSValue jsf_filter_set_help(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf) return JS_EXCEPTION; + const char *str = JS_ToCString(ctx, argv[0]); + if (!str) return JS_EXCEPTION; + gf_filter_set_help(jsf->filter, str); + JS_FreeCString(ctx, str); + return JS_UNDEFINED; +} + +static JSValue jsf_filter_set_name(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf) return JS_EXCEPTION; + if (jsf->log_name) gf_free(jsf->log_name); + jsf->log_name = NULL; + if (argc) { + JSValue global; + const char *str = JS_ToCString(ctx, argv[0]); + if (!str) return JS_EXCEPTION; + jsf->log_name = gf_strdup(str); + JS_FreeCString(ctx, str); + gf_filter_set_name(jsf->filter, jsf->log_name); + + global = JS_GetGlobalObject(ctx); + JS_SetPropertyStr(ctx, global, "_gpac_log_name", JS_NewString(ctx, jsf->log_name)); + JS_FreeValue(ctx, global); + } + return JS_UNDEFINED; +} + + +static JSValue jsf_filter_new_pid(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSPidCtx *pctx; + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf) return JS_EXCEPTION; + GF_FilterPid *opid = gf_filter_pid_new(jsf->filter); + if (!opid) return JS_EXCEPTION; + jsf->disable_filter = GF_FALSE; + + GF_SAFEALLOC(pctx, GF_JSPidCtx); + if (!pctx) + return js_throw_err(ctx, GF_OUT_OF_MEM); + gf_list_add(jsf->pids, pctx); + pctx->jsf = jsf; + pctx->pid = opid; + pctx->jsobj = JS_NewObjectClass(ctx, jsf_pid_class_id); + //keep ref to value we destroy it upon pid removal or filter desctruction + pctx->jsobj = JS_DupValue(ctx, pctx->jsobj); + JS_SetOpaque(pctx->jsobj, pctx); + gf_filter_pid_set_udta(pctx->pid, pctx); + return pctx->jsobj; +} + +static JSValue jsf_filter_send_event(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + Bool upstream = GF_FALSE; + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + GF_JSFilterInstanceCtx *jsfi = JS_GetOpaque(this_val, jsf_filter_inst_class_id); + if (!jsf && !jsfi) return JS_EXCEPTION; + GF_FilterEvent *evt = JS_GetOpaque(argv[0], jsf_event_class_id); + if (!evt) return JS_EXCEPTION; + if (argc>1) { + upstream = JS_ToBool(ctx, argv[1]); + } + + if (jsf) + gf_filter_send_event(jsf->filter, evt, upstream); + else + gf_filter_send_event(jsfi->filter, evt, upstream); + return JS_UNDEFINED; +} + +static JSValue jsf_filter_get_info(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + JSValue res; + const char *name=NULL; + const GF_PropertyValue *prop; + GF_Filter *filter; + GF_PropertyEntry *pe = NULL; + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + GF_JSFilterInstanceCtx *jsfi = JS_GetOpaque(this_val, jsf_filter_inst_class_id); + if (!jsf && !jsfi) return JS_EXCEPTION; + name = JS_ToCString(ctx, argv[0]); + if (!name) return JS_EXCEPTION; + filter = jsf ? jsf->filter : jsfi->filter; + + if ((argc>1) && JS_ToBool(ctx, argv[1])) { + prop = gf_filter_get_info_str(filter, name, &pe); + JS_FreeCString(ctx, name); + if (!prop) return JS_NULL; + res = jsf_NewProp(ctx, prop); + JS_SetPropertyStr(ctx, res, "type", JS_NewInt32(ctx, prop->type)); + } else { + u32 p4cc = gf_props_get_id(name); + JS_FreeCString(ctx, name); + if (!p4cc) + return js_throw_err(ctx, GF_BAD_PARAM); + prop = gf_filter_get_info(filter, p4cc, &pe); + if (!prop) return JS_NULL; + res = jsf_NewPropTranslate(ctx, prop, p4cc); + } + gf_filter_release_property(pe); + return res; +} + + + +static JSValue jsf_filter_is_supported_mime(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + const char *mime=NULL; + JSValue res; + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf || !argc) return JS_EXCEPTION; + mime = JS_ToCString(ctx, argv[0]); + if (!mime) return JS_EXCEPTION; + res = JS_NewBool(ctx, gf_filter_is_supported_mime(jsf->filter, mime)); + JS_FreeCString(ctx, mime); + return res; +} + +static JSValue jsf_filter_is_supported_source(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + const char *src=NULL; + const char *parent=NULL; + JSValue res; + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf || !argc) return JS_EXCEPTION; + src = JS_ToCString(ctx, argv[0]); + if (!src) return JS_EXCEPTION; + if (argc>1) { + parent = JS_ToCString(ctx, argv[1]); + if (!parent) { + JS_FreeCString(ctx, src); + return JS_EXCEPTION; + } + } + res = JS_NewBool(ctx, gf_filter_is_supported_source(jsf->filter, src, parent) ); + JS_FreeCString(ctx, src); + JS_FreeCString(ctx, parent); + return res; +} + +static JSValue jsf_filter_update_status(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + const char *status; + u32 pc=0; + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf || (argc<1)) return JS_EXCEPTION; + + status = JS_ToCString(ctx, argv[0]); + if (argc>1) + JS_ToInt32(ctx, &pc, argv[1]); + gf_filter_update_status(jsf->filter, pc, (char*) status); + JS_FreeCString(ctx, status); + return JS_UNDEFINED; +} + +static JSValue jsf_filter_reschedule_in(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + u32 rt_delay=0; + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf) return JS_EXCEPTION; + + if (argc) { + if (JS_ToInt32(ctx, &rt_delay, argv[0])) + return JS_EXCEPTION; + + gf_filter_ask_rt_reschedule(jsf->filter, rt_delay); + } else { + gf_filter_post_process_task(jsf->filter); + } + return JS_UNDEFINED; +} + +typedef struct +{ + JSValue func; + JSValue obj; + GF_JSFilterCtx *jsf; +} JS_ScriptTask; + +Bool jsf_task_exec(GF_Filter *filter, void *callback, u32 *reschedule_ms) +{ + s32 fun_result = -1; + JS_ScriptTask *task = (JS_ScriptTask *) callback; + JSValue ret; + gf_js_lock(task->jsf->ctx, GF_TRUE); + + if (JS_IsUndefined(task->obj)) { + ret = JS_Call(task->jsf->ctx, task->func, task->jsf->filter_obj, 0, NULL); + } else { + ret = JS_Call(task->jsf->ctx, task->func, task->obj, 0, NULL); + } + + if (JS_IsException(ret)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[%s] Error processing JS-defined task\n", task->jsf->log_name)); + js_dump_error(task->jsf->ctx); + } + else if (JS_IsInteger(ret)) { + JS_ToInt32(task->jsf->ctx, &fun_result, ret); + } + JS_FreeValue(task->jsf->ctx, ret); + + gf_js_lock(task->jsf->ctx, GF_FALSE); + js_do_loop(task->jsf->ctx); + + if (fun_result>=0) { + *reschedule_ms = (u32) fun_result; + return GF_TRUE; + } + if (!JS_IsUndefined(task->obj)) { + JS_FreeValue(task->jsf->ctx, task->obj); + } + JS_FreeValue(task->jsf->ctx, task->func); + gf_free(task); + return GF_FALSE; +} +static JSValue jsf_filter_post_task(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + JS_ScriptTask *task; + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf || !argc) return JS_EXCEPTION; + jsf->disable_filter = GF_FALSE; + if (!JS_IsFunction(ctx, argv[0])) + return JS_EXCEPTION; + if ((argc>1) && !JS_IsObject(argv[1])) + return JS_EXCEPTION; + + GF_SAFEALLOC(task, JS_ScriptTask); + if (!task) + return js_throw_err(ctx, GF_OUT_OF_MEM); + task->jsf = jsf; + task->func = JS_DupValue(ctx, argv[0]); + task->obj = JS_UNDEFINED; + if (argc>1) + task->obj = JS_DupValue(ctx, argv[1]); + + gf_filter_post_task(jsf->filter, jsf_task_exec, task, "jsf_task"); + return JS_UNDEFINED; +} + +static JSValue jsf_filter_notify_failure(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_Err e; + s32 error_type=0; + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf || !argc) return JS_EXCEPTION; + if (JS_ToInt32(ctx, (int *) &e, argv[0])) + return JS_EXCEPTION; + + if (e==GF_OK) return JS_UNDEFINED; + + if ((argc>1) && JS_ToInt32(ctx, &error_type, argv[1])) + return JS_EXCEPTION; + if (error_type==JSF_NOTIF_ERROR_AND_DISCONNECT) + gf_filter_notification_failure(jsf->filter, e, GF_TRUE); + else if (error_type==JSF_NOTIF_ERROR) + gf_filter_notification_failure(jsf->filter, e, GF_FALSE); + else + gf_filter_setup_failure(jsf->filter, e); + return JS_UNDEFINED; +} + +static JSValue jsf_filter_hint_clock(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + s64 time_in_us=0; + GF_Fraction64 media_timestamp; + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf || (argc<2)) return JS_EXCEPTION; + if (JS_ToInt64(ctx, &time_in_us, argv[0])) + return JS_EXCEPTION; + if (argc==2) { + Double t=0; + if (JS_ToFloat64(ctx, &t, argv[0])) + return JS_EXCEPTION; + media_timestamp.den = 1000000; + media_timestamp.num = (s64) (t*media_timestamp.den); + } else { + if (JS_ToInt64(ctx, &media_timestamp.num, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt64(ctx, &media_timestamp.den, argv[0])) + return JS_EXCEPTION; + } + gf_filter_hint_single_clock(jsf->filter, time_in_us, media_timestamp); + return JS_UNDEFINED; +} + +static JSValue jsf_filter_send_update(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + const char *filter_id=NULL; + const char *arg_name=NULL; + const char *arg_val=NULL; + GF_EventPropagateType prop_mask = 0; + JSValue ret = JS_UNDEFINED; + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + GF_JSFilterInstanceCtx *jsfi = JS_GetOpaque(this_val, jsf_filter_inst_class_id); + if ((!jsf && !jsfi) || (argc!=4)) return JS_EXCEPTION; + + filter_id = JS_ToCString(ctx, argv[0]); + arg_name = JS_ToCString(ctx, argv[1]); + arg_val = JS_ToCString(ctx, argv[2]); + if (!arg_name || !arg_val || JS_ToInt32(ctx, (s32 *) &prop_mask, argv[3])) { + ret = JS_EXCEPTION; + goto exit; + } + if (jsf) + gf_filter_send_update(jsf->filter, filter_id, arg_name, arg_val, prop_mask); + else + gf_filter_send_update(jsfi->filter, filter_id, arg_name, arg_val, prop_mask); + +exit: + JS_FreeCString(ctx, filter_id); + JS_FreeCString(ctx, arg_name); + JS_FreeCString(ctx, arg_val); + return ret; +} + +static JSValue jsf_filter_load_filter(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, GF_JSFilterMode mode) +{ + const char *url=NULL; + const char *parent=NULL; + GF_Err e=GF_OK; + Bool inherit_args = GF_FALSE; + GF_JSFilterInstanceCtx *f_ctx; + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf || !argc) return JS_EXCEPTION; + url = JS_ToCString(ctx, argv[0]); + if (!url) return JS_EXCEPTION; + + if ((mode==JSF_FINST_SOURCE) && (argc>1)) { + parent = JS_ToCString(ctx, argv[1]); + if (!parent) { + JS_FreeCString(ctx, url); + return JS_EXCEPTION; + } + if (argc>2) { + inherit_args = JS_ToBool(ctx, argv[2]); + } + } + GF_SAFEALLOC(f_ctx, GF_JSFilterInstanceCtx); + if (!f_ctx) { + JS_FreeCString(ctx, url); + JS_FreeCString(ctx, parent); + return js_throw_err(ctx, GF_OUT_OF_MEM); + } + + f_ctx->fmode = mode; + if (mode==JSF_FINST_SOURCE) { + f_ctx->filter = gf_filter_connect_source(jsf->filter, url, parent, inherit_args, &e); + } else if (mode==JSF_FINST_DEST) { + f_ctx->filter = gf_filter_connect_destination(jsf->filter, url, &e); + } else { + f_ctx->filter = gf_filter_load_filter(jsf->filter, url, &e); + } + JS_FreeCString(ctx, url); + JS_FreeCString(ctx, parent); + if (!f_ctx->filter) { + gf_free(f_ctx); + return js_throw_err(ctx, e); + } + f_ctx->jsf = jsf; + f_ctx->setup_failure_fun = JS_UNDEFINED; + f_ctx->filter_obj = JS_NewObjectClass(ctx, jsf_filter_inst_class_id); + JS_SetOpaque(f_ctx->filter_obj, f_ctx); + return f_ctx->filter_obj; +} + +static JSValue jsf_filter_add_source(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + return jsf_filter_load_filter(ctx, this_val, argc, argv, JSF_FINST_SOURCE); + +} +static JSValue jsf_filter_add_dest(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + return jsf_filter_load_filter(ctx, this_val, argc, argv, JSF_FINST_DEST); +} +static JSValue jsf_filter_add_filter(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + return jsf_filter_load_filter(ctx, this_val, argc, argv, JSF_FINST_FILTER); +} +static JSValue jsf_filter_make_sticky(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf) return JS_EXCEPTION; + gf_filter_make_sticky(jsf->filter); + return JS_UNDEFINED; +} +static JSValue jsf_filter_prevent_blocking(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf || !argc) return JS_EXCEPTION; + gf_filter_prevent_blocking(jsf->filter, JS_ToBool(ctx, argv[0]) ? GF_TRUE : GF_FALSE); + return JS_UNDEFINED; +} +static JSValue jsf_filter_block_eos(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSFilterCtx *jsf = JS_GetOpaque(this_val, jsf_filter_class_id); + if (!jsf || !argc) return JS_EXCEPTION; + gf_filter_block_eos(jsf->filter, JS_ToBool(ctx, argv[0]) ? GF_TRUE : GF_FALSE); + return JS_UNDEFINED; +} + + +static const JSCFunctionListEntry jsf_filter_funcs[] = { + JS_CGETSET_MAGIC_DEF("initialize", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_INITIALIZE), + JS_CGETSET_MAGIC_DEF("finalize", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_FINALIZE), + JS_CGETSET_MAGIC_DEF("configure_pid", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_CONFIGURE_PID), + JS_CGETSET_MAGIC_DEF("process", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_PROCESS), + JS_CGETSET_MAGIC_DEF("process_event", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_PROCESS_EVENT), + JS_CGETSET_MAGIC_DEF("update_arg", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_UPDATE_ARG), + JS_CGETSET_MAGIC_DEF("probe_data", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_PROBE_DATA), + JS_CGETSET_MAGIC_DEF("probe_url", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_PROBE_URL), + JS_CGETSET_MAGIC_DEF("reconfigure_output", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_RECONFIGURE_OUTPUT), + JS_CGETSET_MAGIC_DEF("remove_pid", jsf_filter_prop_get, jsf_filter_prop_set, JSF_EVT_REMOVE_PID), + JS_CGETSET_MAGIC_DEF("max_pids", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_MAX_PIDS), + JS_CGETSET_MAGIC_DEF("block_enabled", jsf_filter_prop_get, NULL, JSF_FILTER_BLOCK_ENABLED), + JS_CGETSET_MAGIC_DEF("output_buffer", jsf_filter_prop_get, NULL, JSF_FILTER_OUTPUT_BUFFER), + JS_CGETSET_MAGIC_DEF("output_playout", jsf_filter_prop_get, NULL, JSF_FILTER_OUTPUT_PLAYOUT), + JS_CGETSET_MAGIC_DEF("sep_args", jsf_filter_prop_get, NULL, JSF_FILTER_SEP_ARGS), + JS_CGETSET_MAGIC_DEF("sep_name", jsf_filter_prop_get, NULL, JSF_FILTER_SEP_NAME), + JS_CGETSET_MAGIC_DEF("sep_list", jsf_filter_prop_get, NULL, JSF_FILTER_SEP_LIST), + JS_CGETSET_MAGIC_DEF("dst_args", jsf_filter_prop_get, NULL, JSF_FILTER_DST_ARGS), + JS_CGETSET_MAGIC_DEF("dst_name", jsf_filter_prop_get, NULL, JSF_FILTER_DST_NAME), + JS_CGETSET_MAGIC_DEF("sinks_done", jsf_filter_prop_get, NULL, JSF_FILTER_SINKS_DONE), + JS_CGETSET_MAGIC_DEF("reports_on", jsf_filter_prop_get, NULL, JSF_FILTER_REPORTING_ENABLED), + JS_CGETSET_MAGIC_DEF("max_screen_width", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_WIDTH), + JS_CGETSET_MAGIC_DEF("max_screen_height", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_HEIGHT), + JS_CGETSET_MAGIC_DEF("max_screen_depth", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_DISPLAY_DEPTH), + JS_CGETSET_MAGIC_DEF("max_screen_fps", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_FPS), + JS_CGETSET_MAGIC_DEF("max_screen_views", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_VIEWS), + JS_CGETSET_MAGIC_DEF("max_audio_channels", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_CHANNELS), + JS_CGETSET_MAGIC_DEF("max_audio_samplerate", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_SAMPLERATE), + JS_CGETSET_MAGIC_DEF("max_audio_depth", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_CAPS_MAX_AUDIO_DEPTH), + JS_CGETSET_MAGIC_DEF("events_queued", jsf_filter_prop_get, NULL, JSF_FILTER_NB_EVTS_QUEUED), + JS_CGETSET_MAGIC_DEF("clock_hint_us", jsf_filter_prop_get, NULL, JSF_FILTER_CLOCK_HINT_TIME), + JS_CGETSET_MAGIC_DEF("clock_hint_mediatime", jsf_filter_prop_get, NULL, JSF_FILTER_CLOCK_HINT_MEDIATIME), + JS_CGETSET_MAGIC_DEF("connections_pending", jsf_filter_prop_get, NULL, JSF_FILTER_CONNECTIONS_PENDING), + JS_CGETSET_MAGIC_DEF("iname", jsf_filter_prop_get, jsf_filter_prop_set, JSF_FILTER_INAME), + + + JS_CFUNC_DEF("set_desc", 0, jsf_filter_set_desc), + JS_CFUNC_DEF("set_version", 0, jsf_filter_set_version), + JS_CFUNC_DEF("set_author", 0, jsf_filter_set_author), + JS_CFUNC_DEF("set_help", 0, jsf_filter_set_help), + JS_CFUNC_DEF("set_arg", 0, jsf_filter_set_arg), + JS_CFUNC_DEF("set_cap", 0, jsf_filter_set_cap), + JS_CFUNC_DEF("set_name", 0, jsf_filter_set_name), + JS_CFUNC_DEF("new_pid", 0, jsf_filter_new_pid), + JS_CFUNC_DEF("send_event", 0, jsf_filter_send_event), + JS_CFUNC_DEF("get_info", 0, jsf_filter_get_info), + JS_CFUNC_DEF("is_supported_mime", 0, jsf_filter_is_supported_mime), + JS_CFUNC_DEF("update_status", 0, jsf_filter_update_status), + JS_CFUNC_DEF("reschedule", 0, jsf_filter_reschedule_in), + JS_CFUNC_DEF("post_task", 0, jsf_filter_post_task), + JS_CFUNC_DEF("notify_failure", 0, jsf_filter_notify_failure), + JS_CFUNC_DEF("is_supported_source", 0, jsf_filter_is_supported_source), + JS_CFUNC_DEF("hint_clock", 0, jsf_filter_hint_clock), + JS_CFUNC_DEF("send_update", 0, jsf_filter_send_update), + JS_CFUNC_DEF("add_source", 0, jsf_filter_add_source), + JS_CFUNC_DEF("add_destination", 0, jsf_filter_add_dest), + JS_CFUNC_DEF("add_filter", 0, jsf_filter_add_filter), + JS_CFUNC_DEF("make_sticky", 0, jsf_filter_make_sticky), + JS_CFUNC_DEF("prevent_blocking", 1, jsf_filter_prevent_blocking), + JS_CFUNC_DEF("block_eos", 1, jsf_filter_block_eos), +}; + + + +static JSValue jsf_filter_set_source_id(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_Err e=GF_OK; + const char *source_id=NULL; + GF_JSFilterInstanceCtx *f_inst = JS_GetOpaque(this_val, jsf_filter_inst_class_id); + if (!f_inst || (argc<1)) return JS_EXCEPTION; + + GF_JSFilterCtx *f_from = JS_GetOpaque(argv[0], jsf_filter_class_id); + GF_JSFilterInstanceCtx *fi_from = JS_GetOpaque(argv[0], jsf_filter_inst_class_id); + if (!f_from && !fi_from) return JS_EXCEPTION; + + source_id = NULL; + if (argc>1) { + source_id = JS_ToCString(ctx, argv[2]); + if (!source_id) return JS_EXCEPTION; + } + + e = gf_filter_set_source(f_inst->filter, fi_from ? fi_from->filter : f_from->filter, source_id); + + JS_FreeCString(ctx, source_id); + if (e) return js_throw_err(ctx, e); + return JS_UNDEFINED; +} + +static JSValue jsf_filter_reset_source(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSFilterInstanceCtx *jsfi = JS_GetOpaque(this_val, jsf_filter_inst_class_id); + if (!jsfi) return JS_EXCEPTION; + gf_filter_reset_source(jsfi->jsf->filter); + return JS_UNDEFINED; +} + +static JSValue jsf_filter_remove(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSFilterInstanceCtx *jsfi = JS_GetOpaque(this_val, jsf_filter_inst_class_id); + if (!jsfi) return JS_EXCEPTION; + if (jsfi->fmode==JSF_FINST_SOURCE) + gf_filter_remove_src(jsfi->jsf->filter, jsfi->filter); + return JS_UNDEFINED; +} + +static JSValue jsf_filter_has_pid_connections_pending(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_Filter *stop_at = NULL; + GF_JSFilterInstanceCtx *jsfi = JS_GetOpaque(this_val, jsf_filter_inst_class_id); + if (!jsfi) return JS_EXCEPTION; + if (argc && (JS_IsNull(argv[0]) || JS_IsObject(argv[0]) ) ) { + GF_JSFilterInstanceCtx *fi_stop = JS_GetOpaque(this_val, jsf_filter_inst_class_id); + GF_JSFilterCtx *f_stop = JS_GetOpaque(this_val, jsf_filter_class_id); + if (fi_stop) stop_at = fi_stop->filter; + else if (f_stop) stop_at = f_stop->filter; + } else if (jsfi->fmode==JSF_FINST_SOURCE) { + stop_at = jsfi->jsf->filter; + } + return JS_NewBool(ctx, gf_filter_has_pid_connection_pending(jsfi->filter, stop_at) ); +} + +static void jsf_on_setup_error(GF_Filter *f, void *on_setup_error_udta, GF_Err e) +{ + GF_JSFilterInstanceCtx *f_inst = on_setup_error_udta; + JSValue ret, argv[1]; + gf_js_lock(f_inst->jsf->ctx, GF_TRUE); + + argv[0] = JS_NewInt32(f_inst->jsf->ctx, e); + + ret = JS_Call(f_inst->jsf->ctx, f_inst->setup_failure_fun, f_inst->filter_obj, 0, NULL); + + JS_FreeValue(f_inst->jsf->ctx, argv[0]); + JS_FreeValue(f_inst->jsf->ctx, ret); + gf_js_lock(f_inst->jsf->ctx, GF_FALSE); + js_do_loop(f_inst->jsf->ctx); +} + +static JSValue jsf_filter_inst_prop_set(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic) +{ + GF_JSFilterInstanceCtx *f_inst = JS_GetOpaque(this_val, jsf_filter_inst_class_id); + if (!f_inst) return JS_EXCEPTION; + + if (magic==0) { + if (JS_IsFunction(ctx, value) || JS_IsUndefined(value) || JS_IsNull(value)) { + if (JS_IsUndefined(f_inst->setup_failure_fun) && !JS_IsUndefined(value)) { + + gf_filter_set_setup_failure_callback(f_inst->jsf->filter, f_inst->filter, jsf_on_setup_error, f_inst); + } + JS_FreeValue(ctx, f_inst->setup_failure_fun); + f_inst->setup_failure_fun = JS_DupValue(ctx, value); + } + return JS_UNDEFINED; + } + return JS_UNDEFINED; +} + +static JSValue jsf_filter_inst_get_arg(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + const char *arg_name=NULL; + JSValue res; + GF_JSFilterInstanceCtx *f_inst = JS_GetOpaque(this_val, jsf_filter_inst_class_id); + if (!f_inst || !argc) return JS_EXCEPTION; + arg_name = JS_ToCString(ctx, argv[0]); + if (!arg_name) return JS_EXCEPTION; + if ((argc>1) && JS_ToBool(ctx, argv[1])) { + char dump[GF_PROP_DUMP_ARG_SIZE]; + const char *arg_val = gf_filter_get_arg_str(f_inst->filter, arg_name, dump); + res = arg_val ? JS_NewString(ctx, dump) : JS_NULL; + } else { + GF_PropertyValue p; + if (gf_filter_get_arg(f_inst->filter, arg_name, &p)) + res = jsf_NewProp(ctx, &p); + else + res = JS_NULL; + } + JS_FreeCString(ctx, arg_name); + return res; +} + + +static JSValue jsf_filter_inst_disable_probe(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSFilterInstanceCtx *jsfi = JS_GetOpaque(this_val, jsf_filter_inst_class_id); + if (!jsfi) return JS_EXCEPTION; + gf_filter_disable_probe(jsfi->filter); + return JS_UNDEFINED; +} +static JSValue jsf_filter_inst_disable_inputs(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSFilterInstanceCtx *jsfi = JS_GetOpaque(this_val, jsf_filter_inst_class_id); + if (!jsfi) return JS_EXCEPTION; + gf_filter_disable_inputs(jsfi->filter); + return JS_UNDEFINED; +} + +static const JSCFunctionListEntry jsf_filter_inst_funcs[] = { + JS_CGETSET_MAGIC_DEF("on_setup_failure", NULL, jsf_filter_inst_prop_set, 0), + JS_CFUNC_DEF("send_event", 0, jsf_filter_send_event), + JS_CFUNC_DEF("get_info", 0, jsf_filter_get_info), + JS_CFUNC_DEF("send_update", 0, jsf_filter_send_update), + JS_CFUNC_DEF("set_source", 0, jsf_filter_set_source_id), + JS_CFUNC_DEF("remove", 0, jsf_filter_remove), + JS_CFUNC_DEF("has_pid_connections_pending", 0, jsf_filter_has_pid_connections_pending), + JS_CFUNC_DEF("get_arg", 0, jsf_filter_inst_get_arg), + JS_CFUNC_DEF("disable_probe", 0, jsf_filter_inst_disable_probe), + JS_CFUNC_DEF("disable_inputs", 0, jsf_filter_inst_disable_inputs), + JS_CFUNC_DEF("reset_source", 0, jsf_filter_reset_source), + +}; + + +static JSValue jsf_pid_set_prop(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic) +{ + u32 ival; + GF_Err e = GF_OK; + const char *str=NULL; + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx) return JS_EXCEPTION; + switch (magic) { + case JSF_PID_NAME: + str = JS_ToCString(ctx, value); + gf_filter_pid_set_name(pctx->pid, str); + break; + case JSF_PID_EOS: + if (JS_ToBool(ctx, value)) + gf_filter_pid_set_eos(pctx->pid); + break; + case JSF_PID_MAX_BUFFER: + if (JS_ToInt32(ctx, &ival, value)) + return JS_EXCEPTION; + gf_filter_pid_set_max_buffer(pctx->pid, ival); + break; + case JSF_PID_LOOSE_CONNECT: + if (JS_ToBool(ctx, value)) + gf_filter_pid_set_loose_connect(pctx->pid); + break; + case JSF_PID_FRAMING_MODE: + gf_filter_pid_set_framing_mode(pctx->pid, JS_ToBool(ctx, value) ); + break; + case JSF_PID_CLOCK_MODE: + gf_filter_pid_set_clock_mode(pctx->pid, JS_ToBool(ctx, value) ); + break; + case JSF_PID_DISCARD: + gf_filter_pid_set_discard(pctx->pid, JS_ToBool(ctx, value) ); + break; + case JSF_PID_REQUIRE_SOURCEID: + if (JS_ToBool(ctx, value)) + gf_filter_pid_require_source_id(pctx->pid); + break; + case JSF_PID_RECOMPUTE_DTS: + gf_filter_pid_recompute_dts(pctx->pid, JS_ToBool(ctx, value)); + break; + } + + if (str) + JS_FreeCString(ctx, str); + if (e) return js_throw_err(ctx, e); + return JS_UNDEFINED; +} + +static JSValue jsf_pid_get_prop(JSContext *ctx, JSValueConst this_val, int magic) +{ + u64 dur; + char *str; + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx) return JS_EXCEPTION; + switch (magic) { + case JSF_PID_NAME: + return JS_NewString(ctx, gf_filter_pid_get_name(pctx->pid) ); + case JSF_PID_EOS: + return JS_NewBool (ctx, gf_filter_pid_is_eos(pctx->pid) ); + case JSF_PID_EOS_SEEN: + return JS_NewBool (ctx, gf_filter_pid_has_seen_eos(pctx->pid) ); + case JSF_PID_WOULD_BLOCK: + return JS_NewBool(ctx, gf_filter_pid_would_block(pctx->pid) ); + case JSF_PID_FILTER_NAME: + return JS_NewString(ctx, gf_filter_pid_get_filter_name(pctx->pid) ); + case JSF_PID_FILTER_SRC: + return JS_NewString(ctx, gf_filter_pid_get_source_filter_name(pctx->pid) ); + case JSF_PID_FILTER_ARGS: + return JS_NewString(ctx, gf_filter_pid_get_args(pctx->pid) ); + case JSF_PID_FILTER_SRC_ARGS: + return JS_NewString(ctx, gf_filter_pid_orig_src_args(pctx->pid) ); + case JSF_PID_MAX_BUFFER: + return JS_NewInt32(ctx, gf_filter_pid_get_max_buffer(pctx->pid) ); + case JSF_PID_BUFFER: + return JS_NewInt64(ctx, gf_filter_pid_query_buffer_duration(pctx->pid, GF_FALSE) ); + case JSF_PID_IS_FULL: + dur = gf_filter_pid_query_buffer_duration(pctx->pid, GF_TRUE); + return JS_NewBool(ctx, !dur ? 0 : 1); + case JSF_PID_FIRST_EMPTY: + return JS_NewBool (ctx, gf_filter_pid_first_packet_is_empty(pctx->pid) ); + case JSF_PID_FIRST_CTS: + if (gf_filter_pid_get_first_packet_cts(pctx->pid, &dur)) + return JS_NewInt64(ctx, dur); + else + return JS_NULL; + case JSF_PID_NB_PACKETS: + return JS_NewInt32(ctx, gf_filter_pid_get_packet_count(pctx->pid) ); + case JSF_PID_TIMESCALE: + return JS_NewInt32(ctx, gf_filter_pid_get_timescale(pctx->pid) ); + case JSF_PID_SRC_URL: + str = gf_filter_pid_get_source(pctx->pid); + if (str) { + JSValue res = JS_NewString(ctx, str); + gf_free(str); + return res; + } + return JS_NULL; + case JSF_PID_DST_URL: + str = gf_filter_pid_get_destination(pctx->pid); + if (str) { + JSValue res = JS_NewString(ctx, str); + gf_free(str); + return res; + } + return JS_NULL; + case JSF_PID_MIN_PCK_DUR: + return JS_NewInt32(ctx, gf_filter_pid_get_min_pck_duration(pctx->pid) ); + case JSF_PID_IS_PLAYING: + return JS_NewBool(ctx, gf_filter_pid_is_playing(pctx->pid) ); + } + return JS_UNDEFINED; +} + +static JSValue jsf_pid_send_event(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx) return JS_EXCEPTION; + GF_FilterEvent *evt = JS_GetOpaque(argv[0], jsf_event_class_id); + if (!evt) return JS_EXCEPTION; + evt->base.on_pid = pctx->pid; + if (evt->base.type == GF_FEVT_PLAY) { + gf_filter_pid_init_play_event(pctx->pid, evt, evt->play.start_range, evt->play.speed, pctx->jsf->log_name); + } + gf_filter_pid_send_event(pctx->pid, evt); + return JS_UNDEFINED; +} + +static JSValue jsf_pid_enum_properties(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + s32 idx; + u32 p4cc; + const char *pname; + JSValue res; + const GF_PropertyValue *prop; + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx) return JS_EXCEPTION; + if (JS_ToInt32(ctx, &idx, argv[0])) + return JS_EXCEPTION; + + if ((argc>1) && JS_ToBool(ctx, argv[1])) { + prop = gf_filter_pid_enum_info(pctx->pid, &idx, &p4cc, &pname); + } else { + prop = gf_filter_pid_enum_properties(pctx->pid, &idx, &p4cc, &pname); + } + if (!prop) return JS_NULL; + if (!pname) pname = gf_props_4cc_get_name(p4cc); + if (!pname) return JS_EXCEPTION; + + res = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, res, "name", JS_NewString(ctx, pname)); + JS_SetPropertyStr(ctx, res, "type", JS_NewInt32(ctx, prop->type)); + JS_SetPropertyStr(ctx, res, "value", jsf_NewProp(ctx, prop)); + return res; +} + +static JSValue jsf_pid_get_property_ex(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, Bool is_info) +{ + JSValue res; + const char *name=NULL; + const GF_PropertyValue *prop; + GF_PropertyEntry *pe = NULL; + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx) return JS_EXCEPTION; + name = JS_ToCString(ctx, argv[0]); + if (!name) return JS_EXCEPTION; + if ((argc>1) && JS_ToBool(ctx, argv[1])) { + if (is_info) { + prop = gf_filter_pid_get_info_str(pctx->pid, name, &pe); + } else { + prop = gf_filter_pid_get_property_str(pctx->pid, name); + } + JS_FreeCString(ctx, name); + if (!prop) return JS_NULL; + res = jsf_NewProp(ctx, prop); + JS_SetPropertyStr(ctx, res, "type", JS_NewInt32(ctx, prop->type)); + } else { + u32 p4cc = gf_props_get_id(name); + JS_FreeCString(ctx, name); + if (!p4cc) return JS_EXCEPTION; + if (is_info) { + prop = gf_filter_pid_get_info(pctx->pid, p4cc, &pe); + } else { + prop = gf_filter_pid_get_property(pctx->pid, p4cc); + } + if (!prop) return JS_NULL; + res = jsf_NewPropTranslate(ctx, prop, p4cc); + } + gf_filter_release_property(pe); + return res; +} +static JSValue jsf_pid_get_property(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + return jsf_pid_get_property_ex(ctx, this_val, argc, argv, GF_FALSE); +} +static JSValue jsf_pid_get_info(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + return jsf_pid_get_property_ex(ctx, this_val, argc, argv, GF_TRUE); +} + + +static JSValue jsf_pid_get_packet(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + JSValue res; + GF_FilterPacket *pck; + GF_JSPckCtx *pckctx; + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx) return JS_EXCEPTION; + + pck = gf_filter_pid_get_packet(pctx->pid); + if (!pck) return JS_NULL; + + if (pctx->pck_head) { + pckctx = pctx->pck_head; + assert(pckctx->pck == pck); + return JS_DupValue(ctx, pckctx->jsobj); + } + + res = JS_NewObjectClass(ctx, jsf_pck_class_id); + pckctx = gf_list_pop_back(pctx->jsf->pck_res); + if (!pckctx) { + GF_SAFEALLOC(pckctx, GF_JSPckCtx); + if (!pckctx) return js_throw_err(ctx, GF_OUT_OF_MEM); + } + memset(pckctx, 0, sizeof(GF_JSPckCtx)); + pckctx->jspid = pctx; + pckctx->pck = pck; + pckctx->jsobj = JS_DupValue(ctx, res); + pckctx->ref_val = JS_UNDEFINED; + pckctx->data_ab = JS_UNDEFINED; + pctx->pck_head = pckctx; + + JS_SetOpaque(res, pckctx); + return res; +} +static JSValue jsf_pid_drop_packet(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSPckCtx *pckctx; + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx) return JS_EXCEPTION; + + if (!pctx->pck_head) { + if (gf_filter_pid_get_packet_count(pctx->pid)) { + gf_filter_pid_drop_packet(pctx->pid); + } + return JS_UNDEFINED; + } + + pckctx = pctx->pck_head; + pckctx->pck = NULL; + pctx->pck_head = NULL; + JS_FreeValue(ctx, pckctx->jsobj); + pckctx->jsobj = JS_UNDEFINED; + gf_filter_pid_drop_packet(pctx->pid); + return JS_UNDEFINED; +} + +static JSValue jsf_pid_is_filter_in_parents(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx || !argc) return JS_EXCEPTION; + GF_JSFilterCtx *f_ctx = JS_GetOpaque(argv[0], jsf_filter_class_id); + GF_JSFilterInstanceCtx *fi_ctx = JS_GetOpaque(argv[0], jsf_filter_inst_class_id); + if (!f_ctx && !fi_ctx) return JS_EXCEPTION; + return JS_NewBool(ctx, gf_filter_pid_is_filter_in_parents(pctx->pid, f_ctx ? f_ctx->filter : fi_ctx->filter)); +} + +static JSValue jsf_pid_get_buffer_occupancy(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + JSValue res; + Bool in_final_flush; + u32 max_units, nb_pck, max_dur, dur; + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx) return JS_EXCEPTION; + + in_final_flush = !gf_filter_pid_get_buffer_occupancy(pctx->pid, &max_units, &nb_pck, &max_dur, &dur); + res = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, res, "max_units", JS_NewInt32(ctx, max_units)); + JS_SetPropertyStr(ctx, res, "nb_pck", JS_NewInt32(ctx, nb_pck)); + JS_SetPropertyStr(ctx, res, "max_dur", JS_NewInt32(ctx, max_dur)); + JS_SetPropertyStr(ctx, res, "dur", JS_NewInt32(ctx, dur)); + JS_SetPropertyStr(ctx, res, "final_flush", JS_NewBool(ctx, in_final_flush)); + return res; +} +static JSValue jsf_pid_clear_eos(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx || !argc) return JS_EXCEPTION; + gf_filter_pid_clear_eos(pctx->pid, JS_ToBool(ctx, argv[0])); + return JS_UNDEFINED; +} +static JSValue jsf_pid_check_caps(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx) return JS_EXCEPTION; + return JS_NewBool(ctx, gf_filter_pid_check_caps(pctx->pid)); +} +static JSValue jsf_pid_discard_block(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx) return JS_EXCEPTION; + gf_filter_pid_discard_block(pctx->pid); + return JS_UNDEFINED; +} +static JSValue jsf_pid_allow_direct_dispatch(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx) return JS_EXCEPTION; + gf_filter_pid_allow_direct_dispatch(pctx->pid); + return JS_UNDEFINED; +} +static JSValue jsf_pid_resolve_file_template(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + u32 fileidx=0; + GF_Err e; + char szFinal[GF_MAX_PATH]; + const char *templ, *suffix=NULL; + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx || !argc) return JS_EXCEPTION; + + templ = JS_ToCString(ctx, argv[0]); + if (!templ) + return JS_EXCEPTION; + + if ((argc>=2) && JS_ToInt32(ctx, &fileidx, argv[1])) { + JS_FreeCString(ctx, templ); + return JS_EXCEPTION; + } + + if (argc==3) + suffix = JS_ToCString(ctx, argv[2]); + + e = gf_filter_pid_resolve_file_template(pctx->pid, (char *)templ, szFinal, fileidx, suffix); + JS_FreeCString(ctx, templ); + + if (e) + return js_throw_err(ctx, e); + return JS_NewString(ctx, szFinal); +} + +static JSValue jsf_pid_query_caps(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + const char *name=NULL; + const GF_PropertyValue *prop; + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx || !argc) return JS_EXCEPTION; + + name = JS_ToCString(ctx, argv[0]); + if (!name) return JS_EXCEPTION; + + if ((argc>1) && JS_ToBool(ctx, argv[1])) { + JSValue res; + prop = gf_filter_pid_caps_query_str(pctx->pid, name); + JS_FreeCString(ctx, name); + if (!prop) return JS_NULL; + res = jsf_NewProp(ctx, prop); + JS_SetPropertyStr(ctx, res, "type", JS_NewInt32(ctx, prop->type)); + } else { + u32 p4cc = gf_props_get_id(name); + JS_FreeCString(ctx, name); + if (!p4cc) + return js_throw_err(ctx, GF_BAD_PARAM); + + prop = gf_filter_pid_caps_query(pctx->pid, p4cc); + if (!prop) return JS_NULL; + return jsf_NewPropTranslate(ctx, prop, p4cc); + } + return JS_UNDEFINED; +} + +static JSValue jsf_pid_get_statistics(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + JSValue res; + u32 mode; + GF_Err e; + GF_FilterPidStatistics stats; + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx || !argc) return JS_EXCEPTION; + if (JS_ToInt32(ctx, &mode, argv[0])) + return JS_EXCEPTION; + + e = gf_filter_pid_get_statistics(pctx->pid, &stats, mode); + if (e) + return js_throw_err(ctx, e); + + res = JS_NewObject(ctx); +#define SET_PROP32(_val)\ + JS_SetPropertyStr(ctx, res, #_val, JS_NewInt32(ctx, stats._val)); +#define SET_PROP64(_val)\ + JS_SetPropertyStr(ctx, res, #_val, JS_NewInt64(ctx, stats._val)); +#define SET_PROPB(_val)\ + JS_SetPropertyStr(ctx, res, #_val, JS_NewBool(ctx, stats._val)); + + SET_PROPB(disconnected) + SET_PROP32(average_process_rate) + SET_PROP32(max_process_rate) + SET_PROP32(avgerage_bitrate) + SET_PROP32(max_bitrate) + SET_PROP32(nb_processed) + SET_PROP32(max_process_time) + SET_PROP64(total_process_time) + SET_PROP64(first_process_time) + SET_PROP64(last_process_time) + SET_PROP32(min_frame_dur) + SET_PROP32(nb_saps) + SET_PROP32(max_sap_process_time) + SET_PROP64(total_sap_process_time) + SET_PROP64(max_buffer_time) + SET_PROP64(max_playout_time) + SET_PROP64(min_playout_time) + SET_PROP64(buffer_time) + SET_PROP32(nb_buffer_units) + return res; +} + +void jsf_pck_shared_del(GF_Filter *filter, GF_FilterPid *PID, GF_FilterPacket *pck) +{ + u32 i, count; + GF_JSPidCtx *pctx = gf_filter_pid_get_udta(PID); + count = gf_list_count(pctx->shared_pck); + for (i=0; ishared_pck, i); + if (pckc->pck == pck) { + if (!JS_IsUndefined(pckc->cbck_val)) { + JSValue res = JS_Call(pctx->jsf->ctx, pckc->cbck_val, pctx->jsobj, 0, NULL); + JS_FreeValue(pctx->jsf->ctx, res); + JS_FreeValue(pctx->jsf->ctx, pckc->cbck_val); + pckc->cbck_val = JS_UNDEFINED; + } + JS_FreeValue(pctx->jsf->ctx, pckc->ref_val); + pckc->ref_val = JS_UNDEFINED; + jsf_pck_detach_ab(pctx->jsf->ctx, pckc); + memset(pckc, 0, sizeof(GF_JSPckCtx)); + gf_list_add(pctx->jsf->pck_res, pckc); + gf_list_rem(pctx->shared_pck, i); + return; + } + } +} + + +#if !defined(GPAC_DISABLE_3D) && !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) +JSValue webgl_get_frame_interface(JSContext *ctx, int argc, JSValueConst *argv, gf_fsess_packet_destructor *pck_del, GF_FilterFrameInterface **f_ifce); +#endif + +static JSValue jsf_pid_new_packet(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + size_t ab_size; + u8 *data, *ab_data; + JSValue obj; + Bool use_shared=GF_FALSE; + GF_JSPckCtx *pckc; + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + + if (!pctx) return JS_EXCEPTION; + + pckc = gf_list_pop_back(pctx->jsf->pck_res); + if (!pckc) { + GF_SAFEALLOC(pckc, GF_JSPckCtx); + if (!pckc) + return js_throw_err(ctx, GF_OUT_OF_MEM); + } + obj = JS_NewObjectClass(ctx, jsf_pck_class_id); + if (JS_IsException(obj)) { + gf_list_add(pctx->jsf->pck_res, pckc); + return JS_EXCEPTION; + } + JS_SetOpaque(obj, pckc); + pckc->jspid = pctx; + pckc->jsobj = obj; + pckc->cbck_val = JS_UNDEFINED; + pckc->ref_val = JS_UNDEFINED; + pckc->data_ab = JS_UNDEFINED; + + if (argc>1) + use_shared = JS_ToBool(ctx, argv[1]); + + if (!argc) { + pckc->pck = gf_filter_pck_new_alloc(pctx->pid, 0, NULL); + goto pck_done; + } + /*string or true alloc*/ + if (JS_IsString(argv[0]) || JS_IsInteger(argv[0]) ) { + u32 len; + const char *str = NULL; + if (JS_IsInteger(argv[0]) ) { + if (use_shared) + return js_throw_err(ctx, GF_BAD_PARAM); + if (JS_ToInt32(ctx, &len, argv[0])) { + gf_list_add(pctx->jsf->pck_res, pckc); + return JS_EXCEPTION; + } + } else { + str = JS_ToCString(ctx, argv[0]); + len = (u32) strlen(str); + } + if (use_shared) { + pckc->pck = gf_filter_pck_new_shared(pctx->pid, str, len, jsf_pck_shared_del); + pckc->ref_val = JS_DupValue(ctx, argv[0]); + JS_FreeCString(ctx, str); + if (!pctx->shared_pck) pctx->shared_pck = gf_list_new(); + gf_list_add(pctx->shared_pck, pckc); + pckc->flags = GF_JS_PCK_IS_SHARED; + if ((argc>2) && JS_IsFunction(ctx, argv[2])) + pckc->cbck_val = JS_DupValue(ctx, argv[2]); + } else { + u8 *pdata=NULL; + pckc->pck = gf_filter_pck_new_alloc(pctx->pid, len, &pdata); + if (str) { + memcpy(pdata, str, len); + JS_FreeCString(ctx, str); + } + } + goto pck_done; + } + //check packet reference + GF_JSPckCtx *pckc_ref = JS_GetOpaque(argv[0], jsf_pck_class_id); + if (pckc_ref) { + if (use_shared) { + pckc->pck = gf_filter_pck_new_ref(pctx->pid, NULL, 0, pckc_ref->pck); + if ((argc>2) && JS_IsFunction(ctx, argv[2])) + pckc->cbck_val = JS_DupValue(ctx, argv[2]); + } else { + u8 *new_data; + if ((argc>2) && JS_ToBool(ctx, argv[2])) { + pckc->pck = gf_filter_pck_new_copy(pctx->pid, pckc_ref->pck, &new_data); + } else { + pckc->pck = gf_filter_pck_new_clone(pctx->pid, pckc_ref->pck, &new_data); + } + } + goto pck_done; + } + //check array buffer + + if (!JS_IsObject(argv[0])) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + ab_data = JS_GetArrayBuffer(ctx, &ab_size, argv[0]); + //this is an array buffer + if (ab_data) { + if (use_shared) { + pckc->pck = gf_filter_pck_new_shared(pctx->pid, ab_data, (u32) ab_size, jsf_pck_shared_del); + pckc->ref_val = JS_DupValue(ctx, argv[0]); + if (!pctx->shared_pck) pctx->shared_pck = gf_list_new(); + gf_list_add(pctx->shared_pck, pckc); + pckc->flags = GF_JS_PCK_IS_SHARED; + if ((argc>2) && JS_IsFunction(ctx, argv[2])) + pckc->cbck_val = JS_DupValue(ctx, argv[2]); + } else { + pckc->pck = gf_filter_pck_new_alloc(pctx->pid, (u32) ab_size, &data); + if (!data) { + JS_FreeValue(ctx, obj); + return js_throw_err(ctx, GF_OUT_OF_MEM); + } + memcpy(data, ab_data, ab_size); + } + goto pck_done; + } + /*try WebGL canvas*/ +#if !defined(GPAC_DISABLE_3D) && !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) + gf_fsess_packet_destructor pck_del = NULL; + GF_FilterFrameInterface *f_ifce = NULL; + JSValue res = webgl_get_frame_interface(ctx, argc, argv, &pck_del, &f_ifce); + if (!JS_IsNull(res)) { + if (JS_IsException(res)) { + JS_FreeValue(ctx, obj); + return res; + } + assert(f_ifce); + pckc->pck = gf_filter_pck_new_frame_interface(pctx->pid, f_ifce, pck_del); + goto pck_done; + } +#endif + + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + + +pck_done: + if (!pckc->pck) { + JS_FreeValue(ctx, obj); + return js_throw_err(ctx, GF_OUT_OF_MEM); + } + pckc->flags |= GF_JS_PCK_IS_OUTPUT; + return obj; +} + +static JSValue jsf_pid_get_clock_info(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + u32 timescale; + u64 val; + GF_FilterClockType cktype; + JSValue res; + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx) return JS_EXCEPTION; + cktype = gf_filter_pid_get_clock_info(pctx->pid, &val, ×cale); + res = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, res, "type", JS_NewInt32(ctx, cktype)); + JS_SetPropertyStr(ctx, res, "timescale", JS_NewInt32(ctx, timescale)); + JS_SetPropertyStr(ctx, res, "value", JS_NewInt64(ctx, val)); + return res; +} + + +static JSValue jsf_pid_set_property_ex(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, u32 mode) +{ + GF_Err e; + GF_PropertyValue prop; + const GF_PropertyValue *the_prop = NULL; + const char *name=NULL; + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx) return JS_EXCEPTION; + name = JS_ToCString(ctx, argv[0]); + if (!name) return JS_EXCEPTION; + + if ((argc>2) && JS_ToBool(ctx, argv[2])) { + if (!JS_IsNull(argv[1])) { + e = jsf_ToProp(pctx->jsf->filter, ctx, argv[1], 0, &prop); + JS_FreeCString(ctx, name); + if (e) + return js_throw_err(ctx, e); + the_prop = ∝ + } + if (mode==1) { + e = gf_filter_pid_set_info_dyn(pctx->pid, (char *) name, &prop); + } else if (mode==2) { + e = gf_filter_pid_negociate_property_dyn(pctx->pid, (char *) name, &prop); + } else { + e = gf_filter_pid_set_property_dyn(pctx->pid, (char *) name, &prop); + } + } else { + u32 p4cc = gf_props_get_id(name); + JS_FreeCString(ctx, name); + if (!p4cc) return JS_EXCEPTION; + if (!JS_IsNull(argv[1])) { + e = jsf_ToProp(pctx->jsf->filter, ctx, argv[1], p4cc, &prop); + if (e) + return js_throw_err(ctx, e); + + the_prop = ∝ + } + if (mode==1) { + e = gf_filter_pid_set_info(pctx->pid, p4cc, the_prop); + } else if (mode==2) { + e = gf_filter_pid_negociate_property(pctx->pid, p4cc, the_prop); + } else { + e = gf_filter_pid_set_property(pctx->pid, p4cc, the_prop); + } + } + + if (the_prop) + gf_props_reset_single(&prop); + if (e) return js_throw_err(ctx, e); + return JS_UNDEFINED; +} +static JSValue jsf_pid_set_property(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + return jsf_pid_set_property_ex(ctx, this_val, argc, argv, 0); +} +static JSValue jsf_pid_set_info(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + return jsf_pid_set_property_ex(ctx, this_val, argc, argv, 1); +} +static JSValue jsf_pid_negociate_prop(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + return jsf_pid_set_property_ex(ctx, this_val, argc, argv, 2); +} + +static JSValue jsf_pid_remove(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx) return JS_EXCEPTION; + gf_filter_pid_remove(pctx->pid); + pctx->pid = NULL; + JS_SetOpaque(this_val, NULL); + return JS_UNDEFINED; +} + +static JSValue jsf_pid_reset_props(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_Err e; + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx) return JS_EXCEPTION; + e = gf_filter_pid_reset_properties(pctx->pid); + if (e) return js_throw_err(ctx, e); + return JS_UNDEFINED; +} + +static JSValue jsf_pid_copy_props(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_Err e; + GF_JSPidCtx *pctx_this = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx_this || !argc) return JS_EXCEPTION; + GF_JSPidCtx *pctx_from = JS_GetOpaque(argv[0], jsf_pid_class_id); + if (!pctx_from) return JS_EXCEPTION; + e = gf_filter_pid_copy_properties(pctx_this->pid, pctx_from->pid); + if (e) return js_throw_err(ctx, e); + return JS_UNDEFINED; +} + +static JSValue jsf_pid_forward(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_Err e; + GF_JSPckCtx *pckc; + GF_JSPidCtx *pctx = JS_GetOpaque(this_val, jsf_pid_class_id); + if (!pctx || !argc) return JS_EXCEPTION; + if (!JS_IsObject(argv[0])) return JS_EXCEPTION; + pckc = JS_GetOpaque(argv[0], jsf_pck_class_id); + if (!pckc || !pckc->pck) return JS_EXCEPTION; + e = gf_filter_pck_forward(pckc->pck, pctx->pid); + if (e) return js_throw_err(ctx, e); + return JS_UNDEFINED; +} + + +static const JSCFunctionListEntry jsf_pid_funcs[] = { + JS_CGETSET_MAGIC_DEF("name", jsf_pid_get_prop, jsf_pid_set_prop, JSF_PID_NAME), + JS_CGETSET_MAGIC_DEF("eos", jsf_pid_get_prop, jsf_pid_set_prop, JSF_PID_EOS), + JS_CGETSET_MAGIC_DEF("eos_seen", jsf_pid_get_prop, NULL, JSF_PID_EOS_SEEN), + JS_CGETSET_MAGIC_DEF("would_block", jsf_pid_get_prop, NULL, JSF_PID_WOULD_BLOCK), + JS_CGETSET_MAGIC_DEF("filter_name", jsf_pid_get_prop, NULL, JSF_PID_FILTER_NAME), + JS_CGETSET_MAGIC_DEF("src_name", jsf_pid_get_prop, NULL, JSF_PID_FILTER_SRC), + JS_CGETSET_MAGIC_DEF("args", jsf_pid_get_prop, NULL, JSF_PID_FILTER_ARGS), + JS_CGETSET_MAGIC_DEF("src_args", jsf_pid_get_prop, NULL, JSF_PID_FILTER_SRC_ARGS), + JS_CGETSET_MAGIC_DEF("max_buffer", jsf_pid_get_prop, jsf_pid_set_prop, JSF_PID_MAX_BUFFER), + JS_CGETSET_MAGIC_DEF("loose_connect", NULL, jsf_pid_set_prop, JSF_PID_LOOSE_CONNECT), + JS_CGETSET_MAGIC_DEF("framing", NULL, jsf_pid_set_prop, JSF_PID_FRAMING_MODE), + JS_CGETSET_MAGIC_DEF("buffer", jsf_pid_get_prop, NULL, JSF_PID_BUFFER), + JS_CGETSET_MAGIC_DEF("full", jsf_pid_get_prop, NULL, JSF_PID_IS_FULL), + JS_CGETSET_MAGIC_DEF("first_empty", jsf_pid_get_prop, NULL, JSF_PID_FIRST_EMPTY), + JS_CGETSET_MAGIC_DEF("first_cts", jsf_pid_get_prop, NULL, JSF_PID_FIRST_CTS), + JS_CGETSET_MAGIC_DEF("nb_pck_queued", jsf_pid_get_prop, NULL, JSF_PID_NB_PACKETS), + JS_CGETSET_MAGIC_DEF("timescale", jsf_pid_get_prop, NULL, JSF_PID_TIMESCALE), + JS_CGETSET_MAGIC_DEF("clock_mode", NULL, jsf_pid_set_prop, JSF_PID_CLOCK_MODE), + JS_CGETSET_MAGIC_DEF("discard", NULL, jsf_pid_set_prop, JSF_PID_DISCARD), + JS_CGETSET_MAGIC_DEF("src_url", jsf_pid_get_prop, NULL, JSF_PID_SRC_URL), + JS_CGETSET_MAGIC_DEF("dst_url", jsf_pid_get_prop, NULL, JSF_PID_DST_URL), + JS_CGETSET_MAGIC_DEF("require_source_id", NULL, jsf_pid_set_prop, JSF_PID_REQUIRE_SOURCEID), + JS_CGETSET_MAGIC_DEF("recompute_dts", NULL, jsf_pid_set_prop, JSF_PID_RECOMPUTE_DTS), + JS_CGETSET_MAGIC_DEF("min_pck_dur", jsf_pid_get_prop, NULL, JSF_PID_MIN_PCK_DUR), + JS_CGETSET_MAGIC_DEF("playing", jsf_pid_get_prop, NULL, JSF_PID_IS_PLAYING), + JS_CFUNC_DEF("send_event", 0, jsf_pid_send_event), + JS_CFUNC_DEF("enum_properties", 0, jsf_pid_enum_properties), + JS_CFUNC_DEF("get_prop", 0, jsf_pid_get_property), + JS_CFUNC_DEF("get_info", 0, jsf_pid_get_info), + JS_CFUNC_DEF("get_packet", 0, jsf_pid_get_packet), + JS_CFUNC_DEF("drop_packet", 0, jsf_pid_drop_packet), + JS_CFUNC_DEF("is_filter_in_parents", 0, jsf_pid_is_filter_in_parents), + JS_CFUNC_DEF("get_buffer_occupancy", 0, jsf_pid_get_buffer_occupancy), + JS_CFUNC_DEF("clear_eos", 0, jsf_pid_clear_eos), + JS_CFUNC_DEF("check_caps", 0, jsf_pid_check_caps), + JS_CFUNC_DEF("discard_block", 0, jsf_pid_discard_block), + JS_CFUNC_DEF("allow_direct_dispatch", 0, jsf_pid_allow_direct_dispatch), + JS_CFUNC_DEF("resolve_file_template", 0, jsf_pid_resolve_file_template), + JS_CFUNC_DEF("query_caps", 0, jsf_pid_query_caps), + JS_CFUNC_DEF("get_stats", 0, jsf_pid_get_statistics), + JS_CFUNC_DEF("get_clock_info", 0, jsf_pid_get_clock_info), + JS_CFUNC_DEF("set_prop", 0, jsf_pid_set_property), + JS_CFUNC_DEF("set_info", 0, jsf_pid_set_info), + JS_CFUNC_DEF("new_packet", 0, jsf_pid_new_packet), + JS_CFUNC_DEF("remove", 0, jsf_pid_remove), + JS_CFUNC_DEF("reset_props", 0, jsf_pid_reset_props), + JS_CFUNC_DEF("copy_props", 0, jsf_pid_copy_props), + JS_CFUNC_DEF("forward", 0, jsf_pid_forward), + JS_CFUNC_DEF("negociate_prop", 0, jsf_pid_negociate_prop), +}; + +enum +{ + JSF_EVENT_TYPE, + JSF_EVENT_NAME, + /*PLAY event*/ + JSF_EVENT_START_RANGE, + JSF_EVENT_SPEED, + JSF_EVENT_HW_BUFFER_RESET, + JSF_EVENT_INITIAL_BROADCAST_PLAY, + JSF_EVENT_TIMESTAMP_BASED, + JSF_EVENT_FULL_FILE_ONLY, + JSF_EVENT_FORCE_DASH_SEG_SWITCH, + JSF_EVENT_FROM_PCK, + /*source switch*/ + JSF_EVENT_START_OFFSET, + JSF_EVENT_END_OFFSET, + JSF_EVENT_SOURCE_SWITCH, + JSF_EVENT_SKIP_CACHE_EXPIRATION, + JSF_EVENT_HINT_BLOCK_SIZE, + /*segment size*/ + JSF_EVENT_SEG_URL, + JSF_EVENT_SEG_IS_INIT, + JSF_EVENT_MEDIA_START_RANGE, + JSF_EVENT_MEDIA_END_RANGE, + JSF_EVENT_IDX_START_RANGE, + JSF_EVENT_IDX_END_RANGE, + /*quality switch*/ + JSF_EVENT_SWITCH_UP, + JSF_EVENT_SWITCH_GROUP_IDX, + JSF_EVENT_SWITCH_QUALITY_IDX, + JSF_EVENT_SWITCH_TILE_MODE, + JSF_EVENT_SWITCH_QUALITY_DEGRADATION, + /*visibility hint*/ + JSF_EVENT_VIS_MIN_X, + JSF_EVENT_VIS_MIN_Y, + JSF_EVENT_VIS_MAX_X, + JSF_EVENT_VIS_MAX_Y, + JSF_EVENT_VIS_IS_GAZE, + /*buffer requirements*/ + JSF_EVENT_BUFREQ_MAX_BUFFER_US, + JSF_EVENT_BUFREQ_MAX_PLAYOUT_US, + JSF_EVENT_BUFREQ_MIN_PLAYOUT_US, + JSF_EVENT_BUFREQ_PID_ONLY, + + JSF_EVENT_USER_TYPE, + JSF_EVENT_USER_KEYCODE, + JSF_EVENT_USER_MOUSE_X, + JSF_EVENT_USER_MOUSE_Y, + JSF_EVENT_USER_WHEEL, + JSF_EVENT_USER_BUTTON, + JSF_EVENT_USER_HWKEY, + JSF_EVENT_USER_DROPFILES, + JSF_EVENT_USER_TEXT, + JSF_EVENT_USER_MT_ROTATION, + JSF_EVENT_USER_MT_PINCH, + JSF_EVENT_USER_MT_FINGERS, + JSF_EVENT_USER_WIDTH, + JSF_EVENT_USER_HEIGHT, + JSF_EVENT_USER_SHOWTYPE, + JSF_EVENT_USER_MOVE_X, + JSF_EVENT_USER_MOVE_Y, + JSF_EVENT_USER_MOVE_RELATIVE, + JSF_EVENT_USER_MOVE_ALIGN_X, + JSF_EVENT_USER_MOVE_ALIGN_Y, + JSF_EVENT_USER_CAPTION, +}; + +static Bool jsf_check_evt(u32 evt_type, u8 ui_type, int magic) +{ + if (magic==JSF_EVENT_TYPE) return GF_TRUE; + if (magic==JSF_EVENT_NAME) return GF_TRUE; + switch (evt_type) { + case GF_FEVT_PLAY: + switch (magic) { + case JSF_EVENT_START_RANGE: + case JSF_EVENT_SPEED: + case JSF_EVENT_HW_BUFFER_RESET: + case JSF_EVENT_INITIAL_BROADCAST_PLAY: + case JSF_EVENT_TIMESTAMP_BASED: + case JSF_EVENT_FULL_FILE_ONLY: + case JSF_EVENT_FORCE_DASH_SEG_SWITCH: + case JSF_EVENT_FROM_PCK: + return GF_TRUE; + default: + return GF_FALSE; + } + break; + case GF_FEVT_SET_SPEED: + return (magic == JSF_EVENT_SPEED) ? GF_TRUE : GF_FALSE; + case GF_FEVT_SOURCE_SWITCH: + switch (magic) { + case JSF_EVENT_START_OFFSET: + case JSF_EVENT_END_OFFSET: + case JSF_EVENT_SOURCE_SWITCH: + case JSF_EVENT_SKIP_CACHE_EXPIRATION: + case JSF_EVENT_HINT_BLOCK_SIZE: + return GF_TRUE; + default: + return GF_FALSE; + } + break; + case GF_FEVT_SEGMENT_SIZE: + switch (magic) { + case JSF_EVENT_SEG_URL: + case JSF_EVENT_SEG_IS_INIT: + case JSF_EVENT_MEDIA_START_RANGE: + case JSF_EVENT_MEDIA_END_RANGE: + case JSF_EVENT_IDX_START_RANGE: + case JSF_EVENT_IDX_END_RANGE: + return GF_TRUE; + default: + return GF_FALSE; + } + break; + case GF_FEVT_QUALITY_SWITCH: + switch (magic) { + case JSF_EVENT_SWITCH_UP: + case JSF_EVENT_SWITCH_GROUP_IDX: + case JSF_EVENT_SWITCH_QUALITY_IDX: + case JSF_EVENT_SWITCH_TILE_MODE: + case JSF_EVENT_SWITCH_QUALITY_DEGRADATION: + return GF_TRUE; + default: + return GF_FALSE; + } + break; + case GF_FEVT_VISIBILITY_HINT: + switch (magic) { + case JSF_EVENT_VIS_MIN_X: + case JSF_EVENT_VIS_MIN_Y: + case JSF_EVENT_VIS_MAX_X: + case JSF_EVENT_VIS_MAX_Y: + case JSF_EVENT_VIS_IS_GAZE: + return GF_TRUE; + default: + return GF_FALSE; + } + break; + case GF_FEVT_BUFFER_REQ: + switch (magic) { + case JSF_EVENT_BUFREQ_MAX_BUFFER_US: + case JSF_EVENT_BUFREQ_MAX_PLAYOUT_US: + case JSF_EVENT_BUFREQ_MIN_PLAYOUT_US: + case JSF_EVENT_BUFREQ_PID_ONLY: + return GF_TRUE; + default: + return GF_FALSE; + } + break; + case GF_FEVT_USER: + if (magic==JSF_EVENT_USER_TYPE) + return GF_TRUE; + + switch (ui_type) { + case GF_EVENT_CLICK: + case GF_EVENT_MOUSEUP: + case GF_EVENT_MOUSEDOWN: + case GF_EVENT_MOUSEOVER: + case GF_EVENT_MOUSEOUT: + case GF_EVENT_MOUSEMOVE: + case GF_EVENT_MOUSEWHEEL: + switch (magic) { + case JSF_EVENT_USER_MOUSE_X: + case JSF_EVENT_USER_MOUSE_Y: + case JSF_EVENT_USER_WHEEL: + case JSF_EVENT_USER_BUTTON: + return GF_TRUE; + default: + break; + } + return GF_FALSE; + case GF_EVENT_MULTITOUCH: + switch (magic) { + case JSF_EVENT_USER_MOUSE_X: + case JSF_EVENT_USER_MOUSE_Y: + case JSF_EVENT_USER_MT_ROTATION: + case JSF_EVENT_USER_MT_PINCH: + case JSF_EVENT_USER_MT_FINGERS: + return GF_TRUE; + default: + break; + } + return GF_FALSE; + + case GF_EVENT_KEYUP: + case GF_EVENT_KEYDOWN: + case GF_EVENT_LONGKEYPRESS: + case GF_EVENT_TEXTINPUT: + switch (magic) { + case JSF_EVENT_USER_KEYCODE: + case JSF_EVENT_USER_HWKEY: + case JSF_EVENT_USER_DROPFILES: + return GF_TRUE; + default: + break; + } + return GF_FALSE; + case GF_EVENT_DROPFILE: + switch (magic) { + case JSF_EVENT_USER_DROPFILES: + return GF_TRUE; + default: + break; + } + return GF_FALSE; + case GF_EVENT_PASTE_TEXT: + case GF_EVENT_COPY_TEXT: + switch (magic) { + case JSF_EVENT_USER_TEXT: + return GF_TRUE; + default: + break; + } + return GF_FALSE; + case GF_EVENT_SIZE: + switch (magic) { + case JSF_EVENT_USER_WIDTH: + case JSF_EVENT_USER_HEIGHT: + return GF_TRUE; + default: + break; + } + return GF_FALSE; + case GF_EVENT_MOVE: + switch (magic) { + case JSF_EVENT_USER_MOVE_X: + case JSF_EVENT_USER_MOVE_Y: + case JSF_EVENT_USER_MOVE_RELATIVE: + case JSF_EVENT_USER_MOVE_ALIGN_X: + case JSF_EVENT_USER_MOVE_ALIGN_Y: + return GF_TRUE; + default: + break; + } + return GF_FALSE; + case GF_EVENT_SET_CAPTION: + if (magic==JSF_EVENT_USER_CAPTION) return GF_TRUE; + return GF_FALSE; + } + } + return GF_FALSE; +} + + +static JSValue jsf_event_set_prop(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic) +{ + GF_Err e = GF_OK; + u32 ival; + Double dval; + const char *str=NULL; + GF_FilterEvent *evt = JS_GetOpaque(this_val, jsf_event_class_id); + if (!evt) return JS_EXCEPTION; + if (!jsf_check_evt(evt->base.type, evt->user_event.event.type, magic)) + return JS_EXCEPTION; + + switch (magic) { + case JSF_EVENT_TYPE: + return JS_EXCEPTION; + /*PLAY*/ + case JSF_EVENT_START_RANGE: + return JS_ToFloat64(ctx, &evt->play.start_range, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_SPEED: + return JS_ToFloat64(ctx, &evt->play.speed, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_HW_BUFFER_RESET: + evt->play.hw_buffer_reset = JS_ToBool(ctx, value); + return JS_UNDEFINED; + case JSF_EVENT_INITIAL_BROADCAST_PLAY: + evt->play.initial_broadcast_play = JS_ToBool(ctx, value); + return JS_UNDEFINED; + case JSF_EVENT_TIMESTAMP_BASED: + if (JS_ToInt32(ctx, &ival, value)) return JS_EXCEPTION; + evt->play.timestamp_based = (u8) ival; + return JS_UNDEFINED; + case JSF_EVENT_FULL_FILE_ONLY: + evt->play.full_file_only = JS_ToBool(ctx, value); + return JS_UNDEFINED; + case JSF_EVENT_FORCE_DASH_SEG_SWITCH: + evt->play.forced_dash_segment_switch = JS_ToBool(ctx, value); + return JS_UNDEFINED; + case JSF_EVENT_FROM_PCK: + return JS_ToInt32(ctx, &evt->play.from_pck, value) ? JS_EXCEPTION : JS_UNDEFINED; + /*source switch*/ + case JSF_EVENT_START_OFFSET: + return JS_ToInt64(ctx, &evt->seek.start_offset, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_END_OFFSET: + return JS_ToInt64(ctx, &evt->seek.end_offset, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_SOURCE_SWITCH: + /*TODO check leak!*/ + evt->seek.source_switch = JS_ToCString(ctx, value); + return JS_UNDEFINED; + case JSF_EVENT_SKIP_CACHE_EXPIRATION: + evt->seek.skip_cache_expiration = JS_ToBool(ctx, value); + return JS_UNDEFINED; + case JSF_EVENT_HINT_BLOCK_SIZE: + return JS_ToInt32(ctx, &evt->seek.hint_block_size, value) ? JS_EXCEPTION : JS_UNDEFINED; + /*segment size*/ + case JSF_EVENT_SEG_URL: + /*TODO check leak!*/ + evt->seg_size.seg_url = JS_ToCString(ctx, value); + return JS_UNDEFINED; + case JSF_EVENT_SEG_IS_INIT: + evt->seg_size.is_init = JS_ToBool(ctx, value); + return JS_UNDEFINED; + case JSF_EVENT_MEDIA_START_RANGE: + return JS_ToInt64(ctx, &evt->seg_size.media_range_start, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_MEDIA_END_RANGE: + return JS_ToInt64(ctx, &evt->seg_size.media_range_end, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_IDX_START_RANGE: + return JS_ToInt64(ctx, &evt->seg_size.idx_range_start, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_IDX_END_RANGE: + return JS_ToInt64(ctx, &evt->seg_size.idx_range_end, value) ? JS_EXCEPTION : JS_UNDEFINED; + /*quality switch*/ + case JSF_EVENT_SWITCH_UP: + evt->quality_switch.up = JS_ToBool(ctx, value); + return JS_UNDEFINED; + case JSF_EVENT_SWITCH_GROUP_IDX: + return JS_ToInt32(ctx, &evt->quality_switch.dependent_group_index, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_SWITCH_QUALITY_IDX: + return JS_ToInt32(ctx, &evt->quality_switch.q_idx, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_SWITCH_TILE_MODE: + return JS_ToInt32(ctx, &evt->quality_switch.set_tile_mode_plus_one, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_SWITCH_QUALITY_DEGRADATION: + return JS_ToInt32(ctx, &evt->quality_switch.quality_degradation, value) ? JS_EXCEPTION : JS_UNDEFINED; + /*visibility hint*/ + case JSF_EVENT_VIS_MIN_X: return JS_ToInt32(ctx, &evt->visibility_hint.min_x, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_VIS_MIN_Y: return JS_ToInt32(ctx, &evt->visibility_hint.min_y, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_VIS_MAX_X: return JS_ToInt32(ctx, &evt->visibility_hint.max_x, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_VIS_MAX_Y: return JS_ToInt32(ctx, &evt->visibility_hint.max_y, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_VIS_IS_GAZE: + evt->visibility_hint.is_gaze = JS_ToBool(ctx, value); + return JS_UNDEFINED; + /*buffer reqs*/ + case JSF_EVENT_BUFREQ_MAX_BUFFER_US: return JS_ToInt32(ctx, &evt->buffer_req.max_buffer_us, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_BUFREQ_MAX_PLAYOUT_US: return JS_ToInt32(ctx, &evt->buffer_req.max_playout_us, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_BUFREQ_MIN_PLAYOUT_US: return JS_ToInt32(ctx, &evt->buffer_req.min_playout_us, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_BUFREQ_PID_ONLY: + evt->buffer_req.pid_only = JS_ToBool(ctx, value); + return JS_UNDEFINED; + + case JSF_EVENT_USER_TYPE: + if (JS_ToInt32(ctx, &ival, value)) return JS_EXCEPTION; + evt->user_event.event.type = (u8) ival; + return JS_UNDEFINED; + + + case JSF_EVENT_USER_MOUSE_X: + if (evt->user_event.event.type==GF_EVENT_MULTITOUCH) { + if (JS_ToFloat64(ctx, &dval, value)) return JS_EXCEPTION; + evt->user_event.event.mtouch.x = FLT2FIX(dval); + return JS_UNDEFINED; + } + return JS_ToInt32(ctx, &evt->user_event.event.mouse.x, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_USER_MOUSE_Y: + if (evt->user_event.event.type==GF_EVENT_MULTITOUCH) { + if (JS_ToFloat64(ctx, &dval, value)) return JS_EXCEPTION; + evt->user_event.event.mtouch.y = FLT2FIX(dval); + return JS_UNDEFINED; + } + return JS_ToInt32(ctx, &evt->user_event.event.mouse.y, value) ? JS_EXCEPTION : JS_UNDEFINED; + + case JSF_EVENT_USER_WHEEL: + if (JS_ToFloat64(ctx, &dval, value)) return JS_EXCEPTION; + evt->user_event.event.mouse.wheel_pos = FLT2FIX(dval); + return JS_UNDEFINED; + case JSF_EVENT_USER_BUTTON: return JS_ToInt32(ctx, &evt->user_event.event.mouse.button, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_USER_HWKEY: return JS_ToInt32(ctx, &evt->user_event.event.key.hw_code, value) ? JS_EXCEPTION : JS_UNDEFINED; + + case JSF_EVENT_USER_MT_ROTATION: + if (JS_ToFloat64(ctx, &dval, value)) return JS_EXCEPTION; + evt->user_event.event.mtouch.rotation = FLT2FIX(dval); + return JS_UNDEFINED; + case JSF_EVENT_USER_MT_PINCH: + if (JS_ToFloat64(ctx, &dval, value)) return JS_EXCEPTION; + evt->user_event.event.mtouch.pinch = FLT2FIX(dval); + return JS_UNDEFINED; + case JSF_EVENT_USER_MT_FINGERS: + return JS_ToInt32(ctx, &evt->user_event.event.mtouch.num_fingers, value) ? JS_EXCEPTION : JS_UNDEFINED; + + case JSF_EVENT_USER_TEXT: + { + str = JS_ToCString(ctx, value); + evt->user_event.event.clipboard.text = gf_strdup(str ? str : ""); + if (str) JS_FreeCString(ctx, str); + return JS_UNDEFINED; + } + + case JSF_EVENT_USER_WIDTH: return JS_ToInt32(ctx, &evt->user_event.event.size.width, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_USER_HEIGHT: return JS_ToInt32(ctx, &evt->user_event.event.size.height, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_USER_SHOWTYPE: return JS_ToInt32(ctx, &evt->user_event.event.show.show_type, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_USER_MOVE_X: return JS_ToInt32(ctx, &evt->user_event.event.move.x, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_USER_MOVE_Y: return JS_ToInt32(ctx, &evt->user_event.event.move.y, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_USER_MOVE_RELATIVE: return JS_ToInt32(ctx, &evt->user_event.event.move.relative, value) ? JS_EXCEPTION : JS_UNDEFINED; + case JSF_EVENT_USER_MOVE_ALIGN_X: + case JSF_EVENT_USER_MOVE_ALIGN_Y: + if (JS_ToInt32(ctx, &ival, value)) return JS_EXCEPTION; + if (magic==JSF_EVENT_USER_MOVE_ALIGN_X) + evt->user_event.event.move.align_x = ival; + else + evt->user_event.event.move.align_x = ival; + return JS_UNDEFINED; + + case JSF_EVENT_USER_CAPTION: + { + str = JS_ToCString(ctx, value); + evt->user_event.event.caption.caption = gf_strdup(str ? str : ""); + if (str) JS_FreeCString(ctx, str); + return JS_UNDEFINED; + } + } + + if (str) + JS_FreeCString(ctx, str); + if (e) return js_throw_err(ctx, e); + return JS_UNDEFINED; +} + +static JSValue jsf_event_get_prop(JSContext *ctx, JSValueConst this_val, int magic) +{ + GF_FilterEvent *evt = JS_GetOpaque(this_val, jsf_event_class_id); + if (!evt) return JS_EXCEPTION; + if (!jsf_check_evt(evt->base.type, evt->user_event.event.type, magic)) + return JS_EXCEPTION; + switch (magic) { + case JSF_EVENT_TYPE: return JS_NewInt32(ctx, evt->base.type); + case JSF_EVENT_NAME: + if (evt->base.type==GF_FEVT_USER) { + const char *ename=NULL; + switch (evt->user_event.event.type) { + case GF_EVENT_CLICK: ename = "click"; break; + case GF_EVENT_MOUSEUP: ename = "mouseup"; break; + case GF_EVENT_MOUSEDOWN: ename = "mousedown"; break; + case GF_EVENT_MOUSEMOVE: ename = "mousemove"; break; + case GF_EVENT_MOUSEWHEEL: ename = "mousewheel"; break; + case GF_EVENT_DBLCLICK: ename = "dblclick"; break; + case GF_EVENT_MULTITOUCH: ename = "touch"; break; + case GF_EVENT_KEYUP: ename = "keyup"; break; + case GF_EVENT_KEYDOWN: ename = "keydown"; break; + case GF_EVENT_TEXTINPUT: ename = "text"; break; + case GF_EVENT_DROPFILE: ename = "dropfile"; break; + case GF_EVENT_TIMESHIFT_DEPTH: ename = "timeshift_depth"; break; + case GF_EVENT_TIMESHIFT_UPDATE: ename = "timeshift_update"; break; + case GF_EVENT_TIMESHIFT_OVERFLOW: ename = "timeshift_overflow"; break; + case GF_EVENT_TIMESHIFT_UNDERRUN: ename = "timeshift_underrun"; break; + case GF_EVENT_PASTE_TEXT: ename = "paste_text"; break; + case GF_EVENT_COPY_TEXT: ename = "copy_text"; break; + case GF_EVENT_SIZE: ename = "size"; break; + case GF_EVENT_SHOWHIDE: ename = "showhide"; break; + case GF_EVENT_MOVE: ename = "move"; break; + case GF_EVENT_SET_CAPTION: ename = "caption"; break; + case GF_EVENT_REFRESH: ename = "refresh"; break; + case GF_EVENT_QUIT: ename = "quit"; break; + case GF_EVENT_VIDEO_SETUP: ename = "video_setup"; break; + default: + ename = "unknown"; break; + } + return JS_NewString(ctx, ename); + } + return JS_NewString(ctx, gf_filter_event_name(evt->base.type)); + /*PLAY*/ + case JSF_EVENT_START_RANGE: return JS_NewFloat64(ctx, evt->play.start_range); + case JSF_EVENT_SPEED: return JS_NewFloat64(ctx, evt->play.speed); + case JSF_EVENT_HW_BUFFER_RESET: return JS_NewBool(ctx, evt->play.hw_buffer_reset); + case JSF_EVENT_INITIAL_BROADCAST_PLAY: return JS_NewBool(ctx, evt->play.initial_broadcast_play); + case JSF_EVENT_TIMESTAMP_BASED: return JS_NewInt32(ctx, evt->play.timestamp_based); + case JSF_EVENT_FULL_FILE_ONLY: return JS_NewBool(ctx, evt->play.full_file_only); + case JSF_EVENT_FORCE_DASH_SEG_SWITCH: return JS_NewBool(ctx, evt->play.forced_dash_segment_switch); + case JSF_EVENT_FROM_PCK: return JS_NewInt32(ctx, evt->play.from_pck); + /*source switch*/ + case JSF_EVENT_START_OFFSET: return JS_NewInt64(ctx, evt->seek.start_offset); + case JSF_EVENT_END_OFFSET: return JS_NewInt64(ctx, evt->seek.end_offset); + case JSF_EVENT_SOURCE_SWITCH: return JS_NewString(ctx, evt->seek.source_switch); + case JSF_EVENT_SKIP_CACHE_EXPIRATION: return JS_NewBool(ctx, evt->seek.skip_cache_expiration); + case JSF_EVENT_HINT_BLOCK_SIZE: return JS_NewInt32(ctx, evt->seek.hint_block_size); + /*segment size*/ + case JSF_EVENT_SEG_URL: return JS_NewString(ctx, evt->seg_size.seg_url); + case JSF_EVENT_SEG_IS_INIT: return JS_NewBool(ctx, evt->seg_size.is_init); + case JSF_EVENT_MEDIA_START_RANGE: return JS_NewInt64(ctx, evt->seg_size.media_range_start); + case JSF_EVENT_MEDIA_END_RANGE: return JS_NewInt64(ctx, evt->seg_size.media_range_end); + case JSF_EVENT_IDX_START_RANGE: return JS_NewInt64(ctx, evt->seg_size.media_range_start); + case JSF_EVENT_IDX_END_RANGE: return JS_NewInt64(ctx, evt->seg_size.idx_range_end); + /*quality switch*/ + case JSF_EVENT_SWITCH_UP: return JS_NewBool(ctx, evt->quality_switch.up); + case JSF_EVENT_SWITCH_GROUP_IDX: return JS_NewInt32(ctx, evt->quality_switch.dependent_group_index); + case JSF_EVENT_SWITCH_QUALITY_IDX: return JS_NewInt32(ctx, evt->quality_switch.q_idx); + case JSF_EVENT_SWITCH_TILE_MODE: return JS_NewInt32(ctx, evt->quality_switch.set_tile_mode_plus_one); + case JSF_EVENT_SWITCH_QUALITY_DEGRADATION: return JS_NewInt32(ctx, evt->quality_switch.quality_degradation); + /*visibility hint*/ + case JSF_EVENT_VIS_MIN_X: return JS_NewInt32(ctx, evt->visibility_hint.min_x); + case JSF_EVENT_VIS_MIN_Y: return JS_NewInt32(ctx, evt->visibility_hint.min_y); + case JSF_EVENT_VIS_MAX_X: return JS_NewInt32(ctx, evt->visibility_hint.max_x); + case JSF_EVENT_VIS_MAX_Y: return JS_NewInt32(ctx, evt->visibility_hint.max_y); + case JSF_EVENT_VIS_IS_GAZE: return JS_NewBool(ctx, evt->visibility_hint.is_gaze); + /*buffer reqs*/ + case JSF_EVENT_BUFREQ_MAX_BUFFER_US: return JS_NewInt32(ctx, evt->buffer_req.max_buffer_us); + case JSF_EVENT_BUFREQ_MAX_PLAYOUT_US: return JS_NewInt32(ctx, evt->buffer_req.max_playout_us); + case JSF_EVENT_BUFREQ_MIN_PLAYOUT_US: return JS_NewInt32(ctx, evt->buffer_req.min_playout_us); + case JSF_EVENT_BUFREQ_PID_ONLY: return JS_NewBool(ctx, evt->buffer_req.pid_only); + /*user event*/ + case JSF_EVENT_USER_TYPE: return JS_NewInt32(ctx, evt->user_event.event.type); + case JSF_EVENT_USER_KEYCODE: +#ifndef GPAC_DISABLE_SVG + return JS_NewString(ctx, gf_dom_get_key_name(evt->user_event.event.key.key_code) ); +#else + return JS_NULL; +#endif + + case JSF_EVENT_USER_MOUSE_X: + if (evt->user_event.event.type==GF_EVENT_MULTITOUCH) + return JS_NewFloat64(ctx, FIX2FLT(evt->user_event.event.mtouch.x) ); + return JS_NewInt32(ctx, evt->user_event.event.mouse.x); + case JSF_EVENT_USER_MOUSE_Y: + if (evt->user_event.event.type==GF_EVENT_MULTITOUCH) + return JS_NewFloat64(ctx, FIX2FLT(evt->user_event.event.mtouch.y) ); + return JS_NewInt32(ctx, evt->user_event.event.mouse.y); + case JSF_EVENT_USER_WHEEL: return JS_NewFloat64(ctx, FIX2FLT(evt->user_event.event.mouse.wheel_pos)); + case JSF_EVENT_USER_BUTTON: return JS_NewInt32(ctx, evt->user_event.event.mouse.button); + case JSF_EVENT_USER_HWKEY: return JS_NewInt32(ctx, evt->user_event.event.key.hw_code); + case JSF_EVENT_USER_DROPFILES: + { + u32 i, idx; + JSValue files_array = JS_NewArray(ctx); + idx=0; + for (i=0; iuser_event.event.open_file.nb_files; i++) { + if (evt->user_event.event.open_file.files[i]) { + JS_SetPropertyUint32(ctx, files_array, idx, JS_NewString(ctx, evt->user_event.event.open_file.files[i]) ); + idx++; + } + } + return files_array; + } + case JSF_EVENT_USER_TEXT: + return JS_NewString(ctx, evt->user_event.event.clipboard.text ? evt->user_event.event.clipboard.text : ""); + + case JSF_EVENT_USER_MT_ROTATION: + return JS_NewFloat64(ctx, FIX2FLT(evt->user_event.event.mtouch.rotation) ); + case JSF_EVENT_USER_MT_PINCH: + return JS_NewFloat64(ctx, FIX2FLT(evt->user_event.event.mtouch.pinch) ); + case JSF_EVENT_USER_MT_FINGERS: + return JS_NewInt32(ctx, evt->user_event.event.mtouch.num_fingers); + case JSF_EVENT_USER_WIDTH: return JS_NewInt32(ctx, evt->user_event.event.size.width); + case JSF_EVENT_USER_HEIGHT: return JS_NewInt32(ctx, evt->user_event.event.size.height); + case JSF_EVENT_USER_SHOWTYPE: return JS_NewInt32(ctx, evt->user_event.event.show.show_type); + + case JSF_EVENT_USER_MOVE_X: return JS_NewInt32(ctx, evt->user_event.event.move.x); + case JSF_EVENT_USER_MOVE_Y: return JS_NewInt32(ctx, evt->user_event.event.move.y); + case JSF_EVENT_USER_MOVE_RELATIVE: return JS_NewInt32(ctx, evt->user_event.event.move.relative); + case JSF_EVENT_USER_MOVE_ALIGN_X: return JS_NewInt32(ctx, evt->user_event.event.move.align_x); + case JSF_EVENT_USER_MOVE_ALIGN_Y: return JS_NewInt32(ctx, evt->user_event.event.move.align_y); + + case JSF_EVENT_USER_CAPTION: + return JS_NewString(ctx, evt->user_event.event.caption.caption ? evt->user_event.event.caption.caption : ""); + } + return JS_UNDEFINED; +} + +GF_FilterEvent *jsf_get_event(JSContext *ctx, JSValueConst this_val) +{ + GF_FilterEvent *evt = JS_GetOpaque(this_val, jsf_event_class_id); + return evt; +} + +static const JSCFunctionListEntry jsf_event_funcs[] = +{ + JS_CGETSET_MAGIC_DEF("type", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_TYPE), + JS_CGETSET_MAGIC_DEF("name", jsf_event_get_prop, NULL, JSF_EVENT_NAME), + /*PLAY event*/ + JS_CGETSET_MAGIC_DEF("start_range", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_START_RANGE), + JS_CGETSET_MAGIC_DEF("speed", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SPEED), + JS_CGETSET_MAGIC_DEF("hw_buffer_reset", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_HW_BUFFER_RESET), + JS_CGETSET_MAGIC_DEF("initial_broadcast_play", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_INITIAL_BROADCAST_PLAY), + JS_CGETSET_MAGIC_DEF("timestamp_based", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_TIMESTAMP_BASED), + JS_CGETSET_MAGIC_DEF("full_file_only", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_FULL_FILE_ONLY), + JS_CGETSET_MAGIC_DEF("forced_dash_segment_switch", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_FORCE_DASH_SEG_SWITCH), + JS_CGETSET_MAGIC_DEF("from_pck", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_FROM_PCK), + /*source switch*/ + JS_CGETSET_MAGIC_DEF("start_offset", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_START_OFFSET), + JS_CGETSET_MAGIC_DEF("end_offset", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_END_OFFSET), + JS_CGETSET_MAGIC_DEF("switch_url", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SOURCE_SWITCH), + JS_CGETSET_MAGIC_DEF("skip_cache_exp", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SKIP_CACHE_EXPIRATION), + JS_CGETSET_MAGIC_DEF("hint_block_size", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_HINT_BLOCK_SIZE), + /*segment size*/ + JS_CGETSET_MAGIC_DEF("seg_url", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SEG_URL), + JS_CGETSET_MAGIC_DEF("is_init", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SEG_IS_INIT), + JS_CGETSET_MAGIC_DEF("media_start_range", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_MEDIA_START_RANGE), + JS_CGETSET_MAGIC_DEF("media_end_range", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_MEDIA_END_RANGE), + JS_CGETSET_MAGIC_DEF("index_start_range", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_IDX_START_RANGE), + JS_CGETSET_MAGIC_DEF("index_end_range", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_IDX_END_RANGE), + /*quality switch*/ + JS_CGETSET_MAGIC_DEF("up", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SWITCH_UP), + JS_CGETSET_MAGIC_DEF("dependent_group_index", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SWITCH_GROUP_IDX), + JS_CGETSET_MAGIC_DEF("q_idx", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SWITCH_QUALITY_IDX), + JS_CGETSET_MAGIC_DEF("set_tile_mode_plus_one", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SWITCH_TILE_MODE), + JS_CGETSET_MAGIC_DEF("quality_degradation", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_SWITCH_QUALITY_DEGRADATION), + /*visibility hint*/ + JS_CGETSET_MAGIC_DEF("min_x", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_VIS_MIN_X), + JS_CGETSET_MAGIC_DEF("min_y", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_VIS_MIN_Y), + JS_CGETSET_MAGIC_DEF("max_x", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_VIS_MAX_X), + JS_CGETSET_MAGIC_DEF("max_y", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_VIS_MAX_Y), + JS_CGETSET_MAGIC_DEF("is_gaze", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_VIS_IS_GAZE), + /*buffer reqs*/ + JS_CGETSET_MAGIC_DEF("max_buffer_us", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_BUFREQ_MAX_BUFFER_US), + JS_CGETSET_MAGIC_DEF("max_playout_us", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_BUFREQ_MAX_PLAYOUT_US), + JS_CGETSET_MAGIC_DEF("min_playout_us", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_BUFREQ_MIN_PLAYOUT_US), + JS_CGETSET_MAGIC_DEF("pid_only", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_BUFREQ_PID_ONLY), + /*ui events*/ + JS_CGETSET_MAGIC_DEF("ui_type", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_TYPE), + JS_CGETSET_MAGIC_DEF("keycode", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_KEYCODE), + JS_CGETSET_MAGIC_DEF("mouse_x", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOUSE_X), + JS_CGETSET_MAGIC_DEF("mouse_y", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOUSE_Y), + JS_CGETSET_MAGIC_DEF("wheel", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_WHEEL), + JS_CGETSET_MAGIC_DEF("button", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_BUTTON), + JS_CGETSET_MAGIC_DEF("hwkey", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_HWKEY), + JS_CGETSET_MAGIC_DEF("dropfiles", jsf_event_get_prop, NULL, JSF_EVENT_USER_DROPFILES), + JS_CGETSET_MAGIC_DEF("clipboard", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_TEXT), + + JS_CGETSET_MAGIC_DEF("mt_x", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOUSE_X), + JS_CGETSET_MAGIC_DEF("mt_y", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOUSE_Y), + JS_CGETSET_MAGIC_DEF("mt_rotate", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MT_ROTATION), + JS_CGETSET_MAGIC_DEF("mt_pinch", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MT_PINCH), + JS_CGETSET_MAGIC_DEF("mt_fingers", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MT_FINGERS), + + JS_CGETSET_MAGIC_DEF("width", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_WIDTH), + JS_CGETSET_MAGIC_DEF("height", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_HEIGHT), + JS_CGETSET_MAGIC_DEF("showtype", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_SHOWTYPE), + JS_CGETSET_MAGIC_DEF("move_x", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOVE_X), + JS_CGETSET_MAGIC_DEF("move_y", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOVE_Y), + JS_CGETSET_MAGIC_DEF("move_relative", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOVE_RELATIVE), + JS_CGETSET_MAGIC_DEF("move_alignx", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOVE_ALIGN_X), + JS_CGETSET_MAGIC_DEF("move_aligny", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_MOVE_ALIGN_Y), + JS_CGETSET_MAGIC_DEF("caption", jsf_event_get_prop, jsf_event_set_prop, JSF_EVENT_USER_CAPTION), +}; + +static JSValue jsf_event_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) +{ + GF_FilterEvent *evt; + JSValue obj; + s32 type; + if (argc!=1) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &type, argv[0])) + return JS_EXCEPTION; + if (!type) + return JS_EXCEPTION; + obj = JS_NewObjectClass(ctx, jsf_event_class_id); + if (JS_IsException(obj)) return obj; + + GF_SAFEALLOC(evt, GF_FilterEvent); + if (!evt) { + JS_FreeValue(ctx, obj); + return js_throw_err(ctx, GF_OUT_OF_MEM); + } + evt->base.type = type; + if (type==GF_FEVT_PLAY) + evt->play.speed = 1.0; + JS_SetOpaque(obj, evt); + return obj; +} + +enum +{ + JSF_PCK_START = 0, + JSF_PCK_END, + JSF_PCK_DTS, + JSF_PCK_CTS, + JSF_PCK_DUR, + JSF_PCK_SAP, + JSF_PCK_TIMESCALE, + JSF_PCK_INTERLACED, + JSF_PCK_SEEK, + JSF_PCK_CORRUPTED, + JSF_PCK_BYTE_OFFSET, + JSF_PCK_ROLL, + JSF_PCK_CRYPT, + JSF_PCK_CLOCK_TYPE, + JSF_PCK_CAROUSEL, + JSF_PCK_SEQNUM, + JSF_PCK_BLOCKING_REF, + JSF_PCK_DEPENDS_ON, + JSF_PCK_DEPENDED_ON, + JSF_PCK_IS_LEADING, + JSF_PCK_HAS_REDUNDANT, + JSF_PCK_SIZE, + JSF_PCK_DATA, + JSF_PCK_FRAME_IFCE, +}; + +static JSValue jsf_pck_set_prop(JSContext *ctx, JSValueConst this_val, JSValueConst value, int magic) +{ + GF_Err e = GF_OK; + u32 ival, flags; + u64 lival; + Bool a1, a2; + const char *str=NULL; + GF_FilterPacket *pck; + GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); + if (!pckctx || !pckctx->pck) return JS_EXCEPTION; + pck = pckctx->pck; + + switch (magic) { + case JSF_PCK_START: + gf_filter_pck_get_framing(pck, &a1, &a2); + gf_filter_pck_set_framing(pck, JS_ToBool(ctx, value), a2); + break; + case JSF_PCK_END: + gf_filter_pck_get_framing(pck, &a1, &a2); + gf_filter_pck_set_framing(pck, a1, JS_ToBool(ctx, value)); + break; + case JSF_PCK_DTS: + if (JS_IsNull(value)) + gf_filter_pck_set_dts(pck, GF_FILTER_NO_TS); + else { + if (JS_ToInt64(ctx, &lival, value)) return JS_EXCEPTION; + gf_filter_pck_set_dts(pck, lival); + } + break; + case JSF_PCK_CTS: + if (JS_IsNull(value)) + gf_filter_pck_set_cts(pck, GF_FILTER_NO_TS); + else { + if (JS_ToInt64(ctx, &lival, value)) return JS_EXCEPTION; + gf_filter_pck_set_cts(pck, lival); + } + break; + case JSF_PCK_DUR: + if (JS_ToInt32(ctx, &ival, value)) return JS_EXCEPTION; + gf_filter_pck_set_duration(pck, ival); + break; + case JSF_PCK_SAP: + if (JS_ToInt32(ctx, &ival, value)) return JS_EXCEPTION; + gf_filter_pck_set_sap(pck, ival); + break; + case JSF_PCK_INTERLACED: + if (JS_ToInt32(ctx, &ival, value)) return JS_EXCEPTION; + gf_filter_pck_set_interlaced(pck, ival); + break; + case JSF_PCK_SEEK: + gf_filter_pck_set_seek_flag(pck, JS_ToBool(ctx, value)); + break; + case JSF_PCK_CORRUPTED: + gf_filter_pck_set_corrupted(pck, JS_ToBool(ctx, value)); + break; + case JSF_PCK_BYTE_OFFSET: + if (JS_IsNull(value)) + gf_filter_pck_set_byte_offset(pck, GF_FILTER_NO_BO); + else { + if (JS_ToInt64(ctx, &lival, value)) return JS_EXCEPTION; + gf_filter_pck_set_byte_offset(pck, lival); + } + break; + case JSF_PCK_ROLL: + if (JS_ToInt32(ctx, &ival, value)) return JS_EXCEPTION; + gf_filter_pck_set_roll_info(pck, (s16) ival); + break; + case JSF_PCK_CRYPT: + if (JS_ToInt32(ctx, &ival, value)) return JS_EXCEPTION; + gf_filter_pck_set_crypt_flags(pck, ival); + break; + case JSF_PCK_CLOCK_TYPE: + if (JS_ToInt32(ctx, &ival, value)) return JS_EXCEPTION; + gf_filter_pck_set_clock_type(pck, ival); + break; + case JSF_PCK_CAROUSEL: + if (JS_ToInt32(ctx, &ival, value)) return JS_EXCEPTION; + gf_filter_pck_set_carousel_version(pck, ival); + break; + case JSF_PCK_SEQNUM: + if (JS_ToInt32(ctx, &ival, value)) return JS_EXCEPTION; + gf_filter_pck_set_seq_num(pck, ival); + break; + case JSF_PCK_IS_LEADING: + if (JS_ToInt32(ctx, &ival, value)) return JS_EXCEPTION; + flags = gf_filter_pck_get_dependency_flags(pck); + flags &= 0x3F; + flags |= (ival & 0x3)<<6; + gf_filter_pck_set_seq_num(pck, flags); + break; + case JSF_PCK_DEPENDS_ON: + if (JS_ToInt32(ctx, &ival, value)) return JS_EXCEPTION; + flags = gf_filter_pck_get_dependency_flags(pck); + flags &= 0xCF; + flags |= (ival & 0x3)<<4; + gf_filter_pck_set_seq_num(pck, flags); + break; + case JSF_PCK_DEPENDED_ON: + if (JS_ToInt32(ctx, &ival, value)) return JS_EXCEPTION; + flags = gf_filter_pck_get_dependency_flags(pck); + flags &= 0xF3; + flags |= (ival & 0x3)<<2; + gf_filter_pck_set_seq_num(pck, flags); + break; + case JSF_PCK_HAS_REDUNDANT: + if (JS_ToInt32(ctx, &ival, value)) return JS_EXCEPTION; + flags = gf_filter_pck_get_dependency_flags(pck); + flags &= 0xFC; + flags |= (ival & 0x3); + gf_filter_pck_set_seq_num(pck, flags); + break; + } + if (str) + JS_FreeCString(ctx, str); + if (e) return js_throw_err(ctx, e); + return JS_UNDEFINED; +} + +static JSValue jsf_pck_get_prop(JSContext *ctx, JSValueConst this_val, int magic) +{ + Bool a1, a2; + u64 lival; + u32 ival; + GF_FilterPacket *pck; + GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); + if (!pckctx || !pckctx->pck) return JS_EXCEPTION; + pck = pckctx->pck; + + switch (magic) { + case JSF_PCK_START: + gf_filter_pck_get_framing(pck, &a1, &a2); + return JS_NewBool(ctx, a1); + case JSF_PCK_END: + gf_filter_pck_get_framing(pck, &a1, &a2); + return JS_NewBool(ctx, a2); + case JSF_PCK_DTS: + lival = gf_filter_pck_get_dts(pck); + if (lival==GF_FILTER_NO_TS) return JS_NULL; + return JS_NewInt64(ctx, lival); + case JSF_PCK_CTS: + lival = gf_filter_pck_get_cts(pck); + if (lival==GF_FILTER_NO_TS) return JS_NULL; + return JS_NewInt64(ctx, lival); + case JSF_PCK_DUR: + return JS_NewInt32(ctx, gf_filter_pck_get_duration(pck)); + case JSF_PCK_SAP: + return JS_NewInt32(ctx, gf_filter_pck_get_sap(pck)); + case JSF_PCK_TIMESCALE: + return JS_NewInt32(ctx, gf_filter_pck_get_timescale(pck)); + case JSF_PCK_INTERLACED: + return JS_NewInt32(ctx, gf_filter_pck_get_interlaced(pck)); + case JSF_PCK_SEEK: + return JS_NewBool(ctx, gf_filter_pck_get_seek_flag(pck)); + case JSF_PCK_CORRUPTED: + return JS_NewBool(ctx, gf_filter_pck_get_corrupted(pck)); + case JSF_PCK_BYTE_OFFSET: + lival = gf_filter_pck_get_byte_offset(pck); + if (lival==GF_FILTER_NO_TS) return JS_NULL; + return JS_NewInt64(ctx, lival); + case JSF_PCK_ROLL: + return JS_NewInt32(ctx, gf_filter_pck_get_roll_info(pck)); + case JSF_PCK_CRYPT: + return JS_NewInt32(ctx, gf_filter_pck_get_crypt_flags(pck)); + case JSF_PCK_CLOCK_TYPE: + return JS_NewInt32(ctx, gf_filter_pck_get_clock_type(pck)); + case JSF_PCK_CAROUSEL: + return JS_NewInt32(ctx, gf_filter_pck_get_carousel_version(pck)); + case JSF_PCK_SEQNUM: + return JS_NewInt32(ctx, gf_filter_pck_get_seq_num(pck)); + case JSF_PCK_BLOCKING_REF: + return JS_NewBool(ctx, gf_filter_pck_is_blocking_ref(pck)); + case JSF_PCK_IS_LEADING: + ival = gf_filter_pck_get_dependency_flags(pck); + return JS_NewBool(ctx, (ival>>6) & 0x3); + case JSF_PCK_DEPENDS_ON: + ival = gf_filter_pck_get_dependency_flags(pck); + return JS_NewBool(ctx, (ival>>4) & 0x3); + case JSF_PCK_DEPENDED_ON: + ival = gf_filter_pck_get_dependency_flags(pck); + return JS_NewBool(ctx, (ival>>2) & 0x3); + case JSF_PCK_HAS_REDUNDANT: + ival = gf_filter_pck_get_dependency_flags(pck); + return JS_NewBool(ctx, (ival) & 0x3); + case JSF_PCK_SIZE: + gf_filter_pck_get_data(pck, &ival); + return JS_NewInt32(ctx, ival); + case JSF_PCK_DATA: + if (JS_IsUndefined(pckctx->data_ab)) { + const u8 *data = gf_filter_pck_get_data(pck, &ival); + if (!data) return JS_NULL; + pckctx->data_ab = JS_NewArrayBuffer(ctx, (u8 *) data, ival, NULL, NULL, GF_TRUE); + } + return JS_DupValue(ctx, pckctx->data_ab); + case JSF_PCK_FRAME_IFCE: + if (gf_filter_pck_get_frame_interface(pck) != NULL) return JS_NewBool(ctx, 1); + else return JS_NewBool(ctx, 0); + } + return JS_UNDEFINED; +} + +static JSValue jsf_pck_set_readonly(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_FilterPacket *pck; + GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); + if (!pckctx || !pckctx->pck) return JS_EXCEPTION; + pck = pckctx->pck; + + gf_filter_pck_set_readonly(pck); + return JS_UNDEFINED; +} +static JSValue jsf_pck_enum_properties(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + s32 idx; + u32 p4cc; + const char *pname; + JSValue res; + const GF_PropertyValue *prop; + GF_FilterPacket *pck; + GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); + if (!pckctx || !pckctx->pck) return JS_EXCEPTION; + pck = pckctx->pck; + + if (JS_ToInt32(ctx, &idx, argv[0])) + return JS_EXCEPTION; + + prop = gf_filter_pck_enum_properties(pck, &idx, &p4cc, &pname); + if (!prop) return JS_NULL; + if (!pname) pname = gf_props_4cc_get_name(p4cc); + if (!pname) return JS_EXCEPTION; + + res = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, res, "name", JS_NewString(ctx, pname)); + JS_SetPropertyStr(ctx, res, "type", JS_NewInt32(ctx, prop->type)); + JS_SetPropertyStr(ctx, res, "value", jsf_NewProp(ctx, prop)); + return res; +} + +static JSValue jsf_pck_get_property(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + JSValue res; + const char *name=NULL; + const GF_PropertyValue *prop; + GF_FilterPacket *pck; + GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); + if (!pckctx || !pckctx->pck) return JS_EXCEPTION; + pck = pckctx->pck; + + name = JS_ToCString(ctx, argv[0]); + if (!name) return JS_EXCEPTION; + if ((argc>1) && JS_ToBool(ctx, argv[1])) { + prop = gf_filter_pck_get_property_str(pck, name); + JS_FreeCString(ctx, name); + if (!prop) return JS_NULL; + res = jsf_NewProp(ctx, prop); + JS_SetPropertyStr(ctx, res, "type", JS_NewInt32(ctx, prop->type)); + } else { + u32 p4cc = gf_props_get_id(name); + JS_FreeCString(ctx, name); + if (!p4cc) + return js_throw_err(ctx, GF_BAD_PARAM); + + prop = gf_filter_pck_get_property(pck, p4cc); + if (!prop) return JS_NULL; + res = jsf_NewPropTranslate(ctx, prop, p4cc); + } + return res; +} + +static JSValue jsf_pck_ref(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + Bool is_ref_props = GF_FALSE; + GF_JSPckCtx *ref_pckctx; + GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); + if (!pckctx || !pckctx->pck) return JS_EXCEPTION; + + if (argc && JS_ToBool(ctx, argv[0])) is_ref_props = GF_TRUE; + + ref_pckctx = gf_list_pop_back(pckctx->jspid->jsf->pck_res); + if (!ref_pckctx) { + GF_SAFEALLOC(ref_pckctx, GF_JSPckCtx); + if (!ref_pckctx) + return js_throw_err(ctx, GF_OUT_OF_MEM); + } + memcpy(ref_pckctx, pckctx, sizeof(GF_JSPckCtx)); + if (is_ref_props) + gf_filter_pck_ref_props(&ref_pckctx->pck); + else { + gf_filter_pck_ref(&ref_pckctx->pck); + } + ref_pckctx->flags = GF_JS_PCK_IS_REF; + ref_pckctx->jsobj = JS_NewObjectClass(ctx, jsf_pck_class_id); + ref_pckctx->data_ab = JS_UNDEFINED; + ref_pckctx->ref_val = JS_UNDEFINED; + JS_SetOpaque(ref_pckctx->jsobj, ref_pckctx); + return JS_DupValue(ctx, ref_pckctx->jsobj); +} + +static JSValue jsf_pck_unref(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); + if (!pckctx || !pckctx->pck) return JS_EXCEPTION; + if (!(pckctx->flags & GF_JS_PCK_IS_REF)) + return js_throw_err_msg(ctx, GF_BAD_PARAM, "Attempt to unref a non-reference packet"); + + gf_filter_pck_unref(pckctx->pck); + pckctx->pck = NULL; + JS_FreeValue(ctx, pckctx->jsobj); + JS_SetOpaque(this_val, NULL); + gf_list_add(pckctx->jspid->jsf->pck_res, pckctx); + memset(pckctx, 0, sizeof(GF_JSPckCtx)); + return JS_UNDEFINED; +} + +static JSValue jsf_pck_send(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_FilterPacket *pck; + GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); + if (!pckctx || !pckctx->pck) return JS_EXCEPTION; + pck = pckctx->pck; + if (!JS_IsUndefined(pckctx->data_ab)) { + JS_FreeValue(ctx, pckctx->data_ab); + pckctx->data_ab = JS_UNDEFINED; + } + gf_filter_pck_send(pck); + JS_SetOpaque(this_val, NULL); + if (!(pckctx->flags & GF_JS_PCK_IS_SHARED)) { + gf_list_add(pckctx->jspid->jsf->pck_res, pckctx); + memset(pckctx, 0, sizeof(GF_JSPckCtx)); + } + return JS_UNDEFINED; +} +static JSValue jsf_pck_discard(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_FilterPacket *pck; + GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); + if (!pckctx || !pckctx->pck) return JS_EXCEPTION; + pck = pckctx->pck; + pckctx->pck = NULL; + gf_filter_pck_discard(pck); + return JS_UNDEFINED; +} + +static JSValue jsf_pck_set_property(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_Err e; + GF_PropertyValue prop; + const char *name=NULL; + GF_FilterPacket *pck; + GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); + if (!pckctx || !pckctx->pck) return JS_EXCEPTION; + pck = pckctx->pck; + + name = JS_ToCString(ctx, argv[0]); + if (!name) return JS_EXCEPTION; + + if ((argc>2) && JS_ToBool(ctx, argv[2])) { + if (!JS_IsNull(argv[2])) { + e = jsf_ToProp(pckctx->jspid->jsf->filter, ctx, argv[1], 0, &prop); + JS_FreeCString(ctx, name); + if (e) return js_throw_err(ctx, e); + e = gf_filter_pck_set_property_dyn(pck, (char *) name, &prop); + gf_props_reset_single(&prop); + } else { + e = gf_filter_pck_set_property_dyn(pck, (char *) name, NULL); + } + } else { + u32 p4cc = gf_props_get_id(name); + JS_FreeCString(ctx, name); + if (!p4cc) return js_throw_err(ctx, GF_OUT_OF_MEM); + if (!!JS_IsNull(argv[1])) { + e = jsf_ToProp(pckctx->jspid->jsf->filter, ctx, argv[1], p4cc, &prop); + if (e) return js_throw_err(ctx, e); + e = gf_filter_pck_set_property(pck, p4cc, &prop); + gf_props_reset_single(&prop); + } else { + e = gf_filter_pck_set_property(pck, p4cc, NULL); + } + } + if (e) return js_throw_err(ctx, e); + return JS_UNDEFINED; +} + +static JSValue jsf_pck_append_data(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + u8 *data_start=NULL; + u32 data_size=0; + u8 *new_start; + GF_Err e; + GF_FilterPacket *pck; + GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); + if (!pckctx || !pckctx->pck || !argc) return JS_EXCEPTION; + pck = pckctx->pck; + + if (JS_IsString(argv[0]) || JS_IsInteger(argv[0])) { + u32 len; + const char *str = NULL; + + if (JS_IsInteger(argv[0])) { + JS_ToInt32(ctx, &len, argv[0]); + } else { + str = JS_ToCString(ctx, argv[0]); + if (!str) return JS_EXCEPTION; + len = (u32) strlen(str); + } + + e = gf_filter_pck_expand(pck, len, &data_start, &new_start, &data_size); + if (!new_start || e) { + if (str) JS_FreeCString(ctx, str); + return js_throw_err(ctx, e); + } + if (str) { + memcpy(new_start, str, len); + JS_FreeCString(ctx, str); + } + + jsf_pck_detach_ab(ctx, pckctx); + return JS_NewArrayBuffer(ctx, (u8 *) new_start, len, NULL, NULL, GF_TRUE); + } + + if (!JS_IsObject(argv[0])) return JS_EXCEPTION; + + size_t ab_size; + u8 *data = JS_GetArrayBuffer(ctx, &ab_size, argv[0]); + if (!data) + return JS_EXCEPTION; + + e = gf_filter_pck_expand(pck, (u32) ab_size, &data_start, &new_start, &data_size); + if (!new_start || e) { + return js_throw_err(ctx, e); + } + memcpy(new_start, data, ab_size); + + jsf_pck_detach_ab(ctx, pckctx); + + return JS_NewArrayBuffer(ctx, (u8 *) new_start, ab_size, NULL, NULL, GF_TRUE); +} + +static JSValue jsf_pck_truncate(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + u32 len=0; + GF_Err e; + GF_FilterPacket *pck; + GF_JSPckCtx *pckctx = JS_GetOpaque(this_val, jsf_pck_class_id); + if (!pckctx || !pckctx->pck) return JS_EXCEPTION; + pck = pckctx->pck; + if (argc) { + JS_ToInt32(ctx, &len, argv[0]); + } + e = gf_filter_pck_truncate(pck, len); + if (e) return js_throw_err(ctx, e); + + jsf_pck_detach_ab(ctx, pckctx); + return JS_UNDEFINED; +} +static JSValue jsf_pck_copy_props(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + GF_Err e; + GF_JSPckCtx *pck_dst = JS_GetOpaque(this_val, jsf_pck_class_id); + if (!pck_dst || !pck_dst->pck || !argc) + return JS_EXCEPTION; + GF_JSPckCtx *pck_from = JS_GetOpaque(argv[0], jsf_pck_class_id); + if (!pck_from || !pck_from->pck) + return JS_EXCEPTION; + e = gf_filter_pck_merge_properties(pck_from->pck, pck_dst->pck); + if (e) return js_throw_err(ctx, e); + return JS_UNDEFINED; +} + +static const JSCFunctionListEntry jsf_pck_funcs[] = +{ + JS_CGETSET_MAGIC_DEF("start", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_START), + JS_CGETSET_MAGIC_DEF("end", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_END), + JS_CGETSET_MAGIC_DEF("dts", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_DTS), + JS_CGETSET_MAGIC_DEF("cts", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_CTS), + JS_CGETSET_MAGIC_DEF("dur", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_DUR), + JS_CGETSET_MAGIC_DEF("sap", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_SAP), + JS_CGETSET_MAGIC_DEF("timescale", jsf_pck_get_prop, NULL, JSF_PCK_TIMESCALE), + JS_CGETSET_MAGIC_DEF("interlaced", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_INTERLACED), + JS_CGETSET_MAGIC_DEF("corrupted", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_CORRUPTED), + JS_CGETSET_MAGIC_DEF("seek", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_SEEK), + JS_CGETSET_MAGIC_DEF("byte_offset", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_BYTE_OFFSET), + JS_CGETSET_MAGIC_DEF("roll", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_ROLL), + JS_CGETSET_MAGIC_DEF("crypt", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_CRYPT), + JS_CGETSET_MAGIC_DEF("clock_type", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_CLOCK_TYPE), + JS_CGETSET_MAGIC_DEF("carousel", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_CAROUSEL), + JS_CGETSET_MAGIC_DEF("seqnum", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_SEQNUM), + JS_CGETSET_MAGIC_DEF("blocking_ref", jsf_pck_get_prop, NULL, JSF_PCK_BLOCKING_REF), + JS_CGETSET_MAGIC_DEF("is_leading", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_IS_LEADING), + JS_CGETSET_MAGIC_DEF("depends_on", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_DEPENDS_ON), + JS_CGETSET_MAGIC_DEF("depended_on", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_DEPENDED_ON), + JS_CGETSET_MAGIC_DEF("redundant", jsf_pck_get_prop, jsf_pck_set_prop, JSF_PCK_HAS_REDUNDANT), + JS_CGETSET_MAGIC_DEF("size", jsf_pck_get_prop, NULL, JSF_PCK_SIZE), + JS_CGETSET_MAGIC_DEF("data", jsf_pck_get_prop, NULL, JSF_PCK_DATA), + JS_CGETSET_MAGIC_DEF("frame_ifce", jsf_pck_get_prop, NULL, JSF_PCK_DATA), + + JS_CFUNC_DEF("set_readonly", 0, jsf_pck_set_readonly), + JS_CFUNC_DEF("enum_properties", 0, jsf_pck_enum_properties), + JS_CFUNC_DEF("get_prop", 0, jsf_pck_get_property), + JS_CFUNC_DEF("ref", 0, jsf_pck_ref), + JS_CFUNC_DEF("unref", 0, jsf_pck_unref), + JS_CFUNC_DEF("send", 0, jsf_pck_send), + JS_CFUNC_DEF("discard", 0, jsf_pck_discard), + JS_CFUNC_DEF("set_prop", 0, jsf_pck_set_property), + JS_CFUNC_DEF("append", 0, jsf_pck_append_data), + JS_CFUNC_DEF("truncate", 0, jsf_pck_truncate), + JS_CFUNC_DEF("copy_props", 0, jsf_pck_copy_props), +}; + + +static GF_Err jsfilter_process(GF_Filter *filter) +{ + JSValue ret; + GF_Err e = GF_OK; + GF_JSFilterCtx *jsf = gf_filter_get_udta(filter); + if (!jsf) return GF_BAD_PARAM; + + gf_js_lock(jsf->ctx, GF_TRUE); + ret = JS_Call(jsf->ctx, jsf->funcs[JSF_EVT_PROCESS], jsf->filter_obj, 0, NULL); + if (JS_IsException(ret)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[%s] Error processing\n", jsf->log_name)); + js_dump_error(jsf->ctx); + e = GF_BAD_PARAM; + } + else if (JS_IsInteger(ret)) + JS_ToInt32(jsf->ctx, (int*)&e, ret); + + JS_FreeValue(jsf->ctx, ret); + gf_js_lock(jsf->ctx, GF_FALSE); + + js_do_loop(jsf->ctx); + return e; +} + + + +static GF_Err jsfilter_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + JSValue ret; + GF_Err e = GF_OK; + GF_JSFilterCtx *jsf = gf_filter_get_udta(filter); + GF_JSPidCtx *pctx; + + if (!jsf) return GF_BAD_PARAM; + + pctx = gf_filter_pid_get_udta(pid); + + if (is_remove) { + assert(pctx); + gf_js_lock(jsf->ctx, GF_TRUE); + ret = JS_Call(jsf->ctx, jsf->funcs[JSF_EVT_REMOVE_PID], jsf->filter_obj, 1, &pctx->jsobj); + if (JS_IsException(ret)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[%s] Error removing pid\n", jsf->log_name)); + js_dump_error(jsf->ctx); + e = GF_BAD_PARAM; + } + else if (JS_IsInteger(ret)) + JS_ToInt32(jsf->ctx, (int*)&e, ret); + JS_FreeValue(jsf->ctx, ret); + JS_FreeValue(jsf->ctx, pctx->jsobj); + gf_list_del_item(jsf->pids, pctx); + gf_filter_pid_set_udta(pid, NULL); + gf_free(pctx); + gf_js_lock(jsf->ctx, GF_FALSE); + js_do_loop(jsf->ctx); + return e; + } + + gf_js_lock(jsf->ctx, GF_TRUE); + + if (!pctx) { + GF_SAFEALLOC(pctx, GF_JSPidCtx); + if (!pctx) return GF_OUT_OF_MEM; + + pctx->jsf = jsf; + pctx->pid = pid; + pctx->jsobj = JS_NewObjectClass(jsf->ctx, jsf_pid_class_id); + gf_filter_pid_set_udta(pid, pctx); + gf_list_add(jsf->pids, pctx); + JS_SetOpaque(pctx->jsobj, pctx); + } + + ret = JS_Call(jsf->ctx, jsf->funcs[JSF_EVT_CONFIGURE_PID], jsf->filter_obj, 1, &pctx->jsobj); + if (JS_IsException(ret)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[%s] Error configure pid\n", jsf->log_name)); + js_dump_error(jsf->ctx); + e = GF_BAD_PARAM; + } + else if (JS_IsInteger(ret)) + JS_ToInt32(jsf->ctx, (int*)&e, ret); + JS_FreeValue(jsf->ctx, ret); + + gf_js_lock(jsf->ctx, GF_FALSE); + js_do_loop(jsf->ctx); + return e; +} + +void js_load_constants(JSContext *ctx, JSValue global_obj) +{ + JSValue val; + + val = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, val, "log", JS_NewCFunction(ctx, js_print, "log", 1)); + JS_SetPropertyStr(ctx, global_obj, "console", val); + +#define DEF_CONST( _val ) \ + JS_SetPropertyStr(ctx, global_obj, #_val, JS_NewInt32(ctx, _val)); + + DEF_CONST(GF_LOG_ERROR) + DEF_CONST(GF_LOG_WARNING) + DEF_CONST(GF_LOG_INFO) + DEF_CONST(GF_LOG_DEBUG) + + DEF_CONST(GF_PROP_BOOL) + DEF_CONST(GF_PROP_UINT) + DEF_CONST(GF_PROP_SINT) + DEF_CONST(GF_PROP_LUINT) + DEF_CONST(GF_PROP_LSINT) + DEF_CONST(GF_PROP_FRACTION) + DEF_CONST(GF_PROP_FRACTION64) + DEF_CONST(GF_PROP_FLOAT) + DEF_CONST(GF_PROP_DOUBLE) + DEF_CONST(GF_PROP_VEC2I) + DEF_CONST(GF_PROP_VEC2) + DEF_CONST(GF_PROP_VEC3I) + DEF_CONST(GF_PROP_VEC3) + DEF_CONST(GF_PROP_VEC4I) + DEF_CONST(GF_PROP_VEC4) + DEF_CONST(GF_PROP_PCMFMT) + DEF_CONST(GF_PROP_PIXFMT) + DEF_CONST(GF_PROP_STRING) + DEF_CONST(GF_PROP_STRING) + DEF_CONST(GF_PROP_STRING_NO_COPY) + DEF_CONST(GF_PROP_DATA) + DEF_CONST(GF_PROP_NAME) + DEF_CONST(GF_PROP_DATA_NO_COPY) + DEF_CONST(GF_PROP_CONST_DATA) + DEF_CONST(GF_PROP_POINTER) + DEF_CONST(GF_PROP_STRING_LIST) + DEF_CONST(GF_PROP_UINT_LIST) + + + DEF_CONST(GF_FEVT_PLAY) + DEF_CONST(GF_FEVT_SET_SPEED) + DEF_CONST(GF_FEVT_STOP) + DEF_CONST(GF_FEVT_PAUSE) + DEF_CONST(GF_FEVT_RESUME) + DEF_CONST(GF_FEVT_SOURCE_SEEK) + DEF_CONST(GF_FEVT_SOURCE_SWITCH) + DEF_CONST(GF_FEVT_SEGMENT_SIZE) + DEF_CONST(GF_FEVT_QUALITY_SWITCH) + DEF_CONST(GF_FEVT_VISIBILITY_HINT) + DEF_CONST(GF_FEVT_INFO_UPDATE) + DEF_CONST(GF_FEVT_BUFFER_REQ) + DEF_CONST(GF_FEVT_CAPS_CHANGE) + DEF_CONST(GF_FEVT_CONNECT_FAIL) + DEF_CONST(GF_FEVT_USER) + + DEF_CONST(GF_STATS_LOCAL) + DEF_CONST(GF_STATS_LOCAL_INPUTS) + DEF_CONST(GF_STATS_DECODER_SINK) + DEF_CONST(GF_STATS_DECODER_SOURCE) + DEF_CONST(GF_STATS_ENCODER_SINK) + DEF_CONST(GF_STATS_ENCODER_SOURCE) + + DEF_CONST(GF_FILTER_CLOCK_NONE) + DEF_CONST(GF_FILTER_CLOCK_PCR) + DEF_CONST(GF_FILTER_CLOCK_PCR_DISC) + + DEF_CONST(GF_FILTER_SAP_NONE) + DEF_CONST(GF_FILTER_SAP_1) + DEF_CONST(GF_FILTER_SAP_2) + DEF_CONST(GF_FILTER_SAP_3) + DEF_CONST(GF_FILTER_SAP_4) + DEF_CONST(GF_FILTER_SAP_4_PROL) + + DEF_CONST(GF_EOS); + DEF_CONST(GF_OK) + DEF_CONST(GF_BAD_PARAM) + DEF_CONST(GF_OUT_OF_MEM) + DEF_CONST(GF_IO_ERR) + DEF_CONST(GF_NOT_SUPPORTED) + DEF_CONST(GF_CORRUPTED_DATA) + DEF_CONST(GF_SG_UNKNOWN_NODE) + DEF_CONST(GF_SG_INVALID_PROTO) + DEF_CONST(GF_SCRIPT_ERROR) + DEF_CONST(GF_BUFFER_TOO_SMALL) + DEF_CONST(GF_NON_COMPLIANT_BITSTREAM) + DEF_CONST(GF_FILTER_NOT_FOUND) + DEF_CONST(GF_URL_ERROR) + DEF_CONST(GF_SERVICE_ERROR) + DEF_CONST(GF_REMOTE_SERVICE_ERROR) + DEF_CONST(GF_STREAM_NOT_FOUND) + DEF_CONST(GF_ISOM_INVALID_FILE) + DEF_CONST(GF_ISOM_INCOMPLETE_FILE) + DEF_CONST(GF_ISOM_INVALID_MEDIA) + DEF_CONST(GF_ISOM_INVALID_MODE) + DEF_CONST(GF_ISOM_UNKNOWN_DATA_REF) + DEF_CONST(GF_ODF_INVALID_DESCRIPTOR) + DEF_CONST(GF_ODF_FORBIDDEN_DESCRIPTOR) + DEF_CONST(GF_ODF_INVALID_COMMAND) + DEF_CONST(GF_BIFS_UNKNOWN_VERSION) + DEF_CONST(GF_IP_ADDRESS_NOT_FOUND) + DEF_CONST(GF_IP_CONNECTION_FAILURE) + DEF_CONST(GF_IP_NETWORK_FAILURE) + DEF_CONST(GF_IP_CONNECTION_CLOSED) + DEF_CONST(GF_IP_NETWORK_EMPTY) + DEF_CONST(GF_IP_SOCK_WOULD_BLOCK) + DEF_CONST(GF_IP_UDP_TIMEOUT) + DEF_CONST(GF_AUTHENTICATION_FAILURE) + DEF_CONST(GF_SCRIPT_NOT_READY) + DEF_CONST(GF_INVALID_CONFIGURATION) + DEF_CONST(GF_NOT_FOUND) + DEF_CONST(GF_PROFILE_NOT_SUPPORTED) + DEF_CONST(GF_REQUIRES_NEW_INSTANCE) + DEF_CONST(GF_FILTER_NOT_SUPPORTED) + + DEF_CONST(JSF_SETUP_ERROR) + DEF_CONST(JSF_NOTIF_ERROR) + DEF_CONST(JSF_NOTIF_ERROR_AND_DISCONNECT) + + DEF_CONST(GF_FILTER_UPDATE_DOWNSTREAM) + DEF_CONST(GF_FILTER_UPDATE_UPSTREAM) + + DEF_CONST(GF_EVENT_CLICK) + DEF_CONST(GF_EVENT_MOUSEUP) + DEF_CONST(GF_EVENT_MOUSEDOWN) + DEF_CONST(GF_EVENT_MOUSEMOVE) + DEF_CONST(GF_EVENT_MOUSEWHEEL) + DEF_CONST(GF_EVENT_DBLCLICK) + DEF_CONST(GF_EVENT_MULTITOUCH) + DEF_CONST(GF_EVENT_KEYUP) + DEF_CONST(GF_EVENT_KEYDOWN) + DEF_CONST(GF_EVENT_TEXTINPUT) + DEF_CONST(GF_EVENT_DROPFILE) + DEF_CONST(GF_EVENT_TIMESHIFT_DEPTH) + DEF_CONST(GF_EVENT_TIMESHIFT_UPDATE) + DEF_CONST(GF_EVENT_TIMESHIFT_OVERFLOW) + DEF_CONST(GF_EVENT_TIMESHIFT_UNDERRUN) + DEF_CONST(GF_EVENT_PASTE_TEXT) + DEF_CONST(GF_EVENT_COPY_TEXT) + DEF_CONST(GF_EVENT_SIZE) + DEF_CONST(GF_EVENT_SHOWHIDE) + DEF_CONST(GF_EVENT_MOVE) + DEF_CONST(GF_EVENT_SET_CAPTION) + DEF_CONST(GF_EVENT_REFRESH) + DEF_CONST(GF_EVENT_QUIT) + + JS_SetPropertyStr(ctx, global_obj, "print", JS_NewCFunction(ctx, js_print, "print", 1)); + JS_SetPropertyStr(ctx, global_obj, "alert", JS_NewCFunction(ctx, js_print, "alert", 1)); + + + //initialize filter event class + JS_NewClassID(&jsf_event_class_id); + JS_NewClass(JS_GetRuntime(ctx), jsf_event_class_id, &jsf_event_class); + JSValue evt_proto = JS_NewObjectClass(ctx, jsf_event_class_id); + JS_SetPropertyFunctionList(ctx, evt_proto, jsf_event_funcs, countof(jsf_event_funcs)); + JS_SetClassProto(ctx, jsf_event_class_id, evt_proto); + + JSValue evt_ctor = JS_NewCFunction2(ctx, jsf_event_constructor, "FilterEvent", 1, JS_CFUNC_constructor, 0); + JS_SetPropertyStr(ctx, global_obj, "FilterEvent", evt_ctor); + +} + +static GF_Err jsfilter_initialize(GF_Filter *filter) +{ + u8 *buf; + u32 buf_len; + u32 flags = JS_EVAL_TYPE_GLOBAL; + JSValue ret; + JSValue global_obj; + u32 i; + JSRuntime *rt; + GF_JSFilterCtx *jsf = gf_filter_get_udta(filter); + + jsf->filter = filter; + jsf->pids = gf_list_new(); + jsf->pck_res = gf_list_new(); + jsf->log_name = gf_strdup("JSF"); + + if (!jsf->js) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[JSF] Missing script file\n")); + return GF_BAD_PARAM; + } + if (!gf_file_exists(jsf->js)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[JSF] Script file %s does not exist\n", jsf->js)); + return GF_BAD_PARAM; + } + jsf->filter_obj = JS_UNDEFINED; + + jsf->ctx = gf_js_create_context(); + if (!jsf->ctx) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[JSF] Failed to load QuickJS context\n")); + return GF_IO_ERR; + } + JS_SetContextOpaque(jsf->ctx, jsf); + rt = JS_GetRuntime(jsf->ctx); + + global_obj = JS_GetGlobalObject(jsf->ctx); + + js_load_constants(jsf->ctx, global_obj); + + + //initialize filter class and create a single filter object in global scope + JS_NewClassID(&jsf_filter_class_id); + JS_NewClass(rt, jsf_filter_class_id, &jsf_filter_class); + + jsf->filter_obj = JS_NewObjectClass(jsf->ctx, jsf_filter_class_id); + JS_SetPropertyFunctionList(jsf->ctx, jsf->filter_obj, jsf_filter_funcs, countof(jsf_filter_funcs)); + JS_SetOpaque(jsf->filter_obj, jsf); + JS_SetPropertyStr(jsf->ctx, global_obj, "filter", jsf->filter_obj); + + //initialize filter instance class + JS_NewClassID(&jsf_filter_inst_class_id); + JS_NewClass(rt, jsf_filter_inst_class_id, &jsf_filter_inst_class); + JSValue finst_proto = JS_NewObjectClass(jsf->ctx, jsf_filter_inst_class_id); + JS_SetPropertyFunctionList(jsf->ctx, finst_proto, jsf_filter_inst_funcs, countof(jsf_filter_inst_funcs)); + JS_SetClassProto(jsf->ctx, jsf_filter_inst_class_id, finst_proto); + + //initialize filter pid class + JS_NewClassID(&jsf_pid_class_id); + JS_NewClass(rt, jsf_pid_class_id, &jsf_pid_class); + JSValue pid_proto = JS_NewObjectClass(jsf->ctx, jsf_pid_class_id); + JS_SetPropertyFunctionList(jsf->ctx, pid_proto, jsf_pid_funcs, countof(jsf_pid_funcs)); + JS_SetClassProto(jsf->ctx, jsf_pid_class_id, pid_proto); + + + //initialize filter event class + JS_NewClassID(&jsf_pck_class_id); + JS_NewClass(rt, jsf_pck_class_id, &jsf_pck_class); + JSValue pck_proto = JS_NewObjectClass(jsf->ctx, jsf_pck_class_id); + JS_SetPropertyFunctionList(jsf->ctx, pck_proto, jsf_pck_funcs, countof(jsf_pck_funcs)); + JS_SetClassProto(jsf->ctx, jsf_pck_class_id, pck_proto); + + JS_SetPropertyStr(jsf->ctx, global_obj, "_gpac_log_name", JS_NewString(jsf->ctx, gf_file_basename(jsf->js) ) ); + JS_FreeValue(jsf->ctx, global_obj); + + + //load script + GF_Err e = gf_file_load_data(jsf->js, &buf, &buf_len); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[JSF] Error loading script file %s: %s\n", jsf->js, gf_error_to_string(e) )); + return e; + } + + if (strstr(buf, "session.")) { + GF_Err gf_fs_load_js_api(JSContext *c, GF_FilterSession *fs); +// GF_FilterSession *fs = sjs->compositor->filter->session; + + e = gf_fs_load_js_api(jsf->ctx, filter->session); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[JSF] Error loading session API: %s\n", gf_error_to_string(e) )); + return e; + } + jsf->unload_session_api = GF_TRUE; + } + + for (i=0; ifuncs[i] = JS_UNDEFINED; + } + + + if (!gf_opts_get_bool("core", "no-js-mods") && JS_DetectModule((char *)buf, buf_len)) { + //init modules + qjs_module_init_gpaccore(jsf->ctx); + qjs_module_init_xhr(jsf->ctx); + qjs_module_init_evg(jsf->ctx); + qjs_module_init_storage(jsf->ctx); + qjs_module_init_webgl(jsf->ctx); + flags = JS_EVAL_TYPE_MODULE; + } + jsf->disable_filter = GF_TRUE; + ret = JS_Eval(jsf->ctx, (char *)buf, buf_len, jsf->js, flags); + gf_free(buf); + + if (JS_IsException(ret)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[JSF] Error loading script %s\n", jsf->js)); + js_dump_error(jsf->ctx); + JS_FreeValue(jsf->ctx, ret); + return GF_BAD_PARAM; + } + JS_FreeValue(jsf->ctx, ret); + return GF_OK; +} + +static void jsfilter_finalize(GF_Filter *filter) +{ + u32 i, count; + GF_JSFilterCtx *jsf = gf_filter_get_udta(filter); + if (!jsf->ctx) return; + + //reset references but do not destroy PIDs yet + count = gf_list_count(jsf->pids); + for (i=0; ipids, i); + JS_FreeValue(jsf->ctx, pctx->jsobj); + if (pctx->shared_pck) { + while (gf_list_count(pctx->shared_pck)) { + GF_JSPckCtx *pckc = gf_list_pop_back(pctx->shared_pck); + JS_FreeValue(jsf->ctx, pckc->ref_val); + pckc->ref_val = JS_UNDEFINED; + jsf_pck_detach_ab(jsf->ctx, pckc); + + //do not free here since pck->jsobj may already have been GCed/destroyed + } + } + } + + for (i=0; ictx, jsf->funcs[i]); + } + + JS_SetOpaque(jsf->filter_obj, NULL); + + if (jsf->unload_session_api) + gf_fs_unload_script(filter->session, jsf->ctx); + + gf_js_delete_context(jsf->ctx); + + while (gf_list_count(jsf->pids)) { + GF_JSPidCtx *pctx = gf_list_pop_back(jsf->pids); + if (pctx->shared_pck) + gf_list_del(pctx->shared_pck); + gf_free(pctx); + } + gf_list_del(jsf->pids); + + if (jsf->log_name) gf_free(jsf->log_name); + + while (gf_list_count(jsf->pck_res)) { + GF_JSPckCtx *pck = gf_list_pop_back(jsf->pck_res); + gf_free(pck); + } + gf_list_del(jsf->pck_res); + + if (jsf->args) { + for (i=0; inb_args; i++) { + if (jsf->args[i].arg_default_val) + gf_free((char *) jsf->args[i].arg_default_val); + if (jsf->args[i].arg_desc) + gf_free((char *) jsf->args[i].arg_desc); + if (jsf->args[i].arg_name) + gf_free((char *) jsf->args[i].arg_name); + if (jsf->args[i].min_max_enum) + gf_free((char *) jsf->args[i].min_max_enum); + } + gf_free(jsf->args); + } + + if (jsf->caps) gf_free(jsf->caps); +} + +static GF_Err jsfilter_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *new_val) +{ + GF_JSFilterCtx *jsf = gf_filter_get_udta(filter); + JSValue ret, val; + const GF_FilterArgs *the_arg = NULL; + u32 i; + GF_Err e = GF_OK; + if (!jsf->ctx) + return GF_OK; + + if (!arg_name && !new_val) { + gf_js_lock(jsf->ctx, GF_TRUE); + if (JS_IsFunction(jsf->ctx, jsf->funcs[JSF_EVT_INITIALIZE]) ) { + ret = JS_Call(jsf->ctx, jsf->funcs[JSF_EVT_INITIALIZE], jsf->filter_obj, 0, NULL); + if (JS_IsException(ret)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[%s] Error initializing filter\n", jsf->log_name)); + js_dump_error(jsf->ctx); + JS_FreeValue(jsf->ctx, ret); + gf_js_lock(jsf->ctx, GF_FALSE); + return GF_BAD_PARAM; + } + if (JS_IsInteger(ret)) + JS_ToInt32(jsf->ctx, (int*)&e, ret); + + JS_FreeValue(jsf->ctx, ret); + } + jsf->initialized = GF_TRUE; + //filter object not used (no new_pid, post_task or set_cap), disable it + if (jsf->disable_filter) { + JSAtom prop; + JSValue global_obj = JS_GetGlobalObject(jsf->ctx); + prop = JS_NewAtom(jsf->ctx, "filter"); + JS_DeleteProperty(jsf->ctx, global_obj, prop, 0); + JS_FreeValue(jsf->ctx, global_obj); + JS_FreeAtom(jsf->ctx, prop); + filter->disabled = GF_TRUE; + jsf->filter_obj = JS_UNDEFINED; + } + gf_js_lock(jsf->ctx, GF_FALSE); + return e; + } + if (!arg_name || (jsf->initialized && jsf->disable_filter)) + return GF_OK; + + for (i=0; inb_args; i++) { + if (jsf->args[i].arg_name && !strcmp(jsf->args[i].arg_name, arg_name)) { + the_arg = &jsf->args[i]; + break; + } + } + if (!the_arg && !jsf->has_wilcard_arg) return GF_OK; + + gf_js_lock(jsf->ctx, GF_TRUE); + + val = jsf_NewProp(jsf->ctx, new_val); + if (!jsf->initialized) { + JS_SetPropertyStr(jsf->ctx, jsf->filter_obj, arg_name, val); + gf_js_lock(jsf->ctx, GF_FALSE); + return GF_OK; + } + + JSValue args[2]; + args[0] = JS_NewString(jsf->ctx, arg_name); + args[1] = val; + ret = JS_Call(jsf->ctx, jsf->funcs[JSF_EVT_UPDATE_ARG], jsf->filter_obj, 2, args); + if (JS_IsException(ret)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[%s] Error updating arg\n", jsf->log_name)); + js_dump_error(jsf->ctx); + e = GF_BAD_PARAM; + } + if (JS_IsInteger(ret)) + JS_ToInt32(jsf->ctx, (int*)&e, ret); + JS_FreeValue(jsf->ctx, ret); + JS_FreeValue(jsf->ctx, args[0]); + JS_FreeValue(jsf->ctx, args[1]); + gf_js_lock(jsf->ctx, GF_FALSE); + return e; +} + +JSValue js_init_evt_obj(JSContext *ctx, const GF_FilterEvent *evt) +{ + JSValue v = JS_NewObjectClass(ctx, jsf_event_class_id); + JS_SetOpaque(v, (void *) evt); + return v; +} + +static Bool jsfilter_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + JSValue argv[2]; + JSValue ret; + Bool canceled=GF_TRUE; + GF_JSFilterCtx *jsf = gf_filter_get_udta(filter); + if (!jsf) return GF_TRUE; + + if (!JS_IsFunction(jsf->ctx, jsf->funcs[JSF_EVT_PROCESS_EVENT])) + return GF_FALSE; + + gf_js_lock(jsf->ctx, GF_TRUE); + if (evt->base.on_pid) { + GF_JSPidCtx *pctx = gf_filter_pid_get_udta(evt->base.on_pid); + argv[0] = pctx ? JS_DupValue(jsf->ctx, pctx->jsobj) : JS_NULL; + } else { + argv[0] = JS_NULL; + } + argv[1] = js_init_evt_obj(jsf->ctx, evt); + + ret = JS_Call(jsf->ctx, jsf->funcs[JSF_EVT_PROCESS_EVENT], jsf->filter_obj, 2, argv); + if (JS_IsException(ret)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_SCRIPT, ("[%s] Error processing event\n", jsf->log_name)); + js_dump_error(jsf->ctx); + } + else { + canceled = JS_ToBool(jsf->ctx, ret); + } + JS_FreeValue(jsf->ctx, ret); + JS_FreeValue(jsf->ctx, argv[0]); + JS_SetOpaque(argv[1], NULL); + JS_FreeValue(jsf->ctx, argv[1]); + gf_js_lock(jsf->ctx, GF_FALSE); + + js_do_loop(jsf->ctx); + return canceled; +} + +/*static GF_Err jsfilter_reconfigure_output(GF_Filter *filter, GF_FilterPid *PID) +{ + return GF_NOT_SUPPORTED; +} + +static GF_FilterProbeScore jsfilter_probe_url(const char *url, const char *mime) +{ + return GF_FPROBE_NOT_SUPPORTED; +} + +static const char * jsfilter_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + return NULL; +} +*/ + +#define OFFS(_n) #_n, offsetof(GF_JSFilterCtx, _n) +static GF_FilterArgs JSFilterArgs[] = +{ + { OFFS(js), "location of script source", GF_PROP_NAME, NULL, NULL, 0}, + { "*", -1, "any possible options defined for the script. See `gpac -hx jsf:js=$YOURSCRIPT`", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_META}, + {0} +}; + +static const GF_FilterCapability JSFilterCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_UNKNOWN), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_UNKNOWN), +}; + +GF_FilterRegister JSFilterRegister = { + .name = "jsf", + GF_FS_SET_DESCRIPTION("JavaScript filter") + GF_FS_SET_HELP("This filter runs a javascript file specified in [-js]() defining a new JavaScript filter.\n" + " \n" + "For more information on how to use JS filters, please check https://wiki.gpac.io/jsfilter\n") + .private_size = sizeof(GF_JSFilterCtx), + .flags = GF_FS_REG_SCRIPT, + .args = JSFilterArgs, + SETCAPS(JSFilterCaps), + .initialize = jsfilter_initialize, + .finalize = jsfilter_finalize, + .configure_pid = jsfilter_configure_pid, + .process = jsfilter_process, + .update_arg = jsfilter_update_arg, + .process_event = jsfilter_process_event, +// .probe_url = jsfilter_probe_url, +// .probe_data = jsfilter_probe_data +// .reconfigure_output = jsfilter_reconfigure_output +}; + + +const GF_FilterRegister *jsfilter_register(GF_FilterSession *session) +{ + return &JSFilterRegister; +} + +#else + +const GF_FilterRegister *jsfilter_register(GF_FilterSession *session) +{ + return NULL; +} + +#endif + diff --git a/modules/ctx_load/ctx_load.c b/src/filters/load_bt_xmt.c similarity index 57% rename from modules/ctx_load/ctx_load.c rename to src/filters/load_bt_xmt.c index 9e3767e..732fe8c 100644 --- a/modules/ctx_load/ctx_load.c +++ b/src/filters/load_bt_xmt.c @@ -2,10 +2,10 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * - * This file is part of GPAC / GPAC Scene Context loader module + * This file is part of GPAC / Scene Context loader filter * * GPAC is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -23,7 +23,7 @@ * */ -#include +#include #include #include #include @@ -33,11 +33,19 @@ typedef struct { + //opts + Bool progressive; + u32 sax_dur; + + //internal + GF_FilterPid *in_pid, *out_pid; + GF_Scene *scene; - GF_Terminal *app; + const char *file_name; + + GF_SceneManager *ctx; GF_SceneLoader load; - char *file_name; u64 file_size; u32 load_flags; u32 nb_streams; @@ -48,56 +56,16 @@ typedef struct GF_List *files_to_delete; /*progressive loading support for XMT X3D*/ FILE *src; - u32 file_pos, sax_max_duration; - Bool progressive_support; + u32 file_pos; + u64 pck_time; const char *service_url; + Bool is_playing; } CTXLoadPriv; -static GF_Err CTXLoad_GetCapabilities(GF_BaseDecoder *plug, GF_CodecCapability *cap) -{ - cap->cap.valueInt = 0; - return GF_NOT_SUPPORTED; -} +void ODS_SetupOD(GF_Scene *scene, GF_ObjectDescriptor *od); -static GF_Err CTXLoad_SetCapabilities(GF_BaseDecoder *plug, const GF_CodecCapability capability) -{ - return GF_OK; -} - -static void ODS_SetupOD(GF_Scene *scene, GF_ObjectDescriptor *od) -{ - GF_ObjectManager *odm; - odm = gf_scene_find_odm(scene, od->objectDescriptorID); - /*remove the old OD*/ - if (odm) gf_odm_disconnect(odm, 1); - odm = gf_odm_new(); - odm->OD = od; - odm->term = scene->root_od->term; - odm->parentscene = scene; - gf_mx_p(scene->mx_resources); - gf_list_add(scene->resources, odm); - gf_mx_v(scene->mx_resources); - - /*locate service owner*/ - gf_odm_setup_object(odm, scene->root_od->net_service); -} - - -static void CTXLoad_Reset(CTXLoadPriv *priv) -{ - if (priv->ctx) gf_sm_del(priv->ctx); - priv->ctx = NULL; - gf_sg_reset(priv->scene->graph); - if (priv->load_flags != 3) priv->load_flags = 0; - while (gf_list_count(priv->files_to_delete)) { - char *fileName = (char*)gf_list_get(priv->files_to_delete, 0); - gf_list_rem(priv->files_to_delete, 0); - gf_delete_file(fileName); - gf_free(fileName); - } -} static void CTXLoad_ExecuteConditional(M_Conditional *c, GF_Scene *scene) { @@ -135,7 +103,7 @@ static void CTXLoad_OnReverseActivate(GF_Node *node, GF_Route *route) CTXLoad_ExecuteConditional(c, scene); } -void CTXLoad_NodeCallback(void *cbk, u32 type, GF_Node *node, void *param) +void CTXLoad_NodeCallback(void *cbk, GF_SGNodeCbkType type, GF_Node *node, void *param) { if ((type==GF_SG_CALLBACK_INIT) && (gf_node_get_tag(node) == TAG_MPEG4_Conditional) ) { M_Conditional*c = (M_Conditional*)node; @@ -143,39 +111,13 @@ void CTXLoad_NodeCallback(void *cbk, u32 type, GF_Node *node, void *param) c->on_reverseActivate = CTXLoad_OnReverseActivate; gf_node_set_private(node, cbk); } else { - gf_term_node_callback(cbk, type, node, param); + gf_scene_node_callback(cbk, type, node, param); } } -static Bool CTXLoad_CheckDownload(CTXLoadPriv *priv) +static GF_Err CTXLoad_Setup(GF_Filter *filter, CTXLoadPriv *priv) { - u64 size; - FILE *f; - u32 now = gf_sys_clock(); - - if (!priv->file_size && (now - priv->last_check_time < 1000) ) return GF_FALSE; - - f = gf_fopen(priv->file_name, "rt"); - if (!f) return GF_FALSE; - gf_fseek(f, 0, SEEK_END); - size = gf_ftell(f); - gf_fclose(f); - - /*we MUST have a complete file for now ...*/ - if (!priv->file_size) { - if (priv->last_check_size == size) return GF_TRUE; - priv->last_check_size = size; - priv->last_check_time = now; - } else { - if (size==priv->file_size) return GF_TRUE; - } - return GF_FALSE; -} - - -static GF_Err CTXLoad_Setup(GF_BaseDecoder *plug) -{ - CTXLoadPriv *priv = (CTXLoadPriv *)plug->privateStack; + const GF_PropertyValue *prop; if (!priv->file_name) return GF_BAD_PARAM; priv->ctx = gf_sm_new(priv->scene->graph); @@ -186,19 +128,71 @@ static GF_Err CTXLoad_Setup(GF_BaseDecoder *plug) priv->load.fileName = priv->file_name; priv->load.src_url = priv->service_url; priv->load.flags = GF_SM_LOAD_FOR_PLAYBACK; - priv->load.localPath = gf_modules_get_option((GF_BaseInterface *)plug, "General", "CacheDirectory"); + priv->load.localPath = gf_get_default_cache_directory(); + priv->load.swf_import_flags = GF_SM_SWF_STATIC_DICT | GF_SM_SWF_QUAD_CURVE | GF_SM_SWF_SCALABLE_LINE | GF_SM_SWF_SPLIT_TIMELINE; + + if (!priv->files_to_delete) + priv->files_to_delete = gf_list_new(); + + prop = gf_filter_pid_get_property(priv->in_pid, GF_PROP_PID_MIME); + if (prop && prop->value.string) { + if (!strcmp(prop->value.string, "application/x-bt")) priv->load.type = GF_SM_LOAD_BT; + else if (!strcmp(prop->value.string, "application/x-xmt")) priv->load.type = GF_SM_LOAD_XMTA; + else if (!strcmp(prop->value.string, "model/vrml")) priv->load.type = GF_SM_LOAD_VRML; + else if (!strcmp(prop->value.string, "x-model/x-vrml")) priv->load.type = GF_SM_LOAD_VRML; + else if (!strcmp(prop->value.string, "model/x3d+vrml")) priv->load.type = GF_SM_LOAD_X3DV; + else if (!strcmp(prop->value.string, "model/x3d+xml")) priv->load.type = GF_SM_LOAD_X3D; + else if (!strcmp(prop->value.string, "application/x-shockwave-flash")) priv->load.type = GF_SM_LOAD_SWF; + else if (!strcmp(prop->value.string, "application/x-LASeR+xml")) priv->load.type = GF_SM_LOAD_XSR; + } + return GF_OK; } -static GF_Err CTXLoad_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd) +GF_Err ctxload_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) { - const char *ext; - GF_BitStream *bs; - u32 size; - CTXLoadPriv *priv = (CTXLoadPriv *)plug->privateStack; - if (esd->decoderConfig->upstream) return GF_NOT_SUPPORTED; + CTXLoadPriv *priv = gf_filter_get_udta(filter); + const GF_PropertyValue *prop; + + if (is_remove) { + priv->in_pid = NULL; + gf_filter_pid_remove(priv->out_pid); + priv->out_pid = NULL; + return GF_OK; + } + + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + //we must have a file path + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_FILEPATH); + if (!prop || ! prop->value.string) { + return GF_NOT_SUPPORTED; + } + + if (!priv->in_pid) { + GF_FilterEvent fevt; + priv->in_pid = pid; + + //we work with full file only, send a play event on source to indicate that + GF_FEVT_INIT(fevt, GF_FEVT_PLAY, pid); + fevt.play.start_range = 0; + fevt.base.on_pid = priv->in_pid; + fevt.play.full_file_only = GF_TRUE; + gf_filter_pid_send_event(priv->in_pid, &fevt); + } else { + if (pid != priv->in_pid) { + return GF_REQUIRES_NEW_INSTANCE; + } + //update of PID filename + if (!prop->value.string || !priv->file_name || strcmp(prop->value.string, priv->file_name)) + return GF_NOT_SUPPORTED; + return GF_OK; + } + +#ifdef FILTER_FIXME /*animation stream like*/ if (priv->ctx) { GF_StreamContext *sc; @@ -211,67 +205,107 @@ static GF_Err CTXLoad_AttachStream(GF_BaseDecoder *plug, GF_ESD *esd) } return GF_NON_COMPLIANT_BITSTREAM; } - /*main dummy stream we need a dsi*/ - if (!esd->decoderConfig->decoderSpecificInfo) - return GF_NON_COMPLIANT_BITSTREAM; - bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ); - priv->file_size = gf_bs_read_u32(bs); - gf_bs_del(bs); - size = esd->decoderConfig->decoderSpecificInfo->dataLength - sizeof(u32); - priv->file_name = (char *) gf_malloc(sizeof(char)*(1 + size) ); - memcpy(priv->file_name, esd->decoderConfig->decoderSpecificInfo->data + sizeof(u32), sizeof(char)*(esd->decoderConfig->decoderSpecificInfo->dataLength - sizeof(u32)) ); - priv->file_name[size] = 0; +#endif + + priv->file_name = prop->value.string; priv->nb_streams = 1; - priv->load_flags = 0; - priv->base_stream_id = esd->ESID; - priv->service_url = esd->service_url; + //declare a new output PID of type scene, codecid RAW + priv->out_pid = gf_filter_pid_new(filter); - CTXLoad_Setup(plug); + gf_filter_pid_copy_properties(priv->out_pid, pid); + gf_filter_pid_set_property(priv->out_pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_SCENE) ); + gf_filter_pid_set_property(priv->out_pid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_property(priv->out_pid, GF_PROP_PID_IN_IOD, &PROP_BOOL(GF_TRUE) ); - priv->progressive_support = GF_FALSE; - priv->sax_max_duration = 0; + gf_filter_pid_set_udta(pid, priv->out_pid); - ext = strrchr(priv->file_name, '.'); - if (!ext) return GF_OK; - ext++; - if (!stricmp(ext, "xmt") || !stricmp(ext, "xmtz") || !stricmp(ext, "xmta") - || !stricmp(ext, "x3d") || !stricmp(ext, "x3dz") - ) { - ext = gf_modules_get_option((GF_BaseInterface *)plug, "SAXLoader", "Progressive"); - priv->progressive_support = (ext && !stricmp(ext, "yes")) ? GF_TRUE : GF_FALSE; - } - if (priv->progressive_support) { - ext = gf_modules_get_option((GF_BaseInterface *)plug, "SAXLoader", "MaxDuration"); - if (ext) priv->sax_max_duration = atoi(ext); + priv->file_size = 0; + priv->load_flags = 0; + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ESID); + priv->base_stream_id = prop ? prop->value.uint : -1; + + priv->pck_time = -1; + + + switch (priv->load.type) { + case GF_SM_LOAD_BT: + gf_filter_set_name(filter, "Load:BT"); + break; + case GF_SM_LOAD_VRML: + gf_filter_set_name(filter, "Load:VRML97"); + break; + case GF_SM_LOAD_X3DV: + gf_filter_set_name(filter, "Load:X3D+vrml"); + break; + case GF_SM_LOAD_XMTA: + gf_filter_set_name(filter, "Load:XMTA"); + break; + case GF_SM_LOAD_X3D: + gf_filter_set_name(filter, "Load:X3D+XML Syntax"); + break; + case GF_SM_LOAD_SWF: + gf_filter_set_name(filter, "Load:SWF"); + break; + case GF_SM_LOAD_XSR: + gf_filter_set_name(filter, "Load:LASeRML"); + break; + case GF_SM_LOAD_MP4: + gf_filter_set_name(filter, "Load:MP4BIFSMemory"); + break; + default: + break; } - return GF_OK; -} -static GF_Err CTXLoad_DetachStream(GF_BaseDecoder *plug, u16 ES_ID) -{ - CTXLoadPriv *priv = (CTXLoadPriv *)plug->privateStack; - priv->nb_streams --; return GF_OK; } -static GF_Err CTXLoad_AttachScene(GF_SceneDecoder *plug, GF_Scene *scene, Bool is_scene_decoder) +static Bool ctxload_process_event(GF_Filter *filter, const GF_FilterEvent *com) { - CTXLoadPriv *priv = (CTXLoadPriv *)plug->privateStack; - if (priv->ctx) return GF_BAD_PARAM; + u32 count, i; + CTXLoadPriv *priv = gf_filter_get_udta(filter); + //check for scene attach + switch (com->base.type) { + case GF_FEVT_PLAY: + //cancel play event, we work with full file + //TODO: animation stream in BT + priv->is_playing = GF_TRUE; + return GF_TRUE; + case GF_FEVT_ATTACH_SCENE: + break; + case GF_FEVT_RESET_SCENE: + gf_sm_load_done(&priv->load); + if (priv->ctx) gf_sm_del(priv->ctx); + priv->ctx = NULL; + priv->load_flags = 3; + return GF_FALSE; + default: + return GF_FALSE; + } + if (!com->attach_scene.on_pid) return GF_TRUE; - priv->scene = scene; - priv->app = scene->root_od->term; - gf_sg_set_node_callback(scene->graph, CTXLoad_NodeCallback); + count = gf_filter_get_ipid_count(filter); + for (i=0; iattach_scene.on_pid) { + if (!priv->scene) { + GF_ObjectManager *odm = com->attach_scene.object_manager; + priv->scene = odm->subscene ? odm->subscene : odm->parentscene; + gf_sg_set_node_callback(priv->scene->graph, CTXLoad_NodeCallback); - return GF_OK; -} + priv->service_url = odm->scene_ns->url; -static GF_Err CTXLoad_ReleaseScene(GF_SceneDecoder *plug) -{ - CTXLoad_Reset((CTXLoadPriv *) plug->privateStack); - return GF_OK; + if (!priv->ctx) CTXLoad_Setup(filter, priv); + + } + return GF_TRUE; + } + } + + return GF_FALSE; } static Bool CTXLoad_StreamInRootOD(GF_ObjectDescriptor *od, u32 ESID) @@ -304,11 +338,12 @@ Double CTXLoad_GetVRMLTime(void *cbk) static void CTXLoad_CheckStreams(CTXLoadPriv *priv ) { u32 i, j, max_dur; - GF_AUContext *au; GF_StreamContext *sc; + u32 nb_aus=0; max_dur = 0; i=0; while ((sc = (GF_StreamContext *)gf_list_enum(priv->ctx->streams, &i))) { + GF_AUContext *au; /*all streams in root OD are handled with ESID 0 to differentiate with any animation streams*/ if (CTXLoad_StreamInRootOD(priv->ctx->root_od, sc->ESID)) sc->in_root_od = GF_TRUE; if (!sc->timeScale) sc->timeScale = 1000; @@ -316,6 +351,8 @@ static void CTXLoad_CheckStreams(CTXLoadPriv *priv ) j=0; while ((au = (GF_AUContext *)gf_list_enum(sc->AUs, &j))) { if (!au->timing) au->timing = (u64) (sc->timeScale*au->timing_sec); + if (gf_list_count(au->commands)) + nb_aus++; } if (au && sc->in_root_od && (au->timing>max_dur)) max_dur = (u32) (au->timing * 1000 / sc->timeScale); } @@ -323,51 +360,80 @@ static void CTXLoad_CheckStreams(CTXLoadPriv *priv ) priv->scene->root_od->duration = max_dur; gf_scene_set_duration(priv->scene); } + if ((priv->load_flags==1) && priv->ctx->root_od && priv->ctx->root_od->URLString) { + gf_filter_pid_set_property(priv->out_pid, GF_PROP_PID_REMOTE_URL, &PROP_STRING(priv->ctx->root_od->URLString) ); + } + if ((priv->load_flags==2) && !nb_aus) { + gf_filter_pid_set_eos(priv->out_pid); + } } -static GF_Err CTXLoad_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u32 inBufferLength, - u16 ES_ID, u32 stream_time, u32 mmlevel) +static GF_Err ctxload_process(GF_Filter *filter) { GF_Err e = GF_OK; - u32 i, j, k, nb_updates, last_rap=0; + GF_FilterPacket *pck; + u32 i, j, k, updates_pending, last_rap=0; GF_AUContext *au; Bool can_delete_com; GF_StreamContext *sc; - CTXLoadPriv *priv = (CTXLoadPriv *)plug->privateStack; + Bool is_seek = GF_FALSE; + Bool is_start, is_end; + u32 min_next_time_ms = 0; + CTXLoadPriv *priv = gf_filter_get_udta(filter); + + //not yet ready + if (!priv->scene) { + if (priv->is_playing) { + gf_filter_pid_set_eos(priv->out_pid); + return GF_EOS; + } + return GF_OK; + } /*something failed*/ if (priv->load_flags==3) return GF_EOS; /*this signals main scene deconnection, destroy the context if needed*/ - assert(ES_ID); if (!priv->ctx) { - e = CTXLoad_Setup((GF_BaseDecoder *)plug); + e = CTXLoad_Setup(filter, priv); if (e) return e; } + is_start = is_end = GF_FALSE; + pck = gf_filter_pid_get_packet(priv->in_pid); + if (pck) { + //source is FILE, untimed - consider we init from 0 + u64 cts = 0; + if ((s64)priv->pck_time<0) priv->pck_time = cts; + else if (priv->pck_time!=cts) { + priv->pck_time = cts; + is_seek = GF_TRUE; + } + //init clocks + gf_odm_check_buffering(priv->scene->root_od, priv->in_pid); + gf_filter_pck_get_framing(pck, &is_start, &is_end); + gf_filter_pid_drop_packet(priv->in_pid); + } - if (stream_time==(u32)-1) { + if (is_seek) { /*seek on root stream: destroy the context manager and reload it. We cannot seek on the main stream - because commands may have changed node attributes/children and we d'ont track the initial value*/ - if (priv->load_flags && (priv->base_stream_id == ES_ID)) { + because commands may have changed node attributes/children and we don't track the initial value*/ + if (priv->load_flags) { if (priv->src) gf_fclose(priv->src); priv->src = NULL; gf_sm_load_done(&priv->load); priv->file_pos = 0; - /*queue scene for detach*/ - gf_term_lock_media_queue(priv->scene->root_od->term, GF_TRUE); - priv->scene->root_od->action_type = GF_ODM_ACTION_SCENE_RECONNECT; - gf_list_add(priv->scene->root_od->term->media_queue, priv->scene->root_od); - gf_term_lock_media_queue(priv->scene->root_od->term, GF_FALSE); - return CTXLoad_Setup((GF_BaseDecoder *)plug); + return CTXLoad_Setup(filter, priv); } i=0; while ((sc = (GF_StreamContext *)gf_list_enum(priv->ctx->streams, &i))) { +#ifdef FILTER_FIXME /*not our stream*/ if (!sc->in_root_od && (sc->ESID != ES_ID)) continue; /*not the base stream*/ if (sc->in_root_od && (priv->base_stream_id != ES_ID)) continue; +#endif /*handle SWF media extraction*/ if ((sc->streamType == GF_STREAM_OD) && (priv->load_flags==1)) continue; sc->last_au_time = 0; @@ -377,7 +443,7 @@ static GF_Err CTXLoad_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u if (priv->load_flags != 2) { - if (priv->progressive_support) { + if (priv->progressive) { u32 entry_time; char file_buf[4096+1]; if (!priv->src) { @@ -391,7 +457,7 @@ static GF_Err CTXLoad_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u gf_fseek(priv->src, priv->file_pos, SEEK_SET); while (1) { u32 diff; - s32 nb_read = (s32) fread(file_buf, 1, 4096, priv->src); + s32 nb_read = (s32) gf_fread(file_buf, 4096, priv->src); if (nb_read<0) { return GF_IO_ERR; } @@ -411,7 +477,7 @@ static GF_Err CTXLoad_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u priv->file_pos += nb_read; if (e) break; diff = gf_sys_clock() - entry_time; - if (diff > priv->sax_max_duration) break; + if (diff > priv->sax_dur) break; } if (!priv->scene->graph_attached) { gf_sg_set_scene_size_info(priv->scene->graph, priv->ctx->scene_width, priv->ctx->scene_height, priv->ctx->is_pixel_metrics); @@ -422,8 +488,8 @@ static GF_Err CTXLoad_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u } /*load first frame only*/ else if (!priv->load_flags) { - /*we need the whole file*/ - if (!CTXLoad_CheckDownload(priv)) return GF_OK; + /*we need the whole file - this could be further optimized by loading from memory the entire buffer*/ + if (!is_end) return GF_OK; priv->load_flags = 1; e = gf_sm_load_init(&priv->load); @@ -451,6 +517,9 @@ static GF_Err CTXLoad_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u gf_sm_del(priv->ctx); priv->ctx = NULL; priv->load_flags = 3; + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[CtxLoad] Failed to load context for file %s: %s\n", priv->file_name, gf_error_to_string(e) )); + if (priv->out_pid) + gf_filter_pid_set_eos(priv->out_pid); return e; } @@ -463,14 +532,22 @@ static GF_Err CTXLoad_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u } } - nb_updates = 0; + updates_pending = 0; i=0; while ((sc = (GF_StreamContext *)gf_list_enum(priv->ctx->streams, &i))) { + u32 stream_time = gf_clock_time(priv->scene->root_od->ck); + + //compositor is in end of stream mode, flush all commands + if (priv->scene->compositor->check_eos_state==2) + stream_time=0xFFFFFFFF; + +#ifdef FILTER_FIXME /*not our stream*/ if (!sc->in_root_od && (sc->ESID != ES_ID)) continue; /*not the base stream*/ if (sc->in_root_od && (priv->base_stream_id != ES_ID)) continue; +#endif /*handle SWF media extraction*/ if ((sc->streamType == GF_STREAM_OD) && (priv->load_flags==1)) continue; @@ -517,9 +594,19 @@ static GF_Err CTXLoad_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u } if (au_time > stream_time) { - nb_updates++; + Double ts_offset; + u32 t = au_time - stream_time; + if (!min_next_time_ms || (min_next_time_ms>t)) + min_next_time_ms = t; + + updates_pending++; + + ts_offset = (Double) au->timing; + ts_offset /= sc->timeScale; + gf_sc_sys_frame_pending(priv->scene->compositor, ts_offset, stream_time, filter); break; } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[CtxLoad] %s applying AU time %d\n", priv->file_name, au_time )); if (sc->streamType == GF_STREAM_SCENE) { GF_Command *com; @@ -557,9 +644,8 @@ static GF_Err CTXLoad_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u if (!esd) { if (od->URLString) { ODS_SetupOD(priv->scene, od); - } else { - gf_odf_desc_del((GF_Descriptor *) od); } + gf_odf_desc_del((GF_Descriptor *) od); continue; } /*fix OCR dependencies*/ @@ -580,7 +666,6 @@ static GF_Err CTXLoad_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u if (!mux || !mux->file_name) { /*only animation streams are handled*/ if (!esd->decoderConfig) { - gf_odf_desc_del((GF_Descriptor *) od); } else if (esd->decoderConfig->streamType==GF_STREAM_SCENE) { /*set ST to private scene to get sure the stream will be redirected to us*/ esd->decoderConfig->streamType = GF_STREAM_PRIVATE_SCENE; @@ -593,9 +678,8 @@ static GF_Err CTXLoad_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u ODS_SetupOD(priv->scene, od); } else if (esd->decoderConfig->streamType==GF_STREAM_OCR) { ODS_SetupOD(priv->scene, od); - } else { - gf_odf_desc_del((GF_Descriptor *) od); } + gf_odf_desc_del((GF_Descriptor *) od); continue; } //solve url before import @@ -624,6 +708,7 @@ static GF_Err CTXLoad_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u esd->decoderConfig->streamType = GF_STREAM_PRIVATE_SCENE; esd->dependsOnESID = priv->base_stream_id; ODS_SetupOD(priv->scene, od); + gf_odf_desc_del((GF_Descriptor *) od); continue; #endif } @@ -649,8 +734,10 @@ static GF_Err CTXLoad_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u gf_odf_desc_del((GF_Descriptor *) od); od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG); od->URLString = remote; + od->fake_remote = GF_TRUE; od->objectDescriptorID = k; ODS_SetupOD(priv->scene, od); + gf_odf_desc_del((GF_Descriptor*)od); } if (keep_com) break; } @@ -693,158 +780,132 @@ static GF_Err CTXLoad_ProcessData(GF_SceneDecoder *plug, const char *inBuffer, u } } if (e) return e; - if ((priv->load_flags==2) && !nb_updates) return GF_EOS; + + if ((priv->load_flags==2) && !updates_pending) { + gf_filter_pid_set_eos(priv->out_pid); + return GF_EOS; + } + if (!min_next_time_ms) min_next_time_ms = 1; + min_next_time_ms *= 1000; + if (min_next_time_ms>2000) + min_next_time_ms = 2000; + + gf_filter_ask_rt_reschedule(filter, min_next_time_ms); return GF_OK; } -const char *CTXLoad_GetName(struct _basedecoder *plug) -{ - CTXLoadPriv *priv = (CTXLoadPriv *)plug->privateStack; - switch (priv->load.type) { - case GF_SM_LOAD_BT: - return "MPEG-4 BT Parser"; - case GF_SM_LOAD_VRML: - return "VRML 97 Parser"; - case GF_SM_LOAD_X3DV: - return "X3D (VRML Syntax) Parser"; - case GF_SM_LOAD_XMTA: - return "XMT-A Parser"; - case GF_SM_LOAD_X3D: - return "X3D (XML Syntax) Parser"; - case GF_SM_LOAD_SWF: - return "Flash (SWF) Emulator"; - case GF_SM_LOAD_XSR: - return "LASeRML Loader"; - case GF_SM_LOAD_MP4: - return "MP4 Memory Loader"; - case GF_SM_LOAD_XBL: - return "XBL Parser"; +static void ctxload_finalize(GF_Filter *filter) +{ + CTXLoadPriv *priv = gf_filter_get_udta(filter); - default: - return "Undetermined"; - } + if (priv->ctx) gf_sm_del(priv->ctx); + if (priv->files_to_delete) gf_list_del(priv->files_to_delete); } -static u32 CTXLoad_CanHandleStream(GF_BaseDecoder *ifce, u32 StreamType, GF_ESD *esd, u8 PL) +#include +static const char *ctxload_probe_data(const u8 *probe_data, u32 size, GF_FilterProbeScore *score) { - if (StreamType==GF_STREAM_PRIVATE_SCENE) { - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - switch (esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_PRIVATE_SCENE_GENERIC: - return GF_CODEC_SUPPORTED; - /*LASeR ML: we use this plugin since it has command handling*/ - case GPAC_OTI_PRIVATE_SCENE_LASER: - return GF_CODEC_SUPPORTED; - /* XBL */ - case GPAC_OTI_PRIVATE_SCENE_XBL: - return GF_CODEC_SUPPORTED; - /*SVG*/ - case GPAC_OTI_PRIVATE_SCENE_SVG: - return GF_CODEC_MAYBE_SUPPORTED; - } - } else if (StreamType==GF_STREAM_SCENE) { - /*media type query*/ - if (!esd) return GF_CODEC_STREAM_TYPE_SUPPORTED; - switch (esd->decoderConfig->objectTypeIndication) { - case GPAC_OTI_SCENE_SVG: - case GPAC_OTI_SCENE_SVG_GZ: - case GPAC_OTI_SCENE_DIMS: - return GF_CODEC_MAYBE_SUPPORTED; - default: - break; - } + const char *mime_type = NULL; + char *dst = NULL; + u8 *res; + + /* check gzip magic header */ + if ((size>2) && (probe_data[0] == 0x1f) && (probe_data[1] == 0x8b)) { + *score = GF_FPROBE_EXT_MATCH; + return "btz|bt.gz|xmt.gz|xmtz|wrl.gz|x3dv.gz|x3dvz|x3d.gz|x3dz"; } - return GF_CODEC_NOT_SUPPORTED; -} -void DeleteContextLoader(GF_BaseDecoder *plug) -{ - CTXLoadPriv *priv; - if (!plug) - return; - priv = (CTXLoadPriv *)plug->privateStack; - if (priv) { - if (priv->file_name) - gf_free(priv->file_name); - priv->file_name = NULL; - assert(!priv->ctx); - if (priv->files_to_delete) - gf_list_del(priv->files_to_delete); - priv->files_to_delete = NULL; - gf_free(priv); - plug->privateStack = NULL; + res = gf_utf_get_utf8_string_from_bom((char *)probe_data, size, &dst); + if (res) probe_data = res; + + if (strstr(probe_data, "files_to_delete = gf_list_new(); - - tmp->privateStack = priv; - tmp->AttachStream = CTXLoad_AttachStream; - tmp->DetachStream = CTXLoad_DetachStream; - tmp->GetCapabilities = CTXLoad_GetCapabilities; - tmp->SetCapabilities = CTXLoad_SetCapabilities; - tmp->ProcessData = CTXLoad_ProcessData; - tmp->AttachScene = CTXLoad_AttachScene; - tmp->ReleaseScene = CTXLoad_ReleaseScene; - tmp->GetName = CTXLoad_GetName; - tmp->CanHandleStream = CTXLoad_CanHandleStream; - GF_REGISTER_MODULE_INTERFACE(tmp, GF_SCENE_DECODER_INTERFACE, "GPAC Context Loader", "gpac distribution") - return (GF_BaseDecoder*)tmp; -} + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "bt|btz|bt.gz|xmt|xmt.gz|xmtz|wrl|wrl.gz|x3dv|x3dv.gz|x3dvz|x3d|x3d.gz|x3dz|swf|xsr"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "application/x-bt|application/x-xmt|model/vrml|x-model/x-vrml|model/x3d+vrml|model/x3d+xml|application/x-shockwave-flash|application/x-LASeR+xml"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; -#endif //defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_SCENEGRAPH) +#define OFFS(_n) #_n, offsetof(CTXLoadPriv, _n) -GPAC_MODULE_EXPORT -const u32 *QueryInterfaces() +static const GF_FilterArgs CTXLoadArgs[] = { - static u32 si [] = { -#if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_SCENEGRAPH) - GF_SCENE_DECODER_INTERFACE, -#endif - 0 - }; - return si; -} + { OFFS(progressive), "enable progressive loading", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(sax_dur), "loading duration for SAX parsing (XMT), 0 disables SAX parsing", GF_PROP_UINT, "1000", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + +GF_FilterRegister CTXLoadRegister = { + .name = "btplay", + GF_FS_SET_DESCRIPTION("BT/XMT/X3D loader") + GF_FS_SET_HELP("This filter parses MPEG-4 BIFS (BT and XMT), VRML97 and X3D (wrl and XML) files directly into the scene graph of the compositor. It cannot be used to dump content.") + .private_size = sizeof(CTXLoadPriv), + .flags = GF_FS_REG_MAIN_THREAD, + .args = CTXLoadArgs, + SETCAPS(CTXLoadCaps), + .finalize = ctxload_finalize, + .process = ctxload_process, + .configure_pid = ctxload_configure_pid, + .process_event = ctxload_process_event, + .probe_data = ctxload_probe_data, +}; + +#endif //defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_SCENEGRAPH) -GPAC_MODULE_EXPORT -GF_BaseInterface *LoadInterface(u32 InterfaceType) -{ - switch (InterfaceType) { -#if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_SCENEGRAPH) - case GF_SCENE_DECODER_INTERFACE: - return (GF_BaseInterface *)NewContextLoader(); -#endif - default: - return NULL; - } -} -GPAC_MODULE_EXPORT -void ShutdownInterface(GF_BaseInterface *ifce) +const GF_FilterRegister *ctxload_register(GF_FilterSession *session) { - switch (ifce->InterfaceType) { #if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_SCENEGRAPH) - case GF_SCENE_DECODER_INTERFACE: - DeleteContextLoader((GF_BaseDecoder *)ifce); - break; + return &CTXLoadRegister; +#else + return NULL; #endif - } } -GPAC_MODULE_STATIC_DECLARATION( ctx_load ) + diff --git a/src/filters/load_svg.c b/src/filters/load_svg.c new file mode 100644 index 0000000..1f0258e --- /dev/null +++ b/src/filters/load_svg.c @@ -0,0 +1,481 @@ +/* + * GPAC Multimedia Framework + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2005-2017 + * All rights reserved + * + * This file is part of GPAC / SVG loader filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include +#include + +#if !defined(GPAC_DISABLE_SVG) && !defined(GPAC_DISABLE_ZLIB) + +#include + +typedef struct +{ + //opts + u32 sax_dur; + + //internal + GF_FilterPid *in_pid, *out_pid; + GF_SceneLoader loader; + GF_Scene *scene; + u32 codecid; + const char *file_name; + u32 file_size; + u16 base_es_id; + u32 file_pos; + void *src; + Bool is_playing; +} SVGIn; + + +#define SVG_PROGRESSIVE_BUFFER_SIZE 4096 + +//static +GF_Err svgin_deflate(SVGIn *svgin, const char *buffer, u32 buffer_len, Bool is_dims) +{ + GF_Err e; + char svg_data[2049]; + int err; + u32 done = 0; + z_stream d_stream; + memset(&d_stream, 0, sizeof(z_stream)); + d_stream.next_in = (Bytef*)buffer; + d_stream.avail_in = buffer_len; + d_stream.next_out = (Bytef*)svg_data; + d_stream.avail_out = 2048; + + if (is_dims==1) { + err = inflateInit2(&d_stream, 16+MAX_WBITS); + } else { + err = inflateInit(&d_stream); + } + + if (err == Z_OK) { + e = GF_OK; + while (d_stream.total_in < buffer_len) { + err = inflate(&d_stream, Z_NO_FLUSH); + if (err < Z_OK) { + e = GF_NON_COMPLIANT_BITSTREAM; + break; + } + svg_data[d_stream.total_out - done] = 0; + e = gf_sm_load_string(&svgin->loader, svg_data, GF_FALSE); + if (e || (err== Z_STREAM_END)) break; + done = (u32) d_stream.total_out; + d_stream.avail_out = 2048; + d_stream.next_out = (Bytef*)svg_data; + } + inflateEnd(&d_stream); + return e; + } + return GF_NON_COMPLIANT_BITSTREAM; +} + +static GF_Err svgin_process(GF_Filter *filter) +{ + Bool start, end; + const u8 *data; + u32 size; + GF_Err e = GF_OK; + GF_FilterPacket *pck; + SVGIn *svgin = (SVGIn *) gf_filter_get_udta(filter); + + //no scene yet attached + if (!svgin->scene) { + if (svgin->is_playing) { + gf_filter_pid_set_eos(svgin->out_pid); + return GF_EOS; + } + return GF_OK; + } + +#ifdef FILTER_FIXME + if (stream_time==(u32)-1) { + if (svgin->src) gf_gzclose(svgin->src); + svgin->src = NULL; + gf_sm_load_done(&svgin->loader); + svgin->loader.fileName = NULL; + svgin->file_pos = 0; + gf_sg_reset(svgin->scene->graph); + return GF_OK; + } +#endif + + switch (svgin->codecid) { + /*! streaming SVG*/ + case GF_CODECID_SVG: + pck = gf_filter_pid_get_packet(svgin->in_pid); + if (!pck) { + if (gf_filter_pid_is_eos(svgin->in_pid)) { + gf_filter_pid_set_eos(svgin->out_pid); + return GF_EOS; + } + return GF_OK; + } + data = gf_filter_pck_get_data(pck, &size); + e = gf_sm_load_string(&svgin->loader, data, GF_FALSE); + gf_filter_pid_drop_packet(svgin->in_pid); + break; + + /*! streaming SVG + gz*/ + case GF_CODECID_SVG_GZ: + pck = gf_filter_pid_get_packet(svgin->in_pid); + if (!pck) { + if (gf_filter_pid_is_eos(svgin->in_pid)) { + gf_filter_pid_set_eos(svgin->out_pid); + return GF_EOS; + } + return GF_OK; + } + data = gf_filter_pck_get_data(pck, &size); + e = svgin_deflate(svgin, data, size, GF_FALSE); + gf_filter_pid_drop_packet(svgin->in_pid); + break; + + /*! DIMS (dsi = 3GPP DIMS configuration)*/ + case GF_CODECID_DIMS: + { + u8 prev, dims_hdr; + u32 nb_bytes; + u64 pos; + char * buf2 ; + GF_BitStream *bs; + + pck = gf_filter_pid_get_packet(svgin->in_pid); + if (!pck) { + if (gf_filter_pid_is_eos(svgin->in_pid)) { + gf_filter_pid_set_eos(svgin->out_pid); + return GF_EOS; + } + return GF_OK; + } + data = gf_filter_pck_get_data(pck, &size); + + buf2 = gf_malloc(size); + bs = gf_bs_new((u8 *)data, size, GF_BITSTREAM_READ); + memcpy(buf2, data, size); + + gf_filter_pid_drop_packet(svgin->in_pid); + e = GF_OK; + while (gf_bs_available(bs)) { + pos = gf_bs_get_position(bs); + size = gf_bs_read_u16(bs); + nb_bytes = 2; + /*GPAC internal hack*/ + if (!size) { + size = gf_bs_read_u32(bs); + nb_bytes = 6; + } + + dims_hdr = gf_bs_read_u8(bs); + prev = buf2[pos + nb_bytes + size]; + + buf2[pos + nb_bytes + size] = 0; + if (dims_hdr & GF_DIMS_UNIT_C) { + e = svgin_deflate(svgin, buf2 + pos + nb_bytes + 1, size - 1, GF_TRUE); + } else { + e = gf_sm_load_string(&svgin->loader, buf2 + pos + nb_bytes + 1, GF_FALSE); + } + buf2[pos + nb_bytes + size] = prev; + gf_bs_skip_bytes(bs, size-1); + if (e) break; + + } + gf_bs_del(bs); + gf_free(buf2); + + if (e) goto exit; + } + break; + + default: + if (!svgin->file_name) return GF_SERVICE_ERROR; + + pck = gf_filter_pid_get_packet(svgin->in_pid); + if (!pck) return GF_OK; + gf_filter_pck_get_framing(pck, &start, &end); + gf_filter_pid_drop_packet( svgin->in_pid); + + /*full doc parsing*/ + if ((svgin->sax_dur == 0) && svgin->file_name) { + if (end) { + svgin->loader.fileName = svgin->file_name; + e = gf_sm_load_init(&svgin->loader); + if (!e) e = gf_sm_load_run(&svgin->loader); + } + } + /*chunk parsing*/ + else { + u32 entry_time; + char file_buf[SVG_PROGRESSIVE_BUFFER_SIZE+2]; + /*initial load*/ + if (!svgin->src && !svgin->file_pos) { + svgin->src = gf_gzopen(svgin->file_name, "rb"); + if (!svgin->src) return GF_URL_ERROR; + svgin->loader.fileName = svgin->file_name; + gf_sm_load_init(&svgin->loader); + } + e = GF_OK; + entry_time = gf_sys_clock(); + + while (1) { + u32 diff; + s32 nb_read; + nb_read = gf_gzread(svgin->src, file_buf, SVG_PROGRESSIVE_BUFFER_SIZE); + /*we may have read nothing but we still need to call parse in case the parser got suspended*/ + if (nb_read<=0) { + if ((e==GF_EOS) && gf_gzeof(svgin->src)) { + gf_set_progress("SVG Parsing", svgin->file_pos, svgin->file_size); + gf_gzclose(svgin->src); + svgin->src = NULL; + gf_sm_load_done(&svgin->loader); + } + goto exit; + } + + file_buf[nb_read] = file_buf[nb_read+1] = 0; + + e = gf_sm_load_string(&svgin->loader, file_buf, GF_FALSE); + svgin->file_pos += nb_read; + + + + /*handle decompression*/ + if (svgin->file_pos > svgin->file_size) svgin->file_size = svgin->file_pos + 1; + if (e) break; + + gf_set_progress("SVG Parsing", svgin->file_pos, svgin->file_size); + diff = gf_sys_clock() - entry_time; + if (diff > svgin->sax_dur) { + break; + } + } + } + break; + } + +exit: + if ((e>=GF_OK) && (svgin->scene->graph_attached!=1) && (gf_sg_get_root_node(svgin->loader.scene_graph)!=NULL) ) { + gf_scene_attach_to_compositor(svgin->scene); + } + /*prepare for next playback*/ + if (e) { + gf_sm_load_done(&svgin->loader); + svgin->loader.fileName = NULL; + gf_filter_pid_set_eos(svgin->out_pid); + } + return e; +} + +static GF_Err svgin_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + SVGIn *svgin = (SVGIn *) gf_filter_get_udta(filter); + const GF_PropertyValue *prop; + + if (is_remove) { + svgin->in_pid = NULL; + return GF_OK; + } + + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (prop && prop->value.uint) svgin->codecid = prop->value.uint; + + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (prop && (prop->value.uint==GF_STREAM_FILE)) { + //we must have a file path or codecid + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_FILEPATH); + if (prop && prop->value.string) svgin->file_name = prop->value.string; + } + + if (!svgin->codecid && !svgin->file_name) + return GF_NOT_SUPPORTED; + + svgin->loader.type = GF_SM_LOAD_SVG; + /* decSpecInfo is not null only when reading from an SVG file (local or distant, cached or not) */ + switch (svgin->codecid) { + case GF_CODECID_SVG: + case GF_CODECID_SVG_GZ: + svgin->loader.flags |= GF_SM_LOAD_CONTEXT_STREAMING; + /*no decSpecInfo defined for streaming svg yet*/ + break; + case GF_CODECID_DIMS: + svgin->loader.type = GF_SM_LOAD_DIMS; + svgin->loader.flags |= GF_SM_LOAD_CONTEXT_STREAMING; + /*decSpecInfo not yet supported for DIMS svg - we need properties at the scene level to store the + various indications*/ + break; + default: + break; + } + + if (!svgin->in_pid) { + GF_FilterEvent fevt; + svgin->in_pid = pid; + + //we work with full file only, send a play event on source to indicate that + GF_FEVT_INIT(fevt, GF_FEVT_PLAY, pid); + fevt.play.start_range = 0; + fevt.base.on_pid = svgin->in_pid; + fevt.play.full_file_only = GF_TRUE; + gf_filter_pid_send_event(svgin->in_pid, &fevt); + + } else { + if (pid != svgin->in_pid) { + return GF_REQUIRES_NEW_INSTANCE; + } + //check for filename change + return GF_OK; + } + + //declare a new output PID of type scene, codecid RAW + svgin->out_pid = gf_filter_pid_new(filter); + + gf_filter_pid_copy_properties(svgin->out_pid, pid); + gf_filter_pid_set_property(svgin->out_pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_SCENE) ); + gf_filter_pid_set_property(svgin->out_pid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_property(svgin->out_pid, GF_PROP_PID_IN_IOD, &PROP_BOOL(GF_TRUE) ); + gf_filter_pid_set_udta(pid, svgin->out_pid); + + + if (svgin->file_name) { + gf_filter_set_name(filter, ((svgin->sax_dur==0) && svgin->file_size) ? "SVGLoad" : "SVGLoad:Progressive"); + } else if (svgin->codecid==GF_CODECID_SVG) { + gf_filter_set_name(filter, "SVG:Streaming"); + } else if (svgin->codecid==GF_CODECID_SVG_GZ) { + gf_filter_set_name(filter, "SVG:Streaming:GZ"); + } else if (svgin->codecid==GF_CODECID_DIMS) { + gf_filter_set_name(filter, "SVG:DIMS"); + } + + return GF_OK; +} + +static Bool svgin_process_event(GF_Filter *filter, const GF_FilterEvent *com) +{ + u32 count, i; + SVGIn *svgin = gf_filter_get_udta(filter); + //check for scene attach + switch (com->base.type) { + case GF_FEVT_PLAY: + //cancel play event, we work with full file + svgin->is_playing = GF_TRUE; + return GF_TRUE; + case GF_FEVT_ATTACH_SCENE: + break; + case GF_FEVT_RESET_SCENE: + gf_sm_load_done(&svgin->loader); + svgin->scene = NULL; + return GF_FALSE; + default: + return GF_FALSE; + } + if (!com->attach_scene.on_pid) return GF_TRUE; + + count = gf_filter_get_ipid_count(filter); + for (i=0; iattach_scene.on_pid) { + if (!svgin->scene) { + GF_ObjectManager *odm = com->attach_scene.object_manager; + svgin->scene = odm->subscene ? odm->subscene : odm->parentscene; + + memset(&svgin->loader, 0, sizeof(GF_SceneLoader)); + svgin->loader.is = svgin->scene; + svgin->loader.scene_graph = svgin->scene->graph; + svgin->loader.localPath = gf_get_default_cache_directory(); + + /*Warning: svgin->loader.type may be overridden in attach stream */ + svgin->loader.type = GF_SM_LOAD_SVG; + svgin->loader.flags = GF_SM_LOAD_FOR_PLAYBACK; + + if (! svgin->file_name) + gf_sm_load_init(&svgin->loader); + + if (svgin->scene->root_od->ck && !svgin->scene->root_od->ck->clock_init) + gf_clock_set_time(svgin->scene->root_od->ck, 0); + + //init clocks + gf_odm_check_buffering(svgin->scene->root_od, svgin->in_pid); + } + return GF_TRUE; + } + } + + return GF_FALSE; +} + +static const GF_FilterCapability SVGInCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "svg|svgz|svg.gz"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "image/svg+xml"), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_RAW), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_SVG), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_SVG_GZ), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_DIMS), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + + +#define OFFS(_n) #_n, offsetof(SVGIn, _n) + +static const GF_FilterArgs SVGInArgs[] = +{ + { OFFS(sax_dur), "loading duration for SAX parsing, 0 disables SAX parsing", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; +GF_FilterRegister SVGInRegister = { + .name = "svgplay", + GF_FS_SET_DESCRIPTION("SVG loader") + GF_FS_SET_HELP("This filter parses SVG files directly into the scene graph of the compositor. It cannot be used to dump content.") + .private_size = sizeof(SVGIn), + .flags = GF_FS_REG_MAIN_THREAD, + .args = SVGInArgs, + SETCAPS(SVGInCaps), + .process = svgin_process, + .configure_pid = svgin_configure_pid, + .process_event = svgin_process_event, +}; + +#endif + + +const GF_FilterRegister *svgin_register(GF_FilterSession *session) +{ +#if !defined(GPAC_DISABLE_SVG) && !defined(GPAC_DISABLE_ZLIB) + return &SVGInRegister; +#else + return NULL; +#endif +} diff --git a/src/filters/load_text.c b/src/filters/load_text.c new file mode 100644 index 0000000..a528b5b --- /dev/null +++ b/src/filters/load_text.c @@ -0,0 +1,3082 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2020 + * All rights reserved + * + * This file is part of GPAC / text import filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_SWF_IMPORT +/* SWF Importer */ +#include +#endif + +#ifndef GPAC_DISABLE_ISOM_WRITE + +typedef struct __txtin_ctx GF_TXTIn; + +struct __txtin_ctx +{ + //opts + u32 width, height, txtx, txty, fontsize; + s32 zorder; + const char *fontname, *lang; + Bool nodefbox, noflush, webvtt; + u32 timescale; + GF_Fraction fps; + + + GF_FilterPid *ipid, *opid; + const char *file_name; + u32 fmt; + u32 playstate; + //0: not seeking, 1: seek request pending, 2: seek configured, discarding packets up until start_range + u32 seek_state; + Double start_range; + + Bool is_setup; + + GF_Err (*text_process)(GF_Filter *filter, GF_TXTIn *ctx); + + s32 unicode_type; + + FILE *src; + + GF_BitStream *bs_w; + Bool first_samp; + Bool hdr_parsed; + + //state vars for srt + u32 state, default_color; + GF_TextSample *samp; + u64 start, end, prev_end; + u32 curLine; + GF_StyleRecord style; + + //WebVTT state + GF_WebVTTParser *vttparser; + + //TTXT state + GF_DOMParser *parser; + u32 cur_child_idx, nb_children, last_desc_idx; + GF_List *text_descs; + Bool last_sample_empty; + u64 last_sample_duration; + //TTML state is the same as ttxt plus the timescale and start (webvtt) for cts compute + u32 txml_timescale; + u32 current_tt_interval; + + //TTML state + GF_XMLNode *root_working_copy, *div_node, *sample_list_node; + GF_DOMParser *parser_working_copy; + Bool non_compliant_ttml; + +#ifndef GPAC_DISABLE_SWF_IMPORT + //SWF text + SWFReader *swf_parse; + Bool do_suspend; +#endif + + + GF_List *intervals; +}; + +typedef struct +{ + s64 begin, end; +} TTMLInterval; + + +enum +{ + GF_TXTIN_MODE_NONE = 0, + GF_TXTIN_MODE_SRT, + GF_TXTIN_MODE_SUB, + GF_TXTIN_MODE_TTXT, + GF_TXTIN_MODE_TEXML, + GF_TXTIN_MODE_WEBVTT, + GF_TXTIN_MODE_TTML, + GF_TXTIN_MODE_SWF_SVG, +}; + +#define REM_TRAIL_MARKS(__str, __sep) while (1) { \ + u32 _len = (u32) strlen(__str); \ + if (!_len) break; \ + _len--; \ + if (strchr(__sep, __str[_len])) __str[_len] = 0; \ + else break; \ + } \ + + +s32 gf_text_get_utf_type(FILE *in_src) +{ + u32 read; + unsigned char BOM[5]; + read = (u32) gf_fread(BOM, 5, in_src); + if ((s32) read < 1) + return -1; + + if ((BOM[0]==0xFF) && (BOM[1]==0xFE)) { + /*UTF32 not supported*/ + if (!BOM[2] && !BOM[3]) return -1; + gf_fseek(in_src, 2, SEEK_SET); + return 3; + } + if ((BOM[0]==0xFE) && (BOM[1]==0xFF)) { + /*UTF32 not supported*/ + if (!BOM[2] && !BOM[3]) return -1; + gf_fseek(in_src, 2, SEEK_SET); + return 2; + } else if ((BOM[0]==0xEF) && (BOM[1]==0xBB) && (BOM[2]==0xBF)) { + gf_fseek(in_src, 3, SEEK_SET); + return 1; + } + if (BOM[0]<0x80) { + gf_fseek(in_src, 0, SEEK_SET); + return 0; + } + return -1; +} +static void ttxt_dom_progress(void *cbk, u64 cur_samp, u64 count) +{ + GF_TXTIn *ctx = (GF_TXTIn *)cbk; + ctx->end = count; +} + +static GF_Err gf_text_guess_format(const char *filename, u32 *fmt) +{ + char szLine[2048]; + u32 val; + s32 uni_type; + FILE *test = gf_fopen(filename, "rb"); + if (!test) return GF_URL_ERROR; + uni_type = gf_text_get_utf_type(test); + + if (uni_type>1) { + const u16 *sptr; + char szUTF[1024]; + u32 read = (u32) gf_fread(szUTF, 1023, test); + if ((s32) read < 0) { + gf_fclose(test); + return GF_IO_ERR; + } + szUTF[read]=0; + sptr = (u16*)szUTF; + /*read = (u32) */gf_utf8_wcstombs(szLine, read, &sptr); + } else { + val = (u32) gf_fread(szLine, 1024, test); + if ((s32) val<0) return GF_IO_ERR; + + szLine[val]=0; + } + REM_TRAIL_MARKS(szLine, "\r\n\t ") + + *fmt = GF_TXTIN_MODE_NONE; + if ((szLine[0]=='{') && strstr(szLine, "}{")) *fmt = GF_TXTIN_MODE_SUB; + else if (szLine[0] == '<') { + char *ext = gf_file_ext_start(filename); + if (!strnicmp(ext, ".ttxt", 5)) *fmt = GF_TXTIN_MODE_TTXT; + else if (!strnicmp(ext, ".ttml", 5)) *fmt = GF_TXTIN_MODE_TTML; + ext = strstr(szLine, "?>"); + if (ext) ext += 2; + if (ext && !ext[0]) { + if (!gf_fgets(szLine, 2048, test)) + szLine[0] = '\0'; + } + if (strstr(szLine, "x-quicktime-tx3g") || strstr(szLine, "text3GTrack")) *fmt = GF_TXTIN_MODE_TEXML; + else if (strstr(szLine, "TextStream")) *fmt = GF_TXTIN_MODE_TTXT; + else if (strstr(szLine, "tt")) *fmt = GF_TXTIN_MODE_TTML; + } + else if (strstr(szLine, "WEBVTT") ) + *fmt = GF_TXTIN_MODE_WEBVTT; + else if (strstr(szLine, " --> ") ) + *fmt = GF_TXTIN_MODE_SRT; /* might want to change the default to WebVTT */ + + else if (!strncmp(szLine, "FWS", 3) || !strncmp(szLine, "CWS", 3)) + *fmt = GF_TXTIN_MODE_SWF_SVG; + + gf_fclose(test); + return GF_OK; +} + + + +char *gf_text_get_utf8_line(char *szLine, u32 lineSize, FILE *txt_in, s32 unicode_type) +{ + u32 i, j, len; + char *sOK; + char szLineConv[1024]; + unsigned short *sptr; + + memset(szLine, 0, sizeof(char)*lineSize); + sOK = gf_fgets(szLine, lineSize, txt_in); + if (!sOK) return NULL; + if (unicode_type<=1) { + j=0; + len = (u32) strlen(szLine); + for (i=0; i> 6) & 0x3 ); + j++; + szLine[i] &= 0xbf; + } + /*UTF8 2 bytes char*/ + else if ( (szLine[i] & 0xe0) == 0xc0) { + szLineConv[j] = szLine[i]; + i++; + j++; + } + /*UTF8 3 bytes char*/ + else if ( (szLine[i] & 0xf0) == 0xe0) { + szLineConv[j] = szLine[i]; + i++; + j++; + szLineConv[j] = szLine[i]; + i++; + j++; + } + /*UTF8 4 bytes char*/ + else if ( (szLine[i] & 0xf8) == 0xf0) { + szLineConv[j] = szLine[i]; + i++; + j++; + szLineConv[j] = szLine[i]; + i++; + j++; + szLineConv[j] = szLine[i]; + i++; + j++; + } else { + i+=1; + continue; + } + } + szLineConv[j] = szLine[i]; + j++; + } + szLineConv[j] = 0; + strcpy(szLine, szLineConv); + return sOK; + } + +#ifdef GPAC_BIG_ENDIAN + if (unicode_type==3) { +#else + if (unicode_type==2) { +#endif + i=0; + while (1) { + char c; + if (!szLine[i] && !szLine[i+1]) break; + c = szLine[i+1]; + szLine[i+1] = szLine[i]; + szLine[i] = c; + i+=2; + } + } + sptr = (u16 *)szLine; + i = (u32) gf_utf8_wcstombs(szLineConv, 1024, (const unsigned short **) &sptr); + szLineConv[i] = 0; + strcpy(szLine, szLineConv); + /*this is ugly indeed: since input is UTF16-LE, there are many chances the gf_fgets never reads the \0 after a \n*/ + if (unicode_type==3) gf_fgetc(txt_in); + return sOK; +} + + +static void txtin_probe_duration(GF_TXTIn *ctx) +{ + GF_Fraction64 dur; + dur.num = 0; + + if (ctx->fmt == GF_TXTIN_MODE_SWF_SVG) { +#ifndef GPAC_DISABLE_SWF_IMPORT + u32 frame_count, frame_rate; + gf_swf_get_duration(ctx->swf_parse, &frame_rate, &frame_count); + if (frame_count) { + GF_Fraction64 tdur; + tdur.num = frame_count; + tdur.den = frame_rate; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, &PROP_FRAC64(tdur)); + } +#endif + return; + } + if ((ctx->fmt == GF_TXTIN_MODE_SRT) || (ctx->fmt == GF_TXTIN_MODE_WEBVTT) || (ctx->fmt == GF_TXTIN_MODE_SUB)) { + u64 pos = gf_ftell(ctx->src); + gf_fseek(ctx->src, 0, SEEK_SET); + while (!gf_feof(ctx->src)) { + u64 end; + char szLine[2048]; + char *sOK = gf_text_get_utf8_line(szLine, 2048, ctx->src, ctx->unicode_type); + if (!sOK) break; + REM_TRAIL_MARKS(szLine, "\r\n\t ") + + if (ctx->fmt == GF_TXTIN_MODE_SUB) { + char szText[2048]; + u32 sframe, eframe; + if (sscanf(szLine, "{%d}{%d}%2047s", &sframe, &eframe, szText) == 3) { + if (ctx->fps.den) + end = 1000 * eframe * ctx->fps.num / ctx->fps.den; + else + end = 1000 * eframe / 25; + if (end > (u64) dur.num) dur.num = (s64) end; + } + } else { + u32 eh, em, es, ems; + char *start = strstr(szLine, "-->"); + if (!start) continue; + while (start[0] && ((start[0] == ' ') || (start[0] == '\t'))) start++; + + if (sscanf(start, "%u:%u:%u,%u", &eh, &em, &es, &ems) != 4) { + eh = 0; + if (sscanf(szLine, "%u:%u,%u", &em, &es, &ems) != 3) { + continue; + } + } + end = (3600*eh + 60*em + es)*1000 + ems; + if (end > (u64) dur.num) dur.num = (s64) end; + } + } + gf_fseek(ctx->src, pos, SEEK_SET); + if (dur.num) { + dur.den = 1000; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, &PROP_FRAC64(dur)); + } + return; + } + if ((ctx->fmt == GF_TXTIN_MODE_TTXT) || (ctx->fmt == GF_TXTIN_MODE_TEXML)) { + u32 i=0; + GF_XMLNode *node, *root = gf_xml_dom_get_root(ctx->parser); + while ((node = gf_list_enum(root->content, &i))) { + u32 j; + u64 duration; + GF_XMLAttribute *att; + if (node->type) { + continue; + } + /*sample text*/ + if ((ctx->fmt == GF_TXTIN_MODE_TTXT) && strcmp(node->name, "TextSample")) continue; + else if ((ctx->fmt == GF_TXTIN_MODE_TEXML) && strcmp(node->name, "sample")) continue; + + + j=0; + while ( (att=(GF_XMLAttribute*)gf_list_enum(node->attributes, &j))) { + u32 h, m, s, ms; + u64 ts=0; + if (ctx->fmt == GF_TXTIN_MODE_TTXT) { + if (strcmp(att->name, "sampleTime")) continue; + + if (sscanf(att->value, "%u:%u:%u.%u", &h, &m, &s, &ms) == 4) { + ts = (h*3600 + m*60 + s)*1000 + ms; + } else { + ts = (u32) (atof(att->value) * 1000); + } + if (ts > (u64) dur.num) dur.num = (s64) ts; + } else { + if (strcmp(att->name, "duration")) continue; + duration = atoi(att->value); + dur.num += (s32) ( (1000 * duration) / ctx->txml_timescale); + } + } + } + if (dur.num) { + dur.den = 1000; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, &PROP_FRAC64(dur)); + } + return; + } + + if (ctx->fmt == GF_TXTIN_MODE_TTML) { + u32 i=0; + GF_XMLNode *node, *p_node; + + while ((node = gf_list_enum(ctx->div_node->content, &i))) { + GF_XMLAttribute *att; + u32 h, m, s, ms, p_idx=0; + u64 ts_end=0; + h = m = s = ms = 0; + while ( (att = (GF_XMLAttribute*)gf_list_enum(node->attributes, &p_idx))) { + if (strcmp(att->name, "end")) continue; + + if (sscanf(att->value, "%u:%u:%u.%u", &h, &m, &s, &ms) == 4) { + ts_end = (h*3600 + m*60+s)*1000+ms; + } else if (sscanf(att->value, "%u:%u:%u", &h, &m, &s) == 3) { + ts_end = (h*3600 + m*60+s)*1000; + } + } + //or under a + p_idx = 0; + while ( (p_node = (GF_XMLNode*)gf_list_enum(node->content, &p_idx))) { + u32 span_idx = 0; + while ( (att = (GF_XMLAttribute*)gf_list_enum(p_node->attributes, &span_idx))) { + if (strcmp(att->name, "end")) continue; + if (sscanf(att->value, "%u:%u:%u.%u", &h, &m, &s, &ms) == 4) { + ts_end = (h*3600 + m*60+s)*1000+ms; + } else if (sscanf(att->value, "%u:%u:%u", &h, &m, &s) == 3) { + ts_end = (h*3600 + m*60+s)*1000; + } + } + } + if (ts_end > (u64) dur.num) dur.num = (s64) ts_end; + } + if (dur.num) { + dur.den = 1000; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, &PROP_FRAC64(dur)); + } + return; + } + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TXTIn] Duration probing not supported for format %d\n", ctx->fmt)); +} + +static GF_Err txtin_setup_srt(GF_Filter *filter, GF_TXTIn *ctx) +{ + u32 ID, OCR_ES_ID, dsi_len, file_size; + u8 *dsi; + GF_TextSampleDescriptor *sd; + + ctx->src = gf_fopen(ctx->file_name, "rb"); + if (!ctx->src) return GF_URL_ERROR; + + file_size = (u32) gf_fsize(ctx->src); + + ctx->unicode_type = gf_text_get_utf_type(ctx->src); + if (ctx->unicode_type<0) { + gf_fclose(ctx->src); + ctx->src = NULL; + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TXTIn] Unsupported SRT UTF encoding\n")); + return GF_NOT_SUPPORTED; + } + + if (!ctx->timescale) ctx->timescale = 1000; + OCR_ES_ID = ID = 0; + + if (!ctx->opid) ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_TEXT) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_TX3G) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->timescale) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DOWN_SIZE, &PROP_LONGUINT(file_size) ); + + if (!ID) ID = 1; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ID, &PROP_UINT(ID) ); + if (OCR_ES_ID) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CLOCK_ID, &PROP_UINT(OCR_ES_ID) ); + if (ctx->width) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->width) ); + if (ctx->height) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->height) ); + if (ctx->zorder) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ZORDER, &PROP_SINT(ctx->zorder) ); + if (ctx->lang) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_LANGUAGE, &PROP_STRING( ctx->lang) ); + + sd = (GF_TextSampleDescriptor*)gf_odf_desc_new(GF_ODF_TX3G_TAG); + sd->fonts = (GF_FontRecord*)gf_malloc(sizeof(GF_FontRecord)); + sd->font_count = 1; + sd->fonts[0].fontID = 1; + sd->fonts[0].fontName = gf_strdup(ctx->fontname ? ctx->fontname : "Serif"); + sd->back_color = 0x00000000; /*transparent*/ + sd->default_style.fontID = 1; + sd->default_style.font_size = ctx->fontsize; + sd->default_style.text_color = 0xFFFFFFFF; /*white*/ + sd->default_style.style_flags = 0; + sd->horiz_justif = 1; /*center of scene*/ + sd->vert_justif = (s8) -1; /*bottom of scene*/ + + if (ctx->nodefbox) { + sd->default_pos.top = sd->default_pos.left = sd->default_pos.right = sd->default_pos.bottom = 0; + } else if ((sd->default_pos.bottom==sd->default_pos.top) || (sd->default_pos.right==sd->default_pos.left)) { + sd->default_pos.left = ctx->txtx; + sd->default_pos.top = ctx->txty; + sd->default_pos.right = ctx->width + sd->default_pos.left; + sd->default_pos.bottom = ctx->height + sd->default_pos.top; + } + + /*store attribs*/ + ctx->style = sd->default_style; + gf_odf_tx3g_write(sd, &dsi, &dsi_len); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_len) ); + + gf_odf_desc_del((GF_Descriptor *)sd); + + ctx->default_color = ctx->style.text_color; + ctx->samp = gf_isom_new_text_sample(); + ctx->state = 0; + ctx->end = ctx->prev_end = ctx->start = 0; + ctx->first_samp = GF_TRUE; + ctx->curLine = 0; + + txtin_probe_duration(ctx); + return GF_OK; +} + +static void txtin_process_send_text_sample(GF_TXTIn *ctx, GF_TextSample *txt_samp, u64 ts, u32 duration, Bool is_rap) +{ + GF_FilterPacket *dst_pck; + u8 *pck_data; + u32 size; + + if (ctx->seek_state==2) { + Double end = (Double) (ts+duration); + end /= 1000; + if (end < ctx->start_range) return; + ctx->seek_state = 0; + } + + size = gf_isom_text_sample_size(txt_samp); + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &pck_data); + gf_bs_reassign_buffer(ctx->bs_w, pck_data, size); + gf_isom_text_sample_write_bs(txt_samp, ctx->bs_w); + + ts *= ctx->timescale; + ts /= 1000; + duration *= ctx->timescale; + duration /= 1000; + + gf_filter_pck_set_sap(dst_pck, is_rap ? GF_FILTER_SAP_1 : GF_FILTER_SAP_NONE); + gf_filter_pck_set_cts(dst_pck, ts); + gf_filter_pck_set_duration(dst_pck, duration); + + gf_filter_pck_send(dst_pck); +} + +static GF_Err txtin_process_srt(GF_Filter *filter, GF_TXTIn *ctx) +{ + u32 i; + u32 sh, sm, ss, sms, eh, em, es, ems, txt_line, char_len, char_line, j, rem_styles; + Bool set_start_char, set_end_char, rem_color; + u32 line, len; + char szLine[2048], szText[2048], *ptr; + unsigned short uniLine[5000], uniText[5000], *sptr; + + if (!ctx->is_setup) { + ctx->is_setup = GF_TRUE; + return txtin_setup_srt(filter, ctx); + } + if (!ctx->opid) return GF_NOT_SUPPORTED; + if (!ctx->playstate) return GF_OK; + else if (ctx->playstate==2) return GF_EOS; + + txt_line = 0; + set_start_char = set_end_char = GF_FALSE; + char_len = 0; + + if (ctx->seek_state == 1) { + ctx->seek_state = 2; + gf_fseek(ctx->src, 0, SEEK_SET); + } + + while (1) { + char *sOK = gf_text_get_utf8_line(szLine, 2048, ctx->src, ctx->unicode_type); + + if (sOK) REM_TRAIL_MARKS(szLine, "\r\n\t ") + + if (!sOK || !strlen(szLine)) { + u32 nb_empty = 1; + u32 pos = (u32) gf_ftell(ctx->src); + if (ctx->state) { + while (!gf_feof(ctx->src)) { + sOK = gf_text_get_utf8_line(szLine+nb_empty, 2048-nb_empty, ctx->src, ctx->unicode_type); + if (sOK) REM_TRAIL_MARKS((szLine+nb_empty), "\r\n\t ") + + if (!sOK) { + gf_fseek(ctx->src, pos, SEEK_SET); + break; + } else if (!strlen(szLine+nb_empty)) { + nb_empty++; + continue; + } else if ( sscanf(szLine+nb_empty, "%u", &line) == 1) { + gf_fseek(ctx->src, pos, SEEK_SET); + break; + } else { + u32 k; + for (k=0; kstyle.style_flags = 0; + ctx->style.text_color = 0xFFFFFFFF; + ctx->style.startCharOffset = ctx->style.endCharOffset = 0; + if (txt_line) { + if (ctx->prev_end && (ctx->start != ctx->prev_end) && (ctx->state<=2)) { + GF_TextSample * empty_samp = gf_isom_new_text_sample(); + txtin_process_send_text_sample(ctx, empty_samp, ctx->prev_end, (u32) (ctx->start - ctx->prev_end), GF_TRUE ); + gf_isom_delete_text_sample(empty_samp); + } + + if (ctx->state<=2) { + txtin_process_send_text_sample(ctx, ctx->samp, ctx->start, (u32) (ctx->end - ctx->start), GF_TRUE); + ctx->prev_end = ctx->end; + } + txt_line = 0; + char_len = 0; + set_start_char = set_end_char = GF_FALSE; + ctx->style.startCharOffset = ctx->style.endCharOffset = 0; + gf_isom_text_reset(ctx->samp); + + gf_filter_pid_set_info(ctx->opid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT( gf_ftell(ctx->src )) ); + } + ctx->state = 0; + if (!sOK) break; + continue; + } + +force_line: + switch (ctx->state) { + case 0: + if (sscanf(szLine, "%u", &line) != 1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TXTIn] Bad SRT formatting - expecting number got \"%s\"\n", szLine)); + break; + } + if (line != ctx->curLine + 1) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TXTIn] Corrupted SRT frame %d after frame %d\n", line, ctx->curLine)); + } + ctx->curLine = line; + ctx->state = 1; + break; + case 1: + if (sscanf(szLine, "%u:%u:%u,%u --> %u:%u:%u,%u", &sh, &sm, &ss, &sms, &eh, &em, &es, &ems) != 8) { + if (sscanf(szLine, "%u:%u:%u.%u --> %u:%u:%u.%u", &sh, &sm, &ss, &sms, &eh, &em, &es, &ems) != 8) { + sh = eh = 0; + if (sscanf(szLine, "%u:%u,%u --> %u:%u,%u", &sm, &ss, &sms, &em, &es, &ems) != 6) { + if (sscanf(szLine, "%u:%u.%u --> %u:%u.%u", &sm, &ss, &sms, &em, &es, &ems) != 6) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TXTIn] Error scanning SRT frame %d timing\n", ctx->curLine)); + ctx->state = 0; + break; + } + } + } + } + ctx->start = (3600*sh + 60*sm + ss)*1000 + sms; + if (ctx->start < ctx->end) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TXTIn] Overlapping SRT frame %d - starts "LLD" ms is before end of previous one "LLD" ms - adjusting time stamps\n", ctx->curLine, ctx->start, ctx->end)); + ctx->start = ctx->end; + } + + ctx->end = (3600*eh + 60*em + es)*1000 + ems; + /*make stream start at 0 by inserting a fake AU*/ + if (ctx->first_samp && (ctx->start > 0)) { + txtin_process_send_text_sample(ctx, ctx->samp, 0, (u32) ctx->start, GF_TRUE); + } + ctx->style.style_flags = 0; + ctx->state = 2; + if (ctx->end <= ctx->prev_end) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TXTIn] Overlapping SRT frame %d end "LLD" is at or before previous end "LLD" - removing\n", ctx->curLine, ctx->end, ctx->prev_end)); + ctx->start = ctx->end; + ctx->state = 3; + } + break; + + default: + /*reset only when text is present*/ + ctx->first_samp = GF_FALSE; + + /*go to line*/ + if (txt_line) { + gf_isom_text_add_text(ctx->samp, "\n", 1); + char_len += 1; + } + + ptr = (char *) szLine; + { + size_t _len = gf_utf8_mbstowcs(uniLine, 5000, (const char **) &ptr); + if (_len == (size_t) -1) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TXTIn] Invalid UTF data (line %d)\n", ctx->curLine)); + ctx->state = 0; + } + len = (u32) _len; + } + i=j=0; + rem_styles = 0; + rem_color = 0; + while (i')) { + style_nb_chars = 3; + style_def_type = 1; + } + else if ( (uniLine[i]=='<') && (uniLine[i+1]=='/') && (uniLine[i+3]=='>')) { + style_def_type = 2; + style_nb_chars = 4; + } + else if (uniLine[i]=='<') { + const unsigned short* src = uniLine + i; + size_t alen = gf_utf8_wcstombs(szLine, 2048, (const unsigned short**) & src); + szLine[alen] = 0; + strlwr(szLine); + if (!strncmp(szLine, "'); + if (e_sep) { + style_nb_chars = (u32) (1 + e_sep - szLine); + style_def_type = 1; + } + } + + } + } + else if (!strncmp(szLine, "", 7) ) { + style_nb_chars = 7; + style_def_type = 2; + font_style = 0xFFFFFFFF; + } + //skip unknown + else { + char *a_sep = strstr(szLine, ">"); + if (a_sep) { + style_nb_chars = (u32) (a_sep - szLine); + i += style_nb_chars; + continue; + } + } + + } + + /*start of new style*/ + if (style_def_type==1) { + /*store prev style*/ + if (set_end_char) { + assert(set_start_char); + gf_isom_text_add_style(ctx->samp, &ctx->style); + set_end_char = set_start_char = GF_FALSE; + ctx->style.style_flags &= ~rem_styles; + rem_styles = 0; + if (rem_color) { + ctx->style.text_color = ctx->default_color; + rem_color = 0; + } + } + if (set_start_char && (ctx->style.startCharOffset != j)) { + ctx->style.endCharOffset = char_len + j; + if (ctx->style.style_flags) gf_isom_text_add_style(ctx->samp, &ctx->style); + } + switch (uniLine[i+1]) { + case 'b': + case 'B': + ctx->style.style_flags |= GF_TXT_STYLE_BOLD; + set_start_char = GF_TRUE; + ctx->style.startCharOffset = char_len + j; + break; + case 'i': + case 'I': + ctx->style.style_flags |= GF_TXT_STYLE_ITALIC; + set_start_char = GF_TRUE; + ctx->style.startCharOffset = char_len + j; + break; + case 'u': + case 'U': + ctx->style.style_flags |= GF_TXT_STYLE_UNDERLINED; + set_start_char = GF_TRUE; + ctx->style.startCharOffset = char_len + j; + break; + case 'f': + case 'F': + if (font_style) { + ctx->style.text_color = font_style; + set_start_char = GF_TRUE; + ctx->style.startCharOffset = char_len + j; + } + break; + } + i += style_nb_chars; + continue; + } + + /*end of prev style*/ + if (style_def_type==2) { + switch (uniLine[i+2]) { + case 'b': + case 'B': + rem_styles |= GF_TXT_STYLE_BOLD; + set_end_char = GF_TRUE; + ctx->style.endCharOffset = char_len + j; + break; + case 'i': + case 'I': + rem_styles |= GF_TXT_STYLE_ITALIC; + set_end_char = GF_TRUE; + ctx->style.endCharOffset = char_len + j; + break; + case 'u': + case 'U': + rem_styles |= GF_TXT_STYLE_UNDERLINED; + set_end_char = GF_TRUE; + ctx->style.endCharOffset = char_len + j; + break; + case 'f': + case 'F': + if (font_style) { + rem_color = 1; + set_end_char = GF_TRUE; + ctx->style.endCharOffset = char_len + j; + } + } + i+=style_nb_chars; + continue; + } + /*store style*/ + if (set_end_char) { + gf_isom_text_add_style(ctx->samp, &ctx->style); + set_end_char = GF_FALSE; + set_start_char = GF_TRUE; + ctx->style.startCharOffset = char_len + j; + ctx->style.style_flags &= ~rem_styles; + rem_styles = 0; + ctx->style.text_color = ctx->default_color; + rem_color = 0; + } + + uniText[j] = uniLine[i]; + j++; + i++; + } + /*store last style*/ + if (set_end_char) { + gf_isom_text_add_style(ctx->samp, &ctx->style); + set_end_char = GF_FALSE; + set_start_char = GF_TRUE; + ctx->style.startCharOffset = char_len + j; + ctx->style.style_flags &= ~rem_styles; + } + + char_line = j; + uniText[j] = 0; + + sptr = (u16 *) uniText; + len = (u32) gf_utf8_wcstombs(szText, 5000, (const u16 **) &sptr); + + gf_isom_text_add_text(ctx->samp, szText, len); + char_len += char_line; + txt_line ++; + break; + } + + if (gf_filter_pid_would_block(ctx->opid)) + return GF_OK; + } + + /*final flush*/ + if (ctx->end && ! ctx->noflush) { + gf_isom_text_reset(ctx->samp); + txtin_process_send_text_sample(ctx, ctx->samp, ctx->end, 0, GF_TRUE); + ctx->end = 0; + } + gf_isom_text_reset(ctx->samp); + + return GF_EOS; +} + +/* Structure used to pass importer and track data to the parsers without exposing the GF_MediaImporter structure + used by WebVTT and Flash->SVG */ +typedef struct { + GF_TXTIn *ctx; + u32 timescale; + u32 track; + u32 descriptionIndex; +} GF_ISOFlusher; + +#ifndef GPAC_DISABLE_VTT + +static GF_Err gf_webvtt_import_report(void *user, GF_Err e, char *message, const char *line) +{ + GF_LOG(e ? GF_LOG_WARNING : GF_LOG_INFO, GF_LOG_AUTHOR, ("[TXTIn] WebVTT line %s: %s\n", line, message) ); + return e; +} + +static void gf_webvtt_import_header(void *user, const char *config) +{ + GF_TXTIn *ctx = (GF_TXTIn *)user; + if (!ctx->hdr_parsed) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA((char *) config, (u32) (1+strlen(config)) ) ); + ctx->hdr_parsed = GF_TRUE; + gf_webvtt_parser_suspend(ctx->vttparser); + } +} + +static void gf_webvtt_flush_sample(void *user, GF_WebVTTSample *samp) +{ + u64 start, end; + GF_TXTIn *ctx = (GF_TXTIn *)user; + GF_ISOSample *s; + + start = gf_webvtt_sample_get_start(samp); + end = gf_webvtt_sample_get_end(samp); + + if (ctx->seek_state==2) { + Double tsend = (Double) end; + tsend /= 1000; + if (tsendstart_range) return; + ctx->seek_state = 0; + } + + s = gf_isom_webvtt_to_sample(samp); + if (s) { + GF_FilterPacket *pck; + u8 *pck_data; + + pck = gf_filter_pck_new_alloc(ctx->opid, s->dataLength, &pck_data); + memcpy(pck_data, s->data, s->dataLength); + gf_filter_pck_set_cts(pck, (u64) (ctx->timescale * start / 1000) ); + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + + + if (end && (end>=start) ) { + gf_filter_pck_set_duration(pck, (u32) (ctx->timescale * (end-start) / 1000) ); + } + gf_filter_pck_send(pck); + + gf_isom_sample_del(&s); + } + gf_webvtt_sample_del(samp); + + gf_filter_pid_set_info(ctx->opid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT( gf_ftell(ctx->src )) ); + + if (gf_filter_pid_would_block(ctx->opid)) + gf_webvtt_parser_suspend(ctx->vttparser); + +} + +static GF_Err txtin_webvtt_setup(GF_Filter *filter, GF_TXTIn *ctx) +{ + GF_Err e; + u32 ID, OCR_ES_ID, file_size, w, h; + Bool is_srt; + char *ext; + + ctx->src = gf_fopen(ctx->file_name, "rb"); + if (!ctx->src) return GF_URL_ERROR; + + file_size = (u32) gf_fsize(ctx->src); + + ctx->unicode_type = gf_text_get_utf_type(ctx->src); + if (ctx->unicode_type<0) { + gf_fclose(ctx->src); + ctx->src = NULL; + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TXTIn] Unsupported SRT UTF encoding\n")); + return GF_NOT_SUPPORTED; + } + ext = gf_file_ext_start(ctx->file_name); + is_srt = (ext && !strnicmp(ext, ".srt", 4)) ? GF_TRUE : GF_FALSE; + + + if (!ctx->timescale) ctx->timescale = 1000; + OCR_ES_ID = ID = 0; + + if (!ctx->opid) ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_TEXT) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_WEBVTT) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->timescale) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DOWN_SIZE, &PROP_LONGUINT(file_size) ); + + w = ctx->width; + h = ctx->height; + if (!ID) ID = 1; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ID, &PROP_UINT(ID) ); + if (OCR_ES_ID) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CLOCK_ID, &PROP_UINT(OCR_ES_ID) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(w) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(h) ); + if (ctx->zorder) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ZORDER, &PROP_SINT(ctx->zorder) ); + if (ctx->lang) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_LANGUAGE, &PROP_STRING( ctx->lang) ); + + ctx->vttparser = gf_webvtt_parser_new(); + + e = gf_webvtt_parser_init(ctx->vttparser, ctx->src, ctx->unicode_type, is_srt, ctx, gf_webvtt_import_report, gf_webvtt_flush_sample, gf_webvtt_import_header); + if (e != GF_OK) { + gf_webvtt_parser_del(ctx->vttparser); + ctx->vttparser = NULL; + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TXTIn] WebVTT parser init error %s\n", gf_error_to_string(e) )); + } + //get the header + e = gf_webvtt_parser_parse(ctx->vttparser); + + txtin_probe_duration(ctx); + return e; +} + +static GF_Err txtin_process_webvtt(GF_Filter *filter, GF_TXTIn *ctx) +{ + GF_Err e; + + if (!ctx->is_setup) { + ctx->is_setup = GF_TRUE; + return txtin_webvtt_setup(filter, ctx); + } + if (!ctx->vttparser) return GF_NOT_SUPPORTED; + if (ctx->seek_state==1) { + ctx->seek_state = 2; + gf_webvtt_parser_restart(ctx->vttparser); + } + + e = gf_webvtt_parser_parse(ctx->vttparser); + if (e < GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TXTIn] WebVTT process error %s\n", gf_error_to_string(e) )); + } + return e; +} + +#endif /*GPAC_DISABLE_VTT*/ + +static char *ttxt_parse_string(char *str, Bool strip_lines) +{ + u32 i=0; + u32 k=0; + u32 len = (u32) strlen(str); + u32 state = 0; + + if (!strip_lines) { + for (i=0; icontent, &idx))) { + if (!strcmp(node->name, "body")) { + GF_XMLNode *body_node; + u32 body_idx = 0; + while ( (body_node = (GF_XMLNode*)gf_list_enum(node->content, &body_idx))) { + if (!strcmp(body_node->name, "div")) { + u32 body_num; + *sample_list_node = body_node; + body_num = gf_list_count(body_node->content); + while (body_num--) { + GF_XMLNode *content_node = (GF_XMLNode*)gf_list_get(body_node->content, 0); + assert(gf_list_find(body_node->content, content_node) == 0); + gf_list_rem(body_node->content, 0); + gf_xml_dom_node_del(content_node); + } + return; + } + } + } + } +} + +#define TTML_NAMESPACE "http://www.w3.org/ns/ttml" + +static s64 ttml_get_timestamp(const char *value) +{ + u32 h, m, s, ms; + s64 ts = -1; + if (sscanf(value, "%u:%u:%u.%u", &h, &m, &s, &ms) == 4) { + ts = (h*3600 + m*60+s)*1000+ms; + } else if (sscanf(value, "%u:%u:%u", &h, &m, &s) == 3) { + ts = (h*3600 + m*60+s)*1000; + } + return ts; +} + +static GF_Err ttml_push_interval(GF_TXTIn *ctx, s64 begin, s64 end) +{ + u32 i; + TTMLInterval *interval; + if (begin==-1) return GF_OK; + if (end==-1) return GF_OK; + + if (end < begin) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TTML EBU-TTD] invalid timings: \"begin\"="LLD" , \"end\"="LLD". Abort.\n", begin, end)); + return GF_NON_COMPLIANT_BITSTREAM; + } + + interval = NULL; + for (i=0; iintervals); i++) { + interval = gf_list_get(ctx->intervals, i); + //contained, do nothing + if ((begin>=interval->begin) && (end<=interval->end)) + return GF_OK; + //not overlapping + if ((end < interval->begin) || (begin > interval->end)) + continue; + + //new interval starts before current and end after, remove current and push extended interval + if ((begin < interval->end) && (end > interval->end)) { + if (begin>interval->begin) + begin = interval->begin; + gf_list_rem(ctx->intervals, i); + gf_free(interval); + return ttml_push_interval(ctx, begin, end); + } + //new interval starts before current and end before, remove current and push rewinded + if ((begin < interval->end) && (end <= interval->end)) { + end = interval->end; + if (begin>interval->begin) + begin = interval->begin; + gf_list_rem(ctx->intervals, i); + gf_free(interval); + return ttml_push_interval(ctx, begin, end); + } + } + //need a new interval + GF_SAFEALLOC(interval, TTMLInterval); + interval->begin = begin; + interval->end = end; + + for (i=0; iintervals); i++) { + TTMLInterval *an_interval = gf_list_get(ctx->intervals, i); + if (an_interval->begin > interval->begin) { + return gf_list_insert(ctx->intervals, interval, i); + } + } + return gf_list_add(ctx->intervals, interval); +} + +static void ttml_reset_intervals(GF_TXTIn *ctx) +{ + while (gf_list_count(ctx->intervals)) { + TTMLInterval *ival = gf_list_pop_back(ctx->intervals); + gf_free(ival); + } +} + +static GF_Err ttml_setup_intervals(GF_TXTIn *ctx) +{ + u32 k; + GF_Err e; + GF_XMLNode *root; + + if (!ctx->intervals) + ctx->intervals = gf_list_new(); + else + ttml_reset_intervals(ctx); + + root = gf_xml_dom_get_root(ctx->parser); + + for (k=0; knb_children; k++) { + u32 p_idx; + GF_XMLAttribute *p_att; + GF_XMLNode *p_node; + s64 begin=-1, end=-1; + GF_XMLNode *adiv_child = (GF_XMLNode*)gf_list_get(ctx->div_node->content, k); + if (adiv_child->type) continue; + e = gf_xml_get_element_check_namespace(adiv_child, "p", root->ns); + if (e) continue; + + p_idx = 0; + while ( (p_att = (GF_XMLAttribute*)gf_list_enum(adiv_child->attributes, &p_idx))) { + if (!strcmp(p_att->name, "begin")) { + begin = ttml_get_timestamp(p_att->value); + } + if (!strcmp(p_att->name, "end")) { + end = ttml_get_timestamp(p_att->value); + } + } + e = ttml_push_interval(ctx, begin, end); + if (e) return e; + + p_idx = 0; + while ( (p_node = (GF_XMLNode*)gf_list_enum(adiv_child->content, &p_idx))) { + s64 s_begin=-1, s_end=-1; + e = gf_xml_get_element_check_namespace(p_node, "span", root->ns); + if (e) continue; + + u32 span_idx = 0; + GF_XMLAttribute *span_att; + while ( (span_att = (GF_XMLAttribute*)gf_list_enum(p_node->attributes, &span_idx))) { + if (!strcmp(span_att->name, "begin")) { + s_begin = ttml_get_timestamp(span_att->value); + } else if (!strcmp(span_att->name, "end")) { + s_end = ttml_get_timestamp(span_att->value); + } + } + e = ttml_push_interval(ctx, s_begin, s_end); + if (e) return e; + } + } + + for (k=0; kintervals); k++) { + TTMLInterval *ival = gf_list_get(ctx->intervals, k); + GF_LOG(GF_LOG_INFO, GF_LOG_PARSER, ("[TTML EBU-TTD] Interval %d: "LLU"-"LLU"\n", k+1, ival->begin, ival->end)); + } + + return GF_OK; +} +static GF_Err gf_text_ttml_setup(GF_Filter *filter, GF_TXTIn *ctx) +{ + GF_Err e; + u32 i, nb_children, ID; + u64 file_size; + GF_XMLAttribute *att; + GF_XMLNode *root, *node, *body_node; + const char *lang = ctx->lang; + + + ctx->is_setup = GF_TRUE; + ctx->parser = gf_xml_dom_new(); + e = gf_xml_dom_parse(ctx->parser, ctx->file_name, ttxt_dom_progress, ctx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TXTIn] Error parsing TTML file: Line %d - %s. Abort.\n", gf_xml_dom_get_line(ctx->parser), gf_xml_dom_get_error(ctx->parser) )); + ctx->is_setup = GF_TRUE; + ctx->non_compliant_ttml = GF_TRUE; + return e; + } + root = gf_xml_dom_get_root(ctx->parser); + if (!root) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TXTIn] Error parsing TTML file: no root XML element found. Abort.\n")); + ctx->non_compliant_ttml = GF_TRUE; + return GF_NON_COMPLIANT_BITSTREAM; + } + + /*look for TTML*/ + if (gf_xml_get_element_check_namespace(root, "tt", NULL) != GF_OK) { + if (root->ns) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TTML file not recognized: root element is \"%s:%s\" (check your namespaces)\n", root->ns, root->name)); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("TTML file not recognized: root element is \"%s\"\n", root->name)); + } + ctx->non_compliant_ttml = GF_TRUE; + return GF_NOT_SUPPORTED; + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_PARSER, ("[TXTIn] TTML EBU-TTD detected\n")); + + root = gf_xml_dom_get_root(ctx->parser); + + + /*** root (including language) ***/ + i=0; + while ( (att = (GF_XMLAttribute *)gf_list_enum(root->attributes, &i))) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_PARSER, ("[TTML] Found root attribute name %s, value %s\n", att->name, att->value)); + + if (!strcmp(att->name, "xmlns")) { + if (strcmp(att->value, TTML_NAMESPACE)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TTML] XML Namespace %s not recognized, expecting %s\n", att->name, att->value, TTML_NAMESPACE)); + ctx->non_compliant_ttml = GF_TRUE; + return GF_NON_COMPLIANT_BITSTREAM; + } + } else if (!strcmp(att->name, "xml:lang") && att->value && strlen(att->value)) { + lang = att->value; + } + } + + //locate body + nb_children = gf_list_count(root->content); + body_node = NULL; + + i=0; + while ( (node = (GF_XMLNode*)gf_list_enum(root->content, &i))) { + if (node->type) { + nb_children--; + continue; + } + e = gf_xml_get_element_check_namespace(node, "body", root->ns); + if (e == GF_BAD_PARAM) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TTML EBU-TTD] ignored \"%s\" node, check your namespaces\n", node->name)); + } else if (e == GF_OK) { + if (body_node) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TTML EBU-TTD] duplicated \"body\" element. Abort.\n")); + ctx->non_compliant_ttml = GF_TRUE; + return GF_NON_COMPLIANT_BITSTREAM; + } + body_node = node; + } + } + if (!body_node) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TTML EBU-TTD] \"body\" element not found. Abort.\n")); + ctx->non_compliant_ttml = GF_TRUE; + return GF_NON_COMPLIANT_BITSTREAM; + } + + i=0; + while ( (node = (GF_XMLNode*)gf_list_enum(body_node->content, &i))) { + if (node->type) { + nb_children--; + continue; + } + e = gf_xml_get_element_check_namespace(node, "div", root->ns); + if (e == GF_BAD_PARAM) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TTML EBU-TTD] ignored \"%s\" node, check your namespaces\n", node->name)); + } else if (e == GF_OK) { + if (ctx->div_node) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TTML EBU-TTD] several \"div\" found in document, only the first one will be imported - not supported but patch is welcome\n")); + } + ctx->div_node = node; + } + } + file_size = ctx->end; + if (!ctx->timescale) ctx->timescale = 1000; + + if (!ctx->opid) ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_TEXT) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_SUBS_XML) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->timescale) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DOWN_SIZE, &PROP_LONGUINT(file_size) ); + + ID = 1; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ID, &PROP_UINT(ID) ); + if (ctx->width) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->width) ); + if (ctx->height) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->height) ); + if (ctx->zorder) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ZORDER, &PROP_SINT(ctx->zorder) ); + if (lang) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_LANGUAGE, &PROP_STRING( lang) ); + gf_filter_pid_set_property_str(ctx->opid, "meta:xmlns", &PROP_STRING(TTML_NAMESPACE) ); + + /*** body ***/ + ctx->parser_working_copy = gf_xml_dom_new(); + e = gf_xml_dom_parse(ctx->parser_working_copy, ctx->file_name, NULL, NULL); + assert (e == GF_OK); + ctx->root_working_copy = gf_xml_dom_get_root(ctx->parser_working_copy); + assert(ctx->root_working_copy); + + /*remove all the sample entries (instances in body) entries from the working copy, we will add each sample in this clone DOM to create full XML of each sample*/ + ebu_ttd_remove_samples(ctx->root_working_copy, &ctx->sample_list_node); + if (!ctx->sample_list_node) { + return GF_NON_COMPLIANT_BITSTREAM; + } + + ctx->nb_children = gf_list_count(ctx->div_node->content); + ctx->current_tt_interval = 0; + + ctx->last_sample_duration = 0; + ctx->end = 0; + ctx->first_samp = GF_TRUE; + + txtin_probe_duration(ctx); + + return ttml_setup_intervals(ctx); +} + +static Bool ttml_check_range(TTMLInterval *interval, s64 ts_begin, s64 ts_end) +{ + //if in current interval, push node + if ((ts_begin != -1) && (ts_end != -1) && ((ts_begin>=interval->begin) && (ts_end<=interval->end)) + ) { + return GF_TRUE; + } + //begin not set, end set: in range if end less than interval end range + else if ((ts_begin==-1) && (ts_end != -1) && (ts_end<=interval->end)) { + return GF_TRUE; + } + //begin set, end not set: in range if begin greater than interval begin range + else if ((ts_begin!=-1) && (ts_end==-1) && (ts_begin>=interval->begin)) { + return GF_TRUE; + } + return GF_FALSE; +} + +static GF_Err gf_text_process_ttml(GF_Filter *filter, GF_TXTIn *ctx) +{ + GF_Err e; + GF_XMLNode *root; + u32 i; + char *samp_text=NULL; + TTMLInterval *interval; + + if (!ctx->is_setup) return gf_text_ttml_setup(filter, ctx); + if (ctx->non_compliant_ttml || !ctx->opid) return GF_NOT_SUPPORTED; + if (!ctx->playstate) return GF_OK; + else if (ctx->playstate==2) return GF_EOS; + + if (ctx->seek_state==1) { + ctx->seek_state = 2; + ctx->current_tt_interval = 0; + } + + interval = gf_list_get(ctx->intervals, ctx->current_tt_interval); + if (!interval) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_PARSER, ("[TTML EBU-TTD] last_sample_duration="LLU", last_sample_end="LLU"\n", ctx->last_sample_duration, ctx->end)); + + gf_filter_pid_set_info_str( ctx->opid, "ttxt:last_dur", &PROP_UINT((u32) ctx->last_sample_duration) ); + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + ctx->current_tt_interval++; + + root = gf_xml_dom_get_root(ctx->parser); + + for (i=0; i < ctx->nb_children; i++) { + GF_XMLNode *p_node; + GF_XMLAttribute *p_att; + u32 p_idx = 0; + s64 ts_begin = -1, ts_end = -1; + Bool in_range; + GF_XMLNode *div_child = (GF_XMLNode*)gf_list_get(ctx->div_node->content, i); + if (div_child->type) { + continue; + } + e = gf_xml_get_element_check_namespace(div_child, "p", root->ns); + if (e == GF_BAD_PARAM) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TTML EBU-TTD] ignored \"%s\" node, check your namespaces\n", div_child->name)); + continue; + } + + + //sample is either in the

... + p_idx = 0; + while ( (p_att = (GF_XMLAttribute*)gf_list_enum(div_child->attributes, &p_idx))) { + if (!strcmp(p_att->name, "begin")) { + if (ts_begin != -1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TTML EBU-TTD] duplicated \"begin\" attribute. Abort.\n")); + e = GF_NON_COMPLIANT_BITSTREAM; + goto exit; + } + ts_begin = ttml_get_timestamp(p_att->value); + } else if (!strcmp(p_att->name, "end")) { + if (ts_end != -1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TTML EBU-TTD] duplicated \"end\" attribute. Abort.\n")); + e = GF_NON_COMPLIANT_BITSTREAM; + goto exit; + } + ts_end = ttml_get_timestamp(p_att->value); + } + } + + in_range = ttml_check_range(interval, ts_begin, ts_end); + if (in_range) { + e = gf_xml_dom_append_child(ctx->sample_list_node, div_child); + assert(e == GF_OK); + } + + //or under a + p_idx = 0; + while ( (p_node = (GF_XMLNode*)gf_list_enum(div_child->content, &p_idx))) { + u32 span_idx = 0; + GF_XMLAttribute *span_att; + e = gf_xml_get_element_check_namespace(p_node, "span", root->ns); + if (e == GF_BAD_PARAM) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TTML EBU-TTD] ignored \"%s\" node, check your namespaces\n", p_node->name)); + } + else if (e) + continue; + + ts_begin = ts_end = -1; + while ( (span_att = (GF_XMLAttribute*)gf_list_enum(p_node->attributes, &span_idx))) { + if (!strcmp(span_att->name, "begin")) { + if (ts_begin != -1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TTML EBU-TTD] duplicated \"begin\" attribute under . Abort.\n")); + e = GF_NON_COMPLIANT_BITSTREAM; + goto exit; + } + ts_begin = ttml_get_timestamp(span_att->value); + } else if (!strcmp(span_att->name, "end")) { + if (ts_end != -1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TTML EBU-TTD] duplicated \"end\" attribute under . Abort.\n")); + e = GF_NON_COMPLIANT_BITSTREAM; + goto exit; + } + ts_end = ttml_get_timestamp(span_att->value); + } + } + + /*append the entire

and break (we cannot split the text content)*/ + in_range = ttml_check_range(interval, ts_begin, ts_end); + if (in_range) { + e = gf_xml_dom_append_child(ctx->sample_list_node, div_child); + assert(e == GF_OK); + break; + } + } + } + + if (gf_list_count(ctx->sample_list_node->content)) { + samp_text = gf_xml_dom_serialize((GF_XMLNode*)ctx->root_working_copy, GF_FALSE); + gf_list_reset(ctx->sample_list_node->content); + } + + if (samp_text) { + GF_FilterPacket *pck; + u8 *pck_data; + Bool skip_pck = GF_FALSE; + u32 txt_len; + char *txt_str; + + + if (interval->begin < (s64) ctx->end) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TTML EBU-TTD] Error computing overlapped intervals! \"begin\" is "LLD" , last \"end\" was "LLD". Abort.\n", interval->begin, ctx->end)); + e = GF_NOT_SUPPORTED; + goto exit; + } + + txt_str = ttxt_parse_string(samp_text, GF_TRUE); + if (!txt_str) txt_str = ""; + txt_len = (u32) strlen(txt_str); + + if (ctx->first_samp) { + interval->begin = 0; /*in MP4 we must start at T=0*/ + ctx->first_samp = GF_FALSE; + } + + ctx->last_sample_duration = interval->end - interval->begin; + + ctx->end = interval->end; + GF_LOG(GF_LOG_DEBUG, GF_LOG_PARSER, ("ts_begin="LLD", ts_end="LLD", last_sample_duration="LLU" (real duration: "LLU"), last_sample_end="LLU"\n", interval->begin, interval->end, interval->end - ctx->end, ctx->last_sample_duration, ctx->end)); + + if (ctx->seek_state==2) { + Double end = (Double) interval->end; + end /= ctx->timescale; + if (endstart_range) skip_pck = GF_TRUE; + else ctx->seek_state = 0; + } + + if (!skip_pck) { + pck = gf_filter_pck_new_alloc(ctx->opid, txt_len, &pck_data); + memcpy(pck_data, txt_str, txt_len); + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + gf_filter_pck_set_cts(pck, (ctx->timescale * interval->begin)/1000); + gf_filter_pck_send(pck); + } + + gf_free(samp_text); + samp_text = NULL; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TTML EBU-TTD] empty sample (begin="LLD", end="LLD"). Skip.\n", interval->begin, interval->end)); + } + + return GF_OK; + + +exit: + if (!ctx->non_compliant_ttml) { + ctx->non_compliant_ttml = GF_TRUE; + gf_filter_pid_set_discard(ctx->ipid, GF_TRUE); + } + return e; +} + +#ifndef GPAC_DISABLE_SWF_IMPORT + +static GF_Err swf_svg_add_iso_sample(void *user, const u8 *data, u32 length, u64 timestamp, Bool isRap) +{ + GF_FilterPacket *pck; + u8 *pck_data; + GF_TXTIn *ctx = (GF_TXTIn *)user; + + if (ctx->seek_state==2) { + Double ts = (Double) timestamp; + ts/=1000; + if (tsstart_range) return GF_OK; + ctx->seek_state = 0; + } + + pck = gf_filter_pck_new_alloc(ctx->opid, length, &pck_data); + memcpy(pck_data, data, length); + gf_filter_pck_set_cts(pck, (u64) (ctx->timescale*timestamp/1000) ); + gf_filter_pck_set_sap(pck, isRap ? GF_FILTER_SAP_1 : GF_FILTER_SAP_NONE); + gf_filter_pck_set_framing(pck, GF_TRUE, GF_FALSE); + + gf_filter_pck_send(pck); + + if (gf_filter_pid_would_block(ctx->opid)) + ctx->do_suspend = GF_TRUE; + return GF_OK; +} + +static GF_Err swf_svg_add_iso_header(void *user, const u8 *data, u32 length, Bool isHeader) +{ + GF_TXTIn *ctx = (GF_TXTIn *)user; + + if (isHeader) { + if (!ctx->hdr_parsed) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA((char *)data, (u32) ( strlen(data)+1 ) ) ); + ctx->hdr_parsed = GF_TRUE; + } + } else if (!ctx->seek_state) { + GF_FilterPacket *pck; + u8 *pck_data; + pck = gf_filter_pck_new_alloc(ctx->opid, length, &pck_data); + memcpy(pck_data, data, length); + gf_filter_pck_set_framing(pck, GF_FALSE, GF_TRUE); + + gf_filter_pck_send(pck); + } + return GF_OK; +} + +static GF_Err gf_text_swf_setup(GF_Filter *filter, GF_TXTIn *ctx) +{ + GF_Err e; + u32 ID; + + ctx->swf_parse = gf_swf_reader_new(NULL, ctx->file_name); + e = gf_swf_read_header(ctx->swf_parse); + if (e) return e; + gf_swf_reader_set_user_mode(ctx->swf_parse, ctx, swf_svg_add_iso_sample, swf_svg_add_iso_header); + + if (!ctx->timescale) ctx->timescale = 1000; + + if (!ctx->opid) ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_TEXT) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_SIMPLE_TEXT) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->timescale) ); +// gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DOWN_SIZE, &PROP_UINT(file_size) ); + + //patch for old arch + ctx->width = FIX2INT(ctx->swf_parse->width); + ctx->height = FIX2INT(ctx->swf_parse->height); + if (!ctx->width && !ctx->height) { + ctx->width = 400; + ctx->height = 60; + } + ID = 1; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ID, &PROP_UINT(ID) ); + if (ctx->width) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->width) ); + if (ctx->height) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->height) ); + if (ctx->zorder) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ZORDER, &PROP_SINT(ctx->zorder) ); + if (ctx->lang) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_LANGUAGE, &PROP_STRING( ctx->lang) ); + + gf_filter_pid_set_property_str(ctx->opid, "meta:mime", &PROP_STRING("image/svg+xml") ); + +#ifndef GPAC_DISABLE_SVG + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TXTIn] swf -> svg not fully migrated, using SWF flags 0 and no flatten angle. Patch welcome\n")); + e = swf_to_svg_init(ctx->swf_parse, 0, 0); +#endif + + //SWF->BIFS is handled in ctx loader, no need to define it here + txtin_probe_duration(ctx); + + return e; +} + +static GF_Err gf_text_process_swf(GF_Filter *filter, GF_TXTIn *ctx) +{ + GF_Err e=GF_OK; + + if (!ctx->is_setup) { + ctx->is_setup = GF_TRUE; + return gf_text_swf_setup(filter, ctx); + } + if (!ctx->opid) return GF_NOT_SUPPORTED; + + if (ctx->seek_state==1) { + ctx->seek_state = 2; + gf_swf_reader_del(ctx->swf_parse); + ctx->swf_parse = gf_swf_reader_new(NULL, ctx->file_name); + gf_swf_read_header(ctx->swf_parse); + gf_swf_reader_set_user_mode(ctx->swf_parse, ctx, swf_svg_add_iso_sample, swf_svg_add_iso_header); + } + + ctx->do_suspend = GF_FALSE; + /*parse all tags*/ + while (e == GF_OK) { + e = swf_parse_tag(ctx->swf_parse); + if (ctx->do_suspend) return GF_OK; + } + if (e==GF_EOS) { + if (ctx->swf_parse->finalize) { + ctx->swf_parse->finalize(ctx->swf_parse); + ctx->swf_parse->finalize = NULL; + } + } + return e; +} +/* end of SWF Importer */ + +#else + +#ifndef GPAC_DISABLE_ZLIB +static GF_Err gf_text_process_swf(GF_Filter *filter, GF_TXTIn *ctx) +{ + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("Warning: GPAC was compiled without SWF import support, can't import file.\n")); + return GF_NOT_SUPPORTED; +} +#endif + + +#endif /*GPAC_DISABLE_SWF_IMPORT*/ + +static GF_Err gf_text_process_sub(GF_Filter *filter, GF_TXTIn *ctx) +{ + u32 i, j, len, line; + GF_TextSample *samp; + Double ts_scale; + char szLine[2048], szTime[20], szText[2048]; + + //same setup as for srt + if (!ctx->is_setup) { + ctx->is_setup = GF_TRUE; + return txtin_setup_srt(filter, ctx); + } + if (!ctx->opid) return GF_NOT_SUPPORTED; + if (!ctx->playstate) return GF_OK; + else if (ctx->playstate==2) return GF_EOS; + + if (ctx->seek_state==1) { + ctx->seek_state = 2; + gf_fseek(ctx->src, 0, SEEK_SET); + } + + if (ctx->fps.den && ctx->fps.num) { + ts_scale = ((Double) ctx->fps.num) / ctx->fps.den; + } else { + ts_scale = 25; + } + + line = 0; + + while (1) { + char *sOK = gf_text_get_utf8_line(szLine, 2048, ctx->src, ctx->unicode_type); + if (!sOK) break; + + REM_TRAIL_MARKS(szLine, "\r\n\t ") + + line++; + len = (u32) strlen(szLine); + if (!len) continue; + + i=0; + if (szLine[i] != '{') { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TXTIn] Bad SUB file (line %d): expecting \"{\" got \"%c\"\n", line, szLine[i])); + continue; + } + while (szLine[i+1] && szLine[i+1]!='}') { + szTime[i] = szLine[i+1]; + i++; + } + szTime[i] = 0; + ctx->start = atoi(szTime); + if (ctx->start < ctx->end) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TXTIn] corrupted SUB frame (line %d) - starts (at %d ms) before end of previous one (%d ms) - adjusting time stamps\n", line, ctx->start, ctx->end)); + ctx->start = ctx->end; + } + j=i+2; + i=0; + if (szLine[i+j] != '{') { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TXTIn] Bad SUB file - expecting \"{\" got \"%c\"\n", szLine[i])); + continue; + } + while (szLine[i+1+j] && szLine[i+1+j]!='}') { + szTime[i] = szLine[i+1+j]; + i++; + } + szTime[i] = 0; + ctx->end = atoi(szTime); + j+=i+2; + + if (ctx->start > ctx->end) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TXTIn] corrupted SUB frame (line %d) - ends (at %d ms) before start of current frame (%d ms) - skipping\n", line, ctx->end, ctx->start)); + continue; + } + + if (ctx->start && ctx->first_samp) { + samp = gf_isom_new_text_sample(); + txtin_process_send_text_sample(ctx, samp, 0, (u32) (ts_scale*ctx->start), GF_TRUE); + ctx->first_samp = GF_FALSE; + gf_isom_delete_text_sample(samp); + } + + for (i=j; iprev_end) { + samp = gf_isom_new_text_sample(); + txtin_process_send_text_sample(ctx, samp, (u64) (ts_scale*(s64)ctx->prev_end), (u32) (ts_scale*(ctx->prev_end - ctx->start)), GF_TRUE); + gf_isom_delete_text_sample(samp); + } + + samp = gf_isom_new_text_sample(); + gf_isom_text_add_text(samp, szText, (u32) strlen(szText) ); + txtin_process_send_text_sample(ctx, samp, (u64) (ts_scale*(s64)ctx->start), (u32) (ts_scale*(ctx->end - ctx->start)), GF_TRUE); + gf_isom_delete_text_sample(samp); + + ctx->prev_end = ctx->end; + + gf_filter_pid_set_info(ctx->opid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT( gf_ftell(ctx->src )) ); + + if (gf_filter_pid_would_block(ctx->opid)) + return GF_OK; + } + /*final flush*/ + if (ctx->end && !ctx->noflush) { + samp = gf_isom_new_text_sample(); + txtin_process_send_text_sample(ctx, samp, (u64) (ts_scale*(s64)ctx->end), 0, GF_TRUE); + gf_isom_delete_text_sample(samp); + } + + gf_filter_pid_set_info_str( ctx->opid, "ttxt:last_dur", &PROP_UINT(0) ); + + return GF_EOS; +} + + +#define CHECK_STR(__str) \ + if (!__str) { \ + e = gf_import_message(import, GF_BAD_PARAM, "Invalid XML formatting (line %d)", parser.line); \ + goto exit; \ + } \ + + +static u32 ttxt_get_color(char *val) +{ + u32 r, g, b, a, res; + r = g = b = a = 0; + if (sscanf(val, "%x %x %x %x", &r, &g, &b, &a) != 4) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[TXTIn] Warning: color badly formatted %s\n", val)); + } + res = (a&0xFF); + res<<=8; + res |= (r&0xFF); + res<<=8; + res |= (g&0xFF); + res<<=8; + res |= (b&0xFF); + return res; +} + +static void ttxt_parse_text_box(GF_XMLNode *n, GF_BoxRecord *box) +{ + u32 i=0; + GF_XMLAttribute *att; + memset(box, 0, sizeof(GF_BoxRecord)); + while ( (att=(GF_XMLAttribute *)gf_list_enum(n->attributes, &i))) { + if (!stricmp(att->name, "top")) box->top = atoi(att->value); + else if (!stricmp(att->name, "bottom")) box->bottom = atoi(att->value); + else if (!stricmp(att->name, "left")) box->left = atoi(att->value); + else if (!stricmp(att->name, "right")) box->right = atoi(att->value); + } +} + +static void ttxt_parse_text_style(GF_TXTIn *ctx, GF_XMLNode *n, GF_StyleRecord *style) +{ + u32 i=0; + GF_XMLAttribute *att; + memset(style, 0, sizeof(GF_StyleRecord)); + style->fontID = 1; + style->font_size = ctx->fontsize ; + style->text_color = 0xFFFFFFFF; + + while ( (att=(GF_XMLAttribute *)gf_list_enum(n->attributes, &i))) { + if (!stricmp(att->name, "fromChar")) style->startCharOffset = atoi(att->value); + else if (!stricmp(att->name, "toChar")) style->endCharOffset = atoi(att->value); + else if (!stricmp(att->name, "fontID")) style->fontID = atoi(att->value); + else if (!stricmp(att->name, "fontSize")) style->font_size = atoi(att->value); + else if (!stricmp(att->name, "color")) style->text_color = ttxt_get_color(att->value); + else if (!stricmp(att->name, "styles")) { + if (strstr(att->value, "Bold")) style->style_flags |= GF_TXT_STYLE_BOLD; + if (strstr(att->value, "Italic")) style->style_flags |= GF_TXT_STYLE_ITALIC; + if (strstr(att->value, "Underlined")) style->style_flags |= GF_TXT_STYLE_UNDERLINED; + } + } +} + +static GF_Err txtin_setup_ttxt(GF_Filter *filter, GF_TXTIn *ctx) +{ + GF_Err e; + u32 j, k, ID, OCR_ES_ID; + u64 file_size; + GF_XMLNode *root, *ext; + GF_PropertyValue *dcd; + + ctx->parser = gf_xml_dom_new(); + e = gf_xml_dom_parse(ctx->parser, ctx->file_name, ttxt_dom_progress, ctx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TXTIn] Error parsing TTXT file: Line %d - %s\n", gf_xml_dom_get_line(ctx->parser), gf_xml_dom_get_error(ctx->parser))); + return e; + } + root = gf_xml_dom_get_root(ctx->parser); + + if (strcmp(root->name, "TextStream")) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TXTIn] Invalid Timed Text file - expecting \"TextStream\" got %s", root->name)); + return GF_NON_COMPLIANT_BITSTREAM; + } + file_size = ctx->end; + ctx->end = 0; + + /*setup track in 3GP format directly (no ES desc)*/ + if (!ctx->timescale) ctx->timescale = 1000; + OCR_ES_ID = ID = 0; + + if (!ctx->opid) ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_TEXT) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_TX3G) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->timescale) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DOWN_SIZE, &PROP_LONGUINT(file_size) ); + + if (!ID) ID = 1; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ID, &PROP_UINT(ID) ); + if (OCR_ES_ID) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CLOCK_ID, &PROP_UINT(OCR_ES_ID) ); + + ctx->nb_children = gf_list_count(root->content); + + ctx->cur_child_idx = 0; + for (ctx->cur_child_idx=0; ctx->cur_child_idx < ctx->nb_children; ctx->cur_child_idx++) { + GF_XMLNode *node = (GF_XMLNode*) gf_list_get(root->content, ctx->cur_child_idx); + + if (node->type) { + continue; + } + + if (!strcmp(node->name, "TextStreamHeader")) { + GF_XMLNode *sdesc; + s32 w, h, tx, ty, layer; + u32 tref_id; + GF_XMLAttribute *att; + w = ctx->width; + h = ctx->height; + tx = ctx->txtx; + ty = ctx->txty; + layer = ctx->zorder; + tref_id = 0; + + j=0; + while ( (att=(GF_XMLAttribute *)gf_list_enum(node->attributes, &j))) { + if (!strcmp(att->name, "width")) w = atoi(att->value); + else if (!strcmp(att->name, "height")) h = atoi(att->value); + else if (!strcmp(att->name, "layer")) layer = atoi(att->value); + else if (!strcmp(att->name, "translation_x")) tx = atoi(att->value); + else if (!strcmp(att->name, "translation_y")) ty = atoi(att->value); + else if (!strcmp(att->name, "trefID")) tref_id = atoi(att->value); + } + + if (tref_id) { + gf_filter_pid_set_property_str(ctx->opid, "tref:chap", &PROP_UINT(tref_id) ); + } + + if (w) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(w) ); + if (h) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(h) ); + if (tx) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TRANS_X, &PROP_UINT(tx) ); + if (ty) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TRANS_X, &PROP_UINT(ty) ); + if (layer) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ZORDER, &PROP_SINT(ctx->zorder) ); + if (ctx->lang) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_LANGUAGE, &PROP_STRING( ctx->lang) ); + + j=0; + while ( (sdesc=(GF_XMLNode*)gf_list_enum(node->content, &j))) { + if (sdesc->type) continue; + + if (!strcmp(sdesc->name, "TextSampleDescription")) { + GF_TextSampleDescriptor td; + memset(&td, 0, sizeof(GF_TextSampleDescriptor)); + td.tag = GF_ODF_TEXT_CFG_TAG; + td.vert_justif = (s8) -1; + td.default_style.fontID = 1; + td.default_style.font_size = ctx->fontsize; + + k=0; + while ( (att=(GF_XMLAttribute *)gf_list_enum(sdesc->attributes, &k))) { + if (!strcmp(att->name, "horizontalJustification")) { + if (!stricmp(att->value, "center")) td.horiz_justif = 1; + else if (!stricmp(att->value, "right")) td.horiz_justif = (s8) -1; + else if (!stricmp(att->value, "left")) td.horiz_justif = 0; + } + else if (!strcmp(att->name, "verticalJustification")) { + if (!stricmp(att->value, "center")) td.vert_justif = 1; + else if (!stricmp(att->value, "bottom")) td.vert_justif = (s8) -1; + else if (!stricmp(att->value, "top")) td.vert_justif = 0; + } + else if (!strcmp(att->name, "backColor")) td.back_color = ttxt_get_color(att->value); + else if (!strcmp(att->name, "verticalText") && !stricmp(att->value, "yes") ) td.displayFlags |= GF_TXT_VERTICAL; + else if (!strcmp(att->name, "fillTextRegion") && !stricmp(att->value, "yes") ) td.displayFlags |= GF_TXT_FILL_REGION; + else if (!strcmp(att->name, "continuousKaraoke") && !stricmp(att->value, "yes") ) td.displayFlags |= GF_TXT_KARAOKE; + else if (!strcmp(att->name, "scroll")) { + if (!stricmp(att->value, "inout")) td.displayFlags |= GF_TXT_SCROLL_IN | GF_TXT_SCROLL_OUT; + else if (!stricmp(att->value, "in")) td.displayFlags |= GF_TXT_SCROLL_IN; + else if (!stricmp(att->value, "out")) td.displayFlags |= GF_TXT_SCROLL_OUT; + } + else if (!strcmp(att->name, "scrollMode")) { + u32 scroll_mode = GF_TXT_SCROLL_CREDITS; + if (!stricmp(att->value, "Credits")) scroll_mode = GF_TXT_SCROLL_CREDITS; + else if (!stricmp(att->value, "Marquee")) scroll_mode = GF_TXT_SCROLL_MARQUEE; + else if (!stricmp(att->value, "Right")) scroll_mode = GF_TXT_SCROLL_RIGHT; + else if (!stricmp(att->value, "Down")) scroll_mode = GF_TXT_SCROLL_DOWN; + td.displayFlags |= ((scroll_mode<<7) & GF_TXT_SCROLL_DIRECTION); + } + } + + k=0; + while ( (ext=(GF_XMLNode*)gf_list_enum(sdesc->content, &k))) { + if (ext->type) continue; + if (!strcmp(ext->name, "TextBox")) ttxt_parse_text_box(ext, &td.default_pos); + else if (!strcmp(ext->name, "Style")) ttxt_parse_text_style(ctx, ext, &td.default_style); + else if (!strcmp(ext->name, "FontTable")) { + GF_XMLNode *ftable; + u32 z=0; + while ( (ftable=(GF_XMLNode*)gf_list_enum(ext->content, &z))) { + u32 m; + if (ftable->type || strcmp(ftable->name, "FontTableEntry")) continue; + td.font_count += 1; + td.fonts = (GF_FontRecord*)gf_realloc(td.fonts, sizeof(GF_FontRecord)*td.font_count); + m=0; + while ( (att=(GF_XMLAttribute *)gf_list_enum(ftable->attributes, &m))) { + if (!stricmp(att->name, "fontID")) td.fonts[td.font_count-1].fontID = atoi(att->value); + else if (!stricmp(att->name, "fontName")) td.fonts[td.font_count-1].fontName = gf_strdup(att->value); + } + } + } + } + if (ctx->nodefbox) { + td.default_pos.top = td.default_pos.left = td.default_pos.right = td.default_pos.bottom = 0; + } else { + if ((td.default_pos.bottom==td.default_pos.top) || (td.default_pos.right==td.default_pos.left)) { + td.default_pos.top = td.default_pos.left = 0; + td.default_pos.right = w; + td.default_pos.bottom = h; + } + } + if (!td.fonts) { + td.font_count = 1; + td.fonts = (GF_FontRecord*)gf_malloc(sizeof(GF_FontRecord)); + td.fonts[0].fontID = 1; + td.fonts[0].fontName = gf_strdup("Serif"); + } + GF_SAFEALLOC(dcd, GF_PropertyValue); + if (dcd) { + dcd->type = GF_PROP_DATA; + + gf_odf_tx3g_write(&td, &dcd->value.data.ptr, &dcd->value.data.size); + if (!ctx->text_descs) ctx->text_descs = gf_list_new(); + gf_list_add(ctx->text_descs, dcd); + } + + for (k=0; ktext_descs) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TXTIn] Invalid Timed Text file - text stream header not found or empty\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + dcd = gf_list_get(ctx->text_descs, 0); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, dcd); + ctx->last_desc_idx = 1; + + ctx->first_samp = GF_TRUE; + ctx->last_sample_empty = GF_FALSE; + ctx->last_sample_duration = 0; + + txtin_probe_duration(ctx); + + return GF_OK; +} + +static GF_Err txtin_process_ttxt(GF_Filter *filter, GF_TXTIn *ctx) +{ + u32 j, k; + GF_XMLNode *root, *ext; + + if (!ctx->is_setup) { + ctx->is_setup = GF_TRUE; + return txtin_setup_ttxt(filter, ctx); + } + if (!ctx->opid) return GF_NON_COMPLIANT_BITSTREAM; + if (!ctx->playstate) return GF_OK; + else if (ctx->playstate==2) return GF_EOS; + + if (ctx->seek_state==1) { + ctx->seek_state = 2; + ctx->cur_child_idx = 0; + } + root = gf_xml_dom_get_root(ctx->parser); + + for (; ctx->cur_child_idx < ctx->nb_children; ctx->cur_child_idx++) { + GF_TextSample * samp; + u32 ts, descIndex; + Bool has_text = GF_FALSE; + GF_XMLAttribute *att; + GF_XMLNode *node = (GF_XMLNode*) gf_list_get(root->content, ctx->cur_child_idx); + + if (node->type) { + continue; + } + /*sample text*/ + else if (strcmp(node->name, "TextSample")) continue; + + samp = gf_isom_new_text_sample(); + ts = 0; + descIndex = 1; + ctx->last_sample_empty = GF_TRUE; + + j=0; + while ( (att=(GF_XMLAttribute*)gf_list_enum(node->attributes, &j))) { + if (!strcmp(att->name, "sampleTime")) { + u32 h, m, s, ms; + if (sscanf(att->value, "%u:%u:%u.%u", &h, &m, &s, &ms) == 4) { + ts = (h*3600 + m*60 + s)*1000 + ms; + } else { + ts = (u32) (atof(att->value) * 1000); + } + } + else if (!strcmp(att->name, "sampleDescriptionIndex")) descIndex = atoi(att->value); + else if (!strcmp(att->name, "text")) { + u32 len; + char *str = ttxt_parse_string(att->value, GF_TRUE); + len = (u32) strlen(str); + gf_isom_text_add_text(samp, str, len); + ctx->last_sample_empty = len ? GF_FALSE : GF_TRUE; + has_text = GF_TRUE; + } + else if (!strcmp(att->name, "scrollDelay")) gf_isom_text_set_scroll_delay(samp, (u32) (1000*atoi(att->value))); + else if (!strcmp(att->name, "highlightColor")) gf_isom_text_set_highlight_color(samp, ttxt_get_color(att->value)); + else if (!strcmp(att->name, "wrap") && !strcmp(att->value, "Automatic")) gf_isom_text_set_wrap(samp, 0x01); + } + + /*get all modifiers*/ + j=0; + while ( (ext=(GF_XMLNode*)gf_list_enum(node->content, &j))) { + if (!has_text && (ext->type==GF_XML_TEXT_TYPE)) { + u32 len; + char *str = ttxt_parse_string(ext->name, GF_FALSE); + len = (u32) strlen(str); + gf_isom_text_add_text(samp, str, len); + ctx->last_sample_empty = len ? GF_FALSE : GF_TRUE; + has_text = GF_TRUE; + } + if (ext->type) continue; + + if (!stricmp(ext->name, "Style")) { + GF_StyleRecord r; + ttxt_parse_text_style(ctx, ext, &r); + gf_isom_text_add_style(samp, &r); + } + else if (!stricmp(ext->name, "TextBox")) { + GF_BoxRecord r; + ttxt_parse_text_box(ext, &r); + gf_isom_text_set_box(samp, r.top, r.left, r.bottom, r.right); + } + else if (!stricmp(ext->name, "Highlight")) { + u16 start, end; + start = end = 0; + k=0; + while ( (att=(GF_XMLAttribute *)gf_list_enum(ext->attributes, &k))) { + if (!strcmp(att->name, "fromChar")) start = atoi(att->value); + else if (!strcmp(att->name, "toChar")) end = atoi(att->value); + } + gf_isom_text_add_highlight(samp, start, end); + } + else if (!stricmp(ext->name, "Blinking")) { + u16 start, end; + start = end = 0; + k=0; + while ( (att=(GF_XMLAttribute *)gf_list_enum(ext->attributes, &k))) { + if (!strcmp(att->name, "fromChar")) start = atoi(att->value); + else if (!strcmp(att->name, "toChar")) end = atoi(att->value); + } + gf_isom_text_add_blink(samp, start, end); + } + else if (!stricmp(ext->name, "HyperLink")) { + u16 start, end; + char *url, *url_tt; + start = end = 0; + url = url_tt = NULL; + k=0; + while ( (att=(GF_XMLAttribute *)gf_list_enum(ext->attributes, &k))) { + if (!strcmp(att->name, "fromChar")) start = atoi(att->value); + else if (!strcmp(att->name, "toChar")) end = atoi(att->value); + else if (!strcmp(att->name, "URL")) url = gf_strdup(att->value); + else if (!strcmp(att->name, "URLToolTip")) url_tt = gf_strdup(att->value); + } + gf_isom_text_add_hyperlink(samp, url, url_tt, start, end); + if (url) gf_free(url); + if (url_tt) gf_free(url_tt); + } + else if (!stricmp(ext->name, "Karaoke")) { + u32 startTime; + GF_XMLNode *krok; + startTime = 0; + k=0; + while ( (att=(GF_XMLAttribute *)gf_list_enum(ext->attributes, &k))) { + if (!strcmp(att->name, "startTime")) startTime = (u32) (1000*atof(att->value)); + } + gf_isom_text_add_karaoke(samp, startTime); + k=0; + while ( (krok=(GF_XMLNode*)gf_list_enum(ext->content, &k))) { + u16 start, end; + u32 endTime, m; + if (krok->type) continue; + if (strcmp(krok->name, "KaraokeRange")) continue; + start = end = 0; + endTime = 0; + m=0; + while ( (att=(GF_XMLAttribute *)gf_list_enum(krok->attributes, &m))) { + if (!strcmp(att->name, "fromChar")) start = atoi(att->value); + else if (!strcmp(att->name, "toChar")) end = atoi(att->value); + else if (!strcmp(att->name, "endTime")) endTime = (u32) (1000*atof(att->value)); + } + gf_isom_text_set_karaoke_segment(samp, endTime, start, end); + } + } + } + + if (!descIndex) descIndex = 1; + if (descIndex != ctx->last_desc_idx) { + GF_PropertyValue *dcd; + ctx->last_desc_idx = descIndex; + dcd = gf_list_get(ctx->text_descs, descIndex-1); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, dcd); + } + + /*in MP4 we must start at T=0, so add an empty sample*/ + if (ts && ctx->first_samp) { + GF_TextSample * firstsamp = gf_isom_new_text_sample(); + txtin_process_send_text_sample(ctx, firstsamp, 0, 0, GF_TRUE); + gf_isom_delete_text_sample(firstsamp); + } + ctx->first_samp = GF_FALSE; + + txtin_process_send_text_sample(ctx, samp, ts, 0, GF_TRUE); + + gf_isom_delete_text_sample(samp); + + if (ctx->last_sample_empty) { + ctx->last_sample_duration = ts - ctx->last_sample_duration; + } else { + ctx->last_sample_duration = ts; + } + + if (gf_filter_pid_would_block(ctx->opid)) { + ctx->cur_child_idx++; + return GF_OK; + } + } + + if (ctx->last_sample_empty) { + //this is a bit ugly, in regular streaming mode we don't want to remove empty samples + //howvere the last one can be removed, adjusting the duration of the previous one. + //doing this here is problematic if the loader is sent a new ttxt file, we would have a cue termination sample + //we therefore share that info through pid, and let the final user (muxer& co) decide what to do + gf_filter_pid_set_info_str( ctx->opid, "ttxt:rem_last", &PROP_BOOL(GF_TRUE) ); + gf_filter_pid_set_info_str( ctx->opid, "ttxt:last_dur", &PROP_UINT((u32) ctx->last_sample_duration) ); + } + + return GF_EOS; +} + + +static u32 tx3g_get_color(char *value) +{ + u32 r, g, b, a; + u32 res, v; + r = g = b = a = 0; + if (sscanf(value, "%u%%, %u%%, %u%%, %u%%", &r, &g, &b, &a) != 4) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("Warning: color badly formatted\n")); + } + v = (u32) (a*255/100); + res = (v&0xFF); + res<<=8; + v = (u32) (r*255/100); + res |= (v&0xFF); + res<<=8; + v = (u32) (g*255/100); + res |= (v&0xFF); + res<<=8; + v = (u32) (b*255/100); + res |= (v&0xFF); + return res; +} + +static void tx3g_parse_text_box(GF_XMLNode *n, GF_BoxRecord *box) +{ + u32 i=0; + GF_XMLAttribute *att; + memset(box, 0, sizeof(GF_BoxRecord)); + while ((att=(GF_XMLAttribute *)gf_list_enum(n->attributes, &i))) { + if (!stricmp(att->name, "x")) box->left = atoi(att->value); + else if (!stricmp(att->name, "y")) box->top = atoi(att->value); + else if (!stricmp(att->name, "height")) box->bottom = atoi(att->value); + else if (!stricmp(att->name, "width")) box->right = atoi(att->value); + } +} + +typedef struct +{ + u32 id; + u32 pos; +} Marker; + +#define GET_MARKER_POS(_val, __isend) \ + { \ + u32 i, __m = atoi(att->value); \ + _val = 0; \ + for (i=0; iparser = gf_xml_dom_new(); + e = gf_xml_dom_parse(ctx->parser, ctx->file_name, ttxt_dom_progress, ctx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TXTIn] Error parsing TeXML file: Line %d - %s", gf_xml_dom_get_line(ctx->parser), gf_xml_dom_get_error(ctx->parser) )); + gf_xml_dom_del(ctx->parser); + ctx->parser = NULL; + return e; + } + + root = gf_xml_dom_get_root(ctx->parser); + + if (strcmp(root->name, "text3GTrack")) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TXTIn] Invalid QT TeXML file - expecting root \"text3GTrack\" got \"%s\"", root->name)); + return GF_NON_COMPLIANT_BITSTREAM; + } + file_size = ctx->end; + ctx->txml_timescale = 600; + + i=0; + while ( (att=(GF_XMLAttribute *)gf_list_enum(root->attributes, &i))) { + if (!strcmp(att->name, "trackWidth")) ctx->width = atoi(att->value); + else if (!strcmp(att->name, "trackHeight")) ctx->height = atoi(att->value); + else if (!strcmp(att->name, "layer")) ctx->zorder = atoi(att->value); + else if (!strcmp(att->name, "timeScale")) ctx->txml_timescale = atoi(att->value); + else if (!strcmp(att->name, "transform")) { + Float fx, fy; + sscanf(att->value, "translate(%f,%f)", &fx, &fy); + ctx->txtx = (u32) fx; + ctx->txty = (u32) fy; + } + } + + /*setup track in 3GP format directly (no ES desc)*/ + OCR_ES_ID = ID = 0; + if (!ctx->timescale) ctx->timescale = 1000; + + if (!ctx->opid) ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_TEXT) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_ISOM_SUBTYPE_TX3G) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->timescale) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DOWN_SIZE, &PROP_LONGUINT(file_size) ); + + + if (!ID) ID = 1; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ID, &PROP_UINT(ID) ); + if (OCR_ES_ID) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CLOCK_ID, &PROP_UINT(OCR_ES_ID) ); + if (ctx->width) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->width) ); + if (ctx->height) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->height) ); + if (ctx->zorder) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ZORDER, &PROP_SINT(ctx->zorder) ); + if (ctx->lang) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_LANGUAGE, &PROP_STRING( ctx->lang) ); + + + ctx->nb_children = gf_list_count(root->content); + ctx->cur_child_idx = 0; + txtin_probe_duration(ctx); + + return GF_OK; +} + +static GF_Err txtin_process_texml(GF_Filter *filter, GF_TXTIn *ctx) +{ + u32 j, k; + GF_StyleRecord styles[50]; + Marker marks[50]; + GF_XMLAttribute *att; + GF_XMLNode *root; + Bool probe_first_desc_only = GF_FALSE; + + if (!ctx->is_setup) { + GF_Err e; + + ctx->is_setup = GF_TRUE; + e = txtin_texml_setup(filter, ctx); + if (e) return e; + probe_first_desc_only = GF_TRUE; + } + if (!ctx->opid) return GF_NON_COMPLIANT_BITSTREAM; + if (!ctx->playstate && !probe_first_desc_only) return GF_OK; + else if (ctx->playstate==2) return GF_EOS; + + if (ctx->seek_state==1) { + ctx->seek_state = 2; + ctx->cur_child_idx = 0; + ctx->start = 0; + } + + root = gf_xml_dom_get_root(ctx->parser); + + for (; ctx->cur_child_idx < ctx->nb_children; ctx->cur_child_idx++) { + GF_XMLNode *node, *desc; + GF_TextSampleDescriptor td; + GF_TextSample * samp = NULL; + u64 duration; + u32 nb_styles, nb_marks; + Bool isRAP, same_style, same_box; + + if (probe_first_desc_only && ctx->text_descs && gf_list_count(ctx->text_descs)) + return GF_OK; + + memset(&td, 0, sizeof(GF_TextSampleDescriptor)); + node = (GF_XMLNode*)gf_list_get(root->content, ctx->cur_child_idx); + if (node->type) continue; + if (strcmp(node->name, "sample")) continue; + + isRAP = GF_TRUE; + duration = 1000; + j=0; + while ((att=(GF_XMLAttribute *)gf_list_enum(node->attributes, &j))) { + if (!strcmp(att->name, "duration")) duration = atoi(att->value); + else if (!strcmp(att->name, "keyframe")) isRAP = (!stricmp(att->value, "true") ? GF_TRUE : GF_FALSE); + } + nb_styles = 0; + nb_marks = 0; + same_style = same_box = GF_FALSE; + j=0; + while ((desc=(GF_XMLNode*)gf_list_enum(node->content, &j))) { + if (desc->type) continue; + + if (!strcmp(desc->name, "description")) { + u8 *dsi; + u32 dsi_len, stsd_idx; + GF_XMLNode *sub; + memset(&td, 0, sizeof(GF_TextSampleDescriptor)); + td.tag = GF_ODF_TEXT_CFG_TAG; + td.vert_justif = (s8) -1; + td.default_style.fontID = 1; + td.default_style.font_size = ctx->fontsize; + + k=0; + while ((att=(GF_XMLAttribute *)gf_list_enum(desc->attributes, &k))) { + if (!strcmp(att->name, "horizontalJustification")) { + if (!stricmp(att->value, "center")) td.horiz_justif = 1; + else if (!stricmp(att->value, "right")) td.horiz_justif = (s8) -1; + else if (!stricmp(att->value, "left")) td.horiz_justif = 0; + } + else if (!strcmp(att->name, "verticalJustification")) { + if (!stricmp(att->value, "center")) td.vert_justif = 1; + else if (!stricmp(att->value, "bottom")) td.vert_justif = (s8) -1; + else if (!stricmp(att->value, "top")) td.vert_justif = 0; + } + else if (!strcmp(att->name, "backgroundColor")) td.back_color = tx3g_get_color(att->value); + else if (!strcmp(att->name, "displayFlags")) { + Bool rev_scroll = GF_FALSE; + if (strstr(att->value, "scroll")) { + u32 scroll_mode = 0; + if (strstr(att->value, "scrollIn")) td.displayFlags |= GF_TXT_SCROLL_IN; + if (strstr(att->value, "scrollOut")) td.displayFlags |= GF_TXT_SCROLL_OUT; + if (strstr(att->value, "reverse")) rev_scroll = GF_TRUE; + if (strstr(att->value, "horizontal")) scroll_mode = rev_scroll ? GF_TXT_SCROLL_RIGHT : GF_TXT_SCROLL_MARQUEE; + else scroll_mode = (rev_scroll ? GF_TXT_SCROLL_DOWN : GF_TXT_SCROLL_CREDITS); + td.displayFlags |= (scroll_mode<<7) & GF_TXT_SCROLL_DIRECTION; + } + /*TODO FIXME: check in QT doc !!*/ + if (strstr(att->value, "writeTextVertically")) td.displayFlags |= GF_TXT_VERTICAL; + if (!strcmp(att->name, "continuousKaraoke")) td.displayFlags |= GF_TXT_KARAOKE; + } + } + + k=0; + while ((sub=(GF_XMLNode*)gf_list_enum(desc->content, &k))) { + if (sub->type) continue; + if (!strcmp(sub->name, "defaultTextBox")) tx3g_parse_text_box(sub, &td.default_pos); + else if (!strcmp(sub->name, "fontTable")) { + GF_XMLNode *ftable; + u32 m=0; + while ((ftable=(GF_XMLNode*)gf_list_enum(sub->content, &m))) { + if (ftable->type) continue; + if (!strcmp(ftable->name, "font")) { + u32 n=0; + td.font_count += 1; + td.fonts = (GF_FontRecord*)gf_realloc(td.fonts, sizeof(GF_FontRecord)*td.font_count); + while ((att=(GF_XMLAttribute *)gf_list_enum(ftable->attributes, &n))) { + if (!stricmp(att->name, "id")) td.fonts[td.font_count-1].fontID = atoi(att->value); + else if (!stricmp(att->name, "name")) td.fonts[td.font_count-1].fontName = gf_strdup(att->value); + } + } + } + } + else if (!strcmp(sub->name, "sharedStyles")) { + GF_XMLNode *style, *ftable; + u32 m=0; + while ((style=(GF_XMLNode*)gf_list_enum(sub->content, &m))) { + if (style->type) continue; + if (!strcmp(style->name, "style")) break; + } + if (style) { + char *cur; + s32 start=0; + char css_style[1024], css_val[1024]; + memset(&styles[nb_styles], 0, sizeof(GF_StyleRecord)); + m=0; + while ( (att=(GF_XMLAttribute *)gf_list_enum(style->attributes, &m))) { + if (!strcmp(att->name, "id")) styles[nb_styles].startCharOffset = atoi(att->value); + } + m=0; + while ( (ftable=(GF_XMLNode*)gf_list_enum(style->content, &m))) { + if (ftable->type) break; + } + cur = ftable ? ftable->name : NULL; + while (cur) { + start = gf_token_get_strip(cur, 0, "{:", " ", css_style, 1024); + if (start <0) break; + start = gf_token_get_strip(cur, start, ":}", " ", css_val, 1024); + if (start <0) break; + cur = strchr(cur+start, '{'); + + if (!strcmp(css_style, "font-table")) { + u32 z; + styles[nb_styles].fontID = atoi(css_val); + for (z=0; ztxty; + td.default_pos.left = ctx->txtx; + td.default_pos.right = ctx->width; + td.default_pos.bottom = ctx->height; + } + if (!td.fonts) { + td.font_count = 1; + td.fonts = (GF_FontRecord*)gf_malloc(sizeof(GF_FontRecord)); + td.fonts[0].fontID = 1; + td.fonts[0].fontName = gf_strdup( ctx->fontname ? ctx->fontname : "Serif"); + } + + gf_odf_tx3g_write(&td, &dsi, &dsi_len); + stsd_idx = 0; + for (k=0; ctx->text_descs && ktext_descs); k++) { + GF_PropertyValue *d = gf_list_get(ctx->text_descs, k); + if (d->value.data.size != dsi_len) continue; + if (! memcmp(d->value.data.ptr, dsi, dsi_len)) { + stsd_idx = k+1; + break; + } + } + if (stsd_idx) { + gf_free(dsi); + } else { + GF_PropertyValue *d; + GF_SAFEALLOC(d, GF_PropertyValue); + if (!d) return GF_OUT_OF_MEM; + d->type = GF_PROP_DATA; + d->value.data.ptr = dsi; + d->value.data.size = dsi_len; + if (!ctx->text_descs) ctx->text_descs = gf_list_new(); + gf_list_add(ctx->text_descs, d); + stsd_idx = gf_list_count(ctx->text_descs); + } + if (stsd_idx != ctx->last_desc_idx) { + ctx->last_desc_idx = stsd_idx; + GF_PropertyValue *d = gf_list_get(ctx->text_descs, stsd_idx-1); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, d); + } + + for (k=0; kname, "sampleData")) { + GF_XMLNode *sub; + u16 start, end; + u32 styleID; + u32 nb_chars, txt_len, m; + nb_chars = 0; + + samp = gf_isom_new_text_sample(); + + k=0; + while ((att=(GF_XMLAttribute *)gf_list_enum(desc->attributes, &k))) { + if (!strcmp(att->name, "targetEncoding") && !strcmp(att->value, "utf16")) ;//is_utf16 = 1; + else if (!strcmp(att->name, "scrollDelay")) gf_isom_text_set_scroll_delay(samp, atoi(att->value) ); + else if (!strcmp(att->name, "highlightColor")) gf_isom_text_set_highlight_color(samp, tx3g_get_color(att->value)); + } + start = end = 0; + k=0; + while ((sub=(GF_XMLNode*)gf_list_enum(desc->content, &k))) { + if (sub->type) continue; + if (!strcmp(sub->name, "text")) { + GF_XMLNode *text; + styleID = 0; + m=0; + while ((att=(GF_XMLAttribute *)gf_list_enum(sub->attributes, &m))) { + if (!strcmp(att->name, "styleID")) styleID = atoi(att->value); + } + txt_len = 0; + + m=0; + while ((text=(GF_XMLNode*)gf_list_enum(sub->content, &m))) { + if (!text->type) { + if (!strcmp(text->name, "marker")) { + u32 z; + memset(&marks[nb_marks], 0, sizeof(Marker)); + marks[nb_marks].pos = nb_chars+txt_len; + + z = 0; + while ( (att=(GF_XMLAttribute *)gf_list_enum(text->attributes, &z))) { + if (!strcmp(att->name, "id")) marks[nb_marks].id = atoi(att->value); + } + nb_marks++; + } + } else if (text->type==GF_XML_TEXT_TYPE) { + txt_len += (u32) strlen(text->name); + gf_isom_text_add_text(samp, text->name, (u32) strlen(text->name)); + } + } + if (styleID && (!same_style || (td.default_style.startCharOffset != styleID))) { + GF_StyleRecord st = td.default_style; + for (m=0; mname, "highlight")) { + m=0; + while ((att=(GF_XMLAttribute *)gf_list_enum(sub->attributes, &m))) { + if (!strcmp(att->name, "startMarker")) GET_MARKER_POS(start, 0) + else if (!strcmp(att->name, "endMarker")) GET_MARKER_POS(end, 1) + } + gf_isom_text_add_highlight(samp, start, end); + } + else if (!stricmp(sub->name, "blink")) { + m=0; + while ((att=(GF_XMLAttribute *)gf_list_enum(sub->attributes, &m))) { + if (!strcmp(att->name, "startMarker")) GET_MARKER_POS(start, 0) + else if (!strcmp(att->name, "endMarker")) GET_MARKER_POS(end, 1) + } + gf_isom_text_add_blink(samp, start, end); + } + else if (!stricmp(sub->name, "link")) { + char *url, *url_tt; + url = url_tt = NULL; + m=0; + while ((att=(GF_XMLAttribute *)gf_list_enum(sub->attributes, &m))) { + if (!strcmp(att->name, "startMarker")) GET_MARKER_POS(start, 0) + else if (!strcmp(att->name, "endMarker")) GET_MARKER_POS(end, 1) + else if (!strcmp(att->name, "URL") || !strcmp(att->name, "href")) url = gf_strdup(att->value); + else if (!strcmp(att->name, "URLToolTip") || !strcmp(att->name, "altString")) url_tt = gf_strdup(att->value); + } + gf_isom_text_add_hyperlink(samp, url, url_tt, start, end); + if (url) gf_free(url); + if (url_tt) gf_free(url_tt); + } + else if (!stricmp(sub->name, "karaoke")) { + u32 time = 0; + GF_XMLNode *krok; + m=0; + while ((att=(GF_XMLAttribute *)gf_list_enum(sub->attributes, &m))) { + if (!strcmp(att->name, "startTime")) time = atoi(att->value); + } + gf_isom_text_add_karaoke(samp, time); + m=0; + while ((krok=(GF_XMLNode*)gf_list_enum(sub->content, &m))) { + u32 u=0; + if (krok->type) continue; + if (strcmp(krok->name, "run")) continue; + start = end = 0; + while ((att=(GF_XMLAttribute *)gf_list_enum(krok->attributes, &u))) { + if (!strcmp(att->name, "startMarker")) GET_MARKER_POS(start, 0) + else if (!strcmp(att->name, "endMarker")) GET_MARKER_POS(end, 1) + else if (!strcmp(att->name, "duration")) time += atoi(att->value); + } + gf_isom_text_set_karaoke_segment(samp, time, start, end); + } + } + } + } + } + /*OK, let's add the sample*/ + if (samp) { + if (!same_box) gf_isom_text_set_box(samp, td.default_pos.top, td.default_pos.left, td.default_pos.bottom, td.default_pos.right); +// if (!same_style) gf_isom_text_add_style(samp, &td.default_style); + + txtin_process_send_text_sample(ctx, samp, (ctx->start*ctx->timescale)/ctx->txml_timescale, (u32) (duration*ctx->timescale)/ctx->txml_timescale, isRAP); + ctx->start += duration; + gf_isom_delete_text_sample(samp); + + } + if (gf_filter_pid_would_block(ctx->opid)) { + ctx->cur_child_idx++; + return GF_OK; + } + } + + return GF_EOS; +} + + +static GF_Err txtin_process(GF_Filter *filter) +{ + GF_TXTIn *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + GF_Err e; + Bool start, end; + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + return GF_OK; + } + gf_filter_pck_get_framing(pck, &start, &end); + if (!end) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + //file is loaded + + e = ctx->text_process(filter, ctx); + + + if (e==GF_EOS) { + //keep input alive until end of stream, so that we keep getting called + gf_filter_pid_drop_packet(ctx->ipid); + if (gf_filter_pid_is_eos(ctx->ipid)) + gf_filter_pid_set_eos(ctx->opid); + } + return e; +} + +static void ttxtin_reset(GF_TXTIn *ctx) +{ + if (ctx->samp) gf_isom_delete_text_sample(ctx->samp); + ctx->samp = NULL; + if (ctx->src) gf_fclose(ctx->src); + ctx->src = NULL; + if (ctx->vttparser) gf_webvtt_parser_del(ctx->vttparser); + ctx->vttparser = NULL; + if (ctx->parser) gf_xml_dom_del(ctx->parser); + ctx->parser = NULL; + if (ctx->parser_working_copy) gf_xml_dom_del(ctx->parser_working_copy); + ctx->parser_working_copy = NULL; +} + +static GF_Err txtin_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_Err e; + const char *src = NULL; + GF_TXTIn *ctx = gf_filter_get_udta(filter); + const GF_PropertyValue *prop; + + if (is_remove) { + ctx->ipid = NULL; + return GF_OK; + } + + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + //we must have a file path + prop = gf_filter_pid_get_property(pid, GF_PROP_PID_FILEPATH); + if (prop && prop->value.string) src = prop->value.string; + if (!src) + return GF_NOT_SUPPORTED; + + if (!ctx->ipid) { + GF_FilterEvent fevt; + ctx->ipid = pid; + + //we work with full file only, send a play event on source to indicate that + GF_FEVT_INIT(fevt, GF_FEVT_PLAY, pid); + fevt.play.start_range = 0; + fevt.base.on_pid = ctx->ipid; + fevt.play.full_file_only = GF_TRUE; + gf_filter_pid_send_event(ctx->ipid, &fevt); + ctx->file_name = src; + } else { + if (pid != ctx->ipid) { + return GF_REQUIRES_NEW_INSTANCE; + } + if (!strcmp(ctx->file_name, src)) return GF_OK; + + ttxtin_reset(ctx); + ctx->is_setup = GF_FALSE; + ctx->file_name = src; + } + //guess type + e = gf_text_guess_format(ctx->file_name, &ctx->fmt); + if (e) return e; + if (!ctx->fmt) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[TXTLoad] Unknown text format for %s\n", ctx->file_name)); + return GF_NOT_SUPPORTED; + } + + if (ctx->webvtt && (ctx->fmt == GF_TXTIN_MODE_SRT)) + ctx->fmt = GF_TXTIN_MODE_WEBVTT; + + switch (ctx->fmt) { + case GF_TXTIN_MODE_SRT: + ctx->text_process = txtin_process_srt; + break; +#ifndef GPAC_DISABLE_VTT + case GF_TXTIN_MODE_WEBVTT: + ctx->text_process = txtin_process_webvtt; + break; +#endif + case GF_TXTIN_MODE_TTXT: + ctx->text_process = txtin_process_ttxt; + break; + case GF_TXTIN_MODE_TEXML: + ctx->text_process = txtin_process_texml; + break; + case GF_TXTIN_MODE_SUB: + ctx->text_process = gf_text_process_sub; + break; + case GF_TXTIN_MODE_TTML: + ctx->text_process = gf_text_process_ttml; + break; +#ifndef GPAC_DISABLE_SWF_IMPORT + case GF_TXTIN_MODE_SWF_SVG: + ctx->text_process = gf_text_process_swf; + break; +#endif + default: + return GF_BAD_PARAM; + } + + return GF_OK; +} + +static Bool txtin_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_TXTIn *ctx = gf_filter_get_udta(filter); + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (ctx->playstate==1) return GF_TRUE; + ctx->playstate = 1; + if ((ctx->start_range < 0.1) && (evt->play.start_range<0.1)) return GF_TRUE; + ctx->start_range = evt->play.start_range; + ctx->seek_state = 1; + //cancel play event, we work with full file + return GF_TRUE; + + case GF_FEVT_STOP: + ctx->playstate = 2; + //cancel play event, we work with full file + return GF_TRUE; + default: + return GF_FALSE; + } + return GF_FALSE; +} + +GF_Err txtin_initialize(GF_Filter *filter) +{ + char data[1]; + GF_TXTIn *ctx = gf_filter_get_udta(filter); + ctx->bs_w = gf_bs_new(data, 1, GF_BITSTREAM_WRITE); + return GF_OK; +} + +void txtin_finalize(GF_Filter *filter) +{ + GF_TXTIn *ctx = gf_filter_get_udta(filter); + + ttxtin_reset(ctx); + if (ctx->bs_w) gf_bs_del(ctx->bs_w); + + if (ctx->text_descs) { + while (gf_list_count(ctx->text_descs)) { + GF_PropertyValue *p = gf_list_pop_back(ctx->text_descs); + gf_free(p->value.data.ptr); + gf_free(p); + } + gf_list_del(ctx->text_descs); + } +#ifndef GPAC_DISABLE_SWF_IMPORT + gf_swf_reader_del(ctx->swf_parse); +#endif + + if (ctx->intervals) { + ttml_reset_intervals(ctx); + gf_list_del(ctx->intervals); + } +} + +static const char *txtin_probe_data(const u8 *data, u32 data_size, GF_FilterProbeScore *score) +{ + char *dst = NULL; + u8 *res; + + res = gf_utf_get_utf8_string_from_bom((char *)data, data_size, &dst); + if (res) data = res; + +#define PROBE_OK(_score, _mime) \ + *score = _score;\ + if (dst) gf_free(dst);\ + return _mime; \ + + + if (!strncmp(data, "WEBVTT", 6)) { + PROBE_OK(GF_FPROBE_SUPPORTED, "subtitle/vtt") + } + if (strstr(data, " --> ")) { + PROBE_OK(GF_FPROBE_MAYBE_SUPPORTED, "subtitle/srt") + } + if (!strncmp(data, "FWS", 3) || !strncmp(data, "CWS", 3)) { + PROBE_OK(GF_FPROBE_MAYBE_SUPPORTED, "application/x-shockwave-flash") + } + + if ((data[0]=='{') && strstr(data, "}{")) { + PROBE_OK(GF_FPROBE_MAYBE_SUPPORTED, "subtitle/sub") + + } + /*XML formats*/ + if (!strstr(data, "?>") ) { + if (dst) gf_free(dst); + return NULL; + } + + if (strstr(data, " +#include + +#ifndef GPAC_DISABLE_AVILIB + +#include + +typedef struct +{ + u32 width, height, pf, stride; + GF_Fraction fps; + u32 sr, bps, nb_ch, wfmt, br; + u32 codec_id; + u32 format; + u32 timescale; + Bool is_video, is_raw_vid; + u32 is_open; + u32 tk_idx; + GF_FilterPid *pid; + Bool suspended; +} AVIStream; + +typedef struct +{ + //options + char *dst; + GF_Fraction fps; + Bool noraw; + u64 opendml_size; + + + avi_t *avi_out; + u32 nb_write; + GF_List *streams; + Bool has_video; + char comp_name[5]; + + u64 last_video_time_ms; + Bool video_is_eos; + char *buf_tmp; + u32 buf_alloc; + Bool in_error; + + u32 cur_file_idx_plus_one; +} GF_AVIMuxCtx; + +static GF_Err avimux_open_close(GF_AVIMuxCtx *ctx, const char *filename, const char *ext, u32 file_idx) +{ + Bool had_file = GF_FALSE; + if (ctx->avi_out) { + had_file = GF_TRUE; + AVI_close(ctx->avi_out); + } + ctx->avi_out = NULL; + if (!filename) return GF_OK; + + if (!strcmp(filename, "std") || !strcmp(filename, "stdout")) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[AVIOut] Writing to stdout not yet implemented\n")); + return GF_NOT_SUPPORTED; + } + + ctx->avi_out = AVI_open_output_file((char *) filename, ctx->opendml_size); + + if (had_file && ctx->nb_write) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[AVIOut] re-opening in write mode output file %s, content overwrite\n", filename)); + } + ctx->nb_write = 0; + if (!ctx->avi_out) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[AVIOut] cannot open output file %s\n", filename)); + ctx->in_error = GF_TRUE; + return GF_IO_ERR; + } + return GF_OK; +} + +static GF_Err avimux_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + AVIStream *stream; + GF_FilterEvent evt; + const GF_PropertyValue *p; + u32 w, h, sr, stride, bps, nb_ch, pf, codec_id, type, br, timescale, wfmt; + GF_Fraction fps; + GF_AVIMuxCtx *ctx = (GF_AVIMuxCtx *) gf_filter_get_udta(filter); + + stream = gf_filter_pid_get_udta(pid); + + if (is_remove) { + if (!stream) return GF_SERVICE_ERROR; + gf_list_del_item(ctx->streams, stream); + gf_free(stream); + + if (!gf_list_count(ctx->streams)) + avimux_open_close(ctx, NULL, NULL, 0); + return GF_OK; + } + gf_filter_pid_check_caps(pid); + + w = h = sr = nb_ch = pf = codec_id = type = timescale = wfmt = stride = 0; + bps = 16; + br=128000; + fps.den = fps.num = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (p) type = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (p) codec_id = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) timescale = p->value.uint; + + if (!codec_id) return GF_NOT_SUPPORTED; + if ((type!=GF_STREAM_VISUAL) && (type!=GF_STREAM_AUDIO)) return GF_NOT_SUPPORTED; + + if (!stream && (type==GF_STREAM_VISUAL) && ctx->has_video) { + return GF_REQUIRES_NEW_INSTANCE; + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + if (p) w = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE); + if (p) stride = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + if (p) h = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (p) fps = p->value.frac; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); + if (p) pf = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); + if (p) sr = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT); + if (p) bps = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS); + if (p) nb_ch = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_BITRATE); + if (p) br = p->value.uint; + + switch (codec_id) { + case GF_CODECID_MPEG4_PART2: + strcpy(ctx->comp_name, "XVID"); + break; + case GF_CODECID_AVC: + case GF_CODECID_SVC: + case GF_CODECID_MVC: + strcpy(ctx->comp_name, "h264"); + break; +// case GF_CODECID_HEVC: +// case GF_CODECID_LHVC: +// strcpy(szComp, "hevc"); +// break; + case GF_CODECID_MPEG_AUDIO: + case GF_CODECID_MPEG2_PART3: + wfmt = 0x55; + break; + case GF_CODECID_AAC_MPEG4: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + wfmt = 0x0000706d; + break; + case GF_CODECID_RAW: + + if (type==GF_STREAM_VISUAL) { + if (pf != GF_PIXEL_BGR) { + gf_filter_pid_negociate_property(pid, GF_PROP_PID_PIXFMT, &PROP_UINT(GF_PIXEL_BGR)); + return GF_OK; + } + } else if (type==GF_STREAM_AUDIO) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT); + if (!p || (p->value.uint != GF_AUDIO_FMT_S16) ) { + gf_filter_pid_negociate_property(pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(GF_AUDIO_FMT_S16)); + return GF_OK; + } + wfmt = WAVE_FORMAT_PCM; + } + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[AVIOut] Unsupported codec %s for AVI error\n", gf_codecid_name(codec_id) )); + return GF_NOT_SUPPORTED; + } + + if (!stream) { + GF_SAFEALLOC(stream, AVIStream); + if (!stream) return GF_OUT_OF_MEM; + stream->pid = pid; + gf_filter_pid_set_udta(pid, stream); + gf_list_add(ctx->streams, stream); + stream->codec_id = codec_id; + stream->timescale = timescale; + if (type==GF_STREAM_VISUAL) { + ctx->has_video = GF_TRUE; + stream->width = w; + stream->height = h; + stream->pf = pf; + stream->fps = fps; + stream->is_video = GF_TRUE; + } else { + stream->sr = sr; + stream->bps = bps; + stream->nb_ch = nb_ch; + stream->wfmt = wfmt; + stream->br = br; + } + GF_FEVT_INIT(evt, GF_FEVT_PLAY, pid); + gf_filter_pid_send_event(pid, &evt); + + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + } else { + if (!stream->is_open) { + stream->codec_id = codec_id; + stream->timescale = timescale; + stream->width = w; + stream->height = h; + stream->pf = pf; + stream->fps = fps; + stream->sr = sr; + stream->bps = bps; + stream->nb_ch = nb_ch; + stream->wfmt = wfmt; + stream->br = br; + stream->stride = stride; + stream->is_raw_vid = (codec_id==GF_CODECID_RAW) ? GF_TRUE : GF_FALSE; + goto check_mx; + } + if ((type==GF_STREAM_VISUAL) && (stream->width==w) && (stream->height==h) && (stream->format==pf) && (stream->codec_id==codec_id) && (stream->timescale==timescale) && (stream->fps.num*fps.den == stream->fps.den*fps.num)) + { + goto check_mx; + } + else if ((type==GF_STREAM_AUDIO) && (stream->sr==sr) && (stream->nb_ch==nb_ch) && (stream->bps==bps) && (stream->codec_id==codec_id) && (stream->timescale==timescale) ) + { + goto check_mx; + } + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[AVIOut] Stream configuration changed for codec %s, not supported in AVI\n", gf_codecid_name(codec_id) )); + + return GF_NOT_SUPPORTED; + } + +check_mx: + if (!ctx->avi_out) { + char szPath[GF_MAX_PATH]; + const char *cur_file_suffix=NULL; + GF_FilterPacket *pck = gf_filter_pid_get_packet(pid); + if (!pck) { + if (gf_filter_pid_is_eos(pid)) return GF_EOS; + return GF_OK; + } + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM); + ctx->cur_file_idx_plus_one = p ? (p->value.uint + 1) : 1; + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILESUF); + if (p && p->value.string) cur_file_suffix = p->value.string; + + gf_filter_pid_resolve_file_template(pid, ctx->dst, szPath, ctx->cur_file_idx_plus_one-1, cur_file_suffix); + avimux_open_close(ctx, szPath, NULL, 0); + } + + return GF_OK; +} + +static void avimux_finalize(GF_Filter *filter) +{ + GF_AVIMuxCtx *ctx = (GF_AVIMuxCtx *) gf_filter_get_udta(filter); + avimux_open_close(ctx, NULL, NULL, 0); + while (gf_list_count(ctx->streams)) { + AVIStream *st = gf_list_pop_back(ctx->streams); + gf_free(st); + } + gf_list_del(ctx->streams); + if (ctx->buf_tmp) gf_free(ctx->buf_tmp); +} + +static GF_Err avimux_process(GF_Filter *filter) +{ + GF_FilterPacket *pck; + s32 res=0; + const GF_PropertyValue *p; + u32 i, count, nb_eos, nb_suspended; + AVIStream *video_st=NULL; + const char *pck_data; + u32 pck_size; + GF_AVIMuxCtx *ctx = (GF_AVIMuxCtx *) gf_filter_get_udta(filter); + + if (ctx->in_error) + return GF_IO_ERR; + + count = gf_list_count(ctx->streams); + if (!ctx->avi_out) { + for (i=0; istreams, i); + GF_Err e = avimux_configure_pid(filter, st->pid, GF_FALSE); + if (e) return e; + } + if (!ctx->avi_out) + return GF_OK; + } + + //no video in mux, force writing 100ms of audio + //hack in raw mode, avoids flushing frames after video TS for video dump + //this should be fixed + if (!ctx->has_video || (ctx->noraw && ctx->video_is_eos)) ctx->last_video_time_ms += 100; + + + //flush all audio + nb_eos = 0; + nb_suspended = 0; + for (i=0; istreams, i); + + if (st->is_video) { + video_st=st; + continue; + } + if (!ctx->last_video_time_ms) continue; + if (st->suspended) { + nb_suspended++; + continue; + } + + while (1) { + pck = gf_filter_pid_get_packet(st->pid); + if (!pck) { + if (gf_filter_pid_is_eos(st->pid)) { + nb_eos++; + if (st->suspended) { + nb_suspended++; + } + } + break; + } + + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM); + if (p) { + if (ctx->cur_file_idx_plus_one == p->value.uint+1) { + } else if (!st->suspended) { + st->suspended = GF_TRUE; + nb_suspended++; + break; + } + } + + cts = gf_filter_pck_get_cts(pck); + if (cts==GF_FILTER_NO_TS) { + + } else { + cts *= 1000; + cts /= st->timescale; + if (!ctx->video_is_eos && (cts > ctx->last_video_time_ms)) + break; + } + + + pck_data = gf_filter_pck_get_data(pck, &pck_size); + if (!st->is_open) { + st->is_open = 1; + AVI_set_audio(ctx->avi_out, st->nb_ch, st->sr, 8*st->bps, st->wfmt, st->br); + st->tk_idx = AVI_get_audio_track(ctx->avi_out); + } else { + AVI_set_audio_track(ctx->avi_out, st->tk_idx); + } + res = AVI_write_audio(ctx->avi_out, (char *) pck_data, pck_size); + gf_filter_pid_drop_packet(st->pid); + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[AVIOut] Audio write error %d\n", res)); + } + ctx->nb_write++; + } + } + //write video + if (!video_st) return GF_OK; + + pck = gf_filter_pid_get_packet(video_st->pid); + + if (!video_st->is_open) { + Double fps; + if (!pck) return GF_OK; + + fps = ctx->fps.num; + fps /= ctx->fps.den; + + if (video_st->fps.num && video_st->fps.den) { + fps = video_st->fps.num; + fps /= video_st->fps.den; + } else { + u32 duration = gf_filter_pck_get_duration(pck); + if (duration) { + fps = video_st->timescale; + fps /= duration; + } + } + AVI_set_video(ctx->avi_out, video_st->width, video_st->height, fps, ctx->comp_name); + video_st->is_open = GF_TRUE; + } + + if (!pck) { + if (gf_filter_pid_is_eos(video_st->pid)) { + nb_eos++; + ctx->video_is_eos=GF_TRUE; + } + } else { + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM); + if (p) { + if (ctx->cur_file_idx_plus_one == p->value.uint+1) { + } else if (!video_st->suspended) { + video_st->suspended = GF_TRUE; + nb_suspended++; + pck = NULL; + } + } + } + + if (pck) { + u32 out_stride = video_st->stride; + u64 cts = gf_filter_pck_get_cts(pck); + int is_rap = gf_filter_pck_get_sap(pck) ? 1 : 0; + pck_data = gf_filter_pck_get_data(pck, &pck_size); + if (!pck_data) { + GF_FilterFrameInterface *frame_ifce = gf_filter_pck_get_frame_interface(pck); + if (frame_ifce && frame_ifce->get_plane) { + GF_Err e = frame_ifce->get_plane(frame_ifce, 0, (const u8 **) &pck_data, &out_stride); + if (e!=GF_OK) { + pck_data = NULL; + } else { + gf_pixel_get_size_info(video_st->pf, video_st->width, video_st->height, &pck_size, &out_stride, NULL, NULL, NULL); + } + } + } + if (pck_data) { + //raw RGB, flip + if (video_st->is_raw_vid) { + if (ctx->buf_allocbuf_alloc = pck_size; + ctx->buf_tmp = gf_realloc(ctx->buf_tmp, pck_size); + } + for (i=0; iheight; i++) { + memcpy(ctx->buf_tmp + i*out_stride, pck_data + (video_st->height-i-1)*out_stride, out_stride); + } + res = AVI_write_frame(ctx->avi_out, (char *) ctx->buf_tmp, pck_size, is_rap); + } else { + res = AVI_write_frame(ctx->avi_out, (char *) pck_data, pck_size, is_rap); + } + } + + if (cts!=GF_FILTER_NO_TS) { + u32 dur = gf_filter_pck_get_duration(pck); + cts += dur; + cts *= 1000; + cts /= video_st->timescale; + ctx->last_video_time_ms = cts + 1; + } else { + ctx->last_video_time_ms ++; + } + gf_filter_pid_drop_packet(video_st->pid); + if (res<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[AVIOut] Video write error %d\n", res)); + } + ctx->nb_write++; + } + + if (nb_suspended && (nb_suspended==count)) { + avimux_open_close(ctx, NULL, NULL, 0); + for (i=0; istreams, i); + st->is_open = GF_FALSE; + st->suspended = GF_FALSE; + } + ctx->avi_out = NULL; + return GF_OK; + } + + if (nb_eos==count) { + if (ctx->avi_out) + avimux_open_close(ctx, NULL, NULL, 0); + return GF_EOS; + } + + return GF_OK; +} + +static GF_FilterProbeScore avimux_probe_url(const char *url, const char *mime) +{ + char *fext = gf_file_ext_start(url); + if (fext && !stricmp(fext, ".avi")) return GF_FPROBE_FORCE; + if (mime) { + if (!stricmp(mime, "video/avi")) return GF_FPROBE_FORCE; + if (!stricmp(mime, "video/x-avi")) return GF_FPROBE_FORCE; + } + return GF_FPROBE_NOT_SUPPORTED; +} + +static GF_Err avimux_initialize(GF_Filter *filter); + + +#define OFFS(_n) #_n, offsetof(GF_AVIMuxCtx, _n) + +static const GF_FilterArgs AVIMuxArgs[] = +{ + { OFFS(dst), "location of destination file", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(fps), "default framerate if none indicated in stream", GF_PROP_FRACTION, "25/1", NULL, 0}, + { OFFS(noraw), "disable raw output in AVI, only compressed ones allowed", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(opendml_size), "force opendml format when chunks are larger than this amount (0 means 1.9Gb max size in each riff chunk)", GF_PROP_LUINT, "0", NULL, GF_FS_ARG_HINT_EXPERT}, + {0} +}; + +static const GF_FilterCapability AVIMuxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AVC), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG4_PART2), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG4), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_MP), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_LCP), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_SSRP), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_PART3), +}; + +static const GF_FilterCapability AVIMuxCapsNoRAW[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AVC), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG4_PART2), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG4), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_MP), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_LCP), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_SSRP), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_PART3), +}; + + +GF_FilterRegister AVIMuxRegister = { + .name = "avimx", + GF_FS_SET_DESCRIPTION("AVI muxer") + GF_FS_SET_HELP("This filter multiplexes raw or compressed audio and video to produce an AVI output.\n" + "\n" + "Unlike other multiplexing filters in GPAC, this filter is a sink filter and does not produce any PID to be redirected in the graph.\n" + "The filter can however use template names for its output, using the first input PID to resolve the final name.\n" + "The filter watches the property `FileNumber` on incoming packets to create new files.\n" + ) + .private_size = sizeof(GF_AVIMuxCtx), + .max_extra_pids = -1, + .args = AVIMuxArgs, + SETCAPS(AVIMuxCaps), + .probe_url = avimux_probe_url, + .initialize = avimux_initialize, + .finalize = avimux_finalize, + .configure_pid = avimux_configure_pid, + .process = avimux_process +}; + +static GF_Err avimux_initialize(GF_Filter *filter) +{ + const char *sep; + GF_AVIMuxCtx *ctx = (GF_AVIMuxCtx *) gf_filter_get_udta(filter); + + if (!ctx || !ctx->dst) return GF_OK; + ctx->streams = gf_list_new(); + if (strnicmp(ctx->dst, "file:/", 6) && strstr(ctx->dst, "://")) { + gf_filter_setup_failure(filter, GF_NOT_SUPPORTED); + return GF_NOT_SUPPORTED; + } + if (ctx->noraw) { + gf_filter_override_caps(filter, AVIMuxCapsNoRAW, sizeof(AVIMuxCapsNoRAW)/sizeof(GF_FilterCapability) ); + } + sep = strchr(ctx->dst, '$'); + if (sep && strchr(sep+1, '$')) { + //using templates, cannot solve final name + } else { + avimux_open_close(ctx, ctx->dst, NULL, 0); + } + return GF_OK; +} + +#endif + +const GF_FilterRegister *avimux_register(GF_FilterSession *session) +{ +#ifndef GPAC_DISABLE_AVILIB + return &AVIMuxRegister; +#else + return NULL; +#endif + +} + diff --git a/src/filters/mux_gsf.c b/src/filters/mux_gsf.c new file mode 100644 index 0000000..5b4968c --- /dev/null +++ b/src/filters/mux_gsf.c @@ -0,0 +1,1154 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018-2019 + * All rights reserved + * + * This file is part of GPAC / GPAC stream serializer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include + +typedef struct +{ + GF_FilterPid *pid; + + u32 idx; + u16 nb_frames; + Bool eos; + u8 config_version; + u64 last_cts_config; + u32 timescale; +} GSFStream; + +typedef struct +{ + //opts + Bool sigsn, sigdur, sigbo, sigdts, minp; + u32 dbg; + const char *magic; + const char *skp; + GF_PropData key; + GF_PropData IV; + GF_Fraction pattern; + u32 mpck; + Double crate; + + //only one output pid declared + GF_FilterPid *opid; + + GF_List *streams; + + u8 *buffer; + u32 alloc_size; + GF_BitStream *bs_w; + + Bool is_start; + u32 max_pid_idx; + + bin128 crypt_IV; + GF_Crypt *crypt; + Bool regenerate_tunein_info; + + u32 nb_frames; + u32 nb_pck; +} GSFMxCtx; + + +typedef enum +{ + GFS_PCKTYPE_HDR=0, + GFS_PCKTYPE_PID_CONFIG, + GFS_PCKTYPE_PID_INFO_UPDATE, + GFS_PCKTYPE_PID_REMOVE, + GFS_PCKTYPE_PID_EOS, + GFS_PCKTYPE_PCK, + + GFS_PCKTYPE_UNDEF1, + GFS_PCKTYPE_UNDEF2, + GFS_PCKTYPE_UNDEF3, + GFS_PCKTYPE_UNDEF4, + GFS_PCKTYPE_UNDEF5, + GFS_PCKTYPE_UNDEF6, + GFS_PCKTYPE_UNDEF7, + GFS_PCKTYPE_UNDEF8, + GFS_PCKTYPE_UNDEF9, + GFS_PCKTYPE_UNDEF10, + + /*NO MORE PACKET TYPE AVAILABLE*/ +} GF_GSFPacketType; + + +static GFINLINE u32 get_vlen_size(u32 len) +{ + if (len<=0x7F) return 1; + if (len<=0x3FFF) return 2; + if (len<=0x1FFFFF) return 3; + if (len<=0xFFFFFFF) return 4; + return 5; +} + +static GFINLINE void gsfmx_write_vlen(GSFMxCtx *ctx, u32 len) +{ + if (len<=0x7F) { + gf_bs_write_int(ctx->bs_w, 0, 1); + gf_bs_write_int(ctx->bs_w, len, 7); + } else if (len <= 0x3FFF){ + gf_bs_write_int(ctx->bs_w, 1, 1); + gf_bs_write_int(ctx->bs_w, 0, 1); + gf_bs_write_int(ctx->bs_w, len, 14); + } else if (len <= 0x1FFFFF){ + gf_bs_write_int(ctx->bs_w, 1, 1); + gf_bs_write_int(ctx->bs_w, 1, 1); + gf_bs_write_int(ctx->bs_w, 0, 1); + gf_bs_write_int(ctx->bs_w, len, 21); + } else if (len <= 0xFFFFFFF){ + gf_bs_write_int(ctx->bs_w, 1, 1); + gf_bs_write_int(ctx->bs_w, 1, 1); + gf_bs_write_int(ctx->bs_w, 1, 1); + gf_bs_write_int(ctx->bs_w, 0, 1); + gf_bs_write_int(ctx->bs_w, len, 28); + } else { + gf_bs_write_int(ctx->bs_w, 1, 1); + gf_bs_write_int(ctx->bs_w, 1, 1); + gf_bs_write_int(ctx->bs_w, 1, 1); + gf_bs_write_int(ctx->bs_w, 1, 1); + gf_bs_write_long_int(ctx->bs_w, len, 36); + } +} + +static GFINLINE u32 gsfmx_get_header_size(GSFMxCtx *ctx, GSFStream *gst, Bool use_seq_num, Bool first_frag, Bool no_frag, u32 pck_size, u32 block_size, u32 block_offset) +{ + u32 hdr_size = 1 + get_vlen_size(pck_size); + hdr_size += get_vlen_size(gst ? gst->idx : 0); + if (use_seq_num) hdr_size += 2; + + if (!no_frag) { + hdr_size += get_vlen_size(block_size); + if (!first_frag) + hdr_size += get_vlen_size(block_offset); + } + return hdr_size; +} + +static void gsfmx_encrypt(GSFMxCtx *ctx, char *data, u32 nb_crypt_bytes) +{ +#ifndef GPAC_DISABLE_CRYPTO + u32 clear_trail; + + clear_trail = nb_crypt_bytes % 16; + nb_crypt_bytes -= clear_trail; + if (! nb_crypt_bytes) return; + + //reset IV at each packet + gf_crypt_set_IV(ctx->crypt, ctx->crypt_IV, 16); + if (ctx->pattern.den && ctx->pattern.num) { + //u32 pos = 0; + while (nb_crypt_bytes) { + u32 bbytes = 16 * (ctx->pattern.num + ctx->pattern.den); + gf_crypt_encrypt(ctx->crypt, data, nb_crypt_bytes >= (u32) (16*ctx->pattern.num) ? 16*ctx->pattern.num : nb_crypt_bytes); + if (nb_crypt_bytes >= bbytes) { + //pos += bbytes; + nb_crypt_bytes -= bbytes; + } else { + nb_crypt_bytes = 0; + } + } + } else { + gf_crypt_encrypt(ctx->crypt, data, nb_crypt_bytes); + } +#endif // GPAC_DISABLE_CRYPTO +} + +static void gsfmx_send_packets(GSFMxCtx *ctx, GSFStream *gst, GF_GSFPacketType pck_type, Bool is_end, Bool is_redundant, u32 frame_size, u32 frame_hdr_size) +{ + u32 pck_size, bytes_remain, pck_offset, block_offset; + Bool first_frag = GF_TRUE; + Bool no_frag = GF_TRUE; + //for now only data packets are encrypted on a per-fragment base, others are fully encrypted + Bool frag_encryption = (pck_type==GFS_PCKTYPE_PCK) ? GF_TRUE : GF_FALSE; + Bool use_seq_num = (pck_type==GFS_PCKTYPE_HDR) ? GF_FALSE : ctx->sigsn; + + gf_bs_get_content_no_truncate(ctx->bs_w, &ctx->buffer, &pck_size, &ctx->alloc_size); + + first_frag = GF_TRUE; + bytes_remain = pck_size; + block_offset = pck_offset = 0; + if (!frame_size) frame_size = pck_size; + + if (ctx->crypt && !frag_encryption) { + u32 crypt_offset=0; + if (pck_type == GFS_PCKTYPE_HDR) { + crypt_offset=25; //sig + version + IV + pattern + } + gsfmx_encrypt(ctx, ctx->buffer + crypt_offset, pck_size - crypt_offset); + } + + while (bytes_remain) { + u8 *output; + GF_FilterPacket *dst_pck; + Bool do_encrypt = GF_FALSE; + u32 crypt_offset=0; + u32 osize; + u32 to_write = bytes_remain; + u32 hdr_size = gsfmx_get_header_size(ctx, gst, use_seq_num, first_frag, no_frag, to_write, frame_size, block_offset); + if (ctx->mpck && (ctx->mpck < to_write + hdr_size)) { + no_frag = GF_FALSE; + hdr_size = gsfmx_get_header_size(ctx, gst, use_seq_num, first_frag, no_frag, ctx->mpck - hdr_size, frame_size, block_offset); + to_write = ctx->mpck - hdr_size; + } + osize = hdr_size + to_write; + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, osize, &output); + + //format header + gf_bs_reassign_buffer(ctx->bs_w, output, osize); + + if (ctx->crypt) do_encrypt = GF_TRUE; + + gf_bs_write_int(ctx->bs_w, 0, 1); //reserved + //fragment flag + if (no_frag) gf_bs_write_int(ctx->bs_w, 0, 2); + else if (first_frag) gf_bs_write_int(ctx->bs_w, 1, 2); + else gf_bs_write_int(ctx->bs_w, 2, 2); + //encrypt flag + gf_bs_write_int(ctx->bs_w, do_encrypt ? 1 : 0, 1); + //packet type + gf_bs_write_int(ctx->bs_w, pck_type, 4); + + //packet size and seq num + gsfmx_write_vlen(ctx, gst ? gst->idx : 0); + if (use_seq_num) { + gf_bs_write_u16(ctx->bs_w, gst ? gst->nb_frames : ctx->nb_frames); + } + + //block size and block offset + if (!no_frag) { + gsfmx_write_vlen(ctx, frame_size); + if (!first_frag) gsfmx_write_vlen(ctx, block_offset); + } + + gsfmx_write_vlen(ctx, to_write); + + hdr_size = (u32) gf_bs_get_position(ctx->bs_w); + assert(hdr_size + to_write == osize); + memcpy(output+hdr_size, ctx->buffer + pck_offset, to_write); + bytes_remain -= to_write; + pck_offset += to_write; + block_offset += to_write; + if (frame_hdr_size) { + if (frame_hdr_size > block_offset) { + frame_hdr_size -= block_offset; + block_offset = 0; + } else { + block_offset -= frame_hdr_size; + frame_hdr_size = 0; + } + } + + if (ctx->mpck && (ctx->mpck < to_write)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[GSFMux] packet type %d size %d exceeds max packet size %d!\n", pck_type, osize, ctx->mpck)); + } + //this is just to detach the buffer from the bit writer + gf_bs_get_content_no_truncate(ctx->bs_w, &output, &to_write, NULL); + + if (do_encrypt && frag_encryption) { + //encrypt start after fragmentation block info, plus any additional offset based on packet type + hdr_size += crypt_offset; + gsfmx_encrypt(ctx, output+hdr_size, osize - hdr_size); + } + + if (is_redundant) { + gf_filter_pck_set_dependency_flags(dst_pck, 1); + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE); + } else if (first_frag && bytes_remain) { + gf_filter_pck_set_framing(dst_pck, ctx->is_start, GF_FALSE); + } else if (!first_frag && bytes_remain) { + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE); + } else if (first_frag) { + gf_filter_pck_set_framing(dst_pck, ctx->is_start, is_end); + } else { + gf_filter_pck_set_framing(dst_pck, GF_FALSE, is_end); + } + gf_filter_pck_send(dst_pck); + + first_frag = GF_FALSE; + } +} + +static void gsfmx_send_pid_rem(GSFMxCtx *ctx, GSFStream *gst) +{ + gf_bs_reassign_buffer(ctx->bs_w, ctx->buffer, ctx->alloc_size); + gst->nb_frames++; + gsfmx_send_packets(ctx, gst, GFS_PCKTYPE_PID_REMOVE, GF_FALSE, GF_FALSE, 0, 0); +} + +static void gsfmx_send_pid_eos(GSFMxCtx *ctx, GSFStream *gst, Bool is_eos) +{ + gf_bs_reassign_buffer(ctx->bs_w, ctx->buffer, ctx->alloc_size); + gst->nb_frames++; + gsfmx_send_packets(ctx, gst, GFS_PCKTYPE_PID_EOS, is_eos, GF_FALSE, 0, 0); +} + +static Bool gsfmx_can_serialize_prop(const GF_PropertyValue *p) +{ + switch (p->type) { + case GF_PROP_POINTER: + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[GSFMux] Cannot serialize pointer property, ignoring !!\n")); + case GF_PROP_FORBIDEN: + return GF_FALSE; + default: + if (p->type>=GF_PROP_LAST_DEFINED) + return GF_FALSE; + return GF_TRUE; + } +} + +static void gsfmx_write_prop(GSFMxCtx *ctx, const GF_PropertyValue *p) +{ + u32 len, len2, i; + switch (p->type) { + case GF_PROP_SINT: + case GF_PROP_UINT: + case GF_PROP_PIXFMT: + case GF_PROP_PCMFMT: + gsfmx_write_vlen(ctx, p->value.uint); + break; + case GF_PROP_LSINT: + case GF_PROP_LUINT: + gf_bs_write_u64(ctx->bs_w, p->value.longuint); + break; + case GF_PROP_BOOL: + gf_bs_write_u8(ctx->bs_w, p->value.boolean ? 1 : 0); + break; + case GF_PROP_FRACTION: + gsfmx_write_vlen(ctx, p->value.frac.num); + gsfmx_write_vlen(ctx, p->value.frac.den); + break; + case GF_PROP_FRACTION64: + gf_bs_write_u64(ctx->bs_w, p->value.lfrac.num); + gf_bs_write_u64(ctx->bs_w, p->value.lfrac.den); + break; + case GF_PROP_FLOAT: + gf_bs_write_float(ctx->bs_w, FIX2FLT(p->value.fnumber) ); + break; + case GF_PROP_DOUBLE: + gf_bs_write_double(ctx->bs_w, p->value.number); + break; + case GF_PROP_VEC2I: + gsfmx_write_vlen(ctx, p->value.vec2i.x); + gsfmx_write_vlen(ctx, p->value.vec2i.y); + break; + case GF_PROP_VEC2: + gf_bs_write_double(ctx->bs_w, p->value.vec2.x); + gf_bs_write_double(ctx->bs_w, p->value.vec2.y); + break; + case GF_PROP_VEC3I: + gsfmx_write_vlen(ctx, p->value.vec3i.x); + gsfmx_write_vlen(ctx, p->value.vec3i.y); + gsfmx_write_vlen(ctx, p->value.vec3i.z); + break; + case GF_PROP_VEC3: + gf_bs_write_double(ctx->bs_w, p->value.vec3.x); + gf_bs_write_double(ctx->bs_w, p->value.vec3.y); + gf_bs_write_double(ctx->bs_w, p->value.vec3.z); + break; + case GF_PROP_VEC4I: + gsfmx_write_vlen(ctx, p->value.vec4i.x); + gsfmx_write_vlen(ctx, p->value.vec4i.y); + gsfmx_write_vlen(ctx, p->value.vec4i.z); + gsfmx_write_vlen(ctx, p->value.vec4i.w); + break; + case GF_PROP_VEC4: + gf_bs_write_double(ctx->bs_w, p->value.vec4.x); + gf_bs_write_double(ctx->bs_w, p->value.vec4.y); + gf_bs_write_double(ctx->bs_w, p->value.vec4.z); + gf_bs_write_double(ctx->bs_w, p->value.vec4.w); + break; + case GF_PROP_STRING: + case GF_PROP_STRING_NO_COPY: + case GF_PROP_NAME: + len = (u32) strlen(p->value.string); + gsfmx_write_vlen(ctx, len); + gf_bs_write_data(ctx->bs_w, p->value.string, len); + break; + + case GF_PROP_DATA: + case GF_PROP_DATA_NO_COPY: + case GF_PROP_CONST_DATA: + len = p->value.data.size; + gsfmx_write_vlen(ctx, len); + gf_bs_write_data(ctx->bs_w, p->value.data.ptr, len); + break; + + //string list: memory is ALWAYS duplicated + case GF_PROP_STRING_LIST: + len2 = gf_list_count(p->value.string_list); + gsfmx_write_vlen(ctx, len2); + for (i=0; ivalue.string_list, i); + len = (u32) strlen(str); + gsfmx_write_vlen(ctx, len); + gf_bs_write_data(ctx->bs_w, str, len); + } + break; + + case GF_PROP_UINT_LIST: + len = p->value.uint_list.nb_items; + gsfmx_write_vlen(ctx, len); + for (i=0; ivalue.uint_list.vals[i] ); + } + break; + case GF_PROP_POINTER: + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[GSFMux] Cannot serialize pointer property, ignoring !!\n")); + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFMux] Cannot serialize property of unknown type, ignoring !!\n")); + break; + } +} + +static GFINLINE Bool gsfmx_is_prop_skip(GSFMxCtx *ctx, u32 prop_4cc, const char *prop_name, u8 sep_l) +{ + if (ctx->minp) { + u8 flags; + if (prop_name) return GF_TRUE; + + flags = gf_props_4cc_get_flags(prop_4cc); + if (flags & GF_PROP_FLAG_GSF_REM) return GF_TRUE; + return GF_FALSE; + } + if (ctx->skp) { + const char *pname = prop_name ? prop_name : gf_4cc_to_str(prop_4cc); + u32 plen = (u32) strlen(pname); + const char *sep = strstr(ctx->skp, pname); + if (sep && ((sep[plen]==sep_l) || !sep[plen])) + return GF_TRUE; + if (prop_4cc) { + pname = gf_props_4cc_get_name(prop_4cc); + if (!pname) pname = gf_4cc_to_str(prop_4cc); + plen = (u32) strlen(pname); + sep = strstr(ctx->skp, pname); + if (sep && ((sep[plen]==sep_l) || !sep[plen])) + return GF_TRUE; + } + } + return GF_FALSE; +} + +static void gsfmx_write_pid_config(GF_Filter *filter, GSFMxCtx *ctx, GSFStream *gst) +{ + u32 nb_4cc_props=0; + u32 nb_str_props=0; + u32 idx=0; + u8 sep_l = gf_filter_get_sep(filter, GF_FS_SEP_LIST); + + while (1) { + u32 prop_4cc; + const char *prop_name; + const GF_PropertyValue *p = gf_filter_pid_enum_properties(gst->pid, &idx, &prop_4cc, &prop_name); + if (!p) break; + if (!gsfmx_can_serialize_prop(p)) continue; + if ( gsfmx_is_prop_skip(ctx, prop_4cc, prop_name, sep_l) ) + continue; + if (prop_4cc) { + if (gf_props_4cc_get_type(prop_4cc) == GF_PROP_FORBIDEN) { + nb_str_props++; + } else { + nb_4cc_props++; + } + } + else if (prop_name) + nb_str_props++; + } + + gf_bs_write_u8(ctx->bs_w, gst->config_version); + gsfmx_write_vlen(ctx, nb_4cc_props); + gsfmx_write_vlen(ctx, nb_str_props); + + idx=0; + while (1) { + u32 prop_4cc; + const char *prop_name; + const GF_PropertyValue *p = gf_filter_pid_enum_properties(gst->pid, &idx, &prop_4cc, &prop_name); + if (!p) break; + if (!gsfmx_can_serialize_prop(p)) continue; + + if (prop_name) continue; + if (gf_props_4cc_get_type(prop_4cc) == GF_PROP_FORBIDEN) + continue; + + if ( gsfmx_is_prop_skip(ctx, prop_4cc, prop_name, sep_l) ) + continue; + + gf_bs_write_u32(ctx->bs_w, prop_4cc); + + gsfmx_write_prop(ctx, p); + } + + idx=0; + while (1) { + u32 prop_4cc, len; + const char *prop_name; + const GF_PropertyValue *p = gf_filter_pid_enum_properties(gst->pid, &idx, &prop_4cc, &prop_name); + if (!p) break; + if (!gsfmx_can_serialize_prop(p)) continue; + if (prop_4cc && (gf_props_4cc_get_type(prop_4cc) != GF_PROP_FORBIDEN)) continue; + + if ( gsfmx_is_prop_skip(ctx, prop_4cc, prop_name, sep_l) ) + continue; + if (!prop_name) + prop_name = gf_4cc_to_str(prop_4cc); + + len = (u32) strlen(prop_name); + gsfmx_write_vlen(ctx, len); + gf_bs_write_data(ctx->bs_w, prop_name, len); + + gf_bs_write_u8(ctx->bs_w, p->type); + gsfmx_write_prop(ctx, p); + } +} + + + +static void gsfmx_send_header(GF_Filter *filter, GSFMxCtx *ctx, Bool is_carousel_update) +{ + u32 mlen=0; + + if (!ctx->bs_w) { + ctx->bs_w = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + if (!ctx->bs_w) return; + } else { + gf_bs_reassign_buffer(ctx->bs_w, ctx->buffer, ctx->alloc_size); + } + + ctx->nb_frames++; + if (ctx->magic) { + mlen = (u32) strlen(ctx->magic); + } + + //header:signature + gf_bs_write_u32(ctx->bs_w, GF_4CC('G','S','5','F') ); + //header protocol version + gf_bs_write_u8(ctx->bs_w, 1); + + if (ctx->crypt) { + gf_bs_write_data(ctx->bs_w, ctx->crypt_IV, 16); + gf_bs_write_u16(ctx->bs_w, ctx->pattern.num); + gf_bs_write_u16(ctx->bs_w, ctx->pattern.den); + } + gf_bs_write_int(ctx->bs_w, ctx->sigsn ? 1 : 0, 1); + gf_bs_write_int(ctx->bs_w, 0, 7); + + //header:magic + gsfmx_write_vlen(ctx, mlen); + if (ctx->magic) { + gf_bs_write_data(ctx->bs_w, ctx->magic, mlen); + } + + gsfmx_send_packets(ctx, NULL, GFS_PCKTYPE_HDR, GF_FALSE, is_carousel_update ? GF_TRUE : GF_FALSE, 0, 0); + ctx->is_start = GF_FALSE; +} + +static void gsfmx_send_pid_config(GF_Filter *filter, GSFMxCtx *ctx, GSFStream *gst, Bool is_info, Bool is_carousel_update) +{ + if (ctx->is_start) { + gsfmx_send_header(filter, ctx, GF_FALSE); + } + + gf_bs_reassign_buffer(ctx->bs_w, ctx->buffer, ctx->alloc_size); + if (!is_info && !is_carousel_update) { + gst->config_version++; + } + gsfmx_write_pid_config(filter, ctx, gst); + gst->nb_frames++; + gsfmx_send_packets(ctx, gst, is_info ? GFS_PCKTYPE_PID_INFO_UPDATE : GFS_PCKTYPE_PID_CONFIG, GF_FALSE, is_carousel_update ? GF_TRUE : GF_FALSE, 0, 0); +} + +static void gsfmx_write_data_packet(GSFMxCtx *ctx, GSFStream *gst, GF_FilterPacket *pck) +{ + u32 w=0, h=0, stride=0, stride_uv=0, pf=0; + u32 nb_planes=0, uv_height=0; + const char *data; + u32 frame_size, frame_hdr_size; + GF_FilterFrameInterface *frame_ifce=NULL; + u32 nb_4cc_props=0; + u32 nb_str_props=0; + u32 idx=0; + u32 tsmode=0; + u32 tsmodebits=0; + u32 tsdiffmode=0; + u32 tsdiffmodebits=0; + Bool start, end; + const GF_PropertyValue *p; + + if (ctx->dbg==2) return; + + gst->nb_frames++; + + while (1) { + u32 prop_4cc; + const char *prop_name; + p = gf_filter_pck_enum_properties(pck, &idx, &prop_4cc, &prop_name); + if (!p) break; + if (!gsfmx_can_serialize_prop(p)) continue; + if (prop_4cc) { + if (gf_props_4cc_get_type(prop_4cc) == GF_PROP_FORBIDEN) + nb_str_props++; + else + nb_4cc_props++; + } + else if (prop_name) + nb_str_props++; + } + + gf_bs_reassign_buffer(ctx->bs_w, ctx->buffer, ctx->alloc_size); + + data = gf_filter_pck_get_data(pck, &frame_size); + if (!data) { + frame_ifce = gf_filter_pck_get_frame_interface(pck); + if (frame_ifce) { + p = gf_filter_pid_get_property(gst->pid, GF_PROP_PID_WIDTH); + w = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(gst->pid, GF_PROP_PID_HEIGHT); + h = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(gst->pid, GF_PROP_PID_PIXFMT); + pf = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(gst->pid, GF_PROP_PID_STRIDE); + stride = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(gst->pid, GF_PROP_PID_STRIDE_UV); + stride_uv = p ? p->value.uint : 0; + + if (! gf_pixel_get_size_info(pf, w, h, &frame_size, &stride, &stride_uv, &nb_planes, &uv_height)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[GSFMux] Raw packet with unsupported pixel format, cannot send\n")); + return; + } + } + } + + if (ctx->dbg) frame_size=0; + + u64 dts = gf_filter_pck_get_dts(pck); + u64 cts = gf_filter_pck_get_cts(pck); + u32 cts_diff=0; + u8 has_dts=0; + u8 has_cts=0; + u8 neg_cts=0; + + //packet structure: + //flags first byte: 1(has_dts) 1(has_cts) 1(has_dur) 1(cts_diff_neg) 2(ts_mode) 2(ts_diff_mode) + //flags second byte: 3(sap) 2(encrypted) 1(has_sample_deps) 1(has builtin props) 1(has_ext) + //if (ext) { + // flags third byte: 1(has_byteoffset) 1(corrupted) 1(seek) 1(has_carousel) 2(ilaced) 2(cktype) + // flags fourth byte: 1(au start) 1(au end) 1(has props) reserved(5) + //} + //if has_dts, dts on tsmodebits + //if has_cts, has_dts ? cts_diff on ts_diff_mode : cts on tsmodebits + //if durmode>0, dur on ts_diff_mode + //if sap==4, roll on signed 16 bits + //if (has sample deps) sample_deps_flags on 8 bits + //if has_carousel, carrousel version on 8 bits + //if has_byteoffset, byte offset on 64 bits + //if (has builtin) vlen nb builtin_props then props[builtin_props] + //if (has props) vlen nb_str_props then props[nb_str_props] + //data + + + u32 duration = ctx->sigdur ? gf_filter_pck_get_duration(pck) : 0; + + if (ctx->sigdts && (dts!=GF_FILTER_NO_TS) && (dts!=cts)) { + has_dts=1; + + if (dts>0xFFFFFFFFUL) tsmode=3; + else if (dts>0xFFFFFF) tsmode=2; + else if (dts>0xFFFF) tsmode=1; + } + if (cts!=GF_FILTER_NO_TS) { + has_cts=1; + if (has_dts) { + s64 diff = ( (s64) cts - (s64) dts); + if (diff>0) cts_diff = (u32) diff; + else { + neg_cts=1; + cts_diff = (u32) (-diff); + } + } else { + if (cts>0xFFFFFFFFUL) tsmode=3; + else if (cts>0xFFFFFF) tsmode=2; + else if (cts>0xFFFF) tsmode=1; + } + } + + if (tsmode==3) tsmodebits=64; + else if (tsmode==2) tsmodebits=32; + else if (tsmode==1) tsmodebits=24; + else tsmodebits=16; + + u32 max_dur = MAX(duration, cts_diff); + if (max_dur < 0xFF) { tsdiffmode=0; tsdiffmodebits=8; } + else if (max_dur < 0xFFFF) { tsdiffmode=1; tsdiffmodebits=16; } + else if (max_dur < 0xFFFFFF) { tsdiffmode=2; tsdiffmodebits=24; } + else { tsdiffmode=3; tsdiffmodebits=32; } + + + assert(tsmodebits<=32); + + //first flags byte + //flags first byte: 1(has_dts) 1(has_cts) 1(has_dur) 1(cts_diff_neg) 2(ts_mode) 2(ts_diff_mode) + gf_bs_write_int(ctx->bs_w, has_dts, 1); + gf_bs_write_int(ctx->bs_w, has_cts, 1); + gf_bs_write_int(ctx->bs_w, duration ? 1 : 0, 1); + gf_bs_write_int(ctx->bs_w, neg_cts, 1); + gf_bs_write_int(ctx->bs_w, tsmode, 2); + gf_bs_write_int(ctx->bs_w, tsdiffmode, 2); + + //flags second byte: 3(sap) 2(encrypted) 1(has_sample_deps) 1(has builtin props) 1(has_ext) + u8 sap = gf_filter_pck_get_sap(pck); + gf_bs_write_int(ctx->bs_w, sap, 3); + u8 crypt = gf_filter_pck_get_crypt_flags(pck); + gf_bs_write_int(ctx->bs_w, crypt, 2); + u8 depflags = gf_filter_pck_get_dependency_flags(pck); + gf_bs_write_int(ctx->bs_w, depflags ? 1 : 0, 1); + gf_bs_write_int(ctx->bs_w, nb_4cc_props ? 1 : 0, 1); + + u8 needs_ext = nb_str_props ? 1 : 0; + u64 bo = ctx->sigbo ? gf_filter_pck_get_byte_offset(pck) : GF_FILTER_NO_BO; + if (bo != GF_FILTER_NO_BO) needs_ext=1; + u8 corr = gf_filter_pck_get_corrupted(pck); + if (corr) needs_ext=1; + u8 seek = gf_filter_pck_get_seek_flag(pck); + if (seek) needs_ext=1; + u8 carv = gf_filter_pck_get_carousel_version(pck); + if (carv) needs_ext=1; + u8 interl = gf_filter_pck_get_interlaced(pck); + if (interl) needs_ext=1; + u8 cktype = gf_filter_pck_get_clock_type(pck); + if (cktype) needs_ext=1; + gf_filter_pck_get_framing(pck, &start, &end); + if (!start || !end) needs_ext=1; + + gf_bs_write_int(ctx->bs_w, needs_ext, 1); + + //extension header + if (needs_ext) { + // flags third byte: 1(has_byteoffset) 1(corrupted) 1(seek) 1(has_carousel) 2(ilaced) 2(cktype) + gf_bs_write_int(ctx->bs_w, (bo==GF_FILTER_NO_BO) ? 0 : 1, 1); + gf_bs_write_int(ctx->bs_w, corr ? 1 : 0, 1); + gf_bs_write_int(ctx->bs_w, seek ? 1 : 0, 1); + gf_bs_write_int(ctx->bs_w, carv ? 1 : 0, 1); + gf_bs_write_int(ctx->bs_w, interl, 2); + gf_bs_write_int(ctx->bs_w, cktype, 2); + + // flags fourth byte: 1(au start) 1(au end) 1(has props) reserved(5) + gf_bs_write_int(ctx->bs_w, start ? 1 : 0, 1); + gf_bs_write_int(ctx->bs_w, end ? 1 : 0, 1); + gf_bs_write_int(ctx->bs_w, nb_str_props ? 1 : 0, 1); + gf_bs_write_int(ctx->bs_w, 0, 5); + } + //done with flags + + if (has_dts) { + if (tsmode==3) gf_bs_write_long_int(ctx->bs_w, dts, tsmodebits); + else gf_bs_write_int(ctx->bs_w, (u32) dts, tsmodebits); + } + if (has_cts) { + if (has_dts) { + gf_bs_write_int(ctx->bs_w, cts_diff, tsdiffmodebits); + } else { + if (tsmode==3) gf_bs_write_long_int(ctx->bs_w, cts, tsmodebits); + else gf_bs_write_int(ctx->bs_w, (u32) cts, tsmodebits); + } + } + + if (duration) { + gf_bs_write_int(ctx->bs_w, duration, tsdiffmodebits); + } + + if ((sap==GF_FILTER_SAP_4) || (sap==GF_FILTER_SAP_4_PROL)) { + s16 roll = gf_filter_pck_get_roll_info(pck); + gf_bs_write_u16(ctx->bs_w, roll); + } + if (depflags) + gf_bs_write_u8(ctx->bs_w, depflags); + + if (carv) { + gf_bs_write_u8(ctx->bs_w, carv); + } + if (bo!=GF_FILTER_NO_BO) { + gf_bs_write_u64(ctx->bs_w, bo); + } + if (nb_4cc_props) { + gsfmx_write_vlen(ctx, nb_4cc_props); + + //write packet properties + idx=0; + while (1) { + u32 prop_4cc; + const char *prop_name; + p = gf_filter_pck_enum_properties(pck, &idx, &prop_4cc, &prop_name); + if (!p) break; + if (!gsfmx_can_serialize_prop(p)) continue; + if (prop_name) continue; + if (gf_props_4cc_get_type(prop_4cc) == GF_PROP_FORBIDEN) continue; + + gf_bs_write_u32(ctx->bs_w, prop_4cc); + + gsfmx_write_prop(ctx, p); + } + } + if (nb_str_props) { + gsfmx_write_vlen(ctx, nb_str_props); + idx=0; + while (1) { + u32 prop_4cc, len; + const char *prop_name; + p = gf_filter_pck_enum_properties(pck, &idx, &prop_4cc, &prop_name); + if (!p) break; + if (!gsfmx_can_serialize_prop(p)) continue; + if (prop_4cc && (gf_props_4cc_get_type(prop_4cc) != GF_PROP_FORBIDEN)) continue; + if (!prop_name) prop_name = gf_4cc_to_str(prop_4cc); + len = (u32) strlen(prop_name); + gsfmx_write_vlen(ctx, len); + gf_bs_write_data(ctx->bs_w, prop_name, len); + + gf_bs_write_u8(ctx->bs_w, p->type); + gsfmx_write_prop(ctx, p); + } + gsfmx_write_vlen(ctx, nb_str_props); + } + + frame_hdr_size = (u32) gf_bs_get_position(ctx->bs_w); + + if (has_cts && ctx->crate>0) { + if (!gst->last_cts_config) { + gst->last_cts_config = has_cts+1; + } else if ( cts - (gst->last_cts_config-1) > ctx->crate * gst->timescale) { + ctx->regenerate_tunein_info = GF_TRUE; + } + } + + + //write packet data + if (ctx->dbg) { + + } else if (data) { + u32 nb_write = gf_bs_write_data(ctx->bs_w, data, frame_size); + if (nb_write != frame_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[GSFMux] Write error, wrote %d bytes but had %d to write\n", nb_write, frame_size)); + } + gsfmx_send_packets(ctx, gst, GFS_PCKTYPE_PCK, GF_FALSE, GF_FALSE, frame_size, frame_hdr_size); + } else if (frame_ifce) { + u32 i, bpp = gf_pixel_get_bytes_per_pixel(pf); + for (i=0; iget_plane(frame_ifce, i, &out_ptr, &out_stride); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[GSFMux] Failed to fetch plane data from hardware frame, cannot write\n")); + break; + } + write_h = h; + if (i) write_h = uv_height; + lsize = bpp * (i ? stride_uv : stride); + for (j=0; jbs_w, out_ptr, lsize); + if (nb_write != lsize) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[GSFMux] Write error, wrote %d bytes but had %d to write\n", nb_write, lsize)); + } + out_ptr += out_stride; + } + } + gsfmx_send_packets(ctx, gst, GFS_PCKTYPE_PCK, GF_FALSE, GF_FALSE, frame_size, frame_hdr_size); + } +} + +GF_Err gsfmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GSFStream *gst=NULL; + const GF_PropertyValue *p; + GSFMxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + gst = gf_filter_pid_get_udta(pid); + assert(gst); + gsfmx_send_pid_rem(ctx, gst); + gf_list_del_item(ctx->streams, gst); + gf_free(gst); + if (!gf_list_count(ctx->streams)) { + gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_name(ctx->opid, "gsf_mux"); + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, pid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT( GF_STREAM_FILE ) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, NULL ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ENCRYPTED, NULL ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, NULL ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING( "*" ) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING("application/x-gpac-sf") ); + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + + gst = gf_filter_pid_get_udta(pid); + if (!gst) { + GF_SAFEALLOC(gst, GSFStream); + if (!gst) return GF_OUT_OF_MEM; + gf_list_add(ctx->streams, gst); + gst->pid = pid; + gf_filter_pid_set_udta(pid, gst); + gst->idx = 1+ctx->max_pid_idx; + ctx->max_pid_idx++; + gst->timescale = 1000; + if (p && p->value.uint) gst->timescale = p->value.uint; + + gsfmx_send_pid_config(filter, ctx, gst, GF_FALSE, GF_FALSE); + + } else { + if (p && p->value.uint) gst->timescale = p->value.uint; + gst->last_cts_config = 0; + gsfmx_send_pid_config(filter, ctx, gst, GF_FALSE, GF_FALSE); + + } + return GF_OK; +} + + +GF_Err gsfmx_process(GF_Filter *filter) +{ + GSFMxCtx *ctx = gf_filter_get_udta(filter); + u32 i, nb_eos, count = gf_list_count(ctx->streams); + + nb_eos = 0; + for (i=0; istreams, i); + GF_FilterPacket *pck = gf_filter_pid_get_packet(gst->pid); + if (!pck) { + if (gf_filter_pid_is_eos(gst->pid)) { + nb_eos++; + if (!gst->eos) { + gsfmx_send_pid_eos(ctx, gst, (nb_eos==count) ? GF_TRUE : GF_FALSE); + gst->eos = GF_TRUE; + } + } + continue; + } + gst->eos = GF_FALSE; + gsfmx_write_data_packet(ctx, gst, pck); + gf_filter_pid_drop_packet(gst->pid); + } + if (ctx->regenerate_tunein_info) { + ctx->regenerate_tunein_info = GF_FALSE; + gsfmx_send_header(filter, ctx, GF_TRUE); + for (i=0; istreams, i); + gsfmx_send_pid_config(filter, ctx, gst, GF_FALSE, GF_TRUE); + } + } + + if (count && (nb_eos==count) ) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; +} + +static Bool gsfmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GSFMxCtx *ctx = gf_filter_get_udta(filter); + if (evt->base.type==GF_FEVT_INFO_UPDATE) { + GSFStream *gst = gf_filter_pid_get_udta(evt->base.on_pid); + if (gst) + gsfmx_send_pid_config(filter, ctx, gst, GF_TRUE, GF_FALSE); + } + //don't cancel events + return GF_FALSE; +} + +static GF_Err gsfmx_initialize(GF_Filter *filter) +{ + GSFMxCtx *ctx = gf_filter_get_udta(filter); + + gf_rand_init(GF_FALSE); + + if (ctx->key.size==16) { +#ifdef GPAC_DISABLE_CRYPTO + return GF_NOT_SUPPORTED; +#else + GF_Err e; + if (ctx->IV.size==16) { + memcpy(ctx->crypt_IV, ctx->IV.ptr, 16); + } else if (ctx->IV.size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFMux] Wrong IV value, size %d expecting 16\n", ctx->key.size)); + return GF_BAD_PARAM; + } else { + char szIV[64]; + u32 i; + * (u32 *) &ctx->crypt_IV[0] = gf_rand(); + * (u32 *) &ctx->crypt_IV[4] = gf_rand(); + * (u32 *) &ctx->crypt_IV[8] = gf_rand(); + * (u32 *) &ctx->crypt_IV[12] = gf_rand(); + szIV[0] = 0; + for (i=0; i<16; i++) { + char szC[3]; + sprintf(szC, "%02X", ctx->crypt_IV[i]); + strcat(szIV, szC); + } + GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[GSFMux] Generated IV value Ox%s\n", szIV)); + } + ctx->crypt = gf_crypt_open(GF_AES_128, GF_CBC); + if (!ctx->crypt) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFMux] Failed to allocate crypt context\n")); + return GF_IO_ERR; + } + e = gf_crypt_init(ctx->crypt, ctx->key.ptr, ctx->crypt_IV); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFMux] Failed to setup encryption: %s\n", gf_error_to_string(e) )); + return GF_IO_ERR; + } +#endif + } else if (ctx->key.size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[GSFMux] Wrong key value, size %d expecting 16\n", ctx->key.size)); + return GF_BAD_PARAM; + } + + ctx->streams = gf_list_new(); + if (!ctx->streams) return GF_OUT_OF_MEM; + + ctx->is_start = GF_TRUE; + + return GF_OK; +} + +static void gsfmx_finalize(GF_Filter *filter) +{ + GSFMxCtx *ctx = gf_filter_get_udta(filter); + while (gf_list_count(ctx->streams)) { + GSFStream *gst = gf_list_pop_back(ctx->streams); + gf_free(gst); + } + gf_list_del(ctx->streams); + + if (ctx->bs_w) gf_bs_del(ctx->bs_w); + if (ctx->buffer) gf_free(ctx->buffer); +#ifndef GPAC_DISABLE_CRYPTO + if (ctx->crypt) gf_crypt_close(ctx->crypt); +#endif +} + +static const GF_FilterCapability GSFMxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "gsf"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "application/x-gpac-sf"), +}; + + + +#define OFFS(_n) #_n, offsetof(GSFMxCtx, _n) +static const GF_FilterArgs GSFMxArgs[] = +{ + { OFFS(sigsn), "signal packet sequence number after header field and before size field. Sequence number is per PID, encoded on 16 bits. Header packet does not have a SN", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(sigdur), "signal duration", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(sigbo), "signal byte offset", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(sigdts), "signal decoding timestamp", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(dbg), "set debug mode\n" + "- no: disable debug\n" + "- nodata: force packet size to 0\n" + "- nopck: skip packet", GF_PROP_UINT, "no", "no|nodata|nopck", GF_FS_ARG_HINT_EXPERT}, +#ifndef GPAC_DISABLE_CRYPTO + { OFFS(key), "encrypt packets using given key - see filter helps", GF_PROP_DATA, NULL, NULL, 0}, + { OFFS(IV), "set IV for encryption - a constant IV is used to keep packet overhead small (cbcs-like)", GF_PROP_DATA, NULL, NULL, 0}, + { OFFS(pattern), "set nb crypt / nb_skip block pattern. default is all encrypted", GF_PROP_FRACTION, "1/0", NULL, GF_FS_ARG_HINT_ADVANCED}, +#endif // GPAC_DISABLE_CRYPTO + { OFFS(mpck), "set max packet size. 0 means no fragmentation (each AU is sent in one packet)", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(magic), "magic string to append in setup packet", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(skp), "comma separated list of pid property names to skip - see filter help", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(minp), "include only the minimum set of properties required for stream processing - see filter help", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(crate), "carousel period for tune-in info in seconds - see filter help", GF_PROP_DOUBLE, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + + +GF_FilterRegister GSFMxRegister = { + .name = "gsfmx", + GF_FS_SET_DESCRIPTION("GSF Muxer") +#ifndef GPAC_DISABLE_DOC + .help = "This filter provides GSF (__GPAC Super/Simple/Serialized/Stream/State Format__) multiplexing.\n" + "It serializes the stream states (config/reconfig/info update/remove/eos) and packets of input PIDs. " + "This allows either saving to file a session, or forwarding the state/data of streams to another instance of GPAC " + "using either pipes or sockets. Upstream events are not serialized.\n" + "\n" + "The default behaviour does not insert sequence numbers. When running over general protocols not ensuring packet order, this should be inserted.\n" + "The serializer sends tune-in packets (global and per pid) at the requested carousel rate - if 0, no carousel. These packets are marked as redundant so that they can be discarded by output filters if needed.\n" + "\n" +#ifndef GPAC_DISABLE_CRYPTO + "The stream format can be encrypted in AES 128 CBC mode. For all packets, the packet header (header, size, frame size/block offset and optional seq num) are in the clear " + "and the followings byte until the last byte of the last multiple of block size (16) fitting in the payload are encrypted.\n" + "For data packets, each fragment is encrypted individually to avoid error propagation in case of losses.\n" + "For other packets, the entire packet is encrypted before fragmentation (fragments cannot be processed individually).\n" + "For header/tunein packets, the first 25 bytes after the header are in the clear (signature,version,IV and pattern).\n" + "The [-IV]() is constant to avoid packet overhead, randomly generated if not set and sent in the initial stream header. " + "Pattern mode can be used (cf CENC cbcs) to encrypt K block and leave N blocks in the clear.\n" + "\n" +#endif + "The header/tunein packet may get quite big when all pid properties are kept. In order to help reduce its size, the [-minp]() option can be used: " + "this will remove all built-in properties marked as dropable (cf property help) as well as all non built-in properties.\n" + "The [-skp]() option may also be used to specify which property to drop:\n" + "EX skp=\"4CC1,Name2\n"\ + "This will remove properties of type 4CC1 and properties (built-in or not) of name Name2.\n" + "\n" + , +#endif + .private_size = sizeof(GSFMxCtx), + .max_extra_pids = (u32) -1, + .args = GSFMxArgs, + .flags = GF_FS_REG_DYNAMIC_REDIRECT, + SETCAPS(GSFMxCaps), + .initialize = gsfmx_initialize, + .finalize = gsfmx_finalize, + .configure_pid = gsfmx_configure_pid, + .process = gsfmx_process, + .process_event = gsfmx_process_event, +}; + + +const GF_FilterRegister *gsfmx_register(GF_FilterSession *session) +{ + return &GSFMxRegister; +} diff --git a/src/filters/mux_isom.c b/src/filters/mux_isom.c new file mode 100644 index 0000000..207edfa --- /dev/null +++ b/src/filters/mux_isom.c @@ -0,0 +1,5653 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / ISOBMF mux filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_ISOM_WRITE + +#define TEXT_DEFAULT_WIDTH 400 +#define TEXT_DEFAULT_HEIGHT 60 +#define TEXT_DEFAULT_FONT_SIZE 18 + +#define GF_VENDOR_GPAC GF_4CC('G','P','A','C') + + +#define ISOM_FILE_EXT "mp4|mpg4|m4a|m4i|3gp|3gpp|3g2|3gp2|iso|ismv|m4s|heif|heic|avci|mj2|mov|qt" +#define ISOM_FILE_MIME "application/x-isomedia|application/mp4|video/mp4|audio/mp4|video/3gpp|audio/3gpp|video/3gp2|audio/3gp2|video/iso.segment|audio/iso.segment|image/heif|image/heic|image/avci|video/quicktime" + +enum{ + NALU_NONE, + NALU_AVC, + NALU_HEVC +}; + + +enum +{ + CENC_NONE=0, + CENC_NEED_SETUP, + CENC_SETUP_DONE, + CENC_SETUP_ERROR +}; + +enum{ + TAG_NONE, + TAG_STRICT, + TAG_ALL +}; + + +typedef struct +{ + GF_FilterPid *ipid; + u32 track_num, track_id; + GF_ISOSample sample; + + u32 src_timescale; + u32 tk_timescale; + u32 stream_type; + u32 codecid; + Bool is_encrypted; + + u32 cfg_crc, enh_cfg_crc; + u32 dep_id; + u32 stsd_idx; + u32 clear_stsd_idx; + + Bool use_dref; + Bool aborted; + Bool suspended; + Bool has_append; + Bool has_ctts; + s64 min_neg_ctts; + u32 nb_samples, samples_in_stsd; + u32 nb_frames_per_sample; + u64 ts_shift; + + Bool skip_bitrate_update; + Bool has_open_gop; + GF_FilterSAPType gdr_type; + + Bool next_is_first_sample; + + u32 media_profile_level; + + Bool import_msg_header_done; + + u32 nal_unit_size; + + GF_AVCConfig *avcc, *svcc; + GF_HEVCConfig *hvcc, *lvcc; + + u8 *inband_hdr; + u32 inband_hdr_size; + u32 is_nalu; + Bool is_av1, is_vpx; + Bool fragment_done; + s32 ts_delay, negctts_shift; + Bool insert_tfdt, probe_min_ctts; + u64 first_dts_in_seg, next_seg_cts, cts_next; + u64 offset_dts; + u32 samples_in_frag; + + //0: not cenc, 1: needs setup of stsd entry, 2: setup done + u32 cenc_state; + u32 skip_byte_block, crypt_byte_block; + u32 IV_size, constant_IV_size; + bin128 constant_IV, KID; + Bool cenc_subsamples; + u32 scheme_type; + + Bool fake_track; + + Bool has_brands; + Bool force_inband_inject; + + u32 dur_in_frag; + + u32 amr_mode_set; + Bool has_seig; + u64 empty_init_dur; + u32 raw_audio_bytes_per_sample; + u64 dts_patch; + + Bool is_item; + u32 item_id; + char status_type; + u32 last_import_pc; + + u32 nb_frames; + u64 down_bytes, down_size; + GF_Fraction64 pid_dur; + //for import message + u64 prog_done, prog_total; + + u32 prev_tid_group; + + Bool box_patched; +} TrackWriter; + +enum +{ + MP4MX_MODE_INTER=0, + MP4MX_MODE_FLAT, + MP4MX_MODE_FASTSTART, + MP4MX_MODE_TIGHT, + MP4MX_MODE_FRAG, + MP4MX_MODE_SFRAG, +}; + + +enum +{ + MP4MX_DASH_OFF=0, + MP4MX_DASH_ON, + MP4MX_DASH_VOD, +}; + +enum +{ + MP4MX_PSSH_MOOV=0, + MP4MX_PSSH_MOOF, + MP4MX_PSSH_SKIP, +}; + +enum +{ + MP4MX_CT_EDIT=0, + MP4MX_CT_NOEDIT, + MP4MX_CT_NEGCTTS, +}; + +enum +{ + MP4MX_VODCACHE_ON=0, + MP4MX_VODCACHE_INSERT, + MP4MX_VODCACHE_REPLACE, +}; + +typedef struct +{ + //filter args + GF_ISOFile *file; + char *tmpd; + Bool m4sys, dref; + GF_Fraction idur; + u32 pack3gp, ctmode; + Bool importer, pack_nal, moof_first, abs_offset, fsap, tfdt_traf; + u32 xps_inband; + u32 block_size; + u32 store, tktpl, mudta; + s32 subs_sidx; + Double cdur; + s32 moovts; + char *m4cc; + Bool chain_sidx; + u32 msn, msninc; + GF_Fraction64 tfdt; + Bool nofragdef, straf, strun, sgpd_traf, noinit; + u32 vodcache; + u32 psshs; + u32 trackid; + Bool fragdur; + Bool btrt; + Bool ssix; + Bool ccst; + s32 mediats; + GF_AudioSampleEntryImportMode ase; + char *styp; + Bool sseg; + Bool noroll; + Bool saio32; + u32 compress; + Bool trun_inter; + char *boxpatch; + Bool fcomp; + Bool deps; + Bool mvex; + u32 sdtp_traf; +#ifdef GF_ENABLE_CTRN + Bool ctrn; + Bool ctrni; +#endif + Bool mfra; + Bool forcesync; + u32 tags; + + //internal + Bool owns_mov; + GF_FilterPid *opid; + Bool first_pck_sent; + + GF_List *tracks; + + GF_BitStream *bs_r; + //fragmentation state + Bool init_movie_done, fragment_started, segment_started, insert_tfdt, insert_pssh, cdur_set; + Double next_frag_start, adjusted_next_frag_start; + + u64 current_offset; + u64 current_size; + + u32 nb_segs, nb_frags, nb_frags_in_seg; + + GF_FilterPacket *dst_pck; + char *seg_name; + u32 dash_seg_num; + Bool flush_seg; + u32 eos_marker; + TrackWriter *ref_tkw; + Bool single_file; + Bool store_output; + FILE *tmp_store; + u64 flush_size, flush_done; + + u32 dash_mode; + Double dash_dur; + Double media_dur; + u32 sidx_max_size, sidx_chunk_offset; + Bool final_sidx_flush; + Bool sidx_size_exact; + + u32 *seg_sizes; + u32 nb_seg_sizes, alloc_seg_sizes; + Bool config_timing; + + u32 major_brand_set; + Bool def_brand_patched; + + Bool force_play; + + Bool moov_inserted; + Bool update_report; + u64 total_bytes_in, total_bytes_out; + u32 total_samples, last_mux_pc; + + u32 maxchunk; + u32 make_qt; + TrackWriter *prores_track; + + GF_SegmentIndexBox *cloned_sidx; + u32 cloned_sidx_index; + Double faststart_ts_regulate; + + Bool is_rewind; + Bool box_patched; + u32 cur_file_idx_plus_one; + char *cur_file_suffix; + Bool notify_filename; + + u32 next_file_idx; + const char *next_file_suffix; +} GF_MP4MuxCtx; + +static void mp4_mux_set_hevc_groups(GF_MP4MuxCtx *ctx, TrackWriter *tkw); + +static GF_Err mp4mx_setup_dash_vod(GF_MP4MuxCtx *ctx, TrackWriter *tkw) +{ + if (tkw) { + const GF_PropertyValue *p; + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_DASH_DUR); + if (p) { + ctx->dash_dur = p->value.number; + } + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_DURATION); + if (p && p->value.lfrac.den) { + Double mdur = (Double) p->value.lfrac.num; + mdur /= p->value.lfrac.den; + if (ctx->media_dur < mdur) ctx->media_dur = mdur; + } + } + ctx->dash_mode = MP4MX_DASH_VOD; + if ((ctx->vodcache==MP4MX_VODCACHE_ON) && !ctx->tmp_store) { + ctx->tmp_store = gf_file_temp(NULL); + if (!ctx->tmp_store) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Cannot allocate temp file for VOD sidx generation\n")); + return GF_IO_ERR; + } + if (!ctx->block_size) ctx->block_size = 10000; + } + + return GF_OK; +} + + +static u32 gf_isom_stream_type_to_media_type(u32 stream_type, u32 codecid) +{ + switch (stream_type) { + case GF_STREAM_SCENE: return GF_ISOM_MEDIA_SCENE; + case GF_STREAM_OD: return GF_ISOM_MEDIA_OD; + case GF_STREAM_OCR: return GF_ISOM_MEDIA_OCR; + case GF_STREAM_OCI: return GF_ISOM_MEDIA_OCI; + case GF_STREAM_MPEG7: return GF_ISOM_MEDIA_MPEG7; + case GF_STREAM_METADATA: return GF_ISOM_MEDIA_META; + case GF_STREAM_VISUAL: return GF_ISOM_MEDIA_VISUAL; + case GF_STREAM_AUDIO: return GF_ISOM_MEDIA_AUDIO; + case GF_STREAM_TEXT: + if (codecid==GF_ISOM_SUBTYPE_STPP) + return GF_ISOM_MEDIA_MPEG_SUBT; + if (codecid == GF_CODECID_SUBPIC) + return GF_ISOM_MEDIA_SUBPIC; + return GF_ISOM_MEDIA_TEXT; + case GF_STREAM_INTERACT: return GF_ISOM_MEDIA_SCENE; + case GF_STREAM_IPMP: return GF_ISOM_MEDIA_IPMP; + case GF_STREAM_MPEGJ: return GF_ISOM_MEDIA_MPEGJ; + case GF_STREAM_IPMP_TOOL: return GF_ISOM_MEDIA_IPMP; + case GF_STREAM_FONT: return GF_ISOM_MEDIA_MPEGJ;//TOCHECK !! + + case GF_STREAM_PRIVATE_SCENE: + case GF_STREAM_ENCRYPTED: + case GF_STREAM_FILE: + return 0; + default: + return stream_type; + } + return 0; +} + +static void mp4_mux_write_ps_list(GF_BitStream *bs, GF_List *list, u32 nalu_size_length) +{ + u32 i, count = list ? gf_list_count(list) : 0; + for (i=0; isize, 8*nalu_size_length); + gf_bs_write_data(bs, sl->data, sl->size); + } +} + +static GF_List *mp4_mux_get_hevc_ps(GF_HEVCConfig *cfg, u8 type) +{ + u32 i, count = gf_list_count(cfg->param_array); + for (i=0; iparam_array, i); + if (pa->type == type) return pa->nalus; + } + return NULL; +} + +static void mp4_mux_make_inband_header(GF_MP4MuxCtx *ctx, TrackWriter *tkw) +{ + GF_BitStream *bs; + if (tkw->inband_hdr) gf_free(tkw->inband_hdr); + + tkw->nal_unit_size = 0; + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + if (tkw->avcc || tkw->svcc) { + if (tkw->avcc) { + mp4_mux_write_ps_list(bs, tkw->avcc->sequenceParameterSets, tkw->avcc->nal_unit_size); + /*if (!tkw->nal_unit_size) */tkw->nal_unit_size = tkw->avcc->nal_unit_size; + } + + if (tkw->svcc) { + mp4_mux_write_ps_list(bs, tkw->svcc->sequenceParameterSets, tkw->svcc->nal_unit_size); + if (!tkw->nal_unit_size) tkw->nal_unit_size = tkw->svcc->nal_unit_size; + } + + if (tkw->avcc && tkw->avcc->sequenceParameterSetExtensions) + mp4_mux_write_ps_list(bs, tkw->avcc->sequenceParameterSetExtensions, tkw->avcc->nal_unit_size); + + if (tkw->svcc && tkw->svcc->sequenceParameterSetExtensions) + mp4_mux_write_ps_list(bs, tkw->svcc->sequenceParameterSetExtensions, tkw->svcc->nal_unit_size); + + if (tkw->avcc) + mp4_mux_write_ps_list(bs, tkw->avcc->pictureParameterSets, tkw->avcc->nal_unit_size); + + if (tkw->svcc) + mp4_mux_write_ps_list(bs, tkw->svcc->pictureParameterSets, tkw->svcc->nal_unit_size); + } + if (tkw->hvcc || tkw->lvcc) { + if (tkw->hvcc) { + mp4_mux_write_ps_list(bs, mp4_mux_get_hevc_ps(tkw->hvcc, GF_HEVC_NALU_VID_PARAM), tkw->hvcc->nal_unit_size); + if (!tkw->nal_unit_size) tkw->nal_unit_size = tkw->hvcc->nal_unit_size; + } + if (tkw->lvcc) { + mp4_mux_write_ps_list(bs, mp4_mux_get_hevc_ps(tkw->lvcc, GF_HEVC_NALU_VID_PARAM), tkw->lvcc->nal_unit_size); + if (!tkw->nal_unit_size) tkw->nal_unit_size = tkw->lvcc->nal_unit_size; + } + if (tkw->hvcc) + mp4_mux_write_ps_list(bs, mp4_mux_get_hevc_ps(tkw->hvcc, GF_HEVC_NALU_SEQ_PARAM), tkw->hvcc->nal_unit_size); + if (tkw->lvcc) + mp4_mux_write_ps_list(bs, mp4_mux_get_hevc_ps(tkw->lvcc, GF_HEVC_NALU_SEQ_PARAM), tkw->lvcc->nal_unit_size); + if (tkw->hvcc) + mp4_mux_write_ps_list(bs, mp4_mux_get_hevc_ps(tkw->hvcc, GF_HEVC_NALU_PIC_PARAM), tkw->hvcc->nal_unit_size); + if (tkw->lvcc) + mp4_mux_write_ps_list(bs, mp4_mux_get_hevc_ps(tkw->lvcc, GF_HEVC_NALU_PIC_PARAM), tkw->lvcc->nal_unit_size); + } + + tkw->inband_hdr=NULL; + gf_bs_get_content(bs, &tkw->inband_hdr, &tkw->inband_hdr_size); + gf_bs_del(bs); + //we may have cases where the param sets are updated before a non-IDR/SAP3 picture, we must inject asap at least once + tkw->force_inband_inject = GF_TRUE; +} + +void mp4_mux_get_video_size(GF_MP4MuxCtx *ctx, u32 *width, u32 *height) +{ + u32 w, h, f_w, f_h, i; + + f_w = f_h = 0; + for (i=0; ifile); i++) { + switch (gf_isom_get_media_type(ctx->file, i+1)) { + case GF_ISOM_MEDIA_SCENE: + case GF_ISOM_MEDIA_VISUAL: + gf_isom_get_visual_info(ctx->file, i+1, 1, &w, &h); + if (w > f_w) f_w = w; + if (h > f_h) f_h = h; + //fallthrough + case GF_ISOM_MEDIA_TEXT: + gf_isom_get_track_layout_info(ctx->file, i+1, &w, &h, NULL, NULL, NULL); + if (w > f_w) f_w = w; + if (h > f_h) f_h = h; + break; + } + } + (*width) = f_w ? f_w : TEXT_DEFAULT_WIDTH; + (*height) = f_h ? f_h : TEXT_DEFAULT_HEIGHT; +} + +static void mp4_mux_track_writer_del(TrackWriter *tkw) +{ + if (tkw->avcc) gf_odf_avc_cfg_del(tkw->avcc); + if (tkw->svcc) gf_odf_avc_cfg_del(tkw->svcc); + if (tkw->hvcc) gf_odf_hevc_cfg_del(tkw->hvcc); + if (tkw->lvcc) gf_odf_hevc_cfg_del(tkw->lvcc); + if (tkw->inband_hdr) gf_free(tkw->inband_hdr); + gf_free(tkw); +} + +static void mp4_mux_write_track_refs(GF_MP4MuxCtx *ctx, TrackWriter *tkw, const char *rname, u32 rtype) +{ + u32 i; + const GF_PropertyValue *p = gf_filter_pid_get_property_str(tkw->ipid, rname); + if (!p) return; + for (i=0; ivalue.uint_list.nb_items; i++) { + gf_isom_set_track_reference(ctx->file, tkw->track_num, rtype, p->value.uint_list.vals[i]); + } +} + +static void mp4mux_track_reorder(void *udta, u32 old_track_num, u32 new_track_num) +{ + GF_MP4MuxCtx *ctx = (GF_MP4MuxCtx *) udta; + u32 i, count; + count = gf_list_count(ctx->tracks); + for (i=0; itracks, i); + if (tkw->track_num==old_track_num) { + tkw->track_num = new_track_num; + return; + } + } +} + +static void mp4mux_reorder_tracks(GF_MP4MuxCtx *ctx) +{ + u32 i, count, prev_num, prev_pos; + GF_List *new_tracks = gf_list_new(); + prev_num = prev_pos = 0; + count = gf_list_count(ctx->tracks); + for (i=0; itracks, i); + if (tkw->track_numtrack_num; + } + if (gf_list_count(new_tracks)!=count) { + gf_list_del(new_tracks); + return; + } + gf_list_del(ctx->tracks); + ctx->tracks = new_tracks; +} + +static void mp4_mux_set_tags(GF_MP4MuxCtx *ctx, TrackWriter *tkw) +{ + u32 idx=0; + if (ctx->tags==TAG_NONE) return; + + while (1) { + GF_Err e; + u32 tag_val; + u32 prop_4cc=0; + s32 itag; + const char *tag_name=NULL; + const GF_PropertyValue *tag = gf_filter_pid_enum_properties(tkw->ipid, &idx, &prop_4cc, &tag_name); + if (!tag) break; + + if (prop_4cc==GF_PROP_PID_COVER_ART) { + e = gf_isom_apple_set_tag(ctx->file, GF_ISOM_ITUNE_COVER_ART, tag->value.data.ptr, tag->value.data.size); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to set cover art: %s\n", gf_error_to_string(e))); + } + } + if (!tag_name) + continue; + + itag = gf_itags_find_by_name(tag_name); + if (itag>=0) { + Bool tag_set=GF_FALSE; + u32 val; + char _t[8]; + u32 itype = gf_itags_get_type((u32) itag); + itag = gf_itags_get_itag(itag); + + e = GF_OK; + if ((itag==GF_ISOM_ITUNE_DISK) ||(itag==GF_ISOM_ITUNE_TRACKNUMBER)) { + u32 n=0, t=0, tlen=0; + if (tag->value.string) { + memset(_t, 0, sizeof(char) * 8); + tlen = (itag == GF_ISOM_ITUNE_DISK) ? 6 : 8; + if (sscanf(tag->value.string, "%u/%u", &n, &t) == 2) { + _t[3] = n; + _t[2] = n >> 8; + _t[5] = t; + _t[4] = t >> 8; + } + else if (sscanf(tag->value.string, "%u", &n) == 1) { + _t[3] = n; + _t[2] = n >> 8; + } + else tlen = 0; + } + if (!tag->value.string || tlen) { + e = gf_isom_apple_set_tag(ctx->file, itag, tlen ? (u8 *)_t : NULL, tlen); + tag_set = GF_TRUE; + } + } + else if (itag==GF_ISOM_ITUNE_GENRE) { + val = 0; + if ((tag->type==GF_PROP_STRING) && tag->value.string) { + val = gf_id3_get_genre_tag(tag->value.string); + if (val) { + e = gf_isom_apple_set_tag(ctx->file, itag, NULL, val); + tag_set = GF_TRUE; + } + } + } + + if (!tag_set) { + switch (itype) { + case GF_ITAG_STR: + case GF_ITAG_SUBSTR: + if ((tag->type==GF_PROP_STRING) && tag->value.string) { + e = gf_isom_apple_set_tag(ctx->file, itag, tag->value.string, (u32) strlen(tag->value.string)); + } else { + e = GF_BAD_PARAM; + } + break; + case GF_ITAG_INT: + val=0; + if ((tag->type==GF_PROP_STRING) && tag->value.string) { + val = atoi(tag->value.string); + } else if (tag->type==GF_PROP_UINT) { + val = tag->value.uint; + } + if (val) { + e = gf_isom_apple_set_tag(ctx->file, itag, NULL, val); + } + break; + + case GF_ITAG_BOOL: + _t[0] = 0; + if ((tag->type==GF_PROP_STRING) && tag->value.string && + (!stricmp(tag->value.string, "yes") || !stricmp(tag->value.string, "true") || !stricmp(tag->value.string, "1")) + ) { + _t[0] = 1; + } else if (tag->value.boolean) { + _t[0] = 1; + } + e = gf_isom_apple_set_tag(ctx->file, itag, _t, 1); + } + } + + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to set tag %s: %s\n", tag_name, gf_error_to_string(e))); + } + continue; + } + if (ctx->tags==TAG_STRICT) + continue; + + if (strnicmp(tag_name, "tag_", 4)) + continue; + + tag_name += 4; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MP4Mux] Unrecognized tag %s: %s\n", tag_name, tag->value.string)); + if ((tag->type!=GF_PROP_STRING) && (tag->type!=GF_PROP_DATA) && (tag->type!=GF_PROP_CONST_DATA)) continue; + + if (strlen(tag_name)==4) { + tag_val = GF_4CC(tag_name[0], tag_name[1], tag_name[2], tag_name[3]); + } else { + tag_val = gf_crc_32(tag_name, (u32) strlen(tag_name)); + GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[MP4Mux] Tag name %s is not a 4CC, using CRC32 %d as value\n", tag_name, tag_val)); + } + if (tag->type==GF_PROP_STRING) { + if (tag->value.string) { + if (tag_val==GF_ID3V2_FRAME_TCOP) { + e = gf_isom_set_copyright(ctx->file, "unk", tag->value.string); + } else { + e = gf_isom_apple_set_tag(ctx->file, tag_val, tag->value.string, (u32) strlen(tag->value.string) ); + } + } else { + e = gf_isom_apple_set_tag(ctx->file, tag_val, NULL, 0); + } + } else { + e = gf_isom_apple_set_tag(ctx->file, tag_val, tag->value.data.ptr , tag->value.data.size); + } + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to set tag %s: %s\n", tag_name, gf_error_to_string(e))); + } + } +} + + +static GF_Err mp4_mux_setup_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_true_pid) +{ + void mux_assign_mime_file_ext(GF_FilterPid *ipid, GF_FilterPid *opid, const char *file_exts, const char *mime_types, const char *def_ext); + Bool use_m4sys = GF_FALSE; + Bool use_tx3g = GF_FALSE; + Bool use_webvtt = GF_FALSE; + Bool needs_track = GF_FALSE; + u32 needs_sample_entry = 0; //1: change of codecID, 2 change of decoder config + Bool use_gen_sample_entry = GF_FALSE; + Bool use_3gpp_config = GF_FALSE; + Bool use_ac3_entry = GF_FALSE; + Bool use_flac_entry = GF_FALSE; + Bool use_avc = GF_FALSE; + Bool use_hevc = GF_FALSE; + Bool use_hvt1 = GF_FALSE; + Bool use_av1 = GF_FALSE; + Bool use_vpX = GF_FALSE; + Bool use_mj2 = GF_FALSE; + Bool use_opus = GF_FALSE; + Bool use_dref = GF_FALSE; + Bool skip_dsi = GF_FALSE; + Bool is_text_subs = GF_FALSE; + Bool force_colr = GF_FALSE; + u32 m_subtype=0; + u32 m_subtype_src=0; + u32 m_subtype_alt_raw=0; + u32 raw_bitdepth=0; + u32 override_stype=0; + u32 width, height, sr, nb_chan, nb_bps, z_order, txt_fsize; + u64 ch_layout; + GF_Fraction fps, sar; + GF_List *multi_pid_stsd = NULL; + u32 multi_pid_idx = 0; + GF_FilterPid *orig_pid = NULL; + u32 codec_id; + u32 frames_per_sample_backup=0; + u32 is_nalu_backup = NALU_NONE; + Bool is_tile_base = GF_FALSE; + Bool unknown_generic = GF_FALSE; + u32 multi_pid_final_stsd_idx = 0; + u32 audio_pli=0; + Bool force_tk_layout = GF_FALSE; + Bool force_mix_xps = GF_FALSE; + Bool make_inband_headers = GF_FALSE; + Bool is_prores = GF_FALSE; + + const char *lang_name = NULL; + const char *comp_name = NULL; + const char *imp_name = NULL; + const char *src_url = NULL; + const char *meta_mime = NULL; + const char *meta_encoding = NULL; + const char *meta_config = NULL; + const char *meta_xmlns = NULL; + const char *meta_schemaloc = NULL; + const char *meta_auxmimes = NULL; + const char *meta_content_encoding = NULL; + char *txt_font = NULL; + + u32 i, count, reuse_stsd = 0; + GF_Err e; + const GF_PropertyValue *dsi=NULL; + const GF_PropertyValue *enh_dsi=NULL; + const GF_PropertyValue *p; + GF_MP4MuxCtx *ctx = gf_filter_get_udta(filter); + GF_AudioSampleEntryImportMode ase_mode = ctx->ase; + TrackWriter *tkw; + + if (ctx->owns_mov && !ctx->opid) { + char *dst; + ctx->opid = gf_filter_pid_new(filter); + + dst = gf_filter_get_dst_name(filter); + if (dst) { + char *ext = gf_file_ext_start(dst); + if (ext && (!stricmp(ext, ".mov") || !stricmp(ext, ".qt")) ) { + ctx->make_qt = 1; + } + gf_free(dst); + } + } + //copy properties at init or reconfig + if (ctx->opid && is_true_pid) { + gf_filter_pid_copy_properties(ctx->opid, pid); + if (gf_list_count(ctx->tracks)>1) + gf_filter_pid_set_name(ctx->opid, "isobmf_mux"); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); + + mux_assign_mime_file_ext(pid, ctx->opid, ISOM_FILE_EXT, ISOM_FILE_MIME, NULL); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DASH_MODE, NULL); + + switch (ctx->store) { + case MP4MX_MODE_FLAT: + case MP4MX_MODE_FASTSTART: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DISABLE_PROGRESSIVE, &PROP_UINT(GF_PID_FILE_PATCH_INSERT) ); + break; + case MP4MX_MODE_INTER: + case MP4MX_MODE_TIGHT: + gf_filter_pid_allow_direct_dispatch(ctx->opid); + break; + } + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TILE_BASE); + if (p && p->value.boolean) is_tile_base = GF_TRUE; + + if (is_true_pid && !is_tile_base) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_MULTI_TRACK); + if (p) { + u32 j, count2; + GF_List *multi_tracks = p->value.ptr; + count = gf_list_count(multi_tracks); + for (i=0; itracks); + for (j=0; jtracks, j); + const GF_PropertyValue *c_pidid = gf_filter_pid_get_property(atkw->ipid, GF_PROP_PID_ID); + if (gf_props_equal(a_pidid, c_pidid)) { + a_ipid = NULL; + break; + } + } + if (a_ipid) + mp4_mux_setup_pid(filter, a_ipid, GF_FALSE); + } + } + } + + audio_pli = gf_isom_get_pl_indication(ctx->file, GF_ISOM_PL_AUDIO); + + //new pid ? + tkw = gf_filter_pid_get_udta(pid); + if (!tkw) { + GF_FilterEvent evt; + GF_SAFEALLOC(tkw, TrackWriter); + if (!tkw) return GF_OUT_OF_MEM; + + gf_list_add(ctx->tracks, tkw); + tkw->ipid = pid; + tkw->fake_track = !is_true_pid; + if (is_true_pid) { + gf_filter_pid_set_udta(pid, tkw); + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + gf_filter_pid_get_min_pck_duration(pid); + } +#endif + if (!ctx->owns_mov || ctx->force_play) { + GF_FEVT_INIT(evt, GF_FEVT_PLAY, pid); + gf_filter_pid_send_event(pid, &evt); + } + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ITEM_ID); + if (p) { + tkw->is_item = GF_TRUE; + } else { + ctx->config_timing = GF_TRUE; + ctx->update_report = GF_TRUE; + } + + } + } + + //check change of pid config + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DEPENDENCY_ID); + if (p) { + if (p->value.uint!=tkw->dep_id) needs_track = GF_TRUE; + tkw->dep_id = p->value.uint; + } + + //check change of pid config + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (p) { + if (p->value.uint!=tkw->codecid) needs_sample_entry = 1; + tkw->codecid = p->value.uint; + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (p) { + u32 stype = p->value.uint; + if (tkw->is_encrypted && (p->value.uint==GF_STREAM_ENCRYPTED) ) { + stype = gf_codecid_type(tkw->codecid); + } + if (stype != tkw->stream_type) { + needs_track = GF_TRUE; + tkw->stream_type = stype; + const char *name = gf_stream_type_name(stype); + tkw->status_type = name ? name[0] : 'U'; + + } + } + + dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (dsi) { + u32 cfg_crc = gf_crc_32(dsi->value.data.ptr, dsi->value.data.size); + if (cfg_crc!=tkw->cfg_crc) needs_sample_entry = 2; + tkw->cfg_crc = cfg_crc; + } else if (tkw->cfg_crc) { + tkw->cfg_crc = 0; + needs_sample_entry = 2; + } + + enh_dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + if (enh_dsi) { + u32 cfg_crc = gf_crc_32(enh_dsi->value.data.ptr, enh_dsi->value.data.size); + if (cfg_crc!=tkw->enh_cfg_crc) needs_sample_entry = 2; + tkw->enh_cfg_crc = cfg_crc; + } else if (tkw->enh_cfg_crc) { + tkw->enh_cfg_crc = 0; + needs_sample_entry = 2; + } + + //TODO: try to merge PPS/SPS for AVC and HEVC rather than creating a new sample description + + switch (tkw->codecid) { + case GF_CODECID_AAC_MPEG4: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + case GF_CODECID_MPEG4_PART2: + case GF_CODECID_AVC: + case GF_CODECID_SVC: + case GF_CODECID_HEVC: + case GF_CODECID_LHVC: + if (!dsi && !enh_dsi) return GF_OK; + break; + case GF_CODECID_APCH: + case GF_CODECID_APCO: + case GF_CODECID_APCN: + case GF_CODECID_APCS: + case GF_CODECID_AP4X: + case GF_CODECID_AP4H: + if (!ctx->make_qt) { + GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[MP4Mux] ProRes track detected, muxing to QTFF even though ISOBMFF was asked\n")); + ctx->make_qt = 2; + } + if (ctx->prores_track && (ctx->prores_track != tkw)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] More than one ProRes track detected, result might be non compliant\n")); + } + is_prores = GF_TRUE; + break; + default: + break; + } + if (!tkw->track_num) { + needs_sample_entry = 1; + needs_track = GF_TRUE; + } + + if (ctx->make_qt) { + gf_isom_remove_root_od(ctx->file); + gf_isom_set_brand_info(ctx->file, GF_ISOM_BRAND_QT, 512); + gf_isom_reset_alt_brands(ctx->file); + tkw->has_brands = GF_TRUE; + ctx->major_brand_set = GF_ISOM_BRAND_QT; + ctx->btrt = GF_FALSE; + + if (is_prores && !ctx->prores_track) { + ctx->prores_track = tkw; + } + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_URL); + if (p) src_url = p->value.string; + + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_MODE); + if (p) { + ctx->dash_mode = MP4MX_DASH_ON; + if (p->value.uint==2) { + e = mp4mx_setup_dash_vod(ctx, tkw); + if (e) return e; + } + } + //we consider that when muxing single segments, we are always in DASH, not VoD mode + else if (ctx->noinit) { + ctx->dash_mode = MP4MX_DASH_ON; + } + + if (!ctx->cdur_set) { + ctx->cdur_set = GF_TRUE; + if (ctx->cdur<0) { + if (ctx->make_qt) + ctx->cdur = 0.5; + else { + ctx->cdur = 1.0; + if (ctx->dash_mode) + ctx->fragdur = GF_FALSE; + } + } else if (ctx->dash_mode) + ctx->fragdur = GF_TRUE; + } + + if (needs_track) { + if (ctx->init_movie_done) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] Cannot add track to already finalized movie in fragmented file, will request a new muxer for that track\n")); + return GF_REQUIRES_NEW_INSTANCE; + } + if (tkw->is_item) { + needs_track = GF_FALSE; + } + } + + if (needs_track) { + u32 tkid=0; + u32 tk_idx=0; + u32 mtype=0; + u32 target_timescale = 0; + + if (ctx->make_qt && (tkw->stream_type==GF_STREAM_VISUAL)) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (p) { + u32 ts=p->value.frac.num, inc=p->value.frac.den; + if (inc * 24000 == ts * 1001) target_timescale = 24000; + else if (inc * 2400 == ts * 100) target_timescale = 2400; + else if (inc * 2500 == ts * 100) target_timescale = 2500; + else if (inc * 30000 == ts * 1001) target_timescale = 30000; + else if (inc * 2997 == ts * 100) target_timescale = 30000; + else if (inc * 3000 == ts * 100) target_timescale = 3000; + else if (inc * 5000 == ts * 100) target_timescale = 5000; + else if (inc * 60000 == ts * 1001) target_timescale = 60000; + else if (inc * 5994 == ts * 100) target_timescale = 60000; + else if (is_prores) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[ProRes] Unrecognized frame rate %g\n", ((Double)ts)/inc )); + return GF_NON_COMPLIANT_BITSTREAM; + } + } + if (!ctx->prores_track) + ctx->prores_track = tkw; + } + + if (!ctx->moov_inserted) { + if (target_timescale) { + ctx->moovts = target_timescale; + gf_isom_set_timescale(ctx->file, target_timescale); + } else if (ctx->moovts>=0) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ISOM_MOVIE_TIME); + if (p && p->value.lfrac.den) { + gf_isom_set_timescale(ctx->file, (u32) p->value.lfrac.den); + ctx->moovts = (u32) p->value.lfrac.den; + } else { + gf_isom_set_timescale(ctx->file, ctx->moovts); + } + } + if (ctx->store==MP4MX_MODE_FASTSTART) { + gf_isom_make_interleave(ctx->file, ctx->cdur); + } + } + + //assign some defaults + tkw->src_timescale = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) tkw->src_timescale = p->value.uint; + + u32 mtimescale = 1000; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); + if (p) mtimescale = p->value.uint; + else { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (p && p->value.frac.den) mtimescale = p->value.frac.den; + } + if (!tkw->src_timescale) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] No timescale specified, guessing from media: %d\n", mtimescale)); + tkw->src_timescale = mtimescale; + } + if (target_timescale) tkw->tk_timescale = target_timescale; + else if (ctx->mediats>0) tkw->tk_timescale = ctx->mediats; + else if (ctx->mediats<0) tkw->tk_timescale = mtimescale; + else tkw->tk_timescale = tkw->src_timescale; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ESID); + if (!p) p = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + if (p) tkid = p->value.uint; + + if (ctx->trackid) tkid = ctx->trackid; + + if (tkw->stream_type == GF_STREAM_ENCRYPTED) { + tkw->is_encrypted = GF_TRUE; + tkw->stream_type = gf_codecid_type(tkw->codecid); + } + mtype = gf_isom_stream_type_to_media_type(tkw->stream_type, tkw->codecid); + + if (ctx->moovts<0) { + ctx->moovts = tkw->tk_timescale; + gf_isom_set_timescale(ctx->file, (u32) ctx->moovts); + } + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_MUX_INDEX); + if (p) { + tk_idx = p->value.uint; + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ISOM_TRACK_TEMPLATE); + if (ctx->tktpl && p && p->value.data.ptr) { + Bool udta_only = (ctx->tktpl==2) ? GF_TRUE : GF_FALSE; + + + tkw->track_num = gf_isom_new_track_from_template(ctx->file, tkid, mtype, tkw->tk_timescale, p->value.data.ptr, p->value.data.size, udta_only); + if (!tkw->track_num) { + tkw->track_num = gf_isom_new_track_from_template(ctx->file, 0, mtype, tkw->tk_timescale, p->value.data.ptr, p->value.data.size, udta_only); + } + + if (!ctx->btrt) { + gf_isom_update_bitrate(ctx->file, tkw->track_num, 0, 0, 0, 0); + } + } else { + if (!mtype) { + mtype = GF_4CC('u','n','k','n'); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] Unable to find ISOM media type for stream type %s codec %s\n", gf_stream_type_name(tkw->stream_type), gf_codecid_name(tkw->codecid) )); + } + if (!tkid) tkid = tk_idx; + + tkw->track_num = gf_isom_new_track(ctx->file, tkid, mtype, tkw->tk_timescale); + if (!tkw->track_num) { + tkw->track_num = gf_isom_new_track(ctx->file, 0, mtype, tkw->tk_timescale); + } + //FIXME once we finally merge to filters, there is an old bug in isobmff initializing the width and height to 320x240 which breaks text import + //this should be removed and hashes regenerated + gf_isom_set_track_layout_info(ctx->file, tkw->track_num, 0, 0, 0, 0, 0); + + if (!gf_sys_is_test_mode()) { + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_URL); + if (tkw->track_num && p && p->value.string) { + char szHName[1025]; + char *f = gf_file_basename(p->value.string); + szHName[1024]=0; + snprintf(szHName, 1024, "*%s@GPAC%s", f ? f : "", gf_gpac_version() ); + gf_isom_set_handler_name(ctx->file, tkw->track_num, szHName); + } + } + } + + if (!tkw->track_num) { + e = gf_isom_last_error(ctx->file); + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to create new track: %s\n", gf_error_to_string(e) )); + return e; + } + tkw->track_id = gf_isom_get_track_id(ctx->file, tkw->track_num); + tkw->next_is_first_sample = GF_TRUE; + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_ISOM_TRACK_FLAGS); + if (p) { + gf_isom_set_track_flags(ctx->file, tkw->track_num, p->value.uint, GF_ISOM_TKFLAGS_SET); + } else { + gf_isom_set_track_enabled(ctx->file, tkw->track_num, GF_TRUE); + } + + //if we have a subtype set for the pid, use it + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SUBTYPE); + if (p) gf_isom_set_media_type(ctx->file, tkw->track_num, p->value.uint); + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_ISOM_HANDLER); + if (p && p->value.string) { + gf_isom_set_handler_name(ctx->file, tkw->track_num, p->value.string); + } + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_ISOM_TRACK_MATRIX); + if (p && (p->value.uint_list.nb_items==9)) { + gf_isom_set_track_matrix(ctx->file, tkw->track_num, (s32 *) p->value.uint_list.vals); + } + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_SRC_MAGIC); + if (p) { + gf_isom_set_track_magic(ctx->file, tkw->track_num, p->value.longuint); + } + if (tk_idx) { + gf_isom_set_track_index(ctx->file, tkw->track_num, tk_idx, mp4mux_track_reorder, ctx); + mp4mux_reorder_tracks(ctx); + } + + //by default use cttsv1 (negative ctts) + gf_isom_set_composition_offset_mode(ctx->file, tkw->track_num, GF_TRUE); + + p = ctx->make_qt ? NULL : gf_filter_pid_get_property(pid, GF_PROP_PID_PROFILE_LEVEL); + if (p) { + tkw->media_profile_level = p->value.uint; + if (tkw->stream_type == GF_STREAM_AUDIO) { + //patch to align old arch (IOD not written in dash) with new + if (!ctx->dash_mode) { + gf_isom_set_pl_indication(ctx->file, GF_ISOM_PL_AUDIO, p->value.uint); + } + } else if (tkw->stream_type == GF_STREAM_VISUAL) { + //patch to align old arch (IOD not written in dash) with new + if (!ctx->dash_mode) { + gf_isom_set_pl_indication(ctx->file, GF_ISOM_PL_VISUAL, p->value.uint); + } + } + } + + if (ctx->mudta && gf_isom_get_track_count(ctx->file)==1) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ISOM_UDTA); + if (ctx->tktpl && p && p->value.data.ptr) { + gf_isom_load_extra_boxes(ctx->file, p->value.data.ptr, p->value.data.size, (ctx->mudta==2) ? GF_TRUE : GF_FALSE); + } + } + + if (ctx->sgpd_traf) + gf_isom_set_sample_group_in_traf(ctx->file); + + if (ctx->noroll) { + gf_isom_remove_sample_group(ctx->file, tkw->track_num, GF_ISOM_SAMPLE_GROUP_ROLL); + } + + + if (ctx->dash_mode==MP4MX_DASH_VOD) { + Bool use_cache = (ctx->vodcache == MP4MX_VODCACHE_ON) ? GF_TRUE : GF_FALSE; + if ((ctx->vodcache == MP4MX_VODCACHE_REPLACE) && (!ctx->media_dur || !ctx->dash_dur) ) { + use_cache = GF_TRUE; + } + + if (ctx->vodcache==MP4MX_VODCACHE_INSERT) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DISABLE_PROGRESSIVE, &PROP_UINT(GF_PID_FILE_PATCH_INSERT) ); + } + else if (!use_cache) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DISABLE_PROGRESSIVE, &PROP_UINT(GF_PID_FILE_PATCH_REPLACE) ); + } + } + } + + if (!tkw->has_brands) { + Bool is_isom = GF_FALSE; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ISOM_MBRAND); + if (p) { + if (!ctx->major_brand_set) { + gf_isom_set_brand_info(ctx->file, p->value.uint, 1); + ctx->major_brand_set = p->value.uint; + } else { + gf_isom_modify_alternate_brand(ctx->file, p->value.uint, GF_TRUE); + } + if (p->value.uint == GF_ISOM_BRAND_ISOM) is_isom = GF_TRUE; + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ISOM_BRANDS); + if (p && p->value.uint_list.nb_items) { + tkw->has_brands = GF_TRUE; + if (!ctx->major_brand_set) { + ctx->major_brand_set = p->value.uint_list.vals[0]; + gf_isom_set_brand_info(ctx->file, p->value.uint_list.vals[0], 1); + } + + for (i=0; ivalue.uint_list.nb_items; i++) { + gf_isom_modify_alternate_brand(ctx->file, p->value.uint_list.vals[i], GF_TRUE); + if (p->value.uint_list.vals[i] == GF_ISOM_BRAND_ISOM) is_isom = GF_TRUE; + } + } + if (!ctx->m4sys && !is_isom && !ctx->def_brand_patched) { + //remove default brand + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISOM, GF_FALSE); + ctx->def_brand_patched = GF_TRUE; + } + } + + width = height = sr = nb_chan = z_order = txt_fsize = 0; + nb_bps = 16; + ch_layout = 0; + fps.num = 25; + fps.den = 1; + sar.num = sar.den = 0; + codec_id = tkw->codecid; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_MULTI_PID); + if (p) { + multi_pid_stsd = p->value.ptr; + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_DASH_MULTI_PID_IDX); + assert(p); + multi_pid_final_stsd_idx = p->value.uint; + + //should never be the case + ctx->xps_inband = 0; + ctx->dref = GF_FALSE; + orig_pid = pid; + goto multipid_stsd_setup; + } + + + //WARNING !! from this point on until the goto multipid_stsd_setup, use pid and not tkw->ipid + //so that we setup the sample entry properly for each PIDs +sample_entry_setup: + + use_m4sys = ctx->m4sys; + use_gen_sample_entry = GF_TRUE; + use_dref = ctx->dref; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + if (p) width = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + if (p) height = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (p) fps = p->value.frac; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); + if (p) sar = p->value.frac; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ZORDER); + if (p) z_order = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); + if (p) sr = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS); + if (p) nb_chan = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_BPS); + if (p) nb_bps = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CHANNEL_LAYOUT); + if (p) ch_layout = p->value.longuint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_LANGUAGE); + if (p) lang_name = p->value.string; + + if (is_true_pid) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_NB_FRAMES); + if (p) tkw->nb_frames = p->value.uint; + else tkw->nb_frames = 0; + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ISOM_SUBTYPE); + if (p) m_subtype_src = p->value.uint; + + + //get our subtype + switch (codec_id) { + case GF_CODECID_MPEG_AUDIO: + case GF_CODECID_MPEG2_PART3: + m_subtype = GF_ISOM_SUBTYPE_MP3; + comp_name = "MP3"; + break; + case GF_CODECID_AAC_MPEG4: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + m_subtype = GF_ISOM_SUBTYPE_MPEG4; + use_m4sys = GF_TRUE; + comp_name = "AAC"; + use_gen_sample_entry = GF_FALSE; + + if (ctx->importer) { + const char *pid_args = gf_filter_pid_get_args(pid); + if (pid_args) { + Bool sbr_i = strstr(pid_args, "sbr=imp") ? GF_TRUE : GF_FALSE; + Bool sbr_x = strstr(pid_args, "sbr=exp") ? GF_TRUE : GF_FALSE; + Bool ps_i = strstr(pid_args, "ps=imp") ? GF_TRUE : GF_FALSE; + Bool ps_x = strstr(pid_args, "ps=exp") ? GF_TRUE : GF_FALSE; + + if (sbr_x) { + if (ps_i) imp_name = "AAC explicit SBR implict PS"; + else if (ps_x) imp_name = "AAC explicit SBR+PS"; + else imp_name = "AAC explicit SBR"; + } else if (sbr_i) { + if (ps_i) imp_name = "AAC implicit SBR+PS"; + else if (ps_x) imp_name = "AAC implicit SBR explicit PS"; + else imp_name = "AAC implicit SBR"; + } else { + if (ps_i) imp_name = "AAC implicit PS"; + else if (ps_x) imp_name = "AAC explicit PS"; + else imp_name = "AAC "; + } + } + } + break; + case GF_CODECID_JPEG: + m_subtype = GF_ISOM_BOX_TYPE_JPEG; + comp_name = "JPEG"; + break; + case GF_CODECID_PNG: + m_subtype = GF_ISOM_BOX_TYPE_PNG; + comp_name = "PNG"; + break; + case GF_CODECID_J2K: + m_subtype = GF_ISOM_BOX_TYPE_MJP2; + comp_name = "JPEG2000"; + use_mj2 = GF_TRUE; + break; + + case GF_CODECID_AMR: + m_subtype = GF_ISOM_SUBTYPE_3GP_AMR; + comp_name = "AMR"; + use_3gpp_config = GF_TRUE; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AMR_MODE_SET); + if (p && (p->value.uint!=tkw->amr_mode_set)) { + tkw->amr_mode_set = p->value.uint; + needs_sample_entry = 2; + } + break; + case GF_CODECID_AMR_WB: + m_subtype = GF_ISOM_SUBTYPE_3GP_AMR_WB; + comp_name = "AMR-WB"; + use_3gpp_config = GF_TRUE; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AMR_MODE_SET); + if (p && (p->value.uint!=tkw->amr_mode_set)) { + tkw->amr_mode_set = p->value.uint; + needs_sample_entry = 2; + } + break; + case GF_CODECID_EVRC: + m_subtype = GF_ISOM_SUBTYPE_3GP_EVRC; + comp_name = "EVRC"; + use_3gpp_config = GF_TRUE; + break; + case GF_CODECID_SMV: + m_subtype = GF_ISOM_SUBTYPE_3GP_SMV; + comp_name = "SMV"; + use_3gpp_config = GF_TRUE; + break; + case GF_CODECID_QCELP: + m_subtype = GF_ISOM_SUBTYPE_3GP_QCELP; + comp_name = "QCELP"; + use_3gpp_config = GF_TRUE; + break; + case GF_CODECID_S263: + case GF_CODECID_H263: + m_subtype = GF_ISOM_SUBTYPE_3GP_H263; + comp_name = "H263"; + use_3gpp_config = GF_TRUE; + break; + case GF_CODECID_AC3: + m_subtype = GF_ISOM_SUBTYPE_AC3; + comp_name = "AC-3"; + use_ac3_entry = GF_TRUE; + break; + case GF_CODECID_EAC3: + m_subtype = GF_ISOM_SUBTYPE_AC3; + comp_name = "EAC-3"; + use_ac3_entry = GF_TRUE; + break; + case GF_CODECID_MPHA: + if ((m_subtype_src!=GF_ISOM_SUBTYPE_MH3D_MHA1) && (m_subtype_src!=GF_ISOM_SUBTYPE_MH3D_MHA2)) + m_subtype = GF_ISOM_SUBTYPE_MH3D_MHA1; + else + m_subtype = m_subtype_src; + comp_name = "MPEG-H Audio"; + break; + case GF_CODECID_MHAS: + if ((m_subtype_src!=GF_ISOM_SUBTYPE_MH3D_MHM1) && (m_subtype_src!=GF_ISOM_SUBTYPE_MH3D_MHM2)) + m_subtype = GF_ISOM_SUBTYPE_MH3D_MHM1; + else + m_subtype = m_subtype_src; + comp_name = "MPEG-H AudioMux"; + break; + case GF_CODECID_FLAC: + m_subtype = GF_ISOM_SUBTYPE_FLAC; + comp_name = "FLAC"; + use_flac_entry = GF_TRUE; + break; + case GF_CODECID_OPUS: + m_subtype = GF_ISOM_SUBTYPE_OPUS; + comp_name = "Opus"; + use_opus = GF_TRUE; + break; + case GF_CODECID_MPEG4_PART2: + m_subtype = GF_ISOM_SUBTYPE_MPEG4; + use_m4sys = GF_TRUE; + comp_name = "MPEG-4 Visual Part 2"; + use_gen_sample_entry = GF_FALSE; + break; + case GF_CODECID_AVC: + case GF_CODECID_SVC: + m_subtype = ((ctx->xps_inband==1) || (ctx->xps_inband==2)) ? GF_ISOM_SUBTYPE_AVC3_H264 : GF_ISOM_SUBTYPE_AVC_H264; + use_avc = GF_TRUE; + comp_name = (codec_id == GF_CODECID_SVC) ? "MPEG-4 SVC" : "MPEG-4 AVC"; + use_gen_sample_entry = GF_FALSE; + if (ctx->xps_inband) { + use_m4sys = GF_FALSE; + if (ctx->xps_inband==1) skip_dsi = GF_TRUE; + } + break; + case GF_CODECID_HEVC: + case GF_CODECID_LHVC: + m_subtype = ((ctx->xps_inband==1) || (ctx->xps_inband==2)) ? GF_ISOM_SUBTYPE_HEV1 : GF_ISOM_SUBTYPE_HVC1; + use_hevc = GF_TRUE; + comp_name = (codec_id == GF_CODECID_LHVC) ? "L-HEVC" : "HEVC"; + use_gen_sample_entry = GF_FALSE; + if (ctx->xps_inband) { + use_m4sys = GF_FALSE; + if (ctx->xps_inband==1) skip_dsi = GF_TRUE; + } + if (codec_id==GF_CODECID_HEVC_TILES) { + m_subtype = GF_ISOM_SUBTYPE_HVT1; + skip_dsi = GF_TRUE; + } + break; + case GF_CODECID_HEVC_TILES: + m_subtype = GF_ISOM_SUBTYPE_HVT1; + skip_dsi = GF_TRUE; + use_hvt1 = GF_TRUE; + use_m4sys = GF_FALSE; + comp_name = "HEVC Tiles"; + use_gen_sample_entry = GF_FALSE; + break; + case GF_CODECID_MPEG1: + case GF_CODECID_MPEG2_422: + case GF_CODECID_MPEG2_SNR: + case GF_CODECID_MPEG2_HIGH: + case GF_CODECID_MPEG2_MAIN: + case GF_CODECID_MPEG2_SIMPLE: + case GF_CODECID_MPEG2_SPATIAL: + m_subtype = GF_ISOM_SUBTYPE_MPEG4; + use_m4sys = GF_TRUE; + comp_name = "MPEG-2 Video"; + use_gen_sample_entry = GF_FALSE; + break; + case 0: + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] muxing codecID %d not yet implemented - patch welcome\n", codec_id)); + return GF_NOT_SUPPORTED; + + case GF_ISOM_SUBTYPE_TX3G: + m_subtype = GF_ISOM_SUBTYPE_TX3G; + use_tx3g = GF_TRUE; + comp_name = "Timed Text"; + is_text_subs = GF_TRUE; + break; + case GF_ISOM_SUBTYPE_WVTT: + m_subtype = GF_ISOM_SUBTYPE_WVTT; + use_webvtt = GF_TRUE; + comp_name = "WebVTT"; + is_text_subs = GF_TRUE; + break; + case GF_CODECID_SUBPIC: + use_m4sys = GF_TRUE; + override_stype = GF_STREAM_ND_SUBPIC; + comp_name = "VobSub"; + break; + case GF_CODECID_TEXT_MPEG4: + use_m4sys = GF_TRUE; + gf_isom_set_media_type(ctx->file, tkw->track_num, GF_ISOM_MEDIA_SCENE); + comp_name = "MPEG4 Streaming Text"; + break; + case GF_CODECID_AV1: + use_gen_sample_entry = GF_FALSE; + m_subtype = GF_ISOM_SUBTYPE_AV01; + use_av1 = GF_TRUE; + comp_name = "AOM AV1 Video"; + break; + + case GF_CODECID_VP8: + use_gen_sample_entry = GF_FALSE; + m_subtype = GF_ISOM_SUBTYPE_VP08; + use_vpX = GF_TRUE; + comp_name = "VP8 Video"; + break; + case GF_CODECID_VP9: + use_gen_sample_entry = GF_FALSE; + m_subtype = GF_ISOM_SUBTYPE_VP09; + use_vpX = GF_TRUE; + comp_name = "VP9 Video"; + break; + case GF_CODECID_VP10: + use_gen_sample_entry = GF_FALSE; + m_subtype = GF_ISOM_SUBTYPE_VP10; + use_vpX = GF_TRUE; + comp_name = "VP10 Video"; + break; + + case GF_CODECID_VORBIS: + case GF_CODECID_THEORA: + use_m4sys = GF_TRUE; + break; + + case GF_CODECID_BIFS: +/* == GF_CODECID_OD_V1:*/ + case GF_CODECID_BIFS_V2: +/* == GF_CODECID_OD_V2:*/ + case GF_CODECID_BIFS_EXTENDED: + case GF_CODECID_LASER: + use_m4sys = GF_TRUE; + break; + + case GF_CODECID_RAW: + m_subtype = codec_id; + unknown_generic = GF_TRUE; + use_gen_sample_entry = GF_TRUE; + use_m4sys = GF_FALSE; + tkw->skip_bitrate_update = GF_TRUE; + if (tkw->stream_type == GF_STREAM_AUDIO) { + u32 req_non_planar_type = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT); + if (!p) break; + comp_name = "RawAudio"; + unknown_generic = GF_FALSE; + //todo: we currently only set QTFF-style raw media, add ISOBMFF raw audio support + switch (p->value.uint) { + case GF_AUDIO_FMT_U8P: + req_non_planar_type = GF_AUDIO_FMT_U8; + case GF_AUDIO_FMT_U8: + m_subtype = GF_QT_SUBTYPE_RAW; + break; + case GF_AUDIO_FMT_S16P: + req_non_planar_type = GF_AUDIO_FMT_S16; + case GF_AUDIO_FMT_S16: + m_subtype = GF_QT_SUBTYPE_SOWT; + m_subtype_alt_raw = GF_ISOM_SUBTYPE_IPCM; + break; + case GF_AUDIO_FMT_S24P: + req_non_planar_type = GF_AUDIO_FMT_S24; + case GF_AUDIO_FMT_S24: + m_subtype = GF_QT_SUBTYPE_IN24; + m_subtype_alt_raw = GF_ISOM_SUBTYPE_IPCM; + break; + case GF_AUDIO_FMT_S32P: + req_non_planar_type = GF_AUDIO_FMT_S32P; + case GF_AUDIO_FMT_S32: + m_subtype = GF_QT_SUBTYPE_IN32; + m_subtype_alt_raw = GF_ISOM_SUBTYPE_IPCM; + break; + case GF_AUDIO_FMT_FLTP: + req_non_planar_type = GF_AUDIO_FMT_FLTP; + case GF_AUDIO_FMT_FLT: + m_subtype = GF_QT_SUBTYPE_FL32; + m_subtype_alt_raw = GF_ISOM_SUBTYPE_FPCM; + break; + case GF_AUDIO_FMT_DBLP: + req_non_planar_type = GF_AUDIO_FMT_DBL; + case GF_AUDIO_FMT_DBL: + m_subtype = GF_QT_SUBTYPE_FL64; + m_subtype_alt_raw = GF_ISOM_SUBTYPE_FPCM; + break; + default: + unknown_generic = GF_TRUE; + m_subtype = p->value.uint; + break; + } + if (req_non_planar_type) { + if (is_true_pid) + gf_filter_pid_negociate_property(pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(GF_AUDIO_FMT_S16)); + else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] raw audio format planar in DASH multi-stsd mode is not supported, try assigning a resampler before the dasher\n")); + return GF_NOT_SUPPORTED; + } + } + raw_bitdepth = gf_audio_fmt_bit_depth(p->value.uint); + tkw->raw_audio_bytes_per_sample = raw_bitdepth; + tkw->raw_audio_bytes_per_sample *= nb_chan; + tkw->raw_audio_bytes_per_sample /= 8; + } + else if (tkw->stream_type == GF_STREAM_VISUAL) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); + if (!p) break; + comp_name = "RawVideo"; + unknown_generic = GF_FALSE; + tkw->skip_bitrate_update = GF_TRUE; + //we currently only set QTFF-style raw media + switch (p->value.uint) { + case GF_PIXEL_RGB: + m_subtype = GF_QT_SUBTYPE_RAW; + break; + case GF_PIXEL_YUV422: + m_subtype = GF_QT_SUBTYPE_YUV422; + force_colr = GF_TRUE; + break; + case GF_PIXEL_YUV422_10: + m_subtype = GF_QT_SUBTYPE_YUV422_10; + force_colr = GF_TRUE; + break; + case GF_PIXEL_YUV444: + m_subtype = GF_QT_SUBTYPE_YUV444; + force_colr = GF_TRUE; + break; + case GF_PIXEL_YUV444_10: + m_subtype = GF_QT_SUBTYPE_YUV444_10; + force_colr = GF_TRUE; + break; + default: + unknown_generic = GF_TRUE; + m_subtype = p->value.uint; + break; + } + } + break; + + default: + m_subtype = codec_id; + unknown_generic = GF_TRUE; + use_gen_sample_entry = GF_TRUE; + use_m4sys = GF_FALSE; + if (is_prores) + unknown_generic = GF_FALSE; + + p = gf_filter_pid_get_property_str(pid, "meta:mime"); + if (p) meta_mime = p->value.string; + p = gf_filter_pid_get_property_str(pid, "meta:encoding"); + if (p) meta_encoding = p->value.string; + p = gf_filter_pid_get_property_str(pid, "meta:content_encoding"); + if (p) meta_content_encoding = p->value.string; + p = gf_filter_pid_get_property_str(pid, "meta:xmlns"); + if (p) meta_xmlns = p->value.string; + p = gf_filter_pid_get_property_str(pid, "meta:schemaloc"); + if (p) meta_schemaloc = p->value.string; + p = gf_filter_pid_get_property_str(pid, "meta:aux_mimes"); + if (p) meta_auxmimes = p->value.string; + break; + } + if (!comp_name) comp_name = gf_codecid_name(codec_id); + if (!comp_name) comp_name = gf_4cc_to_str(m_subtype); + + if (dsi) + meta_config = dsi->value.data.ptr; + + if (is_text_subs && !width && !height) { + mp4_mux_get_video_size(ctx, &width, &height); + } + + if (!ctx->init_movie_done && !tkw->nb_samples && (ctx->mediats<0) && (tkw->tk_timescale==1000)) { + if (sr) { + tkw->tk_timescale = sr; + gf_isom_set_media_timescale(ctx->file, tkw->track_num, sr, 0, GF_TRUE); + } + else if (width && fps.den) { + tkw->tk_timescale = fps.den; + gf_isom_set_media_timescale(ctx->file, tkw->track_num, fps.den, 0, GF_TRUE); + } + } + if (!needs_sample_entry || tkw->is_item) { + goto sample_entry_done; + } + + //we are fragmented, init movie done, we cannot update the sample description + if (ctx->init_movie_done) { + if (needs_sample_entry==1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Cannot create a new sample description entry (codec change) for finalized movie in fragmented mode\n")); + return GF_NOT_SUPPORTED; + } + force_mix_xps = GF_TRUE; + } else if (ctx->store < MP4MX_MODE_FRAG) { + if ((needs_sample_entry==2) && (ctx->xps_inband==2)) { + force_mix_xps = GF_TRUE; + } + else if ((needs_sample_entry==2) && ((ctx->xps_inband==1)||(ctx->xps_inband==3)) ) { + needs_sample_entry = 0; + make_inband_headers = GF_TRUE; + } + } + + if (force_mix_xps) { + + //for AVC and HEVC, move to inband params if config changed + if (use_avc && dsi) { + if (tkw->avcc) gf_odf_avc_cfg_del(tkw->avcc); + + tkw->avcc = gf_odf_avc_cfg_read(dsi->value.data.ptr, dsi->value.data.size); + + if (enh_dsi) { + if (tkw->svcc) gf_odf_avc_cfg_del(tkw->svcc); + tkw->svcc = gf_odf_avc_cfg_read(enh_dsi->value.data.ptr, enh_dsi->value.data.size); + } + if (!ctx->xps_inband) { + if (ctx->init_movie_done) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] AVC config update after movie has been finalized, moving all SPS/PPS inband (file might not be compliant)\n")); + } + ctx->xps_inband = 2; + } + mp4_mux_make_inband_header(ctx, tkw); + return GF_OK; + } + else if (use_hevc && dsi) { + if (tkw->hvcc) gf_odf_hevc_cfg_del(tkw->hvcc); + tkw->hvcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, (codec_id == GF_CODECID_LHVC) ? GF_TRUE : GF_FALSE); + + if (enh_dsi) { + if (tkw->lvcc) gf_odf_hevc_cfg_del(tkw->lvcc); + tkw->lvcc = gf_odf_hevc_cfg_read(enh_dsi->value.data.ptr, enh_dsi->value.data.size, GF_TRUE); + } + if (!ctx->xps_inband) { + if (ctx->init_movie_done) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] HEVC config update after movie has been finalized, moving all SPS/PPS inband (file might not be compliant)\n")); + } + ctx->xps_inband = 2; + } + mp4_mux_make_inband_header(ctx, tkw); + return GF_OK; + } + + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Cannot create a new sample description entry (config changed) for finalized movie in fragmented mode\n")); + return GF_NOT_SUPPORTED; + } + + + //little optim here: if no samples were added on the stream descritpion remove it + if (!tkw->samples_in_stsd && tkw->stsd_idx) { + gf_isom_remove_stream_description(ctx->file, tkw->track_num, tkw->stsd_idx); + } + + if (!use_dref) src_url = NULL; + + if (use_m4sys && !gf_codecid_oti(codec_id)) { + use_m4sys = GF_FALSE; + } + //nope, create sample entry + if (use_m4sys) { + GF_ESD *esd = gf_odf_desc_esd_new(2); + esd->decoderConfig->streamType = override_stype ? override_stype : tkw->stream_type; + esd->decoderConfig->objectTypeIndication = gf_codecid_oti(codec_id); + if (!esd->decoderConfig->objectTypeIndication) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Codec %s does not have an official MPEG-4 systems mapping, cannot mux\n", gf_codecid_name(codec_id) )); + return GF_NOT_SUPPORTED; + + } + esd->slConfig->timestampResolution = tkw->tk_timescale; + if (dsi && !skip_dsi) { + esd->decoderConfig->decoderSpecificInfo->data = dsi->value.data.ptr; + esd->decoderConfig->decoderSpecificInfo->dataLength = dsi->value.data.size; + } + + e = gf_isom_new_mpeg4_description(ctx->file, tkw->track_num, esd, (char *)src_url, NULL, &tkw->stsd_idx); + if (dsi && !skip_dsi) { + esd->decoderConfig->decoderSpecificInfo->data = NULL; + esd->decoderConfig->decoderSpecificInfo->dataLength = 0; + } + gf_odf_desc_del((GF_Descriptor *) esd); + + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new MPEG-4 Systems sample description for stream type %d OTI %d: %s\n", tkw->stream_type, codec_id, gf_error_to_string(e) )); + return e; + } + + tkw->use_dref = src_url ? GF_TRUE : GF_FALSE; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_IN_IOD); + if (p && p->value.boolean) + gf_isom_add_track_to_root_od(ctx->file, tkw->track_num); + + +#ifndef GPAC_DISABLE_AV_PARSERS + if (dsi && (tkw->stream_type==GF_STREAM_AUDIO)) { + GF_M4ADecSpecInfo acfg; + gf_m4a_get_config(dsi->value.data.ptr, dsi->value.data.size, &acfg); + audio_pli = acfg.audioPL; + } + //patch to align old arch (IOD not written in dash) with new + if (audio_pli && !ctx->dash_mode) + gf_isom_set_pl_indication(ctx->file, GF_ISOM_PL_AUDIO, audio_pli); +#endif + + } else if (use_avc) { + if (tkw->avcc) gf_odf_avc_cfg_del(tkw->avcc); + + //not yet known + if (!dsi) return GF_OK; + + tkw->avcc = gf_odf_avc_cfg_read(dsi->value.data.ptr, dsi->value.data.size); + + if (needs_sample_entry) { + + if (tkw->codecid == GF_CODECID_SVC) { + e = gf_isom_svc_config_new(ctx->file, tkw->track_num, tkw->avcc, NULL, NULL, &tkw->stsd_idx); + } else if (tkw->codecid == GF_CODECID_MVC) { + e = gf_isom_mvc_config_new(ctx->file, tkw->track_num, tkw->avcc, NULL, NULL, &tkw->stsd_idx); + } else { + e = gf_isom_avc_config_new(ctx->file, tkw->track_num, tkw->avcc, NULL, NULL, &tkw->stsd_idx); + } + + if (!e && enh_dsi) { + if (tkw->svcc) gf_odf_avc_cfg_del(tkw->svcc); + tkw->svcc = gf_odf_avc_cfg_read(enh_dsi->value.data.ptr, enh_dsi->value.data.size); + if (tkw->svcc) { + if ((tkw->svcc->AVCProfileIndication==118) || (tkw->svcc->AVCProfileIndication==128)) { + e = gf_isom_mvc_config_update(ctx->file, tkw->track_num, tkw->stsd_idx, tkw->svcc, GF_TRUE); + } else { + e = gf_isom_svc_config_update(ctx->file, tkw->track_num, tkw->stsd_idx, tkw->svcc, GF_TRUE); + } + if (e) { + gf_odf_avc_cfg_del(tkw->svcc); + tkw->svcc = NULL; + } + + if (ctx->xps_inband) { + gf_isom_avc_set_inband_config(ctx->file, tkw->track_num, tkw->stsd_idx, (ctx->xps_inband==2) ? GF_TRUE : GF_FALSE); + } + } + } + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new AVC sample description: %s\n", gf_error_to_string(e) )); + return e; + } + } + + if (ctx->xps_inband) { + //this will cleanup all PS in avcC / svcC + gf_isom_avc_set_inband_config(ctx->file, tkw->track_num, tkw->stsd_idx, (ctx->xps_inband==2) ? GF_TRUE : GF_FALSE); + if (ctx->xps_inband==2) make_inband_headers = GF_TRUE; + } else { + gf_odf_avc_cfg_del(tkw->avcc); + tkw->avcc = NULL; + } + //patch to align old arch with filters + if (!ctx->dash_mode && !ctx->make_qt && !gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_ISOM_TREX_TEMPLATE) ) + gf_isom_set_pl_indication(ctx->file, GF_ISOM_PL_VISUAL, 0x7F); + + if (!tkw->has_brands) + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_AVC1, GF_TRUE); + + tkw->is_nalu = NALU_AVC; + + tkw->use_dref = GF_FALSE; + + } else if (use_hvt1) { + if (tkw->hvcc) gf_odf_hevc_cfg_del(tkw->hvcc); + tkw->hvcc = gf_odf_hevc_cfg_new(); + e = gf_isom_hevc_config_new(ctx->file, tkw->track_num, tkw->hvcc, NULL, NULL, &tkw->stsd_idx); + if (!e) { + gf_isom_hevc_set_tile_config(ctx->file, tkw->track_num, tkw->stsd_idx, NULL, GF_FALSE); + } + gf_odf_hevc_cfg_del(tkw->hvcc); + tkw->hvcc = NULL; + tkw->is_nalu = NALU_HEVC; + tkw->use_dref = GF_FALSE; + if (!tkw->has_brands) + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_HVTI, GF_TRUE); + } else if (use_hevc) { + if (tkw->hvcc) gf_odf_hevc_cfg_del(tkw->hvcc); + + if (!dsi && !enh_dsi) { + //not yet known + return GF_OK; + } + if (dsi) { + tkw->hvcc = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, (codec_id == GF_CODECID_LHVC) ? GF_TRUE : GF_FALSE); + } else { + tkw->hvcc = gf_odf_hevc_cfg_new(); + } + tkw->is_nalu = NALU_HEVC; + + if (needs_sample_entry) { + e = gf_isom_hevc_config_new(ctx->file, tkw->track_num, tkw->hvcc, NULL, NULL, &tkw->stsd_idx); + + if (!tkw->has_brands) { + gf_isom_set_brand_info(ctx->file, GF_ISOM_BRAND_ISO4, 1); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISOM, GF_FALSE); + } + //patch for old arch + else if (ctx->dash_mode) { + Bool force_brand=GF_FALSE; + if (((ctx->major_brand_set>>24)=='i') && (((ctx->major_brand_set>>16)&0xFF)=='s') && (((ctx->major_brand_set>>8)&0xFF)=='o')) { + if ( (ctx->major_brand_set&0xFF) <'6') force_brand=GF_TRUE; + } + + if (!force_brand && ctx->major_brand_set) { + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISO6, GF_TRUE); + } else { + gf_isom_set_brand_info(ctx->file, GF_ISOM_BRAND_ISO6, 1); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISOM, GF_FALSE); + } + } + + if (!e && enh_dsi) { + if (tkw->lvcc) gf_odf_hevc_cfg_del(tkw->lvcc); + tkw->lvcc = gf_odf_hevc_cfg_read(enh_dsi->value.data.ptr, enh_dsi->value.data.size, GF_TRUE); + if (tkw->lvcc) { + e = gf_isom_lhvc_config_update(ctx->file, tkw->track_num, tkw->stsd_idx, tkw->lvcc, dsi ? GF_ISOM_LEHVC_WITH_BASE_BACKWARD : GF_ISOM_LEHVC_ONLY); + if (e) { + gf_odf_hevc_cfg_del(tkw->lvcc); + tkw->lvcc = NULL; + } + + if (!dsi && ctx->xps_inband) { + gf_isom_hevc_set_inband_config(ctx->file, tkw->track_num, tkw->stsd_idx, (ctx->xps_inband==2) ? GF_TRUE : GF_FALSE); + } + } + } else if (codec_id == GF_CODECID_LHVC) { + gf_isom_lhvc_config_update(ctx->file, tkw->track_num, tkw->stsd_idx, tkw->hvcc, GF_ISOM_LEHVC_ONLY); + } else if (is_tile_base) { + gf_isom_lhvc_config_update(ctx->file, tkw->track_num, tkw->stsd_idx, tkw->hvcc, GF_ISOM_HEVC_TILE_BASE); + } + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new HEVC sample description: %s\n", gf_error_to_string(e) )); + return e; + } + } + + if (dsi && ctx->xps_inband) { + //this will cleanup all PS in avcC / svcC + gf_isom_hevc_set_inband_config(ctx->file, tkw->track_num, tkw->stsd_idx, (ctx->xps_inband==2) ? GF_TRUE : GF_FALSE); + } else { + gf_odf_hevc_cfg_del(tkw->hvcc); + tkw->hvcc = NULL; + } + + tkw->use_dref = GF_FALSE; + + } else if (use_av1) { + GF_AV1Config *av1c; + + if (!dsi) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] No decoder specific info found for AV1\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + av1c = gf_odf_av1_cfg_read(dsi->value.data.ptr, dsi->value.data.size); + if (!av1c) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to parser AV1 decoder specific info\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + + e = gf_isom_av1_config_new(ctx->file, tkw->track_num, av1c, (char *) src_url, NULL, &tkw->stsd_idx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new AV1 sample description: %s\n", gf_error_to_string(e) )); + return e; + } + tkw->is_av1 = GF_TRUE; + + if (!tkw->has_brands) { + gf_isom_set_brand_info(ctx->file, GF_ISOM_BRAND_ISO4, 1); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISOM, GF_FALSE); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_AV01, GF_TRUE); + } + + gf_odf_av1_cfg_del(av1c); + } else if (use_vpX) { + GF_VPConfig *vpc; + + if (!dsi) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] No decoder specific info found for %s\n", gf_4cc_to_str(codec_id) )); + return GF_NON_COMPLIANT_BITSTREAM; + } + vpc = gf_odf_vp_cfg_read(dsi->value.data.ptr, dsi->value.data.size); + if (!vpc) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to parser %s decoder specific info\n", gf_4cc_to_str(codec_id))); + return GF_NON_COMPLIANT_BITSTREAM; + } + + e = gf_isom_vp_config_new(ctx->file, tkw->track_num, vpc, (char *) src_url, NULL, &tkw->stsd_idx, m_subtype); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new %s sample description: %s\n", gf_4cc_to_str(codec_id), gf_error_to_string(e) )); + return e; + } + tkw->is_vpx = GF_TRUE; + gf_odf_vp_cfg_del(vpc); + } else if (use_3gpp_config) { + GF_3GPConfig gpp_cfg; + memset(&gpp_cfg, 0, sizeof(GF_3GPConfig)); + gpp_cfg.type = m_subtype; + gpp_cfg.vendor = GF_VENDOR_GPAC; + + if (use_dref) { + gpp_cfg.frames_per_sample = 1; + } else { + gpp_cfg.frames_per_sample = ctx->pack3gp; + if (!gpp_cfg.frames_per_sample) gpp_cfg.frames_per_sample = 1; + else if (gpp_cfg.frames_per_sample >15) gpp_cfg.frames_per_sample = 15; + } + gpp_cfg.AMR_mode_set = tkw->amr_mode_set; + if (tkw->stream_type==GF_STREAM_VISUAL) { + /*FIXME - we need more in-depth parsing of the bitstream to detect P3@L10 (streaming wireless)*/ + gpp_cfg.H263_profile = 0; + gpp_cfg.H263_level = 10; + gpp_cfg.frames_per_sample = 0; + } + tkw->nb_frames_per_sample = gpp_cfg.frames_per_sample; + + e = gf_isom_3gp_config_new(ctx->file, tkw->track_num, &gpp_cfg, (char *) src_url, NULL, &tkw->stsd_idx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new 3GPP audio sample description for stream type %d codecid %d: %s\n", tkw->stream_type, codec_id, gf_error_to_string(e) )); + return e; + } + tkw->use_dref = src_url ? GF_TRUE : GF_FALSE; + + if (!tkw->has_brands) { + switch (gpp_cfg.type) { + case GF_ISOM_SUBTYPE_3GP_QCELP: + case GF_ISOM_SUBTYPE_3GP_EVRC: + case GF_ISOM_SUBTYPE_3GP_SMV: + gf_isom_set_brand_info(ctx->file, GF_ISOM_BRAND_3G2A, 65536); + break; + case GF_ISOM_SUBTYPE_3GP_H263: + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_3GG6, GF_TRUE); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_3GG5, GF_TRUE); + break; + } + } + tkw->skip_bitrate_update = GF_TRUE; + } else if (use_ac3_entry) { + GF_AC3Config ac3cfg; + memset(&ac3cfg, 0, sizeof(GF_AC3Config)); + + if (dsi) { + gf_isom_ac3_config_parse(dsi->value.data.ptr, dsi->value.data.size, (codec_id==GF_CODECID_EAC3) ? GF_TRUE : GF_FALSE, &ac3cfg); + } + e = gf_isom_ac3_config_new(ctx->file, tkw->track_num, &ac3cfg, (char *)src_url, NULL, &tkw->stsd_idx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new AC3 audio sample description for stream type %d codecid %d: %s\n", tkw->stream_type, codec_id, gf_error_to_string(e) )); + return e; + } + tkw->use_dref = src_url ? GF_TRUE : GF_FALSE; + } else if (use_flac_entry) { + e = gf_isom_flac_config_new(ctx->file, tkw->track_num, dsi ? dsi->value.data.ptr : NULL, dsi ? dsi->value.data.size : 0, (char *)src_url, NULL, &tkw->stsd_idx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new FLAC audio sample description for stream type %d codecid %d: %s\n", tkw->stream_type, codec_id, gf_error_to_string(e) )); + return e; + } + tkw->use_dref = src_url ? GF_TRUE : GF_FALSE; + } else if (use_opus) { + GF_OpusSpecificBox *opus_cfg = NULL; + GF_BitStream *bs; + + if (!dsi) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] No decoder specific info found for opus\n" )); + return GF_NON_COMPLIANT_BITSTREAM; + } + + bs = gf_bs_new(dsi->value.data.ptr, dsi->value.data.size, GF_BITSTREAM_READ); + e = gf_isom_box_parse((GF_Box**)&opus_cfg, bs); + gf_bs_del(bs); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error parsing opus configuration data: %s\n", gf_error_to_string(e) )); + return e; + } + + e = gf_isom_opus_config_new(ctx->file, tkw->track_num, opus_cfg, (char *)src_url, NULL, &tkw->stsd_idx); + if (opus_cfg) gf_isom_box_del((GF_Box*)opus_cfg); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new AC3 audio sample description for stream type %d codecid %d: %s\n", tkw->stream_type, codec_id, gf_error_to_string(e) )); + return e; + } + tkw->use_dref = src_url ? GF_TRUE : GF_FALSE; + } else if (m_subtype == GF_ISOM_SUBTYPE_METX) { + comp_name = "XML Metadata"; + e = gf_isom_new_xml_metadata_description(ctx->file, tkw->track_num, meta_xmlns, meta_schemaloc, meta_encoding, &tkw->stsd_idx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new METX sample description: %s\n", gf_error_to_string(e) )); + return e; + } + } else if (m_subtype == GF_ISOM_SUBTYPE_METT) { + comp_name = "Text Metadata"; + e = gf_isom_new_stxt_description(ctx->file, tkw->track_num, GF_ISOM_SUBTYPE_METT, meta_mime, meta_encoding, meta_config, &tkw->stsd_idx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new METT sample description: %s\n", gf_error_to_string(e) )); + return e; + } + } else if (m_subtype == GF_ISOM_SUBTYPE_STPP) { + if (meta_xmlns && !strcmp(meta_xmlns, "http://www.w3.org/ns/ttml")) comp_name = "TTML"; + else comp_name = "XML Subtitle"; + e = gf_isom_new_xml_subtitle_description(ctx->file, tkw->track_num, meta_xmlns, meta_schemaloc, meta_auxmimes, &tkw->stsd_idx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new XML subtitle sample description: %s\n", gf_error_to_string(e) )); + return e; + } + } else if ((m_subtype == GF_ISOM_SUBTYPE_SBTT) || (m_subtype == GF_ISOM_SUBTYPE_STXT) ) { + comp_name = (m_subtype == GF_ISOM_SUBTYPE_STXT) ? "Simple Timed Text" : "Textual Subtitle"; + e = gf_isom_new_stxt_description(ctx->file, tkw->track_num, m_subtype, meta_mime, meta_content_encoding, meta_config, &tkw->stsd_idx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new %s sample description: %s\n", gf_4cc_to_str(m_subtype), gf_error_to_string(e) )); + return e; + } + if (m_subtype == GF_ISOM_SUBTYPE_STXT) force_tk_layout = GF_TRUE; + } else if (use_tx3g) { + GF_TextSampleDescriptor *txtc; + if (!dsi) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] No decoder specific info found for TX3G\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + txtc = gf_odf_tx3g_read(dsi->value.data.ptr, dsi->value.data.size); + if (!txtc) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to parse TX3G config\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + + if (!txtc->default_pos.right) txtc->default_pos.right = width + txtc->default_pos.left; + if (!txtc->default_pos.bottom) txtc->default_pos.bottom = height + txtc->default_pos.top; + + + e = gf_isom_new_text_description(ctx->file, tkw->track_num, txtc, NULL, NULL, &tkw->stsd_idx); + if (e) { + gf_odf_desc_del((GF_Descriptor *)txtc); + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new %s sample description: %s\n", gf_4cc_to_str(m_subtype), gf_error_to_string(e) )); + return e; + } + if (ctx->importer) { + txt_fsize = txtc->default_style.font_size; + if (txtc->font_count && txtc->fonts[0].fontName) txt_font = gf_strdup(txtc->fonts[0].fontName); + } + gf_odf_desc_del((GF_Descriptor *)txtc); + + tkw->skip_bitrate_update = GF_TRUE; + } else if (use_webvtt) { + e = gf_isom_new_webvtt_description(ctx->file, tkw->track_num, NULL, NULL, &tkw->stsd_idx, dsi ? dsi->value.data.ptr : NULL); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new %s sample description: %s\n", gf_4cc_to_str(m_subtype), gf_error_to_string(e) )); + return e; + } + tkw->skip_bitrate_update = GF_TRUE; + } else if (use_mj2) { + e = gf_isom_new_mj2k_description(ctx->file, tkw->track_num, NULL, NULL, &tkw->stsd_idx, dsi ? dsi->value.data.ptr : NULL, dsi ? dsi->value.data.size : 0); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new %s sample description: %s\n", gf_4cc_to_str(m_subtype), gf_error_to_string(e) )); + return e; + } + } else if (codec_id==GF_CODECID_TMCD) { + u32 tmcd_flags=0, tmcd_fps_num=0, tmcd_fps_den=0; + s32 tmcd_fpt=0; + + p = gf_filter_pid_get_property_str(pid, "tmcd:flags"); + if (p) tmcd_flags = p->value.uint; + p = gf_filter_pid_get_property_str(pid, "tmcd:framerate"); + if (p) { + tmcd_fps_num = p->value.frac.num; + tmcd_fps_den = p->value.frac.den; + } + p = gf_filter_pid_get_property_str(pid, "tmcd:frames_per_tick"); + if (p) tmcd_fpt = p->value.uint; + if (tkw->tk_timescale != tmcd_fps_num) { + tmcd_fps_den *= tmcd_fps_num; + tmcd_fps_den /= tkw->tk_timescale; + } + + e = gf_isom_tmcd_config_new(ctx->file, tkw->track_num, tmcd_fps_num, tmcd_fps_den, tmcd_fpt, (tmcd_flags & 0x1), (tmcd_flags & 0x8), &tkw->stsd_idx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new tmcd sample description: %s\n", gf_error_to_string(e) )); + return e; + } + } else if (codec_id==GF_CODECID_DIMS) { + GF_DIMSDescription dims_c; + memset(&dims_c, 0, sizeof(GF_DIMSDescription)); + dims_c.contentEncoding = meta_content_encoding; + dims_c.mime_type = meta_mime; + dims_c.textEncoding = meta_encoding; + dims_c.xml_schema_loc = meta_xmlns; + + p = gf_filter_pid_get_property_str(tkw->ipid, "dims:profile"); + if (p) dims_c.profile = p->value.uint; + p = gf_filter_pid_get_property_str(tkw->ipid, "dims:level"); + if (p) dims_c.level = p->value.uint; + p = gf_filter_pid_get_property_str(tkw->ipid, "dims:pathComponents"); + if (p) dims_c.pathComponents = p->value.uint; + p = gf_filter_pid_get_property_str(tkw->ipid, "dims:fullRequestHost"); + if (p) dims_c.fullRequestHost = p->value.uint; + p = gf_filter_pid_get_property_str(tkw->ipid, "dims:streamType"); + if (p) dims_c.streamType = p->value.boolean; + p = gf_filter_pid_get_property_str(tkw->ipid, "dims:redundant"); + if (p) dims_c.containsRedundant = p->value.uint; + p = gf_filter_pid_get_property_str(tkw->ipid, "dims:scriptTypes"); + if (p) dims_c.content_script_types = p->value.string; + + e = gf_isom_new_dims_description(ctx->file, tkw->track_num, &dims_c, NULL, NULL, &tkw->stsd_idx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new DIMS sample description: %s\n", gf_error_to_string(e) )); + return e; + } + } else if (use_gen_sample_entry) { + u8 isor_ext_buf[14]; + u32 len = 0; + GF_GenericSampleDescription udesc; + memset(&udesc, 0, sizeof(GF_GenericSampleDescription)); + + if (!comp_name) comp_name = "Unknown"; + len = (u32) strlen(comp_name); + if (len>32) len = 32; + udesc.compressor_name[0] = len; + memcpy(udesc.compressor_name+1, comp_name, len); + if ((codec_id==GF_CODECID_RAW) || unknown_generic) + udesc.vendor_code = GF_4CC('G','P','A','C'); + + udesc.samplerate = sr; + udesc.nb_channels = nb_chan; + if (codec_id==GF_CODECID_RAW) { + if (ase_mode==GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_MPEG) { + m_subtype = m_subtype_alt_raw; + udesc.extension_buf_size = 14; + udesc.extension_buf = isor_ext_buf; + memset(isor_ext_buf, 0, sizeof(u8)*14); + isor_ext_buf[3] = 14; + isor_ext_buf[4] = 'p'; + isor_ext_buf[5] = 'c'; + isor_ext_buf[6] = 'm'; + isor_ext_buf[7] = 'C'; + isor_ext_buf[12] = 1; //little endian only for now + isor_ext_buf[13] = raw_bitdepth; //little endian only for now + } else { + udesc.is_qtff = GF_TRUE; + udesc.version = 1; + ase_mode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_QTFF; + } + } + udesc.codec_tag = m_subtype; + udesc.width = width; + udesc.height = height; + if (width) { + udesc.v_res = 72; + udesc.h_res = 72; + udesc.depth = 24; + } + if (unknown_generic) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] muxing unknown codec ID %s, using generic sample entry with 4CC \"%s\"\n", gf_codecid_name(codec_id), gf_4cc_to_str(m_subtype) )); + } + + e = gf_isom_new_generic_sample_description(ctx->file, tkw->track_num, (char *)src_url, NULL, &udesc, &tkw->stsd_idx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error creating new sample description for stream type %d codecid %d: %s\n", tkw->stream_type, codec_id, gf_error_to_string(e) )); + return e; + } + tkw->use_dref = src_url ? GF_TRUE : GF_FALSE; + } else { + assert(0); + } + + if (ctx->btrt && !tkw->skip_bitrate_update) { + u32 avg_rate, max_rate, dbsize; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_BITRATE); + avg_rate = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_MAXRATE); + max_rate = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DBSIZE); + dbsize = p ? p->value.uint : 0; + + if (avg_rate && max_rate) { + gf_isom_update_bitrate(ctx->file, tkw->track_num, tkw->stsd_idx, avg_rate, max_rate, dbsize); + } + } else { + gf_isom_update_bitrate(ctx->file, tkw->track_num, tkw->stsd_idx, 0, 0, 0); + } + +multipid_stsd_setup: + if (multi_pid_stsd) { + if (multi_pid_idxnb_frames_per_sample; + is_nalu_backup = tkw->is_nalu; + } + pid = gf_list_get(multi_pid_stsd, multi_pid_idx); + multi_pid_idx ++; + //reload codecID, decoder config and enhancement decoder config + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (p) codec_id = p->value.uint; + dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + enh_dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + //force stsd idx to be 0 to avoid removing the stsd + tkw->stsd_idx = 0; + goto sample_entry_setup; + } + tkw->stsd_idx = multi_pid_final_stsd_idx; + //restore input pid + pid = orig_pid; + codec_id = tkw->codecid; + + tkw->is_nalu = is_nalu_backup; + tkw->nb_frames_per_sample = frames_per_sample_backup; + } + + + //final opt: we couldn't detect before if the same stsd was possible, now that we have create a new one, check again + if (needs_sample_entry) { + reuse_stsd = 0; + //don't try to reuse STSDs in multi STSD setup for DASH + if (multi_pid_stsd) count = 0; + else count = gf_isom_get_sample_description_count(ctx->file, tkw->track_num); + for (i=0; istsd_idx) continue; + + if (gf_isom_is_same_sample_description(ctx->file, tkw->track_num, tkw->stsd_idx, ctx->file, tkw->track_num, i+1) ) { + gf_isom_remove_stream_description(ctx->file, tkw->track_num, tkw->stsd_idx); + tkw->stsd_idx = i+1; + reuse_stsd = 1; + break; + } + } + if (!reuse_stsd) { + tkw->samples_in_stsd = 0; + } else if (use_3gpp_config) { + GF_3GPConfig *gpp_cfg = gf_isom_3gp_config_get(ctx->file, tkw->track_num, tkw->stsd_idx); + if (gpp_cfg) { + gpp_cfg->AMR_mode_set = tkw->amr_mode_set; + gf_isom_3gp_config_update(ctx->file, tkw->track_num, gpp_cfg, tkw->stsd_idx); + gf_free(gpp_cfg); + } + } + } + + if (tkw->is_encrypted) { + const char *scheme_uri=NULL; + const char *kms_uri=NULL; + u32 scheme_version=0; + u32 scheme_type = 0; + Bool is_sel_enc = GF_FALSE; + u32 KI_length=0; + u32 IV_length=0; + /*todo !*/ + const char *oma_contentID=0; + u32 oma_encryption_type=0; + u64 oma_plainTextLength=0; + const char *oma_textual_headers=NULL; + u32 textual_headers_len=0; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PROTECTION_SCHEME_TYPE); + if (p) scheme_type = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PROTECTION_SCHEME_VERSION); + if (p) scheme_version = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PROTECTION_SCHEME_URI); + if (p) scheme_uri = p->value.string; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PROTECTION_KMS_URI); + if (p) kms_uri = p->value.string; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ISMA_SELECTIVE_ENC); + if (p) is_sel_enc = p->value.boolean; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ISMA_IV_LENGTH); + if (p) IV_length = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ISMA_KI_LENGTH); + if (p) KI_length = p->value.uint; + + tkw->scheme_type = scheme_type; + switch (scheme_type) { + case GF_ISOM_ISMACRYP_SCHEME: + gf_isom_set_ismacryp_protection(ctx->file, tkw->track_num, tkw->stsd_idx, scheme_type, scheme_version, (char *) scheme_uri, (char *) kms_uri, is_sel_enc, KI_length, IV_length); + break; + case GF_ISOM_OMADRM_SCHEME: + p = gf_filter_pid_get_property(pid, GF_PROP_PID_OMA_CRYPT_TYPE); + if (p) oma_encryption_type = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_OMA_CID); + if (p) oma_contentID = p->value.string; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_OMA_TXT_HDR); + if (p) oma_textual_headers = p->value.string; + if (oma_textual_headers) textual_headers_len = (u32) strlen(oma_textual_headers); + p = gf_filter_pid_get_property(pid, GF_PROP_PID_OMA_CLEAR_LEN); + if (p) oma_plainTextLength = p->value.longuint; + gf_isom_set_oma_protection(ctx->file, tkw->track_num, tkw->stsd_idx, (char *) oma_contentID, (char*) kms_uri, oma_encryption_type, oma_plainTextLength, (char*)oma_textual_headers, textual_headers_len, + is_sel_enc, KI_length, IV_length); + + break; + case GF_ISOM_ADOBE_SCHEME: + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ADOBE_CRYPT_META); + gf_isom_set_adobe_protection(ctx->file, tkw->track_num, tkw->stsd_idx, scheme_type, 1/*scheme_version*/, 1/*is_sel_enc*/,p ? p->value.data.ptr : NULL, p ? p->value.data.size : 0); + break; + case GF_ISOM_PIFF_SCHEME: + case GF_ISOM_CENC_SCHEME: + case GF_ISOM_CENS_SCHEME: + case GF_ISOM_CBC_SCHEME: + case GF_ISOM_CBCS_SCHEME: + tkw->cenc_state = CENC_NEED_SETUP; + if (tkw->is_nalu || tkw->is_av1 || tkw->is_vpx) tkw->cenc_subsamples = GF_TRUE; + break; + default: + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] Unrecognized protection scheme type %s, using generic signaling\n", gf_4cc_to_str(tkw->stream_type) )); + switch (tkw->stream_type) { + case GF_STREAM_VISUAL: + gf_isom_set_media_type(ctx->file, tkw->track_num, GF_ISOM_BOX_TYPE_ENCV); + break; + case GF_STREAM_AUDIO: + gf_isom_set_media_type(ctx->file, tkw->track_num, GF_ISOM_BOX_TYPE_ENCA); + break; + case GF_STREAM_TEXT: + gf_isom_set_media_type(ctx->file, tkw->track_num, GF_ISOM_BOX_TYPE_ENCT); + break; + case GF_STREAM_FONT: + gf_isom_set_media_type(ctx->file, tkw->track_num, GF_ISOM_BOX_TYPE_ENCF); + break; + default: + gf_isom_set_media_type(ctx->file, tkw->track_num, GF_ISOM_BOX_TYPE_ENCS); + break; + } + gf_isom_set_generic_protection(ctx->file, tkw->track_num, tkw->stsd_idx, scheme_type, scheme_version, (char*)scheme_uri, (char*)kms_uri); + } + } else { + //in case we used track template + gf_isom_remove_samp_enc_box(ctx->file, tkw->track_num); + gf_isom_remove_samp_group_box(ctx->file, tkw->track_num); + } + + if (is_true_pid) { + mp4_mux_write_track_refs(ctx, tkw, "isom:scal", GF_ISOM_REF_SCAL); + mp4_mux_write_track_refs(ctx, tkw, "isom:sabt", GF_ISOM_REF_SABT); + mp4_mux_write_track_refs(ctx, tkw, "isom:tbas", GF_ISOM_REF_TBAS); + } else if (codec_id==GF_CODECID_HEVC_TILES) { + mp4_mux_write_track_refs(ctx, tkw, "isom:tbas", GF_ISOM_REF_TBAS); + } + + if (is_true_pid && ctx->dash_mode && is_tile_base) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_MULTI_TRACK); + if (p) { + GF_List *multi_tracks = p->value.ptr; + count = gf_list_count(multi_tracks); + for (i=0; iccst) { + e = gf_isom_set_image_sequence_coding_constraints(ctx->file, tkw->track_num, tkw->stsd_idx, GF_FALSE, GF_FALSE, GF_TRUE, 15); + if (e) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] Failed to set coding constraints parameter: %s\n", gf_error_to_string(e) )); + } + } + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_ALPHA); + if (p && p->value.boolean) { + e = gf_isom_set_image_sequence_alpha(ctx->file, tkw->track_num, tkw->stsd_idx, GF_FALSE); + if (e) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] Failed to set alpha config: %s\n", gf_error_to_string(e) )); + } + } + } + +sample_entry_done: + if (!tkw->is_item) { + if (ctx->maxchunk) + gf_isom_hint_max_chunk_size(ctx->file, tkw->track_num, ctx->maxchunk); + + if (sr) { + if (use_flac_entry) { + while (sr>65535) { + u32 val = sr/2; + if (val*2 != sr) { + sr=65535; + break; + } + sr = val; + } + } + gf_isom_set_audio_info(ctx->file, tkw->track_num, tkw->stsd_idx, sr, nb_chan, nb_bps, ctx->make_qt ? GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_QTFF : ase_mode); + if ((m_subtype==GF_ISOM_SUBTYPE_IPCM) || (m_subtype==GF_ISOM_SUBTYPE_FPCM)) { + GF_AudioChannelLayout layout; + memset(&layout, 0, sizeof(GF_AudioChannelLayout)); + layout.stream_structure = 1; + layout.channels_count = nb_chan; + if (ch_layout) + layout.definedLayout = gf_audio_fmt_get_cicp_from_layout(ch_layout); + else + layout.definedLayout = gf_audio_fmt_get_cicp_layout(nb_chan, 0, 0); + gf_isom_set_audio_layout(ctx->file, tkw->track_num, tkw->stsd_idx, &layout); + } + } + else if (width) { + u32 colour_type=0; + u16 colour_primaries=0, transfer_characteristics=0, matrix_coefficients=0; + Bool full_range_flag=GF_FALSE; + + gf_isom_set_visual_info(ctx->file, tkw->track_num, tkw->stsd_idx, width, height); + if (sar.den) { + if (sar.num != sar.den) { + gf_isom_set_pixel_aspect_ratio(ctx->file, tkw->track_num, tkw->stsd_idx, sar.num, sar.den, GF_FALSE); + width = width * sar.num / sar.den; + } + //old importer did not set PASP for + else if (!gf_sys_old_arch_compat() || (codec_id!=GF_CODECID_MPEG4_PART2) ) { + gf_isom_set_pixel_aspect_ratio(ctx->file, tkw->track_num, tkw->stsd_idx, 1, 1, GF_TRUE); + } + } + + gf_isom_set_track_layout_info(ctx->file, tkw->track_num, width<<16, height<<16, 0, 0, z_order); + if (codec_id==GF_CODECID_HEVC_TILES) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ORIG_SIZE); + if (p) { + gf_isom_set_track_layout_info(ctx->file, tkw->track_num, p->value.vec2i.x<<16, p->value.vec2i.y<<16, 0, 0, z_order); + } + } + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_COLR_PRIMARIES); + if (p) colour_primaries = p->value.uint; + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_COLR_TRANSFER); + if (p) transfer_characteristics = p->value.uint; + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_COLR_MX); + if (p) matrix_coefficients = p->value.uint; + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_COLR_RANGE); + if (p) full_range_flag = p->value.boolean; + + if ((ctx->prores_track == tkw) || force_colr) { + //other conditions were set above, here we force 1:1 pasp box even if no sar or 1:1 + if (!sar.den || (sar.num == 1)) { + gf_isom_set_pixel_aspect_ratio(ctx->file, tkw->track_num, tkw->stsd_idx, -1, -1, GF_TRUE); + } + + if (colour_primaries || transfer_characteristics || matrix_coefficients) { + gf_isom_set_visual_color_info(ctx->file, tkw->track_num, tkw->stsd_idx, GF_4CC('n','c','l','c'), colour_primaries, transfer_characteristics, matrix_coefficients, GF_FALSE, NULL, 0); + } else { + e = gf_isom_get_color_info(ctx->file, tkw->track_num, tkw->stsd_idx, &colour_type, &colour_primaries, &transfer_characteristics, &matrix_coefficients, &full_range_flag); + if (e==GF_NOT_FOUND) { + colour_primaries = 1; + transfer_characteristics = 1; + matrix_coefficients = 1; + full_range_flag = GF_FALSE; + if (ctx->make_qt==1) { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[ProRes] No color info present in visual track, defaulting to BT709\n")); + } + } + gf_isom_set_visual_color_info(ctx->file, tkw->track_num, tkw->stsd_idx, GF_4CC('n','c','l','c'), 1, 1, 1, GF_FALSE, NULL, 0); + } + + if (ctx->prores_track == tkw) { + u32 chunk_size; + if ((width<=720) && (height<=576)) chunk_size = 2000000; + else chunk_size = 4000000; + gf_isom_hint_max_chunk_size(ctx->file, tkw->track_num, chunk_size); + } + } else { + if (colour_primaries || transfer_characteristics || matrix_coefficients) { + gf_isom_set_visual_color_info(ctx->file, tkw->track_num, tkw->stsd_idx, GF_4CC('n','c','l','x'), colour_primaries, transfer_characteristics, matrix_coefficients, full_range_flag, NULL, 0); + } + + } + } + //default for old arch + else if (force_tk_layout + || (use_m4sys && (tkw->stream_type==GF_STREAM_VISUAL) && !width && !height) + ) { + gf_isom_set_track_layout_info(ctx->file, tkw->track_num, 320<<16, 240<<16, 0, 0, 0); + } + + if (lang_name) gf_isom_set_media_language(ctx->file, tkw->track_num, (char*)lang_name); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ISOM_STSD_TEMPLATE); + if (ctx->tktpl && p && p->value.data.ptr) { + gf_isom_update_sample_description_from_template(ctx->file, tkw->track_num, tkw->stsd_idx, p->value.data.ptr, p->value.data.size); + } + + } + + if (is_true_pid && ctx->importer && !tkw->import_msg_header_done) { +#ifndef GPAC_DISABLE_LOG + const char *dst_type = tkw->is_item ? "Item Importing" : "Track Importing"; +#endif + tkw->import_msg_header_done = GF_TRUE; + if (!imp_name) imp_name = comp_name; + if (sr) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("%s %s - SampleRate %d Num Channels %d\n", dst_type, imp_name, sr, nb_chan)); + } else if (is_text_subs) { + if (txt_fsize || txt_font) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("%s %s - Text track %d x %d font %s (size %d) layer %d\n", dst_type, imp_name, width, height, txt_font ? txt_font : "unspecified", txt_fsize, z_order)); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("%s %s - Text track %d x %d layer %d\n", dst_type, imp_name, width, height, z_order)); + + } + } else if (width) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("%s %s - Width %d Height %d FPS %d/%d SAR %d/%u\n", dst_type, imp_name, width, height, fps.num, fps.den, sar.num, sar.den)); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("%s %s\n", dst_type, imp_name)); + } +#ifndef GPAC_DISABLE_AV_PARSERS + if (tkw->svcc) { + AVCState avc; + memset(&avc, 0, sizeof(AVCState)); + count = gf_list_count(tkw->svcc->sequenceParameterSets); + for (i=0; isvcc->sequenceParameterSets, i); + u8 nal_type = sl->data[0] & 0x1F; + Bool is_subseq = (nal_type == GF_AVC_NALU_SVC_SUBSEQ_PARAM) ? GF_TRUE : GF_FALSE; + s32 ps_idx = gf_media_avc_read_sps(sl->data, sl->size, &avc, is_subseq, NULL); + if (ps_idx>=0) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("SVC Detected - SSPS ID %d - frame size %d x %d\n", ps_idx-GF_SVC_SSPS_ID_SHIFT, avc.sps[ps_idx].width, avc.sps[ps_idx].height )); + + } + } + } +#endif + + } + if (txt_font) gf_free(txt_font); + if (!ctx->xps_inband || tkw->is_item) { + if (tkw->svcc) { + gf_odf_avc_cfg_del(tkw->svcc); + tkw->svcc = NULL; + } + if (tkw->lvcc) { + gf_odf_hevc_cfg_del(tkw->lvcc); + tkw->lvcc = NULL; + } + } else if (needs_sample_entry || make_inband_headers) { + mp4_mux_make_inband_header(ctx, tkw); + } + + tkw->negctts_shift = 0; + tkw->probe_min_ctts = GF_FALSE; + if (is_true_pid && !tkw->nb_samples && !tkw->is_item) { + Bool use_negccts = GF_FALSE; + Bool remove_edits = GF_FALSE; + s64 moffset=0; + ctx->config_timing = GF_TRUE; + ctx->update_report = GF_TRUE; + + //if we have an edit list (due to track template) only providing media offset, trash it + if (!gf_isom_get_edit_list_type(ctx->file, tkw->track_num, &moffset)) { + if (!gf_sys_old_arch_compat()) { + gf_isom_remove_edits(ctx->file, tkw->track_num); + } else { + remove_edits = GF_TRUE; + } + } + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_DELAY); + if (p) { + if (p->value.sint < 0) { + if (ctx->ctmode==MP4MX_CT_NEGCTTS) use_negccts = GF_TRUE; + else { + if (remove_edits) { + gf_isom_remove_edits(ctx->file, tkw->track_num); + } + gf_isom_set_edit(ctx->file, tkw->track_num, 0, 0, -p->value.sint, GF_ISOM_EDIT_NORMAL); + } + } else if (p->value.sint > 0) { + s64 dur = p->value.sint; + dur *= (u32) ctx->moovts; + dur /= tkw->src_timescale; + if (remove_edits) { + gf_isom_remove_edits(ctx->file, tkw->track_num); + } + gf_isom_set_edit(ctx->file, tkw->track_num, 0, dur, 0, GF_ISOM_EDIT_DWELL); + gf_isom_set_edit(ctx->file, tkw->track_num, 0, 0, 0, GF_ISOM_EDIT_NORMAL); + } + tkw->ts_delay = p->value.sint; + } else if (tkw->stream_type==GF_STREAM_VISUAL) { + tkw->probe_min_ctts = GF_TRUE; + } + if (use_negccts) { + gf_isom_set_composition_offset_mode(ctx->file, tkw->track_num, GF_TRUE); + + if (!tkw->has_brands) { + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISO4, GF_TRUE); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISOM, GF_FALSE); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISO1, GF_FALSE); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISO2, GF_FALSE); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISO3, GF_FALSE); + } + + tkw->negctts_shift = (tkw->ts_delay<0) ? -tkw->ts_delay : 0; + } else { + //this will remove any cslg in the track due to template + gf_isom_set_composition_offset_mode(ctx->file, tkw->track_num, GF_FALSE); + } + + mp4_mux_set_tags(ctx, tkw); + } + return GF_OK; +} + +static GF_Err mp4_mux_done(GF_Filter *filter, GF_MP4MuxCtx *ctx, Bool is_final); + +static GF_Err mp4_mux_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_MP4MuxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + TrackWriter *tkw = gf_filter_pid_get_udta(pid); + if (tkw) { + gf_list_del_item(ctx->tracks, tkw); + gf_free(tkw); + } + //removing last pid, flush file + if (ctx->opid && !gf_list_count(ctx->tracks) && ctx->file && !ctx->init_movie_done) { + return mp4_mux_done(filter, ctx, GF_TRUE); + } + return GF_OK; + } + return mp4_mux_setup_pid(filter, pid, GF_TRUE); +} + +static Bool mp4_mux_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_MP4MuxCtx *ctx = gf_filter_get_udta(filter); + + if (evt->base.on_pid && (evt->base.type==GF_FEVT_INFO_UPDATE) ) { + TrackWriter *tkw = gf_filter_pid_get_udta(evt->base.on_pid); + if (tkw) { + GF_PropertyEntry *pe=NULL; + const GF_PropertyValue *p; + p = gf_filter_pid_get_info(tkw->ipid, GF_PROP_PID_DOWN_BYTES, &pe); + if (p) tkw->down_bytes = p->value.longuint; + + p = gf_filter_pid_get_info(tkw->ipid, GF_PROP_PID_DOWN_SIZE, &pe); + if (p) tkw->down_size = p->value.longuint; + + gf_filter_release_property(pe); + } + + return GF_FALSE; + } + if (!evt->base.on_pid && (evt->base.type==GF_FEVT_STOP) ) { + if (ctx->file && ctx->owns_mov) { + mp4_mux_done(filter, ctx, GF_TRUE); + } + } + if (evt->base.on_pid && (evt->base.type==GF_FEVT_PLAY) ) { + //by default don't cancel event - to rework once we have downloading in place + ctx->force_play = GF_TRUE; + if (evt->play.speed<0) + ctx->is_rewind = GF_TRUE; + return GF_FALSE; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +enum +{ + CENC_CONFIG=0, + CENC_ADD_NORMAL, + CENC_ADD_FRAG, +}; + +static void mp4_mux_cenc_insert_pssh(GF_MP4MuxCtx *ctx, TrackWriter *tkw) +{ + bin128 *keyIDs=NULL; + u32 max_keys = 0; + u32 i, nb_pssh; + + //set pssh + const GF_PropertyValue *p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_CENC_PSSH); + if (!p) return; + + if (!ctx->bs_r) ctx->bs_r = gf_bs_new(p->value.data.ptr, p->value.data.size, GF_BITSTREAM_READ); + else gf_bs_reassign_buffer(ctx->bs_r, p->value.data.ptr, p->value.data.size); + + nb_pssh = gf_bs_read_u32(ctx->bs_r); + for (i = 0; i < nb_pssh; i++) { + bin128 sysID; + u32 j, kid_count, version=0; + char *data; + u32 len; + + gf_bs_read_data(ctx->bs_r, sysID, 16); + version = gf_bs_read_u32(ctx->bs_r); + kid_count = gf_bs_read_u32(ctx->bs_r); + + if (kid_count>=max_keys) { + max_keys = kid_count; + keyIDs = gf_realloc(keyIDs, sizeof(bin128)*max_keys); + } + for (j=0; jbs_r, keyIDs[j], 16); + } + len = gf_bs_read_u32(ctx->bs_r); + data = p->value.data.ptr + gf_bs_get_position(ctx->bs_r); + gf_cenc_set_pssh(ctx->file, sysID, version, kid_count, keyIDs, data, len, (tkw->scheme_type==GF_ISOM_PIFF_SCHEME) ? GF_TRUE : GF_FALSE); + gf_bs_skip_bytes(ctx->bs_r, len); + } + if (keyIDs) gf_free(keyIDs); +} + +static GF_Err mp4_mux_cenc_update(GF_MP4MuxCtx *ctx, TrackWriter *tkw, GF_FilterPacket *pck, u32 act_type, u32 pck_size) +{ + const GF_PropertyValue *p; + GF_Err e; + Bool pck_is_encrypted; + u32 skip_byte_block=0, crypt_byte_block=0; + u32 IV_size=0, constant_IV_size=0; + bin128 constant_IV, KID; + u32 scheme_type=0; + u32 scheme_version=0; + char *sai = NULL; + u32 sai_size = 0; + Bool needs_seig = GF_FALSE; + u32 sample_num; + + if (tkw->cenc_state == CENC_SETUP_ERROR) + return GF_SERVICE_ERROR; + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_CENC_PATTERN); + if (p) { + skip_byte_block = p->value.frac.num; + crypt_byte_block = p->value.frac.den; + } + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_CENC_IV_CONST); + if (p && p->value.data.size) { + constant_IV_size = p->value.data.size; + if (constant_IV_size>16) constant_IV_size=16; + memcpy(constant_IV, p->value.data.ptr, constant_IV_size); + } + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_CENC_IV_SIZE); + if (p) IV_size = p->value.uint; + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_KID); + if (p) { + memcpy(KID, p->value.data.ptr, 16); + } + + if (pck) { + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_CENC_SAI); + if (p) { + sai = p->value.data.ptr; + sai_size = p->value.data.size; + } + } + + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_PROTECTION_SCHEME_TYPE); + if (p) scheme_type = p->value.uint; + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_PROTECTION_SCHEME_VERSION); + if (p) scheme_version = p->value.uint; + + //initial setup + if (tkw->cenc_state==CENC_NEED_SETUP) { + u32 cenc_stsd_mode=0; + u32 container_type = GF_ISOM_BOX_TYPE_SENC; + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_CENC_STSD_MODE); + if (p) cenc_stsd_mode = p->value.uint; + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_ENCRYPTED); + if (p) pck_is_encrypted = p->value.boolean; + else pck_is_encrypted = GF_FALSE; + + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_CENC_STORE); + if (p && p->value.uint) container_type = p->value.uint; + + tkw->clear_stsd_idx = 0; + if (cenc_stsd_mode) { + u32 clone_stsd_idx; + e = gf_isom_clone_sample_description(ctx->file, tkw->track_num, ctx->file, tkw->track_num, tkw->stsd_idx, NULL, NULL, &clone_stsd_idx); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to clone sample description: %s\n", gf_error_to_string(e) )); + return e; + } + //before + if (cenc_stsd_mode==1) { + tkw->clear_stsd_idx = tkw->stsd_idx; + tkw->stsd_idx = clone_stsd_idx; + } + //after + else { + tkw->clear_stsd_idx = clone_stsd_idx; + } + } + + tkw->cenc_state = CENC_SETUP_DONE; + e = gf_isom_set_cenc_protection(ctx->file, tkw->track_num, tkw->stsd_idx, scheme_type, scheme_version, pck_is_encrypted, IV_size, KID, crypt_byte_block, skip_byte_block, constant_IV_size, constant_IV); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to setup CENC information: %s\n", gf_error_to_string(e) )); + tkw->cenc_state = CENC_SETUP_ERROR; + return e; + } + + if (ctx->psshs == MP4MX_PSSH_MOOV) + mp4_mux_cenc_insert_pssh(ctx, tkw); + + tkw->IV_size = IV_size; + memcpy(tkw->KID, KID, sizeof(bin128)); + tkw->crypt_byte_block = crypt_byte_block; + tkw->skip_byte_block = skip_byte_block; + tkw->constant_IV_size = constant_IV_size; + memcpy(tkw->constant_IV, constant_IV, sizeof(bin128)); + + if (!tkw->has_brands && (scheme_type==GF_ISOM_OMADRM_SCHEME)) + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_OPF2, GF_TRUE); + + if (container_type) { + e = gf_isom_cenc_allocate_storage(ctx->file, tkw->track_num, container_type, 0, 0, NULL); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to setup CENC storage: %s\n", gf_error_to_string(e) )); + tkw->cenc_state = CENC_SETUP_ERROR; + return e; + } + } + } + if (act_type==CENC_CONFIG) return GF_OK; + + pck_is_encrypted = GF_FALSE; + if (pck) + pck_is_encrypted = gf_filter_pck_get_crypt_flags(pck); + + //!! tkw->nb_samples / tkw->samples_in_frag not yet incremented !! + if (act_type == CENC_ADD_FRAG) + sample_num = tkw->samples_in_frag + 1; + else + sample_num = tkw->nb_samples + 1; + + if (!pck_is_encrypted) { + if (tkw->clear_stsd_idx) { + if (act_type==CENC_ADD_FRAG) { + return gf_isom_fragment_set_cenc_sai(ctx->file, tkw->track_id, 0, NULL, 0, GF_FALSE, ctx->saio32); + } else { + return gf_isom_track_cenc_add_sample_info(ctx->file, tkw->track_num, GF_ISOM_BOX_TYPE_SENC, 0, NULL, 0, GF_FALSE, NULL, ctx->saio32); + } + } else { + bin128 dumb_IV; + memset(dumb_IV, 0, 16); + e = gf_isom_set_sample_cenc_group(ctx->file, tkw->track_num, sample_num, 0, 0, dumb_IV, 0, 0, 0, NULL); + IV_size = 0; + tkw->has_seig = GF_TRUE; + } + } else { + e = GF_OK; + if (tkw->IV_size != IV_size) needs_seig = GF_TRUE; + else if (memcmp(tkw->KID, KID, sizeof(bin128))) needs_seig = GF_TRUE; + else if (tkw->crypt_byte_block != crypt_byte_block) needs_seig = GF_TRUE; + else if (tkw->skip_byte_block != skip_byte_block) needs_seig = GF_TRUE; + else if (tkw->constant_IV_size != constant_IV_size) needs_seig = GF_TRUE; + else if (constant_IV_size && memcmp(tkw->constant_IV, constant_IV, sizeof(bin128))) needs_seig = GF_TRUE; + + if (needs_seig) { + e = gf_isom_set_sample_cenc_group(ctx->file, tkw->track_num, sample_num, 1, IV_size, KID, crypt_byte_block, skip_byte_block, constant_IV_size, constant_IV); + tkw->has_seig = GF_TRUE; + } else if (tkw->has_seig) { + e = gf_isom_set_sample_cenc_default_group(ctx->file, tkw->track_num, sample_num); + } + } + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to set sample encryption group entry: %s)\n", gf_error_to_string(e) )); + return e; + } + + if (!sai) { + if (tkw->constant_IV_size && !tkw->cenc_subsamples) + return GF_OK; + sai_size = pck_size; + } + + if (act_type==CENC_ADD_FRAG) { + if (pck_is_encrypted) { + e = gf_isom_fragment_set_cenc_sai(ctx->file, tkw->track_id, IV_size, sai, sai_size, tkw->cenc_subsamples, ctx->saio32); + } else { + e = gf_isom_fragment_set_cenc_sai(ctx->file, tkw->track_id, 0, NULL, 0, GF_FALSE, ctx->saio32); + } + if (e) return e; + } else { + if (sai) { + e = gf_isom_track_cenc_add_sample_info(ctx->file, tkw->track_num, GF_ISOM_BOX_TYPE_SENC, IV_size, sai, sai_size, tkw->cenc_subsamples, NULL, ctx->saio32); + } else if (!pck_is_encrypted) { + e = gf_isom_track_cenc_add_sample_info(ctx->file, tkw->track_num, GF_ISOM_BOX_TYPE_SENC, 0, NULL, 0, GF_FALSE, NULL, ctx->saio32); + } + if (e) return e; + } + + return GF_OK; +} + +GF_FilterSAPType mp4_mux_get_sap(GF_MP4MuxCtx *ctx, GF_FilterPacket *pck) +{ + GF_FilterSAPType sap = gf_filter_pck_get_sap(pck); + if (!sap) return sap; + if (ctx->forcesync) return GF_FILTER_SAP_1; + return sap; +} + +static GF_Err mp4_mux_process_sample(GF_MP4MuxCtx *ctx, TrackWriter *tkw, GF_FilterPacket *pck, Bool for_fragment) +{ + GF_Err e=GF_OK; + u64 cts, prev_dts; + u32 prev_size=0; + u32 duration = 0; + u32 timescale = 0; + const GF_PropertyValue *subs; + GF_FilterSAPType sap_type; + Bool insert_subsample_dsi = GF_FALSE; + u32 first_nal_is_audelim = GF_FALSE; + u32 sample_desc_index = tkw->stsd_idx; + + timescale = gf_filter_pck_get_timescale(pck); + + subs = gf_filter_pck_get_property(pck, GF_PROP_PCK_SUBS); + + prev_dts = tkw->nb_samples ? tkw->sample.DTS : GF_FILTER_NO_TS; + prev_size = tkw->sample.dataLength; + tkw->sample.CTS_Offset = 0; + tkw->sample.data = (char *)gf_filter_pck_get_data(pck, &tkw->sample.dataLength); + + ctx->update_report = GF_TRUE; + ctx->total_bytes_in += tkw->sample.dataLength; + ctx->total_samples++; + + tkw->sample.DTS = gf_filter_pck_get_dts(pck); + cts = gf_filter_pck_get_cts(pck); + if (tkw->sample.DTS == GF_FILTER_NO_TS) { + if (cts == GF_FILTER_NO_TS) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Sample with no DTS/CTS, cannot add (last DTS "LLU", last size %d)\n", prev_dts, prev_size )); + return GF_NON_COMPLIANT_BITSTREAM; + } else { + u32 min_pck_dur = gf_filter_pid_get_min_pck_duration(tkw->ipid); + if (min_pck_dur) { + tkw->sample.DTS = prev_dts; + if (timescale != tkw->tk_timescale) { + tkw->sample.DTS *= timescale; + tkw->sample.DTS /= tkw->tk_timescale; + } + tkw->sample.DTS += min_pck_dur; + } else { + tkw->sample.DTS = cts; + } + } + } else { + tkw->sample.CTS_Offset = (s32) ((s64) cts - (s64) tkw->sample.DTS); + } + + //tkw->ts_shift is in source timescale, apply it before rescaling TSs/duration + if (tkw->ts_shift) { + if (ctx->is_rewind) { + if (tkw->sample.DTS <= tkw->ts_shift) { + tkw->sample.DTS = tkw->ts_shift - tkw->sample.DTS; + cts = tkw->ts_shift - cts; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] broken timing in track, initial ts "LLU" less than TS "LLU"\n", tkw->ts_shift, tkw->sample.DTS)); + } + } else { + if (tkw->sample.DTS >= tkw->ts_shift) { + tkw->sample.DTS -= tkw->ts_shift; + cts -= tkw->ts_shift; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] broken timing in track, initial ts "LLU" greater than TS "LLU"\n", tkw->ts_shift, tkw->sample.DTS)); + } + } + } + + duration = gf_filter_pck_get_duration(pck); + if (timescale != tkw->tk_timescale) { + s64 ctso; + tkw->sample.DTS *= tkw->tk_timescale; + tkw->sample.DTS /= timescale; + ctso = (s64) tkw->sample.CTS_Offset; + ctso *= tkw->tk_timescale; + ctso /= timescale; + tkw->sample.CTS_Offset = (s32) ctso; + duration *= tkw->tk_timescale; + duration /= timescale; + } + + + tkw->sample.IsRAP = 0; + sap_type = mp4_mux_get_sap(ctx, pck); + if (sap_type==GF_FILTER_SAP_1) + tkw->sample.IsRAP = SAP_TYPE_1; + else if ( (sap_type == GF_FILTER_SAP_4) && (tkw->stream_type != GF_STREAM_VISUAL) ) + tkw->sample.IsRAP = SAP_TYPE_1; + + tkw->sample.DTS += tkw->dts_patch; + if (tkw->nb_samples && (prev_dts >= tkw->sample.DTS) ) { + //the fragmented API will patch the duration on the fly + if (!for_fragment) { + gf_isom_patch_last_sample_duration(ctx->file, tkw->track_num, prev_dts); + } + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] PID %s Sample %d with DTS "LLU" less than previous sample DTS "LLU", adjusting prev sample duration\n", gf_filter_pid_get_name(tkw->ipid), tkw->nb_samples, tkw->sample.DTS, prev_dts )); + + tkw->dts_patch = prev_dts - tkw->sample.DTS; + tkw->sample.DTS += tkw->dts_patch; + } + + + if (tkw->negctts_shift) + tkw->sample.CTS_Offset -= tkw->negctts_shift; + + if (tkw->probe_min_ctts) { + s32 diff = (s32) ((s64) cts - (s64) tkw->sample.DTS); + if (diff < tkw->min_neg_ctts) + tkw->min_neg_ctts = diff; + } + if (tkw->sample.CTS_Offset) tkw->has_ctts = GF_TRUE; + + if (tkw->sample.CTS_Offset < tkw->min_neg_ctts) + tkw->min_neg_ctts = tkw->sample.CTS_Offset; + + tkw->sample.nb_pack = 0; + if (tkw->raw_audio_bytes_per_sample) { + tkw->sample.nb_pack = tkw->sample.dataLength / tkw->raw_audio_bytes_per_sample; + if (tkw->sample.nb_pack) duration /= tkw->sample.nb_pack; + } + + if (tkw->cenc_state && tkw->clear_stsd_idx && !gf_filter_pck_get_crypt_flags(pck)) { + sample_desc_index = tkw->clear_stsd_idx; + } + + if (tkw->use_dref) { + u64 data_offset = gf_filter_pck_get_byte_offset(pck); + if (data_offset != GF_FILTER_NO_BO) { + e = gf_isom_add_sample_reference(ctx->file, tkw->track_num, sample_desc_index, &tkw->sample, data_offset); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to add sample DTS "LLU" as reference: %s\n", tkw->sample.DTS, gf_error_to_string(e) )); + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Cannot add sample reference at DTS "LLU" , input sample data is not continous in source\n", tkw->sample.DTS )); + } + } else if (tkw->nb_frames_per_sample && (tkw->nb_samples % tkw->nb_frames_per_sample)) { + if (for_fragment) { + e = gf_isom_fragment_append_data(ctx->file, tkw->track_id, tkw->sample.data, tkw->sample.dataLength, 0); + } else { + e = gf_isom_append_sample_data(ctx->file, tkw->track_num, tkw->sample.data, tkw->sample.dataLength); + } + tkw->has_append = GF_TRUE; + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to append sample DTS "LLU" data: %s\n", tkw->sample.DTS, gf_error_to_string(e) )); + } + } else { + if ((tkw->sample.IsRAP || tkw->force_inband_inject) && ctx->xps_inband) { + char *au_delim=NULL; + u32 au_delim_size=0; + char *pck_data = tkw->sample.data; + u32 pck_data_len = tkw->sample.dataLength; + tkw->sample.data = tkw->inband_hdr; + tkw->sample.dataLength = tkw->inband_hdr_size; + tkw->force_inband_inject = GF_FALSE; + + if (tkw->is_nalu==NALU_AVC) { + char *nal = pck_data + tkw->nal_unit_size; + if ((nal[0] & 0x1F) == GF_AVC_NALU_ACCESS_UNIT) { + first_nal_is_audelim = au_delim_size = 2 + tkw->nal_unit_size; + au_delim = pck_data; + } + } else { + char *nal = pck_data + tkw->nal_unit_size; + if (((nal[0] & 0x7E)>>1) == GF_HEVC_NALU_ACCESS_UNIT) { + first_nal_is_audelim = au_delim_size = 3 + tkw->nal_unit_size; + au_delim = pck_data; + } + } + + if (au_delim) { + tkw->sample.data = au_delim; + tkw->sample.dataLength = au_delim_size; + pck_data += au_delim_size; + pck_data_len -= au_delim_size; + } + + if (for_fragment) { + e = gf_isom_fragment_add_sample(ctx->file, tkw->track_id, &tkw->sample, sample_desc_index, duration, 0, 0, 0); + if (!e && au_delim) { + e = gf_isom_fragment_append_data(ctx->file, tkw->track_id, tkw->inband_hdr, tkw->inband_hdr_size, 0); + } + if (!e) e = gf_isom_fragment_append_data(ctx->file, tkw->track_id, pck_data, pck_data_len, 0); + } else { + e = gf_isom_add_sample(ctx->file, tkw->track_num, sample_desc_index, &tkw->sample); + if (au_delim && !e) { + e = gf_isom_append_sample_data(ctx->file, tkw->track_num, tkw->inband_hdr, tkw->inband_hdr_size); + } + if (!e) e = gf_isom_append_sample_data(ctx->file, tkw->track_num, pck_data, pck_data_len); + } + insert_subsample_dsi = GF_TRUE; + } else if (for_fragment) { + e = gf_isom_fragment_add_sample(ctx->file, tkw->track_id, &tkw->sample, sample_desc_index, duration, 0, 0, 0); + } else { + e = gf_isom_add_sample(ctx->file, tkw->track_num, sample_desc_index, &tkw->sample); + if (!e && !duration) { + gf_isom_set_last_sample_duration(ctx->file, tkw->track_num, 0); + } + } + + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to add sample DTS "LLU" - prev DTS "LLU": %s\n", tkw->sample.DTS, prev_dts, gf_error_to_string(e) )); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MP4Mux] added sample DTS "LLU" - prev DTS "LLU" - prev size %d\n", tkw->sample.DTS, prev_dts, prev_size)); + } + + if (!e && tkw->cenc_state) { + e = mp4_mux_cenc_update(ctx, tkw, pck, for_fragment ? CENC_ADD_FRAG : CENC_ADD_NORMAL, tkw->sample.dataLength); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to set sample CENC information: %s\n", gf_error_to_string(e) )); + } + } + } + + tkw->nb_samples++; + tkw->samples_in_stsd++; + tkw->samples_in_frag++; + + if (e) return e; + + //compat with old arch: write sample to group info for all samples + if ((sap_type==3) || tkw->has_open_gop) { + if (for_fragment) { + e = gf_isom_fragment_set_sample_rap_group(ctx->file, tkw->track_id, tkw->samples_in_frag, (sap_type==3) ? GF_TRUE : GF_FALSE, 0); + } else if (sap_type==3) { + e = gf_isom_set_sample_rap_group(ctx->file, tkw->track_num, tkw->nb_samples, GF_TRUE /*(sap_type==3) ? GF_TRUE : GF_FALSE*/, 0); + } + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to set sample DTS "LLU" SAP 3 in RAP group: %s\n", tkw->sample.DTS, gf_error_to_string(e) )); + } + tkw->has_open_gop = GF_TRUE; + } + if (!ctx->noroll) { + if ((sap_type==GF_FILTER_SAP_4) || (sap_type==GF_FILTER_SAP_4_PROL) || tkw->gdr_type) { + GF_ISOSampleRollType roll_type = 0; + s16 roll = gf_filter_pck_get_roll_info(pck); + if (sap_type==GF_FILTER_SAP_4) roll_type = GF_ISOM_SAMPLE_ROLL; + else if (sap_type==GF_FILTER_SAP_4_PROL) roll_type = GF_ISOM_SAMPLE_PREROLL; + else if (tkw->gdr_type==GF_FILTER_SAP_4_PROL) { + roll_type = GF_ISOM_SAMPLE_PREROLL_NONE; + } + + if (for_fragment) { + e = gf_isom_fragment_set_sample_roll_group(ctx->file, tkw->track_id, tkw->samples_in_frag, roll_type, roll); + } else { + e = gf_isom_set_sample_roll_group(ctx->file, tkw->track_num, tkw->nb_samples, roll_type, roll); + } + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to set sample DTS "LLU" SAP 4 roll %s in roll group: %s\n", tkw->sample.DTS, roll, gf_error_to_string(e) )); + } + if (sap_type && !tkw->gdr_type) + tkw->gdr_type = sap_type; + } + } + + if (subs) { + //if no AUDelim nal and inband header injection, push new subsample + if (!first_nal_is_audelim && insert_subsample_dsi) { + if (for_fragment) { + gf_isom_fragment_add_subsample(ctx->file, tkw->track_id, 0, tkw->inband_hdr_size, 0, 0, 0); + } else { + gf_isom_add_subsample(ctx->file, tkw->track_num, tkw->nb_samples, 0, tkw->inband_hdr_size, 0, 0, 0); + } + insert_subsample_dsi = GF_FALSE; + } + + if (!ctx->bs_r) ctx->bs_r = gf_bs_new(subs->value.data.ptr, subs->value.data.size, GF_BITSTREAM_READ); + else gf_bs_reassign_buffer(ctx->bs_r, subs->value.data.ptr, subs->value.data.size); + + while (gf_bs_available(ctx->bs_r)) { + u32 flags = gf_bs_read_u32(ctx->bs_r); + u32 subs_size = gf_bs_read_u32(ctx->bs_r); + u32 reserved = gf_bs_read_u32(ctx->bs_r); + u8 priority = gf_bs_read_u8(ctx->bs_r); + u8 discardable = gf_bs_read_u8(ctx->bs_r); + + if (for_fragment) { + gf_isom_fragment_add_subsample(ctx->file, tkw->track_id, flags, subs_size, priority, reserved, discardable); + } else { + gf_isom_add_subsample(ctx->file, tkw->track_num, tkw->nb_samples, flags, subs_size, priority, reserved, discardable); + } + + //we have AUDelim nal and inband header injection, push new subsample for inband header once we have pushed the first subsample (au delim) + if (insert_subsample_dsi) { + if (first_nal_is_audelim != subs_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] inserting inband param after AU delimiter NALU, but sample has subsample information not aligned on NALU (got %d subsample size but expecting %d) - file might be broken!\n", subs_size, first_nal_is_audelim)); + } + if (for_fragment) { + gf_isom_fragment_add_subsample(ctx->file, tkw->track_id, 0, tkw->inband_hdr_size, 0, 0, 0); + } else { + gf_isom_add_subsample(ctx->file, tkw->track_num, tkw->nb_samples, 0, tkw->inband_hdr_size, 0, 0, 0); + } + insert_subsample_dsi = GF_FALSE; + } + } + } + + if (ctx->deps) { + u8 dep_flags = gf_filter_pck_get_dependency_flags(pck); + if (dep_flags) { + u32 is_leading = (dep_flags>>6) & 0x3; + u32 depends_on = (dep_flags>>4) & 0x3; + u32 depended_on = (dep_flags>>2) & 0x3; + u32 redundant = (dep_flags) & 0x3; + if (for_fragment) { + gf_isom_fragment_set_sample_flags(ctx->file, tkw->track_id, is_leading, depends_on, depended_on, redundant); + } else { + gf_isom_set_sample_flags(ctx->file, tkw->track_num, tkw->nb_samples, is_leading, depends_on, depended_on, redundant); + } + } + } + + + tkw->next_is_first_sample = GF_FALSE; + + if (duration && !for_fragment && !tkw->raw_audio_bytes_per_sample) + gf_isom_set_last_sample_duration(ctx->file, tkw->track_num, duration); + + if (ctx->idur.num) { + Bool abort = GF_FALSE; + if (ctx->idur.num>0) { + u64 mdur = gf_isom_get_media_duration(ctx->file, tkw->track_num); + + /*patch to align to old arch */ + if (gf_sys_old_arch_compat() && (tkw->stream_type==GF_STREAM_VISUAL)) { + mdur = tkw->sample.DTS; + } + + if (ctx->importer) { + tkw->prog_done = mdur * ctx->idur.den; + tkw->prog_total = ((u64)tkw->tk_timescale) * ctx->idur.num; + } + + /*patch to align to old arch */ + if (gf_sys_old_arch_compat()) { + if (mdur * (u64) ctx->idur.den > tkw->tk_timescale * (u64) ctx->idur.num) + abort = GF_TRUE; + } else { + if (mdur * (u64) ctx->idur.den >= tkw->tk_timescale * (u64) ctx->idur.num) + abort = GF_TRUE; + } + } else { + if ((s32) tkw->nb_samples >= -ctx->idur.num) + abort = GF_TRUE; + } + + if (abort) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_STOP, tkw->ipid); + gf_filter_pid_send_event(tkw->ipid, &evt); + + tkw->aborted = GF_TRUE; + } + } else if (ctx->importer) { + if (tkw->nb_frames) { + tkw->prog_done = tkw->nb_samples; + tkw->prog_total = tkw->nb_frames; + } else { + u64 data_offset = gf_filter_pck_get_byte_offset(pck); + if (data_offset == GF_FILTER_NO_BO) { + data_offset = tkw->down_bytes; + } + if ((data_offset != GF_FILTER_NO_BO) && tkw->down_size) { + tkw->prog_done = data_offset; + tkw->prog_total = tkw->down_size; + } else { + if (tkw->pid_dur.den && tkw->pid_dur.num) { + tkw->prog_done = tkw->sample.DTS * tkw->pid_dur.den; + tkw->prog_total = tkw->pid_dur.num * tkw->tk_timescale; + } else { + tkw->prog_done = 0; + tkw->prog_total = 1; + } + + } + } + } + return GF_OK; +} + +static GF_Err mp4_mux_process_item(GF_MP4MuxCtx *ctx, TrackWriter *tkw, GF_FilterPacket *pck) +{ + GF_Err e; + u32 meta_type, item_id, size, item_type, nb_items, media_brand; + GF_ImageItemProperties image_props; + const char *data, *item_name=NULL; + const GF_PropertyValue *p, *dsi, *dsi_enh; + GF_Box *config_box = NULL; + + + if (ctx->init_movie_done) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Cannot add item to a finalized movie, not supported\n")); + return GF_NOT_SUPPORTED; + } + + if (tkw->stream_type != GF_STREAM_VISUAL) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Cannot add item other than visual, not supported - use MP4Box for this\n")); + return GF_NOT_SUPPORTED; + } + ctx->update_report = GF_TRUE; + + meta_type = gf_isom_get_meta_type(ctx->file, GF_TRUE, 0); + if (!meta_type) { + e = gf_isom_set_meta_type(ctx->file, GF_TRUE, 0, GF_META_ITEM_TYPE_PICT); + } else if (meta_type != GF_META_ITEM_TYPE_PICT) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] File already has a root 'meta' box of type %s\n", gf_4cc_to_str(meta_type))); + e= GF_BAD_PARAM; + } else { + e = GF_OK; + } + if (e) return e; + + data = (char *)gf_filter_pck_get_data(pck, &size); + if (!data) { + if (gf_filter_pck_get_frame_interface(pck)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Cannot add items from raw decoder outputs, not supported\n")); + return GF_NOT_SUPPORTED; + } + return GF_OK; + } + ctx->total_bytes_in += size; + ctx->total_samples++; + + + item_id = 0; + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_ITEM_ID); + if (p) item_id = p->value.uint; + + item_name = "Image"; + p = gf_filter_pid_get_property_str(tkw->ipid, "meta:name"); + if (p && p->value.string) item_name = p->value.string; + + memset(&image_props, 0, sizeof(GF_ImageItemProperties)); + + dsi = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_DECODER_CONFIG); + dsi_enh = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + + switch (tkw->codecid) { + case GF_CODECID_AVC: + case GF_ISOM_SUBTYPE_SVC_H264: + case GF_ISOM_SUBTYPE_MVC_H264: + if (!dsi) return GF_OK; + + if (tkw->codecid==GF_CODECID_AVC) { + config_box = gf_isom_box_new(GF_ISOM_BOX_TYPE_AVCC); + item_type = GF_ISOM_SUBTYPE_AVC_H264; + } else if (tkw->codecid==GF_CODECID_MVC) { + config_box = gf_isom_box_new(GF_ISOM_BOX_TYPE_MVCC); + item_type = GF_ISOM_SUBTYPE_MVC_H264; + if (dsi_enh) dsi = dsi_enh; + } else { + config_box = gf_isom_box_new(GF_ISOM_BOX_TYPE_SVCC); + item_type = GF_ISOM_SUBTYPE_SVC_H264; + if (dsi_enh) dsi = dsi_enh; + } + + ((GF_AVCConfigurationBox *)config_box)->config = gf_odf_avc_cfg_read(dsi->value.data.ptr, dsi->value.data.size); + if (! ((GF_AVCConfigurationBox *)config_box)->config) return GF_NON_COMPLIANT_BITSTREAM; + + image_props.num_channels = 3; + image_props.bits_per_channel[0] = ((GF_AVCConfigurationBox *)config_box)->config->luma_bit_depth; + image_props.bits_per_channel[1] = ((GF_AVCConfigurationBox *)config_box)->config->chroma_bit_depth; + image_props.bits_per_channel[2] = ((GF_AVCConfigurationBox *)config_box)->config->chroma_bit_depth; + media_brand = GF_ISOM_BRAND_AVCI; + break; + + case GF_CODECID_HEVC: + case GF_CODECID_HEVC_TILES: + case GF_CODECID_LHVC: + if (tkw->codecid == GF_CODECID_LHVC) { + if (dsi_enh) dsi = dsi_enh; + if (!dsi) return GF_OK; + } + config_box = gf_isom_box_new(GF_ISOM_BOX_TYPE_HVCC); + + if (dsi_enh) { + ((GF_HEVCConfigurationBox *)config_box)->config = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, GF_TRUE); + item_type = GF_ISOM_SUBTYPE_LHV1; + } else { + if ((tkw->codecid == GF_CODECID_HEVC) && !dsi) return GF_OK; + + ((GF_HEVCConfigurationBox *)config_box)->config = gf_odf_hevc_cfg_read(dsi->value.data.ptr, dsi->value.data.size, GF_FALSE); + item_type = (tkw->codecid == GF_CODECID_HEVC_TILES) ? GF_ISOM_SUBTYPE_HVT1 : GF_ISOM_SUBTYPE_HVC1; + } + if (! ((GF_HEVCConfigurationBox *)config_box)->config) { + if ((tkw->codecid != GF_CODECID_HEVC_TILES) && !dsi) return GF_NON_COMPLIANT_BITSTREAM; + } else { + image_props.num_channels = 3; + image_props.bits_per_channel[0] = ((GF_HEVCConfigurationBox *)config_box)->config->luma_bit_depth; + image_props.bits_per_channel[1] = ((GF_HEVCConfigurationBox *)config_box)->config->chroma_bit_depth; + image_props.bits_per_channel[2] = ((GF_HEVCConfigurationBox *)config_box)->config->chroma_bit_depth; + } + media_brand = GF_ISOM_BRAND_HEIC; + if (tkw->codecid==GF_CODECID_LHVC) { + media_brand = GF_ISOM_BRAND_HEIM; + } + break; + case GF_CODECID_AV1: + if (!dsi) return GF_OK; + + config_box = gf_isom_box_new(GF_ISOM_BOX_TYPE_AV1C); + ((GF_AV1ConfigurationBox *)config_box)->config = gf_odf_av1_cfg_read(dsi->value.data.ptr, dsi->value.data.size); + + if (! ((GF_AV1ConfigurationBox *)config_box)->config) return GF_NON_COMPLIANT_BITSTREAM; + + item_type = GF_ISOM_SUBTYPE_AV01; + u8 depth = ((GF_AV1ConfigurationBox *)config_box)->config->high_bitdepth ? (((GF_AV1ConfigurationBox *)config_box)->config->twelve_bit ? 12 : 10 ) : 8; + if (((GF_AV1ConfigurationBox *)config_box)->config->monochrome) { + image_props.num_channels = 1; + image_props.bits_per_channel[0] = depth; + image_props.bits_per_channel[1] = 0; + image_props.bits_per_channel[2] = 0; + } else { + image_props.num_channels = 3; + image_props.bits_per_channel[0] = depth; + image_props.bits_per_channel[1] = depth; + image_props.bits_per_channel[2] = depth; + } + media_brand = GF_ISOM_BRAND_AVIF; + break; + case GF_CODECID_JPEG: + item_type = GF_ISOM_SUBTYPE_JPEG; + media_brand = GF_ISOM_SUBTYPE_JPEG /* == GF_4CC('j', 'p', 'e', 'g') */; + break; + case GF_CODECID_J2K: + item_type = GF_ISOM_SUBTYPE_JP2K; + media_brand = GF_4CC('j', '2', 'k', 'i'); + break; + case GF_CODECID_PNG: + item_type = GF_ISOM_SUBTYPE_PNG; + //not defined ! + media_brand = GF_ISOM_SUBTYPE_PNG /* == GF_4CC('j', 'p', 'e', 'g') */; + break; + default: + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Error: Codec %s not supported to create HEIF image items\n", gf_codecid_name(tkw->codecid) )); + return GF_NOT_SUPPORTED; + } + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_WIDTH); + if (p) image_props.width = p->value.uint; + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_HEIGHT); + if (p) image_props.height = p->value.uint; + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_ALPHA); + if (p) image_props.alpha = p->value.boolean; + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_SAR); + if (p) { + image_props.hSpacing = p->value.frac.num; + image_props.vSpacing = p->value.frac.den; + } else { + image_props.hSpacing = image_props.vSpacing = 1; + } + image_props.config = config_box; + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_HIDDEN); + if (p) image_props.hidden = p->value.boolean; + + nb_items = gf_isom_get_meta_item_count(ctx->file, GF_TRUE, 0); + + e = gf_isom_add_meta_item_memory(ctx->file, GF_TRUE, 0, item_name, item_id, item_type, NULL, NULL, &image_props, (u8 *)data, size, NULL); + + if (config_box) gf_isom_box_del(config_box); + + if (e) return e; + + //retrieve the final itemID + gf_isom_get_meta_item_info(ctx->file, GF_TRUE, 0, nb_items+1, &item_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + tkw->item_id = item_id; + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_PRIMARY_ITEM); + if (p && p->value.boolean) { + e = gf_isom_set_meta_primary_item(ctx->file, GF_TRUE, 0, item_id); + if (e) return e; + } + //if primary item is not set, assign one + else if (! gf_isom_get_meta_primary_item_id(ctx->file, GF_TRUE, 0)) { + e = gf_isom_set_meta_primary_item(ctx->file, GF_TRUE, 0, item_id); + if (e) return e; + } + + if (!ctx->major_brand_set) { + gf_isom_set_brand_info(ctx->file, GF_ISOM_BRAND_MIF1, 0); + gf_isom_reset_alt_brands(ctx->file); + ctx->major_brand_set = 2; + } + if (media_brand && (ctx->major_brand_set==2)) { + gf_isom_modify_alternate_brand(ctx->file, media_brand, 1); + } +#if 0 + if (e == GF_OK && meta->ref_type) { + e = gf_isom_meta_add_item_ref(file, meta->root_meta, tk, meta->item_id, meta->ref_item_id, meta->ref_type, NULL); + } +#endif + return GF_OK; +} + +static void mp4mux_send_output(GF_MP4MuxCtx *ctx) +{ + if (ctx->dst_pck) { + if (ctx->notify_filename) { + gf_filter_pck_set_framing(ctx->dst_pck, GF_TRUE, GF_FALSE); + gf_filter_pck_set_property(ctx->dst_pck, GF_PROP_PCK_FILENUM, &PROP_UINT(ctx->cur_file_idx_plus_one-1) ); + if (ctx->cur_file_suffix) { + gf_filter_pck_set_property(ctx->dst_pck, GF_PROP_PCK_FILESUF, &PROP_STRING_NO_COPY(ctx->cur_file_suffix) ); + ctx->cur_file_suffix = NULL; + } + ctx->notify_filename = 0; + } + gf_filter_pck_send(ctx->dst_pck); + ctx->dst_pck = NULL; + } +} + + +static void mp4_mux_flush_frag(GF_MP4MuxCtx *ctx, Bool is_init, u64 idx_start_range, u64 idx_end_range) +{ + GF_FilterEvent evt; + TrackWriter *tkw = NULL; + + if (ctx->dst_pck) { + if (!ctx->single_file) { + Bool s, e; + gf_filter_pck_get_framing(ctx->dst_pck, &s, &e); + gf_filter_pck_set_framing(ctx->dst_pck, s, GF_TRUE); + ctx->first_pck_sent = GF_FALSE; + ctx->current_offset = 0; + } + if (is_init) { + gf_filter_pck_set_dependency_flags(ctx->dst_pck, 0xFF); + gf_filter_pck_set_carousel_version(ctx->dst_pck, 1); + } + mp4mux_send_output(ctx); + } + + if (ctx->dash_mode) { + //send event on first track only + tkw = gf_list_get(ctx->tracks, 0); + GF_FEVT_INIT(evt, GF_FEVT_SEGMENT_SIZE, tkw->ipid); + evt.seg_size.seg_url = NULL; + evt.seg_size.is_init = is_init ? GF_TRUE : GF_FALSE; + if (!is_init || !idx_end_range) { + evt.seg_size.media_range_start = ctx->current_offset; + evt.seg_size.media_range_end = ctx->current_offset + ctx->current_size - 1; + } + evt.seg_size.idx_range_start = idx_start_range; + evt.seg_size.idx_range_end = idx_end_range; + gf_filter_pid_send_event(tkw->ipid, &evt); + + ctx->current_offset += ctx->current_size; + ctx->current_size = 0; + //changing file + if (ctx->seg_name) { + ctx->first_pck_sent = GF_FALSE; + } + } +} + + +static GF_Err mp4_mux_initialize_movie(GF_MP4MuxCtx *ctx) +{ + GF_Err e; + u32 i, count = gf_list_count(ctx->tracks); + TrackWriter *ref_tkw = NULL; + u64 min_dts = 0; + u32 min_dts_scale=0; + u32 def_fake_dur=0; + u32 def_fake_scale=0; +#ifdef GF_ENABLE_CTRN + u32 traf_inherit_base_id=0; +#endif + u32 nb_segments=0; + GF_Fraction64 max_dur; + ctx->single_file = GF_TRUE; + ctx->current_offset = ctx->current_size = 0; + max_dur.den = 1; + max_dur.num = 0; + + if (ctx->sseg && ctx->noinit) + ctx->single_file = GF_FALSE; + + if (ctx->idur.num && ctx->idur.den) { + max_dur.num = ctx->idur.num; + max_dur.den = ctx->idur.den; + } + + //make sure we have one sample from each PID. This will trigger potential pending reconfigure + //for filters updating the PID caps before the first packet dispatch + for (i=0; itracks, i); + GF_FilterPacket *pck; + if (tkw->fake_track) continue; + + pck = gf_filter_pid_get_packet(tkw->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(tkw->ipid)) continue; + return GF_OK; + } + + if (!ctx->dash_mode && !ctx->cur_file_idx_plus_one) { + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM); + if (p) { + ctx->cur_file_idx_plus_one = p->value.uint + 1; + if (!ctx->cur_file_suffix) { + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILESUF); + if (p && p->value.string) ctx->cur_file_suffix = gf_strdup(p->value.string); + } + ctx->notify_filename = GF_TRUE; + } + } + + if (tkw->cenc_state==CENC_NEED_SETUP) { + mp4_mux_cenc_update(ctx, tkw, pck, CENC_CONFIG, 0); + } + + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME); + if (p && strlen(p->value.string)) ctx->single_file = GF_FALSE; + + def_fake_dur = gf_filter_pck_get_duration(pck); + def_fake_scale = tkw->src_timescale; + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_DURATION); + if (p && p->value.lfrac.num>0 && p->value.lfrac.den) { + tkw->pid_dur = p->value.lfrac; + if (max_dur.num * (s64) p->value.lfrac.den < (s64) max_dur.den * p->value.lfrac.num) { + max_dur.num = p->value.lfrac.num; + max_dur.den = p->value.lfrac.den; + } + } +#ifdef GF_ENABLE_CTRN + if (tkw->codecid==GF_CODECID_HEVC) + traf_inherit_base_id = tkw->track_id; +#endif + } + //good to go, finalize for fragments + for (i=0; itracks, i); + + if (tkw->fake_track) { + if (def_fake_scale) { + def_pck_dur = def_fake_dur; + def_pck_dur *= tkw->src_timescale; + def_pck_dur /= def_fake_scale; + } else { + def_pck_dur = 0; + } + } else { + GF_FilterPacket *pck = gf_filter_pid_get_packet(tkw->ipid); + //can be null if eos + if (pck) { + u32 tscale; + //otherwise setup fragmentation, using first sample desc as default idx + //first pck dur as default + def_pck_dur = gf_filter_pck_get_duration(pck); + + dts = gf_filter_pck_get_dts(pck); + if (dts == GF_FILTER_NO_TS) + dts = gf_filter_pck_get_cts(pck); + tscale = gf_filter_pck_get_timescale(pck); + + if (!min_dts || min_dts * tscale > dts * min_dts_scale) { + min_dts = dts; + min_dts_scale = tscale; + } + } else { + def_pck_dur = 0; + } + } + if (tkw->src_timescale != tkw->tk_timescale) { + def_pck_dur *= tkw->tk_timescale; + def_pck_dur /= tkw->src_timescale; + } + + //and consider audio & text all RAPs, the rest not rap - this will need refinement later on + //but won't break the generated files + switch (tkw->stream_type) { + case GF_STREAM_AUDIO: + case GF_STREAM_TEXT: + def_is_rap = GF_TRUE; + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_HAS_SYNC); + if (p && p->value.boolean) + def_is_rap = GF_FALSE; + break; + case GF_STREAM_VISUAL: + switch (tkw->codecid) { + case GF_CODECID_PNG: + case GF_CODECID_JPEG: + case GF_CODECID_J2K: + break; + case GF_CODECID_HEVC_TILES: +#ifdef GF_ENABLE_CTRN + if (ctx->ctrn && ctx->ctrni) + inherit_traf_from_track = traf_inherit_base_id; +#endif + break; + default: + if (!ref_tkw) ref_tkw = tkw; + break; + } + def_is_rap = GF_FALSE; + break; + + default: + def_is_rap = GF_FALSE; + break; + } + + mp4_mux_set_hevc_groups(ctx, tkw); + + //use 1 for the default sample description index. If no multi stsd, this is always the case + //otherwise we need to update the stsd idx in the traf headers + e = gf_isom_setup_track_fragment(ctx->file, tkw->track_id, tkw->stsd_idx, def_pck_dur, 0, (u8) def_is_rap, 0, 0, ctx->nofragdef ? GF_TRUE : GF_FALSE); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Unable to setup fragmentation for track ID %d: %s\n", tkw->track_id, gf_error_to_string(e) )); + return e; + } + + if (ctx->tfdt.den && ctx->tfdt.num) { + tkw->offset_dts = ctx->tfdt.num * tkw->tk_timescale; + tkw->offset_dts /= ctx->tfdt.den; + } + + if (tkw->fake_track) { + gf_list_del_item(ctx->tracks, tkw); + if (ref_tkw==tkw) ref_tkw=NULL; + mp4_mux_track_writer_del(tkw); + i--; + count--; + continue; + } + +#ifdef GF_ENABLE_CTRN + if (inherit_traf_from_track) + gf_isom_enable_traf_inherit(ctx->file, tkw->track_id, inherit_traf_from_track); +#endif + + if (!tkw->box_patched) { + p = gf_filter_pid_get_property_str(tkw->ipid, "boxpatch"); + if (p && p->value.string) { + e = gf_isom_apply_box_patch(ctx->file, tkw->track_id, p->value.string, GF_FALSE); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Unable to apply box patch %s to track %d: %s\n", + p->value.string, tkw->track_id, gf_error_to_string(e) )); + } + } + tkw->box_patched = GF_TRUE; + } + + p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_DASH_SEGMENTS); + if (p && (p->value.uint>nb_segments)) + nb_segments = p->value.uint; + } + + if (max_dur.num) { + u64 mdur = max_dur.num; + mdur *= (u32) ctx->moovts; + mdur /= max_dur.den; + gf_isom_set_movie_duration(ctx->file, mdur); + } + + //if we have an explicit track reference for fragmenting, move it first in our list + if (ref_tkw) { + gf_list_del_item(ctx->tracks, ref_tkw); + gf_list_insert(ctx->tracks, ref_tkw, 0); + } + ctx->ref_tkw = gf_list_get(ctx->tracks, 0); + + if (!ctx->abs_offset) { + u32 mval = ctx->dash_mode ? '6' : '5'; + u32 mbrand, mcount, found=0; + u8 szB[GF_4CC_MSIZE]; + gf_isom_set_fragment_option(ctx->file, 0, GF_ISOM_TFHD_FORCE_MOOF_BASE_OFFSET, 1); + + gf_isom_get_brand_info(ctx->file, &mbrand, NULL, &mcount); + strcpy(szB, gf_4cc_to_str(mbrand)); + if (!strncmp(szB, "iso", 3) && (szB[3] >= mval) && (szB[3] <= 'F') ) found = 1; + i=0; + while (!found && (ifile, i, &mbrand); + strcpy(szB, gf_4cc_to_str(mbrand)); + if (!strncmp(szB, "iso", 3) && (szB[3] >= mval) && (szB[3] <= 'F') ) found = 1; + } + + /*because of movie fragments MOOF based offset, ISOM <4 is forbidden*/ + if (!found) { + gf_isom_set_brand_info(ctx->file, ctx->dash_mode ? GF_ISOM_BRAND_ISO6 : GF_ISOM_BRAND_ISO5, 1); + } + + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISOM, GF_FALSE); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISO1, GF_FALSE); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISO2, GF_FALSE); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISO3, GF_FALSE); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISO4, GF_FALSE); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_AVC1, GF_FALSE); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_MP41, GF_FALSE); + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_MP42, GF_FALSE); + } + + if (ctx->dash_mode) { + /*DASH self-init media segment*/ + if (ctx->dash_mode==MP4MX_DASH_VOD) { + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_DSMS, GF_TRUE); + } else { + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_DASH, GF_TRUE); + } + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_MSIX, ((ctx->dash_mode==MP4MX_DASH_VOD) && (ctx->subs_sidx>=0)) ? GF_TRUE : GF_FALSE); + } + + if (ctx->boxpatch && !ctx->box_patched) { + e = gf_isom_apply_box_patch(ctx->file, 0, ctx->boxpatch, GF_FALSE); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Unable to apply box patch %s: %s\n", ctx->boxpatch, gf_error_to_string(e) )); + } + ctx->box_patched = GF_TRUE; + } + + e = gf_isom_finalize_for_fragment(ctx->file, ctx->dash_mode ? 1 : 0, ctx->mvex); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Unable to finalize moov for fragmentation: %s\n", gf_error_to_string(e) )); + return e; + } + ctx->init_movie_done = GF_TRUE; + + if (min_dts_scale) { + ctx->next_frag_start = (Double) min_dts; + ctx->next_frag_start /= min_dts_scale; + } + ctx->next_frag_start += ctx->cdur; + ctx->adjusted_next_frag_start = ctx->next_frag_start; + ctx->fragment_started = GF_FALSE; + + if (ctx->noinit) { + if (ctx->dst_pck) gf_filter_pck_discard(ctx->dst_pck); + ctx->dst_pck = NULL; + ctx->current_size = ctx->current_offset = 0; + ctx->first_pck_sent = GF_FALSE; + } else { + mp4_mux_flush_frag(ctx, GF_TRUE, 0, 0); + } + assert(!ctx->dst_pck); + + //change major brand for segments + if (ctx->styp && (strlen(ctx->styp)>=4)) { + u32 styp_brand = GF_4CC(ctx->styp[0], ctx->styp[1], ctx->styp[2], ctx->styp[3]); + u32 version = 0; + char *sep = strchr(ctx->styp, '.'); + if (sep) version = atoi(sep+1); + gf_isom_set_brand_info(ctx->file, styp_brand, version); + } + + if (ctx->dash_mode==MP4MX_DASH_VOD) { + if ((ctx->vodcache==MP4MX_VODCACHE_REPLACE) && !nb_segments && (!ctx->media_dur || !ctx->dash_dur) ) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] Media duration unknown, cannot use replace mode of vodcache, using temp file for VoD storage\n")); + ctx->vodcache = MP4MX_VODCACHE_ON; + e = mp4mx_setup_dash_vod(ctx, NULL); + if (e) return e; + } + + if (ctx->vodcache==MP4MX_VODCACHE_REPLACE) { + GF_BitStream *bs; + u8 *output; + char *msg; + GF_FilterPacket *pck; + u32 len; + Bool exact_sidx = GF_TRUE; + + if (!nb_segments) { + exact_sidx = GF_FALSE; + nb_segments = (u32) ( ctx->media_dur / ctx->dash_dur); + //always add an extra segment + nb_segments ++; + //and safety alloc of 10% + if (nb_segments>10) + nb_segments += 10*nb_segments/100; + else + nb_segments ++; + } + + //max sidx size: full box + sidx fields + timing 64 bit + nb segs (each 12 bytes) + ctx->sidx_max_size = 12 + (12 + 16) + 12 * nb_segments; + + //we produce an ssix, add full box + nb subsegs + nb_segments * (range_count=2 + 2*(range+size)) + if (ctx->ssix) { + ctx->sidx_max_size += 12 + 4 + nb_segments * 12; + } + + if (!exact_sidx) { + //and a free box + ctx->sidx_max_size += 8; + ctx->sidx_size_exact = GF_FALSE; + } else { + ctx->sidx_size_exact = GF_TRUE; + } + ctx->sidx_chunk_offset = (u32) (ctx->current_offset + ctx->current_size); + //send a dummy packet + pck = gf_filter_pck_new_alloc(ctx->opid, ctx->sidx_max_size, &output); + gf_filter_pck_set_framing(pck, GF_FALSE, GF_FALSE); + //format as free box for now + bs = gf_bs_new(output, ctx->sidx_max_size, GF_BITSTREAM_WRITE); + gf_bs_write_u32(bs, ctx->sidx_max_size); + gf_bs_write_u32(bs, GF_ISOM_BOX_TYPE_FREE); + msg = "GPAC " GPAC_VERSION" SIDX placeholder"; + len = (u32) strlen(msg); + if (len+8>ctx->sidx_max_size) len = ctx->sidx_max_size - 8; + gf_bs_write_data(bs, msg, len ); + gf_bs_del(bs); + gf_filter_pck_send(pck); + } else if (ctx->vodcache==MP4MX_VODCACHE_ON) { + ctx->store_output = GF_TRUE; + } else { + ctx->store_output = GF_FALSE; + ctx->sidx_chunk_offset = (u32) (ctx->current_offset + ctx->current_size); + } + gf_isom_allocate_sidx(ctx->file, ctx->subs_sidx, ctx->chain_sidx, 0, NULL, NULL, NULL, ctx->ssix); + } + return GF_OK; +} + +static GF_Err mp4_mux_start_fragment(GF_MP4MuxCtx *ctx, GF_FilterPacket *pck) +{ + GF_Err e; + u32 i, count = gf_list_count(ctx->tracks); + Bool has_tfdt=GF_FALSE; + GF_ISOStartFragmentFlags flags=0; + + //setup some default + gf_isom_set_next_moof_number(ctx->file, ctx->msn); + ctx->msn += ctx->msninc; + + if (ctx->moof_first) flags |= GF_ISOM_FRAG_MOOF_FIRST; +#ifdef GF_ENABLE_CTRN + if (ctx->ctrn) flags |= GF_ISOM_FRAG_USE_COMPACT; +#endif + + e = gf_isom_start_fragment(ctx->file, flags); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Unable to start new fragment: %s\n", gf_error_to_string(e) )); + return e; + } + if (pck) { + const GF_PropertyValue *p = gf_filter_pck_get_property(pck, GF_PROP_PCK_MOOF_TEMPLATE); + if (p && p->value.data.ptr) { + GF_SegmentIndexBox *out_sidx = NULL; + gf_isom_set_fragment_template(ctx->file, p->value.data.ptr, p->value.data.size, &has_tfdt, &out_sidx); + if (out_sidx) { + if (ctx->cloned_sidx) gf_isom_box_del((GF_Box *)ctx->cloned_sidx); + ctx->cloned_sidx = out_sidx; + ctx->cloned_sidx_index = 0; + } + } + } + + //setup some default + for (i=0; itracks, i); + e = GF_OK; + if (ctx->strun) { + e = gf_isom_set_fragment_option(ctx->file, tkw->track_id, GF_ISOM_TRAF_RANDOM_ACCESS, 0); + } + //fragment at sap boundaries for video, but not in dash mode (compatibility with old arch) + else if (ctx->fsap && (tkw->stream_type == GF_STREAM_VISUAL) && !ctx->dash_mode) { + e = gf_isom_set_fragment_option(ctx->file, tkw->track_id, GF_ISOM_TRAF_RANDOM_ACCESS, 1); + } + if (e) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] Unable set fragment options: %s\n", gf_error_to_string(e) )); + } + tkw->fragment_done = GF_FALSE; + tkw->insert_tfdt = (has_tfdt || ctx->tfdt_traf) ? GF_TRUE : ctx->insert_tfdt; + tkw->dur_in_frag = 0; + + if (ctx->trun_inter) { + gf_isom_set_fragment_option(ctx->file, tkw->track_id, GF_ISOM_TRUN_SET_INTERLEAVE_ID, 0); + } + + if (ctx->sdtp_traf) + gf_isom_set_fragment_option(ctx->file, tkw->track_id, GF_ISOM_TRAF_USE_SAMPLE_DEPS_BOX, ctx->sdtp_traf); + + + if (ctx->insert_pssh) + mp4_mux_cenc_insert_pssh(ctx, tkw); + } + ctx->fragment_started = GF_TRUE; + ctx->insert_tfdt = GF_FALSE; + ctx->insert_pssh = GF_FALSE; + return GF_OK; +} + +static GF_Err mp4_mux_flush_fragmented(GF_Filter *filter, GF_MP4MuxCtx *ctx) +{ + GF_FilterPacket *pck; + u8 *output; + u32 nb_read, blocksize = ctx->block_size; + if (ctx->flush_done + blocksize>ctx->flush_size) { + blocksize = (u32) (ctx->flush_size - ctx->flush_done); + } + if (!blocksize) return GF_EOS; + pck = gf_filter_pck_new_alloc(ctx->opid, blocksize, &output); + nb_read = (u32) gf_fread(output, blocksize, ctx->tmp_store); + ctx->flush_done += nb_read; + if (nb_read != blocksize) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error flushing fragmented file, read %d bytes but asked %d bytes\n", nb_read, blocksize)); + } + if (ctx->flush_done==ctx->flush_size) { + gf_filter_pck_set_framing(pck, GF_FALSE, GF_TRUE); + gf_filter_pck_send(pck); + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + gf_filter_pck_set_framing(pck, GF_FALSE, GF_FALSE); + gf_filter_pck_send(pck); + //we are not done flushing but we have no more input packets, signal we still need processing + gf_filter_post_process_task(filter); + return GF_OK; +} + +static void mp4mx_frag_box_patch(GF_MP4MuxCtx *ctx) +{ + GF_Err e; + u32 i, count = gf_list_count(ctx->tracks); + for (i=0; itracks, i); + if (!tkw->track_id) continue; + //no box patched set (todo, do we want to allow changing boxpatch props ?) + if (!tkw->box_patched) continue; + + p = gf_filter_pid_get_property_str(tkw->ipid, "boxpatch"); + if (p && p->value.string) { + e = gf_isom_apply_box_patch(ctx->file, tkw->track_id ? tkw->track_id : tkw->item_id, p->value.string, GF_TRUE); + if (e) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] Unable to apply box patch %s to track fragment %d: %s\n", + p->value.string, tkw->track_id, gf_error_to_string(e) )); + } + } + } + + if (ctx->boxpatch) { + e = gf_isom_apply_box_patch(ctx->file, 0, ctx->boxpatch, GF_TRUE); + if (e) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] Unable to apply box patch %s to fragment: %s\n", ctx->boxpatch, gf_error_to_string(e) )); + } + ctx->box_patched = GF_TRUE; + } +} + + +static GF_Err mp4_mux_initialize(GF_Filter *filter); + +GF_Err mp4mx_reload_output(GF_Filter *filter, GF_MP4MuxCtx *ctx) +{ + GF_Err e; + u32 i, count = gf_list_count(ctx->tracks); + + //done with the file + if (ctx->file) { + e = mp4_mux_done(filter, ctx, GF_FALSE); + if (e) return e; + ctx->file = NULL; + } + ctx->init_movie_done = GF_FALSE; + e = mp4_mux_initialize(filter); + if (e) return e; + ctx->config_timing = GF_TRUE; + + for (i=0; itracks, i); + tkw->suspended = GF_FALSE; + tkw->track_num = 0; + tkw->nb_samples = 0; + e = mp4_mux_configure_pid(filter, tkw->ipid, GF_FALSE); + if (e) return e; + tkw->nb_samples = 0; + tkw->sample.DTS = 0; + tkw->sample.CTS_Offset = 0; + tkw->samples_in_stsd = 0; + tkw->samples_in_frag = 0; + } + assert(ctx->next_file_idx); + ctx->cur_file_idx_plus_one = ctx->next_file_idx; + ctx->next_file_idx = 0; + ctx->notify_filename = GF_TRUE; + assert(!ctx->cur_file_suffix); + if (ctx->next_file_suffix) { + ctx->cur_file_suffix = gf_strdup(ctx->next_file_suffix); + ctx->next_file_suffix = NULL; + } + return GF_OK; +} + + +static GF_Err mp4_mux_process_fragmented(GF_Filter *filter, GF_MP4MuxCtx *ctx) +{ + GF_Err e = GF_OK; + u32 nb_eos, nb_done, nb_suspended, i, count; + + if (ctx->flush_size) { + return mp4_mux_flush_fragmented(filter, ctx); + } + + if (!ctx->file) + return GF_EOS; + + //init movie not yet produced + if (!ctx->init_movie_done) { + e = mp4_mux_initialize_movie(ctx); + if (e) return e; + if (!ctx->init_movie_done) + return GF_OK; + } + /*get count after init, some tracks may have been remove*/ + count = gf_list_count(ctx->tracks); + if (ctx->dash_mode && !ctx->segment_started) { + //don't start a new segment if all pids are in eos + nb_eos=0; + for (i=0; itracks, i); + if (gf_filter_pid_is_eos(tkw->ipid)) { + nb_eos ++; + } + } + if (nb_eos==count) + goto check_eos; + + ctx->segment_started = GF_TRUE; + ctx->insert_tfdt = GF_TRUE; + ctx->insert_pssh = (ctx->psshs == MP4MX_PSSH_MOOF) ? GF_TRUE : GF_FALSE; + + gf_isom_start_segment(ctx->file, ctx->single_file ? NULL : "_gpac_isobmff_redirect", GF_FALSE); + } + + //process pid by pid + nb_eos=0; + nb_done = 0; + nb_suspended = 0; + for (i=0; itracks, i); + + if (ctx->fragment_started && tkw->fragment_done) { + nb_done ++; + continue; + } + if (tkw->suspended) { + if (ctx->fragment_started) nb_done++; + nb_suspended++; + continue; + } + + while (1) { + const GF_PropertyValue *p; + u32 orig_frag_bounds=0; + GF_FilterPacket *pck = gf_filter_pid_get_packet(tkw->ipid); + + if (!pck) { + if (gf_filter_pid_is_eos(tkw->ipid)) { + tkw->fragment_done = GF_TRUE; + if (ctx->dash_mode) ctx->flush_seg = GF_TRUE; + if (ctx->next_file_idx) + nb_suspended++; + nb_done ++; + nb_eos++; + break; + } + return GF_OK; + } + if (tkw->aborted) { + gf_filter_pid_drop_packet(tkw->ipid); + nb_eos++; + nb_done ++; + tkw->fragment_done = GF_TRUE; + if (ctx->dash_mode) ctx->flush_seg = GF_TRUE; + break; + } + + cts = gf_filter_pck_get_cts(pck); + + if (cts == GF_FILTER_NO_TS) { + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_EODS); + if (p && p->value.boolean) { + nb_done ++; + tkw->fragment_done = GF_TRUE; + gf_filter_pid_drop_packet(tkw->ipid); + ctx->flush_seg = GF_TRUE; + tkw->next_seg_cts = tkw->cts_next; + break; + } + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MuxIsom] Packet with no CTS assigned, cannot store to track, ignoring\n")); + gf_filter_pid_drop_packet(tkw->ipid); + continue; + } + + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FRAG_START); + if (p) { + orig_frag_bounds = p->value.uint; + + if (orig_frag_bounds==2) { + if (!ctx->segment_started) { + ctx->dash_mode = 1; + ctx->insert_tfdt = GF_TRUE; + gf_isom_start_segment(ctx->file, ctx->single_file ? NULL : "_gpac_isobmff_redirect", GF_FALSE); + } else if (tkw->samples_in_frag) { + tkw->fragment_done = GF_TRUE; + tkw->samples_in_frag = 0; + nb_done ++; + //make sure we flush until the end of the segment + ctx->flush_seg = GF_TRUE; + //store CTS of next packet (first in next segment) for sidx compute + tkw->next_seg_cts = cts; + } + } + } + + //get dash/file segment number + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM); + + //not dash and file end, we need to wait for all streams and resetup + if (!ctx->dash_mode && p) { + if (!ctx->cur_file_idx_plus_one) { + ctx->cur_file_idx_plus_one = p->value.uint + 1; + if (!ctx->cur_file_suffix) { + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILESUF); + if (p && p->value.string) ctx->cur_file_suffix = gf_strdup(p->value.string); + } + ctx->notify_filename = GF_TRUE; + } else if (ctx->cur_file_idx_plus_one == p->value.uint+1) { + } else if (!tkw->suspended) { + tkw->suspended = GF_TRUE; + nb_suspended++; + ctx->next_file_idx = p->value.uint + 1; + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILESUF); + if (p && p->value.string) + ctx->next_file_suffix = p->value.string; + break; + } + } + + + if (!ctx->fragment_started) { + e = mp4_mux_start_fragment(ctx, orig_frag_bounds ? pck : NULL); + if (e) return e; + + ctx->nb_frags++; + if (ctx->dash_mode) + ctx->nb_frags_in_seg++; + + } + + + if (ctx->dash_mode) { + if (p) { + //start of next segment, abort fragmentation for this track and flush all other writers + if (ctx->dash_seg_num && (ctx->dash_seg_num != p->value.uint) ) { + tkw->fragment_done = GF_TRUE; + tkw->samples_in_frag = 0; + nb_done ++; + //make sure we flush until the end of the segment + ctx->flush_seg = GF_TRUE; + //store CTS of next packet (first in next segment) for sidx compute + tkw->next_seg_cts = cts; + + break; + } + //start of current segment, remember segment number and name + ctx->dash_seg_num = p->value.uint; + //get file name prop if any - only send on one pid for muxed content + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME); + if (p && p->value.string) { + if (ctx->seg_name) gf_free(ctx->seg_name); + ctx->seg_name = gf_strdup(p->value.string); + } + //store PRFT only for reference track at segment start + if (tkw==ctx->ref_tkw) { + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_SENDER_NTP); + if (p) { + gf_isom_set_fragment_reference_time(ctx->file, tkw->track_id, p->value.longuint, cts); + GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MuxIsom] Segment %s, storing NTP TS "LLU" for CTS "LLU" at "LLU" us, at UTC "LLU"\n", ctx->seg_name ? ctx->seg_name : "singlefile", p->value.longuint, cts, gf_sys_clock_high_res(), gf_net_get_utc())); + } + } + } + + dts = gf_filter_pck_get_dts(pck); + if (dts==GF_FILTER_NO_TS) dts = cts; + if (tkw->first_dts_in_seg > dts) + tkw->first_dts_in_seg = dts; + } + ncts = cts + gf_filter_pck_get_duration(pck); + if (tkw->cts_next < ncts) + tkw->cts_next = ncts; + + + if (tkw->samples_in_frag && orig_frag_bounds) { + tkw->fragment_done = GF_TRUE; + nb_done ++; + tkw->samples_in_frag = 0; + tkw->dur_in_frag = 0; + break; + } else if (ctx->fragdur && (!ctx->dash_mode || !tkw->fragment_done) ) { + u32 dur = gf_filter_pck_get_duration(pck); + if (tkw->dur_in_frag && (tkw->dur_in_frag >= ctx->cdur * tkw->src_timescale)) { + tkw->fragment_done = GF_TRUE; + nb_done ++; + tkw->dur_in_frag = 0; + tkw->samples_in_frag = 0; + break; + } + tkw->dur_in_frag += dur; + } else if (!ctx->flush_seg && !ctx->dash_mode + && (cts >= (u64) (ctx->adjusted_next_frag_start * tkw->src_timescale) + tkw->ts_delay) + ) { + GF_FilterSAPType sap = mp4_mux_get_sap(ctx, pck); + if ((ctx->store==MP4MX_MODE_FRAG) || (sap && sapfragment_done = GF_TRUE; + tkw->samples_in_frag = 0; + nb_done ++; + if (ctx->store==MP4MX_MODE_SFRAG) { + ctx->adjusted_next_frag_start = (Double) (cts - tkw->ts_delay); + ctx->adjusted_next_frag_start /= tkw->src_timescale; + } + break; + } + } + if (tkw->insert_tfdt) { + u64 odts = gf_filter_pck_get_dts(pck); + if (odts==GF_FILTER_NO_TS) + odts = gf_filter_pck_get_cts(pck); + + if (tkw->offset_dts) odts += tkw->offset_dts; + + tkw->insert_tfdt = GF_FALSE; + gf_isom_set_traf_base_media_decode_time(ctx->file, tkw->track_id, odts); + tkw->first_dts_in_seg = (u64) odts; + } + + if (ctx->trun_inter) { + GF_FilterSAPType sap = mp4_mux_get_sap(ctx, pck); + s32 tid_group = 0; + if (sap) { + tkw->prev_tid_group = 0; + } else { + s64 dts_diff; + s64 p_dts = gf_filter_pck_get_dts(pck); + s64 p_cts = gf_filter_pck_get_cts(pck); + s64 cts_o = p_cts - p_dts; + dts_diff = p_dts - tkw->sample.DTS; + tid_group = (s32) (cts_o / dts_diff); + tid_group = 20 - tid_group; + if (tid_group != tkw->prev_tid_group) { + tkw->prev_tid_group = tid_group; + gf_isom_set_fragment_option(ctx->file, tkw->track_id, GF_ISOM_TRUN_SET_INTERLEAVE_ID, tid_group); + } + } + } + + //process packet + e = mp4_mux_process_sample(ctx, tkw, pck, GF_TRUE); + + //discard + gf_filter_pid_drop_packet(tkw->ipid); + + if (e) return e; + } + //done with this track - if single track per moof, request new fragment but don't touch the + //fragmentation state of the track writers + if (ctx->straf && (i+1 < count)) { + GF_ISOStartFragmentFlags flags = 0; + if (ctx->moof_first) flags |= GF_ISOM_FRAG_MOOF_FIRST; +#ifdef GF_ENABLE_CTRN + if (ctx->ctrn) flags |= GF_ISOM_FRAG_USE_COMPACT; +#endif + e = gf_isom_start_fragment(ctx->file, flags); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Unable to start new fragment: %s\n", gf_error_to_string(e) )); + return e; + } + if (ctx->sdtp_traf) + gf_isom_set_fragment_option(ctx->file, tkw->track_id, GF_ISOM_TRAF_USE_SAMPLE_DEPS_BOX, ctx->sdtp_traf); + } + } + + //all suspended tracks done, flush fragment + if (nb_suspended && (nb_suspended==count)) { + nb_done = count; + } + + + if (nb_done==count) { + Bool is_eos = (count == nb_eos) ? GF_TRUE : GF_FALSE; + Double ref_start; + u64 next_ref_ts = ctx->ref_tkw->next_seg_cts; + if (is_eos) + next_ref_ts = ctx->ref_tkw->cts_next; + ref_start = (Double) next_ref_ts; + ref_start /= ctx->ref_tkw->src_timescale; + + ctx->next_frag_start += ctx->cdur; + while (ctx->next_frag_start <= ctx->adjusted_next_frag_start) { + ctx->next_frag_start += ctx->cdur; + } + ctx->adjusted_next_frag_start = ctx->next_frag_start; + ctx->fragment_started = GF_FALSE; + + mp4mx_frag_box_patch(ctx); + + //end of DASH segment + if (ctx->dash_mode && (ctx->flush_seg || is_eos) ) { + u64 offset = ctx->single_file ? ctx->current_offset : 0; + u64 idx_start_range, idx_end_range, segment_size_in_bytes; + s32 subs_sidx = -1; + u32 track_ref_id = 0; + + idx_start_range = idx_end_range = 0; + if (ctx->subs_sidx>=0) { + subs_sidx = ctx->subs_sidx; + track_ref_id = ctx->ref_tkw->track_id; + } + if (ctx->cloned_sidx && (ctx->subs_sidx!=-2) ) { + subs_sidx = (s32) ctx->cloned_sidx->nb_refs; + track_ref_id = ctx->cloned_sidx->reference_ID; + gf_isom_box_del((GF_Box *)ctx->cloned_sidx); + ctx->cloned_sidx = NULL; + } + + e = gf_isom_close_segment(ctx->file, subs_sidx, track_ref_id, ctx->ref_tkw->first_dts_in_seg, ctx->ref_tkw->ts_delay, next_ref_ts, ctx->chain_sidx, ctx->ssix, ctx->sseg ? GF_FALSE : is_eos, GF_FALSE, ctx->eos_marker, &idx_start_range, &idx_end_range, &segment_size_in_bytes); + if (e) return e; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MP4Mux] Done writing segment %d - estimated next fragment times start %g end %g\n", ctx->dash_seg_num, ref_start, ctx->next_frag_start )); + + if (ctx->dash_mode != MP4MX_DASH_VOD) { + mp4_mux_flush_frag(ctx, GF_FALSE, offset + idx_start_range, idx_end_range ? offset + idx_end_range : 0); + } else if (ctx->vodcache==MP4MX_VODCACHE_REPLACE) { + mp4_mux_flush_frag(ctx, GF_FALSE, 0, 0); + } else { + if (ctx->nb_seg_sizes == ctx->alloc_seg_sizes) { + ctx->alloc_seg_sizes *= 2; + if (!ctx->alloc_seg_sizes) ctx->alloc_seg_sizes = 10; + ctx->seg_sizes = gf_realloc(ctx->seg_sizes, sizeof(u32) * ctx->alloc_seg_sizes); + } + assert(segment_size_in_bytes); + ctx->seg_sizes[ctx->nb_seg_sizes] = (u32) segment_size_in_bytes; + ctx->nb_seg_sizes++; + } + } + //cannot flush in DASH mode if using sidx (vod single sidx or live 1 sidx/seg) + else if (!ctx->dash_mode || ((ctx->subs_sidx<0) && (ctx->dash_modecloned_sidx) ) { + gf_isom_flush_fragments(ctx->file, GF_FALSE); + + if (!ctx->dash_mode || ctx->flush_seg) + mp4_mux_flush_frag(ctx, GF_FALSE, 0, 0); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[MP4Mux] Done writing fragment - next fragment start time %g\n", ctx->next_frag_start )); + } + + if (ctx->flush_seg) { + ctx->segment_started = GF_FALSE; + ctx->flush_seg = GF_FALSE; + ctx->dash_seg_num = 0; + ctx->nb_segs++; + ctx->nb_frags_in_seg=0; + } + } + + if (nb_suspended && (nb_suspended==count)) { + ctx->nb_segs=0; + return mp4mx_reload_output(filter, ctx); + } + +check_eos: + if (count == nb_eos) { + if (ctx->dash_mode==MP4MX_DASH_VOD) { + if (ctx->vodcache!=MP4MX_VODCACHE_ON) { + ctx->final_sidx_flush = GF_TRUE; + //flush SIDX in given space - this will reserve 8 bytes for free box if not fitting + gf_isom_flush_sidx(ctx->file, ctx->sidx_max_size, ctx->sidx_size_exact); + } else { + u64 start_offset; + //reenable packet dispatch + ctx->store_output = GF_FALSE; + gf_isom_flush_sidx(ctx->file, 0, GF_FALSE); + //flush sidx packet + mp4mux_send_output(ctx); + + mp4_mux_flush_frag(ctx, GF_TRUE, ctx->current_offset, ctx->current_offset + ctx->current_size - 1); + + ctx->flush_size = gf_ftell(ctx->tmp_store); + ctx->flush_done = 0; + gf_fseek(ctx->tmp_store, 0, SEEK_SET); + + if (ctx->seg_sizes) { + start_offset = ctx->current_offset; + for (i=0; inb_seg_sizes; i++) { + ctx->current_size = ctx->seg_sizes[i]; + mp4_mux_flush_frag(ctx, GF_FALSE, 0, 0); + } + ctx->current_offset = start_offset; + ctx->current_size = 0; + + gf_free(ctx->seg_sizes); + ctx->seg_sizes = NULL; + ctx->alloc_seg_sizes = ctx->nb_seg_sizes = 0; + } + } + } + //only destroy file if not dash or not onDemand, otherwise (regular dash) the file will be needed to append further segments + if (ctx->dash_mode!=MP4MX_DASH_ON) { + //only delete file in vod mode + if (ctx->file) { + gf_isom_close(ctx->file); + ctx->file = NULL; + } + } + + mp4mux_send_output(ctx); + + if (!ctx->flush_size) gf_filter_pid_set_eos(ctx->opid); + + return ctx->flush_size ? GF_OK : GF_EOS; + } + + return GF_OK; +} + +static void mp4_mux_config_timing(GF_MP4MuxCtx *ctx) +{ + u32 i, count = gf_list_count(ctx->tracks); + //compute min dts of first packet on each track - this assume all tracks are synchronized, might need adjustment for MPEG4 Systems + u64 first_ts_min = (u64) -1; + for (i=0; itracks, i); + if (tkw->fake_track) continue; + pck = gf_filter_pid_get_packet(tkw->ipid); + //check this after fetching a packet since it may reconfigure the track + if (!tkw->track_num) { + if (gf_filter_pid_is_eos(tkw->ipid)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] PID has no input packet and configuration not known after 10 retries, aborting initial timing sync\n")); + continue; + } + return; + } + if (!pck) { + if (gf_filter_pid_is_eos(tkw->ipid)) { + if (tkw->cenc_state==CENC_NEED_SETUP) + mp4_mux_cenc_update(ctx, tkw, NULL, CENC_CONFIG, 0); + + if (!tkw->nb_samples) { + const GF_PropertyValue *p = gf_filter_pid_get_property(tkw->ipid, GF_PROP_PID_ISOM_TREX_TEMPLATE); + if (p) { + gf_isom_setup_track_fragment_template(ctx->file, tkw->track_id, p->value.data.ptr, p->value.data.size, ctx->nofragdef); + } + } + continue; + } + return; + } + //we may have reorder tracks after the get_packet, redo + if (gf_list_find(ctx->tracks, tkw) != i) { + mp4_mux_config_timing(ctx); + return; + } + ts = gf_filter_pck_get_dts(pck); + if (ts==GF_FILTER_NO_TS) + ts = gf_filter_pck_get_cts(pck); + if (ts==GF_FILTER_NO_TS) + ts=0; + + dts_min = ts * 1000000; + dts_min /= tkw->src_timescale; + + if (first_ts_min > dts_min) { + first_ts_min = (u64) dts_min; + } + tkw->ts_shift = ts; + } + if (first_ts_min==(u64)-1) + first_ts_min = 0; + + //for all packets with dts greater than min dts, we need to add a pause + for (i=0; itracks, i); + + //compute offsets + dts_diff = first_ts_min; + dts_diff *= tkw->src_timescale; + dts_diff /= 1000000; + dts_diff = (s64) tkw->ts_shift - dts_diff; + if (ctx->is_rewind) dts_diff = -dts_diff; + //negative could happen due to rounding, ignore them + if (dts_diff<=0) continue; + + // dts_diff > 0, we need to delay the track + dur = dts_diff; + dur *= (u32) ctx->moovts; + dur /= tkw->src_timescale; + if (dur) { + gf_isom_remove_edits(ctx->file, tkw->track_num); + + gf_isom_set_edit(ctx->file, tkw->track_num, 0, dur, dts_diff, GF_ISOM_EDIT_EMPTY); + gf_isom_set_edit(ctx->file, tkw->track_num, dur, 0, 0, GF_ISOM_EDIT_NORMAL); + tkw->empty_init_dur = (u64) dur; + } + } + + ctx->config_timing = GF_FALSE; +} + +void mp4_mux_format_report(GF_Filter *filter, GF_MP4MuxCtx *ctx, u64 done, u64 total) +{ + Bool status_changed=GF_FALSE; + u32 total_pc = 0; + char szStatus[2048], szTK[20]; + if (!gf_filter_reporting_enabled(filter)) + return; + if (!ctx->update_report) + return; + + ctx->update_report = GF_FALSE; + + if (ctx->config_timing) { + sprintf(szStatus, "waiting for clock init"); + status_changed = GF_TRUE; + } else if (total) { + if (done>=total) { + Double ohead = 0; + if (ctx->total_bytes_in) ohead = ((Double) (ctx->total_bytes_out - ctx->total_bytes_in)*100 / ctx->total_bytes_in); + + sprintf(szStatus, "done %d samples - bytes "LLU" in "LLU" out - overhead %02.02f%% (%02.02g B/sample)", ctx->total_samples, ctx->total_bytes_in, ctx->total_bytes_out, ohead, ((Double)(ctx->total_bytes_out-ctx->total_bytes_in))/ctx->total_samples); + status_changed = GF_TRUE; + total_pc = 10000; + + } else { + u32 pc = (u32) ((done*10000)/total); + if (ctx->last_mux_pc == pc + 1) return; + ctx->last_mux_pc = pc + 1; + sprintf(szStatus, "mux %d%%", pc); + status_changed = GF_TRUE; + } + } else { + u32 i, count = gf_list_count(ctx->tracks); + Bool is_frag = GF_FALSE; + + if (ctx->store>=MP4MX_MODE_FRAG) { + is_frag = GF_TRUE; + if (ctx->dash_mode) { + sprintf(szStatus, "mux segments %d (frags %d) next %02.02g", ctx->nb_segs, ctx->nb_frags_in_seg, ctx->next_frag_start); + } else { + sprintf(szStatus, "mux frags %d next %02.02g", ctx->nb_frags, ctx->next_frag_start); + } + } else { + sprintf(szStatus, "%s", ((ctx->store==MP4MX_MODE_FLAT) || (ctx->store==MP4MX_MODE_FASTSTART)) ? "mux" : "import"); + } + for (i=0; itracks, i); + if (tkw->aborted) { + pc=10000; + } else if (ctx->idur.num) { + if (ctx->idur.num>0) { + u64 mdur = gf_isom_get_media_duration(ctx->file, tkw->track_num); + u64 tk_done = mdur * ctx->idur.den; + u64 tk_total = ((u64)tkw->tk_timescale) * ctx->idur.num; + pc = (u32) ((tk_done*10000)/tk_total); + } else { + pc = (u32) ( (10000 * (u64) tkw->nb_samples) / (-ctx->idur.num) ); + } + } else { + if (tkw->nb_frames) { + pc = (u32) ( (10000 * (u64) tkw->nb_samples) / tkw->nb_frames); + } else { + if (tkw->pid_dur.num && tkw->pid_dur.den) { + pc = (u32) ((tkw->sample.DTS*10000 * tkw->pid_dur.den) / (tkw->pid_dur.num * tkw->tk_timescale)); + } else if (tkw->down_bytes && tkw->down_size) { + pc = (u32) (((tkw->down_bytes*10000) / tkw->down_size)); + } + } + } + if (pc>10000) + pc=0; + if (tkw->last_import_pc != pc + 1) { + status_changed = GF_TRUE; + tkw->last_import_pc = pc + 1; + } + if (!total_pc || (total_pc > pc)) + total_pc = pc; + + if (is_frag) { + sprintf(szTK, " TK%d(%c): %d", tkw->track_id, tkw->status_type, tkw->samples_in_frag); + strcat(szStatus, szTK); + status_changed = GF_TRUE; + if (pc) { + sprintf(szTK, " %d %%", pc/100); + strcat(szStatus, szTK); + } + } else { + sprintf(szTK, " %s%d(%c): %d %%", tkw->is_item ? "IT" : "TK", tkw->track_id, tkw->status_type, pc/100); + strcat(szStatus, szTK); + } + } + } + if (status_changed) { + gf_filter_update_status(filter, total_pc, szStatus); + } +} + + +GF_Err mp4_mux_process(GF_Filter *filter) +{ + GF_MP4MuxCtx *ctx = gf_filter_get_udta(filter); + u32 nb_skip, nb_eos, nb_suspended, i, count = gf_list_count(ctx->tracks); + nb_skip = 0; + nb_eos = 0; + + if (ctx->config_timing) { + mp4_mux_config_timing(ctx); + if (ctx->config_timing) { + mp4_mux_format_report(filter, ctx, 0, 0); + return GF_OK; + } + } + + //in frag mode, force fetching a sample from each track before processing + if (ctx->store>=MP4MX_MODE_FRAG) { + u32 done=0; + GF_Err e = mp4_mux_process_fragmented(filter, ctx); + if (e==GF_EOS) done=100; + mp4_mux_format_report(filter, ctx, done, done); + return e; + } + + nb_suspended = 0; + for (i=0; itracks, i); + GF_FilterPacket *pck = gf_filter_pid_get_packet(tkw->ipid); + + if (tkw->suspended) { + nb_suspended++; + continue; + } + + if (!pck) { + if (gf_filter_pid_is_eos(tkw->ipid)) { + tkw->suspended = GF_FALSE; + nb_eos++; + } + continue; + } + + if (tkw->aborted) { + gf_filter_pid_drop_packet(tkw->ipid); + nb_eos++; + continue; + } + + if (ctx->owns_mov) { + const GF_PropertyValue *p; + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM); + if (p) { + if (!ctx->cur_file_idx_plus_one) { + ctx->cur_file_idx_plus_one = p->value.uint + 1; + if (!ctx->cur_file_suffix) { + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILESUF); + if (p && p->value.string) ctx->cur_file_suffix = gf_strdup(p->value.string); + } + ctx->notify_filename = GF_TRUE; + } else if (ctx->cur_file_idx_plus_one == p->value.uint+1) { + } else if (!tkw->suspended) { + tkw->suspended = GF_TRUE; + nb_suspended++; + ctx->next_file_idx = p->value.uint + 1; + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILESUF); + if (p && p->value.string) + ctx->next_file_suffix = p->value.string; + continue; + } + } + } + + //basic regulation in case we do on-the-fly interleaving + //we need to regulate because sources do not produce packets at the same rate + if ((ctx->store==MP4MX_MODE_FASTSTART) && ctx->cdur) { + Double cts = (Double) gf_filter_pck_get_cts(pck); + if (ctx->is_rewind) + cts = tkw->ts_shift - cts; + else + cts -= tkw->ts_shift; + cts /= tkw->src_timescale; + if (!ctx->faststart_ts_regulate) { + ctx->faststart_ts_regulate = ctx->cdur; + } + //ahead of our interleaving window, don't write yet + else if (cts > ctx->faststart_ts_regulate) { + nb_skip++; + continue; + } + } + + if (tkw->cenc_state==CENC_NEED_SETUP) + mp4_mux_cenc_update(ctx, tkw, pck, CENC_CONFIG, 0); + + if (tkw->is_item) { + mp4_mux_process_item(ctx, tkw, pck); + } else { + mp4_mux_process_sample(ctx, tkw, pck, GF_FALSE); + } + + gf_filter_pid_drop_packet(tkw->ipid); + if (tkw->aborted) { + nb_eos++; + } + } + mp4_mux_format_report(filter, ctx, 0, 0); + + if (nb_suspended && (nb_suspended+nb_eos==count)) { + return mp4mx_reload_output(filter, ctx); + } + + if (count == nb_eos) { + if (ctx->file) { + GF_Err e = mp4_mux_done(filter, ctx, GF_TRUE); + if (e) return e; + } + return GF_EOS; + } + //done with this interleaving window, start next one + else if (nb_skip + nb_eos == count) { + ctx->faststart_ts_regulate += ctx->cdur; + } else if (ctx->importer) { + u64 prog_done=0, prog_total=0; + for (i=0; itracks, i); + if (prog_done * tkw->prog_total <= tkw->prog_done * prog_total) { + prog_done = tkw->prog_done; + prog_total = tkw->prog_total; + } + } + gf_set_progress("Import", prog_done, prog_total); + } + + return GF_OK; +} + +static GF_Err mp4_mux_on_data_patch(void *cbk, u8 *data, u32 block_size, u64 file_offset, Bool is_insert) +{ + GF_Filter *filter = (GF_Filter *) cbk; + u8 *output; + GF_MP4MuxCtx *ctx = gf_filter_get_udta(filter); + + GF_FilterPacket *pck = gf_filter_pck_new_alloc(ctx->opid, block_size, &output); + memcpy(output, data, block_size); + gf_filter_pck_set_framing(pck, GF_FALSE, GF_FALSE); + gf_filter_pck_set_seek_flag(pck, GF_TRUE); + if (is_insert) + gf_filter_pck_set_interlaced(pck, 1); + gf_filter_pck_set_byte_offset(pck, file_offset); + gf_filter_pck_send(pck); + return GF_OK; +} + +static GF_Err mp4_mux_on_data(void *cbk, u8 *data, u32 block_size) +{ + GF_Filter *filter = (GF_Filter *) cbk; + u8 *output; + GF_MP4MuxCtx *ctx = gf_filter_get_udta(filter); + + ctx->total_bytes_out += block_size; + + //flush pending packet in frag mode + mp4mux_send_output(ctx); + + if (ctx->final_sidx_flush) { + GF_FilterPacket *pck; + u32 free_size=0; + + if (ctx->vodcache==MP4MX_VODCACHE_INSERT) { + pck = gf_filter_pck_new_alloc(ctx->opid, block_size, &output); + memcpy(output, data, block_size); + gf_filter_pck_set_framing(pck, GF_FALSE, GF_FALSE); + gf_filter_pck_set_byte_offset(pck, ctx->sidx_chunk_offset); + gf_filter_pck_set_seek_flag(pck, GF_TRUE); + gf_filter_pck_set_interlaced(pck, 1); + gf_filter_pck_send(pck); + } else { + GF_BitStream *bs; + assert(!ctx->dst_pck); + + if (block_size > ctx->sidx_max_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Final SIDX chunk larger than preallocated block, will not flush SIDX (output file still readable). Try disabling nocache mode\n")); + return GF_OK; + } + free_size = ctx->sidx_max_size - block_size; + pck = gf_filter_pck_new_alloc(ctx->opid, ctx->sidx_max_size, &output); + gf_filter_pck_set_framing(pck, GF_FALSE, GF_FALSE); + gf_filter_pck_set_byte_offset(pck, ctx->sidx_chunk_offset); + gf_filter_pck_set_seek_flag(pck, GF_TRUE); + bs = gf_bs_new(output, ctx->sidx_max_size, GF_BITSTREAM_WRITE); + if (free_size) { + gf_bs_write_u32(bs, free_size); + gf_bs_write_u32(bs, GF_ISOM_BOX_TYPE_FREE); + gf_bs_skip_bytes(bs, free_size-8); + } + gf_bs_write_data(bs, data, block_size); + gf_bs_del(bs); + gf_filter_pck_send(pck); + } + mp4_mux_flush_frag(ctx, GF_TRUE, ctx->sidx_chunk_offset+free_size, ctx->sidx_chunk_offset+free_size + block_size - 1); + return GF_OK; + } + + if (ctx->store_output) { + u32 nb_write = (u32) gf_fwrite(data, block_size, ctx->tmp_store); + if (nb_write != block_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Error writing to temp cache: %d bytes write instead of %d\n", nb_write, block_size)); + return GF_IO_ERR; + } + return GF_OK; + } + + //allocate new one + ctx->dst_pck = gf_filter_pck_new_alloc(ctx->opid, block_size, &output); + if (!ctx->dst_pck) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Cannot allocate output packets, lost %d bytes\n", block_size)); + } else { + memcpy(output, data, block_size); + gf_filter_pck_set_framing(ctx->dst_pck, !ctx->first_pck_sent, GF_FALSE); + } + //set packet prop as string since we may discard the seg_name packet before this packet is processed + if (!ctx->first_pck_sent && ctx->seg_name) { + ctx->current_offset = 0; + gf_filter_pck_set_property(ctx->dst_pck, GF_PROP_PCK_FILENAME, &PROP_STRING(ctx->seg_name) ); + } + ctx->first_pck_sent = GF_TRUE; + ctx->current_size += block_size; + //non-frag mode, send right away + if (ctx->storeupdate_report = GF_TRUE; + mp4_mux_format_report(filter, ctx, done, total); +} + +static GF_Err mp4_mux_initialize(GF_Filter *filter) +{ + GF_MP4MuxCtx *ctx = gf_filter_get_udta(filter); + gf_filter_set_max_extra_input_pids(filter, -1); + + if (ctx->file) { + if (gf_isom_get_mode(ctx->file) < GF_ISOM_OPEN_WRITE) return GF_BAD_PARAM; + if (ctx->store>=MP4MX_MODE_FRAG) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Cannot use fragmented output on already opened ISOBMF file\n")); + return GF_BAD_PARAM; + } + ctx->owns_mov = GF_FALSE; + } else { + u32 open_mode = GF_ISOM_OPEN_WRITE; + ctx->owns_mov = GF_TRUE; + + switch (ctx->store) { + case MP4MX_MODE_INTER: + case MP4MX_MODE_TIGHT: + open_mode = GF_ISOM_WRITE_EDIT; + break; + } + ctx->file = gf_isom_open("_gpac_isobmff_redirect", open_mode, ctx->tmpd); + if (!ctx->file) return GF_OUT_OF_MEM; + + gf_isom_set_write_callback(ctx->file, mp4_mux_on_data, mp4_mux_on_data_patch, filter, ctx->block_size); + + gf_isom_set_progress_callback(ctx->file, mp4_mux_progress_cbk, filter); + + if (ctx->dref && (ctx->store>=MP4MX_MODE_FRAG)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] Cannot use data reference in movie fragments, not supported. Ignoring it\n")); + ctx->dref = GF_FALSE; + } + + if (ctx->store==MP4MX_MODE_FASTSTART) { + gf_isom_set_storage_mode(ctx->file, GF_ISOM_STORE_FASTSTART); + } + + } + if (!ctx->moovts) ctx->moovts=600; + + if (ctx->mfra && (ctx->store>=MP4MX_MODE_FRAG)) { + GF_Err e = gf_isom_enable_mfra(ctx->file); + if (e) return e; + } + + if (!ctx->tracks) + ctx->tracks = gf_list_new(); + +#ifdef GF_ENABLE_CTRN + if (ctx->ctrni) + ctx->ctrn = GF_TRUE; +#endif + + if (ctx->m4cc) { + if (strlen(ctx->m4cc)==4) + ctx->eos_marker = GF_4CC(ctx->m4cc[0], ctx->m4cc[1], ctx->m4cc[2], ctx->m4cc[3]); + else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] Invalid segment marker 4cc %s, ignoring\n", ctx->m4cc)); + } + } + if (ctx->compress) + gf_isom_enable_compression(ctx->file, ctx->compress, ctx->fcomp); + return GF_OK; +} + + +static void mp4_mux_update_edit_list_for_bframes(GF_MP4MuxCtx *ctx, TrackWriter *tkw) +{ + u32 i, count, di; + u64 max_cts, min_cts, doff; + s64 moffset; + + if (ctx->ctmode > MP4MX_CT_EDIT) return; + + //if we have a complex edit list (due to track template), don't override + if (gf_isom_get_edit_list_type(ctx->file, tkw->track_num, &moffset)) return; + + gf_isom_remove_edits(ctx->file, tkw->track_num); + + + count = gf_isom_get_sample_count(ctx->file, tkw->track_num); + max_cts = 0; + min_cts = (u64) -1; + for (i=0; ifile, tkw->track_num, i+1, &di, &doff); + if (!s) return; + + if (s->DTS + s->CTS_Offset > max_cts) + max_cts = s->DTS + s->CTS_Offset; + + if (min_cts > s->DTS + s->CTS_Offset) + min_cts = s->DTS + s->CTS_Offset; + + gf_isom_sample_del(&s); + } + + if (min_cts || tkw->empty_init_dur) { + max_cts -= min_cts; + max_cts += gf_isom_get_sample_duration(ctx->file, tkw->track_num, count); + + max_cts *= ctx->moovts; + max_cts /= tkw->tk_timescale; + if (tkw->empty_init_dur) { + + gf_isom_set_edit(ctx->file, tkw->track_num, 0, tkw->empty_init_dur, 0, GF_ISOM_EDIT_EMPTY); + if (max_cts >= tkw->empty_init_dur) max_cts -= tkw->empty_init_dur; + else max_cts = 0; + } + + gf_isom_set_edit(ctx->file, tkw->track_num, tkw->empty_init_dur, max_cts, min_cts, GF_ISOM_EDIT_NORMAL); + } +} + +//todo: move from media_import.c to here once done +void gf_media_update_bitrate(GF_ISOFile *file, u32 track); + + +static void mp4_mux_set_hevc_groups(GF_MP4MuxCtx *ctx, TrackWriter *tkw) +{ + u32 avc_base_track, hevc_base_track, ref_track_id; + avc_base_track = hevc_base_track = 0; + u32 i; + GF_PropertyEntry *pe=NULL; + const GF_PropertyValue *p = gf_filter_pid_get_info_str(tkw->ipid, "hevc:oinf", &pe); + if (p) { + u32 gi=0; + gf_isom_add_sample_group_info(ctx->file, tkw->track_num, GF_ISOM_SAMPLE_GROUP_OINF, p->value.data.ptr, p->value.data.size, GF_TRUE, &gi); + + p = gf_filter_pid_get_info_str(tkw->ipid, "hevc:linf", &pe); + if (p) { + gf_isom_add_sample_group_info(ctx->file, tkw->track_num, GF_ISOM_SAMPLE_GROUP_LINF, p->value.data.ptr, p->value.data.size, GF_TRUE, &gi); + gf_isom_set_track_group(ctx->file, tkw->track_num, 1000+gf_isom_get_track_id(ctx->file, tkw->track_num), GF_ISOM_BOX_TYPE_CSTG, GF_TRUE); + } + } + + gf_filter_release_property(pe); + + p = gf_filter_pid_get_property_str(tkw->ipid, "hevc:min_lid"); + if ((!p || !p->value.uint) && (tkw->codecid!=GF_CODECID_LHVC)) { + return; + } + //set linf + for (i=0; i < gf_isom_get_track_count(ctx->file); i++) { + u32 subtype = gf_isom_get_media_subtype(ctx->file, i+1, 1); + switch (subtype) { + case GF_ISOM_SUBTYPE_AVC_H264: + case GF_ISOM_SUBTYPE_AVC2_H264: + case GF_ISOM_SUBTYPE_AVC3_H264: + case GF_ISOM_SUBTYPE_AVC4_H264: + if (!avc_base_track) { + avc_base_track = i+1; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] Warning: More than one AVC bitstream found, use track %d as base layer", avc_base_track)); + } + break; + case GF_ISOM_SUBTYPE_HVC1: + case GF_ISOM_SUBTYPE_HEV1: + case GF_ISOM_SUBTYPE_HVC2: + case GF_ISOM_SUBTYPE_HEV2: + if (!hevc_base_track) { + hevc_base_track = i+1; + if (avc_base_track) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] Warning: Found both AVC and HEVC tracks, using HEVC track %d as base layer\n", hevc_base_track)); + } + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] Warning: More than one HEVC bitstream found, use track %d as base layer\n", avc_base_track)); + } + break; + } + } + if (!hevc_base_track && !avc_base_track) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] Using LHVC external base layer, but no base layer not found - NOT SETTING SBAS TRACK REFERENCE!\n")); + } else { + ref_track_id = gf_isom_get_track_id(ctx->file, hevc_base_track ? hevc_base_track : avc_base_track); + gf_isom_set_track_reference(ctx->file, tkw->track_num, GF_ISOM_REF_BASE, ref_track_id); + + if (hevc_base_track) { + ref_track_id = gf_isom_get_track_id(ctx->file, hevc_base_track); + gf_isom_set_track_reference(ctx->file, tkw->track_num, GF_ISOM_REF_OREF, ref_track_id); + } + } +} + +static GF_Err mp4_mux_done(GF_Filter *filter, GF_MP4MuxCtx *ctx, Bool is_final) +{ + GF_Err e = GF_OK; + u32 i, count; + GF_PropertyEntry *pe=NULL; + + count = gf_list_count(ctx->tracks); + for (i=0; itracks, i); + + if (tkw->min_neg_ctts<0) { + //use ctts v1 negative offsets + if (ctx->ctmode==MP4MX_CT_NEGCTTS) { + gf_isom_set_ctts_v1(ctx->file, tkw->track_num, (u32) -tkw->min_neg_ctts); + } + //ctts v0 + else { + gf_isom_set_cts_packing(ctx->file, tkw->track_num, GF_TRUE); + gf_isom_shift_cts_offset(ctx->file, tkw->track_num, (s32) tkw->min_neg_ctts); + gf_isom_set_cts_packing(ctx->file, tkw->track_num, GF_FALSE); + gf_isom_set_composition_offset_mode(ctx->file, tkw->track_num, GF_FALSE); + + mp4_mux_update_edit_list_for_bframes(ctx, tkw); + } + has_bframes = GF_TRUE; + } else if (tkw->has_ctts && (tkw->stream_type==GF_STREAM_VISUAL)) { + mp4_mux_update_edit_list_for_bframes(ctx, tkw); + + has_bframes = GF_TRUE; + } else if (tkw->ts_delay || tkw->empty_init_dur) { + gf_isom_update_edit_list_duration(ctx->file, tkw->track_num); + } + + if (ctx->importer && ctx->idur.num && ctx->idur.den) { + u64 mdur = gf_isom_get_media_duration(ctx->file, tkw->track_num); + u64 pdur = gf_isom_get_track_duration(ctx->file, tkw->track_num); + if (pdur==mdur) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("[MP4Mux] Imported %d frames - duration %g\n", tkw->nb_samples, ((Double)mdur)/tkw->tk_timescale )); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("[MP4Mux] Imported %d frames - media duration %g - track duration %g\n", tkw->nb_samples, ((Double)mdur)/tkw->tk_timescale, ((Double)pdur)/ctx->moovts )); + } + } + + /*this is plain ugly but since some encoders (divx) don't use the video PL correctly + we force the system video_pl to ASP@L5 since we have I, P, B in base layer*/ + if (tkw->codecid == GF_CODECID_MPEG4_PART2) { + Bool force_rewrite = GF_FALSE; + u32 PL = tkw->media_profile_level; + if (!PL) PL = 0x01; + + if (ctx->importer) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Indicated Profile: %s\n", gf_m4v_get_profile_name((u8) PL) )); + } + + if (has_bframes && (tkw->media_profile_level <= 3)) { + PL = 0xF5; + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[MP4Mux] Indicated profile doesn't include B-VOPs - forcing %s", gf_m4v_get_profile_name((u8) PL) )); + force_rewrite = GF_TRUE; + } + if (PL != tkw->media_profile_level) { + if (force_rewrite) { +#ifndef GPAC_DISABLE_AV_PARSERS + GF_ESD *esd = gf_isom_get_esd(ctx->file, tkw->track_num, tkw->stsd_idx); + assert(esd); + gf_m4v_rewrite_pl(&esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength, (u8) PL); + gf_isom_change_mpeg4_description(ctx->file, tkw->track_num, tkw->stsd_idx, esd); + gf_odf_desc_del((GF_Descriptor*)esd); +#endif + + } + if (!ctx->make_qt) + gf_isom_set_pl_indication(ctx->file, GF_ISOM_PL_VISUAL, PL); + } + } + + + if (tkw->has_append) + gf_isom_refresh_size_info(ctx->file, tkw->track_num); + + if ((tkw->nb_samples == 1) && (ctx->idur.num>0) && ctx->idur.den) { + u32 dur = tkw->tk_timescale * ctx->idur.num; + dur /= ctx->idur.den; + gf_isom_set_last_sample_duration(ctx->file, tkw->track_num, dur); + } + + if (tkw->has_open_gop) { + if (ctx->importer) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("OpenGOP detected - adjusting file brand\n")); + } + gf_isom_modify_alternate_brand(ctx->file, GF_ISOM_BRAND_ISO6, GF_TRUE); + } + + mp4_mux_set_hevc_groups(ctx, tkw); + + p = gf_filter_pid_get_info_str(tkw->ipid, "ttxt:rem_last", &pe); + if (p && p->value.boolean) + gf_isom_remove_sample(ctx->file, tkw->track_num, tkw->nb_samples); + + p = gf_filter_pid_get_info_str(tkw->ipid, "ttxt:last_dur", &pe); + if (p) + gf_isom_set_last_sample_duration(ctx->file, tkw->track_num, p->value.uint); + + if (tkw->is_nalu && ctx->pack_nal && (gf_isom_get_mode(ctx->file)!=GF_ISOM_OPEN_WRITE)) { + u32 msize = 0; + Bool do_rewrite = GF_FALSE; + u32 j, stsd_count = gf_isom_get_sample_description_count(ctx->file, tkw->track_num); + p = gf_filter_pid_get_info(tkw->ipid, GF_PROP_PID_MAX_NALU_SIZE, &pe); + msize = gf_get_bit_size(p->value.uint); + if (msize<8) msize = 8; + else if (msize<16) msize = 16; + else msize = 32; + + if (msize<=0xFFFF) { + for (j=0; jfile, tkw->track_num, j+1); + if (k > msize) { + do_rewrite = GF_TRUE; + } + } + if (do_rewrite) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("[MP4Mux] Adjusting NALU SizeLength to %d bits\n", msize )); + gf_media_nal_rewrite_samples(ctx->file, tkw->track_num, msize); + msize /= 8; + for (j=0; jfile, tkw->track_num, j+1, msize); + } + } + } + } + + //don't update bitrate info for single sample tracks, unless MPEG-4 Systems - compatibility with old arch + if (ctx->btrt && !tkw->skip_bitrate_update && ((tkw->nb_samples>1) || ctx->m4sys) ) + gf_media_update_bitrate(ctx->file, tkw->track_num); + + if (!tkw->box_patched) { + p = gf_filter_pid_get_property_str(tkw->ipid, "boxpatch"); + if (p && p->value.string) { + e = gf_isom_apply_box_patch(ctx->file, tkw->track_id ? tkw->track_id : tkw->item_id, p->value.string, GF_FALSE); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Unable to apply box patch %s to track %d: %s\n", + p->value.string, tkw->track_id, gf_error_to_string(e) )); + } + } + tkw->box_patched = GF_TRUE; + } + } + + gf_filter_release_property(pe); + + if (ctx->boxpatch && !ctx->box_patched) { + e = gf_isom_apply_box_patch(ctx->file, 0, ctx->boxpatch, GF_FALSE); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Unable to apply box patch %s: %s\n", ctx->boxpatch, gf_error_to_string(e) )); + } + ctx->box_patched = GF_TRUE; + } + + + if (ctx->owns_mov) { + switch (ctx->store) { + case MP4MX_MODE_INTER: + if (ctx->cdur==0) { + e = gf_isom_set_storage_mode(ctx->file, GF_ISOM_STORE_STREAMABLE); + } else { + e = gf_isom_make_interleave(ctx->file, ctx->cdur); + } + break; + case MP4MX_MODE_FLAT: + e = gf_isom_set_storage_mode(ctx->file, GF_ISOM_STORE_FLAT); + break; + case MP4MX_MODE_FASTSTART: + e = gf_isom_set_storage_mode(ctx->file, GF_ISOM_STORE_FASTSTART); + break; + case MP4MX_MODE_TIGHT: + e = gf_isom_set_storage_mode(ctx->file, GF_ISOM_STORE_TIGHT); + break; + } + if (e) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("[MP4Mux] Failed to set storage mode: %s\n", gf_error_to_string(e) )); + } else { + e = gf_isom_close(ctx->file); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[MP4Mux] Failed to write file: %s\n", gf_error_to_string(e) )); + } + } + ctx->file = NULL; + if (is_final) + gf_filter_pid_set_eos(ctx->opid); + } else { + ctx->file = NULL; + } + return e; +} + +static void mp4_mux_finalize(GF_Filter *filter) +{ + GF_MP4MuxCtx *ctx = gf_filter_get_udta(filter); + + if (ctx->owns_mov && (ctx->file || (ctx->store>=MP4MX_MODE_FRAG))) { + gf_isom_delete(ctx->file); + } + + while (gf_list_count(ctx->tracks)) { + TrackWriter *tkw = gf_list_pop_back(ctx->tracks); + mp4_mux_track_writer_del(tkw); + } + gf_list_del(ctx->tracks); + if (ctx->bs_r) gf_bs_del(ctx->bs_r); + if (ctx->seg_name) gf_free(ctx->seg_name); + if (ctx->tmp_store) gf_fclose(ctx->tmp_store); + if (ctx->seg_sizes) gf_free(ctx->seg_sizes); + + if (ctx->cur_file_suffix) gf_free(ctx->cur_file_suffix); + +} + +static const GF_FilterCapability MP4MuxCaps[] = +{ + //for now don't accept files as input, although we could store them as items, to refine + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_OD), + //we want framed media only + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + //and any codecid + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + CAP_STRING(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_FILE_EXT, ISOM_FILE_EXT), + CAP_STRING(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_MIME, ISOM_FILE_MIME), + {0}, + //for scene / OD / text, we don't want raw codecid (filters modifying a scene graph we don't expose) + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_SCENE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_OD), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + + +#define OFFS(_n) #_n, offsetof(GF_MP4MuxCtx, _n) +static const GF_FilterArgs MP4MuxArgs[] = +{ + { OFFS(m4sys), "force MPEG-4 Systems signaling of tracks", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(dref), "only references data from source file - not compatible with all media sources", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ctmode), "set composition offset mode for video tracks\n" + "- edit: uses edit lists to shift first frame to presentation time 0\n" + "- noedit: ignore edit lists and does not shift timeline\n" + "- negctts: uses ctts v1 with possibly negative offsets and no edit lists", GF_PROP_UINT, "edit", "edit|noedit|negctts", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(idur), "only import the specified duration. If negative, specify the number of coded frames to import", GF_PROP_FRACTION, "0", NULL, 0}, + { OFFS(pack3gp), "pack a given number of 3GPP audio frames in one sample", GF_PROP_UINT, "1", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(importer), "compatibility with old importer, displays import progress", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(pack_nal), "repack NALU size length to minimum possible size for NALU-based video (AVC/HEVC/...)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(xps_inband), "use inband (in sample data) param set for NALU-based video (AVC/HEVC/...)\n" + "- no: paramater sets are not inband, several sample descriptions might be created\n" + "- all: paramater sets are inband, no param sets in sample description\n" + "- both: paramater sets are inband, signaled as inband, and also first set is kept in sample descripton\n" + "- mix: creates non-standard files using single sample entry with first PSs found, and moves other PS inband", GF_PROP_UINT, "no", "no|all|both|mix|", 0}, + { OFFS(store), "file storage mode\n" + "- inter: perform precise interleave of the file using [-cdur]() (requires temporary storage of all media)\n" + "- flat: write samples as they arrive and moov at end (fastest mode)\n" + "- fstart: write samples as they arrive and moov before mdat\n" + "- tight: uses per-sample interleaving of all tracks (requires temporary storage of all media)\n" + "- frag: fragments the file using cdur duration\n" + "- sfrag: framents the file using cdur duration but adjusting to start with SAP1/3", GF_PROP_UINT, "inter", "inter|flat|fstart|tight|frag|sfrag", 0}, + { OFFS(cdur), "chunk duration for interleaving and fragmentation modes\n" + "- 0: no specific interleaving but moov first\n" + "- negative: defaults to 1.0 unless overridden by storage profile", GF_PROP_DOUBLE, "-1.0", NULL, 0}, + { OFFS(moovts), "timescale to use for movie. A negative value picks the media timescale of the first track added", GF_PROP_SINT, "600", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(moof_first), "generate fragments starting with moof then mdat", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(abs_offset), "use absolute file offset in fragments rather than offsets from moof", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(fsap), "split truns in video fragments at SAPs to reduce file size", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(file), "pointer to a write/edit ISOBMF file used internally by importers and exporters", GF_PROP_POINTER, NULL, NULL, GF_FS_ARG_HINT_HIDE}, + { OFFS(subs_sidx), "number of subsegments per sidx. negative value disables sidx, -2 removes sidx if present in source pid", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(m4cc), "4 character code of empty box to appen at the end of a segment", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(chain_sidx), "use daisy-chaining of SIDX", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(msn), "sequence number of first moof to N", GF_PROP_UINT, "1", NULL, 0}, + { OFFS(msninc), "sequence number increase between moofs", GF_PROP_UINT, "1", NULL, 0}, + { OFFS(tfdt), "set TFDT of first traf", GF_PROP_FRACTION64, "0", NULL, 0}, + { OFFS(tfdt_traf), "set TFDT in each traf", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(nofragdef), "disable default flags in fragments", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(straf), "use a single traf per moov (smooth streaming and co)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(strun), "use a single trun per traf (smooth streaming and co)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(psshs), "set PSSH boxes store mode\n" + "- moof: in first moof of each segments\n" + "- moov: in movie box\n" + "- none: pssh is discarded", GF_PROP_UINT, "moov", "moov|moof|none", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(sgpd_traf), "store sample group descriptions in traf (duplicated for each traf). If not used, sample group descriptions are stored in the movie box", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(vodcache), "enable temp storage for VoD dash modes - see filter help\n" + "- on: use temp storage of complete file for sidx and ssix injection\n" + "- insert: insert sidx and ssix by shifting bytes in output file\n" + "- replace: precompute pace requirements for sidx and ssix and rewrite file range at end", GF_PROP_UINT, "replace", "on|insert|replace", 0}, + { OFFS(noinit), "do not produce initial moov, used for DASH bitstream switching mode", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(tktpl), "use track box from input if any as a template to create new track\n" + "- no: disables template\n" + "- yes: clones the track (except edits and decoder config)\n" + "- udta: only loads udta", GF_PROP_UINT, "yes", "no|yes|udta", GF_FS_ARG_HINT_EXPERT}, + { OFFS(mudta), "use udta and other moov extension boxes from input if any\n" + "- no: disables import\n" + "- yes: clones all extension boxes\n" + "- udta: only loads udta", GF_PROP_UINT, "yes", "no|yes|udta", GF_FS_ARG_HINT_EXPERT}, + { OFFS(tmpd), "set temp directory for intermediate file(s)", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(mvex), "set mvex after tracks", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(sdtp_traf), "use sdtp in traf rather than using flags in trun sample entries\n" + "- no: do not use sdtp\n" + "- sdtp: use sdtp box to indicate sample dependencies and don't write info in trun sample flags\n" + "- both: use sdtp box to indicate sample dependencies and also write info in trun sample flags", GF_PROP_UINT, "no", "no|sdtp|both", GF_FS_ARG_HINT_EXPERT}, + { OFFS(trackid), "track ID of created track for single track. Default 0 uses next available trackID", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(fragdur), "fragment based on fragment duration rather than CTS. Mostly used for MP4Box -frag option", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(btrt), "set btrt box in sample description", GF_PROP_BOOL, "true", NULL, 0}, + { OFFS(styp), "set segment styp major brand to the given 4CC[.version]", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(mediats), "set media timescale. A value of 0 means inherit from pid, a value of -1 means derive from samplerate or frame rate", GF_PROP_SINT, "0", NULL, 0}, + { OFFS(ase), "set audio sample entry mode for more than stereo layouts\n"\ + "- v0: use v0 signaling but channel count from stream, recommended for backward compatibility\n"\ + "- v0s: use v0 signaling and force channel count to 2 (stereo) if more than 2 channels\n"\ + "- v1: use v1 signaling, ISOBMFF style (will mux raw PCM as ISOBMFF style)\n"\ + "- v1qt: use v1 signaling, QTFF style"\ + , GF_PROP_UINT, "v0", "|v0|v0s|v1|v1qt", 0}, + { OFFS(ssix), "create ssix when sidx is present, level 1 mappping I-frames byte ranges, level 0xFF mapping the rest", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ccst), "insert coding constraint box for video tracks", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(maxchunk), "set max chunk size in bytes for runs (only used in non-fragmented mode). 0 means no constraints", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(noroll), "disable roll sample grouping", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(saio32), "set single segment mode for dash", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, +#ifdef GF_ENABLE_CTRN + { OFFS(ctrn), "use compact track run (experimental)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ctrni), "use inheritance in compact track run for HEVC tile tracks (highly experimental)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, +#endif + { OFFS(sseg), "set single segment mode for dash", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_HIDE}, + + { OFFS(compress), "set top-level box compression mode\n" + "- no: disable box compression\n" + "- moov: compress only moov box\n" + "- moof: compress only moof boxes\n" + "- sidx: compress moof and sidx boxes\n" + "- ssix: compress moof, sidx and ssix boxes\n" + "- all: compress moov, moof, sidx and ssix boxes", GF_PROP_UINT, "no", "no|moov|moof|sidx|ssix|all", GF_FS_ARG_HINT_EXPERT}, + { OFFS(fcomp), "force using compress box even when compressed size is larger than uncompressed", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + + { OFFS(trun_inter), "interleave samples in trun based on the temporal level, the lowest level are stored first - this will create as many trun as required", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(block_size), "target output block size, 0 for default internal value (10k)", GF_PROP_UINT, "10000", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(boxpatch), "apply box patch before writing", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(deps), "add samples dependencies information", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(mfra), "enable movie fragment random access when fragmenting (ignored when dashing)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(forcesync), "force all SAP types to be considered sync samples (might produce non-conformant files)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(tags), "tag injection mode\n" + "- none: do not inject tags\n" + "- strict: only inject recognized itunes tags\n" + "- all: inject all possible tags" + , GF_PROP_UINT, "strict", "none|strict|all", GF_FS_ARG_HINT_EXPERT}, + {0} +}; + + +GF_FilterRegister MP4MuxRegister = { + .name = "mp4mx", + GF_FS_SET_DESCRIPTION("ISOBMFF/QT muxer") + GF_FS_SET_HELP("Muxes file according to ISOBMFF (14496-12 and derived specifications) or QuickTime\n" + " \n" + "# Tracks and Items\n" + "By default all input PIDs with ItemID property set are muxed as items, otherwise they are muxed as tracks.\n" + "To prevent source items to be muxed as items, use [-itemid](mp4dmx) option from ISOBMF demuxer.\n" + "EX -i source.mp4:itemid=false -o file.mp4\n" + " \n" + "To force non-item streams to be muxed as items, use __#ItemID__ option on that PID:\n" + "EX -i source.jpg:#ItemID=1 -o file.mp4\n" + " \n" + "# Storage\n" + "The [-store]() option allows controling if the file is fragmented ot not, and when not fragmented, how interleaving is done. For cases where disk requirements are tight and fragmentation cannot be used, it is recommended to use either `flat` or `fstart` modes.\n" + " \n" + "The [-vodcache]() option allows controling how DASH onDemand segments are generated:\n" + "- If set to `on`, file data is stored to a temporary file on disk and flushed upon completion, no padding is present.\n" + "- If set to `insert`, SIDX/SSIX will be injected upon completion of the file by shifting bytes in file. In this case, no padding is required but this might not be compatible with all output sinks and will take longer to write the file.\n" + "- If set to `replace`, SIDX/SSIX size will be estimated based on duration and DASH segment length, and padding will be used in the file __before__ the final SIDX. If input pids have the properties `DSegs` set, this will be as the number of segments.\n" + "The `on` and `insert` modes will produce exactly the same file, while the mode `replace` may inject a `free` box before the sidx.\n" + " \n" + "# Custom boxes\n" + "Custom boxes can be specified as box patches:\n" + "For movie-level patch, the [-boxpatch]() option of the filter should be used.\n" + "Per PID box patch can be specified through the PID property `boxpatch`.\n" + "EX src=source:#boxpatch=myfile.xml dst=mux.mp4\n" + "Per Item box patch can be specified through the PID property `boxpatch`.\n" + "EX src=source:1ItemID=1:#boxpatch=myfile.xml dst=mux.mp4\n" + " \n" + "The box patch is applied before writing the initial moov box in fragmented mode, or when writing the complete file otherwise.\n" + "The box patch can either be a filename or the full XML string.\n" + " \n" + "# Tagging\n" + "When tagging is enabled, the filter will watch the property `CoverArt` and all custom properties on incoming pid.\n" + "The built-in tag names are `album`, `artist`, `comment`, `complilation`, `composer`, `year`, `disk`, `tool`, `genre`, `contentgroup`, `title`, `tempo`, `track`, `tracknum`, `writer`, `encoder`, `album_artist`, `gapless`, `conductor`.\n" + "Other tag class may be specified using `tag_NAME` property names, and will be added if [-tags]() is set to `all` using:\n" + "- `NAME` as a box 4CC if `NAME` is four characters long\n" + "- the CRC32 of the `NAME` as a box 4CC if `NAME` is not four characters long\n" + " \n" + "# Notes\n" + "The filter watches the property `FileNumber` on incoming packets to create new files or new segments in DASH mode.\n" + ) + .private_size = sizeof(GF_MP4MuxCtx), + .args = MP4MuxArgs, + .initialize = mp4_mux_initialize, + .finalize = mp4_mux_finalize, + .flags = GF_FS_REG_DYNAMIC_REDIRECT, + SETCAPS(MP4MuxCaps), + .configure_pid = mp4_mux_configure_pid, + .process = mp4_mux_process, + .process_event = mp4_mux_process_event +}; + + +const GF_FilterRegister *mp4_mux_register(GF_FilterSession *session) +{ + return &MP4MuxRegister; +} + +#else +const GF_FilterRegister *mp4_mux_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // GPAC_DISABLE_ISOM_WRITE + diff --git a/src/filters/mux_ts.c b/src/filters/mux_ts.c new file mode 100644 index 0000000..7cd5922 --- /dev/null +++ b/src/filters/mux_ts.c @@ -0,0 +1,1725 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018-2020 + * All rights reserved + * + * This file is part of GPAC / MPEG-2 TS mux filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include + + +#define M2TS_FILE_EXTS "ts|m2t|mts|dmb|trp" +#define M2TS_MIMES "video/mpeg-2|video/mp2t|video/mpeg|audio/mp2t" + + +void mux_assign_mime_file_ext(GF_FilterPid *ipid, GF_FilterPid *opid, const char *file_exts, const char *mime_types, const char *def_ext) +{ + Bool found=GF_FALSE; + const GF_PropertyValue *p; + + p = gf_filter_pid_get_property(ipid, GF_PROP_PID_FILE_EXT); + if (p) { + char *match = strstr(file_exts, p->value.string); + if (match) { + u32 slen = (u32) strlen(match); + if (!match[slen-1] || (match[slen-1]=='|')) + found = GF_TRUE; + } + } + if (!found) + gf_filter_pid_set_property(opid, GF_PROP_PID_FILE_EXT, &PROP_STRING(def_ext ? (char *)def_ext : "*") ); + + p = gf_filter_pid_get_property(ipid, GF_PROP_PID_MIME); + found = GF_FALSE; + if (p) { + char *match = strstr(mime_types, p->value.string); + if (match) { + u32 slen = (u32) strlen(match); + if (!match[slen-1] || (match[slen-1]=='|')) + found = GF_TRUE; + } + } + if (!found) + gf_filter_pid_set_property(opid, GF_PROP_PID_MIME, &PROP_STRING("*") ); +} + + + +enum +{ + TEMI_TC64_AUTO=0, + TEMI_TC64_OFF, + TEMI_TC64_ALWAYS, +}; + + +typedef struct +{ + u64 sap_time; + u64 offset; + u32 nb_pck; + u32 sap_type; + u64 min_pts_plus_one; + u64 max_pts; +} TS_SIDX; + +typedef struct +{ + //filter args + u32 pmt_id, pmt_rate, pmt_version, sdt_rate, breq, mpeg4; + u64 pcr_offset, first_pts; + u32 rate, pat_rate, repeat_rate, repeat_img, max_pcr, nb_pack, sid, bifs_pes; + GF_M2TS_PackMode pes_pack; + Bool flush_rap, realtime, pcr_only, disc, latm; + s64 pcr_init; + char *name, *provider, *temi; + u32 log_freq; + s32 subs_sidx; + + //internal + GF_FilterPid *opid; + GF_FilterPid *idx_opid; + GF_Filter *idx_filter; + GF_M2TS_Mux *mux; + + GF_List *pids; + + Bool check_pcr; + Bool update_mux; + char *pack_buffer; + u64 nb_pck; + Bool init_buffering; + u32 last_log_time; + Bool pmt_update_pending; + + u32 dash_mode; + Bool init_dash; + u32 dash_seg_num; + Bool wait_dash_flush; + Bool dash_file_switch; + Bool next_is_start; + u32 nb_pck_in_seg; + u64 pck_start_idx; + + char dash_file_name[GF_MAX_PATH]; + char idx_file_name[GF_MAX_PATH]; + + //dash indexing + u32 nb_sidx_entries, nb_sidx_alloc; + TS_SIDX *sidx_entries; + GF_BitStream *idx_bs; + u32 nb_pck_in_file, nb_pck_first_sidx, ref_pid; + u64 total_bytes_in; + + u32 nb_suspended, cur_file_idx_plus_one; + char *cur_file_suffix; + Bool notify_filename; +} GF_TSMuxCtx; + +typedef struct +{ + u32 id, delay, timescale; + u64 offset, init_val; + char *url; + Bool ntp, use_init_val; + u32 mode_64bits; + u64 cts_at_init_val_plus_one; +} TEMIDesc; + +typedef struct +{ + GF_ESInterface esi; + GF_FilterPid *ipid; + GF_M2TS_Mux_Stream *mstream; + GF_M2TS_Mux_Program *prog; + + u32 sid; + u32 codec_id; + u32 pmt_pid; + u32 nb_pck; + Bool is_repeat; + u32 nb_repeat_last; + + GF_TSMuxCtx *ctx; + u32 last_cv; + //ts media skip + s32 media_delay, max_media_skip; + Bool done; + + GF_List *temi_descs; + + u32 last_temi_url; + u8 *af_data; + u32 af_data_alloc; + GF_BitStream *temi_af_bs; + + Bool rewrite_odf; + Bool has_seen_eods; + u32 pck_duration; + + u64 loop_ts_offset; + u64 last_dts; + u32 last_dur; + + u8 *pck_data_buf; + + u32 suspended; +} M2Pid; + +static GF_Err tsmux_format_af_descriptor(GF_BitStream *bs, u32 timeline_id, u64 timecode, u32 timescale, u32 mode_64bits, u64 ntp, const char *temi_url, u32 temi_delay, u32 *last_url_time) +{ + u32 len; + u32 last_time; + if (ntp) { + last_time = 1000*(ntp>>32); + last_time += 1000*(ntp&0xFFFFFFFF)/0xFFFFFFFF; + } else { + last_time = (u32) (1000*timecode/timescale); + } + if (temi_url && (!*last_url_time || (last_time - *last_url_time + 1 >= temi_delay)) ) { + u64 start = gf_bs_get_position(bs); + u64 end; + *last_url_time = last_time + 1; + len = 0; + gf_bs_write_int(bs, GF_M2TS_AFDESC_LOCATION_DESCRIPTOR, 8); + gf_bs_write_int(bs, len, 8); + + gf_bs_write_int(bs, 0, 1); //force_reload + gf_bs_write_int(bs, 0, 1); //is_announcement + gf_bs_write_int(bs, 0, 1); //splicing_flag + gf_bs_write_int(bs, 0, 1); //use_base_temi_url + gf_bs_write_int(bs, 0xFF, 5); //reserved + gf_bs_write_int(bs, timeline_id, 7); //timeline_id + + if (strlen(temi_url)) { + char *url = (char *)temi_url; + if (!strnicmp(temi_url, "http://", 7)) { + gf_bs_write_int(bs, 1, 8); //url_scheme + url = (char *) temi_url + 7; + } else if (!strnicmp(temi_url, "https://", 8)) { + gf_bs_write_int(bs, 2, 8); //url_scheme + url = (char *) temi_url + 8; + } else { + gf_bs_write_int(bs, 0, 8); //url_scheme + } + gf_bs_write_u8(bs, (u32) strlen(url)); //url_path_len + gf_bs_write_data(bs, url, (u32) strlen(url) ); //url + gf_bs_write_u8(bs, 0); //nb_addons + } + //rewrite len + end = gf_bs_get_position(bs); + len = (u32) (end - start - 2); + gf_bs_seek(bs, start+1); + gf_bs_write_int(bs, len, 8); + gf_bs_seek(bs, end); + } + + if (timescale || ntp) { + Bool use64; + if (mode_64bits==TEMI_TC64_AUTO) { + use64 = (timecode > 0xFFFFFFFFUL) ? GF_TRUE : GF_FALSE; + } else if (mode_64bits==TEMI_TC64_ALWAYS) { + use64 = GF_TRUE; + } else { + use64 = GF_FALSE; + while (timecode > 0xFFFFFFFFUL) { + timecode -= 0xFFFFFFFFUL; + } + + } + + len = 3; //3 bytes flags + + if (timescale) len += 4 + (use64 ? 8 : 4); + if (ntp) len += 8; + + //write timeline descriptor + gf_bs_write_int(bs, GF_M2TS_AFDESC_TIMELINE_DESCRIPTOR, 8); + gf_bs_write_int(bs, len, 8); + + gf_bs_write_int(bs, timescale ? (use64 ? 2 : 1) : 0, 2); //has_timestamp + gf_bs_write_int(bs, ntp ? 1 : 0, 1); //has_ntp + gf_bs_write_int(bs, 0, 1); //has_ptp + gf_bs_write_int(bs, 0, 2); //has_timecode + gf_bs_write_int(bs, 0, 1); //force_reload + gf_bs_write_int(bs, 0, 1); //paused + gf_bs_write_int(bs, 0, 1); //discontinuity + gf_bs_write_int(bs, 0xFF, 7); //reserved + gf_bs_write_int(bs, timeline_id, 8); //timeline_id + if (timescale) { + gf_bs_write_u32(bs, timescale); //timescale + if (use64) + gf_bs_write_u64(bs, timecode); //timestamp + else + gf_bs_write_u32(bs, (u32) timecode); //timestamp + } + if (ntp) { + gf_bs_write_u64(bs, ntp); //ntp + } + } + return GF_OK; +} + + +GF_SLConfig *tsmux_get_sl_config(GF_TSMuxCtx *ctx, u32 timescale, GF_SLConfig *slc) +{ + if (!slc) slc = (GF_SLConfig *) gf_odf_desc_new(GF_ODF_SLC_TAG); + slc->predefined = 0; + slc->useAccessUnitStartFlag = 1; + slc->useAccessUnitEndFlag = 1; + slc->useRandomAccessPointFlag = 1; + slc->useTimestampsFlag = 1; + slc->timestampLength = 33; + slc->timestampResolution = timescale; + + /*test mode in which time stamps are 90khz and not coded but copied over from PES header*/ + if (ctx->bifs_pes==2) { + slc->timestampLength = 0; + slc->timestampResolution = 90000; + } + return slc; +} + +static void tsmux_rewrite_odf(GF_TSMuxCtx *ctx, GF_ESIPacket *es_pck) +{ + u32 com_count, com_index, od_count, esd_index, od_index; + GF_ODUpdate *odU; + GF_ESDUpdate *esdU; + GF_ESD *esd; + GF_ODCodec *od_codec = gf_odf_codec_new(); + + gf_odf_codec_set_au(od_codec, es_pck->data, es_pck->data_len); + gf_odf_codec_decode(od_codec); + com_count = gf_list_count(od_codec->CommandList); + for (com_index = 0; com_index < com_count; com_index++) { + GF_ODCom *com = (GF_ODCom *)gf_list_get(od_codec->CommandList, com_index); + switch (com->tag) { + case GF_ODF_OD_UPDATE_TAG: + odU = (GF_ODUpdate*)com; + od_count = gf_list_count(odU->objectDescriptors); + for (od_index=0; od_indexobjectDescriptors, od_index); + esd_index = 0; + while ( (esd = gf_list_enum(od->ESDescriptors, &esd_index)) ) { + assert(esd->slConfig); + esd->slConfig = tsmux_get_sl_config(ctx, esd->slConfig->timestampResolution, esd->slConfig); + } + } + break; + case GF_ODF_ESD_UPDATE_TAG: + esdU = (GF_ESDUpdate*)com; + esd_index = 0; + while ( (esd = gf_list_enum(esdU->ESDescriptors, &esd_index)) ) { + assert(esd->slConfig); + esd->slConfig = tsmux_get_sl_config(ctx, esd->slConfig->timestampResolution, esd->slConfig); + } + break; + } + } + gf_odf_codec_encode(od_codec, 1); + es_pck->data = NULL; + es_pck->data_len = 0; + gf_odf_codec_get_au(od_codec, &es_pck->data, &es_pck->data_len); + gf_odf_codec_del(od_codec); + +} + +static GF_Err tsmux_esi_ctrl(GF_ESInterface *ifce, u32 act_type, void *param) +{ + u32 cversion; + u64 cts, cts_diff; + M2Pid *tspid = (M2Pid *)ifce->input_udta; + if (!tspid) return GF_BAD_PARAM; + + switch (act_type) { + case GF_ESI_INPUT_DATA_FLUSH: + { + u64 dts; + GF_ESIPacket es_pck; + const GF_PropertyValue *p; + GF_FilterPacket *pck; + pck = gf_filter_pid_get_packet(tspid->ipid); + //if PMT update is pending after this packet fetch (reconfigure), don't send the packet + if (tspid->ctx->pmt_update_pending) return GF_OK; + + if (!pck) { + if (gf_filter_pid_is_eos(tspid->ipid)) { + if (tspid->ctx->realtime + && tspid->ctx->repeat_img + && (tspid->nb_pck==1) + && (tspid->esi.stream_type==GF_STREAM_VISUAL) + ) { + tspid->nb_repeat_last++; + tspid->is_repeat = GF_TRUE; + } else { + tspid->done = GF_TRUE; + ifce->caps |= GF_ESI_STREAM_IS_OVER; + } + if (tspid->ctx->dash_mode) + tspid->has_seen_eods = GF_TRUE; + } + return GF_OK; + } + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM); + if (tspid->ctx->dash_mode) { + + if (tspid->has_seen_eods) + return GF_OK; + + //detect segment change + if (p && tspid->ctx->dash_seg_num && (tspid->ctx->dash_seg_num < p->value.uint)) { + tspid->ctx->wait_dash_flush = GF_TRUE; + tspid->ctx->dash_seg_num = p->value.uint; + tspid->ctx->dash_file_name[0] = 0; + } + + //segment change is pending, check for filename as well - we don't do that in the previous test + //since the filename property is sent on a single pid, not each of them + if (tspid->ctx->wait_dash_flush && p && (tspid->ctx->dash_seg_num == p->value.uint)) { + tspid->has_seen_eods = GF_TRUE; + + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME); + if (p) { + strcpy(tspid->ctx->dash_file_name, p->value.string); + tspid->ctx->dash_file_switch = GF_TRUE; + } + return GF_OK; + } + + //at this point the new segment is started + if (p) + tspid->ctx->dash_seg_num = p->value.uint; + + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_IDXFILENAME); + if (p) { + strcpy(tspid->ctx->idx_file_name, p->value.string); + } + + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_EODS); + if (p && p->value.boolean) { + tspid->has_seen_eods = GF_TRUE; + tspid->ctx->wait_dash_flush = GF_TRUE; + gf_filter_pid_drop_packet(tspid->ipid); + return GF_OK; + } + } else if (p) { + if (!tspid->ctx->cur_file_idx_plus_one) { + tspid->ctx->cur_file_idx_plus_one = p->value.uint + 1; + if (!tspid->ctx->cur_file_suffix) { + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILESUF); + if (p && p->value.string) tspid->ctx->cur_file_suffix = gf_strdup(p->value.string); + } + tspid->ctx->notify_filename = GF_TRUE; + } else if (tspid->ctx->cur_file_idx_plus_one == p->value.uint+1) { + } else if (tspid->suspended) { + return GF_OK; + } else if (!tspid->ctx->notify_filename) { + tspid->suspended = GF_TRUE; + tspid->ctx->nb_suspended++; + if (tspid->ctx->nb_suspended==1) { + tspid->ctx->cur_file_idx_plus_one = p->value.uint + 1; + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILESUF); + if (p && p->value.string && !tspid->ctx->cur_file_suffix) + tspid->ctx->cur_file_suffix = gf_strdup(p->value.string); + } + ifce->caps |= GF_ESI_STREAM_IS_OVER; + return GF_OK; + } else { + //notify filename pending + return GF_OK; + } + } + + memset(&es_pck, 0, sizeof(GF_ESIPacket)); + es_pck.flags = GF_ESI_DATA_AU_START | GF_ESI_DATA_AU_END | GF_ESI_DATA_HAS_CTS; + es_pck.sap_type = gf_filter_pck_get_sap(pck); + tspid->pck_duration = gf_filter_pck_get_duration(pck); + cversion = gf_filter_pck_get_carousel_version(pck); + if (cversion+1 == tspid->last_cv) { + es_pck.flags |= GF_ESI_DATA_REPEAT; + } + tspid->last_cv = cversion+1; + + if (tspid->is_repeat) + es_pck.flags |= GF_ESI_DATA_REPEAT; + + cts = gf_filter_pck_get_cts(pck); + dts = gf_filter_pck_get_dts(pck); + if (dts != GF_FILTER_NO_TS) { + dts += tspid->loop_ts_offset; + //loop detected + if (tspid->last_dts && (dtslast_dts)) { + dts -= tspid->loop_ts_offset; + tspid->loop_ts_offset = tspid->last_dts - dts + tspid->last_dur; + dts += tspid->loop_ts_offset; + } + } + if (cts != GF_FILTER_NO_TS) { + cts += tspid->loop_ts_offset; + } + + es_pck.cts = cts; + if (tspid->temi_descs) { + u32 i, count=gf_list_count(tspid->temi_descs); + + if (!tspid->temi_af_bs) tspid->temi_af_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(tspid->temi_af_bs, tspid->af_data, tspid->af_data_alloc); + + for (i=0; itemi_descs, i); + u64 ntp=0; + u32 timescale = ifce->timescale; + u64 tc; + + if (temi->timescale && (temi->timescale!=timescale)) { + timescale = temi->timescale; + } + if (temi->use_init_val) { + if (!temi->cts_at_init_val_plus_one) temi->cts_at_init_val_plus_one = cts+1; + + tc = (cts - (temi->cts_at_init_val_plus_one-1)); + if (timescale != ifce->timescale) { + tc *= temi->timescale; + tc /= ifce->timescale; + } + tc += temi->init_val; + } else { + //TOCHECK: do we want media timeline or composition timeline ? + tc = cts; + if (timescale != ifce->timescale) { + tc *= temi->timescale; + tc /= ifce->timescale; + } + } + + if (temi->offset) { + if (timescale != ifce->timescale) + tc += ((u64) temi->offset) * ifce->timescale / 1000; + else + tc += temi->offset; + } + + if (temi->ntp) { + u32 sec, frac; + gf_net_get_ntp(&sec, &frac); + ntp = sec; + ntp <<= 32; + ntp |= frac; + } + tsmux_format_af_descriptor(tspid->temi_af_bs, temi->id, tc, timescale, temi->mode_64bits, ntp, temi->url, temi->delay, &tspid->last_temi_url); + } + gf_bs_get_content_no_truncate(tspid->temi_af_bs, &tspid->af_data, &es_pck.mpeg2_af_descriptors_size, &tspid->af_data_alloc); + es_pck.mpeg2_af_descriptors = tspid->af_data; + } + es_pck.cts += tspid->max_media_skip + tspid->media_delay; + + if (tspid->nb_repeat_last) { + es_pck.cts += tspid->nb_repeat_last * ifce->timescale * tspid->ctx->repeat_img / 1000; + } + + cts_diff = 0; + if (tspid->prog->cts_offset) { + cts_diff = tspid->prog->cts_offset; + cts_diff *= tspid->esi.timescale; + cts_diff /= 1000000; + + es_pck.cts += cts_diff; + } + + es_pck.dts = es_pck.cts; + if (dts != GF_FILTER_NO_TS) { + tspid->last_dts = dts; + es_pck.dts = dts; + es_pck.dts += tspid->max_media_skip + tspid->media_delay; + + if (es_pck.dts > es_pck.cts) { + u64 diff; + //we don't have reliable dts - double the diff should make sure we don't try to adjust too often + diff = cts_diff = 2*(es_pck.dts - es_pck.cts); + diff *= 1000000; + diff /= tspid->esi.timescale; + assert(tspid->prog->cts_offset <= diff); + tspid->prog->cts_offset += (u32) diff; + + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[M2TSMux] Packet CTS "LLU" is less than packet DTS "LLU", adjusting all CTS by %d / %d!\n", es_pck.cts, es_pck.dts, cts_diff, tspid->esi.timescale)); + + es_pck.cts += cts_diff; + } + if (tspid->esi.stream_type!=GF_STREAM_VISUAL) { + es_pck.dts += cts_diff; + } + + if (es_pck.dts != es_pck.cts) { + es_pck.flags |= GF_ESI_DATA_HAS_DTS; + } + } + es_pck.data = (char *) gf_filter_pck_get_data(pck, &es_pck.data_len); + es_pck.duration = gf_filter_pck_get_duration(pck); + tspid->last_dur = es_pck.duration; + + if (tspid->rewrite_odf) { + tsmux_rewrite_odf(tspid->ctx, &es_pck); + } + + tspid->ctx->total_bytes_in += es_pck.data_len; + + if (tspid->pck_data_buf) gf_free(tspid->pck_data_buf); + tspid->pck_data_buf = NULL; + + //drop formatting for TX3G + if (tspid->codec_id == GF_CODECID_TX3G) { + u16 len = es_pck.data[0]; + len<<=8; + len |= es_pck.data[1]; + es_pck.data += 2; + es_pck.data_len = len; + } + //serialize webvtt cue formatting for TX3G + else if (tspid->codec_id == GF_CODECID_WEBVTT) { + u32 i; + u64 start_ts; + void webvtt_write_cue(GF_BitStream *bs, GF_WebVTTCue *cue); + GF_List *cues; + GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + + start_ts = es_pck.cts * 1000; + start_ts /= tspid->esi.timescale; + cues = gf_webvtt_parse_cues_from_data(es_pck.data, es_pck.data_len, start_ts); + for (i = 0; i < gf_list_count(cues); i++) { + GF_WebVTTCue *cue = (GF_WebVTTCue *)gf_list_get(cues, i); + webvtt_write_cue(bs, cue); + gf_webvtt_cue_del(cue); + } + gf_list_del(cues); + gf_bs_get_content(bs, &es_pck.data, &es_pck.data_len); + gf_bs_del(bs); + tspid->pck_data_buf = es_pck.data; + } + //for TTML we keep the entire payload as a PES packet + + tspid->nb_pck++; + ifce->output_ctrl(ifce, GF_ESI_OUTPUT_DATA_DISPATCH, &es_pck); + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[M2TSMux] PID %d: packet %d CTS "LLU"\n", tspid->esi.stream_id, tspid->nb_pck, es_pck.cts)); + + //data is copied by muxer for now, should need rewrite to avoid un-needed allocations + gf_filter_pid_drop_packet(tspid->ipid); + + if (tspid->rewrite_odf) { + gf_free(es_pck.data); + } + + } + return GF_OK; + + case GF_ESI_INPUT_DESTROY: + return GF_OK; + default: + return GF_BAD_PARAM; + } +} + + +void update_m4sys_info(GF_TSMuxCtx *ctx, GF_M2TS_Mux_Program *prog) +{ + GF_M2TS_Mux_Stream *stream = prog->streams; + + if (prog->iod) gf_odf_desc_del(prog->iod); + prog->iod = gf_odf_desc_new(GF_ODF_IOD_TAG); + while (stream) { + M2Pid *tspid = (M2Pid *)stream->ifce->input_udta; + const GF_PropertyValue *p = gf_filter_pid_get_property(tspid->ipid, GF_PROP_PID_IN_IOD); + if (p && p->value.boolean) { + GF_ESD *esd = gf_odf_desc_esd_new(0); + esd->decoderConfig->objectTypeIndication = stream->ifce->codecid; + esd->decoderConfig->streamType = stream->ifce->stream_type; + esd->ESID = stream->ifce->stream_id; + esd->dependsOnESID = stream->ifce->depends_on_stream; + if (stream->ifce->decoder_config_size) { + esd->decoderConfig->decoderSpecificInfo->data = gf_malloc(sizeof(char)*stream->ifce->decoder_config_size); + memcpy(esd->decoderConfig->decoderSpecificInfo->data, stream->ifce->decoder_config, stream->ifce->decoder_config_size); + esd->decoderConfig->decoderSpecificInfo->dataLength = stream->ifce->decoder_config_size; + } + tsmux_get_sl_config(ctx, stream->ifce->timescale, esd->slConfig); + gf_list_add( ((GF_ObjectDescriptor *)prog->iod)->ESDescriptors, esd); + } + stream->ifce->sl_config = tsmux_get_sl_config(ctx, stream->ifce->timescale, stream->ifce->sl_config); + stream = stream->next; + } +} + +static void tsmux_setup_esi(GF_TSMuxCtx *ctx, GF_M2TS_Mux_Program *prog, M2Pid *tspid, u32 stream_type) +{ + const GF_PropertyValue *p; + + memset(&tspid->esi, 0, sizeof(GF_ESInterface)); + tspid->esi.stream_type = stream_type; + + p = gf_filter_pid_get_property(tspid->ipid, GF_PROP_PID_TIMESCALE); + tspid->esi.timescale = p->value.uint; + + p = gf_filter_pid_get_property(tspid->ipid, GF_PROP_PID_DECODER_CONFIG); + if (p) { + tspid->esi.decoder_config = p->value.data.ptr; + tspid->esi.decoder_config_size = p->value.data.size; + } + p = gf_filter_pid_get_property(tspid->ipid, GF_PROP_PID_ID); + if (p) tspid->esi.stream_id = p->value.uint; + + p = gf_filter_pid_get_property(tspid->ipid, GF_PROP_PID_DEPENDENCY_ID); + if (p) tspid->esi.depends_on_stream = p->value.uint; + + tspid->esi.codecid = tspid->codec_id; + + p = gf_filter_pid_get_property(tspid->ipid, GF_PROP_PID_LANGUAGE); + if (p) { + s32 idx = gf_lang_find(p->value.string); + if (idx>=0) { + const char *code = gf_lang_get_3cc(idx); + if (code) tspid->esi.lang = GF_4CC(code[0], code[1], code[2], ' '); + } + } + + p = gf_filter_pid_get_property(tspid->ipid, GF_PROP_PID_DURATION); + if (p) { + tspid->esi.duration = (Double) p->value.lfrac.num; + tspid->esi.duration /= p->value.lfrac.den; + } + p = gf_filter_pid_get_property(tspid->ipid, GF_PROP_PID_BITRATE); + if (p) tspid->esi.bit_rate = p->value.uint; + /*repeat rate in ms for carrouseling - 0 if no repeat*/ + + tspid->esi.repeat_rate = ctx->repeat_rate; + p = gf_filter_pid_get_property(tspid->ipid, GF_PROP_PID_CAROUSEL_RATE); + if (p) tspid->esi.repeat_rate = p->value.uint; + + tspid->rewrite_odf = GF_FALSE; + if (tspid->esi.stream_type==GF_STREAM_OD) { + tspid->rewrite_odf = GF_TRUE; + update_m4sys_info(ctx, prog); + } else if (prog->iod) { + update_m4sys_info(ctx, prog); + } + + tspid->esi.caps = 0; + switch (tspid->esi.stream_type) { + case GF_STREAM_AUDIO: + if (ctx->latm) tspid->esi.caps |= GF_ESI_AAC_USE_LATM; + case GF_STREAM_VISUAL: + if (ctx->mpeg4==2) { + tspid->esi.caps |= GF_ESI_STREAM_WITHOUT_MPEG4_SYSTEMS; + } + break; + } + + tspid->esi.input_ctrl = tsmux_esi_ctrl; + tspid->esi.input_udta = tspid; + tspid->prog = prog; +} + +static void tsmux_setup_temi(GF_TSMuxCtx *ctx, M2Pid *tspid) +{ + GF_M2TS_Mux_Stream *a_stream; + u32 st_idx=0; + const GF_PropertyValue *p; + char *temi_cfg; + + p = gf_filter_pid_get_property_str(tspid->ipid, "tsmux:temi"); + if (p && (p->type==GF_PROP_STRING)) temi_cfg = p->value.string; + else temi_cfg = ctx->temi; + + if (!temi_cfg) return; + u32 temi_id=0; + + //find our stream index + a_stream = tspid->mstream->program->streams; + while (a_stream) { + if (tspid->mstream == a_stream) break; + st_idx++; + a_stream = a_stream->next; + } + + while (temi_cfg) { + u32 service_id=0; + u32 temi_delay=1000; + u64 temi_offset=0; + u32 temi_timescale=0; + Bool temi_ntp=GF_FALSE; + u32 temi_64bits=TEMI_TC64_AUTO; + Bool temi_use_init_val=GF_FALSE; + u64 temi_init_val = 0; + u32 temi_streamtype=0; + u32 temi_index=0; + Bool done=GF_FALSE; + char *sep; + + if (!strlen(temi_cfg)) + break; + + while (temi_cfg[0]=='#') { + sep = strchr(temi_cfg+1, '#'); + if (!sep) { + temi_cfg += 1; + break; + } + sep[0] = 0; + switch (temi_cfg[1]) { + case 'S': + service_id = atoi(temi_cfg+2); + break; + case 'N': + temi_ntp = GF_TRUE; + break; + case 'D': + temi_delay = atoi(temi_cfg+2); + break; + case 'O': + sscanf(temi_cfg+2, LLU, &temi_offset); + break; + case 'I': + sscanf(temi_cfg+2, LLU, &temi_init_val); + temi_use_init_val=GF_TRUE; + break; + case 'T': + temi_timescale = atoi(temi_cfg+2); + break; + case 'L': + if (temi_cfg[2]=='Y') temi_64bits = TEMI_TC64_ALWAYS; + else if (temi_cfg[2]=='N') temi_64bits = TEMI_TC64_OFF; + break; + case 'P': + //all pids target + if (!temi_cfg[2] || (temi_cfg[2]=='*')) { } + else if ((temi_cfg[2]=='v') || (temi_cfg[2]=='V')) { temi_streamtype = GF_STREAM_VISUAL; } + else if ((temi_cfg[2]=='a') || (temi_cfg[2]=='A')) { temi_streamtype = GF_STREAM_AUDIO; } + else if ((temi_cfg[2]=='t') || (temi_cfg[2]=='T')) { temi_streamtype = GF_STREAM_TEXT; } + else { + temi_index = atoi(temi_cfg+2); + } + break; + default: + done=GF_TRUE; + break; + } + sep[0] = '#'; + if (done) { + temi_cfg++; + break; + } + temi_cfg = sep; + } + if (!temi_cfg || !strlen(temi_cfg)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[M2TSMux] Invalid temi syntax, no ID or URL specified\n")); + return; + } + + sep = strchr(temi_cfg, ','); + if (sep) sep[0] = 0; + + done = GF_TRUE; + if (temi_index && (temi_index!=st_idx)) done = GF_FALSE; + else if (temi_streamtype && (temi_streamtype!=tspid->esi.stream_type)) done = GF_FALSE; + else if (service_id && (service_id != tspid->mstream->program->number)) done = GF_FALSE; + + //that's for this stream + if (done) { + TEMIDesc *temi; + if (!tspid->temi_descs) tspid->temi_descs = gf_list_new(); + GF_SAFEALLOC(temi, TEMIDesc); + temi->id = atoi(temi_cfg); + if (!temi->id) { + temi->url = gf_strdup(temi_cfg); + temi->id = temi_id+1; + } + temi->ntp = temi_ntp; + temi->offset = temi_offset; + temi->delay = temi_delay; + temi->timescale = temi_timescale; + temi->mode_64bits = temi_64bits; + temi->init_val = temi_init_val; + temi->use_init_val = temi_use_init_val; + gf_list_add(tspid->temi_descs, temi); + } + if (!sep) break; + sep[0] = ','; + temi_cfg = sep+1; + } +} + +static void tsmux_del_stream(M2Pid *tspid) +{ + if (tspid->temi_descs) { + while (gf_list_count(tspid->temi_descs)) { + TEMIDesc *temi = gf_list_pop_back(tspid->temi_descs); + if (temi->url) gf_free(temi->url); + gf_free(temi); + } + gf_list_del(tspid->temi_descs); + } + if (tspid->af_data) gf_free(tspid->af_data); + if (tspid->temi_af_bs) gf_bs_del(tspid->temi_af_bs); + + if (tspid->esi.sl_config) gf_free(tspid->esi.sl_config); + if (tspid->pck_data_buf) gf_free(tspid->pck_data_buf); + gf_free(tspid); +} + +static GF_Err tsmux_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 service_id, codec_id, streamtype; + GF_M2TS_Mux_Stream *ts_stream; + const GF_PropertyValue *p; + GF_PropertyEntry *pe=NULL; + M2Pid *tspid=NULL; + char *sname, *pname; + GF_M2TS_Mux_Program *prog; + GF_TSMuxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + tspid = gf_filter_pid_get_udta(pid); + if (!tspid) return GF_OK; + //remove stream - this will update PMT as well + gf_m2ts_program_stream_remove(tspid->mstream); + //destroy or object + gf_list_del_item(ctx->pids, tspid); + tsmux_del_stream(tspid); + return GF_OK; + } + + if (!gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p) return GF_NOT_SUPPORTED; + codec_id = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (!p) return GF_NOT_SUPPORTED; + streamtype = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_ID); + service_id = p ? p->value.uint : ctx->sid; + + sname = ctx->name; + pname = ctx->provider; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_NAME); + if (p) sname = p->value.string; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_PROVIDER); + if (p) pname = p->value.string; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_name(ctx->opid, "ts_mux"); + } + //set output properties at init or reconfig + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_FAKE_MP2T)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(90000)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_NO_TS_LOOP, &PROP_BOOL(GF_TRUE)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DASH_MODE, NULL); + + mux_assign_mime_file_ext(pid, ctx->opid, M2TS_FILE_EXTS, M2TS_MIMES, "ts"); + + p = gf_filter_pid_get_info(pid, GF_PROP_PID_DASH_MODE, &pe); + if (p) { + if (!ctx->dash_mode && p->value.uint) ctx->init_dash = GF_TRUE; + ctx->dash_mode = p->value.uint; + if (ctx->dash_mode) { + ctx->mux->flush_pes_at_rap = GF_TRUE; + gf_m2ts_mux_set_initial_pcr(ctx->mux, 0); + } + } + gf_filter_release_property(pe); + + tspid = gf_filter_pid_get_udta(pid); + if (!tspid) { + GF_SAFEALLOC(tspid, M2Pid); + if (!tspid) return GF_OUT_OF_MEM; + gf_filter_pid_set_udta(pid, tspid); + tspid->ipid = pid; + tspid->sid = service_id; + tspid->ctx = ctx; + gf_list_add(ctx->pids, tspid); + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + + if (ctx->breq) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_BUFFER_REQ, pid); + evt.buffer_req.max_buffer_us = 1000*ctx->breq; + evt.buffer_req.pid_only = GF_TRUE; + gf_filter_pid_send_event(pid, &evt); + } + } + + //do we need a new program + prog = gf_m2ts_mux_program_find(ctx->mux, service_id); + if (!prog) { + u32 nb_progs; + u64 first_pts_val; + u64 pcr_offset=0; + u32 pmt_id = ctx->pmt_id; + + if (!pmt_id) pmt_id = 100; + nb_progs = gf_m2ts_mux_program_count(ctx->mux); + if (nb_progs>1) { + if (ctx->dash_mode) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[M2TSMux] Muxing several programs (%d) in DASH mode is not allowed\n", nb_progs+1)); + return GF_BAD_PARAM; + } + pmt_id += (nb_progs - 1) * 100; + } + + p = gf_filter_pid_get_property_str(tspid->ipid, "tsmux:pcr_offset"); + if (p && (p->type==GF_PROP_LUINT)) { + pcr_offset = p->value.longuint; + } else if (ctx->pcr_offset==(u64)-1) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_MAX_FRAME_SIZE); + if (p && p->value.uint && ctx->rate) { + Double r = p->value.uint * 8; + r *= 90000; + r/= ctx->rate; + //add 10% of safety to cover TS signaling and other potential table update while sending the largest PES + r *= 1.1; + pcr_offset = (u64) r; + } + } else { + pcr_offset = ctx->pcr_offset; + } + p = gf_filter_pid_get_property_str(tspid->ipid, "tsmux:force_pts"); + if (p && (p->type==GF_PROP_LUINT)) first_pts_val = p->value.longuint; + else first_pts_val = ctx->first_pts; + + prog = gf_m2ts_mux_program_add(ctx->mux, service_id, pmt_id, ctx->pmt_rate, pcr_offset, ctx->mpeg4, ctx->pmt_version, ctx->disc, first_pts_val); + + if (sname) gf_m2ts_mux_program_set_name(prog, sname, pname); + + GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[M2TSMux] Setting up program ID %d - send rates: PSI %d ms PCR every %d ms max - PCR offset %d\n", service_id, ctx->pmt_rate, ctx->max_pcr, ctx->pcr_offset)); + } + //no changes in codec ID or stream type + if ((tspid->codec_id == codec_id) && (tspid->esi.stream_type == streamtype)) + return GF_OK; + + if (!tspid->codec_id) { + Bool is_pcr=GF_FALSE; + Bool force_pes=GF_FALSE; + u32 pes_pid; + assert(!tspid->esi.stream_type); + tspid->codec_id = codec_id; + tspid->esi.stream_type = streamtype; + + if (ctx->bifs_pes && (tspid->esi.stream_type==GF_STREAM_SCENE)) + force_pes = GF_TRUE; + + pes_pid = gf_m2ts_mux_program_get_pmt_pid(prog); + pes_pid += 1 + gf_m2ts_mux_program_get_stream_count(prog); + if (gf_m2ts_mux_program_get_pcr_pid(prog)==0) { + if (streamtype==GF_STREAM_VISUAL) + is_pcr = GF_TRUE; + else + ctx->check_pcr = GF_TRUE; + //if no video track we will update the PCR at the first packets + } + + tsmux_setup_esi(ctx, prog, tspid, streamtype); + tspid->mstream = gf_m2ts_program_stream_add(prog, &tspid->esi, pes_pid, is_pcr, force_pes, GF_FALSE); + tsmux_setup_temi(ctx, tspid); + } else { + tspid->codec_id = codec_id; + tsmux_setup_esi(ctx, prog, tspid, streamtype); + prog->pmt->table_needs_update = GF_TRUE; + ctx->pmt_update_pending = GF_TRUE; + } + ctx->update_mux = GF_TRUE; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DELAY); + if (p) { + tspid->media_delay = p->value.sint; + + //compute max ts skip for this program + s64 max_media_skip = 0; + u32 max_skip_ts = 0; + ts_stream = prog->streams; + while (ts_stream) { + M2Pid *atspid = ts_stream->ifce->input_udta; + s64 media_skip = (s64) -atspid->media_delay; + if (media_skip > max_media_skip) { + max_media_skip = media_skip ; + max_skip_ts = atspid->esi.timescale; + } + ts_stream = ts_stream->next; + } + + ts_stream = prog->streams; + while (ts_stream) { + M2Pid *atspid = ts_stream->ifce->input_udta; + if (max_skip_ts) { + atspid->max_media_skip = (s32) (max_media_skip * atspid->esi.timescale / max_skip_ts); + } else { + atspid->max_media_skip = 0; + } + ts_stream = ts_stream->next; + } + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CTS_SHIFT); + if (p) { + u64 diff = p->value.uint; + diff *= 1000000; + diff /= tspid->esi.timescale; + if (diff > tspid->prog->cts_offset) + tspid->prog->cts_offset = (u32) diff; + } + return GF_OK; +} + +static void tsmux_assign_pcr(GF_TSMuxCtx *ctx) +{ + GF_M2TS_Mux_Program *prog = ctx->mux->programs; + while (prog) { + GF_M2TS_Mux_Stream *stream; + if (prog->pcr) { + prog = prog->next; + continue; + } + stream = prog->streams; + while (stream) { + if (stream->ifce->stream_type==GF_STREAM_VISUAL) { + prog->pcr = stream; + break; + } + stream = stream->next; + } + if (!prog->pcr) prog->pcr = prog->streams; + ctx->update_mux = GF_TRUE; + prog = prog->next; + } +} + +static Bool tsmux_init_buffering(GF_Filter *filter, GF_TSMuxCtx *ctx) +{ + u32 mbuf = ctx->breq*1000; + u32 i, count = gf_filter_get_ipid_count(filter); + for (i=0; iinit_buffering = GF_FALSE; + gf_m2ts_mux_update_config(ctx->mux, GF_TRUE); + return GF_TRUE; +} + +static void tsmux_send_seg_event(GF_Filter *filter, GF_TSMuxCtx *ctx) +{ + GF_FilterEvent evt; + u32 i; + M2Pid *tspid = NULL; + + for (i=0; ipids); i++) { + tspid = gf_list_get(ctx->pids, i); + if (ctx->nb_sidx_entries) break; + if (ctx->ref_pid == tspid->mstream->pid) break; + tspid = NULL; + } + if (!tspid) tspid = gf_list_get(ctx->pids, 0); + + if (ctx->nb_sidx_entries) { + GF_FilterPacket *idx_pck; + u8 *output; + Bool large_sidx = GF_FALSE; + u32 segidx_size=0; + u64 last_pck_dur = tspid->pck_duration; + last_pck_dur *= 90000; + last_pck_dur /= tspid->esi.timescale; + + if (ctx->sidx_entries[ctx->nb_sidx_entries-1].sap_time > 0xFFFFFFFFUL) + large_sidx = GF_TRUE; + if (ctx->sidx_entries[0].offset*188 > 0xFFFFFFFFUL) + large_sidx = GF_TRUE; + + //styp box: 8(box) + 4(major) + 4(version) + 4(compat brand) + segidx_size = 20; + //sidx size: 12 (fullbox) + 8 + large ? 16 : 8 + 4 + nb_entries+12 + segidx_size += 24 +( large_sidx ? 16 : 8) + ctx->nb_sidx_entries*12; + + if (!ctx->idx_opid) { + const char *ext = gf_file_ext_start(ctx->idx_file_name); + if (!ext) ext = "idx"; + else ext++; + ctx->idx_filter = gf_filter_connect_destination(filter, ctx->idx_file_name, NULL); + ctx->idx_opid = gf_filter_pid_new(filter); + gf_filter_pid_set_property(ctx->idx_opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); + gf_filter_pid_set_property(ctx->idx_opid, GF_PROP_PID_FILE_EXT, &PROP_STRING(ext) ); + gf_filter_pid_set_property(ctx->idx_opid, GF_PROP_PID_MIME, &PROP_STRING("*") ); + gf_filter_pid_set_name(ctx->idx_opid, "ts_idx"); + if (ctx->idx_filter) gf_filter_set_source(ctx->idx_filter, filter, NULL); + } + idx_pck = gf_filter_pck_new_alloc(ctx->idx_opid, segidx_size, &output); + + if (!ctx->idx_bs) ctx->idx_bs = gf_bs_new(output, segidx_size, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->idx_bs, output, segidx_size); + + //write styp box + gf_bs_write_u32(ctx->idx_bs, 20); + gf_bs_write_u32(ctx->idx_bs, GF_4CC('s','t','y','p') ); + gf_bs_write_u32(ctx->idx_bs, GF_4CC('s','i','s','x') ); + gf_bs_write_u32(ctx->idx_bs, 0); + gf_bs_write_u32(ctx->idx_bs, GF_4CC('s','i','s','x') ); + + //write sidx box + gf_bs_write_u32(ctx->idx_bs, segidx_size - 20); + gf_bs_write_u32(ctx->idx_bs, GF_4CC('s','i','d','x') ); + gf_bs_write_u8(ctx->idx_bs, large_sidx ? 1 : 0); + gf_bs_write_int(ctx->idx_bs, 0, 24); + //reference id + gf_bs_write_u32(ctx->idx_bs, ctx->ref_pid); + //timescale + gf_bs_write_u32(ctx->idx_bs, 90000); + if (large_sidx) { + gf_bs_write_u64(ctx->idx_bs, ctx->sidx_entries[0].min_pts_plus_one-1); + gf_bs_write_u64(ctx->idx_bs, ctx->sidx_entries[0].offset*188); + } else { + gf_bs_write_u32(ctx->idx_bs, (u32) ctx->sidx_entries[0].min_pts_plus_one-1); + gf_bs_write_u32(ctx->idx_bs, (u32) ctx->sidx_entries[0].offset*188); + } + gf_bs_write_u16(ctx->idx_bs, 0); + gf_bs_write_u16(ctx->idx_bs, ctx->nb_sidx_entries); + for (i=0; inb_sidx_entries; i++) { + u64 duration = ctx->sidx_entries[i].max_pts - (ctx->sidx_entries[i].min_pts_plus_one-1); + if (i+1 == ctx->nb_sidx_entries) duration += last_pck_dur; + + gf_bs_write_int(ctx->idx_bs, 0, 1); + gf_bs_write_int(ctx->idx_bs, ctx->sidx_entries[i].nb_pck * 188, 31); + gf_bs_write_int(ctx->idx_bs, (u32) duration, 32); + gf_bs_write_int(ctx->idx_bs, ctx->sidx_entries[i].sap_type ? 1 : 0, 1); + gf_bs_write_int(ctx->idx_bs, ctx->sidx_entries[i].sap_type, 3); + gf_bs_write_int(ctx->idx_bs, (u32) (ctx->sidx_entries[i].sap_time - (ctx->sidx_entries[i].min_pts_plus_one-1) ), 28); + } + gf_filter_pck_set_property(idx_pck, GF_PROP_PCK_FILENAME, &PROP_STRING(ctx->idx_file_name) ); + + gf_filter_pck_send(idx_pck); + ctx->nb_sidx_entries = 0; + } + + + GF_FEVT_INIT(evt, GF_FEVT_SEGMENT_SIZE, tspid->ipid); + evt.seg_size.media_range_start = 188*ctx->pck_start_idx; + evt.seg_size.media_range_end = evt.seg_size.media_range_start + 188*ctx->nb_pck_in_seg - 1; + + gf_filter_pid_send_event(tspid->ipid, &evt); + ctx->nb_pck_in_seg = 0; + ctx->nb_sidx_entries = 0; +} + +static void tsmux_insert_sidx(GF_TSMuxCtx *ctx, Bool final_flush) +{ + if (ctx->subs_sidx<0) return; + + if (!ctx->ref_pid && ctx->mux->sap_inserted) + ctx->ref_pid = ctx->mux->last_pid; + if (!ctx->ref_pid) return; + + if (ctx->nb_sidx_entries) { + TS_SIDX *tsidx = &ctx->sidx_entries[ctx->nb_sidx_entries-1]; + + if (ctx->ref_pid == ctx->mux->last_pid) { + if (!tsidx->min_pts_plus_one) tsidx->min_pts_plus_one = ctx->mux->last_pts + 1; + else if (tsidx->min_pts_plus_one-1 > ctx->mux->last_pts) tsidx->min_pts_plus_one = ctx->mux->last_pts + 1; + + if (tsidx->max_pts < ctx->mux->last_pts) tsidx->max_pts = ctx->mux->last_pts; + } + + if (!final_flush && !ctx->mux->sap_inserted) return; + + tsidx->nb_pck = ctx->nb_pck_in_seg - tsidx->nb_pck; + } + + if (final_flush) return; + if (!ctx->mux->sap_inserted) return; + + if (ctx->nb_sidx_entries == ctx->nb_sidx_alloc) { + ctx->nb_sidx_alloc += 10; + ctx->sidx_entries = gf_realloc(ctx->sidx_entries, sizeof(TS_SIDX)*ctx->nb_sidx_alloc); + } + ctx->sidx_entries[ctx->nb_sidx_entries].sap_time = ctx->mux->sap_time; + ctx->sidx_entries[ctx->nb_sidx_entries].sap_type = ctx->mux->sap_type; + ctx->sidx_entries[ctx->nb_sidx_entries].min_pts_plus_one = ctx->mux->sap_time + 1; + ctx->sidx_entries[ctx->nb_sidx_entries].max_pts = ctx->mux->sap_time; + ctx->sidx_entries[ctx->nb_sidx_entries].nb_pck = ctx->nb_sidx_entries ? ctx->nb_pck_in_seg : 0; + ctx->sidx_entries[ctx->nb_sidx_entries].offset = ctx->nb_sidx_entries ? 0 : ctx->nb_pck_first_sidx; + ctx->nb_sidx_entries ++; +} + +static GF_Err tsmux_process(GF_Filter *filter) +{ + u32 nb_pck_in_pack, nb_pck_in_call; + GF_M2TSMuxState status; + u32 usec_till_next; + GF_FilterPacket *pck; + GF_TSMuxCtx *ctx = gf_filter_get_udta(filter); + + if (ctx->check_pcr) { + ctx->check_pcr = GF_FALSE; + tsmux_assign_pcr(ctx); + } + + if (ctx->init_buffering && !tsmux_init_buffering(filter, ctx)) return GF_OK; + + if (ctx->init_dash) { + u32 i, count = gf_list_count(ctx->pids); + for (i=0; ipids, i); + pck = gf_filter_pid_get_packet(tspid->ipid); + if (!pck) return GF_OK; + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM); + if (p) + tspid->ctx->dash_seg_num = p->value.uint; + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME); + if (p) + strcpy(tspid->ctx->dash_file_name, p->value.string); + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_IDXFILENAME); + if (p) + strcpy(tspid->ctx->idx_file_name, p->value.string); + } + ctx->init_dash = GF_FALSE; + ctx->next_is_start = GF_TRUE; + ctx->wait_dash_flush = GF_FALSE; + } + + if (ctx->update_mux) { + gf_m2ts_mux_update_config(ctx->mux, GF_FALSE); + } + + + if (ctx->wait_dash_flush) { + u32 i, done=0, count = gf_list_count(ctx->pids); + for (i=0; ipids, i); + if (tspid->has_seen_eods) done++; + } + + if (done==count) { + for (i=0; ipids, i); + tspid->has_seen_eods = 0; + } + ctx->wait_dash_flush = GF_FALSE; + ctx->next_is_start = ctx->dash_file_switch; + ctx->mux->force_pat = GF_TRUE; + ctx->dash_file_switch = GF_FALSE; + if (ctx->nb_pck_in_seg) { + tsmux_insert_sidx(ctx, GF_TRUE); + tsmux_send_seg_event(filter, ctx); + } + ctx->dash_seg_num = 0; + + ctx->nb_pck_in_seg = 0; + ctx->pck_start_idx = ctx->nb_pck; + if (ctx->next_is_start) ctx->nb_pck_in_file = 0; + ctx->nb_pck_first_sidx = ctx->nb_pck_in_file; + } + } + + nb_pck_in_call = 0; + nb_pck_in_pack=0; + while (1) { + u64 pck_ts; + u8 *output; + u32 osize; + Bool is_pack_flush = GF_FALSE; + const char *ts_pck; + + ts_pck = gf_m2ts_mux_process(ctx->mux, &status, &usec_till_next); + if (ts_pck == NULL) { + if (!nb_pck_in_pack) + break; + ts_pck = (const char *) ctx->pack_buffer; + is_pack_flush = GF_TRUE; + } else { + + tsmux_insert_sidx(ctx, GF_FALSE); + + if (ctx->nb_pack>1) { + memcpy(ctx->pack_buffer + 188 * nb_pck_in_pack, ts_pck, 188); + nb_pck_in_pack++; + + if (nb_pck_in_pack < ctx->nb_pack) + continue; + + ts_pck = (const char *) ctx->pack_buffer; + } else { + nb_pck_in_pack = 1; + } + } + osize = nb_pck_in_pack * 188; + pck = gf_filter_pck_new_alloc(ctx->opid, osize, &output); + memcpy(output, ts_pck, osize); + gf_filter_pck_set_framing(pck, ctx->nb_pck ? ctx->next_is_start : GF_TRUE, (status==GF_M2TS_STATE_EOS) ? GF_TRUE : GF_FALSE); + + if (ctx->next_is_start && ctx->dash_mode) { + GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[M2TSMux] starting TS segment %d\r", ctx->dash_seg_num)); + gf_filter_pck_set_property(pck, GF_PROP_PCK_FILENUM, &PROP_UINT(ctx->dash_seg_num) ); + if (ctx->dash_file_name[0]) + gf_filter_pck_set_property(pck, GF_PROP_PCK_FILENAME, &PROP_STRING(ctx->dash_file_name) ) ; + + ctx->dash_file_name[0] = 0; + ctx->next_is_start = GF_FALSE; + } + + pck_ts = gf_m2ts_get_ts_clock_90k(ctx->mux); + gf_filter_pck_set_dts(pck, pck_ts); + gf_filter_pck_set_cts(pck, pck_ts); + + if (ctx->notify_filename) { + gf_filter_pck_set_framing(pck, GF_TRUE, (status==GF_M2TS_STATE_EOS) ? GF_TRUE : GF_FALSE); + gf_filter_pck_set_property(pck, GF_PROP_PCK_FILENUM, &PROP_UINT(ctx->cur_file_idx_plus_one-1)); + if (ctx->cur_file_suffix) { + gf_filter_pck_set_property(pck, GF_PROP_PCK_FILESUF, &PROP_STRING_NO_COPY(ctx->cur_file_suffix)); + ctx->cur_file_suffix = NULL; + } + ctx->notify_filename = GF_FALSE; + } + gf_filter_pck_send(pck); + ctx->nb_pck += nb_pck_in_pack; + ctx->nb_pck_in_seg += nb_pck_in_pack; + ctx->nb_pck_in_file += nb_pck_in_pack; + nb_pck_in_call += nb_pck_in_pack; + nb_pck_in_pack = 0; + + if (is_pack_flush) + break; + + if (status>=GF_M2TS_STATE_PADDING) { + break; + } + if (nb_pck_in_call>100) + break; + } + + if (gf_filter_reporting_enabled(filter)) { + char szStatus[1024]; + if (status==GF_M2TS_STATE_EOS) { + Double ohead = 0; + u64 total_bytes_out = ctx->nb_pck; + total_bytes_out *= 188; + + if (ctx->total_bytes_in) ohead = ((Double) (total_bytes_out - ctx->total_bytes_in)*100 / ctx->total_bytes_in); + + sprintf(szStatus, "done - TS clock % 6d ms bitrate %d kbps - bytes "LLD" in "LLD" out overhead %02.02f%%", gf_m2ts_get_ts_clock(ctx->mux), ctx->mux->bit_rate/1000, ctx->total_bytes_in, total_bytes_out, ohead); + gf_filter_update_status(filter, 10000, szStatus); + } else { + + sprintf(szStatus, "sysclock % 6d ms TS clock % 6d ms bitrate % 8d kbps", gf_m2ts_get_sys_clock(ctx->mux), gf_m2ts_get_ts_clock(ctx->mux), ctx->mux->bit_rate/1000); + gf_filter_update_status(filter, -1, szStatus); + } + } + + if (ctx->nb_suspended && (ctx->nb_suspended==gf_list_count(ctx->pids)) ) { + u32 i, count = gf_list_count(ctx->pids); + for (i=0; ipids, i); + tspid->esi.caps &= ~GF_ESI_STREAM_IS_OVER; + tspid->suspended = GF_FALSE; + } + ctx->nb_suspended = 0; + ctx->mux->force_pat = GF_TRUE; + ctx->notify_filename = GF_TRUE; + status = GF_M2TS_STATE_IDLE; + } + + if (status==GF_M2TS_STATE_EOS) { + gf_filter_pid_set_eos(ctx->opid); + if (ctx->nb_pck_in_seg) { + tsmux_insert_sidx(ctx, GF_TRUE); + tsmux_send_seg_event(filter, ctx); + ctx->nb_pck_in_seg = 0; + } + return GF_EOS; + } + + if (ctx->realtime) { + u32 now = gf_sys_clock(); + if (!ctx->last_log_time) + ctx->last_log_time = now; + else if (now > ctx->last_log_time + ctx->log_freq) { + ctx->last_log_time = now; + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("[M2TSMux] time % 6d TS time % 6d bitrate % 8d\r", gf_m2ts_get_sys_clock(ctx->mux), gf_m2ts_get_ts_clock(ctx->mux), ctx->mux->average_birate_kbps)); + } + if (status == GF_M2TS_STATE_IDLE) { +#if 0 + u64 sleep_for=0; + /*wait till next packet is ready to be sent*/ + if (usec_till_next>1000) { + sleep_for = usec_till_next; + } + if (sleep_for) + gf_filter_ask_rt_reschedule(filter, (u32) sleep_for); +#else + //we don't have enough precision on usec counting and we end up eating one core on most machines, + // so let's just sleep one ms whenever we are idle - it's maybe too much but the muxer will catchup afterwards + gf_filter_ask_rt_reschedule(filter, 1000); +#endif + } + } + //PMT update management is still under progress... + ctx->pmt_update_pending = 0; + + return GF_OK; +} + +static GF_Err tsmux_initialize(GF_Filter *filter) +{ + GF_TSMuxCtx *ctx = gf_filter_get_udta(filter); + gf_filter_set_max_extra_input_pids(filter, -1); + + ctx->mux = gf_m2ts_mux_new(ctx->rate, ctx->pat_rate, ctx->realtime); + ctx->mux->flush_pes_at_rap = ctx->flush_rap; + + if (gf_sys_is_test_mode() && ctx->pcr_init<0) + ctx->pcr_init = 1000000; + + gf_m2ts_mux_use_single_au_pes_mode(ctx->mux, ctx->pes_pack); + if (ctx->pcr_init>=0) gf_m2ts_mux_set_initial_pcr(ctx->mux, (u64) ctx->pcr_init); + gf_m2ts_mux_set_pcr_max_interval(ctx->mux, ctx->max_pcr); + gf_m2ts_mux_enable_pcr_only_packets(ctx->mux, ctx->pcr_only); + + if (!ctx->sid) ctx->sid = 1; + if (ctx->sdt_rate) { + gf_m2ts_mux_enable_sdt(ctx->mux, ctx->sdt_rate); + } + + if (!gf_filter_block_enabled(filter)) { + ctx->breq = 0; + } else { + ctx->init_buffering = GF_TRUE; + } + ctx->pids = gf_list_new(); + if (ctx->nb_pack>1) ctx->pack_buffer = gf_malloc(sizeof(char)*188*ctx->nb_pack); + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + gf_m2ts_get_sys_clock(ctx->mux); + } +#endif + return GF_OK; +} + + +static void tsmux_finalize(GF_Filter *filter) +{ + GF_TSMuxCtx *ctx = gf_filter_get_udta(filter); +#ifndef GPAC_DISABLE_LOG + u64 bits = ctx->mux->tot_pck_sent*8*188; +#endif + u64 dur_ms = gf_m2ts_get_ts_clock(ctx->mux); + if (!dur_ms) dur_ms = 1; + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("[M2TSMux] Done muxing - %.02f sec - %sbitrate %d kbps "LLD" packets written\nPadding: "LLD" packets (%g kbps) - "LLD" PES padded bytes (%g kbps)\n", + ((Double) dur_ms)/1000.0, ctx->rate ? "" : "average ", (u32) (bits/dur_ms), ctx->mux->tot_pck_sent, + ctx->mux->tot_pad_sent, (Double) (ctx->mux->tot_pad_sent*188*8.0/dur_ms) , ctx->mux->tot_pes_pad_bytes, (Double) (ctx->mux->tot_pes_pad_bytes*8.0/dur_ms) + )); + + while (gf_list_count(ctx->pids)) { + M2Pid *tspid = gf_list_pop_back(ctx->pids); + tsmux_del_stream(tspid); + } + gf_list_del(ctx->pids); + gf_m2ts_mux_del(ctx->mux); + if (ctx->pack_buffer) gf_free(ctx->pack_buffer); + if (ctx->sidx_entries) gf_free(ctx->sidx_entries); + if (ctx->idx_bs) gf_bs_del(ctx->idx_bs); + if (ctx->cur_file_suffix) gf_free(ctx->cur_file_suffix); +} + +static Bool tsmux_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + if ((evt->base.type==GF_FEVT_STOP) || (evt->base.type==GF_FEVT_PLAY) ) { + u32 i; + GF_TSMuxCtx *ctx = gf_filter_get_udta(filter); + for (i=0; ipids); i++) { + M2Pid *tspid = gf_list_get(ctx->pids, i); + if (evt->base.type==GF_FEVT_STOP) + tspid->esi.caps |= GF_ESI_STREAM_IS_OVER; + else + tspid->esi.caps &= ~GF_ESI_STREAM_IS_OVER; + } + } + return GF_FALSE; +} + +static const GF_FilterCapability TSMuxCaps[] = +{ + //first set of caps describe streams that need reframing (NAL) + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + //unframed streams only + CAP_BOOL(GF_CAPS_INPUT, GF_PROP_PID_UNFRAMED, GF_TRUE), + //for NAL-based, we want annexB format + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_AVC), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_MVC), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_SVC), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_HEVC), + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_LHVC), + //for m4vp2 we want DSI reinsertion + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_MPEG4_PART2), + //for AAC we use the AAC->ADTS or AAC->LATM of the mux, so don't insert here + + //static output cap file extension + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_FILE_EXT, M2TS_FILE_EXTS), + CAP_STRING(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_MIME, M2TS_MIMES), + {0}, + + //for now don't accept files as input, although we could store them as items, to refine + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + //these caps are framed + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + //exclude caps from above + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_AVC), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_MVC), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_SVC), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_HEVC), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_LHVC), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_MPEG4_PART2), + //no RAW support for now + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + + +#define OFFS(_n) #_n, offsetof(GF_TSMuxCtx, _n) +static const GF_FilterArgs TSMuxArgs[] = +{ + { OFFS(breq), "buffer requirements in ms for input pids", GF_PROP_UINT, "100", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(pmt_id), "define the ID of the first PMT to use in the mux", GF_PROP_UINT, "100", NULL, 0}, + { OFFS(rate), "target rate in bps of the multiplex. If not set, variable rate is used", GF_PROP_UINT, "0", NULL, 0}, + { OFFS(pmt_rate), "interval between PMT in ms", GF_PROP_UINT, "200", NULL, 0}, + { OFFS(pat_rate), "interval between PAT in ms", GF_PROP_UINT, "200", NULL, 0}, + { OFFS(first_pts), "force PTS value of first packet, in 90kHz", GF_PROP_LUINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(pcr_offset), "offset all timestamps from PCR by V, in 90kHz. Default value is computed based on input media", GF_PROP_LUINT, "-1", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(mpeg4), "force usage of MPEG-4 signaling (IOD and SL Config)\n"\ + "- none: disables 4on2\n"\ + "- full: sends AUs as SL packets over section for OD, section/pes for scene (cf bifs_pes)\n"\ + "- scene: sends only scene streams as 4on2 but uses regular PES without SL for audio and video"\ + , GF_PROP_UINT, "none", "none|full|scene", GF_FS_ARG_HINT_EXPERT}, + { OFFS(pmt_version), "set version number of the PMT", GF_PROP_UINT, "200", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(disc), "set the discontinuity marker for the first packet of each stream", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(repeat_rate), "interval in ms between two carousel send for MPEG-4 systems. Is overridden by carousel duration PID property if defined", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(repeat_img), "interval in ms between re-sending (as PES) of single-image streams. If 0, image data is sent once only", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(max_pcr), "set max interval in ms between 2 PCR", GF_PROP_UINT, "100", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(nb_pack), "pack N TS packets in output packets", GF_PROP_UINT, "4", NULL, 0}, + { OFFS(pes_pack), "set AU to PES packing mode\n"\ + "- audio: will pack only multiple audio AUs in a PES\n"\ + "- none: make exactly one AU per PES\n"\ + "- all: will pack multiple AUs per PES for all streams", GF_PROP_UINT, "audio", "audio|none|all", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(realtime), "use real-time output", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(bifs_pes), "select BIFS streams packetization (PES vs sections)\n" + "- on: uses BIFS PES\n" + "- off: uses BIFS sections\n" + "- copy: uses BIFS PES but removes timestamps in BIFS SL and only carries PES timestamps", GF_PROP_UINT, "off", "off|on|copy", GF_FS_ARG_HINT_EXPERT}, + { OFFS(flush_rap), "force flushing mux program when RAP is found on video, and injects PAT and PMT before the next video PES begin", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(pcr_only), "enable PCR-only TS packets", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(pcr_init), "set initial PCR value for the programs. Negative value implies random value is picked", GF_PROP_LSINT, "-1", NULL, 0}, + { OFFS(sid), "set service ID for the program - see filter help", GF_PROP_UINT, "0", NULL, 0}, + { OFFS(name), "set service name for the program - see filter help", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(provider), "set service provider name for the program - see filter help", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(sdt_rate), "interval in ms between two DVB SDT tables. If 0, SDT is disabled", GF_PROP_UINT, "0", NULL, 0}, + + { OFFS(temi), "insert TEMI time codes in adaptation field - see filter help", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(log_freq), "delay between logs for realtime mux", GF_PROP_UINT, "500", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(latm), "use LATM AAC encapsulation instead of regular ADTS", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(subs_sidx), "number of subsegments per sidx. negative value disables sidx", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + + +GF_FilterRegister TSMuxRegister = { + .name = "m2tsmx", + GF_FS_SET_DESCRIPTION("MPEG-2 TS muxer") + GF_FS_SET_HELP("GPAC TS multiplexer selects M2TS PID for media streams using the PID of the PMT plus the stream index.\n" + "For example, default config creates the first program with a PMT PID 100, the first stream will have a PID of 101.\n" + "Streams are grouped in programs based on input PID property ServiceID if present. If absent, stream will go in the program with service ID as indicated by [-sid]() option.\n" + "- [-name]() option is overridden by input PID property `ServiceName`.\n" + "- [-provider]() option is overridden by input PID property `ServiceProvider`.\n" + "- [-pcr_offset]() option is overridden by input PID property `\"tsmux:pcr_offset\"`\n" + "- [-first_pts]() option is overridden by input PID property `\"tsmux:force_pts\"`\n" + "- [-temi]() option is overridden by input PID property `\"tsmux:temi\"`\n" + + "\n" + "# Time and External Media Information (TEMI)\n" + "The [-temi]() option allows specifying a list of URLs or timeline IDs to insert in streams of a program.\n" + "One or more TEMI timeline can be specified per PID.\n" + "The syntax is a comma-separated list of one or more TEMI description.\n" + "Each TEMI description is formatted as ID_OR_URL or #OPT1[#OPT2]#ID_OR_URL. Options are:\n" + "- S`N`: gives number N indicating the target serviceID\n" + "- T`N`: set timescale to use (default: PID timescale)\n" + "- D`N`: set delay in ms between two TEMI url descriptors (default 1000)\n" + "- O`N`: set offset (max 64 bits) to add to TEMI timecodes (default 0). If timescale is not specified, offset value is in ms, otherwise in timescale units.\n" + "- I`N`: set initial value (max 64 bits) of TEMI timecodes. If not set, initial value will match first packet CTS. If timescale is not specified, value is in PID timescale units, otherwise in specified timescale units.\n" + "- P`N`: indicate target PID in program. Possible values are\n" + " - `V`: only insert for video streams.\n" + " - `A`: only insert for audio streams.\n" + " - `T`: only insert for text streams.\n" + " - N: only insert for stream with index N (0-based) in the program.\n" + "- L`N`: set 64bit timecode signaling. Possible values are:\n" + " - `A`: automatic switch between 32 and 64 bit depending on timecode value (default if not specified).\n" + " - `Y`: use 64 bit signaling only.\n" + " - `N`: use 32 bit signaling only and wrap around timecode value.\n" + "- N: insert NTP timestamp in TEMI timeline descriptor\n" + "- ID_OR_URL: If number, indicates the TEMI ID to use for external timeline. Otherwise, gives the URL to insert\n" + " \n" + "EX temi=\"url\"\n" + "Inserts a TEMI URL+timecode in the each stream of each program.\n" + "EX temi=\"#P0#url,#P1#4\"\n" + "Inserts a TEMI URL+timecode in the first stream of all programs and an external TEMI with ID 4 in the second stream of all programs.\n" + "EX temi=\"#P0#2,#P0#url,#P1#4\"\n" + "Inserts a TEMI with ID 2 and a TEMI URL+timecode in the first stream of all programs, and an external TEMI with ID 4 in the second stream of all programs.\n" + "EX temi=\"#S20#4,#S10#URL\"\n" + "Inserts an external TEMI with ID 4 in the each stream of program with ServiceID 20 and a TEMI URL in each stream of program with ServiceID 10.\n" + "EX temi=\"#N#D500#PV#T30000#4\"\n" + "Inserts an external TEMI with ID 4 and timescale 30000, NTP injection and carousel of 500 ms in the video stream of all programs.\n" + "\n" + "Warning: multipliers (k,m,g) are not supported in TEMI options.\n" + "# Notes\n" + "In DASH mode, the PCR is always initialized at 0, and [-flush_rap]() is automatically set.\n" + "The filter watches the property `FileNumber` on incoming packets to create new files or new segments in DASH mode.\n" + ) + .private_size = sizeof(GF_TSMuxCtx), + .args = TSMuxArgs, + .initialize = tsmux_initialize, + .finalize = tsmux_finalize, + .flags = GF_FS_REG_DYNAMIC_REDIRECT, + SETCAPS(TSMuxCaps), + .configure_pid = tsmux_configure_pid, + .process = tsmux_process, + .process_event = tsmux_process_event, +}; + + +const GF_FilterRegister *tsmux_register(GF_FilterSession *session) +{ + return &TSMuxRegister; +} diff --git a/src/filters/out_audio.c b/src/filters/out_audio.c new file mode 100644 index 0000000..18943bd --- /dev/null +++ b/src/filters/out_audio.c @@ -0,0 +1,579 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018 + * All rights reserved + * + * This file is part of GPAC / audio output filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include +#include +#include + +typedef struct +{ + //options + char *drv; + u32 bnum, bdur, threaded, priority; + Bool clock; + GF_Fraction64 dur; + Double speed, start; + u32 vol, pan, buffer; + GF_Fraction adelay; + + GF_FilterPid *pid; + u32 sr, afmt, nb_ch, timescale; + u64 ch_cfg; + GF_AudioOutput *audio_out; + GF_Thread *th; + u32 audio_th_state; + Bool needs_recfg, wait_recfg; + u32 bytes_per_sample; + + u32 pck_offset; + u64 first_cts; + Bool aborted; + Bool speed_set; + GF_Filter *filter; + Bool is_eos; + Bool first_write_done; + + s32 pid_delay; + + Bool buffer_done, no_buffering; + u64 hwdelay_us, totaldelay_us; +} GF_AudioOutCtx; + + +void aout_reconfig(GF_AudioOutCtx *ctx) +{ + u32 sr, afmt, old_afmt, nb_ch; + u64 ch_cfg; + GF_Err e = GF_OK; + sr = ctx->sr; + + nb_ch = ctx->nb_ch; + afmt = old_afmt = ctx->afmt; + ch_cfg = ctx->ch_cfg; + + //config not ready, wait + if (!nb_ch || !sr || !afmt) { + //force a get_packet to trigger reconfigure + gf_filter_pid_get_packet(ctx->pid); + return; + } + + e = ctx->audio_out->Configure(ctx->audio_out, &sr, &nb_ch, &afmt, ch_cfg); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[AudioOut] Failed to configure audio output: %s\n", gf_error_to_string(e) )); + afmt = GF_AUDIO_FMT_S16; + sr = 44100; + nb_ch = 2; + } + if (ctx->speed == FIX_ONE) ctx->speed_set = GF_TRUE; + + if (ctx->vol<=100) { + if (ctx->audio_out->SetVolume) + ctx->audio_out->SetVolume(ctx->audio_out, ctx->vol); + ctx->vol = 101; + } + if (ctx->pan<=100) { + if (ctx->audio_out->SetPan) + ctx->audio_out->SetPan(ctx->audio_out, ctx->pan); + ctx->pan = 101; + } + + + if (ctx->sr * ctx->nb_ch * old_afmt == 0) { + ctx->needs_recfg = GF_FALSE; + ctx->wait_recfg = GF_FALSE; + return; + } + + if ((sr != ctx->sr) || (nb_ch!=ctx->nb_ch) || (afmt!=old_afmt) || !ctx->speed_set) { + gf_filter_pid_negociate_property(ctx->pid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(sr)); + gf_filter_pid_negociate_property(ctx->pid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(afmt)); + gf_filter_pid_negociate_property(ctx->pid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(nb_ch)); + gf_filter_pid_negociate_property(ctx->pid, GF_PROP_PID_AUDIO_SPEED, &PROP_DOUBLE(ctx->speed)); + ctx->speed_set = GF_TRUE; + ctx->needs_recfg = GF_FALSE; + //drop all packets until next reconfig + ctx->wait_recfg = GF_TRUE; + ctx->sr = sr; + ctx->nb_ch = nb_ch; + ctx->afmt = afmt; + ctx->ch_cfg = ch_cfg; + } else if (e==GF_OK) { + ctx->needs_recfg = GF_FALSE; + ctx->wait_recfg = GF_FALSE; + } + ctx->bytes_per_sample = gf_audio_fmt_bit_depth(afmt) * nb_ch / 8; + ctx->hwdelay_us = 0; + if (ctx->audio_out->GetAudioDelay) { + ctx->hwdelay_us = ctx->audio_out->GetAudioDelay(ctx->audio_out); + ctx->hwdelay_us *= 1000; + GF_LOG(GF_LOG_INFO, GF_LOG_CORE, ("[AudioOut] Hardware delay is "LLU" us\n", ctx->hwdelay_us )); + } + ctx->totaldelay_us = 0; + if (ctx->audio_out->GetTotalBufferTime) { + ctx->totaldelay_us = ctx->audio_out->GetTotalBufferTime(ctx->audio_out); + ctx->totaldelay_us *= 1000; + GF_LOG(GF_LOG_INFO, GF_LOG_CORE, ("[AudioOut] Total audio delay is "LLU" ms\n", ctx->totaldelay_us )); + } +} + +u32 aout_th_proc(void *p) +{ + GF_AudioOutCtx *ctx = (GF_AudioOutCtx *) p; + + ctx->audio_th_state = 1; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[AudioOut] Entering audio thread ID %d\n", gf_th_id() )); + + while (ctx->audio_th_state == 1) { + if (ctx->needs_recfg) { + aout_reconfig(ctx); + } else if (ctx->pid) { + ctx->audio_out->WriteAudio(ctx->audio_out); + } else { + gf_sleep(10); + } + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[AudioOut] Exiting audio thread\n")); + ctx->audio_out->Shutdown(ctx->audio_out); + ctx->audio_th_state = 3; + return 0; +} + + +static u32 aout_fill_output(void *ptr, u8 *buffer, u32 buffer_size) +{ + u32 done = 0; + GF_AudioOutCtx *ctx = ptr; + Bool is_first_pck = GF_TRUE; + + memset(buffer, 0, buffer_size); + if (!ctx->pid || ctx->aborted) return 0; + + if (!ctx->buffer_done) { + u32 size; + GF_FilterPacket *pck; + + //query full buffer duration in us + u64 dur = gf_filter_pid_query_buffer_duration(ctx->pid, GF_FALSE); + + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[AudioOut] buffer %d / %d ms\r", dur/1000, ctx->buffer)); + + /*the compositor sends empty packets after its reconfiguration to check when the config is active + we therefore probe the first packet before probing the buffer fullness*/ + pck = gf_filter_pid_get_packet(ctx->pid); + if (!pck) return 0; + + if (! gf_filter_pck_is_blocking_ref(pck)) { + if ((dur < ctx->buffer * 1000) && !gf_filter_pid_is_eos(ctx->pid)) + return 0; + gf_filter_pck_get_data(pck, &size); + if (!size) { + gf_filter_pid_drop_packet(ctx->pid); + return 0; + } + //check the decoder output is full (avoids initial underrun) + if (gf_filter_pid_query_buffer_duration(ctx->pid, GF_TRUE)==0) + return 0; + } + ctx->buffer_done = GF_TRUE; + } + //do not throw underflow log util first packet is fetched + if (ctx->first_write_done) + is_first_pck = GF_FALSE; + + while (done < buffer_size) { + const char *data; + u32 size; + u64 cts; + s64 delay; + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->pid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->pid)) { + ctx->is_eos = GF_TRUE; + } else if (!is_first_pck) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[AudioOut] buffer underflow\n")); + } + return done; + } + ctx->is_eos = GF_FALSE; + if (ctx->needs_recfg) { + return done; + } + + delay = ctx->pid_delay; + if (ctx->adelay.den) + delay += ctx->adelay.num * (s32)ctx->timescale / (s32)ctx->adelay.den; + + cts = gf_filter_pck_get_cts(pck); + if (delay >= 0) { + cts += delay; + } else if (cts < (u64) -delay) { + gf_filter_pid_drop_packet(ctx->pid); + continue; + } else { + cts -= (u64) -delay; + } + + if (ctx->dur.num>0) { + if (!ctx->first_cts) ctx->first_cts = cts+1; + + if ((cts - ctx->first_cts + 1) * ctx->dur.den > (u64) ctx->dur.num*ctx->timescale) { + gf_filter_pid_drop_packet(ctx->pid); + if (!ctx->aborted) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_STOP, ctx->pid); + gf_filter_pid_send_event(ctx->pid, &evt); + + ctx->aborted = GF_TRUE; + } + return done; + } + } + + data = gf_filter_pck_get_data(pck, &size); + + if (!done && ctx->clock && data && size) { + GF_Fraction64 timestamp; + timestamp.num = cts; + if (ctx->pck_offset) + timestamp.num += ctx->pck_offset/ctx->bytes_per_sample; + + timestamp.num -= (ctx->hwdelay_us*ctx->timescale)/1000000; + if (timestamp.num<0) timestamp.num = 0; + timestamp.den = ctx->timescale; + gf_filter_hint_single_clock(ctx->filter, gf_sys_clock_high_res(), timestamp); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[AudioOut] At %d ms audio frame CTS "LLU" (compensated time %g s)\n", gf_sys_clock(), cts, ((Double)timestamp.num)/timestamp.den )); + } + + if (data && !ctx->wait_recfg) { + u32 nb_copy; + assert(size >= ctx->pck_offset); + + nb_copy = (size - ctx->pck_offset); + if (nb_copy + done > buffer_size) nb_copy = buffer_size - done; + memcpy(buffer+done, data+ctx->pck_offset, nb_copy); + + if (!done && gf_filter_reporting_enabled(ctx->filter)) { + char szStatus[1024]; + u64 bdur = gf_filter_pid_query_buffer_duration(ctx->pid, GF_FALSE); + sprintf(szStatus, "%d Hz %d ch %s buffer %d / %d ms", ctx->sr, ctx->nb_ch, gf_audio_fmt_name(ctx->afmt), (u32) (bdur/1000), ctx->buffer); + gf_filter_update_status(ctx->filter, -1, szStatus); + } + + + done += nb_copy; + ctx->first_write_done = GF_TRUE; + is_first_pck = GF_FALSE; + if (nb_copy + ctx->pck_offset < size) { + ctx->pck_offset += nb_copy; + return done; + } + ctx->pck_offset = 0; + } + gf_filter_pid_drop_packet(ctx->pid); + } + return done; +} + +void aout_set_priority(GF_AudioOutCtx *ctx, u32 prio) +{ + if (prio==ctx->priority) return; + ctx->priority = prio; + if (ctx->th) gf_th_set_priority(ctx->th, (s32) ctx->priority); + else if (ctx->audio_out->SelfThreaded && ctx->audio_out->SetPriority) + ctx->audio_out->SetPriority(ctx->audio_out, ctx->priority); +} + +static GF_Err aout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_PropertyEntry *pe=NULL; + u32 sr, nb_ch, afmt, timescale; + u64 ch_cfg; + GF_AudioOutCtx *ctx = (GF_AudioOutCtx *) gf_filter_get_udta(filter); + + if (is_remove) { + assert(ctx->pid==pid); + ctx->pid=NULL; + return GF_OK; + } + assert(!ctx->pid || (ctx->pid==pid)); + gf_filter_pid_check_caps(pid); + + sr = afmt = nb_ch = timescale = 0; + ch_cfg = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) timescale = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); + if (p) sr = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT); + if (p) afmt = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS); + if (p) nb_ch = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CHANNEL_LAYOUT); + if (p) ch_cfg = p->value.longuint; + + if (ctx->audio_out->SetVolume) { + p = gf_filter_pid_get_info(pid, GF_PROP_PID_AUDIO_VOLUME, &pe); + if (p) ctx->audio_out->SetVolume(ctx->audio_out, p->value.uint); + } + if (ctx->audio_out->SetPan) { + p = gf_filter_pid_get_info(pid, GF_PROP_PID_AUDIO_PAN, &pe); + if (p) ctx->audio_out->SetPan(ctx->audio_out, p->value.uint); + } + gf_filter_release_property(pe); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_PRIORITY); + if (p) aout_set_priority(ctx, p->value.uint); + + if (ctx->first_cts && (ctx->timescale != timescale)) { + ctx->first_cts-=1; + ctx->first_cts *= timescale; + ctx->first_cts /= ctx->timescale; + ctx->first_cts+=1; + } + ctx->timescale = timescale; + + p = gf_filter_pid_get_property_str(pid, "BufferLength"); + ctx->no_buffering = (p && !p->value.sint) ? GF_TRUE : GF_FALSE; + if (ctx->no_buffering) ctx->buffer_done = GF_TRUE; + + if ((ctx->sr==sr) && (ctx->afmt == afmt) && (ctx->nb_ch == nb_ch) && (ctx->ch_cfg == ch_cfg)) { + ctx->needs_recfg = GF_FALSE; + ctx->wait_recfg = GF_FALSE; + return GF_OK; + } + + //whenever change of sample rate / format / channel, force buffer requirements and speed setup + if ((ctx->sr!=sr) || (ctx->afmt != afmt) || (ctx->nb_ch != nb_ch)) { + GF_FilterEvent evt; + ctx->speed_set = GF_FALSE; + + //set buffer reqs to bdur or 100 ms - we don't "buffer" in the filter, but this will allow dispatching + //several input frames in the buffer (default being 1 pck / 1000 us max in buffers). Not doing so could cause + //the session to end because input is blocked (no tasks posted) and output still holds a packet + GF_FEVT_INIT(evt, GF_FEVT_BUFFER_REQ, pid); + evt.buffer_req.max_buffer_us = ctx->buffer * 1000; + if (ctx->bdur) { + u64 b = ctx->bdur; + b *= 1000; + if (evt.buffer_req.max_buffer_us < b) + evt.buffer_req.max_buffer_us = (u32) b; + } + evt.buffer_req.pid_only = GF_TRUE; + + gf_filter_pid_send_event(pid, &evt); + + gf_filter_pid_init_play_event(pid, &evt, ctx->start, ctx->speed, "AudioOut"); + gf_filter_pid_send_event(pid, &evt); + ctx->speed = evt.play.speed; + ctx->start = evt.play.start_range; + } + + ctx->pid = pid; + ctx->sr = sr; + ctx->afmt = afmt; + ctx->nb_ch = nb_ch; + ctx->ch_cfg = ch_cfg; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_PRIORITY); + if (p) aout_set_priority(ctx, p->value.uint); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DELAY); + ctx->pid_delay = p ? p->value.sint : 0; + + ctx->needs_recfg = GF_TRUE; + + //not threaded, request a task to restart audio (cannot do it during the audio callback) + if (!ctx->th) gf_filter_post_process_task(filter); + return GF_OK; +} + +static GF_Err aout_initialize(GF_Filter *filter) +{ + const char *sOpt; + void *os_wnd_handler; + GF_Err e; + GF_AudioOutCtx *ctx = (GF_AudioOutCtx *) gf_filter_get_udta(filter); + + ctx->filter = filter; + + + ctx->audio_out = (GF_AudioOutput *) gf_module_load(GF_AUDIO_OUTPUT_INTERFACE, ctx->drv); + /*if not init we run with a NULL audio compositor*/ + if (!ctx->audio_out) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[AudioOut] No audio output modules found, cannot load audio output\n")); + return GF_IO_ERR; + } + if (!gf_opts_get_key("core", "audio-output")) { + gf_opts_set_key("core", "audio-output", ctx->audio_out->module_name); + } + + ctx->audio_out->FillBuffer = aout_fill_output; + ctx->audio_out->audio_renderer = ctx; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[AudioOut] Setting up audio module %s\n", ctx->audio_out->module_name)); + + if (!ctx->bnum || !ctx->bdur) ctx->bnum = ctx->bdur = 0; + + os_wnd_handler = NULL; + sOpt = gf_opts_get_key("Temp", "OSWnd"); + if (sOpt) sscanf(sOpt, "%p", &os_wnd_handler); + e = ctx->audio_out->Setup(ctx->audio_out, os_wnd_handler, ctx->bnum, ctx->bdur); + + if (e != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[AudioOut] Could not setup module %s\n", ctx->audio_out->module_name)); + gf_modules_close_interface((GF_BaseInterface *)ctx->audio_out); + ctx->audio_out = NULL; + return e; + } + /*only used for coverage for now*/ + if (ctx->audio_out->QueryOutputSampleRate) { + u32 sr = 48000; + u32 ch = 2; + u32 bps = 16; + ctx->audio_out->QueryOutputSampleRate(ctx->audio_out, &sr, &ch, &bps); + } + if (ctx->audio_out->SelfThreaded) { + } else if (ctx->threaded) { + ctx->th = gf_th_new("AudioOutput"); + gf_th_run(ctx->th, aout_th_proc, ctx); + } + + aout_set_priority(ctx, GF_THREAD_PRIORITY_REALTIME); + + return GF_OK; +} + +static void aout_finalize(GF_Filter *filter) +{ + GF_AudioOutCtx *ctx = (GF_AudioOutCtx *) gf_filter_get_udta(filter); + + /*stop and shutdown*/ + if (ctx->audio_out) { + /*kill audio thread*/ + if (ctx->th) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[AudioOut] stopping audio thread\n")); + ctx->audio_th_state = 2; + while (ctx->audio_th_state != 3) { + gf_sleep(33); + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[AudioOut] audio thread stopped\n")); + gf_th_del(ctx->th); + } else { + ctx->audio_out->Shutdown(ctx->audio_out); + } + gf_modules_close_interface((GF_BaseInterface *)ctx->audio_out); + ctx->audio_out = NULL; + } +} + +static GF_Err aout_process(GF_Filter *filter) +{ + GF_AudioOutCtx *ctx = (GF_AudioOutCtx *) gf_filter_get_udta(filter); + + if (!ctx->th && ctx->needs_recfg) { + aout_reconfig(ctx); + } + + if (ctx->th || ctx->audio_out->SelfThreaded) { + if (ctx->is_eos) return GF_EOS; + gf_filter_ask_rt_reschedule(filter, 100000); + return GF_OK; + } + + ctx->audio_out->WriteAudio(ctx->audio_out); + return GF_OK; +} + +static Bool aout_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_AudioOutCtx *ctx = (GF_AudioOutCtx *) gf_filter_get_udta(filter); + if (!ctx->audio_out) return GF_TRUE; + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (ctx->audio_out->Play) ctx->audio_out->Play(ctx->audio_out, evt->play.hw_buffer_reset ? 2 : 1); + break; + case GF_FEVT_STOP: + if (ctx->audio_out->Play) ctx->audio_out->Play(ctx->audio_out, 0); + break; + default: + break; + } + //cancel + return GF_TRUE; +} + +#define OFFS(_n) #_n, offsetof(GF_AudioOutCtx, _n) + +static const GF_FilterArgs AudioOutArgs[] = +{ + { OFFS(drv), "audio driver name", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(bnum), "number of audio buffers - 0 for auto", GF_PROP_UINT, "2", NULL, 0}, + { OFFS(bdur), "total duration of all buffers in ms - 0 for auto. The longer the audio buffer is, the longer the audio latency will be (pause/resume). The quality of fast forward audio playback will also be degradated when using large audio buffers", GF_PROP_UINT, "100", NULL, 0}, + { OFFS(threaded), "force dedicated thread creation if sound card driver is not threaded", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(dur), "only play the specified duration", GF_PROP_FRACTION, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(clock), "hint audio clock for this stream (reports system time and CTS), for other filters to use", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(speed), "set playback speed. If speed is negative and start is 0, start is set to -1", GF_PROP_DOUBLE, "1.0", NULL, 0}, + { OFFS(start), "set playback start offset. Negative value means percent of media dur with -1 <=> dur", GF_PROP_DOUBLE, "0.0", NULL, 0}, + { OFFS(vol), "set default audio volume, as a percentage between 0 and 100", GF_PROP_UINT, "100", "0-100", GF_FS_ARG_UPDATE}, + { OFFS(pan), "set stereo pan, as a percentage between 0 and 100, 50 being centered", GF_PROP_UINT, "50", "0-100", GF_FS_ARG_UPDATE}, + { OFFS(buffer), "set buffer in ms", GF_PROP_UINT, "200", NULL, 0}, + { OFFS(adelay), "set audio delay in sec", GF_PROP_FRACTION, "0", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + {0} +}; + +static const GF_FilterCapability AudioOutCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW), + //we accept all audio formats, but will ask for input reconfiguration if sound card does not support +}; + + +GF_FilterRegister AudioOutRegister = { + .name = "aout", + GF_FS_SET_DESCRIPTION("Audio output") + GF_FS_SET_HELP("This filter outputs a single uncompressed audio PID to a soundcard.") + .private_size = sizeof(GF_AudioOutCtx), + .args = AudioOutArgs, + SETCAPS(AudioOutCaps), + .initialize = aout_initialize, + .finalize = aout_finalize, + .configure_pid = aout_configure_pid, + .process = aout_process, + .process_event = aout_process_event +}; + +const GF_FilterRegister *aout_register(GF_FilterSession *session) +{ + return &AudioOutRegister; +} + diff --git a/src/filters/out_file.c b/src/filters/out_file.c new file mode 100644 index 0000000..5c0d7df --- /dev/null +++ b/src/filters/out_file.c @@ -0,0 +1,623 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / generic FILE output filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include +#include + +typedef struct +{ + //options + Double start, speed; + char *dst, *mime, *ext; + Bool append, dynext, cat, ow, redund; + u32 mvbk; + + //only one input pid + GF_FilterPid *pid; + + FILE *file; + Bool is_std; + u64 nb_write; + + GF_FilterCapability in_caps[2]; + char szExt[10]; + char szFileName[GF_MAX_PATH]; + + Bool patch_blocks; + Bool is_null; + GF_Err is_error; + u32 dash_mode; + u64 offset_at_seg_start; + const char *original_url; + GF_FileIO *gfio_ref; +} GF_FileOutCtx; + +#ifdef WIN32 +#include +#include +#endif //WIN32 + +static GF_Err fileout_open_close(GF_FileOutCtx *ctx, const char *filename, const char *ext, u32 file_idx, Bool explicit_overwrite, char *file_suffix) +{ + if (ctx->file && !ctx->is_std) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[FileOut] closing output file %s\n", ctx->szFileName)); + gf_fclose(ctx->file); + } + ctx->file = NULL; + + if (!filename) + return GF_OK; + + if (!strcmp(filename, "std")) ctx->is_std = GF_TRUE; + else if (!strcmp(filename, "stdout")) ctx->is_std = GF_TRUE; + else ctx->is_std = GF_FALSE; + + if (ctx->is_std) { + ctx->file = stdout; +#ifdef WIN32 + _setmode(_fileno(stdout), _O_BINARY); +#endif + + } else { + char szName[GF_MAX_PATH], szFinalName[GF_MAX_PATH]; + Bool append = ctx->append; + const char *url = filename; + + if (!strncmp(filename, "gfio://", 7)) + url = gf_fileio_translate_url(filename); + + if (ctx->dynext) { + const char *has_ext = gf_file_ext_start(url); + + strcpy(szName, url); + if (!has_ext && ext) { + strcat(szName, "."); + strcat(szName, ext); + } + } else { + strcpy(szName, url); + } + gf_filter_pid_resolve_file_template(ctx->pid, szName, szFinalName, file_idx, file_suffix); + + if (!gf_file_exists(szFinalName)) append = GF_FALSE; + + if (!ctx->ow && gf_file_exists(szFinalName) && !append) { + char szRes[21]; + s32 res; + + fprintf(stderr, "File %s already exist - override (y/n/a) ?:", szFinalName); + res = scanf("%20s", szRes); + if (!res || (szRes[0] == 'n') || (szRes[0] == 'N')) { + return ctx->is_error = GF_IO_ERR; + } + if ((szRes[0] == 'a') || (szRes[0] == 'A')) ctx->ow = GF_TRUE; + } + + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[FileOut] opening output file %s\n", szFinalName)); + ctx->file = gf_fopen_ex(szFinalName, ctx->original_url, append ? "a+b" : "w+b"); + + if (!strcmp(szFinalName, ctx->szFileName) && !ctx->append && ctx->nb_write && !explicit_overwrite) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[FileOut] re-opening in write mode output file %s, content overwrite\n", szFinalName)); + } + strcpy(ctx->szFileName, szFinalName); + } + ctx->nb_write = 0; + if (!ctx->file) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] cannot open output file %s\n", ctx->szFileName)); + return ctx->is_error = GF_IO_ERR;; + } + + return GF_OK; +} + +static void fileout_setup_file(GF_FileOutCtx *ctx, Bool explicit_overwrite) +{ + const GF_PropertyValue *p, *ext; + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_OUTPATH); + ext = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_FILE_EXT); + + if (p && p->value.string) { + fileout_open_close(ctx, p->value.string, (ext && ctx->dynext) ? ext->value.string : NULL, 0, explicit_overwrite, NULL); + } else if (ctx->dynext) { + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PCK_FILENUM); + if (!p) { + if (ext && ext->value.string) { + fileout_open_close(ctx, ctx->dst, ext->value.string, 0, explicit_overwrite, NULL); + } + } + } else { + fileout_open_close(ctx, ctx->dst, NULL, 0, explicit_overwrite, NULL); + } +} +static GF_Err fileout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_FileOutCtx *ctx = (GF_FileOutCtx *) gf_filter_get_udta(filter); + if (is_remove) { + ctx->pid = NULL; + fileout_open_close(ctx, NULL, NULL, 0, GF_FALSE, NULL); + return GF_OK; + } + gf_filter_pid_check_caps(pid); + + if (!ctx->pid) { + GF_FilterEvent evt; + gf_filter_pid_init_play_event(pid, &evt, ctx->start, ctx->speed, "FileOut"); + gf_filter_pid_send_event(pid, &evt); + } + ctx->pid = pid; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DISABLE_PROGRESSIVE); + if (p && p->value.uint) ctx->patch_blocks = GF_TRUE; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_MODE); + if (p && p->value.uint) ctx->dash_mode = 1; + return GF_OK; +} + +static GF_Err fileout_initialize(GF_Filter *filter) +{ + char *ext=NULL; + const char *dst; + GF_FileOutCtx *ctx = (GF_FileOutCtx *) gf_filter_get_udta(filter); + + if (!ctx || !ctx->dst) return GF_OK; + + if (!ctx->mvbk) + ctx->mvbk = 1; + + if (strnicmp(ctx->dst, "file:/", 6) && strnicmp(ctx->dst, "gfio:/", 6) && strstr(ctx->dst, "://")) { + gf_filter_setup_failure(filter, GF_NOT_SUPPORTED); + return GF_NOT_SUPPORTED; + } + if (!stricmp(ctx->dst, "null") ) { + ctx->is_null = GF_TRUE; + //null and no format specified, we accept any kind + if (!ctx->ext) { + ctx->in_caps[0].code = GF_PROP_PID_STREAM_TYPE; + ctx->in_caps[0].val = PROP_UINT(GF_STREAM_UNKNOWN); + ctx->in_caps[0].flags = GF_CAPS_INPUT_EXCLUDED; + gf_filter_override_caps(filter, ctx->in_caps, 1); + return GF_OK; + } + } + if (!strncmp(ctx->dst, "gfio://", 7)) { + GF_Err e; + ctx->gfio_ref = gf_fileio_open_url(gf_fileio_from_url(ctx->dst), NULL, "ref", &e); + if (!ctx->gfio_ref) { + gf_filter_setup_failure(filter, e); + return e; + } + dst = gf_fileio_translate_url(ctx->dst); + ctx->original_url = ctx->dst; + } else { + dst = ctx->dst; + } + + if (ctx->dynext) return GF_OK; + + if (ctx->ext) ext = ctx->ext; + else if (dst) { + ext = gf_file_ext_start(dst); + if (!ext) ext = ".*"; + ext += 1; + } + + if (!ext && !ctx->mime) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] No extension provided nor mime type for output file %s, cannot infer format\n", ctx->dst)); + return GF_NOT_SUPPORTED; + } + //static cap, streamtype = file + ctx->in_caps[0].code = GF_PROP_PID_STREAM_TYPE; + ctx->in_caps[0].val = PROP_UINT(GF_STREAM_FILE); + ctx->in_caps[0].flags = GF_CAPS_INPUT_STATIC; + + if (ctx->mime) { + ctx->in_caps[1].code = GF_PROP_PID_MIME; + ctx->in_caps[1].val = PROP_NAME( ctx->mime ); + ctx->in_caps[1].flags = GF_CAPS_INPUT; + } else { + strncpy(ctx->szExt, ext, 9); + ctx->szExt[9] = 0; + strlwr(ctx->szExt); + ctx->in_caps[1].code = GF_PROP_PID_FILE_EXT; + ctx->in_caps[1].val = PROP_NAME( ctx->szExt ); + ctx->in_caps[1].flags = GF_CAPS_INPUT; + } + gf_filter_override_caps(filter, ctx->in_caps, 2); + + return GF_OK; +} + +static void fileout_finalize(GF_Filter *filter) +{ + GF_Err e; + GF_FileOutCtx *ctx = (GF_FileOutCtx *) gf_filter_get_udta(filter); + fileout_open_close(ctx, NULL, NULL, 0, GF_FALSE, NULL); + if (ctx->gfio_ref) + gf_fileio_open_url((GF_FileIO *)ctx->gfio_ref, NULL, "unref", &e); +} + +static GF_Err fileout_process(GF_Filter *filter) +{ + GF_FilterPacket *pck; + const GF_PropertyValue *fname, *p; + Bool start, end; + const u8 *pck_data; + u32 pck_size, nb_write; + GF_FileOutCtx *ctx = (GF_FileOutCtx *) gf_filter_get_udta(filter); + + if (ctx->is_error) { + GF_Err e = ctx->is_error; + if (e != GF_EOS) { + gf_filter_pid_set_discard(ctx->pid, GF_TRUE); + ctx->is_error = GF_EOS; + } + return e; + } + + pck = gf_filter_pid_get_packet(ctx->pid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->pid)) { + if (gf_filter_reporting_enabled(filter)) { + char szStatus[1024]; + snprintf(szStatus, 1024, "%s: done - wrote "LLU" bytes", gf_file_basename(ctx->szFileName), ctx->nb_write); + gf_filter_update_status(filter, 10000, szStatus); + } + + if (ctx->dash_mode && ctx->file) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_SEGMENT_SIZE, ctx->pid); + evt.seg_size.seg_url = NULL; + + if (ctx->dash_mode==1) { + evt.seg_size.is_init = GF_TRUE; + ctx->dash_mode = 2; + evt.seg_size.media_range_start = 0; + evt.seg_size.media_range_end = 0; + gf_filter_pid_send_event(ctx->pid, &evt); + } else { + evt.seg_size.is_init = GF_FALSE; + evt.seg_size.media_range_start = ctx->offset_at_seg_start; + evt.seg_size.media_range_end = gf_ftell(ctx->file)-1; + gf_filter_pid_send_event(ctx->pid, &evt); + } + } + fileout_open_close(ctx, NULL, NULL, 0, GF_FALSE, NULL); + return GF_EOS; + } + return GF_OK; + } + + gf_filter_pck_get_framing(pck, &start, &end); + if (!ctx->redund) { + u32 dep_flags = gf_filter_pck_get_dependency_flags(pck); + //redundant packet, do not store + if ((dep_flags & 0x3) == 1) { + gf_filter_pid_drop_packet(ctx->pid); + return GF_OK; + } + } + + if (ctx->is_null) { + if (start) { + u32 fnum=0; + const char *filename=NULL; + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM); + if (p) fnum = p->value.uint; + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_URL); + if (!p) p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_OUTPATH); + if (!p) p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME); + if (p) filename = p->value.string; + if (filename) { + strcpy(ctx->szFileName, filename); + } else { + sprintf(ctx->szFileName, "%d", fnum); + } + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[FileOut] null open (file name is %s)\n", ctx->szFileName)); + } + if (end) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[FileOut] null close (file name was %s)\n", ctx->szFileName)); + } + gf_filter_pid_drop_packet(ctx->pid); + return fileout_process(filter); + } + + if (ctx->file && start && ctx->cat) + start = GF_FALSE; + + if (ctx->dash_mode) { + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM); + if (p) { + GF_FilterEvent evt; + + GF_FEVT_INIT(evt, GF_FEVT_SEGMENT_SIZE, ctx->pid); + evt.seg_size.seg_url = NULL; + + if (ctx->dash_mode==1) { + evt.seg_size.is_init = GF_TRUE; + ctx->dash_mode = 2; + evt.seg_size.media_range_start = 0; + evt.seg_size.media_range_end = 0; + gf_filter_pid_send_event(ctx->pid, &evt); + } else { + evt.seg_size.is_init = GF_FALSE; + evt.seg_size.media_range_start = ctx->offset_at_seg_start; + evt.seg_size.media_range_end = gf_ftell(ctx->file)-1; + ctx->offset_at_seg_start = evt.seg_size.media_range_end; + gf_filter_pid_send_event(ctx->pid, &evt); + } + if ( gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME)) + start = GF_TRUE; + } + } + + if (start) { + const GF_PropertyValue *ext, *fnum, *fsuf; + Bool explicit_overwrite = GF_FALSE; + const char *name = NULL; + fname = ext = NULL; + //file num increased per packet, open new file + fnum = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM); + if (fnum) { + fname = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_OUTPATH); + ext = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_FILE_EXT); + if (!fname) name = ctx->dst; + } + //filename change at packet start, open new file + if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME); + if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PID_OUTPATH); + if (!ext) ext = gf_filter_pck_get_property(pck, GF_PROP_PID_FILE_EXT); + if (fname) name = fname->value.string; + + fsuf = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILESUF); + + if (end && gf_filter_pck_get_seek_flag(pck)) + explicit_overwrite = GF_TRUE; + + if (name) { + fileout_open_close(ctx, name, ext ? ext->value.string : NULL, fnum ? fnum->value.uint : 0, explicit_overwrite, fsuf ? fsuf->value.string : NULL); + } else if (!ctx->file) { + fileout_setup_file(ctx, explicit_overwrite); + } + } + + pck_data = gf_filter_pck_get_data(pck, &pck_size); + if (ctx->file) { + GF_FilterFrameInterface *hwf = gf_filter_pck_get_frame_interface(pck); + if (pck_data) { + if (ctx->patch_blocks && gf_filter_pck_get_seek_flag(pck)) { + u64 bo = gf_filter_pck_get_byte_offset(pck); + if (ctx->is_std) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Cannot patch file, output is stdout\n")); + } else if (bo==GF_FILTER_NO_BO) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Cannot patch file, wrong byte offset\n")); + } else { + u32 ilaced = gf_filter_pck_get_interlaced(pck); + u64 pos = ctx->nb_write; + if (ctx->file) { + assert( ctx->nb_write == gf_ftell(ctx->file) ); + } + + //we are inserting a block: write dummy bytes at end and move bytes + if (ilaced) { + u8 *block; + u64 cur_r, cur_w; + nb_write = (u32) gf_fwrite(pck_data, pck_size, ctx->file); + + if (nb_write!=pck_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Write error, wrote %d bytes but had %d to write\n", nb_write, pck_size)); + } + cur_w = gf_ftell(ctx->file); + + gf_fseek(ctx->file, pos, SEEK_SET); + + cur_r = pos; + pos = cur_w; + block = gf_malloc(ctx->mvbk); + if (!block) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] unable to allocate blockof %d bytes\n", ctx->mvbk)); + } else { + while (cur_r > bo) { + u32 move_bytes = ctx->mvbk; + if (cur_r - bo < move_bytes) + move_bytes = (u32) (cur_r - bo); + + gf_fseek(ctx->file, cur_r - move_bytes, SEEK_SET); + nb_write = (u32) gf_fread(block, (size_t) move_bytes, ctx->file); + + if (nb_write!=move_bytes) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Read error, got %d bytes but had %d to read\n", nb_write, move_bytes)); + } + + gf_fseek(ctx->file, cur_w - move_bytes, SEEK_SET); + nb_write = (u32) gf_fwrite(block, (size_t) move_bytes, ctx->file); + + if (nb_write!=move_bytes) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Write error, wrote %d bytes but had %d to write\n", nb_write, move_bytes)); + } + cur_r -= move_bytes; + cur_w -= move_bytes; + } + gf_free(block); + } + } + + gf_fseek(ctx->file, bo, SEEK_SET); + nb_write = (u32) gf_fwrite(pck_data, pck_size, ctx->file); + gf_fseek(ctx->file, pos, SEEK_SET); + + if (nb_write!=pck_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Write error, wrote %d bytes but had %d to write\n", nb_write, pck_size)); + } + } + } else { + nb_write = (u32) gf_fwrite(pck_data, pck_size, ctx->file); + if (nb_write!=pck_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Write error, wrote %d bytes but had %d to write\n", nb_write, pck_size)); + } + ctx->nb_write += nb_write; + } + } else if (hwf) { + u32 w, h, stride, stride_uv, pf; + u32 nb_planes, uv_height; + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_WIDTH); + w = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_HEIGHT); + h = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_PIXFMT); + pf = p ? p->value.uint : 0; + + stride = stride_uv = 0; + + if (gf_pixel_get_size_info(pf, w, h, NULL, &stride, &stride_uv, &nb_planes, &uv_height) == GF_TRUE) { + u32 i; + for (i=0; iget_plane(hwf, i, &out_ptr, &out_stride); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Failed to fetch plane data from hardware frame, cannot write\n")); + break; + } + if (i) { + write_h = uv_height; + lsize = stride_uv; + } else { + write_h = h; + lsize = stride; + } + for (j=0; jfile); + if (nb_write!=lsize) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Write error, wrote %d bytes but had %d to write\n", nb_write, lsize)); + } + ctx->nb_write += nb_write; + out_ptr += out_stride; + } + } + } + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[FileOut] No data associated with packet, cannot write\n")); + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] output file handle is not opened, discarding %d bytes\n", pck_size)); + } + gf_filter_pid_drop_packet(ctx->pid); + if (end && !ctx->cat) { + fileout_open_close(ctx, NULL, NULL, 0, GF_FALSE, NULL); + } + if (gf_filter_reporting_enabled(filter)) { + char szStatus[1024]; + snprintf(szStatus, 1024, "%s: wrote % 16"LLD_SUF" bytes", gf_file_basename(ctx->szFileName), (s64) ctx->nb_write); + gf_filter_update_status(filter, -1, szStatus); + } + return GF_OK; +} + +static Bool fileout_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + if (evt->base.type==GF_FEVT_FILE_DELETE) { + GF_FileOutCtx *ctx = (GF_FileOutCtx *) gf_filter_get_udta(filter); + if (ctx->is_null) { + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[FileOut] null delete (file name was %s)\n", evt->file_del.url)); + } else { + gf_file_delete(evt->file_del.url); + } + return GF_TRUE; + } + return GF_FALSE; +} +static GF_FilterProbeScore fileout_probe_url(const char *url, const char *mime) +{ + if (strstr(url, "://")) { + + if (!strnicmp(url, "file://", 7)) return GF_FPROBE_MAYBE_SUPPORTED; + if (!strnicmp(url, "gfio://", 7)) { + if (!gf_fileio_write_mode(gf_fileio_from_url(url))) + return GF_FPROBE_NOT_SUPPORTED; + return GF_FPROBE_MAYBE_SUPPORTED; + } + return GF_FPROBE_NOT_SUPPORTED; + } + return GF_FPROBE_MAYBE_SUPPORTED; +} + + +#define OFFS(_n) #_n, offsetof(GF_FileOutCtx, _n) + +static const GF_FilterArgs FileOutArgs[] = +{ + { OFFS(dst), "location of destination file - see filter help ", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(append), "open in append mode", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(dynext), "indicate the file extension is set by filter chain, not dst", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(start), "set playback start offset. Negative value means percent of media dur with -1 <=> dur", GF_PROP_DOUBLE, "0.0", NULL, 0}, + { OFFS(speed), "set playback speed when vsync is on. If speed is negative and start is 0, start is set to -1", GF_PROP_DOUBLE, "1.0", NULL, 0}, + { OFFS(ext), "set extension for graph resolution, regardless of file extension", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(mime), "set mime type for graph resolution", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(cat), "cat each file of input pid rather than creating one file per filename", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ow), "overwrite output if existing", GF_PROP_BOOL, "true", NULL, 0}, + { OFFS(mvbk), "block size used when moving parts of the file around in patch mode", GF_PROP_UINT, "8192", NULL, 0}, + { OFFS(redund), "keep redundant packet in output file", GF_PROP_BOOL, "false", NULL, 0}, + + {0} +}; + +static const GF_FilterCapability FileOutCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT,GF_PROP_PID_FILE_EXT, "*"), +}; + + +GF_FilterRegister FileOutRegister = { + .name = "fout", + GF_FS_SET_DESCRIPTION("File output") + GF_FS_SET_HELP("The file output filter is used to write output to disk, and does not produce any output PID.\n" + "It can work as a null sink when its destination is `null`, dropping all input packets. In this case it accepts ANY type of input pid, not just file ones.\n" + "In regular mode, the filter only accept pid of type file. It will dump to file incomming packets (stream type file), starting a new file for each packet having a __frame_start__ flag set, unless operating in [-cat]() mode.\n" + "The ouput file name can use gpac templating mechanism, see `gpac -h doc`." + "The filter watches the property `FileNumber` on incoming packets to create new files.\n" + ) + .private_size = sizeof(GF_FileOutCtx), + .args = FileOutArgs, + SETCAPS(FileOutCaps), + .probe_url = fileout_probe_url, + .initialize = fileout_initialize, + .finalize = fileout_finalize, + .configure_pid = fileout_configure_pid, + .process = fileout_process, + .process_event = fileout_process_event +}; + + +const GF_FilterRegister *fileout_register(GF_FilterSession *session) +{ + return &FileOutRegister; +} + diff --git a/src/filters/out_http.c b/src/filters/out_http.c new file mode 100644 index 0000000..22a7843 --- /dev/null +++ b/src/filters/out_http.c @@ -0,0 +1,2786 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2019-2020 + * All rights reserved + * + * This file is part of GPAC / http server and output filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +GF_DownloadSession *gf_dm_sess_new_server(GF_Socket *server, void *ssl_ctx, gf_dm_user_io user_io, void *usr_cbk, GF_Err *e); +GF_Err gf_dm_sess_send(GF_DownloadSession *sess, u8 *data, u32 size); + +#ifdef GPAC_HAS_SSL + +void *gf_ssl_new(void *ssl_server_ctx, GF_Socket *client_sock, GF_Err *e); +void gf_ssl_del(void *ssl_ctx); +void *gf_ssl_server_context_new(const char *cert, const char *key); +void gf_ssl_server_context_del(void *ssl_server_ctx); +Bool gf_ssl_init_lib(); +GF_Err gf_ssl_write(void *ssl_ctx, const u8 *buffer, u32 size); + +#endif + +enum +{ + MODE_DEFAULT=0, + MODE_PUSH, + MODE_SOURCE, +}; + +typedef struct +{ + //options + char *dst, *user_agent, *ifce, *cache_control, *ext, *mime, *wdir, *cert, *pkey, *reqlog; + GF_List *rdirs; + Bool close, hold, quit, post, dlist, ice; + u32 port, block_size, maxc, maxp, timeout, hmode, sutc, cors; + + //internal + GF_Filter *filter; + GF_Socket *server_sock; + GF_List *sessions, *active_sessions; + GF_List *inputs; + + u32 next_wake_us; + char *ip; + Bool done; + + GF_SockGroup *sg; + Bool no_etag; + + u32 nb_connections; + + GF_FilterCapability in_caps[2]; + char szExt[10]; + + //set to true when no mounted dirs and not push mode + Bool single_mode; + + void *ssl_ctx; + + u64 req_id; +} GF_HTTPOutCtx; + +typedef struct +{ + GF_HTTPOutCtx *ctx; + GF_FilterPid *ipid; + char *path; + Bool dash_mode; + char *mime; + u32 nb_dest; + Bool hold; + + Bool is_open, done, is_delete; + Bool patch_blocks; + GF_List *file_deletes; + + //for PUT mode, NULL in server mode + GF_DownloadSession *upload; + u32 cur_header; + + u64 offset_at_seg_start; + u64 nb_write, write_start_range, write_end_range; + char range_hdr[100]; + + //for server mode, recording + char *local_path; + FILE *resource; + + u8 *tunein_data; + u32 tunein_data_size; +} GF_HTTPOutInput; + +typedef struct +{ + s64 start; + s64 end; +} HTTByteRange; + +typedef struct __httpout_session +{ + GF_HTTPOutCtx *ctx; + + GF_Socket *socket; + GF_DownloadSession *http_sess; + char peer_address[GF_MAX_IP_NAME_LEN]; + void *ssl; + + u32 play_state; + Double start_range; + + FILE *resource; + char *path, *mime; + u64 file_size, file_pos, nb_bytes, bytes_in_req; + u8 *buffer; + Bool done; + u64 last_file_modif; + + u64 req_start_time; + u64 last_active_time; + Bool is_head; + Bool file_in_progress; + Bool use_chunk_transfer; + u32 put_in_progress; + //for upload only: 0 not an upload, 1 creation, 2: update + u32 upload_type; + u64 content_length; + GF_FilterPid *opid; + Bool reconfigure_output; + + GF_HTTPOutInput *in_source; + Bool send_init_data; + + u32 nb_ranges, alloc_ranges, range_idx; + HTTByteRange *ranges; + + Bool do_log; + u64 req_id; + u32 method_type, reply_code; +} GF_HTTPOutSession; + +static void httpout_reset_socket(GF_HTTPOutSession *sess) +{ + if (!sess->socket) return; + + assert(sess->ctx->nb_connections); + sess->ctx->nb_connections--; + + gf_sk_group_unregister(sess->ctx->sg, sess->socket); +#ifdef GPAC_HAS_SSL + if (sess->ssl) { + gf_ssl_del(sess->ssl); + sess->ssl = NULL; + } +#endif + gf_sk_del(sess->socket); + sess->socket = NULL; + if (sess->in_source) sess->in_source->nb_dest--; +} + +static void httpout_insert_date(u64 time, char **headers, Bool for_listing) +{ + char szDate[200]; + time_t gtime; + struct tm *t; + const char *wday, *month; + u32 sec; + gtime = time / 1000; + t = gf_gmtime(>ime); + sec = t->tm_sec; + //see issue #859, no clue how this happened... + if (sec > 60) + sec = 60; + switch (t->tm_wday) { + case 1: wday = "Mon"; break; + case 2: wday = "Tue"; break; + case 3: wday = "Wed"; break; + case 4: wday = "Thu"; break; + case 5: wday = "Fri"; break; + case 6: wday = "Sat"; break; + default: wday = "Sun"; break; + } + switch (t->tm_mon) { + case 1: month = "Feb"; break; + case 2: month = "Mar"; break; + case 3: month = "Apr"; break; + case 4: month = "May"; break; + case 5: month = "Jun"; break; + case 6: month = "Jul"; break; + case 7: month = "Aug"; break; + case 8: month = "Sep"; break; + case 9: month = "Oct"; break; + case 10: month = "Nov"; break; + case 11: month = "Dec"; break; + default: month = "Jan"; break; + + } + + if (for_listing) { + sprintf(szDate, "%02d-%s-%d %02d:%02d:%02d", t->tm_mday, month, 1900 + t->tm_year, t->tm_hour, t->tm_min, sec); + gf_dynstrcat(headers, szDate, NULL); + } else { + sprintf(szDate, "%s, %02d %s %d %02d:%02d:%02d GMT", wday, t->tm_mday, month, 1900 + t->tm_year, t->tm_hour, t->tm_min, sec); + gf_dynstrcat(headers, "Date: ", NULL); + gf_dynstrcat(headers, szDate, NULL); + gf_dynstrcat(headers, "\r\n", NULL); + } +} + +static Bool httpout_dir_file_enum(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info, Bool is_dir) +{ + char szFmt[200]; + u64 size; + u32 name_len; + char *unit=NULL; + char **listing = (char **) cbck; + + if (file_info && (file_info->hidden || file_info->system)) + return GF_FALSE; + + if (is_dir) + gf_dynstrcat(listing, "+ ", NULL); + gf_dynstrcat(listing, item_name, NULL); + if (is_dir) gf_dynstrcat(listing, "/", NULL); + gf_dynstrcat(listing, "", NULL); + while (name_len<60) { + name_len++; + gf_dynstrcat(listing, " ", NULL); + } + if (file_info) + httpout_insert_date(file_info->last_modified*1000, listing, GF_TRUE); + + if (is_dir || !file_info) { + gf_dynstrcat(listing, " -\n", NULL); + return GF_FALSE; + } + size = file_info->size; + if (size<1000) unit=""; + else if (size<1000000) { unit="K"; size/=1000; } + else if (size<1000000000) { unit="M"; size/=1000000; } + else if (size<1000000000000) { unit="G"; size/=1000000000; } + gf_dynstrcat(listing, " ", NULL); + sprintf(szFmt, LLU"%s\n", size, unit); + gf_dynstrcat(listing, szFmt, NULL); + + return GF_FALSE; +} +static Bool httpout_dir_enum(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) +{ + return httpout_dir_file_enum(cbck, item_name, item_path, file_info, GF_TRUE); +} +static Bool httpout_file_enum(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info) +{ + return httpout_dir_file_enum(cbck, item_name, item_path, file_info, GF_FALSE); +} + +static char *httpout_create_listing(GF_HTTPOutCtx *ctx, char *full_path) +{ + char szHost[GF_MAX_IP_NAME_LEN]; + char *has_par, *dir; + u32 i, count = gf_list_count(ctx->rdirs); + char *listing = NULL; + char *name = full_path; + + if (full_path && (full_path[0]=='.')) + name++; + + gf_dynstrcat(&listing, "\n\n\nIndex of ", NULL); + gf_dynstrcat(&listing, name, NULL); + gf_dynstrcat(&listing, "\n\n

Index of ", NULL); + gf_dynstrcat(&listing, name, NULL); + gf_dynstrcat(&listing, "

\n
Name                                                                Last modified      Size\n
\n", NULL); + + if (!full_path) { + has_par=NULL; + } else { + u32 len = (u32) strlen(full_path); + if (len && strchr("/\\", full_path[len-1])) + full_path[len-1] = 0; + has_par = strrchr(full_path, '/'); + } + if (has_par) { + u8 c = has_par[1]; + has_par[1] = 0; + + //retranslate server root + gf_dynstrcat(&listing, ".. rdirs, i); + u32 dlen = (u32) strlen(dir); + if (!strncmp(dir, name, dlen) && ((name[dlen]=='/') || (name[dlen]==0))) { + gf_dynstrcat(&listing, "/", NULL); + if (count==1) name = NULL; + break; + } + } + + if (name) + gf_dynstrcat(&listing, name, NULL); + + gf_dynstrcat(&listing, "\">Parent Directory\n", NULL); + has_par[1] = c; + } + + if (!full_path || !strlen(full_path)) { + count = gf_list_count(ctx->rdirs); + if (count==1) { + dir = gf_list_get(ctx->rdirs, 0); + gf_enum_directory(dir, GF_TRUE, httpout_dir_enum, &listing, NULL); + gf_enum_directory(dir, GF_FALSE, httpout_file_enum, &listing, NULL); + } else { + for (i=0; irdirs, i); + httpout_dir_file_enum(&listing, dir, NULL, NULL, GF_TRUE); + } + } + } else { + Bool insert_root = GF_FALSE; + if (count>1) { + for (i=0; irdirs, i); + if (!strcmp(full_path, dir)) { + insert_root=GF_TRUE; + break; + } + } + if (insert_root) { + gf_dynstrcat(&listing, ".. Parent Directory -\n", NULL); + } + } + gf_enum_directory(full_path, GF_TRUE, httpout_dir_enum, &listing, NULL); + gf_enum_directory(full_path, GF_FALSE, httpout_file_enum, &listing, NULL); + } + + gf_dynstrcat(&listing, "\n
\n
", NULL); + gf_dynstrcat(&listing, ctx->user_agent, NULL); + gf_sk_get_host_name(szHost); + gf_dynstrcat(&listing, " at ", NULL); + gf_dynstrcat(&listing, szHost, NULL); + gf_dynstrcat(&listing, " Port ", NULL); + sprintf(szHost, "%d", ctx->port); + gf_dynstrcat(&listing, szHost, NULL); + gf_dynstrcat(&listing, "
\n", NULL); + return listing; +} + + +static void httpout_set_local_path(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in) +{ + char *dir; + u32 len; + assert(in->path); + //not recording + if (!ctx->rdirs) return; + + dir = gf_list_get(ctx->rdirs, 0); + if (!dir) return; + len = (u32) strlen(dir); + if (in->local_path) gf_free(in->local_path); + in->local_path = NULL; + gf_dynstrcat(&in->local_path, dir, NULL); + if (!strchr("/\\", dir[len-1])) + gf_dynstrcat(&in->local_path, "/", NULL); + gf_dynstrcat(&in->local_path, in->path+1, NULL); +} + +static Bool httpout_sess_parse_range(GF_HTTPOutSession *sess, char *range) +{ + Bool request_ok = GF_TRUE;; + u32 i; + Bool has_open_start=GF_FALSE; + Bool has_file_end=GF_FALSE; + u64 known_file_size; + sess->nb_ranges = 0; + sess->nb_bytes = 0; + sess->range_idx = 0; + if (!range) return GF_TRUE; + + if (sess->in_source && !sess->ctx->rdirs) + return GF_FALSE; + + while (range) { + char *sep; + u32 len; + s64 start, end; + char *next = strchr(range, ','); + if (next) next[0] = 0; + + while (range[0] == ' ') range++; + + //unsupported unit + if (strncmp(range, "bytes=", 6)) { + return GF_FALSE; + } + range += 6; + sep = strchr(range, '/'); + if (sep) sep[0] = 0; + len = (u32) strlen(range); + start = end = -1; + if (!len) { + request_ok = GF_FALSE; + } + //end range only + else if (range[0] == '-') { + if (has_file_end) + request_ok = GF_FALSE; + has_file_end = GF_TRUE; + start = -1; + if (sscanf(range+1, LLD, &end) != 1) + request_ok = GF_FALSE; + } + //start -> EOF + else if (range[len-1] == '-') { + if (has_open_start) + request_ok = GF_FALSE; + has_open_start = GF_TRUE; + end = -1; + if (sscanf(range, LLD"-", &start) != 1) + request_ok = GF_FALSE; + } else { + if (sscanf(range, LLD"-"LLD, &start, &end) != 2) + request_ok = GF_FALSE; + } + if ((start==-1) && (end==-1)) { + request_ok = GF_FALSE; + } + + if (request_ok) { + if (sess->nb_ranges >= sess->alloc_ranges) { + sess->alloc_ranges = sess->nb_ranges + 1; + sess->ranges = gf_realloc(sess->ranges, sizeof(HTTByteRange)*sess->alloc_ranges); + } + sess->ranges[sess->nb_ranges].start = start; + sess->ranges[sess->nb_ranges].end = end; + sess->nb_ranges++; + } + + if (sep) sep[0] = '/'; + if (!next) break; + next[0] = ','; + range = next+1; + if (!request_ok) break; + } + if (!request_ok) return GF_FALSE; + + if (sess->in_source) { + //cannot fetch end of file it is not yet known ! + if (has_file_end) return GF_FALSE; + known_file_size = sess->in_source->nb_write; + } else { + known_file_size = sess->file_size; + } + sess->bytes_in_req = 0; + for (i=0; inb_ranges; i++) { + if (sess->ranges[i].start>=0) { + //if start, end is a pos in bytes in size (0-based) + if (sess->ranges[i].end==-1) { + sess->ranges[i].end = known_file_size-1; + } + + if (sess->ranges[i].end >= (s64) known_file_size) { + request_ok = GF_FALSE; + break; + } + if (sess->ranges[i].start >= (s64) known_file_size) { + request_ok = GF_FALSE; + break; + } + } else { + //no start, end is a file size + if (sess->ranges[i].end >= (s64) known_file_size) { + request_ok = GF_FALSE; + break; + } + sess->ranges[i].start = known_file_size - sess->ranges[i].end; + sess->ranges[i].end = known_file_size - 1; + } + sess->bytes_in_req += (sess->ranges[i].end + 1 - sess->ranges[i].start); + } + //if we have a single byte range request covering the entire file, reply 200 OK and not 206 partial + if ((sess->nb_ranges == 1) && known_file_size && !sess->ranges[0].start && (sess->ranges[0].end==known_file_size-1)) + sess->nb_ranges = 0; + + if (!request_ok) return GF_FALSE; + sess->file_pos = sess->ranges[0].start; + if (sess->resource) gf_fseek(sess->resource, sess->file_pos, SEEK_SET); + return GF_TRUE; +} + +GF_Err httpout_sess_send(GF_HTTPOutSession *sess, const u8 *buffer, u32 length) +{ + GF_Err e; +#ifdef GPAC_HAS_SSL + if (sess->ssl) { + e = gf_ssl_write(sess->ssl, buffer, length); + } else +#endif + e = gf_sk_send(sess->socket, buffer, length); + + return e; +} + +static Bool httpout_do_log(GF_HTTPOutSession *sess, u32 method) +{ + if (!sess->ctx->reqlog) return GF_FALSE; + + if (!strcmp(sess->ctx->reqlog, "*")) return GF_TRUE; + + switch (method) { + case GF_HTTP_GET: + if (strstr(sess->ctx->reqlog, "GET") || strstr(sess->ctx->reqlog, "get")) return GF_TRUE; + break; + case GF_HTTP_PUT: + if (strstr(sess->ctx->reqlog, "PUT") || strstr(sess->ctx->reqlog, "put")) return GF_TRUE; + break; + case GF_HTTP_POST: + if (strstr(sess->ctx->reqlog, "POST") || strstr(sess->ctx->reqlog, "post")) return GF_TRUE; + break; + case GF_HTTP_DELETE: + if (strstr(sess->ctx->reqlog, "DEL") || strstr(sess->ctx->reqlog, "del")) return GF_TRUE; + break; + case GF_HTTP_HEAD: + if (strstr(sess->ctx->reqlog, "HEAD") || strstr(sess->ctx->reqlog, "head")) return GF_TRUE; + break; + case GF_HTTP_OPTIONS: + if (strstr(sess->ctx->reqlog, "OPT") || strstr(sess->ctx->reqlog, "opt")) return GF_TRUE; + break; + default: + return GF_TRUE; + } + return GF_FALSE; +} + +#ifndef GPAC_DISABLE_LOG +static const char *get_method_name(u32 method) +{ + switch (method) { + case GF_HTTP_GET: return "GET"; + case GF_HTTP_HEAD: return "HEAD"; + case GF_HTTP_PUT: return "PUT"; + case GF_HTTP_POST: return "POST"; + case GF_HTTP_DELETE: return "DELETE"; + case GF_HTTP_TRACE: return "TRACE"; + case GF_HTTP_CONNECT: return "CONNECT"; + case GF_HTTP_OPTIONS: return "OPTIONS"; + default: return "UNKNOWN"; + } +} +#endif //GPAC_DISABLE_LOG + +static void httpout_sess_io(void *usr_cbk, GF_NETIO_Parameter *parameter) +{ + char *rsp_buf = NULL; + const char *url=""; + char *full_path=NULL; + char *response = NULL; + char szFmt[100]; + char szETag[100]; + u64 modif_time=0; + u32 body_size=0; + const char *etag=NULL, *range=NULL; + const char *mime = NULL; + char *response_body = NULL; + GF_Err e=GF_OK; + Bool not_modified = GF_FALSE; + Bool is_upload = GF_FALSE; + u32 i, count; + GF_HTTPOutInput *source_pid = NULL; + GF_HTTPOutSession *source_sess = NULL; + GF_HTTPOutSession *sess = usr_cbk; + + if (parameter->msg_type != GF_NETIO_PARSE_REPLY) { + parameter->error = GF_BAD_PARAM; + return; + } + switch (parameter->reply) { + case GF_HTTP_GET: + case GF_HTTP_HEAD: + break; + case GF_HTTP_PUT: + case GF_HTTP_POST: + case GF_HTTP_DELETE: + is_upload = GF_TRUE; + break; + default: + response = "HTTP/1.1 501 Not Implemented\r\n"; + gf_dynstrcat(&response_body, "Method is not supported by GPAC", NULL); + goto exit; + } + url = gf_dm_sess_get_resource_name(sess->http_sess); + if (!url || (url[0] != '/')) { + response = "HTTP/1.1 400 Bad Request\r\n"; + goto exit; + } + + sess->do_log = httpout_do_log(sess, parameter->reply); + + //resolve name against upload dir + if (is_upload) { + if (!sess->ctx->wdir && (sess->ctx->hmode!=MODE_SOURCE)) { + response = "HTTP/1.1 405 Not Allowed\r\n"; + gf_dynstrcat(&response_body, "No write directory enabled on server", NULL); + goto exit; + } + if (sess->ctx->wdir) { + u32 len = (u32) strlen(sess->ctx->wdir); + gf_dynstrcat(&full_path, sess->ctx->wdir, NULL); + if (!strchr("/\\", sess->ctx->wdir[len-1])) + gf_dynstrcat(&full_path, "/", NULL); + gf_dynstrcat(&full_path, url+1, NULL); + } else if (sess->ctx->hmode==MODE_SOURCE) { + full_path = gf_strdup(url+1); + } + if (parameter->reply==GF_HTTP_DELETE) + is_upload = GF_FALSE; + } + + if (is_upload) { + const char *hdr; + range = gf_dm_sess_get_header(sess->http_sess, "Range"); + + if (sess->in_source) { + sess->in_source->nb_dest--; + sess->in_source = NULL; + } + sess->content_length = 0; + hdr = gf_dm_sess_get_header(sess->http_sess, "Content-Length"); + if (hdr) { + sscanf(hdr, LLU, &sess->content_length); + } + sess->use_chunk_transfer = GF_FALSE; + hdr = gf_dm_sess_get_header(sess->http_sess, "Transfer-Encoding"); + if (hdr && !strcmp(hdr, "chunked")) { + sess->use_chunk_transfer = GF_TRUE; + } + sess->file_in_progress = GF_FALSE; + sess->nb_bytes = 0; + sess->done = GF_FALSE; + assert(full_path); + if (sess->path) gf_free(sess->path); + sess->path = full_path; + if (sess->resource) gf_fclose(sess->resource); + sess->resource = NULL; + + if (sess->ctx->hmode==MODE_SOURCE) { + if (range) { + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[HTTPOut] Cannot handle PUT/POST request as PID output with byte ranges (%s)\n", range)); + response = "416 Requested Range Not Satisfiable\r\n"; + gf_dynstrcat(&response_body, "Server running in source mode - cannot handle PUT/POST request with byte ranges ", NULL); + gf_dynstrcat(&response_body, range, NULL); + goto exit; + } + sess->upload_type = 1; + sess->reconfigure_output = GF_TRUE; + } else { + if (gf_file_exists(sess->path)) + sess->upload_type = 2; + else + sess->upload_type = 1; + + sess->resource = gf_fopen(sess->path, range ? "rb+" : "wb"); + if (!sess->resource) { + response = "HTTP/1.1 403 Forbidden\r\n"; + gf_dynstrcat(&response_body, "File exists but cannot be open", NULL); + goto exit; + } + if (!sess->content_length && !sess->use_chunk_transfer) { + response = "HTTP/1.1 411 Length Required\r\n"; + gf_dynstrcat(&response_body, "No content length specified and chunked transfer not enabled", NULL); + goto exit; + } + sess->file_size = gf_fsize(sess->resource); + } + sess->file_pos = 0; + + range = gf_dm_sess_get_header(sess->http_sess, "Range"); + if (! httpout_sess_parse_range(sess, (char *) range) ) { + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[HTTPOut] Unsupported Range format: %s\n", range)); + response = "416 Requested Range Not Satisfiable\r\n"; + gf_dynstrcat(&response_body, "Range format is not supported, only \"bytes\" units allowed: ", NULL); + gf_dynstrcat(&response_body, range, NULL); + goto exit; + } + if (!sess->buffer) { + sess->buffer = gf_malloc(sizeof(u8)*sess->ctx->block_size); + } + if (gf_list_find(sess->ctx->active_sessions, sess)<0) { + gf_list_add(sess->ctx->active_sessions, sess); + gf_sk_group_register(sess->ctx->sg, sess->socket); + } + sess->last_active_time = gf_sys_clock_high_res(); + + if (sess->do_log) { + sess->req_id = ++sess->ctx->req_id; + sess->method_type = parameter->reply; + if (range) { + GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] REQ#%d %s %s %s [range: %s] start\n", sess->req_id, sess->peer_address, get_method_name(sess->method_type), url+1, range)); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] REQ#%d %s %s %s start\n", sess->req_id, sess->peer_address, get_method_name(sess->method_type), url+1)); + } + } + sess->req_start_time = gf_sys_clock_high_res(); + //send reply once we are done recieving + return; + } + + /*first check active inputs*/ + count = gf_list_count(sess->ctx->inputs); + //delete only accepts local files + if (parameter->reply == GF_HTTP_DELETE) + count = 0; + + for (i=0; ictx->inputs, i); + //matching name and input pid not done: file has been created and is in progress + //if input pid done, try from file + if (!strcmp(in->path, url) && !in->done) { + source_pid = in; + break; + } + } + + /*not resolved and no source matching, check file on disk*/ + if (!source_pid && !full_path) { + count = gf_list_count(sess->ctx->rdirs); + for (i=0; ictx->rdirs, i); + u32 len = (u32) strlen(mdir); + if (!len) continue; + if (count==1) { + gf_dynstrcat(&full_path, mdir, NULL); + if (!strchr("/\\", mdir[len-1])) + gf_dynstrcat(&full_path, "/", NULL); + } + gf_dynstrcat(&full_path, url+1, NULL); + + if (gf_file_exists(full_path) || gf_dir_exists(full_path) ) + break; + gf_free(full_path); + full_path = NULL; + } + } + + if (!full_path && !source_pid) { + if (!sess->ctx->dlist || strcmp(url, "/")) { + sess->reply_code = 404; + response = "HTTP/1.1 404 Not Found\r\n"; + gf_dynstrcat(&response_body, "Resource ", NULL); + gf_dynstrcat(&response_body, url, NULL); + gf_dynstrcat(&response_body, " cannot be resolved", NULL); + goto exit; + } + } + + //check if request is HEAD or GET on a file being uploaded + if (full_path && ((parameter->reply == GF_HTTP_GET) || (parameter->reply == GF_HTTP_HEAD))) { + count = gf_list_count(sess->ctx->sessions); + for (i=0; ictx->sessions, i); + if ((source_sess != sess) && !source_sess->done && source_sess->upload_type && !strcmp(source_sess->path, full_path)) { + break; + } + source_sess = NULL; + } + } + + //session is on an active input being uploaded, always consider as modified + if (source_pid && !sess->ctx->single_mode) { + etag = NULL; + } + //resource is being uploaded, always consider as modified + else if (source_sess) { + etag = NULL; + } + //check ETag + else if (full_path) { + modif_time = gf_file_modification_time(full_path); + sprintf(szETag, LLU, modif_time); + etag = gf_dm_sess_get_header(sess->http_sess, "If-None-Match"); + } + + range = gf_dm_sess_get_header(sess->http_sess, "Range"); + + if (sess->in_source) { + sess->in_source->nb_dest--; + sess->in_source = NULL; + } + sess->file_in_progress = GF_FALSE; + sess->use_chunk_transfer = GF_FALSE; + sess->put_in_progress = 0; + sess->nb_bytes = 0; + sess->upload_type = 0; + + if (parameter->reply==GF_HTTP_DELETE) { + sess->upload_type = 0; + if (sess->path) gf_free(sess->path); + sess->path = full_path; + if (sess->resource) gf_fclose(sess->resource); + sess->resource = NULL; + sess->file_pos = sess->file_size = 0; + + if (gf_file_exists(full_path)) { + e = gf_file_delete(full_path); + + if (e) { + sess->reply_code = 500; + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[HTTPOut] Error deleting file %s (full path %s)\n", url, full_path)); + response = "HTTP/1.1 500 Internal Server Error\r\n"; + gf_dynstrcat(&response_body, "Error while deleting ", NULL); + gf_dynstrcat(&response_body, url, NULL); + gf_dynstrcat(&response_body, ": ", NULL); + gf_dynstrcat(&response_body, gf_error_to_string(e), NULL); + goto exit; + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[HTTPOut] Deleting file %s (full path %s)\n", url, full_path)); + } + + if (sess->do_log) { + sess->req_id = ++sess->ctx->req_id; + GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] REQ#%d %s DELETE %s\n", sess->req_id, sess->peer_address, url+1)); + } + } else { + e = GF_URL_ERROR; + } + range = NULL; + } + /*we have a source and we are not in record mode */ + else if (source_pid && sess->ctx->single_mode) { + if (sess->path) gf_free(sess->path); + sess->path = NULL; + sess->in_source = source_pid; + sess->send_init_data = GF_TRUE; + source_pid->nb_dest++; + source_pid->hold = GF_FALSE; + + sess->file_pos = sess->file_size = 0; + sess->use_chunk_transfer = GF_TRUE; + } + /*we have matching etag*/ + else if (etag && !strcmp(etag, szETag) && !sess->ctx->no_etag) { + if (sess->path) gf_free(sess->path); + sess->path = full_path; + not_modified = GF_TRUE; + } + /*we have the same URL and no associated source*/ + else if (!sess->in_source && (sess->last_file_modif == modif_time) && sess->path && full_path && !strcmp(sess->path, full_path) ) { + gf_free(full_path); + sess->file_pos = 0; + } else { + if (sess->path) gf_free(sess->path); + if (source_pid) { + sess->in_source = source_pid; + source_pid->nb_dest++; + source_pid->hold = GF_FALSE; + sess->send_init_data = GF_TRUE; + + sess->file_in_progress = GF_TRUE; + assert(!full_path); + assert(source_pid->local_path); + full_path = gf_strdup(source_pid->local_path); + sess->use_chunk_transfer = GF_TRUE; + } + sess->path = full_path; + if (!full_path || gf_dir_exists(full_path)) { + if (sess->ctx->dlist) { + range = NULL; + if (!strcmp(url, "/")) { + response_body = httpout_create_listing(sess->ctx, (char *) url); + } else { + response_body = httpout_create_listing(sess->ctx, full_path); + } + sess->file_size = sess->file_pos = 0; + } else { + response = "HTTP/1.1 403 Forbidden\r\n"; + gf_dynstrcat(&response_body, "Directory browsing is not allowed", NULL); + goto exit; + } + } else { + sess->resource = gf_fopen(full_path, "rb"); + //we may not have the file if it is currently being created + if (!sess->resource && !sess->in_source) { + response = "HTTP/1.1 500 Internal Server Error\r\n"; + gf_dynstrcat(&response_body, "File exists but no read access", NULL); + goto exit; + } + + if (!sess->in_source) { + u8 probe_buf[5001]; + u32 read = (u32) gf_fread(probe_buf, 5000, sess->resource); + if ((s32) read < 0) { + if (source_sess) { + read = 0; + } else { + response = "HTTP/1.1 500 Internal Server Error\r\n"; + gf_dynstrcat(&response_body, "File opened but read operation failed", NULL); + goto exit; + } + } + if (read) { + probe_buf[read] = 0; + mime = gf_filter_probe_data(sess->ctx->filter, probe_buf, read); + } + + sess->file_size = gf_fsize(sess->resource); + if (source_sess) { + sess->file_size = 0; + sess->use_chunk_transfer = GF_TRUE; + sess->put_in_progress = 1; + } + } else { + mime = source_pid ? source_pid->mime : NULL; + sess->file_size = 0; + } + } + sess->file_pos = 0; + sess->bytes_in_req = sess->file_size; + + if (sess->mime) gf_free(sess->mime); + sess->mime = ( mime && strcmp(mime, "*")) ? gf_strdup(mime) : NULL; + sess->last_file_modif = gf_file_modification_time(full_path); + } + + if (!sess->in_source && ! httpout_sess_parse_range(sess, (char *) range) ) { + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[HTTPOut] Unsupported Range format: %s", range)); + response = "416 Requested Range Not Satisfiable\r\n"; + gf_dynstrcat(&response_body, "Range format is not supported, only \"bytes\" units allowed: ", NULL); + gf_dynstrcat(&response_body, range, NULL); + goto exit; + } + + if (not_modified) { + gf_dynstrcat(&rsp_buf, "HTTP/1.1 304 Not Modified\r\n", NULL); + sess->reply_code = 304; + } else if (sess->nb_ranges) { + gf_dynstrcat(&rsp_buf, "HTTP/1.1 206 Partial Content\r\n", NULL); + sess->reply_code = 206; + } else if ((parameter->reply==GF_HTTP_DELETE) && (e==GF_URL_ERROR)) { + gf_dynstrcat(&rsp_buf, "HTTP/1.1 204 No Content\r\n", NULL); + sess->reply_code = 204; + } else { + gf_dynstrcat(&rsp_buf, "HTTP/1.1 200 OK\r\n", NULL); + sess->reply_code = 200; + } + gf_dynstrcat(&rsp_buf, "Server: ", NULL); + gf_dynstrcat(&rsp_buf, sess->ctx->user_agent, NULL); + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + httpout_insert_date(gf_net_get_utc(), &rsp_buf, GF_FALSE); + if (sess->ctx->cors) { + gf_dynstrcat(&rsp_buf, "Access-Control-Allow-Origin: *\r\n", NULL); + gf_dynstrcat(&rsp_buf, "Access-Control-Expose-Headers: *\r\n", NULL); + } + if (sess->ctx->sutc) { + sprintf(szFmt, "Server-UTC: "LLU"\r\n", gf_net_get_utc()); + gf_dynstrcat(&rsp_buf, szFmt, NULL); + } + + sess->is_head = (parameter->reply == GF_HTTP_HEAD) ? GF_TRUE : GF_FALSE; + + if (sess->ctx->close) { + gf_dynstrcat(&rsp_buf, "Connection: close\r\n", NULL); + } else { + gf_dynstrcat(&rsp_buf, "Connection: keep-alive\r\n", NULL); + if (sess->ctx->timeout) { + sprintf(szFmt, "timeout=%d", sess->ctx->timeout); + gf_dynstrcat(&rsp_buf, "Keep-Alive: ", NULL); + gf_dynstrcat(&rsp_buf, szFmt, NULL); + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + } + } + + if (response_body) { + body_size = (u32) strlen(response_body); + gf_dynstrcat(&rsp_buf, "Content-Type: text/html\r\n", NULL); + gf_dynstrcat(&rsp_buf, "Content-Length: ", NULL); + sprintf(szFmt, "%d", body_size); + gf_dynstrcat(&rsp_buf, szFmt, NULL); + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + } + //for HEAD/GET only + else if (!not_modified && (parameter->reply!=GF_HTTP_DELETE) ) { + if (!sess->in_source && !sess->ctx->no_etag) { + gf_dynstrcat(&rsp_buf, "ETag: ", NULL); + gf_dynstrcat(&rsp_buf, szETag, NULL); + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + if (sess->ctx->cache_control) { + gf_dynstrcat(&rsp_buf, "Cache-Control: ", NULL); + gf_dynstrcat(&rsp_buf, sess->ctx->cache_control, NULL); + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + } + } else if (sess->in_source && !sess->ctx->rdirs) { + sess->nb_ranges = 0; + gf_dynstrcat(&rsp_buf, "Cache-Control: no-cache, no-store\r\n", NULL); + } + if (sess->bytes_in_req) { + sprintf(szFmt, LLU, sess->bytes_in_req); + gf_dynstrcat(&rsp_buf, "Content-Length: ", NULL); + gf_dynstrcat(&rsp_buf, szFmt, NULL); + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + } + mime = sess->in_source ? sess->in_source->mime : mime; + if (mime && !strcmp(mime, "*")) mime = NULL; + if (mime) { + gf_dynstrcat(&rsp_buf, "Content-Type: ", NULL); + gf_dynstrcat(&rsp_buf, mime, NULL); + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + } + //data comes either directly from source pid, or from file written by source pid, we must use chunk transfer + if (!sess->is_head && sess->use_chunk_transfer) { + gf_dynstrcat(&rsp_buf, "Transfer-Encoding: chunked\r\n", NULL); + } + + if (!sess->is_head && sess->nb_ranges) { + gf_dynstrcat(&rsp_buf, "Content-Range: bytes=", NULL); + for (i=0; inb_ranges; i++) { + if (sess->in_source || !sess->file_size) { + sprintf(szFmt, LLD"-"LLD"/*", sess->ranges[i].start, sess->ranges[i].end); + } else { + sprintf(szFmt, LLD"-"LLD"/"LLU, sess->ranges[i].start, sess->ranges[i].end, sess->file_size); + } + gf_dynstrcat(&rsp_buf, szFmt, i ? ", " : NULL); + } + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + } + + if (sess->in_source && sess->ctx->ice) { + const GF_PropertyValue *p; + u32 sr=0, br=0, nb_ch=0, p_idx; + u32 w=0, h=0; + p = gf_filter_pid_get_property(sess->in_source->ipid, GF_PROP_PID_SAMPLE_RATE); + if (p) sr = p->value.uint; + p = gf_filter_pid_get_property(sess->in_source->ipid, GF_PROP_PID_NUM_CHANNELS); + if (p) nb_ch = p->value.uint; + p = gf_filter_pid_get_property(sess->in_source->ipid, GF_PROP_PID_BITRATE); + if (p) br = p->value.uint; + + p = gf_filter_pid_get_property(sess->in_source->ipid, GF_PROP_PID_WIDTH); + if (p) w = p->value.uint; + p = gf_filter_pid_get_property(sess->in_source->ipid, GF_PROP_PID_HEIGHT); + if (p) h = p->value.uint; + + if (sr || br || nb_ch) { + Bool first=GF_TRUE; + gf_dynstrcat(&rsp_buf, "ice-audio-info: ", NULL); + if (sr) { + gf_dynstrcat(&rsp_buf, "samplerate=", NULL); + sprintf(szFmt, "%d", sr); + gf_dynstrcat(&rsp_buf, szFmt, NULL); + first=GF_FALSE; + } + if (nb_ch) { + gf_dynstrcat(&rsp_buf, "channels=", first ? NULL : ";"); + sprintf(szFmt, "%d", nb_ch); + gf_dynstrcat(&rsp_buf, szFmt, NULL); + first=GF_FALSE; + } + if (br) { + gf_dynstrcat(&rsp_buf, "bitrate=", first ? NULL : ";"); + sprintf(szFmt, "%d", br); + gf_dynstrcat(&rsp_buf, szFmt, NULL); + //first=GF_FALSE; + } + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + } + if (w&&h) { + gf_dynstrcat(&rsp_buf, "ice-video-info: ", NULL); + sprintf(szFmt, "width=%d;height=%d", w, h); + gf_dynstrcat(&rsp_buf, szFmt, NULL); + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + } + + if (br) { + gf_dynstrcat(&rsp_buf, "icy-br: ", NULL); + sprintf(szFmt, "%d", br); + gf_dynstrcat(&rsp_buf, szFmt, NULL); + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + } + gf_dynstrcat(&rsp_buf, "icy-pub: 1\r\n", NULL); + p = gf_filter_pid_get_property(sess->in_source->ipid, GF_PROP_PID_SERVICE_NAME); + if (p && p->value.string) { + gf_dynstrcat(&rsp_buf, "icy-name: ", NULL); + gf_dynstrcat(&rsp_buf, p->value.string, NULL); + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + } + p_idx = 0; + while (1) { + const char *pname; + p = gf_filter_pid_enum_properties(sess->in_source->ipid, &p_idx, NULL, &pname); + if (!p) break; + if (!pname || strncmp(pname, "ice-", 4)) continue; + if (!p->value.string) continue; + gf_dynstrcat(&rsp_buf, pname, NULL); + gf_dynstrcat(&rsp_buf, p->value.string, ": "); + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + } + } + } + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + + if (response_body) { + gf_dynstrcat(&rsp_buf, response_body, NULL); + } + + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Sending response to %s:\n%s\n", sess->peer_address, rsp_buf)); + + if (sess->do_log) { + sess->req_id = ++sess->ctx->req_id; + sess->method_type = parameter->reply; + sess->req_start_time = gf_sys_clock_high_res(); + if (not_modified) { + GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] REQ#%d %s %s %s: reply %d\n", sess->req_id, sess->peer_address, get_method_name(sess->method_type), url+1, sess->reply_code)); + } else if (range) { + GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] REQ#%d %s %s %s [range: %s] start%s\n", sess->req_id, sess->peer_address, get_method_name(sess->method_type), url+1, range, sess->use_chunk_transfer ? " chunk-transfer" : "")); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] REQ#%d %s %s %s start%s\n", sess->req_id, sess->peer_address, get_method_name(sess->method_type), url+1, sess->use_chunk_transfer ? " chunk-transfer" : "")); + } + } + + httpout_sess_send(sess, rsp_buf, (u32) strlen(rsp_buf)); + gf_free(rsp_buf); + if (!sess->buffer) { + sess->buffer = gf_malloc(sizeof(u8)*sess->ctx->block_size); + } + if (response_body) { + gf_free(response_body); + sess->done = GF_TRUE; + if (sess->ctx->quit) + httpout_reset_socket(sess); + } else if (parameter->reply == GF_HTTP_DELETE) { + sess->done = GF_TRUE; + } else if (parameter->reply == GF_HTTP_HEAD) { + sess->done = GF_FALSE; + sess->file_pos = sess->file_size; + sess->is_head = GF_TRUE; + } else { + sess->done = GF_FALSE; + if (gf_list_find(sess->ctx->active_sessions, sess)<0) { + gf_list_add(sess->ctx->active_sessions, sess); + gf_sk_group_register(sess->ctx->sg, sess->socket); + } + sess->is_head = GF_FALSE; + if (not_modified) + sess->done = GF_TRUE; + } + sess->last_active_time = gf_sys_clock_high_res(); + return; + +exit: + if (response) + gf_dynstrcat(&rsp_buf, response, NULL); + else + gf_dynstrcat(&rsp_buf, "HTTP/1.1 501 Not Implemented\r\n", NULL); + + gf_dynstrcat(&rsp_buf, "Server: ", NULL); + gf_dynstrcat(&rsp_buf, sess->ctx->user_agent, NULL); + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + httpout_insert_date(gf_net_get_utc(), &rsp_buf, GF_FALSE); + gf_dynstrcat(&rsp_buf, "Connection: close\r\n", NULL); + if (sess->ctx->cors) { + gf_dynstrcat(&rsp_buf, "Access-Control-Allow-Origin: *\r\n", NULL); + gf_dynstrcat(&rsp_buf, "Access-Control-Expose-Headers: *\r\n", NULL); + } + if (response_body) { + body_size = (u32) strlen(response_body); + gf_dynstrcat(&rsp_buf, "Content-Type: text/plain\r\n", NULL); + gf_dynstrcat(&rsp_buf, "Content-Length: ", NULL); + sprintf(szFmt, "%d", body_size); + gf_dynstrcat(&rsp_buf, szFmt, NULL); + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + } + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + if (response_body) { + gf_dynstrcat(&rsp_buf, response_body, NULL); + gf_free(response_body); + } + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Sending response to %s:\n%s\n", sess->peer_address, rsp_buf)); + httpout_sess_send(sess, rsp_buf, (u32) strlen(rsp_buf)); + gf_free(rsp_buf); + + if (sess->do_log) { + sess->req_id = ++sess->ctx->req_id; + GF_LOG(GF_LOG_WARNING, GF_LOG_ALL, ("[HTTPOut] REQ#%d %s %s %s error %d\n", sess->req_id, sess->peer_address, get_method_name(parameter->reply), url+1, sess->reply_code)); + } + + sess->upload_type = 0; + httpout_reset_socket(sess); + return; +} + +enum +{ + HTTP_PUT_HEADER_ENCODING=0, + HTTP_PUT_HEADER_MIME, + HTTP_PUT_HEADER_RANGE, + HTTP_PUT_HEADER_DONE +}; + +static void httpout_in_io(void *usr_cbk, GF_NETIO_Parameter *parameter) +{ + GF_HTTPOutInput *in =usr_cbk; + + if (parameter->msg_type==GF_NETIO_GET_METHOD) { + if (in->is_delete) + parameter->name = "DELETE"; + else + parameter->name = in->ctx->post ? "POST" : "PUT"; + in->cur_header = HTTP_PUT_HEADER_ENCODING; + return; + } + if (parameter->msg_type==GF_NETIO_GET_HEADER) { + parameter->name = parameter->value = NULL; + + if (in->is_delete) return; + + switch (in->cur_header) { + case HTTP_PUT_HEADER_ENCODING: + parameter->name = "Transfer-Encoding"; + parameter->value = "chunked"; + if (in->mime) + in->cur_header = HTTP_PUT_HEADER_MIME; + else + in->cur_header = in->write_start_range ? HTTP_PUT_HEADER_RANGE : HTTP_PUT_HEADER_DONE; + break; + case HTTP_PUT_HEADER_MIME: + parameter->name = "Content-Type"; + parameter->value = in->mime; + in->cur_header = HTTP_PUT_HEADER_DONE; + if (in->write_start_range) + in->cur_header = HTTP_PUT_HEADER_RANGE; + break; + case HTTP_PUT_HEADER_RANGE: + parameter->name = "Range"; + if (in->write_end_range) { + sprintf(in->range_hdr, "bytes="LLU"-"LLU, in->write_start_range, in->write_end_range); + } else { + sprintf(in->range_hdr, "bytes="LLU"-", in->write_start_range); + } + parameter->value = in->range_hdr; + in->cur_header = HTTP_PUT_HEADER_DONE; + break; + default: + parameter->name = NULL; + parameter->value = NULL; + break; + } + } +} + +static GF_Err httpout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_HTTPOutInput *pctx; + GF_HTTPOutCtx *ctx = (GF_HTTPOutCtx *) gf_filter_get_udta(filter); + + if (!is_remove) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (!p || (p->value.uint!=GF_STREAM_FILE)) + return GF_NOT_SUPPORTED; + } + + pctx = gf_filter_pid_get_udta(pid); + if (!pctx) { + GF_HTTPOutCtx *ctx_orig; + Bool patch_blocks = GF_FALSE; + GF_FilterEvent evt; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DISABLE_PROGRESSIVE); + if (p && p->value.uint) { + if (ctx->hmode==MODE_PUSH) { + if (p->value.uint==GF_PID_FILE_PATCH_INSERT) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Push cannot be used to insert blocks in remote files (not supported by HTTP)\n")); + return GF_FILTER_NOT_SUPPORTED; + } + patch_blocks = GF_TRUE; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Server cannot deliver PIDs with progressive download disabled\n")); + return GF_FILTER_NOT_SUPPORTED; + } + } + + /*if PID was connected to an alias, get the alias context to get the destination + Otherwise PID was directly connected to the main filter, use main filter destination*/ + ctx_orig = (GF_HTTPOutCtx *) gf_filter_pid_get_alias_udta(pid); + if (!ctx_orig) ctx_orig = ctx; + + if (!ctx_orig->dst && (ctx->hmode==MODE_PUSH)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Push output but no destination set !\n")); + return GF_BAD_PARAM; + } + + GF_SAFEALLOC(pctx, GF_HTTPOutInput); + if (!pctx) return GF_OUT_OF_MEM; + pctx->ipid = pid; + pctx->ctx = ctx; + pctx->patch_blocks = patch_blocks; + pctx->hold = ctx->hold; + + if (ctx_orig->dst) { + char *path = strstr(ctx_orig->dst, "://"); + if (path) path = strchr(path+3, '/'); + if (path) pctx->path = gf_strdup(path); + + if (ctx->hmode==MODE_PUSH) { + GF_Err e; + pctx->upload = gf_dm_sess_new(gf_filter_get_download_manager(filter), ctx_orig->dst, GF_NETIO_SESSION_NOT_THREADED|GF_NETIO_SESSION_NOT_CACHED|GF_NETIO_SESSION_PERSISTENT, httpout_in_io, pctx, &e); + if (!pctx->upload) { + gf_free(pctx); + return e; + } +// gf_sk_group_register(ctx->sg, pctx->socket); + } else { + httpout_set_local_path(ctx, pctx); + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_MODE); + if (p && p->value.uint) pctx->dash_mode = GF_TRUE; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_MIME); + if (p && p->value.string) pctx->mime = gf_strdup(p->value.string); + + gf_filter_pid_set_udta(pid, pctx); + gf_list_add(ctx->inputs, pctx); + + gf_filter_pid_init_play_event(pid, &evt, 0.0, 1.0, "HTTPOut"); + gf_filter_pid_send_event(pid, &evt); + } + } + if (is_remove) { + return GF_OK; + } + + //we act as a server + + return GF_OK; +} + + +static void httpout_check_new_session(GF_HTTPOutCtx *ctx) +{ + char peer_address[GF_MAX_IP_NAME_LEN]; + GF_HTTPOutSession *sess; + GF_Err e; + GF_Socket *new_conn=NULL; + + e = gf_sk_accept(ctx->server_sock, &new_conn); + if (e==GF_IP_SOCK_WOULD_BLOCK) return; + else if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Accept failure %s\n", gf_error_to_string(e) )); + return; + } + //check max connections + if (ctx->maxc && (ctx->nb_connections>=ctx->maxc)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[HTTPOut] Connection rejected due to too many connections\n")); + gf_sk_del(new_conn); + return; + } + gf_sk_get_remote_address(new_conn, peer_address); + if (ctx->maxp) { + u32 i, nb_conn=0, count = gf_list_count(ctx->sessions); + for (i=0; isessions, i); + if (!strcmp(sess->peer_address, peer_address)) nb_conn++; + } + if (nb_conn>=ctx->maxp) { + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[HTTPOut] Connection rejected due to too many connections from peer %s\n", peer_address)); + gf_sk_del(new_conn); + return; + } + } + GF_SAFEALLOC(sess, GF_HTTPOutSession); + if (!sess) { + gf_sk_del(new_conn); + return; + } + + sess->socket = new_conn; + sess->ctx = ctx; + +#ifdef GPAC_HAS_SSL + if (ctx->ssl_ctx) { + sess->ssl = gf_ssl_new(ctx->ssl_ctx, new_conn, &e); + if (e) { + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Failed to create TLS session from %s: %s\n", sess->peer_address, gf_error_to_string(e) )); + gf_free(sess); + gf_sk_del(new_conn); + return; + } + } +#endif + + sess->http_sess = gf_dm_sess_new_server(new_conn, sess->ssl, httpout_sess_io, sess, &e); + if (!sess->http_sess) { + gf_sk_del(new_conn); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Failed to create HTTP server session from %s: %s\n", sess->peer_address, gf_error_to_string(e) )); + gf_free(sess); + return; + } + ctx->nb_connections++; + + gf_list_add(ctx->sessions, sess); + gf_list_add(ctx->active_sessions, sess); + gf_sk_group_register(ctx->sg, sess->socket); + + gf_sk_set_buffer_size(new_conn, GF_FALSE, ctx->block_size); + gf_sk_set_buffer_size(new_conn, GF_TRUE, ctx->block_size); + strcpy(sess->peer_address, peer_address); + + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Accepting new connection from %s\n", sess->peer_address)); + ctx->next_wake_us = 0; +} + +static GF_Err httpout_initialize(GF_Filter *filter) +{ + char szIP[1024]; + GF_Err e; + u16 port; + char *ip; + const char *ext = NULL; + char *sep, *url; + GF_HTTPOutCtx *ctx = (GF_HTTPOutCtx *) gf_filter_get_udta(filter); + + + port = ctx->port; + ip = ctx->ifce; + + url = ctx->dst; + sep = NULL; + if (ctx->dst) { + if (!strncmp(ctx->dst, "http://", 7)) { + sep = strchr(ctx->dst+7, '/'); + } else if (!strncmp(ctx->dst, "https://", 8)) { + sep = strchr(ctx->dst+8, '/'); + } + } + if (sep) { + u32 cplen; + url = sep+1; + cplen = (u32) (sep-ctx->dst-7); + if (cplen>1023) cplen=1023; + strncpy(szIP, ctx->dst+7, cplen); + szIP[1023] = 0; + sep = strchr(szIP, ':'); + if (sep) { + port = atoi(sep+1); + if (!port) port = ctx->port; + sep[0] = 0; + } + if (strlen(szIP)) ip = szIP; + } + if (url && !strlen(url)) + url = NULL; + + if (url) { + if (ctx->ext) ext = ctx->ext; + else { + ext = gf_file_ext_start(url); + if (!ext) ext = ".*"; + ext += 1; + } + + if (!ext && !ctx->mime) { + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[HTTPOut] No extension provided nor mime type for output file %s, cannot infer format\nThis may result in invalid filter chain resolution", ctx->dst)); + } else { + //static cap, streamtype = file + ctx->in_caps[0].code = GF_PROP_PID_STREAM_TYPE; + ctx->in_caps[0].val = PROP_UINT(GF_STREAM_FILE); + ctx->in_caps[0].flags = GF_CAPS_INPUT_STATIC; + + if (ctx->mime) { + ctx->in_caps[1].code = GF_PROP_PID_MIME; + ctx->in_caps[1].val = PROP_NAME( ctx->mime ); + ctx->in_caps[1].flags = GF_CAPS_INPUT; + } else { + strncpy(ctx->szExt, ext, 9); + ctx->szExt[9] = 0; + strlwr(ctx->szExt); + ctx->in_caps[1].code = GF_PROP_PID_FILE_EXT; + ctx->in_caps[1].val = PROP_NAME( ctx->szExt ); + ctx->in_caps[1].flags = GF_CAPS_INPUT; + } + gf_filter_override_caps(filter, ctx->in_caps, 2); + } + } + /*this is an alias for our main filter, nothing to initialize*/ + if (gf_filter_is_alias(filter)) { + return GF_OK; + } + + if (ctx->wdir) + ctx->hmode = MODE_DEFAULT; + + if (!url && !ctx->rdirs && !ctx->wdir && (ctx->hmode!=MODE_SOURCE)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] No root dir(s) for server, no URL set and not configured as source, cannot run!\n" )); + return GF_BAD_PARAM; + } + + if (ctx->rdirs || ctx->wdir) { + gf_filter_make_sticky(filter); + } else if (ctx->hmode!=MODE_PUSH) { + ctx->single_mode = GF_TRUE; + } + + ctx->sessions = gf_list_new(); + ctx->active_sessions = gf_list_new(); + ctx->inputs = gf_list_new(); + ctx->filter = filter; + //used in both server and push modes + ctx->sg = gf_sk_group_new(); + + if (ip) + ctx->ip = gf_strdup(ip); + + if (ctx->cache_control) { + if (!strcmp(ctx->cache_control, "none")) ctx->no_etag = GF_TRUE; + } + + if (ctx->hmode==MODE_PUSH) { + ctx->hold = GF_FALSE; + return GF_OK; + } + ctx->port = port; + if (ctx->cert && !ctx->pkey) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] missing server private key file\n")); + return GF_BAD_PARAM; + } + if (!ctx->cert && ctx->pkey) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] missing server certificate file\n")); + return GF_BAD_PARAM; + } + if (ctx->cert && ctx->pkey) { +#ifdef GPAC_HAS_SSL + if (!gf_file_exists(ctx->cert)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Certificate file %s not found\n", ctx->cert)); + return GF_IO_ERR; + } + if (!gf_file_exists(ctx->pkey)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Provate key file %s not found\n", ctx->pkey)); + return GF_IO_ERR; + } + if (gf_ssl_init_lib()) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Failed to initialize OpenSSL library\n")); + return GF_IO_ERR; + } + ctx->ssl_ctx = gf_ssl_server_context_new(ctx->cert, ctx->pkey); + if (!ctx->ssl_ctx) return GF_IO_ERR; +#else + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] TLS key/certificate set but GPAC compiled without TLS support\n")); + return GF_NOT_SUPPORTED; + +#endif + } + + ctx->server_sock = gf_sk_new(GF_SOCK_TYPE_TCP); + e = gf_sk_bind(ctx->server_sock, NULL, port, ip, 0, GF_SOCK_REUSE_PORT); + if (!e) e = gf_sk_listen(ctx->server_sock, ctx->maxc); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] failed to start server on port %d: %s\n", ctx->port, gf_error_to_string(e) )); + return e; + } + gf_sk_group_register(ctx->sg, ctx->server_sock); + + gf_sk_server_mode(ctx->server_sock, GF_TRUE); + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Server running on port %d\n", ctx->port)); + if (ctx->reqlog) { + GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] Server running on port %d\n", ctx->port)); + } + gf_filter_post_process_task(filter); + return GF_OK; +} + +static void httpout_del_session(GF_HTTPOutSession *s) +{ + gf_list_del_item(s->ctx->active_sessions, s); + gf_list_del_item(s->ctx->sessions, s); + if (s->socket) gf_sk_del(s->socket); + if (s->buffer) gf_free(s->buffer); + if (s->path) gf_free(s->path); + if (s->mime) gf_free(s->mime); + if (s->http_sess) gf_dm_sess_del(s->http_sess); + if (s->opid) gf_filter_pid_remove(s->opid); + if (s->resource) gf_fclose(s->resource); + if (s->ranges) gf_free(s->ranges); + gf_free(s); +} + +static void httpout_finalize(GF_Filter *filter) +{ + GF_HTTPOutCtx *ctx = (GF_HTTPOutCtx *) gf_filter_get_udta(filter); + + /*this is an alias for our main filter, nothing to finalize*/ + if (gf_filter_is_alias(filter)) + return; + + while (gf_list_count(ctx->sessions)) { + GF_HTTPOutSession *tmp = gf_list_get(ctx->sessions, 0); + tmp->opid = NULL; + httpout_del_session(tmp); + } + gf_list_del(ctx->sessions); + gf_list_del(ctx->active_sessions); + + while (gf_list_count(ctx->inputs)) { + GF_HTTPOutInput *tmp = gf_list_pop_back(ctx->inputs); + if (tmp->local_path) gf_free(tmp->local_path); + if (tmp->path) gf_free(tmp->path); + if (tmp->mime) gf_free(tmp->mime); + if (tmp->resource) gf_fclose(tmp->resource); + if (tmp->upload) gf_dm_sess_del(tmp->upload); + if (tmp->file_deletes) { + while (gf_list_count(tmp->file_deletes)) { + char *url = gf_list_pop_back(tmp->file_deletes); + gf_free(url); + } + gf_list_del(tmp->file_deletes); + } + gf_free(tmp); + } + gf_list_del(ctx->inputs); + if (ctx->server_sock) gf_sk_del(ctx->server_sock); + if (ctx->sg) gf_sk_group_del(ctx->sg); + if (ctx->ip) gf_free(ctx->ip); + +#ifdef GPAC_HAS_SSL + if (ctx->ssl_ctx) { + gf_ssl_server_context_del(ctx->ssl_ctx); + } +#endif +} + +static GF_Err httpout_sess_data_upload(GF_HTTPOutSession *sess, const u8 *data, u32 size) +{ + u32 remain, write, to_write; + + if (sess->opid || sess->reconfigure_output) { + GF_FilterPacket *pck; + u8 *buffer; + Bool is_first = GF_FALSE; + if (sess->reconfigure_output) { + sess->reconfigure_output = GF_FALSE; + gf_filter_pid_raw_new(sess->ctx->filter, sess->path, NULL, sess->mime, NULL, (u8 *) data, size, GF_FALSE, &sess->opid); + is_first = GF_TRUE; + } + if (!sess->opid) return GF_SERVICE_ERROR; + pck = gf_filter_pck_new_alloc(sess->opid, size, &buffer); + if (!pck) return GF_IO_ERR; + memcpy(buffer, data, size); + gf_filter_pck_set_framing(pck, is_first, GF_FALSE); + gf_filter_pck_send(pck); + return GF_OK; + } + if (!sess->resource) { + assert(0); + } + if (!sess->nb_ranges) { + write = (u32) gf_fwrite(data, size, sess->resource); + if (write != size) { + return GF_IO_ERR; + } + sess->nb_bytes += write; + sess->file_pos += write; + return GF_OK; + } + remain = size; + while (remain) { + to_write = (u32) (sess->ranges[sess->range_idx].end + 1 - sess->file_pos); + if (to_write>=remain) { + write = (u32) gf_fwrite(data, remain, sess->resource); + if (write != remain) { + return GF_IO_ERR; + } + sess->nb_bytes += write; + sess->file_pos += remain; + remain = 0; + break; + } + write = (u32) gf_fwrite(data, to_write, sess->resource); + sess->nb_bytes += write; + remain -= to_write; + sess->range_idx++; + if (sess->range_idx>=sess->nb_ranges) break; + sess->file_pos = sess->ranges[sess->range_idx].start; + gf_fseek(sess->resource, sess->file_pos, SEEK_SET); + } + if (remain) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Error in file upload, more bytes uploaded than described in byte range\n")); + return GF_BAD_PARAM; + } + return GF_OK; +} +static void httpout_check_connection(GF_HTTPOutSession *sess) +{ + GF_Err e = gf_sk_probe(sess->socket); + if (e==GF_IP_CONNECTION_CLOSED) { + sess->last_active_time = gf_sys_clock_high_res(); + sess->done = GF_TRUE; + sess->upload_type = 0; + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Client %s disconnected, destroying session\n", sess->peer_address)); + httpout_reset_socket(sess); + return; + } +} + +static void log_request_done(GF_HTTPOutSession *sess) +{ + if (!sess->do_log) return; + + if (!sess->socket) { + GF_LOG(GF_LOG_WARNING, GF_LOG_ALL, ("[HTTPOut] REQ#%d %s aborted!\n", sess->req_id, get_method_name(sess->method_type))); + } else { + char *unit = "bps"; + u64 diff_us = (gf_sys_clock_high_res() - sess->req_start_time); + Double bps = (Double)sess->nb_bytes * 8000000; + bps /= diff_us; + if (bps>1000000) { + unit = "mbps"; + bps/=1000000; + } else if (bps>1000) { + unit = "kbps"; + bps/=1000; + } + GF_LOG(GF_LOG_INFO, GF_LOG_ALL, ("[HTTPOut] REQ#%d %s done: reply %d - %d bytes in %d ms at %g %s\n", sess->req_id, get_method_name(sess->method_type), sess->reply_code, sess->nb_bytes, (u32) (diff_us/1000), bps, unit)); + } +} + +static void httpout_process_session(GF_Filter *filter, GF_HTTPOutCtx *ctx, GF_HTTPOutSession *sess) +{ + u32 read; + u64 to_read=0; + GF_Err e = GF_OK; + Bool close_session = ctx->close; + + + if (sess->upload_type) { + u32 i, count; + GF_Err write_e=GF_OK; + char *rsp_buf = NULL; + assert(sess->path); + if (sess->done) + return; + + read = 0; + e = gf_dm_sess_fetch_data(sess->http_sess, sess->buffer, ctx->block_size, &read); + + if (e>=GF_OK) { + if (read) + write_e = httpout_sess_data_upload(sess, sess->buffer, read); + else + write_e = GF_OK; + + if (!write_e) { + sess->last_active_time = gf_sys_clock_high_res(); + ctx->next_wake_us = 0; + //we way be in end of stream + if (e==GF_OK) + return; + } else { + e = write_e; + } + } else if (e==GF_IP_NETWORK_EMPTY) { + ctx->next_wake_us = 0; + sess->last_active_time = gf_sys_clock_high_res(); + httpout_check_connection(sess); + return; + } else if (e==GF_IP_CONNECTION_CLOSED) { + sess->last_active_time = gf_sys_clock_high_res(); + sess->done = GF_TRUE; + sess->upload_type = 0; + httpout_reset_socket(sess); + log_request_done(sess); + return; + } + //done (error or end of upload) + if (sess->opid) { + GF_FilterPacket *pck = gf_filter_pck_new_alloc(sess->opid, 0, NULL); + if (pck) { + gf_filter_pck_set_framing(pck, GF_FALSE, GF_TRUE); + gf_filter_pck_send(pck); + } + gf_filter_pid_set_eos(sess->opid); + } else { + if (sess->resource) gf_fclose(sess->resource); + sess->resource = NULL; + } + + + if (e==GF_EOS) { + if (sess->upload_type==2) { + sess->reply_code = 200; + gf_dynstrcat(&rsp_buf, "HTTP/1.1 200 OK\r\n", NULL); + } else { + gf_dynstrcat(&rsp_buf, "HTTP/1.1 201 Created\r\n", NULL); + sess->reply_code = 201; + } + } else if (write_e==GF_BAD_PARAM) { + gf_dynstrcat(&rsp_buf, "HTTP/1.1 416 Requested Range Not Satisfiable\r\n", NULL); + close_session = GF_TRUE; + sess->reply_code = 416; + } else { + gf_dynstrcat(&rsp_buf, "HTTP/1.1 500 Internal Server error\r\n", NULL); + close_session = GF_TRUE; + sess->reply_code = 500; + } + gf_dynstrcat(&rsp_buf, "Server: ", NULL); + gf_dynstrcat(&rsp_buf, sess->ctx->user_agent, NULL); + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + httpout_insert_date(gf_net_get_utc(), &rsp_buf, GF_FALSE); + if (close_session) + gf_dynstrcat(&rsp_buf, "Connection: close\r\n", NULL); + else + gf_dynstrcat(&rsp_buf, "Connection: keep-alive\r\n", NULL); + + if (sess->ctx->cors) { + gf_dynstrcat(&rsp_buf, "Access-Control-Allow-Origin: *\r\n", NULL); + gf_dynstrcat(&rsp_buf, "Access-Control-Expose-Headers: *\r\n", NULL); + } + + if (e==GF_EOS) { + gf_dynstrcat(&rsp_buf, "Content-Location: ", NULL); + if (ctx->hmode==MODE_SOURCE) { + gf_dynstrcat(&rsp_buf, sess->path, "/"); + } else { + char *spath = strchr(sess->path, '/'); + if (spath) spath = spath+1; + else spath = sess->path; + gf_dynstrcat(&rsp_buf, spath, NULL); + } + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + } + gf_dynstrcat(&rsp_buf, "\r\n", NULL); + + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Sending PUT response to %s:\n%s\n", sess->peer_address, rsp_buf)); + + log_request_done(sess); + + httpout_sess_send(sess, rsp_buf, (u32) strlen(rsp_buf)); + gf_free(rsp_buf); + + sess->last_active_time = gf_sys_clock_high_res(); + sess->done = GF_TRUE; + sess->upload_type = 0; + + //notify all download (GET, HEAD) sessions using the same resource that we are done + count = gf_list_count(sess->ctx->sessions); + for (i=0; ictx->sessions, i); + if (a_sess == sess) continue; + if (a_sess->done || !a_sess->put_in_progress) continue; + if (a_sess->path && sess->path && !strcmp(a_sess->path, sess->path)) { + a_sess->put_in_progress = (sess->reply_code>201) ? 2 : 0; + a_sess->file_size = gf_fsize(a_sess->resource); + gf_fseek(a_sess->resource, a_sess->file_pos, SEEK_SET); + } + } + + if (close_session) { + httpout_reset_socket(sess); + } else { + //we keep alive, recreate a dm session + if (sess->http_sess) gf_dm_sess_del(sess->http_sess); + sess->http_sess = gf_dm_sess_new_server(sess->socket, sess->ssl, httpout_sess_io, sess, &e); + if (e) { + httpout_reset_socket(sess); + } + } + return; + } + //read request and process headers + else if (sess->http_sess) { + if (!gf_sk_group_sock_is_set(ctx->sg, sess->socket, GF_SK_SELECT_READ)) { + return; + } + e = gf_dm_sess_process(sess->http_sess); + + if (e==GF_IP_CONNECTION_CLOSED) { + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Connection to %s closed\n", sess->peer_address)); + httpout_reset_socket(sess); + if (!sess->done) + log_request_done(sess); + return; + } + + sess->last_active_time = gf_sys_clock_high_res(); + ctx->next_wake_us = 0; + + //request has been process, if not an upload we don't need the session anymore + //otherwise we use the session to parse transfered data + if (!sess->upload_type) { + //no support for request pipeline yet, just remove the downloader session until done + gf_dm_sess_del(sess->http_sess); + sess->http_sess = NULL; + if (sess->done && sess->socket && (e!=GF_IP_NETWORK_EMPTY) ) + goto session_done; + } else { + //flush any data received + httpout_process_session(filter, ctx, sess); + return; + } + } + if (!sess->socket) return; + if (sess->done) return; + if (sess->in_source) return; + + if (!gf_sk_group_sock_is_set(ctx->sg, sess->socket, GF_SK_SELECT_WRITE)) { + return; + } + //resource is not set + if (!sess->resource && sess->path) { + if (sess->in_source && !sess->in_source->nb_write) { + sess->last_active_time = gf_sys_clock_high_res(); + return; + } + sess->resource = gf_fopen(sess->path, "rb"); + if (!sess->resource) return; + sess->last_active_time = gf_sys_clock_high_res(); + gf_fseek(sess->resource, sess->file_pos, SEEK_SET); + } + + //refresh file size + if (sess->put_in_progress==1) { + sess->file_size = gf_fsize(sess->resource); + gf_fseek(sess->resource, sess->file_pos, SEEK_SET); + } + + //we have ranges + if (sess->nb_ranges) { + //current range is done + if ((s64) sess->file_pos >= sess->ranges[sess->range_idx].end) { + sess->range_idx++; + //load next range, seeking file + if (sess->range_idxnb_ranges) { + sess->file_pos = (u64) sess->ranges[sess->range_idx].start; + gf_fseek(sess->resource, sess->file_pos, SEEK_SET); + } + } + if (sess->range_idxnb_ranges) { + to_read = sess->ranges[sess->range_idx].end + 1 - sess->file_pos; + } + } else if (sess->file_pos < sess->file_size) { + to_read = sess->file_size - sess->file_pos; + } + + if (to_read) { + ctx->next_wake_us = 0; + + if (to_read > (u64) sess->ctx->block_size) + to_read = (u64) sess->ctx->block_size; + + read = (u32) gf_fread(sess->buffer, (u32) to_read, sess->resource); + + //transfer of file being uploaded, use chunk transfer + if (sess->use_chunk_transfer) { + char szHdr[100]; + u32 len; + sprintf(szHdr, "%X\r\n", read); + len = (u32) strlen(szHdr); + + e = httpout_sess_send(sess, szHdr, len); + e |= httpout_sess_send(sess, sess->buffer, read); + e |= httpout_sess_send(sess, "\r\n", 2); + } else { + e = httpout_sess_send(sess, sess->buffer, read); + } + sess->last_active_time = gf_sys_clock_high_res(); + + sess->file_pos += read; + sess->nb_bytes += read; + + if (e) { + if (e==GF_IP_CONNECTION_CLOSED) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[HTTPOut] Connection to %s for %s closed\n", sess->peer_address, sess->path)); + sess->done = GF_TRUE; + httpout_reset_socket(sess); + log_request_done(sess); + return; + } + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Error sending data to %s for %s: %s\n", sess->peer_address, sess->path, gf_error_to_string(e) )); + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[HTTPOut] sending data to %s for %s: "LLU"/"LLU" bytes\n", sess->peer_address, sess->path, sess->nb_bytes, sess->bytes_in_req)); + } + return; + } + //file not done yet ... + if (sess->file_in_progress || (sess->put_in_progress==1)) { + sess->last_active_time = gf_sys_clock_high_res(); + return; + } + + if (!sess->is_head) { + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Done sending %s to %s ("LLU"/"LLU" bytes)\n", sess->path, sess->peer_address, sess->nb_bytes, sess->bytes_in_req)); + } + + log_request_done(sess); + +session_done: + + sess->file_pos = sess->file_size; + sess->last_active_time = gf_sys_clock_high_res(); + + //an error ocured uploading the resource, we cannot notify that error so we force a close... + if (sess->put_in_progress==2) + close_session = GF_TRUE; + + if (!sess->done) { + if (sess->use_chunk_transfer) { + httpout_sess_send(sess, "0\r\n\r\n", 5); + } + if (sess->resource) gf_fclose(sess->resource); + sess->resource = NULL; + //keep resource active + sess->done = GF_TRUE; + } + if (close_session) { + httpout_reset_socket(sess); + } else { + //we keep alive, recreate an dm sess + sess->http_sess = gf_dm_sess_new_server(sess->socket, sess->ssl, httpout_sess_io, sess, &e); + if (e) { + httpout_reset_socket(sess); + } + } +} + +static Bool httpout_open_input(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in, const char *name, Bool is_delete) +{ +// Bool reassign_clients = GF_TRUE; + u32 len; + const char *dir; + const char *sep = name ? strstr(name, "://") : NULL; + + if (sep) sep = strchr(sep+3, '/'); + if (!sep) { + return GF_FALSE; + } + if (in->is_open) return GF_FALSE; + in->done = GF_FALSE; + in->is_open = GF_TRUE; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[HTTPOut] %s output file %s\n", is_delete ? "Deleting" : "Opening", name)); + + if (in->upload) { + GF_Err e; + char *old = in->path; + in->is_delete = is_delete; + in->path = gf_strdup(name); + if (old) gf_free(old); + e = gf_dm_sess_setup_from_url(in->upload, in->path, GF_TRUE); + if (!e) { + in->cur_header = 0; + e = gf_dm_sess_process(in->upload); + } + + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Failed to open output file %s: %s\n", in->path, gf_error_to_string(e) )); + in->is_open = GF_FALSE; + return GF_FALSE; + } + if (is_delete) { + //get response + gf_dm_sess_process(in->upload); + in->done = GF_TRUE; + in->is_open = GF_FALSE; + in->is_delete = GF_FALSE; + } + return GF_TRUE; + } + //server mode not recording, nothing to do + if (!ctx->rdirs) return GF_FALSE; + + if (in->resource) return GF_FALSE; + + dir = gf_list_get(ctx->rdirs, 0); + if (!dir) return GF_FALSE; + len = (u32) strlen(dir); + if (!len) return GF_FALSE; + + if (in->path && !strcmp(in->path, sep)) { +// reassign_clients = GF_FALSE; + } else { + if (in->path) gf_free(in->path); + in->path = gf_strdup(sep); + } + httpout_set_local_path(ctx, in); + + if (is_delete) { + gf_file_delete(in->local_path); + in->done = GF_TRUE; + in->is_open = GF_FALSE; + in->is_delete = GF_FALSE; + } else { + in->resource = gf_fopen(in->local_path, "wb"); + if (!in->resource) + in->is_open = GF_FALSE; + } + return GF_TRUE; +} + +static void httpout_close_input(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in) +{ + if (!in->is_open) return; + in->is_open = GF_FALSE; + in->done = GF_TRUE; + + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[HTTPOut] Closing output file %s\n", in->local_path ? in->local_path : in->path)); + if (in->upload) { + GF_Err e = gf_dm_sess_send(in->upload, "0\r\n\r\n", 5); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Error writing last chunk to output file %s: %s\n", in->local_path ? in->local_path : in->path, gf_error_to_string(e) )); + } + //signal we're done sending the body + gf_dm_sess_send(in->upload, NULL, 0); + + //fetch reply (blocking) + e = gf_dm_sess_process(in->upload); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Error closing output file %s: %s\n", in->local_path ? in->local_path : in->path, gf_error_to_string(e) )); + } + + } else { + u32 i, count; + if (in->resource) { + u64 file_size = gf_ftell(in->resource); + + assert(in->local_path); + //detach all clients from this input and reassign to a regular output + count = gf_list_count(ctx->sessions); + for (i=0; isessions, i); + if (sess->in_source != in) continue; + assert(sess->file_in_progress); + sess->in_source = NULL; + sess->file_size = file_size; + sess->file_in_progress = GF_FALSE; + } + gf_fclose(in->resource); + in->resource = NULL; + } else { + count = gf_list_count(ctx->active_sessions); + for (i=0; iactive_sessions, i); + if (sess->in_source != in) continue; + + httpout_sess_send(sess, "0\r\n\r\n", 5); + log_request_done(sess); + } + } + } + in->nb_write = 0; +} +u32 httpout_write_input(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in, const u8 *pck_data, u32 pck_size, Bool file_start) +{ + char szHdr[100]; + u32 len, out=0; + + if (!in->is_open) return 0; + + sprintf(szHdr, "%X\r\n", pck_size); + len = (u32) strlen(szHdr); + + GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[HTTPOut] Writing %d bytes to output file %s\n", pck_size, in->local_path ? in->local_path : in->path)); + + if (in->upload) { + GF_Err e; + u32 nb_retry = 0; + out = pck_size; + +retry: + e = gf_dm_sess_send(in->upload, szHdr, len); + if (!e) e = gf_dm_sess_send(in->upload, (u8 *) pck_data, pck_size); + strcpy(szHdr, "\r\n"); + if (!e) e = gf_dm_sess_send(in->upload, szHdr, 2); + + if (e==GF_IP_CONNECTION_CLOSED) { + if (file_start && (nb_retry<10) ) { + in->is_open = GF_FALSE; + nb_retry++; + if (httpout_open_input(ctx, in, in->path, GF_FALSE)) + goto retry; + } + } + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Error writing to output file %s: %s\n", in->local_path ? in->local_path : in->path, gf_error_to_string(e) )); + out = 0; + } + + } else { + u32 i, count = gf_list_count(ctx->active_sessions); + + if (in->resource) { + out = (u32) gf_fwrite(pck_data, pck_size, in->resource); + } + + for (i=0; iactive_sessions, i); + if (sess->in_source != in) continue; + + if (sess->send_init_data && in->tunein_data_size && !sess->file_in_progress) { + char szHdrInit[100]; + sprintf(szHdrInit, "%X\r\n", in->tunein_data_size); + u32 len_hdr = (u32) strlen(szHdrInit); + httpout_sess_send(sess, szHdrInit, len_hdr); + httpout_sess_send(sess, in->tunein_data, in->tunein_data_size); + httpout_sess_send(sess, "\r\n", 2); + sess->nb_bytes += in->tunein_data_size; + } + sess->send_init_data = GF_FALSE; + out = pck_size; + + /*source is read from disk, but update file size (this avoids refreshing the file size at each process)*/ + if (sess->file_in_progress) { + sess->file_size += pck_size; + } + /*source is not read from disk, write data*/ + else { + httpout_sess_send(sess, szHdr, len); + httpout_sess_send(sess, pck_data, pck_size); + httpout_sess_send(sess, "\r\n", 2); + sess->nb_bytes += pck_size; + } + } + } + ctx->next_wake_us = 0; + return out; +} + +static Bool httpout_input_write_ready(GF_HTTPOutCtx *ctx, GF_HTTPOutInput *in) +{ + u32 i, count; + if (ctx->rdirs) + return GF_TRUE; + + if (in->upload) { +/* if (!gf_sk_group_sock_is_set(ctx->sg, in->socket, GF_SK_SELECT_WRITE)) + return GF_FALSE; +*/ + return GF_TRUE; + } + + count = gf_list_count(ctx->active_sessions); + for (i=0; iactive_sessions, i); + if (sess->in_source != in) continue; + + if (!sess->file_in_progress && !gf_sk_group_sock_is_set(ctx->sg, sess->socket, GF_SK_SELECT_WRITE)) + return GF_FALSE; + } + return count ? GF_TRUE : GF_FALSE; +} + +static void httpin_send_seg_info(GF_HTTPOutInput *in) +{ + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_SEGMENT_SIZE, in->ipid); + evt.seg_size.seg_url = NULL; + + if (in->dash_mode==1) { + evt.seg_size.is_init = GF_TRUE; + in->dash_mode = 2; + evt.seg_size.media_range_start = 0; + evt.seg_size.media_range_end = 0; + gf_filter_pid_send_event(in->ipid, &evt); + } else { + evt.seg_size.is_init = GF_FALSE; + evt.seg_size.media_range_start = in->offset_at_seg_start; + evt.seg_size.media_range_end = in->nb_write-1; + in->offset_at_seg_start = 1+evt.seg_size.media_range_end; + gf_filter_pid_send_event(in->ipid, &evt); + } +} + +static void httpout_process_inputs(GF_HTTPOutCtx *ctx) +{ + u32 i, nb_eos=0, nb_nopck=0, count = gf_list_count(ctx->inputs); + for (i=0; iinputs, i); + + //not sending/writing anything, delete files + if (!in->is_open && in->file_deletes) { + while (gf_list_count(in->file_deletes)) { + char *url = gf_list_pop_back(in->file_deletes); + httpout_open_input(ctx, in, url, GF_TRUE); + gf_free(url); + } + gf_list_del(in->file_deletes); + in->file_deletes = NULL; + } + + //no destination and holding for first connect, don't drop + if (!ctx->hmode && !ctx->rdirs && !in->nb_dest && in->hold) { + continue; + } + + pck = gf_filter_pid_get_packet(in->ipid); + if (!pck) { + nb_nopck++; + //check end of PID state + if (gf_filter_pid_is_eos(in->ipid)) { + nb_eos++; + if (in->dash_mode && in->is_open) { + httpin_send_seg_info(in); + } + httpout_close_input(ctx, in); + } + continue; + } + + + gf_filter_pck_get_framing(pck, &start, &end); + + if (in->dash_mode) { + const GF_PropertyValue *p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM); + if (p) { + httpin_send_seg_info(in); + + if ( gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME)) + start = GF_TRUE; + } + } + + if (start) { + const GF_PropertyValue *fnum, *fname; + const char *name = NULL; + fname = NULL; + + if (in->is_open) + httpout_close_input(ctx, in); + + //file num increased per packet, open new file + fnum = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM); + if (fnum) { + fname = gf_filter_pid_get_property(in->ipid, GF_PROP_PID_OUTPATH); + //if (!fname) name = ctx->dst; + } + //filename change at packet start, open new file + if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME); + if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PID_OUTPATH); + if (fname) name = fname->value.string; + + if (!name) { + /*if PID was connected to an alias, get the alias context to get the destination + Otherwise PID was directly connected to the main filter, use main filter destination*/ + GF_HTTPOutCtx *orig_ctx = gf_filter_pid_get_alias_udta(in->ipid); + if (!orig_ctx) orig_ctx = ctx; + name = orig_ctx->dst; + } + + httpout_open_input(ctx, in, name, GF_FALSE); + + if (!ctx->hmode && !ctx->rdirs && !in->nb_dest) { + if ((gf_filter_pck_get_dependency_flags(pck)==0xFF) && (gf_filter_pck_get_carousel_version(pck)==1)) { + pck_data = gf_filter_pck_get_data(pck, &pck_size); + if (pck_data) { + in->tunein_data_size = pck_size; + in->tunein_data = gf_realloc(in->tunein_data, pck_size); + memcpy(in->tunein_data, pck_data, pck_size); + } + } + } + } + + //no destination and not holding packets (either first connection not here or disabled), trash packet + if (!ctx->hmode && !ctx->rdirs && !in->nb_dest && !in->hold) { + gf_filter_pid_drop_packet(in->ipid); + continue; + } + + if (!httpout_input_write_ready(ctx, in)) { + continue; + } + + pck_data = gf_filter_pck_get_data(pck, &pck_size); + if (in->upload || ctx->single_mode || in->resource) { + GF_FilterFrameInterface *hwf = gf_filter_pck_get_frame_interface(pck); + if (pck_data && pck_size) { + + if (in->patch_blocks && gf_filter_pck_get_seek_flag(pck)) { + u64 bo = gf_filter_pck_get_byte_offset(pck); + if (bo==GF_FILTER_NO_BO) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Cannot patch file, wrong byte offset\n")); + } else { + if (gf_filter_pck_get_interlaced(pck)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Cannot patch file by byte insertion, not supported by HTTP\n")); + } else { + u64 pos = in->nb_write; + //close file + httpout_close_input(ctx, in); + //re-open file + in->write_start_range = bo; + in->write_end_range = bo + pck_size - 1; + httpout_open_input(ctx, in, in->path, GF_FALSE); + + nb_write = httpout_write_input(ctx, in, pck_data, pck_size, start); + if (nb_write!=pck_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Write error, wrote %d bytes but had %d to write\n", nb_write, pck_size)); + } + httpout_close_input(ctx, in); + + in->write_start_range = pos; + in->write_end_range = 0; + } + } + } else { + if (in->write_start_range) { + httpout_open_input(ctx, in, in->path, GF_FALSE); + } + + nb_write = httpout_write_input(ctx, in, pck_data, pck_size, start); + if (nb_write!=pck_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Write error, wrote %d bytes but had %d to write\n", nb_write, pck_size)); + } + in->nb_write += nb_write; + } + } else if (hwf) { + u32 w, h, stride, stride_uv, pf; + u32 nb_planes, uv_height; + const GF_PropertyValue *p; + p = gf_filter_pid_get_property(in->ipid, GF_PROP_PID_WIDTH); + w = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(in->ipid, GF_PROP_PID_HEIGHT); + h = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(in->ipid, GF_PROP_PID_PIXFMT); + pf = p ? p->value.uint : 0; + + stride = stride_uv = 0; + + if (gf_pixel_get_size_info(pf, w, h, NULL, &stride, &stride_uv, &nb_planes, &uv_height) == GF_TRUE) { + u32 k; + for (k=0; kget_plane(hwf, k, &out_ptr, &out_stride); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] Failed to fetch plane #%d data from hardware frame, cannot write\n", k)); + break; + } + if (k) { + write_h = uv_height; + lsize = stride_uv; + } else { + write_h = h; + lsize = stride; + } + for (j=0; jnb_write += nb_write; + out_ptr += out_stride; + start = GF_FALSE; + } + } + } + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_HTTP, ("[HTTPOut] No data associated with packet, cannot write\n")); + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPOut] output file handle is not opened, discarding %d bytes\n", pck_size)); + } + gf_filter_pid_drop_packet(in->ipid); + if (end) { + httpout_close_input(ctx, in); + } + } + + if (count && (nb_eos==count)) { + if (ctx->rdirs) { + if (gf_list_count(ctx->active_sessions)) + gf_filter_post_process_task(ctx->filter); + else + ctx->done = GF_TRUE; + } + else + ctx->done = GF_TRUE; + } + //push mode and no packets on inputs, do not ask for RT reschedule (we will get called f new packets are to be processed) + if ((nb_nopck==count) && (ctx->hmode==MODE_PUSH)) + ctx->next_wake_us = 0; +} + +static GF_Err httpout_process(GF_Filter *filter) +{ + GF_Err e=GF_OK; + u32 i, count; + GF_HTTPOutCtx *ctx = gf_filter_get_udta(filter); + + if (ctx->done) + return GF_EOS; + + //wakeup every 50ms when inactive + ctx->next_wake_us = 50000; + + gf_sk_group_select(ctx->sg, 10, GF_SK_SELECT_BOTH); + if ((e==GF_OK) && ctx->server_sock) { + //server mode, check pending connections + if (gf_sk_group_sock_is_set(ctx->sg, ctx->server_sock, GF_SK_SELECT_READ)) { + httpout_check_new_session(ctx); + } + + count = gf_list_count(ctx->active_sessions); + for (i=0; iactive_sessions, i); + //push + if (sess->in_source) continue; + + //regular download + httpout_process_session(filter, ctx, sess); + //closed, remove + if (! sess->socket) { + httpout_del_session(sess); + i--; + count--; + if (!count && ctx->quit) + ctx->done = GF_TRUE; + } + } + } + + httpout_process_inputs(ctx); + + if (ctx->timeout && ctx->server_sock) { + count = gf_list_count(ctx->active_sessions); + for (i=0; iactive_sessions, i); + if (!sess->done) continue; + + diff_sec = (u32) (gf_sys_clock_high_res() - sess->last_active_time)/1000000; + if (diff_sec>ctx->timeout) { + GF_LOG(GF_LOG_INFO, GF_LOG_HTTP, ("[HTTPOut] Timeout for peer %s after %d sec, closing connection (last request %s)\n", sess->peer_address, diff_sec, sess->in_source ? sess->in_source->path : sess->path )); + + httpout_reset_socket(sess); + + httpout_del_session(sess); + i--; + count--; + } + } + } + + if (e==GF_EOS) { + if (ctx->dst) return GF_EOS; + e=GF_OK; + } + + if (ctx->next_wake_us) + gf_filter_ask_rt_reschedule(filter, ctx->next_wake_us); + + return e; +} + +static Bool httpout_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_HTTPOutInput *in; + GF_HTTPOutCtx *ctx; + if (evt->base.type!=GF_FEVT_FILE_DELETE) + return GF_FALSE; + + if (!evt->base.on_pid) return GF_TRUE; + in = gf_filter_pid_get_udta(evt->base.on_pid); + if (!in) return GF_TRUE; + + ctx = (GF_HTTPOutCtx *) gf_filter_get_udta(filter); + //simple server mode (no record, no push), nothing to do + if (!in->upload && !ctx->rdirs) return GF_TRUE; + + if (!in->file_deletes) + in->file_deletes = gf_list_new(); + + gf_list_add(in->file_deletes, gf_strdup(evt->file_del.url)); + return GF_TRUE; +} + +static GF_FilterProbeScore httpout_probe_url(const char *url, const char *mime) +{ + if (!strnicmp(url, "http://", 7)) return GF_FPROBE_SUPPORTED; + if (!strnicmp(url, "https://", 8)) return GF_FPROBE_SUPPORTED; + return GF_FPROBE_NOT_SUPPORTED; +} + +static Bool httpout_use_alias(GF_Filter *filter, const char *url, const char *mime) +{ + u32 len; + char *sep; + GF_HTTPOutCtx *ctx = (GF_HTTPOutCtx *) gf_filter_get_udta(filter); + + //check we have same hostname. If so, accept this destination as a source for our filter + sep = strstr(url, "://"); + if (!sep) return GF_FALSE; + sep += 3; + sep = strchr(sep, '/'); + if (!sep) { + if (!strcmp(ctx->dst, url)) return GF_TRUE; + return GF_FALSE; + } + len = (u32) (sep - url); + if (!strncmp(ctx->dst, url, len)) return GF_TRUE; + return GF_FALSE; +} + +static const GF_FilterCapability HTTPOutCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT,GF_PROP_PID_FILE_EXT, "*"), + CAP_STRING(GF_CAPS_INPUT,GF_PROP_PID_MIME, "*"), + {0}, + CAP_UINT(GF_CAPS_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), +}; + + +#define OFFS(_n) #_n, offsetof(GF_HTTPOutCtx, _n) +static const GF_FilterArgs HTTPOutArgs[] = +{ + { OFFS(dst), "location of destination file - see filter help ", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(port), "server port", GF_PROP_UINT, "80", NULL, 0}, + { OFFS(ifce), "default network inteface to use", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(rdirs), "list of directories to expose for read - see filter help", GF_PROP_STRING_LIST, NULL, NULL, 0}, + { OFFS(wdir), "directory to expose for write - see filter help", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(cert), "certificate file in PEM format to use for TLS mode", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(pkey), "private key file in PEM format to use for TLS mode", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(block_size), "block size used to read and write TCP socket", GF_PROP_UINT, "10000", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(user_agent), "user agent string, by default solved from GPAC preferences", GF_PROP_STRING, "$GUA", NULL, 0}, + { OFFS(close), "close HTTP connection after each request", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(maxc), "maximum number of connections, 0 is unlimited", GF_PROP_UINT, "100", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(maxp), "maximum number of connections for one peer, 0 is unlimited", GF_PROP_UINT, "6", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(cache_control), "specify the `Cache-Control` string to add; `none` disable ETag", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(hold), "hold packets until one client connects", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(hmode), "filter operation mode, ignored if [-wdir]() is set. See filter help for more details. Mode can be\n" + "- default: run in server mode (see filter help)\n" + "- push: run in client mode using PUT or POST (see filter help)\n" + "- source: use server as source filter on incoming PUT/POST", GF_PROP_UINT, "default", "default|push|source", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(timeout), "timeout in seconds for persistent connections; 0 disable timeout", GF_PROP_UINT, "30", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ext), "set extension for graph resolution, regardless of file extension", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(mime), "set mime type for graph resolution", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(quit), "exit server once all input PIDs are done and client disconnects (for test purposes)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(post), "use POST instead of PUT for uploading files", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(dlist), "enable HTML listing for GET requests on directories", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(sutc), "insert server UTC in response headers as `Server-UTC: VAL_IN_MS`", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(cors), "insert CORS header allowing all domains", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(reqlog), "provide short log of the requests indicated in this option (comma separated list, `*` for all) regardless of HTTP log settings", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(ice), "insert ICE meta-data in response headers in sink mode - see filter help", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + {0} +}; + + +GF_FilterRegister HTTPOutRegister = { + .name = "httpout", + GF_FS_SET_DESCRIPTION("HTTP Server") + + GF_FS_SET_HELP("The HTTP output filter can act as:\n" + "- a simple HTTP server\n" + "- an HTTP server sink\n" + "- an HTTP server file sink\n" + "- an HTTP __client__ sink\n" + "- an HTTP server __source__\n" + " \n" + "The server currently handles GET, HEAD, PUT, POST, DELETE methods.\n" + "Single or multiple byte ranges are supported for both GET and PUT/POST methods, in all server modes.\n" + "- for GET, the resulting body is a single-part body formed by the concatenated byte ranges as requested (no overlap checking).\n" + "- for PUT/POST, the received data is pushed to the target file according to the byte ranges specified in the client request.\n" + " \n" + "Warning: the partial PUT request is RFC2616 compliant but not compliant with RFC7230. PATCH method is not yet implemented in GPAC.\n" + " \n" + "When a single read directory is specified, the server root `/` is the content of this directory.\n" + "When multiple read directories are specified, the server root `/` contains the list of the mount points with their directory names.\n" + "When a write directory is specified, the upload resource name identifies a file in this directory (the write directory name is not present in the URL).\n" + " \n" + "Listing can be enabled on server using [-dlist]().\n" + "When disabled, a GET on a directory will fail.\n" + "When enabled, a GET on a directory will return a simple HTML listing of the content inspired from Apache.\n" + " \n" + "# Simple HTTP server\n" + "In this mode, the filter doesn't need any input connection and exposes all files in the directories given by [-rdirs]().\n" + "PUT and POST methods are only supported if a write directory is specified by [-wdir]() option.\n" + "EX gpac httpout:rdirs=outcoming\n" + "This sets up a read-only server.\n" + " \n" + "EX gpac httpout:wdir=incoming\n" + "This sets up a write-only server.\n" + " \n" + "EX gpac httpout:rdirs=outcoming:wdir=incoming:port=8080\n" + "This sets up a read-write server running on [-port]() 8080.\n" + " \n" + "# HTTP server sink\n" + "In this mode, the filter will forward input PIDs to connected clients, trashing the data if no client is connected unless [-hold]() is specified.\n" + "The filter doesn't use any read directory in this mode.\n" + "This mode is mostly usefull to setup live HTTP streaming of media sessions such as MP3, MPEG-2 TS or other muxed representations:\n" + "EX gpac -i MP3_SOURCE -o http://localhost/live.mp3 --hold\n" + "In this example, the server waits for client requests on `/live.mp3` and will then push each input packet to all connected clients.\n" + "If the source is not real-time, you can inject a reframer filter performing realtime regulation.\n" + "EX gpac -i MP3_SOURCE reframer:rt=on @ -o http://localhost/live.mp3\n" + "In this example, the server will push each input packet to all connected clients, or trash the packet if no connected clients.\n" + " \n" + "In this mode, ICECast meta-data can be inserted using [-ice](). The default inserted values are `ice-audio-info`, `icy-br`, `icy-pub` (set to 1) and `icy-name` if input `ServiceName` property is set.\n" + "The server will also look for any property called `ice-*` on the input pid and inject them.\n" + "EX gpac -i source.mp3:#ice-Genre=CoolRock -o http://IP/live.mp3 --ice\n" + "This will inject the header `ice-Genre: CoolRock` in the response." + " \n" + "# HTTP server file sink\n" + "In this mode, the filter will write input PIDs to files in the first read directory specified, acting as a file output sink.\n" + "The filter uses a read directory in this mode, which must be writable.\n" + "Upon client GET request, the server will check if the requested URL matches the name of a file currently being written by the server.\n" + "- If so, the server will:\n" + " - send the content using HTTP chunk transfer mode, starting with what is already written on disk\n" + " - push remaining data to the client as soon as recieved while writing it to disk, until source file is done\n" + "- If not so, the server will simply send the file from the disk as a regular HTTP session, without chunk transfer.\n" + " \nThis mode is typically used for origin server in HAS sessions where clients may request files while they are being produced (low latency DASH).\n" + "EX gpac -i SOURCE reframer:rt=on @ -o http://localhost:8080/live.mpd --rdirs=temp --dmode=dynamic --cdur=0.1\n" + "In this example, a real-time dynamic DASH session with chunks of 100ms is created, outputing files in `temp`. A client connecting to the live edge will receive segments as they are produced using HTTP chunk transfer.\n" + " \n" + "# HTTP client sink\n" + "In this mode, the filter will upload input PIDs data to remote server using PUT (or POST if [-post]() is set).\n" + "This mode must be explicitly activated using [-hmode]().\n" + "The filter uses no read or write directories in this mode.\n" + "EX gpac -i SOURCE -o http://targethost:8080/live.mpd:gpac:hmode=push\n" + "In this example, the filter will send PUT methods to the server running on [-port]() 8080 at `targethost` location (IP address or name).\n" + " \n" + "# HTTP server source\n" + "In this mode, the server acts as a source rather than a sink. It declares incoming PUT or POST methods as output PIDs\n" + "This mode must be explicitly activated using [-hmode]().\n" + "The filter uses no read or write directories in this mode, and uploaded data is NOT stored by the server.\n" + "EX gpac httpout:hmode=source vout aout\n" + "In this example, the filter will try to play uploaded files through video and audio output.\n" + " \n" + "# HTTPS server\n" + "The server can run over TLS (https) for all the server modes. TLS is enabled by specifying [-cert]() and [-pkey]() options.\n" + "Both certificate and key must be in PEM format.\n" + "The server currently only operates in either HTTPS or HTTP mode and cannot run both modes at the same time. You will need to use two httpout filters for this, one operating in HTTPS and one operating in HTTP.\n" + ) + .private_size = sizeof(GF_HTTPOutCtx), + .max_extra_pids = -1, + .args = HTTPOutArgs, + .probe_url = httpout_probe_url, + .initialize = httpout_initialize, + .finalize = httpout_finalize, + SETCAPS(HTTPOutCaps), + .configure_pid = httpout_configure_pid, + .process = httpout_process, + .process_event = httpout_process_event, + .use_alias = httpout_use_alias +}; + + +const GF_FilterRegister *httpout_register(GF_FilterSession *session) +{ + return &HTTPOutRegister; +} + diff --git a/src/filters/out_pipe.c b/src/filters/out_pipe.c new file mode 100644 index 0000000..63e1855 --- /dev/null +++ b/src/filters/out_pipe.c @@ -0,0 +1,513 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018 + * All rights reserved + * + * This file is part of GPAC / generic pipe output filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include + +#ifdef WIN32 + +#include + +#else + +#include +#include + +#ifdef GPAC_CONFIG_LINUX +#include +#include +#endif + +#ifndef __BEOS__ +#include +#endif + +#endif + + + +typedef struct +{ + //options + Double start, speed; + char *dst, *mime, *ext; + Bool dynext, mkp; + u32 block_size; + + + //only one input pid + GF_FilterPid *pid; + +#ifdef WIN32 + HANDLE pipe; +#else + int fd; +#endif + + GF_FilterCapability in_caps[2]; + char szExt[10]; + char szFileName[GF_MAX_PATH]; + Bool owns_pipe; + +} GF_PipeOutCtx; + +const char *gf_errno_str(int errnoval); + + +static GF_Err pipeout_open_close(GF_PipeOutCtx *ctx, const char *filename, const char *ext, u32 file_idx, Bool explicit_overwrite) +{ + GF_Err e = GF_OK; + char szName[GF_MAX_PATH], szFinalName[GF_MAX_PATH]; + + if (!filename) { +#ifdef WIN32 + if (ctx->pipe != INVALID_HANDLE_VALUE) CloseHandle(ctx->pipe); + ctx->pipe = INVALID_HANDLE_VALUE; +#else + if (ctx->fd>=0) close(ctx->fd); + ctx->fd = -1; +#endif + return GF_OK; + } + + if (!strncmp(filename, "pipe://", 7)) filename+=7; + + if (ctx->dynext) { + Bool has_ext = (strchr(filename, '.')==NULL) ? GF_FALSE : GF_TRUE; + + strcpy(szName, filename); + if (!has_ext && ext) { + strcat(szName, "."); + strcat(szName, ext); + } + } else { + strcpy(szName, filename); + } + gf_filter_pid_resolve_file_template(ctx->pid, szName, szFinalName, file_idx, NULL); + + if (!strcmp(szFinalName, ctx->szFileName) +#ifdef WIN32 + && (ctx->pipe != INVALID_HANDLE_VALUE) +#else + && ctx->fd>=0 +#endif + ) return GF_OK; + +#ifdef WIN32 + if (ctx->pipe != INVALID_HANDLE_VALUE) CloseHandle(ctx->pipe); + ctx->pipe = INVALID_HANDLE_VALUE; + char szNamedPipe[GF_MAX_PATH]; + if (!strncmp(szFinalName, "\\\\", 2)) { + strcpy(szNamedPipe, szFinalName); + } + else { + strcpy(szNamedPipe, "\\\\.\\pipe\\gpac\\"); + strcat(szNamedPipe, szFinalName); + } + if (strchr(szFinalName, '/')) { + u32 i, len = (u32)strlen(szNamedPipe); + for (i = 0; i < len; i++) { + if (szNamedPipe[i] == '/') + szNamedPipe[i] = '\\'; + } + } + + if (WaitNamedPipeA(szNamedPipe, 1) == FALSE) { + if (!ctx->mkp) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Failed to open %s: %d\n", szNamedPipe, GetLastError())); + e = GF_URL_ERROR; + } + else { + ctx->pipe = CreateNamedPipe(szNamedPipe, PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE, + PIPE_TYPE_BYTE | PIPE_WAIT, + 10, + ctx->block_size, + ctx->block_size, + 0, + NULL); + + if (ctx->pipe == INVALID_HANDLE_VALUE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Failed to create named pipe %s: %d\n", szNamedPipe, GetLastError())); + e = GF_IO_ERR; + } + else { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[PipeOut] Waiting for client connection for %s, blocking\n", szNamedPipe)); + if (!ConnectNamedPipe(ctx->pipe, NULL) && (GetLastError() != ERROR_PIPE_CONNECTED)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Failed to connect named pipe %s: %d\n", szNamedPipe, GetLastError())); + e = GF_IO_ERR; + CloseHandle(ctx->pipe); + ctx->pipe = INVALID_HANDLE_VALUE; + } + else { + ctx->owns_pipe = GF_TRUE; + } + } + } + } + else { + ctx->pipe = CreateFile(szNamedPipe, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + if (ctx->pipe == INVALID_HANDLE_VALUE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Failed to open %s: %d\n", szNamedPipe, GetLastError())); + e = GF_URL_ERROR; + } + } + +#else + if (ctx->fd>=0) close(ctx->fd); + ctx->fd = -1; + + if (!gf_file_exists(szFinalName) && ctx->mkp) { +#ifdef GPAC_CONFIG_DARWIN + mknod(szFinalName, S_IFIFO | 0666, 0); +#else + mkfifo(szFinalName, 0666); +#endif + ctx->owns_pipe = GF_TRUE; + } + + ctx->fd = open(szFinalName, O_WRONLY ); + + if (ctx->fd<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] cannot open output pipe %s: %s\n", ctx->szFileName, gf_errno_str(errno))); + e = ctx->owns_pipe ? GF_IO_ERR : GF_URL_ERROR; + } +#endif + if (e) { + return e; + } + strncpy(ctx->szFileName, szFinalName, GF_MAX_PATH-1); + ctx->szFileName[GF_MAX_PATH-1] = 0; + return GF_OK; +} + +static GF_Err pipeout_setup_file(GF_PipeOutCtx *ctx, Bool explicit_overwrite) +{ + const GF_PropertyValue *p; + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_OUTPATH); + if (p && p->value.string) { + return pipeout_open_close(ctx, p->value.string, NULL, 0, explicit_overwrite); + } else if (ctx->dynext) { + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PCK_FILENUM); + if (!p) { + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_FILE_EXT); + if (p && p->value.string) { + return pipeout_open_close(ctx, ctx->dst, p->value.string, 0, explicit_overwrite); + } + } + } else { + return pipeout_open_close(ctx, ctx->dst, NULL, 0, explicit_overwrite); + } + return GF_BAD_PARAM; +} +static GF_Err pipeout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_PipeOutCtx *ctx = (GF_PipeOutCtx *) gf_filter_get_udta(filter); + if (is_remove) { + ctx->pid = NULL; + pipeout_open_close(ctx, NULL, NULL, 0, GF_FALSE); + return GF_OK; + } + gf_filter_pid_check_caps(pid); + + if (!ctx->pid) { + GF_FilterEvent evt; + gf_filter_pid_init_play_event(pid, &evt, ctx->start, ctx->speed, "PipeOut"); + gf_filter_pid_send_event(pid, &evt); + } + ctx->pid = pid; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DISABLE_PROGRESSIVE); + if (p && p->value.uint) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Block patching is not supported by pipe output\n")); + return GF_NOT_SUPPORTED; + } + return GF_OK; +} + +static GF_Err pipeout_initialize(GF_Filter *filter) +{ + char *ext; + GF_PipeOutCtx *ctx = (GF_PipeOutCtx *) gf_filter_get_udta(filter); + + if (!ctx || !ctx->dst) return GF_OK; + + if (strnicmp(ctx->dst, "pipe://", 7) && strstr(ctx->dst, "://")) { + gf_filter_setup_failure(filter, GF_NOT_SUPPORTED); + return GF_NOT_SUPPORTED; + } + if (ctx->dynext) return GF_OK; + + if (ctx->ext) ext = ctx->ext; + else { + ext = gf_file_ext_start(ctx->dst); + if (ext) ext++; + } + +#ifdef WIN32 + ctx->pipe = INVALID_HANDLE_VALUE; +#else + ctx->fd = -1; +#endif + if (!ext && !ctx->mime) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] No extension provided nor mime type for output file %s, cannot infer format\n", ctx->dst)); + return GF_NOT_SUPPORTED; + } + //static cap, streamtype = file + ctx->in_caps[0].code = GF_PROP_PID_STREAM_TYPE; + ctx->in_caps[0].val = PROP_UINT(GF_STREAM_FILE); + ctx->in_caps[0].flags = GF_CAPS_INPUT_STATIC; + + if (ctx->mime) { + ctx->in_caps[1].code = GF_PROP_PID_MIME; + ctx->in_caps[1].val = PROP_NAME( ctx->mime ); + ctx->in_caps[1].flags = GF_CAPS_INPUT; + } else { + strncpy(ctx->szExt, ext, 9); + ctx->szExt[9] = 0; + strlwr(ctx->szExt); + ctx->in_caps[1].code = GF_PROP_PID_FILE_EXT; + ctx->in_caps[1].val = PROP_NAME( ctx->szExt ); + ctx->in_caps[1].flags = GF_CAPS_INPUT; + } + gf_filter_override_caps(filter, ctx->in_caps, 2); + return GF_OK; +} + +static void pipeout_finalize(GF_Filter *filter) +{ + GF_PipeOutCtx *ctx = (GF_PipeOutCtx *) gf_filter_get_udta(filter); + pipeout_open_close(ctx, NULL, NULL, 0, GF_FALSE); + + if (ctx->owns_pipe) + gf_file_delete(ctx->szFileName); +} + +static GF_Err pipeout_process(GF_Filter *filter) +{ + GF_FilterPacket *pck; + const GF_PropertyValue *fname, *p; + Bool start, end; + const char *pck_data; + u32 pck_size; + s32 nb_write; + GF_PipeOutCtx *ctx = (GF_PipeOutCtx *) gf_filter_get_udta(filter); + + pck = gf_filter_pid_get_packet(ctx->pid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->pid)) { + pipeout_open_close(ctx, NULL, NULL, 0, GF_FALSE); + return GF_EOS; + } + return GF_OK; + } + + gf_filter_pck_get_framing(pck, &start, &end); + + if (start) { + const GF_PropertyValue *fext, *fnum; + + Bool explicit_overwrite = GF_FALSE; + const char *name = NULL; + fname = fext = NULL; + //file num increased per packet, open new file + fnum = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM); + if (fnum) { + fname = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_OUTPATH); + fext = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_FILE_EXT); + if (!fname) name = ctx->dst; + } + //filename change at packet start, open new file + if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME); + if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PID_OUTPATH); + if (!fext) fext = gf_filter_pck_get_property(pck, GF_PROP_PID_FILE_EXT); + if (fname) name = fname->value.string; + + if (end && gf_filter_pck_get_seek_flag(pck)) + explicit_overwrite = GF_TRUE; + + if (name) { + pipeout_open_close(ctx, name, fext ? fext->value.string : NULL, fnum ? fnum->value.uint : 0, explicit_overwrite); + } else if ( +#ifdef WIN32 + ctx->pipe==INVALID_HANDLE_VALUE +#else + ctx->fd<0 +#endif + ) { + GF_Err e = pipeout_setup_file(ctx, explicit_overwrite); + if (e) { + gf_filter_setup_failure(filter, e); + return e; + } + } + } + + pck_data = gf_filter_pck_get_data(pck, &pck_size); + if ( +#ifdef WIN32 + ctx->pipe != INVALID_HANDLE_VALUE +#else + ctx->fd>=0 +#endif + ) { + GF_FilterFrameInterface *hwf = gf_filter_pck_get_frame_interface(pck); + if (pck_data) { +#ifdef WIN32 + if (! WriteFile(ctx->pipe, pck_data, pck_size, &nb_write, NULL)) { + nb_write = 0; + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Write error, wrote %d bytes but had %u to write: error %d\n", nb_write, pck_size, GetLastError() )); + } +#else + nb_write = (s32) write(ctx->fd, pck_data, pck_size); + if (nb_write != pck_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Write error, wrote %d bytes but had %u to write: %s\n", nb_write, pck_size, gf_errno_str(errno))); + } +#endif + } else if (hwf) { + u32 w, h, stride, stride_uv, pf; + u32 nb_planes, uv_height; + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_WIDTH); + w = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_HEIGHT); + h = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_PIXFMT); + pf = p ? p->value.uint : 0; + + //get stride/stride_uv with no padding + stride = stride_uv = 0; + if (gf_pixel_get_size_info(pf, w, h, NULL, &stride, &stride_uv, &nb_planes, &uv_height) == GF_TRUE) { + u32 i; + for (i=0; iget_plane(hwf, i, &out_ptr, &out_stride); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Failed to fetch plane data from hardware frame, cannot write\n")); + break; + } + if (i) { + write_h = uv_height; + lsize = stride_uv; + } else { + write_h = h; + lsize = stride; + } + + for (j=0; jpipe, out_ptr, lsize, &nb_write, NULL)) { + nb_write = 0; + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Write error, wrote %d bytes but had %u to write: %d\n", nb_write, pck_size, GetLastError())); + } +#else + nb_write = (s32) write(ctx->fd, out_ptr, lsize); + if (nb_write != lsize) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] Write error, wrote %d bytes but had %u to write: %s\n", nb_write, lsize, gf_errno_str(errno))); + } +#endif + out_ptr += out_stride; + } + } + } + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[PipeOut] No data associated with packet, cannot write\n")); + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[PipeOut] output file handle is not opened, discarding %d bytes\n", pck_size)); + } + gf_filter_pid_drop_packet(ctx->pid); + if (end) { + pipeout_open_close(ctx, NULL, NULL, 0, GF_FALSE); + } + return GF_OK; +} + +static GF_FilterProbeScore pipeout_probe_url(const char *url, const char *mime) +{ + if (!strnicmp(url, "pipe://", 7)) return GF_FPROBE_SUPPORTED; + if (!strnicmp(url, "pipe:", 5)) return GF_FPROBE_SUPPORTED; + return GF_FPROBE_NOT_SUPPORTED; +} + + +#define OFFS(_n) #_n, offsetof(GF_PipeOutCtx, _n) + +static const GF_FilterArgs PipeOutArgs[] = +{ + { OFFS(dst), "location of destination file", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(ext), "indicate file extension of pipe data", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(mime), "indicate mime type of pipe data", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(dynext), "indicate the file extension is set by filter chain, not dst", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(start), "set playback start offset. Negative value means percent of media dur with -1 <=> dur", GF_PROP_DOUBLE, "0.0", NULL, 0}, + { OFFS(speed), "set playback speed. If speed is negative and start is 0, start is set to -1", GF_PROP_DOUBLE, "1.0", NULL, 0}, + { OFFS(mkp), "create pipe if not found - see filter help", GF_PROP_BOOL, "false", NULL, 0 }, + { OFFS(block_size), "buffer size used to write to pipe, windows only", GF_PROP_UINT, "5000", NULL, GF_FS_ARG_HINT_ADVANCED }, + {0} +}; + +static const GF_FilterCapability PipeOutCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT,GF_PROP_PID_FILE_EXT, "*"), + CAP_STRING(GF_CAPS_INPUT,GF_PROP_PID_MIME, "*"), +}; + + +GF_FilterRegister PipeOutRegister = { + .name = "pout", + GF_FS_SET_DESCRIPTION("pipe output") + GF_FS_SET_HELP("This filter handles generic output pipes (mono-directional) in blocking mode only.\n"\ + "Warning: Output pipes do not currently support non blocking mode.\n"\ + "The assoicated protocol scheme is `pipe://` when loaded as a generic output (eg, -o `pipe://URL` where URL is a relative or absolute pipe name).\n"\ + "Data format of the pipe **shall** be specified using extension (either in filename or through [-ext]() option) or MIME type through [-mime]()\n"\ + "The pipe name indicated in [-dst]() can use template mechanisms from gpac, e.g. `dst=pipe_$ServiceID$`\n"\ + "\n"\ + "On Windows hosts, the default pipe prefix is `\\\\.\\pipe\\gpac\\` if no prefix is set \n"\ + "EX dst=mypipe resolves in \\\\.\\pipe\\gpac\\mypipe\n"\ + "EX dst=\\\\.\\pipe\\myapp\\mypipe resolves in \\\\.\\pipe\\myapp\\mypipe\n" + "Any destination name starting with `\\\\` is used as is, with `\\` translated in `/`\n"\ + "\n"\ + "The pipe input can create the pipe if not found using [-mkp](). On windows hosts, this will create a pipe server.\n"\ + "On non windows hosts, the created pipe will delete the pipe file upon filter destruction."\ + "") + .private_size = sizeof(GF_PipeOutCtx), + .args = PipeOutArgs, + SETCAPS(PipeOutCaps), + .probe_url = pipeout_probe_url, + .initialize = pipeout_initialize, + .finalize = pipeout_finalize, + .configure_pid = pipeout_configure_pid, + .process = pipeout_process +}; + + +const GF_FilterRegister *pipeout_register(GF_FilterSession *session) +{ + return &PipeOutRegister; +} + diff --git a/src/filters/out_rtp.c b/src/filters/out_rtp.c new file mode 100644 index 0000000..3d9a54f --- /dev/null +++ b/src/filters/out_rtp.c @@ -0,0 +1,1194 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2019-2020 + * All rights reserved + * + * This file is part of GPAC / rtp output filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +#if !defined(GPAC_DISABLE_ISOM) && !defined(GPAC_DISABLE_STREAMING) + +#include +#include +#include +#include +#include + +#include "out_rtp.h" + + +typedef struct +{ + //options + char *ip; + char *dst, *ext, *mime; + u32 port; + Bool loop, xps; + Bool mpeg4; + u32 mtu; + u32 ttl; + char *ifce; + u32 payt, tt; + s32 delay; + char *info, *url, *email; + s32 runfor, tso; + Bool latm; + + /*timeline origin of our session (all tracks) in microseconds*/ + u64 sys_clock_at_init; + + /*list of streams in session*/ + GF_List *streams; + + /*base stream if this stream contains a media decoding dependency, 0 otherwise*/ + u32 base_pid_id; + + Bool first_RTCP_sent; + + GF_RTPOutStream *active_stream; + u32 active_stream_idx; + u64 active_min_ts_microsec; + + GF_FilterPid *opid; + + Bool wait_for_loop; + u64 microsec_ts_init; + //0: not single stream, 1: input is raw media, 2: input is TS + u32 single_stream; + GF_FilterCapability in_caps[2]; + char szExt[10]; +} GF_RTPOutCtx; + + +GF_Err rtpout_create_sdp(GF_List *streams, Bool is_rtsp, const char *ip, const char *info, const char *sess_name, const char *url, const char *email, u32 base_pid_id, FILE **sdp_tmp, u64 *session_id) +{ + FILE *sdp_out; + u32 i, count; + u64 session_version; + sdp_out = gf_file_temp(NULL); + if (!sdp_out) return GF_IO_ERR; + *sdp_tmp = sdp_out; + + + count = gf_list_count(streams); + + gf_fprintf(sdp_out, "v=0\n"); + if (gf_sys_is_test_mode()) { + *session_id = 0; + session_version = 0; + } else { + if (! *session_id) *session_id = gf_net_get_ntp_ts(); + session_version = gf_net_get_ntp_ts(); + } + gf_fprintf(sdp_out, "o=gpac "LLU" "LLU" IN IP%d %s\n", *session_id, session_version, gf_net_is_ipv6(ip) ? 6 : 4, ip); + gf_fprintf(sdp_out, "s=%s\n", sess_name); + + if (info) { + gf_fprintf(sdp_out, "i=%s\n", info); + } else { + GF_RTPOutStream *stream = gf_list_get(streams, 0); + const char *src = gf_filter_pid_orig_src_args(stream->pid); + if (!src) src = gf_filter_pid_get_source_filter_name(stream->pid); + else { + src = gf_file_basename(src); + } + if (src) + gf_fprintf(sdp_out, "i=%s\n", src); + } + gf_fprintf(sdp_out, "u=%s\n", url ? url : "http://gpac.io"); + if (email) { + gf_fprintf(sdp_out, "e=%s\n", email); + } + if (is_rtsp) { + gf_fprintf(sdp_out, "c=IN IP4 0.0.0.0\n"); + } else { + gf_fprintf(sdp_out, "c=IN IP%d %s\n", gf_net_is_ipv6(ip) ? 6 : 4, ip); + } + gf_fprintf(sdp_out, "t=0 0\n"); + + if (is_rtsp) { + gf_fprintf(sdp_out, "a=control=*\n"); + } + + if (gf_sys_is_test_mode()) { + gf_fprintf(sdp_out, "a=x-copyright: Streamed with GPAC - http://gpac.io\n"); + } else { + gf_fprintf(sdp_out, "a=x-copyright: Streamed with GPAC %s - %s\n", gf_gpac_version(), gf_gpac_copyright() ); + } + + if (is_rtsp) { + Double max_dur=0; + Bool disable_seek = GF_FALSE; + for (i=0; irtp) continue; + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_DURATION); + if (p) { + Double dur = (Double) p->value.lfrac.num; + dur /= p->value.lfrac.den; + if (dur>max_dur) max_dur = dur; + } + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_PLAYBACK_MODE); + if (!p || (p->value.uintdepends_on == base_pid_id) { + gf_fprintf(sdp_out, " L%d", i+1); + } + } + gf_fprintf(sdp_out, "\n"); + } + + for (i=0; irtp) continue; + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_DECODER_CONFIG); + if (p && p->value.data.ptr) { + dsi = p->value.data.ptr; + dsi_len = p->value.data.size; + } + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + if (p && p->value.data.ptr) { + dsi_enh = p->value.data.ptr; + dsi_enh_len = p->value.data.size; + } + + w = h = tw = th = 0; + tx = ty = 0; + tl = 0; + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_WIDTH); + if (p) w = p->value.uint; + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_HEIGHT); + if (p) h = p->value.uint; + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_PROTECTION_KMS_URI); + if (p) KMS = p->value.string; + + if (stream->codecid == GF_CODECID_TX3G) { + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_TRANS_X); + if (p) tx = p->value.sint; + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_TRANS_Y); + if (p) ty = p->value.sint; + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_ZORDER); + if (p) tl = p->value.sint; + tw = w; + th = h; + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_WIDTH_MAX); + if (p) w = p->value.uint; + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_HEIGHT_MAX); + if (p) h = p->value.uint; + } + + gf_rtp_streamer_append_sdp_extended(stream->rtp, stream->id, dsi, dsi_len, dsi_enh, dsi_enh_len, (char *)KMS, w, h, tw, th, tx, ty, tl, is_rtsp, &sdp_media); + + if (sdp_media) { + gf_fprintf(sdp_out, "%s", sdp_media); + gf_free(sdp_media); + } + if (base_pid_id) { + u32 j; + + gf_fprintf(sdp_out, "a=mid:L%d\n", i+1); + gf_fprintf(sdp_out, "a=depend:%d lay", gf_rtp_streamer_get_payload_type(stream->rtp) ); + + for (j=0; jdepends_on == stream->id) { + gf_fprintf(sdp_out, " L%d:%d", j+1, gf_rtp_streamer_get_payload_type(tk->rtp) ); + } + } + gf_fprintf(sdp_out, "\n"); + } + + if (is_rtsp) { + gf_fprintf(sdp_out, "a=control:trackID=%d\n", stream->ctrl_id); + } + } + gf_fprintf(sdp_out, "\n"); + return GF_OK; +} + +GF_Err rtpout_init_streamer(GF_RTPOutStream *stream, const char *ipdest, Bool inject_xps, Bool use_mpeg4_signaling, Bool use_latm, u32 payt, u32 mtu, u32 ttl, const char *ifce, Bool is_rtsp, u32 *base_pid_id, u32 file_mode) +{ + Bool disable_mpeg4 = GF_FALSE; + u32 flags, average_size, max_size, max_tsdelta, codecid, const_dur, nb_ch, samplerate, max_cts_offset, bandwidth, IV_length, KI_length, dsi_len, max_ptime, au_sn_len; + char *dsi; + Bool is_crypted; + const GF_PropertyValue *p; + + *base_pid_id = 0; + + dsi_len = samplerate = nb_ch = IV_length = KI_length = 0; + is_crypted = 0; + dsi = NULL; + flags = 0; + max_ptime = au_sn_len = 0; + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_CODECID); + codecid = p ? p->value.uint : 0; + if (stream->codecid && (stream->codecid != codecid)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTPOut] Dynamic change of codec in RTP session not supported !\n")); + return GF_FILTER_NOT_SUPPORTED; + } + stream->codecid = codecid; + + stream->is_encrypted = GF_FALSE; + if (stream->streamtype == GF_STREAM_ENCRYPTED) { + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_ORIG_STREAM_TYPE); + if (p) stream->streamtype = p->value.uint; + stream->is_encrypted = GF_TRUE; + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_PROTECTION_SCHEME_TYPE); + if (!p || (p->value.uint != GF_ISOM_ISMACRYP_SCHEME)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTPOut] Protected track with scheme type %s, cannot stream (only ISMA over RTP is supported !\n", p ? gf_4cc_to_str(p->value.uint) : "unknwon" )); + return GF_FILTER_NOT_SUPPORTED; + } + } + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_TIMESCALE); + stream->timescale = p ? p->value.uint : 1000; + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_CONFIG_IDX); + stream->sample_desc_index = p ? p->value.uint : 0; + + + u32 cfg_crc=0; + dsi = NULL; + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_DECODER_CONFIG); + if (p) { + dsi = p->value.data.ptr; + dsi_len = p->value.data.size; + cfg_crc = gf_crc_32(dsi, dsi_len); + } + if (stream->rtp && (cfg_crc==stream->cfg_crc)) + return GF_OK; + + if (inject_xps) + stream->inject_ps = GF_TRUE; + else if (stream->cfg_crc) + stream->inject_ps = GF_TRUE; + stream->cfg_crc = cfg_crc; + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_NB_FRAMES); + stream->nb_aus = p ? p->value.uint : 0; + + switch (stream->streamtype) { + case GF_STREAM_OD: + case GF_STREAM_SCENE: + //todo, check if sync shadow is used +// if (gf_isom_has_sync_shadows(ctx->isom, stream->track_num) || gf_isom_has_sample_dependency(ctx->isom, stream->track_num)) +// flags |= GP_RTP_PCK_SYSTEMS_CAROUSEL; + break; + case GF_STREAM_AUDIO: + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_SAMPLE_RATE); + if (p) samplerate = p->value.uint; + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_NUM_CHANNELS); + if (p) nb_ch = p->value.uint; + break; + case GF_STREAM_VISUAL: + break; + case GF_STREAM_FILE: + if (file_mode==2) { + stream->codecid = codecid = GF_CODECID_FAKE_MP2T; + stream->timescale = 90000; + } + break; + default: + break; + } + + gf_filter_pid_set_framing_mode(stream->pid, (stream->streamtype==GF_STREAM_FILE) ? GF_FALSE : GF_TRUE); + + /*get sample info*/ + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_MAX_FRAME_SIZE); + max_size = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_AVG_FRAME_SIZE); + average_size = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_MAX_TS_DELTA); + max_tsdelta = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_MAX_CTS_OFFSET); + max_cts_offset = p ? p->value.uint : (u32) -1; + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_CONSTANT_DURATION); + const_dur = p ? p->value.uint : 0; + + + if (stream->avcc) gf_odf_avc_cfg_del(stream->avcc); + stream->avcc = NULL; + if (stream->hvcc) gf_odf_hevc_cfg_del(stream->hvcc); + stream->hvcc = NULL; + + stream->avc_nalu_size = 0; + switch (codecid) { + case GF_CODECID_AVC: + case GF_CODECID_SVC: + case GF_CODECID_MVC: + if (dsi) { + GF_AVCConfig *avcc = gf_odf_avc_cfg_read(dsi, dsi_len); + if (avcc) { + stream->avc_nalu_size = avcc->nal_unit_size; + if (stream->inject_ps) + stream->avcc = avcc; + else + gf_odf_avc_cfg_del(avcc); + } + } + break; + case GF_CODECID_HEVC: + case GF_CODECID_LHVC: + if (dsi) { + GF_HEVCConfig *hvcc = gf_odf_hevc_cfg_read(dsi, dsi_len, (codecid==GF_CODECID_LHVC) ? GF_TRUE : GF_FALSE ); + if (hvcc) { + stream->avc_nalu_size = hvcc->nal_unit_size; + if (stream->inject_ps) { + u32 i, count = gf_list_count(hvcc->param_array); + GF_HEVCParamArray *vpsa=NULL, *spsa=NULL; + stream->hvcc = hvcc; + for (i=0; iparam_array, i); + if (!vpsa && (pa->type == GF_HEVC_NALU_VID_PARAM)) { + vpsa = pa; + gf_list_rem(hvcc->param_array, i); + count--; + } + else if (!spsa && (pa->type == GF_HEVC_NALU_SEQ_PARAM)) { + spsa = pa; + gf_list_rem(hvcc->param_array, i); + count--; + } + } + //insert SPS at begining + gf_list_insert(hvcc->param_array, spsa, 0); + //insert VPS at begining - we now have VPS, SPS and other (PPS, SEI...) + gf_list_insert(hvcc->param_array, vpsa, 0); + } else + gf_odf_hevc_cfg_del(hvcc); + } + } + break; + case GF_CODECID_AAC_MPEG4: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + //we cannot disable mpeg4 payload type, compute default values !! + if (!const_dur || !average_size || !max_tsdelta || !max_size) { + const_dur = 1024; + const_dur *= stream->timescale; + const_dur /= samplerate; + max_tsdelta = const_dur; + average_size = 500; + max_size = 1000; + GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTPOut] AAC stream detected but not information available on average size/tsdelta/duration, assuming const dur %d max_tsdelta %d average size %d max size %d\n", const_dur, max_tsdelta, average_size, max_size)); + } + if (use_latm) + flags |= GP_RTP_PCK_USE_LATM_AAC; + break; + } + + if (max_cts_offset==(u32)-1) { + if (stream->streamtype==GF_STREAM_VISUAL) { + disable_mpeg4 = GF_TRUE; + } + max_cts_offset = 0; + } + + if (!disable_mpeg4 && use_mpeg4_signaling) + flags = GP_RTP_PCK_SIGNAL_RAP | GP_RTP_PCK_FORCE_MPEG4; + + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_ID); + stream->id = p ? p->value.uint : 0; + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_BITRATE); + bandwidth = p ? p->value.uint : 0; + + if (codecid==GF_CODECID_AAC_MPEG4) + + if (is_crypted) { + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_ISMA_SELECTIVE_ENC); + if (p->value.boolean) { + flags |= GP_RTP_PCK_SELECTIVE_ENCRYPTION; + } + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_ISMA_IV_LENGTH); + IV_length = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_ISMA_KI_LENGTH); + KI_length = p ? p->value.uint : 0; + } + + + /*init packetizer*/ + stream->rtp = gf_rtp_streamer_new(stream->streamtype, codecid, stream->timescale, + (char *) ipdest, stream->port, mtu, ttl, ifce, + flags, dsi, dsi_len, + payt, samplerate, nb_ch, + is_crypted, IV_length, KI_length, + average_size, max_size, max_tsdelta, max_cts_offset, const_dur, bandwidth, max_ptime, au_sn_len, is_rtsp); + + if (!stream->rtp) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTPOut] Could not initialize RTP for stream %s: not supported\n", gf_filter_pid_get_name(stream->pid) )); + return GF_NOT_SUPPORTED; + } + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_DELAY); + stream->ts_delay = p ? p->value.sint : 0; + + payt++; + stream->microsec_ts_scale_frac.num = 1000000; + stream->microsec_ts_scale_frac.den = stream->timescale; + + while (! (stream->microsec_ts_scale_frac.num % 10) && ! (stream->microsec_ts_scale_frac.den % 10)) { + stream->microsec_ts_scale_frac.num /= 10; + stream->microsec_ts_scale_frac.den /= 10; + } + + p = gf_filter_pid_get_property(stream->pid, GF_PROP_PID_DEPENDENCY_ID); + if (p) { + *base_pid_id = p->value.uint; + gf_rtp_streamer_disable_auto_rtcp(stream->rtp); + } + return GF_OK; +} + +static GF_Err rtpout_setup_sdp(GF_RTPOutCtx *ctx) +{ + FILE *sdp_out; + u32 fsize; + GF_Err e; + u64 sess_id=0; + u8 *output; + const char *ip = ctx->ip; + if (!ip) ip = "127.0.0.1"; + + if (ctx->single_stream) return GF_OK; + + e = rtpout_create_sdp(ctx->streams, GF_FALSE, ip, ctx->info, "livesession", ctx->url, ctx->email, ctx->base_pid_id, &sdp_out, &sess_id); + if (e) return e; + + fsize = (u32) gf_ftell(sdp_out); + GF_FilterPacket *pck = gf_filter_pck_new_alloc(ctx->opid, fsize, &output); + if (pck) { + gf_fseek(sdp_out, 0, SEEK_SET); + u32 read = (u32) gf_fread(output, fsize, sdp_out); + if (read != fsize) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTPOut] Failed to read SDP from temp file, got %d bytes but expecting %d\n", read, fsize)); + gf_filter_pck_discard(pck); + } else { + char c = output[fsize-1]; + output[fsize-1] = 0; + GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTPOut] SDP file generated: %s\n", output)); + output[fsize-1] = c; + gf_filter_pck_set_framing(pck, GF_TRUE, GF_TRUE); + gf_filter_pck_send(pck); + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTPOut] Failed to send SDP file packet\n")); + } + + gf_fclose(sdp_out); + return GF_OK; +} + +static u16 rtpout_check_next_port(GF_RTPOutCtx *ctx, u16 first_port) +{ + u32 i, count = gf_list_count(ctx->streams); + for (i=0;istreams, i); + if (stream->port==first_port) { + return rtpout_check_next_port(ctx, (u16) (first_port+2) ); + } + } + return first_port; +} + +static void rtpout_del_stream(GF_RTPOutStream *st) +{ + if (st->rtp) gf_rtp_streamer_del(st->rtp); + if (st->pck) gf_filter_pid_drop_packet(st->pid); + if (st->avcc) + gf_odf_avc_cfg_del(st->avcc); + if (st->hvcc) + gf_odf_hevc_cfg_del(st->hvcc); + gf_free(st); +} + +static GF_Err rtpout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_RTPOutCtx *ctx = (GF_RTPOutCtx *) gf_filter_get_udta(filter); + GF_Err e = GF_OK; + GF_RTPOutStream *stream; + u16 first_port; + u32 streamType, payt; + const GF_PropertyValue *p; + + first_port = ctx->port; + + if (is_remove) { + GF_RTPOutStream *t =gf_filter_pid_get_udta(pid); + if (t) { + if (ctx->active_stream==t) ctx->active_stream = NULL; + gf_list_del_item(ctx->streams, t); + rtpout_del_stream(t); + } + if (!gf_list_count(ctx->streams)) { + if (ctx->opid) gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + stream = gf_filter_pid_get_udta(pid); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + streamType = p ? p->value.uint : 0; + + switch (streamType) { + case GF_STREAM_VISUAL: + case GF_STREAM_AUDIO: + break; + case GF_STREAM_FILE: + case GF_STREAM_UNKNOWN: + if (stream) { + if (ctx->active_stream==stream) ctx->active_stream = NULL; + gf_list_del_item(ctx->streams, stream); + rtpout_del_stream(stream); + } + if (!ctx->dst) + return GF_FILTER_NOT_SUPPORTED; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_MIME); + if (p && p->value.string && !strcmp(p->value.string, "video/mpeg-2")) { + ctx->single_stream = 2; + } else { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FILE_EXT); + if (p && p->value.string && !strcmp(p->value.string, "ts")) { + ctx->single_stream = 2; + } else { + return GF_FILTER_NOT_SUPPORTED; + } + } + + break; + default: + break; + } + if (!stream) { + GF_SAFEALLOC(stream, GF_RTPOutStream); + if (!stream) return GF_OUT_OF_MEM; + gf_list_add(ctx->streams, stream); + stream->pid = pid; + stream->streamtype = streamType; + stream->min_dts = GF_FILTER_NO_TS; + gf_filter_pid_set_udta(pid, stream); + if (ctx->single_stream) { + GF_FilterEvent evt; + gf_filter_pid_init_play_event(pid, &evt, 0, 1.0, "RTPOut"); + gf_filter_pid_send_event(pid, &evt); + } + } + if (!ctx->single_stream) { + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + gf_filter_pid_copy_properties(ctx->opid, pid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, NULL ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("sdp") ); + gf_filter_pid_set_name(ctx->opid, "SDP"); + } else { + char *dst = ctx->dst+6; + char *sep = strchr(dst, ':'); + if (sep) { + first_port = ctx->port = atoi(sep+1); + sep[0] = 0; + if (ctx->ip) gf_free(ctx->ip); + ctx->ip = gf_strdup(dst); + sep[0] = ':'; + } + } + + stream->port = rtpout_check_next_port(ctx, first_port); + + payt = ctx->payt + gf_list_find(ctx->streams, stream); + //init rtp + e = rtpout_init_streamer(stream, ctx->ip ? ctx->ip : "127.0.0.1", ctx->xps, ctx->mpeg4, ctx->latm, payt, ctx->mtu, ctx->ttl, ctx->ifce, GF_FALSE, &ctx->base_pid_id, ctx->single_stream); + if (e) return e; + + stream->selected = GF_TRUE; + + if (ctx->loop) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PLAYBACK_MODE); + if (!p || (p->value.uintloop = GF_FALSE; + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTPOut] PID %s cannot be seek, disabling loop\n", gf_filter_pid_get_name(pid) )); + } + } + return GF_OK; +} + +static GF_Err rtpout_initialize(GF_Filter *filter) +{ + GF_RTPOutCtx *ctx = (GF_RTPOutCtx *) gf_filter_get_udta(filter); + if (!ctx->payt) ctx->payt = 96; + if (!ctx->port) ctx->port = 7000; + if (!ctx->mtu) ctx->mtu = 1450; + if (ctx->payt<96) ctx->payt = 96; + if (ctx->payt>127) ctx->payt = 127; + ctx->streams = gf_list_new(); + + if (ctx->dst && (ctx->ext || ctx->mime) ) { + //static cap, streamtype = file + ctx->in_caps[0].code = GF_PROP_PID_STREAM_TYPE; + ctx->in_caps[0].val = PROP_UINT(GF_STREAM_FILE); + ctx->in_caps[0].flags = GF_CAPS_INPUT_STATIC; + + if (ctx->mime) { + ctx->in_caps[1].code = GF_PROP_PID_MIME; + ctx->in_caps[1].val = PROP_NAME( ctx->mime ); + ctx->in_caps[1].flags = GF_CAPS_INPUT; + } else { + strncpy(ctx->szExt, ctx->ext, 9); + ctx->szExt[9] = 0; + strlwr(ctx->szExt); + ctx->in_caps[1].code = GF_PROP_PID_FILE_EXT; + ctx->in_caps[1].val = PROP_NAME( ctx->szExt ); + ctx->in_caps[1].flags = GF_CAPS_INPUT; + } + gf_filter_override_caps(filter, ctx->in_caps, 2); + gf_filter_set_max_extra_input_pids(filter, 0); + ctx->single_stream = GF_TRUE; + } + return GF_OK; +} + +static void rtpout_finalize(GF_Filter *filter) +{ + GF_RTPOutCtx *ctx = (GF_RTPOutCtx *) gf_filter_get_udta(filter); + + while (gf_list_count(ctx->streams)) { + GF_RTPOutStream *tmp = gf_list_pop_back(ctx->streams); + rtpout_del_stream(tmp); + } + gf_list_del(ctx->streams); + +} + +static GF_Err rtpout_send_xps(GF_RTPOutStream *stream, GF_List *pslist, Bool *au_start, u32 pck_size, u32 cts, u32 dts, u32 duration) +{ + GF_Err e; + u32 i, count = gf_list_count(pslist); + for (i=0; irtp, (char *) sl->data, sl->size, pck_size, cts, dts, stream->current_sap ? 1 : 0, *au_start, GF_FALSE, stream->pck_num, duration, stream->sample_desc_index); + if (e) return e; + *au_start = GF_FALSE; + } + return GF_OK; +} + +static Bool rtpout_init_clock(GF_RTPOutCtx *ctx) +{ + GF_Err e; + u64 min_dts = GF_FILTER_NO_TS; + u32 i, count = gf_list_count(ctx->streams); + + for (i=0; istreams, i); + GF_FilterPacket *pck = gf_filter_pid_get_packet(stream->pid); + if (!pck) return GF_FALSE; + + dts = gf_filter_pck_get_dts(pck); + if (dts==GF_FILTER_NO_TS) + dts = gf_filter_pck_get_cts(pck); + + if (dts==GF_FILTER_NO_TS) dts=0; + + dts *= 1000000; + dts /= stream->timescale; + if (min_dts > dts) + min_dts = dts; + + if (ctx->tso>0) { + u64 offset = ctx->tso; + offset *= stream->timescale; + offset /= 1000000; + stream->rtp_ts_offset = (u32) offset; + } + } + ctx->sys_clock_at_init = gf_sys_clock_high_res(); + ctx->microsec_ts_init = min_dts; + GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTPOut] RTP clock initialized - time origin set to "LLU" us (sys clock) / "LLU" us (media clock)\n", ctx->sys_clock_at_init, ctx->microsec_ts_init)); + if (ctx->tso<0) { + gf_rand_init(GF_FALSE); + for (i=0; istreams, i); + stream->rtp_ts_offset = gf_rand(); + GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTPOut] RTP stream %d initial RTP TS set to %d\n", i+1, stream->rtp_ts_offset)); + } + } + + e = rtpout_setup_sdp(ctx); + if (e) return e; + + if (ctx->runfor==0) { + for (i=0; istreams, i); + gf_filter_pid_set_discard(stream->pid, GF_TRUE); + } + } + + return GF_TRUE; +} + +GF_Err rtpout_process_rtp(GF_List *streams, GF_RTPOutStream **active_stream, Bool loop, s32 delay, u32 *active_stream_idx, u64 sys_clock_at_init, u64 *active_min_ts_microsec, u64 microsec_ts_init, Bool *wait_for_loop, u32 *repost_delay_us, Bool *first_RTCP_sent, u32 base_pid_id) +{ + GF_Err e = GF_OK; + GF_RTPOutStream *stream; + u32 duration, i, count; + s64 diff; + u64 clock; + const char *pck_data; + u32 pck_size; + u32 dts, cts; + + /*browse all inputs and locate most mature stream*/ + if (! *active_stream) { + u32 nb_eos = 0; + count = gf_list_count(streams); + + *active_min_ts_microsec = (u64) -1; + for (i=0; irtp) continue; + + /*load next AU*/ + if (!stream->pck) { + u64 ts; + stream->pck = gf_filter_pid_get_packet(stream->pid); + + if (!stream->pck) { + if (gf_filter_pid_is_eos(stream->pid)) { + //flush stream + if (!stream->bye_sent) { + stream->bye_sent = GF_TRUE; + gf_rtp_streamer_send_au(stream->rtp, NULL, 0, 0, 0, GF_FALSE); + gf_rtp_streamer_send_bye(stream->rtp); + } + nb_eos++; + } + continue; + } + stream->current_dts = gf_filter_pck_get_dts(stream->pck); + //if CTS is not set, use prev packet CTS + ts = gf_filter_pck_get_cts(stream->pck); + if (ts==GF_FILTER_NO_TS) ts = stream->current_cts; + stream->current_cts = ts; + + stream->current_sap = gf_filter_pck_get_sap(stream->pck); + duration = gf_filter_pck_get_duration(stream->pck); + if (duration) stream->current_duration = duration; + if (stream->current_dts==GF_FILTER_NO_TS) + stream->current_dts = stream->current_cts; + + if (stream->min_dts==GF_FILTER_NO_TS) { + stream->min_dts = stream->current_dts; + } + + stream->microsec_dts = (u64) (stream->microsec_ts_scale_frac.num * (s64) stream->current_dts); + stream->microsec_dts /= stream->microsec_ts_scale_frac.den; + stream->microsec_dts += stream->microsec_ts_offset + sys_clock_at_init; + + if (stream->microsec_dts < microsec_ts_init) { + stream->microsec_dts = 0; + GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTPOut] next RTP packet (stream %d) has a timestamp "LLU" less than initial timestamp "LLU" - forcing to 0\n", i+1, stream->microsec_dts, microsec_ts_init)); + } else { + stream->microsec_dts -= microsec_ts_init; + } + + if (stream->current_sap>GF_FILTER_SAP_3) stream->current_sap = 0; + stream->pck_num++; + *wait_for_loop = GF_FALSE; + } + + /*check timing*/ + if (stream->pck) { + if (*active_min_ts_microsec > stream->microsec_dts) { + *active_min_ts_microsec = stream->microsec_dts; + *active_stream = stream; + *active_stream_idx = i+1; + } + } + } + + /*no input data ...*/ + if (! *active_stream) { + if (nb_eos==count) { + u64 max_dur = 0; + if (!loop) + return GF_EOS; + + for (i=0; icurrent_dts + stream->current_duration - stream->min_dts; + + dur *= 1000000; + dur /= stream->timescale; + + if (max_dur < dur) { + max_dur = dur; + } + } + if (*wait_for_loop) + return GF_OK; + + GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTPOut] RTP session done, looping source\n")); + *wait_for_loop = GF_TRUE; + for (i=0; ipid, GF_PROP_NO_TS_LOOP); + if (!p || !p->value.boolean) { + u64 new_ts; + new_ts = max_dur; + new_ts *= stream->timescale; + new_ts /= 1000000; + stream->ts_offset += new_ts; + stream->microsec_ts_offset = (u64) (stream->ts_offset*(1000000.0/stream->timescale) + sys_clock_at_init); + } + + //loop pid: stop and play + GF_FEVT_INIT(evt, GF_FEVT_STOP, stream->pid); + gf_filter_pid_send_event(stream->pid, &evt); + + GF_FEVT_INIT(evt, GF_FEVT_PLAY, stream->pid); + gf_filter_pid_send_event(stream->pid, &evt); + } + } + return GF_OK; + } + } + + stream = *active_stream; + clock = gf_sys_clock_high_res(); + diff = (s64) *active_min_ts_microsec; + diff += ((s64) delay) * 1000; + diff -= (s64) clock; + + if (diff > 1000) { + u64 repost_in; + //if more than 11 secs ahead of time, ask for delay minus one second, otherwise ask for half the delay + if (diff<=11000) repost_in = diff/3; + else repost_in = diff - 10000; + *repost_delay_us = (u32) repost_in; + GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTPOut] next RTP packet (stream %d DTS "LLU") scheduled in "LLU" us, requesting filter reschedule in "LLU" us - clock "LLU" us\n", *active_stream_idx, stream->current_dts, diff, repost_in, clock)); + return GF_OK; + } else if (diff<=-1000) { + GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTPOut] RTP session stream %d - sending packet %d (DTS "LLU") too late by %d us - clock "LLU" us\n", *active_stream_idx, stream->pck_num, stream->current_dts, -diff, clock)); + } else if (diff>0){ + GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTPOut] RTP session stream %d - sending packet %d (DTS "LLU") ahead of %d us - clock "LLU" us\n", *active_stream_idx, stream->pck_num, stream->current_dts, diff, clock)); + } + + /*send packets*/ + pck_data = gf_filter_pck_get_data(stream->pck, &pck_size); + if (!pck_size) { + gf_filter_pid_drop_packet(stream->pid); + stream->pck = NULL; + *active_stream = NULL; + return GF_OK; + } + + dts = (u32) (stream->current_dts + stream->ts_offset); + cts = (u32) (stream->current_cts + stream->ts_offset); + duration = stream->current_duration; + + dts += stream->rtp_ts_offset; + cts += stream->rtp_ts_offset; + if (stream->ts_delay>=0) { + dts += stream->ts_delay; + cts += stream->ts_delay; + } else { + if ((s32) dts >= -stream->ts_delay) + dts += stream->ts_delay; + else + dts = 0; + + if ((s32) cts >= -stream->ts_delay ) + cts += stream->ts_delay; + else + cts = 0; + } + +#ifndef GPAC_DISABLE_LOG + if (gf_log_tool_level_on(GF_LOG_RTP, GF_LOG_DEBUG)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTPOut] Sending RTP packets for stream %d pck %d/%d DTS "LLU" - CTS "LLU" - RTP TS "LLU" - size %d - SAP %d - clock "LLU" us\n", *active_stream_idx, stream->pck_num, stream->nb_aus, stream->current_dts, stream->current_dts, cts, pck_size, stream->current_sap, clock) ); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTPOut] Runtime %08u ms send stream %d pck %08u/%08u DTS %08u CTS %08u RTP-TS %08u size %08u SAP %d\r", (u32) (clock - sys_clock_at_init)/1000, *active_stream_idx, stream->pck_num, stream->nb_aus, (u32) stream->current_dts, (u32) stream->current_dts, (u32) cts, pck_size, stream->current_sap) ); + } +#endif + + /*we are about to send scalable base: trigger RTCP reports with the same NTP. This avoids + NTP drift due to system clock precision which could break sync decoding*/ + if (! *first_RTCP_sent || (base_pid_id && (base_pid_id== stream->id) )) { + u32 ntp_sec, ntp_frac; + /*force sending RTCP SR every RAP ? - not really compliant but we cannot perform scalable tuning otherwise*/ + u32 ntp_type = stream->current_sap ? 2 : 1; + gf_net_get_ntp(&ntp_sec, &ntp_frac); + count = gf_list_count(streams); + + for (i=0; ipck) break; + + u32 ts = (u32) (astream->current_cts + astream->ts_offset + astream->rtp_ts_offset); + gf_rtp_streamer_send_rtcp(stream->rtp, GF_TRUE, ts, ntp_type, ntp_sec, ntp_frac); + } + *first_RTCP_sent = GF_TRUE; + } + + /*unpack nal units*/ + if (stream->avc_nalu_size) { + Bool au_start, au_end; + u32 v, size; + u32 remain = pck_size; + const char *ptr = pck_data; + + au_start = 1; + e = GF_OK; + + if (stream->avcc && stream->current_sap) { + e = rtpout_send_xps(stream, stream->avcc->sequenceParameterSets, &au_start, pck_size, cts, dts, duration); + if (!e) + e = rtpout_send_xps(stream, stream->avcc->sequenceParameterSetExtensions, &au_start, pck_size, cts, dts, duration); + + if (!e) + e = rtpout_send_xps(stream, stream->avcc->pictureParameterSets, &au_start, pck_size, cts, dts, duration); + } + else if (stream->hvcc && stream->current_sap) { + u32 nbps = gf_list_count(stream->hvcc->param_array); + for (i=0; ihvcc->param_array, i); + + if (!e) + e = rtpout_send_xps(stream, pa->nalus, &au_start, pck_size, cts, dts, duration); + } + } + + while (!e && remain) { + size = 0; + v = (*active_stream)->avc_nalu_size; + while (v) { + size |= (u8) *ptr; + ptr++; + remain--; + v-=1; + if (v) size<<=8; + } + if (remain < size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTPOut] Broken AVC nalu encapsulation: NALU size is %d but only %d bytes left in sample %d\n", size, remain, (*active_stream)->pck_num)); + break; + } + remain -= size; + au_end = remain ? 0 : 1; + + e = gf_rtp_streamer_send_data(stream->rtp, (char *) ptr, size, pck_size, cts, dts, stream->current_sap ? 1 : 0, au_start, au_end, stream->pck_num, duration, stream->sample_desc_index); + ptr += size; + au_start = 0; + } + } else { + e = gf_rtp_streamer_send_data(stream->rtp, (char *) pck_data, pck_size, pck_size, cts, dts, stream->current_sap ? 1 : 0, 1, 1, stream->pck_num, duration, stream->sample_desc_index); + } + gf_filter_pid_drop_packet(stream->pid); + stream->pck = NULL; + + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTPOut] Error sending RTP packet %d: %s\n", stream->pck_num, gf_error_to_string(e) )); + } + + *active_stream = NULL; + return e; + +} + +static GF_Err rtpout_process(GF_Filter *filter) +{ + GF_Err e = GF_OK; + u32 repost_delay_us=0; + GF_RTPOutCtx *ctx = gf_filter_get_udta(filter); + + /*init session timeline - all sessions are sync'ed for packet scheduling purposes*/ + if (!ctx->sys_clock_at_init) { + if (!rtpout_init_clock(ctx)) return GF_OK; + } + + if (ctx->runfor>0) { + s64 diff = gf_sys_clock_high_res(); + diff -= ctx->sys_clock_at_init; + diff /= 1000; + if ((s32) diff > ctx->runfor) { + u32 i, count = gf_list_count(ctx->streams); + for (i=0; istreams, i); + gf_filter_pid_set_discard(stream->pid, GF_TRUE); + stream->pck = NULL; + } + if (ctx->opid) gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + } + + e = rtpout_process_rtp(ctx->streams, &ctx->active_stream, ctx->loop, ctx->delay, &ctx->active_stream_idx, ctx->sys_clock_at_init, &ctx->active_min_ts_microsec, ctx->microsec_ts_init, &ctx->wait_for_loop, &repost_delay_us, &ctx->first_RTCP_sent, ctx->base_pid_id); + if (e) return e; + + if (repost_delay_us) + gf_filter_ask_rt_reschedule(filter, repost_delay_us); + + return GF_OK; +} + +static GF_FilterProbeScore rtpout_probe_url(const char *url, const char *mime) +{ + if (!strnicmp(url, "rtp://", 6)) return GF_FPROBE_SUPPORTED; + return GF_FPROBE_NOT_SUPPORTED; +} + +static const GF_FilterCapability RTPOutCaps[] = +{ + //anything else (not file and framed) result in manifest PID + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "sdp"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "application/sdp"), + {0}, + //anything else (not file and framed) result in media pids not file + CAP_UINT(GF_CAPS_INPUT_EXCLUDED | GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED | GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "ts|m2t|mts|dmb|trp"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "video/mpeg-2|video/mp2t|video/mpeg"), +}; + + +#define OFFS(_n) #_n, offsetof(GF_RTPOutCtx, _n) +static const GF_FilterArgs RTPOutArgs[] = +{ + { OFFS(ip), "destination IP address (NULL is 127.0.0.1)", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(port), "port for first stream in session", GF_PROP_UINT, "7000", NULL, 0}, + { OFFS(loop), "loop all streams in session (not always possible depending on source type)", GF_PROP_BOOL, "true", NULL, 0}, + { OFFS(mpeg4), "send all streams using MPEG-4 generic payload format if posible", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(mtu), "size of RTP MTU in bytes", GF_PROP_UINT, "1460", NULL, 0}, + { OFFS(ttl), "time-to-live for muticast packets", GF_PROP_UINT, "2", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ifce), "default network inteface to use", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(payt), "payload type to use for dynamic configs", GF_PROP_UINT, "96", "96-127", GF_FS_ARG_HINT_EXPERT}, + { OFFS(delay), "send delay for packet (negative means send earlier)", GF_PROP_SINT, "0", NULL, 0}, + { OFFS(tt), "time tolerance in microseconds. Whenever schedule time minus realtime is below this value, the packet is sent right away", GF_PROP_UINT, "1000", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(runfor), "run for the given time in ms. Negative value means run for ever (if loop) or source duration, 0 only outputs the sdp", GF_PROP_SINT, "-1", NULL, 0}, + { OFFS(tso), "set timestamp offset in microsecs. Negative value means random initial timestamp", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(xps), "force parameter set injection at each SAP. If not set, only inject if different from SDP ones", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(latm), "use latm for AAC payload format", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(dst), "URL for direct RTP mode - see filter help", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(ext), "file extension for direct RTP mode - see filter help", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(mime), "set mime type for direct RTP mode - see filter help", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + + +GF_FilterRegister RTPOutRegister = { + .name = "rtpout", + GF_FS_SET_DESCRIPTION("RTP Streamer") + GF_FS_SET_HELP("The RTP streamer handles SDP/RTP output streaming.\n" + "# SDP mode\n" + "When the destination url is an SDP, the filter outputs an SDP on a file pid and streams RTP packets over UDP, starting from the indicated [-port]().\n" + "# Direct RTP mode\n" + "When the destination url uses the protocol scheme `rtp://IP:PORT`, the filter does not output any SDP and streams a single input over RTP, using PORT indicated in the destination URL, or the first [-port]() configured.\n" + "In this mode, it is usually needed to specify the desired format using [-ext]() or [-mime]().\n" + "EX gpac -i src -o rtp://localhost:1234/:ext=ts\n" + "This will indicate that the RTP streamer expects a MPEG-2 TS mux as an input.\n" + "# RTP Packets\n" + "The RTP packets produced have a maximum payload set by the [-mtu]() option (IP packet will be MTU + 40 bytes of IP+UDP+RTP headers).\n" + "The real-time scheduling algorithm works as follows:\n" + "- first initialize the clock by:\n" + " - computing the smallest timestamp for all input pids\n" + " - mapping this media time to the system clock\n" + "- determine the earliest packet to send next on each input pid, adding [-delay]() if any\n" + "- finally compare the packet mapped timestamp __TS__ to the system clock __SC__. When __TS__ - __SC__ is less than [-tt](), the RTP packets for the source packet are sent\n" + ) + .private_size = sizeof(GF_RTPOutCtx), + .max_extra_pids = -1, + .args = RTPOutArgs, + //dynamic redirect since RTP may be dynamically loaded when solving .sdp destinations + .flags = GF_FS_REG_DYNAMIC_REDIRECT, + .initialize = rtpout_initialize, + .finalize = rtpout_finalize, + SETCAPS(RTPOutCaps), + .configure_pid = rtpout_configure_pid, + .probe_url = rtpout_probe_url, + .process = rtpout_process +}; + + +const GF_FilterRegister *rtpout_register(GF_FilterSession *session) +{ + return &RTPOutRegister; +} + +#else + +const GF_FilterRegister *rtpout_register(GF_FilterSession *session) +{ + return NULL; +} + +#endif /* !defined(GPAC_DISABLE_ISOM) && !defined(GPAC_DISABLE_STREAMING) */ diff --git a/src/filters/out_rtp.h b/src/filters/out_rtp.h new file mode 100644 index 0000000..903712c --- /dev/null +++ b/src/filters/out_rtp.h @@ -0,0 +1,107 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / RTP/RTSP input filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _OUT_RTP_H_ +#define _OUT_RTP_H_ + +/*module interface*/ +#include +#include +#ifndef GPAC_DISABLE_STREAMING + +#include +#include +/*IETF lib*/ +#include + + + +typedef struct +{ + GF_RTPStreamer *rtp; + u16 port; + + /*scale from TimeStamps in media timescales to TimeStamps in microseconds*/ + GF_Fraction64 microsec_ts_scale_frac; + + u32 id, codecid; + Bool is_encrypted; + + /*NALU size for H264/AVC parsing*/ + u32 avc_nalu_size; + + GF_FilterPid *pid; + u32 streamtype; + u32 timescale; + u32 nb_aus; + Bool is_playing; + + u32 depends_on; + u32 cfg_crc; + + + /*loaded AU info*/ + GF_FilterPacket *pck; + u64 current_dts, current_cts, min_dts; + u32 current_sap, current_duration; + + u32 pck_num; + u32 sample_duration; + u32 sample_desc_index; + + Bool inject_ps; + + /*normalized DTS in micro-sec*/ + u64 microsec_dts; + + /*offset of CTS/DTS in media timescale, used when looping the pid*/ + u64 ts_offset; + /*offset of CTS/DTS in microseconds, used when looping the pid*/ + u64 microsec_ts_offset; + + GF_AVCConfig *avcc; + GF_HEVCConfig *hvcc; + u32 rtp_ts_offset; + + s32 ts_delay; + Bool bye_sent; + + /*RTSP state*/ + Bool selected, send_rtpinfo; + u32 ctrl_id; + u32 rtp_id, rtcp_id; + u32 mcast_port; +} GF_RTPOutStream; + +GF_Err rtpout_create_sdp(GF_List *streams, Bool is_rtsp, const char *ip, const char *info, const char *sess_name, const char *url, const char *email, u32 base_pid_id, FILE **sdp_tmp, u64 *session_id); + +GF_Err rtpout_init_streamer(GF_RTPOutStream *stream, const char *ipdest, Bool inject_xps, Bool use_mpeg4_signaling, Bool use_latm, u32 payt, u32 mtu, u32 ttl, const char *ifce, Bool is_rtsp, u32 *base_pid_id, u32 file_mode); + +GF_Err rtpout_process_rtp(GF_List *streams, GF_RTPOutStream **active_stream, Bool loop, s32 delay, u32 *active_stream_idx, u64 sys_clock_at_init, u64 *active_min_ts_microsec, u64 microsec_ts_init, Bool *wait_for_loop, u32 *repost_delay_us, Bool *first_RTCP_sent, u32 base_pid_id); + + +#endif /*GPAC_DISABLE_STREAMING*/ + +#endif //_IN_RTP_H_ diff --git a/src/filters/out_rtsp.c b/src/filters/out_rtsp.c new file mode 100644 index 0000000..435dbd7 --- /dev/null +++ b/src/filters/out_rtsp.c @@ -0,0 +1,1348 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2019-2020 + * All rights reserved + * + * This file is part of GPAC / rtsp output filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +#if !defined(GPAC_DISABLE_STREAMING) + +#include +#include +#include +#include +#include + +//common functions for rtpout and rtspout +#include "out_rtp.h" + +enum +{ + SDP_NONE = 0, + SDP_WAIT, + SDP_LOADED +}; + +enum +{ + MCAST_OFF = 0, + MCAST_ON, + MCAST_MIRROR +}; + + +typedef struct +{ + //options + char *dst, *user_agent; + GF_List *mounts; + u32 port, firstport; + Bool xps; + u32 mtu; + u32 ttl; + char *ifce; + u32 payt, tt; + s32 delay; + char *info, *url, *email; + s32 runfor, tso; + u32 maxc; + u32 block_size; + Bool close, loop, dynurl, mpeg4; + u32 mcast; + Bool latm; + + GF_Socket *server_sock; + GF_List *sessions; + + u32 next_wake_us; + char *ip; + Bool done; +} GF_RTSPOutCtx; + + +typedef struct __rtspout_session +{ + GF_RTSPOutCtx *ctx; + + struct __rtspout_session *mcast_mirror; + + GF_RTSPSession *rtsp; + GF_RTSPCommand *command; + GF_RTSPResponse *response; + /*list of streams in session*/ + GF_List *streams; + Bool loop_disabled, loop; + + char *service_name; + char *sessionID; + char peer_address[GF_MAX_IP_NAME_LEN]; + + u32 play_state; + Double start_range; + u32 last_cseq; + Bool interleave; + + /*base stream if this stream contains a media decoding dependency, 0 otherwise*/ + u32 base_pid_id; + + Bool first_RTCP_sent; + + GF_RTPOutStream *active_stream; + u32 active_stream_idx; + u64 active_min_ts_microsec; + + /*timeline origin of our session (all tracks) in microseconds*/ + u64 sys_clock_at_init; + + Bool wait_for_loop; + u64 microsec_ts_init; + + Bool single_session; + char *server_path; + GF_List *filter_srcs; + + u32 sdp_state; + + u32 next_stream_id; + + u64 pause_sys_clock; + Bool request_pending; + char *multicast_ip; + u64 sdp_id; +} GF_RTSPOutSession; + + +static void rtspout_send_response(GF_RTSPOutCtx *ctx, GF_RTSPOutSession *sess) +{ + sess->response->User_Agent = ctx->user_agent; + sess->response->Session = sess->sessionID; + if (ctx->close && !sess->interleave) + sess->response->Connection = "close"; + gf_rtsp_send_response(sess->rtsp, sess->response); + sess->response->User_Agent = NULL; + sess->response->Session = NULL; + if (ctx->close && !sess->interleave) { + sess->response->Connection = NULL; + gf_rtsp_session_del(sess->rtsp); + sess->rtsp = NULL; + } +} + +static void rtspout_check_last_sess(GF_RTSPOutCtx *ctx) +{ + if (gf_list_count(ctx->sessions) ) return; + + if (ctx->dst) + ctx->done = GF_TRUE; + else if (ctx->runfor>0) + ctx->done = GF_TRUE; +} + +static GF_Err rtspout_send_sdp(GF_RTSPOutSession *sess) +{ + FILE *sdp_out; + u32 fsize; + GF_Err e; + const char *ip = sess->ctx->ip; + if (!ip) ip = sess->ctx->ifce; + if (!ip) ip = "127.0.0.1"; + + if (sess->mcast_mirror) { + ip = sess->mcast_mirror->multicast_ip; + e = rtpout_create_sdp(sess->mcast_mirror->streams, GF_FALSE, ip, sess->ctx->info, "livesession", sess->ctx->url, sess->ctx->email, sess->mcast_mirror->base_pid_id, &sdp_out, &sess->sdp_id); + } else { + e = rtpout_create_sdp(sess->streams, GF_TRUE, ip, sess->ctx->info, "livesession", sess->ctx->url, sess->ctx->email, sess->base_pid_id, &sdp_out, &sess->sdp_id); + } + if (e) return e; + + fsize = (u32) gf_ftell(sdp_out); + char *sdp_output = gf_malloc(sizeof(char)*(fsize+1)); + gf_fseek(sdp_out, 0, SEEK_SET); + u32 read = (u32) gf_fread(sdp_output, fsize, sdp_out); + sdp_output[read]=0; + gf_fclose(sdp_out); + + + gf_rtsp_response_reset(sess->response); + sess->response->ResponseCode = NC_RTSP_OK; + sess->response->CSeq = sess->command->CSeq; + sess->response->body = sdp_output; + + rtspout_send_response(sess->ctx, sess); + sess->response->body = NULL; + gf_free(sdp_output); + + return GF_OK; +} + + +static void rtspout_del_stream(GF_RTPOutStream *st) +{ + if (st->rtp) gf_rtp_streamer_del(st->rtp); + if (st->pck) gf_filter_pid_drop_packet(st->pid); + if (st->avcc) + gf_odf_avc_cfg_del(st->avcc); + if (st->hvcc) + gf_odf_hevc_cfg_del(st->hvcc); + gf_free(st); +} + +static void rtspout_del_session(GF_RTSPOutSession *sess) +{ + //server mode, cleanup + while (gf_list_count(sess->streams)) { + GF_RTPOutStream *stream = gf_list_pop_back(sess->streams); + rtspout_del_stream(stream); + } + gf_list_del(sess->streams); + + if (sess->service_name) + gf_free(sess->service_name); + if (sess->sessionID) + gf_free(sess->sessionID); + gf_list_del(sess->filter_srcs); + gf_rtsp_session_del(sess->rtsp); + gf_rtsp_command_del(sess->command); + gf_rtsp_response_del(sess->response); + gf_list_del_item(sess->ctx->sessions, sess); + if (sess->multicast_ip) gf_free(sess->multicast_ip); + gf_free(sess); +} + + +GF_RTSPOutSession *rtspout_locate_session_for_pid(GF_Filter *filter, GF_RTSPOutCtx *ctx, GF_FilterPid *pid) +{ + u32 i, count = gf_list_count(ctx->sessions); + if (ctx->dst) { + for (i=0; isessions, i); + if (sess->single_session) return sess; + } + return NULL; + } + for (i=0; isessions, i); + if (sess->single_session) continue; + nb_filters = gf_list_count(sess->filter_srcs); + for (j=0; jfilter_srcs, j); + if (gf_filter_pid_is_filter_in_parents(pid, srcf)) + return sess; + } + } + + return NULL; +} + +static GF_Err rtspout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_RTSPOutCtx *ctx = (GF_RTSPOutCtx *) gf_filter_get_udta(filter); + GF_Err e = GF_OK; + GF_RTPOutStream *stream; + GF_RTSPOutSession *sess; + u32 streamType, payt; + const GF_PropertyValue *p; + + sess = rtspout_locate_session_for_pid(filter, ctx, pid); + if (!sess) return GF_SERVICE_ERROR; + + if (is_remove) { + GF_RTPOutStream *t = gf_filter_pid_get_udta(pid); + if (t) { + if (sess->active_stream==t) sess->active_stream = NULL; + gf_list_del_item(sess->streams, t); + rtspout_del_stream(t); + } + if (!gf_list_count(sess->streams)) { + rtspout_del_session(sess); + } + return GF_OK; + } + stream = gf_filter_pid_get_udta(pid); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + streamType = p ? p->value.uint : 0; + + switch (streamType) { + case GF_STREAM_VISUAL: + case GF_STREAM_AUDIO: + break; + case GF_STREAM_FILE: + case GF_STREAM_UNKNOWN: + if (stream) { + if (sess->active_stream==stream) sess->active_stream = NULL; + gf_list_del_item(sess->streams, stream); + rtspout_del_stream(stream); + } + return GF_FILTER_NOT_SUPPORTED; + default: + break; + } + if (!stream) { + GF_SAFEALLOC(stream, GF_RTPOutStream); + if (!stream) return GF_OUT_OF_MEM; + gf_list_add(sess->streams, stream); + stream->pid = pid; + stream->streamtype = streamType; + stream->min_dts = GF_FILTER_NO_TS; + gf_filter_pid_set_udta(pid, stream); + } + + stream->ctrl_id = sess->next_stream_id+1; + sess->next_stream_id++; + + payt = ctx->payt + gf_list_find(sess->streams, stream); + + e = rtpout_init_streamer(stream, ctx->ifce ? ctx->ifce : "127.0.0.1", ctx->xps, ctx->mpeg4, ctx->latm, payt, ctx->mtu, ctx->ttl, ctx->ifce, GF_TRUE, &sess->base_pid_id, 0); + if (e) return e; + + if (ctx->loop) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PLAYBACK_MODE); + if (!p || (p->value.uintloop_disabled = GF_TRUE; + } + } + return GF_OK; +} + +static GF_Err rtspout_check_new_session(GF_RTSPOutCtx *ctx, Bool single_session) +{ + GF_RTSPOutSession *sess; + GF_RTSPSession *new_sess = NULL; + + if (!single_session) { + new_sess = gf_rtsp_session_new_server(ctx->server_sock); + if (!new_sess) return GF_OK; + } + + GF_SAFEALLOC(sess, GF_RTSPOutSession); + if (!sess) { + gf_rtsp_session_del(new_sess); + return GF_OUT_OF_MEM; + } + sess->rtsp = new_sess; + sess->command = gf_rtsp_command_new(); + sess->response = gf_rtsp_response_new(); + sess->streams = gf_list_new(); + sess->filter_srcs = gf_list_new(); + + if (new_sess) { + gf_rtsp_set_buffer_size(new_sess, ctx->block_size); + gf_rtsp_get_remote_address(new_sess, sess->peer_address); + GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTSP] Accepting new connection from %s\n", sess->peer_address)); + } else { + sess->single_session = GF_TRUE; + } + sess->ctx = ctx; + gf_list_add(ctx->sessions, sess); + return GF_OK; +} + +static GF_Err rtspout_initialize(GF_Filter *filter) +{ + char szIP[1024]; + GF_Err e; + u16 port; + char *ip; + GF_RTSPOutCtx *ctx = (GF_RTSPOutCtx *) gf_filter_get_udta(filter); + if (!ctx->payt) ctx->payt = 96; + if (!ctx->port) ctx->port = 554; + if (!ctx->firstport) ctx->firstport = 7000; + if (!ctx->mtu) ctx->mtu = 1450; + if (ctx->payt<96) ctx->payt = 96; + if (ctx->payt>127) ctx->payt = 127; + ctx->sessions = gf_list_new(); + + port = ctx->port; + ip = ctx->ifce; + + if (!ctx->dst) { + if (!ctx->mounts) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTSPOut] No root dir for server, cannot run\n" )); + return GF_BAD_PARAM; + } + gf_filter_make_sticky(filter); + } else { + GF_RTSPOutSession *sess; + char *sep = strchr(ctx->dst+7, '/'); + if (sep) { + u32 cplen = (u32) (sep-ctx->dst-7); + if (cplen>1023) cplen = 1023; + strncpy(szIP, ctx->dst+7, cplen); + szIP[1023] = 0; + sep = strchr(szIP, ':'); + if (sep) { + port = atoi(sep+1); + if (!port) port = ctx->port; + sep[0] = 0; + } + if (strlen(szIP)) ip = szIP; + } + rtspout_check_new_session(ctx, GF_TRUE); + sess = gf_list_get(ctx->sessions, 0); + if (!sess) return GF_SERVICE_ERROR; + sess->server_path = ctx->dst; + sess->sdp_state = SDP_LOADED; + } + + if (ip) + ctx->ip = gf_strdup(ip); + + ctx->server_sock = gf_sk_new(GF_SOCK_TYPE_TCP); + e = gf_sk_bind(ctx->server_sock, NULL, port, ip, 0, GF_SOCK_REUSE_PORT); + if (!e) e = gf_sk_listen(ctx->server_sock, ctx->maxc); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTSPOut] failed to start server on port %d: %s\n", ctx->port, gf_error_to_string(e) )); + return e; + } + + gf_sk_server_mode(ctx->server_sock, GF_TRUE); + GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTSPOut] Server running on port %d\n", ctx->port)); + gf_filter_post_process_task(filter); + return GF_OK; +} + +static void rtspout_finalize(GF_Filter *filter) +{ + GF_RTSPOutCtx *ctx = (GF_RTSPOutCtx *) gf_filter_get_udta(filter); + + while (gf_list_count(ctx->sessions)) { + GF_RTSPOutSession *tmp = gf_list_get(ctx->sessions, 0); + rtspout_del_session(tmp); + } + gf_list_del(ctx->sessions); + + gf_sk_del(ctx->server_sock); + if (ctx->ip) gf_free(ctx->ip); +} + + +static Bool rtspout_init_clock(GF_RTSPOutCtx *ctx, GF_RTSPOutSession *sess) +{ + u64 min_dts = GF_FILTER_NO_TS; + u32 i, count = gf_list_count(sess->streams); + + for (i=0; istreams, i); + if (!stream->selected) continue; + + while (1) { + pck = gf_filter_pid_get_packet(stream->pid); + if (!pck) return GF_FALSE; + if (gf_filter_pck_get_seek_flag(pck)) { + gf_filter_pid_drop_packet(stream->pid); + continue; + } + break; + } + + dts = gf_filter_pck_get_dts(pck); + if (dts==GF_FILTER_NO_TS) + dts = gf_filter_pck_get_cts(pck); + + if (dts==GF_FILTER_NO_TS) dts=0; + + dts *= 1000000; + dts /= stream->timescale; + if (min_dts > dts) + min_dts = dts; + + if (ctx->tso>0) { + u64 offset = ctx->tso; + offset *= stream->timescale; + offset /= 1000000; + stream->rtp_ts_offset = (u32) offset; + } + stream->current_cts = gf_filter_pck_get_cts(pck); + } + sess->sys_clock_at_init = gf_sys_clock_high_res(); + sess->microsec_ts_init = min_dts; + GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTSPOut] Session %s: RTP clock initialized - time origin set to "LLU" us (sys clock) / "LLU" us (media clock)\n", sess->service_name, sess->sys_clock_at_init, sess->microsec_ts_init)); + if (ctx->tso<0) { + gf_rand_init(GF_FALSE); + for (i=0; istreams, i); + stream->rtp_ts_offset = gf_rand(); + GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTSPOut] Session %s: RTP stream %d initial RTP TS set to %d\n", sess->service_name, i+1, stream->rtp_ts_offset)); + } + } + + + gf_rtsp_response_reset(sess->response); + sess->response->ResponseCode = NC_RTSP_OK; + for (i=0; istreams, i); + if (!stream->selected) continue; + if (!stream->send_rtpinfo) continue; + stream->send_rtpinfo = GF_FALSE; + + GF_SAFEALLOC(rtpi, GF_RTPInfo); + if (rtpi) { + rtpi->url = gf_malloc(sizeof(char) * (strlen(sess->service_name)+50)); + sprintf(rtpi->url, "%s/trackID=%d", sess->service_name, stream->ctrl_id); + rtpi->seq = gf_rtp_streamer_get_next_rtp_sn(stream->rtp); + rtpi->rtp_time = (u32) (stream->current_cts + stream->ts_offset + stream->rtp_ts_offset); + + gf_list_add(sess->response->RTP_Infos, rtpi); + } + } + GF_SAFEALLOC(sess->response->Range, GF_RTSPRange); + if (sess->response->Range) + sess->response->Range->start = sess->start_range; + + sess->response->CSeq = sess->last_cseq; + rtspout_send_response(ctx, sess); + sess->request_pending = GF_FALSE; + return GF_TRUE; +} + +static void rtspout_send_event(GF_RTSPOutSession *sess, Bool send_stop, Bool send_play, Double start_range) +{ + GF_FilterEvent fevt; + u32 i, count = gf_list_count(sess->streams); + + memset(&fevt, 0, sizeof(GF_FilterEvent)); + + for (i=0; istreams, i); + if (!stream->selected) continue; + + fevt.base.on_pid = stream->pid; + if (send_stop && stream->is_playing) { + stream->is_playing = GF_FALSE; + fevt.base.type = GF_FEVT_STOP; + gf_filter_pid_send_event(stream->pid, &fevt); + } + if (send_play && !stream->is_playing) { + stream->is_playing = GF_TRUE; + fevt.base.type = GF_FEVT_PLAY; + fevt.play.start_range = start_range; + gf_filter_pid_send_event(stream->pid, &fevt); + + stream->send_rtpinfo = GF_TRUE; + } + } +} + +static GF_Err rtspout_process_rtp(GF_Filter *filter, GF_RTSPOutCtx *ctx, GF_RTSPOutSession *sess) +{ + GF_Err e = GF_OK; + u32 repost_delay_us=0; + + /*init session timeline - all sessions are sync'ed for packet scheduling purposes*/ + if (!sess->sys_clock_at_init) { + if (!rtspout_init_clock(ctx, sess)) return GF_OK; + } + + if (ctx->runfor>0) { + s64 diff = gf_sys_clock_high_res(); + diff -= sess->sys_clock_at_init; + diff /= 1000; + if ((s32) diff > ctx->runfor) { + u32 i, count = gf_list_count(sess->streams); + for (i=0; istreams, i); + gf_filter_pid_set_discard(stream->pid, GF_TRUE); + stream->pck = NULL; + } + return GF_EOS; + } + } + + e = rtpout_process_rtp(sess->streams, &sess->active_stream, sess->loop, ctx->delay, &sess->active_stream_idx, sess->sys_clock_at_init, &sess->active_min_ts_microsec, sess->microsec_ts_init, &sess->wait_for_loop, &repost_delay_us, &sess->first_RTCP_sent, sess->base_pid_id); + + if (e) return e; + if (ctx->next_wake_us > repost_delay_us) + ctx->next_wake_us = (u32) repost_delay_us; + return GF_OK; +} + +static GF_Err rtspout_interleave_packet(void *cbk1, void *cbk2, Bool is_rtcp, u8 *pck, u32 pck_size) +{ + GF_RTSPOutSession *sess = (GF_RTSPOutSession *)cbk1; + GF_RTPOutStream *stream = (GF_RTPOutStream *)cbk2; + + u32 idx = is_rtcp ? stream->rtcp_id : stream->rtp_id; + return gf_rtsp_session_write_interleaved(sess->rtsp, idx, pck, pck_size); +} + +void rtspout_on_filter_setup_error(GF_Filter *f, void *on_setup_error_udta, GF_Err e) +{ + GF_RTSPOutSession *sess = (GF_RTSPOutSession *)on_setup_error_udta; + + gf_list_del_item(sess->filter_srcs, f); + if (gf_list_count(sess->filter_srcs)) return; + + if (sess->sdp_state != SDP_LOADED) { + sess->sdp_state = SDP_LOADED; + gf_rtsp_response_reset(sess->response); + sess->response->ResponseCode = NC_RTSP_Internal_Server_Error; + sess->response->CSeq = sess->command->CSeq; + rtspout_send_response(sess->ctx, sess); + } + rtspout_del_session(sess); +} + +static GF_Err rtspout_load_media_service(GF_Filter *filter, GF_RTSPOutCtx *ctx, GF_RTSPOutSession *sess, char *src_url) +{ + GF_Err e; + Bool found = GF_FALSE; + u32 i, count = gf_list_count(sess->filter_srcs); + for (i=0; ifilter_srcs, i); + const char *url = gf_filter_get_arg_str(src, "src", NULL); + if (url && !strcmp(src_url, url)) { + found = GF_TRUE; + break; + } + } + if (!found) { + GF_Filter *filter_src = gf_filter_connect_source(filter, src_url, NULL, GF_FALSE, &e); + if (!filter_src) { + gf_rtsp_response_reset(sess->response); + sess->response->ResponseCode = NC_RTSP_Session_Not_Found; + sess->response->CSeq = sess->command->CSeq; + rtspout_send_response(ctx, sess); + return e; + } + gf_list_add(sess->filter_srcs, filter_src); + gf_filter_set_setup_failure_callback(filter, filter_src, rtspout_on_filter_setup_error, sess); + sess->sdp_state = SDP_WAIT; + } + if (sess->sdp_state==SDP_LOADED) { + //single session, create SDP + rtspout_send_sdp(sess); + } else { + sess->sdp_state = SDP_WAIT; + sess->request_pending = GF_TRUE; + } + return GF_OK; +} + +static GF_Err rtspout_check_sdp(GF_Filter *filter, GF_RTSPOutSession *sess) +{ + u32 i, count = gf_list_count(sess->streams); + u32 j, nb_filters = gf_list_count(sess->filter_srcs); + + for (j=0; jfilter_srcs, j); + //check we have at least one pid + for (i=0; istreams, i); + if (gf_filter_pid_is_filter_in_parents(stream->pid, srcf)) { + found = GF_TRUE; + break; + } + } + //not yet connected + if (!found) return GF_OK; + + //check we don't have other pid connection pendings + if (gf_filter_has_pid_connection_pending(srcf, filter)) + return GF_OK; + } + //all streams should be ready - note that we don't know handle dynamic pid insertion in source service yet + sess->sdp_state = SDP_LOADED; + sess->request_pending = GF_FALSE; + rtspout_send_sdp(sess); + return GF_OK; +} + +static void rtspout_get_next_mcast_port(GF_RTSPOutCtx *ctx, GF_RTSPOutSession *sess, u32 *port) +{ + u32 i, count = gf_list_count(ctx->sessions); + u32 min_port=ctx->firstport; + for (i=0; isessions, i); + if (asess == sess) continue; + if (!asess->multicast_ip || !sess->multicast_ip) continue; + //reuse port number if different multicast groups + if (strcmp(asess->multicast_ip, sess->multicast_ip)) continue; + + count2 = gf_list_count(asess->streams); + for (j=0; jstreams, j); + if (stream->mcast_port>min_port) min_port = stream->mcast_port; + if (stream->mcast_port == *port) *port = 0; + } + } + if (! *port) *port = min_port; +} + +static GF_RTSPOutSession *rtspout_locate_mcast(GF_RTSPOutCtx *ctx, char *res_path) +{ + u32 i, count = gf_list_count(ctx->sessions); + for (i=0; isessions, i); + if (!a_sess->multicast_ip) continue; + if (!a_sess->service_name) continue; + + a_sess_path = strstr(a_sess->service_name, "://"); + if (a_sess_path) a_sess_path = strchr(a_sess_path+3, '/'); + if (a_sess_path) a_sess_path++; + if (a_sess_path && !strcmp(a_sess_path, res_path)) + return a_sess; + } + return NULL; +} + +static char *rtspout_get_local_res_path(GF_RTSPOutCtx *ctx, char *res_path) +{ + u32 i; + char *src_url=NULL; + for (i=0; imounts); i++) { + char *mpoint = gf_list_get(ctx->mounts, i); + + gf_dynstrcat(&src_url, mpoint, NULL); + gf_dynstrcat(&src_url, res_path, "/"); + if (gf_file_exists(src_url)) + break; + gf_free(src_url); + src_url = NULL; + } + return src_url; +} + +static GF_Err rtspout_process_session_signaling(GF_Filter *filter, GF_RTSPOutCtx *ctx, GF_RTSPOutSession **sess_ptr) +{ + GF_Err e; + GF_RTSPOutSession *sess = *sess_ptr; + char *ctrl=NULL; + u32 stream_ctrl_id=0; + + //no rtsp connection on this session + if (!sess->rtsp) return GF_OK; + + if (sess->sdp_state==SDP_WAIT) { + return rtspout_check_sdp(filter, sess); + } + + if (sess->request_pending) return GF_OK; + + e = gf_rtsp_get_command(sess->rtsp, sess->command); + if (e==GF_IP_NETWORK_EMPTY) { + return GF_OK; + } + // + if (e==GF_IP_CONNECTION_CLOSED) { + gf_rtsp_session_del(sess->rtsp); + sess->rtsp = NULL; + rtspout_check_last_sess(ctx); + return GF_OK; + } + if (e) + return e; + + GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTSP] Got request %s from %s\n", sess->command->method, sess->peer_address)); + + //restore session if needed + if (!sess->service_name) { + u32 i, count = gf_list_count(ctx->sessions); + for (i=0; isessions, i); + if (a_sess->rtsp) continue; + + if (a_sess->sessionID && sess->command->Session && !strcmp(a_sess->sessionID, sess->command->Session) ) { + swap_sess = GF_TRUE; + } + else if (!a_sess->sessionID && !sess->response->Session) { + char *sname; + char *cmd_path = strstr(sess->command->service_name, "://"); + if (cmd_path) cmd_path = strchr(cmd_path+3, '/'); + if (cmd_path) cmd_path++; + + sname = a_sess->service_name; + if (!sname) sname = a_sess->server_path; + + if (sname) sname = strstr(sname, "://"); + if (sname) sname = strchr(sname+3, '/'); + if (sname) sname++; + + if (cmd_path && sname && !strncmp(cmd_path, sname, strlen(sname))) { + if (a_sess->service_name) { + //no session ID, match based on peer address + if (!strcmp(sess->peer_address, a_sess->peer_address)) + swap_sess = GF_TRUE; + } else { + swap_sess = GF_TRUE; + } + } + } + + if (!swap_sess) continue; + gf_rtsp_command_del(a_sess->command); + a_sess->command = sess->command; + a_sess->rtsp = sess->rtsp; + sess->rtsp = NULL; + sess->command = NULL; + memcpy(a_sess->peer_address, sess->peer_address, sizeof(char)*GF_MAX_IP_NAME_LEN); + rtspout_del_session(sess); + *sess_ptr = sess = a_sess; + break; + } + } + if (!sess->sessionID && sess->command->Session) { + gf_rtsp_response_reset(sess->response); + sess->response->ResponseCode = NC_RTSP_Session_Not_Found; + sess->response->CSeq = sess->command->CSeq; + rtspout_send_response(ctx, sess); + return GF_OK; + } + + //process options + if (!strcmp(sess->command->method, GF_RTSP_OPTIONS)) { + gf_rtsp_response_reset(sess->response); + sess->response->ResponseCode = NC_RTSP_OK; + sess->response->Public = "DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE"; + sess->response->CSeq = sess->command->CSeq; + rtspout_send_response(ctx, sess); + sess->response->Public = NULL; + return GF_OK; + } + + //process describe + if (!strcmp(sess->command->method, GF_RTSP_DESCRIBE)) { + u32 rsp_code = NC_RTSP_OK; + char *res_path = NULL; + if (sess->command->service_name) { + res_path = strstr(sess->command->service_name, "://"); + if (res_path) res_path = strchr(res_path+3, '/'); + if (res_path) res_path++; + } + + if (res_path && (ctx->mcast==MCAST_MIRROR) ) { + GF_RTSPOutSession *a_sess = rtspout_locate_mcast(ctx, res_path); + if (a_sess) { + sess->mcast_mirror = a_sess; + rtspout_send_sdp(sess); + return GF_OK; + } + } + + if (!res_path) { + rsp_code = NC_RTSP_Not_Found; + } else if (ctx->dst) { + if (sess->server_path) { + char *sepp = strstr(sess->server_path, "://"); + if (sepp) sepp = strchr(sepp+3, '/'); + if (sepp) sepp++; + if (!sepp || strcmp(sepp, res_path)) + rsp_code = NC_RTSP_Not_Found; + } + } + else if ((res_path[0] == '?') || (res_path[0] == '@') ) { + if (!ctx->dynurl) { + GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTSP] client %s wants dynamic services, not enabled\n", sess->peer_address)); + rsp_code = NC_RTSP_Forbidden; + } else { + GF_List *paths = gf_list_new(); + rsp_code = NC_RTSP_OK; + res_path++; + while (res_path) { + char sep_c=0; + char *src_url = NULL; + char *sep = strchr(res_path, '&'); + if (!sep) sep = strchr(res_path, '@'); + if (sep) { + sep_c = sep[0]; + sep[0] = 0; + } + + if (!strstr(res_path, "://")) { + src_url = rtspout_get_local_res_path(ctx, res_path); + if (src_url) gf_list_add(paths, src_url); + else + rsp_code = NC_RTSP_Not_Found; + } else { + if (gf_filter_is_supported_source(filter, src_url, NULL)) { + src_url = gf_strdup(res_path); + gf_list_add(paths, src_url); + } else { + rsp_code = NC_RTSP_Service_Unavailable; + } + } + + if (!sep) break; + sep[0] = sep_c; + res_path = sep+1; + if (rsp_code != NC_RTSP_OK) + break; + } + while (gf_list_count(paths)) { + char *src_url = gf_list_pop_front(paths); + if (rsp_code == NC_RTSP_OK) { + //load media service + e = rtspout_load_media_service(filter, ctx, sess, src_url); + if (e) { + rsp_code = NC_RTSP_Service_Unavailable; + } + } + gf_free(src_url); + } + gf_list_del(paths); + } + } else { + rsp_code = NC_RTSP_Not_Found; + char *src_url = rtspout_get_local_res_path(ctx, res_path); + if (src_url) { + rsp_code = NC_RTSP_OK; + //load media service + e = rtspout_load_media_service(filter, ctx, sess, src_url); + gf_free(src_url); + if (e) { + rsp_code = NC_RTSP_Service_Unavailable; + } + } + } + + if (sess->service_name) gf_free(sess->service_name); + sess->service_name = gf_strdup(sess->command->service_name); + + if (rsp_code != NC_RTSP_OK) { + gf_rtsp_response_reset(sess->response); + sess->response->ResponseCode = rsp_code; + sess->response->CSeq = sess->command->CSeq; + rtspout_send_response(ctx, sess); + return GF_OK; + } + + if (sess->sdp_state==SDP_LOADED) { + //single session, create SDP + rtspout_send_sdp(sess); + return GF_OK; + } + //store cseq and wait for SDP to be loadable + sess->last_cseq = sess->command->CSeq; + sess->request_pending = GF_TRUE; + return GF_OK; + } + + //forbid any access to the streams, ony describe is allowed + if (sess->mcast_mirror) { + gf_rtsp_response_reset(sess->response); + sess->response->ResponseCode = NC_RTSP_Unauthorized; + sess->response->CSeq = sess->command->CSeq; + rtspout_send_response(ctx, sess); + return GF_OK; + } + + //extract control string if any + if (sess->service_name) { + char *sep = strstr(sess->service_name, "://"); + if (sep) sep = strchr(sep+3, '/'); + if (sep) sep = strstr(sess->command->service_name, sep); + + if (sep) { + ctrl = strrchr(sess->command->service_name, '/'); + } + } else { + ctrl = strrchr(sess->command->service_name, '/'); + } + if (ctrl) { + sscanf(ctrl, "/trackID=%d", &stream_ctrl_id); + } + + //process setup + if (!strcmp(sess->command->method, GF_RTSP_SETUP)) { + char remoteIP[GF_MAX_IP_NAME_LEN]; + GF_RTPOutStream *stream = NULL; + GF_RTSPTransport *transport = gf_list_get(sess->command->Transports, 0); + u32 rsp_code=NC_RTSP_OK; + Bool enable_multicast = GF_FALSE; + Bool reset_transport_dest = GF_FALSE; + + if (!ctrl || !transport) { + rsp_code = NC_RTSP_Bad_Request; + } else if (sess->sessionID && sess->command->Session && strcmp(sess->sessionID, sess->command->Session)) { + rsp_code = NC_RTSP_Bad_Request; + } else if (sess->sessionID && !sess->command->Session) { + rsp_code = NC_RTSP_Not_Implemented; + } else { + u32 i, count = gf_list_count(sess->streams); + for (i=0; istreams, i); + if (stream_ctrl_id==stream->ctrl_id) + break; + stream=NULL; + } + if (!stream_ctrl_id) + rsp_code = NC_RTSP_Not_Found; + } + + if (!stream) { + gf_rtsp_response_reset(sess->response); + sess->response->ResponseCode = rsp_code; + sess->response->CSeq = sess->command->CSeq; + rtspout_send_response(ctx, sess); + return GF_OK; + } + + gf_rtsp_response_reset(sess->response); + sess->response->CSeq = sess->command->CSeq; + + stream->selected = GF_TRUE; + if (transport && (rsp_code==NC_RTSP_OK) ) { + if (!transport->IsInterleaved) { + u32 st_idx = gf_list_find(sess->streams, stream); + transport->port_first = ctx->firstport + 2 * st_idx; + transport->port_last = transport->port_first + 1; + if (sess->interleave) + rsp_code = NC_RTSP_Not_Implemented; + } else { + if (!sess->sessionID) { + sess->interleave = GF_TRUE; + } else if (!sess->interleave) { + rsp_code = NC_RTSP_Not_Implemented; + } + } + transport->SSRC = rand(); + transport->is_sender = GF_TRUE; + + if (transport->IsUnicast) { + if (transport->destination && gf_sk_is_multicast_address(transport->destination)) { + rsp_code = NC_RTSP_Bad_Request; + } else if (!transport->destination) { + transport->destination = sess->peer_address; + reset_transport_dest = GF_TRUE; + } + if (sess->multicast_ip) + rsp_code = NC_RTSP_Forbidden; + + if (ctx->dst && (strstr(ctx->dst, "://127.0.0.1") || strstr(ctx->dst, "://localhost") || strstr(ctx->dst, "://::1/128") ) ) { + if (!reset_transport_dest && transport->destination) gf_free(transport->destination); + transport->destination = "127.0.0.1"; + reset_transport_dest = GF_TRUE; + } + } + else { + if (transport->destination && !gf_sk_is_multicast_address(transport->destination)) { + rsp_code = NC_RTSP_Bad_Request; + } else { + if (ctx->mcast != MCAST_OFF) { + enable_multicast = GF_TRUE; + //we don't allow seting up streams on different mcast addresses + if (!sess->multicast_ip) { + sess->multicast_ip = transport->destination; //detach memory + } + transport->source = transport->destination = sess->multicast_ip; + reset_transport_dest = GF_TRUE; + + transport->client_port_first = 0; + transport->client_port_last = 0; + if (ctx->ttl) transport->TTL = ctx->ttl; + if (!transport->TTL) transport->TTL = 1; + stream->mcast_port = transport->port_first; + rtspout_get_next_mcast_port(ctx, sess, &stream->mcast_port); + transport->port_first = stream->mcast_port; + transport->port_last = stream->mcast_port+1; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTSP] SETUP requests a multicast to %s, not allowed\n", transport->destination)); + rsp_code = NC_RTSP_Forbidden; + } + } + } + } + + if (rsp_code != NC_RTSP_OK) { + sess->response->ResponseCode = rsp_code; + } else { + e = gf_rtp_streamer_init_rtsp(stream->rtp, ctx->mtu, transport, ctx->ifce); + if (e) { + sess->response->ResponseCode = NC_RTSP_Internal_Server_Error; + } else { + if (!sess->sessionID) + sess->sessionID = gf_rtsp_generate_session_id(sess->rtsp); + + sess->response->ResponseCode = NC_RTSP_OK; + sess->response->Session = sess->sessionID; + if (!enable_multicast) { + gf_rtsp_get_session_ip(sess->rtsp, remoteIP); + if (!reset_transport_dest && transport->destination) gf_free(transport->destination); + transport->destination = NULL; + transport->source = remoteIP; + } + gf_list_add(sess->response->Transports, transport); + } + } + + + if (sess->interleave) { + stream->rtp_id = transport->rtpID; + stream->rtcp_id = transport->rtcpID; + gf_rtp_streamer_set_interleave_callbacks(stream->rtp, rtspout_interleave_packet, sess, stream); + } + + rtspout_send_response(ctx, sess); + gf_list_reset(sess->response->Transports); + sess->response->Session = NULL; + if (reset_transport_dest) + transport->destination = NULL; + + transport->source = NULL; + return GF_OK; + } + + //process play + if (!strcmp(sess->command->method, GF_RTSP_PLAY)) { + Double start_range=-1; + u32 rsp_code=NC_RTSP_OK; + if (sess->sessionID && sess->command->Session && strcmp(sess->sessionID, sess->command->Session)) { + rsp_code = NC_RTSP_Bad_Request; + } else if (!sess->command->Session || !sess->sessionID) { + rsp_code = NC_RTSP_Bad_Request; + } + if (sess->command->Range) + start_range = sess->command->Range->start; + + if (stream_ctrl_id) { + rsp_code=NC_RTSP_Only_Aggregate_Operation_Allowed; + } + + if (rsp_code!=NC_RTSP_OK) { + gf_rtsp_response_reset(sess->response); + sess->response->ResponseCode = rsp_code; + sess->response->CSeq = sess->command->CSeq; + rtspout_send_response(ctx, sess); + return GF_OK; + } else { + //loop enabled, only if multicast session or single session mode + if (ctx->loop && !sess->loop_disabled && (sess->single_session || sess->multicast_ip)) + sess->loop = GF_TRUE; + + if ((sess->play_state==2) && !sess->command->Range) { + u64 ellapsed_us = gf_sys_clock_high_res() - sess->pause_sys_clock; + sess->pause_sys_clock = 0; + sess->play_state = 1; + sess->sys_clock_at_init += ellapsed_us; + + gf_rtsp_response_reset(sess->response); + sess->response->ResponseCode = NC_RTSP_OK; + sess->response->CSeq = sess->command->CSeq; + rtspout_send_response(ctx, sess); + } else { + sess->play_state = 1; + sess->sys_clock_at_init = 0; + sess->start_range = start_range; + sess->last_cseq = sess->command->CSeq; + sess->request_pending = GF_TRUE; + rtspout_send_event(sess, GF_FALSE, GF_TRUE, start_range); + } + } + return GF_OK; + } + + //process pause (we don't implement range on pause yet) + if (!strcmp(sess->command->method, GF_RTSP_PAUSE)) { + if (sess->play_state!=2) { + sess->play_state = 2; + sess->pause_sys_clock = gf_sys_clock_high_res(); + } + gf_rtsp_response_reset(sess->response); + sess->response->ResponseCode = NC_RTSP_OK; + sess->response->CSeq = sess->command->CSeq; + rtspout_send_response(ctx, sess); + return GF_OK; + } + //process teardown + if (!strcmp(sess->command->method, GF_RTSP_TEARDOWN)) { + sess->play_state = 0; + rtspout_send_event(sess, GF_TRUE, GF_FALSE, 0); + + gf_rtsp_response_reset(sess->response); + sess->response->ResponseCode = NC_RTSP_OK; + sess->response->CSeq = sess->command->CSeq; + rtspout_send_response(ctx, sess); + + rtspout_send_event(sess, GF_TRUE, GF_FALSE, 0); + + rtspout_del_session(sess); + rtspout_check_last_sess(ctx); + *sess_ptr = NULL; + return GF_OK; + } + return GF_OK; +} + + +static GF_Err rtspout_process(GF_Filter *filter) +{ + GF_Err e=GF_OK; + u32 i, count; + GF_RTSPOutCtx *ctx = gf_filter_get_udta(filter); + + if (ctx->done) + return GF_EOS; + + ctx->next_wake_us = 50000; + e = rtspout_check_new_session(ctx, GF_FALSE); + if (e==GF_IP_NETWORK_EMPTY) { + e = GF_OK; + } + + count = gf_list_count(ctx->sessions); + for (i=0; isessions, i); + sess_err = rtspout_process_session_signaling(filter, ctx, &sess); + if (sess_err) e |= sess_err; + + if (sess && sess->play_state) { + sess_err = rtspout_process_rtp(filter, ctx, sess); + if (sess_err) e |= sess_err; + } + } + + if (e==GF_EOS) { + if (ctx->dst) return GF_EOS; + e=GF_OK; + } + + if (ctx->next_wake_us) + gf_filter_ask_rt_reschedule(filter, ctx->next_wake_us); + + return e; +} + +static GF_FilterProbeScore rtspout_probe_url(const char *url, const char *mime) +{ + if (!strnicmp(url, "rtsp://", 7)) return GF_FPROBE_SUPPORTED; + return GF_FPROBE_NOT_SUPPORTED; +} + +static const GF_FilterCapability RTSPOutCaps[] = +{ + //anything else (not file and framed) result in manifest PID + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "sdp"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "application/sdp"), + {0}, + //anything else (not file and framed) result in media pids not file + CAP_UINT(GF_CAPS_INPUT_EXCLUDED | GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED | GF_CAPFLAG_LOADED_FILTER, GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + + +#define OFFS(_n) #_n, offsetof(GF_RTSPOutCtx, _n) +static const GF_FilterArgs RTSPOutArgs[] = +{ + { OFFS(dst), "location of destination file - see filter help ", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(port), "server port", GF_PROP_UINT, "554", NULL, 0}, + { OFFS(firstport), "port for first stream in session", GF_PROP_UINT, "6000", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(mtu), "size of RTP MTU in bytes", GF_PROP_UINT, "1460", NULL, 0}, + { OFFS(ttl), "time-to-live for muticast packets. A value of 0 uses client requested TTL, or 1", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ifce), "default network inteface to use", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(payt), "payload type to use for dynamic configs", GF_PROP_UINT, "96", "96-127", GF_FS_ARG_HINT_EXPERT}, + { OFFS(mpeg4), "send all streams using MPEG-4 generic payload format if posible", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(delay), "send delay for packet (negative means send earlier)", GF_PROP_SINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(tt), "time tolerance in microseconds. Whenever schedule time minus realtime is below this value, the packet is sent right away", GF_PROP_UINT, "1000", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(runfor), "run the session for the given time in ms. Negative value means run for ever (if loop) or source duration, 0 only outputs the sdp", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(tso), "set timestamp offset in microsecs. Negative value means random initial timestamp", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(xps), "force parameter set injection at each SAP. If not set, only inject if different from SDP ones", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(latm), "use latm for AAC payload format", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(mounts), "list of directories to expose in server mode", GF_PROP_STRING_LIST, NULL, NULL, 0}, + { OFFS(block_size), "block size used to read TCP socket", GF_PROP_UINT, "10000", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(maxc), "maximum number of connections", GF_PROP_UINT, "100", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(user_agent), "user agent string, by default solved from GPAC preferences", GF_PROP_STRING, "$GUA", NULL, 0}, + { OFFS(close), "close RTSP connection after each request, except when RTP over RTSP is used", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(loop), "loop all streams in session (not always possible depending on source type) - see filter help", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(dynurl), "allow dynamic service assembly - see filter help", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(mcast), "control multicast setup of a session\n" + "- off: clients are never allowed to create a multicast\n" + "- on: clients can create multicast sessions\n" + "- mirror: clients can create a multicast session. Any later request to the same URL will use that multicast session" + , GF_PROP_UINT, "off", "off|on|mirror", GF_FS_ARG_HINT_EXPERT}, + + {0} +}; + + +GF_FilterRegister RTSPOutRegister = { + .name = "rtspout", + GF_FS_SET_DESCRIPTION("RTSP Server") + GF_FS_SET_HELP("The RTSP server partially implements RTSP 1.0, with support for OPTIONS, DESCRIBE, SETUP, PLAY, PAUSE and TEARDOWN.\n"\ + "Multiple PLAY ranges are not supported, PLAY range end is not supported, PAUSE range is not supported.\n" + "Only aggregated control is supported for PLAY and PAUSE, PAUSE/PLAY on single stream is not supported.\n"\ + "The server only runs on TCP, and handles request in sequence (will not probe for commands until previous response was sent).\n"\ + "The server supports both RTP over UDP delivery and RTP interleaved over RTSP delivery.\n"\ + "\n"\ + "The filter can work as a simple output filter by specifying the [-dst]() option:\n"\ + "EX gpac -i source -o rtsp://myip/sessionname\n"\ + "EX gpac -i source dst=rtsp://myip/sessionname\n"\ + "In this mode, only one session is possible. It is possible to [-loop]() the input source(s).\n"\ + "\n"\ + "The filter can work as a regular RTSP server by specifying the [-mounts]() option to indicate paths of media file to be served:\n"\ + "EX gpac rtspout:mounts=mydir1,mydir2\n"\ + "In server mode, it is possible to load any source supported by gpac by setting the option [-dynurl]().\n"\ + "The expected syntax of the dynamic RTSP URLs is `rtsp://servername/?URL1[&URLN]` or `rtsp://servername/@URL1[@URLN]` \n"\ + "Each URL can be absolute or local, in which case it is resolved against the mount point(s).\n"\ + "EX gpac -i rtsp://localhost/?pipe://mynamepipe&myfile.mp4 [dst filters]\n"\ + "The server will resolve this URL in a new session containing streams from myfile.mp4 and streams from pipe mynamepipe.\n"\ + "When setting [-runfor]() in server mode, the server will exit at the end of the last session being closed.\n"\ + "\n"\ + "In both modes, clients can setup multicast if the [-mcast]() option is `on` or `mirror`.\n"\ + "When [-mcast]() is set to `mirror` mode, any DESCRIBE command on a resource already delivered through a multicast session will use that multicast.\n"\ + "Consequently, only DESCRIBE methods are processed for such sessions, other methods will return Unauthorized.\n"\ + "\n"\ + "The scheduling algorithm and RTP options are the same as the RTP output filter, see [gpac -h rtpout](rtpout)\n"\ + ) + .private_size = sizeof(GF_RTSPOutCtx), + .max_extra_pids = -1, + .args = RTSPOutArgs, + .probe_url = rtspout_probe_url, + .initialize = rtspout_initialize, + .finalize = rtspout_finalize, + SETCAPS(RTSPOutCaps), + .configure_pid = rtspout_configure_pid, + .process = rtspout_process +}; + + +const GF_FilterRegister *rtspout_register(GF_FilterSession *session) +{ + return &RTSPOutRegister; +} + +#else + +const GF_FilterRegister *rtspout_register(GF_FilterSession *session) +{ + return NULL; +} + +#endif /* !defined(GPAC_DISABLE_STREAMING) */ diff --git a/src/filters/out_sock.c b/src/filters/out_sock.c new file mode 100644 index 0000000..528eb71 --- /dev/null +++ b/src/filters/out_sock.c @@ -0,0 +1,581 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2019 + * All rights reserved + * + * This file is part of GPAC / generic socket output filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include +#include + +typedef struct +{ + GF_Socket *socket; + Bool is_tuned; + char address[GF_MAX_IP_NAME_LEN]; + Bool pck_pending; +} GF_SockOutClient; + +typedef struct +{ + //options + Double start, speed; + char *dst, *mime, *ext, *ifce; + Bool listen; + u32 maxc, port, sockbuf, ka, kp, rate; + GF_Fraction pckr, pckd; + + GF_Socket *socket; + //only one output pid + GF_FilterPid *pid; + + GF_List *clients; + + Bool pid_started; + Bool had_clients; + Bool pck_pending; + + GF_FilterCapability in_caps[2]; + char szExt[10]; + char szFileName[GF_MAX_PATH]; + + u32 nb_pck_processed; + u64 start_time; + u64 nb_bytes_sent; + + GF_FilterPacket *rev_pck; + u32 next_pckd_idx, next_pckr_idx; + u32 nb_pckd_wnd, nb_pckr_wnd; +} GF_SockOutCtx; + + +static GF_Err sockout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_SockOutCtx *ctx = (GF_SockOutCtx *) gf_filter_get_udta(filter); + if (is_remove) { + ctx->pid = NULL; + gf_sk_del(ctx->socket); + ctx->socket = NULL; + return GF_OK; + } + gf_filter_pid_check_caps(pid); + + if (!ctx->pid && (!ctx->listen || gf_list_count(ctx->clients) ) ) { + GF_FilterEvent evt; + gf_filter_pid_init_play_event(pid, &evt, ctx->start, ctx->speed, "SockOut"); + gf_filter_pid_send_event(pid, &evt); + ctx->pid_started = GF_TRUE; + } + ctx->pid = pid; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DISABLE_PROGRESSIVE); + if (p && p->value.uint) { + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[SockOut] Block patching is not supported by socket output\n")); + return GF_NOT_SUPPORTED; + } + return GF_OK; +} + +static GF_Err sockout_initialize(GF_Filter *filter) +{ + GF_Err e; + char *str, *url; + u16 port; + char *ext; + u32 sock_type = 0; + GF_SockOutCtx *ctx = (GF_SockOutCtx *) gf_filter_get_udta(filter); + + if (!ctx || !ctx->dst) return GF_OK; + e = GF_NOT_SUPPORTED; + if (!strncmp(ctx->dst, "tcp://", 6)) e = GF_OK; + else if (!strncmp(ctx->dst, "udp://", 6)) e = GF_OK; + else if (!strncmp(ctx->dst, "tcpu://", 7)) e = GF_OK; + else if (!strncmp(ctx->dst, "udpu://", 7)) e = GF_OK; + + + if (e) { + gf_filter_setup_failure(filter, GF_NOT_SUPPORTED); + return GF_NOT_SUPPORTED; + } + if (ctx->ext) ext = ctx->ext; + else { + ext = gf_file_ext_start(ctx->dst); + if (ext) ext++; + if (ext) { + const char *szport = strchr(ext, ':'); + if (szport) + ext = gf_file_ext_start(szport); + } + } + + if (!ext && !ctx->mime) { + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[SockOut] No extension provided nor mime type for output file %s, cannot infer format\n", ctx->dst)); + return GF_NOT_SUPPORTED; + } + + if (ctx->listen) { + ctx->clients = gf_list_new(); + if (!ctx->clients) return GF_OUT_OF_MEM; + } + + //static cap, streamtype = file + ctx->in_caps[0].code = GF_PROP_PID_STREAM_TYPE; + ctx->in_caps[0].val = PROP_UINT(GF_STREAM_FILE); + ctx->in_caps[0].flags = GF_CAPS_INPUT_STATIC; + + if (ctx->mime) { + ctx->in_caps[1].code = GF_PROP_PID_MIME; + ctx->in_caps[1].val = PROP_NAME( ctx->mime ); + ctx->in_caps[1].flags = GF_CAPS_INPUT; + } else { + strncpy(ctx->szExt, ext, 9); + ctx->szExt[9] = 0; + strlwr(ctx->szExt); + ctx->in_caps[1].code = GF_PROP_PID_FILE_EXT; + ctx->in_caps[1].val = PROP_NAME( ctx->szExt ); + ctx->in_caps[1].flags = GF_CAPS_INPUT; + } + gf_filter_override_caps(filter, ctx->in_caps, 2); + + /*create our ourput socket*/ + + if (!strnicmp(ctx->dst, "udp://", 6)) { + sock_type = GF_SOCK_TYPE_UDP; + ctx->listen = GF_FALSE; + } else if (!strnicmp(ctx->dst, "tcp://", 6)) { + sock_type = GF_SOCK_TYPE_TCP; +#ifdef GPAC_HAS_SOCK_UN + } else if (!strnicmp(ctx->dst, "udpu://", 7)) { + sock_type = GF_SOCK_TYPE_UDP_UN; + ctx->listen = GF_FALSE; + } else if (!strnicmp(ctx->dst, "tcpu://", 7)) { + sock_type = GF_SOCK_TYPE_TCP_UN; +#endif + } else { + return GF_NOT_SUPPORTED; + } + + //skip :// + url = strchr(ctx->dst, ':'); + url += 3; + + ctx->socket = gf_sk_new(sock_type); + if (! ctx->socket ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[SockOut] Failed to open socket for %s\n", ctx->dst)); + return GF_IO_ERR; + } + + /*setup port and src*/ + port = ctx->port; + str = strrchr(url, ':'); + /*take care of IPv6 address*/ + if (str && strchr(str, ']')) str = strchr(url, ':'); + if (str) { + port = atoi(str+1); + str[0] = 0; + } + + if (gf_sk_is_multicast_address(url)) { + e = gf_sk_setup_multicast(ctx->socket, url, port, 0, 0, ctx->ifce); + ctx->listen = GF_FALSE; + } else if ((sock_type == GF_SOCK_TYPE_UDP) +#ifdef GPAC_HAS_SOCK_UN + || (sock_type == GF_SOCK_TYPE_UDP_UN) +#endif + ) { + e = gf_sk_bind(ctx->socket, ctx->ifce, port, url, port, GF_SOCK_REUSE_PORT | GF_SOCK_FAKE_BIND); + ctx->listen = GF_FALSE; + } else if (ctx->listen) { + e = gf_sk_bind(ctx->socket, NULL, port, url, 0, GF_SOCK_REUSE_PORT); + if (!e) e = gf_sk_listen(ctx->socket, ctx->maxc); + if (!e) { + gf_filter_post_process_task(filter); + gf_sk_server_mode(ctx->socket, GF_TRUE); + } + } else { + e = gf_sk_connect(ctx->socket, url, port, ctx->ifce); + } + + if (str) str[0] = ':'; + + if (e) { + gf_sk_del(ctx->socket); + ctx->socket = NULL; + return e; + } + + gf_sk_set_buffer_size(ctx->socket, 0, ctx->sockbuf); + + return GF_OK; +} + +static void sockout_finalize(GF_Filter *filter) +{ + GF_SockOutCtx *ctx = (GF_SockOutCtx *) gf_filter_get_udta(filter); + if (ctx->clients) { + while (gf_list_count(ctx->clients)) { + GF_SockOutClient *sc = gf_list_pop_back(ctx->clients); + if (sc->socket) gf_sk_del(sc->socket); + gf_free(sc); + } + gf_list_del(ctx->clients); + } + + if (ctx->socket) gf_sk_del(ctx->socket); +} + +static GF_Err sockout_send_packet(GF_SockOutCtx *ctx, GF_FilterPacket *pck, GF_Socket *dst_sock) +{ + GF_Err e; + const GF_PropertyValue *p; + u32 w, h, stride, stride_uv, pf; + u32 nb_planes, uv_height; + const char *pck_data; + u32 pck_size; + GF_FilterFrameInterface *hwf=NULL; + if (!dst_sock) return GF_OK; + + pck_data = gf_filter_pck_get_data(pck, &pck_size); + if (pck_data) { + e = gf_sk_send(dst_sock, pck_data, pck_size); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[SockOut] Write error: %s\n", gf_error_to_string(e) )); + } + ctx->nb_bytes_sent += pck_size; + return e; + } + hwf = gf_filter_pck_get_frame_interface(pck); + if (!hwf) { + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[SockOut] output file handle is not opened, discarding %d bytes\n", pck_size)); + return GF_OK; + } + + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_WIDTH); + w = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_HEIGHT); + h = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_PIXFMT); + pf = p ? p->value.uint : 0; + + //get stride/stride_uv with no padding + stride = stride_uv = 0; + if (gf_pixel_get_size_info(pf, w, h, NULL, &stride, &stride_uv, &nb_planes, &uv_height) == GF_TRUE) { + u32 i; + for (i=0; iget_plane(hwf, i, &out_ptr, &out_stride); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[SockOut] Failed to fetch plane data from hardware frame, cannot write\n")); + break; + } + if (i) { + write_h = uv_height; + lsize = stride_uv; + } else { + write_h = h; + lsize = stride; + } + for (j=0; jnb_bytes_sent += lsize; + } + } + } + return GF_OK; +} + + +static GF_Err sockout_process(GF_Filter *filter) +{ + GF_Err e; + Bool is_pck_ref = GF_FALSE; + + GF_FilterPacket *pck; + GF_SockOutCtx *ctx = (GF_SockOutCtx *) gf_filter_get_udta(filter); + + if (!ctx->socket) + return GF_EOS; + + if (ctx->rate) { + if (!ctx->start_time) ctx->start_time = gf_sys_clock_high_res(); + else { + u64 now = gf_sys_clock_high_res() - ctx->start_time; + if (ctx->nb_bytes_sent*8*1000000 > ctx->rate * now) { + u64 diff = ctx->nb_bytes_sent*8*1000000 / ctx->rate - now; + gf_filter_ask_rt_reschedule(filter, (u32) MAX(diff, 1000) ); + return GF_OK; + } else { + fprintf(stderr, "[SockOut] Sending at "LLU" kbps \r", ctx->nb_bytes_sent*8*1000/now); + } + } + } + + if (ctx->listen) { + GF_Socket *new_conn=NULL; + e = gf_sk_accept(ctx->socket, &new_conn); + if ((e==GF_OK) && new_conn) { + GF_SockOutClient *sc; + GF_SAFEALLOC(sc, GF_SockOutClient); + if (!sc) return GF_OUT_OF_MEM; + + sc->socket = new_conn; + strcpy(sc->address, "unknown"); + gf_sk_get_remote_address(new_conn, sc->address); + + GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[SockOut] Accepting new connection from %s\n", sc->address)); + gf_list_add(ctx->clients, sc); + ctx->had_clients = GF_TRUE; + if (!ctx->pid_started && ctx->pid) { + GF_FilterEvent evt; + gf_filter_pid_init_play_event(ctx->pid, &evt, ctx->start, ctx->speed, "SockOut"); + gf_filter_pid_send_event(ctx->pid, &evt); + ctx->pid_started = GF_TRUE; + } + sc->pck_pending = ctx->pck_pending; + if (!ctx->nb_pck_processed) + sc->is_tuned = GF_TRUE; + } + } + if (!ctx->pid) { + if (ctx->listen) gf_filter_post_process_task(filter); + return GF_OK; + } + + pck = gf_filter_pid_get_packet(ctx->pid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->pid)) { + if (ctx->rev_pck) { + is_pck_ref = GF_TRUE; + pck = ctx->rev_pck; + } else { + if (!ctx->listen) { + gf_sk_del(ctx->socket); + ctx->socket = NULL; + return GF_EOS; + } + if (!ctx->ka) + return GF_EOS; + //keep alive, ask for real-time reschedule of 100 ms - we should use socket groups and selects ! + gf_filter_ask_rt_reschedule(filter, 100000); + } + } + if (!pck) + return GF_OK; + } + + if (ctx->pckd.den && !ctx->rev_pck) { + u32 pck_idx = ctx->pckd.num; + u32 nb_pck = ctx->nb_pckd_wnd * ctx->pckd.den; + + if (!pck_idx) { + if (!ctx->next_pckd_idx) ctx->next_pckd_idx = gf_rand() % ctx->pckd.den; + pck_idx = ctx->next_pckd_idx; + } + nb_pck += pck_idx; + + if (nb_pck == 1+ctx->nb_pck_processed) { + ctx->nb_pck_processed++; + GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("[SockOut] Droping packet %d per user request\r", ctx->nb_pck_processed)); + gf_filter_pid_drop_packet(ctx->pid); + ctx->next_pckd_idx = 0; + ctx->nb_pckd_wnd ++; + return GF_OK; + } + } + if (ctx->pckr.den && !ctx->rev_pck) { + u32 pck_idx = ctx->pckr.num; + u32 nb_pck = ctx->nb_pckr_wnd * ctx->pckr.den; + + if (!pck_idx) { + if (!ctx->next_pckr_idx) ctx->next_pckr_idx = gf_rand() % ctx->pckr.den; + pck_idx = ctx->next_pckr_idx; + } + nb_pck += pck_idx; + + if (nb_pck == 1+ctx->nb_pck_processed) { + ctx->rev_pck = pck; + gf_filter_pck_ref(&ctx->rev_pck); + GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("[SockOut] Reverting packet %d per user request\r", ctx->nb_pck_processed)); + ctx->nb_pck_processed++; + gf_filter_pid_drop_packet(ctx->pid); + pck = gf_filter_pid_get_packet(ctx->pid); + if (!pck) return GF_OK; + } + } + + if (ctx->listen) { + Bool had_pck_pending = ctx->pck_pending; + u32 i, nb_clients = gf_list_count(ctx->clients); + u8 dep_flags = gf_filter_pck_get_dependency_flags(pck); + if ((dep_flags & 0x3) == 1) { + } + ctx->pck_pending = GF_FALSE; + + if (!nb_clients) { + //client disconnected, drop packet if needed + if (ctx->had_clients && !ctx->kp) { + if (is_pck_ref) { + gf_filter_pck_unref(pck); + ctx->rev_pck = NULL; + } else { + gf_filter_pid_drop_packet(ctx->pid); + } + } + return GF_OK; + } + for (i=0; iclients, i); + if (!sc->is_tuned) { + + } + if (had_pck_pending && !sc->pck_pending) { + continue; + } + sc->pck_pending = GF_FALSE; + + e = sockout_send_packet(ctx, pck, sc->socket); + if (e == GF_BUFFER_TOO_SMALL) { + sc->pck_pending = GF_TRUE; + ctx->pck_pending = GF_TRUE; + } + } + if (ctx->pck_pending) return GF_OK; + + } else { + e = sockout_send_packet(ctx, pck, ctx->socket); + if (e == GF_BUFFER_TOO_SMALL) return GF_OK; + } + + ctx->nb_pck_processed++; + + if (is_pck_ref) { + gf_filter_pck_unref(pck); + ctx->rev_pck = NULL; + } else { + gf_filter_pid_drop_packet(ctx->pid); + if (ctx->rev_pck) { + e = sockout_send_packet(ctx, ctx->rev_pck, ctx->socket); + if (e == GF_BUFFER_TOO_SMALL) return GF_OK; + gf_filter_pck_unref(ctx->rev_pck); + ctx->rev_pck = NULL; + ctx->next_pckr_idx=0; + ctx->nb_pckr_wnd++; + } + } + return GF_OK; +} + +static GF_FilterProbeScore sockout_probe_url(const char *url, const char *mime) +{ + if (!strnicmp(url, "tcp://", 6)) return GF_FPROBE_SUPPORTED; + if (!strnicmp(url, "udp://", 6)) return GF_FPROBE_SUPPORTED; +#ifdef GPAC_HAS_SOCK_UN + if (!strnicmp(url, "tcpu://", 7)) return GF_FPROBE_SUPPORTED; + if (!strnicmp(url, "udpu://", 7)) return GF_FPROBE_SUPPORTED; +#endif + return GF_FPROBE_NOT_SUPPORTED; +} + + +#define OFFS(_n) #_n, offsetof(GF_SockOutCtx, _n) + +static const GF_FilterArgs SockOutArgs[] = +{ + { OFFS(dst), "location of destination file", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(sockbuf), "block size used to read file", GF_PROP_UINT, "65536", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(port), "default port if not specified", GF_PROP_UINT, "1234", NULL, 0}, + { OFFS(ifce), "default multicast interface", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ext), "file extension of pipe data - see filter help", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(mime), "mime type of pipe data - see filter help", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(listen), "indicate the output socket works in server mode", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(maxc), "max number of concurrent connections", GF_PROP_UINT, "+I", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ka), "keep socket alive if no more connections", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(kp), "keep packets in queue if no more clients", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(start), "set playback start offset. Negative value means percent of media dur with -1 <=> dur", GF_PROP_DOUBLE, "0.0", NULL, 0}, + { OFFS(speed), "set playback speed. If speed is negative and start is 0, start is set to -1", GF_PROP_DOUBLE, "1.0", NULL, 0}, + { OFFS(rate), "set send rate in bps, disabled by default (as fast as possible)", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(pckr), "reverse packet every N - see filter help", GF_PROP_FRACTION, "0/0", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(pckd), "drop packet every N - see filter help", GF_PROP_FRACTION, "0/0", NULL, GF_FS_ARG_HINT_EXPERT}, + {0} +}; + +static const GF_FilterCapability SockOutCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT,GF_PROP_PID_FILE_EXT, "*"), + CAP_STRING(GF_CAPS_INPUT,GF_PROP_PID_MIME, "*"), +}; + + +GF_FilterRegister SockOutRegister = { + .name = "sockout", + GF_FS_SET_DESCRIPTION("UDP/TCP output") +#ifndef GPAC_DISABLE_DOC + .help = "This filter handles generic output sockets (mono-directional) in blocking mode only.\n" + "The filter can work in server mode, waiting for source connections, or or in client mode, directly connecting.\n" + "In server mode, the filter can be instructed to keep running at the end of the stream.\n" + "In server mode, the default behaviour is to keep input packets when no more clients are connected; " + "this can be adjusted though the [-kp]() option, however there is no realtime regulation of how fast packets are droped.\n" + "If your sources are not real time, consider adding a real-time scheduler in the chain (cf reframer filter), or set the send [-rate]() option.\n" + "\n" + "- UDP sockets are used for destinations URLs formatted as `udp://NAME`\n" + "- TCP sockets are used for destinations URLs formatted as `tcp://NAME`\n" +#ifdef GPAC_HAS_SOCK_UN + "- UDP unix domain sockets are used for destinations URLs formatted as `udpu://NAME`\n" + "- TCP unix domain sockets are used for destinations URLs formatted as `tcpu://NAME`\n" +#else + "Your platform does not supports unix domain sockets" +#endif + "\n" + "The socket output can be configured to drop or revert packet order for test purposes.\n" + "For both mode, a window size in packets is specified as the drop/revert fraction denominator, and the index of the packet to drop/revert is given as the numerator/\n" + "If the numerator is 0, a packet is randomly chosen in that window.\n" + "EX :pckd=4/10\n"\ + "This drops every 4th packet of each 10 packet window.\n" + "EX :pckr=0/100\n"\ + "This reverts the send order of one random packet in each 100 packet window.\n" + "\n", +#endif //GPAC_DISABLE_DOC + .private_size = sizeof(GF_SockOutCtx), + .args = SockOutArgs, + SETCAPS(SockOutCaps), + .probe_url = sockout_probe_url, + .initialize = sockout_initialize, + .finalize = sockout_finalize, + .configure_pid = sockout_configure_pid, + .process = sockout_process +}; + + +const GF_FilterRegister *sockout_register(GF_FilterSession *session) +{ + return &SockOutRegister; +} + diff --git a/src/filters/out_video.c b/src/filters/out_video.c new file mode 100644 index 0000000..88379d1 --- /dev/null +++ b/src/filters/out_video.c @@ -0,0 +1,1554 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018-2020 + * All rights reserved + * + * This file is part of GPAC / video output filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_PLAYER + +//#define GPAC_DISABLE_3D + +#if !defined(GPAC_DISABLE_3D) && !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) +#define VOUT_USE_OPENGL + +//include openGL +#include "../compositor/gl_inc.h" + +#define DEL_SHADER(_a) if (_a) { glDeleteShader(_a); _a = 0; } +#define DEL_PROGRAM(_a) if (_a) { glDeleteProgram(_a); _a = 0; } + + +#define TEXTURE_TYPE GL_TEXTURE_2D + + +#if defined( _LP64 ) && defined(CONFIG_DARWIN_GL) +#define GF_SHADERID u64 +#else +#define GF_SHADERID u32 +#endif + + +static char *default_glsl_vertex = "\ + attribute vec4 gfVertex;\ + attribute vec2 gfTexCoord;\ + varying vec2 TexCoord;\ + void main(void)\ + {\ + gl_Position = gl_ModelViewProjectionMatrix * gfVertex;\ + TexCoord = gfTexCoord;\ + }"; + +#endif + +typedef enum +{ + MODE_GL, + MODE_GL_PBO, + MODE_2D, + MODE_2D_SOFT, +} GF_VideoOutMode; + +typedef struct +{ + //options + char *drv; + GF_VideoOutMode disp; + Bool vsync, linear, fullscreen, drop, hide; + GF_Fraction64 dur; + Double speed, hold; + u32 back; + GF_PropVec2i wsize; + GF_PropVec2i wpos; + Double start; + u32 buffer; + GF_Fraction vdelay; + const char *out; + GF_PropUIntList dumpframes; + + GF_Filter *filter; + GF_FilterPid *pid; + u32 width, height, stride, pfmt, timescale; + GF_Fraction fps; + + GF_VideoOutput *video_out; + + u32 pck_offset; + u64 first_cts_plus_one; + u64 clock_at_first_cts, last_frame_clock, clock_at_first_frame; + u32 last_pck_dur_us; + Bool aborted; + u32 display_width, display_height; + Bool display_changed; + Float dh, dw, oh, ow; + Bool has_alpha; + u32 nb_frames; + + //if source is raw live grab (webcam/etc), we don't trust cts and always draw the frame + //this is needed for cases where we have a sudden jump in timestamps as is the case with ffmpeg: not doing so would + //hold the frame until its CTS is reached, triggering drops at capture time + Bool raw_grab; + +#ifdef VOUT_USE_OPENGL + GLint glsl_program; + GF_SHADERID vertex_shader; + GF_SHADERID fragment_shader; + + GF_GLTextureWrapper tx; +#endif // VOUT_USE_OPENGL + + u32 num_textures; + + u32 uv_w, uv_h, uv_stride, bit_depth; + Bool is_yuv, in_fullscreen; + u32 nb_drawn; + + GF_Fraction sar; + + Bool force_release; + GF_FilterPacket *last_pck; + + s32 pid_delay; + Bool buffer_done; + Bool no_buffering; + Bool dump_done; + + u32 dump_f_idx; + char *dump_buffer; +} GF_VideoOutCtx; + +static GF_Err vout_draw_frame(GF_VideoOutCtx *ctx); + +#ifdef VOUT_USE_OPENGL +static Bool vout_compile_shader(GF_SHADERID shader_id, const char *name, const char *source) +{ + GLint blen = 0; + GLsizei slen = 0; + u32 len; + if (!source || !shader_id) return 0; + len = (u32) strlen(source); + glShaderSource(shader_id, 1, &source, &len); + glCompileShader(shader_id); + + glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH , &blen); + if (blen > 1) { + char* compiler_log = (char*) gf_malloc(blen); +#if defined(GPAC_USE_GLES2) + glGetShaderInfoLog(shader_id, blen, &slen, compiler_log); +#elif defined(CONFIG_DARWIN_GL) + glGetInfoLogARB((GLhandleARB) shader_id, blen, &slen, compiler_log); +#else + glGetInfoLogARB(shader_id, blen, &slen, compiler_log); +#endif + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[GLSL] Failed to compile shader %s: %s\n", name, compiler_log)); + gf_free (compiler_log); + return 0; + } + return 1; +} +#endif // VOUT_USE_OPENGL + + +static void vout_set_caption(GF_VideoOutCtx *ctx) +{ + GF_Event evt; + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_SET_CAPTION; + evt.caption.caption = gf_filter_pid_orig_src_args(ctx->pid); + if (!evt.caption.caption) evt.caption.caption = gf_filter_pid_get_source_filter_name(ctx->pid); + if (evt.caption.caption) { + if (!strncmp(evt.caption.caption, "src=", 4)) evt.caption.caption += 4; + if (!strncmp(evt.caption.caption, "./", 2)) evt.caption.caption += 2; + ctx->video_out->ProcessEvent(ctx->video_out, &evt); + } +} + +static GF_Err vout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_Event evt; + const GF_PropertyValue *p; + u32 w, h, pfmt, stride, stride_uv, timescale, dw, dh, hw, hh; + GF_VideoOutCtx *ctx = (GF_VideoOutCtx *) gf_filter_get_udta(filter); + + //if we have a pending packet, draw it now + if (ctx->last_pck) { + vout_draw_frame(ctx); + gf_filter_pck_unref(ctx->last_pck); + ctx->last_pck = NULL; + } + + + if (is_remove) { + assert(ctx->pid==pid); + ctx->pid=NULL; + return GF_OK; + } + assert(!ctx->pid || (ctx->pid==pid)); + if (!gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + w = h = pfmt = timescale = stride = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) timescale = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + if (p) w = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + if (p) h = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); + if (p) pfmt = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (p) ctx->fps = p->value.frac; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE); + if (p) stride = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE_UV); + stride_uv = p ? p->value.uint : 0; + + ctx->sar.num = ctx->sar.den = 1; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); + if (p && p->value.frac.den && p->value.frac.num) ctx->sar = p->value.frac; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DELAY); + ctx->pid_delay = p ? p->value.sint : 0; + + p = gf_filter_pid_get_property_str(pid, "BufferLength"); + ctx->no_buffering = (p && !p->value.sint) ? GF_TRUE : GF_FALSE; + if (ctx->no_buffering) ctx->buffer_done = GF_TRUE; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_RAWGRAB); + ctx->raw_grab = (p && p->value.boolean) ? GF_TRUE : GF_FALSE; + + if (!ctx->pid) { + GF_FilterEvent fevt; + + GF_FEVT_INIT(fevt, GF_FEVT_BUFFER_REQ, pid); + fevt.buffer_req.max_buffer_us = ctx->buffer * 1000; +// if (!fevt.buffer_req.max_buffer_us) fevt.buffer_req.max_buffer_us = 100000; + gf_filter_pid_send_event(pid, &fevt); + + gf_filter_pid_init_play_event(pid, &fevt, ctx->start, ctx->speed, "VideoOut"); + if (ctx->dur.num && ctx->dur.den) { + fevt.play.end_range = ((Double)ctx->dur.num) / ctx->dur.den; + fevt.play.end_range += fevt.play.start_range; + } + gf_filter_pid_send_event(pid, &fevt); + + ctx->start = fevt.play.start_range; + if ((ctx->speed<0) && (fevt.play.speed>=0)) + ctx->speed = fevt.play.speed; + + ctx->pid = pid; + } + vout_set_caption(ctx); + + if (ctx->first_cts_plus_one && ctx->timescale && (ctx->timescale != timescale) ) { + ctx->first_cts_plus_one-=1; + ctx->first_cts_plus_one *= timescale; + ctx->first_cts_plus_one /= ctx->timescale; + ctx->first_cts_plus_one+=1; + } + if (!timescale) timescale = 1; + ctx->timescale = timescale; + + //pid not yet ready + if (!pfmt || !w || !h) return GF_OK; + + if ((ctx->width==w) && (ctx->height == h) && (ctx->pfmt == pfmt) ) return GF_OK; + + dw = w; + dh = h; + if (ctx->sar.den != ctx->sar.num) { + dw = dw * ctx->sar.num / ctx->sar.den; + } + if (ctx->wsize.x==0) ctx->wsize.x = w; + if (ctx->wsize.y==0) ctx->wsize.y = h; + if ((ctx->wsize.x>0) && (ctx->wsize.y>0)) { + dw = ctx->wsize.x; + dh = ctx->wsize.y; + } + //in 2D mode we need to send a setup event to resize backbuffer to the video source wsize + if (ctx->disp>=MODE_2D) { + dw = w; + dh = h; + if (ctx->sar.den != ctx->sar.num) { + dw = dw * ctx->sar.num / ctx->sar.den; + } + } + + if ((dw != ctx->display_width) || (dh != ctx->display_height) ) { + + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_VIDEO_SETUP; + evt.setup.width = dw; + evt.setup.height = dh; + +#ifdef VOUT_USE_OPENGL + if (ctx->dispvsync; + ctx->video_out->ProcessEvent(ctx->video_out, &evt); + if (evt.setup.use_opengl) { + gf_opengl_init(); + } + if (!ctx->in_fullscreen) { + ctx->display_width = evt.setup.width; + ctx->display_height = evt.setup.height; + } + ctx->display_changed = GF_TRUE; + +#if defined(VOUT_USE_OPENGL) && defined(WIN32) + if (evt.setup.use_opengl) + gf_opengl_init(); + + if ((ctx->dispdisp = MODE_2D; + ctx->video_out->ProcessEvent(ctx->video_out, &evt); + } +#endif + + if (!ctx->in_fullscreen) { + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_SIZE; + evt.size.width = dw; + evt.size.height = dh; + ctx->video_out->ProcessEvent(ctx->video_out, &evt); + } + } else if ((ctx->wsize.x>0) && (ctx->wsize.y>0)) { + ctx->display_width = ctx->wsize.x; + ctx->display_height = ctx->wsize.y; + ctx->display_changed = GF_TRUE; + } + if (ctx->fullscreen) { + u32 nw=ctx->display_width, nh=ctx->display_height; + ctx->video_out->SetFullScreen(ctx->video_out, GF_TRUE, &nw, &nh); + ctx->in_fullscreen = GF_TRUE; + } else if ((ctx->wpos.x!=-1) && (ctx->wpos.y!=-1)) { + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_MOVE; + evt.move.relative = 0; + evt.move.x = ctx->wpos.x; + evt.move.y = ctx->wpos.y; + ctx->video_out->ProcessEvent(ctx->video_out, &evt); + } + + ctx->timescale = timescale; + ctx->width = w; + ctx->height = h; + ctx->pfmt = pfmt; + ctx->uv_w = ctx->uv_h = 0; + ctx->uv_stride = stride_uv; + ctx->bit_depth = 0; + ctx->has_alpha = GF_FALSE; + + if (!stride) { + gf_pixel_get_size_info(ctx->pfmt, w, h, NULL, &stride, &ctx->uv_stride, NULL, &ctx->uv_h); + } + ctx->stride = stride ? stride : w; + if (ctx->dump_buffer) { + gf_free(ctx->dump_buffer); + ctx->dump_buffer = NULL; + } + + hw = ctx->width/2; + if (ctx->width %2) hw++; + hh = ctx->height/2; + if (ctx->height %2) hh++; + + ctx->is_yuv = GF_FALSE; + switch (ctx->pfmt) { + case GF_PIXEL_YUV444_10: + ctx->bit_depth = 10; + case GF_PIXEL_YUV444: + ctx->uv_w = ctx->width; + ctx->uv_h = ctx->height; + ctx->uv_stride = ctx->stride; + ctx->is_yuv = GF_TRUE; + break; + case GF_PIXEL_YUV422_10: + ctx->bit_depth = 10; + case GF_PIXEL_YUV422: + ctx->uv_w = ctx->width/2; + ctx->uv_h = ctx->height; + ctx->uv_stride = ctx->stride/2; + ctx->is_yuv = GF_TRUE; + break; + case GF_PIXEL_YUV_10: + ctx->bit_depth = 10; + case GF_PIXEL_YUV: + ctx->uv_w = ctx->width/2; + if (ctx->width % 2) ctx->uv_w++; + ctx->uv_h = ctx->height/2; + if (ctx->height % 2) ctx->uv_h++; + if (!ctx->uv_stride) { + ctx->uv_stride = ctx->stride/2; + if (ctx->stride%2) ctx->uv_stride ++; + } + ctx->is_yuv = GF_TRUE; + break; + case GF_PIXEL_NV12_10: + case GF_PIXEL_NV21_10: + ctx->bit_depth = 10; + case GF_PIXEL_NV12: + case GF_PIXEL_NV21: + ctx->uv_w = ctx->width/2; + ctx->uv_h = ctx->height/2; + ctx->uv_stride = ctx->stride; + ctx->is_yuv = GF_TRUE; + break; + case GF_PIXEL_UYVY: + case GF_PIXEL_YUYV: + case GF_PIXEL_YVYU: + case GF_PIXEL_VYUY: + ctx->uv_w = ctx->width/2; + ctx->uv_h = ctx->height; + if (!ctx->uv_stride) { + ctx->uv_stride = ctx->stride/2; + if (ctx->stride%2) ctx->uv_stride ++; + } + ctx->is_yuv = GF_TRUE; + break; + + case GF_PIXEL_ALPHAGREY: + case GF_PIXEL_GREYALPHA: + case GF_PIXEL_BGRA: + case GF_PIXEL_ARGB: + case GF_PIXEL_ABGR: + case GF_PIXEL_RGBA: + ctx->has_alpha = GF_TRUE; + break; + case GF_PIXEL_GREYSCALE: + case GF_PIXEL_RGB: + case GF_PIXEL_BGR: + case GF_PIXEL_BGRX: + case GF_PIXEL_XRGB: + case GF_PIXEL_XBGR: + case GF_PIXEL_RGBX: + case GF_PIXEL_RGB_444: + case GF_PIXEL_RGB_555: + case GF_PIXEL_RGB_565: + break; + + case 0: + //not yet set, happens with some decoders/stream settings - wait until first frame is available and PF is known + return GF_OK; + default: + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[VideoOut] Pixel format %s unknown\n", gf_4cc_to_str(ctx->pfmt))); + return GF_OK; + } + +#ifdef VOUT_USE_OPENGL + if (ctx->dispvideo_out->ProcessEvent(ctx->video_out, &evt); + } + + DEL_SHADER(ctx->vertex_shader); + DEL_SHADER(ctx->fragment_shader); + DEL_PROGRAM(ctx->glsl_program ); + gf_gl_txw_reset(&ctx->tx); + + if (ctx->dispglsl_program = glCreateProgram(); + ctx->vertex_shader = glCreateShader(GL_VERTEX_SHADER); + vout_compile_shader(ctx->vertex_shader, "vertex", default_glsl_vertex); + + ctx->fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); + gf_gl_txw_setup(&ctx->tx, ctx->pfmt, ctx->width, ctx->height, ctx->stride, ctx->uv_stride, ctx->linear, NULL); + + gf_dynstrcat(&frag_shader_src, "#version 120\n", NULL); + + gf_gl_txw_insert_fragment_shader(ctx->tx.pix_fmt, "maintx", &frag_shader_src); + gf_dynstrcat(&frag_shader_src, "varying vec2 TexCoord;\n" + "void main(void) {\n" + "gl_FragColor = maintx_sample(TexCoord.st);\n" + "}" + , NULL); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] Fragment shader is %s\n", frag_shader_src)); + + vout_compile_shader(ctx->fragment_shader, "fragment", frag_shader_src); + gf_free(frag_shader_src); + + glAttachShader(ctx->glsl_program, ctx->vertex_shader); + glAttachShader(ctx->glsl_program, ctx->fragment_shader); + glLinkProgram(ctx->glsl_program); + +#ifdef WIN32 + if (glMapBuffer == NULL) { + if (ctx->disp == MODE_GL_PBO) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[VideoOut] GL PixelBufferObject extensions not found, fallback to regular GL\n")); + ctx->disp = MODE_GL; + } + } +#endif + + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + glViewport(0, 0, ctx->width, ctx->height); + + gf_mx_init(mx); + ft_hw = ((Float)ctx->width)/2; + ft_hh = ((Float)ctx->height)/2; + gf_mx_ortho(&mx, -ft_hw, ft_hw, -ft_hh, ft_hh, 50, -50); + glMatrixMode(GL_PROJECTION); + glLoadMatrixf(mx.m); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glClear(GL_DEPTH_BUFFER_BIT); + glDisable(GL_NORMALIZE); + glDisable(GL_DEPTH_TEST); + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); + glDisable(GL_LINE_SMOOTH); + glDisable(GL_LINE_SMOOTH); + glDisable(GL_LIGHTING); + glDisable(GL_CULL_FACE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if (ctx->has_alpha) { + glEnable(GL_BLEND); + } else { + glDisable(GL_BLEND); + } + } else +#endif //VOUT_USE_OPENGL + { + switch (ctx->pfmt) { + case GF_PIXEL_NV12: + case GF_PIXEL_NV21: + case GF_PIXEL_NV12_10: + case GF_PIXEL_NV21_10: + ctx->num_textures = 2; + break; + case GF_PIXEL_UYVY: + case GF_PIXEL_VYUY: + case GF_PIXEL_YVYU: + case GF_PIXEL_YUYV: + ctx->num_textures = 1; + break; + default: + if (ctx->is_yuv) { + ctx->num_textures = 3; + } else { + ctx->num_textures = 1; + } + break; + } + } + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[VideoOut] Reconfig input wsize %d x %d, %d textures\n", ctx->width, ctx->height, ctx->num_textures)); + return GF_OK; +} + +static Bool vout_on_event(void *cbk, GF_Event *evt) +{ + GF_FilterEvent fevt; + GF_VideoOutCtx *ctx = (GF_VideoOutCtx *) cbk; + + fevt.base.type = 0; + switch (evt->type) { + case GF_EVENT_SIZE: + if ((ctx->display_width != evt->size.width) || (ctx->display_height != evt->size.height)) { + ctx->display_width = evt->size.width; + ctx->display_height = evt->size.height; + ctx->display_changed = GF_TRUE; + } + break; + case GF_EVENT_CLICK: + case GF_EVENT_MOUSEUP: + case GF_EVENT_MOUSEDOWN: + case GF_EVENT_MOUSEOVER: + case GF_EVENT_MOUSEOUT: + case GF_EVENT_MOUSEMOVE: + case GF_EVENT_MOUSEWHEEL: + case GF_EVENT_KEYUP: + case GF_EVENT_KEYDOWN: + case GF_EVENT_LONGKEYPRESS: + case GF_EVENT_TEXTINPUT: + + GF_FEVT_INIT(fevt, GF_FEVT_USER, ctx->pid); + fevt.user_event.event = *evt; + break; + } + if (gf_filter_ui_event(ctx->filter, evt)) + return GF_TRUE; + + if (fevt.base.type) + gf_filter_pid_send_event(ctx->pid, &fevt); + + return GF_TRUE; +} + +GF_VideoOutput *gf_filter_claim_opengl_provider(GF_Filter *filter); +Bool gf_filter_unclaim_opengl_provider(GF_Filter *filter, GF_VideoOutput *vout); + +static GF_Err vout_initialize(GF_Filter *filter) +{ + const char *sOpt; + void *os_wnd_handler, *os_disp_handler; + u32 init_flags; + GF_Err e; + GF_VideoOutCtx *ctx = (GF_VideoOutCtx *) gf_filter_get_udta(filter); + + ctx->filter = filter; + +#ifdef VOUT_USE_OPENGL + if (ctx->disp <= MODE_GL_PBO) { + ctx->video_out = (GF_VideoOutput *) gf_filter_claim_opengl_provider(filter); + } +#endif + + if (!ctx->video_out) + ctx->video_out = (GF_VideoOutput *) gf_module_load(GF_VIDEO_OUTPUT_INTERFACE, ctx->drv); + + + if (ctx->dumpframes.nb_items) { + ctx->hide = GF_TRUE; + ctx->vsync = GF_FALSE; + } + + /*if not init we run with a NULL audio compositor*/ + if (!ctx->video_out) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] No output modules found, cannot load video output\n")); + return GF_IO_ERR; + } + if (!gf_opts_get_key("core", "video-output")) { + gf_opts_set_key("core", "video-output", ctx->video_out->module_name); + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] Setting up video module %s\n", ctx->video_out->module_name)); + ctx->video_out->on_event = vout_on_event; + ctx->video_out->evt_cbk_hdl = ctx; + + os_wnd_handler = os_disp_handler = NULL; + init_flags = 0; + sOpt = gf_opts_get_key("Temp", "OSWnd"); + if (sOpt) sscanf(sOpt, "%p", &os_wnd_handler); + sOpt = gf_opts_get_key("Temp", "OSDisp"); + if (sOpt) sscanf(sOpt, "%p", &os_disp_handler); + sOpt = gf_opts_get_key("Temp", "InitFlags"); + if (sOpt) sscanf(sOpt, "%d", &init_flags); + + if (ctx->hide) + init_flags |= GF_TERM_INIT_HIDE; + + e = ctx->video_out->Setup(ctx->video_out, os_wnd_handler, os_disp_handler, init_flags); + if (e!=GF_OK) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("Failed to Setup Video Driver %s!\n", ctx->video_out->module_name)); + gf_modules_close_interface((GF_BaseInterface *)ctx->video_out); + ctx->video_out = NULL; + return e; + } + +#ifdef VOUT_USE_OPENGL + if ( !(ctx->video_out->hw_caps & GF_VIDEO_HW_OPENGL) ) +#endif + { + if (ctx->disp < MODE_2D) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("No openGL support - using 2D rasterizer!\n", ctx->video_out->module_name)); + ctx->disp = MODE_2D; + } + } +#ifdef VOUT_USE_OPENGL + if (ctx->disp <= MODE_GL_PBO) { + GF_Event evt; + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_VIDEO_SETUP; + evt.setup.width = 320; + evt.setup.height = 240; + evt.setup.use_opengl = GF_TRUE; + evt.setup.back_buffer = 1; + evt.setup.disable_vsync = !ctx->vsync; + ctx->video_out->ProcessEvent(ctx->video_out, &evt); + + if (evt.setup.use_opengl) { + gf_opengl_init(); + } + gf_filter_register_opengl_provider(filter, GF_TRUE); + } +#endif + + gf_filter_set_event_target(filter, GF_TRUE); + return GF_OK; +} + +static void vout_finalize(GF_Filter *filter) +{ + GF_VideoOutCtx *ctx = (GF_VideoOutCtx *) gf_filter_get_udta(filter); + + if (ctx->last_pck) { + gf_filter_pck_unref(ctx->last_pck); + ctx->last_pck = NULL; + } + + if ((ctx->nb_frames==1) || (ctx->hold<0)) { + u32 holdms = (u32) (((ctx->hold>0) ? ctx->hold : -ctx->hold) * 1000); + gf_sleep(holdms); + } + +#ifdef VOUT_USE_OPENGL + DEL_SHADER(ctx->vertex_shader); + DEL_SHADER(ctx->fragment_shader); + DEL_PROGRAM(ctx->glsl_program ); + gf_gl_txw_reset(&ctx->tx); +#endif //VOUT_USE_OPENGL + + /*stop and shutdown*/ + if (ctx->video_out) { + if (! gf_filter_unclaim_opengl_provider(filter, ctx->video_out)) { + ctx->video_out->Shutdown(ctx->video_out); + gf_modules_close_interface((GF_BaseInterface *)ctx->video_out); + } + ctx->video_out = NULL; + } + if (ctx->dump_buffer) gf_free(ctx->dump_buffer); + +} + +#ifdef VOUT_USE_OPENGL + +static void vout_draw_gl_quad(GF_VideoOutCtx *ctx, Bool flip_texture) +{ + Float dw, dh; + + gf_gl_txw_bind(&ctx->tx, "maintx", ctx->glsl_program, 0); + + dw = ((Float)ctx->dw) / 2; + dh = ((Float)ctx->dh) / 2; + + + GLfloat squareVertices[] = { + dw, dh, + dw, -dh, + -dw, -dh, + -dw, dh, + }; + + GLfloat textureVertices[] = { + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + }; + + if (flip_texture) { + textureVertices[1] = textureVertices[7] = 1.0f; + textureVertices[3] = textureVertices[5] = 0.0f; + } + + int loc = glGetAttribLocation(ctx->glsl_program, "gfVertex"); + if (loc >= 0) { + glVertexAttribPointer(loc, 2, GL_FLOAT, 0, 0, squareVertices); + glEnableVertexAttribArray(loc); + + //setup texcoord location + loc = glGetAttribLocation(ctx->glsl_program, "gfTexCoord"); + if (loc >= 0) { + glVertexAttribPointer(loc, 2, GL_FLOAT, 0, 0, textureVertices); + glEnableVertexAttribArray(loc); + + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Failed to gfTexCoord uniform in shader\n")); + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Failed to gfVertex uniform in shader\n")); + } + + glActiveTexture(GL_TEXTURE0); + glBindTexture(TEXTURE_TYPE, 0); + glDisable(TEXTURE_TYPE); + + glUseProgram(0); + + if (ctx->dump_f_idx) { + FILE *fout; + char szFileName[1024]; + if (strchr(gf_file_basename(ctx->out), '.')) { + snprintf(szFileName, 1024, "%s", ctx->out); + } else { + snprintf(szFileName, 1024, "%s_%d.rgb", ctx->out, ctx->dump_f_idx); + } + if (!ctx->dump_buffer) + ctx->dump_buffer = gf_malloc(sizeof(char)*ctx->display_width*ctx->display_height*3); + + glReadPixels(0, 0, ctx->display_width, ctx->display_height, GL_RGB, GL_UNSIGNED_BYTE, ctx->dump_buffer); + GL_CHECK_ERR() + fout = gf_fopen(szFileName, "wb"); + if (fout) { + gf_fwrite(ctx->dump_buffer, ctx->display_width*ctx->display_height*3, fout); + gf_fclose(fout); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Error writing frame %d buffer to %s\n", ctx->nb_frames, szFileName)); + } + } + + ctx->video_out->Flush(ctx->video_out, NULL); +} + +static void vout_draw_gl_hw_textures(GF_VideoOutCtx *ctx, GF_FilterFrameInterface *hwf) +{ + if (ctx->tx.internal_textures) { + glDeleteTextures(ctx->tx.nb_textures, ctx->tx.textures); + ctx->tx.internal_textures = GF_FALSE; + } + ctx->tx.frame_ifce = hwf; + gf_gl_txw_bind(&ctx->tx, "maintx", ctx->glsl_program, 0); + + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + //and draw + vout_draw_gl_quad(ctx, ctx->tx.flip ? GF_TRUE : GF_FALSE); + GL_CHECK_ERR() +} + +static void vout_draw_gl(GF_VideoOutCtx *ctx, GF_FilterPacket *pck) +{ + char *data; + GF_Event evt; + GF_Matrix mx; + Float hw, hh; + u32 wsize; + GF_FilterFrameInterface *frame_ifce; + + if (!ctx->glsl_program) return; + + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_SET_GL; + ctx->video_out->ProcessEvent(ctx->video_out, &evt); + + if (ctx->display_changed) { + //if we fill width to display width and height is outside + if (ctx->display_width * ctx->height / ctx->width > ctx->display_height) { + ctx->dw = (Float) (ctx->display_height * ctx->width / ctx->height); + ctx->dw *= ctx->sar.num; + ctx->dw /= ctx->sar.den; + ctx->dh = (Float) ctx->display_height; + ctx->oh = (Float) 0; + ctx->ow = (Float) (ctx->display_width - ctx->dw ) / 2; + } else { + ctx->dh = (Float) (ctx->display_width * ctx->height / ctx->width); + ctx->dh *= ctx->sar.den; + ctx->dh /= ctx->sar.num; + ctx->dw = (Float) ctx->display_width; + ctx->ow = (Float) 0; + ctx->oh = (Float) (ctx->display_height - ctx->dh ) / 2; + } + if (ctx->dump_buffer) { + gf_free(ctx->dump_buffer); + ctx->dump_buffer = NULL; + } + + ctx->display_changed = GF_FALSE; + } + + + frame_ifce = gf_filter_pck_get_frame_interface(pck); + if (frame_ifce && (frame_ifce->flags & GF_FRAME_IFCE_MAIN_GLFB)) { + ctx->video_out->Flush(ctx->video_out, NULL); + return; + } + + glViewport(0, 0, ctx->display_width, ctx->display_height); + + gf_mx_init(mx); + hw = ((Float)ctx->display_width)/2; + hh = ((Float)ctx->display_height)/2; + gf_mx_ortho(&mx, -hw, hw, -hh, hh, 10, -5); + glMatrixMode(GL_PROJECTION); + glLoadMatrixf(mx.m); + + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + if (ctx->has_alpha) { + Float r, g, b; + r = GF_COL_R(ctx->back); r /= 255; + g = GF_COL_G(ctx->back); g /= 255; + b = GF_COL_B(ctx->back); b /= 255; + glClearColor(r, g, b, 1.0); + } else { + glClearColor(0, 0, 0, 1.0); + } + glClear(GL_COLOR_BUFFER_BIT); + + //we are not sure if we are the only ones using the gl context, reset our defaults + glDisable(GL_LIGHTING); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + if (ctx->has_alpha) { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + } else { + glDisable(GL_BLEND); + } + if (ctx->has_alpha) { + Float r, g, b; + r = GF_COL_R(ctx->back); r /= 255; + g = GF_COL_G(ctx->back); g /= 255; + b = GF_COL_B(ctx->back); b /= 255; + glClearColor(r, g, b, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + } + + glUseProgram(ctx->glsl_program); + + if (frame_ifce && frame_ifce->get_gl_texture) { + vout_draw_gl_hw_textures(ctx, frame_ifce); + return; + } + + data = (char*) gf_filter_pck_get_data(pck, &wsize); + + //upload texture + gf_gl_txw_upload(&ctx->tx, data, frame_ifce); + + //and draw + vout_draw_gl_quad(ctx, GF_FALSE); +} +#endif + +void vout_draw_2d(GF_VideoOutCtx *ctx, GF_FilterPacket *pck) +{ + char *data; + u32 wsize; + GF_Err e; + GF_VideoSurface src_surf; + GF_Window dst_wnd, src_wnd; + + memset(&src_surf, 0, sizeof(GF_VideoSurface)); + src_surf.width = ctx->width; + src_surf.height = ctx->height; + src_surf.pitch_x = 0; + src_surf.pitch_y = ctx->stride; + src_surf.pixel_format = ctx->pfmt; + src_surf.global_alpha = 0xFF; + + data = (char *) gf_filter_pck_get_data(pck, &wsize); + if (!data) { + u32 stride_luma; + u32 stride_chroma; + GF_FilterFrameInterface *frame_ifce = gf_filter_pck_get_frame_interface(pck); + if (frame_ifce->flags & GF_FRAME_IFCE_BLOCKING) + ctx->force_release = GF_TRUE; + if (! frame_ifce->get_plane) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Hardware GL texture blit not supported with non-GL blitter\n")); + return; + } + e = frame_ifce->get_plane(frame_ifce, 0, (const u8 **) &src_surf.video_buffer, &stride_luma); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Error fetching chroma plane from hardware frame\n")); + return; + } + if (ctx->is_yuv && (ctx->num_textures>1)) { + e = frame_ifce->get_plane(frame_ifce, 1, (const u8 **) &src_surf.u_ptr, &stride_chroma); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Error fetching luma U plane from hardware frame\n")); + return; + } + if ((ctx->pfmt!= GF_PIXEL_NV12) && (ctx->pfmt!= GF_PIXEL_NV21)) { + e = frame_ifce->get_plane(frame_ifce, 2, (const u8 **) &src_surf.v_ptr, &stride_chroma); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Error fetching luma V plane from hardware frame\n")); + return; + } + } + } + } else { + src_surf.video_buffer = data; + } + + + if (ctx->display_changed) { + GF_Event evt; + + memset(&evt, 0, sizeof(GF_Event)); + evt.type = GF_EVENT_VIDEO_SETUP; + evt.setup.back_buffer = 1; + evt.setup.width = ctx->display_width; + evt.setup.height = ctx->display_height; + e = ctx->video_out->ProcessEvent(ctx->video_out, &evt); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Error resizing 2D backbuffer %s\n", gf_error_to_string(e) )); + } + //if we fill width to display width and height is outside + if (ctx->display_width * ctx->height / ctx->width > ctx->display_height) { + ctx->dw = (Float) (ctx->display_height * ctx->width * ctx->sar.num / ctx->height / ctx->sar.den); + ctx->dh = (Float) ctx->display_height; + ctx->oh = (Float) 0; + ctx->ow = (Float) (ctx->display_width - ctx->dw ) / 2; + } else { + ctx->dh = (Float) (ctx->display_width * ctx->height *ctx->sar.den / ctx->width / ctx->sar.num); + ctx->dw = (Float) ctx->display_width; + ctx->ow = (Float) 0; + ctx->oh = (Float) (ctx->display_height - ctx->dh ) / 2; + } + ctx->display_changed = GF_FALSE; + } + + dst_wnd.x = (u32) ctx->ow; + dst_wnd.y = (u32) ctx->oh; + dst_wnd.w = (u32) ctx->dw; + dst_wnd.h = (u32) ctx->dh; + e = GF_EOS; + + src_wnd.x = src_wnd.y = 0; + src_wnd.w = ctx->width; + src_wnd.h = ctx->height; + + if ((ctx->disp!=MODE_2D_SOFT) && ctx->video_out->Blit) { + e = ctx->video_out->Blit(ctx->video_out, &src_surf, &src_wnd, &dst_wnd, 0); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Error bliting surface %s - retrying in software mode\n", gf_error_to_string(e) )); + } + } + if (e) { + GF_VideoSurface backbuffer; + + e = ctx->video_out->LockBackBuffer(ctx->video_out, &backbuffer, GF_TRUE); + if (!e) { + gf_stretch_bits(&backbuffer, &src_surf, &dst_wnd, &src_wnd, 0xFF, GF_FALSE, NULL, NULL); + ctx->video_out->LockBackBuffer(ctx->video_out, &backbuffer, GF_FALSE); + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Cannot lock back buffer - Error %s\n", gf_error_to_string(e) )); + } + } + + if (e != GF_OK) + return; + + + if (ctx->dump_f_idx) { + char szFileName[1024]; + GF_VideoSurface backbuffer; + + e = ctx->video_out->LockBackBuffer(ctx->video_out, &backbuffer, GF_TRUE); + if (!e) { + FILE *fout; + const char *src_ext = strchr(gf_file_basename(ctx->out), '.'); + const char *ext = gf_pixel_fmt_sname(backbuffer.pixel_format); + if (!ext) ext = "rgb"; + + if (src_ext && !strcmp(ext, src_ext+1)) { + snprintf(szFileName, 1024, "%s", ctx->out); + } else { + snprintf(szFileName, 1024, "%s_%d.%s", ctx->out, ctx->dump_f_idx, ext); + } + fout = gf_fopen(szFileName, "wb"); + if (fout) { + u32 i; + for (i=0; ivideo_out->LockBackBuffer(ctx->video_out, &backbuffer, GF_FALSE); + } + + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Error writing frame %d buffer to %s: %s\n", ctx->nb_frames, szFileName, gf_error_to_string(e) )); + } + } + + dst_wnd.x = 0; + dst_wnd.y = 0; + dst_wnd.w = ctx->display_width; + dst_wnd.h = ctx->display_height; + e = ctx->video_out->Flush(ctx->video_out, &dst_wnd); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Error flushing vido output %s%s\n", gf_error_to_string(e))); + return; + } +} + +static GF_Err vout_process(GF_Filter *filter) +{ + GF_FilterPacket *pck; + GF_VideoOutCtx *ctx = (GF_VideoOutCtx *) gf_filter_get_udta(filter); + + ctx->video_out->ProcessEvent(ctx->video_out, NULL); + + pck = gf_filter_pid_get_packet(ctx->pid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->pid)) { + if (!ctx->aborted) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_STOP, ctx->pid); + gf_filter_pid_send_event(ctx->pid, &evt); + + ctx->aborted = GF_TRUE; + } + } + if (ctx->aborted) { + if (ctx->last_pck) { + gf_filter_pck_unref(ctx->last_pck); + ctx->last_pck = NULL; + } + if ((ctx->nb_frames>1) && ctx->last_pck_dur_us) { + gf_filter_ask_rt_reschedule(filter, ctx->last_pck_dur_us); + ctx->last_pck_dur_us = 0; + return GF_OK; + } + //fallthrough + } + //check if all sinks are done - if not keep requesting a process to pump window event loop + if (!gf_filter_all_sinks_done(filter)) { + gf_filter_ask_rt_reschedule(filter, 100000); + if (ctx->display_changed) goto draw_frame; + return GF_OK; + } + return ctx->aborted ? GF_EOS : GF_OK; + } + + if (!ctx->width || !ctx->height) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] pid display wsize unknown, discarding packet\n")); + gf_filter_pid_drop_packet(ctx->pid); + return GF_OK; + } + + if (ctx->dumpframes.nb_items) { + u32 i; + + ctx->nb_frames++; + ctx->dump_f_idx = 0; + + for (i=0; idumpframes.nb_items; i++) { + if (ctx->dumpframes.vals[i] == 0) { + ctx->dump_f_idx = ctx->nb_frames; + break; + } + if (ctx->dumpframes.vals[i] == ctx->nb_frames) { + ctx->dump_f_idx = ctx->dumpframes.vals[i]; + break; + } + if (ctx->dumpframes.vals[i] > ctx->nb_frames) { + break; + } + } + + if (ctx->dump_f_idx) { + ctx->last_pck = pck; + vout_draw_frame(ctx); + ctx->last_pck = NULL; + } + gf_filter_pid_drop_packet(ctx->pid); + + if (ctx->dumpframes.vals[ctx->dumpframes.nb_items-1] <= ctx->nb_frames) { + gf_filter_pid_set_discard(ctx->pid, GF_TRUE); + } + return GF_OK; + } + + if (ctx->buffer) { + if (gf_filter_pck_is_blocking_ref(pck)) { + ctx->buffer_done = GF_TRUE; + ctx->buffer = 0; + } else { + //query full buffer duration in us + u64 dur = gf_filter_pid_query_buffer_duration(ctx->pid, GF_FALSE); + + GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[VideoOut] buffer %d / %d ms\r", (u32) (dur/1000), ctx->buffer)); + if (!ctx->buffer_done) { + if ((dur < ctx->buffer * 1000) && !gf_filter_pid_has_seen_eos(ctx->pid)) { + gf_filter_ask_rt_reschedule(filter, 100000); + + if (gf_filter_reporting_enabled(filter)) { + char szStatus[1024]; + sprintf(szStatus, "buffering %d / %d ms", (u32) (dur/1000), ctx->buffer); + gf_filter_update_status(filter, -1, szStatus); + } + return GF_OK; + } + ctx->buffer_done = GF_TRUE; + } + } + } + + if (ctx->vsync || ctx->drop) { + u64 ref_clock = 0; + u64 cts = gf_filter_pck_get_cts(pck); + u64 clock_us, now = gf_sys_clock_high_res(); + Bool check_clock = GF_FALSE; + GF_Fraction64 media_ts; + s64 delay; + + if (ctx->dur.num) { + if ((cts - ctx->first_cts_plus_one + 1) * ctx->dur.den > (u64) (ctx->dur.num * ctx->timescale)) { + GF_FilterEvent evt; + if (ctx->last_pck) { + gf_filter_pck_unref(ctx->last_pck); + ctx->last_pck = NULL; + } + ctx->aborted = GF_TRUE; + GF_FEVT_INIT(evt, GF_FEVT_STOP, ctx->pid); + gf_filter_pid_send_event(ctx->pid, &evt); + gf_filter_pid_set_discard(ctx->pid, GF_TRUE); + return GF_EOS; + } + } + + delay = ctx->pid_delay; + if (ctx->vdelay.den) + delay += ctx->vdelay.num * (s32)ctx->timescale / (s32)ctx->vdelay.den; + + if (delay>=0) { + cts += delay; + } else if (cts < (u64) (-delay) ) { + if (ctx->vsync) { + if (ctx->last_pck) { + gf_filter_pck_unref(ctx->last_pck); + ctx->last_pck = NULL; + } + gf_filter_pid_drop_packet(ctx->pid); + return GF_OK; + } + cts = 0; + } else { + cts -= (u64) -delay; + } + + //check if we have a clock hint from an audio output + gf_filter_get_clock_hint(filter, &clock_us, &media_ts); + if (clock_us && media_ts.den) { + u32 safety; + //ref frame TS in video stream timescale + s64 ref_ts = (s64) (media_ts.num * ctx->timescale); + ref_ts /= media_ts.den; + //compute time ellapsed since last clock ref in timescale + s64 diff = now; + diff -= (s64) clock_us; + if (ctx->timescale!=1000000) { + diff *= ctx->timescale; + diff /= 1000000; + } + assert(diff>=0); + //ref stream hypothetical timestamp at now + ref_ts += diff; + ctx->first_cts_plus_one = cts + 1; + + //allow 10ms video advance + #define DEF_VIDEO_AUDIO_ADVANCE_MS 15 + safety = DEF_VIDEO_AUDIO_ADVANCE_MS * ctx->timescale / 1000; + if (!ctx->raw_grab && ((s64) cts > ref_ts + safety)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] At %d ms display frame CTS "LLU" CTS greater than reference clock CTS "LLU" (%g sec), waiting\n", gf_sys_clock(), cts, ref_ts, ((Double)media_ts.num)/media_ts.den)); + //the clock is not updated continuously, only when audio sound card writes. We therefore + //cannot know if the sampling was recent or old, so ask for a short reschedule time + gf_filter_ask_rt_reschedule(filter, (u32) ((cts-ref_ts - safety) * 1000000 / ctx->timescale) ); + //not ready yet + return GF_OK; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] At %d ms frame CTS "LLU" latest reference clock CTS "LLU" (%g sec)\n", gf_sys_clock(), cts, ref_ts, ((Double)media_ts.num)/media_ts.den)); + ref_clock = ref_ts; + check_clock = GF_TRUE; + } else if (!ctx->first_cts_plus_one) { + //init clock on second frame, first frame likely will have large rendering time + //due to GPU config. While this is not important for recorded media, it may impact + //monitoring of live source (webcams) by consuming frame really late which may + //block source sampling when blocking mode is enabled + + //store first frame TS + if (!ctx->clock_at_first_cts) { + ctx->clock_at_first_cts = 1 + cts; + } else { + //comute CTS diff in ms + u64 diff = cts - ctx->clock_at_first_cts + 1; + diff *= 1000; + diff /= ctx->timescale; + //diff less than 100ms (eg 10fps or more), init on the second frame + if (diff<100) { + ctx->first_cts_plus_one = 1 + cts; + ctx->clock_at_first_cts = now; + } + //otherwise (low frame rate), init on the first frame + else { + ctx->first_cts_plus_one = ctx->clock_at_first_cts /* - 1 + 1*/; + ctx->clock_at_first_cts = ctx->last_frame_clock; + check_clock = GF_TRUE; + } + } + ref_clock = cts; + } else { + check_clock = GF_TRUE; + } + + if (check_clock) { + s64 diff; + if (ctx->speed>=0) { + if (cts < ctx->first_cts_plus_one) cts = ctx->first_cts_plus_one; + diff = (s64) ((now - ctx->clock_at_first_cts) * ctx->speed); + + if (ctx->timescale != 1000000) + diff -= (s64) ( (cts - ctx->first_cts_plus_one + 1) * 1000000 / ctx->timescale); + else + diff -= (s64) (cts - ctx->first_cts_plus_one + 1); + + } else { + diff = (s64) ((now - ctx->clock_at_first_cts) * -ctx->speed); + + if (ctx->timescale != 1000000) + diff -= (s64) ( (ctx->first_cts_plus_one-1 - cts) * 1000000 / ctx->timescale); + else + diff -= (s64) (ctx->first_cts_plus_one-1 - cts); + } + + if (!ctx->raw_grab && (diff < -2000)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] At %d ms frame cts "LLU"/%d "LLU" us too early, waiting\n", gf_sys_clock(), cts, ctx->timescale, -diff)); + if (diff<-1000000) diff = -1000000; + gf_filter_ask_rt_reschedule(filter, (u32) (-diff)); + + if (ctx->display_changed) goto draw_frame; + //not ready yet + return GF_OK; + } + if (ABS(diff)>2000) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] At %d ms frame cts "LLU"/%d is "LLD" us too %s\n", gf_sys_clock(), cts, ctx->timescale, diff<0 ? -diff : diff, diff<0 ? "early" : "late")); + } else { + diff = 0; + } + + if (ctx->timescale != 1000000) + ref_clock = diff * ctx->timescale / 1000000 + cts; + else + ref_clock = diff + cts; + + ctx->last_pck_dur_us = gf_filter_pck_get_duration(pck); + ctx->last_pck_dur_us *= 1000000; + ctx->last_pck_dur_us /= ctx->timescale; + } + //detach packet from pid, so that we can query next cts + gf_filter_pck_ref(&pck); + gf_filter_pid_drop_packet(ctx->pid); + + if (ctx->drop) { + u64 next_ts; + Bool do_drop = GF_FALSE; + //peeking next packet CTS might fail if no packet in buffer, in which case we don't drop + if (gf_filter_pid_get_first_packet_cts(ctx->pid, &next_ts)) { + if (next_tsspeed > 2){ + u32 speed = (u32) ABS(ctx->speed); + + ctx->nb_drawn++; + if (ctx->nb_drawn % speed) { + do_drop = GF_TRUE; + } else { + ctx->nb_drawn = 0; + } + } + if (do_drop) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] At %d ms drop frame "LLU" ms reference clock "LLU" ms\n", gf_sys_clock(), (1000*cts)/ctx->timescale, (1000*ref_clock)/ctx->timescale)); + + gf_filter_pck_unref(pck); + return GF_OK; + } + } + + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] At %d ms display frame cts "LLU"/%d "LLU" ms\n", gf_sys_clock(), cts, ctx->timescale, (1000*cts)/ctx->timescale)); + } else { + gf_filter_pck_ref(&pck); + gf_filter_pid_drop_packet(ctx->pid); + +#ifndef GPAC_DISABLE_LOG +#ifdef VOUT_USE_OPENGL + u64 cts = gf_filter_pck_get_cts(pck); + GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] At %d ms display frame cts "LLU"/%d "LLU" ms\n", gf_sys_clock(), cts, ctx->timescale, (1000*cts)/ctx->timescale)); +#endif +#endif + + } + + if (ctx->last_pck) + gf_filter_pck_unref(ctx->last_pck); + ctx->last_pck = pck; + ctx->nb_frames++; + + +draw_frame: + + if (ctx->last_pck && gf_filter_reporting_enabled(filter)) { + char szStatus[1024]; + u64 dur = gf_filter_pid_query_buffer_duration(ctx->pid, GF_FALSE); + Double fps = 0; + if (ctx->clock_at_first_frame) { + fps = ctx->nb_frames; + fps *= 1000000; + fps /= (1 + gf_sys_clock_high_res() - ctx->clock_at_first_cts); + } else { + ctx->clock_at_first_frame = gf_sys_clock_high_res(); + } + sprintf(szStatus, "%dx%d->%dx%d %s frame TS "LLU"/%d buffer %d / %d ms %02.02f FPS", ctx->width, ctx->height, ctx->display_width, ctx->display_height, + gf_pixel_fmt_name(ctx->pfmt), gf_filter_pck_get_cts(ctx->last_pck), ctx->timescale, (u32) (dur/1000), ctx->buffer, fps); + gf_filter_update_status(filter, -1, szStatus); + } + + return vout_draw_frame(ctx); +} + +static GF_Err vout_draw_frame(GF_VideoOutCtx *ctx) +{ + ctx->force_release = GF_TRUE; + if (ctx->pfmt && ctx->last_pck) { +#ifdef VOUT_USE_OPENGL + if (ctx->disp < MODE_2D) { + gf_rmt_begin_gl(vout_draw_gl); + glGetError(); + vout_draw_gl(ctx, ctx->last_pck); + gf_rmt_end_gl(); + glGetError(); + } else +#endif + { + vout_draw_2d(ctx, ctx->last_pck); + } + } + if (ctx->no_buffering) + ctx->force_release = GF_TRUE; + else if (ctx->last_pck && gf_filter_pck_is_blocking_ref(ctx->last_pck) ) + ctx->force_release = GF_TRUE; + + if (ctx->force_release && ctx->last_pck && !ctx->dump_f_idx) { + gf_filter_pck_unref(ctx->last_pck); + ctx->last_pck = NULL; + } + //remember the clock right after the flush (in case of vsync) + ctx->last_frame_clock = gf_sys_clock_high_res(); + + //note we don't ask RT reschedule in frame duration, we take the decision on postponing the next frame in the next process call + return GF_OK; +} + +static Bool vout_process_event(GF_Filter *filter, const GF_FilterEvent *fevt) +{ + if (fevt->base.type==GF_FEVT_INFO_UPDATE) { + GF_VideoOutCtx *ctx = (GF_VideoOutCtx *) gf_filter_get_udta(filter); + vout_set_caption(ctx); + return GF_TRUE; + } + if (!fevt->base.on_pid && (fevt->base.type==GF_FEVT_USER)) { + GF_VideoOutCtx *ctx = (GF_VideoOutCtx *) gf_filter_get_udta(filter); + GF_Err e; + if (!ctx->video_out) return GF_FALSE; + e = ctx->video_out->ProcessEvent(ctx->video_out, (GF_Event *) &fevt->user_event.event); + if (e) return GF_FALSE; + return GF_TRUE; + } + //cancel + return GF_TRUE; +} + +#define OFFS(_n) #_n, offsetof(GF_VideoOutCtx, _n) + +static const GF_FilterArgs VideoOutArgs[] = +{ + { OFFS(drv), "video driver name", GF_PROP_NAME, NULL, NULL, 0}, + { OFFS(vsync), "enable video screen sync", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(drop), "enable droping late frames", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(disp), "display mode\n" + "- gl: OpenGL\n" + "- pbo: OpenGL with PBO\n" + "- blit: 2D hardware blit\n" + "- soft: software blit", GF_PROP_UINT, "gl", "gl|pbo|blit|soft", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(start), "set playback start offset. Negative value means percent of media dur with -1 <=> dur", GF_PROP_DOUBLE, "0.0", NULL, 0}, + { OFFS(dur), "only play the specified duration", GF_PROP_FRACTION64, "0", NULL, 0}, + { OFFS(speed), "set playback speed when vsync is on. If speed is negative and start is 0, start is set to -1", GF_PROP_DOUBLE, "1.0", NULL, 0}, + { OFFS(hold), "number of seconds to hold display for single-frame streams. A negative value force a hold on last frame for single or multi-frames streams", GF_PROP_DOUBLE, "1.0", NULL, 0}, + { OFFS(linear), "use linear filtering instead of nearest pixel for GL mode", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(back), "back color for transparent images", GF_PROP_UINT, "0x808080", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(wsize), "default init window size. 0x0 holds the window size of the first frame. Negative values indicate video media size", GF_PROP_VEC2I, "-1x-1", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(wpos), "default position (0,0 top-left)", GF_PROP_VEC2I, "-1x-1", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(vdelay), "set delay in sec, positive value displays after audio clock", GF_PROP_FRACTION, "0", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE}, + { OFFS(hide), "hide output window", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(fullscreen), "use fullcreen", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(buffer), "set buffer in ms", GF_PROP_UINT, "100", NULL, 0}, + { OFFS(dumpframes), "ordered list of frames to dump, 1 being first frame - see filter help. Special value 0 means dump all frames", GF_PROP_UINT_LIST, NULL, NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(out), "radical of dump frame filenames. If no extension is provided, frames are exported as $OUT_%d.PFMT", GF_PROP_STRING, "dump", NULL, GF_FS_ARG_HINT_EXPERT}, + {0} +}; + +static const GF_FilterCapability VideoOutCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW) +}; + + +GF_FilterRegister VideoOutRegister = { + .name = "vout", + GF_FS_SET_DESCRIPTION("Video output") + GF_FS_SET_HELP("This filter displays a single visual pid in a window.\n"\ + "The window is created unless a window handle (HWND, xWindow, etc) is indicated in the config file ( [Temp]OSWnd=ptr).\n"\ + "The output uses GPAC video output module indicated in [-drv]() option or in the config file (see GPAC core help).\n"\ + "The video output module can be further configured (see GPAC core help).\n"\ + "The filter can use openGL or 2D blitter of the graphics card, depending on the OS support.\n"\ + "The filter can be used do dump frames as written on the grapics card.\n"\ + "In this case, the window is not visible and only the listed frames are drawn to the GPU.\n"\ + "The pixel format of the dumped frame is always RGB in OpenGL and matches the video backbuffer format in 2D mode.\n"\ + ) + .private_size = sizeof(GF_VideoOutCtx), + .flags = GF_FS_REG_MAIN_THREAD, + .args = VideoOutArgs, + SETCAPS(VideoOutCaps), + .initialize = vout_initialize, + .finalize = vout_finalize, + .configure_pid = vout_configure_pid, + .process = vout_process, + .process_event = vout_process_event +}; + +const GF_FilterRegister *vout_register(GF_FilterSession *session) +{ + return &VideoOutRegister; +} +#else +const GF_FilterRegister *vout_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // GPAC_DISABLE_PLAYER diff --git a/src/filters/reframe_ac3.c b/src/filters/reframe_ac3.c new file mode 100644 index 0000000..516442b --- /dev/null +++ b/src/filters/reframe_ac3.c @@ -0,0 +1,612 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / AC3 reframer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +#ifndef GPAC_DISABLE_AV_PARSERS + +typedef struct +{ + u64 pos; + Double duration; +} AC3Idx; + +#define AC3_FRAME_SIZE 1536 + +typedef struct +{ + //filter args + Double index; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + GF_BitStream *bs; + u64 file_pos, cts; + u32 sample_rate, nb_ch; + GF_Fraction64 duration; + Double start_range; + Bool in_seek; + u32 timescale; + + GF_AC3Header hdr; + u8 *ac3_buffer; + u32 ac3_buffer_size, ac3_buffer_alloc, resume_from; + u64 byte_offset; + + Bool is_playing; + Bool is_file, file_loaded; + Bool initial_play_done; + + Bool is_eac3; + Bool (*ac3_parser_bs)(GF_BitStream*, GF_AC3Header*, Bool); + + GF_FilterPacket *src_pck; + + AC3Idx *indexes; + u32 index_alloc_size, index_size; +} GF_AC3DmxCtx; + + + + +GF_Err ac3dmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_AC3DmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) ctx->timescale = p->value.uint; + + ctx->ac3_parser_bs = gf_ac3_parser_bs; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (p && p->value.uint==GF_CODECID_EAC3) ctx->is_eac3 = GF_TRUE; + else { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_MIME); + if (p && p->value.string && strstr(p->value.string, "eac3")) ctx->is_eac3 = GF_TRUE; + else { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FILE_EXT); + if (p && p->value.string && strstr(p->value.string, "eac3")) ctx->is_eac3 = GF_TRUE; + } + } + if (ctx->is_eac3) { + ctx->ac3_parser_bs = gf_eac3_parser_bs; + } + + if (ctx->timescale && !ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + } + return GF_OK; +} + +static void ac3dmx_check_dur(GF_Filter *filter, GF_AC3DmxCtx *ctx) +{ + FILE *stream; + GF_BitStream *bs; + GF_AC3Header hdr; + u64 duration, cur_dur; + s32 sr = -1; + const GF_PropertyValue *p; + if (!ctx->opid || ctx->timescale || ctx->file_loaded) return; + + if (ctx->index<=0) { + ctx->file_loaded = GF_TRUE; + return; + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) { + ctx->is_file = GF_FALSE; + ctx->file_loaded = GF_TRUE; + return; + } + ctx->is_file = GF_TRUE; + + stream = gf_fopen(p->value.string, "rb"); + if (!stream) return; + + ctx->index_size = 0; + + bs = gf_bs_from_file(stream, GF_BITSTREAM_READ); + duration = 0; + cur_dur = 0; + while ( ctx->ac3_parser_bs(bs, &hdr, GF_FALSE) ) { + if ((sr>=0) && (sr != hdr.sample_rate)) { + duration *= hdr.sample_rate; + duration /= sr; + + cur_dur *= hdr.sample_rate; + cur_dur /= sr; + } + sr = hdr.sample_rate; + duration += AC3_FRAME_SIZE; + cur_dur += AC3_FRAME_SIZE; + if (cur_dur > ctx->index * sr) { + if (!ctx->index_alloc_size) ctx->index_alloc_size = 10; + else if (ctx->index_alloc_size == ctx->index_size) ctx->index_alloc_size *= 2; + ctx->indexes = gf_realloc(ctx->indexes, sizeof(AC3Idx)*ctx->index_alloc_size); + ctx->indexes[ctx->index_size].pos = gf_bs_get_position(bs); + ctx->indexes[ctx->index_size].duration = (Double) duration; + ctx->indexes[ctx->index_size].duration /= sr; + ctx->index_size ++; + cur_dur = 0; + } + + gf_bs_skip_bytes(bs, hdr.framesize); + } + gf_bs_del(bs); + gf_fclose(stream); + + if (!ctx->duration.num || (ctx->duration.num * sr != duration * ctx->duration.den)) { + ctx->duration.num = (s32) duration; + ctx->duration.den = sr; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, & PROP_BOOL(GF_TRUE ) ); +} + +static void ac3dmx_check_pid(GF_Filter *filter, GF_AC3DmxCtx *ctx) +{ + GF_BitStream *bs; + u8 *data; + u32 size; + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + ac3dmx_check_dur(filter, ctx); + } + if ((ctx->sample_rate == ctx->hdr.sample_rate) && (ctx->nb_ch == ctx->hdr.channels) ) return; + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT( GF_STREAM_AUDIO)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLES_PER_FRAME, & PROP_UINT(AC3_FRAME_SIZE) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, & PROP_BOOL(GF_FALSE) ); + + if (ctx->duration.num) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + + + ctx->nb_ch = ctx->hdr.channels; + if (!ctx->timescale) { + //we change sample rate, change cts + if (ctx->cts && (ctx->sample_rate != ctx->hdr.sample_rate)) { + ctx->cts *= ctx->hdr.sample_rate; + ctx->cts /= ctx->sample_rate; + } + } + ctx->sample_rate = ctx->hdr.sample_rate; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->timescale ? ctx->timescale : ctx->sample_rate)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, & PROP_UINT(ctx->sample_rate)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, & PROP_UINT(ctx->nb_ch) ); + + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + if (ctx->is_eac3) { + u32 i; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(GF_CODECID_EAC3) ); + + gf_bs_write_int(bs, ctx->hdr.data_rate, 13); + gf_bs_write_int(bs, ctx->hdr.nb_streams-1, 3); + for (i=0; ihdr.nb_streams; i++) { + gf_bs_write_int(bs, ctx->hdr.streams[i].fscod, 2); + gf_bs_write_int(bs, ctx->hdr.streams[i].bsid, 5); + gf_bs_write_int(bs, 0, 1); + //TODO, expose asvc as an option of reframer, the info is not carried in the bitstream + gf_bs_write_int(bs, ctx->hdr.streams[i].asvc, 1); + gf_bs_write_int(bs, ctx->hdr.streams[i].bsmod, 3); + gf_bs_write_int(bs, ctx->hdr.streams[i].acmod, 3); + gf_bs_write_int(bs, ctx->hdr.streams[i].lfon, 1); + gf_bs_write_int(bs, 0, 3); + gf_bs_write_int(bs, ctx->hdr.streams[i].num_dep_sub, 4); + if (ctx->hdr.streams[i].num_dep_sub) { + gf_bs_write_int(bs, ctx->hdr.streams[i].chan_loc, 9); + } else { + gf_bs_write_int(bs, 0, 1); + } + } + } else { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(GF_CODECID_AC3) ); + + gf_bs_write_int(bs, ctx->hdr.streams[0].fscod, 2); + gf_bs_write_int(bs, ctx->hdr.streams[0].bsid, 5); + gf_bs_write_int(bs, ctx->hdr.streams[0].bsmod, 3); + gf_bs_write_int(bs, ctx->hdr.streams[0].acmod, 3); + gf_bs_write_int(bs, ctx->hdr.streams[0].lfon, 1); + gf_bs_write_int(bs, ctx->hdr.streams[0].brcode, 5); + gf_bs_write_int(bs, 0, 5); + } + + gf_bs_get_content(bs, &data, &size); + gf_bs_del(bs); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, & PROP_DATA_NO_COPY(data, size) ); + + if (ctx->is_file && ctx->index) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, & PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD) ); + } +} + +static Bool ac3dmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i; + GF_FilterEvent fevt; + GF_AC3DmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (!ctx->is_playing) { + ctx->is_playing = GF_TRUE; + ctx->cts = 0; + ctx->ac3_buffer_size = 0; + ctx->resume_from = 0; + } + if (! ctx->is_file) { + return GF_FALSE; + } + ctx->start_range = evt->play.start_range; + ctx->in_seek = GF_TRUE; + ctx->file_pos = 0; + if (ctx->start_range) { + for (i=1; iindex_size; i++) { + if (ctx->indexes[i].duration>ctx->start_range) { + ctx->cts = (u64) (ctx->indexes[i-1].duration * ctx->sample_rate); + ctx->file_pos = ctx->indexes[i-1].pos; + break; + } + } + } + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!ctx->file_pos) + return GF_TRUE; + } + ctx->ac3_buffer_size = 0; + ctx->resume_from = 0; + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = ctx->file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + //don't cancel event + ctx->is_playing = GF_FALSE; + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +static GFINLINE void ac3dmx_update_cts(GF_AC3DmxCtx *ctx) +{ + if (ctx->timescale) { + u64 inc = AC3_FRAME_SIZE; + inc *= ctx->timescale; + inc /= ctx->sample_rate; + ctx->cts += inc; + } else { + ctx->cts += AC3_FRAME_SIZE; + } +} + +GF_Err ac3dmx_process(GF_Filter *filter) +{ + GF_AC3DmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + u8 *output; + u8 *start; + u32 pck_size, remain, prev_pck_size; + u64 cts = GF_FILTER_NO_TS; + + //always reparse duration + if (!ctx->duration.num) + ac3dmx_check_dur(filter, ctx); + + if (ctx->opid && !ctx->is_playing) + return GF_OK; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (!ctx->ac3_buffer_size) { + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = NULL; + return GF_EOS; + } + } else { + return GF_OK; + } + } + + prev_pck_size = ctx->ac3_buffer_size; + if (pck && !ctx->resume_from) { + const u8 *data = gf_filter_pck_get_data(pck, &pck_size); + if (!pck_size) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + + if (ctx->byte_offset != GF_FILTER_NO_BO) { + u64 byte_offset = gf_filter_pck_get_byte_offset(pck); + if (!ctx->ac3_buffer_size) { + ctx->byte_offset = byte_offset; + } else if (ctx->byte_offset + ctx->ac3_buffer_size != byte_offset) { + ctx->byte_offset = GF_FILTER_NO_BO; + if ((byte_offset != GF_FILTER_NO_BO) && (byte_offset>ctx->ac3_buffer_size) ) { + ctx->byte_offset = byte_offset - ctx->ac3_buffer_size; + } + } + } + + if (ctx->ac3_buffer_size + pck_size > ctx->ac3_buffer_alloc) { + ctx->ac3_buffer_alloc = ctx->ac3_buffer_size + pck_size; + ctx->ac3_buffer = gf_realloc(ctx->ac3_buffer, ctx->ac3_buffer_alloc); + } + memcpy(ctx->ac3_buffer + ctx->ac3_buffer_size, data, pck_size); + ctx->ac3_buffer_size += pck_size; + } + + //input pid sets some timescale - we flushed pending data , update cts + if (ctx->timescale && pck) { + cts = gf_filter_pck_get_cts(pck); + } + + if (cts == GF_FILTER_NO_TS) { + //avoids updating cts + prev_pck_size = 0; + } + + remain = ctx->ac3_buffer_size; + start = ctx->ac3_buffer; + + if (ctx->resume_from) { + start += ctx->resume_from - 1; + remain -= ctx->resume_from - 1; + ctx->resume_from = 0; + } + + if (!ctx->bs) { + ctx->bs = gf_bs_new(start, remain, GF_BITSTREAM_READ); + } else { + gf_bs_reassign_buffer(ctx->bs, start, remain); + } + while (remain) { + u8 *sync; + Bool res; + u32 sync_pos, bytes_to_drop=0; + + + res = ctx->ac3_parser_bs(ctx->bs, &ctx->hdr, GF_TRUE); + + sync_pos = (u32) gf_bs_get_position(ctx->bs); + + //startcode not found or not enough bytes, gather more + if (!res || (remain < sync_pos + ctx->hdr.framesize)) + break; + + ac3dmx_check_pid(filter, ctx); + + if (!ctx->is_playing) { + ctx->resume_from = 1 + ctx->ac3_buffer_size - remain; + return GF_OK; + } + + if (sync_pos) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[AC3Dmx] %d bytes unrecovered before sync word\n", sync_pos)); + } + sync = start + sync_pos; + + if (ctx->in_seek) { + u64 nb_samples_at_seek = (u64) (ctx->start_range * ctx->hdr.sample_rate); + if (ctx->cts + AC3_FRAME_SIZE >= nb_samples_at_seek) { + //u32 samples_to_discard = (ctx->cts + ctx->dts_inc) - nb_samples_at_seek; + ctx->in_seek = GF_FALSE; + } + } + + bytes_to_drop = sync_pos + ctx->hdr.framesize; + if (ctx->timescale && !prev_pck_size && (cts != GF_FILTER_NO_TS) ) { + //trust input CTS if diff is more than one sec + if ((cts > ctx->cts + ctx->timescale) || (ctx->cts > cts + ctx->timescale)) + ctx->cts = cts; + cts = GF_FILTER_NO_TS; + } + + if (!ctx->in_seek) { + dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->hdr.framesize, &output); + if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, dst_pck); + + memcpy(output, sync, ctx->hdr.framesize); + gf_filter_pck_set_dts(dst_pck, ctx->cts); + gf_filter_pck_set_cts(dst_pck, ctx->cts); + gf_filter_pck_set_duration(dst_pck, AC3_FRAME_SIZE); + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + + if (ctx->byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, ctx->byte_offset + ctx->hdr.framesize); + } + + gf_filter_pck_send(dst_pck); + } + ac3dmx_update_cts(ctx); + + //truncated last frame + if (bytes_to_drop>remain) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[ADTSDmx] truncated AC3 frame!\n")); + bytes_to_drop=remain; + } + + if (!bytes_to_drop) { + bytes_to_drop = 1; + } + start += bytes_to_drop; + remain -= bytes_to_drop; + gf_bs_reassign_buffer(ctx->bs, start, remain); + + if (prev_pck_size) { + if (prev_pck_size > bytes_to_drop) prev_pck_size -= bytes_to_drop; + else { + prev_pck_size=0; + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = pck; + if (pck) + gf_filter_pck_ref_props(&ctx->src_pck); + } + } + if (ctx->byte_offset != GF_FILTER_NO_BO) + ctx->byte_offset += bytes_to_drop; + } + + if (!pck) { + ctx->ac3_buffer_size = 0; + return ac3dmx_process(filter); + } else { + if (remain) { + memmove(ctx->ac3_buffer, start, remain); + } + ctx->ac3_buffer_size = remain; + gf_filter_pid_drop_packet(ctx->ipid); + } + return GF_OK; +} + +static void ac3dmx_finalize(GF_Filter *filter) +{ + GF_AC3DmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs) gf_bs_del(ctx->bs); + if (ctx->ac3_buffer) gf_free(ctx->ac3_buffer); + if (ctx->indexes) gf_free(ctx->indexes); +} + +static const char *ac3dmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + u32 nb_frames=0; + Bool has_broken_frames = GF_FALSE; + u32 pos=0; + while (1) { + GF_AC3Header ahdr; + if (! gf_ac3_parser((u8 *) data, size, &pos, &ahdr, GF_FALSE) ) + break; + u32 fsize = ahdr.framesize; + if (pos) { + nb_frames=0; + has_broken_frames = GF_TRUE; + } + nb_frames++; + if (fsize > size+pos) break; + if (nb_frames>4) break; + if (size < fsize+pos) break; + size -= fsize+pos; + data += fsize+pos; + } + if (nb_frames>2) { + *score = has_broken_frames ? GF_FPROBE_MAYBE_SUPPORTED : GF_FPROBE_SUPPORTED; + return "audio/ac3"; + } + return NULL; +} + +static const GF_FilterCapability AC3DmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "ac3|eac3"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "audio/x-ac3|audio/ac3|audio/x-eac3|audio/eac3"), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_AC3), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_EAC3), + CAP_BOOL(GF_CAPS_OUTPUT_STATIC_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AC3), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_EAC3), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + +#define OFFS(_n) #_n, offsetof(GF_AC3DmxCtx, _n) +static const GF_FilterArgs AC3DmxArgs[] = +{ + { OFFS(index), "indexing window length", GF_PROP_DOUBLE, "1.0", NULL, 0}, + {0} +}; + + +GF_FilterRegister AC3DmxRegister = { + .name = "rfac3", + GF_FS_SET_DESCRIPTION("AC3 reframer") + GF_FS_SET_HELP("This filter parses AC3 and E-AC3 files/data and outputs corresponding audio PID and frames.") + .private_size = sizeof(GF_AC3DmxCtx), + .args = AC3DmxArgs, + .finalize = ac3dmx_finalize, + SETCAPS(AC3DmxCaps), + .configure_pid = ac3dmx_configure_pid, + .process = ac3dmx_process, + .probe_data = ac3dmx_probe_data, + .process_event = ac3dmx_process_event +}; + + +const GF_FilterRegister *ac3dmx_register(GF_FilterSession *session) +{ + return &AC3DmxRegister; +} + +#else + +const GF_FilterRegister *ac3dmx_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // GPAC_DISABLE_AV_PARSERS diff --git a/src/filters/reframe_adts.c b/src/filters/reframe_adts.c new file mode 100644 index 0000000..a66e8b2 --- /dev/null +++ b/src/filters/reframe_adts.c @@ -0,0 +1,911 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2020 + * All rights reserved + * + * This file is part of GPAC / AAC ADTS reframer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +#ifndef GPAC_DISABLE_AV_PARSERS + +enum +{ + AAC_SIGNAL_NONE=0, + AAC_SIGNAL_IMPLICIT, + AAC_SIGNAL_EXPLICIT +}; + +typedef struct +{ + Bool is_mp2, no_crc; + u32 profile, sr_idx, nb_ch, frame_size, hdr_size; +} ADTSHeader; + +typedef struct +{ + u64 pos; + Double duration; +} ADTSIdx; + +typedef struct +{ + //filter args + u32 frame_size; + Double index; + u32 sbr; + u32 ps; +// Bool mpeg4; + Bool ovsbr; + Bool expart; + + //only one input pid declared + GF_FilterPid *ipid; + //output pid for audio + GF_FilterPid *opid; + + //video pid for cover art + GF_FilterPid *vpid; + + GF_BitStream *bs; + u64 file_pos, cts; + u32 sr_idx, nb_ch, is_mp2, profile; + GF_Fraction64 duration; + Double start_range; + Bool in_seek; + u32 timescale; + + ADTSHeader hdr; + u32 dts_inc; + + Bool is_playing; + Bool is_file, file_loaded; + Bool initial_play_done; + + GF_FilterPacket *src_pck; + + ADTSIdx *indexes; + u32 index_alloc_size, index_size; + + u8 *adts_buffer; + u32 adts_buffer_size, adts_buffer_alloc, resume_from; + u64 byte_offset; + + u32 tag_size; + u8 *id3_buffer; + u32 id3_buffer_size, id3_buffer_alloc; + u32 nb_frames; +} GF_ADTSDmxCtx; + + +static Bool adts_dmx_sync_frame_bs(GF_BitStream *bs, ADTSHeader *hdr) +{ + u32 val; + u64 pos; + + while (gf_bs_available(bs)>7) { + val = gf_bs_read_u8(bs); + if (val!=0xFF) continue; + val = gf_bs_read_int(bs, 4); + if (val != 0x0F) { + gf_bs_read_int(bs, 4); + continue; + } + hdr->is_mp2 = (Bool)gf_bs_read_int(bs, 1); + gf_bs_read_int(bs, 2); + hdr->no_crc = (Bool)gf_bs_read_int(bs, 1); + pos = gf_bs_get_position(bs) - 2; + + hdr->profile = 1 + gf_bs_read_int(bs, 2); + hdr->sr_idx = gf_bs_read_int(bs, 4); + gf_bs_read_int(bs, 1); + hdr->nb_ch = gf_bs_read_int(bs, 3); + gf_bs_read_int(bs, 4); + hdr->frame_size = gf_bs_read_int(bs, 13); + gf_bs_read_int(bs, 11); + gf_bs_read_int(bs, 2); + hdr->hdr_size = 7; + if (!hdr->no_crc) { + gf_bs_read_u16(bs); + hdr->hdr_size = 9; + } + if (!GF_M4ASampleRates[hdr->sr_idx] || (hdr->frame_size < hdr->hdr_size)) { + gf_bs_seek(bs, pos+1); + continue; + } + hdr->frame_size -= hdr->hdr_size; + + if (gf_bs_available(bs) == hdr->frame_size) { + return GF_TRUE; + } + if (gf_bs_available(bs) < hdr->frame_size) { + break; + } + + gf_bs_skip_bytes(bs, hdr->frame_size); + val = gf_bs_read_u8(bs); + if (val!=0xFF) { + gf_bs_seek(bs, pos+1); + continue; + } + val = gf_bs_read_int(bs, 4); + if (val!=0x0F) { + gf_bs_read_int(bs, 4); + gf_bs_seek(bs, pos+1); + continue; + } + gf_bs_seek(bs, pos+hdr->hdr_size); + return GF_TRUE; + } + return GF_FALSE; +} + +void id3dmx_flush(GF_Filter *filter, u8 *id3_buf, u32 id3_buf_size, GF_FilterPid *audio_pid, GF_FilterPid **video_pid_p); + + +GF_Err adts_dmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_ADTSDmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) ctx->timescale = p->value.uint; + + if (ctx->timescale && !ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + //we don't update copy props on output for now - if we decide we need it, we will need to also force resengin the decoder config + } + + return GF_OK; +} + +static void adts_dmx_check_dur(GF_Filter *filter, GF_ADTSDmxCtx *ctx) +{ + FILE *stream; + GF_BitStream *bs; + ADTSHeader hdr; + u64 duration, cur_dur; + s32 sr_idx = -1; + const GF_PropertyValue *p; + if (!ctx->opid || ctx->timescale || ctx->file_loaded) return; + + if (ctx->index<=0) { + ctx->file_loaded = GF_TRUE; + return; + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) { + ctx->is_file = GF_FALSE; + ctx->file_loaded = GF_TRUE; + return; + } + ctx->is_file = GF_TRUE; + + stream = gf_fopen(p->value.string, "rb"); + if (!stream) return; + + ctx->index_size = 0; + + bs = gf_bs_from_file(stream, GF_BITSTREAM_READ); + duration = 0; + cur_dur = 0; + while (adts_dmx_sync_frame_bs(bs, &hdr)) { + if ((sr_idx>=0) && (sr_idx != hdr.sr_idx)) { + duration *= GF_M4ASampleRates[hdr.sr_idx]; + duration /= GF_M4ASampleRates[sr_idx]; + + cur_dur *= GF_M4ASampleRates[hdr.sr_idx]; + cur_dur /= GF_M4ASampleRates[sr_idx]; + } + sr_idx = hdr.sr_idx; + duration += ctx->frame_size; + cur_dur += ctx->frame_size; + if (cur_dur > ctx->index * GF_M4ASampleRates[sr_idx]) { + if (!ctx->index_alloc_size) ctx->index_alloc_size = 10; + else if (ctx->index_alloc_size == ctx->index_size) ctx->index_alloc_size *= 2; + ctx->indexes = gf_realloc(ctx->indexes, sizeof(ADTSIdx)*ctx->index_alloc_size); + ctx->indexes[ctx->index_size].pos = gf_bs_get_position(bs) - hdr.hdr_size; + ctx->indexes[ctx->index_size].duration = (Double) duration; + ctx->indexes[ctx->index_size].duration /= GF_M4ASampleRates[sr_idx]; + ctx->index_size ++; + cur_dur = 0; + } + + gf_bs_skip_bytes(bs, hdr.frame_size); + } + gf_bs_del(bs); + gf_fclose(stream); + + if (sr_idx>=0) { + if (!ctx->duration.num || (ctx->duration.num * GF_M4ASampleRates[sr_idx] != duration * ctx->duration.den)) { + ctx->duration.num = (s32) duration; + ctx->duration.den = GF_M4ASampleRates[sr_idx]; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + } + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, & PROP_BOOL(GF_TRUE ) ); +} + +static void adts_dmx_check_pid(GF_Filter *filter, GF_ADTSDmxCtx *ctx) +{ + GF_BitStream *dsi; + GF_M4ADecSpecInfo acfg; + u8 *dsi_b; + u32 i, sbr_sr_idx, dsi_s, sr, sbr_sr, codecid, timescale=0; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + adts_dmx_check_dur(filter, ctx); + } + + if ((ctx->sr_idx == ctx->hdr.sr_idx) && (ctx->nb_ch == ctx->hdr.nb_ch) + && (ctx->is_mp2 == ctx->hdr.is_mp2) && (ctx->profile == ctx->hdr.profile) ) return; + + //copy properties at init or reconfig + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT( GF_STREAM_AUDIO)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT( GF_CODECID_AAC_MPEG4)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLES_PER_FRAME, & PROP_UINT(ctx->frame_size) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, & PROP_BOOL(GF_FALSE) ); + if (ctx->is_file && ctx->index) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, & PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD) ); + } + + if (ctx->duration.num) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + + + ctx->is_mp2 = ctx->hdr.is_mp2; + ctx->nb_ch = ctx->hdr.nb_ch; + ctx->profile = ctx->hdr.profile; + + sr = GF_M4ASampleRates[ctx->hdr.sr_idx]; + if (!ctx->timescale) { + //we change sample rate, change cts + if (ctx->cts && (ctx->sr_idx != ctx->hdr.sr_idx)) { + ctx->cts *= sr; + ctx->cts /= GF_M4ASampleRates[ctx->sr_idx]; + } + } + ctx->sr_idx = ctx->hdr.sr_idx; + + /*keep MPEG-2 AAC codecid even for HE-SBR (that's correct according to latest MPEG-4 audio spec)*/ + codecid = ctx->hdr.is_mp2 ? ctx->hdr.profile+GF_CODECID_AAC_MPEG2_MP-1 : GF_CODECID_AAC_MPEG4; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(codecid) ); + + //force explicit SBR if explicit PS + if (ctx->ps==AAC_SIGNAL_EXPLICIT) { + ctx->sbr = AAC_SIGNAL_EXPLICIT; + } + /*no provision for explicit indication of MPEG-2 AAC through MPEG-4 PLs, so force implicit*/ + if (ctx->hdr.is_mp2) { + if (ctx->sbr == AAC_SIGNAL_EXPLICIT) ctx->sbr = AAC_SIGNAL_IMPLICIT; + if (ctx->ps == AAC_SIGNAL_EXPLICIT) ctx->ps = AAC_SIGNAL_IMPLICIT; + } + + dsi = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + ctx->dts_inc = ctx->frame_size; + + sbr_sr = sr; + sbr_sr_idx = ctx->hdr.sr_idx; + if (!ctx->ovsbr) { + for (i=0; i<16; i++) { + if (GF_M4ASampleRates[i] == (u32) 2*sr) { + sbr_sr_idx = i; + sbr_sr = 2*sr; + break; + } + } + } + + memset(&acfg, 0, sizeof(GF_M4ADecSpecInfo)); + acfg.base_object_type = ctx->hdr.profile; + acfg.base_sr = sr; + acfg.nb_chan = gf_m4a_get_channel_cfg(ctx->hdr.nb_ch); + acfg.sbr_object_type = 0; + if (ctx->sbr==AAC_SIGNAL_EXPLICIT) { + acfg.has_sbr = GF_TRUE; + acfg.base_object_type = 5; + acfg.sbr_object_type = ctx->hdr.profile; + + /*for better interop, always store using full SR when using explict signaling*/ + ctx->dts_inc *= 2; + sr = sbr_sr; + } else if (ctx->sbr==AAC_SIGNAL_IMPLICIT) { + acfg.has_sbr = GF_TRUE; + } + if (ctx->ps==AAC_SIGNAL_EXPLICIT) { + acfg.has_ps = GF_TRUE; + acfg.base_object_type = 29; + } else if (ctx->ps==AAC_SIGNAL_IMPLICIT) { + acfg.has_ps = GF_TRUE; + } + + acfg.audioPL = gf_m4a_get_profile(&acfg); + /*explicit SBR or PS signal (non backward-compatible)*/ + if (ctx->ps==AAC_SIGNAL_EXPLICIT) { + gf_bs_write_int(dsi, 29, 5); + gf_bs_write_int(dsi, ctx->hdr.sr_idx, 4); + gf_bs_write_int(dsi, ctx->hdr.nb_ch, 4); + gf_bs_write_int(dsi, sbr_sr ? sbr_sr_idx : ctx->hdr.sr_idx, 4); + gf_bs_write_int(dsi, ctx->hdr.profile, 5); + } + /*explicit SBR signal (non backward-compatible)*/ + else if (ctx->sbr==AAC_SIGNAL_EXPLICIT) { + gf_bs_write_int(dsi, 5, 5); + gf_bs_write_int(dsi, ctx->hdr.sr_idx, 4); + gf_bs_write_int(dsi, ctx->hdr.nb_ch, 4); + gf_bs_write_int(dsi, sbr_sr ? sbr_sr_idx : ctx->hdr.sr_idx, 4); + gf_bs_write_int(dsi, ctx->hdr.profile, 5); + } else { + /*regular AAC*/ + gf_bs_write_int(dsi, ctx->hdr.profile, 5); + gf_bs_write_int(dsi, ctx->hdr.sr_idx, 4); + gf_bs_write_int(dsi, ctx->hdr.nb_ch, 4); + gf_bs_align(dsi); + /*implicit AAC SBR signal*/ + if (ctx->sbr==AAC_SIGNAL_IMPLICIT) { + gf_bs_write_int(dsi, 0x2b7, 11); /*sync extension type*/ + gf_bs_write_int(dsi, 5, 5); /*SBR objectType*/ + gf_bs_write_int(dsi, 1, 1); /*SBR present flag*/ + gf_bs_write_int(dsi, sbr_sr_idx, 4); + } + if (ctx->ps==AAC_SIGNAL_IMPLICIT) { + gf_bs_write_int(dsi, 0x548, 11); /*sync extension type*/ + gf_bs_write_int(dsi, 1, 1); /* PS present flag */ + } + } + gf_bs_align(dsi); + + gf_bs_get_content(dsi, &dsi_b, &dsi_s); + gf_bs_del(dsi); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, & PROP_DATA_NO_COPY(dsi_b, dsi_s) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PROFILE_LEVEL, & PROP_UINT (acfg.audioPL) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, & PROP_UINT(sr)); + + timescale = sr; + if (ctx->ovsbr) timescale = 2*sr; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->timescale ? ctx->timescale : timescale)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, & PROP_UINT(ctx->nb_ch) ); + + if (ctx->id3_buffer_size) { + id3dmx_flush(filter, ctx->id3_buffer, ctx->id3_buffer_size, ctx->opid, ctx->expart ? &ctx->vpid : NULL); + ctx->id3_buffer_size = 0; + } + +} + +static Bool adts_dmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i; + GF_FilterEvent fevt; + GF_ADTSDmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (!ctx->is_playing) { + ctx->is_playing = GF_TRUE; + ctx->cts = 0; + } + ctx->nb_frames = 0; + ctx->id3_buffer_size = 0; + + if (! ctx->is_file) { + if (evt->play.start_range || ctx->initial_play_done) { + ctx->adts_buffer_size = 0; + ctx->resume_from = 0; + } + + ctx->initial_play_done = GF_TRUE; + return GF_FALSE; + } + ctx->start_range = evt->play.start_range; + ctx->in_seek = GF_TRUE; + ctx->file_pos = 0; + if (ctx->start_range) { + for (i=1; iindex_size; i++) { + if (ctx->indexes[i].duration>ctx->start_range) { + ctx->cts = (u64) (ctx->indexes[i-1].duration * GF_M4ASampleRates[ctx->sr_idx]); + ctx->file_pos = ctx->indexes[i-1].pos; + break; + } + } + } + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!ctx->file_pos) + return GF_TRUE; + } + ctx->resume_from = 0; + ctx->adts_buffer_size = 0; + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = ctx->file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + //don't cancel event + ctx->is_playing = GF_FALSE; + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +static GFINLINE void adts_dmx_update_cts(GF_ADTSDmxCtx *ctx) +{ + assert(ctx->dts_inc); + + if (ctx->timescale) { + u64 inc = ctx->dts_inc; + inc *= ctx->timescale; + inc /= GF_M4ASampleRates[ctx->sr_idx]; + ctx->cts += inc; + } else { + ctx->cts += ctx->dts_inc; + } +} + +GF_Err adts_dmx_process(GF_Filter *filter) +{ + GF_ADTSDmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + u8 *data, *output; + u8 *start; + u32 pck_size, remain, prev_pck_size; + u64 cts = GF_FILTER_NO_TS; + + //always reparse duration + if (!ctx->duration.num) + adts_dmx_check_dur(filter, ctx); + + if (ctx->opid && !ctx->is_playing) + return GF_OK; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (!ctx->adts_buffer_size) { + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = NULL; + return GF_EOS; + } + } else { + return GF_OK; + } + } + + prev_pck_size = ctx->adts_buffer_size; + if (pck && !ctx->resume_from) { + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + if (!pck_size) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + + if (ctx->byte_offset != GF_FILTER_NO_BO) { + u64 byte_offset = gf_filter_pck_get_byte_offset(pck); + if (!ctx->adts_buffer_size) { + ctx->byte_offset = byte_offset; + } else if (ctx->byte_offset + ctx->adts_buffer_size != byte_offset) { + ctx->byte_offset = GF_FILTER_NO_BO; + if ((byte_offset != GF_FILTER_NO_BO) && (byte_offset>ctx->adts_buffer_size) ) { + ctx->byte_offset = byte_offset - ctx->adts_buffer_size; + } + } + } + + if (ctx->adts_buffer_size + pck_size > ctx->adts_buffer_alloc) { + ctx->adts_buffer_alloc = ctx->adts_buffer_size + pck_size; + ctx->adts_buffer = gf_realloc(ctx->adts_buffer, ctx->adts_buffer_alloc); + } + memcpy(ctx->adts_buffer + ctx->adts_buffer_size, data, pck_size); + ctx->adts_buffer_size += pck_size; + } + + //input pid sets some timescale - we flushed pending data , update cts + if (ctx->timescale && pck) { + cts = gf_filter_pck_get_cts(pck); + } + + if (cts == GF_FILTER_NO_TS) { + //avoids updating cts + prev_pck_size = 0; + } + + remain = ctx->adts_buffer_size; + start = ctx->adts_buffer; + + if (ctx->resume_from) { + start += ctx->resume_from - 1; + remain -= ctx->resume_from - 1; + ctx->resume_from = 0; + } + + while (remain) { + u8 *sync; + u32 sync_pos, size, offset, bytes_to_drop=0; + + if (!ctx->tag_size && (remain>3)) { + + /* Did we read an ID3v2 ? */ + if (start[0] == 'I' && start[1] == 'D' && start[2] == '3') { + if (remain<10) + return GF_OK; + + ctx->tag_size = ((start[9] & 0x7f) + ((start[8] & 0x7f) << 7) + ((start[7] & 0x7f) << 14) + ((start[6] & 0x7f) << 21)); + + bytes_to_drop = 10; + if (ctx->id3_buffer_alloc < ctx->tag_size+10) { + ctx->id3_buffer = gf_realloc(ctx->id3_buffer, ctx->tag_size+10); + ctx->id3_buffer_alloc = ctx->tag_size+10; + } + memcpy(ctx->id3_buffer, start, 10); + ctx->id3_buffer_size = 10; + goto drop_byte; + } + } + if (ctx->tag_size) { + if (ctx->tag_size>remain) { + bytes_to_drop = remain; + ctx->tag_size-=remain; + } else { + bytes_to_drop = ctx->tag_size; + ctx->tag_size = 0; + } + memcpy(ctx->id3_buffer + ctx->id3_buffer_size, start, bytes_to_drop); + ctx->id3_buffer_size += bytes_to_drop; + + if (!ctx->tag_size && ctx->opid) { + id3dmx_flush(filter, ctx->id3_buffer, ctx->id3_buffer_size, ctx->opid, ctx->expart ? &ctx->vpid : NULL); + ctx->id3_buffer_size = 0; + } + goto drop_byte; + + } + + sync = memchr(start, 0xFF, remain); + sync_pos = (u32) (sync ? sync - start : remain); + + //couldn't find sync byte in this packet + if (remain - sync_pos < 7) { + break; + } + + //not sync ! + if ((sync[1] & 0xF0) != 0xF0) { + GF_LOG(ctx->nb_frames ? GF_LOG_WARNING : GF_LOG_DEBUG, GF_LOG_PARSER, ("[ADTSDmx] invalid ADTS sync bytes, resyncing\n")); + ctx->nb_frames = 0; + goto drop_byte; + } + if (!ctx->bs) { + ctx->bs = gf_bs_new(sync + 1, remain - sync_pos - 1, GF_BITSTREAM_READ); + } else { + gf_bs_reassign_buffer(ctx->bs, sync + 1, remain - sync_pos - 1); + } + + //ok parse header + gf_bs_read_int(ctx->bs, 4); + + ctx->hdr.is_mp2 = (Bool)gf_bs_read_int(ctx->bs, 1); + //if (ctx->mpeg4) + //we deprecate old MPEG-2 signaling for AAC in ISOBMFF, as it is not well supported anyway and we don't write adif_header as + //supposed to be for these types + ctx->hdr.is_mp2 = 0; + + gf_bs_read_int(ctx->bs, 2); + ctx->hdr.no_crc = (Bool)gf_bs_read_int(ctx->bs, 1); + + ctx->hdr.profile = 1 + gf_bs_read_int(ctx->bs, 2); + ctx->hdr.sr_idx = gf_bs_read_int(ctx->bs, 4); + gf_bs_read_int(ctx->bs, 1); + ctx->hdr.nb_ch = gf_bs_read_int(ctx->bs, 3); + gf_bs_read_int(ctx->bs, 4); + ctx->hdr.frame_size = gf_bs_read_int(ctx->bs, 13); + gf_bs_read_int(ctx->bs, 11); + gf_bs_read_int(ctx->bs, 2); + ctx->hdr.hdr_size = 7; + + if (!ctx->hdr.no_crc) { + ctx->hdr.hdr_size = 9; + } + + if (!ctx->hdr.frame_size || !GF_M4ASampleRates[ctx->hdr.sr_idx] || !ctx->hdr.nb_ch) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_PARSER, ("[ADTSDmx] invalid ADTS frame header, resyncing\n")); + ctx->nb_frames = 0; + goto drop_byte; + } + + //ready to send packet + if (ctx->hdr.frame_size + 1 < remain) { + u32 next_frame = ctx->hdr.frame_size; + //make sure we are sync! + if ((sync[next_frame] !=0xFF) || ((sync[next_frame+1] & 0xF0) !=0xF0) ) { + GF_LOG(ctx->nb_frames ? GF_LOG_WARNING : GF_LOG_DEBUG, GF_LOG_PARSER, ("[ADTSDmx] invalid next ADTS frame sync, resyncing\n")); + ctx->nb_frames = 0; + goto drop_byte; + } + } + //otherwise wait for next frame, unless if end of stream + else if (pck) { + if (ctx->timescale && !prev_pck_size && (cts != GF_FILTER_NO_TS) ) { + ctx->cts = cts; + } + break; + } + + adts_dmx_check_pid(filter, ctx); + + if (!ctx->is_playing) { + ctx->resume_from = 1 + ctx->adts_buffer_size - remain; + return GF_OK; + } + + ctx->nb_frames++; + size = ctx->hdr.frame_size - ctx->hdr.hdr_size; + offset = ctx->hdr.hdr_size; + + if (ctx->in_seek) { + u64 nb_samples_at_seek = (u64) (ctx->start_range * GF_M4ASampleRates[ctx->sr_idx]); + if (ctx->cts + ctx->dts_inc >= nb_samples_at_seek) { + //u32 samples_to_discard = (ctx->cts + ctx->dts_inc) - nb_samples_at_seek; + ctx->in_seek = GF_FALSE; + } + } + + bytes_to_drop = ctx->hdr.frame_size; + if (ctx->timescale && !prev_pck_size && (cts != GF_FILTER_NO_TS) ) { + ctx->cts = cts; + cts = GF_FILTER_NO_TS; + } + + if (!ctx->in_seek) { + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, dst_pck); + + memcpy(output, sync + offset, size); + gf_filter_pck_set_dts(dst_pck, ctx->cts); + gf_filter_pck_set_cts(dst_pck, ctx->cts); + gf_filter_pck_set_duration(dst_pck, ctx->dts_inc); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + + if (ctx->byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, ctx->byte_offset + ctx->hdr.hdr_size); + } + + gf_filter_pck_send(dst_pck); + } + adts_dmx_update_cts(ctx); + + + //truncated last frame + if (bytes_to_drop>remain) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[ADTSDmx] truncated ADTS frame!\n")); + bytes_to_drop=remain; + } + +drop_byte: + if (!bytes_to_drop) { + bytes_to_drop = 1; + } + start += bytes_to_drop; + remain -= bytes_to_drop; + + if (prev_pck_size) { + if (prev_pck_size > bytes_to_drop) prev_pck_size -= bytes_to_drop; + else { + prev_pck_size=0; + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = pck; + if (pck) + gf_filter_pck_ref_props(&ctx->src_pck); + } + } + if (ctx->byte_offset != GF_FILTER_NO_BO) + ctx->byte_offset += bytes_to_drop; + } + + if (!pck) { + ctx->adts_buffer_size = 0; + return adts_dmx_process(filter); + } else { + if (remain) { + memmove(ctx->adts_buffer, start, remain); + } + ctx->adts_buffer_size = remain; + gf_filter_pid_drop_packet(ctx->ipid); + } + return GF_OK; +} + +static void adts_dmx_finalize(GF_Filter *filter) +{ + GF_ADTSDmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs) gf_bs_del(ctx->bs); + if (ctx->indexes) gf_free(ctx->indexes); + if (ctx->adts_buffer) gf_free(ctx->adts_buffer); + if (ctx->id3_buffer) gf_free(ctx->id3_buffer); +} + +static const char *adts_dmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + u32 nb_frames=0, next_pos=0, max_consecutive_frames=0; + ADTSHeader prev_hdr; + GF_BitStream *bs; + Bool has_id3=GF_FALSE; + Bool has_broken_data=GF_FALSE; + + /*check for id3*/ + if (size>= 10) { + if (data[0] == 'I' && data[1] == 'D' && data[2] == '3') { + u32 tag_size = ((data[9] & 0x7f) + ((data[8] & 0x7f) << 7) + ((data[7] & 0x7f) << 14) + ((data[6] & 0x7f) << 21)); + + if (tag_size+10 > size) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("ID3 tag detected size %d but probe data only %d bytes, will rely on file extension (try increasing probe size using --block_size)\n", tag_size+10, size)); + *score = GF_FPROBE_EXT_MATCH; + return "aac|adts"; + } + data += tag_size+10; + size -= tag_size+10; + has_id3 = GF_TRUE; + } + } + + bs = gf_bs_new(data, size, GF_BITSTREAM_READ); + memset(&prev_hdr, 0, sizeof(ADTSHeader)); + while (gf_bs_available(bs)) { + ADTSHeader hdr; + u32 pos; + hdr.frame_size = 0; + if (!adts_dmx_sync_frame_bs(bs, &hdr)) { + if (hdr.frame_size) { + //nb_frames++; + max_consecutive_frames++; + } + break; + } + if ((hdr.hdr_size!=7) && (hdr.hdr_size!=9)) continue; + if (!hdr.nb_ch) continue; + pos = (u32) gf_bs_get_position(bs); + if ((next_pos + hdr.hdr_size == pos) && (hdr.sr_idx==prev_hdr.sr_idx) && (hdr.nb_ch==prev_hdr.nb_ch) ) { + nb_frames++; + if (max_consecutive_frames5) + break; + } else { + nb_frames=1; + has_broken_data=GF_TRUE; + } + prev_hdr = hdr; + gf_bs_skip_bytes(bs, hdr.frame_size); + next_pos = (u32) gf_bs_get_position(bs); + } + gf_bs_del(bs); + if (max_consecutive_frames>=4) { + *score = has_broken_data ? GF_FPROBE_MAYBE_SUPPORTED : GF_FPROBE_SUPPORTED; + return "audio/aac"; + } + if (has_id3 && max_consecutive_frames) { + *score = GF_FPROBE_MAYBE_SUPPORTED; + return "audio/aac"; + } + return NULL; +} + +static const GF_FilterCapability ADTSDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "aac|adts"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "audio/x-m4a|audio/aac|audio/aacp|audio/x-aac"), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG4), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_MP), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_LCP), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_SSRP), + //we explitely set this one to prevent adts->latm reframer connection + CAP_BOOL(GF_CAPS_OUTPUT_STATIC_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG4), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + + +#define OFFS(_n) #_n, offsetof(GF_ADTSDmxCtx, _n) +static const GF_FilterArgs ADTSDmxArgs[] = +{ + { OFFS(frame_size), "size of AAC frame in audio samples", GF_PROP_UINT, "1024", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(index), "indexing window length", GF_PROP_DOUBLE, "1.0", NULL, 0}, +// { OFFS(mpeg4), "force signaling as MPEG-4 AAC", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ovsbr), "force oversampling SBR (does not multiply timescales by 2)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(sbr), "set SBR signaling\n"\ + "- no: no SBR signaling at all\n"\ + "- imp: backward-compatible SBR signaling (audio signaled as AAC-LC)\n"\ + "- exp: explicit SBR signaling (audio signaled as AAC-SBR)"\ + , GF_PROP_UINT, "no", "no|imp|exp", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(ps), "set PS signaling\n"\ + "- no: no PS signaling at all\n"\ + "- imp: backward-compatible PS signaling (audio signaled as AAC-LC)\n"\ + "- exp: explicit PS signaling (audio signaled as AAC-PS)"\ + , GF_PROP_UINT, "no", "no|imp|exp", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(expart), "expose pictures as a dedicated video pid", GF_PROP_BOOL, "false", NULL, 0}, + {0} +}; + + +GF_FilterRegister ADTSDmxRegister = { + .name = "rfadts", + GF_FS_SET_DESCRIPTION("ADTS reframer") + GF_FS_SET_HELP("This filter parses AAC files/data and outputs corresponding audio PID and frames.") + .private_size = sizeof(GF_ADTSDmxCtx), + .args = ADTSDmxArgs, + .finalize = adts_dmx_finalize, + SETCAPS(ADTSDmxCaps), + .configure_pid = adts_dmx_configure_pid, + .process = adts_dmx_process, + .probe_data = adts_dmx_probe_data, + .process_event = adts_dmx_process_event +}; + + +const GF_FilterRegister *adts_dmx_register(GF_FilterSession *session) +{ + return &ADTSDmxRegister; +} + +#else +const GF_FilterRegister *adts_dmx_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // GPAC_DISABLE_AV_PARSERS diff --git a/src/filters/reframe_amr.c b/src/filters/reframe_amr.c new file mode 100644 index 0000000..6a14fa7 --- /dev/null +++ b/src/filters/reframe_amr.c @@ -0,0 +1,603 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2020 + * All rights reserved + * + * This file is part of GPAC / AMR&EVRC&SMV reframer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include + +typedef struct +{ + u64 pos; + Double duration; +} AMRIdx; + +typedef struct +{ + //filter args + Double index; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + u32 start_offset; + u32 codecid, sample_rate, block_size; + + + u64 file_pos, cts; + + u16 amr_mode_set; + + GF_Fraction64 duration; + Double start_range; + Bool in_seek; + u32 timescale; + Bool is_playing; + Bool is_file; + Bool initial_play_done, file_loaded; + Bool skip_magic; + + u32 hdr; + u32 resume_from; + u32 remaining; + + AMRIdx *indexes; + u32 index_alloc_size, index_size; +} GF_AMRDmxCtx; + + + + +GF_Err amrdmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_AMRDmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) ctx->timescale = p->value.uint; + + ctx->start_offset = 6; + ctx->sample_rate = 8000; + ctx->block_size = 160; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (p) { + if (ctx->codecid && (ctx->codecid != p->value.uint)) { + return GF_NOT_SUPPORTED; + } + ctx->codecid = p->value.uint; + if (ctx->codecid == GF_CODECID_AMR_WB) { + ctx->sample_rate = 16000; + ctx->block_size = 320; + } + ctx->skip_magic = GF_FALSE; + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + } + } + return GF_OK; +} + +static void amrdmx_check_dur(GF_Filter *filter, GF_AMRDmxCtx *ctx) +{ + FILE *stream; + u32 i; + u64 duration, cur_dur; + char magic[20]; + const GF_PropertyValue *p; + if (!ctx->opid || ctx->timescale || ctx->file_loaded) return; + + if (ctx->index<=0) { + ctx->file_loaded = GF_TRUE; + return; + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) { + ctx->is_file = GF_FALSE; + ctx->file_loaded = GF_TRUE; + return; + } + ctx->is_file = GF_TRUE; + + stream = gf_fopen(p->value.string, "rb"); + if (!stream) return; + + ctx->codecid = GF_CODECID_NONE; + ctx->start_offset = 6; + ctx->sample_rate = 8000; + ctx->block_size = 160; + i = (u32) gf_fread(magic, 20, stream); + if (i != 20) return; + + if (!strnicmp(magic, "#!AMR\n", 6)) { + gf_fseek(stream, 6, SEEK_SET); + ctx->codecid = GF_CODECID_AMR; + } + else if (!strnicmp(magic, "#!EVRC\n", 7)) { + gf_fseek(stream, 7, SEEK_SET); + ctx->start_offset = 7; + ctx->codecid = GF_CODECID_EVRC; + } + else if (!strnicmp(magic, "#!SMV\n", 6)) { + gf_fseek(stream, 6, SEEK_SET); + ctx->codecid = GF_CODECID_SMV; + } + else if (!strnicmp(magic, "#!AMR-WB\n", 9)) { + ctx->codecid = GF_CODECID_AMR_WB; + ctx->start_offset = 9; + ctx->sample_rate = 16000; + ctx->block_size = 320; + gf_fseek(stream, 9, SEEK_SET); + } + else if (!strnicmp(magic, "#!AMR_MC1.0\n", 12)) return; + else if (!strnicmp(magic, "#!AMR-WB_MC1.0\n", 15)) return; + else return; + + ctx->index_size = 0; + + cur_dur = 0; + duration = 0; + while (!gf_feof(stream)) { + u32 size=0; + u64 pos; + u8 toc, ft; + toc = gf_fgetc(stream); + + switch (ctx->codecid) { + case GF_CODECID_AMR: + ft = (toc >> 3) & 0x0F; + size = (u32)GF_AMR_FRAME_SIZE[ft]; + break; + case GF_CODECID_AMR_WB: + ft = (toc >> 3) & 0x0F; + size = (u32)GF_AMR_WB_FRAME_SIZE[ft]; + break; + default: + for (i=0; iblock_size; + cur_dur += ctx->block_size; + pos = gf_ftell(stream); + if (cur_dur > ctx->index * ctx->sample_rate) { + if (!ctx->index_alloc_size) ctx->index_alloc_size = 10; + else if (ctx->index_alloc_size == ctx->index_size) ctx->index_alloc_size *= 2; + ctx->indexes = gf_realloc(ctx->indexes, sizeof(AMRIdx)*ctx->index_alloc_size); + ctx->indexes[ctx->index_size].pos = pos - 1; + ctx->indexes[ctx->index_size].duration = (Double) duration; + ctx->indexes[ctx->index_size].duration /= ctx->sample_rate; + ctx->index_size ++; + cur_dur = 0; + } + if (size) gf_fseek(stream, size, SEEK_CUR); + } + gf_fclose(stream); + + if (!ctx->duration.num || (ctx->duration.num * ctx->sample_rate != duration * ctx->duration.den)) { + ctx->duration.num = (u32) duration; + ctx->duration.den = ctx->sample_rate; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, & PROP_BOOL(GF_TRUE ) ); +} + +static void amrdmx_check_pid(GF_Filter *filter, GF_AMRDmxCtx *ctx, u16 amr_mode_set) +{ + if (ctx->opid) { + if (ctx->amr_mode_set != amr_mode_set) { + ctx->amr_mode_set = amr_mode_set; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_AMR_MODE_SET, & PROP_UINT( amr_mode_set)); + } + return; + } + + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT( GF_STREAM_AUDIO)); + + amrdmx_check_dur(filter, ctx); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->sample_rate)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, & PROP_UINT(ctx->sample_rate)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, & PROP_UINT(1) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(ctx->codecid ) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLES_PER_FRAME, & PROP_UINT(ctx->block_size ) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_AMR_MODE_SET, & PROP_UINT(ctx->amr_mode_set)); + + if (ctx->is_file && ctx->index) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, & PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD) ); + } +} + +static Bool amrdmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i; + GF_FilterEvent fevt; + GF_AMRDmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (!ctx->is_playing) { + ctx->is_playing = GF_TRUE; + ctx->cts = 0; + ctx->remaining = 0; + } + if (! ctx->is_file) { + return GF_FALSE; + } + amrdmx_check_dur(filter, ctx); + + ctx->start_range = evt->play.start_range; + ctx->in_seek = GF_TRUE; + ctx->file_pos = 0; + if (ctx->start_range) { + for (i=1; iindex_size; i++) { + if (ctx->indexes[i].duration>ctx->start_range) { + ctx->cts = (u64) (ctx->indexes[i-1].duration * ctx->sample_rate); + ctx->file_pos = ctx->indexes[i-1].pos; + break; + } + } + } + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!ctx->file_pos) { + ctx->skip_magic = GF_TRUE; + return GF_TRUE; + } + } + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + if (!ctx->file_pos) + ctx->skip_magic = GF_TRUE; + fevt.seek.start_offset = ctx->file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + ctx->is_playing = GF_FALSE; + //don't cancel event + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +static GFINLINE void amrdmx_update_cts(GF_AMRDmxCtx *ctx) +{ + if (ctx->timescale) { + u64 inc = ctx->block_size; + inc *= ctx->timescale; + inc /= ctx->sample_rate; + ctx->cts += inc; + } else { + ctx->cts += ctx->block_size; + } +} + +GF_Err amrdmx_process(GF_Filter *filter) +{ + GF_AMRDmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + u64 byte_offset; + u8 *data, *output; + u8 *start; + u32 pck_size, remain; + + //update duration + amrdmx_check_dur(filter, ctx); + + if (ctx->opid && !ctx->is_playing) + return GF_OK; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + assert(ctx->remaining == 0); + return GF_EOS; + } + return GF_OK; + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + byte_offset = gf_filter_pck_get_byte_offset(pck); + + start = data; + remain = pck_size; + + //flush not previously dispatched data + if (ctx->remaining) { + u32 to_send = ctx->remaining; + if (ctx->remaining > pck_size) { + to_send = pck_size; + ctx->remaining -= pck_size; + } else { + ctx->remaining = 0; + } + if (! ctx->in_seek) { + dst_pck = gf_filter_pck_new_alloc(ctx->opid, to_send, &output); + memcpy(output, data, to_send); + + gf_filter_pck_set_cts(dst_pck, ctx->cts); + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + gf_filter_pck_set_framing(dst_pck, GF_FALSE, ctx->remaining ? GF_FALSE : GF_TRUE); + if (byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, byte_offset); + } + gf_filter_pck_send(dst_pck); + } + + if (ctx->remaining) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + amrdmx_update_cts(ctx); + start += to_send; + remain -= to_send; + } + + //input pid sets some timescale - we flushed pending data , update cts + if (ctx->timescale) { + u64 cts = gf_filter_pck_get_cts(pck); + if (cts != GF_FILTER_NO_TS) + ctx->cts = cts; + } + if (ctx->skip_magic) { + + if (!strnicmp(start, "#!AMR\n", 6)) { + ctx->start_offset = 6; + ctx->codecid = GF_CODECID_AMR; + } + else if (!strnicmp(start, "#!EVRC\n", 7)) { + ctx->start_offset = 7; + ctx->codecid = GF_CODECID_EVRC; + } + else if (!strnicmp(start, "#!SMV\n", 6)) { + ctx->start_offset = 6; + ctx->codecid = GF_CODECID_SMV; + } + else if (!strnicmp(start, "#!AMR-WB\n", 9)) { + ctx->codecid = GF_CODECID_AMR_WB; + ctx->start_offset = 9; + ctx->sample_rate = 16000; + ctx->block_size = 320; + } + start += ctx->start_offset; + remain -= ctx->start_offset; + } + if (ctx->resume_from) { + start += ctx->resume_from; + remain -= ctx->resume_from; + ctx->resume_from = 0; + } + + + while (remain) { + u8 toc, ft; + u16 amr_mode_set = ctx->amr_mode_set; + u32 size=0, i; + + toc = start[0]; + if (!toc) { + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[AMRDmx] Could not find TOC word in packet, droping\n")); + break; + } + switch (ctx->codecid) { + case GF_CODECID_AMR: + ft = (toc >> 3) & 0x0F; + + /*update mode set (same mechanism for both AMR and AMR-WB*/ + amr_mode_set |= (1<> 3) & 0x0F; + size = (u32)GF_AMR_WB_FRAME_SIZE[ft]; + + /*update mode set (same mechanism for both AMR and AMR-WB*/ + amr_mode_set |= (1<is_playing) return GF_OK; + size++; + if (size > remain) { + ctx->remaining = size - remain; + size = remain; + } + + if (ctx->in_seek) { + u64 nb_samples_at_seek = (u64) (ctx->start_range * ctx->sample_rate); + if (ctx->cts + ctx->block_size >= nb_samples_at_seek) { + //u32 samples_to_discard = (ctx->cts + ctx->block_size ) - nb_samples_at_seek; + ctx->in_seek = GF_FALSE; + } + } + if (!ctx->in_seek) { + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + + memcpy(output, start, size); + + gf_filter_pck_set_cts(dst_pck, ctx->cts); + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + gf_filter_pck_set_duration(dst_pck, ctx->block_size); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, ctx->remaining ? GF_FALSE : GF_TRUE); + + if (byte_offset != GF_FILTER_NO_BO) { + u64 boffset = byte_offset; + boffset += start - data; + gf_filter_pck_set_byte_offset(dst_pck, boffset); + } + + gf_filter_pck_send(dst_pck); + } + start += size; + remain -= size; + + ctx->skip_magic = 0; + if (ctx->remaining) break; + amrdmx_update_cts(ctx); + + //don't demux too much of input, abort when we would block. This avoid dispatching + //a huge number of frames in a single call + if (gf_filter_pid_would_block(ctx->opid)) { + ctx->resume_from = (u32) ( (char *)start - (char *)data); + return GF_OK; + } + } + gf_filter_pid_drop_packet(ctx->ipid); + + return GF_OK; +} + +static GF_Err amrdmx_initialize(GF_Filter *filter) +{ + GF_AMRDmxCtx *ctx = gf_filter_get_udta(filter); + ctx->skip_magic = GF_TRUE; + return GF_OK; +} + +static void amrdmx_finalize(GF_Filter *filter) +{ + GF_AMRDmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->indexes) gf_free(ctx->indexes); +} + +static const char * amrdmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + if (!strnicmp(data, "#!AMR\n", 6)) { + *score = GF_FPROBE_SUPPORTED; + return "audio/amr"; + } + else if (!strnicmp(data, "#!AMR-WB\n", 9)) { + *score = GF_FPROBE_SUPPORTED; + return "audio/amr"; + } + else if (!strnicmp(data, "#!EVRC\n", 7)) { + *score = GF_FPROBE_SUPPORTED; + return "audio/evrc"; + } + else if (!strnicmp(data, "#!SMV\n", 6)) { + *score = GF_FPROBE_SUPPORTED; + return "audio/smv"; + } + return NULL; +} + +static const GF_FilterCapability AMRDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "amr|awb|evc|smv"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "audio/amr|audio/evrc|audio/smv"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_AMR), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_AMR_WB), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_SMV), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_EVRC), + CAP_BOOL(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + +#define OFFS(_n) #_n, offsetof(GF_AMRDmxCtx, _n) +static const GF_FilterArgs AMRDmxArgs[] = +{ + { OFFS(index), "indexing window length", GF_PROP_DOUBLE, "1.0", NULL, 0}, + {0} +}; + + +GF_FilterRegister AMRDmxRegister = { + .name = "rfamr", + GF_FS_SET_DESCRIPTION("AMR/EVRC reframer") + GF_FS_SET_HELP("This filter parses AMR, AMR Wideband, EVRC and SMV files/data and outputs corresponding audio PID and frames.") + .private_size = sizeof(GF_AMRDmxCtx), + .args = AMRDmxArgs, + .initialize = amrdmx_initialize, + .finalize = amrdmx_finalize, + SETCAPS(AMRDmxCaps), + .configure_pid = amrdmx_configure_pid, + .process = amrdmx_process, + .probe_data = amrdmx_probe_data, + .process_event = amrdmx_process_event +}; + + +const GF_FilterRegister *amrdmx_register(GF_FilterSession *session) +{ + return &AMRDmxRegister; +} + diff --git a/src/filters/reframe_av1.c b/src/filters/reframe_av1.c new file mode 100644 index 0000000..dd4cff4 --- /dev/null +++ b/src/filters/reframe_av1.c @@ -0,0 +1,1114 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Romain Bouqueau, Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018-2019 + * All rights reserved + * + * This file is part of GPAC / AV1 IVF/OBU/annexB reframer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_AV_PARSERS + +typedef struct +{ + u64 pos; + Double duration; +} AV1Idx; + +typedef enum { + NOT_SET, /*Section 5*/ + OBUs, /*Section 5*/ + AnnexB, + IVF, + UNSUPPORTED +} AV1BitstreamSyntax; + +typedef struct +{ + //filter args + GF_Fraction fps; + Double index; + Bool importer; + Bool deps; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + AV1BitstreamSyntax bsmode; + + GF_BitStream *bs; + u64 cts; + u32 width, height; + GF_Fraction64 duration; + Double start_range; + Bool in_seek; + u32 timescale; + GF_Fraction cur_fps; + + u32 resume_from; + + char *buffer; + u32 buf_size, alloc_size; + + //ivf header for now + u32 file_hdr_size; + + Bool is_av1; + Bool is_vp9; + Bool is_vpX; + u32 codecid; + GF_VPConfig *vp_cfg; + + Bool is_playing; + Bool is_file, file_loaded; + Bool initial_play_done; + + GF_FilterPacket *src_pck; + + AV1Idx *indexes; + u32 index_alloc_size, index_size; + + AV1State state; + u32 dsi_crc; + + Bool pts_from_file; + u64 cumulated_dur, last_pts; +} GF_AV1DmxCtx; + + +GF_Err av1dmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_AV1DmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) ctx->timescale = p->value.uint; + ctx->state.mem_mode = GF_TRUE; + if (ctx->timescale && !ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + } + if (ctx->timescale) { + //if we have a FPS prop, use it + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (p) ctx->cur_fps = p->value.frac; + } + return GF_OK; +} + +GF_Err av1dmx_check_format(GF_Filter *filter, GF_AV1DmxCtx *ctx, GF_BitStream *bs, u32 *last_obu_end) +{ + GF_Err e; + if (last_obu_end) (*last_obu_end) = 0; + //probing av1 bs mode + if (ctx->bsmode != NOT_SET) return GF_OK; + + + if (!ctx->state.config) + ctx->state.config = gf_odf_av1_cfg_new(); + + ctx->is_av1 = ctx->is_vp9 = ctx->is_vpX = GF_FALSE; + ctx->codecid = 0; + if (ctx->vp_cfg) gf_odf_vp_cfg_del(ctx->vp_cfg); + ctx->vp_cfg = NULL; + ctx->cur_fps = ctx->fps; + if (!ctx->fps.num || !ctx->fps.den) { + ctx->cur_fps.num = 25000; + ctx->cur_fps.den = 1000; + } + + ctx->pts_from_file = GF_FALSE; + if (gf_media_probe_ivf(bs)) { + u32 width = 0, height = 0; + u32 codec_fourcc = 0, frame_rate = 0, time_scale = 0, num_frames = 0; + ctx->bsmode = IVF; + + e = gf_media_parse_ivf_file_header(bs, &width, &height, &codec_fourcc, &frame_rate, &time_scale, &num_frames); + if (e) return e; + + switch (codec_fourcc) { + case GF_4CC('A', 'V', '0', '1'): + ctx->is_av1 = GF_TRUE; + ctx->codecid = GF_CODECID_AV1; + break; + case GF_4CC('V', 'P', '9', '0'): + ctx->is_vp9 = GF_TRUE; + ctx->codecid = GF_CODECID_VP9; + ctx->vp_cfg = gf_odf_vp_cfg_new(); + break; + case GF_4CC('V', 'P', '8', '0'): + ctx->codecid = GF_CODECID_VP8; + ctx->vp_cfg = gf_odf_vp_cfg_new(); + break; + case GF_4CC('V', 'P', '1', '0'): + ctx->codecid = GF_CODECID_VP10; + ctx->vp_cfg = gf_odf_vp_cfg_new(); + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[IVF] %s parsing not implemented, import might be uncomplete or broken\n", gf_4cc_to_str(codec_fourcc) )); + break; + default: + ctx->codecid = codec_fourcc; + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[IVF] Unsupported codec FourCC %s\n", gf_4cc_to_str(codec_fourcc) )); + return GF_NON_COMPLIANT_BITSTREAM; + } + if (ctx->vp_cfg && !ctx->is_vp9) { + ctx->is_vpX = GF_TRUE; + ctx->vp_cfg->profile = 1; + ctx->vp_cfg->level = 10; + ctx->vp_cfg->bit_depth = 8; + //leave the rest as 0 + } + + ctx->state.width = ctx->state.width < width ? width : ctx->state.width; + ctx->state.height = ctx->state.height < height ? height : ctx->state.height; + ctx->state.tb_num = frame_rate; //time_base.numerator + ctx->state.tb_den = time_scale; //time_base.denominator + + if ((!ctx->fps.num || !ctx->fps.den) && ctx->state.tb_num && ctx->state.tb_den && ! ( (ctx->state.tb_num<=1) && (ctx->state.tb_den<=1) ) ) { + ctx->cur_fps.num = ctx->state.tb_num; + ctx->cur_fps.den = ctx->state.tb_den; + GF_LOG(GF_LOG_DEBUG, GF_LOG_PARSER, ("[AV1Dmx] Detected IVF format FPS %d/%d\n", ctx->cur_fps.num, ctx->cur_fps.den)); + ctx->pts_from_file = GF_TRUE; + } else { + GF_LOG(GF_LOG_DEBUG, GF_LOG_PARSER, ("[AV1Dmx] Detected IVF format\n")); + } + ctx->file_hdr_size = (u32) gf_bs_get_position(bs); + if (last_obu_end) (*last_obu_end) = (u32) gf_bs_get_position(bs); + return GF_OK; + } else if (gf_media_aom_probe_annexb(bs)) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[AV1Dmx] Detected Annex B format\n")); + ctx->bsmode = AnnexB; + } else { + gf_bs_seek(bs, 0); + e = aom_av1_parse_temporal_unit_from_section5(bs, &ctx->state); + if (e && !gf_list_count(ctx->state.frame_state.frame_obus) ) { + gf_filter_setup_failure(filter, e); + ctx->bsmode = UNSUPPORTED; + return e; + } + if (ctx->state.obu_type != OBU_TEMPORAL_DELIMITER) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[AV1Dmx] Error OBU stream start with %s, not a temporal delimiter - NOT SUPPORTED\n", gf_av1_get_obu_name(ctx->state.obu_type) )); + gf_filter_setup_failure(filter, e); + ctx->bsmode = UNSUPPORTED; + return e; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[AV1Dmx] Detected OBUs Section 5 format\n")); + ctx->bsmode = OBUs; + + gf_av1_reset_state(&ctx->state, GF_FALSE); + gf_bs_seek(bs, 0); + } + ctx->is_av1 = GF_TRUE; + ctx->state.unframed = GF_TRUE; + ctx->codecid = GF_CODECID_AV1; + return GF_OK; +} + + +static void av1dmx_check_dur(GF_Filter *filter, GF_AV1DmxCtx *ctx) +{ + FILE *stream; + GF_Err e; + GF_BitStream *bs; + u64 duration, cur_dur, last_cdur; + AV1State av1state; + const char *filepath=NULL; + const GF_PropertyValue *p; + if (!ctx->opid || ctx->timescale || ctx->file_loaded) return; + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) { + ctx->is_file = GF_FALSE; + ctx->file_loaded = GF_TRUE; + return; + } + filepath = p->value.string; + ctx->is_file = GF_TRUE; + + if (ctx->index<0) { + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_DOWN_SIZE); + if (!p || (p->value.longuint > 100000000)) { + GF_LOG(GF_LOG_INFO, GF_LOG_PARSER, ("[AV1/VP9] Source file larger than 100M, skipping indexing\n")); + } else { + ctx->index = -ctx->index; + } + } + if (ctx->index<=0) + return; + + stream = gf_fopen(filepath, "rb"); + if (!stream) return; + + ctx->index_size = 0; + + bs = gf_bs_from_file(stream, GF_BITSTREAM_READ); + + if (ctx->file_hdr_size) { + gf_bs_seek(bs, ctx->file_hdr_size); + } + gf_av1_init_state(&av1state); + av1state.skip_frames = GF_TRUE; + av1state.config = gf_odf_av1_cfg_new(); + + duration = 0; + cur_dur = last_cdur = 0; + while (gf_bs_available(bs)) { + Bool is_sap=GF_FALSE; + u64 pts = GF_FILTER_NO_TS; + u64 frame_start = gf_bs_get_position(bs); + gf_av1_reset_state(&av1state, GF_FALSE); + + /*we process each TU and extract only the necessary OBUs*/ + switch (ctx->bsmode) { + case OBUs: + e = aom_av1_parse_temporal_unit_from_section5(bs, &av1state); + break; + case AnnexB: + e = aom_av1_parse_temporal_unit_from_annexb(bs, &av1state); + break; + case IVF: + if (ctx->is_av1) { + e = aom_av1_parse_temporal_unit_from_ivf(bs, &av1state); + } else { + u64 frame_size; + e = gf_media_parse_ivf_frame_header(bs, &frame_size, &pts); + if (!e) gf_bs_skip_bytes(bs, frame_size); + is_sap = GF_TRUE; + } + break; + default: + e = GF_NOT_SUPPORTED; + } + if (e) + break; + + if (pts != GF_FILTER_NO_TS) { + duration = pts; + cur_dur = pts - last_cdur; + } else { + duration += ctx->cur_fps.den; + cur_dur += ctx->cur_fps.den; + } + if (av1state.frame_state.key_frame) + is_sap = GF_TRUE; + + //only index at I-frame start + if (frame_start && is_sap && (cur_dur > ctx->index * ctx->cur_fps.num) ) { + if (!ctx->index_alloc_size) ctx->index_alloc_size = 10; + else if (ctx->index_alloc_size == ctx->index_size) ctx->index_alloc_size *= 2; + ctx->indexes = gf_realloc(ctx->indexes, sizeof(AV1Idx)*ctx->index_alloc_size); + ctx->indexes[ctx->index_size].pos = frame_start; + ctx->indexes[ctx->index_size].duration = (Double) duration; + ctx->indexes[ctx->index_size].duration /= ctx->cur_fps.num; + ctx->index_size ++; + last_cdur = cur_dur; + cur_dur = 0; + } + } + gf_bs_del(bs); + gf_fclose(stream); + gf_odf_av1_cfg_del(av1state.config); + gf_av1_reset_state(&av1state, GF_TRUE); + + if (!ctx->duration.num || (ctx->duration.num * ctx->cur_fps.num != duration * ctx->duration.den)) { + ctx->duration.num = (s32) duration; + ctx->duration.den = ctx->cur_fps.num; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + } + + //currently not supported because of OBU size field rewrite - could work on some streams but we would + //need to analyse all OBUs in the stream for that + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, & PROP_BOOL(GF_FALSE) ); +} + + +static Bool av1dmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i; + u64 file_pos = 0; + GF_FilterEvent fevt; + GF_AV1DmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (!ctx->is_playing) { + ctx->is_playing = GF_TRUE; + ctx->cts = 0; + } + if (! ctx->is_file) { + ctx->buf_size = 0; + return GF_FALSE; + } + ctx->start_range = evt->play.start_range; + ctx->in_seek = GF_TRUE; + + if (ctx->start_range) { + + if (ctx->index<0) { + ctx->index = -ctx->index; + ctx->file_loaded = GF_FALSE; + ctx->duration.den = ctx->duration.num = 0; + GF_LOG(GF_LOG_INFO, GF_LOG_PARSER, ("[AV1/VP9Demx] Play request from %d, building index\n", ctx->start_range)); + av1dmx_check_dur(filter, ctx); + } + + for (i=1; iindex_size; i++) { + if (ctx->indexes[i].duration>ctx->start_range) { + ctx->cts = (u64) (ctx->indexes[i-1].duration * ctx->cur_fps.num); + file_pos = ctx->indexes[i-1].pos; + break; + } + } + } + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!file_pos) + return GF_TRUE; + } + ctx->buf_size = 0; + if (!file_pos) + file_pos = ctx->file_hdr_size; + + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + //don't cancel event + ctx->is_playing = GF_FALSE; + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +static GFINLINE void av1dmx_update_cts(GF_AV1DmxCtx *ctx) +{ + assert(ctx->cur_fps.num); + assert(ctx->cur_fps.den); + + if (ctx->timescale) { + u64 inc = ctx->cur_fps.den; + inc *= ctx->timescale; + inc /= ctx->cur_fps.num; + ctx->cts += inc; + } else { + ctx->cts += ctx->cur_fps.den; + } +} + +static void av1dmx_check_pid(GF_Filter *filter, GF_AV1DmxCtx *ctx) +{ + u8 *dsi; + u32 dsi_size, crc; + + //no config or no config change + if (ctx->is_av1 && !gf_list_count(ctx->state.frame_state.header_obus)) return; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + av1dmx_check_dur(filter, ctx); + } + dsi = NULL; + dsi_size = 0; + + if (ctx->vp_cfg) { + gf_odf_vp_cfg_write(ctx->vp_cfg, &dsi, &dsi_size, ctx->vp_cfg->codec_initdata_size ? GF_TRUE : GF_FALSE); + } else if (ctx->is_av1) { + //first or config changed, compute dsi + while (gf_list_count(ctx->state.config->obu_array)) { + GF_AV1_OBUArrayEntry *a = (GF_AV1_OBUArrayEntry*) gf_list_pop_back(ctx->state.config->obu_array); + if (a->obu) gf_free(a->obu); + gf_free(a); + } + dsi = NULL; + dsi_size = 0; + while (gf_list_count(ctx->state.frame_state.header_obus)) { + GF_AV1_OBUArrayEntry *a = (GF_AV1_OBUArrayEntry*) gf_list_get(ctx->state.frame_state.header_obus, 0); + gf_list_add(ctx->state.config->obu_array, a); + gf_list_rem(ctx->state.frame_state.header_obus, 0); + } + gf_odf_av1_cfg_write(ctx->state.config, &dsi, &dsi_size); + + if ((!ctx->fps.num || !ctx->fps.den) && ctx->state.tb_num && ctx->state.tb_den && ! ( (ctx->state.tb_num<=1) && (ctx->state.tb_den<=1) ) ) { + ctx->cur_fps.num = ctx->state.tb_num; + ctx->cur_fps.den = ctx->state.tb_den; + } + + } + crc = gf_crc_32(dsi, dsi_size); + + if (crc == ctx->dsi_crc) { + gf_free(dsi); + return; + } + ctx->dsi_crc = crc; + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT(GF_STREAM_VISUAL)); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(ctx->codecid)); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->cur_fps.num)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FPS, & PROP_FRAC(ctx->cur_fps)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, & PROP_UINT(ctx->state.width)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, & PROP_UINT(ctx->state.height)); + + if (ctx->duration.num) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + + if (dsi && dsi_size) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, & PROP_DATA_NO_COPY(dsi, dsi_size)); + + if (ctx->is_file && ctx->index) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, & PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD) ); + } + + if (ctx->is_av1) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_PRIMARIES, & PROP_UINT(ctx->state.color_primaries) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_TRANSFER, & PROP_UINT(ctx->state.transfer_characteristics) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_MX, & PROP_UINT(ctx->state.matrix_coefficients) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_RANGE, & PROP_BOOL(ctx->state.color_range) ); + } +} + +GF_Err av1dmx_parse_ivf(GF_Filter *filter, GF_AV1DmxCtx *ctx) +{ + GF_Err e; + u32 pck_size; + u64 frame_size = 0, pts = GF_FILTER_NO_TS; + GF_FilterPacket *pck; + u64 pos, pos_ivf_hdr; + u8 *output; + + pos_ivf_hdr = gf_bs_get_position(ctx->bs); + e = gf_media_parse_ivf_frame_header(ctx->bs, &frame_size, &pts); + if (e) return e; + + pos = gf_bs_get_position(ctx->bs); + if (gf_bs_available(ctx->bs) < frame_size) { + gf_bs_seek(ctx->bs, pos_ivf_hdr); + return GF_EOS; + } + + if (ctx->pts_from_file) { + pts += ctx->cumulated_dur; + if (ctx->last_pts && (ctx->last_pts>pts)) { + pts -= ctx->cumulated_dur; + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[IVF/AV1] Corrupted timestamp "LLU" less than previous timestamp "LLU", assuming concatenation\n", pts, ctx->last_pts)); + ctx->cumulated_dur = ctx->last_pts + ctx->cur_fps.den; + ctx->cumulated_dur -= pts; + pts = ctx->cumulated_dur; + } + ctx->last_pts = pts; + } + + + //check pid state + av1dmx_check_pid(filter, ctx); + + if (!ctx->opid) { + return GF_OK; + } + + if (!ctx->is_playing) { + gf_bs_seek(ctx->bs, pos_ivf_hdr); + return GF_EOS; + } + + pck_size = (u32)frame_size; + pck = gf_filter_pck_new_alloc(ctx->opid, pck_size, &output); + if (!pck) { + gf_bs_seek(ctx->bs, pos_ivf_hdr); + return GF_OUT_OF_MEM; + } + if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, pck); + + if (ctx->pts_from_file) { + gf_filter_pck_set_cts(pck, pts); + } else { + gf_filter_pck_set_cts(pck, ctx->cts); + } + + gf_bs_seek(ctx->bs, pos); + gf_bs_read_data(ctx->bs, output, pck_size); + + if (output[0] & 0x80) + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + else + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_NONE); + + gf_filter_pck_send(pck); + + av1dmx_update_cts(ctx); + return GF_OK; +} + +GF_Err av1dmx_parse_vp9(GF_Filter *filter, GF_AV1DmxCtx *ctx) +{ + Bool key_frame = GF_FALSE; + u64 frame_size = 0, pts = 0; + u64 pos, pos_ivf_hdr; + u32 width = 0, height = 0, renderWidth, renderHeight; + u32 num_frames_in_superframe = 0, superframe_index_size = 0, i = 0; + u32 frame_sizes[VP9_MAX_FRAMES_IN_SUPERFRAME]; + u8 *output; + GF_Err e; + + pos_ivf_hdr = gf_bs_get_position(ctx->bs); + e = gf_media_parse_ivf_frame_header(ctx->bs, &frame_size, &pts); + if (e) return e; + + pos = gf_bs_get_position(ctx->bs); + if (gf_bs_available(ctx->bs) < frame_size) { + gf_bs_seek(ctx->bs, pos_ivf_hdr); + return GF_EOS; + } + + if (ctx->pts_from_file) { + pts += ctx->cumulated_dur; + if (ctx->last_pts && (ctx->last_pts>pts)) { + pts -= ctx->cumulated_dur; + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[IVF/VP9] Corrupted timestamp "LLU" less than previous timestamp "LLU", assuming concatenation\n", pts, ctx->last_pts)); + ctx->cumulated_dur = ctx->last_pts + ctx->cur_fps.den; + ctx->cumulated_dur -= pts; + pts = ctx->cumulated_dur; + } + ctx->last_pts = pts; + } + + /*check if it is a superframe*/ + e = gf_media_vp9_parse_superframe(ctx->bs, frame_size, &num_frames_in_superframe, frame_sizes, &superframe_index_size); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VP9Dmx] Error parsing superframe structure\n")); + return e; + } + + for (i = 0; i < num_frames_in_superframe; ++i) { + u64 pos2 = gf_bs_get_position(ctx->bs); + if (gf_media_vp9_parse_sample(ctx->bs, ctx->vp_cfg, &key_frame, &width, &height, &renderWidth, &renderHeight) != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VP9Dmx] Error parsing frame\n")); + return e; + } + e = gf_bs_seek(ctx->bs, pos2 + frame_sizes[i]); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[VP9Dmx] Seek bad param (offset "LLU") (1)", pos2 + frame_sizes[i])); + return e; + } + } + if (gf_bs_get_position(ctx->bs) + superframe_index_size != pos + frame_size) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[VP9Dmx] Inconsistent IVF frame size of "LLU" bytes.\n", frame_size)); + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, (" Detected %d frames (+ %d bytes for the superframe index):\n", num_frames_in_superframe, superframe_index_size)); + for (i = 0; i < num_frames_in_superframe; ++i) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, (" superframe %d, size is %u bytes\n", i, frame_sizes[i])); + } + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("\n")); + } + e = gf_bs_seek(ctx->bs, pos + frame_size); + if (e) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[VP9Dmx] Seek bad param (offset "LLU") (2)", pos + frame_size)); + return e; + } + + u32 pck_size = (u32)(gf_bs_get_position(ctx->bs) - pos); + assert(pck_size == frame_size); + + //check pid state + av1dmx_check_pid(filter, ctx); + + if (!ctx->opid) { + return GF_OK; + } + + if (!ctx->is_playing) { + gf_bs_seek(ctx->bs, pos_ivf_hdr); + return GF_EOS; + } + + GF_FilterPacket *pck = gf_filter_pck_new_alloc(ctx->opid, pck_size, &output); + if (!pck) { + gf_bs_seek(ctx->bs, pos_ivf_hdr); + return GF_OUT_OF_MEM; + } + if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, pck); + + if (ctx->pts_from_file) { + gf_filter_pck_set_cts(pck, pts); + } else { + gf_filter_pck_set_cts(pck, ctx->cts); + } + + + if (key_frame) { + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + } + + if (ctx->deps) { + u8 flags = 0; + //dependsOn + flags = (key_frame) ? 2 : 1; + flags <<= 2; + //dependedOn + //flags |= 2; + flags <<= 2; + //hasRedundant + //flags |= ctx->has_redundant ? 1 : 2; + gf_filter_pck_set_dependency_flags(pck, flags); + } + + gf_bs_seek(ctx->bs, pos); + gf_bs_read_data(ctx->bs, output, pck_size); + gf_filter_pck_send(pck); + + av1dmx_update_cts(ctx); + return GF_OK; +} + +static GF_Err av1dmx_parse_flush_sample(GF_Filter *filter, GF_AV1DmxCtx *ctx) +{ + u32 pck_size; + GF_FilterPacket *pck; + u8 *output; + + gf_bs_get_content_no_truncate(ctx->state.bs, &ctx->state.frame_obus, &pck_size, &ctx->state.frame_obus_alloc); + + if (!pck_size) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[AV1Dmx] no frame OBU, skipping OBU\n")); + return GF_OK; + } + + pck = gf_filter_pck_new_alloc(ctx->opid, pck_size, &output); + if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, pck); + + gf_filter_pck_set_cts(pck, ctx->cts); + gf_filter_pck_set_sap(pck, ctx->state.frame_state.key_frame ? GF_FILTER_SAP_1 : 0); + + memcpy(output, ctx->state.frame_obus, pck_size); + + if (ctx->deps) { + u8 flags = 0; + //dependsOn + flags = ( ctx->state.frame_state.key_frame) ? 2 : 1; + flags <<= 2; + //dependedOn + flags |= ctx->state.frame_state.refresh_frame_flags ? 1 : 2; + flags <<= 2; + //hasRedundant + //flags |= ctx->has_redundant ? 1 : 2; + gf_filter_pck_set_dependency_flags(pck, flags); + } + + gf_filter_pck_send(pck); + + av1dmx_update_cts(ctx); + gf_av1_reset_state(&ctx->state, GF_FALSE); + + return GF_OK; + +} +GF_Err av1dmx_parse_av1(GF_Filter *filter, GF_AV1DmxCtx *ctx) +{ + GF_Err e = GF_OK; + u64 start; + + if (!ctx->is_playing) { + ctx->state.frame_state.is_first_frame = GF_TRUE; + } + + /*we process each TU and extract only the necessary OBUs*/ + start = gf_bs_get_position(ctx->bs); + switch (ctx->bsmode) { + case OBUs: + //first frame loaded ! + if (ctx->state.bs && gf_bs_get_position(ctx->state.bs) && (ctx->state.obu_type == OBU_TEMPORAL_DELIMITER)) { + e = GF_OK; + } else { + e = aom_av1_parse_temporal_unit_from_section5(ctx->bs, &ctx->state); + } + break; + case AnnexB: + //first TU loaded ! + if (ctx->state.bs && gf_bs_get_position(ctx->state.bs)) { + e = GF_OK; + } else { + e = aom_av1_parse_temporal_unit_from_annexb(ctx->bs, &ctx->state); + if (e==GF_BUFFER_TOO_SMALL) { + gf_av1_reset_state(&ctx->state, GF_FALSE); + gf_bs_seek(ctx->bs, start); + } + } + break; + case IVF: + //first frame loaded ! + if (ctx->state.bs && gf_bs_get_position(ctx->state.bs)) { + e = GF_OK; + } else { + e = aom_av1_parse_temporal_unit_from_ivf(ctx->bs, &ctx->state); + } + break; + default: + e = GF_NOT_SUPPORTED; + } + + //check pid state + av1dmx_check_pid(filter, ctx); + + if (e) return e; + + + if (!ctx->opid) { + if (ctx->state.obu_type != OBU_TEMPORAL_DELIMITER) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[AV1Dmx] output pid not configured (no sequence header yet ?), skipping OBU\n")); + } + gf_av1_reset_state(&ctx->state, GF_FALSE); + return GF_OK; + } + + if (!ctx->is_playing) { + //don't reset state we would skip seq header obu in first frame + //gf_av1_reset_state(&ctx->state, GF_FALSE); + return GF_OK; + } + + return av1dmx_parse_flush_sample(filter, ctx); + +} + +GF_Err av1dmx_process_buffer(GF_Filter *filter, GF_AV1DmxCtx *ctx, const char *data, u32 data_size, Bool is_copy) +{ + u32 last_obu_end = 0; + GF_Err e = GF_OK; + + if (!ctx->bs) ctx->bs = gf_bs_new(data, data_size, GF_BITSTREAM_READ); + else gf_bs_reassign_buffer(ctx->bs, data, data_size); + + //check ivf vs obu vs annexB + e = av1dmx_check_format(filter, ctx, ctx->bs, &last_obu_end); + if (e) return e; + + while (gf_bs_available(ctx->bs)) { + + if (ctx->is_vp9) { + e = av1dmx_parse_vp9(filter, ctx); + } else if (ctx->is_av1) { + e = av1dmx_parse_av1(filter, ctx); + } else { + e = av1dmx_parse_ivf(filter, ctx); + } + + if (e!=GF_EOS) + last_obu_end = (u32) gf_bs_get_position(ctx->bs); + + if (e) { + break; + } + if (!ctx->is_playing && ctx->opid) + break; + } + + if (is_copy && last_obu_end) { + assert(ctx->buf_size>=last_obu_end); + memmove(ctx->buffer, ctx->buffer+last_obu_end, sizeof(char) * (ctx->buf_size-last_obu_end)); + ctx->buf_size -= last_obu_end; + } + if (e==GF_EOS) return GF_OK; + if (e==GF_BUFFER_TOO_SMALL) return GF_OK; + return e; +} + +GF_Err av1dmx_process(GF_Filter *filter) +{ + GF_Err e; + GF_AV1DmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + char *data; + u32 pck_size; + + if (ctx->bsmode == UNSUPPORTED) return GF_EOS; + + //always reparse duration + if (!ctx->duration.num) + av1dmx_check_dur(filter, ctx); + + if (!ctx->is_playing && ctx->opid) + return GF_OK; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + //flush + while (ctx->buf_size) { + u32 buf_size = ctx->buf_size; + e = av1dmx_process_buffer(filter, ctx, ctx->buffer, ctx->buf_size, GF_TRUE); + if (e) break; + if (buf_size == ctx->buf_size) { + break; + } + } + if (ctx->state.bs && gf_bs_get_position(ctx->state.bs)) + av1dmx_parse_flush_sample(filter, ctx); + + ctx->buf_size = 0; + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = NULL; + return GF_EOS; + } + return GF_OK; + } + + if (ctx->opid) { + if (!ctx->is_playing || gf_filter_pid_would_block(ctx->opid)) + return GF_OK; + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + + //input pid sets some timescale - we flushed pending data , update cts + if (ctx->timescale) { + Bool start, end; + u64 cts; + + e = GF_OK; + + gf_filter_pck_get_framing(pck, &start, &end); + //middle or end of frame, reaggregation + if (!start) { + if (ctx->alloc_size < ctx->buf_size + pck_size) { + ctx->alloc_size = ctx->buf_size + pck_size; + ctx->buffer = gf_realloc(ctx->buffer, ctx->alloc_size); + } + memcpy(ctx->buffer+ctx->buf_size, data, pck_size); + ctx->buf_size += pck_size; + + //end of frame, process av1 + if (end) { + e = av1dmx_process_buffer(filter, ctx, ctx->buffer, ctx->buf_size, GF_TRUE); + } + ctx->buf_size=0; + gf_filter_pid_drop_packet(ctx->ipid); + return e; + } + //flush of pending frame (might have lost something) + if (ctx->buf_size) { + e = av1dmx_process_buffer(filter, ctx, ctx->buffer, ctx->buf_size, GF_TRUE); + ctx->buf_size = 0; + if (e) return e; + } + + //begining of a new frame + cts = gf_filter_pck_get_cts(pck); + if (cts != GF_FILTER_NO_TS) + ctx->cts = cts; + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = pck; + gf_filter_pck_ref_props(&ctx->src_pck); + ctx->buf_size = 0; + + if (!end) { + if (ctx->alloc_size < ctx->buf_size + pck_size) { + ctx->alloc_size = ctx->buf_size + pck_size; + ctx->buffer = gf_realloc(ctx->buffer, ctx->alloc_size); + } + memcpy(ctx->buffer+ctx->buf_size, data, pck_size); + ctx->buf_size += pck_size; + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + assert(start && end); + //process + e = av1dmx_process_buffer(filter, ctx, data, pck_size, GF_FALSE); + + gf_filter_pid_drop_packet(ctx->ipid); + return e; + } + + //not from framed stream, copy buffer + if (ctx->alloc_size < ctx->buf_size + pck_size) { + ctx->alloc_size = ctx->buf_size + pck_size; + ctx->buffer = gf_realloc(ctx->buffer, ctx->alloc_size); + } + memcpy(ctx->buffer+ctx->buf_size, data, pck_size); + ctx->buf_size += pck_size; + e = av1dmx_process_buffer(filter, ctx, ctx->buffer, ctx->buf_size, GF_TRUE); + gf_filter_pid_drop_packet(ctx->ipid); + return e; +} + +static void av1dmx_finalize(GF_Filter *filter) +{ + GF_AV1DmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs) gf_bs_del(ctx->bs); + if (ctx->indexes) gf_free(ctx->indexes); + + gf_av1_reset_state(&ctx->state, GF_TRUE); + if (ctx->state.config) gf_odf_av1_cfg_del(ctx->state.config); + if (ctx->state.bs) gf_bs_del(ctx->state.bs); + if (ctx->state.frame_obus) gf_free(ctx->state.frame_obus); + if (ctx->buffer) gf_free(ctx->buffer); + + if (ctx->vp_cfg) gf_odf_vp_cfg_del(ctx->vp_cfg); +} + +static const char * av1dmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + GF_BitStream *bs = gf_bs_new(data, size, GF_BITSTREAM_READ); + Bool res; + u32 lt; + const char *mime = "video/av1"; + lt = gf_log_get_tool_level(GF_LOG_CODING); + gf_log_set_tool_level(GF_LOG_CODING, GF_LOG_QUIET); + + res = gf_media_probe_ivf(bs); + if (res) { + *score = GF_FPROBE_SUPPORTED; + mime = "video/x-ivf"; + } else { + res = gf_media_aom_probe_annexb(bs); + if (res) *score = GF_FPROBE_SUPPORTED; + else { + AV1State state; + GF_Err e; + u32 nb_units = 0; + + gf_av1_init_state(&state); + state.config = gf_odf_av1_cfg_new(); + while (gf_bs_available(bs)) { + e = aom_av1_parse_temporal_unit_from_section5(bs, &state); + if ((e==GF_OK) || (nb_units && (e==GF_BUFFER_TOO_SMALL) ) ) { + if (!nb_units || gf_list_count(state.frame_state.header_obus) || gf_list_count(state.frame_state.frame_obus)) { + nb_units++; + if (e==GF_BUFFER_TOO_SMALL) + nb_units++; + } else + break; + } else { + break; + } + gf_av1_reset_state(&state, GF_FALSE); + if (nb_units>2) break; + } + gf_odf_av1_cfg_del(state.config); + gf_av1_reset_state(&state, GF_TRUE); + if (nb_units>2) { + res = GF_TRUE; + *score = GF_FPROBE_MAYBE_SUPPORTED; + } + } + } + + gf_log_set_tool_level(GF_LOG_CODING, lt); + + gf_bs_del(bs); + if (res) return mime; + return NULL; +} + +static const GF_FilterCapability AV1DmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "ivf|obu|av1b|av1"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "video/x-ivf|video/av1"), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_AV1), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_VP8), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_VP9), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_VP10), + CAP_BOOL(GF_CAPS_OUTPUT_STATIC_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AV1), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_VP8), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_VP9), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_VP10), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + +#define OFFS(_n) #_n, offsetof(GF_AV1DmxCtx, _n) +static const GF_FilterArgs AV1DmxArgs[] = +{ + { OFFS(fps), "import frame rate (0 default to FPS from bitstream or 25 Hz)", GF_PROP_FRACTION, "0/1000", NULL, 0}, + { OFFS(index), "indexing window length. If 0, bitstream is not probed for duration. A negative value skips the indexing if the source file is larger than 100M (slows down importers) unless a play with start range > 0 is issued, otherwise uses the positive value", GF_PROP_DOUBLE, "-1.0", NULL, 0}, + + { OFFS(importer), "compatibility with old importer", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(deps), "import samples dependencies information", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + {0} +}; + + +GF_FilterRegister AV1DmxRegister = { + .name = "rfav1", + GF_FS_SET_DESCRIPTION("AV1/IVF/VP9 reframer") + GF_FS_SET_HELP("This filter parses AV1 OBU, AV1 AnnexB or IVF with AV1 or VP9 files/data and outputs corresponding visual PID and frames.") + .private_size = sizeof(GF_AV1DmxCtx), + .args = AV1DmxArgs, + .finalize = av1dmx_finalize, + SETCAPS(AV1DmxCaps), + .configure_pid = av1dmx_configure_pid, + .process = av1dmx_process, + .probe_data = av1dmx_probe_data, + .process_event = av1dmx_process_event +}; + + +const GF_FilterRegister *av1dmx_register(GF_FilterSession *session) +{ + return &AV1DmxRegister; +} + +#else +const GF_FilterRegister *av1dmx_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // GPAC_DISABLE_AV_PARSERS + diff --git a/src/filters/reframe_flac.c b/src/filters/reframe_flac.c new file mode 100644 index 0000000..c442f35 --- /dev/null +++ b/src/filters/reframe_flac.c @@ -0,0 +1,665 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2019 + * All rights reserved + * + * This file is part of GPAC / FLAC reframer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +typedef struct +{ + u64 pos; + Double duration; +} FLACIdx; + +typedef struct +{ + u32 block_size; + u32 sample_rate; +} FLACHeader; + +typedef struct +{ + //filter args + Double index; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + GF_BitStream *bs; + u64 file_pos, cts, prev_cts; + + GF_Fraction64 duration; + Double start_range; + Bool in_seek; + u32 timescale; + Bool is_playing; + Bool is_file; + Bool initial_play_done, file_loaded; + + Bool initialized; + u32 sample_rate, nb_channels, bits_per_sample, block_size; + + u8 *flac_buffer; + u32 flac_buffer_size, flac_buffer_alloc, resume_from; + u64 byte_offset; + + GF_FilterPacket *src_pck; + + Bool recompute_cts; + FLACIdx *indexes; + u32 index_alloc_size, index_size; +} GF_FLACDmxCtx; + + + + +GF_Err flac_dmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_FLACDmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) ctx->timescale = p->value.uint; + + p = gf_filter_pid_get_property_str(pid, "nocts"); + if (p && p->value.boolean) ctx->recompute_cts = GF_TRUE; + else ctx->recompute_cts = GF_FALSE; + + if (ctx->timescale && !ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + } + return GF_OK; +} + +static void flac_dmx_check_dur(GF_Filter *filter, GF_FLACDmxCtx *ctx) +{ + u64 duration; + s32 prev_sr = -1; + const GF_PropertyValue *p; + if (!ctx->opid || ctx->timescale || ctx->file_loaded) return; + + if (ctx->index<=0) { + ctx->file_loaded = GF_TRUE; + return; + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) { + ctx->is_file = GF_FALSE; + ctx->file_loaded = GF_TRUE; + return; + } + ctx->is_file = GF_TRUE; + + /*todo ...*/ + duration = 0; + + if (!ctx->duration.num || (ctx->duration.num * prev_sr != duration * ctx->duration.den)) { + ctx->duration.num = (s32) duration; + ctx->duration.den = prev_sr ; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, & PROP_BOOL(GF_TRUE ) ); +} + +static void flac_dmx_check_pid(GF_Filter *filter, GF_FLACDmxCtx *ctx, u8 *dsi, u32 dsi_size) +{ + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + flac_dmx_check_dur(filter, ctx); + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT( GF_STREAM_AUDIO)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL ); + if (ctx->is_file && ctx->index) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, & PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD) ); + } + if (ctx->duration.num) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + + if (!ctx->timescale) gf_filter_pid_set_name(ctx->opid, "audio"); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, & PROP_DATA( dsi, dsi_size ) ); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT( GF_CODECID_FLAC ) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->timescale ? ctx->timescale : ctx->sample_rate)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, & PROP_UINT(ctx->sample_rate)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, & PROP_UINT(ctx->nb_channels) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLES_PER_FRAME, & PROP_UINT(ctx->block_size) ); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_AUDIO_BPS, & PROP_UINT(ctx->bits_per_sample) ); + +} + +static Bool flac_dmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i; + GF_FilterEvent fevt; + GF_FLACDmxCtx *ctx = gf_filter_get_udta(filter); + + if (evt->base.on_pid != ctx->opid) return GF_TRUE; + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (!ctx->is_playing) { + ctx->is_playing = GF_TRUE; + } + if (! ctx->is_file) { + if (evt->play.start_range || ctx->initial_play_done) { + ctx->flac_buffer_size = 0; + ctx->resume_from = 0; + } + ctx->initial_play_done = GF_TRUE; + return GF_FALSE; + } + flac_dmx_check_dur(filter, ctx); + + ctx->start_range = evt->play.start_range; + ctx->in_seek = GF_TRUE; + ctx->file_pos = 0; + if (ctx->start_range) { + for (i=1; iindex_size; i++) { + if (ctx->indexes[i].duration>ctx->start_range) { + ctx->cts = (u64) (ctx->indexes[i-1].duration * ctx->sample_rate); + ctx->file_pos = ctx->indexes[i-1].pos; + break; + } + } + } + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!ctx->file_pos) + return GF_TRUE; + } + ctx->flac_buffer_size = 0; + ctx->resume_from = 0; + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = ctx->file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + ctx->is_playing = GF_FALSE; + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = NULL; + //don't cancel event + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +static GFINLINE void flac_dmx_update_cts(GF_FLACDmxCtx *ctx, u32 nb_samp) +{ + if (ctx->timescale) { + u64 inc = nb_samp; + inc *= ctx->timescale; + inc /= ctx->sample_rate; + ctx->cts += inc; + } else { + ctx->cts += nb_samp; + } +} + + +u8 const flac_dmx_crc8_table[256] = { + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, + 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, + 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, + 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, + 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, + 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, + 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, + 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, + 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, + 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, + 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, + 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, + 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, + 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, + 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, + 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, + 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, + 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, + 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, + 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, + 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, + 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, + 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, + 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, + 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, + 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, + 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 +}; + +u8 flac_dmx_crc8(u8 *data, u32 len) +{ + u8 crc = 0; + while (len--) + crc = flac_dmx_crc8_table[crc ^ *data++]; + return crc; +} + +static u32 flac_dmx_block_sizes[] = +{ + 0, 192, 576, 1152, 2304, 4608, 0, 0, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 +}; +static u32 flac_dmx_samplerates[] = +{ + 0, 88200, 176400, 192000, 8000, 16000, 22050, 24000, 32000, 44100, 48000, 96000 +}; + +static Bool flac_parse_header(GF_FLACDmxCtx *ctx, char *data, u32 size, FLACHeader *hdr) +{ + u32 block_size, sample_rate, res, top, pos, crc, crc_hdr; + + gf_bs_reassign_buffer(ctx->bs, data, size); + gf_bs_read_int(ctx->bs, 15); + /*block_strategy = */gf_bs_read_int(ctx->bs, 1); + block_size = gf_bs_read_int(ctx->bs, 4); + sample_rate = gf_bs_read_int(ctx->bs, 4); + /*u32 channel_layout = */gf_bs_read_int(ctx->bs, 4); + /*u32 bps = */gf_bs_read_int(ctx->bs, 3); + gf_bs_read_int(ctx->bs, 1); + + res = gf_bs_read_u8(ctx->bs); + top = (res & 128) >> 1; + if ((res & 0xC0) == 0x80 || (res >= 0xFE)) return GF_FALSE; + while (res & top) { + s32 tmp = gf_bs_read_u8(ctx->bs); + tmp -= 128; + if(tmp>>6) + return GF_FALSE; + res = (res<<6) + tmp; + top <<= 5; + } + //res &= (top << 1) - 1; + + if (block_size==6) block_size = 1 + gf_bs_read_int(ctx->bs, 8); + else if (block_size==7) block_size = 1 + gf_bs_read_int(ctx->bs, 16); + else { + block_size = flac_dmx_block_sizes[block_size]; + } + +#if 0 + if (bps==0) bps = ctx->bits_per_sample; + else if (bps==1) bps = 8; + else if (bps==2) bps = 12; + else if (bps==4) bps = 16; + else if (bps==5) bps = 20; + else if (bps==6) bps = 24; +#endif + + if (sample_rate==0) sample_rate = ctx->sample_rate; + else if ((sample_rate&0xC)==0xC) { + if (sample_rate==0xC) sample_rate = gf_bs_read_u8(ctx->bs); + else if (sample_rate==0xD) sample_rate = gf_bs_read_u16(ctx->bs); + else if (sample_rate==0xE) sample_rate = 10*gf_bs_read_u16(ctx->bs); + } else { + sample_rate = flac_dmx_samplerates[sample_rate]; + } + + pos = (u32) gf_bs_get_position(ctx->bs); + + crc = gf_bs_read_u8(ctx->bs); + crc_hdr = flac_dmx_crc8(data, pos); + + if (crc != crc_hdr) { + return GF_FALSE; + } + hdr->sample_rate = sample_rate; + hdr->block_size = block_size; + return GF_TRUE; +} + +GF_Err flac_dmx_process(GF_Filter *filter) +{ + GF_FLACDmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + u8 *output; + u8 *start; + Bool final_flush=GF_FALSE; + u32 pck_size, remain, prev_pck_size; + u64 cts = GF_FILTER_NO_TS; + FLACHeader hdr; + + //always reparse duration + if (!ctx->duration.num) + flac_dmx_check_dur(filter, ctx); + + if (ctx->opid && !ctx->is_playing) + return GF_OK; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (!ctx->flac_buffer_size) { + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = NULL; + return GF_EOS; + } + final_flush = GF_TRUE; + } else { + return GF_OK; + } + } + + prev_pck_size = ctx->flac_buffer_size; + if (pck && !ctx->resume_from) { + u8 *data = (u8 *) gf_filter_pck_get_data(pck, &pck_size); + + if (ctx->byte_offset != GF_FILTER_NO_BO) { + u64 byte_offset = gf_filter_pck_get_byte_offset(pck); + if (!ctx->flac_buffer_size) { + ctx->byte_offset = byte_offset; + } else if (ctx->byte_offset + ctx->flac_buffer_size != byte_offset) { + ctx->byte_offset = GF_FILTER_NO_BO; + if ((byte_offset != GF_FILTER_NO_BO) && (byte_offset>ctx->flac_buffer_size) ) { + ctx->byte_offset = byte_offset - ctx->flac_buffer_size; + } + } + } + + if (ctx->flac_buffer_size + pck_size > ctx->flac_buffer_alloc) { + ctx->flac_buffer_alloc = ctx->flac_buffer_size + pck_size; + ctx->flac_buffer = gf_realloc(ctx->flac_buffer, ctx->flac_buffer_alloc); + } + memcpy(ctx->flac_buffer + ctx->flac_buffer_size, data, pck_size); + ctx->flac_buffer_size += pck_size; + } + + //input pid sets some timescale - we flushed pending data , update cts + if (ctx->timescale && pck) { + cts = gf_filter_pck_get_cts(pck); + } + + if (cts == GF_FILTER_NO_TS) { + //avoids updating cts + prev_pck_size = 0; + } + + remain = ctx->flac_buffer_size; + start = ctx->flac_buffer; + + if (ctx->resume_from) { + start += ctx->resume_from - 1; + remain -= ctx->resume_from - 1; + ctx->resume_from = 0; + } + + while (remain>2) { + u32 next_frame=0, nb_samp; + u32 cur_size = remain-2; + u8 *cur_buf = start+2; + u8 *hdr_start = NULL; + + if (final_flush) { + next_frame = remain; + } else { + while (cur_size) { + //wait till we have a frame header + hdr_start = memchr(cur_buf, 0xFF, cur_size); + if (!hdr_start) break; + next_frame = (u32) (hdr_start-start); + if (next_frame == remain) + break; + + if ((hdr_start[1]&0xFC) == 0xF8) { + if (flac_parse_header(ctx, hdr_start, (u32) remain - next_frame, &hdr)) + break; + } + cur_buf = hdr_start+1; + cur_size = (u32) (cur_buf - start); + assert(cur_size<=remain); + cur_size = remain - cur_size; + hdr_start = NULL; + } + if (!hdr_start) break; + if (next_frame == remain) + break; + } + + + if (!ctx->initialized) { + u32 size = next_frame; + u32 dsi_end = 0; + //we have a header + gf_bs_reassign_buffer(ctx->bs, ctx->flac_buffer, size); + u32 magic = gf_bs_read_u32(ctx->bs); + if (magic != GF_4CC('f','L','a','C')) { + + } + while (gf_bs_available(ctx->bs)) { + Bool last = gf_bs_read_int(ctx->bs, 1); + u32 type = gf_bs_read_int(ctx->bs, 7); + u32 len = gf_bs_read_int(ctx->bs, 24); + + if (type==0) { + u16 min_block_size = gf_bs_read_u16(ctx->bs); + u16 max_block_size = gf_bs_read_u16(ctx->bs); + /*u32 min_frame_size = */gf_bs_read_u24(ctx->bs); + /*u32 max_frame_size = */gf_bs_read_u24(ctx->bs); + ctx->sample_rate = gf_bs_read_int(ctx->bs, 20); + ctx->nb_channels = 1 + gf_bs_read_int(ctx->bs, 3); + ctx->bits_per_sample = 1 + gf_bs_read_int(ctx->bs, 5); + if (min_block_size==max_block_size) ctx->block_size = min_block_size; + else ctx->block_size = 0; + + //ignore the rest + /*total samples*/gf_bs_read_long_int(ctx->bs, 36); + gf_bs_skip_bytes(ctx->bs, 16); + dsi_end = (u32) gf_bs_get_position(ctx->bs); + + } else { + //ignore the rest for now + //TODO: expose metadata, pictures and co + gf_bs_skip_bytes(ctx->bs, len); + } + if (last) break; + } + flac_dmx_check_pid(filter, ctx, ctx->flac_buffer+4, dsi_end-4); + remain -= size; + start += size; + ctx->initialized = GF_TRUE; + if (!ctx->is_playing) break; + continue; + } + + //we have a next frame, check we are synchronize + if ((start[0] != 0xFF) && ((start[1]&0xFC) != 0xF8)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[FLACDmx] invalid frame, droping %d bytes and resyncing\n", next_frame)); + start += next_frame; + remain -= next_frame; + continue; + } + + flac_parse_header(ctx,start, next_frame, &hdr); + if (hdr.sample_rate != ctx->sample_rate) { + ctx->sample_rate = hdr.sample_rate; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, & PROP_UINT(ctx->sample_rate)); + } + + nb_samp = hdr.block_size; + + if (ctx->in_seek) { + u64 nb_samples_at_seek = (u64) (ctx->start_range * ctx->sample_rate); + if (ctx->cts + nb_samp >= nb_samples_at_seek) { + //u32 samples_to_discard = (ctx->cts + nb_samp ) - nb_samples_at_seek; + ctx->in_seek = GF_FALSE; + } + } + + if (ctx->timescale && !prev_pck_size && (cts != GF_FILTER_NO_TS) ) { + ctx->cts = cts; + cts = GF_FILTER_NO_TS; + } + + if (!ctx->in_seek) { + dst_pck = gf_filter_pck_new_alloc(ctx->opid, next_frame, &output); + memcpy(output, start, next_frame); + + gf_filter_pck_set_cts(dst_pck, ctx->cts); + if (!ctx->timescale || (ctx->timescale==ctx->sample_rate) ) + gf_filter_pck_set_duration(dst_pck, nb_samp); + else { + gf_filter_pck_set_duration(dst_pck, (nb_samp * ctx->timescale) / ctx->sample_rate); + } + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + + if (ctx->byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, ctx->byte_offset); + } + gf_filter_pck_send(dst_pck); + } + flac_dmx_update_cts(ctx, nb_samp); + + assert (start[0] == 0xFF); + assert((start[1]&0xFC) == 0xF8); + + start += next_frame; + assert(remain >= next_frame); + remain -= next_frame; + + } + + if (!pck) { + ctx->flac_buffer_size = 0; + return flac_dmx_process(filter); + } else { + if (remain < ctx->flac_buffer_size) { + memmove(ctx->flac_buffer, start, remain); + } + ctx->flac_buffer_size = remain; + gf_filter_pid_drop_packet(ctx->ipid); + } + return GF_OK; +} + +static GF_Err flac_dmx_initialize(GF_Filter *filter) +{ + GF_FLACDmxCtx *ctx = gf_filter_get_udta(filter); + ctx->bs = gf_bs_new((u8 *)ctx, 1, GF_BITSTREAM_READ); + return GF_OK; +} +static void flac_dmx_finalize(GF_Filter *filter) +{ + GF_FLACDmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs) gf_bs_del(ctx->bs); + if (ctx->indexes) gf_free(ctx->indexes); + if (ctx->flac_buffer) gf_free(ctx->flac_buffer); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); +} + + +static const char *flac_dmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + if ((size>4) && !strncmp(data, "fLaC", 4)) { + *score = GF_FPROBE_SUPPORTED; + return "audio/flac"; + } + return NULL; +} + +static const GF_FilterCapability FLACDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "flac"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "audio/flac"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_FLAC), + CAP_BOOL(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_FLAC), + CAP_BOOL(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + + + +#define OFFS(_n) #_n, offsetof(GF_FLACDmxCtx, _n) +static const GF_FilterArgs FLACDmxArgs[] = +{ + { OFFS(index), "indexing window length", GF_PROP_DOUBLE, "1.0", NULL, 0}, + {0} +}; + + +GF_FilterRegister FLACDmxRegister = { + .name = "rfflac", + GF_FS_SET_DESCRIPTION("FLAC reframer") + GF_FS_SET_HELP("This filter parses FLAC files/data and outputs corresponding audio PID and frames.") + .private_size = sizeof(GF_FLACDmxCtx), + .args = FLACDmxArgs, + .finalize = flac_dmx_finalize, + .initialize = flac_dmx_initialize, + SETCAPS(FLACDmxCaps), + .configure_pid = flac_dmx_configure_pid, + .process = flac_dmx_process, + .probe_data = flac_dmx_probe_data, + .process_event = flac_dmx_process_event +}; + + +const GF_FilterRegister *flac_dmx_register(GF_FilterSession *session) +{ + return &FLACDmxRegister; +} diff --git a/src/filters/reframe_h263.c b/src/filters/reframe_h263.c new file mode 100644 index 0000000..c0f08e4 --- /dev/null +++ b/src/filters/reframe_h263.c @@ -0,0 +1,722 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / H263 reframer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +typedef struct +{ + u64 pos; + Double duration; +} H263Idx; + +typedef struct +{ + //filter args + GF_Fraction fps; + Double index; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + GF_BitStream *bs; + u64 cts; + u32 width, height; + GF_Fraction64 duration; + Double start_range; + Bool in_seek; + u32 timescale; + + u32 resume_from; + + + u32 bytes_in_header; + char hdr_store[8]; + + Bool is_playing; + Bool is_file, file_loaded; + Bool initial_play_done; + + GF_FilterPacket *src_pck; + + H263Idx *indexes; + u32 index_alloc_size, index_size; +} GF_H263DmxCtx; + + +GF_Err h263dmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_H263DmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) ctx->timescale = p->value.uint; + + if (ctx->timescale && !ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + } + return GF_OK; +} + +#define H263_CACHE_SIZE 4096 +static u32 h263dmx_next_start_code_bs(GF_BitStream *bs) +{ + u32 v, bpos; + unsigned char h263_cache[H263_CACHE_SIZE]; + u64 end, cache_start, load_size; + u64 start = gf_bs_get_position(bs); + + /*skip 16b header*/ + gf_bs_read_u16(bs); + bpos = 0; + load_size = 0; + cache_start = 0; + end = 0; + v = 0xffffffff; + while (!end) { + /*refill cache*/ + if (bpos == (u32) load_size) { + if (!gf_bs_available(bs)) break; + load_size = gf_bs_available(bs); + if (load_size>H263_CACHE_SIZE) load_size=H263_CACHE_SIZE; + bpos = 0; + cache_start = gf_bs_get_position(bs); + gf_bs_read_data(bs, (char *) h263_cache, (u32) load_size); + } + v = (v<<8) | h263_cache[bpos]; + bpos++; + if ((v >> (32-22)) == 0x20) end = cache_start+bpos-4; + } + gf_bs_seek(bs, start); + if (!end) end = gf_bs_get_size(bs); + return (u32) (end-start); +} +static void h263dmx_check_dur(GF_Filter *filter, GF_H263DmxCtx *ctx) +{ + + FILE *stream; + GF_BitStream *bs; + u64 duration, cur_dur; + const GF_PropertyValue *p; + if (!ctx->opid || ctx->timescale || ctx->file_loaded) return; + + if (ctx->index<=0) { + ctx->file_loaded = GF_TRUE; + return; + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) { + ctx->is_file = GF_FALSE; + ctx->file_loaded = GF_TRUE; + return; + } + ctx->is_file = GF_TRUE; + + stream = gf_fopen(p->value.string, "rb"); + if (!stream) return; + + ctx->index_size = 0; + + bs = gf_bs_from_file(stream, GF_BITSTREAM_READ); + duration = 0; + cur_dur = 0; + while (gf_bs_available(bs)) { + u8 type; + u64 pos = gf_bs_get_position(bs); + u64 next_pos = pos + h263dmx_next_start_code_bs(bs); + gf_bs_read_u32(bs); + type = gf_bs_read_u8(bs); + + if (type & 0x02) type = 0; + else + type = 1; + + + duration += ctx->fps.den; + cur_dur += ctx->fps.den; + //only index at I-frame start + if (pos && type && (cur_dur > ctx->index * ctx->fps.num) ) { + if (!ctx->index_alloc_size) ctx->index_alloc_size = 10; + else if (ctx->index_alloc_size == ctx->index_size) ctx->index_alloc_size *= 2; + ctx->indexes = gf_realloc(ctx->indexes, sizeof(H263Idx)*ctx->index_alloc_size); + ctx->indexes[ctx->index_size].pos = pos; + ctx->indexes[ctx->index_size].duration = (Double) duration; + ctx->indexes[ctx->index_size].duration /= ctx->fps.num; + ctx->index_size ++; + cur_dur = 0; + } + + gf_bs_seek(bs, next_pos); + } + gf_bs_del(bs); + gf_fclose(stream); + + if (!ctx->duration.num || (ctx->duration.num * ctx->fps.num != duration * ctx->duration.den)) { + ctx->duration.num = (s32) duration; + ctx->duration.den = ctx->fps.num; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, & PROP_BOOL(GF_TRUE ) ); +} + +static void h263dmx_check_pid(GF_Filter *filter, GF_H263DmxCtx *ctx, u32 width, u32 height) +{ + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + h263dmx_check_dur(filter, ctx); + } + if ((ctx->width == width) && (ctx->height == height)) return; + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT(GF_STREAM_VISUAL)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(GF_CODECID_H263)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->fps.num)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FPS, & PROP_FRAC(ctx->fps)); + + if (ctx->duration.num) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + + ctx->width = width; + ctx->height = height; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, & PROP_UINT( width)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, & PROP_UINT( height)); + + if (ctx->is_file && ctx->index) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, & PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD) ); + } +} + +static Bool h263dmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i; + u64 file_pos = 0; + GF_FilterEvent fevt; + GF_H263DmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (!ctx->is_playing) { + ctx->is_playing = GF_TRUE; + ctx->cts = 0; + ctx->bytes_in_header = 0; + } + if (! ctx->is_file) { + return GF_FALSE; + } + ctx->start_range = evt->play.start_range; + ctx->in_seek = GF_TRUE; + + if (ctx->start_range) { + for (i=1; iindex_size; i++) { + if (ctx->indexes[i].duration>ctx->start_range) { + ctx->cts = (u64) (ctx->indexes[i-1].duration * ctx->fps.num); + file_pos = ctx->indexes[i-1].pos; + break; + } + } + } + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!file_pos) + return GF_TRUE; + } + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + //don't cancel event + ctx->is_playing = GF_FALSE; + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +static GFINLINE void h263dmx_update_cts(GF_H263DmxCtx *ctx) +{ + assert(ctx->fps.num); + assert(ctx->fps.den); + + if (ctx->timescale) { + u64 inc = ctx->fps.den; + inc *= ctx->timescale; + inc /= ctx->fps.num; + ctx->cts += inc; + } else { + ctx->cts += ctx->fps.den; + } +} + + +static s32 h263dmx_next_start_code(u8 *data, u32 size) +{ + u32 v, bpos; + s64 end; + s64 start = 0; + + /*skip 16b header*/ + start+=2; + bpos = 0; + end = 0; + v = 0xffffffff; + while (!end) { + if (bpos == size) return -1; + v = (v<<8) | data[bpos]; + bpos++; + if ((v >> (32-22)) == 0x20) end = start + bpos - 4; + } + + return (s32) (end-start); +} + +static void h263_get_pic_size(GF_BitStream *bs, u32 fmt, u32 *w, u32 *h) +{ + switch (fmt) { + case 1: + *w = 128; + *h = 96; + break; + case 2: + *w = 176; + *h = 144; + break; + case 3: + *w = 352; + *h = 288; + break; + case 4: + *w = 704; + *h = 576; + break; + case 5: + *w = 1409; + *h = 1152 ; + break; + default: + *w = *h = 0; + break; + } +} + +GF_Err h263dmx_process(GF_Filter *filter) +{ + GF_H263DmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + u64 byte_offset; + char *data; + u8 *start; + Bool first_frame_found = GF_FALSE; + u32 pck_size; + s32 remain; + + //always reparse duration + if (!ctx->duration.num) + h263dmx_check_dur(filter, ctx); + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = NULL; + return GF_EOS; + } + return GF_OK; + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + byte_offset = gf_filter_pck_get_byte_offset(pck); + + start = data; + remain = pck_size; + + + if (ctx->bytes_in_header) { +#if 0 + if (ctx->bytes_in_header + remain < 7) { + memcpy(ctx->header + ctx->bytes_in_header, start, remain); + ctx->bytes_in_header += remain; + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + alread_sync = 7 - ctx->bytes_in_header; + memcpy(ctx->header + ctx->bytes_in_header, start, alread_sync); + start += alread_sync; + remain -= alread_sync; + ctx->bytes_in_header = 0; + alread_sync = GF_TRUE; +#endif + + } + //input pid sets some timescale - we flushed pending data , update cts + else if (ctx->timescale) { + u64 cts = gf_filter_pck_get_cts(pck); + if (cts != GF_FILTER_NO_TS) + ctx->cts = cts; + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = pck; + gf_filter_pck_ref_props(&ctx->src_pck); + } + + if (ctx->resume_from) { + if (gf_filter_pid_would_block(ctx->opid)) + return GF_OK; + start += ctx->resume_from; + remain -= ctx->resume_from; + ctx->resume_from = 0; + } + + while (remain) { + u32 size=0; + Bool full_frame; + u8 *pck_data; + s32 current, next; + u32 fmt, w, h; + + //not enough bytes + if (remain<5) { + memcpy(ctx->hdr_store, start, remain); + ctx->bytes_in_header = remain; + break; + } + + if (ctx->bytes_in_header) { + if (first_frame_found) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[H263Dmx] corrupted frame!\n")); + } + + memcpy(ctx->hdr_store + ctx->bytes_in_header, start, 8 - ctx->bytes_in_header); + current = h263dmx_next_start_code(ctx->hdr_store, 8); + + //no start code in stored buffer + if (current<0) { + dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->bytes_in_header, &pck_data); + if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, dst_pck); + + memcpy(pck_data, ctx->hdr_store, ctx->bytes_in_header); + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE); + gf_filter_pck_set_cts(dst_pck, ctx->cts); + gf_filter_pck_set_duration(dst_pck, ctx->fps.den); + if (ctx->in_seek) gf_filter_pck_set_seek_flag(dst_pck, GF_TRUE); + + if (byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, byte_offset - ctx->bytes_in_header); + } + gf_filter_pck_send(dst_pck); + ctx->bytes_in_header = 0; + + current = h263dmx_next_start_code(start, remain); + } + } else { + //locate next start code + current = h263dmx_next_start_code(start, remain); + } + + + if (current<0) { + //not enough bytes to process start code !! + break; + } + + if (current>0) { + if (!ctx->opid) { + if (ctx->bytes_in_header) { + ctx->bytes_in_header -= current; + } else { + start += current; + remain -= current; + } + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[H263Dmx] garbage before first frame!\n")); + continue; + } + if (first_frame_found) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[H263Dmx] corrupted frame!\n")); + } + //flush remaining + dst_pck = gf_filter_pck_new_alloc(ctx->opid, current, &pck_data); + if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, dst_pck); + + if (ctx->bytes_in_header) { + if (byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, byte_offset - ctx->bytes_in_header); + } + ctx->bytes_in_header -= current; + memcpy(pck_data, ctx->hdr_store, current); + } else { + if (byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, byte_offset); + } + memcpy(pck_data, start, current); + start += current; + remain -= current; + } + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_TRUE); + gf_filter_pck_set_cts(dst_pck, ctx->cts); + gf_filter_pck_set_duration(dst_pck, ctx->fps.den); + if (ctx->in_seek) gf_filter_pck_set_seek_flag(dst_pck, GF_TRUE); + gf_filter_pck_send(dst_pck); + + h263dmx_update_cts(ctx); + } + + if (ctx->bytes_in_header) { + gf_bs_reassign_buffer(ctx->bs, ctx->hdr_store+current, 8-current); + } else if (!ctx->bs) { + ctx->bs = gf_bs_new(start, remain, GF_BITSTREAM_READ); + } else { + gf_bs_reassign_buffer(ctx->bs, start, remain); + } + /*parse header*/ + gf_bs_read_int(ctx->bs, 22); + gf_bs_read_int(ctx->bs, 8); + /*spare+0+split_screen_indicator+document_camera_indicator+freeze_picture_release*/ + gf_bs_read_int(ctx->bs, 5); + + fmt = gf_bs_read_int(ctx->bs, 3); + h263_get_pic_size(ctx->bs, fmt, &w, &h); + + h263dmx_check_pid(filter, ctx, w, h); + + if (!ctx->is_playing) { + ctx->resume_from = (u32) ( (char *)start - (char *)data ); + return GF_OK; + } + + if (ctx->in_seek) { + u64 nb_frames_at_seek = (u64) (ctx->start_range * ctx->fps.num); + if (ctx->cts + ctx->fps.den >= nb_frames_at_seek) { + //u32 samples_to_discard = (ctx->cts + ctx->dts_inc) - nb_samples_at_seek; + ctx->in_seek = GF_FALSE; + } + } + + //good to go + next = h263dmx_next_start_code(start+1, remain-1); + + if (next>0) { + size = next+1 + ctx->bytes_in_header; + full_frame = GF_TRUE; + } else { + u8 b3 = start[remain-3]; + u8 b2 = start[remain-2]; + u8 b1 = start[remain-1]; + //we may have a startcode here ! + if (!b1 || !b2 || !b3) { + memcpy(ctx->hdr_store, start+remain-3, 3); + remain -= 3; + ctx->bytes_in_header = 3; + } + size = remain; + full_frame = GF_FALSE; + } + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &pck_data); + if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, dst_pck); + if (ctx->bytes_in_header && current) { + memcpy(pck_data, ctx->hdr_store+current, ctx->bytes_in_header); + size -= ctx->bytes_in_header; + ctx->bytes_in_header = 0; + if (byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, byte_offset + ctx->bytes_in_header); + } + memcpy(pck_data, start, size); + } else { + memcpy(pck_data, start, size); + if (byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, byte_offset + start - (u8 *) data); + } + } + + gf_filter_pck_set_framing(dst_pck, GF_TRUE, full_frame); + gf_filter_pck_set_cts(dst_pck, ctx->cts); + gf_filter_pck_set_sap(dst_pck, (start[4]&0x02) ? GF_FILTER_SAP_NONE : GF_FILTER_SAP_1); + gf_filter_pck_set_duration(dst_pck, ctx->fps.den); + if (ctx->in_seek) gf_filter_pck_set_seek_flag(dst_pck, GF_TRUE); + gf_filter_pck_send(dst_pck); + + first_frame_found = GF_TRUE; + start += size; + remain -= size; + if (!full_frame) break; + h263dmx_update_cts(ctx); + + + //don't demux too much of input, abort when we would block. This avoid dispatching + //a huge number of frames in a single call + if (gf_filter_pid_would_block(ctx->opid)) { + ctx->resume_from = (u32) ( (char *)start - (char *)data); + return GF_OK; + } + } + gf_filter_pid_drop_packet(ctx->ipid); + + return GF_OK; +} + +static void h263dmx_finalize(GF_Filter *filter) +{ + GF_H263DmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs) gf_bs_del(ctx->bs); + if (ctx->indexes) gf_free(ctx->indexes); +} + +static const char * h263dmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + u32 nb_frames=0; + u32 max_nb_frames=0; + u32 prev_fmt=0; + u32 nb_fail=0; + s32 current = h263dmx_next_start_code((u8*)data, size); + while (size && (current>=0) && (current< (s32) size)) { + u32 fmt=0; + data += current; + size -= current; + + GF_BitStream *bs = gf_bs_new((u8 *)data, size, GF_BITSTREAM_READ); + + /*parse header*/ + gf_bs_read_int(bs, 22); + gf_bs_read_int(bs, 8); + gf_bs_read_int(bs, 5); + + fmt = gf_bs_read_int(bs, 3); + gf_bs_del(bs); + + if (fmt>=1 && (fmt<=5)) { + if (!prev_fmt || (prev_fmt==fmt)) { + nb_frames++; + } else { + if (nb_frames>max_nb_frames) { + max_nb_frames = nb_frames; + } + } + prev_fmt=fmt; + } else { + nb_frames=0; + nb_fail++; + break; + } + current = h263dmx_next_start_code((u8*)data+1, size-1); + if (current<=0) break; + current++; + if ((s32) size < current) break; + } + if (nb_fail) return NULL; + if (nb_frames>max_nb_frames) { + max_nb_frames = nb_frames; + } + + if (max_nb_frames>1) { + // *score = max_nb_frames>4 ? GF_FPROBE_SUPPORTED : GF_FPROBE_MAYBE_SUPPORTED; + //above fails the following test: + //gpac -mem-track -mem-track-stack -blacklist=nvdec -i results/temp/hevc-split-merge/high_832x0.hvc @ vout + //returning a score GF_FPROBE_SUPPORTED conflicting with naludmx_probe_data which also returns GF_FPROBE_SUPPORTED + //TODO Change the following code line in order that only naludmx_probe_data is GF_FPROBE_SUPPORTED: + //Tips: "nb_frames of naludmx is larger than the one of h263" may be considered. + *score = GF_FPROBE_MAYBE_SUPPORTED; + return "video/h263"; + } + return NULL; +} + +static const GF_FilterCapability H263DmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "263|h263|s263"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "video/h263"), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_S263), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_H263), + CAP_BOOL(GF_CAPS_OUTPUT_STATIC_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_S263), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_H263), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + +#define OFFS(_n) #_n, offsetof(GF_H263DmxCtx, _n) +static const GF_FilterArgs H263DmxArgs[] = +{ + { OFFS(fps), "import frame rate", GF_PROP_FRACTION, "15000/1000", NULL, 0}, + { OFFS(index), "indexing window length", GF_PROP_DOUBLE, "1.0", NULL, 0}, + {0} +}; + + +GF_FilterRegister H263DmxRegister = { + .name = "rfh263", + GF_FS_SET_DESCRIPTION("H263 reframer") + GF_FS_SET_HELP("This filter parses H263 files/data and outputs corresponding visual PID and frames.") + .private_size = sizeof(GF_H263DmxCtx), + .args = H263DmxArgs, + .finalize = h263dmx_finalize, + SETCAPS(H263DmxCaps), + .configure_pid = h263dmx_configure_pid, + .process = h263dmx_process, + .probe_data = h263dmx_probe_data, + .process_event = h263dmx_process_event +}; + + +const GF_FilterRegister *h263dmx_register(GF_FilterSession *session) +{ + return &H263DmxRegister; +} diff --git a/src/filters/reframe_img.c b/src/filters/reframe_img.c new file mode 100644 index 0000000..8276347 --- /dev/null +++ b/src/filters/reframe_img.c @@ -0,0 +1,434 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / image (jpg/png/bmp/j2k) reframer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + + +#if defined(WIN32) || defined(_WIN32_WCE) +#include +#else + +#ifdef GPAC_CONFIG_LINUX +#include +#endif + +typedef struct tagBITMAPFILEHEADER +{ + u16 bfType; + u32 bfSize; + u16 bfReserved1; + u16 bfReserved2; + u32 bfOffBits; +} BITMAPFILEHEADER; + +typedef struct tagBITMAPINFOHEADER { + u32 biSize; + s32 biWidth; + s32 biHeight; + u16 biPlanes; + u16 biBitCount; + u32 biCompression; + u32 biSizeImage; + s32 biXPelsPerMeter; + s32 biYPelsPerMeter; + u32 biClrUsed; + u32 biClrImportant; +} BITMAPINFOHEADER; + +#define BI_RGB 0L + +#endif + +typedef struct +{ + //options + GF_Fraction fps; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + u32 src_timescale; + Bool is_bmp; + Bool owns_timescale; + u32 codec_id; + + Bool initial_play_done; + Bool is_playing; +} GF_ReframeImgCtx; + + +GF_Err img_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_ReframeImgCtx *ctx = gf_filter_get_udta(filter); + const GF_PropertyValue *p; + + if (is_remove) { + ctx->ipid = NULL; + return GF_OK; + } + + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + ctx->ipid = pid; + //force retest of codecid + ctx->codec_id = 0; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) ctx->src_timescale = p->value.uint; + + if (ctx->src_timescale && !ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + } + ctx->is_playing = GF_TRUE; + return GF_OK; +} + +Bool img_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_FilterEvent fevt; + GF_ReframeImgCtx *ctx = gf_filter_get_udta(filter); + if (evt->base.on_pid != ctx->opid) return GF_TRUE; + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (ctx->is_playing) { + return GF_TRUE; + } + + ctx->is_playing = GF_TRUE; + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + return GF_TRUE; + } + + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = 0; + gf_filter_pid_send_event(ctx->ipid, &fevt); + return GF_TRUE; + case GF_FEVT_STOP: + ctx->is_playing = GF_FALSE; + return GF_FALSE; + default: + break; + } + //cancel all events + return GF_TRUE; +} + +GF_Err img_process(GF_Filter *filter) +{ + GF_ReframeImgCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + GF_Err e; + u8 *data, *output; + u32 size, w=0, h=0, pf=0; + u8 *pix; + u32 i, j, irow, in_stride, out_stride; + GF_BitStream *bs; + BITMAPFILEHEADER fh; + BITMAPINFOHEADER fi; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + ctx->is_playing = GF_FALSE; + return GF_EOS; + } + return GF_OK; + } + data = (char *) gf_filter_pck_get_data(pck, &size); + + if (!ctx->opid || !ctx->codec_id) { +#ifndef GPAC_DISABLE_AV_PARSERS + u32 dsi_size; + u8 *dsi=NULL; +#endif + const char *ext, *mime; + const GF_PropertyValue *prop; + u32 codecid = 0; + + if ((size >= 54) && (data[0] == 'B') && (data[1] == 'M')) { + codecid = GF_CODECID_RAW; + ctx->is_bmp = GF_TRUE; + } +#ifndef GPAC_DISABLE_AV_PARSERS + else { + bs = gf_bs_new(data, size, GF_BITSTREAM_READ); + gf_img_parse(bs, &codecid, &w, &h, &dsi, &dsi_size); + gf_bs_del(bs); + } +#endif + + prop = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_EXT); + ext = (prop && prop->value.string) ? prop->value.string : ""; + prop = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_MIME); + mime = (prop && prop->value.string) ? prop->value.string : ""; + + if (!codecid) { + if (!stricmp(ext, "jpeg") || !stricmp(ext, "jpg") || !strcmp(mime, "image/jpg")) { + codecid = GF_CODECID_JPEG; + } else if (!stricmp(ext, "png") || !strcmp(mime, "image/png")) { + codecid = GF_CODECID_PNG; + } else if (!stricmp(ext, "jp2") || !stricmp(ext, "j2k") || !strcmp(mime, "image/jp2")) { + codecid = GF_CODECID_J2K; + } else if (!stricmp(ext, "pngd")) { + codecid = GF_CODECID_PNG; + pf = GF_PIXEL_RGBD; + } else if (!stricmp(ext, "pngds")) { + codecid = GF_CODECID_PNG; + pf = GF_PIXEL_RGBDS; + } else if (!stricmp(ext, "pngs")) { + codecid = GF_CODECID_PNG; + pf = GF_PIXEL_RGBS; + } else if (!stricmp(ext, "bmp") || !strcmp(mime, "image/png")) { + codecid = GF_CODECID_RAW; + } + } + if (!codecid) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NOT_SUPPORTED; + } + ctx->codec_id = codecid; + ctx->opid = gf_filter_pid_new(filter); + if (!ctx->opid) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_SERVICE_ERROR; + } + if (!ctx->fps.num || !ctx->fps.den) { + ctx->fps.num = 1000; + ctx->fps.den = 1000; + } + //we don't have input reconfig for now + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT(GF_STREAM_VISUAL)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(codecid)); + if (pf) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, & PROP_UINT(pf)); + if (w) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, & PROP_UINT(w)); + if (h) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, & PROP_UINT(h)); +#ifndef GPAC_DISABLE_AV_PARSERS + if (dsi) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, & PROP_DATA_NO_COPY(dsi, dsi_size)); +#endif + if (! gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_TIMESCALE)) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->fps.num) ); + ctx->owns_timescale = GF_TRUE; + } + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NB_FRAMES, &PROP_UINT(1) ); + + if (ext || mime) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, & PROP_BOOL(GF_TRUE ) ); + } + if (! ctx->is_bmp) { + e = GF_OK; + u32 start_offset = 0; + if (ctx->codec_id==GF_CODECID_J2K) { + + if (size<8) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NON_COMPLIANT_BITSTREAM; + } + + if ((data[4]=='j') && (data[5]=='P') && (data[6]==' ') && (data[7]==' ')) { + bs = gf_bs_new(data, size, GF_BITSTREAM_READ); + while (gf_bs_available(bs)) { + u32 bsize = gf_bs_read_u32(bs); + u32 btype = gf_bs_read_u32(bs); + if (btype == GF_4CC('j','p','2','c') ) { + start_offset = (u32) gf_bs_get_position(bs) - 8; + break; + } + gf_bs_skip_bytes(bs, bsize-8); + } + gf_bs_del(bs); + if (start_offset>=size) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NON_COMPLIANT_BITSTREAM; + } + } + } + dst_pck = gf_filter_pck_new_ref(ctx->opid, data+start_offset, size-start_offset, pck); + if (!dst_pck) e = GF_OUT_OF_MEM; + gf_filter_pck_merge_properties(pck, dst_pck); + if (ctx->owns_timescale) { + gf_filter_pck_set_cts(dst_pck, 0); + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1 ); + gf_filter_pck_set_duration(dst_pck, ctx->fps.den); + } + gf_filter_pck_send(dst_pck); + gf_filter_pid_drop_packet(ctx->ipid); + return e; + } + + bs = gf_bs_new(data, size, GF_BITSTREAM_READ); + + /*fh.bfType = */ gf_bs_read_u16(bs); + /*fh.bfSize = */ gf_bs_read_u32(bs); + /*fh.bfReserved1 = */ gf_bs_read_u16(bs); + /*fh.bfReserved2 = */ gf_bs_read_u16(bs); + fh.bfOffBits = gf_bs_read_u32(bs); + fh.bfOffBits = ntohl(fh.bfOffBits); + + gf_bs_read_data(bs, (char *) &fi, 40); + gf_bs_del(bs); + + if ((fi.biCompression != BI_RGB) || (fi.biPlanes!=1)) return GF_NOT_SUPPORTED; + if ((fi.biBitCount!=24) && (fi.biBitCount!=32)) return GF_NOT_SUPPORTED; + + w = fi.biWidth; + h = fi.biHeight; + pf = (fi.biBitCount==24) ? GF_PIXEL_RGB : GF_PIXEL_RGBA; + size = (fi.biBitCount==24) ? 3 : 4; + size *= w; + out_stride = size; + size *= h; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, & PROP_UINT(pf)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, & PROP_UINT(w)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, & PROP_UINT(h)); + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + gf_filter_pck_merge_properties(pck, dst_pck); + if (ctx->owns_timescale) { + gf_filter_pck_set_cts(dst_pck, 0); + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1 ); + gf_filter_pck_set_duration(dst_pck, ctx->fps.den); + } + + in_stride = out_stride; + while (in_stride % 4) in_stride++; + + if (fi.biBitCount==24) { + for (i=0; iipid); + return e; +} + +#include + +static const char * img_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + /*JPEG*/ + if ((data[0]==0xFF) && (data[1]==0xD8) && (data[2]==0xFF)) { + *score = GF_FPROBE_SUPPORTED; + return "image/jpg"; + } + /*PNG*/ + if ((data[0]==0x89) && (data[1]==0x50) && (data[2]==0x4E)) { + *score = GF_FPROBE_SUPPORTED; + return "image/png"; + } + GF_BitStream *bs = gf_bs_new(data, size, GF_BITSTREAM_READ); + u32 bsize = gf_bs_read_u32(bs); + u32 btype = gf_bs_read_u32(bs); + if ( (bsize==12) && ( (btype==GF_ISOM_BOX_TYPE_JP ) || (btype==GF_ISOM_BOX_TYPE_JP2H) ) ) { + if (btype==GF_ISOM_BOX_TYPE_JP2H) { + *score = GF_FPROBE_FORCE; + gf_bs_del(bs); + return "image/jp2"; + } + btype = gf_bs_read_u32(bs); + if (btype==0x0D0A870A) { + *score = GF_FPROBE_FORCE; + gf_bs_del(bs); + return "image/jp2"; + } + } + gf_bs_del(bs); + if ((size >= 54) && (data[0] == 'B') && (data[1] == 'M')) { + *score = GF_FPROBE_SUPPORTED; + return "image/bmp"; + } + return NULL; +} +static const GF_FilterCapability ReframeImgCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "jpg|jpeg|jp2|bmp|png|pngd|pngds|pngs"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "image/jpg|image/jp2|image/bmp|image/png|image/x-png+depth|image/x-png+depth+mask|image/x-png+stereo"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_PNG), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_JPEG), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_J2K), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +// CAP_BOOL(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + +#define OFFS(_n) #_n, offsetof(GF_ReframeImgCtx, _n) +static const GF_FilterArgs ReframeImgArgs[] = +{ + { OFFS(fps), "import frame rate (0 default to 1 Hz)", GF_PROP_FRACTION, "0/1000", NULL, GF_FS_ARG_HINT_HIDE}, + {0} +}; + +GF_FilterRegister ReframeImgRegister = { + .name = "rfimg", + GF_FS_SET_DESCRIPTION("JPG/J2K/PNG/BMP reframer") + GF_FS_SET_HELP("This filter parses JPG/J2K/PNG/BMP files/data and outputs corresponding visual PID and frames.") + .private_size = sizeof(GF_ReframeImgCtx), + .args = ReframeImgArgs, + SETCAPS(ReframeImgCaps), + .configure_pid = img_configure_pid, + .probe_data = img_probe_data, + .process = img_process, + .process_event = img_process_event +}; + +const GF_FilterRegister *img_reframe_register(GF_FilterSession *session) +{ + return &ReframeImgRegister; +} + + diff --git a/src/filters/reframe_latm.c b/src/filters/reframe_latm.c new file mode 100644 index 0000000..8dd051a --- /dev/null +++ b/src/filters/reframe_latm.c @@ -0,0 +1,637 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / AAC ADTS reframer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_AV_PARSERS + +typedef struct +{ + u64 pos; + Double duration; +} LATMIdx; + +typedef struct +{ + //filter args + u32 frame_size; + Double index; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + GF_BitStream *bs; + u64 file_pos, cts; + u32 sr_idx, nb_ch, base_object_type; + GF_Fraction64 duration; + Double start_range; + Bool in_seek; + u32 timescale; + + GF_M4ADecSpecInfo acfg; + + char *latm_buffer; + u32 latm_buffer_size, latm_buffer_alloc; + u32 dts_inc; + + Bool is_playing; + Bool is_file, file_loaded; + Bool initial_play_done; + + GF_FilterPacket *src_pck; + + LATMIdx *indexes; + u32 index_alloc_size, index_size; + u32 resume_from; +} GF_LATMDmxCtx; + + +static Bool latm_dmx_sync_frame_bs(GF_BitStream *bs, GF_M4ADecSpecInfo *acfg, u32 *nb_bytes, u8 *buffer, u32 *nb_skipped) +{ + u32 val, size; + u64 pos, mux_size; + if (nb_skipped) *nb_skipped = 0; + if (!acfg) return 0; + + while (gf_bs_available(bs)>3) { + val = gf_bs_read_u8(bs); + if (val!=0x56) { + if (nb_skipped) (*nb_skipped) ++; + continue; + } + val = gf_bs_read_int(bs, 3); + if (val != 0x07) { + gf_bs_read_int(bs, 5); + if (nb_skipped) (*nb_skipped) ++; + continue; + } + mux_size = gf_bs_read_int(bs, 13); + pos = gf_bs_get_position(bs); + if (mux_size>gf_bs_available(bs) ) { + gf_bs_seek(bs, pos-3); + return GF_FALSE; + } + + /*use same stream mux*/ + if (!gf_bs_read_int(bs, 1)) { + Bool amux_version, amux_versionA; + + amux_version = (Bool)gf_bs_read_int(bs, 1); + amux_versionA = GF_FALSE; + if (amux_version) amux_versionA = (Bool)gf_bs_read_int(bs, 1); + if (!amux_versionA) { + u32 i, allStreamsSameTimeFraming, numProgram; + if (amux_version) gf_latm_get_value(bs); + + allStreamsSameTimeFraming = gf_bs_read_int(bs, 1); + /*numSubFrames = */gf_bs_read_int(bs, 6); + numProgram = gf_bs_read_int(bs, 4); + for (i=0; i<=numProgram; i++) { + u32 j, num_lay; + num_lay = gf_bs_read_int(bs, 3); + for (j=0; j<=num_lay; j++) { + u32 frameLengthType; + Bool same_cfg = GF_FALSE; + if (i || j) same_cfg = (Bool)gf_bs_read_int(bs, 1); + + if (!same_cfg) { + if (amux_version==1) gf_latm_get_value(bs); + gf_m4a_parse_config(bs, acfg, GF_FALSE); + } + frameLengthType = gf_bs_read_int(bs, 3); + if (!frameLengthType) { + /*latmBufferFullness = */gf_bs_read_int(bs, 8); + if (!allStreamsSameTimeFraming) { + } + } else { + /*not supported*/ + } + } + + } + /*other data present*/ + if (gf_bs_read_int(bs, 1)) { +// u32 k = 0; + } + /*CRCcheck present*/ + if (gf_bs_read_int(bs, 1)) { + } + } + } + + size = 0; + while (1) { + u32 tmp = gf_bs_read_int(bs, 8); + size += tmp; + if (tmp!=255) break; + } + if (gf_bs_available(bs) < size) { + gf_bs_seek(bs, pos-3); + return GF_FALSE; + } + + if (nb_bytes) { + *nb_bytes = (u32) size; + } + + if (buffer) { + gf_bs_read_data(bs, (char *) buffer, size); + } else { + while (size) { + gf_bs_read_int(bs, 8); + size--; + } + } + + /*parse amux*/ + gf_bs_seek(bs, pos + mux_size); + + if ((gf_bs_available(bs)>2) && gf_bs_peek_bits(bs, 11, 0) != 0x2B7) { + gf_bs_seek(bs, pos + 1); + if (nb_skipped) (*nb_skipped) ++; + continue; + } + + return GF_TRUE; + } + return GF_FALSE; +} + + + +GF_Err latm_dmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_LATMDmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) ctx->timescale = p->value.uint; + + if (ctx->timescale && !ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + } + + return GF_OK; +} + +static void latm_dmx_check_dur(GF_Filter *filter, GF_LATMDmxCtx *ctx) +{ + FILE *stream; + GF_BitStream *bs; + GF_M4ADecSpecInfo acfg; + u64 duration, cur_dur, cur_pos; + s32 sr_idx = -1; + const GF_PropertyValue *p; + if (!ctx->opid || ctx->timescale || ctx->file_loaded) return; + + if (ctx->index<=0) { + ctx->file_loaded = GF_TRUE; + return; + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) { + ctx->is_file = GF_FALSE; + ctx->file_loaded = GF_TRUE; + return; + } + ctx->is_file = GF_TRUE; + + stream = gf_fopen(p->value.string, "rb"); + if (!stream) return; + + ctx->index_size = 0; + + memset(&acfg, 0, sizeof(GF_M4ADecSpecInfo)); + + + bs = gf_bs_from_file(stream, GF_BITSTREAM_READ); + duration = 0; + cur_dur = 0; + cur_pos = gf_bs_get_position(bs); + while (latm_dmx_sync_frame_bs(bs, &acfg, 0, NULL, NULL)) { + if ((sr_idx>=0) && (sr_idx != acfg.base_sr_index)) { + duration *= GF_M4ASampleRates[acfg.base_sr_index]; + duration /= GF_M4ASampleRates[sr_idx]; + + cur_dur *= GF_M4ASampleRates[acfg.base_sr_index]; + cur_dur /= GF_M4ASampleRates[sr_idx]; + } + sr_idx = acfg.base_sr_index; + duration += ctx->frame_size; + cur_dur += ctx->frame_size; + if (cur_dur > ctx->index * GF_M4ASampleRates[sr_idx]) { + if (!ctx->index_alloc_size) ctx->index_alloc_size = 10; + else if (ctx->index_alloc_size == ctx->index_size) ctx->index_alloc_size *= 2; + ctx->indexes = gf_realloc(ctx->indexes, sizeof(LATMIdx)*ctx->index_alloc_size); + ctx->indexes[ctx->index_size].pos = cur_pos; + ctx->indexes[ctx->index_size].duration = (Double) duration; + ctx->indexes[ctx->index_size].duration /= GF_M4ASampleRates[sr_idx]; + ctx->index_size ++; + cur_dur = 0; + } + + cur_pos = gf_bs_get_position(bs); + } + gf_bs_del(bs); + gf_fclose(stream); + + if (sr_idx>=0) { + if (!ctx->duration.num || (ctx->duration.num * GF_M4ASampleRates[sr_idx] != duration * ctx->duration.den)) { + ctx->duration.num = (s32) duration; + ctx->duration.den = GF_M4ASampleRates[sr_idx]; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + } + } + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, & PROP_BOOL(GF_TRUE ) ); +} + +static void latm_dmx_check_pid(GF_Filter *filter, GF_LATMDmxCtx *ctx) +{ + u8 *dsi_b; + u32 dsi_s, sr, timescale=0; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + latm_dmx_check_dur(filter, ctx); + } + + if ((ctx->sr_idx == ctx->acfg.base_sr_index) && (ctx->nb_ch == ctx->acfg.nb_chan ) + && (ctx->base_object_type == ctx->acfg.base_object_type) ) return; + + //copy properties at init or reconfig + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT( GF_STREAM_AUDIO)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT( GF_CODECID_AAC_MPEG4)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLES_PER_FRAME, & PROP_UINT(ctx->frame_size) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, & PROP_BOOL(GF_FALSE) ); + if (ctx->is_file && ctx->index) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, & PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD) ); + } + if (ctx->duration.num) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + + + ctx->nb_ch = ctx->acfg.nb_chan; + ctx->base_object_type = ctx->acfg.base_object_type; + + sr = GF_M4ASampleRates[ctx->acfg.base_sr_index]; + if (!ctx->timescale) { + //we change sample rate, change cts + if (ctx->cts && (ctx->sr_idx != ctx->acfg.base_sr_index)) { + ctx->cts *= sr; + ctx->cts /= GF_M4ASampleRates[ctx->sr_idx]; + } + } + ctx->sr_idx = ctx->acfg.base_sr_index; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(GF_CODECID_AAC_MPEG4) ); + + ctx->dts_inc = ctx->frame_size; + gf_m4a_write_config(&ctx->acfg, &dsi_b, &dsi_s); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, & PROP_DATA_NO_COPY(dsi_b, dsi_s) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PROFILE_LEVEL, & PROP_UINT (ctx->acfg.audioPL) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, & PROP_UINT(sr)); + + timescale = sr; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->timescale ? ctx->timescale : timescale)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, & PROP_UINT(ctx->nb_ch) ); + +} + +static Bool latm_dmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i; + GF_FilterEvent fevt; + GF_LATMDmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (!ctx->is_playing) { + ctx->is_playing = GF_TRUE; + ctx->cts = 0; + } + if (! ctx->is_file) { + if (evt->play.start_range || ctx->initial_play_done) { + ctx->latm_buffer_size = 0; + ctx->resume_from = 0; + } + + ctx->initial_play_done = GF_TRUE; + return GF_FALSE; + } + ctx->start_range = evt->play.start_range; + ctx->in_seek = GF_TRUE; + ctx->file_pos = 0; + if (ctx->start_range) { + for (i=1; iindex_size; i++) { + if (ctx->indexes[i].duration>ctx->start_range) { + ctx->cts = (u64) (ctx->indexes[i-1].duration * GF_M4ASampleRates[ctx->sr_idx]); + ctx->file_pos = ctx->indexes[i-1].pos; + break; + } + } + } + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!ctx->file_pos) + return GF_TRUE; + } + ctx->latm_buffer_size = 0; + ctx->resume_from = 0; + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = ctx->file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + //don't cancel event + ctx->is_playing = GF_FALSE; + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +static GFINLINE void latm_dmx_update_cts(GF_LATMDmxCtx *ctx) +{ + assert(ctx->dts_inc); + + if (ctx->timescale) { + u64 inc = ctx->dts_inc; + inc *= ctx->timescale; + inc /= GF_M4ASampleRates[ctx->sr_idx]; + ctx->cts += inc; + } else { + ctx->cts += ctx->dts_inc; + } +} + +GF_Err latm_dmx_process(GF_Filter *filter) +{ + GF_LATMDmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + u32 pos; + u8 *data, *output; + u32 pck_size, prev_pck_size; + u64 cts = GF_FILTER_NO_TS; + + //always reparse duration + if (!ctx->duration.num) + latm_dmx_check_dur(filter, ctx); + + if (ctx->opid && !ctx->is_playing) + return GF_OK; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (!ctx->latm_buffer_size) { + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = NULL; + return GF_EOS; + } + } else { + return GF_OK; + } + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + + //input pid sets some timescale - we flushed pending data , update cts + if (ctx->timescale && pck) { + cts = gf_filter_pck_get_cts(pck); + } + + prev_pck_size = ctx->latm_buffer_size; + + if (pck && !ctx->resume_from) { + if (ctx->latm_buffer_size + pck_size > ctx->latm_buffer_alloc) { + ctx->latm_buffer_alloc = ctx->latm_buffer_size + pck_size; + ctx->latm_buffer = gf_realloc(ctx->latm_buffer, ctx->latm_buffer_alloc); + } + memcpy(ctx->latm_buffer + ctx->latm_buffer_size, data, pck_size); + ctx->latm_buffer_size += pck_size; + } + + if (!ctx->bs) ctx->bs = gf_bs_new(ctx->latm_buffer, ctx->latm_buffer_size, GF_BITSTREAM_READ); + else gf_bs_reassign_buffer(ctx->bs, ctx->latm_buffer, ctx->latm_buffer_size); + + if (ctx->resume_from) { + gf_bs_seek(ctx->bs, ctx->resume_from-1); + ctx->resume_from = 0; + } + + if (cts == GF_FILTER_NO_TS) + prev_pck_size = 0; + + + while (1) { + pos = (u32) gf_bs_get_position(ctx->bs); + u8 latm_buffer[4096]; + u32 latm_frame_size = 4096; + if (!latm_dmx_sync_frame_bs(ctx->bs,&ctx->acfg, &latm_frame_size, latm_buffer, NULL)) break; + + if (ctx->in_seek) { + u64 nb_samples_at_seek = (u64) (ctx->start_range * GF_M4ASampleRates[ctx->sr_idx]); + if (ctx->cts + ctx->dts_inc >= nb_samples_at_seek) { + //u32 samples_to_discard = (ctx->cts + ctx->dts_inc) - nb_samples_at_seek; + ctx->in_seek = GF_FALSE; + } + } + + latm_dmx_check_pid(filter, ctx); + + if (!ctx->is_playing) { + ctx->resume_from = pos+1; + return GF_OK; + } + + if (!ctx->in_seek) { + GF_FilterSAPType sap = GF_FILTER_SAP_1; + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, latm_frame_size, &output); + if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, dst_pck); + + memcpy(output, latm_buffer, latm_frame_size); + + gf_filter_pck_set_cts(dst_pck, ctx->cts); + gf_filter_pck_set_duration(dst_pck, ctx->dts_inc); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + + /*xHE-AAC, check RAP*/ + if (ctx->acfg.base_object_type==42) { + if (latm_frame_size && (output[0] & 0x80)) { + sap = GF_FILTER_SAP_1; + } else { + sap = GF_FILTER_SAP_NONE; + } + } + gf_filter_pck_set_sap(dst_pck, sap); + + gf_filter_pck_send(dst_pck); + } + latm_dmx_update_cts(ctx); + + if (prev_pck_size) { + pos = (u32) gf_bs_get_position(ctx->bs); + if (prev_pck_size<=pos) { + prev_pck_size=0; + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = pck; + if (pck) + gf_filter_pck_ref_props(&ctx->src_pck); + } + } + } + + if (pck) { + pos = (u32) gf_bs_get_position(ctx->bs); + assert(ctx->latm_buffer_size >= pos); + memmove(ctx->latm_buffer, ctx->latm_buffer+pos, ctx->latm_buffer_size - pos); + ctx->latm_buffer_size -= pos; + gf_filter_pid_drop_packet(ctx->ipid); + assert(!ctx->resume_from); + } else { + ctx->latm_buffer_size = 0; + return latm_dmx_process(filter); + } + return GF_OK; +} + +static void latm_dmx_finalize(GF_Filter *filter) +{ + GF_LATMDmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs) gf_bs_del(ctx->bs); + if (ctx->indexes) gf_free(ctx->indexes); + if (ctx->latm_buffer) gf_free(ctx->latm_buffer); +} + +static const char *latm_dmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + u32 nb_frames=0; + GF_M4ADecSpecInfo acfg; + GF_BitStream *bs = gf_bs_new(data, size, GF_BITSTREAM_READ); + while (1) { + u32 nb_skipped = 0; + if (!latm_dmx_sync_frame_bs(bs, &acfg, 0, NULL, &nb_skipped)) break; + if (nb_skipped) + nb_frames=0; + nb_frames++; + } + gf_bs_del(bs); + if (nb_frames>=2) { + *score = GF_FPROBE_SUPPORTED; + return "audio/aac+latm"; + } + return NULL; +} + +static const GF_FilterCapability LATMDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "latm"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "audio/aac+latm"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG4), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_MP), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_LCP), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_SSRP), + CAP_BOOL(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG4), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED_LATM, GF_TRUE), +}; + + +#define OFFS(_n) #_n, offsetof(GF_LATMDmxCtx, _n) +static const GF_FilterArgs LATMDmxArgs[] = +{ + { OFFS(frame_size), "size of AAC frame in audio samples", GF_PROP_UINT, "1024", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(index), "indexing window length", GF_PROP_DOUBLE, "1.0", NULL, 0}, + {0} +}; + + +GF_FilterRegister LATMDmxRegister = { + .name = "rflatm", + GF_FS_SET_DESCRIPTION("LATM reframer") + GF_FS_SET_HELP("This filter parses AAC in LATM files/data and outputs corresponding audio PID and frames.") + .private_size = sizeof(GF_LATMDmxCtx), + .args = LATMDmxArgs, + .finalize = latm_dmx_finalize, + SETCAPS(LATMDmxCaps), + .configure_pid = latm_dmx_configure_pid, + .process = latm_dmx_process, + .probe_data = latm_dmx_probe_data, + .process_event = latm_dmx_process_event +}; + + +const GF_FilterRegister *latm_dmx_register(GF_FilterSession *session) +{ + return &LATMDmxRegister; +} + +#else +const GF_FilterRegister *latm_dmx_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // GPAC_DISABLE_AV_PARSERS diff --git a/src/filters/reframe_mp3.c b/src/filters/reframe_mp3.c new file mode 100644 index 0000000..524b07d --- /dev/null +++ b/src/filters/reframe_mp3.c @@ -0,0 +1,820 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / MP3 reframer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +#ifndef GPAC_DISABLE_AV_PARSERS + +typedef struct +{ + u64 pos; + Double duration; +} MP3Idx; + +typedef struct +{ + //filter args + Double index; + Bool expart; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + GF_BitStream *bs; + u64 file_pos, cts, prev_cts; + u32 sr, nb_ch, codecid; + GF_Fraction64 duration; + Double start_range; + Bool in_seek; + u32 timescale; + Bool is_playing; + Bool is_file; + Bool initial_play_done, file_loaded; + + u32 hdr; + + u8 *mp3_buffer; + u32 mp3_buffer_size, mp3_buffer_alloc, resume_from; + u64 byte_offset; + + GF_FilterPacket *src_pck; + + Bool recompute_cts; + MP3Idx *indexes; + u32 index_alloc_size, index_size; + + u32 tag_size; + u8 *id3_buffer; + u32 id3_buffer_size, id3_buffer_alloc; + + GF_FilterPid *vpid; +} GF_MP3DmxCtx; + + + + +GF_Err mp3_dmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_MP3DmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) ctx->timescale = p->value.uint; + + p = gf_filter_pid_get_property_str(pid, "nocts"); + if (p && p->value.boolean) ctx->recompute_cts = GF_TRUE; + else ctx->recompute_cts = GF_FALSE; + + if (ctx->timescale && !ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + } + return GF_OK; +} + +static void mp3_dmx_check_dur(GF_Filter *filter, GF_MP3DmxCtx *ctx) +{ + FILE *stream; + u64 duration, cur_dur; + s32 prev_sr = -1; + const GF_PropertyValue *p; + if (!ctx->opid || ctx->timescale || ctx->file_loaded) return; + + if (ctx->index<=0) { + ctx->file_loaded = GF_TRUE; + return; + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) { + ctx->is_file = GF_FALSE; + ctx->file_loaded = GF_TRUE; + return; + } + ctx->is_file = GF_TRUE; + + stream = gf_fopen(p->value.string, "rb"); + if (!stream) return; + + ctx->index_size = 0; + + duration = 0; + cur_dur = 0; + while (1) { + u32 sr, dur; + u64 pos; + u32 hdr = gf_mp3_get_next_header(stream); + if (!hdr) break; + sr = gf_mp3_sampling_rate(hdr); + + if ((prev_sr>=0) && (prev_sr != sr)) { + duration *= sr; + duration /= prev_sr; + + cur_dur *= sr; + cur_dur /= prev_sr; + } + prev_sr = sr; + dur = gf_mp3_window_size(hdr); + duration += dur; + cur_dur += dur; + pos = gf_ftell(stream); + if (cur_dur > ctx->index * prev_sr) { + if (!ctx->index_alloc_size) ctx->index_alloc_size = 10; + else if (ctx->index_alloc_size == ctx->index_size) ctx->index_alloc_size *= 2; + ctx->indexes = gf_realloc(ctx->indexes, sizeof(MP3Idx)*ctx->index_alloc_size); + ctx->indexes[ctx->index_size].pos = pos - 4; + ctx->indexes[ctx->index_size].duration = (Double) duration; + ctx->indexes[ctx->index_size].duration /= prev_sr; + ctx->index_size ++; + cur_dur = 0; + } + + pos = gf_ftell(stream); + gf_fseek(stream, pos + gf_mp3_frame_size(hdr) - 4, SEEK_SET); + } + gf_fclose(stream); + + if (!ctx->duration.num || (ctx->duration.num * prev_sr != duration * ctx->duration.den)) { + ctx->duration.num = (s32) duration; + ctx->duration.den = prev_sr ; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, & PROP_BOOL(GF_TRUE ) ); +} + + +#include +static void id3dmx_set_string(GF_FilterPid *apid, char *name, u8 *buf, Bool is_dyn) +{ + if ((buf[0]==0xFF) || (buf[0]==0xFE)) { + const u16 *sptr = (u16 *) (buf+2); + s32 len = (s32) ( UTF8_MAX_BYTES_PER_CHAR * gf_utf8_wcslen(sptr) ); + char *tmp = gf_malloc(len+1); + len = (s32) gf_utf8_wcstombs(tmp, len, &sptr); + if (len>=0) { + tmp[len] = 0; + if (is_dyn) { + gf_filter_pid_set_property_dyn(apid, name, &PROP_STRING(tmp) ); + } else { + gf_filter_pid_set_property_str(apid, name, &PROP_STRING(tmp) ); + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[MP3Dmx] Corrupted ID3 text frame %s\n", name)); + } + gf_free(tmp); + } else { + if (is_dyn) { + gf_filter_pid_set_property_dyn(apid, name, &PROP_STRING(buf) ); + } else { + gf_filter_pid_set_property_str(apid, name, &PROP_STRING(buf) ); + } + } +} + +void id3dmx_flush(GF_Filter *filter, u8 *id3_buf, u32 id3_buf_size, GF_FilterPid *audio_pid, GF_FilterPid **video_pid_p) +{ + GF_BitStream *bs = gf_bs_new(id3_buf, id3_buf_size, GF_BITSTREAM_READ); + char *sep_desc; + char *buf=NULL; + u32 buf_alloc=0; + gf_bs_skip_bytes(bs, 3); + /*u8 major = */gf_bs_read_u8(bs); + /*u8 minor = */gf_bs_read_u8(bs); + /*u8 unsync = */gf_bs_read_int(bs, 1); + u8 ext_hdr = gf_bs_read_int(bs, 1); + gf_bs_read_int(bs, 6); + u32 size = gf_id3_read_size(bs); + + + if (ext_hdr) { + + } + + while (size && (gf_bs_available(bs)>=10) ) { + char szTag[1024]; + char *sep; + s32 tag_idx; + u32 pic_size; + //u32 pic_type; + u32 ftag = gf_bs_read_u32(bs); + u32 fsize = gf_id3_read_size(bs); + /*u16 fflags = */gf_bs_read_u16(bs); + + size -= 10; + if (!fsize) + break; + + if (size=0) { + const char *tag_name = gf_itags_get_name((u32) tag_idx); + id3dmx_set_string(audio_pid, (char *) tag_name, buf+1, GF_FALSE); + } else if (ftag==GF_ID3V2_FRAME_TXXX) { + sep = memchr(buf, 0, fsize); + if (sep) { + if (!stricmp(buf+1, "comment")) { + id3dmx_set_string(audio_pid, "comment", sep+1, GF_FALSE); + } else { + strcpy(szTag, "tag_"); + strncat(szTag, buf+1, 1019); + id3dmx_set_string(audio_pid, szTag, sep+1, GF_TRUE); + } + } + } else if (ftag == GF_ID3V2_FRAME_APIC) { + //first char is text encoding + //then mime + sep = memchr(buf+1, 0, fsize-1); + /*pic_type = sep[1];*/ + sep_desc = memchr(sep+2, 0, fsize-1); + + if (sep_desc) { + GF_Err e; + pic_size = (u32) ( (sep_desc + 1) - buf); + pic_size = fsize - pic_size; + + if (video_pid_p) { + e = gf_filter_pid_raw_new(filter, NULL, NULL, buf+1, NULL, sep_desc+1, pic_size, GF_FALSE, video_pid_p); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[MP3Dmx] error setting up video pid for cover art: %s\n", gf_error_to_string(e) )); + } + if (*video_pid_p) { + u8 *out_buffer; + GF_FilterPacket *dst_pck; + gf_filter_pid_set_name(*video_pid_p, "CoverArt"); + gf_filter_pid_set_property(*video_pid_p, GF_PROP_PID_COVER_ART, &PROP_BOOL(GF_TRUE)); + dst_pck = gf_filter_pck_new_alloc(*video_pid_p, pic_size, &out_buffer); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + memcpy(out_buffer, sep_desc+1, pic_size); + gf_filter_pck_send(dst_pck); + + gf_filter_pid_set_eos(*video_pid_p); + } + } else { + gf_filter_pid_set_property(audio_pid, GF_PROP_PID_COVER_ART, &PROP_DATA(sep_desc+1, pic_size) ); + } + } + } else { + sprintf(szTag, "tag_%s", gf_4cc_to_str(ftag)); + if ((ftag>>24) == 'T') { + id3dmx_set_string(audio_pid, szTag, buf+1, GF_TRUE); + } else { + gf_filter_pid_set_property_dyn(audio_pid, szTag, &PROP_DATA(buf, fsize) ); + } + break; + } + size -= fsize; + } + gf_bs_del(bs); + if (buf) gf_free(buf); +} +static void mp3_dmx_flush_id3(GF_Filter *filter, GF_MP3DmxCtx *ctx) +{ + id3dmx_flush(filter, ctx->id3_buffer, ctx->id3_buffer_size, ctx->opid, ctx->expart ? &ctx->vpid : NULL); + ctx->id3_buffer_size = 0; +} + +static void mp3_dmx_check_pid(GF_Filter *filter, GF_MP3DmxCtx *ctx) +{ + u32 sr; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + mp3_dmx_check_dur(filter, ctx); + } + + if ((ctx->sr == gf_mp3_sampling_rate(ctx->hdr)) && (ctx->nb_ch == gf_mp3_num_channels(ctx->hdr) ) + && (ctx->codecid == gf_mp3_object_type_indication(ctx->hdr) ) + ) + return; + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT( GF_STREAM_AUDIO)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL ); + if (ctx->is_file && ctx->index) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, & PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD) ); + } + if (ctx->duration.num) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + + if (!ctx->timescale) gf_filter_pid_set_name(ctx->opid, "audio"); + + ctx->nb_ch = gf_mp3_num_channels(ctx->hdr); + ctx->codecid = gf_mp3_object_type_indication(ctx->hdr); + sr = gf_mp3_sampling_rate(ctx->hdr); + + if (!ctx->timescale) { + //we change sample rate, change cts + if (ctx->cts && ctx->sr && (ctx->sr != sr)) { + ctx->cts *= sr; + ctx->cts /= ctx->sr; + } + } + ctx->sr = sr; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->timescale ? ctx->timescale : sr)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, & PROP_UINT(sr)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, & PROP_UINT(ctx->nb_ch) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(gf_mp3_object_type_indication(ctx->hdr) ) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLES_PER_FRAME, & PROP_UINT(gf_mp3_window_size(ctx->hdr) ) ); + + if (ctx->id3_buffer_size) + mp3_dmx_flush_id3(filter, ctx); + +} + +static Bool mp3_dmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i; + GF_FilterEvent fevt; + GF_MP3DmxCtx *ctx = gf_filter_get_udta(filter); + + if (evt->base.on_pid != ctx->opid) return GF_TRUE; + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (!ctx->is_playing) { + ctx->is_playing = GF_TRUE; + } + if (! ctx->is_file) { + if (evt->play.start_range || ctx->initial_play_done) { + ctx->mp3_buffer_size = 0; + ctx->resume_from = 0; + } + ctx->initial_play_done = GF_TRUE; + return GF_FALSE; + } + mp3_dmx_check_dur(filter, ctx); + + ctx->start_range = evt->play.start_range; + ctx->in_seek = GF_TRUE; + ctx->file_pos = 0; + if (ctx->start_range) { + for (i=1; iindex_size; i++) { + if (ctx->indexes[i].duration>ctx->start_range) { + ctx->cts = (u64) (ctx->indexes[i-1].duration * ctx->sr); + ctx->file_pos = ctx->indexes[i-1].pos; + break; + } + } + } + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!ctx->file_pos) + return GF_TRUE; + } + ctx->mp3_buffer_size = 0; + ctx->resume_from = 0; + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = ctx->file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + ctx->is_playing = GF_FALSE; + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = NULL; + //don't cancel event + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +static GFINLINE void mp3_dmx_update_cts(GF_MP3DmxCtx *ctx) +{ + u32 nb_samp = gf_mp3_window_size(ctx->hdr); + if (ctx->timescale) { + u64 inc = nb_samp; + inc *= ctx->timescale; + inc /= ctx->sr; + ctx->cts += inc; + } else { + ctx->cts += nb_samp; + } +} + +GF_Err mp3_dmx_process(GF_Filter *filter) +{ + GF_MP3DmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + u8 *data, *output; + u8 *start; + u32 pck_size, remain, prev_pck_size; + u64 cts = GF_FILTER_NO_TS; + + //always reparse duration + if (!ctx->duration.num) + mp3_dmx_check_dur(filter, ctx); + + if (ctx->opid && !ctx->is_playing) + return GF_OK; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (!ctx->mp3_buffer_size) { + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = NULL; + return GF_EOS; + } + } else { + return GF_OK; + } + } + + prev_pck_size = ctx->mp3_buffer_size; + if (pck && !ctx->resume_from) { + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + + if (ctx->byte_offset != GF_FILTER_NO_BO) { + u64 byte_offset = gf_filter_pck_get_byte_offset(pck); + if (!ctx->mp3_buffer_size) { + ctx->byte_offset = byte_offset; + } else if (ctx->byte_offset + ctx->mp3_buffer_size != byte_offset) { + ctx->byte_offset = GF_FILTER_NO_BO; + if ((byte_offset != GF_FILTER_NO_BO) && (byte_offset>ctx->mp3_buffer_size) ) { + ctx->byte_offset = byte_offset - ctx->mp3_buffer_size; + } + } + } + + if (ctx->mp3_buffer_size + pck_size > ctx->mp3_buffer_alloc) { + ctx->mp3_buffer_alloc = ctx->mp3_buffer_size + pck_size; + ctx->mp3_buffer = gf_realloc(ctx->mp3_buffer, ctx->mp3_buffer_alloc); + } + memcpy(ctx->mp3_buffer + ctx->mp3_buffer_size, data, pck_size); + ctx->mp3_buffer_size += pck_size; + } + + //input pid sets some timescale - we flushed pending data , update cts + if (ctx->timescale && pck) { + cts = gf_filter_pck_get_cts(pck); + } + + if (cts == GF_FILTER_NO_TS) { + //avoids updating cts + prev_pck_size = 0; + } + + remain = ctx->mp3_buffer_size; + start = ctx->mp3_buffer; + + if (ctx->resume_from) { + start += ctx->resume_from - 1; + remain -= ctx->resume_from - 1; + ctx->resume_from = 0; + } + + while (remain) { + u8 *sync; + Bool skip_id3v1=GF_FALSE; + u32 bytes_skipped=0, size, nb_samp, bytes_to_drop=0;; + + if (!ctx->tag_size && (remain>3)) { + + /* Did we read an ID3v2 ? */ + if (start[0] == 'I' && start[1] == 'D' && start[2] == '3') { + if (remain<10) + return GF_OK; + + ctx->tag_size = ((start[9] & 0x7f) + ((start[8] & 0x7f) << 7) + ((start[7] & 0x7f) << 14) + ((start[6] & 0x7f) << 21)); + + bytes_to_drop = 10; + if (ctx->id3_buffer_alloc < ctx->tag_size+10) { + ctx->id3_buffer = gf_realloc(ctx->id3_buffer, ctx->tag_size+10); + ctx->id3_buffer_alloc = ctx->tag_size+10; + } + memcpy(ctx->id3_buffer, start, 10); + ctx->id3_buffer_size = 10; + goto drop_byte; + } + } + if (ctx->tag_size) { + if (ctx->tag_size>remain) { + bytes_to_drop = remain; + ctx->tag_size-=remain; + } else { + bytes_to_drop = ctx->tag_size; + ctx->tag_size = 0; + } + memcpy(ctx->id3_buffer + ctx->id3_buffer_size, start, bytes_to_drop); + ctx->id3_buffer_size += bytes_to_drop; + + if (!ctx->tag_size && ctx->opid) { + mp3_dmx_flush_id3(filter, ctx); + } + goto drop_byte; + + } + + ctx->hdr = gf_mp3_get_next_header_mem(start, remain, &bytes_skipped); + + //couldn't find sync byte in this packet + if (!ctx->hdr) { + break; + } + sync = start + bytes_skipped; + + size = gf_mp3_frame_size(ctx->hdr); + + + //ready to send packet + if (size + 1 < remain-bytes_skipped) { + //make sure we are sync! + if (sync[size] !=0xFF) { + if ((sync[size]=='T') && (sync[size+1]=='A') && (sync[size+2]=='G')) { + skip_id3v1=GF_TRUE; + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[MP3Dmx] invalid frame, resyncing\n")); + goto drop_byte; + } + } + } + //otherwise wait for next frame, unless if end of stream + else if (pck) { + break; + } + //ready to send packet + mp3_dmx_check_pid(filter, ctx); + + if (!ctx->is_playing) { + ctx->resume_from = (u32) (sync - ctx->mp3_buffer + 1); + return GF_OK; + } + + nb_samp = gf_mp3_window_size(ctx->hdr); + + if (ctx->in_seek) { + u64 nb_samples_at_seek = (u64) (ctx->start_range * ctx->sr); + if (ctx->cts + nb_samp >= nb_samples_at_seek) { + //u32 samples_to_discard = (ctx->cts + nb_samp ) - nb_samples_at_seek; + ctx->in_seek = GF_FALSE; + } + } + + bytes_to_drop = bytes_skipped + size; + if (ctx->timescale && !prev_pck_size && (cts != GF_FILTER_NO_TS) ) { + ctx->cts = cts; + cts = GF_FILTER_NO_TS; + } + + if (!ctx->in_seek) { + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + memcpy(output, sync, size); + + gf_filter_pck_set_cts(dst_pck, ctx->cts); + gf_filter_pck_set_duration(dst_pck, nb_samp); + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + + if (ctx->byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, ctx->byte_offset + bytes_skipped); + } + + gf_filter_pck_send(dst_pck); + } + mp3_dmx_update_cts(ctx); + + //TODO, parse id3v1 ?? + if (skip_id3v1) + bytes_to_drop+=128; + + //truncated last frame + if (bytes_to_drop>remain) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[MP3Dmx] truncated frame!\n")); + bytes_to_drop=remain; + } + +drop_byte: + if (!bytes_to_drop) { + bytes_to_drop = 1; + } + start += bytes_to_drop; + remain -= bytes_to_drop; + + if (prev_pck_size) { + if (prev_pck_size > bytes_to_drop) prev_pck_size -= bytes_to_drop; + else { + prev_pck_size=0; + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = pck; + if (pck) + gf_filter_pck_ref_props(&ctx->src_pck); + } + } + if (ctx->byte_offset != GF_FILTER_NO_BO) + ctx->byte_offset += bytes_to_drop; + } + + if (!pck) { + ctx->mp3_buffer_size = 0; + return mp3_dmx_process(filter); + } else { + if (remain) { + memmove(ctx->mp3_buffer, start, remain); + } + ctx->mp3_buffer_size = remain; + gf_filter_pid_drop_packet(ctx->ipid); + } + return GF_OK; +} + +static void mp3_dmx_finalize(GF_Filter *filter) +{ + GF_MP3DmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs) gf_bs_del(ctx->bs); + if (ctx->indexes) gf_free(ctx->indexes); + if (ctx->mp3_buffer) gf_free(ctx->mp3_buffer); + if (ctx->id3_buffer) gf_free(ctx->id3_buffer); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); +} + + +static const char *mp3_dmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + u32 nb_frames=0; + u32 pos=0; + u32 prev_pos=0; + Bool has_id3 = GF_FALSE; + + /* Check for ID3 */ + if (size>= 10) { + if (data[0] == 'I' && data[1] == 'D' && data[2] == '3') { + u32 tag_size = ((data[9] & 0x7f) + ((data[8] & 0x7f) << 7) + ((data[7] & 0x7f) << 14) + ((data[6] & 0x7f) << 21)); + + if (tag_size+10>size) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("ID3 tag detected size %d but probe data only %d bytes, will rely on file extension (try increasing probe size using --block_size)\n", tag_size+10, size)); + *score = GF_FPROBE_EXT_MATCH; + return "mp3|mp2|mp1"; + } + data += tag_size+10; + size -= tag_size+10; + has_id3 = GF_TRUE; + } + } + + while (1) { + u32 hdr = gf_mp3_get_next_header_mem(data, size, &pos); + if (!hdr) break; + + if (gf_mp3_version(hdr) > 3) + break; + u8 sampleRateIndex = (hdr >> 10) & 0x3; + if (sampleRateIndex>2) + break; + u32 fsize = gf_mp3_frame_size(hdr); + if (prev_pos && pos) { + nb_frames=0; + break; + } + + if (fsize + pos > size) { + nb_frames++; + break; + } + + prev_pos = pos; + nb_frames++; + if (nb_frames>4) break; + if (size < fsize + pos) break; + size -= fsize + pos; + data += fsize + pos; + } + + if (nb_frames>=2) { + *score = GF_FPROBE_SUPPORTED; + return "audio/mp3"; + } + if (nb_frames && has_id3) { + *score = GF_FPROBE_MAYBE_SUPPORTED; + return "audio/mp3"; + } + return NULL; +} + +static const GF_FilterCapability MP3DmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "mp3|mp2|mp1"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "audio/mp3|audio/x-mp3"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_MPEG_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_MPEG2_PART3), + CAP_BOOL(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG_AUDIO), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_PART3), + CAP_BOOL(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + //also declare generic file output for embedded files (cover art & co), but explicit to skip this cap in chain resolution + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_OUTPUT | GF_CAPFLAG_LOADED_FILTER ,GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE) +}; + + + +#define OFFS(_n) #_n, offsetof(GF_MP3DmxCtx, _n) +static const GF_FilterArgs MP3DmxArgs[] = +{ + { OFFS(index), "indexing window length", GF_PROP_DOUBLE, "1.0", NULL, 0}, + { OFFS(expart), "expose pictures as a dedicated video pid", GF_PROP_BOOL, "false", NULL, 0}, + {0} +}; + + +GF_FilterRegister MP3DmxRegister = { + .name = "rfmp3", + GF_FS_SET_DESCRIPTION("MP3 reframer") + GF_FS_SET_HELP("This filter parses MPEG-1/2 audio files/data and outputs corresponding audio PID and frames.") + .private_size = sizeof(GF_MP3DmxCtx), + .args = MP3DmxArgs, + .finalize = mp3_dmx_finalize, + SETCAPS(MP3DmxCaps), + .configure_pid = mp3_dmx_configure_pid, + .process = mp3_dmx_process, + .probe_data = mp3_dmx_probe_data, + .process_event = mp3_dmx_process_event +}; + + +const GF_FilterRegister *mp3_dmx_register(GF_FilterSession *session) +{ + return &MP3DmxRegister; +} + +#else +const GF_FilterRegister *mp3_dmx_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // GPAC_DISABLE_AV_PARSERS + diff --git a/src/filters/reframe_mpgvid.c b/src/filters/reframe_mpgvid.c new file mode 100644 index 0000000..3b0430b --- /dev/null +++ b/src/filters/reframe_mpgvid.c @@ -0,0 +1,1188 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / MPEG-1/2/4(Part2) video reframer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_AV_PARSERS + +typedef struct +{ + u64 pos; + Double start_time; +} MPGVidIdx; + +typedef struct +{ + //filter args + GF_Fraction fps; + Double index; + Bool vfr, importer; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + GF_BitStream *bs; + u64 cts, dts, prev_dts; + u32 width, height; + GF_Fraction64 duration; + Double start_range; + Bool in_seek; + u32 timescale; + + u32 resume_from; + GF_Fraction cur_fps; + + Bool is_mpg12, forced_packed; + GF_M4VParser *vparser; + GF_M4VDecSpecInfo dsi; + u32 b_frames; + Bool is_packed, is_vfr; + GF_List *pck_queue; + u64 last_ref_cts; + Bool frame_started; + + u32 nb_i, nb_p, nb_b, nb_frames, max_b; + + u32 bytes_in_header; + char *hdr_store; + u32 hdr_store_size, hdr_store_alloc; + + Bool is_playing; + Bool is_file, file_loaded; + Bool initial_play_done; + + Bool input_is_au_start, input_is_au_end; + Bool recompute_cts; + + GF_FilterPacket *src_pck; + + MPGVidIdx *indexes; + u32 index_alloc_size, index_size; +} GF_MPGVidDmxCtx; + + +GF_Err mpgviddmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + Bool was_mpeg12; + const GF_PropertyValue *p; + GF_MPGVidDmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + ctx->cur_fps = ctx->fps; + if (!ctx->fps.num || !ctx->fps.den) { + ctx->cur_fps.num = 25000; + ctx->cur_fps.den = 1000; + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) { + ctx->timescale = ctx->cur_fps.num = p->value.uint; + ctx->cur_fps.den = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (p) { + ctx->cur_fps = p->value.frac; + } + p = gf_filter_pid_get_property_str(pid, "nocts"); + if (p && p->value.boolean) ctx->recompute_cts = GF_TRUE; + } + + was_mpeg12 = ctx->is_mpg12; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (p) { + switch (p->value.uint) { + case GF_CODECID_MPEG1: + case GF_CODECID_MPEG2_422: + case GF_CODECID_MPEG2_SNR: + case GF_CODECID_MPEG2_HIGH: + case GF_CODECID_MPEG2_MAIN: + case GF_CODECID_MPEG2_SIMPLE: + case GF_CODECID_MPEG2_SPATIAL: + ctx->is_mpg12 = GF_TRUE; + break; + } + } + else { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_MIME); + if (p && p->value.string && (strstr(p->value.string, "m1v") || strstr(p->value.string, "m2v")) ) ctx->is_mpg12 = GF_TRUE; + else { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FILE_EXT); + if (p && p->value.string && (strstr(p->value.string, "m1v") || strstr(p->value.string, "m2v")) ) ctx->is_mpg12 = GF_TRUE; + } + } + + if (ctx->vparser && (was_mpeg12 != ctx->is_mpg12)) { + gf_m4v_parser_del_no_bs(ctx->vparser); + ctx->vparser = NULL; + } + + if (ctx->timescale && !ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + } + return GF_OK; +} + + +static void mpgviddmx_check_dur(GF_Filter *filter, GF_MPGVidDmxCtx *ctx) +{ + + FILE *stream; + GF_BitStream *bs; + GF_M4VParser *vparser; + GF_M4VDecSpecInfo dsi; + GF_Err e; + u64 duration, cur_dur; + const GF_PropertyValue *p; + if (!ctx->opid || ctx->timescale || ctx->file_loaded) return; + + if (ctx->index<=0) { + ctx->file_loaded = GF_TRUE; + return; + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) { + ctx->is_file = GF_FALSE; + ctx->file_loaded = GF_TRUE; + return; + } + ctx->is_file = GF_TRUE; + + stream = gf_fopen(p->value.string, "rb"); + if (!stream) return; + + ctx->index_size = 0; + + bs = gf_bs_from_file(stream, GF_BITSTREAM_READ); + + vparser = gf_m4v_parser_bs_new(bs, ctx->is_mpg12); + e = gf_m4v_parse_config(vparser, &dsi); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[MPGVid] Could not parse video header - duration not estimated\n")); + ctx->file_loaded = GF_TRUE; + return; + } + + duration = 0; + cur_dur = 0; + while (gf_bs_available(bs)) { + u8 ftype; + u32 tinc; + u64 fsize, start; + Bool is_coded; + u64 pos; + pos = gf_m4v_get_object_start(vparser); + e = gf_m4v_parse_frame(vparser, &dsi, &ftype, &tinc, &fsize, &start, &is_coded); + if (e<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[MPGVid] Could not parse video frame\n")); + continue; + } + + duration += ctx->cur_fps.den; + cur_dur += ctx->cur_fps.den; + //only index at I-frame start + if (pos && (ftype==0) && (cur_dur >= ctx->index * ctx->cur_fps.num) ) { + if (!ctx->index_alloc_size) ctx->index_alloc_size = 10; + else if (ctx->index_alloc_size == ctx->index_size) ctx->index_alloc_size *= 2; + ctx->indexes = gf_realloc(ctx->indexes, sizeof(MPGVidIdx)*ctx->index_alloc_size); + ctx->indexes[ctx->index_size].pos = pos; + ctx->indexes[ctx->index_size].start_time = (Double) (duration-ctx->cur_fps.den); + ctx->indexes[ctx->index_size].start_time /= ctx->cur_fps.num; + ctx->index_size ++; + cur_dur = 0; + } + } + gf_m4v_parser_del(vparser); + gf_fclose(stream); + + if (!ctx->duration.num || (ctx->duration.num * ctx->cur_fps.num != duration * ctx->duration.den)) { + ctx->duration.num = (s32) duration; + ctx->duration.den = ctx->cur_fps.num; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, & PROP_BOOL(GF_TRUE ) ); +} + + +static void mpgviddmx_enqueue_or_dispatch(GF_MPGVidDmxCtx *ctx, GF_FilterPacket *pck, Bool flush_ref, Bool is_eos) +{ + //TODO: we are dispatching frames in "negctts mode", ie we may have DTS>CTS + //need to signal this for consumers using DTS (eg MPEG-2 TS) + if (flush_ref && ctx->pck_queue) { + //send all reference packet queued + u32 i, count = gf_list_count(ctx->pck_queue); + + for (i=0; ipck_queue, i); + u8 carousel = gf_filter_pck_get_carousel_version(q_pck); + if (!carousel) { + gf_filter_pck_send(q_pck); + continue; + } + gf_filter_pck_set_carousel_version(q_pck, 0); + cts = gf_filter_pck_get_cts(q_pck); + if (cts != GF_FILTER_NO_TS) { + //offset the cts of the ref frame to the number of B frames inbetween + if (ctx->last_ref_cts == cts) { + cts += ctx->b_frames * ctx->cur_fps.den; + gf_filter_pck_set_cts(q_pck, cts); + } else { + //shift all other frames (i.e. pending Bs) by 1 frame in the past since we move the ref frame after them + assert(cts >= ctx->cur_fps.den); + cts -= ctx->cur_fps.den; + gf_filter_pck_set_cts(q_pck, cts); + } + } + if (is_eos && (i+1==count)) { + Bool start, end; + gf_filter_pck_get_framing(q_pck, &start, &end); + gf_filter_pck_set_framing(q_pck, start, GF_TRUE); + } + gf_filter_pck_send(q_pck); + } + gf_list_reset(ctx->pck_queue); + } + if (!pck) return; + + if (!ctx->pck_queue) ctx->pck_queue = gf_list_new(); + gf_list_add(ctx->pck_queue, pck); +} + +static void mpgviddmx_check_pid(GF_Filter *filter, GF_MPGVidDmxCtx *ctx, u32 vosh_size, u8 *data) +{ + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + mpgviddmx_check_dur(filter, ctx); + } + + if ((ctx->width == ctx->dsi.width) && (ctx->height == ctx->dsi.height)) return; + + //copy properties at init or reconfig + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT(GF_STREAM_VISUAL)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->timescale ? ctx->timescale : ctx->cur_fps.num)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FPS, & PROP_FRAC(ctx->cur_fps)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + if (ctx->duration.num) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + + mpgviddmx_enqueue_or_dispatch(ctx, NULL, GF_TRUE, GF_FALSE); + + ctx->width = ctx->dsi.width; + ctx->height = ctx->dsi.height; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, & PROP_UINT( ctx->dsi.width)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, & PROP_UINT( ctx->dsi.height)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, & PROP_FRAC_INT(ctx->dsi.par_num, ctx->dsi.par_den)); + + if (ctx->is_mpg12) { + const GF_PropertyValue *cid = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_CODECID); + u32 PL = ctx->dsi.VideoPL; + if (cid) { + switch (cid->value.uint) { + case GF_CODECID_MPEG2_MAIN: + case GF_CODECID_MPEG2_422: + case GF_CODECID_MPEG2_SNR: + case GF_CODECID_MPEG2_HIGH: + //keep same signaling + PL = cid->value.uint; + break; + default: + break; + } + } + if (!PL) PL = GF_CODECID_MPEG2_MAIN; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(PL)); + } else { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(GF_CODECID_MPEG4_PART2)); + } + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PROFILE_LEVEL, & PROP_UINT (ctx->dsi.VideoPL) ); + + ctx->b_frames = 0; + + if (vosh_size) { + u32 i; + char * dcfg = gf_malloc(sizeof(char)*vosh_size); + memcpy(dcfg, data, sizeof(char)*vosh_size); + + /*remove packed flag if any (VOSH user data)*/ + ctx->is_packed = ctx->is_vfr = ctx->forced_packed = GF_FALSE; + i=0; + while (1) { + char *frame = dcfg; + while ((i+3=vosh_size) break; + if (strncmp(frame+i+4, "DivX", 4)) { + i += 4; + continue; + } + frame = strchr(dcfg + i + 4, 'p'); + if (frame) { + ctx->forced_packed = GF_TRUE; + frame[0] = 'n'; + } + break; + } + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, & PROP_DATA_NO_COPY(dcfg, vosh_size)); + } + + if (ctx->is_file && ctx->index) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, & PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD) ); + } +} + +static Bool mpgviddmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i; + u64 file_pos = 0; + GF_FilterEvent fevt; + GF_MPGVidDmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (!ctx->is_playing) { + ctx->is_playing = GF_TRUE; + ctx->cts = 0; + ctx->bytes_in_header = 0; + } + if (! ctx->is_file) { + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + if (evt->play.start_range>0.1) ctx->resume_from = 0; + } + return GF_FALSE; + } + ctx->start_range = evt->play.start_range; + ctx->in_seek = GF_TRUE; + + if (ctx->start_range) { + for (i=1; iindex_size; i++) { + if ((ctx->indexes[i].start_time > ctx->start_range) || (i+1==ctx->index_size)) { + ctx->cts = (u64) (ctx->indexes[i-1].start_time * ctx->cur_fps.num); + file_pos = ctx->indexes[i-1].pos; + break; + } + } + } + ctx->dts = ctx->cts; + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!file_pos) + return GF_TRUE; + } + ctx->resume_from = 0; + ctx->bytes_in_header = 0; + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + ctx->is_playing = GF_FALSE; + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = NULL; + if (ctx->pck_queue) { + while (gf_list_count(ctx->pck_queue)) { + GF_FilterPacket *pck=gf_list_pop_front(ctx->pck_queue); + gf_filter_pck_discard(pck); + } + } + //don't cancel event + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +static GFINLINE void mpgviddmx_update_time(GF_MPGVidDmxCtx *ctx) +{ + assert(ctx->cur_fps.num); + + if (ctx->timescale) { + u64 inc = 3000; + if (ctx->cur_fps.den && ctx->cur_fps.num) { + inc = ctx->cur_fps.den; + if (ctx->cur_fps.num != ctx->timescale) { + inc *= ctx->timescale; + inc /= ctx->cur_fps.num; + } + } + ctx->cts += inc; + ctx->dts += inc; + } else { + assert(ctx->cur_fps.den); + ctx->cts += ctx->cur_fps.den; + ctx->dts += ctx->cur_fps.den; + } +} + + +static s32 mpgviddmx_next_start_code(u8 *data, u32 size) +{ + u32 v, bpos, found; + s64 start, end; + + bpos = 0; + found = 0; + start = 0; + end = 0; + + v = 0xffffffff; + while (!end) { + if (bpos == size) + return -1; + v = ( (v<<8) & 0xFFFFFF00) | data[bpos]; + + bpos++; + if ((v & 0xFFFFFF00) == 0x00000100) { + end = start + bpos - 4; + found = 1; + break; + } + } + if (!found) + return -1; + assert(end >= start); + return (s32) (end - start); +} + +GF_Err mpgviddmx_process(GF_Filter *filter) +{ + GF_MPGVidDmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + u64 byte_offset; + s64 vosh_start = -1; + s64 vosh_end = -1; + GF_Err e; + char *data; + u8 *start; + u32 pck_size; + s32 remain; + + //always reparse duration + if (!ctx->duration.num) + mpgviddmx_check_dur(filter, ctx); + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + mpgviddmx_enqueue_or_dispatch(ctx, NULL, GF_TRUE, GF_TRUE); + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = NULL; + return GF_EOS; + } + return GF_OK; + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + byte_offset = gf_filter_pck_get_byte_offset(pck); + + start = data; + remain = pck_size; + + //input pid sets some timescale - we flushed pending data , update cts + if (!ctx->resume_from && ctx->timescale) { + u64 ts = gf_filter_pck_get_cts(pck); + if (ts != GF_FILTER_NO_TS) { + if (!ctx->cts || !ctx->recompute_cts) + ctx->cts = ts; + } + ts = gf_filter_pck_get_dts(pck); + if (ts != GF_FILTER_NO_TS) { + if (!ctx->dts || !ctx->recompute_cts) + ctx->dts = ts; + + if (!ctx->prev_dts) ctx->prev_dts = ts; + else if (ctx->prev_dts != ts) { + u64 diff = ts; + diff -= ctx->prev_dts; + if (!ctx->cur_fps.den) ctx->cur_fps.den = (u32) diff; + else if (ctx->cur_fps.den > diff) + ctx->cur_fps.den = (u32) diff; + } + } + gf_filter_pck_get_framing(pck, &ctx->input_is_au_start, &ctx->input_is_au_end); + //this will force CTS recomput of each frame + if (ctx->recompute_cts) ctx->input_is_au_start = GF_FALSE; + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = pck; + gf_filter_pck_ref_props(&ctx->src_pck); + } + + //we stored some data to find the complete vosh, aggregate this packet with current one + if (!ctx->resume_from && ctx->hdr_store_size) { + if (ctx->hdr_store_alloc < ctx->hdr_store_size + pck_size) { + ctx->hdr_store_alloc = ctx->hdr_store_size + pck_size; + ctx->hdr_store = gf_realloc(ctx->hdr_store, sizeof(char)*ctx->hdr_store_alloc); + } + memcpy(ctx->hdr_store + ctx->hdr_store_size, data, sizeof(char)*pck_size); + if (byte_offset != GF_FILTER_NO_BO) { + if (byte_offset >= ctx->hdr_store_size) + byte_offset -= ctx->hdr_store_size; + else + byte_offset = GF_FILTER_NO_BO; + } + ctx->hdr_store_size += pck_size; + start = data = ctx->hdr_store; + remain = pck_size = ctx->hdr_store_size; + } + + if (ctx->resume_from) { + if (gf_filter_pid_would_block(ctx->opid)) + return GF_OK; + + //resume from data copied internally + if (ctx->hdr_store_size) { + assert(ctx->resume_from <= ctx->hdr_store_size); + start = data = ctx->hdr_store + ctx->resume_from; + remain = pck_size = ctx->hdr_store_size - ctx->resume_from; + } else { + assert(remain >= (s32) ctx->resume_from); + start += ctx->resume_from; + remain -= ctx->resume_from; + } + ctx->resume_from = 0; + } + + if (!ctx->bs) { + ctx->bs = gf_bs_new(start, remain, GF_BITSTREAM_READ); + } else { + gf_bs_reassign_buffer(ctx->bs, start, remain); + } + if (!ctx->vparser) { + ctx->vparser = gf_m4v_parser_bs_new(ctx->bs, ctx->is_mpg12); + } + + + while (remain) { + Bool full_frame; + u8 *pck_data; + s32 current; + u8 sc_type, forced_sc_type=0; + Bool sc_type_forced = GF_FALSE; + Bool skip_pck = GF_FALSE; + u8 ftype; + u32 tinc; + u64 size=0; + u64 fstart; + Bool is_coded; + u32 bytes_from_store = 0; + u32 hdr_offset = 0; + Bool copy_last_bytes = GF_FALSE; + + //not enough bytes to parse start code + if (remain<5) { + memcpy(ctx->hdr_store, start, remain); + ctx->bytes_in_header = remain; + break; + } + current = -1; + + //we have some potential bytes of a start code in the store, copy some more bytes and check if valid start code. + //if not, dispatch these bytes as continuation of the data + if (ctx->bytes_in_header) { + + memcpy(ctx->hdr_store + ctx->bytes_in_header, start, 8 - ctx->bytes_in_header); + current = mpgviddmx_next_start_code(ctx->hdr_store, 8); + + //no start code in stored buffer + if ((current<0) || (current >= (s32) ctx->bytes_in_header) ) { + dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->bytes_in_header, &pck_data); + if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, dst_pck); + gf_filter_pck_set_cts(dst_pck, GF_FILTER_NO_TS); + gf_filter_pck_set_dts(dst_pck, GF_FILTER_NO_TS); + memcpy(pck_data, ctx->hdr_store, ctx->bytes_in_header); + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE); + + if (byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, byte_offset - ctx->bytes_in_header); + } + + mpgviddmx_enqueue_or_dispatch(ctx, dst_pck, GF_FALSE, GF_FALSE); + if (current<0) current = -1; + else current -= ctx->bytes_in_header; + ctx->bytes_in_header = 0; + } else { + //we have a valid start code, check which byte in our store or in the packet payload is the start code type + //and remember its location to reinit the parser from there + hdr_offset = 4 - ctx->bytes_in_header + current; + //bytes still to dispatch + bytes_from_store = ctx->bytes_in_header; + ctx->bytes_in_header = 0; + if (!hdr_offset) { + forced_sc_type = ctx->hdr_store[current+3]; + } else { + forced_sc_type = start[hdr_offset-1]; + } + sc_type_forced = GF_TRUE; + } + } + //no starcode in store, look for startcode in packet + if (current == -1) { + //locate next start code + current = mpgviddmx_next_start_code(start, remain); + //no start code, dispatch the block + if (current<0) { + u8 b3, b2, b1; + if (! ctx->frame_started) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[MPGVid] no start code in block and no frame started, discarding data\n" )); + break; + } + size = remain; + b3 = start[remain-3]; + b2 = start[remain-2]; + b1 = start[remain-1]; + //we may have a startcode at the end of the packet, store it and don't dispatch the last 3 bytes ! + if (!b1 || !b2 || !b3) { + copy_last_bytes = GF_TRUE; + assert(size >= 3); + size -= 3; + ctx->bytes_in_header = 3; + } + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, (u32) size, &pck_data); + if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, dst_pck); + memcpy(pck_data, start, (size_t) size); + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE); + gf_filter_pck_set_cts(dst_pck, GF_FILTER_NO_TS); + gf_filter_pck_set_dts(dst_pck, GF_FILTER_NO_TS); + + if (byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, byte_offset); + } + + mpgviddmx_enqueue_or_dispatch(ctx, dst_pck, GF_FALSE, GF_FALSE); + if (copy_last_bytes) { + memcpy(ctx->hdr_store, start+remain-3, 3); + } + break; + } + } + + assert(current>=0); + + //if we are in the middle of parsing the vosh, skip over bytes remaining from previous obj not parsed + if ((vosh_start>=0) && current) { + assert(remain>=current); + start += current; + remain -= current; + current = 0; + } + //also skip if no output pid + if (!ctx->opid && current) { + assert(remain>=current); + start += current; + remain -= current; + current = 0; + } + //dispatch remaining bytes + if (current>0) { + //flush remaining + dst_pck = gf_filter_pck_new_alloc(ctx->opid, current, &pck_data); + if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, dst_pck); + gf_filter_pck_set_cts(dst_pck, GF_FILTER_NO_TS); + gf_filter_pck_set_dts(dst_pck, GF_FILTER_NO_TS); + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_TRUE); + //bytes were partly in store, partly in packet + if (bytes_from_store) { + if (byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, byte_offset - bytes_from_store); + } + assert(bytes_from_store>=(u32) current); + bytes_from_store -= current; + memcpy(pck_data, ctx->hdr_store, current); + } else { + //bytes were only in packet + if (byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, byte_offset); + } + memcpy(pck_data, start, current); + assert(remain>=current); + start += current; + remain -= current; + current = 0; + } + gf_filter_pck_set_carousel_version(dst_pck, 1); + + mpgviddmx_enqueue_or_dispatch(ctx, dst_pck, GF_FALSE, GF_FALSE); + } + + //parse headers + + //we have a start code loaded, eg the data packet does not have a full start code at the begining + if (sc_type_forced) { + gf_bs_reassign_buffer(ctx->bs, start + hdr_offset, remain - hdr_offset); + sc_type = forced_sc_type; + } else { + gf_bs_reassign_buffer(ctx->bs, start, remain); + gf_bs_read_int(ctx->bs, 24); + sc_type = gf_bs_read_int(ctx->bs, 8); + } + + if (ctx->is_mpg12) { + switch (sc_type) { + case M2V_SEQ_START_CODE: + case M2V_EXT_START_CODE: + gf_bs_reassign_buffer(ctx->bs, start, remain); + e = gf_m4v_parse_config(ctx->vparser, &ctx->dsi); + //not enough data, accumulate until we can parse the full header + if (e==GF_EOS) { + if (vosh_start<0) vosh_start = 0; + if (ctx->hdr_store_alloc < ctx->hdr_store_size + pck_size - vosh_start) { + ctx->hdr_store_alloc = (u32) (ctx->hdr_store_size + pck_size - vosh_start); + ctx->hdr_store = gf_realloc(ctx->hdr_store, sizeof(char)*ctx->hdr_store_alloc); + } + memcpy(ctx->hdr_store + ctx->hdr_store_size, data + vosh_start, (size_t) (pck_size - vosh_start) ); + ctx->hdr_store_size += pck_size - (u32) vosh_start; + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } else if (e != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[MPGVid] Failed to parse VOS header: %s\n", gf_error_to_string(e) )); + } else { + mpgviddmx_check_pid(filter, ctx, 0, NULL); + } + break; + case M2V_PIC_START_CODE: + break; + default: + break; + } + + } else { + u8 PL; + switch (sc_type) { + case M4V_VOS_START_CODE: + ctx->dsi.VideoPL = (u8) gf_bs_read_u8(ctx->bs); + vosh_start = start - (u8 *)data; + skip_pck = GF_TRUE; + assert(remain>=5); + start += 5; + remain -= 5; + break; + case M4V_VOL_START_CODE: + gf_bs_reassign_buffer(ctx->bs, start, remain); + PL = ctx->dsi.VideoPL; + e = gf_m4v_parse_config(ctx->vparser, &ctx->dsi); + ctx->dsi.VideoPL = PL; + //not enough data, accumulate until we can parse the full header + if (e==GF_EOS) { + if (vosh_start<0) vosh_start = 0; + if (ctx->hdr_store_alloc < ctx->hdr_store_size + pck_size - vosh_start) { + ctx->hdr_store_alloc = (u32) (ctx->hdr_store_size + pck_size - (u32) vosh_start); + ctx->hdr_store = gf_realloc(ctx->hdr_store, sizeof(char)*ctx->hdr_store_alloc); + } + memcpy(ctx->hdr_store + ctx->hdr_store_size, data + vosh_start, (size_t) (pck_size - vosh_start) ); + ctx->hdr_store_size += pck_size - (u32) vosh_start; + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } else if (e != GF_OK) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[MPGVid] Failed to parse VOS header: %s\n", gf_error_to_string(e) )); + } else { + u32 obj_size = (u32) gf_m4v_get_object_start(ctx->vparser); + vosh_end = start - (u8 *)data + obj_size; + vosh_end -= vosh_start; + mpgviddmx_check_pid(filter, ctx,(u32) vosh_end, data+vosh_start); + skip_pck = GF_TRUE; + assert(remain>=(s32) obj_size); + start += obj_size; + remain -= obj_size; + } + break; + case M4V_VOP_START_CODE: + case M4V_GOV_START_CODE: + break; + + case M4V_VO_START_CODE: + case M4V_VISOBJ_START_CODE: + default: + if (vosh_start>=0) { + skip_pck = GF_TRUE; + assert(remain>=4); + start += 4; + remain -= 4; + } + break; + } + } + + if (skip_pck) { + continue; + } + + if (!ctx->opid) { + assert(remain>=4); + start += 4; + remain -= 4; + continue; + } + + if (!ctx->is_playing) { + ctx->resume_from = (u32) ((char *)start - (char *)data); + return GF_OK; + } + //at this point, we no longer reaggregate packets + ctx->hdr_store_size = 0; + + if (ctx->in_seek) { + u64 nb_frames_at_seek = (u64) (ctx->start_range * ctx->cur_fps.num); + if (ctx->cts + ctx->cur_fps.den >= nb_frames_at_seek) { + //u32 samples_to_discard = (ctx->cts + ctx->dts_inc) - nb_samples_at_seek; + ctx->in_seek = GF_FALSE; + } + } + //may happen that after all our checks, only 4 bytes are left, continue to store these 4 bytes + if (remain<5) + continue; + + //good to go + gf_m4v_parser_reset(ctx->vparser, sc_type_forced ? forced_sc_type + 1 : 0); + size = 0; + e = gf_m4v_parse_frame(ctx->vparser, &ctx->dsi, &ftype, &tinc, &size, &fstart, &is_coded); + //true if we strip VO and VISOBJ assert(!fstart); + + //we skipped bytes already in store + end of start code present in packet, so the size of the first object + //needs adjustement + if (bytes_from_store) { + size += bytes_from_store + hdr_offset; + } + + if ((e == GF_EOS) && !ctx->input_is_au_end) { + u8 b3 = start[remain-3]; + u8 b2 = start[remain-2]; + u8 b1 = start[remain-1]; + + //we may have a startcode at the end of the packet, store it and don't dispatch the last 3 bytes ! + if (!b1 || !b2 || !b3) { + copy_last_bytes = GF_TRUE; + assert(size >= 3); + size -= 3; + ctx->bytes_in_header = 3; + } + full_frame = GF_FALSE; + } else { + full_frame = GF_TRUE; + } + + if (!is_coded) { + /*if prev is B and we're parsing a packed bitstream discard n-vop*/ + if (ctx->forced_packed && ctx->b_frames) { + ctx->is_packed = GF_TRUE; + assert(remain>=size); + start += size; + remain -= (s32) size; + continue; + } + /*policy is to import at variable frame rate, skip*/ + if (ctx->vfr) { + ctx->is_vfr = GF_TRUE; + mpgviddmx_update_time(ctx); + assert(remain>=size); + start += size; + remain -= (s32) size; + continue; + } + /*policy is to keep non coded frame (constant frame rate), add*/ + } + + if (ftype==2) { + //count number of B-frames since last ref + ctx->b_frames++; + ctx->nb_b++; + } else { + //flush all pending packets + mpgviddmx_enqueue_or_dispatch(ctx, NULL, GF_TRUE, GF_FALSE); + //remeber the CTS of the last ref + ctx->last_ref_cts = ctx->cts; + if (ctx->max_b < ctx->b_frames) ctx->max_b = ctx->b_frames; + + ctx->b_frames = 0; + if (ftype) + ctx->nb_p++; + else + ctx->nb_i++; + } + ctx->nb_frames++; + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, (u32) size, &pck_data); + if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, dst_pck); + //bytes come from both our store and the data packet + if (bytes_from_store) { + memcpy(pck_data, ctx->hdr_store+current, bytes_from_store); + assert(size >= bytes_from_store); + size -= bytes_from_store; + if (byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, byte_offset - bytes_from_store); + } + memcpy(pck_data + bytes_from_store, start, (size_t) size); + } else { + //bytes only come the data packet + memcpy(pck_data, start, (size_t) size); + if (byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, byte_offset + start - (u8 *) data); + } + } + assert(pck_data[0] == 0); + assert(pck_data[1] == 0); + assert(pck_data[2] == 0x01); + + gf_filter_pck_set_framing(dst_pck, GF_TRUE, (full_frame || ctx->input_is_au_end) ? GF_TRUE : GF_FALSE); + gf_filter_pck_set_cts(dst_pck, ctx->cts); + gf_filter_pck_set_dts(dst_pck, ctx->dts); + if (ctx->input_is_au_start) { + ctx->input_is_au_start = GF_FALSE; + } else { + //we use the carrousel flag temporarly to indicate the cts must be recomputed + gf_filter_pck_set_carousel_version(dst_pck, 1); + } + gf_filter_pck_set_sap(dst_pck, ftype ? GF_FILTER_SAP_NONE : GF_FILTER_SAP_1); + gf_filter_pck_set_duration(dst_pck, ctx->cur_fps.den); + if (ctx->in_seek) gf_filter_pck_set_seek_flag(dst_pck, GF_TRUE); + ctx->frame_started = GF_TRUE; + + mpgviddmx_enqueue_or_dispatch(ctx, dst_pck, GF_FALSE, GF_FALSE); + + mpgviddmx_update_time(ctx); + + if (!full_frame) { + if (copy_last_bytes) { + memcpy(ctx->hdr_store, start+remain-3, 3); + } + break; + } + assert(remain>=size); + start += size; + remain -= (s32) size; + + + //don't demux too much of input, abort when we would block. This avoid dispatching + //a huge number of frames in a single call + if (!ctx->timescale && gf_filter_pid_would_block(ctx->opid)) { + ctx->resume_from = (u32) ((char *)start - (char *)data); + return GF_OK; + } + } + gf_filter_pid_drop_packet(ctx->ipid); + + return GF_OK; +} + +static GF_Err mpgviddmx_initialize(GF_Filter *filter) +{ + GF_MPGVidDmxCtx *ctx = gf_filter_get_udta(filter); + ctx->hdr_store_size = 0; + ctx->hdr_store_alloc = 8; + ctx->hdr_store = gf_malloc(sizeof(char)*8); + return GF_OK; +} + +static void mpgviddmx_finalize(GF_Filter *filter) +{ + GF_MPGVidDmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs) gf_bs_del(ctx->bs); + if (ctx->vparser) gf_m4v_parser_del_no_bs(ctx->vparser); + if (ctx->indexes) gf_free(ctx->indexes); + if (ctx->hdr_store) gf_free(ctx->hdr_store); + if (ctx->pck_queue) { + while (gf_list_count(ctx->pck_queue)) { + GF_FilterPacket *pck = gf_list_pop_back(ctx->pck_queue); + gf_filter_pck_discard(pck); + } + gf_list_del(ctx->pck_queue); + } + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + if (ctx->importer) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("%s Import results: %d VOPs (%d Is - %d Ps - %d Bs)\n", ctx->is_mpg12 ? "MPEG-1/2" : "MPEG-4 (Part 2)", ctx->nb_frames, ctx->nb_i, ctx->nb_p, ctx->nb_b)); + if (ctx->nb_b) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("\t%d max consecutive B-frames%s\n", ctx->max_b, ctx->is_packed ? " - packed bitstream" : "" )); + } + if (ctx->is_vfr && ctx->nb_b && ctx->is_packed) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Warning: Mix of non-coded frames: packed bitstream and encoder skiped - unpredictable timing\n")); + } + } +} + +static const char * mpgvdmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + GF_M4VParser *parser; + u8 ftype; + u32 tinc, nb_frames; + u64 fsize, start; + Bool is_coded; + GF_Err e; + GF_M4VDecSpecInfo dsi; + + memset(&dsi, 0, sizeof(GF_M4VDecSpecInfo)); + parser = gf_m4v_parser_new((char*)data, size, GF_FALSE); + nb_frames = 0; + while (1) { + ftype = 0; + is_coded = GF_FALSE; + e = gf_m4v_parse_frame(parser, &dsi, &ftype, &tinc, &fsize, &start, &is_coded); + if (!nb_frames && start) + break; + if (is_coded) nb_frames++; + if (e==GF_EOS) { + if (is_coded) nb_frames++; + e = GF_OK; + break; + } + if (ftype>2) break; + if (e) break; + nb_frames++; + } + gf_m4v_parser_del(parser); + if ((e==GF_OK) && (nb_frames>1)) { + *score = GF_FPROBE_MAYBE_SUPPORTED; + return "video/mp4v-es"; + } + + memset(&dsi, 0, sizeof(GF_M4VDecSpecInfo)); + parser = gf_m4v_parser_new((char*)data, size, GF_TRUE); + nb_frames = 0; + while (1) { + ftype = 0; + is_coded = GF_FALSE; + e = gf_m4v_parse_frame(parser, &dsi, &ftype, &tinc, &fsize, &start, &is_coded); + if (!nb_frames && start) + break; + if (is_coded) nb_frames++; + if (e==GF_EOS) { + if (is_coded) nb_frames++; + e = GF_OK; + break; + } + if (ftype>2) break; + if (e) break; + nb_frames++; + } + gf_m4v_parser_del(parser); + if ((e==GF_OK) && (nb_frames>1)) { + *score = GF_FPROBE_MAYBE_SUPPORTED; + return "video/mpgv-es"; + } + return NULL; +} + +static const GF_FilterCapability MPGVidDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "cmp|m1v|m2v"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "video/mp4v-es|video/mpgv-es"), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_MPEG4_PART2), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_MPEG1), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_MPEG2_SIMPLE), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_MPEG2_SPATIAL), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_MPEG2_SNR), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_MPEG2_MAIN), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_MPEG2_HIGH), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_MPEG2_422), + CAP_BOOL(GF_CAPS_OUTPUT_STATIC_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG4_PART2), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG1), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_SPATIAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_SNR), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_SIMPLE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_MAIN), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_HIGH), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_422), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + + +#define OFFS(_n) #_n, offsetof(GF_MPGVidDmxCtx, _n) +static const GF_FilterArgs MPGVidDmxArgs[] = +{ + { OFFS(fps), "import frame rate (0 default to FPS from bitstream or 25 Hz)", GF_PROP_FRACTION, "0/1000", NULL, 0}, + { OFFS(index), "indexing window length", GF_PROP_DOUBLE, "1.0", NULL, 0}, + { OFFS(vfr), "set variable frame rate import", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(importer), "compatibility with old importer, displays import results", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + + +GF_FilterRegister MPGVidDmxRegister = { + .name = "rfmpgvid", + GF_FS_SET_DESCRIPTION("M1V/M2V/M4V reframer") + GF_FS_SET_HELP("This filter parses MPEG-1/2 and MPEG-4 part 2 video files/data and outputs corresponding video PID and frames.\n" + "Note: The demux uses negative CTS offsets: CTS is corrrect, but some frames may have DTS greater than CTS.") + .private_size = sizeof(GF_MPGVidDmxCtx), + .args = MPGVidDmxArgs, + .initialize = mpgviddmx_initialize, + .finalize = mpgviddmx_finalize, + SETCAPS(MPGVidDmxCaps), + .configure_pid = mpgviddmx_configure_pid, + .process = mpgviddmx_process, + .probe_data = mpgvdmx_probe_data, + .process_event = mpgviddmx_process_event +}; + + +const GF_FilterRegister *mpgviddmx_register(GF_FilterSession *session) +{ + return &MPGVidDmxRegister; +} + +#else +const GF_FilterRegister *mpgviddmx_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // GPAC_DISABLE_AV_PARSERS + + diff --git a/src/filters/reframe_nalu.c b/src/filters/reframe_nalu.c new file mode 100644 index 0000000..d3f2ee0 --- /dev/null +++ b/src/filters/reframe_nalu.c @@ -0,0 +1,3316 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2020 + * All rights reserved + * + * This file is part of GPAC / NALU (AVC & HEVC) reframer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +//for oinf stuff +#include + +#ifndef GPAC_DISABLE_AV_PARSERS + +#define CTS_POC_OFFSET_SAFETY 1000 + +//storage for nal header + slice header when not fully included in an input buffer - this needs to be big enough to hold a slice header +//note that we only copy the entire input packet in an internal buffer when the current NAL needs to be fully available for parsing (VPS, SPS, PPS), +//or for temporary storage (SEI) +//otherwise we copy remaining bytes in the hdr store if a startcode may be split across two input packets +#define SAFETY_NAL_STORE 50 + +typedef struct +{ + u64 pos; + Double duration; +} NALUIdx; + + +typedef struct +{ + u32 layer_id_plus_one; + u32 min_temporal_id, max_temporal_id; +} LHVCLayerInfo; + +enum { + STRICT_POC_OFF = 0, + STRICT_POC_ON, + STRICT_POC_ERROR, +}; + +typedef struct +{ + //filter args + GF_Fraction fps; + Double index; + Bool explicit, force_sync, nosei, importer, subsamples, nosvc, novpsext, deps, seirw, audelim, analyze; + u32 nal_length; + u32 strict_poc; + GF_Fraction idur; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + //read bitstream for AVC/HEVC parsing + GF_BitStream *bs_r; + //write bitstream for nalus size length rewrite + GF_BitStream *bs_w; + //current CTS/DTS of the stream, may be overridden by input packet if not file (eg TS PES) + u64 cts, dts, prev_dts, prev_cts; + u32 pck_duration; + //basic config stored here: with, height CRC of base and enh layer decoder config, sample aspect ratio + //when changing, a new pid config will be emitted + u32 width, height; + u32 crc_cfg, crc_cfg_enh; + GF_Fraction sar; + GF_Fraction cur_fps; + + //duration of the file if known + GF_Fraction64 duration; + //playback start range + Double start_range; + //indicates we are in seek, packets before start range should be marked + Bool in_seek; + //set once we play something + Bool is_playing; + //is a file, is a file fully loaded on disk (local or download done) + Bool is_file, file_loaded; + //initial PLAY command found + Bool initial_play_done; + + //list of RAP entry points + NALUIdx *indexes; + u32 index_alloc_size, index_size; + + + //timescale of the input pid if any, 0 otherwise + u32 timescale; + //framing flag of input packet when input pid has timing (eg is not a file) + Bool input_is_au_start; + + GF_FilterPacket *src_pck; + + Bool full_au_source; + + //total delay in frames between decode and presentation + s32 max_total_delay; + //max size codable with our nal_length setting + u32 max_nalu_size_allowed; + + //position in input packet from which we resume parsing + u32 resume_from; + //prevents message about possible NAL size optimizaion at finalization + Bool nal_adjusted; + + //avc/hevc switch + Bool is_hevc; + //name of the logger + const char *log_name; + + //list of packet (in decode order !!) not yet dispatched. + //Dispatch depends on the mode: + //strict_poc=0: we wait after each IDR until we find a stable poc diff between pictures, controled by poc_probe_done + //strict_poc>=1: we dispatch only after IDR or at the end (huge delay) + GF_List *pck_queue; + //dts of the last IDR found + u64 dts_last_IDR; + //max size of NALUs in the bitstream + u32 max_nalu_size; + + //we store a few bytes here to make sure we have at least start code and NAL type + //we also store here NALUs we must completely parse (xPS, SEIs) + u32 bytes_in_header; + char *hdr_store; + u32 hdr_store_size, hdr_store_alloc; + + //list of param sets found + GF_List *sps, *pps, *vps, *sps_ext, *pps_svc; + //set to true if one of the PS has been modified, will potentially trigger a PID reconfigure + Bool ps_modified; + + //stats + u32 nb_idr, nb_i, nb_p, nb_b, nb_sp, nb_si, nb_sei, nb_nalus, nb_aud; + + //frame has intra slice + Bool has_islice; + //AU is rap + GF_FilterSAPType au_sap; + //frame first slice + Bool first_slice_in_au; + //paff used - NEED FURTHER CHECKING + Bool is_paff; + Bool bottom_field_flag; + //SEI recovery count - if 0 and I slice only frame, openGOP detection (avc) + s32 sei_recovery_frame_count; + u32 use_opengop_gdr; + //poc compute variables + s32 last_poc, max_last_poc, max_last_b_poc, poc_diff, prev_last_poc, min_poc, poc_shift; + //set to TRUE once 3 frames with same min poc diff are found, enabling dispatch of the frames + Bool poc_probe_done; + //pointer to the first packet of the current frame (the one holding timing info) + //this packet is in the packet queue + GF_FilterPacket *first_pck_in_au; + //frame has slices used as reference + Bool has_ref_slices; + //frame has redundant coding + Bool has_redundant; + + Bool next_nal_end_skip; + + Bool last_frame_is_idr; + + //buffer to store SEI messages + //for AVC: we have to rewrite the SEI to remove some of the messages according to the spec + //for HEVC: we store prefix SEI here and dispatch them once the first VCL is found + char *sei_buffer; + u32 sei_buffer_size, sei_buffer_alloc; + + //subsample buffer, only used for SVC for now + u32 subsamp_buffer_alloc, subsamp_buffer_size, subs_mapped_bytes; + char *subsamp_buffer; + + //AVC specific + //avc bitstream state + AVCState *avc_state; + + //SVC specific + char *svc_prefix_buffer; + u32 svc_prefix_buffer_size, svc_prefix_buffer_alloc; + u32 svc_nalu_prefix_reserved; + u8 svc_nalu_prefix_priority; + + //HEVC specific + HEVCState *hevc_state; + //shvc stats + u32 nb_e_idr, nb_e_i, nb_e_p, nb_e_b; + + LHVCLayerInfo linf[64]; + u8 max_temporal_id[64]; + u8 min_layer_id; + + Bool has_initial_aud; + char init_aud[3]; + + Bool interlaced, eos_in_bs; + + Bool is_mvc; +} GF_NALUDmxCtx; + + +GF_Err naludmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_NALUDmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + if (ctx->opid) gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) { + ctx->timescale = p->value.uint; + //if we have a FPS prop, use it + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (p) { + ctx->cur_fps = p->value.frac; + } else { + ctx->cur_fps.den = 0; + ctx->cur_fps.num = ctx->timescale; + } + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (p) { + switch (p->value.uint) { + case GF_CODECID_HEVC: + case GF_CODECID_LHVC: + ctx->is_hevc = GF_TRUE; + break; + } + } + else { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_MIME); + if (p && p->value.string && ( + strstr(p->value.string, "hvc") + || strstr(p->value.string, "hevc") + || strstr(p->value.string, "265") + || strstr(p->value.string, "shvc") + || strstr(p->value.string, "mhvc") + || strstr(p->value.string, "lhvc") + ) ) ctx->is_hevc = GF_TRUE; + else { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FILE_EXT); + if (p && p->value.string && ( + strstr(p->value.string, "hvc") + || strstr(p->value.string, "hevc") + || strstr(p->value.string, "265") + || strstr(p->value.string, "shvc") + || strstr(p->value.string, "mhvc") + || strstr(p->value.string, "lhvc") + ) ) ctx->is_hevc = GF_TRUE; + } + } + if (ctx->is_hevc) { +#ifdef GPAC_DISABLE_HEVC + return GF_NOT_SUPPORTED; +#else + ctx->log_name = "HEVC"; + if (ctx->avc_state) gf_free(ctx->avc_state); + if (!ctx->hevc_state) GF_SAFEALLOC(ctx->hevc_state, HEVCState); + ctx->min_layer_id = 0xFF; +#endif + } else { + ctx->log_name = "AVC|H264"; + if (ctx->hevc_state) gf_free(ctx->hevc_state); + if (!ctx->avc_state) GF_SAFEALLOC(ctx->avc_state, AVCState); + } + if (ctx->timescale && !ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + ctx->first_slice_in_au = GF_TRUE; + } + ctx->full_au_source = GF_FALSE; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_UNFRAMED_FULL_AU); + if (p && p->value.boolean) + ctx->full_au_source = GF_TRUE; + + //copy properties at init or reconfig + if (ctx->opid) { + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT(GF_STREAM_VISUAL)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(ctx->is_hevc ? GF_CODECID_HEVC : GF_CODECID_AVC)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED_FULL_AU, NULL); + if (!gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_ID)) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ID, &PROP_UINT(1)); + + ctx->ps_modified = GF_TRUE; + ctx->crc_cfg = ctx->crc_cfg_enh = 0; + } + + return GF_OK; +} + + +static void naludmx_check_dur(GF_Filter *filter, GF_NALUDmxCtx *ctx) +{ + FILE *stream; + GF_BitStream *bs; + u64 duration, cur_dur, nal_start, start_code_pos; + AVCState *avc_state = NULL; + HEVCState *hevc_state = NULL; + Bool first_slice_in_pic = GF_TRUE; + const GF_PropertyValue *p; + const char *filepath = NULL; + if (!ctx->opid || ctx->timescale || ctx->file_loaded) return; + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) { + ctx->is_file = GF_FALSE; + ctx->file_loaded = GF_TRUE; + return; + } + filepath = p->value.string; + ctx->is_file = GF_TRUE; + + if (ctx->index<0) { + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_DOWN_SIZE); + if (!p || (p->value.longuint > 100000000)) { + GF_LOG(GF_LOG_INFO, GF_LOG_PARSER, ("[%s] Source file larger than 100M, skipping indexing\n", ctx->log_name)); + } else { + ctx->index = -ctx->index; + } + } + if (ctx->index<=0) { + ctx->duration.num = 1; + ctx->file_loaded = GF_TRUE; + return; + } + + if (ctx->is_hevc) { + GF_SAFEALLOC(hevc_state, HEVCState); + if (!hevc_state) return; + } else { + GF_SAFEALLOC(avc_state, AVCState); + if (!avc_state) return; + } + + stream = gf_fopen(filepath, "rb"); + if (!stream) { + if (hevc_state) gf_free(hevc_state); + if (avc_state) gf_free(avc_state); + return; + } + ctx->index_size = 0; + duration = 0; + cur_dur = 0; + + bs = gf_bs_from_file(stream, GF_BITSTREAM_READ); + gf_bs_enable_emulation_byte_removal(bs, GF_TRUE); + + start_code_pos = gf_bs_get_position(bs); + if (!gf_media_nalu_is_start_code(bs)) { + if (hevc_state) gf_free(hevc_state); + if (avc_state) gf_free(avc_state); + gf_bs_del(bs); + gf_fclose(stream); + ctx->duration.num = 1; + ctx->file_loaded = GF_TRUE; + return; + } + + nal_start = gf_bs_get_position(bs); + + while (gf_bs_available(bs)) { + u32 nal_size; + s32 res; + Bool is_rap = GF_FALSE; + Bool is_slice = GF_FALSE; + nal_size = gf_media_nalu_next_start_code_bs(bs); + + gf_bs_seek(bs, nal_start); + if (hevc_state) { +#ifndef GPAC_DISABLE_HEVC + u8 temporal_id, layer_id, nal_type; + + res = gf_media_hevc_parse_nalu_bs(bs, hevc_state, &nal_type, &temporal_id, &layer_id); + if (res>0) first_slice_in_pic = GF_TRUE; + switch (nal_type) { + case GF_HEVC_NALU_SLICE_IDR_N_LP: + case GF_HEVC_NALU_SLICE_IDR_W_DLP: + case GF_HEVC_NALU_SLICE_CRA: + case GF_HEVC_NALU_SLICE_BLA_N_LP: + case GF_HEVC_NALU_SLICE_BLA_W_LP: + case GF_HEVC_NALU_SLICE_BLA_W_DLP: + is_rap = GF_TRUE; + is_slice = GF_TRUE; + break; + case GF_HEVC_NALU_SLICE_STSA_N: + case GF_HEVC_NALU_SLICE_STSA_R: + case GF_HEVC_NALU_SLICE_RADL_R: + case GF_HEVC_NALU_SLICE_RASL_R: + case GF_HEVC_NALU_SLICE_RADL_N: + case GF_HEVC_NALU_SLICE_RASL_N: + case GF_HEVC_NALU_SLICE_TRAIL_N: + case GF_HEVC_NALU_SLICE_TRAIL_R: + case GF_HEVC_NALU_SLICE_TSA_N: + case GF_HEVC_NALU_SLICE_TSA_R: + is_slice = GF_TRUE; + break; + } +#endif // GPAC_DISABLE_HEVC + } else { + u32 nal_type; + u64 pos = gf_bs_get_position(bs); + res = gf_media_avc_parse_nalu(bs, avc_state); + if (res>0) first_slice_in_pic = GF_TRUE; + + nal_type = avc_state->last_nal_type_parsed; + + switch (nal_type) { + case GF_AVC_NALU_SEQ_PARAM: + gf_bs_seek(bs, pos); + gf_media_avc_read_sps_bs(bs, avc_state, GF_FALSE, NULL); + break; + case GF_AVC_NALU_PIC_PARAM: + gf_bs_seek(bs, pos); + gf_media_avc_read_pps_bs(bs, avc_state); + break; + case GF_AVC_NALU_IDR_SLICE: + is_rap = GF_TRUE; + is_slice = GF_TRUE; + break; + case GF_AVC_NALU_NON_IDR_SLICE: + case GF_AVC_NALU_DP_A_SLICE: + case GF_AVC_NALU_DP_B_SLICE: + case GF_AVC_NALU_DP_C_SLICE: + is_slice = GF_TRUE; + break; + } + } + + if (is_rap && first_slice_in_pic && (cur_dur >= ctx->index * ctx->cur_fps.num) ) { + if (!ctx->index_alloc_size) ctx->index_alloc_size = 10; + else if (ctx->index_alloc_size == ctx->index_size) ctx->index_alloc_size *= 2; + ctx->indexes = gf_realloc(ctx->indexes, sizeof(NALUIdx)*ctx->index_alloc_size); + ctx->indexes[ctx->index_size].pos = start_code_pos; + ctx->indexes[ctx->index_size].duration = (Double) duration; + ctx->indexes[ctx->index_size].duration /= ctx->cur_fps.num; + ctx->index_size ++; + cur_dur = 0; + } + + if (is_slice && first_slice_in_pic) { + duration += ctx->cur_fps.den; + cur_dur += ctx->cur_fps.den; + first_slice_in_pic = GF_FALSE; + } + + gf_bs_seek(bs, nal_start + nal_size); +/* nal_start = gf_media_nalu_next_start_code_bs(bs); + if (nal_start) gf_bs_skip_bytes(bs, nal_start); +*/ + if (gf_bs_available(bs)<4) + break; + + start_code_pos = gf_bs_get_position(bs); + nal_start = gf_media_nalu_is_start_code(bs); + if (!nal_start) { + break; + } + nal_start = gf_bs_get_position(bs); + } + + gf_bs_del(bs); + gf_fclose(stream); + if (hevc_state) gf_free(hevc_state); + if (avc_state) gf_free(avc_state); + + if (!ctx->duration.num || (ctx->duration.num * ctx->cur_fps.num != duration * ctx->duration.den)) { + ctx->duration.num = (s32) duration; + ctx->duration.den = ctx->cur_fps.num; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, & PROP_BOOL(GF_TRUE ) ); +} + + +static void naludmx_enqueue_or_dispatch(GF_NALUDmxCtx *ctx, GF_FilterPacket *n_pck, Bool flush_ref) +{ + //TODO: we are dispatching frames in "negctts mode", ie we may have DTS>CTS + //need to signal this for consumers using DTS (eg MPEG-2 TS) + if (flush_ref && ctx->pck_queue && ctx->poc_diff) { + u32 dts_inc=0; + s32 last_poc = 0; + Bool patch_missing_frame = GF_FALSE; + //send all reference packet queued + if (ctx->strict_poc==STRICT_POC_ERROR) { + u32 i; + u32 nb_bframes = 0; + for (i=0; ipck_queue); i++) { + s32 poc; + u64 poc_ts, dts; + GF_FilterPacket *q_pck = gf_list_get(ctx->pck_queue, i); + + if (q_pck == ctx->first_pck_in_au) break; + + dts = gf_filter_pck_get_dts(q_pck); + if (dts == GF_FILTER_NO_TS) continue; + poc_ts = gf_filter_pck_get_cts(q_pck); + assert(poc_ts != GF_FILTER_NO_TS); + poc = (s32) ((s64) poc_ts - CTS_POC_OFFSET_SAFETY); + + if (i) { + if (last_poc>poc) nb_bframes ++; + else if (last_poc + ctx->poc_diff1) + patch_missing_frame = GF_FALSE; + else if (nb_bframes) + patch_missing_frame = GF_TRUE; + } + last_poc = GF_INT_MIN; + + while (gf_list_count(ctx->pck_queue) ) { + u64 dts; + GF_FilterPacket *q_pck = gf_list_get(ctx->pck_queue, 0); + + if (q_pck == ctx->first_pck_in_au) break; + + dts = gf_filter_pck_get_dts(q_pck); + if (dts != GF_FILTER_NO_TS) { + s32 poc; + u64 poc_ts, cts; + u8 carousel_info = gf_filter_pck_get_carousel_version(q_pck); + + //we reused timing from source packets + if (!carousel_info) { + assert(ctx->timescale); + gf_list_rem(ctx->pck_queue, 0); + gf_filter_pck_send(q_pck); + continue; + } + gf_filter_pck_set_carousel_version(q_pck, 0); + + + poc_ts = gf_filter_pck_get_cts(q_pck); + assert(poc_ts != GF_FILTER_NO_TS); + poc = (s32) ((s64) poc_ts - CTS_POC_OFFSET_SAFETY); + + if (patch_missing_frame) { + if (last_poc!=GF_INT_MIN) { + //check if we missed an IDR (poc reset) + if (poc && (last_poc > poc) ) { + last_poc = 0; + dts_inc += ctx->cur_fps.den; + ctx->dts_last_IDR = dts; + ctx->dts += ctx->cur_fps.den; + } + //check if we miss a frame + while (last_poc + ctx->poc_diff < poc) { + last_poc += ctx->poc_diff; + dts_inc += ctx->cur_fps.den; + ctx->dts += ctx->cur_fps.den; + } + } + last_poc = poc; + dts += dts_inc; + } + //poc is stored as diff since last IDR which has min_poc + cts = ( (ctx->min_poc + (s32) poc) * ctx->cur_fps.den ) / ctx->poc_diff + ctx->dts_last_IDR; + + /*if PAFF, 2 pictures (eg poc) <=> 1 aggregated frame (eg sample), divide by 2*/ + if (ctx->is_paff) { + cts /= 2; + /*in some cases the poc is not on the top field - if that is the case, round up*/ + if (cts % ctx->cur_fps.den) { + cts = ((cts/ctx->cur_fps.den)+1) * ctx->cur_fps.den; + } + } + + gf_filter_pck_set_cts(q_pck, cts); + GF_LOG(GF_LOG_DEBUG, GF_LOG_PARSER, ("[%s] Frame timestamps computed dts "LLU" cts "LLU" (poc %d min poc %d poc_diff %d last IDR DTS "LLU")\n", ctx->log_name, dts, cts, poc, ctx->min_poc, ctx->poc_diff, ctx->dts_last_IDR)); + + if (ctx->importer) { + poc = (s32) ( (s64) cts - (s64) dts); + if (poc<0) poc = -poc; + poc /= ctx->cur_fps.den; + if (poc > ctx->max_total_delay) + ctx->max_total_delay = poc; + } + } + gf_list_rem(ctx->pck_queue, 0); + gf_filter_pck_send(q_pck); + } + } + if (!n_pck) return; + + if (!ctx->pck_queue) ctx->pck_queue = gf_list_new(); + gf_list_add(ctx->pck_queue, n_pck); +} + +#ifndef GPAC_DISABLE_HEVC + +static void naludmx_hevc_add_param(GF_HEVCConfig *cfg, GF_AVCConfigSlot *sl, u8 nal_type) +{ + GF_HEVCParamArray *pa = NULL; + u32 i, count; + count = gf_list_count(cfg->param_array); + for (i=0; iparam_array, i); + if (pa->type == nal_type) break; + pa = NULL; + } + if (!pa) { + GF_SAFEALLOC(pa, GF_HEVCParamArray); + if (!pa) return; + + pa->array_completeness = 1; + pa->type = nal_type; + pa->nalus = gf_list_new(); + gf_list_add(cfg->param_array, pa); + } + gf_list_add(pa->nalus, sl); +} + +static void naludmx_hevc_set_parall_type(GF_NALUDmxCtx *ctx, GF_HEVCConfig *hevc_cfg) +{ + u32 use_tiles, use_wpp, nb_pps, i, count; + HEVCState hevc; + + count = gf_list_count(ctx->pps); + + memset(&hevc, 0, sizeof(HEVCState)); + hevc.sps_active_idx = -1; + + use_tiles = 0; + use_wpp = 0; + nb_pps = 0; + + for (i=0; ipps, i); + s32 idx = gf_media_hevc_read_pps(slc->data, slc->size, &hevc); + + if (idx>=0) { + HEVC_PPS *pps; + nb_pps++; + pps = &hevc.pps[idx]; + if (!pps->entropy_coding_sync_enabled_flag && pps->tiles_enabled_flag) + use_tiles++; + else if (pps->entropy_coding_sync_enabled_flag && !pps->tiles_enabled_flag) + use_wpp++; + } + } + if (!use_tiles && !use_wpp) hevc_cfg->parallelismType = 1; + else if (!use_wpp && (use_tiles==nb_pps) ) hevc_cfg->parallelismType = 2; + else if (!use_tiles && (use_wpp==nb_pps) ) hevc_cfg->parallelismType = 3; + else hevc_cfg->parallelismType = 0; +} +#endif // GPAC_DISABLE_HEVC + +GF_Err naludmx_set_hevc_oinf(GF_NALUDmxCtx *ctx, u8 *max_temporal_id) +{ + GF_OperatingPointsInformation *oinf; + GF_BitStream *bs; + u8 *data; + u32 data_size; + u32 i; + HEVC_VPS *vps; + GF_AVCConfigSlot *vps_sl = gf_list_get(ctx->vps, 0); + if (!vps_sl) return GF_SERVICE_ERROR; + + vps = &ctx->hevc_state->vps[vps_sl->id]; + + if (!vps->vps_extension_found) return GF_OK; + if (vps->max_layers<2) return GF_OK; + + oinf = gf_isom_oinf_new_entry(); + if (!oinf) return GF_OUT_OF_MEM; + + oinf->scalability_mask = 0; + for (i = 0; i < 16; i++) { + if (vps->scalability_mask[i]) + oinf->scalability_mask |= 1 << i; + } + + for (i = 0; i < vps->num_profile_tier_level; i++) { + HEVC_ProfileTierLevel ptl = (i == 0) ? vps->ptl : vps->ext_ptl[i-1]; + LHEVC_ProfileTierLevel *lhevc_ptl; + GF_SAFEALLOC(lhevc_ptl, LHEVC_ProfileTierLevel); + if (!lhevc_ptl) return GF_OUT_OF_MEM; + + lhevc_ptl->general_profile_space = ptl.profile_space; + lhevc_ptl->general_tier_flag = ptl.tier_flag; + lhevc_ptl->general_profile_idc = ptl.profile_idc; + lhevc_ptl->general_profile_compatibility_flags = ptl.profile_compatibility_flag; + lhevc_ptl->general_constraint_indicator_flags = 0; + if (ptl.general_progressive_source_flag) + lhevc_ptl->general_constraint_indicator_flags |= ((u64)1) << 47; + if (ptl.general_interlaced_source_flag) + lhevc_ptl->general_constraint_indicator_flags |= ((u64)1) << 46; + if (ptl.general_non_packed_constraint_flag) + lhevc_ptl->general_constraint_indicator_flags |= ((u64)1) << 45; + if (ptl.general_frame_only_constraint_flag) + lhevc_ptl->general_constraint_indicator_flags |= ((u64)1) << 44; + lhevc_ptl->general_constraint_indicator_flags |= ptl.general_reserved_44bits; + lhevc_ptl->general_level_idc = ptl.level_idc; + gf_list_add(oinf->profile_tier_levels, lhevc_ptl); + } + + for (i = 0; i < vps->num_output_layer_sets; i++) { + LHEVC_OperatingPoint *op; + u32 j; + u16 minPicWidth, minPicHeight, maxPicWidth, maxPicHeight; + u8 maxChromaFormat, maxBitDepth; + u8 maxTemporalId; + GF_SAFEALLOC(op, LHEVC_OperatingPoint); + if (!op) return GF_OUT_OF_MEM; + + op->output_layer_set_idx = i; + op->layer_count = vps->num_necessary_layers[i]; + minPicWidth = minPicHeight = maxPicWidth = maxPicHeight = maxTemporalId = 0; + maxChromaFormat = maxBitDepth = 0; + for (j = 0; j < op->layer_count; j++) { + u32 format_idx; + u32 bitDepth; + op->layers_info[j].ptl_idx = vps->profile_tier_level_idx[i][j]; + op->layers_info[j].layer_id = j; + op->layers_info[j].is_outputlayer = vps->output_layer_flag[i][j]; + //FIXME: we consider that this flag is never set + op->layers_info[j].is_alternate_outputlayer = GF_FALSE; + + if (max_temporal_id) { + if (!maxTemporalId || (maxTemporalId < max_temporal_id[op->layers_info[j].layer_id])) + maxTemporalId = max_temporal_id[op->layers_info[j].layer_id]; + } else { + maxTemporalId = vps->max_sub_layers; + } + + format_idx = vps->rep_format_idx[op->layers_info[j].layer_id]; + if (!minPicWidth || (minPicWidth > vps->rep_formats[format_idx].pic_width_luma_samples)) + minPicWidth = vps->rep_formats[format_idx].pic_width_luma_samples; + if (!minPicHeight || (minPicHeight > vps->rep_formats[format_idx].pic_height_luma_samples)) + minPicHeight = vps->rep_formats[format_idx].pic_height_luma_samples; + if (!maxPicWidth || (maxPicWidth < vps->rep_formats[format_idx].pic_width_luma_samples)) + maxPicWidth = vps->rep_formats[format_idx].pic_width_luma_samples; + if (!maxPicHeight || (maxPicHeight < vps->rep_formats[format_idx].pic_height_luma_samples)) + maxPicHeight = vps->rep_formats[format_idx].pic_height_luma_samples; + if (!maxChromaFormat || (maxChromaFormat < vps->rep_formats[format_idx].chroma_format_idc)) + maxChromaFormat = vps->rep_formats[format_idx].chroma_format_idc; + bitDepth = vps->rep_formats[format_idx].bit_depth_chroma > vps->rep_formats[format_idx].bit_depth_luma ? vps->rep_formats[format_idx].bit_depth_chroma : vps->rep_formats[format_idx].bit_depth_luma; + if (!maxChromaFormat || (maxChromaFormat < bitDepth)) + maxChromaFormat = bitDepth; + } + op->max_temporal_id = maxTemporalId; + op->minPicWidth = minPicWidth; + op->minPicHeight = minPicHeight; + op->maxPicWidth = maxPicWidth; + op->maxPicHeight = maxPicHeight; + op->maxChromaFormat = maxChromaFormat; + op->maxBitDepth = maxBitDepth; + op->frame_rate_info_flag = GF_FALSE; //FIXME: should fetch this info from VUI + op->bit_rate_info_flag = GF_FALSE; //we don't use it + gf_list_add(oinf->operating_points, op); + } + + for (i = 0; i < vps->max_layers; i++) { + LHEVC_DependentLayer *dep; + u32 j, k; + GF_SAFEALLOC(dep, LHEVC_DependentLayer); + if (!dep) return GF_OUT_OF_MEM; + + dep->dependent_layerID = vps->layer_id_in_nuh[i]; + for (j = 0; j < vps->max_layers; j++) { + if (vps->direct_dependency_flag[dep->dependent_layerID][j]) { + dep->dependent_on_layerID[dep->num_layers_dependent_on] = j; + dep->num_layers_dependent_on ++; + } + } + k = 0; + for (j = 0; j < 16; j++) { + if (oinf->scalability_mask & (1 << j)) { + dep->dimension_identifier[j] = vps->dimension_id[i][k]; + k++; + } + } + gf_list_add(oinf->dependency_layers, dep); + } + + //write Operating Points Information Sample Group + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + gf_isom_oinf_write_entry(oinf, bs); + gf_bs_get_content(bs, &data, &data_size); + gf_bs_del(bs); + gf_isom_oinf_del_entry(oinf); + + gf_filter_pid_set_info_str(ctx->opid, "hevc:oinf", &PROP_DATA_NO_COPY(data, data_size) ); + return GF_OK; +} + +static void naludmx_set_hevc_linf(GF_NALUDmxCtx *ctx) +{ + u32 i, nb_layers=0, nb_sublayers=0; + u8 *data; + u32 data_size; + GF_BitStream *bs; + + for (i=0; i<64; i++) { + if (ctx->linf[i].layer_id_plus_one) nb_layers++; + if (ctx->linf[i].min_temporal_id != ctx->linf[i].max_temporal_id) nb_sublayers++; + } + if (!nb_layers && !nb_sublayers) + return; + + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + + gf_bs_write_int(bs, 0, 2); + gf_bs_write_int(bs, nb_layers, 6); + for (i=0; ilinf[i].layer_id_plus_one) continue; + gf_bs_write_int(bs, 0, 4); + gf_bs_write_int(bs, ctx->linf[i].layer_id_plus_one - 1, 6); + gf_bs_write_int(bs, ctx->linf[i].min_temporal_id, 3); + gf_bs_write_int(bs, ctx->linf[i].max_temporal_id, 3); + gf_bs_write_int(bs, 0, 1); + gf_bs_write_int(bs, 0xFF, 7); + + } + gf_bs_get_content(bs, &data, &data_size); + gf_bs_del(bs); + gf_filter_pid_set_info_str(ctx->opid, "hevc:linf", &PROP_DATA_NO_COPY(data, data_size) ); +} + +static void naludmx_create_hevc_decoder_config(GF_NALUDmxCtx *ctx, u8 **dsi, u32 *dsi_size, u8 **dsi_enh, u32 *dsi_enh_size, u32 *max_width, u32 *max_height, u32 *max_enh_width, u32 *max_enh_height, GF_Fraction *sar, Bool *has_hevc_base) +{ +#ifndef GPAC_DISABLE_HEVC + u32 i, count; + u8 layer_id; + Bool first = GF_TRUE; + Bool first_lhvc = GF_TRUE; + GF_HEVCConfig *cfg; + GF_HEVCConfig *hvcc; + GF_HEVCConfig *lvcc; + u32 max_w, max_h, max_ew, max_eh; + + *has_hevc_base = GF_FALSE; + + + max_w = max_h = 0; + max_ew = max_eh = 0; + sar->num = sar->den = 0; + + hvcc = gf_odf_hevc_cfg_new(); + lvcc = gf_odf_hevc_cfg_new(); + hvcc->nal_unit_size = ctx->nal_length; + lvcc->nal_unit_size = ctx->nal_length; + lvcc->is_lhvc = GF_TRUE; + //check we have one pps or sps in base layer + count = gf_list_count(ctx->sps); + for (i=0; isps, i); + layer_id = ((sl->data[0] & 0x1) << 5) | (sl->data[1] >> 3); + if (!layer_id) { + *has_hevc_base = GF_TRUE; + break; + } + } + count = gf_list_count(ctx->pps); + for (i=0; ipps, i); + layer_id = ((sl->data[0] & 0x1) << 5) | (sl->data[1] >> 3); + if (!layer_id) { + *has_hevc_base = GF_TRUE; + break; + } + } + //assign vps first so that they are serialized first + count = gf_list_count(ctx->vps); + for (i=0; ivps, i); + HEVC_VPS *vps = &ctx->hevc_state->vps[sl->id]; + + if (!i) { + hvcc->avgFrameRate = lvcc->avgFrameRate = vps->rates[0].avg_pic_rate; + hvcc->constantFrameRate = lvcc->constantFrameRate = vps->rates[0].constand_pic_rate_idc; + hvcc->numTemporalLayers = lvcc->numTemporalLayers = vps->max_sub_layers; + hvcc->temporalIdNested = lvcc->temporalIdNested = vps->temporal_id_nesting; + } + //TODO set scalability mask + if (!ctx->analyze) + naludmx_hevc_add_param((ctx->explicit || ! (*has_hevc_base) ) ? lvcc : hvcc, sl, GF_HEVC_NALU_VID_PARAM); + } + + count = gf_list_count(ctx->sps); + for (i=0; isps, i); + HEVC_SPS *sps = &ctx->hevc_state->sps[sl->id]; + layer_id = ((sl->data[0] & 0x1) << 5) | (sl->data[1] >> 3); + if (!layer_id) *has_hevc_base = GF_TRUE; + + if (ctx->explicit || layer_id) { + cfg = lvcc; + is_lhvc = GF_TRUE; + } else { + cfg = hvcc; + } + + if (first || (is_lhvc && first_lhvc) ) { + cfg->configurationVersion = 1; + cfg->profile_space = sps->ptl.profile_space; + cfg->tier_flag = sps->ptl.tier_flag; + cfg->profile_idc = sps->ptl.profile_idc; + cfg->general_profile_compatibility_flags = sps->ptl.profile_compatibility_flag; + cfg->progressive_source_flag = sps->ptl.general_progressive_source_flag; + cfg->interlaced_source_flag = sps->ptl.general_interlaced_source_flag; + cfg->non_packed_constraint_flag = sps->ptl.general_non_packed_constraint_flag; + cfg->frame_only_constraint_flag = sps->ptl.general_frame_only_constraint_flag; + cfg->constraint_indicator_flags = sps->ptl.general_reserved_44bits; + cfg->level_idc = sps->ptl.level_idc; + cfg->chromaFormat = sps->chroma_format_idc; + cfg->luma_bit_depth = sps->bit_depth_luma; + cfg->chroma_bit_depth = sps->bit_depth_chroma; + ctx->interlaced = cfg->interlaced_source_flag ? GF_TRUE : GF_FALSE; + + if (sps->aspect_ratio_info_present_flag && sps->sar_width && sps->sar_height) { + sar->num = sps->sar_width; + sar->den = sps->sar_height; + } + + /*disable frame rate scan, most bitstreams have wrong values there*/ + if (!ctx->timescale && first && (!ctx->fps.num || !ctx->fps.den) && sps->has_timing_info + /*if detected FPS is greater than 1000, assume wrong timing info*/ + && (sps->time_scale <= 1000*sps->num_units_in_tick) + ) { + ctx->cur_fps.num = sps->time_scale; + ctx->cur_fps.den = sps->num_units_in_tick; + + if (!ctx->fps.num && ctx->dts==ctx->fps.den) + ctx->dts = ctx->cur_fps.den; + } + ctx->fps = ctx->cur_fps; + } + first = GF_FALSE; + if (is_lhvc) { + first_lhvc = GF_FALSE; + if (sps->width > max_ew) max_ew = sps->width; + if (sps->height > max_eh) max_eh = sps->height; + } else { + if (sps->width > max_w) max_w = sps->width; + if (sps->height > max_h) max_h = sps->height; + } + if (!ctx->analyze) + naludmx_hevc_add_param(cfg, sl, GF_HEVC_NALU_SEQ_PARAM); + } + + cfg = ctx->explicit ? lvcc : hvcc; + count = gf_list_count(ctx->pps); + for (i=0; ipps, i); + layer_id = ((sl->data[0] & 0x1) << 5) | (sl->data[1] >> 3); + if (!layer_id) *has_hevc_base = GF_TRUE; + if (!ctx->analyze) + naludmx_hevc_add_param(layer_id ? lvcc : cfg, sl, GF_HEVC_NALU_PIC_PARAM); + } + + *dsi = *dsi_enh = NULL; + *dsi_size = *dsi_enh_size = 0; + + if (ctx->explicit || ! (*has_hevc_base) ) { + naludmx_hevc_set_parall_type(ctx, lvcc); + gf_odf_hevc_cfg_write(lvcc, dsi, dsi_size); + *max_width = *max_enh_width = max_ew; + *max_height = *max_enh_height = max_eh; + } else { + naludmx_hevc_set_parall_type(ctx, hvcc); + gf_odf_hevc_cfg_write(hvcc, dsi, dsi_size); + if (gf_list_count(lvcc->param_array) ) { + naludmx_hevc_set_parall_type(ctx, lvcc); + gf_odf_hevc_cfg_write(lvcc, dsi_enh, dsi_enh_size); + } + *max_width = max_w; + *max_height = max_h; + *max_enh_width = max_ew; + *max_enh_height = max_eh; + } + count = gf_list_count(hvcc->param_array); + for (i=0; iparam_array, i); + gf_list_reset(pa->nalus); + } + count = gf_list_count(lvcc->param_array); + for (i=0; iparam_array, i); + gf_list_reset(pa->nalus); + } + gf_odf_hevc_cfg_del(hvcc); + gf_odf_hevc_cfg_del(lvcc); +#endif // GPAC_DISABLE_HEVC +} + +void naludmx_create_avc_decoder_config(GF_NALUDmxCtx *ctx, u8 **dsi, u32 *dsi_size, u8 **dsi_enh, u32 *dsi_enh_size, u32 *max_width, u32 *max_height, u32 *max_enh_width, u32 *max_enh_height, GF_Fraction *sar) +{ + u32 i, count; + Bool first = GF_TRUE; + Bool first_svc = GF_TRUE; + GF_AVCConfig *cfg; + GF_AVCConfig *avcc; + GF_AVCConfig *svcc; + u32 max_w, max_h, max_ew, max_eh; + + + max_w = max_h = max_ew = max_eh = 0; + sar->num = sar->den = 0; + + avcc = gf_odf_avc_cfg_new(); + svcc = gf_odf_avc_cfg_new(); + avcc->nal_unit_size = ctx->nal_length; + svcc->nal_unit_size = ctx->nal_length; + + ctx->is_mvc = GF_FALSE; + count = gf_list_count(ctx->sps); + for (i=0; isps, i); + AVC_SPS *sps = &ctx->avc_state->sps[sl->id]; + u32 nal_type = sl->data[0] & 0x1F; + + if ((sps->profile_idc == 118) || (sps->profile_idc == 128)) { + ctx->is_mvc = GF_TRUE; + } + + if (ctx->explicit) { + cfg = svcc; + } else if (nal_type == GF_AVC_NALU_SVC_SUBSEQ_PARAM) { + cfg = svcc; + is_svc = GF_TRUE; + } else { + cfg = avcc; + } + + if (first || (is_svc && first_svc) ) { + cfg->configurationVersion = 1; + cfg->profile_compatibility = sps->prof_compat; + cfg->AVCProfileIndication = sps->profile_idc; + cfg->AVCLevelIndication = sps->level_idc; + cfg->chroma_format = sps->chroma_format; + cfg->luma_bit_depth = 8 + sps->luma_bit_depth_m8; + cfg->chroma_bit_depth = 8 + sps->chroma_bit_depth_m8; + /*try to patch ?*/ + if (!gf_avc_is_rext_profile(cfg->AVCProfileIndication) + && ((cfg->chroma_format>1) || (cfg->luma_bit_depth>8) || (cfg->chroma_bit_depth>8)) + ) { + if ((cfg->luma_bit_depth>8) || (cfg->chroma_bit_depth>8)) { + cfg->AVCProfileIndication = 110; + } else { + cfg->AVCProfileIndication = (cfg->chroma_format==3) ? 244 : 122; + } + } + if (sps->vui_parameters_present_flag && sps->vui.par_num && sps->vui.par_den) { + sar->num = sps->vui.par_num; + sar->den = sps->vui.par_den; + } + ctx->interlaced = sps->frame_mbs_only_flag ? GF_FALSE : GF_TRUE; + + + /*disable frame rate scan, most bitstreams have wrong values there*/ + if (first && (!ctx->fps.num || !ctx->fps.den) && sps->vui.timing_info_present_flag + /*if detected FPS is greater than 1000, assume wrong timing info*/ + && (sps->vui.time_scale <= 1000*sps->vui.num_units_in_tick) + ) { + /*ISO/IEC 14496-10 n11084 Table E-6*/ + /* not used : u8 DeltaTfiDivisorTable[] = {1,1,1,2,2,2,2,3,3,4,6}; */ + u8 DeltaTfiDivisorIdx; + if (!sps->vui.pic_struct_present_flag) { + DeltaTfiDivisorIdx = 1 + (1 - ctx->avc_state->s_info.field_pic_flag); + } else { + if (!ctx->avc_state->sei.pic_timing.pic_struct) + DeltaTfiDivisorIdx = 2; + else if (ctx->avc_state->sei.pic_timing.pic_struct == 8) + DeltaTfiDivisorIdx = 6; + else + DeltaTfiDivisorIdx = (ctx->avc_state->sei.pic_timing.pic_struct+1) / 2; + } + if (!ctx->timescale) { + ctx->cur_fps.num = 2 * sps->vui.time_scale; + ctx->cur_fps.den = 2 * sps->vui.num_units_in_tick * DeltaTfiDivisorIdx; + + if (!ctx->fps.num && ctx->dts==ctx->fps.den) + ctx->dts = ctx->cur_fps.den; + } + if (! sps->vui.fixed_frame_rate_flag) + GF_LOG(GF_LOG_INFO, GF_LOG_PARSER, ("[%s] Possible Variable Frame Rate: VUI \"fixed_frame_rate_flag\" absent\n", ctx->log_name)); + } + ctx->fps = ctx->cur_fps; + } + first = GF_FALSE; + if (is_svc) { + first_svc = GF_FALSE; + if (sps->width > max_ew) max_ew = sps->width; + if (sps->height > max_eh) max_eh = sps->height; + } else { + if (sps->width > max_w) max_w = sps->width; + if (sps->height > max_h) max_h = sps->height; + } + if (!ctx->analyze) + gf_list_add(cfg->sequenceParameterSets, sl); + } + + cfg = ctx->explicit ? svcc : avcc; + count = gf_list_count(ctx->sps_ext); + for (i=0; isps_ext, i); + if (!cfg->sequenceParameterSetExtensions) cfg->sequenceParameterSetExtensions = gf_list_new(); + if (!ctx->analyze) + gf_list_add(cfg->sequenceParameterSetExtensions, sl); + } + + cfg = ctx->explicit ? svcc : avcc; + count = gf_list_count(ctx->pps); + for (i=0; ipps, i); + if (!ctx->analyze) + gf_list_add(cfg->pictureParameterSets, sl); + } + + cfg = svcc; + count = gf_list_count(ctx->pps_svc); + for (i=0; ipps_svc, i); + if (!ctx->analyze) + gf_list_add(cfg->pictureParameterSets, sl); + } + + *dsi = *dsi_enh = NULL; + *dsi_size = *dsi_enh_size = 0; + + if (ctx->explicit) { + gf_odf_avc_cfg_write(svcc, dsi, dsi_size); + } else { + gf_odf_avc_cfg_write(avcc, dsi, dsi_size); + if (gf_list_count(svcc->sequenceParameterSets) || svcc->sequenceParameterSetExtensions) { + gf_odf_avc_cfg_write(svcc, dsi_enh, dsi_enh_size); + } + } + gf_list_reset(avcc->sequenceParameterSets); + gf_list_reset(avcc->sequenceParameterSetExtensions); + gf_list_reset(avcc->pictureParameterSets); + gf_list_reset(svcc->sequenceParameterSets); + gf_list_reset(svcc->sequenceParameterSetExtensions); + gf_list_reset(svcc->pictureParameterSets); + gf_odf_avc_cfg_del(avcc); + gf_odf_avc_cfg_del(svcc); + *max_width = max_w; + *max_height = max_h; + *max_enh_width = max_ew; + *max_enh_height = max_eh; +} + +static void naludmx_check_pid(GF_Filter *filter, GF_NALUDmxCtx *ctx) +{ + u32 w, h, ew, eh; + u8 *dsi, *dsi_enh; + u32 dsi_size, dsi_enh_size; + u32 crc_cfg, crc_cfg_enh; + GF_Fraction sar; + Bool has_hevc_base = GF_TRUE; + Bool has_colr_info = GF_FALSE; + + if (ctx->analyze) { + if (ctx->opid && !ctx->ps_modified) return; + } else { + if (!ctx->ps_modified) return; + } + ctx->ps_modified = GF_FALSE; + + dsi = dsi_enh = NULL; + + if (!ctx->timescale) { + ctx->cur_fps = ctx->fps; + if (!ctx->cur_fps.num || !ctx->cur_fps.den) { + ctx->cur_fps.num = 25000; + ctx->cur_fps.den = 1000; + } + } + + if (ctx->is_hevc) { + naludmx_create_hevc_decoder_config(ctx, &dsi, &dsi_size, &dsi_enh, &dsi_enh_size, &w, &h, &ew, &eh, &sar, &has_hevc_base); + } else { + naludmx_create_avc_decoder_config(ctx, &dsi, &dsi_size, &dsi_enh, &dsi_enh_size, &w, &h, &ew, &eh, &sar); + } + crc_cfg = crc_cfg_enh = 0; + if (dsi) crc_cfg = gf_crc_32(dsi, dsi_size); + if (dsi_enh) crc_cfg_enh = gf_crc_32(dsi_enh, dsi_enh_size); + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + + naludmx_check_dur(filter, ctx); + ctx->first_slice_in_au = GF_TRUE; + } + + if ((ctx->crc_cfg == crc_cfg) && (ctx->crc_cfg_enh == crc_cfg_enh) + && (ctx->width==w) && (ctx->height==h) + && (ctx->sar.num * sar.den == ctx->sar.den * sar.num) + ) { + if (dsi) gf_free(dsi); + if (dsi_enh) gf_free(dsi_enh); + return; + } + + naludmx_enqueue_or_dispatch(ctx, NULL, GF_TRUE); + if (!ctx->analyze && gf_list_count(ctx->pck_queue)) { + GF_LOG(dsi_enh ? GF_LOG_DEBUG : GF_LOG_ERROR, GF_LOG_PARSER, ("[%s] xPS changed but could not flush frames before signaling state change %s\n", ctx->log_name, dsi_enh ? "- likely scalable xPS update" : "!")); + } + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT(GF_STREAM_VISUAL)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + if (!gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_ID)) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ID, &PROP_UINT(1)); + + ctx->width = w; + ctx->height = h; + ctx->sar = sar; + ctx->crc_cfg = crc_cfg; + ctx->crc_cfg_enh = crc_cfg_enh; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, & PROP_UINT( ctx->width)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, & PROP_UINT( ctx->height)); + if (ew && eh) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH_MAX, & PROP_UINT( ew )); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT_MAX, & PROP_UINT( eh )); + } + if (ctx->sar.den) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, & PROP_FRAC(ctx->sar)); + else + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, NULL); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FPS, & PROP_FRAC(ctx->cur_fps)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->timescale ? ctx->timescale : ctx->cur_fps.num)); + + if (ctx->explicit || !has_hevc_base) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(ctx->is_hevc ? GF_CODECID_LHVC : GF_CODECID_SVC)); + if (dsi) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_size) ); + } else { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(ctx->is_hevc ? GF_CODECID_HEVC : GF_CODECID_AVC)); + if (dsi) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, &PROP_DATA_NO_COPY(dsi, dsi_size) ); + if (dsi_enh) gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, &PROP_DATA_NO_COPY(dsi_enh, dsi_enh_size) ); + } + + if (ctx->is_hevc && gf_list_count(ctx->vps) ) { + GF_Err e = naludmx_set_hevc_oinf(ctx, NULL); + if (e) { + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[%s] Failed to create OINF chunk\n", ctx->log_name)); + } + naludmx_set_hevc_linf(ctx); + } + if (ctx->duration.num) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + + if (ctx->is_file /* && ctx->index*/) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, & PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD) ); + } + //set interlaced or remove interlaced property + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_INTERLACED, ctx->interlaced ? & PROP_UINT(GF_TRUE) : NULL); + + if (ctx->is_hevc) { + HEVC_SPS *sps = &ctx->hevc_state->sps[ctx->hevc_state->sps_active_idx]; + if (sps->colour_description_present_flag) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_PRIMARIES, & PROP_UINT(sps->colour_primaries) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_TRANSFER, & PROP_UINT(sps->transfer_characteristic) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_MX, & PROP_UINT(sps->matrix_coeffs) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_RANGE, & PROP_BOOL(sps->video_full_range_flag) ); + has_colr_info = GF_TRUE; + } + } else { + /*use the last active SPS*/ + if (ctx->avc_state->sps[ctx->avc_state->sps_active_idx].vui_parameters_present_flag + && ctx->avc_state->sps[ctx->avc_state->sps_active_idx].vui.colour_description_present_flag) { + AVC_VUI *vui = &ctx->avc_state->sps[ctx->avc_state->sps_active_idx].vui; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_PRIMARIES, & PROP_UINT(vui->colour_primaries) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_TRANSFER, & PROP_UINT(vui->transfer_characteristics) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_MX, & PROP_UINT(vui->matrix_coefficients) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_RANGE, & PROP_BOOL(vui->video_full_range_flag) ); + has_colr_info = GF_TRUE; + } + } + + if (!has_colr_info) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_PRIMARIES, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_TRANSFER, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_MX, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_RANGE, NULL); + } +} + +static Bool naludmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i; + u64 file_pos = 0; + GF_FilterEvent fevt; + GF_NALUDmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (!ctx->is_playing) { + ctx->is_playing = GF_TRUE; + ctx->cts = ctx->dts = 0; + ctx->bytes_in_header = 0; + } + if (! ctx->is_file) { + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + if (evt->play.start_range<0.1) + return GF_FALSE; + } + ctx->resume_from = 0; + return GF_FALSE; + } + if (ctx->start_range && (ctx->index<0)) { + ctx->index = -ctx->index; + ctx->file_loaded = GF_FALSE; + ctx->duration.den = ctx->duration.num = 0; + GF_LOG(GF_LOG_INFO, GF_LOG_PARSER, ("[%s] Play request from %d, building index\n", ctx->log_name, ctx->start_range)); + naludmx_check_dur(filter, ctx); + } + ctx->start_range = evt->play.start_range; + ctx->in_seek = GF_TRUE; + + if (ctx->start_range) { + ctx->nb_nalus = ctx->nb_i = ctx->nb_p = ctx->nb_b = ctx->nb_sp = ctx->nb_si = ctx->nb_sei = ctx->nb_idr = 0; + for (i=1; iindex_size; i++) { + if (ctx->indexes[i].duration>ctx->start_range) { + ctx->cts = ctx->dts = (u64) (ctx->indexes[i-1].duration * ctx->cur_fps.num); + file_pos = ctx->indexes[i-1].pos; + break; + } + } + } + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!file_pos) + return GF_TRUE; + } + ctx->resume_from = 0; + + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + //don't cancel event + ctx->is_playing = GF_FALSE; + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +static GFINLINE void naludmx_update_time(GF_NALUDmxCtx *ctx) +{ + assert(ctx->cur_fps.num); + + if (ctx->timescale) { + //very first frame, no dts diff, assume 3000/90k. It should only hurt if we have several frames packet in the first packet sent + u64 dts_inc = ctx->cur_fps.den ? ctx->cur_fps.den : 3000; + ctx->cts += dts_inc; + ctx->dts += dts_inc; + } else { + assert(ctx->cur_fps.den); + ctx->cts += ctx->cur_fps.den; + ctx->dts += ctx->cur_fps.den; + } +} + +static void naludmx_queue_param_set(GF_NALUDmxCtx *ctx, char *data, u32 size, u32 ps_type, s32 ps_id) +{ + GF_List *list = NULL, *alt_list = NULL; + GF_AVCConfigSlot *sl; + u32 i, count; + u32 crc = gf_crc_32(data, size); + + if (ctx->is_hevc) { + switch (ps_type) { + case GF_HEVC_NALU_VID_PARAM: + if (!ctx->vps) ctx->vps = gf_list_new(); + list = ctx->vps; + break; + case GF_HEVC_NALU_SEQ_PARAM: + list = ctx->sps; + break; + case GF_HEVC_NALU_PIC_PARAM: + list = ctx->pps; + break; + default: + assert(0); + return; + } + } else { + switch (ps_type) { + case GF_AVC_NALU_SVC_SUBSEQ_PARAM: + case GF_AVC_NALU_SEQ_PARAM: + list = ctx->sps; + break; + case GF_AVC_NALU_PIC_PARAM: + list = ctx->pps; + alt_list = ctx->pps_svc; + break; + case GF_AVC_NALU_SEQ_PARAM_EXT: + if (!ctx->sps_ext) ctx->sps_ext = gf_list_new(); + list = ctx->sps_ext; + break; + default: + assert(0); + return; + } + } + sl = NULL; + count = gf_list_count(list); + for (i=0; iid != ps_id) { + sl = NULL; + continue; + } + //same ID, same CRC, we don't change our state + if (sl->crc == crc) return; + break; + } + //handle alt PPS list for SVC + if (!sl && alt_list) { + count = gf_list_count(alt_list); + for (i=0; iid != ps_id) { + sl = NULL; + continue; + } + //same ID, same CRC, we don't change our state + if (sl->crc == crc) return; + break; + } + } + + if (sl) { + //otherwise we keep this new param set + sl->data = gf_realloc(sl->data, size); + memcpy(sl->data, data, size); + sl->size = size; + sl->crc = crc; + ctx->ps_modified = GF_TRUE; + return; + } + //TODO we might want to purge the list after a while !! + + GF_SAFEALLOC(sl, GF_AVCConfigSlot); + if (!sl) return; + sl->data = gf_malloc(sizeof(char) * size); + if (!sl->data) { + gf_free(sl); + return; + } + memcpy(sl->data, data, size); + sl->size = size; + sl->id = ps_id; + sl->crc = crc; + + ctx->ps_modified = GF_TRUE; + gf_list_add(list, sl); +} + +void naludmx_finalize_au_flags(GF_NALUDmxCtx *ctx) +{ + u64 ts; + Bool is_rap = GF_FALSE; + + if (!ctx->first_pck_in_au) + return; + if (ctx->au_sap) { + gf_filter_pck_set_sap(ctx->first_pck_in_au, ctx->au_sap); + if (ctx->au_sap == GF_FILTER_SAP_1) { + ctx->dts_last_IDR = gf_filter_pck_get_dts(ctx->first_pck_in_au); + if (ctx->is_paff) + ctx->dts_last_IDR *= 2; + } + if (ctx->au_sap <= GF_FILTER_SAP_3) { + is_rap = GF_TRUE; + } + } + else if (ctx->has_islice && ctx->force_sync && (ctx->sei_recovery_frame_count==0)) { + gf_filter_pck_set_sap(ctx->first_pck_in_au, GF_FILTER_SAP_1); + if (!ctx->use_opengop_gdr) { + ctx->use_opengop_gdr = 1; + GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("[%s] Forcing non-IDR samples with I slices to be marked as sync points - resulting file will not be ISOBMFF conformant\n", ctx->log_name)); + } + is_rap = GF_TRUE; + } + /*set roll info sampleGroups info*/ + else if (!ctx->au_sap && ( (ctx->sei_recovery_frame_count >= 0) || ctx->has_islice) ) { + /*generic GDR*/ + if (ctx->sei_recovery_frame_count > 0) { + if (!ctx->use_opengop_gdr) ctx->use_opengop_gdr = 1; + gf_filter_pck_set_sap(ctx->first_pck_in_au, GF_FILTER_SAP_4); + gf_filter_pck_set_roll_info(ctx->first_pck_in_au, ctx->sei_recovery_frame_count); + } + /*open-GOP*/ + else if ((ctx->sei_recovery_frame_count == 0) && ctx->has_islice) { + if (!ctx->use_opengop_gdr) ctx->use_opengop_gdr = 2; + gf_filter_pck_set_sap(ctx->first_pck_in_au, GF_FILTER_SAP_3); + is_rap = GF_TRUE; + } + } + if (ctx->is_paff) { + gf_filter_pck_set_interlaced(ctx->first_pck_in_au, ctx->bottom_field_flag ? 2 : 1); + } + + //if TS is set, the packet was the first in AU in the input timed packet (eg PES), we reuse the input timing + ts = gf_filter_pck_get_cts(ctx->first_pck_in_au); + if (ts == GF_FILTER_NO_TS) { + /*we store the POC (last POC minus the poc shift) as the CTS offset and re-update the CTS when dispatching*/ + assert(ctx->last_poc >= ctx->poc_shift); + gf_filter_pck_set_cts(ctx->first_pck_in_au, CTS_POC_OFFSET_SAFETY + ctx->last_poc - ctx->poc_shift); + //we use the carrousel flag temporarly to indicate the cts must be recomputed + gf_filter_pck_set_carousel_version(ctx->first_pck_in_au, 1); + } + + if (ctx->subsamp_buffer_size) { + gf_filter_pck_set_property(ctx->first_pck_in_au, GF_PROP_PCK_SUBS, &PROP_DATA(ctx->subsamp_buffer, ctx->subsamp_buffer_size) ); + ctx->subsamp_buffer_size = 0; + ctx->subs_mapped_bytes = 0; + } + if (ctx->deps) { + u8 flags = 0; + //dependsOn + flags = (is_rap) ? 2 : 1; + flags <<= 2; + //dependedOn + flags |= ctx->has_ref_slices ? 1 : 2; + flags <<= 2; + //hasRedundant + flags |= ctx->has_redundant ? 1 : 2; + gf_filter_pck_set_dependency_flags(ctx->first_pck_in_au, flags); + } + ctx->has_ref_slices = GF_FALSE; + ctx->has_redundant = GF_FALSE; + + //if we reuse input packets timing, we can dispatch asap. + //otherwise if poc probe is done (we know the min_poc_diff between images) and we are not in strict mode, dispatch asap + //otherwise we will need to wait for the next ref frame to make sure we know all pocs ... + if (ctx->timescale || (!ctx->strict_poc && ctx->poc_probe_done) ) + naludmx_enqueue_or_dispatch(ctx, NULL, GF_TRUE); + + ctx->first_pck_in_au = NULL; +} + +static void naludmx_update_nalu_maxsize(GF_NALUDmxCtx *ctx, u32 size) +{ + if (ctx->max_nalu_size < size) { + ctx->max_nalu_size = size; + if (size > ctx->max_nalu_size_allowed) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[%s] nal size %d larger than max allowed size %d - change import settings\n", ctx->log_name, size, ctx->max_nalu_size_allowed )); + } + } +} + + +GF_Err naludmx_realloc_last_pck(GF_NALUDmxCtx *ctx, u32 nb_bytes_to_add, u8 **data_ptr) +{ + GF_Err e; + u8 *pck_data; + u32 full_size; + GF_FilterPacket *pck = gf_list_last(ctx->pck_queue); + *data_ptr = NULL; + if (!pck) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[%s] attempt to reallocate a non-existing packet!\n", ctx->log_name)); + return GF_SERVICE_ERROR; + } + e = gf_filter_pck_expand(pck, nb_bytes_to_add, &pck_data, data_ptr, &full_size); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[%s] Failed to reallocate packet buffer: %s\n", ctx->log_name, gf_error_to_string(e) )); + return e; + } + assert(ctx->bs_w); + //rewrite NALU size length + full_size -= ctx->nal_length; + gf_bs_reassign_buffer(ctx->bs_w, pck_data, ctx->nal_length); + gf_bs_write_int(ctx->bs_w, full_size, 8*ctx->nal_length); + naludmx_update_nalu_maxsize(ctx, full_size); + //rewrite subsample size + if (ctx->subsamples) { + assert(ctx->subsamp_buffer_size>=14); + //reassign to begining of size field (after first u32 flags) + gf_bs_reassign_buffer(ctx->bs_w, ctx->subsamp_buffer + ctx->subsamp_buffer_size-14 + 4, 14 - 4); + gf_bs_write_u32(ctx->bs_w, full_size + ctx->nal_length); + } + return GF_OK; +} + +GF_FilterPacket *naludmx_start_nalu(GF_NALUDmxCtx *ctx, u32 nal_size, Bool skip_nal_field, Bool *au_start, u8 **pck_data) +{ + GF_FilterPacket *dst_pck = gf_filter_pck_new_alloc(ctx->opid, nal_size + (skip_nal_field ? 0 : ctx->nal_length), pck_data); + + if (!skip_nal_field) { + if (!ctx->bs_w) ctx->bs_w = gf_bs_new(*pck_data, ctx->nal_length, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs_w, *pck_data, ctx->nal_length); + gf_bs_write_int(ctx->bs_w, nal_size, 8*ctx->nal_length); + } + + if (*au_start) { + ctx->first_pck_in_au = dst_pck; + if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, dst_pck); + + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_FALSE); + //we reuse the timing of the input packet for the first nal of the first frame starting in this packet + if (ctx->input_is_au_start) { + ctx->input_is_au_start = GF_FALSE; + gf_filter_pck_set_dts(dst_pck, ctx->dts); + gf_filter_pck_set_cts(dst_pck, ctx->cts); + } else { + //we don't set the CTS, it will be set once we detect frame end + gf_filter_pck_set_dts(dst_pck, ctx->dts); + } + //we use the carrousel flag temporarly to indicate the cts must be recomputed + gf_filter_pck_set_carousel_version(dst_pck, ctx->timescale ? 0 : 1); + + gf_filter_pck_set_duration(dst_pck, ctx->pck_duration ? ctx->pck_duration : ctx->cur_fps.den); + if (ctx->in_seek) gf_filter_pck_set_seek_flag(dst_pck, GF_TRUE); + + naludmx_update_time(ctx); + *au_start = GF_FALSE; + } else { + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE); + } + naludmx_update_nalu_maxsize(ctx, nal_size); + + naludmx_enqueue_or_dispatch(ctx, dst_pck, GF_FALSE); + + return dst_pck; +} + +void naludmx_add_subsample(GF_NALUDmxCtx *ctx, u32 subs_size, u8 subs_priority, u32 subs_reserved) +{ + if (ctx->subsamp_buffer_alloc < ctx->subsamp_buffer_size+14 ) { + ctx->subsamp_buffer_alloc = ctx->subsamp_buffer_size+14; + ctx->subsamp_buffer = gf_realloc(ctx->subsamp_buffer, ctx->subsamp_buffer_alloc); + } + assert(ctx->subsamp_buffer); + gf_bs_reassign_buffer(ctx->bs_w, ctx->subsamp_buffer + ctx->subsamp_buffer_size, 14); + gf_bs_write_u32(ctx->bs_w, 0); //flags + gf_bs_write_u32(ctx->bs_w, subs_size + ctx->nal_length); + gf_bs_write_u32(ctx->bs_w, subs_reserved); //reserved + gf_bs_write_u8(ctx->bs_w, subs_priority); //priority + gf_bs_write_u8(ctx->bs_w, 0); //discardable - todo + ctx->subsamp_buffer_size += 14; + ctx->subs_mapped_bytes += subs_size + ctx->nal_length; +} + +static s32 naludmx_parse_nal_hevc(GF_NALUDmxCtx *ctx, char *data, u32 size, Bool *skip_nal, Bool *is_slice, Bool *is_islice) +{ +#ifdef GPAC_DISABLE_HEVC + return -1; +#else + s32 ps_idx = 0; + s32 res; + u8 nal_unit_type, temporal_id, layer_id; + *skip_nal = GF_FALSE; + + gf_bs_reassign_buffer(ctx->bs_r, data, size); + res = gf_media_hevc_parse_nalu_bs(ctx->bs_r, ctx->hevc_state, &nal_unit_type, &temporal_id, &layer_id); + ctx->nb_nalus++; + + if (res < 0) { + if (res == -1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[%s] Warning: Error parsing NAL unit\n", ctx->log_name)); + } + *skip_nal = GF_TRUE; + } + + if (layer_id && ctx->nosvc) { + *skip_nal = GF_TRUE; + return 0; + } + + switch (nal_unit_type) { + case GF_HEVC_NALU_VID_PARAM: + if (ctx->novpsext) { + //this may modify nal_size, but we don't use it for bitstream reading + ps_idx = gf_media_hevc_read_vps_ex(data, &size, ctx->hevc_state, GF_TRUE); + } else { + ps_idx = ctx->hevc_state->last_parsed_vps_id; + } + if (ps_idx<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[%s] Error parsing Video Param Set\n", ctx->log_name)); + } else { + naludmx_queue_param_set(ctx, data, size, GF_HEVC_NALU_VID_PARAM, ps_idx); + } + *skip_nal = GF_TRUE; + break; + case GF_HEVC_NALU_SEQ_PARAM: + ps_idx = ctx->hevc_state->last_parsed_sps_id; + if (ps_idx<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[%s] Error parsing Sequence Param Set\n", ctx->log_name)); + } else { + naludmx_queue_param_set(ctx, data, size, GF_HEVC_NALU_SEQ_PARAM, ps_idx); + } + *skip_nal = GF_TRUE; + break; + case GF_HEVC_NALU_PIC_PARAM: + ps_idx = ctx->hevc_state->last_parsed_pps_id; + if (ps_idx<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[%s] Error parsing Picture Param Set\n", ctx->log_name)); + } else { + naludmx_queue_param_set(ctx, data, size, GF_HEVC_NALU_PIC_PARAM, ps_idx); + } + *skip_nal = GF_TRUE; + break; + case GF_HEVC_NALU_SEI_PREFIX: + gf_media_hevc_parse_sei(data, size, ctx->hevc_state); + if (!ctx->nosei) { + ctx->nb_sei++; + + if (ctx->sei_buffer_alloc < ctx->sei_buffer_size + size + ctx->nal_length) { + ctx->sei_buffer_alloc = ctx->sei_buffer_size + size + ctx->nal_length; + ctx->sei_buffer = gf_realloc(ctx->sei_buffer, ctx->sei_buffer_alloc); + } + + if (!ctx->bs_w) ctx->bs_w = gf_bs_new(ctx->sei_buffer + ctx->sei_buffer_size, ctx->nal_length + size, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs_w, ctx->sei_buffer + ctx->sei_buffer_size, ctx->nal_length + size); + gf_bs_write_int(ctx->bs_w, size, 8*ctx->nal_length); + memcpy(ctx->sei_buffer + ctx->sei_buffer_size + ctx->nal_length, data, size); + ctx->sei_buffer_size += size + ctx->nal_length; + } else { + ctx->nb_nalus--; + } + *skip_nal = GF_TRUE; + break; + case GF_HEVC_NALU_SEI_SUFFIX: + if (! ctx->is_playing) return 0; + if (ctx->nosei) { + *skip_nal = GF_TRUE; + ctx->nb_nalus--; + } else { + ctx->nb_sei++; + } + break; + + /*slice_segment_layer_rbsp*/ + case GF_HEVC_NALU_SLICE_STSA_N: + case GF_HEVC_NALU_SLICE_STSA_R: + case GF_HEVC_NALU_SLICE_RADL_R: + case GF_HEVC_NALU_SLICE_RASL_R: + case GF_HEVC_NALU_SLICE_RADL_N: + case GF_HEVC_NALU_SLICE_RASL_N: + case GF_HEVC_NALU_SLICE_TRAIL_N: + case GF_HEVC_NALU_SLICE_TRAIL_R: + case GF_HEVC_NALU_SLICE_TSA_N: + case GF_HEVC_NALU_SLICE_TSA_R: + case GF_HEVC_NALU_SLICE_BLA_W_LP: + case GF_HEVC_NALU_SLICE_BLA_W_DLP: + case GF_HEVC_NALU_SLICE_BLA_N_LP: + case GF_HEVC_NALU_SLICE_IDR_W_DLP: + case GF_HEVC_NALU_SLICE_IDR_N_LP: + case GF_HEVC_NALU_SLICE_CRA: + if (! ctx->is_playing) return 0; + *is_slice = GF_TRUE; + if (! *skip_nal) { + switch (ctx->hevc_state->s_info.slice_type) { + case GF_HEVC_SLICE_TYPE_P: + if (layer_id) ctx->nb_e_p++; + else ctx->nb_p++; + break; + case GF_HEVC_SLICE_TYPE_I: + if (layer_id) ctx->nb_e_i++; + else ctx->nb_i++; + *is_islice = GF_TRUE; + break; + case GF_HEVC_SLICE_TYPE_B: + if (layer_id) ctx->nb_e_b++; + else ctx->nb_b++; + break; + } + } + break; + + case GF_HEVC_NALU_ACCESS_UNIT: + ctx->nb_aud++; + if (!ctx->audelim) { + *skip_nal = GF_TRUE; + } else if (!ctx->opid) { + ctx->has_initial_aud = GF_TRUE; + memcpy(ctx->init_aud, data, 3); + } + break; + /*remove*/ + case GF_HEVC_NALU_FILLER_DATA: + case GF_HEVC_NALU_END_OF_SEQ: + case GF_HEVC_NALU_END_OF_STREAM: + *skip_nal = GF_TRUE; + break; + + //parsing is partial, see https://github.com/DolbyLaboratories/dlb_mp4base/blob/70a2e1d4d99a8439b7b8087bf50dd503eeea2291/src/esparser/parser_hevc.c#L1233 + case GF_HEVC_NALU_DV_RPU: + ctx->hevc_state->dv_rpu = GF_TRUE; + break; + case GF_HEVC_NALU_DV_EL: + ctx->hevc_state->dv_el = GF_TRUE; + break; + + default: + if (! ctx->is_playing) return 0; + GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[%s] NAL Unit type %d not handled - adding\n", ctx->log_name, nal_unit_type)); + break; + } + if (*skip_nal) return res; + + ctx->linf[layer_id].layer_id_plus_one = layer_id + 1; + if (! ctx->linf[layer_id].max_temporal_id ) ctx->linf[layer_id].max_temporal_id = temporal_id; + else if (ctx->linf[layer_id].max_temporal_id < temporal_id) ctx->linf[layer_id].max_temporal_id = temporal_id; + + if (! ctx->linf[layer_id].min_temporal_id ) ctx->linf[layer_id].min_temporal_id = temporal_id; + else if (ctx->linf[layer_id].min_temporal_id > temporal_id) ctx->linf[layer_id].min_temporal_id = temporal_id; + + if (ctx->max_temporal_id[layer_id] < temporal_id) + ctx->max_temporal_id[layer_id] = temporal_id; + if (ctx->min_layer_id > layer_id) ctx->min_layer_id = layer_id; + return res; +#endif // GPAC_DISABLE_HEVC +} + +static s32 naludmx_parse_nal_avc(GF_NALUDmxCtx *ctx, char *data, u32 size, u32 nal_type, Bool *skip_nal, Bool *is_slice, Bool *is_islice) +{ + s32 ps_idx = 0; + s32 res = 0; + + gf_bs_reassign_buffer(ctx->bs_r, data, size); + *skip_nal = GF_FALSE; + res = gf_media_avc_parse_nalu(ctx->bs_r, ctx->avc_state); + if (ctx->eos_in_bs) + return -1; + if (res < 0) { + if (res == -1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[%s] Warning: Error parsing NAL unit\n", ctx->log_name)); + } + *skip_nal = GF_TRUE; + } + ctx->nb_nalus++; + + switch (nal_type) { + case GF_AVC_NALU_SVC_SUBSEQ_PARAM: + case GF_AVC_NALU_SEQ_PARAM: + ps_idx = ctx->avc_state->last_ps_idx; + if (ps_idx<0) { + if (ctx->avc_state->sps[0].profile_idc) { + GF_LOG(ctx->avc_state->sps[0].profile_idc ? GF_LOG_WARNING : GF_LOG_ERROR, GF_LOG_PARSER, ("[%s] Error parsing Sequence Param Set\n", ctx->log_name)); + } + } else { + naludmx_queue_param_set(ctx, data, size, GF_AVC_NALU_SEQ_PARAM, ps_idx); + } + *skip_nal = GF_TRUE; + return 0; + + case GF_AVC_NALU_PIC_PARAM: + ps_idx = ctx->avc_state->last_ps_idx; + if (ps_idx<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[%s] Error parsing Picture Param Set\n", ctx->log_name)); + } else { + naludmx_queue_param_set(ctx, data, size, GF_AVC_NALU_PIC_PARAM, ps_idx); + } + *skip_nal = GF_TRUE; + return 0; + + case GF_AVC_NALU_SEQ_PARAM_EXT: + ps_idx = ctx->avc_state->last_ps_idx; + if (ps_idx<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[%s] Error parsing Sequence Param Set Extension\n", ctx->log_name)); + } else { + naludmx_queue_param_set(ctx, data, size, GF_AVC_NALU_SEQ_PARAM_EXT, ps_idx); + } + *skip_nal = GF_TRUE; + return 0; + + case GF_AVC_NALU_SEI: + if (ctx->avc_state->sps_active_idx != -1) { + u32 rw_sei_size, sei_size = size; + if (ctx->sei_buffer_alloc < ctx->sei_buffer_size + sei_size + ctx->nal_length) { + ctx->sei_buffer_alloc = ctx->sei_buffer_size + sei_size + ctx->nal_length; + ctx->sei_buffer = gf_realloc(ctx->sei_buffer, ctx->sei_buffer_alloc); + } + + if (!ctx->bs_w) ctx->bs_w = gf_bs_new(ctx->sei_buffer + ctx->sei_buffer_size, ctx->nal_length + sei_size, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs_w, ctx->sei_buffer + ctx->sei_buffer_size, ctx->nal_length + sei_size); + gf_bs_write_int(ctx->bs_w, sei_size, 8*ctx->nal_length); + + memcpy(ctx->sei_buffer + ctx->sei_buffer_size + ctx->nal_length, data, sei_size); + rw_sei_size = gf_media_avc_reformat_sei(ctx->sei_buffer + ctx->sei_buffer_size + ctx->nal_length, sei_size, ctx->seirw, ctx->avc_state); + if (rw_sei_size < sei_size) { + gf_bs_seek(ctx->bs_w, 0); + gf_bs_write_int(ctx->bs_w, rw_sei_size, 8*ctx->nal_length); + } + + *skip_nal = GF_TRUE; + ctx->sei_buffer_size += rw_sei_size + ctx->nal_length; + + if (ctx->nosei) { + ctx->sei_buffer_size = 0; + } else { + ctx->nb_sei++; + } + } + return 0; + + case GF_AVC_NALU_ACCESS_UNIT: + ctx->nb_aud++; + if (!ctx->audelim) { + *skip_nal = GF_TRUE; + } else if (!ctx->opid) { + ctx->has_initial_aud = GF_TRUE; + memcpy(ctx->init_aud, data, 2); + } + return 1; + /*remove*/ + case GF_AVC_NALU_FILLER_DATA: + case GF_AVC_NALU_END_OF_SEQ: + case GF_AVC_NALU_END_OF_STREAM: + *skip_nal = GF_TRUE; + return 0; + + //update stats + case GF_AVC_NALU_NON_IDR_SLICE: + case GF_AVC_NALU_DP_A_SLICE: + case GF_AVC_NALU_DP_B_SLICE: + case GF_AVC_NALU_DP_C_SLICE: + case GF_AVC_NALU_IDR_SLICE: + *is_slice = GF_TRUE; + switch (ctx->avc_state->s_info.slice_type) { + case GF_AVC_TYPE_P: + case GF_AVC_TYPE2_P: + ctx->nb_p++; + break; + case GF_AVC_TYPE_I: + case GF_AVC_TYPE2_I: + ctx->nb_i++; + *is_islice = GF_TRUE; + break; + case GF_AVC_TYPE_B: + case GF_AVC_TYPE2_B: + ctx->nb_b++; + break; + case GF_AVC_TYPE_SP: + case GF_AVC_TYPE2_SP: + ctx->nb_sp++; + break; + case GF_AVC_TYPE_SI: + case GF_AVC_TYPE2_SI: + ctx->nb_si++; + break; + } + break; + + case GF_AVC_NALU_SVC_SLICE: + if (!ctx->explicit) { + u32 i; + for (i = 0; i < gf_list_count(ctx->pps); i ++) { + GF_AVCConfigSlot *slc = (GF_AVCConfigSlot*)gf_list_get(ctx->pps, i); + if (ctx->avc_state->s_info.pps->id == slc->id) { + /* This PPS is used by an SVC NAL unit, it should be moved to the SVC Config Record) */ + gf_list_rem(ctx->pps, i); + i--; + if (!ctx->pps_svc) ctx->pps_svc = gf_list_new(ctx->pps_svc); + gf_list_add(ctx->pps_svc, slc); + ctx->ps_modified = GF_TRUE; + } + } + } + *is_slice = GF_TRUE; + //we disable temporal scalability when parsing mvc - never used and many encoders screw up POC in enhancemen + if (ctx->is_mvc && (res>=0)) { + res=0; + ctx->avc_state->s_info.poc = ctx->last_poc; + } + if (ctx->avc_state->s_info.sps) { + switch (ctx->avc_state->s_info.slice_type) { + case GF_AVC_TYPE_P: + case GF_AVC_TYPE2_P: + ctx->avc_state->s_info.sps->nb_ep++; + break; + case GF_AVC_TYPE_I: + case GF_AVC_TYPE2_I: + ctx->avc_state->s_info.sps->nb_ei++; + break; + case GF_AVC_TYPE_B: + case GF_AVC_TYPE2_B: + ctx->avc_state->s_info.sps->nb_eb++; + break; + } + } + break; + case GF_AVC_NALU_SLICE_AUX: + *is_slice = GF_TRUE; + break; + } + return res; +} + +static void naludmx_on_eos(void *par) +{ + GF_NALUDmxCtx *ctx = (GF_NALUDmxCtx *)par; + ctx->eos_in_bs = GF_TRUE; +} + +static void naldmx_switch_timestamps(GF_NALUDmxCtx *ctx, GF_FilterPacket *pck) +{ + //input pid sets some timescale - we flushed pending data , update cts + if (ctx->timescale) { + u64 ts = gf_filter_pck_get_cts(pck); + if (ts != GF_FILTER_NO_TS) { + ctx->prev_cts = ctx->cts; + ctx->cts = ts; + } + ts = gf_filter_pck_get_dts(pck); + if (ts != GF_FILTER_NO_TS) { + GF_FilterClockType ck_type = gf_filter_pid_get_clock_info(ctx->ipid, NULL, NULL); + if (ck_type==GF_FILTER_CLOCK_PCR_DISC) + ctx->dts = ts; + else if (ctx->dtsdts=ts; + + if (!ctx->prev_dts) ctx->prev_dts = ts; + else if (ctx->prev_dts != ts) { + u64 diff = ts; + diff -= ctx->prev_dts; + if (!ctx->cur_fps.den) + ctx->cur_fps.den = (u32) diff; + else if (ctx->cur_fps.den > diff) + ctx->cur_fps.den = (u32) diff; + + ctx->prev_dts = ts; + } + } + ctx->pck_duration = gf_filter_pck_get_duration(pck); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = pck; + gf_filter_pck_ref_props(&ctx->src_pck); + //store framing flags. If input_is_au_start, the first NAL of the first frame begining in this packet will + //use the DTS/CTS of the input packet, otherwise we will use our internal POC recompute + gf_filter_pck_get_framing(pck, &ctx->input_is_au_start, NULL); + } +} + +GF_Err naludmx_process(GF_Filter *filter) +{ + GF_NALUDmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + GF_Err e; + char *data; + u8 *start; + u32 pck_size; + u32 hdr_size_at_resume = 0; + u32 nalu_before = ctx->nb_nalus; + s32 remain; + Bool is_eos = GF_FALSE; + + //always reparse duration + if (!ctx->file_loaded) + naludmx_check_dur(filter, ctx); + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (ctx->bytes_in_header || ctx->hdr_store_size) { + start = data = ctx->hdr_store; + remain = pck_size = ctx->bytes_in_header ? ctx->bytes_in_header : ctx->hdr_store_size; + ctx->bytes_in_header = 0; + ctx->hdr_store_size = 0; + is_eos = GF_TRUE; + goto naldmx_flush; + } + if (ctx->first_pck_in_au) { + naludmx_finalize_au_flags(ctx); + } + //single-frame stream + if (!ctx->poc_diff) ctx->poc_diff = 1; + ctx->strict_poc = STRICT_POC_OFF; + naludmx_enqueue_or_dispatch(ctx, NULL, GF_TRUE); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = NULL; + if (!ctx->opid) return GF_EOS; + + gf_filter_pid_set_info(ctx->opid, GF_PROP_PID_MAX_NALU_SIZE, &PROP_UINT(ctx->max_nalu_size) ); + if (ctx->is_hevc) { + naludmx_set_hevc_oinf(ctx, ctx->max_temporal_id); + naludmx_set_hevc_linf(ctx); + gf_filter_pid_set_property_str(ctx->opid, "hevc:min_lid", &PROP_UINT(ctx->min_layer_id) ); + } + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + start = data; + remain = pck_size; + + //if we have bytes from previous packet in the header, we cannot switch timing until we know what these bytes are + if (!ctx->bytes_in_header) + naldmx_switch_timestamps(ctx, pck); + + //we stored some data to find the complete vosh, aggregate this packet with current one + if (!ctx->resume_from && ctx->hdr_store_size) { + if (ctx->hdr_store_alloc < ctx->hdr_store_size + pck_size) { + ctx->hdr_store_alloc = ctx->hdr_store_size + pck_size; + ctx->hdr_store = gf_realloc(ctx->hdr_store, sizeof(char)*ctx->hdr_store_alloc); + } + hdr_size_at_resume = ctx->hdr_store_size; + memcpy(ctx->hdr_store + ctx->hdr_store_size, data, sizeof(char)*pck_size); + ctx->hdr_store_size += pck_size; + start = data = ctx->hdr_store; + remain = pck_size = ctx->hdr_store_size; + } + + if (ctx->resume_from) { + if (ctx->opid && gf_filter_pid_would_block(ctx->opid)) + return GF_OK; + + //resume from data copied internally + if (ctx->hdr_store_size) { + assert(ctx->resume_from <= ctx->hdr_store_size); + start = data = ctx->hdr_store + ctx->resume_from; + remain = pck_size = ctx->hdr_store_size - ctx->resume_from; + } else { + assert((s32)ctx->resume_from >0); + if (ctx->resume_from > (u32) remain) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[%s] something wrong, resuming parsing at %d bytes but only %d bytes in packet, discarding data\n", ctx->log_name, ctx->resume_from, remain)); + } else { + start += ctx->resume_from; + remain -= ctx->resume_from; + } + } + ctx->resume_from = 0; + } + +naldmx_flush: + if (!ctx->bs_r) { + ctx->bs_r = gf_bs_new(start, remain, GF_BITSTREAM_READ); + } else { + gf_bs_reassign_buffer(ctx->bs_r, start, remain); + } + if (is_eos) + gf_bs_set_eos_callback(ctx->bs_r, naludmx_on_eos, ctx); + ctx->eos_in_bs = GF_FALSE; + + assert(remain>=0); + + while (remain) { + u8 *pck_data; + u8 *hdr_start; + u32 hdr_avail; + u8 *pck_start; + u32 pck_avail; + s32 current; + Bool nal_hdr_in_store = GF_FALSE; + Bool nal_sc_in_store = GF_FALSE; + u32 nal_bytes_from_store = 0; + Bool skip_nal = GF_FALSE; + u64 size=0; + u32 sc_size, store_sc_size=0; + u32 bytes_from_store = 0; + u32 hdr_offset = 0; + Bool full_nal_required = GF_FALSE; + u32 nal_type = 0; + u32 nal_ref_idc = 0; + s32 next=0; + u32 next_sc_size=0; + s32 nal_parse_result; + Bool slice_is_ref, slice_force_ref; + Bool is_slice = GF_FALSE; + Bool is_islice = GF_FALSE; + Bool bottom_field_flag = GF_FALSE; + Bool au_start; + u32 avc_svc_subs_reserved = 0; + u8 avc_svc_subs_priority = 0; + Bool recovery_point_valid = GF_FALSE; + u32 recovery_point_frame_cnt = 0; + Bool bIntraSlice = GF_FALSE; + GF_FilterSAPType au_sap_type = GF_FILTER_SAP_NONE; + Bool slice_is_b = GF_FALSE; + Bool full_nal = GF_FALSE; + s32 slice_poc = 0; + u32 next_size = 0; + + Bool copy_last_bytes = GF_FALSE; + + //not enough bytes to parse start code + if (!is_eos && (remain<6)) { + memcpy(ctx->hdr_store, start, remain); + ctx->bytes_in_header = remain; + break; + } + current = -1; + + //we have some potential bytes of a start code in the store, copy some more bytes and check if valid start code. + //if not, dispatch these bytes as continuation of the data + if (ctx->bytes_in_header) { + Bool split_start_code=GF_FALSE; + + memcpy(ctx->hdr_store + ctx->bytes_in_header, start, SAFETY_NAL_STORE - ctx->bytes_in_header); + current = gf_media_nalu_next_start_code(ctx->hdr_store, SAFETY_NAL_STORE, &sc_size); + if (current==SAFETY_NAL_STORE) + current = -1; + + //no start code in stored buffer + if (current<0) { + if (!ctx->next_nal_end_skip) { + e = naludmx_realloc_last_pck(ctx, ctx->bytes_in_header, &pck_data); + if (e==GF_OK) { + memcpy(pck_data, ctx->hdr_store, ctx->bytes_in_header); + } + } + ctx->bytes_in_header = 0; + } else { + //if start is greater than stored data, the nal is completely in the input packet (but the start code may not be) + if (current + sc_size > ctx->bytes_in_header) { + //we need to dispatch current bytes from the header, whether all these bytes were in the previous + //packet or some are in the new one does not matter + + bytes_from_store = current; + //the offset to the NAL first byte in the store is current+sc_size, + //hence current+sc_pos-ctx->bytes_in_header in the new packet + hdr_offset = current + sc_size - ctx->bytes_in_header; + nal_sc_in_store = GF_TRUE; + + //since the nal is completely in the pes, its timestamp is the one of the source packet, switch timing + naldmx_switch_timestamps(ctx, pck); + } else { + //this is trickier, nal start is in the store buffer + u8 lastb2, lastb1, nextb1; + + lastb2 = ctx->hdr_store[ctx->bytes_in_header-2]; + lastb1 = ctx->hdr_store[ctx->bytes_in_header-1]; + nextb1 = start[0]; + + if (!lastb1 && !lastb2) { split_start_code=GF_TRUE; } + else if (!lastb1 && !nextb1) { split_start_code = GF_TRUE; } + + if (split_start_code ) { + u32 store_remain = SAFETY_NAL_STORE - (current + sc_size); + u32 following_sc_size; + s32 following = gf_media_nalu_next_start_code(ctx->hdr_store + current+sc_size, store_remain, &following_sc_size); + assert(!current); + if ((following>0) && (following + current+sc_size < ctx->bytes_in_header)) { + next_size = following - current; + nal_bytes_from_store = next_size; + store_sc_size = sc_size; + } else { + split_start_code = 0; + } + } + + //we still have current bytes to dispatch + bytes_from_store = current; + + //the offset in the STORE is current+sc_size, and we parse the store, not the packet !! + hdr_offset = current + sc_size; + + nal_hdr_in_store = GF_TRUE; + + + if (!split_start_code) { + if (sc_size == ctx->bytes_in_header) { + nal_hdr_in_store = GF_FALSE; + sc_size = 0; + hdr_offset=0; + nal_bytes_from_store = 0; + } else { + //we need to copy the nal first bytes from the store + nal_bytes_from_store = ctx->bytes_in_header - (current + sc_size); + } + } + + } + if (!split_start_code) { + ctx->bytes_in_header = 0; + } + } + } + //no starcode in store, look for startcode in packet + if (current == -1) { + //locate next start code + current = gf_media_nalu_next_start_code(start, remain, &sc_size); + if (current == remain) + current = -1; + + //no start code, dispatch the block + if (current<0) { + u8 b3, b2, b1; + if (! ctx->first_pck_in_au) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[%s] no start code in block and no frame started, discarding data\n", ctx->log_name)); + break; + } + size = remain; + b3 = start[remain-3]; + b2 = start[remain-2]; + b1 = start[remain-1]; + //we may have a startcode at the end of the packet, store it and don't dispatch the last 3 bytes ! + if (!is_eos && (!b1 || !b2 || !b3)) { + copy_last_bytes = GF_TRUE; + assert(size >= 3); + size -= 3; + ctx->bytes_in_header = 3; + } + if (!ctx->next_nal_end_skip) { + e = naludmx_realloc_last_pck(ctx, (u32) size, &pck_data); + if (e==GF_OK) + memcpy(pck_data, start, (size_t) size); + } + + if (copy_last_bytes) { + memcpy(ctx->hdr_store, start+remain-3, 3); + } + break; + } + } + + assert(current>=0); + + + //skip if no output pid + if (!ctx->opid && current) { + assert(remain>=current); + assert((s32) current >= 0); + + start += current; + remain -= current; + current = 0; + ctx->next_nal_end_skip = GF_FALSE; + } + + //dispatch remaining bytes + if (current>0) { + e = GF_EOS; + //flush remaining bytes in NAL + if (!ctx->next_nal_end_skip) { + e = naludmx_realloc_last_pck(ctx, current, &pck_data); + } + //bytes were partly in store, partly in packet + if (bytes_from_store) { + if (bytes_from_store>=(u32) current) { + //we still have that many bytes from the store to dispatch +// bytes_from_store -= current; + } else { + //we are done, the nal header and start code is completely in the new packet + u32 shift = current - bytes_from_store; +// bytes_from_store = 0; + assert(remain >= (s32) shift); + assert((s32) shift >= 0); + start += shift; + remain -= shift; + nal_sc_in_store = 0; + } + if (e==GF_OK) { + memcpy(pck_data, ctx->hdr_store, current); + } + } else { + if (e==GF_OK) { + memcpy(pck_data, start, current); + } + assert(remain>=current); + start += current; + remain -= current; + } + } + if (!remain) + break; + + //nal hdr is in the store, use the store to parse slice header + if (nal_hdr_in_store) { + hdr_start = ctx->hdr_store + hdr_offset; + hdr_avail = SAFETY_NAL_STORE - hdr_offset; + pck_start = start; + pck_avail = remain; + sc_size = 0; + } + //nal hdr is in new packet at hdr_offset, use the packet to parse slice header + else if (nal_sc_in_store) { + hdr_start = start + hdr_offset; + hdr_avail = remain - hdr_offset; + pck_start = hdr_start; + pck_avail = hdr_avail; + sc_size = 0; + } + //nal hdr is in new packet start + sc_size, use the packet to parse slice header + else { + hdr_start = start + sc_size; + hdr_avail = remain - sc_size; + pck_start = hdr_start; + pck_avail = hdr_avail; + } + + //figure out which nal we need to completely load + if (ctx->is_hevc) { + nal_type = hdr_start[0]; + nal_type = (nal_type & 0x7E) >> 1; + switch (nal_type) { + case GF_HEVC_NALU_VID_PARAM: + case GF_HEVC_NALU_SEQ_PARAM: + case GF_HEVC_NALU_PIC_PARAM: + case GF_HEVC_NALU_SEI_PREFIX: + case GF_HEVC_NALU_SEI_SUFFIX: + if (!ctx->analyze) + full_nal_required = GF_TRUE; + break; + case GF_HEVC_NALU_SLICE_TRAIL_N: + case GF_HEVC_NALU_SLICE_TSA_N: + case GF_HEVC_NALU_SLICE_STSA_N: + case GF_HEVC_NALU_SLICE_RADL_N: + case GF_HEVC_NALU_SLICE_RASL_N: + case GF_HEVC_NALU_SLICE_RSV_VCL_N10: + case GF_HEVC_NALU_SLICE_RSV_VCL_N12: + case GF_HEVC_NALU_SLICE_RSV_VCL_N14: + if (ctx->deps) { + HEVC_VPS *vps; + u32 temporal_id = hdr_start[1] & 0x7; + vps = & ctx->hevc_state->vps[ctx->hevc_state->s_info.sps->vps_id]; + if (temporal_id + 1 < vps->max_sub_layers) { + nal_ref_idc = GF_TRUE; + } + } + break; + default: + if (nal_typeanalyze) + full_nal_required = GF_TRUE; + break; + default: + break; + } + nal_ref_idc = (hdr_start[0] & 0x60) >> 5; + } + if (!next_size) { + if (full_nal_required && !is_eos) { + //we need the full nal loaded + next = gf_media_nalu_next_start_code(pck_start, pck_avail, &next_sc_size); + if (!ctx->full_au_source && (next==pck_avail)) + next = -1; + + if (next<0) { + if (sc_size) pck_avail += sc_size; + if (ctx->hdr_store_alloc < ctx->hdr_store_size + pck_avail) { + ctx->hdr_store_alloc = ctx->hdr_store_size + pck_avail; + ctx->hdr_store = gf_realloc(ctx->hdr_store, sizeof(char)*ctx->hdr_store_alloc); + } + memcpy(ctx->hdr_store + ctx->hdr_store_size, start, sizeof(char)*pck_avail); + ctx->hdr_store_size += pck_avail; + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + } else { + next = gf_media_nalu_next_start_code(pck_start, pck_avail, &next_sc_size); + if ( (next == pck_avail) && !ctx->full_au_source) { + if (!is_eos) + next = -1; + else if (ctx->is_hevc && pck_avail<2) + next = -1; + } + } + + //ok we have either a full nal, or the start of a NAL we can start to process, parse NAL + if (next<0) { + size = pck_avail; + if (!nal_hdr_in_store) { + hdr_avail = (u32) size; + } + } else { + full_nal = GF_TRUE; + size = next; + if (full_nal_required) { + if (!nal_bytes_from_store && nal_sc_in_store) { + hdr_avail = (u32) size; + hdr_start = pck_start; + + } else { + size += nal_bytes_from_store; + //we require full nal but begining of the nal is in the store, the rest in the current packet. + //We must copy the complete nal in the store before parsing it + if (nal_bytes_from_store && (size > SAFETY_NAL_STORE)) { + u32 copy_size = (u32) (size - nal_bytes_from_store); + if (ctx->hdr_store_allochdr_store_alloc = copy_size; + ctx->hdr_store = gf_realloc(ctx->hdr_store, ctx->hdr_store_alloc); + } + memcpy(ctx->hdr_store + hdr_offset + nal_bytes_from_store, start, copy_size); + } + hdr_avail = (u32) size; + } + } else if (hdr_avail>(u32) size && !nal_bytes_from_store) + hdr_avail = (u32) size; + } + if (!full_nal && (size < SAFETY_NAL_STORE/2)) { + assert(!nal_sc_in_store); + assert(!nal_hdr_in_store); + assert(remain < SAFETY_NAL_STORE); + //we may have garbage at the end os stream, if the stream was cancelled in the middle (truncated nal) + if (!is_eos) { + memcpy(ctx->hdr_store, start, remain); + ctx->bytes_in_header = remain; + } + break; + } + } else { + full_nal = GF_TRUE; + size = next_size; + } + + if (ctx->is_hevc) { + nal_parse_result = naludmx_parse_nal_hevc(ctx, hdr_start, hdr_avail, &skip_nal, &is_slice, &is_islice); + } else { + nal_parse_result = naludmx_parse_nal_avc(ctx, hdr_start, hdr_avail, nal_type, &skip_nal, &is_slice, &is_islice); + } + + if (ctx->eos_in_bs && is_eos) { + break; + } + + + if (ctx->analyze) { + skip_nal = GF_FALSE; + ctx->sei_buffer_size = 0; + } + + //new frame - if no slices, we detected the new frame on AU delimiter, don't flush new frame ! + if ((nal_parse_result>0) && !ctx->first_slice_in_au) { + //new frame - we flush later on + naludmx_finalize_au_flags(ctx); + + ctx->has_islice = GF_FALSE; + ctx->first_slice_in_au = GF_TRUE; + ctx->sei_recovery_frame_count = -1; + ctx->au_sap = GF_FILTER_SAP_NONE; + ctx->bottom_field_flag = GF_FALSE; + } + + if (skip_nal) { + assert(remain >= (s32)sc_size+next); + if (next<0) { + u8 b3, b2, b1; + b3 = start[remain-3]; + b2 = start[remain-2]; + b1 = start[remain-1]; + //we may have a startcode at the end of the packet, store it and don't dispatch the last 3 bytes ! + if (!b1 || !b2 || !b3) { + memcpy(ctx->hdr_store, start+remain-3, 3); + ctx->bytes_in_header = 3; + } + ctx->next_nal_end_skip = GF_TRUE; + break; + } + assert(remain >= next); + start = pck_start + next; + assert((u32) (start - (u8*)data) <= pck_size); + remain = pck_size - (u32) (start - (u8*)data); + if (nal_hdr_in_store) { + if (full_nal && ctx->bytes_in_header) { + memmove(ctx->hdr_store, hdr_start+nal_bytes_from_store, ctx->bytes_in_header - nal_bytes_from_store); + ctx->bytes_in_header -= next_size+store_sc_size; + } else { + ctx->bytes_in_header = 0; + } + } + continue; + } + ctx->next_nal_end_skip = GF_FALSE; + + naludmx_check_pid(filter, ctx); + if (!ctx->opid) { + u32 skip = sc_size+next; + assert(remain >= (s32) skip); + if (skip) { + start += sc_size+next; + remain -= sc_size+next; + } else { + ctx->bytes_in_header = 0; + } + continue; + } + + //at this point, we no longer reaggregate packets + ctx->hdr_store_size = 0; + + + if (!ctx->is_playing) { + ctx->resume_from = (u32) ( (char *)start - (char *)data); + assert(ctx->resume_from<=pck_size); + GF_LOG(GF_LOG_DEBUG, GF_LOG_PARSER, ("[%s] not yet playing\n", ctx->log_name)); + return GF_OK; + } + if (ctx->in_seek) { + u64 nb_frames_at_seek = (u64) (ctx->start_range * ctx->cur_fps.num); + if (ctx->cts + ctx->cur_fps.den >= nb_frames_at_seek) { + //u32 samples_to_discard = (ctx->cts + ctx->dts_inc) - nb_samples_at_seek; + ctx->in_seek = GF_FALSE; + } + } + + if (nal_parse_result<0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[%s] Error parsing NAL Unit type %d - skipping\n", ctx->log_name, nal_type)); + assert(remain >= (s32) sc_size+next); + start += sc_size+next; + remain -= sc_size+next; + continue; + } + + if (is_islice) ctx->has_islice = GF_TRUE; + + //store all variables needed to compute POC/CTS and sample SAP and recovery info + if (ctx->is_hevc) { +#ifndef GPAC_DISABLE_HEVC + slice_is_ref = gf_media_hevc_slice_is_IDR(ctx->hevc_state); + + recovery_point_valid = ctx->hevc_state->sei.recovery_point.valid; + recovery_point_frame_cnt = ctx->hevc_state->sei.recovery_point.frame_cnt; + bIntraSlice = gf_media_hevc_slice_is_intra(ctx->hevc_state); + + au_sap_type = GF_FILTER_SAP_NONE; + if (gf_media_hevc_slice_is_IDR(ctx->hevc_state)) { + au_sap_type = GF_FILTER_SAP_1; + } + else { + switch (ctx->hevc_state->s_info.nal_unit_type) { + case GF_HEVC_NALU_SLICE_BLA_W_LP: + case GF_HEVC_NALU_SLICE_BLA_W_DLP: + au_sap_type = GF_FILTER_SAP_3; + break; + case GF_HEVC_NALU_SLICE_BLA_N_LP: + au_sap_type = GF_FILTER_SAP_1; + break; + case GF_HEVC_NALU_SLICE_CRA: + au_sap_type = GF_FILTER_SAP_3; + break; + } + } + + slice_poc = ctx->hevc_state->s_info.poc; + + /*need to store TS offsets*/ + switch (ctx->hevc_state->s_info.slice_type) { + case GF_AVC_TYPE_B: + case GF_AVC_TYPE2_B: + slice_is_b = GF_TRUE; + break; + } +#endif // GPAC_DISABLE_HEVC + } else { + + /*fixme - we need finer grain for priority*/ + if ((nal_type==GF_AVC_NALU_SVC_PREFIX_NALU) || (nal_type==GF_AVC_NALU_SVC_SLICE)) { + if (!ctx->is_mvc) { + unsigned char *p = (unsigned char *) start; + // RefPicFlag + avc_svc_subs_reserved |= (p[0] & 0x60) ? 0x80000000 : 0; + // RedPicFlag TODO: not supported, would require to parse NAL unit payload + avc_svc_subs_reserved |= (0) ? 0x40000000 : 0; + // VclNALUnitFlag + avc_svc_subs_reserved |= (1<=nal_type && nal_type<=5) || (nal_type==GF_AVC_NALU_SVC_PREFIX_NALU) || (nal_type==GF_AVC_NALU_SVC_SLICE) ? 0x20000000 : 0; + // use values of IdrFlag and PriorityId directly from SVC extension header + avc_svc_subs_reserved |= p[1] << 16; + // use values of DependencyId and QualityId directly from SVC extension header + avc_svc_subs_reserved |= p[2] << 8; + // use values of TemporalId and UseRefBasePicFlag directly from SVC extension header + avc_svc_subs_reserved |= p[3] & 0xFC; + // StoreBaseRepFlag TODO: SVC FF mentions a store_base_rep_flag which cannot be found in SVC spec + avc_svc_subs_reserved |= (0) ? 0x00000002 : 0; + + // priority_id (6 bits) in SVC has inverse meaning -> lower value means higher priority - invert it and scale it to 8 bits + avc_svc_subs_priority = (63 - (p[1] & 0x3F)) << 2; + } + if (nal_type==GF_AVC_NALU_SVC_PREFIX_NALU) { + if (ctx->svc_prefix_buffer_size) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("[%s] broken bitstream, two consecutive SVC prefix NALU without SVC slice inbetween\n", ctx->log_name)); + ctx->svc_prefix_buffer_size = 0; + } + + /* remember reserved and priority value */ + ctx->svc_nalu_prefix_reserved = avc_svc_subs_reserved; + ctx->svc_nalu_prefix_priority = avc_svc_subs_priority; + + ctx->svc_prefix_buffer_size = next; + if (ctx->svc_prefix_buffer_size > ctx->svc_prefix_buffer_alloc) { + ctx->svc_prefix_buffer_alloc = ctx->svc_prefix_buffer_size; + ctx->svc_prefix_buffer = gf_realloc(ctx->svc_prefix_buffer, ctx->svc_prefix_buffer_size); + } + memcpy(ctx->svc_prefix_buffer, start+sc_size, ctx->svc_prefix_buffer_size); + + assert( remain >= (s32) sc_size+next ); + start += sc_size+next; + remain -= sc_size+next; + continue; + } + } else if (is_slice) { + // RefPicFlag + avc_svc_subs_reserved |= (start[0] & 0x60) ? 0x80000000 : 0; + // VclNALUnitFlag + avc_svc_subs_reserved |= (1<=nal_type && nal_type<=5) || (nal_type==GF_AVC_NALU_SVC_PREFIX_NALU) || (nal_type==GF_AVC_NALU_SVC_SLICE) ? 0x20000000 : 0; + avc_svc_subs_priority = 0; + } + + if (is_slice && ctx->avc_state->s_info.field_pic_flag) { + ctx->is_paff = GF_TRUE; + bottom_field_flag = ctx->avc_state->s_info.bottom_field_flag; + } + + slice_is_ref = (ctx->avc_state->s_info.nal_unit_type==GF_AVC_NALU_IDR_SLICE) ? GF_TRUE : GF_FALSE; + + recovery_point_valid = ctx->avc_state->sei.recovery_point.valid; + recovery_point_frame_cnt = ctx->avc_state->sei.recovery_point.frame_cnt; + bIntraSlice = gf_media_avc_slice_is_intra(ctx->avc_state); + + au_sap_type = GF_FILTER_SAP_NONE; + if (ctx->avc_state->s_info.nal_unit_type == GF_AVC_NALU_IDR_SLICE) + au_sap_type = GF_FILTER_SAP_1; + + slice_poc = ctx->avc_state->s_info.poc; + /*need to store TS offsets*/ + switch (ctx->avc_state->s_info.slice_type) { + case GF_AVC_TYPE_B: + case GF_AVC_TYPE2_B: + slice_is_b = GF_TRUE; + break; + } + } + + if (is_slice) { + Bool first_in_au = ctx->first_slice_in_au; + + if (slice_is_ref) + ctx->nb_idr++; + slice_force_ref = GF_FALSE; + + /*we only indicate TRUE IDRs for sync samples (cf AVC file format spec). + SEI recovery should be used to build sampleToGroup & RollRecovery tables*/ + if (ctx->first_slice_in_au) { + ctx->first_slice_in_au = GF_FALSE; + if (recovery_point_valid) { +// if (!ctx->is_hevc) assert(ctx->avc_state->s_info.nal_unit_type!=GF_AVC_NALU_IDR_SLICE || bIntraSlice); + + ctx->sei_recovery_frame_count = recovery_point_frame_cnt; + + /*we allow to mark I-frames as sync on open-GOPs (with sei_recovery_frame_count=0) when forcing sync even when the SEI RP is not available*/ + if (!recovery_point_frame_cnt && bIntraSlice) { + ctx->has_islice = 1; + if (ctx->use_opengop_gdr == 1) { + ctx->use_opengop_gdr = 2; /*avoid message flooding*/ + GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("[%s] No valid SEI Recovery Point found although needed - forcing\n", ctx->log_name)); + } + } + if (ctx->is_hevc) { + ctx->hevc_state->sei.recovery_point.valid = 0; + } else { + ctx->avc_state->sei.recovery_point.valid = 0; + } + if (bIntraSlice && ctx->force_sync && (ctx->sei_recovery_frame_count==0)) + slice_force_ref = GF_TRUE; + } + ctx->au_sap = au_sap_type; + ctx->bottom_field_flag = bottom_field_flag; + } + + if (slice_poc < ctx->poc_shift) { + + u32 i, count = gf_list_count(ctx->pck_queue); + for (i=0; ipck_queue, i); + assert(q_pck); + dts = gf_filter_pck_get_dts(q_pck); + if (dts == GF_FILTER_NO_TS) continue; + cts = gf_filter_pck_get_cts(q_pck); + cts += ctx->poc_shift; + cts -= slice_poc; + gf_filter_pck_set_cts(q_pck, cts); + } + + ctx->poc_shift = slice_poc; + } + + /*if #pics, compute smallest POC increase*/ + if (slice_poc != ctx->last_poc) { + s32 pdiff = ABS(ctx->last_poc - slice_poc); + + if ((slice_poc < 0) && !ctx->last_poc) + ctx->poc_diff = 0; + + if (!ctx->poc_diff || (ctx->poc_diff > (s32) pdiff ) ) { + ctx->poc_diff = pdiff; + ctx->poc_probe_done = GF_FALSE; + } else if (first_in_au) { + //second frame with the same poc diff, we should be able to properly recompute CTSs + ctx->poc_probe_done = GF_TRUE; + } + ctx->last_poc = slice_poc; + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_PARSER, ("[%s] POC is %d - min poc diff %d - slice is ref %d\n", ctx->log_name, slice_poc, ctx->poc_diff, slice_is_ref)); + + /*ref slice, reset poc*/ + if (slice_is_ref) { + if (first_in_au) { + //two consecutive IDRs, force poc_diff to 0 to force frame dispatch + if (ctx->last_frame_is_idr) ctx->poc_diff=1; + //new ref frame, dispatch all pending packets + naludmx_enqueue_or_dispatch(ctx, NULL, GF_TRUE); + + ctx->max_last_poc = ctx->last_poc = ctx->max_last_b_poc = 0; + ctx->poc_shift = 0; + //force probing of POC diff, this will prevent dispatching frames with wrong CTS until we have a clue of min poc_diff used + ctx->poc_probe_done = 0; + ctx->last_frame_is_idr = GF_TRUE; + } + } + /*forced ref slice*/ + else if (slice_force_ref) { + ctx->last_frame_is_idr = GF_FALSE; + if (first_in_au) { + //new ref frame, dispatch all pending packets + naludmx_enqueue_or_dispatch(ctx, NULL, GF_TRUE); + + /*adjust POC shift as sample will now be marked as sync, so we must store poc as if IDR (eg POC=0) for our CTS offset computing to be correct*/ + ctx->poc_shift = slice_poc; + + //force probing of POC diff, this will prevent dispatching frames with wrong CTS until we have a clue of min poc_diff used + ctx->poc_probe_done = 0; + } + } + /*strictly less - this is a new P slice*/ + else if (ctx->max_last_poc < ctx->last_poc) { + ctx->max_last_b_poc = 0; + ctx->max_last_poc = ctx->last_poc; + ctx->last_frame_is_idr = GF_FALSE; + } + /*stricly greater*/ + else if (slice_is_b && (ctx->max_last_poc > ctx->last_poc)) { + ctx->last_frame_is_idr = GF_FALSE; + if (!ctx->max_last_b_poc) { + ctx->max_last_b_poc = ctx->last_poc; + } + /*if same poc than last max, this is a B-slice*/ + else if (ctx->last_poc > ctx->max_last_b_poc) { + ctx->max_last_b_poc = ctx->last_poc; + } + /*otherwise we had a B-slice reference: do nothing*/ + } else { + ctx->last_frame_is_idr = GF_FALSE; + } + + + if (ctx->deps) { + if (nal_ref_idc) { + ctx->has_ref_slices = GF_TRUE; + } + if (!ctx->is_hevc && (ctx->avc_state->s_info.redundant_pic_cnt) ) { + ctx->has_redundant = GF_TRUE; + } + } + } + + //we skipped bytes already in store + end of start code present in packet, so the size of the first object + //needs adjustement + if (nal_hdr_in_store && !next_size && !full_nal_required) { + size += nal_bytes_from_store; + } + + if (! full_nal) { + u8 b3 = start[remain-3]; + u8 b2 = start[remain-2]; + u8 b1 = start[remain-1]; + + //we may have a startcode at the end of the packet, store it and don't dispatch the last 3 bytes ! + if (!b1 || !b2 || !b3) { + copy_last_bytes = GF_TRUE; + assert(size >= 3); + size -= 3; + ctx->bytes_in_header = 3; + } + } + + au_start = ctx->first_pck_in_au ? GF_FALSE : GF_TRUE; + + if (ctx->has_initial_aud) { + u32 audelim_size = ctx->is_hevc ? 3 : 2; + /*dst_pck = */naludmx_start_nalu(ctx, audelim_size, GF_FALSE, &au_start, &pck_data); + memcpy(pck_data + ctx->nal_length , ctx->init_aud, audelim_size); + ctx->has_initial_aud = GF_FALSE; + if (ctx->subsamples) { + naludmx_add_subsample(ctx, audelim_size, avc_svc_subs_priority, avc_svc_subs_reserved); + } + } + if (ctx->sei_buffer_size) { + //sei buffer is already nal size prefixed + /*dst_pck = */naludmx_start_nalu(ctx, ctx->sei_buffer_size, GF_TRUE, &au_start, &pck_data); + memcpy(pck_data, ctx->sei_buffer, ctx->sei_buffer_size); + if (ctx->subsamples) { + naludmx_add_subsample(ctx, ctx->sei_buffer_size - ctx->nal_length, avc_svc_subs_priority, avc_svc_subs_reserved); + } + ctx->sei_buffer_size = 0; + } + + if (ctx->svc_prefix_buffer_size) { + /*dst_pck = */naludmx_start_nalu(ctx, ctx->svc_prefix_buffer_size, GF_FALSE, &au_start, &pck_data); + memcpy(pck_data + ctx->nal_length, ctx->svc_prefix_buffer, ctx->svc_prefix_buffer_size); + if (ctx->subsamples) { + naludmx_add_subsample(ctx, ctx->svc_prefix_buffer_size, ctx->svc_nalu_prefix_priority, ctx->svc_nalu_prefix_reserved); + } + ctx->svc_prefix_buffer_size = 0; + } + + //nalu size field + /*dst_pck = */naludmx_start_nalu(ctx, (u32) size, GF_FALSE, &au_start, &pck_data); + pck_data += ctx->nal_length; + + //add subsample info before touching the size + if (ctx->subsamples) { + naludmx_add_subsample(ctx, (u32) size, avc_svc_subs_priority, avc_svc_subs_reserved); + } + + //bytes come from both our store and the data packet + if (nal_hdr_in_store) { + memcpy(pck_data, hdr_start, nal_bytes_from_store); + assert(size >= nal_bytes_from_store); + size -= nal_bytes_from_store; + if (size) + memcpy(pck_data + nal_bytes_from_store, pck_start, (size_t) size); + + if (next_size) { + assert(!size); + memmove(ctx->hdr_store, hdr_start+nal_bytes_from_store,ctx->bytes_in_header - nal_bytes_from_store); + ctx->bytes_in_header -= next_size+store_sc_size; + continue; + } + //we're done consuming data from previous packet, switch timing + naldmx_switch_timestamps(ctx, pck); + } else { + //bytes only come from the data packet + memcpy(pck_data, pck_start, (size_t) size); + } + + if (! full_nal) { + if (copy_last_bytes) { + memcpy(ctx->hdr_store, start+remain-3, 3); + } + break; + } + + assert(remain >= size); + start = pck_start + size; + assert((u32) (start - (u8*)data) <= pck_size); + remain = pck_size - (u32) (start - (u8*)data); + + + //don't demux too much of input, abort when we would block. This avoid dispatching + //a huge number of frames in a single call + if (remain && gf_filter_pid_would_block(ctx->opid)) { + ctx->resume_from = (u32) ((char *)start - (char *)data); + if (data == ctx->hdr_store) { + assert(ctx->resume_from > hdr_size_at_resume); + ctx->resume_from -= hdr_size_at_resume; + } else { + assert(ctx->resume_from<=pck_size); + } + return GF_OK; + } + } + if (is_eos) + return naludmx_process(filter); + + if ((ctx->nb_nalus>nalu_before) && gf_filter_reporting_enabled(filter)) { + char szStatus[1024]; + + sprintf(szStatus, "%s %dx%d % 10d NALU % 8d I % 8d P % 8d B % 8d SEI", ctx->is_hevc ? "HEVC":"AVC|H264", ctx->width, ctx->height, ctx->nb_nalus, ctx->nb_i, ctx->nb_p, ctx->nb_b, ctx->nb_sei); + gf_filter_update_status(filter, -1, szStatus); + } + + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + +static GF_Err naludmx_initialize(GF_Filter *filter) +{ + GF_NALUDmxCtx *ctx = gf_filter_get_udta(filter); + ctx->hdr_store_size = 0; + ctx->hdr_store_alloc = SAFETY_NAL_STORE; + ctx->hdr_store = gf_malloc(sizeof(char)*SAFETY_NAL_STORE); + ctx->sps = gf_list_new(); + ctx->pps = gf_list_new(); + switch (ctx->nal_length) { + case 1: + ctx->max_nalu_size_allowed = 0xFF; + break; + case 2: + ctx->max_nalu_size_allowed = 0xFFFF; + break; + case 4: + ctx->max_nalu_size_allowed = 0xFFFFFFFF; + break; + case 0: + ctx->max_nalu_size_allowed = 0xFFFFFFFF; + ctx->nal_length = 4; + ctx->nal_adjusted = GF_TRUE; + break; + default: + GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("[%s] NAL size length %d is not allowed, defaulting to 4 bytes\n", ctx->log_name)); + ctx->max_nalu_size_allowed = 0xFFFFFFFF; + ctx->nal_length = 4; + break; + } + return GF_OK; +} + +static void naludmx_del_param_list(GF_List *ps) +{ + if (!ps) return; + while (gf_list_count(ps)) { + GF_AVCConfigSlot *sl = gf_list_pop_back(ps); + if (sl->data) gf_free(sl->data); + gf_free(sl); + } + gf_list_del(ps); +} + +static void naludmx_log_stats(GF_NALUDmxCtx *ctx) +{ + u32 i, count; + const char *msg_import; + u32 nb_frames = 0; + if (ctx->cur_fps.den) + nb_frames = (u32) (ctx->dts / ctx->cur_fps.den); + + if (ctx->idur.den && ctx->idur.num) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("%s duration specified at import time, may have parsed more frames than imported\n", ctx->log_name)); + msg_import = "parsed"; + } else { + msg_import = "Import results:"; + } + + if (ctx->nb_si || ctx->nb_sp) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("%s %s %d frames (%d NALUs) - Slices: %d I %d P %d B %d SP %d SI - %d SEI - %d IDR\n", ctx->log_name, msg_import, nb_frames, ctx->nb_nalus, ctx->nb_i, ctx->nb_p, ctx->nb_b, ctx->nb_sp, ctx->nb_si, ctx->nb_sei, ctx->nb_idr )); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("%s %s %d samples (%d NALUs) - Slices: %d I %d P %d B - %d SEI - %d IDR\n", + ctx->log_name, msg_import, nb_frames, ctx->nb_nalus, ctx->nb_i, ctx->nb_p, ctx->nb_b, ctx->nb_sei, ctx->nb_idr)); + } + + if (!ctx->is_hevc) { + count = gf_list_count(ctx->sps); + for (i=0; isps, i); + sps = & ctx->avc_state->sps[svcc->id]; + if (sps->nb_ei || sps->nb_ep) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("%s SVC (SSPS ID %d, %dx%d) %s Slices: %d I %d P %d B\n", ctx->log_name, svcc->id - GF_SVC_SSPS_ID_SHIFT, sps->width, sps->height, msg_import, sps->nb_ei, sps->nb_ep, sps->nb_eb )); + } + } + } else if (ctx->nb_e_i || ctx->nb_e_p || ctx->nb_e_b) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("%s L-HEVC %s Slices: %d I %d P %d B\n", ctx->log_name, msg_import, ctx->nb_e_i, ctx->nb_e_p, ctx->nb_e_b )); + } + + if (ctx->max_total_delay>1) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("%s Stream uses forward prediction - stream CTS offset: %d frames\n", ctx->log_name, ctx->max_total_delay)); + } + + if (!ctx->nal_adjusted) { + if ((ctx->max_nalu_size < 0xFF) && (ctx->nal_length>1) ){ + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("%s Max NALU size is %d - stream could be optimized by setting nal_length=1\n", ctx->log_name, ctx->max_nalu_size)); + } else if ((ctx->max_nalu_size < 0xFFFF) && (ctx->nal_length>2) ){ + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("%s Max NALU size is %d - stream could be optimized by setting nal_length=2\n", ctx->log_name, ctx->max_nalu_size)); + } + } +} + +static void naludmx_finalize(GF_Filter *filter) +{ + GF_NALUDmxCtx *ctx = gf_filter_get_udta(filter); + + if (ctx->importer) naludmx_log_stats(ctx); + + if (ctx->bs_r) gf_bs_del(ctx->bs_r); + if (ctx->bs_w) gf_bs_del(ctx->bs_w); + if (ctx->indexes) gf_free(ctx->indexes); + if (ctx->hdr_store) gf_free(ctx->hdr_store); + if (ctx->pck_queue) { + while (gf_list_count(ctx->pck_queue)) { + GF_FilterPacket *pck = gf_list_pop_back(ctx->pck_queue); + gf_filter_pck_discard(pck); + } + gf_list_del(ctx->pck_queue); + } + if (ctx->sei_buffer) gf_free(ctx->sei_buffer); + if (ctx->svc_prefix_buffer) gf_free(ctx->svc_prefix_buffer); + if (ctx->subsamp_buffer) gf_free(ctx->subsamp_buffer); + + naludmx_del_param_list(ctx->sps); + naludmx_del_param_list(ctx->pps); + naludmx_del_param_list(ctx->vps); + naludmx_del_param_list(ctx->sps_ext); + naludmx_del_param_list(ctx->pps_svc); + + if (ctx->avc_state) gf_free(ctx->avc_state); + if (ctx->hevc_state) gf_free(ctx->hevc_state); +} + + +static const char *naludmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + u32 sc, sc_size; + u32 not_hevc=0; + u32 not_avc=0; + u32 nb_hevc=0; + u32 nb_avc=0; + u32 nb_nalus=0; + u32 nb_hevc_zero=0; + u32 nb_avc_zero=0; + u32 nb_sps_hevc=0,nb_pps_hevc=0,nb_vps_hevc=0; + u32 nb_sps_avc=0,nb_pps_avc=0; + + while (size) { + u32 avc_type=0; + u32 hevc_type=0; + sc = gf_media_nalu_next_start_code(data, size, &sc_size); + if (!sc_size) break; + + data += sc + sc_size; + if (size <= sc + sc_size) break; + size -= sc + sc_size; + + nb_nalus++; + + hevc_type = (data[0] & 0x7E) >> 1; + if (hevc_type<=40) { + nb_hevc++; + switch (hevc_type) { + case GF_HEVC_NALU_PIC_PARAM: + nb_pps_hevc++; + break; + case GF_HEVC_NALU_SEQ_PARAM: + nb_sps_hevc++; + break; + case GF_HEVC_NALU_VID_PARAM: + nb_vps_hevc++; + break; + case 0: + nb_hevc_zero++; + break; + } + } else { + not_hevc++; + } + + avc_type = data[0] & 0x1F; + if (avc_type && avc_type<=24) { + nb_avc++; + switch (avc_type) { + case GF_AVC_NALU_PIC_PARAM: + nb_pps_avc++; + break; + case GF_AVC_NALU_SEQ_PARAM: + nb_sps_avc++; + break; + case 0: + nb_avc_zero++; + break; + } + } else { + not_avc++; + } + } + if (!nb_sps_avc || !nb_pps_avc) nb_avc=0; + if (!nb_sps_hevc || !nb_pps_hevc || !nb_vps_hevc) nb_hevc=0; + if (not_avc) nb_avc=0; + if (not_hevc) nb_hevc=0; + + if (not_avc && not_hevc) return NULL; + if (nb_avc==nb_avc_zero) nb_avc=0; + if (nb_hevc==nb_hevc_zero) nb_hevc=0; + + if (!nb_hevc && !nb_avc) return NULL; + *score = GF_FPROBE_SUPPORTED; + if (!nb_hevc) return "video/avc"; + if (!nb_avc) return "video/hevc"; + if (nb_hevc>nb_avc) return "video/hevc"; + return "video/avc"; +} + +static const GF_FilterCapability NALUDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "264|h264|26L|h26L|h26l|avc|svc|mvc|hevc|hvc|265|h265|shvc|lvhc|mhvc"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "video/avc|video/h264|video/svc|video/mvc|video/hevc|video/lhvc|video/shvc|video/mhvc"), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_AVC), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_AVC_PS), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_SVC), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_MVC), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_HEVC), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_LHVC), + CAP_BOOL(GF_CAPS_OUTPUT_STATIC_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AVC), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AVC_PS), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_SVC), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MVC), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_HEVC), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_LHVC), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + +#define OFFS(_n) #_n, offsetof(GF_NALUDmxCtx, _n) +static const GF_FilterArgs NALUDmxArgs[] = +{ + { OFFS(fps), "import frame rate (0 default to FPS from bitstream or 25 Hz)", GF_PROP_FRACTION, "0/1000", NULL, 0}, + { OFFS(index), "indexing window length. If 0, bitstream is not probed for duration. A negative value skips the indexing if the source file is larger than 100M (slows down importers) unless a play with start range > 0 is issued, otherwise uses the positive value", GF_PROP_DOUBLE, "-1.0", NULL, 0}, + { OFFS(explicit), "use explicit layered (SVC/LHVC) import", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(strict_poc), "delay frame output of an entire GOP to ensure CTS info is correct when POC suddenly changes\n" + "- off: disable GOP buffering\n" + "- on: enable GOP buffering, assuming no error in POC\n" + "- error: enable GOP buffering and try to detect lost frames", GF_PROP_UINT, "off", "off|on|error", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(nosei), "remove all sei messages", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(nosvc), "remove all SVC/MVC/LHVC data", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(novpsext), "remove all VPS extensions", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(importer), "compatibility with old importer, displays import results", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(idur), "compatibility with old importer to log imported frames only", GF_PROP_FRACTION, "0", NULL, GF_FS_ARG_HINT_HIDE}, + { OFFS(nal_length), "set number of bytes used to code length field: 1, 2 or 4", GF_PROP_UINT, "4", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(subsamples), "import subsamples information", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(deps), "import samples dependencies information", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(seirw), "rewrite AVC sei messages for ISOBMFF constraints", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(audelim), "keep Access Unit delimiter in payload", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(analyze), "skip reformat of decoder config and SEI and dispatch all NAL in input order - shall only be used with inspect filter analyze mode!", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_HIDE}, + {0} +}; + + +GF_FilterRegister NALUDmxRegister = { + .name = "rfnalu", + GF_FS_SET_DESCRIPTION("AVC/HEVC reframer") + GF_FS_SET_HELP("This filter parses AVC|H264 and HEVC files/data and outputs corresponding video PID and frames.\n" + "This demuxer only produces ISOBMFF-compatible output: start codes are removed, NALU length field added and avcC/hvcC config created.\nNote: The demux uses negative CTS offsets: CTS is corrrect, but some frames may have DTS greater than CTS.") + .private_size = sizeof(GF_NALUDmxCtx), + .args = NALUDmxArgs, + .initialize = naludmx_initialize, + .finalize = naludmx_finalize, + SETCAPS(NALUDmxCaps), + .configure_pid = naludmx_configure_pid, + .process = naludmx_process, + .process_event = naludmx_process_event, + .probe_data = naludmx_probe_data, +}; + + +const GF_FilterRegister *naludmx_register(GF_FilterSession *session) +{ + return &NALUDmxRegister; +} + +#else +const GF_FilterRegister *naludmx_register(GF_FilterSession *session) +{ + return NULL; +} +#endif //GPAC_DISABLE_AV_PARSERS diff --git a/src/filters/reframe_prores.c b/src/filters/reframe_prores.c new file mode 100644 index 0000000..6c3034d --- /dev/null +++ b/src/filters/reframe_prores.c @@ -0,0 +1,671 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2019 + * All rights reserved + * + * This file is part of GPAC / ProRes reframer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_AV_PARSERS + +typedef struct +{ + //filter args + GF_Fraction fps; + Bool findex; + char *cid; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + GF_BitStream *bs; + u64 cts; + GF_Fraction64 duration; + u64 file_size, file_pos; + Double start_range; + Bool rewind; + u32 cur_frame; + u32 timescale; + GF_Fraction cur_fps; + + u8 *buffer; + u32 buf_size, alloc_size; + + GF_ProResFrameInfo cur_cfg; + + Bool is_playing; + Bool is_file, file_loaded; + Bool initial_play_done; + + //when source is not a file/pipe/net stream + GF_FilterPacket *src_pck; + + /*frame index 0/NULL if findex is not set*/ + u32 nb_frames; + u32 *frame_sizes; +} GF_ProResDmxCtx; + + +GF_Err proresdmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_ProResDmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) ctx->timescale = p->value.uint; + + if (ctx->timescale && !ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, NULL); + } + if (ctx->timescale) { + //if we have a FPS prop, use it + p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS); + if (p) ctx->cur_fps = p->value.frac; + } + return GF_OK; +} + + +static void proresdmx_check_dur(GF_Filter *filter, GF_ProResDmxCtx *ctx) +{ + FILE *stream; + GF_BitStream *bs; + u64 duration; + u32 idx_size; + const char *filepath=NULL; + const GF_PropertyValue *p; + if (!ctx->opid || ctx->timescale || ctx->file_loaded) return; + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) { + ctx->is_file = GF_FALSE; + ctx->file_loaded = GF_TRUE; + return; + } + filepath = p->value.string; + ctx->is_file = GF_TRUE; + + if (ctx->findex==1) { + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_DOWN_SIZE); + if (!p || (p->value.longuint > 100000000)) { + GF_LOG(GF_LOG_INFO, GF_LOG_PARSER, ("[ProResDmx] Source file larger than 100M, skipping indexing\n")); + } else { + ctx->findex = 2; + } + } + if (!ctx->findex) + return; + + stream = gf_fopen(filepath, "rb"); + if (!stream) return; + + bs = gf_bs_from_file(stream, GF_BITSTREAM_READ); + + idx_size = ctx->nb_frames; + ctx->nb_frames = 0; + ctx->file_size = gf_bs_available(bs); + + duration = 0; + while (gf_bs_available(bs)) { + u64 frame_start = gf_bs_get_position(bs); + u32 fsize = gf_bs_read_u32(bs); + u32 fmark = gf_bs_read_u32(bs); + gf_bs_seek(bs, frame_start + fsize); + if (fmark != GF_4CC('i','c','p','f')) + break; + + duration += ctx->cur_fps.den; + + if (!idx_size) idx_size = 10; + else if (idx_size == ctx->nb_frames) idx_size += 10; + ctx->frame_sizes = gf_realloc(ctx->frame_sizes, sizeof(u32)*idx_size); + ctx->frame_sizes[ctx->nb_frames] = fsize; + ctx->nb_frames++; + } + gf_bs_del(bs); + gf_fclose(stream); + + if (!ctx->duration.num || (ctx->duration.num * ctx->cur_fps.num != duration * ctx->duration.den)) { + ctx->duration.num = (s32) duration; + ctx->duration.den = ctx->cur_fps.num; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + } + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, & PROP_BOOL(GF_TRUE) ); +} + + +static Bool proresdmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i; + u64 file_pos = 0; + GF_FilterEvent fevt; + GF_ProResDmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (!ctx->is_playing) { + ctx->is_playing = GF_TRUE; + ctx->cts = 0; + } + if (! ctx->is_file) { + ctx->buf_size = 0; + return GF_FALSE; + } + ctx->start_range = evt->play.start_range; + + if (ctx->start_range) { + ctx->cur_frame = 0; + + if (ctx->findex==1) { + ctx->findex = 2; + ctx->file_loaded = GF_FALSE; + ctx->duration.den = ctx->duration.num = 0; + GF_LOG(GF_LOG_INFO, GF_LOG_PARSER, ("[ProResDmx] Play request from %d, building index\n", ctx->start_range)); + proresdmx_check_dur(filter, ctx); + } + if ((evt->play.speed<0) && (ctx->start_range<0)) { + ctx->start_range = (Double) ctx->duration.num; + ctx->start_range /= ctx->duration.den; + } + + for (i=0; inb_frames; i++) { + Double t = ctx->cur_frame * ctx->cur_fps.den / ctx->cur_fps.num; + ctx->cts = ctx->cur_frame * ctx->cur_fps.den; + if (t>=ctx->start_range) { + break; + } + if (i+1==ctx->nb_frames) + break; + ctx->cur_frame++; + file_pos += ctx->frame_sizes[i]; + } + } + + ctx->rewind = (ctx->nb_frames && (evt->play.speed<0)) ? GF_TRUE : GF_FALSE; + + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!file_pos) + return GF_TRUE; + } + ctx->buf_size = 0; + ctx->file_pos = file_pos; + + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + //don't cancel event + ctx->is_playing = GF_FALSE; + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +static GFINLINE void proresdmx_update_cts(GF_ProResDmxCtx *ctx) +{ + u64 inc; + assert(ctx->cur_fps.num); + assert(ctx->cur_fps.den); + + if (ctx->timescale) { + inc = ctx->cur_fps.den; + inc *= ctx->timescale; + inc /= ctx->cur_fps.num; + } else { + inc = ctx->cur_fps.den; + } + if (ctx->rewind) + ctx->cts -= inc; + else + ctx->cts += inc; +} + +static void proresdmx_check_pid(GF_Filter *filter, GF_ProResDmxCtx *ctx, GF_ProResFrameInfo *finfo) +{ + Bool same_cfg = GF_TRUE; + u32 codec_id; + u64 bitrate; + GF_Fraction fps; + +#define CHECK_CFG(_name) if (ctx->cur_cfg._name!=finfo->_name) same_cfg = GF_FALSE; + + CHECK_CFG(width) + CHECK_CFG(height) + CHECK_CFG(chroma_format) + CHECK_CFG(interlaced_mode) + CHECK_CFG(aspect_ratio_information) + CHECK_CFG(framerate_code) + CHECK_CFG(color_primaries) + CHECK_CFG(transfer_characteristics) + CHECK_CFG(matrix_coefficients) + CHECK_CFG(alpha_channel_type) + +#undef CHECK_CFG + + if (same_cfg) return; + fps.num = fps.den = 0; + switch (finfo->framerate_code) { + case 1: fps.num = 24000; fps.den = 1001; break; + case 2: fps.num = 24; fps.den = 1; break; + case 3: fps.num = 24; fps.den = 1; break; + case 4: fps.num = 30000; fps.den = 1001; break; + case 5: fps.num = 30; fps.den = 1; break; + case 6: fps.num = 50; fps.den = 1; break; + case 7: fps.num = 60000; fps.den = 1001; break; + case 8: fps.num = 60; fps.den = 1; break; + case 9: fps.num = 100; fps.den = 1; break; + case 10: fps.num = 120000; fps.den = 1001; break; + case 11: fps.num = 120; fps.den = 1; break; + } + + ctx->cur_fps = ctx->fps; + if (!ctx->fps.num || !ctx->fps.den) { + if (fps.num && fps.den) { + ctx->cur_fps = fps; + } else { + ctx->cur_fps.num = 25000; + ctx->cur_fps.den = 1000; + } + } + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + proresdmx_check_dur(filter, ctx); + } + + bitrate = 0; + if (ctx->nb_frames && ctx->file_size) { + u64 dur = ctx->nb_frames * ctx->cur_fps.den / ctx->cur_fps.num; + bitrate = ctx->file_size * 8; + bitrate /= dur; + bitrate /= 1000000; + } + memcpy(&ctx->cur_cfg, finfo, sizeof(GF_ProResFrameInfo)); + if (ctx->cid && (strlen(ctx->cid)>4)) { + codec_id = GF_4CC(ctx->cid[0],ctx->cid[1],ctx->cid[2],ctx->cid[3]); + } else if (finfo->chroma_format==3) { + codec_id = GF_CODECID_AP4H; + } else { + codec_id = GF_CODECID_APCH; + } + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT(GF_STREAM_VISUAL)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(codec_id)); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->cur_fps.num)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FPS, & PROP_FRAC(ctx->cur_fps)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, & PROP_UINT(finfo->width)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, & PROP_UINT(finfo->height)); + switch (finfo->alpha_channel_type) { + case 1: + case 2: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ALPHA, & PROP_BOOL(GF_TRUE)); + break; + default: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ALPHA, NULL); + break; + } + + switch (finfo->aspect_ratio_information) { + case 0: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, NULL); + break; + case 1: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, & PROP_FRAC_INT(1, 1) ); + break; + case 2: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, & PROP_FRAC_INT(4, 3) ); + break; + case 3: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, & PROP_FRAC_INT(16, 9) ); + break; + default: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, NULL); + break; + } + + if (ctx->duration.num) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + + /*prores is all intra, we support rewind*/ + if (ctx->is_file && ctx->findex) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, & PROP_UINT(GF_PLAYBACK_MODE_REWIND) ); + } + if (bitrate && !gf_sys_is_test_mode()) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_BITRATE, & PROP_UINT((u32)bitrate) ); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_PRIMARIES, & PROP_UINT(finfo->color_primaries) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_TRANSFER, & PROP_UINT(finfo->transfer_characteristics) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_COLR_MX, & PROP_UINT(finfo->matrix_coefficients) ); + + //set interlaced or remove interlaced property + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_INTERLACED, ctx->cur_cfg.interlaced_mode ? & PROP_UINT(GF_TRUE) : NULL); +} + + + +GF_Err proresdmx_process_buffer(GF_Filter *filter, GF_ProResDmxCtx *ctx, const u8 *data, u32 data_size, Bool is_copy) +{ + u32 last_frame_end = 0; + GF_Err e = GF_OK; + + if (!ctx->bs) ctx->bs = gf_bs_new(data, data_size, GF_BITSTREAM_READ); + else gf_bs_reassign_buffer(ctx->bs, data, data_size); + + while (gf_bs_available(ctx->bs)) { + u8 *output; + GF_FilterPacket *pck; + GF_ProResFrameInfo finfo; + e = gf_media_prores_parse_bs(ctx->bs, &finfo); + + if (e) { + break; + } + proresdmx_check_pid(filter, ctx, &finfo); + + if (!ctx->is_playing && ctx->opid) + break; + + if (gf_bs_available(ctx->bs)opid, finfo.frame_size, &output); + if (!pck) break; + gf_bs_read_data(ctx->bs, output, finfo.frame_size); + + if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, pck); + + gf_filter_pck_set_dts(pck, ctx->cts); + gf_filter_pck_set_cts(pck, ctx->cts); + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); + gf_filter_pck_set_byte_offset(pck, ctx->file_pos); + + gf_filter_pck_send(pck); + proresdmx_update_cts(ctx); + last_frame_end = (u32) gf_bs_get_position(ctx->bs); + + if (ctx->rewind) { + ctx->buf_size = 0; + last_frame_end = 0; + assert(ctx->cur_frame); + ctx->cur_frame--; + if (!ctx->cur_frame) { + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + } else { + GF_FilterEvent fevt; + ctx->file_pos -= ctx->frame_sizes[ctx->cur_frame]; + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = ctx->file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + } + break; + } else { + ctx->file_pos += finfo.frame_size; + } + } + + if (is_copy && last_frame_end) { + assert(ctx->buf_size >= last_frame_end); + memmove(ctx->buffer, ctx->buffer+last_frame_end, sizeof(char) * (ctx->buf_size-last_frame_end)); + ctx->buf_size -= last_frame_end; + } + if (e==GF_EOS) return GF_OK; + if (e==GF_BUFFER_TOO_SMALL) return GF_OK; + return e; +} + +GF_Err proresdmx_process(GF_Filter *filter) +{ + GF_Err e; + GF_ProResDmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + char *data; + u32 pck_size; + + if (!ctx->is_playing && ctx->opid) + return GF_OK; + + if (ctx->rewind && !ctx->cur_frame) { + gf_filter_pid_set_discard(ctx->ipid, GF_TRUE); + return GF_OK; + } + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + //flush + while (ctx->buf_size) { + u32 buf_size = ctx->buf_size; + e = proresdmx_process_buffer(filter, ctx, ctx->buffer, ctx->buf_size, GF_TRUE); + if (e) break; + if (buf_size == ctx->buf_size) { + break; + } + } + + ctx->buf_size = 0; + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = NULL; + return GF_EOS; + } + return GF_OK; + } + + if (ctx->opid) { + if (!ctx->is_playing || gf_filter_pid_would_block(ctx->opid)) + return GF_OK; + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + + //input pid sets some timescale - we flushed pending data , update cts + if (ctx->timescale) { + Bool start, end; + u64 cts; + + e = GF_OK; + + gf_filter_pck_get_framing(pck, &start, &end); + //middle or end of frame, reaggregation + if (!start) { + if (ctx->alloc_size < ctx->buf_size + pck_size) { + ctx->alloc_size = ctx->buf_size + pck_size; + ctx->buffer = gf_realloc(ctx->buffer, ctx->alloc_size); + } + memcpy(ctx->buffer+ctx->buf_size, data, pck_size); + ctx->buf_size += pck_size; + + //end of frame, process av1 + if (end) { + e = proresdmx_process_buffer(filter, ctx, ctx->buffer, ctx->buf_size, GF_TRUE); + } + ctx->buf_size=0; + gf_filter_pid_drop_packet(ctx->ipid); + return e; + } + //flush of pending frame (might have lost something) + if (ctx->buf_size) { + e = proresdmx_process_buffer(filter, ctx, ctx->buffer, ctx->buf_size, GF_TRUE); + ctx->buf_size = 0; + if (e) return e; + } + + //begining of a new frame + cts = gf_filter_pck_get_cts(pck); + if (cts != GF_FILTER_NO_TS) + ctx->cts = cts; + if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck); + ctx->src_pck = pck; + gf_filter_pck_ref_props(&ctx->src_pck); + ctx->buf_size = 0; + + if (!end) { + if (ctx->alloc_size < ctx->buf_size + pck_size) { + ctx->alloc_size = ctx->buf_size + pck_size; + ctx->buffer = gf_realloc(ctx->buffer, ctx->alloc_size); + } + memcpy(ctx->buffer+ctx->buf_size, data, pck_size); + ctx->buf_size += pck_size; + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + assert(start && end); + //process + e = proresdmx_process_buffer(filter, ctx, data, pck_size, GF_FALSE); + + gf_filter_pid_drop_packet(ctx->ipid); + return e; + } + + //not from framed stream, copy buffer + if (ctx->alloc_size < ctx->buf_size + pck_size) { + ctx->alloc_size = ctx->buf_size + pck_size; + ctx->buffer = gf_realloc(ctx->buffer, ctx->alloc_size); + } + memcpy(ctx->buffer+ctx->buf_size, data, pck_size); + ctx->buf_size += pck_size; + e = proresdmx_process_buffer(filter, ctx, ctx->buffer, ctx->buf_size, GF_TRUE); + gf_filter_pid_drop_packet(ctx->ipid); + return e; +} + +static void proresdmx_finalize(GF_Filter *filter) +{ + GF_ProResDmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs) gf_bs_del(ctx->bs); + if (ctx->frame_sizes) gf_free(ctx->frame_sizes); + if (ctx->buffer) gf_free(ctx->buffer); +} + +static const char * proresdmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + if (size<8) return NULL; + + if ((data[4] == 'i') && (data[5] == 'c') && (data[6] == 'p') && (data[7] == 'f')) { + *score = GF_FPROBE_SUPPORTED; + return "video/prores"; + } + return NULL; +} + +static const GF_FilterCapability ProResDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "prores"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "video/prores"), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_APCN), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_APCO), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_APCH), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_APCS), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_AP4X), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_AP4H), + CAP_BOOL(GF_CAPS_OUTPUT_STATIC_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_APCN), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_APCO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_APCH), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_APCS), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AP4X), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AP4H), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + +#define OFFS(_n) #_n, offsetof(GF_ProResDmxCtx, _n) +static const GF_FilterArgs ProResDmxArgs[] = +{ + { OFFS(fps), "import frame rate (0 default to FPS from bitstream or 25 Hz)", GF_PROP_FRACTION, "0/1000", NULL, 0}, + { OFFS(findex), "index frames. If true, filter will be able to work in rewind mode", GF_PROP_BOOL, "true", NULL, 0}, + + { OFFS(cid), "set QT 4CC for the imported media. If not set, defaults to 'ap4h' for YUV444 or 'apch' for YUV422", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + + +GF_FilterRegister ProResDmxRegister = { + .name = "rfprores", + GF_FS_SET_DESCRIPTION("ProRes reframer") + GF_FS_SET_HELP("This filter parses ProRes raw files/data and outputs corresponding visual PID and frames.") + .private_size = sizeof(GF_ProResDmxCtx), + .args = ProResDmxArgs, + .finalize = proresdmx_finalize, + SETCAPS(ProResDmxCaps), + .configure_pid = proresdmx_configure_pid, + .process = proresdmx_process, + .probe_data = proresdmx_probe_data, + .process_event = proresdmx_process_event +}; + + +const GF_FilterRegister *proresdmx_register(GF_FilterSession *session) +{ + return &ProResDmxRegister; +} + +#else +const GF_FilterRegister *proresdmx_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // GPAC_DISABLE_AV_PARSERS + diff --git a/src/filters/reframe_qcp.c b/src/filters/reframe_qcp.c new file mode 100644 index 0000000..cafa6aa --- /dev/null +++ b/src/filters/reframe_qcp.c @@ -0,0 +1,729 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2017 + * All rights reserved + * + * This file is part of GPAC / AMR&EVRC&SMV reframer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +typedef struct +{ + u64 pos; + Double duration; +} QCPIdx; + +typedef struct +{ + //filter args + Double index; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + u32 codecid, sample_rate, block_size; + Bool done; + + u64 cts; + GF_Fraction64 duration; + Double start_range; + + Bool in_seek; + u32 timescale; + Bool is_playing; + Bool is_file; + Bool initial_play_done, file_loaded; + + u32 data_chunk_offset, data_chunk_size, data_chunk_remain; + u32 resume_from; + u32 remaining; + u32 skip_bytes; + u32 vrat_rate_flag, pck_size, rate_table_count; + QCPRateTable rate_table[8]; + + Bool hdr_processed; + + char *buffer; + u32 buffer_alloc, buffer_size; + + GF_BitStream *bs; + + + QCPIdx *indexes; + u32 index_alloc_size, index_size; +} GF_QCPDmxCtx; + + + + +GF_Err qcpdmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_QCPDmxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + return GF_OK; +} + +static GF_Err qcpdmx_process_header(GF_Filter *filter, GF_QCPDmxCtx *ctx, char *data, u32 size, GF_BitStream *file_bs); + +static void qcpdmx_check_dur(GF_Filter *filter, GF_QCPDmxCtx *ctx) +{ + FILE *stream; + GF_BitStream *bs; + u32 i, chunk_size; + GF_Err e; + u32 data_chunk_size = 0; + u64 duration, cur_dur; + char magic[4]; + const GF_PropertyValue *p; + if (!ctx->opid || ctx->timescale || ctx->file_loaded) return; + + if (ctx->index<=0) { + ctx->file_loaded = GF_TRUE; + return; + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH); + if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) { + ctx->is_file = GF_FALSE; + ctx->file_loaded = GF_TRUE; + return; + } + ctx->is_file = GF_TRUE; + + stream = gf_fopen(p->value.string, "rb"); + if (!stream) return; + + ctx->codecid = 0; + ctx->sample_rate = 8000; + ctx->block_size = 160; + + bs = gf_bs_from_file(stream, GF_BITSTREAM_READ); + if (!ctx->hdr_processed ) { + e = qcpdmx_process_header(filter, ctx, NULL, 0, bs); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[QCPDmx] Header parsed error %s\n", gf_error_to_string(e) )); + } + } else { + gf_bs_skip_bytes(bs, 170); + } + while (gf_bs_available(bs) ) { + gf_bs_read_data(bs, magic, 4); + chunk_size = gf_bs_read_u32_le(bs); + + if (strncmp(magic, "data", 4)) { + gf_bs_skip_bytes(bs, chunk_size); + if (chunk_size%2) gf_bs_skip_bytes(bs, 1); + continue; + } + data_chunk_size = chunk_size; + break; + } + if (!data_chunk_size) { + gf_bs_del(bs); + gf_fclose(stream); + return; + } + + ctx->index_size = 0; + ctx->data_chunk_offset = (u32) gf_ftell(stream); + ctx->data_chunk_size = data_chunk_size; + + duration = 0; + cur_dur = 0; + while (data_chunk_size) { + u32 idx, size=0; + u64 pos; + pos = gf_ftell(stream); + /*get frame rate idx*/ + if (ctx->vrat_rate_flag) { + idx = gf_fgetc(stream); + chunk_size-=1; + for (i=0; irate_table_count; i++) { + if (ctx->rate_table[i].rate_idx==idx) { + size = ctx->rate_table[i].pck_size; + break; + } + } + gf_fseek(stream, size, SEEK_CUR); + size++; + } else { + size = ctx->pck_size; + gf_fseek(stream, size, SEEK_CUR); + } + data_chunk_size-= size; + + duration += ctx->block_size; + cur_dur += ctx->block_size; + if (cur_dur > ctx->index * ctx->sample_rate) { + if (!ctx->index_alloc_size) ctx->index_alloc_size = 10; + else if (ctx->index_alloc_size == ctx->index_size) ctx->index_alloc_size *= 2; + ctx->indexes = gf_realloc(ctx->indexes, sizeof(QCPIdx)*ctx->index_alloc_size); + ctx->indexes[ctx->index_size].pos = pos; + ctx->indexes[ctx->index_size].duration = (Double) duration; + ctx->indexes[ctx->index_size].duration /= ctx->sample_rate; + ctx->index_size ++; + cur_dur = 0; + } + } + gf_bs_del(bs); + gf_fclose(stream); + + if (!ctx->duration.num || (ctx->duration.num * ctx->sample_rate != duration * ctx->duration.den)) { + ctx->duration.num = (s32) duration; + ctx->duration.den = ctx->sample_rate; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration)); + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, & PROP_BOOL(GF_TRUE ) ); +} + +static Bool qcpdmx_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 i; + u64 file_pos = 0; + GF_FilterEvent fevt; + GF_QCPDmxCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (!ctx->is_playing) { + ctx->is_playing = GF_TRUE; + ctx->cts = 0; + ctx->remaining = 0; + } + if (! ctx->is_file) { + return GF_FALSE; + } + qcpdmx_check_dur(filter, ctx); + + ctx->start_range = evt->play.start_range; + ctx->in_seek = GF_TRUE; + if (ctx->start_range) { + for (i=1; iindex_size; i++) { + if (ctx->indexes[i].duration>ctx->start_range) { + ctx->cts = (u64) (ctx->indexes[i-1].duration * ctx->sample_rate); + file_pos = ctx->indexes[i-1].pos; + break; + } + } + } + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!file_pos) { + return GF_TRUE; + } + } + if (!file_pos) { + file_pos = ctx->data_chunk_offset; + ctx->data_chunk_remain = ctx->data_chunk_size; + } + + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = file_pos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + ctx->is_playing = GF_FALSE; + //don't cancel event + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +/*QCP codec GUIDs*/ +static const char *QCP_QCELP_GUID_1 = "\x41\x6D\x7F\x5E\x15\xB1\xD0\x11\xBA\x91\x00\x80\x5F\xB4\xB9\x7E"; +static const char *QCP_QCELP_GUID_2 = "\x42\x6D\x7F\x5E\x15\xB1\xD0\x11\xBA\x91\x00\x80\x5F\xB4\xB9\x7E"; +static const char *QCP_EVRC_GUID = "\x8D\xD4\x89\xE6\x76\x90\xB5\x46\x91\xEF\x73\x6A\x51\x00\xCE\xB4"; +static const char *QCP_SMV_GUID = "\x75\x2B\x7C\x8D\x97\xA7\x46\xED\x98\x5E\xD5\x3C\x8C\xC7\x5F\x84"; + +static GF_Err qcpdmx_process_header(GF_Filter *filter, GF_QCPDmxCtx *ctx, char *data, u32 size, GF_BitStream *file_bs) +{ + char magic[12], GUID[17], name[81], fmt[162]; + u32 riff_size, chunk_size, i, avg_bps; + Bool has_pad; + const GF_PropertyValue *p; + GF_BitStream *bs; + + bs = file_bs ? file_bs : gf_bs_new(data, size, GF_BITSTREAM_READ); + + gf_bs_read_data(bs, magic, 4); + if (strnicmp(magic, "RIFF", 4)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[QCPDmx] Broken file: RIFF header not found\n")); + if (!file_bs) gf_bs_del(bs); + return GF_NON_COMPLIANT_BITSTREAM; + } + riff_size = gf_bs_read_u32_le(bs); + gf_bs_read_data(bs, fmt, 162); + gf_bs_seek(bs, 8); + gf_bs_read_data(bs, magic, 4); + if (strnicmp(magic, "QLCM", 4)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[QCPDmx] Broken file: QLCM header not found\n")); + if (!file_bs) gf_bs_del(bs); + return GF_NON_COMPLIANT_BITSTREAM; + } + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_DOWN_SIZE); + if (p && p->value.longuint != riff_size+8) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[QCPDmx] Broken file: RIFF-Size %d got %d\n", p->value.uint - 8, riff_size)); + } + /*fmt*/ + gf_bs_read_data(bs, magic, 4); + if (strnicmp(magic, "fmt ", 4)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[QCPDmx] Broken file: FMT not found\n")); + if (!file_bs) gf_bs_del(bs); + return GF_NON_COMPLIANT_BITSTREAM; + } + chunk_size = gf_bs_read_u32_le(bs); + has_pad = (chunk_size%2) ? GF_TRUE : GF_FALSE; + /*major = */gf_bs_read_u8(bs); + /*minor = */gf_bs_read_u8(bs); + chunk_size -= 2; + /*codec info*/ + gf_bs_read_data(bs, GUID, 16); + GUID[16]=0; + /*version = */gf_bs_read_u16_le(bs); + chunk_size -= 18; + gf_bs_read_data(bs, name, 80); + name[80]=0; + chunk_size -= 80; + avg_bps = gf_bs_read_u16_le(bs); + ctx->pck_size = gf_bs_read_u16_le(bs); + ctx->block_size = gf_bs_read_u16_le(bs); + ctx->sample_rate = gf_bs_read_u16_le(bs); + /*bps = */gf_bs_read_u16_le(bs); + ctx->rate_table_count = gf_bs_read_u32_le(bs); + chunk_size -= 14; + /*skip var rate*/ + for (i=0; i<8; i++) { + ctx->rate_table[i].pck_size = gf_bs_read_u8(bs); + ctx->rate_table[i].rate_idx = gf_bs_read_u8(bs); + } + chunk_size -= 16; + gf_bs_skip_bytes(bs, 5*4);/*reserved*/ + chunk_size -= 20; + gf_bs_skip_bytes(bs, chunk_size); + if (has_pad) gf_bs_read_u8(bs); + + if (!strncmp(GUID, QCP_QCELP_GUID_1, 16) || !strncmp(GUID, QCP_QCELP_GUID_2, 16)) { + ctx->codecid = GF_CODECID_QCELP; + } else if (!strncmp(GUID, QCP_EVRC_GUID, 16)) { + ctx->codecid = GF_CODECID_EVRC; + } else if (!strncmp(GUID, QCP_SMV_GUID, 16)) { + ctx->codecid = GF_CODECID_SMV; + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[QCPDmx] Unsupported codec GUID %s\n", GUID)); + if (!file_bs) gf_bs_del(bs); + return GF_NON_COMPLIANT_BITSTREAM; + } + /*vrat*/ + gf_bs_read_data(bs, magic, 4); + if (strnicmp(magic, "vrat", 4)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[QCPDmx] Broken file: VRAT not found\n")); + if (!file_bs) gf_bs_del(bs); + return GF_NON_COMPLIANT_BITSTREAM; + } + chunk_size = gf_bs_read_u32_le(bs); + has_pad = (chunk_size%2) ? GF_TRUE : GF_FALSE; + ctx->vrat_rate_flag = gf_bs_read_u32_le(bs); + /*size_in_packet =*/gf_bs_read_u32_le(bs); + chunk_size -= 8; + gf_bs_skip_bytes(bs, chunk_size); + if (has_pad) gf_bs_read_u8(bs); + + if (file_bs) return GF_OK; + + gf_bs_del(bs); + + + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT( GF_STREAM_AUDIO)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->sample_rate)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, & PROP_UINT(ctx->sample_rate)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, & PROP_UINT(1) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT(ctx->codecid ) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLES_PER_FRAME, & PROP_UINT(ctx->block_size ) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_BITRATE, & PROP_UINT(avg_bps)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FRAME_SIZE, & PROP_UINT(ctx->pck_size)); + + qcpdmx_check_dur(filter, ctx); + + return GF_OK; + +} + +GF_Err qcpdmx_process(GF_Filter *filter) +{ + GF_QCPDmxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + u64 byte_offset; + u8 *data, *output; + u8 *start; + u32 pck_size, remain; + GF_Err e; + //update duration + qcpdmx_check_dur(filter, ctx); + + if (ctx->done) return GF_EOS; + + if (ctx->opid && !ctx->is_playing) + return GF_OK; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + assert(ctx->remaining == 0); + return GF_EOS; + } + return GF_OK; + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + byte_offset = gf_filter_pck_get_byte_offset(pck); + + start = data; + remain = pck_size; + + //flush not previously dispatched data + if (ctx->remaining) { + u32 to_send = ctx->remaining; + if (ctx->remaining > pck_size) { + to_send = pck_size; + ctx->remaining -= pck_size; + } else { + ctx->remaining = 0; + } + if (! ctx->in_seek) { + GF_FilterPacket *dst_pck = gf_filter_pck_new_alloc(ctx->opid, to_send, &output); + memcpy(output, data, to_send); + + gf_filter_pck_set_cts(dst_pck, ctx->cts); + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + gf_filter_pck_set_framing(dst_pck, GF_FALSE, ctx->remaining ? GF_FALSE : GF_TRUE); + if (byte_offset != GF_FILTER_NO_BO) { + gf_filter_pck_set_byte_offset(dst_pck, byte_offset); + } + gf_filter_pck_send(dst_pck); + } + assert (ctx->data_chunk_remain >= to_send); + ctx->data_chunk_remain -= to_send; + + if (ctx->remaining) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + ctx->cts += ctx->block_size; + start += to_send; + remain -= to_send; + + if (!ctx->data_chunk_remain) { + ctx->done = GF_TRUE; + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + } + + if (ctx->resume_from) { + start += ctx->resume_from; + remain -= ctx->resume_from; + ctx->resume_from = 0; + } + + + while (remain) { + u32 i, chunk_size=0; + u32 idx = 0; + u32 size = 0; + u64 b_offset; + u8 *pck_data; + Bool has_pad; + + if (!ctx->hdr_processed) { + if (ctx->buffer_size + remain < 170) { + if (ctx->buffer_alloc < ctx->buffer_size + remain) { + ctx->buffer_alloc = ctx->buffer_size + remain; + ctx->buffer = gf_realloc(ctx->buffer, ctx->buffer_alloc); + } + memcpy(ctx->buffer + ctx->buffer_size, start, remain); + ctx->buffer_size += remain; + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + ctx->hdr_processed = GF_TRUE; + if (ctx->buffer_size) { + e = qcpdmx_process_header(filter, ctx, ctx->buffer, ctx->buffer_size, NULL); + } else { + e = qcpdmx_process_header(filter, ctx, start, remain, NULL); + } + start += 170 - ctx->buffer_size; + remain -= 170 - ctx->buffer_size; + ctx->buffer_size = 0; + + if (e) { + gf_filter_setup_failure(filter, e); + ctx->done = GF_TRUE; + gf_filter_pid_drop_packet(ctx->ipid); + return GF_EOS; + } + continue; + } + //skip current chunk + if (ctx->skip_bytes) { + if (remainskip_bytes) { + ctx->skip_bytes -= remain; + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + start += ctx->skip_bytes; + remain -= ctx->skip_bytes; + ctx->skip_bytes = 0; + } + + //load chunk tag + if (!ctx->data_chunk_remain) { + char magic[4]; + //load chunk + if (remain<8) { + if (ctx->buffer_alloc < ctx->buffer_size + 8) { + ctx->buffer_alloc = ctx->buffer_size + 8; + ctx->buffer = gf_realloc(ctx->buffer, ctx->buffer_alloc); + } + memcpy(ctx->buffer + ctx->buffer_size, start, remain); + ctx->buffer_size += remain; + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + if (!ctx->buffer_size) { + if (!ctx->bs) { + ctx->bs = gf_bs_new((u8 *) start, remain, GF_BITSTREAM_READ); + } else { + gf_bs_reassign_buffer(ctx->bs, start, remain); + } + } else { + if (!ctx->bs) { + ctx->bs = gf_bs_new((u8 *) ctx->buffer, ctx->buffer_size, GF_BITSTREAM_READ); + } else { + gf_bs_reassign_buffer(ctx->bs, ctx->buffer, ctx->buffer_size); + } + } + + gf_bs_read_data(ctx->bs, magic, 4); + chunk_size = gf_bs_read_u32_le(ctx->bs); + has_pad = (chunk_size%2) ? GF_TRUE : GF_FALSE; + start += 8-ctx->buffer_size; + remain -= 8-ctx->buffer_size; + ctx->buffer_size = 0; + + //wait until we reach data chunk + if (strnicmp(magic, "data", 4)) { + ctx->skip_bytes = chunk_size; + if (has_pad) ctx->skip_bytes++; + continue; + } else { + ctx->data_chunk_size = ctx->data_chunk_remain = chunk_size; + } + } + + //we are in the data chunk + if (!ctx->is_playing) { + ctx->resume_from = (u32) ( (char *)start - (char *)data); + return GF_OK; + } + + b_offset = gf_filter_pck_get_byte_offset(pck); + if (b_offset != GF_FILTER_NO_BO) { + b_offset += (start - (u8 *) data); + } + /*get frame rate idx*/ + if (ctx->vrat_rate_flag) { + idx = start[0]; + //chunk_size-=1; + for (i=0; irate_table_count; i++) { + if (ctx->rate_table[i].rate_idx==idx) { + size = ctx->rate_table[i].pck_size; + break; + } + } + size++; + } else { + size = ctx->pck_size; + } + + if (size > remain) { + ctx->remaining = size - remain; + size = remain; + } else { + ctx->remaining = 0; + } + + if (ctx->in_seek) { + u64 nb_samples_at_seek = (u64) (ctx->start_range * ctx->sample_rate); + if (ctx->cts + ctx->block_size >= nb_samples_at_seek) { + //u32 samples_to_discard = (ctx->cts + ctx->block_size ) - nb_samples_at_seek; + ctx->in_seek = GF_FALSE; + } + } + + if (!ctx->in_seek) { + GF_FilterPacket *dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &pck_data); + memcpy(pck_data, start, size); + + gf_filter_pck_set_framing(dst_pck, GF_TRUE, ctx->remaining ? GF_FALSE : GF_TRUE); + gf_filter_pck_set_cts(dst_pck, ctx->cts); + gf_filter_pck_set_sap(dst_pck, GF_FILTER_SAP_1); + gf_filter_pck_set_duration(dst_pck, ctx->block_size); + if (b_offset != GF_FILTER_NO_BO) + gf_filter_pck_set_byte_offset(dst_pck, b_offset); + + gf_filter_pck_send(dst_pck); + } + + assert (ctx->data_chunk_remain >= size); + ctx->data_chunk_remain -= size; + if (!ctx->data_chunk_remain) { + ctx->done = GF_TRUE; + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + break; + } + + if (ctx->remaining) break; + ctx->cts += ctx->block_size; + start += size; + remain -= size; + + + //don't demux too much of input, abort when we would block. This avoid dispatching + //a huge number of frames in a single call + if (gf_filter_pid_would_block(ctx->opid)) { + ctx->resume_from = (u32) ((char *)start - (char *)data); + return GF_OK; + } + } + gf_filter_pid_drop_packet(ctx->ipid); + + return GF_OK; +} + +static void qcpdmx_finalize(GF_Filter *filter) +{ + GF_QCPDmxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->indexes) gf_free(ctx->indexes); + if (ctx->bs) gf_bs_del(ctx->bs); + if (ctx->buffer) gf_free(ctx->buffer); +} + +static const char *qcpdmx_probe_data(const u8 *data, u32 size, GF_FilterProbeScore *score) +{ + char magic[5]; + Bool is_qcp = GF_TRUE; + GF_BitStream *bs = gf_bs_new(data, size, GF_BITSTREAM_READ); + + magic[4] = 0; + gf_bs_read_data(bs, magic, 4); + if (strnicmp(magic, "RIFF", 4)) { + is_qcp = GF_FALSE; + } else { + /*riff_size = */gf_bs_read_u32_le(bs); + gf_bs_read_data(bs, magic, 4); + if (strnicmp(magic, "QLCM", 4)) { + is_qcp = GF_FALSE; + } + } + gf_bs_del(bs); + if (!is_qcp) return NULL; + *score = GF_FPROBE_SUPPORTED; + return "audio/qcp"; +} + +static const GF_FilterCapability QCPDmxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "qcp"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "audio/qcp"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_QCELP), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_SMV), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_EVRC), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_EVRC_PV), + CAP_BOOL(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + + +#define OFFS(_n) #_n, offsetof(GF_QCPDmxCtx, _n) +static const GF_FilterArgs QCPDmxArgs[] = +{ + { OFFS(index), "indexing window length", GF_PROP_DOUBLE, "1.0", NULL, 0}, + {0} +}; + + +GF_FilterRegister QCPDmxRegister = { + .name = "rfqcp", + GF_FS_SET_DESCRIPTION("QCP reframer") + GF_FS_SET_HELP("This filter parses QCP files/data and outputs corresponding audio PID and frames.") + .private_size = sizeof(GF_QCPDmxCtx), + .args = QCPDmxArgs, + .finalize = qcpdmx_finalize, + SETCAPS(QCPDmxCaps), + .configure_pid = qcpdmx_configure_pid, + .process = qcpdmx_process, + .probe_data = qcpdmx_probe_data, + .process_event = qcpdmx_process_event +}; + + +const GF_FilterRegister *qcpdmx_register(GF_FilterSession *session) +{ + return &QCPDmxRegister; +} + diff --git a/src/filters/reframe_rawpcm.c b/src/filters/reframe_rawpcm.c new file mode 100644 index 0000000..aac6ce5 --- /dev/null +++ b/src/filters/reframe_rawpcm.c @@ -0,0 +1,324 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018 + * All rights reserved + * + * This file is part of GPAC / RAW PCM reframer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +typedef struct +{ + //opts + u32 framelen, safmt, sr, ch; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + Bool file_loaded, is_playing, initial_play_done; + u64 cts; + u32 frame_size, nb_bytes_in_frame, Bps; + u64 filepos, total_frames; + GF_FilterPacket *out_pck; + u8 *out_data; + Bool reverse_play, done; +} GF_PCMReframeCtx; + + + + +GF_Err pcmreframe_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_PCMReframeCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + if (!ctx->safmt) { + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_EXT); + if (p && p->value.string) ctx->safmt = gf_audio_fmt_parse(p->value.string); + } + if (!ctx->safmt) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[PCMReframe] Missing audio format, cannot parse\n")); + return GF_BAD_PARAM; + } + if (!ctx->sr) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[PCMReframe] Missing audio sample rate, cannot parse\n")); + return GF_BAD_PARAM; + } + if (!ctx->ch) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[PCMReframe] Missing audio ch, cannot parse\n")); + return GF_BAD_PARAM; + } + if (!ctx->framelen) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[PCMReframe] Missing audio framelen, using 1024\n")); + ctx->framelen = 1024; + } + + ctx->Bps = gf_audio_fmt_bit_depth(ctx->safmt) / 8; + ctx->frame_size = ctx->framelen * ctx->Bps * ctx->ch; + + if (!ctx->opid) + ctx->opid = gf_filter_pid_new(filter); + + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_framing_mode(ctx->ipid, GF_FALSE); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_AUDIO)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(ctx->sr)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(ctx->ch)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLES_PER_FRAME, &PROP_UINT(ctx->framelen)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(ctx->safmt)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->sr)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_REWIND)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, &PROP_BOOL(GF_TRUE)); + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_DOWN_SIZE); + if (p && p->value.longuint) { + u64 nb_frames = p->value.longuint; + nb_frames /= ctx->Bps * ctx->ch; + ctx->total_frames = p->value.longuint; + ctx->total_frames /= ctx->frame_size; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, &PROP_FRAC64_INT(nb_frames, ctx->sr)); + } + + return GF_OK; +} + +static Bool pcmreframe_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 nb_frames; + GF_FilterEvent fevt; + GF_PCMReframeCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (!ctx->is_playing) { + ctx->is_playing = GF_TRUE; + ctx->cts = 0; + } + ctx->done = GF_FALSE; + + if (!ctx->total_frames) + return GF_TRUE; + + if (evt->play.start_range>=0) { + ctx->cts = (u64) (evt->play.start_range * ctx->sr); + } else { + ctx->cts = (ctx->total_frames-1) * ctx->framelen; + } + nb_frames = (u32) (ctx->cts / ctx->framelen); + if (nb_frames==ctx->total_frames) { + if (evt->play.speed>=0) { + ctx->done = GF_TRUE; + return GF_TRUE; + } + nb_frames--; + ctx->cts = nb_frames * ctx->framelen; + } + + ctx->filepos = nb_frames * ctx->frame_size; + ctx->reverse_play = (evt->play.speed<0) ? GF_TRUE : GF_FALSE; + + if (!ctx->initial_play_done) { + ctx->initial_play_done = GF_TRUE; + //seek will not change the current source state, don't send a seek + if (!ctx->filepos) + return GF_TRUE; + } + //post a seek + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = ctx->filepos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + //don't cancel event + ctx->is_playing = GF_FALSE; + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +void pcmreframe_flush_packet(GF_PCMReframeCtx *ctx) +{ + if (ctx->reverse_play) { + u32 i, nb_bytes_in_sample, nb_samples = ctx->nb_bytes_in_frame / ctx->Bps / ctx->ch; + nb_bytes_in_sample = ctx->Bps * ctx->ch; + for (i=0; iout_data + i*nb_bytes_in_sample, nb_bytes_in_sample); + memcpy(ctx->out_data + i*nb_bytes_in_sample, ctx->out_data + (nb_samples - i - 1)*nb_bytes_in_sample, nb_bytes_in_sample); + memcpy(ctx->out_data + (nb_samples-i-1)*nb_bytes_in_sample, store, nb_bytes_in_sample); + } + } + gf_filter_pck_send(ctx->out_pck); + ctx->out_pck = NULL; +} +GF_Err pcmreframe_process(GF_Filter *filter) +{ + GF_PCMReframeCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + u64 byte_offset; + u8 *data; + u32 pck_size; + + if (ctx->done) return GF_EOS; + + if (!ctx->is_playing && ctx->opid) return GF_OK; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid) && !ctx->reverse_play) { + if (ctx->out_pck) { + pcmreframe_flush_packet(ctx); + } + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + byte_offset = gf_filter_pck_get_byte_offset(pck); + + while (pck_size) { + if (!ctx->out_pck) { + ctx->out_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->frame_size, &ctx->out_data); + if (!ctx->out_pck) return GF_OUT_OF_MEM; + + gf_filter_pck_set_cts(ctx->out_pck, ctx->cts); + gf_filter_pck_set_sap(ctx->out_pck, GF_FILTER_SAP_1); + gf_filter_pck_set_duration(ctx->out_pck, ctx->framelen); + gf_filter_pck_set_byte_offset(ctx->out_pck, byte_offset); + } + + if (pck_size + ctx->nb_bytes_in_frame < ctx->frame_size) { + memcpy(ctx->out_data + ctx->nb_bytes_in_frame, data, pck_size); + ctx->nb_bytes_in_frame += pck_size; + pck_size = 0; + } else { + u32 remain = ctx->frame_size - ctx->nb_bytes_in_frame; + memcpy(ctx->out_data + ctx->nb_bytes_in_frame, data, remain); + ctx->nb_bytes_in_frame = ctx->frame_size; + pcmreframe_flush_packet(ctx); + + pck_size -= remain; + data += remain; + byte_offset += remain; + ctx->out_pck = NULL; + ctx->nb_bytes_in_frame = 0; + + //reverse playback, the remaining data is for the next frame, we want the previous one. + //Trash packet and seek to previous frame + if (ctx->reverse_play) { + GF_FilterEvent fevt; + if (!ctx->cts) { + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + GF_FEVT_INIT(fevt, GF_FEVT_STOP, ctx->ipid); + gf_filter_pid_send_event(ctx->ipid, &fevt); + ctx->done = GF_TRUE; + return GF_EOS; + } + ctx->cts -= ctx->framelen; + ctx->filepos -= ctx->frame_size; + gf_filter_pid_drop_packet(ctx->ipid); + //post a seek, this will trash remaining packets in buffers + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = ctx->filepos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + return GF_OK; + } + ctx->cts += ctx->framelen; + } + } + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + + +static GF_FilterCapability PCMReframeCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "pcm"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "audio/x-pcm"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + +#define OFFS(_n) #_n, offsetof(GF_PCMReframeCtx, _n) +static GF_FilterArgs PCMReframeArgs[] = +{ + { OFFS(sr), "sample rate", GF_PROP_UINT, "44100", NULL, 0}, + { OFFS(safmt), "audio format", GF_PROP_PCMFMT, "none", NULL, 0}, + { OFFS(ch), "number of channels", GF_PROP_UINT, "2", NULL, 0}, + { OFFS(framelen), "number of samples to put in one audio frame. For planar formats, indicate plane size in samples", GF_PROP_UINT, "1024", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + + +GF_FilterRegister PCMReframeRegister = { + .name = "rfpcm", + GF_FS_SET_DESCRIPTION("PCM reframer") + GF_FS_SET_HELP("This filter parses raw PCM file/data and outputs corresponding raw audio PID and frames.") + .private_size = sizeof(GF_PCMReframeCtx), + .args = PCMReframeArgs, + SETCAPS(PCMReframeCaps), + .configure_pid = pcmreframe_configure_pid, + .process = pcmreframe_process, + .process_event = pcmreframe_process_event +}; + + +const GF_FilterRegister *pcmreframe_register(GF_FilterSession *session) +{ + PCMReframeArgs[1].min_max_enum = gf_audio_fmt_all_names(); + PCMReframeCaps[1].val.value.string = (char *) gf_audio_fmt_all_shortnames(); + return &PCMReframeRegister; +} + diff --git a/src/filters/reframe_rawvid.c b/src/filters/reframe_rawvid.c new file mode 100644 index 0000000..8b9e054 --- /dev/null +++ b/src/filters/reframe_rawvid.c @@ -0,0 +1,306 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018 + * All rights reserved + * + * This file is part of GPAC / RAW video (YUV,RGB) reframer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +typedef struct +{ + //opts + GF_PropVec2i size; + GF_PixelFormat spfmt; + GF_Fraction fps; + Bool copy; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + Bool file_loaded, is_playing, initial_play_done; + u64 cts; + u32 frame_size, nb_bytes_in_frame; + u64 filepos, total_frames; + GF_FilterPacket *out_pck; + u8 *out_data; + Bool reverse_play, done; +} GF_RawVidReframeCtx; + + + + +GF_Err rawvidreframe_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + u32 stride, stride_uv; + GF_RawVidReframeCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + if (!ctx->spfmt) { + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_EXT); + if (p && p->value.string) ctx->spfmt = gf_pixel_fmt_parse(p->value.string); + } + if (!ctx->spfmt) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[RawVidReframe] Missing pixel format, cannot parse\n")); + return GF_BAD_PARAM; + } + if (!ctx->size.x || !ctx->size.y) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[RawVidReframe] Missing video frame size, cannot parse\n")); + return GF_BAD_PARAM; + } + + stride = stride_uv = 0; + if (! gf_pixel_get_size_info(ctx->spfmt, ctx->size.x, ctx->size.y, &ctx->frame_size, &stride, &stride_uv, NULL, NULL)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[RawVidReframe] Failed to query pixel format size info\n")); + return GF_BAD_PARAM; + } + + if (!ctx->opid) + ctx->opid = gf_filter_pid_new(filter); + + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + gf_filter_pid_set_framing_mode(ctx->ipid, GF_FALSE); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_VISUAL)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->size.x)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->size.y)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PIXFMT, &PROP_UINT(ctx->spfmt)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FPS, &PROP_FRAC(ctx->fps)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(ctx->fps.num)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(stride)); + if (stride_uv) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE_UV, &PROP_UINT(stride_uv)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_REWIND)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, &PROP_BOOL(GF_TRUE)); + + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED); + if (p && p->value.boolean) ctx->file_loaded = GF_TRUE; + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_DOWN_SIZE); + if (p && p->value.longuint) { + u64 nb_frames = p->value.longuint; + nb_frames /= ctx->frame_size; + ctx->total_frames = nb_frames; + nb_frames *= ctx->fps.den; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, &PROP_FRAC64_INT(nb_frames, ctx->fps.num)); + } + + if (!ctx->copy) { + gf_filter_pid_set_property_str(ctx->opid, "BufferLength", &PROP_UINT(0)); + } + return GF_OK; +} + +static Bool rawvidreframe_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + u32 nb_frames; + GF_FilterEvent fevt; + GF_RawVidReframeCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + if (!ctx->is_playing) { + ctx->is_playing = GF_TRUE; + ctx->cts = 0; + } + ctx->done = GF_FALSE; + if (evt->play.start_range>=0) { + nb_frames = (u32) (evt->play.start_range * ctx->fps.num); + } else { + nb_frames = (u32) (ctx->cts / ctx->fps.den); + } + if (nb_frames>=ctx->total_frames) + nb_frames = (u32) ctx->total_frames-1; + + ctx->cts = nb_frames * ctx->fps.den; + ctx->filepos = nb_frames * ctx->frame_size; + ctx->reverse_play = (evt->play.speed<0) ? GF_TRUE : GF_FALSE; + + //post a seek even for the begining, to try to load frame by frame + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = ctx->filepos; + fevt.seek.hint_block_size = ctx->frame_size; + gf_filter_pid_send_event(ctx->ipid, &fevt); + + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + //don't cancel event + ctx->is_playing = GF_FALSE; + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +GF_Err rawvidreframe_process(GF_Filter *filter) +{ + GF_RawVidReframeCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + u64 byte_offset; + char *data; + u32 pck_size; + + if (ctx->done) return GF_EOS; + + if (!ctx->is_playing && ctx->opid) return GF_OK; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid) && !ctx->reverse_play) { + if (ctx->out_pck) { + gf_filter_pck_send(ctx->out_pck); + ctx->out_pck = NULL; + } + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + byte_offset = gf_filter_pck_get_byte_offset(pck); + + while (pck_size) { + Bool use_ref = GF_FALSE; + if (!ctx->out_pck) { + assert(! ctx->nb_bytes_in_frame); + if (!ctx->copy && (pck_size >= ctx->frame_size)) { + ctx->out_pck = gf_filter_pck_new_ref(ctx->opid, data, ctx->frame_size, pck); + use_ref = GF_TRUE; + } else { + ctx->out_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->frame_size, &ctx->out_data); + } + if (!ctx->out_pck) return GF_OUT_OF_MEM; + + gf_filter_pck_set_cts(ctx->out_pck, ctx->cts); + gf_filter_pck_set_sap(ctx->out_pck, GF_FILTER_SAP_1); + gf_filter_pck_set_duration(ctx->out_pck, ctx->fps.den); + gf_filter_pck_set_byte_offset(ctx->out_pck, byte_offset); + } + + if (pck_size + ctx->nb_bytes_in_frame < ctx->frame_size) { + memcpy(ctx->out_data + ctx->nb_bytes_in_frame, data, pck_size); + ctx->nb_bytes_in_frame += pck_size; + pck_size = 0; + } else { + u32 remain = ctx->frame_size - ctx->nb_bytes_in_frame; + if (!use_ref) { + memcpy(ctx->out_data + ctx->nb_bytes_in_frame, data, remain); + } + gf_filter_pck_send(ctx->out_pck); + + pck_size -= remain; + data += remain; + byte_offset += remain; + ctx->out_pck = NULL; + ctx->nb_bytes_in_frame = 0; + + //reverse playback, the remaining data is for the next frame, we want the previous one. + //Trash packet and seek to previous frame + if (ctx->reverse_play) { + GF_FilterEvent fevt; + if (!ctx->cts) { + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + ctx->done = GF_TRUE; + return GF_EOS; + } + ctx->cts -= ctx->fps.den; + ctx->filepos -= ctx->frame_size; + gf_filter_pid_drop_packet(ctx->ipid); + //post a seek, this will trash remaining packets in buffers + GF_FEVT_INIT(fevt, GF_FEVT_SOURCE_SEEK, ctx->ipid); + fevt.seek.start_offset = ctx->filepos; + gf_filter_pid_send_event(ctx->ipid, &fevt); + return GF_OK; + } + ctx->cts += ctx->fps.den; + } + } + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + + +static GF_FilterCapability RawVidReframeCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "yuv"), + CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_MIME, "video/x-yuv"), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + +#define OFFS(_n) #_n, offsetof(GF_RawVidReframeCtx, _n) +static GF_FilterArgs RawVidReframeArgs[] = +{ + { OFFS(size), "source video resolution", GF_PROP_VEC2I, "0x0", NULL, 0}, + { OFFS(spfmt), "source pixel format. When not set, derived from file extension", GF_PROP_PIXFMT, "none", NULL, 0}, + { OFFS(fps), "number of frames per second", GF_PROP_FRACTION, "25/1", NULL, 0}, + { OFFS(copy), "copy source bytes into output frame. If not set, source bytes are referenced only", GF_PROP_BOOL, "false", NULL, 0}, + {0} +}; + + +GF_FilterRegister RawVidReframeRegister = { + .name = "rfrawvid", + GF_FS_SET_DESCRIPTION("RAW video reframer") + GF_FS_SET_HELP("This filter parses raw YUV and RGB files/data and outputs corresponding raw video PID and frames.") + .private_size = sizeof(GF_RawVidReframeCtx), + .args = RawVidReframeArgs, + SETCAPS(RawVidReframeCaps), + .configure_pid = rawvidreframe_configure_pid, + .process = rawvidreframe_process, + .process_event = rawvidreframe_process_event +}; + + +const GF_FilterRegister *rawvidreframe_register(GF_FilterSession *session) +{ + RawVidReframeArgs[1].min_max_enum = gf_pixel_fmt_all_names(); + RawVidReframeCaps[1].val.value.string = (char *) gf_pixel_fmt_all_shortnames(); + return &RawVidReframeRegister; +} + diff --git a/src/filters/reframer.c b/src/filters/reframer.c new file mode 100644 index 0000000..2a40e71 --- /dev/null +++ b/src/filters/reframer.c @@ -0,0 +1,1859 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / force reframer filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +enum +{ + REFRAME_RT_OFF = 0, + REFRAME_RT_ON, + REFRAME_RT_SYNC, +}; + +enum +{ + REFRAME_ROUND_BEFORE=0, + REFRAME_ROUND_AFTER, + REFRAME_ROUND_CLOSEST, +}; + +enum +{ + RANGE_NONE=0, + RANGE_CLOSED, + RANGE_OPEN, + RANGE_DONE +}; + +enum +{ + EXTRACT_NONE=0, + EXTRACT_RANGE, + EXTRACT_SAP, + EXTRACT_SIZE, + EXTRACT_DUR, +}; + +typedef struct +{ + GF_FilterPid *ipid, *opid; + u32 timescale; + u64 cts_us_at_init; + u64 sys_clock_at_init; + u32 nb_frames; + Bool can_split; + Bool all_saps; + Bool needs_adjust; + + u64 ts_at_range_start_plus_one; + u64 ts_at_range_end; + s64 cts_init; + + GF_List *pck_queue; + //0: not comuted, 1: computed and valid TS, 2: end of stream on pid + u32 range_start_computed; + u64 range_end_reached_ts; + u64 prev_sap_ts; + u32 prev_sap_frame_idx; + u32 nb_frames_range; + u64 sap_ts_plus_one; + Bool first_pck_sent; + + u32 tk_delay; + Bool in_eos; + u32 split_start; + u32 split_end; + + GF_FilterPacket *split_pck; + GF_FilterPacket *resinsert_single_pck; + Bool is_playing; +} RTStream; + +typedef struct +{ + //args + Bool exporter; + GF_PropUIntList saps; + GF_PropUIntList frames; + Bool refs; + u32 rt; + Double speed; + Bool raw; + GF_List *xs, *xe; + Bool nosap, splitrange, xadjust; + u32 xround; + Double seeksafe; + + //internal + Bool filter_sap1; + Bool filter_sap2; + Bool filter_sap3; + Bool filter_sap4; + Bool filter_sap_none; + + GF_List *streams; + RTStream *clock; + + u64 reschedule_in; + u64 clock_val; + + u32 range_type; + u32 cur_range_idx; + GF_Fraction64 cur_start, cur_end; + u64 start_frame_idx_plus_one, end_frame_idx_plus_one; + + Bool timing_initialized; + Bool in_range; + + Bool seekable; + + GF_Fraction64 extract_dur; + u32 extract_mode; + Bool is_range_extraction; + u32 file_idx; + + u64 min_ts_computed; + u32 min_ts_scale; + u64 split_size; + u64 est_file_size; + u64 prev_min_ts_computed; + u32 prev_min_ts_scale; + u32 gop_depth; + + u32 wait_video_range_adjust; + Bool is_eos, has_seen_eos; + u32 nb_non_saps; +} GF_ReframerCtx; + +static void reframer_reset_stream(GF_ReframerCtx *ctx, RTStream *st) +{ + if (st->pck_queue) { + while (gf_list_count(st->pck_queue)) { + GF_FilterPacket *pck = gf_list_pop_front(st->pck_queue); + gf_filter_pck_unref(pck); + } + gf_list_del(st->pck_queue); + } + if (st->split_pck) gf_filter_pck_unref(st->split_pck); + if (st->resinsert_single_pck) gf_filter_pck_unref(st->resinsert_single_pck); + gf_free(st); +} +GF_Err reframer_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 i; + const GF_PropertyValue *p; + GF_ReframerCtx *ctx = gf_filter_get_udta(filter); + RTStream *st = gf_filter_pid_get_udta(pid); + + if (is_remove) { + if (st) { + gf_filter_pid_remove(st->opid); + gf_list_del_item(ctx->streams, st); + reframer_reset_stream(ctx, st); + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + if (st) { + gf_filter_pid_reset_properties(st->opid); + } else { + GF_SAFEALLOC(st, RTStream); + if (!st) return GF_OUT_OF_MEM; + + gf_list_add(ctx->streams, st); + st->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_udta(pid, st); + gf_filter_pid_set_udta(st->opid, st); + st->ipid = pid; + st->pck_queue = gf_list_new(); + st->all_saps = GF_TRUE; + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(st->opid, pid); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) st->timescale = p->value.uint; + else st->timescale = 1000; + + if (!st->all_saps) { + ctx->nb_non_saps--; + st->all_saps = GF_TRUE; + } + st->can_split = GF_FALSE; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (p) { + switch (p->value.uint) { + case GF_STREAM_TEXT: + st->can_split = GF_TRUE; + break; + } + } + st->needs_adjust = ctx->xadjust ? GF_TRUE : GF_FALSE; + + st->tk_delay = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DELAY); + if (p) { + //delay negative is skip: this is CTS adjustment for B-frames: we keep that notif in the stream + if (p->value.sint<0) { + st->tk_delay = 0; + } + //delay positive is delay, we keep the value for RT regulation and range + else { + st->tk_delay = (u32) p->value.sint; + //if range processing, we drop frames not in the target playback range so do not forward delay + if (ctx->range_type) { + gf_filter_pid_set_property(st->opid, GF_PROP_PID_DELAY, NULL); + } + } + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PLAYBACK_MODE); + if (!p || (p->value.uint < GF_PLAYBACK_MODE_SEEK)) + ctx->seekable = GF_FALSE; + + + ctx->filter_sap1 = ctx->filter_sap2 = ctx->filter_sap3 = ctx->filter_sap4 = ctx->filter_sap_none = GF_FALSE; + for (i=0; isaps.nb_items; i++) { + switch (ctx->saps.vals[i]) { + case 1: ctx->filter_sap1 = GF_TRUE; break; + case 2: ctx->filter_sap2 = GF_TRUE; break; + case 3: ctx->filter_sap3 = GF_TRUE; break; + case 4: ctx->filter_sap4 = GF_TRUE; break; + default: ctx->filter_sap_none = GF_TRUE; break; + } + } + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + + return GF_OK; +} + +static Bool reframer_parse_date(char *date, GF_Fraction64 *value, u64 *frame_idx_plus_one, u32 *extract_mode) +{ + u64 v; + value->num =0; + value->den = 0; + + if (extract_mode) + *extract_mode = EXTRACT_RANGE; + + if (date[0] == 'T') { + u32 h=0, m=0, s=0, ms=0; + if (strchr(date, '.')) { + if (sscanf(date, "T%u:%u:%u.%u", &h, &m, &s, &ms) != 4) { + if (sscanf(date, "T%u:%u.%u", &m, &s, &ms) != 3) { + goto exit; + } + } + if (ms>=1000) ms=0; + } else { + if (sscanf(date, "T%u:%u:%u", &h, &m, &s) != 3) { + goto exit; + } + } + v = h*3600 + m*60 + s; + v *= 1000; + v += ms; + value->num = v; + value->den = 1000; + return GF_TRUE; + } + if ((date[0]=='F') || (date[0]=='f')) { + *frame_idx_plus_one = 1 + atoi(date+1); + return GF_TRUE; + } + if (sscanf(date, LLD"/"LLU, &value->num, &value->den)==2) { + return GF_TRUE; + } + if (sscanf(date, LLU, &v)==1) { + value->num = v; + value->den = 1000; + return GF_TRUE; + } + if (!strcmp(date, "RAP") || !strcmp(date, "SAP")) { + if (extract_mode) + *extract_mode = EXTRACT_SAP; + value->num = 0; + value->den = 1000; + return GF_TRUE; + } + if ((date[0]=='D') || (date[0]=='d')) { + if (extract_mode) + *extract_mode = EXTRACT_DUR; + if (sscanf(date+1, LLD"/"LLU, &value->num, &value->den)==2) { + return GF_TRUE; + } + if (sscanf(date+1, LLU, &v)==1) { + value->num = v; + value->den = 1000; + return GF_TRUE; + } + } + if ((date[0]=='S') || (date[0]=='s')) { + GF_PropertyValue p; + if (extract_mode) + *extract_mode = EXTRACT_SIZE; + p = gf_props_parse_value(GF_PROP_LUINT, "size", date+1, NULL, ','); + if (p.type==GF_PROP_LUINT) { + value->den = p.value.longuint; + return GF_TRUE; + } + } + +exit: + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[Reframer] Unrecognized date format %s, expecting TXX:XX:XX[.XX], INT or FRAC\n", date)); + if (extract_mode) + *extract_mode = EXTRACT_NONE; + return GF_FALSE; +} + +static void reframer_load_range(GF_ReframerCtx *ctx) +{ + u32 i, count; + Bool do_seek = ctx->seekable; + u64 prev_frame = ctx->start_frame_idx_plus_one; + GF_Fraction64 prev_end; + char *start_date=NULL, *end_date=NULL; + + if (ctx->extract_mode==EXTRACT_DUR) { + ctx->cur_start.num += (ctx->extract_dur.num * ctx->cur_start.den) / ctx->extract_dur.den; + ctx->cur_end.num += (ctx->extract_dur.num * ctx->cur_end.den) / ctx->extract_dur.den; + ctx->file_idx++; + return; + } + if ((ctx->extract_mode==EXTRACT_SAP) || (ctx->extract_mode==EXTRACT_SIZE)) { + ctx->cur_start = ctx->cur_end; + ctx->min_ts_computed = 0; + ctx->min_ts_scale = 0; + ctx->file_idx++; + return; + } + prev_end = ctx->cur_end; + ctx->start_frame_idx_plus_one = 0; + ctx->end_frame_idx_plus_one = 0; + ctx->cur_start.num = 0; + ctx->cur_start.den = 0; + ctx->cur_end.num = 0; + ctx->cur_end.den = 0; + + count = gf_list_count(ctx->xs); + if (!count) { + if (ctx->range_type) goto range_done; + return; + } + if (ctx->cur_range_idx>=count) { + goto range_done; + } else { + start_date = gf_list_get(ctx->xs, ctx->cur_range_idx); + end_date = gf_list_get(ctx->xe, ctx->cur_range_idx); + } + if (!start_date) + goto range_done; + + ctx->cur_range_idx++; + if (!end_date) ctx->range_type = RANGE_OPEN; + else ctx->range_type = RANGE_CLOSED; + + if (!reframer_parse_date(start_date, &ctx->cur_start, &ctx->start_frame_idx_plus_one, &ctx->extract_mode)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[Reframer] cannot parse start date, assuming end of ranges\n")); + //done + ctx->range_type = RANGE_DONE; + return; + } + + //range in frame + if (ctx->start_frame_idx_plus_one) { + //either range is before or prev range was not frame-based + if (ctx->start_frame_idx_plus_one > prev_frame) + do_seek = GF_TRUE; + } + //range is time based, prev was frame-based, seek + else if (!prev_end.den) { + do_seek = GF_TRUE; + } else { + //cur start is before previous end, need to seek + if (ctx->cur_start.num * prev_end.den < prev_end.num * ctx->cur_start.den) { + do_seek = GF_TRUE; + } + //cur start is less than our seek safety from previous end, do not seek + if (ctx->cur_start.num * prev_end.den < (prev_end.num + ctx->seeksafe*prev_end.den) * ctx->cur_start.den) + do_seek = GF_FALSE; + } + //do not issue seek on first range, done when catching play requests + if (ctx->cur_range_idx==1) { + do_seek = GF_FALSE; + } + + if (!ctx->seekable && do_seek) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Reframer] ranges not in order and input not seekable, aborting extraction\n")); + goto range_done; + } + + ctx->is_range_extraction = ((ctx->extract_mode==EXTRACT_RANGE) || (ctx->extract_mode==EXTRACT_DUR)) ? GF_TRUE : GF_FALSE; + + if (ctx->extract_mode != EXTRACT_RANGE) { + end_date = NULL; + if (ctx->extract_mode==EXTRACT_DUR) { + ctx->extract_dur = ctx->cur_start; + ctx->cur_start.num = 0; + ctx->cur_start.den = ctx->extract_dur.den; + ctx->cur_end = ctx->extract_dur; + ctx->range_type = RANGE_CLOSED; + ctx->file_idx = 1; + ctx->splitrange = GF_TRUE; + ctx->xadjust = GF_TRUE; + } + else if (ctx->extract_mode==EXTRACT_SIZE) { + ctx->splitrange = GF_TRUE; + ctx->split_size = ctx->cur_start.den; + if (!ctx->split_size) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[Reframer] invalid split size %d\n", ctx->split_size)); + goto range_done; + } + ctx->file_idx = 1; + } + else if (ctx->extract_mode==EXTRACT_SAP) { + ctx->splitrange = GF_TRUE; + } + } + if (end_date) { + if (!reframer_parse_date(end_date, &ctx->cur_end, &ctx->end_frame_idx_plus_one, NULL)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[Reframer] cannot parse end date, assuming open range\n")); + ctx->range_type = RANGE_OPEN; + } + } + + //reset realtime range and issue seek requests + if (ctx->rt || do_seek) { + Double start_range = 0; + if (do_seek) { + start_range = (Double) ctx->cur_start.num; + start_range /= ctx->cur_start.den; + if (start_range > ctx->seeksafe) + start_range -= ctx->seeksafe; + else + start_range = 0; + ctx->has_seen_eos = GF_FALSE; + } + count = gf_list_count(ctx->streams); + for (i=0; istreams, i); + if (ctx->rt) { + st->cts_us_at_init = 0; + st->sys_clock_at_init = 0; + } + if (do_seek) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_STOP, st->ipid); + gf_filter_pid_send_event(st->ipid, &evt); + GF_FEVT_INIT(evt, GF_FEVT_PLAY, st->ipid); + evt.play.start_range = start_range; + evt.play.speed = 1; + gf_filter_pid_send_event(st->ipid, &evt); + } + } + } + return; + +range_done: + //done + ctx->range_type = RANGE_DONE; + count = gf_list_count(ctx->streams); + for (i=0; istreams, i); + gf_filter_pid_set_discard(st->ipid, GF_TRUE); + GF_FEVT_INIT(evt, GF_FEVT_STOP, st->ipid); + gf_filter_pid_send_event(st->ipid, &evt); + gf_filter_pid_set_eos(st->opid); + } + +} + +static void reframer_update_ts_shift(GF_ReframerCtx *ctx, s64 diff_ts, u32 timescale) +{ + u32 i, count = gf_list_count(ctx->streams); + for (i=0; istreams, i); + ts_shift = diff_ts; + ts_shift *= st->timescale; + ts_shift /= timescale; + st->cts_init -= ts_shift; + } +} +void reframer_drop_packet(GF_ReframerCtx *ctx, RTStream *st, GF_FilterPacket *pck, Bool pck_is_ref) +{ + if (pck_is_ref) { + gf_list_rem(st->pck_queue, 0); + gf_filter_pck_unref(pck); + } else { + gf_filter_pid_drop_packet(st->ipid); + } +} + +Bool reframer_send_packet(GF_Filter *filter, GF_ReframerCtx *ctx, RTStream *st, GF_FilterPacket *pck, Bool pck_is_ref) +{ + Bool do_send = GF_FALSE; + + + if (!ctx->rt) { + do_send = GF_TRUE; + } else { + u64 cts_us = gf_filter_pck_get_dts(pck); + if (cts_us==GF_FILTER_NO_TS) + cts_us = gf_filter_pck_get_cts(pck); + + if (cts_us==GF_FILTER_NO_TS) { + do_send = GF_TRUE; + } else { + u64 clock = ctx->clock_val; + cts_us += st->tk_delay; + + cts_us *= 1000000; + cts_us /= st->timescale; + if (ctx->rt==REFRAME_RT_SYNC) { + if (!ctx->clock) ctx->clock = st; + + st = ctx->clock; + } + if (!st->sys_clock_at_init) { + st->cts_us_at_init = cts_us; + st->sys_clock_at_init = clock; + do_send = GF_TRUE; + } else if (cts_us < st->cts_us_at_init) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[Reframer] CTS less than CTS used to initialize clock, not delaying\n")); + do_send = GF_TRUE; + } else { + u64 diff = cts_us - st->cts_us_at_init; + if (ctx->speed>0) diff = (u64) ( diff / ctx->speed); + else if (ctx->speed<0) diff = (u64) ( diff / -ctx->speed); + + clock -= st->sys_clock_at_init; + if (clock + 1000 >= diff) { + do_send = GF_TRUE; + GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[Reframer] Sending packet "LLU" us too late (clock diff "LLU" - CTS diff "LLU")\n", 1000+clock - diff, clock, diff)); + } else { + diff -= clock; + if (!ctx->reschedule_in) + ctx->reschedule_in = diff; + else if (ctx->reschedule_in > diff) + ctx->reschedule_in = diff; + } + } + } + } + + if (!ctx->range_type && ctx->frames.nb_items) { + u32 i; + Bool found=GF_FALSE; + for (i=0; iframes.nb_items; i++) { + if (ctx->frames.vals[i] == st->nb_frames + 1) { + found=GF_TRUE; + break; + } + } + if (!found) { + //drop + gf_filter_pid_drop_packet(st->ipid); + st->nb_frames++; + return GF_TRUE; + } + } + + if (!do_send) + return GF_FALSE; + + //range processing + if (st->ts_at_range_start_plus_one) { + s64 ts; + GF_FilterPacket *new_pck = gf_filter_pck_new_ref(st->opid, NULL, 0, pck); + gf_filter_pck_merge_properties(pck, new_pck); + + //signal chunk start boundary + if (!st->first_pck_sent) { + u32 i, len; + char *file_suf_name = NULL; + char *start = gf_list_get(ctx->xs, ctx->cur_range_idx-1); + char *end = NULL; + if (ctx->range_type==1) end = gf_list_get(ctx->xe, ctx->cur_range_idx-1); + st->first_pck_sent = GF_TRUE; + + if (ctx->extract_mode==EXTRACT_RANGE) { + gf_filter_pck_set_property(new_pck, GF_PROP_PCK_FILENUM, &PROP_UINT(ctx->cur_range_idx) ); + gf_dynstrcat(&file_suf_name, start, NULL); + if (end) + gf_dynstrcat(&file_suf_name, end, "_"); + + len = (u32) strlen(file_suf_name); + //replace : and / characters + for (i=0; icur_start.num * 1000; + start_t /= ctx->cur_start.den; + end_t = ctx->cur_end.num * 1000; + end_t /= ctx->cur_end.den; + + gf_filter_pck_set_property(new_pck, GF_PROP_PCK_FILENUM, &PROP_UINT(ctx->file_idx) ); + sprintf(szFileSuf, LLU"-"LLU, start_t, end_t); + gf_filter_pck_set_property(new_pck, GF_PROP_PCK_FILESUF, &PROP_STRING(szFileSuf) ); + } + } + + //rewrite timestamps + ts = gf_filter_pck_get_cts(pck); + + if (ts != GF_FILTER_NO_TS) { + ts += st->tk_delay; + ts += st->ts_at_range_end; + ts -= st->ts_at_range_start_plus_one - 1; + ts -= st->cts_init; + if (ts<0) { + reframer_update_ts_shift(ctx, -ts, st->timescale); + ts = 0; + } + + gf_filter_pck_set_cts(new_pck, (u64) ts); + if (ctx->raw) { + gf_filter_pck_set_dts(new_pck, ts); + } + } + if (!ctx->raw) { + ts = gf_filter_pck_get_dts(pck); + if (ts != GF_FILTER_NO_TS) { + ts += st->tk_delay; + ts -= st->ts_at_range_start_plus_one - 1; + ts += st->ts_at_range_end; + gf_filter_pck_set_dts(new_pck, (u64) ts); + } + } + if (st->split_start) { + u32 dur = gf_filter_pck_get_duration(pck); + assert(dur>st->split_start); + dur -= st->split_start; + gf_filter_pck_set_duration(new_pck, dur); + st->ts_at_range_start_plus_one += st->split_start; + st->split_start = 0; + } + //last packet and forced duration + if (st->split_end && (gf_list_count(st->pck_queue)==1)) { + gf_filter_pck_set_duration(new_pck, st->split_end); + st->split_end = 0; + } + + gf_filter_pck_send(new_pck); + } else { + gf_filter_pck_forward(pck, st->opid); + } + + reframer_drop_packet(ctx, st, pck, pck_is_ref); + st->nb_frames++; + return GF_TRUE; +} + +static Bool reframer_init_timing(GF_Filter *filter, GF_ReframerCtx *ctx) +{ + u32 i, count = gf_filter_get_ipid_count(filter); + u64 min_dts_plus_one = 0; + u32 pid_scale=0; + for (i=0; iis_playing && !gf_filter_pid_is_eos(ipid)) + return GF_FALSE; + //not playing or eos + continue; + } + ts = gf_filter_pck_get_dts(pck); + ts += st->tk_delay; + + if (!min_dts_plus_one) { + min_dts_plus_one = 1 + ts; + pid_scale = st->timescale; + } else if (ts * pid_scale < (min_dts_plus_one-1) * st->timescale) { + min_dts_plus_one = 1 + ts; + pid_scale = st->timescale; + } + } + for (i=0; itimescale; + + st->cts_init = min_dts_plus_one-1; + if (st->timescale != pid_scale) { + st->cts_init *= st->timescale; + st->cts_init /= pid_scale; + } + } + ctx->timing_initialized = GF_TRUE; + return GF_TRUE; +} + +static u32 reframer_check_pck_range(GF_ReframerCtx *ctx, RTStream *st, u64 ts, u32 dur, u32 frame_idx) +{ + if (ctx->start_frame_idx_plus_one) { + //frame not after our range start + if (frame_idxstart_frame_idx_plus_one) { + return 0; + } else { + //closed range, check + if ((ctx->range_type!=RANGE_OPEN) && (frame_idx>=ctx->end_frame_idx_plus_one)) { + return 2; + } + return 1; + } + } else { + Bool before = GF_FALSE; + Bool after = GF_FALSE; + + //ts not after our range start + if ((s64) (ts * ctx->cur_start.den) < ctx->cur_start.num * st->timescale) { + before = GF_TRUE; + } + //closed range, check + if ((ctx->range_type!=RANGE_OPEN) && ((s64) ((ts+dur) * ctx->cur_end.den) >= ctx->cur_end.num * st->timescale)) { + after = GF_TRUE; + } + if (before) { + if (!after) + return 0; + //long duration samples (typically text) can both start before and end after the target range + else + return 2; + } + if (after) return 2; + return 1; + } + return 0; +} + +void reframer_purge_queues(GF_ReframerCtx *ctx, u64 ts, u32 timescale) +{ + u32 i, count = gf_list_count(ctx->streams); + for (i=0; istreams, i); + u64 ts_rescale = ts; + if (st->resinsert_single_pck) + continue; + + if (st->timescale != timescale) { + ts_rescale *= st->timescale; + ts_rescale /= timescale; + } + while (1) { + GF_FilterPacket *pck = gf_list_get(st->pck_queue, 0); + if (!pck) break; + u64 dts = gf_filter_pck_get_dts(pck); + if (dts==GF_FILTER_NO_TS) + dts = gf_filter_pck_get_cts(pck); + + dts += gf_filter_pck_get_duration(pck); + if (dts >= ts_rescale) break; + gf_list_rem(st->pck_queue, 0); + gf_filter_pck_unref(pck); + st->nb_frames++; + } + } +} + +static void check_gop_split(GF_ReframerCtx *ctx) +{ + u32 i, count = gf_list_count(ctx->streams); + Bool flush_all = GF_FALSE; + + if (!ctx->min_ts_scale) { + u64 min_ts = 0; + u32 min_timescale=0; + u64 min_ts_a = 0; + u32 min_timescale_a=0; + u32 nb_eos = 0; + Bool has_empty_streams = GF_FALSE; + Bool wait_for_sap = GF_FALSE; + for (i=0; istreams, i); + nb_pck = gf_list_count(st->pck_queue); + nb_sap = 0; + if (st->in_eos) { + nb_eos++; + if (!nb_pck) { + has_empty_streams = GF_TRUE; + continue; + } + } + + for (j=0; jpck_queue, j); + if (!ctx->raw && !gf_filter_pck_get_sap(pck) ) { + continue; + } + ts = gf_filter_pck_get_dts(pck); + if (ts==GF_FILTER_NO_TS) + ts = gf_filter_pck_get_cts(pck); + ts += st->tk_delay; + + nb_sap++; + if (nb_sap <= 1 + ctx->gop_depth) { + continue; + } + + last_sap_ts = ts; + break; + } + //in SAP split, flush as soon as we no longer have 2 consecutive saps + if (!last_sap_ts) { + if (st->in_eos && !flush_all && !st->resinsert_single_pck) { + flush_all = GF_TRUE; + } else if (!st->all_saps) { + wait_for_sap = GF_TRUE; + } + } + + if (st->all_saps) { + if (!min_ts_a || (last_sap_ts * min_timescale_a < min_ts_a * st->timescale) ) { + min_ts_a = last_sap_ts; + min_timescale_a = st->timescale; + } + } else { + if (!min_ts || (last_sap_ts * min_timescale < min_ts * st->timescale) ) { + min_ts = last_sap_ts; + min_timescale = st->timescale; + } + } + } + + //in size split, flush as soon as one stream is in eos + if (nb_eos && has_empty_streams) { + flush_all = GF_TRUE; + } + + //if flush, get timestamp + dur of last packet in each stream and use this as final end time + if (flush_all) { + for (i=0; istreams, i); + if (!st->in_eos) + return; + + pck = gf_list_last(st->pck_queue); + if (!pck) continue; + u32 dur = gf_filter_pck_get_duration(pck); + if (!dur) dur=1; + ts = gf_filter_pck_get_dts(pck); + if (ts==GF_FILTER_NO_TS) + ts = gf_filter_pck_get_cts(pck); + ts += st->tk_delay; + ts += dur; + if (!min_ts || (ts * min_timescale > min_ts * st->timescale) ) { + min_ts = ts; + min_timescale = st->timescale; + } + } + } + + if (!min_ts) { + //video not ready, need more input + if (wait_for_sap) + return; + min_ts = min_ts_a; + min_timescale = min_timescale_a; + } + if (!min_ts) { + //other streams not ready, need more input + if (nb_eosmin_ts_scale = min_timescale; + ctx->min_ts_computed = min_ts; + } + } + //check all streams have reached min ts unless we are in final flush + if (!flush_all) { + for (i=0; istreams, i); + if (st->range_start_computed==2) continue; + if (st->resinsert_single_pck) continue; + pck = gf_list_last(st->pck_queue); + assert(pck); + ts = gf_filter_pck_get_dts(pck); + if (ts==GF_FILTER_NO_TS) + ts = gf_filter_pck_get_cts(pck); + ts += st->tk_delay; + + if (ts * ctx->min_ts_scale < ctx->min_ts_computed * st->timescale) { + return; + } + } + } + + //check condition + if (ctx->extract_mode==EXTRACT_SIZE) { + u32 nb_stop_at_min_ts = 0; + u64 cumulated_size = 0; + Bool use_prev = GF_FALSE; + u32 nb_eos = 0; + + //check all streams have reached min ts + for (i=0; istreams, i); + nb_pck = gf_list_count(st->pck_queue); + + for (j=0; jpck_queue, j); + + ts = gf_filter_pck_get_dts(pck); + if (ts==GF_FILTER_NO_TS) + ts = gf_filter_pck_get_cts(pck); + ts += st->tk_delay; + + if (ts * ctx->min_ts_scale >= ctx->min_ts_computed * st->timescale) { + nb_stop_at_min_ts ++; + found = GF_TRUE; + break; + } + gf_filter_pck_get_data(pck, &size); + cumulated_size += size; + } + if ((j==nb_pck) && st->in_eos && !found) { + nb_eos++; + } + } + //not done yet (estimated size less than target split) + if ( + (cumulated_size < ctx->split_size) + && ctx->min_ts_scale + //do this only if first time we estimate this chunk size, or if previous estimated min_ts is not the same as current min_ts + && (!ctx->prev_min_ts_computed || (ctx->prev_min_ts_computed < ctx->min_ts_computed)) + ) { + if ((nb_stop_at_min_ts + nb_eos) == count) { + ctx->est_file_size = cumulated_size; + ctx->prev_min_ts_computed = ctx->min_ts_computed; + ctx->prev_min_ts_scale = ctx->min_ts_scale; + ctx->min_ts_computed = 0; + ctx->min_ts_scale = 0; + ctx->gop_depth++; + } + return; + } + + //decide which split size we use + if (ctx->xround==REFRAME_ROUND_BEFORE) { + use_prev = GF_TRUE; + } else if (ctx->xround==REFRAME_ROUND_AFTER) { + use_prev = GF_FALSE; + } else { + s64 diff_prev = (s64) ctx->split_size; + s64 diff_cur = (s64) ctx->split_size; + diff_prev -= (s64) ctx->est_file_size; + diff_cur -= (s64) cumulated_size; + if (ABS(diff_cur)prev_min_ts_scale) + use_prev = GF_FALSE; + + if (use_prev) { + //ctx->est_file_size = ctx->est_file_size; + ctx->min_ts_computed = ctx->prev_min_ts_computed; + ctx->min_ts_scale = ctx->prev_min_ts_scale; + } else { + ctx->est_file_size = cumulated_size; + } + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[Reframer] split computed using %s estimation of file size ("LLU")\n", use_prev ? "previous" : "current", ctx->est_file_size)); + ctx->prev_min_ts_computed = 0; + ctx->prev_min_ts_scale = 0; + } + + //good to go + ctx->in_range = GF_TRUE; + ctx->gop_depth = 0; + for (i=0; istreams, i); + GF_FilterPacket *pck = gf_list_get(st->pck_queue, 0); + st->range_end_reached_ts = (ctx->min_ts_computed * st->timescale); + if (ctx->min_ts_scale) + st->range_end_reached_ts /= ctx->min_ts_scale; + + st->range_end_reached_ts += 1; + st->first_pck_sent = GF_FALSE; + if (pck) { + ts = gf_filter_pck_get_dts(pck); + if (ts==GF_FILTER_NO_TS) + ts = gf_filter_pck_get_cts(pck); + ts += st->tk_delay; + st->ts_at_range_start_plus_one = ts + 1; + } else { + //this will be a eos signal + st->range_end_reached_ts = 0; + assert(st->range_start_computed==2); + } + } + ctx->cur_end.num = ctx->min_ts_computed; + ctx->cur_end.den = ctx->min_ts_scale; + +} + + +GF_Err reframer_process(GF_Filter *filter) +{ + GF_ReframerCtx *ctx = gf_filter_get_udta(filter); + u32 i, nb_eos, nb_end_of_range, count = gf_filter_get_ipid_count(filter); + + if (ctx->is_eos) { + return GF_EOS; + } + if (ctx->rt) { + ctx->reschedule_in = 0; + ctx->clock_val = gf_sys_clock_high_res(); + } + + /*active range, process as follows: + - if stream is marked as "start reached" or "end reached" do nothing + - queue up packets until we reach start range: + - if packet is in range: + - queue it (ref &nd detach from pid) + - if pck is SAP and first SAP after start and context is not yet marked "in range": + - check if we start from this SAP or from previous SAP (possibly before start) according to cround + - and mark stream as "start ready" + - if stream is video and xadjust is set, prevent all other stream processing + - if packet is out of range + - do NOT enqueue packet + - if stream was not marked as "start ready" (no SAP in active range), use previous SAP before start and mark as active + - mark as end of range reached + - if stream is video and xadjust is set, re-enable all other stream processing + + Once all streams are marked as "start ready" + - compute min time at which we will adjust the start range for all streams + - purge all packets before this time + - mark global context as "in range" + + The regular (non-range) process is then adjusted as follows: + - if context is "in range" get packet from internal queue + - if no more packets in internal queue, mark stream as "range done" + + Once all streams are marked as "range done" + - adjust next_ts of each stream + - mark each stream as not "start ready" and not "range done" + - mark context as not "in range" + - load next range and let the algo loop + */ + if (ctx->range_type && (ctx->range_type!=RANGE_DONE)) { + u32 nb_start_range_reached = 0; + Bool check_split = GF_FALSE; + //init timing + if (!ctx->timing_initialized) { + if (!reframer_init_timing(filter, ctx)) return GF_OK; + } + + //fetch input packets + for (i=0; iis_playing) { + nb_start_range_reached++; + continue; + } + + if (st->range_start_computed && !ctx->wait_video_range_adjust) { + nb_start_range_reached++; + continue; + } + //if eos is marked we are flushing so don't check range_end + if (!ctx->has_seen_eos && st->range_end_reached_ts) continue; + + if (st->split_pck) { + pck = st->split_pck; + drop_input = GF_FALSE; + } else { + pck = gf_filter_pid_get_packet(ipid); + } + if (!pck) { + if (gf_filter_pid_is_eos(ipid)) { + //special case for PIDs with a single packet, we reinsert them at the begining of each extracted range + //this allows dealing with BIFS/OD/JPEG/PNG tracks + if (st->resinsert_single_pck) { + if (!ctx->in_range && !st->range_start_computed) { + st->range_start_computed = 3; + if (!gf_list_count(st->pck_queue)) { + pck = st->resinsert_single_pck; + gf_filter_pck_ref(&pck); + gf_list_add(st->pck_queue, pck); + if (!ctx->is_range_extraction) { + check_split = GF_TRUE; + } + } + } + if (st->range_start_computed) { + nb_start_range_reached++; + } + if (!ctx->is_range_extraction) { + st->in_eos = GF_TRUE; + } + continue; + } + + if (!ctx->is_range_extraction) { + check_split = GF_TRUE; + st->in_eos = GF_TRUE; + } else { + st->range_start_computed = 2; + if (ctx->wait_video_range_adjust && ctx->xadjust && st->needs_adjust) { + ctx->wait_video_range_adjust = GF_FALSE; + } + } + //force flush in case of extract dur to avoid creating file with only a few samples of one track only + if (st->is_playing && (ctx->extract_mode==EXTRACT_DUR)) { + ctx->has_seen_eos = GF_TRUE; + ctx->in_range = 1; + } + } + continue; + } + st->nb_frames_range++; + + ts = gf_filter_pck_get_dts(pck); + if (ts==GF_FILTER_NO_TS) + ts = gf_filter_pck_get_cts(pck); + ts += st->tk_delay; + + //if nosap is set, consider all packet SAPs + is_sap = (ctx->nosap || ctx->raw || gf_filter_pck_get_sap(pck)) ? GF_TRUE : GF_FALSE; + + if (!is_sap) { + if (st->all_saps) { + st->all_saps = GF_FALSE; + ctx->nb_non_saps++; + if (ctx->nb_non_saps>1) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[Reframer] %d streams using predictive coding, results may be undefined or broken when aligning SAP, consider remuxing the source\n", ctx->nb_non_saps)); + } + + if (ctx->xadjust) { + st->needs_adjust = GF_TRUE; + if (st->range_start_computed==1) { + if (ctx->is_range_extraction) { + ctx->wait_video_range_adjust = GF_TRUE; + } + } + } + } + } + + //SAP or size split, push packet in queue and ask for gop split check + if (!ctx->is_range_extraction) { + //add packet + gf_filter_pck_ref(&pck); + gf_filter_pid_drop_packet(st->ipid); + gf_list_add(st->pck_queue, pck); + check_split = GF_TRUE; + //keep ref to first packet until we see a second one + if (st->nb_frames_range==1) { + gf_filter_pck_ref(&pck); + st->resinsert_single_pck = pck; + } else if (st->resinsert_single_pck) { + gf_filter_pck_unref(st->resinsert_single_pck); + st->resinsert_single_pck = NULL; + } + continue; + } + dur = gf_filter_pck_get_duration(pck); + + //dur split or range extraction but we wait for video end range to be adjusted, don't enqueue packet + if (ctx->wait_video_range_adjust && !st->needs_adjust) + continue; + + //check if packet is in our range + pck_in_range = reframer_check_pck_range(ctx, st, ts, dur, st->nb_frames_range); + + + //SAP packet, decide if we cut here or at previous SAP + if (is_sap) { + //if streamtype is video or we have only one pid, purge all packets in all streams before this time + // + //for more complex cases we keep packets because we don't know if we will need SAP packets before the final + //decided start range + if (!pck_in_range && ((count==1) || !st->all_saps) ) { + reframer_purge_queues(ctx, ts, st->timescale); + } + + //packet in range and global context not yet in range, mark which SAP will be the begining of our range + if (!ctx->in_range && (pck_in_range==1)) { + if (ctx->xround==REFRAME_ROUND_CLOSEST) { + Bool cur_closer = GF_FALSE; + //check which frame is closer + if (ctx->start_frame_idx_plus_one) { + s64 diff_prev = ctx->start_frame_idx_plus_one-1; + s64 diff_cur = ctx->start_frame_idx_plus_one-1; + diff_prev -= st->prev_sap_frame_idx; + diff_cur -= st->nb_frames_range; + if (ABS(diff_cur) < ABS(diff_prev)) cur_closer = GF_TRUE; + } else { + s64 diff_prev, diff_cur; + u64 start_range_ts = ctx->cur_start.num; + start_range_ts *= st->timescale; + start_range_ts /= ctx->cur_start.den; + + diff_prev = diff_cur = start_range_ts; + diff_prev -= st->prev_sap_ts; + diff_cur -= ts; + if (ABS(diff_cur) < ABS(diff_prev)) cur_closer = GF_TRUE; + } + if (cur_closer) { + st->sap_ts_plus_one = ts+1; + } else { + st->sap_ts_plus_one = st->prev_sap_ts + 1; + } + } else if (ctx->xround==REFRAME_ROUND_BEFORE) { + st->sap_ts_plus_one = st->prev_sap_ts+1; + } else { + st->sap_ts_plus_one = ts+1; + } + st->range_start_computed = 1; + nb_start_range_reached++; + } + //remember prev sap time + if (pck_in_range!=2) { + st->prev_sap_ts = ts; + st->prev_sap_frame_idx = st->nb_frames_range; + } + //video stream start and xadjust set, prevent all other streams from being processed until wee determine the end of the video range + //and re-enable other streams processing + if (!ctx->wait_video_range_adjust && ctx->xadjust && st->needs_adjust) { + ctx->wait_video_range_adjust = GF_TRUE; + } + } + + if ((ctx->extract_mode==EXTRACT_DUR) && ctx->has_seen_eos && (pck_in_range==2)) + pck_in_range = 1; + + //after range: whether SAP or not, mark end of range reached + if (pck_in_range==2) { + if (!ctx->xadjust || is_sap) { + Bool enqueue = GF_FALSE; + st->split_end = 0; + if (!st->range_start_computed) { + st->sap_ts_plus_one = st->prev_sap_ts + 1; + st->range_start_computed = 1; + nb_start_range_reached++; + if (st->prev_sap_ts == ts) + enqueue = GF_TRUE; + } + //remember the timestamp of first packet after range + st->range_end_reached_ts = ts + 1; + + //time-based extraction or dur split, try to clone packet + if (st->can_split && !ctx->start_frame_idx_plus_one) { + if ((s64) (ts * ctx->cur_end.den) < ctx->cur_end.num * st->timescale) { + //force enqueing this packet + enqueue = GF_TRUE; + st->split_end = (u32) ( (ctx->cur_end.num * st->timescale) / ctx->cur_end.den - ts); + st->range_end_reached_ts += st->split_end; + //and remember it for next chunk - note that we dequeue the input to get proper eos notification + gf_filter_pck_ref(&pck); + st->split_pck = pck; + } + } + //video stream end detected and xadjust set, adjust cur_end to match the video stream end range + //and re-enable other streams processing + if (ctx->wait_video_range_adjust && ctx->xadjust && st->needs_adjust) { + ctx->cur_end.num = st->range_end_reached_ts-1; + ctx->cur_end.den = st->timescale; + ctx->wait_video_range_adjust = GF_FALSE; + } + + //do NOT enqueue packet + if (!enqueue) + break; + } + } + + //add packet + gf_filter_pck_ref(&pck); + gf_list_add(st->pck_queue, pck); + if (drop_input) { + gf_filter_pid_drop_packet(st->ipid); + //keep ref to first packet until we see a second one + if (st->nb_frames_range==1) { + gf_filter_pck_ref(&pck); + st->resinsert_single_pck = pck; + } else if (st->resinsert_single_pck) { + gf_filter_pck_unref(st->resinsert_single_pck); + st->resinsert_single_pck = NULL; + } + } else { + assert(pck == st->split_pck); + gf_filter_pck_unref(st->split_pck); + st->split_pck = NULL; + } + } + + if (check_split) { + check_gop_split(ctx); + } + + //all streams reached the start range, compute min ts + if (!ctx->in_range + && (nb_start_range_reached==count) + && ctx->is_range_extraction + ) { + u64 min_ts = 0; + u32 min_timescale=0; + u64 min_ts_a = 0; + u32 min_timescale_a=0; + u64 min_ts_split = 0; + u32 min_timescale_split=0; + Bool purge_all = GF_FALSE; + for (i=0; iis_playing) continue; + assert(st->range_start_computed); + //eos + if (st->range_start_computed==2) { + continue; + } + //packet will be reinserted at cut time, do not check its timestamp + if (st->range_start_computed==3) + continue; + + if (st->can_split) { + if (!min_ts_split || ((st->sap_ts_plus_one-1) * min_timescale_split < min_ts_split * st->timescale) ) { + min_ts_split = st->sap_ts_plus_one; + min_timescale_split = st->timescale; + } + } + else if (st->all_saps) { + if (!min_ts_a || ((st->sap_ts_plus_one-1) * min_timescale_a < min_ts_a * st->timescale) ) { + min_ts_a = st->sap_ts_plus_one; + min_timescale_a = st->timescale; + } + } else { + if (!min_ts || ((st->sap_ts_plus_one-1) * min_timescale < min_ts * st->timescale) ) { + min_ts = st->sap_ts_plus_one; + min_timescale = st->timescale; + } + } + } + if (!min_ts) { + min_ts = min_ts_a; + min_timescale = min_timescale_a; + if (!min_ts && min_ts_split) { + if (ctx->start_frame_idx_plus_one) { + min_ts = min_ts_split; + min_timescale = min_timescale_split; + } else { + min_ts = ctx->cur_start.num+1; + min_timescale = (u32) ctx->cur_start.den; + } + } + } + if (!min_ts) { + purge_all = GF_TRUE; + if (ctx->extract_mode==EXTRACT_RANGE) { + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[Reframer] All streams in end of stream for desired start range "LLD"/"LLU"\n", ctx->cur_start.num, ctx->cur_start.den)); + } + ctx->is_eos = GF_TRUE; + } else { + min_ts -= 1; + } + //purge everything before min ts + for (i=0; ipck_queue)) { + GF_FilterPacket *pck = gf_list_get(st->pck_queue, 0); + if (!purge_all) { + u32 is_start = 0; + u64 ts, ots; + u64 dur; + ts = gf_filter_pck_get_dts(pck); + if (ts==GF_FILTER_NO_TS) + ts = gf_filter_pck_get_cts(pck); + ts += st->tk_delay; + dur = (u64) gf_filter_pck_get_duration(pck); + if (!dur) dur=1; + ots = ts; + if (min_timescale != st->timescale) { + ts *= min_timescale; + ts /= st->timescale; + dur *= min_timescale; + dur /= st->timescale; + } + + if (ts >= min_ts) { + is_start = 1; + } + else if (st->can_split && (ts+dur >= min_ts)) { + is_start = 2; + } + else if (st->range_start_computed==3) { + is_start = 1; + } + + if (is_start) { + //remember TS at range start + s64 orig = min_ts; + if (st->timescale != min_timescale) { + orig *= st->timescale; + orig /= min_timescale; + } + st->split_start = 0; + if (is_start==2) { + st->split_start = (u32) (min_ts - ts); + if (min_timescale != st->timescale) { + st->split_start *= st->timescale; + st->split_start /= min_timescale; + } + } + st->ts_at_range_start_plus_one = ots + 1; + + if ((st->range_start_computed==1) + && (orig < (s64) ots) + && ctx->splitrange + && (ctx->cur_range_idx>1) + ) { + s32 delay = (s32) ((s64) ots - (s64) orig); + gf_filter_pid_set_property(st->opid, GF_PROP_PID_DELAY, &PROP_SINT(delay) ); + } + break; + } + } + gf_list_rem(st->pck_queue, 0); + gf_filter_pck_unref(pck); + st->nb_frames++; + } + //reset start range computed + st->range_start_computed = 0; + if (ctx->extract_mode==EXTRACT_DUR) { + st->first_pck_sent = GF_FALSE; + } else { + st->first_pck_sent = ctx->splitrange ? GF_FALSE : GF_TRUE; + } + + if (purge_all && (ctx->extract_mode!=EXTRACT_RANGE)) { + gf_filter_pid_get_packet(st->ipid); + gf_filter_pid_set_eos(st->opid); + } + } + if (purge_all) { + if (ctx->extract_mode!=EXTRACT_RANGE) + return GF_EOS; + + goto load_next_range; + } + + //we are in the range + ctx->in_range = GF_TRUE; + } + if (!ctx->in_range) + return GF_OK; + } + + nb_eos = 0; + nb_end_of_range = 0; + for (i=0; irange_type && (ctx->range_type!=RANGE_DONE) ) { + pck = gf_list_get(st->pck_queue, 0); + pck_is_ref = GF_TRUE; + + if (pck && !ctx->is_range_extraction && st->range_end_reached_ts) { + u64 ts; + ts = gf_filter_pck_get_dts(pck); + if (ts==GF_FILTER_NO_TS) + ts = gf_filter_pck_get_cts(pck); + ts += st->tk_delay; + if (ts>= st->range_end_reached_ts-1) { + nb_end_of_range++; + break; + } + } + + } else { + pck = gf_filter_pid_get_packet(ipid); + } + + if (!pck) { + if (st->range_end_reached_ts) { + nb_end_of_range++; + break; + } + + if (!st->is_playing) { + nb_eos++; + } else { + //force a eos check if this was a split pid + if (st->can_split) + gf_filter_pid_get_packet(st->ipid); + + if (gf_filter_pid_is_eos(ipid)) { + gf_filter_pid_set_eos(st->opid); + nb_eos++; + } + } + break; + } + + if (ctx->refs) { + u8 deps = gf_filter_pck_get_dependency_flags(pck); + deps >>= 2; + deps &= 0x3; + //not used as reference, don't forward + if (deps==2) + forward = GF_FALSE; + } + if (ctx->saps.nb_items) { + u32 sap = gf_filter_pck_get_sap(pck); + switch (sap) { + case GF_FILTER_SAP_1: + if (!ctx->filter_sap1) forward = GF_FALSE; + break; + case GF_FILTER_SAP_2: + if (!ctx->filter_sap2) forward = GF_FALSE; + break; + case GF_FILTER_SAP_3: + if (!ctx->filter_sap3) forward = GF_FALSE; + break; + case GF_FILTER_SAP_4: + case GF_FILTER_SAP_4_PROL: + if (!ctx->filter_sap4) forward = GF_FALSE; + break; + default: + if (!ctx->filter_sap_none) forward = GF_FALSE; + break; + } + } + if (ctx->range_type==RANGE_DONE) + forward = GF_FALSE; + + if (!forward) { + reframer_drop_packet(ctx, st, pck, pck_is_ref); + st->nb_frames++; + continue; + } + + if (! reframer_send_packet(filter, ctx, st, pck, pck_is_ref)) + break; + + } + } + + //end of range + if (nb_end_of_range + nb_eos == count) { +load_next_range: + nb_end_of_range = 0; + nb_eos=0; + for (i=0; iresinsert_single_pck) { + u64 start = ctx->cur_start.num; + start *= st->timescale; + start /= ctx->cur_start.den; + st->ts_at_range_end = ctx->cur_end.num; + st->ts_at_range_end *= st->timescale; + st->ts_at_range_end /= ctx->cur_end.den; + st->ts_at_range_end -= start; + } else { + st->ts_at_range_end = (st->range_end_reached_ts - 1) - (st->ts_at_range_start_plus_one - 1); + } + st->ts_at_range_start_plus_one = 0; + st->range_end_reached_ts = 0; + st->range_start_computed = 0; + if (st->in_eos) { + if (gf_list_count(st->pck_queue)) { + nb_end_of_range++; + } else { + gf_filter_pid_set_eos(st->opid); + nb_eos++; + } + } else if (st->split_pck) { + nb_end_of_range++; + } + } + //and load next range + ctx->in_range = GF_FALSE; + reframer_load_range(ctx); + if (nb_end_of_range) + gf_filter_post_process_task(filter); + } + + if (nb_eos==count) return GF_EOS; + + if (ctx->rt) { + if (ctx->reschedule_in>2000) { + gf_filter_ask_rt_reschedule(filter, (u32) (ctx->reschedule_in - 2000)); + } else if (ctx->reschedule_in>1000) { + gf_filter_ask_rt_reschedule(filter, (u32) (ctx->reschedule_in / 2)); + } + } + + return GF_OK; +} + +static const GF_FilterCapability ReframerRAWCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW) +}; + +static GF_Err reframer_initialize(GF_Filter *filter) +{ + GF_ReframerCtx *ctx = gf_filter_get_udta(filter); + + ctx->streams = gf_list_new(); + ctx->seekable = GF_TRUE; + reframer_load_range(ctx); + + if (ctx->raw) { + gf_filter_override_caps(filter, ReframerRAWCaps, sizeof(ReframerRAWCaps) / sizeof(GF_FilterCapability) ); + } + +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + reframer_update_ts_shift(ctx, -1, 1000); + } +#endif + return GF_OK; +} + +static Bool reframer_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_ReframerCtx *ctx = gf_filter_get_udta(filter); + GF_FilterEvent fevt; + RTStream *st; + if (!evt->base.on_pid) return GF_FALSE; + st = gf_filter_pid_get_udta(evt->base.on_pid); + if (!st) return GF_TRUE; + //if we have a PID, we always cancel the event and forward the same event to the associated input pid + fevt = *evt; + fevt.base.on_pid = st->ipid; + + //if range extraction based on time, adjust start range + if (evt->base.type==GF_FEVT_PLAY) { + if (ctx->range_type && !ctx->start_frame_idx_plus_one) { + Double start_range = (Double) ctx->cur_start.num; + start_range /= ctx->cur_start.den; + //rewind safety offset + if (start_range > ctx->seeksafe) + start_range -= ctx->seeksafe; + else + start_range = 0.0; + + fevt.play.start_range = start_range; + } + st->in_eos = GF_FALSE; + st->is_playing = GF_TRUE; + ctx->is_eos = GF_FALSE; + } else if (evt->base.type==GF_FEVT_STOP) { + st->is_playing = GF_FALSE; + } + + gf_filter_pid_send_event(st->ipid, &fevt); + return GF_TRUE; +} + +static void reframer_finalize(GF_Filter *filter) +{ + GF_ReframerCtx *ctx = gf_filter_get_udta(filter); + + while (gf_list_count(ctx->streams)) { + RTStream *st = gf_list_pop_back(ctx->streams); + reframer_reset_stream(ctx, st); + } + gf_list_del(ctx->streams); +} + +static const GF_FilterCapability ReframerCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + //we do accept everything, including raw streams + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + //we don't accept files as input so don't output them + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + //we don't produce RAW streams during dynamic chain resolution - this will avoid loading the filter for compositor/other raw access + CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + //but we may produce raw streams when filter is explicitly loaded (media exporter) + CAP_UINT(GF_CAPS_OUTPUT_LOADED_FILTER, GF_PROP_PID_CODECID, GF_CODECID_RAW) +}; + + +#define OFFS(_n) #_n, offsetof(GF_ReframerCtx, _n) +static const GF_FilterArgs ReframerArgs[] = +{ + { OFFS(exporter), "compatibility with old exporter, displays export results", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(rt), "real-time regulation mode of input\n" + "- off: disables real-time regulation\n" + "- on: enables real-time regulation, one clock per pid\n" + "- sync: enables real-time regulation one clock for all pids", GF_PROP_UINT, "off", "off|on|sync", GF_FS_ARG_HINT_NORMAL}, + { OFFS(saps), "drop non-SAP packets, off by default. The list gives the SAP types (0,1,2,3,4) to forward. Note that forwarding only sap 0 will break the decoding", GF_PROP_UINT_LIST, NULL, "0|1|2|3|4", GF_FS_ARG_HINT_NORMAL}, + { OFFS(refs), "forward only frames used as reference frames, if indicated in the input stream", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_NORMAL}, + { OFFS(speed), "speed for real-time regulation mode - only positive value", GF_PROP_DOUBLE, "1.0", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(raw), "force input streams to be in raw format (i.e. forces decoding of input)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_NORMAL}, + { OFFS(frames), "drop all except listed frames (first being 1), off by default", GF_PROP_UINT_LIST, NULL, NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(xs), "extraction start time(s), see filter help", GF_PROP_STRING_LIST, NULL, NULL, GF_FS_ARG_HINT_NORMAL}, + { OFFS(xe), "extraction end time(s). If less values than start times, the last time interval extracted is an open range", GF_PROP_STRING_LIST, NULL, NULL, GF_FS_ARG_HINT_NORMAL}, + { OFFS(xround), "adjustment of extraction start range I-frame\n" + "- before: use first I-frame preceeding or equal to start range\n" + "- after: use first I-frame (if any) following or equal to start range\n" + "- closest: use I-frame closest to start range", GF_PROP_UINT, "before", "before|after|closest", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(xadjust), "adjust end time of extraction range to be before next I-frame", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(nosap), "do not cut at SAP when extracting range (may result in broken streams)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(splitrange), "signal file boundary at each extraction first packet for template-base file generation", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(seeksafe), "rewind play requests by given seconds (to make sur I-frame preceeding start is catched)", GF_PROP_DOUBLE, "10.0", NULL, GF_FS_ARG_HINT_EXPERT}, + {0} +}; + +GF_FilterRegister ReframerRegister = { + .name = "reframer", + GF_FS_SET_DESCRIPTION("Media Reframer") + GF_FS_SET_HELP("This filter provides various compressed domain tools on inputs:\n" + "- ensure reframing\n" + "- optionally force decoding\n" + "- real-time regulation\n" + "- packet filtering based on SAP types or frame numbers\n" + "- time-range extraction and spliting\n" + "This filter forces input pids to be properly framed (1 packet = 1 Access Unit).\n" + "It is typcially needed to force remultiplexing in file to file operations when source and destination files use the same format.\n" + " \n" + "# SAP filtering\n" + "The filter can remove packets based on their SAP types using [-saps]() option.\n" + "For example, this can be used to extract only the key frame (SAP 1,2,3) of a video to create a trick mode version.\n" + " \n" + "# Frame filtering\n" + "This filter can keep only specific Access Units of the source using [-frames]() option.\n" + "For example, this can be used to extract only specific key frame of a video to create a HEIF collection.\n" + " \n" + "# Frame decoding\n" + "This filter can force input media streams to be decoded using the [-raw]() option.\n" + "EX gpac src=m.mp4 reframer:raw @ [dst]\n" + "# Real-time Regulation\n" + "The filter can perform real-time regulation of input packets, based on their timescale and timestamps.\n" + "For example to simulate a live DASH:\n" + "EX gpac src=m.mp4 reframer:rt=on @ dst=live.mpd:dynamic\n" + " \n" + "# Range extraction\n" + "The filter can perform time range extraction of the source using [-xs]() and [-xe]() options.\n" + "The formats allowed for times specifiers are:\n" + "- 'T'H:M:S: specify time in hours, minutes, seconds\n" + "- 'T'H:M:S.MS: specify time in hours, minutes, seconds and milliseconds\n" + "- INT: specify time in millisecond\n" + "- NUM/DEN: specify time in seconds as fraction\n" + "- FNUM: specify time as frame number\n" + "In this mode, the timestamps are rewritten to form a continuous timeline.\n" + "When multiple ranges are given, the filter will try to seek if supported by source." + "\n" + "EX gpac src=m.mp4 reframer:xs=T00:00:10,T00:01:10,T00:02:00:xs=T00:00:20,T00:01:20 [dst]\n" + "This will extract the time ranges [10s,20s], [1m10s,1m20s] and all media starting from 2m\n" + "\n" + "It is possible to signal range boundaries in output packets using [-splitrange]().\n" + "This will expose on the first packet of each range in each pid the following properties:\n" + "- FileNumber: starting at 1 for the first range, to be used as replacement for $num$ in templates\n" + "- FileSuffix: corresponding to `StartRange_EndRange` or `StartRange` for open ranges, to be used as replacement for $FS$ in templates\n" + "\n" + "EX gpac src=m.mp4 reframer:xs=T00:00:10,T00:01:10:xe=T00:00:20:splitrange -o dump_$FS$.264\n" + "This will create two output files dump_T00.00.10_T00.02.00.264 and dump_T00.01.10.264.\n" + "Note: The `:` and `/` characters are replaced by `.` in `FileSuffix` property.\n" + "\n" + "# Other split actions\n" + "The filter can perform splitting of the source using [-xs]() option.\n" + "The additional formats allowed for [-xs]() option are:\n" + "- 'SAP': split source at each SAP/RAP\n" + "- 'D'VAL: split source by chunks of VAL ms\n" + "- 'D'NUM/DEN: split source by chunks of NUM/DEN seconds\n" + "- 'S'VAL: split source by chunks of estimated size VAL bytes, VAL can use property multipliers\n" + "\n" + "Note: In these modes, [-splitrange]() and [-xadjust]() are implicitly set.\n" + ) + .private_size = sizeof(GF_ReframerCtx), + .max_extra_pids = (u32) -1, + .args = ReframerArgs, + //reframer is explicit only, so we don't load the reframer during resolution process + .flags = GF_FS_REG_EXPLICIT_ONLY, + SETCAPS(ReframerCaps), + .initialize = reframer_initialize, + .finalize = reframer_finalize, + .configure_pid = reframer_configure_pid, + .process = reframer_process, + .process_event = reframer_process_event, +}; + + +const GF_FilterRegister *reframer_register(GF_FilterSession *session) +{ + return &ReframerRegister; +} diff --git a/src/filters/resample_audio.c b/src/filters/resample_audio.c new file mode 100644 index 0000000..86b3866 --- /dev/null +++ b/src/filters/resample_audio.c @@ -0,0 +1,404 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018 + * All rights reserved + * + * This file is part of GPAC / audio resample filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_PLAYER + +typedef struct +{ + //opts + u32 ch, sr, fmt; + + //internal + GF_FilterPid *ipid, *opid; + GF_AudioMixer *mixer; + Bool cfg_forced; + //output config + u32 freq, nb_ch, afmt; + u64 ch_cfg; + u64 out_cts; + //source is planar + Bool src_is_planar; + GF_AudioInterface input_ai; + Bool passthrough; + u32 timescale; + const char *data; + u32 size, bytes_consumed; + Fixed speed; + GF_FilterPacket *in_pck; +} GF_ResampleCtx; + + +static u8 *resample_fetch_frame(void *callback, u32 *size, u32 *planar_stride, u32 audio_delay_ms) +{ + u32 sample_offset; + GF_ResampleCtx *ctx = (GF_ResampleCtx *) callback; + if (!ctx->data) { + *size = 0; + return NULL; + } + assert(ctx->data); + *size = ctx->size - ctx->bytes_consumed; + sample_offset = ctx->bytes_consumed; + //planar mode, bytes consummed correspond to all channels, so move frame pointer + //to first sample non consumed = bytes_consumed/nb_channels + if (ctx->src_is_planar) { + *planar_stride = ctx->size / ctx->nb_ch; + sample_offset /= ctx->nb_ch; + } + return (char*)ctx->data + sample_offset; +} + +static void resample_release_frame(void *callback, u32 nb_bytes) +{ + GF_ResampleCtx *ctx = (GF_ResampleCtx *) callback; + ctx->bytes_consumed += nb_bytes; + assert(ctx->bytes_consumed<=ctx->size); + if (ctx->bytes_consumed==ctx->size) { + //trash packet and get a new one + gf_filter_pid_drop_packet(ctx->ipid); + ctx->data = NULL; + ctx->size = ctx->bytes_consumed = 0; + ctx->in_pck = gf_filter_pid_get_packet(ctx->ipid); + if (!ctx->in_pck) { + return; + } + ctx->out_cts = gf_filter_pck_get_cts(ctx->in_pck); + ctx->data = gf_filter_pck_get_data(ctx->in_pck, &ctx->size); + ctx->bytes_consumed = 0; + } +} + +static Bool resample_get_config(struct _audiointerface *ai, Bool for_reconf) +{ + return GF_TRUE; +} +static Bool resample_is_muted(void *callback) +{ + return GF_FALSE; +} +static Fixed resample_get_speed(void *callback) +{ + GF_ResampleCtx *ctx = (GF_ResampleCtx *) callback; + return ctx->speed; +} +static Bool resample_get_channel_volume(void *callback, Fixed *vol) +{ + u32 i; + for (i=0; imixer = gf_mixer_new(NULL); + if (!ctx->mixer) return GF_OUT_OF_MEM; + + ctx->input_ai.callback = ctx; + ctx->input_ai.FetchFrame = resample_fetch_frame; + ctx->input_ai.ReleaseFrame = resample_release_frame; + ctx->input_ai.GetConfig = resample_get_config; + ctx->input_ai.IsMuted = resample_is_muted; + ctx->input_ai.GetSpeed = resample_get_speed; + ctx->input_ai.GetChannelVolume = resample_get_channel_volume; + ctx->speed = FIX_ONE; + return GF_OK; +} + +static void resample_finalize(GF_Filter *filter) +{ + GF_ResampleCtx *ctx = gf_filter_get_udta(filter); + if (ctx->mixer) gf_mixer_del(ctx->mixer); + if (ctx->in_pck && ctx->ipid) gf_filter_pid_drop_packet(ctx->ipid); +} + + +static GF_Err resample_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + u32 sr, nb_ch, afmt; + u64 ch_cfg; + GF_ResampleCtx *ctx = gf_filter_get_udta(filter); + if (is_remove) { + if (ctx->opid) { + gf_mixer_remove_input(ctx->mixer, &ctx->input_ai); + gf_filter_pid_remove(ctx->opid); + } + if (ctx->in_pck) gf_filter_pid_drop_packet(ctx->ipid); + ctx->in_pck = NULL; + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_max_buffer(ctx->opid, gf_filter_pid_get_max_buffer(pid) ); + } + if (!ctx->ipid) { + ctx->ipid = pid; + gf_mixer_add_input(ctx->mixer, &ctx->input_ai); + } + + sr = ctx->freq; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); + if (p) sr = p->value.uint; + if (!sr) sr = 44100; + + nb_ch = ctx->nb_ch; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS); + if (p) nb_ch = p->value.uint; + if (!nb_ch) nb_ch = 1; + + afmt = ctx->afmt; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT); + if (p) afmt = p->value.uint; + + ch_cfg = ctx->ch_cfg; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CHANNEL_LAYOUT); + if (p) ch_cfg = p->value.longuint; + if (!ch_cfg) ch_cfg = (nb_ch==1) ? GF_AUDIO_CH_FRONT_CENTER : (GF_AUDIO_CH_FRONT_LEFT|GF_AUDIO_CH_FRONT_RIGHT); + + ctx->timescale = sr; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (p) ctx->timescale = p->value.uint; + + //initial config + if (!ctx->freq || !ctx->nb_ch || !ctx->afmt) { + ctx->afmt = ctx->fmt ? ctx->fmt : afmt; + ctx->freq = ctx->sr ? ctx->sr : sr; + ctx->nb_ch = ctx->ch ? ctx->ch : nb_ch; + ctx->ch_cfg = ch_cfg; + + gf_mixer_set_config(ctx->mixer, ctx->freq, ctx->nb_ch, afmt, ctx->ch_cfg); + } + //input reconfig + if ((sr != ctx->input_ai.samplerate) || (nb_ch != ctx->input_ai.chan) + || (afmt != ctx->input_ai.afmt) || (ch_cfg != ctx->input_ai.ch_layout) + || (ctx->src_is_planar != gf_audio_fmt_is_planar(afmt)) + ) { + ctx->input_ai.samplerate = sr; + ctx->input_ai.afmt = afmt; + ctx->input_ai.chan = nb_ch; + ctx->input_ai.ch_layout = ch_cfg; + ctx->src_is_planar = gf_audio_fmt_is_planar(afmt); + } + + ctx->passthrough = GF_FALSE; + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + + if ((ctx->input_ai.samplerate==ctx->freq) && (ctx->input_ai.chan==ctx->nb_ch) && (ctx->input_ai.afmt==ctx->afmt) && (ctx->speed==FIX_ONE)) + ctx->passthrough = GF_TRUE; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(ctx->freq)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(ctx->afmt)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(ctx->nb_ch)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CHANNEL_LAYOUT, &PROP_LONGUINT(ctx->ch_cfg)); + return GF_OK; +} + + +static GF_Err resample_process(GF_Filter *filter) +{ + u8 *output; + u32 osize, written; + GF_FilterPacket *dstpck; + GF_ResampleCtx *ctx = gf_filter_get_udta(filter); + u32 bps, bytes_per_samp; + if (!ctx->ipid) return GF_OK; + + bps = gf_audio_fmt_bit_depth(ctx->afmt); + bytes_per_samp = ctx->nb_ch * bps / 8; + + while (1) { + if (!ctx->in_pck) { + ctx->in_pck = gf_filter_pid_get_packet(ctx->ipid); + + if (!ctx->in_pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (ctx->opid) + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + ctx->data = gf_filter_pck_get_data(ctx->in_pck, &ctx->size); + ctx->out_cts = gf_filter_pck_get_cts(ctx->in_pck); + } + + if (ctx->passthrough) { + gf_filter_pck_forward(ctx->in_pck, ctx->opid); + gf_filter_pid_drop_packet(ctx->ipid); + ctx->in_pck = NULL; + continue; + } + + osize = ctx->size * ctx->nb_ch * bps; + osize /= ctx->input_ai.chan * gf_audio_fmt_bit_depth(ctx->input_ai.afmt); + + dstpck = gf_filter_pck_new_alloc(ctx->opid, osize, &output); + if (!dstpck) return GF_OK; + gf_filter_pck_merge_properties(ctx->in_pck, dstpck); + + written = gf_mixer_get_output(ctx->mixer, output, osize, 0); + if (written != osize) { + gf_filter_pck_truncate(dstpck, written); + } + gf_filter_pck_set_dts(dstpck, ctx->out_cts); + gf_filter_pck_set_cts(dstpck, ctx->out_cts); + gf_filter_pck_send(dstpck); + + if (ctx->timescale==ctx->freq) { + ctx->out_cts += written / bytes_per_samp; + } else { + u64 ts_inc = written / bytes_per_samp; + ts_inc *= ctx->timescale; + ts_inc /= ctx->freq; + + ctx->out_cts += ts_inc; + + } + //still some bytes to use from packet, do not discard + if (ctx->bytes_consumedsize) { + continue; + } + //done with this packet + if (ctx->in_pck) { + ctx->in_pck = NULL; + gf_filter_pid_drop_packet(ctx->ipid); + } + } + return GF_OK; +} + +static GF_Err resample_reconfigure_output(GF_Filter *filter, GF_FilterPid *pid) +{ + u32 sr, nb_ch, afmt; + u64 ch_cfg; + const GF_PropertyValue *p; + GF_ResampleCtx *ctx = gf_filter_get_udta(filter); + if (ctx->opid != pid) return GF_BAD_PARAM; + + sr = ctx->freq; + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_SAMPLE_RATE); + if (p) sr = p->value.uint; + + nb_ch = ctx->nb_ch; + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_NUM_CHANNELS); + if (p) nb_ch = p->value.uint; + + afmt = ctx->afmt; + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_AUDIO_FORMAT); + if (p) afmt = p->value.uint; + + ch_cfg = ctx->ch_cfg; + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_CHANNEL_LAYOUT); + if (p) ch_cfg = p->value.longuint; + + p = gf_filter_pid_caps_query(pid, GF_PROP_PID_AUDIO_SPEED); + if (p) { + ctx->speed = FLT2FIX((Float) p->value.number); + if (ctx->speed<0) ctx->speed = -ctx->speed; + } else { + ctx->speed = FIX_ONE; + } + + if ((sr==ctx->freq) && (nb_ch==ctx->nb_ch) && (afmt==ctx->afmt) && (ch_cfg==ctx->ch_cfg) && (ctx->speed == FIX_ONE) ) return GF_OK; + + ctx->afmt = afmt; + ctx->freq = sr; + ctx->nb_ch = nb_ch; + ctx->ch_cfg = ch_cfg; + + gf_mixer_set_config(ctx->mixer, ctx->freq, ctx->nb_ch, ctx->afmt, ctx->ch_cfg); + ctx->passthrough = GF_FALSE; + + if ((ctx->input_ai.samplerate==ctx->freq) && (ctx->input_ai.chan==ctx->nb_ch) && (ctx->input_ai.afmt==afmt) && (ctx->speed == FIX_ONE)) + ctx->passthrough = GF_TRUE; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, &PROP_UINT(sr)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(afmt)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(nb_ch)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CHANNEL_LAYOUT, &PROP_LONGUINT(ch_cfg)); + + if (ctx->speed > FIX_ONE) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_BUFFER_REQ, ctx->ipid); + evt.buffer_req.max_buffer_us = FIX2INT( ctx->speed * 100000 ); + gf_filter_pid_send_event(ctx->ipid, &evt); + } + + return GF_OK; +} + +static const GF_FilterCapability ResamplerCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +#define OFFS(_n) #_n, offsetof(GF_ResampleCtx, _n) +static const GF_FilterArgs ResamplerArgs[] = +{ + { OFFS(ch), "desired number of output audio channels - 0 for auto", GF_PROP_UINT, "0", NULL, 0}, + { OFFS(sr), "desired sample rate of output audio - 0 for auto", GF_PROP_UINT, "0", NULL, 0}, + { OFFS(fmt), "desired format of output audio - none for auto", GF_PROP_PCMFMT, "none", NULL, 0}, + {0} +}; + +GF_FilterRegister ResamplerRegister = { + .name = "resample", + GF_FS_SET_DESCRIPTION("Audio resampler") + GF_FS_SET_HELP("This filter resamples raw audio to a target sample rate, number of channels or audio format.") + .private_size = sizeof(GF_ResampleCtx), + .initialize = resample_initialize, + .finalize = resample_finalize, + .args = ResamplerArgs, + SETCAPS(ResamplerCaps), + .configure_pid = resample_configure_pid, + .process = resample_process, + .reconfigure_output = resample_reconfigure_output, +}; + + +const GF_FilterRegister *resample_register(GF_FilterSession *session) +{ + return &ResamplerRegister; +} +#else + +const GF_FilterRegister *resample_register(GF_FilterSession *session) +{ + return NULL; +} + +#endif // GPAC_DISABLE_PLAYER diff --git a/src/filters/rewind.c b/src/filters/rewind.c new file mode 100644 index 0000000..2bbe6bf --- /dev/null +++ b/src/filters/rewind.c @@ -0,0 +1,262 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018 + * All rights reserved + * + * This file is part of GPAC / media rewinder filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +typedef struct +{ + //opts + u32 rbuffer; + + //internal + GF_FilterPid *ipid, *opid; + u32 type; + + Bool passthrough; + + GF_List *frames; + + u32 nb_ch; + u32 bytes_per_sample; + Bool is_planar; + + Bool wait_for_next_sap; + +} GF_RewindCtx; + + +static GF_Err rewind_initialize(GF_Filter *filter) +{ + GF_RewindCtx *ctx = gf_filter_get_udta(filter); + ctx->frames = gf_list_new(); + return GF_OK; +} + +static void rewind_finalize(GF_Filter *filter) +{ + GF_RewindCtx *ctx = gf_filter_get_udta(filter); + gf_list_del(ctx->frames); +} + + +static GF_Err rewind_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + u32 afmt; + GF_RewindCtx *ctx = gf_filter_get_udta(filter); + if (is_remove) { + if (ctx->opid) { + gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + if (!p) return GF_NOT_SUPPORTED; + ctx->type = p->value.uint; + + if (ctx->type==GF_STREAM_AUDIO) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS); + if (p) ctx->nb_ch = p->value.uint; + if (!ctx->nb_ch) ctx->nb_ch = 1; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT); + if (!p) return GF_NOT_SUPPORTED; + + afmt = p->value.uint; + ctx->bytes_per_sample = gf_audio_fmt_bit_depth(afmt) * ctx->nb_ch / 8; + ctx->is_planar = gf_audio_fmt_is_planar(afmt); + } + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_max_buffer(ctx->opid, gf_filter_pid_get_max_buffer(pid) ); + } + if (!ctx->ipid) { + ctx->ipid = pid; + } + gf_filter_pid_copy_properties(ctx->opid, ctx->ipid); + return GF_OK; +} + +static GF_Err rewind_process_video(GF_RewindCtx *ctx, GF_FilterPacket *pck) +{ + Bool do_flush = GF_FALSE; + if (pck) { + //keep a ref on this packet + gf_filter_pck_ref(&pck); + //and drop input + gf_filter_pid_drop_packet(ctx->ipid); + if (gf_filter_pck_get_sap(pck)) { + do_flush = GF_TRUE; + ctx->wait_for_next_sap = GF_FALSE; + } + else if (gf_list_count(ctx->frames)>ctx->rbuffer) { + do_flush = GF_TRUE; + ctx->wait_for_next_sap = GF_TRUE; + GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[Rewind] Too many frames in GOP, %d vs %d max allowed, flushing until next SAP\n", gf_list_count(ctx->frames), ctx->rbuffer)); + } + } else { + do_flush = GF_TRUE; + } + //frame was a SAP, flush all previous frames in reverse order + if (do_flush) { + while (1) { + GF_FilterPacket *frame = gf_list_pop_back(ctx->frames); + if (!frame) break; + gf_filter_pck_forward(frame, ctx->opid); + gf_filter_pck_unref(frame); + } + } + if (pck) { + //rewind buffer exceeded + if (ctx->wait_for_next_sap) { + gf_filter_pck_forward(pck, ctx->opid); + gf_filter_pck_unref(pck); + } else { + gf_list_add(ctx->frames, pck); + } + } + return GF_OK; +} + +static GF_Err rewind_process(GF_Filter *filter) +{ + u8 *output; + const u8 *data; + u32 size; + GF_FilterPacket *pck, *dstpck; + GF_RewindCtx *ctx = gf_filter_get_udta(filter); + + if (!ctx->ipid) return GF_OK; + + pck = gf_filter_pid_get_packet(ctx->ipid); + + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (!ctx->passthrough && (ctx->type == GF_STREAM_VISUAL)) { + return rewind_process_video(ctx, NULL); + } + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + if (ctx->passthrough) { + gf_filter_pck_forward(pck, ctx->opid); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + + if (ctx->type == GF_STREAM_VISUAL) { + return rewind_process_video(ctx, pck); + } + data = gf_filter_pck_get_data(pck, &size); + + dstpck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + if (!dstpck) return GF_OK; + gf_filter_pck_merge_properties(pck, dstpck); + + if (ctx->is_planar) { + u32 i, j, nb_samples, planesize, bytes_per_samp; + nb_samples = size / ctx->bytes_per_sample; + planesize = nb_samples * ctx->bytes_per_sample / ctx->nb_ch; + + bytes_per_samp = ctx->bytes_per_sample / ctx->nb_ch; + for (j=0; jnb_ch; j++) { + char *dst = output + j * planesize; + char *src = (char *) data + j * planesize; + + for (i=0; ibytes_per_sample; + + for (i=0; ibytes_per_sample, data + (nb_samples - i - 1)*ctx->bytes_per_sample, ctx->bytes_per_sample); + } + } + gf_filter_pck_send(dstpck); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + +static Bool rewind_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ + GF_RewindCtx *ctx = gf_filter_get_udta(filter); + if (evt->base.type==GF_FEVT_PLAY) { + if (evt->play.speed>0) ctx->passthrough = GF_TRUE; + else ctx->passthrough = GF_FALSE; + } + return GF_FALSE; +} + +static const GF_FilterCapability RewinderCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW), + {0}, + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +#define OFFS(_n) #_n, offsetof(GF_RewindCtx, _n) +static const GF_FilterArgs RewinderArgs[] = +{ + { OFFS(rbuffer), "size of video rewind buffer in frames. If more frames than this, flush is performed", GF_PROP_UINT, "100", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + +GF_FilterRegister RewinderRegister = { + .name = "rewind", + GF_FS_SET_DESCRIPTION("Audio/Video rewinder") + GF_FS_SET_HELP("This filter reverses audio and video frames in negative playback speed.\nThe filter is in passthrough if speed is positive. Otherwise, it reverts decoded GOPs for video, or revert samples in decoded frame for audio (not really nice for most codecs).") + .private_size = sizeof(GF_RewindCtx), + //rewind shall be explicitly loaded + .flags = GF_FS_REG_EXPLICIT_ONLY, + .initialize = rewind_initialize, + .finalize = rewind_finalize, + .args = RewinderArgs, + SETCAPS(RewinderCaps), + .configure_pid = rewind_configure_pid, + .process = rewind_process, + .process_event = rewind_process_event +}; + + +const GF_FilterRegister *rewind_register(GF_FilterSession *session) +{ + return &RewinderRegister; +} diff --git a/src/filters/rewrite_adts.c b/src/filters/rewrite_adts.c new file mode 100644 index 0000000..b4a08b3 --- /dev/null +++ b/src/filters/rewrite_adts.c @@ -0,0 +1,396 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2020 + * All rights reserved + * + * This file is part of GPAC / AAC ADTS write filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +#include + + +typedef struct +{ + //opts + Bool exporter, mpeg2; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + u32 codecid, channels, sr_idx, aac_type; + + Bool is_latm; + + GF_BitStream *bs_w; + +#ifndef GPAC_DISABLE_AV_PARSERS + GF_M4ADecSpecInfo acfg; +#else + u8 *dsi; + u32 dsi_size; +#endif + u32 dsi_crc; + Bool update_dsi; + GF_Fraction fdsi; + u64 last_cts; + u32 timescale; +} GF_ADTSMxCtx; + + + + +GF_Err adtsmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 i, sr; + Bool patch_channels = GF_FALSE; + const GF_PropertyValue *p; +#ifndef GPAC_DISABLE_AV_PARSERS + GF_M4ADecSpecInfo acfg; +#endif + GF_ADTSMxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p) return GF_NOT_SUPPORTED; + ctx->codecid = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); + if (!p) return GF_NOT_SUPPORTED; + sr = p->value.uint; + + ctx->channels = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS); + if (p) + ctx->channels = p->value.uint; + +#ifndef GPAC_DISABLE_AV_PARSERS + memset(&acfg, 0, sizeof(GF_M4ADecSpecInfo)); +#endif + + ctx->aac_type = 0; + if (ctx->is_latm) { + u32 crc; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (!p) return GF_NOT_SUPPORTED; + + crc = gf_crc_32(p->value.data.ptr, p->value.data.size); + if (crc != ctx->dsi_crc) { + ctx->dsi_crc = crc; +#ifndef GPAC_DISABLE_AV_PARSERS + gf_m4a_get_config(p->value.data.ptr, p->value.data.size, &ctx->acfg); +#endif + ctx->update_dsi = GF_TRUE; + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); + if (!p) return GF_NOT_SUPPORTED; + ctx->timescale = p->value.uint; + + } else if (ctx->codecid==GF_CODECID_AAC_MPEG4) { + if (!ctx->mpeg2) { +#ifndef GPAC_DISABLE_AV_PARSERS + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (p) { + gf_m4a_get_config(p->value.data.ptr, p->value.data.size, &acfg); + ctx->aac_type = acfg.base_object_type - 1; + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("RFADTS] no AAC decoder config, assuming AAC-LC\n")); + ctx->aac_type = GF_M4A_AAC_LC; + } +#endif + } + } else { + ctx->aac_type = ctx->codecid - GF_CODECID_AAC_MPEG2_MP; + } + +#ifndef GPAC_DISABLE_AV_PARSERS + if (ctx->channels && acfg.nb_chan && (ctx->channels != acfg.nb_chan)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[RFADTS] Mismatch betwwen container number of channels (%d) and AAC config (%d), using AAC config\n", ctx->channels, acfg.nb_chan)); + ctx->channels = acfg.nb_chan; + patch_channels = GF_TRUE; + } + if ((acfg.base_object_type==2) && (acfg.base_sr!=sr)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[RFADTS] Mismatch betwwen container samplerate (%d) and AAC config SBR base samplerate (%d), using AAC config\n", sr, acfg.base_sr)); + sr = acfg.base_sr; + } +#else + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (!p) return GF_NOT_SUPPORTED; + ctx->dsi = p->value.data.ptr; + ctx->dsi_size = p->value.data.size; +#endif + + if (ctx->channels>7) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("RFADTS] Invalid channel config %d for ADTS, forcing 7\n", ctx->channels)); + ctx->channels = 7; + } else if (!ctx->channels) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("RFADTS] no channel config found for ADTS, forcing stereo\n")); + } + + for (i=0; i<16; i++) { + if (GF_M4ASampleRates[i] == (u32) sr) { + ctx->sr_idx = i; + break; + } + } + + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + ctx->ipid = pid; + gf_filter_pid_copy_properties(ctx->opid, pid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, &PROP_BOOL(GF_TRUE) ); + if (ctx->is_latm) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED_LATM, &PROP_BOOL(GF_TRUE) ); + + if (patch_channels) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, &PROP_UINT(ctx->channels)); + gf_filter_pid_set_framing_mode(ctx->ipid, GF_TRUE); + return GF_OK; +} + + + +GF_Err adtsmx_process(GF_Filter *filter) +{ + GF_ADTSMxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + u8 *data, *output; + u32 pck_size, size; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + + if (ctx->is_latm) { + u32 asize; + + size = pck_size+20; + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + + if (!ctx->bs_w) ctx->bs_w = gf_bs_new(output, size, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs_w, output, size); + + gf_bs_write_int(ctx->bs_w, 0x2B7, 11); + gf_bs_write_int(ctx->bs_w, 0, 13); + + if (ctx->fdsi.num && ctx->fdsi.den) { + u64 ts = gf_filter_pck_get_cts(pck); + if (ts != GF_FILTER_NO_TS) { + if ((ts - ctx->last_cts) * ctx->fdsi.den >= ctx->timescale * ctx->fdsi.num) { + ctx->last_cts = ts; + ctx->update_dsi = GF_TRUE; + } + } + } + + /*same mux config = 0 (refresh aac config)*/ + if (ctx->update_dsi) { + gf_bs_write_int(ctx->bs_w, 0, 1); + /*mux config */ + gf_bs_write_int(ctx->bs_w, 0, 1);/*audio mux version = 0*/ + gf_bs_write_int(ctx->bs_w, 1, 1);/*allStreamsSameTimeFraming*/ + gf_bs_write_int(ctx->bs_w, 0, 6);/*numSubFrames*/ + gf_bs_write_int(ctx->bs_w, 0, 4);/*numProgram*/ + gf_bs_write_int(ctx->bs_w, 0, 3);/*numLayer prog 1*/ + +#ifndef GPAC_DISABLE_AV_PARSERS + gf_m4a_write_config_bs(ctx->bs_w, &ctx->acfg); +#else + gf_bs_write_data(ctx->bs_w, ctx->dsi, ctx->dsi_size); +#endif + gf_bs_write_int(ctx->bs_w, 0, 3);/*frameLengthType*/ + gf_bs_write_int(ctx->bs_w, 0, 8);/*latmBufferFullness*/ + gf_bs_write_int(ctx->bs_w, 0, 1);/*other data present*/ + gf_bs_write_int(ctx->bs_w, 0, 1);/*crcCheckPresent*/ + + ctx->update_dsi = 0; + } else { + gf_bs_write_int(ctx->bs_w, 1, 1); + } + /*write payloadLengthInfo*/ + asize = pck_size; + while (1) { + if (asize>=255) { + gf_bs_write_int(ctx->bs_w, 255, 8); + asize -= 255; + } else { + gf_bs_write_int(ctx->bs_w, asize, 8); + break; + } + } + gf_bs_write_data(ctx->bs_w, data, pck_size); + gf_bs_align(ctx->bs_w); + + size = (u32) gf_bs_get_position(ctx->bs_w); + gf_filter_pck_truncate(dst_pck, size); + + /*rewrite LATM frame header*/ + output[1] |= ((size-3) >> 8 ) & 0x1F; + output[2] = (size-3) & 0xFF; + + } else { + size = pck_size+7; + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + + if (!ctx->bs_w) ctx->bs_w = gf_bs_new(output, size, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs_w, output, size); + + gf_bs_write_int(ctx->bs_w, 0xFFF, 12);/*sync*/ + gf_bs_write_int(ctx->bs_w, (ctx->mpeg2==1) ? 1 : 0, 1);/*mpeg2 aac*/ + gf_bs_write_int(ctx->bs_w, 0, 2); /*layer*/ + gf_bs_write_int(ctx->bs_w, 1, 1); /* protection_absent*/ + gf_bs_write_int(ctx->bs_w, ctx->aac_type, 2); + gf_bs_write_int(ctx->bs_w, ctx->sr_idx, 4); + gf_bs_write_int(ctx->bs_w, 0, 1); + gf_bs_write_int(ctx->bs_w, ctx->channels, 3); + gf_bs_write_int(ctx->bs_w, 0, 4); + gf_bs_write_int(ctx->bs_w, 7+pck_size, 13); + gf_bs_write_int(ctx->bs_w, 0x7FF, 11); + gf_bs_write_int(ctx->bs_w, 0, 2); + memcpy(output+7, data, pck_size); + } + + gf_filter_pck_merge_properties(pck, dst_pck); + gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO); + + + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + + gf_filter_pck_send(dst_pck); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + +static void adtsmx_finalize(GF_Filter *filter) +{ + GF_ADTSMxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs_w) gf_bs_del(ctx->bs_w); +} + +static const GF_FilterCapability ADTSMxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG4), + CAP_UINT(GF_CAPS_INPUT_OUTPUT_OPT, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_MP), + CAP_UINT(GF_CAPS_INPUT_OUTPUT_OPT, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_LCP), + CAP_UINT(GF_CAPS_INPUT_OUTPUT_OPT, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_SSRP), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_BOOL(GF_CAPS_OUTPUT, GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + + +#define OFFS(_n) #_n, offsetof(GF_ADTSMxCtx, _n) +static const GF_FilterArgs ADTSMxArgs[] = +{ + { OFFS(mpeg2), "signal as MPEG2 AAC\n" + "- auto: selects based on AAC profile\n" + "- no: always signals as MPEG-4 AAC\n" + "- yes: always signals as MPEG-2 AAC" + "", GF_PROP_UINT, "auto", "auto|no|yes", GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + + +GF_FilterRegister ADTSMxRegister = { + .name = "ufadts", + GF_FS_SET_DESCRIPTION("ADTS writer") + GF_FS_SET_HELP("This filter converts AAC streams into ADTS encapsulated data.") + .private_size = sizeof(GF_ADTSMxCtx), + .args = ADTSMxArgs, + .finalize = adtsmx_finalize, + SETCAPS(ADTSMxCaps), + .configure_pid = adtsmx_configure_pid, + .process = adtsmx_process +}; + + +const GF_FilterRegister *adtsmx_register(GF_FilterSession *session) +{ + return &ADTSMxRegister; +} + +static GF_Err latmmx_initialize(GF_Filter*filter) +{ + GF_ADTSMxCtx *ctx = gf_filter_get_udta(filter); + ctx->is_latm = GF_TRUE; + return GF_OK; +} + +#define OFFS(_n) #_n, offsetof(GF_ADTSMxCtx, _n) +static const GF_FilterArgs LATMMxArgs[] = +{ + { OFFS(fdsi), "set delay between two LATM Audio Config", GF_PROP_FRACTION, "0", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + + +static const GF_FilterCapability LATMMxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG4), + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_MP), + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_LCP), + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_SSRP), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_BOOL(GF_CAPS_OUTPUT, GF_PROP_PID_UNFRAMED_LATM, GF_TRUE), +}; + +GF_FilterRegister LATMMxRegister = { + .name = "uflatm", + GF_FS_SET_DESCRIPTION("Raw AAC to LATM writer") + GF_FS_SET_HELP("This filter converts AAC streams into LATM encapsulated data.") + .private_size = sizeof(GF_ADTSMxCtx), + .args = LATMMxArgs, + .initialize = latmmx_initialize, + .finalize = adtsmx_finalize, + SETCAPS(LATMMxCaps), + .configure_pid = adtsmx_configure_pid, + .process = adtsmx_process +}; + + +const GF_FilterRegister *latm_mx_register(GF_FilterSession *session) +{ + return &LATMMxRegister; +} diff --git a/src/filters/rewrite_mp4v.c b/src/filters/rewrite_mp4v.c new file mode 100644 index 0000000..99a8e7a --- /dev/null +++ b/src/filters/rewrite_mp4v.c @@ -0,0 +1,169 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018 + * All rights reserved + * + * This file is part of GPAC / MPEG-4 part2 video rewrite filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +typedef struct +{ + //opts + Bool rcfg; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + u32 crc; + char *dsi; + u32 dsi_size; +} GF_M4VMxCtx; + +GF_Err m4vmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 crc; + const GF_PropertyValue *dcd; + GF_M4VMxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + dcd = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (!dcd) return GF_NON_COMPLIANT_BITSTREAM; + + crc = gf_crc_32(dcd->value.data.ptr, dcd->value.data.size); + if (ctx->crc == crc) return GF_OK; + ctx->crc = crc; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, pid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, &PROP_BOOL(GF_TRUE) ); + + ctx->ipid = pid; + + ctx->dsi = dcd->value.data.ptr; + ctx->dsi_size = dcd->value.data.size; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL); + gf_filter_pid_set_framing_mode(ctx->ipid, GF_TRUE); + return GF_OK; +} + + +GF_Err m4vmx_process(GF_Filter *filter) +{ + GF_M4VMxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + u8 *data, *output; + u32 pck_size, size, sap_type; + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + + sap_type = gf_filter_pck_get_sap(pck); + if (!sap_type) { + u8 flags = gf_filter_pck_get_dependency_flags(pck); + //get dependsOn + if (flags) { + flags>>=4; + flags &= 0x3; + if (flags==2) sap_type = 3; //could be 1, 2 or 3 + } + } + + if (sap_type && ctx->dsi) { + GF_FilterPacket *dst_pck; + size = pck_size + ctx->dsi_size; + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + memcpy(output, ctx->dsi, ctx->dsi_size); + memcpy(output+ctx->dsi_size, data, pck_size); + gf_filter_pck_merge_properties(pck, dst_pck); + gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO); + gf_filter_pck_send(dst_pck); + + if (!ctx->rcfg) { + ctx->dsi = NULL; + ctx->dsi_size = 0; + } + + } else { + gf_filter_pck_forward(pck, ctx->opid); + } + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + +static const GF_FilterCapability M4VMxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG4_PART2), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_BOOL(GF_CAPS_OUTPUT, GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + + + +#define OFFS(_n) #_n, offsetof(GF_M4VMxCtx, _n) +static const GF_FilterArgs M4VMxArgs[] = +{ + { OFFS(rcfg), "force repeating decoder config at each I-frame", GF_PROP_BOOL, "true", NULL, 0}, + {0} +}; + + +GF_FilterRegister M4VMxRegister = { + .name = "ufm4v", + GF_FS_SET_DESCRIPTION("M4V writer") + GF_FS_SET_HELP("This filter converts MPEG-4 part 2 visual streams into dumpable format (reinsert decoder config).") + .private_size = sizeof(GF_M4VMxCtx), + .args = M4VMxArgs, + SETCAPS(M4VMxCaps), + .configure_pid = m4vmx_configure_pid, + .process = m4vmx_process +}; + + +const GF_FilterRegister *m4vmx_register(GF_FilterSession *session) +{ + return &M4VMxRegister; +} diff --git a/src/filters/rewrite_nalu.c b/src/filters/rewrite_nalu.c new file mode 100644 index 0000000..8f3bf8c --- /dev/null +++ b/src/filters/rewrite_nalu.c @@ -0,0 +1,495 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017 + * All rights reserved + * + * This file is part of GPAC / NALU video AnnexB write filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +typedef struct +{ + //opts + Bool rcfg, delim; + u32 extract; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + Bool is_hevc; + + u32 nal_hdr_size, crc, crc_enh; + u8 *dsi; + u32 dsi_size; + + GF_BitStream *bs_w, *bs_r; + u32 nb_nalu, nb_nalu_in_hdr; + u32 width, height; +} GF_NALUMxCtx; + + + + +static void nalumx_write_ps_list(GF_NALUMxCtx *ctx, GF_BitStream *bs, GF_List *list) +{ + u32 i, count = list ? gf_list_count(list) : 0; + for (i=0; idata, sl->size); + ctx->nb_nalu_in_hdr++; + } +} + +static GF_List *nalumx_get_hevc_ps(GF_HEVCConfig *cfg, u8 type) +{ + u32 i, count = gf_list_count(cfg->param_array); + for (i=0; iparam_array, i); + if (pa->type == type) return pa->nalus; + } + return NULL; +} + +static GF_Err nalumx_make_inband_header(GF_NALUMxCtx *ctx, char *dsi, u32 dsi_len, char *dsi_enh, u32 dsi_enh_len) +{ + GF_BitStream *bs; + GF_AVCConfig *avcc = NULL; + GF_AVCConfig *svcc = NULL; + GF_HEVCConfig *hvcc = NULL; + GF_HEVCConfig *lvcc = NULL; + + if (ctx->is_hevc) { + if (dsi) hvcc = gf_odf_hevc_cfg_read(dsi, dsi_len, GF_FALSE); + if (dsi_enh) lvcc = gf_odf_hevc_cfg_read(dsi_enh, dsi_enh_len, GF_TRUE); + if (!hvcc && !lvcc) return GF_NON_COMPLIANT_BITSTREAM; + ctx->nal_hdr_size = hvcc ? hvcc->nal_unit_size : lvcc->nal_unit_size; + } else { + if (dsi) avcc = gf_odf_avc_cfg_read(dsi, dsi_len); + if (dsi_enh) svcc = gf_odf_avc_cfg_read(dsi_enh, dsi_enh_len); + if (!avcc && !svcc) return GF_NON_COMPLIANT_BITSTREAM; + ctx->nal_hdr_size = avcc ? avcc->nal_unit_size : svcc->nal_unit_size; + } + bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + if (avcc || svcc) { + if (avcc) { + nalumx_write_ps_list(ctx, bs, avcc->sequenceParameterSets); + } + + if (svcc) + nalumx_write_ps_list(ctx, bs, svcc->sequenceParameterSets); + + if (avcc && avcc->sequenceParameterSetExtensions) + nalumx_write_ps_list(ctx, bs, avcc->sequenceParameterSetExtensions); + + if (svcc && svcc->sequenceParameterSetExtensions) + nalumx_write_ps_list(ctx, bs, svcc->sequenceParameterSetExtensions); + + if (avcc) + nalumx_write_ps_list(ctx, bs, avcc->pictureParameterSets); + + if (svcc) + nalumx_write_ps_list(ctx, bs, svcc->pictureParameterSets); + } + if (hvcc || lvcc) { + if (hvcc) + nalumx_write_ps_list(ctx, bs, nalumx_get_hevc_ps(hvcc, GF_HEVC_NALU_VID_PARAM)); + if (lvcc) + nalumx_write_ps_list(ctx, bs, nalumx_get_hevc_ps(lvcc, GF_HEVC_NALU_VID_PARAM)); + if (hvcc) + nalumx_write_ps_list(ctx, bs, nalumx_get_hevc_ps(hvcc, GF_HEVC_NALU_SEQ_PARAM)); + if (lvcc) + nalumx_write_ps_list(ctx, bs, nalumx_get_hevc_ps(lvcc, GF_HEVC_NALU_SEQ_PARAM)); + if (hvcc) + nalumx_write_ps_list(ctx, bs, nalumx_get_hevc_ps(hvcc, GF_HEVC_NALU_PIC_PARAM)); + if (lvcc) + nalumx_write_ps_list(ctx, bs, nalumx_get_hevc_ps(lvcc, GF_HEVC_NALU_PIC_PARAM)); + } + if (ctx->dsi) gf_free(ctx->dsi); + gf_bs_get_content(bs, &ctx->dsi, &ctx->dsi_size); + gf_bs_del(bs); + + if (avcc) gf_odf_avc_cfg_del(avcc); + if (svcc) gf_odf_avc_cfg_del(svcc); + if (hvcc) gf_odf_hevc_cfg_del(hvcc); + if (lvcc) gf_odf_hevc_cfg_del(lvcc); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, NULL); + + return GF_OK; +} + +GF_Err nalumx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 crc, crc_enh, codecid; + const GF_PropertyValue *p, *dcd, *dcd_enh; + GF_NALUMxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p) return GF_NOT_SUPPORTED; + codecid = p->value.uint; + + //it may happen we don't have anything yet ... + dcd = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (!dcd) { + crc = -1; + } else { + crc = gf_crc_32(dcd->value.data.ptr, dcd->value.data.size); + } + dcd_enh = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT); + + crc_enh = dcd_enh ? gf_crc_32(dcd_enh->value.data.ptr, dcd_enh->value.data.size) : 0; + if ((ctx->crc == crc) && (ctx->crc_enh == crc_enh)) return GF_OK; + ctx->crc = crc; + ctx->crc_enh = crc_enh; + + if ((codecid==GF_CODECID_HEVC) || (codecid==GF_CODECID_LHVC)) { + ctx->is_hevc = GF_TRUE; + } else { + ctx->is_hevc = GF_FALSE; + } + + ctx->width = ctx->height = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + if (p) ctx->width = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + if (p) ctx->height = p->value.uint; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, pid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, &PROP_BOOL(GF_TRUE) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, NULL ); + + ctx->ipid = pid; + gf_filter_pid_set_framing_mode(ctx->ipid, GF_TRUE); + if (!dcd && !dcd_enh) + return GF_OK; + + return nalumx_make_inband_header(ctx, dcd ? dcd->value.data.ptr : NULL, dcd ? dcd->value.data.size : 0, dcd_enh ? dcd_enh->value.data.ptr : NULL, dcd_enh ? dcd_enh->value.data.size : 0); +} + + +static Bool nalumx_is_nal_skip(GF_NALUMxCtx *ctx, char *data, u32 pos, Bool *has_nal_delim, u32 *out_temporal_id, u32 *out_layer_id, u8 *avc_hdr) +{ + Bool is_layer = GF_FALSE; + if (ctx->is_hevc) { + u8 nal_type = (data[pos] & 0x7E) >> 1; + u8 temporal_id = data[pos+1] & 0x7; + u8 layer_id = data[pos] & 1; + layer_id <<= 5; + layer_id |= (data[pos+1]>>3) & 0x1F; + if (temporal_id > *out_temporal_id) *out_temporal_id = temporal_id; + if (! (*out_layer_id) ) *out_layer_id = 1+layer_id; + + switch (nal_type) { + case GF_HEVC_NALU_VID_PARAM: + break; + case GF_HEVC_NALU_ACCESS_UNIT: + *has_nal_delim = GF_TRUE; + break; + default: + if (layer_id) is_layer = GF_TRUE; + break; + } + } else { + u32 nal_type = data[pos] & 0x1F; + switch (nal_type) { + case GF_AVC_NALU_SVC_PREFIX_NALU: + case GF_AVC_NALU_SVC_SLICE: + case GF_AVC_NALU_VDRD: + case GF_AVC_NALU_SVC_SUBSEQ_PARAM: + case GF_AVC_NALU_SEQ_PARAM: + case GF_AVC_NALU_SEQ_PARAM_EXT: + case GF_AVC_NALU_PIC_PARAM: + is_layer = GF_TRUE; + break; + case GF_AVC_NALU_ACCESS_UNIT: + *has_nal_delim = GF_TRUE; + break; + default: + if (! (*avc_hdr)) + (*avc_hdr) = data[pos]; + break; + } + } + if (ctx->extract==1) return is_layer ? GF_TRUE : GF_FALSE; + else if (ctx->extract==2) return is_layer ? GF_FALSE : GF_TRUE; + return GF_FALSE; +} + +GF_Err nalumx_process(GF_Filter *filter) +{ + GF_NALUMxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + u8 *data, *output; + u32 pck_size, size, sap, temporal_id, layer_id; + u8 avc_hdr; + Bool insert_dsi = GF_FALSE; + Bool has_nalu_delim = GF_FALSE; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + if (!ctx->nal_hdr_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[NALWrite] no NAL size length field set, assuming 4\n")); + ctx->nal_hdr_size = 4; + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + if (!pck_size || !data) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + if (!ctx->bs_r) ctx->bs_r = gf_bs_new(data, pck_size, GF_BITSTREAM_READ); + else gf_bs_reassign_buffer(ctx->bs_r, data, pck_size); + + size = 0; + temporal_id = layer_id = 0; + avc_hdr = 0; + + while (gf_bs_available((ctx->bs_r))) { + Bool skip_nal = GF_FALSE; + Bool is_nalu_delim = GF_FALSE; + u32 pos; + u32 nal_size = gf_bs_read_int(ctx->bs_r, 8*ctx->nal_hdr_size); + if (nal_size > gf_bs_available(ctx->bs_r) ) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NON_COMPLIANT_BITSTREAM; + } + pos = (u32) gf_bs_get_position(ctx->bs_r); + //even if not filtering, parse to check for AU delim + skip_nal = nalumx_is_nal_skip(ctx, data, pos, &is_nalu_delim, &layer_id, &temporal_id, &avc_hdr); + if (!ctx->extract) { + skip_nal = GF_FALSE; + } + if (is_nalu_delim) { + has_nalu_delim = GF_TRUE; + if (!ctx->delim) + skip_nal = GF_TRUE; + } + if (!skip_nal) { + size += nal_size + 4; + } + gf_bs_skip_bytes(ctx->bs_r, nal_size); + } + gf_bs_seek(ctx->bs_r, 0); + + if (!ctx->delim) + has_nalu_delim = GF_TRUE; + + //we need to insert NALU delim + if (!has_nalu_delim) { + size += ctx->is_hevc ? 3 : 2; + size += 4; + } + + if (ctx->dsi) { + sap = gf_filter_pck_get_sap(pck); + if (sap && (sap <= GF_FILTER_SAP_3) ) { + insert_dsi = GF_TRUE; + } + if (!insert_dsi) { + u8 flags = gf_filter_pck_get_dependency_flags(pck); + //get dependsOn + if (flags) { + flags>>=4; + flags &= 0x3; + if (flags==2) insert_dsi = GF_TRUE; //could be SAP 1, 2 or 3 + } + } + + if (insert_dsi) { + size += ctx->dsi_size; + } + } + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + + if (!ctx->bs_w) ctx->bs_w = gf_bs_new(output, size, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs_w, output, size); + + // nalu delimiter is not present, write it first + if (!has_nalu_delim) { + gf_bs_write_u32(ctx->bs_w, 1); + if (ctx->is_hevc) { + if (!layer_id) + layer_id=1; + if (!temporal_id) + temporal_id=1; +#ifndef GPAC_DISABLE_HEVC + gf_bs_write_int(ctx->bs_w, 0, 1); + gf_bs_write_int(ctx->bs_w, GF_HEVC_NALU_ACCESS_UNIT, 6); + gf_bs_write_int(ctx->bs_w, layer_id-1, 6); //we should pick the layerID of the following nalus ... + gf_bs_write_int(ctx->bs_w, temporal_id, 3); + /*pic-type - by default we signal all slice types possible*/ + gf_bs_write_int(ctx->bs_w, 2, 3); + gf_bs_write_int(ctx->bs_w, 0, 5); +#endif + } else { + gf_bs_write_int(ctx->bs_w, (avc_hdr & 0x60) | GF_AVC_NALU_ACCESS_UNIT, 8); + gf_bs_write_int(ctx->bs_w, 0xF0 , 8); /*7 "all supported NALUs" (=111) + rbsp trailing (10000)*/; + } + ctx->nb_nalu++; + } + + while (gf_bs_available((ctx->bs_r))) { + u32 pos; + Bool skip_nal = GF_FALSE; + Bool is_nalu_delim = GF_FALSE; + u32 nal_size = gf_bs_read_int(ctx->bs_r, 8*ctx->nal_hdr_size); + if (nal_size > gf_bs_available(ctx->bs_r) ) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NON_COMPLIANT_BITSTREAM; + } + pos = (u32) gf_bs_get_position(ctx->bs_r); + + skip_nal = nalumx_is_nal_skip(ctx, data, pos, &is_nalu_delim, &layer_id, &temporal_id, &avc_hdr); + if (!ctx->extract) { + skip_nal = GF_FALSE; + } + //we don't serialize nalu delimiter, skip nal + else if (!ctx->delim && is_nalu_delim) { + skip_nal = GF_TRUE; + } + + + if (skip_nal) { + gf_bs_skip_bytes(ctx->bs_r, nal_size); + continue; + } + + //insert dsi only after NALUD if any + if (insert_dsi && !is_nalu_delim) { + insert_dsi = GF_FALSE; + gf_bs_write_data(ctx->bs_w, ctx->dsi, ctx->dsi_size); + ctx->nb_nalu += ctx->nb_nalu_in_hdr; + + if (!ctx->rcfg) { + gf_free(ctx->dsi); + ctx->dsi = NULL; + ctx->dsi_size = 0; + } + } + + gf_bs_write_u32(ctx->bs_w, 1); + gf_bs_write_data(ctx->bs_w, data+pos, nal_size); + + gf_bs_skip_bytes(ctx->bs_r, nal_size); + ctx->nb_nalu++; + } + gf_filter_pck_merge_properties(pck, dst_pck); + gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO); + + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + gf_filter_pck_send(dst_pck); + + gf_filter_pid_drop_packet(ctx->ipid); + + if (gf_filter_reporting_enabled(filter)) { + char szStatus[1024]; + + sprintf(szStatus, "%s Annex-B %dx%d % 10d NALU", ctx->is_hevc ? "HEVC":"AVC|H264", ctx->width, ctx->height, ctx->nb_nalu); + gf_filter_update_status(filter, -1, szStatus); + + } + return GF_OK; +} + +static void nalumx_finalize(GF_Filter *filter) +{ + GF_NALUMxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs_r) gf_bs_del(ctx->bs_r); + if (ctx->bs_w) gf_bs_del(ctx->bs_w); + if (ctx->dsi) gf_free(ctx->dsi); +} + +static const GF_FilterCapability NALUMxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_AVC), + CAP_UINT(GF_CAPS_INPUT_OUTPUT_OPT,GF_PROP_PID_CODECID, GF_CODECID_AVC_PS), + CAP_UINT(GF_CAPS_INPUT_OUTPUT_OPT,GF_PROP_PID_CODECID, GF_CODECID_SVC), + CAP_UINT(GF_CAPS_INPUT_OUTPUT_OPT,GF_PROP_PID_CODECID, GF_CODECID_MVC), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_BOOL(GF_CAPS_OUTPUT, GF_PROP_PID_UNFRAMED, GF_TRUE), + {0}, + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_HEVC), + CAP_UINT(GF_CAPS_INPUT_OUTPUT_OPT,GF_PROP_PID_CODECID, GF_CODECID_LHVC), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_BOOL(GF_CAPS_OUTPUT, GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + + +#define OFFS(_n) #_n, offsetof(GF_NALUMxCtx, _n) +static const GF_FilterArgs NALUMxArgs[] = +{ + { OFFS(rcfg), "force repeating decoder config at each I-frame", GF_PROP_BOOL, "true", NULL, 0}, + { OFFS(extract), "layer extraction mode\n" + "- full: extracts all layers\n" + "- base: extract base layer only\n" + "- layer: extract non-base layer(s) only", GF_PROP_UINT, "all", "all|base|layer", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(delim), "insert AU Delimiter NAL", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + + +GF_FilterRegister NALUMxRegister = { + .name = "ufnalu", + GF_FS_SET_DESCRIPTION("AVC/HEVC to AnnexB writer") + GF_FS_SET_HELP("This filter converts AVC|H264 and HEVC streams into AnnexB format, with inband parameter sets and start codes.") + .private_size = sizeof(GF_NALUMxCtx), + .args = NALUMxArgs, + .finalize = nalumx_finalize, + SETCAPS(NALUMxCaps), + .configure_pid = nalumx_configure_pid, + .process = nalumx_process +}; + + +const GF_FilterRegister *nalumx_register(GF_FilterSession *session) +{ + return &NALUMxRegister; +} diff --git a/src/filters/rewrite_obu.c b/src/filters/rewrite_obu.c new file mode 100644 index 0000000..998116f --- /dev/null +++ b/src/filters/rewrite_obu.c @@ -0,0 +1,420 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018 + * All rights reserved + * + * This file is part of GPAC / AV1 OBU rewrite filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +#ifndef GPAC_DISABLE_AV_PARSERS + +typedef struct +{ + //opts + Bool rcfg, tsep; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + u32 crc; + + Bool ivf_hdr; + u32 mode; + GF_BitStream *bs_w; + GF_BitStream *bs_r; + u32 w, h; + GF_Fraction fps; + GF_AV1Config *av1c; + u32 av1b_cfg_size; + u32 codec_id; +} GF_OBUMxCtx; + +GF_Err obumx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 crc; + const GF_PropertyValue *p, *dcd; + GF_OBUMxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + dcd = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (!dcd) return GF_NON_COMPLIANT_BITSTREAM; + + crc = gf_crc_32(dcd->value.data.ptr, dcd->value.data.size); + if (ctx->crc == crc) return GF_OK; + ctx->crc = crc; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, pid); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, &PROP_BOOL(GF_TRUE) ); + + ctx->ipid = pid; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL); + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_CODECID); + ctx->codec_id = p ? p->value.uint : 0; + switch (ctx->codec_id) { + case GF_CODECID_AV1: + //check output type OBU vs av1b + p = gf_filter_pid_caps_query(ctx->opid, GF_PROP_PID_FILE_EXT); + if (p) { + if (!strcmp(p->value.string, "obu")) ctx->mode = 0; + else if (!strcmp(p->value.string, "av1b") || !strcmp(p->value.string, "av1")) ctx->mode = 1; + //we might want to add a generic IVF read/write at some point + else if (!strcmp(p->value.string, "ivf")) { + ctx->mode = 2; + ctx->ivf_hdr = 1; + } + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[OBUWrite] Couldn't guess desired output format type, assuming plain OBU\n")); + } + break; + case GF_CODECID_VP8: + case GF_CODECID_VP9: + case GF_CODECID_VP10: + ctx->mode = 2; //IVF only + ctx->ivf_hdr = 1; + break; + } + + if (ctx->av1c) gf_odf_av1_cfg_del(ctx->av1c); + ctx->av1c = NULL; + if (ctx->mode==1) { + u32 i=0; + GF_AV1_OBUArrayEntry *obu; + ctx->av1c = gf_odf_av1_cfg_read(dcd->value.data.ptr, dcd->value.data.size); + if (!ctx->av1c) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[OBUWrite] Invalid av1 config\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + ctx->av1b_cfg_size = 0; + + while ((obu = gf_list_enum(ctx->av1c->obu_array, &i))) { + //we don't output sequence header since it shall be present in sync sample + //this avoids creating duplicate of the seqeunce header in the output stream + if (obu->obu_type==OBU_SEQUENCE_HEADER) { + i--; + gf_list_rem(ctx->av1c->obu_array, i); + gf_free(obu->obu); + gf_free(obu); + continue; + } + ctx->av1b_cfg_size += (u32) obu->obu_length; + ctx->av1b_cfg_size += gf_av1_leb128_size(obu->obu_length); + } + } + + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_WIDTH); + if (p) ctx->w = p->value.uint; + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_HEIGHT); + if (p) ctx->h = p->value.uint; + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FPS); + if (p) ctx->fps = p->value.frac; + if (!ctx->fps.num || !ctx->fps.den) { + ctx->fps.num = 25; + ctx->fps.den = 1; + } + gf_filter_pid_set_property_str(ctx->opid, "obu:mode", &PROP_UINT(ctx->mode) ); + gf_filter_pid_set_framing_mode(ctx->ipid, GF_TRUE); + + return GF_OK; +} + + +GF_Err obumx_process(GF_Filter *filter) +{ + u32 i; + u32 frame_sizes[128], max_frames; + GF_OBUMxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + u8 *data, *output; + u32 pck_size, size, sap_type, hdr_size, av1b_frame_size=0; + + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + hdr_size = 0; + size = pck_size; + //always add temporal delim + if (ctx->codec_id==GF_CODECID_AV1) + size += 2; + + sap_type = gf_filter_pck_get_sap(pck); + if (!sap_type) { + u8 flags = gf_filter_pck_get_dependency_flags(pck); + if (flags) { + //get dependsOn + flags>>=4; + flags &= 0x3; + if (flags==2) sap_type = 3; //could be 1, 2 or 3 + } + } + + memset(frame_sizes, 0, sizeof(u32)*128); + if (ctx->mode==2) { + if (ctx->ivf_hdr) hdr_size += 32; + hdr_size += 12; + } + if (ctx->mode==1) { + u32 obu_sizes=0; + u32 frame_idx=0; + + obu_sizes = frame_sizes[0] = 3; + if (sap_type && ctx->av1b_cfg_size) { + frame_sizes[0] += ctx->av1b_cfg_size; + obu_sizes += ctx->av1b_cfg_size; + } + + if (!ctx->bs_r) ctx->bs_r = gf_bs_new(data, pck_size, GF_BITSTREAM_READ); + else gf_bs_reassign_buffer(ctx->bs_r, data, pck_size); + + while (gf_bs_available(ctx->bs_r)) { + ObuType obu_type; + u32 obu_size; + Bool obu_extension_flag, obu_has_size_field; + u8 temporal_id, spatial_id; + u32 obu_hdr_size = (u32) gf_bs_get_position(ctx->bs_r); + + gf_av1_parse_obu_header(ctx->bs_r, &obu_type, &obu_extension_flag, &obu_has_size_field, &temporal_id, &spatial_id); + + if (!obu_has_size_field) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("[OBUWrite] OBU without size field, bug in demux filter !!\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + obu_size = (u32)gf_av1_leb128_read(ctx->bs_r, NULL); + obu_hdr_size = (u32) gf_bs_get_position(ctx->bs_r) - obu_hdr_size; + gf_bs_skip_bytes(ctx->bs_r, obu_size); + + obu_size += obu_hdr_size; + obu_sizes += obu_size + gf_av1_leb128_size(obu_size); + + if (obu_type==OBU_FRAME) { + frame_idx++; + if (frame_idx==128) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("[OBUWrite] more than 128 frames in a temporal unit not supported\n")); + return GF_NOT_SUPPORTED; + } + if (frame_idx>1) + frame_sizes[frame_idx-1] = 0; + } + frame_sizes[frame_idx ? (frame_idx-1) : 0] += obu_size + gf_av1_leb128_size(obu_size); + } + max_frames = frame_idx; + size = obu_sizes; + //packet without frame + if (!max_frames) { + size += gf_av1_leb128_size(frame_sizes[0]); + } else { + for (i=0;iav1b_cfg_size) size += ctx->av1b_cfg_size; + } + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, hdr_size+size, &output); + gf_filter_pck_merge_properties(pck, dst_pck); + + if (!ctx->bs_w) ctx->bs_w = gf_bs_new(output, hdr_size+size, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs_w, output, hdr_size+size); + + if (ctx->mode==1) { + u32 frame_idx = 0; + //temporal unit + gf_av1_leb128_write(ctx->bs_w, av1b_frame_size); + assert(frame_sizes[0]); + gf_av1_leb128_write(ctx->bs_w, frame_sizes[0]); + + //write temporal delim with obu size set + gf_av1_leb128_write(ctx->bs_w, 2); + gf_bs_write_u8(ctx->bs_w, 0x12); + gf_bs_write_u8(ctx->bs_w, 0); + + if (sap_type && ctx->av1b_cfg_size) { + GF_AV1_OBUArrayEntry *obu; + i=0; + while ((obu = gf_list_enum(ctx->av1c->obu_array, &i))) { + gf_av1_leb128_write(ctx->bs_w, obu->obu_length); + gf_bs_write_data(ctx->bs_w, obu->obu, (u32) obu->obu_length); + } + } + + gf_bs_reassign_buffer(ctx->bs_r, data, pck_size); + + while (gf_bs_available(ctx->bs_r)) { + ObuType obu_type; + u32 obu_size; + Bool obu_extension_flag, obu_has_size_field; + u8 temporal_id, spatial_id; + u32 obu_hdr_size, start = (u32) gf_bs_get_position(ctx->bs_r); + + gf_av1_parse_obu_header(ctx->bs_r, &obu_type, &obu_extension_flag, &obu_has_size_field, &temporal_id, &spatial_id); + obu_size = (u32)gf_av1_leb128_read(ctx->bs_r, NULL); + + obu_hdr_size = (u32) gf_bs_get_position(ctx->bs_r) - start; + gf_bs_skip_bytes(ctx->bs_r, obu_size); + + if (obu_type==OBU_FRAME) { + frame_idx++; + if (frame_idx>1) { + gf_av1_leb128_write(ctx->bs_w, frame_sizes[frame_idx-1] ); + } + } + + obu_size += obu_hdr_size; + gf_av1_leb128_write(ctx->bs_w, obu_size); + gf_bs_write_data(ctx->bs_w, data+start, obu_size); + + } + assert(gf_bs_get_position(ctx->bs_w) == size); + } else { + + //write IVF headers + if (ctx->ivf_hdr) { + gf_bs_write_u32(ctx->bs_w, GF_4CC('D', 'K', 'I', 'F')); + gf_bs_write_u16_le(ctx->bs_w, 0); + gf_bs_write_u16_le(ctx->bs_w, 32); + + gf_bs_write_u32(ctx->bs_w, GF_4CC('A', 'V', '0', '1') ); //codec_fourcc + gf_bs_write_u16_le(ctx->bs_w, ctx->w); + gf_bs_write_u16_le(ctx->bs_w, ctx->h); + gf_bs_write_u32_le(ctx->bs_w, ctx->fps.num); + gf_bs_write_u32_le(ctx->bs_w, ctx->fps.den); + gf_bs_write_u32_le(ctx->bs_w, 0); //nb frames + gf_bs_write_u32_le(ctx->bs_w, 0); + ctx->ivf_hdr = 0; + } + if (ctx->mode==2) { + u64 cts = gf_filter_pck_get_cts(pck); + cts *= ctx->fps.den; + cts /= ctx->fps.num; + cts /= gf_filter_pck_get_timescale(pck); + gf_bs_write_u32_le(ctx->bs_w, size); + gf_bs_write_u64(ctx->bs_w, cts); + } + if (ctx->codec_id==GF_CODECID_AV1) { + //write temporal delim with obu size set + gf_bs_write_u8(ctx->bs_w, 0x12); + gf_bs_write_u8(ctx->bs_w, 0); + + if (sap_type && ctx->av1b_cfg_size) { + GF_AV1_OBUArrayEntry *obu; + i=0; + while ((obu = gf_list_enum(ctx->av1c->obu_array, &i))) { + gf_av1_leb128_write(ctx->bs_w, obu->obu_length); + gf_bs_write_data(ctx->bs_w, obu->obu, (u32) obu->obu_length); + } + } + } + gf_bs_write_data(ctx->bs_w, data, pck_size); + } + + if (!ctx->rcfg) { + ctx->av1b_cfg_size = 0; + } + gf_filter_pck_send(dst_pck); + gf_filter_pid_drop_packet(ctx->ipid); + + return GF_OK; +} +static void obumx_finalize(GF_Filter *filter) +{ + GF_OBUMxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs_r) gf_bs_del(ctx->bs_r); + if (ctx->bs_w) gf_bs_del(ctx->bs_w); + if (ctx->av1c) gf_odf_av1_cfg_del(ctx->av1c); +} + +static const GF_FilterCapability OBUMxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_AV1), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_VP8), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_VP9), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_VP10), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_BOOL(GF_CAPS_OUTPUT, GF_PROP_PID_UNFRAMED, GF_TRUE), +}; + + + +#define OFFS(_n) #_n, offsetof(GF_OBUMxCtx, _n) +static const GF_FilterArgs OBUMxArgs[] = +{ + { OFFS(rcfg), "force repeating decoder config at each I-frame", GF_PROP_BOOL, "true", NULL, 0}, + {0} +}; + + +GF_FilterRegister OBUMxRegister = { + .name = "ufobu", + GF_FS_SET_DESCRIPTION("IVF/OBU/annexB writer") + GF_FS_SET_HELP("This filter is used to rewrite AV1 OBU bitstream into IVF, annex B or OBU sequence, reinserting the temporal delimiter OBU.") + .private_size = sizeof(GF_OBUMxCtx), + .args = OBUMxArgs, + SETCAPS(OBUMxCaps), + .finalize = obumx_finalize, + .configure_pid = obumx_configure_pid, + .process = obumx_process +}; + + +const GF_FilterRegister *obumx_register(GF_FilterSession *session) +{ + return &OBUMxRegister; +} + +#else +const GF_FilterRegister *obumx_register(GF_FilterSession *session) +{ + return NULL; +} +#endif // GPAC_DISABLE_AV_PARSERS + diff --git a/src/filters/tileagg.c b/src/filters/tileagg.c new file mode 100644 index 0000000..e86030a --- /dev/null +++ b/src/filters/tileagg.c @@ -0,0 +1,290 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2019 + * All rights reserved + * + * This file is part of GPAC / tile aggregrator filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#include +#include +#include +#include + + +typedef struct +{ + //options + GF_PropUIntList tiledrop; + + //internal + GF_FilterPid *opid; + GF_FilterPid *base_ipid; + u32 nalu_size_length; + u32 base_id; + + GF_BitStream *bs_r; +} GF_TileAggCtx; + + +static GF_Err tileagg_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 codec_id=0; + const GF_PropertyValue *p; + GF_HEVCConfig *hvcc; + GF_TileAggCtx *ctx = (GF_TileAggCtx *) gf_filter_get_udta(filter); + + if (is_remove) { + if (ctx->base_ipid == pid) { + gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + } + return GF_OK; + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p) return GF_NOT_SUPPORTED; + codec_id = p->value.uint; + + + if ((codec_id==GF_CODECID_HEVC) && ctx->base_ipid && (ctx->base_ipid != pid)) return GF_REQUIRES_NEW_INSTANCE; + + if ((codec_id==GF_CODECID_HEVC_TILES) && !ctx->base_ipid) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[TileAggr] Base HEVC PID not found for tiled HEVC PID %s\n", gf_filter_pid_get_name(pid) )); + return GF_NOT_SUPPORTED; + } + + if (!ctx->opid) ctx->opid = gf_filter_pid_new(filter); + + if (!ctx->base_ipid) { + ctx->base_ipid = pid; + } + if (ctx->base_ipid == pid) { + gf_filter_pid_copy_properties(ctx->opid, ctx->base_ipid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TILE_BASE, NULL); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SRD, NULL); + //we keep the SRDREF property to indicate this was a tiled HEVC reassembled + gf_filter_pid_set_property_str(ctx->opid, "isom:sabt", NULL); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (!p) return GF_NOT_SUPPORTED; + hvcc = gf_odf_hevc_cfg_read(p->value.data.ptr, p->value.data.size, GF_FALSE); + ctx->nalu_size_length = hvcc ? hvcc->nal_unit_size : 4; + if (hvcc) gf_odf_hevc_cfg_del(hvcc); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); + if (!p) return GF_NOT_SUPPORTED; + ctx->base_id = p->value.uint; + } else { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DEPENDENCY_ID); + if (!p) return GF_NOT_SUPPORTED; + if (ctx->base_id != p->value.uint) return GF_NOT_SUPPORTED; + } + + return GF_OK; +} + + +static GF_Err tileagg_process(GF_Filter *filter) +{ + GF_TileAggCtx *ctx = (GF_TileAggCtx *) gf_filter_get_udta(filter); + u32 i, j, count = gf_filter_get_ipid_count(filter); + GF_FilterPacket *pck, *dst_pck; + u64 min_cts = GF_FILTER_NO_TS; + u32 pck_size, size = 0; + u32 pos; + Bool has_sei_suffix = GF_FALSE; + const char *data; + u8 *output; + if (!ctx->base_ipid) return GF_EOS; + + pck = gf_filter_pid_get_packet(ctx->base_ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->base_ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + min_cts = gf_filter_pck_get_cts(pck); + gf_filter_pck_get_data(pck, &pck_size); + size = pck_size; + + for (i=0; ibase_ipid) continue; + while (1) { + pck = gf_filter_pid_get_packet(pid); + if (!pck) return GF_OK; + + cts = gf_filter_pck_get_cts(pck); + if (cts < min_cts) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[TileAggr] Tiled pid %s with cts "LLU" less than base tile pid cts "LLU" - discarding packet\n", gf_filter_pid_get_name(pid), cts, min_cts )); + gf_filter_pid_drop_packet(pid); + } else { + break; + } + } + assert(pck); + if (cts > min_cts) continue; + + for (j=0; jtiledrop.nb_items; j++) { + if (ctx->tiledrop.vals[j] == i) + do_drop=GF_TRUE; + } + if (do_drop) { + gf_filter_pid_drop_packet(pid); + continue; + } + + gf_filter_pck_get_data(pck, &pck_size); + size += pck_size; + } + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + + pck = gf_filter_pid_get_packet(ctx->base_ipid); + gf_filter_pck_merge_properties(pck, dst_pck); + data = gf_filter_pck_get_data(pck, &pck_size); + + //copy all NAL from base except SEI suffixes + gf_bs_reassign_buffer(ctx->bs_r, data, pck_size); + pos = 0; + size = 0; + while (posbs_r, 8*ctx->nalu_size_length); + u8 nal_type; + gf_bs_read_int(ctx->bs_r, 1); + nal_type = gf_bs_read_int(ctx->bs_r, 6); + gf_bs_read_int(ctx->bs_r, 1); + gf_bs_skip_bytes(ctx->bs_r, nal_size-1); + if (nal_type == GF_HEVC_NALU_SEI_SUFFIX) { + has_sei_suffix = GF_TRUE; + } else { + memcpy(output+size, data+pos, nal_size + ctx->nalu_size_length); + size += nal_size + ctx->nalu_size_length; + } + pos += nal_size + ctx->nalu_size_length; + } + + for (i=0; ibase_ipid) continue; + pck = gf_filter_pid_get_packet(pid); + //can happen if we drop one tile + if (!pck) continue; + + cts = gf_filter_pck_get_cts(pck); + if (cts != min_cts) continue; + + data = gf_filter_pck_get_data(pck, &pck_size); + memcpy(output+size, data, pck_size); + size += pck_size; + + gf_filter_pid_drop_packet(pid); + } + + //append all SEI suffixes + if (has_sei_suffix) { + pck = gf_filter_pid_get_packet(ctx->base_ipid); + data = gf_filter_pck_get_data(pck, &pck_size); + gf_bs_reassign_buffer(ctx->bs_r, data, pck_size); + + pos = 0; + while (posbs_r, 8*ctx->nalu_size_length); + u8 nal_type; + gf_bs_read_int(ctx->bs_r, 1); + nal_type = gf_bs_read_int(ctx->bs_r, 6); + gf_bs_read_int(ctx->bs_r, 1); + gf_bs_skip_bytes(ctx->bs_r, nal_size-1); + if (nal_type == GF_HEVC_NALU_SEI_SUFFIX) { + memcpy(output+size, data+pos, nal_size + ctx->nalu_size_length); + size += nal_size + ctx->nalu_size_length; + } + pos += nal_size + ctx->nalu_size_length; + } + } + + gf_filter_pid_drop_packet(ctx->base_ipid); + + gf_filter_pck_send(dst_pck); + return GF_OK; +} + +static GF_Err tileagg_initialize(GF_Filter *filter) +{ + GF_TileAggCtx *ctx = (GF_TileAggCtx *) gf_filter_get_udta(filter); + ctx->bs_r = gf_bs_new((char *)ctx, 1, GF_BITSTREAM_READ); + + return GF_OK; +} + +static void tileagg_finalize(GF_Filter *filter) +{ + GF_TileAggCtx *ctx = (GF_TileAggCtx *) gf_filter_get_udta(filter); + gf_bs_del(ctx->bs_r); +} + +static const GF_FilterCapability TileAggCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_HEVC), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_TILE_BASE, GF_TRUE), + + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT_STATIC, GF_PROP_PID_CODECID, GF_CODECID_HEVC), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_HEVC_TILES), +}; + +#define OFFS(_n) #_n, offsetof(GF_TileAggCtx, _n) + +static const GF_FilterArgs TileAggArgs[] = +{ + { OFFS(tiledrop), "specify indexes of tiles to drop", GF_PROP_UINT_LIST, "", NULL, GF_FS_ARG_UPDATE}, + {0} +}; + +GF_FilterRegister TileAggRegister = { + .name = "tileagg", + GF_FS_SET_DESCRIPTION("HEVC tile aggregator") + GF_FS_SET_HELP("This filter reaggregates a set of split tiled HEVC streams (`hvt1` or `hvt2` in isobmff) into a single HEVC stream.") + .private_size = sizeof(GF_TileAggCtx), + SETCAPS(TileAggCaps), + .initialize = tileagg_initialize, + .finalize = tileagg_finalize, + .args = TileAggArgs, + .configure_pid = tileagg_configure_pid, + .process = tileagg_process, + .max_extra_pids = (u32) (-1), +}; + +const GF_FilterRegister *tileagg_register(GF_FilterSession *session) +{ + return &TileAggRegister; +} + + diff --git a/src/filters/tssplit.c b/src/filters/tssplit.c new file mode 100644 index 0000000..a68709c --- /dev/null +++ b/src/filters/tssplit.c @@ -0,0 +1,480 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom Paris 2019 + * All rights reserved + * + * This file is part of GPAC / MPEG Transport Stream splitter filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include + +#include + +typedef struct +{ + GF_FilterPid *opid; + u32 pmt_pid; + u8 pat_pck[192]; + u32 pat_pck_size; + + u8 *pck_buffer; + u32 nb_pck; +} GF_M2TSSplit_SPTS; + + +typedef struct +{ + //options + Bool dvb; + s32 mux_id; + Bool avonly; + u32 nb_pack; + + //internal + GF_Filter *filter; + GF_FilterPid *ipid; + + GF_List *streams; + + GF_M2TS_Demuxer *dmx; + + u8 tsbuf[192]; + GF_BitStream *bsw; + +} GF_M2TSSplitCtx; + + + +void m2tssplit_send_packet(GF_M2TSSplitCtx *ctx, GF_M2TSSplit_SPTS *stream, u8 *data, u32 size) +{ + u8 *buffer; + GF_FilterPacket *pck; + if (ctx->nb_pack) { + assert (stream->nb_pcknb_pack); + if (data) { + memcpy(stream->pck_buffer + size*stream->nb_pck, data, size); + stream->nb_pck++; + if (stream->nb_pcknb_pack) { + return; + } + } + u32 osize = size*stream->nb_pck; + pck = gf_filter_pck_new_alloc(stream->opid, osize, &buffer); + gf_filter_pck_set_framing(pck, GF_FALSE, GF_FALSE); + memcpy(buffer, stream->pck_buffer, osize); + gf_filter_pck_send(pck); + stream->nb_pck = 0; + } else { + pck = gf_filter_pck_new_alloc(stream->opid, size, &buffer); + gf_filter_pck_set_framing(pck, GF_FALSE, GF_FALSE); + memcpy(buffer, data, size); + gf_filter_pck_send(pck); + } +} + +void m2tssplit_flush(GF_M2TSSplitCtx *ctx) +{ + u32 i; + if (!ctx->nb_pack) return; + + for (i=0; istreams); i++ ) { + GF_M2TSSplit_SPTS *stream = gf_list_get(ctx->streams, i); + if (stream->opid && stream->nb_pck) + m2tssplit_send_packet(ctx, stream, NULL, 0); + } + +} + +GF_Err m2tssplit_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_M2TSSplitCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + m2tssplit_flush(ctx); + while (gf_list_count(ctx->streams) ) { + GF_M2TSSplit_SPTS *st = gf_list_pop_back(ctx->streams); + gf_filter_pid_remove(st->opid); + if (st->pck_buffer) gf_free(st->pck_buffer); + gf_free(st); + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + ctx->ipid = pid; + return GF_OK; +} + +static Bool m2tssplit_process_event(GF_Filter *filter, const GF_FilterEvent *evt) +{ +// GF_M2TSSplitCtx *ctx = gf_filter_get_udta(filter); + + switch (evt->base.type) { + case GF_FEVT_PLAY: + //cancel event + return GF_TRUE; + + case GF_FEVT_STOP: + return GF_FALSE; + + case GF_FEVT_SET_SPEED: + //cancel event + return GF_TRUE; + default: + break; + } + //by default don't cancel event - to rework once we have downloading in place + return GF_FALSE; +} + +GF_Err m2tssplit_process(GF_Filter *filter) +{ + GF_M2TSSplitCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + const u8 *data; + u32 data_size; + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) + m2tssplit_flush(ctx); + return GF_OK; + } + data = gf_filter_pck_get_data(pck, &data_size); + if (data) { + gf_m2ts_process_data(ctx->dmx, (u8 *)data, data_size); + } + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + + +void m2tssplit_on_event(struct tag_m2ts_demux *ts, u32 evt_type, void *par) +{ + u32 i; + GF_M2TSSplitCtx *ctx = ts->user; + + if ((evt_type==GF_M2TS_EVT_PAT_FOUND) || (evt_type==GF_M2TS_EVT_PAT_UPDATE)) { + //todo, purge previous programs if PMT PID changes + for (i=0; idmx->programs); i++) { + GF_M2TS_SectionInfo *sinfo = par; + GF_M2TSSplit_SPTS *stream=NULL; + u32 j, pck_size, tot_len=188, crc, offset=5, mux_id; + u8 *buffer; + Bool first_pck=GF_FALSE; + GF_M2TS_Program *prog = gf_list_get(ctx->dmx->programs, i); + assert(prog->pmt_pid); + + for (j=0; jstreams); j++) { + stream = gf_list_get(ctx->streams, j); + if (stream->pmt_pid==prog->pmt_pid) + break; + stream = NULL; + } + if (!stream) { + GF_SAFEALLOC(stream, GF_M2TSSplit_SPTS); + if (!stream) return; + stream->pmt_pid = prog->pmt_pid; + first_pck = GF_TRUE; + prog->user = stream; + if (ctx->nb_pack) + stream->pck_buffer = gf_malloc(sizeof(char) * ctx->nb_pack * (ctx->dmx->prefix_present ? 192 : 188) ); + + gf_list_add(ctx->streams, stream); + + //do not create output stream until we are sure we have AV component in program + } + + //generate a pat + gf_bs_seek(ctx->bsw, 0); + if (ctx->dmx->prefix_present) { + tot_len += 4; + offset += 4; + gf_bs_write_u32(ctx->bsw, 1); + } + gf_bs_write_u8(ctx->bsw, 0x47); + gf_bs_write_int(ctx->bsw, 0, 1); // error indicator + gf_bs_write_int(ctx->bsw, 1, 1); // start ind + gf_bs_write_int(ctx->bsw, 0, 1); // transport priority + gf_bs_write_int(ctx->bsw, 0, 13); // pid + gf_bs_write_int(ctx->bsw, 0, 2); // scrambling + gf_bs_write_int(ctx->bsw, 1, 2); // we do not use adaptation field for sections + gf_bs_write_int(ctx->bsw, 0, 4); // continuity counter + gf_bs_write_u8(ctx->bsw, 0); // ptr_field + gf_bs_write_int(ctx->bsw, GF_M2TS_TABLE_ID_PAT, 8); + gf_bs_write_int(ctx->bsw, 1/*use_syntax_indicator*/, 1); + gf_bs_write_int(ctx->bsw, 0, 1); + gf_bs_write_int(ctx->bsw, 3, 2); /* reserved bits are all set */ + gf_bs_write_int(ctx->bsw, 5 + 4 + 4, 12); //syntax indicator section + PAT payload + CRC32 + /*syntax indicator section*/ + + if (ctx->mux_id>=0) { + mux_id = ctx->mux_id; + } else { + //we use muxID*255 + streamIndex as the target mux ID if not configured + mux_id = sinfo->ex_table_id; + mux_id *= 255; + } + mux_id += i; + + gf_bs_write_int(ctx->bsw, mux_id, 16); + gf_bs_write_int(ctx->bsw, 3, 2); /* reserved bits are all set */ + gf_bs_write_int(ctx->bsw, sinfo->version_number, 5); + gf_bs_write_int(ctx->bsw, 1, 1); /* current_next_indicator = 1: we don't send version in advance */ + gf_bs_write_int(ctx->bsw, 0, 8); //current section number + gf_bs_write_int(ctx->bsw, 0, 8); //last section number + + /*PAT payload*/ + gf_bs_write_u16(ctx->bsw, prog->number); + gf_bs_write_int(ctx->bsw, 0x7, 3); /*reserved*/ + gf_bs_write_int(ctx->bsw, prog->pmt_pid, 13); /*reserved*/ + + pck_size = (u32) gf_bs_get_position(ctx->bsw); + /*write CRC32 starting from first field in section until last before CRC*/ + crc = gf_crc_32(ctx->tsbuf + offset, pck_size - offset); + ctx->tsbuf[pck_size] = (crc >> 24) & 0xFF; + ctx->tsbuf[pck_size+1] = (crc >> 16) & 0xFF; + ctx->tsbuf[pck_size+2] = (crc >> 8) & 0xFF; + ctx->tsbuf[pck_size+3] = crc & 0xFF; + pck_size += 4; + + //pad the rest to 0xFF + memset(ctx->tsbuf + pck_size, 0xFF, tot_len - pck_size); + //copy over for PAT repeat + memcpy(stream->pat_pck, ctx->tsbuf, tot_len); + stream->pat_pck_size = tot_len; + + //output new PAT + if (stream->opid) { + GF_FilterPacket *pck = gf_filter_pck_new_alloc(stream->opid, tot_len, &buffer); + gf_filter_pck_set_framing(pck, first_pck, GF_FALSE); + memcpy(buffer, ctx->tsbuf, tot_len); + gf_filter_pck_send(pck); + } + } + return; + } + //resend PAT + if (evt_type==GF_M2TS_EVT_PAT_REPEAT) { + for (i=0; idmx->programs); i++) { + u8 *buffer; + GF_M2TS_Program *prog = gf_list_get(ctx->dmx->programs, i); + GF_M2TSSplit_SPTS *stream = prog->user; + if (!stream) continue; + if (!stream->opid) continue; + + GF_FilterPacket *pck = gf_filter_pck_new_alloc(stream->opid, stream->pat_pck_size, &buffer); + gf_filter_pck_set_framing(pck, GF_FALSE, GF_FALSE); + memcpy(buffer, stream->pat_pck, stream->pat_pck_size); + gf_filter_pck_send(pck); + } + return; + } + if ((evt_type==GF_M2TS_EVT_PMT_FOUND) || (evt_type==GF_M2TS_EVT_PMT_UPDATE)) { + GF_M2TS_Program *prog = par; + GF_M2TSSplit_SPTS *stream = prog->user; + u32 known_streams = 0; + for (i=0; istreams); i++) { + GF_M2TS_ES *es = gf_list_get(prog->streams, i); + switch (es->stream_type) { + case GF_M2TS_VIDEO_MPEG1: + case GF_M2TS_VIDEO_MPEG2: + case GF_M2TS_VIDEO_DCII: + case GF_M2TS_VIDEO_MPEG4: + case GF_M2TS_VIDEO_H264: + case GF_M2TS_VIDEO_SVC: + case GF_M2TS_VIDEO_HEVC: + case GF_M2TS_VIDEO_HEVC_TEMPORAL: + case GF_M2TS_VIDEO_HEVC_MCTS: + case GF_M2TS_VIDEO_SHVC: + case GF_M2TS_VIDEO_SHVC_TEMPORAL: + case GF_M2TS_VIDEO_MHVC: + case GF_M2TS_VIDEO_MHVC_TEMPORAL: + case GF_M2TS_AUDIO_MPEG1: + case GF_M2TS_AUDIO_MPEG2: + case GF_M2TS_AUDIO_LATM_AAC: + case GF_M2TS_AUDIO_AAC: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + case GF_M2TS_AUDIO_AC3: + case GF_M2TS_AUDIO_EC3: + case GF_M2TS_SYSTEMS_MPEG4_SECTIONS: + case GF_M2TS_SYSTEMS_MPEG4_PES: + case GF_M2TS_METADATA_PES: + case GF_M2TS_METADATA_ID3_HLS: + known_streams++; + break; + default: + break; + } + } + //if avonly mode and no known streams, do not forward program + if (ctx->avonly && !known_streams) + return; + //good to go, create output and send PAT + if (!stream->opid) { + u8 *buffer; + + stream->opid = gf_filter_pid_new(ctx->filter); + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE)); + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_MIME, &PROP_STRING("video/mpeg-2")); + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("ts")); + gf_filter_pid_set_property(stream->opid, GF_PROP_PID_SERVICE_ID, &PROP_UINT(prog->number)); + + GF_FilterPacket *pck = gf_filter_pck_new_alloc(stream->opid, stream->pat_pck_size, &buffer); + gf_filter_pck_set_framing(pck, GF_TRUE, GF_FALSE); + memcpy(buffer, stream->pat_pck, stream->pat_pck_size); + gf_filter_pck_send(pck); + } + return; + } + + if (evt_type==GF_M2TS_EVT_PCK) { + GF_M2TS_TSPCK *tspck = par; + Bool do_fwd; + GF_M2TSSplit_SPTS *stream = tspck->stream ? tspck->stream->program->user : NULL; + + if (stream) { + if (!stream->opid) return; + + if (ctx->dmx->prefix_present) { + u8 *data = tspck->data; + data -= 4; + m2tssplit_send_packet(ctx, stream, data, 192); + } else { + m2tssplit_send_packet(ctx, stream, tspck->data, 188); + } + return; + } + + if (!ctx->dvb) return; + + do_fwd = GF_FALSE; + switch (tspck->pid) { + case GF_M2TS_PID_CAT: + case GF_M2TS_PID_TSDT: + case GF_M2TS_PID_NIT_ST: + case GF_M2TS_PID_SDT_BAT_ST: + case GF_M2TS_PID_EIT_ST_CIT: + case GF_M2TS_PID_RST_ST: + case GF_M2TS_PID_TDT_TOT_ST: + case GF_M2TS_PID_NET_SYNC: + case GF_M2TS_PID_RNT: + case GF_M2TS_PID_IN_SIG: + case GF_M2TS_PID_MEAS: + case GF_M2TS_PID_DIT: + case GF_M2TS_PID_SIT: + do_fwd = GF_TRUE; + } + if (do_fwd) { + u32 count = gf_list_count(ctx->streams); + for (i=0; istreams, i); + if (!stream->opid) continue; + + if (ctx->dmx->prefix_present) { + u8 *data = tspck->data; + data -= 4; + m2tssplit_send_packet(ctx, stream, data, 192); + } else { + m2tssplit_send_packet(ctx, stream, tspck->data, 188); + } + } + } + } +} + +GF_Err m2tssplit_initialize(GF_Filter *filter) +{ + GF_M2TSSplitCtx *ctx = gf_filter_get_udta(filter); + ctx->streams = gf_list_new(); + ctx->dmx = gf_m2ts_demux_new(); + ctx->dmx->on_event = m2tssplit_on_event; + ctx->dmx->split_mode = GF_TRUE; + ctx->dmx->user = ctx; + ctx->filter = filter; + ctx->bsw = gf_bs_new(ctx->tsbuf, 192, GF_BITSTREAM_WRITE); + if (ctx->nb_pack<=1) + ctx->nb_pack = 0; + return GF_OK; +} + +void m2tssplit_finalize(GF_Filter *filter) +{ + GF_M2TSSplitCtx *ctx = gf_filter_get_udta(filter); + + while (gf_list_count(ctx->streams)) { + GF_M2TSSplit_SPTS *st = gf_list_pop_back(ctx->streams); + if (st->pck_buffer) gf_free(st->pck_buffer); + gf_free(st); + } + gf_list_del(ctx->streams); + gf_bs_del(ctx->bsw); + gf_m2ts_demux_del(ctx->dmx); +} + +static const GF_FilterCapability M2TSSplitCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_FILE_EXT, "ts|m2t|mts|dmb|trp"), + CAP_STRING(GF_CAPS_INPUT_OUTPUT, GF_PROP_PID_MIME, "video/mpeg-2|video/mp2t|video/mpeg"), +}; + +#define OFFS(_n) #_n, offsetof(GF_M2TSSplitCtx, _n) +static const GF_FilterArgs M2TSSplitArgs[] = +{ + { OFFS(dvb), "forward all packets from global DVB PIDs", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(mux_id), "set initial ID of output mux; the first program will use mux_id, the second mux_id+1, etc. If not set, this value will be set to sourceMuxId*255", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_HINT_EXPERT}, + { OFFS(avonly), "do not forward programs with no AV component", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(nb_pack), "pack N packets before sending", GF_PROP_UINT, "10", NULL, GF_FS_ARG_HINT_ADVANCED}, + + {0} +}; + + + +GF_FilterRegister M2TSSplitRegister = { + .name = "tssplit", + GF_FS_SET_DESCRIPTION("MPEG Transport Stream splitter") + GF_FS_SET_HELP("This filter splits an MPEG-2 transport stream into several single program transport streams.\n" + "Only the PAT table is rewritten, the CAT table, PMT and all program streams are forwarded as is.\n" + "In [-full]() mode, global DVB tables of the input multiplex are forwarded to each output mux; otherwise these tables are discarded.") + .flags = GF_FS_REG_EXPLICIT_ONLY, + .private_size = sizeof(GF_M2TSSplitCtx), + .initialize = m2tssplit_initialize, + .finalize = m2tssplit_finalize, + .args = M2TSSplitArgs, + SETCAPS(M2TSSplitCaps), + .configure_pid = m2tssplit_configure_pid, + .process = m2tssplit_process, + .process_event = m2tssplit_process_event, +}; + +const GF_FilterRegister *m2tssplit_register(GF_FilterSession *session) +{ + return &M2TSSplitRegister; +} + diff --git a/src/filters/unit_test_filter.c b/src/filters/unit_test_filter.c new file mode 100644 index 0000000..164d167 --- /dev/null +++ b/src/filters/unit_test_filter.c @@ -0,0 +1,1001 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2018 + * All rights reserved + * + * This file is part of GPAC / unit test filters + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include + +typedef struct +{ + GF_FilterPid *src_pid; + GF_FilterPid *dst_pid; + GF_SHA1Context *sha_ctx; + u32 nb_packets, pck_del; + + GF_FilterFrameInterface frame_ifce; + u8 ifce_data[10]; +} PIDCtx; + +enum +{ + UTF_MODE_SOURCE=0, + UTF_MODE_SINK, + UTF_MODE_FILTER, +}; +typedef struct +{ + GF_List *pids; + + //0: source, 1: sink, 2: filter + u32 mode; + u32 max_pck; + s32 max_out; + const char *pid_att; + Bool alloc; + u32 nb_pids; + u32 fwd; + u32 framing; + Bool cov; + Bool norecfg; + const char *update; + + Bool gsftest; + GF_Fraction64 dummy1; +} GF_UnitTestFilter; + +static void test_pck_del(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck) +{ + PIDCtx *stack = (PIDCtx *) gf_filter_pid_get_udta(pid); + stack->pck_del++; + assert(stack->nb_packets >= stack->pck_del); + GF_LOG(GF_LOG_DEBUG, GF_LOG_APP, ("%s: Packet deleted - %d out there (%d sent %d destroyed)\n", gf_filter_get_name(filter), stack->nb_packets - stack->pck_del, stack->nb_packets, stack->pck_del)); +} + + +void dump_properties(GF_FilterPacket *pck, u32 nb_pck) +{ + u32 idx = 0; + while (1) { + u32 p4cc; + const char *pname; + const GF_PropertyValue *p = gf_filter_pck_enum_properties(pck, &idx, &p4cc, &pname); + if (!p) break; + //dump_property(pck, nb_pck, p4cc, pname, p); + } + if (nb_pck==1) { + gf_filter_pck_get_property(pck, GF_4CC('c','u','s','t')); + gf_filter_pck_get_property_str(pck, "custom"); + } +} + +static void ut_filter_finalize(GF_Filter *filter) +{ + u32 i, count; + u8 digest[GF_SHA1_DIGEST_SIZE]; + GF_UnitTestFilter *stack = (GF_UnitTestFilter *) gf_filter_get_udta(filter); + + count = gf_list_count(stack->pids); + for (i=0; ipids, i); + if (pidctx->sha_ctx && (stack->mode!=UTF_MODE_SOURCE) ) { + gf_sha1_finish(pidctx->sha_ctx, digest); + + if (!pidctx->src_pid) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[%s] Pid %d Source PID not available while dumping SHA1\n", gf_filter_get_name(filter), i+1 )); + } else { + const GF_PropertyValue *p = gf_filter_pid_get_property(pidctx->src_pid, GF_4CC('s','h','a','1') ); + if (!p) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[%s] Pid %d sha1 property not found on input pid\n", gf_filter_get_name(filter), i+1 )); + } else if (p->value.data.size != GF_SHA1_DIGEST_SIZE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[%s] Pid %d wrong size for sha1 property\n", gf_filter_get_name(filter), i+1 )); + } else if (memcmp(p->value.data.ptr, digest, p->value.data.size )) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[%s] Pid %d wrong hash after execution\n", gf_filter_get_name(filter), i+1 )); + } else { + GF_LOG(GF_LOG_WARNING, GF_LOG_APP, ("[%s] Pid %d hash OK after execution\n", gf_filter_get_name(filter), i+1 )); + } + } + } + gf_free(pidctx); + } + gf_list_del(stack->pids); +} + +static void ut_filter_send_update(GF_Filter *filter, u32 nb_pck) +{ + GF_UnitTestFilter *stack = (GF_UnitTestFilter *) gf_filter_get_udta(filter); + + if (stack->update && (nb_pck==stack->max_pck/2) ) { + char *sep, *fid; + char *cmd = gf_strdup(stack->update); + fid = cmd; + sep = strchr(cmd, ','); + if (sep) { + char *name, *val; + sep[0]=0; + sep+=1; + name=sep; + sep = strchr(name, ','); + if (sep) { + sep[0]=0; + val = sep+1; + } else { + val=NULL; + } + gf_filter_send_update(filter, fid, name, val, 0); + } + gf_free(cmd); + } +} + + +static GF_Err ut_filter_process_filter(GF_Filter *filter) +{ + u32 size, i, j, count, nb_loops; + u32 fwd; + GF_FilterPacket *pck_dst; + GF_UnitTestFilter *stack = (GF_UnitTestFilter *) gf_filter_get_udta(filter); + + count = gf_list_count(stack->pids); + for (i=0; ipids, i); + GF_FilterPacket *pck = gf_filter_pid_get_packet(pidctx->src_pid); + if (!pck) + return GF_OK; + + if ((stack-> max_out>=0) && (pidctx->nb_packets - pidctx->pck_del >= (u32) stack->max_out) ) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_APP, ("TestSource: No packets to emit, waiting for destruction\n")); + return GF_OK; + } + } + + //loop on each PID + for (i=0; ipids, i); + GF_FilterPacket *pck = gf_filter_pid_get_packet(pidctx->src_pid); + assert (pck); + assert(pidctx == gf_filter_pid_get_udta(pidctx->src_pid)); + + data = gf_filter_pck_get_data(pck, &size); + gf_sha1_update(pidctx->sha_ctx, (u8*)data, size); + + nb_loops = stack->framing ? 3 : 1; + pck_size = stack->framing ? size/nb_loops : size; + data_ptr = (u8 *)data; + + for (j=0; jfwd; + if (fwd==3) fwd = pidctx->nb_packets % 3; + + //shared memory + if (fwd==0) { + pck_dst = gf_filter_pck_new_shared(pidctx->dst_pid, data_ptr, pck_size, test_pck_del); + } + //copy memory + else if (fwd==1) { + u8 *data_dst; + pck_dst = gf_filter_pck_new_alloc(pidctx->dst_pid, pck_size, &data_dst); + if (pck_dst) { + memcpy(data_dst, data_ptr, pck_size); + } + } + //packet reference + else { + if (stack->framing) { + pck_dst = gf_filter_pck_new_ref(pidctx->dst_pid, data_ptr, pck_size, pck); + } else { + pck_dst = gf_filter_pck_new_ref(pidctx->dst_pid, NULL, 0, pck); + } + } + + + if (pck_dst) { + Bool is_start, is_end; + //get source packet framing + gf_filter_pck_get_framing(pck, &is_start, &is_end); + //adjust flags given our framing + if (is_start && j) is_start = GF_FALSE; + if (is_end && (j+1 < nb_loops) ) is_end = GF_FALSE; + if (stack->framing==2) is_start = GF_FALSE; + if (stack->framing==3) is_end = GF_FALSE; + + gf_filter_pck_set_framing(pck_dst, is_start, is_end); + + pidctx->nb_packets++; + //copy over src props to dst + gf_filter_pck_merge_properties(pck, pck_dst); + gf_filter_pck_send(pck_dst); + } + //move our data pointer + data_ptr += pck_size; + + } //end framing loop + + gf_filter_pid_drop_packet(pidctx->src_pid); + + } //end PID loop + + return GF_OK; + +} + +static void ut_source_pck_del(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck) +{ + +} + +static GF_Err ut_source_ifce_get_plane(struct _gf_filter_frame_interface *frame, u32 plane_idx, const u8 **outPlane, u32 *outStride) +{ + PIDCtx *pctx = frame->user_data; + memset(pctx->ifce_data, 0, 10); + if (plane_idx) return GF_BAD_PARAM; + *outPlane = pctx->ifce_data; + *outStride = 5; + return GF_OK; +} + +static GF_Err ut_filter_process_source(GF_Filter *filter) +{ + GF_PropertyValue p; + GF_FilterPacket *pck; + u32 i, count, nb_eos; + GF_UnitTestFilter *stack = (GF_UnitTestFilter *) gf_filter_get_udta(filter); + + nb_eos = 0; + count = gf_list_count(stack->pids); + for (i=0; ipids, i); + + if (pidctx->nb_packets==stack->max_pck) { + if (stack->gsftest && pidctx->dst_pid) { + gf_filter_pid_remove(pidctx->dst_pid); + pidctx->dst_pid = NULL; + } + nb_eos++; + continue; + } + + if ((stack->max_out>=0) && (pidctx->nb_packets - pidctx->pck_del >= (u32) stack->max_out) ) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_APP, ("TestSource: No packets to emit, waiting for destruction\n")); + continue; + } + pidctx->nb_packets++; + + if (stack->gsftest && pidctx->nb_packets==4) { + pidctx->frame_ifce.get_plane = ut_source_ifce_get_plane; + pidctx->frame_ifce.user_data = pidctx; + pck = gf_filter_pck_new_frame_interface(pidctx->dst_pid, &pidctx->frame_ifce, ut_source_pck_del); + } else if (stack->alloc) { + u8 *data; + pck = gf_filter_pck_new_alloc(pidctx->dst_pid, 10, &data); + memcpy(data, "PacketCopy", 10); + gf_sha1_update(pidctx->sha_ctx, "PacketCopy", 10); + } else { + pck = gf_filter_pck_new_shared(pidctx->dst_pid, "PacketShared", 12, test_pck_del); + gf_sha1_update(pidctx->sha_ctx, "PacketShared", 12); + } + GF_LOG(GF_LOG_DEBUG, GF_LOG_APP, ("TestSource: pck %d PacketShared\n", pidctx->nb_packets)); + + gf_filter_pck_set_cts(pck, pidctx->nb_packets); + + p.type = GF_PROP_NAME; + p.value.string = "custom_value"; + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','t'), &p); + + //try all our properties + if (pidctx->nb_packets==1) { + u32 val=1; + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','1'), &PROP_BOOL(GF_TRUE) ); + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','2'), &PROP_SINT(-1)); + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','3'), &PROP_UINT(1)); + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','4'), &PROP_LONGSINT(-1)); + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','5'), &PROP_LONGUINT(1)); + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','6'), &PROP_FLOAT(1.0f)); + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','7'), &PROP_DOUBLE(1.0)); + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','8'), &PROP_FRAC_INT(1,1)); + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','8'), &PROP_FRAC64_INT(1,1)); + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','9'), &PROP_POINTER(pck)); + + if (stack->gsftest) { + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','a'), &PROP_DATA(pidctx->ifce_data, 8)); + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','b'), &PROP_CONST_DATA(pidctx->ifce_data, 8)); + } else { + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','a'), &PROP_DATA((char *) pidctx, sizeof(pidctx))); + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','b'), &PROP_CONST_DATA((char *) pidctx, sizeof(pidctx))); + } + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','c'), &PROP_STRING("custom")); + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','d'), &PROP_STRING("custom")); + memset(&p, 0, sizeof(GF_PropertyValue)); + p.type = GF_PROP_VEC2; + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','e'), &p); + p.type = GF_PROP_VEC2I; + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','f'), &p); + p.type = GF_PROP_VEC3; + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','g'), &p); + p.type = GF_PROP_VEC3I; + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','h'), &p); + p.type = GF_PROP_VEC4; + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','i'), &p); + p.type = GF_PROP_VEC4I; + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','j'), &p); + p.type = GF_PROP_STRING_LIST; + p.value.string_list = gf_list_new(); + gf_list_add(p.value.string_list, gf_strdup("custom")); + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','k'), &p); + p.value.string_list = NULL; + p.type = GF_PROP_UINT_LIST; + p.value.uint_list.nb_items = 1; + p.value.uint_list.vals = &val; + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','l'), &p); + + gf_filter_pck_set_property_str(pck, "cusd", &PROP_BOOL(GF_TRUE) ); + gf_filter_pck_set_property_dyn(pck, "cuse", &PROP_BOOL(GF_TRUE) ); + } + if (stack->gsftest) { + gf_filter_pck_set_dts(pck, pidctx->nb_packets-1); + gf_filter_pck_set_cts(pck, pidctx->nb_packets-1); + gf_filter_pck_set_duration(pck, 1); + if (pidctx->nb_packets==2) { + gf_filter_pck_set_seek_flag(pck, GF_TRUE); + gf_filter_pck_set_carousel_version(pck, 1); + gf_filter_pck_set_interlaced(pck, GF_TRUE); + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_3); + gf_filter_pck_set_dependency_flags(pck, 0xFF); + gf_filter_pck_set_property(pck, GF_PROP_PCK_SENDER_NTP, &PROP_LONGUINT(0) ); + } + else if (pidctx->nb_packets==3) { + gf_filter_pck_set_sap(pck, GF_FILTER_SAP_4); + gf_filter_pck_set_roll_info(pck, 1); + gf_filter_pck_set_byte_offset(pck, 20); + } + } + + ut_filter_send_update(filter, pidctx->nb_packets); + + if (pidctx->nb_packets==stack->max_pck) { + if (pidctx->sha_ctx) { + u8 digest[GF_SHA1_DIGEST_SIZE]; + gf_sha1_finish(pidctx->sha_ctx, digest); + pidctx->sha_ctx = NULL; + p.type = GF_PROP_DATA; + p.value.data.size = GF_SHA1_DIGEST_SIZE; + p.value.data.ptr = (char *) digest; + //with this we test both: + //- SHA of send data is correct at the receiver side + //- property update on a PID + gf_filter_pid_set_property(pidctx->dst_pid, GF_4CC('s','h','a','1'), &p); + } + } + //just for coverage: check keeping a reference to the packet + gf_filter_pck_ref(& pck); + + gf_filter_pck_send(pck); + + //and destroy the reference + gf_filter_pck_unref(pck); + + } + if (nb_eos==count) return GF_EOS; + return GF_OK; +} + + +static GF_Err ut_filter_process_sink(GF_Filter *filter) +{ + u32 size, i, count, nb_eos; + const char *data; + GF_UnitTestFilter *stack = (GF_UnitTestFilter *) gf_filter_get_udta(filter); + + count = gf_list_count(stack->pids); + nb_eos=0; + + for (i=0; ipids, i); + + GF_FilterPacket *pck = gf_filter_pid_get_packet(pidctx->src_pid); + if (!pck) { + if (gf_filter_pid_is_eos(pidctx->src_pid)) nb_eos++; + continue; + } + + data = gf_filter_pck_get_data(pck, &size); + + if (stack->cov && !pidctx->nb_packets) { + GF_PropertyValue p; + Bool old_strict = gf_log_set_strict_error(GF_FALSE); + gf_filter_pck_send(pck); + gf_filter_pck_set_property(pck, GF_4CC('c','u','s','t'), &p); + gf_filter_pck_merge_properties(pck, pck); + gf_filter_pck_set_framing(pck, GF_TRUE, GF_FALSE); + gf_log_set_strict_error(old_strict); + } + + gf_sha1_update(pidctx->sha_ctx, (u8*)data, size); + + pidctx->nb_packets++; + GF_LOG(GF_LOG_DEBUG, GF_LOG_APP, ("TestSink: Consuming packet %d bytes\n", size)); + + dump_properties(pck, pidctx->nb_packets); + + gf_filter_pid_drop_packet(pidctx->src_pid); + + } //end PID loop + + if (nb_eos==count) return GF_EOS; + + return GF_OK; +} + + +static GF_Err ut_filter_config_input(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *format; + GF_PropertyValue p; + PIDCtx *pidctx; + u32 i, count; + GF_UnitTestFilter *stack = (GF_UnitTestFilter *) gf_filter_get_udta(filter); + + if (stack->mode==UTF_MODE_SOURCE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error: Attempt to connect PID on source filter\n")); + return GF_BAD_PARAM; + } + //for both filter and sink modes, check input format + + count = gf_list_count(stack->pids); + for (i=0; ipids, i); + + //something is being reconfigured. We check we have the same custum arg, otherwise we do not support + if (pidctx->src_pid == pid) { + format = gf_filter_pid_get_property(pidctx->src_pid, GF_4CC('c','u','s','t') ); + if (!format || !format->value.string || strcmp(format->value.string, stack->pid_att)) { + return GF_NOT_SUPPORTED; + } + //filter mode, set properties on output + if (stack->mode==UTF_MODE_FILTER) { + //this is not needed since copy_properties does that, used for coverage/tests + gf_filter_pid_reset_properties(pidctx->dst_pid); + gf_filter_pid_copy_properties(pidctx->dst_pid, pidctx->src_pid); + } + return GF_OK; + } + } + + //check our functions + format = gf_filter_pid_get_property_str(pid, "custom1"); + if (!format) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("%s: expecting property string custom1 on PID\n", gf_filter_get_name(filter) )); + } + format = gf_filter_pid_get_property_str(pid, "custom2"); + if (!format) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("%s: expecting property string custom2 on PID\n", gf_filter_get_name(filter) )); + } + + format = gf_filter_pid_get_property(pid, GF_4CC('c','u','s','t') ); + if (!format || !format->value.string || strcmp(format->value.string, stack->pid_att)) { + return GF_NOT_SUPPORTED; + } + + //new PID + GF_SAFEALLOC(pidctx, PIDCtx); + if (!pidctx) return GF_OUT_OF_MEM; + pidctx->src_pid = pid; + gf_list_add(stack->pids, pidctx); + assert(pidctx->src_pid); + + //coverage mode + if (stack->cov) { + u8 *data; + Bool old_strict = gf_log_set_strict_error(GF_FALSE); + gf_filter_pid_set_property(pidctx->src_pid, GF_4CC('s','h','a','1'), format); + gf_filter_pid_reset_properties(pidctx->src_pid); + gf_filter_pck_new_alloc(pidctx->src_pid, 20, &data); + gf_filter_pck_new_shared(pidctx->src_pid, "foo", 3, NULL); + gf_filter_pck_new_ref(pidctx->src_pid, "foo", 3, NULL); + gf_log_set_strict_error(old_strict); + } + + //filter mode, setup output + if (stack->mode==UTF_MODE_FILTER) { + pidctx->dst_pid = gf_filter_pid_new(filter); + p.type=GF_PROP_NAME; + p.value.string = (char *) stack->pid_att; + gf_filter_pid_copy_properties(pidctx->dst_pid, pidctx->src_pid); + + if (stack->cov) { + Bool old_strict = gf_log_set_strict_error(GF_FALSE); + gf_filter_pid_copy_properties(pidctx->src_pid, pidctx->dst_pid); + gf_filter_pid_get_packet(pidctx->dst_pid); + gf_filter_pid_drop_packet(pidctx->dst_pid); + gf_filter_pid_drop_packet(pidctx->src_pid); + gf_log_set_strict_error(old_strict); + } + + gf_filter_pid_set_property(pidctx->dst_pid, GF_4CC('c','u','s','t'), &p); + + gf_filter_pid_set_udta(pidctx->dst_pid, pidctx); + gf_filter_pid_set_udta(pidctx->src_pid, pidctx); + + gf_filter_pid_set_framing_mode(pidctx->src_pid, GF_TRUE); + pidctx->sha_ctx = gf_sha1_starts(); + } + //sink mode, request full reconstruction of input blocks or not depending on framing mode + else { + GF_FilterEvent evt; + gf_filter_pid_set_framing_mode(pidctx->src_pid, stack->framing ? GF_FALSE : GF_TRUE); + pidctx->sha_ctx = gf_sha1_starts(); + GF_FEVT_INIT(evt, GF_FEVT_PLAY, pid); + gf_filter_pid_send_event(pid, &evt); + } + + return GF_OK; +} + + +static GF_Err ut_filter_config_source(GF_Filter *filter) +{ + GF_PropertyValue p; + PIDCtx *pidctx; + u32 i; + GF_UnitTestFilter *stack = (GF_UnitTestFilter *) gf_filter_get_udta(filter); + + for (i=0; inb_pids; i++) { + //create a pid + GF_SAFEALLOC(pidctx, PIDCtx); + if (!pidctx) return GF_OUT_OF_MEM; + gf_list_add(stack->pids, pidctx); + pidctx->dst_pid = gf_filter_pid_new(filter); + gf_filter_pid_set_udta(pidctx->dst_pid, pidctx); + + //set a custum property + p.type = GF_PROP_NAME; + p.value.string = (char *) stack->pid_att; + gf_filter_pid_set_property(pidctx->dst_pid, GF_4CC('c','u','s','t'), &p); + + //for coverage + gf_filter_pid_set_property_str(pidctx->dst_pid, "custom1", &p); + gf_filter_pid_set_property_dyn(pidctx->dst_pid, "custom2", &p); + + if (stack->cov) { + Bool old_strict = gf_log_set_strict_error(GF_FALSE); + gf_filter_pid_set_framing_mode(pidctx->dst_pid, GF_TRUE); + gf_log_set_strict_error(old_strict); + } + + pidctx->sha_ctx = gf_sha1_starts(); + + if (stack->gsftest) { + gf_filter_pid_set_property(pidctx->dst_pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_VISUAL) ); + gf_filter_pid_set_property(pidctx->dst_pid, GF_PROP_PID_WIDTH, &PROP_UINT(5) ); + gf_filter_pid_set_property(pidctx->dst_pid, GF_PROP_PID_HEIGHT, &PROP_UINT(2) ); + gf_filter_pid_set_property(pidctx->dst_pid, GF_PROP_PID_FPS, &PROP_FRAC_INT(25,1) ); + gf_filter_pid_set_property(pidctx->dst_pid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW) ); + gf_filter_pid_set_property(pidctx->dst_pid, GF_PROP_PID_PIXFMT, &PROP_UINT(GF_PIXEL_GREYSCALE) ); + gf_filter_pid_set_property_str(pidctx->dst_pid, "gsfdummy", &p); + gf_filter_pid_set_property(pidctx->dst_pid, GF_PROP_PID_TIMESCALE, &PROP_UINT(25) ); + } + + } + return GF_OK; +} + + +static GF_Err ut_filter_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *arg_val) +{ + return GF_OK; +} + +GF_Err utfilter_initialize(GF_Filter *filter) +{ + GF_PropertyValue p; + GF_UnitTestFilter *stack = gf_filter_get_udta(filter); + + stack->pids = gf_list_new(); + + if (stack->cov) { + Bool old_strict; + char szFmt[40]; + s64 val; + u32 i; + GF_PropertyValue p2; + p = gf_props_parse_value(GF_PROP_BOOL, "prop", "true", NULL, 0); + if (p.value.boolean != GF_TRUE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing boolean value\n")); + } + p = gf_props_parse_value(GF_PROP_BOOL, "prop", "yes", NULL, 0); + if (p.value.boolean != GF_TRUE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing boolean value\n")); + } + p = gf_props_parse_value(GF_PROP_BOOL, "prop", "no", NULL, 0); + if (p.value.boolean != GF_FALSE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing boolean value\n")); + } + p = gf_props_parse_value(GF_PROP_BOOL, "prop", "false", NULL, 0); + if (p.value.boolean != GF_FALSE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing boolean value\n")); + } + p = gf_props_parse_value(GF_PROP_SINT, "prop", "-1", NULL, 0); + if (p.value.sint != -1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing sint value\n")); + } + p = gf_props_parse_value(GF_PROP_SINT, "prop", "-1k", NULL, 0); + if (p.value.sint != -1000) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing sint value\n")); + } + p = gf_props_parse_value(GF_PROP_UINT, "prop", "1", NULL, 0); + if (p.value.uint != 1) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing uint value\n")); + } + p = gf_props_parse_value(GF_PROP_UINT, "prop", "1m", NULL, 0); + if (p.value.uint != 1000000) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing uint 1m value\n")); + } + p = gf_props_parse_value(GF_PROP_UINT, "prop", "0x10000000", NULL, 0); + if (p.value.uint != 0x10000000) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing uint hex value\n")); + } + p = gf_props_parse_value(GF_PROP_UINT, "prop", "moof", NULL, 0); + if (p.value.uint != GF_4CC('m','o','o','f')) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing uint 4CC value\n")); + } + val = 0xFFFFFFFF; + val *= 2; + sprintf(szFmt, ""LLD, -val); + p = gf_props_parse_value(GF_PROP_LSINT, "prop", szFmt, NULL, 0); + if (p.value.longsint != -val) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing longsint value\n")); + } + p = gf_props_parse_value(GF_PROP_LSINT, "prop", "-1m", NULL, 0); + if (p.value.longsint != -1000000) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing longsint value\n")); + } + sprintf(szFmt, ""LLU, val); + p = gf_props_parse_value(GF_PROP_LUINT, "prop", szFmt, NULL, 0); + if (p.value.longuint != val) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing longuint value\n")); + } + p = gf_props_parse_value(GF_PROP_LUINT, "prop", "1k", NULL, 0); + if (p.value.longuint != 1000) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing longuint value\n")); + } + p = gf_props_parse_value(GF_PROP_FLOAT, "prop", "1.0", NULL, 0); + if (p.value.fnumber != FIX_ONE) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing float value\n")); + } + p = gf_props_parse_value(GF_PROP_DOUBLE, "prop", "1.0", NULL, 0); + if (p.value.number != 1.0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing double value\n")); + } + p = gf_props_parse_value(GF_PROP_DOUBLE, "prop", "1.0m", NULL, 0); + if (p.value.number != 1000000.0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing double value\n")); + } + p = gf_props_parse_value(GF_PROP_FRACTION, "prop", "1000/1", NULL, 0); + if ((p.value.frac.den != 1) || (p.value.frac.num != 1000)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing fraction value\n")); + } + p = gf_props_parse_value(GF_PROP_FRACTION, "prop", "1000", NULL, 0); + if ((p.value.frac.den != 1) || (p.value.frac.num != 1000)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing fraction value\n")); + } + p = gf_props_parse_value(GF_PROP_FRACTION, "prop", "1.001", NULL, 0); + if ((p.value.frac.den != 1000000) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing fraction fp value\n")); + } + p = gf_props_parse_value(GF_PROP_STRING, "prop", "test", NULL, 0); + if (!p.value.string || strcmp(p.value.string, "test")) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing fraction value\n")); + } + if (p.value.string) gf_free(p.value.string); + + p = gf_props_parse_value(GF_PROP_FRACTION64, "prop", "1.001", NULL, 0); + if ((p.value.lfrac.den != 1000000) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing fraction64 fp value\n")); + } + p = gf_props_parse_value(GF_PROP_VEC2I, "prop", "1x1", NULL, 0); + if ((p.value.vec2i.x != 1) || (p.value.vec2i.y != 1) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing vec2i value\n")); + } + p = gf_props_parse_value(GF_PROP_VEC2, "prop", "1x1", NULL, 0); + if ((p.value.vec2.x != 1.0) || (p.value.vec2.y != 1.0) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing vec2 value\n")); + } + p = gf_props_parse_value(GF_PROP_VEC3I, "prop", "1x1x1", NULL, 0); + if ((p.value.vec3i.x != 1) || (p.value.vec3i.y != 1) || (p.value.vec3i.z != 1)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing vec3i value\n")); + } + p = gf_props_parse_value(GF_PROP_VEC3, "prop", "1x1x1", NULL, 0); + if ((p.value.vec3.x != 1.0) || (p.value.vec3.y != 1.0) || (p.value.vec3.z != 1.0)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing vec3 value\n")); + } + p = gf_props_parse_value(GF_PROP_VEC4I, "prop", "1x1x1x1", NULL, 0); + if ((p.value.vec4i.x != 1) || (p.value.vec4i.y != 1) || (p.value.vec4i.z != 1) || (p.value.vec4i.w != 1)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing vec4i value\n")); + } + p = gf_props_parse_value(GF_PROP_VEC4, "prop", "1x1x1x1", NULL, 0); + if ((p.value.vec4.x != 1.0) || (p.value.vec4.y != 1.0) || (p.value.vec4.z != 1.0) || (p.value.vec4.w != 1.0)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing vec4 value\n")); + } + p = gf_props_parse_value(GF_PROP_PIXFMT, "prop", "rgb", NULL, 0); + if (p.value.uint != GF_PIXEL_RGB) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing pixfmt value\n")); + } + p = gf_props_parse_value(GF_PROP_PCMFMT, "prop", "pcm", NULL, 0); + if (p.value.uint != GF_AUDIO_FMT_S16) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing pcmfmt value\n")); + } + + sprintf(szFmt, "%d@%p", (u32) sizeof(stack), stack); + p = gf_props_parse_value(GF_PROP_DATA, "prop", szFmt, NULL, 0); + if ((p.value.data.size != (u32) sizeof(stack)) || memcmp(p.value.data.ptr, (char *) stack, sizeof(stack))) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing data value\n")); + } + p = gf_props_parse_value(GF_PROP_CONST_DATA, "prop", szFmt, NULL, 0); + if ((p.value.data.ptr != (u8 *) stack) || (p.value.data.size != (u32) sizeof(stack))) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing data value\n")); + } + p = gf_props_parse_value(GF_PROP_CONST_DATA, "prop", "0xABCDEF", NULL, 0); + if (!p.value.data.ptr || (p.value.data.size != 3) || (p.value.data.ptr[0] != 0xAB) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing data value\n")); + } + if (p.value.data.ptr) gf_free(p.value.data.ptr); + + sprintf(szFmt, "%p", stack); + p = gf_props_parse_value(GF_PROP_POINTER, "prop", szFmt, NULL, 0); + if (p.value.ptr != stack) { + GF_LOG(GF_LOG_ERROR, GF_LOG_APP, ("[UTFilter] Error parsing data value\n")); + } + + old_strict = gf_log_set_strict_error(GF_FALSE); + //negative tests + gf_props_parse_value(GF_PROP_STRING, "prop", "file@_no_exist", NULL, 0); + gf_props_parse_value(GF_PROP_STRING, "prop", "bxml@_no_exist", NULL, 0); + gf_props_parse_value(GF_PROP_DATA, "prop", "file@_no_exist", NULL, 0); + gf_props_parse_value(GF_PROP_DATA, "prop", "bxml@_no_exist", NULL, 0); + gf_props_parse_value(GF_PROP_BOOL, "prop", "", NULL, 0); + gf_props_parse_value(GF_PROP_SINT, "prop", "", NULL, 0); + gf_props_parse_value(GF_PROP_UINT, "prop", "", NULL, 0); + gf_props_parse_value(GF_PROP_LSINT, "prop", "", NULL, 0); + gf_props_parse_value(GF_PROP_LUINT, "prop", "", NULL, 0); + gf_props_parse_value(GF_PROP_FLOAT, "prop", "", NULL, 0); + gf_props_parse_value(GF_PROP_DOUBLE, "prop", "", NULL, 0); + gf_props_parse_value(GF_PROP_FRACTION, "prop", "", NULL, 0); + gf_props_parse_value(GF_PROP_FRACTION, "prop", "", NULL, 0); + gf_props_parse_value(GF_PROP_STRING, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_DATA, "prop", "", NULL, 0); + gf_props_parse_value(GF_PROP_CONST_DATA, "prop", "", NULL, 0); + gf_props_parse_value(GF_PROP_POINTER, "prop", "", NULL, 0); + gf_props_parse_value(GF_PROP_BOOL, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_SINT, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_UINT, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_LSINT, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_LUINT, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_FLOAT, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_DOUBLE, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_FRACTION, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_FRACTION64, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_VEC2I, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_VEC2, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_VEC3I, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_VEC3, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_VEC4I, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_VEC4, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_STRING, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_DATA, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_CONST_DATA, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_POINTER, "prop", NULL, NULL, 0); + gf_props_parse_value(GF_PROP_UINT, "prop", "test", "foo|bar", 0); + gf_props_parse_value(100, "prop", "test", NULL, 0); + + memset(&p, 0, sizeof(GF_PropertyValue)); + p2=p; + for (i=GF_PROP_FORBIDEN; imode=UTF_MODE_SINK; + gf_filter_set_max_extra_input_pids(filter, 10); + } + else if (! strcmp( "UTFilter", gf_filter_get_name(filter))) stack->mode=UTF_MODE_FILTER; + else { + stack->mode=UTF_MODE_SOURCE; + return ut_filter_config_source(filter); + } + return GF_OK; +} + +#define OFFS(_n) #_n, offsetof(GF_UnitTestFilter, _n) +static const GF_FilterArgs UTFilterArgs[] = +{ + { OFFS(pid_att), "set default value for PID `cust` attribute", GF_PROP_NAME, "UTSourceData", NULL, 0}, + { OFFS(max_pck), "maximum number of packets to send in source mode", GF_PROP_UINT, "1000", NULL, 0}, + { OFFS(nb_pids), "number of PIDs in source mode", GF_PROP_UINT, "1", "1-+I", 0}, + { OFFS(max_out), "maximum number of shared packets not yet released in source/filter mode, no limit if -1", GF_PROP_SINT, "-1", NULL, GF_FS_ARG_UPDATE}, + { OFFS(alloc), "use allocated memory packets in source mode", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE}, + { OFFS(fwd), "indicate packet forward mode for filter.\n" + "- shared: use shared memory (dangerous)\n" + "- copy: use copy\n" + "- ref: use references to source packet\n" + "- mix: change mode at each packet sent", GF_PROP_UINT, "shared", "shared|copy|ref|mix", GF_FS_ARG_UPDATE}, + { OFFS(framing), "packet framing.\n" + "- none: disable packet split\n" + "- default: divide packets in 3 for filter mode and allows partial blocks for sink mode\n" + "- nostart: same as default but does not signal packet start flag\n" + "- noend: same as default but does not signal packet end flag" + "", GF_PROP_UINT, "none", "none|default|nostart|noend", GF_FS_ARG_UPDATE}, + { OFFS(update), "send update message after half packet send. Update format is FID,argname,argval", GF_PROP_STRING, NULL, NULL, GF_FS_ARG_UPDATE}, + { OFFS(cov), "dump options and exercise error cases for code coverage", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE}, + { OFFS(norecfg), "disable reconfig on input pid in filter/sink mode", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE}, + { OFFS(gsftest), "dispatch a fake single video pid with props and packet props for GSF testing", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE}, + + { OFFS(dummy1), "dummy for coverage", GF_PROP_LSINT, "0", NULL, GF_FS_ARG_UPDATE}, + { OFFS(dummy1), "dummy for coverage", GF_PROP_LUINT, "0", NULL, GF_FS_ARG_UPDATE}, + { OFFS(dummy1), "dummy for coverage", GF_PROP_FLOAT, "0", NULL, GF_FS_ARG_UPDATE}, + { OFFS(dummy1), "dummy for coverage", GF_PROP_DOUBLE, "0", NULL, GF_FS_ARG_UPDATE}, + { OFFS(dummy1), "dummy for coverage", GF_PROP_FRACTION, "0", NULL, GF_FS_ARG_UPDATE}, + { OFFS(dummy1), "dummy for coverage", GF_PROP_POINTER, "0", NULL, GF_FS_ARG_UPDATE}, + { OFFS(dummy1), "dummy for coverage", GF_PROP_FRACTION64, "0", NULL, GF_FS_ARG_UPDATE}, + { NULL } +}; + +#define UT_CAP_CODE GF_4CC('c','u','s','t') +static const GF_FilterCapability UTFilterCaps[] = +{ + CAP_STRING(GF_CAPS_INPUT, UT_CAP_CODE, "UTSourceData"), + CAP_STRING(GF_CAPS_INPUT, UT_CAP_CODE, "UTFilterData"), + CAP_STRING(GF_CAPS_OUTPUT, UT_CAP_CODE, "UTSourceData"), + CAP_STRING(GF_CAPS_OUTPUT, UT_CAP_CODE, "UTFilterData"), +}; + +static const GF_FilterCapability UTSinkInputs[] = +{ + CAP_STRING(GF_CAPS_INPUT, UT_CAP_CODE, "UTSourceData"), +}; + +static const GF_FilterCapability UTSink2Inputs[] = +{ + CAP_STRING(GF_CAPS_INPUT, UT_CAP_CODE, "UTFilterData"), +}; + +static const GF_FilterCapability UTSourceOutputs[] = +{ + CAP_STRING(GF_CAPS_OUTPUT, UT_CAP_CODE, "UTSourceData"), +}; + + +const GF_FilterRegister UTFilterRegister = { + .name = "UTFilter", + GF_FS_SET_DESCRIPTION("Unit Test Filter") + GF_FS_SET_HELP("This filter is only used for unit testing of filter framework") + .private_size = sizeof(GF_UnitTestFilter), + .flags = GF_FS_REG_EXPLICIT_ONLY, + SETCAPS( UTFilterCaps), + .args = UTFilterArgs, + .initialize = utfilter_initialize, + .finalize = ut_filter_finalize, + .process = ut_filter_process_filter, + .configure_pid = ut_filter_config_input, + .update_arg = ut_filter_update_arg +}; + + +const GF_FilterRegister UTSinkRegister = { + .name = "UTSink", + GF_FS_SET_DESCRIPTION("Unit Test Sink") + GF_FS_SET_HELP("This filter is only used for unit testing of filter framework") + .private_size = sizeof(GF_UnitTestFilter), + .flags = GF_FS_REG_EXPLICIT_ONLY, + SETCAPS(UTSinkInputs), + .args = UTFilterArgs, + .initialize = utfilter_initialize, + .finalize = ut_filter_finalize, + .process = ut_filter_process_sink, + .configure_pid = ut_filter_config_input, + .update_arg = ut_filter_update_arg +}; + +const GF_FilterRegister UTSink2Register = { + .name = "UTSink2", + GF_FS_SET_DESCRIPTION("Unit Test Sink2") + GF_FS_SET_HELP("This filter is only used for unit testing of filter framework") + .private_size = sizeof(GF_UnitTestFilter), + .flags = GF_FS_REG_EXPLICIT_ONLY, + SETCAPS(UTSink2Inputs), + .args = UTFilterArgs, + .initialize = utfilter_initialize, + .finalize = ut_filter_finalize, + .process = ut_filter_process_sink, + .configure_pid = ut_filter_config_input, + .update_arg = ut_filter_update_arg +}; + +const GF_FilterRegister UTSourceRegister = { + .name = "UTSource", + GF_FS_SET_DESCRIPTION("Unit Test Source") + GF_FS_SET_HELP("This filter is only used for unit testing of filter framework") + .private_size = sizeof(GF_UnitTestFilter), + .flags = GF_FS_REG_EXPLICIT_ONLY, + SETCAPS(UTSourceOutputs), + .args = UTFilterArgs, + .initialize = utfilter_initialize, + .finalize = ut_filter_finalize, + .process = ut_filter_process_source, + .update_arg = ut_filter_update_arg +}; + + +const GF_FilterRegister *ut_filter_register(GF_FilterSession *session, Bool load_meta_filters) +{ + return &UTFilterRegister; +} +const GF_FilterRegister *ut_source_register(GF_FilterSession *session, Bool load_meta_filters) +{ + return &UTSourceRegister; +} +const GF_FilterRegister *ut_sink_register(GF_FilterSession *session, Bool load_meta_filters) +{ + return &UTSinkRegister; +} +const GF_FilterRegister *ut_sink2_register(GF_FilterSession *session, Bool load_meta_filters) +{ + return &UTSink2Register; +} + + + diff --git a/src/filters/vcrop.c b/src/filters/vcrop.c new file mode 100644 index 0000000..0bc779f --- /dev/null +++ b/src/filters/vcrop.c @@ -0,0 +1,626 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2018-2020 + * All rights reserved + * + * This file is part of GPAC / video cropping filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +typedef struct +{ + //options + const char *wnd; + Bool copy; + u32 round; + + //internal data + Bool initialized; + + GF_FilterPid *ipid, *opid; + u32 w, h, stride, s_pfmt; + GF_Fraction ar; + Bool passthrough; + + u32 dst_stride[5]; + u32 src_stride[5]; + u32 nb_planes, nb_src_planes, out_size, out_src_size, src_uv_height, dst_uv_height; + + Bool use_reference; + u32 dst_width, dst_height; + s32 src_x, src_y; + Bool packed_422; + + GF_List *frames, *frames_res; +} GF_VCropCtx; + +typedef struct +{ + GF_FilterFrameInterface frame_ifce; + + //reference to the source packet + GF_FilterPacket *pck; + u8 *planes[5]; + u32 stride[5]; + + GF_VCropCtx *ctx; +} GF_VCropFrame; + + +GF_Err vcrop_frame_get_plane(GF_FilterFrameInterface *frame, u32 plane_idx, const u8 **outPlane, u32 *outStride) +{ + GF_VCropFrame *vframe = frame->user_data; + + if (plane_idx>=vframe->ctx->nb_planes) return GF_BAD_PARAM; + if (outPlane) *outPlane = vframe->planes[plane_idx]; + if (outStride) *outStride = vframe->stride[plane_idx]; + return GF_OK; +} + +void vcrop_packet_destruct(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck) +{ + GF_VCropFrame *vframe; + GF_FilterFrameInterface *frame_ifce = gf_filter_pck_get_frame_interface(pck); + if (!frame_ifce) return; + vframe = frame_ifce->user_data; + assert(vframe->pck); + gf_filter_pck_unref(vframe->pck); + vframe->pck = NULL; + gf_list_add(vframe->ctx->frames_res, vframe); +} + + +static GF_Err vcrop_process(GF_Filter *filter) +{ + const u8 *data; + u8 *output; + u32 size; + u32 bps; + u32 s_off_x, s_off_y, d_off_x, d_off_y, i, copy_w, copy_h; + u8 *src, *dst; + u8 *src_planes[5]; + u8 *dst_planes[5]; + Bool do_memset = GF_FALSE; + GF_FilterPacket *dst_pck; + GF_FilterFrameInterface *frame_ifce; + GF_VCropCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->ipid); + + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + if (ctx->passthrough) { + gf_filter_pck_forward(pck, ctx->opid); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + data = gf_filter_pck_get_data(pck, &size); + frame_ifce = gf_filter_pck_get_frame_interface(pck); + + memset(src_planes, 0, sizeof(src_planes)); + memset(dst_planes, 0, sizeof(dst_planes)); + + if (data) { + src_planes[0] = (u8 *) data; + + if (ctx->nb_src_planes==1) { + } else if (ctx->nb_src_planes==2) { + src_planes[1] = src_planes[0] + ctx->src_stride[0]*ctx->h; + } else if (ctx->nb_src_planes==3) { + src_planes[1] = src_planes[0] + ctx->src_stride[0] * ctx->h; + src_planes[2] = src_planes[1] + ctx->src_stride[1] * ctx->src_uv_height; + } else if (ctx->nb_src_planes==4) { + src_planes[1] = src_planes[0] + ctx->src_stride[0] * ctx->h; + src_planes[2] = src_planes[1] + ctx->src_stride[1] * ctx->src_uv_height; + src_planes[3] = src_planes[2] + ctx->src_stride[2] * ctx->src_uv_height; + } + } else if (frame_ifce && frame_ifce->get_plane) { + for (i=0; i<4; i++) { + if (frame_ifce->get_plane(frame_ifce, i, (const u8 **) &src_planes[i], &ctx->src_stride[i])!=GF_OK) + break; + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[VCrop] No data associated with packet, not supported\n")); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NOT_SUPPORTED; + } + + bps = gf_pixel_get_bytes_per_pixel(ctx->s_pfmt); + + copy_w = ctx->dst_width; + copy_h = ctx->dst_height; + s_off_x = s_off_y = d_off_x = d_off_y = 0; + if (ctx->src_x>=0) { + s_off_x = ctx->src_x; + } else { + d_off_x = (u32) (-ctx->src_x); + copy_w = (s32) ctx->dst_width + ctx->src_x; + do_memset = GF_TRUE; + } + if (s_off_x + copy_w > ctx->w) { + copy_w = ctx->w - s_off_x; + do_memset = GF_TRUE; + } + if (ctx->src_y>=0) { + s_off_y = ctx->src_y; + } else { + d_off_y = (u32) (-ctx->src_y); + copy_h = (s32) ctx->dst_height + ctx->src_y; + do_memset = GF_TRUE; + } + if (s_off_y + copy_h > ctx->h) { + copy_h = ctx->h - s_off_y; + do_memset = GF_TRUE; + } + + if (ctx->use_reference) { + GF_VCropFrame *vframe = gf_list_pop_back(ctx->frames_res); + if (!vframe) { + GF_SAFEALLOC(vframe, GF_VCropFrame); + if (!vframe) return GF_OUT_OF_MEM; + } + vframe->ctx = ctx; + memcpy(vframe->stride, ctx->src_stride, sizeof(vframe->stride)); + if (ctx->packed_422) { + vframe->planes[0] = src_planes[0] + s_off_x * bps * 2 + ctx->src_stride[0] * s_off_y; + } else { + vframe->planes[0] = src_planes[0] + s_off_x * bps + ctx->src_stride[0] * s_off_y; + } + //nv12/21 + if (ctx->nb_planes==2) { + vframe->planes[1] = src_planes[1] + s_off_x * bps + ctx->src_stride[1] * s_off_y/2; + } else if (ctx->nb_planes>=3) { + u32 div_x, div_y; + //alpha/depth/other plane, treat as luma plane + if (ctx->nb_planes==4) { + vframe->planes[3] = src_planes[3] + s_off_x * bps + ctx->src_stride[3] * s_off_y; + } + div_x = (ctx->src_stride[1]==ctx->src_stride[0]) ? 1 : 2; + div_y = (ctx->src_uv_height==ctx->h) ? 1 : 2; + + vframe->planes[1] = src_planes[1] + s_off_x * bps / div_x + ctx->src_stride[1] * s_off_y / div_y; + vframe->planes[2] =src_planes[2] + s_off_x * bps / div_x + ctx->src_stride[2] * s_off_y / div_y; + } + vframe->frame_ifce.user_data = vframe; + vframe->frame_ifce.get_plane = vcrop_frame_get_plane; + dst_pck = gf_filter_pck_new_frame_interface(ctx->opid, &vframe->frame_ifce, vcrop_packet_destruct); + //keep a ref to input packet + vframe->pck = pck; + gf_filter_pck_ref(&vframe->pck); + gf_filter_pck_merge_properties(pck, dst_pck); + gf_filter_pck_send(dst_pck); + //remove input packet + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->out_size, &output); + if (!dst_pck) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OUT_OF_MEM; + } + gf_filter_pck_merge_properties(pck, dst_pck); + dst_planes[0] = output; + if (ctx->nb_planes==1) { + } else if (ctx->nb_planes==2) { + dst_planes[1] = output + ctx->dst_stride[0] * ctx->dst_height; + } else if (ctx->nb_planes==3) { + dst_planes[1] = output + ctx->dst_stride[0] * ctx->dst_height; + dst_planes[2] = dst_planes[1] + ctx->dst_stride[1]*ctx->dst_uv_height; + } else if (ctx->nb_planes==4) { + dst_planes[1] = output + ctx->dst_stride[0] * ctx->dst_height; + dst_planes[2] = dst_planes[1] + ctx->dst_stride[1]*ctx->dst_uv_height; + dst_planes[3] = dst_planes[2] + ctx->dst_stride[2]*ctx->dst_uv_height; + } else { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NOT_SUPPORTED; + } + + if (do_memset) { + memset(output, 0x00, sizeof(char)*ctx->out_size); + } + + //YUYV variations need *2 on horizontal dimension + if (ctx->packed_422) { + src = src_planes[0] + s_off_x * bps * 2 + ctx->src_stride[0] * s_off_y; + dst = dst_planes[0] + d_off_x * bps * 2 + ctx->dst_stride[0] * d_off_y; + for (i=0; isrc_stride[0]; + dst += ctx->dst_stride[0]; + } + } else { + //copy first plane + src = src_planes[0] + s_off_x * bps + ctx->src_stride[0] * s_off_y; + dst = dst_planes[0] + d_off_x * bps + ctx->dst_stride[0] * d_off_y; + for (i=0; isrc_stride[0]; + dst += ctx->dst_stride[0]; + } + } + + //nv12/21 + if (ctx->nb_planes==2) { + src = src_planes[1] + s_off_x * bps + ctx->src_stride[1] * s_off_y/2; + dst = dst_planes[1] + d_off_x * bps + ctx->dst_stride[1] * d_off_y/2; + //half vertical res (/2) + for (i=0; isrc_stride[1]; + dst += ctx->dst_stride[1]; + } + } else if ((ctx->nb_planes==3) || (ctx->nb_planes==4)) { + u32 div_x, div_y; + //alpha/depth/other plane, treat as luma plane + if (ctx->nb_planes==4) { + src = src_planes[3] + s_off_x * bps + ctx->src_stride[3] * s_off_y; + dst = dst_planes[3] + d_off_x * bps + ctx->dst_stride[3] * d_off_y; + for (i=0; isrc_stride[3]; + dst += ctx->dst_stride[3]; + } + } + + div_x = (ctx->src_stride[1]==ctx->src_stride[0]) ? 1 : 2; + div_y = (ctx->src_uv_height==ctx->h) ? 1 : 2; + + src = src_planes[1] + s_off_x * bps / div_x + ctx->src_stride[1] * s_off_y / div_y; + dst = dst_planes[1] + d_off_x * bps / div_x + ctx->dst_stride[1] * d_off_y / div_y; + copy_h /= div_y; + copy_w /= div_x; + + for (i=0; isrc_stride[1]; + dst += ctx->dst_stride[1]; + } + + src = src_planes[2] + s_off_x * bps / div_x + ctx->src_stride[2] * s_off_y / div_y; + dst = dst_planes[2] + d_off_x * bps / div_x + ctx->dst_stride[2] * d_off_y / div_y; + for (i=0; isrc_stride[1]; + dst += ctx->dst_stride[1]; + } + } + + gf_filter_pck_send(dst_pck); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + +static Bool parse_crop_props(GF_VCropCtx *ctx, u32 src_w, u32 src_h, GF_PixelFormat pfmt) +{ + s32 dim[4]; + s32 dim_off[4]; + Bool dim_pc[4]; + u32 nb_dim=0; + char *args = (char *)ctx->wnd; + + memset(dim, 0, sizeof(dim)); + memset(dim_pc, 0, sizeof(dim_pc)); + memset(dim_off, 0, sizeof(dim_off)); + + while (args) { + char *sep_offset=NULL, *sep_pc, sign=0; + char *sep = strchr(args, 'x'); + if (sep) sep[0] = 0; + + sep_pc = strchr(args, '%'); + if (sep_pc) { + sep_pc[0] = 0; + dim[nb_dim] = atoi(args); + sep_pc[0] = '%'; + dim_pc[nb_dim] = GF_TRUE; + + sep_offset = strchr(sep_pc, '+'); + if (sep_offset) { + sign = sep_offset[0]; + dim_off[nb_dim] = atoi(sep_offset+1); + sep_offset[0] = 0; + } else { + sep_offset = strchr(args, '-'); + if (sep_offset) { + sign = sep_offset[0]; + dim_off[nb_dim] = - atoi(sep_offset+1); + sep_offset[0] = 0; + } + } + + } else { + dim[nb_dim] = atoi(args); + } + + if (sep_offset) sep_offset[0] = sign; + nb_dim++; + if (!sep) break; + sep[0] = 'x'; + args = sep+1; + if (nb_dim==4) break; + } + +#define GET_DIM(_dst, _i, _s) if (dim_pc[_i]) \ + _dst = (dim[_i] * (s32) _s / 100) + dim_off[_i]; \ + else \ + _dst = dim[_i] + dim_off[_i]; \ + + + if (nb_dim==2) { + ctx->src_x = 0; + ctx->src_y = 0; + GET_DIM(ctx->dst_width, 0, src_w) + GET_DIM(ctx->dst_height, 1, src_h) + } else if (nb_dim==4) { + GET_DIM(ctx->src_x, 0, src_w) + GET_DIM(ctx->src_y, 1, src_h) + GET_DIM(ctx->dst_width, 2, src_w) + GET_DIM(ctx->dst_height, 3, src_h) + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[VCrop] Cannot parse crop parameters, expected 2 or 4 numbers, got %d\n", nb_dim)); + return GF_FALSE; + } + if ((s32) ctx->dst_width < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[VCrop] Negative destination width %d !\n", (s32) ctx->dst_width)); + return GF_FALSE; + } + if ((s32) ctx->dst_height < 0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[VCrop] Negative destination height %d !\n", (s32) ctx->dst_height)); + return GF_FALSE; + } + +#define ROUND_IT(_a) { if ((ctx->round==0) || (ctx->round==2)) { (_a)++; } else { (_a)--; } } + + //for YUV 420, adjust to multiple of 2 on both dim + ctx->packed_422 = GF_FALSE; + switch (pfmt) { + case GF_PIXEL_YUV: + case GF_PIXEL_NV21: + case GF_PIXEL_NV21_10: + case GF_PIXEL_NV12: + case GF_PIXEL_NV12_10: + case GF_PIXEL_YUVA: + case GF_PIXEL_YUVD: + case GF_PIXEL_YUV_10: + if (ctx->src_x % 2) ROUND_IT(ctx->src_x); + if (ctx->src_y % 2) ROUND_IT(ctx->src_y); + if ((ctx->src_x + ctx->dst_width) % 2) ROUND_IT(ctx->dst_width); + if ((ctx->src_y + ctx->dst_height) % 2) ROUND_IT(ctx->dst_height); + break; + //for YUV 422, adjust to multiple of 2 on horizontal dim + case GF_PIXEL_YUYV: + case GF_PIXEL_YVYU: + case GF_PIXEL_UYVY: + case GF_PIXEL_VYUY: + ctx->packed_422 = GF_TRUE; + case GF_PIXEL_YUV422: + case GF_PIXEL_YUV422_10: + if (ctx->src_x % 2) ROUND_IT(ctx->src_x); + if ((ctx->src_x + ctx->dst_width) % 2) ROUND_IT(ctx->dst_width); + if (ctx->round>1) { + if (ctx->src_y % 2) ROUND_IT(ctx->src_y); + if ((ctx->src_y + ctx->dst_height) % 2) ROUND_IT(ctx->dst_height); + } + break; + default: + if (ctx->round>1) { + if (ctx->src_x % 2) ROUND_IT(ctx->src_x); + if (ctx->src_y % 2) ROUND_IT(ctx->src_y); + if ((ctx->src_x + ctx->dst_width) % 2) ROUND_IT(ctx->dst_width); + if ((ctx->src_y + ctx->dst_height) % 2) ROUND_IT(ctx->dst_height); + } + break; + } + return GF_TRUE; +} + +static GF_Err vcrop_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + GF_PropVec2i vec; + const GF_PropertyValue *p; + u32 w, h, stride, pfmt; + GF_Fraction sar; + GF_VCropCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + if (ctx->opid) { + gf_filter_pid_remove(ctx->opid); + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, pid); + + if (!ctx->ipid) { + ctx->ipid = pid; + } + w = h = pfmt = stride = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + if (p) w = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + if (p) h = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE); + if (p) stride = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); + if (p) pfmt = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); + if (p) sar = p->value.frac; + else sar.den = sar.num = 1; + + if (!w || !h || !pfmt) { + ctx->passthrough = GF_TRUE; + return GF_OK; + } + ctx->passthrough = GF_FALSE; + + if ((ctx->w == w) && (ctx->h == h) && (ctx->s_pfmt == pfmt) && (ctx->stride == stride)) { + //nothing to reconfigure + } else { + Bool res; + if (! parse_crop_props(ctx, w, h, pfmt)) { + return GF_BAD_PARAM; + } + + if (ctx->src_x<0) ctx->use_reference = GF_FALSE; + else if (ctx->src_y<0) ctx->use_reference = GF_FALSE; + else if (ctx->src_x + ctx->dst_width > w) ctx->use_reference = GF_FALSE; + else if (ctx->src_y + ctx->dst_height > h) ctx->use_reference = GF_FALSE; + else ctx->use_reference = ctx->copy ? GF_FALSE : GF_TRUE; + + if (ctx->use_reference) { + if (!ctx->frames) ctx->frames = gf_list_new(); + if (!ctx->frames_res) ctx->frames_res = gf_list_new(); + } + + //get layout info for source + memset(ctx->src_stride, 0, sizeof(ctx->src_stride)); + if (ctx->stride) ctx->src_stride[0] = ctx->stride; + + res = gf_pixel_get_size_info(pfmt, w, h, &ctx->out_src_size, &ctx->src_stride[0], &ctx->src_stride[1], &ctx->nb_src_planes, &ctx->src_uv_height); + if (!res) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[VCrop] Failed to query source pixel format characteristics\n")); + return GF_NOT_SUPPORTED; + } + if (ctx->nb_src_planes==3) ctx->src_stride[2] = ctx->src_stride[1]; + if (ctx->nb_src_planes==4) ctx->src_stride[3] = ctx->src_stride[0]; + + + //get layout info for dest + memset(ctx->dst_stride, 0, sizeof(ctx->dst_stride)); + res = gf_pixel_get_size_info(pfmt, ctx->dst_width, ctx->dst_height, &ctx->out_size, &ctx->dst_stride[0], &ctx->dst_stride[1], &ctx->nb_planes, &ctx->dst_uv_height); + if (!res) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[VCrop] Failed to query output pixel format characteristics\n")); + return GF_NOT_SUPPORTED; + } + if (ctx->nb_planes==3) ctx->dst_stride[2] = ctx->dst_stride[1]; + if (ctx->nb_planes==4) ctx->dst_stride[3] = ctx->dst_stride[0]; + + + ctx->w = w; + ctx->h = h; + ctx->s_pfmt = pfmt; + + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[VCrop] Configured output window to crop %dx%dx%dx%d from full frame size %dx%d\n", ctx->src_x, ctx->src_y, ctx->dst_width, ctx->dst_height, ctx->w, ctx->h)); + + if (!ctx->src_x && !ctx->src_y && (ctx->dst_width==ctx->w) && (ctx->dst_height==ctx->h) ) { + ctx->passthrough = GF_TRUE; + } + } + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->dst_width)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->dst_height)); + if (ctx->use_reference) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->src_stride[0] )); + if (ctx->nb_planes>1) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE_UV, &PROP_UINT(ctx->src_stride[1])); + } else { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->dst_stride[0] )); + if (ctx->nb_planes>1) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE_UV, &PROP_UINT(ctx->dst_stride[1])); + } + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, &PROP_FRAC(sar) ); + vec.x = ctx->src_x; + vec.y = ctx->src_y; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CROP_POS, &PROP_VEC2I(vec) ); + vec.x = ctx->w; + vec.y = ctx->h; + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_ORIG_SIZE, &PROP_VEC2I(vec) ); + + return GF_OK; +} + +void vcrop_finalize(GF_Filter *filter) +{ + GF_VCropCtx *ctx = gf_filter_get_udta(filter); + if (ctx->frames) { + while (gf_list_count(ctx->frames)) { + GF_VCropFrame *vframe = gf_list_pop_back(ctx->frames); + gf_free(vframe); + } + gf_list_del(ctx->frames); + } + if (ctx->frames_res) { + while (gf_list_count(ctx->frames_res)) { + GF_VCropFrame *vframe = gf_list_pop_back(ctx->frames_res); + gf_free(vframe); + } + gf_list_del(ctx->frames_res); + } +} + + +#define OFFS(_n) #_n, offsetof(GF_VCropCtx, _n) +static GF_FilterArgs VCropArgs[] = +{ + { OFFS(wnd), "size of output to crop, indicated as TxLxWxH. If % is indicated after a number, the value is in percent of the source width (for L and W) or height (for T and H). An absolute offset (+x, -x) can be added after percent", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(copy), "copy the source pixels. By default the filter will try to forward crop frames by adjusting offsets and strides of the source if possible (window contained in frame)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(round), "adjust dimension to be a multiple of 2\n" + "- up: up rounding\n" + "- down: down rounding\n" + "- allup: up rounding on formats that do not require it (RGB, YUV444)\n" + "- alldown: down rounding on formats that do not require it (RGB, YUV444)", GF_PROP_UINT, "up", "up|down|allup|alldown", GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + +static const GF_FilterCapability VCropCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW), +}; + +GF_FilterRegister VCropRegister = { + .name = "vcrop", + GF_FS_SET_DESCRIPTION("Video crop") + GF_FS_SET_HELP("This filter is used to crop raw video data.") + .private_size = sizeof(GF_VCropCtx), + .flags = GF_FS_REG_EXPLICIT_ONLY, + .args = VCropArgs, + .configure_pid = vcrop_configure_pid, + SETCAPS(VCropCaps), + .process = vcrop_process, + .finalize = vcrop_finalize, +}; + + + +const GF_FilterRegister *vcrop_register(GF_FilterSession *session) +{ + return &VCropRegister; +} diff --git a/src/filters/vflip.c b/src/filters/vflip.c new file mode 100644 index 0000000..c493fa3 --- /dev/null +++ b/src/filters/vflip.c @@ -0,0 +1,511 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Samir Mustapha - Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2019 + * All rights reserved + * + * This file is part of GPAC / video flip filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +typedef struct +{ + //options + u32 mode; + + //internal data + Bool initialized; + + GF_FilterPid *ipid, *opid; + u32 w, h, stride, s_pfmt; + u32 bps; + GF_Fraction ar; + Bool passthrough; + u32 dst_width, dst_height; + + u32 dst_stride[5]; + u32 src_stride[5]; + u32 nb_planes, nb_src_planes, out_size, out_src_size, src_uv_height, dst_uv_height; + + Bool use_reference; + Bool packed_422; + + + char *line_buffer_vf; //vertical flip + char *line_buffer_hf; //horizontal flip + +} GF_VFlipCtx; + +enum +{ + VFLIP_OFF = 0, + VFLIP_VERT, + VFLIP_HORIZ, + VFLIP_BOTH, +}; + + + +static void swap_2Ys_YUVpixel(GF_VFlipCtx *ctx, u8 *line_src, u8 *line_dst, u32 FourBytes_start_index) +{ + u32 isFirstY_indexOne; + switch (ctx->s_pfmt) { + case GF_PIXEL_YUYV: + case GF_PIXEL_YVYU: + isFirstY_indexOne= (u32) 0; + break; + case GF_PIXEL_UYVY: + case GF_PIXEL_VYUY: + isFirstY_indexOne= (u32) 1; + break; + default: + return; + } + + //Y2_dst = Y1_src + line_dst[FourBytes_start_index + 2 + isFirstY_indexOne]=line_src[FourBytes_start_index + 0 + isFirstY_indexOne]; + + //Y1_dst = Y2_src + line_dst[FourBytes_start_index + 0 + isFirstY_indexOne]=line_src[FourBytes_start_index + 2 + isFirstY_indexOne]; +} + +static void horizontal_flip_per_line(GF_VFlipCtx *ctx, u8 *line_src, u8 *line_dst, u32 plane_idx, u32 wiB) +{ + u32 j, line_size = wiB; + + if (ctx->s_pfmt == GF_PIXEL_RGB || ctx->s_pfmt == GF_PIXEL_BGR || ctx->s_pfmt == GF_PIXEL_XRGB || ctx->s_pfmt == GF_PIXEL_RGBX || ctx->s_pfmt == GF_PIXEL_XBGR || ctx->s_pfmt == GF_PIXEL_BGRX){ + //to avoid "3*j > line_size - 3*j - 3" or "4*j > line_size - 4*j - 4" + //jmax=line_size/(2*3) or jmax=line_size/(2*4) + for (j=0; j < line_size/(2*ctx->bps); j++) { + u8 pix[4]; + memcpy(pix, line_src + line_size - ctx->bps*(j+1), ctx->bps); + memcpy(line_dst + line_size - ctx->bps*(j+1), line_src + ctx->bps*j, ctx->bps); + memcpy(line_dst + ctx->bps*j, pix, ctx->bps); + } + } else if (ctx->packed_422) { + //If the source data is assigned to the output packet during the destination pack allocation + //i.e dst_planes[0]= src_planes[0], line_src is going to change while reading it as far as writing on line_dst=line_src + //To avoid this situation, ctx->line_buffer_hf keeps the values of line_src + memcpy(ctx->line_buffer_hf, line_src, wiB); + + //reversing of 4-bytes sequences + u32 fourBytesSize = wiB/4; + for (j=0; j < fourBytesSize; j++) { + //buffer = last 4 columns + u32 last_4bytes_index = wiB-4-(4*j); + u32 p, first_4bytes_index = 4*j; + for (p = 0; p < 4; p++) { + line_dst[first_4bytes_index+p] = ctx->line_buffer_hf[last_4bytes_index+p]; + } + //exchanging of Ys within a yuv pixel + swap_2Ys_YUVpixel(ctx, line_dst, line_dst, first_4bytes_index); + } + } else { + //nv12/21 + //second plane is U-plane={u1,v1, u2,v2...} + if (ctx->nb_planes==2 && plane_idx==1){ + if (ctx->bps==1) { + //to avoid "line_size - 2*j - 2 > 2*j", jmax=line_size/4 + for (j=0; j < line_size/4; j++) { + u8 u_comp, v_comp; + u_comp = line_src[line_size - 2*j - 2]; + v_comp = line_src[line_size - 2*j - 1]; + + line_dst[line_size - 2*j - 2] = line_src[2*j]; + line_dst[line_size - 2*j - 1] = line_src[2*j + 1]; + + line_dst[2*j] = u_comp; + line_dst[2*j + 1] = v_comp; + } + } else { + for (j=0; j < line_size/4; j++) { + u16 u_comp, v_comp; + u_comp = line_src[line_size - 2*j - 2]; + v_comp = line_src[line_size - 2*j - 1]; + + ((u16 *)line_dst)[line_size - 2*j - 2] = ((u16 *)line_src)[2*j]; + ((u16 *)line_dst)[line_size - 2*j - 1] = ((u16 *)line_src)[2*j + 1]; + + ((u16 *)line_dst)[2*j] = u_comp; + ((u16 *)line_dst)[2*j + 1] = v_comp; + } + } + } else if (ctx->bps==1) { + u32 wx = line_size/2; + u8 tmp; + for (j=0; j < wx; j++) { + //tmp = last column + tmp = line_src[line_size-1-j]; + + //last column = first column + line_dst[line_size-1-j] = line_src[j]; + + //first column = tmp + line_dst[j]=tmp; + } + } else { + line_size /= 2; + u32 wx = line_size/2; + u16 tmp; + for (j=0; j < wx; j++) { + //tmp = last column + tmp = (u16) ( ((u16 *)line_src) [line_size-1-j] ); + + //last column = first column + ((u16 *)line_dst) [line_size-1-j] = ((u16*)line_src)[j]; + + //first column = tmp + ((u16 *)line_dst) [j]=tmp; + } + } + } +} + +static void horizontal_flip(GF_VFlipCtx *ctx, u8 *src_plane, u8 *dst_plane, u32 height, u32 plane_idx, u32 wiB, u32 *src_stride) +{ + u32 i; + for (i=0; idst_stride[plane_idx]; + + horizontal_flip_per_line(ctx, src_first_line, dst_first_line, plane_idx, wiB); + } +} + +static void vertical_flip(GF_VFlipCtx *ctx, u8 *src_plane, u8 *dst_plane, u32 height, u32 plane_idx, u32 wiB){ + u32 hy, i; + hy = height/2; + for (i=0; isrc_stride[plane_idx]; + u8 *src_last_line = src_plane+ (height - 1 - i) * ctx->src_stride[plane_idx]; + + u8 *dst_first_line = dst_plane+ i*ctx->dst_stride[plane_idx]; + u8 *dst_last_line = dst_plane+ (height - 1 - i) * ctx->dst_stride[plane_idx]; + + memcpy(ctx->line_buffer_vf, src_last_line, wiB); + memcpy(dst_last_line, src_first_line, wiB); + memcpy(dst_first_line, ctx->line_buffer_vf, wiB); + } +} + +static GF_Err vflip_process(GF_Filter *filter) +{ + const char *data; + u8 *output; + u32 size; + u32 i; + u32 wiB, height; //wiB: width in Bytes of a plane + u8 *src_planes[5]; + u8 *dst_planes[5]; + GF_FilterPacket *dst_pck; + GF_FilterFrameInterface *frame_ifce; + GF_VFlipCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck = gf_filter_pid_get_packet(ctx->ipid); + + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + if (ctx->passthrough) { + gf_filter_pck_forward(pck, ctx->opid); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + data = gf_filter_pck_get_data(pck, &size); + frame_ifce = gf_filter_pck_get_frame_interface(pck); + + memset(src_planes, 0, sizeof(src_planes)); + memset(dst_planes, 0, sizeof(dst_planes)); + + if (data) { + src_planes[0] = (u8 *) data; + + if (ctx->nb_src_planes==1) { + } else if (ctx->nb_src_planes==2) { + src_planes[1] = src_planes[0] + ctx->src_stride[0]*ctx->h; + } else if (ctx->nb_src_planes==3) { + src_planes[1] = src_planes[0] + ctx->src_stride[0] * ctx->h; + src_planes[2] = src_planes[1] + ctx->src_stride[1] * ctx->src_uv_height; + } else if (ctx->nb_src_planes==4) { + src_planes[1] = src_planes[0] + ctx->src_stride[0] * ctx->h; + src_planes[2] = src_planes[1] + ctx->src_stride[1] * ctx->src_uv_height; + src_planes[3] = src_planes[2] + ctx->src_stride[2] * ctx->src_uv_height; + } + } else if (frame_ifce && frame_ifce->get_plane) { + for (i=0; i<4; i++) { + if (frame_ifce->get_plane(frame_ifce, i, (const u8 **) &src_planes[i], &ctx->src_stride[i])!=GF_OK) + break; + } + } else { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[VFlip] No data associated with packet, not supported\n")); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NOT_SUPPORTED; + } + + ctx->bps = gf_pixel_get_bytes_per_pixel(ctx->s_pfmt); + + + if (frame_ifce){ + dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->out_size, &output); + gf_filter_pck_merge_properties(pck, dst_pck); + } else { + dst_pck = gf_filter_pck_new_clone(ctx->opid, pck, &output); + } + + if (!dst_pck) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OUT_OF_MEM; + } + + + dst_planes[0] = output; + if (ctx->nb_planes==1) { + } else if (ctx->nb_planes==2) { + dst_planes[1] = output + ctx->dst_stride[0] * ctx->dst_height; + } else if (ctx->nb_planes==3) { + dst_planes[1] = output + ctx->dst_stride[0] * ctx->dst_height; + dst_planes[2] = dst_planes[1] + ctx->dst_stride[1]*ctx->dst_uv_height; + } else if (ctx->nb_planes==4) { + dst_planes[1] = output + ctx->dst_stride[0] * ctx->dst_height; + dst_planes[2] = dst_planes[1] + ctx->dst_stride[1]*ctx->dst_uv_height; + dst_planes[3] = dst_planes[2] + ctx->dst_stride[2]*ctx->dst_uv_height; + } + + //computing of height and wiB + //YUYV variations need *2 on horizontal dimension + for (i=0; inb_planes; i++) { + if (i==0) { + if (ctx->packed_422) { + wiB = ctx->bps * ctx->dst_width *2; + } else { + wiB = ctx->bps * ctx->dst_width; + } + height = ctx->h; + }else { + //nv12/21 + if (i==1 && ctx->nb_planes==2) { + //half vertical res (/2) + //half horizontal res (/2) but two chroma packed per pixel (*2) + wiB = ctx->bps * ctx->dst_width; + height = ctx->h / 2; + } else if (ctx->nb_planes>=3) { + u32 div_x, div_y; + //alpha/depth/other plane, treat as luma plane + if (i==3 && ctx->nb_planes==4) { + wiB = ctx->bps * ctx->dst_width; + height = ctx->h; + }else if (i==1 || i==2) { + div_x = (ctx->src_stride[1]==ctx->src_stride[0]) ? 1 : 2; + div_y = (ctx->src_uv_height==ctx->h) ? 1 : 2; + height = ctx->dst_height; + height /= div_y; + wiB = ctx->bps * ctx->dst_width; + wiB /= div_x; + } + } + } + + //processing according selected mode + if (ctx->mode==VFLIP_VERT){ + vertical_flip(ctx, src_planes[i], dst_planes[i], height, i, wiB); + }else if (ctx->mode==VFLIP_HORIZ){ + horizontal_flip(ctx, src_planes[i], dst_planes[i], height, i, wiB, ctx->src_stride); + }else if (ctx->mode==VFLIP_BOTH){ + vertical_flip(ctx, src_planes[i], dst_planes[i], height, i, wiB); + horizontal_flip(ctx, dst_planes[i], dst_planes[i], height, i, wiB, ctx->dst_stride); + } + } + + gf_filter_pck_send(dst_pck); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; +} + + +static GF_Err vflip_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + u32 w, h, stride, pfmt; + GF_Fraction sar; + GF_VFlipCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + if (ctx->opid) { + gf_filter_pid_remove(ctx->opid); + } + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + } + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, pid); + + if (!ctx->ipid) { + ctx->ipid = pid; + } + w = h = pfmt = stride = 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + if (p) w = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + if (p) h = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE); + if (p) stride = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); + if (p) pfmt = p->value.uint; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); + if (p) sar = p->value.frac; + else sar.den = sar.num = 1; + + if (!w || !h || !pfmt) { + ctx->passthrough = GF_TRUE; + return GF_OK; + } + + if ((ctx->w == w) && (ctx->h == h) && (ctx->s_pfmt == pfmt) && (ctx->stride == stride)) { + //nothing to reconfigure + ctx->passthrough = GF_TRUE; + } else if (ctx->mode==VFLIP_OFF) { + ctx->passthrough = GF_TRUE; + } else { + Bool res; + + ctx->w = w; + ctx->h = h; + ctx->s_pfmt = pfmt; + ctx->stride = stride; + ctx->dst_width = w; + ctx->dst_height = h; + ctx->passthrough = GF_FALSE; + + //get layout info for source + memset(ctx->src_stride, 0, sizeof(ctx->src_stride)); + if (ctx->stride) ctx->src_stride[0] = ctx->stride; + + res = gf_pixel_get_size_info(pfmt, w, h, &ctx->out_src_size, &ctx->src_stride[0], &ctx->src_stride[1], &ctx->nb_src_planes, &ctx->src_uv_height); + if (!res) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[VFlip] Failed to query source pixel format characteristics\n")); + return GF_NOT_SUPPORTED; + } + if (ctx->nb_src_planes==3) ctx->src_stride[2] = ctx->src_stride[1]; + if (ctx->nb_src_planes==4) ctx->src_stride[3] = ctx->src_stride[0]; + + + //get layout info for dest + memset(ctx->dst_stride, 0, sizeof(ctx->dst_stride)); + res = gf_pixel_get_size_info(pfmt, ctx->dst_width, ctx->dst_height, &ctx->out_size, &ctx->dst_stride[0], &ctx->dst_stride[1], &ctx->nb_planes, &ctx->dst_uv_height); + if (!res) { + GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[VFlip] Failed to query output pixel format characteristics\n")); + return GF_NOT_SUPPORTED; + } + if (ctx->nb_planes==3) ctx->dst_stride[2] = ctx->dst_stride[1]; + if (ctx->nb_planes==4) ctx->dst_stride[3] = ctx->dst_stride[0]; + + + ctx->w = w; + ctx->h = h; + ctx->s_pfmt = pfmt; + + GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[VFlip] Configured output full frame size %dx%d\n", ctx->w, ctx->h)); + + ctx->line_buffer_vf = gf_realloc(ctx->line_buffer_vf, sizeof(char)*ctx->dst_stride[0] ); + ctx->line_buffer_hf = gf_realloc(ctx->line_buffer_hf, sizeof(char)*ctx->src_stride[0] ); + + ctx->packed_422 = GF_FALSE; + switch (pfmt) { + //for YUV 422, adjust to multiple of 2 on horizontal dim + case GF_PIXEL_YUYV: + case GF_PIXEL_YVYU: + case GF_PIXEL_UYVY: + case GF_PIXEL_VYUY: + ctx->packed_422 = GF_TRUE; + break; + } + } + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_WIDTH, &PROP_UINT(ctx->dst_width)); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_HEIGHT, &PROP_UINT(ctx->dst_height)); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, &PROP_UINT(ctx->dst_stride[0] )); + if (ctx->nb_planes>1) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE_UV, &PROP_UINT(ctx->dst_stride[1])); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAR, &PROP_FRAC(sar) ); + + //an access unit corresponds to a single packet + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + return GF_OK; +} + +void vflip_finalize(GF_Filter *filter) +{ + GF_VFlipCtx *ctx = gf_filter_get_udta(filter); + if (ctx->line_buffer_vf) gf_free(ctx->line_buffer_vf); + if (ctx->line_buffer_hf) gf_free(ctx->line_buffer_hf); +} + + +#define OFFS(_n) #_n, offsetof(GF_VFlipCtx, _n) +static GF_FilterArgs VFlipArgs[] = +{ + { OFFS(mode), "flip mode\n" + "- off: no flipping (passthrough)\n" + "- vert: vertical flip\n" + "- horiz: horizontal flip\n" + "- both: horizontal and vertical flip" + "", GF_PROP_UINT, "vert", "off|vert|horiz|both", GF_FS_ARG_UPDATE | GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + +static const GF_FilterCapability VFlipCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW) +}; + +GF_FilterRegister VFlipRegister = { + .name = "vflip", + GF_FS_SET_DESCRIPTION("Video flip") + GF_FS_SET_HELP("Filter used to flip video frames vertically, horizontally, in both directions or no flip") + .private_size = sizeof(GF_VFlipCtx), + .flags = GF_FS_REG_EXPLICIT_ONLY, + .args = VFlipArgs, + .configure_pid = vflip_configure_pid, + SETCAPS(VFlipCaps), + .process = vflip_process, + .finalize = vflip_finalize, +}; + + + +const GF_FilterRegister *vflip_register(GF_FilterSession *session) +{ + return &VFlipRegister; +} diff --git a/src/filters/write_generic.c b/src/filters/write_generic.c new file mode 100644 index 0000000..c214778 --- /dev/null +++ b/src/filters/write_generic.c @@ -0,0 +1,1063 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2018 + * All rights reserved + * + * This file is part of GPAC / generic stream to file filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +#include + +enum +{ + DECINFO_NO=0, + DECINFO_FIRST, + DECINFO_SAP, + DECINFO_AUTO +}; + +typedef struct +{ + //opts + Bool exporter, frame, split; + u32 sstart, send; + u32 pfmt, afmt, decinfo; + GF_Fraction dur; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + u32 codecid; + Bool is_mj2k; + u32 sample_num; + + const char *dcfg; + u32 dcfg_size; + Bool cfg_sent; + + GF_Fraction64 duration; + Bool first; + + GF_BitStream *bs; + + u32 target_pfmt, target_afmt; + Bool is_bmp; + u32 is_wav; + u32 w, h, stride; + u64 nb_bytes; + Bool dash_mode; + + u64 first_dts_plus_one; +} GF_GenDumpCtx; + + +GF_Err writegen_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 cid, chan, sr, w, h, stype, pf, sfmt, av1mode, nb_bps; + const char *name, *mimetype; + char szExt[GF_4CC_MSIZE], szCodecExt[30], *sep; + const GF_PropertyValue *p; + GF_GenDumpCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + ctx->opid = NULL; + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p) return GF_NOT_SUPPORTED; + cid = p->value.uint; + + ctx->codecid = cid; + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + ctx->first = GF_TRUE; + } + + ctx->ipid = pid; + + //copy properties at init or reconfig + gf_filter_pid_copy_properties(ctx->opid, pid); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL); + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + stype = p ? p->value.uint : 0; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); + sr = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS); + chan = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT); + sfmt = p ? p->value.uint : GF_AUDIO_FMT_S16; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_BPS); + nb_bps = p ? p->value.uint : 0; + + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + ctx->w = w = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + ctx->h = h = p ? p->value.uint : 0; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT); + pf = p ? p->value.uint : 0; + if (!pf) pf = GF_PIXEL_YUV; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (p) { + ctx->dcfg = p->value.data.ptr; + ctx->dcfg_size = p->value.data.size; + } + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_MODE); + ctx->dash_mode = (p && p->value.uint) ? GF_TRUE : GF_FALSE; + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); + + //special case for xml text, override to xml + switch (cid) { + case GF_CODECID_META_XML: + case GF_CODECID_SUBS_XML: + strcpy(szCodecExt, "xml"); + break; + default: + strncpy(szCodecExt, gf_codecid_file_ext(cid), 29); + szCodecExt[29]=0; + sep = strchr(szCodecExt, '|'); + if (sep) sep[0] = 0; + break; + } + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING(szCodecExt) ); + + mimetype = gf_codecid_mime(cid); + + switch (cid) { + case GF_CODECID_AAC_MPEG4: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + p = gf_filter_pid_get_property(pid, GF_PROP_PID_UNFRAMED_LATM); + if (p && p->value.boolean) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("latm") ); + } + break; + case GF_CODECID_PNG: + case GF_CODECID_JPEG: + ctx->split = GF_TRUE; + break; + case GF_CODECID_J2K: + ctx->split = GF_TRUE; + ctx->is_mj2k = GF_TRUE; + break; + case GF_CODECID_AMR: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING(mimetype) ); + ctx->dcfg = "#!AMR\n"; + ctx->dcfg_size = 6; + ctx->decinfo = DECINFO_FIRST; + break; + case GF_CODECID_AMR_WB: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING(mimetype) ); + ctx->dcfg = "#!AMR-WB\n"; + ctx->dcfg_size = 9; + ctx->decinfo = DECINFO_FIRST; + break; + case GF_CODECID_SMV: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING(mimetype) ); + ctx->dcfg = "#!SMV\n"; + ctx->dcfg_size = 6; + ctx->decinfo = DECINFO_FIRST; + break; + case GF_CODECID_EVRC_PV: + case GF_CODECID_EVRC: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING(mimetype) ); + ctx->dcfg = "#!EVRC\n"; + ctx->dcfg_size = 7; + ctx->decinfo = DECINFO_FIRST; + break; + + case GF_CODECID_FLAC: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING(mimetype) ); + ctx->decinfo = DECINFO_FIRST; + break; + + + case GF_CODECID_SIMPLE_TEXT: + if (!gf_filter_pid_get_property(pid, GF_PROP_PID_MIME)) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING(mimetype) ); + if (ctx->decinfo == DECINFO_AUTO) + ctx->decinfo = DECINFO_FIRST; + break; + case GF_CODECID_META_TEXT: + if (!gf_filter_pid_get_property(pid, GF_PROP_PID_MIME)) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING(mimetype) ); + if (ctx->decinfo == DECINFO_AUTO) + ctx->decinfo = DECINFO_FIRST; + break; + case GF_CODECID_META_XML: + if (!gf_filter_pid_get_property(pid, GF_PROP_PID_MIME)) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING(mimetype) ); + if (ctx->decinfo == DECINFO_AUTO) + ctx->decinfo = DECINFO_FIRST; + break; + case GF_CODECID_SUBS_TEXT: + if (!gf_filter_pid_get_property(pid, GF_PROP_PID_MIME)) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING(mimetype) ); + if (ctx->decinfo == DECINFO_AUTO) + ctx->decinfo = DECINFO_FIRST; + break; + case GF_CODECID_SUBS_XML: + if (!gf_filter_pid_get_property(pid, GF_PROP_PID_MIME)) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING(mimetype) ); + + if (!ctx->frame) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("Subtitles re-assembling is not supported yet\n")); + return GF_NOT_SUPPORTED; + } + ctx->split = GF_TRUE; + if (ctx->decinfo == DECINFO_AUTO) + ctx->decinfo = DECINFO_FIRST; + break; + + case GF_CODECID_AV1: + av1mode = 0; + p = gf_filter_pid_get_property_str(ctx->ipid, "obu:mode"); + if (p) av1mode = p->value.uint; + if (av1mode==1) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("av1b") ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING("video/x-av1b") ); + } else if (av1mode==2) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("ivf") ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING("video/x-ivf") ); + } else { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("obu") ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING("video/x-av1") ); + } + break; + case GF_CODECID_RAW: + ctx->dcfg = NULL; + ctx->dcfg_size = 0; + if (stype==GF_STREAM_VISUAL) { + strcpy(szExt, gf_pixel_fmt_sname(ctx->target_pfmt ? ctx->target_pfmt : pf)); + p = gf_filter_pid_caps_query(ctx->opid, GF_PROP_PID_FILE_EXT); + if (p) { + strncpy(szExt, p->value.string, GF_4CC_MSIZE-1); + szExt[GF_4CC_MSIZE-1] = 0; + if (!strcmp(szExt, "bmp")) { + ctx->is_bmp = GF_TRUE; + //request BGR + ctx->target_pfmt = GF_PIXEL_BGR; + ctx->split = GF_TRUE; + } else { + ctx->target_pfmt = gf_pixel_fmt_parse(szExt); + if (!ctx->target_pfmt) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("Cannot guess pixel format from extension type %s\n", szExt)); + return GF_NOT_SUPPORTED; + } + strcpy(szExt, gf_pixel_fmt_sname(ctx->target_pfmt)); + } + //forcing pixel format regardless of extension + if (ctx->pfmt) { + if (pf != ctx->pfmt) { + gf_filter_pid_negociate_property(ctx->ipid, GF_PROP_PID_PIXFMT, &PROP_UINT(ctx->pfmt)); + //make sure we reconfigure + ctx->codecid = 0; + pf = ctx->pfmt; + } + } + //use extension to derive pixel format + else if (pf != ctx->target_pfmt) { + gf_filter_pid_negociate_property(ctx->ipid, GF_PROP_PID_PIXFMT, &PROP_UINT(ctx->target_pfmt)); + strcpy(szExt, gf_pixel_fmt_sname(ctx->target_pfmt)); + //make sure we reconfigure + ctx->codecid = 0; + } + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE); + ctx->stride = p ? p->value.uint : 0; + + if (!ctx->stride) { + gf_pixel_get_size_info(ctx->target_pfmt, ctx->w, ctx->h, NULL, &ctx->stride, NULL, NULL, NULL); + } + + } else if (stype==GF_STREAM_AUDIO) { + strcpy(szExt, gf_audio_fmt_sname(ctx->target_pfmt ? ctx->target_afmt : sfmt)); + p = gf_filter_pid_caps_query(ctx->opid, GF_PROP_PID_FILE_EXT); + if (p) { + strncpy(szExt, p->value.string, GF_4CC_MSIZE-1); + szExt[GF_4CC_MSIZE-1] = 0; + if (!strcmp(szExt, "wav")) { + ctx->is_wav = GF_TRUE; + //request PCMs16 ? +// ctx->target_afmt = GF_AUDIO_FMT_S16; + ctx->target_afmt = sfmt; + } else { + ctx->target_afmt = gf_audio_fmt_parse(szExt); + strcpy(szExt, gf_audio_fmt_sname(ctx->target_afmt)); + } + //forcing sample format regardless of extension + if (ctx->afmt) { + if (sfmt != ctx->afmt) { + gf_filter_pid_negociate_property(ctx->ipid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(ctx->afmt)); + //make sure we reconfigure + ctx->codecid = 0; + sfmt = ctx->afmt; + } + } + //use extension to derive sample format + else if (sfmt != ctx->target_afmt) { + gf_filter_pid_negociate_property(ctx->ipid, GF_PROP_PID_AUDIO_FORMAT, &PROP_UINT(ctx->target_afmt)); + strcpy(szExt, gf_audio_fmt_sname(ctx->target_afmt)); + //make sure we reconfigure + ctx->codecid = 0; + } + } + + } else { + strcpy(szExt, gf_4cc_to_str(cid)); + } + if (ctx->is_bmp) strcpy(szExt, "bmp"); + else if (ctx->is_wav) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DISABLE_PROGRESSIVE, &PROP_UINT(GF_PID_FILE_PATCH_REPLACE) ); + strcpy(szExt, "wav"); + } else if (!strlen(szExt)) strcpy(szExt, "raw"); + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING( szExt ) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING("application/octet-string") ); + if (!ctx->codecid) return GF_OK; + break; + + default: + if (!strcmp(szCodecExt, "raw")) { + strcpy(szExt, gf_4cc_to_str(cid)); + if (!strlen(szExt)) strcpy(szExt, "raw"); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING( szExt ) ); + } else { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING( szCodecExt ) ); + } + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING(mimetype) ); + break; + } + + if (ctx->decinfo == DECINFO_AUTO) + ctx->decinfo = DECINFO_NO; + + name = gf_codecid_name(cid); + if (ctx->exporter) { + if (w && h) { + if (cid==GF_CODECID_RAW) name = gf_pixel_fmt_name(pf); + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting %s - Size %dx%d\n", name, w, h)); + } else if (sr && chan) { + if (cid==GF_CODECID_RAW) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting PCM %s SampleRate %d %d channels %d bits per sample\n", gf_audio_fmt_name(sfmt), sr, chan, gf_audio_fmt_bit_depth(sfmt) )); + } else { + if (!nb_bps) + nb_bps = gf_audio_fmt_bit_depth(sfmt); + + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting %s - SampleRate %d %d channels %d bits per sample\n", name, sr, chan, nb_bps )); + } + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting %s\n", name)); + } + } + + //avoid creating a file when dumping individual samples + if (ctx->split) { + p = gf_filter_pid_get_property(pid, GF_PROP_PID_NB_FRAMES); + if (!p || (p->value.uint>1)) + gf_filter_pid_set_property(ctx->opid, GF_PROP_PCK_FILENUM, &PROP_UINT(0) ); + else + ctx->split = GF_FALSE; + } else if (ctx->frame) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PCK_FILENUM, &PROP_UINT(0) ); + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DURATION); + if (p) ctx->duration = p->value.lfrac; + + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + return GF_OK; +} + +static GF_FilterPacket *writegen_write_j2k(GF_GenDumpCtx *ctx, char *data, u32 data_size, GF_FilterPacket *in_pck) +{ + u32 size; + u8 *output; + GF_FilterPacket *dst_pck; + char sig[8]; + sig[0] = sig[1] = sig[2] = 0; + sig[3] = 0xC; + sig[4] = 'j'; + sig[5] = 'P'; + sig[6] = sig[7] = ' '; + + if ((data_size>16) && !memcmp(data, sig, 8)) { + return gf_filter_pck_new_ref(ctx->opid, NULL, 0, in_pck); + } + + size = data_size + 8*4 /*jP%20%20 + ftyp*/ + ctx->dcfg_size + 8; + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + + + if (!ctx->bs) ctx->bs = gf_bs_new(output, size, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs, output, size); + + gf_bs_write_u32(ctx->bs, 12); + gf_bs_write_u32(ctx->bs, GF_ISOM_BOX_TYPE_JP); + gf_bs_write_u32(ctx->bs, 0x0D0A870A); + gf_bs_write_u32(ctx->bs, 20); + gf_bs_write_u32(ctx->bs, GF_ISOM_BOX_TYPE_FTYP); + gf_bs_write_u32(ctx->bs, GF_ISOM_BRAND_JP2); + gf_bs_write_u32(ctx->bs, 0); + gf_bs_write_u32(ctx->bs, GF_ISOM_BRAND_JP2); + + //todo, write colr and other info ? + if (ctx->dcfg) { + gf_bs_write_u32(ctx->bs, 8+ctx->dcfg_size); + gf_bs_write_u32(ctx->bs, GF_ISOM_BOX_TYPE_JP2H); + gf_bs_write_data(ctx->bs, ctx->dcfg, ctx->dcfg_size); + } + gf_bs_write_data(ctx->bs, data, data_size); + + return dst_pck; +} + +#ifdef WIN32 +#include +#else +typedef struct tagBITMAPFILEHEADER +{ + u16 bfType; + u32 bfSize; + u16 bfReserved1; + u16 bfReserved2; + u32 bfOffBits; +} BITMAPFILEHEADER; + +typedef struct tagBITMAPINFOHEADER { + u32 biSize; + s32 biWidth; + s32 biHeight; + u16 biPlanes; + u16 biBitCount; + u32 biCompression; + u32 biSizeImage; + s32 biXPelsPerMeter; + s32 biYPelsPerMeter; + u32 biClrUsed; + u32 biClrImportant; +} BITMAPINFOHEADER; + +#define BI_RGB 0L + +#endif + +static GF_FilterPacket *writegen_write_bmp(GF_GenDumpCtx *ctx, char *data, u32 data_size) +{ + u32 size; + u8 *output; + BITMAPFILEHEADER fh; + BITMAPINFOHEADER fi; + GF_FilterPacket *dst_pck; + u32 i; + + size = ctx->w*ctx->h*3 + 54; //14 + 40 = size of BMP file header and BMP file info; + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + + memset(&fh, 0, sizeof(fh)); + fh.bfType = 19778; + fh.bfOffBits = 54; + + if (!ctx->bs) ctx->bs = gf_bs_new(output, size, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs, output, size); + + gf_bs_write_data(ctx->bs, (const char *) &fh.bfType, 2); + gf_bs_write_data(ctx->bs, (const char *) &fh.bfSize, 4); + gf_bs_write_data(ctx->bs, (const char *) &fh.bfReserved1, 2); + gf_bs_write_data(ctx->bs, (const char *) &fh.bfReserved2, 2); + gf_bs_write_data(ctx->bs, (const char *) &fh.bfOffBits, 4); + + memset(&fi, 0, sizeof(char)*40); + fi.biSize = 40; + fi.biWidth = ctx->w; + fi.biHeight = ctx->h; + fi.biPlanes = 1; + fi.biBitCount = 24; + fi.biCompression = BI_RGB; + fi.biSizeImage = ctx->w * ctx->h * 3; + + memcpy(output+14, &fi, sizeof(char)*40); + //reverse lines + output += sizeof(u8) * 54; + for (i=ctx->h; i>0; i--) { + u8 *ptr = data + (i-1)*ctx->stride; + memcpy(output, ptr, sizeof(u8)*3*ctx->w); + output += 3*ctx->w; + } + return dst_pck; +} + +static void writegen_write_wav_header(GF_GenDumpCtx *ctx) +{ + u32 size; + u8 *output; + GF_FilterPacket *dst_pck; + const GF_PropertyValue *p; + u32 nb_ch, sample_rate, afmt, bps; + const char chunkID[] = {'R', 'I', 'F', 'F'}; + const char format[] = {'W', 'A', 'V', 'E'}; + const char subchunk1ID[] = {'f', 'm', 't', ' '}; + const char subchunk2ID[] = {'d', 'a', 't', 'a'}; + + if (ctx->is_wav!=1) return; + ctx->is_wav = 2; + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_SAMPLE_RATE); + if (!p) return; + sample_rate = p->value.uint; + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_NUM_CHANNELS); + if (!p) return; + nb_ch = p->value.uint; + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_AUDIO_FORMAT); + if (!p) return; + afmt = p->value.uint; + + bps = gf_audio_fmt_bit_depth(afmt); + + size = 44; + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + + if (!ctx->bs) ctx->bs = gf_bs_new(output, size, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs, output, size); + + gf_bs_write_data(ctx->bs, chunkID, 4); + gf_bs_write_u32_le(ctx->bs, (u32) ctx->nb_bytes + 36); + gf_bs_write_data(ctx->bs, format, 4); + + gf_bs_write_data(ctx->bs, subchunk1ID, 4); + gf_bs_write_u32_le(ctx->bs, 16); //subchunk1 size + gf_bs_write_u16_le(ctx->bs, 1); //audio format + gf_bs_write_u16_le(ctx->bs, nb_ch); + gf_bs_write_u32_le(ctx->bs, sample_rate); + gf_bs_write_u32_le(ctx->bs, sample_rate * nb_ch * bps / 8); //byte rate + gf_bs_write_u16_le(ctx->bs, nb_ch * bps / 8); // block align + gf_bs_write_u16_le(ctx->bs, bps); + + gf_bs_write_data(ctx->bs, subchunk2ID, 4); + gf_bs_write_u32_le(ctx->bs, (u32) ctx->nb_bytes); + + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE); + gf_filter_pck_set_byte_offset(dst_pck, 0); + gf_filter_pck_set_seek_flag(dst_pck, GF_TRUE); + gf_filter_pck_send(dst_pck); +} + +GF_Err writegen_process(GF_Filter *filter) +{ + GF_GenDumpCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + char *data; + u32 pck_size; + Bool do_abort = GF_FALSE; + Bool split = ctx->split; + if (!ctx->ipid) return GF_EOS; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck || !ctx->codecid) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (ctx->is_wav) writegen_write_wav_header(ctx); + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + ctx->sample_num++; + + if (ctx->sstart) { + if (ctx->sstart > ctx->sample_num) { + gf_filter_pid_drop_packet(ctx->ipid); + return GF_OK; + } + if ((ctx->sstart <= ctx->send) && (ctx->sample_num>ctx->send) ) { + do_abort = GF_TRUE; + } + } else if (ctx->dur.num && ctx->dur.den) { + u64 dts = gf_filter_pck_get_dts(pck); + if (dts==GF_FILTER_NO_TS) + dts = gf_filter_pck_get_cts(pck); + + if (!ctx->first_dts_plus_one) { + ctx->first_dts_plus_one = dts+1; + } else { + if (ctx->dur.den * (dts + 1 - ctx->first_dts_plus_one) > ctx->dur.num * gf_filter_pck_get_timescale(pck)) { + do_abort = GF_TRUE; + } + } + } + if (do_abort) { + GF_FilterEvent evt; + gf_filter_pid_drop_packet(ctx->ipid); + GF_FEVT_INIT(evt, GF_FEVT_STOP, ctx->ipid); + gf_filter_pid_send_event(ctx->ipid, &evt); + return GF_OK; + } + + //except in dash mode, force a new file if GF_PROP_PCK_FILENUM is set + if (!ctx->dash_mode && (gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM) != NULL)) { + ctx->cfg_sent = GF_FALSE; + ctx->first = GF_TRUE; + } + + if (ctx->frame) { + split = GF_TRUE; + } else if (ctx->dcfg_size && gf_filter_pck_get_sap(pck) && !ctx->is_mj2k && (ctx->decinfo!=DECINFO_NO) && !ctx->cfg_sent) { + dst_pck = gf_filter_pck_new_shared(ctx->opid, ctx->dcfg, ctx->dcfg_size, NULL); + gf_filter_pck_merge_properties(pck, dst_pck); + gf_filter_pck_set_framing(dst_pck, ctx->first, GF_FALSE); + ctx->first = GF_FALSE; + gf_filter_pck_set_readonly(dst_pck); + gf_filter_pck_send(dst_pck); + if ((ctx->decinfo==DECINFO_FIRST) && !ctx->split) { + ctx->dcfg_size = 0; + ctx->dcfg = NULL; + } + ctx->cfg_sent = GF_TRUE; + return GF_OK; + } + ctx->cfg_sent = GF_FALSE; + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + + if (ctx->is_mj2k) { + dst_pck = writegen_write_j2k(ctx, data, pck_size, pck); + } else if (ctx->is_bmp) { + dst_pck = writegen_write_bmp(ctx, data, pck_size); + } else if (ctx->is_wav && ctx->first) { + u8 * output; + dst_pck = gf_filter_pck_new_alloc(ctx->opid, 44, &output); + gf_filter_pck_merge_properties(pck, dst_pck); + gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_FALSE); + gf_filter_pck_set_corrupted(dst_pck, GF_TRUE); + ctx->first = GF_FALSE; + gf_filter_pck_send(dst_pck); + ctx->nb_bytes += 44; + return GF_OK; + } else { + dst_pck = gf_filter_pck_new_ref(ctx->opid, NULL, 0, pck); + } + gf_filter_pck_merge_properties(pck, dst_pck); + //don't keep byte offset + gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO); + + if (split) { + gf_filter_pck_set_property(dst_pck, GF_PROP_PCK_FILENUM, &PROP_UINT(ctx->sample_num) ); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + } else { + gf_filter_pck_set_framing(dst_pck, ctx->first, GF_FALSE); + ctx->first = GF_FALSE; + } + + gf_filter_pck_send(dst_pck); + gf_filter_pck_set_seek_flag(dst_pck, 0); + ctx->nb_bytes += pck_size; + + if (ctx->exporter) { + u32 timescale = gf_filter_pck_get_timescale(pck); + u64 ts = gf_filter_pck_get_dts(pck); + if (ts==GF_FILTER_NO_TS) + ts = gf_filter_pck_get_cts(pck); + if (ts!=GF_FILTER_NO_TS) { + ts += gf_filter_pck_get_duration(pck); + ts *= ctx->duration.den; + ts /= timescale; + gf_set_progress("Exporting", ts, ctx->duration.num); + } + } + + gf_filter_pid_drop_packet(ctx->ipid); + + return GF_OK; +} + +/* + We list capabilities per codec type, associating the codec type to the file extension. This allows, + when matching to an output filter accepting a given file extension to match the codec type in write_gen caps, and + setup the right filter chain +*/ +static GF_FilterCapability GenDumpCaps[] = +{ + //raw color dump YUV and RGB - keep it as first for field extension assignment + //cf below + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "gpac"), + {0}, + //raw audio dump - keep it as second for field extension assignment + //cf below + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "gpac" ), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "bmp"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "image/bmp"), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "wav"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "audio/wav|audio/wave"), + {0}, + + //we accept unframed AVC (annex B format) + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AVC), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AVC_PS), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_SVC), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MVC), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "264|h264|avc|svc|mvc"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "video/avc|video/h264|video/svc|video/mvc"), + {0}, + + //we accept unframed HEVC (annex B format) + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_HEVC), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_LHVC), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "265|h265|hvc|hevc|shvc|lhvc"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "video/hevc|video/lhvc|video/shvc|video/mhvc"), + {0}, + + //we accept unframed OBU (without size field) + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AV1), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_VP8), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_VP9), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_VP10), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "obu|av1|av1b|ivf"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "video/x-ivf|video/av1"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG4_PART2), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "cmp|m4ve"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "video/mp4v-es"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_H263), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_S263), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "h263|263|s263"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "video/h263"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG1), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "m1v"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "video/mpgv-es"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_422), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_SNR), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_HIGH), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_MAIN), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_SIMPLE), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_SPATIAL), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "m2v"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "video/mpgv-es"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AP4H), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AP4X), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_APCH), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_APCO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_APCS), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_APCN), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "prores"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "video/prores"), + {0}, + + //we accept unframed AAC + ADTS + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG4), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_MP), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_LCP), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_SSRP), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED,GF_PROP_PID_UNFRAMED_LATM, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "aac|adts"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "audio/x-m4a|audio/aac|audio/aacp|audio/x-aac"), + {0}, + + //we accept unframed AAC + LATM + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG4), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_MP), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_LCP), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_SSRP), + CAP_BOOL(GF_CAPS_INPUT,GF_PROP_PID_UNFRAMED_LATM, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "latm"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "audio/aac+latm"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_PNG), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "png"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "image/png"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_JPEG), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "jpg|jpeg"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "image/jpg"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_J2K), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "jp2|j2k"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "image/jp2"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_MPEG2_PART3), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "mp3|mp1|mp2"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "audio/mp3|audio/x-mp3"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AMR), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AMR_WB), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "amr"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "audio/amr"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_SMV), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "smv"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "audio/smv"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_EVRC_PV), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_EVRC), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "evc"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "audio/evrc"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_AC3), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "ac3"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "audio/x-ac3|audio/ac3"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_EAC3), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "eac3"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "audio/x-eac3|audio/eac3"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_SIMPLE_TEXT), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_META_TEXT), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_SUBS_TEXT), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "txt"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "x-subtitle/srt|subtitle/srt|text/srt"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_META_XML), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_SUBS_XML), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "xml|txml|ttml"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "subtitle/ttml|text/ttml|application/xml+ttml"), + {0}, + + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_QCELP), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "qcelp"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "audio/qcelp"), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_THEORA), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "theo"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "video/x-theora"), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_VORBIS), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "vorb"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "audio/x-vorbis"), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_SPEEX), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "spx"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "audio/x-speex"), + {0}, + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_FLAC), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "flac"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "audio/flac"), + {0}, + + //anything else: only for explicit dump (filter loaded on purpose), otherwise don't load for filter link resolution + CAP_UINT(GF_CAPS_OUTPUT_LOADED_FILTER, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "*"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "*"), + //for the rest, we include everything, only specifies excluded ones from above and non-handled ones + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_AVC), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_AVC_PS), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_SVC), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_MVC), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_HEVC), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_LHVC), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_MPEG4_PART2), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG4), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_MP), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_LCP), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_AAC_MPEG2_SSRP), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_RAW), + + //WebVTT needs rewrite + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_WEBVTT), + //systems streams not dumpable to file + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_BIFS), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_BIFS_V2), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_OD_V1), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_OD_V2), + //this one need QCP + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_QCELP), + + //other not supported (yet) + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_THEORA), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_VORBIS) +}; + + +#define OFFS(_n) #_n, offsetof(GF_GenDumpCtx, _n) +static GF_FilterArgs GenDumpArgs[] = +{ + { OFFS(exporter), "compatibility with old exporter, displays export results", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(pfmt), "pixel format for raw extract. If not set, derived from extension", GF_PROP_PIXFMT, "none", NULL, 0}, + { OFFS(afmt), "audio format for raw extract. If not set, derived from extension", GF_PROP_PCMFMT, "none", NULL, 0}, + { OFFS(decinfo), "decoder config insert mode\n" + "- no: never inserted\n" + "- first: inserted on first packet\n" + "- sap: inserted at each SAP\n" + "- auto: selects between no and first based on media type", GF_PROP_UINT, "auto", "no|first|sap|auto", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(split), "force one file per decoded frame", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(frame), "force single frame dump with no rewrite. In this mode, all codecids are supported", GF_PROP_BOOL, "false", NULL, 0}, + { OFFS(sstart), "start number of frame to forward. If 0, all samples are forwarded", GF_PROP_UINT, "0", NULL, 0}, + { OFFS(send), "end number of frame to forward. If less than start frame, all samples after start are forwarded", GF_PROP_UINT, "0", NULL, 0}, + { OFFS(dur), "duration of media to forward after first sample. If 0, all samples are forwarded", GF_PROP_FRACTION, "0", NULL, 0}, + {0} +}; + +static GF_Err writegen_initialize(GF_Filter *filter); + +void writegen_finalize(GF_Filter *filter) +{ + GF_GenDumpCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs) gf_bs_del(ctx->bs); +} + +GF_FilterRegister GenDumpRegister = { + .name = "writegen", + GF_FS_SET_DESCRIPTION("Stream to file") + GF_FS_SET_HELP("Generic single stream to file converter, used when extracting/converting PIDs.\n" + "The writegen filter should usually not be explicetly loaded without a source ID specified, since the filter would likely match any pid connection.") + .private_size = sizeof(GF_GenDumpCtx), + .args = GenDumpArgs, + .initialize = writegen_initialize, + .finalize = writegen_finalize, + SETCAPS(GenDumpCaps), + .configure_pid = writegen_configure_pid, + .process = writegen_process +}; + +static const GF_FilterCapability FrameDumpCaps[] = +{ + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_UNKNOWN), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE) +}; + +static GF_Err writegen_initialize(GF_Filter *filter) +{ + GF_GenDumpCtx *ctx = gf_filter_get_udta(filter); + if (ctx->frame) { + return gf_filter_override_caps(filter, (const GF_FilterCapability *) FrameDumpCaps, sizeof(FrameDumpCaps) / sizeof(GF_FilterCapability)); + } + return GF_OK; +} + + +const GF_FilterRegister *writegen_register(GF_FilterSession *session) +{ + + //assign runtime caps on first load + if (!strcmp(GenDumpCaps[3].val.value.string, "gpac")) { + GenDumpCaps[3].val.value.string = (char *) gf_pixel_fmt_all_shortnames(); + GenDumpCaps[8].val.value.string = (char *) gf_audio_fmt_all_shortnames(); + GenDumpArgs[1].min_max_enum = gf_pixel_fmt_all_names(); + GenDumpArgs[2].min_max_enum = gf_audio_fmt_all_names(); + } + + return &GenDumpRegister; +} + diff --git a/src/filters/write_nhml.c b/src/filters/write_nhml.c new file mode 100644 index 0000000..6ea26cf --- /dev/null +++ b/src/filters/write_nhml.c @@ -0,0 +1,954 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2020 + * All rights reserved + * + * This file is part of GPAC / NHML stream to file filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include + +#include + +#ifndef GPAC_DISABLE_ZLIB +#include +#endif + +typedef struct +{ + //opts + const char *name; + Bool exporter, dims, pckp, nhmlonly, chksum; + FILE *filep; + + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid_nhml, *opid_mdia, *opid_info; + + u32 codecid; + u32 streamtype; + u32 oti; + u32 chan, sr, bps, w, h; + const char *dcfg; + u32 dcfg_size; + char *media_file, *info_file; + const char *szRootName; + + GF_Fraction64 duration; + Bool first; + Bool uncompress, is_dims, is_stpp; + + GF_BitStream *bs_w, *bs_r; + u8 *nhml_buffer; + u32 nhml_buffer_size; + char *b64_buffer; + u32 b64_buffer_size; + u64 mdia_pos; + u32 pck_num; + + Bool side_streams_config; +} GF_NHMLDumpCtx; + +GF_Err nhmldump_config_side_stream(GF_Filter *filter, GF_NHMLDumpCtx *ctx) +{ + char *mime=NULL, *name; + char fileName[GF_MAX_PATH+1]; + const GF_PropertyValue *p; + GF_FileIO *gfio = NULL; + + if (ctx->name) { + strncpy(fileName, ctx->name, GF_MAX_PATH); + fileName[GF_MAX_PATH] = 0; + } else { + char *url = gf_filter_pid_get_destination(ctx->opid_nhml); + if (url) { + if (!strncmp(url, "gfio://", 7)) { + gfio = gf_fileio_from_url(url); + strncpy(fileName, gf_fileio_translate_url(url), GF_MAX_PATH); + } else { + strncpy(fileName, url, GF_MAX_PATH); + } + fileName[GF_MAX_PATH] = 0; + gf_free(url); + } else { + strcpy(fileName, "dump"); + } + } + name = gf_file_ext_start(fileName); + if (name) { + name[0] = 0; + } + + if (!ctx->opid_mdia && !ctx->nhmlonly) + ctx->opid_mdia = gf_filter_pid_new(filter); + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_DECODER_CONFIG); + if (p) { + ctx->dcfg = p->value.data.ptr; + ctx->dcfg_size = p->value.data.size; + + if (!ctx->opid_info && !ctx->nhmlonly) { + ctx->opid_info = gf_filter_pid_new(filter); + } + + } else if (ctx->opid_info) { + gf_filter_pid_remove(ctx->opid_info); + } + if (ctx->info_file) gf_free(ctx->info_file); + ctx->info_file = NULL; + + if (ctx->opid_mdia) { + GF_Err e; + char *res_name; + gf_filter_pid_set_property(ctx->opid_mdia, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); + gf_filter_pid_set_property(ctx->opid_mdia, GF_PROP_PID_MIME, &PROP_STRING(mime) ); + + name = gf_file_ext_start(fileName); + if (name) name[0] = 0; + strcat(fileName, ".media"); + if (gfio) { + res_name = (char *) gf_fileio_factory(gfio, gf_file_basename(fileName) ); + } else { + res_name = fileName; + } + if (!ctx->exporter) { + gf_filter_pid_set_property(ctx->opid_mdia, GF_PROP_PID_OUTPATH, &PROP_STRING(res_name) ); + } + + if (ctx->media_file) gf_free(ctx->media_file); + ctx->media_file = gf_strdup(fileName); + gf_filter_pid_set_property(ctx->opid_mdia, GF_PROP_PID_FILE_EXT, &PROP_STRING("media") ); + + if (!ctx->exporter) { + GF_Filter *o_media = gf_filter_connect_destination(filter, res_name, &e); + if (o_media) gf_filter_set_source(o_media, filter, NULL); + } + } + + if (ctx->opid_info) { + char *res_name; + GF_Err e; + gf_filter_pid_set_property(ctx->opid_info, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); + gf_filter_pid_set_property(ctx->opid_info, GF_PROP_PID_MIME, &PROP_STRING(mime) ); + + name = gf_file_ext_start(fileName); + if (name) name[0] = 0; + strcat(fileName, ".info"); + if (gfio) { + res_name = (char *) gf_fileio_factory(gfio, gf_file_basename(fileName) ); + } else { + res_name = fileName; + } + if (!ctx->exporter) { + gf_filter_pid_set_property(ctx->opid_info, GF_PROP_PID_OUTPATH, &PROP_STRING(res_name) ); + } + + if (ctx->info_file) gf_free(ctx->info_file); + ctx->info_file = gf_strdup(fileName); + gf_filter_pid_set_property(ctx->opid_info, GF_PROP_PID_FILE_EXT, &PROP_STRING("info") ); + + if (!ctx->exporter) { + GF_Filter *o_info = gf_filter_connect_destination(filter, res_name, &e); + if (o_info) gf_filter_set_source(o_info, filter, NULL); + } + } + + if (ctx->opid_mdia) + gf_filter_pid_set_name(ctx->opid_mdia, "media"); + if (ctx->opid_info) + gf_filter_pid_set_name(ctx->opid_info, "info"); + + ctx->side_streams_config = GF_TRUE; + return GF_OK; +} + +GF_Err nhmldump_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 cid; + char *mime=NULL, *name; + const GF_PropertyValue *p; + GF_NHMLDumpCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid_nhml); + gf_filter_pid_remove(ctx->opid_mdia); + if (ctx->opid_info) gf_filter_pid_remove(ctx->opid_info); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p) return GF_NOT_SUPPORTED; + cid = p->value.uint; + + if (ctx->codecid == cid) { + return GF_OK; + } + ctx->codecid = cid; + + if (ctx->codecidoti = ctx->codecid; + } else { + ctx->oti = gf_codecid_oti(ctx->codecid); + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + ctx->streamtype = p ? p->value.uint : GF_STREAM_UNKNOWN; + + if (!ctx->opid_nhml && !ctx->filep) + ctx->opid_nhml = gf_filter_pid_new(filter); + + ctx->side_streams_config = GF_FALSE; + if (ctx->nhmlonly) + ctx->side_streams_config = GF_TRUE; + + ctx->is_dims = GF_FALSE; + if ((ctx->codecid == GF_CODECID_DIMS) && ctx->dims) { + if (ctx->opid_mdia) { + gf_filter_pid_remove(ctx->opid_mdia); + ctx->opid_mdia = NULL; + } + ctx->is_dims = GF_TRUE; + ctx->side_streams_config = GF_TRUE; + } + + //file pointer set, we act as a sink, send play + if (!ctx->ipid && ctx->filep) { + GF_FilterEvent evt; + GF_FEVT_INIT(evt, GF_FEVT_PLAY, pid); + gf_filter_pid_send_event(pid, &evt); + } + ctx->ipid = pid; + + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); + ctx->sr = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS); + ctx->chan = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT); + ctx->bps = p ? gf_audio_fmt_bit_depth(p->value.uint) : 16; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + ctx->w = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + ctx->h = p ? p->value.uint : 0; + + name = (char*) gf_codecid_name(ctx->codecid); + if (ctx->exporter) { + if (ctx->w && ctx->h) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting %s - Size %dx%d\n", name, ctx->w, ctx->h)); + } else if (ctx->sr && ctx->chan) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting %s - SampleRate %d %d channels %d bits per sample\n", name, ctx->sr, ctx->chan, ctx->bps)); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting %s\n", name)); + } + } + + if (ctx->opid_nhml) { + char *ext; + mime = ctx->dims ? "application/dims" : "application/x-nhml"; + ext = ctx->dims ? "dims" : "nhml"; + + if (ctx->name) { + char fileName[GF_MAX_PATH+1]; + strncpy(fileName, ctx->name, GF_MAX_PATH); + fileName[GF_MAX_PATH] = 0; + name = gf_file_ext_start(fileName); + if (name) { + name[0] = 0; + gf_filter_pid_set_property(ctx->opid_nhml, GF_PROP_PID_OUTPATH, &PROP_STRING(ctx->name) ); + } else { + strcat(fileName, ".nhml"); + gf_filter_pid_set_property(ctx->opid_nhml, GF_PROP_PID_OUTPATH, &PROP_STRING(fileName) ); + } + } + + gf_filter_pid_set_property(ctx->opid_nhml, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); + gf_filter_pid_set_property(ctx->opid_nhml, GF_PROP_PID_MIME, &PROP_STRING(mime) ); + gf_filter_pid_set_property(ctx->opid_nhml, GF_PROP_PID_FILE_EXT, &PROP_STRING(ext) ); + } + + + ctx->first = GF_TRUE; + ctx->is_stpp = (cid==GF_CODECID_SUBS_XML) ? GF_TRUE : GF_FALSE; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DURATION); + if (p) ctx->duration = p->value.lfrac; + + + if (ctx->opid_nhml) + gf_filter_pid_set_name(ctx->opid_nhml, "nhml"); + + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + + return GF_OK; +} + +#define NHML_PRINT_4CC(_code, _pname, _name) \ + if (_code) p = gf_filter_pid_get_property(ctx->ipid, _code); \ + else p = gf_filter_pid_get_property_str(ctx->ipid, _pname); \ + if (p) { \ + sprintf(nhml, "%s=\"%s\" ", _name, gf_4cc_to_str(p->value.uint)); \ + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); \ + } + +#define NHML_PRINT_UINT(_code, _pname, _name) \ + if (_code) p = gf_filter_pid_get_property(ctx->ipid, _code); \ + else p = gf_filter_pid_get_property_str(ctx->ipid, _pname); \ + if (p) { \ + sprintf(nhml, "%s=\"%d\" ", _name, p->value.uint); \ + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); \ + } + +#define NHML_PRINT_STRING(_code, _pname, _name) \ + if (_code) p = gf_filter_pid_get_property(ctx->ipid, _code); \ + else p = gf_filter_pid_get_property_str(ctx->ipid, _pname); \ + if (p) { \ + sprintf(nhml, "%s=\"%s\" ", _name, p->value.string); \ + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); \ + } + + + +static void nhmldump_send_header(GF_NHMLDumpCtx *ctx) +{ + GF_FilterPacket *dst_pck; + char nhml[1024]; + u32 size; + u8 *output; + const GF_PropertyValue *p; + + ctx->szRootName = "NHNTStream"; + if (ctx->dims) { + ctx->szRootName = "DIMSStream"; + } + + if (!ctx->filep) { + sprintf(nhml, "\n"); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + + /*write header*/ + sprintf(nhml, "<%s version=\"1.0\" ", ctx->szRootName); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + + + NHML_PRINT_UINT(GF_PROP_PID_ID, NULL, "trackID") + NHML_PRINT_UINT(GF_PROP_PID_TIMESCALE, NULL, "timeScale") + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_IN_IOD); + if (p && p->value.boolean) { + sprintf(nhml, "inRootOD=\"yes\" "); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + + if (ctx->oti && (ctx->otistreamtype, ctx->oti); + gf_bs_write_data(ctx->bs_w, nhml, (u32)strlen(nhml)); + } else { + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_SUBTYPE); + if (p) { + sprintf(nhml, "%s=\"%s\" ", "mediaType", gf_4cc_to_str(p->value.uint)); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + + NHML_PRINT_4CC(GF_PROP_PID_ISOM_SUBTYPE, "mediaSubType", "mediaSubType") + } else { + NHML_PRINT_4CC(GF_PROP_PID_CODECID, NULL, "codecID") + } + } + + if (ctx->w && ctx->h) { + //compatibility with old arch, we might want to remove this + switch (ctx->streamtype) { + case GF_STREAM_VISUAL: + case GF_STREAM_SCENE: + sprintf(nhml, "width=\"%d\" height=\"%d\" ", ctx->w, ctx->h); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + break; + default: + break; + } + } + else if (ctx->sr && ctx->chan) { + sprintf(nhml, "sampleRate=\"%d\" numChannels=\"%d\" ", ctx->sr, ctx->chan); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + sprintf(nhml, "sampleRate=\"%d\" numChannels=\"%d\" ", ctx->sr, ctx->chan); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_AUDIO_FORMAT); + if (p) + sprintf(nhml, "bitsPerSample=\"%d\" ", gf_audio_fmt_bit_depth(p->value.uint)); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + + NHML_PRINT_4CC(0, "codec_vendor", "codecVendor") + NHML_PRINT_UINT(0, "codec_version", "codecVersion") + NHML_PRINT_UINT(0, "codec_revision", "codecRevision") + NHML_PRINT_STRING(0, "compressor_name", "compressorName") + NHML_PRINT_UINT(0, "temporal_quality", "temporalQuality") + NHML_PRINT_UINT(0, "spatial_quality", "spatialQuality") + NHML_PRINT_UINT(0, "hres", "horizontalResolution") + NHML_PRINT_UINT(0, "vres", "verticalResolution") + NHML_PRINT_UINT(GF_PROP_PID_BIT_DEPTH_Y, NULL, "bitDepth") + + NHML_PRINT_STRING(0, "meta:xmlns", "xml_namespace") + NHML_PRINT_STRING(0, "meta:schemaloc", "xml_schema_location") + NHML_PRINT_STRING(0, "meta:mime", "mime_type") + + NHML_PRINT_STRING(0, "meta:config", "config") + NHML_PRINT_STRING(0, "meta:aux_mimes", "aux_mime_type") + + if (ctx->codecid == GF_CODECID_DIMS) { + if (gf_filter_pid_get_property_str(ctx->ipid, "meta:xmlns")==NULL) { + sprintf(nhml, "xmlns=\"http://www.3gpp.org/richmedia\" "); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + + NHML_PRINT_UINT(0, "dims:profile", "profile") + NHML_PRINT_UINT(0, "dims:level", "level") + NHML_PRINT_UINT(0, "dims:pathComponents", "pathComponents") + + p = gf_filter_pid_get_property_str(ctx->ipid, "dims:fullRequestHost"); + if (p) { + sprintf(nhml, "useFullRequestHost=\"%s\" ", p->value.boolean ? "yes" : "no"); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + p = gf_filter_pid_get_property_str(ctx->ipid, "dims:streamType"); + if (p) { + sprintf(nhml, "stream_type=\"%s\" ", p->value.boolean ? "primary" : "secondary"); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + p = gf_filter_pid_get_property_str(ctx->ipid, "dims:redundant"); + if (p) { + sprintf(nhml, "contains_redundant=\"%s\" ", (p->value.uint==1) ? "main" : ((p->value.uint==1) ? "redundant" : "main+redundant") ); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + NHML_PRINT_UINT(0, "dims:scriptTypes", "scriptTypes") + } + + //send DCD + if (ctx->opid_info) { + sprintf(nhml, "specificInfoFile=\"%s\" ", gf_file_basename(ctx->info_file) ); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + + dst_pck = gf_filter_pck_new_shared(ctx->opid_info, ctx->dcfg, ctx->dcfg_size, NULL); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + gf_filter_pck_set_readonly(dst_pck); + gf_filter_pck_send(dst_pck); + } + + NHML_PRINT_STRING(0, "meta:encoding", "encoding") + NHML_PRINT_STRING(0, "meta:contentEncoding", "content_encoding") + ctx->uncompress = GF_FALSE; + if (p) { + if (!strcmp(p->value.string, "deflate")) ctx->uncompress = GF_TRUE; + else { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[NHMLMx] content_encoding %s not supported\n", p->value.string )); + } + } + + if (ctx->opid_mdia) { + sprintf(nhml, "baseMediaFile=\"%s\" ", gf_file_basename(ctx->media_file) ); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + sprintf(nhml, ">\n"); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + + gf_bs_get_content_no_truncate(ctx->bs_w, &ctx->nhml_buffer, &size, &ctx->nhml_buffer_size); + + if (ctx->filep) { + gf_fwrite(ctx->nhml_buffer, size, ctx->filep); + return; + } + + dst_pck = gf_filter_pck_new_alloc(ctx->opid_nhml, size, &output); + memcpy(output, ctx->nhml_buffer, size); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_FALSE); + gf_filter_pck_send(dst_pck); +} + +static void nhmldump_send_dims(GF_NHMLDumpCtx *ctx, char *data, u32 data_size, GF_FilterPacket *pck) +{ + char nhml[1024]; + u32 size; + u8 *output; + GF_FilterPacket *dst_pck; + u64 dts = gf_filter_pck_get_dts(pck); + u64 cts = gf_filter_pck_get_cts(pck); + + if (dts==GF_FILTER_NO_TS) dts = cts; + if (cts==GF_FILTER_NO_TS) cts = dts; + + if (!ctx->bs_r) ctx->bs_r = gf_bs_new(data, data_size, GF_BITSTREAM_READ); + else gf_bs_reassign_buffer(ctx->bs_r, data, data_size); + + while (gf_bs_available(ctx->bs_r)) { + u64 pos = gf_bs_get_position(ctx->bs_r); + size = gf_bs_read_u16(ctx->bs_r); + u8 flags = gf_bs_read_u8(ctx->bs_r); + u8 prev; + + if (pos+size+2 > data_size) + break; + + prev = data[pos+2+size]; + data[pos+2+size] = 0; + + + sprintf(nhml, "bs_w, nhml, (u32) strlen(nhml)); + + /*DIMS flags*/ + if (flags & GF_DIMS_UNIT_S) { + sprintf(nhml, " is-Scene=\"yes\""); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + if (flags & GF_DIMS_UNIT_M) { + sprintf(nhml, " is-RAP=\"yes\""); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + if (flags & GF_DIMS_UNIT_I) { + sprintf(nhml, " is-redundant=\"yes\""); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + if (flags & GF_DIMS_UNIT_D) { + sprintf(nhml, " redundant-exit=\"yes\""); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + if (flags & GF_DIMS_UNIT_P) { + sprintf(nhml, " priority=\"high\""); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + if (flags & GF_DIMS_UNIT_C) { + sprintf(nhml, " compressed=\"yes\""); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + sprintf(nhml, ">"); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + if (ctx->uncompress && (flags & GF_DIMS_UNIT_C)) { +#ifndef GPAC_DISABLE_ZLIB + char svg_data[2049]; + int err; + u32 done = 0; + z_stream d_stream; + d_stream.zalloc = (alloc_func)0; + d_stream.zfree = (free_func)0; + d_stream.opaque = (voidpf)0; + d_stream.next_in = (Bytef*) data+pos+3; + d_stream.avail_in = size-1; + d_stream.next_out = (Bytef*)svg_data; + d_stream.avail_out = 2048; + + err = inflateInit(&d_stream); + if (err == Z_OK) { + while ((s32) d_stream.total_in < size-1) { + err = inflate(&d_stream, Z_NO_FLUSH); + if (err < Z_OK) break; + svg_data[d_stream.total_out - done] = 0; + gf_bs_write_data(ctx->bs_w, svg_data, (u32) strlen(svg_data)); + + if (err== Z_STREAM_END) break; + done = (u32) d_stream.total_out; + d_stream.avail_out = 2048; + d_stream.next_out = (Bytef*)svg_data; + } + inflateEnd(&d_stream); + } +#else + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("Error: your version of GPAC was compiled with no libz support.")); + gf_bs_del(ctx->bs_r); + return; +#endif + } else { + gf_bs_write_data(ctx->bs_w, data+pos+3, size-1); + } + sprintf(nhml, "\n"); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + + data[pos+2+size] = prev; + gf_bs_skip_bytes(ctx->bs_r, size-1); + } + + gf_bs_get_content_no_truncate(ctx->bs_w, &ctx->nhml_buffer, &size, &ctx->nhml_buffer_size); + + if (ctx->filep) { + gf_fwrite(ctx->nhml_buffer, size, ctx->filep); + return; + } + + dst_pck = gf_filter_pck_new_alloc(ctx->opid_nhml, size, &output); + memcpy(output, ctx->nhml_buffer, size); + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE); + gf_filter_pck_send(dst_pck); +} + + +static void nhmldump_pck_property(GF_NHMLDumpCtx *ctx, u32 p4cc, const char *pname, const GF_PropertyValue *att) +{ + u32 i; + char nhml[1024]; + char pval[GF_PROP_DUMP_ARG_SIZE]; + if (!pname) pname = gf_props_4cc_get_name(p4cc); + + sprintf(nhml, "%s=\"", pname ? pname : gf_4cc_to_str(p4cc)); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + + switch (att->type) { + case GF_PROP_DATA: + case GF_PROP_CONST_DATA: + case GF_PROP_DATA_NO_COPY: + sprintf(nhml, "0x"); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + for (i=0; ivalue.data.size; i++) { + sprintf(nhml, "%02X", (unsigned char) att->value.data.ptr[i]); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + nhml[0] = 0; + break; + default: + sprintf(nhml, "%s", gf_props_dump_val(att, pval, GF_FALSE, NULL) ); + break; + } + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + sprintf(nhml, "\""); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); +} + +static void nhmldump_send_frame(GF_NHMLDumpCtx *ctx, char *data, u32 data_size, GF_FilterPacket *pck) +{ + GF_FilterPacket *dst_pck; + char nhml[1024]; + const GF_PropertyValue *p; + u32 size; + u8 *output; + GF_FilterSAPType sap = gf_filter_pck_get_sap(pck); + u64 dts = gf_filter_pck_get_dts(pck); + u64 cts = gf_filter_pck_get_cts(pck); + + if (dts==GF_FILTER_NO_TS) dts = cts; + if (cts==GF_FILTER_NO_TS) cts = dts; + + ctx->pck_num++; + sprintf(nhml, "pck_num, dts, data_size); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + if (ctx->pckp || (cts != dts) ) { + sprintf(nhml, "CTSOffset=\"%d\" ", (s32) ((s64)cts - (s64)dts)); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + if (sap==GF_FILTER_SAP_1) { + sprintf(nhml, "isRAP=\"yes\" "); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } else if (sap) { + sprintf(nhml, "SAPType=\"%d\" ", sap); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } else if (ctx->pckp) { + sprintf(nhml, "isRAP=\"no\" "); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + if ((sap==GF_FILTER_SAP_4) || (sap==GF_FILTER_SAP_4_PROL)) { + s32 roll = gf_filter_pck_get_roll_info(pck); + sprintf(nhml, "SAPType=\"4\" %s=\"%d\" ", (sap==GF_FILTER_SAP_4_PROL) ? "prol" : "roll", roll); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + } + + if (ctx->pckp) { + u64 bo; + u32 duration, idx; + sprintf(nhml, "mediaOffset=\""LLU"\" ", ctx->mdia_pos); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + + bo = gf_filter_pck_get_byte_offset(pck); + if (bo!=GF_FILTER_NO_BO) { + sprintf(nhml, "sourceByteOffset=\""LLU"\" ", bo); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + duration = gf_filter_pck_get_duration(pck); + if (duration) { + sprintf(nhml, "duration=\"%d\" ", duration); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + idx = gf_filter_pck_get_carousel_version(pck); + if (idx) { + sprintf(nhml, "carouselVersion=\"%d\" ", idx); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + idx = 0; + while (1) { + u32 prop_4cc; + const char *prop_name; + p = gf_filter_pck_enum_properties(pck, &idx, &prop_4cc, &prop_name); + if (!p) break; + if (prop_4cc == GF_PROP_PCK_SUBS) continue; + nhmldump_pck_property(ctx, prop_4cc, prop_name, p); + } + } + + if (ctx->chksum) { + if (ctx->chksum==1) { + u32 crc = gf_crc_32(data, data_size); + sprintf(nhml, "crc=\"%08X\" ", crc); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } else { + u32 j; + u8 hash[GF_SHA1_DIGEST_SIZE]; + gf_sha1_csum(data, data_size, hash); + sprintf(nhml, "sha1=\""); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + for (j=0; j<20; j++) { + sprintf(nhml, "%02X", hash[j]); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + sprintf(nhml, "\" "); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + } + + sprintf(nhml, ">\n"); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + + p = gf_filter_pck_get_property(pck, GF_PROP_PCK_SUBS); + if (p) { + u32 offset_in_sample = 0; + Bool first_subs = GF_TRUE; + if (!ctx->bs_r) ctx->bs_r = gf_bs_new(p->value.data.ptr, p->value.data.size, GF_BITSTREAM_READ); + else gf_bs_reassign_buffer(ctx->bs_r, p->value.data.ptr, p->value.data.size); + + //(data) binary blob containing N [(u32)flags(u32)size(u32)reserved(u8)priority(u8) discardable] + while (gf_bs_available(ctx->bs_r)) { + u32 s_flags = gf_bs_read_u32(ctx->bs_r); + u32 s_size = gf_bs_read_u32(ctx->bs_r); + u32 s_res = gf_bs_read_u32(ctx->bs_r); + u8 s_prio = gf_bs_read_u8(ctx->bs_r); + u8 s_discard = gf_bs_read_u8(ctx->bs_r); + + + if (offset_in_sample + s_size > data_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("Wrong subsample info: sample size %d vs subsample offset+size %dn", data_size, offset_in_sample + s_size)); + break; + } + + if (ctx->is_stpp && ctx->nhmlonly) { + if (first_subs) { + sprintf(nhml, "\n"); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + + gf_bs_write_data(ctx->bs_w, data, s_size); + + sprintf(nhml, "\n"); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } else { + u32 d_size; + if (ctx->b64_buffer_size<2*s_size) { + ctx->b64_buffer_size = 2 * s_size; + ctx->b64_buffer = gf_realloc(ctx->b64_buffer, ctx->b64_buffer_size); + } + d_size = gf_base64_encode(data + offset_in_sample, s_size, ctx->b64_buffer, ctx->b64_buffer_size); + ctx->b64_buffer[d_size] = 0; + sprintf(nhml, "bs_w, nhml, (u32) strlen(nhml)); + gf_bs_write_data(ctx->bs_w, ctx->b64_buffer, d_size); + sprintf(nhml, "\">\n"); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + } else { + sprintf(nhml, "\n", s_size, s_flags, s_res, s_prio, s_discard); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + first_subs = GF_FALSE; + } + } else if (ctx->is_stpp && ctx->nhmlonly) { + sprintf(nhml, "bs_w, nhml, (u32) strlen(nhml)); + gf_bs_write_data(ctx->bs_w, data, data_size); + sprintf(nhml, "]]>\n"); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + } + sprintf(nhml, "\n"); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + + gf_bs_get_content_no_truncate(ctx->bs_w, &ctx->nhml_buffer, &size, &ctx->nhml_buffer_size); + + if (ctx->filep) { + gf_fwrite(ctx->nhml_buffer, size, ctx->filep); + return; + } + + dst_pck = gf_filter_pck_new_alloc(ctx->opid_nhml, size, &output); + memcpy(output, ctx->nhml_buffer, size); + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE); + gf_filter_pck_send(dst_pck); + + ctx->mdia_pos += data_size; + + if (ctx->opid_mdia) { + //send data packet + dst_pck = gf_filter_pck_new_ref(ctx->opid_mdia, data, data_size, pck); + gf_filter_pck_merge_properties(pck, dst_pck); + //keep byte offset ? +// gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO); + + gf_filter_pck_set_framing(dst_pck, ctx->first, GF_FALSE); + gf_filter_pck_send(dst_pck); + } +} + + +GF_Err nhmldump_process(GF_Filter *filter) +{ + GF_NHMLDumpCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck; + char *data; + u32 pck_size; + + if (!ctx->side_streams_config) { + return nhmldump_config_side_stream(filter, ctx); + } + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (ctx->bs_w && ctx->szRootName) { + char nhml[1024]; + u32 size; + gf_bs_reassign_buffer(ctx->bs_w, ctx->nhml_buffer, ctx->nhml_buffer_size); + sprintf(nhml, "\n", ctx->szRootName); + gf_bs_write_data(ctx->bs_w, nhml, (u32) strlen(nhml)); + + gf_bs_get_content_no_truncate(ctx->bs_w, &ctx->nhml_buffer, &size, &ctx->nhml_buffer_size); + + if (ctx->filep) { + gf_fwrite(ctx->nhml_buffer, size, ctx->filep); + } else { + GF_FilterPacket *dst_pck; + u8 *output; + dst_pck = gf_filter_pck_new_alloc(ctx->opid_nhml, size, &output); + memcpy(output, ctx->nhml_buffer, size); + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_TRUE); + gf_filter_pck_send(dst_pck); + } + ctx->szRootName = NULL; + } + if (ctx->opid_nhml) gf_filter_pid_set_eos(ctx->opid_nhml); + if (ctx->opid_mdia) gf_filter_pid_set_eos(ctx->opid_mdia); + if (ctx->opid_info) gf_filter_pid_set_eos(ctx->opid_info); + return GF_EOS; + } + return GF_OK; + } + + if (!ctx->bs_w) ctx->bs_w = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs_w, ctx->nhml_buffer, ctx->nhml_buffer_size); + + if (ctx->first) { + nhmldump_send_header(ctx); + gf_bs_reassign_buffer(ctx->bs_w, ctx->nhml_buffer, ctx->nhml_buffer_size); + } + + //get media data + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + + //send data + if (ctx->is_dims) { + nhmldump_send_dims(ctx, data, pck_size, pck); + } else { + nhmldump_send_frame(ctx, data, pck_size, pck); + } + ctx->first = GF_FALSE; + + + if (ctx->exporter) { + u32 timescale = gf_filter_pck_get_timescale(pck); + u64 ts = gf_filter_pck_get_cts(pck); + gf_set_progress("Exporting", ts*ctx->duration.den, ctx->duration.num*timescale); + } + + gf_filter_pid_drop_packet(ctx->ipid); + + return GF_OK; +} + +static GF_Err nhmldump_initialize(GF_Filter *filter) +{ + return GF_OK; +} + +static void nhmldump_finalize(GF_Filter *filter) +{ + GF_NHMLDumpCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs_r) gf_bs_del(ctx->bs_r); + if (ctx->bs_w) gf_bs_del(ctx->bs_w); + if (ctx->nhml_buffer) gf_free(ctx->nhml_buffer); + if (ctx->b64_buffer) gf_free(ctx->b64_buffer); + if (ctx->info_file) gf_free(ctx->info_file); + if (ctx->media_file) gf_free(ctx->media_file); +} + +static const GF_FilterCapability NHMLDumpCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "nhml|dims|dml"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "application/x-nhml|application/dims"), +}; + +#define OFFS(_n) #_n, offsetof(GF_NHMLDumpCtx, _n) +static const GF_FilterArgs NHMLDumpArgs[] = +{ + { OFFS(exporter), "compatibility with old exporter, displays export results", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(dims), "use DIMS mode", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(name), "set output name of files produced (needed media/info files refered to from XML", GF_PROP_STRING, NULL, NULL, 0}, + { OFFS(nhmlonly), "only dump NHML info, not media", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(pckp), "full NHML dump", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(chksum), "insert frame checksum\n" + "- none: no checksum\n" + "- crc: CRC32 checksum\n" + "- sha1: SHA1 checksum" + "", GF_PROP_UINT, "none", "none|crc|sha1", GF_FS_ARG_HINT_ADVANCED}, + { OFFS(filep), "dump directly to the given FILE pointer (used by MP4Box)", GF_PROP_POINTER, NULL, NULL, GF_FS_ARG_HINT_HIDE}, + {0} +}; + + +GF_FilterRegister NHMLDumpRegister = { + .name = "nhmlw", + GF_FS_SET_DESCRIPTION("NHML writer") + GF_FS_SET_HELP("This filter converts a single stream to an NHML output file.\n" + "NHML documentation is available at https://wiki.gpac.io/NHML-Format\n") + .private_size = sizeof(GF_NHMLDumpCtx), + .args = NHMLDumpArgs, + .initialize = nhmldump_initialize, + .finalize = nhmldump_finalize, + SETCAPS(NHMLDumpCaps), + .configure_pid = nhmldump_configure_pid, + .process = nhmldump_process +}; + +const GF_FilterRegister *nhmldump_register(GF_FilterSession *session) +{ + return &NHMLDumpRegister; +} + diff --git a/src/filters/write_nhnt.c b/src/filters/write_nhnt.c new file mode 100644 index 0000000..1ce1b5b --- /dev/null +++ b/src/filters/write_nhnt.c @@ -0,0 +1,390 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2017-2018 + * All rights reserved + * + * This file is part of GPAC / NHNT stream to file filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +#include + + +typedef struct +{ + //opts + Bool exporter, large; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid_nhnt, *opid_mdia, *opid_info; + + u32 codecid; + u32 streamtype; + u32 oti; + + const char *dcfg; + u32 dcfg_size; + + GF_Fraction64 duration; + Bool first; + + GF_BitStream *bs; + u64 mdia_pos; + Bool configure_side_streams; +} GF_NHNTDumpCtx; + +GF_Err nhntdump_config_side_streams(GF_Filter *filter, GF_NHNTDumpCtx *ctx) +{ + const GF_PropertyValue *p; + char *name, *url, *res_name; + char fileName[GF_MAX_PATH+1]; + GF_FileIO *gfio = NULL; + GF_Err e; + + url = gf_filter_pid_get_destination(ctx->opid_nhnt); + if (url) { + if (!strncmp(url, "gfio://", 7)) { + gfio = gf_fileio_from_url(url); + strncpy(fileName, gf_fileio_translate_url(url), GF_MAX_PATH); + } else { + strncpy(fileName, url, GF_MAX_PATH); + } + fileName[GF_MAX_PATH] = 0; + gf_free(url); + } else { + strcpy(fileName, "dump"); + } + + name = gf_file_ext_start(fileName); + if (name) { + name[0] = 0; + } + if (!ctx->opid_mdia) + ctx->opid_mdia = gf_filter_pid_new(filter); + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_DECODER_CONFIG); + if (p) { + ctx->dcfg = p->value.data.ptr; + ctx->dcfg_size = p->value.data.size; + + if (!ctx->opid_info) + ctx->opid_info = gf_filter_pid_new(filter); + + } else if (ctx->opid_info) { + gf_filter_pid_remove(ctx->opid_info); + } + + gf_filter_pid_set_property(ctx->opid_mdia, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); + gf_filter_pid_set_property(ctx->opid_mdia, GF_PROP_PID_FILE_EXT, &PROP_STRING("media") ); + gf_filter_pid_set_property(ctx->opid_mdia, GF_PROP_PID_MIME, &PROP_STRING("application/x-nhnt") ); + + if (!ctx->exporter) { + name = gf_file_ext_start(fileName); + if (name) name[0] = 0; + strcat(fileName, ".media"); + if (gfio) { + res_name = (char *) gf_fileio_factory(gfio, gf_file_basename(fileName) ); + } else { + res_name = fileName; + } + gf_filter_pid_set_property(ctx->opid_mdia, GF_PROP_PID_OUTPATH, &PROP_STRING(res_name) ); + + GF_Filter *o_media = gf_filter_connect_destination(filter, res_name, &e); + if (o_media) gf_filter_set_source(o_media, filter, NULL); + } + + + if (ctx->opid_info) { + gf_filter_pid_set_property(ctx->opid_info, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); + gf_filter_pid_set_property(ctx->opid_info, GF_PROP_PID_FILE_EXT, &PROP_STRING("info") ); + gf_filter_pid_set_property(ctx->opid_info, GF_PROP_PID_MIME, &PROP_STRING("application/x-nhnt") ); + + if (!ctx->exporter) { + name = gf_file_ext_start(fileName); + if (name) name[0] = 0; + strcat(fileName, ".info"); + if (gfio) { + res_name = (char *) gf_fileio_factory(gfio, gf_file_basename(fileName) ); + } else { + res_name = fileName; + } + gf_filter_pid_set_property(ctx->opid_info, GF_PROP_PID_OUTPATH, &PROP_STRING(res_name) ); + + GF_Filter *o_info = gf_filter_connect_destination(filter, res_name, &e); + if (o_info) gf_filter_set_source(o_info, filter, NULL); + } + + } + ctx->configure_side_streams = GF_FALSE; + return GF_OK; +} + +GF_Err nhntdump_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 cid, chan, sr, bps, w, h; + const char *name; + const GF_PropertyValue *p; + GF_NHNTDumpCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid_nhnt); + gf_filter_pid_remove(ctx->opid_mdia); + if (ctx->opid_info) gf_filter_pid_remove(ctx->opid_info); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p) return GF_NOT_SUPPORTED; + cid = p->value.uint; + + if (ctx->codecid == cid) { + return GF_OK; + } + ctx->codecid = cid; + ctx->configure_side_streams = GF_TRUE; + + if (ctx->codecidoti = ctx->codecid; + else { + ctx->oti = gf_codecid_oti(ctx->codecid); + } + if (!ctx->oti) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("CodecID %s has no mapping to MPEG-4 systems, cannot use NHNT. Use NHML instead\n", gf_4cc_to_str(cid) )); + return GF_NOT_SUPPORTED; + } + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); + ctx->streamtype = p ? p->value.uint : GF_STREAM_UNKNOWN; + + if (!ctx->opid_nhnt) + ctx->opid_nhnt = gf_filter_pid_new(filter); + + ctx->ipid = pid; + + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); + sr = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS); + chan = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT); + bps = p ? gf_audio_fmt_bit_depth(p->value.uint) : 16; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); + w = p ? p->value.uint : 0; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); + h = p ? p->value.uint : 0; + + + name = gf_codecid_name(ctx->codecid); + if (ctx->exporter) { + if (w && h) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting %s - Size %dx%d\n", name, w, h)); + } else if (sr && chan) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting %s - SampleRate %d %d channels %d bits per sample\n", name, sr, chan, bps)); + } else { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting %s\n", name)); + } + } + + gf_filter_pid_set_property(ctx->opid_nhnt, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); + gf_filter_pid_set_property(ctx->opid_nhnt, GF_PROP_PID_FILE_EXT, &PROP_STRING("nhnt") ); + gf_filter_pid_set_property(ctx->opid_nhnt, GF_PROP_PID_MIME, &PROP_STRING("application/x-nhnt") ); + + + ctx->first = GF_TRUE; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DURATION); + if (p) ctx->duration = p->value.lfrac; + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + return GF_OK; +} + +GF_Err nhntdump_process(GF_Filter *filter) +{ + GF_NHNTDumpCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + u8 *data; + u8 *output; + u32 size, pck_size; + u64 dts, cts; + + if (ctx->configure_side_streams) { + return nhntdump_config_side_streams(filter, ctx); + } + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid_nhnt); + gf_filter_pid_set_eos(ctx->opid_mdia); + if (ctx->opid_info) gf_filter_pid_set_eos(ctx->opid_info); + return GF_EOS; + } + return GF_OK; + } + + if (ctx->first) { + u32 nhnt_hdr_size = 4+1+1+1+2+3+4+4+4; + const GF_PropertyValue *p; + + dst_pck = gf_filter_pck_new_alloc(ctx->opid_nhnt, nhnt_hdr_size, &output); + if (!ctx->bs) ctx->bs = gf_bs_new(output, nhnt_hdr_size, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs, output, nhnt_hdr_size); + + /*write header*/ + /*'NHnt' format*/ + gf_bs_write_data(ctx->bs, ctx->large ? "NHnl" : "NHnt", 4); + /*version 1*/ + gf_bs_write_u8(ctx->bs, 0); + /*streamType*/ + gf_bs_write_u8(ctx->bs, ctx->streamtype); + /*OTI*/ + gf_bs_write_u8(ctx->bs, ctx->oti); + /*reserved*/ + gf_bs_write_u16(ctx->bs, 0); + /*bufferDB*/ + //p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_DB_SIZE); + gf_bs_write_u24(ctx->bs, 0); + /*avg BitRate*/ + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_BITRATE); + gf_bs_write_u32(ctx->bs, p ? p->value.uint : 0); + /*max bitrate*/ + //p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_MAX_RATE); + gf_bs_write_u32(ctx->bs, 0); + /*timescale*/ + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_TIMESCALE); + gf_bs_write_u32(ctx->bs, p ? p->value.uint : 1000); + + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_FALSE); + gf_filter_pck_send(dst_pck); + + if (ctx->opid_info) { + dst_pck = gf_filter_pck_new_shared(ctx->opid_info, ctx->dcfg, ctx->dcfg_size, NULL); + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE); + gf_filter_pck_set_readonly(dst_pck); + gf_filter_pck_send(dst_pck); + } + } + + //get media data + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + + //nhnt data size + size = 3 + 1 + 3*(ctx->large ? 8 : 4); + dst_pck = gf_filter_pck_new_alloc(ctx->opid_nhnt, size, &output); + //send nhnt data + gf_bs_reassign_buffer(ctx->bs, output, size); + + /*dump nhnt info*/ + gf_bs_write_u24(ctx->bs, pck_size); + gf_bs_write_int(ctx->bs, gf_filter_pck_get_sap(pck) ? 1 : 0, 1); + /*AU start & end flag always true*/ + gf_bs_write_int(ctx->bs, 1, 1); + gf_bs_write_int(ctx->bs, 1, 1); + /*reserved*/ + gf_bs_write_int(ctx->bs, 0, 3); + gf_bs_write_int(ctx->bs, gf_filter_pck_get_sap(pck) ? 0 : 1, 2); + + dts = gf_filter_pck_get_dts(pck); + cts = gf_filter_pck_get_cts(pck); + if (ctx->large) { + gf_bs_write_u64(ctx->bs, ctx->mdia_pos); + gf_bs_write_u64(ctx->bs, cts); + gf_bs_write_u64(ctx->bs, dts); + } else { + gf_bs_write_u32(ctx->bs, (u32) ctx->mdia_pos); + gf_bs_write_u32(ctx->bs, (u32) cts); + gf_bs_write_u32(ctx->bs, (u32) dts); + } + ctx->mdia_pos += pck_size; + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE); + gf_filter_pck_send(dst_pck); + + //send data packet + dst_pck = gf_filter_pck_new_ref(ctx->opid_mdia, data, pck_size, pck); + gf_filter_pck_merge_properties(pck, dst_pck); + //keep byte offset ? +// gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO); + + gf_filter_pck_set_framing(dst_pck, ctx->first, GF_FALSE); + gf_filter_pck_send(dst_pck); + + ctx->first = GF_FALSE; + + if (ctx->exporter) { + u32 timescale = gf_filter_pck_get_timescale(pck); + u64 ts = gf_filter_pck_get_cts(pck); + gf_set_progress("Exporting", ts*ctx->duration.den, ctx->duration.num*timescale); + } + + gf_filter_pid_drop_packet(ctx->ipid); + + return GF_OK; +} + +static void nhntdump_finalize(GF_Filter *filter) +{ + GF_NHNTDumpCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs) gf_bs_del(ctx->bs); +} + +static const GF_FilterCapability NHNTDumpCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "nhnt"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "application/x-nhnt"), +}; + +#define OFFS(_n) #_n, offsetof(GF_NHNTDumpCtx, _n) +static const GF_FilterArgs NHNTDumpArgs[] = +{ + { OFFS(exporter), "compatibility with old exporter, displays export results", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(large), "use large file mode", GF_PROP_BOOL, "false", NULL, 0}, + {0} +}; + + +GF_FilterRegister NHNTDumpRegister = { + .name = "nhntw", + GF_FS_SET_DESCRIPTION("NHNT writer") + GF_FS_SET_HELP("This filter converts a single stream to an NHNT output file.\n" + "NHNT documentation is available at https://wiki.gpac.io/NHNT-Format\n") + .private_size = sizeof(GF_NHNTDumpCtx), + .args = NHNTDumpArgs, + .finalize = nhntdump_finalize, + SETCAPS(NHNTDumpCaps), + .configure_pid = nhntdump_configure_pid, + .process = nhntdump_process +}; + +const GF_FilterRegister *nhntdump_register(GF_FilterSession *session) +{ + return &NHNTDumpRegister; +} + diff --git a/src/filters/write_qcp.c b/src/filters/write_qcp.c new file mode 100644 index 0000000..0056b68 --- /dev/null +++ b/src/filters/write_qcp.c @@ -0,0 +1,381 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2018 + * All rights reserved + * + * This file is part of GPAC / QCP stream to file filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +#include + + +typedef struct +{ + //opts + Bool exporter, mpeg2; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + u32 codecid; + Bool first; + + GF_Fraction64 duration; + + char GUID[16]; + u32 qcp_type, needs_rate_byte; + QCPRateTable rtable[8]; + unsigned int *qcp_rates, rt_cnt; /*contains constants*/ + Bool has_qcp_pad; + Bool needs_final_pach; + u32 data_size; + u32 nb_frames; + +} GF_QCPMxCtx; + + +/*QCP codec GUIDs*/ +static const char *QCP_QCELP_GUID_1 = "\x41\x6D\x7F\x5E\x15\xB1\xD0\x11\xBA\x91\x00\x80\x5F\xB4\xB9\x7E"; +static const char *QCP_EVRC_GUID = "\x8D\xD4\x89\xE6\x76\x90\xB5\x46\x91\xEF\x73\x6A\x51\x00\xCE\xB4"; +static const char *QCP_SMV_GUID = "\x75\x2B\x7C\x8D\x97\xA7\x46\xED\x98\x5E\xD5\x3C\x8C\xC7\x5F\x84"; + +GF_Err qcpmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + u32 sr, chan, bps; + const GF_PropertyValue *p; + GF_QCPMxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p) return GF_NOT_SUPPORTED; + ctx->codecid = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); + if (!p) return GF_NOT_SUPPORTED; + sr = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS); + if (!p) return GF_NOT_SUPPORTED; + chan = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_AUDIO_FORMAT); + bps = p ? gf_audio_fmt_bit_depth(p->value.uint) : 16; + + + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); + } + + switch (ctx->codecid) { + case GF_CODECID_QCELP: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("qcp") ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING("audio/qcp") ); + ctx->qcp_type = 1; + memcpy(ctx->GUID, QCP_QCELP_GUID_1, sizeof(char)*16); + break; + case GF_CODECID_EVRC_PV: + case GF_CODECID_EVRC: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("evc") ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING("audio/qcp") ); + memcpy(ctx->GUID, QCP_EVRC_GUID, sizeof(char)*16); + ctx->qcp_type = 3; + break; + case GF_CODECID_SMV: + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("smv") ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING("audio/qcp") ); + ctx->qcp_type = 2; + memcpy(ctx->GUID, QCP_SMV_GUID, sizeof(char)*16); + break; + } + + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("qcp") ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING("audio/qcp") ); + ctx->first = GF_TRUE; + + if (ctx->exporter) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting %s - SampleRate %d %d channels %d bits per sample\n", gf_codecid_name(ctx->codecid), sr, chan, bps)); + } + + ctx->ipid = pid; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DURATION); + if (p) ctx->duration = p->value.lfrac; + + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_MEDIA_DATA_SIZE); + if (!p) { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[QCP] Unknown total media size, cannot write QCP file right away\n")); + ctx->data_size = 0; + } else { + ctx->data_size = (u32) p->value.longuint; + } + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_NB_FRAMES); + if (!p) { + GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[QCP] Unknown total number of media frames, cannot write QCP file\n")); + ctx->nb_frames = 0; + } else { + ctx->nb_frames = (u32) p->value.uint; + } + + if (!ctx->data_size || !ctx->nb_frames) { + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DISABLE_PROGRESSIVE, &PROP_UINT(GF_PID_FILE_PATCH_REPLACE) ); + ctx->needs_final_pach = GF_TRUE; + } + + return GF_OK; +} + +static void qcpmx_send_header(GF_QCPMxCtx *ctx, u32 data_size, u32 frame_count) +{ + const GF_PropertyValue *p; + Bool needs_rate_octet; + char szName[80]; + u32 i, tot_size, size, sample_size, avg_rate; + u32 block_size = 160; + u32 sample_rate = 8000; + GF_BitStream *bs; + u8 *output; + GF_FilterPacket *dst_pck; + + if (ctx->qcp_type==1) { + ctx->qcp_rates = (unsigned int*)GF_QCELP_RATE_TO_SIZE; + ctx->rt_cnt = GF_QCELP_RATE_TO_SIZE_NB; + } else { + ctx->qcp_rates = (unsigned int*)GF_SMV_EVRC_RATE_TO_SIZE; + ctx->rt_cnt = GF_SMV_EVRC_RATE_TO_SIZE_NB; + } + + /*dumps full table...*/ + for (i=0; irt_cnt; i++) { + ctx->rtable[i].rate_idx = ctx->qcp_rates[2*i]; + ctx->rtable[i].pck_size = ctx->qcp_rates[2*i+1]; + } + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FRAME_SIZE); + sample_size = p ? p->value.uint : block_size; + + /*check sample format - packetvideo doesn't include rate octet...*/ + needs_rate_octet = (ctx->codecid==GF_CODECID_EVRC_PV) ? GF_TRUE : GF_FALSE; + + if (needs_rate_octet) data_size += frame_count; + ctx->has_qcp_pad = (data_size % 2) ? GF_TRUE : GF_FALSE; + + p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_BITRATE); + if (p) avg_rate = p->value.uint; + else avg_rate = frame_count ? 8*data_size*sample_rate/frame_count/block_size : 0; + + /*QLCM + fmt + vrat + data*/ + size = tot_size = 4+ 8+150 + 8+8 + 8 + data_size; + /*pad is included in riff size*/ + if (ctx->has_qcp_pad) { + tot_size++; + } + size += 8; + size -= data_size; + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + bs = gf_bs_new(output, size, GF_BITSTREAM_WRITE); + + gf_bs_write_data(bs, "RIFF", 4); + gf_bs_write_u32_le(bs, tot_size); + gf_bs_write_data(bs, "QLCM", 4); + gf_bs_write_data(bs, "fmt ", 4); + gf_bs_write_u32_le(bs, 150);/*fmt chunk size*/ + gf_bs_write_u8(bs, 1); + gf_bs_write_u8(bs, 0); + gf_bs_write_data(bs, ctx->GUID, 16); + gf_bs_write_u16_le(bs, 1); + memset(szName, 0, 80); + strcpy(szName, (ctx->qcp_type==1) ? "QCELP-GPACExport" : ((ctx->qcp_type==2) ? "SMV-GPACExport" : "EVRC-GPACExport")); + gf_bs_write_data(bs, szName, 80); + gf_bs_write_u16_le(bs, avg_rate); + gf_bs_write_u16_le(bs, sample_size); + gf_bs_write_u16_le(bs, block_size); + gf_bs_write_u16_le(bs, sample_rate); + gf_bs_write_u16_le(bs, avg_rate); + gf_bs_write_u32_le(bs, ctx->rt_cnt); + for (i=0; i<8; i++) { + if (irt_cnt) { + /*frame size MINUS rate octet*/ + gf_bs_write_u8(bs, ctx->rtable[i].pck_size - 1); + gf_bs_write_u8(bs, ctx->rtable[i].rate_idx); + } else { + gf_bs_write_u16(bs, 0); + } + } + memset(szName, 0, 80); + gf_bs_write_data(bs, szName, 20);/*reserved*/ + gf_bs_write_data(bs, "vrat", 4); + gf_bs_write_u32_le(bs, 8);/*vrat chunk size*/ + gf_bs_write_u32_le(bs, ctx->rt_cnt); + gf_bs_write_u32_le(bs, frame_count); + gf_bs_write_data(bs, "data", 4); + gf_bs_write_u32_le(bs, data_size);/*data chunk size*/ + + ctx->needs_rate_byte = needs_rate_octet ? 1 : 0; + + gf_bs_del(bs); + + if (!ctx->first) { + assert(ctx->needs_final_pach); + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_FALSE); + gf_filter_pck_set_seek_flag(dst_pck, GF_TRUE); + gf_filter_pck_set_byte_offset(dst_pck, 0); + } else { + gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_FALSE); + gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO); + ctx->first = GF_FALSE; + } + + gf_filter_pck_send(dst_pck); +} + +GF_Err qcpmx_process(GF_Filter *filter) +{ + GF_QCPMxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + u8 *data, *output; + u32 pck_size, size; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + if (ctx->needs_final_pach) { + qcpmx_send_header(ctx, ctx->data_size, ctx->nb_frames); + ctx->needs_final_pach = GF_FALSE; + } + if (ctx->has_qcp_pad) { + dst_pck = gf_filter_pck_new_alloc(ctx->opid, 1, &output); + output[0] = 0; + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_TRUE); + ctx->has_qcp_pad = GF_FALSE; + gf_filter_pck_send(dst_pck); + } + gf_filter_pid_set_eos(ctx->opid); + return GF_EOS; + } + return GF_OK; + } + + if (ctx->first) { + qcpmx_send_header(ctx, ctx->data_size, ctx->nb_frames); + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + + size = pck_size; + ctx->data_size += pck_size; + ctx->nb_frames ++; + + /*fix rate octet for QCP*/ + if (ctx->needs_rate_byte) { + u32 j; + u32 rate_found = 0; + size ++; + + for (j=0; jrt_cnt; j++) { + if (ctx->qcp_rates[2*j+1] == pck_size) { + rate_found = ctx->qcp_rates[2*j]; + break; + } + } + if (!rate_found) { + GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[QCP] Frame size %d not in rate table, ignoring frame\n", pck_size)); + gf_filter_pid_drop_packet(ctx->ipid); + return GF_NON_COMPLIANT_BITSTREAM; + } + + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + output[0] = rate_found; + memcpy(output+1, data, pck_size); + } else { + dst_pck = gf_filter_pck_new_ref(ctx->opid, data, size, pck); + } + + gf_filter_pck_merge_properties(pck, dst_pck); + gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO); + + gf_filter_pck_set_framing(dst_pck, ctx->first, GF_FALSE); + ctx->first = GF_FALSE; + + gf_filter_pck_send(dst_pck); + + if (ctx->exporter) { + u32 timescale = gf_filter_pck_get_timescale(pck); + u64 ts = gf_filter_pck_get_cts(pck); + gf_set_progress("Exporting", ts*ctx->duration.den, ctx->duration.num*timescale); + } + + gf_filter_pid_drop_packet(ctx->ipid); + + return GF_OK; +} + +static const GF_FilterCapability QCPMxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_AUDIO), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_QCELP), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_EVRC), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_EVRC_PV), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_SMV), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "qcp"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "audio/qcp"), +}; + + +#define OFFS(_n) #_n, offsetof(GF_QCPMxCtx, _n) +static const GF_FilterArgs QCPMxArgs[] = +{ + { OFFS(exporter), "compatibility with old exporter, displays export results", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + + +GF_FilterRegister QCPMxRegister = { + .name = "writeqcp", + GF_FS_SET_DESCRIPTION("QCP writer") + GF_FS_SET_HELP("This filter converts a single stream to a QCP output file.") + .private_size = sizeof(GF_QCPMxCtx), + .args = QCPMxArgs, + SETCAPS(QCPMxCaps), + .configure_pid = qcpmx_configure_pid, + .process = qcpmx_process +}; + + +const GF_FilterRegister *qcpmx_register(GF_FilterSession *session) +{ + return &QCPMxRegister; +} diff --git a/src/filters/write_vtt.c b/src/filters/write_vtt.c new file mode 100644 index 0000000..b451d86 --- /dev/null +++ b/src/filters/write_vtt.c @@ -0,0 +1,322 @@ +/* + * GPAC - Multimedia Framework C SDK + * + * Authors: Jean Le Feuvre + * Copyright (c) Telecom ParisTech 2000-2018 + * All rights reserved + * + * This file is part of GPAC / WebVTT stream to file filter + * + * GPAC is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * GPAC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include + + +typedef struct +{ + //opts + Bool exporter, merge; + + //only one input pid declared + GF_FilterPid *ipid; + //only one output pid declared + GF_FilterPid *opid; + + u32 codecid; + Bool first; + + GF_Fraction64 duration; + char *dcd; + + GF_BitStream *bs_w; + u8 *cues_buffer; + u32 cues_buffer_size; + + GF_WebVTTParser *parser; + +} GF_WebVTTMxCtx; + +static void vttmx_write_cue(void *ctx, GF_WebVTTCue *cue); + +GF_Err vttmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove) +{ + const GF_PropertyValue *p; + GF_WebVTTMxCtx *ctx = gf_filter_get_udta(filter); + + if (is_remove) { + ctx->ipid = NULL; + gf_filter_pid_remove(ctx->opid); + return GF_OK; + } + if (! gf_filter_pid_check_caps(pid)) + return GF_NOT_SUPPORTED; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); + if (!p) return GF_NOT_SUPPORTED; + ctx->codecid = p->value.uint; + + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG); + if (!p) return GF_NOT_SUPPORTED; + if (ctx->dcd && !strcmp(ctx->dcd, p->value.string)) return GF_OK; + if (ctx->dcd) gf_free(ctx->dcd); + + if ((p->type==GF_PROP_DATA) || (p->type==GF_PROP_DATA_NO_COPY)) { + ctx->dcd = gf_malloc(p->value.data.size+1); + memcpy(ctx->dcd, p->value.data.ptr, p->value.data.size); + ctx->dcd[p->value.data.size]=0; + } else { + ctx->dcd = gf_strdup(p->value.string); + } + if (!ctx->opid) { + ctx->opid = gf_filter_pid_new(filter); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("vtt") ); + gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING("text/vtt") ); + ctx->first = GF_TRUE; + + if (ctx->exporter) { + GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting %s\n", gf_codecid_name(ctx->codecid))); + } + + } + ctx->ipid = pid; + p = gf_filter_pid_get_property(pid, GF_PROP_PID_DURATION); + if (p) ctx->duration = p->value.lfrac; + + if (ctx->parser) gf_webvtt_parser_del(ctx->parser); + ctx->parser = NULL; + if (ctx->merge) { + ctx->parser = gf_webvtt_parser_new(); + gf_webvtt_parser_cue_callback(ctx->parser, vttmx_write_cue, ctx); + } + gf_filter_pid_set_framing_mode(pid, GF_TRUE); + return GF_OK; +} + +void vttmx_timestamp_dump(GF_BitStream *bs, GF_WebVTTTimestamp *ts, Bool dump_hour) +{ + char szTS[200]; + szTS[0] = 0; + if (dump_hour || ts->hour != 0) { + sprintf(szTS, "%02u:", ts->hour); + } + sprintf(szTS, "%02u:%02u.%03u", ts->min, ts->sec, ts->ms); + gf_bs_write_data(bs, szTS, (u32) strlen(szTS) ); +} + +void webvtt_write_cue(GF_BitStream *bs, GF_WebVTTCue *cue) +{ + if (!cue) return; + if (cue->pre_text) { + gf_bs_write_data(bs, cue->pre_text, (u32) strlen(cue->pre_text)); + gf_bs_write_data(bs, "\n\n", 2); + } + if (cue->id) gf_bs_write_data(bs, cue->id, (u32) strlen(cue->id) ); + if (cue->start.hour || cue->end.hour) { + vttmx_timestamp_dump(bs, &cue->start, GF_TRUE); + gf_bs_write_data(bs, " --> ", 5); + vttmx_timestamp_dump(bs, &cue->end, GF_TRUE); + } else { + vttmx_timestamp_dump(bs, &cue->start, GF_FALSE); + gf_bs_write_data(bs, " --> ", 5); + vttmx_timestamp_dump(bs, &cue->end, GF_FALSE); + } + if (cue->settings) { + gf_bs_write_data(bs, " ", 1); + gf_bs_write_data(bs, cue->settings, (u32) strlen(cue->settings)); + } + gf_bs_write_data(bs, "\n", 1); + if (cue->text) + gf_bs_write_data(bs, cue->text, (u32) strlen(cue->text)); + gf_bs_write_data(bs, "\n\n", 2); + + if (cue->post_text) { + gf_bs_write_data(bs, cue->post_text, (u32) strlen(cue->post_text)); + gf_bs_write_data(bs, "\n\n", 2); + } +} + +static void vttmx_write_cue(void *udta, GF_WebVTTCue *cue) +{ + GF_WebVTTMxCtx *ctx = (GF_WebVTTMxCtx *)udta; + webvtt_write_cue(ctx->bs_w, cue); +} + +void vttmx_parser_flush(GF_WebVTTMxCtx *ctx) +{ + u8 *output; + u64 duration = ctx->duration.num; + duration *= 1000; + duration /= ctx->duration.den; + + if (!ctx->bs_w) ctx->bs_w = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs_w, ctx->cues_buffer, ctx->cues_buffer_size); + + gf_webvtt_parser_finalize(ctx->parser, duration); + + if (gf_bs_get_position(ctx->bs_w)) { + GF_FilterPacket *dst_pck; + u32 size; + gf_bs_get_content_no_truncate(ctx->bs_w, &ctx->cues_buffer, &size, &ctx->cues_buffer_size); + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + memcpy(output, ctx->cues_buffer, size); + + gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO); + + gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_TRUE); + gf_filter_pck_send(dst_pck); + } + + + gf_webvtt_parser_del(ctx->parser); + ctx->parser = NULL; + +} +GF_Err vttmx_process(GF_Filter *filter) +{ + GF_WebVTTMxCtx *ctx = gf_filter_get_udta(filter); + GF_FilterPacket *pck, *dst_pck; + u8 *data, *output; + u64 start_ts; + u32 i, pck_size, size, timescale; + GF_List *cues; + + pck = gf_filter_pid_get_packet(ctx->ipid); + if (!pck) { + if (gf_filter_pid_is_eos(ctx->ipid)) { + gf_filter_pid_set_eos(ctx->opid); + if (ctx->parser) { + vttmx_parser_flush(ctx); + } + return GF_EOS; + } + return GF_OK; + } + + data = (char *) gf_filter_pck_get_data(pck, &pck_size); + + if (ctx->first && ctx->dcd) { + size = (u32) strlen(ctx->dcd)+2; + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + memcpy(output, ctx->dcd, size-2); + output[size-2] = '\n'; + output[size-1] = '\n'; + gf_filter_pck_merge_properties(pck, dst_pck); + gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO); + gf_filter_pck_set_framing(dst_pck, ctx->first, GF_FALSE); + ctx->first = GF_FALSE; + gf_filter_pck_send(dst_pck); + } + + if (!ctx->bs_w) ctx->bs_w = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + else gf_bs_reassign_buffer(ctx->bs_w, ctx->cues_buffer, ctx->cues_buffer_size); + + start_ts = gf_filter_pck_get_cts(pck); + start_ts *= 1000; + timescale = gf_filter_pck_get_timescale(pck); + if (!timescale) timescale=1000; + start_ts /= timescale; + + cues = gf_webvtt_parse_cues_from_data(data, pck_size, start_ts); + if (ctx->parser) { + gf_webvtt_merge_cues(ctx->parser, start_ts, cues); + } else { + for (i = 0; i < gf_list_count(cues); i++) { + GF_WebVTTCue *cue = (GF_WebVTTCue *)gf_list_get(cues, i); + vttmx_write_cue(ctx, cue); + gf_webvtt_cue_del(cue); + } + } + gf_list_del(cues); + + gf_bs_get_content_no_truncate(ctx->bs_w, &ctx->cues_buffer, &size, &ctx->cues_buffer_size); + if (size) { + dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output); + memcpy(output, ctx->cues_buffer, size); + + gf_filter_pck_merge_properties(pck, dst_pck); + gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO); + + + gf_filter_pck_set_framing(dst_pck, ctx->first, GF_FALSE); + ctx->first = GF_FALSE; + + gf_filter_pck_send(dst_pck); + } + + if (ctx->exporter) { + u64 ts = gf_filter_pck_get_cts(pck); + timescale = gf_filter_pck_get_timescale(pck); + gf_set_progress("Exporting", ts*ctx->duration.den, ctx->duration.num*timescale); + } + + gf_filter_pid_drop_packet(ctx->ipid); + + return GF_OK; +} + +static void vttmx_finalize(GF_Filter *filter) +{ + GF_WebVTTMxCtx *ctx = gf_filter_get_udta(filter); + if (ctx->bs_w) gf_bs_del(ctx->bs_w); + if (ctx->dcd) gf_free(ctx->dcd); + if (ctx->cues_buffer) gf_free(ctx->cues_buffer); + + if (ctx->parser) gf_webvtt_parser_del(ctx->parser); +} + +static const GF_FilterCapability WebVTTMxCaps[] = +{ + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT), + CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_WEBVTT), + CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE), + CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "vtt"), + CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "subtitle/vtt|text/vtt"), +}; + + +#define OFFS(_n) #_n, offsetof(GF_WebVTTMxCtx, _n) +static const GF_FilterArgs WebVTTMxArgs[] = +{ + { OFFS(exporter), "compatibility with old exporter, displays export results", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + { OFFS(merge), "merge VTT cue if needed", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED}, + {0} +}; + + +GF_FilterRegister WebVTTMxRegister = { + .name = "writevtt", + GF_FS_SET_DESCRIPTION("WebVTT writer") + GF_FS_SET_HELP("This filter converts a single stream to a WebVTT output file.") + .private_size = sizeof(GF_WebVTTMxCtx), + .args = WebVTTMxArgs, + .finalize = vttmx_finalize, + SETCAPS(WebVTTMxCaps), + .configure_pid = vttmx_configure_pid, + .process = vttmx_process +}; + + +const GF_FilterRegister *vttmx_register(GF_FilterSession *session) +{ + return &WebVTTMxRegister; +} diff --git a/src/ietf/rtcp.c b/src/ietf/rtcp.c index 6703cc8..feeedb9 100644 --- a/src/ietf/rtcp.c +++ b/src/ietf/rtcp.c @@ -30,29 +30,27 @@ #include -#ifndef _WIN32_WCE -#include -#endif - GF_EXPORT -u32 gf_rtp_read_rtcp(GF_RTPChannel *ch, char *buffer, u32 buffer_size) +u32 gf_rtp_read_rtcp(GF_RTPChannel *ch, u8 *buffer, u32 buffer_size) { GF_Err e; u32 res; //only if the socket exist (otherwise RTSP interleaved channel) if (!ch || !ch->rtcp) return 0; - - e = gf_sk_receive(ch->rtcp, buffer, buffer_size, 0, &res); + if (ch->no_select) { + e = gf_sk_receive_no_select(ch->rtcp, buffer, buffer_size, &res); + } else { + e = gf_sk_receive(ch->rtcp, buffer, buffer_size, &res); + } if (e) return 0; return res; } GF_EXPORT -GF_Err gf_rtp_decode_rtcp(GF_RTPChannel *ch, char *pck, u32 pck_size, Bool *has_sr) +GF_Err gf_rtp_decode_rtcp(GF_RTPChannel *ch, u8 *pck, u32 pck_size, Bool *has_sr) { GF_RTCPHeader rtcp_hdr; - GF_BitStream *bs; char sdes_buffer[300]; u32 i, sender_ssrc, cur_ssrc, val, sdes_type, sdes_len, res, first; GF_Err e = GF_OK; @@ -61,24 +59,22 @@ GF_Err gf_rtp_decode_rtcp(GF_RTPChannel *ch, char *pck, u32 pck_size, Bool *has_ //bad RTCP packet if (pck_size < 4 ) return GF_NON_COMPLIANT_BITSTREAM; - bs = gf_bs_new(pck, pck_size, GF_BITSTREAM_READ); + gf_bs_reassign_buffer(ch->bs_r, pck, pck_size); first = 1; while (pck_size) { //global header - rtcp_hdr.Version = gf_bs_read_int(bs, 2); + rtcp_hdr.Version = gf_bs_read_int(ch->bs_r, 2); if (rtcp_hdr.Version != 2 ) { - gf_bs_del(bs); return GF_NOT_SUPPORTED; } - rtcp_hdr.Padding = gf_bs_read_int(bs, 1); - rtcp_hdr.Count = gf_bs_read_int(bs, 5); - rtcp_hdr.PayloadType = gf_bs_read_u8(bs); - rtcp_hdr.Length = 1 + gf_bs_read_u16(bs); + rtcp_hdr.Padding = gf_bs_read_int(ch->bs_r, 1); + rtcp_hdr.Count = gf_bs_read_int(ch->bs_r, 5); + rtcp_hdr.PayloadType = gf_bs_read_u8(ch->bs_r); + rtcp_hdr.Length = 1 + gf_bs_read_u16(ch->bs_r); //check pck size if (pck_size < (u32) rtcp_hdr.Length * 4) { - gf_bs_del(bs); //we return OK return GF_CORRUPTED_DATA; } @@ -93,8 +89,7 @@ GF_Err gf_rtp_decode_rtcp(GF_RTPChannel *ch, char *pck, u32 pck_size, Bool *has_ if ( ( (rtcp_hdr.PayloadType!=200) && (rtcp_hdr.PayloadType!=201) ) || rtcp_hdr.Padding ) { - gf_bs_del(bs); - GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTCP] Corrupted RTCP packet: payload type %d (200 or 201 expected) - Padding %d (0 expected)\n", rtcp_hdr.PayloadType, rtcp_hdr.Padding)); + GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTCP] Corrupted RTCP packet: payload type %d (200 or 201 expected) - Padding %d (0 expected)\n", rtcp_hdr.PayloadType, rtcp_hdr.Padding)); return GF_CORRUPTED_DATA; } first = 0; @@ -105,23 +100,22 @@ GF_Err gf_rtp_decode_rtcp(GF_RTPChannel *ch, char *pck, u32 pck_size, Bool *has_ //Sender report - we assume there's only one sender case 200: /*sender ssrc*/ - sender_ssrc = gf_bs_read_u32(bs); + sender_ssrc = gf_bs_read_u32(ch->bs_r); rtcp_hdr.Length -= 1; /*not for us...*/ if (ch->SenderSSRC && (ch->SenderSSRC != sender_ssrc)) break; if (ch->first_SR) { - ch->first_SR = 0; gf_rtp_get_next_report_time(ch); ch->SenderSSRC = sender_ssrc; } ch->last_report_time = gf_rtp_get_report_time(); - ch->last_SR_NTP_sec = gf_bs_read_u32(bs); - ch->last_SR_NTP_frac = gf_bs_read_u32(bs); - ch->last_SR_rtp_time = gf_bs_read_u32(bs); - /*nb_pck = */gf_bs_read_u32(bs); - /*nb_bytes =*/gf_bs_read_u32(bs); + ch->last_SR_NTP_sec = gf_bs_read_u32(ch->bs_r); + ch->last_SR_NTP_frac = gf_bs_read_u32(ch->bs_r); + ch->last_SR_rtp_time = gf_bs_read_u32(ch->bs_r); + /*nb_pck = */gf_bs_read_u32(ch->bs_r); + /*nb_bytes =*/gf_bs_read_u32(ch->bs_r); rtcp_hdr.Length -= 5; if (has_sr) *has_sr = GF_TRUE; @@ -130,11 +124,12 @@ GF_Err gf_rtp_decode_rtcp(GF_RTPChannel *ch, char *pck, u32 pck_size, Bool *has_ if (gf_log_tool_level_on(GF_LOG_RTP, GF_LOG_INFO)) { #ifndef _WIN32_WCE time_t gtime = ch->last_SR_NTP_sec - GF_NTP_SEC_1900_TO_1970; - const char *ascTime = asctime(gmtime(>ime)); + const char *ascTime = asctime(gf_gmtime(>ime)); #else const char *ascTime = "Not Available"; #endif - GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTP] RTCP SR: SSRC %d - RTP Time %d - Nb Pck %d - Nb Bytes %d - Time %s\n", + GF_LOG(ch->first_SR ? GF_LOG_INFO : GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] RTCP %sSR: SSRC %d - RTP Time %u - Nb Pck %d - Nb Bytes %d - Time %s\n", + ch->first_SR ? "Initial " : "", ch->SenderSSRC, ch->last_SR_rtp_time, ch->total_pck, @@ -144,13 +139,15 @@ GF_Err gf_rtp_decode_rtcp(GF_RTPChannel *ch, char *pck, u32 pck_size, Bool *has_ } #endif + ch->first_SR = 0; + //common encoding for SR and RR goto process_reports; case 201: //sender ssrc - /*sender_ssrc = */gf_bs_read_u32(bs); + /*sender_ssrc = */gf_bs_read_u32(ch->bs_r); rtcp_hdr.Length -= 1; process_reports: @@ -160,19 +157,19 @@ process_reports: //to add for (i=0; ibs_r); //frac lost - gf_bs_read_u8(bs); + gf_bs_read_u8(ch->bs_r); //cumulative lost - gf_bs_read_u24(bs); + gf_bs_read_u24(ch->bs_r); //extended seq num - gf_bs_read_u32(bs); + gf_bs_read_u32(ch->bs_r); //jitter - gf_bs_read_u32(bs); + gf_bs_read_u32(ch->bs_r); //LSR - gf_bs_read_u32(bs); + gf_bs_read_u32(ch->bs_r); //DLSR - gf_bs_read_u32(bs); + gf_bs_read_u32(ch->bs_r); rtcp_hdr.Length -= 6; } @@ -183,17 +180,17 @@ process_reports: //SDES case 202: for (i=0; ibs_r); rtcp_hdr.Length -= 1; val = 0; while (1) { - sdes_type = gf_bs_read_u8(bs); + sdes_type = gf_bs_read_u8(ch->bs_r); val += 1; if (!sdes_type) break; - sdes_len = gf_bs_read_u8(bs); + sdes_len = gf_bs_read_u8(ch->bs_r); val += 1; - gf_bs_read_data(bs, sdes_buffer, sdes_len); + gf_bs_read_data(ch->bs_r, sdes_buffer, sdes_len); sdes_buffer[sdes_len] = 0; val += sdes_len; } @@ -201,7 +198,7 @@ process_reports: //re-align on 32bit res = val%4; if (res) { - gf_bs_skip_bytes(bs, (4-res)); + gf_bs_skip_bytes(ch->bs_r, (4-res)); val = val/4 + 1; } else { val = val/4; @@ -213,7 +210,7 @@ process_reports: //BYE packet - close the channel - we work with 1 SSRC only */ case 203: for (i=0; ibs_r); rtcp_hdr.Length -= 1; if (ch->SenderSSRC == cur_ssrc) { e = GF_EOS; @@ -222,7 +219,7 @@ process_reports: } //extra info - skip it while (rtcp_hdr.Length) { - gf_bs_read_u32(bs); + gf_bs_read_u32(ch->bs_r); rtcp_hdr.Length -= 1; } break; @@ -248,26 +245,18 @@ process_reports: */ default: //read all till end - gf_bs_read_data(bs, sdes_buffer, rtcp_hdr.Length*4); + gf_bs_read_data(ch->bs_r, sdes_buffer, rtcp_hdr.Length*4); rtcp_hdr.Length = 0; break; } //WE SHALL CONSUME EVERYTHING otherwise the packet is bad if (rtcp_hdr.Length) { - gf_bs_del(bs); return GF_CORRUPTED_DATA; } } - - gf_bs_del(bs); return e; } -u32 gf_rtp_get_ntp_frac(u32 sec, u32 frac) -{ - return ( ((sec & 0x0000ffff) << 16) | ((frac & 0xffff0000) >> 16)); -} - static u32 RTCP_FormatReport(GF_RTPChannel *ch, GF_BitStream *bs, u32 NTP_Time) { u32 length, is_sr, sec, frac, expected, val, size; @@ -355,7 +344,11 @@ static u32 RTCP_FormatReport(GF_RTPChannel *ch, GF_BitStream *bs, u32 NTP_Time) //RTP specs annexe A.8 gf_bs_write_u32(bs, ( ch->Jitter >> 4)); //LSR - val = ch->last_SR_NTP_sec ? gf_rtp_get_ntp_frac(ch->last_SR_NTP_sec, ch->last_SR_NTP_frac) : 0; + if (ch->last_SR_NTP_sec) { + val = ( ((ch->last_SR_NTP_sec & 0x0000ffff) << 16) | ((ch->last_SR_NTP_frac & 0xffff0000) >> 16)); + } else { + val = 0; + } gf_bs_write_u32(bs, val); // DLSR @@ -366,11 +359,11 @@ static u32 RTCP_FormatReport(GF_RTPChannel *ch, GF_BitStream *bs, u32 NTP_Time) if (gf_log_tool_level_on(GF_LOG_RTP, GF_LOG_DEBUG)) { #ifndef _WIN32_WCE time_t gtime = ch->last_SR_NTP_sec - GF_NTP_SEC_1900_TO_1970; - const char *ascTime = asctime(gmtime(>ime)); + const char *ascTime = asctime(gf_gmtime(>ime)); #else const char *ascTime = "Not Available"; #endif - GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] RTCP-RR\t%d\t%d\t%d\t%d\t%d\t%s\n", + GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] RTCP-RR: SSRC %d Jitter %d extended %d expect_diff %d loss_diff %d time %s\n", ch->SSRC, ch->Jitter >> 4, extended, @@ -455,13 +448,11 @@ static u32 RTCP_FormatBYE(GF_RTPChannel *ch, GF_BitStream *bs) } GF_EXPORT -GF_Err gf_rtp_send_bye(GF_RTPChannel *ch, - GF_Err (*RTP_TCPCallback)(void *cbk, char *pck, u32 pck_size), - void *rtsp_cbk) +GF_Err gf_rtp_send_bye(GF_RTPChannel *ch) { GF_BitStream *bs; u32 report_size; - char *report_buf; + u8 *report_buf; GF_Err e = GF_OK; bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); @@ -486,8 +477,8 @@ GF_Err gf_rtp_send_bye(GF_RTPChannel *ch, if (ch->rtcp) { e = gf_sk_send(ch->rtcp, report_buf, report_size); } else { - if (RTP_TCPCallback) - e = RTP_TCPCallback(rtsp_cbk, report_buf, report_size); + if (ch->send_interleave) + e = ch->send_interleave(ch->interleave_cbk1, ch->interleave_cbk2, GF_TRUE, report_buf, report_size); else e = GF_BAD_PARAM; } @@ -496,14 +487,13 @@ GF_Err gf_rtp_send_bye(GF_RTPChannel *ch, } GF_EXPORT -GF_Err gf_rtp_send_rtcp_report(GF_RTPChannel *ch, - GF_Err (*RTP_TCPCallback)(void *cbk, char *pck, u32 pck_size), - void *rtsp_cbk) +GF_Err gf_rtp_send_rtcp_report(GF_RTPChannel *ch) { u32 Time, report_size; GF_BitStream *bs; - char *report_buf; + u8 *report_buf; GF_Err e = GF_OK; + /*skip first SR when acting as a receiver*/ if (!ch->forced_ntp_sec && ch->first_SR) return GF_OK; @@ -527,12 +517,11 @@ GF_Err gf_rtp_send_rtcp_report(GF_RTPChannel *ch, gf_bs_get_content(bs, &report_buf, &report_size); gf_bs_del(bs); - if (ch->rtcp) { e = gf_sk_send(ch->rtcp, report_buf, report_size); } else { - if (RTP_TCPCallback) - e = RTP_TCPCallback(rtsp_cbk, report_buf, report_size); + if (ch->send_interleave) + e = ch->send_interleave(ch->interleave_cbk1, ch->interleave_cbk2, GF_TRUE, report_buf, report_size); else e = GF_BAD_PARAM; } @@ -553,12 +542,23 @@ GF_Err gf_rtp_send_rtcp_report(GF_RTPChannel *ch, return e; } +#if 0 //unused + +enum +{ + GF_RTCP_INFO_NAME = 0, + GF_RTCP_INFO_EMAIL, + GF_RTCP_INFO_PHONE, + GF_RTCP_INFO_LOCATION, + GF_RTCP_INFO_TOOL, + GF_RTCP_INFO_NOTE, + GF_RTCP_INFO_PRIV +}; #define RTCP_SAFE_FREE(p) if (p) gf_free(p); \ p = NULL; -GF_EXPORT GF_Err gf_rtp_set_info_rtcp(GF_RTPChannel *ch, u32 InfoCode, char *info_string) { if (!ch) return GF_BAD_PARAM; @@ -598,4 +598,7 @@ GF_Err gf_rtp_set_info_rtcp(GF_RTPChannel *ch, u32 InfoCode, char *info_string) return GF_OK; } +#endif + + #endif /*GPAC_DISABLE_STREAMING*/ diff --git a/src/ietf/rtp.c b/src/ietf/rtp.c index 4a8b113..bc8ab10 100644 --- a/src/ietf/rtp.c +++ b/src/ietf/rtp.c @@ -39,6 +39,8 @@ GF_RTPChannel *gf_rtp_new() return NULL; tmp->first_SR = 1; tmp->SSRC = gf_rand(); + tmp->bs_r = gf_bs_new("d", 1, GF_BITSTREAM_READ); + tmp->bs_w = gf_bs_new("d", 1, GF_BITSTREAM_WRITE); return tmp; } @@ -62,6 +64,8 @@ void gf_rtp_del(GF_RTPChannel *ch) if (ch->s_tool) gf_free(ch->s_tool); if (ch->s_note) gf_free(ch->s_note); if (ch->s_priv) gf_free(ch->s_priv); + if (ch->bs_r) gf_bs_del(ch->bs_r); + if (ch->bs_w) gf_bs_del(ch->bs_w); memset(ch, 0, sizeof(GF_RTPChannel)); gf_free(ch); } @@ -100,8 +104,13 @@ GF_Err gf_rtp_setup_transport(GF_RTPChannel *ch, GF_RTSPTransport *trans_info, c } else { ch->net_info.source = gf_strdup(remote_address); } - if (trans_info->SSRC) ch->SenderSSRC = trans_info->SSRC; - + if (trans_info->SSRC) { + if (trans_info->is_sender) { + ch->SSRC = trans_info->SSRC; + } else { + ch->SenderSSRC = trans_info->SSRC; + } + } //check we REALLY have unicast or multicast if (gf_sk_is_multicast_address(ch->net_info.source) && ch->net_info.IsUnicast) return GF_SERVICE_ERROR; return GF_OK; @@ -114,11 +123,14 @@ void gf_rtp_reset_buffers(GF_RTPChannel *ch) if (ch->rtp) gf_sk_reset(ch->rtp); if (ch->rtcp) gf_sk_reset(ch->rtcp); if (ch->po) gf_rtp_reorderer_reset(ch->po); - /*also reset ssrc*/ - //ch->SenderSSRC = 0; ch->first_SR = 1; } +GF_EXPORT +void gf_rtp_reset_ssrc(GF_RTPChannel *ch) +{ + if (ch) ch->SenderSSRC = 0; +} GF_EXPORT void gf_rtp_enable_nat_keepalive(GF_RTPChannel *ch, u32 nat_timeout) @@ -135,7 +147,7 @@ GF_EXPORT GF_Err gf_rtp_set_info_rtp(GF_RTPChannel *ch, u32 seq_num, u32 rtp_time, u32 ssrc) { if (!ch) return GF_BAD_PARAM; - ch->rtp_time = rtp_time; + ch->CurrentTime = ch->rtp_time = seq_num ? (1 + rtp_time) : 0; ch->last_pck_sn = 0; ch->rtp_first_SN = seq_num; ch->num_sn_loops = 0; @@ -219,7 +231,7 @@ GF_Err gf_rtp_initialize(GF_RTPChannel *ch, u32 UDPBufferSize, Bool IsSource, u3 //else bind and set remote destination else { if (!ch->net_info.port_first) ch->net_info.port_first = ch->net_info.client_port_first; - e = gf_sk_bind(ch->rtp, local_ip,ch->net_info.port_first, ch->net_info.destination, ch->net_info.client_port_first, GF_SOCK_REUSE_PORT); + e = gf_sk_bind(ch->rtp, local_ip,ch->net_info.port_first, ch->net_info.destination, ch->net_info.client_port_first, GF_SOCK_REUSE_PORT | GF_SOCK_FAKE_BIND); if (e) return e; } } else { @@ -231,13 +243,6 @@ GF_Err gf_rtp_initialize(GF_RTPChannel *ch, u32 UDPBufferSize, Bool IsSource, u3 } if (UDPBufferSize) gf_sk_set_buffer_size(ch->rtp, IsSource, UDPBufferSize); - if (IsSource) { - if (ch->send_buffer) gf_free(ch->send_buffer); - ch->send_buffer = (char *) gf_malloc(sizeof(char) * PathMTU); - ch->send_buffer_size = PathMTU; - } - - //create re-ordering queue for UDP only, and receive if (ReorederingSize && !IsSource) { if (!MaxReorderDelay) MaxReorderDelay = 200; @@ -259,7 +264,7 @@ GF_Err gf_rtp_initialize(GF_RTPChannel *ch, u32 UDPBufferSize, Bool IsSource, u3 e = gf_sk_bind(ch->rtcp, local_ip, ch->net_info.client_port_last, ch->net_info.source, port, GF_SOCK_REUSE_PORT); if (e) return e; } else { - e = gf_sk_bind(ch->rtcp, local_ip, ch->net_info.port_last, ch->net_info.destination, ch->net_info.client_port_last, GF_SOCK_REUSE_PORT); + e = gf_sk_bind(ch->rtcp, local_ip, ch->net_info.port_last, ch->net_info.destination, ch->net_info.client_port_last, GF_SOCK_REUSE_PORT | GF_SOCK_FAKE_BIND); if (e) return e; } } else { @@ -270,6 +275,12 @@ GF_Err gf_rtp_initialize(GF_RTPChannel *ch, u32 UDPBufferSize, Bool IsSource, u3 } } + if (IsSource) { + if (ch->send_buffer) gf_free(ch->send_buffer); + ch->send_buffer_size = PathMTU + 12; + ch->send_buffer = (char *) gf_malloc(sizeof(char) * ch->send_buffer_size); + } + //format CNAME if not done yet if (!ch->CName) { //this is the real CName setup @@ -279,7 +290,7 @@ GF_Err gf_rtp_initialize(GF_RTPChannel *ch, u32 UDPBufferSize, Bool IsSource, u3 char name[GF_MAX_IP_NAME_LEN]; size_t start; - gf_get_user_name(name, 1024); + gf_get_user_name(name); if (strlen(name)) strcat(name, "@"); start = strlen(name); //get host IP or loopback if error @@ -288,7 +299,6 @@ GF_Err gf_rtp_initialize(GF_RTPChannel *ch, u32 UDPBufferSize, Bool IsSource, u3 } } - #ifndef GPAC_DISABLE_LOG if (gf_log_tool_level_on(GF_LOG_RTP, GF_LOG_DEBUG)) { GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Packet Log Format: SSRC SequenceNumber TimeStamp NTP@recvTime deviance, Jiter, PckLost PckTotal BytesTotal\n")); @@ -325,49 +335,89 @@ void gf_rtp_get_next_report_time(GF_RTPChannel *ch) d = 0.5 + ((Double) gf_rand()) / ((Double) RAND_MAX); /*of a minimal 5sec interval expressed in 1/65536 of a sec*/ d = 5.0 * d * 65536; - /*we should estimate bandwidth sharing too, but as we only support one sender*/ + /*we should estimate bandwidth sharing too*/ ch->next_report_time = gf_rtp_get_report_time() + (u32) d; } +GF_EXPORT +u32 gf_rtp_read_flush(GF_RTPChannel *ch, u8 *buffer, u32 buffer_size) +{ + char *pck; + u32 res = 0; + if (!ch->po) return 0; + + //pck queue may need to be flushed + pck = (char *) gf_rtp_reorderer_get(ch->po, &res, GF_TRUE); + if (pck) { + memcpy(buffer, pck, res); + gf_free(pck); + } + return res; +} + +GF_EXPORT +u32 gf_rtp_flush_rtp(GF_RTPChannel *ch, u8 *buffer, u32 buffer_size) +{ + u32 res; + char *pck; + + //only if the socket exist (otherwise RTSP interleaved channel) + if (!ch || !ch->rtp || !ch->po) return 0; + + //pck queue may need to be flushed + pck = (char *) gf_rtp_reorderer_get(ch->po, &res, GF_FALSE); + if (pck) { + memcpy(buffer, pck, res); + gf_free(pck); + return res; + } + return 0; +} GF_EXPORT -u32 gf_rtp_read_rtp(GF_RTPChannel *ch, char *buffer, u32 buffer_size) +u32 gf_rtp_read_rtp(GF_RTPChannel *ch, u8 *buffer, u32 buffer_size) { GF_Err e; u32 seq_num, res; - char *pck; //only if the socket exist (otherwise RTSP interleaved channel) if (!ch || !ch->rtp) return 0; - e = gf_sk_receive(ch->rtp, buffer, buffer_size, 0, &res); - if (!res || e || (res < 12)) res = 0; + if (ch->no_select) { + e = gf_sk_receive_no_select(ch->rtp, buffer, buffer_size, &res); + } else { + e = gf_sk_receive(ch->rtp, buffer, buffer_size, &res); + } + if (!res || e || (res < 12)) { + assert(res==0); + res = 0; + } if (res) { ch->total_bytes+=res; ch->total_pck++; } //add the packet to our Queue if any if (ch->po) { + char *pck; if (res) { - seq_num = ((buffer[2] << 8) & 0xFF00) | (buffer[3] & 0xFF); + seq_num = ((((u32)buffer[2]) << 8) & 0xFF00) | (buffer[3] & 0xFF); gf_rtp_reorderer_add(ch->po, (void *) buffer, res, seq_num); } //pck queue may need to be flushed - pck = (char *) gf_rtp_reorderer_get(ch->po, &res); + pck = (char *) gf_rtp_reorderer_get(ch->po, &res, GF_FALSE); if (pck) { memcpy(buffer, pck, res); gf_free(pck); } } /*monitor keep-alive period*/ - if (ch->nat_keepalive_time_period) { + if (ch->nat_keepalive_time_period && !ch->send_interleave) { u32 now = gf_sys_clock(); if (res) { ch->last_nat_keepalive_time = now; } else { if (now - ch->last_nat_keepalive_time >= ch->nat_keepalive_time_period) { -#if 0 char rtp_nat[12]; rtp_nat[0] = (u8) 0xC0; rtp_nat[1] = ch->PayloadType; @@ -381,8 +431,7 @@ u32 gf_rtp_read_rtp(GF_RTPChannel *ch, char *buffer, u32 buffer_size) rtp_nat[9] = (ch->SenderSSRC>>16)&0xFF; rtp_nat[10] = (ch->SenderSSRC>>8)&0xFF; rtp_nat[11] = (ch->SenderSSRC)&0xFF; -#endif - e = gf_sk_send(ch->rtp, buffer, 12); + e = gf_sk_send(ch->rtp, rtp_nat, 12); if (e) { GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTP] Error sending NAT keep-alive packet: %s - disabling NAT\n", gf_error_to_string(e) )); ch->nat_keepalive_time_period = 0; @@ -398,7 +447,7 @@ u32 gf_rtp_read_rtp(GF_RTPChannel *ch, char *buffer, u32 buffer_size) GF_EXPORT -GF_Err gf_rtp_decode_rtp(GF_RTPChannel *ch, char *pck, u32 pck_size, GF_RTPHeader *rtp_hdr, u32 *PayloadStart) +GF_Err gf_rtp_decode_rtp(GF_RTPChannel *ch, u8 *pck, u32 pck_size, GF_RTPHeader *rtp_hdr, u32 *PayloadStart) { GF_Err e; s32 deviance, delta; @@ -408,30 +457,29 @@ GF_Err gf_rtp_decode_rtp(GF_RTPChannel *ch, char *pck, u32 pck_size, GF_RTPHeade if (!rtp_hdr) return GF_BAD_PARAM; e = GF_OK; + gf_bs_reassign_buffer(ch->bs_r, pck, pck_size); //we need to uncompress the RTP header - rtp_hdr->Version = (pck[0] & 0xC0 ) >> 6; + rtp_hdr->Version = gf_bs_read_int(ch->bs_r, 2); if (rtp_hdr->Version != 2) return GF_NOT_SUPPORTED; - rtp_hdr->Padding = ( pck[0] & 0x20 ) >> 5; - rtp_hdr->Extension = ( pck[0] & 0x10 ) >> 4; - rtp_hdr->CSRCCount = pck[0] & 0x0F; - rtp_hdr->Marker = ( pck[1] & 0x80 ) >> 7; - rtp_hdr->PayloadType = pck[1] & 0x7F; + rtp_hdr->Padding = gf_bs_read_int(ch->bs_r, 1); + rtp_hdr->Extension = gf_bs_read_int(ch->bs_r, 1); + rtp_hdr->CSRCCount = gf_bs_read_int(ch->bs_r, 4); + rtp_hdr->Marker = gf_bs_read_int(ch->bs_r, 1); + rtp_hdr->PayloadType = gf_bs_read_int(ch->bs_r, 7); /*we don't support multiple CSRC now. Only one source (the server) is allowed*/ if (rtp_hdr->CSRCCount) return GF_NOT_SUPPORTED; /*SeqNum*/ - rtp_hdr->SequenceNumber = ((pck[2] << 8) & 0xFF00) | (pck[3] & 0xFF); + rtp_hdr->SequenceNumber = gf_bs_read_u16(ch->bs_r); /*TS*/ - rtp_hdr->TimeStamp = (u32) ((pck[4]<<24) &0xFF000000) | ((pck[5]<<16) & 0xFF0000) | ((pck[6]<<8) & 0xFF00) | ((pck[7]) & 0xFF); + rtp_hdr->TimeStamp = gf_bs_read_u32(ch->bs_r); /*SSRC*/ - rtp_hdr->SSRC = ((pck[8]<<24) &0xFF000000) | ((pck[9]<<16) & 0xFF0000) | ((pck[10]<<8) & 0xFF00) | ((pck[11]) & 0xFF); - /*first we only work with one payload type...*/ - if (rtp_hdr->PayloadType != ch->PayloadType) return GF_NOT_SUPPORTED; + rtp_hdr->SSRC = gf_bs_read_u32(ch->bs_r); /*update RTP time if we didn't get the info*/ if (!ch->rtp_time) { - ch->rtp_time = rtp_hdr->TimeStamp; + ch->rtp_time = 1 + rtp_hdr->TimeStamp; ch->rtp_first_SN = rtp_hdr->SequenceNumber; ch->num_sn_loops = 0; } @@ -440,7 +488,6 @@ GF_Err gf_rtp_decode_rtp(GF_RTPChannel *ch, char *pck, u32 pck_size, GF_RTPHeade GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTP] Assigning SSRC to %d because none was specified through SDP/RTSP\n", ch->SenderSSRC)); } - if (!ch->ntp_init && ch->SenderSSRC && (ch->SenderSSRC != rtp_hdr->SSRC) ) { GF_LOG(GF_LOG_WARNING, GF_LOG_RTP, ("[RTP] SSRC mismatch: %d vs %d\n", rtp_hdr->SSRC, ch->SenderSSRC)); return GF_IP_NETWORK_EMPTY; @@ -459,12 +506,16 @@ GF_Err gf_rtp_decode_rtp(GF_RTPChannel *ch, char *pck, u32 pck_size, GF_RTPHeade } if (ch->last_SR_rtp_time) { - s32 diff_sec = ((s32) rtp_hdr->TimeStamp - (s32) ch->last_SR_rtp_time) / (s32) ch->TimeScale; + s64 diff_sec; u32 sec = ch->last_SR_NTP_sec; s64 frac; - //frac = ch->last_SR_NTP_frac; - frac = (s32) rtp_hdr->TimeStamp - (s32) ch->last_SR_rtp_time - diff_sec*(s32)ch->TimeScale; + frac = (s64) rtp_hdr->TimeStamp; + frac -= (s64) ch->last_SR_rtp_time; + if (frac<0) frac += 0xFFFFFFFF; + diff_sec = frac / ch->TimeScale; + + frac -= diff_sec*ch->TimeScale; frac *= 0xFFFFFFFF; frac /= ch->TimeScale; frac += ch->last_SR_NTP_frac; @@ -472,7 +523,7 @@ GF_Err gf_rtp_decode_rtp(GF_RTPChannel *ch, char *pck, u32 pck_size, GF_RTPHeade sec += 1; frac -= 0xFFFFFFFF; } - rtp_hdr->recomputed_ntp_ts = sec + diff_sec; + rtp_hdr->recomputed_ntp_ts = sec + (s32) diff_sec; rtp_hdr->recomputed_ntp_ts <<= 32; rtp_hdr->recomputed_ntp_ts |= frac; } @@ -515,7 +566,6 @@ GF_Err gf_rtp_decode_rtp(GF_RTPChannel *ch, char *pck, u32 pck_size, GF_RTPHeade #ifndef GPAC_DISABLE_LOG if (gf_log_tool_level_on(GF_LOG_RTP, GF_LOG_DEBUG)) { - ch->total_pck++; ch->total_bytes += pck_size-12; GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP]\t%d\t%d\t%u\t%d\t%d\t%d\t%d\t%d\t%d\n", @@ -535,6 +585,15 @@ GF_Err gf_rtp_decode_rtp(GF_RTPChannel *ch, char *pck, u32 pck_size, GF_RTPHeade //we work with no CSRC so payload offset is always 12 *PayloadStart = 12; + if (rtp_hdr->Extension) { + u16 ext_size; + char *payl = pck + *PayloadStart; + ext_size = payl[2]; + ext_size <<= 8; + ext_size |= payl[3]; + *PayloadStart += 4 + (ext_size * 4); + } + //store the time ch->CurrentTime = rtp_hdr->TimeStamp; return e; @@ -545,9 +604,9 @@ GF_EXPORT Double gf_rtp_get_current_time(GF_RTPChannel *ch) { Double ret; - if (!ch) return 0.0; + if (!ch || !ch->rtp_time) return 0.0; ret = (Double) ch->CurrentTime; - ret -= (Double) ch->rtp_time; + ret -= (Double) (ch->rtp_time-1); ret /= ch->TimeScale; return ret; } @@ -558,14 +617,12 @@ Double gf_rtp_get_current_time(GF_RTPChannel *ch) GF_EXPORT -GF_Err gf_rtp_send_packet(GF_RTPChannel *ch, GF_RTPHeader *rtp_hdr, char *pck, u32 pck_size, Bool fast_send) +GF_Err gf_rtp_send_packet(GF_RTPChannel *ch, GF_RTPHeader *rtp_hdr, u8 *pck, u32 pck_size, Bool fast_send) { GF_Err e; u32 i, Start; char *hdr = NULL; - GF_BitStream *bs; - if (!ch || !rtp_hdr || !ch->send_buffer || !pck @@ -573,34 +630,42 @@ GF_Err gf_rtp_send_packet(GF_RTPChannel *ch, GF_RTPHeader *rtp_hdr, char *pck, u if (rtp_hdr->CSRCCount) fast_send = GF_FALSE; - if (12 + pck_size + 4*rtp_hdr->CSRCCount > ch->send_buffer_size) return GF_IO_ERR; + if (12 + pck_size + 4*rtp_hdr->CSRCCount > ch->send_buffer_size) + return GF_IO_ERR; if (fast_send) { hdr = pck - 12; - bs = gf_bs_new(hdr, 12, GF_BITSTREAM_WRITE); + gf_bs_reassign_buffer(ch->bs_w, hdr, 12); } else { - bs = gf_bs_new(ch->send_buffer, ch->send_buffer_size, GF_BITSTREAM_WRITE); + gf_bs_reassign_buffer(ch->bs_w, ch->send_buffer, ch->send_buffer_size); } //write header - gf_bs_write_int(bs, rtp_hdr->Version, 2); - gf_bs_write_int(bs, rtp_hdr->Padding, 1); - gf_bs_write_int(bs, rtp_hdr->Extension, 1); - gf_bs_write_int(bs, rtp_hdr->CSRCCount, 4); - gf_bs_write_int(bs, rtp_hdr->Marker, 1); - gf_bs_write_int(bs, rtp_hdr->PayloadType, 7); - gf_bs_write_u16(bs, rtp_hdr->SequenceNumber); - gf_bs_write_u32(bs, rtp_hdr->TimeStamp); - gf_bs_write_u32(bs, ch->SSRC); + gf_bs_write_int(ch->bs_w, rtp_hdr->Version, 2); + gf_bs_write_int(ch->bs_w, rtp_hdr->Padding, 1); + gf_bs_write_int(ch->bs_w, rtp_hdr->Extension, 1); + gf_bs_write_int(ch->bs_w, rtp_hdr->CSRCCount, 4); + gf_bs_write_int(ch->bs_w, rtp_hdr->Marker, 1); + gf_bs_write_int(ch->bs_w, rtp_hdr->PayloadType, 7); + gf_bs_write_u16(ch->bs_w, rtp_hdr->SequenceNumber); + gf_bs_write_u32(ch->bs_w, rtp_hdr->TimeStamp); + gf_bs_write_u32(ch->bs_w, ch->SSRC); for (i=0; iCSRCCount; i++) { - gf_bs_write_u32(bs, rtp_hdr->CSRC[i]); + gf_bs_write_u32(ch->bs_w, rtp_hdr->CSRC[i]); } //nb: RTP header is always aligned - Start = (u32) gf_bs_get_position(bs); - gf_bs_del(bs); + Start = (u32) gf_bs_get_position(ch->bs_w); + if (ch->send_interleave) { + if (fast_send) { + e = ch->send_interleave(ch->interleave_cbk1, ch->interleave_cbk2, GF_FALSE, hdr, pck_size+12); + } else { + memcpy(ch->send_buffer + Start, pck, pck_size); + e = ch->send_interleave(ch->interleave_cbk1, ch->interleave_cbk2, GF_FALSE, ch->send_buffer, Start + pck_size); + } + } //copy payload - if (fast_send) { + else if (fast_send) { e = gf_sk_send(ch->rtp, hdr, pck_size+12); } else { memcpy(ch->send_buffer + Start, pck, pck_size); @@ -624,7 +689,7 @@ GF_Err gf_rtp_send_packet(GF_RTPChannel *ch, GF_RTPHeader *rtp_hdr, char *pck, u ch->last_pck_ts = rtp_hdr->TimeStamp; gf_net_get_ntp(&ch->last_pck_ntp_sec, &ch->last_pck_ntp_frac); - if (!ch->no_auto_rtcp) gf_rtp_send_rtcp_report(ch, NULL, NULL); + if (!ch->no_auto_rtcp) gf_rtp_send_rtcp_report(ch); return GF_OK; } @@ -649,13 +714,14 @@ u32 gf_rtp_get_clockrate(GF_RTPChannel *ch) return ch->TimeScale; } -GF_EXPORT +#if 0 //unused u32 gf_rtp_is_active(GF_RTPChannel *ch) { if (!ch) return 0; if (!ch->rtp_first_SN && !ch->rtp_time) return 0; return 1; } +#endif GF_EXPORT u8 gf_rtp_get_low_interleave_id(GF_RTPChannel *ch) @@ -712,28 +778,28 @@ GF_Err gf_rtp_set_ports(GF_RTPChannel *ch, u16 first_port) GF_EXPORT -GF_Err gf_rtp_setup_payload(GF_RTPChannel *ch, GF_RTPMap *map) +GF_Err gf_rtp_setup_payload(GF_RTPChannel *ch, u32 PayloadType, u32 ClockRate) { - if (!ch || !map) return GF_BAD_PARAM; - ch->PayloadType = map->PayloadType; - ch->TimeScale = map->ClockRate; + if (!ch || !PayloadType || !ClockRate) return GF_BAD_PARAM; + ch->PayloadType = PayloadType; + ch->TimeScale = ClockRate; return GF_OK; } GF_EXPORT -GF_RTSPTransport *gf_rtp_get_transport(GF_RTPChannel *ch) +const GF_RTSPTransport *gf_rtp_get_transport(GF_RTPChannel *ch) { if (!ch) return NULL; return &ch->net_info; } -GF_EXPORT +#if 0 //unused u32 gf_rtp_get_local_ssrc(GF_RTPChannel *ch) { if (!ch) return 0; return ch->SSRC; } - +#endif #if 0 "#RTP log format:\n" @@ -790,29 +856,27 @@ GF_RTPReorder *gf_rtp_reorderer_new(u32 MaxCount, u32 MaxDelay) return tmp; } -static void DelItem(GF_POItem *it) -{ - if (it) { - if (it->next) DelItem(it->next); - gf_free(it->pck); - gf_free(it); - } -} - - GF_EXPORT void gf_rtp_reorderer_del(GF_RTPReorder *po) { - if (po->in) DelItem(po->in); + gf_rtp_reorderer_reset(po); gf_free(po); } GF_EXPORT void gf_rtp_reorderer_reset(GF_RTPReorder *po) { + GF_POItem *item; if (!po) return; - if (po->in) DelItem(po->in); + item = po->in; + while (item) { + GF_POItem *next = item->next; + gf_free(item->pck); + gf_free(item); + item = next; + } + po->head_seqnum = 0; po->Count = 0; po->IsInit = 0; @@ -913,7 +977,7 @@ discard: //ever received packet if its SeqNum was unknown //the BUFFER is yours, you must delete it GF_EXPORT -void *gf_rtp_reorderer_get(GF_RTPReorder *po, u32 *pck_size) +void *gf_rtp_reorderer_get(GF_RTPReorder *po, u32 *pck_size, Bool force_flush) { GF_POItem *t; u32 bounds; @@ -951,6 +1015,9 @@ void *gf_rtp_reorderer_get(GF_RTPReorder *po, u32 *pck_size) //update timing else { check_timeout: + + if (force_flush) goto send_it; + if (!po->LastTime) { po->LastTime = gf_sys_clock(); GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[rtp] Packet Reorderer: starting timeout at %d\n", po->LastTime)); @@ -979,5 +1046,15 @@ send_it: return ret; } +GF_Err gf_rtp_set_interleave_callbacks(GF_RTPChannel *ch, gf_rtp_tcp_callback RTP_TCPCallback, void *cbk1, void *cbk2) +{ + if (!ch) return GF_BAD_PARAM; + ch->send_interleave = RTP_TCPCallback; + ch->interleave_cbk1 = cbk1; + ch->interleave_cbk2 = cbk2; + return GF_OK; +} + + #endif /*GPAC_DISABLE_STREAMING*/ diff --git a/src/ietf/rtp_depacketizer.c b/src/ietf/rtp_depacketizer.c index d682dc0..6195095 100644 --- a/src/ietf/rtp_depacketizer.c +++ b/src/ietf/rtp_depacketizer.c @@ -27,15 +27,28 @@ #ifndef GPAC_DISABLE_STREAMING -#include #include #include +#include #include #include +static void gf_rtp_parse_pass_through(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, u8 *payload, u32 size) +{ + if (!rtp) return; + + rtp->sl_hdr.accessUnitStartFlag = 1; + if (rtp->sl_hdr.compositionTimeStamp != hdr->TimeStamp) + rtp->sl_hdr.accessUnitStartFlag = 1; + rtp->sl_hdr.compositionTimeStamp = hdr->TimeStamp; + rtp->sl_hdr.decodingTimeStamp = hdr->TimeStamp; + rtp->sl_hdr.accessUnitEndFlag = hdr->Marker; + rtp->sl_hdr.randomAccessPointFlag = 1; + rtp->on_sl_packet(rtp->udta, payload, size, &rtp->sl_hdr, GF_OK); +} -static void gf_rtp_parse_mpeg4(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload, u32 size) +static void gf_rtp_parse_mpeg4(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, u8 *payload, u32 size) { u32 aux_size, first_idx, au_hdr_size, num_au; u64 pay_start, au_size; @@ -45,8 +58,6 @@ static void gf_rtp_parse_mpeg4(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char hdr_bs = gf_bs_new(payload, size, GF_BITSTREAM_READ); aux_bs = gf_bs_new(payload, size, GF_BITSTREAM_READ); -// fprintf(stderr, "parsing packet %d size %d ts %d M %d\n", hdr->SequenceNumber, size, hdr->TimeStamp, hdr->Marker); - /*global AU header len*/ au_hdr_size = 0; if (rtp->sl_map.auh_first_min_len) { @@ -179,6 +190,8 @@ static void gf_rtp_parse_mpeg4(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char au_hdr_size -= 1; if (rtp->sl_hdr.randomAccessPointFlag) rtp->sl_hdr.randomAccessPointFlag=1; + } else { + rtp->sl_hdr.randomAccessPointFlag=1; } /*stream state - map directly to seqNum*/ if (rtp->sl_map.StreamStateIndication) { @@ -239,7 +252,7 @@ static void gf_rtp_parse_mpeg4(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char #ifndef GPAC_DISABLE_AV_PARSERS -static void gf_rtp_parse_mpeg12_audio(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload, u32 size) +static void gf_rtp_parse_mpeg12_audio(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, u8 *payload, u32 size) { u16 offset; u32 mp3hdr, ts; @@ -263,12 +276,20 @@ static void gf_rtp_parse_mpeg12_audio(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr /*frame start if no offset*/ rtp->sl_hdr.accessUnitStartFlag = offset ? 0 : 1; + rtp->sl_hdr.randomAccessPointFlag = 1; /*new frame, store size*/ rtp->sl_hdr.compositionTimeStampFlag = 0; if (rtp->sl_hdr.accessUnitStartFlag) { - mp3hdr = GF_4CC((u8) payload[0], (u8) payload[1], (u8) payload[2], (u8) payload[3]); + mp3hdr = GF_4CC((u32) payload[0], (u8) payload[1], (u8) payload[2], (u8) payload[3]); rtp->sl_hdr.accessUnitLength = gf_mp3_frame_size(mp3hdr); + rtp->sl_hdr.channels = gf_mp3_num_channels(mp3hdr); + rtp->sl_hdr.samplerate = gf_mp3_sampling_rate(mp3hdr); + if (rtp->sl_hdr.samplerate) { + rtp->sl_hdr.au_duration = gf_mp3_window_size(mp3hdr); + rtp->sl_hdr.au_duration *= rtp->clock_rate; + rtp->sl_hdr.au_duration /= rtp->sl_hdr.samplerate; + } rtp->sl_hdr.compositionTimeStampFlag = 1; } if (!rtp->sl_hdr.accessUnitLength) break; @@ -300,7 +321,7 @@ static void gf_rtp_parse_mpeg12_audio(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr #endif -static void gf_rtp_parse_mpeg12_video(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload, u32 size) +static void gf_rtp_parse_mpeg12_video(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, u8 *payload, u32 size) { u8 pic_type; @@ -333,9 +354,9 @@ static void gf_rtp_parse_mpeg12_video(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr } } -static void gf_rtp_parse_amr(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload, u32 size) +static void gf_rtp_parse_amr(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, u8 *payload, u32 size) { - unsigned char c, type; + unsigned char c; char *data; /*we support max 30 frames in one RTP packet...*/ u32 nbFrame, i, frame_size; @@ -353,6 +374,7 @@ static void gf_rtp_parse_amr(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *p rtp->sl_hdr.compositionTimeStamp = hdr->TimeStamp; /*then each frame*/ for (i=0; i> 3); if (rtp->payt==GF_RTP_PAYT_AMR) { @@ -364,6 +386,7 @@ static void gf_rtp_parse_amr(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *p rtp->sl_hdr.compositionTimeStampFlag = 1; rtp->sl_hdr.accessUnitStartFlag = 1; rtp->sl_hdr.accessUnitEndFlag = 0; + rtp->sl_hdr.randomAccessPointFlag = 1; /*send TOC*/ rtp->on_sl_packet(rtp->udta, &payload[i+1], 1, &rtp->sl_hdr, GF_OK); rtp->sl_hdr.packetSequenceNumber ++; @@ -378,7 +401,7 @@ static void gf_rtp_parse_amr(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *p } -static void gf_rtp_parse_h263(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload, u32 size) +static void gf_rtp_parse_h263(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, u8 *payload, u32 size) { GF_BitStream *bs; Bool P_bit, V_bit; @@ -413,10 +436,9 @@ static void gf_rtp_parse_h263(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char * rtp->sl_hdr.accessUnitStartFlag = 1; rtp->sl_hdr.accessUnitEndFlag = 0; - if (rtp->sl_hdr.accessUnitStartFlag) { - /*the first 16 bytes are NOT sent on the wire*/ - rtp->sl_hdr.randomAccessPointFlag = (payload[offset+2]&0x02) ? 0 : 1; - } + /*the first 16 bytes are NOT sent on the wire*/ + rtp->sl_hdr.randomAccessPointFlag = (payload[offset+2]&0x02) ? 0 : 1; + /*send missing start code*/ rtp->on_sl_packet(rtp->udta, (char *) blank, 2, &rtp->sl_hdr, GF_OK); /*send payload*/ @@ -437,7 +459,7 @@ static void gf_rtp_parse_h263(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char * static void gf_rtp_ttxt_flush(GF_RTPDepacketizer *rtp, u32 ts) { GF_BitStream *bs; - char *data; + u8 *data; u32 data_size; if (!rtp->inter_bs) return; @@ -472,7 +494,7 @@ static void gf_rtp_ttxt_flush(GF_RTPDepacketizer *rtp, u32 ts) rtp->nb_txt_frag = rtp->cur_txt_frag = rtp->sidx = rtp->txt_len = rtp->nb_mod_frag = 0; } -static void gf_rtp_parse_ttxt(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload, u32 size) +static void gf_rtp_parse_ttxt(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, u8 *payload, u32 size) { Bool is_utf_16; u32 type, ttu_len, duration, ts, sidx, txt_size; @@ -584,7 +606,7 @@ static void gf_rtp_parse_ttxt(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char * static void gf_rtp_h264_flush(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, Bool missed_end) { - char *data; + u8 *data; u32 data_size, nal_s; if (!rtp->inter_bs) return; @@ -605,19 +627,20 @@ static void gf_rtp_h264_flush(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, Bool m data[3] = nal_s&0xFF; } /*set F-bit since nal is corrupted*/ - if (missed_end) data[4] |= 0x80; +// if (missed_end) data[4] |= 0x80; - rtp->sl_hdr.accessUnitEndFlag = (rtp->flags & GF_RTP_UNRELIABLE_M) ? 0 : hdr->Marker; + rtp->sl_hdr.accessUnitEndFlag = hdr->Marker; rtp->sl_hdr.compositionTimeStampFlag = 1; rtp->sl_hdr.compositionTimeStamp = hdr->TimeStamp; rtp->sl_hdr.decodingTimeStampFlag = 0; + rtp->on_sl_packet(rtp->udta, data, data_size, &rtp->sl_hdr, GF_OK); rtp->sl_hdr.accessUnitStartFlag = 0; rtp->sl_hdr.randomAccessPointFlag = 0; gf_free(data); } -void gf_rtp_parse_h264(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload, u32 size) +void gf_rtp_parse_h264(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, u8 *payload, u32 size) { char nalhdr[4]; u32 nal_type; @@ -627,24 +650,17 @@ void gf_rtp_parse_h264(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload /*set start*/ if (rtp->sl_hdr.compositionTimeStamp != hdr->TimeStamp) { - if (rtp->flags & GF_RTP_UNRELIABLE_M) { - rtp->sl_hdr.accessUnitEndFlag = 1; - rtp->on_sl_packet(rtp->udta, NULL, 0, &rtp->sl_hdr, GF_OK); - } rtp->sl_hdr.accessUnitEndFlag = 0; rtp->sl_hdr.accessUnitStartFlag = 1; rtp->sl_hdr.compositionTimeStamp = hdr->TimeStamp; rtp->sl_hdr.compositionTimeStampFlag = 1; rtp->sl_hdr.decodingTimeStampFlag = 0; rtp->sl_hdr.randomAccessPointFlag = 0; - } else if (rtp->sl_hdr.accessUnitEndFlag) { - rtp->flags |= GF_RTP_UNRELIABLE_M; - GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[H264 RTP] error in Marker bit - switching to unreliable mode\n")); } /*single NALU*/ if (nal_type<23) { - if (nal_type==5) { + if (nal_type==GF_AVC_NALU_IDR_SLICE) { rtp->sl_hdr.randomAccessPointFlag = 1; rtp->flags &= ~GF_RTP_AVC_WAIT_RAP; } @@ -670,7 +686,7 @@ void gf_rtp_parse_h264(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload rtp->sl_hdr.accessUnitStartFlag = 0; rtp->sl_hdr.compositionTimeStampFlag = 1; rtp->sl_hdr.compositionTimeStamp = hdr->TimeStamp; - rtp->sl_hdr.accessUnitEndFlag = (rtp->flags & GF_RTP_UNRELIABLE_M) ? 0 : hdr->Marker; + rtp->sl_hdr.accessUnitEndFlag = hdr->Marker; /*send NAL payload*/ rtp->on_sl_packet(rtp->udta, payload, size, &rtp->sl_hdr, GF_OK); @@ -684,7 +700,7 @@ void gf_rtp_parse_h264(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload nal_size<<=8; nal_size |= (u8) payload[offset+1]; offset += 2; - if ((payload[offset] & 0x1F) == 5) { + if ((payload[offset] & 0x1F) == GF_AVC_NALU_IDR_SLICE) { rtp->sl_hdr.randomAccessPointFlag = 1; rtp->flags &= ~GF_RTP_AVC_WAIT_RAP; } @@ -707,7 +723,7 @@ void gf_rtp_parse_h264(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload rtp->sl_hdr.accessUnitStartFlag = 0; rtp->sl_hdr.compositionTimeStampFlag = 0; } - rtp->sl_hdr.accessUnitEndFlag = (!(rtp->flags & GF_RTP_UNRELIABLE_M) && hdr->Marker && (offset+nal_size==size)) ? 1 : 0; + rtp->sl_hdr.accessUnitEndFlag = (hdr->Marker && (offset+nal_size==size)) ? 1 : 0; if (send) rtp->on_sl_packet(rtp->udta, payload+offset, nal_size, &rtp->sl_hdr, GF_OK); offset += nal_size; } @@ -719,7 +735,7 @@ void gf_rtp_parse_h264(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload /*flush*/ if (is_start) gf_rtp_h264_flush(rtp, hdr, GF_TRUE); - if ((payload[1] & 0x1F) == 5) { + if ((payload[1] & 0x1F) == GF_AVC_NALU_IDR_SLICE) { rtp->flags &= ~GF_RTP_AVC_WAIT_RAP; rtp->sl_hdr.randomAccessPointFlag = 1; } else if (rtp->flags & GF_RTP_AVC_WAIT_RAP) @@ -732,7 +748,7 @@ void gf_rtp_parse_h264(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload /*copy F and NRI*/ nal_hdr = payload[0] & 0xE0; /*start bit not set, signal corrupted data (we missed start packet)*/ - if (!is_start) nal_hdr |= 0x80; +// if (!is_start) nal_hdr |= 0x80; /*copy NALU type*/ nal_hdr |= (payload[1] & 0x1F); /*dummy size field*/ @@ -748,7 +764,7 @@ void gf_rtp_parse_h264(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload static void gf_rtp_hevc_flush(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, Bool missed_end) { - char *data; + u8 *data; u32 data_size, nal_s; if (!rtp->inter_bs) return; @@ -766,7 +782,7 @@ static void gf_rtp_hevc_flush(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, Bool m /*set F-bit since nal is corrupted*/ if (missed_end) data[4] |= 0x80; - rtp->sl_hdr.accessUnitEndFlag = (rtp->flags & GF_RTP_UNRELIABLE_M) ? 0 : hdr->Marker; + rtp->sl_hdr.accessUnitEndFlag = hdr->Marker; rtp->sl_hdr.compositionTimeStampFlag = 1; rtp->sl_hdr.compositionTimeStamp = hdr->TimeStamp; rtp->sl_hdr.decodingTimeStampFlag = 0; @@ -776,7 +792,7 @@ static void gf_rtp_hevc_flush(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, Bool m gf_free(data); } -static void gf_rtp_parse_hevc(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload, u32 size) +static void gf_rtp_parse_hevc(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, u8 *payload, u32 size) { u32 nal_type; char nalu_size[4]; @@ -785,19 +801,12 @@ static void gf_rtp_parse_hevc(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char * /*set start*/ if (rtp->sl_hdr.compositionTimeStamp != hdr->TimeStamp) { - if (rtp->flags & GF_RTP_UNRELIABLE_M) { - rtp->sl_hdr.accessUnitEndFlag = 1; - rtp->on_sl_packet(rtp->udta, NULL, 0, &rtp->sl_hdr, GF_OK); - } rtp->sl_hdr.accessUnitEndFlag = 0; rtp->sl_hdr.accessUnitStartFlag = 1; rtp->sl_hdr.compositionTimeStamp = hdr->TimeStamp; rtp->sl_hdr.compositionTimeStampFlag = 1; rtp->sl_hdr.decodingTimeStampFlag = 0; rtp->sl_hdr.randomAccessPointFlag = 0; - } else if (rtp->sl_hdr.accessUnitEndFlag) { - rtp->flags |= GF_RTP_UNRELIABLE_M; - GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[HEVC RTP] error in Marker bit - switching to unreliable mode\n")); } /*Single NALU*/ @@ -820,7 +829,7 @@ static void gf_rtp_parse_hevc(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char * rtp->sl_hdr.accessUnitStartFlag = 0; rtp->sl_hdr.compositionTimeStampFlag = 1; rtp->sl_hdr.compositionTimeStamp = hdr->TimeStamp; - rtp->sl_hdr.accessUnitEndFlag = (rtp->flags & GF_RTP_UNRELIABLE_M) ? 0 : hdr->Marker; + rtp->sl_hdr.accessUnitEndFlag = hdr->Marker; /*send NAL payload*/ rtp->on_sl_packet(rtp->udta, payload, size, &rtp->sl_hdr, GF_OK); @@ -849,7 +858,7 @@ static void gf_rtp_parse_hevc(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char * rtp->sl_hdr.accessUnitStartFlag = 0; rtp->sl_hdr.compositionTimeStampFlag = 0; - rtp->sl_hdr.accessUnitEndFlag = (!(rtp->flags & GF_RTP_UNRELIABLE_M) && hdr->Marker && (offset+nal_size==size)) ? 1 : 0; + rtp->sl_hdr.accessUnitEndFlag = (hdr->Marker && (offset+nal_size==size)) ? 1 : 0; rtp->on_sl_packet(rtp->udta, payload+offset, nal_size, &rtp->sl_hdr, GF_OK); offset += nal_size; } @@ -890,7 +899,7 @@ static void gf_rtp_parse_hevc(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char * #ifndef GPAC_DISABLE_AV_PARSERS -static void gf_rtp_parse_latm(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload, u32 size) +static void gf_rtp_parse_latm(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, u8 *payload, u32 size) { u32 remain, latm_hdr_size, latm_size; @@ -918,6 +927,7 @@ static void gf_rtp_parse_latm(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char * } #endif +#if GPAC_ENABLE_3GPP_DIMS_RTP static void gf_rtp_parse_3gpp_dims(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload, u32 size) { u32 du_size, offset, dsize, hdr_size; @@ -995,27 +1005,30 @@ static void gf_rtp_parse_3gpp_dims(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, c } } +#endif + #ifndef GPAC_DISABLE_AV_PARSERS -static void gf_rtp_parse_ac3(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload, u32 size) +static void gf_rtp_parse_ac3(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, u8 *payload, u32 size) { u8 ft; rtp->sl_hdr.compositionTimeStampFlag = 1; rtp->sl_hdr.compositionTimeStamp = hdr->TimeStamp; + rtp->sl_hdr.randomAccessPointFlag = 1; ft = payload[0]; /*nb_pck = payload[1];*/ payload += 2; size -= 2; if (!ft) { - GF_AC3Header hdr; - memset(&hdr, 0, sizeof(GF_AC3Header)); + GF_AC3Header ac3hdr; + memset(&ac3hdr, 0, sizeof(GF_AC3Header)); rtp->sl_hdr.accessUnitStartFlag = rtp->sl_hdr.accessUnitEndFlag = 1; while (size) { u32 offset; - if (!gf_ac3_parser((u8*)payload, size, &offset, &hdr, GF_FALSE)) { + if (!gf_ac3_parser((u8*)payload, size, &offset, &ac3hdr, GF_FALSE)) { return; } if (offset) { @@ -1023,10 +1036,10 @@ static void gf_rtp_parse_ac3(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *p payload+=offset; size-=offset; } - rtp->on_sl_packet(rtp->udta, payload, hdr.framesize, &rtp->sl_hdr, GF_OK); - if (size < hdr.framesize) return; - size -= hdr.framesize; - payload += hdr.framesize; + rtp->on_sl_packet(rtp->udta, payload, ac3hdr.framesize, &rtp->sl_hdr, GF_OK); + if (size < ac3hdr.framesize) return; + size -= ac3hdr.framesize; + payload += ac3hdr.framesize; rtp->sl_hdr.compositionTimeStamp += 1536; } rtp->flags |= GF_RTP_NEW_AU; @@ -1078,7 +1091,9 @@ static u32 gf_rtp_get_payload_type(GF_RTPMap *map, GF_SDPMedia *media) else if (!stricmp(map->payload_name, "AMR-WB")) return GF_RTP_PAYT_AMR_WB; else if (!stricmp(map->payload_name, "3gpp-tt")) return GF_RTP_PAYT_3GPP_TEXT; else if (!stricmp(map->payload_name, "H264")) return GF_RTP_PAYT_H264_AVC; +#if GPAC_ENABLE_3GPP_DIMS_RTP else if (!stricmp(map->payload_name, "richmedia+xml")) return GF_RTP_PAYT_3GPP_DIMS; +#endif else if (!stricmp(map->payload_name, "ac3")) return GF_RTP_PAYT_AC3; else if (!stricmp(map->payload_name, "H264-SVC")) return GF_RTP_PAYT_H264_SVC; else if (!stricmp(map->payload_name, "H265")) return GF_RTP_PAYT_HEVC; @@ -1134,7 +1149,7 @@ static GF_Err payt_set_param(GF_RTPDepacketizer *rtp, char *param_name, char *pa } /*object type indication (not needed when IOD is here)*/ else if (!stricmp(param_name, "ObjectType")) { - rtp->sl_map.ObjectTypeIndication = atoi(param_val); + rtp->sl_map.CodecID = atoi(param_val); } else if (!stricmp(param_name, "StreamType")) rtp->sl_map.StreamType = atoi(param_val); @@ -1143,12 +1158,12 @@ static GF_Err payt_set_param(GF_RTPDepacketizer *rtp, char *param_name, char *pa /*in case no IOD and no streamType/OTI in the file*/ if (!stricmp(param_val, "AAC-hbr") || !stricmp(param_val, "AAC-lbr") || !stricmp(param_val, "CELP-vbr") || !stricmp(param_val, "CELP-cbr")) { rtp->sl_map.StreamType = GF_STREAM_AUDIO; - rtp->sl_map.ObjectTypeIndication = GPAC_OTI_AUDIO_AAC_MPEG4; + rtp->sl_map.CodecID = GF_CODECID_AAC_MPEG4; } /*in case no IOD and no streamType/OTI in the file*/ else if (!stricmp(param_val, "avc-video") ) { rtp->sl_map.StreamType = GF_STREAM_VISUAL; - rtp->sl_map.ObjectTypeIndication = GPAC_OTI_VIDEO_AVC; + rtp->sl_map.CodecID = GF_CODECID_AVC; } } @@ -1171,7 +1186,7 @@ static GF_Err payt_set_param(GF_RTPDepacketizer *rtp, char *param_name, char *pa } /*ISMACryp config*/ else if (!stricmp(param_name, "ISMACrypCryptoSuite")) { if (!stricmp(param_val, "AES_CTR_128")) - rtp->isma_scheme = GF_4CC('i','A','E','C'); + rtp->isma_scheme = GF_ISOM_ISMACRYP_SCHEME; else rtp->isma_scheme = 0; } @@ -1206,18 +1221,20 @@ static GF_Err gf_rtp_payt_setup(GF_RTPDepacketizer *rtp, GF_RTPMap *map, GF_SDPM /*reset sl map*/ memset(&rtp->sl_map, 0, sizeof(GP_RTPSLMap)); - if (!stricmp(map->payload_name, "enc-mpeg4-generic")) rtp->flags |= GF_RTP_HAS_ISMACRYP; + if (map && !stricmp(map->payload_name, "enc-mpeg4-generic")) rtp->flags |= GF_RTP_HAS_ISMACRYP; /*then process all FMTPs*/ - i=0; - while ((fmtp = (GF_SDP_FMTP*)gf_list_enum(media->FMTP, &i))) { - GF_X_Attribute *att; - //we work with only one PayloadType for now - if (fmtp->PayloadType != map->PayloadType) continue; - j=0; - while ((att = (GF_X_Attribute *)gf_list_enum(fmtp->Attributes, &j))) { - payt_set_param(rtp, att->Name, att->Value); + if (media) { + i=0; + while ((fmtp = (GF_SDP_FMTP*)gf_list_enum(media->FMTP, &i))) { + GF_X_Attribute *att; + //we work with only one PayloadType for now + if (map && (fmtp->PayloadType != map->PayloadType)) continue; + j=0; + while ((att = (GF_X_Attribute *)gf_list_enum(fmtp->Attributes, &j))) { + payt_set_param(rtp, att->Name, att->Value); + } } } @@ -1274,7 +1291,7 @@ static GF_Err gf_rtp_payt_setup(GF_RTPDepacketizer *rtp, GF_RTPMap *map, GF_SDPM gf_bs_get_content(bs, &rtp->sl_map.config, &rtp->sl_map.configSize); gf_bs_del(bs); rtp->sl_map.StreamType = GF_STREAM_AUDIO; - rtp->sl_map.ObjectTypeIndication = GPAC_OTI_AUDIO_AAC_MPEG4; + rtp->sl_map.CodecID = GF_CODECID_AAC_MPEG4; /*assign depacketizer*/ rtp->depacketize = gf_rtp_parse_latm; @@ -1285,7 +1302,7 @@ static GF_Err gf_rtp_payt_setup(GF_RTPDepacketizer *rtp, GF_RTPMap *map, GF_SDPM /*mark if AU header is present*/ rtp->sl_map.auh_first_min_len = 0; if (rtp->flags & GF_RTP_HAS_ISMACRYP) { - if (!rtp->isma_scheme) rtp->isma_scheme = GF_4CC('i','A','E','C'); + if (!rtp->isma_scheme) rtp->isma_scheme = GF_ISOM_ISMACRYP_SCHEME; if (!rtp->sl_map.IV_length) rtp->sl_map.IV_length = 4; if (rtp->flags & GF_RTP_ISMA_SEL_ENC) rtp->sl_map.auh_first_min_len += 8; @@ -1299,36 +1316,38 @@ static GF_Err gf_rtp_payt_setup(GF_RTPDepacketizer *rtp, GF_RTPMap *map, GF_SDPM rtp->sl_map.auh_min_len = rtp->sl_map.auh_first_min_len; rtp->sl_map.auh_first_min_len += rtp->sl_map.IndexLength; rtp->sl_map.auh_min_len += rtp->sl_map.IndexDeltaLength; + if (!map) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTP] Missing required payload map\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } /*RFC3016 flags*/ if (!stricmp(map->payload_name, "MP4V-ES")) { rtp->sl_map.StreamType = GF_STREAM_VISUAL; - rtp->sl_map.ObjectTypeIndication = GPAC_OTI_VIDEO_MPEG4_PART2; + rtp->sl_map.CodecID = GF_CODECID_MPEG4_PART2; } else if (!strnicmp(map->payload_name, "AAC", 3)) { rtp->sl_map.StreamType = GF_STREAM_AUDIO; - rtp->sl_map.ObjectTypeIndication = GPAC_OTI_AUDIO_AAC_MPEG4; + rtp->sl_map.CodecID = GF_CODECID_AAC_MPEG4; } else if (!stricmp(map->payload_name, "MP4A-LATM")) { rtp->sl_map.StreamType = GF_STREAM_AUDIO; - rtp->sl_map.ObjectTypeIndication = GPAC_OTI_AUDIO_AAC_MPEG4; + rtp->sl_map.CodecID = GF_CODECID_AAC_MPEG4; } /*MPEG-4 video, check RAPs if not indicated*/ - if ((rtp->sl_map.StreamType == GF_STREAM_VISUAL) && (rtp->sl_map.ObjectTypeIndication == GPAC_OTI_VIDEO_MPEG4_PART2) && !rtp->sl_map.RandomAccessIndication) { + if ((rtp->sl_map.StreamType == GF_STREAM_VISUAL) && (rtp->sl_map.CodecID == GF_CODECID_MPEG4_PART2) && !rtp->sl_map.RandomAccessIndication) { rtp->flags |= GF_RTP_M4V_CHECK_RAP; } #ifndef GPAC_DISABLE_AV_PARSERS - if ((rtp->sl_map.ObjectTypeIndication == GPAC_OTI_AUDIO_AAC_MPEG4) && !rtp->sl_map.config) { + if ((rtp->sl_map.CodecID == GF_CODECID_AAC_MPEG4) && !rtp->sl_map.config && media) { GF_M4ADecSpecInfo cfg; - GF_RTPMap*map = (GF_RTPMap*)gf_list_get(media->RTPMaps, 0); + GF_RTPMap *a_map = (GF_RTPMap*)gf_list_get(media->RTPMaps, 0); memset(&cfg, 0, sizeof(GF_M4ADecSpecInfo)); cfg.audioPL = rtp->sl_map.PL_ID; - cfg.nb_chan = map->AudioChannels; - cfg.nb_chan = 1; - cfg.base_sr = map->ClockRate/2; - cfg.sbr_sr = map->ClockRate; - cfg.base_object_type = GF_M4A_AAC_LC; - cfg.base_object_type = 5; + cfg.nb_chan = a_map->AudioChannels ? a_map->AudioChannels : 1; + cfg.base_sr = a_map->ClockRate/2; + cfg.sbr_sr = a_map->ClockRate; + cfg.base_object_type = GF_M4A_AAC_SBR; cfg.sbr_object_type = GF_M4A_AAC_MAIN; gf_m4a_write_config(&cfg, &rtp->sl_map.config, &rtp->sl_map.configSize); } @@ -1339,7 +1358,7 @@ static GF_Err gf_rtp_payt_setup(GF_RTPDepacketizer *rtp, GF_RTPMap *map, GF_SDPM #ifndef GPAC_DISABLE_AV_PARSERS case GF_RTP_PAYT_MPEG12_AUDIO: rtp->sl_map.StreamType = GF_STREAM_AUDIO; - rtp->sl_map.ObjectTypeIndication = GPAC_OTI_AUDIO_MPEG2_PART3; + rtp->sl_map.CodecID = GF_CODECID_MPEG2_PART3; /*assign depacketizer*/ rtp->depacketize = gf_rtp_parse_mpeg12_audio; break; @@ -1350,77 +1369,52 @@ static GF_Err gf_rtp_payt_setup(GF_RTPDepacketizer *rtp, GF_RTPMap *map, GF_SDPM rtp->sl_map.RandomAccessIndication = GF_TRUE; rtp->sl_map.StreamType = GF_STREAM_VISUAL; /*FIXME: how to differentiate MPEG1 from MPEG2 video before any frame is received??*/ - rtp->sl_map.ObjectTypeIndication = GPAC_OTI_VIDEO_MPEG1; + rtp->sl_map.CodecID = GF_CODECID_MPEG1; /*assign depacketizer*/ rtp->depacketize = gf_rtp_parse_mpeg12_video; break; case GF_RTP_PAYT_AMR: case GF_RTP_PAYT_AMR_WB: - { - GF_BitStream *bs; rtp->sl_map.StreamType = GF_STREAM_AUDIO; - rtp->sl_map.ObjectTypeIndication = GPAC_OTI_MEDIA_GENERIC; - /*create DSI*/ - bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - if (rtp->payt == GF_RTP_PAYT_AMR) { - gf_bs_write_u32(bs, GF_4CC('s', 'a', 'm', 'r')); - gf_bs_write_u32(bs, 8000); - gf_bs_write_u16(bs, 1); - gf_bs_write_u16(bs, 160); - } else { - gf_bs_write_u32(bs, GF_4CC('s', 'a', 'w', 'b')); - gf_bs_write_u32(bs, 16000); - gf_bs_write_u16(bs, 1); - gf_bs_write_u16(bs, 320); - } - gf_bs_write_u8(bs, 16); - gf_bs_write_u8(bs, 1); - gf_bs_get_content(bs, &rtp->sl_map.config, &rtp->sl_map.configSize); - gf_bs_del(bs); + rtp->sl_map.CodecID = (rtp->payt == GF_RTP_PAYT_AMR) ? GF_CODECID_AMR : GF_CODECID_AMR_WB; /*assign depacketizer*/ rtp->depacketize = gf_rtp_parse_amr; - } - break; + break; case GF_RTP_PAYT_H263: - { - u32 x, y, w, h; - GF_X_Attribute *att; - GF_BitStream *bs; - x = y = w = h = 0; - j=0; - while ((att = (GF_X_Attribute *)gf_list_enum(media->Attributes, &j))) { - if (stricmp(att->Name, "cliprect")) continue; - /*only get the display area*/ - sscanf(att->Value, "%u,%u,%u,%u", &y, &x, &h, &w); + if (media) { + GF_X_Attribute *att; + j=0; + while ((att = (GF_X_Attribute *)gf_list_enum(media->Attributes, &j))) { + if (stricmp(att->Name, "cliprect")) continue; + /*only get the display area*/ + sscanf(att->Value, "%u,%u,%u,%u", &rtp->y, &rtp->x, &rtp->h, &rtp->w); + } } - rtp->sl_map.StreamType = GF_STREAM_VISUAL; - rtp->sl_map.ObjectTypeIndication = GPAC_OTI_MEDIA_GENERIC; - /*create DSI*/ - bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - gf_bs_write_u32(bs, GF_4CC('h', '2', '6', '3')); - gf_bs_write_u16(bs, w); - gf_bs_write_u16(bs, h); - gf_bs_get_content(bs, &rtp->sl_map.config, &rtp->sl_map.configSize); - gf_bs_del(bs); + rtp->sl_map.CodecID = GF_CODECID_H263; + /*we signal RAPs*/ rtp->sl_map.RandomAccessIndication = GF_TRUE; + /*assign depacketizer*/ rtp->depacketize = gf_rtp_parse_h263; - } - break; + break; + case GF_RTP_PAYT_3GPP_TEXT: { char *tx3g, *a_tx3g; GF_BitStream *bs; u32 nb_desc; - GF_SDP_FMTP *fmtp; GF_TextConfig tcfg; memset(&tcfg, 0, sizeof(GF_TextConfig)); tcfg.tag = GF_ODF_TEXT_CFG_TAG; tcfg.Base3GPPFormat = 0x10; tcfg.MPEGExtendedFormat = 0x10; tcfg.profileLevel = 0x10; + if (!map || !media) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTP] Missing required payload map\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } tcfg.timescale = map->ClockRate; tcfg.sampleDescriptionFlags = 1; tx3g = NULL; @@ -1466,18 +1460,22 @@ static GF_Err gf_rtp_payt_setup(GF_RTPDepacketizer *rtp, GF_RTPMap *map, GF_SDPM } a_tx3g = tx3g; gf_bs_write_u8(bs, nb_desc); + nb_desc = 1; while (1) { char *next_tx3g, szOut[1000]; - u32 len; - strcpy(a_tx3g, tx3g); + u32 len, s_len; next_tx3g = strchr(a_tx3g, ','); - if (next_tx3g) next_tx3g[0] = 0; - len = gf_base64_decode(a_tx3g, (u32) strlen(a_tx3g), szOut, 1000); + if (next_tx3g) s_len = (u32) (next_tx3g - a_tx3g - 1); + else s_len = (u32) strlen(a_tx3g); + + len = gf_base64_decode(a_tx3g, s_len, szOut, 1000); + nb_desc++; gf_bs_write_data(bs, szOut, len); - tx3g = strchr(tx3g, ','); - if (!tx3g) break; - tx3g += 1; - while (tx3g[0] == ' ') tx3g += 1; + if (!next_tx3g) break; + a_tx3g = strchr(a_tx3g, ','); + if (!a_tx3g) break; + a_tx3g += 1; + while (a_tx3g[0] == ' ') a_tx3g += 1; } /*write video cfg*/ @@ -1487,7 +1485,7 @@ static GF_Err gf_rtp_payt_setup(GF_RTPDepacketizer *rtp, GF_RTPMap *map, GF_SDPM gf_bs_write_u16(bs, tcfg.vert_offset); gf_bs_get_content(bs, &rtp->sl_map.config, &rtp->sl_map.configSize); rtp->sl_map.StreamType = GF_STREAM_TEXT; - rtp->sl_map.ObjectTypeIndication = 0x08; + rtp->sl_map.CodecID = GF_CODECID_TEXT_MPEG4; gf_bs_del(bs); /*assign depacketizer*/ rtp->depacketize = gf_rtp_parse_ttxt; @@ -1497,15 +1495,20 @@ static GF_Err gf_rtp_payt_setup(GF_RTPDepacketizer *rtp, GF_RTPMap *map, GF_SDPM case GF_RTP_PAYT_H264_AVC: case GF_RTP_PAYT_H264_SVC: { - GF_SDP_FMTP *fmtp; - GF_AVCConfig *avcc = gf_odf_avc_cfg_new(); + GF_AVCConfig *avcc; + if (!map || !media) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTP] Missing required payload map\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + + avcc = gf_odf_avc_cfg_new(); avcc->AVCProfileIndication = (rtp->sl_map.PL_ID>>16) & 0xFF; avcc->profile_compatibility = (rtp->sl_map.PL_ID>>8) & 0xFF; avcc->AVCLevelIndication = rtp->sl_map.PL_ID & 0xFF; avcc->configurationVersion = 1; avcc->nal_unit_size = 4; rtp->sl_map.StreamType = 4; - rtp->sl_map.ObjectTypeIndication = GPAC_OTI_VIDEO_AVC; + rtp->sl_map.CodecID = GF_CODECID_AVC; /*we will signal RAPs*/ rtp->sl_map.RandomAccessIndication = GF_TRUE; /*rewrite sps and pps*/ @@ -1554,7 +1557,11 @@ static GF_Err gf_rtp_payt_setup(GF_RTPDepacketizer *rtp, GF_RTPMap *map, GF_SDPM } } } - gf_odf_avc_cfg_write(avcc, &rtp->sl_map.config, &rtp->sl_map.configSize); + if (gf_list_count(avcc->sequenceParameterSets) && gf_list_count(avcc->pictureParameterSets)) { + gf_odf_avc_cfg_write(avcc, &rtp->sl_map.config, &rtp->sl_map.configSize); + } else { + rtp->flags |= GF_RTP_AVC_USE_ANNEX_B; + } gf_odf_avc_cfg_del(avcc); } /*assign depacketizer*/ @@ -1564,12 +1571,16 @@ static GF_Err gf_rtp_payt_setup(GF_RTPDepacketizer *rtp, GF_RTPMap *map, GF_SDPM case GF_RTP_PAYT_LHVC: #ifndef GPAC_DISABLE_HEVC { - GF_SDP_FMTP *fmtp; - GF_HEVCConfig *hevcc = gf_odf_hevc_cfg_new(); + GF_HEVCConfig *hevcc; + if (!map || !media) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTP] Missing required payload map\n")); + return GF_NON_COMPLIANT_BITSTREAM; + } + hevcc = gf_odf_hevc_cfg_new(); hevcc->configurationVersion = 1; hevcc->nal_unit_size = 4; rtp->sl_map.StreamType = 4; - rtp->sl_map.ObjectTypeIndication = GPAC_OTI_VIDEO_HEVC; + rtp->sl_map.CodecID = GF_CODECID_HEVC; /*we will signal RAPs*/ rtp->sl_map.RandomAccessIndication = GF_TRUE; i=0; @@ -1643,10 +1654,11 @@ static GF_Err gf_rtp_payt_setup(GF_RTPDepacketizer *rtp, GF_RTPMap *map, GF_SDPM break; #endif /*GPAC_DISABLE_AV_PARSERS*/ +#if GPAC_ENABLE_3GPP_DIMS_RTP /*todo - rewrite DIMS config*/ case GF_RTP_PAYT_3GPP_DIMS: rtp->sl_map.StreamType = GF_STREAM_SCENE; - rtp->sl_map.ObjectTypeIndication = GPAC_OTI_SCENE_DIMS; + rtp->sl_map.CodecID = GF_CODECID_DIMS; /*we will signal RAPs*/ rtp->sl_map.RandomAccessIndication = GF_TRUE; /*we map DIMS CTR to AU seq num, hence 3 bits*/ @@ -1655,49 +1667,128 @@ static GF_Err gf_rtp_payt_setup(GF_RTPDepacketizer *rtp, GF_RTPMap *map, GF_SDPM /*assign depacketizer*/ rtp->depacketize = gf_rtp_parse_3gpp_dims; break; +#endif + #ifndef GPAC_DISABLE_AV_PARSERS case GF_RTP_PAYT_AC3: rtp->sl_map.StreamType = GF_STREAM_AUDIO; - rtp->sl_map.ObjectTypeIndication = 0xA5; + rtp->sl_map.CodecID = GF_CODECID_AC3; rtp->sl_map.RandomAccessIndication = GF_TRUE; /*assign depacketizer*/ rtp->depacketize = gf_rtp_parse_ac3; break; #endif /*GPAC_DISABLE_AV_PARSERS*/ default: - return GF_NOT_SUPPORTED; + if (rtp->payt >= GF_RTP_PAYT_LAST_STATIC_DEFINED) + return GF_NOT_SUPPORTED; + rtp->depacketize = gf_rtp_parse_pass_through; + return GF_OK; } return GF_OK; } +const GF_RTPStaticMap static_payloads [] = +{ + { GF_RTP_PAYT_PCMU, 8000, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_GSM, 8000, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_G723, 8000, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_DVI4_8K, 8000, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_DVI4_16K, 16000, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_LPC, 8000, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_PCMA, 8000, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_G722, 8000, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_L16_STEREO, 44100, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_L16_MONO, 44100, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_QCELP_BASIC, 8000, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_CN, 8000, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_MPEG12_AUDIO, 90000, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_G728, 8000, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_DVI4_11K, 11025, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_DVI4_22K, 22050, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_G729, 8000, GF_STREAM_AUDIO}, + { GF_RTP_PAYT_CelB, 90000, GF_STREAM_VISUAL}, + { GF_RTP_PAYT_JPEG, 90000, GF_STREAM_VISUAL}, + { GF_RTP_PAYT_nv, 90000, GF_STREAM_VISUAL}, + { GF_RTP_PAYT_H261, 90000, GF_STREAM_VISUAL}, + { GF_RTP_PAYT_MPEG12_VIDEO, 90000, GF_STREAM_VISUAL}, + { GF_RTP_PAYT_MP2T, 90000, GF_STREAM_FILE, 0, "video/mp2t"}, + { GF_RTP_PAYT_H263, 90000, GF_STREAM_VISUAL} +}; + +static const GF_RTPStaticMap *gf_rtp_is_valid_static_payt(u32 payt) +{ + u32 i, count = sizeof(static_payloads) / sizeof(struct rtp_static_payt); + if (payt>= GF_RTP_PAYT_LAST_STATIC_DEFINED) return NULL; + for (i=0; ifmt_list || (gf_list_count(media->RTPMaps) > 1)) return NULL; + if (!sl_packet_cbk || (!media && !hdr_payt)) return NULL; + if (media && (gf_list_count(media->RTPMaps) > 1)) return NULL; - /*check payload type*/ - map = (GF_RTPMap *)gf_list_get(media->RTPMaps, 0); +#ifdef GPAC_ENABEL_COVERAGE + if (gf_sys_is_cov_mode()) + gf_rtp_is_valid_static_payt(22); +#endif - payt = gf_rtp_get_payload_type(map, media); - if (!payt) return NULL; + if (!media) { + static_map = gf_rtp_is_valid_static_payt(hdr_payt); + if (!static_map) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("RTP: Invalid/unsupported static payload type %d\n", hdr_payt)); + return NULL; + } + clock_rate = static_map->clock_rate; + payt = hdr_payt; + } else { + /*check payload type*/ + map = (GF_RTPMap *)gf_list_get(media->RTPMaps, 0); + if (!map) { + //we deal with at most one format + if (!media->fmt_list || strchr(media->fmt_list, ' ')) return NULL; + + payt = atoi(media->fmt_list); + static_map = gf_rtp_is_valid_static_payt(payt); + if (!static_map) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("RTP: Invalid static payload type %d\n", payt)); + return NULL; + } + clock_rate = static_map->clock_rate; + } else { + payt = gf_rtp_get_payload_type(map, media); + if (!payt) return NULL; + clock_rate = map->ClockRate; + } + } GF_SAFEALLOC(tmp, GF_RTPDepacketizer); if (!tmp) return NULL; tmp->payt = payt; + tmp->static_map = static_map; e = gf_rtp_payt_setup(tmp, map, media); if (e) { gf_free(tmp); return NULL; } + assert(tmp->depacketize); - tmp->clock_rate = map->ClockRate; + tmp->clock_rate = clock_rate; tmp->on_sl_packet = sl_packet_cbk; tmp->udta = udta; return tmp; @@ -1730,7 +1821,7 @@ void gf_rtp_depacketizer_del(GF_RTPDepacketizer *rtp) } GF_EXPORT -void gf_rtp_depacketizer_process(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, char *payload, u32 size) +void gf_rtp_depacketizer_process(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, u8 *payload, u32 size) { assert(rtp && rtp->depacketize); rtp->sl_hdr.sender_ntp = hdr->recomputed_ntp_ts; @@ -1738,7 +1829,7 @@ void gf_rtp_depacketizer_process(GF_RTPDepacketizer *rtp, GF_RTPHeader *hdr, cha } -GF_EXPORT +#if 0 //unused void gf_rtp_depacketizer_get_slconfig(GF_RTPDepacketizer *rtp, GF_SLConfig *slc) { memset(slc, 0, sizeof(GF_SLConfig)); @@ -1781,6 +1872,7 @@ void gf_rtp_depacketizer_get_slconfig(GF_RTPDepacketizer *rtp, GF_SLConfig *slc) } } } +#endif #endif /*GPAC_DISABLE_STREAMING*/ diff --git a/src/ietf/rtp_packetizer.c b/src/ietf/rtp_packetizer.c index 82a0e61..1ae3015 100644 --- a/src/ietf/rtp_packetizer.c +++ b/src/ietf/rtp_packetizer.c @@ -41,11 +41,11 @@ GP_RTPPacketizer *gf_rtp_builder_new(u32 rtp_payt, GF_SLConfig *slc, u32 flags, void (*OnNewPacket)(void *cbk, GF_RTPHeader *header), void (*OnPacketDone)(void *cbk, GF_RTPHeader *header), void (*OnDataReference)(void *cbk, u32 payload_size, u32 offset_from_orig), - void (*OnData)(void *cbk, char *data, u32 data_size, Bool is_head) + void (*OnData)(void *cbk, u8 *data, u32 data_size, Bool is_head) ) { GP_RTPPacketizer *tmp; - if (!rtp_payt || !cbk_obj | !OnPacketDone) return NULL; + if (!rtp_payt || !cbk_obj || !OnPacketDone) return NULL; GF_SAFEALLOC(tmp, GP_RTPPacketizer); if (!tmp) return NULL; @@ -84,7 +84,7 @@ void gf_rtp_builder_del(GP_RTPPacketizer *builder) } GF_EXPORT -GF_Err gf_rtp_builder_process(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration, u8 descIndex) +GF_Err gf_rtp_builder_process(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration, u8 descIndex) { if (!builder) return GF_BAD_PARAM; @@ -113,13 +113,17 @@ GF_Err gf_rtp_builder_process(GP_RTPPacketizer *builder, char *data, u32 data_si return gp_rtp_builder_do_smv(builder, data, data_size, IsAUEnd, FullAUSize); case GF_RTP_PAYT_LATM: return gp_rtp_builder_do_latm(builder, data, data_size, IsAUEnd, FullAUSize, duration); +#if GPAC_ENABLE_3GPP_DIMS_RTP case GF_RTP_PAYT_3GPP_DIMS: return gp_rtp_builder_do_dims(builder, data, data_size, IsAUEnd, FullAUSize, duration); +#endif case GF_RTP_PAYT_AC3: return gp_rtp_builder_do_ac3(builder, data, data_size, IsAUEnd, FullAUSize); case GF_RTP_PAYT_HEVC: case GF_RTP_PAYT_LHVC: return gp_rtp_builder_do_hevc(builder, data, data_size, IsAUEnd, FullAUSize); + case GF_RTP_PAYT_MP2T: + return gp_rtp_builder_do_mp2t(builder, data, data_size, IsAUEnd, FullAUSize); default: return GF_NOT_SUPPORTED; } @@ -129,7 +133,7 @@ GF_Err gf_rtp_builder_process(GP_RTPPacketizer *builder, char *data, u32 data_si //Compute the #params of the slMap GF_EXPORT void gf_rtp_builder_init(GP_RTPPacketizer *builder, u8 PayloadType, u32 PathMTU, u32 max_ptime, - u32 StreamType, u32 OTI, u32 PL_ID, + u32 StreamType, u32 codecid, u32 PL_ID, u32 avgSize, u32 maxSize, u32 avgTS, u32 maxDTS, u32 IV_length, u32 KI_length, @@ -141,7 +145,7 @@ void gf_rtp_builder_init(GP_RTPPacketizer *builder, u8 PayloadType, u32 PathMTU, builder->Path_MTU = PathMTU; builder->PayloadType = PayloadType; builder->slMap.StreamType = StreamType; - builder->slMap.ObjectTypeIndication = OTI; + builder->slMap.CodecID = codecid; builder->slMap.PL_ID = PL_ID; builder->max_ptime = max_ptime; if (pref_mode) strcpy(builder->slMap.mode, pref_mode); @@ -207,7 +211,7 @@ void gf_rtp_builder_init(GP_RTPPacketizer *builder, u8 PayloadType, u32 PathMTU, builder->flags &= 0x07; /*disable aggregation for visual streams, except for AVC where STAP/MTAP can be used*/ if (StreamType==GF_STREAM_VISUAL) { - if ((OTI != GPAC_OTI_VIDEO_AVC) && (OTI != GPAC_OTI_VIDEO_SVC) && (OTI != GPAC_OTI_VIDEO_MVC) && (OTI != GPAC_OTI_VIDEO_HEVC) && (OTI != GPAC_OTI_VIDEO_LHVC)) { + if ((codecid != GF_CODECID_AVC) && (codecid != GF_CODECID_SVC) && (codecid != GF_CODECID_MVC) && (codecid != GF_CODECID_HEVC) && (codecid != GF_CODECID_LHVC)) { builder->flags &= ~GP_RTP_PCK_USE_MULTI; } } @@ -328,7 +332,7 @@ void gf_rtp_builder_init(GP_RTPPacketizer *builder, u8 PayloadType, u32 PathMTU, builder->flags &= ~GP_RTP_PCK_SIGNAL_AU_IDX; builder->flags &= ~GP_RTP_PCK_USE_INTERLEAVING; builder->flags &= ~GP_RTP_PCK_KEY_IDX_PER_AU; - gf_rtp_builder_init(builder, PayloadType, PathMTU, max_ptime, StreamType, OTI, PL_ID, avgSize, maxSize, avgTS, maxDTS, IV_length, KI_length, pref_mode); + gf_rtp_builder_init(builder, PayloadType, PathMTU, max_ptime, StreamType, codecid, PL_ID, avgSize, maxSize, avgTS, maxDTS, IV_length, KI_length, pref_mode); return; } @@ -377,7 +381,7 @@ check_header: builder->slMap.IV_delta_length = gf_get_bit_size(maxSize); } /*ISMACryp video mode*/ - if ((builder->slMap.StreamType==GF_STREAM_VISUAL) && (builder->slMap.ObjectTypeIndication==GPAC_OTI_VIDEO_MPEG4_PART2) + if ((builder->slMap.StreamType==GF_STREAM_VISUAL) && (builder->slMap.CodecID==GF_CODECID_MPEG4_PART2) && (builder->flags & GP_RTP_PCK_SIGNAL_RAP) && builder->slMap.IV_length && !(builder->flags & GP_RTP_PCK_SIGNAL_AU_IDX) && !(builder->flags & GP_RTP_PCK_SIGNAL_SIZE) /*shall have SignalTS*/ @@ -386,7 +390,7 @@ check_header: strcpy(builder->slMap.mode, "mpeg4-video"); } /*ISMACryp AVC video mode*/ - else if ((builder->slMap.StreamType==GF_STREAM_VISUAL) && (builder->slMap.ObjectTypeIndication==GPAC_OTI_VIDEO_AVC) + else if ((builder->slMap.StreamType==GF_STREAM_VISUAL) && (builder->slMap.CodecID==GF_CODECID_AVC) && (builder->flags & GP_RTP_PCK_SIGNAL_RAP) && builder->slMap.IV_length && !(builder->flags & GP_RTP_PCK_SIGNAL_AU_IDX) && !(builder->flags & GP_RTP_PCK_SIGNAL_SIZE) /*shall have SignalTS*/ @@ -411,8 +415,9 @@ check_header: } } -void gp_rtp_builder_set_cryp_info(GP_RTPPacketizer *builder, u64 IV, char *key_indicator, Bool is_encrypted) +void gf_rtp_builder_set_cryp_info(GP_RTPPacketizer *builder, u64 IV, char *key_indicator, Bool is_encrypted) { + if (!builder) return; if (!key_indicator) { if (builder->key_indicator) { /*force flush if no provision for keyIndicator per AU*/ @@ -441,13 +446,13 @@ void gp_rtp_builder_set_cryp_info(GP_RTPPacketizer *builder, u64 IV, char *key_i } GF_EXPORT -Bool gf_rtp_builder_get_payload_name(GP_RTPPacketizer *rtpb, char *szPayloadName, char *szMediaName) +Bool gf_rtp_builder_get_payload_name(GP_RTPPacketizer *rtpb, char szPayloadName[20], char szMediaName[20]) { u32 flags = rtpb->flags; switch (rtpb->rtp_payt) { case GF_RTP_PAYT_MPEG4: - if ((rtpb->slMap.StreamType==GF_STREAM_VISUAL) && (rtpb->slMap.ObjectTypeIndication==GPAC_OTI_VIDEO_MPEG4_PART2)) { + if ((rtpb->slMap.StreamType==GF_STREAM_VISUAL) && (rtpb->slMap.CodecID==GF_CODECID_MPEG4_PART2)) { strcpy(szMediaName, "video"); /*ISMACryp video*/ if ( (flags & GP_RTP_PCK_SIGNAL_RAP) && rtpb->slMap.IV_length @@ -508,7 +513,11 @@ Bool gf_rtp_builder_get_payload_name(GP_RTPPacketizer *rtpb, char *szPayloadName return GF_TRUE; case GF_RTP_PAYT_EVRC_SMV: strcpy(szMediaName, "audio"); - strcpy(szPayloadName, (rtpb->slMap.ObjectTypeIndication==0xA0) ? "EVRC" : "SMV"); + if ((rtpb->slMap.CodecID==0xA0) || (rtpb->slMap.CodecID==GF_CODECID_EVRC)) + strcpy(szPayloadName, "EVRC"); + else + strcpy(szPayloadName, "SMV"); + /*header-free version*/ if (rtpb->auh_size<=1) strcat(szPayloadName, "0"); return GF_TRUE; @@ -516,10 +525,12 @@ Bool gf_rtp_builder_get_payload_name(GP_RTPPacketizer *rtpb, char *szPayloadName strcpy(szMediaName, "audio"); strcpy(szPayloadName, "MP4A-LATM"); return GF_TRUE; +#if GPAC_ENABLE_3GPP_DIMS_RTP case GF_RTP_PAYT_3GPP_DIMS: strcpy(szMediaName, "video"); strcpy(szPayloadName, "richmedia+xml"); return GF_TRUE; +#endif case GF_RTP_PAYT_AC3: strcpy(szMediaName, "audio"); strcpy(szPayloadName, "ac3"); @@ -548,7 +559,7 @@ Bool gf_rtp_builder_get_payload_name(GP_RTPPacketizer *rtpb, char *szPayloadName GF_EXPORT GF_Err gf_rtp_builder_format_sdp(GP_RTPPacketizer *builder, char *payload_name, char *sdpLine, char *dsi, u32 dsi_size) { - char buffer[20000], dsiString[20000]; + char buffer[20100], dsiString[20000]; u32 i, k; Bool is_first = GF_TRUE; @@ -583,7 +594,7 @@ GF_Err gf_rtp_builder_format_sdp(GP_RTPPacketizer *builder, char *payload_name, } /*optional fields*/ - if (builder->slMap.ObjectTypeIndication) SDP_ADD_INT("objectType", builder->slMap.ObjectTypeIndication); + if (builder->slMap.CodecID) SDP_ADD_INT("objectType", builder->slMap.CodecID); if (builder->slMap.ConstantSize) SDP_ADD_INT("constantSize", builder->slMap.ConstantSize); if (builder->slMap.ConstantDuration) SDP_ADD_INT("constantDuration", builder->slMap.ConstantDuration); if (builder->slMap.maxDisplacement) SDP_ADD_INT("maxDisplacement", builder->slMap.maxDisplacement); diff --git a/src/ietf/rtp_pck_3gpp.c b/src/ietf/rtp_pck_3gpp.c index 8b2b539..7fab60d 100644 --- a/src/ietf/rtp_pck_3gpp.c +++ b/src/ietf/rtp_pck_3gpp.c @@ -31,7 +31,7 @@ static void rtp_amr_flush(GP_RTPPacketizer *builder) { - char *hdr; + u8 *hdr; u32 hdr_size; if (!builder->bytesInPacket) return; gf_bs_get_content(builder->pck_hdr, &hdr, &hdr_size); @@ -46,7 +46,7 @@ static void rtp_amr_flush(GP_RTPPacketizer *builder) builder->last_au_sn = 0; } -GF_Err gp_rtp_builder_do_amr(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) +GF_Err gp_rtp_builder_do_amr(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) { u32 offset, rtp_ts, block_size; @@ -128,7 +128,7 @@ static GFINLINE u8 qes_get_rate_size(u32 idx, const unsigned int *rates, const u return 0; } -GF_Err gp_rtp_builder_do_qcelp(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) +GF_Err gp_rtp_builder_do_qcelp(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) { u32 offset, rtp_ts; u8 hdr; @@ -190,12 +190,11 @@ GF_Err gp_rtp_builder_do_qcelp(GP_RTPPacketizer *builder, char *data, u32 data_s return GF_OK; } - static void rtp_evrc_smv_flush(GP_RTPPacketizer *builder) { if (!builder->bytesInPacket) return; if (builder->auh_size>1) { - char *hdr; + u8 *hdr; u32 hdr_size; /*padding*/ if (builder->last_au_sn % 2) gf_bs_write_int(builder->pck_hdr, 0, 4); @@ -213,7 +212,7 @@ static void rtp_evrc_smv_flush(GP_RTPPacketizer *builder) builder->last_au_sn = 0; } -GF_Err gp_rtp_builder_do_smv(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) +GF_Err gp_rtp_builder_do_smv(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) { u32 offset, rtp_ts; @@ -250,7 +249,7 @@ GF_Err gp_rtp_builder_do_smv(GP_RTPPacketizer *builder, char *data, u32 data_siz builder->pck_hdr = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); /*RRLLLNNN (all 0, no interleaving)*/ gf_bs_write_u8(builder->pck_hdr, 0); - /*MMM + count-1 : overriden when flushing*/ + /*MMM + count-1 : overridden when flushing*/ gf_bs_write_u8(builder->pck_hdr, 0); builder->bytesInPacket = 2; } @@ -282,10 +281,9 @@ GF_Err gp_rtp_builder_do_smv(GP_RTPPacketizer *builder, char *data, u32 data_siz return GF_OK; } -GF_Err gp_rtp_builder_do_h263(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) +GF_Err gp_rtp_builder_do_h263(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) { - GF_BitStream *bs; - char hdr[2]; + u8 hdr[2]; Bool Pbit; u32 offset, size, max_size; @@ -302,6 +300,7 @@ GF_Err gp_rtp_builder_do_h263(GP_RTPPacketizer *builder, char *data, u32 data_si max_size = builder->Path_MTU - 2; while(data_size > 0) { + GF_BitStream *bs; if(data_size > max_size) { size = max_size; builder->rtp_header.Marker = 0; @@ -338,10 +337,46 @@ GF_Err gp_rtp_builder_do_h263(GP_RTPPacketizer *builder, char *data, u32 data_si return GF_OK; } -GF_Err gp_rtp_builder_do_tx3g(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration, u8 descIndex) +GF_Err gp_rtp_builder_do_mp2t(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) +{ + u32 offset, size, max_size; + + builder->rtp_header.TimeStamp = (u32) builder->sl_header.compositionTimeStamp; + + if (!data) return GF_OK; + + max_size = builder->Path_MTU; + offset = 0; + while (data_size > 0) { + if (data_size > max_size) { + size = max_size / 188; + size *= 188; + } else { + size = data_size; + } + + data_size -= size; + + /*create new RTP Packet */ + builder->rtp_header.SequenceNumber += 1; + builder->OnNewPacket(builder->cbk_obj, &builder->rtp_header); + + /*add payload*/ + if (builder->OnDataReference) + builder->OnDataReference(builder->cbk_obj, size, offset); + else + builder->OnData(builder->cbk_obj, data + offset, size, GF_TRUE); + + builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header); + + offset += size; + } + return GF_OK; +} +GF_Err gp_rtp_builder_do_tx3g(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration, u8 descIndex) { GF_BitStream *bs; - char *hdr; + u8 *hdr; u32 samp_size, txt_size, pay_start, hdr_size, txt_done, cur_frag, nb_frag; Bool is_utf_16 = GF_FALSE; @@ -374,6 +409,8 @@ GF_Err gp_rtp_builder_do_tx3g(GP_RTPPacketizer *builder, char *data, u32 data_si builder->OnPacketDone(builder->cbk_obj, &builder->rtp_header); builder->bytesInPacket = 0; } + //we only deal with static SIDX + descIndex += GF_RTP_TX3G_SIDX_OFFSET; /*first TTU in packet*/ if (!builder->bytesInPacket) { @@ -544,6 +581,7 @@ GF_Err gp_rtp_builder_do_tx3g(GP_RTPPacketizer *builder, char *data, u32 data_si } +#if GPAC_ENABLE_3GPP_DIMS_RTP GF_Err gp_rtp_builder_do_dims(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration) { u32 frag_state; @@ -667,6 +705,7 @@ GF_Err gp_rtp_builder_do_dims(GP_RTPPacketizer *builder, char *data, u32 data_si gf_bs_del(bs); return GF_OK; } +#endif @@ -685,7 +724,7 @@ static void gf_rtp_ac3_flush(GP_RTPPacketizer *builder) builder->ac3_ft = 0; } -GF_Err gp_rtp_builder_do_ac3(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) +GF_Err gp_rtp_builder_do_ac3(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) { char hdr[2]; u32 offset, nb_pck; diff --git a/src/ietf/rtp_pck_mpeg12.c b/src/ietf/rtp_pck_mpeg12.c index e7d4345..c108522 100644 --- a/src/ietf/rtp_pck_mpeg12.c +++ b/src/ietf/rtp_pck_mpeg12.c @@ -32,7 +32,7 @@ static void mpa12_do_flush(GP_RTPPacketizer *builder, Bool start_new) { - char *tmp; + u8 *tmp; u32 tmp_size; /*flush*/ if (builder->pck_hdr) { @@ -65,7 +65,7 @@ static void mpa12_do_flush(GP_RTPPacketizer *builder, Bool start_new) builder->bytesInPacket = 0; } -GF_Err gp_rtp_builder_do_mpeg12_audio(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) +GF_Err gp_rtp_builder_do_mpeg12_audio(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) { u32 pck_size; u16 offset; @@ -114,12 +114,12 @@ GF_Err gp_rtp_builder_do_mpeg12_audio(GP_RTPPacketizer *builder, char *data, u32 #define MPEG12_PICTURE_START_CODE 0x00000100 #define MPEG12_SEQUENCE_START_CODE 0x000001b3 -GF_Err gp_rtp_builder_do_mpeg12_video(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) +GF_Err gp_rtp_builder_do_mpeg12_video(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) { u32 startcode, pic_type, max_pck_size, offset, prev_slice, next_slice; Bool start_with_slice, slices_done, got_slice, first_slice, have_seq; - char mpv_hdr[4]; - char *payload, *buffer; + u8 mpv_hdr[4]; + u8 *payload, *buffer; /*no flsuh (no aggregation)*/ if (!data) return GF_OK; @@ -152,7 +152,7 @@ GF_Err gp_rtp_builder_do_mpeg12_video(GP_RTPPacketizer *builder, char *data, u32 mpv_hdr[3] = 0; if ((pic_type==2) || (pic_type== 3)) { - mpv_hdr[3] = payload[3] << 5; + mpv_hdr[3] = (u8) ((((u32)payload[3]) << 5) & 0xf); if ((payload[4] & 0x80) != 0) mpv_hdr[3] |= 0x10; if (pic_type == 3) mpv_hdr[3] |= (payload[4] >> 3) & 0xf; } diff --git a/src/ietf/rtp_pck_mpeg4.c b/src/ietf/rtp_pck_mpeg4.c index a510714..c449fe2 100644 --- a/src/ietf/rtp_pck_mpeg4.c +++ b/src/ietf/rtp_pck_mpeg4.c @@ -202,9 +202,9 @@ u32 gf_rtp_build_au_hdr_write(GP_RTPPacketizer *builder, u32 PayloadSize, u32 RT } -GF_Err gp_rtp_builder_do_mpeg4(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) +GF_Err gp_rtp_builder_do_mpeg4(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize) { - char *sl_buffer, *payl_buffer; + u8 *sl_buffer, *payl_buffer; u32 sl_buffer_size, payl_buffer_size; u32 auh_size_tmp, bytesLeftInPacket, infoSize, pckSize; u64 pos; @@ -399,7 +399,7 @@ flush_packet: } -GF_Err gp_rtp_builder_do_avc(GP_RTPPacketizer *builder, char *nalu, u32 nalu_size, u8 IsAUEnd, u32 FullAUSize) +GF_Err gp_rtp_builder_do_avc(GP_RTPPacketizer *builder, u8 *nalu, u32 nalu_size, u8 IsAUEnd, u32 FullAUSize) { u32 do_flush, bytesLeft, size, nal_type; char shdr[2]; @@ -425,6 +425,7 @@ GF_Err gp_rtp_builder_do_avc(GP_RTPPacketizer *builder, char *nalu, u32 nalu_siz /*need a new RTP packet*/ if (!builder->bytesInPacket) { builder->rtp_header.PayloadType = builder->PayloadType; + builder->rtp_header.Marker = 0; builder->rtp_header.TimeStamp = (u32) builder->sl_header.compositionTimeStamp; builder->rtp_header.SequenceNumber += 1; builder->OnNewPacket(builder->cbk_obj, &builder->rtp_header); @@ -534,7 +535,7 @@ GF_Err gp_rtp_builder_do_avc(GP_RTPPacketizer *builder, char *nalu, u32 nalu_siz return GF_OK; } -GF_Err gp_rtp_builder_do_hevc(GP_RTPPacketizer *builder, char *nalu, u32 nalu_size, u8 IsAUEnd, u32 FullAUSize) +GF_Err gp_rtp_builder_do_hevc(GP_RTPPacketizer *builder, u8 *nalu, u32 nalu_size, u8 IsAUEnd, u32 FullAUSize) { u32 do_flush, bytesLeft, size; @@ -698,11 +699,10 @@ void latm_flush(GP_RTPPacketizer *builder) builder->rtp_header.TimeStamp = (u32) builder->sl_header.compositionTimeStamp; } -GF_Err gp_rtp_builder_do_latm(GP_RTPPacketizer *builder, char *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration) +GF_Err gp_rtp_builder_do_latm(GP_RTPPacketizer *builder, u8 *data, u32 data_size, u8 IsAUEnd, u32 FullAUSize, u32 duration) { u32 size, latm_hdr_size, i, data_offset; Bool fragmented; - unsigned char *latm_hdr; if (!data) { latm_flush(builder); @@ -722,6 +722,7 @@ GF_Err gp_rtp_builder_do_latm(GP_RTPPacketizer *builder, char *data, u32 data_si data_offset = 0; fragmented = GF_FALSE; while (data_size > 0) { + u8 *latm_hdr; latm_hdr_size = (data_size / 255) + 1; /*fragmenting*/ if (latm_hdr_size + data_size > builder->Path_MTU) { diff --git a/src/ietf/rtp_streamer.c b/src/ietf/rtp_streamer.c index 7f4ad04..65da91f 100644 --- a/src/ietf/rtp_streamer.c +++ b/src/ietf/rtp_streamer.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / IETF RTP/RTSP/SDP sub-project @@ -34,6 +34,9 @@ #if !defined(GPAC_DISABLE_STREAMING) && !defined(GPAC_DISABLE_ISOM) +/*for ISOBMFF subtypes*/ +#include + struct __rtp_streamer { GP_RTPPacketizer *packetizer; @@ -60,15 +63,19 @@ static void rtp_stream_on_packet_done(void *cbk, GF_RTPHeader *header) #ifndef GPAC_DISABLE_LOG if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("Error %s sending RTP packet\n", gf_error_to_string(e))); + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTP] Error %s sending RTP packet SN %u - TS %u\n", gf_error_to_string(e), header->SequenceNumber, header->TimeStamp)); } else { GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("RTP SN %u - TS %u - M %u - Size %u\n", header->SequenceNumber, header->TimeStamp, header->Marker, rtp->payload_len + 12)); } +#else + if (e) { + fprintf(stderr, "Error %s sending RTP packet SN %u - TS %u\n", gf_error_to_string(e), header->SequenceNumber, header->TimeStamp); + } #endif rtp->payload_len = 0; } -static void rtp_stream_on_data(void *cbk, char *data, u32 data_size, Bool is_head) +static void rtp_stream_on_data(void *cbk, u8 *data, u32 data_size, Bool is_head) { GF_RTPStreamer *rtp = (GF_RTPStreamer*)cbk; if (!data ||!data_size) return; @@ -87,6 +94,26 @@ static void rtp_stream_on_data(void *cbk, char *data, u32 data_size, Bool is_hea rtp->payload_len += data_size; } + +GF_Err gf_rtp_streamer_init_rtsp(GF_RTPStreamer *rtp, u32 path_mtu, GF_RTSPTransport *tr, const char *ifce_addr) +{ + GF_Err res; + + if (!rtp->channel) rtp->channel = gf_rtp_new(); + + res = gf_rtp_setup_transport(rtp->channel, tr, tr->destination); + if (res !=0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("Cannot setup RTP transport info: %s\n", gf_error_to_string(res) )); + return res; + } + + res = gf_rtp_initialize(rtp->channel, 0, GF_TRUE, path_mtu, 0, 0, (char *)ifce_addr); + if (res !=0) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("Cannot initialize RTP sockets: %s\n", gf_error_to_string(res) )); + return res; + } + return GF_OK; +} static GF_Err rtp_stream_init_channel(GF_RTPStreamer *rtp, u32 path_mtu, const char * dest, int port, int ttl, const char *ifce_addr) { GF_RTSPTransport tr; @@ -129,15 +156,13 @@ static GF_Err rtp_stream_init_channel(GF_RTPStreamer *rtp, u32 path_mtu, const c } GF_EXPORT -GF_RTPStreamer *gf_rtp_streamer_new_extended(u32 streamType, u32 oti, u32 timeScale, +GF_RTPStreamer *gf_rtp_streamer_new(u32 streamType, u32 codecid, u32 timeScale, const char *ip_dest, u16 port, u32 MTU, u8 TTL, const char *ifce_addr, - u32 flags, char *dsi, u32 dsi_len, - + u32 flags, const u8 *dsi, u32 dsi_len, u32 PayloadType, u32 sample_rate, u32 nb_ch, Bool is_crypted, u32 IV_length, u32 KI_length, u32 MinSize, u32 MaxSize, u32 avgTS, u32 maxDTSDelta, u32 const_dur, u32 bandwidth, u32 max_ptime, - u32 au_sn_len - ) + u32 au_sn_len, Bool for_rtsp) { GF_SLConfig slc; GF_RTPStreamer *stream; @@ -168,244 +193,212 @@ GF_RTPStreamer *gf_rtp_streamer_new_extended(u32 streamType, u32 oti, u32 timeSc /*timed-text is a bit special, we support multiple stream descriptions & co*/ switch (streamType) { - case GF_STREAM_TEXT: - if (oti!=GPAC_OTI_TEXT_MPEG4) - return NULL; - - rtp_type = GF_RTP_PAYT_3GPP_TEXT; - /*fixme - this works cos there's only one PL for text in mpeg4 at the current time*/ - PL_ID = 0x10; - break; case GF_STREAM_AUDIO: required_rate = sample_rate; - switch (oti) { - /*AAC*/ - case GPAC_OTI_AUDIO_AAC_MPEG4: - case GPAC_OTI_AUDIO_AAC_MPEG2_MP: - case GPAC_OTI_AUDIO_AAC_MPEG2_LCP: - case GPAC_OTI_AUDIO_AAC_MPEG2_SSRP: - PL_ID = 0x01; - mpeg4mode = "AAC"; - rtp_type = GF_RTP_PAYT_MPEG4; - required_rate = sample_rate; - -#ifndef GPAC_DISABLE_AV_PARSERS - if (dsi) { - GF_M4ADecSpecInfo a_cfg; - gf_m4a_get_config(dsi, dsi_len, &a_cfg); - //nb_ch = a_cfg.nb_chan; - //sample_rate = a_cfg.base_sr; - PL_ID = a_cfg.audioPL; - switch (a_cfg.base_object_type) { - case GF_M4A_AAC_MAIN: - case GF_M4A_AAC_LC: - if (flags & GP_RTP_PCK_USE_LATM_AAC) { - rtp_type = GF_RTP_PAYT_LATM; - break; - } - case GF_M4A_AAC_SBR: - case GF_M4A_AAC_PS: - case GF_M4A_AAC_LTP: - case GF_M4A_AAC_SCALABLE: - case GF_M4A_ER_AAC_LC: - case GF_M4A_ER_AAC_LTP: - case GF_M4A_ER_AAC_SCALABLE: - mpeg4mode = "AAC"; - break; - case GF_M4A_CELP: - case GF_M4A_ER_CELP: - mpeg4mode = "CELP"; - break; - } - } -#endif - break; - - /*MPEG1/2 audio*/ - case GPAC_OTI_AUDIO_MPEG2_PART3: - case GPAC_OTI_AUDIO_MPEG1: - if (!is_crypted) { - rtp_type = GF_RTP_PAYT_MPEG12_AUDIO; - /*use official RTP/AVP payload type*/ - OfficialPayloadType = 14; - required_rate = 90000; - } - /*encrypted MP3 must be sent through MPEG-4 generic to signal all ISMACryp stuff*/ - else { - rtp_type = GF_RTP_PAYT_MPEG4; - } - break; - - /*QCELP audio*/ - case GPAC_OTI_AUDIO_13K_VOICE: - rtp_type = GF_RTP_PAYT_QCELP; - OfficialPayloadType = 12; - required_rate = 8000; - //nb_ch = 1; - break; - - /*EVRC/SVM audio*/ - case GPAC_OTI_AUDIO_EVRC_VOICE: - case GPAC_OTI_AUDIO_SMV_VOICE: - rtp_type = GF_RTP_PAYT_EVRC_SMV; - required_rate = 8000; - //nb_ch = 1; - } - break; - case GF_STREAM_VISUAL: rtp_type = GF_RTP_PAYT_MPEG4; required_rate = default_rtp_rate; if (is_crypted) { /*that's another pain with ISMACryp, even if no B-frames the DTS is signaled...*/ - if (oti==GPAC_OTI_VIDEO_MPEG4_PART2) force_dts_delta = 22; + if (codecid==GF_CODECID_MPEG4_PART2) force_dts_delta = 22; flags |= GP_RTP_PCK_SIGNAL_RAP | GP_RTP_PCK_SIGNAL_TS; } - - switch (oti) { - /*ISO/IEC 14496-2*/ - case GPAC_OTI_VIDEO_MPEG4_PART2: - PL_ID = 1; -#ifndef GPAC_DISABLE_AV_PARSERS - if (dsi) { - GF_M4VDecSpecInfo vhdr; - gf_m4v_get_config(dsi, dsi_len, &vhdr); - PL_ID = vhdr.VideoPL; - } -#endif - break; - - /*MPEG1/2 video*/ - case GPAC_OTI_VIDEO_MPEG1: - case GPAC_OTI_VIDEO_MPEG2_SIMPLE: - case GPAC_OTI_VIDEO_MPEG2_MAIN: - case GPAC_OTI_VIDEO_MPEG2_SNR: - case GPAC_OTI_VIDEO_MPEG2_SPATIAL: - case GPAC_OTI_VIDEO_MPEG2_HIGH: - case GPAC_OTI_VIDEO_MPEG2_422: - if (!is_crypted) { - rtp_type = GF_RTP_PAYT_MPEG12_VIDEO; - OfficialPayloadType = 32; - } - break; - /*AVC/H.264*/ - case GPAC_OTI_VIDEO_AVC: - required_rate = 90000; /* "90 kHz clock rate MUST be used"*/ - rtp_type = GF_RTP_PAYT_H264_AVC; - PL_ID = 0x0F; - break; - /*H264-SVC*/ - case GPAC_OTI_VIDEO_SVC: - case GPAC_OTI_VIDEO_MVC: - required_rate = 90000; /* "90 kHz clock rate MUST be used"*/ - rtp_type = GF_RTP_PAYT_H264_SVC; - PL_ID = 0x0F; - break; - /*HEVC*/ - case GPAC_OTI_VIDEO_HEVC: - required_rate = 90000; /* "90 kHz clock rate MUST be used"*/ - rtp_type = GF_RTP_PAYT_HEVC; - PL_ID = 0x0F; - break; - /*LHVC*/ - case GPAC_OTI_VIDEO_LHVC: - required_rate = 90000; /* "90 kHz clock rate MUST be used"*/ - rtp_type = GF_RTP_PAYT_LHVC; - PL_ID = 0x0F; - break; - } break; - case GF_STREAM_SCENE: case GF_STREAM_OD: - if (oti == GPAC_OTI_SCENE_DIMS) { + if (codecid == GF_CODECID_DIMS) { +#if GPAC_ENABLE_3GPP_DIMS_RTP rtp_type = GF_RTP_PAYT_3GPP_DIMS; has_mpeg4_mapping = GF_FALSE; +#else + gf_free(stream); + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTP Packetizer] 3GPP DIMS over RTP disabled in build\n", streamType)); + return NULL; +#endif } else { rtp_type = GF_RTP_PAYT_MPEG4; } break; + } + switch (codecid) { + /*AAC*/ + case GF_CODECID_AAC_MPEG4: + case GF_CODECID_AAC_MPEG2_MP: + case GF_CODECID_AAC_MPEG2_LCP: + case GF_CODECID_AAC_MPEG2_SSRP: + PL_ID = 0x01; + mpeg4mode = "AAC"; + rtp_type = GF_RTP_PAYT_MPEG4; + required_rate = sample_rate; - case GF_STREAM_4CC: - switch (oti) { - case GF_ISOM_SUBTYPE_3GP_H263: - rtp_type = GF_RTP_PAYT_H263; - required_rate = 90000; - streamType = GF_STREAM_VISUAL; - OfficialPayloadType = 34; - /*not 100% compliant (short header is missing) but should still work*/ - oti = GPAC_OTI_VIDEO_MPEG4_PART2; - PL_ID = 0x01; - break; - case GF_ISOM_SUBTYPE_3GP_AMR: - required_rate = 8000; - rtp_type = GF_RTP_PAYT_AMR; - streamType = GF_STREAM_AUDIO; - has_mpeg4_mapping = GF_FALSE; -// nb_ch = 1; - break; - case GF_ISOM_SUBTYPE_3GP_AMR_WB: - required_rate = 16000; - rtp_type = GF_RTP_PAYT_AMR_WB; - streamType = GF_STREAM_AUDIO; - has_mpeg4_mapping = GF_FALSE; -// nb_ch = 1; - break; - case GF_ISOM_SUBTYPE_AC3: - rtp_type = GF_RTP_PAYT_AC3; - streamType = GF_STREAM_AUDIO; - has_mpeg4_mapping = GF_TRUE; -// nb_ch = 1; - break; - case GF_ISOM_SUBTYPE_AVC_H264: - case GF_ISOM_SUBTYPE_AVC2_H264: - case GF_ISOM_SUBTYPE_AVC3_H264: - case GF_ISOM_SUBTYPE_AVC4_H264: - case GF_ISOM_SUBTYPE_SVC_H264: - case GF_ISOM_SUBTYPE_MVC_H264: - { - required_rate = 90000; /* "90 kHz clock rate MUST be used"*/ - rtp_type = GF_RTP_PAYT_H264_AVC; - streamType = GF_STREAM_VISUAL; - oti = GPAC_OTI_VIDEO_AVC; - PL_ID = 0x0F; +#ifndef GPAC_DISABLE_AV_PARSERS + if (dsi) { + GF_M4ADecSpecInfo a_cfg; + gf_m4a_get_config((u8 *)dsi, dsi_len, &a_cfg); + //nb_ch = a_cfg.nb_chan; + //sample_rate = a_cfg.base_sr; + PL_ID = a_cfg.audioPL; + switch (a_cfg.base_object_type) { + case GF_M4A_AAC_MAIN: + case GF_M4A_AAC_LC: + if (flags & GP_RTP_PCK_USE_LATM_AAC) { + rtp_type = GF_RTP_PAYT_LATM; + break; + } + case GF_M4A_AAC_SBR: + case GF_M4A_AAC_PS: + case GF_M4A_AAC_LTP: + case GF_M4A_AAC_SCALABLE: + case GF_M4A_ER_AAC_LC: + case GF_M4A_ER_AAC_LTP: + case GF_M4A_ER_AAC_SCALABLE: + mpeg4mode = "AAC"; + break; + case GF_M4A_CELP: + case GF_M4A_ER_CELP: + mpeg4mode = "CELP"; + break; + } } +#endif break; - case GF_ISOM_SUBTYPE_3GP_QCELP: - required_rate = 8000; - rtp_type = GF_RTP_PAYT_QCELP; - streamType = GF_STREAM_AUDIO; - oti = GPAC_OTI_AUDIO_13K_VOICE; - OfficialPayloadType = 12; -// nb_ch = 1; - break; - case GF_ISOM_SUBTYPE_3GP_EVRC: - case GF_ISOM_SUBTYPE_3GP_SMV: - required_rate = 8000; - rtp_type = GF_RTP_PAYT_EVRC_SMV; - streamType = GF_STREAM_AUDIO; - oti = (oti==GF_ISOM_SUBTYPE_3GP_EVRC) ? GPAC_OTI_AUDIO_EVRC_VOICE : GPAC_OTI_AUDIO_SMV_VOICE; -// nb_ch = 1; - break; - case GF_ISOM_SUBTYPE_MP3: + + /*MPEG1/2 audio*/ + case GF_CODECID_MPEG2_PART3: + case GF_CODECID_MPEG_AUDIO: + if (!is_crypted) { rtp_type = GF_RTP_PAYT_MPEG12_AUDIO; /*use official RTP/AVP payload type*/ OfficialPayloadType = 14; required_rate = 90000; - break; } + /*encrypted MP3 must be sent through MPEG-4 generic to signal all ISMACryp stuff*/ + else { + rtp_type = GF_RTP_PAYT_MPEG4; + } + break; + + /*ISO/IEC 14496-2*/ + case GF_CODECID_MPEG4_PART2: + PL_ID = 1; +#ifndef GPAC_DISABLE_AV_PARSERS + if (dsi) { + GF_M4VDecSpecInfo vhdr; + gf_m4v_get_config((u8 *)dsi, dsi_len, &vhdr); + PL_ID = vhdr.VideoPL; + } +#endif + break; + + /*MPEG1/2 video*/ + case GF_CODECID_MPEG1: + case GF_CODECID_MPEG2_SIMPLE: + case GF_CODECID_MPEG2_MAIN: + case GF_CODECID_MPEG2_SNR: + case GF_CODECID_MPEG2_SPATIAL: + case GF_CODECID_MPEG2_HIGH: + case GF_CODECID_MPEG2_422: + if (!is_crypted) { + rtp_type = GF_RTP_PAYT_MPEG12_VIDEO; + OfficialPayloadType = 32; + } + break; + /*AVC/H.264*/ + case GF_CODECID_AVC: + required_rate = 90000; /* "90 kHz clock rate MUST be used"*/ + rtp_type = GF_RTP_PAYT_H264_AVC; + PL_ID = 0x0F; + break; + /*H264-SVC*/ + case GF_CODECID_SVC: + case GF_CODECID_MVC: + required_rate = 90000; /* "90 kHz clock rate MUST be used"*/ + rtp_type = GF_RTP_PAYT_H264_SVC; + PL_ID = 0x0F; + break; + + /*HEVC*/ + case GF_CODECID_HEVC: + required_rate = 90000; /* "90 kHz clock rate MUST be used"*/ + rtp_type = GF_RTP_PAYT_HEVC; + PL_ID = 0x0F; + break; + /*LHVC*/ + case GF_CODECID_LHVC: + required_rate = 90000; /* "90 kHz clock rate MUST be used"*/ + rtp_type = GF_RTP_PAYT_LHVC; + PL_ID = 0x0F; + break; + + case GF_CODECID_H263: + rtp_type = GF_RTP_PAYT_H263; + required_rate = 90000; + streamType = GF_STREAM_VISUAL; + OfficialPayloadType = 34; + /*not 100% compliant (short header is missing) but should still work*/ + codecid = GF_CODECID_MPEG4_PART2; + PL_ID = 0x01; + break; + case GF_CODECID_AMR: + required_rate = 8000; + rtp_type = GF_RTP_PAYT_AMR; + streamType = GF_STREAM_AUDIO; + has_mpeg4_mapping = GF_FALSE; + break; + case GF_CODECID_AMR_WB: + required_rate = 16000; + rtp_type = GF_RTP_PAYT_AMR_WB; + streamType = GF_STREAM_AUDIO; + has_mpeg4_mapping = GF_FALSE; + break; + case GF_CODECID_AC3: + rtp_type = GF_RTP_PAYT_AC3; + streamType = GF_STREAM_AUDIO; + has_mpeg4_mapping = GF_TRUE; + break; + + case GF_CODECID_QCELP: + required_rate = 8000; + rtp_type = GF_RTP_PAYT_QCELP; + streamType = GF_STREAM_AUDIO; + codecid = GF_CODECID_QCELP; + OfficialPayloadType = 12; +// nb_ch = 1; + break; + case GF_CODECID_EVRC: + case GF_CODECID_SMV: + required_rate = 8000; + rtp_type = GF_RTP_PAYT_EVRC_SMV; + streamType = GF_STREAM_AUDIO; + codecid = (codecid==GF_ISOM_SUBTYPE_3GP_EVRC) ? GF_CODECID_EVRC : GF_CODECID_SMV; +// nb_ch = 1; + break; + case GF_CODECID_TX3G: + rtp_type = GF_RTP_PAYT_3GPP_TEXT; + /*fixme - this works cos there's only one PL for text in mpeg4 at the current time*/ + PL_ID = 0x10; + break; + case GF_CODECID_TEXT_MPEG4: + rtp_type = GF_RTP_PAYT_3GPP_TEXT; + /*fixme - this works cos there's only one PL for text in mpeg4 at the current time*/ + PL_ID = 0x10; + break; + case GF_CODECID_FAKE_MP2T: + rtp_type = GF_RTP_PAYT_MP2T; + PayloadType = OfficialPayloadType = GF_RTP_PAYT_MP2T; + required_rate = 90000; break; default: - GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTP Packetizer] Unsupported stream type %x\n", streamType)); - return NULL; + if (!rtp_type) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTP Packetizer] Unsupported stream type %x\n", streamType)); + return NULL; + } + break; } - /*not supported*/ - if (!rtp_type) return NULL; - /*override hinter type if requested and possible*/ if (has_mpeg4_mapping && (flags & GP_RTP_PCK_FORCE_MPEG4)) { rtp_type = GF_RTP_PAYT_MPEG4; @@ -464,18 +457,21 @@ GF_RTPStreamer *gf_rtp_streamer_new_extended(u32 streamType, u32 oti, u32 timeSc return NULL; } - gf_rtp_builder_init(stream->packetizer, PayloadType, MTU, max_ptime, - streamType, oti, PL_ID, MinSize, MaxSize, avgTS, maxDTSDelta, IV_length, KI_length, mpeg4mode); + gf_rtp_builder_init(stream->packetizer, (u8) PayloadType, MTU, max_ptime, + streamType, codecid, PL_ID, MinSize, MaxSize, avgTS, maxDTSDelta, IV_length, KI_length, mpeg4mode); if (force_dts_delta) stream->packetizer->slMap.DTSDeltaLength = force_dts_delta; - e = rtp_stream_init_channel(stream, MTU + 12, ip_dest, port, TTL, ifce_addr); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTP Packetizer] Failed to create RTP channel - error %s\n", gf_error_to_string(e) )); - gf_free(stream); - return NULL; + if (!for_rtsp) { + e = rtp_stream_init_channel(stream, MTU + 12, ip_dest, port, TTL, ifce_addr); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTP Packetizer] Failed to create RTP channel - error %s\n", gf_error_to_string(e) )); + gf_free(stream); + return NULL; + } } + stream->ts_scale = slc.timestampResolution; stream->ts_scale /= timeScale; @@ -486,17 +482,6 @@ GF_RTPStreamer *gf_rtp_streamer_new_extended(u32 streamType, u32 oti, u32 timeSc } -GF_EXPORT -GF_RTPStreamer *gf_rtp_streamer_new(u32 streamType, u32 oti, u32 timeScale, - const char *ip_dest, u16 port, u32 MTU, u8 TTL, const char *ifce_addr, - u32 flags, char *dsi, u32 dsi_len) -{ - return gf_rtp_streamer_new_extended(streamType, oti, timeScale, ip_dest, port, MTU, TTL, ifce_addr, flags, dsi, dsi_len, - - 96, 0, 0, GF_FALSE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - -} - GF_EXPORT void gf_rtp_streamer_del(GF_RTPStreamer *streamer) { @@ -510,43 +495,20 @@ void gf_rtp_streamer_del(GF_RTPStreamer *streamer) #if !defined(GPAC_DISABLE_ISOM) && !defined(GPAC_DISABLE_STREAMING) -void gf_media_format_ttxt_sdp(GP_RTPPacketizer *builder, char *payload_name, char *sdpLine, GF_ISOFile *file, u32 track) +void gf_media_format_ttxt_sdp(GP_RTPPacketizer *builder, char *payload_name, char *sdpLine, u32 w, u32 h, s32 tx, s32 ty, s16 l, u32 max_w, u32 max_h, char *tx3g_base64) { char buffer[2000]; - u32 w, h, i, m_w, m_h; - s32 tx, ty; - s16 l; sprintf(sdpLine, "a=fmtp:%d sver=60; ", builder->PayloadType); - gf_isom_get_track_layout_info(file, track, &w, &h, &tx, &ty, &l); + sprintf(buffer, "width=%d; height=%d; tx=%d; ty=%d; layer=%d; ", w, h, tx, ty, l); strcat(sdpLine, buffer); - m_w = w; - m_h = h; - for (i=0; im_w) m_w = w; - if (h>m_h) m_h = h; - break; - default: - break; - } - } - sprintf(buffer, "max-w=%d; max-h=%d", m_w, m_h); + + sprintf(buffer, "max-w=%d; max-h=%d", max_w, max_h); strcat(sdpLine, buffer); - strcat(sdpLine, "; tx3g="); - for (i=0; ipacketizer->slMap.IV_length ? "SAVP" : "AVP", rtp->packetizer->PayloadType); + sprintf(sdp, "m=%s %d RTP/%s %d\n", mediaName, for_rtsp ? 0 : port, rtp->packetizer->slMap.IV_length ? "SAVP" : "AVP", rtp->packetizer->PayloadType); sprintf(sdpLine, "a=rtpmap:%d %s/%d\n", rtp->packetizer->PayloadType, payloadName, rtp->packetizer->sl_config.timestampResolution); strcat(sdp, sdpLine); - if (ESID && (rtp->packetizer->rtp_payt != GF_RTP_PAYT_3GPP_DIMS)) { + + if (ESID +#if GPAC_ENABLE_3GPP_DIMS_RTP + && (rtp->packetizer->rtp_payt != GF_RTP_PAYT_3GPP_DIMS) +#endif + ) { sprintf(sdpLine, "a=mpeg4-esid:%d\n", ESID); strcat(sdp, sdpLine); } @@ -592,7 +560,7 @@ GF_Err gf_rtp_streamer_append_sdp_extended(GF_RTPStreamer *rtp, u16 ESID, char * } /*Text*/ else if (rtp->packetizer->rtp_payt == GF_RTP_PAYT_3GPP_TEXT) { - gf_media_format_ttxt_sdp(rtp->packetizer, payloadName, sdpLine, isofile, isotrack); + gf_media_format_ttxt_sdp(rtp->packetizer, payloadName, sdpLine, tw, th, tx, ty, tl, width, height, (u8 *)dsi_enh); strcat(sdpLine, "\n"); } /*EVRC/SMV in non header-free mode*/ @@ -601,7 +569,7 @@ GF_Err gf_rtp_streamer_append_sdp_extended(GF_RTPStreamer *rtp, u16 ESID, char * } /*H264/AVC*/ else if ((rtp->packetizer->rtp_payt == GF_RTP_PAYT_H264_AVC) || (rtp->packetizer->rtp_payt == GF_RTP_PAYT_H264_SVC)) { - GF_AVCConfig *avcc = dsi ? gf_odf_avc_cfg_read(dsi, dsi_len) : NULL; + GF_AVCConfig *avcc = dsi ? gf_odf_avc_cfg_read((u8*)dsi, dsi_len) : NULL; if (avcc) { sprintf(sdpLine, "a=fmtp:%d profile-level-id=%02X%02X%02X; packetization-mode=1", rtp->packetizer->PayloadType, avcc->AVCProfileIndication, avcc->profile_compatibility, avcc->AVCLevelIndication); @@ -633,7 +601,7 @@ GF_Err gf_rtp_streamer_append_sdp_extended(GF_RTPStreamer *rtp, u16 ESID, char * } else if ((rtp->packetizer->rtp_payt == GF_RTP_PAYT_HEVC) || (rtp->packetizer->rtp_payt == GF_RTP_PAYT_LHVC)) { #ifndef GPAC_DISABLE_HEVC - GF_HEVCConfig *hevcc = dsi ? gf_odf_hevc_cfg_read(dsi, dsi_len, GF_FALSE) : NULL; + GF_HEVCConfig *hevcc = dsi ? gf_odf_hevc_cfg_read((u8*)dsi, dsi_len, GF_FALSE) : NULL; if (hevcc) { u32 count, i, j, b64s; char b64[200]; @@ -663,7 +631,7 @@ GF_Err gf_rtp_streamer_append_sdp_extended(GF_RTPStreamer *rtp, u16 ESID, char * } /*MPEG-4 decoder config*/ else if (rtp->packetizer->rtp_payt==GF_RTP_PAYT_MPEG4) { - gf_rtp_builder_format_sdp(rtp->packetizer, payloadName, sdpLine, dsi, dsi_len); + gf_rtp_builder_format_sdp(rtp->packetizer, payloadName, sdpLine, (u8*)dsi, dsi_len); strcat(sdpLine, "\n"); if (rtp->packetizer->slMap.IV_length && KMS_URI) { @@ -676,6 +644,7 @@ GF_Err gf_rtp_streamer_append_sdp_extended(GF_RTPStreamer *rtp, u16 ESID, char * strcat(sdpLine, "\n"); } } +#if GPAC_ENABLE_3GPP_DIMS_RTP /*DIMS decoder config*/ else if (rtp->packetizer->rtp_payt==GF_RTP_PAYT_3GPP_DIMS) { sprintf(sdpLine, "a=fmtp:%d Version-profile=%d", rtp->packetizer->PayloadType, 10); @@ -684,10 +653,11 @@ GF_Err gf_rtp_streamer_append_sdp_extended(GF_RTPStreamer *rtp, u16 ESID, char * } strcat(sdpLine, "\n"); } +#endif /*MPEG-4 Audio LATM*/ else if (rtp->packetizer->rtp_payt==GF_RTP_PAYT_LATM) { GF_BitStream *bs; - char *config_bytes; + u8 *config_bytes; u32 config_size; /* form config string */ @@ -729,98 +699,65 @@ GF_Err gf_rtp_streamer_append_sdp_extended(GF_RTPStreamer *rtp, u16 ESID, char * return GF_OK; } -GF_EXPORT -GF_Err gf_rtp_streamer_append_sdp_decoding_dependency(GF_ISOFile *isofile, u32 isotrack, u8 *payload_type, char **out_sdp_buffer) -{ - u32 size, i, ref_track; - s32 count; - char sdp[20000], sdpLine[10000]; - - sprintf(sdp, "a=mid:L%d\n", isotrack); - - count = gf_isom_get_reference_count(isofile, isotrack, GF_ISOM_REF_SCAL); - if (count > 0) - { - sprintf(sdpLine, "a=depend:%d lay", payload_type[isotrack-1]); - strcat(sdp, sdpLine); - for (i = 0; i < (u32) count; i++) - { - gf_isom_get_reference(isofile, isotrack, GF_ISOM_REF_SCAL, i+1, &ref_track); - sprintf(sdpLine, " L%d:%d", ref_track, payload_type[ref_track-1]); - strcat(sdp, sdpLine); - } - strcat(sdp, "\n"); - } - size = (u32) strlen(sdp) + (*out_sdp_buffer ? (u32) strlen(*out_sdp_buffer) : 0) + 1; - if ( !*out_sdp_buffer) { - *out_sdp_buffer = (char*)gf_malloc(sizeof(char)*size); - if (! *out_sdp_buffer) return GF_OUT_OF_MEM; - strcpy(*out_sdp_buffer, sdp); - } else { - *out_sdp_buffer = (char*)gf_realloc(*out_sdp_buffer, sizeof(char)*size); - if (! *out_sdp_buffer) return GF_OUT_OF_MEM; - strcat(*out_sdp_buffer, sdp); - } - return GF_OK; -} GF_EXPORT char *gf_rtp_streamer_format_sdp_header(char *app_name, char *ip_dest, char *session_name, char *iod64) { u64 size; char *sdp, *tmp_fn = NULL; - FILE *tmp = gf_temp_file_new(&tmp_fn); + FILE *tmp = gf_file_temp(&tmp_fn); if (!tmp) return NULL; /* write SDP header*/ - fprintf(tmp, "v=0\n"); - fprintf(tmp, "o=%s 3326096807 1117107880000 IN IP%d %s\n", app_name, gf_net_is_ipv6(ip_dest) ? 6 : 4, ip_dest); - fprintf(tmp, "s=%s\n", (session_name ? session_name : "GPAC Scene Streaming Session")); - fprintf(tmp, "c=IN IP%d %s\n", gf_net_is_ipv6(ip_dest) ? 6 : 4, ip_dest); - fprintf(tmp, "t=0 0\n"); + gf_fprintf(tmp, "v=0\n"); + gf_fprintf(tmp, "o=%s 3326096807 1117107880000 IN IP%d %s\n", app_name, gf_net_is_ipv6(ip_dest) ? 6 : 4, ip_dest); + gf_fprintf(tmp, "s=%s\n", (session_name ? session_name : "GPAC Scene Streaming Session")); + gf_fprintf(tmp, "c=IN IP%d %s\n", gf_net_is_ipv6(ip_dest) ? 6 : 4, ip_dest); + gf_fprintf(tmp, "t=0 0\n"); - if (iod64) fprintf(tmp, "a=mpeg4-iod:\"data:application/mpeg4-iod;base64,%s\"\n", iod64); + if (iod64) + gf_fprintf(tmp, "a=mpeg4-iod:\"data:application/mpeg4-iod;base64,%s\"\n", iod64); - gf_fseek(tmp, 0, SEEK_END); - size = gf_ftell(tmp); - gf_fseek(tmp, 0, SEEK_SET); + size = gf_fsize(tmp); sdp = (char*)gf_malloc(sizeof(char) * (size_t)(size+1)); - size = fread(sdp, 1, (size_t)size, tmp); + size = gf_fread(sdp, (size_t)size, tmp); sdp[size] = 0; gf_fclose(tmp); - gf_delete_file(tmp_fn); + gf_file_delete(tmp_fn); gf_free(tmp_fn); return sdp; } GF_EXPORT -GF_Err gf_rtp_streamer_append_sdp(GF_RTPStreamer *rtp, u16 ESID, char *dsi, u32 dsi_len, char *KMS_URI, char **out_sdp_buffer) +GF_Err gf_rtp_streamer_append_sdp(GF_RTPStreamer *rtp, u16 ESID, const u8 *dsi, u32 dsi_len, char *KMS_URI, char **out_sdp_buffer) { - return gf_rtp_streamer_append_sdp_extended(rtp, ESID, dsi, dsi_len, NULL, 0, KMS_URI, 0, 0, out_sdp_buffer); + return gf_rtp_streamer_append_sdp_extended(rtp, ESID, dsi, dsi_len, NULL, 0, KMS_URI, 0, 0, 0, 0, 0, 0, 0, GF_FALSE, out_sdp_buffer); } -GF_Err gf_rtp_streamer_send_data(GF_RTPStreamer *rtp, char *data, u32 size, u32 fullsize, u64 cts, u64 dts, Bool is_rap, Bool au_start, Bool au_end, u32 au_sn, u32 sampleDuration, u32 sampleDescIndex) +GF_EXPORT +GF_Err gf_rtp_streamer_send_data(GF_RTPStreamer *rtp, u8 *data, u32 size, u32 fullsize, u64 cts, u64 dts, Bool is_rap, Bool au_start, Bool au_end, u32 au_sn, u32 sampleDuration, u32 sampleDescIndex) { rtp->packetizer->sl_header.compositionTimeStamp = (u64) (cts*rtp->ts_scale); rtp->packetizer->sl_header.decodingTimeStamp = (u64) (dts*rtp->ts_scale); rtp->packetizer->sl_header.randomAccessPointFlag = is_rap; rtp->packetizer->sl_header.accessUnitStartFlag = au_start; rtp->packetizer->sl_header.accessUnitEndFlag = au_end; - rtp->packetizer->sl_header.randomAccessPointFlag = is_rap; rtp->packetizer->sl_header.AU_sequenceNumber = au_sn; sampleDuration = (u32) (sampleDuration * rtp->ts_scale); + if (au_start && size) rtp->packetizer->nb_aus++; return gf_rtp_builder_process(rtp->packetizer, data, size, (u8) au_end, fullsize, sampleDuration, sampleDescIndex); } -GF_Err gf_rtp_streamer_send_au(GF_RTPStreamer *rtp, char *data, u32 size, u64 cts, u64 dts, Bool is_rap) +GF_EXPORT +GF_Err gf_rtp_streamer_send_au(GF_RTPStreamer *rtp, u8 *data, u32 size, u64 cts, u64 dts, Bool is_rap) { return gf_rtp_streamer_send_data(rtp, data, size, size, cts, dts, is_rap, GF_TRUE, GF_TRUE, 0, 0, 0); } GF_EXPORT -GF_Err gf_rtp_streamer_send_au_with_sn(GF_RTPStreamer *rtp, char *data, u32 size, u64 cts, u64 dts, Bool is_rap, u32 inc_au_sn) +GF_Err gf_rtp_streamer_send_au_with_sn(GF_RTPStreamer *rtp, u8 *data, u32 size, u64 cts, u64 dts, Bool is_rap, u32 inc_au_sn) { if (inc_au_sn) rtp->packetizer->sl_header.AU_sequenceNumber += inc_au_sn; return gf_rtp_streamer_send_data(rtp, data, size, size, cts, dts, is_rap, GF_TRUE, GF_TRUE, rtp->packetizer->sl_header.AU_sequenceNumber, 0, 0); @@ -840,13 +777,32 @@ GF_Err gf_rtp_streamer_send_rtcp(GF_RTPStreamer *streamer, Bool force_ts, u32 rt streamer->channel->forced_ntp_frac = force_ntp_type ? ntp_frac : 0; if (force_ntp_type==2) streamer->channel->next_report_time = 0; - return gf_rtp_send_rtcp_report(streamer->channel, NULL, NULL); + return gf_rtp_send_rtcp_report(streamer->channel); +} + +GF_EXPORT +GF_Err gf_rtp_streamer_send_bye(GF_RTPStreamer *streamer) +{ + return gf_rtp_send_bye(streamer->channel); } GF_EXPORT u8 gf_rtp_streamer_get_payload_type(GF_RTPStreamer *streamer) { - return streamer->packetizer->PayloadType; + return streamer ? streamer->packetizer->PayloadType : 0; +} + +GF_EXPORT +u16 gf_rtp_streamer_get_next_rtp_sn(GF_RTPStreamer *streamer) +{ + return streamer->packetizer->rtp_header.SequenceNumber+1; +} + +GF_EXPORT +GF_Err gf_rtp_streamer_set_interleave_callbacks(GF_RTPStreamer *streamer, gf_rtp_tcp_callback RTP_TCPCallback, void *cbk1, void *cbk2) +{ + + return gf_rtp_set_interleave_callbacks(streamer->channel, RTP_TCPCallback, cbk1, cbk2); } #endif /*GPAC_DISABLE_STREAMING && GPAC_DISABLE_ISOM*/ diff --git a/src/ietf/rtsp_command.c b/src/ietf/rtsp_command.c index 5e6fb38..745181e 100644 --- a/src/ietf/rtsp_command.c +++ b/src/ietf/rtsp_command.c @@ -48,8 +48,6 @@ GF_RTSPCommand *gf_rtsp_command_new() GF_EXPORT void gf_rtsp_command_reset(GF_RTSPCommand *com) { - GF_RTSPTransport *trans; - GF_X_Attribute *att; if (!com) return; //free all headers @@ -83,12 +81,12 @@ void gf_rtsp_command_reset(GF_RTSPCommand *com) com->Range = NULL; while (gf_list_count(com->Transports)) { - trans = (GF_RTSPTransport *) gf_list_get(com->Transports, 0); + GF_RTSPTransport *trans = (GF_RTSPTransport *) gf_list_get(com->Transports, 0); gf_list_rem(com->Transports, 0); gf_rtsp_transport_del(trans); } while (gf_list_count(com->Xtensions)) { - att = (GF_X_Attribute*)gf_list_get(com->Xtensions, 0); + GF_X_Attribute *att = (GF_X_Attribute*)gf_list_get(com->Xtensions, 0); gf_list_rem(com->Xtensions, 0); gf_free(att->Name); gf_free(att->Value); @@ -111,12 +109,10 @@ GF_Err RTSP_WriteCommand(GF_RTSPSession *sess, GF_RTSPCommand *com, unsigned cha unsigned char **out_buffer, u32 *out_size) { u32 i, cur_pos, size, count; - char *buffer, temp[50]; - GF_RTSPTransport *trans; - GF_X_Attribute *att; + char *buffer; *out_buffer = NULL; - + size = RTSP_WRITE_STEPALLOC; buffer = (char *) gf_malloc(size); cur_pos = 0; @@ -186,6 +182,7 @@ GF_Err RTSP_WriteCommand(GF_RTSPSession *sess, GF_RTSPCommand *com, unsigned cha if (count) { RTSP_WRITE_ALLOC_STR(buffer, size, cur_pos, "Transport: "); for (i=0; iTransports, i); @@ -238,7 +235,7 @@ GF_Err RTSP_WriteCommand(GF_RTSPSession *sess, GF_RTSPCommand *com, unsigned cha } if (trans->SSRC) { RTSP_WRITE_ALLOC_STR(buffer, size, cur_pos, ";ssrc="); - RTSP_WRITE_INT(buffer, size, cur_pos, trans->SSRC, 0); + RTSP_WRITE_HEX(buffer, size, cur_pos, trans->SSRC, 0); } } //done with transport @@ -249,7 +246,7 @@ GF_Err RTSP_WriteCommand(GF_RTSPSession *sess, GF_RTSPCommand *com, unsigned cha //eXtensions count = gf_list_count(com->Xtensions); for (i=0; iXtensions, i); + GF_X_Attribute *att = (GF_X_Attribute *) gf_list_get(com->Xtensions, i); RTSP_WRITE_ALLOC_STR(buffer, size, cur_pos, "x-"); RTSP_WRITE_HEADER(buffer, size, cur_pos, att->Name, att->Value); } @@ -292,7 +289,7 @@ GF_Err gf_rtsp_send_command(GF_RTSPSession *sess, GF_RTSPCommand *com) && strcmp(com->method, GF_RTSP_PLAY) && strcmp(com->method, GF_RTSP_PAUSE) && strcmp(com->method, GF_RTSP_RECORD) - && strcmp(com->method, GF_RTSP_REDIRECTE) + && strcmp(com->method, GF_RTSP_REDIRECT) && strcmp(com->method, GF_RTSP_TEARDOWN) && strcmp(com->method, GF_RTSP_OPTIONS) @@ -418,7 +415,8 @@ void gf_rtsp_set_command_value(GF_RTSPCommand *com, char *Header, char *Value) else if (!stricmp(Header, "Range")) com->Range = gf_rtsp_range_parse(Value); else if (!stricmp(Header, "Referer")) com->Referer = gf_strdup(Value); else if (!stricmp(Header, "Scale")) sscanf(Value, "%lf", &com->Scale); - else if (!stricmp(Header, "Session")) com->Session = gf_strdup(Value); + else if (!stricmp(Header, "Session")) + com->Session = gf_strdup(Value); else if (!stricmp(Header, "Speed")) sscanf(Value, "%lf", &com->Speed); else if (!stricmp(Header, "User_Agent")) com->User_Agent = gf_strdup(Value); //Transports @@ -451,7 +449,7 @@ GF_Err RTSP_ParseCommandHeader(GF_RTSPSession *sess, GF_RTSPCommand *com, u32 Bo u32 Size; Size = sess->CurrentSize - sess->CurrentPos; - buffer = sess->TCPBuffer + sess->CurrentPos; + buffer = sess->tcp_buffer + sess->CurrentPos; //by default the command is wrong ;) com->StatusCode = NC_RTSP_Bad_Request; @@ -471,7 +469,7 @@ GF_Err RTSP_ParseCommandHeader(GF_RTSPSession *sess, GF_RTSPCommand *com, u32 Bo com->service_name = gf_strdup(ValBuf); //RTSP version - Pos = gf_token_get(LineBuffer, Pos, "\t\r\n", ValBuf, 1024); + Pos = gf_token_get(LineBuffer, Pos, " \t\r\n", ValBuf, 1024); if (Pos <= 0) return GF_OK; if (strcmp(ValBuf, GF_RTSP_VERSION)) { com->StatusCode = NC_RTSP_RTSP_Version_Not_Supported; @@ -483,6 +481,21 @@ GF_Err RTSP_ParseCommandHeader(GF_RTSPSession *sess, GF_RTSPCommand *com, u32 Bo return gf_rtsp_parse_header(buffer + ret, Size - ret, BodyStart, com, NULL); } +char *RTSP_DEFINED_METHODS[] = +{ + GF_RTSP_DESCRIBE, + GF_RTSP_SETUP, + GF_RTSP_PLAY, + GF_RTSP_PAUSE, + GF_RTSP_RECORD, + GF_RTSP_TEARDOWN, + GF_RTSP_GET_PARAMETER, + GF_RTSP_SET_PARAMETER, + GF_RTSP_OPTIONS, + GF_RTSP_ANNOUNCE, + GF_RTSP_REDIRECT, + NULL +}; GF_EXPORT GF_Err gf_rtsp_get_command(GF_RTSPSession *sess, GF_RTSPCommand *com) @@ -497,19 +510,30 @@ GF_Err gf_rtsp_get_command(GF_RTSPSession *sess, GF_RTSPCommand *com) if (!sess->connection) return GF_IP_CONNECTION_CLOSED; //lock - gf_mx_p(sess->mx); - //fill TCP buffer e = gf_rtsp_fill_buffer(sess); if (e) goto exit; + if (sess->TCPChannels) //this is upcoming, interleaved data - if (strncmp(sess->TCPBuffer+sess->CurrentPos, "RTSP", 4)) { - e = GF_IP_NETWORK_EMPTY; - goto exit; + if (sess->interleaved) { + u32 i=0; + Bool sync = GF_FALSE; + while (RTSP_DEFINED_METHODS[i]) { + if (!strncmp(sess->tcp_buffer+sess->CurrentPos, RTSP_DEFINED_METHODS[i], strlen(RTSP_DEFINED_METHODS[i]) ) ) { + sync = GF_TRUE; + break; + } + } + if (!sync) { + e = GF_IP_NETWORK_EMPTY; + goto exit; + } } e = gf_rtsp_read_reply(sess); if (e) goto exit; + GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTSP] Got Command:\n%s\n", sess->tcp_buffer+sess->CurrentPos)); + gf_rtsp_get_body_info(sess, &BodyStart, &size); e = RTSP_ParseCommandHeader(sess, com, BodyStart); //before returning an error we MUST reset the TCP buffer @@ -517,12 +541,13 @@ GF_Err gf_rtsp_get_command(GF_RTSPSession *sess, GF_RTSPCommand *com) //copy the body if any if (!e && com->Content_Length) { com->body = (char *) gf_malloc(sizeof(char) * (com->Content_Length)); - memcpy(com->body, sess->TCPBuffer+sess->CurrentPos + BodyStart, com->Content_Length); + memcpy(com->body, sess->tcp_buffer+sess->CurrentPos + BodyStart, com->Content_Length); } //reset TCP buffer sess->CurrentPos += BodyStart + com->Content_Length; - if (!com->CSeq) com->StatusCode = NC_RTSP_Bad_Request; + if (!com->CSeq) + com->StatusCode = NC_RTSP_Bad_Request; if (e || (com->StatusCode != NC_RTSP_OK)) goto exit; @@ -565,7 +590,6 @@ GF_Err gf_rtsp_get_command(GF_RTSPSession *sess, GF_RTSPCommand *com) } exit: - gf_mx_v(sess->mx); return e; } diff --git a/src/ietf/rtsp_common.c b/src/ietf/rtsp_common.c index a8342ae..5fd1793 100644 --- a/src/ietf/rtsp_common.c +++ b/src/ietf/rtsp_common.c @@ -61,11 +61,11 @@ void gf_rtsp_get_body_info(GF_RTSPSession *sess, u32 *body_start, u32 *body_size u32 i; s32 start; char *buffer; - char *cl_str, val[30]; + char *cl_str; *body_start = *body_size = 0; - buffer = sess->TCPBuffer + sess->CurrentPos; + buffer = sess->tcp_buffer + sess->CurrentPos; start = gf_token_find(buffer, 0, sess->CurrentSize - sess->CurrentPos, "\r\n\r\n"); if (start<=0) { return; @@ -79,6 +79,7 @@ void gf_rtsp_get_body_info(GF_RTSPSession *sess, u32 *body_start, u32 *body_size if (!cl_str) cl_str = strstr(buffer, "Content-length: "); if (cl_str) { + char val[30]; cl_str += 16; i = 0; while (cl_str[i] != '\r') { @@ -106,17 +107,15 @@ GF_Err gf_rtsp_refill_buffer(GF_RTSPSession *sess) if (!res) return gf_rtsp_fill_buffer(sess); ptr = (char *)gf_malloc(sizeof(char) * res); - memcpy(ptr, sess->TCPBuffer+sess->CurrentPos, res); - memcpy(sess->TCPBuffer, ptr, res); + memcpy(ptr, sess->tcp_buffer + sess->CurrentPos, res); + memcpy(sess->tcp_buffer, ptr, res); gf_free(ptr); sess->CurrentPos = 0; sess->CurrentSize = res; //now read from current pos - e = gf_sk_receive(sess->connection, sess->TCPBuffer + sess->CurrentSize, - RTSP_TCP_BUF_SIZE - sess->CurrentSize, - 0, &res); + e = gf_sk_receive(sess->connection, sess->tcp_buffer + sess->CurrentSize, sess->SockBufferSize - sess->CurrentSize, &res); if (!e) { sess->CurrentSize += res; @@ -132,16 +131,16 @@ GF_Err gf_rtsp_fill_buffer(GF_RTSPSession *sess) if (!sess->connection) return GF_IP_NETWORK_EMPTY; if (sess->CurrentSize == sess->CurrentPos) { - e = gf_sk_receive(sess->connection, sess->TCPBuffer, RTSP_TCP_BUF_SIZE, 0, &sess->CurrentSize); + e = gf_sk_receive(sess->connection, sess->tcp_buffer, sess->SockBufferSize, &sess->CurrentSize); sess->CurrentPos = 0; - sess->TCPBuffer[sess->CurrentSize] = 0; + sess->tcp_buffer[sess->CurrentSize] = 0; if (e) sess->CurrentSize = 0; } else if (!sess->CurrentSize) e = GF_IP_NETWORK_EMPTY; return e; } -GF_RTSPTransport *gf_rtsp_transport_parse(char *buffer) +GF_RTSPTransport *gf_rtsp_transport_parse(u8 *buffer) { Bool IsFirst; char buf[100], param_name[100], param_val[100]; @@ -222,7 +221,7 @@ GF_RTSPTransport *gf_rtsp_transport_parse(char *buffer) -GF_Err gf_rtsp_parse_header(char *buffer, u32 BufferSize, u32 BodyStart, GF_RTSPCommand *com, GF_RTSPResponse *rsp) +GF_Err gf_rtsp_parse_header(u8 *buffer, u32 BufferSize, u32 BodyStart, GF_RTSPCommand *com, GF_RTSPResponse *rsp) { char LineBuffer[1024]; char HeaderBuf[100], ValBuf[1024], temp[400]; diff --git a/src/ietf/rtsp_response.c b/src/ietf/rtsp_response.c index 3b070f1..397cc13 100644 --- a/src/ietf/rtsp_response.c +++ b/src/ietf/rtsp_response.c @@ -49,9 +49,6 @@ GF_RTSPResponse *gf_rtsp_response_new() GF_EXPORT void gf_rtsp_response_reset(GF_RTSPResponse *rsp) { - GF_RTPInfo *inf; - GF_RTSPTransport *trans; - GF_X_Attribute *att; if (!rsp) return; //free all headers @@ -103,19 +100,19 @@ void gf_rtsp_response_reset(GF_RTSPResponse *rsp) rsp->SessionTimeOut = 0; while (gf_list_count(rsp->Transports)) { - trans = (GF_RTSPTransport*) gf_list_get(rsp->Transports, 0); + GF_RTSPTransport *trans = (GF_RTSPTransport*) gf_list_get(rsp->Transports, 0); gf_list_rem(rsp->Transports, 0); gf_rtsp_transport_del(trans); } while (gf_list_count(rsp->RTP_Infos)) { - inf = (GF_RTPInfo*) gf_list_get(rsp->RTP_Infos, 0); + GF_RTPInfo *inf = (GF_RTPInfo*) gf_list_get(rsp->RTP_Infos, 0); gf_list_rem(rsp->RTP_Infos, 0); if (inf->url) gf_free(inf->url); gf_free(inf); } while (gf_list_count(rsp->Xtensions)) { - att = (GF_X_Attribute*)gf_list_get(rsp->Xtensions, 0); + GF_X_Attribute *att = (GF_X_Attribute*)gf_list_get(rsp->Xtensions, 0); gf_list_rem(rsp->Xtensions, 0); gf_free(att->Name); gf_free(att->Value); @@ -325,7 +322,7 @@ GF_Err RTSP_ParseResponseHeader(GF_RTSPSession *sess, GF_RTSPResponse *rsp, u32 u32 Size; Size = sess->CurrentSize - sess->CurrentPos; - buffer = sess->TCPBuffer + sess->CurrentPos; + buffer = sess->tcp_buffer + sess->CurrentPos; //parse first line ret = gf_token_get_line(buffer, 0, Size, LineBuffer, 1024); @@ -376,11 +373,6 @@ GF_Err gf_rtsp_get_response(GF_RTSPSession *sess, GF_RTSPResponse *rsp) if (!sess || !rsp) return GF_BAD_PARAM; gf_rtsp_response_reset(rsp); - - //LOCK - gf_mx_p(sess->mx); - - e = gf_rtsp_check_connection(sess); if (e) goto exit; @@ -391,7 +383,7 @@ GF_Err gf_rtsp_get_response(GF_RTSPSession *sess, GF_RTSPResponse *rsp) goto exit; //this is interleaved data - if (!IsRTSPMessage(sess->TCPBuffer+sess->CurrentPos) ) { + if (!IsRTSPMessage(sess->tcp_buffer+sess->CurrentPos) ) { gf_rtsp_session_read(sess); e = GF_IP_NETWORK_EMPTY; goto exit; @@ -407,10 +399,10 @@ GF_Err gf_rtsp_get_response(GF_RTSPSession *sess, GF_RTSPResponse *rsp) //copy the body if any if (!e && rsp->Content_Length) { rsp->body = (char *)gf_malloc(sizeof(char) * (rsp->Content_Length)); - memcpy(rsp->body, sess->TCPBuffer+sess->CurrentPos + BodyStart, rsp->Content_Length); + memcpy(rsp->body, sess->tcp_buffer+sess->CurrentPos + BodyStart, rsp->Content_Length); } - GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTSP] Got Response:\n%s\n", sess->TCPBuffer+sess->CurrentPos)); + GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTSP] Got Response:\n%s\n", sess->tcp_buffer+sess->CurrentPos)); //reset TCP buffer sess->CurrentPos += BodyStart + rsp->Content_Length; @@ -438,7 +430,6 @@ GF_Err gf_rtsp_get_response(GF_RTSPSession *sess, GF_RTSPResponse *rsp) //to an aggreagated sequence of requests //if we have reseted the connection (due to an APP error) return empty if (rsp->CSeq && sess->CSeq > rsp->CSeq + sess->NbPending) { - gf_mx_v(sess->mx); return gf_rtsp_get_response(sess, rsp); } @@ -478,8 +469,6 @@ exit: sess->http = NULL; } } - - gf_mx_v(sess->mx); return e; } @@ -487,11 +476,7 @@ GF_Err RTSP_WriteResponse(GF_RTSPSession *sess, GF_RTSPResponse *rsp, unsigned char **out_buffer, u32 *out_size) { u32 i, cur_pos, size, count; - char *buffer, temp[50]; - GF_RTSPTransport *trans; - GF_X_Attribute *att; - GF_RTPInfo *info; - + char *buffer; *out_buffer = NULL; @@ -575,6 +560,7 @@ GF_Err RTSP_WriteResponse(GF_RTSPSession *sess, GF_RTSPResponse *rsp, RTSP_WRITE_ALLOC_STR(buffer, size, cur_pos, "RTPInfo: "); for (i=0; iRTP_Infos, i); @@ -611,6 +597,7 @@ GF_Err RTSP_WriteResponse(GF_RTSPSession *sess, GF_RTSPResponse *rsp, if (count) { RTSP_WRITE_ALLOC_STR(buffer, size, cur_pos, "Transport: "); for (i=0; iTransports, i); @@ -663,7 +650,7 @@ GF_Err RTSP_WriteResponse(GF_RTSPSession *sess, GF_RTSPResponse *rsp, } if (trans->SSRC) { RTSP_WRITE_ALLOC_STR(buffer, size, cur_pos, ";ssrc="); - RTSP_WRITE_INT(buffer, size, cur_pos, trans->SSRC, 0); + RTSP_WRITE_HEX(buffer, size, cur_pos, trans->SSRC, 0); } } //done with transport @@ -679,7 +666,7 @@ GF_Err RTSP_WriteResponse(GF_RTSPSession *sess, GF_RTSPResponse *rsp, //eXtensions count = gf_list_count(rsp->Xtensions); for (i=0; iXtensions, i); + GF_X_Attribute *att = (GF_X_Attribute*)gf_list_get(rsp->Xtensions, i); RTSP_WRITE_ALLOC_STR(buffer, size, cur_pos, "x-"); RTSP_WRITE_HEADER(buffer, size, cur_pos, att->Name, att->Value); } @@ -707,14 +694,10 @@ GF_Err gf_rtsp_send_response(GF_RTSPSession *sess, GF_RTSPResponse *rsp) if (rsp->CSeq > sess->CSeq) return GF_BAD_PARAM; e = RTSP_WriteResponse(sess, rsp, (unsigned char **) &buffer, &size); - if (e) goto exit; - - //send buffer - e = gf_rtsp_send_data(sess, buffer, size); - if (e) return e; -// fprintf(stderr, "RTSP Send Response\n\n%s\n\n", buffer); - -exit: + if (!e) { + //send buffer + e = gf_rtsp_send_data(sess, buffer, size); + } if (buffer) gf_free(buffer); return e; } diff --git a/src/ietf/rtsp_session.c b/src/ietf/rtsp_session.c index 1aac348..b138db2 100644 --- a/src/ietf/rtsp_session.c +++ b/src/ietf/rtsp_session.c @@ -29,6 +29,8 @@ #ifndef GPAC_DISABLE_STREAMING +/*default packet size to use when storing incomplete packets*/ +#define RTSP_PCK_SIZE 1000 GF_Err RTSP_UnpackURL(char *sURL, char *Server, u16 *Port, char *Service, Bool *useTCP) { @@ -136,9 +138,10 @@ GF_RTSPSession *gf_rtsp_session_new(char *sURL, u16 DefaultPort) sess->HasTunnel = GF_TRUE; } + gf_rtsp_set_buffer_size(sess, RTSP_PCK_SIZE); + sess->Server = gf_strdup(server); sess->Service = gf_strdup(service); - sess->mx = gf_mx_new("RTSPSession"); sess->TCPChannels = gf_list_new(); gf_rtsp_session_reset(sess, GF_FALSE); return sess; @@ -150,7 +153,6 @@ void gf_rtsp_reset_aggregation(GF_RTSPSession *sess) { if (!sess) return; - gf_mx_p(sess->mx); if (sess->RTSP_State == GF_RTSP_STATE_WAIT_FOR_CONTROL) { strcpy(sess->RTSPLastRequest, "RESET"); //skip all we haven't received @@ -158,18 +160,12 @@ void gf_rtsp_reset_aggregation(GF_RTSPSession *sess) sess->NbPending = 0; } sess->RTSP_State = GF_RTSP_STATE_INIT; - gf_mx_v(sess->mx); -} - -void RTSP_AcknowledgeError(GF_RTSPSession *sess) -{ } void RemoveTCPChannels(GF_RTSPSession *sess) { - GF_TCPChan *ch; while (gf_list_count(sess->TCPChannels)) { - ch = (GF_TCPChan*)gf_list_get(sess->TCPChannels, 0); + GF_TCPChan *ch = (GF_TCPChan*)gf_list_get(sess->TCPChannels, 0); gf_free(ch); gf_list_rem(sess->TCPChannels, 0); } @@ -179,8 +175,6 @@ void RemoveTCPChannels(GF_RTSPSession *sess) GF_EXPORT void gf_rtsp_session_reset(GF_RTSPSession *sess, Bool ResetConnection) { - gf_mx_p(sess->mx); - sess->last_session_id = NULL; sess->NeedConnection = 1; @@ -200,7 +194,6 @@ void gf_rtsp_session_reset(GF_RTSPSession *sess, Bool ResetConnection) sess->CurrentPos = sess->CurrentSize = 0; strcpy(sess->RTSPLastRequest, ""); RemoveTCPChannels(sess); - gf_mx_v(sess->mx); } GF_EXPORT @@ -216,8 +209,8 @@ void gf_rtsp_session_del(GF_RTSPSession *sess) if (sess->Service) gf_free(sess->Service); gf_list_del(sess->TCPChannels); if (sess->rtsp_pck_buf) gf_free(sess->rtsp_pck_buf); - if (sess->MobileIP) gf_free(sess->MobileIP); - gf_mx_del(sess->mx); + gf_free(sess->tcp_buffer); + gf_free(sess); } @@ -227,51 +220,38 @@ u32 gf_rtsp_get_session_state(GF_RTSPSession *sess) u32 state; if (!sess) return GF_RTSP_STATE_INVALIDATED; - gf_mx_p(sess->mx); state = sess->RTSP_State; - gf_mx_v(sess->mx); return state; } -GF_EXPORT -void gf_rtsp_set_mobile_ip(GF_RTSPSession *sess, char *MobileIP) -{ - if (sess->MobileIP) gf_free(sess->MobileIP); - sess->MobileIP = NULL; - if (MobileIP) sess->MobileIP = gf_strdup(MobileIP); -} - - -GF_EXPORT +#if 0 //unused char *gf_rtsp_get_last_request(GF_RTSPSession *sess) { - char *ret; if (!sess) return NULL; - gf_mx_p(sess->mx); - ret = sess->RTSPLastRequest; - gf_mx_v(sess->mx); - return ret; + return sess->RTSPLastRequest; } +#endif //check whether the url contains server and service name //no thread protection as this is const throughout the session GF_EXPORT -u32 gf_rtsp_is_my_session(GF_RTSPSession *sess, char *url) +Bool gf_rtsp_is_my_session(GF_RTSPSession *sess, char *url) { - if (!sess) return 0; - if (!strstr(url, sess->Server)) return 0; + if (!sess) return GF_FALSE; + if (!strstr(url, sess->Server)) return GF_FALSE; //same url or sub-url - if (strstr(url, sess->Service)) return 1; - return 0; + if (strstr(url, sess->Service)) return GF_TRUE; + return GF_FALSE; } -GF_EXPORT +#if 0 //unused const char *gf_rtsp_get_last_session_id(GF_RTSPSession *sess) { if (!sess) return NULL; return sess->last_session_id; } +#endif GF_EXPORT char *gf_rtsp_get_server_name(GF_RTSPSession *sess) @@ -280,13 +260,6 @@ char *gf_rtsp_get_server_name(GF_RTSPSession *sess) return sess->Server; } -GF_EXPORT -char *gf_rtsp_get_service_name(GF_RTSPSession *sess) -{ - if (!sess) return NULL; - return sess->Service; -} - GF_EXPORT u16 gf_rtsp_get_session_port(GF_RTSPSession *sess) { @@ -305,13 +278,20 @@ GF_Err gf_rtsp_check_connection(GF_RTSPSession *sess) if (!sess->connection) return GF_OUT_OF_MEM; } //the session is down, reconnect - e = gf_sk_connect(sess->connection, sess->Server, sess->Port, sess->MobileIP); + e = gf_sk_connect(sess->connection, sess->Server, sess->Port, NULL); if (e) return e; if (sess->SockBufferSize) gf_sk_set_buffer_size(sess->connection, GF_FALSE, sess->SockBufferSize); +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) + gf_rtsp_http_tunnel_start(NULL, NULL); +#endif + if (!sess->http && sess->HasTunnel) { - e = gf_rtsp_http_tunnel_start(sess, "toto is the king of RTSP"); + const char *ua = gf_opts_get_key("core", "user-agent"); + if (!ua) ua = "GPAC " GPAC_VERSION; + e = gf_rtsp_http_tunnel_start(sess, (char *)ua); if (e) return e; } sess->NeedConnection = 0; @@ -319,7 +299,7 @@ GF_Err gf_rtsp_check_connection(GF_RTSPSession *sess) } -GF_Err gf_rtsp_send_data(GF_RTSPSession *sess, char *buffer, u32 Size) +GF_Err gf_rtsp_send_data(GF_RTSPSession *sess, u8 *buffer, u32 Size) { GF_Err e; u32 Size64; @@ -347,7 +327,7 @@ static GF_TCPChan *GetTCPChannel(GF_RTSPSession *sess, u8 rtpID, u8 rtcpID, Bool u32 i, count = gf_list_count(sess->TCPChannels); for (i=0; iTCPChannels, i); - if (ptr->rtpID == rtpID) goto exit;; + if (ptr->rtpID == rtpID) goto exit; if (ptr->rtcpID == rtcpID) goto exit; } return NULL; @@ -369,7 +349,7 @@ GF_Err gf_rtsp_set_deinterleave(GF_RTSPSession *sess) if (!sess) return GF_SERVICE_ERROR; Size = sess->CurrentSize - sess->CurrentPos; - buffer = sess->TCPBuffer + sess->CurrentPos; + buffer = sess->tcp_buffer + sess->CurrentPos; if (!Size) return GF_IP_NETWORK_EMPTY; @@ -447,38 +427,12 @@ GF_Err gf_rtsp_set_deinterleave(GF_RTSPSession *sess) } - - - - - - -/* - Exposed API, thread-safe -*/ - -GF_Err RTSP_ResetInterleaving(GF_RTSPSession *sess, Bool ResetChannels) -{ - if (!sess) return GF_BAD_PARAM; - - gf_mx_p(sess->mx); - sess->payloadSize = 0; - sess->pck_start = 0; - sess->InterID = (u8) -1; - if (ResetChannels) RemoveTCPChannels(sess); - gf_mx_v(sess->mx); - - return GF_OK; -} - - GF_EXPORT GF_Err gf_rtsp_session_read(GF_RTSPSession *sess) { GF_Err e; if (!sess) return GF_BAD_PARAM; - gf_mx_p(sess->mx); e = gf_rtsp_fill_buffer(sess); if (!e) { //only read if not RTSP @@ -487,7 +441,6 @@ GF_Err gf_rtsp_session_read(GF_RTSPSession *sess) if (e) break; } } - gf_mx_v(sess->mx); return e; } @@ -495,13 +448,15 @@ GF_Err gf_rtsp_session_read(GF_RTSPSession *sess) GF_EXPORT u32 gf_rtsp_unregister_interleave(GF_RTSPSession *sess, u8 LowInterID) { + u32 res; GF_TCPChan *ptr; + if (!sess) return 0; - gf_mx_p(sess->mx); ptr = GetTCPChannel(sess, LowInterID, LowInterID, GF_TRUE); if (ptr) gf_free(ptr); - gf_mx_v(sess->mx); - return gf_list_count(sess->TCPChannels); + res = gf_list_count(sess->TCPChannels); + if (!res) sess->interleaved = GF_FALSE; + return res; } GF_EXPORT @@ -511,7 +466,6 @@ GF_Err gf_rtsp_register_interleave(GF_RTSPSession *sess, void *the_ch, u8 LowInt if (!sess) return GF_BAD_PARAM; - gf_mx_p(sess->mx); //do NOT register twice ptr = GetTCPChannel(sess, LowInterID, HighInterID, GF_FALSE); if (!ptr) { @@ -521,33 +475,25 @@ GF_Err gf_rtsp_register_interleave(GF_RTSPSession *sess, void *the_ch, u8 LowInt ptr->rtcpID = HighInterID; gf_list_add(sess->TCPChannels, ptr); } - gf_mx_v(sess->mx); + sess->interleaved=GF_TRUE; return GF_OK; } GF_EXPORT -GF_Err gf_rtsp_set_interleave_callback(GF_RTSPSession *sess, - GF_Err (*SignalData)(GF_RTSPSession *sess, void *chan, char *buffer, u32 bufferSize, Bool IsRTCP) - ) +GF_Err gf_rtsp_set_interleave_callback(GF_RTSPSession *sess, gf_rtsp_interleave_callback SignalData) { if (!sess) return GF_BAD_PARAM; - gf_mx_p(sess->mx); - //only if existing if (SignalData) sess->RTSP_SignalData = SignalData; - //gf_realloc or alloc - if (sess->rtsp_pck_buf && sess->rtsp_pck_size != RTSP_PCK_SIZE) { - sess->rtsp_pck_size = RTSP_PCK_SIZE; - sess->rtsp_pck_buf = (char *)gf_realloc(sess->rtsp_pck_buf, sizeof(char)*sess->rtsp_pck_size); - } else if (!sess->rtsp_pck_buf) { + if (!sess->rtsp_pck_buf || (sess->rtsp_pck_size != RTSP_PCK_SIZE) ) { + if (!sess->rtsp_pck_buf) + sess->pck_start = 0; sess->rtsp_pck_size = RTSP_PCK_SIZE; sess->rtsp_pck_buf = (char *)gf_realloc(sess->rtsp_pck_buf, sizeof(char)*sess->rtsp_pck_size); - sess->pck_start = 0; } - gf_mx_v(sess->mx); return GF_OK; } @@ -555,7 +501,9 @@ GF_EXPORT GF_Err gf_rtsp_set_buffer_size(GF_RTSPSession *sess, u32 BufferSize) { if (!sess) return GF_BAD_PARAM; + if (sess->SockBufferSize >= BufferSize) return GF_OK; sess->SockBufferSize = BufferSize; + sess->tcp_buffer = gf_realloc(sess->tcp_buffer, BufferSize); return GF_OK; } @@ -567,16 +515,25 @@ static Bool HTTP_RandInit = GF_TRUE; #define HTTP_RSP_OK "HTTP/1.0 200 OK" -void RTSP_GenerateHTTPCookie(GF_RTSPSession *sess) +//http tunnelling start. +GF_EXPORT +GF_Err gf_rtsp_http_tunnel_start(GF_RTSPSession *sess, char *UserAgent) { + GF_Err e; + u32 size; + s32 pos; u32 i, num, temp; + char buffer[GF_RTSP_DEFAULT_BUFFER]; + + if (!sess || !UserAgent) return GF_BAD_PARAM; + //generate http cookie if (HTTP_RandInit) { gf_rand_init(GF_FALSE); HTTP_RandInit = GF_FALSE; } if (!sess->CookieRadLen) { - strcpy(sess->HTTP_Cookie, "MPEG4M4"); + strcpy(sess->HTTP_Cookie, "GPACROH"); sess->CookieRadLen = 8; } num = gf_rand(); @@ -585,20 +542,6 @@ void RTSP_GenerateHTTPCookie(GF_RTSPSession *sess) sess->HTTP_Cookie[sess->CookieRadLen + i] = (u8) temp + sess->HTTP_Cookie[0]; } sess->HTTP_Cookie[sess->CookieRadLen + i] = 0; -} - - - - -//http tunnelling start. -GF_Err gf_rtsp_http_tunnel_start(GF_RTSPSession *sess, char *UserAgent) -{ - GF_Err e; - u32 size; - s32 pos; - char buffer[GF_RTSP_DEFAULT_BUFFER]; - - RTSP_GenerateHTTPCookie(sess); // 1. send "GET /sample.mov HTTP/1.0\r\n ..." memset(buffer, 0, GF_RTSP_DEFAULT_BUFFER); @@ -615,7 +558,7 @@ GF_Err gf_rtsp_http_tunnel_start(GF_RTSPSession *sess, char *UserAgent) if (e) return e; // 2. wait for "HTTP/1.0 200 OK" - e = gf_sk_receive_wait(sess->connection, buffer, GF_RTSP_DEFAULT_BUFFER, 0, &size, HTTP_WAIT_SEC); + e = gf_sk_receive_wait(sess->connection, buffer, GF_RTSP_DEFAULT_BUFFER, &size, HTTP_WAIT_SEC); if (e) return e; //get HTTP/1.0 200 OK @@ -626,8 +569,7 @@ GF_Err gf_rtsp_http_tunnel_start(GF_RTSPSession *sess, char *UserAgent) sess->http = gf_sk_new(GF_SOCK_TYPE_TCP); if (!sess->http ) return GF_IP_NETWORK_FAILURE; - /*mobileIP is enabled, bind first*/ - if (gf_sk_connect(sess->http, sess->Server, sess->Port, sess->MobileIP)) return GF_IP_CONNECTION_FAILURE; + if (gf_sk_connect(sess->http, sess->Server, sess->Port, NULL)) return GF_IP_CONNECTION_FAILURE; memset(buffer, 0, GF_RTSP_DEFAULT_BUFFER); pos = 0; @@ -693,13 +635,13 @@ GF_RTSPSession *gf_rtsp_session_new_server(GF_Socket *rtsp_listener) sess->ConnectionType = fam; gf_sk_get_host_name(name); sess->Server = gf_strdup(name); - + gf_rtsp_set_buffer_size(sess, 4096); sess->TCPChannels = gf_list_new(); return sess; } -GF_EXPORT +#if 0 //unused GF_Err gf_rtsp_load_service_name(GF_RTSPSession *sess, char *URL) { char server[1024], service[1024]; @@ -722,7 +664,7 @@ GF_Err gf_rtsp_load_service_name(GF_RTSPSession *sess, char *URL) sess->Service = gf_strdup(service); return GF_OK; } - +#endif GF_EXPORT char *gf_rtsp_generate_session_id(GF_RTSPSession *sess) @@ -747,7 +689,7 @@ char *gf_rtsp_generate_session_id(GF_RTSPSession *sess) GF_EXPORT -GF_Err gf_rtsp_get_session_ip(GF_RTSPSession *sess, char *buffer) +GF_Err gf_rtsp_get_session_ip(GF_RTSPSession *sess, char buffer[GF_MAX_IP_NAME_LEN]) { if (!sess || !sess->connection) return GF_BAD_PARAM; gf_sk_get_local_ip(sess->connection, buffer); @@ -755,7 +697,7 @@ GF_Err gf_rtsp_get_session_ip(GF_RTSPSession *sess, char *buffer) } -GF_EXPORT +#if 0 //unused u8 gf_rtsp_get_next_interleave_id(GF_RTSPSession *sess) { u32 i; @@ -769,7 +711,7 @@ u8 gf_rtsp_get_next_interleave_id(GF_RTSPSession *sess) } return id; } - +#endif GF_EXPORT GF_Err gf_rtsp_get_remote_address(GF_RTSPSession *sess, char *buf) @@ -778,4 +720,21 @@ GF_Err gf_rtsp_get_remote_address(GF_RTSPSession *sess, char *buf) return gf_sk_get_remote_address(sess->connection, buf); } +GF_EXPORT +GF_Err gf_rtsp_session_write_interleaved(GF_RTSPSession *sess, u32 idx, u8 *pck, u32 pck_size) +{ + GF_Err e; + char streamID[4]; + if (!sess || !sess->connection) return GF_BAD_PARAM; + + streamID[0] = '$'; + streamID[1] = (u8) idx; + streamID[2] = (pck_size>>8) & 0xFF; + streamID[3] = pck_size & 0xFF; + + e = gf_sk_send_wait(sess->connection, streamID, 4, 20); + e |= gf_sk_send_wait(sess->connection, pck, pck_size, 20); + return e; +} + #endif /*GPAC_DISABLE_STREAMING*/ diff --git a/src/ietf/sdp.c b/src/ietf/sdp.c index bc3faaf..0251ace 100644 --- a/src/ietf/sdp.c +++ b/src/ietf/sdp.c @@ -47,10 +47,9 @@ GF_SDP_FMTP *gf_sdp_fmtp_new() GF_EXPORT void gf_sdp_fmtp_del(GF_SDP_FMTP *fmtp) { - GF_X_Attribute *att; if (!fmtp) return; while (gf_list_count(fmtp->Attributes)) { - att = (GF_X_Attribute*)gf_list_get(fmtp->Attributes, 0); + GF_X_Attribute *att = (GF_X_Attribute*)gf_list_get(fmtp->Attributes, 0); gf_list_rem(fmtp->Attributes, 0); if (att->Name) gf_free(att->Name); if (att->Value) gf_free(att->Value); @@ -77,8 +76,6 @@ void SDP_ParseAttribute(GF_SDPInfo *sdp, char *buffer, GF_SDPMedia *media) s32 pos; u32 PayT; char comp[3000]; - GF_RTPMap *map; - GF_SDP_FMTP *fmtp; GF_X_Attribute *att; pos = gf_token_get(buffer, 0, " :\t\r\n", comp, 3000); @@ -182,6 +179,7 @@ void SDP_ParseAttribute(GF_SDPInfo *sdp, char *buffer, GF_SDPMedia *media) return; } if (!strcmp(comp, "rtpmap")) { + GF_RTPMap *map; if (!media) return; map = (GF_RTPMap*)gf_malloc(sizeof(GF_RTPMap)); pos = gf_token_get(buffer, pos, ": \r\n", comp, 3000); @@ -197,6 +195,7 @@ void SDP_ParseAttribute(GF_SDPInfo *sdp, char *buffer, GF_SDPMedia *media) } //FMTP if (!strcmp(comp, "fmtp")) { + GF_SDP_FMTP *fmtp; if (!media) return; pos = gf_token_get(buffer, pos, ": \r\n", comp, 3000); PayT = atoi(comp); @@ -245,22 +244,17 @@ void SDP_ParseAttribute(GF_SDPInfo *sdp, char *buffer, GF_SDPMedia *media) GF_EXPORT void gf_sdp_media_del(GF_SDPMedia *media) { - GF_SDPBandwidth *bw; - GF_RTPMap *map; - GF_SDPConnection *conn; - GF_SDP_FMTP *fmtp; - GF_X_Attribute *att; if (!media) return; while (gf_list_count(media->FMTP)) { - fmtp = (GF_SDP_FMTP*)gf_list_get(media->FMTP, 0); + GF_SDP_FMTP *fmtp = (GF_SDP_FMTP*)gf_list_get(media->FMTP, 0); gf_list_rem(media->FMTP, 0); gf_sdp_fmtp_del(fmtp); } gf_list_del(media->FMTP); while (gf_list_count(media->Attributes)) { - att = (GF_X_Attribute*)gf_list_get(media->Attributes, 0); + GF_X_Attribute *att = (GF_X_Attribute*)gf_list_get(media->Attributes, 0); gf_list_rem(media->Attributes, 0); if (att->Name) gf_free(att->Name); if (att->Value) gf_free(att->Value); @@ -269,7 +263,7 @@ void gf_sdp_media_del(GF_SDPMedia *media) gf_list_del(media->Attributes); while (gf_list_count(media->RTPMaps)) { - map = (GF_RTPMap*)gf_list_get(media->RTPMaps, 0); + GF_RTPMap *map = (GF_RTPMap*)gf_list_get(media->RTPMaps, 0); gf_free(map->payload_name); gf_free(map); gf_list_rem(media->RTPMaps, 0); @@ -277,14 +271,14 @@ void gf_sdp_media_del(GF_SDPMedia *media) gf_list_del(media->RTPMaps); while (gf_list_count(media->Connections)) { - conn = (GF_SDPConnection*)gf_list_get(media->Connections, 0); + GF_SDPConnection *conn = (GF_SDPConnection*)gf_list_get(media->Connections, 0); gf_list_rem(media->Connections, 0); gf_sdp_conn_del(conn); } gf_list_del(media->Connections); while (gf_list_count(media->Bandwidths)) { - bw = (GF_SDPBandwidth*)gf_list_get(media->Bandwidths, 0); + GF_SDPBandwidth *bw = (GF_SDPBandwidth*)gf_list_get(media->Bandwidths, 0); gf_list_rem(media->Bandwidths, 0); if (bw->name) gf_free(bw->name); gf_free(bw); @@ -357,33 +351,28 @@ GF_SDPInfo *gf_sdp_info_new() GF_EXPORT void gf_sdp_info_reset(GF_SDPInfo *sdp) { - GF_SDPBandwidth *bw; - GF_SDPMedia *media; - GF_SDPTiming *timing; - GF_X_Attribute *att; - if (!sdp) return; while (gf_list_count(sdp->media_desc)) { - media = (GF_SDPMedia*)gf_list_get(sdp->media_desc, 0); + GF_SDPMedia *media = (GF_SDPMedia*)gf_list_get(sdp->media_desc, 0); gf_list_rem(sdp->media_desc, 0); gf_sdp_media_del(media); } while (gf_list_count(sdp->Attributes)) { - att = (GF_X_Attribute*)gf_list_get(sdp->Attributes, 0); + GF_X_Attribute *att = (GF_X_Attribute*)gf_list_get(sdp->Attributes, 0); gf_list_rem(sdp->Attributes, 0); if (att->Name) gf_free(att->Name); if (att->Value) gf_free(att->Value); gf_free(att); } while (gf_list_count(sdp->b_bandwidth)) { - bw = (GF_SDPBandwidth*)gf_list_get(sdp->b_bandwidth, 0); + GF_SDPBandwidth *bw = (GF_SDPBandwidth*)gf_list_get(sdp->b_bandwidth, 0); gf_list_rem(sdp->b_bandwidth, 0); if (bw->name) gf_free(bw->name); gf_free(bw); } while (gf_list_count(sdp->Timing)) { - timing = (GF_SDPTiming*)gf_list_get(sdp->Timing, 0); + GF_SDPTiming *timing = (GF_SDPTiming*)gf_list_get(sdp->Timing, 0); gf_list_rem(sdp->Timing, 0); gf_free(timing); } @@ -444,7 +433,7 @@ Bool SDP_IsDynamicPayload(GF_SDPMedia *media, char *payt) } //translate h || m || d in sec. Fractions are not allowed with this writing -s32 SDP_MakeSeconds(char *buf) +static s32 SDP_MakeSeconds(char *buf) { s32 sign; char num[30], *test; @@ -492,6 +481,12 @@ GF_Err gf_sdp_info_parse(GF_SDPInfo *sdp, char *sdp_text, u32 text_size) if (!sdp) return GF_BAD_PARAM; +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + SDP_MakeSeconds("30m"); + } +#endif + //Clean SDP info gf_sdp_info_reset(sdp); @@ -614,7 +609,7 @@ GF_Err gf_sdp_info_parse(GF_SDPInfo *sdp, char *sdp_text, u32 text_size) timing->RepeatInterval = SDP_MakeSeconds(comp); pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, 3000); timing->ActiveDuration = SDP_MakeSeconds(comp); - while (1) { + while (pos>=0) { pos = gf_token_get(LineBuf, pos, " \t\r\n", comp, 3000); if (pos <= 0) break; timing->OffsetFromStart[timing->NbRepeatOffsets] = SDP_MakeSeconds(comp); @@ -723,8 +718,9 @@ GF_Err gf_sdp_info_parse(GF_SDPInfo *sdp, char *sdp_text, u32 text_size) } +#if 0 //unused -GF_Err SDP_CheckConnection(GF_SDPConnection *conn) +static GF_Err SDP_CheckConnection(GF_SDPConnection *conn) { if (!conn) return GF_BAD_PARAM; if (!conn->host || !conn->add_type || !conn->net_type) return GF_REMOTE_SERVICE_ERROR; @@ -739,7 +735,6 @@ GF_Err SDP_CheckConnection(GF_SDPConnection *conn) //return GF_BAD_PARAM if invalid structure, GF_REMOTE_SERVICE_ERROR if bad formatting //or GF_OK -GF_EXPORT GF_Err gf_sdp_info_check(GF_SDPInfo *sdp) { GF_Err e; @@ -870,7 +865,6 @@ GF_Err gf_sdp_info_check(GF_SDPInfo *sdp) SDP_WRITE_ALLOC_STR("\r\n", 0); \ } -GF_EXPORT GF_Err gf_sdp_info_write(GF_SDPInfo *sdp, char **out_str_buf) { char *buf; @@ -1165,5 +1159,7 @@ GF_Err gf_sdp_info_write(GF_SDPInfo *sdp, char **out_str_buf) *out_str_buf = buf; return GF_OK; } +#endif + #endif /*GPAC_DISABLE_STREAMING*/ diff --git a/src/isomedia/avc_ext.c b/src/isomedia/avc_ext.c index 933ef5a..ca55d02 100644 --- a/src/isomedia/avc_ext.c +++ b/src/isomedia/avc_ext.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -25,10 +25,7 @@ #include #include - -#ifndef GPAC_DISABLE_AV_PARSERS #include -#endif #ifndef GPAC_DISABLE_ISOM @@ -36,26 +33,30 @@ Bool gf_isom_is_nalu_based_entry(GF_MediaBox *mdia, GF_SampleEntryBox *_entry) { GF_MPEGVisualSampleEntryBox *entry; - if (mdia->handler->handlerType != GF_ISOM_MEDIA_VISUAL) return GF_FALSE; + if (!gf_isom_is_video_handler_type(mdia->handler->handlerType)) + return GF_FALSE; + if (!_entry) return GF_FALSE; + entry = (GF_MPEGVisualSampleEntryBox*)_entry; + switch (_entry->type) { - case GF_4CC('a','v','c','1'): - case GF_4CC('a','v','c','2'): - case GF_4CC('a','v','c','3'): - case GF_4CC('a','v','c','4'): - case GF_4CC('s','v','c','1'): - case GF_4CC('s','v','c','2'): - case GF_4CC('m','v','c','1'): - case GF_4CC('m','v','c','2'): - case GF_4CC('h','v','c','1'): - case GF_4CC('h','e','v','1'): - case GF_4CC('h','v','c','2'): - case GF_4CC('h','e','v','2'): - case GF_4CC('l','h','v','1'): - case GF_4CC('l','h','e','1'): - case GF_4CC('m','h','v','1'): - case GF_4CC('m','h','c','1'): - case GF_4CC('h','v','t','1'): - case GF_4CC('l','h','t','1'): + case GF_ISOM_BOX_TYPE_AVC1: + case GF_ISOM_BOX_TYPE_AVC2: + case GF_ISOM_BOX_TYPE_AVC3: + case GF_ISOM_BOX_TYPE_AVC4: + case GF_ISOM_BOX_TYPE_SVC1: + case GF_ISOM_BOX_TYPE_SVC2: + case GF_ISOM_BOX_TYPE_MVC1: + case GF_ISOM_BOX_TYPE_MVC2: + case GF_ISOM_BOX_TYPE_HVC1: + case GF_ISOM_BOX_TYPE_HEV1: + case GF_ISOM_BOX_TYPE_HVC2: + case GF_ISOM_BOX_TYPE_HEV2: + case GF_ISOM_BOX_TYPE_LHV1: + case GF_ISOM_BOX_TYPE_LHE1: + case GF_ISOM_BOX_TYPE_MHV1: + case GF_ISOM_BOX_TYPE_MHC1: + case GF_ISOM_BOX_TYPE_HVT1: + case GF_ISOM_BOX_TYPE_LHT1: return GF_TRUE; case GF_ISOM_BOX_TYPE_GNRV: case GF_ISOM_BOX_TYPE_GNRA: @@ -64,9 +65,23 @@ Bool gf_isom_is_nalu_based_entry(GF_MediaBox *mdia, GF_SampleEntryBox *_entry) default: break; } - entry = (GF_MPEGVisualSampleEntryBox*)_entry; - if (!entry) return GF_FALSE; - if (entry->avc_config || entry->svc_config || entry->mvc_config || entry->hevc_config || entry->lhvc_config) return GF_TRUE; + + if (!gf_isom_is_video_handler_type(entry->internal_type)) + return GF_FALSE; + + if (entry->avc_config || entry->svc_config || entry->mvc_config || entry->hevc_config || entry->lhvc_config) { + GF_ProtectionSchemeInfoBox *schi = (GF_ProtectionSchemeInfoBox *) gf_isom_box_find_child(entry->child_boxes, GF_ISOM_BOX_TYPE_SINF); + if (!schi || !schi->scheme_type) return GF_TRUE; + switch (schi->scheme_type->scheme_type) { + case GF_ISOM_CENC_SCHEME: + case GF_ISOM_CBC_SCHEME: + case GF_ISOM_CENS_SCHEME: + case GF_ISOM_CBCS_SCHEME: + return GF_TRUE; + default: + break; + } + } return GF_FALSE; } @@ -83,45 +98,52 @@ static void rewrite_nalus_list(GF_List *nalus, GF_BitStream *bs, Bool rewrite_st } -static GF_Err process_extractor(GF_ISOFile *file, GF_MediaBox *mdia, u32 sampleNumber, u64 sampleDTS, u32 nal_size, u16 nal_hdr, u32 nal_unit_size_field, Bool is_hevc, Bool rewrite_ps, Bool rewrite_start_codes, GF_BitStream *src_bs, GF_BitStream *dst_bs, u32 extractor_mode) +static GF_Err process_extractor(GF_ISOFile *file, GF_MediaBox *mdia, u32 sampleNumber, u64 sampleDTS, u32 nal_size, u16 nal_hdr, u32 nal_unit_size_field, Bool is_hevc, Bool rewrite_ps, Bool rewrite_start_codes, u32 extractor_mode) { GF_Err e; u32 di, ref_track_index, ref_track_num, data_offset, data_length, cur_extract_mode, ref_extract_mode, ref_nalu_size, nb_bytes_nalh; GF_TrackReferenceTypeBox *dpnd; - GF_ISOSample *ref_samp; - GF_BitStream *ref_bs; GF_TrackBox *ref_trak; s8 sample_offset; - char*buffer = NULL; - u32 max_size = 0; u32 last_byte, ref_sample_num, prev_ref_sample_num; - + Bool header_written = GF_FALSE; nb_bytes_nalh = is_hevc ? 2 : 1; switch (extractor_mode) { case 0: - last_byte = (u32) gf_bs_get_position(src_bs) + nal_size - (is_hevc ? 2 : 1); - if (!is_hevc) gf_bs_read_int(src_bs, 24); //1 byte for HEVC , 3 bytes for AVC of NALUHeader in extractor - while (gf_bs_get_position(src_bs) < last_byte) { + last_byte = (u32) gf_bs_get_position(mdia->nalu_parser) + nal_size - (is_hevc ? 2 : 1); + if (!is_hevc) gf_bs_read_int(mdia->nalu_parser, 24); //1 byte for HEVC , 3 bytes for AVC of NALUHeader in extractor + while (gf_bs_get_position(mdia->nalu_parser) < last_byte) { u32 xmode = 0; //hevc extractors use constructors - if (is_hevc) xmode = gf_bs_read_u8(src_bs); + if (is_hevc) xmode = gf_bs_read_u8(mdia->nalu_parser); if (xmode) { - u8 len = gf_bs_read_u8(src_bs); - while (len) { - u8 c = gf_bs_read_u8(src_bs); - gf_bs_write_u8(dst_bs, c); - len--; + u8 done=0, len = gf_bs_read_u8(mdia->nalu_parser); + while (donenalu_parser); + done++; + if (header_written) { + gf_bs_write_u8(mdia->nalu_out_bs, c); + } else if (done==nal_unit_size_field) { + if (rewrite_start_codes) { + gf_bs_write_int(mdia->nalu_out_bs, 1, 32); + } else { + gf_bs_write_u8(mdia->nalu_out_bs, c); + } + header_written = GF_TRUE; + } else if (!rewrite_start_codes) { + gf_bs_write_u8(mdia->nalu_out_bs, c); + } } continue; } - - ref_track_index = gf_bs_read_u8(src_bs); - sample_offset = (s8) gf_bs_read_int(src_bs, 8); - data_offset = gf_bs_read_int(src_bs, nal_unit_size_field*8); - data_length = gf_bs_read_int(src_bs, nal_unit_size_field*8); - Track_FindRef(mdia->mediaTrack, GF_4CC('s','c','a','l'), &dpnd); + ref_track_index = gf_bs_read_u8(mdia->nalu_parser); + sample_offset = (s8) gf_bs_read_int(mdia->nalu_parser, 8); + data_offset = gf_bs_read_int(mdia->nalu_parser, nal_unit_size_field*8); + data_length = gf_bs_read_int(mdia->nalu_parser, nal_unit_size_field*8); + + Track_FindRef(mdia->mediaTrack, GF_ISOM_REF_SCAL, &dpnd); ref_track_num = 0; if (dpnd && ref_track_index && (ref_track_index<=dpnd->trackIDCount)) ref_track_num = gf_isom_get_track_by_id(file, dpnd->trackIDs[ref_track_index-1]); @@ -141,8 +163,14 @@ static GF_Err process_extractor(GF_ISOFile *file, GF_MediaBox *mdia, u32 sampleN ref_trak = gf_isom_get_track_from_file(file, ref_track_num); if (!ref_trak) return GF_ISOM_INVALID_FILE; - ref_samp = gf_isom_sample_new(); - if (!ref_samp) return GF_IO_ERR; + if (!mdia->extracted_samp) { + mdia->extracted_samp = gf_isom_sample_new(); + if (!mdia->extracted_samp) return GF_IO_ERR; + } + if (!mdia->extracted_bs) { + mdia->extracted_bs = gf_bs_new("a", 1, GF_BITSTREAM_READ); + if (!mdia->extracted_bs) return GF_IO_ERR; + } e = stbl_findEntryForTime(ref_trak->Media->information->sampleTable, sampleDTS, 0, &ref_sample_num, &prev_ref_sample_num); if (e) return e; @@ -151,10 +179,12 @@ static GF_Err process_extractor(GF_ISOFile *file, GF_MediaBox *mdia, u32 sampleN if ((sample_offset<0) && (ref_sample_num > (u32) -sample_offset)) return GF_ISOM_INVALID_FILE; ref_sample_num = (u32) ( (s32) ref_sample_num + sample_offset); - e = Media_GetSample(ref_trak->Media, ref_sample_num, &ref_samp, &di, GF_FALSE, NULL); + e = Media_GetSample(ref_trak->Media, ref_sample_num, &mdia->extracted_samp, &di, GF_FALSE, NULL); if (e) return e; - - if (rewrite_start_codes) { + if (!mdia->extracted_samp->alloc_size) + mdia->extracted_samp->alloc_size = mdia->extracted_samp->dataLength; +#if 0 + if (!header_written && rewrite_start_codes) { gf_bs_write_int(dst_bs, 1, 32); if (is_hevc) { gf_bs_write_int(dst_bs, 0, 1); @@ -168,59 +198,68 @@ static GF_Err process_extractor(GF_ISOFile *file, GF_MediaBox *mdia, u32 sampleN gf_bs_write_int(dst_bs, 0xF0 , 8); /*7 "all supported NALUs" (=111) + rbsp trailing (10000)*/; } } - ref_bs = gf_bs_new(ref_samp->data + data_offset, ref_samp->dataLength - data_offset, GF_BITSTREAM_READ); - if (!data_length) - data_length = ref_samp->dataLength - data_offset; +#endif + gf_bs_reassign_buffer(mdia->extracted_bs, mdia->extracted_samp->data + data_offset, mdia->extracted_samp->dataLength - data_offset); - if (ref_samp->dataLength - data_offset >= data_length) { + if (mdia->extracted_samp->dataLength - data_offset >= data_length) { - while (data_length && gf_bs_available(ref_bs)) { - ref_nalu_size = gf_bs_read_int(ref_bs, 8*nal_unit_size_field); - assert(ref_nalu_size <= data_length); + while (data_length && gf_bs_available(mdia->extracted_bs)) { + if (!header_written) { + ref_nalu_size = gf_bs_read_int(mdia->extracted_bs, 8*nal_unit_size_field); - if (ref_nalu_size > max_size) { - buffer = (char*) gf_realloc(buffer, sizeof(char) * ref_nalu_size ); - max_size = ref_nalu_size; + assert(data_length>nal_unit_size_field); + data_length -= nal_unit_size_field; + if (data_length > gf_bs_available(mdia->extracted_bs)) { + data_length = (u32)gf_bs_available(mdia->extracted_bs); + } + } else { + ref_nalu_size = data_length; } - gf_bs_read_data(ref_bs, buffer, ref_nalu_size); - if (rewrite_start_codes) - gf_bs_write_u32(dst_bs, 1); - else - gf_bs_write_int(dst_bs, ref_nalu_size, 8*nal_unit_size_field); + if (ref_nalu_size > mdia->tmp_nal_copy_buffer_alloc) { + mdia->tmp_nal_copy_buffer_alloc = ref_nalu_size; + mdia->tmp_nal_copy_buffer = (char*) gf_realloc(mdia->tmp_nal_copy_buffer, sizeof(char) * ref_nalu_size ); + } + gf_bs_read_data(mdia->extracted_bs, mdia->tmp_nal_copy_buffer, ref_nalu_size); + + if (!header_written) { + if (rewrite_start_codes) + gf_bs_write_u32(mdia->nalu_out_bs, 1); + else + gf_bs_write_int(mdia->nalu_out_bs, ref_nalu_size, 8*nal_unit_size_field); + } + assert(data_length >= ref_nalu_size); + gf_bs_write_data(mdia->nalu_out_bs, mdia->tmp_nal_copy_buffer, ref_nalu_size); + data_length -= ref_nalu_size; + + header_written = GF_FALSE; - gf_bs_write_data(dst_bs, buffer, ref_nalu_size); - data_length -= ref_nalu_size + nal_unit_size_field; } } else { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("ISOBMF: Extractor size is larger than refered sample size - skipping.\n")); } - gf_isom_sample_del(&ref_samp); - ref_samp = NULL; - gf_bs_del(ref_bs); - ref_bs = NULL; - if (buffer) gf_free(buffer); - buffer = NULL; gf_isom_set_nalu_extract_mode(file, ref_track_num, cur_extract_mode); - + if (!is_hevc) break; } break; case 1: //skip to end of this NALU - gf_bs_skip_bytes(src_bs, nal_size - nb_bytes_nalh); + gf_bs_skip_bytes(mdia->nalu_parser, nal_size - nb_bytes_nalh); break; case 2: - buffer = (char*) gf_malloc( sizeof(char) * (nal_size - nb_bytes_nalh)); - gf_bs_read_data(src_bs, buffer, nal_size - nb_bytes_nalh); + if (nal_size - nb_bytes_nalh > mdia->tmp_nal_copy_buffer_alloc) { + mdia->tmp_nal_copy_buffer_alloc = nal_size - nb_bytes_nalh; + mdia->tmp_nal_copy_buffer = (char*) gf_realloc(mdia->tmp_nal_copy_buffer, sizeof(char) * (nal_size - nb_bytes_nalh) ); + } + gf_bs_read_data(mdia->nalu_parser, mdia->tmp_nal_copy_buffer, nal_size - nb_bytes_nalh); if (rewrite_start_codes) - gf_bs_write_u32(dst_bs, 1); + gf_bs_write_u32(mdia->nalu_out_bs, 1); else - gf_bs_write_int(dst_bs, nal_size, 8*nal_unit_size_field); + gf_bs_write_int(mdia->nalu_out_bs, nal_size, 8*nal_unit_size_field); - gf_bs_write_u8(dst_bs, nal_hdr); - gf_bs_write_data(dst_bs, buffer, nal_size - nb_bytes_nalh); - gf_free(buffer); + gf_bs_write_u8(mdia->nalu_out_bs, nal_hdr); + gf_bs_write_data(mdia->nalu_out_bs, mdia->tmp_nal_copy_buffer, nal_size - nb_bytes_nalh); break; } return GF_OK; @@ -228,7 +267,7 @@ static GF_Err process_extractor(GF_ISOFile *file, GF_MediaBox *mdia, u32 sampleN #ifndef GPAC_DISABLE_HEVC /* returns the SAP type as defined in the 14496-12 specification */ -static SAPType sap_type_from_nal_type(u8 nal_type) { +static GF_ISOSAPType sap_type_from_nal_type(u8 nal_type) { switch (nal_type) { case GF_HEVC_NALU_SLICE_CRA: return SAP_TYPE_3; @@ -245,11 +284,10 @@ static SAPType sap_type_from_nal_type(u8 nal_type) { } #endif -static SAPType is_sample_idr(GF_ISOSample *sample, GF_MPEGVisualSampleEntryBox *entry) +static GF_ISOSAPType is_sample_idr(GF_MediaBox *mdia, GF_ISOSample *sample, GF_MPEGVisualSampleEntryBox *entry) { Bool is_hevc = GF_FALSE; u32 nalu_size_field = 0; - GF_BitStream *bs; if (entry->avc_config && entry->avc_config->config) nalu_size_field = entry->avc_config->config->nal_unit_size; else if (entry->svc_config && entry->svc_config->config) nalu_size_field = entry->svc_config->config->nal_unit_size; else if (entry->mvc_config && entry->mvc_config->config) nalu_size_field = entry->mvc_config->config->nal_unit_size; @@ -263,30 +301,31 @@ static SAPType is_sample_idr(GF_ISOSample *sample, GF_MPEGVisualSampleEntryBox * } if (!nalu_size_field) return RAP_NO; - bs = gf_bs_new(sample->data, sample->dataLength, GF_BITSTREAM_READ); - if (!bs) return RAP_NO; + if (!mdia->nalu_parser) + mdia->nalu_parser = gf_bs_new(sample->data, sample->dataLength, GF_BITSTREAM_READ); + else + gf_bs_reassign_buffer(mdia->nalu_parser, sample->data, sample->dataLength); + + if (!mdia->nalu_parser) return RAP_NO; - while (gf_bs_available(bs)) { + while (gf_bs_available(mdia->nalu_parser)) { u8 nal_type; - u32 size = gf_bs_read_int(bs, 8*nalu_size_field); + u32 size = gf_bs_read_int(mdia->nalu_parser, 8*nalu_size_field); if (is_hevc) { #ifndef GPAC_DISABLE_HEVC - u16 nal_hdr = gf_bs_read_u16(bs); + u16 nal_hdr = gf_bs_read_u16(mdia->nalu_parser); nal_type = (nal_hdr&0x7E00) >> 9; switch (nal_type) { case GF_HEVC_NALU_SLICE_CRA: - gf_bs_del(bs); return SAP_TYPE_3; case GF_HEVC_NALU_SLICE_IDR_N_LP: case GF_HEVC_NALU_SLICE_BLA_N_LP: - gf_bs_del(bs); return SAP_TYPE_1; case GF_HEVC_NALU_SLICE_IDR_W_DLP: case GF_HEVC_NALU_SLICE_BLA_W_DLP: case GF_HEVC_NALU_SLICE_BLA_W_LP: - gf_bs_del(bs); return SAP_TYPE_2; case GF_HEVC_NALU_ACCESS_UNIT: case GF_HEVC_NALU_FILLER_DATA: @@ -296,13 +335,12 @@ static SAPType is_sample_idr(GF_ISOSample *sample, GF_MPEGVisualSampleEntryBox * case GF_HEVC_NALU_PIC_PARAM: break; default: - gf_bs_del(bs); return RAP_NO; } - gf_bs_skip_bytes(bs, size - 2); + gf_bs_skip_bytes(mdia->nalu_parser, size - 2); #endif } else { - u8 nal_hdr = gf_bs_read_u8(bs); + u8 nal_hdr = gf_bs_read_u8(mdia->nalu_parser); nal_type = nal_hdr & 0x1F; switch (nal_type) { @@ -310,25 +348,23 @@ static SAPType is_sample_idr(GF_ISOSample *sample, GF_MPEGVisualSampleEntryBox * case GF_AVC_NALU_PIC_PARAM: case GF_AVC_NALU_SEQ_PARAM_EXT: case GF_AVC_NALU_SVC_SUBSEQ_PARAM: - */ case GF_AVC_NALU_IDR_SLICE: - gf_bs_del(bs); + */ + case GF_AVC_NALU_IDR_SLICE: return SAP_TYPE_1; case GF_AVC_NALU_ACCESS_UNIT: case GF_AVC_NALU_FILLER_DATA: case GF_AVC_NALU_SEI: break; default: - gf_bs_del(bs); return RAP_NO; } - gf_bs_skip_bytes(bs, size - 1); + gf_bs_skip_bytes(mdia->nalu_parser, size - 1); } } - gf_bs_del(bs); return RAP_NO; } -static void nalu_merge_ps(GF_BitStream *ps_bs, Bool rewrite_start_codes, u32 nal_unit_size_field, GF_MPEGVisualSampleEntryBox *entry, Bool is_hevc) +static void nalu_merge_ps(GF_BitStream *ps_bs, Bool rewrite_start_codes, u32 nal_unit_size_field, GF_MPEGVisualSampleEntryBox *entry, Bool is_hevc, Bool *has_vps) { u32 i, count; if (is_hevc) { @@ -336,6 +372,10 @@ static void nalu_merge_ps(GF_BitStream *ps_bs, Bool rewrite_start_codes, u32 nal count = gf_list_count(entry->hevc_config->config->param_array); for (i=0; ihevc_config->config->param_array, i); + if (ar->type == GF_HEVC_NALU_VID_PARAM) { + if (! *has_vps) *has_vps = GF_TRUE; + else continue; + } rewrite_nalus_list(ar->nalus, ps_bs, rewrite_start_codes, nal_unit_size_field); } } @@ -343,6 +383,10 @@ static void nalu_merge_ps(GF_BitStream *ps_bs, Bool rewrite_start_codes, u32 nal count = gf_list_count(entry->lhvc_config->config->param_array); for (i=0; ilhvc_config->config->param_array, i); + if (ar->type == GF_HEVC_NALU_VID_PARAM) { + if (! *has_vps) *has_vps = GF_TRUE; + else continue; + } rewrite_nalus_list(ar->nalus, ps_bs, rewrite_start_codes, nal_unit_size_field); } } @@ -374,23 +418,19 @@ GF_Err gf_isom_nalu_sample_rewrite(GF_MediaBox *mdia, GF_ISOSample *sample, u32 // Bool check_cra_bla = (mdia->information->sampleTable->SyncSample && mdia->information->sampleTable->SyncSample->nb_entries>1) ? 0 : 1; Bool check_cra_bla = GF_TRUE; Bool insert_nalu_delim = GF_TRUE; + Bool force_sei_inspect = GF_FALSE; GF_Err e = GF_OK; - GF_ISOSample *ref_samp; - GF_BitStream *src_bs, *ref_bs, *dst_bs, *ps_bs; - u32 nal_size, max_size, nal_unit_size_field, extractor_mode; + GF_BitStream *sei_suffix_bs = NULL; + Bool ps_transfered = GF_FALSE; + u32 nal_size, nal_unit_size_field, extractor_mode; Bool rewrite_ps, rewrite_start_codes, insert_vdrd_code; - s8 nal_type; + u8 nal_type; u32 nal_hdr, sabt_ref, i, track_num; u32 temporal_id = 0; - char *buffer; GF_ISOFile *file = mdia->mediaTrack->moov->mov; GF_TrackReferenceTypeBox *scal = NULL; - src_bs = ref_bs = dst_bs = ps_bs = NULL; - ref_samp = NULL; - buffer = NULL; - - Track_FindRef(mdia->mediaTrack, GF_4CC('s','c','a','l'), &scal); + Track_FindRef(mdia->mediaTrack, GF_ISOM_REF_SCAL, &scal); rewrite_ps = (mdia->mediaTrack->extractor_mode & GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG) ? GF_TRUE : GF_FALSE; rewrite_start_codes = (mdia->mediaTrack->extractor_mode & GF_ISOM_NALU_EXTRACT_ANNEXB_FLAG) ? GF_TRUE : GF_FALSE; @@ -407,12 +447,11 @@ GF_Err gf_isom_nalu_sample_rewrite(GF_MediaBox *mdia, GF_ISOSample *sample, u32 if ( (extractor_mode != GF_ISOM_NALU_EXTRACT_INSPECT) && !(mdia->mediaTrack->extractor_mode & GF_ISOM_NALU_EXTRACT_TILE_ONLY) ) { u32 ref_track, di; //aggregate all sabt samples with the same DTS - - if (entry->lhvc_config && !entry->hevc_config) { - GF_ISOSample *base_samp; + if (entry->lhvc_config && !entry->hevc_config && !(mdia->mediaTrack->extractor_mode & GF_ISOM_NALU_EXTRACT_LAYER_ONLY)) { if (gf_isom_get_reference_count(mdia->mediaTrack->moov->mov, track_num, GF_ISOM_REF_SCAL) <= 0) { //FIXME - for now we only support two layers (base + enh) in implicit if ( gf_isom_get_reference_count(mdia->mediaTrack->moov->mov, track_num, GF_ISOM_REF_BASE) >= 1) { + GF_ISOSample *base_samp; gf_isom_get_reference(mdia->mediaTrack->moov->mov, track_num, GF_ISOM_REF_BASE, 1, &ref_track); switch (gf_isom_get_media_subtype(mdia->mediaTrack->moov->mov , ref_track, 1)) { case GF_ISOM_SUBTYPE_HVC1: @@ -420,15 +459,22 @@ GF_Err gf_isom_nalu_sample_rewrite(GF_MediaBox *mdia, GF_ISOSample *sample, u32 case GF_ISOM_SUBTYPE_HEV1: case GF_ISOM_SUBTYPE_HEV2: - base_samp = gf_isom_get_sample(mdia->mediaTrack->moov->mov, ref_track, sampleNumber + mdia->mediaTrack->sample_count_at_seg_start, &di); + if (!mdia->extracted_samp) { + mdia->extracted_samp = gf_isom_sample_new(); + if (!mdia->extracted_samp) return GF_OUT_OF_MEM; + } + + base_samp = gf_isom_get_sample_ex(mdia->mediaTrack->moov->mov, ref_track, sampleNumber + mdia->mediaTrack->sample_count_at_seg_start, &di, mdia->extracted_samp, NULL); if (base_samp && base_samp->data) { - sample->data = gf_realloc(sample->data, sample->dataLength+base_samp->dataLength); + if (!sample->alloc_size || (sample->alloc_sizedataLength+base_samp->dataLength) ) { + sample->data = gf_realloc(sample->data, sample->dataLength+base_samp->dataLength); + if (sample->alloc_size) sample->alloc_size = sample->dataLength+base_samp->dataLength; + } memmove(sample->data + base_samp->dataLength, sample->data , sample->dataLength); memcpy(sample->data, base_samp->data, base_samp->dataLength); sample->dataLength += base_samp->dataLength; } - if (base_samp) gf_isom_sample_del(&base_samp); - Track_FindRef(mdia->mediaTrack, GF_4CC('s','b','a','s'), &scal); + Track_FindRef(mdia->mediaTrack, GF_ISOM_REF_BASE, &scal); break; } } @@ -436,34 +482,43 @@ GF_Err gf_isom_nalu_sample_rewrite(GF_MediaBox *mdia, GF_ISOSample *sample, u32 } sabt_ref = gf_isom_get_reference_count(mdia->mediaTrack->moov->mov, track_num, GF_ISOM_REF_SABT); - if ((s32) sabt_ref >= 0) { + if ((s32) sabt_ref > 0) { + force_sei_inspect = GF_TRUE; for (i=0; imediaTrack->moov->mov, track_num, GF_ISOM_REF_SABT, i+1, &ref_track); - tile_samp = gf_isom_get_sample(mdia->mediaTrack->moov->mov, ref_track, sampleNumber + mdia->mediaTrack->sample_count_at_seg_start, &di); + + if (!mdia->extracted_samp) { + mdia->extracted_samp = gf_isom_sample_new(); + if (!mdia->extracted_samp) return GF_OUT_OF_MEM; + } + + tile_samp = gf_isom_get_sample_ex(mdia->mediaTrack->moov->mov, ref_track, sampleNumber + mdia->mediaTrack->sample_count_at_seg_start, &di, mdia->extracted_samp, NULL); if (tile_samp && tile_samp ->data) { - sample->data = gf_realloc(sample->data, sample->dataLength+tile_samp->dataLength); + if (!sample->alloc_size || (sample->alloc_sizedataLength+tile_samp->dataLength) ) { + sample->data = gf_realloc(sample->data, sample->dataLength+tile_samp->dataLength); + if (sample->alloc_size) sample->alloc_size = sample->dataLength+tile_samp->dataLength; + } memcpy(sample->data + sample->dataLength, tile_samp->data, tile_samp->dataLength); sample->dataLength += tile_samp->dataLength; } - if (tile_samp) gf_isom_sample_del(&tile_samp); } } } if ( gf_isom_get_reference_count(mdia->mediaTrack->moov->mov, track_num, GF_ISOM_REF_TBAS) >= 1) { u32 ref_track; - u32 idx = gf_list_find(mdia->information->sampleTable->SampleDescription->other_boxes, entry); + u32 idx = gf_list_find(mdia->information->sampleTable->SampleDescription->child_boxes, entry); GF_TrackBox *tbas; gf_isom_get_reference(mdia->mediaTrack->moov->mov, track_num, GF_ISOM_REF_TBAS, 1, &ref_track); tbas = (GF_TrackBox *)gf_list_get(mdia->mediaTrack->moov->trackList, ref_track-1); - entry = gf_list_get(tbas->Media->information->sampleTable->SampleDescription->other_boxes, idx); + entry = gf_list_get(tbas->Media->information->sampleTable->SampleDescription->child_boxes, idx); } if (sample->IsRAP < SAP_TYPE_2) { if (mdia->information->sampleTable->no_sync_found || (!sample->IsRAP && check_cra_bla) ) { - sample->IsRAP = is_sample_idr(sample, entry); + sample->IsRAP = is_sample_idr(mdia, sample, entry); } } if (!sample->IsRAP) @@ -472,6 +527,8 @@ GF_Err gf_isom_nalu_sample_rewrite(GF_MediaBox *mdia, GF_ISOSample *sample, u32 if (extractor_mode != GF_ISOM_NALU_EXTRACT_LAYER_ONLY) insert_vdrd_code = GF_FALSE; + if (!entry) return GF_BAD_PARAM; + //this is a compatible HEVC, don't insert VDRD, insert NALU delim if (entry->lhvc_config && entry->hevc_config) insert_vdrd_code = GF_FALSE; @@ -481,7 +538,6 @@ GF_Err gf_isom_nalu_sample_rewrite(GF_MediaBox *mdia, GF_ISOSample *sample, u32 return GF_OK; } - if (!entry) return GF_BAD_PARAM; nal_unit_size_field = 0; /*if svc rewrite*/ if (entry->svc_config && entry->svc_config->config) @@ -497,25 +553,53 @@ GF_Err gf_isom_nalu_sample_rewrite(GF_MediaBox *mdia, GF_ISOSample *sample, u32 } /*otherwise do nothing*/ - else if (!rewrite_ps && !rewrite_start_codes && !scal) { + else if (!rewrite_ps && !rewrite_start_codes && !scal && !force_sei_inspect) { return GF_OK; } if (!nal_unit_size_field) { - if (entry->avc_config) nal_unit_size_field = entry->avc_config->config->nal_unit_size; - else if (entry->hevc_config || entry->lhvc_config ) { - nal_unit_size_field = entry->lhvc_config ? entry->lhvc_config->config->nal_unit_size : entry->hevc_config->config->nal_unit_size; + if (entry->avc_config && entry->avc_config->config) + nal_unit_size_field = entry->avc_config->config->nal_unit_size; + else if (entry->lhvc_config && entry->lhvc_config->config) { + nal_unit_size_field = entry->lhvc_config->config->nal_unit_size; + is_hevc = GF_TRUE; + } + else if (entry->hevc_config && entry->hevc_config->config) { + nal_unit_size_field = entry->hevc_config->config->nal_unit_size; is_hevc = GF_TRUE; } } if (!nal_unit_size_field) return GF_ISOM_INVALID_FILE; - dst_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - ps_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); - src_bs = gf_bs_new(sample->data, sample->dataLength, GF_BITSTREAM_READ); - if (!src_bs && sample->data) return GF_ISOM_INVALID_FILE; - max_size = 4096; + //setup PS rewriter + if (!mdia->nalu_ps_bs) + mdia->nalu_ps_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + gf_bs_seek(mdia->nalu_ps_bs, 0); + + //setup sample reader + if (mdia->in_sample_buffer_allocdataLength) { + mdia->in_sample_buffer_alloc = sample->dataLength; + mdia->in_sample_buffer = gf_realloc(mdia->in_sample_buffer, sample->dataLength); + } + memcpy(mdia->in_sample_buffer, sample->data, sample->dataLength); + + if (!mdia->nalu_parser) { + mdia->nalu_parser = gf_bs_new(mdia->in_sample_buffer, sample->dataLength, GF_BITSTREAM_READ); + if (!mdia->nalu_parser && sample->data) return GF_ISOM_INVALID_FILE; + } else { + e = gf_bs_reassign_buffer(mdia->nalu_parser, mdia->in_sample_buffer, sample->dataLength); + if (e) return e; + } + //setup ouput + if (!mdia->nalu_out_bs) { + u8 *output; + u32 outSize; + mdia->nalu_out_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + gf_bs_get_content(mdia->nalu_out_bs, &output, &outSize); + } + + gf_bs_reassign_buffer(mdia->nalu_out_bs, sample->data, sample->alloc_size ? sample->alloc_size : sample->dataLength); /*rewrite start code with NALU delim*/ if (rewrite_start_codes) { @@ -523,60 +607,60 @@ GF_Err gf_isom_nalu_sample_rewrite(GF_MediaBox *mdia, GF_ISOSample *sample, u32 //we are SVC, don't write NALU delim, only insert VDRD NALU if (insert_vdrd_code) { if (is_hevc) { - //spec is not clear here, we insert a NALU AU delimiter before the layer starts + //spec is not clear here, we don't insert an NALU AU delimiter before the layer starts since it breaks openHEVC +// insert_nalu_delim=0; } else { - gf_bs_write_int(dst_bs, 1, 32); - gf_bs_write_int(dst_bs, GF_AVC_NALU_VDRD , 8); + gf_bs_write_int(mdia->nalu_out_bs, 1, 32); + gf_bs_write_int(mdia->nalu_out_bs, GF_AVC_NALU_VDRD , 8); insert_nalu_delim=0; } } //AVC/HEVC base, insert NALU delim if (insert_nalu_delim) { - gf_bs_write_int(dst_bs, 1, 32); + gf_bs_write_int(mdia->nalu_out_bs, 1, 32); if (is_hevc) { #ifndef GPAC_DISABLE_HEVC - gf_bs_write_int(dst_bs, 0, 1); - gf_bs_write_int(dst_bs, GF_HEVC_NALU_ACCESS_UNIT, 6); - gf_bs_write_int(dst_bs, insert_vdrd_code ? 1 : 0, 6); //we should pick the layerID of the following nalus ... - gf_bs_write_int(dst_bs, 1, 3); //nuh_temporal_id_plus1 - cannot be 0, we use 1 by default, and overwrite it if needed at the end + gf_bs_write_int(mdia->nalu_out_bs, 0, 1); + gf_bs_write_int(mdia->nalu_out_bs, GF_HEVC_NALU_ACCESS_UNIT, 6); + gf_bs_write_int(mdia->nalu_out_bs, insert_vdrd_code ? 1 : 0, 6); //we should pick the layerID of the following nalus ... + gf_bs_write_int(mdia->nalu_out_bs, 1, 3); //nuh_temporal_id_plus1 - cannot be 0, we use 1 by default, and overwrite it if needed at the end /*pic-type - by default we signal all slice types possible*/ - gf_bs_write_int(dst_bs, 2, 3); - gf_bs_write_int(dst_bs, 0, 5); + gf_bs_write_int(mdia->nalu_out_bs, 2, 3); + gf_bs_write_int(mdia->nalu_out_bs, 0, 5); #endif } else { - gf_bs_write_int(dst_bs, (sample->data[0] & 0x60) | GF_AVC_NALU_ACCESS_UNIT, 8); - gf_bs_write_int(dst_bs, 0xF0 , 8); /*7 "all supported NALUs" (=111) + rbsp trailing (10000)*/; + gf_bs_write_int(mdia->nalu_out_bs, (sample->data[0] & 0x60) | GF_AVC_NALU_ACCESS_UNIT, 8); + gf_bs_write_int(mdia->nalu_out_bs, 0xF0 , 8); /*7 "all supported NALUs" (=111) + rbsp trailing (10000)*/; } } } if (rewrite_ps) { + Bool has_vps = GF_FALSE; //in inspect mode or single-layer mode just use the xPS from this layer if (extractor_mode == GF_ISOM_NALU_EXTRACT_DEFAULT) { - u32 i; - if (scal) { for (i=0; itrackIDCount; i++) { GF_TrackBox *a_track = GetTrackbyID(mdia->mediaTrack->moov, scal->trackIDs[i]); GF_MPEGVisualSampleEntryBox *an_entry = NULL; if (a_track && a_track->Media && a_track->Media->information && a_track->Media->information->sampleTable && a_track->Media->information->sampleTable->SampleDescription) - an_entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(a_track->Media->information->sampleTable->SampleDescription->other_boxes, 0); + an_entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(a_track->Media->information->sampleTable->SampleDescription->child_boxes, 0); if (an_entry) - nalu_merge_ps(ps_bs, rewrite_start_codes, nal_unit_size_field, an_entry, is_hevc); + nalu_merge_ps(mdia->nalu_ps_bs, rewrite_start_codes, nal_unit_size_field, an_entry, is_hevc, &has_vps); } } } - nalu_merge_ps(ps_bs, rewrite_start_codes, nal_unit_size_field, entry, is_hevc); + nalu_merge_ps(mdia->nalu_ps_bs, rewrite_start_codes, nal_unit_size_field, entry, is_hevc, &has_vps); if (is_hevc) { /*little optimization if we are not asked to start codes: copy over the sample*/ if (!rewrite_start_codes && !entry->lhvc_config && !scal) { - if (ps_bs) { - u8 nal_type = (sample->data[nal_unit_size_field] & 0x7E) >> 1; + if (! ps_transfered) { + nal_type = (sample->data[nal_unit_size_field] & 0x7E) >> 1; //temp fix - if we detect xPS in the beginning of the sample do NOT copy the ps bitstream //this is not correct since we are not sure whether they are the same xPS or not, but it crashes openHEVC ... switch (nal_type) { @@ -587,77 +671,75 @@ GF_Err gf_isom_nalu_sample_rewrite(GF_MediaBox *mdia, GF_ISOSample *sample, u32 break; #endif default: - gf_bs_transfer(dst_bs, ps_bs); + gf_bs_transfer(mdia->nalu_out_bs, mdia->nalu_ps_bs, GF_TRUE); break; } - gf_bs_del(ps_bs); - ps_bs = NULL; } - gf_bs_write_data(dst_bs, sample->data, sample->dataLength); - gf_free(sample->data); - sample->data = NULL; - gf_bs_get_content(dst_bs, &sample->data, &sample->dataLength); - gf_bs_del(src_bs); - gf_bs_del(dst_bs); + gf_bs_write_data(mdia->nalu_out_bs, mdia->in_sample_buffer, sample->dataLength); + gf_bs_get_content_no_truncate(mdia->nalu_out_bs, &sample->data, &sample->dataLength, &sample->alloc_size); + return GF_OK; } } + } else { + ps_transfered = GF_TRUE; } /*little optimization if we are not asked to rewrite extractors or start codes: copy over the sample*/ - if (!scal && !rewrite_start_codes && !rewrite_ps) { - if (ps_bs) + if (!scal && !rewrite_start_codes && !rewrite_ps && !force_sei_inspect) { + if (! ps_transfered) { - gf_bs_transfer(dst_bs, ps_bs); - gf_bs_del(ps_bs); - ps_bs = NULL; - } - gf_bs_write_data(dst_bs, sample->data, sample->dataLength); - gf_free(sample->data); - sample->data = NULL; - gf_bs_get_content(dst_bs, &sample->data, &sample->dataLength); - gf_bs_del(src_bs); - gf_bs_del(dst_bs); + gf_bs_transfer(mdia->nalu_out_bs, mdia->nalu_ps_bs, GF_TRUE); + } + gf_bs_write_data(mdia->nalu_out_bs, mdia->in_sample_buffer, sample->dataLength); + gf_bs_get_content_no_truncate(mdia->nalu_out_bs, &sample->data, &sample->dataLength, &sample->alloc_size); return GF_OK; } - buffer = (char *)gf_malloc(sizeof(char)*max_size); + if (!mdia->tmp_nal_copy_buffer) { + mdia->tmp_nal_copy_buffer = gf_malloc(sizeof(char) * 4096); + mdia->tmp_nal_copy_buffer_alloc = 4096; + } + - while (gf_bs_available(src_bs)) { - nal_size = gf_bs_read_int(src_bs, 8*nal_unit_size_field); - if (gf_bs_get_position(src_bs) + nal_size > sample->dataLength) { + while (gf_bs_available(mdia->nalu_parser)) { + nal_size = gf_bs_read_int(mdia->nalu_parser, 8*nal_unit_size_field); + if (gf_bs_get_position(mdia->nalu_parser) + nal_size > sample->dataLength) { GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("Sample %u (size %u) rewrite: corrupted NAL Unit (size %u)\n", sampleNumber, sample->dataLength, nal_size)); goto exit; } - if (nal_size>max_size) { - buffer = (char*) gf_realloc(buffer, sizeof(char)*nal_size); - max_size = nal_size; + if (nal_size > mdia->tmp_nal_copy_buffer_alloc) { + mdia->tmp_nal_copy_buffer_alloc = nal_size; + mdia->tmp_nal_copy_buffer = (char*) gf_realloc(mdia->tmp_nal_copy_buffer, sizeof(char)*nal_size); } if (is_hevc) { - nal_hdr = gf_bs_read_u16(src_bs); + nal_hdr = gf_bs_read_u16(mdia->nalu_parser); nal_type = (nal_hdr&0x7E00) >> 9; } else { - nal_hdr = gf_bs_read_u8(src_bs); + nal_hdr = gf_bs_read_u8(mdia->nalu_parser); nal_type = nal_hdr & 0x1F; } if (is_hevc) { - if (ps_bs) { - gf_bs_transfer(dst_bs, ps_bs); - gf_bs_del(ps_bs); - ps_bs = NULL; +#ifndef GPAC_DISABLE_HEVC + GF_BitStream *write_to_bs = mdia->nalu_out_bs; +#endif + + if (!ps_transfered) { + gf_bs_transfer(mdia->nalu_out_bs, mdia->nalu_ps_bs, GF_TRUE); + ps_transfered = GF_TRUE; } #ifndef GPAC_DISABLE_HEVC /*we already wrote this stuff*/ if (nal_type==GF_HEVC_NALU_ACCESS_UNIT) { - gf_bs_skip_bytes(src_bs, nal_size-2); + gf_bs_skip_bytes(mdia->nalu_parser, nal_size-2); continue; } switch (nal_type) { //extractor case 49: - e = process_extractor(file, mdia, sampleNumber, sample->DTS, nal_size, nal_hdr, nal_unit_size_field, GF_TRUE, rewrite_ps, rewrite_start_codes, src_bs, dst_bs, extractor_mode); + e = process_extractor(file, mdia, sampleNumber, sample->DTS, nal_size, nal_hdr, nal_unit_size_field, GF_TRUE, rewrite_ps, rewrite_start_codes, extractor_mode); if (e) goto exit; break; @@ -668,14 +750,14 @@ GF_Err gf_isom_nalu_sample_rewrite(GF_MediaBox *mdia, GF_ISOSample *sample, u32 if (temporal_id < (nal_hdr & 0x7)) temporal_id = (nal_hdr & 0x7); /*rewrite nal*/ - gf_bs_read_data(src_bs, buffer, nal_size-2); + gf_bs_read_data(mdia->nalu_parser, mdia->tmp_nal_copy_buffer, nal_size-2); if (rewrite_start_codes) - gf_bs_write_u32(dst_bs, 1); + gf_bs_write_u32(mdia->nalu_out_bs, 1); else - gf_bs_write_int(dst_bs, nal_size, 8*nal_unit_size_field); + gf_bs_write_int(mdia->nalu_out_bs, nal_size, 8*nal_unit_size_field); - gf_bs_write_u16(dst_bs, nal_hdr); - gf_bs_write_data(dst_bs, buffer, nal_size-2); + gf_bs_write_u16(mdia->nalu_out_bs, nal_hdr); + gf_bs_write_data(mdia->nalu_out_bs, mdia->tmp_nal_copy_buffer, nal_size-2); break; case GF_HEVC_NALU_SLICE_BLA_W_LP: @@ -686,13 +768,8 @@ GF_Err gf_isom_nalu_sample_rewrite(GF_MediaBox *mdia, GF_ISOSample *sample, u32 case GF_HEVC_NALU_SLICE_CRA: //insert xPS before CRA/BLA if (check_cra_bla && !sample->IsRAP) { - if (ref_samp) gf_isom_sample_del(&ref_samp); - if (src_bs) gf_bs_del(src_bs); - if (ref_bs) gf_bs_del(ref_bs); - if (dst_bs) gf_bs_del(dst_bs); - if (buffer) gf_free(buffer); - sample->IsRAP = sap_type_from_nal_type(nal_type); + if (sei_suffix_bs) gf_bs_del(sei_suffix_bs); return gf_isom_nalu_sample_rewrite(mdia, sample, sampleNumber, entry); } default: @@ -702,14 +779,21 @@ GF_Err gf_isom_nalu_sample_rewrite(GF_MediaBox *mdia, GF_ISOSample *sample, u32 e = GF_NON_COMPLIANT_BITSTREAM; goto exit; } - gf_bs_read_data(src_bs, buffer, nal_size-2); + + gf_bs_read_data(mdia->nalu_parser, mdia->tmp_nal_copy_buffer, nal_size-2); + + if (nal_type==GF_HEVC_NALU_SEI_SUFFIX) { + if (!sei_suffix_bs) sei_suffix_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + write_to_bs = sei_suffix_bs; + } + if (rewrite_start_codes) - gf_bs_write_u32(dst_bs, 1); + gf_bs_write_u32(write_to_bs, 1); else - gf_bs_write_int(dst_bs, nal_size, 8*nal_unit_size_field); + gf_bs_write_int(write_to_bs, nal_size, 8*nal_unit_size_field); - gf_bs_write_u16(dst_bs, nal_hdr); - gf_bs_write_data(dst_bs, buffer, nal_size-2); + gf_bs_write_u16(write_to_bs, nal_hdr); + gf_bs_write_data(write_to_bs, mdia->tmp_nal_copy_buffer, nal_size-2); } #endif @@ -720,11 +804,11 @@ GF_Err gf_isom_nalu_sample_rewrite(GF_MediaBox *mdia, GF_ISOSample *sample, u32 switch(nal_type) { case GF_AVC_NALU_ACCESS_UNIT: /*we already wrote this stuff*/ - gf_bs_skip_bytes(src_bs, nal_size-1); + gf_bs_skip_bytes(mdia->nalu_parser, nal_size-1); continue; //extractor case 31: - e = process_extractor(file, mdia, sampleNumber, sample->DTS, nal_size, nal_hdr, nal_unit_size_field, GF_FALSE, rewrite_ps, rewrite_start_codes, src_bs, dst_bs, extractor_mode); + e = process_extractor(file, mdia, sampleNumber, sample->DTS, nal_size, nal_hdr, nal_unit_size_field, GF_FALSE, rewrite_ps, rewrite_start_codes, extractor_mode); if (e) goto exit; break; // case GF_AVC_NALU_SEI: @@ -733,31 +817,30 @@ GF_Err gf_isom_nalu_sample_rewrite(GF_MediaBox *mdia, GF_ISOSample *sample, u32 case GF_AVC_NALU_SEQ_PARAM_EXT: case GF_AVC_NALU_SVC_SUBSEQ_PARAM: // we will rewrite the sps/pps if and only if there is no sps/pps in bistream - if (ps_bs) { - gf_bs_del(ps_bs); - ps_bs = NULL; + if (!ps_transfered) { + ps_transfered = GF_TRUE; } default: - if (ps_bs) { - gf_bs_transfer(dst_bs, ps_bs); - gf_bs_del(ps_bs); - ps_bs = NULL; + if (!ps_transfered) { + gf_bs_transfer(mdia->nalu_out_bs, mdia->nalu_ps_bs, GF_TRUE); + ps_transfered = GF_TRUE; } - gf_bs_read_data(src_bs, buffer, nal_size-1); + gf_bs_read_data(mdia->nalu_parser, mdia->tmp_nal_copy_buffer, nal_size-1); if (rewrite_start_codes) - gf_bs_write_u32(dst_bs, 1); + gf_bs_write_u32(mdia->nalu_out_bs, 1); else - gf_bs_write_int(dst_bs, nal_size, 8*nal_unit_size_field); + gf_bs_write_int(mdia->nalu_out_bs, nal_size, 8*nal_unit_size_field); - gf_bs_write_u8(dst_bs, nal_hdr); - gf_bs_write_data(dst_bs, buffer, nal_size-1); + gf_bs_write_u8(mdia->nalu_out_bs, nal_hdr); + gf_bs_write_data(mdia->nalu_out_bs, mdia->tmp_nal_copy_buffer, nal_size-1); } } + if (sei_suffix_bs) { + gf_bs_transfer(mdia->nalu_out_bs, sei_suffix_bs, GF_FALSE); + } /*done*/ - gf_free(sample->data); - sample->data = NULL; - gf_bs_get_content(dst_bs, &sample->data, &sample->dataLength); + gf_bs_get_content_no_truncate(mdia->nalu_out_bs, &sample->data, &sample->dataLength, &sample->alloc_size); /*rewrite temporal ID of AU Ddelim NALU (first one)*/ if (rewrite_start_codes && is_hevc && temporal_id) { @@ -766,17 +849,15 @@ GF_Err gf_isom_nalu_sample_rewrite(GF_MediaBox *mdia, GF_ISOSample *sample, u32 exit: - if (ref_samp) gf_isom_sample_del(&ref_samp); - if (src_bs) gf_bs_del(src_bs); - if (ref_bs) gf_bs_del(ref_bs); - if (dst_bs) gf_bs_del(dst_bs); - if (buffer) gf_free(buffer); + if (sei_suffix_bs) + gf_bs_del(sei_suffix_bs); + return e; } GF_HEVCConfig *HEVC_DuplicateConfig(GF_HEVCConfig *cfg) { - char *data; + u8 *data; u32 data_size; GF_HEVCConfig *new_cfg; GF_BitStream *bs; @@ -800,7 +881,10 @@ static GF_AVCConfig *AVC_DuplicateConfig(GF_AVCConfig *cfg) { u32 i, count; GF_AVCConfigSlot *p1, *p2; - GF_AVCConfig *cfg_new = gf_odf_avc_cfg_new(); + GF_AVCConfig *cfg_new; + if (!cfg) + return NULL; + cfg_new = gf_odf_avc_cfg_new(); cfg_new->AVCLevelIndication = cfg->AVCLevelIndication; cfg_new->AVCProfileIndication = cfg->AVCProfileIndication; cfg_new->configurationVersion = cfg->configurationVersion; @@ -851,8 +935,10 @@ static GF_AVCConfig *AVC_DuplicateConfig(GF_AVCConfig *cfg) static void merge_avc_config(GF_AVCConfig *dst_cfg, GF_AVCConfig *src_cfg) { - GF_AVCConfig *cfg = AVC_DuplicateConfig(src_cfg); - if (!cfg || !dst_cfg) return; + GF_AVCConfig *cfg; + if (!src_cfg || !dst_cfg) return; + cfg = AVC_DuplicateConfig(src_cfg); + if (!cfg) return; while (gf_list_count(cfg->sequenceParameterSets)) { GF_AVCConfigSlot *p = (GF_AVCConfigSlot*)gf_list_get(cfg->sequenceParameterSets, 0); @@ -921,7 +1007,7 @@ void merge_all_config(GF_AVCConfig *avc_cfg, GF_HEVCConfig *hevc_cfg, GF_MediaBo { u32 i; GF_TrackReferenceTypeBox *scal = NULL; - Track_FindRef(mdia->mediaTrack, GF_4CC('s','c','a','l'), &scal); + Track_FindRef(mdia->mediaTrack, GF_ISOM_REF_SCAL, &scal); if (!scal) return; @@ -929,7 +1015,7 @@ void merge_all_config(GF_AVCConfig *avc_cfg, GF_HEVCConfig *hevc_cfg, GF_MediaBo GF_TrackBox *a_track = GetTrackbyID(mdia->mediaTrack->moov, scal->trackIDs[i]); GF_MPEGVisualSampleEntryBox *an_entry = NULL; if (a_track && a_track->Media && a_track->Media->information && a_track->Media->information->sampleTable && a_track->Media->information->sampleTable->SampleDescription) - an_entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(a_track->Media->information->sampleTable->SampleDescription->other_boxes, 0); + an_entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(a_track->Media->information->sampleTable->SampleDescription->child_boxes, 0); if (!an_entry) continue; @@ -954,7 +1040,6 @@ void merge_all_config(GF_AVCConfig *avc_cfg, GF_HEVCConfig *hevc_cfg, GF_MediaBo void AVC_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *avc, GF_MediaBox *mdia) { - GF_AVCConfig *avcc, *svcc, *mvcc; GF_BitRateBox *btrt = gf_isom_sample_entry_get_bitrate((GF_SampleEntryBox *)avc, GF_FALSE); if (avc->emul_esd) gf_odf_desc_del((GF_Descriptor *)avc->emul_esd); @@ -963,22 +1048,23 @@ void AVC_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *avc, GF_MediaBox *md /*AVC OTI is 0x21, AVC parameter set stream OTI (not supported in gpac) is 0x22, SVC OTI is 0x24*/ /*if we have only SVC stream, set objectTypeIndication to AVC OTI; else set it to AVC OTI*/ if (avc->svc_config && !avc->avc_config) - avc->emul_esd->decoderConfig->objectTypeIndication = GPAC_OTI_VIDEO_SVC; + avc->emul_esd->decoderConfig->objectTypeIndication = GF_CODECID_SVC; else if (avc->mvc_config && !avc->avc_config) - avc->emul_esd->decoderConfig->objectTypeIndication = GPAC_OTI_VIDEO_MVC; + avc->emul_esd->decoderConfig->objectTypeIndication = GF_CODECID_MVC; else - avc->emul_esd->decoderConfig->objectTypeIndication = GPAC_OTI_VIDEO_AVC; + avc->emul_esd->decoderConfig->objectTypeIndication = GF_CODECID_AVC; if (btrt) { avc->emul_esd->decoderConfig->bufferSizeDB = btrt->bufferSizeDB; avc->emul_esd->decoderConfig->avgBitrate = btrt->avgBitrate; avc->emul_esd->decoderConfig->maxBitrate = btrt->maxBitrate; } - if (avc->descr) { + GF_MPEG4ExtensionDescriptorsBox *mdesc = (GF_MPEG4ExtensionDescriptorsBox *) gf_isom_box_find_child(avc->child_boxes, GF_ISOM_BOX_TYPE_M4DS); + if (mdesc) { u32 i=0; GF_Descriptor *desc,*clone; i=0; - while ((desc = (GF_Descriptor *)gf_list_enum(avc->descr->descriptors, &i))) { + while ((desc = (GF_Descriptor *)gf_list_enum(mdesc->descriptors, &i))) { clone = NULL; gf_odf_desc_copy(desc, &clone); if (gf_odf_desc_add_desc((GF_Descriptor *)avc->emul_esd, clone) != GF_OK) @@ -986,7 +1072,7 @@ void AVC_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *avc, GF_MediaBox *md } } if (avc->avc_config) { - avcc = avc->avc_config->config ? AVC_DuplicateConfig(avc->avc_config->config) : NULL; + GF_AVCConfig *avcc = avc->avc_config->config ? AVC_DuplicateConfig(avc->avc_config->config) : NULL; /*merge SVC config*/ if (avc->svc_config) { merge_avc_config(avcc, avc->svc_config->config); @@ -1002,7 +1088,7 @@ void AVC_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *avc, GF_MediaBox *md gf_odf_avc_cfg_del(avcc); } } else if (avc->svc_config) { - svcc = AVC_DuplicateConfig(avc->svc_config->config); + GF_AVCConfig *svcc = AVC_DuplicateConfig(avc->svc_config->config); if (mdia) merge_all_config(svcc, NULL, mdia); @@ -1010,7 +1096,7 @@ void AVC_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *avc, GF_MediaBox *md gf_odf_avc_cfg_del(svcc); } else if (avc->mvc_config) { - mvcc = AVC_DuplicateConfig(avc->mvc_config->config); + GF_AVCConfig *mvcc = AVC_DuplicateConfig(avc->mvc_config->config); if (mdia) merge_all_config(mvcc, NULL, mdia); @@ -1031,20 +1117,19 @@ void HEVC_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *hevc, GF_MediaBox * if (hevc->emul_esd) gf_odf_desc_del((GF_Descriptor *)hevc->emul_esd); hevc->emul_esd = gf_odf_desc_esd_new(2); hevc->emul_esd->decoderConfig->streamType = GF_STREAM_VISUAL; - hevc->emul_esd->decoderConfig->objectTypeIndication = GPAC_OTI_VIDEO_HEVC; - if (hevc->lhvc_config /*&& !hevc->hevc_config*/) - hevc->emul_esd->decoderConfig->objectTypeIndication = GPAC_OTI_VIDEO_LHVC; + hevc->emul_esd->decoderConfig->objectTypeIndication = GF_CODECID_HEVC; if (btrt) { hevc->emul_esd->decoderConfig->bufferSizeDB = btrt->bufferSizeDB; hevc->emul_esd->decoderConfig->avgBitrate = btrt->avgBitrate; hevc->emul_esd->decoderConfig->maxBitrate = btrt->maxBitrate; } - if (hevc->descr) { + GF_MPEG4ExtensionDescriptorsBox *mdesc = (GF_MPEG4ExtensionDescriptorsBox *) gf_isom_box_find_child(hevc->child_boxes, GF_ISOM_BOX_TYPE_M4DS); + if (mdesc) { u32 i=0; GF_Descriptor *desc,*clone; i=0; - while ((desc = (GF_Descriptor *)gf_list_enum(hevc->descr->descriptors, &i))) { + while ((desc = (GF_Descriptor *)gf_list_enum(mdesc->descriptors, &i))) { clone = NULL; gf_odf_desc_copy(desc, &clone); if (gf_odf_desc_add_desc((GF_Descriptor *)hevc->emul_esd, clone) != GF_OK) @@ -1066,6 +1151,7 @@ void HEVC_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *hevc, GF_MediaBox * if (mdia && ((mdia->mediaTrack->extractor_mode&0x0000FFFF) != GF_ISOM_NALU_EXTRACT_INSPECT)) { hcfg->is_lhvc=GF_FALSE; } + gf_odf_hevc_cfg_write(hcfg, &hevc->emul_esd->decoderConfig->decoderSpecificInfo->data, &hevc->emul_esd->decoderConfig->decoderSpecificInfo->dataLength); gf_odf_hevc_cfg_del(hcfg); } @@ -1080,8 +1166,10 @@ GF_Err AVC_HEVC_UpdateESD(GF_MPEGVisualSampleEntryBox *avc, GF_ESD *esd) { GF_BitRateBox *btrt = gf_isom_sample_entry_get_bitrate((GF_SampleEntryBox *)avc, GF_TRUE); - if (avc->descr) gf_isom_box_del((GF_Box *) avc->descr); - avc->descr = NULL; + GF_MPEG4ExtensionDescriptorsBox *mdesc = (GF_MPEG4ExtensionDescriptorsBox *) gf_isom_box_find_child(avc->child_boxes, GF_ISOM_BOX_TYPE_M4DS); + if (mdesc) { + gf_isom_box_del_parent(&avc->child_boxes, (GF_Box *) mdesc); + } btrt->avgBitrate = esd->decoderConfig->avgBitrate; btrt->maxBitrate = esd->decoderConfig->maxBitrate; btrt->bufferSizeDB = esd->decoderConfig->bufferSizeDB; @@ -1092,51 +1180,59 @@ GF_Err AVC_HEVC_UpdateESD(GF_MPEGVisualSampleEntryBox *avc, GF_ESD *esd) || gf_list_count(esd->extensionDescriptors) || esd->ipiPtr || esd->qos || esd->RegDescriptor) { - avc->descr = (GF_MPEG4ExtensionDescriptorsBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_M4DS); + mdesc = (GF_MPEG4ExtensionDescriptorsBox *) gf_isom_box_new_parent(&avc->child_boxes, GF_ISOM_BOX_TYPE_M4DS); + if (!mdesc) return GF_OUT_OF_MEM; + if (esd->RegDescriptor) { - gf_list_add(avc->descr->descriptors, esd->RegDescriptor); + gf_list_add(mdesc->descriptors, esd->RegDescriptor); esd->RegDescriptor = NULL; } if (esd->qos) { - gf_list_add(avc->descr->descriptors, esd->qos); + gf_list_add(mdesc->descriptors, esd->qos); esd->qos = NULL; } if (esd->ipiPtr) { - gf_list_add(avc->descr->descriptors, esd->ipiPtr); + gf_list_add(mdesc->descriptors, esd->ipiPtr); esd->ipiPtr= NULL; } while (gf_list_count(esd->IPIDataSet)) { GF_Descriptor *desc = (GF_Descriptor *)gf_list_get(esd->IPIDataSet, 0); gf_list_rem(esd->IPIDataSet, 0); - gf_list_add(avc->descr->descriptors, desc); + gf_list_add(mdesc->descriptors, desc); } while (gf_list_count(esd->IPMPDescriptorPointers)) { GF_Descriptor *desc = (GF_Descriptor *)gf_list_get(esd->IPMPDescriptorPointers, 0); gf_list_rem(esd->IPMPDescriptorPointers, 0); - gf_list_add(avc->descr->descriptors, desc); + gf_list_add(mdesc->descriptors, desc); } if (esd->langDesc) { - gf_list_add(avc->descr->descriptors, esd->langDesc); + gf_list_add(mdesc->descriptors, esd->langDesc); esd->langDesc = NULL; } while (gf_list_count(esd->extensionDescriptors)) { GF_Descriptor *desc = (GF_Descriptor *)gf_list_get(esd->extensionDescriptors, 0); gf_list_rem(esd->extensionDescriptors, 0); - gf_list_add(avc->descr->descriptors, desc); + gf_list_add(mdesc->descriptors, desc); } } - if (!avc->lhvc_config && (esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_HEVC)) { - if (!avc->hevc_config) avc->hevc_config = (GF_HEVCConfigurationBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_HVCC); + if (!avc->lhvc_config && (esd->decoderConfig->objectTypeIndication==GF_CODECID_HEVC)) { + if (!avc->hevc_config) { + avc->hevc_config = (GF_HEVCConfigurationBox *)gf_isom_box_new_parent(&avc->child_boxes, GF_ISOM_BOX_TYPE_HVCC); + if (!avc->hevc_config) return GF_OUT_OF_MEM; + } if (esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->data) { if (avc->hevc_config->config) gf_odf_hevc_cfg_del(avc->hevc_config->config); avc->hevc_config->config = gf_odf_hevc_cfg_read(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_FALSE); } } - else if (!avc->svc_config && !avc->mvc_config && (esd->decoderConfig->objectTypeIndication==GPAC_OTI_VIDEO_AVC)) { - if (!avc->avc_config) avc->avc_config = (GF_AVCConfigurationBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_AVCC); + else if (!avc->svc_config && !avc->mvc_config && (esd->decoderConfig->objectTypeIndication==GF_CODECID_AVC)) { + if (!avc->avc_config) { + avc->avc_config = (GF_AVCConfigurationBox *)gf_isom_box_new_parent(&avc->child_boxes, GF_ISOM_BOX_TYPE_AVCC); + if (!avc->avc_config) return GF_OUT_OF_MEM; + } if (esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->data) { if (avc->avc_config->config) gf_odf_avc_cfg_del(avc->avc_config->config); avc->avc_config->config = gf_odf_avc_cfg_read(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength); @@ -1152,13 +1248,225 @@ GF_Err AVC_HEVC_UpdateESD(GF_MPEGVisualSampleEntryBox *avc, GF_ESD *esd) return GF_OK; } +#if !defined(GPAC_DISABLE_AV_PARSERS) && !defined(GPAC_DISABLE_HEVC) +void gf_media_hevc_parse_ps(GF_HEVCConfig* hevccfg, HEVCState* hevc, u32 nal_type) +{ + u32 i, j; + if (!hevccfg) return; + + for (i = 0; i < gf_list_count(hevccfg->param_array); i++) { + GF_HEVCParamArray* ar = gf_list_get(hevccfg->param_array, i); + if (ar->type != nal_type) continue; + for (j = 0; j < gf_list_count(ar->nalus); j++) { + u8 ntype, tid, lid; + GF_AVCConfigSlot* sl = gf_list_get(ar->nalus, j); + gf_media_hevc_parse_nalu(sl->data, sl->size, hevc, &ntype, &tid, &lid); + } + } +} +#endif + + +static GF_Err gf_isom_check_mvc(GF_ISOFile *the_file, GF_TrackBox *trak, GF_MPEGVisualSampleEntryBox *entry) +{ + u32 i; + GF_Box *mvci; + GF_MultiviewGroupBox *mvcg; + GF_ViewIdentifierBox *vwid; + + if (entry->mvc_config) {} + else if (entry->avc_config && entry->avc_config->config && entry->avc_config->config->sequenceParameterSetExtensions) {} + else + return GF_OK; + + mvci = gf_isom_box_find_child(trak->Media->information->child_boxes, GF_ISOM_BOX_TYPE_MVCI); + if (!mvci) { + mvci = gf_isom_box_new_parent(&trak->Media->information->child_boxes, GF_ISOM_BOX_TYPE_MVCI); + if (!mvci) return GF_OUT_OF_MEM; + } + mvcg = (GF_MultiviewGroupBox *) gf_isom_box_find_child(mvci->child_boxes, GF_ISOM_BOX_TYPE_MVCG); + if (!mvcg) { + mvcg = (GF_MultiviewGroupBox *)gf_isom_box_new_parent(&mvci->child_boxes, GF_ISOM_BOX_TYPE_MVCG); + if (!mvcg) return GF_OUT_OF_MEM; + } + //this is very crude, we should try to parse the bitstream to fill these + mvcg->num_entries = 0; + if (mvcg->entries) { + gf_free(mvcg->entries); + mvcg->entries = NULL; + } + if (entry->avc_config) { + if (gf_list_count(entry->avc_config->config->sequenceParameterSets)) + mvcg->num_entries += 1; + mvcg->num_entries += gf_list_count(entry->avc_config->config->sequenceParameterSetExtensions); + } + if (entry->mvc_config) { + mvcg->num_entries += gf_list_count(entry->mvc_config->config->sequenceParameterSets); + } + mvcg->entries = gf_malloc(sizeof(MVCIEntry)*mvcg->num_entries); + if (!mvcg->entries) return GF_OUT_OF_MEM; + memset(mvcg->entries, 0, sizeof(MVCIEntry)*mvcg->num_entries); + for (i=0; inum_entries; i++) { + mvcg->entries[i].entry_type = 2; + mvcg->entries[i].output_view_id = i; + } + vwid = (GF_ViewIdentifierBox *) gf_isom_box_find_child(entry->child_boxes, GF_ISOM_BOX_TYPE_VWID); + if (!vwid) { + vwid = (GF_ViewIdentifierBox *)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_VWID); + if (!mvcg) return GF_OUT_OF_MEM; + } + if (vwid->views) gf_free(vwid->views); + vwid->num_views = mvcg->num_entries; + vwid->views = gf_malloc(sizeof(ViewIDEntry)*vwid->num_views); + if (!vwid->views) return GF_OUT_OF_MEM; + memset(vwid->views, 0, sizeof(ViewIDEntry)*vwid->num_views); + + for (i=0; inum_views; i++) { + vwid->views[i].base_view_type = i ? 0 : 1; + vwid->views[i].view_id = i; + vwid->views[i].view_order_index = i; + } + + return GF_OK; +} + +static GF_AV1Config* AV1_DuplicateConfig(GF_AV1Config const * const cfg) { + u32 i = 0; + GF_AV1Config *out = gf_malloc(sizeof(GF_AV1Config)); + + out->marker = cfg->marker; + out->version = cfg->version; + out->seq_profile = cfg->seq_profile; + out->seq_level_idx_0 = cfg->seq_level_idx_0; + out->seq_tier_0 = cfg->seq_tier_0; + out->high_bitdepth = cfg->high_bitdepth; + out->twelve_bit = cfg->twelve_bit; + out->monochrome = cfg->monochrome; + out->chroma_subsampling_x = cfg->chroma_subsampling_x; + out->chroma_subsampling_y = cfg->chroma_subsampling_y; + out->chroma_sample_position = cfg->chroma_sample_position; + + out->initial_presentation_delay_present = cfg->initial_presentation_delay_present; + out->initial_presentation_delay_minus_one = cfg->initial_presentation_delay_minus_one; + out->obu_array = gf_list_new(); + for (i = 0; iobu_array); ++i) { + GF_AV1_OBUArrayEntry *dst = gf_malloc(sizeof(GF_AV1_OBUArrayEntry)), *src = gf_list_get(cfg->obu_array, i); + dst->obu_length = src->obu_length; + dst->obu_type = src->obu_type; + dst->obu = gf_malloc((size_t)dst->obu_length); + memcpy(dst->obu, src->obu, (size_t)src->obu_length); + gf_list_add(out->obu_array, dst); + } + return out; +} + +void AV1_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *av1, GF_MediaBox *mdia) +{ + GF_BitRateBox *btrt = gf_isom_sample_entry_get_bitrate((GF_SampleEntryBox *)av1, GF_FALSE); + + if (av1->emul_esd) gf_odf_desc_del((GF_Descriptor *)av1->emul_esd); + av1->emul_esd = gf_odf_desc_esd_new(2); + av1->emul_esd->decoderConfig->streamType = GF_STREAM_VISUAL; + av1->emul_esd->decoderConfig->objectTypeIndication = GF_CODECID_AV1; + + if (btrt) { + av1->emul_esd->decoderConfig->bufferSizeDB = btrt->bufferSizeDB; + av1->emul_esd->decoderConfig->avgBitrate = btrt->avgBitrate; + av1->emul_esd->decoderConfig->maxBitrate = btrt->maxBitrate; + } + if (av1->av1_config) { + GF_AV1Config *av1_cfg = AV1_DuplicateConfig(av1->av1_config->config); + if (av1_cfg) { + gf_odf_av1_cfg_write(av1_cfg, &av1->emul_esd->decoderConfig->decoderSpecificInfo->data, &av1->emul_esd->decoderConfig->decoderSpecificInfo->dataLength); + gf_odf_av1_cfg_del(av1_cfg); + } + } +} + +void AV1_RewriteESDescriptor(GF_MPEGVisualSampleEntryBox *av1) +{ + AV1_RewriteESDescriptorEx(av1, NULL); +} + + + +static GF_VPConfig* VP_DuplicateConfig(GF_VPConfig const * const cfg) +{ + GF_VPConfig *out = gf_odf_vp_cfg_new(); + if (out) { + out->profile = cfg->profile; + out->level = cfg->level; + out->bit_depth = cfg->bit_depth; + out->chroma_subsampling = cfg->chroma_subsampling; + out->video_fullRange_flag = cfg->video_fullRange_flag; + out->colour_primaries = cfg->colour_primaries; + out->transfer_characteristics = cfg->transfer_characteristics; + out->matrix_coefficients = cfg->matrix_coefficients; + } + + return out; +} + +void VP9_RewriteESDescriptorEx(GF_MPEGVisualSampleEntryBox *vp9, GF_MediaBox *mdia) +{ + GF_BitRateBox *btrt = gf_isom_sample_entry_get_bitrate((GF_SampleEntryBox *)vp9, GF_FALSE); + + if (vp9->emul_esd) gf_odf_desc_del((GF_Descriptor *)vp9->emul_esd); + vp9->emul_esd = gf_odf_desc_esd_new(2); + vp9->emul_esd->decoderConfig->streamType = GF_STREAM_VISUAL; + if (vp9->type == GF_ISOM_BOX_TYPE_VP08) + vp9->emul_esd->decoderConfig->objectTypeIndication = GF_CODECID_VP8; + else + vp9->emul_esd->decoderConfig->objectTypeIndication = GF_CODECID_VP9; + + if (btrt) { + vp9->emul_esd->decoderConfig->bufferSizeDB = btrt->bufferSizeDB; + vp9->emul_esd->decoderConfig->avgBitrate = btrt->avgBitrate; + vp9->emul_esd->decoderConfig->maxBitrate = btrt->maxBitrate; + } + + if (vp9->vp_config) { + GF_VPConfig *vp9_cfg = VP_DuplicateConfig(vp9->vp_config->config); + if (vp9_cfg) { + gf_odf_vp_cfg_write(vp9_cfg, &vp9->emul_esd->decoderConfig->decoderSpecificInfo->data, &vp9->emul_esd->decoderConfig->decoderSpecificInfo->dataLength, GF_FALSE); + gf_odf_vp_cfg_del(vp9_cfg); + } + } +} + +void VP9_RewriteESDescriptor(GF_MPEGVisualSampleEntryBox *vp9) +{ + VP9_RewriteESDescriptorEx(vp9, NULL); +} + + + +static GF_DOVIDecoderConfigurationRecord* DOVI_DuplicateConfig(GF_DOVIDecoderConfigurationRecord *cfg) +{ + GF_DOVIDecoderConfigurationRecord* out = NULL; + GF_SAFEALLOC(out, GF_DOVIDecoderConfigurationRecord); + if (!out) return NULL; + + out->dv_version_major = cfg->dv_version_major; + out->dv_version_minor = cfg->dv_version_minor; + out->dv_profile = cfg->dv_profile; + out->dv_level = cfg->dv_level; + out->rpu_present_flag = cfg->rpu_present_flag; + out->el_present_flag = cfg->el_present_flag; + out->bl_present_flag = cfg->bl_present_flag; + + return out; +} + + #ifndef GPAC_DISABLE_ISOM_WRITE GF_EXPORT -GF_Err gf_isom_avc_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_AVCConfig *cfg, char *URLname, char *URNname, u32 *outDescriptionIndex) +GF_Err gf_isom_avc_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_AVCConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex) { GF_TrackBox *trak; GF_Err e; + GF_SampleDescriptionBox *stsd; u32 dataRefIndex; GF_MPEGVisualSampleEntryBox *entry; @@ -1169,38 +1477,43 @@ GF_Err gf_isom_avc_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_AVCConfi if (!trak || !trak->Media || !cfg) return GF_BAD_PARAM; //get or create the data ref - e = Media_FindDataRef(trak->Media->information->dataInformation->dref, URLname, URNname, &dataRefIndex); + e = Media_FindDataRef(trak->Media->information->dataInformation->dref, (char *)URLname, (char *)URNname, &dataRefIndex); if (e) return e; if (!dataRefIndex) { - e = Media_CreateDataRef(trak->Media->information->dataInformation->dref, URLname, URNname, &dataRefIndex); + e = Media_CreateDataRef(the_file, trak->Media->information->dataInformation->dref, (char *)URLname, (char *)URNname, &dataRefIndex); if (e) return e; } - if (!the_file->keep_utc) + if (!the_file->keep_utc && !gf_sys_is_test_mode() ) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); + stsd = trak->Media->information->sampleTable->SampleDescription; + //create a new entry - entry = (GF_MPEGVisualSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_AVC1); + entry = (GF_MPEGVisualSampleEntryBox *) gf_isom_box_new_parent(&stsd->child_boxes, GF_ISOM_BOX_TYPE_AVC1); if (!entry) return GF_OUT_OF_MEM; - entry->avc_config = (GF_AVCConfigurationBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_AVCC); + *outDescriptionIndex = gf_list_count(stsd->child_boxes); + + entry->avc_config = (GF_AVCConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_AVCC); + if (!entry->avc_config) return GF_OUT_OF_MEM; entry->avc_config->config = AVC_DuplicateConfig(cfg); + if (!entry->avc_config->config) return GF_OUT_OF_MEM; entry->dataReferenceIndex = dataRefIndex; - e = gf_list_add(trak->Media->information->sampleTable->SampleDescription->other_boxes, entry); - *outDescriptionIndex = gf_list_count(trak->Media->information->sampleTable->SampleDescription->other_boxes); AVC_RewriteESDescriptor(entry); return e; } -static GF_Err gf_isom_avc_config_update_ex(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_AVCConfig *cfg, u32 op_type) +static GF_Err gf_isom_avc_config_update_ex(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_AVCConfig *cfg, u32 op_type, Bool keep_xps) { GF_TrackBox *trak; GF_Err e; + u32 i; GF_MPEGVisualSampleEntryBox *entry; e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return GF_BAD_PARAM; - entry = (GF_MPEGVisualSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, DescriptionIndex-1); + entry = (GF_MPEGVisualSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex-1); if (!entry) return GF_BAD_PARAM; switch (entry->type) { case GF_ISOM_BOX_TYPE_AVC1: @@ -1218,57 +1531,76 @@ static GF_Err gf_isom_avc_config_update_ex(GF_ISOFile *the_file, u32 trackNumber /*AVCC replacement*/ case 0: if (!cfg) return GF_BAD_PARAM; - if (!entry->avc_config) entry->avc_config = (GF_AVCConfigurationBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_AVCC); + if (!entry->avc_config) { + entry->avc_config = (GF_AVCConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_AVCC); + if (!entry->avc_config) return GF_OUT_OF_MEM; + } if (entry->avc_config->config) gf_odf_avc_cfg_del(entry->avc_config->config); entry->avc_config->config = AVC_DuplicateConfig(cfg); + if (!entry->avc_config->config) return GF_OUT_OF_MEM; entry->type = GF_ISOM_BOX_TYPE_AVC1; break; /*SVCC replacement*/ case 1: if (!cfg) return GF_BAD_PARAM; - if (!entry->svc_config) entry->svc_config = (GF_AVCConfigurationBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_SVCC); + if (!entry->svc_config) { + entry->svc_config = (GF_AVCConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_SVCC); + if (!entry->svc_config) return GF_OUT_OF_MEM; + } if (entry->svc_config->config) gf_odf_avc_cfg_del(entry->svc_config->config); entry->svc_config->config = AVC_DuplicateConfig(cfg); + if (!entry->svc_config->config) return GF_OUT_OF_MEM; entry->type = GF_ISOM_BOX_TYPE_AVC1; break; /*SVCC replacement and AVC removal*/ case 2: if (!cfg) return GF_BAD_PARAM; if (entry->avc_config) { - gf_isom_box_del((GF_Box*)entry->avc_config); + gf_isom_box_del_parent(&entry->child_boxes, (GF_Box*)entry->avc_config); entry->avc_config = NULL; } - if (!entry->svc_config) entry->svc_config = (GF_AVCConfigurationBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_SVCC); + if (!entry->svc_config) { + entry->svc_config = (GF_AVCConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_SVCC); + if (!entry->svc_config) return GF_OUT_OF_MEM; + } if (entry->svc_config->config) gf_odf_avc_cfg_del(entry->svc_config->config); entry->svc_config->config = AVC_DuplicateConfig(cfg); + if (!entry->svc_config->config) return GF_OUT_OF_MEM; entry->type = GF_ISOM_BOX_TYPE_SVC1; break; /*AVCC removal and switch to avc3*/ case 3: if (!entry->avc_config || !entry->avc_config->config) return GF_BAD_PARAM; + if (!keep_xps) { + for (i=0; i<3; i++) { + GF_AVCConfigurationBox *a_cfg = entry->avc_config; + if (i==1) a_cfg = entry->svc_config; + else if (i==2) a_cfg = entry->mvc_config; + if (!a_cfg) continue; + + + while (gf_list_count(a_cfg->config->sequenceParameterSets)) { + GF_AVCConfigSlot *sl = (GF_AVCConfigSlot*)gf_list_get(a_cfg->config->sequenceParameterSets, 0); + gf_list_rem(a_cfg->config->sequenceParameterSets, 0); + if (sl->data) gf_free(sl->data); + gf_free(sl); + } - if (entry->svc_config) { - gf_isom_box_del((GF_Box*)entry->svc_config); - entry->svc_config = NULL; - } - if (entry->mvc_config) { - gf_isom_box_del((GF_Box*)entry->mvc_config); - entry->mvc_config = NULL; - } - - while (gf_list_count(entry->avc_config->config->sequenceParameterSets)) { - GF_AVCConfigSlot *sl = (GF_AVCConfigSlot*)gf_list_get(entry->avc_config->config->sequenceParameterSets, 0); - gf_list_rem(entry->avc_config->config->sequenceParameterSets, 0); - if (sl->data) gf_free(sl->data); - gf_free(sl); - } + while (gf_list_count(a_cfg->config->pictureParameterSets)) { + GF_AVCConfigSlot *sl = (GF_AVCConfigSlot*)gf_list_get(a_cfg->config->pictureParameterSets, 0); + gf_list_rem(a_cfg->config->pictureParameterSets, 0); + if (sl->data) gf_free(sl->data); + gf_free(sl); + } - while (gf_list_count(entry->avc_config->config->pictureParameterSets)) { - GF_AVCConfigSlot *sl = (GF_AVCConfigSlot*)gf_list_get(entry->avc_config->config->pictureParameterSets, 0); - gf_list_rem(entry->avc_config->config->pictureParameterSets, 0); - if (sl->data) gf_free(sl->data); - gf_free(sl); + while (gf_list_count(a_cfg->config->sequenceParameterSetExtensions)) { + GF_AVCConfigSlot *sl = (GF_AVCConfigSlot*)gf_list_get(a_cfg->config->sequenceParameterSetExtensions, 0); + gf_list_rem(a_cfg->config->sequenceParameterSetExtensions, 0); + if (sl->data) gf_free(sl->data); + gf_free(sl); + } + } } if (entry->type == GF_ISOM_BOX_TYPE_AVC1) @@ -1279,22 +1611,34 @@ static GF_Err gf_isom_avc_config_update_ex(GF_ISOFile *the_file, u32 trackNumber /*MVCC replacement*/ case 4: if (!cfg) return GF_BAD_PARAM; - if (!entry->mvc_config) entry->mvc_config = (GF_AVCConfigurationBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_MVCC); + if (!entry->mvc_config) { + entry->mvc_config = (GF_AVCConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_MVCC); + if (!entry->mvc_config) return GF_OUT_OF_MEM; + } if (entry->mvc_config->config) gf_odf_avc_cfg_del(entry->mvc_config->config); entry->mvc_config->config = AVC_DuplicateConfig(cfg); + if (!entry->mvc_config->config) return GF_OUT_OF_MEM; entry->type = GF_ISOM_BOX_TYPE_AVC1; + e = gf_isom_check_mvc(the_file, trak, entry); + if (e) return e; break; /*MVCC replacement and AVC removal*/ case 5: if (!cfg) return GF_BAD_PARAM; if (entry->avc_config) { - gf_isom_box_del((GF_Box*)entry->avc_config); + gf_isom_box_del_parent(&entry->child_boxes, (GF_Box*)entry->avc_config); entry->avc_config = NULL; } - if (!entry->mvc_config) entry->mvc_config = (GF_AVCConfigurationBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_MVCC); + if (!entry->mvc_config) { + entry->mvc_config = (GF_AVCConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_MVCC); + if (!entry->mvc_config) return GF_OUT_OF_MEM; + } if (entry->mvc_config->config) gf_odf_avc_cfg_del(entry->mvc_config->config); entry->mvc_config->config = AVC_DuplicateConfig(cfg); + if (!entry->mvc_config->config) return GF_OUT_OF_MEM; entry->type = GF_ISOM_BOX_TYPE_MVC1; + e = gf_isom_check_mvc(the_file, trak, entry); + if (e) return e; break; } AVC_RewriteESDescriptor(entry); @@ -1302,25 +1646,26 @@ static GF_Err gf_isom_avc_config_update_ex(GF_ISOFile *the_file, u32 trackNumber } GF_EXPORT -GF_Err gf_isom_avc_set_inband_config(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex) +GF_Err gf_isom_avc_set_inband_config(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, Bool keep_xps) { - return gf_isom_avc_config_update_ex(the_file, trackNumber, DescriptionIndex, NULL, 3); + return gf_isom_avc_config_update_ex(the_file, trackNumber, DescriptionIndex, NULL, 3, keep_xps); } GF_EXPORT GF_Err gf_isom_avc_config_update(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_AVCConfig *cfg) { - return gf_isom_avc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, 0); + return gf_isom_avc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, 0, GF_FALSE); } +GF_EXPORT GF_Err gf_isom_svc_config_update(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_AVCConfig *cfg, Bool is_add) { - return gf_isom_avc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, is_add ? 1 : 2); + return gf_isom_avc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, is_add ? 1 : 2, GF_FALSE); } GF_Err gf_isom_mvc_config_update(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_AVCConfig *cfg, Bool is_add) { - return gf_isom_avc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, is_add ? 4 : 5); + return gf_isom_avc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, is_add ? 4 : 5, GF_FALSE); } static GF_Err gf_isom_svc_mvc_config_del(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, Bool is_mvc) @@ -1333,7 +1678,7 @@ static GF_Err gf_isom_svc_mvc_config_del(GF_ISOFile *the_file, u32 trackNumber, if (e) return e; trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return GF_BAD_PARAM; - entry = (GF_MPEGVisualSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, DescriptionIndex-1); + entry = (GF_MPEGVisualSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex-1); if (!entry) return GF_BAD_PARAM; switch (entry->type) { case GF_ISOM_BOX_TYPE_AVC1: @@ -1348,11 +1693,11 @@ static GF_Err gf_isom_svc_mvc_config_del(GF_ISOFile *the_file, u32 trackNumber, } if (is_mvc && entry->mvc_config) { - gf_isom_box_del((GF_Box*)entry->mvc_config); + gf_isom_box_del_parent(&entry->child_boxes, (GF_Box*)entry->mvc_config); entry->mvc_config = NULL; } else if (!is_mvc && entry->svc_config) { - gf_isom_box_del((GF_Box*)entry->svc_config); + gf_isom_box_del_parent(&entry->child_boxes, (GF_Box*)entry->svc_config); entry->svc_config = NULL; } AVC_RewriteESDescriptor(entry); @@ -1381,7 +1726,7 @@ GF_Err gf_isom_set_ipod_compatible(GF_ISOFile *the_file, u32 trackNumber) if (e) return e; trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak || !trak->Media) return GF_BAD_PARAM; - entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, 0); + entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, 0); if (!entry) return GF_OK; switch (entry->type) { case GF_ISOM_BOX_TYPE_AVC1: @@ -1398,7 +1743,10 @@ GF_Err gf_isom_set_ipod_compatible(GF_ISOFile *the_file, u32 trackNumber) return GF_OK; } - if (!entry->ipod_ext) entry->ipod_ext = (GF_UnknownUUIDBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_UUID); + if (!entry->ipod_ext) { + entry->ipod_ext = (GF_UnknownUUIDBox *) gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_UUID); + if (!entry->ipod_ext) return GF_OUT_OF_MEM; + } memcpy(entry->ipod_ext->uuid, GF_ISOM_IPOD_EXT, sizeof(u8)*16); entry->ipod_ext->dataSize = 0; return GF_OK; @@ -1409,6 +1757,7 @@ static GF_Err gf_isom_svc_mvc_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_TrackBox *trak; GF_Err e; u32 dataRefIndex; + GF_SampleDescriptionBox *stsd; GF_MPEGVisualSampleEntryBox *entry; e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); @@ -1421,50 +1770,55 @@ static GF_Err gf_isom_svc_mvc_config_new(GF_ISOFile *the_file, u32 trackNumber, e = Media_FindDataRef(trak->Media->information->dataInformation->dref, URLname, URNname, &dataRefIndex); if (e) return e; if (!dataRefIndex) { - e = Media_CreateDataRef(trak->Media->information->dataInformation->dref, URLname, URNname, &dataRefIndex); + e = Media_CreateDataRef(the_file, trak->Media->information->dataInformation->dref, URLname, URNname, &dataRefIndex); if (e) return e; } if (!the_file->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); + stsd = trak->Media->information->sampleTable->SampleDescription; //create a new entry if (is_mvc) { - entry = (GF_MPEGVisualSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_MVC1); + entry = (GF_MPEGVisualSampleEntryBox *) gf_isom_box_new_parent(&stsd->child_boxes, GF_ISOM_BOX_TYPE_MVC1); if (!entry) return GF_OUT_OF_MEM; - entry->mvc_config = (GF_AVCConfigurationBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_MVCC); + entry->mvc_config = (GF_AVCConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_MVCC); + if (!entry->mvc_config) return GF_OUT_OF_MEM; entry->mvc_config->config = AVC_DuplicateConfig(cfg); + if (!entry->mvc_config->config) return GF_OUT_OF_MEM; } else { - entry = (GF_MPEGVisualSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_SVC1); + entry = (GF_MPEGVisualSampleEntryBox *) gf_isom_box_new_parent(&stsd->child_boxes,GF_ISOM_BOX_TYPE_SVC1); if (!entry) return GF_OUT_OF_MEM; - entry->svc_config = (GF_AVCConfigurationBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_SVCC); + entry->svc_config = (GF_AVCConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes,GF_ISOM_BOX_TYPE_SVCC); + if (!entry->svc_config) return GF_OUT_OF_MEM; entry->svc_config->config = AVC_DuplicateConfig(cfg); + if (!entry->svc_config->config) return GF_OUT_OF_MEM; } entry->dataReferenceIndex = dataRefIndex; - e = gf_list_add(trak->Media->information->sampleTable->SampleDescription->other_boxes, entry); - *outDescriptionIndex = gf_list_count(trak->Media->information->sampleTable->SampleDescription->other_boxes); + *outDescriptionIndex = gf_list_count(stsd->child_boxes); AVC_RewriteESDescriptor(entry); return e; } GF_EXPORT -GF_Err gf_isom_svc_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_AVCConfig *cfg, char *URLname, char *URNname, u32 *outDescriptionIndex) +GF_Err gf_isom_svc_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_AVCConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex) { - return gf_isom_svc_mvc_config_new(the_file, trackNumber, cfg, GF_FALSE, URLname, URNname,outDescriptionIndex); + return gf_isom_svc_mvc_config_new(the_file, trackNumber, cfg, GF_FALSE, (char *) URLname, (char *) URNname,outDescriptionIndex); } GF_EXPORT -GF_Err gf_isom_mvc_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_AVCConfig *cfg, char *URLname, char *URNname, u32 *outDescriptionIndex) +GF_Err gf_isom_mvc_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_AVCConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex) { - return gf_isom_svc_mvc_config_new(the_file, trackNumber, cfg, GF_TRUE, URLname, URNname,outDescriptionIndex); + return gf_isom_svc_mvc_config_new(the_file, trackNumber, cfg, GF_TRUE, (char *) URLname, (char *) URNname,outDescriptionIndex); } GF_EXPORT -GF_Err gf_isom_hevc_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_HEVCConfig *cfg, char *URLname, char *URNname, u32 *outDescriptionIndex) +GF_Err gf_isom_hevc_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_HEVCConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex) { GF_TrackBox *trak; GF_Err e; u32 dataRefIndex; + GF_SampleDescriptionBox *stsd; GF_MPEGVisualSampleEntryBox *entry; e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); @@ -1474,27 +1828,108 @@ GF_Err gf_isom_hevc_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_HEVCCon if (!trak || !trak->Media || !cfg) return GF_BAD_PARAM; //get or create the data ref - e = Media_FindDataRef(trak->Media->information->dataInformation->dref, URLname, URNname, &dataRefIndex); + e = Media_FindDataRef(trak->Media->information->dataInformation->dref, (char *)URLname, (char *)URNname, &dataRefIndex); if (e) return e; if (!dataRefIndex) { - e = Media_CreateDataRef(trak->Media->information->dataInformation->dref, URLname, URNname, &dataRefIndex); + e = Media_CreateDataRef(the_file, trak->Media->information->dataInformation->dref, (char *)URLname, (char *)URNname, &dataRefIndex); if (e) return e; } if (!the_file->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); + stsd = trak->Media->information->sampleTable->SampleDescription; //create a new entry - entry = (GF_MPEGVisualSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_HVC1); + entry = (GF_MPEGVisualSampleEntryBox *) gf_isom_box_new_parent(&stsd->child_boxes, GF_ISOM_BOX_TYPE_HVC1); if (!entry) return GF_OUT_OF_MEM; - entry->hevc_config = (GF_HEVCConfigurationBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_HVCC); + entry->hevc_config = (GF_HEVCConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_HVCC); + if (!entry->hevc_config) return GF_OUT_OF_MEM; entry->hevc_config->config = HEVC_DuplicateConfig(cfg); + if (!entry->hevc_config->config) return GF_OUT_OF_MEM; entry->dataReferenceIndex = dataRefIndex; - e = gf_list_add(trak->Media->information->sampleTable->SampleDescription->other_boxes, entry); - *outDescriptionIndex = gf_list_count(trak->Media->information->sampleTable->SampleDescription->other_boxes); + *outDescriptionIndex = gf_list_count(stsd->child_boxes); HEVC_RewriteESDescriptor(entry); return e; } +GF_EXPORT +GF_Err gf_isom_vp_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_VPConfig *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex, u32 vpx_type) +{ + GF_TrackBox *trak; + GF_Err e; + u32 dataRefIndex; + GF_SampleDescriptionBox *stsd; + GF_MPEGVisualSampleEntryBox *entry; + + e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + if (e) return e; + + trak = gf_isom_get_track_from_file(the_file, trackNumber); + if (!trak || !trak->Media || !cfg) return GF_BAD_PARAM; + + //get or create the data ref + e = Media_FindDataRef(trak->Media->information->dataInformation->dref, (char *)URLname, (char *)URNname, &dataRefIndex); + if (e) return e; + if (!dataRefIndex) { + e = Media_CreateDataRef(the_file, trak->Media->information->dataInformation->dref, (char *)URLname, (char *)URNname, &dataRefIndex); + if (e) return e; + } + if (!the_file->keep_utc) + trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); + + stsd = trak->Media->information->sampleTable->SampleDescription; + //create a new entry + entry = (GF_MPEGVisualSampleEntryBox *)gf_isom_box_new_parent(&stsd->child_boxes, vpx_type); + if (!entry) return GF_OUT_OF_MEM; + + entry->vp_config = (GF_VPConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_VPCC); + if (!entry->vp_config) return GF_OUT_OF_MEM; + entry->vp_config->config = VP_DuplicateConfig(cfg); + if (!entry->vp_config->config) return GF_OUT_OF_MEM; + strncpy(entry->compressor_name, "\012VPC Coding", sizeof(entry->compressor_name)-1); + entry->dataReferenceIndex = dataRefIndex; + *outDescriptionIndex = gf_list_count(stsd->child_boxes); + return e; +} + +GF_EXPORT +GF_Err gf_isom_av1_config_new(GF_ISOFile *the_file, u32 trackNumber, GF_AV1Config *cfg, const char *URLname, const char *URNname, u32 *outDescriptionIndex) +{ + GF_TrackBox *trak; + GF_Err e; + u32 dataRefIndex; + GF_MPEGVisualSampleEntryBox *entry; + GF_SampleDescriptionBox *stsd; + + e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); + if (e) return e; + + trak = gf_isom_get_track_from_file(the_file, trackNumber); + if (!trak || !trak->Media || !cfg) return GF_BAD_PARAM; + + //get or create the data ref + e = Media_FindDataRef(trak->Media->information->dataInformation->dref, (char *)URLname, (char *)URNname, &dataRefIndex); + if (e) return e; + if (!dataRefIndex) { + e = Media_CreateDataRef(the_file, trak->Media->information->dataInformation->dref, (char *)URLname, (char *)URNname, &dataRefIndex); + if (e) return e; + } + if (!the_file->keep_utc) + trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); + + stsd = trak->Media->information->sampleTable->SampleDescription; + //create a new entry + entry = (GF_MPEGVisualSampleEntryBox *)gf_isom_box_new_parent(&stsd->child_boxes, GF_ISOM_BOX_TYPE_AV01); + if (!entry) return GF_OUT_OF_MEM; + entry->av1_config = (GF_AV1ConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_AV1C); + if (!entry->av1_config) return GF_OUT_OF_MEM; + entry->av1_config->config = AV1_DuplicateConfig(cfg); + if (!entry->av1_config->config) return GF_OUT_OF_MEM; + entry->dataReferenceIndex = dataRefIndex; + *outDescriptionIndex = gf_list_count(stsd->child_boxes); + return e; +} + + typedef enum { GF_ISOM_HVCC_UPDATE = 0, @@ -1504,9 +1939,11 @@ typedef enum GF_ISOM_HVCC_SET_LHVC, GF_ISOM_HVCC_SET_LHVC_WITH_BASE, GF_ISOM_HVCC_SET_LHVC_WITH_BASE_BACKWARD, + GF_ISOM_HVCC_SET_HEVC_TILE_BASE, + GF_ISOM_LHCC_SET_INBAND } HevcConfigUpdateType; -static Bool hevc_cleanup_config(GF_HEVCConfig *cfg, HevcConfigUpdateType operand_type) +static Bool hevc_cleanup_config(GF_HEVCConfig *cfg, HevcConfigUpdateType operand_type, Bool keep_xps) { u32 i; Bool array_incomplete = (operand_type==GF_ISOM_HVCC_SET_INBAND) ? 1 : 0; @@ -1516,11 +1953,12 @@ static Bool hevc_cleanup_config(GF_HEVCConfig *cfg, HevcConfigUpdateType operand GF_HEVCParamArray *ar = (GF_HEVCParamArray*)gf_list_get(cfg->param_array, i); /*we want to force hev1*/ - if (operand_type==GF_ISOM_HVCC_SET_INBAND) + if (operand_type==GF_ISOM_HVCC_SET_INBAND) { ar->array_completeness = 0; - - if (!ar->array_completeness) { - array_incomplete = 1; + if (keep_xps) { + array_incomplete=1; + continue; + } while (gf_list_count(ar->nalus)) { GF_AVCConfigSlot *sl = (GF_AVCConfigSlot*)gf_list_get(ar->nalus, 0); @@ -1530,27 +1968,32 @@ static Bool hevc_cleanup_config(GF_HEVCConfig *cfg, HevcConfigUpdateType operand } gf_list_del(ar->nalus); gf_free(ar); + ar=NULL; gf_list_rem(cfg->param_array, i); i--; - + continue; } + if (ar && !ar->array_completeness) + array_incomplete = 1; } return array_incomplete; } static -GF_Err gf_isom_hevc_config_update_ex(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_HEVCConfig *cfg, u32 operand_type) +GF_Err gf_isom_hevc_config_update_ex(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_HEVCConfig *cfg, u32 operand_type, Bool keep_xps) { u32 array_incomplete; GF_TrackBox *trak; GF_Err e; GF_MPEGVisualSampleEntryBox *entry; + GF_SampleDescriptionBox *stsd; e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return GF_BAD_PARAM; - entry = (GF_MPEGVisualSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, DescriptionIndex-1); + stsd = trak->Media->information->sampleTable->SampleDescription; + entry = (GF_MPEGVisualSampleEntryBox *)gf_list_get(stsd->child_boxes, DescriptionIndex-1); if (!entry) return GF_BAD_PARAM; switch (entry->type) { case GF_ISOM_BOX_TYPE_HVC1: @@ -1572,25 +2015,29 @@ GF_Err gf_isom_hevc_config_update_ex(GF_ISOFile *the_file, u32 trackNumber, u32 else if (entry->type==GF_ISOM_BOX_TYPE_HEV1) entry->type = GF_ISOM_BOX_TYPE_HEV2; } else if (operand_type == GF_ISOM_HVCC_SET_TILE) { - if (!entry->hevc_config) entry->hevc_config = (GF_HEVCConfigurationBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_HVCC); + if (!entry->hevc_config) entry->hevc_config = (GF_HEVCConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_HVCC); + if (!entry->hevc_config) return GF_OUT_OF_MEM; if (entry->hevc_config->config) gf_odf_hevc_cfg_del(entry->hevc_config->config); entry->hevc_config->config = NULL; entry->type = GF_ISOM_BOX_TYPE_HVT1; } else if (operand_type < GF_ISOM_HVCC_SET_LHVC) { - if ((operand_type != GF_ISOM_HVCC_SET_INBAND) && !entry->hevc_config) - entry->hevc_config = (GF_HEVCConfigurationBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_HVCC); + if ((operand_type != GF_ISOM_HVCC_SET_INBAND) && !entry->hevc_config) { + entry->hevc_config = (GF_HEVCConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_HVCC); + if (!entry->hevc_config) return GF_OUT_OF_MEM; + } if (cfg) { if (entry->hevc_config->config) gf_odf_hevc_cfg_del(entry->hevc_config->config); entry->hevc_config->config = HEVC_DuplicateConfig(cfg); - } else if (operand_type != GF_ISOM_HVCC_SET_TILE) { + } else { operand_type=GF_ISOM_HVCC_SET_INBAND; } array_incomplete = (operand_type==GF_ISOM_HVCC_SET_INBAND) ? 1 : 0; - if (entry->hevc_config && hevc_cleanup_config(entry->hevc_config->config, operand_type)) + if (entry->hevc_config && hevc_cleanup_config(entry->hevc_config->config, operand_type, keep_xps)) { array_incomplete=1; + } - if (entry->lhvc_config && hevc_cleanup_config(entry->lhvc_config->config, operand_type)) + if (entry->lhvc_config && hevc_cleanup_config(entry->lhvc_config->config, operand_type, keep_xps)) array_incomplete=1; switch (entry->type) { @@ -1610,21 +2057,27 @@ GF_Err gf_isom_hevc_config_update_ex(GF_ISOFile *the_file, u32 trackNumber, u32 } else { /*SVCC replacement/removal with HEVC base, backward compatible signaling*/ - if ((operand_type==GF_ISOM_HVCC_SET_LHVC_WITH_BASE_BACKWARD) || (operand_type==GF_ISOM_HVCC_SET_LHVC_WITH_BASE)) { + if ((operand_type==GF_ISOM_HVCC_SET_LHVC_WITH_BASE_BACKWARD) || (operand_type==GF_ISOM_HVCC_SET_LHVC_WITH_BASE) || (operand_type==GF_ISOM_HVCC_SET_HEVC_TILE_BASE) ) { if (!entry->hevc_config) return GF_BAD_PARAM; if (!cfg) { if (entry->lhvc_config) { - gf_isom_box_del((GF_Box*)entry->lhvc_config); + gf_isom_box_del_parent(&entry->child_boxes, (GF_Box*)entry->lhvc_config); entry->lhvc_config = NULL; } if (entry->type==GF_ISOM_BOX_TYPE_LHE1) entry->type = (operand_type==GF_ISOM_HVCC_SET_LHVC_WITH_BASE) ? GF_ISOM_BOX_TYPE_HEV2 : GF_ISOM_BOX_TYPE_HEV1; else if (entry->type==GF_ISOM_BOX_TYPE_HEV1) entry->type = (operand_type==GF_ISOM_HVCC_SET_LHVC_WITH_BASE) ? GF_ISOM_BOX_TYPE_HEV2 : GF_ISOM_BOX_TYPE_HEV1; else entry->type = (operand_type==GF_ISOM_HVCC_SET_LHVC_WITH_BASE) ? GF_ISOM_BOX_TYPE_HVC2 : GF_ISOM_BOX_TYPE_HVC1; } else { - if (!entry->lhvc_config) entry->lhvc_config = (GF_HEVCConfigurationBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_LHVC); - if (entry->lhvc_config->config) gf_odf_hevc_cfg_del(entry->lhvc_config->config); - entry->lhvc_config->config = HEVC_DuplicateConfig(cfg); - + if (operand_type != GF_ISOM_HVCC_SET_HEVC_TILE_BASE) { + if (!entry->lhvc_config) { + entry->lhvc_config = (GF_HEVCConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_LHVC); + if (!entry->lhvc_config) return GF_OUT_OF_MEM; + } + if (entry->lhvc_config->config) gf_odf_hevc_cfg_del(entry->lhvc_config->config); + entry->lhvc_config->config = HEVC_DuplicateConfig(cfg); + if (!entry->lhvc_config->config) return GF_OUT_OF_MEM; + } + if (operand_type==GF_ISOM_HVCC_SET_LHVC_WITH_BASE_BACKWARD) { if (entry->type==GF_ISOM_BOX_TYPE_HEV2) entry->type = GF_ISOM_BOX_TYPE_HEV1; else entry->type = GF_ISOM_BOX_TYPE_HVC1; @@ -1637,18 +2090,26 @@ GF_Err gf_isom_hevc_config_update_ex(GF_ISOFile *the_file, u32 trackNumber, u32 /*LHEVC track without base*/ else if (operand_type==GF_ISOM_HVCC_SET_LHVC) { if (entry->hevc_config) { - gf_isom_box_del((GF_Box*)entry->hevc_config); + gf_isom_box_del_parent(&entry->child_boxes, (GF_Box*)entry->hevc_config); entry->hevc_config=NULL; } if (!cfg) return GF_BAD_PARAM; - if (!entry->lhvc_config) entry->lhvc_config = (GF_HEVCConfigurationBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_LHVC); + if (!entry->lhvc_config) { + entry->lhvc_config = (GF_HEVCConfigurationBox*)gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_LHVC); + if (!entry->lhvc_config) return GF_OUT_OF_MEM; + } if (entry->lhvc_config->config) gf_odf_hevc_cfg_del(entry->lhvc_config->config); entry->lhvc_config->config = HEVC_DuplicateConfig(cfg); + if (!entry->lhvc_config->config) return GF_OUT_OF_MEM; if ((entry->type==GF_ISOM_BOX_TYPE_HEV1) || (entry->type==GF_ISOM_BOX_TYPE_HEV2)) entry->type = GF_ISOM_BOX_TYPE_LHE1; else entry->type = GF_ISOM_BOX_TYPE_LHV1; } + /*LHEVC inband, no config change*/ + else if (operand_type==GF_ISOM_LHCC_SET_INBAND) { + entry->type = GF_ISOM_BOX_TYPE_LHE1; + } } HEVC_RewriteESDescriptor(entry); @@ -1658,31 +2119,41 @@ GF_Err gf_isom_hevc_config_update_ex(GF_ISOFile *the_file, u32 trackNumber, u32 GF_EXPORT GF_Err gf_isom_hevc_config_update(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_HEVCConfig *cfg) { - return gf_isom_hevc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, GF_ISOM_HVCC_UPDATE); + return gf_isom_hevc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, GF_ISOM_HVCC_UPDATE, GF_FALSE); +} + +GF_EXPORT +GF_Err gf_isom_hevc_set_inband_config(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, Bool keep_xps) +{ + return gf_isom_hevc_config_update_ex(the_file, trackNumber, DescriptionIndex, NULL, GF_ISOM_HVCC_SET_INBAND, keep_xps); } GF_EXPORT -GF_Err gf_isom_hevc_set_inband_config(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex) +GF_Err gf_isom_lhvc_force_inband_config(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex) { - return gf_isom_hevc_config_update_ex(the_file, trackNumber, DescriptionIndex, NULL, GF_ISOM_HVCC_SET_INBAND); + return gf_isom_hevc_config_update_ex(the_file, trackNumber, DescriptionIndex, NULL, GF_ISOM_LHCC_SET_INBAND, GF_FALSE); } + GF_EXPORT GF_Err gf_isom_hevc_set_tile_config(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_HEVCConfig *cfg, Bool is_base_track) { - return gf_isom_hevc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, is_base_track ? GF_ISOM_HVCC_SET_TILE_BASE_TRACK : GF_ISOM_HVCC_SET_TILE); + return gf_isom_hevc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, is_base_track ? GF_ISOM_HVCC_SET_TILE_BASE_TRACK : GF_ISOM_HVCC_SET_TILE, GF_FALSE); } +GF_EXPORT GF_Err gf_isom_lhvc_config_update(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex, GF_HEVCConfig *cfg, GF_ISOMLHEVCTrackType track_type) { if (cfg) cfg->is_lhvc = GF_TRUE; switch (track_type) { case GF_ISOM_LEHVC_ONLY: - return gf_isom_hevc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, GF_ISOM_HVCC_SET_LHVC); + return gf_isom_hevc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, GF_ISOM_HVCC_SET_LHVC, GF_FALSE); case GF_ISOM_LEHVC_WITH_BASE: - return gf_isom_hevc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, GF_ISOM_HVCC_SET_LHVC_WITH_BASE); + return gf_isom_hevc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, GF_ISOM_HVCC_SET_LHVC_WITH_BASE, GF_FALSE); case GF_ISOM_LEHVC_WITH_BASE_BACKWARD: - return gf_isom_hevc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, GF_ISOM_HVCC_SET_LHVC_WITH_BASE_BACKWARD); + return gf_isom_hevc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, GF_ISOM_HVCC_SET_LHVC_WITH_BASE_BACKWARD, GF_FALSE); + case GF_ISOM_HEVC_TILE_BASE: + return gf_isom_hevc_config_update_ex(the_file, trackNumber, DescriptionIndex, cfg, GF_ISOM_HVCC_SET_HEVC_TILE_BASE, GF_FALSE); default: return GF_BAD_PARAM; } @@ -1690,26 +2161,31 @@ GF_Err gf_isom_lhvc_config_update(GF_ISOFile *the_file, u32 trackNumber, u32 Des #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_EXPORT -GF_Box *gf_isom_clone_config_box(GF_Box *box) -{ - GF_Box *clone; - switch (box->type) - { - case GF_ISOM_BOX_TYPE_AVCC: - case GF_ISOM_BOX_TYPE_SVCC: - case GF_ISOM_BOX_TYPE_MVCC: - clone = gf_isom_box_new(box->type); - ((GF_AVCConfigurationBox *)clone)->config = AVC_DuplicateConfig(((GF_AVCConfigurationBox *)box)->config); - break; - case GF_ISOM_BOX_TYPE_HVCC: - clone = gf_isom_box_new(box->type); - ((GF_HEVCConfigurationBox *)clone)->config = HEVC_DuplicateConfig(((GF_HEVCConfigurationBox *)box)->config); - break; - default: +GF_EXPORT +GF_Box *gf_isom_clone_config_box(GF_Box *box) +{ + u8 *data=NULL; + u32 size=0; + GF_Err e; + GF_Box *clone=NULL; + GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); + e = gf_isom_box_size(box); + if (!e) + e = gf_isom_box_write(box, bs); + + gf_bs_get_content(bs, &data, &size); + gf_bs_del(bs); + if (!e) { + bs = gf_bs_new(data, size, GF_BITSTREAM_READ); + e = gf_isom_box_parse(&clone, bs); + gf_bs_del(bs); + } + if (data) + gf_free(data); + if (e) { + if (clone) + gf_isom_box_del(clone); clone = NULL; - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Cloning of config not supported for type %s\n", gf_4cc_to_str(box->type))); - break; } return clone; } @@ -1724,7 +2200,7 @@ GF_AVCConfig *gf_isom_avc_config_get(GF_ISOFile *the_file, u32 trackNumber, u32 if (gf_isom_get_avc_svc_type(the_file, trackNumber, DescriptionIndex)==GF_ISOM_AVCTYPE_NONE) return NULL; - entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, DescriptionIndex-1); + entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex-1); if (!entry) return NULL; if (!entry->avc_config) return NULL; @@ -1747,7 +2223,7 @@ GF_HEVCConfig *gf_isom_hevc_config_get(GF_ISOFile *the_file, u32 trackNumber, u3 if (!trak || !trak->Media || !DescriptionIndex) return NULL; if (gf_isom_get_hevc_lhvc_type(the_file, trackNumber, DescriptionIndex)==GF_ISOM_HEVCTYPE_NONE) return NULL; - entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, DescriptionIndex-1); + entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex-1); if (!entry) return NULL; if (!entry->hevc_config) return NULL; return HEVC_DuplicateConfig(entry->hevc_config->config); @@ -1762,7 +2238,7 @@ GF_AVCConfig *gf_isom_svc_config_get(GF_ISOFile *the_file, u32 trackNumber, u32 if (!trak || !trak->Media || !DescriptionIndex) return NULL; if (gf_isom_get_avc_svc_type(the_file, trackNumber, DescriptionIndex)==GF_ISOM_AVCTYPE_NONE) return NULL; - entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, DescriptionIndex-1); + entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex-1); if (!entry) return NULL; if (!entry->svc_config) return NULL; return AVC_DuplicateConfig(entry->svc_config->config); @@ -1778,29 +2254,73 @@ GF_AVCConfig *gf_isom_mvc_config_get(GF_ISOFile *the_file, u32 trackNumber, u32 if (!trak || !trak->Media || !DescriptionIndex) return NULL; if (gf_isom_get_avc_svc_type(the_file, trackNumber, DescriptionIndex)==GF_ISOM_AVCTYPE_NONE) return NULL; - entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, DescriptionIndex-1); + entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex-1); if (!entry) return NULL; if (!entry->mvc_config) return NULL; return AVC_DuplicateConfig(entry->mvc_config->config); } +GF_EXPORT +GF_AV1Config *gf_isom_av1_config_get(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex) +{ + GF_TrackBox *trak; + GF_MPEGVisualSampleEntryBox *entry; + if (gf_isom_get_reference_count(the_file, trackNumber, GF_ISOM_REF_TBAS)) { + u32 ref_track; + GF_Err e = gf_isom_get_reference(the_file, trackNumber, GF_ISOM_REF_TBAS, 1, &ref_track); + if (e == GF_OK) { + trackNumber = ref_track; + } + } + trak = gf_isom_get_track_from_file(the_file, trackNumber); + if (!trak || !trak->Media || !DescriptionIndex) return NULL; + entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex - 1); + if (!entry || !entry->av1_config) return NULL; + return AV1_DuplicateConfig(entry->av1_config->config); +} + +GF_EXPORT +GF_VPConfig *gf_isom_vp_config_get(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex) +{ + GF_TrackBox *trak; + GF_MPEGVisualSampleEntryBox *entry; + trak = gf_isom_get_track_from_file(the_file, trackNumber); + if (!trak || !trak->Media || !DescriptionIndex) return NULL; + entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex - 1); + if (!entry || !entry->vp_config) return NULL; + return VP_DuplicateConfig(entry->vp_config->config); +} GF_EXPORT -u32 gf_isom_get_avc_svc_type(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex) +GF_DOVIDecoderConfigurationRecord *gf_isom_dovi_config_get(GF_ISOFile* the_file, u32 trackNumber, u32 DescriptionIndex) +{ + GF_TrackBox* trak; + GF_MPEGVisualSampleEntryBox *entry; + trak = gf_isom_get_track_from_file(the_file, trackNumber); + if (!trak || !trak->Media || !DescriptionIndex) return NULL; + entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex - 1); + if (!entry || !entry->dovi_config) return NULL; + return DOVI_DuplicateConfig(&entry->dovi_config->DOVIConfig); +} + +GF_EXPORT +GF_ISOMAVCType gf_isom_get_avc_svc_type(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex) { u32 type; GF_TrackBox *trak; GF_MPEGVisualSampleEntryBox *entry; trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return GF_ISOM_AVCTYPE_NONE; - if (trak->Media->handler->handlerType != GF_ISOM_MEDIA_VISUAL) return GF_ISOM_AVCTYPE_NONE; - entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, DescriptionIndex-1); + if (!gf_isom_is_video_handler_type(trak->Media->handler->handlerType)) + return GF_ISOM_AVCTYPE_NONE; + + entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex-1); if (!entry) return GF_ISOM_AVCTYPE_NONE; type = entry->type; if (type == GF_ISOM_BOX_TYPE_ENCV) { - GF_ProtectionSchemeInfoBox *sinf = (GF_ProtectionSchemeInfoBox *) gf_list_get(entry->protections, 0); + GF_ProtectionSchemeInfoBox *sinf = (GF_ProtectionSchemeInfoBox *) gf_isom_box_find_child(entry->child_boxes, GF_ISOM_BOX_TYPE_SINF); if (sinf && sinf->original_format) type = sinf->original_format->data_format; } else if (type == GF_ISOM_BOX_TYPE_RESV) { @@ -1827,26 +2347,31 @@ u32 gf_isom_get_avc_svc_type(GF_ISOFile *the_file, u32 trackNumber, u32 Descript } GF_EXPORT -u32 gf_isom_get_hevc_lhvc_type(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex) +GF_ISOMHEVCType gf_isom_get_hevc_lhvc_type(GF_ISOFile *the_file, u32 trackNumber, u32 DescriptionIndex) { u32 type; GF_TrackBox *trak; GF_MPEGVisualSampleEntryBox *entry; trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak || !trak->Media || !DescriptionIndex) return GF_ISOM_HEVCTYPE_NONE; - if (trak->Media->handler->handlerType != GF_ISOM_MEDIA_VISUAL) return GF_ISOM_HEVCTYPE_NONE; - entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, DescriptionIndex-1); - if (!entry) return GF_ISOM_AVCTYPE_NONE; + if (!gf_isom_is_video_handler_type(trak->Media->handler->handlerType)) + return GF_ISOM_HEVCTYPE_NONE; + entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex-1); + if (!entry) return GF_ISOM_HEVCTYPE_NONE; type = entry->type; if (type == GF_ISOM_BOX_TYPE_ENCV) { - GF_ProtectionSchemeInfoBox *sinf = (GF_ProtectionSchemeInfoBox *) gf_list_get(entry->protections, 0); + GF_ProtectionSchemeInfoBox *sinf = (GF_ProtectionSchemeInfoBox *) gf_isom_box_find_child(entry->child_boxes, GF_ISOM_BOX_TYPE_SINF); if (sinf && sinf->original_format) type = sinf->original_format->data_format; } else if (type == GF_ISOM_BOX_TYPE_RESV) { if (entry->rinf && entry->rinf->original_format) type = entry->rinf->original_format->data_format; } + if (type == GF_ISOM_BOX_TYPE_DVHE) { + type = GF_ISOM_BOX_TYPE_HEV1; + } + switch (type) { case GF_ISOM_BOX_TYPE_HVC1: case GF_ISOM_BOX_TYPE_HEV1: @@ -1876,7 +2401,7 @@ GF_HEVCConfig *gf_isom_lhvc_config_get(GF_ISOFile *the_file, u32 trackNumber, u3 if (!trak || !trak->Media || !DescriptionIndex) return NULL; if (gf_isom_get_hevc_lhvc_type(the_file, trackNumber, DescriptionIndex)==GF_ISOM_HEVCTYPE_NONE) return NULL; - entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, DescriptionIndex-1); + entry = (GF_MPEGVisualSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, DescriptionIndex-1); if (!entry) return NULL; if (!entry->lhvc_config) return NULL; lhvc = HEVC_DuplicateConfig(entry->lhvc_config->config); @@ -1897,20 +2422,21 @@ GF_HEVCConfig *gf_isom_lhvc_config_get(GF_ISOFile *the_file, u32 trackNumber, u3 } -void btrt_del(GF_Box *s) +void btrt_box_del(GF_Box *s) { GF_BitRateBox *ptr = (GF_BitRateBox *)s; if (ptr) gf_free(ptr); } -GF_Err btrt_Read(GF_Box *s, GF_BitStream *bs) +GF_Err btrt_box_read(GF_Box *s, GF_BitStream *bs) { GF_BitRateBox *ptr = (GF_BitRateBox *)s; + ISOM_DECREASE_SIZE(ptr, 12) ptr->bufferSizeDB = gf_bs_read_u32(bs); ptr->maxBitrate = gf_bs_read_u32(bs); ptr->avgBitrate = gf_bs_read_u32(bs); return GF_OK; } -GF_Box *btrt_New() +GF_Box *btrt_box_new() { GF_BitRateBox *tmp = (GF_BitRateBox *) gf_malloc(sizeof(GF_BitRateBox)); if (tmp == NULL) return NULL; @@ -1920,7 +2446,7 @@ GF_Box *btrt_New() } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err btrt_Write(GF_Box *s, GF_BitStream *bs) +GF_Err btrt_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_BitRateBox *ptr = (GF_BitRateBox *) s; @@ -1932,7 +2458,7 @@ GF_Err btrt_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u32(bs, ptr->avgBitrate); return GF_OK; } -GF_Err btrt_Size(GF_Box *s) +GF_Err btrt_box_size(GF_Box *s) { GF_BitRateBox *ptr = (GF_BitRateBox *)s; ptr->size += 12; @@ -1942,14 +2468,14 @@ GF_Err btrt_Size(GF_Box *s) -void m4ds_del(GF_Box *s) +void m4ds_box_del(GF_Box *s) { GF_MPEG4ExtensionDescriptorsBox *ptr = (GF_MPEG4ExtensionDescriptorsBox *)s; gf_odf_desc_list_del(ptr->descriptors); gf_list_del(ptr->descriptors); gf_free(ptr); } -GF_Err m4ds_Read(GF_Box *s, GF_BitStream *bs) +GF_Err m4ds_box_read(GF_Box *s, GF_BitStream *bs) { GF_Err e; char *enc_od; @@ -1962,7 +2488,7 @@ GF_Err m4ds_Read(GF_Box *s, GF_BitStream *bs) gf_free(enc_od); return e; } -GF_Box *m4ds_New() +GF_Box *m4ds_box_new() { GF_MPEG4ExtensionDescriptorsBox *tmp = (GF_MPEG4ExtensionDescriptorsBox *) gf_malloc(sizeof(GF_MPEG4ExtensionDescriptorsBox)); if (tmp == NULL) return NULL; @@ -1973,10 +2499,10 @@ GF_Box *m4ds_New() } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err m4ds_Write(GF_Box *s, GF_BitStream *bs) +GF_Err m4ds_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; - char *enc_ods; + u8 *enc_ods; u32 enc_od_size; GF_MPEG4ExtensionDescriptorsBox *ptr = (GF_MPEG4ExtensionDescriptorsBox *) s; if (!s) return GF_BAD_PARAM; @@ -1992,7 +2518,7 @@ GF_Err m4ds_Write(GF_Box *s, GF_BitStream *bs) } return GF_OK; } -GF_Err m4ds_Size(GF_Box *s) +GF_Err m4ds_box_size(GF_Box *s) { GF_Err e; u32 descSize = 0; @@ -2005,20 +2531,24 @@ GF_Err m4ds_Size(GF_Box *s) -void avcc_del(GF_Box *s) +void avcc_box_del(GF_Box *s) { GF_AVCConfigurationBox *ptr = (GF_AVCConfigurationBox *)s; if (ptr->config) gf_odf_avc_cfg_del(ptr->config); + ptr->config = NULL; gf_free(ptr); } -GF_Err avcc_Read(GF_Box *s, GF_BitStream *bs) +GF_Err avcc_box_read(GF_Box *s, GF_BitStream *bs) { u32 i, count; GF_AVCConfigurationBox *ptr = (GF_AVCConfigurationBox *)s; if (ptr->config) gf_odf_avc_cfg_del(ptr->config); ptr->config = gf_odf_avc_cfg_new(); + + ISOM_DECREASE_SIZE(ptr, 7) //7 includes the 2 counts of sps and pps + ptr->config->configurationVersion = gf_bs_read_u8(bs); ptr->config->AVCProfileIndication = gf_bs_read_u8(bs); ptr->config->profile_compatibility = gf_bs_read_u8(bs); @@ -2033,22 +2563,27 @@ GF_Err avcc_Read(GF_Box *s, GF_BitStream *bs) gf_bs_read_int(bs, 3); count = gf_bs_read_int(bs, 5); - ptr->size -= 7; //including 2nd count - for (i=0; isize = gf_bs_read_u16(bs); + if (!sl->size || (gf_bs_available(bs) < sl->size) || (ptr->size < sl->size) ) { + gf_free(sl); + GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("AVCC: Not enough bits to parse. Aborting.\n")); + return GF_ISOM_INVALID_FILE; + } sl->data = (char *)gf_malloc(sizeof(char) * sl->size); gf_bs_read_data(bs, sl->data, sl->size); gf_list_add(ptr->config->sequenceParameterSets, sl); - ptr->size -= 2+sl->size; + ptr->size -= sl->size; } count = gf_bs_read_u8(bs); for (i=0; isize = gf_bs_read_u16(bs); - if (gf_bs_available(bs) < sl->size) { + if (!sl->size || (gf_bs_available(bs) < sl->size) || (ptr->sizesize)) { gf_free(sl); GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("AVCC: Not enough bits to parse. Aborting.\n")); return GF_ISOM_INVALID_FILE; @@ -2056,7 +2591,7 @@ GF_Err avcc_Read(GF_Box *s, GF_BitStream *bs) sl->data = (char *)gf_malloc(sizeof(char) * sl->size); gf_bs_read_data(bs, sl->data, sl->size); gf_list_add(ptr->config->pictureParameterSets, sl); - ptr->size -= 2+sl->size; + ptr->size -= sl->size; } if (ptr->type==GF_ISOM_BOX_TYPE_AVCC) { @@ -2064,9 +2599,10 @@ GF_Err avcc_Read(GF_Box *s, GF_BitStream *bs) if (!ptr->size) { #ifndef GPAC_DISABLE_AV_PARSERS AVCState avc; - s32 idx, vui_flag_pos; + s32 idx; GF_AVCConfigSlot *sl = (GF_AVCConfigSlot*)gf_list_get(ptr->config->sequenceParameterSets, 0); - idx = gf_media_avc_read_sps(sl->data+1, sl->size-1, &avc, 0, (u32 *) &vui_flag_pos); + idx = sl ? gf_media_avc_read_sps(sl->data+1, sl->size-1, &avc, 0, NULL) : -1; + if (idx>=0) { ptr->config->chroma_format = avc.sps[idx].chroma_format; ptr->config->luma_bit_depth = 8 + avc.sps[idx].luma_bit_depth_m8; @@ -2080,6 +2616,7 @@ GF_Err avcc_Read(GF_Box *s, GF_BitStream *bs) #endif return GF_OK; } + ISOM_DECREASE_SIZE(ptr, 4) gf_bs_read_int(bs, 6); ptr->config->chroma_format = gf_bs_read_int(bs, 2); gf_bs_read_int(bs, 5); @@ -2088,7 +2625,6 @@ GF_Err avcc_Read(GF_Box *s, GF_BitStream *bs) ptr->config->chroma_bit_depth = 8 + gf_bs_read_int(bs, 3); count = gf_bs_read_int(bs, 8); - ptr->size -= 4; if (count*2 > ptr->size) { //ffmpeg just ignores this part while allocating bytes (filled with garbage?) GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("AVCC: invalid numOfSequenceParameterSetExt value. Skipping.\n")); @@ -2098,8 +2634,9 @@ GF_Err avcc_Read(GF_Box *s, GF_BitStream *bs) ptr->config->sequenceParameterSetExtensions = gf_list_new(); for (i=0; isize = gf_bs_read_u16(bs); - if (gf_bs_available(bs) < sl->size) { + if ((gf_bs_available(bs) < sl->size) || (ptr->sizesize)) { gf_free(sl); GF_LOG(GF_LOG_ERROR, GF_LOG_CODING, ("AVCC: Not enough bits to parse. Aborting.\n")); return GF_ISOM_INVALID_FILE; @@ -2107,7 +2644,7 @@ GF_Err avcc_Read(GF_Box *s, GF_BitStream *bs) sl->data = (char *)gf_malloc(sizeof(char) * sl->size); gf_bs_read_data(bs, sl->data, sl->size); gf_list_add(ptr->config->sequenceParameterSetExtensions, sl); - ptr->size -= sl->size + 2; + ptr->size -= sl->size; } } } @@ -2115,7 +2652,7 @@ GF_Err avcc_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *avcc_New() +GF_Box *avcc_box_new() { GF_AVCConfigurationBox *tmp = (GF_AVCConfigurationBox *) gf_malloc(sizeof(GF_AVCConfigurationBox)); if (tmp == NULL) return NULL; @@ -2125,7 +2662,7 @@ GF_Box *avcc_New() } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err avcc_Write(GF_Box *s, GF_BitStream *bs) +GF_Err avcc_box_write(GF_Box *s, GF_BitStream *bs) { u32 i, count; GF_Err e; @@ -2184,7 +2721,7 @@ GF_Err avcc_Write(GF_Box *s, GF_BitStream *bs) } return GF_OK; } -GF_Err avcc_Size(GF_Box *s) +GF_Err avcc_box_size(GF_Box *s) { u32 i, count; GF_AVCConfigurationBox *ptr = (GF_AVCConfigurationBox *)s; @@ -2216,29 +2753,29 @@ GF_Err avcc_Size(GF_Box *s) -void hvcc_del(GF_Box *s) +void hvcc_box_del(GF_Box *s) { GF_HEVCConfigurationBox *ptr = (GF_HEVCConfigurationBox*)s; if (ptr->config) gf_odf_hevc_cfg_del(ptr->config); gf_free(ptr); } -GF_Err hvcc_Read(GF_Box *s, GF_BitStream *bs) +GF_Err hvcc_box_read(GF_Box *s, GF_BitStream *bs) { - u64 pos; + u64 consummed; GF_HEVCConfigurationBox *ptr = (GF_HEVCConfigurationBox *)s; if (ptr->config) gf_odf_hevc_cfg_del(ptr->config); - pos = gf_bs_get_position(bs); + consummed = gf_bs_get_position(bs); ptr->config = gf_odf_hevc_cfg_read_bs(bs, (s->type == GF_ISOM_BOX_TYPE_HVCC) ? GF_FALSE : GF_TRUE); - pos = gf_bs_get_position(bs) - pos ; - if (pos < ptr->size) - ptr->size -= (u32) pos; + consummed = gf_bs_get_position(bs) - consummed ; + ISOM_DECREASE_SIZE(ptr, (u32)consummed) return ptr->config ? GF_OK : GF_ISOM_INVALID_FILE; } -GF_Box *hvcc_New() + +GF_Box *hvcc_box_new() { GF_HEVCConfigurationBox *tmp = (GF_HEVCConfigurationBox *) gf_malloc(sizeof(GF_HEVCConfigurationBox)); if (tmp == NULL) return NULL; @@ -2248,7 +2785,7 @@ GF_Box *hvcc_New() } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err hvcc_Write(GF_Box *s, GF_BitStream *bs) +GF_Err hvcc_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_HEVCConfigurationBox *ptr = (GF_HEVCConfigurationBox *) s; @@ -2259,7 +2796,8 @@ GF_Err hvcc_Write(GF_Box *s, GF_BitStream *bs) return gf_odf_hevc_cfg_write_bs(ptr->config, bs); } -GF_Err hvcc_Size(GF_Box *s) + +GF_Err hvcc_box_size(GF_Box *s) { u32 i, count, j, subcount; GF_HEVCConfigurationBox *ptr = (GF_HEVCConfigurationBox *)s; @@ -2268,7 +2806,7 @@ GF_Err hvcc_Size(GF_Box *s) ptr->size = 0; return GF_OK; } - + if (!ptr->config->is_lhvc) ptr->size += 23; else @@ -2285,8 +2823,265 @@ GF_Err hvcc_Size(GF_Box *s) } return GF_OK; } + +GF_Box *av1c_box_new() { + GF_AV1ConfigurationBox *tmp = (GF_AV1ConfigurationBox *)gf_malloc(sizeof(GF_AV1ConfigurationBox)); + if (tmp == NULL) return NULL; + memset(tmp, 0, sizeof(GF_AV1ConfigurationBox)); + tmp->type = GF_ISOM_BOX_TYPE_AV1C; + return (GF_Box *)tmp; +} + +void av1c_box_del(GF_Box *s) { + GF_AV1ConfigurationBox *ptr = (GF_AV1ConfigurationBox*)s; + if (ptr->config) gf_odf_av1_cfg_del(ptr->config); + gf_free(ptr); +} + +GF_Err av1c_box_read(GF_Box *s, GF_BitStream *bs) +{ + u64 pos, read; + GF_AV1ConfigurationBox *ptr = (GF_AV1ConfigurationBox*)s; + + if (ptr->config) gf_odf_av1_cfg_del(ptr->config); + + pos = gf_bs_get_position(bs); + + ptr->config = gf_odf_av1_cfg_read_bs_size(bs, (u32) ptr->size); + + read = gf_bs_get_position(bs) - pos; + + if (read < ptr->size) + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[ISOBMFF] AV1ConfigurationBox: read only "LLU" bytes (expected "LLU").\n", read, ptr->size)); + if (read > ptr->size) + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[ISOBMFF] AV1ConfigurationBox overflow read "LLU" bytes, of box size "LLU".\n", read, ptr->size)); + + return GF_OK; +} + +GF_Err av1c_box_write(GF_Box *s, GF_BitStream *bs) { + GF_Err e; + GF_AV1ConfigurationBox *ptr = (GF_AV1ConfigurationBox*)s; + if (!s) return GF_BAD_PARAM; + if (!ptr->config) return GF_BAD_PARAM; + e = gf_isom_box_write_header(s, bs); + if (e) return e; + + return gf_odf_av1_cfg_write_bs(ptr->config, bs); +} + +GF_Err av1c_box_size(GF_Box *s) { + u32 i; + GF_AV1ConfigurationBox *ptr = (GF_AV1ConfigurationBox *)s; + + if (!ptr->config) { + ptr->size = 0; + return GF_BAD_PARAM; + } + + ptr->size += 4; + + for (i = 0; i < gf_list_count(ptr->config->obu_array); ++i) { + GF_AV1_OBUArrayEntry *a = gf_list_get(ptr->config->obu_array, i); + ptr->size += a->obu_length; + } + + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + + + +void vpcc_box_del(GF_Box *s) +{ + GF_VPConfigurationBox *ptr = (GF_VPConfigurationBox*)s; + if (ptr->config) gf_odf_vp_cfg_del(ptr->config); + ptr->config = NULL; + gf_free(ptr); +} + +GF_Err vpcc_box_read(GF_Box *s, GF_BitStream *bs) +{ + u64 pos; + GF_VPConfigurationBox *ptr = (GF_VPConfigurationBox *)s; + + if (ptr->config) gf_odf_vp_cfg_del(ptr->config); + ptr->config = NULL; + + pos = gf_bs_get_position(bs); + ptr->config = gf_odf_vp_cfg_read_bs(bs, ptr->version == 0); + pos = gf_bs_get_position(bs) - pos ; + + if (pos < ptr->size) + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[ISOBMFF] VPConfigurationBox: read only "LLU" bytes (expected "LLU").\n", pos, ptr->size)); + if (pos > ptr->size) + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[ISOBMFF] VPConfigurationBox overflow read "LLU" bytes, of box size "LLU".\n", pos, ptr->size)); + + return ptr->config ? GF_OK : GF_ISOM_INVALID_FILE; +} + +GF_Box *vpcc_box_new() +{ + GF_VPConfigurationBox *tmp = (GF_VPConfigurationBox *) gf_malloc(sizeof(GF_VPConfigurationBox)); + if (tmp == NULL) return NULL; + memset(tmp, 0, sizeof(GF_VPConfigurationBox)); + tmp->type = GF_ISOM_BOX_TYPE_VPCC; + tmp->version = 1; + return (GF_Box *)tmp; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE +GF_Err vpcc_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_VPConfigurationBox *ptr = (GF_VPConfigurationBox *) s; + if (!s) return GF_BAD_PARAM; + if (!ptr->config) return GF_OK; + + e = gf_isom_full_box_write(s, bs); + if (e) return e; + + return gf_odf_vp_cfg_write_bs(ptr->config, bs, ptr->version == 0); +} +#endif + +GF_Err vpcc_box_size(GF_Box *s) +{ + GF_VPConfigurationBox *ptr = (GF_VPConfigurationBox *)s; + + if (!ptr->config) { + ptr->size = 0; + return GF_OK; + } + + if (ptr->version == 0) { + ptr->size += 6; + } else { + if (ptr->config->codec_initdata_size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[ISOBMFF] VPConfigurationBox: codec_initdata_size MUST be 0, was %d\n", ptr->config->codec_initdata_size)); + return GF_ISOM_INVALID_FILE; + } + + ptr->size += 8; + } + + return GF_OK; +} + + +GF_Box *SmDm_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_SMPTE2086MasteringDisplayMetadataBox, GF_ISOM_BOX_TYPE_SMDM); + return (GF_Box *)tmp; +} + +void SmDm_box_del(GF_Box *a) +{ + GF_SMPTE2086MasteringDisplayMetadataBox *p = (GF_SMPTE2086MasteringDisplayMetadataBox *)a; + gf_free(p); +} + +GF_Err SmDm_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_SMPTE2086MasteringDisplayMetadataBox *p = (GF_SMPTE2086MasteringDisplayMetadataBox *)s; + ISOM_DECREASE_SIZE(p, 24) + p->primaryRChromaticity_x = gf_bs_read_u16(bs); + p->primaryRChromaticity_y = gf_bs_read_u16(bs); + p->primaryGChromaticity_x = gf_bs_read_u16(bs); + p->primaryGChromaticity_y = gf_bs_read_u16(bs); + p->primaryBChromaticity_x = gf_bs_read_u16(bs); + p->primaryBChromaticity_y = gf_bs_read_u16(bs); + p->whitePointChromaticity_x = gf_bs_read_u16(bs); + p->whitePointChromaticity_y = gf_bs_read_u16(bs); + p->luminanceMax = gf_bs_read_u32(bs); + p->luminanceMin = gf_bs_read_u32(bs); + + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err SmDm_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_SMPTE2086MasteringDisplayMetadataBox *p = (GF_SMPTE2086MasteringDisplayMetadataBox*)s; + e = gf_isom_full_box_write(s, bs); + if (e) return e; + + gf_bs_write_u16(bs, p->primaryRChromaticity_x); + gf_bs_write_u16(bs, p->primaryRChromaticity_y); + gf_bs_write_u16(bs, p->primaryGChromaticity_x); + gf_bs_write_u16(bs, p->primaryGChromaticity_y); + gf_bs_write_u16(bs, p->primaryBChromaticity_x); + gf_bs_write_u16(bs, p->primaryBChromaticity_y); + gf_bs_write_u16(bs, p->whitePointChromaticity_x); + gf_bs_write_u16(bs, p->whitePointChromaticity_y); + gf_bs_write_u32(bs, p->luminanceMax); + gf_bs_write_u32(bs, p->luminanceMin); + + return GF_OK; +} + +GF_Err SmDm_box_size(GF_Box *s) +{ + GF_SMPTE2086MasteringDisplayMetadataBox *p = (GF_SMPTE2086MasteringDisplayMetadataBox*)s; + p->size += 24; + return GF_OK; +} + #endif /*GPAC_DISABLE_ISOM_WRITE*/ + +GF_Box *CoLL_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_VPContentLightLevelBox, GF_ISOM_BOX_TYPE_COLL); + return (GF_Box *)tmp; +} + +void CoLL_box_del(GF_Box *a) +{ + GF_VPContentLightLevelBox *p = (GF_VPContentLightLevelBox *)a; + gf_free(p); +} + +GF_Err CoLL_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_VPContentLightLevelBox *p = (GF_VPContentLightLevelBox *)s; + ISOM_DECREASE_SIZE(p, 4) + p->maxCLL = gf_bs_read_u16(bs); + p->maxFALL = gf_bs_read_u16(bs); + + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err CoLL_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_VPContentLightLevelBox *p = (GF_VPContentLightLevelBox*)s; + e = gf_isom_full_box_write(s, bs); + if (e) return e; + + gf_bs_write_u16(bs, p->maxCLL); + gf_bs_write_u16(bs, p->maxFALL); + + return GF_OK; +} + +GF_Err CoLL_box_size(GF_Box *s) +{ + GF_VPContentLightLevelBox *p = (GF_VPContentLightLevelBox*)s; + p->size += 4; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + + GF_OperatingPointsInformation *gf_isom_oinf_new_entry() { GF_OperatingPointsInformation* ptr; @@ -2361,6 +3156,8 @@ GF_Err gf_isom_oinf_read_entry(void *entry, GF_BitStream *bs) op->output_layer_set_idx = gf_bs_read_u16(bs); op->max_temporal_id = gf_bs_read_u8(bs); op->layer_count = gf_bs_read_u8(bs); + if (op->layer_count > GF_ARRAY_LENGTH(op->layers_info)) + return GF_NON_COMPLIANT_BITSTREAM; for (j = 0; j < op->layer_count; j++) { op->layers_info[j].ptl_idx = gf_bs_read_u8(bs); op->layers_info[j].layer_id = gf_bs_read_int(bs, 6); @@ -2394,6 +3191,10 @@ GF_Err gf_isom_oinf_read_entry(void *entry, GF_BitStream *bs) if (!dep) return GF_OUT_OF_MEM; dep->dependent_layerID = gf_bs_read_u8(bs); dep->num_layers_dependent_on = gf_bs_read_u8(bs); + if (dep->num_layers_dependent_on > GF_ARRAY_LENGTH(dep->dependent_on_layerID)) { + gf_free(dep); + return GF_NON_COMPLIANT_BITSTREAM; + } for (j = 0; j < dep->num_layers_dependent_on; j++) dep->dependent_on_layerID[j] = gf_bs_read_u8(bs); for (j = 0; j < 16; j++) { @@ -2428,7 +3229,7 @@ GF_Err gf_isom_oinf_write_entry(void *entry, GF_BitStream *bs) count=gf_list_count(ptr->operating_points); gf_bs_write_u16(bs, count); for (i = 0; i < count; i++) { - LHEVC_OperatingPoint *op = (LHEVC_OperatingPoint *)gf_list_get(ptr->operating_points, i);; + LHEVC_OperatingPoint *op = (LHEVC_OperatingPoint *)gf_list_get(ptr->operating_points, i); gf_bs_write_u16(bs, op->output_layer_set_idx); gf_bs_write_u8(bs, op->max_temporal_id); gf_bs_write_u8(bs, op->layer_count); @@ -2486,7 +3287,7 @@ u32 gf_isom_oinf_size_entry(void *entry) size += 2;//num_operating_points count=gf_list_count(ptr->operating_points); for (i = 0; i < count; i++) { - LHEVC_OperatingPoint *op = (LHEVC_OperatingPoint *)gf_list_get(ptr->operating_points, i);; + LHEVC_OperatingPoint *op = (LHEVC_OperatingPoint *)gf_list_get(ptr->operating_points, i); size += 2/*output_layer_set_idx*/ + 1/*max_temporal_id*/ + 1/*layer_count*/; size += op->layer_count * 2; size += 9; @@ -2501,7 +3302,7 @@ u32 gf_isom_oinf_size_entry(void *entry) count=gf_list_count(ptr->dependency_layers); for (i = 0; i < count; i++) { LHEVC_DependentLayer *dep = (LHEVC_DependentLayer *)gf_list_get(ptr->dependency_layers, i); - size += 1/*dependent_layerID*/ + 1/*num_layers_dependent_on*/; + size += 1/*dependent_layerID*/ + 1/*num_layers_dependent_on*/; size += dep->num_layers_dependent_on * 1;//dependent_on_layerID for (j = 0; j < 16; j++) { if (ptr->scalability_mask & (1 << j)) @@ -2517,7 +3318,7 @@ GF_LHVCLayerInformation *gf_isom_linf_new_entry() GF_LHVCLayerInformation* ptr; GF_SAFEALLOC(ptr, GF_LHVCLayerInformation); if (ptr) ptr->num_layers_in_track = gf_list_new(); - + return ptr; } diff --git a/src/isomedia/box_code_3gpp.c b/src/isomedia/box_code_3gpp.c index a3d6af5..9faddb2 100644 --- a/src/isomedia/box_code_3gpp.c +++ b/src/isomedia/box_code_3gpp.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -24,19 +24,20 @@ */ #include +#include #ifndef GPAC_DISABLE_ISOM -GF_Box *gppc_New() +GF_Box *gppc_box_new() { //default type is amr but overwritten by box constructor ISOM_DECL_BOX_ALLOC(GF_3GPPConfigBox, GF_ISOM_BOX_TYPE_DAMR); return (GF_Box *)tmp; } -void gppc_del(GF_Box *s) +void gppc_box_del(GF_Box *s) { GF_3GPPConfigBox *ptr = (GF_3GPPConfigBox *)s; if (ptr == NULL) return; @@ -44,21 +45,24 @@ void gppc_del(GF_Box *s) } -GF_Err gppc_Read(GF_Box *s, GF_BitStream *bs) +GF_Err gppc_box_read(GF_Box *s, GF_BitStream *bs) { GF_3GPPConfigBox *ptr = (GF_3GPPConfigBox *)s; if (ptr == NULL) return GF_BAD_PARAM; memset(&ptr->cfg, 0, sizeof(GF_3GPConfig)); + ISOM_DECREASE_SIZE(s, 5) ptr->cfg.vendor = gf_bs_read_u32(bs); ptr->cfg.decoder_version = gf_bs_read_u8(bs); switch (ptr->type) { case GF_ISOM_BOX_TYPE_D263: + ISOM_DECREASE_SIZE(s, 2) ptr->cfg.H263_level = gf_bs_read_u8(bs); ptr->cfg.H263_profile = gf_bs_read_u8(bs); break; case GF_ISOM_BOX_TYPE_DAMR: + ISOM_DECREASE_SIZE(s, 4) ptr->cfg.AMR_mode_set = gf_bs_read_u16(bs); ptr->cfg.AMR_mode_change_period = gf_bs_read_u8(bs); ptr->cfg.frames_per_sample = gf_bs_read_u8(bs); @@ -66,6 +70,7 @@ GF_Err gppc_Read(GF_Box *s, GF_BitStream *bs) case GF_ISOM_BOX_TYPE_DEVC: case GF_ISOM_BOX_TYPE_DQCP: case GF_ISOM_BOX_TYPE_DSMV: + ISOM_DECREASE_SIZE(s, 1) ptr->cfg.frames_per_sample = gf_bs_read_u8(bs); break; } @@ -74,7 +79,7 @@ GF_Err gppc_Read(GF_Box *s, GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err gppc_Write(GF_Box *s, GF_BitStream *bs) +GF_Err gppc_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_3GPPConfigBox *ptr = (GF_3GPPConfigBox *)s; @@ -103,11 +108,30 @@ GF_Err gppc_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err gppc_Size(GF_Box *s) +GF_Err gppc_box_size(GF_Box *s) { GF_3GPPConfigBox *ptr = (GF_3GPPConfigBox *)s; s->size += 5; + if (!ptr->cfg.type) { + switch (ptr->type) { + case GF_ISOM_BOX_TYPE_D263: + ptr->cfg.type = GF_ISOM_SUBTYPE_3GP_H263; + break; + case GF_ISOM_BOX_TYPE_DAMR: + ptr->cfg.type = GF_ISOM_SUBTYPE_3GP_AMR; + break; + case GF_ISOM_BOX_TYPE_DEVC: + ptr->cfg.type = GF_ISOM_SUBTYPE_3GP_EVRC; + break; + case GF_ISOM_BOX_TYPE_DQCP: + ptr->cfg.type = GF_ISOM_SUBTYPE_3GP_QCELP; + break; + case GF_ISOM_BOX_TYPE_DSMV: + ptr->cfg.type = GF_ISOM_SUBTYPE_3GP_SMV; + break; + } + } switch (ptr->cfg.type) { case GF_ISOM_SUBTYPE_3GP_H263: s->size += 2; @@ -128,12 +152,12 @@ GF_Err gppc_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *ftab_New() +GF_Box *ftab_box_new() { ISOM_DECL_BOX_ALLOC(GF_FontTableBox, GF_ISOM_BOX_TYPE_FTAB); return (GF_Box *) tmp; } -void ftab_del(GF_Box *s) +void ftab_box_del(GF_Box *s) { GF_FontTableBox *ptr = (GF_FontTableBox *)s; if (ptr->fonts) { @@ -144,7 +168,7 @@ void ftab_del(GF_Box *s) } gf_free(ptr); } -GF_Err ftab_Read(GF_Box *s, GF_BitStream *bs) +GF_Err ftab_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; GF_FontTableBox *ptr = (GF_FontTableBox *)s; @@ -157,13 +181,18 @@ GF_Err ftab_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } ptr->fonts = (GF_FontRecord *) gf_malloc(sizeof(GF_FontRecord)*ptr->entry_count); + if (!ptr->fonts) return GF_OUT_OF_MEM; + memset(ptr->fonts, 0, sizeof(GF_FontRecord)*ptr->entry_count); for (i=0; ientry_count; i++) { u32 len; + ISOM_DECREASE_SIZE(ptr, 3); ptr->fonts[i].fontID = gf_bs_read_u16(bs); len = gf_bs_read_u8(bs); if (len) { + ISOM_DECREASE_SIZE(ptr, len); ptr->fonts[i].fontName = (char *)gf_malloc(sizeof(char)*(len+1)); + if (!ptr->fonts[i].fontName) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->fonts[i].fontName, len); ptr->fonts[i].fontName[len] = 0; } @@ -172,7 +201,7 @@ GF_Err ftab_Read(GF_Box *s, GF_BitStream *bs) } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err ftab_Write(GF_Box *s, GF_BitStream *bs) +GF_Err ftab_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -192,7 +221,7 @@ GF_Err ftab_Write(GF_Box *s, GF_BitStream *bs) } return GF_OK; } -GF_Err ftab_Size(GF_Box *s) +GF_Err ftab_box_size(GF_Box *s) { u32 i; GF_FontTableBox *ptr = (GF_FontTableBox *)s; @@ -209,13 +238,13 @@ GF_Err ftab_Size(GF_Box *s) -GF_Box *text_New() +GF_Box *text_box_new() { ISOM_DECL_BOX_ALLOC(GF_TextSampleEntryBox, GF_ISOM_BOX_TYPE_TEXT); return (GF_Box *) tmp; } -void text_del(GF_Box *s) +void text_box_del(GF_Box *s) { GF_TextSampleEntryBox *ptr = (GF_TextSampleEntryBox*)s; gf_isom_sample_entry_predestroy((GF_SampleEntryBox *)s); @@ -225,24 +254,19 @@ void text_del(GF_Box *s) gf_free(ptr); } -GF_Box *tx3g_New() +GF_Box *tx3g_box_new() { ISOM_DECL_BOX_ALLOC(GF_Tx3gSampleEntryBox, GF_ISOM_BOX_TYPE_TX3G); return (GF_Box *) tmp; } -void tx3g_del(GF_Box *s) +void tx3g_box_del(GF_Box *s) { - GF_Tx3gSampleEntryBox *ptr = (GF_Tx3gSampleEntryBox*)s; - gf_isom_sample_entry_predestroy((GF_SampleEntryBox *)s); - - if (ptr->font_table) - gf_isom_box_del((GF_Box *)ptr->font_table); - gf_free(ptr); + gf_free(s); } -static u32 gpp_read_rgba(GF_BitStream *bs) +u32 gpp_read_rgba(GF_BitStream *bs) { u8 r, g, b, a; u32 col; @@ -261,7 +285,7 @@ static u32 gpp_read_rgba(GF_BitStream *bs) } #define GPP_BOX_SIZE 8 -static void gpp_read_box(GF_BitStream *bs, GF_BoxRecord *rec) +void gpp_read_box(GF_BitStream *bs, GF_BoxRecord *rec) { rec->top = gf_bs_read_u16(bs); rec->left = gf_bs_read_u16(bs); @@ -270,7 +294,7 @@ static void gpp_read_box(GF_BitStream *bs, GF_BoxRecord *rec) } #define GPP_STYLE_SIZE 12 -static void gpp_read_style(GF_BitStream *bs, GF_StyleRecord *rec) +void gpp_read_style(GF_BitStream *bs, GF_StyleRecord *rec) { rec->startCharOffset = gf_bs_read_u16(bs); rec->endCharOffset = gf_bs_read_u16(bs); @@ -280,7 +304,7 @@ static void gpp_read_style(GF_BitStream *bs, GF_StyleRecord *rec) rec->text_color = gpp_read_rgba(bs); } -GF_Err tx3g_AddBox(GF_Box *s, GF_Box *a) +GF_Err tx3g_on_child_box(GF_Box *s, GF_Box *a) { GF_Tx3gSampleEntryBox *ptr = (GF_Tx3gSampleEntryBox*)s; switch (a->type) { @@ -289,17 +313,17 @@ GF_Err tx3g_AddBox(GF_Box *s, GF_Box *a) ptr->font_table = (GF_FontTableBox *)a; break; default: - return gf_isom_box_add_default(s, a); + return GF_OK; } return GF_OK; } -GF_Err tx3g_Read(GF_Box *s, GF_BitStream *bs) +GF_Err tx3g_box_read(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_Tx3gSampleEntryBox *ptr = (GF_Tx3gSampleEntryBox*)s; - if (ptr->size < 18 + GPP_BOX_SIZE + GPP_STYLE_SIZE) return GF_ISOM_INVALID_FILE; + ISOM_DECREASE_SIZE(ptr, (18 + GPP_BOX_SIZE + GPP_STYLE_SIZE) ); e = gf_isom_base_sample_entry_read((GF_SampleEntryBox *)ptr, bs); if (e) return e; @@ -311,18 +335,19 @@ GF_Err tx3g_Read(GF_Box *s, GF_BitStream *bs) gpp_read_box(bs, &ptr->default_box); gpp_read_style(bs, &ptr->default_style); - ISOM_DECREASE_SIZE(ptr, (18 + GPP_BOX_SIZE + GPP_STYLE_SIZE) ); - return gf_isom_box_array_read(s, bs, tx3g_AddBox); + return gf_isom_box_array_read(s, bs, tx3g_on_child_box); } /*this is a quicktime specific box - see apple documentation*/ -GF_Err text_Read(GF_Box *s, GF_BitStream *bs) +GF_Err text_box_read(GF_Box *s, GF_BitStream *bs) { GF_Err e; u16 pSize; GF_TextSampleEntryBox *ptr = (GF_TextSampleEntryBox*)s; + ISOM_DECREASE_SIZE(ptr, 51); + e = gf_isom_base_sample_entry_read((GF_SampleEntryBox *)ptr, bs); if (e) return e; @@ -336,21 +361,23 @@ GF_Err text_Read(GF_Box *s, GF_BitStream *bs) ptr->reserved2 = gf_bs_read_u8(bs); /*Reserved*/ ptr->reserved3 = gf_bs_read_u16(bs); /*Reserved*/ gf_bs_read_data(bs, ptr->foreground_color, 6); /*Foreground color*/ - ISOM_DECREASE_SIZE(ptr, 51); + /*ffmpeg compatibility with iPod streams: no pascal string*/ if (!ptr->size) - return GF_OK; /*ffmpeg compatibility with iPod streams: no pascal string*/ + return GF_OK; - pSize = gf_bs_read_u8(bs); /*a Pascal string begins with its size: get textName size*/ ISOM_DECREASE_SIZE(ptr, 1); + pSize = gf_bs_read_u8(bs); /*a Pascal string begins with its size: get textName size*/ if (ptr->size < pSize) { - u32 s = pSize; + u32 b_size = pSize; size_t i = 0; GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[iso file] text box doesn't use a Pascal string: trying to decode anyway.\n")); ptr->textName = (char*)gf_malloc((size_t)ptr->size + 1 + 1); + if (!ptr->textName) return GF_OUT_OF_MEM; + do { - char c = (char)s; + char c = (char)b_size; if (c == '\0') { break; } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { @@ -365,8 +392,8 @@ GF_Err text_Read(GF_Box *s, GF_BitStream *bs) if (!ptr->size) break; ptr->size--; - s = gf_bs_read_u8(bs); - } while (s); + b_size = gf_bs_read_u8(bs); + } while (b_size); ptr->textName[i] = '\0'; /*Font name*/ GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] text box doesn't use a Pascal string: \"%s\" detected.\n", ptr->textName)); @@ -374,6 +401,8 @@ GF_Err text_Read(GF_Box *s, GF_BitStream *bs) } if (pSize) { ptr->textName = (char*) gf_malloc(pSize+1 * sizeof(char)); + if (!ptr->textName) return GF_OUT_OF_MEM; + if (gf_bs_read_data(bs, ptr->textName, pSize) != pSize) { gf_free(ptr->textName); ptr->textName = NULL; @@ -382,8 +411,7 @@ GF_Err text_Read(GF_Box *s, GF_BitStream *bs) ptr->textName[pSize] = '\0'; /*Font name*/ } ISOM_DECREASE_SIZE(ptr, pSize); - - return GF_OK; + return gf_isom_box_array_read(s, bs, NULL); } void gpp_write_rgba(GF_BitStream *bs, u32 col) @@ -415,7 +443,7 @@ void gpp_write_style(GF_BitStream *bs, GF_StyleRecord *rec) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err tx3g_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tx3g_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_Tx3gSampleEntryBox *ptr = (GF_Tx3gSampleEntryBox*)s; @@ -430,10 +458,10 @@ GF_Err tx3g_Write(GF_Box *s, GF_BitStream *bs) gpp_write_rgba(bs, ptr->back_color); gpp_write_box(bs, &ptr->default_box); gpp_write_style(bs, &ptr->default_style); - return gf_isom_box_write((GF_Box *) ptr->font_table, bs); + return GF_OK; } -GF_Err text_Write(GF_Box *s, GF_BitStream *bs) +GF_Err text_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u16 pSize; @@ -463,22 +491,14 @@ GF_Err text_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err tx3g_Size(GF_Box *s) +GF_Err tx3g_box_size(GF_Box *s) { - GF_Err e; - GF_Tx3gSampleEntryBox *ptr = (GF_Tx3gSampleEntryBox*)s; - /*base + this + box + style*/ s->size += 18 + GPP_BOX_SIZE + GPP_STYLE_SIZE; - if (ptr->font_table) { - e = gf_isom_box_size((GF_Box *) ptr->font_table); - if (e) return e; - s->size += ptr->font_table->size; - } return GF_OK; } -GF_Err text_Size(GF_Box *s) +GF_Err text_box_size(GF_Box *s) { GF_TextSampleEntryBox *ptr = (GF_TextSampleEntryBox*)s; @@ -491,27 +511,34 @@ GF_Err text_Size(GF_Box *s) #endif -GF_Box *styl_New() +GF_Box *styl_box_new() { ISOM_DECL_BOX_ALLOC(GF_TextStyleBox, GF_ISOM_BOX_TYPE_STYL); return (GF_Box *) tmp; } -void styl_del(GF_Box *s) +void styl_box_del(GF_Box *s) { GF_TextStyleBox*ptr = (GF_TextStyleBox*)s; if (ptr->styles) gf_free(ptr->styles); gf_free(ptr); } -GF_Err styl_Read(GF_Box *s, GF_BitStream *bs) +GF_Err styl_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; GF_TextStyleBox*ptr = (GF_TextStyleBox*)s; + ISOM_DECREASE_SIZE(ptr, 2); ptr->entry_count = gf_bs_read_u16(bs); + + if (ptr->sizeentry_count * GPP_STYLE_SIZE) + return GF_ISOM_INVALID_FILE; + if (ptr->entry_count) { ptr->styles = (GF_StyleRecord*)gf_malloc(sizeof(GF_StyleRecord)*ptr->entry_count); + if (!ptr->styles) return GF_OUT_OF_MEM; for (i=0; ientry_count; i++) { + ISOM_DECREASE_SIZE(ptr, GPP_STYLE_SIZE); gpp_read_style(bs, &ptr->styles[i]); } } @@ -519,7 +546,7 @@ GF_Err styl_Read(GF_Box *s, GF_BitStream *bs) } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err styl_Write(GF_Box *s, GF_BitStream *bs) +GF_Err styl_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -532,7 +559,7 @@ GF_Err styl_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err styl_Size(GF_Box *s) +GF_Err styl_box_size(GF_Box *s) { GF_TextStyleBox*ptr = (GF_TextStyleBox*)s; @@ -542,27 +569,28 @@ GF_Err styl_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *hlit_New() +GF_Box *hlit_box_new() { ISOM_DECL_BOX_ALLOC(GF_TextHighlightBox, GF_ISOM_BOX_TYPE_HLIT); return (GF_Box *) tmp; } -void hlit_del(GF_Box *s) +void hlit_box_del(GF_Box *s) { gf_free(s); } -GF_Err hlit_Read(GF_Box *s, GF_BitStream *bs) +GF_Err hlit_box_read(GF_Box *s, GF_BitStream *bs) { GF_TextHighlightBox *ptr = (GF_TextHighlightBox *)s; + ISOM_DECREASE_SIZE(ptr, 4) ptr->startcharoffset = gf_bs_read_u16(bs); ptr->endcharoffset = gf_bs_read_u16(bs); return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err hlit_Write(GF_Box *s, GF_BitStream *bs) +GF_Err hlit_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TextHighlightBox *ptr = (GF_TextHighlightBox *)s; @@ -573,7 +601,7 @@ GF_Err hlit_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err hlit_Size(GF_Box *s) +GF_Err hlit_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -581,26 +609,27 @@ GF_Err hlit_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *hclr_New() +GF_Box *hclr_box_new() { ISOM_DECL_BOX_ALLOC(GF_TextHighlightColorBox, GF_ISOM_BOX_TYPE_HCLR); return (GF_Box *) tmp; } -void hclr_del(GF_Box *s) +void hclr_box_del(GF_Box *s) { gf_free(s); } -GF_Err hclr_Read(GF_Box *s, GF_BitStream *bs) +GF_Err hclr_box_read(GF_Box *s, GF_BitStream *bs) { GF_TextHighlightColorBox*ptr = (GF_TextHighlightColorBox*)s; + ISOM_DECREASE_SIZE(ptr, 4) ptr->hil_color = gpp_read_rgba(bs); return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err hclr_Write(GF_Box *s, GF_BitStream *bs) +GF_Err hclr_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TextHighlightColorBox*ptr = (GF_TextHighlightColorBox*)s; @@ -610,7 +639,7 @@ GF_Err hclr_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err hclr_Size(GF_Box *s) +GF_Err hclr_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -618,29 +647,35 @@ GF_Err hclr_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *krok_New() +GF_Box *krok_box_new() { ISOM_DECL_BOX_ALLOC(GF_TextKaraokeBox, GF_ISOM_BOX_TYPE_KROK); return (GF_Box *) tmp; } -void krok_del(GF_Box *s) +void krok_box_del(GF_Box *s) { GF_TextKaraokeBox*ptr = (GF_TextKaraokeBox*)s; if (ptr->records) gf_free(ptr->records); gf_free(ptr); } -GF_Err krok_Read(GF_Box *s, GF_BitStream *bs) +GF_Err krok_box_read(GF_Box *s, GF_BitStream *bs) { GF_TextKaraokeBox*ptr = (GF_TextKaraokeBox*)s; + ISOM_DECREASE_SIZE(ptr, 6) ptr->highlight_starttime = gf_bs_read_u32(bs); ptr->nb_entries = gf_bs_read_u16(bs); + if (ptr->size < ptr->nb_entries * 8) + return GF_ISOM_INVALID_FILE; + if (ptr->nb_entries) { u32 i; ptr->records = (KaraokeRecord*)gf_malloc(sizeof(KaraokeRecord)*ptr->nb_entries); + if (!ptr->records) return GF_OUT_OF_MEM; for (i=0; inb_entries; i++) { + ISOM_DECREASE_SIZE(ptr, 8) ptr->records[i].highlight_endtime = gf_bs_read_u32(bs); ptr->records[i].start_charoffset = gf_bs_read_u16(bs); ptr->records[i].end_charoffset = gf_bs_read_u16(bs); @@ -650,7 +685,7 @@ GF_Err krok_Read(GF_Box *s, GF_BitStream *bs) } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err krok_Write(GF_Box *s, GF_BitStream *bs) +GF_Err krok_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -668,35 +703,36 @@ GF_Err krok_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err krok_Size(GF_Box *s) +GF_Err krok_box_size(GF_Box *s) { GF_TextKaraokeBox*ptr = (GF_TextKaraokeBox*)s; - s->size += 6 * 8*ptr->nb_entries; + s->size += 6 + 8*ptr->nb_entries; return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *dlay_New() +GF_Box *dlay_box_new() { ISOM_DECL_BOX_ALLOC(GF_TextScrollDelayBox, GF_ISOM_BOX_TYPE_DLAY); return (GF_Box *) tmp; } -void dlay_del(GF_Box *s) +void dlay_box_del(GF_Box *s) { gf_free(s); } -GF_Err dlay_Read(GF_Box *s, GF_BitStream *bs) +GF_Err dlay_box_read(GF_Box *s, GF_BitStream *bs) { GF_TextScrollDelayBox*ptr = (GF_TextScrollDelayBox*)s; + ISOM_DECREASE_SIZE(ptr, 4) ptr->scroll_delay = gf_bs_read_u32(bs); return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err dlay_Write(GF_Box *s, GF_BitStream *bs) +GF_Err dlay_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TextScrollDelayBox*ptr = (GF_TextScrollDelayBox*)s; @@ -706,7 +742,7 @@ GF_Err dlay_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err dlay_Size(GF_Box *s) +GF_Err dlay_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -714,13 +750,13 @@ GF_Err dlay_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *href_New() +GF_Box *href_box_new() { ISOM_DECL_BOX_ALLOC(GF_TextHyperTextBox, GF_ISOM_BOX_TYPE_HREF); return (GF_Box *) tmp; } -void href_del(GF_Box *s) +void href_box_del(GF_Box *s) { GF_TextHyperTextBox*ptr = (GF_TextHyperTextBox*)s; if (ptr->URL) gf_free(ptr->URL); @@ -728,21 +764,26 @@ void href_del(GF_Box *s) gf_free(ptr); } -GF_Err href_Read(GF_Box *s, GF_BitStream *bs) +GF_Err href_box_read(GF_Box *s, GF_BitStream *bs) { u32 len; GF_TextHyperTextBox*ptr = (GF_TextHyperTextBox*)s; + ISOM_DECREASE_SIZE(ptr, 6) //including 2 length fields ptr->startcharoffset = gf_bs_read_u16(bs); ptr->endcharoffset = gf_bs_read_u16(bs); len = gf_bs_read_u8(bs); if (len) { + ISOM_DECREASE_SIZE(ptr, len) ptr->URL = (char *) gf_malloc(sizeof(char) * (len+1)); + if (!ptr->URL) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->URL, len); ptr->URL[len] = 0; } len = gf_bs_read_u8(bs); if (len) { + ISOM_DECREASE_SIZE(ptr, len) ptr->URL_hint = (char *) gf_malloc(sizeof(char) * (len+1)); + if (!ptr->URL_hint) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->URL_hint, len); ptr->URL_hint[len]= 0; } @@ -750,7 +791,7 @@ GF_Err href_Read(GF_Box *s, GF_BitStream *bs) } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err href_Write(GF_Box *s, GF_BitStream *bs) +GF_Err href_box_write(GF_Box *s, GF_BitStream *bs) { u32 len; GF_Err e; @@ -777,7 +818,7 @@ GF_Err href_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err href_Size(GF_Box *s) +GF_Err href_box_size(GF_Box *s) { GF_TextHyperTextBox*ptr = (GF_TextHyperTextBox*)s; s->size += 6; @@ -789,26 +830,27 @@ GF_Err href_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *tbox_New() +GF_Box *tbox_box_new() { ISOM_DECL_BOX_ALLOC(GF_TextBoxBox, GF_ISOM_BOX_TYPE_TBOX); return (GF_Box *) tmp; } -void tbox_del(GF_Box *s) +void tbox_box_del(GF_Box *s) { gf_free(s); } -GF_Err tbox_Read(GF_Box *s, GF_BitStream *bs) +GF_Err tbox_box_read(GF_Box *s, GF_BitStream *bs) { GF_TextBoxBox*ptr = (GF_TextBoxBox*)s; + ISOM_DECREASE_SIZE(ptr, GPP_BOX_SIZE) gpp_read_box(bs, &ptr->box); return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err tbox_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tbox_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TextBoxBox*ptr = (GF_TextBoxBox*)s; @@ -818,7 +860,7 @@ GF_Err tbox_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err tbox_Size(GF_Box *s) +GF_Err tbox_box_size(GF_Box *s) { s->size += 8; return GF_OK; @@ -827,27 +869,28 @@ GF_Err tbox_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *blnk_New() +GF_Box *blnk_box_new() { ISOM_DECL_BOX_ALLOC(GF_TextBlinkBox, GF_ISOM_BOX_TYPE_BLNK); return (GF_Box *) tmp; } -void blnk_del(GF_Box *s) +void blnk_box_del(GF_Box *s) { gf_free(s); } -GF_Err blnk_Read(GF_Box *s, GF_BitStream *bs) +GF_Err blnk_box_read(GF_Box *s, GF_BitStream *bs) { GF_TextBlinkBox*ptr = (GF_TextBlinkBox*)s; + ISOM_DECREASE_SIZE(ptr, 4) ptr->startcharoffset = gf_bs_read_u16(bs); ptr->endcharoffset = gf_bs_read_u16(bs); return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err blnk_Write(GF_Box *s, GF_BitStream *bs) +GF_Err blnk_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TextBlinkBox*ptr = (GF_TextBlinkBox*)s; @@ -858,7 +901,7 @@ GF_Err blnk_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err blnk_Size(GF_Box *s) +GF_Err blnk_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -866,26 +909,27 @@ GF_Err blnk_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *twrp_New() +GF_Box *twrp_box_new() { ISOM_DECL_BOX_ALLOC(GF_TextWrapBox, GF_ISOM_BOX_TYPE_TWRP); return (GF_Box *) tmp; } -void twrp_del(GF_Box *s) +void twrp_box_del(GF_Box *s) { gf_free(s); } -GF_Err twrp_Read(GF_Box *s, GF_BitStream *bs) +GF_Err twrp_box_read(GF_Box *s, GF_BitStream *bs) { GF_TextWrapBox*ptr = (GF_TextWrapBox*)s; + ISOM_DECREASE_SIZE(ptr, 1) ptr->wrap_flag = gf_bs_read_u8(bs); return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err twrp_Write(GF_Box *s, GF_BitStream *bs) +GF_Err twrp_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TextWrapBox*ptr = (GF_TextWrapBox*)s; @@ -894,7 +938,7 @@ GF_Err twrp_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u8(bs, ptr->wrap_flag); return GF_OK; } -GF_Err twrp_Size(GF_Box *s) +GF_Err twrp_box_size(GF_Box *s) { s->size += 1; return GF_OK; @@ -902,7 +946,7 @@ GF_Err twrp_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void tsel_del(GF_Box *s) +void tsel_box_del(GF_Box *s) { GF_TrackSelectionBox *ptr; ptr = (GF_TrackSelectionBox *) s; @@ -911,13 +955,13 @@ void tsel_del(GF_Box *s) gf_free(ptr); } -GF_Err tsel_Read(GF_Box *s,GF_BitStream *bs) +GF_Err tsel_box_read(GF_Box *s,GF_BitStream *bs) { u32 i; GF_TrackSelectionBox *ptr = (GF_TrackSelectionBox *) s; - ptr->switchGroup = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); + ptr->switchGroup = gf_bs_read_u32(bs); if (ptr->size % 4) return GF_ISOM_INVALID_FILE; ptr->attributeListCount = (u32)ptr->size/4; @@ -930,7 +974,7 @@ GF_Err tsel_Read(GF_Box *s,GF_BitStream *bs) return GF_OK; } -GF_Box *tsel_New() +GF_Box *tsel_box_new() { ISOM_DECL_BOX_ALLOC(GF_TrackSelectionBox, GF_ISOM_BOX_TYPE_TSEL); return (GF_Box *)tmp; @@ -939,7 +983,7 @@ GF_Box *tsel_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err tsel_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tsel_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -956,7 +1000,7 @@ GF_Err tsel_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err tsel_Size(GF_Box *s) +GF_Err tsel_box_size(GF_Box *s) { GF_TrackSelectionBox *ptr = (GF_TrackSelectionBox *) s; ptr->size += 4 + (4*ptr->attributeListCount); @@ -966,12 +1010,12 @@ GF_Err tsel_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *dimC_New() +GF_Box *dimC_box_new() { ISOM_DECL_BOX_ALLOC(GF_DIMSSceneConfigBox, GF_ISOM_BOX_TYPE_DIMC); return (GF_Box *)tmp; } -void dimC_del(GF_Box *s) +void dimC_box_del(GF_Box *s) { GF_DIMSSceneConfigBox *p = (GF_DIMSSceneConfigBox *)s; if (p->contentEncoding) gf_free(p->contentEncoding); @@ -979,12 +1023,13 @@ void dimC_del(GF_Box *s) gf_free(p); } -GF_Err dimC_Read(GF_Box *s, GF_BitStream *bs) +GF_Err dimC_box_read(GF_Box *s, GF_BitStream *bs) { char str[1024]; u32 i; GF_DIMSSceneConfigBox *p = (GF_DIMSSceneConfigBox *)s; + ISOM_DECREASE_SIZE(p, 3); p->profile = gf_bs_read_u8(bs); p->level = gf_bs_read_u8(bs); p->pathComponents = gf_bs_read_int(bs, 4); @@ -992,11 +1037,9 @@ GF_Err dimC_Read(GF_Box *s, GF_BitStream *bs) p->streamType = gf_bs_read_int(bs, 1); p->containsRedundant = gf_bs_read_int(bs, 2); - ISOM_DECREASE_SIZE(p, 3); - i=0; str[0]=0; - while (1) { + while (i < GF_ARRAY_LENGTH(str)) { str[i] = gf_bs_read_u8(bs); if (!str[i]) break; i++; @@ -1007,7 +1050,7 @@ GF_Err dimC_Read(GF_Box *s, GF_BitStream *bs) i=0; str[0]=0; - while (1) { + while (i < GF_ARRAY_LENGTH(str)) { str[i] = gf_bs_read_u8(bs); if (!str[i]) break; i++; @@ -1019,10 +1062,10 @@ GF_Err dimC_Read(GF_Box *s, GF_BitStream *bs) } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err dimC_Write(GF_Box *s, GF_BitStream *bs) +GF_Err dimC_box_write(GF_Box *s, GF_BitStream *bs) { GF_DIMSSceneConfigBox *p = (GF_DIMSSceneConfigBox *)s; - GF_Err e = gf_isom_full_box_write(s, bs); + GF_Err e = gf_isom_box_write_header(s, bs); if (e) return e; gf_bs_write_u8(bs, p->profile); gf_bs_write_u8(bs, p->level); @@ -1030,33 +1073,39 @@ GF_Err dimC_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_int(bs, p->fullRequestHost, 1); gf_bs_write_int(bs, p->streamType, 1); gf_bs_write_int(bs, p->containsRedundant, 2); - gf_bs_write_data(bs, p->textEncoding, (u32) strlen(p->textEncoding)+1); - gf_bs_write_data(bs, p->contentEncoding, (u32) strlen(p->contentEncoding)+1); + if (p->textEncoding) + gf_bs_write_data(bs, p->textEncoding, (u32) strlen(p->textEncoding)); + gf_bs_write_u8(bs, 0); + if (p->contentEncoding) + gf_bs_write_data(bs, p->contentEncoding, (u32) strlen(p->contentEncoding)); + gf_bs_write_u8(bs, 0); return GF_OK; } -GF_Err dimC_Size(GF_Box *s) +GF_Err dimC_box_size(GF_Box *s) { GF_DIMSSceneConfigBox *p = (GF_DIMSSceneConfigBox *)s; - s->size += 3 + 1 + strlen(p->textEncoding) + 1 + strlen(p->contentEncoding); + s->size += 3 + 2; + if (p->textEncoding) s->size += strlen(p->textEncoding); + if (p->contentEncoding) s->size += strlen(p->contentEncoding); return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *diST_New() +GF_Box *diST_box_new() { ISOM_DECL_BOX_ALLOC(GF_DIMSScriptTypesBox, GF_ISOM_BOX_TYPE_DIST); return (GF_Box *)tmp; } -void diST_del(GF_Box *s) +void diST_box_del(GF_Box *s) { GF_DIMSScriptTypesBox *p = (GF_DIMSScriptTypesBox *)s; if (p->content_script_types) gf_free(p->content_script_types); gf_free(p); } -GF_Err diST_Read(GF_Box *s, GF_BitStream *bs) +GF_Err diST_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; char str[1024]; @@ -1076,7 +1125,7 @@ GF_Err diST_Read(GF_Box *s, GF_BitStream *bs) } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err diST_Write(GF_Box *s, GF_BitStream *bs) +GF_Err diST_box_write(GF_Box *s, GF_BitStream *bs) { GF_DIMSScriptTypesBox *p = (GF_DIMSScriptTypesBox *)s; GF_Err e = gf_isom_box_write_header(s, bs); @@ -1087,7 +1136,7 @@ GF_Err diST_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u8(bs, 0); return GF_OK; } -GF_Err diST_Size(GF_Box *s) +GF_Err diST_box_size(GF_Box *s) { GF_DIMSScriptTypesBox *p = (GF_DIMSScriptTypesBox *)s; s->size += p->content_script_types ? (strlen(p->content_script_types)+1) : 1; @@ -1096,22 +1145,18 @@ GF_Err diST_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *dims_New() +GF_Box *dims_box_new() { ISOM_DECL_BOX_ALLOC(GF_DIMSSampleEntryBox, GF_ISOM_BOX_TYPE_DIMS); return (GF_Box*)tmp; } -void dims_del(GF_Box *s) +void dims_box_del(GF_Box *s) { - GF_DIMSSampleEntryBox *p = (GF_DIMSSampleEntryBox *)s; gf_isom_sample_entry_predestroy((GF_SampleEntryBox *)s); - - if (p->config) gf_isom_box_del((GF_Box *)p->config); - if (p->scripts) gf_isom_box_del((GF_Box *)p->scripts); - gf_free(p); + gf_free(s); } -static GF_Err dims_AddBox(GF_Box *s, GF_Box *a) +static GF_Err dims_on_child_box(GF_Box *s, GF_Box *a) { GF_DIMSSampleEntryBox *ptr = (GF_DIMSSampleEntryBox *)s; switch (a->type) { @@ -1123,15 +1168,10 @@ static GF_Err dims_AddBox(GF_Box *s, GF_Box *a) if (ptr->scripts) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->scripts = (GF_DIMSScriptTypesBox*)a; break; - case GF_ISOM_BOX_TYPE_SINF: - gf_list_add(ptr->protections, a); - break; - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err dims_Read(GF_Box *s, GF_BitStream *bs) +GF_Err dims_box_read(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_DIMSSampleEntryBox *p = (GF_DIMSSampleEntryBox *)s; @@ -1140,46 +1180,28 @@ GF_Err dims_Read(GF_Box *s, GF_BitStream *bs) if (e) return e; ISOM_DECREASE_SIZE(p, 8); - return gf_isom_box_array_read(s, bs, dims_AddBox); + return gf_isom_box_array_read(s, bs, dims_on_child_box); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err dims_Write(GF_Box *s, GF_BitStream *bs) +GF_Err dims_box_write(GF_Box *s, GF_BitStream *bs) { GF_DIMSSampleEntryBox *p = (GF_DIMSSampleEntryBox *)s; GF_Err e = gf_isom_box_write_header(s, bs); if (e) return e; gf_bs_write_data(bs, p->reserved, 6); gf_bs_write_u16(bs, p->dataReferenceIndex); - if (p->config) { - e = gf_isom_box_write((GF_Box *)p->config, bs); - if (e) return e; - } - if (p->scripts) { - e = gf_isom_box_write((GF_Box *)p->scripts, bs); - if (e) return e; - } - return gf_isom_box_array_write(s, p->protections, bs); + return GF_OK; } -GF_Err dims_Size(GF_Box *s) +GF_Err dims_box_size(GF_Box *s) { - GF_Err e; + u32 pos = 0; GF_DIMSSampleEntryBox *p = (GF_DIMSSampleEntryBox *)s; - s->size += 8; - - if (p->config) { - e = gf_isom_box_size((GF_Box *) p->config); - if (e) return e; - p->size += p->config->size; - } - if (p->scripts) { - e = gf_isom_box_size((GF_Box *) p->scripts); - if (e) return e; - p->size += p->scripts->size; - } - return gf_isom_box_array_size(s, p->protections); + gf_isom_check_position(s, (GF_Box *) p->config, &pos); + gf_isom_check_position(s, (GF_Box *) p->scripts, &pos); + return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ diff --git a/src/isomedia/box_code_adobe.c b/src/isomedia/box_code_adobe.c index aa57676..ab5115f 100644 --- a/src/isomedia/box_code_adobe.c +++ b/src/isomedia/box_code_adobe.c @@ -1,8 +1,8 @@ /* * GPAC - Multimedia Framework C SDK * - * Author: Romain Bouqueau - * Copyright (c) Romain Bouqueau 2012- + * Author: Romain Bouqueau, Jean Le Feuvre + * Copyright (c) Romain Bouqueau 2012- Telecom Paris 2019- * All rights reserved * * Note: this development was kindly sponsorized by Vizion'R (http://vizionr.com) @@ -33,7 +33,7 @@ #ifndef GPAC_DISABLE_ISOM_FRAGMENTS -void abst_del(GF_Box *s) +void abst_box_del(GF_Box *s) { GF_AdobeBootstrapInfoBox *ptr = (GF_AdobeBootstrapInfoBox *)s; if (ptr == NULL) return; @@ -57,6 +57,7 @@ void abst_del(GF_Box *s) } gf_list_del(ptr->quality_entry_table); + while (gf_list_count(ptr->segment_run_table_entries)) { gf_isom_box_del((GF_Box *)gf_list_get(ptr->segment_run_table_entries, 0)); gf_list_rem(ptr->segment_run_table_entries, 0); @@ -72,7 +73,7 @@ void abst_del(GF_Box *s) gf_free(ptr); } -GF_Err abst_Read(GF_Box *s, GF_BitStream *bs) +GF_Err abst_box_read(GF_Box *s, GF_BitStream *bs) { GF_AdobeBootstrapInfoBox *ptr = (GF_AdobeBootstrapInfoBox *)s; int i; @@ -80,6 +81,7 @@ GF_Err abst_Read(GF_Box *s, GF_BitStream *bs) char *tmp_str; GF_Err e; + ISOM_DECREASE_SIZE(ptr, 25) ptr->bootstrapinfo_version = gf_bs_read_u32(bs); ptr->profile = gf_bs_read_int(bs, 2); ptr->live = gf_bs_read_int(bs, 1); @@ -91,85 +93,112 @@ GF_Err abst_Read(GF_Box *s, GF_BitStream *bs) i=0; if (ptr->size<8) return GF_ISOM_INVALID_FILE; - tmp_strsize=(u32)ptr->size-8; + tmp_strsize =(u32)ptr->size; tmp_str = gf_malloc(sizeof(char)*tmp_strsize); + if (!tmp_str) return GF_OUT_OF_MEM; + memset(tmp_str, 0, sizeof(char)*tmp_strsize); while (tmp_strsize) { + ISOM_DECREASE_SIZE(ptr, 1) tmp_str[i] = gf_bs_read_u8(bs); tmp_strsize--; if (!tmp_str[i]) break; i++; } - if (i) + if (i) { ptr->movie_identifier = gf_strdup(tmp_str); + } + ISOM_DECREASE_SIZE(ptr, 1) ptr->server_entry_count = gf_bs_read_u8(bs); for (i=0; iserver_entry_count; i++) { int j=0; - tmp_strsize=(u32)ptr->size-8; + tmp_strsize=(u32)ptr->size; while (tmp_strsize) { + ISOM_DECREASE_SIZE(ptr, 1) tmp_str[j] = gf_bs_read_u8(bs); tmp_strsize--; if (!tmp_str[j]) break; j++; } - gf_list_insert(ptr->server_entry_table, gf_strdup(tmp_str), i); + if (j) { + gf_list_insert(ptr->server_entry_table, gf_strdup(tmp_str), i); + } } + ISOM_DECREASE_SIZE(ptr, 1) ptr->quality_entry_count = gf_bs_read_u8(bs); for (i=0; iquality_entry_count; i++) { int j=0; - tmp_strsize=(u32)ptr->size-8; + tmp_strsize=(u32)ptr->size; while (tmp_strsize) { + ISOM_DECREASE_SIZE(ptr, 1) tmp_str[j] = gf_bs_read_u8(bs); tmp_strsize--; if (!tmp_str[j]) break; j++; } - gf_list_insert(ptr->quality_entry_table, gf_strdup(tmp_str), i); + + if (j) { + gf_list_insert(ptr->quality_entry_table, gf_strdup(tmp_str), i); + } } i=0; - tmp_strsize=(u32)ptr->size-8; + tmp_strsize=(u32)ptr->size; while (tmp_strsize) { + ISOM_DECREASE_SIZE(ptr, 1) tmp_str[i] = gf_bs_read_u8(bs); tmp_strsize--; if (!tmp_str[i]) break; i++; } - if (i) + if (i) { ptr->drm_data = gf_strdup(tmp_str); + } i=0; - tmp_strsize=(u32)ptr->size-8; + tmp_strsize=(u32)ptr->size; while (tmp_strsize) { + ISOM_DECREASE_SIZE(ptr, 1) tmp_str[i] = gf_bs_read_u8(bs); tmp_strsize--; if (!tmp_str[i]) break; i++; } - if (i) + if (i) { ptr->meta_data = gf_strdup(tmp_str); + } + ISOM_DECREASE_SIZE(ptr, 1) ptr->segment_run_table_count = gf_bs_read_u8(bs); for (i=0; isegment_run_table_count; i++) { - GF_AdobeSegmentRunTableBox *asrt; + GF_AdobeSegmentRunTableBox *asrt = NULL; e = gf_isom_box_parse((GF_Box **)&asrt, bs); - if (e) return e; - gf_list_insert(ptr->segment_run_table_entries, asrt, i); + if (e) { + if (asrt) gf_isom_box_del((GF_Box*)asrt); + gf_free(tmp_str); + return e; + } + gf_list_add(ptr->segment_run_table_entries, asrt); } + ISOM_DECREASE_SIZE(ptr, 1) ptr->fragment_run_table_count = gf_bs_read_u8(bs); for (i=0; ifragment_run_table_count; i++) { - GF_AdobeFragmentRunTableBox *afrt; + GF_AdobeFragmentRunTableBox *afrt = NULL; e = gf_isom_box_parse((GF_Box **)&afrt, bs); - if (e) return e; - gf_list_insert(ptr->fragment_run_table_entries, afrt, i); + if (e) { + if (afrt) gf_isom_box_del((GF_Box*)afrt); + gf_free(tmp_str); + return e; + } + gf_list_add(ptr->fragment_run_table_entries, afrt); } gf_free(tmp_str); @@ -177,8 +206,7 @@ GF_Err abst_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_EXPORT -GF_Box *abst_New() +GF_Box *abst_box_new() { ISOM_DECL_BOX_ALLOC(GF_AdobeBootstrapInfoBox, GF_ISOM_BOX_TYPE_ABST); tmp->server_entry_table = gf_list_new(); @@ -190,8 +218,7 @@ GF_Box *abst_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_EXPORT -GF_Err abst_Write(GF_Box *s, GF_BitStream *bs) +GF_Err abst_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; unsigned int i; @@ -250,11 +277,10 @@ GF_Err abst_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_EXPORT -GF_Err abst_Size(GF_Box *s) +GF_Err abst_box_size(GF_Box *s) { GF_Err e; - int i; + u32 i; GF_AdobeBootstrapInfoBox *ptr = (GF_AdobeBootstrapInfoBox *)s; s->size += 25 @@ -287,13 +313,12 @@ GF_Err abst_Size(GF_Box *s) if (e) return e; s->size += box->size; } - return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void afra_del(GF_Box *s) +void afra_box_del(GF_Box *s) { GF_AdobeFragRandomAccessBox *ptr = (GF_AdobeFragRandomAccessBox *)s; if (ptr == NULL) return; @@ -313,11 +338,12 @@ void afra_del(GF_Box *s) gf_free(ptr); } -GF_Err afra_Read(GF_Box *s, GF_BitStream *bs) +GF_Err afra_box_read(GF_Box *s, GF_BitStream *bs) { unsigned int i; GF_AdobeFragRandomAccessBox *ptr = (GF_AdobeFragRandomAccessBox *)s; + ISOM_DECREASE_SIZE(ptr, 9) ptr->long_ids = gf_bs_read_int(bs, 1); ptr->long_offsets = gf_bs_read_int(bs, 1); ptr->global_entries = gf_bs_read_int(bs, 1); @@ -325,35 +351,49 @@ GF_Err afra_Read(GF_Box *s, GF_BitStream *bs) ptr->time_scale = gf_bs_read_u32(bs); ptr->entry_count = gf_bs_read_u32(bs); + if (ptr->size < ptr->entry_count * (ptr->long_offsets ? 16 : 12)) + return GF_ISOM_INVALID_FILE; + for (i=0; ientry_count; i++) { GF_AfraEntry *ae = gf_malloc(sizeof(GF_AfraEntry)); + if (!ae) return GF_OUT_OF_MEM; + ISOM_DECREASE_SIZE(ptr, 8) ae->time = gf_bs_read_u64(bs); - if (ptr->long_offsets) + if (ptr->long_offsets) { + ISOM_DECREASE_SIZE(ptr, 8) ae->offset = gf_bs_read_u64(bs); - else + } else { + ISOM_DECREASE_SIZE(ptr, 4) ae->offset = gf_bs_read_u32(bs); + } gf_list_insert(ptr->local_access_entries, ae, i); } if (ptr->global_entries) { + ISOM_DECREASE_SIZE(ptr, 4) ptr->global_entry_count = gf_bs_read_u32(bs); for (i=0; iglobal_entry_count; i++) { GF_GlobalAfraEntry *ae = gf_malloc(sizeof(GF_GlobalAfraEntry)); - + if (!ae) return GF_OUT_OF_MEM; + ISOM_DECREASE_SIZE(ptr, 8) ae->time = gf_bs_read_u64(bs); if (ptr->long_ids) { + ISOM_DECREASE_SIZE(ptr, 8) ae->segment = gf_bs_read_u32(bs); ae->fragment = gf_bs_read_u32(bs); } else { + ISOM_DECREASE_SIZE(ptr, 4) ae->segment = gf_bs_read_u16(bs); ae->fragment = gf_bs_read_u16(bs); } if (ptr->long_offsets) { + ISOM_DECREASE_SIZE(ptr, 16) ae->afra_offset = gf_bs_read_u64(bs); ae->offset_from_afra = gf_bs_read_u64(bs); } else { + ISOM_DECREASE_SIZE(ptr, 8) ae->afra_offset = gf_bs_read_u32(bs); ae->offset_from_afra = gf_bs_read_u32(bs); } @@ -365,8 +405,7 @@ GF_Err afra_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_EXPORT -GF_Box *afra_New() +GF_Box *afra_box_new() { ISOM_DECL_BOX_ALLOC(GF_AdobeFragRandomAccessBox, GF_ISOM_BOX_TYPE_AFRA); tmp->local_access_entries = gf_list_new(); @@ -376,7 +415,7 @@ GF_Box *afra_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err afra_Write(GF_Box *s, GF_BitStream *bs) +GF_Err afra_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; unsigned int i; @@ -427,7 +466,7 @@ GF_Err afra_Write(GF_Box *s, GF_BitStream *bs) } -GF_Err afra_Size(GF_Box *s) +GF_Err afra_box_size(GF_Box *s) { GF_AdobeFragRandomAccessBox *ptr = (GF_AdobeFragRandomAccessBox *)s; @@ -440,7 +479,7 @@ GF_Err afra_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void asrt_del(GF_Box *s) +void asrt_box_del(GF_Box *s) { GF_AdobeSegmentRunTableBox *ptr = (GF_AdobeSegmentRunTableBox *)s; if (ptr == NULL) return; @@ -460,16 +499,21 @@ void asrt_del(GF_Box *s) gf_free(ptr); } -GF_Err asrt_Read(GF_Box *s, GF_BitStream *bs) +GF_Err asrt_box_read(GF_Box *s, GF_BitStream *bs) { unsigned int i; GF_AdobeSegmentRunTableBox *ptr = (GF_AdobeSegmentRunTableBox *)s; + ISOM_DECREASE_SIZE(ptr, 1) ptr->quality_entry_count = gf_bs_read_u8(bs); + if (ptr->size < ptr->quality_entry_count) + return GF_ISOM_INVALID_FILE; + for (i=0; iquality_entry_count; i++) { int j=0; - u32 tmp_strsize=(u32)ptr->size-8; + u32 tmp_strsize=(u32)ptr->size; char *tmp_str = (char*) gf_malloc(tmp_strsize); + if (!tmp_str) return GF_OUT_OF_MEM; while (tmp_strsize) { tmp_str[j] = gf_bs_read_u8(bs); tmp_strsize--; @@ -477,12 +521,19 @@ GF_Err asrt_Read(GF_Box *s, GF_BitStream *bs) break; j++; } + ISOM_DECREASE_SIZE(ptr, j) gf_list_insert(ptr->quality_segment_url_modifiers, tmp_str, i); } + ISOM_DECREASE_SIZE(ptr, 4) ptr->segment_run_entry_count = gf_bs_read_u32(bs); + if (ptr->size < ptr->segment_run_entry_count*8) + return GF_ISOM_INVALID_FILE; + for (i=0; isegment_run_entry_count; i++) { GF_AdobeSegmentRunEntry *sre = gf_malloc(sizeof(GF_AdobeSegmentRunEntry)); + if (!sre) return GF_OUT_OF_MEM; + ISOM_DECREASE_SIZE(ptr, 8) sre->first_segment = gf_bs_read_u32(bs); sre->fragment_per_segment = gf_bs_read_u32(bs); gf_list_insert(ptr->segment_run_entry_table, sre, i); @@ -491,8 +542,7 @@ GF_Err asrt_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_EXPORT -GF_Box *asrt_New() +GF_Box *asrt_box_new() { ISOM_DECL_BOX_ALLOC(GF_AdobeSegmentRunTableBox, GF_ISOM_BOX_TYPE_ASRT); tmp->quality_segment_url_modifiers = gf_list_new(); @@ -502,7 +552,7 @@ GF_Box *asrt_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err asrt_Write(GF_Box *s, GF_BitStream *bs) +GF_Err asrt_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; unsigned int i; @@ -528,7 +578,7 @@ GF_Err asrt_Write(GF_Box *s, GF_BitStream *bs) } -GF_Err asrt_Size(GF_Box *s) +GF_Err asrt_box_size(GF_Box *s) { int i; GF_AdobeSegmentRunTableBox *ptr = (GF_AdobeSegmentRunTableBox *)s; @@ -545,7 +595,7 @@ GF_Err asrt_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void afrt_del(GF_Box *s) +void afrt_box_del(GF_Box *s) { GF_AdobeFragmentRunTableBox *ptr = (GF_AdobeFragmentRunTableBox *)s; if (ptr == NULL) return; @@ -565,18 +615,23 @@ void afrt_del(GF_Box *s) gf_free(ptr); } -GF_Err afrt_Read(GF_Box *s, GF_BitStream *bs) +GF_Err afrt_box_read(GF_Box *s, GF_BitStream *bs) { unsigned int i; GF_AdobeFragmentRunTableBox *ptr = (GF_AdobeFragmentRunTableBox *)s; + ISOM_DECREASE_SIZE(ptr, 5) ptr->timescale = gf_bs_read_u32(bs); ptr->quality_entry_count = gf_bs_read_u8(bs); + if (ptr->size < ptr->quality_entry_count) + return GF_ISOM_INVALID_FILE; + for (i=0; iquality_entry_count; i++) { int j=0; u32 tmp_strsize=(u32)ptr->size-8; char *tmp_str = (char*) gf_malloc(tmp_strsize); + if (!tmp_str) return GF_OUT_OF_MEM; while (tmp_strsize) { tmp_str[j] = gf_bs_read_u8(bs); tmp_strsize--; @@ -584,25 +639,31 @@ GF_Err afrt_Read(GF_Box *s, GF_BitStream *bs) break; j++; } + ISOM_DECREASE_SIZE(ptr, j) gf_list_insert(ptr->quality_segment_url_modifiers, tmp_str, i); } ptr->fragment_run_entry_count = gf_bs_read_u32(bs); + if (ptr->size < ptr->fragment_run_entry_count*16) + return GF_ISOM_INVALID_FILE; for (i=0; ifragment_run_entry_count; i++) { GF_AdobeFragmentRunEntry *fre = gf_malloc(sizeof(GF_AdobeFragmentRunEntry)); + if (!fre) return GF_ISOM_INVALID_FILE; + ISOM_DECREASE_SIZE(ptr, 16) fre->first_fragment = gf_bs_read_u32(bs); fre->first_fragment_timestamp = gf_bs_read_u64(bs); fre->fragment_duration = gf_bs_read_u32(bs); - if (!fre->fragment_duration) + if (!fre->fragment_duration) { + ISOM_DECREASE_SIZE(ptr, 1) fre->discontinuity_indicator = gf_bs_read_u8(bs); + } gf_list_insert(ptr->fragment_run_entry_table, fre, i); } return GF_OK; } -GF_EXPORT -GF_Box *afrt_New() +GF_Box *afrt_box_new() { ISOM_DECL_BOX_ALLOC(GF_AdobeFragmentRunTableBox, GF_ISOM_BOX_TYPE_AFRT); tmp->quality_segment_url_modifiers = gf_list_new(); @@ -612,7 +673,7 @@ GF_Box *afrt_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err afrt_Write(GF_Box *s, GF_BitStream *bs) +GF_Err afrt_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; unsigned int i; @@ -642,7 +703,7 @@ GF_Err afrt_Write(GF_Box *s, GF_BitStream *bs) } -GF_Err afrt_Size(GF_Box *s) +GF_Err afrt_box_size(GF_Box *s) { u32 i; GF_AdobeFragmentRunTableBox *ptr = (GF_AdobeFragmentRunTableBox *)s; diff --git a/src/isomedia/box_code_apple.c b/src/isomedia/box_code_apple.c index 9563596..51fd658 100644 --- a/src/isomedia/box_code_apple.c +++ b/src/isomedia/box_code_apple.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2006-2012 + * Copyright (c) Telecom ParisTech 2006-2019 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -27,14 +27,14 @@ #ifndef GPAC_DISABLE_ISOM -void ilst_del(GF_Box *s) +void ilst_box_del(GF_Box *s) { GF_ItemListBox *ptr = (GF_ItemListBox *)s; if (ptr == NULL) return; gf_free(ptr); } -GF_Err ilst_Read(GF_Box *s, GF_BitStream *bs) +GF_Err ilst_box_read(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 sub_type; @@ -44,10 +44,20 @@ GF_Err ilst_Read(GF_Box *s, GF_BitStream *bs) /*if no ilst type coded, break*/ sub_type = gf_bs_peek_bits(bs, 32, 0); if (sub_type) { - e = gf_isom_box_parse(&a, bs); - if (e) return e; + e = gf_isom_box_parse_ex(&a, bs, s->type, GF_FALSE); + + /* the macro will return in this case before we can free */ + if (!e && ptr->size < a->size) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[isom] not enough bytes in box %s: %d left, reading %d (file %s, line %d)\n", gf_4cc_to_str(ptr->type), ptr->size, a->size, __FILE__, __LINE__ )); \ + e = GF_ISOM_INVALID_FILE; + } + if (e) { + if (a) gf_isom_box_del(a); + return e; + } + ISOM_DECREASE_SIZE(ptr, a->size); - gf_list_add(ptr->other_boxes, a); + gf_list_add(ptr->child_boxes, a); } else { gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); @@ -56,16 +66,16 @@ GF_Err ilst_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *ilst_New() +GF_Box *ilst_box_new() { ISOM_DECL_BOX_ALLOC(GF_ItemListBox, GF_ISOM_BOX_TYPE_ILST); - tmp->other_boxes = gf_list_new(); + tmp->child_boxes = gf_list_new(); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err ilst_Write(GF_Box *s, GF_BitStream *bs) +GF_Err ilst_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; // GF_ItemListBox *ptr = (GF_ItemListBox *)s; @@ -77,47 +87,80 @@ GF_Err ilst_Write(GF_Box *s, GF_BitStream *bs) } -GF_Err ilst_Size(GF_Box *s) +GF_Err ilst_box_size(GF_Box *s) { return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void ilst_item_del(GF_Box *s) +void ilst_item_box_del(GF_Box *s) { GF_ListItemBox *ptr = (GF_ListItemBox *) s; if (ptr == NULL) return; - if (ptr->data != NULL) { - if (ptr->data->data) gf_free(ptr->data->data); - gf_free(ptr->data); - } gf_free(ptr); } -GF_Err ilst_item_Read(GF_Box *s,GF_BitStream *bs) +GF_Err ilst_item_box_read(GF_Box *s,GF_BitStream *bs) { GF_Err e; u32 sub_type; GF_Box *a = NULL; GF_ListItemBox *ptr = (GF_ListItemBox *)s; - /*iTunes way: there's a data atom containing the data*/ sub_type = gf_bs_peek_bits(bs, 32, 4); if (sub_type == GF_ISOM_BOX_TYPE_DATA ) { e = gf_isom_box_parse(&a, bs); - if (e) return e; + + if (!e && a && (ptr->size < a->size)) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[isom] not enough bytes in box %s: %d left, reading %d (file %s, line %d)\n", gf_4cc_to_str(ptr->type), ptr->size, a->size, __FILE__, __LINE__ )); \ + e = GF_ISOM_INVALID_FILE; + } + if (!a) e = GF_ISOM_INVALID_FILE; + + if (e) { + if (a) gf_isom_box_del(a); + return e; + } + if (!a) return GF_NON_COMPLIANT_BITSTREAM; + ISOM_DECREASE_SIZE(ptr, a->size); - if (a && ptr->data) gf_isom_box_del((GF_Box *) ptr->data); - ptr->data = (GF_DataBox *)a; + if (ptr->data) gf_isom_box_del_parent(&ptr->child_boxes, (GF_Box *) ptr->data); + + /* otherwise a->data will always overflow */ + if (a->size > 4 && (a->type != GF_ISOM_BOX_TYPE_VOID)) { + ptr->data = (GF_DataBox *)a; + if (!ptr->child_boxes) ptr->child_boxes = gf_list_new(); + gf_list_add(ptr->child_boxes, ptr->data); + } else { + ptr->data = NULL; + gf_isom_box_del(a); + } } /*QT way*/ else { - ptr->data->type = 0; + u64 pos = gf_bs_get_position(bs); + u64 prev_size = s->size; + /*try parsing as generic box list*/ + e = gf_isom_box_array_read(s, bs, NULL); + if (e==GF_OK) return GF_OK; + //reset content and retry - this deletes ptr->data !! + gf_isom_box_array_del(s->child_boxes); + s->child_boxes=NULL; + gf_bs_seek(bs, pos); + s->size = prev_size; + + ptr->data = (GF_DataBox *)gf_isom_box_new_parent(&ptr->child_boxes, GF_ISOM_BOX_TYPE_DATA); + //nope, check qt-style + ptr->data->qt_style = GF_TRUE; + ISOM_DECREASE_SIZE(ptr, 2); ptr->data->dataSize = gf_bs_read_u16(bs); gf_bs_read_u16(bs); + ptr->data->data = (char *) gf_malloc(sizeof(char)*(ptr->data->dataSize + 1)); + if (!ptr->data->data) return GF_OUT_OF_MEM; + gf_bs_read_data(bs, ptr->data->data, ptr->data->dataSize); ptr->data->data[ptr->data->dataSize] = 0; ISOM_DECREASE_SIZE(ptr, ptr->data->dataSize); @@ -125,23 +168,21 @@ GF_Err ilst_item_Read(GF_Box *s,GF_BitStream *bs) return GF_OK; } -GF_Box *ilst_item_New(u32 type) +GF_Box *ilst_item_box_new() { - ISOM_DECL_BOX_ALLOC(GF_ListItemBox, type); - - tmp->data = (GF_DataBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_DATA); - + ISOM_DECL_BOX_ALLOC(GF_ListItemBox, GF_ISOM_BOX_TYPE_CPIL); //type will be overwrite + tmp->data = (GF_DataBox *)gf_isom_box_new_parent(&tmp->child_boxes, GF_ISOM_BOX_TYPE_DATA); if (tmp->data == NULL) { + if (tmp->child_boxes) gf_list_del(tmp->child_boxes); gf_free(tmp); return NULL; } - return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err ilst_item_Write(GF_Box *s, GF_BitStream *bs) +GF_Err ilst_item_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_ListItemBox *ptr = (GF_ListItemBox *) s; @@ -149,27 +190,38 @@ GF_Err ilst_item_Write(GF_Box *s, GF_BitStream *bs) e = gf_isom_box_write_header(s, bs); if (e) return e; - /*iTune way*/ - if (ptr->data->type) return gf_isom_box_write((GF_Box* )ptr->data, bs); - /*QT way*/ - gf_bs_write_u16(bs, ptr->data->dataSize); - gf_bs_write_u16(bs, 0); - gf_bs_write_data(bs, ptr->data->data, ptr->data->dataSize); + /*generic box list*/ + if (ptr->child_boxes && !ptr->data) { + } + //empty ilst + else if (!ptr->data) { + } + /*iTune way: data-box-encapsulated box list*/ + else if (!ptr->data->qt_style) { + } + /*QT way: raw data*/ + else { + gf_bs_write_u16(bs, ptr->data->dataSize); + gf_bs_write_u16(bs, 0); + gf_bs_write_data(bs, ptr->data->data, ptr->data->dataSize); + ptr->size = 0; //abort + } return GF_OK; } -GF_Err ilst_item_Size(GF_Box *s) +GF_Err ilst_item_box_size(GF_Box *s) { - GF_Err e; GF_ListItemBox *ptr = (GF_ListItemBox *)s; - /*iTune way*/ - if (ptr->data && ptr->data->type) { - e = gf_isom_box_size((GF_Box *)ptr->data); - if (e) return e; - ptr->size += ptr->data->size; + /*generic box list*/ + if (ptr->child_boxes && !ptr->data) { } - /*QT way*/ + /*iTune way: data-box-encapsulated box list*/ + else if (ptr->data && !ptr->data->qt_style) { + u32 pos=0; + gf_isom_check_position(s, (GF_Box* ) ptr->data, &pos); + } + /*QT way: raw data*/ else if (ptr->data) { ptr->size += ptr->data->dataSize + 4; } @@ -178,7 +230,7 @@ GF_Err ilst_item_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void databox_del(GF_Box *s) +void databox_box_del(GF_Box *s) { GF_DataBox *ptr = (GF_DataBox *) s; if (ptr == NULL) return; @@ -188,17 +240,17 @@ void databox_del(GF_Box *s) } -GF_Err databox_Read(GF_Box *s,GF_BitStream *bs) +GF_Err databox_box_read(GF_Box *s,GF_BitStream *bs) { GF_DataBox *ptr = (GF_DataBox *)s; - ptr->reserved = gf_bs_read_int(bs, 32); ISOM_DECREASE_SIZE(ptr, 4); + ptr->reserved = gf_bs_read_u32(bs); if (ptr->size) { ptr->dataSize = (u32) ptr->size; ptr->data = (char*)gf_malloc(ptr->dataSize * sizeof(ptr->data[0]) + 1); - if (ptr->data == NULL) return GF_OUT_OF_MEM; + if (!ptr->data) return GF_OUT_OF_MEM; ptr->data[ptr->dataSize] = 0; gf_bs_read_data(bs, ptr->data, ptr->dataSize); } @@ -206,7 +258,7 @@ GF_Err databox_Read(GF_Box *s,GF_BitStream *bs) return GF_OK; } -GF_Box *databox_New() +GF_Box *databox_box_new() { ISOM_DECL_BOX_ALLOC(GF_DataBox, GF_ISOM_BOX_TYPE_DATA); @@ -215,7 +267,7 @@ GF_Box *databox_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err databox_Write(GF_Box *s, GF_BitStream *bs) +GF_Err databox_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_DataBox *ptr = (GF_DataBox *) s; @@ -229,7 +281,7 @@ GF_Err databox_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err databox_Size(GF_Box *s) +GF_Err databox_box_size(GF_Box *s) { GF_DataBox *ptr = (GF_DataBox *)s; @@ -242,10 +294,84 @@ GF_Err databox_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ +void alis_box_del(GF_Box *s) +{ + GF_DataEntryAliasBox *ptr = (GF_DataEntryAliasBox *)s; + if (ptr == NULL) return; + gf_free(ptr); +} + +GF_Err alis_box_read(GF_Box *s, GF_BitStream *bs) +{ +// GF_DataEntryAliasBox *ptr = (GF_DataEntryAliasBox *)s; + return GF_OK; +} + +GF_Box *alis_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_DataEntryAliasBox, GF_QT_BOX_TYPE_ALIS); + return (GF_Box *)tmp; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err alis_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; +// GF_DataEntryAliasBox *ptr = (GF_DataEntryAliasBox *)s; + + e = gf_isom_full_box_write(s, bs); + if (e) return e; + return GF_OK; +} + +GF_Err alis_box_size(GF_Box *s) +{ + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + +void wide_box_del(GF_Box *s) +{ + if (s == NULL) return; + gf_free(s); +} + + +GF_Err wide_box_read(GF_Box *s, GF_BitStream *bs) +{ + gf_bs_skip_bytes(bs, s->size); + s->size = 0; + return GF_OK; +} + +GF_Box *wide_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_WideBox, GF_QT_BOX_TYPE_WIDE); + return (GF_Box *)tmp; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err wide_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + e = gf_isom_box_write_header(s, bs); + if (e) return e; + return GF_OK; +} + +GF_Err wide_box_size(GF_Box *s) +{ + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + GF_MetaBox *gf_isom_apple_get_meta_extensions(GF_ISOFile *mov) { u32 i; - GF_MetaBox *meta; GF_UserDataMap *map; if (!mov || !mov->moov) return NULL; @@ -254,8 +380,8 @@ GF_MetaBox *gf_isom_apple_get_meta_extensions(GF_ISOFile *mov) map = udta_getEntry(mov->moov->udta, GF_ISOM_BOX_TYPE_META, NULL); if (!map) return NULL; - for(i = 0; i < gf_list_count(map->other_boxes); i++) { - meta = (GF_MetaBox*)gf_list_get(map->other_boxes, i); + for(i = 0; i < gf_list_count(map->boxes); i++) { + GF_MetaBox *meta = (GF_MetaBox*)gf_list_get(map->boxes, i); if(meta != NULL && meta->handler != NULL && meta->handler->handlerType == GF_ISOM_HANDLER_TYPE_MDIR) return meta; } @@ -274,31 +400,31 @@ GF_MetaBox *gf_isom_apple_create_meta_extensions(GF_ISOFile *mov) if (!mov || !mov->moov) return NULL; if (!mov->moov->udta) { - e = moov_AddBox((GF_Box*)mov->moov, gf_isom_box_new(GF_ISOM_BOX_TYPE_UDTA)); + e = moov_on_child_box((GF_Box*)mov->moov, gf_isom_box_new_parent(&mov->moov->child_boxes, GF_ISOM_BOX_TYPE_UDTA)); if (e) return NULL; } map = udta_getEntry(mov->moov->udta, GF_ISOM_BOX_TYPE_META, NULL); if (map) { - for(i = 0; i < gf_list_count(map->other_boxes); i++) { - meta = (GF_MetaBox*)gf_list_get(map->other_boxes, i); + for(i = 0; i < gf_list_count(map->boxes); i++) { + meta = (GF_MetaBox*)gf_list_get(map->boxes, i); if(meta != NULL && meta->handler != NULL && meta->handler->handlerType == GF_ISOM_HANDLER_TYPE_MDIR) return meta; } } + //udta handles children boxes through maps meta = (GF_MetaBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_META); if(meta != NULL) { - meta->handler = (GF_HandlerBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_HDLR); + meta->handler = (GF_HandlerBox *)gf_isom_box_new_parent(&meta->child_boxes, GF_ISOM_BOX_TYPE_HDLR); if(meta->handler == NULL) { gf_isom_box_del((GF_Box *)meta); return NULL; } meta->handler->handlerType = GF_ISOM_HANDLER_TYPE_MDIR; - if (!meta->other_boxes) meta->other_boxes = gf_list_new(); - gf_list_add(meta->other_boxes, gf_isom_box_new(GF_ISOM_BOX_TYPE_ILST)); - udta_AddBox((GF_Box *)mov->moov->udta, (GF_Box *)meta); + gf_isom_box_new_parent(&meta->child_boxes, GF_ISOM_BOX_TYPE_ILST); + udta_on_child_box((GF_Box *)mov->moov->udta, (GF_Box *)meta); } return meta; @@ -306,4 +432,547 @@ GF_MetaBox *gf_isom_apple_create_meta_extensions(GF_ISOFile *mov) #endif /*GPAC_DISABLE_ISOM_WRITE*/ + +void gmin_box_del(GF_Box *s) +{ + gf_free(s); +} + +GF_Err gmin_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_GenericMediaHeaderInfoBox *ptr = (GF_GenericMediaHeaderInfoBox *)s; + ISOM_DECREASE_SIZE(ptr, 12); + ptr->graphics_mode = gf_bs_read_u16(bs); + ptr->op_color_red = gf_bs_read_u16(bs); + ptr->op_color_green = gf_bs_read_u16(bs); + ptr->op_color_blue = gf_bs_read_u16(bs); + ptr->balance = gf_bs_read_u16(bs); + ptr->reserved = gf_bs_read_u16(bs); + return GF_OK; +} + +GF_Box *gmin_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_GenericMediaHeaderInfoBox, GF_QT_BOX_TYPE_GMIN); + return (GF_Box *)tmp; +} + + + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err gmin_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_GenericMediaHeaderInfoBox *ptr = (GF_GenericMediaHeaderInfoBox *)s; + + e = gf_isom_full_box_write(s, bs); + if (e) return e; + + gf_bs_write_u16(bs, ptr->graphics_mode); + gf_bs_write_u16(bs, ptr->op_color_red); + gf_bs_write_u16(bs, ptr->op_color_green); + gf_bs_write_u16(bs, ptr->op_color_blue); + gf_bs_write_u16(bs, ptr->balance); + gf_bs_write_u16(bs, ptr->reserved); + return GF_OK; +} + +GF_Err gmin_box_size(GF_Box *s) +{ + GF_VideoMediaHeaderBox *ptr = (GF_VideoMediaHeaderBox *)s; + ptr->size += 12; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + + + +void clef_box_del(GF_Box *s) +{ + gf_free(s); +} + +GF_Err clef_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_ApertureBox *ptr = (GF_ApertureBox *)s; + ISOM_DECREASE_SIZE(ptr, 8); + ptr->width = gf_bs_read_u32(bs); + ptr->height = gf_bs_read_u32(bs); + return GF_OK; +} + +GF_Box *clef_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_ApertureBox, GF_QT_BOX_TYPE_CLEF); + return (GF_Box *)tmp; +} + + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err clef_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_ApertureBox *ptr = (GF_ApertureBox *)s; + + e = gf_isom_full_box_write(s, bs); + if (e) return e; + gf_bs_write_u32(bs, ptr->width); + gf_bs_write_u32(bs, ptr->height); + return GF_OK; +} + +GF_Err clef_box_size(GF_Box *s) +{ + s->size += 8; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +void tmcd_box_del(GF_Box *s) +{ + if (s == NULL) return; + gf_isom_sample_entry_predestroy((GF_SampleEntryBox *)s); + gf_free(s); +} + + +GF_Err tmcd_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_TimeCodeSampleEntryBox *ptr = (GF_TimeCodeSampleEntryBox *)s; + GF_Err e = gf_isom_base_sample_entry_read((GF_SampleEntryBox *)s, bs); + if (e) return e; + + ISOM_DECREASE_SIZE(s, 26); + gf_bs_read_u32(bs); //reserved + ptr->flags = gf_bs_read_u32(bs); + ptr->timescale = gf_bs_read_u32(bs); + ptr->frame_duration = gf_bs_read_u32(bs); + ptr->frames_per_counter_tick = gf_bs_read_u8(bs); + gf_bs_read_u8(bs); //reserved + + return gf_isom_box_array_read(s, bs, NULL); +} + +GF_Box *tmcd_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_TimeCodeSampleEntryBox, GF_QT_BOX_TYPE_TMCD); + gf_isom_sample_entry_init((GF_SampleEntryBox*)tmp); + return (GF_Box *)tmp; +} + + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err tmcd_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_TimeCodeSampleEntryBox *ptr = (GF_TimeCodeSampleEntryBox *)s; + e = gf_isom_box_write_header(s, bs); + if (e) return e; + + gf_bs_write_data(bs, ptr->reserved, 6); + gf_bs_write_u16(bs, ptr->dataReferenceIndex); + + gf_bs_write_u32(bs, 0); //reserved + gf_bs_write_u32(bs, ptr->flags); + gf_bs_write_u32(bs, ptr->timescale); + gf_bs_write_u32(bs, ptr->frame_duration); + gf_bs_write_u8(bs, ptr->frames_per_counter_tick); + gf_bs_write_u8(bs, 0); //reserved + + return GF_OK; +} + +GF_Err tmcd_box_size(GF_Box *s) +{ + GF_SampleEntryBox *ptr = (GF_SampleEntryBox *)s; + ptr->size += 8 + 18; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +void tcmi_box_del(GF_Box *s) +{ + GF_TimeCodeMediaInformationBox *ptr = (GF_TimeCodeMediaInformationBox *)s; + if (ptr->font) gf_free(ptr->font); + gf_free(s); +} + + +GF_Err tcmi_box_read(GF_Box *s, GF_BitStream *bs) +{ + u32 len; + GF_TimeCodeMediaInformationBox *ptr = (GF_TimeCodeMediaInformationBox *)s; + + //don't remove font name len field, some writers just skip it if no font ... + ISOM_DECREASE_SIZE(s, 20); + + ptr->text_font = gf_bs_read_u16(bs); + ptr->text_face = gf_bs_read_u16(bs); + ptr->text_size = gf_bs_read_u16(bs); + gf_bs_read_u16(bs); + ptr->text_color_red = gf_bs_read_u16(bs); + ptr->text_color_green = gf_bs_read_u16(bs); + ptr->text_color_blue = gf_bs_read_u16(bs); + ptr->back_color_red = gf_bs_read_u16(bs); + ptr->back_color_green = gf_bs_read_u16(bs); + ptr->back_color_blue = gf_bs_read_u16(bs); + if (!ptr->size) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] broken tmci box, missing font name length field\n" )); + return GF_OK; + } + ISOM_DECREASE_SIZE(s, 1); + + len = gf_bs_read_u8(bs); + if (len > ptr->size) + len = (u32) ptr->size; + if (len) { + ptr->font = gf_malloc(len+1); + if (!ptr->font) return GF_OUT_OF_MEM; + gf_bs_read_data(bs, ptr->font, len); + ptr->size -= len; + ptr->font[len]=0; + } + return GF_OK; +} + +GF_Box *tcmi_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_TimeCodeMediaInformationBox, GF_QT_BOX_TYPE_TCMI); + tmp->text_size = 12; + tmp->text_color_red = 0xFFFF; + tmp->text_color_green = 0xFFFF; + tmp->text_color_blue = 0xFFFF; + return (GF_Box *)tmp; +} + + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err tcmi_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + u32 len; + GF_TimeCodeMediaInformationBox *ptr = (GF_TimeCodeMediaInformationBox *)s; + + e = gf_isom_full_box_write(s, bs); + if (e) return e; + gf_bs_write_u16(bs, ptr->text_font); + gf_bs_write_u16(bs, ptr->text_face); + gf_bs_write_u16(bs, ptr->text_size); + gf_bs_write_u16(bs, 0); + gf_bs_write_u16(bs, ptr->text_color_red); + gf_bs_write_u16(bs, ptr->text_color_green); + gf_bs_write_u16(bs, ptr->text_color_blue); + gf_bs_write_u16(bs, ptr->back_color_red); + gf_bs_write_u16(bs, ptr->back_color_green); + gf_bs_write_u16(bs, ptr->back_color_blue); + len = ptr->font ? (u32) strlen(ptr->font) : 0; + gf_bs_write_u8(bs, len); + if (ptr->font) + gf_bs_write_data(bs, ptr->font, len); + + return GF_OK; +} + +GF_Err tcmi_box_size(GF_Box *s) +{ + GF_TimeCodeMediaInformationBox *ptr = (GF_TimeCodeMediaInformationBox *)s; + ptr->size += 21; + if (ptr->font) + ptr->size += strlen(ptr->font); + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +void fiel_box_del(GF_Box *s) +{ + gf_free(s); +} + + +GF_Err fiel_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_FieldInfoBox *ptr = (GF_FieldInfoBox *)s; + + ISOM_DECREASE_SIZE(s, 2); + + ptr->field_count = gf_bs_read_u8(bs); + ptr->field_order = gf_bs_read_u8(bs); + return GF_OK; +} + +GF_Box *fiel_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_FieldInfoBox, GF_QT_BOX_TYPE_FIEL); + return (GF_Box *)tmp; +} + + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err fiel_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_FieldInfoBox *ptr = (GF_FieldInfoBox *)s; + e = gf_isom_box_write_header(s, bs); + if (e) return e; + gf_bs_write_u8(bs, ptr->field_count); + gf_bs_write_u8(bs, ptr->field_order); + return GF_OK; +} + +GF_Err fiel_box_size(GF_Box *s) +{ + s->size += 2; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +void gama_box_del(GF_Box *s) +{ + gf_free(s); +} + + +GF_Err gama_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_GamaInfoBox *ptr = (GF_GamaInfoBox *)s; + + ISOM_DECREASE_SIZE(s, 4); + + ptr->gama = gf_bs_read_u32(bs); + return GF_OK; +} + +GF_Box *gama_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_GamaInfoBox, GF_QT_BOX_TYPE_GAMA); + return (GF_Box *)tmp; +} + + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err gama_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_GamaInfoBox *ptr = (GF_GamaInfoBox *)s; + GF_Err e = gf_isom_box_write_header(s, bs); + if (e) return e; + gf_bs_write_u32(bs, ptr->gama); + return GF_OK; +} + +GF_Err gama_box_size(GF_Box *s) +{ + s->size += 4; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +void chrm_box_del(GF_Box *s) +{ + gf_free(s); +} + + +GF_Err chrm_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_ChromaInfoBox *ptr = (GF_ChromaInfoBox *)s; + + ISOM_DECREASE_SIZE(s, 2); + + ptr->chroma = gf_bs_read_u16(bs); + return GF_OK; +} + +GF_Box *chrm_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_ChromaInfoBox, GF_QT_BOX_TYPE_CHRM); + return (GF_Box *)tmp; +} + + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err chrm_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_ChromaInfoBox *ptr = (GF_ChromaInfoBox *)s; + GF_Err e = gf_isom_box_write_header(s, bs); + if (e) return e; + gf_bs_write_u16(bs, ptr->chroma); + return GF_OK; +} + +GF_Err chrm_box_size(GF_Box *s) +{ + s->size += 2; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +void chan_box_del(GF_Box *s) +{ + GF_ChannelLayoutInfoBox *ptr = (GF_ChannelLayoutInfoBox *)s; + if (ptr->audio_descs) gf_free(ptr->audio_descs); + if (ptr->ext_data) gf_free(ptr->ext_data); + gf_free(s); +} + + +GF_Err chan_box_read(GF_Box *s, GF_BitStream *bs) +{ + u32 i; + GF_ChannelLayoutInfoBox *ptr = (GF_ChannelLayoutInfoBox *)s; + + ISOM_DECREASE_SIZE(s, 12); + ptr->layout_tag = gf_bs_read_u32(bs); + ptr->bitmap = gf_bs_read_u32(bs); + ptr->num_audio_description = gf_bs_read_u32(bs); + + if (ptr->size < ptr->num_audio_description*20) + return GF_ISOM_INVALID_FILE; + + ptr->audio_descs = gf_malloc(sizeof(GF_AudioChannelDescription) * ptr->num_audio_description); + if (!ptr->audio_descs) return GF_OUT_OF_MEM; + + for (i=0; inum_audio_description; i++) { + GF_AudioChannelDescription *adesc = &ptr->audio_descs[i]; + ISOM_DECREASE_SIZE(s, 20); + adesc->label = gf_bs_read_u32(bs); + adesc->flags = gf_bs_read_u32(bs); + adesc->coordinates[0] = gf_bs_read_float(bs); + adesc->coordinates[1] = gf_bs_read_float(bs); + adesc->coordinates[2] = gf_bs_read_float(bs); + } + //avoids warning on most files + if (ptr->size==20) { + ptr->size=0; + gf_bs_skip_bytes(bs, 20); + } + if (ptr->size<10000) { + ptr->ext_data_size = (u32) ptr->size; + ptr->ext_data = gf_malloc(sizeof(u8) * ptr->ext_data_size); + if (!ptr->ext_data) return GF_OUT_OF_MEM; + gf_bs_read_data(bs, (char *)ptr->ext_data, (u32) ptr->size); + ptr->size = 0; + } + return GF_OK; +} + +GF_Box *chan_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_ChannelLayoutInfoBox, GF_QT_BOX_TYPE_CHAN); + return (GF_Box *)tmp; +} + + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err chan_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + u32 i; + GF_ChannelLayoutInfoBox *ptr = (GF_ChannelLayoutInfoBox *)s; + + e = gf_isom_full_box_write(s, bs); + if (e) return e; + + + gf_bs_write_u32(bs, ptr->layout_tag); + gf_bs_write_u32(bs, ptr->bitmap); + gf_bs_write_u32(bs, ptr->num_audio_description); + for (i=0; inum_audio_description; i++) { + GF_AudioChannelDescription *adesc = &ptr->audio_descs[i]; + gf_bs_write_u32(bs, adesc->label); + gf_bs_write_u32(bs, adesc->flags); + gf_bs_write_float(bs, adesc->coordinates[0]); + gf_bs_write_float(bs, adesc->coordinates[1]); + gf_bs_write_float(bs, adesc->coordinates[2]); + } + if (ptr->ext_data) { + gf_bs_write_data(bs, ptr->ext_data, ptr->ext_data_size); + } + return GF_OK; +} + +GF_Err chan_box_size(GF_Box *s) +{ + GF_ChannelLayoutInfoBox *ptr = (GF_ChannelLayoutInfoBox *)s; + s->size += 12 + 20 * ptr->num_audio_description; + if (ptr->ext_data) { + s->size += ptr->ext_data_size; + } + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + + +void load_box_del(GF_Box *s) +{ + gf_free(s); +} + + +GF_Err load_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_TrackLoadBox *ptr = (GF_TrackLoadBox *)s; + ISOM_DECREASE_SIZE(s, 16); + ptr->preload_start_time = gf_bs_read_u32(bs); + ptr->preload_duration = gf_bs_read_u32(bs); + ptr->preload_flags = gf_bs_read_u32(bs); + ptr->default_hints = gf_bs_read_u32(bs); + return GF_OK; +} + +GF_Box *load_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_TrackLoadBox, GF_QT_BOX_TYPE_LOAD); + return (GF_Box *)tmp; +} + + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err load_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_TrackLoadBox *ptr = (GF_TrackLoadBox *)s; + + GF_Err e = gf_isom_box_write_header(s, bs); + if (e) return e; + + gf_bs_write_u32(bs, ptr->preload_start_time); + gf_bs_write_u32(bs, ptr->preload_duration); + gf_bs_write_u32(bs, ptr->preload_flags); + gf_bs_write_u32(bs, ptr->default_hints); + return GF_OK; +} + +GF_Err load_box_size(GF_Box *s) +{ + s->size += 16; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + #endif /*GPAC_DISABLE_ISOM*/ diff --git a/src/isomedia/box_code_base.c b/src/isomedia/box_code_base.c index a21c390..809106b 100644 --- a/src/isomedia/box_code_base.c +++ b/src/isomedia/box_code_base.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2019 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -29,7 +29,7 @@ #ifndef GPAC_DISABLE_ISOM -void co64_del(GF_Box *s) +void co64_box_del(GF_Box *s) { GF_ChunkLargeOffsetBox *ptr; ptr = (GF_ChunkLargeOffsetBox *) s; @@ -38,7 +38,7 @@ void co64_del(GF_Box *s) gf_free(ptr); } -GF_Err co64_Read(GF_Box *s,GF_BitStream *bs) +GF_Err co64_box_read(GF_Box *s,GF_BitStream *bs) { u32 entries; GF_ChunkLargeOffsetBox *ptr = (GF_ChunkLargeOffsetBox *) s; @@ -60,7 +60,7 @@ GF_Err co64_Read(GF_Box *s,GF_BitStream *bs) return GF_OK; } -GF_Box *co64_New() +GF_Box *co64_box_new() { ISOM_DECL_BOX_ALLOC(GF_ChunkLargeOffsetBox, GF_ISOM_BOX_TYPE_CO64); return (GF_Box *)tmp; @@ -69,7 +69,7 @@ GF_Box *co64_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err co64_Write(GF_Box *s, GF_BitStream *bs) +GF_Err co64_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -84,7 +84,7 @@ GF_Err co64_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err co64_Size(GF_Box *s) +GF_Err co64_box_size(GF_Box *s) { GF_ChunkLargeOffsetBox *ptr = (GF_ChunkLargeOffsetBox *) s; @@ -94,7 +94,7 @@ GF_Err co64_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void cprt_del(GF_Box *s) +void cprt_box_del(GF_Box *s) { GF_CopyrightBox *ptr = (GF_CopyrightBox *) s; if (ptr == NULL) return; @@ -104,7 +104,7 @@ void cprt_del(GF_Box *s) } -GF_Box *chpl_New() +GF_Box *chpl_box_new() { ISOM_DECL_BOX_ALLOC(GF_ChapterListBox, GF_ISOM_BOX_TYPE_CHPL); tmp->list = gf_list_new(); @@ -112,7 +112,7 @@ GF_Box *chpl_New() return (GF_Box *)tmp; } -void chpl_del(GF_Box *s) +void chpl_box_del(GF_Box *s) { GF_ChapterListBox *ptr = (GF_ChapterListBox *) s; if (ptr == NULL) return; @@ -127,12 +127,13 @@ void chpl_del(GF_Box *s) } /*this is using chpl format according to some NeroRecode samples*/ -GF_Err chpl_Read(GF_Box *s,GF_BitStream *bs) +GF_Err chpl_box_read(GF_Box *s,GF_BitStream *bs) { GF_ChapterEntry *ce; u32 nb_chaps, len, i, count; GF_ChapterListBox *ptr = (GF_ChapterListBox *)s; + ISOM_DECREASE_SIZE(ptr, 5) /*reserved or ???*/ gf_bs_read_u32(bs); nb_chaps = gf_bs_read_u8(bs); @@ -141,10 +142,14 @@ GF_Err chpl_Read(GF_Box *s,GF_BitStream *bs) while (nb_chaps) { GF_SAFEALLOC(ce, GF_ChapterEntry); if (!ce) return GF_OUT_OF_MEM; + ISOM_DECREASE_SIZE(ptr, 9) ce->start_time = gf_bs_read_u64(bs); len = gf_bs_read_u8(bs); + if (ptr->sizename = (char *)gf_malloc(sizeof(char)*(len+1)); + if (!ce->name) return GF_OUT_OF_MEM; + ISOM_DECREASE_SIZE(ptr, len) gf_bs_read_data(bs, ce->name, len); ce->name[len] = 0; } else { @@ -168,7 +173,7 @@ GF_Err chpl_Read(GF_Box *s,GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err chpl_Write(GF_Box *s, GF_BitStream *bs) +GF_Err chpl_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 count, i; @@ -195,7 +200,7 @@ GF_Err chpl_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err chpl_Size(GF_Box *s) +GF_Err chpl_box_size(GF_Box *s) { u32 count, i; GF_ChapterListBox *ptr = (GF_ChapterListBox *)s; @@ -214,16 +219,16 @@ GF_Err chpl_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Err cprt_Read(GF_Box *s,GF_BitStream *bs) +GF_Err cprt_box_read(GF_Box *s,GF_BitStream *bs) { GF_CopyrightBox *ptr = (GF_CopyrightBox *)s; + ISOM_DECREASE_SIZE(ptr, 2); gf_bs_read_int(bs, 1); //the spec is unclear here, just says "the value 0 is interpreted as undetermined" ptr->packedLanguageCode[0] = gf_bs_read_int(bs, 5); ptr->packedLanguageCode[1] = gf_bs_read_int(bs, 5); ptr->packedLanguageCode[2] = gf_bs_read_int(bs, 5); - ISOM_DECREASE_SIZE(ptr, 2); //but before or after compaction ?? We assume before if (ptr->packedLanguageCode[0] || ptr->packedLanguageCode[1] || ptr->packedLanguageCode[2]) { @@ -244,7 +249,7 @@ GF_Err cprt_Read(GF_Box *s,GF_BitStream *bs) return GF_OK; } -GF_Box *cprt_New() +GF_Box *cprt_box_new() { ISOM_DECL_BOX_ALLOC(GF_CopyrightBox, GF_ISOM_BOX_TYPE_CPRT); tmp->packedLanguageCode[0] = 'u'; @@ -256,7 +261,7 @@ GF_Box *cprt_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err cprt_Write(GF_Box *s, GF_BitStream *bs) +GF_Err cprt_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_CopyrightBox *ptr = (GF_CopyrightBox *) s; @@ -277,7 +282,7 @@ GF_Err cprt_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err cprt_Size(GF_Box *s) +GF_Err cprt_box_size(GF_Box *s) { GF_CopyrightBox *ptr = (GF_CopyrightBox *)s; @@ -289,7 +294,7 @@ GF_Err cprt_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void kind_del(GF_Box *s) +void kind_box_del(GF_Box *s) { GF_KindBox *ptr = (GF_KindBox *) s; if (ptr == NULL) return; @@ -298,7 +303,7 @@ void kind_del(GF_Box *s) gf_free(ptr); } -GF_Err kind_Read(GF_Box *s,GF_BitStream *bs) +GF_Err kind_box_read(GF_Box *s,GF_BitStream *bs) { GF_KindBox *ptr = (GF_KindBox *)s; @@ -307,30 +312,30 @@ GF_Err kind_Read(GF_Box *s,GF_BitStream *bs) char *data; u32 schemeURIlen; data = (char*)gf_malloc(bytesToRead * sizeof(char)); - if (data == NULL) return GF_OUT_OF_MEM; + if (!data) return GF_OUT_OF_MEM; gf_bs_read_data(bs, data, bytesToRead); /*safety check in case the string is not null-terminated*/ if (data[bytesToRead-1]) { - char *str = (char*)gf_malloc((u32) bytesToRead + 1); - memcpy(str, data, (u32) bytesToRead); - str[ptr->size] = 0; - gf_free(data); - data = str; + data = (char*)gf_realloc(data, sizeof(char)*(bytesToRead + 1)); + if (!data) return GF_OUT_OF_MEM; + data[bytesToRead] = 0; bytesToRead++; } ptr->schemeURI = gf_strdup(data); + if (!ptr->schemeURI) return GF_OUT_OF_MEM; schemeURIlen = (u32) strlen(data); if (bytesToRead > schemeURIlen+1) { /* read the value */ char *data_value = data + schemeURIlen +1; ptr->value = gf_strdup(data_value); + if (!ptr->value) return GF_OUT_OF_MEM; } gf_free(data); } return GF_OK; } -GF_Box *kind_New() +GF_Box *kind_box_new() { ISOM_DECL_BOX_ALLOC(GF_KindBox, GF_ISOM_BOX_TYPE_KIND); return (GF_Box *)tmp; @@ -338,25 +343,29 @@ GF_Box *kind_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err kind_Write(GF_Box *s, GF_BitStream *bs) +GF_Err kind_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_KindBox *ptr = (GF_KindBox *) s; e = gf_isom_full_box_write(s, bs); if (e) return e; - gf_bs_write_data(bs, ptr->schemeURI, (u32) (strlen(ptr->schemeURI) + 1 )); - if (ptr->value) { + if (ptr->schemeURI) + gf_bs_write_data(bs, ptr->schemeURI, (u32) (strlen(ptr->schemeURI) + 1 )); + else + gf_bs_write_u8(bs, 0); + + if (ptr->value) { gf_bs_write_data(bs, ptr->value, (u32) (strlen(ptr->value) + 1) ); } return GF_OK; } -GF_Err kind_Size(GF_Box *s) +GF_Err kind_box_size(GF_Box *s) { GF_KindBox *ptr = (GF_KindBox *)s; - ptr->size += strlen(ptr->schemeURI) + 1; + ptr->size += (ptr->schemeURI ? strlen(ptr->schemeURI) : 0) + 1; if (ptr->value) { ptr->size += strlen(ptr->value) + 1; } @@ -365,7 +374,7 @@ GF_Err kind_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void ctts_del(GF_Box *s) +void ctts_box_del(GF_Box *s) { GF_CompositionOffsetBox *ptr = (GF_CompositionOffsetBox *)s; if (ptr->entries) gf_free(ptr->entries); @@ -374,14 +383,14 @@ void ctts_del(GF_Box *s) -GF_Err ctts_Read(GF_Box *s, GF_BitStream *bs) +GF_Err ctts_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; u32 sampleCount; GF_CompositionOffsetBox *ptr = (GF_CompositionOffsetBox *)s; - ptr->nb_entries = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); + ptr->nb_entries = gf_bs_read_u32(bs); if (ptr->nb_entries > ptr->size / 8) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Invalid number of entries %d in ctts\n", ptr->nb_entries)); @@ -393,12 +402,16 @@ GF_Err ctts_Read(GF_Box *s, GF_BitStream *bs) if (!ptr->entries) return GF_OUT_OF_MEM; sampleCount = 0; for (i=0; inb_entries; i++) { + ISOM_DECREASE_SIZE(ptr, 8); ptr->entries[i].sampleCount = gf_bs_read_u32(bs); if (ptr->version) ptr->entries[i].decodingOffset = gf_bs_read_int(bs, 32); else ptr->entries[i].decodingOffset = (s32) gf_bs_read_u32(bs); sampleCount += ptr->entries[i].sampleCount; + + if (ptr->max_ts_delta < ABS(ptr->entries[i].decodingOffset)) + ptr->max_ts_delta = ABS(ptr->entries[i].decodingOffset); } #ifndef GPAC_DISABLE_ISOM_WRITE ptr->w_LastSampleNumber = sampleCount; @@ -406,7 +419,7 @@ GF_Err ctts_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *ctts_New() +GF_Box *ctts_box_new() { ISOM_DECL_BOX_ALLOC(GF_CompositionOffsetBox, GF_ISOM_BOX_TYPE_CTTS); return (GF_Box *) tmp; @@ -416,7 +429,7 @@ GF_Box *ctts_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err ctts_Write(GF_Box *s, GF_BitStream *bs) +GF_Err ctts_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -436,7 +449,7 @@ GF_Err ctts_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err ctts_Size(GF_Box *s) +GF_Err ctts_box_size(GF_Box *s) { GF_CompositionOffsetBox *ptr = (GF_CompositionOffsetBox *) s; @@ -446,7 +459,7 @@ GF_Err ctts_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void cslg_del(GF_Box *s) +void cslg_box_del(GF_Box *s) { GF_CompositionToDecodeBox *ptr = (GF_CompositionToDecodeBox *)s; if (ptr == NULL) return; @@ -454,10 +467,11 @@ void cslg_del(GF_Box *s) return; } -GF_Err cslg_Read(GF_Box *s, GF_BitStream *bs) +GF_Err cslg_box_read(GF_Box *s, GF_BitStream *bs) { GF_CompositionToDecodeBox *ptr = (GF_CompositionToDecodeBox *)s; + ISOM_DECREASE_SIZE(ptr, 20); ptr->compositionToDTSShift = gf_bs_read_int(bs, 32); ptr->leastDecodeToDisplayDelta = gf_bs_read_int(bs, 32); ptr->greatestDecodeToDisplayDelta = gf_bs_read_int(bs, 32); @@ -466,7 +480,7 @@ GF_Err cslg_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *cslg_New() +GF_Box *cslg_box_new() { ISOM_DECL_BOX_ALLOC(GF_CompositionToDecodeBox, GF_ISOM_BOX_TYPE_CSLG); return (GF_Box *) tmp; @@ -474,7 +488,7 @@ GF_Box *cslg_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err cslg_Write(GF_Box *s, GF_BitStream *bs) +GF_Err cslg_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_CompositionToDecodeBox *ptr = (GF_CompositionToDecodeBox *)s; @@ -489,7 +503,7 @@ GF_Err cslg_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err cslg_Size(GF_Box *s) +GF_Err cslg_box_size(GF_Box *s) { GF_CompositionToDecodeBox *ptr = (GF_CompositionToDecodeBox *)s; @@ -498,14 +512,14 @@ GF_Err cslg_Size(GF_Box *s) } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void ccst_del(GF_Box *s) +void ccst_box_del(GF_Box *s) { GF_CodingConstraintsBox *ptr = (GF_CodingConstraintsBox *)s; if (ptr) gf_free(ptr); return; } -GF_Err ccst_Read(GF_Box *s, GF_BitStream *bs) +GF_Err ccst_box_read(GF_Box *s, GF_BitStream *bs) { GF_CodingConstraintsBox *ptr = (GF_CodingConstraintsBox *)s; @@ -517,7 +531,7 @@ GF_Err ccst_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *ccst_New() +GF_Box *ccst_box_new() { ISOM_DECL_BOX_ALLOC(GF_CodingConstraintsBox, GF_ISOM_BOX_TYPE_CCST); return (GF_Box *) tmp; @@ -525,7 +539,7 @@ GF_Box *ccst_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err ccst_Write(GF_Box *s, GF_BitStream *bs) +GF_Err ccst_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_CodingConstraintsBox *ptr = (GF_CodingConstraintsBox *)s; @@ -539,7 +553,7 @@ GF_Err ccst_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err ccst_Size(GF_Box *s) +GF_Err ccst_box_size(GF_Box *s) { GF_CodingConstraintsBox *ptr = (GF_CodingConstraintsBox *)s; ptr->size += 4; @@ -548,7 +562,7 @@ GF_Err ccst_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void url_del(GF_Box *s) +void url_box_del(GF_Box *s) { GF_DataEntryURLBox *ptr = (GF_DataEntryURLBox *)s; if (ptr == NULL) return; @@ -558,7 +572,7 @@ void url_del(GF_Box *s) } -GF_Err url_Read(GF_Box *s, GF_BitStream *bs) +GF_Err url_box_read(GF_Box *s, GF_BitStream *bs) { GF_DataEntryURLBox *ptr = (GF_DataEntryURLBox *)s; @@ -566,11 +580,15 @@ GF_Err url_Read(GF_Box *s, GF_BitStream *bs) ptr->location = (char*)gf_malloc((u32) ptr->size); if (! ptr->location) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->location, (u32)ptr->size); + if (ptr->location[ptr->size-1]) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] url box location is not 0-terminated\n" )); + return GF_ISOM_INVALID_FILE; + } } return GF_OK; } -GF_Box *url_New() +GF_Box *url_box_new() { ISOM_DECL_BOX_ALLOC(GF_DataEntryURLBox, GF_ISOM_BOX_TYPE_URL); return (GF_Box *)tmp; @@ -578,7 +596,7 @@ GF_Box *url_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err url_Write(GF_Box *s, GF_BitStream *bs) +GF_Err url_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_DataEntryURLBox *ptr = (GF_DataEntryURLBox *)s; @@ -594,7 +612,7 @@ GF_Err url_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err url_Size(GF_Box *s) +GF_Err url_box_size(GF_Box *s) { GF_DataEntryURLBox *ptr = (GF_DataEntryURLBox *)s; @@ -606,7 +624,7 @@ GF_Err url_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void urn_del(GF_Box *s) +void urn_box_del(GF_Box *s) { GF_DataEntryURNBox *ptr = (GF_DataEntryURNBox *)s; if (ptr == NULL) return; @@ -616,7 +634,7 @@ void urn_del(GF_Box *s) } -GF_Err urn_Read(GF_Box *s, GF_BitStream *bs) +GF_Err urn_box_read(GF_Box *s, GF_BitStream *bs) { u32 i, to_read; char *tmpName; @@ -632,7 +650,7 @@ GF_Err urn_Read(GF_Box *s, GF_BitStream *bs) //then get the break i = 0; - while ( (tmpName[i] != 0) && (i < to_read) ) { + while ( (i < to_read) && (tmpName[i] != 0) ) { i++; } //check the data is consistent @@ -652,20 +670,27 @@ GF_Err urn_Read(GF_Box *s, GF_BitStream *bs) gf_free(tmpName); return GF_OUT_OF_MEM; } - ptr->location = (char*)gf_malloc(sizeof(char) * (to_read - i - 1)); - if (!ptr->location) { - gf_free(tmpName); - gf_free(ptr->nameURN); - ptr->nameURN = NULL; - return GF_OUT_OF_MEM; - } memcpy(ptr->nameURN, tmpName, i + 1); - memcpy(ptr->location, tmpName + i + 1, (to_read - i - 1)); + + if (tmpName[to_read - 1] != 0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] urn box contains invalid location field\n" )); + } + else { + ptr->location = (char*)gf_malloc(sizeof(char) * (to_read - i - 1)); + if (!ptr->location) { + gf_free(tmpName); + gf_free(ptr->nameURN); + ptr->nameURN = NULL; + return GF_OUT_OF_MEM; + } + memcpy(ptr->location, tmpName + i + 1, (to_read - i - 1)); + } + gf_free(tmpName); return GF_OK; } -GF_Box *urn_New() +GF_Box *urn_box_new() { ISOM_DECL_BOX_ALLOC(GF_DataEntryURNBox, GF_ISOM_BOX_TYPE_URN); return (GF_Box *)tmp; @@ -674,7 +699,7 @@ GF_Box *urn_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err urn_Write(GF_Box *s, GF_BitStream *bs) +GF_Err urn_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_DataEntryURNBox *ptr = (GF_DataEntryURNBox *)s; @@ -694,7 +719,7 @@ GF_Err urn_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err urn_Size(GF_Box *s) +GF_Err urn_box_size(GF_Box *s) { GF_DataEntryURNBox *ptr = (GF_DataEntryURNBox *)s; @@ -707,7 +732,7 @@ GF_Err urn_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void unkn_del(GF_Box *s) +void unkn_box_del(GF_Box *s) { GF_UnknownBox *ptr = (GF_UnknownBox *) s; if (!s) return; @@ -716,7 +741,7 @@ void unkn_del(GF_Box *s) } -GF_Err unkn_Read(GF_Box *s, GF_BitStream *bs) +GF_Err unkn_box_read(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 bytesToRead, sub_size, sub_a; @@ -752,39 +777,40 @@ GF_Err unkn_Read(GF_Box *s, GF_BitStream *bs) if (e == GF_OK) { gf_bs_seek(sub_bs, 0); - e = gf_isom_box_array_read(s, sub_bs, gf_isom_box_add_default); + gf_bs_set_cookie(sub_bs, GF_ISOM_BS_COOKIE_NO_LOGS); + e = gf_isom_box_array_read(s, sub_bs, NULL); } gf_bs_del(sub_bs); if (e==GF_OK) { gf_free(ptr->data); ptr->data = NULL; ptr->dataSize = 0; - } else if (s->other_boxes) { - gf_isom_box_array_del(s->other_boxes); - s->other_boxes=NULL; + } else if (s->child_boxes) { + gf_isom_box_array_del(s->child_boxes); + s->child_boxes=NULL; } return GF_OK; } -GF_Box *unkn_New(u32 box_type) +GF_Box *unkn_box_new() { ISOM_DECL_BOX_ALLOC(GF_UnknownBox, GF_ISOM_BOX_TYPE_UNKNOWN); - tmp->original_4cc = box_type; return (GF_Box *) tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err unkn_Write(GF_Box *s, GF_BitStream *bs) +GF_Err unkn_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; - u32 type = s->type; + u32 type; GF_UnknownBox *ptr = (GF_UnknownBox *)s; if (!s) return GF_BAD_PARAM; - s->type = ptr->original_4cc; + type = s->type; + ptr->type = ptr->original_4cc; e = gf_isom_box_write_header(s, bs); - s->type = type; + ptr->type = type; if (e) return e; if (ptr->dataSize && ptr->data) { @@ -793,7 +819,7 @@ GF_Err unkn_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err unkn_Size(GF_Box *s) +GF_Err unkn_box_size(GF_Box *s) { GF_UnknownBox *ptr = (GF_UnknownBox *)s; @@ -806,18 +832,18 @@ GF_Err unkn_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void def_cont_box_del(GF_Box *s) +void def_parent_box_del(GF_Box *s) { if (s) gf_free(s); } -GF_Err def_cont_box_Read(GF_Box *s, GF_BitStream *bs) +GF_Err def_parent_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, gf_isom_box_add_default); + return gf_isom_box_array_read(s, bs, NULL); } -GF_Box *def_cont_box_New() +GF_Box *def_parent_box_new() { ISOM_DECL_BOX_ALLOC(GF_Box, 0); return (GF_Box *) tmp; @@ -825,19 +851,51 @@ GF_Box *def_cont_box_New() #ifndef GPAC_DISABLE_ISOM_WRITEHintSa -GF_Err def_cont_box_Write(GF_Box *s, GF_BitStream *bs) +GF_Err def_parent_box_write(GF_Box *s, GF_BitStream *bs) { return gf_isom_box_write_header(s, bs); } -GF_Err def_cont_box_Size(GF_Box *s) +GF_Err def_parent_box_size(GF_Box *s) +{ + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +void def_parent_full_box_del(GF_Box *s) +{ + if (s) gf_free(s); +} + + +GF_Err def_parent_full_box_read(GF_Box *s, GF_BitStream *bs) +{ + return gf_isom_box_array_read(s, bs, NULL); +} + +GF_Box *def_parent_full_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_Box, 0); + return (GF_Box *) tmp; +} + +#ifndef GPAC_DISABLE_ISOM_WRITEHintSa + +GF_Err def_parent_full_box_write(GF_Box *s, GF_BitStream *bs) +{ + return gf_isom_full_box_write(s, bs); +} + +GF_Err def_parent_full_box_size(GF_Box *s) { return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void uuid_del(GF_Box *s) +void uuid_box_del(GF_Box *s) { GF_UnknownUUIDBox *ptr = (GF_UnknownUUIDBox *) s; if (!s) return; @@ -846,7 +904,7 @@ void uuid_del(GF_Box *s) } -GF_Err uuid_Read(GF_Box *s, GF_BitStream *bs) +GF_Err uuid_box_read(GF_Box *s, GF_BitStream *bs) { u32 bytesToRead; GF_UnknownUUIDBox *ptr = (GF_UnknownUUIDBox *)s; @@ -862,7 +920,7 @@ GF_Err uuid_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *uuid_New() +GF_Box *uuid_box_new() { ISOM_DECL_BOX_ALLOC(GF_UnknownUUIDBox, GF_ISOM_BOX_TYPE_UUID); return (GF_Box *) tmp; @@ -870,7 +928,7 @@ GF_Box *uuid_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err uuid_Write(GF_Box *s, GF_BitStream *bs) +GF_Err uuid_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_UnknownUUIDBox *ptr = (GF_UnknownUUIDBox*)s; @@ -884,7 +942,7 @@ GF_Err uuid_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err uuid_Size(GF_Box *s) +GF_Err uuid_box_size(GF_Box *s) { GF_UnknownUUIDBox*ptr = (GF_UnknownUUIDBox*)s; ptr->size += ptr->dataSize; @@ -894,44 +952,42 @@ GF_Err uuid_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void dinf_del(GF_Box *s) +void dinf_box_del(GF_Box *s) { - GF_DataInformationBox *ptr = (GF_DataInformationBox *)s; - if (ptr == NULL) return; - gf_isom_box_del((GF_Box *)ptr->dref); - gf_free(ptr); + gf_free(s); } -GF_Err dinf_AddBox(GF_Box *s, GF_Box *a) +GF_Err dinf_on_child_box(GF_Box *s, GF_Box *a) { GF_DataInformationBox *ptr = (GF_DataInformationBox *)s; switch(a->type) { case GF_ISOM_BOX_TYPE_DREF: if (ptr->dref) ERROR_ON_DUPLICATED_BOX(a, ptr) - - ptr->dref = (GF_DataReferenceBox *)a; + ptr->dref = (GF_DataReferenceBox *)a; return GF_OK; - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err dinf_Read(GF_Box *s, GF_BitStream *bs) +GF_Err dinf_box_read(GF_Box *s, GF_BitStream *bs) { - GF_Err e = gf_isom_box_array_read(s, bs, dinf_AddBox); + GF_DataInformationBox *dinf; + GF_Err e = gf_isom_box_array_read(s, bs, dinf_on_child_box); if (e) { return e; } - if (!((GF_DataInformationBox *)s)->dref) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Missing dref box in dinf\n")); - ((GF_DataInformationBox *)s)->dref = (GF_DataReferenceBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_DREF); + dinf = (GF_DataInformationBox *)s; + if (!dinf->dref) { + if (! (gf_bs_get_cookie(bs) & GF_ISOM_BS_COOKIE_NO_LOGS) ) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Missing dref box in dinf\n")); + } + dinf->dref = (GF_DataReferenceBox *) gf_isom_box_new_parent(&dinf->child_boxes, GF_ISOM_BOX_TYPE_DREF); } return GF_OK; } -GF_Box *dinf_New() +GF_Box *dinf_box_new() { ISOM_DECL_BOX_ALLOC(GF_DataInformationBox, GF_ISOM_BOX_TYPE_DINF); return (GF_Box *)tmp; @@ -939,35 +995,19 @@ GF_Box *dinf_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err dinf_Write(GF_Box *s, GF_BitStream *bs) +GF_Err dinf_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_DataInformationBox *ptr = (GF_DataInformationBox *)s; - - e = gf_isom_box_write_header(s, bs); - if (e) return e; - if (ptr->dref) { - e = gf_isom_box_write((GF_Box *)ptr->dref, bs); - if (e) return e; - } - return GF_OK; + return gf_isom_box_write_header(s, bs); } -GF_Err dinf_Size(GF_Box *s) +GF_Err dinf_box_size(GF_Box *s) { - GF_Err e; - GF_DataInformationBox *ptr = (GF_DataInformationBox *)s; - if (ptr->dref) { - e = gf_isom_box_size((GF_Box *) ptr->dref); - if (e) return e; - ptr->size += ptr->dref->size; - } return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void dref_del(GF_Box *s) +void dref_box_del(GF_Box *s) { GF_DataReferenceBox *ptr = (GF_DataReferenceBox *) s; if (ptr == NULL) return; @@ -975,32 +1015,15 @@ void dref_del(GF_Box *s) } -GF_Err dref_AddDataEntry(GF_Box *ptr, GF_Box *entry) -{ - if (entry->type==GF_4CC('a','l','i','s')) { - GF_DataEntryURLBox *urle = (GF_DataEntryURLBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_URL); - urle->flags = 1; - gf_isom_box_del(entry); - gf_isom_box_add_default(ptr, (GF_Box *)urle); - GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[iso file] Apple \'alis\' box found, not supported - converting to self-pointing \'url \' \n" )); - } else { - return gf_isom_box_add_default(ptr, entry); - } - return GF_OK; -} - -GF_Err dref_Read(GF_Box *s, GF_BitStream *bs) +GF_Err dref_box_read(GF_Box *s, GF_BitStream *bs) { GF_DataReferenceBox *ptr = (GF_DataReferenceBox *)s; - - if (ptr == NULL) return GF_BAD_PARAM; - gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); - - return gf_isom_box_array_read(s, bs, dref_AddDataEntry); + gf_bs_read_u32(bs); + return gf_isom_box_array_read(s, bs, NULL); } -GF_Box *dref_New() +GF_Box *dref_box_new() { ISOM_DECL_BOX_ALLOC(GF_DataReferenceBox, GF_ISOM_BOX_TYPE_DREF); return (GF_Box *)tmp; @@ -1009,7 +1032,7 @@ GF_Box *dref_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err dref_Write(GF_Box *s, GF_BitStream *bs) +GF_Err dref_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 count; @@ -1018,12 +1041,12 @@ GF_Err dref_Write(GF_Box *s, GF_BitStream *bs) e = gf_isom_full_box_write(s, bs); if (e) return e; - count = ptr->other_boxes ? gf_list_count(ptr->other_boxes) : 0; + count = ptr->child_boxes ? gf_list_count(ptr->child_boxes) : 0; gf_bs_write_u32(bs, count); return GF_OK; } -GF_Err dref_Size(GF_Box *s) +GF_Err dref_box_size(GF_Box *s) { GF_DataReferenceBox *ptr = (GF_DataReferenceBox *)s; if (!s) return GF_BAD_PARAM; @@ -1034,34 +1057,32 @@ GF_Err dref_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void edts_del(GF_Box *s) +void edts_box_del(GF_Box *s) { - GF_EditBox *ptr = (GF_EditBox *) s; - gf_isom_box_del((GF_Box *)ptr->editList); - gf_free(ptr); + gf_free(s); } -GF_Err edts_AddBox(GF_Box *s, GF_Box *a) +GF_Err edts_on_child_box(GF_Box *s, GF_Box *a) { GF_EditBox *ptr = (GF_EditBox *)s; if (a->type == GF_ISOM_BOX_TYPE_ELST) { - if (ptr->editList) return GF_BAD_PARAM; + if (ptr->editList) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->editList = (GF_EditListBox *)a; return GF_OK; } else { - return gf_isom_box_add_default(s, a); + return GF_OK; } return GF_OK; } -GF_Err edts_Read(GF_Box *s, GF_BitStream *bs) +GF_Err edts_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, edts_AddBox); + return gf_isom_box_array_read(s, bs, edts_on_child_box); } -GF_Box *edts_New() +GF_Box *edts_box_new() { ISOM_DECL_BOX_ALLOC(GF_EditBox, GF_ISOM_BOX_TYPE_EDTS); return (GF_Box *) tmp; @@ -1069,43 +1090,35 @@ GF_Box *edts_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err edts_Write(GF_Box *s, GF_BitStream *bs) +GF_Err edts_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; GF_EditBox *ptr = (GF_EditBox *)s; //here we have a trick: if editList is empty, skip the box if (ptr->editList && gf_list_count(ptr->editList->entryList)) { - e = gf_isom_box_write_header(s, bs); - if (e) return e; - e = gf_isom_box_write((GF_Box *) ptr->editList, bs); - if (e) return e; + return gf_isom_box_write_header(s, bs); + } else { + s->size = 0; } return GF_OK; } -GF_Err edts_Size(GF_Box *s) +GF_Err edts_box_size(GF_Box *s) { - GF_Err e; GF_EditBox *ptr = (GF_EditBox *)s; //here we have a trick: if editList is empty, skip the box if (!ptr->editList || ! gf_list_count(ptr->editList->entryList)) { ptr->size = 0; - } else { - e = gf_isom_box_size((GF_Box *)ptr->editList); - if (e) return e; - ptr->size += ptr->editList->size; } return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void elst_del(GF_Box *s) +void elst_box_del(GF_Box *s) { GF_EditListBox *ptr; - GF_EdtsEntry *p; u32 nb_entries; u32 i; @@ -1113,26 +1126,22 @@ void elst_del(GF_Box *s) if (ptr == NULL) return; nb_entries = gf_list_count(ptr->entryList); for (i = 0; i < nb_entries; i++) { - p = (GF_EdtsEntry*)gf_list_get(ptr->entryList, i); + GF_EdtsEntry *p = (GF_EdtsEntry*)gf_list_get(ptr->entryList, i); if (p) gf_free(p); } gf_list_del(ptr->entryList); gf_free(ptr); } - - - -GF_Err elst_Read(GF_Box *s, GF_BitStream *bs) +GF_Err elst_box_read(GF_Box *s, GF_BitStream *bs) { u32 entries; s32 tr; u32 nb_entries; - GF_EdtsEntry *p; GF_EditListBox *ptr = (GF_EditListBox *)s; - nb_entries = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); + nb_entries = gf_bs_read_u32(bs); if (ptr->version == 1) { if (nb_entries > ptr->size / 20) { @@ -1147,17 +1156,20 @@ GF_Err elst_Read(GF_Box *s, GF_BitStream *bs) } - for (entries = 0; entries < nb_entries; entries++ ) { - p = (GF_EdtsEntry *) gf_malloc(sizeof(GF_EdtsEntry)); + for (entries = 0; entries < nb_entries; entries++) { + GF_EdtsEntry *p = (GF_EdtsEntry *) gf_malloc(sizeof(GF_EdtsEntry)); if (!p) return GF_OUT_OF_MEM; if (ptr->version == 1) { + ISOM_DECREASE_SIZE(ptr, 16); p->segmentDuration = gf_bs_read_u64(bs); p->mediaTime = (s64) gf_bs_read_u64(bs); } else { + ISOM_DECREASE_SIZE(ptr, 8); p->segmentDuration = gf_bs_read_u32(bs); tr = gf_bs_read_u32(bs); p->mediaTime = (s64) tr; } + ISOM_DECREASE_SIZE(ptr, 4); p->mediaRate = gf_bs_read_u16(bs); gf_bs_read_u16(bs); gf_list_add(ptr->entryList, p); @@ -1165,7 +1177,7 @@ GF_Err elst_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *elst_New() +GF_Box *elst_box_new() { ISOM_DECL_BOX_ALLOC(GF_EditListBox, GF_ISOM_BOX_TYPE_ELST); tmp->entryList = gf_list_new(); @@ -1178,12 +1190,11 @@ GF_Box *elst_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err elst_Write(GF_Box *s, GF_BitStream *bs) +GF_Err elst_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; u32 nb_entries; - GF_EdtsEntry *p; GF_EditListBox *ptr = (GF_EditListBox *)s; if (!ptr) return GF_BAD_PARAM; @@ -1192,7 +1203,7 @@ GF_Err elst_Write(GF_Box *s, GF_BitStream *bs) if (e) return e; gf_bs_write_u32(bs, nb_entries); for (i = 0; i < nb_entries; i++ ) { - p = (GF_EdtsEntry*)gf_list_get(ptr->entryList, i); + GF_EdtsEntry *p = (GF_EdtsEntry*)gf_list_get(ptr->entryList, i); if (ptr->version == 1) { gf_bs_write_u64(bs, p->segmentDuration); gf_bs_write_u64(bs, p->mediaTime); @@ -1206,7 +1217,7 @@ GF_Err elst_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err elst_Size(GF_Box *s) +GF_Err elst_box_size(GF_Box *s) { u32 durtimebytes; u32 i, nb_entries; @@ -1231,7 +1242,7 @@ GF_Err elst_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void esds_del(GF_Box *s) +void esds_box_del(GF_Box *s) { GF_ESDBox *ptr = (GF_ESDBox *)s; if (ptr == NULL) return; @@ -1240,18 +1251,16 @@ void esds_del(GF_Box *s) } -GF_Err esds_Read(GF_Box *s, GF_BitStream *bs) +GF_Err esds_box_read(GF_Box *s, GF_BitStream *bs) { GF_Err e=GF_OK; u32 descSize; - char *enc_desc; - u32 SLIsPredefined(GF_SLConfig *sl); GF_ESDBox *ptr = (GF_ESDBox *)s; descSize = (u32) (ptr->size); if (descSize) { - enc_desc = (char*)gf_malloc(sizeof(char) * descSize); + char *enc_desc = (char*)gf_malloc(sizeof(char) * descSize); if (!enc_desc) return GF_OUT_OF_MEM; //get the payload gf_bs_read_data(bs, enc_desc, descSize); @@ -1271,7 +1280,7 @@ GF_Err esds_Read(GF_Box *s, GF_BitStream *bs) ptr->desc = NULL; } else { /*fix broken files*/ - if (!ptr->desc->URLString) { + if (ptr->desc && !ptr->desc->URLString) { if (!ptr->desc->slConfig) { ptr->desc->slConfig = (GF_SLConfig *) gf_odf_desc_new(GF_ODF_SLC_TAG); ptr->desc->slConfig->predefined = SLPredef_MP4; @@ -1285,7 +1294,7 @@ GF_Err esds_Read(GF_Box *s, GF_BitStream *bs) return e; } -GF_Box *esds_New() +GF_Box *esds_box_new() { ISOM_DECL_BOX_ALLOC(GF_ESDBox, GF_ISOM_BOX_TYPE_ESDS); return (GF_Box *)tmp; @@ -1294,13 +1303,17 @@ GF_Box *esds_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err esds_Write(GF_Box *s, GF_BitStream *bs) +GF_Err esds_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; - char *enc_desc; + u8 *enc_desc; u32 descSize = 0; GF_ESDBox *ptr = (GF_ESDBox *)s; - + //make sure we write with no ESID and no OCRESID + if (ptr->desc) { + ptr->desc->ESID = 0; + ptr->desc->OCRESID = 0; + } e = gf_isom_full_box_write(s, bs); if (e) return e; e = gf_odf_desc_write((GF_Descriptor *)ptr->desc, &enc_desc, &descSize); @@ -1311,7 +1324,7 @@ GF_Err esds_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err esds_Size(GF_Box *s) +GF_Err esds_box_size(GF_Box *s) { u32 descSize = 0; GF_ESDBox *ptr = (GF_ESDBox *)s; @@ -1322,7 +1335,7 @@ GF_Err esds_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void free_del(GF_Box *s) +void free_box_del(GF_Box *s) { GF_FreeSpaceBox *ptr = (GF_FreeSpaceBox *)s; if (ptr->data) gf_free(ptr->data); @@ -1330,7 +1343,7 @@ void free_del(GF_Box *s) } -GF_Err free_Read(GF_Box *s, GF_BitStream *bs) +GF_Err free_box_read(GF_Box *s, GF_BitStream *bs) { u32 bytesToRead; GF_FreeSpaceBox *ptr = (GF_FreeSpaceBox *)s; @@ -1341,13 +1354,14 @@ GF_Err free_Read(GF_Box *s, GF_BitStream *bs) if (bytesToRead) { ptr->data = (char*)gf_malloc(bytesToRead * sizeof(char)); + if (!ptr->data) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->data, bytesToRead); ptr->dataSize = bytesToRead; } return GF_OK; } -GF_Box *free_New() +GF_Box *free_box_new() { ISOM_DECL_BOX_ALLOC(GF_FreeSpaceBox, GF_ISOM_BOX_TYPE_FREE); return (GF_Box *)tmp; @@ -1355,7 +1369,7 @@ GF_Box *free_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err free_Write(GF_Box *s, GF_BitStream *bs) +GF_Err free_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_FreeSpaceBox *ptr = (GF_FreeSpaceBox *)s; @@ -1382,7 +1396,7 @@ GF_Err free_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err free_Size(GF_Box *s) +GF_Err free_box_size(GF_Box *s) { GF_FreeSpaceBox *ptr = (GF_FreeSpaceBox *)s; ptr->size += ptr->dataSize; @@ -1391,37 +1405,35 @@ GF_Err free_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void ftyp_del(GF_Box *s) +void ftyp_box_del(GF_Box *s) { GF_FileTypeBox *ptr = (GF_FileTypeBox *) s; if (ptr->altBrand) gf_free(ptr->altBrand); gf_free(ptr); } -GF_Box *ftyp_New() +GF_Box *ftyp_box_new() { ISOM_DECL_BOX_ALLOC(GF_FileTypeBox, GF_ISOM_BOX_TYPE_FTYP); return (GF_Box *)tmp; } -GF_Err ftyp_Read(GF_Box *s,GF_BitStream *bs) +GF_Err ftyp_box_read(GF_Box *s,GF_BitStream *bs) { u32 i; GF_FileTypeBox *ptr = (GF_FileTypeBox *)s; - if (ptr->size < 8) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Found ftyp with size < 8, likely broken!\n")); - return GF_BAD_PARAM; - } + ISOM_DECREASE_SIZE(ptr, 8); ptr->majorBrand = gf_bs_read_u32(bs); ptr->minorVersion = gf_bs_read_u32(bs); - ISOM_DECREASE_SIZE(ptr, 8); + if (ptr->size % 4) return GF_ISOM_INVALID_FILE; ptr->altCount = ( (u32) (ptr->size)) / 4; if (!ptr->altCount) return GF_OK; - if (ptr->altCount * 4 != (u32) (ptr->size)) return GF_ISOM_INVALID_FILE; ptr->altBrand = (u32*)gf_malloc(sizeof(u32)*ptr->altCount); + if (!ptr->altBrand) return GF_OUT_OF_MEM; + for (i = 0; ialtCount; i++) { ptr->altBrand[i] = gf_bs_read_u32(bs); } @@ -1432,7 +1444,7 @@ GF_Err ftyp_Read(GF_Box *s,GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err ftyp_Write(GF_Box *s, GF_BitStream *bs) +GF_Err ftyp_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -1448,7 +1460,7 @@ GF_Err ftyp_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err ftyp_Size(GF_Box *s) +GF_Err ftyp_box_size(GF_Box *s) { GF_FileTypeBox *ptr = (GF_FileTypeBox *)s; @@ -1460,7 +1472,7 @@ GF_Err ftyp_Size(GF_Box *s) -void gnrm_del(GF_Box *s) +void gnrm_box_del(GF_Box *s) { GF_GenericSampleEntryBox *ptr = (GF_GenericSampleEntryBox *)s; gf_isom_sample_entry_predestroy((GF_SampleEntryBox *)ptr); @@ -1468,7 +1480,7 @@ void gnrm_del(GF_Box *s) gf_free(ptr); } -GF_Box *gnrm_New() +GF_Box *gnrm_box_new() { ISOM_DECL_BOX_ALLOC(GF_GenericSampleEntryBox, GF_ISOM_BOX_TYPE_GNRM); @@ -1477,14 +1489,14 @@ GF_Box *gnrm_New() } //dummy -GF_Err gnrm_Read(GF_Box *s, GF_BitStream *bs) +GF_Err gnrm_box_read(GF_Box *s, GF_BitStream *bs) { return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err gnrm_Write(GF_Box *s, GF_BitStream *bs) +GF_Err gnrm_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_GenericSampleEntryBox *ptr = (GF_GenericSampleEntryBox *)s; @@ -1500,7 +1512,7 @@ GF_Err gnrm_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err gnrm_Size(GF_Box *s) +GF_Err gnrm_box_size(GF_Box *s) { GF_GenericSampleEntryBox *ptr = (GF_GenericSampleEntryBox *)s; s->type = GF_ISOM_BOX_TYPE_GNRM; @@ -1511,7 +1523,7 @@ GF_Err gnrm_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void gnrv_del(GF_Box *s) +void gnrv_box_del(GF_Box *s) { GF_GenericVisualSampleEntryBox *ptr = (GF_GenericVisualSampleEntryBox *)s; gf_isom_sample_entry_predestroy((GF_SampleEntryBox *)ptr); @@ -1519,14 +1531,14 @@ void gnrv_del(GF_Box *s) gf_free(ptr); } -GF_Box *gnrv_New() +GF_Box *gnrv_box_new() { ISOM_DECL_BOX_ALLOC(GF_GenericVisualSampleEntryBox, GF_ISOM_BOX_TYPE_GNRV); gf_isom_video_sample_entry_init((GF_VisualSampleEntryBox*) tmp); return (GF_Box *)tmp; } //dummy -GF_Err gnrv_Read(GF_Box *s, GF_BitStream *bs) +GF_Err gnrv_box_read(GF_Box *s, GF_BitStream *bs) { return GF_OK; } @@ -1534,7 +1546,7 @@ GF_Err gnrv_Read(GF_Box *s, GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err gnrv_Write(GF_Box *s, GF_BitStream *bs) +GF_Err gnrv_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_GenericVisualSampleEntryBox *ptr = (GF_GenericVisualSampleEntryBox *)s; @@ -1550,7 +1562,7 @@ GF_Err gnrv_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err gnrv_Size(GF_Box *s) +GF_Err gnrv_box_size(GF_Box *s) { GF_GenericVisualSampleEntryBox *ptr = (GF_GenericVisualSampleEntryBox *)s; s->type = GF_ISOM_BOX_TYPE_GNRV; @@ -1563,7 +1575,7 @@ GF_Err gnrv_Size(GF_Box *s) -void gnra_del(GF_Box *s) +void gnra_box_del(GF_Box *s) { GF_GenericAudioSampleEntryBox *ptr = (GF_GenericAudioSampleEntryBox *)s; gf_isom_sample_entry_predestroy((GF_SampleEntryBox *)ptr); @@ -1571,21 +1583,21 @@ void gnra_del(GF_Box *s) gf_free(ptr); } -GF_Box *gnra_New() +GF_Box *gnra_box_new() { ISOM_DECL_BOX_ALLOC(GF_GenericAudioSampleEntryBox, GF_ISOM_BOX_TYPE_GNRA); gf_isom_audio_sample_entry_init((GF_AudioSampleEntryBox*) tmp); return (GF_Box *)tmp; } //dummy -GF_Err gnra_Read(GF_Box *s, GF_BitStream *bs) +GF_Err gnra_box_read(GF_Box *s, GF_BitStream *bs) { return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err gnra_Write(GF_Box *s, GF_BitStream *bs) +GF_Err gnra_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_GenericAudioSampleEntryBox *ptr = (GF_GenericAudioSampleEntryBox *)s; @@ -1603,7 +1615,7 @@ GF_Err gnra_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err gnra_Size(GF_Box *s) +GF_Err gnra_box_size(GF_Box *s) { GF_GenericAudioSampleEntryBox *ptr = (GF_GenericAudioSampleEntryBox *)s; s->type = GF_ISOM_BOX_TYPE_GNRA; @@ -1614,7 +1626,7 @@ GF_Err gnra_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void hdlr_del(GF_Box *s) +void hdlr_box_del(GF_Box *s) { GF_HandlerBox *ptr = (GF_HandlerBox *)s; if (ptr == NULL) return; @@ -1623,41 +1635,42 @@ void hdlr_del(GF_Box *s) } -GF_Err hdlr_Read(GF_Box *s, GF_BitStream *bs) +GF_Err hdlr_box_read(GF_Box *s, GF_BitStream *bs) { + u64 cookie; GF_HandlerBox *ptr = (GF_HandlerBox *)s; + ISOM_DECREASE_SIZE(ptr, 20); ptr->reserved1 = gf_bs_read_u32(bs); ptr->handlerType = gf_bs_read_u32(bs); gf_bs_read_data(bs, (char*)ptr->reserved2, 12); - ISOM_DECREASE_SIZE(ptr, 20); + cookie = gf_bs_get_cookie(bs); + if (ptr->handlerType==GF_ISOM_MEDIA_VISUAL) + cookie |= GF_ISOM_BS_COOKIE_VISUAL_TRACK; + else + cookie &= ~GF_ISOM_BS_COOKIE_VISUAL_TRACK; + gf_bs_set_cookie(bs, cookie); if (ptr->size) { - size_t len; ptr->nameUTF8 = (char*)gf_malloc((u32) ptr->size); - if (ptr->nameUTF8 == NULL) return GF_OUT_OF_MEM; + if (!ptr->nameUTF8) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->nameUTF8, (u32) ptr->size); - /*safety check in case the string is not null-terminated*/ + + //patch for old QT files - we cannot rely on checking if str[0]==len(str+1) since we may have + //cases where the first character of the string decimal value is indeed the same as the string length!! + //we had this issue with encryption_import test + //we therefore only check if last char is null, and if not so assume old QT style if (ptr->nameUTF8[ptr->size-1]) { - char *str = (char*)gf_malloc((u32) ptr->size + 1); - memcpy(str, ptr->nameUTF8, (u32) ptr->size); - str[ptr->size] = 0; - gf_free(ptr->nameUTF8); - ptr->nameUTF8 = str; - } - //patch for old QT files - if (ptr->size > 1 && ptr->nameUTF8[0] == ptr->size-1) { - len = strlen(ptr->nameUTF8 + 1); - memmove(ptr->nameUTF8, ptr->nameUTF8+1, len ); - ptr->nameUTF8[len] = 0; + memmove(ptr->nameUTF8, ptr->nameUTF8+1, sizeof(char) * (u32) (ptr->size-1) ); + ptr->nameUTF8[ptr->size-1] = 0; ptr->store_counted_string = GF_TRUE; } } return GF_OK; } -GF_Box *hdlr_New() +GF_Box *hdlr_box_new() { ISOM_DECL_BOX_ALLOC(GF_HandlerBox, GF_ISOM_BOX_TYPE_HDLR); return (GF_Box *)tmp; @@ -1666,7 +1679,7 @@ GF_Box *hdlr_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err hdlr_Write(GF_Box *s, GF_BitStream *bs) +GF_Err hdlr_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_HandlerBox *ptr = (GF_HandlerBox *)s; @@ -1691,7 +1704,7 @@ GF_Err hdlr_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err hdlr_Size(GF_Box *s) +GF_Err hdlr_box_size(GF_Box *s) { GF_HandlerBox *ptr = (GF_HandlerBox *)s; ptr->size += 20 + 1; //null term or counted string @@ -1704,21 +1717,21 @@ GF_Err hdlr_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void hinf_del(GF_Box *s) +void hinf_box_del(GF_Box *s) { GF_HintInfoBox *hinf = (GF_HintInfoBox *)s; gf_free(hinf); } -GF_Box *hinf_New() +GF_Box *hinf_box_new() { ISOM_DECL_BOX_ALLOC(GF_HintInfoBox, GF_ISOM_BOX_TYPE_HINF); - tmp->other_boxes = gf_list_new(); + tmp->child_boxes = gf_list_new(); return (GF_Box *)tmp; } -GF_Err hinf_AddBox(GF_Box *s, GF_Box *a) +GF_Err hinf_on_child_box(GF_Box *s, GF_Box *a) { GF_MAXRBox *maxR; GF_HintInfoBox *hinf = (GF_HintInfoBox *)s; @@ -1726,37 +1739,37 @@ GF_Err hinf_AddBox(GF_Box *s, GF_Box *a) switch (a->type) { case GF_ISOM_BOX_TYPE_MAXR: i=0; - while ((maxR = (GF_MAXRBox *)gf_list_enum(hinf->other_boxes, &i))) { + while ((maxR = (GF_MAXRBox *)gf_list_enum(hinf->child_boxes, &i))) { if ((maxR->type==GF_ISOM_BOX_TYPE_MAXR) && (maxR->granularity == ((GF_MAXRBox *)a)->granularity)) - return GF_ISOM_INVALID_FILE; + ERROR_ON_DUPLICATED_BOX(a, s) } break; } - return gf_isom_box_add_default(s, a); + return GF_OK; } -GF_Err hinf_Read(GF_Box *s, GF_BitStream *bs) +GF_Err hinf_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, hinf_AddBox); + return gf_isom_box_array_read(s, bs, hinf_on_child_box); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err hinf_Write(GF_Box *s, GF_BitStream *bs) +GF_Err hinf_box_write(GF_Box *s, GF_BitStream *bs) { // GF_HintInfoBox *ptr = (GF_HintInfoBox *)s; if (!s) return GF_BAD_PARAM; return gf_isom_box_write_header(s, bs); } -GF_Err hinf_Size(GF_Box *s) +GF_Err hinf_box_size(GF_Box *s) { return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void hmhd_del(GF_Box *s) +void hmhd_box_del(GF_Box *s) { GF_HintMediaHeaderBox *ptr = (GF_HintMediaHeaderBox *)s; if (ptr == NULL) return; @@ -1764,10 +1777,11 @@ void hmhd_del(GF_Box *s) } -GF_Err hmhd_Read(GF_Box *s,GF_BitStream *bs) +GF_Err hmhd_box_read(GF_Box *s,GF_BitStream *bs) { GF_HintMediaHeaderBox *ptr = (GF_HintMediaHeaderBox *)s; + ISOM_DECREASE_SIZE(ptr, 16); ptr->maxPDUSize = gf_bs_read_u16(bs); ptr->avgPDUSize = gf_bs_read_u16(bs); ptr->maxBitrate = gf_bs_read_u32(bs); @@ -1776,7 +1790,7 @@ GF_Err hmhd_Read(GF_Box *s,GF_BitStream *bs) return GF_OK; } -GF_Box *hmhd_New() +GF_Box *hmhd_box_new() { ISOM_DECL_BOX_ALLOC(GF_HintMediaHeaderBox, GF_ISOM_BOX_TYPE_HMHD); return (GF_Box *)tmp; @@ -1785,7 +1799,7 @@ GF_Box *hmhd_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err hmhd_Write(GF_Box *s, GF_BitStream *bs) +GF_Err hmhd_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_HintMediaHeaderBox *ptr = (GF_HintMediaHeaderBox *)s; @@ -1800,7 +1814,7 @@ GF_Err hmhd_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err hmhd_Size(GF_Box *s) +GF_Err hmhd_box_size(GF_Box *s) { GF_HintMediaHeaderBox *ptr = (GF_HintMediaHeaderBox *)s; ptr->size += 16; @@ -1809,18 +1823,18 @@ GF_Err hmhd_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *hnti_New() +GF_Box *hnti_box_new() { ISOM_DECL_BOX_ALLOC(GF_HintTrackInfoBox, GF_ISOM_BOX_TYPE_HNTI); return (GF_Box *)tmp; } -void hnti_del(GF_Box *a) +void hnti_box_del(GF_Box *a) { gf_free(a); } -GF_Err hnti_AddBox(GF_Box *s, GF_Box *a) +GF_Err hnti_on_child_box(GF_Box *s, GF_Box *a) { GF_HintTrackInfoBox *hnti = (GF_HintTrackInfoBox *)s; if (!hnti || !a) return GF_BAD_PARAM; @@ -1829,28 +1843,28 @@ GF_Err hnti_AddBox(GF_Box *s, GF_Box *a) //this is the value for GF_RTPBox - same as HintSampleEntry for RTP !!! case GF_ISOM_BOX_TYPE_RTP: case GF_ISOM_BOX_TYPE_SDP: - if (hnti->SDP) return GF_BAD_PARAM; + if (hnti->SDP) ERROR_ON_DUPLICATED_BOX(a, s) hnti->SDP = a; break; default: break; } - return gf_isom_box_add_default(s, a); + return GF_OK; } -GF_Err hnti_Read(GF_Box *s, GF_BitStream *bs) +GF_Err hnti_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read_ex(s, bs, hnti_AddBox, s->type); + return gf_isom_box_array_read_ex(s, bs, hnti_on_child_box, s->type); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err hnti_Write(GF_Box *s, GF_BitStream *bs) +GF_Err hnti_box_write(GF_Box *s, GF_BitStream *bs) { return gf_isom_box_write_header(s, bs); } -GF_Err hnti_Size(GF_Box *s) +GF_Err hnti_box_size(GF_Box *s) { return GF_OK; } @@ -1860,14 +1874,14 @@ GF_Err hnti_Size(GF_Box *s) GF_SDPBox **********************************************************/ -void sdp_del(GF_Box *s) +void sdp_box_del(GF_Box *s) { GF_SDPBox *ptr = (GF_SDPBox *)s; if (ptr->sdpText) gf_free(ptr->sdpText); gf_free(ptr); } -GF_Err sdp_Read(GF_Box *s, GF_BitStream *bs) +GF_Err sdp_box_read(GF_Box *s, GF_BitStream *bs) { u32 length; GF_SDPBox *ptr = (GF_SDPBox *)s; @@ -1882,13 +1896,13 @@ GF_Err sdp_Read(GF_Box *s, GF_BitStream *bs) ptr->sdpText[length] = 0; return GF_OK; } -GF_Box *sdp_New() +GF_Box *sdp_box_new() { ISOM_DECL_BOX_ALLOC(GF_SDPBox, GF_ISOM_BOX_TYPE_SDP); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err sdp_Write(GF_Box *s, GF_BitStream *bs) +GF_Err sdp_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_SDPBox *ptr = (GF_SDPBox *)s; @@ -1896,14 +1910,16 @@ GF_Err sdp_Write(GF_Box *s, GF_BitStream *bs) e = gf_isom_box_write_header(s, bs); if (e) return e; //don't write the NULL char!!! - gf_bs_write_data(bs, ptr->sdpText, (u32) strlen(ptr->sdpText)); + if (ptr->sdpText) + gf_bs_write_data(bs, ptr->sdpText, (u32) strlen(ptr->sdpText)); return GF_OK; } -GF_Err sdp_Size(GF_Box *s) +GF_Err sdp_box_size(GF_Box *s) { GF_SDPBox *ptr = (GF_SDPBox *)s; //don't count the NULL char!!! - ptr->size += strlen(ptr->sdpText); + if (ptr->sdpText) + ptr->size += strlen(ptr->sdpText); return GF_OK; } @@ -1912,14 +1928,14 @@ GF_Err sdp_Size(GF_Box *s) -void rtp_hnti_del(GF_Box *s) +void rtp_hnti_box_del(GF_Box *s) { GF_RTPBox *ptr = (GF_RTPBox *)s; if (ptr->sdpText) gf_free(ptr->sdpText); gf_free(ptr); } -GF_Err rtp_hnti_Read(GF_Box *s, GF_BitStream *bs) +GF_Err rtp_hnti_box_read(GF_Box *s, GF_BitStream *bs) { u32 length; GF_RTPBox *ptr = (GF_RTPBox *)s; @@ -1938,14 +1954,14 @@ GF_Err rtp_hnti_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *rtp_hnti_New() +GF_Box *rtp_hnti_box_new() { ISOM_DECL_BOX_ALLOC(GF_RTPBox, GF_ISOM_BOX_TYPE_RTP); tmp->subType = GF_ISOM_BOX_TYPE_SDP; return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err rtp_hnti_Write(GF_Box *s, GF_BitStream *bs) +GF_Err rtp_hnti_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_RTPBox *ptr = (GF_RTPBox *)s; @@ -1958,7 +1974,7 @@ GF_Err rtp_hnti_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err rtp_hnti_Size(GF_Box *s) +GF_Err rtp_hnti_box_size(GF_Box *s) { GF_RTPBox *ptr = (GF_RTPBox *)s; ptr->size += 4 + strlen(ptr->sdpText); @@ -1972,24 +1988,25 @@ GF_Err rtp_hnti_Size(GF_Box *s) TRPY GF_Box **********************************************************/ -void trpy_del(GF_Box *s) +void trpy_box_del(GF_Box *s) { gf_free((GF_TRPYBox *)s); } -GF_Err trpy_Read(GF_Box *s, GF_BitStream *bs) +GF_Err trpy_box_read(GF_Box *s, GF_BitStream *bs) { GF_TRPYBox *ptr = (GF_TRPYBox *)s; + ISOM_DECREASE_SIZE(ptr, 8); ptr->nbBytes = gf_bs_read_u64(bs); return GF_OK; } -GF_Box *trpy_New() +GF_Box *trpy_box_new() { ISOM_DECL_BOX_ALLOC(GF_TRPYBox, GF_ISOM_BOX_TYPE_TRPY); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err trpy_Write(GF_Box *s, GF_BitStream *bs) +GF_Err trpy_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TRPYBox *ptr = (GF_TRPYBox *)s; @@ -2000,7 +2017,7 @@ GF_Err trpy_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u64(bs, ptr->nbBytes); return GF_OK; } -GF_Err trpy_Size(GF_Box *s) +GF_Err trpy_box_size(GF_Box *s) { s->size += 8; return GF_OK; @@ -2011,17 +2028,18 @@ GF_Err trpy_Size(GF_Box *s) TOTL GF_Box **********************************************************/ -void totl_del(GF_Box *s) +void totl_box_del(GF_Box *s) { gf_free((GF_TRPYBox *)s); } -GF_Err totl_Read(GF_Box *s, GF_BitStream *bs) +GF_Err totl_box_read(GF_Box *s, GF_BitStream *bs) { GF_TOTLBox *ptr = (GF_TOTLBox *)s; + ISOM_DECREASE_SIZE(ptr, 4); ptr->nbBytes = gf_bs_read_u32(bs); return GF_OK; } -GF_Box *totl_New() +GF_Box *totl_box_new() { ISOM_DECL_BOX_ALLOC(GF_TOTLBox, GF_ISOM_BOX_TYPE_TOTL); return (GF_Box *)tmp; @@ -2029,7 +2047,7 @@ GF_Box *totl_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err totl_Write(GF_Box *s, GF_BitStream *bs) +GF_Err totl_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TOTLBox *ptr = (GF_TOTLBox *)s; @@ -2039,7 +2057,7 @@ GF_Err totl_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u32(bs, ptr->nbBytes); return GF_OK; } -GF_Err totl_Size(GF_Box *s) +GF_Err totl_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -2051,24 +2069,25 @@ GF_Err totl_Size(GF_Box *s) NUMP GF_Box **********************************************************/ -void nump_del(GF_Box *s) +void nump_box_del(GF_Box *s) { gf_free((GF_NUMPBox *)s); } -GF_Err nump_Read(GF_Box *s, GF_BitStream *bs) +GF_Err nump_box_read(GF_Box *s, GF_BitStream *bs) { GF_NUMPBox *ptr = (GF_NUMPBox *)s; + ISOM_DECREASE_SIZE(ptr, 8); ptr->nbPackets = gf_bs_read_u64(bs); return GF_OK; } -GF_Box *nump_New() +GF_Box *nump_box_new() { ISOM_DECL_BOX_ALLOC(GF_NUMPBox, GF_ISOM_BOX_TYPE_NUMP); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err nump_Write(GF_Box *s, GF_BitStream *bs) +GF_Err nump_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_NUMPBox *ptr = (GF_NUMPBox *)s; @@ -2078,7 +2097,7 @@ GF_Err nump_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u64(bs, ptr->nbPackets); return GF_OK; } -GF_Err nump_Size(GF_Box *s) +GF_Err nump_box_size(GF_Box *s) { s->size += 8; return GF_OK; @@ -2090,23 +2109,24 @@ GF_Err nump_Size(GF_Box *s) NPCK GF_Box **********************************************************/ -void npck_del(GF_Box *s) +void npck_box_del(GF_Box *s) { gf_free((GF_NPCKBox *)s); } -GF_Err npck_Read(GF_Box *s, GF_BitStream *bs) +GF_Err npck_box_read(GF_Box *s, GF_BitStream *bs) { GF_NPCKBox *ptr = (GF_NPCKBox *)s; + ISOM_DECREASE_SIZE(ptr, 4); ptr->nbPackets = gf_bs_read_u32(bs); return GF_OK; } -GF_Box *npck_New() +GF_Box *npck_box_new() { ISOM_DECL_BOX_ALLOC(GF_NPCKBox, GF_ISOM_BOX_TYPE_NPCK); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err npck_Write(GF_Box *s, GF_BitStream *bs) +GF_Err npck_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_NPCKBox *ptr = (GF_NPCKBox *)s; @@ -2116,7 +2136,7 @@ GF_Err npck_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u32(bs, ptr->nbPackets); return GF_OK; } -GF_Err npck_Size(GF_Box *s) +GF_Err npck_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -2128,24 +2148,25 @@ GF_Err npck_Size(GF_Box *s) TPYL GF_Box **********************************************************/ -void tpyl_del(GF_Box *s) +void tpyl_box_del(GF_Box *s) { gf_free((GF_NTYLBox *)s); } -GF_Err tpyl_Read(GF_Box *s, GF_BitStream *bs) +GF_Err tpyl_box_read(GF_Box *s, GF_BitStream *bs) { GF_NTYLBox *ptr = (GF_NTYLBox *)s; if (ptr == NULL) return GF_BAD_PARAM; + ISOM_DECREASE_SIZE(ptr, 8); ptr->nbBytes = gf_bs_read_u64(bs); return GF_OK; } -GF_Box *tpyl_New() +GF_Box *tpyl_box_new() { ISOM_DECL_BOX_ALLOC(GF_NTYLBox, GF_ISOM_BOX_TYPE_TPYL); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err tpyl_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tpyl_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_NTYLBox *ptr = (GF_NTYLBox *)s; @@ -2155,7 +2176,7 @@ GF_Err tpyl_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u64(bs, ptr->nbBytes); return GF_OK; } -GF_Err tpyl_Size(GF_Box *s) +GF_Err tpyl_box_size(GF_Box *s) { s->size += 8; return GF_OK; @@ -2166,23 +2187,24 @@ GF_Err tpyl_Size(GF_Box *s) TPAY GF_Box **********************************************************/ -void tpay_del(GF_Box *s) +void tpay_box_del(GF_Box *s) { gf_free((GF_TPAYBox *)s); } -GF_Err tpay_Read(GF_Box *s, GF_BitStream *bs) +GF_Err tpay_box_read(GF_Box *s, GF_BitStream *bs) { GF_TPAYBox *ptr = (GF_TPAYBox *)s; + ISOM_DECREASE_SIZE(ptr, 4); ptr->nbBytes = gf_bs_read_u32(bs); return GF_OK; } -GF_Box *tpay_New() +GF_Box *tpay_box_new() { ISOM_DECL_BOX_ALLOC(GF_TPAYBox, GF_ISOM_BOX_TYPE_TPAY); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err tpay_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tpay_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TPAYBox *ptr = (GF_TPAYBox *)s; @@ -2192,7 +2214,7 @@ GF_Err tpay_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u32(bs, ptr->nbBytes); return GF_OK; } -GF_Err tpay_Size(GF_Box *s) +GF_Err tpay_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -2204,25 +2226,26 @@ GF_Err tpay_Size(GF_Box *s) MAXR GF_Box **********************************************************/ -void maxr_del(GF_Box *s) +void maxr_box_del(GF_Box *s) { gf_free((GF_MAXRBox *)s); } -GF_Err maxr_Read(GF_Box *s, GF_BitStream *bs) +GF_Err maxr_box_read(GF_Box *s, GF_BitStream *bs) { GF_MAXRBox *ptr = (GF_MAXRBox *)s; if (ptr == NULL) return GF_BAD_PARAM; + ISOM_DECREASE_SIZE(ptr, 8); ptr->granularity = gf_bs_read_u32(bs); ptr->maxDataRate = gf_bs_read_u32(bs); return GF_OK; } -GF_Box *maxr_New() +GF_Box *maxr_box_new() { ISOM_DECL_BOX_ALLOC(GF_MAXRBox, GF_ISOM_BOX_TYPE_MAXR); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err maxr_Write(GF_Box *s, GF_BitStream *bs) +GF_Err maxr_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_MAXRBox *ptr = (GF_MAXRBox *)s; @@ -2233,7 +2256,7 @@ GF_Err maxr_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u32(bs, ptr->maxDataRate); return GF_OK; } -GF_Err maxr_Size(GF_Box *s) +GF_Err maxr_box_size(GF_Box *s) { s->size += 8; return GF_OK; @@ -2245,23 +2268,24 @@ GF_Err maxr_Size(GF_Box *s) DMED GF_Box **********************************************************/ -void dmed_del(GF_Box *s) +void dmed_box_del(GF_Box *s) { gf_free((GF_DMEDBox *)s); } -GF_Err dmed_Read(GF_Box *s, GF_BitStream *bs) +GF_Err dmed_box_read(GF_Box *s, GF_BitStream *bs) { GF_DMEDBox *ptr = (GF_DMEDBox *)s; + ISOM_DECREASE_SIZE(ptr, 8); ptr->nbBytes = gf_bs_read_u64(bs); return GF_OK; } -GF_Box *dmed_New() +GF_Box *dmed_box_new() { ISOM_DECL_BOX_ALLOC(GF_DMEDBox, GF_ISOM_BOX_TYPE_DMED); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err dmed_Write(GF_Box *s, GF_BitStream *bs) +GF_Err dmed_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_DMEDBox *ptr = (GF_DMEDBox *)s; @@ -2271,7 +2295,7 @@ GF_Err dmed_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u64(bs, ptr->nbBytes); return GF_OK; } -GF_Err dmed_Size(GF_Box *s) +GF_Err dmed_box_size(GF_Box *s) { s->size += 8; return GF_OK; @@ -2282,23 +2306,24 @@ GF_Err dmed_Size(GF_Box *s) DIMM GF_Box **********************************************************/ -void dimm_del(GF_Box *s) +void dimm_box_del(GF_Box *s) { gf_free((GF_DIMMBox *)s); } -GF_Err dimm_Read(GF_Box *s, GF_BitStream *bs) +GF_Err dimm_box_read(GF_Box *s, GF_BitStream *bs) { GF_DIMMBox *ptr = (GF_DIMMBox *)s; + ISOM_DECREASE_SIZE(ptr, 8) ptr->nbBytes = gf_bs_read_u64(bs); return GF_OK; } -GF_Box *dimm_New() +GF_Box *dimm_box_new() { ISOM_DECL_BOX_ALLOC(GF_DIMMBox, GF_ISOM_BOX_TYPE_DIMM); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err dimm_Write(GF_Box *s, GF_BitStream *bs) +GF_Err dimm_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_DIMMBox *ptr = (GF_DIMMBox *)s; @@ -2308,7 +2333,7 @@ GF_Err dimm_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u64(bs, ptr->nbBytes); return GF_OK; } -GF_Err dimm_Size(GF_Box *s) +GF_Err dimm_box_size(GF_Box *s) { s->size += 8; return GF_OK; @@ -2319,23 +2344,24 @@ GF_Err dimm_Size(GF_Box *s) DREP GF_Box **********************************************************/ -void drep_del(GF_Box *s) +void drep_box_del(GF_Box *s) { gf_free((GF_DREPBox *)s); } -GF_Err drep_Read(GF_Box *s, GF_BitStream *bs) +GF_Err drep_box_read(GF_Box *s, GF_BitStream *bs) { GF_DREPBox *ptr = (GF_DREPBox *)s; + ISOM_DECREASE_SIZE(ptr, 8) ptr->nbBytes = gf_bs_read_u64(bs); return GF_OK; } -GF_Box *drep_New() +GF_Box *drep_box_new() { ISOM_DECL_BOX_ALLOC(GF_DREPBox, GF_ISOM_BOX_TYPE_DREP); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err drep_Write(GF_Box *s, GF_BitStream *bs) +GF_Err drep_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_DREPBox *ptr = (GF_DREPBox *)s; @@ -2345,7 +2371,7 @@ GF_Err drep_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u64(bs, ptr->nbBytes); return GF_OK; } -GF_Err drep_Size(GF_Box *s) +GF_Err drep_box_size(GF_Box *s) { s->size += 8; return GF_OK; @@ -2358,23 +2384,24 @@ GF_Err drep_Size(GF_Box *s) TMIN GF_Box **********************************************************/ -void tmin_del(GF_Box *s) +void tmin_box_del(GF_Box *s) { gf_free((GF_TMINBox *)s); } -GF_Err tmin_Read(GF_Box *s, GF_BitStream *bs) +GF_Err tmin_box_read(GF_Box *s, GF_BitStream *bs) { GF_TMINBox *ptr = (GF_TMINBox *)s; + ISOM_DECREASE_SIZE(ptr, 4) ptr->minTime = gf_bs_read_u32(bs); return GF_OK; } -GF_Box *tmin_New() +GF_Box *tmin_box_new() { ISOM_DECL_BOX_ALLOC(GF_TMINBox, GF_ISOM_BOX_TYPE_TMIN); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err tmin_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tmin_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TMINBox *ptr = (GF_TMINBox *)s; @@ -2384,7 +2411,7 @@ GF_Err tmin_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u32(bs, ptr->minTime); return GF_OK; } -GF_Err tmin_Size(GF_Box *s) +GF_Err tmin_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -2396,23 +2423,24 @@ GF_Err tmin_Size(GF_Box *s) TMAX GF_Box **********************************************************/ -void tmax_del(GF_Box *s) +void tmax_box_del(GF_Box *s) { gf_free((GF_TMAXBox *)s); } -GF_Err tmax_Read(GF_Box *s, GF_BitStream *bs) +GF_Err tmax_box_read(GF_Box *s, GF_BitStream *bs) { GF_TMAXBox *ptr = (GF_TMAXBox *)s; + ISOM_DECREASE_SIZE(ptr, 4) ptr->maxTime = gf_bs_read_u32(bs); return GF_OK; } -GF_Box *tmax_New() +GF_Box *tmax_box_new() { ISOM_DECL_BOX_ALLOC(GF_TMAXBox, GF_ISOM_BOX_TYPE_TMAX); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err tmax_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tmax_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TMAXBox *ptr = (GF_TMAXBox *)s; @@ -2422,7 +2450,7 @@ GF_Err tmax_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u32(bs, ptr->maxTime); return GF_OK; } -GF_Err tmax_Size(GF_Box *s) +GF_Err tmax_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -2434,23 +2462,24 @@ GF_Err tmax_Size(GF_Box *s) PMAX GF_Box **********************************************************/ -void pmax_del(GF_Box *s) +void pmax_box_del(GF_Box *s) { gf_free((GF_PMAXBox *)s); } -GF_Err pmax_Read(GF_Box *s, GF_BitStream *bs) +GF_Err pmax_box_read(GF_Box *s, GF_BitStream *bs) { GF_PMAXBox *ptr = (GF_PMAXBox *)s; + ISOM_DECREASE_SIZE(ptr, 4) ptr->maxSize = gf_bs_read_u32(bs); return GF_OK; } -GF_Box *pmax_New() +GF_Box *pmax_box_new() { ISOM_DECL_BOX_ALLOC(GF_PMAXBox, GF_ISOM_BOX_TYPE_PMAX); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err pmax_Write(GF_Box *s, GF_BitStream *bs) +GF_Err pmax_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_PMAXBox *ptr = (GF_PMAXBox *)s; @@ -2460,7 +2489,7 @@ GF_Err pmax_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u32(bs, ptr->maxSize); return GF_OK; } -GF_Err pmax_Size(GF_Box *s) +GF_Err pmax_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -2472,23 +2501,24 @@ GF_Err pmax_Size(GF_Box *s) DMAX GF_Box **********************************************************/ -void dmax_del(GF_Box *s) +void dmax_box_del(GF_Box *s) { gf_free((GF_DMAXBox *)s); } -GF_Err dmax_Read(GF_Box *s, GF_BitStream *bs) +GF_Err dmax_box_read(GF_Box *s, GF_BitStream *bs) { GF_DMAXBox *ptr = (GF_DMAXBox *)s; + ISOM_DECREASE_SIZE(ptr, 4) ptr->maxDur = gf_bs_read_u32(bs); return GF_OK; } -GF_Box *dmax_New() +GF_Box *dmax_box_new() { ISOM_DECL_BOX_ALLOC(GF_DMAXBox, GF_ISOM_BOX_TYPE_DMAX); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err dmax_Write(GF_Box *s, GF_BitStream *bs) +GF_Err dmax_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_DMAXBox *ptr = (GF_DMAXBox *)s; @@ -2498,7 +2528,7 @@ GF_Err dmax_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u32(bs, ptr->maxDur); return GF_OK; } -GF_Err dmax_Size(GF_Box *s) +GF_Err dmax_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -2510,34 +2540,35 @@ GF_Err dmax_Size(GF_Box *s) PAYT GF_Box **********************************************************/ -void payt_del(GF_Box *s) +void payt_box_del(GF_Box *s) { GF_PAYTBox *payt = (GF_PAYTBox *)s; if (payt->payloadString) gf_free(payt->payloadString); gf_free(payt); } -GF_Err payt_Read(GF_Box *s, GF_BitStream *bs) +GF_Err payt_box_read(GF_Box *s, GF_BitStream *bs) { u32 length; GF_PAYTBox *ptr = (GF_PAYTBox *)s; + ISOM_DECREASE_SIZE(ptr, 5 ); ptr->payloadCode = gf_bs_read_u32(bs); length = gf_bs_read_u8(bs); + ISOM_DECREASE_SIZE(ptr, length); ptr->payloadString = (char*)gf_malloc(sizeof(char) * (length+1) ); if (! ptr->payloadString) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->payloadString, length); ptr->payloadString[length] = 0; - ISOM_DECREASE_SIZE(ptr, (4+length+1) ); return GF_OK; } -GF_Box *payt_New() +GF_Box *payt_box_new() { ISOM_DECL_BOX_ALLOC(GF_PAYTBox, GF_ISOM_BOX_TYPE_PAYT); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err payt_Write(GF_Box *s, GF_BitStream *bs) +GF_Err payt_box_write(GF_Box *s, GF_BitStream *bs) { u32 len; GF_Err e; @@ -2546,16 +2577,16 @@ GF_Err payt_Write(GF_Box *s, GF_BitStream *bs) e = gf_isom_box_write_header(s, bs); if (e) return e; gf_bs_write_u32(bs, ptr->payloadCode); - len = (u32) strlen(ptr->payloadString); + len = ptr->payloadString ? (u32) strlen(ptr->payloadString) : 0; gf_bs_write_u8(bs, len); if (len) gf_bs_write_data(bs, ptr->payloadString, len); return GF_OK; } -GF_Err payt_Size(GF_Box *s) +GF_Err payt_box_size(GF_Box *s) { GF_PAYTBox *ptr = (GF_PAYTBox *)s; - s->size += 4; - if (ptr->payloadString) ptr->size += strlen(ptr->payloadString) + 1; + s->size += 4 + 1; + if (ptr->payloadString) ptr->size += strlen(ptr->payloadString); return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ @@ -2565,13 +2596,13 @@ GF_Err payt_Size(GF_Box *s) PAYT GF_Box **********************************************************/ -void name_del(GF_Box *s) +void name_box_del(GF_Box *s) { GF_NameBox *name = (GF_NameBox *)s; if (name->string) gf_free(name->string); gf_free(name); } -GF_Err name_Read(GF_Box *s, GF_BitStream *bs) +GF_Err name_box_read(GF_Box *s, GF_BitStream *bs) { u32 length; GF_NameBox *ptr = (GF_NameBox *)s; @@ -2584,13 +2615,13 @@ GF_Err name_Read(GF_Box *s, GF_BitStream *bs) ptr->string[length] = 0; return GF_OK; } -GF_Box *name_New() +GF_Box *name_box_new() { ISOM_DECL_BOX_ALLOC(GF_NameBox, GF_ISOM_BOX_TYPE_NAME); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err name_Write(GF_Box *s, GF_BitStream *bs) +GF_Err name_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_NameBox *ptr = (GF_NameBox *)s; @@ -2602,7 +2633,7 @@ GF_Err name_Write(GF_Box *s, GF_BitStream *bs) } return GF_OK; } -GF_Err name_Size(GF_Box *s) +GF_Err name_box_size(GF_Box *s) { GF_NameBox *ptr = (GF_NameBox *)s; if (ptr->string) ptr->size += strlen(ptr->string) + 1; @@ -2611,24 +2642,25 @@ GF_Err name_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void tssy_del(GF_Box *s) +void tssy_box_del(GF_Box *s) { gf_free(s); } -GF_Err tssy_Read(GF_Box *s, GF_BitStream *bs) +GF_Err tssy_box_read(GF_Box *s, GF_BitStream *bs) { GF_TimeStampSynchronyBox *ptr = (GF_TimeStampSynchronyBox *)s; + ISOM_DECREASE_SIZE(ptr, 1) gf_bs_read_int(bs, 6); ptr->timestamp_sync = gf_bs_read_int(bs, 2); return GF_OK; } -GF_Box *tssy_New() +GF_Box *tssy_box_new() { ISOM_DECL_BOX_ALLOC(GF_TimeStampSynchronyBox, GF_ISOM_BOX_TYPE_TSSY); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err tssy_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tssy_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TimeStampSynchronyBox *ptr = (GF_TimeStampSynchronyBox *)s; @@ -2639,7 +2671,7 @@ GF_Err tssy_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_int(bs, ptr->timestamp_sync, 2); return GF_OK; } -GF_Err tssy_Size(GF_Box *s) +GF_Err tssy_box_size(GF_Box *s) { s->size += 1; return GF_OK; @@ -2647,15 +2679,12 @@ GF_Err tssy_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void srpp_del(GF_Box *s) +void srpp_box_del(GF_Box *s) { - GF_SRTPProcessBox *ptr = (GF_SRTPProcessBox *)s; - if (ptr->info) gf_isom_box_del((GF_Box*)ptr->info); - if (ptr->scheme_type) gf_isom_box_del((GF_Box*)ptr->scheme_type); gf_free(s); } -GF_Err srpp_AddBox(GF_Box *s, GF_Box *a) +GF_Err srpp_on_child_box(GF_Box *s, GF_Box *a) { GF_SRTPProcessBox *ptr = (GF_SRTPProcessBox *)s; switch(a->type) { @@ -2668,10 +2697,10 @@ GF_Err srpp_AddBox(GF_Box *s, GF_Box *a) ptr->scheme_type = (GF_SchemeTypeBox *)a; return GF_OK; } - return gf_isom_box_add_default(s, a); + return GF_OK; } -GF_Err srpp_Read(GF_Box *s, GF_BitStream *bs) +GF_Err srpp_box_read(GF_Box *s, GF_BitStream *bs) { GF_SRTPProcessBox *ptr = (GF_SRTPProcessBox *)s; @@ -2679,16 +2708,16 @@ GF_Err srpp_Read(GF_Box *s, GF_BitStream *bs) ptr->encryption_algorithm_rtp = gf_bs_read_u32(bs); ptr->encryption_algorithm_rtcp = gf_bs_read_u32(bs); ptr->integrity_algorithm_rtp = gf_bs_read_u32(bs); - ptr->integrity_algorithm_rtp = gf_bs_read_u32(bs); - return gf_isom_box_array_read(s, bs, gf_isom_box_add_default); + ptr->integrity_algorithm_rtcp = gf_bs_read_u32(bs); + return gf_isom_box_array_read(s, bs, srpp_on_child_box); } -GF_Box *srpp_New() +GF_Box *srpp_box_new() { ISOM_DECL_BOX_ALLOC(GF_SRTPProcessBox, GF_ISOM_BOX_TYPE_SRPP); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err srpp_Write(GF_Box *s, GF_BitStream *bs) +GF_Err srpp_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_SRTPProcessBox *ptr = (GF_SRTPProcessBox *)s; @@ -2700,55 +2729,40 @@ GF_Err srpp_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u32(bs, ptr->encryption_algorithm_rtcp); gf_bs_write_u32(bs, ptr->integrity_algorithm_rtp); gf_bs_write_u32(bs, ptr->integrity_algorithm_rtcp); - if (ptr->info) { - e = gf_isom_box_write((GF_Box*)ptr->info, bs); - if (e) return e; - } - if (ptr->scheme_type) { - e = gf_isom_box_write((GF_Box*)ptr->scheme_type, bs); - if (e) return e; - } + return GF_OK; } -GF_Err srpp_Size(GF_Box *s) +GF_Err srpp_box_size(GF_Box *s) { - GF_Err e; + u32 pos = 0; GF_SRTPProcessBox *ptr = (GF_SRTPProcessBox *)s; - s->size += 16; - if (ptr->info) { - e = gf_isom_box_size((GF_Box*)ptr->info); - if (e) return e; - ptr->size += ptr->info->size; - } - if (ptr->scheme_type) { - e = gf_isom_box_size((GF_Box*)ptr->scheme_type); - if (e) return e; - ptr->size += ptr->scheme_type->size; - } + gf_isom_check_position(s, (GF_Box*)ptr->info, &pos); + gf_isom_check_position(s, (GF_Box*)ptr->scheme_type, &pos); return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void rssr_del(GF_Box *s) +void rssr_box_del(GF_Box *s) { gf_free(s); } -GF_Err rssr_Read(GF_Box *s, GF_BitStream *bs) +GF_Err rssr_box_read(GF_Box *s, GF_BitStream *bs) { GF_ReceivedSsrcBox *ptr = (GF_ReceivedSsrcBox *)s; + ISOM_DECREASE_SIZE(ptr, 4) ptr->ssrc = gf_bs_read_u32(bs); return GF_OK; } -GF_Box *rssr_New() +GF_Box *rssr_box_new() { ISOM_DECL_BOX_ALLOC(GF_ReceivedSsrcBox, GF_ISOM_BOX_TYPE_RSSR); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err rssr_Write(GF_Box *s, GF_BitStream *bs) +GF_Err rssr_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_ReceivedSsrcBox *ptr = (GF_ReceivedSsrcBox *)s; @@ -2757,7 +2771,7 @@ GF_Err rssr_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u32(bs, ptr->ssrc); return GF_OK; } -GF_Err rssr_Size(GF_Box *s) +GF_Err rssr_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -2767,7 +2781,7 @@ GF_Err rssr_Size(GF_Box *s) -void iods_del(GF_Box *s) +void iods_box_del(GF_Box *s) { GF_ObjectDescriptorBox *ptr = (GF_ObjectDescriptorBox *)s; if (ptr == NULL) return; @@ -2776,7 +2790,7 @@ void iods_del(GF_Box *s) } -GF_Err iods_Read(GF_Box *s, GF_BitStream *bs) +GF_Err iods_box_read(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 descSize; @@ -2786,6 +2800,8 @@ GF_Err iods_Read(GF_Box *s, GF_BitStream *bs) //use the OD codec... descSize = (u32) (ptr->size); desc = (char*)gf_malloc(sizeof(char) * descSize); + if (!desc) return GF_OUT_OF_MEM; + gf_bs_read_data(bs, desc, descSize); e = gf_odf_desc_read(desc, descSize, &ptr->descriptor); //OK, free our desc @@ -2793,7 +2809,7 @@ GF_Err iods_Read(GF_Box *s, GF_BitStream *bs) return e; } -GF_Box *iods_New() +GF_Box *iods_box_new() { ISOM_DECL_BOX_ALLOC(GF_ObjectDescriptorBox, GF_ISOM_BOX_TYPE_IODS); return (GF_Box *)tmp; @@ -2803,11 +2819,11 @@ GF_Box *iods_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err iods_Write(GF_Box *s, GF_BitStream *bs) +GF_Err iods_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 descSize; - char *desc; + u8 *desc; GF_ObjectDescriptorBox *ptr = (GF_ObjectDescriptorBox *)s; e = gf_isom_full_box_write(s, bs); if (e) return e; @@ -2820,7 +2836,7 @@ GF_Err iods_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err iods_Size(GF_Box *s) +GF_Err iods_box_size(GF_Box *s) { GF_ObjectDescriptorBox *ptr = (GF_ObjectDescriptorBox *)s; @@ -2830,7 +2846,7 @@ GF_Err iods_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void mdat_del(GF_Box *s) +void mdat_box_del(GF_Box *s) { GF_MediaDataBox *ptr = (GF_MediaDataBox *)s; if (!s) return; @@ -2840,18 +2856,20 @@ void mdat_del(GF_Box *s) } -GF_Err mdat_Read(GF_Box *s, GF_BitStream *bs) +GF_Err mdat_box_read(GF_Box *s, GF_BitStream *bs) { GF_MediaDataBox *ptr = (GF_MediaDataBox *)s; if (ptr == NULL) return GF_BAD_PARAM; ptr->dataSize = s->size; + ptr->bsOffset = gf_bs_get_position(bs); + //then skip these bytes gf_bs_skip_bytes(bs, ptr->dataSize); return GF_OK; } -GF_Box *mdat_New() +GF_Box *mdat_box_new() { ISOM_DECL_BOX_ALLOC(GF_MediaDataBox, GF_ISOM_BOX_TYPE_MDAT); return (GF_Box *)tmp; @@ -2859,7 +2877,7 @@ GF_Box *mdat_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err mdat_Write(GF_Box *s, GF_BitStream *bs) +GF_Err mdat_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_MediaDataBox *ptr = (GF_MediaDataBox *)s; @@ -2867,14 +2885,14 @@ GF_Err mdat_Write(GF_Box *s, GF_BitStream *bs) if (e) return e; //make sure we have some data ... - //if not, we handle that independantly (edit files) + //if not, we handle that independently (edit files) if (ptr->data) { gf_bs_write_data(bs, ptr->data, (u32) ptr->dataSize); } return GF_OK; } -GF_Err mdat_Size(GF_Box *s) +GF_Err mdat_box_size(GF_Box *s) { GF_MediaDataBox *ptr = (GF_MediaDataBox *)s; ptr->size += ptr->dataSize; @@ -2883,23 +2901,25 @@ GF_Err mdat_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void mdhd_del(GF_Box *s) +void mdhd_box_del(GF_Box *s) { GF_MediaHeaderBox *ptr = (GF_MediaHeaderBox *)s; if (ptr == NULL) return; gf_free(ptr); } -GF_Err mdhd_Read(GF_Box *s, GF_BitStream *bs) +GF_Err mdhd_box_read(GF_Box *s, GF_BitStream *bs) { GF_MediaHeaderBox *ptr = (GF_MediaHeaderBox *)s; if (ptr->version == 1) { + ISOM_DECREASE_SIZE(ptr, 28) ptr->creationTime = gf_bs_read_u64(bs); ptr->modificationTime = gf_bs_read_u64(bs); ptr->timeScale = gf_bs_read_u32(bs); ptr->duration = gf_bs_read_u64(bs); } else { + ISOM_DECREASE_SIZE(ptr, 16) ptr->creationTime = gf_bs_read_u32(bs); ptr->modificationTime = gf_bs_read_u32(bs); ptr->timeScale = gf_bs_read_u32(bs); @@ -2912,6 +2932,7 @@ GF_Err mdhd_Read(GF_Box *s, GF_BitStream *bs) ptr->original_duration = ptr->duration; + ISOM_DECREASE_SIZE(ptr, 4) //our padding bit gf_bs_read_int(bs, 1); //the spec is unclear here, just says "the value 0 is interpreted as undetermined" @@ -2932,7 +2953,7 @@ GF_Err mdhd_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *mdhd_New() +GF_Box *mdhd_box_new() { ISOM_DECL_BOX_ALLOC(GF_MediaHeaderBox, GF_ISOM_BOX_TYPE_MDHD); @@ -2945,7 +2966,7 @@ GF_Box *mdhd_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err mdhd_Write(GF_Box *s, GF_BitStream *bs) +GF_Err mdhd_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_MediaHeaderBox *ptr = (GF_MediaHeaderBox *)s; @@ -2971,7 +2992,7 @@ GF_Err mdhd_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err mdhd_Size(GF_Box *s) +GF_Err mdhd_box_size(GF_Box *s) { GF_MediaHeaderBox *ptr = (GF_MediaHeaderBox *)s; ptr->version = (ptr->duration>0xFFFFFFFF) ? 1 : 0; @@ -2984,48 +3005,53 @@ GF_Err mdhd_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void mdia_del(GF_Box *s) +void mdia_box_del(GF_Box *s) { GF_MediaBox *ptr = (GF_MediaBox *)s; if (ptr == NULL) return; - if (ptr->mediaHeader) gf_isom_box_del((GF_Box *)ptr->mediaHeader); - if (ptr->information) gf_isom_box_del((GF_Box *)ptr->information); - if (ptr->handler) gf_isom_box_del((GF_Box *)ptr->handler); + if (ptr->nalu_parser) gf_bs_del(ptr->nalu_parser); + if (ptr->nalu_out_bs) gf_bs_del(ptr->nalu_out_bs); + if (ptr->nalu_ps_bs) gf_bs_del(ptr->nalu_ps_bs); + if (ptr->extracted_bs) gf_bs_del(ptr->extracted_bs); + if (ptr->extracted_samp) gf_isom_sample_del(&ptr->extracted_samp); + if (ptr->in_sample_buffer) gf_free(ptr->in_sample_buffer); + if (ptr->tmp_nal_copy_buffer) gf_free(ptr->tmp_nal_copy_buffer); gf_free(ptr); } -GF_Err mdia_AddBox(GF_Box *s, GF_Box *a) +GF_Err mdia_on_child_box(GF_Box *s, GF_Box *a) { GF_MediaBox *ptr = (GF_MediaBox *)s; switch(a->type) { case GF_ISOM_BOX_TYPE_MDHD: if (ptr->mediaHeader) ERROR_ON_DUPLICATED_BOX(a, ptr) - - ptr->mediaHeader = (GF_MediaHeaderBox *)a; + ptr->mediaHeader = (GF_MediaHeaderBox *)a; return GF_OK; case GF_ISOM_BOX_TYPE_HDLR: if (ptr->handler) ERROR_ON_DUPLICATED_BOX(a, ptr) - - ptr->handler = (GF_HandlerBox *)a; + ptr->handler = (GF_HandlerBox *)a; return GF_OK; case GF_ISOM_BOX_TYPE_MINF: if (ptr->information) ERROR_ON_DUPLICATED_BOX(a, ptr) - - ptr->information = (GF_MediaInformationBox *)a; + ptr->information = (GF_MediaInformationBox *)a; return GF_OK; - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err mdia_Read(GF_Box *s, GF_BitStream *bs) +GF_Err mdia_box_read(GF_Box *s, GF_BitStream *bs) { - GF_Err e = gf_isom_box_array_read(s, bs, mdia_AddBox); + GF_Err e; + u64 cookie = gf_bs_get_cookie(bs); + cookie &= ~GF_ISOM_BS_COOKIE_VISUAL_TRACK; + gf_bs_set_cookie(bs, cookie); + e = gf_isom_box_array_read(s, bs, mdia_on_child_box); + gf_bs_set_cookie(bs, cookie); + if (e) return e; if (!((GF_MediaBox *)s)->information) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Missing MediaInformationBox\n")); @@ -3042,7 +3068,7 @@ GF_Err mdia_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *mdia_New() +GF_Box *mdia_box_new() { ISOM_DECL_BOX_ALLOC(GF_MediaBox, GF_ISOM_BOX_TYPE_MDIA); return (GF_Box *)tmp; @@ -3050,71 +3076,42 @@ GF_Box *mdia_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err mdia_Write(GF_Box *s, GF_BitStream *bs) +GF_Err mdia_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_MediaBox *ptr = (GF_MediaBox *)s; - e = gf_isom_box_write_header(s, bs); - if (e) return e; - //Header first - if (ptr->mediaHeader) { - e = gf_isom_box_write((GF_Box *) ptr->mediaHeader, bs); - if (e) return e; - } - //then handler - if (ptr->handler) { - e = gf_isom_box_write((GF_Box *) ptr->handler, bs); - if (e) return e; - } - if (ptr->information) { - e = gf_isom_box_write((GF_Box *) ptr->information, bs); - if (e) return e; - } - return GF_OK; + return gf_isom_box_write_header(s, bs); } -GF_Err mdia_Size(GF_Box *s) +GF_Err mdia_box_size(GF_Box *s) { - GF_Err e; + u32 pos = 0; GF_MediaBox *ptr = (GF_MediaBox *)s; - - if (ptr->mediaHeader) { - e = gf_isom_box_size((GF_Box *) ptr->mediaHeader); - if (e) return e; - ptr->size += ptr->mediaHeader->size; - } - if (ptr->handler) { - e = gf_isom_box_size((GF_Box *) ptr->handler); - if (e) return e; - ptr->size += ptr->handler->size; - } - if (ptr->information) { - e = gf_isom_box_size((GF_Box *) ptr->information); - if (e) return e; - ptr->size += ptr->information->size; - } + //Header first + gf_isom_check_position(s, (GF_Box*)ptr->mediaHeader, &pos); + //then handler + gf_isom_check_position(s, (GF_Box*)ptr->handler, &pos); + //then info + gf_isom_check_position(s, (GF_Box*)ptr->information, &pos); return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void mfra_del(GF_Box *s) +void mfra_box_del(GF_Box *s) { GF_MovieFragmentRandomAccessBox *ptr = (GF_MovieFragmentRandomAccessBox *)s; if (ptr == NULL) return; - if (ptr->mfro) gf_isom_box_del((GF_Box*)ptr->mfro); - gf_isom_box_array_del(ptr->tfra_list); + gf_list_del(ptr->tfra_list); gf_free(ptr); } -GF_Box *mfra_New() +GF_Box *mfra_box_new() { ISOM_DECL_BOX_ALLOC(GF_MovieFragmentRandomAccessBox, GF_ISOM_BOX_TYPE_MFRA); tmp->tfra_list = gf_list_new(); return (GF_Box *)tmp; } -GF_Err mfra_AddBox(GF_Box *s, GF_Box *a) +GF_Err mfra_on_child_box(GF_Box *s, GF_Box *a) { GF_MovieFragmentRandomAccessBox *ptr = (GF_MovieFragmentRandomAccessBox *)s; switch(a->type) { @@ -3124,50 +3121,35 @@ GF_Err mfra_AddBox(GF_Box *s, GF_Box *a) if (ptr->mfro) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->mfro = (GF_MovieFragmentRandomAccessOffsetBox *)a; return GF_OK; - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err mfra_Read(GF_Box *s, GF_BitStream *bs) +GF_Err mfra_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, mfra_AddBox); + return gf_isom_box_array_read(s, bs, mfra_on_child_box); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err mfra_Write(GF_Box *s, GF_BitStream *bs) +GF_Err mfra_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_MovieFragmentRandomAccessBox *ptr = (GF_MovieFragmentRandomAccessBox *)s; - - e = gf_isom_box_array_write(s, ptr->tfra_list, bs); - if (e) return e; - if (ptr->mfro) { - e = gf_isom_box_write((GF_Box *) ptr->mfro, bs); - if (e) return e; - } - return GF_OK; + return gf_isom_box_write_header(s, bs); } -GF_Err mfra_Size(GF_Box *s) +GF_Err mfra_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_MovieFragmentRandomAccessBox *ptr = (GF_MovieFragmentRandomAccessBox *)s; - - if (ptr->mfro) { - e = gf_isom_box_size((GF_Box *)ptr->mfro); - if (e) return e; - ptr->size += ptr->mfro->size; - } - return gf_isom_box_array_size(s, ptr->tfra_list); + gf_isom_check_position_list(s, ptr->tfra_list, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->mfro, &pos); + return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void tfra_del(GF_Box *s) +void tfra_box_del(GF_Box *s) { GF_TrackFragmentRandomAccessBox *ptr = (GF_TrackFragmentRandomAccessBox *)s; if (ptr == NULL) return; @@ -3175,33 +3157,31 @@ void tfra_del(GF_Box *s) gf_free(ptr); } -GF_Box *tfra_New() +GF_Box *tfra_box_new() { ISOM_DECL_BOX_ALLOC(GF_TrackFragmentRandomAccessBox, GF_ISOM_BOX_TYPE_TFRA); return (GF_Box *)tmp; } -GF_Err tfra_Read(GF_Box *s, GF_BitStream *bs) +GF_Err tfra_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; GF_RandomAccessEntry *p = 0; GF_TrackFragmentRandomAccessBox *ptr = (GF_TrackFragmentRandomAccessBox *)s; - if (ptr->size<12) return GF_ISOM_INVALID_FILE; + ISOM_DECREASE_SIZE(ptr, 12); ptr->track_id = gf_bs_read_u32(bs); - ISOM_DECREASE_SIZE(ptr, 4); + if (gf_bs_read_int(bs, 26) != 0) + return GF_ISOM_INVALID_FILE; - if (gf_bs_read_int(bs, 26) !=0) return GF_ISOM_INVALID_FILE; - ptr->traf_bits = (gf_bs_read_int(bs, 2)+1)*8; - ptr->trun_bits = (gf_bs_read_int(bs, 2)+1)*8; - ptr->sample_bits = (gf_bs_read_int(bs, 2)+1)*8; - ISOM_DECREASE_SIZE(ptr, 4); + ptr->traf_bits = (gf_bs_read_int(bs, 2) + 1) * 8; + ptr->trun_bits = (gf_bs_read_int(bs, 2) + 1) * 8; + ptr->sample_bits = (gf_bs_read_int(bs, 2) + 1) * 8; ptr->nb_entries = gf_bs_read_u32(bs); - ISOM_DECREASE_SIZE(ptr, 4); - if (ptr->version==1) { + if (ptr->version == 1) { if (ptr->nb_entries > ptr->size / (16+(ptr->traf_bits+ptr->trun_bits+ptr->sample_bits)/8)) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Invalid number of entries %d in traf\n", ptr->nb_entries)); return GF_ISOM_INVALID_FILE; @@ -3213,8 +3193,7 @@ GF_Err tfra_Read(GF_Box *s, GF_BitStream *bs) } } - if (ptr->nb_entries) - { + if (ptr->nb_entries) { p = (GF_RandomAccessEntry *) gf_malloc(sizeof(GF_RandomAccessEntry) * ptr->nb_entries); if (!p) return GF_OUT_OF_MEM; } @@ -3224,12 +3203,10 @@ GF_Err tfra_Read(GF_Box *s, GF_BitStream *bs) for (i=0; inb_entries; i++) { memset(p, 0, sizeof(GF_RandomAccessEntry)); - if (ptr->version==1) { + if (ptr->version == 1) { p->time = gf_bs_read_u64(bs); p->moof_offset = gf_bs_read_u64(bs); - } - else - { + } else { p->time = gf_bs_read_u32(bs); p->moof_offset = gf_bs_read_u32(bs); } @@ -3239,16 +3216,17 @@ GF_Err tfra_Read(GF_Box *s, GF_BitStream *bs) ++p; } + return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err tfra_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tfra_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; - u32 i; + u32 i, sap_nb_entries; GF_TrackFragmentRandomAccessBox *ptr = (GF_TrackFragmentRandomAccessBox *)s; e = gf_isom_full_box_write(s, bs); @@ -3261,10 +3239,19 @@ GF_Err tfra_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_int(bs, ptr->trun_bits/8 - 1, 2); gf_bs_write_int(bs, ptr->sample_bits/8 - 1, 2); - gf_bs_write_u32(bs, ptr->nb_entries); + sap_nb_entries = 0; + for (i=0; inb_entries; i++) { + GF_RandomAccessEntry *p = &ptr->entries[i]; + //no sap found, do not store + if (p->trun_number) sap_nb_entries++; + } + + gf_bs_write_u32(bs, sap_nb_entries); for (i=0; inb_entries; i++) { - GF_RandomAccessEntry *p = &ptr->entries[i];; + GF_RandomAccessEntry *p = &ptr->entries[i]; + //no sap found, do not store + if (!p->trun_number) continue; if (ptr->version==1) { gf_bs_write_u64(bs, p->time); gf_bs_write_u64(bs, p->moof_offset); @@ -3279,13 +3266,18 @@ GF_Err tfra_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err tfra_Size(GF_Box *s) +GF_Err tfra_box_size(GF_Box *s) { + u32 i; GF_TrackFragmentRandomAccessBox *ptr = (GF_TrackFragmentRandomAccessBox *)s; - ptr->size += 12; - ptr->size += ptr->nb_entries * ( ((ptr->version==1) ? 16 : 8 ) + ptr->traf_bits + ptr->trun_bits + ptr->sample_bits); + for (i=0; inb_entries; i++) { + GF_RandomAccessEntry *p = &ptr->entries[i]; + //no sap found, do not store + if (!p->trun_number) continue; + ptr->size += ((ptr->version==1) ? 16 : 8 ) + ptr->traf_bits/8 + ptr->trun_bits/8 + ptr->sample_bits/8; + } return GF_OK; } @@ -3293,33 +3285,32 @@ GF_Err tfra_Size(GF_Box *s) -void mfro_del(GF_Box *s) +void mfro_box_del(GF_Box *s) { GF_MovieFragmentRandomAccessOffsetBox *ptr = (GF_MovieFragmentRandomAccessOffsetBox *)s; if (ptr == NULL) return; gf_free(ptr); } -GF_Box *mfro_New() +GF_Box *mfro_box_new() { ISOM_DECL_BOX_ALLOC(GF_MovieFragmentRandomAccessOffsetBox, GF_ISOM_BOX_TYPE_MFRO); return (GF_Box *)tmp; } -GF_Err mfro_Read(GF_Box *s, GF_BitStream *bs) +GF_Err mfro_box_read(GF_Box *s, GF_BitStream *bs) { GF_MovieFragmentRandomAccessOffsetBox *ptr = (GF_MovieFragmentRandomAccessOffsetBox *)s; - ptr->container_size = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); - + ptr->container_size = gf_bs_read_u32(bs); return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err mfro_Write(GF_Box *s, GF_BitStream *bs) +GF_Err mfro_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_MovieFragmentRandomAccessOffsetBox *ptr = (GF_MovieFragmentRandomAccessOffsetBox *)s; @@ -3331,7 +3322,7 @@ GF_Err mfro_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err mfro_Size(GF_Box *s) +GF_Err mfro_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -3340,7 +3331,7 @@ GF_Err mfro_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void elng_del(GF_Box *s) +void elng_box_del(GF_Box *s) { GF_ExtendedLanguageBox *ptr = (GF_ExtendedLanguageBox *)s; if (ptr == NULL) return; @@ -3348,7 +3339,7 @@ void elng_del(GF_Box *s) gf_free(ptr); } -GF_Err elng_Read(GF_Box *s, GF_BitStream *bs) +GF_Err elng_box_read(GF_Box *s, GF_BitStream *bs) { GF_ExtendedLanguageBox *ptr = (GF_ExtendedLanguageBox *)s; @@ -3359,6 +3350,7 @@ GF_Err elng_Read(GF_Box *s, GF_BitStream *bs) /*safety check in case the string is not null-terminated*/ if (ptr->extended_language[ptr->size-1]) { char *str = (char*)gf_malloc((u32) ptr->size + 1); + if (!str) return GF_OUT_OF_MEM; memcpy(str, ptr->extended_language, (u32) ptr->size); str[ptr->size] = 0; gf_free(ptr->extended_language); @@ -3368,7 +3360,7 @@ GF_Err elng_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *elng_New() +GF_Box *elng_box_new() { ISOM_DECL_BOX_ALLOC(GF_MediaBox, GF_ISOM_BOX_TYPE_ELNG); return (GF_Box *)tmp; @@ -3376,7 +3368,7 @@ GF_Box *elng_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err elng_Write(GF_Box *s, GF_BitStream *bs) +GF_Err elng_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_ExtendedLanguageBox *ptr = (GF_ExtendedLanguageBox *)s; @@ -3388,7 +3380,7 @@ GF_Err elng_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err elng_Size(GF_Box *s) +GF_Err elng_box_size(GF_Box *s) { GF_ExtendedLanguageBox *ptr = (GF_ExtendedLanguageBox *)s; @@ -3402,21 +3394,22 @@ GF_Err elng_Size(GF_Box *s) #ifndef GPAC_DISABLE_ISOM_FRAGMENTS -void mfhd_del(GF_Box *s) +void mfhd_box_del(GF_Box *s) { GF_MovieFragmentHeaderBox *ptr = (GF_MovieFragmentHeaderBox *)s; if (ptr == NULL) return; gf_free(ptr); } -GF_Err mfhd_Read(GF_Box *s, GF_BitStream *bs) +GF_Err mfhd_box_read(GF_Box *s, GF_BitStream *bs) { GF_MovieFragmentHeaderBox *ptr = (GF_MovieFragmentHeaderBox *)s; + ISOM_DECREASE_SIZE(ptr, 4); ptr->sequence_number = gf_bs_read_u32(bs); return GF_OK; } -GF_Box *mfhd_New() +GF_Box *mfhd_box_new() { ISOM_DECL_BOX_ALLOC(GF_MovieFragmentHeaderBox, GF_ISOM_BOX_TYPE_MFHD); return (GF_Box *)tmp; @@ -3426,7 +3419,7 @@ GF_Box *mfhd_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err mfhd_Write(GF_Box *s, GF_BitStream *bs) +GF_Err mfhd_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_MovieFragmentHeaderBox *ptr = (GF_MovieFragmentHeaderBox *) s; @@ -3438,7 +3431,7 @@ GF_Err mfhd_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err mfhd_Size(GF_Box *s) +GF_Err mfhd_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -3451,7 +3444,7 @@ GF_Err mfhd_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_FRAGMENTS*/ -void minf_del(GF_Box *s) +void minf_box_del(GF_Box *s) { GF_MediaInformationBox *ptr = (GF_MediaInformationBox *)s; if (ptr == NULL) return; @@ -3460,13 +3453,10 @@ void minf_del(GF_Box *s) if (ptr->dataHandler) { gf_isom_datamap_close(ptr); } - if (ptr->InfoHeader) gf_isom_box_del((GF_Box *)ptr->InfoHeader); - if (ptr->dataInformation) gf_isom_box_del((GF_Box *)ptr->dataInformation); - if (ptr->sampleTable) gf_isom_box_del((GF_Box *)ptr->sampleTable); gf_free(ptr); } -GF_Err minf_AddBox(GF_Box *s, GF_Box *a) +GF_Err minf_on_child_box(GF_Box *s, GF_Box *a) { GF_MediaInformationBox *ptr = (GF_MediaInformationBox *)s; switch (a->type) { @@ -3489,52 +3479,39 @@ GF_Err minf_AddBox(GF_Box *s, GF_Box *a) if (ptr->sampleTable ) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->sampleTable = (GF_SampleTableBox *)a; return GF_OK; - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err minf_Read(GF_Box *s, GF_BitStream *bs) +GF_Err minf_box_read(GF_Box *s, GF_BitStream *bs) { GF_MediaInformationBox *ptr = (GF_MediaInformationBox *)s; GF_Err e; - e = gf_isom_box_array_read(s, bs, minf_AddBox); - if (! ptr->dataInformation) { - GF_Box *dinf, *dref, *url; - Bool dump_mode = GF_FALSE; + + e = gf_isom_box_array_read(s, bs, minf_on_child_box); + + if (!e && ! ptr->dataInformation) { + GF_Box *url; GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Missing DataInformationBox\n")); //commented on purpose, we are still able to handle the file, we only throw an error but keep processing // e = GF_ISOM_INVALID_FILE; //add a dinf box to avoid any access to a null dinf - dinf = gf_isom_box_new(GF_ISOM_BOX_TYPE_DINF); - if (!dinf) return GF_OUT_OF_MEM; - if (ptr->InfoHeader && gf_list_find(ptr->other_boxes, ptr->InfoHeader)>=0) dump_mode = GF_TRUE; - if (ptr->sampleTable && gf_list_find(ptr->other_boxes, ptr->sampleTable)>=0) dump_mode = GF_TRUE; + ptr->dataInformation = (GF_DataInformationBox *) gf_isom_box_new_parent(&ptr->child_boxes, GF_ISOM_BOX_TYPE_DINF); + if (!ptr->dataInformation) return GF_OUT_OF_MEM; - ptr->dataInformation = (GF_DataInformationBox *)dinf; + ptr->dataInformation->dref = (GF_DataReferenceBox *) gf_isom_box_new_parent(&ptr->dataInformation->child_boxes, GF_ISOM_BOX_TYPE_DREF); + if (!ptr->dataInformation->dref) return GF_OUT_OF_MEM; - dref = gf_isom_box_new(GF_ISOM_BOX_TYPE_DREF); - if (!dref) return GF_OUT_OF_MEM; - e = dinf_AddBox(dinf, dref); - - url = gf_isom_box_new(GF_ISOM_BOX_TYPE_URL); + url = gf_isom_box_new_parent(&ptr->dataInformation->dref->child_boxes, GF_ISOM_BOX_TYPE_URL); if (!url) return GF_OUT_OF_MEM; ((GF_FullBox*)url)->flags = 1; - e = gf_isom_box_add_default(dref, url); - - if (dump_mode) { - gf_list_add(ptr->other_boxes, ptr->dataInformation); - if (!dinf->other_boxes) dinf->other_boxes = gf_list_new(); - gf_list_add(dinf->other_boxes, dref); - } } return e; } -GF_Box *minf_New() +GF_Box *minf_box_new() { ISOM_DECL_BOX_ALLOC(GF_MediaInformationBox, GF_ISOM_BOX_TYPE_MINF); return (GF_Box *)tmp; @@ -3543,53 +3520,22 @@ GF_Box *minf_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err minf_Write(GF_Box *s, GF_BitStream *bs) +GF_Err minf_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_MediaInformationBox *ptr = (GF_MediaInformationBox *)s; - if (!s) return GF_BAD_PARAM; - - e = gf_isom_box_write_header(s, bs); - if (e) return e; - - //Header first - if (ptr->InfoHeader) { - e = gf_isom_box_write((GF_Box *) ptr->InfoHeader, bs); - if (e) return e; - } - //then dataInfo - if (ptr->dataInformation) { - e = gf_isom_box_write((GF_Box *) ptr->dataInformation, bs); - if (e) return e; - } - //then sampleTable - if (ptr->sampleTable) { - e = gf_isom_box_write((GF_Box *) ptr->sampleTable, bs); - if (e) return e; - } - return GF_OK; + return gf_isom_box_write_header(s, bs); } -GF_Err minf_Size(GF_Box *s) +GF_Err minf_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_MediaInformationBox *ptr = (GF_MediaInformationBox *)s; - - if (ptr->InfoHeader) { - e = gf_isom_box_size((GF_Box *) ptr->InfoHeader); - if (e) return e; - ptr->size += ptr->InfoHeader->size; - } - if (ptr->dataInformation) { - e = gf_isom_box_size((GF_Box *) ptr->dataInformation); - if (e) return e; - ptr->size += ptr->dataInformation->size; - } - if (ptr->sampleTable) { - e = gf_isom_box_size((GF_Box *) ptr->sampleTable); - if (e) return e; - ptr->size += ptr->sampleTable->size; - } + //Header first + gf_isom_check_position(s, (GF_Box *)ptr->InfoHeader, &pos); + //then dataInfo + gf_isom_check_position(s, (GF_Box *)ptr->dataInformation, &pos); + gf_isom_check_position(s, gf_isom_box_find_child(s->child_boxes, GF_ISOM_BOX_TYPE_MVCI), &pos); + //then sampleTable + gf_isom_check_position(s, (GF_Box *)ptr->sampleTable, &pos); return GF_OK; } @@ -3597,40 +3543,40 @@ GF_Err minf_Size(GF_Box *s) #ifndef GPAC_DISABLE_ISOM_FRAGMENTS -void moof_del(GF_Box *s) +void moof_box_del(GF_Box *s) { GF_MovieFragmentBox *ptr = (GF_MovieFragmentBox *)s; if (ptr == NULL) return; - if (ptr->mfhd) gf_isom_box_del((GF_Box *) ptr->mfhd); - gf_isom_box_array_del(ptr->TrackList); + gf_list_del(ptr->TrackList); + if (ptr->PSSHs) gf_list_del(ptr->PSSHs); if (ptr->mdat) gf_free(ptr->mdat); gf_free(ptr); } -GF_Err moof_AddBox(GF_Box *s, GF_Box *a) +GF_Err moof_on_child_box(GF_Box *s, GF_Box *a) { GF_MovieFragmentBox *ptr = (GF_MovieFragmentBox *)s; switch (a->type) { case GF_ISOM_BOX_TYPE_MFHD: if (ptr->mfhd) ERROR_ON_DUPLICATED_BOX(a, ptr) - - ptr->mfhd = (GF_MovieFragmentHeaderBox *) a; + ptr->mfhd = (GF_MovieFragmentHeaderBox *) a; return GF_OK; case GF_ISOM_BOX_TYPE_TRAF: return gf_list_add(ptr->TrackList, a); case GF_ISOM_BOX_TYPE_PSSH: - default: - return gf_isom_box_add_default(s, a); + if (!ptr->PSSHs) ptr->PSSHs = gf_list_new(); + return gf_list_add(ptr->PSSHs, a); } + return GF_OK; } -GF_Err moof_Read(GF_Box *s, GF_BitStream *bs) +GF_Err moof_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, moof_AddBox); + return gf_isom_box_array_read(s, bs, moof_on_child_box); } -GF_Box *moof_New() +GF_Box *moof_box_new() { ISOM_DECL_BOX_ALLOC(GF_MovieFragmentBox, GF_ISOM_BOX_TYPE_MOOF); tmp->TrackList = gf_list_new(); @@ -3640,125 +3586,90 @@ GF_Box *moof_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err moof_Write(GF_Box *s, GF_BitStream *bs) +GF_Err moof_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_MovieFragmentBox *ptr = (GF_MovieFragmentBox *) s; - if (!s) return GF_BAD_PARAM; - - e = gf_isom_box_write_header(s, bs); - if (e) return e; - //Header First - if (ptr->mfhd) { - e = gf_isom_box_write((GF_Box *) ptr->mfhd, bs); - if (e) return e; - } -//then the track list -return gf_isom_box_array_write(s, ptr->TrackList, bs); + return gf_isom_box_write_header(s, bs); } -GF_Err moof_Size(GF_Box *s) +GF_Err moof_box_size(GF_Box *s) { - GF_Err e; - GF_MovieFragmentBox *ptr = (GF_MovieFragmentBox *)s; - - if (ptr->mfhd) { - e = gf_isom_box_size((GF_Box *)ptr->mfhd); - if (e) return e; - ptr->size += ptr->mfhd->size; - } - return gf_isom_box_array_size(s, ptr->TrackList); + u32 pos=0; + GF_MovieFragmentBox *ptr = (GF_MovieFragmentBox *) s; + if (!s) return GF_BAD_PARAM; + //Header First + gf_isom_check_position(s, (GF_Box *)ptr->mfhd, &pos); + //then PSSH + gf_isom_check_position_list(s, ptr->PSSHs, &pos); + //then the track list + gf_isom_check_position_list(s, ptr->TrackList, &pos); + return GF_OK; } - #endif /*GPAC_DISABLE_ISOM_WRITE*/ #endif /*GPAC_DISABLE_ISOM_FRAGMENTS*/ -void moov_del(GF_Box *s) +void moov_box_del(GF_Box *s) { GF_MovieBox *ptr = (GF_MovieBox *)s; if (ptr == NULL) return; - - if (ptr->mvhd) gf_isom_box_del((GF_Box *)ptr->mvhd); - if (ptr->meta) gf_isom_box_del((GF_Box *)ptr->meta); - if (ptr->iods) gf_isom_box_del((GF_Box *)ptr->iods); - if (ptr->udta) gf_isom_box_del((GF_Box *)ptr->udta); -#ifndef GPAC_DISABLE_ISOM_FRAGMENTS - if (ptr->mvex) gf_isom_box_del((GF_Box *)ptr->mvex); -#endif - - gf_isom_box_array_del(ptr->trackList); + gf_list_del(ptr->trackList); gf_free(ptr); } - -GF_Err moov_AddBox(GF_Box *s, GF_Box *a) +GF_Err moov_on_child_box(GF_Box *s, GF_Box *a) { GF_MovieBox *ptr = (GF_MovieBox *)s; switch (a->type) { case GF_ISOM_BOX_TYPE_IODS: if (ptr->iods) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->iods = (GF_ObjectDescriptorBox *)a; + ptr->iods = (GF_ObjectDescriptorBox *)a; //if no IOD, delete the box if (!ptr->iods->descriptor) { ptr->iods = NULL; - gf_isom_box_del(a); + gf_isom_box_del_parent(&s->child_boxes, a); } return GF_OK; case GF_ISOM_BOX_TYPE_MVHD: if (ptr->mvhd) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->mvhd = (GF_MovieHeaderBox *)a; + ptr->mvhd = (GF_MovieHeaderBox *)a; return GF_OK; case GF_ISOM_BOX_TYPE_UDTA: if (ptr->udta) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->udta = (GF_UserDataBox *)a; + ptr->udta = (GF_UserDataBox *)a; return GF_OK; #ifndef GPAC_DISABLE_ISOM_FRAGMENTS case GF_ISOM_BOX_TYPE_MVEX: if (ptr->mvex) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->mvex = (GF_MovieExtendsBox *)a; + ptr->mvex = (GF_MovieExtendsBox *)a; ptr->mvex->mov = ptr->mov; return GF_OK; #endif case GF_ISOM_BOX_TYPE_META: if (ptr->meta) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->meta = (GF_MetaBox *)a; + ptr->meta = (GF_MetaBox *)a; return GF_OK; case GF_ISOM_BOX_TYPE_TRAK: //set our pointer to this obj ((GF_TrackBox *)a)->moov = ptr; return gf_list_add(ptr->trackList, a); - case GF_ISOM_BOX_TYPE_PSSH: - default: - return gf_isom_box_add_default(s, a); } + return GF_OK; } -GF_Err moov_Read(GF_Box *s, GF_BitStream *bs) +GF_Err moov_box_read(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - e = gf_isom_box_array_read(s, bs, moov_AddBox); - if (e) { - return e; - } - else { - if (!((GF_MovieBox *)s)->mvhd) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Missing MovieHeaderBox\n")); - return GF_ISOM_INVALID_FILE; - } - } - return e; + return gf_isom_box_array_read(s, bs, moov_on_child_box); } -GF_Box *moov_New() +GF_Box *moov_box_new() { ISOM_DECL_BOX_ALLOC(GF_MovieBox, GF_ISOM_BOX_TYPE_MOOV); tmp->trackList = gf_list_new(); @@ -3774,183 +3685,286 @@ GF_Box *moov_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err moov_Write(GF_Box *s, GF_BitStream *bs) +GF_Err moov_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_MovieBox *ptr = (GF_MovieBox *)s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_box_write_header(s, bs); - if (e) return e; - - if (ptr->mvhd) { - e = gf_isom_box_write((GF_Box *) ptr->mvhd, bs); - if (e) return e; - } - if (ptr->iods) { - e = gf_isom_box_write((GF_Box *) ptr->iods, bs); - if (e) return e; - } - if (ptr->meta) { - e = gf_isom_box_write((GF_Box *) ptr->meta, bs); - if (e) return e; - } -#ifndef GPAC_DISABLE_ISOM_FRAGMENTS - if (ptr->mvex) { - e = gf_isom_box_write((GF_Box *) ptr->mvex, bs); - if (e) return e; - } -#endif - - e = gf_isom_box_array_write(s, ptr->trackList, bs); - if (e) return e; - - if (ptr->udta) { - e = gf_isom_box_write((GF_Box *) ptr->udta, bs); - if (e) return e; - } - return GF_OK; + return gf_isom_box_write_header(s, bs); } -GF_Err moov_Size(GF_Box *s) +GF_Err moov_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_MovieBox *ptr = (GF_MovieBox *)s; - if (ptr->mvhd) { - e = gf_isom_box_size((GF_Box *) ptr->mvhd); - if (e) return e; - ptr->size += ptr->mvhd->size; - } - if (ptr->iods) { - e = gf_isom_box_size((GF_Box *) ptr->iods); - if (e) return e; - ptr->size += ptr->iods->size; - } - if (ptr->udta) { - e = gf_isom_box_size((GF_Box *) ptr->udta); - if (e) return e; - ptr->size += ptr->udta->size; - } - if (ptr->meta) { - e = gf_isom_box_size((GF_Box *) ptr->meta); - if (e) return e; - ptr->size += ptr->meta->size; - } + gf_isom_check_position(s, (GF_Box *) ptr->mvhd, &pos); + gf_isom_check_position(s, (GF_Box *) ptr->iods, &pos); + gf_isom_check_position(s, (GF_Box *) ptr->meta, &pos); #ifndef GPAC_DISABLE_ISOM_FRAGMENTS - if (ptr->mvex) { - e = gf_isom_box_size((GF_Box *) ptr->mvex); - if (e) return e; - ptr->size += ptr->mvex->size; + if (ptr->mvex && !ptr->mvex_after_traks) { + gf_isom_check_position(s, (GF_Box *) ptr->mvex, &pos); } #endif + gf_isom_check_position_list(s, ptr->trackList, &pos); - return gf_isom_box_array_size(s, ptr->trackList); +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + if (ptr->mvex && ptr->mvex_after_traks) { + gf_isom_check_position(s, (GF_Box *) ptr->mvex, &pos); + } +#endif + return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void audio_sample_entry_del(GF_Box *s) +void audio_sample_entry_box_del(GF_Box *s) { GF_MPEGAudioSampleEntryBox *ptr = (GF_MPEGAudioSampleEntryBox *)s; if (ptr == NULL) return; gf_isom_sample_entry_predestroy((GF_SampleEntryBox *)s); - - if (ptr->esd) gf_isom_box_del((GF_Box *)ptr->esd); if (ptr->slc) gf_odf_desc_del((GF_Descriptor *)ptr->slc); - if (ptr->cfg_ac3) gf_isom_box_del((GF_Box *)ptr->cfg_ac3); - if (ptr->cfg_3gpp) gf_isom_box_del((GF_Box *)ptr->cfg_3gpp); gf_free(ptr); } -GF_Err audio_sample_entry_AddBox(GF_Box *s, GF_Box *a) +GF_Err audio_sample_entry_on_child_box(GF_Box *s, GF_Box *a) { + GF_UnknownBox *wave = NULL; + Bool drop_wave=GF_FALSE; GF_MPEGAudioSampleEntryBox *ptr = (GF_MPEGAudioSampleEntryBox *)s; + switch (a->type) { case GF_ISOM_BOX_TYPE_ESDS: if (ptr->esd) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->esd = (GF_ESDBox *)a; - break; - case GF_ISOM_BOX_TYPE_SINF: - gf_list_add(ptr->protections, a); + ptr->esd = (GF_ESDBox *)a; + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; break; + case GF_ISOM_BOX_TYPE_DAMR: case GF_ISOM_BOX_TYPE_DEVC: case GF_ISOM_BOX_TYPE_DQCP: case GF_ISOM_BOX_TYPE_DSMV: + if (ptr->cfg_3gpp) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->cfg_3gpp = (GF_3GPPConfigBox *) a; /*for 3GP config, remember sample entry type in config*/ ptr->cfg_3gpp->cfg.type = ptr->type; + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; break; + case GF_ISOM_BOX_TYPE_DOPS: + if (ptr->cfg_opus) ERROR_ON_DUPLICATED_BOX(a, ptr) + ptr->cfg_opus = (GF_OpusSpecificBox *)a; + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; + break; case GF_ISOM_BOX_TYPE_DAC3: + if (ptr->cfg_ac3) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->cfg_ac3 = (GF_AC3ConfigBox *) a; + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; break; case GF_ISOM_BOX_TYPE_DEC3: + if (ptr->cfg_ac3) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->cfg_ac3 = (GF_AC3ConfigBox *) a; break; + case GF_ISOM_BOX_TYPE_MHAC: + if (ptr->cfg_mha) ERROR_ON_DUPLICATED_BOX(a, ptr) + ptr->cfg_mha = (GF_MHAConfigBox *) a; + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; + break; + case GF_ISOM_BOX_TYPE_DFLA: + if (ptr->cfg_flac) ERROR_ON_DUPLICATED_BOX(a, ptr) + ptr->cfg_flac = (GF_FLACConfigBox *) a; + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; + break; case GF_ISOM_BOX_TYPE_UNKNOWN: - if (ptr->esd) ERROR_ON_DUPLICATED_BOX(a, ptr) - /*HACK for QT files: get the esds box from the track*/ - { - GF_UnknownBox *wave = (GF_UnknownBox *)a; - if (wave->original_4cc == GF_4CC('w','a','v','e')) { - u32 offset = 0; - while ((wave->data[offset + 4] != 'e') && (wave->data[offset + 5] != 's')) { - offset++; - if (offset == wave->dataSize) break; + wave = (GF_UnknownBox *)a; + /*HACK for QT files: get the esds box from the track*/ + if (s->type == GF_ISOM_BOX_TYPE_MP4A) { + if (ptr->esd) ERROR_ON_DUPLICATED_BOX(a, ptr) + + //wave subboxes may have been properly parsed + if ((wave->original_4cc == GF_QT_BOX_TYPE_WAVE) && gf_list_count(wave->child_boxes)) { + u32 i; + for (i =0; ichild_boxes); i++) { + GF_Box *inner_box = (GF_Box *)gf_list_get(wave->child_boxes, i); + if (inner_box->type == GF_ISOM_BOX_TYPE_ESDS) { + ptr->esd = (GF_ESDBox *)inner_box; + if (ptr->qtff_mode & GF_ISOM_AUDIO_QTFF_CONVERT_FLAG) { + gf_list_rem(a->child_boxes, i); + drop_wave=GF_TRUE; + ptr->compression_id = 0; + gf_list_add(ptr->child_boxes, inner_box); + } + } + } + if (drop_wave) { + gf_isom_box_del_parent(&ptr->child_boxes, a); + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; + ptr->version = 0; + return GF_OK; } - if (offset < wave->dataSize) { - GF_Box *a; - GF_Err e; - GF_BitStream *bs = gf_bs_new(wave->data + offset, wave->dataSize - offset, GF_BITSTREAM_READ); - e = gf_isom_box_parse(&a, bs); - gf_bs_del(bs); - if (e) return e; - ptr->esd = (GF_ESDBox *)a; - gf_isom_box_add_for_dump_mode((GF_Box *)ptr, a); + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_ON_EXT_VALID; + return GF_OK; + } + gf_isom_box_del_parent(&ptr->child_boxes, a); + return GF_ISOM_INVALID_MEDIA; + + } + ptr->qtff_mode &= ~GF_ISOM_AUDIO_QTFF_CONVERT_FLAG; + if ((wave->original_4cc == GF_QT_BOX_TYPE_WAVE) && gf_list_count(wave->child_boxes)) { + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_ON_NOEXT; + } + return GF_OK; + case GF_QT_BOX_TYPE_WAVE: + { + u32 subtype = 0; + GF_Box **cfg_ptr = NULL; + if (s->type == GF_ISOM_BOX_TYPE_MP4A) { + cfg_ptr = (GF_Box **) &ptr->esd; + subtype = GF_ISOM_BOX_TYPE_ESDS; + } + else if (s->type == GF_ISOM_BOX_TYPE_AC3) { + cfg_ptr = (GF_Box **) &ptr->cfg_ac3; + subtype = GF_ISOM_BOX_TYPE_DAC3; + } + else if (s->type == GF_ISOM_BOX_TYPE_EC3) { + cfg_ptr = (GF_Box **) &ptr->cfg_ac3; + subtype = GF_ISOM_BOX_TYPE_DEC3; + } + else if (s->type == GF_ISOM_BOX_TYPE_OPUS) { + cfg_ptr = (GF_Box **) &ptr->cfg_opus; + subtype = GF_ISOM_BOX_TYPE_DOPS; + } + else if ((s->type == GF_ISOM_BOX_TYPE_MHA1) + || (s->type == GF_ISOM_BOX_TYPE_MHA2) + || (s->type == GF_ISOM_BOX_TYPE_MHM1) + || (s->type == GF_ISOM_BOX_TYPE_MHM2) + ) { + cfg_ptr = (GF_Box **) &ptr->cfg_mha; + subtype = GF_ISOM_BOX_TYPE_MHAC; + } + + if (cfg_ptr) { + if (*cfg_ptr) ERROR_ON_DUPLICATED_BOX(a, ptr) + + //wave subboxes may have been properly parsed + if (gf_list_count(a->child_boxes)) { + u32 i; + for (i =0; ichild_boxes); i++) { + GF_Box *inner_box = (GF_Box *)gf_list_get(a->child_boxes, i); + if (inner_box->type == subtype) { + *cfg_ptr = inner_box; + if (ptr->qtff_mode & GF_ISOM_AUDIO_QTFF_CONVERT_FLAG) { + gf_list_rem(a->child_boxes, i); + drop_wave=GF_TRUE; + gf_list_add(ptr->child_boxes, inner_box); + } + break; + } + } + if (drop_wave) { + gf_isom_box_del_parent(&ptr->child_boxes, a); + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; + ptr->compression_id = 0; + ptr->version = 0; + return GF_OK; } - } - else { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Cannot process box %s\n!", gf_4cc_to_str(a->type))); - } - gf_isom_box_del(a); - return GF_ISOM_INVALID_MEDIA; + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_ON_EXT_VALID; + return GF_OK; + } } - break; - default: - return gf_isom_box_add_default(s, a); + } + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_ON_EXT_VALID; + return GF_OK; } return GF_OK; } -GF_Err audio_sample_entry_Read(GF_Box *s, GF_BitStream *bs) +GF_Err audio_sample_entry_box_read(GF_Box *s, GF_BitStream *bs) { GF_MPEGAudioSampleEntryBox *ptr; char *data; - u32 i, size; + u8 a, b, c, d; + u32 i, size, v, nb_alnum; GF_Err e; - u64 pos; + u64 pos, start; + + ptr = (GF_MPEGAudioSampleEntryBox *)s; + start = gf_bs_get_position(bs); + gf_bs_seek(bs, start + 8); + v = gf_bs_read_u16(bs); + if (v) + ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_ON_NOEXT; + + //try to disambiguate QTFF v1 and MP4 v1 audio sample entries ... + if (v==1) { + //go to end of ISOM audio sample entry, skip 4 byte (box size field), read 4 bytes (box type) and check if this looks like a box + gf_bs_seek(bs, start + 8 + 20 + 4); + a = gf_bs_read_u8(bs); + b = gf_bs_read_u8(bs); + c = gf_bs_read_u8(bs); + d = gf_bs_read_u8(bs); + nb_alnum = 0; + if (isalnum(a)) nb_alnum++; + if (isalnum(b)) nb_alnum++; + if (isalnum(c)) nb_alnum++; + if (isalnum(d)) nb_alnum++; + if (nb_alnum>2) ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE; + } + + gf_bs_seek(bs, start); e = gf_isom_audio_sample_entry_read((GF_AudioSampleEntryBox*)s, bs); if (e) return e; pos = gf_bs_get_position(bs); size = (u32) s->size; - e = gf_isom_box_array_read(s, bs, audio_sample_entry_AddBox); - if (!e) return GF_OK; + //when cookie is set on bs, always convert qtff-style mp4a to isobmff-style + //since the conversion is done in addBox and we don't have the bitstream there (arg...), flag the box + if (gf_bs_get_cookie(bs) & GF_ISOM_BS_COOKIE_QT_CONV) { + ptr->qtff_mode |= GF_ISOM_AUDIO_QTFF_CONVERT_FLAG; + } + + e = gf_isom_box_array_read(s, bs, audio_sample_entry_on_child_box); + if (!e) { + if (s->type==GF_ISOM_BOX_TYPE_ENCA) { + GF_ProtectionSchemeInfoBox *sinf = (GF_ProtectionSchemeInfoBox *) gf_isom_box_find_child(s->child_boxes, GF_ISOM_BOX_TYPE_SINF); + + if (sinf && sinf->original_format) { + u32 type = sinf->original_format->data_format; + switch (type) { + case GF_ISOM_SUBTYPE_3GP_AMR: + case GF_ISOM_SUBTYPE_3GP_AMR_WB: + case GF_ISOM_SUBTYPE_3GP_EVRC: + case GF_ISOM_SUBTYPE_3GP_QCELP: + case GF_ISOM_SUBTYPE_3GP_SMV: + if (ptr->cfg_3gpp) ptr->cfg_3gpp->cfg.type = type; + break; + } + } + } + return GF_OK; + } if (size<8) return GF_ISOM_INVALID_FILE; + /*hack for some weird files (possibly recorded with live.com tools, needs further investigations)*/ - ptr = (GF_MPEGAudioSampleEntryBox *)s; gf_bs_seek(bs, pos); data = (char*)gf_malloc(sizeof(char) * size); + if (!data) return GF_OUT_OF_MEM; + gf_bs_read_data(bs, data, size); for (i=0; iesd) gf_isom_box_del_parent(&ptr->child_boxes, (GF_Box *)ptr->esd); + ptr->esd = NULL; e = gf_isom_box_parse((GF_Box **)&ptr->esd, mybs); gf_bs_del(mybs); + if (e==GF_OK) { + if (!ptr->child_boxes) ptr->child_boxes = gf_list_new(); + gf_list_add(ptr->child_boxes, ptr->esd); + } else if (ptr->esd) { + gf_isom_box_del((GF_Box *)ptr->esd); + ptr->esd = NULL; + } break; } } @@ -3958,14 +3972,14 @@ GF_Err audio_sample_entry_Read(GF_Box *s, GF_BitStream *bs) return e; } -GF_Box *audio_sample_entry_New() +GF_Box *audio_sample_entry_box_new() { ISOM_DECL_BOX_ALLOC(GF_MPEGAudioSampleEntryBox, GF_ISOM_BOX_TYPE_MP4A); gf_isom_audio_sample_entry_init((GF_AudioSampleEntryBox*)tmp); return (GF_Box *)tmp; } -GF_Box *enca_New() +GF_Box *enca_box_new() { ISOM_DECL_BOX_ALLOC(GF_MPEGAudioSampleEntryBox, GF_ISOM_BOX_TYPE_ENCA); gf_isom_audio_sample_entry_init((GF_AudioSampleEntryBox*)tmp); @@ -3975,84 +3989,104 @@ GF_Box *enca_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err audio_sample_entry_Write(GF_Box *s, GF_BitStream *bs) +GF_Err audio_sample_entry_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; - GF_MPEGAudioSampleEntryBox *ptr = (GF_MPEGAudioSampleEntryBox *)s; e = gf_isom_box_write_header(s, bs); if (e) return e; gf_isom_audio_sample_entry_write((GF_AudioSampleEntryBox*)s, bs); - if (ptr->esd) { - e = gf_isom_box_write((GF_Box *)ptr->esd, bs); - if (e) return e; - } - if (ptr->cfg_3gpp) { - e = gf_isom_box_write((GF_Box *)ptr->cfg_3gpp, bs); - if (e) return e; - } - if (ptr->cfg_ac3) { - e = gf_isom_box_write((GF_Box *)ptr->cfg_ac3, bs); - if (e) return e; - } - return gf_isom_box_array_write(s, ptr->protections, bs); + return GF_OK; } -GF_Err audio_sample_entry_Size(GF_Box *s) +GF_Err audio_sample_entry_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_MPEGAudioSampleEntryBox *ptr = (GF_MPEGAudioSampleEntryBox *)s; - gf_isom_audio_sample_entry_size((GF_AudioSampleEntryBox*)s); - if (ptr->esd) { - e = gf_isom_box_size((GF_Box *)ptr->esd); - if (e) return e; - ptr->size += ptr->esd->size; - } - if (ptr->cfg_3gpp) { - e = gf_isom_box_size((GF_Box *)ptr->cfg_3gpp); - if (e) return e; - ptr->size += ptr->cfg_3gpp->size; - } - if (ptr->cfg_ac3) { - e = gf_isom_box_size((GF_Box *)ptr->cfg_ac3); - if (e) return e; - ptr->size += ptr->cfg_ac3->size; - } - return gf_isom_box_array_size(s, ptr->protections); + if (ptr->qtff_mode) + return GF_OK; + + gf_isom_check_position(s, (GF_Box *)ptr->esd, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->cfg_3gpp, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->cfg_opus, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->cfg_ac3, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->cfg_flac, &pos); + return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void mp4s_del(GF_Box *s) +void gen_sample_entry_box_del(GF_Box *s) { - GF_MPEGSampleEntryBox *ptr = (GF_MPEGSampleEntryBox *)s; + GF_SampleEntryBox *ptr = (GF_SampleEntryBox *)s; if (ptr == NULL) return; gf_isom_sample_entry_predestroy((GF_SampleEntryBox *)s); + gf_free(ptr); +} + + +GF_Err gen_sample_entry_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e = gf_isom_base_sample_entry_read((GF_SampleEntryBox *)s, bs); + if (e) return e; + ISOM_DECREASE_SIZE(s, 8); + return gf_isom_box_array_read(s, bs, NULL); +} + +GF_Box *gen_sample_entry_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_SampleEntryBox, GF_QT_SUBTYPE_C608);//type will be overriten + gf_isom_sample_entry_init((GF_SampleEntryBox*)tmp); + return (GF_Box *)tmp; +} + + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err gen_sample_entry_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_SampleEntryBox *ptr = (GF_SampleEntryBox *)s; + e = gf_isom_box_write_header(s, bs); + if (e) return e; + + gf_bs_write_data(bs, ptr->reserved, 6); + gf_bs_write_u16(bs, ptr->dataReferenceIndex); + return GF_OK; +} + +GF_Err gen_sample_entry_box_size(GF_Box *s) +{ + GF_SampleEntryBox *ptr = (GF_SampleEntryBox *)s; + ptr->size += 8; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ - if (ptr->esd) gf_isom_box_del((GF_Box *)ptr->esd); +void mp4s_box_del(GF_Box *s) +{ + GF_MPEGSampleEntryBox *ptr = (GF_MPEGSampleEntryBox *)s; + if (ptr == NULL) return; + gf_isom_sample_entry_predestroy((GF_SampleEntryBox *)s); if (ptr->slc) gf_odf_desc_del((GF_Descriptor *)ptr->slc); gf_free(ptr); } -GF_Err mp4s_AddBox(GF_Box *s, GF_Box *a) +GF_Err mp4s_on_child_box(GF_Box *s, GF_Box *a) { GF_MPEGSampleEntryBox *ptr = (GF_MPEGSampleEntryBox *)s; switch (a->type) { case GF_ISOM_BOX_TYPE_ESDS: if (ptr->esd) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->esd = (GF_ESDBox *)a; - break; - case GF_ISOM_BOX_TYPE_SINF: - gf_list_add(ptr->protections, a); + ptr->esd = (GF_ESDBox *)a; break; - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err mp4s_Read(GF_Box *s, GF_BitStream *bs) +GF_Err mp4s_box_read(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_MPEGSampleEntryBox *ptr = (GF_MPEGSampleEntryBox *)s; @@ -4061,27 +4095,29 @@ GF_Err mp4s_Read(GF_Box *s, GF_BitStream *bs) if (e) return e; ISOM_DECREASE_SIZE(ptr, 8); - return gf_isom_box_array_read(s, bs, mp4s_AddBox); + return gf_isom_box_array_read(s, bs, mp4s_on_child_box); } -GF_Box *mp4s_New() +GF_Box *mp4s_box_new() { ISOM_DECL_BOX_ALLOC(GF_MPEGSampleEntryBox, GF_ISOM_BOX_TYPE_MP4S); gf_isom_sample_entry_init((GF_SampleEntryBox*)tmp); + tmp->internal_type = GF_ISOM_SAMPLE_ENTRY_MP4S; return (GF_Box *)tmp; } -GF_Box *encs_New() +GF_Box *encs_box_new() { ISOM_DECL_BOX_ALLOC(GF_MPEGSampleEntryBox, GF_ISOM_BOX_TYPE_ENCS); gf_isom_sample_entry_init((GF_SampleEntryBox*)tmp); + tmp->internal_type = GF_ISOM_SAMPLE_ENTRY_MP4S; return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err mp4s_Write(GF_Box *s, GF_BitStream *bs) +GF_Err mp4s_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_MPEGSampleEntryBox *ptr = (GF_MPEGSampleEntryBox *)s; @@ -4090,65 +4126,39 @@ GF_Err mp4s_Write(GF_Box *s, GF_BitStream *bs) if (e) return e; gf_bs_write_data(bs, ptr->reserved, 6); gf_bs_write_u16(bs, ptr->dataReferenceIndex); - e = gf_isom_box_write((GF_Box *)ptr->esd, bs); - if (e) return e; - return gf_isom_box_array_write(s, ptr->protections, bs); + return GF_OK; } -GF_Err mp4s_Size(GF_Box *s) +GF_Err mp4s_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_MPEGSampleEntryBox *ptr = (GF_MPEGSampleEntryBox *)s; - - ptr->size += 8; - e = gf_isom_box_size((GF_Box *)ptr->esd); - if (e) return e; - ptr->size += ptr->esd->size; - return gf_isom_box_array_size(s, ptr->protections); + s->size += 8; + gf_isom_check_position(s, (GF_Box *)ptr->esd, &pos); + return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void video_sample_entry_del(GF_Box *s) +void video_sample_entry_box_del(GF_Box *s) { GF_MPEGVisualSampleEntryBox *ptr = (GF_MPEGVisualSampleEntryBox *)s; if (ptr == NULL) return; gf_isom_sample_entry_predestroy((GF_SampleEntryBox *)s); - if (ptr->esd) gf_isom_box_del((GF_Box *)ptr->esd); if (ptr->slc) gf_odf_desc_del((GF_Descriptor *)ptr->slc); /*for publishing*/ if (ptr->emul_esd) gf_odf_desc_del((GF_Descriptor *)ptr->emul_esd); - - if (ptr->avc_config) gf_isom_box_del((GF_Box *) ptr->avc_config); - if (ptr->svc_config) gf_isom_box_del((GF_Box *) ptr->svc_config); - if (ptr->mvc_config) gf_isom_box_del((GF_Box *) ptr->mvc_config); - if (ptr->hevc_config) gf_isom_box_del((GF_Box *) ptr->hevc_config); - if (ptr->lhvc_config) gf_isom_box_del((GF_Box *) ptr->lhvc_config); - if (ptr->cfg_3gpp) gf_isom_box_del((GF_Box *)ptr->cfg_3gpp); - - if (ptr->descr) gf_isom_box_del((GF_Box *) ptr->descr); - if (ptr->ipod_ext) gf_isom_box_del((GF_Box *)ptr->ipod_ext); - - if (ptr->pasp) gf_isom_box_del((GF_Box *)ptr->pasp); - if (ptr->clap) gf_isom_box_del((GF_Box *)ptr->clap); - if (ptr->rinf) gf_isom_box_del((GF_Box *)ptr->rinf); - - if (ptr->rvcc) gf_isom_box_del((GF_Box *)ptr->rvcc); - gf_free(ptr); } -GF_Err video_sample_entry_AddBox(GF_Box *s, GF_Box *a) +GF_Err video_sample_entry_on_child_box(GF_Box *s, GF_Box *a) { GF_MPEGVisualSampleEntryBox *ptr = (GF_MPEGVisualSampleEntryBox *)s; switch (a->type) { case GF_ISOM_BOX_TYPE_ESDS: if (ptr->esd) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->esd = (GF_ESDBox *)a; - break; - case GF_ISOM_BOX_TYPE_SINF: - gf_list_add(ptr->protections, a); + ptr->esd = (GF_ESDBox *)a; break; case GF_ISOM_BOX_TYPE_RINF: if (ptr->rinf) ERROR_ON_DUPLICATED_BOX(a, ptr) @@ -4156,78 +4166,110 @@ GF_Err video_sample_entry_AddBox(GF_Box *s, GF_Box *a) break; case GF_ISOM_BOX_TYPE_AVCC: if (ptr->avc_config) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->avc_config = (GF_AVCConfigurationBox *)a; + ptr->avc_config = (GF_AVCConfigurationBox *)a; break; case GF_ISOM_BOX_TYPE_HVCC: if (ptr->hevc_config) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->hevc_config = (GF_HEVCConfigurationBox *)a; + ptr->hevc_config = (GF_HEVCConfigurationBox *)a; break; case GF_ISOM_BOX_TYPE_SVCC: if (ptr->svc_config) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->svc_config = (GF_AVCConfigurationBox *)a; + ptr->svc_config = (GF_AVCConfigurationBox *)a; break; case GF_ISOM_BOX_TYPE_MVCC: if (ptr->mvc_config) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->mvc_config = (GF_AVCConfigurationBox *)a; + ptr->mvc_config = (GF_AVCConfigurationBox *)a; break; case GF_ISOM_BOX_TYPE_LHVC: if (ptr->lhvc_config) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->lhvc_config = (GF_HEVCConfigurationBox *)a; + ptr->lhvc_config = (GF_HEVCConfigurationBox *)a; break; - case GF_ISOM_BOX_TYPE_M4DS: - if (ptr->descr) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->descr = (GF_MPEG4ExtensionDescriptorsBox *)a; + case GF_ISOM_BOX_TYPE_AV1C: + if (ptr->av1_config) ERROR_ON_DUPLICATED_BOX(a, ptr) + ptr->av1_config = (GF_AV1ConfigurationBox *)a; + break; + case GF_ISOM_BOX_TYPE_VPCC: + if (ptr->vp_config) ERROR_ON_DUPLICATED_BOX(a, ptr) + ptr->vp_config = (GF_VPConfigurationBox *)a; + break; + case GF_ISOM_BOX_TYPE_DVCC: + if (ptr->dovi_config) ERROR_ON_DUPLICATED_BOX(a, ptr) + ptr->dovi_config = (GF_DOVIConfigurationBox*)a; break; case GF_ISOM_BOX_TYPE_UUID: if (! memcmp(((GF_UnknownUUIDBox*)a)->uuid, GF_ISOM_IPOD_EXT, 16)) { if (ptr->ipod_ext) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->ipod_ext = (GF_UnknownUUIDBox *)a; } else { - return gf_isom_box_add_default(s, a); + return GF_OK; } break; case GF_ISOM_BOX_TYPE_D263: + if (ptr->cfg_3gpp) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->cfg_3gpp = (GF_3GPPConfigBox *)a; /*for 3GP config, remember sample entry type in config*/ ptr->cfg_3gpp->cfg.type = ptr->type; break; - break; + + case GF_ISOM_BOX_TYPE_JP2H: + if (ptr->jp2h) ERROR_ON_DUPLICATED_BOX(a, ptr) + ptr->jp2h = (GF_J2KHeaderBox *)a; + return GF_OK; case GF_ISOM_BOX_TYPE_PASP: - if (ptr->pasp) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->pasp = (GF_PixelAspectRatioBox *)a; - break; case GF_ISOM_BOX_TYPE_CLAP: - if (ptr->clap) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->clap = (GF_CleanAppertureBox *)a; - break; + case GF_ISOM_BOX_TYPE_COLR: + case GF_ISOM_BOX_TYPE_MDCV: + case GF_ISOM_BOX_TYPE_CLLI: + case GF_ISOM_BOX_TYPE_CCST: + case GF_ISOM_BOX_TYPE_AUXI: case GF_ISOM_BOX_TYPE_RVCC: - if (ptr->rvcc) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->rvcc = (GF_RVCConfigurationBox *)a; - break; - default: - return gf_isom_box_add_default(s, a); + case GF_ISOM_BOX_TYPE_M4DS: + if (!gf_isom_box_check_unique(s->child_boxes, a)) { + ERROR_ON_DUPLICATED_BOX(a, ptr) + } + return GF_OK; } return GF_OK; } -GF_Err video_sample_entry_Read(GF_Box *s, GF_BitStream *bs) +GF_Err video_sample_entry_box_read(GF_Box *s, GF_BitStream *bs) { GF_MPEGVisualSampleEntryBox *mp4v = (GF_MPEGVisualSampleEntryBox*)s; GF_Err e; e = gf_isom_video_sample_entry_read((GF_VisualSampleEntryBox *)s, bs); if (e) return e; - e = gf_isom_box_array_read(s, bs, video_sample_entry_AddBox); + e = gf_isom_box_array_read(s, bs, video_sample_entry_on_child_box); if (e) return e; /*this is an AVC sample desc*/ - if (mp4v->avc_config || mp4v->svc_config || mp4v->mvc_config) AVC_RewriteESDescriptor(mp4v); + if (mp4v->avc_config || mp4v->svc_config || mp4v->mvc_config) + AVC_RewriteESDescriptor(mp4v); /*this is an HEVC sample desc*/ if (mp4v->hevc_config || mp4v->lhvc_config || (mp4v->type==GF_ISOM_BOX_TYPE_HVT1)) HEVC_RewriteESDescriptor(mp4v); + /*this is an AV1 sample desc*/ + if (mp4v->av1_config) + AV1_RewriteESDescriptor(mp4v); + /*this is a VP8-9 sample desc*/ + if (mp4v->vp_config) + VP9_RewriteESDescriptor(mp4v); + + if (s->type==GF_ISOM_BOX_TYPE_ENCV) { + GF_ProtectionSchemeInfoBox *sinf = (GF_ProtectionSchemeInfoBox *) gf_isom_box_find_child(s->child_boxes, GF_ISOM_BOX_TYPE_SINF); + + if (sinf && sinf->original_format) { + u32 type = sinf->original_format->data_format; + switch (type) { + case GF_ISOM_SUBTYPE_3GP_H263: + if (mp4v->cfg_3gpp) mp4v->cfg_3gpp->cfg.type = type; + break; + } + } + } return GF_OK; } -GF_Box *video_sample_entry_New() +GF_Box *video_sample_entry_box_new() { GF_MPEGVisualSampleEntryBox *tmp; GF_SAFEALLOC(tmp, GF_MPEGVisualSampleEntryBox); @@ -4240,156 +4282,51 @@ GF_Box *video_sample_entry_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err video_sample_entry_Write(GF_Box *s, GF_BitStream *bs) +GF_Err video_sample_entry_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; - GF_MPEGVisualSampleEntryBox *ptr = (GF_MPEGVisualSampleEntryBox *)s; e = gf_isom_box_write_header(s, bs); if (e) return e; gf_isom_video_sample_entry_write((GF_VisualSampleEntryBox *)s, bs); - - /*mp4v*/ - if (ptr->esd) { - e = gf_isom_box_write((GF_Box *)ptr->esd, bs); - if (e) return e; - } - /*mp4v*/ - else if (ptr->cfg_3gpp) { - e = gf_isom_box_write((GF_Box *)ptr->cfg_3gpp, bs); - if (e) return e; - } - /*avc or hevc*/ - else { - if (ptr->avc_config && ptr->avc_config->config) { - e = gf_isom_box_write((GF_Box *) ptr->avc_config, bs); - if (e) return e; - } - if (ptr->hevc_config && ptr->hevc_config->config) { - e = gf_isom_box_write((GF_Box *) ptr->hevc_config, bs); - if (e) return e; - } - if (ptr->ipod_ext) { - e = gf_isom_box_write((GF_Box *) ptr->ipod_ext, bs); - if (e) return e; - } - if (ptr->descr) { - e = gf_isom_box_write((GF_Box *) ptr->descr, bs); - if (e) return e; - } - if (ptr->svc_config && ptr->svc_config->config) { - e = gf_isom_box_write((GF_Box *) ptr->svc_config, bs); - if (e) return e; - } - if (ptr->mvc_config && ptr->mvc_config->config) { - e = gf_isom_box_write((GF_Box *) ptr->mvc_config, bs); - if (e) return e; - } - if (ptr->lhvc_config && ptr->lhvc_config->config) { - e = gf_isom_box_write((GF_Box *) ptr->lhvc_config, bs); - if (e) return e; - } - } - if (ptr->pasp) { - e = gf_isom_box_write((GF_Box *)ptr->pasp, bs); - if (e) return e; - } - if (ptr->clap) { - e = gf_isom_box_write((GF_Box *)ptr->clap, bs); - if (e) return e; - } - if (ptr->rvcc) { - e = gf_isom_box_write((GF_Box *)ptr->rvcc, bs); - if (e) return e; - } - if (ptr->rinf) { - e = gf_isom_box_write((GF_Box *)ptr->rinf, bs); - if (e) return e; - } - return gf_isom_box_array_write(s, ptr->protections, bs); + return GF_OK; } -GF_Err video_sample_entry_Size(GF_Box *s) +GF_Err video_sample_entry_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_MPEGVisualSampleEntryBox *ptr = (GF_MPEGVisualSampleEntryBox *)s; - gf_isom_video_sample_entry_size((GF_VisualSampleEntryBox *)s); - if (ptr->esd) { - e = gf_isom_box_size((GF_Box *)ptr->esd); - if (e) return e; - ptr->size += ptr->esd->size; - } else if (ptr->cfg_3gpp) { - e = gf_isom_box_size((GF_Box *)ptr->cfg_3gpp); - if (e) return e; - ptr->size += ptr->cfg_3gpp->size; - } else { - if (!ptr->avc_config && !ptr->svc_config && !ptr->hevc_config && !ptr->lhvc_config && (ptr->type!=GF_ISOM_BOX_TYPE_HVT1) ) { - return GF_ISOM_INVALID_FILE; - } + /*make sure we write the config box first, we don't care about the rest*/ - if (ptr->hevc_config && ptr->hevc_config->config) { - e = gf_isom_box_size((GF_Box *)ptr->hevc_config); - if (e) return e; - ptr->size += ptr->hevc_config->size; - } + /*mp4v*/ + gf_isom_check_position(s, (GF_Box *)ptr->esd, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->cfg_3gpp, &pos); + /*avc / SVC + MVC*/ + gf_isom_check_position(s, (GF_Box *)ptr->avc_config, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->svc_config, &pos); + if (ptr->mvc_config) { + gf_isom_check_position(s, gf_isom_box_find_child(s->child_boxes, GF_ISOM_BOX_TYPE_VWID), &pos); + gf_isom_check_position(s, (GF_Box *)ptr->mvc_config, &pos); + } - if (ptr->avc_config && ptr->avc_config->config) { - e = gf_isom_box_size((GF_Box *) ptr->avc_config); - if (e) return e; - ptr->size += ptr->avc_config->size; - } + /*HEVC*/ + gf_isom_check_position(s, (GF_Box *)ptr->hevc_config, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->lhvc_config, &pos); - if (ptr->svc_config && ptr->svc_config->config) { - e = gf_isom_box_size((GF_Box *) ptr->svc_config); - if (e) return e; - ptr->size += ptr->svc_config->size; - } + /*AV1*/ + gf_isom_check_position(s, (GF_Box *)ptr->av1_config, &pos); - if (ptr->mvc_config && ptr->mvc_config->config) { - e = gf_isom_box_size((GF_Box *) ptr->mvc_config); - if (e) return e; - ptr->size += ptr->mvc_config->size; - } + /*VPx*/ + gf_isom_check_position(s, (GF_Box *)ptr->vp_config, &pos); - if (ptr->lhvc_config && ptr->lhvc_config->config) { - e = gf_isom_box_size((GF_Box *) ptr->lhvc_config); - if (e) return e; - ptr->size += ptr->lhvc_config->size; - } + /*JP2H*/ + gf_isom_check_position(s, (GF_Box *)ptr->jp2h, &pos); - if (ptr->ipod_ext) { - e = gf_isom_box_size((GF_Box *) ptr->ipod_ext); - if (e) return e; - ptr->size += ptr->ipod_ext->size; - } - if (ptr->descr) { - e = gf_isom_box_size((GF_Box *) ptr->descr); - if (e) return e; - ptr->size += ptr->descr->size; - } - } - if (ptr->pasp) { - e = gf_isom_box_size((GF_Box *)ptr->pasp); - if (e) return e; - ptr->size += ptr->pasp->size; - } - if (ptr->clap) { - e = gf_isom_box_size((GF_Box *)ptr->clap); - if (e) return e; - ptr->size += ptr->clap->size; - } - if (ptr->rvcc) { - e = gf_isom_box_size((GF_Box *)ptr->rvcc); - if (e) return e; - ptr->size += ptr->rvcc->size; - } - if (ptr->rinf) { - e = gf_isom_box_size((GF_Box *)ptr->rinf); - if (e) return e; - ptr->size += ptr->rinf->size; - } - return gf_isom_box_array_size(s, ptr->protections); + /*DolbyVision*/ + gf_isom_check_position(s, (GF_Box *)ptr->dovi_config, &pos); + + return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ @@ -4398,18 +4335,17 @@ GF_Err video_sample_entry_Size(GF_Box *s) #ifndef GPAC_DISABLE_ISOM_FRAGMENTS -void mvex_del(GF_Box *s) +void mvex_box_del(GF_Box *s) { GF_MovieExtendsBox *ptr = (GF_MovieExtendsBox *)s; if (ptr == NULL) return; - if (ptr->mehd) gf_isom_box_del((GF_Box*)ptr->mehd); - gf_isom_box_array_del(ptr->TrackExList); - gf_isom_box_array_del(ptr->TrackExPropList); + gf_list_del(ptr->TrackExList); + gf_list_del(ptr->TrackExPropList); gf_free(ptr); } -GF_Err mvex_AddBox(GF_Box *s, GF_Box *a) +GF_Err mvex_on_child_box(GF_Box *s, GF_Box *a) { GF_MovieExtendsBox *ptr = (GF_MovieExtendsBox *)s; @@ -4419,23 +4355,21 @@ GF_Err mvex_AddBox(GF_Box *s, GF_Box *a) case GF_ISOM_BOX_TYPE_TREP: return gf_list_add(ptr->TrackExPropList, a); case GF_ISOM_BOX_TYPE_MEHD: - if (ptr->mehd) break; + if (ptr->mehd) ERROR_ON_DUPLICATED_BOX(a, s) ptr->mehd = (GF_MovieExtendsHeaderBox*)a; return GF_OK; - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err mvex_Read(GF_Box *s, GF_BitStream *bs) +GF_Err mvex_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, mvex_AddBox); + return gf_isom_box_array_read(s, bs, mvex_on_child_box); } -GF_Box *mvex_New() +GF_Box *mvex_box_new() { ISOM_DECL_BOX_ALLOC(GF_MovieExtendsBox, GF_ISOM_BOX_TYPE_MVEX); tmp->TrackExList = gf_list_new(); @@ -4457,63 +4391,49 @@ GF_Box *mvex_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err mvex_Write(GF_Box *s, GF_BitStream *bs) +GF_Err mvex_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_MovieExtendsBox *ptr = (GF_MovieExtendsBox *) s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_box_write_header(s, bs); - if (e) return e; - if (ptr->mehd) { - e = gf_isom_box_write((GF_Box *)ptr->mehd, bs); - if (e) return e; - } - e = gf_isom_box_array_write(s, ptr->TrackExList, bs); - if (e) return e; - return gf_isom_box_array_write(s, ptr->TrackExPropList, bs); + return gf_isom_box_write_header(s, bs); } -GF_Err mvex_Size(GF_Box *s) +GF_Err mvex_box_size(GF_Box *s) { - GF_Err e; - GF_MovieExtendsBox *ptr = (GF_MovieExtendsBox *)s; - - if (ptr->mehd) { - e = gf_isom_box_size((GF_Box *)ptr->mehd); - if (e) return e; - ptr->size += ptr->mehd->size; - } - e = gf_isom_box_array_size(s, ptr->TrackExList); - if (e) return e; - return gf_isom_box_array_size(s, ptr->TrackExPropList); + u32 pos=0; + GF_MovieExtendsBox *ptr = (GF_MovieExtendsBox *) s; + gf_isom_check_position(s, (GF_Box *)ptr->mehd, &pos); + gf_isom_check_position_list(s, ptr->TrackExList, &pos); + gf_isom_check_position_list(s, ptr->TrackExPropList, &pos); + return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *mehd_New() +GF_Box *mehd_box_new() { ISOM_DECL_BOX_ALLOC(GF_MovieExtendsHeaderBox, GF_ISOM_BOX_TYPE_MEHD); return (GF_Box *)tmp; } -void mehd_del(GF_Box *s) +void mehd_box_del(GF_Box *s) { gf_free(s); } -GF_Err mehd_Read(GF_Box *s, GF_BitStream *bs) +GF_Err mehd_box_read(GF_Box *s, GF_BitStream *bs) { GF_MovieExtendsHeaderBox *ptr = (GF_MovieExtendsHeaderBox *)s; if (ptr->version==1) { + ISOM_DECREASE_SIZE(ptr, 8); ptr->fragment_duration = gf_bs_read_u64(bs); } else { + ISOM_DECREASE_SIZE(ptr, 4); ptr->fragment_duration = (u64) gf_bs_read_u32(bs); } return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err mehd_Write(GF_Box *s, GF_BitStream *bs) +GF_Err mehd_box_write(GF_Box *s, GF_BitStream *bs) { GF_MovieExtendsHeaderBox *ptr = (GF_MovieExtendsHeaderBox *)s; GF_Err e = gf_isom_full_box_write(s, bs); @@ -4525,7 +4445,7 @@ GF_Err mehd_Write(GF_Box *s, GF_BitStream *bs) } return GF_OK; } -GF_Err mehd_Size(GF_Box *s) +GF_Err mehd_box_size(GF_Box *s) { GF_MovieExtendsHeaderBox *ptr = (GF_MovieExtendsHeaderBox *)s; ptr->version = (ptr->fragment_duration>0xFFFFFFFF) ? 1 : 0; @@ -4537,7 +4457,7 @@ GF_Err mehd_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_FRAGMENTS*/ -void mvhd_del(GF_Box *s) +void mvhd_box_del(GF_Box *s) { GF_MovieHeaderBox *ptr = (GF_MovieHeaderBox *)s; if (ptr == NULL) return; @@ -4545,16 +4465,18 @@ void mvhd_del(GF_Box *s) } -GF_Err mvhd_Read(GF_Box *s, GF_BitStream *bs) +GF_Err mvhd_box_read(GF_Box *s, GF_BitStream *bs) { GF_MovieHeaderBox *ptr = (GF_MovieHeaderBox *)s; if (ptr == NULL) return GF_BAD_PARAM; if (ptr->version == 1) { + ISOM_DECREASE_SIZE(ptr, 28); ptr->creationTime = gf_bs_read_u64(bs); ptr->modificationTime = gf_bs_read_u64(bs); ptr->timeScale = gf_bs_read_u32(bs); ptr->duration = gf_bs_read_u64(bs); } else { + ISOM_DECREASE_SIZE(ptr, 16); ptr->creationTime = gf_bs_read_u32(bs); ptr->modificationTime = gf_bs_read_u32(bs); ptr->timeScale = gf_bs_read_u32(bs); @@ -4564,6 +4486,7 @@ GF_Err mvhd_Read(GF_Box *s, GF_BitStream *bs) GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Movie header timescale is invalid (0) - defaulting to 600\n" )); ptr->timeScale = 600; } + ISOM_DECREASE_SIZE(ptr, 80); ptr->preferredRate = gf_bs_read_u32(bs); ptr->preferredVolume = gf_bs_read_u16(bs); gf_bs_read_data(bs, ptr->reserved, 10); @@ -4587,7 +4510,7 @@ GF_Err mvhd_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *mvhd_New() +GF_Box *mvhd_box_new() { ISOM_DECL_BOX_ALLOC(GF_MovieHeaderBox, GF_ISOM_BOX_TYPE_MVHD); @@ -4606,7 +4529,7 @@ GF_Box *mvhd_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err mvhd_Write(GF_Box *s, GF_BitStream *bs) +GF_Err mvhd_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_MovieHeaderBox *ptr = (GF_MovieHeaderBox *)s; @@ -4645,7 +4568,7 @@ GF_Err mvhd_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err mvhd_Size(GF_Box *s) +GF_Err mvhd_box_size(GF_Box *s) { GF_MovieHeaderBox *ptr = (GF_MovieHeaderBox *)s; if (ptr->duration==(u64) -1) ptr->version = 0; @@ -4659,7 +4582,7 @@ GF_Err mvhd_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void nmhd_del(GF_Box *s) +void nmhd_box_del(GF_Box *s) { GF_MPEGMediaHeaderBox *ptr = (GF_MPEGMediaHeaderBox *)s; if (ptr == NULL) return; @@ -4668,12 +4591,12 @@ void nmhd_del(GF_Box *s) -GF_Err nmhd_Read(GF_Box *s, GF_BitStream *bs) +GF_Err nmhd_box_read(GF_Box *s, GF_BitStream *bs) { return GF_OK; } -GF_Box *nmhd_New() +GF_Box *nmhd_box_new() { ISOM_DECL_BOX_ALLOC(GF_MPEGMediaHeaderBox, GF_ISOM_BOX_TYPE_NMHD); return (GF_Box *)tmp; @@ -4682,12 +4605,12 @@ GF_Box *nmhd_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err nmhd_Write(GF_Box *s, GF_BitStream *bs) +GF_Err nmhd_box_write(GF_Box *s, GF_BitStream *bs) { return gf_isom_full_box_write(s, bs); } -GF_Err nmhd_Size(GF_Box *s) +GF_Err nmhd_box_size(GF_Box *s) { return GF_OK; } @@ -4696,7 +4619,7 @@ GF_Err nmhd_Size(GF_Box *s) -void padb_del(GF_Box *s) +void padb_box_del(GF_Box *s) { GF_PaddingBitsBox *ptr = (GF_PaddingBitsBox *) s; if (ptr == NULL) return; @@ -4705,14 +4628,19 @@ void padb_del(GF_Box *s) } -GF_Err padb_Read(GF_Box *s,GF_BitStream *bs) +GF_Err padb_box_read(GF_Box *s,GF_BitStream *bs) { u32 i; GF_PaddingBitsBox *ptr = (GF_PaddingBitsBox *)s; + ISOM_DECREASE_SIZE(ptr, 4); ptr->SampleCount = gf_bs_read_u32(bs); + if (ptr->size < ptr->SampleCount/2) //half byte per sample + return GF_ISOM_INVALID_FILE; ptr->padbits = (u8 *)gf_malloc(sizeof(u8)*ptr->SampleCount); + if (!ptr->padbits) return GF_OUT_OF_MEM; + for (i=0; iSampleCount; i += 2) { gf_bs_read_int(bs, 1); if (i+1 < ptr->SampleCount) { @@ -4726,7 +4654,7 @@ GF_Err padb_Read(GF_Box *s,GF_BitStream *bs) return GF_OK; } -GF_Box *padb_New() +GF_Box *padb_box_new() { ISOM_DECL_BOX_ALLOC(GF_PaddingBitsBox, GF_ISOM_BOX_TYPE_PADB); return (GF_Box *)tmp; @@ -4736,7 +4664,7 @@ GF_Box *padb_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err padb_Write(GF_Box *s, GF_BitStream *bs) +GF_Err padb_box_write(GF_Box *s, GF_BitStream *bs) { u32 i; GF_Err e; @@ -4759,7 +4687,7 @@ GF_Err padb_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err padb_Size(GF_Box *s) +GF_Err padb_box_size(GF_Box *s) { GF_PaddingBitsBox *ptr = (GF_PaddingBitsBox *)s; ptr->size += 4; @@ -4771,22 +4699,23 @@ GF_Err padb_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void rely_del(GF_Box *s) +void rely_box_del(GF_Box *s) { GF_RelyHintBox *rely = (GF_RelyHintBox *)s; gf_free(rely); } -GF_Err rely_Read(GF_Box *s, GF_BitStream *bs) +GF_Err rely_box_read(GF_Box *s, GF_BitStream *bs) { GF_RelyHintBox *ptr = (GF_RelyHintBox *)s; + ISOM_DECREASE_SIZE(ptr, 1); ptr->reserved = gf_bs_read_int(bs, 6); - ptr->prefered = gf_bs_read_int(bs, 1); + ptr->preferred = gf_bs_read_int(bs, 1); ptr->required = gf_bs_read_int(bs, 1); return GF_OK; } -GF_Box *rely_New() +GF_Box *rely_box_new() { ISOM_DECL_BOX_ALLOC(GF_RelyHintBox, GF_ISOM_BOX_TYPE_RELY); return (GF_Box *)tmp; @@ -4794,7 +4723,7 @@ GF_Box *rely_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err rely_Write(GF_Box *s, GF_BitStream *bs) +GF_Err rely_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_RelyHintBox *ptr = (GF_RelyHintBox *)s; @@ -4802,12 +4731,12 @@ GF_Err rely_Write(GF_Box *s, GF_BitStream *bs) e = gf_isom_box_write_header(s, bs); if (e) return e; gf_bs_write_int(bs, ptr->reserved, 6); - gf_bs_write_int(bs, ptr->prefered, 1); + gf_bs_write_int(bs, ptr->preferred, 1); gf_bs_write_int(bs, ptr->required, 1); return GF_OK; } -GF_Err rely_Size(GF_Box *s) +GF_Err rely_box_size(GF_Box *s) { s->size += 1; return GF_OK; @@ -4815,26 +4744,27 @@ GF_Err rely_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void rtpo_del(GF_Box *s) +void rtpo_box_del(GF_Box *s) { GF_RTPOBox *rtpo = (GF_RTPOBox *)s; gf_free(rtpo); } -GF_Err rtpo_Read(GF_Box *s, GF_BitStream *bs) +GF_Err rtpo_box_read(GF_Box *s, GF_BitStream *bs) { GF_RTPOBox *ptr = (GF_RTPOBox *)s; + ISOM_DECREASE_SIZE(ptr, 4); ptr->timeOffset = gf_bs_read_u32(bs); return GF_OK; } -GF_Box *rtpo_New() +GF_Box *rtpo_box_new() { ISOM_DECL_BOX_ALLOC(GF_RTPOBox, GF_ISOM_BOX_TYPE_RTPO); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err rtpo_Write(GF_Box *s, GF_BitStream *bs) +GF_Err rtpo_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_RTPOBox *ptr = (GF_RTPOBox *)s; @@ -4848,14 +4778,14 @@ GF_Err rtpo_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err rtpo_Size(GF_Box *s) +GF_Err rtpo_box_size(GF_Box *s) { s->size += 4; return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void smhd_del(GF_Box *s) +void smhd_box_del(GF_Box *s) { GF_SoundMediaHeaderBox *ptr = (GF_SoundMediaHeaderBox *)s; if (ptr == NULL ) return; @@ -4863,15 +4793,16 @@ void smhd_del(GF_Box *s) } -GF_Err smhd_Read(GF_Box *s, GF_BitStream *bs) +GF_Err smhd_box_read(GF_Box *s, GF_BitStream *bs) { GF_SoundMediaHeaderBox *ptr = (GF_SoundMediaHeaderBox *)s; + ISOM_DECREASE_SIZE(ptr, 4); ptr->balance = gf_bs_read_u16(bs); ptr->reserved = gf_bs_read_u16(bs); return GF_OK; } -GF_Box *smhd_New() +GF_Box *smhd_box_new() { ISOM_DECL_BOX_ALLOC(GF_SoundMediaHeaderBox, GF_ISOM_BOX_TYPE_SMHD); return (GF_Box *)tmp; @@ -4880,7 +4811,7 @@ GF_Box *smhd_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err smhd_Write(GF_Box *s, GF_BitStream *bs) +GF_Err smhd_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_SoundMediaHeaderBox *ptr = (GF_SoundMediaHeaderBox *)s; @@ -4891,7 +4822,7 @@ GF_Err smhd_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err smhd_Size(GF_Box *s) +GF_Err smhd_box_size(GF_Box *s) { GF_SoundMediaHeaderBox *ptr = (GF_SoundMediaHeaderBox *)s; @@ -4904,20 +4835,21 @@ GF_Err smhd_Size(GF_Box *s) -void snro_del(GF_Box *s) +void snro_box_del(GF_Box *s) { GF_SeqOffHintEntryBox *snro = (GF_SeqOffHintEntryBox *)s; gf_free(snro); } -GF_Err snro_Read(GF_Box *s, GF_BitStream *bs) +GF_Err snro_box_read(GF_Box *s, GF_BitStream *bs) { GF_SeqOffHintEntryBox *ptr = (GF_SeqOffHintEntryBox *)s; + ISOM_DECREASE_SIZE(ptr, 4); ptr->SeqOffset = gf_bs_read_u32(bs); return GF_OK; } -GF_Box *snro_New() +GF_Box *snro_box_new() { ISOM_DECL_BOX_ALLOC(GF_SeqOffHintEntryBox, GF_ISOM_BOX_TYPE_SNRO); return (GF_Box *)tmp; @@ -4925,7 +4857,7 @@ GF_Box *snro_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err snro_Write(GF_Box *s, GF_BitStream *bs) +GF_Err snro_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_SeqOffHintEntryBox *ptr = (GF_SeqOffHintEntryBox *)s; @@ -4937,7 +4869,7 @@ GF_Err snro_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err snro_Size(GF_Box *s) +GF_Err snro_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -4945,99 +4877,86 @@ GF_Err snro_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -#define WRITE_SAMPLE_FRAGMENTS 1 - -void stbl_del(GF_Box *s) +void stbl_box_del(GF_Box *s) { GF_SampleTableBox *ptr = (GF_SampleTableBox *)s; if (ptr == NULL) return; - if (ptr->ChunkOffset) gf_isom_box_del(ptr->ChunkOffset); - if (ptr->CompositionOffset) gf_isom_box_del((GF_Box *) ptr->CompositionOffset); - if (ptr->CompositionToDecode) gf_isom_box_del((GF_Box *) ptr->CompositionToDecode); - if (ptr->DegradationPriority) gf_isom_box_del((GF_Box *) ptr->DegradationPriority); - if (ptr->SampleDescription) gf_isom_box_del((GF_Box *) ptr->SampleDescription); - if (ptr->SampleSize) gf_isom_box_del((GF_Box *) ptr->SampleSize); - if (ptr->SampleToChunk) gf_isom_box_del((GF_Box *) ptr->SampleToChunk); - if (ptr->ShadowSync) gf_isom_box_del((GF_Box *) ptr->ShadowSync); - if (ptr->SyncSample) gf_isom_box_del((GF_Box *) ptr->SyncSample); - if (ptr->TimeToSample) gf_isom_box_del((GF_Box *) ptr->TimeToSample); - if (ptr->SampleDep) gf_isom_box_del((GF_Box *) ptr->SampleDep); - if (ptr->PaddingBits) gf_isom_box_del((GF_Box *) ptr->PaddingBits); - if (ptr->Fragments) gf_isom_box_del((GF_Box *) ptr->Fragments); - if (ptr->sub_samples) gf_isom_box_array_del(ptr->sub_samples); - if (ptr->sampleGroups) gf_isom_box_array_del(ptr->sampleGroups); - if (ptr->sampleGroupsDescription) gf_isom_box_array_del(ptr->sampleGroupsDescription); - - if (ptr->sai_sizes) gf_isom_box_array_del(ptr->sai_sizes); - if (ptr->sai_offsets) gf_isom_box_array_del(ptr->sai_offsets); - + if (ptr->sub_samples) gf_list_del(ptr->sub_samples); + if (ptr->sampleGroups) gf_list_del(ptr->sampleGroups); + if (ptr->sampleGroupsDescription) gf_list_del(ptr->sampleGroupsDescription); + if (ptr->sai_sizes) gf_list_del(ptr->sai_sizes); + if (ptr->sai_offsets) gf_list_del(ptr->sai_offsets); + if (ptr->traf_map) { + if (ptr->traf_map->frag_starts) { + u32 i; + for (i=0; itraf_map->nb_entries; i++) { + if (ptr->traf_map->frag_starts[i].moof_template) + gf_free(ptr->traf_map->frag_starts[i].moof_template); + } + gf_free(ptr->traf_map->frag_starts); + } + gf_free(ptr->traf_map); + } gf_free(ptr); } -GF_Err stbl_AddBox(GF_Box *s, GF_Box *a) +GF_Err stbl_on_child_box(GF_Box *s, GF_Box *a) { GF_SampleTableBox *ptr = (GF_SampleTableBox *)s; if (!a) return GF_OK; switch (a->type) { case GF_ISOM_BOX_TYPE_STTS: if (ptr->TimeToSample) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->TimeToSample = (GF_TimeToSampleBox *)a; + ptr->TimeToSample = (GF_TimeToSampleBox *)a; break; case GF_ISOM_BOX_TYPE_CTTS: if (ptr->CompositionOffset) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->CompositionOffset = (GF_CompositionOffsetBox *)a; + ptr->CompositionOffset = (GF_CompositionOffsetBox *)a; break; case GF_ISOM_BOX_TYPE_CSLG: if (ptr->CompositionToDecode) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->CompositionToDecode = (GF_CompositionToDecodeBox *)a; + ptr->CompositionToDecode = (GF_CompositionToDecodeBox *)a; break; case GF_ISOM_BOX_TYPE_STSS: if (ptr->SyncSample) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->SyncSample = (GF_SyncSampleBox *)a; + ptr->SyncSample = (GF_SyncSampleBox *)a; break; case GF_ISOM_BOX_TYPE_STSD: if (ptr->SampleDescription) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->SampleDescription =(GF_SampleDescriptionBox *)a; + ptr->SampleDescription =(GF_SampleDescriptionBox *)a; break; case GF_ISOM_BOX_TYPE_STZ2: case GF_ISOM_BOX_TYPE_STSZ: if (ptr->SampleSize) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->SampleSize = (GF_SampleSizeBox *)a; + ptr->SampleSize = (GF_SampleSizeBox *)a; break; case GF_ISOM_BOX_TYPE_STSC: if (ptr->SampleToChunk) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->SampleToChunk = (GF_SampleToChunkBox *)a; + ptr->SampleToChunk = (GF_SampleToChunkBox *)a; break; case GF_ISOM_BOX_TYPE_PADB: if (ptr->PaddingBits) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->PaddingBits = (GF_PaddingBitsBox *) a; + ptr->PaddingBits = (GF_PaddingBitsBox *) a; break; //WARNING: AS THIS MAY CHANGE DYNAMICALLY DURING EDIT, case GF_ISOM_BOX_TYPE_CO64: case GF_ISOM_BOX_TYPE_STCO: - if (ptr->ChunkOffset) { - gf_isom_box_del(ptr->ChunkOffset); - } + if (ptr->ChunkOffset) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->ChunkOffset = a; return GF_OK; case GF_ISOM_BOX_TYPE_STSH: if (ptr->ShadowSync) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->ShadowSync = (GF_ShadowSyncBox *)a; + ptr->ShadowSync = (GF_ShadowSyncBox *)a; break; case GF_ISOM_BOX_TYPE_STDP: if (ptr->DegradationPriority) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->DegradationPriority = (GF_DegradationPriorityBox *)a; + ptr->DegradationPriority = (GF_DegradationPriorityBox *)a; break; case GF_ISOM_BOX_TYPE_SDTP: if (ptr->SampleDep) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->SampleDep= (GF_SampleDependencyTypeBox *)a; - break; - - case GF_ISOM_BOX_TYPE_STSF: - if (ptr->Fragments) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->Fragments = (GF_SampleFragmentBox *)a; + ptr->SampleDep = (GF_SampleDependencyTypeBox *)a; break; case GF_ISOM_BOX_TYPE_SUBS: @@ -5047,7 +4966,11 @@ GF_Err stbl_AddBox(GF_Box *s, GF_Box *a) { GF_SubSampleInformationBox *subs = (GF_SubSampleInformationBox *)a; GF_SubSampleInfoEntry *ent = gf_list_get(subs->Samples, 0); - if (ent->sample_delta==0) { + if (!ent) { + gf_list_rem(subs->Samples, 0); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] first entry in SubSample in track SampleTable is invalid\n")); + } + else if (ent->sample_delta==0) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] first entry in SubSample in track SampleTable has sample_delta of 0, should be one. Fixing\n")); ent->sample_delta = 1; } @@ -5071,8 +4994,6 @@ GF_Err stbl_AddBox(GF_Box *s, GF_Box *a) if (!ptr->sai_offsets) ptr->sai_offsets = gf_list_new(); gf_list_add(ptr->sai_offsets, a); break; - default: - return gf_isom_box_add_default((GF_Box *)ptr, a); } return GF_OK; } @@ -5080,25 +5001,37 @@ GF_Err stbl_AddBox(GF_Box *s, GF_Box *a) -GF_Err stbl_Read(GF_Box *s, GF_BitStream *bs) +GF_Err stbl_box_read(GF_Box *s, GF_BitStream *bs) { GF_Err e; //we need to parse DegPrior in a special way GF_SampleTableBox *ptr = (GF_SampleTableBox *)s; - e = gf_isom_box_array_read(s, bs, stbl_AddBox); + e = gf_isom_box_array_read(s, bs, stbl_on_child_box); if (e) return e; if (!ptr->SyncSample) ptr->no_sync_found = 1; ptr->nb_sgpd_in_stbl = gf_list_count(ptr->sampleGroupsDescription); - ptr->nb_other_boxes_in_stbl = gf_list_count(ptr->other_boxes); + ptr->nb_stbl_boxes = gf_list_count(ptr->child_boxes); + + if (gf_bs_get_cookie(bs) & GF_ISOM_BS_COOKIE_CLONE_TRACK) + return GF_OK; +// return GF_OK; + //these boxes are mandatory ! + if (!ptr->SampleToChunk || !ptr->SampleSize || !ptr->ChunkOffset || !ptr->TimeToSample) + return GF_ISOM_INVALID_FILE; + //sanity check + if (ptr->SampleSize->sampleCount) { + if (!ptr->TimeToSample->nb_entries || !ptr->SampleToChunk->nb_entries) + return GF_ISOM_INVALID_FILE; + } return GF_OK; } -GF_Box *stbl_New() +GF_Box *stbl_box_new() { ISOM_DECL_BOX_ALLOC(GF_SampleTableBox, GF_ISOM_BOX_TYPE_STBL); //maxSamplePer chunk is 10 by default @@ -5111,190 +5044,43 @@ GF_Box *stbl_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err stbl_Write(GF_Box *s, GF_BitStream *bs) +GF_Err stbl_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_SampleTableBox *ptr = (GF_SampleTableBox *)s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_box_write_header(s, bs); - if (e) return e; - - if (ptr->SampleDescription) { - e = gf_isom_box_write((GF_Box *) ptr->SampleDescription, bs); - if (e) return e; - } - if (ptr->TimeToSample) { - e = gf_isom_box_write((GF_Box *) ptr->TimeToSample, bs); - if (e) return e; - } - if (ptr->CompositionOffset) { - e = gf_isom_box_write((GF_Box *) ptr->CompositionOffset, bs); - if (e) return e; - } - if (ptr->CompositionToDecode) { - e = gf_isom_box_write((GF_Box *) ptr->CompositionToDecode, bs); - if (e) return e; - } - if (ptr->SyncSample) { - e = gf_isom_box_write((GF_Box *) ptr->SyncSample, bs); - if (e) return e; - } - if (ptr->ShadowSync) { - e = gf_isom_box_write((GF_Box *) ptr->ShadowSync, bs); - if (e) return e; - } - if (ptr->SampleToChunk) { - e = gf_isom_box_write((GF_Box *) ptr->SampleToChunk, bs); - if (e) return e; - } - if (ptr->SampleSize) { - e = gf_isom_box_write((GF_Box *) ptr->SampleSize, bs); - if (e) return e; - } - if (ptr->ChunkOffset) { - e = gf_isom_box_write(ptr->ChunkOffset, bs); - if (e) return e; - } - if (ptr->DegradationPriority) { - e = gf_isom_box_write((GF_Box *) ptr->DegradationPriority, bs); - if (e) return e; - } - if (ptr->SampleDep && ptr->SampleDep->sampleCount) { - e = gf_isom_box_write((GF_Box *) ptr->SampleDep, bs); - if (e) return e; - } - if (ptr->PaddingBits) { - e = gf_isom_box_write((GF_Box *) ptr->PaddingBits, bs); - if (e) return e; - } - if (ptr->sub_samples) { - e = gf_isom_box_array_write(s, ptr->sub_samples, bs); - if (e) return e; - } - if (ptr->sampleGroupsDescription) { - e = gf_isom_box_array_write(s, ptr->sampleGroupsDescription, bs); - if (e) return e; - } - if (ptr->sampleGroups) { - e = gf_isom_box_array_write(s, ptr->sampleGroups, bs); - if (e) return e; - } - if (ptr->sai_sizes) { - e = gf_isom_box_array_write(s, ptr->sai_sizes, bs); - if (e) return e; - } - if (ptr->sai_offsets) { - e = gf_isom_box_array_write(s, ptr->sai_offsets, bs); - if (e) return e; - } - -#if WRITE_SAMPLE_FRAGMENTS - //sampleFragments - if (ptr->Fragments) { - e = gf_isom_box_write((GF_Box *) ptr->Fragments, bs); - if (e) return e; - } -#endif - return GF_OK; + return gf_isom_box_write_header(s, bs); } -GF_Err stbl_Size(GF_Box *s) +GF_Err stbl_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_SampleTableBox *ptr = (GF_SampleTableBox *)s; - //Mandatory boxs (but not internally :) - if (ptr->SampleDescription) { - e = gf_isom_box_size((GF_Box *) ptr->SampleDescription); - if (e) return e; - ptr->size += ptr->SampleDescription->size; - } - if (ptr->SampleSize) { - e = gf_isom_box_size((GF_Box *) ptr->SampleSize); - if (e) return e; - ptr->size += ptr->SampleSize->size; - } - if (ptr->SampleToChunk) { - e = gf_isom_box_size((GF_Box *) ptr->SampleToChunk); - if (e) return e; - ptr->size += ptr->SampleToChunk->size; - } - if (ptr->TimeToSample) { - e = gf_isom_box_size((GF_Box *) ptr->TimeToSample); - if (e) return e; - ptr->size += ptr->TimeToSample->size; - } - if (ptr->ChunkOffset) { - e = gf_isom_box_size(ptr->ChunkOffset); - if (e) return e; - ptr->size += ptr->ChunkOffset->size; - } - - //optional boxs - if (ptr->CompositionOffset) { - e = gf_isom_box_size((GF_Box *) ptr->CompositionOffset); - if (e) return e; - ptr->size += ptr->CompositionOffset->size; - } - if (ptr->CompositionToDecode) { - e = gf_isom_box_size((GF_Box *) ptr->CompositionToDecode); - if (e) return e; - ptr->size += ptr->CompositionToDecode->size; - } - if (ptr->DegradationPriority) { - e = gf_isom_box_size((GF_Box *) ptr->DegradationPriority); - if (e) return e; - ptr->size += ptr->DegradationPriority->size; - } - if (ptr->ShadowSync) { - e = gf_isom_box_size((GF_Box *) ptr->ShadowSync); - if (e) return e; - ptr->size += ptr->ShadowSync->size; - } - if (ptr->SyncSample) { - e = gf_isom_box_size((GF_Box *) ptr->SyncSample); - if (e) return e; - ptr->size += ptr->SyncSample->size; - } - if (ptr->SampleDep && ptr->SampleDep->sampleCount) { - e = gf_isom_box_size((GF_Box *) ptr->SampleDep); - if (e) return e; - ptr->size += ptr->SampleDep->size; - } - //padb - if (ptr->PaddingBits) { - e = gf_isom_box_size((GF_Box *) ptr->PaddingBits); - if (e) return e; - ptr->size += ptr->PaddingBits->size; - } -#if WRITE_SAMPLE_FRAGMENTS - //sample fragments - if (ptr->Fragments) { - e = gf_isom_box_size((GF_Box *) ptr->Fragments); - if (e) return e; - ptr->size += ptr->Fragments->size; - } -#endif + gf_isom_check_position(s, (GF_Box *)ptr->SampleDescription, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->TimeToSample, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->CompositionOffset, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->CompositionToDecode, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->SyncSample, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->ShadowSync, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->SampleToChunk, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->SampleSize, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->ChunkOffset, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->DegradationPriority, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->SampleDep, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->PaddingBits, &pos); if (ptr->sub_samples) { - e = gf_isom_box_array_size(s, ptr->sub_samples); - if (e) return e; - } - if (ptr->sampleGroups) { - e = gf_isom_box_array_size(s, ptr->sampleGroups); - if (e) return e; + gf_isom_check_position_list(s, ptr->sub_samples, &pos); } if (ptr->sampleGroupsDescription) { - e = gf_isom_box_array_size(s, ptr->sampleGroupsDescription); - if (e) return e; + gf_isom_check_position_list(s, ptr->sampleGroupsDescription, &pos); + } + if (ptr->sampleGroups) { + gf_isom_check_position_list(s, ptr->sampleGroups, &pos); } if (ptr->sai_sizes) { - e = gf_isom_box_array_size(s, ptr->sai_sizes); - if (e) return e; + gf_isom_check_position_list(s, ptr->sai_sizes, &pos); } if (ptr->sai_offsets) { - e = gf_isom_box_array_size(s, ptr->sai_offsets); - if (e) return e; + gf_isom_check_position_list(s, ptr->sai_offsets, &pos); } return GF_OK; } @@ -5302,7 +5088,7 @@ GF_Err stbl_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void stco_del(GF_Box *s) +void stco_box_del(GF_Box *s) { GF_ChunkOffsetBox *ptr = (GF_ChunkOffsetBox *)s; if (ptr == NULL) return; @@ -5311,13 +5097,13 @@ void stco_del(GF_Box *s) } -GF_Err stco_Read(GF_Box *s, GF_BitStream *bs) +GF_Err stco_box_read(GF_Box *s, GF_BitStream *bs) { u32 entries; GF_ChunkOffsetBox *ptr = (GF_ChunkOffsetBox *)s; - ptr->nb_entries = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); + ptr->nb_entries = gf_bs_read_u32(bs); if (ptr->nb_entries > ptr->size / 4) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Invalid number of entries %d in stco\n", ptr->nb_entries)); return GF_ISOM_INVALID_FILE; @@ -5335,7 +5121,7 @@ GF_Err stco_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *stco_New() +GF_Box *stco_box_new() { ISOM_DECL_BOX_ALLOC(GF_ChunkOffsetBox, GF_ISOM_BOX_TYPE_STCO); return (GF_Box *)tmp; @@ -5345,7 +5131,7 @@ GF_Box *stco_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err stco_Write(GF_Box *s, GF_BitStream *bs) +GF_Err stco_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -5360,7 +5146,7 @@ GF_Err stco_Write(GF_Box *s, GF_BitStream *bs) } -GF_Err stco_Size(GF_Box *s) +GF_Err stco_box_size(GF_Box *s) { GF_ChunkOffsetBox *ptr = (GF_ChunkOffsetBox *)s; @@ -5372,7 +5158,7 @@ GF_Err stco_Size(GF_Box *s) -void stdp_del(GF_Box *s) +void stdp_box_del(GF_Box *s) { GF_DegradationPriorityBox *ptr = (GF_DegradationPriorityBox *)s; if (ptr == NULL ) return; @@ -5381,7 +5167,7 @@ void stdp_del(GF_Box *s) } //this is called through stbl_read... -GF_Err stdp_Read(GF_Box *s, GF_BitStream *bs) +GF_Err stdp_box_read(GF_Box *s, GF_BitStream *bs) { u32 entry; GF_DegradationPriorityBox *ptr = (GF_DegradationPriorityBox *)s; @@ -5399,7 +5185,7 @@ GF_Err stdp_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *stdp_New() +GF_Box *stdp_box_new() { ISOM_DECL_BOX_ALLOC(GF_DegradationPriorityBox, GF_ISOM_BOX_TYPE_STDP); return (GF_Box *)tmp; @@ -5408,7 +5194,7 @@ GF_Box *stdp_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err stdp_Write(GF_Box *s, GF_BitStream *bs) +GF_Err stdp_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -5422,7 +5208,7 @@ GF_Err stdp_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err stdp_Size(GF_Box *s) +GF_Err stdp_box_size(GF_Box *s) { GF_DegradationPriorityBox *ptr = (GF_DegradationPriorityBox *)s; @@ -5433,7 +5219,7 @@ GF_Err stdp_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void stsc_del(GF_Box *s) +void stsc_box_del(GF_Box *s) { GF_SampleToChunkBox *ptr = (GF_SampleToChunkBox *)s; if (ptr == NULL) return; @@ -5442,21 +5228,25 @@ void stsc_del(GF_Box *s) } -GF_Err stsc_Read(GF_Box *s, GF_BitStream *bs) +GF_Err stsc_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; GF_SampleToChunkBox *ptr = (GF_SampleToChunkBox *)s; - ptr->nb_entries = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); + ptr->nb_entries = gf_bs_read_u32(bs); + if (ptr->nb_entries > ptr->size / 12) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Invalid number of entries %d in stsc\n", ptr->nb_entries)); return GF_ISOM_INVALID_FILE; } ptr->alloc_size = ptr->nb_entries; - ptr->entries = gf_malloc(sizeof(GF_StscEntry)*ptr->alloc_size); - if (!ptr->entries) return GF_OUT_OF_MEM; + ptr->entries = NULL; + if (ptr->nb_entries) { + ptr->entries = gf_malloc(sizeof(GF_StscEntry)*ptr->alloc_size); + if (!ptr->entries) return GF_OUT_OF_MEM; + } for (i = 0; i < ptr->nb_entries; i++) { ptr->entries[i].firstChunk = gf_bs_read_u32(bs); @@ -5479,7 +5269,7 @@ GF_Err stsc_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *stsc_New() +GF_Box *stsc_box_new() { ISOM_DECL_BOX_ALLOC(GF_SampleToChunkBox, GF_ISOM_BOX_TYPE_STSC); return (GF_Box *)tmp; @@ -5488,7 +5278,7 @@ GF_Box *stsc_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err stsc_Write(GF_Box *s, GF_BitStream *bs) +GF_Err stsc_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -5505,7 +5295,7 @@ GF_Err stsc_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err stsc_Size(GF_Box *s) +GF_Err stsc_box_size(GF_Box *s) { GF_SampleToChunkBox *ptr = (GF_SampleToChunkBox *)s; @@ -5515,79 +5305,32 @@ GF_Err stsc_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void stsd_del(GF_Box *s) +void stsd_box_del(GF_Box *s) { GF_SampleDescriptionBox *ptr = (GF_SampleDescriptionBox *)s; if (ptr == NULL) return; gf_free(ptr); } -GF_Err stsd_AddBox(GF_Box *s, GF_Box *a) +GF_Err stsd_on_child_box(GF_Box *s, GF_Box *a) { GF_UnknownBox *def; - GF_SampleDescriptionBox *ptr = (GF_SampleDescriptionBox *)s; if (!a) return GF_OK; + if (gf_box_valid_in_parent(a, "stsd")) { + return GF_OK; + } switch (a->type) { - case GF_ISOM_BOX_TYPE_MP4S: - case GF_ISOM_BOX_TYPE_ENCS: - case GF_ISOM_BOX_TYPE_MP4A: - case GF_ISOM_BOX_TYPE_ENCA: - case GF_ISOM_BOX_TYPE_MP4V: - case GF_ISOM_BOX_TYPE_ENCV: - case GF_ISOM_BOX_TYPE_RESV: - case GF_ISOM_BOX_TYPE_GHNT: - case GF_ISOM_BOX_TYPE_RTP_STSD: - case GF_ISOM_BOX_TYPE_SRTP_STSD: - case GF_ISOM_BOX_TYPE_FDP_STSD: - case GF_ISOM_BOX_TYPE_RRTP_STSD: - case GF_ISOM_BOX_TYPE_RTCP_STSD: - case GF_ISOM_BOX_TYPE_AVC1: - case GF_ISOM_BOX_TYPE_AVC2: - case GF_ISOM_BOX_TYPE_AVC3: - case GF_ISOM_BOX_TYPE_AVC4: - case GF_ISOM_BOX_TYPE_SVC1: - case GF_ISOM_BOX_TYPE_MVC1: - case GF_ISOM_BOX_TYPE_HVC1: - case GF_ISOM_BOX_TYPE_HEV1: - case GF_ISOM_BOX_TYPE_HVC2: - case GF_ISOM_BOX_TYPE_HEV2: - case GF_ISOM_BOX_TYPE_HVT1: - case GF_ISOM_BOX_TYPE_LHV1: - case GF_ISOM_BOX_TYPE_LHE1: - case GF_ISOM_BOX_TYPE_TX3G: - case GF_ISOM_BOX_TYPE_TEXT: - case GF_ISOM_BOX_TYPE_ENCT: - case GF_ISOM_BOX_TYPE_METX: - case GF_ISOM_BOX_TYPE_METT: - case GF_ISOM_BOX_TYPE_STXT: - case GF_ISOM_BOX_TYPE_DIMS: - case GF_ISOM_BOX_TYPE_AC3: - case GF_ISOM_BOX_TYPE_EC3: - case GF_ISOM_BOX_TYPE_LSR1: - case GF_ISOM_BOX_TYPE_WVTT: - case GF_ISOM_BOX_TYPE_STPP: - case GF_ISOM_BOX_TYPE_SBTT: - case GF_ISOM_BOX_TYPE_ELNG: - case GF_ISOM_BOX_TYPE_MP3: - case GF_ISOM_SUBTYPE_3GP_AMR: - case GF_ISOM_SUBTYPE_3GP_AMR_WB: - case GF_ISOM_SUBTYPE_3GP_EVRC: - case GF_ISOM_SUBTYPE_3GP_QCELP: - case GF_ISOM_SUBTYPE_3GP_SMV: - case GF_ISOM_SUBTYPE_3GP_H263: - return gf_isom_box_add_default((GF_Box*)ptr, a); - //unknown sample description: we need a specific box to handle the data ref index //rather than a default box ... case GF_ISOM_BOX_TYPE_UNKNOWN: def = (GF_UnknownBox *)a; /*we need at least 8 bytes for unknown sample entries*/ if (def->dataSize < 8) { - gf_isom_box_del(a); - return GF_OK; + gf_isom_box_del_parent(&s->child_boxes, a); + return GF_ISOM_INVALID_MEDIA; } - return gf_isom_box_add_default((GF_Box*)ptr, a); + return GF_OK; default: GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Cannot process box of type %s\n", gf_4cc_to_str(a->type))); @@ -5596,25 +5339,25 @@ GF_Err stsd_AddBox(GF_Box *s, GF_Box *a) } -GF_Err stsd_Read(GF_Box *s, GF_BitStream *bs) +GF_Err stsd_box_read(GF_Box *s, GF_BitStream *bs) { - gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(s, 4) + gf_bs_read_u32(bs); - return gf_isom_box_array_read(s, bs, stsd_AddBox); + return gf_isom_box_array_read_ex(s, bs, stsd_on_child_box, GF_ISOM_BOX_TYPE_STSD); } -GF_Box *stsd_New() +GF_Box *stsd_box_new() { ISOM_DECL_BOX_ALLOC(GF_SampleDescriptionBox, GF_ISOM_BOX_TYPE_STSD); - tmp->other_boxes = gf_list_new(); + tmp->child_boxes = gf_list_new(); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err stsd_Write(GF_Box *s, GF_BitStream *bs) +GF_Err stsd_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 nb_entries; @@ -5622,12 +5365,12 @@ GF_Err stsd_Write(GF_Box *s, GF_BitStream *bs) e = gf_isom_full_box_write(s, bs); if (e) return e; - nb_entries = gf_list_count(ptr->other_boxes); + nb_entries = gf_list_count(ptr->child_boxes); gf_bs_write_u32(bs, nb_entries); return GF_OK; } -GF_Err stsd_Size(GF_Box *s) +GF_Err stsd_box_size(GF_Box *s) { GF_SampleDescriptionBox *ptr = (GF_SampleDescriptionBox *)s; ptr->size += 4; @@ -5636,116 +5379,8 @@ GF_Err stsd_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void stsf_del(GF_Box *s) -{ - u32 nb_entries; - u32 i; - GF_StsfEntry *pe; - GF_SampleFragmentBox *ptr = (GF_SampleFragmentBox *)s; - if (ptr == NULL) return; - - if (ptr->entryList) { - nb_entries = gf_list_count(ptr->entryList); - for ( i = 0; i < nb_entries; i++ ) { - pe = (GF_StsfEntry*)gf_list_get(ptr->entryList, i); - if (pe->fragmentSizes) gf_free(pe->fragmentSizes); - gf_free(pe); - } - gf_list_del(ptr->entryList); - } - gf_free(ptr); -} - - - -GF_Err stsf_Read(GF_Box *s, GF_BitStream *bs) -{ - u32 entries, i; - u32 nb_entries; - GF_StsfEntry *p; - GF_SampleFragmentBox *ptr = (GF_SampleFragmentBox *)s; - - p = NULL; - if (!ptr) return GF_BAD_PARAM; - nb_entries = gf_bs_read_u32(bs); - - p = NULL; - for ( entries = 0; entries < nb_entries; entries++ ) { - p = (GF_StsfEntry *) gf_malloc(sizeof(GF_StsfEntry)); - if (!p) return GF_OUT_OF_MEM; - p->SampleNumber = gf_bs_read_u32(bs); - p->fragmentCount = gf_bs_read_u32(bs); - p->fragmentSizes = (u16*)gf_malloc(sizeof(GF_StsfEntry) * p->fragmentCount); - for (i=0; ifragmentCount; i++) { - p->fragmentSizes[i] = gf_bs_read_u16(bs); - } - gf_list_add(ptr->entryList, p); - } -#ifndef GPAC_DISABLE_ISOM_WRITE - ptr->w_currentEntry = p; - ptr->w_currentEntryIndex = nb_entries-1; -#endif - return GF_OK; -} - -GF_Box *stsf_New() -{ - ISOM_DECL_BOX_ALLOC(GF_SampleFragmentBox, GF_ISOM_BOX_TYPE_STSF); - - tmp->entryList = gf_list_new(); - if (! tmp->entryList) { - gf_free(tmp); - return NULL; - } - return (GF_Box *) tmp; -} - - - - -#ifndef GPAC_DISABLE_ISOM_WRITE - -GF_Err stsf_Write(GF_Box *s, GF_BitStream *bs) -{ - GF_Err e; - u32 i, j; - u32 nb_entries; - GF_StsfEntry *p; - GF_SampleFragmentBox *ptr = (GF_SampleFragmentBox *)s; - - e = gf_isom_full_box_write(s, bs); - if (e) return e; - nb_entries = gf_list_count(ptr->entryList); - gf_bs_write_u32(bs, nb_entries); - for ( i = 0; i < nb_entries; i++ ) { - p = (GF_StsfEntry*)gf_list_get(ptr->entryList, i); - gf_bs_write_u32(bs, p->SampleNumber); - gf_bs_write_u32(bs, p->fragmentCount); - for (j=0; jfragmentCount; j++) { - gf_bs_write_u16(bs, p->fragmentSizes[j]); - } - } - return GF_OK; -} - -GF_Err stsf_Size(GF_Box *s) -{ - GF_StsfEntry *p; - u32 nb_entries, i; - GF_SampleFragmentBox *ptr = (GF_SampleFragmentBox *) s; - - nb_entries = gf_list_count(ptr->entryList); - ptr->size += 4; - for (i=0; ientryList, i); - ptr->size += 8 + 2*p->fragmentCount; - } - return GF_OK; -} - -#endif /*GPAC_DISABLE_ISOM_WRITE*/ -void stsh_del(GF_Box *s) +void stsh_box_del(GF_Box *s) { u32 i = 0; GF_StshEntry *ent; @@ -5760,17 +5395,19 @@ void stsh_del(GF_Box *s) -GF_Err stsh_Read(GF_Box *s, GF_BitStream *bs) +GF_Err stsh_box_read(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 count, i; - GF_StshEntry *ent; GF_ShadowSyncBox *ptr = (GF_ShadowSyncBox *)s; + ISOM_DECREASE_SIZE(s, 4) count = gf_bs_read_u32(bs); + if (ptr->size < count*8) + return GF_ISOM_INVALID_FILE; for (i = 0; i < count; i++) { - ent = (GF_StshEntry *) gf_malloc(sizeof(GF_StshEntry)); + GF_StshEntry *ent = (GF_StshEntry *) gf_malloc(sizeof(GF_StshEntry)); if (!ent) return GF_OUT_OF_MEM; ent->shadowedSampleNumber = gf_bs_read_u32(bs); ent->syncSampleNumber = gf_bs_read_u32(bs); @@ -5780,7 +5417,7 @@ GF_Err stsh_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *stsh_New() +GF_Box *stsh_box_new() { ISOM_DECL_BOX_ALLOC(GF_ShadowSyncBox, GF_ISOM_BOX_TYPE_STSH); @@ -5795,7 +5432,7 @@ GF_Box *stsh_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err stsh_Write(GF_Box *s, GF_BitStream *bs) +GF_Err stsh_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -5813,7 +5450,7 @@ GF_Err stsh_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err stsh_Size(GF_Box *s) +GF_Err stsh_box_size(GF_Box *s) { GF_ShadowSyncBox *ptr = (GF_ShadowSyncBox *)s; ptr->size += 4 + (8 * gf_list_count(ptr->entries)); @@ -5824,7 +5461,7 @@ GF_Err stsh_Size(GF_Box *s) -void stss_del(GF_Box *s) +void stss_box_del(GF_Box *s) { GF_SyncSampleBox *ptr = (GF_SyncSampleBox *)s; if (ptr == NULL) return; @@ -5832,14 +5469,14 @@ void stss_del(GF_Box *s) gf_free(ptr); } -GF_Err stss_Read(GF_Box *s, GF_BitStream *bs) +GF_Err stss_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; GF_SyncSampleBox *ptr = (GF_SyncSampleBox *)s; - ptr->nb_entries = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); - if (ptr->nb_entries > ptr->size / 4) { + ptr->nb_entries = gf_bs_read_u32(bs); + if (ptr->size < ptr->nb_entries * 4) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Invalid number of entries %d in stss\n", ptr->nb_entries)); return GF_ISOM_INVALID_FILE; } @@ -5854,7 +5491,7 @@ GF_Err stss_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *stss_New() +GF_Box *stss_box_new() { ISOM_DECL_BOX_ALLOC(GF_SyncSampleBox, GF_ISOM_BOX_TYPE_STSS); return (GF_Box*)tmp; @@ -5863,7 +5500,7 @@ GF_Box *stss_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err stss_Write(GF_Box *s, GF_BitStream *bs) +GF_Err stss_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -5878,7 +5515,7 @@ GF_Err stss_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err stss_Size(GF_Box *s) +GF_Err stss_box_size(GF_Box *s) { GF_SyncSampleBox *ptr = (GF_SyncSampleBox *)s; ptr->size += 4 + (4 * ptr->nb_entries); @@ -5888,7 +5525,7 @@ GF_Err stss_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void stsz_del(GF_Box *s) +void stsz_box_del(GF_Box *s) { GF_SampleSizeBox *ptr = (GF_SampleSizeBox *)s; if (ptr == NULL) return; @@ -5897,7 +5534,7 @@ void stsz_del(GF_Box *s) } -GF_Err stsz_Read(GF_Box *s, GF_BitStream *bs) +GF_Err stsz_box_read(GF_Box *s, GF_BitStream *bs) { u32 i, estSize; GF_SampleSizeBox *ptr = (GF_SampleSizeBox *)s; @@ -5905,16 +5542,15 @@ GF_Err stsz_Read(GF_Box *s, GF_BitStream *bs) //support for CompactSizes if (s->type == GF_ISOM_BOX_TYPE_STSZ) { + ISOM_DECREASE_SIZE(ptr, 8); ptr->sampleSize = gf_bs_read_u32(bs); ptr->sampleCount = gf_bs_read_u32(bs); - - ISOM_DECREASE_SIZE(ptr, 8); } else { //24-reserved + ISOM_DECREASE_SIZE(ptr, 8); gf_bs_read_int(bs, 24); i = gf_bs_read_u8(bs); ptr->sampleCount = gf_bs_read_u32(bs); - ISOM_DECREASE_SIZE(ptr, 8); switch (i) { case 4: case 8: @@ -5946,10 +5582,14 @@ GF_Err stsz_Read(GF_Box *s, GF_BitStream *bs) return GF_ISOM_INVALID_FILE; } ptr->sizes = (u32 *) gf_malloc(ptr->sampleCount * sizeof(u32)); - ptr->alloc_size = ptr->sampleCount; if (! ptr->sizes) return GF_OUT_OF_MEM; + ptr->alloc_size = ptr->sampleCount; for (i = 0; i < ptr->sampleCount; i++) { ptr->sizes[i] = gf_bs_read_u32(bs); + if (ptr->max_size < ptr->sizes[i]) + ptr->max_size = ptr->sizes[i]; + ptr->total_size += ptr->sizes[i]; + ptr->total_samples++; } } } else { @@ -5988,12 +5628,16 @@ GF_Err stsz_Read(GF_Box *s, GF_BitStream *bs) i += 1; break; } + if (ptr->max_size < ptr->sizes[i]) + ptr->max_size = ptr->sizes[i]; + ptr->total_size += ptr->sizes[i]; + ptr->total_samples++; } } return GF_OK; } -GF_Box *stsz_New() +GF_Box *stsz_box_new() { ISOM_DECL_BOX_ALLOC(GF_SampleSizeBox, 0); @@ -6004,7 +5648,7 @@ GF_Box *stsz_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err stsz_Write(GF_Box *s, GF_BitStream *bs) +GF_Err stsz_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -6050,7 +5694,7 @@ GF_Err stsz_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err stsz_Size(GF_Box *s) +GF_Err stsz_box_size(GF_Box *s) { u32 i, fieldSize, size; GF_SampleSizeBox *ptr = (GF_SampleSizeBox *)s; @@ -6116,7 +5760,7 @@ GF_Err stsz_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void stts_del(GF_Box *s) +void stts_box_del(GF_Box *s) { GF_TimeToSampleBox *ptr = (GF_TimeToSampleBox *)s; if (ptr->entries) gf_free(ptr->entries); @@ -6124,7 +5768,7 @@ void stts_del(GF_Box *s) } -GF_Err stts_Read(GF_Box *s, GF_BitStream *bs) +GF_Err stts_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; GF_TimeToSampleBox *ptr = (GF_TimeToSampleBox *)s; @@ -6132,9 +5776,10 @@ GF_Err stts_Read(GF_Box *s, GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE ptr->w_LastDTS = 0; #endif - ptr->nb_entries = gf_bs_read_u32(bs); + ISOM_DECREASE_SIZE(ptr, 4); - if (ptr->nb_entries > ptr->size / 8) { + ptr->nb_entries = gf_bs_read_u32(bs); + if (ptr->size < ptr->nb_entries * 8) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Invalid number of entries %d in stts\n", ptr->nb_entries)); return GF_ISOM_INVALID_FILE; } @@ -6142,13 +5787,16 @@ GF_Err stts_Read(GF_Box *s, GF_BitStream *bs) ptr->alloc_size = ptr->nb_entries; ptr->entries = gf_malloc(sizeof(GF_SttsEntry)*ptr->alloc_size); if (!ptr->entries) return GF_OUT_OF_MEM; + for (i=0; inb_entries; i++) { ptr->entries[i].sampleCount = gf_bs_read_u32(bs); ptr->entries[i].sampleDelta = gf_bs_read_u32(bs); #ifndef GPAC_DISABLE_ISOM_WRITE ptr->w_currentSampleNum += ptr->entries[i].sampleCount; - ptr->w_LastDTS += ptr->entries[i].sampleCount * ptr->entries[i].sampleDelta; + ptr->w_LastDTS += (u64)ptr->entries[i].sampleCount * ptr->entries[i].sampleDelta; #endif + if (ptr->max_ts_deltaentries[i].sampleDelta) + ptr->max_ts_delta = ptr->entries[i].sampleDelta; if (!ptr->entries[i].sampleDelta) { if ((i+1nb_entries) ) { @@ -6173,7 +5821,7 @@ GF_Err stts_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *stts_New() +GF_Box *stts_box_new() { ISOM_DECL_BOX_ALLOC(GF_TimeToSampleBox, GF_ISOM_BOX_TYPE_STTS); return (GF_Box *)tmp; @@ -6182,7 +5830,7 @@ GF_Box *stts_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err stts_Write(GF_Box *s, GF_BitStream *bs) +GF_Err stts_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -6198,7 +5846,7 @@ GF_Err stts_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err stts_Size(GF_Box *s) +GF_Err stts_box_size(GF_Box *s) { GF_TimeToSampleBox *ptr = (GF_TimeToSampleBox *)s; ptr->size += 4 + (8 * ptr->nb_entries); @@ -6211,39 +5859,45 @@ GF_Err stts_Size(GF_Box *s) #ifndef GPAC_DISABLE_ISOM_FRAGMENTS -void tfhd_del(GF_Box *s) +void tfhd_box_del(GF_Box *s) { GF_TrackFragmentHeaderBox *ptr = (GF_TrackFragmentHeaderBox *)s; if (ptr == NULL) return; gf_free(ptr); } -GF_Err tfhd_Read(GF_Box *s, GF_BitStream *bs) +GF_Err tfhd_box_read(GF_Box *s, GF_BitStream *bs) { GF_TrackFragmentHeaderBox *ptr = (GF_TrackFragmentHeaderBox *)s; + ISOM_DECREASE_SIZE(ptr, 4); ptr->trackID = gf_bs_read_u32(bs); //The rest depends on the flags if (ptr->flags & GF_ISOM_TRAF_BASE_OFFSET) { + ISOM_DECREASE_SIZE(ptr, 8); ptr->base_data_offset = gf_bs_read_u64(bs); } if (ptr->flags & GF_ISOM_TRAF_SAMPLE_DESC) { + ISOM_DECREASE_SIZE(ptr, 4); ptr->sample_desc_index = gf_bs_read_u32(bs); } if (ptr->flags & GF_ISOM_TRAF_SAMPLE_DUR) { + ISOM_DECREASE_SIZE(ptr, 4); ptr->def_sample_duration = gf_bs_read_u32(bs); } if (ptr->flags & GF_ISOM_TRAF_SAMPLE_SIZE) { + ISOM_DECREASE_SIZE(ptr, 4); ptr->def_sample_size = gf_bs_read_u32(bs); } if (ptr->flags & GF_ISOM_TRAF_SAMPLE_FLAGS) { + ISOM_DECREASE_SIZE(ptr, 4); ptr->def_sample_flags = gf_bs_read_u32(bs); } return GF_OK; } -GF_Box *tfhd_New() +GF_Box *tfhd_box_new() { ISOM_DECL_BOX_ALLOC(GF_TrackFragmentHeaderBox, GF_ISOM_BOX_TYPE_TFHD); //NO FLAGS SET BY DEFAULT @@ -6255,7 +5909,7 @@ GF_Box *tfhd_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err tfhd_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tfhd_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TrackFragmentHeaderBox *ptr = (GF_TrackFragmentHeaderBox *) s; @@ -6284,7 +5938,7 @@ GF_Err tfhd_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err tfhd_Size(GF_Box *s) +GF_Err tfhd_box_size(GF_Box *s) { GF_TrackFragmentHeaderBox *ptr = (GF_TrackFragmentHeaderBox *)s; ptr->size += 4; @@ -6303,20 +5957,21 @@ GF_Err tfhd_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_FRAGMENTS*/ -void tims_del(GF_Box *s) +void tims_box_del(GF_Box *s) { GF_TSHintEntryBox *tims = (GF_TSHintEntryBox *)s; gf_free(tims); } -GF_Err tims_Read(GF_Box *s, GF_BitStream *bs) +GF_Err tims_box_read(GF_Box *s, GF_BitStream *bs) { GF_TSHintEntryBox *ptr = (GF_TSHintEntryBox *)s; + ISOM_DECREASE_SIZE(ptr, 4); ptr->timeScale = gf_bs_read_u32(bs); return GF_OK; } -GF_Box *tims_New() +GF_Box *tims_box_new() { ISOM_DECL_BOX_ALLOC(GF_TSHintEntryBox, GF_ISOM_BOX_TYPE_TIMS); return (GF_Box *)tmp; @@ -6324,7 +5979,7 @@ GF_Box *tims_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err tims_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tims_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TSHintEntryBox *ptr = (GF_TSHintEntryBox *)s; @@ -6335,7 +5990,7 @@ GF_Err tims_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err tims_Size(GF_Box *s) +GF_Err tims_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -6344,7 +5999,7 @@ GF_Err tims_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void tkhd_del(GF_Box *s) +void tkhd_box_del(GF_Box *s) { GF_TrackHeaderBox *ptr = (GF_TrackHeaderBox *)s; if (ptr == NULL) return; @@ -6353,23 +6008,28 @@ void tkhd_del(GF_Box *s) } -GF_Err tkhd_Read(GF_Box *s, GF_BitStream *bs) +GF_Err tkhd_box_read(GF_Box *s, GF_BitStream *bs) { GF_TrackHeaderBox *ptr = (GF_TrackHeaderBox *)s; if (ptr->version == 1) { + ISOM_DECREASE_SIZE(ptr, 32); ptr->creationTime = gf_bs_read_u64(bs); ptr->modificationTime = gf_bs_read_u64(bs); ptr->trackID = gf_bs_read_u32(bs); ptr->reserved1 = gf_bs_read_u32(bs); ptr->duration = gf_bs_read_u64(bs); } else { + ISOM_DECREASE_SIZE(ptr, 20); ptr->creationTime = gf_bs_read_u32(bs); ptr->modificationTime = gf_bs_read_u32(bs); ptr->trackID = gf_bs_read_u32(bs); ptr->reserved1 = gf_bs_read_u32(bs); ptr->duration = gf_bs_read_u32(bs); } + ptr->initial_duration = ptr->duration; + + ISOM_DECREASE_SIZE(ptr, 60); ptr->reserved2[0] = gf_bs_read_u32(bs); ptr->reserved2[1] = gf_bs_read_u32(bs); ptr->layer = gf_bs_read_u16(bs); @@ -6390,7 +6050,7 @@ GF_Err tkhd_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *tkhd_New() +GF_Box *tkhd_box_new() { ISOM_DECL_BOX_ALLOC(GF_TrackHeaderBox, GF_ISOM_BOX_TYPE_TKHD); tmp->matrix[0] = 0x00010000; @@ -6403,7 +6063,7 @@ GF_Box *tkhd_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err tkhd_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tkhd_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TrackHeaderBox *ptr = (GF_TrackHeaderBox *)s; @@ -6443,7 +6103,7 @@ GF_Err tkhd_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err tkhd_Size(GF_Box *s) +GF_Err tkhd_box_size(GF_Box *s) { GF_TrackHeaderBox *ptr = (GF_TrackHeaderBox *)s; @@ -6460,41 +6120,37 @@ GF_Err tkhd_Size(GF_Box *s) #ifndef GPAC_DISABLE_ISOM_FRAGMENTS -void traf_del(GF_Box *s) +void traf_box_del(GF_Box *s) { GF_TrackFragmentBox *ptr = (GF_TrackFragmentBox *)s; if (ptr == NULL) return; - if (ptr->tfhd) gf_isom_box_del((GF_Box *) ptr->tfhd); - if (ptr->sdtp) gf_isom_box_del((GF_Box *) ptr->sdtp); - if (ptr->sub_samples) gf_isom_box_array_del(ptr->sub_samples); - if (ptr->tfdt) gf_isom_box_del((GF_Box *) ptr->tfdt); - if (ptr->sample_encryption) gf_isom_box_del((GF_Box *) ptr->sample_encryption); - gf_isom_box_array_del(ptr->TrackRuns); - if (ptr->sampleGroups) gf_isom_box_array_del(ptr->sampleGroups); - if (ptr->sampleGroupsDescription) gf_isom_box_array_del(ptr->sampleGroupsDescription); - if (ptr->sai_sizes) gf_isom_box_array_del(ptr->sai_sizes); - if (ptr->sai_offsets) gf_isom_box_array_del(ptr->sai_offsets); + if (ptr->sub_samples) gf_list_del(ptr->sub_samples); + gf_list_del(ptr->TrackRuns); + if (ptr->sampleGroups) gf_list_del(ptr->sampleGroups); + if (ptr->sampleGroupsDescription) gf_list_del(ptr->sampleGroupsDescription); + if (ptr->sai_sizes) gf_list_del(ptr->sai_sizes); + if (ptr->sai_offsets) gf_list_del(ptr->sai_offsets); gf_free(ptr); } -GF_Err traf_AddBox(GF_Box *s, GF_Box *a) +GF_Err traf_on_child_box(GF_Box *s, GF_Box *a) { GF_TrackFragmentBox *ptr = (GF_TrackFragmentBox *)s; switch (a->type) { case GF_ISOM_BOX_TYPE_TFHD: if (ptr->tfhd) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->tfhd = (GF_TrackFragmentHeaderBox *) a; + ptr->tfhd = (GF_TrackFragmentHeaderBox *) a; return GF_OK; case GF_ISOM_BOX_TYPE_TRUN: return gf_list_add(ptr->TrackRuns, a); case GF_ISOM_BOX_TYPE_SDTP: if (ptr->sdtp) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->sdtp = (GF_SampleDependencyTypeBox *)a; + ptr->sdtp = (GF_SampleDependencyTypeBox *)a; return GF_OK; case GF_ISOM_BOX_TYPE_TFDT: if (ptr->tfdt) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->tfdt = (GF_TFBaseMediaDecodeTimeBox*) a; + ptr->tfdt = (GF_TFBaseMediaDecodeTimeBox*) a; return GF_OK; case GF_ISOM_BOX_TYPE_SUBS: if (!ptr->sub_samples) ptr->sub_samples = gf_list_new(); @@ -6523,24 +6179,22 @@ GF_Err traf_AddBox(GF_Box *s, GF_Box *a) ptr->sample_encryption->traf = ptr; return GF_OK; } else { - return gf_isom_box_add_default(s, a); + return GF_OK; } case GF_ISOM_BOX_TYPE_SENC: if (ptr->sample_encryption) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->sample_encryption = (GF_SampleEncryptionBox *)a; ptr->sample_encryption->traf = ptr; return GF_OK; - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err traf_Read(GF_Box *s, GF_BitStream *bs) +GF_Err traf_box_read(GF_Box *s, GF_BitStream *bs) { GF_TrackFragmentBox *ptr = (GF_TrackFragmentBox *)s; - GF_Err e = gf_isom_box_array_read(s, bs, traf_AddBox); + GF_Err e = gf_isom_box_array_read(s, bs, traf_on_child_box); if (e) return e; if (!ptr->tfhd) { @@ -6550,7 +6204,7 @@ GF_Err traf_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *traf_New() +GF_Box *traf_box_new() { ISOM_DECL_BOX_ALLOC(GF_TrackFragmentBox, GF_ISOM_BOX_TYPE_TRAF); tmp->TrackRuns = gf_list_new(); @@ -6561,31 +6215,32 @@ GF_Box *traf_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Box *tfxd_New() +GF_Box *tfxd_box_new() { ISOM_DECL_BOX_ALLOC(GF_MSSTimeExtBox, GF_ISOM_BOX_TYPE_UUID); tmp->internal_4cc = GF_ISOM_BOX_UUID_TFXD; return (GF_Box *)tmp; } -void tfxd_del(GF_Box *s) +void tfxd_box_del(GF_Box *s) { gf_free(s); } -GF_Err tfxd_Read(GF_Box *s, GF_BitStream *bs) +GF_Err tfxd_box_read(GF_Box *s, GF_BitStream *bs) { GF_MSSTimeExtBox *ptr = (GF_MSSTimeExtBox *)s; - if (ptr->size<4) return GF_ISOM_INVALID_FILE; + ISOM_DECREASE_SIZE(ptr, 4); ptr->version = gf_bs_read_u8(bs); ptr->flags = gf_bs_read_u24(bs); - ISOM_DECREASE_SIZE(ptr, 4); if (ptr->version == 0x01) { + ISOM_DECREASE_SIZE(ptr, 16); ptr->absolute_time_in_track_timescale = gf_bs_read_u64(bs); ptr->fragment_duration_in_track_timescale = gf_bs_read_u64(bs); } else { + ISOM_DECREASE_SIZE(ptr, 8); ptr->absolute_time_in_track_timescale = gf_bs_read_u32(bs); ptr->fragment_duration_in_track_timescale = gf_bs_read_u32(bs); } @@ -6593,9 +6248,9 @@ GF_Err tfxd_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err tfxd_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tfxd_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e = GF_OK; + GF_Err e; GF_MSSTimeExtBox *uuid = (GF_MSSTimeExtBox*)s; e = gf_isom_box_write_header(s, bs); if (e) return e; @@ -6608,121 +6263,43 @@ GF_Err tfxd_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err tfxd_Size(GF_Box *s) +GF_Err tfxd_box_size(GF_Box *s) { s->size += 20; return GF_OK; } -GF_Err traf_Write(GF_Box *s, GF_BitStream *bs) +GF_Err traf_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_TrackFragmentBox *ptr = (GF_TrackFragmentBox *) s; - if (!s) return GF_BAD_PARAM; + return gf_isom_box_write_header(s, bs); +} - e = gf_isom_box_write_header(s, bs); - if (e) return e; +GF_Err traf_box_size(GF_Box *s) +{ + u32 pos=0; + GF_TrackFragmentBox *ptr = (GF_TrackFragmentBox *) s; //Header first - if (ptr->tfhd) { - e = gf_isom_box_write((GF_Box *) ptr->tfhd, bs); - if (e) return e; - } - if (ptr->sub_samples) { - e = gf_isom_box_array_write(s, ptr->sub_samples, bs); - if (e) return e; - } - if (ptr->tfdt) { - e = gf_isom_box_write((GF_Box *) ptr->tfdt, bs); - if (e) return e; - } - if (ptr->sdtp) { - e = gf_isom_box_write((GF_Box *) ptr->sdtp, bs); - if (e) return e; - } - if (ptr->sampleGroupsDescription) { - e = gf_isom_box_array_write(s, ptr->sampleGroupsDescription, bs); - if (e) return e; - } - if (ptr->sampleGroups) { - e = gf_isom_box_array_write(s, ptr->sampleGroups, bs); - if (e) return e; - } - if (ptr->sai_sizes) { - e = gf_isom_box_array_write(s, ptr->sai_sizes, bs); - if (e) return e; - } - if (ptr->sai_offsets) { - e = gf_isom_box_array_write(s, ptr->sai_offsets, bs); - if (e) return e; - } - e = gf_isom_box_array_write(s, ptr->TrackRuns, bs); - if (e) return e; + gf_isom_check_position(s, (GF_Box *)ptr->tfhd, &pos); + gf_isom_check_position_list(s, ptr->sub_samples, &pos); - if (ptr->sample_encryption) { - e = gf_isom_box_write((GF_Box *) ptr->sample_encryption, bs); - if (e) return e; - } - //tfxd should be last ... - if (ptr->tfxd) { - e = gf_isom_box_write((GF_Box *) ptr->tfxd, bs); - if (e) return e; - } - return GF_OK; -} + gf_isom_check_position(s, (GF_Box *)ptr->tfdt, &pos); + gf_isom_check_position_list(s, ptr->sampleGroupsDescription, &pos); + gf_isom_check_position_list(s, ptr->sampleGroups, &pos); + gf_isom_check_position_list(s, ptr->sai_sizes, &pos); + gf_isom_check_position_list(s, ptr->sai_offsets, &pos); -GF_Err traf_Size(GF_Box *s) -{ - GF_Err e; - GF_TrackFragmentBox *ptr = (GF_TrackFragmentBox *)s; + gf_isom_check_position(s, (GF_Box *)ptr->sample_encryption, &pos); - if (ptr->tfhd) { - e = gf_isom_box_size((GF_Box *) ptr->tfhd); - if (e) return e; - ptr->size += ptr->tfhd->size; - } - if (ptr->sub_samples) { - e = gf_isom_box_array_size(s, ptr->sub_samples); - if (e) return e; - } - if (ptr->sdtp) { - e = gf_isom_box_size((GF_Box *) ptr->sdtp); - if (e) return e; - ptr->size += ptr->sdtp->size; - } - if (ptr->tfdt) { - e = gf_isom_box_size((GF_Box *) ptr->tfdt); - if (e) return e; - ptr->size += ptr->tfdt->size; - } + gf_isom_check_position_list(s, ptr->TrackRuns, &pos); - if (ptr->sampleGroups) { - e = gf_isom_box_array_size(s, ptr->sampleGroups); - if (e) return e; - } - if (ptr->sampleGroupsDescription) { - e = gf_isom_box_array_size(s, ptr->sampleGroupsDescription); - if (e) return e; - } - if (ptr->sai_sizes) { - e = gf_isom_box_array_size(s, ptr->sai_sizes); - if (e) return e; - } - if (ptr->sai_offsets) { - e = gf_isom_box_array_size(s, ptr->sai_offsets); - if (e) return e; - } - if (ptr->sample_encryption) { - e = gf_isom_box_size((GF_Box *) ptr->sample_encryption); - if (e) return e; - ptr->size += ptr->sample_encryption->size; - } - if (ptr->tfxd) { - e = gf_isom_box_size((GF_Box *)ptr->tfxd); - if (e) return e; - s->size += ptr->tfxd->size; - } - return gf_isom_box_array_size(s, ptr->TrackRuns); + //when sdtp is present (smooth-like) write it after the trun box + gf_isom_check_position(s, (GF_Box *)ptr->sdtp, &pos); + + //tfxd should be last ... + if (ptr->tfxd) + gf_isom_check_position(s, (GF_Box *)ptr->tfxd, &pos); + return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ @@ -6730,20 +6307,14 @@ GF_Err traf_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_FRAGMENTS*/ -void trak_del(GF_Box *s) +void trak_box_del(GF_Box *s) { - GF_TrackBox *ptr = (GF_TrackBox *) s; - if (ptr == NULL) return; - - if (ptr->Header) gf_isom_box_del((GF_Box *)ptr->Header); - if (ptr->udta) gf_isom_box_del((GF_Box *)ptr->udta); - if (ptr->Media) gf_isom_box_del((GF_Box *)ptr->Media); - if (ptr->References) gf_isom_box_del((GF_Box *)ptr->References); - if (ptr->editBox) gf_isom_box_del((GF_Box *)ptr->editBox); - if (ptr->meta) gf_isom_box_del((GF_Box *)ptr->meta); - if (ptr->name) gf_free(ptr->name); - if (ptr->groups) gf_isom_box_del((GF_Box *)ptr->groups); - gf_free(ptr); +#ifndef GPAC_DISABLE_ISOM_WRITE + GF_TrackBox *ptr = (GF_TrackBox *)s; + if (ptr->chunk_cache) + gf_bs_del(ptr->chunk_cache); +#endif + gf_free(s); } static void gf_isom_check_sample_desc(GF_TrackBox *trak) @@ -6751,6 +6322,8 @@ static void gf_isom_check_sample_desc(GF_TrackBox *trak) GF_BitStream *bs; GF_UnknownBox *a; u32 i; + GF_Err e; + GF_SampleTableBox *stbl; if (!trak->Media || !trak->Media->information) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Track with no media box !\n" )); @@ -6758,17 +6331,18 @@ static void gf_isom_check_sample_desc(GF_TrackBox *trak) } if (!trak->Media->information->sampleTable) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Track with no sample table !\n" )); - trak->Media->information->sampleTable = (GF_SampleTableBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_STBL); + trak->Media->information->sampleTable = (GF_SampleTableBox *) gf_isom_box_new_parent(&trak->Media->information->child_boxes, GF_ISOM_BOX_TYPE_STBL); } + stbl = trak->Media->information->sampleTable; - if (!trak->Media->information->sampleTable->SampleDescription) { + if (!stbl->SampleDescription) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Track with no sample description box !\n" )); - trak->Media->information->sampleTable->SampleDescription = (GF_SampleDescriptionBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_STSD); + stbl->SampleDescription = (GF_SampleDescriptionBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSD); return; } i=0; - while ((a = (GF_UnknownBox*)gf_list_enum(trak->Media->information->sampleTable->SampleDescription->other_boxes, &i))) { + while ((a = (GF_UnknownBox*)gf_list_enum(trak->Media->information->sampleTable->SampleDescription->child_boxes, &i))) { switch (a->type) { case GF_ISOM_BOX_TYPE_MP4S: case GF_ISOM_BOX_TYPE_ENCS: @@ -6805,10 +6379,15 @@ static void gf_isom_check_sample_desc(GF_TrackBox *trak) case GF_ISOM_BOX_TYPE_HVT1: case GF_ISOM_BOX_TYPE_LHV1: case GF_ISOM_BOX_TYPE_LHE1: + case GF_ISOM_BOX_TYPE_AV01: + case GF_ISOM_BOX_TYPE_VP08: + case GF_ISOM_BOX_TYPE_VP09: + case GF_ISOM_BOX_TYPE_AV1C: case GF_ISOM_BOX_TYPE_TX3G: case GF_ISOM_BOX_TYPE_TEXT: case GF_ISOM_BOX_TYPE_ENCT: case GF_ISOM_BOX_TYPE_DIMS: + case GF_ISOM_BOX_TYPE_OPUS: case GF_ISOM_BOX_TYPE_AC3: case GF_ISOM_BOX_TYPE_EC3: case GF_ISOM_BOX_TYPE_LSR1: @@ -6816,10 +6395,51 @@ static void gf_isom_check_sample_desc(GF_TrackBox *trak) case GF_ISOM_BOX_TYPE_STPP: case GF_ISOM_BOX_TYPE_SBTT: case GF_ISOM_BOX_TYPE_MP3: + case GF_ISOM_BOX_TYPE_JPEG: + case GF_ISOM_BOX_TYPE_PNG: + case GF_ISOM_BOX_TYPE_JP2K: + case GF_ISOM_BOX_TYPE_MHA1: + case GF_ISOM_BOX_TYPE_MHA2: + case GF_ISOM_BOX_TYPE_MHM1: + case GF_ISOM_BOX_TYPE_MHM2: + case GF_ISOM_BOX_TYPE_MJP2: + case GF_QT_SUBTYPE_RAW_AUD: + case GF_QT_SUBTYPE_TWOS: + case GF_QT_SUBTYPE_SOWT: + case GF_QT_SUBTYPE_FL32: + case GF_QT_SUBTYPE_FL64: + case GF_QT_SUBTYPE_IN24: + case GF_QT_SUBTYPE_IN32: + case GF_QT_SUBTYPE_ULAW: + case GF_QT_SUBTYPE_ALAW: + case GF_QT_SUBTYPE_ADPCM: + case GF_QT_SUBTYPE_IMA_ADPCM: + case GF_QT_SUBTYPE_DVCA: + case GF_QT_SUBTYPE_QDMC: + case GF_QT_SUBTYPE_QDMC2: + case GF_QT_SUBTYPE_QCELP: + case GF_QT_SUBTYPE_kMP3: + case GF_QT_SUBTYPE_RAW_VID: + case GF_QT_SUBTYPE_APCH: + case GF_QT_SUBTYPE_APCO: + case GF_QT_SUBTYPE_APCN: + case GF_QT_SUBTYPE_APCS: + case GF_QT_SUBTYPE_AP4X: + case GF_QT_SUBTYPE_AP4H: + case GF_QT_SUBTYPE_YUV422: + case GF_QT_SUBTYPE_YUV444: + case GF_QT_SUBTYPE_YUV422_10: + case GF_QT_SUBTYPE_YUV444_10: + case GF_ISOM_BOX_TYPE_IPCM: + case GF_ISOM_BOX_TYPE_FPCM: continue; + case GF_ISOM_BOX_TYPE_UNKNOWN: break; default: + if (gf_box_valid_in_parent((GF_Box *) a, "stsd")) { + continue; + } GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Unexpected box %s in stsd!\n", gf_4cc_to_str(a->type))); continue; } @@ -6835,125 +6455,75 @@ static void gf_isom_check_sample_desc(GF_TrackBox *trak) continue; } - /*only process visual or audio*/ +#define STSD_SWITCH_BOX(_box) \ + if (gf_bs_available(bs)) { \ + u64 pos = gf_bs_get_position(bs); \ + u32 count_subb = 0; \ + gf_bs_set_cookie(bs, GF_ISOM_BS_COOKIE_NO_LOGS);\ + e = gf_isom_box_array_read((GF_Box *) _box, bs, NULL); \ + count_subb = _box->child_boxes ? gf_list_count(_box->child_boxes) : 0; \ + if (!count_subb || e) { \ + gf_bs_seek(bs, pos); \ + _box->data_size = (u32) gf_bs_available(bs); \ + if (_box->data_size) { \ + _box->data = a->data; \ + a->data = NULL; \ + memmove(_box->data, _box->data + pos, _box->data_size); \ + } \ + } else { \ + _box->data_size = 0; \ + } \ + } \ + gf_bs_del(bs); \ + if (!_box->data_size && _box->data) { \ + gf_free(_box->data); \ + _box->data = NULL; \ + } \ + _box->size = 0; \ + _box->EntryType = a->original_4cc; \ + gf_list_rem(trak->Media->information->sampleTable->SampleDescription->child_boxes, i-1); \ + gf_isom_box_del((GF_Box *)a); \ + gf_list_insert(trak->Media->information->sampleTable->SampleDescription->child_boxes, _box, i-1); \ + + + /*only process visual or audio + note: no need for new_box_parent here since we always store sample descriptions in child_boxes*/ switch (trak->Media->handler->handlerType) { - case GF_ISOM_MEDIA_VISUAL: + case GF_ISOM_MEDIA_VISUAL: + case GF_ISOM_MEDIA_AUXV: + case GF_ISOM_MEDIA_PICT: { - GF_GenericVisualSampleEntryBox *genv; - /*remove entry*/ - gf_list_rem(trak->Media->information->sampleTable->SampleDescription->other_boxes, i-1); - genv = (GF_GenericVisualSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_GNRV); + GF_GenericVisualSampleEntryBox *genv = (GF_GenericVisualSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_GNRV); bs = gf_bs_new(a->data, a->dataSize, GF_BITSTREAM_READ); genv->size = a->size-8; gf_isom_video_sample_entry_read((GF_VisualSampleEntryBox *) genv, bs); - if (gf_bs_available(bs)) { - u64 pos = gf_bs_get_position(bs); - //try to parse as boxes - GF_Err e = gf_isom_box_array_read((GF_Box *) genv, bs, gf_isom_box_add_default); - if (e) { - gf_bs_seek(bs, pos); - genv->data_size = (u32) gf_bs_available(bs); - if (genv->data_size) { - genv->data = a->data; - a->data = NULL; - memmove(genv->data, genv->data + pos, genv->data_size); - } - } else { - genv->data_size = 0; - } - } - gf_bs_del(bs); - if (!genv->data_size && genv->data) { - gf_free(genv->data); - genv->data = NULL; - } + STSD_SWITCH_BOX(genv) - genv->size = 0; - genv->EntryType = a->original_4cc; - gf_isom_box_del((GF_Box *)a); - gf_list_insert(trak->Media->information->sampleTable->SampleDescription->other_boxes, genv, i-1); } break; case GF_ISOM_MEDIA_AUDIO: { - GF_GenericAudioSampleEntryBox *gena; - /*remove entry*/ - gf_list_rem(trak->Media->information->sampleTable->SampleDescription->other_boxes, i-1); - gena = (GF_GenericAudioSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_GNRA); + GF_GenericAudioSampleEntryBox *gena = (GF_GenericAudioSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_GNRA); gena->size = a->size-8; bs = gf_bs_new(a->data, a->dataSize, GF_BITSTREAM_READ); gf_isom_audio_sample_entry_read((GF_AudioSampleEntryBox *) gena, bs); - if (gf_bs_available(bs)) { - u64 pos = gf_bs_get_position(bs); - //try to parse as boxes - GF_Err e = gf_isom_box_array_read((GF_Box *) gena, bs, gf_isom_box_add_default); - if (e) { - gf_bs_seek(bs, pos); - gena->data_size = (u32) gf_bs_available(bs); - if (gena->data_size) { - gena->data = a->data; - a->data = NULL; - memmove(gena->data, gena->data + pos, gena->data_size); - } - } else { - gena->data_size = 0; - } - } - gf_bs_del(bs); - if (!gena->data_size && gena->data) { - gf_free(gena->data); - gena->data = NULL; - } - gena->size = 0; - gena->EntryType = a->original_4cc; - gf_isom_box_del((GF_Box *)a); - gf_list_insert(trak->Media->information->sampleTable->SampleDescription->other_boxes, gena, i-1); + STSD_SWITCH_BOX(gena) + } break; default: { - GF_Err e; - GF_GenericSampleEntryBox *genm; - /*remove entry*/ - gf_list_rem(trak->Media->information->sampleTable->SampleDescription->other_boxes, i-1); - genm = (GF_GenericSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_GNRM); + GF_GenericSampleEntryBox *genm = (GF_GenericSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_GNRM); genm->size = a->size-8; bs = gf_bs_new(a->data, a->dataSize, GF_BITSTREAM_READ); e = gf_isom_base_sample_entry_read((GF_SampleEntryBox *)genm, bs); if (e) return; - genm->size -= 8; - - if (gf_bs_available(bs)) { - u64 pos = gf_bs_get_position(bs); - //try to parse as boxes - GF_Err e = gf_isom_box_array_read((GF_Box *) genm, bs, gf_isom_box_add_default); - if (e) { - gf_bs_seek(bs, pos); - genm->data_size = (u32) gf_bs_available(bs); - if (genm->data_size) { - genm->data = a->data; - a->data = NULL; - memmove(genm->data, genm->data + pos, genm->data_size); - } - } else { - genm->data_size = 0; - } - } - gf_bs_del(bs); - if (!genm->data_size && genm->data) { - gf_free(genm->data); - genm->data = NULL; - } - genm->size = 0; - - genm->EntryType = a->original_4cc; - gf_isom_box_del((GF_Box *)a); - gf_list_insert(trak->Media->information->sampleTable->SampleDescription->other_boxes, genm, i-1); + STSD_SWITCH_BOX(genm) } break; } @@ -6962,61 +6532,63 @@ static void gf_isom_check_sample_desc(GF_TrackBox *trak) } -GF_Err trak_AddBox(GF_Box *s, GF_Box *a) +GF_Err trak_on_child_box(GF_Box *s, GF_Box *a) { GF_TrackBox *ptr = (GF_TrackBox *)s; if (!a) return GF_OK; switch(a->type) { case GF_ISOM_BOX_TYPE_TKHD: if (ptr->Header) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->Header = (GF_TrackHeaderBox *)a; + ptr->Header = (GF_TrackHeaderBox *)a; return GF_OK; case GF_ISOM_BOX_TYPE_EDTS: if (ptr->editBox) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->editBox = (GF_EditBox *)a; + ptr->editBox = (GF_EditBox *)a; return GF_OK; case GF_ISOM_BOX_TYPE_UDTA: if (ptr->udta) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->udta = (GF_UserDataBox *)a; + ptr->udta = (GF_UserDataBox *)a; return GF_OK; case GF_ISOM_BOX_TYPE_META: if (ptr->meta) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->meta = (GF_MetaBox *)a; + ptr->meta = (GF_MetaBox *)a; return GF_OK; case GF_ISOM_BOX_TYPE_TREF: if (ptr->References) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->References = (GF_TrackReferenceBox *)a; + ptr->References = (GF_TrackReferenceBox *)a; return GF_OK; case GF_ISOM_BOX_TYPE_MDIA: if (ptr->Media) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->Media = (GF_MediaBox *)a; + ptr->Media = (GF_MediaBox *)a; ((GF_MediaBox *)a)->mediaTrack = ptr; return GF_OK; case GF_ISOM_BOX_TYPE_TRGR: if (ptr->groups) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->groups = (GF_TrackGroupBox *)a; return GF_OK; + case GF_QT_BOX_TYPE_TAPT: + if (ptr->Aperture) ERROR_ON_DUPLICATED_BOX(a, ptr) + ptr->Aperture = (GF_Box *)a; + return GF_OK; case GF_ISOM_BOX_TYPE_SENC: ptr->sample_encryption = (GF_SampleEncryptionBox*)a; - return gf_isom_box_add_default((GF_Box *)ptr, a); + return GF_OK; case GF_ISOM_BOX_TYPE_UUID: if (((GF_UnknownUUIDBox *)a)->internal_4cc == GF_ISOM_BOX_UUID_PSEC) { ptr->sample_encryption = (GF_SampleEncryptionBox*) a; - return gf_isom_box_add_default((GF_Box *)ptr, a); + return GF_OK; } - - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err trak_Read(GF_Box *s, GF_BitStream *bs) +GF_Err trak_box_read(GF_Box *s, GF_BitStream *bs) { GF_Err e; + u32 i; GF_TrackBox *ptr = (GF_TrackBox *)s; - e = gf_isom_box_array_read(s, bs, trak_AddBox); + e = gf_isom_box_array_read(s, bs, trak_on_child_box); if (e) return e; gf_isom_check_sample_desc(ptr); @@ -7028,11 +6600,32 @@ GF_Err trak_Read(GF_Box *s, GF_BitStream *bs) GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Missing MediaBox\n")); return GF_ISOM_INVALID_FILE; } + if (!ptr->Media->information || !ptr->Media->information->sampleTable) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Invalid MediaBox\n")); + return GF_ISOM_INVALID_FILE; + } + if (!ptr->Media->information->sampleTable->SampleSize || (ptr->Media->information->sampleTable->SampleSize->sampleCount==0)) { + if (ptr->Header->initial_duration) { + GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[iso file] Track with no samples but duration defined, ignoring duration\n")); + ptr->Header->initial_duration = 0; + } + } + for (i=0; iMedia->information->sampleTable->child_boxes); i++) { + GF_Box *a = gf_list_get(ptr->Media->information->sampleTable->child_boxes, i); + if ((a->type ==GF_ISOM_BOX_TYPE_UUID) && (((GF_UUIDBox *)a)->internal_4cc == GF_ISOM_BOX_UUID_PSEC)) { + ptr->sample_encryption = (struct __sample_encryption_box *) a; + break; + } + else if (a->type == GF_ISOM_BOX_TYPE_SENC) { + ptr->sample_encryption = (struct __sample_encryption_box *)a; + break; + } + } return e; } -GF_Box *trak_New() +GF_Box *trak_box_new() { ISOM_DECL_BOX_ALLOC(GF_TrackBox, GF_ISOM_BOX_TYPE_TRAK); return (GF_Box *)tmp; @@ -7041,90 +6634,35 @@ GF_Box *trak_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err trak_Write(GF_Box *s, GF_BitStream *bs) +GF_Err trak_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_TrackBox *ptr = (GF_TrackBox *)s; - e = gf_isom_box_write_header(s, bs); - if (e) return e; - - if (ptr->Header) { - e = gf_isom_box_write((GF_Box *) ptr->Header, bs); - if (e) return e; - } - if (ptr->References) { - e = gf_isom_box_write((GF_Box *) ptr->References, bs); - if (e) return e; - } - if (ptr->editBox) { - e = gf_isom_box_write((GF_Box *) ptr->editBox, bs); - if (e) return e; - } - if (ptr->Media) { - e = gf_isom_box_write((GF_Box *) ptr->Media, bs); - if (e) return e; - } - if (ptr->meta) { - e = gf_isom_box_write((GF_Box *) ptr->meta, bs); - if (e) return e; - } - if (ptr->groups) { - e = gf_isom_box_write((GF_Box *) ptr->groups, bs); - if (e) return e; - } - if (ptr->udta) { - e = gf_isom_box_write((GF_Box *) ptr->udta, bs); - if (e) return e; - } - return GF_OK; + return gf_isom_box_write_header(s, bs); } -GF_Err trak_Size(GF_Box *s) +GF_Err trak_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_TrackBox *ptr = (GF_TrackBox *)s; - if (ptr->Header) { - e = gf_isom_box_size((GF_Box *) ptr->Header); - if (e) return e; - ptr->size += ptr->Header->size; - } - if (ptr->udta) { - e = gf_isom_box_size((GF_Box *) ptr->udta); - if (e) return e; - ptr->size += ptr->udta->size; - } - if (ptr->References) { - e = gf_isom_box_size((GF_Box *) ptr->References); - if (e) return e; - ptr->size += ptr->References->size; - } - if (ptr->editBox) { - e = gf_isom_box_size((GF_Box *) ptr->editBox); - if (e) return e; - ptr->size += ptr->editBox->size; - } - if (ptr->Media) { - e = gf_isom_box_size((GF_Box *) ptr->Media); - if (e) return e; - ptr->size += ptr->Media->size; - } - if (ptr->meta) { - e = gf_isom_box_size((GF_Box *) ptr->meta); - if (e) return e; - ptr->size += ptr->meta->size; - } - if (ptr->groups) { - e = gf_isom_box_size((GF_Box *) ptr->groups); + if (ptr->sample_encryption && ptr->sample_encryption->load_needed) { + GF_Err e = senc_Parse(ptr->moov->mov->movieFileMap->bs, ptr, NULL, ptr->sample_encryption); if (e) return e; - ptr->size += ptr->groups->size; } + + gf_isom_check_position(s, (GF_Box *)ptr->Header, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->Aperture, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->References, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->editBox, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->Media, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->meta, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->groups, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->udta, &pos); return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void stri_del(GF_Box *s) +void stri_box_del(GF_Box *s) { GF_SubTrackInformationBox *ptr = (GF_SubTrackInformationBox *)s; if (ptr == NULL) return; @@ -7132,24 +6670,25 @@ void stri_del(GF_Box *s) gf_free(ptr); } -GF_Err stri_Read(GF_Box *s, GF_BitStream *bs) +GF_Err stri_box_read(GF_Box *s, GF_BitStream *bs) { size_t i; GF_SubTrackInformationBox *ptr = (GF_SubTrackInformationBox *)s; + ISOM_DECREASE_SIZE(ptr, 8) ptr->switch_group = gf_bs_read_u16(bs); ptr->alternate_group = gf_bs_read_u16(bs); ptr->sub_track_id = gf_bs_read_u32(bs); - ptr->size -= 8; ptr->attribute_count = ptr->size / 4; GF_SAFE_ALLOC_N(ptr->attribute_list, (size_t)ptr->attribute_count, u32); if (!ptr->attribute_list) return GF_OUT_OF_MEM; for (i = 0; i < ptr->attribute_count; i++) { + ISOM_DECREASE_SIZE(ptr, 4) ptr->attribute_list[i] = gf_bs_read_u32(bs); } return GF_OK; } -GF_Box *stri_New() +GF_Box *stri_box_new() { ISOM_DECL_BOX_ALLOC(GF_SubTrackInformationBox, GF_ISOM_BOX_TYPE_STRI); return (GF_Box *)tmp; @@ -7158,7 +6697,7 @@ GF_Box *stri_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err stri_Write(GF_Box *s, GF_BitStream *bs) +GF_Err stri_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -7175,7 +6714,7 @@ GF_Err stri_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err stri_Size(GF_Box *s) +GF_Err stri_box_size(GF_Box *s) { GF_SubTrackInformationBox *ptr = (GF_SubTrackInformationBox *)s; @@ -7185,7 +6724,7 @@ GF_Err stri_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void stsg_del(GF_Box *s) +void stsg_box_del(GF_Box *s) { GF_SubTrackSampleGroupBox *ptr = (GF_SubTrackSampleGroupBox *)s; if (ptr == NULL) return; @@ -7193,7 +6732,7 @@ void stsg_del(GF_Box *s) gf_free(ptr); } -GF_Err stsg_Read(GF_Box *s, GF_BitStream *bs) +GF_Err stsg_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; GF_SubTrackSampleGroupBox *ptr = (GF_SubTrackSampleGroupBox *)s; @@ -7209,7 +6748,7 @@ GF_Err stsg_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *stsg_New() +GF_Box *stsg_box_new() { ISOM_DECL_BOX_ALLOC(GF_SubTrackSampleGroupBox, GF_ISOM_BOX_TYPE_STSG); return (GF_Box *)tmp; @@ -7218,7 +6757,7 @@ GF_Box *stsg_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err stsg_Write(GF_Box *s, GF_BitStream *bs) +GF_Err stsg_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -7234,7 +6773,7 @@ GF_Err stsg_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err stsg_Size(GF_Box *s) +GF_Err stsg_box_size(GF_Box *s) { GF_SubTrackSampleGroupBox *ptr = (GF_SubTrackSampleGroupBox *)s; ptr->size += 6 + 4 * ptr->nb_groups; @@ -7244,49 +6783,44 @@ GF_Err stsg_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void strk_del(GF_Box *s) +void strk_box_del(GF_Box *s) { - GF_SubTrackBox *ptr = (GF_SubTrackBox *)s; - if (ptr == NULL) return; - if (ptr->info) gf_isom_box_del((GF_Box *)ptr->info); - gf_free(ptr); + gf_free(s); } -GF_Err strk_AddBox(GF_Box *s, GF_Box *a) +GF_Err strk_on_child_box(GF_Box *s, GF_Box *a) { GF_SubTrackBox *ptr = (GF_SubTrackBox *)s; if (!a) return GF_OK; switch (a->type) { case GF_ISOM_BOX_TYPE_STRI: if (ptr->info) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->info = (GF_SubTrackInformationBox *)a; + ptr->info = (GF_SubTrackInformationBox *)a; return GF_OK; case GF_ISOM_BOX_TYPE_STRD: if (ptr->strd) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->strd = a; + ptr->strd = a; return GF_OK; - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err strk_Read(GF_Box *s, GF_BitStream *bs) +GF_Err strk_box_read(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_SubTrackBox *ptr = (GF_SubTrackBox *)s; - e = gf_isom_box_array_read(s, bs, strk_AddBox); + e = gf_isom_box_array_read(s, bs, strk_on_child_box); if (e) return e; if (!ptr->info) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Missing SubTrackInformationBox\n")); return GF_ISOM_INVALID_FILE; } - return e; + return GF_OK; } -GF_Box *strk_New() +GF_Box *strk_box_new() { ISOM_DECL_BOX_ALLOC(GF_SubTrackBox, GF_ISOM_BOX_TYPE_STRK); return (GF_Box *)tmp; @@ -7295,41 +6829,19 @@ GF_Box *strk_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err strk_Write(GF_Box *s, GF_BitStream *bs) +GF_Err strk_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_SubTrackBox *ptr = (GF_SubTrackBox *)s; - e = gf_isom_box_write_header(s, bs); - if (e) return e; - - if (ptr->info) { - e = gf_isom_box_write((GF_Box *)ptr->info, bs); - if (e) return e; - } - return GF_OK; + return gf_isom_box_write_header(s, bs); } -GF_Err strk_Size(GF_Box *s) +GF_Err strk_box_size(GF_Box *s) { - GF_Err e; - GF_SubTrackBox *ptr = (GF_SubTrackBox *)s; - - if (ptr->info) { - e = gf_isom_box_size((GF_Box *)ptr->info); - if (e) return e; - ptr->size += ptr->info->size; - } return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Err tref_AddBox(GF_Box *ptr, GF_Box *a) -{ - return gf_isom_box_add_default(ptr, a); -} - -void tref_del(GF_Box *s) +void tref_box_del(GF_Box *s) { GF_TrackReferenceBox *ptr = (GF_TrackReferenceBox *)s; if (ptr == NULL) return; @@ -7337,12 +6849,12 @@ void tref_del(GF_Box *s) } -GF_Err tref_Read(GF_Box *s, GF_BitStream *bs) +GF_Err tref_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read_ex(s, bs, gf_isom_box_add_default, s->type); + return gf_isom_box_array_read_ex(s, bs, NULL, s->type); } -GF_Box *tref_New() +GF_Box *tref_box_new() { ISOM_DECL_BOX_ALLOC(GF_TrackReferenceBox, GF_ISOM_BOX_TYPE_TREF); return (GF_Box *)tmp; @@ -7350,20 +6862,20 @@ GF_Box *tref_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err tref_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tref_box_write(GF_Box *s, GF_BitStream *bs) { // GF_TrackReferenceBox *ptr = (GF_TrackReferenceBox *)s; return gf_isom_box_write_header(s, bs); } -GF_Err tref_Size(GF_Box *s) +GF_Err tref_box_size(GF_Box *s) { return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void reftype_del(GF_Box *s) +void reftype_box_del(GF_Box *s) { GF_TrackReferenceTypeBox *ptr = (GF_TrackReferenceTypeBox *)s; if (!ptr) return; @@ -7372,7 +6884,7 @@ void reftype_del(GF_Box *s) } -GF_Err reftype_Read(GF_Box *s, GF_BitStream *bs) +GF_Err reftype_box_read(GF_Box *s, GF_BitStream *bs) { u32 bytesToRead; u32 i; @@ -7382,7 +6894,7 @@ GF_Err reftype_Read(GF_Box *s, GF_BitStream *bs) if (!bytesToRead) return GF_OK; ptr->trackIDCount = (u32) (bytesToRead) / sizeof(u32); - ptr->trackIDs = (u32 *) gf_malloc(ptr->trackIDCount * sizeof(u32)); + ptr->trackIDs = (GF_ISOTrackID *) gf_malloc(ptr->trackIDCount * sizeof(GF_ISOTrackID)); if (!ptr->trackIDs) return GF_OUT_OF_MEM; for (i = 0; i < ptr->trackIDCount; i++) { @@ -7391,14 +6903,14 @@ GF_Err reftype_Read(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Box *reftype_New() +GF_Box *reftype_box_new() { ISOM_DECL_BOX_ALLOC(GF_TrackReferenceTypeBox, GF_ISOM_BOX_TYPE_REFT); return (GF_Box *)tmp; } -GF_Err reftype_AddRefTrack(GF_TrackReferenceTypeBox *ref, u32 trackID, u16 *outRefIndex) +GF_Err reftype_AddRefTrack(GF_TrackReferenceTypeBox *ref, GF_ISOTrackID trackID, u16 *outRefIndex) { u32 i; if (!ref || !trackID) return GF_BAD_PARAM; @@ -7412,7 +6924,7 @@ GF_Err reftype_AddRefTrack(GF_TrackReferenceTypeBox *ref, u32 trackID, u16 *outR } } - ref->trackIDs = (u32 *) gf_realloc(ref->trackIDs, (ref->trackIDCount + 1) * sizeof(u32) ); + ref->trackIDs = (GF_ISOTrackID *) gf_realloc(ref->trackIDs, (ref->trackIDCount + 1) * sizeof(GF_ISOTrackID) ); if (!ref->trackIDs) return GF_OUT_OF_MEM; ref->trackIDs[ref->trackIDCount] = trackID; ref->trackIDCount++; @@ -7424,14 +6936,12 @@ GF_Err reftype_AddRefTrack(GF_TrackReferenceTypeBox *ref, u32 trackID, u16 *outR #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err reftype_Write(GF_Box *s, GF_BitStream *bs) +GF_Err reftype_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; GF_TrackReferenceTypeBox *ptr = (GF_TrackReferenceTypeBox *)s; ptr->type = ptr->reference_type; - if (!ptr->trackIDCount) return GF_OK; - e = gf_isom_box_write_header(s, bs); ptr->type = GF_ISOM_BOX_TYPE_REFT; if (e) return e; @@ -7442,12 +6952,10 @@ GF_Err reftype_Write(GF_Box *s, GF_BitStream *bs) } -GF_Err reftype_Size(GF_Box *s) +GF_Err reftype_box_size(GF_Box *s) { GF_TrackReferenceTypeBox *ptr = (GF_TrackReferenceTypeBox *)s; - if (!ptr->trackIDCount) - ptr->size=0; - else + if (ptr->trackIDCount) ptr->size += (ptr->trackIDCount * sizeof(u32)); return GF_OK; } @@ -7458,7 +6966,7 @@ GF_Err reftype_Size(GF_Box *s) #ifndef GPAC_DISABLE_ISOM_FRAGMENTS -void trex_del(GF_Box *s) +void trex_box_del(GF_Box *s) { GF_TrackExtendsBox *ptr = (GF_TrackExtendsBox *)s; if (ptr == NULL) return; @@ -7466,24 +6974,20 @@ void trex_del(GF_Box *s) } -GF_Err trex_Read(GF_Box *s, GF_BitStream *bs) +GF_Err trex_box_read(GF_Box *s, GF_BitStream *bs) { GF_TrackExtendsBox *ptr = (GF_TrackExtendsBox *)s; + ISOM_DECREASE_SIZE(ptr, 20); ptr->trackID = gf_bs_read_u32(bs); ptr->def_sample_desc_index = gf_bs_read_u32(bs); ptr->def_sample_duration = gf_bs_read_u32(bs); ptr->def_sample_size = gf_bs_read_u32(bs); ptr->def_sample_flags = gf_bs_read_u32(bs); - - if (!ptr->def_sample_desc_index) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] TREX with default sample description set to 0, likely broken ! Fixing to 1\n" )); - ptr->def_sample_desc_index = 1; - } return GF_OK; } -GF_Box *trex_New() +GF_Box *trex_box_new() { ISOM_DECL_BOX_ALLOC(GF_TrackExtendsBox, GF_ISOM_BOX_TYPE_TREX); return (GF_Box *)tmp; @@ -7494,7 +6998,7 @@ GF_Box *trex_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err trex_Write(GF_Box *s, GF_BitStream *bs) +GF_Err trex_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TrackExtendsBox *ptr = (GF_TrackExtendsBox *) s; @@ -7503,14 +7007,15 @@ GF_Err trex_Write(GF_Box *s, GF_BitStream *bs) if (e) return e; gf_bs_write_u32(bs, ptr->trackID); - gf_bs_write_u32(bs, ptr->def_sample_desc_index); + //we always write 1 in trex default sample desc as using 0 breaks chrome/opera/... + gf_bs_write_u32(bs, ptr->def_sample_desc_index ? ptr->def_sample_desc_index : 1); gf_bs_write_u32(bs, ptr->def_sample_duration); gf_bs_write_u32(bs, ptr->def_sample_size); gf_bs_write_u32(bs, ptr->def_sample_flags); return GF_OK; } -GF_Err trex_Size(GF_Box *s) +GF_Err trex_box_size(GF_Box *s) { GF_TrackExtendsBox *ptr = (GF_TrackExtendsBox *)s; ptr->size += 20; @@ -7523,7 +7028,7 @@ GF_Err trex_Size(GF_Box *s) -void trep_del(GF_Box *s) +void trep_box_del(GF_Box *s) { GF_TrackExtensionPropertiesBox *ptr = (GF_TrackExtensionPropertiesBox *)s; if (ptr == NULL) return; @@ -7531,20 +7036,20 @@ void trep_del(GF_Box *s) } -GF_Err trep_Read(GF_Box *s, GF_BitStream *bs) +GF_Err trep_box_read(GF_Box *s, GF_BitStream *bs) { GF_TrackExtensionPropertiesBox *ptr = (GF_TrackExtensionPropertiesBox *)s; - ptr->trackID = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); + ptr->trackID = gf_bs_read_u32(bs); - return gf_isom_box_array_read(s, bs, gf_isom_box_add_default); + return gf_isom_box_array_read(s, bs, NULL); } -GF_Box *trep_New() +GF_Box *trep_box_new() { ISOM_DECL_BOX_ALLOC(GF_TrackExtensionPropertiesBox, GF_ISOM_BOX_TYPE_TREP); - tmp->other_boxes = gf_list_new(); + tmp->child_boxes = gf_list_new(); return (GF_Box *)tmp; } @@ -7553,7 +7058,7 @@ GF_Box *trep_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err trep_Write(GF_Box *s, GF_BitStream *bs) +GF_Err trep_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TrackExtensionPropertiesBox *ptr = (GF_TrackExtensionPropertiesBox *) s; @@ -7565,7 +7070,7 @@ GF_Err trep_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err trep_Size(GF_Box *s) +GF_Err trep_box_size(GF_Box *s) { GF_TrackExtensionPropertiesBox *ptr = (GF_TrackExtensionPropertiesBox *)s; ptr->size += 4; @@ -7582,157 +7087,596 @@ GF_Err trep_Size(GF_Box *s) #ifndef GPAC_DISABLE_ISOM_FRAGMENTS -void trun_del(GF_Box *s) +void trun_box_del(GF_Box *s) { - GF_TrunEntry *p; GF_TrackFragmentRunBox *ptr = (GF_TrackFragmentRunBox *)s; if (ptr == NULL) return; - while (gf_list_count(ptr->entries)) { - p = (GF_TrunEntry*)gf_list_get(ptr->entries, 0); - gf_list_rem(ptr->entries, 0); - gf_free(p); - } - gf_list_del(ptr->entries); + if (ptr->samples) gf_free(ptr->samples); if (ptr->cache) gf_bs_del(ptr->cache); + if (ptr->sample_order) gf_free(ptr->sample_order); gf_free(ptr); } -GF_Err trun_Read(GF_Box *s, GF_BitStream *bs) +#ifdef GF_ENABLE_CTRN + +static u32 ctrn_field_size(u32 field_idx) +{ + if (field_idx==3) return 4; + return field_idx; +} + +u32 gf_isom_ctrn_field_size_bits(u32 field_idx) +{ + if (field_idx==3) return 32; + return field_idx*8; +} +static u32 ctrn_read_flags(GF_BitStream *bs, u32 nbbits) +{ + u32 val = gf_bs_read_int(bs, nbbits); + if (nbbits==16) val <<= 16; + else if (nbbits==8) val <<= 24; + return val; +} + +static GF_Err ctrn_box_read(GF_Box *s, GF_BitStream *bs) +{ + u32 i, count, flags, first_idx=0; + Bool inherit_dur, inherit_size, inherit_flags, inherit_ctso; + GF_TrunEntry *ent; + GF_TrackFragmentRunBox *ptr = (GF_TrackFragmentRunBox *)s; + flags = ptr->flags; + ptr->ctrn_flags = flags; + ptr->flags = 0; + + ptr->sample_count = gf_bs_read_u16(bs); + ISOM_DECREASE_SIZE(ptr, 2); + + if (flags & GF_ISOM_TRUN_DATA_OFFSET) { + if (flags & GF_ISOM_CTRN_DATAOFFSET_16) { + ptr->data_offset = gf_bs_read_u16(bs); + ISOM_DECREASE_SIZE(ptr, 2); + } else { + ptr->data_offset = gf_bs_read_u32(bs); + ISOM_DECREASE_SIZE(ptr, 4); + } + ptr->flags |= GF_ISOM_TRUN_DATA_OFFSET; + } + if (flags & GF_ISOM_CTRN_CTSO_MULTIPLIER) { + ptr->ctso_multiplier = gf_bs_read_u16(bs); + ISOM_DECREASE_SIZE(ptr, 2); + } + /*no sample dur/sample_flag/size/ctso for first or following, create a pack sample */ + if (! (flags & 0x00FFFF00)) { + GF_SAFEALLOC(ent, GF_TrunEntry); + if (!ent) return GF_OUT_OF_MEM; + ent->nb_pack = ptr->sample_count; + gf_list_add(ptr->entries, ent); + return GF_OK; + } + /*allocate all entries*/ + for (i=0; isample_count; i++) { + GF_SAFEALLOC(ent, GF_TrunEntry); + if (!ent) return GF_OUT_OF_MEM; + gf_list_add(ptr->entries, ent); + } + //unpack flags + ptr->ctrn_first_dur = (flags>>22) & 0x3; + ptr->ctrn_first_size = (flags>>20) & 0x3; + ptr->ctrn_first_sample_flags = (flags>>18) & 0x3; + ptr->ctrn_first_ctts = (flags>>16) & 0x3; + ptr->ctrn_dur = (flags>>14) & 0x3; + ptr->ctrn_size = (flags>>12) & 0x3; + ptr->ctrn_sample_flags = (flags>>10) & 0x3; + ptr->ctrn_ctts = (flags>>8) & 0x3; + + inherit_dur = flags & GF_ISOM_CTRN_INHERIT_DUR; + inherit_size = flags & GF_ISOM_CTRN_INHERIT_SIZE; + inherit_flags = flags & GF_ISOM_CTRN_INHERIT_FLAGS; + inherit_ctso = flags & GF_ISOM_CTRN_INHERIT_CTSO; + + if (flags & GF_ISOM_CTRN_FIRST_SAMPLE) { + ent = gf_list_get(ptr->entries, 0); + first_idx = 1; + if (!inherit_dur && ptr->ctrn_first_dur) { + ent->Duration = gf_bs_read_int(bs, gf_isom_ctrn_field_size_bits(ptr->ctrn_first_dur) ); + ISOM_DECREASE_SIZE(ptr, ctrn_field_size(ptr->ctrn_first_dur) ); + } + if (!inherit_size && ptr->ctrn_first_size) { + ent->size = gf_bs_read_int(bs, gf_isom_ctrn_field_size_bits(ptr->ctrn_first_size) ); + ISOM_DECREASE_SIZE(ptr, ctrn_field_size(ptr->ctrn_first_size) ); + } + if (!inherit_flags && ptr->ctrn_first_sample_flags) { + ent->flags = ctrn_read_flags(bs, gf_isom_ctrn_field_size_bits(ptr->ctrn_first_sample_flags) ); + ISOM_DECREASE_SIZE(ptr, ctrn_field_size(ptr->ctrn_first_sample_flags) ); + } + if (!inherit_ctso && ptr->ctrn_first_ctts) { + ent->CTS_Offset = gf_bs_read_int(bs, gf_isom_ctrn_field_size_bits(ptr->ctrn_first_ctts) ); + ISOM_DECREASE_SIZE(ptr, ctrn_field_size(ptr->ctrn_first_ctts) ); + if (ptr->ctso_multiplier) + ent->CTS_Offset *= (s32) ptr->ctso_multiplier; + } + } + count = ptr->sample_count - first_idx; + if (!inherit_dur && ptr->ctrn_dur) { + u32 nbbits = gf_isom_ctrn_field_size_bits(ptr->ctrn_dur); + ISOM_DECREASE_SIZE(ptr, count * nbbits / 8); + for (i=first_idx; isample_count; i++) { + ent = gf_list_get(ptr->entries, i); + ent->Duration = gf_bs_read_int(bs, nbbits); + } + } + if (!inherit_size && ptr->ctrn_size) { + u32 nbbits = gf_isom_ctrn_field_size_bits(ptr->ctrn_size); + ISOM_DECREASE_SIZE(ptr, count * nbbits / 8); + for (i=first_idx; isample_count; i++) { + ent = gf_list_get(ptr->entries, i); + ent->size = gf_bs_read_int(bs, nbbits); + } + } + if (!inherit_flags && ptr->ctrn_sample_flags) { + u32 nbbits = gf_isom_ctrn_field_size_bits(ptr->ctrn_sample_flags); + ISOM_DECREASE_SIZE(ptr, count * nbbits / 8); + for (i=first_idx; isample_count; i++) { + ent = gf_list_get(ptr->entries, i); + ent->flags = ctrn_read_flags(bs, nbbits); + } + } + if (!inherit_ctso && ptr->ctrn_ctts) { + u32 nbbits = gf_isom_ctrn_field_size_bits(ptr->ctrn_ctts); + ISOM_DECREASE_SIZE(ptr, count * nbbits / 8); + for (i=first_idx; isample_count; i++) { + ent = gf_list_get(ptr->entries, i); + ent->CTS_Offset = gf_bs_read_int(bs, nbbits); + if (ptr->ctso_multiplier) + ent->CTS_Offset *= (s32) ptr->ctso_multiplier; + } + } + + return GF_OK; +} +#endif + +GF_Err trun_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; - GF_TrunEntry *p; GF_TrackFragmentRunBox *ptr = (GF_TrackFragmentRunBox *)s; +#ifdef GF_ENABLE_CTRN + if (ptr->type == GF_ISOM_BOX_TYPE_CTRN) { + ptr->type = GF_ISOM_BOX_TYPE_TRUN; + ptr->use_ctrn = GF_TRUE; + return ctrn_box_read(s, bs); + } +#endif + //check this is a good file if ((ptr->flags & GF_ISOM_TRUN_FIRST_FLAG) && (ptr->flags & GF_ISOM_TRUN_FLAGS)) return GF_ISOM_INVALID_FILE; + ISOM_DECREASE_SIZE(ptr, 4); ptr->sample_count = gf_bs_read_u32(bs); //The rest depends on the flags if (ptr->flags & GF_ISOM_TRUN_DATA_OFFSET) { - ptr->data_offset = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); + ptr->data_offset = gf_bs_read_u32(bs); } if (ptr->flags & GF_ISOM_TRUN_FIRST_FLAG) { - ptr->first_sample_flags = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); + ptr->first_sample_flags = gf_bs_read_u32(bs); } - - //read each entry (even though nothing may be written) - for (i=0; isample_count; i++) { - u32 trun_size = 0; - p = (GF_TrunEntry *) gf_malloc(sizeof(GF_TrunEntry)); - if (!p) return GF_OUT_OF_MEM; - memset(p, 0, sizeof(GF_TrunEntry)); - - if (ptr->flags & GF_ISOM_TRUN_DURATION) { - p->Duration = gf_bs_read_u32(bs); - trun_size += 4; - } - if (ptr->flags & GF_ISOM_TRUN_SIZE) { - p->size = gf_bs_read_u32(bs); - trun_size += 4; - } - //SHOULDN'T BE USED IF GF_ISOM_TRUN_FIRST_FLAG IS DEFINED - if (ptr->flags & GF_ISOM_TRUN_FLAGS) { - p->flags = gf_bs_read_u32(bs); - trun_size += 4; - } - if (ptr->flags & GF_ISOM_TRUN_CTS_OFFSET) { - if (ptr->version==0) { - p->CTS_Offset = (u32) gf_bs_read_u32(bs); - } else { - p->CTS_Offset = (s32) gf_bs_read_u32(bs); + if (! (ptr->flags & (GF_ISOM_TRUN_DURATION | GF_ISOM_TRUN_SIZE | GF_ISOM_TRUN_FLAGS | GF_ISOM_TRUN_CTS_OFFSET) ) ) { + ptr->samples = gf_malloc(sizeof(GF_TrunEntry)); + if (!ptr->samples) return GF_OUT_OF_MEM; + ptr->sample_alloc = ptr->nb_samples = 1; + ptr->samples[0].nb_pack = ptr->sample_count; + } else { + //if we get here, at least one flag (so at least 4 bytes) is set, check size + if (ptr->sample_count * 4 > ptr->size) { + ISOM_DECREASE_SIZE(ptr, ptr->sample_count*4); + } + ptr->samples = gf_malloc(sizeof(GF_TrunEntry) * ptr->sample_count); + if (!ptr->samples) return GF_OUT_OF_MEM; + ptr->sample_alloc = ptr->nb_samples = ptr->sample_count; + + //read each entry (even though nothing may be written) + for (i=0; isample_count; i++) { + u32 trun_size = 0; + GF_TrunEntry *p = &ptr->samples[i]; + memset(p, 0, sizeof(GF_TrunEntry)); + + if (ptr->flags & GF_ISOM_TRUN_DURATION) { + p->Duration = gf_bs_read_u32(bs); + trun_size += 4; + } + if (ptr->flags & GF_ISOM_TRUN_SIZE) { + p->size = gf_bs_read_u32(bs); + trun_size += 4; + } + //SHOULDN'T BE USED IF GF_ISOM_TRUN_FIRST_FLAG IS DEFINED + if (ptr->flags & GF_ISOM_TRUN_FLAGS) { + p->flags = gf_bs_read_u32(bs); + trun_size += 4; } + if (ptr->flags & GF_ISOM_TRUN_CTS_OFFSET) { + if (ptr->version==0) { + p->CTS_Offset = (u32) gf_bs_read_u32(bs); + } else { + p->CTS_Offset = (s32) gf_bs_read_u32(bs); + } + trun_size += 4; + } + ISOM_DECREASE_SIZE(ptr, trun_size); } - gf_list_add(ptr->entries, p); - ISOM_DECREASE_SIZE(ptr, trun_size); + } + /*todo parse sample reorder*/ + if (ptr->size) { + gf_bs_skip_bytes(bs, ptr->size); + ptr->size = 0; } return GF_OK; } -GF_Box *trun_New() +GF_Box *trun_box_new() { ISOM_DECL_BOX_ALLOC(GF_TrackFragmentRunBox, GF_ISOM_BOX_TYPE_TRUN); - tmp->entries = gf_list_new(); //NO FLAGS SET BY DEFAULT return (GF_Box *)tmp; } +#ifndef GPAC_DISABLE_ISOM_WRITE + +#ifdef GF_ENABLE_CTRN +static void ctrn_write_sample_flags(GF_BitStream *bs, u32 flags, u32 field_size) +{ + if (!field_size) return; + if (field_size==8) flags = flags>>24; + else if (field_size==16) flags = flags>>16; + gf_bs_write_int(bs, flags, field_size); +} -#ifndef GPAC_DISABLE_ISOM_WRITE +static void ctrn_write_ctso(GF_TrackFragmentRunBox *ctrn, GF_BitStream *bs, u32 ctso, u32 field_size) +{ + if (!field_size) return; + + if (ctrn->ctso_multiplier) { + gf_bs_write_int(bs, ctso / ctrn->ctso_multiplier, field_size); + } else { + gf_bs_write_int(bs, ctso, field_size); + } +} -GF_Err trun_Write(GF_Box *s, GF_BitStream *bs) +GF_Err ctrn_box_write(GF_Box *s, GF_BitStream *bs) { - GF_TrunEntry *p; GF_Err e; - u32 i, count; - GF_TrackFragmentRunBox *ptr = (GF_TrackFragmentRunBox *) s; + u32 i, count, flags; + GF_TrunEntry *ent; + GF_TrackFragmentRunBox *ctrn = (GF_TrackFragmentRunBox *) s; if (!s) return GF_BAD_PARAM; + flags = ctrn->flags; + ctrn->flags = ctrn->ctrn_flags; + ctrn->type = GF_ISOM_BOX_TYPE_CTRN; e = gf_isom_full_box_write(s, bs); if (e) return e; + ctrn->flags = flags; + ctrn->type = GF_ISOM_BOX_TYPE_TRUN; - gf_bs_write_u32(bs, ptr->sample_count); - - //The rest depends on the flags - if (ptr->flags & GF_ISOM_TRUN_DATA_OFFSET) { - gf_bs_write_u32(bs, ptr->data_offset); + gf_bs_write_u16(bs, ctrn->sample_count); + if (ctrn->flags & GF_ISOM_TRUN_DATA_OFFSET) { + if (ctrn->ctrn_flags & GF_ISOM_CTRN_DATAOFFSET_16) { + gf_bs_write_u16(bs, ctrn->data_offset); + } else { + gf_bs_write_u32(bs, ctrn->data_offset); + } } - if (ptr->flags & GF_ISOM_TRUN_FIRST_FLAG) { - gf_bs_write_u32(bs, ptr->first_sample_flags); + if (ctrn->ctso_multiplier) { + gf_bs_write_u16(bs, ctrn->ctso_multiplier); } + /*we always write first sample using first flags*/ + ent = gf_list_get(ctrn->entries, 0); + gf_bs_write_int(bs, ent->Duration, gf_isom_ctrn_field_size_bits(ctrn->ctrn_first_dur) ); + gf_bs_write_int(bs, ent->size, gf_isom_ctrn_field_size_bits(ctrn->ctrn_first_size) ); + ctrn_write_sample_flags(bs, ent->flags, gf_isom_ctrn_field_size_bits(ctrn->ctrn_first_sample_flags) ); + ctrn_write_ctso(ctrn,bs, ent->CTS_Offset, gf_isom_ctrn_field_size_bits(ctrn->ctrn_first_ctts) ); - //if nothing to do, this will be skipped automatically - count = gf_list_count(ptr->entries); - for (i=0; ientries, i); - - if (ptr->flags & GF_ISOM_TRUN_DURATION) { - gf_bs_write_u32(bs, p->Duration); + count = gf_list_count(ctrn->entries); + if (ctrn->ctrn_dur) { + u32 nbbits = gf_isom_ctrn_field_size_bits(ctrn->ctrn_dur); + for (i=1; ientries, i); + gf_bs_write_int(bs, a_ent->Duration, nbbits); + } + } + if (ctrn->ctrn_size) { + u32 nbbits = gf_isom_ctrn_field_size_bits(ctrn->ctrn_size); + for (i=1; ientries, i); + gf_bs_write_int(bs, a_ent->size, nbbits); } - if (ptr->flags & GF_ISOM_TRUN_SIZE) { - gf_bs_write_u32(bs, p->size); + } + if (ctrn->ctrn_sample_flags) { + u32 nbbits = gf_isom_ctrn_field_size_bits(ctrn->ctrn_sample_flags); + for (i=1; ientries, i); + ctrn_write_sample_flags(bs, a_ent->flags, nbbits); } - //SHOULDN'T BE USED IF GF_ISOM_TRUN_FIRST_FLAG IS DEFINED - if (ptr->flags & GF_ISOM_TRUN_FLAGS) { - gf_bs_write_u32(bs, p->flags); + } + if (ctrn->ctrn_ctts) { + u32 nbbits = gf_isom_ctrn_field_size_bits(ctrn->ctrn_ctts); + for (i=1; ientries, i); + ctrn_write_ctso(ctrn, bs, a_ent->CTS_Offset, nbbits); } - if (ptr->flags & GF_ISOM_TRUN_CTS_OFFSET) { - if (ptr->version==0) { - gf_bs_write_u32(bs, p->CTS_Offset); - } else { - gf_bs_write_u32(bs, (u32) p->CTS_Offset); + } + + return GF_OK; +} +#endif + +GF_Err trun_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + u32 i; + GF_TrackFragmentRunBox *ptr = (GF_TrackFragmentRunBox *) s; + if (!s) return GF_BAD_PARAM; + +#ifdef GF_ENABLE_CTRN + if (ptr->use_ctrn) + return ctrn_box_write(s, bs); +#endif + + e = gf_isom_full_box_write(s, bs); + if (e) return e; + + gf_bs_write_u32(bs, ptr->sample_count); + + //The rest depends on the flags + if (ptr->flags & GF_ISOM_TRUN_DATA_OFFSET) { + gf_bs_write_u32(bs, ptr->data_offset); + } + if (ptr->flags & GF_ISOM_TRUN_FIRST_FLAG) { + gf_bs_write_u32(bs, ptr->first_sample_flags); + } + + if (ptr->flags & (GF_ISOM_TRUN_DURATION | GF_ISOM_TRUN_SIZE | GF_ISOM_TRUN_FLAGS | GF_ISOM_TRUN_CTS_OFFSET) ) { + for (i=0; inb_samples; i++) { + GF_TrunEntry *p = &ptr->samples[i]; + + if (ptr->flags & GF_ISOM_TRUN_DURATION) { + gf_bs_write_u32(bs, p->Duration); + } + if (ptr->flags & GF_ISOM_TRUN_SIZE) { + gf_bs_write_u32(bs, p->size); + } + //SHOULDN'T BE USED IF GF_ISOM_TRUN_FIRST_FLAG IS DEFINED + if (ptr->flags & GF_ISOM_TRUN_FLAGS) { + gf_bs_write_u32(bs, p->flags); + } + if (ptr->flags & GF_ISOM_TRUN_CTS_OFFSET) { + if (ptr->version==0) { + gf_bs_write_u32(bs, p->CTS_Offset); + } else { + gf_bs_write_u32(bs, (u32) p->CTS_Offset); + } } } } + + if (ptr->sample_order) { + u32 nb_bits = 8; + if (ptr->sample_count>0xFFFFFF) nb_bits = 32; + else if (ptr->sample_count>0xFFFF) nb_bits = 24; + else if (ptr->sample_count>0xFF) nb_bits = 16; + + for (i=0; isample_count; i++) { + gf_bs_write_int(bs, ptr->sample_order[i], nb_bits); + } + } return GF_OK; } -GF_Err trun_Size(GF_Box *s) +#ifdef GF_ENABLE_CTRN +static u32 ctrn_sample_flags_to_index(u32 val) +{ + if (!val) return 0; + if (val & 0x0000FFFF) + return 3; + if (val & 0x00FF0000) + return 2; + return 1; +} +static u32 ctrn_u32_to_index(u32 val) +{ + if (!val) return 0; + if (val<=255) return 1; + if (val<=65535) return 2; + return 3; +} +static u32 ctrn_s32_to_index(s32 val) +{ + if (!val) return 0; + if (ABS(val)<=127) return 1; + if (ABS(val)<=32767) return 2; + return 3; +} +static u32 ctrn_ctts_to_index(GF_TrackFragmentRunBox *ctrn, s32 ctts) +{ + if (!(ctrn->flags & GF_ISOM_TRUN_CTS_OFFSET)) + return 0; + + if (!ctts) return 0; + + if (ctrn->version) { + if (ctrn->ctso_multiplier) return ctrn_s32_to_index(ctts / ctrn->ctso_multiplier); + return ctrn_s32_to_index(ctts); + } + assert(ctts>0); + if (ctrn->ctso_multiplier) return ctrn_u32_to_index((u32)ctts / ctrn->ctso_multiplier); + return ctrn_s32_to_index((u32)ctts); +} + +static GF_Err ctrn_box_size(GF_TrackFragmentRunBox *ctrn) { + Bool use_ctso_multi = GF_TRUE; u32 i, count; + GF_TrunEntry *ent; + + ctrn->ctrn_flags = 0; + ctrn->ctrn_first_dur = ctrn->ctrn_first_size = ctrn->ctrn_first_sample_flags = ctrn->ctrn_first_ctts = 0; + ctrn->ctrn_dur = ctrn->ctrn_size = ctrn->ctrn_sample_flags = ctrn->ctrn_ctts = 0; + + ctrn->size += 2; //16 bits for sample count + if (ctrn->flags & GF_ISOM_TRUN_DATA_OFFSET) { + ctrn->ctrn_flags |= GF_ISOM_TRUN_DATA_OFFSET; + if (ABS(ctrn->data_offset) < 32767) { + ctrn->size += 2; + ctrn->ctrn_flags |= GF_ISOM_CTRN_DATAOFFSET_16; + } else + ctrn->size += 4; + } + + count = gf_list_count(ctrn->entries); + if (ctrn->ctso_multiplier && (ctrn->flags & GF_ISOM_TRUN_CTS_OFFSET) && (ctrn->ctso_multiplier<=0xFFFF) ) { + for (i=0; ientries, i); + if (a_ent->CTS_Offset % ctrn->ctso_multiplier) { + use_ctso_multi = GF_FALSE; + break; + } + } + } else { + use_ctso_multi = GF_FALSE; + } + if (ctrn->use_inherit) { + use_ctso_multi = GF_FALSE; + ctrn->ctrn_flags |= 0xB0; //duration=1,size=0,flags=1,cts=1 << 4 + } + + if (use_ctso_multi) { + ctrn->size += 2; + ctrn->ctrn_flags |= GF_ISOM_CTRN_CTSO_MULTIPLIER; + } else { + ctrn->ctso_multiplier = 0; + } + + /*we always write first sample using first flags*/ + ent = gf_list_get(ctrn->entries, 0); + ctrn->ctrn_flags |= GF_ISOM_CTRN_FIRST_SAMPLE; + + if (!ctrn->use_inherit && (ctrn->flags & GF_ISOM_TRUN_DURATION)) { + ctrn->ctrn_first_dur = ctrn_u32_to_index(ent->Duration); + if (ctrn->ctrn_first_dur) { + ctrn->size += ctrn_field_size(ctrn->ctrn_first_dur); + ctrn->ctrn_flags |= ctrn->ctrn_first_dur<<22; + } + } + + if (ctrn->flags & GF_ISOM_TRUN_SIZE) { + ctrn->ctrn_first_size = ctrn_u32_to_index(ent->size); + if (ctrn->ctrn_first_size) { + ctrn->size += ctrn_field_size(ctrn->ctrn_first_size); + ctrn->ctrn_flags |= ctrn->ctrn_first_size<<20; + } + } + + if (!ctrn->use_inherit && (ctrn->flags & GF_ISOM_TRUN_FLAGS)) { + ctrn->ctrn_first_sample_flags = ctrn_sample_flags_to_index(ent->flags); + if (ctrn->ctrn_first_sample_flags) { + ctrn->size += ctrn_field_size(ctrn->ctrn_first_sample_flags); + ctrn->ctrn_flags |= ctrn->ctrn_first_sample_flags<<18; + } + } + if (!ctrn->use_inherit && (ctrn->flags & GF_ISOM_TRUN_CTS_OFFSET)) { + ctrn->ctrn_first_ctts = ctrn_ctts_to_index(ctrn, ent->CTS_Offset); + if (ctrn->ctrn_first_ctts) { + ctrn->size += ctrn_field_size(ctrn->ctrn_first_ctts); + ctrn->ctrn_flags |= ctrn->ctrn_first_ctts<<16; + } + } + + for (i=1; ientries, i); + + if (!ctrn->use_inherit && (ctrn->flags & GF_ISOM_TRUN_DURATION)) { + field_idx = ctrn_u32_to_index(a_ent->Duration); + if (ctrn->ctrn_dur < field_idx) + ctrn->ctrn_dur = field_idx; + } + if (ctrn->flags & GF_ISOM_TRUN_SIZE) { + field_idx = ctrn_u32_to_index(a_ent->size); + if (ctrn->ctrn_size < field_idx) + ctrn->ctrn_size = field_idx; + } + if (!ctrn->use_inherit && (ctrn->flags & GF_ISOM_TRUN_FLAGS)) { + field_idx = ctrn_sample_flags_to_index(a_ent->flags); + if (ctrn->ctrn_sample_flags < field_idx) + ctrn->ctrn_sample_flags = field_idx; + } + if (!ctrn->use_inherit) { + field_idx = ctrn_ctts_to_index(ctrn, a_ent->CTS_Offset); + if (ctrn->ctrn_ctts < field_idx) + ctrn->ctrn_ctts = field_idx; + } + } + count-=1; + if (ctrn->ctrn_dur) { + ctrn->size += count * ctrn_field_size(ctrn->ctrn_dur); + ctrn->ctrn_flags |= ctrn->ctrn_dur<<14; + } + if (ctrn->ctrn_size) { + ctrn->size += count * ctrn_field_size(ctrn->ctrn_size); + ctrn->ctrn_flags |= ctrn->ctrn_size<<12; + } + if (ctrn->ctrn_sample_flags) { + ctrn->size += count * ctrn_field_size(ctrn->ctrn_sample_flags); + ctrn->ctrn_flags |= ctrn->ctrn_sample_flags<<10; + } + if (ctrn->ctrn_ctts) { + ctrn->size += count * ctrn_field_size(ctrn->ctrn_ctts); + ctrn->ctrn_flags |= ctrn->ctrn_ctts<<8; + } + return GF_OK; +} +#endif + +GF_Err trun_box_size(GF_Box *s) +{ GF_TrackFragmentRunBox *ptr = (GF_TrackFragmentRunBox *)s; +#ifdef GF_ENABLE_CTRN + if (ptr->use_ctrn) + return ctrn_box_size(ptr); +#endif + ptr->size += 4; //The rest depends on the flags if (ptr->flags & GF_ISOM_TRUN_DATA_OFFSET) ptr->size += 4; if (ptr->flags & GF_ISOM_TRUN_FIRST_FLAG) ptr->size += 4; - //if nothing to do, this will be skipped automatically - count = gf_list_count(ptr->entries); - for (i=0; iflags & GF_ISOM_TRUN_DURATION) ptr->size += 4; - if (ptr->flags & GF_ISOM_TRUN_SIZE) ptr->size += 4; - //SHOULDN'T BE USED IF GF_ISOM_TRUN_FIRST_FLAG IS DEFINED - if (ptr->flags & GF_ISOM_TRUN_FLAGS) ptr->size += 4; - if (ptr->flags & GF_ISOM_TRUN_CTS_OFFSET) ptr->size += 4; + if (ptr->sample_order) { + u32 nb_bytes = 1; + if (ptr->sample_count>0xFFFFFF) nb_bytes = 4; + else if (ptr->sample_count>0xFFFF) nb_bytes = 3; + else if (ptr->sample_count>0xFF) nb_bytes = 2; + ptr->size += ptr->sample_count*nb_bytes; } + if (! (ptr->flags & (GF_ISOM_TRUN_DURATION | GF_ISOM_TRUN_SIZE | GF_ISOM_TRUN_FLAGS | GF_ISOM_TRUN_CTS_OFFSET) ) ) { + return GF_OK; + } + + //if nothing to do, this will be skipped automatically + if (ptr->flags & GF_ISOM_TRUN_DURATION) ptr->size += 4*ptr->nb_samples; + if (ptr->flags & GF_ISOM_TRUN_SIZE) ptr->size += 4*ptr->nb_samples; + //SHOULDN'T BE USED IF GF_ISOM_TRUN_FIRST_FLAG IS DEFINED + if (ptr->flags & GF_ISOM_TRUN_FLAGS) ptr->size += 4*ptr->nb_samples; + if (ptr->flags & GF_ISOM_TRUN_CTS_OFFSET) ptr->size += 4*ptr->nb_samples; + return GF_OK; } @@ -7743,20 +7687,21 @@ GF_Err trun_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_FRAGMENTS*/ -void tsro_del(GF_Box *s) +void tsro_box_del(GF_Box *s) { GF_TimeOffHintEntryBox *tsro = (GF_TimeOffHintEntryBox *)s; gf_free(tsro); } -GF_Err tsro_Read(GF_Box *s, GF_BitStream *bs) +GF_Err tsro_box_read(GF_Box *s, GF_BitStream *bs) { GF_TimeOffHintEntryBox *ptr = (GF_TimeOffHintEntryBox *)s; + ISOM_DECREASE_SIZE(ptr, 4); ptr->TimeOffset = gf_bs_read_u32(bs); return GF_OK; } -GF_Box *tsro_New() +GF_Box *tsro_box_new() { ISOM_DECL_BOX_ALLOC(GF_TimeOffHintEntryBox, GF_ISOM_BOX_TYPE_TSRO); return (GF_Box *)tmp; @@ -7764,7 +7709,7 @@ GF_Box *tsro_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err tsro_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tsro_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TimeOffHintEntryBox *ptr = (GF_TimeOffHintEntryBox *)s; @@ -7776,7 +7721,7 @@ GF_Err tsro_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err tsro_Size(GF_Box *s) +GF_Err tsro_box_size(GF_Box *s) { s->size += 4; return GF_OK; @@ -7784,7 +7729,7 @@ GF_Err tsro_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void udta_del(GF_Box *s) +void udta_box_del(GF_Box *s) { u32 i; GF_UserDataMap *map; @@ -7792,7 +7737,7 @@ void udta_del(GF_Box *s) if (ptr == NULL) return; i=0; while ((map = (GF_UserDataMap *)gf_list_enum(ptr->recordList, &i))) { - gf_isom_box_array_del(map->other_boxes); + gf_isom_box_array_del(map->boxes); gf_free(map); } gf_list_del(ptr->recordList); @@ -7814,7 +7759,7 @@ GF_UserDataMap *udta_getEntry(GF_UserDataBox *ptr, u32 box_type, bin128 *uuid) return NULL; } -GF_Err udta_AddBox(GF_Box *s, GF_Box *a) +GF_Err udta_on_child_box(GF_Box *s, GF_Box *a) { GF_Err e; u32 box_type; @@ -7823,12 +7768,14 @@ GF_Err udta_AddBox(GF_Box *s, GF_Box *a) if (!ptr) return GF_BAD_PARAM; if (!a) return GF_OK; + //detach from parent list if any + gf_list_del_item(ptr->child_boxes, a); + /* for unknown udta boxes, we reference them by their original box type */ box_type = a->type; if (box_type == GF_ISOM_BOX_TYPE_UNKNOWN) { GF_UnknownBox* unkn = (GF_UnknownBox *)a; - if (unkn) - box_type = unkn->original_4cc; + box_type = unkn->original_4cc; } map = udta_getEntry(ptr, box_type, (a->type==GF_ISOM_BOX_TYPE_UUID) ? & ((GF_UUIDBox *)a)->uuid : NULL); @@ -7840,24 +7787,33 @@ GF_Err udta_AddBox(GF_Box *s, GF_Box *a) map->boxType = box_type; if (a->type == GF_ISOM_BOX_TYPE_UUID) memcpy(map->uuid, ((GF_UUIDBox *)a)->uuid, 16); - map->other_boxes = gf_list_new(); - if (!map->other_boxes) { + map->boxes = gf_list_new(); + if (!map->boxes) { gf_free(map); return GF_OUT_OF_MEM; } e = gf_list_add(ptr->recordList, map); if (e) return e; } - return gf_list_add(map->other_boxes, a); + return gf_list_add(map->boxes, a); } -GF_Err udta_Read(GF_Box *s, GF_BitStream *bs) +GF_Err udta_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, udta_AddBox); + GF_Err e = gf_isom_box_array_read(s, bs, udta_on_child_box); + if (e) return e; + if (s->size==4) { + u32 val = gf_bs_read_u32(bs); + s->size = 0; + if (val) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] udta has 4 remaining bytes set to %08X but they should be 0\n", val)); + } + } + return GF_OK; } -GF_Box *udta_New() +GF_Box *udta_box_new() { ISOM_DECL_BOX_ALLOC(GF_UserDataBox, GF_ISOM_BOX_TYPE_UDTA); tmp->recordList = gf_list_new(); @@ -7871,7 +7827,7 @@ GF_Box *udta_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err udta_Write(GF_Box *s, GF_BitStream *bs) +GF_Err udta_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -7884,13 +7840,13 @@ GF_Err udta_Write(GF_Box *s, GF_BitStream *bs) while ((map = (GF_UserDataMap *)gf_list_enum(ptr->recordList, &i))) { //warning: here we are not passing the actual "parent" of the list //but the UDTA box. The parent itself is not an box, we don't care about it - e = gf_isom_box_array_write(s, map->other_boxes, bs); + e = gf_isom_box_array_write(s, map->boxes, bs); if (e) return e; } return GF_OK; } -GF_Err udta_Size(GF_Box *s) +GF_Err udta_box_size(GF_Box *s) { GF_Err e; u32 i; @@ -7901,7 +7857,7 @@ GF_Err udta_Size(GF_Box *s) while ((map = (GF_UserDataMap *)gf_list_enum(ptr->recordList, &i))) { //warning: here we are not passing the actual "parent" of the list //but the UDTA box. The parent itself is not an box, we don't care about it - e = gf_isom_box_array_size(s, map->other_boxes); + e = gf_isom_box_array_size(s, map->boxes); if (e) return e; } return GF_OK; @@ -7910,7 +7866,7 @@ GF_Err udta_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void vmhd_del(GF_Box *s) +void vmhd_box_del(GF_Box *s) { GF_VideoMediaHeaderBox *ptr = (GF_VideoMediaHeaderBox *)s; if (ptr == NULL) return; @@ -7918,15 +7874,16 @@ void vmhd_del(GF_Box *s) } -GF_Err vmhd_Read(GF_Box *s, GF_BitStream *bs) +GF_Err vmhd_box_read(GF_Box *s, GF_BitStream *bs) { GF_VideoMediaHeaderBox *ptr = (GF_VideoMediaHeaderBox *)s; + ISOM_DECREASE_SIZE(ptr, 8); ptr->reserved = gf_bs_read_u64(bs); return GF_OK; } -GF_Box *vmhd_New() +GF_Box *vmhd_box_new() { ISOM_DECL_BOX_ALLOC(GF_VideoMediaHeaderBox, GF_ISOM_BOX_TYPE_VMHD); tmp->flags = 1; @@ -7937,7 +7894,7 @@ GF_Box *vmhd_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err vmhd_Write(GF_Box *s, GF_BitStream *bs) +GF_Err vmhd_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_VideoMediaHeaderBox *ptr = (GF_VideoMediaHeaderBox *)s; @@ -7948,7 +7905,7 @@ GF_Err vmhd_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err vmhd_Size(GF_Box *s) +GF_Err vmhd_box_size(GF_Box *s) { GF_VideoMediaHeaderBox *ptr = (GF_VideoMediaHeaderBox *)s; ptr->size += 8; @@ -7957,20 +7914,19 @@ GF_Err vmhd_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ - -void void_del(GF_Box *s) +void void_box_del(GF_Box *s) { gf_free(s); } -GF_Err void_Read(GF_Box *s, GF_BitStream *bs) +GF_Err void_box_read(GF_Box *s, GF_BitStream *bs) { if (s->size) return GF_ISOM_INVALID_FILE; return GF_OK; } -GF_Box *void_New() +GF_Box *void_box_new() { ISOM_DECL_BOX_ALLOC(GF_Box, GF_ISOM_BOX_TYPE_VOID); return tmp; @@ -7979,13 +7935,13 @@ GF_Box *void_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err void_Write(GF_Box *s, GF_BitStream *bs) +GF_Err void_box_write(GF_Box *s, GF_BitStream *bs) { gf_bs_write_u32(bs, 0); return GF_OK; } -GF_Err void_Size(GF_Box *s) +GF_Err void_box_size(GF_Box *s) { s->size = 4; return GF_OK; @@ -7995,15 +7951,14 @@ GF_Err void_Size(GF_Box *s) -GF_Box *pdin_New() +GF_Box *pdin_box_new() { ISOM_DECL_BOX_ALLOC(GF_ProgressiveDownloadBox, GF_ISOM_BOX_TYPE_PDIN); - tmp->flags = 1; return (GF_Box *)tmp; } -void pdin_del(GF_Box *s) +void pdin_box_del(GF_Box *s) { GF_ProgressiveDownloadBox *ptr = (GF_ProgressiveDownloadBox*)s; if (ptr == NULL) return; @@ -8013,14 +7968,16 @@ void pdin_del(GF_Box *s) } -GF_Err pdin_Read(GF_Box *s, GF_BitStream *bs) +GF_Err pdin_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; GF_ProgressiveDownloadBox *ptr = (GF_ProgressiveDownloadBox*)s; ptr->count = (u32) (ptr->size) / 8; ptr->rates = (u32*)gf_malloc(sizeof(u32)*ptr->count); + if (!ptr->rates) return GF_OUT_OF_MEM; ptr->times = (u32*)gf_malloc(sizeof(u32)*ptr->count); + if (!ptr->times) return GF_OUT_OF_MEM; for (i=0; icount; i++) { ptr->rates[i] = gf_bs_read_u32(bs); ptr->times[i] = gf_bs_read_u32(bs); @@ -8031,7 +7988,7 @@ GF_Err pdin_Read(GF_Box *s, GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err pdin_Write(GF_Box *s, GF_BitStream *bs) +GF_Err pdin_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -8045,7 +8002,7 @@ GF_Err pdin_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err pdin_Size(GF_Box *s) +GF_Err pdin_box_size(GF_Box *s) { GF_ProgressiveDownloadBox *ptr = (GF_ProgressiveDownloadBox *)s; ptr->size += 8*ptr->count; @@ -8057,15 +8014,14 @@ GF_Err pdin_Size(GF_Box *s) -GF_Box *sdtp_New() +GF_Box *sdtp_box_new() { ISOM_DECL_BOX_ALLOC(GF_SampleDependencyTypeBox, GF_ISOM_BOX_TYPE_SDTP); - tmp->flags = 1; return (GF_Box *)tmp; } -void sdtp_del(GF_Box *s) +void sdtp_box_del(GF_Box *s) { GF_SampleDependencyTypeBox *ptr = (GF_SampleDependencyTypeBox*)s; if (ptr == NULL) return; @@ -8074,7 +8030,7 @@ void sdtp_del(GF_Box *s) } -GF_Err sdtp_Read(GF_Box *s, GF_BitStream *bs) +GF_Err sdtp_box_read(GF_Box *s, GF_BitStream *bs) { GF_SampleDependencyTypeBox *ptr = (GF_SampleDependencyTypeBox*)s; @@ -8083,6 +8039,8 @@ GF_Err sdtp_Read(GF_Box *s, GF_BitStream *bs) else if (ptr->sampleCount > (u32) ptr->size) return GF_ISOM_INVALID_FILE; ptr->sample_info = (u8 *) gf_malloc(sizeof(u8)*ptr->sampleCount); + if (!ptr->sample_info) return GF_OUT_OF_MEM; + ptr->sample_alloc = ptr->sampleCount; gf_bs_read_data(bs, (char*)ptr->sample_info, ptr->sampleCount); ISOM_DECREASE_SIZE(ptr, ptr->sampleCount); return GF_OK; @@ -8091,7 +8049,7 @@ GF_Err sdtp_Read(GF_Box *s, GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err sdtp_Write(GF_Box *s, GF_BitStream *bs) +GF_Err sdtp_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_SampleDependencyTypeBox *ptr = (GF_SampleDependencyTypeBox *)s; @@ -8101,7 +8059,7 @@ GF_Err sdtp_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err sdtp_Size(GF_Box *s) +GF_Err sdtp_box_size(GF_Box *s) { GF_SampleDependencyTypeBox *ptr = (GF_SampleDependencyTypeBox *)s; ptr->size += ptr->sampleCount; @@ -8111,14 +8069,14 @@ GF_Err sdtp_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *pasp_New() +GF_Box *pasp_box_new() { ISOM_DECL_BOX_ALLOC(GF_PixelAspectRatioBox, GF_ISOM_BOX_TYPE_PASP); return (GF_Box *)tmp; } -void pasp_del(GF_Box *s) +void pasp_box_del(GF_Box *s) { GF_PixelAspectRatioBox *ptr = (GF_PixelAspectRatioBox*)s; if (ptr == NULL) return; @@ -8126,19 +8084,19 @@ void pasp_del(GF_Box *s) } -GF_Err pasp_Read(GF_Box *s, GF_BitStream *bs) +GF_Err pasp_box_read(GF_Box *s, GF_BitStream *bs) { GF_PixelAspectRatioBox *ptr = (GF_PixelAspectRatioBox*)s; + ISOM_DECREASE_SIZE(ptr, 8); ptr->hSpacing = gf_bs_read_u32(bs); ptr->vSpacing = gf_bs_read_u32(bs); - ISOM_DECREASE_SIZE(ptr, 8); return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err pasp_Write(GF_Box *s, GF_BitStream *bs) +GF_Err pasp_box_write(GF_Box *s, GF_BitStream *bs) { GF_PixelAspectRatioBox *ptr = (GF_PixelAspectRatioBox *)s; GF_Err e = gf_isom_box_write_header(s, bs); @@ -8148,7 +8106,7 @@ GF_Err pasp_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err pasp_Size(GF_Box *s) +GF_Err pasp_box_size(GF_Box *s) { s->size += 8; return GF_OK; @@ -8158,24 +8116,24 @@ GF_Err pasp_Size(GF_Box *s) -GF_Box *clap_New() +GF_Box *clap_box_new() { - ISOM_DECL_BOX_ALLOC(GF_CleanAppertureBox, GF_ISOM_BOX_TYPE_CLAP); + ISOM_DECL_BOX_ALLOC(GF_CleanApertureBox, GF_ISOM_BOX_TYPE_CLAP); return (GF_Box *)tmp; } -void clap_del(GF_Box *s) +void clap_box_del(GF_Box *s) { - GF_CleanAppertureBox *ptr = (GF_CleanAppertureBox*)s; + GF_CleanApertureBox *ptr = (GF_CleanApertureBox*)s; if (ptr == NULL) return; gf_free(ptr); } -GF_Err clap_Read(GF_Box *s, GF_BitStream *bs) +GF_Err clap_box_read(GF_Box *s, GF_BitStream *bs) { - GF_CleanAppertureBox *ptr = (GF_CleanAppertureBox*)s; + GF_CleanApertureBox *ptr = (GF_CleanApertureBox*)s; ISOM_DECREASE_SIZE(ptr, 32); ptr->cleanApertureWidthN = gf_bs_read_u32(bs); ptr->cleanApertureWidthD = gf_bs_read_u32(bs); @@ -8191,9 +8149,9 @@ GF_Err clap_Read(GF_Box *s, GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err clap_Write(GF_Box *s, GF_BitStream *bs) +GF_Err clap_box_write(GF_Box *s, GF_BitStream *bs) { - GF_CleanAppertureBox *ptr = (GF_CleanAppertureBox *)s; + GF_CleanApertureBox *ptr = (GF_CleanApertureBox *)s; GF_Err e = gf_isom_box_write_header(s, bs); if (e) return e; gf_bs_write_u32(bs, ptr->cleanApertureWidthN); @@ -8207,7 +8165,7 @@ GF_Err clap_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err clap_Size(GF_Box *s) +GF_Err clap_box_size(GF_Box *s) { s->size += 32; return GF_OK; @@ -8218,7 +8176,7 @@ GF_Err clap_Size(GF_Box *s) -GF_Box *metx_New() +GF_Box *metx_box_new() { //type is overridden by the box constructor ISOM_DECL_BOX_ALLOC(GF_MetaDataSampleEntryBox, GF_ISOM_BOX_TYPE_METX); @@ -8227,7 +8185,7 @@ GF_Box *metx_New() } -void metx_del(GF_Box *s) +void metx_box_del(GF_Box *s) { GF_MetaDataSampleEntryBox *ptr = (GF_MetaDataSampleEntryBox*)s; if (ptr == NULL) return; @@ -8237,30 +8195,26 @@ void metx_del(GF_Box *s) if (ptr->xml_namespace) gf_free(ptr->xml_namespace); if (ptr->xml_schema_loc) gf_free(ptr->xml_schema_loc); if (ptr->mime_type) gf_free(ptr->mime_type); - if (ptr->config) gf_isom_box_del((GF_Box *)ptr->config); gf_free(ptr); } -GF_Err metx_AddBox(GF_Box *s, GF_Box *a) +GF_Err metx_on_child_box(GF_Box *s, GF_Box *a) { GF_MetaDataSampleEntryBox *ptr = (GF_MetaDataSampleEntryBox *)s; switch (a->type) { case GF_ISOM_BOX_TYPE_SINF: - gf_list_add(ptr->protections, a); - break; + return GF_OK; case GF_ISOM_BOX_TYPE_TXTC: //we allow the config box on metx if (ptr->config) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->config = (GF_TextConfigBox *)a; + ptr->config = (GF_TextConfigBox *)a; break; - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err metx_Read(GF_Box *s, GF_BitStream *bs) +GF_Err metx_box_read(GF_Box *s, GF_BitStream *bs) { u32 size, i; GF_Err e; @@ -8269,20 +8223,29 @@ GF_Err metx_Read(GF_Box *s, GF_BitStream *bs) e = gf_isom_base_sample_entry_read((GF_SampleEntryBox *)ptr, bs); if (e) return e; + ISOM_DECREASE_SIZE(ptr, 8); - size = (u32) ptr->size - 8; + size = (u32) ptr->size; str = gf_malloc(sizeof(char)*size); + if (!str) return GF_OUT_OF_MEM; i=0; while (size) { str[i] = gf_bs_read_u8(bs); size--; - if (!str[i]) + if (!str[i]) { + i++; break; + } i++; } - if (i) { + if (!size && i>1 && str[i-1]) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] metx read invalid string\n")); + gf_free(str); + return GF_ISOM_INVALID_FILE; + } + if (i>1) { if (ptr->type==GF_ISOM_BOX_TYPE_STPP) { ptr->xml_namespace = gf_strdup(str); } else { @@ -8294,12 +8257,19 @@ GF_Err metx_Read(GF_Box *s, GF_BitStream *bs) while (size) { str[i] = gf_bs_read_u8(bs); size--; - if (!str[i]) + if (!str[i]) { + i++; break; + } i++; } + if (!size && i>1 && str[i-1]) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] metx read invalid string\n")); + gf_free(str); + return GF_ISOM_INVALID_FILE; + } if ((ptr->type==GF_ISOM_BOX_TYPE_METX) || (ptr->type==GF_ISOM_BOX_TYPE_STPP)) { - if (i) { + if (i>1) { if (ptr->type==GF_ISOM_BOX_TYPE_STPP) { ptr->xml_schema_loc = gf_strdup(str); } else { @@ -8311,11 +8281,18 @@ GF_Err metx_Read(GF_Box *s, GF_BitStream *bs) while (size) { str[i] = gf_bs_read_u8(bs); size--; - if (!str[i]) + if (!str[i]) { + i++; break; + } i++; } - if (i) { + if (!size && i>1 && str[i-1]) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] metx read invalid string\n")); + gf_free(str); + return GF_ISOM_INVALID_FILE; + } + if (i>1) { if (ptr->type==GF_ISOM_BOX_TYPE_STPP) { ptr->mime_type = gf_strdup(str); } else { @@ -8325,17 +8302,17 @@ GF_Err metx_Read(GF_Box *s, GF_BitStream *bs) } //mett, sbtt, stxt, stpp else { - if (i) ptr->mime_type = gf_strdup(str); + if (i>1) ptr->mime_type = gf_strdup(str); } ptr->size = size; gf_free(str); - return gf_isom_box_array_read(s, bs, metx_AddBox); + return gf_isom_box_array_read(s, bs, metx_on_child_box); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err metx_Write(GF_Box *s, GF_BitStream *bs) +GF_Err metx_box_write(GF_Box *s, GF_BitStream *bs) { GF_MetaDataSampleEntryBox *ptr = (GF_MetaDataSampleEntryBox *)s; GF_Err e = gf_isom_box_write_header(s, bs); @@ -8373,18 +8350,13 @@ GF_Err metx_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_data(bs, ptr->mime_type, (u32) strlen(ptr->mime_type)); gf_bs_write_u8(bs, 0); - - if (ptr->config) { - gf_isom_box_write((GF_Box *)ptr->config, bs); - } } - return gf_isom_box_array_write(s, ptr->protections, bs); + return GF_OK; } -GF_Err metx_Size(GF_Box *s) +GF_Err metx_box_size(GF_Box *s) { - GF_Err e; GF_MetaDataSampleEntryBox *ptr = (GF_MetaDataSampleEntryBox *)s; ptr->size += 8; @@ -8416,28 +8388,22 @@ GF_Err metx_Size(GF_Box *s) if (ptr->mime_type) ptr->size += strlen(ptr->mime_type); ptr->size++; - - if (ptr->config) { - e = gf_isom_box_size((GF_Box *)ptr->config); - if (e) return e; - ptr->size += ptr->config->size; - } } - return gf_isom_box_array_size(s, ptr->protections); + return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ /* SimpleTextSampleEntry */ -GF_Box *txtc_New() +GF_Box *txtc_box_new() { ISOM_DECL_BOX_ALLOC(GF_TextConfigBox, GF_ISOM_BOX_TYPE_TXTC); return (GF_Box *)tmp; } -void txtc_del(GF_Box *s) +void txtc_box_del(GF_Box *s) { GF_TextConfigBox *ptr = (GF_TextConfigBox*)s; if (ptr == NULL) return; @@ -8446,34 +8412,20 @@ void txtc_del(GF_Box *s) gf_free(ptr); } -GF_Err txtc_Read(GF_Box *s, GF_BitStream *bs) +GF_Err txtc_box_read(GF_Box *s, GF_BitStream *bs) { - u32 size, i; - char *str; GF_TextConfigBox *ptr = (GF_TextConfigBox*)s; - - size = (u32) ptr->size; - str = (char *)gf_malloc(sizeof(char)*size); - - i=0; - - while (size) { - str[i] = gf_bs_read_u8(bs); - size--; - if (!str[i]) - break; - i++; - } - if (i) ptr->config = gf_strdup(str); - gf_free(str); - + ptr->config = (char *)gf_malloc(sizeof(char)*((u32) ptr->size+1)); + if (!ptr->config) return GF_OUT_OF_MEM; + gf_bs_read_data(bs, ptr->config, (u32) ptr->size); + ptr->config[ptr->size] = 0; return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err txtc_Write(GF_Box *s, GF_BitStream *bs) +GF_Err txtc_box_write(GF_Box *s, GF_BitStream *bs) { GF_TextConfigBox *ptr = (GF_TextConfigBox *)s; GF_Err e = gf_isom_full_box_write(s, bs); @@ -8485,7 +8437,7 @@ GF_Err txtc_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err txtc_Size(GF_Box *s) +GF_Err txtc_box_size(GF_Box *s) { GF_TextConfigBox *ptr = (GF_TextConfigBox *)s; if (ptr->config) @@ -8496,66 +8448,38 @@ GF_Err txtc_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *dac3_New() +GF_Box *dac3_box_new() { ISOM_DECL_BOX_ALLOC(GF_AC3ConfigBox, GF_ISOM_BOX_TYPE_DAC3); return (GF_Box *)tmp; } -GF_Box *dec3_New() +GF_Box *dec3_box_new() { ISOM_DECL_BOX_ALLOC(GF_AC3ConfigBox, GF_ISOM_BOX_TYPE_DAC3); tmp->cfg.is_ec3 = 1; return (GF_Box *)tmp; } -void dac3_del(GF_Box *s) +void dac3_box_del(GF_Box *s) { GF_AC3ConfigBox *ptr = (GF_AC3ConfigBox *)s; gf_free(ptr); } +GF_Err gf_isom_ac3_config_parse_bs(GF_BitStream *bs, Bool is_ec3, GF_AC3Config *cfg); -GF_Err dac3_Read(GF_Box *s, GF_BitStream *bs) +GF_Err dac3_box_read(GF_Box *s, GF_BitStream *bs) { GF_AC3ConfigBox *ptr = (GF_AC3ConfigBox *)s; if (ptr == NULL) return GF_BAD_PARAM; - - if (ptr->cfg.is_ec3) { - u32 i; - ptr->cfg.brcode = gf_bs_read_int(bs, 13); - ptr->cfg.nb_streams = gf_bs_read_int(bs, 3) + 1; - for (i=0; icfg.nb_streams; i++) { - ptr->cfg.streams[i].fscod = gf_bs_read_int(bs, 2); - ptr->cfg.streams[i].bsid = gf_bs_read_int(bs, 5); - ptr->cfg.streams[i].bsmod = gf_bs_read_int(bs, 5); - ptr->cfg.streams[i].acmod = gf_bs_read_int(bs, 3); - ptr->cfg.streams[i].lfon = gf_bs_read_int(bs, 1); - gf_bs_read_int(bs, 3); - ptr->cfg.streams[i].nb_dep_sub = gf_bs_read_int(bs, 4); - if (ptr->cfg.streams[i].nb_dep_sub) { - ptr->cfg.streams[i].chan_loc = gf_bs_read_int(bs, 9); - } else { - gf_bs_read_int(bs, 1); - } - } - } else { - ptr->cfg.nb_streams = 1; - ptr->cfg.streams[0].fscod = gf_bs_read_int(bs, 2); - ptr->cfg.streams[0].bsid = gf_bs_read_int(bs, 5); - ptr->cfg.streams[0].bsmod = gf_bs_read_int(bs, 3); - ptr->cfg.streams[0].acmod = gf_bs_read_int(bs, 3); - ptr->cfg.streams[0].lfon = gf_bs_read_int(bs, 1); - ptr->cfg.brcode = gf_bs_read_int(bs, 5); - gf_bs_read_int(bs, 5); - } - return GF_OK; + return gf_isom_ac3_config_parse_bs(bs, ptr->cfg.is_ec3, &ptr->cfg); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err dac3_Write(GF_Box *s, GF_BitStream *bs) +GF_Err dac3_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_AC3ConfigBox *ptr = (GF_AC3ConfigBox *)s; @@ -8595,7 +8519,7 @@ GF_Err dac3_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err dac3_Size(GF_Box *s) +GF_Err dac3_box_size(GF_Box *s) { GF_AC3ConfigBox *ptr = (GF_AC3ConfigBox *)s; @@ -8617,7 +8541,7 @@ GF_Err dac3_Size(GF_Box *s) -void lsrc_del(GF_Box *s) +void lsrc_box_del(GF_Box *s) { GF_LASERConfigurationBox *ptr = (GF_LASERConfigurationBox *)s; if (ptr == NULL) return; @@ -8626,16 +8550,17 @@ void lsrc_del(GF_Box *s) } -GF_Err lsrc_Read(GF_Box *s, GF_BitStream *bs) +GF_Err lsrc_box_read(GF_Box *s, GF_BitStream *bs) { GF_LASERConfigurationBox *ptr = (GF_LASERConfigurationBox *)s; ptr->hdr_size = (u32) ptr->size; ptr->hdr = gf_malloc(sizeof(char)*ptr->hdr_size); + if (!ptr->hdr) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->hdr, ptr->hdr_size); return GF_OK; } -GF_Box *lsrc_New() +GF_Box *lsrc_box_new() { ISOM_DECL_BOX_ALLOC(GF_LASERConfigurationBox, GF_ISOM_BOX_TYPE_LSRC); return (GF_Box *)tmp; @@ -8644,7 +8569,7 @@ GF_Box *lsrc_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err lsrc_Write(GF_Box *s, GF_BitStream *bs) +GF_Err lsrc_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_LASERConfigurationBox *ptr = (GF_LASERConfigurationBox *)s; @@ -8654,7 +8579,7 @@ GF_Err lsrc_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err lsrc_Size(GF_Box *s) +GF_Err lsrc_box_size(GF_Box *s) { GF_LASERConfigurationBox *ptr = (GF_LASERConfigurationBox *)s; ptr->size += ptr->hdr_size; @@ -8664,37 +8589,32 @@ GF_Err lsrc_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void lsr1_del(GF_Box *s) +void lsr1_box_del(GF_Box *s) { GF_LASeRSampleEntryBox *ptr = (GF_LASeRSampleEntryBox *)s; if (ptr == NULL) return; gf_isom_sample_entry_predestroy((GF_SampleEntryBox *)s); - if (ptr->slc) gf_odf_desc_del((GF_Descriptor *)ptr->slc); - if (ptr->lsr_config) gf_isom_box_del((GF_Box *) ptr->lsr_config); - if (ptr->descr) gf_isom_box_del((GF_Box *) ptr->descr); gf_free(ptr); } -GF_Err lsr1_AddBox(GF_Box *s, GF_Box *a) +GF_Err lsr1_on_child_box(GF_Box *s, GF_Box *a) { GF_LASeRSampleEntryBox *ptr = (GF_LASeRSampleEntryBox *)s; switch (a->type) { case GF_ISOM_BOX_TYPE_LSRC: if (ptr->lsr_config) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->lsr_config = (GF_LASERConfigurationBox *)a; + ptr->lsr_config = (GF_LASERConfigurationBox *)a; break; case GF_ISOM_BOX_TYPE_M4DS: if (ptr->descr) ERROR_ON_DUPLICATED_BOX(a, ptr) - ptr->descr = (GF_MPEG4ExtensionDescriptorsBox *)a; + ptr->descr = (GF_MPEG4ExtensionDescriptorsBox *)a; break; - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err lsr1_Read(GF_Box *s, GF_BitStream *bs) +GF_Err lsr1_box_read(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_LASeRSampleEntryBox *ptr = (GF_LASeRSampleEntryBox*)s; @@ -8704,10 +8624,10 @@ GF_Err lsr1_Read(GF_Box *s, GF_BitStream *bs) ISOM_DECREASE_SIZE(ptr, 8); - return gf_isom_box_array_read(s, bs, lsr1_AddBox); + return gf_isom_box_array_read(s, bs, lsr1_on_child_box); } -GF_Box *lsr1_New() +GF_Box *lsr1_box_new() { ISOM_DECL_BOX_ALLOC(GF_LASeRSampleEntryBox, GF_ISOM_BOX_TYPE_LSR1); gf_isom_sample_entry_init((GF_SampleEntryBox*)tmp); @@ -8718,7 +8638,7 @@ GF_Box *lsr1_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err lsr1_Write(GF_Box *s, GF_BitStream *bs) +GF_Err lsr1_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_LASeRSampleEntryBox *ptr = (GF_LASeRSampleEntryBox *)s; @@ -8727,40 +8647,22 @@ GF_Err lsr1_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_data(bs, ptr->reserved, 6); gf_bs_write_u16(bs, ptr->dataReferenceIndex); - if (ptr->lsr_config) { - e = gf_isom_box_write((GF_Box *)ptr->lsr_config, bs); - if (e) return e; - } - if (ptr->descr) { - e = gf_isom_box_write((GF_Box *)ptr->descr, bs); - if (e) return e; - } - return e; + return GF_OK; } -GF_Err lsr1_Size(GF_Box *s) +GF_Err lsr1_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_LASeRSampleEntryBox *ptr = (GF_LASeRSampleEntryBox *)s; - s->size += 8; - if (ptr->lsr_config) { - e = gf_isom_box_size((GF_Box *)ptr->lsr_config); - if (e) return e; - ptr->size += ptr->lsr_config->size; - } - if (ptr->descr) { - e = gf_isom_box_size((GF_Box *)ptr->descr); - if (e) return e; - ptr->size += ptr->descr->size; - } + gf_isom_check_position(s, (GF_Box *)ptr->lsr_config, &pos); return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void sidx_del(GF_Box *s) +void sidx_box_del(GF_Box *s) { GF_SegmentIndexBox *ptr = (GF_SegmentIndexBox *) s; if (ptr == NULL) return; @@ -8768,29 +8670,30 @@ void sidx_del(GF_Box *s) gf_free(ptr); } -GF_Err sidx_Read(GF_Box *s,GF_BitStream *bs) +GF_Err sidx_box_read(GF_Box *s,GF_BitStream *bs) { u32 i; GF_SegmentIndexBox *ptr = (GF_SegmentIndexBox*) s; + ISOM_DECREASE_SIZE(ptr, 8); ptr->reference_ID = gf_bs_read_u32(bs); ptr->timescale = gf_bs_read_u32(bs); - ISOM_DECREASE_SIZE(ptr, 8); if (ptr->version==0) { + ISOM_DECREASE_SIZE(ptr, 8); ptr->earliest_presentation_time = gf_bs_read_u32(bs); ptr->first_offset = gf_bs_read_u32(bs); - ISOM_DECREASE_SIZE(ptr, 8); } else { + ISOM_DECREASE_SIZE(ptr, 16); ptr->earliest_presentation_time = gf_bs_read_u64(bs); ptr->first_offset = gf_bs_read_u64(bs); - ISOM_DECREASE_SIZE(ptr, 16); } + ISOM_DECREASE_SIZE(ptr, 4); gf_bs_read_u16(bs); /* reserved */ ptr->nb_refs = gf_bs_read_u16(bs); - ISOM_DECREASE_SIZE(ptr, 4); ptr->refs = gf_malloc(sizeof(GF_SIDXReference)*ptr->nb_refs); + if (!ptr->refs) return GF_OUT_OF_MEM; for (i=0; inb_refs; i++) { ptr->refs[i].reference_type = gf_bs_read_int(bs, 1); ptr->refs[i].reference_size = gf_bs_read_int(bs, 31); @@ -8804,7 +8707,7 @@ GF_Err sidx_Read(GF_Box *s,GF_BitStream *bs) return GF_OK; } -GF_Box *sidx_New() +GF_Box *sidx_box_new() { ISOM_DECL_BOX_ALLOC(GF_SegmentIndexBox, GF_ISOM_BOX_TYPE_SIDX); return (GF_Box *)tmp; @@ -8813,7 +8716,7 @@ GF_Box *sidx_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err sidx_Write(GF_Box *s, GF_BitStream *bs) +GF_Err sidx_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -8844,7 +8747,7 @@ GF_Err sidx_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err sidx_Size(GF_Box *s) +GF_Err sidx_box_size(GF_Box *s) { GF_SegmentIndexBox *ptr = (GF_SegmentIndexBox*) s; @@ -8860,49 +8763,54 @@ GF_Err sidx_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void ssix_del(GF_Box *s) +void ssix_box_del(GF_Box *s) { u32 i; GF_SubsegmentIndexBox *ptr = (GF_SubsegmentIndexBox *)s; if (ptr == NULL) return; if (ptr->subsegments) { - for (i = 0; i < ptr->subsegment_count; i++) { - GF_Subsegment *subsegment = &ptr->subsegments[i]; - if (subsegment->levels) gf_free(subsegment->levels); - if (subsegment->range_sizes) gf_free(subsegment->range_sizes); + for (i = 0; i < ptr->subsegment_alloc; i++) { + GF_SubsegmentInfo *subsegment = &ptr->subsegments[i]; + if (subsegment->ranges) gf_free(subsegment->ranges); } gf_free(ptr->subsegments); } gf_free(ptr); } -GF_Err ssix_Read(GF_Box *s, GF_BitStream *bs) +GF_Err ssix_box_read(GF_Box *s, GF_BitStream *bs) { u32 i,j; GF_SubsegmentIndexBox *ptr = (GF_SubsegmentIndexBox*)s; - if (ptr->size < 4) return GF_BAD_PARAM; + ISOM_DECREASE_SIZE(ptr, 4) ptr->subsegment_count = gf_bs_read_u32(bs); - ptr->size -= 4; - ptr->subsegments = gf_malloc(ptr->subsegment_count*sizeof(GF_Subsegment)); + //each subseg has at least one range_count (4 bytes), abort if not enough bytes (broken box) + if (ptr->size < ptr->subsegment_count*4) + return GF_ISOM_INVALID_FILE; + + GF_SAFE_ALLOC_N(ptr->subsegments, ptr->subsegment_count, GF_SubsegmentInfo); + if (!ptr->subsegments) + return GF_OUT_OF_MEM; for (i = 0; i < ptr->subsegment_count; i++) { - GF_Subsegment *subseg = &ptr->subsegments[i]; - if (ptr->size < 4) return GF_BAD_PARAM; + GF_SubsegmentInfo *subseg = &ptr->subsegments[i]; + ISOM_DECREASE_SIZE(ptr, 4) subseg->range_count = gf_bs_read_u32(bs); - ptr->size -= 4; - if (ptr->size < subseg->range_count*4) return GF_BAD_PARAM; - subseg->levels = gf_malloc(sizeof(u8)*subseg->range_count); - subseg->range_sizes = gf_malloc(sizeof(u32)*subseg->range_count); + //each range is 4 bytes, abort if not enough bytes + if (ptr->size < subseg->range_count*4) + return GF_ISOM_INVALID_FILE; + subseg->ranges = (GF_SubsegmentRangeInfo*) gf_malloc(sizeof(GF_SubsegmentRangeInfo) * subseg->range_count); + if (!subseg->ranges) return GF_OUT_OF_MEM; for (j = 0; j < subseg->range_count; j++) { - subseg->levels[j] = gf_bs_read_u8(bs); - subseg->range_sizes[j] = gf_bs_read_u24(bs); - ptr->size -= 4; + ISOM_DECREASE_SIZE(ptr, 4) + subseg->ranges[j].level = gf_bs_read_u8(bs); + subseg->ranges[j].range_size = gf_bs_read_u24(bs); } } return GF_OK; } -GF_Box *ssix_New() +GF_Box *ssix_box_new() { ISOM_DECL_BOX_ALLOC(GF_SubsegmentIndexBox, GF_ISOM_BOX_TYPE_SSIX); return (GF_Box *)tmp; @@ -8911,7 +8819,7 @@ GF_Box *ssix_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err ssix_Write(GF_Box *s, GF_BitStream *bs) +GF_Err ssix_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i, j; @@ -8923,15 +8831,15 @@ GF_Err ssix_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u32(bs, ptr->subsegment_count); for (i = 0; isubsegment_count; i++) { gf_bs_write_u32(bs, ptr->subsegments[i].range_count); - for (j = 0; j < ptr->subsegment_count; j++) { - gf_bs_write_u8(bs, ptr->subsegments[i].levels[j]); - gf_bs_write_u24(bs, ptr->subsegments[i].range_sizes[j]); + for (j = 0; j < ptr->subsegments[i].range_count; j++) { + gf_bs_write_u8(bs, ptr->subsegments[i].ranges[j].level); + gf_bs_write_u24(bs, ptr->subsegments[i].ranges[j].range_size); } } return GF_OK; } -GF_Err ssix_Size(GF_Box *s) +GF_Err ssix_box_size(GF_Box *s) { u32 i; GF_SubsegmentIndexBox *ptr = (GF_SubsegmentIndexBox*)s; @@ -8945,7 +8853,7 @@ GF_Err ssix_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void leva_del(GF_Box *s) +void leva_box_del(GF_Box *s) { GF_LevelAssignmentBox *ptr = (GF_LevelAssignmentBox *)s; if (ptr == NULL) return; @@ -8953,38 +8861,48 @@ void leva_del(GF_Box *s) gf_free(ptr); } -GF_Err leva_Read(GF_Box *s, GF_BitStream *bs) +GF_Err leva_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; GF_LevelAssignmentBox *ptr = (GF_LevelAssignmentBox*)s; - if (ptr->size < 4) return GF_BAD_PARAM; + ISOM_DECREASE_SIZE(ptr, 1) ptr->level_count = gf_bs_read_u8(bs); - ptr->size -= 4; + //each level is at least 5 bytes + if (ptr->size < ptr->level_count * 5) + return GF_ISOM_INVALID_FILE; + GF_SAFE_ALLOC_N(ptr->levels, ptr->level_count, GF_LevelAssignment); + if (!ptr->levels) return GF_OUT_OF_MEM; + for (i = 0; i < ptr->level_count; i++) { GF_LevelAssignment *level = &ptr->levels[i]; u8 tmp; - if (ptr->size < 5) return GF_BAD_PARAM; + if (!level || ptr->size < 5) return GF_BAD_PARAM; + ISOM_DECREASE_SIZE(ptr, 5) + level->track_id = gf_bs_read_u32(bs); tmp = gf_bs_read_u8(bs); level->padding_flag = tmp >> 7; level->type = tmp & 0x7F; if (level->type == 0) { + ISOM_DECREASE_SIZE(ptr, 4) level->grouping_type = gf_bs_read_u32(bs); } else if (level->type == 1) { + ISOM_DECREASE_SIZE(ptr, 8) level->grouping_type = gf_bs_read_u32(bs); level->grouping_type_parameter = gf_bs_read_u32(bs); } else if (level->type == 4) { + ISOM_DECREASE_SIZE(ptr, 4) level->sub_track_id = gf_bs_read_u32(bs); } } return GF_OK; } -GF_Box *leva_New() +GF_Box *leva_box_new() { ISOM_DECL_BOX_ALLOC(GF_LevelAssignmentBox, GF_ISOM_BOX_TYPE_LEVA); return (GF_Box *)tmp; @@ -8993,7 +8911,7 @@ GF_Box *leva_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err leva_Write(GF_Box *s, GF_BitStream *bs) +GF_Err leva_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -9020,7 +8938,7 @@ GF_Err leva_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err leva_Size(GF_Box *s) +GF_Err leva_box_size(GF_Box *s) { u32 i; GF_LevelAssignmentBox *ptr = (GF_LevelAssignmentBox*)s; @@ -9039,13 +8957,13 @@ GF_Err leva_Size(GF_Box *s) } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *pcrb_New() +GF_Box *pcrb_box_new() { ISOM_DECL_BOX_ALLOC(GF_PcrInfoBox, GF_ISOM_BOX_TYPE_PCRB); return (GF_Box *)tmp; } -void pcrb_del(GF_Box *s) +void pcrb_box_del(GF_Box *s) { GF_PcrInfoBox *ptr = (GF_PcrInfoBox *) s; if (ptr == NULL) return; @@ -9053,15 +8971,16 @@ void pcrb_del(GF_Box *s) gf_free(ptr); } -GF_Err pcrb_Read(GF_Box *s,GF_BitStream *bs) +GF_Err pcrb_box_read(GF_Box *s,GF_BitStream *bs) { u32 i; GF_PcrInfoBox *ptr = (GF_PcrInfoBox*) s; - ptr->subsegment_count = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); + ptr->subsegment_count = gf_bs_read_u32(bs); ptr->pcr_values = gf_malloc(sizeof(u64)*ptr->subsegment_count); + if (!ptr->pcr_values) return GF_OUT_OF_MEM; for (i=0; isubsegment_count; i++) { u64 data1 = gf_bs_read_u32(bs); u64 data2 = gf_bs_read_u16(bs); @@ -9074,7 +8993,7 @@ GF_Err pcrb_Read(GF_Box *s,GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err pcrb_Write(GF_Box *s, GF_BitStream *bs) +GF_Err pcrb_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -9095,7 +9014,7 @@ GF_Err pcrb_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err pcrb_Size(GF_Box *s) +GF_Err pcrb_box_size(GF_Box *s) { GF_PcrInfoBox *ptr = (GF_PcrInfoBox*) s; @@ -9108,14 +9027,14 @@ GF_Err pcrb_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *subs_New() +GF_Box *subs_box_new() { ISOM_DECL_BOX_ALLOC(GF_SubSampleInformationBox, GF_ISOM_BOX_TYPE_SUBS); tmp->Samples = gf_list_new(); return (GF_Box *)tmp; } -void subs_del(GF_Box *s) +void subs_box_del(GF_Box *s) { GF_SubSampleInformationBox *ptr = (GF_SubSampleInformationBox *)s; if (ptr == NULL) return; @@ -9140,12 +9059,11 @@ void subs_del(GF_Box *s) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err subs_Write(GF_Box *s, GF_BitStream *bs) +GF_Err subs_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i, j, entry_count; u16 subsample_count; - GF_SubSampleInfoEntry *pSamp; GF_SubSampleEntry *pSubSamp; GF_SubSampleInformationBox *ptr = (GF_SubSampleInformationBox *) s; @@ -9156,7 +9074,7 @@ GF_Err subs_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u32(bs, entry_count); for (i=0; iSamples, i); + GF_SubSampleInfoEntry *pSamp = (GF_SubSampleInfoEntry*) gf_list_get(ptr->Samples, i); subsample_count = gf_list_count(pSamp->SubSamples); gf_bs_write_u32(bs, pSamp->sample_delta); gf_bs_write_u16(bs, subsample_count); @@ -9176,10 +9094,9 @@ GF_Err subs_Write(GF_Box *s, GF_BitStream *bs) return e; } -GF_Err subs_Size(GF_Box *s) +GF_Err subs_box_size(GF_Box *s) { GF_SubSampleInformationBox *ptr = (GF_SubSampleInformationBox *) s; - GF_SubSampleInfoEntry *pSamp; u32 entry_count, i; u16 subsample_count; @@ -9187,7 +9104,7 @@ GF_Err subs_Size(GF_Box *s) ptr->size += 4; entry_count = gf_list_count(ptr->Samples); for (i=0; iSamples, i); + GF_SubSampleInfoEntry *pSamp = (GF_SubSampleInfoEntry*) gf_list_get(ptr->Samples, i); subsample_count = gf_list_count(pSamp->SubSamples); // 4 byte for sample_delta, 2 byte for subsample_count // and 6 + (4 or 2) bytes for each subsample @@ -9198,14 +9115,14 @@ GF_Err subs_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Err subs_Read(GF_Box *s, GF_BitStream *bs) +GF_Err subs_box_read(GF_Box *s, GF_BitStream *bs) { GF_SubSampleInformationBox *ptr = (GF_SubSampleInformationBox *)s; u32 entry_count, i, j; u16 subsample_count; - entry_count = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); + entry_count = gf_bs_read_u32(bs); for (i=0; iversion==1) { - ptr->baseMediaDecodeTime = gf_bs_read_u64(bs); ISOM_DECREASE_SIZE(ptr, 8); + ptr->baseMediaDecodeTime = gf_bs_read_u64(bs); } else { - ptr->baseMediaDecodeTime = (u32) gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); + ptr->baseMediaDecodeTime = (u32) gf_bs_read_u32(bs); } return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err tfdt_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tfdt_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TFBaseMediaDecodeTimeBox *ptr = (GF_TFBaseMediaDecodeTimeBox *) s; @@ -9289,7 +9206,7 @@ GF_Err tfdt_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err tfdt_Size(GF_Box *s) +GF_Err tfdt_box_size(GF_Box *s) { GF_TFBaseMediaDecodeTimeBox *ptr = (GF_TFBaseMediaDecodeTimeBox *)s; @@ -9308,32 +9225,32 @@ GF_Err tfdt_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_FRAGMENTS*/ -GF_Box *rvcc_New() +GF_Box *rvcc_box_new() { ISOM_DECL_BOX_ALLOC(GF_RVCConfigurationBox, GF_ISOM_BOX_TYPE_RVCC); return (GF_Box *)tmp; } -void rvcc_del(GF_Box *s) +void rvcc_box_del(GF_Box *s) { gf_free(s); } -GF_Err rvcc_Read(GF_Box *s,GF_BitStream *bs) +GF_Err rvcc_box_read(GF_Box *s,GF_BitStream *bs) { GF_RVCConfigurationBox *ptr = (GF_RVCConfigurationBox*)s; - ptr->predefined_rvc_config = gf_bs_read_u16(bs); ISOM_DECREASE_SIZE(ptr, 2); + ptr->predefined_rvc_config = gf_bs_read_u16(bs); if (!ptr->predefined_rvc_config) { - ptr->rvc_meta_idx = gf_bs_read_u16(bs); ISOM_DECREASE_SIZE(ptr, 2); + ptr->rvc_meta_idx = gf_bs_read_u16(bs); } return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err rvcc_Write(GF_Box *s, GF_BitStream *bs) +GF_Err rvcc_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_RVCConfigurationBox *ptr = (GF_RVCConfigurationBox*) s; @@ -9348,7 +9265,7 @@ GF_Err rvcc_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err rvcc_Size(GF_Box *s) +GF_Err rvcc_box_size(GF_Box *s) { GF_RVCConfigurationBox *ptr = (GF_RVCConfigurationBox *)s; ptr->size += 2; @@ -9360,46 +9277,48 @@ GF_Err rvcc_Size(GF_Box *s) -GF_Box *sbgp_New() +GF_Box *sbgp_box_new() { ISOM_DECL_BOX_ALLOC(GF_SampleGroupBox, GF_ISOM_BOX_TYPE_SBGP); return (GF_Box *)tmp; } -void sbgp_del(GF_Box *a) +void sbgp_box_del(GF_Box *a) { GF_SampleGroupBox *p = (GF_SampleGroupBox *)a; if (p->sample_entries) gf_free(p->sample_entries); gf_free(p); } -GF_Err sbgp_Read(GF_Box *s, GF_BitStream *bs) +GF_Err sbgp_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; GF_SampleGroupBox *ptr = (GF_SampleGroupBox *)s; + ISOM_DECREASE_SIZE(ptr, 8); ptr->grouping_type = gf_bs_read_u32(bs); - ISOM_DECREASE_SIZE(ptr, 4); if (ptr->version==1) { - ptr->grouping_type_parameter = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); + ptr->grouping_type_parameter = gf_bs_read_u32(bs); } ptr->entry_count = gf_bs_read_u32(bs); - ISOM_DECREASE_SIZE(ptr, 4); + + if (ptr->size < sizeof(GF_SampleGroupEntry)*ptr->entry_count) + return GF_ISOM_INVALID_FILE; ptr->sample_entries = gf_malloc(sizeof(GF_SampleGroupEntry)*ptr->entry_count); - if (!ptr->sample_entries) return GF_IO_ERR; + if (!ptr->sample_entries) return GF_OUT_OF_MEM; + for (i=0; ientry_count; i++) { + ISOM_DECREASE_SIZE(ptr, 8); ptr->sample_entries[i].sample_count = gf_bs_read_u32(bs); ptr->sample_entries[i].group_description_index = gf_bs_read_u32(bs); - - ISOM_DECREASE_SIZE(ptr, 8); } return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err sbgp_Write(GF_Box *s, GF_BitStream *bs) +GF_Err sbgp_box_write(GF_Box *s, GF_BitStream *bs) { u32 i; GF_Err e; @@ -9419,7 +9338,7 @@ GF_Err sbgp_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err sbgp_Size(GF_Box *s) +GF_Err sbgp_box_size(GF_Box *s) { GF_SampleGroupBox *p = (GF_SampleGroupBox*)s; @@ -9435,7 +9354,9 @@ GF_Err sbgp_Size(GF_Box *s) static void *sgpd_parse_entry(u32 grouping_type, GF_BitStream *bs, u32 entry_size, u32 *total_bytes) { - GF_DefaultSampleGroupDescriptionEntry *ptr; + Bool null_size_ok = GF_FALSE; + GF_DefaultSampleGroupDescriptionEntry *def_ptr; + switch (grouping_type) { case GF_ISOM_SAMPLE_GROUP_ROLL: case GF_ISOM_SAMPLE_GROUP_PROL: @@ -9468,6 +9389,16 @@ static void *sgpd_parse_entry(u32 grouping_type, GF_BitStream *bs, u32 entry_siz *total_bytes = 1; return ptr; } + case GF_ISOM_SAMPLE_GROUP_SYNC: + { + GF_SYNCEntry *ptr; + GF_SAFEALLOC(ptr, GF_SYNCEntry); + if (!ptr) return NULL; + gf_bs_read_int(bs, 2); + ptr->NALU_type = gf_bs_read_int(bs, 6); + *total_bytes = 1; + return ptr; + } case GF_ISOM_SAMPLE_GROUP_TELE: { GF_TemporalLevelEntry *ptr; @@ -9492,7 +9423,11 @@ static void *sgpd_parse_entry(u32 grouping_type, GF_BitStream *bs, u32 entry_siz *total_bytes = 20; if ((ptr->IsProtected == 1) && !ptr->Per_Sample_IV_size) { ptr->constant_IV_size = gf_bs_read_u8(bs); - assert((ptr->constant_IV_size == 8) || (ptr->constant_IV_size == 16)); + if ((ptr->constant_IV_size != 8) && (ptr->constant_IV_size != 16)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] seig sample group have invalid constant_IV size\n")); + gf_free(ptr); + return NULL; + } gf_bs_read_data(bs, (char *)ptr->constant_IV, ptr->constant_IV_size); *total_bytes += 1 + ptr->constant_IV_size; } @@ -9550,32 +9485,51 @@ static void *sgpd_parse_entry(u32 grouping_type, GF_BitStream *bs, u32 entry_siz rle = gf_bs_read_int(bs, 1); entry_count = gf_bs_read_int(bs, large_size ? 16 : 8); gf_bs_seek(bs, start); - entry_size = 1 + large_size ? 2 : 1; + entry_size = 1 + (large_size ? 2 : 1); entry_size += entry_count * 2; if (rle) entry_size += entry_count * (large_size ? 2 : 1); GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] nalm sample group does not indicate entry size, deprecated in spec\n")); } break; + + case GF_ISOM_SAMPLE_GROUP_TSAS: + case GF_ISOM_SAMPLE_GROUP_STSA: + null_size_ok = GF_TRUE; + break; + //TODO, add support for these ones ? + case GF_ISOM_SAMPLE_GROUP_TSCL: + entry_size = 20; + break; + case GF_ISOM_SAMPLE_GROUP_LBLI: + entry_size = 2; + break; default: break; } - if (!entry_size) { - GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] %s sample group does not indicate entry size, cannot parse!\n", gf_4cc_to_str( grouping_type) )); + if (!entry_size && !null_size_ok) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] %s sample group does not indicate entry size and is not implemented, cannot parse!\n", gf_4cc_to_str( grouping_type) )); return NULL; } - GF_SAFEALLOC(ptr, GF_DefaultSampleGroupDescriptionEntry); - if (!ptr) return NULL; - ptr->length = entry_size; - ptr->data = (u8 *) gf_malloc(sizeof(u8)*ptr->length); - gf_bs_read_data(bs, (char *) ptr->data, ptr->length); - *total_bytes = entry_size; - return ptr; + GF_SAFEALLOC(def_ptr, GF_DefaultSampleGroupDescriptionEntry); + if (!def_ptr) return NULL; + if (entry_size) { + def_ptr->length = entry_size; + def_ptr->data = (u8 *) gf_malloc(sizeof(u8)*def_ptr->length); + if (!def_ptr->data) { + gf_free(def_ptr); + return NULL; + } + gf_bs_read_data(bs, (char *) def_ptr->data, def_ptr->length); + *total_bytes = entry_size; + } + return def_ptr; } static void sgpd_del_entry(u32 grouping_type, void *entry) { switch (grouping_type) { + case GF_ISOM_SAMPLE_GROUP_SYNC: case GF_ISOM_SAMPLE_GROUP_ROLL: case GF_ISOM_SAMPLE_GROUP_PROL: case GF_ISOM_SAMPLE_GROUP_RAP: @@ -9615,6 +9569,10 @@ void sgpd_write_entry(u32 grouping_type, void *entry, GF_BitStream *bs) gf_bs_write_int(bs, 0, 3); gf_bs_write_int(bs, ((GF_SAPEntry*)entry)->SAP_type, 4); return; + case GF_ISOM_SAMPLE_GROUP_SYNC: + gf_bs_write_int(bs, 0, 2); + gf_bs_write_int(bs, ((GF_SYNCEntry*)entry)->NALU_type, 6); + return; case GF_ISOM_SAMPLE_GROUP_TELE: gf_bs_write_int(bs, ((GF_TemporalLevelEntry*)entry)->level_independently_decodable, 1); gf_bs_write_int(bs, 0, 7); @@ -9640,7 +9598,8 @@ void sgpd_write_entry(u32 grouping_type, void *entry, GF_BitStream *bs) default: { GF_DefaultSampleGroupDescriptionEntry *ptr = (GF_DefaultSampleGroupDescriptionEntry *)entry; - gf_bs_write_data(bs, (char *) ptr->data, ptr->length); + if (ptr->length) + gf_bs_write_data(bs, (char *) ptr->data, ptr->length); } } } @@ -9655,7 +9614,15 @@ static u32 sgpd_size_entry(u32 grouping_type, void *entry) case GF_ISOM_SAMPLE_GROUP_TELE: case GF_ISOM_SAMPLE_GROUP_RAP: case GF_ISOM_SAMPLE_GROUP_SAP: + case GF_ISOM_SAMPLE_GROUP_SYNC: return 1; + case GF_ISOM_SAMPLE_GROUP_TSCL: + return 20; + case GF_ISOM_SAMPLE_GROUP_LBLI: + return 2; + case GF_ISOM_SAMPLE_GROUP_TSAS: + case GF_ISOM_SAMPLE_GROUP_STSA: + return 0; case GF_ISOM_SAMPLE_GROUP_SEIG: return ((((GF_CENCSampleEncryptionGroupEntry *)entry)->IsProtected == 1) && !((GF_CENCSampleEncryptionGroupEntry *)entry)->Per_Sample_IV_size) ? 21 + ((GF_CENCSampleEncryptionGroupEntry *)entry)->constant_IV_size : 20; case GF_ISOM_SAMPLE_GROUP_OINF: @@ -9668,7 +9635,7 @@ static u32 sgpd_size_entry(u32 grouping_type, void *entry) } #endif -GF_Box *sgpd_New() +GF_Box *sgpd_box_new() { ISOM_DECL_BOX_ALLOC(GF_SampleGroupDescriptionBox, GF_ISOM_BOX_TYPE_SGPD); /*version 0 is deprecated, use v1 by default*/ @@ -9677,7 +9644,7 @@ GF_Box *sgpd_New() return (GF_Box *)tmp; } -void sgpd_del(GF_Box *a) +void sgpd_box_del(GF_Box *a) { GF_SampleGroupDescriptionBox *p = (GF_SampleGroupDescriptionBox *)a; while (gf_list_count(p->group_descriptions)) { @@ -9689,38 +9656,38 @@ void sgpd_del(GF_Box *a) gf_free(p); } -GF_Err sgpd_Read(GF_Box *s, GF_BitStream *bs) +GF_Err sgpd_box_read(GF_Box *s, GF_BitStream *bs) { u32 entry_count; GF_SampleGroupDescriptionBox *p = (GF_SampleGroupDescriptionBox *)s; + ISOM_DECREASE_SIZE(p, 8); p->grouping_type = gf_bs_read_u32(bs); - ISOM_DECREASE_SIZE(p, 4); if (p->version>=1) { - p->default_length = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(p, 4); + p->default_length = gf_bs_read_u32(bs); } if (p->version>=2) { - p->default_description_index = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(p, 4); + p->default_description_index = gf_bs_read_u32(bs); } entry_count = gf_bs_read_u32(bs); - ISOM_DECREASE_SIZE(p, 4); if (entry_count>p->size) return GF_ISOM_INVALID_FILE; while (entry_count) { void *ptr; - u32 parsed_bytes; + u32 parsed_bytes=0; u32 size = p->default_length; if ((p->version>=1) && !size) { size = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(p, 4); } ptr = sgpd_parse_entry(p->grouping_type, bs, size, &parsed_bytes); - if (!ptr) return GF_ISOM_INVALID_FILE; + //don't return an error, just stop parsing so that we skip over the sgpd box + if (!ptr) return GF_OK; ISOM_DECREASE_SIZE(p, parsed_bytes); @@ -9731,7 +9698,7 @@ GF_Err sgpd_Read(GF_Box *s, GF_BitStream *bs) } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err sgpd_Write(GF_Box *s, GF_BitStream *bs) +GF_Err sgpd_box_write(GF_Box *s, GF_BitStream *bs) { u32 i; GF_SampleGroupDescriptionBox *p = (GF_SampleGroupDescriptionBox *)s; @@ -9755,7 +9722,7 @@ GF_Err sgpd_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err sgpd_Size(GF_Box *s) +GF_Err sgpd_box_size(GF_Box *s) { u32 i; GF_SampleGroupDescriptionBox *p = (GF_SampleGroupDescriptionBox *)s; @@ -9787,7 +9754,7 @@ GF_Err sgpd_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void saiz_del(GF_Box *s) +void saiz_box_del(GF_Box *s) { GF_SampleAuxiliaryInfoSizeBox*ptr = (GF_SampleAuxiliaryInfoSizeBox*)s; if (ptr == NULL) return; @@ -9796,29 +9763,35 @@ void saiz_del(GF_Box *s) } -GF_Err saiz_Read(GF_Box *s, GF_BitStream *bs) +GF_Err saiz_box_read(GF_Box *s, GF_BitStream *bs) { GF_SampleAuxiliaryInfoSizeBox*ptr = (GF_SampleAuxiliaryInfoSizeBox*)s; if (ptr->flags & 1) { + ISOM_DECREASE_SIZE(ptr, 8); ptr->aux_info_type = gf_bs_read_u32(bs); ptr->aux_info_type_parameter = gf_bs_read_u32(bs); - - ISOM_DECREASE_SIZE(ptr, 8); } + ISOM_DECREASE_SIZE(ptr, 5); ptr->default_sample_info_size = gf_bs_read_u8(bs); ptr->sample_count = gf_bs_read_u32(bs); - ISOM_DECREASE_SIZE(ptr, 5); if (ptr->default_sample_info_size == 0) { + if (ptr->size < sizeof(u8)*ptr->sample_count) + return GF_ISOM_INVALID_FILE; + ptr->sample_info_size = gf_malloc(sizeof(u8)*ptr->sample_count); - gf_bs_read_data(bs, (char *) ptr->sample_info_size, ptr->sample_count); + ptr->sample_alloc = ptr->sample_count; + if (!ptr->sample_info_size) + return GF_OUT_OF_MEM; + ISOM_DECREASE_SIZE(ptr, ptr->sample_count); + gf_bs_read_data(bs, (char *) ptr->sample_info_size, ptr->sample_count); } return GF_OK; } -GF_Box *saiz_New() +GF_Box *saiz_box_new() { ISOM_DECL_BOX_ALLOC(GF_SampleAuxiliaryInfoSizeBox, GF_ISOM_BOX_TYPE_SAIZ); return (GF_Box *)tmp; @@ -9826,7 +9799,7 @@ GF_Box *saiz_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err saiz_Write(GF_Box *s, GF_BitStream *bs) +GF_Err saiz_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_SampleAuxiliaryInfoSizeBox*ptr = (GF_SampleAuxiliaryInfoSizeBox*) s; @@ -9841,12 +9814,15 @@ GF_Err saiz_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u8(bs, ptr->default_sample_info_size); gf_bs_write_u32(bs, ptr->sample_count); if (!ptr->default_sample_info_size) { - gf_bs_write_data(bs, (char *) ptr->sample_info_size, ptr->sample_count); + if (!ptr->sample_info_size) + gf_bs_write_u8(bs, 0); + else + gf_bs_write_data(bs, (char *) ptr->sample_info_size, ptr->sample_count); } return GF_OK; } -GF_Err saiz_Size(GF_Box *s) +GF_Err saiz_box_size(GF_Box *s) { GF_SampleAuxiliaryInfoSizeBox *ptr = (GF_SampleAuxiliaryInfoSizeBox*)s; @@ -9860,47 +9836,50 @@ GF_Err saiz_Size(GF_Box *s) } #endif //GPAC_DISABLE_ISOM_WRITE -void saio_del(GF_Box *s) +void saio_box_del(GF_Box *s) { GF_SampleAuxiliaryInfoOffsetBox *ptr = (GF_SampleAuxiliaryInfoOffsetBox*)s; if (ptr == NULL) return; if (ptr->offsets) gf_free(ptr->offsets); - if (ptr->offsets_large) gf_free(ptr->offsets_large); + if (ptr->cached_data) gf_free(ptr->cached_data); gf_free(ptr); } -GF_Err saio_Read(GF_Box *s, GF_BitStream *bs) +GF_Err saio_box_read(GF_Box *s, GF_BitStream *bs) { GF_SampleAuxiliaryInfoOffsetBox *ptr = (GF_SampleAuxiliaryInfoOffsetBox *)s; if (ptr->flags & 1) { + ISOM_DECREASE_SIZE(ptr, 8); ptr->aux_info_type = gf_bs_read_u32(bs); ptr->aux_info_type_parameter = gf_bs_read_u32(bs); - ISOM_DECREASE_SIZE(ptr, 8); } - ptr->entry_count = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); + ptr->entry_count = gf_bs_read_u32(bs); if (ptr->entry_count) { u32 i; + if (ptr->size < (ptr->version == 0 ? 4 : 8) * ptr->entry_count) + return GF_ISOM_INVALID_FILE; + ptr->offsets = gf_malloc(sizeof(u64)*ptr->entry_count); + if (!ptr->offsets) + return GF_OUT_OF_MEM; + ptr->entry_alloc = ptr->entry_count; if (ptr->version==0) { - ptr->offsets = gf_malloc(sizeof(u32)*ptr->entry_count); + ISOM_DECREASE_SIZE(ptr, 4*ptr->entry_count); for (i=0; ientry_count; i++) ptr->offsets[i] = gf_bs_read_u32(bs); - - ISOM_DECREASE_SIZE(ptr, 4*ptr->entry_count); } else { - ptr->offsets_large = gf_malloc(sizeof(u64)*ptr->entry_count); - for (i=0; ientry_count; i++) - ptr->offsets_large[i] = gf_bs_read_u64(bs); ISOM_DECREASE_SIZE(ptr, 8*ptr->entry_count); + for (i=0; ientry_count; i++) + ptr->offsets[i] = gf_bs_read_u64(bs); } } return GF_OK; } -GF_Box *saio_New() +GF_Box *saio_box_new() { ISOM_DECL_BOX_ALLOC(GF_SampleAuxiliaryInfoOffsetBox, GF_ISOM_BOX_TYPE_SAIO); return (GF_Box *)tmp; @@ -9908,7 +9887,7 @@ GF_Box *saio_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err saio_Write(GF_Box *s, GF_BitStream *bs) +GF_Err saio_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_SampleAuxiliaryInfoOffsetBox *ptr = (GF_SampleAuxiliaryInfoOffsetBox *) s; @@ -9925,47 +9904,48 @@ GF_Err saio_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_u32(bs, ptr->entry_count); if (ptr->entry_count) { u32 i; - //store position in bitstream before writing data - offsets can be NULL if a single offset is rewritten later on (cf senc_write) + //store position in bitstream before writing data - offsets can be NULL if a single offset is rewritten later on (cf senc_box_write) ptr->offset_first_offset_field = gf_bs_get_position(bs); if (ptr->version==0) { if (!ptr->offsets) { gf_bs_write_u32(bs, 0); } else { for (i=0; ientry_count; i++) - gf_bs_write_u32(bs, ptr->offsets[i]); + gf_bs_write_u32(bs, (u32) ptr->offsets[i]); } } else { - if (!ptr->offsets_large) { + if (!ptr->offsets) { gf_bs_write_u64(bs, 0); } else { for (i=0; ientry_count; i++) - gf_bs_write_u64(bs, ptr->offsets_large[i]); + gf_bs_write_u64(bs, ptr->offsets[i]); } } } return GF_OK; } -GF_Err saio_Size(GF_Box *s) +GF_Err saio_box_size(GF_Box *s) { GF_SampleAuxiliaryInfoOffsetBox *ptr = (GF_SampleAuxiliaryInfoOffsetBox*)s; if (ptr->aux_info_type || ptr->aux_info_type_parameter) { ptr->flags |= 1; } - if (ptr->offsets_large) { - ptr->version = 1; - } if (ptr->flags & 1) ptr->size += 8; ptr->size += 4; //a little optim here: in cenc, the saio always points to a single data block, only one entry is needed - if (ptr->aux_info_type == GF_4CC('c', 'e', 'n', 'c')) { - if (ptr->offsets_large) gf_free(ptr->offsets_large); + switch (ptr->aux_info_type) { + case GF_ISOM_CENC_SCHEME: + case GF_ISOM_CBC_SCHEME: + case GF_ISOM_CENS_SCHEME: + case GF_ISOM_CBCS_SCHEME: if (ptr->offsets) gf_free(ptr->offsets); - ptr->offsets_large = NULL; ptr->offsets = NULL; + ptr->entry_alloc = 0; ptr->entry_count = 1; + break; } ptr->size += ((ptr->version==1) ? 8 : 4) * ptr->entry_count; @@ -9974,33 +9954,36 @@ GF_Err saio_Size(GF_Box *s) #endif //GPAC_DISABLE_ISOM_WRITE -void prft_del(GF_Box *s) +void prft_box_del(GF_Box *s) { gf_free(s); } -GF_Err prft_Read(GF_Box *s,GF_BitStream *bs) +GF_Err prft_box_read(GF_Box *s,GF_BitStream *bs) { GF_ProducerReferenceTimeBox *ptr = (GF_ProducerReferenceTimeBox *) s; + ISOM_DECREASE_SIZE(ptr, 12); ptr->refTrackID = gf_bs_read_u32(bs); ptr->ntp = gf_bs_read_u64(bs); if (ptr->version==0) { + ISOM_DECREASE_SIZE(ptr, 4); ptr->timestamp = gf_bs_read_u32(bs); } else { + ISOM_DECREASE_SIZE(ptr, 8); ptr->timestamp = gf_bs_read_u64(bs); } return GF_OK; } -GF_Box *prft_New() +GF_Box *prft_box_new() { ISOM_DECL_BOX_ALLOC(GF_ProducerReferenceTimeBox, GF_ISOM_BOX_TYPE_PRFT); return (GF_Box *)tmp; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err prft_Write(GF_Box *s, GF_BitStream *bs) +GF_Err prft_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_ProducerReferenceTimeBox *ptr = (GF_ProducerReferenceTimeBox *) s; @@ -10019,7 +10002,7 @@ GF_Err prft_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err prft_Size(GF_Box *s) +GF_Err prft_box_size(GF_Box *s) { GF_ProducerReferenceTimeBox *ptr = (GF_ProducerReferenceTimeBox*)s; @@ -10028,7 +10011,7 @@ GF_Err prft_Size(GF_Box *s) } #endif //GPAC_DISABLE_ISOM_WRITE -GF_Box *trgr_New() +GF_Box *trgr_box_new() { ISOM_DECL_BOX_ALLOC(GF_TrackGroupBox, GF_ISOM_BOX_TYPE_TRGR); tmp->groups = gf_list_new(); @@ -10039,75 +10022,72 @@ GF_Box *trgr_New() return (GF_Box *)tmp; } -void trgr_del(GF_Box *s) +void trgr_box_del(GF_Box *s) { GF_TrackGroupBox *ptr = (GF_TrackGroupBox *)s; if (ptr == NULL) return; - gf_isom_box_array_del(ptr->groups); + gf_list_del(ptr->groups); gf_free(ptr); } -GF_Err trgr_AddBox(GF_Box *s, GF_Box *a) +GF_Err trgr_on_child_box(GF_Box *s, GF_Box *a) { GF_TrackGroupBox *ptr = (GF_TrackGroupBox *)s; return gf_list_add(ptr->groups, a); } -GF_Err trgr_Read(GF_Box *s, GF_BitStream *bs) +GF_Err trgr_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read_ex(s, bs, trgr_AddBox, s->type); + return gf_isom_box_array_read_ex(s, bs, trgr_on_child_box, s->type); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err trgr_Write(GF_Box *s, GF_BitStream *bs) +GF_Err trgr_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_TrackGroupBox *ptr = (GF_TrackGroupBox *) s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_box_write_header(s, bs); - if (e) return e; - return gf_isom_box_array_write(s, ptr->groups, bs); + return gf_isom_box_write_header(s, bs); } -GF_Err trgr_Size(GF_Box *s) +GF_Err trgr_box_size(GF_Box *s) { - GF_TrackGroupBox *ptr = (GF_TrackGroupBox *)s; - return gf_isom_box_array_size(s, ptr->groups); + u32 pos=0; + GF_TrackGroupBox *ptr = (GF_TrackGroupBox *) s; + gf_isom_check_position_list(s, ptr->groups, &pos); + return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *trgt_New() +GF_Box *trgt_box_new() { ISOM_DECL_BOX_ALLOC(GF_TrackGroupTypeBox, GF_ISOM_BOX_TYPE_TRGT); return (GF_Box *)tmp; } -void trgt_del(GF_Box *s) +void trgt_box_del(GF_Box *s) { GF_TrackGroupTypeBox *ptr = (GF_TrackGroupTypeBox *)s; if (ptr == NULL) return; gf_free(ptr); } -GF_Err trgt_Read(GF_Box *s, GF_BitStream *bs) +GF_Err trgt_box_read(GF_Box *s, GF_BitStream *bs) { GF_TrackGroupTypeBox *ptr = (GF_TrackGroupTypeBox *)s; - ptr->track_group_id = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); + ptr->track_group_id = gf_bs_read_u32(bs); return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err trgt_Write(GF_Box *s, GF_BitStream *bs) +GF_Err trgt_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TrackGroupTypeBox *ptr = (GF_TrackGroupTypeBox *) s; @@ -10120,7 +10100,7 @@ GF_Err trgt_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err trgt_Size(GF_Box *s) +GF_Err trgt_box_size(GF_Box *s) { GF_TrackGroupBox *ptr = (GF_TrackGroupBox *)s; @@ -10132,13 +10112,13 @@ GF_Err trgt_Size(GF_Box *s) -GF_Box *stvi_New() +GF_Box *stvi_box_new() { ISOM_DECL_BOX_ALLOC(GF_StereoVideoBox, GF_ISOM_BOX_TYPE_STVI); return (GF_Box *)tmp; } -void stvi_del(GF_Box *s) +void stvi_box_del(GF_Box *s) { GF_StereoVideoBox *ptr = (GF_StereoVideoBox *)s; if (ptr == NULL) return; @@ -10146,7 +10126,7 @@ void stvi_del(GF_Box *s) gf_free(ptr); } -GF_Err stvi_Read(GF_Box *s, GF_BitStream *bs) +GF_Err stvi_box_read(GF_Box *s, GF_BitStream *bs) { GF_StereoVideoBox *ptr = (GF_StereoVideoBox *)s; @@ -10156,7 +10136,10 @@ GF_Err stvi_Read(GF_Box *s, GF_BitStream *bs) ptr->stereo_scheme = gf_bs_read_u32(bs); ptr->sit_len = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, ptr->sit_len); + ptr->stereo_indication_type = gf_malloc(sizeof(char)*ptr->sit_len); + if (!ptr->stereo_indication_type) return GF_OUT_OF_MEM; + gf_bs_read_data(bs, ptr->stereo_indication_type, ptr->sit_len); return GF_OK; } @@ -10164,7 +10147,7 @@ GF_Err stvi_Read(GF_Box *s, GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err stvi_Write(GF_Box *s, GF_BitStream *bs) +GF_Err stvi_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_StereoVideoBox *ptr = (GF_StereoVideoBox *) s; @@ -10181,7 +10164,7 @@ GF_Err stvi_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err stvi_Size(GF_Box *s) +GF_Err stvi_box_size(GF_Box *s) { GF_StereoVideoBox *ptr = (GF_StereoVideoBox *)s; @@ -10193,24 +10176,22 @@ GF_Err stvi_Size(GF_Box *s) -GF_Box *fiin_New() +GF_Box *fiin_box_new() { ISOM_DECL_BOX_ALLOC(FDItemInformationBox, GF_ISOM_BOX_TYPE_FIIN); return (GF_Box *)tmp; } -void fiin_del(GF_Box *s) +void fiin_box_del(GF_Box *s) { FDItemInformationBox *ptr = (FDItemInformationBox *)s; if (ptr == NULL) return; - if (ptr->partition_entries) gf_isom_box_array_del(ptr->partition_entries); - if (ptr->session_info) gf_isom_box_del((GF_Box*)ptr->session_info); - if (ptr->group_id_to_name) gf_isom_box_del((GF_Box*)ptr->group_id_to_name); + if (ptr->partition_entries) gf_list_del(ptr->partition_entries); gf_free(ptr); } -GF_Err fiin_AddBox(GF_Box *s, GF_Box *a) +GF_Err fiin_on_child_box(GF_Box *s, GF_Box *a) { FDItemInformationBox *ptr = (FDItemInformationBox *)s; switch(a->type) { @@ -10225,25 +10206,23 @@ GF_Err fiin_AddBox(GF_Box *s, GF_Box *a) if (ptr->group_id_to_name) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->group_id_to_name = (GroupIdToNameBox *)a; return GF_OK; - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err fiin_Read(GF_Box *s, GF_BitStream *bs) +GF_Err fiin_box_read(GF_Box *s, GF_BitStream *bs) { FDItemInformationBox *ptr = (FDItemInformationBox *)s; ISOM_DECREASE_SIZE(ptr, 2); gf_bs_read_u16(bs); - return gf_isom_box_array_read(s, bs, fiin_AddBox); + return gf_isom_box_array_read(s, bs, fiin_on_child_box); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err fiin_Write(GF_Box *s, GF_BitStream *bs) +GF_Err fiin_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; FDItemInformationBox *ptr = (FDItemInformationBox *) s; @@ -10252,58 +10231,34 @@ GF_Err fiin_Write(GF_Box *s, GF_BitStream *bs) if (e) return e; gf_bs_write_u16(bs, gf_list_count(ptr->partition_entries) ); - e = gf_isom_box_array_write(s, ptr->partition_entries, bs); - if (e) return e; - if (ptr->session_info) gf_isom_box_write((GF_Box*)ptr->session_info, bs); - if (ptr->group_id_to_name) gf_isom_box_write((GF_Box*)ptr->group_id_to_name, bs); return GF_OK; } -GF_Err fiin_Size(GF_Box *s) +GF_Err fiin_box_size(GF_Box *s) { - GF_Err e; - FDItemInformationBox *ptr = (FDItemInformationBox *)s; - - ptr->size+= 2; - if (ptr->partition_entries) { - e = gf_isom_box_array_size(s, ptr->partition_entries); - if (e) return e; - } - if (ptr->session_info) { - e = gf_isom_box_size((GF_Box *)ptr->session_info); - if (e) return e; - ptr->size += ptr->session_info->size; - } - if (ptr->group_id_to_name) { - e = gf_isom_box_size((GF_Box *) ptr->group_id_to_name); - if (e) return e; - ptr->size += ptr->group_id_to_name->size; - } - + u32 pos=0; + FDItemInformationBox *ptr = (FDItemInformationBox *) s; + s->size+= 2; + gf_isom_check_position_list(s, ptr->partition_entries, &pos); return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *paen_New() +GF_Box *paen_box_new() { ISOM_DECL_BOX_ALLOC(FDPartitionEntryBox, GF_ISOM_BOX_TYPE_PAEN); return (GF_Box *)tmp; } -void paen_del(GF_Box *s) +void paen_box_del(GF_Box *s) { - FDPartitionEntryBox *ptr = (FDPartitionEntryBox *)s; - if (ptr == NULL) return; - if (ptr->blocks_and_symbols) gf_isom_box_del((GF_Box*)ptr->blocks_and_symbols); - if (ptr->FEC_symbol_locations) gf_isom_box_del((GF_Box*)ptr->FEC_symbol_locations); - if (ptr->File_symbol_locations) gf_isom_box_del((GF_Box*)ptr->File_symbol_locations); - gf_free(ptr); + gf_free(s); } -GF_Err paen_AddBox(GF_Box *s, GF_Box *a) +GF_Err paen_on_child_box(GF_Box *s, GF_Box *a) { FDPartitionEntryBox *ptr = (FDPartitionEntryBox *)s; switch(a->type) { @@ -10319,61 +10274,25 @@ GF_Err paen_AddBox(GF_Box *s, GF_Box *a) if (ptr->File_symbol_locations) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->File_symbol_locations = (FileReservoirBox *)a; return GF_OK; - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err paen_Read(GF_Box *s, GF_BitStream *bs) +GF_Err paen_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, fiin_AddBox); + return gf_isom_box_array_read(s, bs, fiin_on_child_box); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err paen_Write(GF_Box *s, GF_BitStream *bs) +GF_Err paen_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - FDPartitionEntryBox *ptr = (FDPartitionEntryBox *) s; if (!s) return GF_BAD_PARAM; - - if (ptr->blocks_and_symbols) { - e = gf_isom_box_write((GF_Box *)ptr->blocks_and_symbols, bs); - if (e) return e; - } - if (ptr->FEC_symbol_locations) { - e = gf_isom_box_write((GF_Box *)ptr->FEC_symbol_locations, bs); - if (e) return e; - } - if (ptr->File_symbol_locations) { - e = gf_isom_box_write((GF_Box *)ptr->File_symbol_locations, bs); - if (e) return e; - } - return GF_OK; + return gf_isom_box_write_header(s, bs); } -GF_Err paen_Size(GF_Box *s) +GF_Err paen_box_size(GF_Box *s) { - GF_Err e; - FDPartitionEntryBox *ptr = (FDPartitionEntryBox *)s; - - if (ptr->blocks_and_symbols) { - e = gf_isom_box_size((GF_Box *)ptr->blocks_and_symbols); - if (e) return e; - ptr->size += ptr->blocks_and_symbols->size; - } - if (ptr->FEC_symbol_locations) { - e = gf_isom_box_size((GF_Box *) ptr->FEC_symbol_locations); - if (e) return e; - ptr->size += ptr->FEC_symbol_locations->size; - } - if (ptr->File_symbol_locations) { - e = gf_isom_box_size((GF_Box *) ptr->File_symbol_locations); - if (e) return e; - ptr->size += ptr->File_symbol_locations->size; - } - return GF_OK; } @@ -10382,13 +10301,13 @@ GF_Err paen_Size(GF_Box *s) -GF_Box *fpar_New() +GF_Box *fpar_box_new() { ISOM_DECL_BOX_ALLOC(FilePartitionBox, GF_ISOM_BOX_TYPE_FPAR); return (GF_Box *)tmp; } -void fpar_del(GF_Box *s) +void fpar_box_del(GF_Box *s) { FilePartitionBox *ptr = (FilePartitionBox *)s; if (ptr == NULL) return; @@ -10397,12 +10316,14 @@ void fpar_del(GF_Box *s) gf_free(ptr); } -GF_Err gf_isom_read_null_terminated_string(GF_Box *s, GF_BitStream *bs, u32 size, char **out_str) +GF_Err gf_isom_read_null_terminated_string(GF_Box *s, GF_BitStream *bs, u64 size, char **out_str) { u32 len=10; u32 i=0; *out_str = gf_malloc(sizeof(char)*len); + if (! *out_str) return GF_OUT_OF_MEM; + while (1) { ISOM_DECREASE_SIZE(s, 1 ); (*out_str)[i] = gf_bs_read_u8(bs); @@ -10426,7 +10347,7 @@ GF_Err gf_isom_read_null_terminated_string(GF_Box *s, GF_BitStream *bs, u32 size return GF_OK; } -GF_Err fpar_Read(GF_Box *s, GF_BitStream *bs) +GF_Err fpar_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; GF_Err e; @@ -10447,8 +10368,13 @@ GF_Err fpar_Read(GF_Box *s, GF_BitStream *bs) ISOM_DECREASE_SIZE(ptr, (ptr->version ? 4 : 2) ); ptr->nb_entries = gf_bs_read_int(bs, ptr->version ? 32 : 16); + if (ptr->nb_entries > UINT_MAX / 6) + return GF_ISOM_INVALID_FILE; + ISOM_DECREASE_SIZE(ptr, ptr->nb_entries * 6 ); GF_SAFE_ALLOC_N(ptr->entries, ptr->nb_entries, FilePartitionEntry); + if (!ptr->entries) return GF_OUT_OF_MEM; + for (i=0;i < ptr->nb_entries; i++) { ptr->entries[i].block_count = gf_bs_read_u16(bs); ptr->entries[i].block_size = gf_bs_read_u32(bs); @@ -10459,7 +10385,7 @@ GF_Err fpar_Read(GF_Box *s, GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err fpar_Write(GF_Box *s, GF_BitStream *bs) +GF_Err fpar_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -10491,11 +10417,11 @@ GF_Err fpar_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err fpar_Size(GF_Box *s) +GF_Err fpar_box_size(GF_Box *s) { FilePartitionBox *ptr = (FilePartitionBox *)s; - ptr->size+= 13 + ptr->version ? 8 : 4; + ptr->size += 13 + (ptr->version ? 8 : 4); if (ptr->scheme_specific_info) ptr->size += strlen(ptr->scheme_specific_info); @@ -10506,13 +10432,13 @@ GF_Err fpar_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *fecr_New() +GF_Box *fecr_box_new() { ISOM_DECL_BOX_ALLOC(FECReservoirBox, GF_ISOM_BOX_TYPE_FECR); return (GF_Box *)tmp; } -void fecr_del(GF_Box *s) +void fecr_box_del(GF_Box *s) { FECReservoirBox *ptr = (FECReservoirBox *)s; if (ptr == NULL) return; @@ -10520,7 +10446,7 @@ void fecr_del(GF_Box *s) gf_free(ptr); } -GF_Err fecr_Read(GF_Box *s, GF_BitStream *bs) +GF_Err fecr_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; FECReservoirBox *ptr = (FECReservoirBox *)s; @@ -10530,6 +10456,8 @@ GF_Err fecr_Read(GF_Box *s, GF_BitStream *bs) ISOM_DECREASE_SIZE(ptr, ptr->nb_entries * (ptr->version ? 8 : 6) ); GF_SAFE_ALLOC_N(ptr->entries, ptr->nb_entries, FECReservoirEntry); + if (!ptr->entries) return GF_OUT_OF_MEM; + for (i=0; inb_entries; i++) { ptr->entries[i].item_id = gf_bs_read_int(bs, ptr->version ? 32 : 16); ptr->entries[i].symbol_count = gf_bs_read_u32(bs); @@ -10540,7 +10468,7 @@ GF_Err fecr_Read(GF_Box *s, GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err fecr_Write(GF_Box *s, GF_BitStream *bs) +GF_Err fecr_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -10557,7 +10485,7 @@ GF_Err fecr_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err fecr_Size(GF_Box *s) +GF_Err fecr_box_size(GF_Box *s) { FECReservoirBox *ptr = (FECReservoirBox *)s; ptr->size += (ptr->version ? 4 : 2) + ptr->nb_entries * (ptr->version ? 8 : 6); @@ -10568,13 +10496,13 @@ GF_Err fecr_Size(GF_Box *s) -GF_Box *segr_New() +GF_Box *segr_box_new() { ISOM_DECL_BOX_ALLOC(FDSessionGroupBox, GF_ISOM_BOX_TYPE_SEGR); return (GF_Box *)tmp; } -void segr_del(GF_Box *s) +void segr_box_del(GF_Box *s) { u32 i; FDSessionGroupBox *ptr = (FDSessionGroupBox *)s; @@ -10587,32 +10515,42 @@ void segr_del(GF_Box *s) gf_free(ptr); } -GF_Err segr_Read(GF_Box *s, GF_BitStream *bs) +GF_Err segr_box_read(GF_Box *s, GF_BitStream *bs) { u32 i, k; FDSessionGroupBox *ptr = (FDSessionGroupBox *)s; ISOM_DECREASE_SIZE(ptr, 2); ptr->num_session_groups = gf_bs_read_u16(bs); - if (ptr->num_session_groups*3>ptr->size) { + if (ptr->size < ptr->num_session_groups) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Invalid number of entries %d in segr\n", ptr->num_session_groups)); + ptr->num_session_groups = 0; return GF_ISOM_INVALID_FILE; } GF_SAFE_ALLOC_N(ptr->session_groups, ptr->num_session_groups, SessionGroupEntry); + if (!ptr->session_groups) return GF_OUT_OF_MEM; + for (i=0; inum_session_groups; i++) { ptr->session_groups[i].nb_groups = gf_bs_read_u8(bs); ISOM_DECREASE_SIZE(ptr, 1); + + ISOM_DECREASE_SIZE(ptr, ptr->session_groups[i].nb_groups*4); + GF_SAFE_ALLOC_N(ptr->session_groups[i].group_ids, ptr->session_groups[i].nb_groups, u32); + if (!ptr->session_groups[i].group_ids) return GF_OUT_OF_MEM; + for (k=0; ksession_groups[i].nb_groups; k++) { - ISOM_DECREASE_SIZE(ptr, 4); ptr->session_groups[i].group_ids[k] = gf_bs_read_u32(bs); } ptr->session_groups[i].nb_channels = gf_bs_read_u16(bs); + ISOM_DECREASE_SIZE(ptr, ptr->session_groups[i].nb_channels*4); + GF_SAFE_ALLOC_N(ptr->session_groups[i].channels, ptr->session_groups[i].nb_channels, u32); + if (!ptr->session_groups[i].channels) return GF_OUT_OF_MEM; + for (k=0; ksession_groups[i].nb_channels; k++) { - ISOM_DECREASE_SIZE(ptr, 4); ptr->session_groups[i].channels[k] = gf_bs_read_u32(bs); } } @@ -10622,12 +10560,16 @@ GF_Err segr_Read(GF_Box *s, GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err segr_Write(GF_Box *s, GF_BitStream *bs) +GF_Err segr_box_write(GF_Box *s, GF_BitStream *bs) { u32 i, k; + GF_Err e; FDSessionGroupBox *ptr = (FDSessionGroupBox *) s; if (!s) return GF_BAD_PARAM; + e = gf_isom_box_write_header(s, bs); + if (e) return e; + gf_bs_write_u16(bs, ptr->num_session_groups); for (i=0; inum_session_groups; i++) { gf_bs_write_u8(bs, ptr->session_groups[i].nb_groups); @@ -10643,7 +10585,7 @@ GF_Err segr_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err segr_Size(GF_Box *s) +GF_Err segr_box_size(GF_Box *s) { u32 i; FDSessionGroupBox *ptr = (FDSessionGroupBox *)s; @@ -10660,13 +10602,13 @@ GF_Err segr_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *gitn_New() +GF_Box *gitn_box_new() { ISOM_DECL_BOX_ALLOC(GroupIdToNameBox, GF_ISOM_BOX_TYPE_GITN); return (GF_Box *)tmp; } -void gitn_del(GF_Box *s) +void gitn_box_del(GF_Box *s) { u32 i; GroupIdToNameBox *ptr = (GroupIdToNameBox *)s; @@ -10678,7 +10620,7 @@ void gitn_del(GF_Box *s) gf_free(ptr); } -GF_Err gitn_Read(GF_Box *s, GF_BitStream *bs) +GF_Err gitn_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; GF_Err e; @@ -10686,8 +10628,12 @@ GF_Err gitn_Read(GF_Box *s, GF_BitStream *bs) ISOM_DECREASE_SIZE(ptr, 2); ptr->nb_entries = gf_bs_read_u16(bs); + if (ptr->size < ptr->nb_entries*4) + return GF_ISOM_INVALID_FILE; GF_SAFE_ALLOC_N(ptr->entries, ptr->nb_entries, GroupIdNameEntry); + if (!ptr->entries) return GF_OUT_OF_MEM; + for (i=0; inb_entries; i++) { ISOM_DECREASE_SIZE(ptr, 4); ptr->entries[i].group_id = gf_bs_read_u32(bs); @@ -10701,7 +10647,7 @@ GF_Err gitn_Read(GF_Box *s, GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err gitn_Write(GF_Box *s, GF_BitStream *bs) +GF_Err gitn_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -10719,7 +10665,7 @@ GF_Err gitn_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err gitn_Size(GF_Box *s) +GF_Err gitn_box_size(GF_Box *s) { u32 i; GroupIdToNameBox *ptr = (GroupIdToNameBox *)s; @@ -10736,13 +10682,13 @@ GF_Err gitn_Size(GF_Box *s) #ifndef GPAC_DISABLE_ISOM_HINTING -GF_Box *fdpa_New() +GF_Box *fdpa_box_new() { ISOM_DECL_BOX_ALLOC(GF_FDpacketBox, GF_ISOM_BOX_TYPE_FDPA); return (GF_Box *)tmp; } -void fdpa_del(GF_Box *s) +void fdpa_box_del(GF_Box *s) { u32 i; GF_FDpacketBox *ptr = (GF_FDpacketBox *)s; @@ -10757,7 +10703,7 @@ void fdpa_del(GF_Box *s) gf_free(ptr); } -GF_Err fdpa_Read(GF_Box *s, GF_BitStream *bs) +GF_Err fdpa_box_read(GF_Box *s, GF_BitStream *bs) { u32 i; GF_FDpacketBox *ptr = (GF_FDpacketBox *)s; @@ -10771,24 +10717,32 @@ GF_Err fdpa_Read(GF_Box *s, GF_BitStream *bs) ptr->info.transport_object_identifier = gf_bs_read_u16(bs); ISOM_DECREASE_SIZE(ptr, 2); ptr->header_ext_count = gf_bs_read_u16(bs); - if (ptr->header_ext_count*2>ptr->size) { + if (ptr->size < ptr->header_ext_count*2) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Invalid number of entries %d in fdpa\n", ptr->header_ext_count)); return GF_ISOM_INVALID_FILE; } GF_SAFE_ALLOC_N(ptr->headers, ptr->header_ext_count, GF_LCTheaderExtension); + if (!ptr->headers) return GF_OUT_OF_MEM; + for (i=0; iheader_ext_count; i++) { ptr->headers[i].header_extension_type = gf_bs_read_u8(bs); ISOM_DECREASE_SIZE(ptr, 1); if (ptr->headers[i].header_extension_type > 127) { + ISOM_DECREASE_SIZE(ptr, 3); gf_bs_read_data(bs, (char *) ptr->headers[i].content, 3); } else { ISOM_DECREASE_SIZE(ptr, 1); ptr->headers[i].data_length = gf_bs_read_u8(bs); if (ptr->headers[i].data_length) { ptr->headers[i].data_length = 4*ptr->headers[i].data_length - 2; + if (ptr->size < sizeof(char) * ptr->headers[i].data_length) + return GF_ISOM_INVALID_FILE; ptr->headers[i].data = gf_malloc(sizeof(char) * ptr->headers[i].data_length); + if (!ptr->headers[i].data) return GF_OUT_OF_MEM; + + ISOM_DECREASE_SIZE(ptr, ptr->headers[i].data_length); gf_bs_read_data(bs, ptr->headers[i].data, ptr->headers[i].data_length); } } @@ -10798,18 +10752,22 @@ GF_Err fdpa_Read(GF_Box *s, GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err fdpa_Write(GF_Box *s, GF_BitStream *bs) +GF_Err fdpa_box_write(GF_Box *s, GF_BitStream *bs) { + GF_Err e; u32 i; GF_FDpacketBox *ptr = (GF_FDpacketBox *) s; if (!s) return GF_BAD_PARAM; + e = gf_isom_box_write_header(s, bs); + if (e) return e; + gf_bs_write_int(bs, ptr->info.sender_current_time_present, 1); gf_bs_write_int(bs, ptr->info.expected_residual_time_present, 1); gf_bs_write_int(bs, ptr->info.session_close_bit, 1); gf_bs_write_int(bs, ptr->info.object_close_bit, 1); gf_bs_write_int(bs, 0, 4); - ptr->info.transport_object_identifier = gf_bs_read_u16(bs); + gf_bs_write_u16(bs, ptr->info.transport_object_identifier); gf_bs_write_u16(bs, ptr->header_ext_count); for (i=0; iheader_ext_count; i++) { gf_bs_write_u8(bs, ptr->headers[i].header_extension_type); @@ -10825,7 +10783,7 @@ GF_Err fdpa_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err fdpa_Size(GF_Box *s) +GF_Err fdpa_box_size(GF_Box *s) { u32 i; GF_FDpacketBox *ptr = (GF_FDpacketBox *)s; @@ -10846,32 +10804,32 @@ GF_Err fdpa_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *extr_New() +GF_Box *extr_box_new() { ISOM_DECL_BOX_ALLOC(GF_ExtraDataBox, GF_ISOM_BOX_TYPE_EXTR); return (GF_Box *)tmp; } -void extr_del(GF_Box *s) +void extr_box_del(GF_Box *s) { GF_ExtraDataBox *ptr = (GF_ExtraDataBox *)s; if (ptr == NULL) return; if (ptr->feci) gf_isom_box_del((GF_Box*)ptr->feci); - if (ptr->data) gf_free(ptr->data); gf_free(ptr); } -GF_Err extr_Read(GF_Box *s, GF_BitStream *bs) +GF_Err extr_box_read(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_ExtraDataBox *ptr = (GF_ExtraDataBox *)s; e = gf_isom_box_parse((GF_Box**) &ptr->feci, bs); if (e) return e; - if (ptr->feci->size>ptr->size) return GF_ISOM_INVALID_MEDIA; + if (!ptr->feci || ptr->feci->size > ptr->size) return GF_ISOM_INVALID_MEDIA; ptr->data_length = (u32) (ptr->size - ptr->feci->size); ptr->data = gf_malloc(sizeof(char)*ptr->data_length); + if (!ptr->data) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->data, ptr->data_length); return GF_OK; @@ -10880,11 +10838,15 @@ GF_Err extr_Read(GF_Box *s, GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err extr_Write(GF_Box *s, GF_BitStream *bs) +GF_Err extr_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_ExtraDataBox *ptr = (GF_ExtraDataBox *) s; if (!s) return GF_BAD_PARAM; + + e = gf_isom_box_write_header(s, bs); + if (e) return e; + if (ptr->feci) { e = gf_isom_box_write((GF_Box *)ptr->feci, bs); if (e) return e; @@ -10893,17 +10855,15 @@ GF_Err extr_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err extr_Size(GF_Box *s) +GF_Err extr_box_size(GF_Box *s) { - GF_Err e; GF_ExtraDataBox *ptr = (GF_ExtraDataBox *) s; - + ptr->size += ptr->data_length; if (ptr->feci) { - e = gf_isom_box_size((GF_Box *)ptr->feci); + GF_Err e = gf_isom_box_size((GF_Box*)ptr->feci); if (e) return e; ptr->size += ptr->feci->size; } - ptr->size += ptr->data_length; return GF_OK; } @@ -10911,7 +10871,7 @@ GF_Err extr_Size(GF_Box *s) -GF_Box *fdsa_New() +GF_Box *fdsa_box_new() { ISOM_DECL_BOX_ALLOC(GF_HintSample, GF_ISOM_BOX_TYPE_FDSA); if (!tmp) return NULL; @@ -10920,15 +10880,14 @@ GF_Box *fdsa_New() return (GF_Box*)tmp; } -void fdsa_del(GF_Box *s) +void fdsa_box_del(GF_Box *s) { GF_HintSample *ptr = (GF_HintSample *)s; - gf_isom_box_array_del(ptr->packetTable); - if (ptr->extra_data) gf_isom_box_del((GF_Box*)ptr->extra_data); + gf_list_del(ptr->packetTable); gf_free(ptr); } -GF_Err fdsa_AddBox(GF_Box *s, GF_Box *a) +GF_Err fdsa_on_child_box(GF_Box *s, GF_Box *a) { GF_HintSample *ptr = (GF_HintSample *)s; switch(a->type) { @@ -10939,25 +10898,26 @@ GF_Err fdsa_AddBox(GF_Box *s, GF_Box *a) if (ptr->extra_data) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->extra_data = (GF_ExtraDataBox*)a; break; - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err fdsa_Read(GF_Box *s, GF_BitStream *bs) +GF_Err fdsa_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, fdsa_AddBox); + return gf_isom_box_array_read(s, bs, fdsa_on_child_box); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err fdsa_Write(GF_Box *s, GF_BitStream *bs) +GF_Err fdsa_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_HintSample *ptr = (GF_HintSample *) s; if (!s) return GF_BAD_PARAM; + e = gf_isom_box_write_header(s, bs); + if (e) return e; + e = gf_isom_box_array_write(s, ptr->packetTable, bs); if (e) return e; if (ptr->extra_data) { @@ -10967,17 +10927,9 @@ GF_Err fdsa_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err fdsa_Size(GF_Box *s) +GF_Err fdsa_box_size(GF_Box *s) { - GF_HintSample *ptr = (GF_HintSample*)s; - GF_Err e; - - if (ptr->extra_data) { - e = gf_isom_box_size((GF_Box *)ptr->extra_data); - if (e) return e; - ptr->size += ptr->extra_data->size; - } - return gf_isom_box_array_size(s, ptr->packetTable); + return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ @@ -10985,7 +10937,7 @@ GF_Err fdsa_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_HINTING*/ -void trik_del(GF_Box *s) +void trik_box_del(GF_Box *s) { GF_TrickPlayBox *ptr = (GF_TrickPlayBox *) s; if (ptr == NULL) return; @@ -10993,13 +10945,13 @@ void trik_del(GF_Box *s) gf_free(ptr); } -GF_Err trik_Read(GF_Box *s,GF_BitStream *bs) +GF_Err trik_box_read(GF_Box *s,GF_BitStream *bs) { u32 i; GF_TrickPlayBox *ptr = (GF_TrickPlayBox *) s; ptr->entry_count = (u32) ptr->size; ptr->entries = (GF_TrickPlayBoxEntry *) gf_malloc(ptr->entry_count * sizeof(GF_TrickPlayBoxEntry) ); - if (ptr->entries == NULL) return GF_OUT_OF_MEM; + if (!ptr->entries) return GF_OUT_OF_MEM; for (i=0; i< ptr->entry_count; i++) { ptr->entries[i].pic_type = gf_bs_read_int(bs, 2); @@ -11008,7 +10960,7 @@ GF_Err trik_Read(GF_Box *s,GF_BitStream *bs) return GF_OK; } -GF_Box *trik_New() +GF_Box *trik_box_new() { ISOM_DECL_BOX_ALLOC(GF_TrickPlayBox, GF_ISOM_BOX_TYPE_TRIK); return (GF_Box *)tmp; @@ -11016,7 +10968,7 @@ GF_Box *trik_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err trik_Write(GF_Box *s, GF_BitStream *bs) +GF_Err trik_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -11032,7 +10984,7 @@ GF_Err trik_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err trik_Size(GF_Box *s) +GF_Err trik_box_size(GF_Box *s) { GF_TrickPlayBox *ptr = (GF_TrickPlayBox *) s; ptr->size += 8 * ptr->entry_count; @@ -11042,12 +10994,12 @@ GF_Err trik_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void bloc_del(GF_Box *s) +void bloc_box_del(GF_Box *s) { gf_free(s); } -GF_Err bloc_Read(GF_Box *s,GF_BitStream *bs) +GF_Err bloc_box_read(GF_Box *s,GF_BitStream *bs) { GF_BaseLocationBox *ptr = (GF_BaseLocationBox *) s; @@ -11060,7 +11012,7 @@ GF_Err bloc_Read(GF_Box *s,GF_BitStream *bs) return GF_OK; } -GF_Box *bloc_New() +GF_Box *bloc_box_new() { ISOM_DECL_BOX_ALLOC(GF_BaseLocationBox, GF_ISOM_BOX_TYPE_TRIK); return (GF_Box *)tmp; @@ -11068,7 +11020,7 @@ GF_Box *bloc_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err bloc_Write(GF_Box *s, GF_BitStream *bs) +GF_Err bloc_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -11084,7 +11036,7 @@ GF_Err bloc_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err bloc_Size(GF_Box *s) +GF_Err bloc_box_size(GF_Box *s) { s->size += 1024; return GF_OK; @@ -11092,14 +11044,14 @@ GF_Err bloc_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void ainf_del(GF_Box *s) +void ainf_box_del(GF_Box *s) { GF_AssetInformationBox *ptr = (GF_AssetInformationBox *) s; if (ptr->APID) gf_free(ptr->APID); gf_free(s); } -GF_Err ainf_Read(GF_Box *s,GF_BitStream *bs) +GF_Err ainf_box_read(GF_Box *s,GF_BitStream *bs) { GF_AssetInformationBox *ptr = (GF_AssetInformationBox *) s; @@ -11108,7 +11060,7 @@ GF_Err ainf_Read(GF_Box *s,GF_BitStream *bs) return gf_isom_read_null_terminated_string(s, bs, s->size, &ptr->APID); } -GF_Box *ainf_New() +GF_Box *ainf_box_new() { ISOM_DECL_BOX_ALLOC(GF_AssetInformationBox, GF_ISOM_BOX_TYPE_AINF); return (GF_Box *)tmp; @@ -11116,7 +11068,7 @@ GF_Box *ainf_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err ainf_Write(GF_Box *s, GF_BitStream *bs) +GF_Err ainf_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_AssetInformationBox *ptr = (GF_AssetInformationBox *) s; @@ -11124,18 +11076,881 @@ GF_Err ainf_Write(GF_Box *s, GF_BitStream *bs) e = gf_isom_full_box_write(s, bs); if (e) return e; gf_bs_write_u32(bs, ptr->profile_version); - gf_bs_write_data(bs, ptr->APID, (u32) strlen(ptr->APID) + 1); + if (ptr->APID) + gf_bs_write_data(bs, ptr->APID, (u32) strlen(ptr->APID) ); + gf_bs_write_u8(bs, 0); return GF_OK; } -GF_Err ainf_Size(GF_Box *s) +GF_Err ainf_box_size(GF_Box *s) { GF_AssetInformationBox *ptr = (GF_AssetInformationBox *) s; - s->size += 4 + strlen(ptr->APID) + 1; + s->size += 4 + (ptr->APID ? strlen(ptr->APID) : 0 ) + 1; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +void mhac_box_del(GF_Box *s) +{ + GF_MHAConfigBox *ptr = (GF_MHAConfigBox *) s; + if (ptr->mha_config) gf_free(ptr->mha_config); + gf_free(s); +} + +GF_Err mhac_box_read(GF_Box *s,GF_BitStream *bs) +{ + GF_MHAConfigBox *ptr = (GF_MHAConfigBox *) s; + + ISOM_DECREASE_SIZE(s, 5) + ptr->configuration_version = gf_bs_read_u8(bs); + ptr->mha_pl_indication = gf_bs_read_u8(bs); + ptr->reference_channel_layout = gf_bs_read_u8(bs); + ptr->mha_config_size = gf_bs_read_u16(bs); + if (ptr->mha_config_size) { + ISOM_DECREASE_SIZE(s, ptr->mha_config_size) + + ptr->mha_config = gf_malloc(sizeof(char)*ptr->mha_config_size); + if (!ptr->mha_config) return GF_OUT_OF_MEM; + + gf_bs_read_data(bs, ptr->mha_config, ptr->mha_config_size); + } + return GF_OK; +} + +GF_Box *mhac_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_MHAConfigBox, GF_ISOM_BOX_TYPE_MHAC); + tmp->configuration_version = 1; + return (GF_Box *)tmp; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err mhac_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_MHAConfigBox *ptr = (GF_MHAConfigBox *) s; + + e = gf_isom_box_write_header(s, bs); + if (e) return e; + gf_bs_write_u8(bs, ptr->configuration_version); + gf_bs_write_u8(bs, ptr->mha_pl_indication); + gf_bs_write_u8(bs, ptr->reference_channel_layout); + gf_bs_write_u16(bs, ptr->mha_config ? ptr->mha_config_size : 0); + if (ptr->mha_config && ptr->mha_config_size) + gf_bs_write_data(bs, ptr->mha_config, ptr->mha_config_size); + + return GF_OK; +} + +GF_Err mhac_box_size(GF_Box *s) +{ + GF_MHAConfigBox *ptr = (GF_MHAConfigBox *) s; + s->size += 5; + if (ptr->mha_config_size && ptr->mha_config) s->size += ptr->mha_config_size; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +void jp2h_box_del(GF_Box *s) +{ + gf_free(s); +} + +GF_Err jp2h_on_child_box(GF_Box *s, GF_Box *a) +{ + GF_J2KHeaderBox *ptr = (GF_J2KHeaderBox *)s; + switch(a->type) { + case GF_ISOM_BOX_TYPE_IHDR: + if (ptr->ihdr) ERROR_ON_DUPLICATED_BOX(a, ptr) + ptr->ihdr = (GF_J2KImageHeaderBox*)a; + return GF_OK; + case GF_ISOM_BOX_TYPE_COLR: + if (ptr->colr) ERROR_ON_DUPLICATED_BOX(a, ptr) + ptr->colr = (GF_ColourInformationBox*)a; + return GF_OK; + } + return GF_OK; +} +GF_Err jp2h_box_read(GF_Box *s,GF_BitStream *bs) +{ + return gf_isom_box_array_read_ex(s, bs, jp2h_on_child_box, s->type); +} + +GF_Box *jp2h_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_J2KHeaderBox, GF_ISOM_BOX_TYPE_JP2H); + return (GF_Box *)tmp; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err jp2h_box_write(GF_Box *s, GF_BitStream *bs) +{ + return gf_isom_box_write_header(s, bs); +} + +GF_Err jp2h_box_size(GF_Box *s) +{ + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +void ihdr_box_del(GF_Box *s) +{ + gf_free(s); +} + +GF_Err ihdr_box_read(GF_Box *s,GF_BitStream *bs) +{ + GF_J2KImageHeaderBox *ptr = (GF_J2KImageHeaderBox *) s; + + ISOM_DECREASE_SIZE(s, 14) + + ptr->height = gf_bs_read_u32(bs); + ptr->width = gf_bs_read_u32(bs); + ptr->nb_comp = gf_bs_read_u16(bs); + ptr->bpc = gf_bs_read_u8(bs); + ptr->Comp = gf_bs_read_u8(bs); + ptr->UnkC = gf_bs_read_u8(bs); + ptr->IPR = gf_bs_read_u8(bs); + + return GF_OK; +} + +GF_Box *ihdr_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_J2KImageHeaderBox, GF_ISOM_BOX_TYPE_IHDR); + return (GF_Box *)tmp; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err ihdr_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_J2KImageHeaderBox *ptr = (GF_J2KImageHeaderBox *) s; + + e = gf_isom_box_write_header(s, bs); + if (e) return e; + + gf_bs_write_u32(bs, ptr->height); + gf_bs_write_u32(bs, ptr->width); + gf_bs_write_u16(bs, ptr->nb_comp); + gf_bs_write_u8(bs, ptr->bpc); + gf_bs_write_u8(bs, ptr->Comp); + gf_bs_write_u8(bs, ptr->UnkC); + gf_bs_write_u8(bs, ptr->IPR); + return GF_OK; +} + +GF_Err ihdr_box_size(GF_Box *s) +{ + s->size += 14; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +/* Dolby Vision */ + +GF_Box *dvcC_box_new() +{ + GF_DOVIConfigurationBox *tmp = (GF_DOVIConfigurationBox *)gf_malloc(sizeof(GF_DOVIConfigurationBox)); + if (tmp == NULL) return NULL; + memset(tmp, 0, sizeof(GF_DOVIConfigurationBox)); + tmp->type = GF_ISOM_BOX_TYPE_DVCC; + return (GF_Box *)tmp; +} + +void dvcC_box_del(GF_Box *s) +{ + GF_DOVIConfigurationBox *ptr = (GF_DOVIConfigurationBox*)s; + gf_free(ptr); +} + +GF_Err dvcC_box_read(GF_Box *s, GF_BitStream *bs) +{ + u32 i; + u32 data[5]; + GF_DOVIConfigurationBox *ptr = (GF_DOVIConfigurationBox *)s; + + //GF_DOVIDecoderConfigurationRecord + ISOM_DECREASE_SIZE(ptr, 24) + ptr->DOVIConfig.dv_version_major = gf_bs_read_u8(bs); + ptr->DOVIConfig.dv_version_minor = gf_bs_read_u8(bs); + ptr->DOVIConfig.dv_profile = gf_bs_read_int(bs, 7); + ptr->DOVIConfig.dv_level = gf_bs_read_int(bs, 6); + ptr->DOVIConfig.rpu_present_flag = gf_bs_read_int(bs, 1); + ptr->DOVIConfig.el_present_flag = gf_bs_read_int(bs, 1); + ptr->DOVIConfig.bl_present_flag = gf_bs_read_int(bs, 1); + + memset(data, 0, sizeof(u32)*5); + gf_bs_read_data(bs, (char*)data, 20); + for (i = 0; i < 5; ++i) { + if (data[i] != 0) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] dvcC reserved bytes are not zero\n")); + //return GF_ISOM_INVALID_FILE; + } + } + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE +GF_Err dvcC_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_DOVIConfigurationBox *ptr = (GF_DOVIConfigurationBox *) s; + if (!s) return GF_BAD_PARAM; + + e = gf_isom_box_write_header(s, bs); + if (e) return e; + + //GF_DOVIDecoderConfigurationRecord + gf_bs_write_u8(bs, ptr->DOVIConfig.dv_version_major); + gf_bs_write_u8(bs, ptr->DOVIConfig.dv_version_minor); + gf_bs_write_int(bs, ptr->DOVIConfig.dv_profile, 7); + gf_bs_write_int(bs, ptr->DOVIConfig.dv_level, 6); + gf_bs_write_int(bs, ptr->DOVIConfig.rpu_present_flag, 1); + gf_bs_write_int(bs, ptr->DOVIConfig.el_present_flag, 1); + gf_bs_write_int(bs, ptr->DOVIConfig.bl_present_flag, 1); + gf_bs_write_u32(bs, 0); + gf_bs_write_u32(bs, 0); + gf_bs_write_u32(bs, 0); + gf_bs_write_u32(bs, 0); + gf_bs_write_u32(bs, 0); + + return GF_OK; +} + +GF_Err dvcC_box_size(GF_Box *s) +{ + GF_DOVIConfigurationBox *ptr = (GF_DOVIConfigurationBox *)s; + + ptr->size += 24; return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ +GF_Box *dOps_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_OpusSpecificBox, GF_ISOM_BOX_TYPE_DOPS); + return (GF_Box *)tmp; +} + +void dOps_box_del(GF_Box *s) +{ + GF_OpusSpecificBox *ptr = (GF_OpusSpecificBox *)s; + if (ptr) gf_free(ptr); +} + +GF_Err dOps_box_read(GF_Box *s, GF_BitStream *bs) +{ + GF_OpusSpecificBox *ptr = (GF_OpusSpecificBox *)s; + ptr->version = gf_bs_read_u8(bs); + ptr->OutputChannelCount = gf_bs_read_u8(bs); + ptr->PreSkip = gf_bs_read_u16(bs); + ptr->InputSampleRate = gf_bs_read_u32(bs); + ptr->OutputGain = gf_bs_read_u16(bs); + ptr->ChannelMappingFamily = gf_bs_read_u8(bs); + ISOM_DECREASE_SIZE(ptr, 11) + if (ptr->size) { + ISOM_DECREASE_SIZE(ptr, 2+ptr->OutputChannelCount); + ptr->StreamCount = gf_bs_read_u8(bs); + ptr->CoupledCount = gf_bs_read_u8(bs); + gf_bs_read_data(bs, (char *) ptr->ChannelMapping, ptr->OutputChannelCount); + } + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err dOps_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_OpusSpecificBox *ptr = (GF_OpusSpecificBox *)s; + if (!s) return GF_BAD_PARAM; + e = gf_isom_box_write_header(s, bs); + if (e) return e; + gf_bs_write_u8(bs, ptr->version); + gf_bs_write_u8(bs, ptr->OutputChannelCount); + gf_bs_write_u16(bs, ptr->PreSkip); + gf_bs_write_u32(bs, ptr->InputSampleRate); + gf_bs_write_u16(bs, ptr->OutputGain); + gf_bs_write_u8(bs, ptr->ChannelMappingFamily); + if (ptr->ChannelMappingFamily) { + gf_bs_write_u8(bs, ptr->StreamCount); + gf_bs_write_u8(bs, ptr->CoupledCount); + gf_bs_write_data(bs, (char *) ptr->ChannelMapping, ptr->OutputChannelCount); + } + return GF_OK; +} + +GF_Err dOps_box_size(GF_Box *s) +{ + GF_OpusSpecificBox *ptr = (GF_OpusSpecificBox *)s; + ptr->size += 11; + if (ptr->ChannelMappingFamily) + ptr->size += 2 + ptr->OutputChannelCount; + + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +void dfla_box_del(GF_Box *s) +{ + GF_FLACConfigBox *ptr = (GF_FLACConfigBox *) s; + if (ptr->data) gf_free(ptr->data); + gf_free(ptr); +} + +GF_Err dfla_box_read(GF_Box *s,GF_BitStream *bs) +{ + GF_FLACConfigBox *ptr = (GF_FLACConfigBox *) s; + ptr->dataSize = (u32) ptr->size; + ptr->size=0; + ptr->data = gf_malloc(ptr->dataSize); + gf_bs_read_data(bs, ptr->data, ptr->dataSize); + return GF_OK; +} + +GF_Box *dfla_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_FLACConfigBox, GF_ISOM_BOX_TYPE_DFLA); + return (GF_Box *)tmp; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err dfla_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_FLACConfigBox *ptr = (GF_FLACConfigBox *) s; + e = gf_isom_full_box_write(s, bs); + if (e) return e; + gf_bs_write_data(bs, ptr->data, ptr->dataSize); + return GF_OK; +} + +GF_Err dfla_box_size(GF_Box *s) +{ + GF_FLACConfigBox *ptr = (GF_FLACConfigBox *) s; + ptr->size += ptr->dataSize; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + + +void mvcg_box_del(GF_Box *s) +{ + GF_MultiviewGroupBox *ptr = (GF_MultiviewGroupBox *) s; + if (ptr->entries) gf_free(ptr->entries); + gf_free(ptr); +} + +GF_Err mvcg_box_read(GF_Box *s,GF_BitStream *bs) +{ + u32 i; + GF_MultiviewGroupBox *ptr = (GF_MultiviewGroupBox *) s; + ISOM_DECREASE_SIZE(s, 7) + ptr->multiview_group_id = gf_bs_read_u32(bs); + ptr->num_entries = gf_bs_read_u16(bs); + gf_bs_read_u8(bs); + ptr->entries = gf_malloc(ptr->num_entries * sizeof(MVCIEntry)); + memset(ptr->entries, 0, ptr->num_entries * sizeof(MVCIEntry)); + for (i=0; inum_entries; i++) { + ISOM_DECREASE_SIZE(s, 1) + ptr->entries[i].entry_type = gf_bs_read_u8(bs); + switch (ptr->entries[i].entry_type) { + case 0: + ISOM_DECREASE_SIZE(s, 4) + ptr->entries[i].trackID = gf_bs_read_u32(bs); + break; + case 1: + ISOM_DECREASE_SIZE(s, 6) + ptr->entries[i].trackID = gf_bs_read_u32(bs); + ptr->entries[i].tierID = gf_bs_read_u16(bs); + break; + case 2: + ISOM_DECREASE_SIZE(s, 2) + gf_bs_read_int(bs, 6); + ptr->entries[i].output_view_id = gf_bs_read_int(bs, 10); + break; + case 3: + ISOM_DECREASE_SIZE(s, 4) + gf_bs_read_int(bs, 6) ; + ptr->entries[i].start_view_id = gf_bs_read_int(bs, 10); + ptr->entries[i].view_count = gf_bs_read_u16(bs); + break; + } + } + return gf_isom_box_array_read(s, bs, NULL); +} + +GF_Box *mvcg_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_MultiviewGroupBox, GF_ISOM_BOX_TYPE_MVCG); + return (GF_Box *)tmp; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err mvcg_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + u32 i; + GF_MultiviewGroupBox *ptr = (GF_MultiviewGroupBox *) s; + e = gf_isom_full_box_write(s, bs); + if (e) return e; + + + gf_bs_write_u32(bs, ptr->multiview_group_id); + gf_bs_write_u16(bs, ptr->num_entries); + gf_bs_write_u8(bs, 0); + + for (i=0; inum_entries; i++) { + gf_bs_write_u8(bs, ptr->entries[i].entry_type); + switch (ptr->entries[i].entry_type) { + case 0: + gf_bs_write_u32(bs, ptr->entries[i].trackID); + break; + case 1: + gf_bs_write_u32(bs, ptr->entries[i].trackID); + gf_bs_write_u16(bs, ptr->entries[i].tierID); + break; + case 2: + gf_bs_write_int(bs, 0, 6); + gf_bs_write_int(bs, ptr->entries[i].output_view_id, 10); + break; + case 3: + gf_bs_write_int(bs, 0, 6) ; + gf_bs_write_int(bs, ptr->entries[i].start_view_id, 10); + gf_bs_write_u16(bs, ptr->entries[i].view_count); + break; + } + } + return GF_OK; +} + +GF_Err mvcg_box_size(GF_Box *s) +{ + u32 i; + GF_MultiviewGroupBox *ptr = (GF_MultiviewGroupBox *) s; + + ptr->size += 7; + for (i=0; inum_entries; i++) { + switch (ptr->entries[i].entry_type) { + case 0: + ptr->size += 1 + 4; + break; + case 1: + ptr->size += 1 + 6; + break; + case 2: + ptr->size += 1 + 2; + break; + case 3: + ptr->size += 1 + 4; + break; + } + } + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +void vwid_box_del(GF_Box *s) +{ + u32 i; + GF_ViewIdentifierBox *ptr = (GF_ViewIdentifierBox *) s; + if (ptr->views) { + for (i=0; inum_views; i++) { + if (ptr->views[i].view_refs) + gf_free(ptr->views[i].view_refs); + } + gf_free(ptr->views); + } + gf_free(ptr); +} + +GF_Err vwid_box_read(GF_Box *s,GF_BitStream *bs) +{ + u32 i; + GF_ViewIdentifierBox *ptr = (GF_ViewIdentifierBox *) s; + ISOM_DECREASE_SIZE(s, 3) + gf_bs_read_int(bs, 2); + ptr->min_temporal_id = gf_bs_read_int(bs, 3); + ptr->max_temporal_id = gf_bs_read_int(bs, 3); + ptr->num_views = gf_bs_read_u16(bs); + if (6 * ptr->num_views > ptr->size) + return GF_ISOM_INVALID_FILE; + + ptr->views = gf_malloc(sizeof(ViewIDEntry)*ptr->num_views); + for (i=0; inum_views; i++) { + u32 j; + ISOM_DECREASE_SIZE(s, 6) + + gf_bs_read_int(bs, 6); + ptr->views[i].view_id = gf_bs_read_int(bs, 10); + gf_bs_read_int(bs, 6); + ptr->views[i].view_order_index = gf_bs_read_int(bs, 10); + ptr->views[i].texture_in_stream = gf_bs_read_int(bs, 1); + ptr->views[i].texture_in_track = gf_bs_read_int(bs, 1); + ptr->views[i].depth_in_stream = gf_bs_read_int(bs, 1); + ptr->views[i].depth_in_track = gf_bs_read_int(bs, 1); + ptr->views[i].base_view_type = gf_bs_read_int(bs, 2); + ptr->views[i].num_ref_views = gf_bs_read_int(bs, 10); + + if (2 * ptr->views[i].num_ref_views > ptr->size) + return GF_ISOM_INVALID_FILE; + + ptr->views[i].view_refs = gf_malloc(sizeof(ViewIDRefViewEntry)*ptr->views[i].num_ref_views); + for (j=0; jviews[i].num_ref_views; j++) { + ISOM_DECREASE_SIZE(s, 2) + gf_bs_read_int(bs, 4); + ptr->views[i].view_refs[j].dep_comp_idc = gf_bs_read_int(bs, 2); + ptr->views[i].view_refs[j].ref_view_id = gf_bs_read_int(bs, 10); + } + } + return GF_OK; +} + +GF_Box *vwid_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_ViewIdentifierBox, GF_ISOM_BOX_TYPE_VWID); + return (GF_Box *)tmp; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err vwid_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + u32 i, j; + GF_ViewIdentifierBox *ptr = (GF_ViewIdentifierBox *) s; + e = gf_isom_full_box_write(s, bs); + if (e) return e; + + gf_bs_write_int(bs, 0, 2); + gf_bs_write_int(bs, ptr->min_temporal_id, 3); + gf_bs_write_int(bs, ptr->max_temporal_id, 3); + gf_bs_write_u16(bs, ptr->num_views); + + for (i=0; inum_views; i++) { + gf_bs_write_int(bs, 0, 6); + gf_bs_write_int(bs, ptr->views[i].view_id, 10); + gf_bs_write_int(bs, 0, 6); + gf_bs_write_int(bs, ptr->views[i].view_order_index, 10); + + gf_bs_write_int(bs, ptr->views[i].texture_in_stream, 1); + gf_bs_write_int(bs, ptr->views[i].texture_in_track, 1); + gf_bs_write_int(bs, ptr->views[i].depth_in_stream, 1); + gf_bs_write_int(bs, ptr->views[i].depth_in_track, 1); + gf_bs_write_int(bs, ptr->views[i].base_view_type, 2); + gf_bs_write_int(bs, ptr->views[i].num_ref_views, 10); + + for (j=0; jviews[i].num_ref_views; j++) { + gf_bs_write_int(bs, 0, 4); + gf_bs_write_int(bs, ptr->views[i].view_refs[j].dep_comp_idc, 2); + gf_bs_write_int(bs, ptr->views[i].view_refs[j].ref_view_id, 10); + } + } + return GF_OK; +} + +GF_Err vwid_box_size(GF_Box *s) +{ + u32 i; + GF_ViewIdentifierBox *ptr = (GF_ViewIdentifierBox *) s; + ptr->size += 3; + for (i=0; inum_views; i++) { + ptr->size += 6 + 2 * ptr->views[i].num_ref_views; + } + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +void pcmC_box_del(GF_Box *s) +{ + gf_free(s); +} + +GF_Err pcmC_box_read(GF_Box *s,GF_BitStream *bs) +{ + GF_PCMConfigBox *ptr = (GF_PCMConfigBox *) s; + + ISOM_DECREASE_SIZE(s, 2) + ptr->format_flags = gf_bs_read_u8(bs); + ptr->PCM_sample_size = gf_bs_read_u8(bs); + return GF_OK; +} + +GF_Box *pcmC_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_PCMConfigBox, GF_ISOM_BOX_TYPE_PCMC); + return (GF_Box *)tmp; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err pcmC_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_PCMConfigBox *ptr = (GF_PCMConfigBox *) s; + + e = gf_isom_full_box_write(s, bs); + if (e) return e; + gf_bs_write_u8(bs, ptr->format_flags); + gf_bs_write_u8(bs, ptr->PCM_sample_size); + return GF_OK; +} + +GF_Err pcmC_box_size(GF_Box *s) +{ + s->size += 2; + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + + +void chnl_box_del(GF_Box *s) +{ + gf_free(s); +} + +GF_Err chnl_box_read(GF_Box *s,GF_BitStream *bs) +{ + GF_ChannelLayoutBox *ptr = (GF_ChannelLayoutBox *) s; + + ISOM_DECREASE_SIZE(s, 1) + ptr->layout.stream_structure = gf_bs_read_u8(bs); + if (ptr->layout.stream_structure & 1) { + ISOM_DECREASE_SIZE(s, 1) + ptr->layout.definedLayout = gf_bs_read_u8(bs); + if (ptr->layout.definedLayout) { + u32 remain = (u32) ptr->size; + if (ptr->layout.stream_structure & 2) remain--; + ptr->layout.channels_count = 0; + while (remain) { + ISOM_DECREASE_SIZE(s, 1) + ptr->layout.layouts[ptr->layout.channels_count].position = gf_bs_read_u8(bs); + remain--; + if (ptr->layout.layouts[ptr->layout.channels_count].position == 126) { + ISOM_DECREASE_SIZE(s, 3) + ptr->layout.layouts[ptr->layout.channels_count].azimuth = gf_bs_read_int(bs, 16); + ptr->layout.layouts[ptr->layout.channels_count].elevation = gf_bs_read_int(bs, 8); + remain-=3; + } + } + } else { + ISOM_DECREASE_SIZE(s, 8) + ptr->layout.omittedChannelsMap = gf_bs_read_u64(bs); + } + } + if (ptr->layout.stream_structure & 2) { + ISOM_DECREASE_SIZE(s, 1) + ptr->layout.object_count = gf_bs_read_u8(bs); + } + return GF_OK; +} + +GF_Box *chnl_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_ChannelLayoutBox, GF_ISOM_BOX_TYPE_CHNL); + return (GF_Box *)tmp; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err chnl_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + GF_ChannelLayoutBox *ptr = (GF_ChannelLayoutBox *) s; + + e = gf_isom_full_box_write(s, bs); + if (e) return e; + + gf_bs_write_u8(bs, ptr->layout.stream_structure); + if (ptr->layout.stream_structure & 1) { + gf_bs_write_u8(bs, ptr->layout.definedLayout); + if (ptr->layout.definedLayout==0) { + u32 i; + for (i=0; ilayout.channels_count; i++) { + gf_bs_write_u8(bs, ptr->layout.layouts[i].position); + if (ptr->layout.layouts[i].position==126) { + gf_bs_write_int(bs, ptr->layout.layouts[i].azimuth, 16); + gf_bs_write_int(bs, ptr->layout.layouts[i].elevation, 8); + } + } + } else { + gf_bs_write_u64(bs, ptr->layout.omittedChannelsMap); + } + } + if (ptr->layout.stream_structure & 2) { + gf_bs_write_u8(bs, ptr->layout.object_count); + } + return GF_OK; +} + +GF_Err chnl_box_size(GF_Box *s) +{ + GF_ChannelLayoutBox *ptr = (GF_ChannelLayoutBox *) s; + s->size += 1; + if (ptr->layout.stream_structure & 1) { + s->size += 1; + if (ptr->layout.definedLayout==0) { + u32 i; + for (i=0; ilayout.channels_count; i++) { + s->size+=1; + if (ptr->layout.layouts[i].position==126) + s->size+=3; + } + } else { + s->size += 8; + } + } + if (ptr->layout.stream_structure & 2) { + s->size += 1; + } + return GF_OK; +} + +#endif /*GPAC_DISABLE_ISOM_WRITE*/ + + +GF_Box *emsg_box_new() +{ + ISOM_DECL_BOX_ALLOC(GF_EventMessageBox, GF_ISOM_BOX_TYPE_EMSG); + return (GF_Box *)tmp; +} + +void emsg_box_del(GF_Box *s) +{ + GF_EventMessageBox *ptr = (GF_EventMessageBox *) s; + if (ptr == NULL) return; + if (ptr->scheme_id_uri) gf_free(ptr->scheme_id_uri); + if (ptr->value) gf_free(ptr->value); + if (ptr->message_data) gf_free(ptr->message_data); + gf_free(ptr); +} + +GF_Err emsg_box_read(GF_Box *s,GF_BitStream *bs) +{ + GF_Err e; + GF_EventMessageBox *ptr = (GF_EventMessageBox*) s; + + if (ptr->version==0) { + e = gf_isom_read_null_terminated_string(s, bs, ptr->size, &ptr->scheme_id_uri); + if (e) return e; + e = gf_isom_read_null_terminated_string(s, bs, ptr->size, &ptr->value); + if (e) return e; + + ISOM_DECREASE_SIZE(ptr, 16); + ptr->timescale = gf_bs_read_u32(bs); + ptr->presentation_time_delta = gf_bs_read_u32(bs); + ptr->event_duration = gf_bs_read_u32(bs); + ptr->event_id = gf_bs_read_u32(bs); + } else if (ptr->version==1) { + ISOM_DECREASE_SIZE(ptr, 20); + ptr->timescale = gf_bs_read_u32(bs); + ptr->presentation_time_delta = gf_bs_read_u64(bs); + ptr->event_duration = gf_bs_read_u32(bs); + ptr->event_id = gf_bs_read_u32(bs); + + e = gf_isom_read_null_terminated_string(s, bs, ptr->size, &ptr->scheme_id_uri); + if (e) return e; + e = gf_isom_read_null_terminated_string(s, bs, ptr->size, &ptr->value); + if (e) return e; + } else { + return GF_OK; + } + if (ptr->size) { + if (ptr->size>0xFFFFFFFUL) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[IsoMedia] emsg message data size too big ("LLU") to be loaded\n", ptr->size)); + return GF_OUT_OF_MEM; + } + ptr->message_data_size = (u32) ptr->size; + ptr->message_data = gf_malloc(ptr->message_data_size); + if (!ptr->message_data) return GF_OUT_OF_MEM; + gf_bs_read_data(bs, ptr->message_data, ptr->message_data_size); + ptr->size = 0; + } + return GF_OK; +} + +#ifndef GPAC_DISABLE_ISOM_WRITE + +GF_Err emsg_box_write(GF_Box *s, GF_BitStream *bs) +{ + GF_Err e; + u32 len; + GF_EventMessageBox *ptr = (GF_EventMessageBox*) s; + + e = gf_isom_full_box_write(s, bs); + if (e) return e; + + if (ptr->version==1) { + gf_bs_write_u32(bs, ptr->timescale); + gf_bs_write_u64(bs, ptr->presentation_time_delta); + gf_bs_write_u32(bs, ptr->event_duration); + gf_bs_write_u32(bs, ptr->event_id); + } + + len = ptr->scheme_id_uri ? (u32) strlen(ptr->scheme_id_uri) : 0; + if (len) gf_bs_write_data(bs, ptr->scheme_id_uri, len); + gf_bs_write_u8(bs, 0); + + len = ptr->value ? (u32) strlen(ptr->value) : 0; + if (len) gf_bs_write_data(bs, ptr->value, len); + gf_bs_write_u8(bs, 0); + + if (ptr->version==0) { + gf_bs_write_u32(bs, ptr->timescale); + gf_bs_write_u32(bs, (u32) ptr->presentation_time_delta); + gf_bs_write_u32(bs, ptr->event_duration); + gf_bs_write_u32(bs, ptr->event_id); + } + if (ptr->message_data) + gf_bs_write_data(bs, ptr->message_data, ptr->message_data_size); + return GF_OK; +} + +GF_Err emsg_box_size(GF_Box *s) +{ + GF_EventMessageBox *ptr = (GF_EventMessageBox*) s; + + ptr->size += 4; + if (ptr->version) { + ptr->size += 20; + } else { + ptr->size += 16; + } + ptr->size+=2; //1 NULL-terminated strings + if (ptr->scheme_id_uri) ptr->size += strlen(ptr->scheme_id_uri); + if (ptr->value) ptr->size += strlen(ptr->value); + if (ptr->message_data) + ptr->size += ptr->message_data_size; + + return GF_OK; +} +#endif // GPAC_DISABLE_ISOM_WRITE + #endif /*GPAC_DISABLE_ISOM*/ diff --git a/src/isomedia/box_code_drm.c b/src/isomedia/box_code_drm.c index 730a249..32c5e16 100644 --- a/src/isomedia/box_code_drm.c +++ b/src/isomedia/box_code_drm.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Cyril Concolato, Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2012 + * Copyright (c) Telecom ParisTech 2005-2019 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -28,111 +28,83 @@ #ifndef GPAC_DISABLE_ISOM /* ProtectionInfo Box */ -GF_Box *sinf_New() +GF_Box *sinf_box_new() { ISOM_DECL_BOX_ALLOC(GF_ProtectionSchemeInfoBox, GF_ISOM_BOX_TYPE_SINF); return (GF_Box *)tmp; } -void sinf_del(GF_Box *s) +void sinf_box_del(GF_Box *s) { - GF_ProtectionSchemeInfoBox *ptr = (GF_ProtectionSchemeInfoBox *)s; - if (ptr == NULL) return; - if (ptr->original_format) gf_isom_box_del((GF_Box *)ptr->original_format); - if (ptr->info) gf_isom_box_del((GF_Box *)ptr->info); - if (ptr->scheme_type) gf_isom_box_del((GF_Box *)ptr->scheme_type); - gf_free(ptr); + gf_free(s); } -GF_Err sinf_AddBox(GF_Box *s, GF_Box *a) +GF_Err sinf_on_child_box(GF_Box *s, GF_Box *a) { GF_ProtectionSchemeInfoBox *ptr = (GF_ProtectionSchemeInfoBox *)s; switch (a->type) { case GF_ISOM_BOX_TYPE_FRMA: - if (ptr->original_format) return GF_ISOM_INVALID_FILE; + if (ptr->original_format) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->original_format = (GF_OriginalFormatBox*)a; break; case GF_ISOM_BOX_TYPE_SCHM: - if (ptr->scheme_type) return GF_ISOM_INVALID_FILE; + if (ptr->scheme_type) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->scheme_type = (GF_SchemeTypeBox*)a; break; case GF_ISOM_BOX_TYPE_SCHI: - if (ptr->info) return GF_ISOM_INVALID_FILE; + if (ptr->info) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->info = (GF_SchemeInformationBox*)a; break; - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err sinf_Read(GF_Box *s, GF_BitStream *bs) +GF_Err sinf_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, sinf_AddBox); + return gf_isom_box_array_read(s, bs, sinf_on_child_box); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err sinf_Write(GF_Box *s, GF_BitStream *bs) +GF_Err sinf_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_ProtectionSchemeInfoBox *ptr = (GF_ProtectionSchemeInfoBox *)s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_box_write_header(s, bs); - if (e) return e; - //frma - e = gf_isom_box_write((GF_Box *) ptr->original_format, bs); - if (e) return e; - // schm - e = gf_isom_box_write((GF_Box *) ptr->scheme_type, bs); - if (e) return e; - // schi - e = gf_isom_box_write((GF_Box *) ptr->info, bs); - if (e) return e; - return GF_OK; + return gf_isom_box_write_header(s, bs); } -GF_Err sinf_Size(GF_Box *s) +GF_Err sinf_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_ProtectionSchemeInfoBox *ptr = (GF_ProtectionSchemeInfoBox *)s; - if (!s) return GF_BAD_PARAM; - - e = gf_isom_box_size((GF_Box *) ptr->original_format); - if (e) return e; - ptr->size += ptr->original_format->size; - e = gf_isom_box_size((GF_Box *) ptr->scheme_type); - if (e) return e; - ptr->size += ptr->scheme_type->size; - e = gf_isom_box_size((GF_Box *) ptr->info); - if (e) return e; - ptr->size += ptr->info->size; - return GF_OK; + gf_isom_check_position(s, (GF_Box *)ptr->original_format, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->scheme_type, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->info, &pos); + return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ /* OriginalFormat Box */ -GF_Box *frma_New() +GF_Box *frma_box_new() { ISOM_DECL_BOX_ALLOC(GF_OriginalFormatBox, GF_ISOM_BOX_TYPE_FRMA); return (GF_Box *)tmp; } -void frma_del(GF_Box *s) +void frma_box_del(GF_Box *s) { GF_OriginalFormatBox *ptr = (GF_OriginalFormatBox *)s; if (ptr == NULL) return; gf_free(ptr); } -GF_Err frma_Read(GF_Box *s, GF_BitStream *bs) +GF_Err frma_box_read(GF_Box *s, GF_BitStream *bs) { GF_OriginalFormatBox *ptr = (GF_OriginalFormatBox *)s; + ISOM_DECREASE_SIZE(ptr, 4); ptr->data_format = gf_bs_read_u32(bs); return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err frma_Write(GF_Box *s, GF_BitStream *bs) +GF_Err frma_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_OriginalFormatBox *ptr = (GF_OriginalFormatBox *)s; @@ -143,7 +115,7 @@ GF_Err frma_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err frma_Size(GF_Box *s) +GF_Err frma_box_size(GF_Box *s) { GF_OriginalFormatBox *ptr = (GF_OriginalFormatBox *)s; ptr->size += 4; @@ -152,13 +124,13 @@ GF_Err frma_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ /* SchemeType Box */ -GF_Box *schm_New() +GF_Box *schm_box_new() { ISOM_DECL_BOX_ALLOC(GF_SchemeTypeBox, GF_ISOM_BOX_TYPE_SCHM); return (GF_Box *)tmp; } -void schm_del(GF_Box *s) +void schm_box_del(GF_Box *s) { GF_SchemeTypeBox *ptr = (GF_SchemeTypeBox *)s; if (ptr == NULL) return; @@ -166,13 +138,13 @@ void schm_del(GF_Box *s) gf_free(ptr); } -GF_Err schm_Read(GF_Box *s, GF_BitStream *bs) +GF_Err schm_box_read(GF_Box *s, GF_BitStream *bs) { GF_SchemeTypeBox *ptr = (GF_SchemeTypeBox *)s; + ISOM_DECREASE_SIZE(ptr, 8); ptr->scheme_type = gf_bs_read_u32(bs); ptr->scheme_version = gf_bs_read_u32(bs); - ISOM_DECREASE_SIZE(ptr, 8); if (ptr->size && (ptr->flags & 0x000001)) { u32 len = (u32) (ptr->size); @@ -184,7 +156,7 @@ GF_Err schm_Read(GF_Box *s, GF_BitStream *bs) } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err schm_Write(GF_Box *s, GF_BitStream *bs) +GF_Err schm_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_SchemeTypeBox *ptr = (GF_SchemeTypeBox *) s; @@ -193,67 +165,63 @@ GF_Err schm_Write(GF_Box *s, GF_BitStream *bs) if (e) return e; gf_bs_write_u32(bs, ptr->scheme_type); gf_bs_write_u32(bs, ptr->scheme_version); - if (ptr->flags & 0x000001) gf_bs_write_data(bs, ptr->URI, (u32) strlen(ptr->URI)+1); + if (ptr->flags & 0x000001) { + if (ptr->URI) + gf_bs_write_data(bs, ptr->URI, (u32) strlen(ptr->URI)+1); + else + gf_bs_write_u8(bs, 0); + } return GF_OK; } -GF_Err schm_Size(GF_Box *s) +GF_Err schm_box_size(GF_Box *s) { GF_SchemeTypeBox *ptr = (GF_SchemeTypeBox *) s; if (!s) return GF_BAD_PARAM; ptr->size += 8; - if (ptr->flags & 0x000001) ptr->size += strlen(ptr->URI)+1; + if (ptr->flags & 0x000001) ptr->size += 1 + (ptr->URI ? strlen(ptr->URI) : 0); return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ /* SchemeInformation Box */ -GF_Box *schi_New() +GF_Box *schi_box_new() { ISOM_DECL_BOX_ALLOC(GF_SchemeInformationBox, GF_ISOM_BOX_TYPE_SCHI); return (GF_Box *)tmp; } -void schi_del(GF_Box *s) +void schi_box_del(GF_Box *s) { - GF_SchemeInformationBox *ptr = (GF_SchemeInformationBox *)s; - if (ptr == NULL) return; - if (ptr->ikms) gf_isom_box_del((GF_Box *)ptr->ikms); - if (ptr->isfm) gf_isom_box_del((GF_Box *)ptr->isfm); - if (ptr->islt) gf_isom_box_del((GF_Box *)ptr->islt); - if (ptr->odkm) gf_isom_box_del((GF_Box *)ptr->odkm); - if (ptr->tenc) gf_isom_box_del((GF_Box *)ptr->tenc); - if (ptr->piff_tenc) gf_isom_box_del((GF_Box *)ptr->piff_tenc); - if (ptr->adkm) gf_isom_box_del((GF_Box *)ptr->adkm); - gf_free(ptr); + gf_free(s); } -GF_Err schi_AddBox(GF_Box *s, GF_Box *a) +GF_Err schi_on_child_box(GF_Box *s, GF_Box *a) { GF_SchemeInformationBox *ptr = (GF_SchemeInformationBox *)s; switch (a->type) { case GF_ISOM_BOX_TYPE_IKMS: - if (ptr->ikms) return GF_ISOM_INVALID_FILE; + if (ptr->ikms) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->ikms = (GF_ISMAKMSBox*)a; return GF_OK; case GF_ISOM_BOX_TYPE_ISFM: - if (ptr->isfm) return GF_ISOM_INVALID_FILE; + if (ptr->isfm) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->isfm = (GF_ISMASampleFormatBox*)a; return GF_OK; case GF_ISOM_BOX_TYPE_ISLT: - if (ptr->islt) return GF_ISOM_INVALID_FILE; + if (ptr->islt) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->islt = (GF_ISMACrypSaltBox*)a; return GF_OK; case GF_ISOM_BOX_TYPE_ODKM: - if (ptr->odkm) return GF_ISOM_INVALID_FILE; + if (ptr->odkm) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->odkm = (GF_OMADRMKMSBox*)a; return GF_OK; case GF_ISOM_BOX_TYPE_TENC: - if (ptr->tenc) return GF_ISOM_INVALID_FILE; + if (ptr->tenc) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->tenc = (GF_TrackEncryptionBox *)a; return GF_OK; case GF_ISOM_BOX_TYPE_ADKM: - if (ptr->adkm) return GF_ISOM_INVALID_FILE; + if (ptr->adkm) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->adkm = (GF_AdobeDRMKeyManagementSystemBox *)a; return GF_OK; case GF_ISOM_BOX_TYPE_UUID: @@ -262,112 +230,48 @@ GF_Err schi_AddBox(GF_Box *s, GF_Box *a) ptr->piff_tenc = (GF_PIFFTrackEncryptionBox *)a; return GF_OK; } else { - return gf_isom_box_add_default(s, a); + return GF_OK; } - default: - return gf_isom_box_add_default(s, a); } + return GF_OK; } -GF_Err schi_Read(GF_Box *s, GF_BitStream *bs) +GF_Err schi_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, schi_AddBox); + return gf_isom_box_array_read(s, bs, schi_on_child_box); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err schi_Write(GF_Box *s, GF_BitStream *bs) +GF_Err schi_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_SchemeInformationBox *ptr = (GF_SchemeInformationBox *)s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_box_write_header(s, bs); - if (e) return e; - - if (ptr->ikms) { - e = gf_isom_box_write((GF_Box *) ptr->ikms, bs); - if (e) return e; - } - if (ptr->isfm) { - e = gf_isom_box_write((GF_Box *) ptr->isfm, bs); - if (e) return e; - } - if (ptr->islt) { - e = gf_isom_box_write((GF_Box *) ptr->islt, bs); - if (e) return e; - } - if (ptr->odkm) { - e = gf_isom_box_write((GF_Box *) ptr->odkm, bs); - if (e) return e; - } - if (ptr->tenc) { - e = gf_isom_box_write((GF_Box *) ptr->tenc, bs); - if (e) return e; - } - if (ptr->adkm) { - e = gf_isom_box_write((GF_Box *) ptr->adkm, bs); - if (e) return e; - } - if (ptr->piff_tenc) { - e = gf_isom_box_write((GF_Box *) ptr->piff_tenc, bs); - if (e) return e; - } - return GF_OK; + return gf_isom_box_write_header(s, bs); } -GF_Err schi_Size(GF_Box *s) +GF_Err schi_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_SchemeInformationBox *ptr = (GF_SchemeInformationBox *)s; - if (!s) return GF_BAD_PARAM; - if (ptr->ikms) { - e = gf_isom_box_size((GF_Box *) ptr->ikms); - if (e) return e; - ptr->size += ptr->ikms->size; - } - if (ptr->isfm) { - e = gf_isom_box_size((GF_Box *) ptr->isfm); - if (e) return e; - ptr->size += ptr->isfm->size; - } - if (ptr->islt) { - e = gf_isom_box_size((GF_Box *) ptr->islt); - if (e) return e; - ptr->size += ptr->isfm->size; - } - if (ptr->odkm) { - e = gf_isom_box_size((GF_Box *) ptr->odkm); - if (e) return e; - ptr->size += ptr->odkm->size; - } - if (ptr->tenc) { - e = gf_isom_box_size((GF_Box *) ptr->tenc); - if (e) return e; - ptr->size += ptr->tenc->size; - } - if (ptr->adkm) { - e = gf_isom_box_size((GF_Box *) ptr->adkm); - if (e) return e; - ptr->size += ptr->adkm->size; - } - if (ptr->piff_tenc) { - e = gf_isom_box_size((GF_Box *) ptr->piff_tenc); - if (e) return e; - ptr->size += ptr->piff_tenc->size; - } + gf_isom_check_position(s, (GF_Box *)ptr->ikms, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->isfm, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->islt, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->odkm, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->tenc, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->adkm, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->piff_tenc, &pos); return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ /* ISMAKMS Box */ -GF_Box *iKMS_New() +GF_Box *iKMS_box_new() { ISOM_DECL_BOX_ALLOC(GF_ISMAKMSBox, GF_ISOM_BOX_TYPE_IKMS); return (GF_Box *)tmp; } -void iKMS_del(GF_Box *s) +void iKMS_box_del(GF_Box *s) { GF_ISMAKMSBox *ptr = (GF_ISMAKMSBox *)s; if (ptr == NULL) return; @@ -375,7 +279,7 @@ void iKMS_del(GF_Box *s) gf_free(ptr); } -GF_Err iKMS_Read(GF_Box *s, GF_BitStream *bs) +GF_Err iKMS_box_read(GF_Box *s, GF_BitStream *bs) { u32 len; GF_ISMAKMSBox *ptr = (GF_ISMAKMSBox *)s; @@ -388,33 +292,35 @@ GF_Err iKMS_Read(GF_Box *s, GF_BitStream *bs) } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err iKMS_Write(GF_Box *s, GF_BitStream *bs) +GF_Err iKMS_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_ISMAKMSBox *ptr = (GF_ISMAKMSBox *)s; if (!s) return GF_BAD_PARAM; e = gf_isom_full_box_write(s, bs); if (e) return e; - gf_bs_write_data(bs, ptr->URI, (u32) strlen(ptr->URI)+1); + if (ptr->URI) + gf_bs_write_data(bs, ptr->URI, (u32) strlen(ptr->URI)); + gf_bs_write_u8(bs, 0); return GF_OK; } -GF_Err iKMS_Size(GF_Box *s) +GF_Err iKMS_box_size(GF_Box *s) { GF_ISMAKMSBox *ptr = (GF_ISMAKMSBox *)s; - ptr->size += strlen(ptr->URI)+1; + ptr->size += (ptr->URI ? strlen(ptr->URI) : 0) + 1; return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ /* ISMASampleFormat Box */ -GF_Box *iSFM_New() +GF_Box *iSFM_box_new() { ISOM_DECL_BOX_ALLOC(GF_ISMASampleFormatBox, GF_ISOM_BOX_TYPE_ISFM); return (GF_Box *)tmp; } -void iSFM_del(GF_Box *s) +void iSFM_box_del(GF_Box *s) { GF_ISMASampleFormatBox *ptr = (GF_ISMASampleFormatBox *)s; if (ptr == NULL) return; @@ -422,10 +328,11 @@ void iSFM_del(GF_Box *s) } -GF_Err iSFM_Read(GF_Box *s, GF_BitStream *bs) +GF_Err iSFM_box_read(GF_Box *s, GF_BitStream *bs) { GF_ISMASampleFormatBox *ptr = (GF_ISMASampleFormatBox *)s; + ISOM_DECREASE_SIZE(ptr, 3); ptr->selective_encryption = gf_bs_read_int(bs, 1); gf_bs_read_int(bs, 7); ptr->key_indicator_length = gf_bs_read_u8(bs); @@ -434,7 +341,7 @@ GF_Err iSFM_Read(GF_Box *s, GF_BitStream *bs) } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err iSFM_Write(GF_Box *s, GF_BitStream *bs) +GF_Err iSFM_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_ISMASampleFormatBox *ptr = (GF_ISMASampleFormatBox *)s; @@ -448,7 +355,7 @@ GF_Err iSFM_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err iSFM_Size(GF_Box *s) +GF_Err iSFM_box_size(GF_Box *s) { GF_ISMASampleFormatBox *ptr = (GF_ISMASampleFormatBox *)s; ptr->size += 3; @@ -457,13 +364,13 @@ GF_Err iSFM_Size(GF_Box *s) #endif /*GPAC_DISABLE_ISOM_WRITE*/ /* ISMASampleFormat Box */ -GF_Box *iSLT_New() +GF_Box *iSLT_box_new() { ISOM_DECL_BOX_ALLOC(GF_ISMACrypSaltBox, GF_ISOM_BOX_TYPE_ISLT); return (GF_Box *)tmp; } -void iSLT_del(GF_Box *s) +void iSLT_box_del(GF_Box *s) { GF_ISMACrypSaltBox *ptr = (GF_ISMACrypSaltBox *)s; if (ptr == NULL) return; @@ -471,7 +378,7 @@ void iSLT_del(GF_Box *s) } -GF_Err iSLT_Read(GF_Box *s, GF_BitStream *bs) +GF_Err iSLT_box_read(GF_Box *s, GF_BitStream *bs) { GF_ISMACrypSaltBox *ptr = (GF_ISMACrypSaltBox *)s; if (ptr == NULL) return GF_BAD_PARAM; @@ -481,15 +388,19 @@ GF_Err iSLT_Read(GF_Box *s, GF_BitStream *bs) } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err iSLT_Write(GF_Box *s, GF_BitStream *bs) +GF_Err iSLT_box_write(GF_Box *s, GF_BitStream *bs) { + GF_Err e; GF_ISMACrypSaltBox *ptr = (GF_ISMACrypSaltBox *)s; if (!s) return GF_BAD_PARAM; + e = gf_isom_full_box_write(s, bs); + if (e) return e; + gf_bs_write_u64(bs, ptr->salt); return GF_OK; } -GF_Err iSLT_Size(GF_Box *s) +GF_Err iSLT_box_size(GF_Box *s) { s->size += 8; return GF_OK; @@ -499,14 +410,14 @@ GF_Err iSLT_Size(GF_Box *s) /* OMADRMCommonHeader Box */ -GF_Box *ohdr_New() +GF_Box *ohdr_box_new() { ISOM_DECL_BOX_ALLOC(GF_OMADRMCommonHeaderBox, GF_ISOM_BOX_TYPE_OHDR); - tmp->other_boxes = gf_list_new(); + tmp->child_boxes = gf_list_new(); return (GF_Box *)tmp; } -void ohdr_del(GF_Box *s) +void ohdr_box_del(GF_Box *s) { GF_OMADRMCommonHeaderBox *ptr = (GF_OMADRMCommonHeaderBox*)s; if (ptr == NULL) return; @@ -516,51 +427,49 @@ void ohdr_del(GF_Box *s) gf_free(ptr); } -GF_Err ohdr_AddBox(GF_Box *s, GF_Box *a) -{ - return gf_isom_box_add_default(s, a); -} - -GF_Err ohdr_Read(GF_Box *s, GF_BitStream *bs) +GF_Err ohdr_box_read(GF_Box *s, GF_BitStream *bs) { u16 cid_len, ri_len; GF_OMADRMCommonHeaderBox *ptr = (GF_OMADRMCommonHeaderBox*)s; + ISOM_DECREASE_SIZE(ptr, (1+1+8+2+2+2) ); ptr->EncryptionMethod = gf_bs_read_u8(bs); ptr->PaddingScheme = gf_bs_read_u8(bs); ptr->PlaintextLength = gf_bs_read_u64(bs); cid_len = gf_bs_read_u16(bs); ri_len = gf_bs_read_u16(bs); ptr->TextualHeadersLen = gf_bs_read_u16(bs); - ISOM_DECREASE_SIZE(ptr, (1+1+8+2+2+2) ); if (ptr->sizeTextualHeadersLen) return GF_ISOM_INVALID_FILE; if (cid_len) { ptr->ContentID = (char *)gf_malloc(sizeof(char)*(cid_len+1)); + if (!ptr->ContentID) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->ContentID, cid_len); ptr->ContentID[cid_len]=0; } if (ri_len) { ptr->RightsIssuerURL = (char *)gf_malloc(sizeof(char)*(ri_len+1)); + if (!ptr->RightsIssuerURL) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->RightsIssuerURL, ri_len); ptr->RightsIssuerURL[ri_len]=0; } if (ptr->TextualHeadersLen) { ptr->TextualHeaders = (char *)gf_malloc(sizeof(char)*(ptr->TextualHeadersLen+1)); + if (!ptr->TextualHeaders) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->TextualHeaders, ptr->TextualHeadersLen); ptr->TextualHeaders[ptr->TextualHeadersLen] = 0; } ISOM_DECREASE_SIZE(ptr, (cid_len+ri_len+ptr->TextualHeadersLen) ); - return gf_isom_box_array_read(s, bs, ohdr_AddBox); + return gf_isom_box_array_read(s, bs, NULL); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err ohdr_Write(GF_Box *s, GF_BitStream *bs) +GF_Err ohdr_box_write(GF_Box *s, GF_BitStream *bs) { u16 cid_len, ri_len; GF_Err e; @@ -586,7 +495,7 @@ GF_Err ohdr_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err ohdr_Size(GF_Box *s) +GF_Err ohdr_box_size(GF_Box *s) { GF_OMADRMCommonHeaderBox *ptr = (GF_OMADRMCommonHeaderBox *)s; ptr->size += 1+1+8+2+2+2; @@ -599,13 +508,13 @@ GF_Err ohdr_Size(GF_Box *s) /* OMADRMGroupID Box */ -GF_Box *grpi_New() +GF_Box *grpi_box_new() { ISOM_DECL_BOX_ALLOC(GF_OMADRMGroupIDBox, GF_ISOM_BOX_TYPE_GRPI); return (GF_Box *)tmp; } -void grpi_del(GF_Box *s) +void grpi_box_del(GF_Box *s) { GF_OMADRMGroupIDBox *ptr = (GF_OMADRMGroupIDBox *)s; if (ptr == NULL) return; @@ -614,31 +523,32 @@ void grpi_del(GF_Box *s) gf_free(ptr); } -GF_Err grpi_Read(GF_Box *s, GF_BitStream *bs) +GF_Err grpi_box_read(GF_Box *s, GF_BitStream *bs) { u16 gid_len; GF_OMADRMGroupIDBox *ptr = (GF_OMADRMGroupIDBox*)s; + ISOM_DECREASE_SIZE(ptr, (1+2+2) ); gid_len = gf_bs_read_u16(bs); ptr->GKEncryptionMethod = gf_bs_read_u8(bs); ptr->GKLength = gf_bs_read_u16(bs); - ISOM_DECREASE_SIZE(ptr, (1+2+2) ); - if (ptr->sizeGKLength) return GF_ISOM_INVALID_FILE; ptr->GroupID = gf_malloc(sizeof(char)*(gid_len+1)); + if (!ptr->GroupID) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->GroupID, gid_len); ptr->GroupID[gid_len]=0; ptr->GroupKey = (char *)gf_malloc(sizeof(char)*ptr->GKLength); + if (!ptr->GroupKey) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->GroupKey, ptr->GKLength); ISOM_DECREASE_SIZE(ptr, (gid_len+ptr->GKLength) ); return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err grpi_Write(GF_Box *s, GF_BitStream *bs) +GF_Err grpi_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u16 gid_len; @@ -655,7 +565,7 @@ GF_Err grpi_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err grpi_Size(GF_Box *s) +GF_Err grpi_box_size(GF_Box *s) { GF_OMADRMGroupIDBox *ptr = (GF_OMADRMGroupIDBox *)s; ptr->size += 2+2+1 + ptr->GKLength; @@ -668,26 +578,26 @@ GF_Err grpi_Size(GF_Box *s) /* OMADRMMutableInformation Box */ -GF_Box *mdri_New() +GF_Box *mdri_box_new() { ISOM_DECL_BOX_ALLOC(GF_OMADRMMutableInformationBox, GF_ISOM_BOX_TYPE_MDRI); return (GF_Box *)tmp; } -void mdri_del(GF_Box *s) +void mdri_box_del(GF_Box *s) { GF_OMADRMMutableInformationBox*ptr = (GF_OMADRMMutableInformationBox*)s; if (ptr == NULL) return; gf_free(ptr); } -GF_Err mdri_Read(GF_Box *s, GF_BitStream *bs) +GF_Err mdri_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, gf_isom_box_add_default); + return gf_isom_box_array_read(s, bs, NULL); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err mdri_Write(GF_Box *s, GF_BitStream *bs) +GF_Err mdri_box_write(GF_Box *s, GF_BitStream *bs) { // GF_OMADRMMutableInformationBox*ptr = (GF_OMADRMMutableInformationBox*)s; GF_Err e = gf_isom_box_write_header(s, bs); @@ -695,7 +605,7 @@ GF_Err mdri_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err mdri_Size(GF_Box *s) +GF_Err mdri_box_size(GF_Box *s) { return GF_OK; } @@ -703,19 +613,19 @@ GF_Err mdri_Size(GF_Box *s) /* OMADRMTransactionTracking Box */ -GF_Box *odtt_New() +GF_Box *odtt_box_new() { ISOM_DECL_BOX_ALLOC(GF_OMADRMTransactionTrackingBox, GF_ISOM_BOX_TYPE_ODTT); return (GF_Box *)tmp; } -void odtt_del(GF_Box *s) +void odtt_box_del(GF_Box *s) { GF_OMADRMTransactionTrackingBox *ptr = (GF_OMADRMTransactionTrackingBox*)s; gf_free(ptr); } -GF_Err odtt_Read(GF_Box *s, GF_BitStream *bs) +GF_Err odtt_box_read(GF_Box *s, GF_BitStream *bs) { GF_OMADRMTransactionTrackingBox *ptr = (GF_OMADRMTransactionTrackingBox *)s; @@ -725,7 +635,7 @@ GF_Err odtt_Read(GF_Box *s, GF_BitStream *bs) } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err odtt_Write(GF_Box *s, GF_BitStream *bs) +GF_Err odtt_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_OMADRMTransactionTrackingBox *ptr = (GF_OMADRMTransactionTrackingBox*)s; @@ -736,7 +646,7 @@ GF_Err odtt_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err odtt_Size(GF_Box *s) +GF_Err odtt_box_size(GF_Box *s) { s->size += 16; return GF_OK; @@ -746,32 +656,33 @@ GF_Err odtt_Size(GF_Box *s) /* OMADRMRightsObject Box */ -GF_Box *odrb_New() +GF_Box *odrb_box_new() { ISOM_DECL_BOX_ALLOC(GF_OMADRMRightsObjectBox, GF_ISOM_BOX_TYPE_ODRB); return (GF_Box *)tmp; } -void odrb_del(GF_Box *s) +void odrb_box_del(GF_Box *s) { GF_OMADRMRightsObjectBox *ptr = (GF_OMADRMRightsObjectBox*)s; if (ptr->oma_ro) gf_free(ptr->oma_ro); gf_free(ptr); } -GF_Err odrb_Read(GF_Box *s, GF_BitStream *bs) +GF_Err odrb_box_read(GF_Box *s, GF_BitStream *bs) { GF_OMADRMRightsObjectBox *ptr = (GF_OMADRMRightsObjectBox *)s; ptr->oma_ro_size = (u32) ptr->size; ptr->oma_ro = (char*) gf_malloc(sizeof(char)*ptr->oma_ro_size); + if (!ptr->oma_ro) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->oma_ro, ptr->oma_ro_size); ptr->size = 0; return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err odrb_Write(GF_Box *s, GF_BitStream *bs) +GF_Err odrb_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_OMADRMRightsObjectBox *ptr = (GF_OMADRMRightsObjectBox *)s; @@ -782,7 +693,7 @@ GF_Err odrb_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err odrb_Size(GF_Box *s) +GF_Err odrb_box_size(GF_Box *s) { GF_OMADRMRightsObjectBox *ptr = (GF_OMADRMRightsObjectBox *)s; s->size += ptr->oma_ro_size; @@ -794,18 +705,15 @@ GF_Err odrb_Size(GF_Box *s) /* OMADRMKMS Box */ -GF_Box *odkm_New() +GF_Box *odkm_box_new() { ISOM_DECL_BOX_ALLOC(GF_OMADRMKMSBox, GF_ISOM_BOX_TYPE_ODKM); return (GF_Box *)tmp; } -void odkm_del(GF_Box *s) +void odkm_box_del(GF_Box *s) { - GF_OMADRMKMSBox *ptr = (GF_OMADRMKMSBox *)s; - if (ptr->hdr) gf_isom_box_del((GF_Box*)ptr->hdr); - if (ptr->fmt) gf_isom_box_del((GF_Box*)ptr->fmt); - gf_free(ptr); + gf_free(s); } GF_Err odkm_Add(GF_Box *s, GF_Box *a) @@ -813,57 +721,34 @@ GF_Err odkm_Add(GF_Box *s, GF_Box *a) GF_OMADRMKMSBox *ptr = (GF_OMADRMKMSBox *)s; switch (a->type) { case GF_ISOM_BOX_TYPE_OHDR: - if (ptr->hdr) gf_isom_box_del((GF_Box*)ptr->hdr); + if (ptr->hdr) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->hdr = (GF_OMADRMCommonHeaderBox *)a; return GF_OK; case GF_ISOM_BOX_TYPE_ODAF: - if (ptr->fmt) gf_isom_box_del((GF_Box*)ptr->fmt); + if (ptr->fmt) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->fmt = (GF_OMADRMAUFormatBox*)a; return GF_OK; - default: - gf_isom_box_del(a); - return GF_OK; } + return GF_OK; } -GF_Err odkm_Read(GF_Box *s, GF_BitStream *bs) +GF_Err odkm_box_read(GF_Box *s, GF_BitStream *bs) { return gf_isom_box_array_read(s, bs, odkm_Add); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err odkm_Write(GF_Box *s, GF_BitStream *bs) +GF_Err odkm_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_OMADRMKMSBox *ptr = (GF_OMADRMKMSBox *)s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_full_box_write(s, bs); - if (e) return e; - if (ptr->hdr) { - e = gf_isom_box_write((GF_Box*)ptr->hdr, bs); - if (e) return e; - } - if (ptr->fmt) { - e = gf_isom_box_write((GF_Box*)ptr->fmt, bs); - if (e) return e; - } - return GF_OK; + return gf_isom_full_box_write(s, bs); } -GF_Err odkm_Size(GF_Box *s) +GF_Err odkm_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_OMADRMKMSBox *ptr = (GF_OMADRMKMSBox *)s; - if (ptr->hdr) { - e = gf_isom_box_size((GF_Box*)ptr->hdr); - if (e) return e; - ptr->size += ptr->hdr->size; - } - if (ptr->fmt) { - e = gf_isom_box_size((GF_Box*)ptr->fmt); - if (e) return e; - ptr->size += ptr->fmt->size; - } + gf_isom_check_position(s, (GF_Box *)ptr->hdr, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->fmt, &pos); return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ @@ -871,13 +756,13 @@ GF_Err odkm_Size(GF_Box *s) -GF_Box *pssh_New() +GF_Box *pssh_box_new() { ISOM_DECL_BOX_ALLOC(GF_ProtectionSystemHeaderBox, GF_ISOM_BOX_TYPE_PSSH); return (GF_Box *)tmp; } -void pssh_del(GF_Box *s) +void pssh_box_del(GF_Box *s) { GF_ProtectionSystemHeaderBox *ptr = (GF_ProtectionSystemHeaderBox*)s; if (ptr == NULL) return; @@ -886,7 +771,7 @@ void pssh_del(GF_Box *s) gf_free(ptr); } -GF_Err pssh_Read(GF_Box *s, GF_BitStream *bs) +GF_Err pssh_box_read(GF_Box *s, GF_BitStream *bs) { GF_ProtectionSystemHeaderBox *ptr = (GF_ProtectionSystemHeaderBox *)s; @@ -897,7 +782,11 @@ GF_Err pssh_Read(GF_Box *s, GF_BitStream *bs) ISOM_DECREASE_SIZE(ptr, 4); if (ptr->KID_count) { u32 i; + if (ptr->size < ptr->KID_count * sizeof(bin128)) + return GF_ISOM_INVALID_FILE; ptr->KIDs = gf_malloc(ptr->KID_count*sizeof(bin128)); + if (!ptr->KIDs) + return GF_OUT_OF_MEM; for (i=0; iKID_count; i++) { gf_bs_read_data(bs, (char *) ptr->KIDs[i], 16); ISOM_DECREASE_SIZE(ptr, 16); @@ -907,7 +796,11 @@ GF_Err pssh_Read(GF_Box *s, GF_BitStream *bs) ptr->private_data_size = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); if (ptr->private_data_size) { + if (ptr->size < ptr->private_data_size) + return GF_ISOM_INVALID_FILE; ptr->private_data = gf_malloc(sizeof(char)*ptr->private_data_size); + if (!ptr->private_data) + return GF_OUT_OF_MEM; gf_bs_read_data(bs, (char *) ptr->private_data, ptr->private_data_size); ISOM_DECREASE_SIZE(ptr, ptr->private_data_size); } @@ -916,7 +809,7 @@ GF_Err pssh_Read(GF_Box *s, GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err pssh_Write(GF_Box *s, GF_BitStream *bs) +GF_Err pssh_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_ProtectionSystemHeaderBox *ptr = (GF_ProtectionSystemHeaderBox *) s; @@ -939,7 +832,7 @@ GF_Err pssh_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err pssh_Size(GF_Box *s) +GF_Err pssh_box_size(GF_Box *s) { GF_ProtectionSystemHeaderBox *ptr = (GF_ProtectionSystemHeaderBox*)s; @@ -955,21 +848,23 @@ GF_Err pssh_Size(GF_Box *s) #endif //GPAC_DISABLE_ISOM_WRITE -GF_Box *tenc_New() +GF_Box *tenc_box_new() { ISOM_DECL_BOX_ALLOC(GF_TrackEncryptionBox, GF_ISOM_BOX_TYPE_TENC); return (GF_Box *)tmp; } -void tenc_del(GF_Box *s) +void tenc_box_del(GF_Box *s) { gf_free(s); } -GF_Err tenc_Read(GF_Box *s, GF_BitStream *bs) +GF_Err tenc_box_read(GF_Box *s, GF_BitStream *bs) { GF_TrackEncryptionBox *ptr = (GF_TrackEncryptionBox*)s; + ISOM_DECREASE_SIZE(ptr, 20); + gf_bs_read_u8(bs); //reserved if (!ptr->version) { gf_bs_read_u8(bs); //reserved @@ -981,20 +876,21 @@ GF_Err tenc_Read(GF_Box *s, GF_BitStream *bs) ptr->Per_Sample_IV_Size = gf_bs_read_u8(bs); gf_bs_read_data(bs, (char *) ptr->KID, 16); - ISOM_DECREASE_SIZE(ptr, 20); if ((ptr->isProtected == 1) && !ptr->Per_Sample_IV_Size) { ptr->constant_IV_size = gf_bs_read_u8(bs); - gf_bs_read_data(bs, (char *) ptr->constant_IV, ptr->constant_IV_size); - ISOM_DECREASE_SIZE(ptr, (1 + ptr->constant_IV_size) ); + if (ptr->constant_IV_size > sizeof(ptr->constant_IV)) + return GF_ISOM_INVALID_FILE; + ISOM_DECREASE_SIZE(ptr, (1 + ptr->constant_IV_size) ); + gf_bs_read_data(bs, (char *) ptr->constant_IV, ptr->constant_IV_size); } return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err tenc_Write(GF_Box *s, GF_BitStream *bs) +GF_Err tenc_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_TrackEncryptionBox *ptr = (GF_TrackEncryptionBox *) s; @@ -1019,7 +915,7 @@ GF_Err tenc_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err tenc_Size(GF_Box *s) +GF_Err tenc_box_size(GF_Box *s) { GF_TrackEncryptionBox *ptr = (GF_TrackEncryptionBox*)s; ptr->size += 20; @@ -1030,37 +926,37 @@ GF_Err tenc_Size(GF_Box *s) } #endif //GPAC_DISABLE_ISOM_WRITE -GF_Box *piff_tenc_New() +GF_Box *piff_tenc_box_new() { ISOM_DECL_BOX_ALLOC(GF_PIFFTrackEncryptionBox, GF_ISOM_BOX_TYPE_UUID); tmp->internal_4cc = GF_ISOM_BOX_UUID_TENC; return (GF_Box *)tmp; } -void piff_tenc_del(GF_Box *s) +void piff_tenc_box_del(GF_Box *s) { gf_free(s); } -GF_Err piff_tenc_Read(GF_Box *s, GF_BitStream *bs) +GF_Err piff_tenc_box_read(GF_Box *s, GF_BitStream *bs) { GF_PIFFTrackEncryptionBox *ptr = (GF_PIFFTrackEncryptionBox*)s; - if (ptr->size<4) return GF_ISOM_INVALID_FILE; + ISOM_DECREASE_SIZE(ptr, 4); + //PIFF TENC extends UUID and fullbox ptr->version = gf_bs_read_u8(bs); ptr->flags = gf_bs_read_u24(bs); - ISOM_DECREASE_SIZE(ptr, 4); + ISOM_DECREASE_SIZE(ptr, 20); ptr->AlgorithmID = gf_bs_read_int(bs, 24); ptr->IV_size = gf_bs_read_u8(bs); gf_bs_read_data(bs, (char *) ptr->KID, 16); - ISOM_DECREASE_SIZE(ptr, 20); return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err piff_tenc_Write(GF_Box *s, GF_BitStream *bs) +GF_Err piff_tenc_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_PIFFTrackEncryptionBox *ptr = (GF_PIFFTrackEncryptionBox *) s; @@ -1077,7 +973,7 @@ GF_Err piff_tenc_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err piff_tenc_Size(GF_Box *s) +GF_Err piff_tenc_box_size(GF_Box *s) { GF_PIFFTrackEncryptionBox *ptr = (GF_PIFFTrackEncryptionBox*)s; ptr->size += 24; @@ -1086,7 +982,7 @@ GF_Err piff_tenc_Size(GF_Box *s) #endif //GPAC_DISABLE_ISOM_WRITE -GF_Box *piff_psec_New() +GF_Box *piff_psec_box_new() { ISOM_DECL_BOX_ALLOC(GF_SampleEncryptionBox, GF_ISOM_BOX_TYPE_UUID); tmp->internal_4cc = GF_ISOM_BOX_UUID_PSEC; @@ -1094,7 +990,7 @@ GF_Box *piff_psec_New() return (GF_Box *)tmp; } -void piff_psec_del(GF_Box *s) +void piff_psec_box_del(GF_Box *s) { GF_SampleEncryptionBox *ptr = (GF_SampleEncryptionBox *)s; while (gf_list_count(ptr->samp_aux_info)) { @@ -1107,33 +1003,34 @@ void piff_psec_del(GF_Box *s) } -GF_Err piff_psec_Read(GF_Box *s, GF_BitStream *bs) +GF_Err piff_psec_box_read(GF_Box *s, GF_BitStream *bs) { - u32 sample_count; GF_SampleEncryptionBox *ptr = (GF_SampleEncryptionBox *)s; - if (ptr->size<4) return GF_ISOM_INVALID_FILE; + + ISOM_DECREASE_SIZE(ptr, 4); + //PIFF PSEC extends UUID and fullbox ptr->version = gf_bs_read_u8(bs); ptr->flags = gf_bs_read_u24(bs); - ISOM_DECREASE_SIZE(ptr, 4); if (ptr->flags & 1) { + ISOM_DECREASE_SIZE(ptr, 20); ptr->AlgorithmID = gf_bs_read_int(bs, 24); ptr->IV_size = gf_bs_read_u8(bs); gf_bs_read_data(bs, (char *) ptr->KID, 16); - ISOM_DECREASE_SIZE(ptr, 20); } if (ptr->IV_size == 0) ptr->IV_size = 8; //default to 8 ptr->bs_offset = gf_bs_get_position(bs); - sample_count = gf_bs_read_u32(bs); + /*u32 sample_count = */gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); if (ptr->IV_size != 8 && ptr->IV_size != 16) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] PIFF PSEC box incorrect IV size: %u - shall be 8 or 16\n", ptr->IV_size)); return GF_BAD_PARAM; } //as for senc, we skip parsing of the box until we have all saiz/saio info + gf_bs_skip_bytes(bs, ptr->size); ptr->size = 0; return GF_OK; } @@ -1148,7 +1045,10 @@ GF_Err store_senc_info(GF_SampleEncryptionBox *ptr, GF_BitStream *bs) pos = gf_bs_get_position(bs); if (pos>0xFFFFFFFFULL) { - GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[iso file] \"senc\" offset larger than 32-bits , \"saio\" box version must be 1 .\n")); + if (ptr->cenc_saio && !ptr->cenc_saio->version) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] saio offset larger than 32-bits but box version 0 enforced. Retry without \"saio32\" option\n")); + return GF_BAD_PARAM; + } } e = gf_bs_seek(bs, ptr->cenc_saio->offset_first_offset_field); if (e) return e; @@ -1162,27 +1062,40 @@ GF_Err store_senc_info(GF_SampleEncryptionBox *ptr, GF_BitStream *bs) new_pos = pos; } - if (ptr->cenc_saio->offsets_large) { + if (ptr->cenc_saio->offsets) { u32 i; - u64 old_offset = ptr->cenc_saio->offsets_large[0]; + u64 old_offset = ptr->cenc_saio->offsets[0]; for (i=0; icenc_saio->entry_count; i++) { - gf_bs_write_u64(bs, new_pos + ptr->cenc_saio->offsets_large[i] - old_offset); - ptr->cenc_saio->offsets_large[i] = new_pos + ptr->cenc_saio->offsets_large[i] - old_offset; + if (ptr->cenc_saio->version) { + gf_bs_write_u64(bs, new_pos + ptr->cenc_saio->offsets[i] - old_offset); + } else { + gf_bs_write_u32(bs, (u32) (new_pos + ptr->cenc_saio->offsets[i] - old_offset)); + } + ptr->cenc_saio->offsets[i] = new_pos + ptr->cenc_saio->offsets[i] - old_offset; } } else { - gf_bs_write_u64(bs, new_pos); + if (ptr->cenc_saio->version) { + gf_bs_write_u64(bs, new_pos); + } else { + gf_bs_write_u32(bs, (u32) new_pos); + } } return gf_bs_seek(bs, pos); } -GF_Err piff_psec_Write(GF_Box *s, GF_BitStream *bs) +GF_Err piff_psec_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 sample_count; GF_SampleEncryptionBox *ptr = (GF_SampleEncryptionBox *) s; if (!s) return GF_BAD_PARAM; + sample_count = gf_list_count(ptr->samp_aux_info); + if (!sample_count) { + ptr->size = 0; + return GF_OK; + } e = gf_isom_box_write_header(s, bs); if (e) return e; gf_bs_write_u8(bs, ptr->version); @@ -1214,63 +1127,76 @@ GF_Err piff_psec_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err piff_psec_Size(GF_Box *s) +GF_Err piff_psec_box_size(GF_Box *s) { u32 i, sample_count; GF_SampleEncryptionBox *ptr = (GF_SampleEncryptionBox*)s; + sample_count = gf_list_count(ptr->samp_aux_info); + if (!sample_count) { + ptr->size = 0; + return GF_OK; + } + ptr->size += 4; if (ptr->flags & 1) { ptr->size += 20; } ptr->size += 4; - sample_count = gf_list_count(ptr->samp_aux_info); - if (sample_count) { - for (i = 0; i < sample_count; i++) { - GF_CENCSampleAuxInfo *sai = (GF_CENCSampleAuxInfo *)gf_list_get(ptr->samp_aux_info, i); - if (! sai->IV_size) continue; - ptr->size += 18 + 6*sai->subsample_count; - } + + for (i = 0; i < sample_count; i++) { + GF_CENCSampleAuxInfo *sai = (GF_CENCSampleAuxInfo *)gf_list_get(ptr->samp_aux_info, i); + if (! sai->IV_size) continue; + ptr->size += 2 + sai->IV_size + 6*sai->subsample_count; } return GF_OK; } #endif //GPAC_DISABLE_ISOM_WRITE -GF_Box *piff_pssh_New() +GF_Box *piff_pssh_box_new() { ISOM_DECL_BOX_ALLOC(GF_PIFFProtectionSystemHeaderBox, GF_ISOM_BOX_TYPE_UUID); tmp->internal_4cc = GF_ISOM_BOX_UUID_PSSH; return (GF_Box *)tmp; } -void piff_pssh_del(GF_Box *s) +void piff_pssh_box_del(GF_Box *s) { gf_free(s); } -GF_Err piff_pssh_Read(GF_Box *s, GF_BitStream *bs) +GF_Err piff_pssh_box_read(GF_Box *s, GF_BitStream *bs) { GF_PIFFProtectionSystemHeaderBox *ptr = (GF_PIFFProtectionSystemHeaderBox*)s; + ISOM_DECREASE_SIZE(ptr, 24); + //PIFF PSSH extends UUID and fullbox + ptr->version = gf_bs_read_u8(bs); + ptr->flags = gf_bs_read_u24(bs); gf_bs_read_data(bs, (char *) ptr->SystemID, 16); ptr->private_data_size = gf_bs_read_u32(bs); - ISOM_DECREASE_SIZE(ptr, 20); - + if (ptr->size < sizeof(char)*ptr->private_data_size) + return GF_ISOM_INVALID_FILE; ptr->private_data = gf_malloc(sizeof(char)*ptr->private_data_size); - gf_bs_read_data(bs, (char *) ptr->private_data, ptr->private_data_size); + if (!ptr->private_data) + return GF_OUT_OF_MEM; + ISOM_DECREASE_SIZE(ptr, ptr->private_data_size); + gf_bs_read_data(bs, (char *) ptr->private_data, ptr->private_data_size); return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err piff_pssh_Write(GF_Box *s, GF_BitStream *bs) +GF_Err piff_pssh_box_write(GF_Box *s, GF_BitStream *bs) { GF_PIFFProtectionSystemHeaderBox *ptr = (GF_PIFFProtectionSystemHeaderBox *) s; - GF_Err e = gf_isom_full_box_write(s, bs); + GF_Err e = gf_isom_box_write_header(s, bs); if (e) return e; + gf_bs_write_u8(bs, ptr->version); + gf_bs_write_u24(bs, ptr->flags); gf_bs_write_data(bs, (char *) ptr->SystemID, 16); gf_bs_write_u32(bs, ptr->private_data_size); @@ -1278,7 +1204,7 @@ GF_Err piff_pssh_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err piff_pssh_Size(GF_Box *s) +GF_Err piff_pssh_box_size(GF_Box *s) { GF_PIFFProtectionSystemHeaderBox *ptr = (GF_PIFFProtectionSystemHeaderBox*)s; @@ -1287,13 +1213,13 @@ GF_Err piff_pssh_Size(GF_Box *s) } #endif //GPAC_DISABLE_ISOM_WRITE -GF_Box *senc_New() +GF_Box *senc_box_new() { ISOM_DECL_BOX_ALLOC(GF_SampleEncryptionBox, GF_ISOM_BOX_TYPE_SENC); return (GF_Box *)tmp; } -void senc_del(GF_Box *s) +void senc_box_del(GF_Box *s) { GF_SampleEncryptionBox *ptr = (GF_SampleEncryptionBox *)s; while (gf_list_count(ptr->samp_aux_info)) { @@ -1312,8 +1238,12 @@ GF_Err senc_Parse(GF_BitStream *bs, GF_TrackBox *trak, void *traf, GF_SampleEncr #endif { GF_Err e; - u32 i, j, count; + Bool parse_failed = GF_FALSE; + u32 i, j, count, sample_number; + u32 senc_size = (u32) senc->size; + u32 subs_size = 0, def_IV_size; u64 pos = gf_bs_get_position(bs); + Bool do_warn = GF_TRUE; #ifdef GPAC_DISABLE_ISOM_FRAGMENTS if (!traf) @@ -1322,120 +1252,211 @@ GF_Err senc_Parse(GF_BitStream *bs, GF_TrackBox *trak, void *traf, GF_SampleEncr gf_bs_seek(bs, senc->bs_offset); - count = gf_bs_read_u32(bs); - if (!senc->samp_aux_info) senc->samp_aux_info = gf_list_new(); - for (i=0; iis_piff) { + //UUID + if (senc_size<16) return GF_BAD_PARAM; + senc_size -= 16; + } + if (senc->flags & 2) subs_size = 8; + + if (senc_size<4) return GF_BAD_PARAM; - samp_count = i+1; + sample_number = 1; #ifndef GPAC_DISABLE_ISOM_FRAGMENTS - if (trak) samp_count += trak->sample_count_at_seg_start; + if (trak) sample_number += trak->sample_count_at_seg_start; #endif - e = gf_isom_get_sample_cenc_info_ex(trak, traf, senc, samp_count, &is_encrypted, &sai->IV_size, NULL, NULL, NULL, NULL, NULL); - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[isobmf] could not get cenc info for sample %d: %s\n", samp_count, gf_error_to_string(e) )); - return e; + count = gf_bs_read_u32(bs); + senc_size -= 4; + + def_IV_size = 0; + //check the target size if we have one subsample + if (senc_size >= count * (16 + subs_size)) { + def_IV_size = 16; + } + else if (senc_size >= count * (8 + subs_size)) { + def_IV_size = 8; + } + else if (senc_size >= count * (subs_size)) { + def_IV_size = 0; + } + + if (!senc->samp_aux_info) senc->samp_aux_info = gf_list_new(); + for (i=0; iIV_size, NULL, NULL, NULL, NULL, NULL); + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[isobmf] could not get cenc info for sample %d: %s\n", sample_number, gf_error_to_string(e) )); + gf_isom_cenc_samp_aux_info_del(sai); + return e; + } } + //no init movie setup (segment dump/inspaction, assume default encrypted and 16 bytes IV + else { + if (do_warn) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[isobmf] no moov found, cannot get cenc default info, assuming isEncrypted, IV size %d (computed from senc size)\n", def_IV_size)); + do_warn = GF_FALSE; + } + is_encrypted = GF_TRUE; + sai->IV_size = def_IV_size; + } + if (senc_size < sai->IV_size) { + parse_failed = GF_TRUE; + gf_isom_cenc_samp_aux_info_del(sai); + break; + } + + sample_number++; + //subsample info is only signaled for encrypted samples if (is_encrypted) { - gf_bs_read_data(bs, (char *)sai->IV, sai->IV_size); + if (sai->IV_size > GF_ARRAY_LENGTH(sai->IV)) { + gf_isom_cenc_samp_aux_info_del(sai); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[isobmf] Failed to parse SENC box, invalid SAI size\n" )); + return GF_ISOM_INVALID_FILE; + } + if (sai->IV_size) { + gf_bs_read_data(bs, (char *)sai->IV, sai->IV_size); + senc_size -= sai->IV_size; + } + if (senc->flags & 0x00000002) { sai->subsample_count = gf_bs_read_u16(bs); sai->subsamples = (GF_CENCSubSampleEntry *)gf_malloc(sai->subsample_count*sizeof(GF_CENCSubSampleEntry)); + if (!sai->subsamples) return GF_OUT_OF_MEM; + + if ((s32) senc_size < 2 + sai->subsample_count * 6) { + parse_failed = GF_TRUE; + gf_isom_cenc_samp_aux_info_del(sai); + break; + } + for (j = 0; j < sai->subsample_count; j++) { + if (gf_bs_get_size(bs) - gf_bs_get_position(bs) < 6) { + gf_isom_cenc_samp_aux_info_del(sai); + if (trak && trak->moov && trak->moov->mov && trak->moov->mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG) { + gf_bs_seek(bs, pos); + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[isobmf] Failed to parse SENC box, invalid SAI size\n" )); + return GF_OK; + } + return GF_ISOM_INVALID_FILE; + } sai->subsamples[j].bytes_clear_data = gf_bs_read_u16(bs); sai->subsamples[j].bytes_encrypted_data = gf_bs_read_u32(bs); } + senc_size -= 2 + sai->subsample_count * 6; } + } else { + i--; } gf_list_add(senc->samp_aux_info, sai); } gf_bs_seek(bs, pos); + if (parse_failed) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[isobmf] cannot parse senc, missing IV/crypto state\n")); + } return GF_OK; } -GF_Err senc_Read(GF_Box *s, GF_BitStream *bs) +GF_Err senc_box_read(GF_Box *s, GF_BitStream *bs) { GF_SampleEncryptionBox *ptr = (GF_SampleEncryptionBox *)s; + ISOM_DECREASE_SIZE(ptr, 4); //WARNING - PSEC (UUID) IS TYPECASTED TO SENC (FULL BOX) SO WE CANNOT USE USUAL FULL BOX FUNCTIONS ptr->version = gf_bs_read_u8(bs); ptr->flags = gf_bs_read_u24(bs); - ISOM_DECREASE_SIZE(ptr, 4); ptr->bs_offset = gf_bs_get_position(bs); gf_bs_skip_bytes(bs, ptr->size); ptr->size = 0; + ptr->load_needed = GF_TRUE; return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err senc_Write(GF_Box *s, GF_BitStream *bs) +GF_Err senc_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; + u32 i, j; u32 sample_count; GF_SampleEncryptionBox *ptr = (GF_SampleEncryptionBox *) s; + + sample_count = gf_list_count(ptr->samp_aux_info); + if (!sample_count) { + ptr->size = 0; + return GF_OK; + } + e = gf_isom_box_write_header(s, bs); if (e) return e; //WARNING - PSEC (UUID) IS TYPECASTED TO SENC (FULL BOX) SO WE CANNOT USE USUAL FULL BOX FUNCTIONS gf_bs_write_u8(bs, ptr->version); gf_bs_write_u24(bs, ptr->flags); - sample_count = gf_list_count(ptr->samp_aux_info); gf_bs_write_u32(bs, sample_count); - if (sample_count) { - u32 i, j; - e = store_senc_info(ptr, bs); - if (e) return e; + e = store_senc_info(ptr, bs); + if (e) return e; - for (i = 0; i < sample_count; i++) { - GF_CENCSampleAuxInfo *sai = (GF_CENCSampleAuxInfo *)gf_list_get(ptr->samp_aux_info, i); - //for cbcs scheme, IV_size is 0, constant IV shall be used. It is written in tenc box rather than in sai - if (sai->IV_size) - gf_bs_write_data(bs, (char *)sai->IV, sai->IV_size); - if (ptr->flags & 0x00000002) { - gf_bs_write_u16(bs, sai->subsample_count); - for (j = 0; j < sai->subsample_count; j++) { - gf_bs_write_u16(bs, sai->subsamples[j].bytes_clear_data); - gf_bs_write_u32(bs, sai->subsamples[j].bytes_encrypted_data); - } + for (i = 0; i < sample_count; i++) { + GF_CENCSampleAuxInfo *sai = (GF_CENCSampleAuxInfo *)gf_list_get(ptr->samp_aux_info, i); + + //for cbcs scheme, IV_size is 0, constant IV shall be used. It is written in tenc box rather than in sai + if (sai->IV_size) + gf_bs_write_data(bs, (char *)sai->IV, sai->IV_size); + + if (ptr->flags & 0x00000002) { + gf_bs_write_u16(bs, sai->subsample_count); + for (j = 0; j < sai->subsample_count; j++) { + gf_bs_write_u16(bs, sai->subsamples[j].bytes_clear_data); + gf_bs_write_u32(bs, sai->subsamples[j].bytes_encrypted_data); } } } return GF_OK; } -GF_Err senc_Size(GF_Box *s) +GF_Err senc_box_size(GF_Box *s) { u32 sample_count; u32 i; GF_SampleEncryptionBox *ptr = (GF_SampleEncryptionBox*)s; + sample_count = gf_list_count(ptr->samp_aux_info); + + if (!sample_count) { + ptr->size = 0; + return GF_OK; + } + //WARNING - PSEC (UUID) IS TYPECASTED TO SENC (FULL BOX) SO WE CANNOT USE USUAL FULL BOX FUNCTIONS ptr->size += 4; //version and flags ptr->size += 4; //sample count - sample_count = gf_list_count(ptr->samp_aux_info); - if (sample_count) { - for (i = 0; i < sample_count; i++) { - GF_CENCSampleAuxInfo *sai = (GF_CENCSampleAuxInfo *)gf_list_get(ptr->samp_aux_info, i); - //if (! sai->IV_size) continue; - ptr->size += sai->IV_size; - if (ptr->flags & 0x00000002) - ptr->size += 2 + 6*sai->subsample_count; - } + for (i = 0; i < sample_count; i++) { + GF_CENCSampleAuxInfo *sai = (GF_CENCSampleAuxInfo *)gf_list_get(ptr->samp_aux_info, i); + + ptr->size += sai->IV_size; + + if (ptr->flags & 0x00000002) + ptr->size += 2 + 6*sai->subsample_count; } return GF_OK; } #endif //GPAC_DISABLE_ISOM_WRITE -GF_Box *adkm_New() +GF_Box *adkm_box_new() { ISOM_DECL_BOX_ALLOC(GF_AdobeDRMKeyManagementSystemBox, GF_ISOM_BOX_TYPE_ADKM); tmp->version = 1; @@ -1443,73 +1464,51 @@ GF_Box *adkm_New() return (GF_Box *)tmp; } -void adkm_del(GF_Box *s) +void adkm_box_del(GF_Box *s) { GF_AdobeDRMKeyManagementSystemBox *ptr = (GF_AdobeDRMKeyManagementSystemBox *)s; if (!ptr) return; - if (ptr->header) gf_isom_box_del((GF_Box *)ptr->header); - if (ptr->au_format) gf_isom_box_del((GF_Box *)ptr->au_format); gf_free(s); } -GF_Err adkm_AddBox(GF_Box *s, GF_Box *a) +GF_Err adkm_on_child_box(GF_Box *s, GF_Box *a) { GF_AdobeDRMKeyManagementSystemBox *ptr = (GF_AdobeDRMKeyManagementSystemBox *)s; switch (a->type) { case GF_ISOM_BOX_TYPE_AHDR: - if (ptr->header) return GF_ISOM_INVALID_FILE; + if (ptr->header) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->header = (GF_AdobeDRMHeaderBox *)a; break; case GF_ISOM_BOX_TYPE_ADAF: - if (ptr->au_format) return GF_ISOM_INVALID_FILE; + if (ptr->au_format) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->au_format = (GF_AdobeDRMAUFormatBox *)a; break; - - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err adkm_Read(GF_Box *s, GF_BitStream *bs) +GF_Err adkm_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, adkm_AddBox); + return gf_isom_box_array_read(s, bs, adkm_on_child_box); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err adkm_Write(GF_Box *s, GF_BitStream *bs) +GF_Err adkm_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_AdobeDRMKeyManagementSystemBox *ptr = (GF_AdobeDRMKeyManagementSystemBox *)s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_full_box_write(s, bs); - if (e) return e; - //ahdr - e = gf_isom_box_write((GF_Box *) ptr->header, bs); - if (e) return e; - //adaf - e = gf_isom_box_write((GF_Box *) ptr->au_format, bs); - if (e) return e; - - return GF_OK; + return gf_isom_full_box_write(s, bs); } -GF_Err adkm_Size(GF_Box *s) +GF_Err adkm_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_AdobeDRMKeyManagementSystemBox *ptr = (GF_AdobeDRMKeyManagementSystemBox *)s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_box_size((GF_Box *) ptr->header); - if (e) return e; - ptr->size += ptr->header->size; - e = gf_isom_box_size((GF_Box *) ptr->au_format); - if (e) return e; - ptr->size += ptr->au_format->size; - return GF_OK; + gf_isom_check_position(s, (GF_Box *)ptr->header, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->au_format, &pos); + return GF_OK; } #endif //GPAC_DISABLE_ISOM_WRITE -GF_Box *ahdr_New() +GF_Box *ahdr_box_new() { ISOM_DECL_BOX_ALLOC(GF_AdobeDRMHeaderBox, GF_ISOM_BOX_TYPE_AHDR); tmp->version = 2; @@ -1517,62 +1516,45 @@ GF_Box *ahdr_New() return (GF_Box *)tmp; } -void ahdr_del(GF_Box *s) +void ahdr_box_del(GF_Box *s) { - GF_AdobeDRMHeaderBox *ptr = (GF_AdobeDRMHeaderBox *)s; - if (!ptr) return; - if (ptr->std_enc_params) gf_isom_box_del((GF_Box *)ptr->std_enc_params); gf_free(s); } -GF_Err ahdr_AddBox(GF_Box *s, GF_Box *a) +GF_Err ahdr_on_child_box(GF_Box *s, GF_Box *a) { GF_AdobeDRMHeaderBox *ptr = (GF_AdobeDRMHeaderBox *)s; switch (a->type) { case GF_ISOM_BOX_TYPE_APRM: - if (ptr->std_enc_params) return GF_ISOM_INVALID_FILE; + if (ptr->std_enc_params) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->std_enc_params = (GF_AdobeStdEncryptionParamsBox *)a; break; - - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err ahdr_Read(GF_Box *s, GF_BitStream *bs) +GF_Err ahdr_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, ahdr_AddBox); + return gf_isom_box_array_read(s, bs, ahdr_on_child_box); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err ahdr_Write(GF_Box *s, GF_BitStream *bs) +GF_Err ahdr_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_AdobeDRMHeaderBox *ptr = (GF_AdobeDRMHeaderBox *)s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_full_box_write(s, bs); - if (e) return e; - e = gf_isom_box_write((GF_Box *) ptr->std_enc_params, bs); - if (e) return e; - - return GF_OK; + return gf_isom_full_box_write(s, bs); } -GF_Err ahdr_Size(GF_Box *s) +GF_Err ahdr_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_AdobeDRMHeaderBox *ptr = (GF_AdobeDRMHeaderBox *)s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_box_size((GF_Box *) ptr->std_enc_params); - if (e) return e; - ptr->size += ptr->std_enc_params->size; - return GF_OK; + gf_isom_check_position(s, (GF_Box *)ptr->std_enc_params, &pos); + return GF_OK; } #endif //GPAC_DISABLE_ISOM_WRITE -GF_Box *aprm_New() +GF_Box *aprm_box_new() { ISOM_DECL_BOX_ALLOC(GF_AdobeStdEncryptionParamsBox, GF_ISOM_BOX_TYPE_APRM); tmp->version = 1; @@ -1580,73 +1562,49 @@ GF_Box *aprm_New() return (GF_Box *)tmp; } -void aprm_del(GF_Box *s) +void aprm_box_del(GF_Box *s) { - GF_AdobeStdEncryptionParamsBox *ptr = (GF_AdobeStdEncryptionParamsBox *)s; - if (!ptr) return; - if (ptr->enc_info) gf_isom_box_del((GF_Box *)ptr->enc_info); - if (ptr->key_info) gf_isom_box_del((GF_Box *)ptr->key_info); gf_free(s); } -GF_Err aprm_AddBox(GF_Box *s, GF_Box *a) +GF_Err aprm_on_child_box(GF_Box *s, GF_Box *a) { GF_AdobeStdEncryptionParamsBox *ptr = (GF_AdobeStdEncryptionParamsBox *)s; switch (a->type) { case GF_ISOM_BOX_TYPE_AEIB: - if (ptr->enc_info) return GF_ISOM_INVALID_FILE; + if (ptr->enc_info) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->enc_info = (GF_AdobeEncryptionInfoBox *)a; break; case GF_ISOM_BOX_TYPE_AKEY: - if (ptr->key_info) return GF_ISOM_INVALID_FILE; + if (ptr->key_info) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->key_info = (GF_AdobeKeyInfoBox *)a; break; - - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err aprm_Read(GF_Box *s, GF_BitStream *bs) +GF_Err aprm_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, aprm_AddBox); + return gf_isom_box_array_read(s, bs, aprm_on_child_box); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err aprm_Write(GF_Box *s, GF_BitStream *bs) +GF_Err aprm_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_AdobeStdEncryptionParamsBox *ptr = (GF_AdobeStdEncryptionParamsBox *)s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_full_box_write(s, bs); - if (e) return e; - //aeib - e = gf_isom_box_write((GF_Box *) ptr->enc_info, bs); - if (e) return e; - //akey - e = gf_isom_box_write((GF_Box *) ptr->key_info, bs); - if (e) return e; - - return GF_OK; + return gf_isom_full_box_write(s, bs); } -GF_Err aprm_Size(GF_Box *s) +GF_Err aprm_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_AdobeStdEncryptionParamsBox *ptr = (GF_AdobeStdEncryptionParamsBox *)s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_box_size((GF_Box *) ptr->enc_info); - if (e) return e; - ptr->size += ptr->enc_info->size; - e = gf_isom_box_size((GF_Box *) ptr->key_info); - if (e) return e; - ptr->size += ptr->key_info->size; - return GF_OK; + gf_isom_check_position(s, (GF_Box *)ptr->enc_info, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->key_info, &pos); + return GF_OK; } #endif //GPAC_DISABLE_ISOM_WRITE -GF_Box *aeib_New() +GF_Box *aeib_box_new() { ISOM_DECL_BOX_ALLOC(GF_AdobeEncryptionInfoBox, GF_ISOM_BOX_TYPE_AEIB); tmp->version = 1; @@ -1654,7 +1612,7 @@ GF_Box *aeib_New() return (GF_Box *)tmp; } -void aeib_del(GF_Box *s) +void aeib_box_del(GF_Box *s) { GF_AdobeEncryptionInfoBox *ptr = (GF_AdobeEncryptionInfoBox*)s; if (!ptr) return; @@ -1662,15 +1620,15 @@ void aeib_del(GF_Box *s) gf_free(ptr); } -GF_Err aeib_Read(GF_Box *s, GF_BitStream *bs) +GF_Err aeib_box_read(GF_Box *s, GF_BitStream *bs) { GF_AdobeEncryptionInfoBox *ptr = (GF_AdobeEncryptionInfoBox*)s; u32 len; len = (u32) ptr->size - 1; if (len) { - if (ptr->enc_algo) return GF_ISOM_INVALID_FILE; ptr->enc_algo = (char *)gf_malloc(len*sizeof(char)); + if (!ptr->enc_algo) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->enc_algo, len); } ptr->key_length = gf_bs_read_u8(bs); @@ -1680,7 +1638,7 @@ GF_Err aeib_Read(GF_Box *s, GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err aeib_Write(GF_Box *s, GF_BitStream *bs) +GF_Err aeib_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_AdobeEncryptionInfoBox *ptr = (GF_AdobeEncryptionInfoBox *) s; @@ -1695,7 +1653,7 @@ GF_Err aeib_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err aeib_Size(GF_Box *s) +GF_Err aeib_box_size(GF_Box *s) { GF_AdobeEncryptionInfoBox *ptr = (GF_AdobeEncryptionInfoBox*)s; if (ptr->enc_algo) @@ -1705,7 +1663,7 @@ GF_Err aeib_Size(GF_Box *s) } #endif //GPAC_DISABLE_ISOM_WRITE -GF_Box *akey_New() +GF_Box *akey_box_new() { ISOM_DECL_BOX_ALLOC(GF_AdobeKeyInfoBox, GF_ISOM_BOX_TYPE_AKEY); tmp->version = 1; @@ -1713,67 +1671,50 @@ GF_Box *akey_New() return (GF_Box *)tmp; } -void akey_del(GF_Box *s) +void akey_box_del(GF_Box *s) { - GF_AdobeKeyInfoBox *ptr = (GF_AdobeKeyInfoBox *)s; - if (!ptr) return; - if (ptr->params) gf_isom_box_del((GF_Box *)ptr->params); gf_free(s); } -GF_Err akey_AddBox(GF_Box *s, GF_Box *a) +GF_Err akey_on_child_box(GF_Box *s, GF_Box *a) { GF_AdobeKeyInfoBox *ptr = (GF_AdobeKeyInfoBox *)s; switch (a->type) { case GF_ISOM_BOX_TYPE_FLXS: - if (ptr->params) return GF_ISOM_INVALID_FILE; + if (ptr->params) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->params = (GF_AdobeFlashAccessParamsBox *)a; break; - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err akey_Read(GF_Box *s, GF_BitStream *bs) +GF_Err akey_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, akey_AddBox); + return gf_isom_box_array_read(s, bs, akey_on_child_box); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err akey_Write(GF_Box *s, GF_BitStream *bs) +GF_Err akey_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_AdobeKeyInfoBox *ptr = (GF_AdobeKeyInfoBox *)s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_full_box_write(s, bs); - if (e) return e; - e = gf_isom_box_write((GF_Box *) ptr->params, bs); - if (e) return e; - - return GF_OK; + return gf_isom_full_box_write(s, bs); } -GF_Err akey_Size(GF_Box *s) +GF_Err akey_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_AdobeKeyInfoBox *ptr = (GF_AdobeKeyInfoBox *)s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_box_size((GF_Box *) ptr->params); - if (e) return e; - ptr->size += ptr->params->size; - e = gf_isom_box_size((GF_Box *) ptr->params); - return e; + gf_isom_check_position(s, (GF_Box *)ptr->params, &pos); + return GF_OK; } #endif //GPAC_DISABLE_ISOM_WRITE -GF_Box *flxs_New() +GF_Box *flxs_box_new() { ISOM_DECL_BOX_ALLOC(GF_AdobeFlashAccessParamsBox, GF_ISOM_BOX_TYPE_FLXS); return (GF_Box *)tmp; } -void flxs_del(GF_Box *s) +void flxs_box_del(GF_Box *s) { GF_AdobeFlashAccessParamsBox *ptr = (GF_AdobeFlashAccessParamsBox*)s; if (!ptr) return; @@ -1782,15 +1723,15 @@ void flxs_del(GF_Box *s) gf_free(ptr); } -GF_Err flxs_Read(GF_Box *s, GF_BitStream *bs) +GF_Err flxs_box_read(GF_Box *s, GF_BitStream *bs) { GF_AdobeFlashAccessParamsBox *ptr = (GF_AdobeFlashAccessParamsBox*)s; u32 len; len = (u32) ptr->size; if (len) { - if (ptr->metadata) return GF_ISOM_INVALID_FILE; ptr->metadata = (char *)gf_malloc(len*sizeof(char)); + if (!ptr->metadata) return GF_OUT_OF_MEM; gf_bs_read_data(bs, ptr->metadata, len); } return GF_OK; @@ -1798,7 +1739,7 @@ GF_Err flxs_Read(GF_Box *s, GF_BitStream *bs) #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err flxs_Write(GF_Box *s, GF_BitStream *bs) +GF_Err flxs_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_AdobeFlashAccessParamsBox *ptr = (GF_AdobeFlashAccessParamsBox *) s; @@ -1812,7 +1753,7 @@ GF_Err flxs_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err flxs_Size(GF_Box *s) +GF_Err flxs_box_size(GF_Box *s) { GF_AdobeFlashAccessParamsBox *ptr = (GF_AdobeFlashAccessParamsBox*)s; if (ptr->metadata) @@ -1821,31 +1762,31 @@ GF_Err flxs_Size(GF_Box *s) } #endif //GPAC_DISABLE_ISOM_WRITE -GF_Box *adaf_New() +GF_Box *adaf_box_new() { ISOM_DECL_BOX_ALLOC(GF_AdobeDRMAUFormatBox, GF_ISOM_BOX_TYPE_ADAF); return (GF_Box *)tmp; } -void adaf_del(GF_Box *s) +void adaf_box_del(GF_Box *s) { gf_free(s); } -GF_Err adaf_Read(GF_Box *s, GF_BitStream *bs) +GF_Err adaf_box_read(GF_Box *s, GF_BitStream *bs) { GF_AdobeDRMAUFormatBox *ptr = (GF_AdobeDRMAUFormatBox*)s; + ISOM_DECREASE_SIZE(ptr, 3); ptr->selective_enc = gf_bs_read_u8(bs); gf_bs_read_u8(bs);//resersed ptr->IV_length = gf_bs_read_u8(bs); - ISOM_DECREASE_SIZE(ptr, 3); return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err adaf_Write(GF_Box *s, GF_BitStream *bs) +GF_Err adaf_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_AdobeDRMAUFormatBox *ptr = (GF_AdobeDRMAUFormatBox *) s; @@ -1859,7 +1800,7 @@ GF_Err adaf_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err adaf_Size(GF_Box *s) +GF_Err adaf_box_size(GF_Box *s) { GF_AdobeDRMAUFormatBox *ptr = (GF_AdobeDRMAUFormatBox*)s; ptr->size += 3; diff --git a/src/isomedia/box_code_meta.c b/src/isomedia/box_code_meta.c index e3be545..1d54fa6 100644 --- a/src/isomedia/box_code_meta.c +++ b/src/isomedia/box_code_meta.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Cyril Concolato / Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2005-2012 + * Copyright (c) Telecom ParisTech 2005-2019 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -24,13 +24,14 @@ */ #include +#include #ifndef GPAC_DISABLE_ISOM -GF_Box *meta_New() +GF_Box *meta_box_new() { ISOM_DECL_BOX_ALLOC(GF_MetaBox, GF_ISOM_BOX_TYPE_META); - tmp->other_boxes = gf_list_new(); + tmp->child_boxes = gf_list_new(); return (GF_Box *)tmp; } @@ -39,204 +40,114 @@ void meta_reset(GF_Box *s) GF_MetaBox *ptr = (GF_MetaBox *)s; if (ptr == NULL) return; - gf_isom_box_del((GF_Box *)ptr->handler); + //destroy all boxes + if (ptr->child_boxes) gf_isom_box_array_del(ptr->child_boxes); + ptr->child_boxes = NULL; + + //reset pointers (destroyed above) ptr->handler = NULL; - if (ptr->primary_resource) gf_isom_box_del((GF_Box *)ptr->primary_resource); ptr->primary_resource = NULL; - if (ptr->file_locations) gf_isom_box_del((GF_Box *)ptr->file_locations); ptr->file_locations = NULL; - if (ptr->item_locations) gf_isom_box_del((GF_Box *)ptr->item_locations); ptr->item_locations = NULL; - if (ptr->protections) gf_isom_box_del((GF_Box *)ptr->protections); ptr->protections = NULL; - if (ptr->item_infos) gf_isom_box_del((GF_Box *)ptr->item_infos); ptr->item_infos = NULL; - if (ptr->IPMP_control) gf_isom_box_del((GF_Box *)ptr->IPMP_control); ptr->IPMP_control = NULL; - if (ptr->item_refs) gf_isom_box_del((GF_Box *)ptr->item_refs); ptr->item_refs = NULL; - if (ptr->item_props) gf_isom_box_del((GF_Box *)ptr->item_props); ptr->item_props = NULL; - if (ptr->other_boxes) gf_isom_box_array_del(ptr->other_boxes); - ptr->other_boxes = NULL; } -void meta_del(GF_Box *s) +void meta_box_del(GF_Box *s) { meta_reset(s); gf_free(s); } -GF_Err meta_AddBox(GF_Box *s, GF_Box *a) +GF_Err meta_on_child_box(GF_Box *s, GF_Box *a) { GF_MetaBox *ptr = (GF_MetaBox *)s; switch (a->type) { case GF_ISOM_BOX_TYPE_HDLR: - if (ptr->handler) return GF_ISOM_INVALID_FILE; + if (ptr->handler) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->handler = (GF_HandlerBox*)a; break; case GF_ISOM_BOX_TYPE_PITM: - if (ptr->primary_resource) return GF_ISOM_INVALID_FILE; + if (ptr->primary_resource) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->primary_resource = (GF_PrimaryItemBox*)a; break; case GF_ISOM_BOX_TYPE_DINF: - if (ptr->file_locations) return GF_ISOM_INVALID_FILE; + if (ptr->file_locations) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->file_locations = (GF_DataInformationBox*)a; break; case GF_ISOM_BOX_TYPE_ILOC: - if (ptr->item_locations) return GF_ISOM_INVALID_FILE; + if (ptr->item_locations) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->item_locations = (GF_ItemLocationBox*)a; break; case GF_ISOM_BOX_TYPE_IPRO: - if (ptr->protections) return GF_ISOM_INVALID_FILE; + if (ptr->protections) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->protections = (GF_ItemProtectionBox*)a; break; case GF_ISOM_BOX_TYPE_IINF: - if (ptr->item_infos) return GF_ISOM_INVALID_FILE; + if (ptr->item_infos) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->item_infos = (GF_ItemInfoBox*)a; break; case GF_ISOM_BOX_TYPE_IREF: - if (ptr->item_refs) return GF_ISOM_INVALID_FILE; + if (ptr->item_refs) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->item_refs = (GF_ItemReferenceBox*)a; break; case GF_ISOM_BOX_TYPE_IPRP: - if (ptr->item_props) return GF_ISOM_INVALID_FILE; + if (ptr->item_props) ERROR_ON_DUPLICATED_BOX(a, ptr) ptr->item_props = (GF_ItemPropertiesBox*)a; break; - //case ???: ptr->IPMP_control = (???*)a; break; - case GF_ISOM_BOX_TYPE_XML: - case GF_ISOM_BOX_TYPE_BXML: - case GF_ISOM_BOX_TYPE_ILST: - default: - return gf_isom_box_add_default(s, a); } return GF_OK; } -GF_Err meta_Read(GF_Box *s, GF_BitStream *bs) +GF_Err meta_box_read(GF_Box *s, GF_BitStream *bs) { u64 pos = gf_bs_get_position(bs); u64 size = s->size; - GF_Err e = gf_isom_box_array_read(s, bs, meta_AddBox); + GF_Err e = gf_isom_box_array_read(s, bs, meta_on_child_box); /*try to hack around QT files which don't use a full box for meta, rewind 4 bytes*/ if (e && (pos>4) ) { gf_bs_seek(bs, pos-4); meta_reset(s); s->size = size+4; - e = gf_isom_box_array_read(s, bs, meta_AddBox); + e = gf_isom_box_array_read(s, bs, meta_on_child_box); } return e; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err meta_Write(GF_Box *s, GF_BitStream *bs) +GF_Err meta_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - GF_MetaBox *ptr = (GF_MetaBox *)s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_full_box_write(s, bs); - if (e) return e; - if (ptr->handler) { - e = gf_isom_box_write((GF_Box *) ptr->handler, bs); - if (e) return e; - } - if (ptr->primary_resource) { - e = gf_isom_box_write((GF_Box *) ptr->primary_resource, bs); - if (e) return e; - } - if (ptr->file_locations) { - e = gf_isom_box_write((GF_Box *) ptr->file_locations, bs); - if (e) return e; - } - if (ptr->item_locations) { - e = gf_isom_box_write((GF_Box *) ptr->item_locations, bs); - if (e) return e; - } - if (ptr->protections) { - e = gf_isom_box_write((GF_Box *) ptr->protections, bs); - if (e) return e; - } - if (ptr->item_infos) { - e = gf_isom_box_write((GF_Box *) ptr->item_infos, bs); - if (e) return e; - } - if (ptr->IPMP_control) { - e = gf_isom_box_write((GF_Box *) ptr->IPMP_control, bs); - if (e) return e; - } - if (ptr->item_refs) { - e = gf_isom_box_write((GF_Box *)ptr->item_refs, bs); - if (e) return e; - } - if (ptr->item_props) { - e = gf_isom_box_write((GF_Box *) ptr->item_props, bs); - if (e) return e; - } - return GF_OK; + return gf_isom_full_box_write(s, bs); } -GF_Err meta_Size(GF_Box *s) +GF_Err meta_box_size(GF_Box *s) { - GF_Err e; + u32 pos=0; GF_MetaBox *ptr = (GF_MetaBox *)s; - if (!s) return GF_BAD_PARAM; - if (ptr->handler) { - e = gf_isom_box_size((GF_Box *) ptr->handler); - if (e) return e; - ptr->size += ptr->handler->size; - } - if (ptr->primary_resource) { - e = gf_isom_box_size((GF_Box *) ptr->primary_resource); - if (e) return e; - ptr->size += ptr->primary_resource->size; - } - if (ptr->file_locations) { - e = gf_isom_box_size((GF_Box *) ptr->file_locations); - if (e) return e; - ptr->size += ptr->file_locations->size; - } - if (ptr->item_locations) { - e = gf_isom_box_size((GF_Box *) ptr->item_locations); - if (e) return e; - ptr->size += ptr->item_locations->size; - } - if (ptr->protections) { - e = gf_isom_box_size((GF_Box *) ptr->protections); - if (e) return e; - ptr->size += ptr->protections->size; - } - if (ptr->item_infos) { - e = gf_isom_box_size((GF_Box *) ptr->item_infos); - if (e) return e; - ptr->size += ptr->item_infos->size; - } - if (ptr->IPMP_control) { - e = gf_isom_box_size((GF_Box *) ptr->IPMP_control); - if (e) return e; - ptr->size += ptr->IPMP_control->size; - } - if (ptr->item_refs) { - e = gf_isom_box_size((GF_Box *)ptr->item_refs); - if (e) return e; - ptr->size += ptr->item_refs->size; - } - if (ptr->item_props) { - e = gf_isom_box_size((GF_Box *) ptr->item_props); - if (e) return e; - ptr->size += ptr->item_props->size; - } + + gf_isom_check_position(s, (GF_Box *)ptr->handler, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->primary_resource, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->file_locations, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->item_locations, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->protections, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->item_infos, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->item_refs, &pos); + gf_isom_check_position(s, (GF_Box *)ptr->item_props, &pos); return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *xml_New() +GF_Box *xml_box_new() { ISOM_DECL_BOX_ALLOC(GF_XMLBox, GF_ISOM_BOX_TYPE_XML); return (GF_Box *)tmp; } -void xml_del(GF_Box *s) +void xml_box_del(GF_Box *s) { GF_XMLBox *ptr = (GF_XMLBox *)s; if (ptr == NULL) return; @@ -244,14 +155,14 @@ void xml_del(GF_Box *s) gf_free(ptr); } -GF_Err xml_Read(GF_Box *s, GF_BitStream *bs) +GF_Err xml_box_read(GF_Box *s, GF_BitStream *bs) { GF_XMLBox *ptr = (GF_XMLBox *)s; return gf_isom_read_null_terminated_string(s, bs, s->size, &ptr->xml); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err xml_Write(GF_Box *s, GF_BitStream *bs) +GF_Err xml_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_XMLBox *ptr = (GF_XMLBox *)s; @@ -263,22 +174,22 @@ GF_Err xml_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err xml_Size(GF_Box *s) +GF_Err xml_box_size(GF_Box *s) { GF_XMLBox *ptr = (GF_XMLBox *)s; if (!s) return GF_BAD_PARAM; - ptr->size += strlen(ptr->xml)+1; + ptr->size += (ptr->xml ? strlen(ptr->xml) : 0) + 1; return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *bxml_New() +GF_Box *bxml_box_new() { ISOM_DECL_BOX_ALLOC(GF_BinaryXMLBox, GF_ISOM_BOX_TYPE_BXML); return (GF_Box *)tmp; } -void bxml_del(GF_Box *s) +void bxml_box_del(GF_Box *s) { GF_BinaryXMLBox *ptr = (GF_BinaryXMLBox *)s; if (ptr == NULL) return; @@ -286,7 +197,7 @@ void bxml_del(GF_Box *s) gf_free(ptr); } -GF_Err bxml_Read(GF_Box *s, GF_BitStream *bs) +GF_Err bxml_box_read(GF_Box *s, GF_BitStream *bs) { GF_BinaryXMLBox *ptr = (GF_BinaryXMLBox *)s; @@ -298,7 +209,7 @@ GF_Err bxml_Read(GF_Box *s, GF_BitStream *bs) } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err bxml_Write(GF_Box *s, GF_BitStream *bs) +GF_Err bxml_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_BinaryXMLBox *ptr = (GF_BinaryXMLBox *)s; @@ -309,7 +220,7 @@ GF_Err bxml_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err bxml_Size(GF_Box *s) +GF_Err bxml_box_size(GF_Box *s) { GF_BinaryXMLBox *ptr = (GF_BinaryXMLBox *)s; if (!s) return GF_BAD_PARAM; @@ -318,7 +229,7 @@ GF_Err bxml_Size(GF_Box *s) } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *iloc_New() +GF_Box *iloc_box_new() { ISOM_DECL_BOX_ALLOC(GF_ItemLocationBox, GF_ISOM_BOX_TYPE_ILOC); tmp->location_entries = gf_list_new(); @@ -337,7 +248,7 @@ void iloc_entry_del(GF_ItemLocationEntry *location) gf_free(location); } -void iloc_del(GF_Box *s) +void iloc_box_del(GF_Box *s) { u32 i, item_count; GF_ItemLocationBox *ptr = (GF_ItemLocationBox *)s; @@ -351,58 +262,71 @@ void iloc_del(GF_Box *s) gf_free(ptr); } -GF_Err iloc_Read(GF_Box *s, GF_BitStream *bs) +GF_Err iloc_box_read(GF_Box *s, GF_BitStream *bs) { u32 item_count, extent_count, i, j; GF_ItemLocationBox *ptr = (GF_ItemLocationBox *)s; + ISOM_DECREASE_SIZE(ptr, 2) ptr->offset_size = gf_bs_read_int(bs, 4); ptr->length_size = gf_bs_read_int(bs, 4); ptr->base_offset_size = gf_bs_read_int(bs, 4); if (ptr->version == 1 || ptr->version == 2) { ptr->index_size = gf_bs_read_int(bs, 4); - } - else { + } else { gf_bs_read_int(bs, 4); } if (ptr->version < 2) { + ISOM_DECREASE_SIZE(ptr, 2) item_count = gf_bs_read_u16(bs); - } - else { + } else { + ISOM_DECREASE_SIZE(ptr, 4) item_count = gf_bs_read_u32(bs); } + for (i = 0; i < item_count; i++) { GF_ItemLocationEntry *location_entry = (GF_ItemLocationEntry *)gf_malloc(sizeof(GF_ItemLocationEntry)); + if (!location_entry) return GF_OUT_OF_MEM; + gf_list_add(ptr->location_entries, location_entry); if (ptr->version < 2) { + ISOM_DECREASE_SIZE(ptr, 2) location_entry->item_ID = gf_bs_read_u16(bs); - } - else { + } else { + ISOM_DECREASE_SIZE(ptr, 4) location_entry->item_ID = gf_bs_read_u32(bs); } if (ptr->version == 1 || ptr->version == 2) { + ISOM_DECREASE_SIZE(ptr, 2) location_entry->construction_method = gf_bs_read_u16(bs); } else { location_entry->construction_method = 0; } + ISOM_DECREASE_SIZE(ptr, (2 + ptr->base_offset_size) ) location_entry->data_reference_index = gf_bs_read_u16(bs); location_entry->base_offset = gf_bs_read_int(bs, 8*ptr->base_offset_size); #ifndef GPAC_DISABLE_ISOM_WRITE location_entry->original_base_offset = location_entry->base_offset; #endif + ISOM_DECREASE_SIZE(ptr, 2) extent_count = gf_bs_read_u16(bs); location_entry->extent_entries = gf_list_new(); for (j = 0; j < extent_count; j++) { GF_ItemExtentEntry *extent_entry = (GF_ItemExtentEntry *)gf_malloc(sizeof(GF_ItemExtentEntry)); + if (!extent_entry) return GF_OUT_OF_MEM; + gf_list_add(location_entry->extent_entries, extent_entry); if ((ptr->version == 1 || ptr->version == 2) && ptr->index_size > 0) { - extent_entry->extent_index = gf_bs_read_int(bs, 8 * ptr->index_size);; + ISOM_DECREASE_SIZE(ptr, ptr->index_size) + extent_entry->extent_index = gf_bs_read_int(bs, 8 * ptr->index_size); } else { extent_entry->extent_index = 0; } + ISOM_DECREASE_SIZE(ptr, (ptr->offset_size+ptr->length_size) ) + extent_entry->extent_offset = gf_bs_read_int(bs, 8*ptr->offset_size); extent_entry->extent_length = gf_bs_read_int(bs, 8*ptr->length_size); #ifndef GPAC_DISABLE_ISOM_WRITE @@ -414,7 +338,7 @@ GF_Err iloc_Read(GF_Box *s, GF_BitStream *bs) } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err iloc_Write(GF_Box *s, GF_BitStream *bs) +GF_Err iloc_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i, j, item_count, extent_count; @@ -460,7 +384,7 @@ GF_Err iloc_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err iloc_Size(GF_Box *s) +GF_Err iloc_box_size(GF_Box *s) { u32 i, item_count, extent_count; GF_ItemLocationBox *ptr = (GF_ItemLocationBox *)s; @@ -498,29 +422,29 @@ GF_Err iloc_Size(GF_Box *s) } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *pitm_New() +GF_Box *pitm_box_new() { ISOM_DECL_BOX_ALLOC(GF_PrimaryItemBox, GF_ISOM_BOX_TYPE_PITM); return (GF_Box *)tmp; } -void pitm_del(GF_Box *s) +void pitm_box_del(GF_Box *s) { GF_PrimaryItemBox *ptr = (GF_PrimaryItemBox *)s; if (ptr == NULL) return; gf_free(ptr); } -GF_Err pitm_Read(GF_Box *s, GF_BitStream *bs) +GF_Err pitm_box_read(GF_Box *s, GF_BitStream *bs) { GF_PrimaryItemBox *ptr = (GF_PrimaryItemBox *)s; - + ISOM_DECREASE_SIZE(ptr, 2) ptr->item_ID = gf_bs_read_u16(bs); return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err pitm_Write(GF_Box *s, GF_BitStream *bs) +GF_Err pitm_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; GF_PrimaryItemBox *ptr = (GF_PrimaryItemBox *)s; @@ -531,7 +455,7 @@ GF_Err pitm_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err pitm_Size(GF_Box *s) +GF_Err pitm_box_size(GF_Box *s) { GF_ItemLocationBox *ptr = (GF_ItemLocationBox *)s; if (!s) return GF_BAD_PARAM; @@ -540,44 +464,38 @@ GF_Err pitm_Size(GF_Box *s) } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *ipro_New() +GF_Box *ipro_box_new() { ISOM_DECL_BOX_ALLOC(GF_ItemProtectionBox, GF_ISOM_BOX_TYPE_IPRO); tmp->protection_information = gf_list_new(); return (GF_Box *)tmp; } -void ipro_del(GF_Box *s) +void ipro_box_del(GF_Box *s) { - u32 count, i; GF_ItemProtectionBox *ptr = (GF_ItemProtectionBox *)s; if (ptr == NULL) return; - count = gf_list_count(ptr->protection_information); - for (i = 0; i < count; i++) { - GF_Box *a = (GF_Box *)gf_list_get(ptr->protection_information, i); - gf_isom_box_del(a); - } gf_list_del(ptr->protection_information); gf_free(ptr); } -GF_Err ipro_AddBox(GF_Box *s, GF_Box *a) +GF_Err ipro_on_child_box(GF_Box *s, GF_Box *a) { GF_ItemProtectionBox *ptr = (GF_ItemProtectionBox *)s; if (a->type == GF_ISOM_BOX_TYPE_SINF) return gf_list_add(ptr->protection_information, a); - else - return gf_isom_box_add_default(s, a); + + return GF_OK; } -GF_Err ipro_Read(GF_Box *s, GF_BitStream *bs) +GF_Err ipro_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read(s, bs, ipro_AddBox); + return gf_isom_box_array_read(s, bs, ipro_on_child_box); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err ipro_Write(GF_Box *s, GF_BitStream *bs) +GF_Err ipro_box_write(GF_Box *s, GF_BitStream *bs) { - u32 count, i; + u32 count; GF_Err e; GF_ItemProtectionBox *ptr = (GF_ItemProtectionBox *)s; if (!s) return GF_BAD_PARAM; @@ -585,42 +503,26 @@ GF_Err ipro_Write(GF_Box *s, GF_BitStream *bs) if (e) return e; count = gf_list_count(ptr->protection_information); gf_bs_write_u16(bs, count); - if (count) { - for (i = 0; i < count; i++) { - GF_Box *a = (GF_Box *)gf_list_get(ptr->protection_information, i); - e = gf_isom_box_write(a, bs); - if (e) return e; - } - } return GF_OK; } -GF_Err ipro_Size(GF_Box *s) +GF_Err ipro_box_size(GF_Box *s) { - u32 i, count; - GF_Err e; GF_ItemProtectionBox *ptr = (GF_ItemProtectionBox *)s; - if (!s) return GF_BAD_PARAM; - ptr->size += 2; - if ((count = gf_list_count(ptr->protection_information))) { - for (i = 0; i < count; i++) { - GF_Box *a = (GF_Box *)gf_list_get(ptr->protection_information, i); - e = gf_isom_box_size(a); - if (e) return e; - ptr->size += a->size; - } - } + u32 pos=0; + gf_isom_check_position_list(s, ptr->protection_information, &pos); + s->size += 2; return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *infe_New() +GF_Box *infe_box_new() { ISOM_DECL_BOX_ALLOC(GF_ItemInfoEntryBox, GF_ISOM_BOX_TYPE_INFE); return (GF_Box *)tmp; } -void infe_del(GF_Box *s) +void infe_box_del(GF_Box *s) { GF_ItemInfoEntryBox *ptr = (GF_ItemInfoEntryBox *)s; if (ptr == NULL) return; @@ -631,21 +533,23 @@ void infe_del(GF_Box *s) gf_free(ptr); } -GF_Err infe_Read(GF_Box *s, GF_BitStream *bs) +GF_Err infe_box_read(GF_Box *s, GF_BitStream *bs) { char *buf; u32 buf_len, i, string_len, string_start; GF_ItemInfoEntryBox *ptr = (GF_ItemInfoEntryBox *)s; + ISOM_DECREASE_SIZE(ptr, 4); ptr->item_ID = gf_bs_read_u16(bs); ptr->item_protection_index = gf_bs_read_u16(bs); - ISOM_DECREASE_SIZE(ptr, 4); + if (ptr->version == 2) { - ptr->item_type = gf_bs_read_u32(bs); ISOM_DECREASE_SIZE(ptr, 4); + ptr->item_type = gf_bs_read_u32(bs); } buf_len = (u32) (ptr->size); buf = (char*)gf_malloc(buf_len); + if (!buf) return GF_OUT_OF_MEM; if (buf_len != gf_bs_read_data(bs, buf, buf_len)) { gf_free(buf); return GF_ISOM_INVALID_FILE; @@ -656,12 +560,15 @@ GF_Err infe_Read(GF_Box *s, GF_BitStream *bs) if (buf[i] == 0) { if (!ptr->item_name) { ptr->item_name = (char*)gf_malloc(sizeof(char)*string_len); + if (!ptr->item_name) return GF_OUT_OF_MEM; memcpy(ptr->item_name, buf+string_start, string_len); } else if (!ptr->content_type) { ptr->content_type = (char*)gf_malloc(sizeof(char)*string_len); + if (!ptr->content_type) return GF_OUT_OF_MEM; memcpy(ptr->content_type, buf+string_start, string_len); } else { ptr->content_encoding = (char*)gf_malloc(sizeof(char)*string_len); + if (!ptr->content_encoding) return GF_OUT_OF_MEM; memcpy(ptr->content_encoding, buf+string_start, string_len); } string_start += string_len; @@ -673,12 +580,14 @@ GF_Err infe_Read(GF_Box *s, GF_BitStream *bs) string_len++; } gf_free(buf); - if (!ptr->item_name || (!ptr->content_type && ptr->version < 2)) return GF_ISOM_INVALID_FILE; + if (!ptr->item_name || (!ptr->content_type && ptr->version < 2)) { + GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[isoff] Infe without name or content type !\n")); + } return GF_OK; } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err infe_Write(GF_Box *s, GF_BitStream *bs) +GF_Err infe_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 len; @@ -702,7 +611,7 @@ GF_Err infe_Write(GF_Box *s, GF_BitStream *bs) } else { gf_bs_write_byte(bs, 0, 1); } - if (ptr->item_type == GF_4CC('m', 'i', 'm', 'e') || ptr->item_type == GF_4CC('u', 'r', 'i', ' ')) { + if (ptr->item_type == GF_META_ITEM_TYPE_MIME || ptr->item_type == GF_META_ITEM_TYPE_URI) { if (ptr->content_type) { len = (u32)strlen(ptr->content_type) + 1; gf_bs_write_data(bs, ptr->content_type, len); @@ -711,7 +620,7 @@ GF_Err infe_Write(GF_Box *s, GF_BitStream *bs) gf_bs_write_byte(bs, 0, 1); } } - if (ptr->item_type == GF_4CC('m', 'i', 'm', 'e')) { + if (ptr->item_type == GF_META_ITEM_TYPE_MIME) { if (ptr->content_encoding) { len = (u32)strlen(ptr->content_encoding) + 1; gf_bs_write_data(bs, ptr->content_encoding, len); @@ -723,7 +632,7 @@ GF_Err infe_Write(GF_Box *s, GF_BitStream *bs) return GF_OK; } -GF_Err infe_Size(GF_Box *s) +GF_Err infe_box_size(GF_Box *s) { GF_ItemInfoEntryBox *ptr = (GF_ItemInfoEntryBox *)s; if (!s) return GF_BAD_PARAM; @@ -745,11 +654,11 @@ GF_Err infe_Size(GF_Box *s) } if (ptr->item_name) ptr->size += strlen(ptr->item_name)+1; else ptr->size += 1; - if (ptr->item_type == GF_4CC('m', 'i', 'm', 'e') || ptr->item_type == GF_4CC('u', 'r', 'i', ' ')) { + if (ptr->item_type == GF_META_ITEM_TYPE_MIME || ptr->item_type == GF_META_ITEM_TYPE_URI) { if (ptr->content_type) ptr->size += strlen(ptr->content_type) + 1; else ptr->size += 1; } - if (ptr->item_type == GF_4CC('m','i','m','e')) { + if (ptr->item_type == GF_META_ITEM_TYPE_MIME) { if (ptr->content_encoding) ptr->size += strlen(ptr->content_encoding) + 1; else ptr->size += 1; } @@ -757,39 +666,33 @@ GF_Err infe_Size(GF_Box *s) } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Box *iinf_New() +GF_Box *iinf_box_new() { ISOM_DECL_BOX_ALLOC(GF_ItemInfoBox, GF_ISOM_BOX_TYPE_IINF); tmp->item_infos = gf_list_new(); return (GF_Box *)tmp; } -void iinf_del(GF_Box *s) +void iinf_box_del(GF_Box *s) { - u32 count, i; GF_ItemInfoBox *ptr = (GF_ItemInfoBox *)s; if (ptr == NULL) return; - count = gf_list_count(ptr->item_infos); - for (i = 0; i < count; i++) { - GF_Box *a = (GF_Box *)gf_list_get(ptr->item_infos, i); - gf_isom_box_del(a); - } gf_list_del(ptr->item_infos); gf_free(ptr); } -GF_Err iinf_AddBox(GF_Box *s, GF_Box *a) +GF_Err iinf_on_child_box(GF_Box *s, GF_Box *a) { GF_ItemInfoBox *ptr = (GF_ItemInfoBox *)s; if (a->type == GF_ISOM_BOX_TYPE_INFE) { return gf_list_add(ptr->item_infos, a); } else { - return gf_isom_box_add_default(s, a); + return GF_OK; } } -GF_Err iinf_Read(GF_Box *s, GF_BitStream *bs) +GF_Err iinf_box_read(GF_Box *s, GF_BitStream *bs) { GF_ItemInfoBox *ptr = (GF_ItemInfoBox *)s; @@ -800,11 +703,11 @@ GF_Err iinf_Read(GF_Box *s, GF_BitStream *bs) ISOM_DECREASE_SIZE(s, 4) gf_bs_read_u32(bs); } - return gf_isom_box_array_read(s, bs, iinf_AddBox); + return gf_isom_box_array_read(s, bs, iinf_on_child_box); } #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err iinf_Write(GF_Box *s, GF_BitStream *bs) +GF_Err iinf_box_write(GF_Box *s, GF_BitStream *bs) { u32 count; GF_Err e; @@ -813,54 +716,46 @@ GF_Err iinf_Write(GF_Box *s, GF_BitStream *bs) e = gf_isom_full_box_write(s, bs); if (e) return e; count = gf_list_count(ptr->item_infos); - gf_bs_write_u16(bs, count); + if (ptr->version == 0) + gf_bs_write_u16(bs, count); + else + gf_bs_write_u32(bs, count); - if (count) { - gf_isom_box_array_write(s, ptr->item_infos, bs); - } return GF_OK; } -GF_Err iinf_Size(GF_Box *s) +GF_Err iinf_box_size(GF_Box *s) { - u32 count; + u32 pos=0; GF_ItemInfoBox *ptr = (GF_ItemInfoBox *)s; if (!s) return GF_BAD_PARAM; - ptr->size += 2; - if ((count = gf_list_count(ptr->item_infos))) { - gf_isom_box_array_size(s, ptr->item_infos); - } + ptr->size += (ptr->version == 0) ? 2 : 4; + gf_isom_check_position_list(s, ptr->item_infos, &pos); return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -GF_Err iref_AddBox(GF_Box *s, GF_Box *a) +GF_Err iref_on_child_box(GF_Box *s, GF_Box *a) { GF_ItemReferenceBox *ptr = (GF_ItemReferenceBox *)s; return gf_list_add(ptr->references, a); } -void iref_del(GF_Box *s) +void iref_box_del(GF_Box *s) { - u32 count, i; GF_ItemReferenceBox *ptr = (GF_ItemReferenceBox *)s; if (ptr == NULL) return; - count = gf_list_count(ptr->references); - for (i = 0; i < count; i++) { - GF_Box *a = (GF_Box *)gf_list_get(ptr->references, i); - gf_isom_box_del(a); - } gf_list_del(ptr->references); gf_free(ptr); } -GF_Err iref_Read(GF_Box *s, GF_BitStream *bs) +GF_Err iref_box_read(GF_Box *s, GF_BitStream *bs) { - return gf_isom_box_array_read_ex(s, bs, iref_AddBox, s->type); + return gf_isom_box_array_read_ex(s, bs, iref_on_child_box, s->type); } -GF_Box *iref_New() +GF_Box *iref_box_new() { ISOM_DECL_BOX_ALLOC(GF_ItemReferenceBox, GF_ISOM_BOX_TYPE_IREF); tmp->references = gf_list_new(); @@ -869,42 +764,22 @@ GF_Box *iref_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err iref_Write(GF_Box *s, GF_BitStream *bs) +GF_Err iref_box_write(GF_Box *s, GF_BitStream *bs) { - GF_Err e; - u32 count, i; - GF_ItemReferenceBox *ptr = (GF_ItemReferenceBox *)s; - if (!s) return GF_BAD_PARAM; - e = gf_isom_full_box_write(s, bs); - if (e) return e; - count = gf_list_count(ptr->references); - for (i = 0; i < count; i++) { - GF_Box *a = (GF_Box *)gf_list_get(ptr->references, i); - e = gf_isom_box_write(a, bs); - if (e) return e; - } - return e; + return gf_isom_full_box_write(s, bs); } -GF_Err iref_Size(GF_Box *s) +GF_Err iref_box_size(GF_Box *s) { - GF_Err e; - u32 count, i; + u32 pos=0; GF_ItemReferenceBox *ptr = (GF_ItemReferenceBox *)s; - if (!s) return GF_BAD_PARAM; - count = gf_list_count(ptr->references); - for (i = 0; i < count; i++) { - GF_Box *a = (GF_Box *)gf_list_get(ptr->references, i); - e = gf_isom_box_size(a); - if (e) return e; - s->size += a->size; - } - return e; + gf_isom_check_position_list(s, ptr->references, &pos); + return GF_OK; } #endif /*GPAC_DISABLE_ISOM_WRITE*/ -void ireftype_del(GF_Box *s) +void ireftype_box_del(GF_Box *s) { GF_ItemReferenceTypeBox *ptr = (GF_ItemReferenceTypeBox *)s; if (!ptr) return; @@ -912,27 +787,28 @@ void ireftype_del(GF_Box *s) gf_free(ptr); } -GF_Err ireftype_Read(GF_Box *s, GF_BitStream *bs) +GF_Err ireftype_box_read(GF_Box *s, GF_BitStream *bs) { - u32 bytesToRead; u32 i; GF_ItemReferenceTypeBox *ptr = (GF_ItemReferenceTypeBox *)s; - bytesToRead = (u32)(ptr->size); - if (!bytesToRead) return GF_OK; - + ISOM_DECREASE_SIZE(ptr, 4) ptr->from_item_id = gf_bs_read_u16(bs); ptr->reference_count = gf_bs_read_u16(bs); + if (ptr->size < ptr->reference_count*2) + return GF_ISOM_INVALID_FILE; + ptr->to_item_IDs = (u32 *)gf_malloc(ptr->reference_count * sizeof(u32)); if (!ptr->to_item_IDs) return GF_OUT_OF_MEM; - for (i = 0; i < ptr->reference_count; i++) { + for (i=0; i < ptr->reference_count; i++) { + ISOM_DECREASE_SIZE(ptr, 2) ptr->to_item_IDs[i] = gf_bs_read_u16(bs); } return GF_OK; } -GF_Box *ireftype_New() +GF_Box *ireftype_box_new() { ISOM_DECL_BOX_ALLOC(GF_ItemReferenceTypeBox, GF_ISOM_BOX_TYPE_REFI); return (GF_Box *)tmp; @@ -940,7 +816,7 @@ GF_Box *ireftype_New() #ifndef GPAC_DISABLE_ISOM_WRITE -GF_Err ireftype_Write(GF_Box *s, GF_BitStream *bs) +GF_Err ireftype_box_write(GF_Box *s, GF_BitStream *bs) { GF_Err e; u32 i; @@ -958,7 +834,7 @@ GF_Err ireftype_Write(GF_Box *s, GF_BitStream *bs) } -GF_Err ireftype_Size(GF_Box *s) +GF_Err ireftype_box_size(GF_Box *s) { GF_ItemReferenceTypeBox *ptr = (GF_ItemReferenceTypeBox *)s; ptr->size += 4 + (ptr->reference_count * sizeof(u16)); diff --git a/src/isomedia/box_dump.c b/src/isomedia/box_dump.c index cbfaa73..b6f253a 100644 --- a/src/isomedia/box_dump.c +++ b/src/isomedia/box_dump.c @@ -2,7 +2,7 @@ * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre - * Copyright (c) Telecom ParisTech 2000-2012 + * Copyright (c) Telecom ParisTech 2000-2020 * All rights reserved * * This file is part of GPAC / ISO Media File Format sub-project @@ -28,7 +28,7 @@ #include #include #include -#include +#include #ifndef GPAC_DISABLE_ISOM_DUMP @@ -36,31 +36,31 @@ static void dump_data(FILE *trace, char *data, u32 dataLength) { u32 i; - fprintf(trace, "data:application/octet-string,"); + gf_fprintf(trace, "data:application/octet-string,"); for (i=0; i': - fprintf(trace, ">"); + gf_fprintf(trace, ">"); break; case '<': - fprintf(trace, "<"); + gf_fprintf(trace, "<"); break; default: - fprintf(trace, "%c", (u8) data[i]); + gf_fprintf(trace, "%c", (u8) data[i]); break; } } } -GF_Err gf_isom_box_dump(void *ptr, FILE * trace) -{ - return gf_isom_box_dump_ex(ptr, trace, 0); -} - GF_Err gf_isom_box_array_dump(GF_List *list, FILE * trace) { u32 i; @@ -108,35 +103,42 @@ GF_Err gf_isom_box_array_dump(GF_List *list, FILE * trace) return GF_OK; } -extern Bool use_dump_mode; - +static Bool dump_skip_samples = GF_FALSE; GF_EXPORT -GF_Err gf_isom_dump(GF_ISOFile *mov, FILE * trace) +GF_Err gf_isom_dump(GF_ISOFile *mov, FILE * trace, Bool skip_init, Bool skip_samples) { u32 i; + const char *fname; GF_Box *box; if (!mov || !trace) return GF_BAD_PARAM; - use_dump_mode = mov->dump_mode_alloc; - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); - fprintf(trace, "\n", mov->fileName); + fname = strrchr(mov->fileName, '/'); + if (!fname) fname = strrchr(mov->fileName, '\\'); + if (!fname) fname = mov->fileName; + else fname+=1; + gf_fprintf(trace, "\n", fname); + dump_skip_samples = skip_samples; i=0; + if (skip_init) + i = mov->nb_box_init_seg; + while ((box = (GF_Box *)gf_list_enum(mov->TopBoxes, &i))) { if (box->type==GF_ISOM_BOX_TYPE_UNKNOWN) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } else if (box->type==GF_ISOM_BOX_TYPE_UUID) { } else if (!gf_isom_box_is_file_level(box)) { - fprintf(trace, "\n", gf_4cc_to_str(box->type)); + gf_fprintf(trace, "\n", gf_4cc_to_str(box->type)); } gf_isom_box_dump(box, trace); } - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); return GF_OK; } -GF_Err reftype_dump(GF_Box *a, FILE * trace) +GF_Err reftype_box_dump(GF_Box *a, FILE * trace) { u32 i; GF_TrackReferenceTypeBox *p = (GF_TrackReferenceTypeBox *)a; @@ -144,19 +146,19 @@ GF_Err reftype_dump(GF_Box *a, FILE * trace) p->type = p->reference_type; gf_isom_box_dump_start(a, "TrackReferenceTypeBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); for (i=0; itrackIDCount; i++) { - fprintf(trace, "\n", p->trackIDs[i]); + gf_fprintf(trace, "\n", p->trackIDs[i]); } if (!p->size) - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); gf_isom_box_dump_done("TrackReferenceTypeBox", a, trace); p->type = GF_ISOM_BOX_TYPE_REFT; return GF_OK; } -GF_Err ireftype_dump(GF_Box *a, FILE * trace) +GF_Err ireftype_box_dump(GF_Box *a, FILE * trace) { u32 i; GF_ItemReferenceTypeBox *p = (GF_ItemReferenceTypeBox *)a; @@ -164,12 +166,12 @@ GF_Err ireftype_dump(GF_Box *a, FILE * trace) p->type = p->reference_type; gf_isom_box_dump_start(a, "ItemReferenceBox", trace); - fprintf(trace, "from_item_id=\"%d\">\n", p->from_item_id); + gf_fprintf(trace, "from_item_id=\"%d\">\n", p->from_item_id); for (i = 0; i < p->reference_count; i++) { - fprintf(trace, "\n", p->to_item_IDs[i]); + gf_fprintf(trace, "\n", p->to_item_IDs[i]); } if (!p->size) - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); gf_isom_box_dump_done("ItemReferenceBox", a, trace); @@ -177,199 +179,182 @@ GF_Err ireftype_dump(GF_Box *a, FILE * trace) return GF_OK; } -GF_Err free_dump(GF_Box *a, FILE * trace) +GF_Err free_box_dump(GF_Box *a, FILE * trace) { GF_FreeSpaceBox *p = (GF_FreeSpaceBox *)a; gf_isom_box_dump_start(a, (a->type==GF_ISOM_BOX_TYPE_FREE) ? "FreeSpaceBox" : "SkipBox", trace); - fprintf(trace, "dataSize=\"%d\">\n", p->dataSize); + gf_fprintf(trace, "dataSize=\"%d\">\n", p->dataSize); gf_isom_box_dump_done( (a->type==GF_ISOM_BOX_TYPE_FREE) ? "FreeSpaceBox" : "SkipBox", a, trace); return GF_OK; } -GF_Err mdat_dump(GF_Box *a, FILE * trace) +GF_Err wide_box_dump(GF_Box *a, FILE * trace) +{ + gf_isom_box_dump_start(a, "WideBox", trace); + gf_fprintf(trace, ">\n"); + gf_isom_box_dump_done("WideBox", a, trace); + return GF_OK; +} + +GF_Err mdat_box_dump(GF_Box *a, FILE * trace) { GF_MediaDataBox *p; const char *name = (a->type==GF_ISOM_BOX_TYPE_IDAT ? "ItemDataBox" : "MediaDataBox"); p = (GF_MediaDataBox *)a; gf_isom_box_dump_start(a, name, trace); - fprintf(trace, "dataSize=\""LLD"\">\n", LLD_CAST p->dataSize); + gf_fprintf(trace, "dataSize=\""LLD"\">\n", p->dataSize); gf_isom_box_dump_done(name, a, trace); return GF_OK; } -GF_Err moov_dump(GF_Box *a, FILE * trace) +GF_Err moov_box_dump(GF_Box *a, FILE * trace) { - GF_MovieBox *p; - p = (GF_MovieBox *)a; + GF_MovieBox *p = (GF_MovieBox *) a; gf_isom_box_dump_start(a, "MovieBox", trace); - fprintf(trace, ">\n"); - if (p->iods) gf_isom_box_dump(p->iods, trace); - if (p->meta) gf_isom_box_dump(p->meta, trace); - //dump only if size - if (p->size) - gf_isom_box_dump_ex(p->mvhd, trace,GF_ISOM_BOX_TYPE_MVHD); - -#ifndef GPAC_DISABLE_ISOM_FRAGMENTS - if (p->mvex) gf_isom_box_dump(p->mvex, trace); -#endif - - gf_isom_box_array_dump(p->trackList, trace); - if (p->udta) gf_isom_box_dump(p->udta, trace); + if (p->compressed_diff) + gf_fprintf(trace, "compressedSize=\""LLU"\"", p->size - p->compressed_diff); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("MovieBox", a, trace); return GF_OK; } -GF_Err mvhd_dump(GF_Box *a, FILE * trace) +GF_Err mvhd_box_dump(GF_Box *a, FILE * trace) { GF_MovieHeaderBox *p; p = (GF_MovieHeaderBox *) a; gf_isom_box_dump_start(a, "MovieHeaderBox", trace); - fprintf(trace, "CreationTime=\""LLD"\" ", LLD_CAST p->creationTime); - fprintf(trace, "ModificationTime=\""LLD"\" ", LLD_CAST p->modificationTime); - fprintf(trace, "TimeScale=\"%d\" ", p->timeScale); - fprintf(trace, "Duration=\""LLD"\" ", LLD_CAST p->duration); - fprintf(trace, "NextTrackID=\"%d\">\n", p->nextTrackID); + gf_fprintf(trace, "CreationTime=\""LLD"\" ", p->creationTime); + gf_fprintf(trace, "ModificationTime=\""LLD"\" ", p->modificationTime); + gf_fprintf(trace, "TimeScale=\"%d\" ", p->timeScale); + gf_fprintf(trace, "Duration=\""LLD"\" ", p->duration); + gf_fprintf(trace, "NextTrackID=\"%d\">\n", p->nextTrackID); gf_isom_box_dump_done("MovieHeaderBox", a, trace); return GF_OK; } -GF_Err mdhd_dump(GF_Box *a, FILE * trace) +GF_Err mdhd_box_dump(GF_Box *a, FILE * trace) { GF_MediaHeaderBox *p; p = (GF_MediaHeaderBox *)a; gf_isom_box_dump_start(a, "MediaHeaderBox", trace); - fprintf(trace, "CreationTime=\""LLD"\" ", LLD_CAST p->creationTime); - fprintf(trace, "ModificationTime=\""LLD"\" ", LLD_CAST p->modificationTime); - fprintf(trace, "TimeScale=\"%d\" ", p->timeScale); - fprintf(trace, "Duration=\""LLD"\" ", LLD_CAST p->duration); - fprintf(trace, "LanguageCode=\"%c%c%c\">\n", p->packedLanguage[0], p->packedLanguage[1], p->packedLanguage[2]); + gf_fprintf(trace, "CreationTime=\""LLD"\" ", p->creationTime); + gf_fprintf(trace, "ModificationTime=\""LLD"\" ", p->modificationTime); + gf_fprintf(trace, "TimeScale=\"%d\" ", p->timeScale); + gf_fprintf(trace, "Duration=\""LLD"\" ", p->duration); + gf_fprintf(trace, "LanguageCode=\"%c%c%c\">\n", p->packedLanguage[0], p->packedLanguage[1], p->packedLanguage[2]); gf_isom_box_dump_done("MediaHeaderBox", a, trace); return GF_OK; } -GF_Err vmhd_dump(GF_Box *a, FILE * trace) +GF_Err vmhd_box_dump(GF_Box *a, FILE * trace) { gf_isom_box_dump_start(a, "VideoMediaHeaderBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("VideoMediaHeaderBox", a, trace); return GF_OK; } -GF_Err smhd_dump(GF_Box *a, FILE * trace) +GF_Err gmin_box_dump(GF_Box *a, FILE * trace) +{ + GF_GenericMediaHeaderInfoBox *p = (GF_GenericMediaHeaderInfoBox *)a; + gf_isom_box_dump_start(a, "GenericMediaHeaderInformationBox", trace); + gf_fprintf(trace, " graphicsMode=\"%d\" opcolorRed=\"%d\" opcolorGreen=\"%d\" opcolorBlue=\"%d\" balance=\"%d\">\n", + p->graphics_mode, p->op_color_red, p->op_color_green, p->op_color_blue, p->balance); + gf_isom_box_dump_done("GenericMediaHeaderInformationBox", a, trace); + return GF_OK; +} + +GF_Err clef_box_dump(GF_Box *a, FILE * trace) +{ + Float w, h; + const char *name = "TrackCleanApertureDimensionsBox"; + GF_ApertureBox *p = (GF_ApertureBox *)a; + if (p->type==GF_QT_BOX_TYPE_PROF) + name = "TrackProductionApertureDimensionsBox"; + else if (p->type==GF_QT_BOX_TYPE_ENOF) + name = "TrackEncodedPixelsDimensionsBox"; + + gf_isom_box_dump_start(a, name, trace); + w = (Float) (p->width&0xFFFF); + w /= 0xFFFF; + w += (p->width>>16); + + h = (Float) (p->height&0xFFFF); + h /= 0xFFFF; + h += (p->height>>16); + + gf_fprintf(trace, " width=\"%g\" height=\"%g\">\n", w, h); + gf_isom_box_dump_done(name, a, trace); + return GF_OK; +} + +GF_Err smhd_box_dump(GF_Box *a, FILE * trace) { gf_isom_box_dump_start(a, "SoundMediaHeaderBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("SoundMediaHeaderBox", a, trace); return GF_OK; } -GF_Err hmhd_dump(GF_Box *a, FILE * trace) +GF_Err hmhd_box_dump(GF_Box *a, FILE * trace) { GF_HintMediaHeaderBox *p; p = (GF_HintMediaHeaderBox *)a; gf_isom_box_dump_start(a, "HintMediaHeaderBox", trace); - fprintf(trace, "MaximumPDUSize=\"%d\" ", p->maxPDUSize); - fprintf(trace, "AveragePDUSize=\"%d\" ", p->avgPDUSize); - fprintf(trace, "MaxBitRate=\"%d\" ", p->maxBitrate); - fprintf(trace, "AverageBitRate=\"%d\">\n", p->avgBitrate); + gf_fprintf(trace, "MaximumPDUSize=\"%d\" ", p->maxPDUSize); + gf_fprintf(trace, "AveragePDUSize=\"%d\" ", p->avgPDUSize); + gf_fprintf(trace, "MaxBitRate=\"%d\" ", p->maxBitrate); + gf_fprintf(trace, "AverageBitRate=\"%d\">\n", p->avgBitrate); gf_isom_box_dump_done("HintMediaHeaderBox", a, trace); return GF_OK; } -GF_Err nmhd_dump(GF_Box *a, FILE * trace) +GF_Err nmhd_box_dump(GF_Box *a, FILE * trace) { gf_isom_box_dump_start(a, "MPEGMediaHeaderBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("MPEGMediaHeaderBox", a, trace); return GF_OK; } -GF_Err stbl_dump(GF_Box *a, FILE * trace) +GF_Err stbl_box_dump(GF_Box *a, FILE * trace) { - GF_SampleTableBox *p; - p = (GF_SampleTableBox *)a; gf_isom_box_dump_start(a, "SampleTableBox", trace); - fprintf(trace, ">\n"); - - if (p->size) - gf_isom_box_dump_ex(p->SampleDescription, trace, GF_ISOM_BOX_TYPE_STSD); - if (p->size) - gf_isom_box_dump_ex(p->TimeToSample, trace, GF_ISOM_BOX_TYPE_STTS); - - if (p->CompositionOffset) gf_isom_box_dump(p->CompositionOffset, trace); - if (p->CompositionToDecode) gf_isom_box_dump(p->CompositionToDecode, trace); - if (p->SyncSample) gf_isom_box_dump(p->SyncSample, trace); - if (p->ShadowSync) gf_isom_box_dump(p->ShadowSync, trace); - - if (p->size) - gf_isom_box_dump_ex(p->SampleToChunk, trace, GF_ISOM_BOX_TYPE_STSC); - if (p->size) - gf_isom_box_dump_ex(p->SampleSize, trace, GF_ISOM_BOX_TYPE_STSZ); - if (p->size) - gf_isom_box_dump_ex(p->ChunkOffset, trace, GF_ISOM_BOX_TYPE_STCO); - - if (p->DegradationPriority) gf_isom_box_dump(p->DegradationPriority, trace); - if (p->SampleDep) gf_isom_box_dump(p->SampleDep, trace); - if (p->PaddingBits) gf_isom_box_dump(p->PaddingBits, trace); - if (p->Fragments) gf_isom_box_dump(p->Fragments, trace); - if (p->sub_samples) gf_isom_box_array_dump(p->sub_samples, trace); - if (p->sampleGroupsDescription) gf_isom_box_array_dump(p->sampleGroupsDescription, trace); - if (p->sampleGroups) gf_isom_box_array_dump(p->sampleGroups, trace); - if (p->sai_sizes) { - u32 i; - for (i = 0; i < gf_list_count(p->sai_sizes); i++) { - GF_SampleAuxiliaryInfoSizeBox *saiz = (GF_SampleAuxiliaryInfoSizeBox *)gf_list_get(p->sai_sizes, i); - gf_isom_box_dump(saiz, trace); - } - } - - if (p->sai_offsets) { - u32 i; - for (i = 0; i < gf_list_count(p->sai_offsets); i++) { - GF_SampleAuxiliaryInfoOffsetBox *saio = (GF_SampleAuxiliaryInfoOffsetBox *)gf_list_get(p->sai_offsets, i); - gf_isom_box_dump(saio, trace); - } - } - + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("SampleTableBox", a, trace); return GF_OK; } -GF_Err dinf_dump(GF_Box *a, FILE * trace) +GF_Err dinf_box_dump(GF_Box *a, FILE * trace) { - GF_DataInformationBox *p; - p = (GF_DataInformationBox *)a; gf_isom_box_dump_start(a, "DataInformationBox", trace); - fprintf(trace, ">\n"); - if (p->size) - gf_isom_box_dump_ex(p->dref, trace, GF_ISOM_BOX_TYPE_DREF); - + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("DataInformationBox", a, trace); return GF_OK; } -GF_Err url_dump(GF_Box *a, FILE * trace) +GF_Err url_box_dump(GF_Box *a, FILE * trace) { GF_DataEntryURLBox *p; p = (GF_DataEntryURLBox *)a; gf_isom_box_dump_start(a, "URLDataEntryBox", trace); if (p->location) { - fprintf(trace, " URL=\"%s\">\n", p->location); + gf_fprintf(trace, " URL=\"%s\">\n", p->location); } else { - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); if (p->size) { if (! (p->flags & 1) ) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } else { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } } } @@ -377,38 +362,48 @@ GF_Err url_dump(GF_Box *a, FILE * trace) return GF_OK; } -GF_Err urn_dump(GF_Box *a, FILE * trace) +GF_Err urn_box_dump(GF_Box *a, FILE * trace) { GF_DataEntryURNBox *p; p = (GF_DataEntryURNBox *)a; gf_isom_box_dump_start(a, "URNDataEntryBox", trace); - if (p->nameURN) fprintf(trace, " URN=\"%s\"", p->nameURN); - if (p->location) fprintf(trace, " URL=\"%s\"", p->location); - fprintf(trace, ">\n"); + if (p->nameURN) gf_fprintf(trace, " URN=\"%s\"", p->nameURN); + if (p->location) gf_fprintf(trace, " URL=\"%s\"", p->location); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("URNDataEntryBox", a, trace); return GF_OK; } -GF_Err cprt_dump(GF_Box *a, FILE * trace) +GF_Err alis_box_dump(GF_Box *a, FILE * trace) +{ +// GF_DataEntryAliasBox *p = (GF_DataEntryAliasBox *)a; + gf_isom_box_dump_start(a, "AliasDataEntryBox", trace); + gf_fprintf(trace, ">\n"); + + gf_isom_box_dump_done("AliasDataEntryBox", a, trace); + return GF_OK; +} + +GF_Err cprt_box_dump(GF_Box *a, FILE * trace) { GF_CopyrightBox *p; p = (GF_CopyrightBox *)a; gf_isom_box_dump_start(a, "CopyrightBox", trace); - fprintf(trace, "LanguageCode=\"%s\" CopyrightNotice=\"%s\">\n", p->packedLanguageCode, p->notice); + gf_fprintf(trace, "LanguageCode=\"%s\" CopyrightNotice=\"%s\">\n", p->packedLanguageCode, p->notice); gf_isom_box_dump_done("CopyrightBox", a, trace); return GF_OK; } -GF_Err kind_dump(GF_Box *a, FILE * trace) +GF_Err kind_box_dump(GF_Box *a, FILE * trace) { GF_KindBox *p; p = (GF_KindBox *)a; gf_isom_box_dump_start(a, "KindBox", trace); - fprintf(trace, "schemeURI=\"%s\" value=\"%s\">\n", p->schemeURI, (p->value ? p->value : "")); + gf_fprintf(trace, "schemeURI=\"%s\" value=\"%s\">\n", p->schemeURI, (p->value ? p->value : "")); gf_isom_box_dump_done("KindBox", a, trace); return GF_OK; } @@ -417,6 +412,7 @@ GF_Err kind_dump(GF_Box *a, FILE * trace) static char *format_duration(u64 dur, u32 timescale, char *szDur) { u32 h, m, s, ms; + if (!timescale) return NULL; dur = (u32) (( ((Double) (s64) dur)/timescale)*1000); h = (u32) (dur / 3600000); dur -= h*3600000; @@ -425,144 +421,154 @@ static char *format_duration(u64 dur, u32 timescale, char *szDur) s = (u32) (dur/1000); dur -= s*1000; ms = (u32) (dur); - sprintf(szDur, "%02d:%02d:%02d.%03d", h, m, s, ms); + + if (h<=24) { + sprintf(szDur, "%02d:%02d:%02d.%03d", h, m, s, ms); + } else { + u32 d = (u32) (dur / 3600000 / 24); + h = (u32) (dur/3600000)-24*d; + if (d<=365) { + sprintf(szDur, "%d Days, %02d:%02d:%02d.%03d", d, h, m, s, ms); + } else { + u32 y=0; + while (d>365) { + y++; + d-=365; + if (y%4) d--; + } + sprintf(szDur, "%d Years %d Days, %02d:%02d:%02d.%03d", y, d, h, m, s, ms); + } + } return szDur; } static void dump_escape_string(FILE * trace, char *name) { - u32 i, len = (u32) strlen(name); + u32 i, len = name ? (u32) strlen(name) : 0; for (i=0; i\n"); + gf_fprintf(trace, ">\n"); if (p->size) { count = gf_list_count(p->list); for (i=0; ilist, i); - fprintf(trace, "name); - fprintf(trace, "\" startTime=\"%s\" />\n", format_duration(ce->start_time, 1000*10000, szDur)); + gf_fprintf(trace, "\" startTime=\"%s\" />\n", format_duration(ce->start_time, 1000*10000, szDur)); } } else { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } - +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + format_duration(0, 0, NULL); + dump_escape_string(NULL, NULL); + } +#endif gf_isom_box_dump_done("ChapterListBox", a, trace); return GF_OK; } -GF_Err pdin_dump(GF_Box *a, FILE * trace) +GF_Err pdin_box_dump(GF_Box *a, FILE * trace) { u32 i; GF_ProgressiveDownloadBox *p = (GF_ProgressiveDownloadBox *)a; gf_isom_box_dump_start(a, "ProgressiveDownloadBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); if (p->size) { for (i=0; icount; i++) { - fprintf(trace, "\n", p->rates[i], p->times[i]); + gf_fprintf(trace, "\n", p->rates[i], p->times[i]); } } else { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done("ProgressiveDownloadBox", a, trace); return GF_OK; } -GF_Err hdlr_dump(GF_Box *a, FILE * trace) +GF_Err hdlr_box_dump(GF_Box *a, FILE * trace) { GF_HandlerBox *p = (GF_HandlerBox *)a; gf_isom_box_dump_start(a, "HandlerBox", trace); - if (p->nameUTF8 && (u32) p->nameUTF8[0] == strlen(p->nameUTF8+1)) { - fprintf(trace, "hdlrType=\"%s\" Name=\"%s\" ", gf_4cc_to_str(p->handlerType), p->nameUTF8+1); + if (p->nameUTF8 && (u32) p->nameUTF8[0] == strlen(p->nameUTF8)-1) { + gf_fprintf(trace, "hdlrType=\"%s\" Name=\"%s\" ", gf_4cc_to_str(p->handlerType), p->nameUTF8+1); } else { - fprintf(trace, "hdlrType=\"%s\" Name=\"%s\" ", gf_4cc_to_str(p->handlerType), p->nameUTF8); + gf_fprintf(trace, "hdlrType=\"%s\" Name=\"%s\" ", gf_4cc_to_str(p->handlerType), p->nameUTF8); } - fprintf(trace, "reserved1=\"%d\" reserved2=\"", p->reserved1); + gf_fprintf(trace, "reserved1=\"%d\" reserved2=\"", p->reserved1); dump_data(trace, (char *) p->reserved2, 12); - fprintf(trace, "\""); + gf_fprintf(trace, "\""); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("HandlerBox", a, trace); return GF_OK; } -GF_Err iods_dump(GF_Box *a, FILE * trace) +GF_Err iods_box_dump(GF_Box *a, FILE * trace) { GF_ObjectDescriptorBox *p; p = (GF_ObjectDescriptorBox *)a; gf_isom_box_dump_start(a, "ObjectDescriptorBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); if (p->descriptor) { #ifndef GPAC_DISABLE_OD_DUMP gf_odf_dump_desc(p->descriptor, trace, 1, GF_TRUE); #else - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); #endif } else if (p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done("ObjectDescriptorBox", a, trace); return GF_OK; } -GF_Err trak_dump(GF_Box *a, FILE * trace) +GF_Err trak_box_dump(GF_Box *a, FILE * trace) { GF_TrackBox *p; p = (GF_TrackBox *)a; gf_isom_box_dump_start(a, "TrackBox", trace); - fprintf(trace, ">\n"); - if (p->Header) { - gf_isom_box_dump(p->Header, trace); - } else if (p->size) { - fprintf(trace, "\n"); - } - if (p->References) gf_isom_box_dump(p->References, trace); - if (p->meta) gf_isom_box_dump(p->meta, trace); - if (p->editBox) gf_isom_box_dump(p->editBox, trace); - if (p->Media) gf_isom_box_dump(p->Media, trace); - if (p->groups) gf_isom_box_dump(p->groups, trace); - if (p->udta) gf_isom_box_dump(p->udta, trace); + gf_fprintf(trace, ">\n"); + if (p->size && !p->Header) { + gf_fprintf(trace, "\n"); + } gf_isom_box_dump_done("TrackBox", a, trace); return GF_OK; } -GF_Err mp4s_dump(GF_Box *a, FILE * trace) +GF_Err mp4s_box_dump(GF_Box *a, FILE * trace) { GF_MPEGSampleEntryBox *p; p = (GF_MPEGSampleEntryBox *)a; gf_isom_box_dump_start(a, "MPEGSystemsSampleDescriptionBox", trace); - fprintf(trace, "DataReferenceIndex=\"%d\">\n", p->dataReferenceIndex); - if (p->esd) { - gf_isom_box_dump(p->esd, trace); - } else if (p->size) { - fprintf(trace, "\n"); - } - if (a->type == GF_ISOM_BOX_TYPE_ENCS) { - gf_isom_box_array_dump(p->protections, trace); + gf_fprintf(trace, "DataReferenceIndex=\"%d\">\n", p->dataReferenceIndex); + if (!p->esd && p->size) { + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done("MPEGSystemsSampleDescriptionBox", a, trace); return GF_OK; } -GF_Err video_sample_entry_dump(GF_Box *a, FILE * trace) +GF_Err video_sample_entry_box_dump(GF_Box *a, FILE * trace) { + Bool full_dump=GF_FALSE; GF_MPEGVisualSampleEntryBox *p = (GF_MPEGVisualSampleEntryBox *)a; const char *name; @@ -589,44 +595,81 @@ GF_Err video_sample_entry_dump(GF_Box *a, FILE * trace) case GF_ISOM_SUBTYPE_LHE1: name = "LHEVCSampleEntryBox"; break; + case GF_ISOM_SUBTYPE_AV01: + name = "AV1SampleEntryBox"; + break; case GF_ISOM_SUBTYPE_3GP_H263: name = "H263SampleDescriptionBox"; break; + case GF_ISOM_SUBTYPE_MJP2: + name = "MJ2KSampleDescriptionBox"; + break; + case GF_QT_SUBTYPE_APCH: + case GF_QT_SUBTYPE_APCO: + case GF_QT_SUBTYPE_APCN: + case GF_QT_SUBTYPE_APCS: + case GF_QT_SUBTYPE_AP4X: + case GF_QT_SUBTYPE_AP4H: + name = "ProResSampleEntryBox"; + full_dump=GF_TRUE; + break; + case GF_QT_SUBTYPE_RAW: + name = "RGBSampleEntryBox"; + full_dump=GF_TRUE; + break; + case GF_QT_SUBTYPE_YUV422: + name = "YUV422SampleEntryBox"; + full_dump=GF_TRUE; + break; + case GF_QT_SUBTYPE_YUV422_10: + name = "YUV422_10_SampleEntryBox"; + full_dump=GF_TRUE; + break; + case GF_QT_SUBTYPE_YUV444: + name = "YUV444SampleEntryBox"; + full_dump=GF_TRUE; + break; + case GF_QT_SUBTYPE_YUV444_10: + name = "YUV444_10_SampleEntryBox"; + full_dump=GF_TRUE; + break; default: + //DO NOT TOUCH FOR NOW, this breaks all hashes name = "MPEGVisualSampleDescriptionBox"; + break; } - gf_isom_box_dump_start(a, name, trace); - fprintf(trace, " DataReferenceIndex=\"%d\" Width=\"%d\" Height=\"%d\"", p->dataReferenceIndex, p->Width, p->Height); + gf_isom_box_dump_start(a, name, trace); - //dump reserved info - fprintf(trace, " XDPI=\"%d\" YDPI=\"%d\" BitDepth=\"%d\"", p->horiz_res, p->vert_res, p->bit_depth); - if (strlen((const char*)p->compressor_name) ) - fprintf(trace, " CompressorName=\"%s\"\n", p->compressor_name+1); + gf_fprintf(trace, " DataReferenceIndex=\"%d\" Width=\"%d\" Height=\"%d\"", p->dataReferenceIndex, p->Width, p->Height); + if (full_dump) { + Float dpih, dpiv; + gf_fprintf(trace, " Version=\"%d\" Revision=\"%d\" Vendor=\"%s\" TemporalQuality=\"%d\" SpatialQuality=\"%d\" FramesPerSample=\"%d\" ColorTableIndex=\"%d\"", + p->version, p->revision, gf_4cc_to_str(p->vendor), p->temporal_quality, p->spatial_quality, p->frames_per_sample, p->color_table_index); - fprintf(trace, ">\n"); + dpih = (Float) (p->horiz_res&0xFFFF); + dpih /= 0xFFFF; + dpih += (p->vert_res>>16); + dpiv = (Float) (p->vert_res&0xFFFF); + dpiv /= 0xFFFF; + dpiv += (p->vert_res>>16); - if (p->esd) { - gf_isom_box_dump(p->esd, trace); + gf_fprintf(trace, " XDPI=\"%g\" YDPI=\"%g\" BitDepth=\"%d\"", dpih, dpiv, p->bit_depth); } else { - if (p->hevc_config) gf_isom_box_dump(p->hevc_config, trace); - if (p->avc_config) gf_isom_box_dump(p->avc_config, trace); - if (p->ipod_ext) gf_isom_box_dump(p->ipod_ext, trace); - if (p->descr) gf_isom_box_dump(p->descr, trace); - if (p->svc_config) gf_isom_box_dump(p->svc_config, trace); - if (p->mvc_config) gf_isom_box_dump(p->mvc_config, trace); - if (p->lhvc_config) gf_isom_box_dump(p->lhvc_config, trace); - if (p->cfg_3gpp) gf_isom_box_dump(p->cfg_3gpp, trace); - } - if (a->type == GF_ISOM_BOX_TYPE_ENCV) { - gf_isom_box_array_dump(p->protections, trace); - } - if (p->pasp) gf_isom_box_dump(p->pasp, trace); - if (p->rvcc) gf_isom_box_dump(p->rvcc, trace); - if (p->rinf) gf_isom_box_dump(p->rinf, trace); + //dump reserved info + gf_fprintf(trace, " XDPI=\"%d\" YDPI=\"%d\" BitDepth=\"%d\"", p->horiz_res, p->vert_res, p->bit_depth); + } + if (strlen((const char*)p->compressor_name) ) { + if (isalnum(p->compressor_name[0])) { + gf_fprintf(trace, " CompressorName=\"%s\"\n", p->compressor_name); + } else { + gf_fprintf(trace, " CompressorName=\"%s\"\n", p->compressor_name+1); + } + } + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done(name, a, trace); return GF_OK; } @@ -634,45 +677,80 @@ GF_Err video_sample_entry_dump(GF_Box *a, FILE * trace) void base_audio_entry_dump(GF_AudioSampleEntryBox *p, FILE * trace) { - fprintf(trace, " DataReferenceIndex=\"%d\" SampleRate=\"%d\"", p->dataReferenceIndex, p->samplerate_hi); - fprintf(trace, " Channels=\"%d\" BitsPerSample=\"%d\"", p->channel_count, p->bitspersample); + gf_fprintf(trace, " DataReferenceIndex=\"%d\"", p->dataReferenceIndex); + if (p->version) + gf_fprintf(trace, " Version=\"%d\"", p->version); + gf_fprintf(trace, " SampleRate=\"%d\"", p->samplerate_hi); + gf_fprintf(trace, " Channels=\"%d\" BitsPerSample=\"%d\"", p->channel_count, p->bitspersample); + if (p->qtff_mode) { + gf_fprintf(trace, " isQTFF=\"%d\"", p->qtff_mode); + gf_fprintf(trace, " qtRevisionLevel=\"%d\"", p->revision); + gf_fprintf(trace, " qtVendor=\"%d\"", p->vendor); + gf_fprintf(trace, " qtCompressionId=\"%d\"", p->compression_id); + gf_fprintf(trace, " qtPacketSize=\"%d\"", p->packet_size); + if (p->version == 1) { + gf_fprintf(trace, " qtSamplesPerPacket=\"%d\"", p->extensions[0]<<24 | p->extensions[1]<<16 | p->extensions[2]<<8 | p->extensions[3]); + gf_fprintf(trace, " qtBytesPerPacket=\"%d\"", p->extensions[4]<<24 | p->extensions[5]<<16 | p->extensions[6]<<8 | p->extensions[7]); + gf_fprintf(trace, " qtBytesPerFrame=\"%d\"", p->extensions[8]<<24 | p->extensions[9]<<16 | p->extensions[10]<<8 | p->extensions[11]); + gf_fprintf(trace, " qtBytesPerSample=\"%d\"", p->extensions[12]<<24 | p->extensions[13]<<16 | p->extensions[14]<<8 | p->extensions[15]); + } + } } -GF_Err audio_sample_entry_dump(GF_Box *a, FILE * trace) +GF_Err audio_sample_entry_box_dump(GF_Box *a, FILE * trace) { char *szName; - Bool is_3gpp = GF_FALSE; + const char *error=NULL; GF_MPEGAudioSampleEntryBox *p = (GF_MPEGAudioSampleEntryBox *)a; switch (p->type) { case GF_ISOM_SUBTYPE_3GP_AMR: szName = "AMRSampleDescriptionBox"; - is_3gpp = GF_TRUE; + if (!p->cfg_3gpp) + error = ""; break; case GF_ISOM_SUBTYPE_3GP_AMR_WB: szName = "AMR_WB_SampleDescriptionBox"; - is_3gpp = GF_TRUE; + if (!p->cfg_3gpp) + error = ""; break; case GF_ISOM_SUBTYPE_3GP_EVRC: szName = "EVRCSampleDescriptionBox"; - is_3gpp = GF_TRUE; + if (!p->cfg_3gpp) + error = ""; break; case GF_ISOM_SUBTYPE_3GP_QCELP: szName = "QCELPSampleDescriptionBox"; - is_3gpp = GF_TRUE; + if (!p->cfg_3gpp) + error = ""; break; case GF_ISOM_SUBTYPE_3GP_SMV: szName = "SMVSampleDescriptionBox"; - is_3gpp = GF_TRUE; + if (!p->cfg_3gpp) + error = ""; break; case GF_ISOM_BOX_TYPE_MP4A: szName = "MPEGAudioSampleDescriptionBox"; + if (!p->esd) + error = ""; break; case GF_ISOM_BOX_TYPE_AC3: szName = "AC3SampleEntryBox"; + if (!p->cfg_ac3) + error = ""; break; case GF_ISOM_BOX_TYPE_EC3: szName = "EC3SampleEntryBox"; + if (!p->cfg_ac3) + error = ""; + break; + case GF_ISOM_BOX_TYPE_MHA1: + case GF_ISOM_BOX_TYPE_MHA2: + if (!p->cfg_mha) + error = ""; + case GF_ISOM_BOX_TYPE_MHM1: + case GF_ISOM_BOX_TYPE_MHM2: + szName = "MHASampleEntry"; break; default: szName = "AudioSampleDescriptionBox"; @@ -681,86 +759,85 @@ GF_Err audio_sample_entry_dump(GF_Box *a, FILE * trace) gf_isom_box_dump_start(a, szName, trace); base_audio_entry_dump((GF_AudioSampleEntryBox *)p, trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); - if (p->esd) { - gf_isom_box_dump(p->esd, trace); - } else if (p->cfg_3gpp) { - gf_isom_box_dump(p->cfg_3gpp, trace); + if (error) { + gf_fprintf(trace, "%s\n", error); + } + gf_isom_box_dump_done(szName, a, trace); + return GF_OK; +} - } else if (p->cfg_ac3) { - if (p->size) - gf_isom_box_dump(p->cfg_ac3, trace); +GF_Err gen_sample_entry_box_dump(GF_Box *a, FILE * trace) +{ + char *szName; + GF_SampleEntryBox *p = (GF_SampleEntryBox *)a; - } else if (p->size) { - if (is_3gpp) { - fprintf(trace, "\n"); - } else { - fprintf(trace, "\n"); - } - } - if (a->type == GF_ISOM_BOX_TYPE_ENCA) { - gf_isom_box_array_dump(p->protections, trace); + switch (p->type) { + case GF_QT_SUBTYPE_C608: + szName = "ClosedCaption"; + break; + default: + szName = "GenericSampleDescriptionBox"; + break; } + + gf_isom_box_dump_start(a, szName, trace); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done(szName, a, trace); return GF_OK; } -GF_Err gnrm_dump(GF_Box *a, FILE * trace) +GF_Err gnrm_box_dump(GF_Box *a, FILE * trace) { GF_GenericSampleEntryBox *p = (GF_GenericSampleEntryBox *)a; if (p->EntryType) a->type = p->EntryType; - gf_isom_box_dump_start(a, "SampleDescriptionBox", trace); - fprintf(trace, "DataReferenceIndex=\"%d\" ExtensionDataSize=\"%d\">\n", p->dataReferenceIndex, p->data_size); + gf_isom_box_dump_start(a, "SampleDescriptionEntryBox", trace); + gf_fprintf(trace, "DataReferenceIndex=\"%d\" ExtensionDataSize=\"%d\">\n", p->dataReferenceIndex, p->data_size); a->type = GF_ISOM_BOX_TYPE_GNRM; - gf_isom_box_dump_done("SampleDescriptionBox", a, trace); + gf_isom_box_dump_done("SampleDescriptionEntryBox", a, trace); return GF_OK; } -GF_Err gnrv_dump(GF_Box *a, FILE * trace) +GF_Err gnrv_box_dump(GF_Box *a, FILE * trace) { GF_GenericVisualSampleEntryBox *p = (GF_GenericVisualSampleEntryBox *)a; if (p->EntryType) a->type = p->EntryType; gf_isom_box_dump_start(a, "VisualSampleDescriptionBox", trace); - fprintf(trace, "DataReferenceIndex=\"%d\" Version=\"%d\" Revision=\"%d\" Vendor=\"%d\" TemporalQuality=\"%d\" SpacialQuality=\"%d\" Width=\"%d\" Height=\"%d\" HorizontalResolution=\"%d\" VerticalResolution=\"%d\" CompressorName=\"%s\" BitDepth=\"%d\">\n", - p->dataReferenceIndex, p->version, p->revision, p->vendor, p->temporal_quality, p->spatial_quality, p->Width, p->Height, p->horiz_res, p->vert_res, p->compressor_name+1, p->bit_depth); + gf_fprintf(trace, "DataReferenceIndex=\"%d\" Version=\"%d\" Revision=\"%d\" Vendor=\"%d\" TemporalQuality=\"%d\" SpacialQuality=\"%d\" Width=\"%d\" Height=\"%d\" HorizontalResolution=\"%d\" VerticalResolution=\"%d\" CompressorName=\"%s\" BitDepth=\"%d\">\n", + p->dataReferenceIndex, p->version, p->revision, p->vendor, p->temporal_quality, p->spatial_quality, p->Width, p->Height, p->horiz_res, p->vert_res, isalnum(p->compressor_name[0]) ? p->compressor_name : p->compressor_name+1, p->bit_depth); a->type = GF_ISOM_BOX_TYPE_GNRV; gf_isom_box_dump_done("VisualSampleDescriptionBox", a, trace); return GF_OK; } -GF_Err gnra_dump(GF_Box *a, FILE * trace) +GF_Err gnra_box_dump(GF_Box *a, FILE * trace) { GF_GenericAudioSampleEntryBox *p = (GF_GenericAudioSampleEntryBox *)a; if (p->EntryType) a->type = p->EntryType; gf_isom_box_dump_start(a, "AudioSampleDescriptionBox", trace); - fprintf(trace, "DataReferenceIndex=\"%d\" Version=\"%d\" Revision=\"%d\" Vendor=\"%d\" ChannelCount=\"%d\" BitsPerSample=\"%d\" Samplerate=\"%d\">\n", + gf_fprintf(trace, "DataReferenceIndex=\"%d\" Version=\"%d\" Revision=\"%d\" Vendor=\"%d\" ChannelCount=\"%d\" BitsPerSample=\"%d\" Samplerate=\"%d\">\n", p->dataReferenceIndex, p->version, p->revision, p->vendor, p->channel_count, p->bitspersample, p->samplerate_hi); a->type = GF_ISOM_BOX_TYPE_GNRA; gf_isom_box_dump_done("AudioSampleDescriptionBox", a, trace); return GF_OK; } -GF_Err edts_dump(GF_Box *a, FILE * trace) +GF_Err edts_box_dump(GF_Box *a, FILE * trace) { - GF_EditBox *p; - - p = (GF_EditBox *)a; gf_isom_box_dump_start(a, "EditBox", trace); - fprintf(trace, ">\n"); - if (p->size) - gf_isom_box_dump_ex(p->editList, trace, GF_ISOM_BOX_TYPE_ELST); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("EditBox", a, trace); return GF_OK; } -GF_Err udta_dump(GF_Box *a, FILE * trace) +GF_Err udta_box_dump(GF_Box *a, FILE * trace) { GF_UserDataBox *p; GF_UserDataMap *map; @@ -768,100 +845,109 @@ GF_Err udta_dump(GF_Box *a, FILE * trace) p = (GF_UserDataBox *)a; gf_isom_box_dump_start(a, "UserDataBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); i=0; while ((map = (GF_UserDataMap *)gf_list_enum(p->recordList, &i))) { - gf_isom_box_array_dump(map->other_boxes, trace); + gf_isom_box_array_dump(map->boxes, trace); } gf_isom_box_dump_done("UserDataBox", a, trace); return GF_OK; } -GF_Err dref_dump(GF_Box *a, FILE * trace) +GF_Err dref_box_dump(GF_Box *a, FILE * trace) { // GF_DataReferenceBox *p = (GF_DataReferenceBox *)a; gf_isom_box_dump_start(a, "DataReferenceBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("DataReferenceBox", a, trace); return GF_OK; } -GF_Err stsd_dump(GF_Box *a, FILE * trace) +GF_Err stsd_box_dump(GF_Box *a, FILE * trace) { -// GF_SampleDescriptionBox *p = (GF_SampleDescriptionBox *)a; + GF_SampleDescriptionBox *p = (GF_SampleDescriptionBox *)a; gf_isom_box_dump_start(a, "SampleDescriptionBox", trace); - fprintf(trace, ">\n"); + if (p->version) + gf_fprintf(trace, " version=\"%d\"", p->version); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("SampleDescriptionBox", a, trace); return GF_OK; } -GF_Err stts_dump(GF_Box *a, FILE * trace) +GF_Err stts_box_dump(GF_Box *a, FILE * trace) { GF_TimeToSampleBox *p; u32 i, nb_samples; + if (dump_skip_samples) + return GF_OK; + p = (GF_TimeToSampleBox *)a; gf_isom_box_dump_start(a, "TimeToSampleBox", trace); - fprintf(trace, "EntryCount=\"%d\">\n", p->nb_entries); + gf_fprintf(trace, "EntryCount=\"%d\">\n", p->nb_entries); nb_samples = 0; for (i=0; inb_entries; i++) { - fprintf(trace, "\n", p->entries[i].sampleDelta, p->entries[i].sampleCount); + gf_fprintf(trace, "\n", p->entries[i].sampleDelta, p->entries[i].sampleCount); nb_samples += p->entries[i].sampleCount; } if (p->size) - fprintf(trace, "\n", nb_samples); + gf_fprintf(trace, "\n", nb_samples); else - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); gf_isom_box_dump_done("TimeToSampleBox", a, trace); return GF_OK; } -GF_Err ctts_dump(GF_Box *a, FILE * trace) +GF_Err ctts_box_dump(GF_Box *a, FILE * trace) { GF_CompositionOffsetBox *p; u32 i, nb_samples; p = (GF_CompositionOffsetBox *)a; + + if (dump_skip_samples) + return GF_OK; + gf_isom_box_dump_start(a, "CompositionOffsetBox", trace); - fprintf(trace, "EntryCount=\"%d\">\n", p->nb_entries); + gf_fprintf(trace, "EntryCount=\"%d\">\n", p->nb_entries); nb_samples = 0; for (i=0; inb_entries; i++) { - fprintf(trace, "\n", p->entries[i].decodingOffset, p->entries[i].sampleCount); + gf_fprintf(trace, "\n", p->entries[i].decodingOffset, p->entries[i].sampleCount); nb_samples += p->entries[i].sampleCount; } if (p->size) - fprintf(trace, "\n", nb_samples); + gf_fprintf(trace, "\n", nb_samples); else - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); gf_isom_box_dump_done("CompositionOffsetBox", a, trace); return GF_OK; } -GF_Err cslg_dump(GF_Box *a, FILE * trace) +GF_Err cslg_box_dump(GF_Box *a, FILE * trace) { GF_CompositionToDecodeBox *p; p = (GF_CompositionToDecodeBox *)a; gf_isom_box_dump_start(a, "CompositionToDecodeBox", trace); - fprintf(trace, "compositionToDTSShift=\"%d\" leastDecodeToDisplayDelta=\"%d\" compositionStartTime=\"%d\" compositionEndTime=\"%d\">\n", p->leastDecodeToDisplayDelta, p->greatestDecodeToDisplayDelta, p->compositionStartTime, p->compositionEndTime); + gf_fprintf(trace, "compositionToDTSShift=\"%d\" leastDecodeToDisplayDelta=\"%d\" greatestDecodeToDisplayDelta=\"%d\" compositionStartTime=\"%d\" compositionEndTime=\"%d\">\n", p->compositionToDTSShift, p->leastDecodeToDisplayDelta, p->greatestDecodeToDisplayDelta, p->compositionStartTime, p->compositionEndTime); gf_isom_box_dump_done("CompositionToDecodeBox", a, trace); return GF_OK; } -GF_Err ccst_dump(GF_Box *a, FILE * trace) +GF_Err ccst_box_dump(GF_Box *a, FILE * trace) { GF_CodingConstraintsBox *p = (GF_CodingConstraintsBox *)a; gf_isom_box_dump_start(a, "CodingConstraintsBox", trace); - fprintf(trace, "all_ref_pics_intra=\"%d\" intra_pred_used=\"%d\" max_ref_per_pic=\"%d\" reserved=\"%d\">\n", p->all_ref_pics_intra, p->intra_pred_used, p->max_ref_per_pic, p->reserved); + gf_fprintf(trace, "all_ref_pics_intra=\"%d\" intra_pred_used=\"%d\" max_ref_per_pic=\"%d\" reserved=\"%d\">\n", p->all_ref_pics_intra, p->intra_pred_used, p->max_ref_per_pic, p->reserved); gf_isom_box_dump_done("CodingConstraintsBox", a, trace); return GF_OK; } -GF_Err stsh_dump(GF_Box *a, FILE * trace) +GF_Err stsh_box_dump(GF_Box *a, FILE * trace) { GF_ShadowSyncBox *p; u32 i; @@ -869,19 +955,19 @@ GF_Err stsh_dump(GF_Box *a, FILE * trace) p = (GF_ShadowSyncBox *)a; gf_isom_box_dump_start(a, "SyncShadowBox", trace); - fprintf(trace, "EntryCount=\"%d\">\n", gf_list_count(p->entries)); + gf_fprintf(trace, "EntryCount=\"%d\">\n", gf_list_count(p->entries)); i=0; while ((t = (GF_StshEntry *)gf_list_enum(p->entries, &i))) { - fprintf(trace, "\n", t->shadowedSampleNumber, t->syncSampleNumber); + gf_fprintf(trace, "\n", t->shadowedSampleNumber, t->syncSampleNumber); } if (!p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done("SyncShadowBox", a, trace); return GF_OK; } -GF_Err elst_dump(GF_Box *a, FILE * trace) +GF_Err elst_box_dump(GF_Box *a, FILE * trace) { GF_EditListBox *p; u32 i; @@ -889,31 +975,34 @@ GF_Err elst_dump(GF_Box *a, FILE * trace) p = (GF_EditListBox *)a; gf_isom_box_dump_start(a, "EditListBox", trace); - fprintf(trace, "EntryCount=\"%d\">\n", gf_list_count(p->entryList)); + gf_fprintf(trace, "EntryCount=\"%d\">\n", gf_list_count(p->entryList)); i=0; while ((t = (GF_EdtsEntry *)gf_list_enum(p->entryList, &i))) { - fprintf(trace, "\n", LLD_CAST t->segmentDuration, LLD_CAST t->mediaTime, t->mediaRate); + gf_fprintf(trace, "\n", t->segmentDuration, t->mediaTime, t->mediaRate); } if (!p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done("EditListBox", a, trace); return GF_OK; } -GF_Err stsc_dump(GF_Box *a, FILE * trace) +GF_Err stsc_box_dump(GF_Box *a, FILE * trace) { GF_SampleToChunkBox *p; u32 i, nb_samples; + if (dump_skip_samples) + return GF_OK; + p = (GF_SampleToChunkBox *)a; gf_isom_box_dump_start(a, "SampleToChunkBox", trace); - fprintf(trace, "EntryCount=\"%d\">\n", p->nb_entries); + gf_fprintf(trace, "EntryCount=\"%d\">\n", p->nb_entries); nb_samples = 0; for (i=0; inb_entries; i++) { - fprintf(trace, "\n", p->entries[i].firstChunk, p->entries[i].samplesPerChunk, p->entries[i].sampleDescriptionIndex); + gf_fprintf(trace, "\n", p->entries[i].firstChunk, p->entries[i].samplesPerChunk, p->entries[i].sampleDescriptionIndex); if (i+1nb_entries) { nb_samples += (p->entries[i+1].firstChunk - p->entries[i].firstChunk) * p->entries[i].samplesPerChunk; } else { @@ -921,19 +1010,21 @@ GF_Err stsc_dump(GF_Box *a, FILE * trace) } } if (p->size) - fprintf(trace, "\n", nb_samples); + gf_fprintf(trace, "\n", nb_samples); else - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); gf_isom_box_dump_done("SampleToChunkBox", a, trace); return GF_OK; } -GF_Err stsz_dump(GF_Box *a, FILE * trace) +GF_Err stsz_box_dump(GF_Box *a, FILE * trace) { GF_SampleSizeBox *p; u32 i; p = (GF_SampleSizeBox *)a; + if (dump_skip_samples) + return GF_OK; if (a->type == GF_ISOM_BOX_TYPE_STSZ) { gf_isom_box_dump_start(a, "SampleSizeBox", trace); @@ -942,463 +1033,424 @@ GF_Err stsz_dump(GF_Box *a, FILE * trace) gf_isom_box_dump_start(a, "CompactSampleSizeBox", trace); } - fprintf(trace, "SampleCount=\"%d\"", p->sampleCount); + gf_fprintf(trace, "SampleCount=\"%d\"", p->sampleCount); if (a->type == GF_ISOM_BOX_TYPE_STSZ) { if (p->sampleSize) { - fprintf(trace, " ConstantSampleSize=\"%d\"", p->sampleSize); + gf_fprintf(trace, " ConstantSampleSize=\"%d\"", p->sampleSize); } } else { - fprintf(trace, " SampleSizeBits=\"%d\"", p->sampleSize); + gf_fprintf(trace, " SampleSizeBits=\"%d\"", p->sampleSize); } - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); if ((a->type != GF_ISOM_BOX_TYPE_STSZ) || !p->sampleSize) { if (!p->sizes && p->size) { - fprintf(trace, "\n"); - } else { + gf_fprintf(trace, "\n"); + } else if (p->sizes) { for (i=0; isampleCount; i++) { - fprintf(trace, "\n", p->sizes[i]); + gf_fprintf(trace, "\n", p->sizes[i]); } } } if (!p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done((a->type == GF_ISOM_BOX_TYPE_STSZ) ? "SampleSizeBox" : "CompactSampleSizeBox", a, trace); return GF_OK; } -GF_Err stco_dump(GF_Box *a, FILE * trace) +GF_Err stco_box_dump(GF_Box *a, FILE * trace) { GF_ChunkOffsetBox *p; u32 i; + if (dump_skip_samples) + return GF_OK; + p = (GF_ChunkOffsetBox *)a; gf_isom_box_dump_start(a, "ChunkOffsetBox", trace); - fprintf(trace, "EntryCount=\"%d\">\n", p->nb_entries); + gf_fprintf(trace, "EntryCount=\"%d\">\n", p->nb_entries); if (!p->offsets && p->size) { - fprintf(trace, "\n"); - } else { + gf_fprintf(trace, "\n"); + } else if (p->offsets) { for (i=0; inb_entries; i++) { - fprintf(trace, "\n", p->offsets[i]); + gf_fprintf(trace, "\n", p->offsets[i]); } } if (!p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done("ChunkOffsetBox", a, trace); return GF_OK; } -GF_Err stss_dump(GF_Box *a, FILE * trace) +GF_Err stss_box_dump(GF_Box *a, FILE * trace) { GF_SyncSampleBox *p; u32 i; + if (dump_skip_samples) + return GF_OK; + p = (GF_SyncSampleBox *)a; gf_isom_box_dump_start(a, "SyncSampleBox", trace); - fprintf(trace, "EntryCount=\"%d\">\n", p->nb_entries); + gf_fprintf(trace, "EntryCount=\"%d\">\n", p->nb_entries); if (!p->sampleNumbers && p->size) { - fprintf(trace, "\n"); - } else { + gf_fprintf(trace, "\n"); + } else if (p->sampleNumbers) { for (i=0; inb_entries; i++) { - fprintf(trace, "\n", p->sampleNumbers[i]); + gf_fprintf(trace, "\n", p->sampleNumbers[i]); } } if (!p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done("SyncSampleBox", a, trace); return GF_OK; } -GF_Err stdp_dump(GF_Box *a, FILE * trace) +GF_Err stdp_box_dump(GF_Box *a, FILE * trace) { GF_DegradationPriorityBox *p; u32 i; + if (dump_skip_samples) + return GF_OK; + p = (GF_DegradationPriorityBox *)a; gf_isom_box_dump_start(a, "DegradationPriorityBox", trace); - fprintf(trace, "EntryCount=\"%d\">\n", p->nb_entries); + gf_fprintf(trace, "EntryCount=\"%d\">\n", p->nb_entries); if (!p->priorities && p->size) { - fprintf(trace, "\n"); - } else { + gf_fprintf(trace, "\n"); + } else if (p->priorities) { for (i=0; inb_entries; i++) { - fprintf(trace, "\n", p->priorities[i]); + gf_fprintf(trace, "\n", p->priorities[i]); } } if (!p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done("DegradationPriorityBox", a, trace); return GF_OK; } -GF_Err sdtp_dump(GF_Box *a, FILE * trace) +GF_Err sdtp_box_dump(GF_Box *a, FILE * trace) { GF_SampleDependencyTypeBox *p; u32 i; + if (dump_skip_samples) + return GF_OK; + p = (GF_SampleDependencyTypeBox*)a; gf_isom_box_dump_start(a, "SampleDependencyTypeBox", trace); - fprintf(trace, "SampleCount=\"%d\">\n", p->sampleCount); + gf_fprintf(trace, "SampleCount=\"%d\">\n", p->sampleCount); - if (!p->sample_info && p->size) { - fprintf(trace, "\n"); + if (!p->sample_info) { + gf_fprintf(trace, "\n"); } else { for (i=0; isampleCount; i++) { + const char *type; u8 flag = p->sample_info[i]; - fprintf(trace, "> 6) & 3) { + case 1: type="openGOP"; break; + case 2: type="no"; break; + case 3: type="SAP2"; break; + default: + case 0: type="unknown"; break; + } + gf_fprintf(trace, "isLeading=\"%s\" ", type); + switch ( (flag >> 4) & 3) { - case 0: - fprintf(trace, "dependsOnOther=\"unknown\" "); - break; - case 1: - fprintf(trace, "dependsOnOther=\"yes\" "); - break; - case 2: - fprintf(trace, "dependsOnOther=\"no\" "); - break; - case 3: - fprintf(trace, "dependsOnOther=\"RESERVED\" "); - break; + case 1: type="yes"; break; + case 2: type="no"; break; + case 3: type="RESERVED"; break; + default: + case 0: type="unknown"; break; } + gf_fprintf(trace, "dependsOnOther=\"%s\" ", type); + switch ( (flag >> 2) & 3) { - case 0: - fprintf(trace, "dependedOn=\"unknown\" "); - break; - case 1: - fprintf(trace, "dependedOn=\"yes\" "); - break; - case 2: - fprintf(trace, "dependedOn=\"no\" "); - break; - case 3: - fprintf(trace, "dependedOn=\"RESERVED\" "); - break; + case 1: type="yes"; break; + case 2: type="no"; break; + case 3: type="RESERVED"; break; + default: + case 0: type="unknown"; break; } + gf_fprintf(trace, "dependedOn=\"%s\" ", type); + switch ( flag & 3) { - case 0: - fprintf(trace, "hasRedundancy=\"unknown\" "); - break; - case 1: - fprintf(trace, "hasRedundancy=\"yes\" "); - break; - case 2: - fprintf(trace, "hasRedundancy=\"no\" "); - break; - case 3: - fprintf(trace, "hasRedundancy=\"RESERVED\" "); - break; + case 1: type="yes"; break; + case 2: type="no"; break; + case 3: type="RESERVED"; break; + default: + case 0: type="unknown"; break; } - fprintf(trace, " />\n"); + gf_fprintf(trace, "hasRedundancy=\"%s\"/>\n", type); } } if (!p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done("SampleDependencyTypeBox", a, trace); return GF_OK; } -GF_Err co64_dump(GF_Box *a, FILE * trace) +GF_Err co64_box_dump(GF_Box *a, FILE * trace) { GF_ChunkLargeOffsetBox *p; u32 i; + if (dump_skip_samples) + return GF_OK; + p = (GF_ChunkLargeOffsetBox *)a; gf_isom_box_dump_start(a, "ChunkLargeOffsetBox", trace); - fprintf(trace, "EntryCount=\"%d\">\n", p->nb_entries); + gf_fprintf(trace, "EntryCount=\"%d\">\n", p->nb_entries); if (!p->offsets && p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); #endif } else if (p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done("MPEG4ESDescriptorBox", a, trace); return GF_OK; } -GF_Err minf_dump(GF_Box *a, FILE * trace) +GF_Err minf_box_dump(GF_Box *a, FILE * trace) { - GF_MediaInformationBox *p; - - p = (GF_MediaInformationBox *)a; gf_isom_box_dump_start(a, "MediaInformationBox", trace); - fprintf(trace, ">\n"); - - if (p->size) - gf_isom_box_dump_ex(p->InfoHeader, trace, GF_ISOM_BOX_TYPE_NMHD); - if (p->size) - gf_isom_box_dump_ex(p->dataInformation, trace, GF_ISOM_BOX_TYPE_DINF); - if (p->size) - gf_isom_box_dump_ex(p->sampleTable, trace, GF_ISOM_BOX_TYPE_STBL); - + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("MediaInformationBox", a, trace); return GF_OK; } -GF_Err tkhd_dump(GF_Box *a, FILE * trace) +GF_Err tkhd_box_dump(GF_Box *a, FILE * trace) { GF_TrackHeaderBox *p; p = (GF_TrackHeaderBox *)a; gf_isom_box_dump_start(a, "TrackHeaderBox", trace); - fprintf(trace, "CreationTime=\""LLD"\" ModificationTime=\""LLD"\" TrackID=\"%u\" Duration=\""LLD"\"", - LLD_CAST p->creationTime, LLD_CAST p->modificationTime, p->trackID, LLD_CAST p->duration); + gf_fprintf(trace, "CreationTime=\""LLD"\" ModificationTime=\""LLD"\" TrackID=\"%u\" Duration=\""LLD"\"", + p->creationTime, p->modificationTime, p->trackID, p->duration); - if (p->alternate_group) fprintf(trace, " AlternateGroupID=\"%d\"", p->alternate_group); + if (p->alternate_group) gf_fprintf(trace, " AlternateGroupID=\"%d\"", p->alternate_group); if (p->volume) { - fprintf(trace, " Volume=\"%.2f\"", (Float)p->volume / 256); + gf_fprintf(trace, " Volume=\"%.2f\"", (Float)p->volume / 256); } else if (p->width || p->height) { - fprintf(trace, " Width=\"%.2f\" Height=\"%.2f\"", (Float)p->width / 65536, (Float)p->height / 65536); - if (p->layer) fprintf(trace, " Layer=\"%d\"", p->layer); + gf_fprintf(trace, " Width=\"%.2f\" Height=\"%.2f\"", (Float)p->width / 65536, (Float)p->height / 65536); + if (p->layer) gf_fprintf(trace, " Layer=\"%d\"", p->layer); } - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); if (p->width || p->height) { - fprintf(trace, "matrix[0], p->matrix[1], p->matrix[2]); - fprintf(trace, "m21=\"0x%.8x\" m22=\"0x%.8x\" m23=\"0x%.8x\" ", p->matrix[3], p->matrix[4], p->matrix[5]); - fprintf(trace, "m31=\"0x%.8x\" m32=\"0x%.8x\" m33=\"0x%.8x\"/>\n", p->matrix[6], p->matrix[7], p->matrix[8]); + gf_fprintf(trace, "matrix[0], p->matrix[1], p->matrix[2]); + gf_fprintf(trace, "m21=\"0x%.8x\" m22=\"0x%.8x\" m23=\"0x%.8x\" ", p->matrix[3], p->matrix[4], p->matrix[5]); + gf_fprintf(trace, "m31=\"0x%.8x\" m32=\"0x%.8x\" m33=\"0x%.8x\"/>\n", p->matrix[6], p->matrix[7], p->matrix[8]); } gf_isom_box_dump_done("TrackHeaderBox", a, trace); return GF_OK; } -GF_Err tref_dump(GF_Box *a, FILE * trace) +GF_Err tref_box_dump(GF_Box *a, FILE * trace) { -// GF_TrackReferenceBox *p = (GF_TrackReferenceBox *)a; gf_isom_box_dump_start(a, "TrackReferenceBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("TrackReferenceBox", a, trace); return GF_OK; } -GF_Err mdia_dump(GF_Box *a, FILE * trace) +GF_Err mdia_box_dump(GF_Box *a, FILE * trace) { - GF_MediaBox *p = (GF_MediaBox *)a; gf_isom_box_dump_start(a, "MediaBox", trace); - fprintf(trace, ">\n"); - if (p->size) - gf_isom_box_dump_ex(p->mediaHeader, trace, GF_ISOM_BOX_TYPE_MDHD); - if (p->size) - gf_isom_box_dump_ex(p->handler, trace,GF_ISOM_BOX_TYPE_HDLR); - if (p->size) - gf_isom_box_dump_ex(p->information, trace, GF_ISOM_BOX_TYPE_MINF); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("MediaBox", a, trace); return GF_OK; } -GF_Err mfra_dump(GF_Box *a, FILE * trace) +GF_Err mfra_box_dump(GF_Box *a, FILE * trace) { - GF_MovieFragmentRandomAccessBox *p = (GF_MovieFragmentRandomAccessBox *)a; - u32 i, count; - GF_TrackFragmentRandomAccessBox *tfra; - gf_isom_box_dump_start(a, "MovieFragmentRandomAccessBox", trace); - fprintf(trace, ">\n"); - count = gf_list_count(p->tfra_list); - for (i=0; itfra_list, i); - gf_isom_box_dump_ex(tfra, trace, GF_ISOM_BOX_TYPE_TFRA); - } + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("MovieFragmentRandomAccessBox", a, trace); return GF_OK; } -GF_Err tfra_dump(GF_Box *a, FILE * trace) +GF_Err tfra_box_dump(GF_Box *a, FILE * trace) { u32 i; GF_TrackFragmentRandomAccessBox *p = (GF_TrackFragmentRandomAccessBox *)a; gf_isom_box_dump_start(a, "TrackFragmentRandomAccessBox", trace); - fprintf(trace, "TrackId=\"%u\" number_of_entries=\"%u\">\n", p->track_id, p->nb_entries); + gf_fprintf(trace, "TrackId=\"%u\" number_of_entries=\"%u\">\n", p->track_id, p->nb_entries); for (i=0; inb_entries; i++) { - fprintf(trace, "\n", + gf_fprintf(trace, "\n", p->entries[i].time, p->entries[i].moof_offset, p->entries[i].traf_number, p->entries[i].trun_number, p->entries[i].sample_number); } if (!p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done("TrackFragmentRandomAccessBox", a, trace); return GF_OK; } -GF_Err mfro_dump(GF_Box *a, FILE * trace) +GF_Err mfro_box_dump(GF_Box *a, FILE * trace) { GF_MovieFragmentRandomAccessOffsetBox *p = (GF_MovieFragmentRandomAccessOffsetBox *)a; gf_isom_box_dump_start(a, "MovieFragmentRandomAccessOffsetBox", trace); - fprintf(trace, "container_size=\"%d\" >\n", p->container_size); + gf_fprintf(trace, "container_size=\"%d\" >\n", p->container_size); gf_isom_box_dump_done("MovieFragmentRandomAccessOffsetBox", a, trace); return GF_OK; } -GF_Err elng_dump(GF_Box *a, FILE * trace) +GF_Err elng_box_dump(GF_Box *a, FILE * trace) { GF_ExtendedLanguageBox *p = (GF_ExtendedLanguageBox *)a; gf_isom_box_dump_start(a, "ExtendedLanguageBox", trace); - fprintf(trace, "LanguageCode=\"%s\">\n", p->extended_language); + gf_fprintf(trace, "LanguageCode=\"%s\">\n", p->extended_language); gf_isom_box_dump_done("ExtendedLanguageBox", a, trace); return GF_OK; } -GF_Err unkn_dump(GF_Box *a, FILE * trace) +GF_Err unkn_box_dump(GF_Box *a, FILE * trace) { + const char *name = "UnknownBox"; GF_UnknownBox *u = (GF_UnknownBox *)a; - u->type = u->original_4cc; - gf_isom_box_dump_start(a, "UnknownBox", trace); - u->type = GF_ISOM_BOX_TYPE_UNKNOWN; - if (u->dataSize<100) + if (!a->type && (a->size==8)) + name = "TerminatorBox"; + + gf_isom_box_dump_start(a, name, trace); + + if (u->dataSize && u->dataSize<100) dump_data_attribute(trace, "data", u->data, u->dataSize); - fprintf(trace, ">\n"); - gf_isom_box_dump_done("UnknownBox", a, trace); + + gf_fprintf(trace, ">\n"); + gf_isom_box_dump_done(name, a, trace); +#ifdef GPAC_ENABLE_COVERAGE + if (gf_sys_is_cov_mode()) { + dump_data_string(NULL, NULL, 0); + } +#endif return GF_OK; } -GF_Err uuid_dump(GF_Box *a, FILE * trace) +GF_Err uuid_box_dump(GF_Box *a, FILE * trace) { gf_isom_box_dump_start(a, "UUIDBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("UUIDBox", a, trace); return GF_OK; } -GF_Err void_dump(GF_Box *a, FILE * trace) +GF_Err void_box_dump(GF_Box *a, FILE * trace) { gf_isom_box_dump_start(a, "VoidBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("VoidBox", a, trace); return GF_OK; } -GF_Err ftyp_dump(GF_Box *a, FILE * trace) +GF_Err ftyp_box_dump(GF_Box *a, FILE * trace) { GF_FileTypeBox *p; u32 i; p = (GF_FileTypeBox *)a; gf_isom_box_dump_start(a, (a->type == GF_ISOM_BOX_TYPE_FTYP ? "FileTypeBox" : "SegmentTypeBox"), trace); - fprintf(trace, "MajorBrand=\"%s\" MinorVersion=\"%d\">\n", gf_4cc_to_str(p->majorBrand), p->minorVersion); + gf_fprintf(trace, "MajorBrand=\"%s\" MinorVersion=\"%d\">\n", gf_4cc_to_str(p->majorBrand), p->minorVersion); for (i=0; ialtCount; i++) { - fprintf(trace, "\n", gf_4cc_to_str(p->altBrand[i])); + gf_fprintf(trace, "\n", gf_4cc_to_str(p->altBrand[i])); } if (!p->type) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done((a->type == GF_ISOM_BOX_TYPE_FTYP ? "FileTypeBox" : "SegmentTypeBox"), a, trace); return GF_OK; } -GF_Err padb_dump(GF_Box *a, FILE * trace) +GF_Err padb_box_dump(GF_Box *a, FILE * trace) { GF_PaddingBitsBox *p; u32 i; p = (GF_PaddingBitsBox *)a; gf_isom_box_dump_start(a, "PaddingBitsBox", trace); - fprintf(trace, "EntryCount=\"%d\">\n", p->SampleCount); + gf_fprintf(trace, "EntryCount=\"%d\">\n", p->SampleCount); for (i=0; iSampleCount; i+=1) { - fprintf(trace, "\n", p->padbits[i]); + gf_fprintf(trace, "\n", p->padbits[i]); } if (!p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done("PaddingBitsBox", a, trace); return GF_OK; } -GF_Err stsf_dump(GF_Box *a, FILE * trace) -{ - GF_SampleFragmentBox *p; - GF_StsfEntry *ent; - u32 i, j, count; - - - p = (GF_SampleFragmentBox *)a; - count = gf_list_count(p->entryList); - gf_isom_box_dump_start(a, "SampleFragmentBox", trace); - fprintf(trace, "EntryCount=\"%d\">\n", count); - - for (i=0; ientryList, i); - fprintf(trace, "\n", ent->SampleNumber, ent->fragmentCount); - for (j=0; jfragmentCount; j++) fprintf(trace, "\n", ent->fragmentSizes[j]); - fprintf(trace, "\n"); - } - if (!p->size) { - fprintf(trace, "\n"); - fprintf(trace, "\n"); - fprintf(trace, "\n"); - } - gf_isom_box_dump_done("SampleFragmentBox", a, trace); - return GF_OK; -} - -GF_Err gppc_dump(GF_Box *a, FILE * trace) +GF_Err gppc_box_dump(GF_Box *a, FILE * trace) { GF_3GPPConfigBox *p = (GF_3GPPConfigBox *)a; - const char *name = gf_4cc_to_str(p->cfg.vendor); switch (p->cfg.type) { case GF_ISOM_SUBTYPE_3GP_AMR: case GF_ISOM_SUBTYPE_3GP_AMR_WB: gf_isom_box_dump_start(a, "AMRConfigurationBox", trace); - fprintf(trace, "Vendor=\"%s\" Version=\"%d\"", name, p->cfg.decoder_version); - fprintf(trace, " FramesPerSample=\"%d\" SupportedModes=\"%x\" ModeRotating=\"%d\"", p->cfg.frames_per_sample, p->cfg.AMR_mode_set, p->cfg.AMR_mode_change_period); - fprintf(trace, ">\n"); + gf_fprintf(trace, "Vendor=\"%s\" Version=\"%d\"", gf_4cc_to_str(p->cfg.vendor), p->cfg.decoder_version); + gf_fprintf(trace, " FramesPerSample=\"%d\" SupportedModes=\"%x\" ModeRotating=\"%d\"", p->cfg.frames_per_sample, p->cfg.AMR_mode_set, p->cfg.AMR_mode_change_period); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("AMRConfigurationBox", a, trace); break; case GF_ISOM_SUBTYPE_3GP_EVRC: gf_isom_box_dump_start(a, "EVRCConfigurationBox", trace); - fprintf(trace, "Vendor=\"%s\" Version=\"%d\" FramesPerSample=\"%d\" >\n", name, p->cfg.decoder_version, p->cfg.frames_per_sample); + gf_fprintf(trace, "Vendor=\"%s\" Version=\"%d\" FramesPerSample=\"%d\" >\n", gf_4cc_to_str(p->cfg.vendor), p->cfg.decoder_version, p->cfg.frames_per_sample); gf_isom_box_dump_done("EVRCConfigurationBox", a, trace); break; case GF_ISOM_SUBTYPE_3GP_QCELP: gf_isom_box_dump_start(a, "QCELPConfigurationBox", trace); - fprintf(trace, "Vendor=\"%s\" Version=\"%d\" FramesPerSample=\"%d\" >\n", name, p->cfg.decoder_version, p->cfg.frames_per_sample); + gf_fprintf(trace, "Vendor=\"%s\" Version=\"%d\" FramesPerSample=\"%d\" >\n", gf_4cc_to_str(p->cfg.vendor), p->cfg.decoder_version, p->cfg.frames_per_sample); gf_isom_box_dump_done("QCELPConfigurationBox", a, trace); break; case GF_ISOM_SUBTYPE_3GP_SMV: gf_isom_box_dump_start(a, "SMVConfigurationBox", trace); - fprintf(trace, "Vendor=\"%s\" Version=\"%d\" FramesPerSample=\"%d\" >\n", name, p->cfg.decoder_version, p->cfg.frames_per_sample); + gf_fprintf(trace, "Vendor=\"%s\" Version=\"%d\" FramesPerSample=\"%d\" >\n", gf_4cc_to_str(p->cfg.vendor), p->cfg.decoder_version, p->cfg.frames_per_sample); gf_isom_box_dump_done("SMVConfigurationBox", a, trace); break; case GF_ISOM_SUBTYPE_3GP_H263: gf_isom_box_dump_start(a, "H263ConfigurationBox", trace); - fprintf(trace, "Vendor=\"%s\" Version=\"%d\"", name, p->cfg.decoder_version); - fprintf(trace, " Profile=\"%d\" Level=\"%d\"", p->cfg.H263_profile, p->cfg.H263_level); - fprintf(trace, ">\n"); + gf_fprintf(trace, "Vendor=\"%s\" Version=\"%d\"", gf_4cc_to_str(p->cfg.vendor), p->cfg.decoder_version); + gf_fprintf(trace, " Profile=\"%d\" Level=\"%d\"", p->cfg.H263_profile, p->cfg.H263_level); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("H263ConfigurationBox", a, trace); break; default: @@ -1408,7 +1460,7 @@ GF_Err gppc_dump(GF_Box *a, FILE * trace) } -GF_Err avcc_dump(GF_Box *a, FILE * trace) +GF_Err avcc_box_dump(GF_Box *a, FILE * trace) { u32 i, count; GF_AVCConfigurationBox *p = (GF_AVCConfigurationBox *) a; @@ -1416,76 +1468,76 @@ GF_Err avcc_dump(GF_Box *a, FILE * trace) char boxname[256]; sprintf(boxname, "%sConfigurationBox", name); gf_isom_box_dump_start(a, boxname, trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); - fprintf(trace, "<%sDecoderConfigurationRecord", name); + gf_fprintf(trace, "<%sDecoderConfigurationRecord", name); if (! p->config) { if (p->size) { - fprintf(trace, ">\n"); - fprintf(trace, "\n"); + gf_fprintf(trace, ">\n"); + gf_fprintf(trace, "\n"); } else { - fprintf(trace, " configurationVersion=\"\" AVCProfileIndication=\"\" profile_compatibility=\"\" AVCLevelIndication=\"\" nal_unit_size=\"\" complete_representation=\"\""); + gf_fprintf(trace, " configurationVersion=\"\" AVCProfileIndication=\"\" profile_compatibility=\"\" AVCLevelIndication=\"\" nal_unit_size=\"\" complete_representation=\"\""); - fprintf(trace, " chroma_format=\"\" luma_bit_depth=\"\" chroma_bit_depth=\"\""); - fprintf(trace, ">\n"); + gf_fprintf(trace, " chroma_format=\"\" luma_bit_depth=\"\" chroma_bit_depth=\"\""); + gf_fprintf(trace, ">\n"); - fprintf(trace, "\n"); - fprintf(trace, "\n"); - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } - fprintf(trace, "\n", name); + gf_fprintf(trace, "\n", name); gf_isom_box_dump_done(boxname, a, trace); return GF_OK; } - fprintf(trace, " configurationVersion=\"%d\" AVCProfileIndication=\"%d\" profile_compatibility=\"%d\" AVCLevelIndication=\"%d\" nal_unit_size=\"%d\"", p->config->configurationVersion, p->config->AVCProfileIndication, p->config->profile_compatibility, p->config->AVCLevelIndication, p->config->nal_unit_size); + gf_fprintf(trace, " configurationVersion=\"%d\" AVCProfileIndication=\"%d\" profile_compatibility=\"%d\" AVCLevelIndication=\"%d\" nal_unit_size=\"%d\"", p->config->configurationVersion, p->config->AVCProfileIndication, p->config->profile_compatibility, p->config->AVCLevelIndication, p->config->nal_unit_size); if ((p->type==GF_ISOM_BOX_TYPE_SVCC) || (p->type==GF_ISOM_BOX_TYPE_MVCC) ) - fprintf(trace, " complete_representation=\"%d\"", p->config->complete_representation); + gf_fprintf(trace, " complete_representation=\"%d\"", p->config->complete_representation); if (p->type==GF_ISOM_BOX_TYPE_AVCC) { if (gf_avc_is_rext_profile(p->config->AVCProfileIndication)) { - fprintf(trace, " chroma_format=\"%s\" luma_bit_depth=\"%d\" chroma_bit_depth=\"%d\"", gf_avc_hevc_get_chroma_format_name(p->config->chroma_format), p->config->luma_bit_depth, p->config->chroma_bit_depth); + gf_fprintf(trace, " chroma_format=\"%s\" luma_bit_depth=\"%d\" chroma_bit_depth=\"%d\"", gf_avc_hevc_get_chroma_format_name(p->config->chroma_format), p->config->luma_bit_depth, p->config->chroma_bit_depth); } } - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); count = gf_list_count(p->config->sequenceParameterSets); for (i=0; iconfig->sequenceParameterSets, i); - fprintf(trace, "size); + gf_fprintf(trace, "size); dump_data(trace, c->data, c->size); - fprintf(trace, "\"/>\n"); + gf_fprintf(trace, "\"/>\n"); } count = gf_list_count(p->config->pictureParameterSets); for (i=0; iconfig->pictureParameterSets, i); - fprintf(trace, "size); + gf_fprintf(trace, "size); dump_data(trace, c->data, c->size); - fprintf(trace, "\"/>\n"); + gf_fprintf(trace, "\"/>\n"); } if (p->config->sequenceParameterSetExtensions) { count = gf_list_count(p->config->sequenceParameterSetExtensions); for (i=0; iconfig->sequenceParameterSetExtensions, i); - fprintf(trace, "size); + gf_fprintf(trace, "size); dump_data(trace, c->data, c->size); - fprintf(trace, "\"/>\n"); + gf_fprintf(trace, "\"/>\n"); } } - fprintf(trace, "\n", name); + gf_fprintf(trace, "\n", name); gf_isom_box_dump_done(boxname, a, trace); return GF_OK; } -GF_Err hvcc_dump(GF_Box *a, FILE * trace) +GF_Err hvcc_box_dump(GF_Box *a, FILE * trace) { u32 i, count; const char *name = (a->type==GF_ISOM_BOX_TYPE_HVCC) ? "HEVC" : "L-HEVC"; @@ -1494,115 +1546,188 @@ GF_Err hvcc_dump(GF_Box *a, FILE * trace) sprintf(boxname, "%sConfigurationBox", name); gf_isom_box_dump_start(a, boxname, trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); if (! p->config) { if (p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } else { - fprintf(trace, "<%sDecoderConfigurationRecord nal_unit_size=\"\" configurationVersion=\"\" ", name); + gf_fprintf(trace, "<%sDecoderConfigurationRecord nal_unit_size=\"\" configurationVersion=\"\" ", name); if (a->type==GF_ISOM_BOX_TYPE_HVCC) { - fprintf(trace, "profile_space=\"\" tier_flag=\"\" profile_idc=\"\" general_profile_compatibility_flags=\"\" progressive_source_flag=\"\" interlaced_source_flag=\"\" non_packed_constraint_flag=\"\" frame_only_constraint_flag=\"\" constraint_indicator_flags=\"\" level_idc=\"\" "); + gf_fprintf(trace, "profile_space=\"\" tier_flag=\"\" profile_idc=\"\" general_profile_compatibility_flags=\"\" progressive_source_flag=\"\" interlaced_source_flag=\"\" non_packed_constraint_flag=\"\" frame_only_constraint_flag=\"\" constraint_indicator_flags=\"\" level_idc=\"\" "); } - fprintf(trace, "min_spatial_segmentation_idc=\"\" parallelismType=\"\" "); + gf_fprintf(trace, "min_spatial_segmentation_idc=\"\" parallelismType=\"\" "); if (a->type==GF_ISOM_BOX_TYPE_HVCC) - fprintf(trace, "chroma_format=\"\" luma_bit_depth=\"\" chroma_bit_depth=\"\" avgFrameRate=\"\" constantFrameRate=\"\" numTemporalLayers=\"\" temporalIdNested=\"\""); + gf_fprintf(trace, "chroma_format=\"\" luma_bit_depth=\"\" chroma_bit_depth=\"\" avgFrameRate=\"\" constantFrameRate=\"\" numTemporalLayers=\"\" temporalIdNested=\"\""); - fprintf(trace, ">\n"); - fprintf(trace, "\n"); - fprintf(trace, "\n"); - fprintf(trace, "\n"); - fprintf(trace, "\n", name); + gf_fprintf(trace, ">\n"); + gf_fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); + gf_fprintf(trace, "\n", name); } - fprintf(trace, "\n", name); + gf_fprintf(trace, "\n", name); return GF_OK; } - fprintf(trace, "<%sDecoderConfigurationRecord nal_unit_size=\"%d\" ", name, p->config->nal_unit_size); - fprintf(trace, "configurationVersion=\"%u\" ", p->config->configurationVersion); + gf_fprintf(trace, "<%sDecoderConfigurationRecord nal_unit_size=\"%d\" ", name, p->config->nal_unit_size); + gf_fprintf(trace, "configurationVersion=\"%u\" ", p->config->configurationVersion); if (a->type==GF_ISOM_BOX_TYPE_HVCC) { - fprintf(trace, "profile_space=\"%u\" ", p->config->profile_space); - fprintf(trace, "tier_flag=\"%u\" ", p->config->tier_flag); - fprintf(trace, "profile_idc=\"%u\" ", p->config->profile_idc); - fprintf(trace, "general_profile_compatibility_flags=\"%X\" ", p->config->general_profile_compatibility_flags); - fprintf(trace, "progressive_source_flag=\"%u\" ", p->config->progressive_source_flag); - fprintf(trace, "interlaced_source_flag=\"%u\" ", p->config->interlaced_source_flag); - fprintf(trace, "non_packed_constraint_flag=\"%u\" ", p->config->non_packed_constraint_flag); - fprintf(trace, "frame_only_constraint_flag=\"%u\" ", p->config->frame_only_constraint_flag); - fprintf(trace, "constraint_indicator_flags=\""LLX"\" ", p->config->constraint_indicator_flags); - fprintf(trace, "level_idc=\"%d\" ", p->config->level_idc); - } - fprintf(trace, "min_spatial_segmentation_idc=\"%u\" ", p->config->min_spatial_segmentation_idc); - fprintf(trace, "parallelismType=\"%u\" ", p->config->parallelismType); + gf_fprintf(trace, "profile_space=\"%u\" ", p->config->profile_space); + gf_fprintf(trace, "tier_flag=\"%u\" ", p->config->tier_flag); + gf_fprintf(trace, "profile_idc=\"%u\" ", p->config->profile_idc); + gf_fprintf(trace, "general_profile_compatibility_flags=\"%X\" ", p->config->general_profile_compatibility_flags); + gf_fprintf(trace, "progressive_source_flag=\"%u\" ", p->config->progressive_source_flag); + gf_fprintf(trace, "interlaced_source_flag=\"%u\" ", p->config->interlaced_source_flag); + gf_fprintf(trace, "non_packed_constraint_flag=\"%u\" ", p->config->non_packed_constraint_flag); + gf_fprintf(trace, "frame_only_constraint_flag=\"%u\" ", p->config->frame_only_constraint_flag); + gf_fprintf(trace, "constraint_indicator_flags=\""LLX"\" ", p->config->constraint_indicator_flags); + gf_fprintf(trace, "level_idc=\"%d\" ", p->config->level_idc); + } + gf_fprintf(trace, "min_spatial_segmentation_idc=\"%u\" ", p->config->min_spatial_segmentation_idc); + gf_fprintf(trace, "parallelismType=\"%u\" ", p->config->parallelismType); if (a->type==GF_ISOM_BOX_TYPE_HVCC) - fprintf(trace, "chroma_format=\"%s\" luma_bit_depth=\"%u\" chroma_bit_depth=\"%u\" avgFrameRate=\"%u\" constantFrameRate=\"%u\" numTemporalLayers=\"%u\" temporalIdNested=\"%u\"", + gf_fprintf(trace, "chroma_format=\"%s\" luma_bit_depth=\"%u\" chroma_bit_depth=\"%u\" avgFrameRate=\"%u\" constantFrameRate=\"%u\" numTemporalLayers=\"%u\" temporalIdNested=\"%u\"", gf_avc_hevc_get_chroma_format_name(p->config->chromaFormat), p->config->luma_bit_depth, p->config->chroma_bit_depth, p->config->avgFrameRate, p->config->constantFrameRate, p->config->numTemporalLayers, p->config->temporalIdNested); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); count = gf_list_count(p->config->param_array); for (i=0; iconfig->param_array, i); - fprintf(trace, "\n", ar->type, ar->array_completeness); + gf_fprintf(trace, "\n", ar->type, ar->array_completeness); nalucount = gf_list_count(ar->nalus); for (j=0; jnalus, j); - fprintf(trace, "size); + gf_fprintf(trace, "size); dump_data(trace, c->data, c->size); - fprintf(trace, "\"/>\n"); + gf_fprintf(trace, "\"/>\n"); } - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } - fprintf(trace, "\n", name); + gf_fprintf(trace, "\n", name); gf_isom_box_dump_done(boxname, a, trace); return GF_OK; } -GF_Err m4ds_dump(GF_Box *a, FILE * trace) +GF_Err av1c_box_dump(GF_Box *a, FILE *trace) { + GF_AV1ConfigurationBox *ptr = (GF_AV1ConfigurationBox*)a; + gf_fprintf(trace, "\n"); + if (ptr->config) { + u32 i, obu_count = gf_list_count(ptr->config->obu_array); + + gf_fprintf(trace, "config->version, (u32)ptr->config->seq_profile, (u32)ptr->config->seq_level_idx_0, (u32)ptr->config->seq_tier_0); + gf_fprintf(trace, "high_bitdepth=\"%u\" twelve_bit=\"%u\" monochrome=\"%u\" ", (u32)ptr->config->high_bitdepth, (u32)ptr->config->twelve_bit, (u32)ptr->config->monochrome); + gf_fprintf(trace, "chroma_subsampling_x=\"%u\" chroma_subsampling_y=\"%u\" chroma_sample_position=\"%u\" ", (u32)ptr->config->chroma_subsampling_x, (u32)ptr->config->chroma_subsampling_y, (u32)ptr->config->chroma_sample_position); + gf_fprintf(trace, "initial_presentation_delay=\"%u\" OBUs_count=\"%u\">\n", ptr->config->initial_presentation_delay_minus_one+1, obu_count); + + for (i=0; iconfig->obu_array, i); + gf_fprintf(trace, "obu_type, gf_av1_get_obu_name(obu_a->obu_type), (u32) obu_a->obu_length); + dump_data(trace, (char *)obu_a->obu, (u32) obu_a->obu_length); + gf_fprintf(trace, "\"/>\n"); + } + gf_fprintf(trace, "\n"); + } + gf_fprintf(trace, "\n"); + return GF_OK; +} + + +GF_Err vpcc_box_dump(GF_Box *a, FILE *trace) { + GF_VPConfigurationBox *ptr = (GF_VPConfigurationBox*)a; + gf_fprintf(trace, "\n"); + if (ptr->config) { + gf_fprintf(trace, "config->profile); + gf_fprintf(trace, " level=\"%u\"", ptr->config->level); + gf_fprintf(trace, " bit_depth=\"%u\"", ptr->config->bit_depth); + gf_fprintf(trace, " chroma_subsampling=\"%u\"", ptr->config->chroma_subsampling); + gf_fprintf(trace, " video_fullRange_flag=\"%u\"", ptr->config->video_fullRange_flag); + gf_fprintf(trace, " colour_primaries=\"%u\"", ptr->config->colour_primaries); + gf_fprintf(trace, " transfer_characteristics=\"%u\"", ptr->config->transfer_characteristics); + gf_fprintf(trace, " matrix_coefficients=\"%u\"", ptr->config->matrix_coefficients); + gf_fprintf(trace, " codec_initdata_size=\"%u\"", ptr->config->codec_initdata_size); + + gf_fprintf(trace, ">\n\n"); + } + gf_fprintf(trace, "\n"); + return GF_OK; +} + +GF_Err SmDm_box_dump(GF_Box *a, FILE *trace) { + GF_SMPTE2086MasteringDisplayMetadataBox * ptr = (GF_SMPTE2086MasteringDisplayMetadataBox *)a; + if (!a) return GF_BAD_PARAM; + gf_isom_box_dump_start(a, "SMPTE2086MasteringDisplayMetadataBox", trace); + gf_fprintf(trace, "primaryRChromaticity_x=\"%u\" ", ptr->primaryRChromaticity_x); + gf_fprintf(trace, "primaryRChromaticity_y=\"%u\" ", ptr->primaryRChromaticity_y); + gf_fprintf(trace, "primaryGChromaticity_x=\"%u\" ", ptr->primaryGChromaticity_x); + gf_fprintf(trace, "primaryGChromaticity_y=\"%u\" ", ptr->primaryGChromaticity_y); + gf_fprintf(trace, "primaryBChromaticity_x=\"%u\" ", ptr->primaryBChromaticity_x); + gf_fprintf(trace, "primaryBChromaticity_y=\"%u\" ", ptr->primaryBChromaticity_y); + gf_fprintf(trace, "whitePointChromaticity_x=\"%u\" ", ptr->whitePointChromaticity_x); + gf_fprintf(trace, "whitePointChromaticity_y=\"%u\" ", ptr->whitePointChromaticity_y); + gf_fprintf(trace, "luminanceMax=\"%u\" ", ptr->luminanceMax); + gf_fprintf(trace, "luminanceMin=\"%u\">\n", ptr->luminanceMin); + gf_isom_box_dump_done("SMPTE2086MasteringDisplayMetadataBox", a, trace); + return GF_OK; +} + +GF_Err CoLL_box_dump(GF_Box *a, FILE *trace) { + GF_VPContentLightLevelBox * ptr = (GF_VPContentLightLevelBox *)a; + if (!a) return GF_BAD_PARAM; + gf_isom_box_dump_start(a, "VPContentLightLevelBox", trace); + gf_fprintf(trace, "maxCLL=\"%u\" maxFALL=\"%u\">\n", ptr->maxCLL, ptr->maxFALL); + gf_isom_box_dump_done("VPContentLightLevelBox", a, trace); + return GF_OK; +} + +GF_Err m4ds_box_dump(GF_Box *a, FILE * trace) { u32 i; GF_Descriptor *desc; GF_MPEG4ExtensionDescriptorsBox *p = (GF_MPEG4ExtensionDescriptorsBox *) a; gf_isom_box_dump_start(a, "MPEG4ExtensionDescriptorsBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); i=0; while ((desc = (GF_Descriptor *)gf_list_enum(p->descriptors, &i))) { #ifndef GPAC_DISABLE_OD_DUMP gf_odf_dump_desc(desc, trace, 1, GF_TRUE); #else - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); #endif } gf_isom_box_dump_done("MPEG4ExtensionDescriptorsBox", a, trace); return GF_OK; } -GF_Err btrt_dump(GF_Box *a, FILE * trace) +GF_Err btrt_box_dump(GF_Box *a, FILE * trace) { GF_BitRateBox *p = (GF_BitRateBox*)a; gf_isom_box_dump_start(a, "BitRateBox", trace); - fprintf(trace, "BufferSizeDB=\"%d\" avgBitRate=\"%d\" maxBitRate=\"%d\">\n", p->bufferSizeDB, p->avgBitrate, p->maxBitrate); + gf_fprintf(trace, "BufferSizeDB=\"%d\" avgBitRate=\"%d\" maxBitRate=\"%d\">\n", p->bufferSizeDB, p->avgBitrate, p->maxBitrate); gf_isom_box_dump_done("BitRateBox", a, trace); return GF_OK; } -GF_Err ftab_dump(GF_Box *a, FILE * trace) +GF_Err ftab_box_dump(GF_Box *a, FILE * trace) { u32 i; GF_FontTableBox *p = (GF_FontTableBox *)a; gf_isom_box_dump_start(a, "FontTableBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); for (i=0; ientry_count; i++) { - fprintf(trace, "\n", p->fonts[i].fontID, p->fonts[i].fontName ? p->fonts[i].fontName : "NULL"); + gf_fprintf(trace, "\n", p->fonts[i].fontID, p->fonts[i].fontName ? p->fonts[i].fontName : "NULL"); } if (!p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done("FontTableBox", a, trace); return GF_OK; @@ -1610,511 +1735,490 @@ GF_Err ftab_dump(GF_Box *a, FILE * trace) static void tx3g_dump_rgba8(FILE * trace, char *name, u32 col) { - fprintf(trace, "%s=\"%x %x %x %x\"", name, (col>>16)&0xFF, (col>>8)&0xFF, (col)&0xFF, (col>>24)&0xFF); + gf_fprintf(trace, "%s=\"%x %x %x %x\"", name, (col>>16)&0xFF, (col>>8)&0xFF, (col)&0xFF, (col>>24)&0xFF); } static void tx3g_dump_rgb16(FILE * trace, char *name, char col[6]) { - fprintf(trace, "%s=\"%x %x %x\"", name, *((u16*)col), *((u16*)(col+1)), *((u16*)(col+2))); + gf_fprintf(trace, "%s=\"%x %x %x\"", name, *((u16*)col), *((u16*)(col+2)), *((u16*)(col+4))); } static void tx3g_dump_box(FILE * trace, GF_BoxRecord *rec) { - fprintf(trace, "\n", rec->top, rec->left, rec->bottom, rec->right); + gf_fprintf(trace, "\n", rec->top, rec->left, rec->bottom, rec->right); } static void tx3g_dump_style(FILE * trace, GF_StyleRecord *rec) { - fprintf(trace, "startCharOffset, rec->endCharOffset, rec->fontID); + gf_fprintf(trace, "startCharOffset, rec->endCharOffset, rec->fontID); if (!rec->style_flags) { - fprintf(trace, "Normal"); + gf_fprintf(trace, "Normal"); } else { - if (rec->style_flags & 1) fprintf(trace, "Bold "); - if (rec->style_flags & 2) fprintf(trace, "Italic "); - if (rec->style_flags & 4) fprintf(trace, "Underlined "); + if (rec->style_flags & 1) gf_fprintf(trace, "Bold "); + if (rec->style_flags & 2) gf_fprintf(trace, "Italic "); + if (rec->style_flags & 4) gf_fprintf(trace, "Underlined "); } - fprintf(trace, "\" fontSize=\"%d\" ", rec->font_size); + gf_fprintf(trace, "\" fontSize=\"%d\" ", rec->font_size); tx3g_dump_rgba8(trace, "textColor", rec->text_color); - fprintf(trace, "/>\n"); + gf_fprintf(trace, "/>\n"); } -GF_Err tx3g_dump(GF_Box *a, FILE * trace) +GF_Err tx3g_box_dump(GF_Box *a, FILE * trace) { GF_Tx3gSampleEntryBox *p = (GF_Tx3gSampleEntryBox *)a; gf_isom_box_dump_start(a, "Tx3gSampleEntryBox", trace); - fprintf(trace, "dataReferenceIndex=\"%d\" displayFlags=\"%x\" horizontal-justification=\"%d\" vertical-justification=\"%d\" ", + gf_fprintf(trace, "dataReferenceIndex=\"%d\" displayFlags=\"%x\" horizontal-justification=\"%d\" vertical-justification=\"%d\" ", p->dataReferenceIndex, p->displayFlags, p->horizontal_justification, p->vertical_justification); tx3g_dump_rgba8(trace, "backgroundColor", p->back_color); - fprintf(trace, ">\n"); - fprintf(trace, "\n"); + gf_fprintf(trace, ">\n"); + gf_fprintf(trace, "\n"); tx3g_dump_box(trace, &p->default_box); - gf_isom_box_dump_done("DefaultBox", a, trace); - fprintf(trace, "\n"); + + gf_fprintf(trace, "\n"); tx3g_dump_style(trace, &p->default_style); - fprintf(trace, "\n"); - if (p->size) { - gf_isom_box_dump_ex(p->font_table, trace, GF_ISOM_BOX_TYPE_FTAB); - } + gf_fprintf(trace, "\n"); gf_isom_box_dump_done("Tx3gSampleEntryBox", a, trace); return GF_OK; } -GF_Err text_dump(GF_Box *a, FILE * trace) +GF_Err text_box_dump(GF_Box *a, FILE * trace) { GF_TextSampleEntryBox *p = (GF_TextSampleEntryBox *)a; gf_isom_box_dump_start(a, "TextSampleEntryBox", trace); - fprintf(trace, "dataReferenceIndex=\"%d\" displayFlags=\"%x\" textJustification=\"%d\" ", + gf_fprintf(trace, "dataReferenceIndex=\"%d\" displayFlags=\"%x\" textJustification=\"%d\" ", p->dataReferenceIndex, p->displayFlags, p->textJustification); if (p->textName) - fprintf(trace, "textName=\"%s\" ", p->textName); + gf_fprintf(trace, "textName=\"%s\" ", p->textName); tx3g_dump_rgb16(trace, "background-color", p->background_color); tx3g_dump_rgb16(trace, " foreground-color", p->foreground_color); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); tx3g_dump_box(trace, &p->default_box); gf_isom_box_dump_done("DefaultBox", a, trace); gf_isom_box_dump_done("TextSampleEntryBox", a, trace); return GF_OK; } -GF_Err styl_dump(GF_Box *a, FILE * trace) +GF_Err styl_box_dump(GF_Box *a, FILE * trace) { u32 i; GF_TextStyleBox*p = (GF_TextStyleBox*)a; gf_isom_box_dump_start(a, "TextStyleBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); for (i=0; ientry_count; i++) tx3g_dump_style(trace, &p->styles[i]); if (!p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done("TextStyleBox", a, trace); return GF_OK; } -GF_Err hlit_dump(GF_Box *a, FILE * trace) +GF_Err hlit_box_dump(GF_Box *a, FILE * trace) { GF_TextHighlightBox*p = (GF_TextHighlightBox*)a; gf_isom_box_dump_start(a, "TextHighlightBox", trace); - fprintf(trace, "startcharoffset=\"%d\" endcharoffset=\"%d\">\n", p->startcharoffset, p->endcharoffset); + gf_fprintf(trace, "startcharoffset=\"%d\" endcharoffset=\"%d\">\n", p->startcharoffset, p->endcharoffset); gf_isom_box_dump_done("TextHighlightBox", a, trace); return GF_OK; } -GF_Err hclr_dump(GF_Box *a, FILE * trace) +GF_Err hclr_box_dump(GF_Box *a, FILE * trace) { GF_TextHighlightColorBox*p = (GF_TextHighlightColorBox*)a; gf_isom_box_dump_start(a, "TextHighlightColorBox", trace); tx3g_dump_rgba8(trace, "highlight_color", p->hil_color); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("TextHighlightColorBox", a, trace); return GF_OK; } -GF_Err krok_dump(GF_Box *a, FILE * trace) +GF_Err krok_box_dump(GF_Box *a, FILE * trace) { u32 i; GF_TextKaraokeBox*p = (GF_TextKaraokeBox*)a; gf_isom_box_dump_start(a, "TextKaraokeBox", trace); - fprintf(trace, "highlight_starttime=\"%d\">\n", p->highlight_starttime); + gf_fprintf(trace, "highlight_starttime=\"%d\">\n", p->highlight_starttime); for (i=0; inb_entries; i++) { - fprintf(trace, "\n", p->records[i].highlight_endtime, p->records[i].start_charoffset, p->records[i].end_charoffset); + gf_fprintf(trace, "\n", p->records[i].highlight_endtime, p->records[i].start_charoffset, p->records[i].end_charoffset); } if (!p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done("TextKaraokeBox", a, trace); return GF_OK; } -GF_Err dlay_dump(GF_Box *a, FILE * trace) +GF_Err dlay_box_dump(GF_Box *a, FILE * trace) { GF_TextScrollDelayBox*p = (GF_TextScrollDelayBox*)a; gf_isom_box_dump_start(a, "TextScrollDelayBox", trace); - fprintf(trace, "scroll_delay=\"%d\">\n", p->scroll_delay); + gf_fprintf(trace, "scroll_delay=\"%d\">\n", p->scroll_delay); gf_isom_box_dump_done("TextScrollDelayBox", a, trace); return GF_OK; } -GF_Err href_dump(GF_Box *a, FILE * trace) +GF_Err href_box_dump(GF_Box *a, FILE * trace) { GF_TextHyperTextBox*p = (GF_TextHyperTextBox*)a; gf_isom_box_dump_start(a, "TextHyperTextBox", trace); - fprintf(trace, "startcharoffset=\"%d\" endcharoffset=\"%d\" URL=\"%s\" altString=\"%s\">\n", p->startcharoffset, p->endcharoffset, p->URL ? p->URL : "NULL", p->URL_hint ? p->URL_hint : "NULL"); + gf_fprintf(trace, "startcharoffset=\"%d\" endcharoffset=\"%d\" URL=\"%s\" altString=\"%s\">\n", p->startcharoffset, p->endcharoffset, p->URL ? p->URL : "NULL", p->URL_hint ? p->URL_hint : "NULL"); gf_isom_box_dump_done("TextHyperTextBox", a, trace); return GF_OK; } -GF_Err tbox_dump(GF_Box *a, FILE * trace) +GF_Err tbox_box_dump(GF_Box *a, FILE * trace) { GF_TextBoxBox*p = (GF_TextBoxBox*)a; gf_isom_box_dump_start(a, "TextBoxBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); tx3g_dump_box(trace, &p->box); gf_isom_box_dump_done("TextBoxBox", a, trace); return GF_OK; } -GF_Err blnk_dump(GF_Box *a, FILE * trace) +GF_Err blnk_box_dump(GF_Box *a, FILE * trace) { GF_TextBlinkBox*p = (GF_TextBlinkBox*)a; gf_isom_box_dump_start(a, "TextBlinkBox", trace); - fprintf(trace, "start_charoffset=\"%d\" end_charoffset=\"%d\">\n", p->startcharoffset, p->endcharoffset); + gf_fprintf(trace, "start_charoffset=\"%d\" end_charoffset=\"%d\">\n", p->startcharoffset, p->endcharoffset); gf_isom_box_dump_done("TextBlinkBox", a, trace); return GF_OK; } -GF_Err twrp_dump(GF_Box *a, FILE * trace) +GF_Err twrp_box_dump(GF_Box *a, FILE * trace) { GF_TextWrapBox*p = (GF_TextWrapBox*)a; gf_isom_box_dump_start(a, "TextWrapBox", trace); - fprintf(trace, "wrap_flag=\"%s\">\n", p->wrap_flag ? ( (p->wrap_flag>1) ? "Reserved" : "Automatic" ) : "No Wrap"); + gf_fprintf(trace, "wrap_flag=\"%s\">\n", p->wrap_flag ? ( (p->wrap_flag>1) ? "Reserved" : "Automatic" ) : "No Wrap"); gf_isom_box_dump_done("TextWrapBox", a, trace); return GF_OK; } -GF_Err meta_dump(GF_Box *a, FILE * trace) +GF_Err meta_box_dump(GF_Box *a, FILE * trace) { - GF_MetaBox *p; - p = (GF_MetaBox *)a; gf_isom_box_dump_start(a, "MetaBox", trace); - fprintf(trace, ">\n"); - - if (p->handler) gf_isom_box_dump(p->handler, trace); - if (p->primary_resource) gf_isom_box_dump(p->primary_resource, trace); - if (p->file_locations) gf_isom_box_dump(p->file_locations, trace); - if (p->item_locations) gf_isom_box_dump(p->item_locations, trace); - if (p->protections) gf_isom_box_dump(p->protections, trace); - if (p->item_infos) gf_isom_box_dump(p->item_infos, trace); - if (p->IPMP_control) gf_isom_box_dump(p->IPMP_control, trace); - if (p->item_refs) gf_isom_box_dump(p->item_refs, trace); - if (p->item_props) gf_isom_box_dump(p->item_props, trace); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("MetaBox", a, trace); return GF_OK; } -GF_Err xml_dump(GF_Box *a, FILE * trace) +GF_Err xml_box_dump(GF_Box *a, FILE * trace) { GF_XMLBox *p = (GF_XMLBox *)a; gf_isom_box_dump_start(a, "XMLBox", trace); - fprintf(trace, ">\n"); - fprintf(trace, "\n"); + gf_fprintf(trace, "xml) - gf_fwrite(p->xml, strlen(p->xml), 1, trace); - fprintf(trace, "]]>\n"); + gf_fwrite(p->xml, strlen(p->xml), trace); + gf_fprintf(trace, "]]>\n"); gf_isom_box_dump_done("XMLBox", a, trace); return GF_OK; } -GF_Err bxml_dump(GF_Box *a, FILE * trace) +GF_Err bxml_box_dump(GF_Box *a, FILE * trace) { GF_BinaryXMLBox *p = (GF_BinaryXMLBox *)a; gf_isom_box_dump_start(a, "BinaryXMLBox", trace); - fprintf(trace, "binarySize=\"%d\">\n", p->data_length); + gf_fprintf(trace, "binarySize=\"%d\">\n", p->data_length); gf_isom_box_dump_done("BinaryXMLBox", a, trace); return GF_OK; } -GF_Err pitm_dump(GF_Box *a, FILE * trace) +GF_Err pitm_box_dump(GF_Box *a, FILE * trace) { GF_PrimaryItemBox *p = (GF_PrimaryItemBox *)a; gf_isom_box_dump_start(a, "PrimaryItemBox", trace); - fprintf(trace, "item_ID=\"%d\">\n", p->item_ID); + gf_fprintf(trace, "item_ID=\"%d\">\n", p->item_ID); gf_isom_box_dump_done("PrimaryItemBox", a, trace); return GF_OK; } -GF_Err ipro_dump(GF_Box *a, FILE * trace) +GF_Err ipro_box_dump(GF_Box *a, FILE * trace) { - GF_ItemProtectionBox *p = (GF_ItemProtectionBox *)a; gf_isom_box_dump_start(a, "ItemProtectionBox", trace); - fprintf(trace, ">\n"); - gf_isom_box_array_dump(p->protection_information, trace); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("ItemProtectionBox", a, trace); return GF_OK; } -GF_Err infe_dump(GF_Box *a, FILE * trace) +GF_Err infe_box_dump(GF_Box *a, FILE * trace) { GF_ItemInfoEntryBox *p = (GF_ItemInfoEntryBox *)a; gf_isom_box_dump_start(a, "ItemInfoEntryBox", trace); - fprintf(trace, "item_ID=\"%d\" item_protection_index=\"%d\" item_name=\"%s\" content_type=\"%s\" content_encoding=\"%s\" item_type=\"%s\">\n", p->item_ID, p->item_protection_index, p->item_name, p->content_type, p->content_encoding, gf_4cc_to_str(p->item_type)); + gf_fprintf(trace, "item_ID=\"%d\" item_protection_index=\"%d\" item_name=\"%s\" content_type=\"%s\" content_encoding=\"%s\" item_type=\"%s\">\n", p->item_ID, p->item_protection_index, p->item_name, p->content_type, p->content_encoding, gf_4cc_to_str(p->item_type)); gf_isom_box_dump_done("ItemInfoEntryBox", a, trace); return GF_OK; } -GF_Err iinf_dump(GF_Box *a, FILE * trace) +GF_Err iinf_box_dump(GF_Box *a, FILE * trace) { - GF_ItemInfoBox *p = (GF_ItemInfoBox *)a; gf_isom_box_dump_start(a, "ItemInfoBox", trace); - fprintf(trace, ">\n"); - gf_isom_box_array_dump(p->item_infos, trace); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("ItemInfoBox", a, trace); return GF_OK; } -GF_Err iloc_dump(GF_Box *a, FILE * trace) +GF_Err iloc_box_dump(GF_Box *a, FILE * trace) { u32 i, j, count, count2; GF_ItemLocationBox *p = (GF_ItemLocationBox*)a; gf_isom_box_dump_start(a, "ItemLocationBox", trace); - fprintf(trace, "offset_size=\"%d\" length_size=\"%d\" base_offset_size=\"%d\" index_size=\"%d\">\n", p->offset_size, p->length_size, p->base_offset_size, p->index_size); + gf_fprintf(trace, "offset_size=\"%d\" length_size=\"%d\" base_offset_size=\"%d\" index_size=\"%d\">\n", p->offset_size, p->length_size, p->base_offset_size, p->index_size); count = gf_list_count(p->location_entries); for (i=0; ilocation_entries, i); count2 = gf_list_count(ie->extent_entries); - fprintf(trace, "\n", ie->item_ID, ie->data_reference_index, LLD_CAST ie->base_offset, ie->construction_method); + gf_fprintf(trace, "\n", ie->item_ID, ie->data_reference_index, ie->base_offset, ie->construction_method); for (j=0; jextent_entries, j); - fprintf(trace, "\n", LLD_CAST iee->extent_offset, LLD_CAST iee->extent_length, LLD_CAST iee->extent_index); + gf_fprintf(trace, "\n", iee->extent_offset, iee->extent_length, iee->extent_index); } - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } if (!p->size) { - fprintf(trace, "\n"); - fprintf(trace, "\n"); - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } gf_isom_box_dump_done("ItemLocationBox", a, trace); return GF_OK; } -GF_Err iref_dump(GF_Box *a, FILE * trace) +GF_Err iref_box_dump(GF_Box *a, FILE * trace) { - GF_ItemReferenceBox *p = (GF_ItemReferenceBox *)a; gf_isom_box_dump_start(a, "ItemReferenceBox", trace); - fprintf(trace, ">\n"); - gf_isom_box_array_dump(p->references, trace); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("ItemReferenceBox", a, trace); return GF_OK; } -GF_Err hinf_dump(GF_Box *a, FILE * trace) +GF_Err hinf_box_dump(GF_Box *a, FILE * trace) { // GF_HintInfoBox *p = (GF_HintInfoBox *)a; gf_isom_box_dump_start(a, "HintInfoBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("HintInfoBox", a, trace); return GF_OK; } -GF_Err trpy_dump(GF_Box *a, FILE * trace) +GF_Err trpy_box_dump(GF_Box *a, FILE * trace) { GF_TRPYBox *p = (GF_TRPYBox *)a; gf_isom_box_dump_start(a, "LargeTotalRTPBytesBox", trace); - fprintf(trace, "RTPBytesSent=\""LLD"\">\n", LLD_CAST p->nbBytes); + gf_fprintf(trace, "RTPBytesSent=\""LLD"\">\n", p->nbBytes); gf_isom_box_dump_done("LargeTotalRTPBytesBox", a, trace); return GF_OK; } -GF_Err totl_dump(GF_Box *a, FILE * trace) +GF_Err totl_box_dump(GF_Box *a, FILE * trace) { GF_TOTLBox *p; p = (GF_TOTLBox *)a; gf_isom_box_dump_start(a, "TotalRTPBytesBox", trace); - fprintf(trace, "RTPBytesSent=\"%d\">\n", p->nbBytes); + gf_fprintf(trace, "RTPBytesSent=\"%d\">\n", p->nbBytes); gf_isom_box_dump_done("TotalRTPBytesBox", a, trace); return GF_OK; } -GF_Err nump_dump(GF_Box *a, FILE * trace) +GF_Err nump_box_dump(GF_Box *a, FILE * trace) { GF_NUMPBox *p; p = (GF_NUMPBox *)a; gf_isom_box_dump_start(a, "LargeTotalPacketBox", trace); - fprintf(trace, "PacketsSent=\""LLD"\">\n", LLD_CAST p->nbPackets); + gf_fprintf(trace, "PacketsSent=\""LLD"\">\n", p->nbPackets); gf_isom_box_dump_done("LargeTotalPacketBox", a, trace); return GF_OK; } -GF_Err npck_dump(GF_Box *a, FILE * trace) +GF_Err npck_box_dump(GF_Box *a, FILE * trace) { GF_NPCKBox *p; p = (GF_NPCKBox *)a; gf_isom_box_dump_start(a, "TotalPacketBox", trace); - fprintf(trace, "packetsSent=\"%d\">\n", p->nbPackets); + gf_fprintf(trace, "packetsSent=\"%d\">\n", p->nbPackets); gf_isom_box_dump_done("TotalPacketBox", a, trace); return GF_OK; } -GF_Err tpyl_dump(GF_Box *a, FILE * trace) +GF_Err tpyl_box_dump(GF_Box *a, FILE * trace) { GF_NTYLBox *p; p = (GF_NTYLBox *)a; gf_isom_box_dump_start(a, "LargeTotalMediaBytesBox", trace); - fprintf(trace, "BytesSent=\""LLD"\">\n", LLD_CAST p->nbBytes); + gf_fprintf(trace, "BytesSent=\""LLD"\">\n", p->nbBytes); gf_isom_box_dump_done("LargeTotalMediaBytesBox", a, trace); return GF_OK; } -GF_Err tpay_dump(GF_Box *a, FILE * trace) +GF_Err tpay_box_dump(GF_Box *a, FILE * trace) { GF_TPAYBox *p; p = (GF_TPAYBox *)a; gf_isom_box_dump_start(a, "TotalMediaBytesBox", trace); - fprintf(trace, "BytesSent=\"%d\">\n", p->nbBytes); + gf_fprintf(trace, "BytesSent=\"%d\">\n", p->nbBytes); gf_isom_box_dump_done("TotalMediaBytesBox", a, trace); return GF_OK; } -GF_Err maxr_dump(GF_Box *a, FILE * trace) +GF_Err maxr_box_dump(GF_Box *a, FILE * trace) { GF_MAXRBox *p; p = (GF_MAXRBox *)a; gf_isom_box_dump_start(a, "MaxDataRateBox", trace); - fprintf(trace, "MaxDataRate=\"%d\" Granularity=\"%d\">\n", p->maxDataRate, p->granularity); + gf_fprintf(trace, "MaxDataRate=\"%d\" Granularity=\"%d\">\n", p->maxDataRate, p->granularity); gf_isom_box_dump_done("MaxDataRateBox", a, trace); return GF_OK; } -GF_Err dmed_dump(GF_Box *a, FILE * trace) +GF_Err dmed_box_dump(GF_Box *a, FILE * trace) { GF_DMEDBox *p; p = (GF_DMEDBox *)a; gf_isom_box_dump_start(a, "BytesFromMediaTrackBox", trace); - fprintf(trace, "BytesSent=\""LLD"\">\n", LLD_CAST p->nbBytes); + gf_fprintf(trace, "BytesSent=\""LLD"\">\n", p->nbBytes); gf_isom_box_dump_done("BytesFromMediaTrackBox", a, trace); return GF_OK; } -GF_Err dimm_dump(GF_Box *a, FILE * trace) +GF_Err dimm_box_dump(GF_Box *a, FILE * trace) { GF_DIMMBox *p; p = (GF_DIMMBox *)a; gf_isom_box_dump_start(a, "ImmediateDataBytesBox", trace); - fprintf(trace, "BytesSent=\""LLD"\">\n", LLD_CAST p->nbBytes); + gf_fprintf(trace, "BytesSent=\""LLD"\">\n", p->nbBytes); gf_isom_box_dump_done("ImmediateDataBytesBox", a, trace); return GF_OK; } -GF_Err drep_dump(GF_Box *a, FILE * trace) +GF_Err drep_box_dump(GF_Box *a, FILE * trace) { GF_DREPBox *p; p = (GF_DREPBox *)a; gf_isom_box_dump_start(a, "RepeatedDataBytesBox", trace); - fprintf(trace, "RepeatedBytes=\""LLD"\">\n", LLD_CAST p->nbBytes); + gf_fprintf(trace, "RepeatedBytes=\""LLD"\">\n", p->nbBytes); gf_isom_box_dump_done("RepeatedDataBytesBox", a, trace); return GF_OK; } -GF_Err tssy_dump(GF_Box *a, FILE * trace) +GF_Err tssy_box_dump(GF_Box *a, FILE * trace) { GF_TimeStampSynchronyBox *p = (GF_TimeStampSynchronyBox *)a; gf_isom_box_dump_start(a, "TimeStampSynchronyBox", trace); - fprintf(trace, "timestamp_sync=\"%d\">\n", p->timestamp_sync); + gf_fprintf(trace, "timestamp_sync=\"%d\">\n", p->timestamp_sync); gf_isom_box_dump_done("TimeStampSynchronyBox", a, trace); return GF_OK; } -GF_Err rssr_dump(GF_Box *a, FILE * trace) +GF_Err rssr_box_dump(GF_Box *a, FILE * trace) { GF_ReceivedSsrcBox *p = (GF_ReceivedSsrcBox *)a; gf_isom_box_dump_start(a, "ReceivedSsrcBox", trace); - fprintf(trace, "SSRC=\"%d\">\n", p->ssrc); + gf_fprintf(trace, "SSRC=\"%d\">\n", p->ssrc); gf_isom_box_dump_done("ReceivedSsrcBox", a, trace); return GF_OK; } -GF_Err tmin_dump(GF_Box *a, FILE * trace) +GF_Err tmin_box_dump(GF_Box *a, FILE * trace) { GF_TMINBox *p; p = (GF_TMINBox *)a; gf_isom_box_dump_start(a, "MinTransmissionTimeBox", trace); - fprintf(trace, "MinimumTransmitTime=\"%d\">\n", p->minTime); + gf_fprintf(trace, "MinimumTransmitTime=\"%d\">\n", p->minTime); gf_isom_box_dump_done("MinTransmissionTimeBox", a, trace); return GF_OK; } -GF_Err tmax_dump(GF_Box *a, FILE * trace) +GF_Err tmax_box_dump(GF_Box *a, FILE * trace) { GF_TMAXBox *p; p = (GF_TMAXBox *)a; gf_isom_box_dump_start(a, "MaxTransmissionTimeBox", trace); - fprintf(trace, "MaximumTransmitTime=\"%d\">\n", p->maxTime); + gf_fprintf(trace, "MaximumTransmitTime=\"%d\">\n", p->maxTime); gf_isom_box_dump_done("MaxTransmissionTimeBox", a, trace); return GF_OK; } -GF_Err pmax_dump(GF_Box *a, FILE * trace) +GF_Err pmax_box_dump(GF_Box *a, FILE * trace) { GF_PMAXBox *p; p = (GF_PMAXBox *)a; gf_isom_box_dump_start(a, "MaxPacketSizeBox", trace); - fprintf(trace, "MaximumSize=\"%d\">\n", p->maxSize); + gf_fprintf(trace, "MaximumSize=\"%d\">\n", p->maxSize); gf_isom_box_dump_done("MaxPacketSizeBox", a, trace); return GF_OK; } -GF_Err dmax_dump(GF_Box *a, FILE * trace) +GF_Err dmax_box_dump(GF_Box *a, FILE * trace) { GF_DMAXBox *p; p = (GF_DMAXBox *)a; gf_isom_box_dump_start(a, "MaxPacketDurationBox", trace); - fprintf(trace, "MaximumDuration=\"%d\">\n", p->maxDur); + gf_fprintf(trace, "MaximumDuration=\"%d\">\n", p->maxDur); gf_isom_box_dump_done("MaxPacketDurationBox", a, trace); return GF_OK; } -GF_Err payt_dump(GF_Box *a, FILE * trace) +GF_Err payt_box_dump(GF_Box *a, FILE * trace) { GF_PAYTBox *p; p = (GF_PAYTBox *)a; gf_isom_box_dump_start(a, "PayloadTypeBox", trace); - fprintf(trace, "PayloadID=\"%d\" PayloadString=\"%s\">\n", p->payloadCode, p->payloadString); + gf_fprintf(trace, "PayloadID=\"%d\" PayloadString=\"%s\">\n", p->payloadCode, p->payloadString); gf_isom_box_dump_done("PayloadTypeBox", a, trace); return GF_OK; } -GF_Err name_dump(GF_Box *a, FILE * trace) +GF_Err name_box_dump(GF_Box *a, FILE * trace) { GF_NameBox *p; p = (GF_NameBox *)a; gf_isom_box_dump_start(a, "NameBox", trace); - fprintf(trace, "Name=\"%s\">\n", p->string); + gf_fprintf(trace, "Name=\"%s\">\n", p->string); gf_isom_box_dump_done("NameBox", a, trace); return GF_OK; } -GF_Err rely_dump(GF_Box *a, FILE * trace) +GF_Err rely_box_dump(GF_Box *a, FILE * trace) { GF_RelyHintBox *p; p = (GF_RelyHintBox *)a; gf_isom_box_dump_start(a, "RelyTransmissionBox", trace); - fprintf(trace, "Prefered=\"%d\" required=\"%d\">\n", p->prefered, p->required); + gf_fprintf(trace, "preferred=\"%d\" required=\"%d\">\n", p->preferred, p->required); gf_isom_box_dump_done("RelyTransmissionBox", a, trace); return GF_OK; } -GF_Err snro_dump(GF_Box *a, FILE * trace) +GF_Err snro_box_dump(GF_Box *a, FILE * trace) { GF_SeqOffHintEntryBox *p; p = (GF_SeqOffHintEntryBox *)a; gf_isom_box_dump_start(a, "PacketSequenceOffsetBox", trace); - fprintf(trace, "SeqNumOffset=\"%d\">\n", p->SeqOffset); + gf_fprintf(trace, "SeqNumOffset=\"%d\">\n", p->SeqOffset); gf_isom_box_dump_done("PacketSequenceOffsetBox", a, trace); return GF_OK; } -GF_Err tims_dump(GF_Box *a, FILE * trace) +GF_Err tims_box_dump(GF_Box *a, FILE * trace) { GF_TSHintEntryBox *p; p = (GF_TSHintEntryBox *)a; gf_isom_box_dump_start(a, "RTPTimeScaleBox", trace); - fprintf(trace, "TimeScale=\"%d\">\n", p->timeScale); + gf_fprintf(trace, "TimeScale=\"%d\">\n", p->timeScale); gf_isom_box_dump_done("RTPTimeScaleBox", a, trace); return GF_OK; } -GF_Err tsro_dump(GF_Box *a, FILE * trace) +GF_Err tsro_box_dump(GF_Box *a, FILE * trace) { GF_TimeOffHintEntryBox *p; p = (GF_TimeOffHintEntryBox *)a; gf_isom_box_dump_start(a, "TimeStampOffsetBox", trace); - fprintf(trace, "TimeStampOffset=\"%d\">\n", p->TimeOffset); + gf_fprintf(trace, "TimeStampOffset=\"%d\">\n", p->TimeOffset); gf_isom_box_dump_done("TimeStampOffsetBox", a, trace); return GF_OK; } -GF_Err ghnt_dump(GF_Box *a, FILE * trace) +GF_Err ghnt_box_dump(GF_Box *a, FILE * trace) { char *name; GF_HintSampleEntryBox *p; @@ -2134,257 +2238,335 @@ GF_Err ghnt_dump(GF_Box *a, FILE * trace) name = "GenericHintSampleEntryBox"; } gf_isom_box_dump_start(a, name, trace); - fprintf(trace, "DataReferenceIndex=\"%d\" HintTrackVersion=\"%d\" LastCompatibleVersion=\"%d\"", p->dataReferenceIndex, p->HintTrackVersion, p->LastCompatibleVersion); + gf_fprintf(trace, "DataReferenceIndex=\"%d\" HintTrackVersion=\"%d\" LastCompatibleVersion=\"%d\"", p->dataReferenceIndex, p->HintTrackVersion, p->LastCompatibleVersion); if ((a->type == GF_ISOM_BOX_TYPE_RTP_STSD) || (a->type == GF_ISOM_BOX_TYPE_SRTP_STSD) || (a->type == GF_ISOM_BOX_TYPE_RRTP_STSD) || (a->type == GF_ISOM_BOX_TYPE_RTCP_STSD)) { - fprintf(trace, " MaxPacketSize=\"%d\"", p->MaxPacketSize); + gf_fprintf(trace, " MaxPacketSize=\"%d\"", p->MaxPacketSize); } else if (a->type == GF_ISOM_BOX_TYPE_FDP_STSD) { - fprintf(trace, " partition_entry_ID=\"%d\" FEC_overhead=\"%d\"", p->partition_entry_ID, p->FEC_overhead); + gf_fprintf(trace, " partition_entry_ID=\"%d\" FEC_overhead=\"%d\"", p->partition_entry_ID, p->FEC_overhead); } - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done(name, a, trace); return GF_OK; } -GF_Err hnti_dump(GF_Box *a, FILE * trace) +GF_Err hnti_box_dump(GF_Box *a, FILE * trace) { gf_isom_box_dump_start(a, "HintTrackInfoBox", trace); - fprintf(trace, ">\n"); - gf_isom_box_dump_done("HintTrackInfoBox", NULL, trace); + gf_fprintf(trace, ">\n"); + gf_isom_box_dump_done("HintTrackInfoBox", a, trace); return GF_OK; } -GF_Err sdp_dump(GF_Box *a, FILE * trace) +GF_Err sdp_box_dump(GF_Box *a, FILE * trace) { GF_SDPBox *p = (GF_SDPBox *)a; gf_isom_box_dump_start(a, "SDPBox", trace); - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); if (p->sdpText) - fprintf(trace, "\n", p->sdpText); + gf_fprintf(trace, "\n", p->sdpText); gf_isom_box_dump_done("SDPBox", a, trace); return GF_OK; } -GF_Err rtp_hnti_dump(GF_Box *a, FILE * trace) +GF_Err rtp_hnti_box_dump(GF_Box *a, FILE * trace) { GF_RTPBox *p = (GF_RTPBox *)a; gf_isom_box_dump_start(a, "RTPMovieHintInformationBox", trace); - fprintf(trace, "descriptionformat=\"%s\">\n", gf_4cc_to_str(p->subType)); + gf_fprintf(trace, "descriptionformat=\"%s\">\n", gf_4cc_to_str(p->subType)); if (p->sdpText) - fprintf(trace, "\n", p->sdpText); + gf_fprintf(trace, "\n", p->sdpText); gf_isom_box_dump_done("RTPMovieHintInformationBox", a, trace); return GF_OK; } -GF_Err rtpo_dump(GF_Box *a, FILE * trace) +GF_Err rtpo_box_dump(GF_Box *a, FILE * trace) { GF_RTPOBox *p; p = (GF_RTPOBox *)a; gf_isom_box_dump_start(a, "RTPTimeOffsetBox", trace); - fprintf(trace, "PacketTimeOffset=\"%d\">\n", p->timeOffset); + gf_fprintf(trace, "PacketTimeOffset=\"%d\">\n", p->timeOffset); gf_isom_box_dump_done("RTPTimeOffsetBox", a, trace); return GF_OK; } #ifndef GPAC_DISABLE_ISOM_FRAGMENTS -GF_Err mvex_dump(GF_Box *a, FILE * trace) +GF_Err mvex_box_dump(GF_Box *a, FILE * trace) { - GF_MovieExtendsBox *p; - p = (GF_MovieExtendsBox *)a; gf_isom_box_dump_start(a, "MovieExtendsBox", trace); - fprintf(trace, ">\n"); - if (p->mehd) gf_isom_box_dump(p->mehd, trace); - gf_isom_box_array_dump(p->TrackExList, trace); - gf_isom_box_array_dump(p->TrackExPropList, trace); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("MovieExtendsBox", a, trace); return GF_OK; } -GF_Err mehd_dump(GF_Box *a, FILE * trace) +GF_Err mehd_box_dump(GF_Box *a, FILE * trace) { GF_MovieExtendsHeaderBox *p = (GF_MovieExtendsHeaderBox*)a; gf_isom_box_dump_start(a, "MovieExtendsHeaderBox", trace); - fprintf(trace, "fragmentDuration=\""LLD"\" >\n", LLD_CAST p->fragment_duration); + gf_fprintf(trace, "fragmentDuration=\""LLD"\" >\n", p->fragment_duration); gf_isom_box_dump_done("MovieExtendsHeaderBox", a, trace); return GF_OK; } void sample_flags_dump(const char *name, u32 sample_flags, FILE * trace) { - fprintf(trace, "<%s", name); - fprintf(trace, " IsLeading=\"%d\"", GF_ISOM_GET_FRAG_LEAD(sample_flags) ); - fprintf(trace, " SampleDependsOn=\"%d\"", GF_ISOM_GET_FRAG_DEPENDS(sample_flags) ); - fprintf(trace, " SampleIsDependedOn=\"%d\"", GF_ISOM_GET_FRAG_DEPENDED(sample_flags) ); - fprintf(trace, " SampleHasRedundancy=\"%d\"", GF_ISOM_GET_FRAG_REDUNDANT(sample_flags) ); - fprintf(trace, " SamplePadding=\"%d\"", GF_ISOM_GET_FRAG_PAD(sample_flags) ); - fprintf(trace, " SampleSync=\"%d\"", GF_ISOM_GET_FRAG_SYNC(sample_flags)); - fprintf(trace, " SampleDegradationPriority=\"%d\"", GF_ISOM_GET_FRAG_DEG(sample_flags)); - fprintf(trace, "/>\n"); + gf_fprintf(trace, "<%s", name); + gf_fprintf(trace, " IsLeading=\"%d\"", GF_ISOM_GET_FRAG_LEAD(sample_flags) ); + gf_fprintf(trace, " SampleDependsOn=\"%d\"", GF_ISOM_GET_FRAG_DEPENDS(sample_flags) ); + gf_fprintf(trace, " SampleIsDependedOn=\"%d\"", GF_ISOM_GET_FRAG_DEPENDED(sample_flags) ); + gf_fprintf(trace, " SampleHasRedundancy=\"%d\"", GF_ISOM_GET_FRAG_REDUNDANT(sample_flags) ); + gf_fprintf(trace, " SamplePadding=\"%d\"", GF_ISOM_GET_FRAG_PAD(sample_flags) ); + gf_fprintf(trace, " SampleSync=\"%d\"", GF_ISOM_GET_FRAG_SYNC(sample_flags)); + gf_fprintf(trace, " SampleDegradationPriority=\"%d\"", GF_ISOM_GET_FRAG_DEG(sample_flags)); + gf_fprintf(trace, "/>\n"); } -GF_Err trex_dump(GF_Box *a, FILE * trace) +GF_Err trex_box_dump(GF_Box *a, FILE * trace) { GF_TrackExtendsBox *p; p = (GF_TrackExtendsBox *)a; gf_isom_box_dump_start(a, "TrackExtendsBox", trace); - fprintf(trace, "TrackID=\"%d\"", p->trackID); - fprintf(trace, " SampleDescriptionIndex=\"%d\" SampleDuration=\"%d\" SampleSize=\"%d\"", p->def_sample_desc_index, p->def_sample_duration, p->def_sample_size); - fprintf(trace, ">\n"); + gf_fprintf(trace, "TrackID=\"%d\"", p->trackID); + gf_fprintf(trace, " SampleDescriptionIndex=\"%d\" SampleDuration=\"%d\" SampleSize=\"%d\"", p->def_sample_desc_index, p->def_sample_duration, p->def_sample_size); + gf_fprintf(trace, ">\n"); sample_flags_dump("DefaultSampleFlags", p->def_sample_flags, trace); gf_isom_box_dump_done("TrackExtendsBox", a, trace); return GF_OK; } -GF_Err trep_dump(GF_Box *a, FILE * trace) +GF_Err trep_box_dump(GF_Box *a, FILE * trace) { GF_TrackExtensionPropertiesBox *p = (GF_TrackExtensionPropertiesBox*)a; gf_isom_box_dump_start(a, "TrackExtensionPropertiesBox", trace); - fprintf(trace, "TrackID=\"%d\">\n", p->trackID); + gf_fprintf(trace, "TrackID=\"%d\">\n", p->trackID); gf_isom_box_dump_done("TrackExtensionPropertiesBox", a, trace); return GF_OK; } -GF_Err moof_dump(GF_Box *a, FILE * trace) +GF_Err moof_box_dump(GF_Box *a, FILE * trace) { GF_MovieFragmentBox *p; p = (GF_MovieFragmentBox *)a; gf_isom_box_dump_start(a, "MovieFragmentBox", trace); - fprintf(trace, "TrackFragments=\"%d\">\n", gf_list_count(p->TrackList)); - if (p->mfhd) gf_isom_box_dump(p->mfhd, trace); - gf_isom_box_array_dump(p->TrackList, trace); + gf_fprintf(trace, "TrackFragments=\"%d\"", gf_list_count(p->TrackList)); + if (p->compressed_diff) + gf_fprintf(trace, " compressedSize=\""LLU"\"", p->size - p->compressed_diff); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("MovieFragmentBox", a, trace); return GF_OK; } -GF_Err mfhd_dump(GF_Box *a, FILE * trace) +GF_Err mfhd_box_dump(GF_Box *a, FILE * trace) { GF_MovieFragmentHeaderBox *p; p = (GF_MovieFragmentHeaderBox *)a; gf_isom_box_dump_start(a, "MovieFragmentHeaderBox", trace); - fprintf(trace, "FragmentSequenceNumber=\"%d\">\n", p->sequence_number); + gf_fprintf(trace, "FragmentSequenceNumber=\"%d\">\n", p->sequence_number); gf_isom_box_dump_done("MovieFragmentHeaderBox", a, trace); return GF_OK; } -GF_Err traf_dump(GF_Box *a, FILE * trace) +GF_Err traf_box_dump(GF_Box *a, FILE * trace) { - GF_TrackFragmentBox *p; - p = (GF_TrackFragmentBox *)a; gf_isom_box_dump_start(a, "TrackFragmentBox", trace); - fprintf(trace, ">\n"); - if (p->tfhd) gf_isom_box_dump(p->tfhd, trace); - if (p->sdtp) gf_isom_box_dump(p->sdtp, trace); - if (p->tfdt) gf_isom_box_dump(p->tfdt, trace); - if (p->sub_samples) gf_isom_box_array_dump(p->sub_samples, trace); - if (p->sampleGroupsDescription) gf_isom_box_array_dump(p->sampleGroupsDescription, trace); - if (p->sampleGroups) gf_isom_box_array_dump(p->sampleGroups, trace); - gf_isom_box_array_dump(p->TrackRuns, trace); - if (p->sai_sizes) gf_isom_box_array_dump(p->sai_sizes, trace); - if (p->sai_offsets) gf_isom_box_array_dump(p->sai_offsets, trace); - if (p->sample_encryption) gf_isom_box_dump(p->sample_encryption, trace); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("TrackFragmentBox", a, trace); return GF_OK; } -static void frag_dump_sample_flags(FILE * trace, u32 flags) +static void frag_dump_sample_flags(FILE * trace, u32 flags, u32 field_idx) { - fprintf(trace, " SamplePadding=\"%d\" Sync=\"%d\" DegradationPriority=\"%d\" IsLeading=\"%d\" DependsOn=\"%d\" IsDependedOn=\"%d\" HasRedundancy=\"%d\"", + if (!field_idx) return; + if (field_idx==1) { + gf_fprintf(trace, " IsLeading=\"%d\" DependsOn=\"%d\"", GF_ISOM_GET_FRAG_LEAD(flags), GF_ISOM_GET_FRAG_DEPENDS(flags)); + } else if (field_idx==2) { + gf_fprintf(trace, " IsLeading=\"%d\" DependsOn=\"%d\" IsDependedOn=\"%d\" HasRedundancy=\"%d\" SamplePadding=\"%d\" Sync=\"%d\"", + GF_ISOM_GET_FRAG_LEAD(flags), GF_ISOM_GET_FRAG_DEPENDS(flags), GF_ISOM_GET_FRAG_DEPENDED(flags), GF_ISOM_GET_FRAG_REDUNDANT(flags), GF_ISOM_GET_FRAG_PAD(flags), GF_ISOM_GET_FRAG_SYNC(flags)); + } else { + gf_fprintf(trace, " SamplePadding=\"%d\" Sync=\"%d\" DegradationPriority=\"%d\" IsLeading=\"%d\" DependsOn=\"%d\" IsDependedOn=\"%d\" HasRedundancy=\"%d\"", GF_ISOM_GET_FRAG_PAD(flags), GF_ISOM_GET_FRAG_SYNC(flags), GF_ISOM_GET_FRAG_DEG(flags), GF_ISOM_GET_FRAG_LEAD(flags), GF_ISOM_GET_FRAG_DEPENDS(flags), GF_ISOM_GET_FRAG_DEPENDED(flags), GF_ISOM_GET_FRAG_REDUNDANT(flags)); + } } -GF_Err tfhd_dump(GF_Box *a, FILE * trace) +GF_Err tfhd_box_dump(GF_Box *a, FILE * trace) { GF_TrackFragmentHeaderBox *p; p = (GF_TrackFragmentHeaderBox *)a; gf_isom_box_dump_start(a, "TrackFragmentHeaderBox", trace); - fprintf(trace, "TrackID=\"%u\"", p->trackID); + gf_fprintf(trace, "TrackID=\"%u\"", p->trackID); if (p->flags & GF_ISOM_TRAF_BASE_OFFSET) { - fprintf(trace, " BaseDataOffset=\""LLU"\"", p->base_data_offset); + gf_fprintf(trace, " BaseDataOffset=\""LLU"\"", p->base_data_offset); } else { - fprintf(trace, " BaseDataOffset=\"%s\"", (p->flags & GF_ISOM_MOOF_BASE_OFFSET) ? "moof" : "moof-or-previous-traf"); + gf_fprintf(trace, " BaseDataOffset=\"%s\"", (p->flags & GF_ISOM_MOOF_BASE_OFFSET) ? "moof" : "moof-or-previous-traf"); } if (p->flags & GF_ISOM_TRAF_SAMPLE_DESC) - fprintf(trace, " SampleDescriptionIndex=\"%u\"", p->sample_desc_index); + gf_fprintf(trace, " SampleDescriptionIndex=\"%u\"", p->sample_desc_index); if (p->flags & GF_ISOM_TRAF_SAMPLE_DUR) - fprintf(trace, " SampleDuration=\"%u\"", p->def_sample_duration); + gf_fprintf(trace, " SampleDuration=\"%u\"", p->def_sample_duration); if (p->flags & GF_ISOM_TRAF_SAMPLE_SIZE) - fprintf(trace, " SampleSize=\"%u\"", p->def_sample_size); + gf_fprintf(trace, " SampleSize=\"%u\"", p->def_sample_size); if (p->flags & GF_ISOM_TRAF_SAMPLE_FLAGS) { - frag_dump_sample_flags(trace, p->def_sample_flags); + frag_dump_sample_flags(trace, p->def_sample_flags, 3); } - fprintf(trace, ">\n"); + gf_fprintf(trace, ">\n"); gf_isom_box_dump_done("TrackFragmentHeaderBox", a, trace); return GF_OK; } -GF_Err tfxd_dump(GF_Box *a, FILE * trace) +GF_Err tfxd_box_dump(GF_Box *a, FILE * trace) { GF_MSSTimeExtBox *ptr = (GF_MSSTimeExtBox*)a; if (!a) return GF_BAD_PARAM; gf_isom_box_dump_start(a, "MSSTimeExtensionBox", trace); - fprintf(trace, "AbsoluteTime=\""LLU"\" FragmentDuration=\""LLU"\">\n", ptr->absolute_time_in_track_timescale, ptr->fragment_duration_in_track_timescale); - fprintf(trace, "\n", ptr->version, ptr->flags); + gf_fprintf(trace, "AbsoluteTime=\""LLU"\" FragmentDuration=\""LLU"\">\n", ptr->absolute_time_in_track_timescale, ptr->fragment_duration_in_track_timescale); + gf_fprintf(trace, "\n", ptr->version, ptr->flags); gf_isom_box_dump_done("MSSTimeExtensionBox", a, trace); return GF_OK; } -GF_Err trun_dump(GF_Box *a, FILE * trace) +GF_Err trun_box_dump(GF_Box *a, FILE * trace) { - u32 i; - GF_TrunEntry *ent; + u32 i, flags; + Bool full_dump = GF_FALSE; GF_TrackFragmentRunBox *p; p = (GF_TrackFragmentRunBox *)a; + flags = p->flags; +#ifdef GF_ENABLE_CTRN + if (p->use_ctrn) { + p->flags = p->ctrn_flags; + p->type = GF_ISOM_BOX_TYPE_CTRN; + } + gf_isom_box_dump_start(a, p->use_ctrn ? "CompactTrackRunBox" : "TrackRunBox", trace); +#else gf_isom_box_dump_start(a, "TrackRunBox", trace); - fprintf(trace, "SampleCount=\"%d\"", p->sample_count); +#endif + p->flags = flags; + p->type = GF_ISOM_BOX_TYPE_TRUN; + gf_fprintf(trace, "SampleCount=\"%d\"", p->sample_count); if (p->flags & GF_ISOM_TRUN_DATA_OFFSET) - fprintf(trace, " DataOffset=\"%d\"", p->data_offset); - fprintf(trace, ">\n"); + gf_fprintf(trace, " DataOffset=\"%d\"", p->data_offset); + +#ifdef GF_ENABLE_CTRN + if (p->use_ctrn) { + if (p->ctrn_flags & GF_ISOM_CTRN_DATAOFFSET_16) + gf_fprintf(trace, " dataOffset16Bits=\"yes\""); + + if (p->ctso_multiplier) + gf_fprintf(trace, " ctsoMultiplier=\"%d\"", p->ctso_multiplier); + + if (p->ctrn_flags & GF_ISOM_CTRN_INHERIT_DUR) + gf_fprintf(trace, " inheritDuration=\"yes\""); + if (p->ctrn_flags & GF_ISOM_CTRN_INHERIT_SIZE) + gf_fprintf(trace, " inheritSize=\"yes\""); + if (p->ctrn_flags & GF_ISOM_CTRN_INHERIT_FLAGS) + gf_fprintf(trace, " inheritFlags=\"yes\""); + if (p->ctrn_flags & GF_ISOM_CTRN_INHERIT_CTSO) + gf_fprintf(trace, " inheritCTSOffset=\"yes\""); + + gf_fprintf(trace, " firstSampleDurationBits=\"%d\"", gf_isom_ctrn_field_size_bits(p->ctrn_first_dur)); + gf_fprintf(trace, " firstSampleSizeBits=\"%d\"", gf_isom_ctrn_field_size_bits(p->ctrn_first_size)); + gf_fprintf(trace, " firstSampleFlagsBits=\"%d\"", gf_isom_ctrn_field_size_bits(p->ctrn_first_sample_flags)); + gf_fprintf(trace, " firstSampleCTSOBits=\"%d\"", gf_isom_ctrn_field_size_bits(p->ctrn_first_ctts)); + gf_fprintf(trace, " sampleDurationBits=\"%d\"", gf_isom_ctrn_field_size_bits(p->ctrn_dur)); + gf_fprintf(trace, " sampleSizeBits=\"%d\"", gf_isom_ctrn_field_size_bits(p->ctrn_size)); + gf_fprintf(trace, " sampleFlagsBits=\"%d\"", gf_isom_ctrn_field_size_bits(p->ctrn_sample_flags)); + gf_fprintf(trace, " sampleCTSOBits=\"%d\"", gf_isom_ctrn_field_size_bits(p->ctrn_ctts)); + if (p->ctrn_flags & 0x00FFFF00) + full_dump = GF_TRUE; + + gf_fprintf(trace, ">\n"); + } else +#endif + { + gf_fprintf(trace, ">\n"); - if (p->flags & GF_ISOM_TRUN_FIRST_FLAG) { - sample_flags_dump("FirstSampleFlags", p->first_sample_flags, trace); + if (p->flags & GF_ISOM_TRUN_FIRST_FLAG) { + sample_flags_dump("FirstSampleFlags", p->first_sample_flags, trace); + } + if (p->flags & (GF_ISOM_TRUN_DURATION|GF_ISOM_TRUN_SIZE|GF_ISOM_TRUN_CTS_OFFSET|GF_ISOM_TRUN_FLAGS)) { + full_dump = GF_TRUE; + } } - if (p->flags & (GF_ISOM_TRUN_DURATION|GF_ISOM_TRUN_SIZE|GF_ISOM_TRUN_CTS_OFFSET|GF_ISOM_TRUN_FLAGS)) { - i=0; - while ((ent = (GF_TrunEntry *)gf_list_enum(p->entries, &i))) { - - fprintf(trace, "flags & GF_ISOM_TRUN_DURATION) - fprintf(trace, " Duration=\"%u\"", ent->Duration); - if (p->flags & GF_ISOM_TRUN_SIZE) - fprintf(trace, " Size=\"%u\"", ent->size); - if (p->flags & GF_ISOM_TRUN_CTS_OFFSET) + if (full_dump) { + for (i=0; inb_samples; i++) { + GF_TrunEntry *ent = &p->samples[i]; + + gf_fprintf(trace, "use_ctrn) { + if ((i==1) && (p->ctrn_flags&GF_ISOM_CTRN_FIRST_SAMPLE) ) { + if (p->ctrn_first_dur) + gf_fprintf(trace, " Duration=\"%u\"", ent->Duration); + if (p->ctrn_first_size) + gf_fprintf(trace, " Size=\"%u\"", ent->size); + if (p->ctrn_first_ctts) { + if (p->version == 0) + gf_fprintf(trace, " CTSOffset=\"%u\"", (u32) ent->CTS_Offset); + else + gf_fprintf(trace, " CTSOffset=\"%d\"", ent->CTS_Offset); + } + if (p->ctrn_first_sample_flags) + frag_dump_sample_flags(trace, ent->flags, p->ctrn_first_sample_flags); + } else { + if (p->ctrn_dur) + gf_fprintf(trace, " Duration=\"%u\"", ent->Duration); + if (p->ctrn_size) + gf_fprintf(trace, " Size=\"%u\"", ent->size); + if (p->ctrn_ctts) { + if (p->version == 0) + gf_fprintf(trace, " CTSOffset=\"%u\"", (u32) ent->CTS_Offset); + else + gf_fprintf(trace, " CTSOffset=\"%d\"", ent->CTS_Offset); + } + if (p->ctrn_sample_flags) + frag_dump_sample_flags(trace, ent->flags, p->ctrn_sample_flags); + } + } else +#endif { - if (p->version == 0) - fprintf(trace, " CTSOffset=\"%u\"", (u32) ent->CTS_Offset); - else - fprintf(trace, " CTSOffset=\"%d\"", ent->CTS_Offset); - } - if (p->flags & GF_ISOM_TRUN_FLAGS) { - frag_dump_sample_flags(trace, ent->flags); + if (p->flags & GF_ISOM_TRUN_DURATION) + gf_fprintf(trace, " Duration=\"%u\"", ent->Duration); + if (p->flags & GF_ISOM_TRUN_SIZE) + gf_fprintf(trace, " Size=\"%u\"", ent->size); + if (p->flags & GF_ISOM_TRUN_CTS_OFFSET) + { + if (p->version == 0) + gf_fprintf(trace, " CTSOffset=\"%u\"", (u32) ent->CTS_Offset); + else + gf_fprintf(trace, " CTSOffset=\"%d\"", ent->CTS_Offset); + } + + if (p->flags & GF_ISOM_TRUN_FLAGS) { + frag_dump_sample_flags(trace, ent->flags, 3); + } } - fprintf(trace, "/>\n"); + gf_fprintf(trace, "/>\n"); } } else if (p->size) { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } else { - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } +#ifdef GF_ENABLE_CTRN + gf_isom_box_dump_done(p->use_ctrn ? "CompactTrackRunBox" : "TrackRunBox", a, trace); +#else gf_isom_box_dump_done("TrackRunBox", a, trace); +#endif return GF_OK; } @@ -2394,35 +2576,35 @@ GF_Err trun_dump(GF_Box *a, FILE * trace) GF_Err DTE_Dump(GF_List *dte, FILE * trace) { - GF_GenericDTE *p; - GF_ImmediateDTE *i_p; - GF_SampleDTE *s_p; - GF_StreamDescDTE *sd_p; u32 i, count; count = gf_list_count(dte); for (i=0; isource) { case 0: - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); break; case 1: i_p = (GF_ImmediateDTE *) p; - fprintf(trace, "\n", i_p->dataLength); + gf_fprintf(trace, "\n", i_p->dataLength); break; case 2: s_p = (GF_SampleDTE *) p; - fprintf(trace, "\n", + gf_fprintf(trace, "\n", s_p->dataLength, s_p->byteOffset, s_p->sampleNumber, s_p->trackRefIndex); break; case 3: sd_p = (GF_StreamDescDTE *) p; - fprintf(trace, "\n", + gf_fprintf(trace, "\n", sd_p->dataLength, sd_p->byteOffset, sd_p->streamDescIndex, sd_p->trackRefIndex); break; default: - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); break; } } @@ -2483,7 +2665,7 @@ GF_Err gf_isom_dump_hint_sample(GF_ISOFile *the_file, u32 trackNumber, u32 Sampl count = gf_list_count(s->packetTable); - fprintf(trace, "<%sHintSample SampleNumber=\"%d\" DecodingTime=\""LLD"\" RandomAccessPoint=\"%d\" PacketCount=\"%u\" reserved=\"%u\">\n", szName, SampleNum, LLD_CAST tmp->DTS, tmp->IsRAP, s->packetCount, s->reserved); + gf_fprintf(trace, "<%sHintSample SampleNumber=\"%d\" DecodingTime=\""LLD"\" RandomAccessPoint=\"%d\" PacketCount=\"%u\" reserved=\"%u\">\n", szName, SampleNum, tmp->DTS, tmp->IsRAP, s->packetCount, s->reserved); if (s->hint_subtype==GF_ISOM_BOX_TYPE_FDP_STSD) { e = gf_isom_box_dump((GF_Box*) s, trace); @@ -2491,7 +2673,7 @@ GF_Err gf_isom_dump_hint_sample(GF_ISOFile *the_file, u32 trackNumber, u32 Sampl } if (s->packetCount != count) { - fprintf(trace, "\n", szName, s->packetCount, count); + gf_fprintf(trace, "\n", szName, s->packetCount, count); } @@ -2500,41 +2682,40 @@ GF_Err gf_isom_dump_hint_sample(GF_ISOFile *the_file, u32 trackNumber, u32 Sampl if (pck->hint_subtype==GF_ISOM_BOX_TYPE_RTCP_STSD) { GF_RTCPPacket *rtcp_pck = (GF_RTCPPacket *) pck; - fprintf(trace, "Version, rtcp_pck->Padding, rtcp_pck->Count, rtcp_pck->PayloadType); if (rtcp_pck->data) dump_data_attribute(trace, "payload", (char*)rtcp_pck->data, rtcp_pck->length); - fprintf(trace, ">\n"); - fprintf(trace, "\n"); + gf_fprintf(trace, ">\n"); + gf_fprintf(trace, "\n"); } else { - fprintf(trace, "P_bit, pck->X_bit, pck->M_bit, pck->payloadType); - fprintf(trace, " SequenceNumber=\"%d\" RepeatedPacket=\"%d\" DropablePacket=\"%d\" RelativeTransmissionTime=\"%d\" FullPacketSize=\"%d\">\n", + gf_fprintf(trace, " SequenceNumber=\"%d\" RepeatedPacket=\"%d\" DropablePacket=\"%d\" RelativeTransmissionTime=\"%d\" FullPacketSize=\"%d\">\n", pck->SequenceNumber, pck->R_bit, pck->B_bit, pck->relativeTransTime, gf_isom_hint_rtp_length(pck)); //TLV is made of Boxes count2 = gf_list_count(pck->TLV); if (count2) { - fprintf(trace, "\n", count2); - gf_isom_box_array_dump(pck->TLV, trace); - fprintf(trace, "\n"); + gf_fprintf(trace, "\n", count2); + gf_fprintf(trace, "\n"); } //DTE is made of NON boxes count2 = gf_list_count(pck->DataTable); if (count2) { - fprintf(trace, "\n", count2); + gf_fprintf(trace, "\n", count2); DTE_Dump(pck->DataTable, trace); - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } - fprintf(trace, "\n"); + gf_fprintf(trace, "\n"); } } err_exit: - fprintf(trace, "\n", szName); + gf_fprintf(trace, "\n", szName); gf_isom_sample_del(&tmp); gf_isom_hint_sample_del(s); return e; @@ -2544,7 +2725,7 @@ err_exit: static void tx3g_dump_box_nobox(FILE * trace, GF_BoxRecord *rec) { - fprintf(trace, "\n", rec->top, rec->left, rec->bottom, rec->right); + gf_fprintf(trace, "\n", rec->top, rec->left, rec->bottom, rec->right); } static void tx3g_print_char_offsets(FILE * trace, u32 start, u32 end, u32 *shift_offset, u32 so_count) @@ -2564,26 +2745,26 @@ static void tx3g_print_char_offsets(FILE * trace, u32 start, u32 end, u32 *shift } } } - if (start || end) fprintf(trace, "fromChar=\"%d\" toChar=\"%d\" ", start, end); + if (start || end) gf_fprintf(trace, "fromChar=\"%d\" toChar=\"%d\" ", start, end); } static void tx3g_dump_style_nobox(FILE * trace, GF_StyleRecord *rec, u32 *shift_offset, u32 so_count) { - fprintf(trace, "